[
  {
    "path": ".all_crates.sh",
    "content": "\nALL_CRATES_PATH=\"data linalg core nnef nnef/nnef-resources pulse-opl pulse extra transformers hir tflite tensorflow onnx-opl onnx gpu metal cuda libcli api api/rs api/ffi api/proxy/sys api/proxy cli\"\n"
  },
  {
    "path": ".change_crate_dep.sh",
    "content": "#!/bin/bash\n\ncrate=$1\nversion=$2\n\nperl -pi -e \"s/^($crate = {.*version *= *)\\\"([^\\\"]*)\\\"(.*)$/\\$1\\\"=$version\\\"\\$3/\" \\\n    `find . -name Cargo.toml \\! -path \"./target/*\" \\! -path \"./issue*\"`\n"
  },
  {
    "path": ".clang-format",
    "content": "BasedOnStyle: LLVM\nIndentWidth: 4\nTabWidth: 4\nUseTab: Never\nIndentPPDirectives: BeforeHash\nPPIndentWidth: 4\nColumnLimit: 100\n\n# OneLineFormatOffRegex: '^\\s*#\\s*pragma\\s+unroll\\b'\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n    groups:\n      actions:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: \"cargo\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n    open-pull-requests-limit: 10\n    groups:\n      rust-dependencies:\n        patterns:\n          - \"*\"\n\n  - package-ecosystem: \"pip\"\n    directory: \"/api/py\"\n    ignore:\n      # Only update them manually since updating them might break compatibility\n      - dependency-name: \"numpy\"\n    schedule:\n      interval: \"weekly\"\n      day: \"monday\"\n"
  },
  {
    "path": ".github/workflows/asan.yml",
    "content": "name: Sanitized build tests\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron:  '0 5 * * MON'\n\nenv:\n  CARGO_INCREMENTAL: false\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n\njobs:\n  sanitizer-address:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ ubuntu-latest, macOS-latest ]\n\n    runs-on: ${{matrix.os}}\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Rustup update\n      run: rustup update\n    - name: Run sanitized tests\n      run: .travis/asan.sh\n"
  },
  {
    "path": ".github/workflows/binaries.yml",
    "content": "on:\n  release:\n    types:\n      - created\n\nname: Upload Release Binaries\n\nenv:\n  CARGO_INCREMENTAL: false\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n\njobs:\n  assets:\n    name: Upload Release Binaries\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ ubuntu-latest, macOS-latest ]\n        arch: [ x86_64, aarch64, armv7 ]\n        include:\n          - os: ubuntu-latest\n            arch: x86_64\n            target: x86_64-unknown-linux-musl\n            musl: x86_64-linux-musl\n          - os: ubuntu-latest\n            arch: aarch64\n            target: aarch64-unknown-linux-musl\n            musl: aarch64-linux-musl\n          - os: ubuntu-latest\n            arch: armv7\n            target: armv7-unknown-linux-musleabihf\n            musl: armv7l-linux-musleabihf\n          - os: macOS-latest\n            arch: x86_64\n            target: x86_64-apple-darwin\n          - os: macOS-latest\n            arch: aarch64\n            target: aarch64-apple-darwin\n        exclude:\n          - os: macOS-latest\n            arch: armv7\n\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Extract version tag\n        id: version\n        run: echo value=$(echo ${{ github.ref }} | cut -f 3 -d / | sed 's/^v//' ) >> $GITHUB_OUTPUT\n\n      - name: Build tract\n        run: |\n          set -ex\n          target=${{matrix.target}}\n          version=${{steps.version.outputs.value}}\n          name=${target}-${version}\n\n          rustup update\n          rustup target add ${target}\n\n          if [ -n \"${{matrix.musl}}\" ]\n          then\n            MUSL_TRIPLE=${{matrix.musl}}\n            curl -s https://s3.amazonaws.com/tract-ci-builds/toolchains/${MUSL_TRIPLE}-cross.tgz | tar zx\n\n            MUSL_BIN=`pwd`/${MUSL_TRIPLE}-cross/bin\n            export PATH=$MUSL_BIN:$PATH\n\n            export TARGET_CC=$MUSL_BIN/${MUSL_TRIPLE}-gcc\n\n            RUST_TRIPLE_ENV=$(echo ${target} | tr 'a-z-' 'A-Z_')\n            export CARGO_TARGET_${RUST_TRIPLE_ENV}_CC=$TARGET_CC\n            export CARGO_TARGET_${RUST_TRIPLE_ENV}_LINKER=$TARGET_CC\n          fi\n\n          cargo build --target ${target} --release -p tract-cli\n          mkdir tract-$name\n          cp target/${target}/release/tract tract-${name}\n          tar czf tract-${name}.tgz tract-${name}\n\n      - name: Upload asset\n        uses: softprops/action-gh-release@v2\n        with:\n          files: tract-${{matrix.target}}-${{ steps.version.outputs.value }}.tgz\n          name: ${{ steps.version.outputs.value }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n"
  },
  {
    "path": ".github/workflows/cost_model.yml",
    "content": "\non:\n  workflow_dispatch:\n    inputs:\n      dataset_id:\n        description: 'dataset identifier'\n        required: true\n\nname: Generate cost model analysis dataset\n\nenv:\n  CARGO_INCREMENTAL: false\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n\njobs:\n  build:\n    name: Upload cost model tasks\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        target: [ \"aarch64\", \"armv7\" ]\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Build and upload\n        run: ./.travis/cost_model_task_build.sh ${{matrix.target}} ${{github.event.inputs.dataset_id}}\n        env:\n          AWS_ACCESS_KEY_ID: ${{secrets.TRACT_CI_AWS_ACCESS_KEY_ID}}\n          AWS_SECRET_ACCESS_KEY: ${{secrets.TRACT_CI_AWS_SECRET_ACCESS_KEY}}\n          AWS_EC2_METADATA_DISABLED: true\n"
  },
  {
    "path": ".github/workflows/crates.yml",
    "content": "name: Rust crates\n\non:\n  pull_request:\n  schedule:\n    - cron:  '0 3 * * *'\n  workflow_dispatch:\n\nenv:\n  CARGO_INCREMENTAL: false\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n\njobs:\n  prepare-matrix:\n    runs-on: ubuntu-latest\n    outputs:\n      os: ${{steps.set-matrix.outputs.os}}\n      rust: ${{steps.set-matrix.outputs.rust}}\n\n    steps:\n      - id: set-matrix\n        env:\n          FULL: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}\n        run: |\n          if [ \"$FULL\" == \"true\" ]\n          then\n            echo 'os=[\"ubuntu-latest\", \"macos-latest\"]' >> $GITHUB_OUTPUT\n            echo 'rust=[\"1.91.0\", \"stable\", \"beta\", \"nightly\"]' >> $GITHUB_OUTPUT\n          else\n            echo ::notice::Skipping macOS checks on PR and commit. Dispatch workflow manually if needed.\n            echo 'os=[\"ubuntu-latest\"]' >> $GITHUB_OUTPUT\n            echo 'rust=[\"1.91.0\"]' >> $GITHUB_OUTPUT\n          fi\n\n  crates:\n    name: ${{matrix.os}} / ${{matrix.crate}} / ${{matrix.rust}}\n    needs: prepare-matrix\n    strategy:\n      matrix:\n        os: ${{fromJson(needs.prepare-matrix.outputs.os)}}\n        rust: ${{fromJson(needs.prepare-matrix.outputs.rust)}}\n        crate: [  tract-data, tract-linalg, tract-core, tract-nnef, tract-hir, tract-onnx,\n                  tract-pulse, tract-onnx-opl, tract-pulse-opl, tract,\n                  test-unit-core, test-onnx-core, test-nnef-cycle, test-f16,\n               ]\n      fail-fast: false\n\n    runs-on: ${{matrix.os}}\n    env:\n      RUSTUP_TOOLCHAIN: ${{matrix.rust}}\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Cargo test\n      run: cargo test -p ${{matrix.crate}}\n\n  cuda:\n    runs-on: cuda-lovelace\n    needs: prepare-matrix\n    strategy:\n      matrix:\n        rust: ${{fromJson(needs.prepare-matrix.outputs.rust)}}\n      fail-fast: false\n    env:\n      RUSTUP_TOOLCHAIN: ${{matrix.rust}}\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Cargo test\n      run: cargo test -p tract-cuda -p test-cuda\n\n  metal:\n    runs-on: macOS\n    needs: prepare-matrix\n    strategy:\n      matrix:\n        rust: ${{fromJson(needs.prepare-matrix.outputs.rust)}}\n      fail-fast: false\n    env:\n      RUSTUP_TOOLCHAIN: ${{matrix.rust}}\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Cargo test\n      run: cargo test -p tract-metal -p test-metal\n   \n  pedantic:\n    name: fmt, clippy, etc (${{matrix.os}} / ${{matrix.rust}})\n    needs: prepare-matrix\n    strategy:\n      matrix:\n        os: ${{fromJson(needs.prepare-matrix.outputs.os)}}\n        rust: ${{fromJson(needs.prepare-matrix.outputs.rust)}}\n      fail-fast: false\n    runs-on: ${{matrix.os}}\n    env:\n      RUSTUP_TOOLCHAIN: ${{matrix.rust}}\n    steps:\n    - uses: actions/checkout@v6\n    - run: rustup component add clippy && cargo clippy\n    - name: fmt\n      run: rustup component add rustfmt && cargo fmt --check\n    - name: Warnings\n      env:\n        RUSTFLAGS: -D warnings\n      run: cargo check\n\n  cargo-deny:\n    strategy:\n      fail-fast: false\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install cargo-deny\n      run: |\n        curl -L https://github.com/EmbarkStudios/cargo-deny/releases/download/$VERSION/cargo-deny-$VERSION-x86_64-unknown-linux-musl.tar.gz \\\n            | tar -zx --strip-components=1 \"cargo-deny-$VERSION-x86_64-unknown-linux-musl/cargo-deny\"\n      env:\n          VERSION: 0.18.9\n    - name: Run cargo-deny\n      run: .travis/cargo-deny-check.sh\n\n\n"
  },
  {
    "path": ".github/workflows/cross-platform.yml",
    "content": "name: Embedded targets\n\non:\n  pull_request:\n  schedule:\n    - cron:  '0 5 * * *'\n  workflow_dispatch:\n\nenv:\n  CARGO_INCREMENTAL: false\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n  RUSTUP_TOOLCHAIN: 1.91.0\n\njobs:\n  linux:\n    strategy:\n      fail-fast: false\n      matrix:\n        platform:\n          - raspbian\n          - aarch64-unknown-linux-gnu\n          - aarch64-unknown-linux-gnu-stretch\n          - armv6vfp-unknown-linux-gnueabihf\n          - armv7-unknown-linux-gnueabihf\n          - armv7-unknown-linux-gnueabihf-stretch\n          - aarch64-unknown-linux-musl\n          - cortexa53-unknown-linux-musl\n          - armv7-unknown-linux-musl\n          - aarch64-linux-android\n          - armv7-linux-androideabi\n          - i686-linux-android\n          - x86_64-linux-android\n          - x86_64-unknown-linux-gnu-stretch\n          - wasm32-unknown-unknown\n          - wasm32-wasi\n\n    runs-on: ubuntu-latest\n    permissions:\n      id-token: write\n      contents: read\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Get current date\n      id: date\n      run: echo \"date=$(date +'%Y-%m-%d')\" >> $GITHUB_OUTPUT\n\n    - name: Configure AWS Credentials\n      continue-on-error: true\n      uses: aws-actions/configure-aws-credentials@v6\n      with:\n        role-to-assume: arn:aws:iam::567805100031:role/github-runner-tract-ci\n        aws-region: us-east-2\n\n    - uses: actions/cache@v5\n      with:\n        path: |\n          ~/.rustup\n          ~/.cargo/registry\n          ~/.cargo/git\n          #          ~/.cache/sccache\n          .cached\n          target\n        key: ${{ runner.os }}-${{matrix.platform}}-${{steps.date.outputs.date}}\n\n    - name: Setup wasmtime\n      if: ${{ matrix.platform }} == \"wasm32-wasi\"\n      uses: bytecodealliance/actions/wasmtime/setup@v1\n\n    - name: Cross script\n      env:\n        PLATFORM: ${{matrix.platform}}\n        AWS_EC2_METADATA_DISABLED: true\n      run: .travis/cross.sh\n\n  apple:\n    strategy:\n      fail-fast: false\n      matrix:\n        platform:\n          - aarch64-apple-ios\n          - aarch64-apple-darwin\n\n    runs-on: macos-latest\n    permissions:\n      id-token: write\n      contents: read\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Configure AWS Credentials\n      continue-on-error: true\n      uses: aws-actions/configure-aws-credentials@v6\n      with:\n        role-to-assume: arn:aws:iam::567805100031:role/github-runner-tract-ci\n        aws-region: us-east-2\n\n    - name: Get current date\n      id: date\n      run: echo \"date=$(date +'%Y-%m-%d')\" >> $GITHUB_OUTPUT\n\n    - name: Cross script\n      env:\n        PLATFORM: ${{matrix.platform}}\n      run: .travis/cross.sh\n"
  },
  {
    "path": ".github/workflows/examples.yml",
    "content": "name: Examples\n\non:\n  schedule:\n    - cron:  '0 3 * * *'\n  workflow_dispatch:\n\nenv:\n  CARGO_INCREMENTAL: false\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n  RUSTUP_TOOLCHAIN: 1.91.0\n\njobs:\n  examples:\n    runs-on: ubuntu-latest\n    outputs:\n      examples: ${{steps.set-matrix.outputs.examples}}\n\n    steps:\n      - uses: actions/checkout@v6\n      - id: set-matrix\n        run: |\n          echo examples=`find examples -name ci.sh | cut -d/ -f 2 | jq -Rsc '. / \"\\n\" - [\"\"]'` >> \"$GITHUB_OUTPUT\"\n\n  example:\n    name: ${{ matrix.ex }}\n    runs-on: ubuntu-latest\n    needs: examples\n    strategy:\n      fail-fast: false\n      matrix:\n        ex: ${{fromJSON(needs.examples.outputs.examples)}}\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Configure AWS Credentials\n      # if: github.repository == 'sonos/tract'\n      continue-on-error: true\n      uses: aws-actions/configure-aws-credentials@v6\n      with:\n        role-to-assume: arn:aws:iam::567805100031:role/github-runner-tract-ci\n        aws-region: us-east-2\n\n    - name: example tests\n      env:\n        AWS_EC2_METADATA_DISABLED: true\n      timeout-minutes: 30\n      run: |\n        cd examples/${{matrix.ex}}\n        ./ci.sh\n\n  build-tract-cli:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - run: cargo build -p tract-cli --profile opt-no-lto\n      - uses: actions/upload-artifact@v7\n        with:\n          name: tract-cli-x86_64\n          path: ./target/opt-no-lto/tract\n\n  build-tract-cli-macos:\n    runs-on: macOS\n    steps:\n      - uses: actions/checkout@v6\n      - run: cargo build -p tract-cli --profile opt-no-lto\n      - uses: actions/upload-artifact@v7\n        with:\n          name: tract-cli-aarch64-apple\n          path: ./target/opt-no-lto/tract\n\n  gpu-examples:\n    runs-on: ubuntu-latest\n    outputs:\n      examples: ${{steps.set-matrix.outputs.examples}}\n\n    steps:\n      - uses: actions/checkout@v6\n      - id: set-matrix\n        run: |\n          echo examples=`find examples -name ci-gpu.sh | cut -d/ -f 2 | jq -Rsc '. / \"\\n\" - [\"\"]'` >> \"$GITHUB_OUTPUT\"\n\n  gpu-example:\n    name: ${{ matrix.ex }} (CUDA)\n    runs-on: cuda-lovelace\n    needs: [gpu-examples, build-tract-cli]\n    strategy:\n      fail-fast: false\n      matrix:\n        ex: ${{fromJSON(needs.gpu-examples.outputs.examples)}}\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - uses: actions/download-artifact@v8\n      with:\n        name: tract-cli-x86_64\n        path: target/opt-no-lto\n\n    - run: chmod +x target/opt-no-lto/tract\n\n    - name: GPU example tests\n      timeout-minutes: 60\n      run: |\n        cd examples/${{matrix.ex}}\n        ./ci-gpu.sh\n\n  gpu-example-metal:\n    name: ${{ matrix.ex }} (Metal)\n    runs-on: macOS\n    needs: [gpu-examples, build-tract-cli-macos]\n    strategy:\n      fail-fast: false\n      matrix:\n        ex: ${{fromJSON(needs.gpu-examples.outputs.examples)}}\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - uses: actions/download-artifact@v8\n      with:\n        name: tract-cli-aarch64-apple\n        path: target/opt-no-lto\n\n    - run: chmod +x target/opt-no-lto/tract\n\n    - name: Metal GPU example tests\n      timeout-minutes: 60\n      run: |\n        cd examples/${{matrix.ex}}\n        ./ci-gpu.sh\n"
  },
  {
    "path": ".github/workflows/full.yml",
    "content": "name: Full test harness\n\non:\n  schedule:\n    - cron:  '0 3 * * *'\n  workflow_dispatch:\n    inputs:\n      pr_number:\n        description: \"Optional PR number to test (from fork ok). Leave empty to run on selected branch.\"\n        required: false\n        type: number\n\nenv:\n  CARGO_INCREMENTAL: false\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n\njobs:\n  prepare:\n    runs-on: ubuntu-latest\n    outputs:\n      test_ref: ${{ steps.set.outputs.test_ref }}\n    steps:\n      - id: set\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const prInput = context.payload.inputs?.pr_number;\n            core.info(`Fetching PR ${prInput}`);\n            if (!prInput) {\n              // Use the ref the workflow was triggered on (branch/tag/SHA in base repo)\n              core.setOutput('test_ref', process.env.GITHUB_SHA);\n              return;\n            }\n            const pr = await github.rest.pulls.get({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              pull_number: Number(prInput),\n            });\n            core.info(pr.data.head.sha);\n            core.setOutput('test_ref', pr.data.head.sha);\n\n  old-harness:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n    permissions:\n      id-token: write\n      contents: read\n    needs: prepare\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n\n    - name: Configure AWS Credentials\n      continue-on-error: true\n      uses: aws-actions/configure-aws-credentials@v6\n      with:\n        role-to-assume: arn:aws:iam::567805100031:role/github-runner-tract-ci\n        aws-region: us-east-2\n\n    - name: Full test\n      run: .travis/test-harness.sh\n\n  cli-tests:\n    runs-on: ubuntu-latest\n    needs: prepare\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n    - name: Full test \n      env:\n        AWS_EC2_METADATA_DISABLED: true\n      run: .travis/cli-tests.sh\n\n  onnx-tests:\n    runs-on: ubuntu-latest\n    needs: prepare\n    strategy:\n      matrix:\n        opset: [1_4_1, 1_5_0, 1_6_0, 1_7_0, 1_8_1, 1_9_0, 1_10_2, 1_11_0, 1_12_0, 1_13_0, 1_14_1, 1_15_0, 1_16_2, 1_17_0, 1_18_0, 1_19_1]\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n    - name: Full test\n      run: .travis/onnx-tests.sh ${{ matrix.opset }}\n\n  tflite:\n    runs-on: ubuntu-latest\n    needs: prepare\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n    - name: Full test\n      run: .travis/tflite.sh\n\n  some-tests-with-paranoid-asserts:\n    runs-on: ubuntu-latest\n    needs: prepare\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n\n    - name: With assertions\n      run: |\n        rustup update\n        cargo test --features tract-core/paranoid_assertions -p test-onnx-core -p test-unit-core\n\n  without-default-features:\n    runs-on: ubuntu-latest\n    needs: prepare\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n    - name: Without default features\n      run: |\n        rustup update\n        cargo check -p tract-cli --no-default-features $CARGO_EXTRA\n\n  complexes:\n    runs-on: ubuntu-latest\n    needs: prepare\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n    - name: With complexes\n      run: |\n        rustup update\n        cargo check -p tract-nnef --features complex $CARGO_EXTRA\n\n  check-all-targets:\n    runs-on: ubuntu-latest\n    needs: prepare\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n    - name: Check all targets\n      run: |\n        ROOT=$(pwd) ./.travis/ci-system-setup.sh\n        cargo check --all-targets --workspace --exclude test-metal --exclude tract-metal\n\n  C:\n    runs-on: ubuntu-latest\n    needs: prepare\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n    - name: C smoke tests\n      run: |\n        cd api/c\n        cargo install cbindgen\n        make\n\n  python:\n    runs-on: ubuntu-latest\n    needs: prepare\n\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        ref: ${{ needs.prepare.outputs.test_ref }}\n        fetch-depth: 0\n    - name: Setup Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: \"3.13\"\n\n    - name: Install uv\n      uses: astral-sh/setup-uv@v7\n\n    - name: Pytest bindings\n      timeout-minutes: 60\n      run: |\n        cd api/py\n        uv venv --python 3.13\n        source .venv/bin/activate\n        uv pip install -e \".[dev]\"\n        pytest .\n"
  },
  {
    "path": ".github/workflows/large_models.yml",
    "content": "name: Large models\n\non:\n  pull_request:\n  schedule:\n    - cron:  '0 3 * * *'\n  workflow_dispatch:\n\nenv:\n  LARGE_MODELS: true\n  \njobs:\n  cli: \n    name: Build tract on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ macos-latest, ubuntu-latest ]\n    steps:\n      - uses: actions/checkout@v6\n      - run: |\n          ROOT=. ./.travis/ci-system-setup.sh\n          cargo build -p tract-cli --profile opt-no-lto --no-default-features --features transformers\n      - run: echo uname=$(uname) >> $GITHUB_ENV\n      - uses: actions/upload-artifact@v7\n        with:\n          name: tract-cli-${{env.uname}}\n          path: ./target/opt-no-lto/tract\n\n  foundation-llms:\n    runs-on: ubuntu-latest\n    outputs:\n      models: ${{steps.set-matrix.outputs.models}}\n      q: ${{steps.set-matrix.outputs.q}}\n\n    steps:\n      - id: set-matrix\n        env:\n          FULL: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}\n        run: |\n          if [ \"$FULL\" = \"true\" ]\n          then\n            echo 'models=[ \"openelm-270M\", \"llama-3.2-1B-instruct\", \"llama-3.2-3B-instruct\", \"llama-3.1-8B-instruct\", \"qwen3-1.7B\", \"qwen3-8B\" ]' >> $GITHUB_OUTPUT\n            echo 'q=[ \"f16f16\", \"f32f32\", \"q40ef16\" ]' >> $GITHUB_OUTPUT\n          else\n            echo ::notice::Skipping most checks on PR and commit. Dispatch workflow manually if needed.\n            echo 'models=[ \"llama-3.2-1B-instruct\" ]' >> $GITHUB_OUTPUT\n            echo 'q=[ \"f32f32\", \"q40ef16\" ]' >> $GITHUB_OUTPUT\n          fi\n\n  foundation-llm:\n    name: ${{ matrix.os }} / ${{matrix.rt}} / ${{ matrix.model }} / ${{ matrix.q }}\n    needs: [ cli, foundation-llms ]\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ macOS, cuda-lovelace ]\n        model: ${{fromJson(needs.foundation-llms.outputs.models)}}\n        q: ${{fromJson(needs.foundation-llms.outputs.q)}}\n        rt: [ cpu, gpu ]\n        exclude:\n          - model: openelm-270M\n            q: f32f32\n          - model: Llama-3.2-3B-Instruct\n            q: f32f32\n          - model: Llama-3.2-3B-Instruct\n            q: f32f32\n          - model: Llama-3.1-8B-Instruct\n            q: f32f32\n          - model: Qwen3-1.7B\n            q: f32f32\n          - model: Qwen3-8B\n            q: f32f32\n          - model: OpenELM-270M\n            q: f32f32\n      fail-fast: false\n    permissions:\n      id-token: write\n      contents: read\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Configure AWS Credentials\n      continue-on-error: true\n      uses: aws-actions/configure-aws-credentials@v6\n      with:\n        role-to-assume: arn:aws:iam::567805100031:role/github-runner-tract-ci\n        aws-region: us-east-2\n    - run: echo uname=$(uname) >> $GITHUB_ENV\n    - uses: actions/download-artifact@v8\n      with:\n        name: tract-cli-${{env.uname}}\n        path: tract-cli-${{env.uname}}\n\n    - name: Download and run\n      run: |\n        chmod +x tract-cli-${{env.uname}}/tract\n        export TRACT_RUN=$GITHUB_WORKSPACE/tract-cli-${{env.uname}}/tract\n        if [ \"${{matrix.rt}}\" = \"gpu\" ]\n        then\n          case $(uname) in\n            Darwin) RT=metal;;\n            Linux) RT=cuda;;\n          esac\n        fi\n        .travis/test-llm.sh ${{matrix.model}} ${{matrix.q}} $RT\n\n  parakeet-tdt-600m-v3:\n    name: ${{matrix.os}} / Parakeet TDT 600m v3\n    needs: [ cli ]\n    strategy:\n      matrix:\n        os: [ macOS, cuda-lovelace ]\n      fail-fast: false\n    permissions:\n      id-token: write\n      contents: read\n    runs-on: ${{ matrix.os }}\n    steps:\n    - uses: actions/checkout@v6\n    - run: echo uname=$(uname) >> $GITHUB_ENV\n    - uses: actions/download-artifact@v8\n      with:\n        name: tract-cli-${{env.uname}}\n        path: tract-cli-${{env.uname}}\n\n    - name: Download and run\n      run: |\n        chmod +x tract-cli-${{env.uname}}/tract\n        export TRACT_RUN=$GITHUB_WORKSPACE/tract-cli-${{env.uname}}/tract\n        ./harness/parakeet-tdt-600m-v3/ci.sh\n\n"
  },
  {
    "path": ".github/workflows/pydoc.yml",
    "content": "name: Python gh-pages doc\n\non:\n  pull_request:\n  release:\n  workflow_dispatch:\n\nenv:\n  CARGO_INCREMENTAL: false\n\njobs:\n  build_doc:\n    name: Build doc\n    runs-on: ubuntu-latest\n    if: github.repository == 'sonos/tract'\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Install Rust toolchain\n        uses: dtolnay/rust-toolchain@stable\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.12\"\n\n      - name: Extract version tag\n        id: version\n        if: github.event_name == 'release' && github.event.action == 'published'\n        run: echo value=$(echo ${{ github.ref }} | cut -f 3 -d / | sed 's/^v//' ) >> $GITHUB_OUTPUT\n\n      - name: Build doc\n        run: |\n          set -ex\n          cd api/py\n          python -m venv pydocs\n          source pydocs/bin/activate\n          pip install -r requirements-docs.txt\n          pip install -e .\n          sphinx-build -b html . _build/html\n          cp _static/redirect-index.html _build/html/index.html\n\n      - name: Deploy to gh-pages\n        if: github.event_name != 'pull_request'\n        run: |\n          set -ex\n          git config user.name \"CI bot\"\n          git config user.email ci-bot@tract.rs\n\n          version=\"${{ steps.version.outputs.value }}\"\n          if [ -z \"$version\" ]; then\n            version=\"dev\"\n          fi\n\n          # fetch existing gh-pages into a work directory\n          git fetch origin gh-pages --depth=1 || true\n          workdir=$(mktemp -d)\n          git worktree add \"$workdir\" gh-pages 2>/dev/null || {\n            git worktree add --orphan \"$workdir\" gh-pages\n          }\n\n          # copy new build into the versioned subdirectory\n          rm -rf \"$workdir/$version\"\n          cp -r api/py/_build/html \"$workdir/$version\"\n\n          # regenerate versions.json (mike-compatible format) from directories present\n          cd \"$workdir\"\n          python3 -c \"\n          import json, os, re\n          dirs = sorted(\n              [d for d in os.listdir('.') if os.path.isdir(d) and d != '.git'],\n              key=lambda v: [int(x) if x.isdigit() else x for x in re.split(r'(\\d+)', v)],\n              reverse=True,\n          )\n          versions = [{'version': d, 'title': d, 'aliases': []} for d in dirs]\n          with open('versions.json', 'w') as f:\n              json.dump(versions, f, indent=2)\n          \"\n\n          # commit and push\n          git add -A\n          git commit -m \"Update Python docs ($version)\" || true\n          git push origin gh-pages\n\n          # clean up worktree\n          cd -\n          git worktree remove \"$workdir\"\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "on:\n  push:\n    tags:\n    - 'v*'\n\nname: Create release\n\nenv:\n  CARGO_INCREMENTAL: false\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n\njobs:\n  release:\n    name: Create release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Extract version tag\n        id: version\n        run: echo value=$(echo ${{ github.ref }} | cut -f 3 -d / | sed 's/^v//' ) >> $GITHUB_OUTPUT\n\n      - uses: actions/checkout@v6\n\n      - name: Create Release\n        uses: softprops/action-gh-release@v2\n        with:\n          name: tract ${{ steps.version.outputs.value }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN_RELEASE }}\n\n"
  },
  {
    "path": ".github/workflows/tract-ci-bench.yml",
    "content": "name: Bench with tract-ci-minion\n\non:\n  schedule:\n    - cron:  '1 * * * *' # every hour at minute 1\n  workflow_dispatch:\n\njobs:\n  minion:\n    strategy:\n      fail-fast: false\n      matrix:\n        os:\n          - macOS\n          - cuda-lovelace\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n    - name: Run minion if found\n      run: |\n        if [ -d $HOME/tract-minion ]\n        then\n          echo \"Running minion\"\n          cd $HOME/tract-minion\n          ./tract-ci-minion --once\n        else\n          echo \"Not running minion\"\n        fi\n"
  },
  {
    "path": ".github/workflows/wheels.yml",
    "content": "name: Python wheels\n\non:\n  schedule:\n    - cron:  '0 3 * * MON'\n  release:\n    types: [created]\n  workflow_dispatch:\n    inputs:\n      publish:\n        description: force publish to pypi\n        type: boolean\n      pypi_version_override:\n        description: override version id detection\n        type: string\n\n\nenv:\n  CARGO_INCREMENTAL: false\n  PYPI_VERSION_OVERRIDE: ${{ inputs.pypi_version_override }}\n  CIBW_ENVIRONMENT_PASS_LINUX: \"PYPI_VERSION_OVERRIDE\"\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n  MACOSX_DEPLOYMENT_TARGET: 10.13\n\njobs:\n  build_wheels:\n    name: Build wheels on ${{ matrix.os }}\n    runs-on: ${{ matrix.os}}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-22.04, windows-2022, macos-14]\n\n    steps:\n      - uses: actions/checkout@v6\n        \n      - name: Setup | Rust\n        uses: dtolnay/rust-toolchain@stable\n\n      - uses: actions/setup-python@v6\n        with:\n          python-version: \"3.13\"\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@v7\n\n      - name: Install rust toolchains\n        if: startsWith(matrix.os, 'macOS')\n        run: rustup target install x86_64-apple-darwin aarch64-apple-darwin\n\n      - name: Build wheels\n        uses: nick-fields/retry@v4\n        with:\n          max_attempts: 1\n          timeout_seconds: 54000 # 15 hours :/\n          command: uvx cibuildwheel --output-dir wheelhouse api/py\n\n      - uses: actions/upload-artifact@v7\n        with:\n          name: wheels-${{github.run_id}}-${{matrix.os}}\n          path: ./wheelhouse/*.whl\n\n  make_sdist:\n    name: Make SDist\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Install uv\n      uses: astral-sh/setup-uv@v7\n\n    - name: Build SDist\n      run: cd api/py && uv build --sdist\n\n    - uses: actions/upload-artifact@v7\n      with:\n        name: wheels-${{github.run_id}}-src\n        path: api/py/dist/*.tar.gz\n\n  upload_all:\n    needs: [build_wheels, make_sdist]\n    runs-on: ubuntu-latest\n    if: (github.event_name == 'release' && github.event.action == 'published') || inputs.publish\n\n    steps:\n    - uses: actions/download-artifact@v8\n      with:\n        pattern: wheels-${{github.run_id}}-*\n        merge-multiple: true\n        path: dist\n\n    - uses: pypa/gh-action-pypi-publish@v1.13.0\n      with:\n        user: __token__\n        password: ${{ secrets.PYPI }}\n        verbose: true\n"
  },
  {
    "path": ".github/workflows/windows.yml",
    "content": "name: Windows unit tests\n\non:\n#  pull_request:\n  workflow_dispatch:\n  schedule:\n    - cron:  '0 3 * * *'\n\n\nenv:\n  CARGO_INCREMENTAL: false\n  FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true\n\njobs:\n  windows:\n    strategy:\n      matrix:\n        os: [ windows-2022 ]\n        toolchain: [ gnu, msvc ]\n      fail-fast: false\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n    - uses: actions/checkout@v6\n    - uses: nick-fields/retry@v4\n      name: Install Rustup using win.rustup.rs\n      with:\n        timeout_minutes: 10\n        max_attempts: 8\n        shell: pwsh\n        command: |\n            # Disable the download progress bar which can cause perf issues\n            $ProgressPreference = \"SilentlyContinue\"\n            Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe\n            .\\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --profile=minimal\n    - uses: nick-fields/retry@v4\n      name: Install the target\n      with:\n        timeout_minutes: 10\n        max_attempts: 8\n        shell: pwsh\n        command: |\n            rustup toolchain add stable-x86_64-pc-windows-${{matrix.toolchain}}\n            rustup default stable-x86_64-pc-windows-${{matrix.toolchain}}\n    - name: Install LLVM and Clang\n      uses: KyleMayes/install-llvm-action@v2\n      with:\n        version: \"11.0\"\n    - name: debug\n      run: dir \"C:\\\\Program Files\\\\LLVM\"\n    - name: debug lib\n      run: dir \"C:\\\\Program Files\\\\LLVM\\\\lib\"\n    - name: debug bin\n      run: dir \"C:\\\\Program Files\\\\LLVM\\\\bin\"\n    - name: top level cargo check\n      run: cargo check --workspace --exclude test-blas --exclude tract-metal --exclude test-metal --exclude causal_llm\n      env:\n        LIBCLANG_PATH: \"C:\\\\Program Files\\\\LLVM\\\\bin\"\n    - name: data / linalg / core / nnef / onnx / onnx-opl\n      run: cargo test -p tract-data -p tract-linalg -p tract-core -p tract-nnef -p tract-onnx -p tract-onnx-opl\n    - name: Onnx test suite\n      run: |\n          cargo test --release -p test-onnx-core -p test-unit-core\n      env:\n        TRACT_LOG: info\n"
  },
  {
    "path": ".gitignore",
    "content": "target\n**/*.rs.bk\n*.rustfmt\n*.back\nCargo.lock\nexamples/data\n.idea\n.cached/**\nflamegraph.svg\nperf.data*\nreadings.*\nmetrics\ntract.out\n.gdb_history\n/issue-*\n/.dinghy.toml\n.cargo\nproptest-regressions\n/tmp\nwheelhouse\ntarget-bisector*\n/nvidia\n"
  },
  {
    "path": ".travis/README.md",
    "content": "# Travis & minions test infrastructure\n\n## Principles\n\n* travis is triggered on each commit, it will run `./.travis/native.sh` to\n    perform x86_64 builds, plus a series of `./.travis/cross.sh` for as many\n    arm boards configurations.\n* `.travis/cross.sh` pushes a `.tgz` to a s3 bucket for each configuration. The\n    bundle contains a `entrypoint.sh` script and anything it depends on,\n    including the relevant `tract` cli executable. The script is actually names\n    `bundle-entrypoint.sh` in the repository.\n* devices are running `minion.sh` and will pick the new bundles from the s3 bucket,\n    untar and run the `entrypoint.sh`\n\n## Testing locally\n\n```\ncargo build --release -p tract-cli && cargo bench -p tract-linalg --no-run && .travis/run-bundle.sh `.travis/make_bundle.sh`\n```\n\n## minion setup\n\n```\nMINION=user@hostname.local\nscp .travis/minionrc $MINION:.minionrc\nscp .travis/minion.sh $MINION:\n```\n\nalso setup aws credentials (.aws/credentials)\n\n```\napt install wget curl perl awscli screen vim netcat\n```\n\nOn device: `.minioncrc` set a MINION_ID. At this point, running `./minion.sh`\nshould work.\n\n## crontab\n\n`crontab -e`\n\n```\n*/10 * * * * $HOME/minion.sh\n```\n\n## systemd timers\n\nin /etc/systemd/system/minion.service\n\n```\n[Unit]\nDescription=Travis ci bench minion\n\n[Service]\nUser=root\nType=oneshot\nExecStart=/home/root/minion.sh\n```\n\nin /etc/systemd/system/minion.timer\n\n```\n[Unit]\nDescription=Run minion.service every 5 minutes\n\n[Timer]\nOnCalendar=*:0/5\n\n[Install]\nWantedBy=timers.target\n\n```\n\nthen\n\n```\nsystemctl enable minion.timer\nsystemctl start minion.timer\n```\n\n# Setup file server (http only)\n\n\n```\nsudo apt install nginx awscli vim\n```\n\n* setup aws credentials (.aws/credentials)\n* in $HOME/sync-data.sh:\n\n```\n\n```\n* chmod +x $HOME/sync-data.sh\n* run it: ./sync-data.sh\n* `crontab -e`\n\n```\n*/5 * * * * $HOME/sync-data.sh\n```\n\n\n* `sudo vi /etc/nginx/sites-available/models`\n\n```\nserver {\n    root /home/raspbian/models/;\n\n    location /models {\n    }\n}\n```\n\n* `sudo ln -s /etc/nginx/sites-available/models /etc/nginx/sites-enabled/`\n* `sudo rm /etc/nginx/sites-enabled/default`\n* `sudo /etc/init.d/nginx reload`\n* test : `curl -I http://localhost/hey_snips_v1.pb`\n"
  },
  {
    "path": ".travis/android-ndk.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nwhich java || sudo apt install -y default-jdk\n\nANDROID_SDK=$HOME/cached/android-sdk\nif [ ! -d \"$ANDROID_SDK\" ]\nthen\n    mkdir -p $ANDROID_SDK\n    cd $ANDROID_SDK\n\n      # ANDROID_SDK_VERSION=4333796\n      # \"https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_VERSION}.zip\"\n\n    curl -s -o android-sdk.zip \\\n       https://dl.google.com/android/repository/commandlinetools-linux-8092744_latest.zip\n    unzip -q android-sdk.zip\n    rm android-sdk.zip\nfi\n\nyes | $ANDROID_SDK/cmdline-tools/bin/sdkmanager --sdk_root=$ANDROID_SDK --licenses > /dev/null\n\n$ANDROID_SDK/cmdline-tools/bin/sdkmanager --sdk_root=$ANDROID_SDK \\\n    \"build-tools;30.0.0\" \"platform-tools\" \"platforms;android-31\" \"tools\" \"ndk-bundle\" \\\n    > /dev/null\n"
  },
  {
    "path": ".travis/asan.sh",
    "content": "#!/bin/sh\n\nset -ex\n\n# RUSTFLAGS=-Zsanitizer=address cargo +nightly test -Zbuild-std --target $(rustc -vV | sed -n 's|host: ||p')\n\nTARGET=$(rustc -vV | sed -n 's|host: ||p')\n\nrustup toolchain add nightly\nrustup component add rust-src --toolchain nightly-$TARGET\n\nexport RUSTFLAGS=-Zsanitizer=address \nexport RUSTDOCFLAGS=$RUSTFLAGS\nexport RUSTUP_TOOLCHAIN=nightly\nexport RUST_VERSION=nightly\nexport CARGO_EXTRA=\"--target $TARGET\"\n\ncargo -q test -q -p tract-linalg $CARGO_EXTRA\n\n# inventory, asan and macos liner are not playing nice, so we have to stop there \nif [ $(uname) == \"Darwin\" ]\nthen\n    exit 0\nfi\n\ncargo -q test -q -p tract-core --features paranoid_assertions $CARGO_EXTRA\n\n./.travis/regular-tests.sh\nif [ -n \"$CI\" ]\nthen\n    cargo clean\nfi\n./.travis/onnx-tests.sh\nif [ -n \"$CI\" ]\nthen\n    cargo clean\nfi\n./.travis/cli-tests.sh\n\n"
  },
  {
    "path": ".travis/bundle-entrypoint.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nstart=$(date +%s)\n\nROOT=`pwd`\n\nif [ -n \"$TRACT_RUN\" ]\nthen\n    TRACT=$TRACT_RUN\nelif [ -x tract ]\nthen\n    TRACT=\"./tract\"\nelse\n    cargo build -p tract-cli -q --release\n    TRACT=\"./target/release/tract\"\nfi\n\nCACHEDIR=${CACHEDIR:-$HOME/.cache/tract-ci-minion-models}\ncase $CACHEDIR in\n    \"http\"*)\n        wget $CACHEDIR/private/private-benches.sh\n        PRIVATE=`pwd`/private-benches.sh\n    ;;\n    *)\n        [ -d $CACHEDIR ] || mkdir $CACHEDIR\n        PATH=$PATH:/usr/local/bin # for aws command on darwin\n        aws s3 sync s3://tract-ci-builds/model $CACHEDIR || echo \"Warning: aws s3 sync failed, continuing with cached models\"\n        (cd $CACHEDIR\n            [ -d en_libri_real ] || tar zxf en_libri_real.tar.gz\n            [ -d en_tdnn_lstm_bn_q7 ] || tar zxf en_tdnn_lstm_bn_q7.tar.gz\n        )\n        PRIVATE=$CACHEDIR/private/private-benches.sh\n    ;;\nesac\n\n\n\ntouch metrics\nif [ -e sizes ]\nthen\n    cat sizes >> metrics\nfi\n\nif [ $(uname) = \"Linux\" ]\nthen\n    if [ -r /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor -a `cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor` = \"userspace\" ]\n    then\n            F=$(printf \"%s\\n\" `cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies` | sort -n | tail -1)\n            echo $F > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed\n    fi\nfi\n\nnet_bench() {\n    net=$1\n    pb=$2\n    shift 2\n\n    $TRACT \"$@\" --machine-friendly -O bench --allow-random-input $BENCH_OPTS > tract.out\n    v=`cat tract.out | grep -a real | cut -f 2 -d ' ' | sed 's/\\([0-9]\\{9,9\\}\\)[0-9]*/\\1/'`\n    echo net.$net.evaltime.$pb $v >> metrics\n\n    $TRACT \"$@\" --readings --readings-heartbeat 1000 --machine-friendly -O bench --allow-random-input $BENCH_OPTS > tract.out\n\n    for stage in model_ready before_optimize\n    do\n        pattern=$(echo $stage | sed 's/[_-]/./g')\n        v=$(grep -a $pattern readings.out | sed 's/  */ /g;s/^  *//' | cut -f 1 -d ' ')\n        echo net.$net.time_to_$stage.$pb $v >> metrics\n        v=$(grep -a $pattern readings.out | sed 's/  */ /g;s/^  *//' | cut -f 4 -d ' ')\n        echo net.$net.rsz_at_$stage.$pb $v >> metrics\n        f=$(grep -a $pattern readings.out | sed 's/  */ /g;s/^  *//' | cut -f 11 -d ' ')\n        a=$(grep -a $pattern readings.out | sed 's/  */ /g;s/^  *//' | cut -f 10 -d ' ')\n        echo net.$net.active_at_$stage.$pb $(($a-$f)) >> metrics\n    done\n}\n\nllm_bench() {\n    net=$1\n    pb=$2\n    shift 2\n\n    if  $TRACT \"$@\"  --llm --machine-friendly -O llm-bench $BENCH_OPTS > tract.out\n    then\n        cat tract.out\n        echo llm.$net.pp512.$pb $(cat tract.out | grep -a PP512 | cut -f 2 -d ' ') >> metrics\n        echo llm.$net.tg128.$pb $(cat tract.out | grep -a TG128 | cut -f 2 -d ' ') >> metrics\n    fi \n\n    if $TRACT \"$@\" --readings --readings-heartbeat 1000 --llm --machine-friendly -O llm-bench $BENCH_OPTS > /dev/null\n    then\n        for stage in model_ready before_optimize\n        do\n            pattern=$(echo $stage | sed 's/[_-]/./g')\n            v=$(grep -a $pattern readings.out | sed 's/  */ /g;s/^  *//' | cut -f 1 -d ' ')\n            echo llm.$net.time_to_$stage.$pb $v >> metrics\n            v=$(grep -a $pattern readings.out | sed 's/  */ /g;s/^  *//' | cut -f 4 -d ' ')\n            echo llm.$net.rsz_at_$stage.$pb $v >> metrics\n            f=$(grep -a $pattern readings.out | sed 's/  */ /g;s/^  *//' | cut -f 11 -d ' ')\n            a=$(grep -a $pattern readings.out | sed 's/  */ /g;s/^  *//' | cut -f 10 -d ' ')\n            if [ -n \"$a\" -a -n \"$f\" ]\n            then\n                 echo llm.$net.active_at_$stage.$pb $(($a-$f)) >> metrics\n            fi\n        done\n    fi\n}\n\nnet_bench arm_ml_kws_cnn_m pass $CACHEDIR/ARM-ML-KWS-CNN-M.pb -i 49,10,f32 --partial --input-node Mfcc\n\nnet_bench hey_snips_v1 400ms $CACHEDIR/hey_snips_v1.pb -i 80,40,f32\nnet_bench hey_snips_v31 400ms $CACHEDIR/hey_snips_v3.1.pb -i 40,40,f32\n\nnet_bench hey_snips_v4_model17 2sec $CACHEDIR/hey_snips_v4_model17.pb -i 200,20,f32\nnet_bench hey_snips_v4_model17 pulse8 $CACHEDIR/hey_snips_v4_model17.pb -i S,20,f32 --pulse 8\nnet_bench hey_snips_v4_model17_nnef pulse8 --nnef-tract-pulse $CACHEDIR/hey_snips_v4_model17.alpha1.tar\n\nnet_bench mobilenet_v1_1 pass $CACHEDIR/mobilenet_v1_1.0_224_frozen.pb -i 1,224,224,3,f32\nnet_bench mobilenet_v2_1 pass $CACHEDIR/mobilenet_v2_1.4_224_frozen.pb -i 1,224,224,3,f32\n\nnet_bench inceptionv1q pass $CACHEDIR/inceptionv1_quant.nnef.tar.gz --nnef-tract-core\nnet_bench inceptionv3 pass $CACHEDIR/inception_v3_2016_08_28_frozen.pb -i 1,299,299,3,f32\n\n\nnet_bench mdl-en-2019-Q3-librispeech_onnx 2600ms $CACHEDIR/en_libri_real/model.onnx --output-node output -i 264,40\nnet_bench mdl-en-2019-Q3-librispeech_onnx pulse_240ms $CACHEDIR/en_libri_real/model.onnx --output-node output -i S,40 --pulse 24\nnet_bench en_tdnn_lstm_bn_q7 2600ms $CACHEDIR/en_tdnn_lstm_bn_q7/model.onnx --output-node output -i 264,40\nnet_bench en_tdnn_lstm_bn_q7 pulse_240ms $CACHEDIR/en_tdnn_lstm_bn_q7/model.onnx --output-node output -i S,40 --pulse 24\nnet_bench en_tdnn_8M 2600ms $CACHEDIR/mdl-en-2019-12-24-aho-corasick-18h01m33s.onnx --output-node output -i 264,40\nnet_bench en_tdnn_8M pulse_240ms $CACHEDIR/mdl-en-2019-12-24-aho-corasick-18h01m33s.onnx --output-node output -i S,40 --pulse 24\nnet_bench en_tdnn_8M pulse_180ms $CACHEDIR/mdl-en-2019-12-24-aho-corasick-18h01m33s.onnx --output-node output -i S,40 --pulse 18\nnet_bench en_tdnn_8M pulse_120ms $CACHEDIR/mdl-en-2019-12-24-aho-corasick-18h01m33s.onnx --output-node output -i S,40 --pulse 12\nnet_bench en_tdnn_8M_nnef pulse_240ms $CACHEDIR/mdl-en-2019-12-24-aho-corasick-18h01m33s.alpha1.a.tar --nnef-tract-pulse\nnet_bench en_tdnn_15M 2600ms $CACHEDIR/en_tdnn_15M.onnx --output-node output -i 264,40\nnet_bench en_tdnn_15M pulse_240ms $CACHEDIR/en_tdnn_15M.onnx --output-node output -i S,40 --pulse 24\nnet_bench en_tdnn_15M pulse_120ms $CACHEDIR/en_tdnn_15M.onnx --output-node output -i S,40 --pulse 12\nnet_bench en_tdnn_15M_nnef pulse_240ms $CACHEDIR/en_tdnn_15M.alpha1.tar --nnef-tract-pulse\nnet_bench dummy-conmer-12M pulse_120ms $CACHEDIR/dummy-conmer-12M.nnef.tar --nnef-tract-core --pulse 12\n\nnet_bench en_tdnn_pyt_15M pulse_120ms $CACHEDIR/mdl-en-2023-03-27-allen-17h11m50s.nnef.tar --nnef-tract-core --pulse 12\n\nnet_bench speaker_id pulse8 $CACHEDIR/speaker-id-2019-03.onnx -i 1,S,40,f32 --output-node 257 --partial --pulse 8\n\nnet_bench voicecom_fake_quant 2sec $CACHEDIR/snips-voice-commands-cnn-fake-quant.pb -i 200,10,f32\nnet_bench voicecom_float 2sec $CACHEDIR/snips-voice-commands-cnn-float.pb -i 200,10,f32\n\nnet_bench trunet pulse1_f32 $CACHEDIR/trunet_dummy.nnef.tgz --nnef-tract-core --pulse 1\nnet_bench trunet pulse1_f16 $CACHEDIR/trunet_dummy.nnef.tgz --nnef-tract-core -t f32_to_f16 --pulse 1\n\n. $PRIVATE\n\nif [ $(uname) = \"Darwin\" ]\nthen\n    LLM_BACKENDS=\"cpu metal\"\nfi\n\nif which nvidia-smi \nthen \n    LLM_BACKENDS=\"cpu cuda\"\nfi\n\nif [ -n \"$LLM_BACKENDS\" ]\nthen\n    for backend in $LLM_BACKENDS\n    do\n        case $backend in\n            cpu) extra=\"--timeout 180\";;\n            metal) extra=\"--metal --timeout 60\"\n                   BENCH_OPTS=\"--warmup-loops 1\"\n                   ;;\n            cuda) extra=\"--cuda --timeout 60\"\n                  BENCH_OPTS=\"--warmup-loops 1\"\n                  ;;\n        esac\n        llm_bench llama-3_2-1B-q40ef32-516 $backend $CACHEDIR/Llama-3.2-1B-q40ef32.516.nnef.tgz $extra\n        llm_bench openelm-270M-q40ef16-516 $backend $CACHEDIR/OpenELM-270M-q40ef16.516.nnef.tgz $extra\n        llm_bench llama-3_2-1B-instruct-q40ef16-541 $backend $CACHEDIR/Llama-3.2-1B-Instruct-q40ef16.541.nnef.tgz $extra\n        llm_bench openelm-270M-q40ef16-541 $backend $CACHEDIR/OpenELM-270M-q40ef16.541.nnef.tgz $extra\n        net_bench parakeet-tdt-600m-v3-f32f32-preprocessor_1s $backend $CACHEDIR/parakeet-tdt-0.6b-v3-f32f32.608.preprocessor.nnef.tgz \\\n                        -t transformers_detect_all --nnef-tract-transformers --set B=1 --set A=16000 $extra\n        net_bench parakeet-tdt-600m-v3-f32f32-encoder_1s $backend $CACHEDIR/parakeet-tdt-0.6b-v3-f32f32.608.encoder.p1.nnef.tgz \\\n                        -t transformers_detect_all --nnef-tract-transformers --set B=1 --set S=100 $extra\n        net_bench parakeet-tdt-600m-v3-f32f32-decoder_pass $backend $CACHEDIR/parakeet-tdt-0.6b-v3-f32f32.608.decoder.nnef.tgz \\\n                        -t transformers_detect_all --nnef-tract-transformers --set B=1 --set T=1 $extra\n        net_bench parakeet-tdt-600m-v3-f32f32-joint_pass $backend $CACHEDIR/parakeet-tdt-0.6b-v3-f32f32.608.joint.nnef.tgz \\\n                        -t transformers_detect_all --nnef-tract-transformers --set B=1 --set R=1 --set U=1 $extra\n\n        if [ \"$backend\" != \"cpu\" ]\n        then\n            llm_bench llama-3_2-3B-q40ef32-516 $backend $CACHEDIR/Llama-3.2-3B-q40ef32.516.nnef.tgz $extra\n            llm_bench llama-3_1-8B-instruct-q40ef16-541 $backend $CACHEDIR/Llama-3.1-8B-Instruct-q40ef16.541.nnef.tgz $extra\n            llm_bench llama-3_2-3B-instruct-q40ef16-541 $backend $CACHEDIR/Llama-3.2-3B-Instruct-q40ef16.541.nnef.tgz $extra\n            llm_bench qwen3-1_7B-q40ef16-541 $backend $CACHEDIR/Qwen3-1.7B-q40ef16.541.nnef.tgz $extra\n        fi\n    done\nfi\n\nend=$(date +%s)\n\necho bundle.bench-runtime  $(($end - $start)) >> metrics\n\n"
  },
  {
    "path": ".travis/cache_file.sh",
    "content": "#!/bin/sh\n\nset -e\n\nif [ -z \"$CACHEDIR\" ]\nthen\n    CACHEDIR=`dirname $0`/../.cached\nfi\n\nmkdir -p $CACHEDIR\ncd $CACHEDIR\nfor file in $@\ndo\n    mkdir -p $(dirname $file)\n    if [ ! -e $file ]\n    then\n        wget --no-verbose https://s3.amazonaws.com/tract-ci-builds/tests/$file -O $file.tmp \\\n        || aws s3 cp s3://tract-ci-builds/tests/$file $file.tmp\n        mv $file.tmp $file\n    fi\ndone\n\nexit 0\n"
  },
  {
    "path": ".travis/cargo-deny-check.sh",
    "content": "#!/bin/sh\n\nif [ -e cargo-deny ]\nthen\n    CARGO_DENY=`pwd`/cargo-deny\nelse\n    CARGO_DENY=\"cargo deny\"\nfi\n\n(cd api/rs ; $CARGO_DENY check)\n"
  },
  {
    "path": ".travis/ci-system-setup.sh",
    "content": "#!/bin/sh\nset -e\n\n[ -d $ROOT/.travis ] || exit 1 \"\\$ROOT not set correctly '$ROOT'\"\n\nif [ -z \"$RUSTUP_TOOLCHAIN\" ]\nthen\n    export RUSTUP_TOOLCHAIN=1.91.0\nfi\n\nexport RUSTUP_TOOLCHAIN\nPATH=$PATH:$HOME/.cargo/bin\n\nif [ -n \"$CI\" -a ! -e /tmp/ci-setup-done ]\nthen\n    if [ `uname` = \"Darwin\" ]\n    then\n        sysctl -n machdep.cpu.brand_string\n        python3 --version\n        brew install coreutils numpy python-setuptools jshon\n        PATH=\"/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH\"\n        export PYTHON_BIN_PATH=python3\n    else\n        if [ \"$RUNNER_ENVIRONMENT\" != \"self-hosted\" ]\n        then\n            if [ `whoami` != \"root\" ]\n            then\n                SUDO=sudo\n            fi\n            $SUDO apt-get update\n            # $SUDO apt-get upgrade -y\n            $SUDO apt-get install -y llvm python3 python3-numpy jshon wget curl build-essential sudo jshon clang \n            if ! which aws\n            then\n                curl -fsSL \"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip\" -o awscliv2.zip\n                $SUDO apt-get install -y unzip\n                unzip -q awscliv2.zip\n                $SUDO ./aws/install\n                aws --version\n            fi\n        fi\n    fi\n\n    which rustup || curl https://sh.rustup.rs -sSf | sh -s -- -y\n    rustup update\n    rustup toolchain add $RUSTUP_TOOLCHAIN\n    [ -n \"$GITHUB_PATH\" ] && echo $HOME/.cargo/bin >> $GITHUB_PATH\n\n    touch /tmp/ci-setup-done\nfi\n\nS3=https://s3.amazonaws.com/tract-ci-builds/tests\n\nif  [ -n \"$LARGE_MODELS\" ]\nthen\n    export CACHE_FILE=$ROOT/.travis/cache_file.sh\n    export MODELS=$HOME/.cache/models\n    export CACHEDIR=$MODELS\n    mkdir -p $MODELS\nelif [ -n \"$CI\" ]\nthen\n    MODELS=$S3\n    CACHE_FILE=true\nelse \n    CACHE_FILE=$ROOT/.travis/cache_file.sh\n    MODELS=${MODELS:-$ROOT/.cached}\n    mkdir -p $MODELS\nfi\n\nif [ -z \"$TRACT_RUN\" ]\nthen\n    TRACT_RUN=\"cargo run -p tract-cli $CARGO_EXTRA --profile opt-no-lto --no-default-features --features transformers,pulse --\"\n    export TRACT_RUN\nfi\n\nTRACT_RUNTIMES=\"-O\"\nif [ \"$(uname)\" = \"Darwin\" ] && (system_profiler SPDisplaysDataType | grep -i \"Metal\")\nthen \n    TRACT_RUNTIMES=\"$TRACT_RUNTIMES --metal\"\nfi\n\nif which nvidia-smi\nthen\n    TRACT_RUNTIMES=\"$TRACT_RUNTIMES --cuda\"\nfi\n\necho $TRACT_RUNTIMES\n"
  },
  {
    "path": ".travis/cli-tests.sh",
    "content": "#!/bin/sh\n\nWHITE='\\033[1;37m'\nNC='\\033[0m' # No Color\n\nset -e\n\nROOT=$(dirname $(dirname $(realpath $0)))\n. $ROOT/.travis/ci-system-setup.sh\n\necho\necho $WHITE • build tract $NC\necho\n\nTRACT_RUN=$(cargo build --message-format json -p tract-cli $CARGO_EXTRA --profile opt-no-lto | jq -r 'select(.target.name == \"tract\" and .executable).executable')\necho TRACT_RUN=$TRACT_RUN\nexport TRACT_RUN\n\necho\necho $WHITE • harness/nnef-test-cases $NC\necho\n\nfor t in `find harness/nnef-test-cases -name runme.sh`\ndo\n    echo $WHITE$t$NC\n    $t\ndone\n\necho\necho $WHITE • onnx/test_cases $NC\necho\n\n# ( cd onnx/test_cases ; CACHEDIR=$MODELS ./run_all.sh )\n\necho\necho $WHITE • full models command line test cases $NC\necho\n\necho $WHITE     image $NC\n\n$CACHE_FILE squeezenet.onnx\n$TRACT_RUN $MODELS/squeezenet.onnx -O \\\n    run -q \\\n    --allow-random-input \\\n    --assert-output-fact 1,1000,1,1,f32\n\n$CACHE_FILE inception_v3_2016_08_28_frozen.pb\n$TRACT_RUN \\\n    $MODELS/inception_v3_2016_08_28_frozen.pb \\\n    -i 1,299,299,3,f32 -O \\\n    run -q \\\n    --allow-random-input \\\n    --assert-output-fact 1,1001,f32\n\n$TRACT_RUN \\\n    $MODELS/inception_v3_2016_08_28_frozen.pb \\\n    -i 1,299,299,3,f32 -O \\\n    run -q \\\n    --allow-random-input \\\n    --assert-output-fact 1,1001,f32\n\n$CACHE_FILE mobilenet_v1_1.0_224_frozen.pb\n$TRACT_RUN $MODELS/mobilenet_v1_1.0_224_frozen.pb \\\n    -O -i 1,224,224,3,f32 \\\n    run -q \\\n    --allow-random-input \\\n    --assert-output-fact 1,1001,f32\n\n$CACHE_FILE mobilenet_v2_1.4_224_frozen.pb\n$TRACT_RUN $MODELS/mobilenet_v2_1.4_224_frozen.pb \\\n    -O -i 1,224,224,3,f32 \\\n    run -q \\\n    --allow-random-input \\\n    --assert-output-fact 1,1001,f32\n\n$CACHE_FILE inceptionv1_quant.nnef.tar.gz inceptionv1_quant.io.npz\n$TRACT_RUN $MODELS/inceptionv1_quant.nnef.tar.gz \\\n    --nnef-tract-core \\\n    --input-facts-from-bundle $MODELS/inceptionv1_quant.io.npz -O \\\n    run \\\n    --input-from-bundle $MODELS/inceptionv1_quant.io.npz \\\n    --allow-random-input \\\n    --assert-output-bundle $MODELS/inceptionv1_quant.io.npz\n\necho $WHITE     audio $NC\n\n$CACHE_FILE ARM-ML-KWS-CNN-M.pb\n$TRACT_RUN $MODELS/ARM-ML-KWS-CNN-M.pb \\\n    -O -i 49,10,f32 --partial \\\n    --input-node Mfcc \\\n    run -q \\\n    --allow-random-input\n\n$CACHE_FILE GRU128KeywordSpotter-v2-10epochs.onnx\n$TRACT_RUN $MODELS/GRU128KeywordSpotter-v2-10epochs.onnx \\\n    -O run -q \\\n    --allow-random-input \\\n    --assert-output-fact 1,3,f32\n\n$CACHE_FILE mdl-en-2019-Q3-librispeech.onnx\n$TRACT_RUN $MODELS/mdl-en-2019-Q3-librispeech.onnx \\\n    -O -i S,40,f32 --output-node output --pulse 24 \\\n    run -q \\\n    --allow-random-input\n    \n$CACHE_FILE hey_snips_v4_model17.pb\n$TRACT_RUN $MODELS/hey_snips_v4_model17.pb \\\n    -i S,20,f32 --pulse 8 dump --cost -q \\\n    --assert-cost \"FMA(F32)=2060448,Div(F32)=24576,Buffer(F32)=2920,Params(F32)=222251\"\n\n$TRACT_RUN $MODELS/hey_snips_v4_model17.pb -i S,20,f32 \\\n    dump -q \\\n    --assert-op-count AddAxis 0\n\n$CACHE_FILE trunet_dummy.nnef.tgz\n$TRACT_RUN --nnef-tract-core $MODELS/trunet_dummy.nnef.tgz dump -q\n\necho $WHITE     LLM $NC\n\nTEMP_ELM=$(mktemp -d)\n$CACHE_FILE 2024_06_25_elm_micro_export_with_kv_cache.nnef.tgz\n$TRACT_RUN $MODELS/2024_06_25_elm_micro_export_with_kv_cache.nnef.tgz \\\n    --nnef-tract-core \\\n    --assert \"S>0\" --assert \"P>0\" --assert \"S+P<2048\" \\\n    dump -q --nnef $TEMP_ELM/with-asserts.nnef.tgz\n$TRACT_RUN --nnef-tract-core $TEMP_ELM/with-asserts.nnef.tgz dump -q\nrm -rf $TEMP_ELM\n\nfor t in harness/pre-optimized-graphes/*\ndo\n    ( cd $t ; ./runme.sh)\ndone\n\n(\nif aws s3 ls tract-ci-builds/model/private\nthen\n    echo\n    echo $WHITE • private tests $NC\n    echo\n    if [ -n \"$CI\" ]\n    then\n        OUTPUT=/dev/null\n    else\n        set -x\n        OUTPUT=/dev/stdout\n    fi\n    (\n    mkdir -p $CACHEDIR\n    cd $CACHEDIR\n    aws s3 sync s3://tract-ci-builds/model/private private\n    for t in `find private -name t.sh`\n    do\n        ( cd `dirname $t` ; sh ./t.sh )\n    done\n    ) 2>&1 > $OUTPUT\n\n    echo\n    echo $WHITE • benches on full models $NC\n    echo\n\n    ./.travis/bundle-entrypoint.sh\nfi\n)\n\n"
  },
  {
    "path": ".travis/cost_model_task_build.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nARCH=$1\nID=$2\n\ncase $ARCH in\n    aarch64)\n        MUSL_TRIPLE=aarch64-linux-musl\n        RUST_TRIPLE=aarch64-unknown-linux-musl\n        PLATFORM=aarch64-unknown-linux-musl\n    ;;\n    armv7)\n        MUSL_TRIPLE=armv7l-linux-musleabihf\n        RUST_TRIPLE=armv7-unknown-linux-musleabihf\n        PLATFORM=armv7-unknown-linux-musl\n    ;;\n    *)\n        exit \"Can't build with musl for $ARCH\"\n    ;;\nesac\n\nrustup update\nrustup target add $RUST_TRIPLE\n\n#curl -s https://musl.cc/${MUSL_TRIPLE}-cross.tgz | tar zx\ncurl -s https://s3.amazonaws.com/tract-ci-builds/toolchains/${MUSL_TRIPLE}-cross.tgz | tar zx\n\nMUSL_BIN=`pwd`/${MUSL_TRIPLE}-cross/bin\nexport PATH=$MUSL_BIN:$PATH\n\nexport TARGET_CC=$MUSL_BIN/${MUSL_TRIPLE}-gcc\n\nRUST_TRIPLE_ENV=$(echo $RUST_TRIPLE | tr 'a-z-' 'A-Z_')\nexport CARGO_TARGET_${RUST_TRIPLE_ENV}_CC=$TARGET_CC\nexport CARGO_TARGET_${RUST_TRIPLE_ENV}_LINKER=$TARGET_CC\n\n( cd linalg/cost_model ; cargo build --target $RUST_TRIPLE --release )\n\nTASK_NAME=cost-model-dataset-$ID\n\nmkdir $TASK_NAME\nmv linalg/cost_model/target/${RUST_TRIPLE}/release/cost_model $TASK_NAME\necho \"export TIMEOUT=$((86400*4))\" > $TASK_NAME/vars\necho \"#!/bin/sh\" > $TASK_NAME/entrypoint.sh\necho \"mkdir product\" >> $TASK_NAME/entrypoint.sh\necho \"./cost_model ds --size 10000 product/$TASK_NAME.txt\" >> $TASK_NAME/entrypoint.sh\n# echo \"./cost_model ds --size 2000 -k 128 -n 16 product/$TASK_NAME-small-k-tiny-n.txt\" >> $TASK_NAME/entrypoint.sh\n# echo \"./cost_model ds --size 5000 -m 1-512 -k 16,64,256 -n 1-20 product/$TASK_NAME-multiple-k-tiny-n.txt\" >> $TASK_NAME/entrypoint.sh\n# echo \"./cost_model ds --size 1000 -m 1-512 -k 256,1024 -n 1-512 product/$TASK_NAME-bigmn\" >> $TASK_NAME/entrypoint.sh\nchmod +x $TASK_NAME/entrypoint.sh\ntar czf $TASK_NAME.tgz $TASK_NAME\n\nif [ -n \"$AWS_ACCESS_KEY_ID\" ]\nthen\n    aws s3 cp $TASK_NAME.tgz s3://tract-ci-builds/tasks/$PLATFORM/$TASK_NAME.tgz\nfi\n"
  },
  {
    "path": ".travis/cross.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nROOT=$(dirname $(dirname $(realpath $0)))\n. $ROOT/.travis/ci-system-setup.sh\n\nwhich cargo-dinghy || ( mkdir -p /tmp/cargo-dinghy\n    if [ `arch` = x86_64 -o `arch` = i386 -o `arch` = arm64 ]\n    then\n         cd /tmp/cargo-dinghy\n         if [ `uname` = \"Darwin\" ]\n         then\n             NAME=macos\n         else\n             NAME=linux\n         fi\n         VERSION=0.8.0\n         wget -q https://github.com/snipsco/dinghy/releases/download/$VERSION/cargo-dinghy-$NAME-$VERSION.tgz -O cargo-dinghy.tgz\n         tar vzxf cargo-dinghy.tgz --strip-components 1\n         mv cargo-dinghy $HOME/.cargo/bin\n    else\n        cargo install cargo-dinghy\n    fi\n)\n\nif [ -z \"$PLATFORM\" -a -n \"$1\" ]\nthen\n    PLATFORM=$1\nfi\n\ncase \"$PLATFORM\" in\n    \"raspbian\")\n        [ -e $HOME/cached/raspitools ] || git clone --depth 1 https://github.com/raspberrypi/tools $HOME/cached/raspitools\n        TOOLCHAIN=$HOME/cached/raspitools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf\n        export RUSTC_TRIPLE=arm-unknown-linux-gnueabihf\n        rustup target add $RUSTC_TRIPLE\n        echo \"[platforms.$PLATFORM]\\nrustc_triple='$RUSTC_TRIPLE'\\ntoolchain='$TOOLCHAIN'\" > .dinghy.toml\n        cargo dinghy --platform $PLATFORM build --release -p tract-cli -p example-tensorflow-mobilenet-v2 -p tract-ffi\n        ;;\n\n    \"aarch64-linux-android\"|\"armv7-linux-androideabi\"|\"i686-linux-android\"|\"x86_64-linux-android\")\n        case \"$PLATFORM\" in\n            \"aarch64-linux-android\")\n                ANDROID_CPU=aarch64\n                RUSTC_TRIPLE=aarch64-linux-android\n                ;;\n            \"armv7-linux-androideabi\")\n                ANDROID_CPU=armv7\n                RUSTC_TRIPLE=armv7-linux-androideabi\n                ;;\n            \"i686-linux-android\")\n                ANDROID_CPU=i686\n                RUSTC_TRIPLE=i686-linux-android\n                ;;\n            \"x86_64-linux-android\")\n                ANDROID_CPU=x86_64\n                RUSTC_TRIPLE=x86_64-linux-android\n                ;;\n        esac\n\n        export TARGET_AR=ar\n\n        if [ -e /usr/local/lib/android/sdk/ndk-bundle ]\n        then\n            export ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk-bundle\n        else\n            export ANDROID_SDK_HOME=$HOME/cached/android-sdk\n            [ -e $ANDROID_SDK_HOME ] || ./.travis/android-ndk.sh\n        fi\n\n        rustup target add $RUSTC_TRIPLE\n        cargo dinghy --platform auto-android-$ANDROID_CPU build -p tract-linalg -p tract-ffi\n        ;;\n\n    \"aarch64-apple-ios\")\n        rustup target add aarch64-apple-ios\n        cargo dinghy --platform auto-ios-aarch64 check -p tract-linalg -p tract-ffi\n        ;;\n\n    \"aarch64-apple-darwin\" | \"x86_64-unknown-linux-gnu\")\n        RUSTC_TRIPLE=$PLATFORM\n        rustup target add $RUSTC_TRIPLE\n        cargo build --target $RUSTC_TRIPLE -p tract-cli --release\n        ;;\n\n    \"aarch64-unknown-linux-gnu-stretch\" | \"armv7-unknown-linux-gnueabihf-stretch\" | \"x86_64-unknown-linux-gnu-stretch\")\n        INNER_PLATFORM=${PLATFORM%-stretch}\n        (cd .travis/docker-debian-stretch; docker build --tag debian-stretch .)\n        docker run -v `pwd`:/tract -w /tract \\\n            -e CI=true \\\n            -e SKIP_QEMU_TEST=skip \\\n            -e PLATFORM=$INNER_PLATFORM debian-stretch \\\n            ./.travis/cross.sh\n        sudo chown -R `whoami` .\n        export RUSTC_TRIPLE=$INNER_PLATFORM\n        ;;\n\n    \"aarch64-unknown-linux-gnu\" | \"armv6vfp-unknown-linux-gnueabihf\" | \"armv7-unknown-linux-gnueabihf\" | \\\n        \"aarch64-unknown-linux-musl\" | \"armv7-unknown-linux-musl\" | \"cortexa53-unknown-linux-musl\" )\n\n        case \"$PLATFORM\" in\n            \"aarch64-unknown-linux-gnu\")\n                export ARCH=aarch64\n                export QEMU_ARCH=aarch64\n                export LIBC_ARCH=arm64\n                export TRACT_CPU_AARCH64_KIND=a55\n                export RUSTC_TRIPLE=$ARCH-unknown-linux-gnu\n                export DEBIAN_TRIPLE=$ARCH-linux-gnu\n                ;;\n            \"armv6vfp-unknown-linux-gnueabihf\")\n                export ARCH=armv6vfp\n                export LIBC_ARCH=armhf\n                export QEMU_ARCH=arm\n                export QEMU_OPTS=\"-cpu cortex-a15\"\n                export RUSTC_TRIPLE=arm-unknown-linux-gnueabihf\n                export DEBIAN_TRIPLE=arm-linux-gnueabihf\n                ;;\n            \"armv7-unknown-linux-gnueabihf\")\n                export ARCH=armv7\n                export QEMU_ARCH=arm\n                export LIBC_ARCH=armhf\n                export QEMU_OPTS=\"-cpu cortex-a15\"\n                export RUSTC_TRIPLE=armv7-unknown-linux-gnueabihf\n                export DEBIAN_TRIPLE=arm-linux-gnueabihf\n                export TARGET_CC=$DEBIAN_TRIPLE-gcc\n                export TRACT_CPU_ARM32_NEON=true\n                export DINGHY_TEST_ARGS=\"--env TRACT_CPU_ARM32_NEON=true\"\n                ;;\n            \"aarch64-unknown-linux-musl\")\n                export ARCH=aarch64\n                export QEMU_ARCH=aarch64\n                export LIBC_ARCH=arm64\n                export RUSTC_TRIPLE=$ARCH-unknown-linux-musl\n                export DEBIAN_TRIPLE=$ARCH-linux-gnu\n                export TRACT_CPU_AARCH64_KIND=a55\n                export CUSTOM_TC=`pwd`/aarch64-linux-musl-cross\n                [ -d \"$CUSTOM_TC\" ] || curl -s https://s3.amazonaws.com/tract-ci-builds/toolchains/aarch64-linux-musl-cross.tgz | tar zx\n                ;;\n            \"cortexa53-unknown-linux-musl\")\n                export ARCH=aarch64\n                export QEMU_ARCH=aarch64\n                export LIBC_ARCH=arm64\n                export RUSTC_TRIPLE=$ARCH-unknown-linux-musl\n                export DEBIAN_TRIPLE=$ARCH-linux-gnu\n                export TRACT_CPU_AARCH64_KIND=a53\n                export QEMU_OPTS=\"-cpu cortex-a53\"\n                export CUSTOM_TC=`pwd`/aarch64-linux-musl-cross\n                [ -d \"$CUSTOM_TC\" ] || curl -s https://s3.amazonaws.com/tract-ci-builds/toolchains/aarch64-linux-musl-cross.tgz | tar zx\n                ;;\n            \"armv7-unknown-linux-musl\")\n                export ARCH=armv7\n                export QEMU_ARCH=arm\n                export LIBC_ARCH=armhf\n                export RUSTC_TRIPLE=armv7-unknown-linux-musleabihf\n                export DEBIAN_TRIPLE=arm-linux-gnueabihf\n                export CUSTOM_TC=`pwd`/armv7l-linux-musleabihf-cross\n                export TRACT_CPU_ARM32_NEON=true\n                export DINGHY_TEST_ARGS=\"--env TRACT_CPU_ARM32_NEON=true\"\n                [ -d \"$CUSTOM_TC\" ] || curl -s https://s3.amazonaws.com/tract-ci-builds/toolchains/armv7l-linux-musleabihf-cross.tgz | tar zx\n                export TARGET_CFLAGS=\"-mfpu=neon\"\n                ;;\n            *)\n                echo \"unsupported platform $PLATFORM\"\n                exit 1\n                ;;\n        esac\n\n        mkdir -p $ROOT/target/$RUSTC_TRIPLE\n        echo \"[platforms.$PLATFORM]\\nrustc_triple='$RUSTC_TRIPLE'\" > .dinghy.toml\n        if [ -n \"$DEBIAN_TRIPLE\" ]\n        then\n            PACKAGES=\"$PACKAGES binutils-$DEBIAN_TRIPLE gcc-$DEBIAN_TRIPLE libc6-dev-$LIBC_ARCH-cross\"\n            echo \"deb_multiarch='$DEBIAN_TRIPLE'\" >> .dinghy.toml\n        fi\n\n        if [ -n \"$CUSTOM_TC\" ]\n        then\n            echo \"toolchain='$CUSTOM_TC'\" >> .dinghy.toml\n        fi\n\n        echo \"[script_devices.qemu-$PLATFORM]\\nplatform='$PLATFORM'\\npath='$ROOT/target/$RUSTC_TRIPLE/qemu-$PLATFORM'\" >> .dinghy.toml\n        echo \"#!/bin/sh\\nexe=\\$1\\nshift\\n/usr/bin/qemu-$QEMU_ARCH $QEMU_OPTS -L /usr/$DEBIAN_TRIPLE/ \\$exe --test-threads 1 \\\"\\$@\\\"\" > $ROOT/target/$RUSTC_TRIPLE/qemu-$PLATFORM\n        chmod +x $ROOT/target/$RUSTC_TRIPLE/qemu-$PLATFORM\n\n        DINGHY_TEST_ARGS=\"$DINGHY_TEST_ARGS --env PROPTEST_MAX_SHRINK_ITERS=100000000\"\n\n        $SUDO apt-get -y install --no-install-recommends qemu-system-arm qemu-user libssl-dev pkg-config $PACKAGES\n        rustup target add $RUSTC_TRIPLE\n        if [ -z \"$SKIP_QEMU_TEST\" ]\n        then\n            qemu-$QEMU_ARCH --version\n            cargo dinghy --platform $PLATFORM $DINGHY_TEST_ARGS test --profile opt-no-lto -p tract-linalg -- --nocapture\n            cargo dinghy --platform $PLATFORM $DINGHY_TEST_ARGS test --profile opt-no-lto -p tract-core\n        fi\n\n        cargo dinghy --platform $PLATFORM $DINGHY_TEST_ARGS check -p tract-ffi\n        # keep lto for these two are they're going to devices.\n        cargo dinghy --platform $PLATFORM build --release -p tract-cli -p example-tensorflow-mobilenet-v2\n        ;;\n\n    wasm32-wasi)\n        PLATFORM=wasm32-wasip1\n        wasmtime --version\n\n        rustup target add $PLATFORM\n        cargo check --target $PLATFORM --features getrandom-js -p tract-onnx -p tract-tensorflow\n        RUSTFLAGS='-C target-feature=+simd128' CARGO_TARGET_WASM32_WASIP1_RUNNER=wasmtime \\\n            cargo test --target=$PLATFORM -p tract-linalg -p tract-core -p test-unit-core\n        ;;\n    wasm32-*)\n        rustup target add $PLATFORM\n        cargo check --target $PLATFORM --features getrandom-js -p tract-onnx -p tract-tensorflow\n        ;;\n    *)\n        echo \"Don't know what to do for platform: $PLATFORM\"\n        exit 2\n        ;;\nesac\n\nif [ -e \"target/$RUSTC_TRIPLE/release/tract\" ]\nthen\n    export RUSTC_TRIPLE\n    TASK_NAME=`.travis/make_bundle.sh`\n    echo bench task: $TASK_NAME \n    if [ -n \"$AWS_ACCESS_KEY_ID\" ]\n    then\n        aws s3 cp $TASK_NAME.tgz s3://tract-ci-builds/tasks/$PLATFORM/$TASK_NAME.tgz\n    fi\nfi\n"
  },
  {
    "path": ".travis/debug-tests.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nif [ -z \"$CACHEDIR\" ]\nthen\n    CACHEDIR=`dirname $0`/../.cached\nfi\n\n# useful as debug_asserts will come into play\ncargo test -p tract-core\ncargo test -p test-onnx-core -p test-nnef-cycle -p test-unit-core\n"
  },
  {
    "path": ".travis/docker-debian-stretch/Dockerfile",
    "content": "FROM debian:stretch\nCOPY sources.list /etc/apt/sources.list\n"
  },
  {
    "path": ".travis/docker-debian-stretch/sources.list",
    "content": "deb http://archive.debian.org/debian/ stretch contrib main non-free\ndeb http://archive.debian.org/debian stretch-backports main\ndeb http://archive.debian.org/debian-security stretch/updates main\n"
  },
  {
    "path": ".travis/examples.sh",
    "content": "#!/bin/sh\n\nWHITE='\\033[1;37m'\nNC='\\033[0m' # No Color\n\nset -e\n\nROOT=$(dirname $(dirname $(realpath $0)))\n. $ROOT/.travis/ci-system-setup.sh\n\nfor t in `find examples -name ci.sh`\ndo\n    df -h\n    ex=$(dirname $t)\n    echo ::group:: $ex\n    echo $WHITE $ex $NC\n    ( cd $ex ; sh ./ci.sh )\n    if [ -n \"$CI\" ]\n    then\n        cargo clean\n    fi\n    echo ::endgroup::\ndone\n\n"
  },
  {
    "path": ".travis/llm-expectations-541",
    "content": "Qwen--Qwen3-1.7B-f16f16.p0s100.arm64.cpu 0.96\nQwen--Qwen3-1.7B-f16f16.p0s100.arm64.metal 0.96\nQwen--Qwen3-1.7B-f16f16.p0s100.x86_64.cpu 0.99\nQwen--Qwen3-1.7B-f16f16.p0s100.x86_64.cuda 0.99\nQwen--Qwen3-1.7B-f16f16.p50s50.arm64.cpu 0.97\nQwen--Qwen3-1.7B-f16f16.p50s50.arm64.metal 0.97\nQwen--Qwen3-1.7B-f16f16.p50s50.x86_64.cpu 0.99\nQwen--Qwen3-1.7B-f16f16.p50s50.x86_64.cuda 0.99\nQwen--Qwen3-1.7B-f16f16.p99s1.arm64.cpu 0.99\nQwen--Qwen3-1.7B-f16f16.p99s1.arm64.metal 0.99\nQwen--Qwen3-1.7B-f16f16.p99s1.x86_64.cpu 0.99\nQwen--Qwen3-1.7B-f16f16.p99s1.x86_64.cuda 0.99\nQwen--Qwen3-1.7B-q40ef16.p0s100.arm64.cpu 0.92\nQwen--Qwen3-1.7B-q40ef16.p0s100.arm64.metal 0.98\nQwen--Qwen3-1.7B-q40ef16.p0s100.x86_64.cpu 0.99\nQwen--Qwen3-1.7B-q40ef16.p0s100.x86_64.cuda 0.92\nQwen--Qwen3-1.7B-q40ef16.p50s50.arm64.cpu 0.96\nQwen--Qwen3-1.7B-q40ef16.p50s50.arm64.metal 0.99\nQwen--Qwen3-1.7B-q40ef16.p50s50.x86_64.cpu 0.99\nQwen--Qwen3-1.7B-q40ef16.p50s50.x86_64.cuda 0.98\nQwen--Qwen3-1.7B-q40ef16.p99s1.arm64.cpu 0.97\nQwen--Qwen3-1.7B-q40ef16.p99s1.arm64.metal 0.99\nQwen--Qwen3-1.7B-q40ef16.p99s1.x86_64.cpu 0.99\nQwen--Qwen3-1.7B-q40ef16.p99s1.x86_64.cuda 0.96\nQwen--Qwen3-8B-f16f16.p0s100.arm64.cpu 0.94\nQwen--Qwen3-8B-f16f16.p0s100.arm64.metal 0.95\nQwen--Qwen3-8B-f16f16.p0s100.x86_64.cpu 0.99\nQwen--Qwen3-8B-f16f16.p0s100.x86_64.cuda 0.99\nQwen--Qwen3-8B-f16f16.p50s50.arm64.cpu 0.94\nQwen--Qwen3-8B-f16f16.p50s50.arm64.metal 0.95\nQwen--Qwen3-8B-f16f16.p50s50.x86_64.cpu 0.99\nQwen--Qwen3-8B-f16f16.p50s50.x86_64.cuda 0.99\nQwen--Qwen3-8B-f16f16.p99s1.arm64.cpu 0.96\nQwen--Qwen3-8B-f16f16.p99s1.arm64.metal 0.99\nQwen--Qwen3-8B-f16f16.p99s1.x86_64.cpu 0.99\nQwen--Qwen3-8B-f16f16.p99s1.x86_64.cuda 0.99\nQwen--Qwen3-8B-q40ef16.p0s100.arm64.cpu 0.86\nQwen--Qwen3-8B-q40ef16.p0s100.arm64.metal 0.97\nQwen--Qwen3-8B-q40ef16.p0s100.x86_64.cpu 0.99\nQwen--Qwen3-8B-q40ef16.p0s100.x86_64.cuda 0.96\nQwen--Qwen3-8B-q40ef16.p50s50.arm64.cpu 0.98\nQwen--Qwen3-8B-q40ef16.p50s50.arm64.metal 0.99\nQwen--Qwen3-8B-q40ef16.p50s50.x86_64.cpu 0.99\nQwen--Qwen3-8B-q40ef16.p50s50.x86_64.cuda 0.99\nQwen--Qwen3-8B-q40ef16.p99s1.arm64.cpu 0.96\nQwen--Qwen3-8B-q40ef16.p99s1.arm64.metal 0.98\nQwen--Qwen3-8B-q40ef16.p99s1.x86_64.cpu 0.99\nQwen--Qwen3-8B-q40ef16.p99s1.x86_64.cuda 0.96\napple--OpenELM-270M-f16f16.p0s100.arm64.cpu 0.98\napple--OpenELM-270M-f16f16.p0s100.arm64.metal 0.99\napple--OpenELM-270M-f16f16.p0s100.x86_64.cpu 0.99\napple--OpenELM-270M-f16f16.p0s100.x86_64.cuda 0.98\napple--OpenELM-270M-f16f16.p50s50.arm64.cpu 0.92\napple--OpenELM-270M-f16f16.p50s50.arm64.metal 0.92\napple--OpenELM-270M-f16f16.p50s50.x86_64.cpu 0.99\napple--OpenELM-270M-f16f16.p50s50.x86_64.cuda 0.99\napple--OpenELM-270M-f16f16.p99s1.arm64.cpu 0.97\napple--OpenELM-270M-f16f16.p99s1.arm64.metal 0.99\napple--OpenELM-270M-f16f16.p99s1.x86_64.cpu 0.99\napple--OpenELM-270M-f16f16.p99s1.x86_64.cuda 0.99\napple--OpenELM-270M-q40ef16.p0s100.arm64.cpu 0.99\napple--OpenELM-270M-q40ef16.p0s100.arm64.metal 0.99\napple--OpenELM-270M-q40ef16.p0s100.x86_64.cpu 0.99\napple--OpenELM-270M-q40ef16.p0s100.x86_64.cuda 0.95\napple--OpenELM-270M-q40ef16.p50s50.arm64.cpu 0.97\napple--OpenELM-270M-q40ef16.p50s50.arm64.metal 0.95\napple--OpenELM-270M-q40ef16.p50s50.x86_64.cpu 0.99\napple--OpenELM-270M-q40ef16.p50s50.x86_64.cuda 0.94\napple--OpenELM-270M-q40ef16.p99s1.arm64.cpu 0.99\napple--OpenELM-270M-q40ef16.p99s1.arm64.metal 0.99\napple--OpenELM-270M-q40ef16.p99s1.x86_64.cpu 0.99\napple--OpenELM-270M-q40ef16.p99s1.x86_64.cuda 0.89\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p0s100.arm64.cpu 0.96\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p0s100.arm64.metal 0.92\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p0s100.x86_64.cpu 0.99\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p0s100.x86_64.cuda 0.99\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p50s50.arm64.cpu 0.95\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p50s50.arm64.metal 0.95\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p50s50.x86_64.cpu 0.98\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p50s50.x86_64.cuda 0.98\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p99s1.arm64.cpu 0.97\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p99s1.arm64.metal 0.99\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p99s1.x86_64.cpu 0.99\nmeta-llama--Llama-3.1-8B-Instruct-f16f16.p99s1.x86_64.cuda 0.99\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p0s100.arm64.cpu 0.93\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p0s100.arm64.metal 0.99\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p0s100.x86_64.cpu 0.97\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p0s100.x86_64.cuda 0.97\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p50s50.arm64.cpu 0.93\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p50s50.arm64.metal 0.98\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p50s50.x86_64.cpu 0.99\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p50s50.x86_64.cuda 0.99\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p99s1.arm64.cpu 0.97\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p99s1.arm64.metal 0.99\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p99s1.x86_64.cpu 0.99\nmeta-llama--Llama-3.1-8B-Instruct-q40ef16.p99s1.x86_64.cuda 0.97\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p0s100.arm64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p0s100.arm64.metal 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p0s100.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p0s100.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p50s50.arm64.cpu 0.96\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p50s50.arm64.metal 0.96\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p50s50.x86_64.cpu 0.98\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p50s50.x86_64.cuda 0.97\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p99s1.arm64.cpu 0.97\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p99s1.arm64.metal 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p99s1.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f16f16.p99s1.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p0s100.arm64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p0s100.arm64.metal 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p0s100.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p0s100.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p50s50.arm64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p50s50.arm64.metal 0.96\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p50s50.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p50s50.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p99s1.arm64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p99s1.arm64.metal 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p99s1.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-f32f32.p99s1.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p0s100.arm64.cpu 0.97\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p0s100.arm64.metal 0.99\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p0s100.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p0s100.x86_64.cuda 0.98\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p50s50.arm64.cpu 0.86\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p50s50.arm64.metal 0.99\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p50s50.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p50s50.x86_64.cuda 0.94\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p99s1.arm64.cpu 0.98\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p99s1.arm64.metal 0.99\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p99s1.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-1B-Instruct-q40ef16.p99s1.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p0s100.arm64.cpu 0.98\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p0s100.arm64.metal 0.97\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p0s100.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p0s100.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p50s50.arm64.cpu 0.96\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p50s50.arm64.metal 0.98\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p50s50.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p50s50.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p99s1.arm64.cpu 0.96\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p99s1.arm64.metal 0.98\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p99s1.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-3B-Instruct-f16f16.p99s1.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p0s100.arm64.cpu 0.96\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p0s100.arm64.metal 0.99\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p0s100.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p0s100.x86_64.cuda 0.99\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p50s50.arm64.cpu 0.97\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p50s50.arm64.metal 0.98\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p50s50.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p50s50.x86_64.cuda 0.97\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p99s1.arm64.cpu 0.93\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p99s1.arm64.metal 0.99\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p99s1.x86_64.cpu 0.99\nmeta-llama--Llama-3.2-3B-Instruct-q40ef16.p99s1.x86_64.cuda 0.94\n"
  },
  {
    "path": ".travis/make_bundle.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nTRAVIS_COMMIT=${GITHUB_SHA:-dummy-commit-id}\nBRANCH=$(echo $GITHUB_HEAD_REF | tr '/' '_')\nBRANCH=${BRANCH:-main}\nPLATFORM=${PLATFORM:-dummy-platform}\n\ndates=`date -u +\"%Y%m%dT%H%M%S %s\"`\ndate_iso=`echo $dates | cut -f 1 -d ' '`\ntimestamp=`echo $dates | cut -f 2 -d ' '`\nTASK_NAME=tract-$date_iso\nmkdir -p $TASK_NAME\necho \"export TASK_NAME=$TASK_NAME\" > $TASK_NAME/vars\necho \"export TRAVIS_COMMIT=$TRAVIS_COMMIT\" >> $TASK_NAME/vars\nTRAVIS_BRANCH_SANE=`echo $BRANCH | tr '/' '_'`\necho \"export TRAVIS_BRANCH_SANE=$TRAVIS_BRANCH_SANE\" >> $TASK_NAME/vars\necho \"export DATE_ISO=$date_iso\" >> $TASK_NAME/vars\necho \"export TIMESTAMP=$timestamp\" >> $TASK_NAME/vars\necho \"export PLATFORM=$PLATFORM\" >> $TASK_NAME/vars\n\nif which gstat > /dev/null\nthen\n    STAT=gstat\nelse\n    STAT=stat\nfi\n\ntouch sizes\nfor bin in example-tensorflow-mobilenet-v2 tract\ndo\n    if [ -e target/$RUSTC_TRIPLE/release/$bin ]\n    then\n\n        binary_size_cli=$($STAT -c \"%s\" target/$RUSTC_TRIPLE/release/$bin)\n        token=$(echo $bin | tr '-' '_')\n        if [ \"$bin\" = \"tract\" ]\n        then\n            token=cli\n        fi\n        echo binary_size.$token $binary_size_cli >> sizes\n    fi\ndone\n\ncp target/$RUSTC_TRIPLE/release/tract $TASK_NAME\ncp sizes $TASK_NAME\ncp .travis/bundle-entrypoint.sh $TASK_NAME/entrypoint.sh\ntar czf $TASK_NAME.tgz $TASK_NAME/\n\necho $TASK_NAME\n"
  },
  {
    "path": ".travis/minion.sh",
    "content": "#!/bin/bash\n\nset -ex\n. $HOME/.minionrc\n\nexec 200>$LOCKFILE || exit 1\nflock -n 200 || { echo \"WARN: flock() failed.\" >&2; exit 0; }\n\nmkdir -p $WORKDIR/taskdone/\nfor task in `aws s3 ls $S3PATH_TASKS/$PLATFORM/ | awk '{ print $4; }'`\ndo\n    cd $HOME\n    task_name=\"${task%.tgz}\"\n    if [ -e $WORKDIR/taskdone/$task_name ]\n    then\n        continue\n    fi\n    echo considering task $task\n    rm -rf $WORKDIR/current\n    mkdir -p $WORKDIR/current\n    cd $WORKDIR/current\n    aws s3 cp s3://$S3PATH_TASKS/$PLATFORM/$task .\n    tar zxf $task\n    . $task_name/vars\n    cd $task_name\n    (\n        ./entrypoint.sh 2> stderr.log > stdout.log || true\n    )\n    gzip stderr.log\n    gzip stdout.log\n    aws s3 cp stderr.log.gz s3://$S3PATH_RESULTS/$MINION_ID/$task_name/stderr.log.gz\n    aws s3 cp stdout.log.gz s3://$S3PATH_RESULTS/$MINION_ID/$task_name/stdout.log.gz\n    touch $WORKDIR/taskdone/$task_name\n    cat metrics | sed \"s/^/$GRAPHITE_PREFIX.$PLATFORM.$MINION_ID.$TRAVIS_BRANCH_SANE./;s/$/ $TIMESTAMP/\" \\\n        | tr '-' '_' > graphite\n    if nc --version\n    then\n\t# GNU\n\texport GRAPHITE_HOST\n\texport GRAPHITE_PORT\n\tcat graphite | while read line\n\tdo\n\t\techo $line | nc -c -w 1 $GRAPHITE_HOST $GRAPHITE_PORT\n\tdone\n    else\n        # BSD\n\tnc -q 5 $GRAPHITE_HOST $GRAPHITE_PORT < graphite\n    fi\n\ndone\n\nsleep 1\n\necho \"DONE\"\n"
  },
  {
    "path": ".travis/minionrc",
    "content": "MINION_ID=\nLOCKFILE=/tmp/minion-lock\nPLATFORM=raspbian\nGRAPHITE_HOST=graphite-proxy.snips.net\nGRAPHITE_PORT=2003\nGRAPHITE_PREFIX=tract\n\nS3PATH_TASKS=tract-ci-builds/tasks\nS3PATH_LOGS=tract-ci-builds/logs\nS3PATH_RESULTS=tract-ci-builds/logs\nWORKDIR=$HOME/tract-minion\nCACHEDIR=$WORKDIR/cache\n"
  },
  {
    "path": ".travis/native.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nif [ -z \"$RUSTUP_TOOLCHAIN\" ]\nthen\n    export RUSTUP_TOOLCHAIN=1.91.0\nfi\n\nrustup update\n\ncargo update\ncargo check --all-targets --workspace --exclude test-tflite --exclude test-metal --exclude tract-metal\n\n./.travis/onnx-tests.sh\n./.travis/regular-tests.sh\n./.travis/test-harness.sh\n\nif [ -n \"$CI\" ]\nthen\n    cargo clean\nfi\n\nif [ `uname` = \"Linux\" ]\nthen\n    ./.travis/tflite.sh\nfi\n\nif [ -n \"$CI\" ]\nthen\n    cargo clean\nfi\nif nvidia-smi > /dev/null 2>&1\nthen\n    cargo test -p tract-cuda --lib\n    cargo test -p test-cuda\nfi\n\n./.travis/cli-tests.sh\n"
  },
  {
    "path": ".travis/onnx-tests.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nROOT=$(dirname $(realpath $0))/..\n. $ROOT/.travis/ci-system-setup.sh\n\nopset=onnx_\"${1:-1_13_0}\"\n\ncargo -q test -p test-unit-core $CARGO_EXTRA -q \ncargo -q test -p test-onnx-core $CARGO_EXTRA -q --no-default-features --features $opset\ncargo -q test -p test-nnef-cycle $CARGO_EXTRA -q --no-default-features\n"
  },
  {
    "path": ".travis/regular-tests.sh",
    "content": "#!/bin/sh\n\nset -e\nset -x\n\ncd $(dirname $0)\n\n./test-published-crates.sh\nif [ -n \"$CI\" ]\nthen\n    cargo clean\nfi\n./test-rt.sh\nif [ -n \"$CI\" ]\nthen\n    cargo clean\nfi\n"
  },
  {
    "path": ".travis/run-bundle.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nBUNDLE_NAME=$1\n\ntar zxf $BUNDLE_NAME.tgz\n(\n    cd $BUNDLE_NAME\n    . ./vars\n    ./entrypoint.sh\n)\n# rm -rf \"$BUNDLE_NAME\" \"$BUNDLE_NAME.tgz\"\n"
  },
  {
    "path": ".travis/run_all.sh",
    "content": "#!/bin/sh\n\nset -ex\n\n`dirname $0`/native.sh\ncd `dirname $0`/../examples\nfor i in *\ndo\n    (cd $i; cargo test --release)\ndone\n"
  },
  {
    "path": ".travis/setup-sccache.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nexport SCCACHE_DIR=$HOME/.cache/sccache\nexport SCCACHE_CACHE_SIZE=2G\n\nif [ -n \"$GITHUB_ENV\" ]\nthen\n    echo \"SCCACHE_DIR=$HOME/.cache/sccache\" >> $GITHUB_ENV\n    echo \"SCCACHE_CACHE_SIZE=2G\" >> $GITHUB_ENV\n    echo \"RUSTC_WRAPPER=sccache\" >> $GITHUB_ENV\n    echo \"$HOME/.local/bin\" >> $GITHUB_PATH\nfi\n\nLINK=https://github.com/mozilla/sccache/releases/download\nSCCACHE_VERSION=v0.10.0\n\necho $HOME\nif [ `uname` = \"Linux\" ]\nthen\n  SCCACHE_FILE=sccache-$SCCACHE_VERSION-x86_64-unknown-linux-musl\nelse\n  SCCACHE_FILE=sccache-$SCCACHE_VERSION-x86_64-apple-darwin\nfi\n\nmkdir -p $SCCACHE_DIR\nmkdir -p $HOME/.local/bin\nfor i in 1 2 3 4 5\ndo\n    curl -L \"$LINK/$SCCACHE_VERSION/$SCCACHE_FILE.tar.gz\" | tar xz && break\n    sleep 15\ndone\nmv -f $SCCACHE_FILE/sccache $HOME/.local/bin/sccache\nchmod +x $HOME/.local/bin/sccache\n\n\n\n"
  },
  {
    "path": ".travis/test-harness.sh",
    "content": "#!/bin/sh\n\nWHITE='\\033[1;37m'\nNC='\\033[0m' # No Color\n\nif [ -e /proc/cpuinfo ]\nthen\n    grep \"^flags\" /proc/cpuinfo | head -1 | \\\n        grep --color=always '\\(s\\?sse[0-9_]*\\|fma\\|f16c\\|avx[^ ]*\\)'\nfi\n\nset -x\n\nROOT=$(dirname $0)/..\n. $ROOT/.travis/ci-system-setup.sh\n\nset -e\n\nif [ `arch` = \"x86_64\" -a \"$RUST_VERSION\" = \"stable\" ]\nthen\n    ALL_FEATURES=--all-features\nfi\n\nset +x\n\ncargo -q test $CARGO_EXTRA -q -p tract\ncargo -q test $CARGO_EXTRA -q --profile opt-no-lto -p core-proptest-pulse $ALL_FEATURES\ncargo -q test $CARGO_EXTRA -q --profile opt-no-lto -p nnef-inceptionv3 $ALL_FEATURES\ncargo -q test $CARGO_EXTRA -q --profile opt-no-lto -p tf-inceptionv3 $ALL_FEATURES\ncargo -q test $CARGO_EXTRA -q --profile opt-no-lto -p tf-mobilenet-v2 $ALL_FEATURES\ncargo -q test $CARGO_EXTRA -q --profile opt-no-lto -p tfl-mobilenet-v2-q $ALL_FEATURES\n\n"
  },
  {
    "path": ".travis/test-llm.sh",
    "content": "#!/bin/bash\n\nset -e\nset -o pipefail\n\nexport LC_ALL=C\n\nROOT=$(dirname $(dirname $(realpath $0)))\n. $ROOT/.travis/ci-system-setup.sh\n\nmodel=$1\nq=$2\ndevice=$3\nif [ -z \"$device\" ]\nthen\n    device=cpu\nfi\ngeneration=541\n\nif [ \"$model\" = \"all\" ]\nthen\n    for m in \\\n        openelm-270M \\\n\tllama-3.2-1B-instruct \\\n\tllama-3.2-3B-instruct \\\n\tllama-3.1-8B-instruct \\\n\tqwen3-1.7B \\\n\tqwen3-8B\n    do\n        $0 $m $2 $device\n    done\n    exit 0\nfi\n\nmodel=$(echo $model | tr 'A-Z' 'a-z' | tr -d \"_.-\")\n\nfor m in \\\n    apple--OpenELM-270M \\\n    meta-llama--Llama-3.2-1B-Instruct \\\n    meta-llama--Llama-3.2-3B-Instruct \\\n    meta-llama--Llama-3.1-8B-Instruct \\\n    Qwen--Qwen3-1.7B \\\n    Qwen--Qwen3-8B\ndo\n    norm=$(echo $m | tr \"A-Z\" \"a-z\" | tr -d \"_.-\")\n    if [[ \"$norm\" == *\"$model\"* ]];\n    then\n        model_id=$m\n    fi\ndone\n\nif [ -z \"$model_id\" ]\nthen\n    echo \"No model matched\"\nfi\n\nif [ \"$q\" = \"all\" ]\nthen\n    for q in q40ef16 f16f16 f32f32\n    do\n        $0 $1 $q $device\n    done\n    exit 0\nfi\n\nid=$model_id-$q\n\nif which gstat > /dev/null\nthen\n    STAT=gstat\nelse\n    STAT=stat\nfi\n\nnnef=llm/$generation/$id/$id.nnef.tgz\n\n$CACHE_FILE $nnef\n\nif [ -e $MODELS/$nnef ]\nthen\n    size=$($STAT -c %s $MODELS/$nnef)\nelse\n    size=$(curl -s -I $MODELS/$nnef | grep Content-Length | cut -d \" \" -f 2 | tr -cd 0123456789)\nfi\n\nif which nvidia-smi > /dev/null\nthen\n    vram=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | awk '{print $1*1024*1024}')\n    if [ $vram -lt $size ]\n    then\n        echo \"::warning::Skipping this test, not enough VRAM.\"\n        exit 0\n    fi\nfi\n\n$TRACT_RUN -v --nnef-tract-transformers $MODELS/$nnef -O --readings  --assert-maximal-mm-quality-cost 0 $TRACT_EXTRA_ARGS dump -q\nalloc_max=$(cat readings.out | tail -n +2 | awk '{print $10-$11}' | sort -n | tail -1)\nratio=$((alloc_max * 100 / size))\n\necho \"  ###########################################\"\necho \"      Alloc max to model size ratio: ${ratio}%.\"\necho \"  ###########################################\"\n\nlimit=125\n\nif [ $ratio -gt $limit ]\nthen\n    echo \"RSZ max is ${ratio}% the size of the unzipped model!\"\n    exit 1\nfi\n\nfor t in p0s100 p50s50 p99s1 \ndo\n    npz=llm/$generation/$id/$id.$t.io.npz\n    $CACHE_FILE $npz\n\n    key=$id.$t.$(arch).$device\n    expectations=\"$ROOT/.travis/llm-expectations-541\"\n\n    echo\n    echo \"      Key: $key\"\n    echo\n\n    case $device in\n        cuda)\n            DEVICE=\"--cuda\"\n            GPU_ASSERT=\"--assert-op-only Cuda*,Gpu*,DeviceSync*,Const,Source,Range,Gather\"\n        ;;\n        metal)\n            DEVICE=\"--metal\"\n            GPU_ASSERT=\"--assert-op-only Metal*,Gpu*,DeviceSync*,Const,Source,Range,Gather\"\n        ;;\n        *)\n            GPU_ASSERT=\"\"\n        ;;\n    esac\n\n    if [ -n \"$RESET\" ]\n    then\n        $TRACT_RUN -v $MODELS/$nnef $TRACT_EXTRA_ARGS \\\n            --llm --transform unfold-kv-cache -O $DEVICE run --prompt-chunk-size 60 --allow-missing-outputs \\\n            --input-from-npz $MODELS/$npz \\\n            --assert-output-bundle $MODELS/$npz \\\n            --assert-llm-rbo 0.0 \\\n            $approx --allow-float-casts $GPU_ASSERT 2>&1 | tee output.txt\n        found=$(cat output.txt | perl -MPOSIX=floor -ne 'printf(\"%.2f\\n\", floor($1 * 100) / 100) if /LLM RBO:\\s+([\\d.]+)/')\n        ( ( grep -v $key $expectations || true) ; echo $key $found) | sort > $expectations.tmp\n        mv $expectations.tmp $expectations\n    elif [ -n \"$RELAX\" ]\n    then\n        prior=$(grep $key $expectations | cut -f 2 -d ' ')\n        $TRACT_RUN -v $MODELS/$nnef $TRACT_EXTRA_ARGS \\\n            --llm --transform unfold-kv-cache -O $DEVICE run --prompt-chunk-size 60 --allow-missing-outputs \\\n            --input-from-npz $MODELS/$npz \\\n            --assert-output-bundle $MODELS/$npz \\\n            --assert-llm-rbo 0.0 \\\n            $approx --allow-float-casts $GPU_ASSERT 2>&1 | tee output.txt\n        found=$(cat output.txt | perl -MPOSIX=floor -ne 'printf(\"%.2f\\n\", floor($1 * 100) / 100) if /LLM RBO:\\s+([\\d.]+)/')\n        if [ -n \"$prior\" ] && perl -e 'exit($ARGV[0] <= $ARGV[1] ? 1 : 0)' \"$found\" \"$prior\"\n        then\n            found=$prior\n        fi\n        ( ( grep -v $key $expectations || true) ; echo $key $found) | sort > $expectations.tmp\n        mv $expectations.tmp $expectations\n    else # test !\n        expectation=$(grep $key $expectations | cut -f 2 -d ' ')\n        $TRACT_RUN -v $MODELS/$nnef $TRACT_EXTRA_ARGS \\\n            --llm --transform unfold-kv-cache -O $DEVICE run --prompt-chunk-size 60 --allow-missing-outputs \\\n            --input-from-npz $MODELS/$npz \\\n            --assert-output-bundle $MODELS/$npz \\\n            --assert-llm-rbo $expectation \\\n            $approx --allow-float-casts $GPU_ASSERT\n    fi\n\ndone\n"
  },
  {
    "path": ".travis/test-published-crates.sh",
    "content": "#!/bin/sh\n\nWHITE='\\033[1;37m'\nNC='\\033[0m' # No Color\n\nif [ -e /proc/cpuinfo ]\nthen\n    grep \"^flags\" /proc/cpuinfo | head -1 | \\\n        grep --color=always '\\(s\\?sse[0-9_]*\\|fma\\|f16c\\|avx[^ ]*\\)'\nfi\n\nset -x\n\nROOT=$(dirname $0)/..\n. $ROOT/.travis/ci-system-setup.sh\n\nset -e\n\nif [ `arch` = \"x86_64\" -a \"$RUST_VERSION\" = \"stable\" ]\nthen\n    ALL_FEATURES=--all-features\nfi\n\nset +x\ncargo update\n\necho\necho \"$WHITE ### tract ### $NC\"\necho\n\ncargo -q test $CARGO_EXTRA -q -p tract\n\nfor c in data linalg core nnef hir onnx pulse onnx-opl pulse-opl\ndo\n    echo\n    echo \"$WHITE ### $c ### $NC\"\n    echo\n    cargo -q test $CARGO_EXTRA -q -p tract-$c\ndone\n\nif [ `uname` = \"Darwin\" -a -z \"$CI\" ]\nthen\n    echo\n    echo \"$WHITE ### metal ### $NC\"\n    echo\n    cargo -q test $CARGO_EXTRA -q -p tract-metal\nfi\n\nif command -v nvcc >/dev/null 2>&1 && [ -z \"$CI\" ]\nthen\n    echo\n    echo \"$WHITE ### cuda ### $NC\"\n    echo\n    cargo -q test -q -p tract-cuda\nfi\n\n$ROOT/api/proxy/ci.sh\n\n# doc test are not finding libtensorflow.so\nif ! cargo -q test $CARGO_EXTRA -q -p tract-tensorflow --lib $ALL_FEATURES\nthen\n    # this crate triggers an incremental bug on nightly.\n    cargo clean -p tract-tensorflow\n    cargo -q test $CARGO_EXTRA -q -p tract-tensorflow --lib $ALL_FEATURES\nfi\n"
  },
  {
    "path": ".travis/test-rt.sh",
    "content": "#!/bin/sh\n\nWHITE='\\033[1;37m'\nNC='\\033[0m' # No Color\n\nif [ -e /proc/cpuinfo ]\nthen\n    grep \"^flags\" /proc/cpuinfo | head -1 | \\\n        grep --color=always '\\(s\\?sse[0-9_]*\\|fma\\|f16c\\|avx[^ ]*\\)'\nfi\n\nset -x\n\nROOT=$(dirname $0)/..\n. $ROOT/.travis/ci-system-setup.sh\n\nset -e\n\nif [ `arch` = \"x86_64\" -a \"$RUST_VERSION\" = \"stable\" ]\nthen\n    ALL_FEATURES=--all-features\nfi\n\nset +x\n\ncd $ROOT\nfor c in test-rt/test*; do\n    case \"$c\" in\n        test-rt/test-tflite)\n            echo \"$WHITE ### $c ### IGNORED $NC\"\n            continue\n            ;;\n        test-rt/test-metal)\n            if [ \"$(uname)\" != \"Darwin\" ] || [ -n \"$CI\" ]; then\n                echo \"$WHITE ### $c ### IGNORED $NC\"\n                continue\n            fi\n            ;;\n        test-rt/test-cuda)\n            if ! command -v nvcc >/dev/null; then\n                echo \"$WHITE ### $c ### IGNORED $NC\"\n                continue\n            fi\n            ;;\n    esac\n\n    echo\n    echo \"$WHITE ### $c ### $NC\"\n    echo\n    (cd \"$c\" && cargo test -q $CARGO_EXTRA)\n\n    if [ -n \"$CI\" ]; then\n        df -h\n        cargo clean\n    fi\ndone\n\n"
  },
  {
    "path": ".travis/tf.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nif [ -z \"$CACHEDIR\" ]\nthen\n    CACHEDIR=`dirname $0`/../.cached\nfi\n\n\n(cd tensorflow; cargo test --release --features conform)\n"
  },
  {
    "path": ".travis/tflite/Dockerfile.tensorflow-aarch64",
    "content": "# vim: set syntax=Dockerfile:\n\nFROM tensorflow/tensorflow:devel\n\nRUN apt-get update ; apt-get upgrade -y\nRUN apt-get install -y crossbuild-essential-arm64\nCOPY linux_makefile.inc /tensorflow_src/tensorflow/lite/tools/make/targets/linux_makefile.inc\nCOPY disable_nnapi.patch /tensorflow_src\n\nWORKDIR /tensorflow_src\nRUN patch -p1 < disable_nnapi.patch\n"
  },
  {
    "path": ".travis/tflite/Dockerfile.tensorflow-official-rpi",
    "content": "# vim: set syntax=Dockerfile:\n\nFROM tensorflow/tensorflow:nightly-devel\n\nRUN apt-get update ; apt-get upgrade -y\nRUN apt-get -y install git crossbuild-essential-armhf\n\nWORKDIR /tensorflow\nRUN ./tensorflow/lite/tools/make/download_dependencies.sh\n\n\n"
  },
  {
    "path": ".travis/tflite/Dockerfile.tensorflow-rpitools",
    "content": "# vim: set syntax=Dockerfile:\n\nFROM tensorflow/tensorflow:nightly-devel\n\nRUN apt-get update ; apt-get upgrade -y\nRUN apt-get -yy  install git\n\nWORKDIR /tensorflow\nRUN ./tensorflow/lite/tools/make/download_dependencies.sh\n\nRUN git clone https://github.com/raspberrypi/tools /raspitools\nENV PATH=/raspitools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin:$PATH\n\n"
  },
  {
    "path": ".travis/tflite/build_tflite_aarch64.sh",
    "content": "#!/bin/sh\n\nset -ex\nmkdir -p result\n\ndocker build -f Dockerfile.tensorflow-aarch64 --tag tensorflow-aarch64 .\ndocker run --rm -it \\\n    -v `pwd`/result:/result \\\n    tensorflow-aarch64 \\\n    sh -c \"\n         cd /tensorflow_src ;\n         export EXTRA_CXXFLAGS=-flax-vector-conversions \n         export DISABLE_NNAPI=true\n         ./tensorflow/lite/tools/make/download_dependencies.sh\n         make -j 3 -f tensorflow/lite/tools/make/Makefile TARGET=linux TARGET_ARCH=aarch64 ;\n         cp /tensorflow_src/tensorflow/lite/tools/make/gen/linux_aarch64/bin/benchmark_model /result/tflite_benchmark_model_aarch64\n     \"\n"
  },
  {
    "path": ".travis/tflite/build_tflite_raspbian.sh",
    "content": "#!/bin/sh\n\nset -ex\nmkdir -p result\n\n# build pseudo-rpi official tensorflow, https://www.tensorflow.org/lite/rpi, only works on pi3\n\ndocker build -f Dockerfile.tensorflow-official-rpi --tag tensorflow-official-rpi .\ndocker run --rm \\\n    -e CC_PREFIX=arm-linux-gnueabihf- \\\n    -v `pwd`/result:/result \\\n    tensorflow-official-rpi \\\n    sh -c \"\n        make -j 3 -f tensorflow/lite/tools/make/Makefile TARGET=rpi TARGET_ARCH=armv7l;\n        cp /tensorflow/tensorflow/lite/tools/make/gen/rpi_armv7l/bin/benchmark_model /result/tflite_benchmark_model_official_rpi\n    \"\n\n# build with rpi tools (works on rpi0, 1 and 2)\n\ndocker build -f Dockerfile.tensorflow-rpitools --tag tensorflow-rpitools .\ndocker run --rm \\\n    -e CC_PREFIX=arm-linux-gnueabihf- \\\n    -v `pwd`/result:/result \\\n    tensorflow-rpitools \\\n    sh -c \"\n        make -j 3 -f tensorflow/lite/tools/make/Makefile TARGET=rpi TARGET_ARCH=armv6;\n        cp /tensorflow/tensorflow/lite/tools/make/gen/rpi_armv6/bin/benchmark_model /result/tflite_benchmark_model_rpitools\n    \"\n"
  },
  {
    "path": ".travis/tflite/convert_all.sh",
    "content": "\nrun_in_tf_docker() {\n    docker run --rm -v $HOME/.cache:/models -it tensorflow/tensorflow:nightly-devel sh -c \"$@\"\n}\n\n# # inception v3 \n# run_in_tf_docker \"cd /models ; tflite_convert \\\n#     --graph_def_file inception_v3_2016_08_28_frozen.pb \\\n#     --input_arrays input \\\n#     --input_shapes 1,299,299,3 \\\n#     --output_arrays InceptionV3/Predictions/Reshape_1 \\\n#     --output_format tflite \\\n#     --output_file inception_v3_2016_08_28_frozen.tflite\"\n# \n# # arm ml kws\n# run_in_tf_docker \"cd /models ; tflite_convert \\\n#     --graph_def_file ARM-ML-KWS-CNN-M.pb \\\n#     --input_arrays Mfcc \\\n#     --input_shapes 1,49,10 \\\n#     --output_arrays labels_softmax \\\n#     --output_format tflite \\\n#     --output_file ARM-ML-KWS-CNN-M.tflite\"\n\n# hey_snips v1\nrun_in_tf_docker \"cd /models ; tflite_convert \\\n    --graph_def_file hey_snips_v1.pb \\\n    --input_arrays inputs \\\n    --input_shapes 80,40 \\\n    --output_arrays logits \\\n    --output_format tflite \\\n    --output_file hey_snips_v1.tflite\"\n\n# hey_snips v3.1\n# (tflite does not support 1D dil)\n# run_in_tf_docker \"cd /models ; tflite_convert \\\n#     --graph_def_file hey_snips_v3.1.pb \\\n#     --input_arrays inputs \\\n#     --input_shapes 40,40 \\\n#     --output_arrays logits \\\n#     --output_format tflite \\\n#     --output_file hey_snips_v3.1.tflite\"\n# \n# # hey_snips v4 model17, 2seconds\n# (tflite does not support AddN)\n# run_in_tf_docker \"cd /models ; tflite_convert \\\n#     --graph_def_file hey_snips_v4_model17.pb \\\n#     --input_arrays input_node \\\n#     --input_shapes 200,20 \\\n#     --output_arrays output_node \\\n#     --output_format tflite \\\n#     --output_file hey_snips_v4_model17.tflite\"\n"
  },
  {
    "path": ".travis/tflite/linux_makefile.inc",
    "content": "# Settings for Linux.\nifeq ($(TARGET), linux)\n  CXXFLAGS += \\\n    -fPIC \\\n    -DGEMMLOWP_ALLOW_SLOW_SCALAR_FALLBACK \\\n    -pthread\n  # TODO(petewarden): In the future we may want to add architecture-specific\n  # flags like -msse4.2\n\tLIBS := -lstdc++ -lpthread -lm -ldl\n    TARGET_ARCH=aarch64\n  TARGET_TOOLCHAIN_PREFIX := aarch64-linux-gnu-\nendif\n"
  },
  {
    "path": ".travis/tflite/run_all.sh",
    "content": "#!/bin/sh\n\n./benchmark_model --graph=inception_v3_2016_08_28_frozen.tflite\n\n"
  },
  {
    "path": ".travis/tflite.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nROOT=$(dirname $(dirname $(realpath $0)))\n. $ROOT/.travis/ci-system-setup.sh\n\nif [ `uname` = \"Darwin\" ]\nthen\n    brew install coreutils\nfi\nif [ -n \"$GITHUB_ACTIONS\" ]\nthen\n    pip install numpy\nfi\n\ncargo check -p tract-tflite\ncargo -q test -p test-tflite $CARGO_EXTRA -q \n"
  },
  {
    "path": ".travis/travis.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nif [ -z \"$PLATFORM\" ]\nthen\n  .travis/native.sh\nelse\n  .travis/cross.sh\nfi\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: rust\ndist: trusty\ncache:\n  # - cargo\n  #- directories:\n  #- $HOME/cached\naddons:\n  apt:\n    packages:\n    - awscli\n    - jshon\n    - moreutils\nenv:\n  global:\n  - RUST_BACKTRACE=1\n  - AWS_DEFAULT_REGION=us-east-1\n    #  - CACHEDIR=$HOME/cached\n  - secure: DdQqS4fFDevY/JknS0M6CwW+ooj6ZhKmBk4lFliG3WQgjYietbPMCkiHPEC8Df8U07l54+8G4j+sZJJ4VYQY8WQcuKGWt9/ALjoYHYZ1Dlw0KW0rRJ1BZWLUh8MwpJ5pxHSWfl1a8QqTy/0mI3eJ8iVIDxiZR6b1fGwPYDkNXyqnfOvz31X1aMyoGslNkP7LitObCdBJyzobPlvWafGCQLf7oLbK4a5Ysyc9T607n1B0okco3Te2ztahEOwNoxmHlEFRojM6ZmAmo8LzwkCdFQNjHw+mQ3vScC8gpngi61G9U35luAfweMt30m1UmecVGADeEmwSnJLeAHo5HYKT5n6Q1begxlMGMxezinstTHUX6G8EhEumO/ii3PAscFJ6C+VfciGA7JDS2ICghygKSIqQvVeugNR0glW97lhszLnoXCNY45siknAZVTVqwhgn1ctTQiPWqGVuQp+m9NYIoQAYUpOFNo1mEtEjurHOk96Q0XjRJMfUSLOB5KfPakk/ghEY1ZYDDB9wi134f3Z5hLw1FGj/Uiw9LcnEIcORV2o8fbFrb2IgGsKQbRtdPEJ92q5bdeA00TbSrzoDDRFGbwBg+8ibFMF8O2J3Q54sUu6LmkP7qjtMIT2vB09M7LWQYtW3Vd6ovHwI6v+tNJK2D4cJA0KOSwzpOgIBhEubrZI=\n  - secure: su5wLbN7lr59HSKGM8M9pW9VSFMtdTBbQ1oBZFTvw+RNoNew3BBMh8XymbjEv6R+yyGmDAa8Dw6E7HPiBtI5O4q89rHxl7MO5keZINjpXDzTydZ5MQ2juZPIRQpLfpl6AN1meG8I3SoOrTMDgGqfPh2rqEjIS6cBZbo8Re/0KSBuJB781qdT6x80Qfpy87sDu9GTRM8ueXsV+jRw0Yek709m29MSB8pkFAP9OitKHxzF5OFnxVPV2becj53racOe7q9ZE5QWmWnzPZUxflVyrtzDsN0J/C4g0SoEbxLFg1OLbffO1GVJ/Iv6ZeaggzYRvCYwSSANyfqorqSUDT5NPwQlUXjgBPHlOXbCfY2s5hFzQQ3z4R86fyzfdfBQ56uXTXkB4CWpn/JieUoviuDO0/YNaI0KU6hOrOn68BZrBSqwHcxwhtcP3cw/EXfR1aiok7OoDPAWnR4f3Lu/+fmkW+VUEg5Ufh/GgjZt7XwsNBfo+pmvO3mO/5okxa2/HbOwcoTpELAzPMKyh1xn5gjrk5bWcZofeGtFMoXN4+G8+1qlQ/sLp3144QHyRf00n4qlhA6xZwplpBWN12haXyKRx67lPTzE5QuT1BRoyCdRvbjQiOdo61xGvoOK9J8PL9C06xtnKQ+6iDnjFaWielASoENvcNL+DCKqiecpUb5hoR4=\n  - secure: HZHubUhHLYh+v5yuyMy5TmfleHqAdcmVZd6hZf7c2sXQLsrcjoLGPxu4jzS9OJt27Sfp6xXVFeeA7SFDMobxe8AFm3+SRCbzvT6mu8/LlsuTsQO3jYQtt8j5OIhtLQ7yfDyOscXwy2I3SgluhVQ0HCIi6ShQ8YRD1vE119Qku2x/sWlKcZQckcl5T/yyig08sXfOM+IfFQPIW1gvMWM3dv6RigiCiy4qjfeQy8v8xbvbRayXeT4Vpfv9CqN79kAQ03r7MSmBBn6i88mGoQEzVDMEZPZq8rMNqn8qyIn8LxjXaCpUz0fTCYJrHSmzKyCE7+k7IEidlkyT6hJpvpXCfNYBSk2fB7SDxlm98ELVgqPBq6vjYoPaqsqs7Cz+pTTQYOCnCKvRDYhccqhAsgjNVKDRIJc0H7cT5sv4TuJxsMp/vYVh7RxoFem2r37ns4pu8XPP9RVsmoAVYzlHu8Fnd8TWY30MXACf3t43ceaPfor0IARrZcjQR1lt7eMJGQDkduJRxzq0cBB1djP8HfZGab/I0cVFEXGeJWDQfaHZ8Pq/M3+bBPLD9XLqKmpoNbW+gqQQl49/w01/EJrp9QhK/Og6ujfkeA1OCziPCUDLAHYvmwaYZYzV9z5VSPfUYwiiJzva92/ywWfhCmz3SpXPbq8cTPoDWzeBUeqcw8iIWVU=\n  - secure: vH8bS7RVgaHLGZUeqtViCQYDJfhubMiCMETLPD959pv9sODmSfjOhYtFgZtbn0wZ2fhCQFgKhYKUJdti5Vo9OUlyBiUsfLPilAAaeZu0Y2SQIKpbuNU9kJibuzyj6KZoRhjvsifhO5/mB03W7CpzjSGvJntK62BM0b6CrDtUlHlOgjwd3U1c5brZS9VWfnkh8pohgneB/XYtefTJXHuGjJgf75uw2TO/ZKQmmaKJWPoMVN76cgarRmXS/SoGMLr0+7ArnvIMNW9QRMABrSzUgP0RBvNfndwjiIQDZpIefIz/UVTa5e/pS79CLoQKM9FSWZANf3ZJgz0SzYgMprSe9f3RZGu5i0BLQA0SzdxCjCra5/3/pz+p86/iWGHnBfV6pvH9c2W1OUCCTiohNk7bgUfXxVrxk2RHxhc375MFiCxu6KtPRW8kJoRTSZP+k2itaBPUSevV0cdWrVlRjnTwoCskxMIFQH+vStxQjUXV0/g9SZzwdIR/j1aKIjb6VdQS2WOh0+BKgHy0jy2w4GJHtuObIg0aTcQAtt44aK0T/VeHJ0f1FxfjzPxrcqcgSxvi2E4HgedSCvtOHPWs5YYYGt76yH0G5ZOMOF8xP2CRStlcNB0TtLdpcUvQT2ejK7t4sCOj8Kz81s2cbLCZnJdkFaaBsffV7JtbjexXRwohGxI=\n\nmatrix:\n  include:\n    - rust: stable\n    - rust: stable\n      env: PLATFORM=raspbian\n    - rust: 1.35.0\n    - rust: stable\n      script: .travis/tf.sh\n    - rust: stable\n      script: .travis/debug-tests.sh\n    - rust: stable\n      os: osx\n      osx_image: xcode9.3\n      env: PARTIAL_CI=true\n    - rust: stable\n      env: PLATFORM=aarch64-unknown-linux-gnu\n    - rust: stable\n      env:\n        - PLATFORM=armv6vfp-unknown-linux-gnueabihf\n        - TRACT_CPU_EXPECT_ARM32_NEON=false\n    - rust: stable\n      env:\n        - PLATFORM=armv7-unknown-linux-gnueabihf\n        - TRACT_CPU_ARM32_NEON=true\n        - TRACT_CPU_EXPECT_ARM32_NEON=true\n    - rust: stable\n      env: PLATFORM=aarch64-linux-android\n    - rust: stable\n      env: PLATFORM=armv7-linux-androideabi\n    - rust: stable\n      env: PLATFORM=i686-linux-android\n    - rust: stable\n      env: PLATFORM=x86_64-linux-android\n    - rust: stable\n      os: osx\n      osx_image: xcode9.3\n      env: PLATFORM=aarch64-apple-ios\n    - rust: beta\n    - rust: nightly\n    - rust: stable\n      os: windows\n      script: cargo test -p tract-linalg\n  allow_failures:\n   - rust: nightly\n   - os: windows\n\nscript: \".travis/travis.sh\"\n"
  },
  {
    "path": ".vim/coc-settings.json",
    "content": "{\n    \"rust-analyzer.imports.granularity.group\": \"module\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Unreleased\n\n* [Breaking][MSRV] MSRV bumped to 1.91.0 (for `const TypeId::of`).\n\n# 0.23.0-dev.3 — 2026-03-20\n\n### Breaking changes\n\n- **`Value` renamed to `Tensor`** across the entire public API surface (Rust, C, Python). The deprecated `Value` alias has been removed.\n- **Crate renamed: `tract-rs` → `tract`** — update your `Cargo.toml` dependency accordingly. The CLI binary is now `tract-cli` (previously `tract`).\n- **`into_tract()` renamed to `into_model()`** in all API layers.\n- **`DatumType` variant names shortened** — the `TRACT_DATUM_TYPE_` prefix is dropped (C API).\n- **Deprecated state methods removed**: `init_states()`, `state_initializers`, and the `n_states` parameter are gone from `State` trait and `RunTensors`.\n- **Python**: `concretize_symbols` and `pulse` methods replaced by typed transform classes; `TransformSpec` is now an abstract base class.\n\n### Improvements\n\n- **`UnfoldKeyValueCacheTransform`** — explicit KV-cache I/O mode now available as a first-class transform (CLI: `--transform unfold-kv-cache`).\n- **Structured `NodeFilter`** for `FloatPrecisionTranslator` — replaces raw filter strings.\n- Python docs migrated from mkdocs to **Sphinx** (hosted on GitHub Pages with version switcher).\n- New GPU inference section (CUDA example, `Runtime` usage).\n\n# 0.23.0-dev.2 - 2026-02-18\n* This is a pre-release release. It will be a pretty big one, here are some hilights.\n  * New public api: tract-rs should be the main point of entry for any new project. A caveat: it does support most of tract simple uses as is, but some specialized sections like state management and model transforms are not satisfactory yet, so the real 0.23.0 will presumably break these again. The plan is that this facade will be tract public API, and that it will be the only surface under semver rules. Up to there is was essentially everything `pub`, which boils down to mostly \"everything\".\n  * GPU: The new API puts forward the Runtime trait. It allows running models on GPU (runtimes \"cuda\" and \"metal\" offser some support, while \"default\" is the CPU runtime). See https://github.com/sonos/tract/blob/main/examples/nemo-parakeet-asr/src/main.rs#L21 for an example. Instead of calling into_runnable(), use Runtime::prepare().\n  * Additionally, the internal API compatibility is broken in many places (e.g. RunnableModel always takes the model as an Arc<TypedModel> while it was accepting AsRef before). As you're going to need to fix code to upgrade, it is recommended to try and use the new \"tract-rs\" facade (please tell us if the current API coverage is not enough, or awkward to use).\n* [Breaking][MSRV] MSRV bumped to 1.89.0\n* [linalg] Avoid panic in Apple sysctl-based feature probing (AMX detection).\n\n# 0.22.1 - 2026-02-23\n* [backport] Small bug fixes release (Slice decluttering bug)\n\n# 0.22.0 - 2025-08-25\n* [Breaking][MSRV] MSRV bumped to 1.85.0\n* port to edition 2024\n* bump virtuaally each and every dependency\n* (wip, experimental) cuda support for llm\n\n# 0.21.14 - 2026-02-23\n* [backport] Small bug fixes release (Slice decluttering bug)\n\n# 0.21.12 - 2025-04-10\n\n* multithread matmul is feature gated now (\"multithread-mm\" on linal)\n* full hand made arm64 f32-accumulator matmul kit\n* more auditing improvment around einsum and its matmul translations\n* bugfix in matmul translation and gather\n* more test-rt-level coverage of low-level matmuls (metal and cpu)\n* memory arena improvements (metal)\n* q40 for convolution weights\n\n\n# 0.21.11 - 2025-03-19\n\n* [cli] augment audit capabilities for mm implementation choices\n* revisit matmul kernel selection\n* improve gather with compressed inputs\n* revisit slice bubbling up to unlock optimisations\n* fix a bug around flipping substractions\n* support for left q40 input in arm64 f32 accumulating kernels (unlocks q40f32 compression on arm64)\n\n# 0.21.10 - 2025-02-21\n* WIP llm testability (--approx-custom)\n* [metal] ggml-ported kernels\n* WIP einsum-to-matmul testability\n* optimisation around reduce<sum> impacting some modern/exotic normalisation layers\n* WIP towards better handling of shared weights (e.g. embeddings duplication)\n\n# 0.21.9 - 2025-01-08\n* [metal] experimental profile\n* [cpu] new versatile (mmm/mmmv) kernels combinations for various architectures\n* [metal] scaled-masked-softmax detector and impl\n\n\n# 0.21.8 - 2024-12-05\n* [linalg, compression] introduce mmm kits\n* [linalg] (wip) rework f16 on non-f16 machines\n* [linalg] element-wise binary operators optimisation\n* [core, compression] gather with compressed weights\n* [metal] new kernels\n* [metal] new memory management\n* [nnef] opt-in deterministic tar encoding\n\n# 0.21.7 - 2024-09-23\n* [metal] (experimental) introduce partial support for Apple Metal\n* [core] Potential internal API breaking changes (operator names, comparison ops refactored)\n* [data] (experimental) Smarter TDim simplification, handling of Min and Max. TDim assertions for simplifications.\n* [data] (experimental) WIP around multiple scenarios (modes) for LLM inference\n* Extra examples\n* [linalg] (experimental) kernels targetting LLM block-quantized tasks (inc. intel 32x1 q40f32)\n\n# 0.21.6 - 2024-07-24\n* [data] Rework tdim and symbols, introduce inequalities assertions, min and max operators\n* [data] Generalize Blob usage in Tensor\n* [linalg] Rework reduce implementation, introduce more generic binary ops support (wip)\n* [linalg] Introduce multithreaded matrix multiplication runner\n* [linalg] Introduce Q4_0 block quantization for weights (wip)\n* [linalg] Introduce AMX f16 kernels, Neon Q40F16 kernel (experimental)\n* [linalg] wasm f32 4x4 kernel \n* [core] Introduce Opaque and OpaqueFact to escape Tensor and TValue formalism\n* [core] generalize/improve float precision translator, with translation filter\n* [core] Introduce garbage collecting in patch application, new compact algo, and rework constant propagation to spare memory\n* [core] Rework packed format and packing metadata\n* [linalg/core] Introduce multiple packing format for matmul kernels\n* [core] Work In Progress refactoring binary, towards more optimized execution strategies\n* [nnef] inequalities assertions extension, q4_0 extension\n* [tflite] plug in tanh and sigmoid\n\n# 0.21.5 - 2024-05-11\n* [TFLite] fixes for fully connected and max pool layers\n* Allow opting out of new memory friendly execution order optimisation\n\n# 0.21.4 - 2024-04-23\n* More memory/cache friendly execution order\n* Several fixes around symbolic dimensions computation (some should help with attention models)\n\n# 0.21.3 - 2024-04-03\n* [AMX] Put AMX for iOS behind a feature gate (\"tract-linalg/apple-amx-ios\").\n\n# 0.21.2 - 2024-03-29 (yanked)\n* [ONNX] Support for external storage of tensors with offset and length\n* [ONNX] Lots of fixes around binary quantized operators (add, mul, etc)\n* [PY] Fix python source distribution\n* [AMX] Activate AMX on iOS\n* [API] Introduce transforms in external api\n* [BLAS] Introduce a simple BLAS transform for Matrix multiplication\n* [F16] Introduce a Reduce<MeanOfSquares> that solves many L2 normalization errors in f16\n\nThis version has been yanked to revert systematic activation of AMX on iOS. AMX is a private API and Apple may reject an App that performs AMX instructions.\n\n# 0.21.1 - 2024-02-08\n* [ONNX] Support for external storage of tensors with offset and length\n\n# 0.21.0 - 2024-01-16\n* MSRV is now 1.75.0\n* [internal] ConvUnary and MatmulUnary are replaced by binary, potentially dynamic equivalent\n\n# 0.20.22 - 2023-11-28\n* [ONNX] LayerNormalization support\n\n# 0.20.21 - 2023-10-31\n* [ONNX] ignoring output shapes is now the default\n* \n\n# 0.20.18 - 2023-08-30\n* [intel] fix in AVX512F matrix vector product\n* [tflite] alpha, embryonic support. some convolutional models working.\n* [kaldi] remove abandonned kaldi experimental support\n* [refactoring] Runtime abstraction and runtime-targetting tests\n* [refactoring] Refactoring Python and C API around a possible tract-api. Introducing dylib support.\n* [pytorch compat] fixes around node names starting by / (bug triggered by recent pytorch versions)\n\n0.20.7 to 0.20.17 are misfires\n\n# 0.20.6 - 2023-06-09\n* Bug fixes, fix display of If operator\n\n# 0.20.5 - 2023-05-26\n* Various bugfix around Einsum\n* Einsum now has functions to translate to MatMul and other axes manipulations\n\n# 0.20.0, 0.20.1, 0,20.2, 0.20.3 - 2023-04-25\n* [optim] 32x32 f32 AMX kernel (for Apple Silicon M family)\n* [optim] bunch of AMX512F kernels (square, skinny, vector)\n* [ONNX] introduce Trilu, TopK\n* [NNEF/OPL] submodel loader\n* [ONNX] support alternative layout for LSTM (layout=1, batch becomes first axis)\n* [ONNX] If operators with dynamic condition (very basic optimisations, no nnef support yet).\n\n# 0.19.9 & 0.19.10 - 2023-04-17\n* HardSwiwh ONNX, tract_core_hard_swish in NNEF/OPL\n* introducing tract_core_submodel in NNEF/OPL\n* JSON resource loader in NNEF/OPL\n* Profiling API tweaks\n* `--folded` view for model command line dump (hide Scan loops)\n\n# 0.19.8 - 2023-03-27\n* Various bug fixes\n\n# 0.19.7 & 0.19.6 - 2023-02-24\n* more bug fixes\n* wip on python doc auto-deploy\n\n# 0.19.5 - 2023-02-22\n* 0.19.3 and 0.19.4 are release misfires\n* lots of bugfixes following 0.19 big changes\n* introducing the JSON NNEF resource\n\n# 0.19.2 - 2023-01-30\n* [NNEF/OPL] introduce json resource loader\n* extend Complex number support (under a feature flag)\n\n# 0.19.1 - 2023-01-23\n* [nnef] new identifier syntax is now opt-in for serialization (both accepted at loading)\n* alpha-level C interface. how and how to deploy it (where to put the .h, whether or not to build and ship dylibs)\n* alpha-level python interface. deployed on pypi as \"tract\". At this stage, API is undocumented and may still change significantly.\n\n# 0.19.0 - 2023-01-11\n* [BREAKING] TValue are now used in run() instead of the previous mix of Tensor and Arc<Tensor>\n* internal API breaking changes: no more op_families, libcli split away. State is no longer Send (but can be \"frozen\" to a Send counterpart).\n* Symbols can now be String instead of char. They are not shared globally anymore, but scoped in the Model instead.\n* [pulse] S symbol is no longer magic. The time dimension symbol must be provided at pulsification time.\n* [pulse] In most cases, we can now pulsify without an explicit pulse len (pulse len can be expression).\n* [cli] deprecated \"x\" syntax for shape is removed\n* [nnef/opl] new i\"...\" syntax for escaping identifiers: i\"some arbitrary string\". Allow serialization of any ONNX model with any kind of string as node names.\n* [ONNX] Signal processing operators (DTF, STFT, MelWeightMatrix, BlackmanWindow, HammingWindow, HannWindow)\n* [ONNX] bitwise operations\n* [ONNX] Compatibility target raised to operator set 18\n\n# 0.18.3 - 2022-10-27\n* [NNEF] Introduce a \"resource\" extension for loading values from a separate source (as a config file)\n* Workaround for cpu detection failure on FreeBSD / arm64\n* Various bug fixes\n\n# 0.18.2 - 2022-10-18\n* [pulse] improve convolution (and others) pulsification to avoid some unecessary buffering delay\n* [cli] support multiple streaming inputs and outputs\n* [ONNX] more relaxed Clip operator rules\n\n# 0.18.1 - 2022-10-06\n* prepare NNEF for further tract-opl extension (resource support)\n* more generic matmul\n* optimise some EinSum cases as matmul\n\n# 0.18.0 - 2022-09-21\n* [ONNX Breaking] Several changes to move towards supporting ONNX symbolic dimensions (actual fixes, but they may break stuff that was working more or less by accident). It may be required to erase output shapes explicitely when input shape is overriden on models that were working before.\n* [CLI breaking] ONXN symbolic dimensions has some impact here too. --input-bundle is deprecated, is was overriden and ambiguous. Instead, there is a  --input-facts-from-bundle global option, and a --input-from-bundle option in the subcommands run, profile, dump. --allow-random-input is also moved to subcommands. We think all previously supported behaviours are still there. Please open issues if not.\n\n# 0.17.7 - 2022-09-05\n* clippy up all tract code\n* various fixes\n* 0.17.5 and 0.17.6 are misfired\n\n# 0.17.4 - 2022-08-11\n* [cli] global --set (as a somehat cleaner --concretize successor) allow to set a symbol value after decluttering\n* [cli] run --save-outputs output.npz to save execution outputs\n* dozens of fixs and code cleanup (clippy-fication in progress)\n\n# 0.17.3 - 2022-07-25\n* [License] Allowing https://spdx.org/licenses/Unicode-DFS-2016.html (no tldr yet, but pretty similar to BSD-2)\n* [Breaking] CLI --json option reports costs as strings instead of numbers (in order to allow symbolic values).\n* Sigmoid/Tanh f32 reimpl, plus new f16 impl.\n\n# 0.17.1 - 2022-07-11\n* Sanitiser=address in the CI. Fixed a couple of overflow/memleaks. (Nothing looked too awful.)\n* ONNX NonMaxSuppression\n\n# 0.17.0 - 2022-06-13\n* [Breaking] [ONNX-ML] TreeEnsembleClassifier with binary output (single class) now mimicks scikit-learn output layout.\n\n# 0.16.9 - 2022-06-10\n* bump ONNX protobuf file and support external tensors format\n* new \"skinny\" kernels for avx2/fma f32 multiplication (positive impact on low, non 1 batch size for DNN-heavy loads)\n\n# 0.16.7 - 2022-05-16\n* Softmax is now an operator in core, coming with a direct quantized implementation\n* new TypedFact constructor API ( f32::fact(&[1, 4, 12]), f32::fact(shape!(Symbol::from('N'), 12)))\n* fixes and optimisation of re-quantization pipeline\n* fixes around symbols in NNEF/OPL\n\n# 0.16.6 - 2022-05-03\n* Various changes around quantization support (qi32 appearance)\n\n# 0.16.5 - 2022-04-27\n* Intel optimisation are back\n* Range is now more flexible, should unlock some BERT models with symbolic dimensions.\n\n# 0.16.4 - 2022-04-14\n* some optimisations in depthwise convolutions\n* various bugfixes\n* [Breaking] Fixed nnef \"tile\" operator definition (\"repeats\" is plural). As a consequence models using \"tile\" serialized with tract with prior versions can not be loaded anymore (and vice-versa).\n\n# 0.16.3 - 2022-03-30\n* [Breaking] tract-opl models Scan syntax changed a bit. Models exported by <0.16.2 are loadable in >=0.16.2, but not the other way around.\n* Optimisation in deconv\n\n# 0.16.1 - 2022-03-02\n* [Breaking] Minimum Rust Supported Version is now 1.59.0\n* [Breaking] Small API changes in model api: .compact(), .optimize(), .declutter() now take &mut self and work in place.\n* [LICENSE] Only the licensing for dependencies of the top-level library crates (tensorflow, onnx, kaldi, pulse) will now be monitored. The command line tool (tract crate in cli folder) is for developpers (tract developpers or tract integrators), is not meant to be shipped to end-user, and it concentrates most of the license and dependency complexity.\n* [LICENSE] BSD-3-Clause is now accepted in tract.\n* Optimisations around convolutions and deconvolution\n* Optimisation on Cortex-A53, first round of Cortex-A55 optimisation too.\n\n# 0.15.8 - 2021-11-18\n* Fix brand new ArrayFeatureExtractor inference\n\n# 0.15.7 - 2021-11-16\n* ONNX ArrayFeatureExtractor\n* ConvTranspose/deconv optimisation\n\n# 0.15.6 - yanked \n* just a release script failure\n\n# 0.15.5 - 2021-10-26\n* hold half at 1.7.x for compat with rust 1.50\n\n# 0.15.4 - 2021-10-21\n* ConvTranspose/deconv pulse support\n* ONNX SpaceToDepth/DepthToSpace\n\n# 0.15.3 - 2021-07-29\n* optimise i8*u8, u8*i8 and u8*u8 matrix products (and convo)\n\n# 0.15.2 - 2021-07-09\n* bump prost dep\n\n# 0.15.1 - 2021-07-08\n* some optimisations for arm32 (cortex-a7 and a9)\n\n# 0.15.0 - 2021-06-24\n\n* Switched the order of item_type and item_type_vendor in the NNEF tendor format to be consistent with NNEF-tools, and changed the item_type of integers due to an error in the specification. Breaking for tensor files containing integers or strings.\n* Scan output batching optimisation\n* Concat pulsification over a secondary axis\n* new aarch64 16x4 f32 kernel\n\n## 0.14.2 - 2021-05-27\n\n* better handling of errors in ONNX parser\n* fix/workaround some performance regressions bubling from recent ndarray changes\n\n## 0.14.1 - 2021-05-18\n\n* ONNX ConvTranspose, Gather, GatherND, GatherElements, Scatter, ScatterND, ScatterElements support (and NNEF deconv)\n* Fixes around integer serialisation in NNEF\n* workaround subtle breaking changes in ndarray (between 0.15.1 and 0.15.2)\n\n## 0.14.0 - 2021-04-19\n\n* low-level functions in linalg are now version tagged: two versions of tract can now co-exist in the same binary\n* rustc minimal version is now 1.50\n* dependencies version bumps (ndarray, itertools, and others)\n\n## 0.13.2\n\n* fix sigmoid and tanh variability on intel\n\n## 0.13.1\n\n* temporary disable binary unicast add fusing (too many bugs)\n\n## 0.13.0\n\n* Release are now \"in sync\": all tract crate versions on a build *must* be aligned\n* optimisations, with a focus on aarch64\n\n## 0.12.5 - 2021-01-12\n\n* Dependency bumps\n\n## 0.12.4 - 2021-01-06\n\n* 0.12.3 is a misfire\n* hotfixes on 0.12.2 new tree classifier\n* fix X compilation from macos/aarch64 to macos/intel\n\n## 0.12.2 - 2021-01-05\n\n* ONNX-ML: CategoryMapper and TreeEnsembleClassifier (partial, SoftmaxZero and Probits are missing). With NNEF support.\n* cargo-deny enforces licences choices\n\n## 0.12.1 - 2020-12-11\n\n* 0.12.0 is a misfire.\n\n* API BREAKING: TypedFact::dt_shape & friends can not fail anymore, no longer return a result (remove `?`)\n* Breaking: Rust minimal version bumped to 1.42\n\n* Early, basic, correct but slow support for i8 by u8 matrix mult.\n* Support for Apple Silicon, aka M1, aka aarch64 darwin (but not in CI yet)\n* dynamic quantization convolution support\n* release now ships cli musl builds for linux\n* optimizations targetting small Cortex-A (like 7, 8, and 9)\n* command line dump --profile --cost now computes flops\n* ONNX: OneHot op support\n\n## 0.11.2 - 2020-10-26\n\n* ONNX: new op: DynamicQuantizeLinear\n* tract-data crate split from core, containing tensor, dim, and datum types.\n\n## 0.11.1 - 2020-10-20\n\n* switch from error_chain to anyhow\n* simplify trivial gathers to a slice\n* generalize symbolic dimension a bit: support \"2S\" and the like\n* deprecate \"x\" syntax in CLI, please use `,`  instead\n\n## 0.11.0\n\n### Breaking \n\n* NNEF: tract-nnef no longer performs gunziping, but expect an uncompressed tar\n    stream. We found out is it counter-productive (weights matrices are more or\n    less random, they do not compress easily, and decompression is expensive).\n    NNEF networks in the wild are .tgz file. Using flate2, decompression is a\n    one-liner, but it must be done by the client code now.\n* bumped extended nnef compat version (unchecked at this stage) to \"alpha1\"\n* move pulse operators and translation to their own crate and nnef registry\n* generalize TDim to support an arbitrary number of symbols\n* concretize_stream_dim is superseded by concrentize_dims\n\n### Notable\n\n* new crates, building on tract-opl introduction:\n    * *tract-pulse-opl*: pulse runtime (handful of ops, including Delay) is now separated from core\n    * *tract-onnx-opl*: onnx runtime (4 ops not belonging in core)\n    * *tract-pulse*: pulsification of models (model-translation time)\n    * tract-onnx is now limited to onnx model loading and conversion\n\n## 0.10.10 - 2020-08-30\n\n* load a NNEF as a TypedModel using tract_nnef, and from the CLI\n* dump a tract TypedModel to NNEF (with extensions for op not nnef compatbile)\n* not a full coverage of nnef, but enough for most CNN (image categorizers zoo working)\n* 80% of onnx tests are surviving a NNEF dump and reload at this stage\n\n## 0.10.0 - 2020-07-28\n\n### ONNX\n\n* covered operators compatible with Operator Sets 9, 10, 11 (new) and 12 (new)\n\n### API Breaking\n\n* Tensor::l1 method is gone\n\n### Windows\n\n* Support for -gnu targets (non-mvsc).\n\n### Notable\n\n* --cost now gives the number of parameters in the model\n* SimpleState is clonable again (actually useful !)\n\n## 0.9.2 - 2020-06-16\n\n* introduce `TypedModel::method.concretize_stream_dim`\n* various pulsification bugfixes\n\n## 0.9.1 - 2020-06-16\n\n* fix Reshape with TDim\n\n## 0.9.0 - 2020-06-15\n\nStill no shortage of version numbers...\n\n### API Breakage\n\n* NormalizedModel (and friends) are gone. They were only useful as a pre-pulse transformation pre-requisite that the current TypedModel (& co) meets.\n* TypedModel::into_optimized() is gone. InferenceModel::into_optimized() stays as an end-to-end shortcut for simple cases. It does .into_typed()?.declutter()?.optimize()).\n* TypedModel::codegen() is now ::optimize()\n\n## 0.8.0 - 2020-06-13\n\nI wish I had seen these issues yesterday. Anyway, version numbers are cheap.\n\n* Bumping minimum rust to 1.41\n\n## 0.7.0 - 2020-06-12\n\n* CLI refactoring (hopefully stabilizing a bit?)\n    * `profile --bench` is now bench\n    * profile is now `dump --profile`\n    * cost is now `dump --cost`\n    * profiling is now done during a full net instead of per op\n    * new \"compact\" graph dumper, profile visual hints\n    * `dump --cost --profile --json` output profiling and cost information\n    * show logical names for ops instead of the Op struct names (not 100% sure it's right)\n    * criterion integration\n* WASM support for tract-onnx and tract-tensorflow targets (CI)\n* Convenience methods added to Models to allow model building in fluent style, up to Plan instantiation (SimplePlan now nicknamed RunnableModel). Non breaking.\n* Support for ONNX bidi LSTM (CI), GRU and RNN (untested, consider alpha)\n* Fixes around nets with a non trivial batch size (axis simplification code, matmul op fusion)\n\n## 0.6.3 - 2020-04-25\n\n* Lock ndarray version to dodge rustc/llvm issue (https://github.com/rust-lang/rust/issues/71506)\n\n## 0.6.2 - 2020-04-15\n\n* Use http://gihub.com/kali/readings for instrumentation.\n\n## 0.6.0 - 2020-02-19\n\n### Notable\n\n* New jupyter/keras/tf example\n* ARMv8 tanh / sigmoid optimisation\n\n### API Breaking\n\n* refactor exports and dependencies\n    * preferred way to use tract is now to `use tract_tensorflow::prelude::*;`\n    * singleton framework is built by `let tensorflow = tensorflow()`. The Framework trait is in the prelude too.\n    * the prelude contains a reexport of `tract_core`, and of ndarray as `tract_ndarray`\n    * no more need to declare dependency on `tract-core` and/or `tract-linalg` in Cargo.toml\n    * same goes for `tract_onnx`\n\n## 0.5.9 - 2020-02-07\n\n### Breaking\n\n* Rustc minimum version is now 1.39\n\n### Onnx\n\n* Support for MatMulInteger, ConvInteger\n* Support for QuantizeLinear DequantizeLinear\n* Basic support for QLinearMatMul, QLinearConv\n\n## 0.5.6 - 2019-10-30\n\n### Tensorflow\n\n* Initial support for GatherV2\n\n### Onnx\n\n* Fix PReLu normalization\n\n## 0.5.5 - 2019-10-25\n\n### Tensorflow\n\n* Initial support for AddV2, Mean, Min, Prod, Sum\n\n## 0.5.4 - 2019-09-30\n\n### Notable\n\n* Make Onnx loader operator set aware, and Slice-10 support.\n* Cost now reports Delay ops buffer size\n* Bump dependencies (protobuf) and fix codegen\n* Windows CI now performs a top-level \"cargo check\"\n\n## 0.5.1 - 2019-09-24\n\n### Bugfix\n\n* remove the no_panic checks, as too fragile (breaking non-lto builds)\n\n## 0.5.0 - 2019-09-20\n\n### Breaking\n\n* Change tensor facts names for consistency: TensorFact is now InferenceFact.\n\n### Notable\n\n* Introduce Windows support, including CI coverage for linalg\n* Switch from Travis to GitHub Actions\n* Internal refactoring around tract-core canonic opset\n* Tract CLI can now compute a FLOP number for networks (\"cost\" subcommand). \n    Furthermore the CI asserts its value for a few networks to prevent optimisation regressions.\n* Fix: handling of -1 in ONNX Reshape op\n\n## 0.4.2 - 2019-09-10\n\n* Fix release script after 0.4.1 release disaster.\n\n## 0.4.1 - 2019-09-09 [YANKED]\n\n* Fix for OS where CARGO_CFG_TARGET_FAMILY is undefined\n* Linear Algebra package refactor\n* tract-core canonic operator set introduction\n* significant performance boost (up to 20% on some real-life networks)\n\n## 0.4.0 - 2019-07-30\n\n* Start Kaldi networks support (LSTM, Renorm, Affine, downsample)\n\n## Before...\n\nThis Changelog started way too late. But better late than never.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"2\"\nmembers = [\n    \"data\",\n    \"linalg\",\n    \"core\",\n    \"pulse\",\n    \"pulse-opl\",\n    \"hir\",\n    \"nnef\",\n    \"nnef/cli\",\n    \"nnef/nnef-resources\",\n    \"tensorflow\",\n    \"tflite\",\n    \"onnx-opl\",\n    \"onnx\",\n    \"libcli\",\n    \"cli\",\n    \"gpu\",\n    \"metal\",\n    \"extra\",\n    \"transformers\",\n    \"cuda\",\n\n    \"api\",\n    \"api/rs\",\n    \"api/ffi\",\n    \"api/proxy\",\n    \"api/proxy/sys\",\n\n    \"examples/face_detection_yolov8onnx_example\",\n    \"examples/face_similarity_arcface_onnx\",\n    \"examples/tensorflow-mobilenet-v2\",\n    \"examples/tflite-mobilenet-v3\",\n    \"examples/keras-tract-tf2\",\n    \"examples/nemo-parakeet-asr\",\n    \"examples/nemo-nemotron-asr\",\n    \"examples/nnef-dump-mobilenet-v2\",\n    \"examples/nnef-mobilenet-v2\",\n    \"examples/nnef-mobilenet-v2-api\",\n    \"examples/onnx-mobilenet-v2\",\n    \"examples/pytorch-albert-v2\",\n    \"examples/pytorch-resnet\",\n    \"examples/causal_llm\",\n    \"examples/stable-diffusion\",\n    \"examples/stable-diffusion-3\",\n    \"examples/stable-diffusion-xl\",\n\n    \"harness/core-proptest-pulse\",\n    \"harness/nnef-inceptionv3\",\n    \"harness/tf-inceptionv3\",\n    \"harness/tf-mobilenet-v2\",\n    \"harness/tfl-mobilenet-v2-q\",\n\n    \"test-rt/infra\",\n    \"test-rt/suite-unit\",\n    \"test-rt/suite-onnx\",\n    \"test-rt/test-f16\",\n    \"test-rt/test-blas\",\n    \"test-rt/test-metal\",\n    \"test-rt/test-cuda\",\n    \"test-rt/test-unit-core\",\n    \"test-rt/test-onnx-core\",\n    \"test-rt/test-nnef-cycle\",\n    \"test-rt/test-tflite\"\n]\n\n# same, without metal, test-metal, cuda, test-cuda and test-tflite which are probelematic on specific targets\ndefault-members = [\n    \"data\",\n    \"linalg\",\n    \"core\",\n    \"pulse\",\n    \"pulse-opl\",\n    \"hir\",\n    \"nnef\",\n    \"nnef/cli\",\n    \"nnef/nnef-resources\",\n    \"tensorflow\",\n    \"tflite\",\n    \"onnx-opl\",\n    \"onnx\",\n    \"libcli\",\n    \"cli\",\n    \"extra\",\n    \"transformers\",\n\n    \"api\",\n    \"api/rs\",\n    \"api/ffi\",\n\n    \"examples/face_detection_yolov8onnx_example\",\n    \"examples/face_similarity_arcface_onnx\",\n    \"examples/tensorflow-mobilenet-v2\",\n    \"examples/tflite-mobilenet-v3\",\n    \"examples/keras-tract-tf2\",\n    \"examples/nnef-dump-mobilenet-v2\",\n    \"examples/nnef-mobilenet-v2\",\n    \"examples/onnx-mobilenet-v2\",\n    \"examples/pytorch-albert-v2\",\n    \"examples/pytorch-resnet\",\n\n    \"harness/core-proptest-pulse\",\n    \"harness/nnef-inceptionv3\",\n    \"harness/tf-inceptionv3\",\n    \"harness/tf-mobilenet-v2\",\n    \"harness/tfl-mobilenet-v2-q\",\n\n    \"test-rt/infra\",\n    \"test-rt/suite-unit\",\n    \"test-rt/suite-onnx\",\n    \"test-rt/test-f16\",\n    \"test-rt/test-blas\",\n    \"test-rt/test-unit-core\",\n    \"test-rt/test-onnx-core\",\n    \"test-rt/test-nnef-cycle\",\n]\n\n[workspace.package]\nrust-version = \"1.91\"\n\n[workspace.dependencies]\naccelerate-src = \"0.3\"\nanstyle = \"1.0.2\"\nanstyle-parse = \"1.0.0\"\nanstyle-query = \"1.0.0\"\nanyhow = \"1.0.43\"\nanymap3 = \"1.0\"\napprox = \"0.5\"\natty = \"0.2.14\"\nbit-set = \"0.10.0\"\nboow = \"0.1.3\"\nbox_drawing = \"0.1.2\"\nbyteorder = \"1.4.3\"\nbytes = \"1.0.1\"\ncc = \"1.0.69\"\nclap = { version = \"4\", features = [ \"cargo\", \"derive\" ] }\ncolorous = \"1.0.5\"\ncore_affinity = \"0.8.0\"\ncriterion = \"0.8\"\ncudarc = { version = \"0.19\", features = [\"dynamic-loading\", \"f16\", \"cudnn\"] }\nderive-new = \"0.7\"\ndinghy-test = \"0.8\"\ndirs = \"6.0.0\"\ndowncast-rs = \"2.0\"\ndyn-clone = \"1.0.4\"\ndyn-eq = \"0.1\"\ndyn-hash = \"1.0\"\nenv_logger = \"0.11\"\nerased-serde = \"0.4\"\nflatbuffers = \"25.12.19\"\nflate2 = \"1.0.20\"\nfloat-ord = \"0.3.2\"\nfs-err = \"3\"\nfs2 = \"0.4.3\"\ngetrandom = \"0.4\"\nhalf = { version=\">=2.4,<3.0\", features = [ \"std\", \"num-traits\" ] }\nhome = \"=0.5.12\"\nicu_normalizer = \"2.1\"\nicu_normalizer_data = \"2.1\"\nicu_properties = \"2.1\"\nicu_properties_data = \"2.1\"\nidna_adapter = \"1.2.0\"\nimage = \"0.25\"\ninventory = \"0.3.21\"\nitertools = \"0.14\"\nlazy_static = \"1.5.0\"\nlibc = \"0.2.164\"\nlibloading = \"0.9\"\nlibm = \"0.2.11\"\nliquid = \"0.26.8\"\nliquid-core = \"0.26.8\"\nliquid-derive = \"0.26.8\"\nlitemap = \"0.8\"\nlog = \"0.4.14\"\nmaplit = \"1.0.2\"\nmemmap2 = \"0.9\"\nmetal = { version = \"0.33.0\" }\nndarray = \"0.17\"\nndarray-npy = { version = \"0.10\", features = [ \"compressed_npz\" ] }\nnom = \"8.0.0\"\nnom-language = \"0.1\"\nnu-ansi-term = \"0.50\"\nnum-complex = \"0.4.0\"\nnum-integer = \"0.1.44\"\nnum-traits = \"0.2.14\"\nnum_cpus = \"1\"\nopenblas-src = { version = \"0.10\", features = [\"static\"] }\npastey = \"0.2\"\nproptest = \"1.0.0\"\nprost = \"0.14\"\nprost-types = \"0.14\"\npy_literal = \"0.4.0\"\nrand = \"0.10\"\nrand_distr = \"0.6\"\nrayon = \"1.10\"\nreadings-probe = \"0.1.8\"\nregex = \"1.5.4\"\nron = \"0.12\"\nreqwest = { version = \"0.13\", features = [ \"blocking\", \"rustls-no-provider\" ], default-features = false }\nrustfft = { version = \"6.1\", features = [ \"neon\" ] }\nrustls = { version = \"0.23\", default-features = false, features = [ \"ring\", \"std\", \"tls12\" ] }\nwebpki-roots = \"1\"\nsafetensors = \"0.7\"\nscan_fmt = \"0.2.6\"\nserde = { version = \"1.0.127\", features = [ \"derive\" ] }\nserde_json = \"1.0\"\nsimd-adler32 = { version = \"0.3.7\", features = [\"std\"] }\nsmallvec = \"1.6.1\"\nstring-interner = \"0.19\"\ntar = \"0.4.37\"\ntempfile = \"3.8\"\ntensorflow = \"0.21.0\"\ntflitec = { git = \"https://github.com/kali/tflitec-rs.git\", rev=\"9ceb838\" }\ntime = \"0.3.23\"\ntokenizers = \"0.22\"\nunicode-normalization = \"0.1.19\"\nwalkdir = \"2.3.2\"\nzerofrom = \"0.1.5\"\ntract-api = { version = \"0.23.0-pre\", path = 'api' }\ntract-core = { version = \"0.23.0-pre\", path = 'core' }\ntract-cuda = { version = \"0.23.0-pre\", path = 'cuda' }\ntract-data = { version = \"0.23.0-pre\", path = 'data' }\ntract-extra = { version = \"0.23.0-pre\", path = 'extra' }\ntract-gpu = { version = \"0.23.0-pre\", path = 'gpu' }\ntract-hir = { version = \"0.23.0-pre\", path = 'hir' }\ntract-libcli = { version = \"0.23.0-pre\", path = 'libcli' }\ntract-linalg = { version = \"0.23.0-pre\", path = 'linalg' }\ntract-metal = { version = \"0.23.0-pre\", path = 'metal' }\ntract-nnef-resources = { version = \"0.23.0-pre\", path = 'nnef/nnef-resources' }\ntract-nnef = { version = \"0.23.0-pre\", path = 'nnef' }\ntract-onnx-opl = { version = \"0.23.0-pre\", path = 'onnx-opl' }\ntract-onnx = { version = \"0.23.0-pre\", path = 'onnx' }\ntract-pulse-opl = { version = \"0.23.0-pre\", path = 'pulse-opl' }\ntract-pulse = { version = \"0.23.0-pre\", path = 'pulse' }\ntract-tensorflow = { version = \"0.23.0-pre\", path = 'tensorflow' }\ntract-tflite = { version = \"0.23.0-pre\", path = 'tflite' }\ntract-transformers = { version = \"0.23.0-pre\", path = 'transformers' }\ntract = { version = \"0.23.0-pre\", path = 'api/rs' }\ntract-proxy-sys = { version = \"0.23.0-pre\", path = 'api/proxy/sys' }\ntract-cli = { version = \"0.23.0-pre\", path = 'cli' }\ntract-ffi = { version = \"0.23.0-pre\" }\ntract-proxy = { version = \"0.23.0-pre\" }\n\n\n[profile.opt-no-lto]\ninherits=\"release\"\nlto=false\n\n[profile.release]\n# debug = true\nlto = true\n\n[profile.bench]\ndebug = true\n\n[profile.dev.package.\"*\"]\nopt-level = 2\n\n[profile.dev.build-override]\ndebug = false\n# strip = \"debuginfo\" does not work on android and ios\nincremental = false\n"
  },
  {
    "path": "LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![tract-logo](assets/tract-logo/PNG/tract-horizontal-blue.png)\n\n![Rust](https://img.shields.io/badge/rust-%23000000.svg?style=for-the-badge&logo=rust&logoColor=white)\n![rustc >= 1.91.0](https://img.shields.io/badge/rustc-%3E%3D1.91.0-brightgreen)\n![MIT/Apache 2](https://img.shields.io/crates/l/tract)\n[![Native Linux test status](https://github.com/snipsco/tract/workflows/Native%20Linux/badge.svg)](https://github.com/snipsco/tract/actions)\n[![Embedded targets status](https://github.com/snipsco/tract/workflows/Embedded%20targets/badge.svg)](https://github.com/snipsco/tract/actions)\n[![Doc](https://docs.rs/tract-core/badge.svg)](https://docs.rs/tract-core)\n\n[![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54)](https://pypi.org/project/tract/)\n\n\nSonos' Neural Network inference engine.\n\n_This project used to be called tfdeploy, or Tensorflow-deploy-rust._\n\n## What ?\n\n`tract` is a Neural Network inference toolkit. It can read ONNX or NNEF, optimize them and run them.\n\n## Quick start, examples\n\n* [MobileNet v2 with ONNX](examples/onnx-mobilenet-v2)\n* [BERT example with ONNX](examples/pytorch-albert-v2)\n* [MobileNet v2 with TensorFlow](examples/tensorflow-mobilenet-v2)\n* [From Keras and TensorFlow 2 to tract](examples/keras-tract-tf2)\n* [ResNet with PyTorch](examples/pytorch-resnet)\n\nThere is also [some technical documentation](doc/) and [blog](https://tech-blog.sonos.com/posts/optimising-a-neural-network-for-inference/) posts.\n\n## Tract in the landscape\n\n### ONNX\n\nAs of today, `tract` passes successfully about 85% of ONNX backends\ntests. All \"real life\" integration tests in ONNX test suite are passing: \nbvlc_alexnet, densenet121, inception_v1, inception_v2, resnet50, shufflenet,\nsqueezenet, vgg19, zfnet512.\n\nNotable missing parts are operators dealing with Tensor Sequences and Optional Tensors : tract /really/ wants to flow Tensors and nothing else.\nThis is structural. Changing it would be pretty difficult, and it's unclear whether it can be done without impairing performance or maintainability.\nWe are not convinced these features have shown their interest in the wild yet, so we prefer to leave them aside.\n\nOther dark corners are specific operators like \"Resize\" which fit perfectly in the framework but need a complex internal logic that is far\nfrom our core business. In these cases, we are happy to accept contributions and to help. \n\nThe following operators are implemented and tested.\n\nAbs, Acos, Acosh, Add, And, ArgMax, ArgMin, ArrayFeatureExtractor, Asin, Asinh, Atan, Atanh, AveragePool, BatchNormalization, BitShift, BitwiseAnd, BitwiseNot, BitwiseOr, BitwiseXor, BlackmanWindow, Cast, CastLike, CategoryMapper, Ceil, Clip, Compress, Concat, Constant, ConstantLike, ConstantOfShape, Conv, ConvInteger, ConvTranspose, Cos, Cosh, CumSum, DFT, DepthToSpace, DequantizeLinear, Div, Dropout, DynamicQuantizeLinear, Einsum, Elu, Equal, Erf, Exp, Expand, EyeLike, Flatten, Floor, GRU, Gather, GatherElements, GatherND, Gemm, GlobalAveragePool, GlobalLpPool, GlobalMaxPool, Greater, GreaterOrEqual, HammingWindow, HannWindow, HardSigmoid, Hardmax, Identity, If, InstanceNormalization, IsInf, IsNaN, LRN, LSTM, LeakyRelu, Less, LessOrEqual, Log, LogSoftmax, MatMul, MatMulInteger, Max, MaxPool, Mean, MelWeightMatrix, Min, Mod, Mul, Multinomial, Neg, NonMaxSuppression, NonZero, Not, OneHot, Or, PRelu, Pad, ParametricSoftplus, Pow, QLinearConv, QLinearMatMul, QuantizeLinear, RNN, RandomNormal, RandomNormalLike, RandomUniform, RandomUniformLike, Range, Reciprocal, ReduceL1, ReduceL2, ReduceLogSum, ReduceLogSumExp, ReduceMax, ReduceMean, ReduceMin, ReduceProd, ReduceSum, ReduceSumSquare, Relu, Reshape, Resize, Round, Rsqrt, STFT, ScaledTanh, Scan, Scatter, ScatterElements, ScatterND, Selu, Shape, Shrink, Sigmoid, Sign, Sin, Sinh, Size, Slice, Softmax, Softplus, Softsign, SpaceToDepth, Split, Sqrt, Squeeze, Sub, Sum, Tan, Tanh, ThresholdedRelu, Tile, Transpose, TreeEnsembleClassifier, Unsqueeze, Where, Xor\n\nWe test these operators against from ONNX 1.4.1 (operator set 9), up to ONNX 1.13.0 (operator set 18).\n\nWe are using ONNX test suite, but it does not cover everything.\nWe also deliberately ignore some tests, or restricting their scope depending on what we feel is realistic.\nSometimes these decisions are just wrong, and sometimes they become wrong as time goes by and the fields moves in unexpected directions.\nSo if you are puzzled by an ONNX model that does not work in tract, we are happy to take a look.\n\n### NNEF\n\nLong story short, TensorFlow and ONNX formats are good for designing and\ntraining networks. They need to move fast to follow the research field, tend to\nintegrate new features and operators greedily. They also exhibit a high level\nof expressivity to facilitate network design.\n\nOn the other hand, only a subset of operators and network features actually\nreach production, so systems running production network do not have to deal\nwith so many operators. Furthermore, some information required for training can\nbe stripped from the network before going to production for prediction.\n\nNNEF tries to bridge the gap between training frameworks and inference by\nproposing a format dedicated to production and prediction.\n\nTract supports NNEF:\n\n* tract_nnef can load and execute NNEF networks\n* tract supports most of the NNEF specification, the most notable exception\n    being the ROI operators\n* tract introduces tract-OPL, a series of NNEF extensions to support other\n    operators (or extend some operators semantics) in order to represent the\n    full range of tract-core neural network support: any network understood by\n    tract should be serializable to tract-OPL. This is a work in progress.\n* tract command line can translate networks from TensorFlow or ONNX to NNEF/OPL.\n\n### tract-opl version compatibility\n\nA remainder: NNEF is not expressive enough to represent all ONNX. tract-OPL extends\nNNEF using proprietary to support what is missing. Notable extensions are pulse\noperators, recurring operators (as Scan) and symbolic extensions.\n\nThere is no strict check in place here, so... implementation is not bullet proof.\n* NNEF part aims at being very stable. It is strongly constrained with compatibility\nwith NNEF specification.\n* tract-opl is a bit more in flux. Nevertheless we try to maintain the following\ngolden rule:\n\n     `models serialized with tract 0.x.y should work with tract 0.x.z where z >= y`\n\n* in practice, breaking changes have been relatively rare so far. Most models are\nforward and retro compatible from when tract has acquired NNEF support.\n\nNotable breakage occurred:\n* 0.16.3 (forward compatible) on Scan operator\n* 0.17.0 for binary decision tree classifier\n\nStarting with `0.17.0`, a model property is injected in tract-opl files (`tract_nnef_ser_version`)\nto tag which version of tract generated the file. As most models will remain compatible,\ntract will not do any version check. It is up to the application developer to do so.\n\nA softer version tag exists as `tract_nnef_format_version`. pre-0.17.0 version set it to\n`alpha1`, post-0.17.0 set it `beta1`. Don't put too much emphasis into the \"alpha-ness\" naming \nof versions here.\n\n### Note: support for TensorFlow 1.x\n\nEven if `tract` is very far from supporting any arbitrary model, it can run\nGoogle Inception v3 and Snips wake word models. Missing operators are relatively \neasy to add. The lack of easy to reuse test suite, and the wide diversity of \noperators in Tensorflow make it difficult to target a full support.\n\nThe following operators are implemented and tested:\n\nAbs, Add, AddN, AddV2, Assign, AvgPool, BatchToSpaceND, BiasAdd, BlockLSTM, Cast, Ceil, ConcatV2, Const, Conv2D, DepthwiseConv2dNative, Div, Enter, Equal, Exit, ExpandDims, FakeQuantWithMinMaxVars, Fill, FloorMod, FusedBatchNorm, GatherNd, GatherV2, Greater, GreaterEqual, Identity, Less, LessEqual, Log, LogicalAnd, LogicalOr, LoopCond, MatMul, Max, MaxPool, Maximum, Mean, Merge, Min, Minimum, Mul, Neg, NoOp, Pack, Pad, Placeholder, Pow, Prod, RandomUniform, RandomUniformInt, Range, RealDiv, Relu, Relu6, Reshape, Rsqrt, Shape, Sigmoid, Slice, Softmax, SpaceToBatchND, Squeeze, StridedSlice, Sub, Sum, Switch, Tanh, Tile, Transpose, VariableV2\n\nAdditionally, the complexity of TensorFlow 2 make it very unlikely that a direct\nsupport will ever exist in tract. But many TensorFlow 2 models can be\nconverted to ONNX and then loaded in tract.\n\n## Example of supported networks\n\nThese models among others, are used to track tract performance evolution as\npart of the Continuous Integration jobs. See [.travis/README.md](readme) and \n[.travis/bundle-entrypoint.sh](.travis/bundle-entrypoint.sh) for more\ninformation.\n\n### Keyword spotting on Arm Cortex-M Microcontrollers\n\nhttps://github.com/ARM-software/ML-KWS-for-MCU\n\nARM demonstrated the capabilities of the Cortex-M family by providing\ntutorials and pre-trained models for keyword spotting. While the exercise\nis ultimately meant for micro-controllers, `tract` can run the intermediate\nTensorFlow models.\n\nFor instance, on a Raspberry Pi Zero, the \"CNN M\" model runs in about 70\nmicro-seconds, and 11 micro-seconds on a Raspberry Pi 3.\n\n### Snips wake word models\n\nhttps://arxiv.org/abs/1811.07684\n\nSnips uses `tract` to run the wake word detectors. While earlier models were\nclass-based and did not require any special treatment, `tract` pulsing\ncapabilities made it possible to run WaveNet models efficiently enough for a\nRaspberry Pi Zero.\n\n### Inception v3\n\n|      Device         |      Family    |  TensorFlow-lite  |  tract  |\n|---------------------|----------------|-------------------|---------|\n|  Raspberry Pi Zero  |    Armv6 VFP   |        113s       |   39s   |\n|  Raspberry Pi 2     |    Armv7 NEON  |         25s       |    7s   |\n|  Raspberry Pi 3     |  aarch32 NEON  |          5s       |    5s   |\n\nNotes:\n\n * while the Raspberry Pi 3 is an Armv8 device, this bench is running\n     on Raspbian, an armv6 operating system, crippling the performance\n     of both benches\n * there exists other benches on the internet that show better\n     performance results for TensorFlow (not -Lite) on the Pi 3.\n     They use all four cores of the device. Both TensorFlow-Lite and tract\n     here have been made to run on a single-core.\n\n# License\n\nNote: files in the `tensorflow/protos` directory are copied from the\n[TensorFlow](https://github.com/tensorflow/tensorflow) project and are not\ncovered by the following licence statement.\n\nNote: files in the `onnx/protos` directory are copied from the\n[ONNX](https://github.com/onnx/onnx) project and are not\ncovered by the following license statement.\n\n## Apache 2.0/MIT\n\nAll original work licensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n## Contribution\n\nUnless you explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "api/.gitignore",
    "content": "*.nnef.tgz\n*.onnx\n"
  },
  {
    "path": "api/Cargo.toml",
    "content": "[package]\nname = \"tract-api\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/sonos/tract\"\nkeywords = [ \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\ninclude = [ \"Cargo.toml\", \"src/**/*.rs\", \"LICENSE*\", \"tract.h\" ]\n\n[dependencies]\nanyhow.workspace = true\nboow.workspace = true\nflate2.workspace = true\nhalf.workspace = true\nndarray.workspace = true\nserde.workspace = true\nserde_json.workspace = true\n\n[features]\ncomplex = []\n\n[dev-dependencies]\nlazy_static = \"1.4.0\"\nreqwest.workspace = true\ntempfile.workspace = true\n"
  },
  {
    "path": "api/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "api/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "api/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "api/c/.gitignore",
    "content": "*.h\n*.so\nmobilenet\nmobilenet_v2_1.0.onnx.nnef.tgz\n\n\n"
  },
  {
    "path": "api/c/Makefile",
    "content": "run: mobilenet mobilenet_v2_1.0.onnx.nnef.tgz\n\tLD_LIBRARY_PATH=. ./mobilenet\n\nclean:\n\trm -f mobilenet libtract.so tract.h\n\nmobilenet: tract.h libtract.so mobilenet.c\n\tcc mobilenet.c -o mobilenet -L. -ltract\n\nlibtract.so:\n\tcargo build -p tract-ffi --profile opt-no-lto\n\tcp ../../target/opt-no-lto/libtract.so .\n\ntract.h:\n\tcd ../ffi ; cbindgen -l c > tract.h\n\tcp ../ffi/tract.h .\n\nmobilenet_v2_1.0.onnx.nnef.tgz:\n\twget -q https://s3.amazonaws.com/tract-ci-builds/tests/mobilenet_v2_1.0.onnx.nnef.tgz\n"
  },
  {
    "path": "api/c/mobilenet.c",
    "content": "#include \"tract.h\"\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#define check(call)                                                            \\\n    {                                                                          \\\n        TRACT_RESULT result = call;                                            \\\n        if (result == TRACT_RESULT_KO) {                                       \\\n            fprintf(stderr, \"Error calling tract: %s\",                         \\\n                    tract_get_last_error());                                   \\\n            exit(1);                                                           \\\n        }                                                                      \\\n    }\n\nint main() {\n    // Initialize nnef parser\n    TractNnef *nnef = NULL;\n    check(tract_nnef_create(&nnef));\n    assert(nnef);\n\n    // Load the model\n    TractModel *model = NULL;\n    check(tract_nnef_load(nnef, \"mobilenet_v2_1.0.onnx.nnef.tgz\", &model));\n    assert(model);\n    assert(nnef);\n\n    // once the model is build, the framework is not necessary anymore\n    check(tract_nnef_destroy(&nnef));\n    assert(!nnef);\n\n    // Pick a runtime\n    TractRuntime *runtime = NULL;\n    check(tract_runtime_for_name(\"default\", &runtime));\n    assert(runtime);\n\n    // Make the model runnable\n    TractRunnable *runnable = NULL;\n    check(tract_runtime_prepare(runtime, &model, &runnable));\n    assert(runnable);\n    assert(!model);\n\n    float *image = malloc(3 * 224 * 224 * sizeof(float));\n    FILE *fd = fopen(\"grace_hopper_3_224_224.f32.raw\", \"rb\");\n    assert(fread(image, sizeof(float), 3 * 224 * 224, fd) == 3 * 224 * 224);\n    fclose(fd);\n\n    TractTensor *input = NULL;\n    size_t shape[] = {1, 3, 224, 224};\n    check(\n        tract_tensor_from_bytes(TRACT_DATUM_TYPE_F32, 4, shape, image, &input));\n    free(image);\n\n    TractTensor *output = NULL;\n\n    // simple stateless run...\n    check(tract_runnable_run(runnable, &input, &output));\n\n    const float *data = NULL;\n    check(tract_tensor_as_bytes(output, NULL, NULL, NULL, (const void **)&data));\n    float max = data[0];\n    int argmax = 0;\n    for (int i = 0; i < 1000; i++) {\n        float val = data[i];\n        if (val > max) {\n            max = val;\n            argmax = i;\n        }\n    }\n    printf(\"Max is %f for category %d\\n\", max, argmax);\n    check(tract_tensor_destroy(&output));\n\n    // or spawn a state to run the model\n    TractState *state = NULL;\n    check(tract_runnable_spawn_state(runnable, &state));\n    assert(state);\n\n    // runnable is refcounted by the spawned states, so we can release it now.\n    check(tract_runnable_release(&runnable));\n    assert(!runnable);\n\n    check(tract_state_run(state, &input, &output));\n\n    check(tract_tensor_as_bytes(output, NULL, NULL, NULL, (const void **)&data));\n    assert(data[argmax] == max);\n    check(tract_tensor_destroy(&output));\n\n    // done with out state and input\n    check(tract_state_destroy(&state));\n    check(tract_tensor_destroy(&input));\n}\n"
  },
  {
    "path": "api/ffi/Cargo.toml",
    "content": "[package]\nname = \"tract-ffi\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, neural network inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\ninclude = [ \"Cargo.toml\", \"src/**/*.rs\", \"LICENSE*\" ]\n\n[lib]\nname = \"tract\"\ncrate-type = [\"cdylib\"]\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nanyhow.workspace = true\nflate2.workspace = true\nserde.workspace = true\nserde_json.workspace = true\ntract-api.workspace = true\ntract-rs = { version = \"0.23.0-pre\", path = \"../rs\", package = \"tract\" }\n"
  },
  {
    "path": "api/ffi/cbindgen.toml",
    "content": "language = \"C\"\n\nafter_includes = \"\"\"\n    typedef enum DatumType {\n      TRACT_DATUM_TYPE_BOOL = 1,\n      TRACT_DATUM_TYPE_U8 = 17,\n      TRACT_DATUM_TYPE_U16 = 18,\n      TRACT_DATUM_TYPE_U32 = 20,\n      TRACT_DATUM_TYPE_U64 = 24,\n      TRACT_DATUM_TYPE_I8 = 33,\n      TRACT_DATUM_TYPE_I16 = 34,\n      TRACT_DATUM_TYPE_I32 = 36,\n      TRACT_DATUM_TYPE_I64 = 40,\n      TRACT_DATUM_TYPE_F16 = 50,\n      TRACT_DATUM_TYPE_F32 = 52,\n      TRACT_DATUM_TYPE_F64 = 56,\n      TRACT_DATUM_TYPE_COMPLEX_I16 = 66,\n      TRACT_DATUM_TYPE_COMPLEX_I32 = 68,\n      TRACT_DATUM_TYPE_COMPLEX_I64 = 72,\n      TRACT_DATUM_TYPE_COMPLEX_F16 = 82,\n      TRACT_DATUM_TYPE_COMPLEX_F32 = 84,\n      TRACT_DATUM_TYPE_COMPLEX_F64 = 88,\n    } DatumType;\n\"\"\"\n"
  },
  {
    "path": "api/ffi/src/lib.rs",
    "content": "#![allow(clippy::missing_safety_doc)]\n\nuse anyhow::{Context, Result};\nuse std::cell::RefCell;\nuse std::ffi::{CStr, CString, c_char, c_void};\nuse tract_api::{\n    AsFact, DatumType, DimInterface, FactInterface, InferenceModelInterface, ModelInterface,\n    NnefInterface, OnnxInterface, RunnableInterface, RuntimeInterface, StateInterface,\n    TensorInterface,\n};\nuse tract_rs::{State, Tensor};\n\n/// Used as a return type of functions that can encounter errors.\n/// If the function encountered an error, you can retrieve it using the `tract_get_last_error`\n/// function\n#[repr(C)]\n#[allow(non_camel_case_types)]\n#[derive(Debug, PartialEq, Eq)]\npub enum TRACT_RESULT {\n    /// The function returned successfully\n    TRACT_RESULT_OK = 0,\n    /// The function returned an error\n    TRACT_RESULT_KO = 1,\n}\n\nthread_local! {\n    pub(crate) static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };\n}\n\nfn wrap<F: FnOnce() -> anyhow::Result<()>>(func: F) -> TRACT_RESULT {\n    match func() {\n        Ok(_) => TRACT_RESULT::TRACT_RESULT_OK,\n        Err(e) => {\n            let msg = format!(\"{e:?}\");\n            if std::env::var(\"TRACT_ERROR_STDERR\").is_ok() {\n                eprintln!(\"{msg}\");\n            }\n            LAST_ERROR.with(|p| {\n                *p.borrow_mut() = Some(CString::new(msg).unwrap_or_else(|_| {\n                    CString::new(\"tract error message contains 0, can't convert to CString\")\n                        .unwrap()\n                }))\n            });\n            TRACT_RESULT::TRACT_RESULT_KO\n        }\n    }\n}\n\n/// Retrieve the last error that happened in this thread. A function encountered an error if\n/// its return type is of type `TRACT_RESULT` and it returned `TRACT_RESULT_KO`.\n///\n/// # Return value\n///  It returns a pointer to a null-terminated UTF-8 string that will contain the error description.\n///  Rust side keeps ownership of the buffer. It will be valid as long as no other tract calls is\n///  performed by the thread.\n///  If no error occured, null is returned.\n#[unsafe(no_mangle)]\npub extern \"C\" fn tract_get_last_error() -> *const std::ffi::c_char {\n    LAST_ERROR.with(|msg| msg.borrow().as_ref().map(|s| s.as_ptr()).unwrap_or(std::ptr::null()))\n}\n\n/// Returns a pointer to a static buffer containing a null-terminated version string.\n///\n/// The returned pointer must not be freed.\n#[unsafe(no_mangle)]\npub extern \"C\" fn tract_version() -> *const std::ffi::c_char {\n    unsafe {\n        CStr::from_bytes_with_nul_unchecked(concat!(env!(\"CARGO_PKG_VERSION\"), \"\\0\").as_bytes())\n            .as_ptr()\n    }\n}\n\n/// Frees a string allocated by libtract.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_free_cstring(ptr: *mut std::ffi::c_char) {\n    unsafe {\n        if !ptr.is_null() {\n            let _ = CString::from_raw(ptr);\n        }\n    }\n}\n\nmacro_rules! check_not_null {\n    ($($ptr:expr),*) => {\n        $(\n            if $ptr.is_null() {\n                anyhow::bail!(concat!(\"Unexpected null pointer \", stringify!($ptr)));\n            }\n         )*\n    }\n}\n\nmacro_rules! release {\n    ($ptr:expr) => {\n        wrap(|| unsafe {\n            check_not_null!($ptr, *$ptr);\n            let _ = Box::from_raw(*$ptr);\n            *$ptr = std::ptr::null_mut();\n            Ok(())\n        })\n    };\n}\n\n// NNEF\npub struct TractNnef(tract_rs::Nnef);\n\n/// Creates an instance of an NNEF framework and parser that can be used to load and dump NNEF models.\n///\n/// The returned object should be destroyed with `tract_nnef_destroy` once the model\n/// has been loaded.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_create(nnef: *mut *mut TractNnef) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef);\n        *nnef = Box::into_raw(Box::new(TractNnef(tract_rs::nnef()?)));\n        Ok(())\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_enable_tract_core(nnef: *mut TractNnef) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef);\n        (*nnef).0.enable_tract_core()\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_enable_tract_extra(nnef: *mut TractNnef) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef);\n        (*nnef).0.enable_tract_extra()\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_enable_tract_transformers(\n    nnef: *mut TractNnef,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef);\n        (*nnef).0.enable_tract_transformers()\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_enable_onnx(nnef: *mut TractNnef) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef);\n        (*nnef).0.enable_onnx()\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_enable_pulse(nnef: *mut TractNnef) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef);\n        (*nnef).0.enable_pulse()\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_enable_extended_identifier_syntax(\n    nnef: *mut TractNnef,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef);\n        (*nnef).0.enable_extended_identifier_syntax()\n    })\n}\n\n/// Destroy the NNEF parser. It is safe to detroy the NNEF parser once the model had been loaded.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_destroy(nnef: *mut *mut TractNnef) -> TRACT_RESULT {\n    release!(nnef)\n}\n\n/// Parse and load an NNEF model as a tract TypedModel.\n///\n/// `path` is a null-terminated utf-8 string pointer. It can be an archive (tar or tar.gz file) or a\n/// directory.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_load(\n    nnef: *const TractNnef,\n    path: *const c_char,\n    model: *mut *mut TractModel,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef, model, path);\n        *model = std::ptr::null_mut();\n        let path = CStr::from_ptr(path).to_str()?;\n        let m = Box::new(TractModel(\n            (*nnef).0.load(path).with_context(|| format!(\"opening file {path:?}\"))?,\n        ));\n        *model = Box::into_raw(m);\n        Ok(())\n    })\n}\n\n/// Parse and load an NNEF buffer as a tract TypedModel.\n///\n/// `data` is a buffer pointer\n/// `len` ise the buffer len\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_load_buffer(\n    nnef: *const TractNnef,\n    data: *const c_void,\n    len: usize,\n    model: *mut *mut TractModel,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef, model, data);\n        *model = std::ptr::null_mut();\n        let slice = std::slice::from_raw_parts(data as *const u8, len);\n        let m = Box::new(TractModel((*nnef).0.load_buffer(slice)?));\n        *model = Box::into_raw(m);\n        Ok(())\n    })\n}\n\n/// Dump a TypedModel as a NNEF tar file.\n///\n/// `path` is a null-terminated utf-8 string pointer to the `.tar` file to be created.\n///\n/// This function creates a plain, non-compressed, archive.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_write_model_to_tar(\n    nnef: *const TractNnef,\n    path: *const c_char,\n    model: *const TractModel,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef, model, path);\n        let path = CStr::from_ptr(path).to_str()?;\n        (*nnef).0.write_model_to_tar(path, &(*model).0)?;\n        Ok(())\n    })\n}\n\n/// Dump a TypedModel as a NNEF .tar.gz file.\n///\n/// `path` is a null-terminated utf-8 string pointer to the `.tar.gz` file to be created.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_write_model_to_tar_gz(\n    nnef: *const TractNnef,\n    path: *const c_char,\n    model: *const TractModel,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef, model, path);\n        let path = CStr::from_ptr(path).to_str()?;\n        (*nnef).0.write_model_to_tar_gz(path, &(*model).0)?;\n        Ok(())\n    })\n}\n\n/// Dump a TypedModel as a NNEF directory.\n///\n/// `path` is a null-terminated utf-8 string pointer to the directory to be created.\n///\n/// This function creates a plain, non-compressed, archive.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_nnef_write_model_to_dir(\n    nnef: *const TractNnef,\n    path: *const c_char,\n    model: *const TractModel,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef, model, path);\n        let path = CStr::from_ptr(path).to_str()?;\n        (*nnef).0.write_model_to_dir(path, &(*model).0)?;\n        Ok(())\n    })\n}\n\n// ONNX\npub struct TractOnnx(tract_rs::Onnx);\n\n/// Creates an instance of an ONNX framework and parser that can be used to load models.\n///\n/// The returned object should be destroyed with `tract_nnef_destroy` once the model\n/// has been loaded.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_onnx_create(onnx: *mut *mut TractOnnx) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(onnx);\n        *onnx = Box::into_raw(Box::new(TractOnnx(tract_rs::onnx()?)));\n        Ok(())\n    })\n}\n\n/// Destroy the NNEF parser. It is safe to detroy the NNEF parser once the model had been loaded.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_onnx_destroy(onnx: *mut *mut TractOnnx) -> TRACT_RESULT {\n    release!(onnx)\n}\n\n/// Parse and load an ONNX model as a tract InferenceModel.\n///\n/// `path` is a null-terminated utf-8 string pointer. It must point to a `.onnx` model file.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_onnx_load(\n    onnx: *const TractOnnx,\n    path: *const c_char,\n    model: *mut *mut TractInferenceModel,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(onnx, path, model);\n        *model = std::ptr::null_mut();\n        let path = CStr::from_ptr(path).to_str()?;\n        let m = Box::new(TractInferenceModel((*onnx).0.load(path)?));\n        *model = Box::into_raw(m);\n        Ok(())\n    })\n}\n\n/// Parse and load an ONNX buffer as a tract InferenceModel.\n///\n/// `data` is a buffer pointer\n/// `len` ise the buffer len\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_onnx_load_buffer(\n    onnx: *const TractOnnx,\n    data: *const c_void,\n    len: usize,\n    model: *mut *mut TractInferenceModel,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(onnx, model, data);\n        *model = std::ptr::null_mut();\n        let slice = std::slice::from_raw_parts(data as *const u8, len);\n        let m = Box::new(TractInferenceModel((*onnx).0.load_buffer(slice)?));\n        *model = Box::into_raw(m);\n        Ok(())\n    })\n}\n\n// INFERENCE MODEL\npub struct TractInferenceModel(tract_rs::InferenceModel);\n\n/// Query an InferenceModel input counts.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_input_count(\n    model: *const TractInferenceModel,\n    inputs: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, inputs);\n        let model = &(*model).0;\n        *inputs = model.input_count()?;\n        Ok(())\n    })\n}\n\n/// Query an InferenceModel output counts.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_output_count(\n    model: *const TractInferenceModel,\n    outputs: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, outputs);\n        let model = &(*model).0;\n        *outputs = model.output_count()?;\n        Ok(())\n    })\n}\n\n/// Query the name of a model input.\n///\n/// The returned name must be freed by the caller using tract_free_cstring.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_input_name(\n    model: *const TractInferenceModel,\n    input: usize,\n    name: *mut *mut c_char,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, name);\n        *name = std::ptr::null_mut();\n        let m = &(*model).0;\n        *name = CString::new(&*m.input_name(input)?)?.into_raw();\n        Ok(())\n    })\n}\n\n/// Query the name of a model output.\n///\n/// The returned name must be freed by the caller using tract_free_cstring.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_output_name(\n    model: *const TractInferenceModel,\n    output: usize,\n    name: *mut *mut i8,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, name);\n        *name = std::ptr::null_mut();\n        let m = &(*model).0;\n        *name = CString::new(&*m.output_name(output)?)?.into_raw() as _;\n        Ok(())\n    })\n}\n\n/// Query a model input fact.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_input_fact(\n    model: *const TractInferenceModel,\n    input_id: usize,\n    fact: *mut *mut TractInferenceFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, fact);\n        *fact = std::ptr::null_mut();\n        let f = (*model).0.input_fact(input_id)?;\n        *fact = Box::into_raw(Box::new(TractInferenceFact(f)));\n        Ok(())\n    })\n}\n\n/// Set an input fact of an InferenceModel.\n///\n/// The `fact` argument is only borrowed by this function, it still must be destroyed.\n/// `fact` can be set to NULL to erase the current output fact of the model.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_set_input_fact(\n    model: *mut TractInferenceModel,\n    input_id: usize,\n    fact: *const TractInferenceFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model);\n        let f = fact.as_ref().map(|f| &f.0).cloned().unwrap_or_default();\n        (*model).0.set_input_fact(input_id, f)?;\n        Ok(())\n    })\n}\n\n/// Query an output fact for an InferenceModel.\n///\n/// The return model must be freed using `tract_inference_fact_destroy`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_output_fact(\n    model: *const TractInferenceModel,\n    output_id: usize,\n    fact: *mut *mut TractInferenceFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, fact);\n        *fact = std::ptr::null_mut();\n        let f = (*model).0.output_fact(output_id)?;\n        *fact = Box::into_raw(Box::new(TractInferenceFact(f)));\n        Ok(())\n    })\n}\n\n/// Set an output fact of an InferenceModel.\n///\n/// The `fact` argument is only borrowed by this function, it still must be destroyed.\n/// `fact` can be set to NULL to erase the current output fact of the model.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_set_output_fact(\n    model: *mut TractInferenceModel,\n    output_id: usize,\n    fact: *const TractInferenceFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model);\n        let f = fact.as_ref().map(|f| &f.0).cloned().unwrap_or_default();\n        (*model).0.set_output_fact(output_id, f)?;\n        Ok(())\n    })\n}\n\n/// Analyse an InferencedModel in-place.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_analyse(\n    model: *mut TractInferenceModel,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model);\n        (*model).0.analyse()?;\n        Ok(())\n    })\n}\n\n/// Transform a fully analysed InferenceModel to a TypedModel.\n///\n/// This function takes ownership of the InferenceModel `model` whether it succeeds\n/// or not. `tract_inference_model_destroy` must not be used on `model`.\n///\n/// On the other hand, caller will be owning the newly created typed model.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_into_model(\n    model: *mut *mut TractInferenceModel,\n    typed: *mut *mut TractModel,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, *model, typed);\n        *typed = std::ptr::null_mut();\n        let m = Box::from_raw(*model);\n        *model = std::ptr::null_mut();\n        let result = m.0.into_model()?;\n        *typed = Box::into_raw(Box::new(TractModel(result))) as _;\n        Ok(())\n    })\n}\n\n/// Destroy an InferenceModel.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_model_destroy(\n    model: *mut *mut TractInferenceModel,\n) -> TRACT_RESULT {\n    release!(model)\n}\n// TYPED MODEL\n\npub struct TractModel(tract_rs::Model);\n\n/// Query an InferenceModel input counts.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_input_count(\n    model: *const TractModel,\n    inputs: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, inputs);\n        let model = &(*model).0;\n        *inputs = model.input_count()?;\n        Ok(())\n    })\n}\n\n/// Query an InferenceModel output counts.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_output_count(\n    model: *const TractModel,\n    outputs: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, outputs);\n        let model = &(*model).0;\n        *outputs = model.output_count()?;\n        Ok(())\n    })\n}\n\n/// Query the name of a model input.\n///\n/// The returned name must be freed by the caller using tract_free_cstring.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_input_name(\n    model: *const TractModel,\n    input: usize,\n    name: *mut *mut c_char,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, name);\n        *name = std::ptr::null_mut();\n        let m = &(*model).0;\n        *name = CString::new(m.input_name(input)?)?.into_raw();\n        Ok(())\n    })\n}\n\n/// Query the input fact of a model.\n///\n/// Thre returned fact must be freed with tract_fact_destroy.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_input_fact(\n    model: *const TractModel,\n    input_id: usize,\n    fact: *mut *mut TractFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, fact);\n        *fact = std::ptr::null_mut();\n        let f = (*model).0.input_fact(input_id)?;\n        *fact = Box::into_raw(Box::new(TractFact(f)));\n        Ok(())\n    })\n}\n\n/// Query the name of a model output.\n///\n/// The returned name must be freed by the caller using tract_free_cstring.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_output_name(\n    model: *const TractModel,\n    output: usize,\n    name: *mut *mut c_char,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, name);\n        *name = std::ptr::null_mut();\n        let m = &(*model).0;\n        *name = CString::new(m.output_name(output)?)?.into_raw();\n        Ok(())\n    })\n}\n\n/// Query the output fact of a model.\n///\n/// Thre returned fact must be freed with tract_fact_destroy.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_output_fact(\n    model: *const TractModel,\n    input_id: usize,\n    fact: *mut *mut TractFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, fact);\n        *fact = std::ptr::null_mut();\n        let f = (*model).0.output_fact(input_id)?;\n        *fact = Box::into_raw(Box::new(TractFact(f)));\n        Ok(())\n    })\n}\n\n/// Apply a transform to the model.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_transform(\n    model: *mut TractModel,\n    transform: *const i8,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, transform);\n        let t = CStr::from_ptr(transform as _)\n            .to_str()\n            .context(\"failed to parse transform name (not utf8)\")?;\n        (*model).0.transform(t)\n    })\n}\n\n/// Perform a profile of the model using the provided inputs.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_profile_json(\n    model: *mut TractRunnable,\n    inputs: *mut *mut TractTensor,\n    json: *mut *mut i8,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, json);\n\n        let input: Option<Vec<Tensor>> = if !inputs.is_null() {\n            let input_len = (*model).0.input_count()?;\n            Some(\n                std::slice::from_raw_parts(inputs, input_len)\n                    .iter()\n                    .map(|tv| (**tv).0.clone())\n                    .collect(),\n            )\n        } else {\n            None\n        };\n\n        let profile = (*model).0.profile_json(input)?;\n        *json = CString::new(profile)?.into_raw() as _;\n        Ok(())\n    })\n}\n\n/// Convert a TypedModel into a TypedRunnableModel.\n///\n/// This function transfers ownership of the `model` argument to the newly-created `runnable` model.\n///\n/// Runnable are reference counted. When done, it should be released with `tract_runnable_release`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_into_runnable(\n    model: *mut *mut TractModel,\n    runnable: *mut *mut TractRunnable,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, runnable);\n        let m = Box::from_raw(*model).0;\n        *model = std::ptr::null_mut();\n        *runnable = Box::into_raw(Box::new(TractRunnable(m.into_runnable()?))) as _;\n        Ok(())\n    })\n}\n\n/// Query the number of properties in a model.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_property_count(\n    model: *const TractModel,\n    count: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, count);\n        *count = (*model).0.property_keys()?.len();\n        Ok(())\n    })\n}\n\n/// Query the properties names of a model.\n///\n/// The \"names\" array should be big enough to fit `tract_model_property_count` string pointers.\n///\n/// Each name will have to be freed using `tract_free_cstring`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_property_names(\n    model: *const TractModel,\n    names: *mut *mut i8,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, names);\n        for (ix, name) in (*model).0.property_keys()?.iter().enumerate() {\n            *names.add(ix) = CString::new(&**name)?.into_raw() as _;\n        }\n        Ok(())\n    })\n}\n\n/// Query a property tensor in a model.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_property(\n    model: *const TractModel,\n    name: *const i8,\n    tensor: *mut *mut TractTensor,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, name, tensor);\n        let name = CStr::from_ptr(name as _)\n            .to_str()\n            .context(\"failed to parse property name (not utf8)\")?\n            .to_owned();\n        let v = (*model).0.property(name).context(\"Property not found\")?;\n        *tensor = Box::into_raw(Box::new(TractTensor(v)));\n        Ok(())\n    })\n}\n\n/// Parse a fact specification string into an Fact.\n///\n/// The returned fact must be free with `tract_fact_destroy`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_parse_fact(\n    model: *mut TractModel,\n    spec: *const c_char,\n    fact: *mut *mut TractFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, spec, fact);\n        let spec = CStr::from_ptr(spec).to_str()?;\n        let f: tract_rs::Fact = spec.as_fact(&(*model).0)?.as_ref().clone();\n        *fact = Box::into_raw(Box::new(TractFact(f)));\n        Ok(())\n    })\n}\n\n/// Destroy a TypedModel.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_model_destroy(model: *mut *mut TractModel) -> TRACT_RESULT {\n    release!(model)\n}\n\n// RUNTIME MODEL\npub struct TractRuntime(tract_rs::Runtime);\n\n/// Creates an instance of a tract Runtime that can be used to run model on a specific\n/// hardware / software stack (like a GPU).\n///\n/// The returned object should be released with `tract_runtime_release`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runtime_for_name(\n    name: *const c_char,\n    nnef: *mut *mut TractRuntime,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(nnef);\n        let name = CStr::from_ptr(name).to_str()?;\n        *nnef = Box::into_raw(Box::new(TractRuntime(tract_rs::runtime_for_name(name)?)));\n        Ok(())\n    })\n}\n\n/// Query the name of a Runtime.\n///\n/// The returned name must be freed by the caller using tract_free_cstring.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runtime_name(\n    runtime: *const TractRuntime,\n    name: *mut *mut c_char,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(runtime, name);\n        *name = std::ptr::null_mut();\n        let n = (*runtime).0.name()?;\n        *name = CString::new(n)?.into_raw();\n        Ok(())\n    })\n}\n\n/// Convert a Model into a Runnable for this Runtime.\n///\n/// This function transfers ownership of the `model` argument to the newly-created `runnable` model.\n///\n/// Runnable are reference counted. When done, it should be released with `tract_runnable_release`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runtime_prepare(\n    runtime: *const TractRuntime,\n    model: *mut *mut TractModel,\n    runnable: *mut *mut TractRunnable,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(runtime, model, runnable);\n        let m = Box::from_raw(*model).0;\n        *model = std::ptr::null_mut();\n        *runnable = Box::into_raw(Box::new(TractRunnable((*runtime).0.prepare(m)?))) as _;\n        Ok(())\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runtime_release(runtime: *mut *mut TractRuntime) -> TRACT_RESULT {\n    release!(runtime)\n}\n\n// RUNNABLE MODEL\npub struct TractRunnable(tract_rs::Runnable);\n\n/// Spawn a session state from a runnable model.\n///\n/// This function does not take ownership of the `runnable` object, it can be used again to spawn\n/// other state instances. The runnable object is internally reference counted, it will be\n/// kept alive as long as any associated `State` exists (or as long as the `runnable` is not\n/// explicitely release with `tract_runnable_release`).\n///\n/// `state` is a newly-created object. It should ultimately be detroyed with `tract_state_destroy`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_spawn_state(\n    runnable: *mut TractRunnable,\n    state: *mut *mut TractState,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(runnable, state);\n        *state = std::ptr::null_mut();\n        let s = (*runnable).0.spawn_state()?;\n        *state = Box::into_raw(Box::new(TractState(s)));\n        Ok(())\n    })\n}\n\n/// Convenience function to run a stateless model.\n///\n/// `inputs` is a pointer to an pre-existing array of input TractTensor. Its length *must* be equal\n/// to the number of inputs of the models. The function does not take ownership of the input\n/// tensors.\n/// `outputs` is a pointer to a pre-existing array of TractTensor pointers that will be overwritten\n/// with pointers to output tensors. These tensors are under the responsiblity of the caller, it\n/// will have to release them with `tract_tensor_destroy`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_run(\n    runnable: *mut TractRunnable,\n    inputs: *mut *mut TractTensor,\n    outputs: *mut *mut TractTensor,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(runnable);\n        let mut s = (*runnable).0.spawn_state()?;\n        state_run(&mut s, inputs, outputs)\n    })\n}\n\n/// Query a Runnable input counts.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_input_count(\n    model: *const TractRunnable,\n    inputs: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, inputs);\n        let model = &(*model).0;\n        *inputs = model.input_count()?;\n        Ok(())\n    })\n}\n\n/// Query an Runnable output counts.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_output_count(\n    model: *const TractRunnable,\n    outputs: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, outputs);\n        let model = &(*model).0;\n        *outputs = model.output_count()?;\n        Ok(())\n    })\n}\n\n/// Query the input fact of a runnable model.\n///\n/// Thre returned fact must be freed with tract_fact_destroy.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_input_fact(\n    runnable: *const TractRunnable,\n    input_id: usize,\n    fact: *mut *mut TractFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(runnable, fact);\n        *fact = std::ptr::null_mut();\n        let f = (*runnable).0.input_fact(input_id)?;\n        *fact = Box::into_raw(Box::new(TractFact(f)));\n        Ok(())\n    })\n}\n\n/// Query the output fact of a runnable model.\n///\n/// Thre returned fact must be freed with tract_fact_destroy.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_output_fact(\n    runnable: *const TractRunnable,\n    output_id: usize,\n    fact: *mut *mut TractFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(runnable, fact);\n        *fact = std::ptr::null_mut();\n        let f = (*runnable).0.output_fact(output_id)?;\n        *fact = Box::into_raw(Box::new(TractFact(f)));\n        Ok(())\n    })\n}\n\n/// Query the number of properties in a runnable model.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_property_count(\n    model: *const TractRunnable,\n    count: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, count);\n        *count = (*model).0.property_keys()?.len();\n        Ok(())\n    })\n}\n\n/// Query the properties names of a runnable model.\n///\n/// The \"names\" array should be big enough to fit `tract_model_property_count` string pointers.\n///\n/// Each name will have to be freed using `tract_free_cstring`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_property_names(\n    model: *const TractRunnable,\n    names: *mut *mut i8,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, names);\n        for (ix, name) in (*model).0.property_keys()?.iter().enumerate() {\n            *names.add(ix) = CString::new(&**name)?.into_raw() as _;\n        }\n        Ok(())\n    })\n}\n\n/// Query a property tensor in a runnable model.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_property(\n    model: *const TractRunnable,\n    name: *const i8,\n    tensor: *mut *mut TractTensor,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, name, tensor);\n        let name = CStr::from_ptr(name as _)\n            .to_str()\n            .context(\"failed to parse property name (not utf8)\")?\n            .to_owned();\n        let v = (*model).0.property(name).context(\"Property not found\")?;\n        *tensor = Box::into_raw(Box::new(TractTensor(v)));\n        Ok(())\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_runnable_release(runnable: *mut *mut TractRunnable) -> TRACT_RESULT {\n    release!(runnable)\n}\n\n// TENSOR\npub struct TractTensor(tract_rs::Tensor);\n\n/// Create a TractTensor from caller data and metadata.\n///\n/// This call copies the data into tract space. All the pointers only need to be alive for the\n/// duration of the call.\n///\n/// rank is the number of dimensions of the tensor (i.e. the length of the shape vector).\n///\n/// The returned tensor must be destroyed by `tract_tensor_destroy`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_tensor_from_bytes(\n    datum_type: DatumType,\n    rank: usize,\n    shape: *const usize,\n    data: *mut c_void,\n    tensor: *mut *mut TractTensor,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(tensor);\n        *tensor = std::ptr::null_mut();\n        let shape = std::slice::from_raw_parts(shape, rank);\n        let len = shape.iter().product::<usize>();\n        let data = std::slice::from_raw_parts(data as *const u8, len * datum_type.size_of());\n        let it = Tensor::from_bytes(datum_type, shape, data)?;\n        *tensor = Box::into_raw(Box::new(TractTensor(it)));\n        Ok(())\n    })\n}\n\n/// Write a tensor as a debug string\n///\n/// The returned string must be freed by the caller using tract_free_cstring.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_tensor_dump(\n    tensor: *const TractTensor,\n    spec: *mut *mut c_char,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(tensor, spec);\n        *spec = CString::new(format!(\"{:?}\", (*tensor).0))?.into_raw();\n        Ok(())\n    })\n}\n\n/// Convert a tensor to a new datum type.\n///\n/// This function will perform a cheap shallow clone if the destination type is\n/// the same as the current type, otherwise it returns a newly allocated Tensor instead.\n///\n/// In both cases, the returned tensor must be destroyed by `tract_tensor_destroy`.\n/// The input tensor is not consumed, it still need to be destroyed.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_tensor_convert_to(\n    input: *const TractTensor,\n    datum_type: DatumType,\n    output: *mut *mut TractTensor,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(input, output);\n        *output = std::ptr::null_mut();\n        let new = (*input).0.convert_to(datum_type)?;\n        *output = Box::into_raw(Box::new(TractTensor(new)));\n        Ok(())\n    })\n}\n\n/// Destroy a tensor.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_tensor_destroy(tensor: *mut *mut TractTensor) -> TRACT_RESULT {\n    release!(tensor)\n}\n\n/// Inspect part of a tensor. Except `tensor`, all argument pointers can be null if only some specific bits\n/// are required.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_tensor_as_bytes(\n    tensor: *mut TractTensor,\n    datum_type: *mut DatumType,\n    rank: *mut usize,\n    shape: *mut *const usize,\n    data: *mut *const c_void,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(tensor);\n        let tensor = &(*tensor).0;\n        let bits = tensor.as_bytes()?;\n        if !datum_type.is_null() {\n            *datum_type = bits.0;\n        }\n        if !rank.is_null() {\n            *rank = bits.1.len();\n        }\n        if !shape.is_null() {\n            *shape = bits.1.as_ptr();\n        }\n        if !data.is_null() {\n            *data = bits.2.as_ptr() as _;\n        }\n        Ok(())\n    })\n}\n\n// STATE\npub struct TractState(tract_rs::State);\n\n/// Run a turn on a model state\n///\n/// `inputs` is a pointer to an pre-existing array of input TractTensor. Its length *must* be equal\n/// to the number of inputs of the models. The function does not take ownership of the input\n/// tensors.\n/// `outputs` is a pointer to a pre-existing array of TractTensor pointers that will be overwritten\n/// with pointers to output tensors. These tensors are under the responsiblity of the caller, it\n/// will have to release them with `tract_tensor_destroy`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_state_run(\n    state: *mut TractState,\n    inputs: *mut *mut TractTensor,\n    outputs: *mut *mut TractTensor,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(state, inputs, outputs);\n        state_run(&mut (*state).0, inputs, outputs)\n    })\n}\n\n/// Query a State input counts.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_state_input_count(\n    state: *const TractState,\n    inputs: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(state, inputs);\n        let state = &(*state).0;\n        *inputs = state.input_count()?;\n        Ok(())\n    })\n}\n\n/// Query an State output counts.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_state_output_count(\n    state: *const TractState,\n    outputs: *mut usize,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(state, outputs);\n        let state = &(*state).0;\n        *outputs = state.output_count()?;\n        Ok(())\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_state_destroy(state: *mut *mut TractState) -> TRACT_RESULT {\n    release!(state)\n}\n\n// FACT\npub struct TractFact(tract_rs::Fact);\n\n/// Gets the rank (aka number of axes/dimensions) of a fact.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_fact_rank(fact: *const TractFact, rank: *mut usize) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(fact, rank);\n        *rank = (*fact).0.rank()?;\n        Ok(())\n    })\n}\n\n/// Extract the datum type of the fact.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_fact_datum_type(\n    fact: *const TractFact,\n    datum_type: *mut DatumType,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(fact, datum_type);\n        *datum_type = (*fact).0.datum_type()?;\n        Ok(())\n    })\n}\n\n/// Extract the dimension from one dimension of the fact.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_fact_dim(\n    fact: *const TractFact,\n    axis: usize,\n    dim: *mut *mut TractDim,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(fact, dim);\n        let d = (*fact).0.dim(axis)?;\n        *dim = Box::into_raw(Box::new(TractDim(d)));\n        Ok(())\n    })\n}\n\n/// Write a fact as its specification string.\n///\n/// The returned string must be freed by the caller using tract_free_cstring.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_fact_dump(\n    fact: *const TractFact,\n    spec: *mut *mut c_char,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(fact, spec);\n        *spec = CString::new(format!(\"{}\", (*fact).0))?.into_raw();\n        Ok(())\n    })\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_fact_destroy(fact: *mut *mut TractFact) -> TRACT_RESULT {\n    release!(fact)\n}\n\n// INFERENCE FACT\npub struct TractInferenceFact(tract_rs::InferenceFact);\n\n/// Parse a fact specification string into an InferenceFact.\n///\n/// The returned fact must be free with `tract_inference_fact_destroy`.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_fact_parse(\n    model: *mut TractInferenceModel,\n    spec: *const c_char,\n    fact: *mut *mut TractInferenceFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(model, spec, fact);\n        let spec = CStr::from_ptr(spec).to_str()?;\n        let f: tract_rs::InferenceFact = spec.as_fact(&(*model).0)?.as_ref().clone();\n        *fact = Box::into_raw(Box::new(TractInferenceFact(f)));\n        Ok(())\n    })\n}\n\n/// Creates an empty inference fact.\n///\n/// The returned fact must be freed by the caller using tract_inference_fact_destroy\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_fact_empty(\n    fact: *mut *mut TractInferenceFact,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(fact);\n        *fact = Box::into_raw(Box::new(TractInferenceFact(Default::default())));\n        Ok(())\n    })\n}\n\n/// Write an inference fact as its specification string.\n///\n/// The returned string must be freed by the caller using tract_free_cstring.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_fact_dump(\n    fact: *const TractInferenceFact,\n    spec: *mut *mut c_char,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(fact, spec);\n        *spec = CString::new(format!(\"{}\", (*fact).0))?.into_raw();\n        Ok(())\n    })\n}\n\n/// Destroy a fact.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_inference_fact_destroy(\n    fact: *mut *mut TractInferenceFact,\n) -> TRACT_RESULT {\n    release!(fact)\n}\n\n/// Dim\npub struct TractDim(tract_rs::Dim);\n\n/// Substitute symbols by the provided values in the Dim, generating a new one.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_dim_eval(\n    dim: *const TractDim,\n    nb_symbols: usize,\n    symbols: *const *const i8,\n    values: *const i64,\n    result: *mut *mut TractDim,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(dim, symbols, values, result);\n        let mut table = vec![];\n        for i in 0..nb_symbols {\n            let name = CStr::from_ptr(*symbols.add(i) as _)\n                .to_str()\n                .with_context(|| {\n                    format!(\"failed to parse symbol name for {i}th symbol (not utf8)\")\n                })?\n                .to_owned();\n            table.push((name, *values.add(i)));\n        }\n        let r = (*dim).0.eval(table)?;\n        *result = Box::into_raw(Box::new(TractDim(r)));\n        Ok(())\n    })\n}\n\n/// Try converting a Dim into an actual integer\n///\n/// Will fail if the Dim contains symbols.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_dim_to_int64(fact: *const TractDim, i: *mut i64) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(fact, i);\n        *i = (*fact).0.to_int64()?;\n        Ok(())\n    })\n}\n\n/// Write a dim as its specification string.\n///\n/// The returned string must be freed by the caller using tract_free_cstring.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_dim_dump(\n    dim: *const TractDim,\n    spec: *mut *mut c_char,\n) -> TRACT_RESULT {\n    wrap(|| unsafe {\n        check_not_null!(dim, spec);\n        *spec = CString::new((*dim).0.to_string())?.into_raw();\n        Ok(())\n    })\n}\n\n/// Destroy a dim.\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn tract_dim_destroy(dim: *mut *mut TractDim) -> TRACT_RESULT {\n    release!(dim)\n}\n\n// MISC\n\n// HELPERS\n\nunsafe fn state_run(\n    state: &mut State,\n    inputs: *mut *mut TractTensor,\n    outputs: *mut *mut TractTensor,\n) -> Result<()> {\n    unsafe {\n        let values: Vec<_> = std::slice::from_raw_parts(inputs, state.input_count()?)\n            .iter()\n            .map(|tv| (**tv).0.clone())\n            .collect();\n        let values = state.run(values)?;\n        for (i, value) in values.into_iter().enumerate() {\n            *(outputs.add(i)) = Box::into_raw(Box::new(TractTensor(value)))\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "api/generate-tract-h.sh",
    "content": "#!/bin/sh\n\nset -ex\n\ncargo install cbindgen\n\ncbindgen ffi > tract.h\ncp tract.h c\nmv tract.h proxy/sys\n"
  },
  {
    "path": "api/proxy/Cargo.toml",
    "content": "[package]\nname = \"tract-proxy\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/sonos/tract\"\nkeywords = [ \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\ninclude = [ \"Cargo.toml\", \"src/**/*.rs\", \"LICENSE*\" ]\n\n[dependencies]\nanyhow.workspace = true\nboow.workspace = true\nhome.workspace = true\nndarray.workspace = true\ntract-api.workspace = true\ntract-proxy-sys.workspace = true\n\n[dev-dependencies]\nreqwest.workspace = true\nrustls.workspace = true\ntempfile.workspace = true\nserde_json.workspace = true\n"
  },
  {
    "path": "api/proxy/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "api/proxy/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "api/proxy/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "api/proxy/ci.sh",
    "content": "#!/bin/sh\n\nROOT=$(dirname $(realpath $0))/../..\n\nset -ex\n\ncargo build --release -p tract-ffi $CARGO_EXTRA\nSO=$(cargo build  --message-format=json --release -p tract-ffi $CARGO_EXTRA | grep cdylib | jshon -e  filenames -e 0 -u)\nSO_PATH=$(dirname $SO)\nexport TRACT_DYLIB_SEARCH_PATH=$SO_PATH\nexport LD_LIBRARY_PATH=$SO_PATH\n\ncd $(dirname $(realpath $0))\ncargo test $CARGO_EXTRA\n"
  },
  {
    "path": "api/proxy/src/lib.rs",
    "content": "use std::ffi::{CStr, CString};\nuse std::path::Path;\nuse std::ptr::{null, null_mut};\n\nuse tract_api::*;\nuse tract_proxy_sys as sys;\n\nuse anyhow::{Context, Result};\nuse ndarray::*;\n\nmacro_rules! check {\n    ($expr:expr) => {\n        unsafe {\n            if $expr == sys::TRACT_RESULT_TRACT_RESULT_KO {\n                let buf = CStr::from_ptr(sys::tract_get_last_error());\n                Err(anyhow::anyhow!(buf.to_string_lossy().to_string()))\n            } else {\n                Ok(())\n            }\n        }\n    };\n}\n\nmacro_rules! wrapper {\n    ($new_type:ident, $c_type:ident, $dest:ident $(, $typ:ty )*) => {\n        #[derive(Debug, Clone)]\n        pub struct $new_type(*mut sys::$c_type $(, $typ)*);\n\n        impl Drop for $new_type {\n            fn drop(&mut self) {\n                unsafe {\n                    sys::$dest(&mut self.0);\n                }\n            }\n        }\n    };\n}\n\npub fn nnef() -> Result<Nnef> {\n    let mut nnef = null_mut();\n    check!(sys::tract_nnef_create(&mut nnef))?;\n    Ok(Nnef(nnef))\n}\n\npub fn onnx() -> Result<Onnx> {\n    let mut onnx = null_mut();\n    check!(sys::tract_onnx_create(&mut onnx))?;\n    Ok(Onnx(onnx))\n}\n\npub fn version() -> &'static str {\n    unsafe { CStr::from_ptr(sys::tract_version()).to_str().unwrap() }\n}\n\nwrapper!(Nnef, TractNnef, tract_nnef_destroy);\nimpl NnefInterface for Nnef {\n    type Model = Model;\n    fn load(&self, path: impl AsRef<Path>) -> Result<Model> {\n        let path = path.as_ref();\n        let path = CString::new(\n            path.to_str().with_context(|| format!(\"Failed to re-encode {path:?} to uff-8\"))?,\n        )?;\n        let mut model = null_mut();\n        check!(sys::tract_nnef_load(self.0, path.as_ptr(), &mut model))?;\n        Ok(Model(model))\n    }\n\n    fn load_buffer(&self, data: &[u8]) -> Result<Model> {\n        let mut model = null_mut();\n        check!(sys::tract_nnef_load_buffer(self.0, data.as_ptr() as _, data.len(), &mut model))?;\n        Ok(Model(model))\n    }\n\n    fn enable_tract_core(&mut self) -> Result<()> {\n        check!(sys::tract_nnef_enable_tract_core(self.0))\n    }\n\n    fn enable_tract_extra(&mut self) -> Result<()> {\n        check!(sys::tract_nnef_enable_tract_extra(self.0))\n    }\n\n    fn enable_tract_transformers(&mut self) -> Result<()> {\n        check!(sys::tract_nnef_enable_tract_transformers(self.0))\n    }\n\n    fn enable_onnx(&mut self) -> Result<()> {\n        check!(sys::tract_nnef_enable_onnx(self.0))\n    }\n\n    fn enable_pulse(&mut self) -> Result<()> {\n        check!(sys::tract_nnef_enable_pulse(self.0))\n    }\n\n    fn enable_extended_identifier_syntax(&mut self) -> Result<()> {\n        check!(sys::tract_nnef_enable_extended_identifier_syntax(self.0))\n    }\n\n    fn write_model_to_dir(&self, path: impl AsRef<Path>, model: &Model) -> Result<()> {\n        let path = path.as_ref();\n        let path = CString::new(\n            path.to_str().with_context(|| format!(\"Failed to re-encode {path:?} to uff-8\"))?,\n        )?;\n        check!(sys::tract_nnef_write_model_to_dir(self.0, path.as_ptr(), model.0))?;\n        Ok(())\n    }\n\n    fn write_model_to_tar(&self, path: impl AsRef<Path>, model: &Model) -> Result<()> {\n        let path = path.as_ref();\n        let path = CString::new(\n            path.to_str().with_context(|| format!(\"Failed to re-encode {path:?} to uff-8\"))?,\n        )?;\n        check!(sys::tract_nnef_write_model_to_tar(self.0, path.as_ptr(), model.0))?;\n        Ok(())\n    }\n\n    fn write_model_to_tar_gz(&self, path: impl AsRef<Path>, model: &Model) -> Result<()> {\n        let path = path.as_ref();\n        let path = CString::new(\n            path.to_str().with_context(|| format!(\"Failed to re-encode {path:?} to uff-8\"))?,\n        )?;\n        check!(sys::tract_nnef_write_model_to_tar_gz(self.0, path.as_ptr(), model.0))?;\n        Ok(())\n    }\n}\n\n// ONNX\nwrapper!(Onnx, TractOnnx, tract_onnx_destroy);\n\nimpl OnnxInterface for Onnx {\n    type InferenceModel = InferenceModel;\n    fn load(&self, path: impl AsRef<Path>) -> Result<InferenceModel> {\n        let path = path.as_ref();\n        let path = CString::new(\n            path.to_str().with_context(|| format!(\"Failed to re-encode {path:?} to uff-8\"))?,\n        )?;\n        let mut model = null_mut();\n        check!(sys::tract_onnx_load(self.0, path.as_ptr(), &mut model))?;\n        Ok(InferenceModel(model))\n    }\n\n    fn load_buffer(&self, data: &[u8]) -> Result<InferenceModel> {\n        let mut model = null_mut();\n        check!(sys::tract_onnx_load_buffer(self.0, data.as_ptr() as _, data.len(), &mut model))?;\n        Ok(InferenceModel(model))\n    }\n}\n\n// INFERENCE MODEL\nwrapper!(InferenceModel, TractInferenceModel, tract_inference_model_destroy);\nimpl InferenceModelInterface for InferenceModel {\n    type Model = Model;\n    type InferenceFact = InferenceFact;\n    fn input_count(&self) -> Result<usize> {\n        let mut count = 0;\n        check!(sys::tract_inference_model_input_count(self.0, &mut count))?;\n        Ok(count)\n    }\n\n    fn output_count(&self) -> Result<usize> {\n        let mut count = 0;\n        check!(sys::tract_inference_model_output_count(self.0, &mut count))?;\n        Ok(count)\n    }\n\n    fn input_name(&self, id: usize) -> Result<String> {\n        let mut ptr = null_mut();\n        check!(sys::tract_inference_model_input_name(self.0, id, &mut ptr))?;\n        unsafe {\n            let ret = CStr::from_ptr(ptr).to_str()?.to_owned();\n            sys::tract_free_cstring(ptr);\n            Ok(ret)\n        }\n    }\n\n    fn output_name(&self, id: usize) -> Result<String> {\n        let mut ptr = null_mut();\n        check!(sys::tract_inference_model_output_name(self.0, id, &mut ptr))?;\n        unsafe {\n            let ret = CStr::from_ptr(ptr).to_str()?.to_owned();\n            sys::tract_free_cstring(ptr);\n            Ok(ret)\n        }\n    }\n\n    fn input_fact(&self, id: usize) -> Result<InferenceFact> {\n        let mut ptr = null_mut();\n        check!(sys::tract_inference_model_input_fact(self.0, id, &mut ptr))?;\n        Ok(InferenceFact(ptr))\n    }\n\n    fn set_input_fact(\n        &mut self,\n        id: usize,\n        fact: impl AsFact<Self, Self::InferenceFact>,\n    ) -> Result<()> {\n        let fact = fact.as_fact(self)?;\n        check!(sys::tract_inference_model_set_input_fact(self.0, id, fact.0))?;\n        Ok(())\n    }\n\n    fn output_fact(&self, id: usize) -> Result<InferenceFact> {\n        let mut ptr = null_mut();\n        check!(sys::tract_inference_model_output_fact(self.0, id, &mut ptr))?;\n        Ok(InferenceFact(ptr))\n    }\n\n    fn set_output_fact(\n        &mut self,\n        id: usize,\n        fact: impl AsFact<InferenceModel, InferenceFact>,\n    ) -> Result<()> {\n        let fact = fact.as_fact(self)?;\n        check!(sys::tract_inference_model_set_output_fact(self.0, id, fact.0))?;\n        Ok(())\n    }\n\n    fn analyse(&mut self) -> Result<()> {\n        check!(sys::tract_inference_model_analyse(self.0))?;\n        Ok(())\n    }\n\n    fn into_model(mut self) -> Result<Self::Model> {\n        let mut ptr = null_mut();\n        check!(sys::tract_inference_model_into_model(&mut self.0, &mut ptr))?;\n        Ok(Model(ptr))\n    }\n}\n\n// MODEL\nwrapper!(Model, TractModel, tract_model_destroy);\n\nimpl ModelInterface for Model {\n    type Fact = Fact;\n    type Tensor = Tensor;\n    type Runnable = Runnable;\n    fn input_count(&self) -> Result<usize> {\n        let mut count = 0;\n        check!(sys::tract_model_input_count(self.0, &mut count))?;\n        Ok(count)\n    }\n\n    fn output_count(&self) -> Result<usize> {\n        let mut count = 0;\n        check!(sys::tract_model_output_count(self.0, &mut count))?;\n        Ok(count)\n    }\n\n    fn input_name(&self, id: usize) -> Result<String> {\n        let mut ptr = null_mut();\n        check!(sys::tract_model_input_name(self.0, id, &mut ptr))?;\n        unsafe {\n            let ret = CStr::from_ptr(ptr).to_str()?.to_owned();\n            sys::tract_free_cstring(ptr);\n            Ok(ret)\n        }\n    }\n\n    fn output_name(&self, id: usize) -> Result<String> {\n        let mut ptr = null_mut();\n        check!(sys::tract_model_output_name(self.0, id, &mut ptr))?;\n        unsafe {\n            let ret = CStr::from_ptr(ptr).to_str()?.to_owned();\n            sys::tract_free_cstring(ptr);\n            Ok(ret)\n        }\n    }\n\n    fn input_fact(&self, id: usize) -> Result<Fact> {\n        let mut ptr = null_mut();\n        check!(sys::tract_model_input_fact(self.0, id, &mut ptr))?;\n        Ok(Fact(ptr))\n    }\n\n    fn output_fact(&self, id: usize) -> Result<Fact> {\n        let mut ptr = null_mut();\n        check!(sys::tract_model_output_fact(self.0, id, &mut ptr))?;\n        Ok(Fact(ptr))\n    }\n\n    fn into_runnable(self) -> Result<Runnable> {\n        let mut model = self;\n        let mut runnable = null_mut();\n        check!(sys::tract_model_into_runnable(&mut model.0, &mut runnable))?;\n        Ok(Runnable(runnable))\n    }\n\n    fn transform(&mut self, spec: impl Into<TransformSpec>) -> Result<()> {\n        let transform = spec.into().to_transform_string();\n        let t = CString::new(transform)?;\n        check!(sys::tract_model_transform(self.0, t.as_ptr()))?;\n        Ok(())\n    }\n\n    fn property_keys(&self) -> Result<Vec<String>> {\n        let mut len = 0;\n        check!(sys::tract_model_property_count(self.0, &mut len))?;\n        let mut keys = vec![null_mut(); len];\n        check!(sys::tract_model_property_names(self.0, keys.as_mut_ptr()))?;\n        unsafe {\n            keys.into_iter()\n                .map(|pc| {\n                    let s = CStr::from_ptr(pc).to_str()?.to_owned();\n                    sys::tract_free_cstring(pc);\n                    Ok(s)\n                })\n                .collect()\n        }\n    }\n\n    fn property(&self, name: impl AsRef<str>) -> Result<Tensor> {\n        let mut v = null_mut();\n        let name = CString::new(name.as_ref())?;\n        check!(sys::tract_model_property(self.0, name.as_ptr(), &mut v))?;\n        Ok(Tensor(v))\n    }\n\n    fn parse_fact(&self, spec: &str) -> Result<Self::Fact> {\n        let spec = CString::new(spec)?;\n        let mut ptr = null_mut();\n        check!(sys::tract_model_parse_fact(self.0, spec.as_ptr(), &mut ptr))?;\n        Ok(Fact(ptr))\n    }\n}\n\n// RUNTIME\nwrapper!(Runtime, TractRuntime, tract_runtime_release);\n\npub fn runtime_for_name(name: &str) -> Result<Runtime> {\n    let mut rt = null_mut();\n    let name = CString::new(name)?;\n    check!(sys::tract_runtime_for_name(name.as_ptr(), &mut rt))?;\n    Ok(Runtime(rt))\n}\n\nimpl RuntimeInterface for Runtime {\n    type Runnable = Runnable;\n\n    type Model = Model;\n\n    fn name(&self) -> Result<String> {\n        let mut ptr = null_mut();\n        check!(sys::tract_runtime_name(self.0, &mut ptr))?;\n        unsafe {\n            let ret = CStr::from_ptr(ptr).to_str()?.to_owned();\n            sys::tract_free_cstring(ptr);\n            Ok(ret)\n        }\n    }\n\n    fn prepare(&self, model: Self::Model) -> Result<Self::Runnable> {\n        let mut model = model;\n        let mut runnable = null_mut();\n        check!(sys::tract_runtime_prepare(self.0, &mut model.0, &mut runnable))?;\n        Ok(Runnable(runnable))\n    }\n}\n\n// RUNNABLE\nwrapper!(Runnable, TractRunnable, tract_runnable_release);\nunsafe impl Send for Runnable {}\nunsafe impl Sync for Runnable {}\n\nimpl RunnableInterface for Runnable {\n    type Tensor = Tensor;\n    type State = State;\n    type Fact = Fact;\n\n    fn run(&self, inputs: impl IntoInputs<Tensor>) -> Result<Vec<Tensor>> {\n        StateInterface::run(&mut self.spawn_state()?, inputs.into_inputs()?)\n    }\n\n    fn spawn_state(&self) -> Result<State> {\n        let mut state = null_mut();\n        check!(sys::tract_runnable_spawn_state(self.0, &mut state))?;\n        Ok(State(state))\n    }\n\n    fn input_count(&self) -> Result<usize> {\n        let mut count = 0;\n        check!(sys::tract_runnable_input_count(self.0, &mut count))?;\n        Ok(count)\n    }\n\n    fn output_count(&self) -> Result<usize> {\n        let mut count = 0;\n        check!(sys::tract_runnable_output_count(self.0, &mut count))?;\n        Ok(count)\n    }\n\n    fn input_fact(&self, id: usize) -> Result<Self::Fact> {\n        let mut ptr = null_mut();\n        check!(sys::tract_runnable_input_fact(self.0, id, &mut ptr))?;\n        Ok(Fact(ptr))\n    }\n\n    fn output_fact(&self, id: usize) -> Result<Self::Fact> {\n        let mut ptr = null_mut();\n        check!(sys::tract_runnable_output_fact(self.0, id, &mut ptr))?;\n        Ok(Fact(ptr))\n    }\n\n    fn property_keys(&self) -> Result<Vec<String>> {\n        let mut len = 0;\n        check!(sys::tract_runnable_property_count(self.0, &mut len))?;\n        let mut keys = vec![null_mut(); len];\n        check!(sys::tract_runnable_property_names(self.0, keys.as_mut_ptr()))?;\n        unsafe {\n            keys.into_iter()\n                .map(|pc| {\n                    let s = CStr::from_ptr(pc).to_str()?.to_owned();\n                    sys::tract_free_cstring(pc);\n                    Ok(s)\n                })\n                .collect()\n        }\n    }\n\n    fn property(&self, name: impl AsRef<str>) -> Result<Tensor> {\n        let mut v = null_mut();\n        let name = CString::new(name.as_ref())?;\n        check!(sys::tract_runnable_property(self.0, name.as_ptr(), &mut v))?;\n        Ok(Tensor(v))\n    }\n\n    fn cost_json(&self) -> Result<String> {\n        let input: Option<Vec<Tensor>> = None;\n        self.profile_json(input)\n    }\n\n    fn profile_json<I, IV, IE>(&self, inputs: Option<I>) -> Result<String>\n    where\n        I: IntoIterator<Item = IV>,\n        IV: TryInto<Self::Tensor, Error = IE>,\n        IE: Into<anyhow::Error>,\n    {\n        let inputs = if let Some(inputs) = inputs {\n            let inputs = inputs\n                .into_iter()\n                .map(|i| i.try_into().map_err(|e| e.into()))\n                .collect::<Result<Vec<Tensor>>>()?;\n            anyhow::ensure!(self.input_count()? == inputs.len());\n            Some(inputs)\n        } else {\n            None\n        };\n        let mut iptrs: Option<Vec<*mut sys::TractTensor>> =\n            inputs.as_ref().map(|is| is.iter().map(|v| v.0).collect());\n        let mut json: *mut i8 = null_mut();\n        let values = iptrs.as_mut().map(|it| it.as_mut_ptr()).unwrap_or(null_mut());\n\n        check!(sys::tract_runnable_profile_json(self.0, values, &mut json))?;\n        anyhow::ensure!(!json.is_null());\n        unsafe {\n            let s = CStr::from_ptr(json).to_owned();\n            sys::tract_free_cstring(json);\n            Ok(s.to_str()?.to_owned())\n        }\n    }\n}\n\n// STATE\nwrapper!(State, TractState, tract_state_destroy);\n\nimpl StateInterface for State {\n    type Tensor = Tensor;\n    type Fact = Fact;\n\n    fn run(&mut self, inputs: impl IntoInputs<Tensor>) -> Result<Vec<Tensor>> {\n        let inputs = inputs.into_inputs()?;\n        let mut outputs = vec![null_mut(); self.output_count()?];\n        let mut inputs: Vec<_> = inputs.iter().map(|v| v.0).collect();\n        check!(sys::tract_state_run(self.0, inputs.as_mut_ptr(), outputs.as_mut_ptr()))?;\n        let outputs = outputs.into_iter().map(Tensor).collect();\n        Ok(outputs)\n    }\n\n    fn input_count(&self) -> Result<usize> {\n        let mut count = 0;\n        check!(sys::tract_state_input_count(self.0, &mut count))?;\n        Ok(count)\n    }\n\n    fn output_count(&self) -> Result<usize> {\n        let mut count = 0;\n        check!(sys::tract_state_output_count(self.0, &mut count))?;\n        Ok(count)\n    }\n}\n\n// TENSOR\nwrapper!(Tensor, TractTensor, tract_tensor_destroy);\nunsafe impl Send for Tensor {}\nunsafe impl Sync for Tensor {}\n\nimpl TensorInterface for Tensor {\n    fn from_bytes(dt: DatumType, shape: &[usize], data: &[u8]) -> Result<Self> {\n        anyhow::ensure!(data.len() == shape.iter().product::<usize>() * dt.size_of());\n        let mut value = null_mut();\n        check!(sys::tract_tensor_from_bytes(\n            dt as _,\n            shape.len(),\n            shape.as_ptr(),\n            data.as_ptr() as _,\n            &mut value\n        ))?;\n        Ok(Tensor(value))\n    }\n\n    fn as_bytes(&self) -> Result<(DatumType, &[usize], &[u8])> {\n        let mut rank = 0;\n        let mut dt = sys::DatumType_TRACT_DATUM_TYPE_BOOL as _;\n        let mut shape = null();\n        let mut data = null();\n        check!(sys::tract_tensor_as_bytes(self.0, &mut dt, &mut rank, &mut shape, &mut data))?;\n        unsafe {\n            let dt: DatumType = std::mem::transmute(dt);\n            let shape = std::slice::from_raw_parts(shape, rank);\n            let len: usize = shape.iter().product();\n            let data = std::slice::from_raw_parts(data as *const u8, len * dt.size_of());\n            Ok((dt, shape, data))\n        }\n    }\n\n    fn datum_type(&self) -> Result<DatumType> {\n        let mut dt = sys::DatumType_TRACT_DATUM_TYPE_BOOL as _;\n        check!(sys::tract_tensor_as_bytes(\n            self.0,\n            &mut dt,\n            std::ptr::null_mut(),\n            std::ptr::null_mut(),\n            std::ptr::null_mut()\n        ))?;\n        unsafe {\n            let dt: DatumType = std::mem::transmute(dt);\n            Ok(dt)\n        }\n    }\n\n    fn convert_to(&self, to: DatumType) -> Result<Self> {\n        let mut new = null_mut();\n        check!(sys::tract_tensor_convert_to(self.0, to as _, &mut new))?;\n        Ok(Tensor(new))\n    }\n}\n\nimpl PartialEq for Tensor {\n    fn eq(&self, other: &Self) -> bool {\n        let Ok((me_dt, me_shape, me_data)) = self.as_bytes() else { return false };\n        let Ok((other_dt, other_shape, other_data)) = other.as_bytes() else { return false };\n        me_dt == other_dt && me_shape == other_shape && me_data == other_data\n    }\n}\n\ntensor_from_to_ndarray!();\n\n// FACT\nwrapper!(Fact, TractFact, tract_fact_destroy);\n\nimpl Fact {\n    fn new(model: &Model, spec: impl ToString) -> Result<Fact> {\n        let cstr = CString::new(spec.to_string())?;\n        let mut fact = null_mut();\n        check!(sys::tract_model_parse_fact(model.0, cstr.as_ptr(), &mut fact))?;\n        Ok(Fact(fact))\n    }\n\n    fn dump(&self) -> Result<String> {\n        let mut ptr = null_mut();\n        check!(sys::tract_fact_dump(self.0, &mut ptr))?;\n        unsafe {\n            let s = CStr::from_ptr(ptr).to_owned();\n            sys::tract_free_cstring(ptr);\n            Ok(s.to_str()?.to_owned())\n        }\n    }\n}\n\nimpl FactInterface for Fact {\n    type Dim = Dim;\n\n    fn datum_type(&self) -> Result<DatumType> {\n        let mut dt = 0u32;\n        check!(sys::tract_fact_datum_type(self.0, &mut dt as *const u32 as _))?;\n        Ok(unsafe { std::mem::transmute::<u32, DatumType>(dt) })\n    }\n\n    fn rank(&self) -> Result<usize> {\n        let mut rank = 0;\n        check!(sys::tract_fact_rank(self.0, &mut rank))?;\n        Ok(rank)\n    }\n\n    fn dim(&self, axis: usize) -> Result<Self::Dim> {\n        let mut ptr = null_mut();\n        check!(sys::tract_fact_dim(self.0, axis, &mut ptr))?;\n        Ok(Dim(ptr))\n    }\n}\n\nimpl std::fmt::Display for Fact {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self.dump() {\n            Ok(s) => f.write_str(&s),\n            Err(_) => Err(std::fmt::Error),\n        }\n    }\n}\n\n// INFERENCE FACT\nwrapper!(InferenceFact, TractInferenceFact, tract_inference_fact_destroy);\n\nimpl InferenceFact {\n    fn new(model: &InferenceModel, spec: impl ToString) -> Result<InferenceFact> {\n        let cstr = CString::new(spec.to_string())?;\n        let mut fact = null_mut();\n        check!(sys::tract_inference_fact_parse(model.0, cstr.as_ptr(), &mut fact))?;\n        Ok(InferenceFact(fact))\n    }\n\n    fn dump(&self) -> Result<String> {\n        let mut ptr = null_mut();\n        check!(sys::tract_inference_fact_dump(self.0, &mut ptr))?;\n        unsafe {\n            let s = CStr::from_ptr(ptr).to_owned();\n            sys::tract_free_cstring(ptr);\n            Ok(s.to_str()?.to_owned())\n        }\n    }\n}\n\nimpl InferenceFactInterface for InferenceFact {\n    fn empty() -> Result<InferenceFact> {\n        let mut fact = null_mut();\n        check!(sys::tract_inference_fact_empty(&mut fact))?;\n        Ok(InferenceFact(fact))\n    }\n}\n\nimpl Default for InferenceFact {\n    fn default() -> Self {\n        Self::empty().unwrap()\n    }\n}\n\nimpl std::fmt::Display for InferenceFact {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self.dump() {\n            Ok(s) => f.write_str(&s),\n            Err(_) => Err(std::fmt::Error),\n        }\n    }\n}\n\nas_inference_fact_impl!(InferenceModel, InferenceFact);\nas_fact_impl!(Model, Fact);\n\n// Dim\nwrapper!(Dim, TractDim, tract_dim_destroy);\n\nimpl Dim {\n    fn dump(&self) -> Result<String> {\n        let mut ptr = null_mut();\n        check!(sys::tract_dim_dump(self.0, &mut ptr))?;\n        unsafe {\n            let s = CStr::from_ptr(ptr).to_owned();\n            sys::tract_free_cstring(ptr);\n            Ok(s.to_str()?.to_owned())\n        }\n    }\n}\n\nimpl DimInterface for Dim {\n    fn eval(&self, values: impl IntoIterator<Item = (impl AsRef<str>, i64)>) -> Result<Self> {\n        let (names, values): (Vec<_>, Vec<_>) = values.into_iter().unzip();\n        let c_strings: Vec<CString> =\n            names.into_iter().map(|a| Ok(CString::new(a.as_ref())?)).collect::<Result<_>>()?;\n        let ptrs: Vec<_> = c_strings.iter().map(|cs| cs.as_ptr()).collect();\n        let mut ptr = null_mut();\n        check!(sys::tract_dim_eval(self.0, ptrs.len(), ptrs.as_ptr(), values.as_ptr(), &mut ptr))?;\n        Ok(Dim(ptr))\n    }\n\n    fn to_int64(&self) -> Result<i64> {\n        let mut i = 0;\n        check!(sys::tract_dim_to_int64(self.0, &mut i))?;\n        Ok(i)\n    }\n}\n\nimpl std::fmt::Display for Dim {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self.dump() {\n            Ok(s) => f.write_str(&s),\n            Err(_) => Err(std::fmt::Error),\n        }\n    }\n}\n"
  },
  {
    "path": "api/proxy/sys/Cargo.toml",
    "content": "[package]\nname = \"tract-proxy-sys\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/sonos/tract\"\nkeywords = [ \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\ninclude = [ \"Cargo.toml\", \"src/**/*.rs\", \"LICENSE*\", \"build.rs\", \"tract.h\" ]\n\n[build-dependencies]\nbindgen = \"0.72.1\"\n"
  },
  {
    "path": "api/proxy/sys/build.rs",
    "content": "use std::env;\nuse std::path::PathBuf;\n\nfn main() {\n    println!(\"cargo:rerun-if-env-changed=TRACT_DYLIB_SEARCH_PATH\");\n    println!(\"cargo:rerun-if-changed=tract.h\");\n    if let Ok(path) = std::env::var(\"TRACT_DYLIB_SEARCH_PATH\") {\n        println!(\"cargo:rustc-link-search={path}\");\n    }\n    println!(\"cargo:rustc-link-lib=tract\");\n\n    let bindings = bindgen::Builder::default()\n        .header(\"tract.h\")\n        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))\n        .generate()\n        .expect(\"Unable to generate bindings\");\n\n    let out_path = PathBuf::from(env::var(\"OUT_DIR\").unwrap());\n    bindings.write_to_file(out_path.join(\"bindings.rs\")).expect(\"Couldn't write bindings!\");\n}\n"
  },
  {
    "path": "api/proxy/sys/src/lib.rs",
    "content": "#![allow(non_upper_case_globals)]\n#![allow(non_camel_case_types)]\n#![allow(non_snake_case)]\n#![allow(improper_ctypes)]\n#![allow(deref_nullptr)]\n#![allow(unsafe_op_in_unsafe_fn)]\n#![allow(clippy::redundant_static_lifetimes)]\n#![allow(clippy::useless_transmute)]\n\ninclude!(concat!(env!(\"OUT_DIR\"), \"/bindings.rs\"));\n"
  },
  {
    "path": "api/proxy/sys/tract.h",
    "content": "#include <stdarg.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdlib.h>\n    typedef enum DatumType {\n      TRACT_DATUM_TYPE_BOOL = 1,\n      TRACT_DATUM_TYPE_U8 = 17,\n      TRACT_DATUM_TYPE_U16 = 18,\n      TRACT_DATUM_TYPE_U32 = 20,\n      TRACT_DATUM_TYPE_U64 = 24,\n      TRACT_DATUM_TYPE_I8 = 33,\n      TRACT_DATUM_TYPE_I16 = 34,\n      TRACT_DATUM_TYPE_I32 = 36,\n      TRACT_DATUM_TYPE_I64 = 40,\n      TRACT_DATUM_TYPE_F16 = 50,\n      TRACT_DATUM_TYPE_F32 = 52,\n      TRACT_DATUM_TYPE_F64 = 56,\n      TRACT_DATUM_TYPE_COMPLEX_I16 = 66,\n      TRACT_DATUM_TYPE_COMPLEX_I32 = 68,\n      TRACT_DATUM_TYPE_COMPLEX_I64 = 72,\n      TRACT_DATUM_TYPE_COMPLEX_F16 = 82,\n      TRACT_DATUM_TYPE_COMPLEX_F32 = 84,\n      TRACT_DATUM_TYPE_COMPLEX_F64 = 88,\n    } DatumType;\n\n\n/**\n * Used as a return type of functions that can encounter errors.\n * If the function encountered an error, you can retrieve it using the `tract_get_last_error`\n * function\n */\ntypedef enum TRACT_RESULT {\n  /**\n   * The function returned successfully\n   */\n  TRACT_RESULT_OK = 0,\n  /**\n   * The function returned an error\n   */\n  TRACT_RESULT_KO = 1,\n} TRACT_RESULT;\n\n/**\n * Dim\n */\ntypedef struct TractDim TractDim;\n\ntypedef struct TractFact TractFact;\n\ntypedef struct TractInferenceFact TractInferenceFact;\n\ntypedef struct TractInferenceModel TractInferenceModel;\n\ntypedef struct TractModel TractModel;\n\ntypedef struct TractNnef TractNnef;\n\ntypedef struct TractOnnx TractOnnx;\n\ntypedef struct TractRunnable TractRunnable;\n\ntypedef struct TractRuntime TractRuntime;\n\ntypedef struct TractState TractState;\n\ntypedef struct TractTensor TractTensor;\n\n/**\n * Retrieve the last error that happened in this thread. A function encountered an error if\n * its return type is of type `TRACT_RESULT` and it returned `TRACT_RESULT_KO`.\n *\n * # Return value\n *  It returns a pointer to a null-terminated UTF-8 string that will contain the error description.\n *  Rust side keeps ownership of the buffer. It will be valid as long as no other tract calls is\n *  performed by the thread.\n *  If no error occured, null is returned.\n */\nconst char *tract_get_last_error(void);\n\n/**\n * Returns a pointer to a static buffer containing a null-terminated version string.\n *\n * The returned pointer must not be freed.\n */\nconst char *tract_version(void);\n\n/**\n * Frees a string allocated by libtract.\n */\nvoid tract_free_cstring(char *ptr);\n\n/**\n * Creates an instance of an NNEF framework and parser that can be used to load and dump NNEF models.\n *\n * The returned object should be destroyed with `tract_nnef_destroy` once the model\n * has been loaded.\n */\nenum TRACT_RESULT tract_nnef_create(struct TractNnef **nnef);\n\nenum TRACT_RESULT tract_nnef_enable_tract_core(struct TractNnef *nnef);\n\nenum TRACT_RESULT tract_nnef_enable_tract_extra(struct TractNnef *nnef);\n\nenum TRACT_RESULT tract_nnef_enable_tract_transformers(struct TractNnef *nnef);\n\nenum TRACT_RESULT tract_nnef_enable_onnx(struct TractNnef *nnef);\n\nenum TRACT_RESULT tract_nnef_enable_pulse(struct TractNnef *nnef);\n\nenum TRACT_RESULT tract_nnef_enable_extended_identifier_syntax(struct TractNnef *nnef);\n\n/**\n * Destroy the NNEF parser. It is safe to detroy the NNEF parser once the model had been loaded.\n */\nenum TRACT_RESULT tract_nnef_destroy(struct TractNnef **nnef);\n\n/**\n * Parse and load an NNEF model as a tract TypedModel.\n *\n * `path` is a null-terminated utf-8 string pointer. It can be an archive (tar or tar.gz file) or a\n * directory.\n */\nenum TRACT_RESULT tract_nnef_load(const struct TractNnef *nnef,\n                                  const char *path,\n                                  struct TractModel **model);\n\n/**\n * Parse and load an NNEF buffer as a tract TypedModel.\n *\n * `data` is a buffer pointer\n * `len` ise the buffer len\n */\nenum TRACT_RESULT tract_nnef_load_buffer(const struct TractNnef *nnef,\n                                         const void *data,\n                                         uintptr_t len,\n                                         struct TractModel **model);\n\n/**\n * Dump a TypedModel as a NNEF tar file.\n *\n * `path` is a null-terminated utf-8 string pointer to the `.tar` file to be created.\n *\n * This function creates a plain, non-compressed, archive.\n */\nenum TRACT_RESULT tract_nnef_write_model_to_tar(const struct TractNnef *nnef,\n                                                const char *path,\n                                                const struct TractModel *model);\n\n/**\n * Dump a TypedModel as a NNEF .tar.gz file.\n *\n * `path` is a null-terminated utf-8 string pointer to the `.tar.gz` file to be created.\n */\nenum TRACT_RESULT tract_nnef_write_model_to_tar_gz(const struct TractNnef *nnef,\n                                                   const char *path,\n                                                   const struct TractModel *model);\n\n/**\n * Dump a TypedModel as a NNEF directory.\n *\n * `path` is a null-terminated utf-8 string pointer to the directory to be created.\n *\n * This function creates a plain, non-compressed, archive.\n */\nenum TRACT_RESULT tract_nnef_write_model_to_dir(const struct TractNnef *nnef,\n                                                const char *path,\n                                                const struct TractModel *model);\n\n/**\n * Creates an instance of an ONNX framework and parser that can be used to load models.\n *\n * The returned object should be destroyed with `tract_nnef_destroy` once the model\n * has been loaded.\n */\nenum TRACT_RESULT tract_onnx_create(struct TractOnnx **onnx);\n\n/**\n * Destroy the NNEF parser. It is safe to detroy the NNEF parser once the model had been loaded.\n */\nenum TRACT_RESULT tract_onnx_destroy(struct TractOnnx **onnx);\n\n/**\n * Parse and load an ONNX model as a tract InferenceModel.\n *\n * `path` is a null-terminated utf-8 string pointer. It must point to a `.onnx` model file.\n */\nenum TRACT_RESULT tract_onnx_load(const struct TractOnnx *onnx,\n                                  const char *path,\n                                  struct TractInferenceModel **model);\n\n/**\n * Parse and load an ONNX buffer as a tract InferenceModel.\n *\n * `data` is a buffer pointer\n * `len` ise the buffer len\n */\nenum TRACT_RESULT tract_onnx_load_buffer(const struct TractOnnx *onnx,\n                                         const void *data,\n                                         uintptr_t len,\n                                         struct TractInferenceModel **model);\n\n/**\n * Query an InferenceModel input counts.\n */\nenum TRACT_RESULT tract_inference_model_input_count(const struct TractInferenceModel *model,\n                                                    uintptr_t *inputs);\n\n/**\n * Query an InferenceModel output counts.\n */\nenum TRACT_RESULT tract_inference_model_output_count(const struct TractInferenceModel *model,\n                                                     uintptr_t *outputs);\n\n/**\n * Query the name of a model input.\n *\n * The returned name must be freed by the caller using tract_free_cstring.\n */\nenum TRACT_RESULT tract_inference_model_input_name(const struct TractInferenceModel *model,\n                                                   uintptr_t input,\n                                                   char **name);\n\n/**\n * Query the name of a model output.\n *\n * The returned name must be freed by the caller using tract_free_cstring.\n */\nenum TRACT_RESULT tract_inference_model_output_name(const struct TractInferenceModel *model,\n                                                    uintptr_t output,\n                                                    int8_t **name);\n\n/**\n * Query a model input fact.\n */\nenum TRACT_RESULT tract_inference_model_input_fact(const struct TractInferenceModel *model,\n                                                   uintptr_t input_id,\n                                                   struct TractInferenceFact **fact);\n\n/**\n * Set an input fact of an InferenceModel.\n *\n * The `fact` argument is only borrowed by this function, it still must be destroyed.\n * `fact` can be set to NULL to erase the current output fact of the model.\n */\nenum TRACT_RESULT tract_inference_model_set_input_fact(struct TractInferenceModel *model,\n                                                       uintptr_t input_id,\n                                                       const struct TractInferenceFact *fact);\n\n/**\n * Query an output fact for an InferenceModel.\n *\n * The return model must be freed using `tract_inference_fact_destroy`.\n */\nenum TRACT_RESULT tract_inference_model_output_fact(const struct TractInferenceModel *model,\n                                                    uintptr_t output_id,\n                                                    struct TractInferenceFact **fact);\n\n/**\n * Set an output fact of an InferenceModel.\n *\n * The `fact` argument is only borrowed by this function, it still must be destroyed.\n * `fact` can be set to NULL to erase the current output fact of the model.\n */\nenum TRACT_RESULT tract_inference_model_set_output_fact(struct TractInferenceModel *model,\n                                                        uintptr_t output_id,\n                                                        const struct TractInferenceFact *fact);\n\n/**\n * Analyse an InferencedModel in-place.\n */\nenum TRACT_RESULT tract_inference_model_analyse(struct TractInferenceModel *model);\n\n/**\n * Transform a fully analysed InferenceModel to a TypedModel.\n *\n * This function takes ownership of the InferenceModel `model` whether it succeeds\n * or not. `tract_inference_model_destroy` must not be used on `model`.\n *\n * On the other hand, caller will be owning the newly created typed model.\n */\nenum TRACT_RESULT tract_inference_model_into_model(struct TractInferenceModel **model,\n                                                   struct TractModel **typed);\n\n/**\n * Destroy an InferenceModel.\n */\nenum TRACT_RESULT tract_inference_model_destroy(struct TractInferenceModel **model);\n\n/**\n * Query an InferenceModel input counts.\n */\nenum TRACT_RESULT tract_model_input_count(const struct TractModel *model, uintptr_t *inputs);\n\n/**\n * Query an InferenceModel output counts.\n */\nenum TRACT_RESULT tract_model_output_count(const struct TractModel *model, uintptr_t *outputs);\n\n/**\n * Query the name of a model input.\n *\n * The returned name must be freed by the caller using tract_free_cstring.\n */\nenum TRACT_RESULT tract_model_input_name(const struct TractModel *model,\n                                         uintptr_t input,\n                                         char **name);\n\n/**\n * Query the input fact of a model.\n *\n * Thre returned fact must be freed with tract_fact_destroy.\n */\nenum TRACT_RESULT tract_model_input_fact(const struct TractModel *model,\n                                         uintptr_t input_id,\n                                         struct TractFact **fact);\n\n/**\n * Query the name of a model output.\n *\n * The returned name must be freed by the caller using tract_free_cstring.\n */\nenum TRACT_RESULT tract_model_output_name(const struct TractModel *model,\n                                          uintptr_t output,\n                                          char **name);\n\n/**\n * Query the output fact of a model.\n *\n * Thre returned fact must be freed with tract_fact_destroy.\n */\nenum TRACT_RESULT tract_model_output_fact(const struct TractModel *model,\n                                          uintptr_t input_id,\n                                          struct TractFact **fact);\n\n/**\n * Apply a transform to the model.\n */\nenum TRACT_RESULT tract_model_transform(struct TractModel *model, const int8_t *transform);\n\n/**\n * Perform a profile of the model using the provided inputs.\n */\nenum TRACT_RESULT tract_runnable_profile_json(struct TractRunnable *model,\n                                              struct TractTensor **inputs,\n                                              int8_t **json);\n\n/**\n * Convert a TypedModel into a TypedRunnableModel.\n *\n * This function transfers ownership of the `model` argument to the newly-created `runnable` model.\n *\n * Runnable are reference counted. When done, it should be released with `tract_runnable_release`.\n */\nenum TRACT_RESULT tract_model_into_runnable(struct TractModel **model,\n                                            struct TractRunnable **runnable);\n\n/**\n * Query the number of properties in a model.\n */\nenum TRACT_RESULT tract_model_property_count(const struct TractModel *model, uintptr_t *count);\n\n/**\n * Query the properties names of a model.\n *\n * The \"names\" array should be big enough to fit `tract_model_property_count` string pointers.\n *\n * Each name will have to be freed using `tract_free_cstring`.\n */\nenum TRACT_RESULT tract_model_property_names(const struct TractModel *model, int8_t **names);\n\n/**\n * Query a property tensor in a model.\n */\nenum TRACT_RESULT tract_model_property(const struct TractModel *model,\n                                       const int8_t *name,\n                                       struct TractTensor **tensor);\n\n/**\n * Parse a fact specification string into an Fact.\n *\n * The returned fact must be free with `tract_fact_destroy`.\n */\nenum TRACT_RESULT tract_model_parse_fact(struct TractModel *model,\n                                         const char *spec,\n                                         struct TractFact **fact);\n\n/**\n * Destroy a TypedModel.\n */\nenum TRACT_RESULT tract_model_destroy(struct TractModel **model);\n\n/**\n * Creates an instance of a tract Runtime that can be used to run model on a specific\n * hardware / software stack (like a GPU).\n *\n * The returned object should be released with `tract_runtime_release`.\n */\nenum TRACT_RESULT tract_runtime_for_name(const char *name, struct TractRuntime **nnef);\n\n/**\n * Query the name of a Runtime.\n *\n * The returned name must be freed by the caller using tract_free_cstring.\n */\nenum TRACT_RESULT tract_runtime_name(const struct TractRuntime *runtime, char **name);\n\n/**\n * Convert a Model into a Runnable for this Runtime.\n *\n * This function transfers ownership of the `model` argument to the newly-created `runnable` model.\n *\n * Runnable are reference counted. When done, it should be released with `tract_runnable_release`.\n */\nenum TRACT_RESULT tract_runtime_prepare(const struct TractRuntime *runtime,\n                                        struct TractModel **model,\n                                        struct TractRunnable **runnable);\n\nenum TRACT_RESULT tract_runtime_release(struct TractRuntime **runtime);\n\n/**\n * Spawn a session state from a runnable model.\n *\n * This function does not take ownership of the `runnable` object, it can be used again to spawn\n * other state instances. The runnable object is internally reference counted, it will be\n * kept alive as long as any associated `State` exists (or as long as the `runnable` is not\n * explicitely release with `tract_runnable_release`).\n *\n * `state` is a newly-created object. It should ultimately be detroyed with `tract_state_destroy`.\n */\nenum TRACT_RESULT tract_runnable_spawn_state(struct TractRunnable *runnable,\n                                             struct TractState **state);\n\n/**\n * Convenience function to run a stateless model.\n *\n * `inputs` is a pointer to an pre-existing array of input TractTensor. Its length *must* be equal\n * to the number of inputs of the models. The function does not take ownership of the input\n * tensors.\n * `outputs` is a pointer to a pre-existing array of TractTensor pointers that will be overwritten\n * with pointers to output tensors. These tensors are under the responsiblity of the caller, it\n * will have to release them with `tract_tensor_destroy`.\n */\nenum TRACT_RESULT tract_runnable_run(struct TractRunnable *runnable,\n                                     struct TractTensor **inputs,\n                                     struct TractTensor **outputs);\n\n/**\n * Query a Runnable input counts.\n */\nenum TRACT_RESULT tract_runnable_input_count(const struct TractRunnable *model, uintptr_t *inputs);\n\n/**\n * Query an Runnable output counts.\n */\nenum TRACT_RESULT tract_runnable_output_count(const struct TractRunnable *model,\n                                              uintptr_t *outputs);\n\n/**\n * Query the input fact of a runnable model.\n *\n * Thre returned fact must be freed with tract_fact_destroy.\n */\nenum TRACT_RESULT tract_runnable_input_fact(const struct TractRunnable *runnable,\n                                            uintptr_t input_id,\n                                            struct TractFact **fact);\n\n/**\n * Query the output fact of a runnable model.\n *\n * Thre returned fact must be freed with tract_fact_destroy.\n */\nenum TRACT_RESULT tract_runnable_output_fact(const struct TractRunnable *runnable,\n                                             uintptr_t output_id,\n                                             struct TractFact **fact);\n\n/**\n * Query the number of properties in a runnable model.\n */\nenum TRACT_RESULT tract_runnable_property_count(const struct TractRunnable *model,\n                                                uintptr_t *count);\n\n/**\n * Query the properties names of a runnable model.\n *\n * The \"names\" array should be big enough to fit `tract_model_property_count` string pointers.\n *\n * Each name will have to be freed using `tract_free_cstring`.\n */\nenum TRACT_RESULT tract_runnable_property_names(const struct TractRunnable *model, int8_t **names);\n\n/**\n * Query a property tensor in a runnable model.\n */\nenum TRACT_RESULT tract_runnable_property(const struct TractRunnable *model,\n                                          const int8_t *name,\n                                          struct TractTensor **tensor);\n\nenum TRACT_RESULT tract_runnable_release(struct TractRunnable **runnable);\n\n/**\n * Create a TractTensor from caller data and metadata.\n *\n * This call copies the data into tract space. All the pointers only need to be alive for the\n * duration of the call.\n *\n * rank is the number of dimensions of the tensor (i.e. the length of the shape vector).\n *\n * The returned tensor must be destroyed by `tract_tensor_destroy`.\n */\nenum TRACT_RESULT tract_tensor_from_bytes(DatumType datum_type,\n                                         uintptr_t rank,\n                                         const uintptr_t *shape,\n                                         void *data,\n                                         struct TractTensor **tensor);\n\n/**\n * Write a tensor as a debug string\n *\n * The returned string must be freed by the caller using tract_free_cstring.\n */\nenum TRACT_RESULT tract_tensor_dump(const struct TractTensor *tensor, char **spec);\n\n/**\n * Convert a tensor to a new datum type.\n *\n * This function will perform a cheap shallow clone if the destination type is\n * the same as the current type, otherwise it returns a newly allocated tensor instead.\n *\n * In both cases, the returned tensor must be destroyed by `tract_tensor_destroy`.\n * The input tensor is not consumed, it still need to be destroyed.\n */\nenum TRACT_RESULT tract_tensor_convert_to(const struct TractTensor *input,\n                                         DatumType datum_type,\n                                         struct TractTensor **output);\n\n/**\n * Destroy a tensor.\n */\nenum TRACT_RESULT tract_tensor_destroy(struct TractTensor **tensor);\n\n/**\n * Inspect part of a tensor. Except `tensor`, all argument pointers can be null if only some specific bits\n * are required.\n */\nenum TRACT_RESULT tract_tensor_as_bytes(struct TractTensor *tensor,\n                                       DatumType *datum_type,\n                                       uintptr_t *rank,\n                                       const uintptr_t **shape,\n                                       const void **data);\n\n/**\n * Run a turn on a model state\n *\n * `inputs` is a pointer to an pre-existing array of input TractTensor. Its length *must* be equal\n * to the number of inputs of the models. The function does not take ownership of the input\n * tensors.\n * `outputs` is a pointer to a pre-existing array of TractTensor pointers that will be overwritten\n * with pointers to output tensors. These tensors are under the responsiblity of the caller, it\n * will have to release them with `tract_tensor_destroy`.\n */\nenum TRACT_RESULT tract_state_run(struct TractState *state,\n                                  struct TractTensor **inputs,\n                                  struct TractTensor **outputs);\n\n/**\n * Query a State input counts.\n */\nenum TRACT_RESULT tract_state_input_count(const struct TractState *state, uintptr_t *inputs);\n\n/**\n * Query an State output counts.\n */\nenum TRACT_RESULT tract_state_output_count(const struct TractState *state, uintptr_t *outputs);\n\nenum TRACT_RESULT tract_state_destroy(struct TractState **state);\n\n/**\n * Gets the rank (aka number of axes/dimensions) of a fact.\n */\nenum TRACT_RESULT tract_fact_rank(const struct TractFact *fact, uintptr_t *rank);\n\n/**\n * Extract the datum type of the fact.\n */\nenum TRACT_RESULT tract_fact_datum_type(const struct TractFact *fact, DatumType *datum_type);\n\n/**\n * Extract the dimension from one dimension of the fact.\n */\nenum TRACT_RESULT tract_fact_dim(const struct TractFact *fact,\n                                 uintptr_t axis,\n                                 struct TractDim **dim);\n\n/**\n * Write a fact as its specification string.\n *\n * The returned string must be freed by the caller using tract_free_cstring.\n */\nenum TRACT_RESULT tract_fact_dump(const struct TractFact *fact, char **spec);\n\nenum TRACT_RESULT tract_fact_destroy(struct TractFact **fact);\n\n/**\n * Parse a fact specification string into an InferenceFact.\n *\n * The returned fact must be free with `tract_inference_fact_destroy`.\n */\nenum TRACT_RESULT tract_inference_fact_parse(struct TractInferenceModel *model,\n                                             const char *spec,\n                                             struct TractInferenceFact **fact);\n\n/**\n * Creates an empty inference fact.\n *\n * The returned fact must be freed by the caller using tract_inference_fact_destroy\n */\nenum TRACT_RESULT tract_inference_fact_empty(struct TractInferenceFact **fact);\n\n/**\n * Write an inference fact as its specification string.\n *\n * The returned string must be freed by the caller using tract_free_cstring.\n */\nenum TRACT_RESULT tract_inference_fact_dump(const struct TractInferenceFact *fact, char **spec);\n\n/**\n * Destroy a fact.\n */\nenum TRACT_RESULT tract_inference_fact_destroy(struct TractInferenceFact **fact);\n\n/**\n * Substitute symbols by the provided values in the Dim, generating a new one.\n */\nenum TRACT_RESULT tract_dim_eval(const struct TractDim *dim,\n                                 uintptr_t nb_symbols,\n                                 const int8_t *const *symbols,\n                                 const int64_t *values,\n                                 struct TractDim **result);\n\n/**\n * Try converting a Dim into an actual integer\n *\n * Will fail if the Dim contains symbols.\n */\nenum TRACT_RESULT tract_dim_to_int64(const struct TractDim *fact, int64_t *i);\n\n/**\n * Write a dim as its specification string.\n *\n * The returned string must be freed by the caller using tract_free_cstring.\n */\nenum TRACT_RESULT tract_dim_dump(const struct TractDim *dim, char **spec);\n\n/**\n * Destroy a dim.\n */\nenum TRACT_RESULT tract_dim_destroy(struct TractDim **dim);\n"
  },
  {
    "path": "api/proxy/tests/mobilenet.rs",
    "content": "use tract_api::*;\nuse tract_proxy::*;\n\ninclude!(\"../../tests/mobilenet/mod.rs\");\n"
  },
  {
    "path": "api/py/.gitignore",
    "content": "__pycache__\n*.so\n*.egg-info\n*.onnx\nbuild\nmobilenet_v2_1.0.onnx.nnef.tgz\nrust-workspace\ndist\n\n"
  },
  {
    "path": "api/py/MANIFEST.in",
    "content": "graft rust-workspace\ngraft docs\n"
  },
  {
    "path": "api/py/_static/redirect-index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"refresh\" content=\"0; url=docs/index.html\">\n  <script>window.location.href = \"docs/index.html\";</script>\n</head>\n<body>\n  <p>Redirecting to <a href=\"docs/index.html\">documentation</a>...</p>\n</body>\n</html>\n"
  },
  {
    "path": "api/py/_static/version-switcher.js",
    "content": "// Version switcher for multi-version gh-pages docs.\n// Reads versions.json (mike-compatible format) from the site root and injects\n// a <select> into the page.\n// Format: [{\"version\": \"0.22.0\", \"title\": \"0.22.0\", \"aliases\": [\"latest\"]}, ...]\n(function () {\n  \"use strict\";\n\n  // Resolve the site root from script src.\n  // Script lives at <root>/<version>/_static/version-switcher.js\n  var scriptSrc = document.currentScript && document.currentScript.src;\n  if (!scriptSrc) return;\n\n  var parts = scriptSrc.split(\"/\");\n  // pop \"version-switcher.js?...\", \"_static\", \"<version>\"\n  parts.pop(); // filename\n  parts.pop(); // _static\n  var current = parts.pop(); // version directory name\n  var siteRoot = parts.join(\"/\");\n\n  function injectSwitcher() {\n    fetch(siteRoot + \"/versions.json\")\n      .then(function (r) { return r.json(); })\n      .then(function (versions) {\n        var select = document.createElement(\"select\");\n        select.setAttribute(\"aria-label\", \"Version\");\n        select.style.cssText =\n          \"display:block;margin:0.5em auto;padding:4px 8px;\" +\n          \"border:1px solid var(--color-sidebar-border,#ccc);\" +\n          \"border-radius:4px;font-size:.85em;\" +\n          \"background:var(--color-sidebar-background,#fff);\" +\n          \"color:var(--color-sidebar-text,#333);cursor:pointer;\";\n\n        versions.forEach(function (v) {\n          var opt = document.createElement(\"option\");\n          opt.value = siteRoot + \"/\" + v.version + \"/\";\n          opt.textContent = v.title || v.version;\n          if (v.version === current) opt.selected = true;\n          select.appendChild(opt);\n        });\n\n        select.addEventListener(\"change\", function () {\n          window.location.href = this.value;\n        });\n\n        // Insert into Furo's sidebar brand area\n        var target =\n          document.querySelector(\".sidebar-brand\") ||\n          document.querySelector(\".sidebar-sticky\") ||\n          document.querySelector(\"header\");\n        if (target) {\n          target.appendChild(select);\n        }\n      })\n      .catch(function () {\n        // No versions.json (local build) — silently skip.\n      });\n  }\n\n  // Run immediately — script is at end of <body> so DOM is ready\n  injectSwitcher();\n})();\n"
  },
  {
    "path": "api/py/conf.py",
    "content": "project = \"tract-python\"\ncopyright = \"Sonos\"\nauthor = \"Sonos\"\n\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.napoleon\",\n    \"sphinx.ext.viewcode\",\n    \"myst_parser\",\n]\n\nhtml_theme = \"furo\"\nautodoc_member_order = \"bysource\"\nnapoleon_google_docstring = True\n\nmaster_doc = \"docs/index\"\n\nexclude_patterns = [\n    \"_build\",\n    \"_static\",\n    \".venv\",\n    \".pytest_cache\",\n    \"rust-workspace\",\n    \"tract.egg-info\",\n    \"tests\",\n    \"setup.py\",\n]\n\nhtml_static_path = [\"_static\"]\nhtml_js_files = [\"version-switcher.js\"]\n"
  },
  {
    "path": "api/py/docs/fact.md",
    "content": "# Facts and Dimensions\n\n```{eval-rst}\n.. automodule:: tract.fact\n   :members:\n\n.. automodule:: tract.dim\n   :members:\n```\n"
  },
  {
    "path": "api/py/docs/index.md",
    "content": "# `tract` python bindings\n\n`tract` is a library for neural network inference. While PyTorch and TensorFlow\ndeal with the much harder training problem, `tract` focuses on what happens once\nthe model in trained.\n\n`tract` ultimate goal is to use the model on end-user data (aka \"running the\nmodel\") as efficiently as possible, in a variety of possible deployments,\nincluding some which are no completely mainstream : a lot of energy have been\ninvested in making `tract` an efficient engine to run models on ARM single board\ncomputers.\n\n```{toctree}\n:hidden:\n\nonnx\nnnef\ninference_model\nmodel\nfact\nrunnable\ntensor\n```\n\n## API Reference\n\n- [ONNX](onnx.md) — load ONNX models\n- [NNEF](nnef.md) — load and save NNEF models\n- [Inference model](inference_model.md) — partially typed model from ONNX\n- [Model](model.md) — fully typed model, central to the cooking pipeline\n- [Facts and Dimensions](fact.md) — shape, type, and symbolic dimension information\n- [Runtime, Runnable and State](runnable.md) — runtimes (CPU, Metal, CUDA), execution, and stateful models\n- [Tensor](tensor.md) — tensor data\n\n## Getting started\n\n### Install tract library\n\n`pip install tract`. Prebuilt wheels are provided for x86-64 Linux and\nWindows, x86-64 and arm64 for MacOS.\n\n### Downloading the model\n\nFirst we need to obtain the model. We will download an ONNX-converted MobileNET\n2.7 from the ONNX model zoo.\n\n`wget https://github.com/onnx/models/raw/main/vision/classification/mobilenet/model/mobilenetv2-7.onnx`.\n\n### Preprocessing an image\n\nThen we need a sample image. You can use pretty much anything. If you lack\ninspiration, you can this picture of Grace Hopper.\n\n`wget https://s3.amazonaws.com/tract-ci-builds/tests/grace_hopper.jpg`\n\nWe will be needing `pillow` to load the image and crop it.\n\n`pip install pillow`\n\nNow let's start our python script. We will want to use tract, obviously, but we\nwill also need PIL's Image and numpy to put the data in the form MobileNet expects it.\n\n```python\n#!/usr/bin/env python\n\nimport tract\nimport numpy\nfrom PIL import Image\n```\n\nWe want to load the image, crop it into its central square, then scale this\nsquare to be 224x224.\n\n```python\nim = Image.open(\"grace_hopper.jpg\")\nif im.height > im.width:\n    top_crop = int((im.height - im.width) / 2)\n    im = im.crop((0, top_crop, im.width, top_crop + im.width))\nelse:\n    left_crop = int((im.width - im.height) / 2)\n    im = im.crop((left_crop, 0, left_crop + im_height, im.height))\nim = im.resize((224, 224))\nim = numpy.array(im)\n```\n\nAt this stage, we obtain a 224x224x3 tensor of 8-bit positive integers. We need to transform\nthese integers to floats and normalize them for MobileNet.\nAt some point during this normalization, numpy decides to promote our tensor to\ndouble precision, but our model is single precison, so we are converting it\nagain after the normalization.\n\n```python\nim = (im.astype(float) / 255. - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225]\nim = im.astype(numpy.single)\n```\n\nFinally, ONNX variant of Mobilenet expects its input in NCHW convention, and\nour data is in HWC. We need to move the C axis before H and W, then insert the\nN at the left.\n\n```python\nim = numpy.moveaxis(im, 2, 0)\nim = numpy.expand_dims(im, 0)\n```\n\n### Loading the model\n\nLoading a model is relatively simple. We need to instantiate the ONNX loader\nfirst, the we use it to load the model. Then we ask tract to optimize the model\nand get it ready to run.\n\n```python\nmodel = tract.onnx().load(\"./mobilenetv2-7.onnx\").into_model().into_runnable()\n```\n\nIf we wanted to process several images, this would only have to be done once\nout of our image loop.\n\n### Running the model\n\ntract run methods take a list of inputs and returns a list of outputs. Each input\ncan be a numpy array. The outputs are tract's own Tensor data type, which should\nbe converted to numpy array.\n\n```python\noutputs = model.run([im])\noutput = outputs[0].to_numpy()\n```\n\n### Interpreting the result\n\nIf we print the output, what we get is a array of 1000 values. Each value is\nthe score of our image on one of the 1000 categoris of ImageNet. What we want\nis to find the category with the highest score.\n\n```python\nprint(numpy.argmax(output))\n```\n\nIf all goes according to plan, this should output the number 652. There is a copy\nof ImageNet categories at the following URL, with helpful line numbering.\n\n```\nhttps://github.com/sonos/tract/blob/main/examples/nnef-mobilenet-v2/imagenet_slim_labels.txt\n```\n\nAnd... 652 is \"microphone\". Which is wrong. The trick is, the lines are\nnumbered from 1, while our results starts at 0, plus the label list includes a\n\"dummy\" label first that should be ignored. So the right value is at the line\n654: \"military uniform\". If you looked at the picture before you noticed that\nGrace Hopper is in uniform on the picture, so it does make sense.\n\n## Running on GPU\n\nThe getting started example above runs on the CPU, which is the default runtime.\nOn systems with an NVIDIA GPU, `tract` can leverage CUDA for accelerated\ninference. The only change is in how the model is prepared: instead of calling\n`into_runnable()`, use a CUDA runtime to prepare the model.\n\n```python\nimport tract\n\n# load and type the model as before\nmodel = tract.onnx().load(\"./mobilenetv2-7.onnx\").into_model()\n\n# prepare it for the CUDA runtime\ncuda = tract.runtime_for_name(\"cuda\")\nrunnable = cuda.prepare(model)\n\n# run exactly as before\noutputs = runnable.run([im])\noutput = outputs[0].to_numpy()\n```\n\nThe Metal runtime works the same way on Apple Silicon Macs: just replace\n`\"cuda\"` with `\"metal\"`.\n\n## Model cooking with `tract`\n\nOver the years of `tract` development, it became clear that beside \"training\"\nand \"running\", there was a third time in the life-cycle of a model. One of\nour contributors nicknamed it \"model cooking\" and the term stuck. This extra stage\nis about all what happens after the training and before running.\n\nIf training and Runtime are relatively easy to define, the model cooking gets a\nbit less obvious. It comes from the realisation that the training form (an ONNX\nor TensorFlow file or ste of files) of a model may is usually not the most\nconvenient form for running it. Every time a device loads a model in ONNX form\nand transform it into a suitable form for runtime, it goes through the same\nseries or more or less complicated operations, that can amount to several\nseconds of high-CPU usage for current models. When running the model on a\ndevice, this can have several negative impact on experience: the device will\ntake time to start-up, consume a lot of battery energy to get ready, maybe fight\nover CPU availability with other processes trying to get ready at the same\ninstant on the device.\n\nAs this sequence of operations is generally the same, it becomes relevant to\npersist the model resulting of the transformation. It could be persisted at the\nfirst application start-up for instance. But it could also be \"prepared\", or\n\"cooked\" before distribution to the devices.\n\n## Cooking to NNEF\n\n`tract` supports NNEF. It can read a NNEF neural network and run it. But it can\nalso dump its preferred representation of a model in NNEF.\n\nAt this stage, a possible path to production for a neural model becomes can be drawn:\n* model is trained, typically on big servers on the cloud, and exported to ONNX.\n* model is cooked, simplified, using `tract` command line or python bindings.\n* model is shipped to devices or servers in charge of running it.\n\n## Testing and benching models early\n\nAs soon as the model is in ONNX form, `tract` can load and run it. It gives\nopportunities to validate and test on the training system, asserting early on that\n`tract` will compute at runtime the same result than what the training model\npredicts, limiting the risk of late-minute surprise.\n\nBut tract command line can also be used to bench and profile an ONNX model on\nthe target system answering very early the \"will the device be fast enough\"\nquestion. The nature of neural network is such that in many cases an\nuntrained model, or a poorly trained one will perform the same computations than\nthe final model, so it may be possible to bench the model for on-device\nefficiency before going through a costly and long model training.\n\n## tract-opl\n\nNNEF is a pretty little standard. But we needed to go beyond it and we extended\nit in several ways. For instance, NNEF does not provide syntax for recurring\nneural network (LSTM and friends), which are an absolute must in signal and voice\nprocessing. `tract` also supports symbolic dimensions, which are useful to\nrepresent a late bound batch dimension (if you don't know in advance how many\ninputs will have to be computed concurrently).\n\n## Pulsing\n\nFor interactive applications where time plays a role (voice, signal, ...),\n`tract` can automatically transform batch models, to equivalent streaming models\nsuitable for runtime. While batch models are presented at training time the\nwhole signal in one go, a streaming model received the signal by \"pulse\" and\nproduces step by step the same output that the batching model.\n\nIt does not work for every model, `tract` can obviously not generate a model\nwhere the output at a time depends on input not received yet. Of course, models\nhave to be *causal* to be pulsable. For instance, a bi-directional LSTM is not\npulsable. Most convolution nets can be made causal at designe time by padding,\nor at cooking time by adding fixed delays.\n\nThis cooking step is a recurring annoyance in the real-time voice and signal\nfield : it can be done manually, but is very easy to get wrong. `tract` makes\nit automactic.\n"
  },
  {
    "path": "api/py/docs/inference_model.md",
    "content": "# Inference model\n\n```{eval-rst}\n.. automodule:: tract.inference_model\n   :members:\n```\n"
  },
  {
    "path": "api/py/docs/model.md",
    "content": "# Model (aka Typed Model)\n\n```{eval-rst}\n.. automodule:: tract.model\n   :members:\n```\n"
  },
  {
    "path": "api/py/docs/nnef.md",
    "content": "# NNEF\n\n```{eval-rst}\n.. automodule:: tract.nnef\n   :members:\n```\n"
  },
  {
    "path": "api/py/docs/onnx.md",
    "content": "# ONNX\n\n```{eval-rst}\n.. automodule:: tract.onnx\n   :members:\n```\n"
  },
  {
    "path": "api/py/docs/runnable.md",
    "content": "# Runtime, Runnable and State\n\n## Runtime\n\n```{eval-rst}\n.. autofunction:: tract.runtime.runtime_for_name\n\n.. autoclass:: tract.runtime.Runtime\n   :members:\n```\n\n## Runnable\n\n```{eval-rst}\n.. automodule:: tract.runnable\n   :members:\n```\n\n## State\n\n```{eval-rst}\n.. automodule:: tract.state\n   :members:\n```\n"
  },
  {
    "path": "api/py/docs/tensor.md",
    "content": "# Tensor\n\n```{eval-rst}\n.. automodule:: tract.tensor\n   :members:\n```\n"
  },
  {
    "path": "api/py/pyproject.toml",
    "content": "[build-system]\nrequires = [\n    \"setuptools >=80, <83\",\n    \"setuptools_rust >=1.12, <1.13\",\n    \"wheel >=0.46, <0.47\",\n    \"toml >=0.10, <0.11\"\n]\n\n[tool.cibuildwheel]\nbuild-frontend = \"build[uv]\"\nenvironment = \"PATH=$PATH:$HOME/.cargo/bin\"\ntest-requires = \"pytest\"\ntest-command = \"\"\"pytest {project}\"\"\"\n\n[tool.cibuildwheel.linux]\nskip = \"*i686 cp???t-*\"\nbefore-build = \"\"\"\nset -ex\nuv pip install --system \"numpy>=2,<3\" --config-settings=setup-args=\"-Dallow-noblas=true\"\ncargo --version || (curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal\n. $HOME/.cargo/env\nrustup toolchain add stable\nrustup default stable)\n[ -e $HOME/.local/bin/sccache ] || ./.travis/setup-sccache.sh ]\n\"\"\"\nenvironment = \"\"\"\nPATH=$HOME/.local/bin:$HOME/.cargo/bin:$PATH\nSCCACHE_DIR=$HOME/.cache/sccache\nSCCACHE_CACHE_SIZE=2G\nRUSTC_WRAPPER=sccache\n\"\"\"\n\n[tool.cibuildwheel.macos]\narchs = \"x86_64 universal2 arm64\"\nskip = \"pp* cp???t-*\"\nbefore-build = \"\"\"\nuv pip install --system \"numpy>=2,<3\" --config-settings=setup-args=\"-Dallow-noblas=true\"\nrustup target add aarch64-apple-darwin\n[ -e $HOME/.local/bin/sccache ] || ./.travis/setup-sccache.sh ]\n\"\"\"\n\n[tool.cibuildwheel.windows]\nbefore-build = \"\"\"\nchoco install mingw --version=8.1.0\nuv pip install --system \"numpy==1.25.2\"\n\"\"\"\nskip = \"*-win32 pp* cp???t*\"\n"
  },
  {
    "path": "api/py/requirements-docs.txt",
    "content": "sphinx\nfuro\nmyst-parser\n"
  },
  {
    "path": "api/py/requirements.txt",
    "content": "numpy==1.26.4\nsetuptools==82.0.1\nsetuptools_rust==1.12.1\ntoml==0.10.2\n"
  },
  {
    "path": "api/py/setup.py",
    "content": "from setuptools import setup\nfrom setuptools_rust import Binding, RustExtension\nimport shutil\nimport toml\nimport re\nimport os\n\nif not os.path.exists(\"rust-workspace\"):\n    shutil.copytree(\n        \"../..\",\n        \"rust-workspace\",\n        ignore = shutil.ignore_patterns(\".cached\", \"target\", \".git\", \"issue-*\", \".travis\", \"assets\", \".github\", \"py\")\n    )\n\nversion = os.environ.get(\"PYPI_VERSION_OVERRIDE\")\nif version is None or version == \"\":\n    version = toml.load(\"rust-workspace/api/Cargo.toml\")[\"package\"][\"version\"]\n    version = re.sub(\"\\-alpha\\.\", \"a\", version)\n    version = re.sub(\"\\\\-dev(\\\\.)?\", \".dev\", version)\n\nwith open('docs/index.md', 'r') as file:\n    readme = file.read()\n\nbuild_requires = toml.load(\"pyproject.toml\")[\"build-system\"][\"requires\"]\n\nsetup(\n        name=\"tract\",\n        author=\"Mathieu Poumeyrol, Sonos, and tract contributors\",\n        author_email=\"mathieu@poumeyrol.fr\",\n        keywords=\"onnx tensorflow nnef runtime neural network\",\n        version=version,\n        description=\"Python bindings for tract, a neural network inference engine\",\n        project_urls={\n            \"Documentation\": \"https://sonos.github.io/tract\",\n            \"Source\": \"https://github.com/sonos/tract\",\n        },\n        license=\"(Apache-2.0 OR MIT)\",\n        long_description=readme,\n        long_description_content_type=\"text/markdown\",\n        options={\"bdist_wheel\": {\"universal\": True}},\n        classifiers=[\n            \"Programming Language :: Python :: 3\",\n            \"Programming Language :: Python :: 3.9\",\n            \"Programming Language :: Python :: 3.10\",\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.12\",\n            \"Programming Language :: Python :: 3.13\",\n            \"Programming Language :: Rust\",\n            \"Topic :: Scientific/Engineering :: Mathematics\",\n            \"Topic :: Scientific/Engineering :: Artificial Intelligence\",\n            \"License :: OSI Approved :: Apache Software License\",\n            \"License :: OSI Approved :: MIT License\"\n            ],\n        rust_extensions=[RustExtension(\"tract.tract\", binding=Binding.NoBinding, path=\"rust-workspace/api/ffi/Cargo.toml\")],\n        packages=[\"tract\"],\n        zip_safe=False,\n        python_requires=\">=3.9\",\n        install_requires=[ \"numpy\" ],\n        extras_require={\n            \"test\": [\"pytest\"],\n            \"dev\": build_requires + [\"pytest\"],\n        },\n)\n"
  },
  {
    "path": "api/py/tests/mobilenet_onnx_test.py",
    "content": "import tract\nimport numpy\nimport urllib.request\nimport tempfile\nimport json\nimport pytest\nfrom pathlib import Path\n\ndef setup_module(module):\n    if not Path(\"mobilenetv2-7.onnx\").exists():\n        urllib.request.urlretrieve(\n            \"https://s3.amazonaws.com/tract-ci-builds/tests/mobilenetv2-7.onnx\",\n            \"mobilenetv2-7.onnx\",\n        )\n    if not Path(\"mobilenet_v2_1.0.onnx.nnef.tgz\").exists():\n        urllib.request.urlretrieve(\n            \"https://s3.amazonaws.com/tract-ci-builds/tests/mobilenet_v2_1.0.onnx.nnef.tgz\",\n            \"mobilenet_v2_1.0.onnx.nnef.tgz\"\n        )\n\ndef grace_hopper_1x3x224x244():\n    return numpy.load(Path(__file__).parent.parent / \"grace_hopper_1x3x224x244.npy\")\n\ndef test_version():\n    tract.version()\n\ndef test_onnx():\n    model = (\n        tract.onnx()\n        .load(\"./mobilenetv2-7.onnx\")\n        .into_model()\n        .into_runnable()\n    )\n    result = model.run([grace_hopper_1x3x224x244()])\n    confidences = result[0].to_numpy()\n    assert numpy.argmax(confidences) == 652\n\ndef test_state():\n    model = (\n        tract.onnx()\n        .load(\"./mobilenetv2-7.onnx\")\n        .into_model()\n        .into_runnable()\n    )\n    state = model.spawn_state()\n    result = state.run([grace_hopper_1x3x224x244()])\n    confidences = result[0].to_numpy()\n    assert numpy.argmax(confidences) == 652\n\ndef test_nnef_register():\n    tract.nnef().with_tract_core().with_onnx().with_pulse().with_tract_extra()\n\ndef test_nnef():\n    model = (\n        tract.nnef()\n        .load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")\n        .into_runnable()\n    )\n    result = model.run([grace_hopper_1x3x224x244()])\n    confidences = result[0].to_numpy()\n    assert numpy.argmax(confidences) == 652\n\ndef test_inference_model():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    assert model.input_count() == 1\n    assert model.output_count() == 1\n    assert model.input_name(0) == \"data\"\n    assert model.output_name(0) == \"mobilenetv20_output_flatten0_reshape0\"\n    assert str(model.input_fact(0)) == \"1,3,224,224,f32\"\n    model.set_input_fact(0, \"B,3,224,224,f32\")\n    model.set_output_fact(0, None)\n    model.analyse()\n    assert str(model.output_fact(0)) == \"B,1000,f32\"\n    typed = model.into_model()\n\n\ndef test_typed_model():\n    model = tract.nnef().load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")\n    assert model.input_count() == 1\n    assert model.output_count() == 1\n    assert model.input_name(0) == \"data\"\n    assert model.output_name(0) == \"conv_53\"\n    assert str(model.input_fact(0)) == \"1,3,224,224,f32\"\n    assert str(model.output_fact(0)) == \"1,1000,f32\"\n\ndef test_runtime():\n    model = tract.nnef().load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")\n    rt = tract.runtime_for_name(\"default\")\n    runnable = rt.prepare(model)\n    result = runnable.run([grace_hopper_1x3x224x244()])\n    confidences = result[0].to_numpy()\n    assert numpy.argmax(confidences) == 652\n\n\ndef test_concretize():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"B,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    assert str(typed.input_fact(0)) == \"B,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"B,1000,f32\"\n    typed.transform(tract.ConcretizeSymbols({\"B\": 1}))\n    assert str(typed.input_fact(0)) == \"1,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"1,1000,f32\"\n\ndef test_concretize_builder():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"B,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    typed.transform(tract.ConcretizeSymbols().value(\"B\", 1))\n    assert str(typed.input_fact(0)) == \"1,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"1,1000,f32\"\n\ndef test_concretize_raw_string():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"B,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    typed.transform('{\"name\":\"concretize_symbols\",\"values\":{\"B\":1}}')\n    assert str(typed.input_fact(0)) == \"1,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"1,1000,f32\"\n\ndef test_pulse():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"B,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    assert str(typed.input_fact(0)) == \"B,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"B,1000,f32\"\n    typed.transform(tract.Pulse(\"5\", symbol=\"B\"))\n    assert str(typed.input_fact(0)) == \"5,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"5,1000,f32\"\n    properties = typed.property_keys()\n    properties.sort()\n    assert properties == [\"pulse.delay\", \"pulse.input_axes\", \"pulse.output_axes\"]\n    assert typed.property(\"pulse.delay\").to_numpy() == [0]\n\ndef test_pulse_builder():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"B,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    typed.transform(tract.Pulse(\"5\").symbol(\"B\"))\n    assert str(typed.input_fact(0)) == \"5,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"5,1000,f32\"\n\ndef test_pulse_raw_string():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"B,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    typed.transform('{\"name\":\"pulse\",\"symbol\":\"B\",\"pulse\":\"5\"}')\n    assert str(typed.input_fact(0)) == \"5,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"5,1000,f32\"\n\ndef test_runtime_fact():\n    runnable = tract.nnef().load(\"mobilenet_v2_1.0.onnx.nnef.tgz\").into_runnable()\n    assert str(runnable.input_fact(0)) ==  \"1,3,224,224,f32\"\n    assert str(runnable.output_fact(0)) == \"1,1000,f32\"\n\ndef test_runtime_properties():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"B,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    typed.transform(tract.Pulse(\"5\", symbol=\"B\"))\n    runnable = typed.into_runnable()\n    properties = runnable.property_keys()\n    properties.sort()\n    assert properties == [\"pulse.delay\", \"pulse.input_axes\", \"pulse.output_axes\"]\n    assert runnable.property(\"pulse.delay\").to_numpy() == [0]\n\ndef test_f32_to_f16():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"1,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    typed.transform(tract.FloatPrecision(tract.DatumType.F32, tract.DatumType.F16))\n    assert str(typed.input_fact(0)) == \"1,3,224,224,f16\"\n    assert str(typed.output_fact(0)) == \"1,1000,f16\"\n\ndef test_f32_to_f16_raw_string():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"1,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    typed.transform(\"f32_to_f16\")\n    assert str(typed.input_fact(0)) == \"1,3,224,224,f16\"\n    assert str(typed.output_fact(0)) == \"1,1000,f16\"\n\ndef test_f16_to_f32():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"1,3,224,224,f32\")\n    model.analyse()\n\n    #Convert model to half\n    typed = model.into_model()\n    typed.transform(tract.FloatPrecision(tract.DatumType.F32, tract.DatumType.F16))\n    assert str(typed.input_fact(0)) == \"1,3,224,224,f16\"\n    assert str(typed.output_fact(0)) == \"1,1000,f16\"\n\n    # Convert back to f32\n    typed.transform(tract.FloatPrecision(tract.DatumType.F16, tract.DatumType.F32))\n    assert str(typed.input_fact(0)) == \"1,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"1,1000,f32\"\n\ndef test_f16_to_f32_raw_string():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"1,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    typed.transform(\"f32_to_f16\")\n    typed.transform(\"f16_to_f32\")\n    assert str(typed.input_fact(0)) == \"1,3,224,224,f32\"\n    assert str(typed.output_fact(0)) == \"1,1000,f32\"\n\ndef test_typed_model_to_nnef_and_back():\n    model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n    model.set_input_fact(0, \"B,3,224,224,f32\")\n    model.analyse()\n    typed = model.into_model()\n    with tempfile.TemporaryDirectory() as tmpdirname:\n        tmpdirname = Path(tmpdirname)\n        nnef = tract.nnef().with_tract_core()\n\n        path = tmpdirname / \"nnef-dir\"\n        nnef.write_model_to_dir(typed, path)\n        reloaded = nnef.load(path)\n        assert str(reloaded.input_fact(0)) == \"B,3,224,224,f32\"\n        assert str(reloaded.output_fact(0)) == \"B,1000,f32\"\n\n        path = tmpdirname / \"nnef.tar\"\n        nnef.write_model_to_tar(typed, path)\n        reloaded = nnef.load(path)\n        assert str(reloaded.input_fact(0)) == \"B,3,224,224,f32\"\n        assert str(reloaded.output_fact(0)) == \"B,1000,f32\"\n\n        path = tmpdirname / \"nnef.tar.gz\"\n        nnef = nnef.with_extended_identifier_syntax()\n        nnef.write_model_to_tar_gz(typed, path)\n        reloaded = nnef.load(path)\n        assert str(reloaded.input_fact(0)) == \"B,3,224,224,f32\"\n        assert str(reloaded.output_fact(0)) == \"B,1000,f32\"\n\ndef test_cost():\n    model = tract.nnef().load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")\n    assert str(model.input_fact(0)) == \"1,3,224,224,f32\"\n    runnable = model.into_runnable()\n    profile = runnable.profile_json(None)\n    profile = json.loads(profile)\n    assert len(profile[\"nodes\"]) > 10\n    assert profile[\"nodes\"][0][\"node_name\"] != \"\"\n    assert profile[\"nodes\"][0][\"op_name\"] != \"\"\n    assert next(filter(lambda node: \"cost\" in node and \"FMA(F32)\" in node[\"cost\"], profile[\"nodes\"]), None) != None\n\ndef test_profile():\n    model = tract.nnef().load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")\n    assert str(model.input_fact(0)) == \"1,3,224,224,f32\"\n    runnable = model.into_runnable()\n    data = numpy.random.rand(1,3,224,224).astype(dtype=\"float32\")\n    profile = runnable.profile_json([data])\n    profile = json.loads(profile)\n    profiling_info = profile[\"profiling_info\"]\n    assert profiling_info[\"iterations\"] >= 1\n    assert len(profile[\"nodes\"]) > 10\n    assert profile[\"nodes\"][0][\"node_name\"] != \"\"\n    assert profile[\"nodes\"][0][\"op_name\"] != \"\"\n    if \"secs_per_iter\" in profile[\"nodes\"][0]:\n        assert profile[\"nodes\"][0][\"secs_per_iter\"] >= 0\n    assert next(filter(lambda node: \"cost\" in node and \"FMA(F32)\" in node[\"cost\"], profile[\"nodes\"]), None) != None\n\ndef test_transform_registry():\n    nnef = tract.nnef().with_tract_core()\n    model = nnef.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")\n\n    #Convert model to half\n    model.transform(\"f32_to_f16\")\n    assert str(model.input_fact(0)) == \"1,3,224,224,f16\"\n    assert str(model.output_fact(0)) == \"1,1000,f16\"\n    \n    # Convert back to f32 \n    model.transform(\"f16_to_f32\")\n    assert str(model.input_fact(0)) == \"1,3,224,224,f32\"\n\ndef test_fact_and_dims():\n    nnef = tract.nnef().with_tract_core()\n    model = nnef.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")\n    fact = model.parse_fact(\"B,S+P,64,f32\")\n    assert fact.datum_type() == tract.DatumType.F32\n    assert fact.rank() == 3\n    assert str(fact.dim(1)) == \"S+P\"\n    s_plus_p = fact.dim(1)\n    s_plus_twelve = s_plus_p.eval({ \"P\": 12 })\n    assert str(s_plus_twelve) == \"S+12\"\n    fourteen = s_plus_twelve.eval({\"S\": 2})\n    assert fourteen.to_int64() == 14\n    assert int(fourteen) == 14\n\ndef test_fact_and_dims_iterators():\n    nnef = tract.nnef().with_tract_core()\n    model = nnef.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")\n    facts = model.input_facts()\n    assert len(facts) == 1\n    dims = facts[0].dims()\n    assert len(dims) == 4\n    assert int(dims[0]) == 1\n    assert int(dims[1]) == 3\n    assert int(dims[2]) == 224\n    assert int(dims[3]) == 224\n\ndef test_runtime_fact_iterator():\n    nnef = tract.nnef().with_tract_core()\n    runnable = nnef.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\").into_runnable()\n    inputs = runnable.input_facts();\n    assert len(inputs) == 1\n    assert str(inputs[0]) == \"1,3,224,224,f32\"\n    outputs = runnable.output_facts();\n    assert len(outputs) == 1\n    assert str(outputs[0]) == \"1,1000,f32\"\n\ndef test_value_method():\n    floats = tract.Tensor.from_numpy(numpy.array([-1, -0.3, 0., 0.25, 0.75, 1.2], dtype=numpy.float32))\n    assert floats.datum_type().is_float()\n    ints = floats.convert_to(tract.DatumType.I8)\n    assert ints.datum_type().is_signed()\n    assert numpy.array_equal(ints.to_numpy(), [-1, 0, 0, 0, 0, 1])\n    same = tract.Tensor.from_numpy(numpy.array([-1, -0.3, 0., 0.25, 0.75, 1.2], dtype=numpy.float32))\n    assert floats == same\n    halves = ints.convert_to(tract.DatumType.F16)\n    print(halves)\n    print(halves.to_numpy())\n    assert numpy.array_equal(halves.to_numpy(), [-1, 0, 0, 0, 0, 1])\n\n"
  },
  {
    "path": "api/py/tract/__init__.py",
    "content": "\"\"\"\n`tract` Python bindings library\n\n`tract` is a neural network inference engine.\nIts main purpose is to *run* a neural network on production premises after it has been trained.\nIt is a native library written in Rust, and specific attention has been given to its performance\nin sound streaming applications in embedded context (typically ARM Cortex-A CPUs), but it is meant\nto be a generic engine, performing more than decently with various loads on most architectures. You\ncan use it to run image categorization on a PC.\n\n```python\nimport tract\n\n# load MobileNet version 2, an image categorization model\nmodel = (\n    tract.onnx()\n    .load(\"./mobilenetv2-7.onnx\")\n    .into_model()\n    .into_runnable()\n)\n\n# load, as a numpy array, a picture of Grace Hopper, wearing her military uniform\ngrace_hopper_1x3x224x244 = numpy.load(\"grace_hopper_1x3x224x244.npy\")\n\n# run the image through Mobilenet in tract\nresult = model.run([grace_hopper_1x3x224x244])\n\n# output is an array of confidence for each class of the ImageNet challenge\nconfidences = result[0].to_numpy()\n\n# class 652 is \"military uniform\"\nassert numpy.argmax(confidences) == 652\n```\n\n`tract` can also be used as a \"model cooking\" toolbox: once a model has been trained, it is\nsometimes useful to perform some transformations, simplifications and optimizations before shipping\nit. These bindings offer access to some of `tract` cooking facilities.\n\"\"\"\n\nimport numpy\nfrom ctypes import *\nfrom pathlib import Path\nfrom typing import Dict, List, Union\n\nfrom .bindings import check, lib, TractError\nfrom .tensor import Tensor, DatumType\nfrom .fact import Fact, InferenceFact\nfrom .model import Model\nfrom .inference_model import InferenceModel\nfrom .runnable import Runnable\nfrom .runtime import Runtime, runtime_for_name\nfrom .transform import TransformSpec, ConcretizeSymbols, FloatPrecision, Pulse\nfrom .nnef import Nnef\nfrom .onnx import Onnx\n\ndef version() -> str:\n    \"\"\"Return the version string of `tract` native library\"\"\"\n    return str(lib.tract_version(), \"utf-8\")\n\ndef nnef() -> Nnef:\n    \"\"\"Return a newly-created NNEF context for loading and saving models\"\"\"\n    return Nnef()\n\ndef onnx() -> Onnx:\n    \"\"\"Return a newly-created ONNX context for loading models\"\"\"\n    return Onnx()\n\n"
  },
  {
    "path": "api/py/tract/bindings.py",
    "content": "from ctypes import *\nfrom pathlib import Path\n\nclass TractError(Exception):\n    pass\n\nif len(list(Path(__file__).parent.glob(\"*.so\"))) > 0:\n    dylib_path = list(Path(__file__).parent.glob(\"*.so\"))[0]\nelif len(list(Path(__file__).parent.glob(\"*.pyd\"))) > 0:\n    dylib_path = list(Path(__file__).parent.glob(\"*.pyd\"))[0]\nelse:\n    raise TractError(\"Can not find dynamic library\")\n\nlib = cdll.LoadLibrary(str(dylib_path))\n\nlib.tract_version.restype = c_char_p\nlib.tract_get_last_error.restype = c_char_p\nlib.tract_free_cstring.restype = None\n\n\n\ndef check(err):\n    if err != 0:\n        raise TractError(str(lib.tract_get_last_error(), \"utf-8\"))\n\n"
  },
  {
    "path": "api/py/tract/dim.py",
    "content": "from ctypes import *\nfrom tract.bindings import TractError, check, lib\nfrom typing import Dict\n\nclass Dim:\n    \"\"\"\n    A possibly symbolic dimension of a tensor.\n\n    Dimensions can be concrete integers or symbolic expressions (e.g. ``N``,\n    ``N+1``). Use :meth:`to_int64` to extract a concrete value, or :meth:`eval`\n    to substitute symbols.\n    \"\"\"\n\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        if self.ptr:\n            check(lib.tract_dim_destroy(byref(self.ptr)))\n\n    def __str__(self):\n        return self.dump()\n\n    def __int__(self):\n        return self.to_int64()\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid dim\")\n\n    def dump(self) -> str:\n        \"\"\"Return a human-readable representation of the dimension.\"\"\"\n        self._valid()\n        cstring = c_char_p();\n        check(lib.tract_dim_dump(self.ptr, byref(cstring)))\n        result = str(cstring.value, \"utf-8\")\n        lib.tract_free_cstring(cstring)\n        return result\n\n    def eval(self, values: Dict[str, int]) -> \"Dim\":\n        \"\"\"Substitute symbols by concrete values and return the resulting dimension.\"\"\"\n        self._valid()\n        nb = len(values)\n        names_str = []\n        names = (c_char_p * nb)()\n        values_list = (c_int64 * nb)()\n        for ix, (k, v) in enumerate(values.items()):\n            names_str.append(str(k).encode(\"utf-8\"))\n            names[ix] = names_str[ix]\n            values_list[ix] = v\n        ptr = c_void_p()\n        check(lib.tract_dim_eval(self.ptr, c_size_t(nb), names, values_list, byref(ptr)))\n        return Dim(ptr)\n\n    def to_int64(self) -> int:\n        \"\"\"Convert to a concrete integer. Raises if the dimension is symbolic.\"\"\"\n        self._valid()\n        i = c_int64()\n        check(lib.tract_dim_to_int64(self.ptr, byref(i)))\n        return i.value\n"
  },
  {
    "path": "api/py/tract/fact.py",
    "content": "from ctypes import *\nfrom tract.bindings import TractError, check, lib\nfrom .dim import Dim\n\nclass InferenceFact:\n    \"\"\"\n    Tract inference fact, to be used with InferenceModel.\n\n    It can represent partial type and shape information of a Tensor during model analysis.\n    \"\"\"\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        if self.ptr:\n            check(lib.tract_inference_fact_destroy(byref(self.ptr)))\n\n    def __str__(self):\n        return self.dump()\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid inference fact (maybe already consumed ?)\")\n\n    def dump(self) -> str:\n        \"\"\"Return a human-readable representation of the fact.\"\"\"\n        self._valid()\n        cstring = c_char_p();\n        check(lib.tract_inference_fact_dump(self.ptr, byref(cstring)))\n        result = str(cstring.value, \"utf-8\")\n        lib.tract_free_cstring(cstring)\n        return result\n\nclass Fact:\n    \"\"\"\n    Tract-core fact, to be used with Model.\n\n    It always contains the full shape (sometimes using symbolic dimensions) and item type.\n    In some situation it can also contain the constant value of the associated tensor.\n    \"\"\"\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        if self.ptr:\n            check(lib.tract_fact_destroy(byref(self.ptr)))\n\n    def __str__(self):\n        return self.dump()\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid fact (maybe already consumed ?)\")\n\n    def datum_type(self) -> int:\n        \"\"\"Return the element type of the fact as a ``DatumType`` integer.\"\"\"\n        self._valid()\n        dt = c_uint32()\n        check(lib.tract_fact_datum_type(self.ptr, byref(dt)))\n        return dt.value\n\n    def rank(self) -> int:\n        \"\"\"Return the number of dimensions (axes) of the tensor.\"\"\"\n        self._valid()\n        rank = c_size_t()\n        check(lib.tract_fact_rank(self.ptr, byref(rank)))\n        return rank.value\n\n    def dim(self, axis: int) -> Dim:\n        \"\"\"Return the :class:`~tract.dim.Dim` for the given axis.\"\"\"\n        self._valid()\n        ptr = c_void_p();\n        check(lib.tract_fact_dim(self.ptr,  c_size_t(axis), byref(ptr)))\n        return Dim(ptr)\n\n    def dump(self) -> str:\n        \"\"\"Return a human-readable representation of the fact.\"\"\"\n        self._valid()\n        cstring = c_char_p();\n        check(lib.tract_fact_dump(self.ptr, byref(cstring)))\n        result = str(cstring.value, \"utf-8\")\n        lib.tract_free_cstring(cstring)\n        return result\n\n    def dims(self):\n        \"\"\"Return a list of :class:`~tract.dim.Dim` for all axes.\"\"\"\n        return [ self.dim(axis) for axis in range(self.rank()) ]\n"
  },
  {
    "path": "api/py/tract/inference_model.py",
    "content": "from ctypes import *\nfrom typing import Dict, List, Union\nfrom .bindings import TractError, check, lib\nfrom .fact import InferenceFact\nfrom .model import Model\n\nclass InferenceModel:\n    \"\"\"\n    ONNX models are loaded as ``InferenceModel`` instances instead of ``Model`` instances:\n    many ONNX models come with partial shape and element type information, while tract's\n    ``Model`` assumes full shape and element type knowledge. In this case, it is generally\n    sufficient to inform tract about the input shape and type, then let tract *infer* the\n    rest of the missing shape information before converting the ``InferenceModel`` to a\n    regular ``Model``.\n\n    .. code-block:: python\n\n        # load the model as an InferenceModel\n        model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n\n        # set the shape and type of its first and only input\n        model.set_input_fact(0, \"1,3,224,224,f32\")\n\n        # get ready to run the model\n        model = model.into_model().into_runnable()\n    \"\"\"\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        if self.ptr:\n            check(lib.tract_inference_model_destroy(byref(self.ptr)))\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid inference model (maybe already consumed ?)\")\n\n    def into_model(self) -> Model:\n        \"\"\"\n        Convert an InferenceModel to a regular typed ``Model``.\n\n        This will leave the opportunity to run more transformation on the intermediary form of the\n        model, before optimising it all the way.\n        \"\"\"\n        self._valid()\n        model = c_void_p()\n        check(lib.tract_inference_model_into_model(byref(self.ptr), byref(model)))\n        return Model(model)\n\n    def input_count(self) -> int:\n        \"\"\"Return the number of inputs of the model\"\"\"\n        self._valid()\n        i = c_size_t()\n        check(lib.tract_inference_model_input_count(self.ptr, byref(i)))\n        return i.value\n\n    def output_count(self) -> int:\n        \"\"\"Return the number of outputs of the model\"\"\"\n        self._valid()\n        i = c_size_t()\n        check(lib.tract_inference_model_output_count(self.ptr, byref(i)))\n        return i.value\n\n    def input_name(self, input_id: int) -> str:\n        \"\"\"Return the name of the ``input_id``-th input.\"\"\"\n        self._valid()\n        cstring = c_char_p()\n        check(lib.tract_inference_model_input_name(self.ptr, input_id, byref(cstring)))\n        result = str(cstring.value, \"utf-8\")\n        lib.tract_free_cstring(cstring)\n        return result\n\n    def input_fact(self, input_id: int) -> InferenceFact:\n        \"\"\"Extract the InferenceFact of the ``input_id``-th input.\"\"\"\n        self._valid()\n        fact = c_void_p()\n        check(lib.tract_inference_model_input_fact(self.ptr, input_id, byref(fact)))\n        return InferenceFact(fact)\n\n    def set_input_fact(self, input_id: int, fact: Union[InferenceFact, str, None]) -> None:\n        \"\"\"Change the InferenceFact of the ``input_id``-th input.\"\"\"\n        self._valid()\n        if isinstance(fact, str):\n            fact = self.fact(fact)\n        if fact == None:\n            check(lib.tract_inference_model_set_input_fact(self.ptr, input_id, None))\n        else:\n            check(lib.tract_inference_model_set_input_fact(self.ptr, input_id, fact.ptr))\n\n    def output_name(self, output_id: int) -> str:\n        \"\"\"Return the name of the ``output_id``-th output.\"\"\"\n        self._valid()\n        cstring = c_char_p()\n        check(lib.tract_inference_model_output_name(self.ptr, output_id, byref(cstring)))\n        result = str(cstring.value, \"utf-8\")\n        lib.tract_free_cstring(cstring)\n        return result\n\n    def output_fact(self, output_id: int) -> InferenceFact:\n        \"\"\"Extract the InferenceFact of the ``output_id``-th output.\"\"\"\n        self._valid()\n        fact = c_void_p()\n        check(lib.tract_inference_model_output_fact(self.ptr, output_id, byref(fact)))\n        return InferenceFact(fact)\n\n    def set_output_fact(self, output_id: int, fact: Union[InferenceFact, str, None]) -> None:\n        \"\"\"Change the InferenceFact of the ``output_id``-th output.\"\"\"\n        self._valid()\n        if isinstance(fact, str):\n            fact = self.fact(fact)\n        if fact == None:\n            check(lib.tract_inference_model_set_output_fact(self.ptr, output_id, None))\n        else:\n            check(lib.tract_inference_model_set_output_fact(self.ptr, output_id, fact.ptr))\n\n    def fact(self, spec:str) -> InferenceFact:\n        \"\"\"\n        Parse a fact specification as an ``InferenceFact``.\n\n        Typical ``InferenceFact`` specification is in the form \"1,224,224,3,f32\". Comma-separated\n        list of dimension, one for each axis, plus an mnemonic for the element type. f32 is \n        single precision \"float\", i16 is a 16-bit signed integer, and u8 a 8-bit unsigned integer.\n        \"\"\"\n        self._valid()\n        spec = str(spec).encode(\"utf-8\")\n        fact = c_void_p();\n        check(lib.tract_inference_fact_parse(self.ptr, spec, byref(fact)))\n        return InferenceFact(fact)\n\n    def analyse(self) -> None:\n        \"\"\"\n        Perform shape and element type inference on the model.\n        \"\"\"\n        self._valid()\n        check(lib.tract_inference_model_analyse(self.ptr, False))\n\n    def into_analysed(self) -> \"InferenceModel\":\n        \"\"\"\n        Perform shape and element type inference on the model.\n        \"\"\"\n        self.analyse()\n        return self\n"
  },
  {
    "path": "api/py/tract/model.py",
    "content": "import numpy\nfrom ctypes import *\nfrom typing import Dict, List, Union\nfrom .bindings import TractError, check, lib\nfrom .fact import Fact\nfrom .tensor import Tensor\nfrom .runnable import Runnable\nfrom .transform import TransformSpec\n\nclass Model:\n    \"\"\"\n    Main model object, central focus point of the model transformation pipeline.\n\n    The Model is the central point of tract model loading and \"model cooking\". ONNX and NNEF\n    serialized models are converted to Model (more or less directly) before we can do anything\n    of value with them. Model can be dumped to NNEF (or tract-opl which is NNEF plus tract\n    proprietary extensions).\n\n    A Model can be ``optimize()``-d, substituting the \"high level\" operators in tract-core\n    operator set by the best implementation available for the current system. From there it\n    can be transformed into a Runnable object that we will use to run.\n\n    **Model cooking**\n\n    Some model transformations can be performed on the Model class:\n\n    - declutter (getting rid of training artefacts)\n    - \"pulsification\" (transforming a batch-oriented model into a streaming model)\n    - symbol substitution (make N or Batch a fixed number, unlocking potential optimisation later on)\n    - static cost evaluation and dynamic profiling\n    - ...\n\n    In some situations, these operations are done \"on-the-fly\" when an ONNX or NNEF model is\n    loaded, at start-up time. In other situations, when start-up time becomes an issue, it may\n    be beneficial to \"pre-cook\" the model: apply the transformations once, serialize the model\n    as NNEF (with tract-opl extension if needed). At start-up this model can be significantly\n    less expensive to \"cook\" for inference.\n\n    **Model and TypedModel**\n\n    This class is actually a wrapper around the \"TypedModel\" in Rust codebase. The \"Typed\"\n    bit means that all shapes and element types in all input, output and temporary values must\n    be known. There is support in tract for symbols in dimensions, with some limited computation\n    capabilities on symbolic expressions. For instance, it is relatively frequent to work with\n    a Model where all tensor shapes start with ``N`` or ``Batch``.\n    \"\"\"\n\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        if self.ptr:\n            check(lib.tract_model_destroy(byref(self.ptr)))\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid model (maybe already consumed ?)\")\n\n    def input_count(self) -> int:\n        \"\"\"Return the number of inputs of the model\"\"\"\n        self._valid()\n        i = c_size_t()\n        check(lib.tract_model_input_count(self.ptr, byref(i)))\n        return i.value\n\n    def output_count(self) -> int:\n        \"\"\"Return the number of outputs of the model\"\"\"\n        self._valid()\n        i = c_size_t()\n        check(lib.tract_model_output_count(self.ptr, byref(i)))\n        return i.value\n\n    def input_name(self, input_id: int) -> str:\n        \"\"\"Return the name of the input_id-th input\"\"\"\n        self._valid()\n        cstring = c_char_p()\n        check(lib.tract_model_input_name(self.ptr, input_id, byref(cstring)))\n        result = str(cstring.value, \"utf-8\")\n        lib.tract_free_cstring(cstring)\n        return result\n\n    def input_fact(self, input_id: int) -> Fact:\n        \"\"\"Return the fact of the input_id-th input\"\"\"\n        self._valid()\n        fact = c_void_p()\n        check(lib.tract_model_input_fact(self.ptr, input_id, byref(fact)))\n        return Fact(fact)\n\n    def output_name(self, output_id: int) -> str:\n        \"\"\"Return the name of the output_id-th output\"\"\"\n        self._valid()\n        cstring = c_char_p()\n        check(lib.tract_model_output_name(self.ptr, output_id, byref(cstring)))\n        result = str(cstring.value, \"utf-8\")\n        lib.tract_free_cstring(cstring)\n        return result\n\n    def output_fact(self, output_id: int) -> Fact:\n        \"\"\"Return the fact of the output_id-th output\"\"\"\n        self._valid()\n        fact = c_void_p()\n        check(lib.tract_model_output_fact(self.ptr, output_id, byref(fact)))\n        return Fact(fact)\n\n    def transform(self, transform: Union[str, \"TransformSpec\"]) -> None:\n        \"\"\"Apply a transform to the model.\n\n        ``transform`` can be:\n\n        - a plain string name (e.g. ``\"f32_to_f16\"``)\n        - a JSON string with a ``\"name\"`` key and parameters\n        - a :class:`TransformSpec` subclass such as :class:`ConcretizeSymbols`\n          or :class:`Pulse`\n        \"\"\"\n        self._valid()\n        if isinstance(transform, TransformSpec):\n            transform = transform.to_json()\n        check(lib.tract_model_transform(self.ptr, str(transform).encode(\"utf-8\")))\n\n    def into_runnable(self) -> Runnable:\n        \"\"\"Transform the model into a ready to be used Runnable model\"\"\"\n        self._valid()\n        runnable = c_void_p()\n        check(lib.tract_model_into_runnable(byref(self.ptr), byref(runnable)))\n        self.ptr = None\n        return Runnable(runnable)\n\n    def parse_fact(self, spec: str) -> Fact:\n        self._valid()\n        fact = c_void_p()\n        check(lib.tract_model_parse_fact(self.ptr, str(spec).encode(\"utf-8\"), byref(fact)))\n        return Fact(fact)\n\n    def property_keys(self) -> List[str]:\n        \"\"\"Query the list of properties names of the model.\"\"\"\n        self._valid()\n        count = c_size_t()\n        check(lib.tract_model_property_count(self.ptr, byref(count)))\n        count = count.value\n        cstrings = (POINTER(c_char) * count)()\n        check(lib.tract_model_property_names(self.ptr, cstrings))\n        names = []\n        for i in range(0, count):\n            names.append(str(cast(cstrings[i], c_char_p).value, \"utf-8\"))\n            lib.tract_free_cstring(cstrings[i])\n        return names\n\n    def property(self, name: str) -> Tensor:\n        \"\"\"Query a property by name\"\"\"\n        self._valid()\n        value = c_void_p()\n        check(lib.tract_model_property(self.ptr, str(name).encode(\"utf-8\"), byref(value)))\n        return Tensor(value)\n\n    def input_facts(self) -> List[Fact]:\n        return [ self.input_fact(ix) for ix in range(self.input_count()) ]\n\n    def output_facts(self):\n        return [ self.output_fact(ix) for ix in range(self.output_count()) ]\n"
  },
  {
    "path": "api/py/tract/nnef.py",
    "content": "from ctypes import *\nfrom pathlib import Path\nfrom typing import Dict, List, Union\nfrom .bindings import TractError, check, lib\nfrom .model import Model\n\nclass Nnef:\n    \"\"\"\n    Represent a NNEF context in tract.\n\n    NNEF is a neural model interchange format, similar to ONNX but focusing on the needs\n    of an inference engine instead of a training framework.\n\n    ``tract`` can natively load NNEF models. It can also save models in tract internal format\n    as ``tract-opl`` models. ``tract-opl`` is a set of proprietary extensions to NNEF allowing\n    serialization of most of the models tract can handle. These extensions can be activated by\n    the ``with_*()`` methods.\n    \"\"\"\n\n    def __init__(self):\n        ptr = c_void_p()\n        check(lib.tract_nnef_create(byref(ptr)))\n        self.ptr = ptr\n\n    def __del__(self):\n        check(lib.tract_nnef_destroy(byref(self.ptr)))\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid inference model (maybe already consumed ?)\")\n\n    def load(self, path: Union[str, Path]) -> Model:\n        \"\"\"\n        Load an NNEF model from the file or folder at ``path``.\n\n        .. code-block:: python\n\n            model = (\n                tract.nnef()\n                .load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")\n                .into_runnable()\n            )\n        \"\"\"\n        self._valid()\n        model = c_void_p()\n        path = str(path).encode(\"utf-8\")\n        check(lib.tract_nnef_load(self.ptr, path, byref(model)))\n        return Model(model)\n\n    def with_tract_core(self) -> \"Nnef\":\n        \"\"\"\n        Enable tract-opl extensions to NNEF to covers tract-core operator set\n        \"\"\"\n        self._valid()\n        check(lib.tract_nnef_enable_tract_core(self.ptr))\n        return self\n\n    def with_tract_extra(self) -> \"Nnef\":\n        \"\"\"\n        Enable tract-extra extensions to NNEF.\n        \"\"\"\n        self._valid()\n        check(lib.tract_nnef_enable_tract_extra(self.ptr))\n        return self\n\n    def with_tract_transformers(self) -> \"Nnef\":\n        \"\"\"\n        Enable tract-transformers extensions to NNEF.\n        \"\"\"\n        self._valid()\n        check(lib.tract_nnef_enable_tract_transformers(self.ptr))\n        return self\n\n    def with_onnx(self) -> \"Nnef\":\n        \"\"\"\n        Enable tract-opl extensions to NNEF to covers (more or) ONNX operator set\n        \"\"\"\n        self._valid()\n        check(lib.tract_nnef_enable_onnx(self.ptr))\n        return self\n\n    def with_pulse(self) -> \"Nnef\":\n        \"\"\"\n        Enable tract-opl extensions to NNEF for tract pulse operators (for audio streaming)\n        \"\"\"\n        self._valid()\n        check(lib.tract_nnef_enable_pulse(self.ptr))\n        return self\n\n    def with_extended_identifier_syntax(self) -> \"Nnef\":\n        \"\"\"\n        Enable tract-opl extensions to NNEF for extended identifiers (will support PyTorch 2 path-like ids)\n        \"\"\"\n        self._valid()\n        check(lib.tract_nnef_enable_extended_identifier_syntax(self.ptr, True))\n        return self\n\n    def write_model_to_dir(self, model: Model, path: Union[str, Path]) -> None:\n        \"\"\"\n        Save ``model`` as a NNEF directory model in ``path``.\n\n        tract tries to stick to strict NNEF even if extensions have been enabled.\n        \"\"\"\n        self._valid()\n        model._valid()\n        if not isinstance(model, Model):\n            raise TractError(\"Expected a Model, called with \" + model);\n        path = str(path).encode(\"utf-8\")\n        check(lib.tract_nnef_write_model_to_dir(self.ptr, path, model.ptr))\n\n    def write_model_to_tar(self, model: Model, path: Union[str, Path]) -> None:\n        \"\"\"\n        Save ``model`` as a NNEF tar archive in ``path``.\n\n        tract tries to stick to strict NNEF even if extensions have been enabled.\n        \"\"\"\n        self._valid()\n        model._valid()\n        if not isinstance(model, Model):\n            raise TractError(\"Expected a Model, called with \" + model);\n        path = str(path).encode(\"utf-8\")\n        check(lib.tract_nnef_write_model_to_tar(self.ptr, path, model.ptr))\n\n    def write_model_to_tar_gz(self, model: Model, path: Union[str, Path]) -> None:\n        \"\"\"\n        Save ``model`` as a NNEF tar compressed archive in ``path``.\n\n        tract tries to stick to strict NNEF even if extensions have been enabled.\n        \"\"\"\n        self._valid()\n        model._valid()\n        if not isinstance(model, Model):\n            raise TractError(\"Expected a Model, called with \" + model);\n        path = str(path).encode(\"utf-8\")\n        check(lib.tract_nnef_write_model_to_tar_gz(self.ptr, path, model.ptr))\n\n"
  },
  {
    "path": "api/py/tract/onnx.py",
    "content": "from ctypes import *\nfrom pathlib import Path\nfrom typing import Dict, List, Union\nfrom .bindings import check, lib\nfrom .inference_model import InferenceModel\n\nclass Onnx:\n    \"\"\"\n    Represent the ONNX context in tract.\n\n    It essentially allows to load ONNX models. Note that an ONNX model is loaded as an\n    ``InferenceModel`` and not as a ``Model``: many ONNX models come with partial shape and\n    element type information, while tract's ``Model`` assume full shape and element type\n    knownledge. In this case, it is generally sufficient to inform tract about the input\n    shape and type, then let tract *infer* the rest of the missing shape information\n    before converting the ``InferenceModel`` to a regular ``Model``.\n\n    .. code-block:: python\n\n        # load the model as an InferenceModel\n        model = tract.onnx().load(\"./mobilenetv2-7.onnx\")\n\n        # set the shape and type of its first and only input\n        model.set_input_fact(0, \"1,3,224,224,f32\")\n\n        # get ready to run the model\n        model = model.into_model().into_runnable()\n    \"\"\"\n\n    def __init__(self):\n        ptr = c_void_p()\n        check(lib.tract_onnx_create(byref(ptr)))\n        self.ptr = ptr\n\n    def __del__(self):\n        check(lib.tract_onnx_destroy(byref(self.ptr)))\n\n    def load(self, path: Union[str, Path]) -> InferenceModel:\n        \"\"\"\n        Load an ONNX file as an InferenceModel\n        \"\"\"\n        model = c_void_p()\n        path = str(path).encode(\"utf-8\")\n        check(lib.tract_onnx_load(self.ptr, path, byref(model)))\n        return InferenceModel(model)\n"
  },
  {
    "path": "api/py/tract/runnable.py",
    "content": "from ctypes import *\nfrom typing import Dict, List, Union # after ctypes so that Union is overriden\nimport numpy\nfrom .fact import Fact\nfrom .tensor import Tensor\nfrom .state import State\nfrom .bindings import TractError, check, lib\n\nclass Runnable:\n    \"\"\"\n    A model that has been fully optimized and is ready to perform computation.\n\n    This is the final stage of the model pipeline. A ``Runnable`` is obtained\n    either by calling ``Model.into_runnable()`` (CPU default) or by passing a\n    ``Model`` to ``Runtime.prepare()`` for GPU-accelerated execution. Once\n    obtained, call :meth:`run` with numpy arrays or ``Tensor`` instances to\n    perform inference.\n    \"\"\"\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        check(lib.tract_runnable_release(byref(self.ptr)))\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid runnable (maybe already consumed ?)\")\n\n    def input_count(self) -> int:\n        \"\"\"Return the number of inputs of the underlying model\"\"\"\n        self._valid()\n        i = c_size_t()\n        check(lib.tract_runnable_input_count(self.ptr, byref(i)))\n        return i.value\n\n    def output_count(self) -> int:\n        \"\"\"Return the number of outputs of the underlying model\"\"\"\n        self._valid()\n        i = c_size_t()\n        check(lib.tract_runnable_output_count(self.ptr, byref(i)))\n        return i.value\n\n    def input_fact(self, input_id: int) -> Fact:\n        \"\"\"Return the fact of the input_id-th input\"\"\"\n        self._valid()\n        fact = c_void_p()\n        check(lib.tract_runnable_input_fact(self.ptr, input_id, byref(fact)))\n        return Fact(fact)\n\n    def output_fact(self, output_id: int) -> Fact:\n        \"\"\"Return the fact of the output_id-th output\"\"\"\n        self._valid()\n        fact = c_void_p()\n        check(lib.tract_runnable_output_fact(self.ptr, output_id, byref(fact)))\n        return Fact(fact)\n\n    def property_keys(self) -> List[str]:\n        \"\"\"Query the list of properties names of the runnable model.\"\"\"\n        self._valid()\n        count = c_size_t()\n        check(lib.tract_runnable_property_count(self.ptr, byref(count)))\n        count = count.value\n        cstrings = (POINTER(c_char) * count)()\n        check(lib.tract_runnable_property_names(self.ptr, cstrings))\n        names = []\n        for i in range(0, count):\n            names.append(str(cast(cstrings[i], c_char_p).value, \"utf-8\"))\n            lib.tract_free_cstring(cstrings[i])\n        return names\n\n    def property(self, name: str) -> Tensor:\n        \"\"\"Query a property by name\"\"\"\n        self._valid()\n        value = c_void_p()\n        check(lib.tract_runnable_property(self.ptr, str(name).encode(\"utf-8\"), byref(value)))\n        return Tensor(value)\n\n    def run(self, inputs: List[Union[Tensor, numpy.ndarray]]) -> List[Tensor]:\n        \"\"\"\n        Runs the model over the provided input list, and returns the model outputs.\n        \"\"\"\n        return self.spawn_state().run(inputs)\n\n    def spawn_state(self) -> State:\n        \"\"\"Create a new execution state for stateful (e.g. streaming) models.\"\"\"\n        self._valid()\n        state = c_void_p()\n        check(lib.tract_runnable_spawn_state(self.ptr, byref(state)))\n        return State(state)\n\n    def profile_json(self, inputs: Union[None, List[Union[Tensor, numpy.ndarray]]]) -> str:\n        \"\"\"Profile the model. Also compute the static costs of operators.\n\n        Returns is a json buffer.\n        \"\"\"\n        self._valid()\n        cstring = c_char_p()\n        input_values = []\n        input_ptrs = None\n        if inputs != None:\n            for v in inputs:\n                if isinstance(v, Tensor):\n                    input_values.append(v)\n                elif isinstance(v, numpy.ndarray):\n                    input_values.append(Tensor.from_numpy(v))\n                else:\n                    raise TractError(f\"Inputs must be of type tract.Tensor or numpy.Array, got {v}\")\n            input_ptrs = (c_void_p * len(inputs))()\n            for ix, v in enumerate(input_values):\n                input_ptrs[ix] = v.ptr\n\n        check(lib.tract_runnable_profile_json(self.ptr, input_ptrs, byref(cstring)))\n        result = str(cstring.value, \"utf-8\")\n        lib.tract_free_cstring(cstring)\n        return result\n\n    def input_facts(self) -> List[Fact]:\n        \"\"\"Return the list of input facts.\"\"\"\n        return [ self.input_fact(ix) for ix in range(self.input_count()) ]\n\n    def output_facts(self) -> List[Fact]:\n        \"\"\"Return the list of output facts.\"\"\"\n        return [ self.output_fact(ix) for ix in range(self.output_count()) ]\n"
  },
  {
    "path": "api/py/tract/runtime.py",
    "content": "from ctypes import *\nfrom typing import Dict, List, Union # after ctypes so that Union is overriden\nimport numpy\nfrom .runnable import Runnable\nfrom .model import Model\nfrom .bindings import TractError, check, lib\n\ndef runtime_for_name(name: str):\n    \"\"\"Look up a runtime by name and return a ``Runtime`` instance.\n\n    Available runtimes depend on the build and the platform: ``\"default\"`` for\n    CPU, ``\"metal\"`` on Apple Silicon Macs, ``\"cuda\"`` on systems with NVIDIA\n    GPUs.\n    \"\"\"\n    runtime = c_void_p()\n    check(lib.tract_runtime_for_name(str(name).encode(\"utf-8\"), byref(runtime)))\n    return Runtime(runtime)\n\nclass Runtime:\n    \"\"\"\n    A hardware/software backend that can execute a ``Model``.\n\n    Calling ``Model.into_runnable()`` implicitly uses the default CPU runtime.\n    To run on a GPU, obtain a ``Runtime`` via :func:`runtime_for_name` —\n    ``\"metal\"`` on Apple Silicon Macs, ``\"cuda\"`` on NVIDIA systems — then\n    call :meth:`prepare` to produce a ``Runnable`` optimized for that backend.\n    \"\"\"\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        check(lib.tract_runtime_release(byref(self.ptr)))\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid runtime (maybe already consumed ?)\")\n   \n    def name(self) -> str:\n        \"\"\"Return the name of this Runtime.\"\"\"\n        self._valid()\n        ptr = c_char_p()\n        check(lib.tract_runtime_name(self.ptr, byref(ptr)))\n        result = ptr.value.decode(\"utf-8\")\n        lib.tract_free_cstring(ptr)\n        return result\n\n    def prepare(self, model:Model) -> Runnable:\n        \"\"\"Prepare a model for execution on the Runtime.\n\n        NB: The passed model is invalidated by this call.\n        \"\"\"\n        self._valid()\n        runnable = c_void_p()\n        check(lib.tract_runtime_prepare(self.ptr, byref(model.ptr), byref(runnable)))\n        model.ptr = None\n        return Runnable(runnable)\n\n    \n"
  },
  {
    "path": "api/py/tract/state.py",
    "content": "import numpy\nfrom ctypes import *\nfrom typing import List, Union\nfrom .bindings import TractError, check, lib\nfrom .fact import Fact\nfrom .tensor import Tensor\n\nclass State:\n    \"\"\"\n    Mutable execution state for stateful (typically streaming) models.\n\n    Stateful models maintain internal buffers between calls to :meth:`run`\n    (e.g. recurrent networks, pulsed convolutions). A ``State`` is created by\n    ``Runnable.spawn_state()`` and can be called repeatedly with successive\n    input chunks. Use :meth:`freeze` to snapshot the state for later reuse.\n    \"\"\"\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        check(lib.tract_state_destroy(byref(self.ptr)))\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid state (maybe already destroyed ?)\")\n\n    def input_count(self) -> int:\n        \"\"\"Return the number of inputs of the underlying model\"\"\"\n        self._valid()\n        i = c_size_t()\n        check(lib.tract_state_input_count(self.ptr, byref(i)))\n        return i.value\n\n    def output_count(self) -> int:\n        \"\"\"Return the number of outputs of the underlying model\"\"\"\n        self._valid()\n        i = c_size_t()\n        check(lib.tract_state_output_count(self.ptr, byref(i)))\n        return i.value\n\n    def run(self, inputs: List[Union[Tensor, numpy.ndarray]]) -> List[Tensor]:\n        \"\"\"\n        Runs the model over the provided input list, and returns the model outputs.\n        \"\"\"\n        self._valid()\n        input_values = []\n        for v in inputs:\n            if isinstance(v, Tensor):\n                input_values.append(v)\n            elif isinstance(v, numpy.ndarray):\n                input_values.append(Tensor.from_numpy(v))\n            else:\n                raise TractError(f\"Inputs must be of type tract.Tensor or numpy.Array, got {v}\")\n        input_ptrs = (c_void_p * self.input_count())()\n        output_ptrs = (c_void_p * self.output_count())()\n        for ix, v in enumerate(input_values):\n            input_ptrs[ix] = v.ptr\n        check(lib.tract_state_run(self.ptr, input_ptrs, output_ptrs))\n        result = []\n        for v in output_ptrs:\n            result.append(Tensor(c_void_p(v)))\n        return result\n\n    def freeze(self) -> \"FrozenState\":\n        \"\"\"Freeze the state into an immutable ``FrozenState`` snapshot.\"\"\"\n        self._valid()\n        frozen = c_void_p()\n        check(lib.tract_state_freeze(self.ptr, byref(frozen)))\n        return FrozenState(frozen)\n\nclass FrozenState:\n    \"\"\"\n    A frozen (immutable) snapshot of a stateful model's state.\n\n    Can be unfrozen back into a mutable ``State`` with :meth:`unfreeze`.\n    \"\"\"\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        check(lib.tract_frozen_state_destroy(byref(self.ptr)))\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid frozen state (maybe already destroyed ?)\")\n\n    def unfreeze(self) -> State:\n        \"\"\"Restore a mutable ``State`` from this frozen snapshot.\"\"\"\n        self._valid()\n        state = c_void_p()\n        check(lib.tract_frozen_state_unfreeze(self.ptr, byref(state)))\n        return State(state)\n"
  },
  {
    "path": "api/py/tract/tensor.py",
    "content": "import numpy\nimport math\nfrom ctypes import *\nfrom typing import Dict, List, Union\nfrom tract.bindings import TractError, check, lib\n\nfrom enum import IntEnum\n\nclass DatumType(IntEnum):\n    BOOL = 0x01\n    U8 = 0x11\n    U16 = 0x12\n    U32 = 0x14\n    U64 = 0x18\n    I8 = 0x21\n    I16 = 0x22\n    I32 = 0x24\n    I64 = 0x28\n    F16 = 0x32\n    F32 = 0x34\n    F64 = 0x38\n    COMPLEX_I16 = 0x42\n    COMPLEX_I32 = 0x44\n    COMPLEX_I64 = 0x48\n    COMPLEX_F16 = 0x52\n    COMPLEX_F32 = 0x54\n    COMPLEX_F64 = 0x58\n\n    def __str__(self) -> str:\n        return self.name\n\n    def is_bool(self) -> bool:\n        return self == self.BOOL\n\n    def is_number(self) -> bool:\n        return self != self.BOOL\n\n    def is_float(self) -> bool:\n        return self == self.F16 or self == self.F32 or self == self.F64\n\n    def is_signed(self) -> bool:\n        return self == self.I8 or self == self.I16 or self == self.I32 or self == self.I64\n\n    def is_unsigned(self) -> bool:\n        return self == self.U8 or self == self.U16 or self == self.U32 or self == self.U64\n\n    def ctype(self):\n        if self == self.BOOL:\n            return c_bool\n        if self == self.U8:\n            return c_uint8\n        if self == self.U16 or self == self.F16:\n            return c_uint16\n        if self == self.U32:\n            return c_uint32\n        if self == self.U64:\n            return c_uint64\n        if self == self.I8:\n            return c_int8\n        if self == self.I16:\n            return c_int16\n        if self == self.I32:\n            return c_int32\n        if self == self.I64:\n            return c_int64\n        if self == self.F32:\n            return c_float\n        if self == self.F64:\n            return c_double\n        raise \"invalid datum type\"\n\ndef dt_numpy_to_tract(dt):\n    if dt.kind == 'b':\n        return DatumType.BOOL\n    if dt.kind == 'u':\n        return 0x10 + dt.itemsize\n    if dt.kind == 'i':\n        return 0x20 + dt.itemsize\n    if dt.kind == 'f':\n        return 0x30 + dt.itemsize\n    if dt.kind == 'c':\n        return 0x50 + dt.itemsize / 2\n    raise TractError(\"Unsupported Numpy dtype: \" + dt)\n\n\nclass Tensor:\n    \"\"\"\n    A multidimensional array of typed data, the fundamental data carrier in tract.\n\n    Tensors flow in and out of models: they are the inputs you feed to\n    ``Runnable.run()`` and the outputs you get back. On the Python side,\n    convert to and from numpy arrays with :meth:`to_numpy` and\n    :func:`from_numpy`.\n    \"\"\"\n    def __init__(self, ptr):\n        self.ptr = ptr\n\n    def __del__(self):\n        if self.ptr:\n            check(lib.tract_tensor_destroy(byref(self.ptr)))\n\n    def __str__(self):\n        return self.dump()\n\n    def _valid(self):\n        if self.ptr == None:\n            raise TractError(\"invalid tensor (maybe already consumed ?)\")\n\n    def __eq__(self, other):\n        (self_dt, self_shape, self_ptr) = self._parts()\n        (other_dt, other_shape, other_ptr) = other._parts()\n        self_len = math.prod(self_shape) * sizeof(self_dt.ctype())\n        other_len = math.prod(self_shape) * sizeof(self_dt.ctype())\n        self_buf = string_at(self_ptr, self_len)\n        other_buf = string_at(other_ptr, other_len)\n        return self_dt == other_dt and self_shape == other_shape and self_buf == other_buf\n\n\n    def _parts(self) -> (DatumType, [int], c_void_p):\n        self._valid()\n        rank = c_size_t();\n        shape = POINTER(c_size_t)()\n        data = c_void_p();\n        dt = c_uint32(0)\n        check(lib.tract_tensor_as_bytes(self.ptr, byref(dt), byref(rank), byref(shape), byref(data)))\n        rank = rank.value\n        shape = [ int(shape[ix]) for ix in range(0, rank) ]\n        return (DatumType(dt.value), shape, data)\n\n    def from_numpy(array: numpy.ndarray) -> \"Tensor\":\n        array = numpy.ascontiguousarray(array)\n\n        data = array.__array_interface__['data'][0]\n        data = c_void_p(data)\n        ptr = c_void_p()\n        shape_t = c_size_t * array.ndim\n        shape = shape_t()\n        for ix in range(0, array.ndim):\n            shape[ix] = array.shape[ix]\n        dt = dt_numpy_to_tract(array.dtype)\n        check(lib.tract_tensor_from_bytes(dt, c_size_t(array.ndim), shape, data, byref(ptr)))\n        return Tensor(ptr)\n\n    def to_numpy(self) -> numpy.array:\n        \"\"\"Builds a numpy array equivalent to the data in this tensor.\"\"\"\n        (dt, shape, data) = self._parts()\n        data = cast(data, POINTER(dt.ctype()))\n        array = numpy.ctypeslib.as_array(data, shape).copy()\n        if dt == DatumType.F16:\n            array = array.view(numpy.float16)\n        return array\n\n    def into_numpy(self) -> numpy.array:\n        \"\"\"Same as to_numpy(), but drop the tensor content once the numpy array is built.\"\"\"\n        result = self.to_numpy()\n        check(lib.tract_tensor_destroy(byref(self.ptr)))\n        return result\n\n    def datum_type(self) -> DatumType:\n        self._valid()\n        dt = c_uint32(0)\n        check(lib.tract_tensor_as_bytes(self.ptr, byref(dt), None, None, None))\n        return DatumType(dt.value)\n\n    def convert_to(self, to: DatumType) -> \"Tensor\":\n        self._valid()\n        ptr = c_void_p()\n        check(lib.tract_tensor_convert_to(self.ptr, to, byref(ptr)))\n        return Tensor(ptr)\n\n    def dump(self):\n        self._valid()\n        cstring = c_char_p();\n        check(lib.tract_tensor_dump(self.ptr, byref(cstring)))\n        result = str(cstring.value, \"utf-8\")\n        lib.tract_free_cstring(cstring)\n        return result\n"
  },
  {
    "path": "api/py/tract/transform.py",
    "content": "import json\nfrom abc import ABC, abstractmethod\nfrom typing import Dict, Optional, Union\n\nfrom .tensor import DatumType\n\n\nclass TransformSpec(ABC):\n    \"\"\"Base class for typed transform specifications.\n\n    Subclasses represent specific transforms with typed parameters.\n    Can be passed directly to :meth:`Model.transform`.\n    \"\"\"\n\n    @abstractmethod\n    def to_json(self) -> str:\n        \"\"\"Serialize this transform spec to the JSON string the FFI layer expects.\"\"\"\n        ...\n\n\nclass ConcretizeSymbols(TransformSpec):\n    \"\"\"Replace symbolic dimensions with concrete integer values.\n\n    Example::\n\n        model.transform(ConcretizeSymbols({\"B\": 1}))\n        # or with builder pattern:\n        model.transform(ConcretizeSymbols().value(\"B\", 1))\n    \"\"\"\n\n    def __init__(self, values: Optional[Dict[str, int]] = None):\n        self._values: Dict[str, int] = dict(values) if values else {}\n\n    def value(self, symbol: str, val: int) -> \"ConcretizeSymbols\":\n        \"\"\"Set a symbol to a concrete value. Returns self for chaining.\"\"\"\n        self._values[symbol] = val\n        return self\n\n    def to_json(self) -> str:\n        return json.dumps({\"name\": \"concretize_symbols\", \"values\": self._values})\n\n\nclass Pulse(TransformSpec):\n    \"\"\"Convert a model to a pulsed (streaming) model.\n\n    Example::\n\n        model.transform(Pulse(\"5\", symbol=\"B\"))\n        # or with builder pattern:\n        model.transform(Pulse(\"5\").symbol(\"B\"))\n    \"\"\"\n\n    def __init__(self, pulse: Union[str, int], *, symbol: Optional[str] = None):\n        self._pulse = str(pulse)\n        self._symbol = symbol\n\n    def symbol(self, symbol: str) -> \"Pulse\":\n        \"\"\"Set the symbol to pulse over. Returns self for chaining.\"\"\"\n        self._symbol = symbol\n        return self\n\n    def to_json(self) -> str:\n        d = {\"name\": \"pulse\", \"pulse\": self._pulse}\n        if self._symbol is not None:\n            d[\"symbol\"] = self._symbol\n        return json.dumps(d)\n\n\nclass FloatPrecision(TransformSpec):\n    \"\"\"Change the float precision of a model (e.g. F32 to F16).\n\n    Example::\n\n        model.transform(FloatPrecision(DatumType.F32, DatumType.F16))\n        # with include/exclude:\n        model.transform(FloatPrecision(DatumType.F32, DatumType.F16, exclude=[\"layer.1\"]))\n    \"\"\"\n\n    _DT_NAMES = {\n        DatumType.F16: \"f16\",\n        DatumType.F32: \"f32\",\n        DatumType.F64: \"f64\",\n    }\n\n    def __init__(\n        self,\n        from_dt: DatumType,\n        to_dt: DatumType,\n        *,\n        include: Optional[list] = None,\n        exclude: Optional[list] = None,\n    ):\n        if from_dt not in self._DT_NAMES:\n            raise ValueError(f\"from_dt must be a float DatumType, got {from_dt}\")\n        if to_dt not in self._DT_NAMES:\n            raise ValueError(f\"to_dt must be a float DatumType, got {to_dt}\")\n        self._from = from_dt\n        self._to = to_dt\n        self._include = list(include) if include else None\n        self._exclude = list(exclude) if exclude else None\n\n    def include(self, patterns: list) -> \"FloatPrecision\":\n        \"\"\"Set include patterns — only matching nodes are translated. Returns self for chaining.\"\"\"\n        self._include = list(patterns)\n        return self\n\n    def exclude(self, patterns: list) -> \"FloatPrecision\":\n        \"\"\"Set exclude patterns — matching nodes are excluded. Returns self for chaining.\"\"\"\n        self._exclude = list(patterns)\n        return self\n\n    def to_json(self) -> str:\n        d = {\n            \"name\": \"float_precision\",\n            \"from\": self._DT_NAMES[self._from],\n            \"to\": self._DT_NAMES[self._to],\n        }\n        if self._include is not None:\n            d[\"include\"] = self._include\n        if self._exclude is not None:\n            d[\"exclude\"] = self._exclude\n        return json.dumps(d)\n"
  },
  {
    "path": "api/rs/Cargo.toml",
    "content": "[package]\nname = \"tract\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/sonos/tract\"\nkeywords = [\"NeuralNetworks\"]\ncategories = [\"science\"]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\ninclude = [\"Cargo.toml\", \"src/**/*.rs\", \"LICENSE*\", \"tests\"]\n\n[dependencies]\nanyhow.workspace = true\nboow.workspace = true\nerased-serde.workspace = true\nflate2.workspace = true\nhalf.workspace = true\nicu_normalizer.workspace = true\nicu_properties.workspace = true\nndarray.workspace = true\ntract-api.workspace = true\ntract-nnef.workspace = true\ntract-onnx-opl.workspace = true\ntract-onnx.workspace = true\ntract-extra.workspace = true\ntract-pulse.workspace = true\ntract-libcli.workspace = true\ntract-transformers.workspace = true\nserde_json.workspace = true\n\n[target.'cfg(any(target_vendor = \"apple\"))'.dependencies]\ntract-metal.workspace = true\n\n[target.'cfg(any(target_os = \"linux\", target_os = \"windows\"))'.dependencies]\ntract-cuda.workspace = true\n\n\n[dev-dependencies]\nreqwest.workspace = true\nrustls.workspace = true\ntempfile.workspace = true\nserde_json.workspace = true\n\n[features]\ncomplex = []\n# default = [ \"dylib\" ]\n# dylib = []\n# staticlib = []\n"
  },
  {
    "path": "api/rs/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "api/rs/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "api/rs/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "api/rs/src/lib.rs",
    "content": "#[cfg(target_vendor = \"apple\")]\nextern crate tract_metal;\n\n#[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\nextern crate tract_cuda;\nextern crate tract_transformers;\n\nuse std::fmt::{Debug, Display};\nuse std::io::Cursor;\nuse std::path::Path;\nuse std::sync::Arc;\n\nuse anyhow::{Context, Result};\nuse ndarray::{Data, Dimension, RawData};\nuse tract_extra::WithTractExtra;\nuse tract_libcli::annotations::Annotations;\nuse tract_libcli::profile::BenchLimits;\nuse tract_libcli::tensor::RunTensors;\nuse tract_nnef::internal::Runtime as _;\nuse tract_nnef::prelude::{\n    Framework, IntoArcTensor, IntoTValue, SymbolValues, TDim, TValue, TVec,\n    Tensor as InternalTensor, TractResult, TypedFact, TypedModel, TypedSimplePlan,\n};\nuse tract_onnx::prelude::InferenceModelExt;\nuse tract_onnx_opl::WithOnnx;\nuse tract_pulse::WithPulse;\nuse tract_transformers::WithTractTransformers;\n\nuse tract_api::*;\n\npub mod prelude {\n    // Concrete types\n    pub use crate::{\n        Dim, Fact, InferenceFact, InferenceModel, Model, Nnef, Onnx, Runnable, Runtime, State,\n        Tensor, nnef, onnx, runtime_for_name,\n    };\n    pub use ndarray as tract_ndarray;\n\n    // User-facing API types\n    pub use tract_api::{\n        ConcretizeSymbols, Datum, DatumType, FloatPrecision, Pulse, TransformSpec, tensor,\n    };\n\n    // Traits needed for method resolution — hidden from namespace\n    pub use tract_api::{\n        DimInterface as _, FactInterface as _, InferenceFactInterface as _,\n        InferenceModelInterface as _, ModelInterface as _, NnefInterface as _, OnnxInterface as _,\n        RunnableInterface as _, RuntimeInterface as _, StateInterface as _, TensorInterface as _,\n    };\n}\n\n/// Creates an instance of an NNEF framework and parser that can be used to load and dump NNEF models.\npub fn nnef() -> Result<Nnef> {\n    Ok(Nnef(tract_nnef::nnef()))\n}\n\npub fn onnx() -> Result<Onnx> {\n    Ok(Onnx(tract_onnx::onnx()))\n}\n\npub fn runtime_for_name(name: &str) -> Result<Runtime> {\n    if let Some(rt) = tract_onnx::tract_core::runtime::runtime_for_name(name) {\n        Ok(Runtime(rt))\n    } else {\n        anyhow::bail!(\"Runtime {name} not available\")\n    }\n}\n\n/// tract version tag\npub fn version() -> &'static str {\n    env!(\"CARGO_PKG_VERSION\")\n}\n\npub struct Nnef(tract_nnef::internal::Nnef);\n\nimpl Debug for Nnef {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Nnef\")\n    }\n}\n\nimpl NnefInterface for Nnef {\n    type Model = Model;\n    fn load(&self, path: impl AsRef<Path>) -> Result<Model> {\n        let m = self.0.model_for_path(path)?.into_decluttered()?;\n        Ok(Model(m))\n    }\n\n    fn load_buffer(&self, data: &[u8]) -> Result<Self::Model> {\n        let m = self.0.model_for_read(&mut Cursor::new(data))?.into_decluttered()?;\n        Ok(Model(m))\n    }\n\n    fn enable_tract_core(&mut self) -> Result<()> {\n        self.0.enable_tract_core();\n        Ok(())\n    }\n\n    fn enable_tract_extra(&mut self) -> Result<()> {\n        self.0.enable_tract_extra();\n        Ok(())\n    }\n\n    fn enable_tract_transformers(&mut self) -> Result<()> {\n        self.0.enable_tract_transformers();\n        Ok(())\n    }\n\n    fn enable_onnx(&mut self) -> Result<()> {\n        self.0.enable_onnx();\n        Ok(())\n    }\n\n    fn enable_pulse(&mut self) -> Result<()> {\n        self.0.enable_pulse();\n        Ok(())\n    }\n\n    fn enable_extended_identifier_syntax(&mut self) -> Result<()> {\n        self.0.allow_extended_identifier_syntax(true);\n        Ok(())\n    }\n\n    fn write_model_to_dir(&self, path: impl AsRef<Path>, model: &Model) -> Result<()> {\n        self.0.write_to_dir(&model.0, path)\n    }\n\n    fn write_model_to_tar(&self, path: impl AsRef<Path>, model: &Model) -> Result<()> {\n        let file = std::fs::File::create(path)?;\n        self.0.write_to_tar(&model.0, file)?;\n        Ok(())\n    }\n\n    fn write_model_to_tar_gz(&self, path: impl AsRef<Path>, model: &Model) -> Result<()> {\n        let file = std::fs::File::create(path)?;\n        let gz = flate2::write::GzEncoder::new(file, flate2::Compression::default());\n        self.0.write_to_tar(&model.0, gz)?;\n        Ok(())\n    }\n}\n\npub struct Onnx(tract_onnx::Onnx);\n\nimpl Debug for Onnx {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Onnx\")\n    }\n}\n\nimpl OnnxInterface for Onnx {\n    type InferenceModel = InferenceModel;\n    fn load(&self, path: impl AsRef<Path>) -> Result<Self::InferenceModel> {\n        Ok(InferenceModel(self.0.model_for_path(path)?))\n    }\n\n    fn load_buffer(&self, data: &[u8]) -> Result<Self::InferenceModel> {\n        let m = self.0.model_for_read(&mut Cursor::new(data))?;\n        Ok(InferenceModel(m))\n    }\n}\n\npub struct InferenceModel(tract_onnx::prelude::InferenceModel);\n\nimpl Debug for InferenceModel {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"InferenceModel\")\n    }\n}\n\nimpl InferenceModelInterface for InferenceModel {\n    type Model = Model;\n    type InferenceFact = InferenceFact;\n\n    fn input_count(&self) -> Result<usize> {\n        Ok(self.0.inputs.len())\n    }\n\n    fn output_count(&self) -> Result<usize> {\n        Ok(self.0.outputs.len())\n    }\n\n    fn input_name(&self, id: usize) -> Result<String> {\n        let node = self.0.inputs[id].node;\n        Ok(self.0.node(node).name.to_string())\n    }\n\n    fn output_name(&self, id: usize) -> Result<String> {\n        let node = self.0.outputs[id].node;\n        Ok(self.0.node(node).name.to_string())\n    }\n\n    fn input_fact(&self, id: usize) -> Result<InferenceFact> {\n        Ok(InferenceFact(self.0.input_fact(id)?.clone()))\n    }\n\n    fn set_input_fact(\n        &mut self,\n        id: usize,\n        fact: impl AsFact<Self, Self::InferenceFact>,\n    ) -> Result<()> {\n        let fact = fact.as_fact(self)?.0.clone();\n        self.0.set_input_fact(id, fact)\n    }\n\n    fn output_fact(&self, id: usize) -> Result<InferenceFact> {\n        Ok(InferenceFact(self.0.output_fact(id)?.clone()))\n    }\n\n    fn set_output_fact(\n        &mut self,\n        id: usize,\n        fact: impl AsFact<Self, Self::InferenceFact>,\n    ) -> Result<()> {\n        let fact = fact.as_fact(self)?.0.clone();\n        self.0.set_output_fact(id, fact)\n    }\n\n    fn analyse(&mut self) -> Result<()> {\n        self.0.analyse(false)?;\n        Ok(())\n    }\n\n    fn into_model(self) -> Result<Self::Model> {\n        let typed = self.0.into_typed()?.into_decluttered()?;\n        Ok(Model(typed))\n    }\n}\n\n// MODEL\n#[derive(Debug, Clone)]\npub struct Model(TypedModel);\n\nimpl ModelInterface for Model {\n    type Fact = Fact;\n    type Runnable = Runnable;\n    type Tensor = Tensor;\n\n    fn input_count(&self) -> Result<usize> {\n        Ok(self.0.inputs.len())\n    }\n\n    fn output_count(&self) -> Result<usize> {\n        Ok(self.0.outputs.len())\n    }\n\n    fn input_name(&self, id: usize) -> Result<String> {\n        let node = self.0.inputs[id].node;\n        Ok(self.0.node(node).name.to_string())\n    }\n\n    fn output_name(&self, id: usize) -> Result<String> {\n        let node = self.0.outputs[id].node;\n        Ok(self.0.node(node).name.to_string())\n    }\n\n    fn input_fact(&self, id: usize) -> Result<Fact> {\n        Ok(Fact(self.0.input_fact(id)?.clone()))\n    }\n\n    fn output_fact(&self, id: usize) -> Result<Fact> {\n        Ok(Fact(self.0.output_fact(id)?.clone()))\n    }\n\n    fn into_runnable(self) -> Result<Runnable> {\n        let runnable = tract_nnef::internal::DefaultRuntime.prepare(self.0)?;\n        Ok(Runnable(runnable.into()))\n    }\n\n    fn transform(&mut self, spec: impl Into<TransformSpec>) -> Result<()> {\n        let transform = spec.into().to_transform_string();\n        let transform_obj = if transform.trim_start().starts_with('{') {\n            // JSON input: parse, extract name, deserialize params\n            let v: serde_json::Value = serde_json::from_str(&transform)?;\n            let obj = v.as_object().context(\"expected JSON object\")?;\n            let name = obj\n                .get(\"name\")\n                .and_then(|v| v.as_str())\n                .context(\"missing 'name' field\")?\n                .to_string();\n            let mut params = v.clone();\n            params.as_object_mut().unwrap().remove(\"name\");\n            let mut erased = <dyn erased_serde::Deserializer>::erase(params);\n            tract_onnx::tract_core::transform::get_transform_with_params(&name, &mut erased)?\n                .with_context(|| format!(\"transform `{name}' could not be found\"))?\n        } else {\n            // Plain name (no params)\n            tract_onnx::tract_core::transform::get_transform(&transform)?\n                .with_context(|| format!(\"transform `{transform}' could not be found\"))?\n        };\n        transform_obj.transform(&mut self.0)?;\n        self.0.declutter()\n    }\n\n    fn parse_fact(&self, spec: &str) -> Result<Fact> {\n        let f = spec.as_fact(self)?;\n        Ok(Fact(f.0.clone()))\n    }\n\n    fn property_keys(&self) -> Result<Vec<String>> {\n        Ok(self.0.properties.keys().cloned().collect())\n    }\n\n    fn property(&self, name: impl AsRef<str>) -> Result<Tensor> {\n        let name = name.as_ref();\n        self.0\n            .properties\n            .get(name)\n            .with_context(|| format!(\"no property for name {name}\"))\n            .map(|t| Tensor(t.clone()))\n    }\n}\n\n// RUNTIME\npub struct Runtime(&'static dyn tract_nnef::internal::Runtime);\n\nimpl Debug for Runtime {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Runtime({:?})\", self.0.name())\n    }\n}\n\nimpl RuntimeInterface for Runtime {\n    type Runnable = Runnable;\n\n    type Model = Model;\n\n    fn name(&self) -> Result<String> {\n        Ok(self.0.name().into_owned())\n    }\n\n    fn prepare(&self, model: Self::Model) -> Result<Self::Runnable> {\n        let runnable = self.0.prepare(model.0)?;\n        Ok(Runnable(runnable.into()))\n    }\n}\n\n// RUNNABLE\n#[derive(Debug, Clone)]\npub struct Runnable(Arc<dyn tract_nnef::internal::Runnable>);\n\nimpl RunnableInterface for Runnable {\n    type Tensor = Tensor;\n    type State = State;\n    type Fact = Fact;\n\n    fn run(&self, inputs: impl IntoInputs<Tensor>) -> Result<Vec<Tensor>> {\n        StateInterface::run(&mut self.spawn_state()?, inputs.into_inputs()?)\n    }\n\n    fn spawn_state(&self) -> Result<State> {\n        Ok(State(self.0.spawn()?))\n    }\n\n    fn input_count(&self) -> Result<usize> {\n        Ok(self.0.input_count())\n    }\n\n    fn output_count(&self) -> Result<usize> {\n        Ok(self.0.output_count())\n    }\n\n    fn input_fact(&self, id: usize) -> Result<Fact> {\n        Ok(Fact(self.0.input_fact(id)?.clone()))\n    }\n\n    fn output_fact(&self, id: usize) -> Result<Fact> {\n        Ok(Fact(self.0.output_fact(id)?.clone()))\n    }\n\n    fn property_keys(&self) -> Result<Vec<String>> {\n        Ok(self.0.properties().keys().cloned().collect())\n    }\n\n    fn property(&self, name: impl AsRef<str>) -> Result<Tensor> {\n        let name = name.as_ref();\n        self.0\n            .properties()\n            .get(name)\n            .with_context(|| format!(\"no property for name {name}\"))\n            .map(|t| Tensor(t.clone()))\n    }\n\n    fn cost_json(&self) -> Result<String> {\n        let input: Option<Vec<Tensor>> = None;\n        self.profile_json(input)\n    }\n\n    fn profile_json<I, IV, IE>(&self, inputs: Option<I>) -> Result<String>\n    where\n        I: IntoIterator<Item = IV>,\n        IV: TryInto<Self::Tensor, Error = IE>,\n        IE: Into<anyhow::Error> + Debug,\n    {\n        let model = self\n            .0\n            .downcast_ref::<Arc<TypedSimplePlan>>()\n            .context(\"Can only profile TypedModel-based runnables\")?\n            .model();\n        let mut annotations = Annotations::from_model(model)?;\n        tract_libcli::profile::extract_costs(&mut annotations, model, &SymbolValues::default())?;\n        if let Some(inputs) = inputs {\n            let inputs = inputs\n                .into_iter()\n                .map(|v| Ok(v.try_into().unwrap().0.into_tvalue()))\n                .collect::<TractResult<TVec<_>>>()?;\n\n            tract_libcli::profile::profile(\n                &self.0,\n                &BenchLimits::default(),\n                &mut annotations,\n                &RunTensors { sources: vec![inputs] },\n                None,\n                true,\n            )?;\n        };\n        let export = tract_libcli::export::GraphPerfInfo::from(model, &annotations);\n        Ok(serde_json::to_string(&export)?)\n    }\n}\n\n// STATE\npub struct State(Box<dyn tract_nnef::internal::State>);\n\nimpl Debug for State {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"State\")\n    }\n}\n\nimpl StateInterface for State {\n    type Fact = Fact;\n    type Tensor = Tensor;\n\n    fn input_count(&self) -> Result<usize> {\n        Ok(self.0.input_count())\n    }\n\n    fn output_count(&self) -> Result<usize> {\n        Ok(self.0.output_count())\n    }\n\n    fn run(&mut self, inputs: impl IntoInputs<Tensor>) -> Result<Vec<Tensor>> {\n        let inputs: TVec<TValue> =\n            inputs.into_inputs()?.into_iter().map(|v| v.0.into_tvalue()).collect();\n        let outputs = self.0.run(inputs)?;\n        Ok(outputs.into_iter().map(|t| Tensor(t.into_arc_tensor())).collect())\n    }\n}\n\n// TENSOR\n#[derive(Clone, Debug)]\npub struct Tensor(Arc<InternalTensor>);\n\nimpl TensorInterface for Tensor {\n    fn datum_type(&self) -> Result<DatumType> {\n        from_internal_dt(self.0.datum_type())\n    }\n\n    fn from_bytes(dt: DatumType, shape: &[usize], data: &[u8]) -> Result<Self> {\n        let dt = to_internal_dt(dt);\n        let len = shape.iter().product::<usize>() * dt.size_of();\n        anyhow::ensure!(len == data.len());\n        let tensor = unsafe { InternalTensor::from_raw_dt(dt, shape, data)? };\n        Ok(Tensor(tensor.into_arc_tensor()))\n    }\n\n    fn as_bytes(&self) -> Result<(DatumType, &[usize], &[u8])> {\n        let dt = from_internal_dt(self.0.datum_type())?;\n        Ok((dt, self.0.shape(), self.0.try_as_plain()?.as_bytes()))\n    }\n\n    fn convert_to(&self, to: DatumType) -> Result<Self> {\n        let to = to_internal_dt(to);\n        if self.0.datum_type() == to {\n            Ok(self.clone())\n        } else {\n            Ok(Tensor(self.0.cast_to_dt(to)?.into_owned().into_arc_tensor()))\n        }\n    }\n}\n\nimpl PartialEq for Tensor {\n    fn eq(&self, other: &Self) -> bool {\n        let Ok((me_dt, me_shape, me_data)) = self.as_bytes() else { return false };\n        let Ok((other_dt, other_shape, other_data)) = other.as_bytes() else { return false };\n        me_dt == other_dt && me_shape == other_shape && me_data == other_data\n    }\n}\n\nimpl Display for Tensor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.0.dump(false).map_err(|_| std::fmt::Error)?)\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct Fact(TypedFact);\n\nimpl FactInterface for Fact {\n    type Dim = Dim;\n\n    fn datum_type(&self) -> Result<DatumType> {\n        from_internal_dt(self.0.datum_type)\n    }\n\n    fn rank(&self) -> Result<usize> {\n        Ok(self.0.rank())\n    }\n\n    fn dim(&self, axis: usize) -> Result<Self::Dim> {\n        anyhow::ensure!(axis < self.0.rank());\n        Ok(Dim(self.0.shape[axis].clone()))\n    }\n}\n\nimpl Fact {\n    fn new(model: &Model, spec: impl ToString) -> Result<Fact> {\n        let fact = tract_libcli::tensor::parse_spec(&model.0.symbols, &spec.to_string())?;\n        let fact = tract_onnx::prelude::Fact::to_typed_fact(&fact)?.into_owned();\n        Ok(Fact(fact))\n    }\n}\n\nimpl Display for Fact {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        for (i, dim) in self.0.shape.iter().enumerate() {\n            if i > 0 {\n                write!(f, \",\")?;\n            }\n            write!(f, \"{dim}\")?;\n        }\n        let dt = format!(\"{:?}\", self.0.datum_type).to_lowercase();\n        if self.0.rank() > 0 {\n            write!(f, \",\")?;\n        }\n        write!(f, \"{dt}\")\n    }\n}\n\n#[derive(Default, Clone, Debug)]\npub struct InferenceFact(tract_onnx::prelude::InferenceFact);\n\nimpl InferenceFactInterface for InferenceFact {\n    fn empty() -> Result<InferenceFact> {\n        Ok(InferenceFact(Default::default()))\n    }\n}\n\nimpl InferenceFact {\n    fn new(model: &InferenceModel, spec: impl ToString) -> Result<InferenceFact> {\n        let fact = tract_libcli::tensor::parse_spec(&model.0.symbols, &spec.to_string())?;\n        Ok(InferenceFact(fact))\n    }\n}\n\nimpl Display for InferenceFact {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let s = self.0.format_dt_shape();\n        // Lowercase the trailing datum type token (e.g. \"F32\" → \"f32\")\n        // to match the format accepted by the parser.\n        if let Some(pos) = s.rfind(',') {\n            let (dims, dt) = s.split_at(pos + 1);\n            write!(f, \"{dims}{}\", dt.to_lowercase())\n        } else {\n            // Scalar or unknown: the entire string is the dtype\n            write!(f, \"{}\", s.to_lowercase())\n        }\n    }\n}\n\ntensor_from_to_ndarray!();\nas_inference_fact_impl!(InferenceModel, InferenceFact);\nas_fact_impl!(Model, Fact);\n\n#[derive(Clone, Debug)]\npub struct Dim(TDim);\n\nimpl DimInterface for Dim {\n    fn eval(&self, values: impl IntoIterator<Item = (impl AsRef<str>, i64)>) -> Result<Dim> {\n        if let Some(scope) = self.0.find_scope() {\n            let mut table = SymbolValues::default();\n            for (k, v) in values {\n                table = table.with(&scope.sym(k.as_ref()), v);\n            }\n            let result = self.0.eval(&table);\n            Ok(Dim(result))\n        } else {\n            Ok(self.clone())\n        }\n    }\n\n    fn to_int64(&self) -> Result<i64> {\n        self.0.to_i64()\n    }\n}\n\nimpl Display for Dim {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.0)\n    }\n}\n\n/*\n#[inline(always)]\nfn to_datum_type<T: TractProxyDatumType>() -> Result<tract_nnef::prelude::DatumType> {\nmacro_rules! dt { ($($t:ty),*) => { $(if TypeId::of::<T>() == TypeId::of::<$t>() { return Ok(<$t>::datum_type()); })* }}\ndt!(f32, f16, f64, i64, i32, i16, i8, bool, u64, u32, u16, u8);\nanyhow::bail!(\"Unsupported type {}\", std::any::type_name::<T>())\n}\n*/\n\nfn to_internal_dt(it: DatumType) -> tract_nnef::prelude::DatumType {\n    type Api = DatumType;\n    type Internal = tract_nnef::prelude::DatumType;\n    match it {\n        Api::Bool => Internal::Bool,\n        Api::U8 => Internal::U8,\n        Api::U16 => Internal::U16,\n        Api::U32 => Internal::U32,\n        Api::U64 => Internal::U64,\n        Api::I8 => Internal::I8,\n        Api::I16 => Internal::I16,\n        Api::I32 => Internal::I32,\n        Api::I64 => Internal::I64,\n        Api::F16 => Internal::F16,\n        Api::F32 => Internal::F32,\n        Api::F64 => Internal::F64,\n        #[cfg(feature = \"complex\")]\n        Api::ComplexI16 => Internal::ComplexI16,\n        #[cfg(feature = \"complex\")]\n        Api::ComplexI32 => Internal::ComplexI32,\n        #[cfg(feature = \"complex\")]\n        Api::ComplexI64 => Internal::ComplexI64,\n        #[cfg(feature = \"complex\")]\n        Api::ComplexF16 => Internal::ComplexF16,\n        #[cfg(feature = \"complex\")]\n        Api::ComplexF32 => Internal::ComplexF32,\n        #[cfg(feature = \"complex\")]\n        Api::ComplexF64 => Internal::ComplexF64,\n    }\n}\n\nfn from_internal_dt(it: tract_nnef::prelude::DatumType) -> Result<DatumType> {\n    type Api = DatumType;\n    type Internal = tract_nnef::prelude::DatumType;\n    Ok(match it {\n        Internal::Bool => Api::Bool,\n        Internal::U8 => Api::U8,\n        Internal::U16 => Api::U16,\n        Internal::U32 => Api::U32,\n        Internal::U64 => Api::U64,\n        Internal::I8 => Api::I8,\n        Internal::I16 => Api::I16,\n        Internal::I32 => Api::I32,\n        Internal::I64 => Api::I64,\n        Internal::F16 => Api::F16,\n        Internal::F32 => Api::F32,\n        Internal::F64 => Api::F64,\n        #[cfg(feature = \"complex\")]\n        Internal::ComplexI16 => Api::ComplexI16,\n        #[cfg(feature = \"complex\")]\n        Internal::ComplexI32 => Api::ComplexI32,\n        #[cfg(feature = \"complex\")]\n        Internal::ComplexI64 => Api::ComplexI64,\n        #[cfg(feature = \"complex\")]\n        Internal::ComplexF16 => Api::ComplexF16,\n        #[cfg(feature = \"complex\")]\n        Internal::ComplexF32 => Api::ComplexF32,\n        #[cfg(feature = \"complex\")]\n        Internal::ComplexF64 => Api::ComplexF64,\n        _ => {\n            anyhow::bail!(\"Unsupported DatumType in the public API {:?}\", it)\n        }\n    })\n}\n"
  },
  {
    "path": "api/rs/tests/mobilenet.rs",
    "content": "use tract::prelude::*;\n\ninclude!(\"../../tests/mobilenet/mod.rs\");\n"
  },
  {
    "path": "api/src/lib.rs",
    "content": "use anyhow::{Result, ensure};\nuse boow::Bow;\nuse std::fmt::{Debug, Display};\nuse std::path::Path;\n\n#[macro_use]\npub mod macros;\npub mod transform;\n\npub use transform::{ConcretizeSymbols, FloatPrecision, Pulse, TransformConfig, TransformSpec};\n\n/// an implementation of tract's NNEF framework object\n///\n/// Entry point for NNEF model manipulation: loading from file, dumping to file.\npub trait NnefInterface: Debug + Sized {\n    type Model: ModelInterface;\n    /// Load a NNEF model from the path into a tract-core model.\n    ///\n    /// * `path` can point to a directory, a `tar` file or a `tar.gz` file.\n    fn load(&self, path: impl AsRef<Path>) -> Result<Self::Model>;\n\n    /// Load a NNEF model from a buffer into a tract-core model.\n    ///\n    /// data is the content of a NNEF model, as a `tar` file or a `tar.gz` file.\n    fn load_buffer(&self, data: &[u8]) -> Result<Self::Model>;\n\n    /// Allow the framework to use tract_core extensions instead of a stricter NNEF definition.\n    fn enable_tract_core(&mut self) -> Result<()>;\n\n    /// Allow the framework to use tract_extra extensions.\n    fn enable_tract_extra(&mut self) -> Result<()>;\n\n    /// Allow the framework to use tract_transformers extensions to support common transformer operators.\n    fn enable_tract_transformers(&mut self) -> Result<()>;\n\n    /// Allow the framework to use tract_onnx extensions to support operators in ONNX that are\n    /// absent from NNEF.\n    fn enable_onnx(&mut self) -> Result<()>;\n\n    /// Allow the framework to use tract_pulse extensions to support stateful streaming operation.\n    fn enable_pulse(&mut self) -> Result<()>;\n\n    /// Allow the framework to use a tract-proprietary extension that can support special characters\n    /// in node names. If disable, tract will replace everything by underscore '_' to keep\n    /// compatibility with NNEF. If enabled, the extended syntax will be used, allowing to maintain\n    /// the node names in serialized form.\n    fn enable_extended_identifier_syntax(&mut self) -> Result<()>;\n\n    /// Convenience function, similar with enable_tract_core but allowing method chaining.\n    fn with_tract_core(mut self) -> Result<Self> {\n        self.enable_tract_core()?;\n        Ok(self)\n    }\n\n    /// Convenience function, similar with enable_tract_core but allowing method chaining.\n    fn with_tract_extra(mut self) -> Result<Self> {\n        self.enable_tract_extra()?;\n        Ok(self)\n    }\n\n    /// Convenience function, similar with enable_tract_transformers but allowing method chaining.\n    fn with_tract_transformers(mut self) -> Result<Self> {\n        self.enable_tract_transformers()?;\n        Ok(self)\n    }\n\n    /// Convenience function, similar with enable_onnx but allowing method chaining.\n    fn with_onnx(mut self) -> Result<Self> {\n        self.enable_onnx()?;\n        Ok(self)\n    }\n\n    /// Convenience function, similar with enable_pulse but allowing method chaining.\n    fn with_pulse(mut self) -> Result<Self> {\n        self.enable_pulse()?;\n        Ok(self)\n    }\n\n    /// Convenience function, similar with enable_extended_identifier_syntax but allowing method chaining.\n    fn with_extended_identifier_syntax(mut self) -> Result<Self> {\n        self.enable_extended_identifier_syntax()?;\n        Ok(self)\n    }\n\n    /// Dump a TypedModel as a NNEF directory.\n    ///\n    /// `path` is the directory name to dump to\n    fn write_model_to_dir(&self, path: impl AsRef<Path>, model: &Self::Model) -> Result<()>;\n\n    /// Dump a TypedModel as a NNEF tar file.\n    ///\n    /// This function creates a plain, non-compressed, archive.\n    ///\n    /// `path` is the archive name\n    fn write_model_to_tar(&self, path: impl AsRef<Path>, model: &Self::Model) -> Result<()>;\n    fn write_model_to_tar_gz(&self, path: impl AsRef<Path>, model: &Self::Model) -> Result<()>;\n}\n\npub trait OnnxInterface: Debug {\n    type InferenceModel: InferenceModelInterface;\n    fn load(&self, path: impl AsRef<Path>) -> Result<Self::InferenceModel>;\n    /// Load a ONNX model from a buffer into an InferenceModel.\n    fn load_buffer(&self, data: &[u8]) -> Result<Self::InferenceModel>;\n}\n\npub trait InferenceModelInterface: Debug + Sized {\n    type Model: ModelInterface;\n    type InferenceFact: InferenceFactInterface;\n    fn input_count(&self) -> Result<usize>;\n    fn output_count(&self) -> Result<usize>;\n    fn input_name(&self, id: usize) -> Result<String>;\n    fn output_name(&self, id: usize) -> Result<String>;\n\n    fn input_fact(&self, id: usize) -> Result<Self::InferenceFact>;\n\n    fn set_input_fact(\n        &mut self,\n        id: usize,\n        fact: impl AsFact<Self, Self::InferenceFact>,\n    ) -> Result<()>;\n\n    fn output_fact(&self, id: usize) -> Result<Self::InferenceFact>;\n\n    fn set_output_fact(\n        &mut self,\n        id: usize,\n        fact: impl AsFact<Self, Self::InferenceFact>,\n    ) -> Result<()>;\n\n    fn analyse(&mut self) -> Result<()>;\n\n    fn into_model(self) -> Result<Self::Model>;\n}\n\npub trait ModelInterface: Debug + Sized {\n    type Fact: FactInterface;\n    type Runnable: RunnableInterface;\n    type Tensor: TensorInterface;\n    fn input_count(&self) -> Result<usize>;\n\n    fn output_count(&self) -> Result<usize>;\n\n    fn input_name(&self, id: usize) -> Result<String>;\n\n    fn output_name(&self, id: usize) -> Result<String>;\n\n    fn input_fact(&self, id: usize) -> Result<Self::Fact>;\n\n    fn output_fact(&self, id: usize) -> Result<Self::Fact>;\n\n    fn into_runnable(self) -> Result<Self::Runnable>;\n\n    fn transform(&mut self, spec: impl Into<TransformSpec>) -> Result<()>;\n\n    fn property_keys(&self) -> Result<Vec<String>>;\n\n    fn property(&self, name: impl AsRef<str>) -> Result<Self::Tensor>;\n\n    fn parse_fact(&self, spec: &str) -> Result<Self::Fact>;\n\n    fn input_facts(&self) -> Result<impl Iterator<Item = Self::Fact>> {\n        Ok((0..self.input_count()?)\n            .map(|ix| self.input_fact(ix))\n            .collect::<Result<Vec<_>>>()?\n            .into_iter())\n    }\n\n    fn output_facts(&self) -> Result<impl Iterator<Item = Self::Fact>> {\n        Ok((0..self.output_count()?)\n            .map(|ix| self.output_fact(ix))\n            .collect::<Result<Vec<_>>>()?\n            .into_iter())\n    }\n}\n\npub trait RuntimeInterface: Debug {\n    type Runnable: RunnableInterface;\n    type Model: ModelInterface;\n    fn name(&self) -> Result<String>;\n    fn prepare(&self, model: Self::Model) -> Result<Self::Runnable>;\n}\n\npub trait RunnableInterface: Debug + Send + Sync {\n    type Tensor: TensorInterface;\n    type Fact: FactInterface;\n    type State: StateInterface<Tensor = Self::Tensor>;\n    fn run(&self, inputs: impl IntoInputs<Self::Tensor>) -> Result<Vec<Self::Tensor>> {\n        self.spawn_state()?.run(inputs.into_inputs()?)\n    }\n\n    fn input_count(&self) -> Result<usize>;\n    fn output_count(&self) -> Result<usize>;\n    fn input_fact(&self, id: usize) -> Result<Self::Fact>;\n\n    fn output_fact(&self, id: usize) -> Result<Self::Fact>;\n\n    fn input_facts(&self) -> Result<impl Iterator<Item = Self::Fact>> {\n        Ok((0..self.input_count()?)\n            .map(|ix| self.input_fact(ix))\n            .collect::<Result<Vec<_>>>()?\n            .into_iter())\n    }\n\n    fn output_facts(&self) -> Result<impl Iterator<Item = Self::Fact>> {\n        Ok((0..self.output_count()?)\n            .map(|ix| self.output_fact(ix))\n            .collect::<Result<Vec<_>>>()?\n            .into_iter())\n    }\n\n    fn property_keys(&self) -> Result<Vec<String>>;\n    fn property(&self, name: impl AsRef<str>) -> Result<Self::Tensor>;\n\n    fn spawn_state(&self) -> Result<Self::State>;\n\n    fn cost_json(&self) -> Result<String>;\n\n    fn profile_json<I, IV, IE>(&self, inputs: Option<I>) -> Result<String>\n    where\n        I: IntoIterator<Item = IV>,\n        IV: TryInto<Self::Tensor, Error = IE>,\n        IE: Into<anyhow::Error> + Debug;\n}\n\npub trait StateInterface: Debug {\n    type Fact: FactInterface;\n    type Tensor: TensorInterface;\n\n    fn input_count(&self) -> Result<usize>;\n    fn output_count(&self) -> Result<usize>;\n\n    fn run(&mut self, inputs: impl IntoInputs<Self::Tensor>) -> Result<Vec<Self::Tensor>>;\n}\n\npub trait TensorInterface: Debug + Sized + Clone + PartialEq + Send + Sync {\n    fn datum_type(&self) -> Result<DatumType>;\n    fn from_bytes(dt: DatumType, shape: &[usize], data: &[u8]) -> Result<Self>;\n    fn as_bytes(&self) -> Result<(DatumType, &[usize], &[u8])>;\n\n    fn from_slice<T: Datum>(shape: &[usize], data: &[T]) -> Result<Self> {\n        let data = unsafe {\n            std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data))\n        };\n        Self::from_bytes(T::datum_type(), shape, data)\n    }\n\n    fn as_slice<T: Datum>(&self) -> Result<&[T]> {\n        let (dt, _shape, data) = self.as_bytes()?;\n        ensure!(T::datum_type() == dt);\n        let data = unsafe {\n            std::slice::from_raw_parts(\n                data.as_ptr() as *const T,\n                data.len() / std::mem::size_of::<T>(),\n            )\n        };\n        Ok(data)\n    }\n\n    fn as_shape_and_slice<T: Datum>(&self) -> Result<(&[usize], &[T])> {\n        let (_, shape, _) = self.as_bytes()?;\n        let data = self.as_slice()?;\n        Ok((shape, data))\n    }\n\n    fn shape(&self) -> Result<&[usize]> {\n        let (_, shape, _) = self.as_bytes()?;\n        Ok(shape)\n    }\n\n    fn view<T: Datum>(&self) -> Result<ndarray::ArrayViewD<'_, T>> {\n        let (shape, data) = self.as_shape_and_slice()?;\n        Ok(unsafe { ndarray::ArrayViewD::from_shape_ptr(shape, data.as_ptr()) })\n    }\n\n    fn view1<T: Datum>(&self) -> Result<ndarray::ArrayView1<'_, T>> {\n        Ok(self.view::<T>()?.into_dimensionality()?)\n    }\n\n    fn view2<T: Datum>(&self) -> Result<ndarray::ArrayView2<'_, T>> {\n        Ok(self.view::<T>()?.into_dimensionality()?)\n    }\n\n    fn view3<T: Datum>(&self) -> Result<ndarray::ArrayView3<'_, T>> {\n        Ok(self.view::<T>()?.into_dimensionality()?)\n    }\n\n    fn view4<T: Datum>(&self) -> Result<ndarray::ArrayView4<'_, T>> {\n        Ok(self.view::<T>()?.into_dimensionality()?)\n    }\n\n    fn view5<T: Datum>(&self) -> Result<ndarray::ArrayView5<'_, T>> {\n        Ok(self.view::<T>()?.into_dimensionality()?)\n    }\n\n    fn view6<T: Datum>(&self) -> Result<ndarray::ArrayView6<'_, T>> {\n        Ok(self.view::<T>()?.into_dimensionality()?)\n    }\n\n    fn convert_to(&self, to: DatumType) -> Result<Self>;\n}\n\npub trait FactInterface: Debug + Display + Clone {\n    type Dim: DimInterface;\n    fn datum_type(&self) -> Result<DatumType>;\n    fn rank(&self) -> Result<usize>;\n    fn dim(&self, axis: usize) -> Result<Self::Dim>;\n\n    fn dims(&self) -> Result<impl Iterator<Item = Self::Dim>> {\n        Ok((0..self.rank()?).map(|axis| self.dim(axis)).collect::<Result<Vec<_>>>()?.into_iter())\n    }\n}\n\npub trait DimInterface: Debug + Display + Clone {\n    fn eval(&self, values: impl IntoIterator<Item = (impl AsRef<str>, i64)>) -> Result<Self>;\n    fn to_int64(&self) -> Result<i64>;\n}\n\npub trait InferenceFactInterface: Debug + Display + Default + Clone {\n    fn empty() -> Result<Self>;\n}\n\npub trait AsFact<M, F>: Debug {\n    fn as_fact(&self, model: &M) -> Result<Bow<'_, F>>;\n}\n\n#[repr(C)]\n#[derive(Debug, PartialEq, Eq, Copy, Clone)]\npub enum DatumType {\n    Bool = 0x01,\n    U8 = 0x11,\n    U16 = 0x12,\n    U32 = 0x14,\n    U64 = 0x18,\n    I8 = 0x21,\n    I16 = 0x22,\n    I32 = 0x24,\n    I64 = 0x28,\n    F16 = 0x32,\n    F32 = 0x34,\n    F64 = 0x38,\n    #[cfg(feature = \"complex\")]\n    ComplexI16 = 0x42,\n    #[cfg(feature = \"complex\")]\n    ComplexI32 = 0x44,\n    #[cfg(feature = \"complex\")]\n    ComplexI64 = 0x48,\n    #[cfg(feature = \"complex\")]\n    ComplexF16 = 0x52,\n    #[cfg(feature = \"complex\")]\n    ComplexF32 = 0x54,\n    #[cfg(feature = \"complex\")]\n    ComplexF64 = 0x58,\n}\n\nimpl DatumType {\n    pub fn size_of(&self) -> usize {\n        use DatumType::*;\n        match &self {\n            Bool | U8 | I8 => 1,\n            U16 | I16 | F16 => 2,\n            U32 | I32 | F32 => 4,\n            U64 | I64 | F64 => 8,\n            #[cfg(feature = \"complex\")]\n            ComplexI16 | ComplexF16 => 4,\n            #[cfg(feature = \"complex\")]\n            ComplexI32 | ComplexF32 => 8,\n            #[cfg(feature = \"complex\")]\n            ComplexI64 | ComplexF64 => 16,\n        }\n    }\n\n    pub fn is_bool(&self) -> bool {\n        *self == DatumType::Bool\n    }\n\n    pub fn is_number(&self) -> bool {\n        *self != DatumType::Bool\n    }\n\n    pub fn is_unsigned(&self) -> bool {\n        use DatumType::*;\n        *self == U8 || *self == U16 || *self == U32 || *self == U64\n    }\n\n    pub fn is_signed(&self) -> bool {\n        use DatumType::*;\n        *self == I8 || *self == I16 || *self == I32 || *self == I64\n    }\n\n    pub fn is_float(&self) -> bool {\n        use DatumType::*;\n        *self == F16 || *self == F32 || *self == F64\n    }\n}\n\npub trait Datum {\n    fn datum_type() -> DatumType;\n}\n\n// IntoInputs trait — ergonomic input conversion for run()\npub trait IntoInputs<V: TensorInterface> {\n    fn into_inputs(self) -> Result<Vec<V>>;\n}\n\n// Arrays of anything convertible to Tensor\nimpl<V, T, E, const N: usize> IntoInputs<V> for [T; N]\nwhere\n    V: TensorInterface,\n    T: TryInto<V, Error = E>,\n    E: Into<anyhow::Error>,\n{\n    fn into_inputs(self) -> Result<Vec<V>> {\n        self.into_iter().map(|v| v.try_into().map_err(|e| e.into())).collect()\n    }\n}\n\n// Vec<V> passthrough\nimpl<V: TensorInterface> IntoInputs<V> for Vec<V> {\n    fn into_inputs(self) -> Result<Vec<V>> {\n        Ok(self)\n    }\n}\n\n// Tuples — each element converts independently\nmacro_rules! impl_into_inputs_tuple {\n    ($($idx:tt : $T:ident),+) => {\n        impl<V, $($T),+> IntoInputs<V> for ($($T,)+)\n        where\n            V: TensorInterface,\n            $($T: TryInto<V>,\n              <$T as TryInto<V>>::Error: Into<anyhow::Error>,)+\n        {\n            fn into_inputs(self) -> Result<Vec<V>> {\n                Ok(vec![$(self.$idx.try_into().map_err(|e| e.into())?),+])\n            }\n        }\n    };\n}\n\nimpl_into_inputs_tuple!(0: A);\nimpl_into_inputs_tuple!(0: A, 1: B);\nimpl_into_inputs_tuple!(0: A, 1: B, 2: C);\nimpl_into_inputs_tuple!(0: A, 1: B, 2: C, 3: D);\nimpl_into_inputs_tuple!(0: A, 1: B, 2: C, 3: D, 4: E_);\nimpl_into_inputs_tuple!(0: A, 1: B, 2: C, 3: D, 4: E_, 5: F);\nimpl_into_inputs_tuple!(0: A, 1: B, 2: C, 3: D, 4: E_, 5: F, 6: G);\nimpl_into_inputs_tuple!(0: A, 1: B, 2: C, 3: D, 4: E_, 5: F, 6: G, 7: H);\n\n/// Convert any compatible input into a `V: TensorInterface`.\npub fn tensor<V, T, E>(v: T) -> Result<V>\nwhere\n    V: TensorInterface,\n    T: TryInto<V, Error = E>,\n    E: Into<anyhow::Error>,\n{\n    v.try_into().map_err(|e| e.into())\n}\n\nmacro_rules! impl_datum_type {\n    ($ty:ty, $c_repr:expr) => {\n        impl Datum for $ty {\n            fn datum_type() -> DatumType {\n                $c_repr\n            }\n        }\n    };\n}\n\nimpl_datum_type!(bool, DatumType::Bool);\nimpl_datum_type!(u8, DatumType::U8);\nimpl_datum_type!(u16, DatumType::U16);\nimpl_datum_type!(u32, DatumType::U32);\nimpl_datum_type!(u64, DatumType::U64);\nimpl_datum_type!(i8, DatumType::I8);\nimpl_datum_type!(i16, DatumType::I16);\nimpl_datum_type!(i32, DatumType::I32);\nimpl_datum_type!(i64, DatumType::I64);\nimpl_datum_type!(half::f16, DatumType::F16);\nimpl_datum_type!(f32, DatumType::F32);\nimpl_datum_type!(f64, DatumType::F64);\n"
  },
  {
    "path": "api/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! as_inference_fact_impl {\n    ($IM:ident, $IF: ident) => {\n        impl AsFact<$IM, $IF> for $IF {\n            fn as_fact(&self, _model: &$IM) -> Result<boow::Bow<$IF>> {\n                Ok(boow::Bow::Borrowed(self))\n            }\n        }\n\n        impl AsFact<$IM, $IF> for &str {\n            fn as_fact(&self, model: &$IM) -> Result<boow::Bow<$IF>> {\n                Ok(boow::Bow::Owned($IF::new(model, self)?))\n            }\n        }\n\n        impl AsFact<$IM, $IF> for () {\n            fn as_fact(&self, model: &$IM) -> Result<boow::Bow<$IF>> {\n                Ok(boow::Bow::Owned($IF::new(model, \"\")?))\n            }\n        }\n\n        impl AsFact<$IM, $IF> for Option<&str> {\n            fn as_fact(&self, model: &$IM) -> Result<boow::Bow<$IF>> {\n                if let Some(it) = self {\n                    Ok(boow::Bow::Owned($IF::new(model, it)?))\n                } else {\n                    Ok(boow::Bow::Owned($IF::new(model, \"\")?))\n                }\n            }\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! as_fact_impl {\n    ($M:ident, $F: ident) => {\n        impl AsFact<$M, $F> for $F {\n            fn as_fact(&self, _model: &$M) -> Result<boow::Bow<$F>> {\n                Ok(boow::Bow::Borrowed(self))\n            }\n        }\n\n        impl AsFact<$M, $F> for &str {\n            fn as_fact(&self, model: &$M) -> Result<boow::Bow<$F>> {\n                Ok(boow::Bow::Owned($F::new(model, self)?))\n            }\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! tensor_from_to_ndarray {\n    () => {\n        impl<T, S, D> TryFrom<ndarray::ArrayBase<S, D>> for Tensor\n        where\n            T: $crate::Datum + Clone + 'static,\n            S: RawData<Elem = T> + Data,\n            D: Dimension,\n        {\n            type Error = anyhow::Error;\n            fn try_from(view: ndarray::ArrayBase<S, D>) -> Result<Tensor> {\n                if let Some(slice) = view.as_slice_memory_order()\n                    && (0..view.ndim()).all(|ix| {\n                        view.strides().get(ix + 1).is_none_or(|next| *next <= view.strides()[ix])\n                    })\n                {\n                    Tensor::from_slice(view.shape(), slice)\n                } else {\n                    let slice: Vec<_> = view.iter().cloned().collect();\n                    Tensor::from_slice(view.shape(), &slice)\n                }\n            }\n        }\n\n        impl<'a, T: $crate::Datum> TryFrom<&'a Tensor> for ndarray::ArrayViewD<'a, T> {\n            type Error = anyhow::Error;\n            fn try_from(value: &'a Tensor) -> Result<Self, Self::Error> {\n                value.view()\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "api/src/transform.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::DatumType;\n\n/// A serialized transform specification passed to `ModelInterface::transform`.\n///\n/// Wraps the string representation expected by the transform registry.\n/// Constructed from raw strings or typed config structs implementing [`TransformConfig`].\n#[derive(Debug, Clone)]\npub struct TransformSpec(String);\n\nimpl TransformSpec {\n    /// Produce the string the transform registry expects.\n    pub fn to_transform_string(&self) -> String {\n        self.0.clone()\n    }\n}\n\nimpl From<&str> for TransformSpec {\n    fn from(s: &str) -> Self {\n        TransformSpec(s.to_string())\n    }\n}\n\nimpl From<String> for TransformSpec {\n    fn from(s: String) -> Self {\n        TransformSpec(s)\n    }\n}\n\nimpl From<&String> for TransformSpec {\n    fn from(s: &String) -> Self {\n        TransformSpec(s.clone())\n    }\n}\n\n/// Trait for typed transform configurations.\n///\n/// Implementors derive [`serde::Serialize`] and provide a transform [`name()`](TransformConfig::name).\n/// The default [`to_transform_string()`](TransformConfig::to_transform_string) serializes the\n/// struct as a JSON object and injects the `\"name\"` key.\npub trait TransformConfig: serde::Serialize {\n    /// The transform registry name (e.g. `\"pulse\"`, `\"float_precision\"`).\n    fn name(&self) -> &'static str;\n\n    /// Produce the string the transform registry expects.\n    ///\n    /// The default implementation serializes `self` to a JSON object and inserts `\"name\"`.\n    fn to_transform_string(&self) -> String {\n        let mut obj: serde_json::Map<String, serde_json::Value> = serde_json::to_value(self)\n            .expect(\"TransformConfig serialization cannot fail\")\n            .as_object()\n            .expect(\"TransformConfig must serialize to a JSON object\")\n            .clone();\n        obj.insert(\"name\".into(), serde_json::Value::String(self.name().to_string()));\n        serde_json::to_string(&obj).expect(\"serialization cannot fail\")\n    }\n}\n\n/// Implements [`TransformConfig`] and `From<$ty> for TransformSpec`.\nmacro_rules! transform_config {\n    ($ty:ty, $name:expr) => {\n        impl TransformConfig for $ty {\n            fn name(&self) -> &'static str {\n                $name\n            }\n        }\n\n        impl From<$ty> for TransformSpec {\n            fn from(config: $ty) -> Self {\n                TransformSpec(config.to_transform_string())\n            }\n        }\n    };\n}\n\n/// Typed config for the `concretize_symbols` transform.\n///\n/// Replaces symbolic dimensions with concrete integer values.\n///\n/// # Example\n/// ```ignore\n/// model.transform(ConcretizeSymbols::new().value(\"B\", 1))?;\n/// ```\n#[derive(Debug, Clone, Default, serde::Serialize)]\npub struct ConcretizeSymbols {\n    values: HashMap<String, i64>,\n}\n\nimpl ConcretizeSymbols {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Set a symbol to a concrete value.\n    pub fn value(mut self, symbol: impl Into<String>, val: i64) -> Self {\n        self.values.insert(symbol.into(), val);\n        self\n    }\n}\n\ntransform_config!(ConcretizeSymbols, \"concretize_symbols\");\n\n/// Typed config for the `pulse` transform.\n///\n/// Converts a model to a pulsed (streaming) model.\n///\n/// # Example\n/// ```ignore\n/// model.transform(Pulse::new(\"5\").symbol(\"B\"))?;\n/// ```\n#[derive(Debug, Clone, serde::Serialize)]\npub struct Pulse {\n    pulse: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    symbol: Option<String>,\n}\n\nimpl Pulse {\n    /// Create a new Pulse config with the given pulse dimension.\n    pub fn new(pulse: impl Into<String>) -> Self {\n        Self { pulse: pulse.into(), symbol: None }\n    }\n\n    /// Set the symbol to pulse over (defaults to \"S\" if not set).\n    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {\n        self.symbol = Some(symbol.into());\n        self\n    }\n}\n\ntransform_config!(Pulse, \"pulse\");\n\n/// Typed config for the `float_precision` transform.\n///\n/// Changes the float precision of a model (e.g. F32 to F16).\n///\n/// # Example\n/// ```ignore\n/// use tract_api::DatumType;\n/// model.transform(FloatPrecision::new(DatumType::F32, DatumType::F16))?;\n/// ```\n#[derive(Debug, Clone, serde::Serialize)]\npub struct FloatPrecision {\n    from: String,\n    to: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    include: Option<Vec<String>>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    exclude: Option<Vec<String>>,\n}\n\nfn datum_type_to_str(dt: DatumType) -> &'static str {\n    match dt {\n        DatumType::F16 => \"f16\",\n        DatumType::F32 => \"f32\",\n        DatumType::F64 => \"f64\",\n        _ => panic!(\"FloatPrecision only supports float datum types (F16, F32, F64)\"),\n    }\n}\n\nimpl FloatPrecision {\n    pub fn new(from: DatumType, to: DatumType) -> Self {\n        Self {\n            from: datum_type_to_str(from).to_string(),\n            to: datum_type_to_str(to).to_string(),\n            include: None,\n            exclude: None,\n        }\n    }\n\n    /// Set include patterns — only nodes matching at least one pattern are translated.\n    pub fn include(mut self, patterns: Vec<String>) -> Self {\n        self.include = Some(patterns);\n        self\n    }\n\n    /// Set exclude patterns — matching nodes are excluded from translation.\n    pub fn exclude(mut self, patterns: Vec<String>) -> Self {\n        self.exclude = Some(patterns);\n        self\n    }\n}\n\ntransform_config!(FloatPrecision, \"float_precision\");\n"
  },
  {
    "path": "api/tests/mobilenet/mod.rs",
    "content": "use std::sync::Once;\n\nfn grace_hopper() -> Tensor {\n    let data = std::fs::read(\"../tests/grace_hopper_3_224_224.f32.raw\").unwrap();\n    let data: &[f32] = unsafe { std::slice::from_raw_parts(data.as_ptr() as _, 3 * 224 * 224) };\n    Tensor::from_slice(&[1, 3, 224, 224], data).unwrap()\n}\n\nfn ensure_models() -> anyhow::Result<()> {\n    static START: Once = Once::new();\n    START.call_once(|| {\n        let _ = rustls::crypto::ring::default_provider().install_default();\n        for (url, file) in [\n            (\n                \"https://s3.amazonaws.com/tract-ci-builds/tests/mobilenetv2-7.onnx\",\n                \"mobilenetv2-7.onnx\",\n            ),\n            (\n                \"https://s3.amazonaws.com/tract-ci-builds/tests/mobilenet_v2_1.0.onnx.nnef.tgz\",\n                \"mobilenet_v2_1.0.onnx.nnef.tgz\",\n            ),\n        ] {\n            if std::fs::metadata(file).is_err() {\n                let client = reqwest::blocking::Client::new();\n                let model = client.get(url).send().unwrap();\n                std::fs::write(file, model.bytes().unwrap()).unwrap();\n            }\n        }\n    });\n    Ok(())\n}\n\n#[test]\nfn test_onnx() -> anyhow::Result<()> {\n    ensure_models()?;\n    let model = onnx()?.load(\"mobilenetv2-7.onnx\")?.into_model()?.into_runnable()?;\n    let hopper = grace_hopper();\n    let result = model.run([hopper])?;\n    let result = result[0].view::<f32>()?;\n    let best = result\n        .as_slice()\n        .unwrap()\n        .iter()\n        .enumerate()\n        .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())\n        .unwrap();\n    assert_eq!(best.0, 652);\n    Ok(())\n}\n\n#[test]\nfn test_state() -> anyhow::Result<()> {\n    ensure_models()?;\n    let model = onnx()?.load(\"mobilenetv2-7.onnx\")?.into_model()?.into_runnable()?;\n    let mut state = model.spawn_state()?;\n    let hopper = grace_hopper();\n    let result = state.run([hopper])?;\n    let result = result[0].view::<f32>()?;\n    let best = result\n        .as_slice()\n        .unwrap()\n        .iter()\n        .enumerate()\n        .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())\n        .unwrap();\n    assert_eq!(best.0, 652);\n    Ok(())\n}\n\n#[test]\nfn test_nnef() -> anyhow::Result<()> {\n    ensure_models()?;\n    let model = nnef()?.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?.into_runnable()?;\n    let hopper = grace_hopper();\n    let result = model.run([hopper])?;\n    assert_eq!(result[0].datum_type()?, f32::datum_type());\n    let result = result[0].view::<f32>()?;\n    let best = result\n        .as_slice()\n        .unwrap()\n        .iter()\n        .enumerate()\n        .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())\n        .unwrap();\n    assert_eq!(best.0, 652);\n    Ok(())\n}\n\n#[test]\nfn test_inference_model() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    assert_eq!(model.input_count().unwrap(), 1);\n    assert_eq!(model.output_count().unwrap(), 1);\n    assert_eq!(model.input_name(0).unwrap(), \"data\");\n    assert_eq!(model.output_name(0).unwrap(), \"mobilenetv20_output_flatten0_reshape0\");\n    assert_eq!(model.input_fact(0).unwrap().to_string(), \"1,3,224,224,f32\");\n    model.set_input_fact(0, \"1,3,224,224,f32\")?;\n    let model = model.into_model()?.into_runnable()?;\n    let hopper = grace_hopper();\n    let result = model.run([hopper])?;\n    let view = result[0].view::<f32>()?;\n    let best = view\n        .as_slice()\n        .unwrap()\n        .iter()\n        .enumerate()\n        .max_by(|a, b| a.1.partial_cmp(b.1).unwrap())\n        .unwrap();\n    assert_eq!(best.0, 652);\n    Ok(())\n}\n\n\n#[test]\nfn test_typed_model() -> anyhow::Result<()> {\n    ensure_models()?;\n    let model = nnef()?.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?;\n    assert_eq!(model.input_count()?, 1);\n    assert_eq!(model.output_count()?, 1);\n    assert_eq!(model.input_name(0)?, \"data\");\n    assert_eq!(model.output_name(0)?, \"conv_53\");\n    assert_eq!(model.input_fact(0)?.to_string(), \"1,3,224,224,f32\");\n    assert_eq!(model.output_fact(0)?.to_string(), \"1,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_runtime() -> anyhow::Result<()> {\n    ensure_models()?;\n    let model = nnef()?.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?;\n    let rt = runtime_for_name(\"default\")?;\n    let runnable = rt.prepare(model)?;\n    let hopper = grace_hopper();\n    let _result = runnable.run([hopper])?;\n    Ok(())\n}\n\n\n#[test]\nfn test_concretize() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let mut typed = model.into_model()?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"B,3,224,224,f32\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"B,1000,f32\");\n    typed.transform(ConcretizeSymbols::new().value(\"B\", 1))?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"1,3,224,224,f32\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"1,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_concretize_raw_string() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let mut typed = model.into_model()?;\n    typed.transform(r#\"{\"name\":\"concretize_symbols\",\"values\":{\"B\":1}}\"#)?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"1,3,224,224,f32\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"1,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_pulse() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let mut typed = model.into_model()?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"B,3,224,224,f32\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"B,1000,f32\");\n    typed.transform(Pulse::new(\"5\").symbol(\"B\"))?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"5,3,224,224,f32\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"5,1000,f32\");\n    let mut properties = typed.property_keys()?;\n    properties.sort();\n    assert_eq!(&properties, &[\"pulse.delay\", \"pulse.input_axes\", \"pulse.output_axes\"]);\n    assert_eq!(typed.property(\"pulse.delay\")?.view::<i64>()?, ndarray::arr1(&[0i64]).into_dyn());\n    Ok(())\n}\n\n#[test]\nfn test_pulse_raw_string() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let mut typed = model.into_model()?;\n    typed.transform(r#\"{\"name\":\"pulse\",\"symbol\":\"B\",\"pulse\":\"5\"}\"#)?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"5,3,224,224,f32\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"5,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_runtime_fact() -> anyhow::Result<()> {\n    ensure_models()?;\n    let runnable = nnef()?.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?.into_runnable()?;\n    assert_eq!(runnable.input_fact(0)?.to_string(), \"1,3,224,224,f32\");\n    assert_eq!(runnable.output_fact(0)?.to_string(), \"1,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_runtime_properties() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let mut typed = model.into_model()?;\n    typed.transform(r#\"{\"name\":\"pulse\",\"symbol\":\"B\",\"pulse\":\"5\"}\"#)?;\n    let runnable = typed.into_runnable()?;\n    let mut properties = runnable.property_keys()?;\n    properties.sort();\n    assert_eq!(&properties, &[\"pulse.delay\", \"pulse.input_axes\", \"pulse.output_axes\"]);\n    assert_eq!(runnable.property(\"pulse.delay\")?.view::<i64>()?, ndarray::arr1(&[0i64]).into_dyn());\n    Ok(())\n}\n\n#[test]\nfn test_f32_to_f16() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let mut typed = model.into_model()?;\n    typed.transform(FloatPrecision::new(\n        DatumType::F32,\n        DatumType::F16,\n    ))?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"B,3,224,224,f16\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"B,1000,f16\");\n    Ok(())\n}\n\n#[test]\nfn test_f32_to_f16_raw_string() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let mut typed = model.into_model()?;\n    typed.transform(\"f32_to_f16\")?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"B,3,224,224,f16\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"B,1000,f16\");\n    Ok(())\n}\n\n#[test]\nfn test_f16_to_f32() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let mut typed = model.into_model()?;\n\n    // Convert model to half\n    typed.transform(FloatPrecision::new(\n        DatumType::F32,\n        DatumType::F16,\n    ))?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"B,3,224,224,f16\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"B,1000,f16\");\n\n    // Convert back to f32\n    typed.transform(FloatPrecision::new(\n        DatumType::F16,\n        DatumType::F32,\n    ))?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"B,3,224,224,f32\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"B,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_f16_to_f32_raw_string() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let mut typed = model.into_model()?;\n    typed.transform(\"f32_to_f16\")?;\n    typed.transform(\"f16_to_f32\")?;\n    assert_eq!(typed.input_fact(0)?.to_string(), \"B,3,224,224,f32\");\n    assert_eq!(typed.output_fact(0)?.to_string(), \"B,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_typed_model_to_nnef_and_back() -> anyhow::Result<()> {\n    ensure_models()?;\n    let mut model = onnx()?.load(\"mobilenetv2-7.onnx\")?;\n    model.set_input_fact(0, \"B,3,224,224,f32\")?;\n    model.analyse()?;\n    let typed = model.into_model()?;\n    let dir = tempfile::tempdir()?;\n    let nnef = nnef()?.with_tract_core()?;\n\n    let path = dir.path().join(\"nnef-dir\");\n    nnef.write_model_to_dir(&path, &typed)?;\n    let reloaded = nnef.load(path)?;\n    assert_eq!(reloaded.input_fact(0)?.to_string(), \"B,3,224,224,f32\");\n    assert_eq!(reloaded.output_fact(0)?.to_string(), \"B,1000,f32\");\n\n    let path = dir.path().join(\"nnef.tar\");\n    nnef.write_model_to_tar(&path, &typed)?;\n    let reloaded = nnef.load(path)?;\n    assert_eq!(reloaded.input_fact(0)?.to_string(), \"B,3,224,224,f32\");\n    assert_eq!(reloaded.output_fact(0)?.to_string(), \"B,1000,f32\");\n\n    let path = dir.path().join(\"nnef.tar.gz\");\n    nnef.write_model_to_tar_gz(&path, &typed)?;\n    let reloaded = nnef.load(path)?;\n    assert_eq!(reloaded.input_fact(0)?.to_string(), \"B,3,224,224,f32\");\n    assert_eq!(reloaded.output_fact(0)?.to_string(), \"B,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_cost() -> anyhow::Result<()> {\n    ensure_models()?;\n    let model = nnef()?.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?.into_runnable()?;\n    let profile = model.cost_json()?;\n    let json: serde_json::Value = serde_json::from_str(&profile)?;\n    let nodes = json.get(\"nodes\").unwrap().as_array().unwrap();\n    assert!(nodes.len() > 10);\n    let node = nodes[0].as_object().unwrap();\n    assert!(node[\"node_name\"].as_str().unwrap() != \"\");\n    assert!(node[\"op_name\"].as_str().unwrap() != \"\");\n    assert!(\n        nodes\n            .iter()\n            .find_map(|n| n.get(\"cost\").and_then(|c| c.as_object().unwrap().get(\"FMA(F32)\")))\n            .is_some()\n    );\n    Ok(())\n}\n\n#[test]\nfn test_profile() -> anyhow::Result<()> {\n    ensure_models()?;\n    let model = nnef()?.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?.into_runnable()?;\n    let data = ndarray::ArrayD::<f32>::zeros(vec![1, 3, 224, 224]);\n    let profile = model.profile_json(Some([data]))?;\n    let profile: serde_json::Value = serde_json::from_str(&profile)?;\n    let profiling_info = profile[\"profiling_info\"].as_object().unwrap();\n    assert!(profiling_info[\"iterations\"].as_i64().unwrap() >= 1);\n    let nodes = profile.get(\"nodes\").unwrap().as_array().unwrap();\n    assert!(nodes.iter().find_map(|n| n.get(\"secs_per_iter\").and_then(|c| c.as_f64())).is_some());\n    Ok(())\n}\n\n#[test]\nfn test_transform_registry() -> anyhow::Result<()> {\n    ensure_models()?;\n\n    let nnef = nnef()?.with_tract_core()?;\n    let mut model = nnef.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?;\n\n    // Convert model to half\n    model.transform(\"f32_to_f16\")?;\n    assert_eq!(model.input_fact(0)?.to_string(), \"1,3,224,224,f16\");\n    assert_eq!(model.output_fact(0)?.to_string(), \"1,1000,f16\");\n\n    // Convert back to f32\n    model.transform(\"f16_to_f32\")?;\n    assert_eq!(model.input_fact(0)?.to_string(), \"1,3,224,224,f32\");\n    assert_eq!(model.output_fact(0)?.to_string(), \"1,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_fact_and_dims() -> anyhow::Result<()> {\n    ensure_models()?;\n    let nnef = nnef()?.with_tract_core()?;\n    let model = nnef.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?;\n    let fact = model.parse_fact(\"B,S+P,64,f32\")?;\n    assert_eq!(fact.datum_type()?, f32::datum_type());\n    assert_eq!(fact.rank()?, 3);\n    assert_eq!(fact.dim(1)?.to_string(), \"S+P\");\n    let s_plus_p = fact.dim(1)?;\n    let s_plus_twelve = s_plus_p.eval([(\"P\", 12)])?;\n    assert_eq!(s_plus_twelve.to_string(), \"S+12\");\n    let fourteen = s_plus_twelve.eval([(\"S\", 2)])?;\n    assert_eq!(fourteen.to_int64()?, 14);\n    Ok(())\n}\n\n#[test]\nfn test_fact_and_dims_iterators() -> anyhow::Result<()> {\n    ensure_models()?;\n    let nnef = nnef()?.with_tract_core()?;\n    let model = nnef.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?;\n    let fact = model.input_facts()?.collect::<Vec<_>>();\n    assert!(fact.len() == 1);\n    let dims = fact[0].dims()?.collect::<Vec<_>>();\n    assert_eq!(dims.len(), 4);\n    assert_eq!(dims[0].to_string(), \"1\");\n    assert_eq!(dims[1].to_string(), \"3\");\n    assert_eq!(dims[2].to_string(), \"224\");\n    assert_eq!(dims[3].to_string(), \"224\");\n    Ok(())\n}\n\n#[test]\nfn test_runtime_fact_iterator() -> anyhow::Result<()> {\n    ensure_models()?;\n    let nnef = nnef()?.with_tract_core()?;\n    let runnable = nnef.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?.into_runnable()?;\n    let inputs = runnable.input_facts()?.collect::<Vec<_>>();\n    assert!(inputs.len() == 1);\n    assert_eq!(inputs[0].to_string(), \"1,3,224,224,f32\");\n    let outputs = runnable.output_facts()?.collect::<Vec<_>>();\n    assert!(outputs.len() == 1);\n    assert_eq!(outputs[0].to_string(), \"1,1000,f32\");\n    Ok(())\n}\n\n#[test]\nfn test_tensor_methods() -> anyhow::Result<()> {\n    let floats: Tensor = tensor(ndarray::prelude::arr1(&[-1f32, -0.3, 0., 0.25, 0.75, 1.2]))?;\n    assert!(floats.datum_type()?.is_float());\n    let ints = floats.convert_to(i8::datum_type())?;\n    assert!(ints.datum_type()?.is_signed());\n    assert_eq!(ints.view::<i8>()?.as_slice().unwrap(), &[-1, 0, 0, 0, 0, 1]);\n    let same: Tensor = tensor(ndarray::prelude::arr1(&[-1f32, -0.3, 0., 0.25, 0.75, 1.2]))?;\n    assert_eq!(floats, same);\n    Ok(())\n}\n"
  },
  {
    "path": "ci/tract-ci-minion/.gitignore",
    "content": "minion.toml\n"
  },
  {
    "path": "ci/tract-ci-minion/Cargo.toml",
    "content": "[package]\nname = \"tract-ci-minion\"\nversion = \"0.20.7-pre\"\nedition = \"2024\"\n\n[workspace]\nmembers = []\n\n[dependencies]\nclap = { version=\"4\", features = [\"derive\"]}\ndirs = \"3\"\nfs2 = \"0\"\nrust-s3 = { version = \"0.26.4\", default-features = false, features = [ \"rustls-tls\" ] }\nserde = { version = \"1.0\", features = [\"derive\"] }\ntoml = \"0.5\"\nanyhow = \"1.0.42\"\nlog = \"0.4.14\"\nenv_logger = \"0.11\"\npipe = \"0.4.0\"\nflate2 = \"1.0.20\"\ntar = \"0.4.35\"\ndaemonize = \"0.5\"\nlibc = \"0.2.119\"\nwait-timeout = \"0.2.0\"\nlazy_static = \"1.4.0\"\nsimple-signal = \"1\"\n"
  },
  {
    "path": "ci/tract-ci-minion/minion.toml.example",
    "content": "id = \"tsar\"\nworkdir = \"tmp/workdir\"\nplatform = \"test\"\ngraphite = { host = \"...\", port = 2003, prefix = \"tract\" }\n\n# MINION_ID=\n# LOCKFILE=/tmp/minion-lock\n# PLATFORM=raspbian\n# GRAPHITE_HOST=graphite-proxy.snips.net\n# GRAPHITE_PORT=2003\n# GRAPHITE_PREFIX=tract\n# \n# S3PATH_TASKS=tract-ci-builds/tasks\n# S3PATH_LOGS=tract-ci-builds/logs\n# S3PATH_RESULTS=tract-ci-builds/logs\n# WORKDIR=$HOME/tract-minion\n# CACHEDIR=$WORKDIR/cache\n"
  },
  {
    "path": "ci/tract-ci-minion/src/main.rs",
    "content": "use anyhow::{bail, Context, Result};\nuse clap::Parser;\nuse flate2::read::GzEncoder;\nuse fs2::FileExt;\nuse s3::creds::Credentials;\nuse s3::serde_types::Object;\nuse s3::Bucket;\nuse s3::Region;\nuse serde::{Deserialize, Deserializer};\nuse std::collections::HashMap;\nuse std::fs::File;\nuse std::io::Write;\nuse std::net::TcpStream;\nuse std::path::{Path, PathBuf};\nuse std::sync::atomic::AtomicI32;\nuse std::time::Duration;\nuse wait_timeout::ChildExt;\n\nlazy_static::lazy_static! {\n    static ref CHILD: std::sync::Arc<AtomicI32> = std::sync::Arc::new(AtomicI32::new(0));\n}\n\n#[derive(Deserialize, Debug)]\nstruct Config {\n    id: String,\n    #[serde(default = \"default_workdir\")]\n    workdir: PathBuf,\n    #[serde(default = \"default_region\", deserialize_with = \"deser_region\")]\n    region: Region,\n    aws_credentials: Option<AwsCredentials>,\n    #[serde(default = \"default_bucket\")]\n    s3_bucket: String,\n    #[serde(default = \"default_tasks\")]\n    s3_tasks: String,\n    #[serde(default = \"default_logs\")]\n    s3_logs: String,\n    #[serde(default = \"default_products\")]\n    s3_products: String,\n    platform: String,\n    graphite: Option<Graphite>,\n    #[serde(default = \"default_idle_sleep_secs\")]\n    idle_sleep_secs: usize,\n    #[serde(default)]\n    env: HashMap<String, String>,\n    #[serde(default = \"default_timeout_runtime_secs\")]\n    timeout_runtime_secs: usize,\n}\n\n#[derive(Deserialize, Debug)]\nstruct Graphite {\n    host: String,\n    port: u16,\n    prefix: String,\n}\n\n#[derive(Deserialize)]\nstruct AwsCredentials {\n    access_key: String,\n    secret_key: String,\n}\n\nimpl std::fmt::Debug for AwsCredentials {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"AwsCredentials {{ access_key: {}, secret_key: <...> }}\", self.access_key)\n    }\n}\n\nfn default_workdir() -> PathBuf {\n    dirs::home_dir().expect(\"HOME does not exist\").join(\"tract-minion\")\n}\n\nfn default_region() -> Region {\n    Region::UsEast1\n}\n\nfn default_bucket() -> String {\n    \"tract-ci-builds\".to_string()\n}\n\nfn default_tasks() -> String {\n    \"tasks\".to_string()\n}\n\nfn default_logs() -> String {\n    \"logs\".to_string()\n}\n\nfn default_products() -> String {\n    \"products\".to_string()\n}\n\nfn default_idle_sleep_secs() -> usize {\n    5 * 60\n}\n\nfn default_timeout_runtime_secs() -> usize {\n    300\n}\n\nfn deser_region<'de, D>(d: D) -> Result<Region, D::Error>\nwhere\n    D: Deserializer<'de>,\n{\n    use serde::de::Error;\n    let s: &str = Deserialize::deserialize(d)?;\n    s.parse().map_err(D::Error::custom)\n}\n\nfn config_path() -> PathBuf {\n    if let Ok(c) = std::env::var(\"TRACT_MINION_CONFIG\") {\n        PathBuf::from(c)\n    } else if std::path::Path::new(\"minion.toml\").exists() {\n        PathBuf::from(\"minion.toml\")\n    } else {\n        dirs::home_dir().context(\"HOME does not exist\").unwrap().join(\".minion.toml\")\n    }\n}\n\nfn read_config(path: impl AsRef<Path>) -> Result<Config> {\n    let text = std::fs::read_to_string(&path)\n        .with_context(|| format!(\"Opening config {:?}\", path.as_ref()))?;\n    let config = toml::from_str(&text)\n        .with_context(|| format!(\"Parsing configuration file {:?}\", path.as_ref()))?;\n    Ok(config)\n}\n\nfn dl_task(task_name: &str) -> Result<()> {\n    let config = config()?;\n    let bucket = bucket(&config)?;\n    let task_url = std::path::PathBuf::from(&config.s3_tasks)\n        .join(&config.platform)\n        .join(task_name)\n        .with_extension(\"tgz\");\n    log::info!(\"Downloading task {}\", task_name);\n    let task_dir = config.workdir.join(\"current\");\n    if task_dir.exists() {\n        std::fs::remove_dir_all(&task_dir)?;\n    }\n    std::fs::create_dir_all(&task_dir)?;\n    let (reader, mut writer) = pipe::pipe_buffered();\n    let task_dir_2 = task_dir.clone();\n    let bucket_2 = bucket.clone();\n    std::thread::spawn(move || {\n        bucket_2.get_object_stream_blocking(task_url.to_str().unwrap(), &mut writer).unwrap();\n    });\n    let uncompressed = flate2::read::GzDecoder::new(reader);\n    tar::Archive::new(uncompressed).unpack(task_dir_2)?;\n    log::info!(\"Download {} ok\", task_name);\n    Ok(())\n}\n\nfn vars(task_name: &str) -> Result<HashMap<String, String>> {\n    let config = config()?;\n    let task_dir = config.workdir.join(\"current\");\n    let vars_file = task_dir.join(task_name).join(\"vars\");\n    let mut vars: HashMap<String, String> = config.env.clone();\n    if vars_file.exists() {\n        log::debug!(\"Reading vars...\");\n        for line in std::fs::read_to_string(vars_file)?.lines() {\n            if line.starts_with(\"export \") {\n                let mut pair = line.split_whitespace().nth(1).unwrap().split(\"=\");\n                vars.insert(pair.next().unwrap().to_string(), pair.next().unwrap().to_string());\n            }\n        }\n    } else {\n        log::info!(\"No vars file\");\n    }\n    Ok(vars)\n}\n\nfn run_task(task_name: &str, timeout: Duration) -> Result<()> {\n    let config = config()?;\n    let bucket = bucket(&config)?;\n    log::info!(\"Running task {}\", task_name);\n    let task_dir = config.workdir.join(\"current\");\n    let vars: HashMap<String, String> = vars(task_name)?;\n    let mut cmd = std::process::Command::new(\"sh\");\n    cmd.current_dir(task_dir.join(task_name))\n        .arg(\"-c\")\n        .arg(\"./entrypoint.sh 2> stderr.log > stdout.log\");\n    log::info!(\"Running {:?}\", cmd);\n    cmd.envs(&vars); // do not log env as it may contain sensitive vars\n    let mut child = cmd.spawn()?;\n    let result = child.wait_timeout(timeout)?;\n    let Some(status) = result else {\n        let _ = child.kill();\n        log::info!(\"Script timeout, sending a kill, waiting...\");\n        child.wait()?;\n        bail!(\"entrypoint.sh script timeout\")\n    };\n    if !status.success() {\n        bail!(\"entrypoint.sh script failed: {status:?}\")\n    }\n    log::info!(\"entrypoint.sh ran successfully\");\n    for log in &[\"stderr.log\", \"stdout.log\"] {\n        let local_path = task_dir.join(task_name).join(log);\n        if local_path.exists() {\n            let s3name = Path::new(&config.s3_logs)\n                .join(&config.id)\n                .join(task_name)\n                .join(log)\n                .with_extension(\"gz\");\n            log::info!(\"Uploading {} to {:?}\", log, s3name);\n            let mut gz =\n                GzEncoder::new(std::fs::File::open(&local_path)?, flate2::Compression::default());\n            let mut content = vec![];\n            gz.write_all(&mut content)?;\n            bucket\n                .put_object_blocking(s3name.to_str().unwrap(), &content)\n                .with_context(|| format!(\"uploading {}\", s3name.to_str().unwrap()))?;\n        } else {\n            log::info!(\"Could not find {}\", log);\n        }\n    }\n    let metrics_files = task_dir.join(task_name).join(\"metrics\");\n    if metrics_files.exists() {\n        if let Some(gr) = &config.graphite {\n            let prefix = format!(\n                \"{}.{}.{}.{}\",\n                gr.prefix, config.platform, config.id, vars[\"TRAVIS_BRANCH_SANE\"]\n            )\n            .replace(\"-\", \"_\");\n            let mut socket = TcpStream::connect((gr.host.clone(), gr.port))\n                .with_context(|| format!(\"Opening socket to {:?}\", gr))?;\n            let ts = &vars[\"TIMESTAMP\"];\n            for line in std::fs::read_to_string(metrics_files)?.lines() {\n                let mut tokens = line.split_whitespace();\n                let graphite = format!(\n                    \"{}.{} {} {}\",\n                    prefix,\n                    tokens.next().unwrap().replace(\"-\", \"_\"),\n                    tokens.next().unwrap(),\n                    ts\n                );\n                log::trace!(\"Sending to graphite: {graphite}\");\n                writeln!(socket, \"{graphite}\").context(\"Writing to graphite socket\")?;\n            }\n        }\n    }\n    let product_dir = task_dir.join(task_name).join(\"product\");\n    if product_dir.exists() {\n        let tar_name = format!(\"{}.{}\", task_name, config.id);\n        let s3name =\n            Path::new(&config.s3_products).join(&config.id).join(task_name).with_extension(\"tgz\");\n        let mut buf = vec![];\n        let tgz = flate2::write::GzEncoder::new(&mut buf, flate2::Compression::default());\n        tar::Builder::new(tgz).append_dir_all(tar_name, product_dir)?;\n        bucket\n            .put_object_blocking(s3name.to_str().unwrap(), &buf)\n            .with_context(|| format!(\"uploading {}\", s3name.to_str().unwrap()))?;\n    }\n    Ok(())\n}\n\n#[derive(Debug)]\nstruct Timeout;\nimpl std::error::Error for Timeout {}\n\nimpl std::fmt::Display for Timeout {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Task imeout!\")\n    }\n}\n\nfn consider_task(config: &Config, task: &Object) -> Result<bool> {\n    let task_path = Path::new(&task.key);\n    let task_name = task_path\n        .file_stem()\n        .context(\"Filename can not be splitted\")?\n        .to_str()\n        .unwrap()\n        .to_string();\n    let done_file = config.workdir.join(\"taskdone\").join(&task_name);\n    if done_file.exists() {\n        return Ok(false);\n    }\n    for attempt in 0.. {\n        // match subcommand(\"download-task\", &task_name, Duration::from_secs(60)) {\n        match dl_task(&task_name) {\n            Err(e) if e.root_cause().is::<Timeout>() && attempt < 5 => continue,\n            Err(e) => Err(e)?,\n            Ok(()) => break,\n        }\n    }\n\n    let vars = vars(&task_name)?;\n    let timeout =\n        vars.get(\"TIMEOUT\").map(|s| s.parse()).transpose()?.unwrap_or(config.timeout_runtime_secs)\n            as u64;\n    // let result = subcommand(\"run-task\", &task_name, Duration::from_secs(timeout));\n    let result = run_task(&task_name, Duration::from_secs(timeout));\n    std::fs::File::create(&done_file)?;\n    result.map(|_| true)\n}\n\nfn run(config: &Config) -> Result<bool> {\n    std::thread::sleep(Duration::from_secs(10));\n    let bucket = bucket(config)?;\n    let tasks_prefix = std::path::PathBuf::from(&config.s3_tasks).join(&config.platform).join(\"\");\n    let objects_parts =\n        bucket.list_blocking(tasks_prefix.to_str().unwrap().to_string(), Some(\"/\".to_string()))?;\n    let mut done_anything = false;\n    for parts in objects_parts {\n        for item in parts.0.contents {\n            done_anything = consider_task(config, &item)? || done_anything;\n        }\n    }\n    Ok(done_anything)\n}\n\nfn config() -> Result<Config> {\n    let cf_path = config_path();\n    log::debug!(\"Reading config from {:?}\", cf_path);\n    let config = read_config(cf_path)?;\n    log::debug!(\"{:?}\", config);\n    Ok(config)\n}\n\nfn bucket(config: &Config) -> Result<Bucket> {\n    let credentials = Credentials::new(\n        config.aws_credentials.as_ref().map(|cred| &*cred.access_key),\n        config.aws_credentials.as_ref().map(|cred| &*cred.secret_key),\n        None,\n        None,\n        None,\n    )\n    .unwrap();\n    let bucket = Bucket::new(&config.s3_bucket, config.region.clone(), credentials)?;\n    Ok(bucket)\n}\n\n#[derive(Debug, Clone, Parser)]\nstruct Args {\n    /// Run in background\n    #[arg(short, long)]\n    daemonize: bool,\n\n    /// Run once, stop when all tasks are done\n    #[arg(short, long)]\n    once: bool,\n}\n\nfn main_loop(args: &Args) -> Result<()> {\n    let config = config()?;\n    let lock = config.workdir.join(\"lock\");\n    log::info!(\"Locking {:?}\", lock);\n    std::fs::create_dir_all(&config.workdir)?;\n    std::fs::create_dir_all(&config.workdir.join(\"taskdone\"))?;\n    let lock =\n        std::fs::File::create(&lock).with_context(|| format!(\"Creating lock file {:?}\", lock))?;\n    loop {\n        if let Ok(_) = lock.try_lock_exclusive() {\n            log::info!(\"Acquired lock, fetching task list\");\n            match run(&config) {\n                Ok(done_something) => {\n                    if !done_something {\n                        if args.once {\n                            log::info!(\"No more work, stopping\");\n                            return Ok(());\n                        } else {\n                            let dur = Duration::from_secs(config.idle_sleep_secs as _);\n                            log::info!(\"No task left, sleeping for {:?}\", dur);\n                            std::thread::sleep(dur);\n                        }\n                    }\n                }\n                Err(e) => {\n                    if args.once {\n                        return Err(e);\n                    }\n                    log::error!(\"{e:?}\");\n                }\n            }\n        } else {\n            log::info!(\"Already locked, retry in 1 sec...\");\n            std::thread::sleep(Duration::from_secs(1));\n        };\n    }\n}\n\nextern \"C\" fn signal_handler(sig: libc::size_t) -> libc::size_t {\n    let child_id = CHILD.load(std::sync::atomic::Ordering::SeqCst);\n    if child_id != 0 {\n        unsafe {\n            libc::kill(-(child_id as i32), sig as _);\n        }\n    }\n    eprintln!(\"** Caught signal, cleanup...\");\n    std::process::exit(1);\n}\n\nfn do_main() -> anyhow::Result<()> {\n    let args = Args::parse();\n    if args.daemonize {\n        let _config = config()?;\n\n        log::info!(\"Deamonizing\");\n        let stdout = File::create(\"tract-ci-minion.out\")?;\n        let stderr = File::create(\"tract-ci-minion.err\")?;\n\n        let daemonize = daemonize::Daemonize::new()\n            .working_directory(std::env::current_dir()?)\n            .pid_file(\"tract-ci-minion.pid\")\n            .stdout(stdout)\n            .stderr(stderr);\n        daemonize.start()?;\n    }\n    main_loop(&args)\n}\n\nfn main() {\n    env_logger::Builder::new()\n        .filter_level(log::LevelFilter::Info)\n        .parse_env(\"TRACT_MINION_LOG\")\n        .init();\n    unsafe {\n        libc::signal(libc::SIGTERM, signal_handler as libc::sighandler_t);\n        libc::signal(libc::SIGINT, signal_handler as libc::sighandler_t);\n    }\n\n    if let Err(e) = do_main() {\n        log::error!(\"{e:?}\");\n        std::process::exit(1);\n    }\n}\n"
  },
  {
    "path": "cli/Cargo.toml",
    "content": "[package]\nname = \"tract-cli\"\nversion = \"0.23.0-pre\"\nauthors = [\n  \"Romain Liautaud <romain.liautaud@snips.ai>\",\n  \"Mathieu Poumeyrol <kali@zoy.org>\",\n]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [\"TensorFlow\", \"NeuralNetworks\"]\ncategories = [\"science\"]\nautobenches = false\nedition = \"2024\"\ninclude = [\"Cargo.toml\", \"src/**/*.rs\", \"LICENSE*\"]\n\n[[bin]]\nname = \"tract\"\npath = \"src/main.rs\"\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\natty.workspace = true\nbox_drawing.workspace = true\nclap.workspace = true\nerased-serde.workspace = true\ncriterion.workspace = true\ncolorous.workspace = true\nenv_logger.workspace = true\nflate2.workspace = true\nfs-err.workspace = true\nicu_normalizer.workspace = true\nicu_normalizer_data.workspace = true\nicu_properties.workspace = true\nicu_properties_data.workspace = true\nidna_adapter.workspace = true\ninventory.workspace = true\nlazy_static.workspace = true\nlitemap.workspace = true\nlog.workspace = true\nndarray-npy.workspace = true\nnu-ansi-term.workspace = true\nnum_cpus.workspace = true\npy_literal.workspace = true\nreadings-probe.workspace = true\nron.workspace = true\nregex.workspace = true\nreqwest.workspace = true\nrustls.workspace = true\nwebpki-roots.workspace = true\nscan_fmt.workspace = true\nserde.workspace = true\nserde_json.workspace = true\ntract-linalg = { workspace = true, features = [\"hwbench\"] }\ntract-core.workspace = true\ntract-hir.workspace = true\ntract-nnef.workspace = true\ntract-nnef-resources.workspace = true\ntract-libcli.workspace = true\ntract-gpu.workspace = true\ntract-extra = { workspace = true, optional = true }\ntract-pulse = { workspace = true, optional = true }\ntract-pulse-opl = { workspace = true, optional = true }\ntract-onnx = { workspace = true, optional = true }\ntract-tensorflow = { workspace = true, optional = true }\ntract-tflite = { workspace = true, optional = true }\ntract-transformers = { workspace = true, optional = true }\nzerofrom.workspace = true\nfloat-ord.workspace = true\n\n[target.'cfg(any(target_os = \"macos\", target_os = \"ios\"))'.dependencies]\ntract-metal.workspace = true\n\n[target.'cfg(any(target_os = \"linux\", target_os = \"windows\"))'.dependencies]\ncudarc.workspace = true\ntract-cuda.workspace = true\n\n[features]\ndefault = [\n  \"onnx\",\n  \"tf\",\n  \"pulse\",\n  \"pulse-opl\",\n  \"tflite\",\n  \"transformers\",\n  \"extra\",\n]\napple-amx-ios = [\"tract-linalg/apple-amx-ios\"]\nonnx = [\"tract-onnx\", \"tract-libcli/hir\", \"tract-libcli/onnx\"]\nextra = [\"tract-extra\"]\npulse-opl = [\"tract-pulse-opl\"]\npulse = [\"tract-pulse\", \"tract-pulse-opl\"]\ntf = [\"tract-tensorflow\", \"tract-libcli/hir\"]\ntflite = [\"tract-tflite\"]\ntransformers = [\"tract-transformers\", \"tract-libcli/transformers\"]\nconform = [\"tract-tensorflow/conform\"]\nmultithread-mm = [\"tract-linalg/multithread-mm\"]\n"
  },
  {
    "path": "cli/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "cli/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "cli/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "cli/src/bench.rs",
    "content": "use crate::Parameters;\nuse tract_hir::internal::*;\nuse tract_libcli::profile::{BenchLimits, run_one_step};\nuse tract_libcli::tensor::get_or_make_inputs;\nuse tract_libcli::terminal;\n\npub fn criterion(\n    params: &Parameters,\n    _matches: &clap::ArgMatches,\n    sub_matches: &clap::ArgMatches,\n) -> TractResult<()> {\n    let mut crit = criterion::Criterion::default();\n    let mut group = crit.benchmark_group(\"net\");\n\n    let run_params = crate::tensor::run_params_from_subcommand(params, sub_matches)?;\n    let inputs = get_or_make_inputs(&params.tract_model, &run_params)?;\n\n    let runnable = params.req_runnable()?;\n    let mut state = runnable.spawn()?;\n    group.bench_function(\"run\", move |b| b.iter(|| run_one_step(&runnable, &mut state, &inputs)));\n\n    Ok(())\n}\n\npub fn handle(\n    params: &Parameters,\n    sub_matches: &clap::ArgMatches,\n    limits: &BenchLimits,\n) -> TractResult<()> {\n    let run_params = crate::tensor::run_params_from_subcommand(params, sub_matches)?;\n    let inputs = get_or_make_inputs(&params.tract_model, &run_params)?;\n\n    limits.warmup(&params.req_runnable()?, &inputs)?;\n\n    let (iters, dur) = {\n        #[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\n        let _profiler =\n            sub_matches.get_flag(\"cuda-gpu-trace\").then(cudarc::driver::safe::Profiler::new);\n        limits.bench(&params.req_runnable()?, &inputs)?\n    };\n    let dur = dur.div_f64(iters as _);\n\n    if params.machine_friendly {\n        println!(\"real: {}\", dur.as_secs_f64());\n    } else {\n        println!(\"Bench ran {} times, {}.\", iters, terminal::dur_avg(dur));\n    }\n\n    if let Some(pp) = sub_matches.get_one::<String>(\"pp\") {\n        let pp = pp.parse::<usize>()?;\n        let tokens = pp as f64 / dur.as_secs_f64();\n        println!(\"PP{pp}: {tokens:.1} tokens/sec\");\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "cli/src/compare.rs",
    "content": "#![allow(dead_code)]\nuse std::fmt::{Debug, Display};\n#[allow(unused_imports)]\nuse std::fs;\n\nuse nu_ansi_term::Color::*;\n\nuse log::Level::Info;\nuse tract_core::internal::*;\n\nuse crate::dump::annotate_with_graph_def;\nuse crate::*;\nuse tract_libcli::display_params::DisplayParams;\nuse tract_libcli::tensor::{RunParams, get_or_make_inputs};\n\npub fn handle(\n    params: &mut Parameters,\n    _matches: &clap::ArgMatches,\n    sub_matches: &clap::ArgMatches,\n    output_params: DisplayParams,\n) -> TractResult<()> {\n    let run_params = crate::tensor::run_params_from_subcommand(params, sub_matches)?;\n\n    if sub_matches.get_flag(\"stream\") {\n        return handle_stream(params, &output_params, &run_params);\n    }\n\n    let cumulative = sub_matches.get_flag(\"cumulative\");\n    let resilent = sub_matches.get_flag(\"resilient\");\n    if sub_matches.get_one::<String>(\"stage\").is_some() {\n        // --with is by pipeline and put in params\n        return handle_reference_stage(cumulative, params, &output_params, &run_params);\n    } else if let Some(npz) = sub_matches.get_one::<String>(\"npz\") {\n        return handle_npz(cumulative, npz, params, &output_params, &run_params);\n    } else if sub_matches.get_flag(\"twice\") {\n        return handle_twice(cumulative, params, &output_params, &run_params);\n    }\n    if let Some(pbdir) = sub_matches.get_one::<String>(\"pbdir\") {\n        return handle_pbdir(cumulative, pbdir, params, &output_params, &run_params);\n    }\n    if sub_matches.get_flag(\"tf\") {\n        return handle_tensorflow(cumulative, resilent, params, &output_params, &run_params);\n    }\n    bail!(\"No comparison target found\")\n}\n\n#[cfg(not(feature = \"conform\"))]\npub fn handle_tensorflow(\n    _cumulative: bool,\n    _resilient: bool,\n    _params: &mut Parameters,\n    _output_params: &DisplayParams,\n    _run_params: &RunParams,\n) -> TractResult<()> {\n    bail!(\"`tf` feature is required for this to work\");\n}\n\n#[cfg(feature = \"conform\")]\npub fn handle_tensorflow(\n    cumulative: bool,\n    resilient: bool,\n    params: &mut Parameters,\n    output_params: &DisplayParams,\n    run_params: &RunParams,\n) -> TractResult<()> {\n    let tract = &params.tract_model;\n    let mut tf = params.tf_model.take().unwrap();\n    // First generate random values for the inputs.\n    let input_facts = tract\n        .input_outlets()\n        .iter()\n        .map(|&i| tract.outlet_typedfact(i))\n        .collect::<TractResult<Vec<_>>>()?;\n    let generated = crate::tensor::make_inputs(&*input_facts)?;\n\n    // Execute the model on tensorflow first.\n    info!(\"Running the model on tensorflow.\");\n    trace!(\"Inject inputs in tensorflow graph.\");\n    let pairs: Vec<_> = tract\n        .input_outlets()\n        .iter()\n        .map(|s| &*tract.node_name(s.node))\n        .zip(generated.iter().cloned())\n        .collect();\n\n    trace!(\"Execute the model on tensorflow.\");\n    let eval_order = tract.eval_order()?;\n\n    let mut wanted_outputs: Vec<&str> = eval_order\n        .iter()\n        .filter(|&n| !tract.input_outlets().contains(&OutletId::new(*n, 0)))\n        .map(|&n| tract.node_name(n))\n        .collect();\n\n    for o in tract.output_outlets() {\n        let name = &*tract.node_name(o.node);\n        if !wanted_outputs.contains(&name) {\n            wanted_outputs.push(name);\n        }\n    }\n\n    let mut all_values: HashMap<String, Vec<TractResult<TValue>>> = HashMap::new();\n    if resilient {\n        for name in wanted_outputs {\n            all_values.insert(\n                name.to_string(),\n                vec![\n                    tf.run(pairs.clone(), &name)\n                        .map(|t| Arc::new(t[0].clone().into()))\n                        .map_err(|e| e.into()),\n                ],\n            );\n        }\n    } else {\n        tf.run_get_many(pairs, wanted_outputs)?.into_iter().for_each(|(k, v)| {\n            all_values.insert(k.to_string(), vec![Ok(v[0].clone().into())]);\n        });\n    };\n\n    for (ix, input) in tract.input_outlets().iter().enumerate() {\n        let name = tract.node_name(input.node);\n        all_values.insert(name.to_string(), vec![Ok(generated[ix].clone().into_arc_tensor())]);\n    }\n    dispatch_model_no_pulse!(params.tract_model, |m| compare(\n        cumulative,\n        m,\n        &all_values,\n        &params,\n        &output_params,\n        run_params,\n        (\"tract\", \"tf\"),\n    ))\n}\n\npub fn handle_npz(\n    cumulative: bool,\n    npz: &str,\n    params: &Parameters,\n    output_params: &DisplayParams,\n    run_params: &RunParams,\n) -> TractResult<()> {\n    use tract_libcli::tensor::for_npz;\n    let mut npz = ndarray_npy::NpzReader::new(fs_err::File::open(npz)?)?;\n    let mut values: HashMap<String, Vec<TractResult<TValue>>> = HashMap::new();\n    let multiturn = npz.names()?.iter().any(|n| n.starts_with(\"turn_0/\"));\n    for name in npz.names()? {\n        if let Ok(value) = for_npz(&mut npz, &name) {\n            if multiturn {\n                let name = name\n                    .split('/')\n                    .nth(1)\n                    .with_context(|| {\n                        format!(\n                            \"npy filenames should be turn_XX/... in multiturn mode, got `{name}'\"\n                        )\n                    })?\n                    .trim_end_matches(\".npy\");\n                values.entry(name.to_string()).or_default().push(Ok(value.into_tvalue()));\n            } else {\n                let name = name.trim_end_matches(\".npy\");\n                values.insert(name.to_string(), vec![Ok(value.into())]);\n            }\n        }\n    }\n    dispatch_model_no_pulse!(&params.tract_model, |m| compare(\n        cumulative,\n        &m,\n        &values,\n        params,\n        output_params,\n        run_params,\n        (\"tract\", \"npz\"),\n    ))\n}\n\n#[cfg(not(feature = \"onnx\"))]\npub fn handle_pbdir(\n    _cumulative: bool,\n    _pbdir: &str,\n    _params: &Parameters,\n    _output_params: &DisplayParams,\n    _run_params: &RunParams,\n) -> TractResult<()> {\n    bail!(\"`onnx` feature is required for this to work\");\n}\n\n#[cfg(feature = \"onnx\")]\npub fn handle_pbdir(\n    cumulative: bool,\n    pbdir: &str,\n    params: &Parameters,\n    output_params: &DisplayParams,\n    run_params: &RunParams,\n) -> TractResult<()> {\n    use tract_onnx::data_resolver::FopenDataResolver;\n    use tract_onnx::tensor::load_tensor;\n\n    let mut values: HashMap<String, Vec<TractResult<TValue>>> = HashMap::new();\n    for entry in fs::read_dir(pbdir)? {\n        let entry = entry?;\n        let file = fs::File::open(entry.path())?;\n        let tensor = tract_onnx::tensor::proto_from_reader(file)?;\n        let name = tensor.name.to_string();\n        let value: Tensor = load_tensor(&FopenDataResolver, &tensor, None)?;\n        values.insert(name, vec![Ok(value.into_tvalue())]);\n    }\n    dispatch_model_no_pulse!(&params.tract_model, |m| compare(\n        cumulative,\n        &m,\n        &values,\n        params,\n        output_params,\n        run_params,\n        (\"tract\", \"pbdir\"),\n    ))\n}\n\npub fn handle_twice(\n    cumulative: bool,\n    params: &Parameters,\n    output_params: &DisplayParams,\n    run_params: &RunParams,\n) -> TractResult<()> {\n    let reference_model = &params.tract_model;\n    let reference_model: Arc<TypedModel> = Arc::downcast::<TypedModel>(reference_model.clone())\n        .map_err(|_| anyhow!(\"Only works with a typed reference model\"))?;\n    handle_with_model(cumulative, params, output_params, &reference_model, run_params)\n}\n\npub fn handle_reference_stage(\n    cumulative: bool,\n    params: &Parameters,\n    output_params: &DisplayParams,\n    run_params: &RunParams,\n) -> TractResult<()> {\n    info!(\"Computing results for reference stage\");\n    let reference_model: &Arc<dyn Model> =\n        params.reference_model.as_ref().context(\"Missing reference model\")?;\n    let reference_model = Arc::downcast::<TypedModel>(reference_model.clone())\n        .map_err(|_| anyhow!(\"Only works with a typed reference model\"))?;\n    handle_with_model(cumulative, params, output_params, &reference_model, run_params)\n}\n\npub fn handle_with_model(\n    cumulative: bool,\n    params: &Parameters,\n    output_params: &DisplayParams,\n    reference_model: &Arc<TypedModel>,\n    run_params: &RunParams,\n) -> TractResult<()> {\n    let mut values: HashMap<String, Vec<TractResult<TValue>>> = HashMap::new();\n    let plan = reference_model.clone().into_runnable()?;\n    let mut state = plan.spawn()?;\n    let inputs = get_or_make_inputs(&(reference_model.clone() as _), run_params)?;\n    for input in inputs.sources {\n        state.run_plan_with_eval(input, |session, state, node, input| -> TractResult<_> {\n            let result = tract_core::plan::eval(session, state, node, input)?;\n            if node.outputs.len() == 1 {\n                values.entry(node.name.clone()).or_default().push(Ok(result[0].clone()));\n            }\n            for (output_slot, v) in result.iter().enumerate() {\n                if let Some(tag) = reference_model.outlet_label((node.id, output_slot).into()) {\n                    if tag != node.name {\n                        values.entry(tag.to_string()).or_default().push(Ok(v.clone()));\n                    }\n                }\n            }\n            Ok(result)\n        })?;\n    }\n    dispatch_model_no_pulse!(&params.tract_model, |m| compare(\n        cumulative,\n        &m,\n        &values,\n        params,\n        output_params,\n        run_params,\n        (\"tract\", \"reference\"),\n    ))\n}\n\n// handle_stream runs the pulsed model pulse-by-pulse, stitches per-node valid output regions\n// (accounting for each node's delay), then delegates to compare() which runs the concretized\n// reference model and handles all comparison, annotation, and rendering.\n#[cfg(not(feature = \"pulse\"))]\npub fn handle_stream(\n    _params: &Parameters,\n    _output_params: &DisplayParams,\n    _run_params: &RunParams,\n) -> TractResult<()> {\n    bail!(\"`pulse` feature is required for --stream to work\");\n}\n\n#[cfg(feature = \"pulse\")]\npub fn handle_stream(\n    params: &Parameters,\n    output_params: &DisplayParams,\n    run_params: &RunParams,\n) -> TractResult<()> {\n    use tract_core::ndarray::{ArrayD, Axis};\n    use tract_core::plan::SimpleState;\n    use tract_pulse::internal::*;\n\n    let reference: &TypedModel = params\n        .reference_model\n        .as_ref()\n        .context(\"Reference (pre-pulse) model not available\")?\n        .downcast_ref::<TypedModel>()\n        .context(\"Reference model is not a TypedModel\")?;\n\n    let model: Arc<TypedModel> =\n        params.typed_model().context(\"Post-pulse TypedModel not available\")?;\n\n    let ref_input_fact = reference.input_fact(0)?;\n    let model_input_fact = model.input_fact(0)?;\n\n    let input_axis = model\n        .properties\n        .get(\"pulse.input_axes\")\n        .context(\"Expect pulse.input_axes property\")?\n        .cast_to::<i64>()?\n        .try_as_plain()?\n        .as_slice::<i64>()?[0] as usize;\n\n    let stream_symbol = ref_input_fact.shape[input_axis]\n        .symbols()\n        .into_iter()\n        .next()\n        .context(\"Could not find streaming symbol in reference model input\")?;\n\n    let input_pulse = model_input_fact.shape[input_axis]\n        .to_usize()\n        .context(\"Pulse dimension should be concrete\")?;\n\n    // Recreate PulsedModel to get per-node delay/axis/pulse metadata\n    let pulsed = PulsedModel::new(reference, stream_symbol.clone(), &input_pulse.to_dim())?;\n\n    // Pre-build per-node pulse metadata (keyed by node name, slot 0 only)\n    struct PulseInfo {\n        delay: usize,\n        output_axis: usize,\n        output_pulse: usize,\n        fixed_output_len: usize,\n    }\n    let mut max_delay: usize = 0;\n    let mut pulse_meta: HashMap<String, PulseInfo> = HashMap::new();\n\n    // We need stream_dim to compute fixed_output_len, but stream_dim depends on max_delay.\n    // First pass: find max_delay.\n    for pulsed_node in pulsed.nodes() {\n        if let Ok(fact) = pulsed.outlet_fact(OutletId::new(pulsed_node.id, 0)) {\n            if let Some(stream) = &fact.stream {\n                max_delay = max_delay.max(stream.delay);\n            }\n        }\n    }\n\n    let stream_dim = max_delay + 3 * input_pulse + input_pulse / 2;\n    let concrete_sym_values = SymbolValues::default().with(&stream_symbol, stream_dim as _);\n\n    // Second pass: build full metadata with fixed_output_len\n    for pulsed_node in pulsed.nodes() {\n        if let Ok(fact) = pulsed.outlet_fact(OutletId::new(pulsed_node.id, 0)) {\n            if let Some(stream) = &fact.stream {\n                let output_pulse = fact.pulse().context(\"no pulse\")?.to_usize()?;\n                let fixed_output_len = stream.dim.eval_to_i64(&concrete_sym_values)? as usize;\n                pulse_meta.insert(\n                    pulsed_node.name.clone(),\n                    PulseInfo {\n                        delay: stream.delay,\n                        output_axis: stream.axis,\n                        output_pulse,\n                        fixed_output_len,\n                    },\n                );\n            }\n        }\n    }\n\n    // Generate the fixed input (same seed as compare()'s get_or_make_inputs will use)\n    let concrete_shape: Vec<usize> =\n        ref_input_fact.shape.eval_to_usize(&concrete_sym_values)?.into_owned().into_vec();\n    let concrete_input_fact = ref_input_fact.datum_type.fact(&*concrete_shape);\n    let fixed_input = tract_libcli::tensor::tensor_for_fact(&concrete_input_fact, None, None)?;\n\n    // Run the pulsed model pulse-by-pulse, collecting per-node valid output slices\n    let plan = Arc::new(model.as_ref().clone().into_runnable()?);\n    let mut state = SimpleState::new(&plan)?;\n    let input_shape: TVec<usize> =\n        model_input_fact.shape.iter().map(|d| d.to_usize()).collect::<TractResult<TVec<_>>>()?;\n\n    let mut node_slices: HashMap<String, Vec<Tensor>> = HashMap::new();\n    let mut node_axes: HashMap<String, usize> = HashMap::new();\n    let num_pulses = (stream_dim + max_delay) / input_pulse + 2;\n\n    for i in 0..num_pulses {\n        let mut pulsed_input = ArrayD::from_elem(&*input_shape, f32::NAN);\n        let offset = i * input_pulse;\n        if offset < stream_dim {\n            let count = input_pulse.min(stream_dim - offset);\n            pulsed_input.slice_axis_mut(Axis(input_axis), (0..count).into()).assign(\n                &fixed_input\n                    .to_plain_array_view::<f32>()?\n                    .slice_axis(Axis(input_axis), (offset..offset + count).into()),\n            );\n        }\n        if offset + input_pulse > stream_dim {\n            state.turn_state.resolved_symbols.set(&stream_symbol, stream_dim as _);\n        }\n\n        state.run_plan_with_eval(\n            tvec!(pulsed_input.into_tensor().into()),\n            |session, op_state, node, input| -> TractResult<TVec<TValue>> {\n                let result = tract_core::plan::eval(session, op_state, node, input)?;\n\n                if let Some(info) = pulse_meta.get(&node.name) {\n                    let output_offset = (i + 1) * info.output_pulse;\n                    // Check if this pulse has valid output for this node\n                    if output_offset > info.delay\n                        && output_offset - info.output_pulse < info.delay + info.fixed_output_len\n                    {\n                        let (p_o, count) = if output_offset - info.output_pulse < info.delay {\n                            // Beginning of signal: partial overlap\n                            let count = output_offset - info.delay;\n                            (info.output_pulse - count, count)\n                        } else if output_offset > info.delay + info.fixed_output_len {\n                            // End of signal: partial overlap\n                            let count = info.delay + info.fixed_output_len\n                                - (output_offset - info.output_pulse);\n                            (0, count)\n                        } else {\n                            // Full pulse in valid region\n                            (0, info.output_pulse)\n                        };\n                        let valid = result[0].slice(info.output_axis, p_o, p_o + count)?;\n                        node_slices.entry(node.name.clone()).or_default().push(valid.into_tensor());\n                        node_axes.insert(node.name.clone(), info.output_axis);\n                    }\n                } else if i == 0 && node.outputs.len() == 1 {\n                    // Non-streaming node: capture on first pulse only\n                    node_slices\n                        .entry(node.name.clone())\n                        .or_default()\n                        .push(result[0].clone().into_tensor());\n                }\n\n                Ok(result)\n            },\n        )?;\n    }\n\n    // Stitch: concatenate valid slices per node into full-length tensors\n    let mut all_values: HashMap<String, Vec<TractResult<TValue>>> = HashMap::new();\n    for (name, slices) in &node_slices {\n        if let Some(&axis) = node_axes.get(name) {\n            let stitched = Tensor::stack_tensors(axis, slices)?;\n            all_values.insert(name.clone(), vec![Ok(stitched.into_tvalue())]);\n        } else {\n            all_values.insert(name.clone(), vec![Ok(slices[0].clone().into_tvalue())]);\n        }\n    }\n\n    // Concretize the reference model and delegate to compare()\n    let concrete_ref = Arc::new(reference.clone().concretize_dims(&concrete_sym_values)?);\n    compare(\n        false,\n        &concrete_ref,\n        &all_values,\n        params,\n        output_params,\n        run_params,\n        (\"reference\", \"pulsed\"),\n    )\n}\n\npub fn compare<F, O>(\n    cumulative: bool,\n    tract: &Arc<Graph<F, O>>,\n    all_values: &HashMap<String, Vec<TractResult<TValue>>>,\n    params: &Parameters,\n    output_params: &DisplayParams,\n    run_params: &RunParams,\n    labels: (&str, &str),\n) -> TractResult<()>\nwhere\n    F: Fact + Clone + Hash,\n    O: AsRef<dyn Op> + AsMut<dyn Op> + Display + Debug + Clone,\n    Graph<F, O>: Model,\n{\n    info!(\"Obtained reference data, starting test run\");\n    // Execute the model step-by-step on tract.\n    let plan = Arc::clone(tract).into_runnable()?;\n    let mut state = plan.spawn()?;\n\n    let mut annotations = Annotations::from_model(tract.as_ref() as &dyn Model)?;\n    annotate_with_graph_def(&mut annotations, tract.as_ref(), &params.graph)?;\n\n    let mut failing = std::collections::HashSet::new();\n    let mut unchecked = std::collections::HashSet::new();\n    let mut ok = 0;\n    fn canonic(s: &str) -> String {\n        s.replace(['.', '-'], \"_\")\n    }\n    let all_values: HashMap<String, &Vec<TractResult<TValue>>> =\n        all_values.iter().map(|(k, v)| (canonic(k), v)).collect();\n    let inputs = get_or_make_inputs(&(tract.clone() as _), run_params)?;\n    for (turn, inputs) in inputs.sources.into_iter().enumerate() {\n        state.run_plan_with_eval(\n            inputs,\n            |session_state, state, node, input| -> TractResult<TVec<TValue>> {\n                let mut returning = tract_core::plan::eval(session_state, state, node, input)?;\n                let tags = annotations.node_mut(node.id.into());\n                let mut comparison_error = None;\n                for slot in 0..returning.len() {\n                    let get_value = |label: &str| {\n                        all_values\n                            .get(&canonic(label))\n                            .and_then(|v| v.get(turn))\n                            .and_then(|r| r.as_ref().ok())\n                            .cloned()\n                    };\n                    let reference: Option<TValue> = tract\n                        .outlet_label((node.id, slot).into())\n                        .and_then(get_value)\n                        .or_else(|| get_value(&node.name).filter(|_| slot == 0));\n\n                    let Some(reference) = reference else {\n                        tags.style = Some(Yellow.into());\n                        unchecked.insert(node.id);\n                        continue;\n                    };\n\n                    let mut reference = reference.into_tensor();\n                    let clarified_fact = crate::utils::clarify_typed_fact(\n                        node.outputs[slot].fact.to_typed_fact().unwrap(),\n                    );\n                    let needed_type = clarified_fact.datum_type;\n                    let needed_shape =\n                        clarified_fact.shape.eval_to_usize(&session_state.resolved_symbols)?;\n\n                    if **needed_shape != *reference.shape() {\n                        let Ok(reshaped) = reference.clone().into_shape(&needed_shape) else {\n                            comparison_error = Some(format!(\"Incompatible shape on output {slot} reference is {reference:?}, model expects {:?}.\", needed_shape));\n                            tags.style = Some(Red.into());\n                            continue;\n                        };\n                        reference = reshaped;\n                    }\n\n                    if needed_type != reference.datum_type() {\n                        if needed_type.unquantized() == reference.datum_type().unquantized() {\n                            unsafe { reference.set_datum_type(needed_type) };\n                        } else if needed_type.is_float() && reference.datum_type().is_float() {\n                            reference = reference.cast_to_dt(needed_type)?.into_owned();\n                        } else {\n                            comparison_error = Some(format!(\"Incompatible type on output {slot} reference is {reference:?}, model expects {:?}.\", needed_type));\n                            tags.style = Some(Red.into());\n                            continue;\n                        }\n                    }\n\n                    let computed = crate::utils::clarify_tvalue(&returning[slot])?;\n\n                    if let Err(e) =\n                        computed.close_enough(&reference, params.assertions.approximation)\n                    {\n                        comparison_error = Some(\"Mismatch value\".to_string());\n                        let mut msg = vec![Red\n                            .bold()\n                            .paint(format!(\n                                \"At turn {turn}, wrong value for output {slot}, {e}\"\n                            ))\n                            .to_string()];\n                        msg.push(format!(\"{:<8}: {:?}\", labels.0, computed));\n                        msg.push(format!(\"{:<8}: {:?}\", labels.1, reference));\n                        tags.sections.push(msg);\n                    } else {\n                        debug!(\n                            \"At turn {}, matching value for {:?}\",\n                            turn,\n                            OutletId::new(node.id, slot)\n                        )\n                    }\n\n                    if !cumulative && returning[slot].is_plain() {\n                        returning[slot] = reference.into_tvalue();\n                    }\n                }\n                if let Some(e) = comparison_error {\n                    annotations.node_mut(node.id.into()).style = Some(Red.into());\n                    annotations.node_mut(node.id.into()).labels.push(e);\n                    failing.insert(node.id);\n                } else {\n                    ok += 1;\n                }\n                Ok(returning)\n            },\n        )?;\n    }\n    for node in tract.nodes() {\n        let color: nu_ansi_term::Style = if failing.contains(&node.id) {\n            Red.into()\n        } else if unchecked.contains(&node.id) {\n            White.into()\n        } else {\n            Green.bold()\n        };\n        annotations.node_mut(node.id.into()).style = Some(color);\n    }\n\n    if log_enabled!(Info) {\n        tract_libcli::terminal::render(tract.as_ref(), &annotations, output_params)?;\n    } else {\n        for f in failing.iter().sorted() {\n            tract_libcli::terminal::render_node(tract.as_ref(), *f, &annotations, output_params)?;\n        }\n    }\n\n    if failing.len() > 0 {\n        bail!(\"{} error(s).\", failing.len())\n    } else {\n        println!(\"{}\", Green.paint(format!(\"{ok} node(s) passed the comparison.\")));\n    };\n    Ok(())\n}\n"
  },
  {
    "path": "cli/src/cost.rs",
    "content": "use crate::TractResult;\nuse tract_hir::internal::*;\n\npub fn parse_costs(spec: &str) -> TractResult<Vec<(Cost, usize)>> {\n    spec.split(',')\n        .map(|spec| {\n            let mut toks = spec.split('=');\n            let name = toks.next().unwrap();\n            let n = toks.next().unwrap().parse::<usize>().unwrap();\n            let c = match name {\n                \"FMA(F32)\" => Cost::FMA(f32::datum_type()),\n                \"Div(F32)\" => Cost::Div(f32::datum_type()),\n                \"Buffer(F32)\" => Cost::Buffer(f32::datum_type()),\n                \"Params(F32)\" => Cost::Params(f32::datum_type()),\n                _ => bail!(\"Unknown cost specifier {}\", name),\n            };\n            Ok((c, n))\n        })\n        .collect()\n}\n"
  },
  {
    "path": "cli/src/dump.rs",
    "content": "use crate::Parameters;\nuse crate::params::SomeGraphDef;\nuse crate::plan_options::plan_options_from_subcommand;\nuse crate::tensor::run_params_from_subcommand;\nuse fs_err as fs;\nuse nu_ansi_term::Color::*;\n#[allow(unused_imports)]\nuse nu_ansi_term::Style;\nuse tract_core::ops::einsum::EinSum;\nuse tract_core::ops::matmul::optimized::{OptMatMul, ProtoFusedSpec};\nuse tract_core::ops::matmul::pack::DynPackedExoticFact;\nuse tract_core::ops::scan::OptScan;\n#[allow(unused_imports)]\n#[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\nuse tract_cuda::utils::ensure_cuda_runtime_dependencies;\nuse tract_hir::internal::*;\nuse tract_itertools::Itertools;\nuse tract_libcli::annotations::*;\nuse tract_libcli::display_params::*;\nuse tract_libcli::model::Model;\nuse tract_libcli::profile::BenchLimits;\nuse tract_libcli::tensor::get_or_make_inputs;\nuse tract_libcli::terminal;\nuse tract_linalg::block_quant::PackedBlockQuantFact;\nuse tract_linalg::mmm::PackedExoticFact;\n\n#[allow(unused_variables)]\npub fn annotate_with_graph_def(\n    annotations: &mut Annotations,\n    model: &dyn Model,\n    graph_def: &SomeGraphDef,\n) -> TractResult<()> {\n    match graph_def {\n        SomeGraphDef::NoGraphDef => Ok(()),\n        SomeGraphDef::Nnef(_) => todo!(),\n        #[cfg(feature = \"onnx\")]\n        SomeGraphDef::Onnx(onnx, _) => annotate_with_onnx_model(annotations, model, onnx),\n        #[cfg(feature = \"tf\")]\n        SomeGraphDef::Tf(tf) => annotate_with_tf_graph_def(annotations, model, tf),\n        #[cfg(feature = \"tflite\")]\n        SomeGraphDef::Tflite(tflite) => annotate_with_tflite_graph_def(annotations, model, tflite),\n    }\n}\n\n#[cfg(feature = \"tf\")]\nfn annotate_with_tf_graph_def(\n    annotations: &mut Annotations,\n    model: &dyn Model,\n    graph_def: &tract_tensorflow::tfpb::tensorflow::GraphDef,\n) -> TractResult<()> {\n    let bold = Style::new().bold();\n    for gnode in graph_def.node.iter() {\n        if let Ok(node_id) = model.node_id_by_name(&gnode.name) {\n            let mut v = vec![];\n            for a in gnode.attr.iter() {\n                let value =\n                    if let Some(tract_tensorflow::tfpb::tensorflow::attr_value::Value::Tensor(r)) =\n                        &a.1.value\n                    {\n                        format!(\"{r:?}\")\n                    } else {\n                        format!(\"{:?}\", a.1)\n                    };\n                v.push(format!(\"Attr {}: {:.300}\", bold.paint(a.0), value));\n            }\n            annotations.node_mut(node_id.into()).sections.push(v);\n        }\n    }\n    Ok(())\n}\n\n#[cfg(feature = \"tflite\")]\nfn annotate_with_tflite_graph_def(\n    _annotations: &mut Annotations,\n    _model: &dyn Model,\n    _graph_def: &tract_tflite::internal::TfliteProtoModel,\n) -> TractResult<()> {\n    Ok(())\n}\n\n#[cfg(feature = \"onnx\")]\nfn annotate_with_onnx_model(\n    annotations: &mut Annotations,\n    model: &dyn Model,\n    model_proto: &tract_onnx::pb::ModelProto,\n) -> TractResult<()> {\n    use tract_onnx::data_resolver::FopenDataResolver;\n    use tract_onnx::tensor::load_tensor;\n\n    let bold = Style::new().bold();\n    for gnode in model_proto.graph.as_ref().unwrap().node.iter() {\n        if let Some(id) = model\n            .node_id_by_name(&gnode.name)\n            .ok()\n            .or_else(|| gnode.output.first().and_then(|n| model.node_id_by_name(n).ok()))\n        {\n            let mut v = vec![];\n            for a in gnode.attribute.iter() {\n                let value = if let Some(t) = &a.t {\n                    format!(\"{:?}\", load_tensor(&FopenDataResolver, t, None)?)\n                } else {\n                    format!(\"{a:?}\")\n                };\n                v.push(format!(\"Attr {}: {:.240}\", bold.paint(&a.name), value));\n            }\n            annotations.node_mut(id.into()).sections.push(v);\n        }\n    }\n    Ok(())\n}\n\npub fn handle(\n    params: &Parameters,\n    options: &DisplayParams,\n    matches: &clap::ArgMatches,\n    sub_matches: &clap::ArgMatches,\n    bench_limits: &BenchLimits,\n    _inner: Vec<String>,\n) -> TractResult<()> {\n    let mut annotations = Annotations::from_model(&*params.tract_model)?;\n    annotate_with_graph_def(&mut annotations, &*params.tract_model, &params.graph)?;\n    let run_params = run_params_from_subcommand(params, sub_matches)?;\n    if options.cost || options.summary || options.audit_json {\n        tract_libcli::profile::extract_costs(\n            &mut annotations,\n            &*params.tract_model,\n            &run_params.symbols,\n        )?;\n    }\n    if options.summary {\n        terminal::render_summary(&*params.tract_model, &annotations)?;\n        return Ok(());\n    }\n    if options.profile {\n        let run_params = run_params_from_subcommand(params, sub_matches)?;\n        let inputs = get_or_make_inputs(&params.tract_model, &run_params)?;\n\n        if matches.get_flag(\"metal\") || matches.get_flag(\"cuda\") {\n            #[cfg(not(any(\n                target_os = \"macos\",\n                target_os = \"ios\",\n                target_os = \"linux\",\n                target_os = \"windows\"\n            )))]\n            bail!(\"GPU profiling is only supported on window, linux, and osx, ios\");\n\n            #[cfg(not(any(target_os = \"macos\", target_os = \"ios\")))]\n            {\n                #[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\n                ensure_cuda_runtime_dependencies(\"GPU profiling called on non-GPU device\")?;\n            }\n\n            #[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\n            let is_cuda = matches.get_flag(\"cuda\");\n            #[cfg(not(any(target_os = \"linux\", target_os = \"windows\")))]\n            let is_cuda = false;\n\n            if is_cuda {\n                #[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\n                tract_cuda::with_cuda_stream(|s| {\n                    s.enable_profiling();\n                    Ok(())\n                })?;\n            }\n\n            let before_node: Box<dyn Fn(usize)> = if is_cuda {\n                #[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\n                {\n                    Box::new(|node_id| {\n                        tract_cuda::with_cuda_stream(|s| {\n                            s.set_current_node(node_id);\n                            Ok(())\n                        })\n                        .ok();\n                    })\n                }\n                #[cfg(not(any(target_os = \"linux\", target_os = \"windows\")))]\n                Box::new(|_| {})\n            } else {\n                Box::new(|_| {})\n            };\n\n            let after_iteration: Box<\n                dyn Fn(\n                    &mut tract_libcli::annotations::Annotations,\n                    &[(usize, String)],\n                ) -> TractResult<()>,\n            > = if is_cuda {\n                #[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\n                {\n                    Box::new(|dg, prefix| {\n                        tract_cuda::with_cuda_stream(|s| {\n                            s.synchronize()?;\n                            if let Some(entries) = s.drain_profile() {\n                                for entry in entries {\n                                    let ms = entry.start.elapsed_ms(&entry.end)?;\n                                    let dur =\n                                        std::time::Duration::from_secs_f64(ms as f64 / 1000.0);\n                                    let node_id = tract_libcli::annotations::NodeQId(\n                                        prefix.into(),\n                                        entry.node_id,\n                                    );\n                                    *dg.node_mut(node_id)\n                                        .accelerator_profile\n                                        .get_or_insert(std::time::Duration::default()) += dur;\n                                }\n                            }\n                            Ok(())\n                        })\n                    })\n                }\n                #[cfg(not(any(target_os = \"linux\", target_os = \"windows\")))]\n                Box::new(|_, _| Ok(()))\n            } else {\n                Box::new(|_, _| Ok(()))\n            };\n\n            tract_libcli::profile::profile_gpu(\n                &params.req_runnable()?,\n                bench_limits,\n                sub_matches,\n                &mut annotations,\n                &inputs,\n                &*before_node,\n                &*after_iteration,\n            )?;\n        } else {\n            tract_libcli::profile::profile(\n                &params.req_runnable()?,\n                bench_limits,\n                &mut annotations,\n                &inputs,\n                None,\n                options.folded,\n            )?;\n        }\n    }\n\n    if sub_matches.get_flag(\"axes\") || sub_matches.contains_id(\"axes-names\") {\n        let mut hints = HashMap::default();\n        if let Some(names) = sub_matches.get_many::<String>(\"axes-names\") {\n            for param in names {\n                let param = param.as_str();\n                let (node, names) = if let Some((node, axes)) = param.split_once('=') {\n                    (params.tract_model.node_id_by_name(node)?, axes)\n                } else {\n                    (params.tract_model.input_outlets()[0].node, param)\n                };\n                let names: TVec<String> = names.split(',').map(|s| s.to_string()).collect();\n                hints.insert(OutletId::new(node, 0), names);\n            }\n        }\n        annotations.track_axes(&*params.tract_model, &hints)?;\n    }\n\n    if sub_matches.contains_id(\"memory-arena\") {\n        #[cfg(not(any(target_os = \"macos\", target_os = \"ios\")))]\n        {\n            ensure_cuda_runtime_dependencies(\n                \"Memory arena is only enabled for MacOS / iOS devices or CUDA devices\",\n            )?;\n        }\n        crate::memory_arena::dump_metrics(\n            &params.req_typed_model(),\n            &plan_options_from_subcommand(sub_matches)?,\n            std::path::Path::new(\n                sub_matches\n                    .get_one::<String>(\"memory-arena\")\n                    .ok_or(anyhow!(\"Path to JSON file required\"))?,\n            ),\n        )?;\n    }\n\n    if sub_matches.get_flag(\"tmp_mem_usage\") {\n        let plan_options = plan_options_from_subcommand(sub_matches)?;\n        annotations.track_tmp_memory_usage(\n            &*params.tract_model,\n            |n| !(n.op_is::<tract_core::ops::konst::Const>()),\n            plan_options.skip_order_opt_ram,\n        )?;\n    }\n\n    if let Some(asserts) = &params.assertions.assert_output_facts {\n        let outputs_facts: Vec<InferenceFact> = params\n            .tract_model\n            .output_outlets()\n            .iter()\n            .map(|o| Ok(InferenceFact::from(params.tract_model.outlet_typedfact(*o)?)))\n            .collect::<TractResult<Vec<InferenceFact>>>()?;\n        crate::utils::check_inferred(&outputs_facts, asserts)?;\n    }\n    if let Some(asserts) = &params.assertions.assert_op_count {\n        for (name, expected) in asserts {\n            let count = crate::utils::count_op(&*params.tract_model, name)?;\n            if count != *expected {\n                bail!(\"Wrong number of {} operators: expected {}, got {}\", name, expected, count);\n            }\n        }\n    }\n    if let Some(patterns) = &params.assertions.assert_op_only {\n        crate::utils::check_op_only(&*params.tract_model, patterns)?;\n    }\n\n    let compress_submodels = sub_matches.get_flag(\"compress-submodels\");\n    let deterministic = sub_matches.get_flag(\"nnef-deterministic\");\n    if let Some(path) = sub_matches.get_one::<String>(\"nnef\") {\n        let nnef = super::nnef(matches);\n        if let Some(typed) = params.typed_model().to_owned() {\n            let mut typed = Arc::unwrap_or_clone(typed);\n            rename_outputs(&mut typed, sub_matches)?;\n            let file = fs::File::create(path)?;\n            let encoder = flate2::write::GzEncoder::new(file, flate2::Compression::default());\n            nnef.write_to_tar_with_config(&typed, encoder, compress_submodels, deterministic)\n                .context(\"Writing model to tgz\")?;\n        } else {\n            bail!(\"Only typed model can be dumped\")\n        }\n    }\n\n    if let Some(path) = sub_matches.get_one::<String>(\"nnef-tar\") {\n        let nnef = super::nnef(matches);\n        if let Some(typed) = params.typed_model().to_owned() {\n            let mut typed = Arc::unwrap_or_clone(typed);\n            rename_outputs(&mut typed, sub_matches)?;\n            let file = fs::File::create(path)?;\n            nnef.write_to_tar_with_config(&typed, file, compress_submodels, deterministic)\n                .context(\"Writing model to tar\")?;\n        } else {\n            bail!(\"Only typed model can be dumped\")\n        }\n    }\n\n    if let Some(path) = sub_matches.get_one::<String>(\"nnef-dir\") {\n        let nnef = super::nnef(matches);\n        if let Some(typed) = params.typed_model().to_owned() {\n            let mut typed = Arc::unwrap_or_clone(typed);\n            rename_outputs(&mut typed, sub_matches)?;\n            if let Some(renamed) = sub_matches.get_many::<String>(\"nnef-override-output-name\") {\n                for (ix, name) in renamed.enumerate() {\n                    let output = typed.wire_node(\n                        name.as_str(),\n                        tract_core::ops::identity::Identity,\n                        &[typed.output_outlets()?[ix]],\n                    )?;\n                    typed.outputs[ix] = output[0];\n                }\n            }\n            nnef.write_to_dir(&typed, path)?\n        } else {\n            bail!(\"Only typed model can be dumped\")\n        }\n    }\n\n    if let Some(path) = sub_matches.get_one::<String>(\"nnef-graph\") {\n        let nnef = super::nnef(matches);\n        if let Some(typed) = params.typed_model().to_owned() {\n            let mut typed = Arc::unwrap_or_clone(typed);\n            rename_outputs(&mut typed, sub_matches)?;\n            let proto = tract_nnef::ser::to_proto_model(&nnef, &typed)?;\n            if path == \"-\" {\n                tract_nnef::ast::dump::Dumper::new(&nnef, &mut std::io::stdout())\n                    .document(&proto.doc)?;\n            } else {\n                let mut file = fs::File::create(path)?;\n                tract_nnef::ast::dump::Dumper::new(&nnef, &mut file).document(&proto.doc)?;\n            }\n        } else {\n            bail!(\"Only typed model can be dumped\")\n        }\n    }\n\n    #[cfg(feature = \"tflite\")]\n    if let Some(path) = sub_matches.get_one::<String>(\"tflite\") {\n        let tflite = tract_tflite::tflite();\n        if let Some(typed) = params.typed_model().to_owned() {\n            let mut typed = Arc::unwrap_or_clone(typed);\n            rename_outputs(&mut typed, sub_matches)?;\n            let file = fs::File::create(path)?;\n            tflite.write(&typed, file).context(\"Writing model to tflite\")?;\n        } else {\n            bail!(\"Only typed model can be dumped\")\n        }\n    }\n\n    #[cfg(not(feature = \"tflite\"))]\n    if sub_matches.get_one::<String>(\"tflite\").is_some() {\n        bail!(\"This is a tract build without support for tflite.\")\n    }\n\n    if options.cost {\n        let total = annotations.tags.values().sum::<NodeTags>();\n        let assert = sub_matches\n            .get_one::<String>(\"assert-cost\")\n            .map(|s| crate::cost::parse_costs(s))\n            .transpose()?;\n        if let Some(assert) = assert {\n            let assert: HashMap<Cost, TDim> =\n                assert.iter().map(|(c, n)| (c.clone(), n.to_dim())).collect();\n            let total = total.cost.iter().cloned().collect::<HashMap<_, _>>();\n            if assert != total {\n                bail!(\"Cost assertion not met: expected {:?} got {:?}\", assert, total);\n            }\n        }\n    }\n\n    if params\n        .tract_model\n        .properties()\n        .get(\"tract_stage\")\n        .and_then(|t| t.try_as_plain().ok()?.to_scalar::<String>().ok().cloned())\n        .is_some_and(|s| s == \"optimized\")\n    {\n        for n in 0..params.tract_model.nodes_len() {\n            if params.tract_model.node_op_name(n) == \"EinSum\" {\n                let tags = annotations.tags.entry(NodeQId(tvec!(), n)).or_default();\n                tags.style = Some(Red.bold());\n                tags.labels.push(\"⚠️⚠️⚠️ EinSum in optimised model\".to_string());\n            }\n        }\n    }\n\n    if options.audit_json {\n        tract_libcli::export::audit_json(&*params.tract_model, &annotations, std::io::stdout())?;\n    } else if options.json {\n        let export = tract_libcli::export::GraphPerfInfo::from(&*params.tract_model, &annotations);\n        serde_json::to_writer(std::io::stdout(), &export)?;\n    } else {\n        terminal::render(&*params.tract_model, &annotations, options)?;\n        terminal::render_summaries(&*params.tract_model, &annotations, options)?;\n    }\n\n    if options.mm {\n        mm_report(params, options, matches, sub_matches)?;\n    }\n\n    Ok(())\n}\n\nfn rename_outputs(typed: &mut TypedModel, sub_matches: &clap::ArgMatches) -> TractResult<()> {\n    if let Some(renamed) = sub_matches.get_many::<String>(\"nnef-override-output-name\") {\n        for (ix, name) in renamed.enumerate() {\n            let output = typed.wire_node(\n                name.as_str(),\n                tract_core::ops::identity::Identity,\n                &[typed.output_outlets()?[ix]],\n            )?;\n            typed.outputs[ix] = output[0];\n        }\n    }\n    Ok(())\n}\npub fn mm_report(\n    params: &Parameters,\n    _options: &DisplayParams,\n    _matches: &clap::ArgMatches,\n    _sub_matches: &clap::ArgMatches,\n) -> TractResult<()> {\n    println!(\"{}\", White.bold().paint(\"# Matrix multiplication\"));\n    let Some(model) = params.tract_model.downcast_ref::<TypedModel>() else {\n        println!(\"Only available on TypedModel\");\n        return Ok(());\n    };\n    let count = model.nodes.iter().filter(|n| n.op_is::<OptMatMul>()).count();\n    println!(\"* {count} matrix multiplications\");\n\n    type EinsumConf<'m> = (String, String, String);\n    type MatMulConf = (TDim, TDim, TDim, TDim, bool, String, String, String, String);\n\n    let mut einsums = HashMap::<EinsumConf, TDim>::new();\n    let mut opt_mat_muls = HashMap::<MatMulConf, TDim>::new();\n\n    fn scan_model<'m>(\n        model: &'m TypedModel,\n        einsums: &mut HashMap<EinsumConf<'m>, TDim>,\n        opt_mat_muls: &mut HashMap<MatMulConf, TDim>,\n        mult: &TDim,\n    ) -> TractResult<()> {\n        for (n, op) in model.nodes.iter().filter_map(|n| n.op_as::<EinSum>().map(|m| (n, m))) {\n            let it = (\n                op.axes.to_string(),\n                model\n                    .node_input_facts(n.id)?\n                    .iter()\n                    .map(|f| format!(\"{:?}\", f.without_value()))\n                    .join(\" • \"),\n                model\n                    .node_output_facts(n.id)?\n                    .iter()\n                    .map(|f| format!(\"{:?}\", f.without_value()))\n                    .join(\" • \"),\n            );\n            *einsums.entry(it).or_default() += mult;\n        }\n        for (node, op) in model.nodes.iter().filter_map(|n| n.op_as::<OptMatMul>().map(|m| (n, m)))\n        {\n            let (m, k, n) = (op.m().clone(), op.guess_k().unwrap_or(TDim::Val(0)), op.n().clone());\n            let facts = model.node_input_facts(node.id)?;\n            let packings = op\n                .micro_ops\n                .iter()\n                .find_map(|mo| {\n                    if let ProtoFusedSpec::AddMatMul { packings, .. } = mo {\n                        Some(packings.clone())\n                    } else {\n                        None\n                    }\n                })\n                .unwrap();\n            let panel_extractor = packings\n                .iter()\n                .map(|(_, repack)| {\n                    repack.as_ref().map(|rp| rp.to_string()).unwrap_or(\"Ø\".to_string())\n                })\n                .join(\", \");\n            let (pack_a, pack_b) = facts\n                .iter()\n                .take(2)\n                .map(|fact| {\n                    fact.exotic_fact\n                        .as_ref()\n                        .and_then(|of| {\n                            of.downcast_ref::<DynPackedExoticFact>()\n                                .map(|of| of.packers.iter().map(|m| format!(\"{m}\")).join(\", \"))\n                                .or_else(|| {\n                                    of.downcast_ref::<PackedExoticFact>()\n                                        .map(|pof| format!(\"{}\", pof.format))\n                                })\n                                .or_else(|| {\n                                    of.downcast_ref::<PackedBlockQuantFact>()\n                                        .map(|pof| format!(\"{}\", pof.format))\n                                })\n                        })\n                        .unwrap_or_else(|| format!(\"{fact:?}\"))\n                    //                        .unwrap_or_default()\n                })\n                .collect_tuple()\n                .unwrap();\n            let iters = op\n                .c_fact\n                .shape\n                .iter()\n                .enumerate()\n                .filter(|(ix, _dim)| Some(*ix) != op.c_m_axis && Some(*ix) != op.c_n_axis)\n                .map(|(_ix, d)| d)\n                .product::<TDim>();\n            let mmm = op.mmm.iter().map(|m| format!(\"{m:?}\")).join(\", \");\n            *opt_mat_muls\n                .entry((\n                    m,\n                    k,\n                    n,\n                    iters * mult,\n                    facts[0].konst.is_some(),\n                    mmm,\n                    pack_a,\n                    panel_extractor,\n                    pack_b,\n                ))\n                .or_default() += mult;\n        }\n        for (node, op) in model.nodes.iter().filter_map(|n| n.op_as::<OptScan>().map(|o| (n, o))) {\n            let inputs = model.node_input_facts(node.id)?;\n            let iters = &op.nested_model_multipliers(&inputs)[0].1;\n            scan_model(op.plan.model(), einsums, opt_mat_muls, &(mult.clone() * iters))?;\n        }\n        Ok(())\n    }\n    scan_model(model, &mut einsums, &mut opt_mat_muls, &1.to_dim())?;\n\n    let mmm_width = opt_mat_muls.keys().map(|cf| cf.5.len()).max().unwrap_or(0);\n    let pa_width = opt_mat_muls.keys().map(|cf| cf.6.len()).max().unwrap_or(0);\n    let panel_width = opt_mat_muls.keys().map(|cf| cf.7.len()).max().unwrap_or(0);\n    let pb_width = opt_mat_muls.keys().map(|cf| cf.8.len()).max().unwrap_or(0);\n    println!(\n        \"| count |     |     m |     k |     n | iters | {:^mmm_width$} | {:^pa_width$} | {:^panel_width$} | {:^pb_width$} |\",\n        \"kernels\", \"packing a\", \"panel\", \"packing b\",\n    );\n    for (config, count) in opt_mat_muls.iter().sorted_by_key(|(conf, count)| {\n        (-(count.to_isize().unwrap_or_default()), -(conf.0.as_i64().unwrap_or(0)))\n    }) {\n        let (m, k, n, iters, w, mmm, pa, panel, pb) = config;\n        println!(\n            \"| {:>5} | {} | {:>5} | {:>5} | {:>5} | {:>5} | {mmm:^mmm_width$} | {pa:^pa_width$} | {panel:^panel_width$} | {pb:^pb_width$} |\",\n            count.to_string(),\n            if *w { \"   \" } else { \"X•Y\" },\n            m.to_string(),\n            k.to_string(),\n            n.to_string(),\n            iters.to_string()\n        );\n    }\n    if einsums.len() > 0 {\n        println!(\"{}\", Red.bold().paint(\"# 💩💩💩 Unoptimized Einsums 💩💩💩\"));\n        for ((axes, ifacts, ofacts), count) in\n            einsums.iter().sorted_by_key(|(_conf, count)| -count.as_i64().unwrap_or_default())\n        {\n            println!(\n                \"{}\",\n                Red.bold().paint(format!(\n                    \"| {:>5} | {axes:^20} | {ifacts} => {ofacts}\",\n                    count.to_string(),\n                ))\n            )\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "cli/src/hwbench.rs",
    "content": "use nu_ansi_term::Color::*;\nuse tract_core::prelude::*;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_libcli::terminal::si_prefix;\nuse tract_linalg::hwbench::bandwidth::{l1_bandwidth_seq, main_memory_bandwith_seq};\nuse tract_linalg::hwbench::runner::run_bench;\nuse tract_linalg::mmm::{AsInputValue, FusedSpec};\n\npub(crate) fn handle() -> TractResult<()> {\n    println!(\"# Cores\");\n    println!(\"cpus: {}\", num_cpus::get());\n    println!(\"physical cpus: {}\", num_cpus::get_physical());\n    println!();\n\n    if let Ok(cpuinfo) = std::fs::read_to_string(\"/proc/cpuinfo\") {\n        println!(\"# Excerpt from /proc/cpuinfo\");\n        for line in cpuinfo.lines() {\n            if line.is_empty() {\n                break;\n            }\n            if [\"model name\", \"cache size\", \"bogomips\", \"BogoMIPS\", \"Features\", \"CPU\", \"flags\"]\n                .iter()\n                .any(|needle| line.starts_with(needle))\n            {\n                println!(\" * {line}\");\n            }\n        }\n        println!();\n\n        if let Some(flags) = cpuinfo\n            .lines()\n            .find(|line| line.starts_with(\"flags\") || line.starts_with(\"Features\"))\n            .and_then(|l| l.split_once(\":\"))\n            .map(|pair| pair.1)\n        {\n            print!(\"# Relevant CPU flags/features: \");\n            for flag in flags.split_whitespace() {\n                if [\"fpu\", \"sse\", \"avx\", \"f16\", \"fma\", \"fp\", \"asimd\", \"neon\", \"vfp\"]\n                    .iter()\n                    .any(|needle| flag.starts_with(needle))\n                {\n                    print!(\"{flag} \")\n                };\n            }\n            println!(\"\\n\");\n        }\n    }\n\n    #[cfg(target_arch = \"aarch64\")]\n    {\n        println!(\n            \"# Aarch64 subfamily detected by tract-linalg: {:?}\\n\",\n            tract_linalg::arm64::Kind::choose()\n        );\n    }\n\n    println!(\"# Cache\");\n    let mut threads = (1..=num_cpus::get()).collect_vec();\n    for extra in [1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 4.0] {\n        let value = (num_cpus::get() * (extra * 4.) as usize) / 4;\n        if !threads.contains(&value) {\n            threads.push(value);\n        }\n    }\n    for &t in &threads {\n        let m = l1_bandwidth_seq(t);\n        println!(\n            \"{t:2}-thread L1 : {} — {}\",\n            si_prefix(m, \"B/s\"),\n            si_prefix(m / t as f64, \"B/s/thread\"),\n        );\n    }\n\n    println!(\"\\n# Main memory\");\n    for &t in &threads {\n        let measured = main_memory_bandwith_seq(t);\n        println!(\n            \"{t:2}-thread L∞ : {} — {}\",\n            si_prefix(measured, \"B/s\"),\n            si_prefix(measured / t as f64, \"B/s/thread\")\n        );\n    }\n    println!();\n\n    let big = if cfg!(target_arch = \"arm\") { 128 } else { 512 };\n    mmm(f32::datum_type(), big, big, big)?;\n    mmm(f32::datum_type(), big, big, 1)?;\n    mmm(f16::datum_type(), big, big, big)?;\n    mmm(f16::datum_type(), big, big, 1)?;\n\n    Ok(())\n}\n\nfn mmm(dt: DatumType, m: usize, k: usize, n: usize) -> TractResult<()> {\n    let a = Tensor::zero_dt(dt, &[m, k])?;\n    let b = Tensor::zero_dt(dt, &[k, n])?;\n    let mut c = Tensor::zero_dt(dt, &[m, n])?;\n    let selection = tract_linalg::ops().mmm(dt, Some(m), Some(k), Some(n));\n    println!(\"# Matmul {m}x{k}x{n}x{dt:?}\\n\");\n    let mmms = tract_linalg::ops().mmm_impls();\n    unsafe {\n        mmms.iter()\n            .flat_map(|mmm| {\n                mmm.packings().iter().enumerate().map(move |(pix, (pa, pb))| (mmm, pix, pa, pb))\n            })\n            .filter(|(_mmm, _pix, pa, pb)| {\n                pa.precursor().as_dt() == Some(dt) && pb.precursor().as_dt() == Some(dt)\n            })\n            .map(|(mmm, pix, pa, pb)| {\n                if atty::is(atty::Stream::Stderr) {\n                    eprint!(\"Benching {} ({pix})\", mmm.name());\n                }\n                let a = pa.prepare_one(&a, 1, 0).unwrap();\n                let b = pb.prepare_one(&b, 0, 1).unwrap();\n                let pc = mmm.c_view(Some(0), Some(1)).wrap(&c.view_mut());\n                let time = run_bench(|loops| {\n                    let mut scratch = mmm.allocate_scratch_space();\n                    for _ in 0..loops {\n                        mmm.run_with_scratch_space(\n                            m,\n                            n,\n                            scratch.as_mut(),\n                            &[\n                                FusedSpec::AddMatMul {\n                                    a: AsInputValue::Borrowed(&*a),\n                                    b: AsInputValue::Borrowed(&*b),\n                                    packing: 0,\n                                },\n                                FusedSpec::Store(pc),\n                            ],\n                        )\n                        .unwrap();\n                    }\n                });\n                if atty::is(atty::Stream::Stderr) {\n                    eprint!(\"\\x1B[2K\\r\"); // clear current line + CR\n                }\n                let flops = (m * k * n) as f64 / time;\n                (mmm, pix, pa, pb, flops)\n            })\n            .sorted_by_key(|(_mmm, _pix, _pa, _pb, flops)| -(*flops as i64))\n            .for_each(|(mmm, pix, pa, pb, flops)| {\n                print!(\"{:>35} {:30}\", format!(\"{mmm:?} ({pix})\"), format!(\"{pa} • {pb}\"));\n                let color = if flops.log10() > 9.0 {\n                    Green\n                } else if flops.log10() > 6.0 {\n                    Yellow\n                } else {\n                    LightRed\n                };\n                println!(\n                    \" {} {}\",\n                    color.paint(si_prefix(flops, \"flop/s\")),\n                    if pix == 0 && Some(mmm) == selection.as_ref() { \"<--\" } else { \"\" }\n                );\n            });\n    }\n    println!();\n\n    Ok(())\n}\n"
  },
  {
    "path": "cli/src/llm.rs",
    "content": "use crate::Parameters;\nuse float_ord::FloatOrd;\nuse readings_probe::Probe;\nuse std::time::{Duration, Instant};\nuse tract_core::num_traits::Zero;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_hir::internal::*;\nuse tract_libcli::profile::BenchLimits;\nuse tract_libcli::tensor::get_or_make_inputs;\n#[cfg(feature = \"transformers\")]\nuse tract_transformers::figure_out_causal_llm_b_s_p;\n\npub fn handle(\n    params: &Parameters,\n    matches: &clap::ArgMatches,\n    sub_matches: &clap::ArgMatches,\n    limits: &BenchLimits,\n    probe: Option<&Probe>,\n) -> TractResult<()> {\n    bench_pp(params, matches, sub_matches, limits, 512, probe)?;\n    bench_tg(params, matches, sub_matches, limits, 128, probe)?;\n    Ok(())\n}\n\npub fn bench_pp(\n    params: &Parameters,\n    _matches: &clap::ArgMatches,\n    sub_matches: &clap::ArgMatches,\n    limits: &BenchLimits,\n    pp: usize,\n    _probe: Option<&Probe>,\n) -> TractResult<()> {\n    let mut run_params = crate::tensor::run_params_from_subcommand(params, sub_matches)?;\n    run_params.allow_random_input = true;\n    let model = params.req_typed_model();\n\n    let (b, s, p) = tract_transformers::figure_out_causal_llm_b_s_p(&model)\n        .context(\"Could not find out LLM symbolic parameters\")?;\n    if let Some(b) = b {\n        run_params.symbols.set(&b, 1);\n    }\n\n    ensure!(s.is_some() && p.is_some(), \"Could not find LLM symbols in model\");\n    // Warmup\n    run_params.symbols.set(&p.unwrap(), 0);\n    run_params.symbols.set(&s.unwrap(), pp as i64);\n    let inputs = get_or_make_inputs(&params.tract_model, &run_params)?;\n    limits.warmup(&params.req_runnable()?, &inputs)?;\n\n    let inputs = get_or_make_inputs(&params.tract_model, &run_params)?;\n\n    let (iters, dur) = limits.bench(&params.req_runnable()?, &inputs)?;\n    let tokens = pp as f64 / dur.as_secs_f64() * iters as f64;\n    println!(\"PP{pp}: {tokens:.1} tokens/sec\");\n    Ok(())\n}\n\npub fn bench_tg(\n    params: &Parameters,\n    _matches: &clap::ArgMatches,\n    sub_matches: &clap::ArgMatches,\n    limits: &BenchLimits,\n    tg: usize,\n    probe: Option<&Probe>,\n) -> TractResult<()> {\n    let mut run_params = crate::tensor::run_params_from_subcommand(params, sub_matches)?;\n    run_params.allow_random_input = true;\n    let model = params.req_typed_model();\n\n    let (b, s, p) = figure_out_causal_llm_b_s_p(&model)\n        .context(\"Could not find out LLM symbolic parameters\")?;\n    if let Some(b) = b {\n        run_params.symbols.set(&b, 1);\n    }\n\n    ensure!(s.is_some() && p.is_some(), \"Could not find LLM symbols in model\");\n    run_params.symbols.set(&s.unwrap(), 1);\n\n    let p = p.unwrap();\n    // Warmup\n    if !limits.warmup_loops.is_zero() || !limits.warmup_time.is_zero() {\n        let mut iters = 0;\n        let max_loops =\n            if limits.warmup_loops.is_zero() { usize::MAX } else { limits.warmup_loops };\n        let max_time =\n            if limits.warmup_time.is_zero() { Duration::MAX } else { limits.warmup_time };\n        let start_warmup = Instant::now();\n        info!(\"TG warming before profiling...\");\n        while iters < max_loops && start_warmup.elapsed() < max_time {\n            let mut state = params.req_runnable()?.spawn()?;\n            for t in 0..tg {\n                run_params.symbols.set(&p, t as i64);\n                let mut inputs = get_or_make_inputs(&params.tract_model, &run_params)?;\n                state.run(inputs.sources.remove(0))?;\n            }\n            iters += 1;\n        }\n        info!(\"Done warming up.\");\n    }\n\n    // Bench\n    let mut tot_dur = Duration::default();\n    let mut state = params.req_runnable()?.spawn()?;\n    for t in 0..tg {\n        if let Some(p) = probe {\n            p.log_event(&format!(\"Starting token {t}\"))?;\n        }\n\n        run_params.symbols.set(&p, t as i64);\n        let mut inputs = get_or_make_inputs(&params.tract_model, &run_params)?;\n\n        let start = Instant::now();\n        state.run(inputs.sources.remove(0))?;\n        tot_dur += start.elapsed();\n    }\n    let tokens = tg as f64 / tot_dur.as_secs_f64();\n    println!(\"TG{tg}: {tokens:.1} tokens/sec\");\n    Ok(())\n}\n\npub fn top_logits_rbo(test: &Tensor, reference: &Tensor, p: f64, depth: usize) -> TractResult<f64> {\n    use std::collections::HashSet;\n\n    let rankings: Vec<Vec<usize>> = [test, reference]\n        .into_iter()\n        .map(|t| {\n            t.cast_to::<f32>()\n                .unwrap()\n                .try_as_plain()\n                .unwrap()\n                .as_slice::<f32>()\n                .unwrap()\n                .iter()\n                .copied()\n                .enumerate()\n                .sorted_by_key(|(_, f)| FloatOrd(-*f))\n                .map(|p| p.0)\n                .collect_vec()\n        })\n        .collect();\n\n    let a = &rankings[0];\n    let b = &rankings[1];\n    let k = depth.min(a.len()).min(b.len());\n\n    let mut set_a: HashSet<usize> = HashSet::new();\n    let mut set_b: HashSet<usize> = HashSet::new();\n    let mut rbo = 0.0;\n\n    for d in 1..=k {\n        set_a.insert(a[d - 1]);\n        set_b.insert(b[d - 1]);\n        let overlap = set_a.intersection(&set_b).count() as f64 / d as f64;\n        rbo += p.powi((d as i32) - 1) * overlap;\n    }\n\n    let top1_match = a[0] == b[0];\n    let top5_overlap = {\n        let sa: HashSet<usize> = a[..5.min(k)].iter().copied().collect();\n        let sb: HashSet<usize> = b[..5.min(k)].iter().copied().collect();\n        sa.intersection(&sb).count()\n    };\n    debug!(\"RBO detail: top1_match={top1_match} top5_overlap={top5_overlap}/5\");\n\n    Ok((1.0 - p) * rbo)\n}\n"
  },
  {
    "path": "cli/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! dispatch_model {\n    ($model: expr, $expr: expr) => {\n        (|model: &Arc<dyn Model>| {\n            if let Ok(m) = Arc::downcast::<tract_hir::prelude::InferenceModel>(model.clone()) {\n                return $expr(m);\n            }\n            if let Ok(m) = Arc::downcast::<tract_hir::prelude::TypedModel>(model.clone()) {\n                return $expr(m);\n            }\n            #[cfg(feature = \"pulse\")]\n            {\n                if let Ok(m) = Arc::downcast::<PulsedModel>(model.clone()) {\n                    return $expr(m);\n                }\n            }\n            unreachable!()\n        })($model)\n    };\n}\n\n#[macro_export]\nmacro_rules! dispatch_model_no_pulse {\n    ($model: expr, $expr: expr) => {\n        (|model: &Arc<dyn Model>| {\n            if let Ok(m) = Arc::downcast::<tract_hir::prelude::InferenceModel>(model.clone()) {\n                return $expr(m);\n            }\n            if let Ok(m) = Arc::downcast::<tract_hir::prelude::TypedModel>(model.clone()) {\n                return $expr(m);\n            }\n            bail!(\"Pulse model are unsupported here\")\n        })($model)\n    };\n}\n"
  },
  {
    "path": "cli/src/main.rs",
    "content": "#![allow(clippy::len_zero)]\n#![allow(clippy::redundant_closure_call)]\n#![allow(clippy::collapsible_if)]\n#[macro_use]\nextern crate log;\n\n#[macro_use]\nmod macros;\npub(crate) mod runtimes;\n\n#[allow(unused_imports)]\nuse tract_itertools::Itertools;\n\nuse tract_core::internal::*;\nuse tract_hir::internal::*;\n\nuse nu_ansi_term::Color::*;\nuse tract_libcli::annotations::Annotations;\nuse tract_libcli::display_params::DisplayParams;\nuse tract_libcli::model::Model;\nuse tract_libcli::profile::BenchLimits;\n\nuse fs_err as fs;\nuse readings_probe::*;\n\nmod bench;\nmod compare;\nmod cost;\nmod dump;\nmod hwbench;\n#[cfg(feature = \"transformers\")]\nmod llm;\nmod memory_arena;\nmod params;\nmod plan_options;\nmod run;\nmod tensor;\nmod utils;\n\nuse params::*;\nuse tract_linalg::WeightType;\nuse tract_linalg::block_quant::Q4_0;\nuse tract_linalg::mmm::MatMatMul;\n\nreadings_probe::instrumented_allocator!();\n\npub const QUALITY_COLORS: [nu_ansi_term::Color; 5] = [LightGreen, Green, White, Yellow, LightRed];\n\nfn info_usage(stage: &str, probe: Option<&Probe>) {\n    if let Some(mon) = probe {\n        let _ = mon.log_event(stage);\n    }\n    if log::log_enabled!(log::Level::Info) {\n        let usage = readings_probe::get_os_readings().unwrap();\n        let allocated = readings_probe::alloc::ALLOCATED.load(std::sync::atomic::Ordering::Relaxed);\n        let freeed = readings_probe::alloc::FREEED.load(std::sync::atomic::Ordering::Relaxed);\n        info!(\n            \"Resource usage {}: vsz:{} rsz:{} rszmax:{} alloc:{}\",\n            stage,\n            usage.virtual_size,\n            usage.resident_size,\n            usage.resident_size_max,\n            allocated.saturating_sub(freeed),\n        );\n    }\n}\n\npub const STAGES: &[&str] = &[\n    \"load\",\n    \"analyse\",\n    \"incorporate\",\n    \"type\",\n    \"declutter\",\n    \"pulse\",\n    \"pulse-to-type\",\n    \"pulse-declutter\",\n    \"set\",\n    \"set-declutter\",\n    \"nnef-cycle\",\n    \"nnef-cycle-declutter\",\n    \"tflite-cycle-predump\",\n    \"tflite-cycle\",\n    \"tflite-cycle-declutter\",\n    \"before-optimize\",\n    \"optimize\",\n];\n\n/// Entrypoint for the command-line interface.\nfn main() -> TractResult<()> {\n    use clap::*;\n    let mut app = command!()\n        .allow_hyphen_values(true)\n        .arg(arg!(--readings \"Start readings instrumentation\"))\n        .arg(arg!(--\"readings-heartbeat\" [MS] \"Heartbeat for readings background collector\").default_value(\"5\"))\n        .arg(arg!(verbose: -v ... \"Sets the level of verbosity.\").action(clap::ArgAction::Count))\n        .arg(arg!(--\"keep-last\" \"Keep last model alive to dump if there is an error\"))\n        .arg(arg!([model] \"Sets the model to use\").required(false))\n        .arg(arg!(-f --format [format]\n                  \"Hint the model format ('onnx', 'nnef', 'tflite' or 'tf') instead of guess from extension.\"))\n        .arg(Arg::new(\"input\").long(\"input\").short('i').num_args(1).action(clap::ArgAction::Append).long_help(\n                \"Set input shape and type (@file.pb or @file.npz:thing.npy or 3,4,i32).\"))\n        .arg(Arg::new(\"constantize\").long(\"constantize\").num_args(1).action(clap::ArgAction::Append).long_help(\n                \"Transorm an input into a Constant\"))\n\n        .arg(arg!(--\"assert\").num_args(1).action(clap::ArgAction::Append).long_help(\"Adds a TDim pre-condition (prefix by optional \\\"scenario_name:\\\")\"))\n        .arg(arg!(--\"scenario\").num_args(1).action(clap::ArgAction::Append).long_help(\"Adds a scenario\"))\n\n        // deprecated\n        .arg(arg!(--\"input-bundle\" [input_bundle] \"Path to an input container (.npz). This sets input facts and tensor values.\").hide(true))\n        // deprecated\n        .arg(arg!(--\"allow-random-input\" \"Will use random generated input\").hide(true))\n\n        .arg(arg!(--\"input-facts-from-bundle\" [input_bundle] \"Path to an input container (.npz). This only sets input facts.\"))\n\n        .arg(arg!(--\"onnx-test-data-set\" [data_set] \"Use onnx-test data-set as input (expect test_data_set_N dir with input_X.pb, etc. inside)\"))\n        .arg(arg!(--\"onnx-ignore-output-shapes\" \"Ignore output shapes from model (workaround for pytorch export bug with mask axes)\"))\n        .arg(arg!(--\"onnx-ignore-output-types\" \"Ignore output shapes from types (workaround for tdim conflicting with integer types)\"))\n        .arg(arg!(--\"onnx-ignore-value-info\" \"Ignore value info from ONNX file while loading network\"))\n\n        .arg(arg!(--\"input-node\" [node] ... \"Override input nodes names (auto-detects otherwise).\"))\n        .arg(Arg::new(\"output-node\").long(\"output-node\").num_args(1).action(clap::ArgAction::Append).long_help(\n                \"Override output nodes by name.\"))\n        .arg(arg!(--\"label-wires\" \"Propagate node labels to wires\"))\n\n        .arg(arg!(--\"override-fact\" [fact] \"Override a fact.\"))\n\n        .arg(arg!(--\"analyse-fail-fast\" \"Stop analyse at first error.\"))\n        .arg(arg!(--recursive \"Apply to sub graphes\"))\n        .arg(arg!(--proto \"Keep proto model around after parse\"))\n        .arg(arg!(--determinize \"Enforce a seed in random operator\"))\n        .arg(arg!(--partial \"Before analyse, eliminate dead branches\"))\n\n        .arg(arg!(--pass [STAGE] \"Pass to stop preprocessing after.\").value_parser(clap::builder::PossibleValuesParser::new(STAGES)))\n        .arg(arg!(--\"declutter-step\" [STEP] \"Stop decluttering process after application of patch number N\"))\n        .arg(arg!(--\"declutter-set-step\" [STEP] \"Stop decluttering process (the one after --set application) at patch number N\"))\n        .arg(arg!(--\"optimize-step\" [STEP] \"Stop optimizing process after application of patch number N\"))\n        .arg(arg!(--\"extract-decluttered-sub\" [SUB] \"Zoom on a subgraph after decluttering by parent node name\"))\n\n        .arg(arg!(--\"metal\").long_help(\"Convert supported operators to Metal GPU equivalent. Only available on MacOS and iOS\"))\n        .arg(Arg::new(\"force-metal-backend\").long(\"force-metal-backend\").num_args(1).long_help(\"Force specific implementations for MM kernels. Possible values: mlx, ggml, mfa. Backend is dynamically selected if option is not present\"))\n        .arg(arg!(--\"cuda\").long_help(\"Convert supported operators to CUDA equivalent\"))\n        .arg(arg!(-r --runtime [runtime] \"Run on alternative runtime (cuda, metal, ...)\"))\n        .arg(Arg::new(\"transform\").short('t').long(\"transform\").num_args(1).action(clap::ArgAction::Append).help(\"Apply a built-in transformation to the model\"))\n        .arg(Arg::new(\"set\").long(\"set\").num_args(1).action(clap::ArgAction::Append).long_help(\"Set a symbol to a concrete value after decluttering\"))\n        .arg(Arg::new(\"hint\").long(\"hint\").num_args(1).action(clap::ArgAction::Append).long_help(\"Provide a typical value to a symbol to be used during planning (--hint S=12)\"))\n\n        .arg(arg!(--\"causal-llm-hints\" \"Figures out P and S and gives them suitable hints\"))\n        .arg(arg!(--llm \"Shortcut setting --opl (aka all nnef extensions) --causal-llm-hints -t transformers_detect_all\"))\n        // deprecated\n        .arg(arg!(--\"allow-float-casts\" \"Allow casting between f16, f32 and f64 around model\").hide(true))\n\n        .arg(arg!(--\"nnef-cycle\" \"Perform NNEF dump and reload before optimizing\"))\n        .arg(arg!(--\"tflite-cycle\" \"Perform TFLITE dump and reload before optimizing\"))\n\n        .arg(arg!(--\"no-nnef-tract-core\" \"Disable usage of tract-core extension in NNEF dump and load\"))\n        .arg(arg!(--\"nnef-tract-core\" \"Allow usage of tract-core extension in NNEF dump and load\")).hide(true)\n        .arg(arg!(--\"nnef-tract-resource\" \"Allow usage of tract-resource extension in NNEF dump and load\"))\n        .arg(arg!(--\"nnef-tract-onnx\" \"Allow usage of tract-onnx extension in NNEF dump and load\"))\n        .arg(arg!(--\"nnef-tract-pulse\" \"Allow usage of tract-pulse extension in NNEF dump and load\"))\n        .arg(arg!(--\"nnef-tract-extra\" \"Allow usage of tract-extra extension in NNEF dump and load\"))\n        .arg(arg!(--\"nnef-tract-transformers\" \"Allow usage of tract-transformers extension in NNEF dump and load\"))\n        .arg(arg!(--\"nnef-extended-identifier\" \"Allow usage of the i\\\"...\\\" syntax to escape identifier names\"))\n        .arg(arg!(--\"nnef-extern-all-constants\" \"Do not inline small tensors\"))\n        .arg(arg!(--opl \"Activates all NNEF tract extensions (like --nnef-tract-*)\"))\n\n\n        .arg(arg!(--\"threads\" [THREADS] \"Setup a thread pool for computing. 0 will guess the number of physical cores\"))\n\n        .arg(arg!(-O --optimize \"Optimize before running\"))\n        .arg(arg!(--\"assert-maximal-mm-quality-cost\" [MAX] \"Maximum value for quality category (0=assembly, 4=dreadful rust code)\"))\n        .arg(arg!(--pulse [PULSE] \"Translate to pulse network\"))\n\n        .arg(arg!(--\"machine-friendly\" \"Machine friendly output\"))\n        .arg(arg!(--\"timeout\" [SECONDS] \"Kill the process after this many seconds\"))\n\n        .subcommand(Command::new(\"list-ops\").about(\"List ops in TF/ONNX frameworks\"))\n        .subcommand(Command::new(\"list-runtimes\").about(\"List runtimes\"))\n        .subcommand(Command::new(\"kernels\").about(\"Print kernels for the current plaform\"))\n        .subcommand(Command::new(\"hwbench\").about(\"Print current hardware key metrics\"));\n\n    let compare = clap::Command::new(\"compare\")\n        .long_about(\"Compares the output of tract and tensorflow on randomly generated input.\")\n        .arg(\n            Arg::new(\"stage\")\n                .long(\"stage\")\n                .value_parser(clap::builder::PossibleValuesParser::new(STAGES))\n                .help(\"Loading pipeline stage to compare with\"),\n        )\n        .arg(\n            Arg::new(\"tf\").long(\"tf\").action(ArgAction::SetTrue).help(\"Compare against tensorflow\"),\n        )\n        .arg(\n            Arg::new(\"twice\")\n                .long(\"twice\")\n                .action(ArgAction::SetTrue)\n                .help(\"Run twice and compare\"),\n        )\n        .arg(Arg::new(\"npz\").long(\"npz\").num_args(1).help(\"NPZ file to compare against\"))\n        .arg(\n            Arg::new(\"pbdir\")\n                .long(\"pbdir\")\n                .num_args(1)\n                .help(\"protobuf directory file to compare against (like ONNX tests)\"),\n        )\n        .arg(\n            Arg::new(\"stream\")\n                .long(\"stream\")\n                .action(ArgAction::SetTrue)\n                .help(\"Compare pulsed execution against non-pulsed reference\"),\n        )\n        .group(\n            ArgGroup::new(\"reference\")\n                .args(&[\"npz\", \"pbdir\", \"stage\", \"tf\", \"twice\", \"stream\"])\n                .required(true),\n        )\n        .arg(\n            Arg::new(\"cumulative\")\n                .long(\"cumulative\")\n                .action(ArgAction::SetTrue)\n                .help(\"Do not reset with reference values at each node\"),\n        )\n        .arg(\n            Arg::new(\"resilient\")\n                .long(\"resilient\")\n                .action(ArgAction::SetTrue)\n                .help(\"Try nodes one per one to mitigate crashes\"),\n        );\n    let compare = run_options(compare);\n    let compare = assertions_options(compare);\n    app = app.subcommand(output_options(compare));\n\n    let bench =\n        clap::Command::new(\"bench\").long_about(\"Benchmarks tract on randomly generated input.\");\n    let bench = run_options(bench);\n    let bench = output_options(bench);\n    let bench = bench_options(bench);\n    let bench = assertions_options(bench);\n    app = app.subcommand(bench);\n\n    let criterion = clap::Command::new(\"criterion\")\n        .long_about(\"Benchmarks tract on randomly generated input using criterion.\");\n    let criterion = run_options(criterion);\n    app = app.subcommand(criterion);\n\n    app = app.subcommand(dump_subcommand());\n\n    let run = clap::Command::new(\"run\")\n        .long_about(\"Run the graph\")\n        .arg(Arg::new(\"dump\").long(\"dump\").action(ArgAction::SetTrue).help(\"Show output\"))\n        .arg(\n            Arg::new(\"save-outputs-npz\")\n                .long(\"save-outputs-npz\")\n                .alias(\"save-outputs\")\n                .num_args(1)\n                .help(\"Save the outputs into a npz file\"),\n        )\n        .arg(\n            Arg::new(\"save-outputs-nnef\")\n                .long(\"save-outputs-nnef\")\n                .num_args(1)\n                .help(\"Save the output tensor into a folder of nnef .dat files\"),\n        )\n        .arg(\n            Arg::new(\"steps\")\n                .long(\"steps\")\n                .action(ArgAction::SetTrue)\n                .help(\"Show all inputs and outputs\"),\n        )\n        .arg(\n            Arg::new(\"save-steps\")\n                .long(\"save-steps\")\n                .num_args(1)\n                .help(\"Save intermediary values as a npz file\"),\n        )\n        .arg(\n            Arg::new(\"check-f16-overflow\")\n                .long(\"check-f16-overflow\")\n                .action(ArgAction::SetTrue)\n                .help(\"Check for f16 overflow in all outputs\"),\n        )\n        .arg(\n            Arg::new(\"assert-sane-floats\")\n                .long(\"assert-sane-floats\")\n                .action(ArgAction::SetTrue)\n                .help(\"Check float for NaN and infinites at each step\"),\n        );\n    let run = run_options(run);\n    let run = output_options(run);\n    let run = assertions_options(run);\n    app = app.subcommand(run);\n\n    #[cfg(feature = \"transformers\")]\n    {\n        let llm_bench =\n            clap::Command::new(\"llm-bench\").long_about(\"llamas.cpp-style bench (tg128 and pp512)\");\n        let llm_bench = assertions_options(llm_bench);\n        let llm_bench = run_options(llm_bench);\n        let llm_bench = bench_options(llm_bench);\n        app = app.subcommand(llm_bench);\n    }\n\n    let matches = app.get_matches();\n\n    if let Some(timeout) = matches.get_one::<String>(\"timeout\") {\n        let seconds: u64 = timeout.parse().expect(\"--timeout value must be an integer (seconds)\");\n        std::thread::spawn(move || {\n            std::thread::sleep(std::time::Duration::from_secs(seconds));\n            eprintln!(\"Timeout: process killed after {seconds}s\");\n            std::process::exit(124);\n        });\n    }\n\n    let probe = if matches.get_flag(\"readings\") {\n        let file = fs::File::create(\"readings.out\").unwrap();\n        let mut probe = Probe::new(file).unwrap();\n        probe.register_i64(\"progress\").unwrap();\n        let heartbeat =\n            matches.get_one::<String>(\"readings-heartbeat\").unwrap().parse::<f32>().unwrap();\n        probe.spawn_heartbeat(std::time::Duration::from_secs_f32(heartbeat / 1000.0)).unwrap();\n        Some(probe)\n    } else {\n        None\n    };\n\n    if ::std::env::var(\"TRACT_LOG\").is_err() {\n        let level = match matches.get_count(\"verbose\") {\n            0 => \"cli=warn,tract=warn\",\n            1 => \"cli=info,tract=info\",\n            2 => \"cli=debug,tract=debug\",\n            _ => \"cli=trace,tract=trace\",\n        };\n        unsafe {\n            std::env::set_var(\"TRACT_LOG\", level);\n        }\n    }\n\n    let env = env_logger::Env::default().filter_or(\"TRACT_LOG\", \"warn\");\n\n    env_logger::Builder::from_env(env).format_timestamp_nanos().init();\n    info_usage(\"init\", probe.as_ref());\n\n    rustls::crypto::ring::default_provider()\n        .install_default()\n        .expect(\"failed to install ring provider\");\n\n    let res = handle(matches, probe.as_ref());\n\n    if let Err(e) = res {\n        error!(\"{e:?}\");\n        std::process::exit(1);\n    }\n\n    info_usage(\"done\", probe.as_ref());\n    Ok(())\n}\n\n#[allow(clippy::let_and_return)]\nfn dump_subcommand() -> clap::Command {\n    use clap::*;\n    let dump = clap::Command::new(\"dump\")\n        .long_about(\"Dumps the graph in human readable form.\")\n        .arg(\n            Arg::new(\"axes\")\n            .long(\"axes\")\n            .action(clap::ArgAction::SetTrue)\n            .help(\"Compute and display axis tracking\")\n            )\n        .arg(\n            Arg::new(\"axes-names\")\n            .number_of_values(1)\n            .action(clap::ArgAction::Append)\n            .long(\"axes-names\")\n            .help(\"Gave meaningful names to axes: [node_name=]axis0,axis1,..,axisN (apply to first input if no node_name is provided)\")\n            )\n        .arg(\n            Arg::new(\"assert-cost\")\n            .long(\"assert-cost\")\n            .num_args(1)\n            .help(\"Checks computed against the provided value (form: \\\"FMA(F32)=2060448 DIV(F32)=24576\\\")\")\n            )\n        .arg(\n            Arg::new(\"memory-arena\")\n            .long(\"memory-arena\")\n            .num_args(1)\n            .help(\"Dump arena memory statistics to a JSON file (MacOS / iOS only)\")\n        )\n        .arg(\n            Arg::new(\"nnef-override-output-name\")\n            .number_of_values(1)\n            .long(\"nnef-override-output-name\")\n            .help(\"Rename output before dumping network\")\n            )\n        .arg(\n            Arg::new(\"nnef-dir\")\n            .long(\"nnef-dir\")\n            .num_args(1)\n            .help(\"Dump the network in NNEF format (as a directory)\"),\n            )\n        .arg(\n            Arg::new(\"nnef-tar\")\n            .long(\"nnef-tar\")\n            .num_args(1)\n            .help(\"Dump the network in NNEF format (as a tar file)\"),\n            )\n        .arg(\n            Arg::new(\"nnef\")\n            .long(\"nnef\")\n            .num_args(1)\n            .help(\"Dump the network in NNEF format (as a tar.gz file)\"),\n            )\n        .arg(\n            Arg::new(\"tflite\")\n            .long(\"tflite\")\n            .num_args(1)\n            .help(\"Dump the network in TfLite format\"),\n            )\n        .arg(\n            Arg::new(\"compress-submodels\")\n            .long(\"compress-submodels\")\n            .action(clap::ArgAction::SetTrue)\n            .help(\"Compress submodels if any (as a .tgz file)\"),\n        )\n        .arg(\n            Arg::new(\"nnef-deterministic\")\n            .long(\"nnef-deterministic\")\n            .action(clap::ArgAction::SetTrue)\n            .help(\"If provided, will try to make output .nnef.tar files deterministic\"),\n            )\n        .arg(\n            Arg::new(\"nnef-graph\")\n            .long(\"nnef-graph\")\n            .num_args(1)\n            .help(\"Dump the network definition (without the weights) as a graph.nnef-like file\"),\n            )\n        .arg(\n            Arg::new(\"inner\")\n            .number_of_values(1)\n            .action(clap::ArgAction::Append)\n            .long(\"inner\")\n            .help(\"Navigate to a sub-model\"),\n            )\n        .arg(\n            Arg::new(\"summary\")\n            .short('s')\n            .long(\"summary\")\n            .action(clap::ArgAction::SetTrue)\n            .help(\"Display a short summary: properties, model inputs and outputs\"),\n            );\n    let dump = run_options(dump);\n    let dump = output_options(dump);\n    let dump = assertions_options(dump);\n    let dump = bench_options(dump);\n    dump\n}\n\nfn assertions_options(command: clap::Command) -> clap::Command {\n    use clap::*;\n    command\n        .arg(\n            Arg::new(\"approx\")\n            .value_parser([\"exact\", \"close\", \"approximate\", \"very\", \"super\", \"ultra\"])\n            .default_value(\"close\")\n            .long(\"approx\")\n            .help(\"Approximation level used in assertions.\"),\n            )\n        .arg(\n            Arg::new(\"approx-custom\")\n            .long(\"approx-custom\")\n            .num_args(1)\n            .help(\"Approximation level used in assertions (atol, rtol, outlier ratio). 3 coma-separated floats.\"),\n            )\n        .arg(\n            Arg::new(\"assert-output\")\n            .action(clap::ArgAction::Append)\n            .number_of_values(1)\n            .long(\"assert-output\")\n            .help(\"Fact to check the ouput tensor against (@filename, or 3x4xf32)\"),\n            )\n        .arg(\n            Arg::new(\"assert-output-bundle\")\n            .long(\"assert-output-bundle\")\n            .num_args(1)\n            .help(\"Checks values against these tensor (.npz)\"),\n            )\n        .arg(\n            Arg::new(\"assert-output-fact\")\n            .long(\"assert-output-fact\")\n            .num_args(1)\n            .help(\"Infered shape and datum type must match exactly this\"),\n            )\n        .arg(\n            Arg::new(\"assert-output-count\")\n            .long(\"assert-output-count\")\n            .num_args(1)\n            .help(\"Check the number of outputs found.\"),\n            )\n        .arg(\n            Arg::new(\"allow-missing-outputs\")\n            .long(\"allow-missing-outputs\")\n            .action(clap::ArgAction::SetTrue)\n            .help(\"Allow missing output in checks\")\n            )\n        .arg(\n            Arg::new(\"assert-llm-rbo\")\n            .long(\"assert-llm-rbo\")\n            .num_args(1)\n            .help(\"Use RBO (Rank-Biased Overlap) on logit output. Pass minimum similarity score (0.0-1.0)\")\n            )\n        .arg(\n            Arg::new(\"assert-llm-rbo-p\")\n            .long(\"assert-llm-rbo-p\")\n            .default_value(\"0.9\")\n            .help(\"RBO persistence parameter (default 0.9)\")\n            )\n        .arg(\n            Arg::new(\"assert-llm-rbo-depth\")\n            .long(\"assert-llm-rbo-depth\")\n            .default_value(\"100\")\n            .help(\"RBO max evaluation depth (default 100)\")\n            )\n        .arg(\n            Arg::new(\"assert-op-count\")\n            .value_parser(clap::builder::NonEmptyStringValueParser::new())\n            .number_of_values(2)\n            .value_names(&[\"operator\", \"count\"])\n            .action(clap::ArgAction::Append)\n            .long(\"assert-op-count\")\n            .help(\"Specified operator must appear exactly the specified number of times. This argument can appear multiple times.\"),\n            )\n        .arg(\n            Arg::new(\"assert-op-only\")\n            .long(\"assert-op-only\")\n            .num_args(1)\n            .help(\"Assert all ops match the given comma-separated patterns (prefix* or exact)\"),\n            )\n}\n\nfn bench_options(command: clap::Command) -> clap::Command {\n    use clap::*;\n    command.args(&[\n                 arg!(--\"warmup-time\" [warmup_time] \"Time to run (approx.) before starting the clock.\"),\n                 arg!(--\"warmup-loops\" [warmup_loops] \"Number of loops to run before starting the clock.\"),\n                 arg!(--\"max-loops\" [max_iters] \"Sets the maximum number of iterations for each node [default: 100_000].\").alias(\"max-iters\"),\n                 arg!(--\"max-time\" [max_time] \"Sets the maximum execution time for each node (in ms) [default: 5000].\") ])\n}\n\nfn run_options(command: clap::Command) -> clap::Command {\n    use clap::*;\n    command\n        .arg(\n            Arg::new(\"input-from-npz\")\n            .long(\"input-from-npz\")\n            .alias(\"input-from-bundle\")\n            .num_args(1)\n            .help(\"Path to an input container (.npz). This sets tensor values.\"),\n            )\n        .arg(\n            Arg::new(\"set\")\n                .long(\"set\")\n                .action(clap::ArgAction::Append)\n                .number_of_values(1)\n                .help(\"Set a symbol value before running the model (--set S=12)\"),\n        )\n        .arg(\n            Arg::new(\"input-from-nnef\").long(\"input-from-nnef\").num_args(1).help(\n                \"Path to a directory containing input tensors in NNEF format (.dat files). This sets tensor values.\",\n                ),\n                )\n        .arg(arg!(--pp [pp] \"Random input for LLM-like prompt processing\"))\n        .arg(arg!(--tg [tg] \"Random input for LLM-like text generation\"))\n        .arg(Arg::new(\"skip-order-opt-ram\")\n            .long(\"skip-order-opt-ram\")\n            .action(clap::ArgAction::SetTrue)\n            .help(\"Plan node evaluation order without RAM optimisation\"),\n            )\n        .arg(\n            Arg::new(\"allow-random-input\")\n            .short('R')\n            .long(\"allow-random-input\")\n            .action(clap::ArgAction::SetTrue)\n            .help(\"Will use random generated input\"),\n            )\n        .arg(\n            Arg::new(\"random-range\")\n            .long(\"random-range\")\n            .num_args(1)\n            .action(clap::ArgAction::Append)\n            .help(\"Constraint random values to a given range (example: input=1.0..10.0)\"),\n            )\n        .arg(\n            Arg::new(\"allow-float-casts\")\n            .long(\"allow-float-casts\")\n            .action(clap::ArgAction::SetTrue)\n            .help(\"Allow casting between f16, f32 and f64 around model\"),\n            )\n        .arg(\n            Arg::new(\"metal-gpu-trace\")\n                .long(\"metal-gpu-trace\")\n                .num_args(1)\n                .help(\"Capture Metal GPU trace and save it at given path. Only available on MacOS and iOS\")\n        )\n        .arg(\n            Arg::new(\"cuda-gpu-trace\")\n                .long(\"cuda-gpu-trace\")\n                .action(clap::ArgAction::SetTrue)\n                .help(\"Capture CUDA GPU trace. Must be used with nsys profile -c cudaProfilerApi before cargo command\")\n        )\n        .arg(\n            Arg::new(\"prompt-chunk-size\")\n                .long(\"prompt-chunk-size\")\n                .number_of_values(1)\n                .help(\"Set prompt chunk size. Help splitting too big prompts\")\n        )\n}\n\nfn output_options(command: clap::Command) -> clap::Command {\n    use clap::*;\n    command\n        .args(&[\n            arg!(--\"natural-order\" \"dump nodes in id order instead of evaluation order\"),\n            arg!(--\"opt-ram-order\" \"dump nodes in RAM optimising order\"),\n            arg!(-q --quiet \"don't dump\"),\n        ])\n        .arg(\n            Arg::new(\"debug-op\")\n                .long(\"debug-op\")\n                .action(ArgAction::SetTrue)\n                .help(\"show debug dump for each op\"),\n        )\n        .arg(Arg::new(\"node-id\").long(\"node-id\").num_args(1).help(\"Select a node to dump\"))\n        .arg(Arg::new(\"successors\").long(\"successors\").num_args(1).help(\"Show successors of node\"))\n        .arg(Arg::new(\"op-name\").long(\"op-name\").num_args(1).help(\"Select one op to dump\"))\n        .arg(Arg::new(\"node-name\").long(\"node-name\").num_args(1).help(\"Select one node to dump\"))\n        .arg(\n            Arg::new(\"const\")\n                .long(\"const\")\n                .action(ArgAction::SetTrue)\n                .help(\"also display consts nodes\"),\n        )\n        .arg(\n            Arg::new(\"info\")\n                .long(\"info\")\n                .action(ArgAction::SetTrue)\n                .help(\"show op inner information\"),\n        )\n        .arg(\n            Arg::new(\"io-long\")\n                .long(\"io-long\")\n                .action(ArgAction::SetTrue)\n                .help(\"show full i/o information\"),\n        )\n        .arg(\n            Arg::new(\"io-none\")\n                .long(\"io-none\")\n                .action(ArgAction::SetTrue)\n                .help(\"hide i/o information\"),\n        )\n        .arg(\n            Arg::new(\"json\")\n                .long(\"json\")\n                .action(ArgAction::SetTrue)\n                .help(\"dump performance info as json\"),\n        )\n        .arg(\n            Arg::new(\"audit-json\")\n                .long(\"audit-json\")\n                .action(ArgAction::SetTrue)\n                .help(\"dump full model graph as JSON for machine consumption\"),\n        )\n        .arg(\n            Arg::new(\"mm\")\n                .long(\"mm\")\n                .action(ArgAction::SetTrue)\n                .help(\"display Matrix Multiplication report\"),\n        )\n        .arg(\n            Arg::new(\"outlet-labels\")\n                .long(\"outlet-labels\")\n                .action(ArgAction::SetTrue)\n                .help(\"display outlet labels\"),\n        )\n        .arg(\n            Arg::new(\"cost\")\n                .long(\"cost\")\n                .action(ArgAction::SetTrue)\n                .help(\"Include const information\"),\n        )\n        .arg(\n            Arg::new(\"tmp_mem_usage\")\n                .long(\"tmp-mem-usage\")\n                .action(ArgAction::SetTrue)\n                .help(\"Include temporary memory usage information\"),\n        )\n        .arg(\n            Arg::new(\"profile\")\n                .long(\"profile\")\n                .action(ArgAction::SetTrue)\n                .help(\"Include results for profile run\"),\n        )\n        .arg(\n            Arg::new(\"folded\")\n                .long(\"folded\")\n                .action(ArgAction::SetTrue)\n                .help(\"Don't display submodel informations\"),\n        )\n        .arg(\n            Arg::new(\"invariants\")\n                .long(\"invariants\")\n                .action(ArgAction::SetTrue)\n                .help(\"Display operators invariants\"),\n        )\n}\n\n/// Handles the command-line input.\nfn handle(matches: clap::ArgMatches, probe: Option<&Probe>) -> TractResult<()> {\n    match matches.subcommand() {\n        Some((\"list-runtimes\", _)) => {\n            tract_core::runtime::runtimes().for_each(|ir| {\n                println!(\" * {}\", ir.name());\n            });\n            return Ok(());\n        }\n        Some((\"list-ops\", _)) => {\n            #[cfg(feature = \"onnx\")]\n            {\n                let onnx = tract_onnx::onnx();\n                let names = onnx.op_register.0.keys().sorted().join(\", \");\n                println!(\"Onnx:\\n\");\n                println!(\"{names}\");\n                println!(\"\\n\");\n            }\n            #[cfg(feature = \"tf\")]\n            {\n                let tf = tract_tensorflow::tensorflow();\n                let names = tf.op_register.0.keys().sorted().join(\", \");\n                println!(\"Tensorflow:\\n\");\n                println!(\"{names}\");\n                println!(\"\\n\");\n            }\n            return Ok(());\n        }\n        Some((\"hwbench\", _)) => return hwbench::handle(),\n        Some((\"kernels\", _)) => {\n            println!();\n            fn colored_name(m: &dyn MatMatMul) -> String {\n                format!(\n                    \"{} {}\",\n                    QUALITY_COLORS[m.quality().cost()].paint(m.name()),\n                    match m.dynamic_boost().signum() {\n                        1 => Green.paint(\"●\"),\n                        -1 => Red.paint(\"●\"),\n                        _ => \"-\".to_string().into(),\n                    }\n                )\n            }\n            println!(\"{}\", White.bold().paint(\"# By implementation\"));\n            println!();\n            for m in tract_linalg::ops().mmm_impls() {\n                println!(\"{} -> {:?}\", colored_name(&**m), m.stores());\n                for packings in m.packings() {\n                    println!(\"   - {:?} • {:?}\", packings.0, packings.1);\n                }\n            }\n            println!();\n            println!(\"{}\", White.bold().paint(\"# By weights\"));\n            println!();\n            for w in [\n                WeightType::Plain(f16::datum_type()),\n                WeightType::Plain(f32::datum_type()),\n                WeightType::Plain(f64::datum_type()),\n                WeightType::Plain(i8::datum_type()),\n                WeightType::from(Q4_0),\n            ] {\n                println!(\"{}\", White.bold().paint(format!(\"{w:?}\")));\n                for packing in tract_linalg::ops()\n                    .all_possible_packing(w)\n                    .sorted_by_key(|f| format!(\"{f:?}\"))\n                    .dedup()\n                {\n                    println!(\"  * {packing:?}\");\n                    for mmm in tract_linalg::ops().mmm_impls() {\n                        for (ix, p) in mmm.packings().iter().enumerate() {\n                            if p.0.dyn_eq(packing) {\n                                println!(\n                                    \"    - {} ({ix}) {:?} {:?}\",\n                                    colored_name(&**mmm),\n                                    p.0,\n                                    p.1\n                                );\n                            } else if let Some(pe) = tract_linalg::ops()\n                                .panel_extractors()\n                                .iter()\n                                .find(|pe| pe.from.dyn_eq(packing) && p.0.dyn_eq(&pe.to))\n                            {\n                                println!(\n                                    \"    - {} ({ix}) {:?} {:?} using {}\",\n                                    colored_name(&**mmm),\n                                    p.0,\n                                    p.1,\n                                    pe.name\n                                );\n                            }\n                        }\n                    }\n                }\n            }\n            return Ok(());\n        }\n        Some((\"dump\", m)) if m.contains_id(\"metal-gpu-trace\") => {\n            // Set env variable before loading metal lib\n            unsafe {\n                std::env::set_var(\"METAL_CAPTURE_ENABLED\", \"1\");\n                std::env::set_var(\"METAL_DEVICE_WRAPPER_TYPE\", \"1\");\n            }\n        }\n        _ => (),\n    }\n\n    let builder_result = Parameters::from_clap(&matches, probe);\n    #[allow(unused_mut)]\n    let mut params = match builder_result {\n        Ok(params) => params,\n        Err(e) => {\n            if let Some(params::ModelBuildingError(broken_model, _)) = e.downcast_ref() {\n                let mut broken_model: Box<dyn Model> =\n                    tract_hir::tract_core::dyn_clone::clone(broken_model);\n                let annotations = Annotations::from_model(broken_model.as_ref())?;\n                let display_params = if let Some((\"dump\", sm)) = matches.subcommand() {\n                    display_params_from_clap(&matches, sm)?\n                } else {\n                    DisplayParams::default()\n                };\n\n                if broken_model.output_outlets().len() == 0 {\n                    broken_model.auto_outputs()?;\n                }\n                tract_libcli::terminal::render(\n                    broken_model.as_ref(),\n                    &annotations,\n                    &display_params,\n                )?;\n            }\n            Err(e)?\n        }\n    };\n\n    let mut need_optimisations = false;\n\n    #[cfg(feature = \"multithread-mm\")]\n    if let Some(threads) = matches.get_one::<String>(\"threads\") {\n        let threads: usize = threads.parse()?;\n        let threads = if threads == 0 { num_cpus::get_physical() } else { threads };\n        multithread::set_default_executor(multithread::Executor::multithread(threads));\n    }\n    #[cfg(not(feature = \"multithread-mm\"))]\n    if matches.get_one::<String>(\"threads\").is_some() {\n        bail!(\"tract is compiled without multithread support\")\n    }\n\n    match matches.subcommand() {\n        Some((\"bench\", m)) => {\n            need_optimisations = true;\n            bench::handle(&params, m, &params::bench_limits_from_clap(m)?)\n        }\n\n        Some((\"criterion\", m)) => {\n            need_optimisations = true;\n            bench::criterion(&params, &matches, m)\n        }\n\n        Some((\"compare\", m)) => {\n            compare::handle(&mut params, &matches, m, display_params_from_clap(&matches, m)?)\n        }\n\n        Some((\"run\", m)) => run::handle(&params, &matches, m),\n\n        None => dump::handle(\n            &params,\n            &DisplayParams::default(),\n            &matches,\n            &dump_subcommand().get_matches_from::<_, &'static str>([]),\n            &BenchLimits::default(),\n            vec![],\n        ),\n\n        Some((\"dump\", m)) => {\n            need_optimisations = m.get_flag(\"profile\");\n            let inner = m\n                .get_many::<String>(\"inner\")\n                .map(|ss| ss.map(|s| s.to_string()).collect())\n                .unwrap_or_default();\n            dump::handle(\n                &params,\n                &display_params_from_clap(&matches, m)?,\n                &matches,\n                m,\n                &params::bench_limits_from_clap(m)?,\n                inner,\n            )\n        }\n\n        #[cfg(feature = \"transformers\")]\n        Some((\"llm-bench\", m)) => {\n            need_optimisations = true;\n            llm::handle(&params, &matches, m, &params::bench_limits_from_clap(m)?, probe)\n        }\n\n        Some((s, _)) => bail!(\"Unknown subcommand {}.\", s),\n    }?;\n\n    if need_optimisations {\n        let style = nu_ansi_term::Style::new().fg(nu_ansi_term::Color::Red).bold();\n        if cfg!(debug_assertions) {\n            warn!(\"{}\", style.paint(\"Profiling a debug build of tract!\"));\n        }\n        if !matches.get_flag(\"cuda\")\n            && !matches.get_flag(\"metal\")\n            && !matches.get_flag(\"optimize\")\n            && !matches.contains_id(\"runtime\")\n        {\n            warn!(\"{}\", style.paint(\"Profiling a non-optimized model. Use -O or a runtime.\"));\n        }\n    }\n    Ok(())\n}\n\nfn nnef(matches: &clap::ArgMatches) -> tract_nnef::internal::Nnef {\n    let mut fw = tract_nnef::nnef();\n    if matches.get_flag(\"nnef-tract-onnx\") || matches.get_flag(\"opl\") {\n        #[cfg(feature = \"onnx\")]\n        {\n            use tract_onnx::WithOnnx;\n            fw = fw.with_onnx();\n        }\n        #[cfg(not(feature = \"onnx\"))]\n        {\n            panic!(\"tract is build without ONNX support\")\n        }\n    }\n    if matches.get_flag(\"nnef-tract-pulse\") || matches.get_flag(\"opl\") {\n        #[cfg(feature = \"pulse-opl\")]\n        {\n            use tract_pulse::WithPulse;\n            fw = fw.with_pulse();\n        }\n        #[cfg(not(feature = \"pulse-opl\"))]\n        {\n            panic!(\"tract is build without pulse-opl support\")\n        }\n    }\n    if matches.get_flag(\"nnef-tract-extra\") || matches.get_flag(\"opl\") {\n        #[cfg(feature = \"extra\")]\n        {\n            use tract_extra::WithTractExtra;\n            fw = fw.with_tract_extra();\n        }\n        #[cfg(not(feature = \"extra\"))]\n        {\n            panic!(\"tract is build without tract-extra support\")\n        }\n    }\n    if matches.get_flag(\"nnef-tract-transformers\")\n        || matches.get_flag(\"llm\")\n        || matches.get_flag(\"opl\")\n    {\n        #[cfg(feature = \"transformers\")]\n        {\n            use tract_transformers::WithTractTransformers;\n            fw = fw.with_tract_transformers();\n        }\n        #[cfg(not(feature = \"transformers\"))]\n        {\n            panic!(\"tract is build without tract-transformers support\")\n        }\n    }\n    if !matches.get_flag(\"no-nnef-tract-core\") {\n        fw = fw.with_tract_core();\n    }\n    if matches.get_flag(\"nnef-tract-resource\") || matches.get_flag(\"opl\") {\n        use tract_nnef_resources::internal::JsonLoader;\n        fw = fw.with_tract_resource().with_resource_loader(JsonLoader);\n    }\n    if matches.get_flag(\"nnef-extended-identifier\") || matches.get_flag(\"opl\") {\n        fw.allow_extended_identifier_syntax(true);\n    }\n    if matches.get_flag(\"nnef-extern-all-constants\") {\n        fw.extern_all_constants(true);\n    }\n    fw\n}\n"
  },
  {
    "path": "cli/src/memory_arena.rs",
    "content": "use serde::{Deserialize, Serialize};\nuse std::collections::BTreeMap;\nuse tract_gpu::memory::DeviceMemSchema;\nuse tract_hir::internal::*;\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\nstruct MemArenaUsage {\n    arena_memory_size: i64,\n    peak_memory_size: i64,\n    peak_memory_usage: f32,\n}\n\nimpl MemArenaUsage {\n    pub fn eval_from_schema(\n        schema: &DeviceMemSchema,\n        symbol_values: &SymbolValues,\n    ) -> TractResult<Self> {\n        Ok(Self {\n            arena_memory_size: schema.eval_memory_size(symbol_values)?,\n            peak_memory_size: schema.eval_peak_memory_size(symbol_values)?,\n            peak_memory_usage: schema.eval_usage(symbol_values)?,\n        })\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\nstruct MemArenaMetrics {\n    memory_size: String,\n    size_by_partition: Vec<String>,\n    pp: BTreeMap<i64, MemArenaUsage>,\n    tg: BTreeMap<i64, MemArenaUsage>,\n    max_memory_size: i64,\n    aggregate_usage: f32,\n}\n\nimpl MemArenaMetrics {\n    pub fn from_schema(schema: &DeviceMemSchema) -> TractResult<Self> {\n        log::info!(\"Analyzing memory arena utilization...\");\n        const MAX_GEN_TOKENS: i64 = 2048;\n        const MAX_PROMPT_TOKENS: i64 = 2048;\n\n        const STEP_TOKENS: i64 = 16;\n        let memory_size: String = schema.memory_size().to_string();\n        let size_by_partition: Vec<String> =\n            schema.size_by_partition().iter().map(|it| it.to_string()).collect();\n        let symbol_scope = SymbolScope::default();\n        let sequence_length = symbol_scope.sym(\"S\");\n        let past_sequence_length = symbol_scope.sym(\"P\");\n\n        let mut pp = BTreeMap::new();\n        let mut max_memory_size: i64 = 0;\n        let mut sum_size: i64 = 0;\n        let mut sum_used: i64 = 0;\n        for s in (STEP_TOKENS..MAX_PROMPT_TOKENS + 1).step_by(STEP_TOKENS as usize) {\n            log::info!(\"Prompt processing: P: 0, S: {s}\");\n            let symbol_values =\n                SymbolValues::default().with(&sequence_length, s).with(&past_sequence_length, 0);\n            let usage = MemArenaUsage::eval_from_schema(schema, &symbol_values)?;\n            max_memory_size = max_memory_size.max(usage.arena_memory_size);\n            sum_size += usage.arena_memory_size;\n            sum_used += usage.peak_memory_size;\n            pp.insert(s, usage);\n        }\n        let mut tg = BTreeMap::new();\n        for p in (0..MAX_GEN_TOKENS + 1).step_by(STEP_TOKENS as usize) {\n            log::info!(\"Token generation: P: {p}, S: 1\");\n            let symbol_values =\n                SymbolValues::default().with(&sequence_length, 1).with(&past_sequence_length, p);\n            let usage = MemArenaUsage::eval_from_schema(schema, &symbol_values)?;\n            max_memory_size = max_memory_size.max(usage.arena_memory_size);\n            sum_size += usage.arena_memory_size;\n            sum_used += usage.peak_memory_size;\n            tg.insert(p, usage);\n        }\n\n        let aggregate_usage = ((sum_used * 100 / sum_size.max(1)) as f32) / 100.0;\n        Ok(Self { memory_size, size_by_partition, pp, tg, max_memory_size, aggregate_usage })\n    }\n}\n\npub fn dump_metrics(\n    model: &Arc<TypedModel>,\n    options: &RunOptions,\n    path: impl AsRef<std::path::Path>,\n) -> TractResult<()> {\n    log::info!(\"Analyzing Metal memory schema utilization...\");\n    const SCHEMA_HINT_S: i64 = 1024;\n    const SCHEMA_HINT_P: i64 = 0;\n\n    let plan = SimplePlan::new_with_options(model.clone(), options)?;\n    let order = plan.order_without_consts();\n    let mut symbol_values = SymbolValues::default();\n    let sequence_length = model.symbols.get(\"S\").context(\"Could not find symbol S in model\")?;\n    let past_sequence_length =\n        model.symbols.get(\"P\").context(\"Could not find symbol P in model\")?;\n\n    symbol_values.set(&sequence_length, SCHEMA_HINT_S);\n    symbol_values.set(&past_sequence_length, SCHEMA_HINT_P);\n\n    let schema = DeviceMemSchema::build(model, order, &symbol_values)?;\n\n    println!(\"resolved_memory_size: {}\", schema.eval_memory_size(&symbol_values)?);\n    println!(\"Schema:\\n{schema}\");\n\n    let metrics = MemArenaMetrics::from_schema(&schema)?;\n\n    std::fs::write(path.as_ref(), serde_json::to_string(&metrics)?).expect(\"Unable to write file\");\n\n    Ok(())\n}\n"
  },
  {
    "path": "cli/src/model.rs",
    "content": "\n"
  },
  {
    "path": "cli/src/params.rs",
    "content": "use fs_err as fs;\nuse reqwest::Url;\nuse scan_fmt::scan_fmt;\nuse std::io::Cursor;\nuse std::io::Read;\nuse std::path::PathBuf;\nuse std::str::FromStr;\nuse tract_core::internal::*;\nuse tract_core::model::TypedModel;\nuse tract_core::ops::konst::Const;\n#[allow(unused_imports)]\nuse tract_core::transform::ModelTransform;\nuse tract_hir::internal::*;\n#[allow(unused_imports)]\nuse tract_itertools::Itertools;\nuse tract_libcli::profile::BenchLimits;\nuse tract_nnef::tensors::read_tensor;\n#[cfg(feature = \"pulse\")]\nuse tract_pulse::internal::*;\n#[cfg(feature = \"tf\")]\nuse tract_tensorflow::tfpb::tensorflow::GraphDef;\n#[cfg(feature = \"tflite\")]\nuse tract_tflite::internal::TfliteProtoModel;\n\nuse tract_nnef::ast::dump::Dumper;\n\nuse crate::TractResult;\nuse tract_libcli::display_params;\nuse tract_libcli::display_params::DisplayParams;\nuse tract_libcli::model::Model;\nuse tract_libcli::tensor;\nuse tract_libcli::tensor::{TensorValues, TensorsValues};\n\nuse readings_probe::*;\n\nuse super::info_usage;\n\nuse std::convert::*;\n\n#[derive(Debug)]\nenum Location {\n    Fs(PathBuf),\n    Http(Url),\n}\n\nimpl Location {\n    fn path(&self) -> Cow<'_, std::path::Path> {\n        match self {\n            Location::Fs(p) => p.into(),\n            Location::Http(u) => std::path::Path::new(u.path()).into(),\n        }\n    }\n\n    fn is_dir(&self) -> bool {\n        if let &Location::Fs(p) = &self { p.is_dir() } else { false }\n    }\n\n    fn read(&self) -> TractResult<Box<dyn Read>> {\n        match self {\n            Location::Fs(p) => Ok(Box::new(fs::File::open(p)?)),\n            Location::Http(u) => Ok(Box::new(http_client()?.get(u.clone()).send()?)),\n        }\n    }\n\n    fn bytes(&self) -> TractResult<Vec<u8>> {\n        let mut vec = vec![];\n        self.read()?.read_to_end(&mut vec)?;\n        Ok(vec)\n    }\n\n    fn find(s: impl AsRef<str>) -> TractResult<Self> {\n        let s = s.as_ref();\n        let path = std::path::PathBuf::from(s);\n        if s.starts_with(\"http://\") || s.starts_with(\"https://\") {\n            return Ok(Location::Http(s.parse()?));\n        } else if path.exists() {\n            return Ok(Location::Fs(path));\n        } else if path.is_relative()\n            && cfg!(any(\n                target_os = \"ios\",\n                target_os = \"watchos\",\n                target_os = \"tvos\",\n                target_os = \"android\"\n            ))\n        {\n            if let Ok(pwd) = std::env::current_exe() {\n                let absolute = pwd.parent().unwrap().join(&path);\n                if absolute.exists() {\n                    return Ok(Location::Fs(absolute));\n                }\n            }\n        }\n        bail!(\"File not found {}\", s)\n    }\n}\n\n#[derive(Debug, Clone)]\n#[allow(clippy::large_enum_variant, dead_code)]\npub enum SomeGraphDef {\n    NoGraphDef,\n    Nnef(tract_nnef::ProtoModel),\n    #[cfg(feature = \"onnx\")]\n    Onnx(tract_onnx::pb::ModelProto, tract_onnx::model::ParseResult),\n    #[cfg(feature = \"tf\")]\n    Tf(GraphDef),\n    #[cfg(feature = \"tflite\")]\n    Tflite(TfliteProtoModel),\n}\n\n#[derive(Debug)]\npub struct ModelBuildingError(pub Box<dyn Model>, pub Box<dyn std::error::Error + Send + Sync>);\n\nimpl std::fmt::Display for ModelBuildingError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"ModelBuildingError\")\n    }\n}\n\nimpl std::error::Error for ModelBuildingError {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        Some(&*self.1)\n    }\n}\n\n#[cfg(not(feature = \"pulse\"))]\ntype PulsedModel = ();\n\n/// Structure holding the parsed parameters.\n#[derive(Clone, Debug)]\npub struct Parameters {\n    pub graph: SomeGraphDef,\n\n    pub runnable: Option<Arc<dyn Runnable>>,\n    pub tract_model: Arc<dyn Model>,\n    pub reference_model: Option<Arc<dyn Model>>,\n\n    #[cfg(feature = \"conform\")]\n    pub tf_model: Option<tract_tensorflow::conform::tf::Tensorflow>,\n\n    #[cfg(not(feature = \"conform\"))]\n    #[allow(dead_code)]\n    pub tf_model: (),\n\n    pub tensors_values: TensorsValues,\n    pub assertions: Assertions,\n\n    pub machine_friendly: bool,\n    pub allow_random_input: bool,\n    pub allow_float_casts: bool,\n}\n\n#[cfg(feature = \"tf\")]\ntype TfExt = tract_tensorflow::model::TfModelExtensions;\n#[cfg(not(feature = \"tf\"))]\ntype TfExt = ();\n\nimpl Parameters {\n    fn disco_model(matches: &clap::ArgMatches) -> TractResult<(Location, bool)> {\n        let model = matches.get_one::<String>(\"model\").with_context(|| {\n            format!(\n                \"Model argument required for subcommand {}\",\n                matches.subcommand_name().unwrap_or(\"\")\n            )\n        })?;\n        let location = Location::find(model)?;\n        if location.is_dir() && location.path().join(\"model.onnx\").exists() {\n            Ok((Location::Fs(location.path().join(\"model.onnx\")), false))\n        } else {\n            Ok((location, false))\n        }\n    }\n\n    fn load_model(\n        matches: &clap::ArgMatches,\n        probe: Option<&Probe>,\n        location: &Location,\n        tensors_values: &TensorsValues,\n        symbols: SymbolScope,\n    ) -> TractResult<(SomeGraphDef, Box<dyn Model>, Option<TfExt>)> {\n        let need_graph =\n            matches.get_flag(\"proto\") || matches.subcommand_name() == Some(\"compare-pbdir\");\n\n        let format = matches.get_one::<String>(\"format\").map(String::as_str).unwrap_or(\n            if location.path().extension().map(|s| s == \"onnx\").unwrap_or(false) {\n                \"onnx\"\n            } else if location.path().extension().map(|s| s == \"tflite\").unwrap_or(false) {\n                \"tflite\"\n            } else if location.is_dir()\n                || location.path().to_string_lossy().ends_with(\".tar\")\n                || location.path().to_string_lossy().ends_with(\".tar.gz\")\n                || location.path().extension().map(|s| s == \"tgz\").unwrap_or(false)\n            {\n                \"nnef\"\n            } else {\n                \"tf\"\n            },\n        );\n        let triplet: (SomeGraphDef, Box<dyn Model>, Option<TfExt>) = match format {\n            \"nnef\" => {\n                let nnef = super::nnef(matches);\n                let mut proto_model = if location.is_dir() {\n                    if let Location::Fs(dir) = location {\n                        nnef.proto_model_for_path(dir)?\n                    } else {\n                        unreachable!();\n                    }\n                } else if location\n                    .path()\n                    .extension()\n                    .map(|e| e.to_string_lossy().ends_with(\"gz\"))\n                    .unwrap_or(false)\n                {\n                    nnef.proto_model_for_read(&mut flate2::read::GzDecoder::new(\n                        &mut *location.read()?,\n                    ))?\n                } else {\n                    nnef.proto_model_for_read(&mut *location.read()?)?\n                };\n                for (ix, name) in proto_model.doc.graph_def.parameters.iter().enumerate() {\n                    #[allow(unused_imports)]\n                    use tract_nnef::ast::{LValue, RValue};\n                    if let Some(over) = tensors_values\n                        .input_by_name(&name.0)\n                        .or_else(|| tensors_values.by_input_ix(ix))\n                        .and_then(|tv| tv.fact.as_ref())\n                    {\n                        let assignment_id = proto_model\n                            .doc\n                            .graph_def\n                            .body\n                            .iter()\n                            .position(|a| a.left == LValue::Identifier(name.clone()))\n                            .context(\"Could not find input assignement in nnef body\")?;\n                        let mut formatted = vec![];\n                        let ass = &mut proto_model.doc.graph_def.body[assignment_id];\n                        let inv = if let RValue::Invocation(inv) = &mut ass.right {\n                            inv\n                        } else {\n                            unreachable!();\n                        };\n                        assert!(\n                            inv.id.0 == \"external\" || inv.id.0 == \"tract_core_external\",\n                            \"invalid id: expected 'external' or 'tract_core_external' but found {:?}\",\n                            inv.id\n                        );\n                        assert!(\n                            inv.arguments.len() <= 2,\n                            \"expected 1 argument but found {:?} for inv.arguments={:?}\",\n                            inv.arguments.len(),\n                            inv.arguments\n                        );\n                        assert_eq!(inv.arguments[0].id.as_ref().map(|i| &*i.0), Some(\"shape\"));\n                        Dumper::new(&nnef, &mut formatted).rvalue(&inv.arguments[0].rvalue)?;\n                        let shape = over\n                            .shape\n                            .concretize()\n                            .context(\"Can only use concrete shapes in override\")?;\n                        info!(\n                            \"Overriding model input shape named \\\"{}\\\". Replacing {} by {:?}.\",\n                            name.0,\n                            String::from_utf8_lossy(&formatted),\n                            &shape\n                        );\n                        inv.arguments[0].rvalue = tract_nnef::ser::tdims(&shape);\n                    }\n                }\n                info_usage(\"proto model loaded\", probe);\n                let template = TypedModel { symbols, ..TypedModel::default() };\n                let graph_def = if need_graph {\n                    SomeGraphDef::Nnef(proto_model.clone())\n                } else {\n                    SomeGraphDef::NoGraphDef\n                };\n                (\n                    graph_def,\n                    Box::new(\n                        nnef.translate(&proto_model, template)\n                            .map_err(|(g, e)| ModelBuildingError(Box::new(g), e.into()))?,\n                    ),\n                    Option::<TfExt>::None,\n                )\n            }\n            #[cfg(feature = \"tflite\")]\n            \"tflite\" => {\n                let tflite = tract_tflite::tflite();\n                info_usage(\"loaded framework (tflite)\", probe);\n                let proto = tflite.proto_model_for_read(&mut *location.read()?)?;\n                info_usage(\"proto model loaded\", probe);\n                let template = TypedModel { symbols, ..TypedModel::default() };\n                let model = tflite.model_for_proto_model_with_model_template(&proto, template)?;\n                info_usage(\"proto model translated\", probe);\n                (SomeGraphDef::Tflite(proto), Box::new(model), Option::<TfExt>::None)\n            }\n            #[cfg(feature = \"onnx\")]\n            \"onnx\" => {\n                let onnx = tract_onnx::onnx()\n                    .with_ignore_output_shapes(matches.get_flag(\"onnx-ignore-output-shapes\"))\n                    .with_ignore_output_types(matches.get_flag(\"onnx-ignore-output-types\"))\n                    .with_ignore_value_info(matches.get_flag(\"onnx-ignore-value-info\"));\n                info_usage(\"loaded framework (onnx)\", probe);\n                let graph = onnx.proto_model_for_read(&mut *location.read()?)?;\n                info_usage(\"proto model loaded\", probe);\n                let path = &location.path().clone();\n                let template = InferenceModel { symbols, ..InferenceModel::default() };\n                let mut parsed = onnx.parse_with_template(\n                    &graph,\n                    path.parent().and_then(|it| it.to_str()),\n                    template,\n                )?;\n\n                if matches.get_flag(\"determinize\") {\n                    tract_onnx::Onnx::determinize(&mut parsed.model)?;\n                }\n\n                if need_graph {\n                    (\n                        SomeGraphDef::Onnx(graph, parsed.clone()),\n                        Box::new(parsed.model),\n                        Option::<TfExt>::None,\n                    )\n                } else {\n                    (SomeGraphDef::NoGraphDef, Box::new(parsed.model), Option::<TfExt>::None)\n                }\n            }\n            #[cfg(feature = \"tf\")]\n            \"tf\" => {\n                let tf = tract_tensorflow::tensorflow();\n                info_usage(\"loaded framework (tf)\", probe);\n                let mut graph = tf.proto_model_for_read(&mut *location.read()?)?;\n                info_usage(\"proto model loaded\", probe);\n                if matches.get_flag(\"determinize\") {\n                    tract_tensorflow::Tensorflow::determinize(&mut graph)?;\n                }\n                let template = InferenceModel { symbols, ..InferenceModel::default() };\n                let model_and_ext = tf.parse_graph_with_template(&graph, template)?;\n                if need_graph {\n                    (SomeGraphDef::Tf(graph), Box::new(model_and_ext.0), Some(model_and_ext.1))\n                } else {\n                    (SomeGraphDef::NoGraphDef, Box::new(model_and_ext.0), Some(model_and_ext.1))\n                }\n            }\n            _ => bail!(\n                \"Format {} not supported. You may need to recompile tract with the right features.\",\n                format\n            ),\n        };\n        Ok(triplet)\n    }\n\n    fn use_onnx_test_case_data_set(\n        symbol_table: &SymbolScope,\n        inputs_dir: &std::path::Path,\n    ) -> TractResult<Vec<TensorValues>> {\n        let mut result = vec![];\n        for file in inputs_dir.read_dir()? {\n            let file = file?;\n            let filename = file\n                .file_name()\n                .into_string()\n                .map_err(|s| format_err!(\"Can't convert OSString to String ({:?})\", s))?;\n            let is_input = filename.starts_with(\"input_\");\n            let is_output = filename.starts_with(\"output_\");\n            if is_input || is_output {\n                let ix = filename\n                    .split('_')\n                    .nth(1)\n                    .unwrap()\n                    .split('.')\n                    .next()\n                    .unwrap()\n                    .parse::<usize>()?;\n                let fd = fs::File::open(file.path())?;\n                let (name, tensor) =\n                    tensor::for_data(symbol_table, file.path().to_str().unwrap(), fd)?;\n                result.push(TensorValues {\n                    input_index: Some(ix).filter(|_| is_input),\n                    output_index: Some(ix).filter(|_| is_output),\n                    name,\n                    values: tensor.value.concretize().map(|t| vec![t.into_tensor().into()]),\n                    fact: Some(tensor.without_value()),\n                    random_range: None,\n                    only_input: is_input,\n                    only_output: is_output,\n                })\n            }\n        }\n        Ok(result)\n    }\n\n    fn tensor_values_from_iter(\n        iter: impl Iterator<Item = (String, usize, Tensor)>,\n        get_values: bool,\n        get_facts: bool,\n    ) -> Vec<TensorValues> {\n        let mut result = vec![];\n        for (name, vals) in iter.chunk_by(|triple| triple.0.clone()).into_iter() {\n            let vals: Vec<_> = vals\n                .into_iter()\n                .sorted_by_key(|(_, turn, _)| *turn)\n                .map(|(_, _, tensor)| tensor.into_tvalue())\n                .collect();\n            result.push(TensorValues {\n                name: Some(name),\n                fact: if get_facts {\n                    Some(vals[0].datum_type().fact(vals[0].shape()).into())\n                } else {\n                    None\n                },\n                values: if get_values { Some(vals) } else { None },\n                ..TensorValues::default()\n            })\n        }\n        result\n    }\n\n    pub fn parse_nnef_tensors(\n        input: &str,\n        get_values: bool,\n        get_facts: bool,\n    ) -> TractResult<Vec<TensorValues>> {\n        let files = fs::read_dir(input)?;\n        let vector = files\n            .map(|n| {\n                let file_path = n?.path();\n                let tensor_file = fs::read(&file_path)?;\n                let file_name = file_path.as_os_str().to_str().unwrap();\n                if let Ok((turn, name)) =\n                    scan_fmt::scan_fmt!(file_name, \"turn_{d}/{}.dat\", usize, String)\n                {\n                    Ok((name, turn, read_tensor(tensor_file.as_slice())?))\n                } else {\n                    let name = file_path.file_stem().unwrap().to_os_string().into_string().unwrap();\n                    Ok((name, 0, read_tensor(tensor_file.as_slice())?))\n                }\n            })\n            .collect::<TractResult<Vec<_>>>()?;\n        Ok(Self::tensor_values_from_iter(vector.into_iter(), get_values, get_facts))\n    }\n\n    pub fn parse_set_and_hint(\n        typed_model: &TypedModel,\n        set: impl Iterator<Item = impl AsRef<str>>,\n    ) -> TractResult<SymbolValues> {\n        let mut values = SymbolValues::default();\n        for set in set {\n            let set = set.as_ref();\n            let (key, value) = set.split_once('=').with_context(|| {\n                format!(\"--set and --hint must be in the X=value form, got {set}\")\n            })?;\n            let value: i64 = value\n                .parse()\n                .with_context(|| format!(\"value expected to be an integer, got {value}\"))?;\n            let key = typed_model.get_or_intern_symbol(key);\n            values.set(&key, value);\n        }\n        Ok(values)\n    }\n\n    pub fn parse_npz(\n        input: &str,\n        get_values: bool,\n        get_facts: bool,\n    ) -> TractResult<Vec<TensorValues>> {\n        let loc = Location::find(input)?;\n        let mut npz = ndarray_npy::NpzReader::new(Cursor::new(loc.bytes()?))?;\n        let triples = npz\n            .names()?\n            .iter()\n            .map(|n| {\n                if let Ok((turn, name)) = scan_fmt::scan_fmt!(n, \"turn_{d}/{}.npy\", usize, String) {\n                    Ok((name, turn, tensor::for_npz(&mut npz, n)?))\n                } else {\n                    let name = n.trim_end_matches(\".npy\").to_string();\n                    Ok((name, 0, tensor::for_npz(&mut npz, n)?))\n                }\n            })\n            .collect::<TractResult<Vec<_>>>()?;\n        Ok(Self::tensor_values_from_iter(triples.into_iter(), get_values, get_facts))\n    }\n\n    fn parse_tensors(\n        matches: &clap::ArgMatches,\n        location: &Location,\n        onnx_tc: bool,\n        symbol_table: &SymbolScope,\n    ) -> TractResult<TensorsValues> {\n        let mut result = TensorsValues::default();\n\n        if let Some(inputs) = matches.get_many::<String>(\"input\") {\n            for (ix, v) in inputs.enumerate() {\n                let v = v.as_str();\n                let (name, fact) = tensor::for_string(symbol_table, v)?;\n                let input_index = if name.is_some() { None } else { Some(ix) };\n                result.add(TensorValues {\n                    input_index,\n                    name,\n                    values: fact.value.concretize().map(|t| vec![t.into_tensor().into()]),\n                    fact: Some(fact.without_value()),\n                    only_input: true,\n                    ..TensorValues::default()\n                });\n            }\n        }\n\n        if let Some(bundle) = matches.get_many::<String>(\"input-bundle\") {\n            warn!(\n                \"Argument --input-bundle is deprecated and may be removed in a future release. Use --input-facts-from-bundle and/or --input-from-bundle instead.\"\n            );\n            for input in bundle {\n                let input = input.as_str();\n                for tv in Self::parse_npz(input, true, true)? {\n                    result.add(tv);\n                }\n            }\n        }\n\n        if let Some(bundle) = matches.get_many::<String>(\"input-facts-from-bundle\") {\n            for input in bundle {\n                let input = input.as_str();\n                for tv in Self::parse_npz(input, false, true)? {\n                    result.add(tv);\n                }\n            }\n        }\n\n        if let Some((_, sub)) = matches.subcommand() {\n            if let Some(values) = sub.get_many::<String>(\"assert-output\") {\n                for (ix, o) in values.enumerate() {\n                    let o = o.as_str();\n                    let (name, fact) = tensor::for_string(symbol_table, o)?;\n                    info!(\n                        \"Output assertion #{}: (named: {}) {:?}\",\n                        ix,\n                        name.as_deref().unwrap_or(\"\"),\n                        fact\n                    );\n                    result.add(TensorValues {\n                        output_index: Some(ix),\n                        name,\n                        values: fact.value.concretize().map(|t| vec![t.into_tensor().into()]),\n                        fact: Some(fact.without_value()),\n                        only_output: true,\n                        ..TensorValues::default()\n                    });\n                }\n            }\n\n            if let Some(bundles) = sub.get_many::<String>(\"assert-output-bundle\") {\n                for bundle in bundles {\n                    let bundle = bundle.as_str();\n                    for mut tv in Self::parse_npz(bundle, true, false)? {\n                        tv.only_output = true;\n                        result.add(tv);\n                    }\n                }\n            }\n        }\n\n        if onnx_tc {\n            let data_set_name = matches\n                .get_one::<String>(\"onnx-test-data-set\")\n                .map(String::as_str)\n                .unwrap_or(\"test_data_set_0\");\n\n            for tv in Self::use_onnx_test_case_data_set(\n                symbol_table,\n                location.path().parent().unwrap().join(data_set_name).as_path(),\n            )? {\n                result.add(tv)\n            }\n        }\n\n        if let Some((_, sub)) = matches.subcommand() {\n            if let Some(ranges) = sub.get_many::<String>(\"random-range\") {\n                for (ix, spec) in ranges.enumerate() {\n                    let spec = spec.as_str();\n                    let (name, from, to) = if let Ok((name, from, to)) =\n                        scan_fmt!(spec, \"{}={f}..{f}\", String, f32, f32)\n                    {\n                        (Some(name), from, to)\n                    } else if let Ok((from, to)) = scan_fmt!(spec, \"{f}..{f}\", f32, f32) {\n                        (None, from, to)\n                    } else {\n                        bail!(\"Can't parse random-range parameter {}\", spec)\n                    };\n                    let tv = if let Some(name) = name {\n                        result.by_name_mut_with_default(&name)\n                    } else {\n                        result.by_input_ix_mut_with_default(ix)\n                    };\n                    tv.random_range = Some(from..to);\n                }\n            }\n        }\n\n        Ok(result)\n    }\n\n    #[allow(unused_variables)]\n    #[allow(clippy::type_complexity)]\n    fn load_and_declutter(\n        matches: &clap::ArgMatches,\n        probe: Option<&readings_probe::Probe>,\n        raw_model: Box<dyn Model>,\n        tf_model_extensions: Option<TfExt>,\n        reference_stage: Option<&str>,\n        keep_last: bool,\n    ) -> TractResult<(Arc<dyn Model>, Option<Arc<dyn Model>>)> {\n        let stop_at = matches\n            .get_one::<String>(\"pass\")\n            .map(String::as_str)\n            .unwrap_or(if matches.get_flag(\"optimize\") { \"optimize\" } else { \"before-optimize\" });\n\n        info!(\"Will stop at {stop_at}\");\n\n        if stop_at == \"load\" {\n            return Ok((raw_model.into(), None));\n        }\n\n        let mut inference_model: Option<Arc<InferenceModel>> = None;\n        let mut typed_model: Option<Arc<TypedModel>> = None;\n        #[allow(unused_mut)]\n        let mut pulsed_model: Option<Arc<PulsedModel>> = None;\n        let mut reference_model: Option<Arc<dyn Model>> = None;\n\n        if raw_model.is::<InferenceModel>() {\n            inference_model = Some(raw_model.downcast::<InferenceModel>().unwrap().into());\n        } else if raw_model.is::<TypedModel>() {\n            typed_model = Some(raw_model.downcast::<TypedModel>().unwrap().into());\n        }\n\n        macro_rules! stage {\n            ($name:expr, $from:ident -> $to:ident, $block:expr) => {\n                if let Some(from) = $from.take() {\n                    info!(\"Running {:?}\", $name);\n                    let mut last_model: Option<Box<dyn Model>> =\n                        if keep_last { Some(Box::new(from.as_ref().clone())) } else { None };\n                    let block: &dyn Fn(_) -> TractResult<_> = &$block;\n                    let owned_model =\n                        Arc::try_unwrap(from).unwrap_or_else(|from| from.as_ref().clone());\n                    match block(owned_model).with_context(|| format!(\"Error at stage {:?}\", $name)) {\n                        Ok(it) => {\n                            $to = Some(Arc::new(it));\n                        }\n                        Err(e) => {\n                            if e.is::<ModelBuildingError>() {\n                                return Err(e)?;\n                            } else if let Some(last_model) = last_model.take() {\n                                return Err(ModelBuildingError(last_model, e.into()))?;\n                            } else {\n                                return Err(e)?;\n                            }\n                        }\n                    }\n                    info_usage(&format!(\"after {:?}\", $name), probe);\n                    if reference_stage.as_deref() == Some($name) {\n                        reference_model = Some($to.as_ref().unwrap().clone());\n                    }\n                    if stop_at == $name {\n                        return Ok((\n                                $to.take().expect(\"returnable model\"),\n                                reference_model,\n                                ));\n                    }\n                } else {\n                    debug!(\"Skip stage {}\", $name);\n                    if stop_at == $name {\n                        bail!(\"Stage {} is skipped, it can not be used as stop with these input format or parameters.\", $name);\n                    }\n                }\n            };\n        }\n\n        stage!(\"analyse\", inference_model -> inference_model,\n        |mut m:InferenceModel| -> TractResult<_> {\n            m.analyse(!matches.get_flag(\"analyse-fail-fast\")).map_err(|e|\n                                                                        ModelBuildingError(Box::new(m.clone()), e.into())\n                                                                       )?;\n            if let Some(fail) = m.missing_type_shape()?.first() {\n                bail!(ModelBuildingError(Box::new(m.clone()), format!(\"{} has incomplete typing\", m.node(fail.node)).into()))\n            }\n            Ok(m)\n        });\n        if let Some(ext) = tf_model_extensions {\n            #[cfg(feature = \"tf\")]\n            stage!(\"tf-preproc\", inference_model -> inference_model, |m:InferenceModel| ext.preproc(m));\n        }\n        stage!(\"incorporate\", inference_model -> inference_model, |m:InferenceModel| m.incorporate());\n        stage!(\"type\", inference_model -> typed_model, |m:InferenceModel| { let mut m = m.into_typed()?; m.compact()?; Ok(m) });\n        stage!(\"declutter\", typed_model -> typed_model, |mut m:TypedModel| {\n            if matches.get_flag(\"label-wires\") {\n                for node in 0..m.nodes().len() {\n                    if m.outlet_label(node.into()).is_none() {\n                        m.set_outlet_label(node.into(), m.node(node).name.to_string())?;\n                    }\n                }\n            }\n            let mut dec = tract_core::optim::Optimizer::declutter();\n            if let Some(steps) = matches.get_one::<String>(\"declutter-step\") {\n                dec = dec.stopping_at(steps.parse()?);\n            }\n            dec.optimize(&mut m)?;\n            Ok(m)\n        });\n        #[cfg(not(feature = \"pulse\"))]\n        {\n            if matches.get_one::<String>(\"pulse\").is_some() {\n                bail!(\"This build of tract has pulse disabled.\")\n            }\n        }\n        #[cfg(feature = \"pulse\")]\n        {\n            if let Some(spec) = matches.get_one::<String>(\"pulse\") {\n                stage!(\"pulse\", typed_model -> pulsed_model, |m:TypedModel| {\n                    let (sym, pulse) = if let Ok((s,p)) = scan_fmt!(spec, \"{}={}\", String, String) {\n                        (s, parse_tdim(&m.symbols, &p)?)\n                    } else if let Ok(i) = parse_tdim(&m.symbols, spec) {\n                        (\"S\".to_owned(), i)\n                    } else {\n                        bail!(\"Can not parse pulse specification {}\", spec)\n                    };\n                    let sym = m.symbols.sym(&sym);\n                    PulsedModel::new(&m, sym, &pulse)\n                });\n                stage!(\"pulse-to-type\", pulsed_model -> typed_model, |m:PulsedModel| m.into_typed());\n                stage!(\"pulse-declutter\", typed_model -> typed_model, |m:TypedModel| m.into_decluttered());\n            }\n        }\n        let mut transforms: Vec<&str> = matches\n            .get_many::<String>(\"transform\")\n            .map(|values| values.map(String::as_str).collect())\n            .unwrap_or_default();\n        if matches.get_flag(\"llm\") {\n            transforms.insert(0, \"transformers_detect_all\");\n        }\n        if transforms.len() > 0 {\n            for spec in transforms {\n                let (name, params_str) = tract_core::transform::split_spec(spec);\n                let transform = if params_str.is_empty() {\n                    tract_core::transform::get_transform(&name)?\n                } else {\n                    let mut de = ron::Deserializer::from_str(params_str)\n                        .with_context(|| format!(\"Parsing RON params for transform {name}\"))?;\n                    tract_core::transform::get_transform_with_params(\n                        &name,\n                        &mut <dyn erased_serde::Deserializer>::erase(&mut de),\n                    )?\n                }\n                .with_context(|| format!(\"Could not find transform named {name}\"))?;\n                stage!(&transform.name(), typed_model -> typed_model, |m:TypedModel| {\n                    transform.transform_into(m)\n                });\n                stage!(&format!(\"{}_declutter\", transform.name()), typed_model -> typed_model, |m:TypedModel| m.into_decluttered());\n            }\n        }\n\n        if let Some(set) = matches.get_many::<String>(\"set\") {\n            let values = Self::parse_set_and_hint(typed_model.as_ref().unwrap(), set)?;\n            stage!(\"set\", typed_model -> typed_model, |mut m: TypedModel| {\n                for node in m.eval_order()? {\n                    let node = m.node_mut(node);\n                    if let Some(op) = node.op_as_mut::<Const>() {\n                        if op.val().datum_type() == DatumType::TDim { {\n                            // get inner value to Arc<Tensor>\n                            let mut constant:Tensor = (**op.val()).clone();\n                            // Generally a shape or hyperparam\n                            constant\n                                .try_as_plain_mut()?\n                                .as_slice_mut::<TDim>()?\n                                .iter_mut()\n                                .for_each(|x| *x = x.eval(&values));\n\n                            *op = Const::new(constant.into_arc_tensor())?;\n                        }\n                        }\n                    }\n                }\n                m.concretize_dims(&values)\n            });\n            stage!(\"set-declutter\", typed_model -> typed_model, |mut m| {\n                let mut dec = tract_core::optim::Optimizer::declutter();\n                if let Some(steps) = matches.get_one::<String>(\"declutter-set-step\") {\n                    dec = dec.stopping_at(steps.parse()?);\n                }\n                dec.optimize(&mut m)?;\n                Ok(m)\n            })\n        }\n        if matches.get_flag(\"nnef-cycle\") {\n            stage!(\"nnef-cycle\", typed_model -> typed_model, |m:TypedModel| {\n                let nnef = super::nnef(matches);\n                let mut vec = vec!();\n                nnef.write(&m, &mut vec).context(\"Serializing to nnef\")?;\n                info!(\"Dumped, now reloading...\");\n                nnef.model_for_read(&mut &*vec).context(\"Deserializing from nnef intermediary\")\n            });\n            stage!(\"nnef-declutter\", typed_model -> typed_model, |m:TypedModel| m.into_decluttered());\n        }\n        #[cfg(feature = \"tflite\")]\n        if matches.get_flag(\"tflite-cycle\") {\n            stage!(\"tflite-cycle-predump\", typed_model -> typed_model, |mut m:TypedModel| {\n                tract_tflite::rewriter::rewrite_for_tflite(&mut m)?;\n                Ok(m)\n            });\n            stage!(\"tflite-cycle\", typed_model -> typed_model, |m:TypedModel| {\n                let tflite = tract_tflite::tflite();\n                let mut vec = vec!();\n                tflite.write(&m, &mut vec).context(\"Serializing to tflite\")?;\n                info!(\"Dumped, now reloading...\");\n                tflite.model_for_read(&mut &*vec).context(\"Deserializing from tflite intermediary\")\n            });\n            stage!(\"tflite-declutter\", typed_model -> typed_model, |m:TypedModel| m.into_decluttered());\n        }\n        #[cfg(not(feature = \"tflite\"))]\n        if matches.get_flag(\"tflite-cycle\") {\n            bail!(\"This tract build did not include tflite features.\");\n        }\n        if let Some(sub) = matches.get_one::<String>(\"extract-decluttered-sub\") {\n            stage!(\"extract\", typed_model -> typed_model, |m:TypedModel| {\n                let node = m.node_id_by_name(sub)?;\n                Ok(m.nested_models(node)[0].1.downcast_ref::<TypedModel>().unwrap().clone())\n            });\n        }\n        stage!(\"before-optimize\", typed_model -> typed_model, Ok);\n        Ok((typed_model.clone().unwrap(), reference_model))\n    }\n\n    #[allow(unused_variables)]\n    #[allow(clippy::let_unit_value)]\n    /// Parses the command-line arguments.\n    pub fn from_clap(matches: &clap::ArgMatches, probe: Option<&Probe>) -> TractResult<Parameters> {\n        let symbols = SymbolScope::default();\n        for scenario in matches.get_many::<String>(\"scenario\").unwrap_or_default() {\n            symbols.add_scenario(scenario)?;\n        }\n        for rule in matches.get_many::<String>(\"assert\").unwrap_or_default() {\n            if let Some((scenario, assertion)) = rule.split_once(':') {\n                symbols.add_scenario_assertion(scenario, assertion)?;\n            } else {\n                symbols.add_assertion(rule)?;\n            }\n        }\n        let (filename, onnx_tc) = Self::disco_model(matches)?;\n        let tensors_values = Self::parse_tensors(matches, &filename, onnx_tc, &symbols)?;\n        let (mut graph, mut raw_model, tf_model_extensions) =\n            Self::load_model(matches, probe, &filename, &tensors_values, symbols.clone())?;\n\n        info!(\"Model {filename:?} loaded\");\n        info_usage(\"model loaded\", probe);\n\n        let (need_tensorflow_model, need_reference_model) = match matches.subcommand() {\n            Some((\"compare\", sm)) => {\n                if let Some(with) = sm.get_one::<String>(\"stage\").map(String::as_str) {\n                    (false, Some(with))\n                } else if sm.get_flag(\"stream\") {\n                    (false, Some(\"declutter\"))\n                } else {\n                    (true, None)\n                }\n            }\n            _ => (false, None),\n        };\n\n        #[cfg(not(feature = \"conform\"))]\n        let tf_model = ();\n        #[cfg(feature = \"conform\")]\n        let tf_model = if need_tensorflow_model {\n            info!(\"Tensorflow version: {}\", tract_tensorflow::conform::tf::version());\n            if matches.get_flag(\"determinize\") {\n                if let SomeGraphDef::Tf(ref graph) = graph {\n                    let graph = graph.write_to_bytes().unwrap();\n                    Some(tract_tensorflow::conform::tf::for_slice(&graph)?)\n                } else {\n                    unreachable!()\n                }\n            } else {\n                Some(tract_tensorflow::conform::tf::for_path(&filename)?)\n            }\n        } else {\n            None\n        };\n\n        let need_proto = matches.get_flag(\"proto\")\n            || (matches.subcommand_matches(\"compare\").map(|sc| sc.contains_id(\"pbdir\")))\n                .unwrap_or(false);\n\n        if !need_proto {\n            graph = SomeGraphDef::NoGraphDef;\n        }\n\n        if let Some(inputs) = matches.get_many::<String>(\"input-node\") {\n            let inputs: Vec<&str> = inputs.map(String::as_str).collect();\n            raw_model.set_input_names(&inputs)?;\n        };\n\n        if let Some(outputs) = matches.get_many::<String>(\"output-node\") {\n            let outputs: Vec<&str> = outputs.map(String::as_str).collect();\n            raw_model.select_outputs_by_name(&outputs)?;\n        };\n\n        if let Some(override_facts) = matches.get_many::<String>(\"override-fact\") {\n            for fact in override_facts {\n                let fact = fact.as_str();\n                let (name, fact) = tensor::for_string(&symbols, fact)?;\n                let node = raw_model.node_id_by_name(name.as_ref().unwrap())?;\n                if let Some(inf) = raw_model.downcast_mut::<InferenceModel>() {\n                    inf.set_outlet_fact(OutletId::new(node, 0), fact)?;\n                } else if let Some(typ) = raw_model.downcast_mut::<TypedModel>() {\n                    typ.set_outlet_fact(OutletId::new(node, 0), (&fact).try_into()?)?;\n                }\n            }\n        };\n\n        if let Some(consts) = matches.get_many::<String>(\"constantize\") {\n            for konst in consts {\n                let konst = konst.as_str();\n                if let Some(value) = tensors_values\n                    .by_name(konst)\n                    .and_then(|tv| tv.values.as_ref())\n                    .and_then(|v| v.first())\n                {\n                    let value = value.clone().into_arc_tensor();\n                    let id = raw_model.node_id_by_name(konst)?;\n                    info!(\n                        \"Commuting {}, fact:{:?} into a const of {:?}\",\n                        raw_model.node_display(id),\n                        raw_model.outlet_typedfact(id.into())?,\n                        value\n                    );\n                    let op = Box::new(Const::new(value.clone().into_arc_tensor())?);\n                    if let Some(inf) = raw_model.downcast_mut::<InferenceModel>() {\n                        inf.inputs.retain(|i| i.node != id);\n                        inf.nodes[id].op = op;\n                        inf.nodes[id].outputs[0].fact = Default::default();\n                    } else if let Some(typ) = raw_model.downcast_mut::<TypedModel>() {\n                        typ.inputs.retain(|i| i.node != id);\n                        typ.nodes[id].op = op;\n                        typ.nodes[id].outputs[0].fact = TypedFact::try_from(value.clone())?;\n                    }\n                }\n            }\n        }\n\n        let output_names_and_labels: Vec<Vec<String>> = raw_model\n            .output_outlets()\n            .iter()\n            .map(|o| {\n                let mut v = vec![format!(\"{}:{}\", raw_model.node_name(o.node), o.slot)];\n                if o.slot == 0 {\n                    v.push(raw_model.node_name(o.node).to_string());\n                }\n                if let Some(l) = raw_model.outlet_label(*o) {\n                    v.push(l.to_string());\n                }\n                v\n            })\n            .collect();\n\n        let assertions = match matches.subcommand() {\n            Some((\"dump\" | \"run\" | \"compare\", sm)) => Assertions::from_clap(sm, &symbols)?,\n            _ => Assertions::default(),\n        };\n\n        if let Some(infer) = raw_model.downcast_mut::<InferenceModel>() {\n            for (ix, node_id) in infer.inputs.iter().enumerate() {\n                let tv = tensors_values\n                    .input_by_name(&infer.node(node_id.node).name)\n                    .or_else(|| tensors_values.by_input_ix(ix));\n                if let Some(tv) = tv {\n                    if let Some(fact) = &tv.fact {\n                        infer.nodes[node_id.node].outputs[0].fact = fact.clone();\n                    }\n                }\n            }\n        }\n\n        if matches.get_flag(\"partial\") {\n            if let Some(m) = raw_model.downcast_ref::<InferenceModel>() {\n                raw_model = Box::new(m.clone().into_compact()?);\n            } else if let Some(m) = raw_model.downcast_ref::<TypedModel>() {\n                raw_model = Box::new(m.clone().into_compact()?);\n            }\n        }\n\n        let (allow_random_input, allow_float_casts) = match matches.subcommand() {\n            None => (false, false),\n            Some((_, m)) => (m.get_flag(\"allow-random-input\"), m.get_flag(\"allow-float-casts\")),\n        };\n\n        let keep_last = matches.get_flag(\"keep-last\");\n        let (tract_model, reference_model) = Self::load_and_declutter(\n            matches,\n            probe,\n            raw_model,\n            tf_model_extensions,\n            need_reference_model,\n            keep_last,\n        )?;\n\n        info!(\"Model fully loaded\");\n        info_usage(\"model fully loaded\", probe);\n\n        let runtime = if let Some(rt) = matches.get_one::<String>(\"runtime\").map(String::as_str) {\n            rt\n        } else if matches.get_flag(\"cuda\") {\n            \"cuda\"\n        } else if matches.get_flag(\"metal\") {\n            \"metal\"\n        } else if matches.get_flag(\"optimize\") {\n            \"default\"\n        } else {\n            \"unoptimized\"\n        };\n\n        let runtime =\n            runtime_for_name(runtime).with_context(|| format!(\"Runtime `{runtime}' not found\"))?;\n        let (tract_model, runnable): (Arc<dyn Model>, Option<Arc<dyn Runnable>>) =\n            if tract_model.downcast_ref::<TypedModel>().is_some() {\n                let tract_model: Arc<TypedModel> = Arc::downcast(tract_model).unwrap();\n                let typed_model = Arc::try_unwrap(tract_model).unwrap();\n                let hints = if let Some(hints) = matches.get_many::<String>(\"hint\") {\n                    Some(Self::parse_set_and_hint(&typed_model, hints)?)\n                } else if matches.get_flag(\"llm\") || matches.get_flag(\"causal-llm-hints\") {\n                    #[cfg(feature = \"transformers\")]\n                    {\n                        Some(tract_transformers::memory_arena_hints_for_causal_llm(&typed_model)?)\n                    }\n                    #[cfg(not(feature = \"transformers\"))]\n                    {\n                        bail!(\"transformers feature is required for llms\")\n                    }\n                } else {\n                    None\n                };\n\n                let options = RunOptions { memory_sizing_hints: hints, ..Default::default() };\n                let runnable = runtime.prepare_with_options(typed_model, &options)?;\n                // we assume the runnable will be a typed_model() (it is the case for all current runtimes)\n                // so we consume tract_model knowning the runnable will give us a new one later.\n                // we should hold on the old model in the general case, but this leads to dup models weights in memory\n                let typed_model = runnable.typed_model().unwrap();\n                (typed_model.clone(), Some(runnable.into()))\n            } else {\n                (tract_model, None)\n            };\n\n        info!(\"Model ready\");\n        info_usage(\"model ready\", probe);\n        Ok(Parameters {\n            graph,\n            runnable,\n            tract_model,\n            reference_model,\n            tf_model,\n            tensors_values,\n            assertions,\n            machine_friendly: matches.get_flag(\"machine-friendly\"),\n            allow_random_input,\n            allow_float_casts,\n        })\n    }\n\n    pub(crate) fn typed_model(&self) -> Option<Arc<TypedModel>> {\n        self.runnable\n            .clone()\n            .and_then(|runnable| runnable.typed_model().cloned())\n            .or_else(|| Arc::downcast(self.tract_model.clone()).ok())\n    }\n\n    pub(crate) fn req_typed_model(&self) -> Arc<TypedModel> {\n        self.typed_model().expect(\"Not a TypedModel\")\n    }\n\n    #[allow(dead_code)]\n    pub(crate) fn req_runnable(&self) -> TractResult<Arc<dyn Runnable>> {\n        self.runnable.clone().context(\"Requires a runnable\")\n    }\n}\n\npub fn bench_limits_from_clap(matches: &clap::ArgMatches) -> TractResult<BenchLimits> {\n    let max_loops = matches\n        .get_one::<String>(\"max-loops\")\n        .map(|s| usize::from_str(s))\n        .transpose()?\n        .unwrap_or(100_000);\n    let warmup_loops = matches\n        .get_one::<String>(\"warmup-loops\")\n        .map(|s| usize::from_str(s))\n        .transpose()?\n        .unwrap_or(0);\n    let max_time = matches\n        .get_one::<String>(\"max-time\")\n        .map(|s| u64::from_str(s))\n        .transpose()?\n        .map(std::time::Duration::from_millis)\n        .unwrap_or(std::time::Duration::from_secs(5));\n    let warmup_time = matches\n        .get_one::<String>(\"warmup-time\")\n        .map(|s| u64::from_str(s))\n        .transpose()?\n        .map(std::time::Duration::from_millis)\n        .unwrap_or(std::time::Duration::from_secs(0));\n    Ok(BenchLimits { max_loops, max_time, warmup_time, warmup_loops })\n}\n\npub fn display_params_from_clap(\n    root_matches: &clap::ArgMatches,\n    matches: &clap::ArgMatches,\n) -> TractResult<DisplayParams> {\n    Ok(DisplayParams {\n        konst: matches.get_flag(\"const\"),\n        cost: matches.get_flag(\"cost\"),\n        tmp_mem_usage: matches.get_flag(\"tmp_mem_usage\"),\n        profile: matches.get_flag(\"profile\"),\n        folded: matches.get_flag(\"folded\"),\n        left_column_width: 0,\n        invariants: matches.get_flag(\"invariants\"),\n        quiet: matches.get_flag(\"quiet\"),\n        natural_order: matches.get_flag(\"natural-order\"),\n        opt_ram_order: matches.get_flag(\"opt-ram-order\"),\n        debug_op: matches.get_flag(\"debug-op\"),\n        node_ids: matches.get_many::<String>(\"node-id\").map(|values| {\n            values.map(|id| tvec!((id.parse::<usize>().unwrap(), \"\".to_string()))).collect()\n        }),\n        node_name: matches.get_one::<String>(\"node-name\").cloned(),\n        op_name: matches.get_one::<String>(\"op-name\").cloned(),\n        //        successors: matches.get_one::<String>(\"successors\").map(|id| id.parse().unwrap()),\n        expect_core: root_matches\n            .get_one::<String>(\"pass\")\n            .map(String::as_str)\n            .unwrap_or(\"declutter\")\n            == \"declutter\"\n            && !root_matches.get_flag(\"optimize\"),\n        outlet_labels: matches.get_flag(\"outlet-labels\"),\n        io: if matches.get_flag(\"io-long\") {\n            display_params::Io::Long\n        } else if matches.get_flag(\"io-none\") {\n            display_params::Io::None\n        } else {\n            display_params::Io::Short\n        },\n        info: matches.get_flag(\"info\"),\n        json: matches.get_flag(\"json\"),\n        mm: matches.get_flag(\"mm\"),\n        summary: matches.try_get_one::<bool>(\"summary\").ok().flatten().copied().unwrap_or(false),\n        audit_json: matches\n            .try_get_one::<bool>(\"audit-json\")\n            .ok()\n            .flatten()\n            .copied()\n            .unwrap_or(false),\n    })\n}\n\n#[derive(Debug, Default, Clone)]\npub struct Assertions {\n    pub assert_outputs: bool,\n    pub assert_output_facts: Option<Vec<InferenceFact>>,\n    pub assert_op_count: Option<Vec<(String, usize)>>,\n    pub approximation: Approximation,\n    pub allow_missing_outputs: bool,\n    pub assert_llm_rbo: Option<f64>,\n    pub assert_llm_rbo_p: f64,\n    pub assert_llm_rbo_depth: usize,\n    pub assert_op_only: Option<Vec<String>>,\n}\n\nimpl Assertions {\n    fn from_clap(sub: &clap::ArgMatches, symbol_table: &SymbolScope) -> TractResult<Assertions> {\n        let assert_outputs =\n            sub.contains_id(\"assert-output\") || sub.contains_id(\"assert-output-bundle\");\n        let assert_output_facts: Option<Vec<InferenceFact>> = sub\n            .get_many::<String>(\"assert-output-fact\")\n            .map(|vs| vs.map(|v| tensor::for_string(symbol_table, v).unwrap().1).collect());\n        let assert_op_count: Option<Vec<(String, usize)>> =\n            sub.get_many::<String>(\"assert-op-count\").and_then(|vs| {\n                vs.map(String::as_str)\n                    .chunks(2)\n                    .into_iter()\n                    .map(|mut args| Some((args.next()?.to_string(), args.next()?.parse().ok()?)))\n                    .collect()\n            });\n        let allow_missing_outputs = sub.get_flag(\"allow-missing-outputs\");\n        let approximation = if let Some(custom) = sub.get_one::<String>(\"approx-custom\") {\n            let Some((atol, rtol, approx)) = custom.split(\",\").collect_tuple() else {\n                bail!(\"Can't parse approx custom. It should look like 0.001,0.002,0.003\")\n            };\n            Approximation::Custom(atol.parse()?, rtol.parse()?, approx.parse()?)\n        } else {\n            match sub.get_one::<String>(\"approx\").map(String::as_str).unwrap() {\n                \"exact\" => Approximation::Exact,\n                \"close\" => Approximation::Close,\n                \"approx\" | \"approximate\" => Approximation::Approximate,\n                \"very\" => Approximation::VeryApproximate,\n                \"super\" => Approximation::SuperApproximate,\n                \"ultra\" => Approximation::UltraApproximate,\n                _ => panic!(),\n            }\n        };\n        let assert_llm_rbo =\n            sub.get_one::<String>(\"assert-llm-rbo\").map(|v| v.parse()).transpose()?;\n        let assert_llm_rbo_p: f64 = sub\n            .get_one::<String>(\"assert-llm-rbo-p\")\n            .map(String::as_str)\n            .unwrap_or(\"0.9\")\n            .parse()?;\n        let assert_llm_rbo_depth: usize = sub\n            .get_one::<String>(\"assert-llm-rbo-depth\")\n            .map(String::as_str)\n            .unwrap_or(\"100\")\n            .parse()?;\n        let assert_op_only = sub\n            .get_one::<String>(\"assert-op-only\")\n            .map(|v| v.split(',').map(|s| s.trim().to_string()).collect());\n        Ok(Assertions {\n            assert_outputs,\n            assert_output_facts,\n            assert_op_count,\n            approximation,\n            allow_missing_outputs,\n            assert_llm_rbo,\n            assert_llm_rbo_p,\n            assert_llm_rbo_depth,\n            assert_op_only,\n        })\n    }\n}\n\nfn http_client() -> TractResult<reqwest::blocking::Client> {\n    use rustls::{ClientConfig, RootCertStore};\n    use std::sync::Arc;\n\n    let mut root_store = RootCertStore::empty();\n    root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());\n\n    let config =\n        ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))\n            .with_safe_default_protocol_versions()?\n            .with_root_certificates(root_store)\n            .with_no_client_auth();\n\n    Ok(reqwest::blocking::Client::builder().use_preconfigured_tls(config).build()?)\n}\n"
  },
  {
    "path": "cli/src/plan_options.rs",
    "content": "use tract_core::internal::*;\n\npub fn plan_options_from_subcommand(sub_matches: &clap::ArgMatches) -> TractResult<RunOptions> {\n    let skip_order_opt_ram: bool = sub_matches.get_flag(\"skip-order-opt-ram\");\n    if skip_order_opt_ram {\n        log::info!(\"Plan options: skip_order_opt_ram -> {skip_order_opt_ram:?}\");\n    }\n    Ok(RunOptions { skip_order_opt_ram, ..RunOptions::default() })\n}\n"
  },
  {
    "path": "cli/src/run.rs",
    "content": "use fs::File;\nuse std::fmt::{Debug, Display};\nuse std::path::PathBuf;\nuse std::str::FromStr;\n\nuse crate::TractResult;\nuse crate::{Model, Parameters};\nuse fs_err as fs;\nuse ndarray_npy::NpzWriter;\nuse nu_ansi_term::Color::*;\nuse tract_core::ops::cnn::conv::Im2Col;\nuse tract_core::ops::matmul::pack::OptMatMulPack;\nuse tract_core::tract_data::itertools::izip;\nuse tract_hir::internal::*;\nuse tract_libcli::tensor::{RunTensors, get_or_make_inputs};\nuse tract_nnef::tensors::write_tensor;\n#[cfg(feature = \"pulse\")]\nuse tract_pulse::internal::*;\n\n/// Add a tensor entry into a npz file.\nfn npz_add_tensor(npz: &mut NpzWriter<File>, name: String, tensor: &Tensor) -> TractResult<()> {\n    match tensor.datum_type() {\n        DatumType::F16 => {\n            npz.add_array(name, &tensor.cast_to::<f32>()?.to_plain_array_view::<f32>()?)?\n        }\n        DatumType::Bool => npz.add_array(name, &tensor.to_plain_array_view::<bool>()?)?,\n        DatumType::U8 => npz.add_array(name, &tensor.to_plain_array_view::<u8>()?)?,\n        DatumType::U16 => npz.add_array(name, &tensor.to_plain_array_view::<u16>()?)?,\n        DatumType::U32 => npz.add_array(name, &tensor.to_plain_array_view::<u32>()?)?,\n        DatumType::U64 => npz.add_array(name, &tensor.to_plain_array_view::<u64>()?)?,\n        DatumType::I8 => npz.add_array(name, &tensor.to_plain_array_view::<i8>()?)?,\n        DatumType::I16 => npz.add_array(name, &tensor.to_plain_array_view::<i16>()?)?,\n        DatumType::I32 => npz.add_array(name, &tensor.to_plain_array_view::<i32>()?)?,\n        DatumType::I64 => npz.add_array(name, &tensor.to_plain_array_view::<i64>()?)?,\n        DatumType::F32 => npz.add_array(name, &tensor.to_plain_array_view::<f32>()?)?,\n        DatumType::F64 => npz.add_array(name, &tensor.to_plain_array_view::<f64>()?)?,\n        DatumType::QI8(_) => npz.add_array(name, &tensor.to_plain_array_view::<i8>()?)?,\n        DatumType::QU8(_) => npz.add_array(name, &tensor.to_plain_array_view::<u8>()?)?,\n        DatumType::QI32(_) => npz.add_array(name, &tensor.to_plain_array_view::<i32>()?)?,\n        _ => warn!(\"Not writing {name}, {tensor:?}, unsupported type\"),\n    }\n\n    Ok(())\n}\n\npub fn handle(\n    params: &Parameters,\n    matches: &clap::ArgMatches,\n    sub_matches: &clap::ArgMatches,\n) -> TractResult<()> {\n    let dump = sub_matches.get_flag(\"dump\");\n    // let outputs = dispatch_model!(&(Arc::clone(params.tract_model) as _), |m| run_regular(\n    //     m,\n    //     params,\n    //     matches,\n    //     sub_matches\n    // ))?;\n    let outputs = run_regular(&params.tract_model, params, matches, sub_matches)?;\n\n    if dump {\n        for (ix, output) in outputs.iter().enumerate() {\n            for (turn, output) in output.iter().enumerate() {\n                println!(\"output #{}, turn #{}\\n{}\\n\", ix, turn, output.dump(true)?);\n            }\n        }\n    }\n\n    if let Some(file_path) = sub_matches.get_one::<String>(\"save-outputs-nnef\") {\n        fs::create_dir_all(file_path).with_context(|| format!(\"Creating {file_path} directory\"))?;\n        for (ix, outputs) in outputs.iter().enumerate() {\n            let name = params\n                .tract_model\n                .outlet_label(params.tract_model.output_outlets()[ix])\n                .map(|name| format!(\"{name}.dat\"))\n                .unwrap_or_else(|| format!(\"output_{ix}.dat\"));\n\n            if outputs.len() == 1 {\n                let mut f = fs::File::create(PathBuf::from_str(file_path)?.join(&name))?;\n                write_tensor(&mut f, &outputs[0])?;\n            } else {\n                for (turn, output) in outputs.iter().enumerate() {\n                    let name = format!(\"turn_{turn}/{name}\");\n                    let mut f = fs::File::open(PathBuf::from_str(file_path)?.join(name))?;\n                    write_tensor(&mut f, output)?;\n                }\n            }\n        }\n    }\n\n    if let Some(file_path) = sub_matches.get_one::<String>(\"save-outputs-npz\") {\n        let file = fs::File::create(file_path).with_context(|| format!(\"Creating {file_path}\"))?;\n        let mut npz = ndarray_npy::NpzWriter::new_compressed(file);\n\n        for (ix, outputs) in outputs.iter().enumerate() {\n            let name = params\n                .tract_model\n                .outlet_label(params.tract_model.output_outlets()[ix])\n                .map(|name| name.to_string())\n                .unwrap_or_else(|| format!(\"output_{ix}\"));\n            if outputs.len() == 1 {\n                npz_add_tensor(&mut npz, name, &outputs[0])?;\n            } else {\n                for (turn, output) in outputs.iter().enumerate() {\n                    let name = format!(\"turn_{turn}/{name}\");\n                    npz_add_tensor(&mut npz, name, output)?;\n                }\n            }\n        }\n    }\n\n    if let Some(count) = sub_matches.get_one::<String>(\"assert-output-count\") {\n        let count = count.parse::<usize>()?;\n        if count != outputs.len() {\n            bail!(\n                \"Wrong number of outputs, command line expected {}, found {:?}\",\n                count,\n                outputs.len()\n            );\n        }\n    }\n\n    if params.assertions.assert_outputs {\n        crate::utils::check_outputs(&outputs, params)?;\n    }\n\n    if let Some(facts) = &params.assertions.assert_output_facts {\n        let outputs: Vec<InferenceFact> =\n            outputs.iter().map(|t| t[0].datum_type().fact(t[0].shape()).into()).collect();\n        crate::utils::check_inferred(&outputs, facts)?;\n    }\n\n    if let Some(asserts) = &params.assertions.assert_op_count {\n        for (name, expected) in asserts {\n            let count = crate::utils::count_op(&*params.tract_model, name)?;\n            if count != *expected {\n                bail!(\"Wrong number of {} operators: expected {}, got {}\", name, expected, count);\n            }\n        }\n    }\n\n    if let Some(patterns) = &params.assertions.assert_op_only {\n        crate::utils::check_op_only(&*params.tract_model, patterns)?;\n    }\n\n    Ok(())\n}\n\nfn run_regular_t<F, O>(\n    state: &mut SimpleState<F, O>,\n    inputs: RunTensors,\n    steps: bool,\n    check_f16_overflow: bool,\n    assert_sane_floats: bool,\n    mut npz: Option<NpzWriter<File>>,\n) -> TractResult<TVec<Vec<TValue>>>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    let mut results = tvec!(vec!(); state.model().outputs.len());\n    let multiturn = inputs.sources.len() > 1;\n\n    // Build output→input cache mapping for unfolded KV cache threading.\n    // Output labeled \"{name}_concat\" feeds input named \"{name}\".\n    let cache_output_to_input: Vec<(usize, usize)> = if multiturn {\n        let model = state.model();\n        let mut mapping = Vec::new();\n        for (out_ix, out_outlet) in model.outputs.iter().enumerate() {\n            let out_label = model.outlet_label(*out_outlet).unwrap_or(\"\");\n            if let Some(base) = out_label.strip_suffix(\"_concat\") {\n                for (in_ix, in_outlet) in model.inputs.iter().enumerate() {\n                    let in_name = &model.nodes[in_outlet.node].name;\n                    if in_name == base {\n                        mapping.push((out_ix, in_ix));\n                        break;\n                    }\n                }\n            }\n        }\n        mapping\n    } else {\n        Vec::new()\n    };\n\n    let mut sources = inputs.sources;\n    for turn in 0..sources.len() {\n        let inputs = std::mem::replace(&mut sources[turn], TVec::new());\n        let turn_results =\n            state.run_plan_with_eval(inputs, |session_state, state, node, input| {\n                if steps {\n                    for (ix, i) in input.iter().enumerate() {\n                        eprintln!(\n                            \"{} {}{}{:?}\",\n                            White.bold().paint(node.to_string()),\n                            ix,\n                            Blue.bold().paint(\"<< \"),\n                            i\n                        );\n                    }\n                }\n                let r = tract_core::plan::eval(session_state, state, node, input)?;\n\n                if steps || npz.is_some() || check_f16_overflow || assert_sane_floats {\n                    let clarified_r = crate::utils::clarify_tvalues(&r)?;\n                    if steps {\n                        for (ix, o) in clarified_r.iter().enumerate() {\n                            eprintln!(\n                                \"{} {}{}{:?}\",\n                                White.bold().paint(node.to_string()),\n                                ix,\n                                Yellow.bold().paint(\">> \"),\n                                o\n                            );\n                        }\n                    }\n                    if let Some(npz) = npz.as_mut() {\n                        for (ix, t) in clarified_r.iter().enumerate() {\n                            let mut name = if ix == 0 {\n                                node.name.to_string()\n                            } else {\n                                format!(\"{}:{}\", node.name, ix)\n                            };\n                            if multiturn {\n                                name = format!(\"turn_{turn}/{name}\");\n                            }\n                            npz_add_tensor(npz, name, t)?;\n                        }\n                    }\n                    if check_f16_overflow {\n                        for (ix, o) in clarified_r.iter().enumerate() {\n                            if let Ok(plain) = o.try_as_plain() {\n                                if let Ok(f32s) = plain.as_slice::<f32>() {\n                                    if f32s.iter().any(|f| f.abs() > f16::MAX.to_f32()) {\n                                        warn!(\"{node}, output {ix} overflows f16\");\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    if assert_sane_floats {\n                        for (ix, o) in clarified_r.iter().enumerate() {\n                            if node.op_is::<Im2Col>() || node.op_is::<OptMatMulPack>() {\n                                continue;\n                            }\n                            if let Ok(plain) = o.try_as_plain() {\n                                if let Ok(floats) = plain.as_slice::<f32>() {\n                                    if let Some(pos) = floats.iter().position(|f| !f.is_finite()) {\n                                        eprintln!(\"{floats:?}\");\n                                        bail!(\"Found {} in output {} of {}\", floats[pos], ix, node);\n                                    }\n                                } else if let Ok(floats) = plain.as_slice::<f16>() {\n                                    if let Some(pos) = floats.iter().position(|f| !f.is_finite()) {\n                                        eprintln!(\"{floats:?}\");\n                                        bail!(\"Found {} in output {} of {}\", floats[pos], ix, node);\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n\n                Ok(r)\n            })?;\n        // Thread cache outputs into next turn's inputs\n        if turn + 1 < sources.len() && !cache_output_to_input.is_empty() {\n            for &(out_ix, in_ix) in &cache_output_to_input {\n                sources[turn + 1][in_ix] = turn_results[out_ix].clone();\n            }\n        }\n\n        izip!(&mut results, turn_results).for_each(|(r, tr)| r.push(tr));\n    }\n    Ok(results)\n}\n\nfn run_regular(\n    tract: &Arc<dyn Model>,\n    params: &Parameters,\n    _matches: &clap::ArgMatches,\n    sub_matches: &clap::ArgMatches,\n) -> TractResult<TVec<Vec<TValue>>> {\n    let run_params = crate::tensor::run_params_from_subcommand(params, sub_matches)?;\n\n    let steps = sub_matches.get_flag(\"steps\");\n    let check_f16_overflow = sub_matches.get_flag(\"check-f16-overflow\");\n    let assert_sane_floats = sub_matches.get_flag(\"assert-sane-floats\");\n    let npz = if let Some(npz) = sub_matches.get_one::<String>(\"save-steps\") {\n        let npz = fs::File::create(npz).with_context(|| format!(\"Creating {npz}\"))?;\n        Some(ndarray_npy::NpzWriter::new_compressed(npz))\n    } else {\n        None\n    };\n\n    let inputs = get_or_make_inputs(tract, &run_params)?;\n    if let Some(runnable) = &params.runnable {\n        if let Some(plan) = runnable.typed_plan() {\n            let mut state = plan.spawn()?;\n            let results = run_regular_t(\n                &mut state,\n                inputs,\n                steps,\n                check_f16_overflow,\n                assert_sane_floats,\n                npz,\n            )?;\n            Ok(results)\n        } else {\n            todo!(\"Run handler for abstract runtime/runnable\");\n        }\n    } else {\n        dispatch_model!(tract, |m| {\n            let plan_options = crate::plan_options::plan_options_from_subcommand(sub_matches)?;\n            let plan = SimplePlan::new_with_options(m, &plan_options)?;\n            let mut state = plan.spawn()?;\n\n            let results = run_regular_t(\n                &mut state,\n                inputs,\n                steps,\n                check_f16_overflow,\n                assert_sane_floats,\n                npz,\n            )?;\n            Ok(results)\n        })\n    }\n}\n\n/*\n#[cfg(feature = \"pulse\")]\nfn run_pulse_t(model: &PulsedModel, params: &Parameters) -> TractResult<TVec<TValue>> {\nlet input_fact = model.input_fact(0)?;\nlet output_fact = model.output_fact(0)?;\n\nlet output_pulse = output_fact.pulse();\n//    println!(\"output_fact: {:?}\", output_fact);\nlet axis = input_fact.axis;\nlet name = model.node_name(model.input_outlets()?[0].node);\nlet input: &Tensor = &params.tensors_values.by_name(name).unwrap().values.as_ref().unwrap()[0];\n//    println!(\"input_shape: {:?}\", input.shape());\nlet input_dim = input.shape()[axis];\n//    println!(\"output_fact: {:?}\", output_fact);\nlet output_dim = output_fact\n.dim\n.eval(&SymbolValues::default().with(stream_symbol(), input_dim as i64))\n.to_usize()?;\nlet mut output_shape = output_fact.shape.to_vec();\noutput_shape[output_fact.axis] =\n(output_dim as usize + output_fact.delay + 4 * output_fact.pulse()).to_dim();\nlet output_shape: TVec<usize> = output_shape.iter().map(|d| d.to_usize().unwrap()).collect();\nlet plan = SimplePlan::new(model)?;\nlet mut state = ::tract_core::plan::SimpleState::new(&plan)?;\n//    println!(\"output_shape: {:?}\", output_shape);\nlet pulse = input_fact.pulse();\nlet mut result = tract_ndarray::ArrayD::<f32>::default(&*output_shape);\nlet input = input.to_array_view::<f32>()?;\nfor ix in 0..input_dim.divceil(pulse) {\nlet chunk =\ninput.slice_axis(tract_ndarray::Axis(axis), (ix * pulse..(ix + 1) * pulse).into());\nlet input = if chunk.shape()[input_fact.axis] < pulse {\nlet mut chunk_shape = chunk.shape().to_vec();\nchunk_shape[input_fact.axis] = pulse;\nlet mut padded_chunk = tract_ndarray::ArrayD::<f32>::default(chunk_shape);\npadded_chunk\n.slice_axis_mut(\ntract_ndarray::Axis(input_fact.axis),\n(..chunk.shape()[input_fact.axis]).into(),\n)\n.assign(&chunk);\npadded_chunk\n} else {\nchunk.to_owned()\n};\nlet outputs = state.run(tvec!(input.into_tensor().into()))?;\nlet result_chunk = outputs[0].to_array_view::<f32>()?;\nresult\n.slice_axis_mut(\ntract_ndarray::Axis(output_fact.axis),\n((output_pulse * ix)..(output_pulse * (ix + 1))).into(),\n)\n.assign(&result_chunk);\n}\nresult.slice_axis_inplace(tract_ndarray::Axis(output_fact.axis), (output_fact.delay..).into());\nresult\n.slice_axis_inplace(tract_ndarray::Axis(output_fact.axis), (..output_dim as usize).into());\nOk(tvec!(result.into_tvalue()))\n}\n*/\n"
  },
  {
    "path": "cli/src/runtimes.rs",
    "content": "use tract_core::internal::*;\n\n#[derive(Default, Debug, Copy, Clone)]\npub struct UnoptimizedRuntime;\n\nregister_runtime!(UnoptimizedRuntime = UnoptimizedRuntime);\n\nimpl Runtime for UnoptimizedRuntime {\n    fn name(&self) -> StaticName {\n        Cow::Borrowed(\"unoptimized\")\n    }\n\n    fn prepare_with_options(\n        &self,\n        model: TypedModel,\n        options: &RunOptions,\n    ) -> TractResult<Box<dyn Runnable>> {\n        Ok(Box::new(SimplePlan::new_with_options(model, options)?))\n    }\n    fn check(&self) -> TractResult<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cli/src/tensor.rs",
    "content": "use tract_core::internal::*;\nuse tract_libcli::tensor::RunParams;\n#[cfg(feature = \"transformers\")]\nuse tract_transformers::figure_out_causal_llm_b_s_p;\n\nuse crate::params::Parameters;\n\npub fn run_params_from_subcommand(\n    params: &Parameters,\n    sub_matches: &clap::ArgMatches,\n) -> TractResult<RunParams> {\n    let mut tv = params.tensors_values.clone();\n\n    if let Some(bundle) = sub_matches.get_many::<String>(\"input-from-npz\") {\n        for input in bundle {\n            let input = input.as_str();\n            for tensor in Parameters::parse_npz(input, true, false)? {\n                tv.add(tensor);\n            }\n        }\n    }\n\n    if let Some(dir) = sub_matches.get_one::<String>(\"input-from-nnef\") {\n        for tensor in Parameters::parse_nnef_tensors(dir, true, false)? {\n            tv.add(tensor);\n        }\n    }\n\n    // We also support the global arg variants for backward compatibility\n    #[allow(unused_mut)]\n    let mut allow_random_input: bool =\n        params.allow_random_input || sub_matches.get_flag(\"allow-random-input\");\n    let allow_float_casts: bool =\n        params.allow_float_casts || sub_matches.get_flag(\"allow-float-casts\");\n\n    let mut symbols = SymbolValues::default();\n\n    #[cfg(feature = \"transformers\")]\n    if let Some(pp) = sub_matches.get_one::<String>(\"pp\") {\n        let value: i64 =\n            pp.parse().with_context(|| format!(\"Can not parse symbol value in --pp {pp}\"))?;\n        let Some(typed_model) = params.tract_model.downcast_ref::<TypedModel>() else {\n            bail!(\"PP mode can only be used with a TypedModel\");\n        };\n        let (b, s, p) = figure_out_causal_llm_b_s_p(typed_model)?;\n        if let Some(b) = b {\n            symbols.set(&b, 1);\n        }\n\n        ensure!(s.is_some() && p.is_some(), \"Could not find LLM symbols in model\");\n        symbols.set(&p.unwrap(), 0);\n        symbols.set(&s.unwrap(), value);\n        allow_random_input = true\n    }\n\n    #[cfg(feature = \"transformers\")]\n    if let Some(tg) = sub_matches.get_one::<String>(\"tg\") {\n        let value: i64 =\n            tg.parse().with_context(|| format!(\"Can not parse symbol value in --tg {tg}\"))?;\n        let Some(typed_model) = params.tract_model.downcast_ref::<TypedModel>() else {\n            bail!(\"TG mode can only be used with a TypedModel\");\n        };\n        let (b, s, p) = figure_out_causal_llm_b_s_p(typed_model)?;\n        if let Some(b) = b {\n            symbols.set(&b, 1);\n        }\n\n        ensure!(s.is_some() && p.is_some(), \"Could not find LLM symbols in model\");\n        symbols.set(&p.unwrap(), value - 1);\n        symbols.set(&s.unwrap(), 1);\n        allow_random_input = true\n    }\n\n    if let Some(set) = sub_matches.get_many::<String>(\"set\") {\n        for set in set {\n            let set = set.as_str();\n            let (sym, value) = set.split_once('=').context(\"--set expect S=12 form\")?;\n            let sym = params.tract_model.get_or_intern_symbol(sym);\n            let value: i64 = value\n                .parse()\n                .with_context(|| format!(\"Can not parse symbol value in set {set}\"))?;\n            symbols.set(&sym, value);\n        }\n    }\n\n    let prompt_chunk_size = sub_matches\n        .get_one::<String>(\"prompt-chunk-size\")\n        .and_then(|chunk_size| chunk_size.parse().ok());\n    Ok(RunParams {\n        tensors_values: tv,\n        allow_random_input,\n        allow_float_casts,\n        symbols,\n        prompt_chunk_size,\n    })\n}\n"
  },
  {
    "path": "cli/src/utils.rs",
    "content": "use std::iter::once;\n\nuse crate::params::Parameters;\nuse tract_hir::internal::*;\nuse tract_itertools::Itertools;\nuse tract_libcli::model::Model;\n\n/// Compares the outputs of a node in tract and tensorflow.\npub fn check_outputs(got: &[Vec<TValue>], params: &Parameters) -> TractResult<()> {\n    let mut error = None;\n    // iter over all possible tract model outputs\n    for (ix, output) in params.tract_model.output_outlets().iter().enumerate() {\n        // get either name from outlet_label or from node_name\n        let mut lookup_names: Vec<&str> = params\n            .tract_model\n            .outlet_label(*output)\n            .into_iter()\n            .chain(once(params.tract_model.node_name(output.node)))\n            .collect_vec();\n        lookup_names.dedup();\n        let exp = lookup_names.iter().find_map(|name| params.tensors_values.by_name(name));\n        if exp.is_none() {\n            if params.assertions.allow_missing_outputs {\n                debug!(\"Missing reference output in bundle for {}\", lookup_names.join(\" or \"));\n                continue;\n            } else {\n                bail!(\"Missing reference output in bundle for {}\", lookup_names.join(\" or \"));\n            }\n        }\n        let exp = exp.unwrap();\n        debug!(\"Output {ix}, expects {exp:?}\");\n        let mut exp: TValue = exp.values.as_ref().with_context(|| {\n            format!(\"Output {lookup_names:?}: found reference info without value: {exp:?}\")\n        })?[0]\n            .clone();\n\n        let props = params.tract_model.properties();\n\n        let got: TValue = if got[ix].len() > 1 && props.get(\"pulse.output_axes\").is_some() {\n            let axis = props.get(\"pulse.output_axes\").unwrap().try_as_plain()?.as_slice::<i64>()?\n                [ix] as usize;\n            let delay =\n                props.get(\"pulse.delay\").unwrap().try_as_plain()?.as_slice::<i64>()?[ix] as usize;\n            let stacked = Tensor::stack_tensors(axis, &got[ix])?;\n            stacked.slice(axis, delay, delay + exp.shape()[axis])?.into()\n        } else {\n            // This handles LLM prompt-chunking output\n            got[ix].last().unwrap().clone()\n        };\n        if (params.allow_float_casts\n            && exp.datum_type() == f32::datum_type()\n            && got.datum_type() == f16::datum_type())\n            || exp.datum_type().unquantized() == got.datum_type().unquantized()\n        {\n            exp = exp.cast_to_dt(got.datum_type())?.into_owned().into_tvalue();\n        }\n        #[allow(unused)]\n        let result: TractResult<()> = if let Some(min_rbo) = params.assertions.assert_llm_rbo {\n            #[cfg(not(feature = \"transformers\"))]\n            {\n                bail!(\"transformers feature is required for RBO metric\")\n            }\n            #[cfg(feature = \"transformers\")]\n            {\n                let rbo = crate::llm::top_logits_rbo(\n                    &exp,\n                    &got,\n                    params.assertions.assert_llm_rbo_p,\n                    params.assertions.assert_llm_rbo_depth,\n                )?;\n                let rbo = (rbo * 100.0).floor() / 100.0;\n                info!(\"LLM RBO: {rbo:.2}\");\n                if rbo >= min_rbo {\n                    Ok(())\n                } else {\n                    TractResult::Err(anyhow!(\n                        \"RBO criteria not met: rbo={rbo:.2}, min required {min_rbo}\"\n                    ))\n                }\n            }\n        } else {\n            exp.close_enough(&got, params.assertions.approximation)\n        };\n        if let Err(e) = result.context(format!(\"Checking output {ix}\")) {\n            if error.is_some() {\n                error!(\"{e:?}\");\n            } else {\n                error = Some(e);\n            }\n        } else {\n            info!(\"Checked output #{ix}, ok.\");\n        }\n    }\n\n    if let Some(e) = error { Err(e) } else { Ok(()) }\n}\n\n/// Compares the outputs of a node in tract and tensorflow.\npub fn check_inferred(got: &[InferenceFact], expected: &[InferenceFact]) -> TractResult<()> {\n    if got.len() != expected.len() {\n        bail!(\"Number of output differ: got:{}, expected:{}\", got.len(), expected.len())\n    }\n\n    for (got, exp) in got.iter().zip(expected.iter()) {\n        if exp.datum_type != got.datum_type {\n            bail!(\"Failed to infer datum type: expected {:?}, got {:?}\", exp, got);\n        }\n        if exp.shape != got.shape {\n            bail!(\"Failed to infer shape: expected {:?}, got {:?}\", exp, got);\n        }\n    }\n\n    Ok(())\n}\n\npub fn clarify_tvalue(t: &TValue) -> TractResult<TValue> {\n    Ok((*t).clone())\n}\n\npub fn clarify_tvalues(values: &TVec<TValue>) -> TractResult<TVec<TValue>> {\n    values.iter().map(clarify_tvalue).collect()\n}\n\npub fn clarify_typed_fact<'a>(fact: impl Into<Cow<'a, TypedFact>>) -> Cow<'a, TypedFact> {\n    let fact = fact.into();\n    if fact.is_exotic() {\n        fact.exotic_fact\n            .as_ref()\n            .and_then(|it| it.clarify_dt_shape())\n            .map(|(dt, s)| Cow::Owned(TypedFact::dt_shape(dt, s)))\n            .unwrap_or_else(|| fact)\n    } else {\n        fact\n    }\n}\n\nfn matches_pattern(op_name: &str, pattern: &str) -> bool {\n    if let Some(prefix) = pattern.strip_suffix('*') {\n        op_name.starts_with(prefix)\n    } else {\n        op_name == pattern\n    }\n}\n\npub fn check_op_only(model: &dyn Model, patterns: &[String]) -> TractResult<()> {\n    let mut unexpected = vec![];\n    for node_id in model.eval_order()? {\n        let op = model.node_op_name(node_id);\n        if !patterns.iter().any(|p| matches_pattern(&op, p)) {\n            let name = model.node_name(node_id);\n            unexpected.push(format!(\"  {name} ({op})\"));\n        }\n    }\n    unexpected.sort();\n    unexpected.dedup();\n    if unexpected.is_empty() {\n        Ok(())\n    } else {\n        bail!(\"Model has {} unexpected op(s):\\n{}\", unexpected.len(), unexpected.join(\"\\n\"))\n    }\n}\n\npub fn count_op(model: &dyn Model, name: &str) -> TractResult<usize> {\n    Ok(model\n        .eval_order()\n        .context(\"Cannot assert op count without an eval order\")?\n        .into_iter()\n        .map(|i| {\n            if model.node_op_name(i) == name {\n                1\n            } else {\n                model.nested_models(i).into_iter().flat_map(|(_, m)| count_op(m, name)).sum()\n            }\n        })\n        .sum())\n}\n"
  },
  {
    "path": "core/Cargo.toml",
    "content": "[package]\nname = \"tract-core\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\naccelerate-src = { workspace = true, optional = true }\nanyhow.workspace = true\nanymap3.workspace = true\nbit-set.workspace = true\nblis-src = { version = \"0.2\", features = [\"static\", \"pthreads\"], optional = true }\ncblas = { version = \"0.5\", optional = true }\nderive-new.workspace = true\ndowncast-rs.workspace = true\nerased-serde.workspace = true\ndyn-clone.workspace = true\ndyn-eq.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\nmaplit.workspace = true\nndarray.workspace = true\nnum-integer.workspace = true\nnum-traits.workspace = true\nnum-complex.workspace = true\nopenblas-src = { workspace=true, optional = true }\npastey.workspace = true\nrustfft.workspace = true\nsmallvec.workspace = true\nserde.workspace = true\ntract-linalg.workspace = true\ntract-data.workspace = true\ninventory.workspace = true\n\n[features]\ndefault = [ ]\ncomplex = [ \"tract-data/complex\", \"tract-linalg/complex\" ]\nblas = [ \"cblas\" ]\naccelerate = [ \"blas\", \"accelerate-src\" ]\nblis = [ \"blas\", \"blis-src\" ]\nopenblas = [ \"blas\", \"openblas-src\" ]\nparanoid_assertions = []\n\n[dev-dependencies]\nenv_logger.workspace = true\nlazy_static.workspace = true\napprox.workspace = true\n\n[target.'cfg(not(target_family = \"wasm\"))'.dev-dependencies]\ncriterion.workspace = true\nproptest.workspace = true\n\n[target.'cfg(target_family = \"wasm\")'.dev-dependencies]\n# Wasm doesn't support the `rayon` feature of criterion\ncriterion = { version = \"0.8\", default-features = false, features = [\"plotters\", \"cargo_bench_support\"] }\n# Wasm doesn't support the `fork` feature of proptest.\nproptest = { version = \"1.0.0\", default-features = false, features = [\"std\", \"bit-set\"] }\n"
  },
  {
    "path": "core/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "core/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "core/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "core/src/axes/mapping.rs",
    "content": "use std::fmt::Display;\nuse std::str::FromStr;\n\nuse tract_data::itertools::izip;\nuse tract_ndarray::{ArrayViewD, ArrayViewMutD};\n\nuse crate::internal::*;\nuse crate::prelude::tract_itertools::Itertools;\n\nuse super::Axis;\n\npub trait AxisPattern: std::fmt::Debug {\n    fn search(&self, mapping: &AxesMapping) -> Option<usize>;\n}\n\nimpl AxisPattern for char {\n    fn search(&self, mapping: &AxesMapping) -> Option<usize> {\n        mapping.axes.iter().position(|axis| axis.repr == *self)\n    }\n}\n\nimpl AxisPattern for (InOut, usize) {\n    fn search(&self, mapping: &AxesMapping) -> Option<usize> {\n        match self.0 {\n            InOut::In(i) => mapping.axes.iter().position(|axis| axis.inputs[i].contains(&self.1)),\n            InOut::Out(o) => mapping.axes.iter().position(|axis| axis.outputs[o].contains(&self.1)),\n        }\n    }\n}\n\nimpl AxisPattern for &Axis {\n    fn search(&self, mapping: &AxesMapping) -> Option<usize> {\n        mapping.axes.iter().position(|ax| self == &ax)\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct AxesMapping {\n    input_count: usize,\n    output_count: usize,\n    axes: TVec<Axis>,\n}\n\nimpl AxesMapping {\n    pub fn new(\n        input_count: usize,\n        output_count: usize,\n        it: impl AsRef<[Axis]>,\n    ) -> TractResult<AxesMapping> {\n        let axes: TVec<_> = it.as_ref().into();\n        AxesMapping { axes, output_count, input_count }.sorted().check()\n    }\n\n    pub fn for_numpy_matmul(\n        rank: usize,\n        transposing_a: bool,\n        transposing_b: bool,\n        transposing_c: bool,\n    ) -> TractResult<AxesMapping> {\n        let mut axes: TVec<Axis> = ('a'..)\n            .take(rank - 2)\n            .enumerate()\n            .map(|(ix, repr)| Axis {\n                repr,\n                inputs: tvec!(tvec!(ix), tvec!(ix)),\n                outputs: tvec!(tvec!(ix)),\n            })\n            .collect();\n        axes.push(Axis {\n            repr: 'm',\n            inputs: tvec!(tvec!(rank - 2 + transposing_a as usize), tvec!()),\n            outputs: tvec!(tvec!(rank - 2 + transposing_c as usize)),\n        });\n        axes.push(Axis {\n            repr: 'k',\n            inputs: tvec!(\n                tvec!(rank - 1 - transposing_a as usize),\n                tvec!(rank - 2 + transposing_b as usize)\n            ),\n            outputs: tvec!(tvec!()),\n        });\n        axes.push(Axis {\n            repr: 'n',\n            inputs: tvec!(tvec!(), tvec!(rank - 1 - transposing_b as usize),),\n            outputs: tvec!(tvec!(rank - 1 - transposing_c as usize)),\n        });\n        AxesMapping::new(2, 1, axes)\n    }\n\n    pub fn disconnected(inputs: &[&TypedFact], outputs: &[&TypedFact]) -> TractResult<AxesMapping> {\n        let input_ranks: TVec<usize> = inputs.iter().map(|i| i.rank()).collect();\n        let output_ranks: TVec<usize> = outputs.iter().map(|i| i.rank()).collect();\n        Self::disconnected_for_ranks(&input_ranks, &output_ranks)\n    }\n\n    pub fn disconnected_for_ranks(inputs: &[usize], outputs: &[usize]) -> TractResult<AxesMapping> {\n        let mut axes = tvec!();\n        let mut alphabet = 'a'..;\n        for (ix, &rank) in inputs.iter().enumerate() {\n            for a in 0..rank {\n                axes.push(\n                    Axis::new(alphabet.next().unwrap(), inputs.len(), outputs.len()).input(ix, a),\n                );\n            }\n        }\n        for (ix, &rank) in outputs.iter().enumerate() {\n            for a in 0..rank {\n                axes.push(\n                    Axis::new(alphabet.next().unwrap(), inputs.len(), outputs.len()).output(ix, a),\n                );\n            }\n        }\n        AxesMapping::new(inputs.len(), outputs.len(), axes)\n    }\n\n    pub fn natural(inputs: &[&TypedFact], outputs: &[&TypedFact]) -> TractResult<AxesMapping> {\n        let rank = inputs[0].rank();\n        let axes = (0..rank)\n            .zip('a'..)\n            .map(|(axis_id, repr)| Axis::natural(inputs.len(), outputs.len(), repr, axis_id))\n            .collect::<TVec<_>>();\n        AxesMapping::new(inputs.len(), outputs.len(), axes)\n    }\n\n    pub fn natural_for_rank(\n        inputs: usize,\n        outputs: usize,\n        rank: usize,\n    ) -> TractResult<AxesMapping> {\n        let axes = (0..rank)\n            .zip('a'..)\n            .map(|(axis_id, repr)| Axis::natural(inputs, outputs, repr, axis_id))\n            .collect::<TVec<_>>();\n        AxesMapping::new(inputs, outputs, axes)\n    }\n\n    pub fn iter_all_axes(&self) -> impl Iterator<Item = &Axis> {\n        self.axes.iter()\n    }\n\n    pub fn iter_all_axes_mut(&mut self) -> impl Iterator<Item = &mut Axis> {\n        self.axes.iter_mut()\n    }\n\n    pub fn input_count(&self) -> usize {\n        self.input_count\n    }\n\n    pub fn output_count(&self) -> usize {\n        self.output_count\n    }\n\n    pub fn axis_positions(&self, io: InOut, p: impl AxisPattern) -> TractResult<&[usize]> {\n        let axis = self.axis(p)?;\n        Ok(match io {\n            InOut::In(i) => &*axis.inputs[i],\n            InOut::Out(o) => &*axis.outputs[o],\n        })\n    }\n\n    pub fn rank(&self, io: InOut) -> usize {\n        match io {\n            InOut::In(i) => self.iter_all_axes().map(|axis| axis.inputs[i].len()).sum(),\n            InOut::Out(o) => self.iter_all_axes().map(|axis| axis.outputs[o].len()).sum(),\n        }\n    }\n\n    fn search(&self, p: impl AxisPattern) -> TractResult<usize> {\n        p.search(self).with_context(|| format!(\"Axis {p:?} not found in {self}\"))\n    }\n\n    pub fn axis(&self, p: impl AxisPattern) -> TractResult<&Axis> {\n        Ok(&self.axes[self.search(p)?])\n    }\n\n    fn axis_mut(&mut self, p: impl AxisPattern) -> TractResult<&mut Axis> {\n        let ix = self.search(p)?;\n        Ok(&mut self.axes[ix])\n    }\n\n    pub fn axes(&self, io: InOut) -> impl Iterator<Item = &Axis> {\n        (0..self.rank(io)).map(move |ix| self.axis((io, ix)).unwrap())\n    }\n\n    pub fn track_axis(&self, from: impl AxisPattern, to: InOut) -> TractResult<Option<usize>> {\n        let axis = self.axis(from)?;\n        let positions = axis.interface(to);\n        Ok(if positions.len() == 1 { Some(positions[0]) } else { None })\n    }\n\n    pub fn renaming(mut self, axis: impl AxisPattern, name: char) -> TractResult<AxesMapping> {\n        let position = self.search(axis)?;\n        let old_label = self.axes[position].repr;\n        if let Ok(conflict) = self.axis_mut(name) {\n            conflict.repr = old_label\n        }\n        self.axes[position].repr = name;\n        self.sort();\n        self.check()\n    }\n\n    pub fn linking(\n        mut self,\n        target: impl AxisPattern,\n        axis: impl AxisPattern,\n    ) -> TractResult<AxesMapping> {\n        let axis = self.axis(axis)?;\n        let axis_ix = self.axes.iter().position(|a| a == axis).unwrap();\n        let axis = self.axes.remove(axis_ix);\n        let target = self.axis_mut(target)?;\n        for (ia, ib) in target.inputs.iter_mut().zip(axis.inputs.iter()) {\n            ia.extend(ib.into_iter().cloned())\n        }\n        for (ia, ib) in target.outputs.iter_mut().zip(axis.outputs.iter()) {\n            ia.extend(ib.into_iter().cloned())\n        }\n        self.sort();\n        self.check()\n    }\n\n    fn sort(&mut self) {\n        let order: Vec<(usize, usize, usize, char)> = self\n            .axes\n            .iter()\n            .flat_map(|axis| {\n                axis.inputs\n                    .iter()\n                    .enumerate()\n                    .flat_map(move |(slot, input)| {\n                        input.iter().map(move |p| (1, slot, *p, axis.repr))\n                    })\n                    .chain(axis.outputs.iter().enumerate().flat_map(move |(slot, output)| {\n                        output.iter().map(move |p| (0, slot, *p, axis.repr))\n                    }))\n            })\n            .sorted()\n            .dedup()\n            .collect_vec();\n        self.axes.sort_by_key(|axis| order.iter().position(|tuple| tuple.3 == axis.repr).unwrap());\n    }\n\n    fn sorted(mut self) -> AxesMapping {\n        self.sort();\n        self\n    }\n\n    fn do_check(&self) -> TractResult<()> {\n        for axis in &self.axes {\n            ensure!(axis.inputs.len() == self.input_count);\n            ensure!(axis.outputs.len() == self.output_count);\n            ensure!(\n                axis.inputs.iter().map(|i| i.len()).sum::<usize>()\n                    + axis.outputs.iter().map(|o| o.len()).sum::<usize>()\n                    > 0\n            );\n        }\n        for input_ix in 0..self.input_count() {\n            for axis in 0..self.rank(InOut::In(input_ix)) {\n                ensure!(self.axis((InOut::In(input_ix), axis)).is_ok());\n            }\n        }\n        for output_ix in 0..self.output_count() {\n            for axis in 0..self.rank(InOut::Out(output_ix)) {\n                ensure!(self.axis((InOut::Out(output_ix), axis)).is_ok());\n            }\n        }\n        ensure!(self.axes.iter().map(|ax| ax.repr).duplicates().count() == 0);\n        ensure!(\n            self == &{\n                let mut x = self.clone();\n                x.sort();\n                x\n            }\n        );\n        Ok(())\n    }\n\n    pub fn check(self) -> TractResult<AxesMapping> {\n        self.do_check().with_context(|| format!(\"Checking {:?}\", self.axes))?;\n        Ok(self)\n    }\n\n    pub fn available_label(&self) -> char {\n        self.available_labels().next().unwrap()\n    }\n\n    pub fn available_labels(&self) -> impl Iterator<Item = char> + '_ {\n        ('a'..).filter(|c| self.iter_all_axes().all(|axis| axis.repr != *c))\n    }\n\n    pub fn is_element_wise_unary(&self) -> bool {\n        self.input_count == 1\n            && self.output_count == 1\n            && self\n                .iter_all_axes()\n                .all(|axis| axis.inputs[0].len() == 1 && axis.outputs[0] == axis.inputs[0])\n    }\n\n    pub fn extract_sub_mapping(\n        &self,\n        inputs: &[usize],\n        outputs: &[usize],\n    ) -> TractResult<AxesMapping> {\n        let axes: Vec<_> = self\n            .iter_all_axes()\n            .filter(|axis| {\n                inputs.iter().any(|i| axis.inputs[*i].len() > 0)\n                    || outputs.iter().any(|o| axis.outputs[*o].len() > 0)\n            })\n            .map(|axis| Axis {\n                inputs: axis\n                    .inputs\n                    .iter()\n                    .enumerate()\n                    .filter(|(ix, _)| inputs.contains(ix))\n                    .map(|(_, it)| it.clone())\n                    .collect(),\n                outputs: axis\n                    .outputs\n                    .iter()\n                    .enumerate()\n                    .filter(|(ix, _)| outputs.contains(ix))\n                    .map(|(_, it)| it.clone())\n                    .collect(),\n                repr: axis.repr,\n            })\n            .collect();\n        AxesMapping::new(inputs.len(), outputs.len(), axes)\n    }\n\n    pub fn relabel(mut self) -> TractResult<AxesMapping> {\n        for (ax, repr) in self.axes.iter_mut().zip('a'..) {\n            ax.repr = repr;\n        }\n        Ok(self)\n    }\n\n    pub fn remove_axis(&self, repr: char) -> TractResult<AxesMapping> {\n        let mut axes: TVec<Axis> =\n            self.axes.iter().filter(|axis| axis.repr != repr).cloned().collect();\n        let removed = self.axis(repr).context(\"Axis not found\")?;\n        for input in 0..self.input_count {\n            for &position in &removed.inputs[input] {\n                for other in &mut axes {\n                    other.inputs[input]\n                        .iter_mut()\n                        .for_each(|other_pos| *other_pos -= (*other_pos > position) as usize);\n                }\n            }\n        }\n        for output in 0..self.output_count {\n            for &position in &removed.outputs[output] {\n                for other in &mut axes {\n                    other.outputs[output]\n                        .iter_mut()\n                        .for_each(|other_pos| *other_pos -= (*other_pos > position) as usize);\n                }\n            }\n        }\n        AxesMapping::new(self.input_count, self.output_count, axes)\n    }\n\n    pub fn remove_axis_occurency(&self, slot: InOut, position: usize) -> TractResult<AxesMapping> {\n        let axis = self.axis((slot, position))?;\n        if axis.inputs.iter().map(|i| i.len()).sum::<usize>()\n            + axis.outputs.iter().map(|i| i.len()).sum::<usize>()\n            == 1\n        {\n            return self.remove_axis(axis.repr);\n        }\n        let mut axes = self.axes.clone();\n        match slot {\n            InOut::In(slot) => {\n                for axis in &mut axes {\n                    axis.inputs[slot].retain(|pos| *pos != position);\n                    axis.inputs[slot].iter_mut().for_each(|pos| *pos -= (*pos > position) as usize);\n                }\n            }\n            InOut::Out(slot) => {\n                for axis in &mut axes {\n                    axis.outputs[slot].retain(|pos| *pos != position);\n                    axis.outputs[slot]\n                        .iter_mut()\n                        .for_each(|pos| *pos -= (*pos > position) as usize);\n                }\n            }\n        }\n        AxesMapping::new(self.input_count, self.output_count, axes)\n    }\n\n    pub fn remove_slot(&self, slot: InOut) -> TractResult<AxesMapping> {\n        let mut axes = self.clone();\n        while axes.rank(slot) > 0 {\n            axes = axes.remove_axis_occurency(slot, 0)?\n        }\n        match slot {\n            InOut::In(slot) => {\n                for axis in &mut axes.axes {\n                    axis.inputs.remove(slot);\n                }\n                axes.input_count -= 1;\n            }\n            InOut::Out(slot) => {\n                for axis in &mut axes.axes {\n                    axis.outputs.remove(slot);\n                }\n                axes.output_count -= 1;\n            }\n        }\n        axes.sorted().check()\n    }\n\n    pub fn with_extra_input(self, slot: usize) -> TractResult<AxesMapping> {\n        let axes: TVec<Axis> = self\n            .iter_all_axes()\n            .map(|axis| {\n                let mut axis = axis.clone();\n                axis.inputs.insert(slot, tvec!());\n                axis\n            })\n            .collect();\n        AxesMapping::new(self.input_count + 1, self.output_count, axes)\n    }\n\n    pub fn with_extra_axis(\n        mut self,\n        repr: char,\n        io: InOut,\n        position: usize,\n    ) -> TractResult<AxesMapping> {\n        let axis = Axis::new(repr, self.input_count, self.output_count);\n        self.axes.push(axis);\n        self.with_extra_axis_occurency(repr, io, position)\n    }\n\n    pub fn with_extra_axis_occurency(\n        mut self,\n        axis: impl AxisPattern,\n        io: InOut,\n        position: usize,\n    ) -> TractResult<AxesMapping> {\n        match io {\n            InOut::In(slot) => {\n                self.axes.iter_mut().for_each(|axis| {\n                    axis.inputs[slot].iter_mut().for_each(|pos| *pos += (*pos >= position) as usize)\n                });\n                self.axis_mut(axis)?.inputs[slot].push(position);\n            }\n            InOut::Out(slot) => {\n                self.axes.iter_mut().for_each(|axis| {\n                    axis.outputs[slot]\n                        .iter_mut()\n                        .for_each(|pos| *pos += (*pos >= position) as usize)\n                });\n                self.axis_mut(axis)?.outputs[slot].push(position);\n            }\n        }\n        self.sort();\n        self.check()\n    }\n\n    pub fn translate_to_axis_ops(&self) -> TractResult<Vec<AxisOp>> {\n        ensure!(self.input_count() == 1);\n        ensure!(self.output_count() == 1);\n        ensure!(self.iter_all_axes().all(|axis| axis.inputs[0].len() <= 1));\n        let rms = self\n            .iter_all_axes()\n            .filter(|a| a.outputs[0].len() == 0)\n            .sorted_by_key(|axis| -(axis.inputs[0][0] as isize))\n            .collect_vec();\n        let adds = self\n            .iter_all_axes()\n            .filter(|a| a.inputs[0].len() == 0)\n            .sorted_by_key(|axis| axis.outputs[0][0] as isize)\n            .collect_vec();\n        let permutation = rms\n            .iter()\n            .chain(adds.iter())\n            .try_fold(self.clone(), |mapping, axis| mapping.remove_axis(axis.repr))?;\n        let permutation = permutation\n            .iter_all_axes()\n            .sorted_by_key(|axis| axis.outputs[0][0])\n            .map(|axis| axis.inputs[0][0])\n            .collect_vec();\n        let permutation = perm_to_ops(&permutation);\n        let rms = rms.iter().map(|axis| AxisOp::Rm(axis.inputs[0][0]));\n        let adds = adds.iter().map(|axis| AxisOp::Add(axis.outputs[0][0]));\n        Ok(rms.chain(permutation).chain(adds).collect())\n    }\n\n    pub fn from_strs(\n        inputs: &[impl AsRef<str>],\n        outputs: &[impl AsRef<str>],\n    ) -> TractResult<AxesMapping> {\n        let mut axes = HashMap::<char, Axis>::default();\n        for (input_ix, input) in inputs.iter().enumerate() {\n            for (ix, axis) in input.as_ref().chars().enumerate() {\n                axes.entry(axis)\n                    .or_insert_with(|| Axis::new(axis, inputs.len(), outputs.len().max(1)))\n                    .add_input(input_ix, ix);\n            }\n        }\n        for (output_ix, output) in outputs.iter().enumerate() {\n            for (ix, axis) in output.as_ref().chars().enumerate() {\n                axes.entry(axis)\n                    .or_insert_with(|| Axis::new(axis, inputs.len(), outputs.len().max(1)))\n                    .add_output(output_ix, ix);\n            }\n        }\n        if outputs.len() == 0 {\n            axes.iter_mut()\n                .sorted_by_key(|(k, _)| *k)\n                .filter(|(_, v)| v.inputs.iter().map(|input| input.len()).sum::<usize>() == 1)\n                .enumerate()\n                .for_each(|(ix, (_, v))| v.add_output(0, ix))\n        }\n        Self::new(\n            inputs.len(),\n            outputs.len().max(1),\n            axes.into_iter().sorted_by_key(|(k, _)| *k).map(|(_, v)| v).collect_vec(),\n        )\n    }\n\n    pub fn to_strs(&self) -> (TVec<String>, TVec<String>) {\n        let mut inputs = tvec![];\n        let mut outputs = tvec![];\n        for input in 0..self.input_count() {\n            let s = self\n                .iter_all_axes()\n                .flat_map(|axis| {\n                    axis.inputs[input].iter().map(move |position| (position, axis.repr))\n                })\n                .sorted()\n                .map(|(_, r)| r)\n                .collect();\n            inputs.push(s);\n        }\n        for output in 0..self.output_count() {\n            let s = self\n                .iter_all_axes()\n                .flat_map(|axis| {\n                    axis.outputs[output].iter().map(move |position| (position, axis.repr))\n                })\n                .sorted()\n                .map(|(_, r)| r)\n                .collect();\n            outputs.push(s);\n        }\n        (inputs, outputs)\n    }\n\n    pub fn change_axis_sink(&self, io: InOut, change: &AxisOp) -> TractResult<Option<AxesMapping>> {\n        let (mut inputs, mut outputs) = self.to_strs();\n        let interface: &mut String = match io {\n            InOut::In(i) => &mut inputs[i],\n            InOut::Out(o) => &mut outputs[o],\n        };\n        let mut axes: Vec<char> = interface.chars().collect();\n        match change {\n            AxisOp::Rm(rm) => {\n                axes.remove(*rm);\n            }\n            AxisOp::Add(add) => axes.insert(*add, self.available_label()),\n            AxisOp::Move(from, to) => {\n                let c = axes.remove(*from);\n                axes.insert(*to, c);\n            }\n            _ => return Ok(None),\n        };\n        *interface = axes.into_iter().collect();\n        Ok(Some(AxesMapping::from_strs(&inputs, &outputs)?))\n    }\n\n    pub fn direct(&self, a: InOut, b: InOut) -> bool {\n        self.axes.iter().all(|axis| axis.interface(a) == axis.interface(b))\n    }\n\n    pub fn same_layout<D: DimLike>(\n        &self,\n        a: InOut,\n        b: InOut,\n        shape_a: impl AsRef<[D]>,\n        shape_b: impl AsRef<[D]>,\n    ) -> bool {\n        let shape_a = shape_a.as_ref();\n        let shape_b = shape_b.as_ref();\n        shape_a.iter().cloned().product::<D>() == shape_b.iter().cloned().product()\n            && izip!(\n                self.axes(a).zip(shape_a.iter()).filter(|(_axis, d)| **d != D::one()),\n                self.axes(b).zip(shape_b.iter()).filter(|(_axis, d)| **d != D::one())\n            )\n            .all(|(a, b)| a == b)\n    }\n\n    pub fn axis_ops_to_canonical(&self, io: InOut) -> TractResult<Vec<AxisOp>> {\n        let rank = self.rank(io);\n        let target_rank = self.axes.len();\n        let mut next_insert_axis = 0;\n        let mut permutation = tvec!();\n        for axis in &self.axes {\n            let spec = match io {\n                InOut::In(i) => axis.inputs[i].first(),\n                InOut::Out(o) => axis.outputs[o].first(),\n            };\n            if let Some(pos_in_a) = spec {\n                permutation.push(pos_in_a + target_rank - rank)\n            } else {\n                permutation.push(next_insert_axis);\n                next_insert_axis += 1;\n            }\n        }\n        let mut ops = vec![AxisOp::Add(0); target_rank - rank];\n        ops.extend(crate::ops::change_axes::perm_to_ops(&permutation));\n        Ok(ops)\n    }\n\n    pub fn view_to_canonical<D>(&self, io: InOut, view: &mut ArrayViewD<D>) -> TractResult<()> {\n        for op in self.axis_ops_to_canonical(io)? {\n            op.change_view(view)?;\n        }\n        Ok(())\n    }\n\n    pub fn view_to_canonical_mut<D>(\n        &self,\n        io: InOut,\n        view: &mut ArrayViewMutD<D>,\n    ) -> TractResult<()> {\n        for op in self.axis_ops_to_canonical(io)? {\n            op.change_view_mut(view)?;\n        }\n        Ok(())\n    }\n\n    pub fn compose(&self, other: &AxesMapping) -> TractResult<AxesMapping> {\n        ensure!(self.input_count() == 1 && self.output_count() == 1);\n        ensure!(other.input_count() == 1 && other.output_count() == 1);\n        let mut result = AxesMapping::disconnected_for_ranks(\n            &[self.rank(InOut::In(0))],\n            &[other.rank(InOut::Out(0))],\n        )?;\n        for ix in 0..result.rank(InOut::In(0)) {\n            let Some(inter) = self.track_axis((InOut::In(0), ix), InOut::Out(0))? else { continue };\n            let Some(out) = other.track_axis((InOut::In(0), inter), InOut::Out(0))? else {\n                continue;\n            };\n            result = result.linking((InOut::Out(0), out), (InOut::In(0), ix))?;\n        }\n        Ok(result)\n    }\n}\n\nimpl FromStr for AxesMapping {\n    type Err = TractError;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        assert!(!s.contains(\"...\"));\n        let s = s.replace(' ', \"\");\n        let (inputs, outputs) =\n            if let Some((i, r)) = s.split_once(\"->\") { (i, r) } else { (&*s, \"\") };\n        let inputs: TVec<&str> = inputs.split(',').collect();\n        let outputs: TVec<&str> = outputs.split(',').filter(|s| s.len() > 0).collect();\n        AxesMapping::from_strs(&inputs, &outputs)\n    }\n}\n\nimpl Display for AxesMapping {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let (inputs, outputs) = self.to_strs();\n        write!(f, \"{}->{}\", inputs.iter().join(\",\"), outputs.iter().join(\",\"))\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    fn m(s: &str) -> AxesMapping {\n        s.parse().unwrap()\n    }\n\n    #[test]\n    fn test_parse_transpose() {\n        assert_eq!(\n            m(\"ij->ji\"),\n            AxesMapping::new(\n                1,\n                1,\n                tvec![\n                    Axis::new('i', 1, 1).output(0, 1).input(0, 0),\n                    Axis::new('j', 1, 1).output(0, 0).input(0, 1)\n                ]\n            )\n            .unwrap(),\n        )\n    }\n\n    #[test]\n    fn test_parse_diag() {\n        assert_eq!(\n            m(\"ii->i\"),\n            AxesMapping::new(\n                1,\n                1,\n                tvec![Axis::new('i', 1, 1).output(0, 0).input(0, 0).input(0, 1)]\n            )\n            .unwrap(),\n        )\n    }\n\n    #[test]\n    fn test_parse_adamar_product_explicit() {\n        assert_eq!(\n            m(\"i,i->i\"),\n            AxesMapping::new(\n                2,\n                1,\n                tvec![Axis::new('i', 2, 1).output(0, 0).input(0, 0).input(1, 0)]\n            )\n            .unwrap(),\n        )\n    }\n\n    #[test]\n    fn test_parse_inner_product_implicit() {\n        assert_eq!(m(\"i,i\"), m(\"i,i->\"))\n    }\n\n    #[test]\n    fn test_parse_batch_matmul() {\n        assert_eq!(\n            m(\"bij , bjk -> bik \"),\n            AxesMapping::new(\n                2,\n                1,\n                tvec![\n                    Axis::new('b', 2, 1).output(0, 0).input(0, 0).input(1, 0),\n                    Axis::new('i', 2, 1).output(0, 1).input(0, 1),\n                    Axis::new('j', 2, 1).input(0, 2).input(1, 1),\n                    Axis::new('k', 2, 1).output(0, 2).input(1, 2)\n                ]\n            )\n            .unwrap()\n        )\n    }\n\n    #[test]\n    fn test_parse_outer_product() {\n        assert_eq!(\n            m(\"i,j->ij\"),\n            AxesMapping::new(\n                2,\n                1,\n                tvec![\n                    Axis::new('i', 2, 1).output(0, 0).input(0, 0),\n                    Axis::new('j', 2, 1).output(0, 1).input(1, 0)\n                ]\n            )\n            .unwrap(),\n        )\n    }\n\n    #[test]\n    fn test_parse_bilinear() {\n        assert_eq!(\n            m(\"ik,jkl,il->ij\"),\n            AxesMapping::new(\n                3,\n                1,\n                tvec![\n                    Axis::new('i', 3, 1).output(0, 0).input(0, 0).input(2, 0),\n                    Axis::new('j', 3, 1).output(0, 1).input(1, 0),\n                    Axis::new('k', 3, 1).input(0, 1).input(1, 1),\n                    Axis::new('l', 3, 1).input(1, 2).input(2, 1)\n                ]\n            )\n            .unwrap(),\n        )\n    }\n\n    #[test]\n    fn test_parse_complex_tensor_contraction() {\n        assert_eq!(\n            m(\"pqrs,tuqvr->pstuv\"),\n            AxesMapping::new(\n                2,\n                1,\n                tvec![\n                    Axis::new('p', 2, 1).output(0, 0).input(0, 0),\n                    Axis::new('q', 2, 1).input(0, 1).input(1, 2),\n                    Axis::new('r', 2, 1).input(0, 2).input(1, 4),\n                    Axis::new('s', 2, 1).output(0, 1).input(0, 3),\n                    Axis::new('t', 2, 1).output(0, 2).input(1, 0),\n                    Axis::new('u', 2, 1).output(0, 3).input(1, 1),\n                    Axis::new('v', 2, 1).output(0, 4).input(1, 3),\n                ]\n            )\n            .unwrap(),\n        )\n    }\n\n    #[test]\n    fn test_parse_complex_tensor_contraction_implicit() {\n        assert_eq!(m(\"pqrs,tuqvr\"), m(\"pqrs,tuqvr->pstuv\"))\n    }\n\n    #[test]\n    fn test_display_expr() {\n        assert_eq!(m(\"pqrs,tuqvr->pstuv\").to_string(), \"pqrs,tuqvr->pstuv\");\n    }\n\n    #[test]\n    fn test_parse_pulsed_matmul() {\n        assert_eq!(\n            m(\"sij,ijk->sik\"),\n            AxesMapping::new(\n                2,\n                1,\n                tvec![\n                    Axis::new('i', 2, 1).output(0, 1).input(0, 1).input(1, 0),\n                    Axis::new('j', 2, 1).input(0, 2).input(1, 1),\n                    Axis::new('k', 2, 1).output(0, 2).input(1, 2),\n                    Axis::new('s', 2, 1).output(0, 0).input(0, 0),\n                ]\n            )\n            .unwrap()\n        )\n    }\n\n    #[test]\n    fn test_parse_pulsed_batch_matmul() {\n        assert_eq!(\n            m(\"bsij,ijk->bsik\"),\n            AxesMapping::new(\n                2,\n                1,\n                tvec![\n                    Axis::new('b', 2, 1).output(0, 0).input(0, 0),\n                    Axis::new('i', 2, 1).output(0, 2).input(0, 2).input(1, 0),\n                    Axis::new('j', 2, 1).input(0, 3).input(1, 1),\n                    Axis::new('k', 2, 1).output(0, 3).input(1, 2),\n                    Axis::new('s', 2, 1).output(0, 1).input(0, 1),\n                ]\n            )\n            .unwrap()\n        )\n    }\n\n    #[test]\n    fn test_extract_sub_mapping() {\n        assert_eq!(m(\"bsij,ijk->bsik\").extract_sub_mapping(&[0], &[0]).unwrap(), m(\"bsij->bsik\"));\n        assert_eq!(m(\"bsij,ijk->bsik\").extract_sub_mapping(&[1], &[0]).unwrap(), m(\"ijk->bsik\"));\n        assert_eq!(m(\"bsij,ijk->ij\").extract_sub_mapping(&[1], &[0]).unwrap(), m(\"ijk->ij\"));\n    }\n\n    #[test]\n    fn test_remove_axis_0() {\n        assert_eq!(m(\"ab->a\").remove_axis('b').unwrap(), m(\"a->a\"));\n        assert_eq!(m(\"ba->a\").remove_axis('b').unwrap(), m(\"a->a\"));\n        assert_eq!(m(\"a->ba\").remove_axis('b').unwrap(), m(\"a->a\"));\n        assert_eq!(m(\"a->ab\").remove_axis('b').unwrap(), m(\"a->a\"));\n        assert_eq!(m(\"ab,a->a\").remove_axis('b').unwrap(), m(\"a,a->a\"));\n        assert_eq!(m(\"ba,a->a\").remove_axis('b').unwrap(), m(\"a,a->a\"));\n        assert_eq!(m(\"a,ab->a\").remove_axis('b').unwrap(), m(\"a,a->a\"));\n        assert_eq!(m(\"a,ba->a\").remove_axis('b').unwrap(), m(\"a,a->a\"));\n        assert_eq!(m(\"a,a->ab\").remove_axis('b').unwrap(), m(\"a,a->a\"));\n        assert_eq!(m(\"a,a->ba\").remove_axis('b').unwrap(), m(\"a,a->a\"));\n        assert_eq!(m(\"bsij,ijk->bsik\").remove_axis('i').unwrap(), m(\"bsj,jk->bsk\"),);\n    }\n\n    #[test]\n    fn test_translate_to_ops_rm_add() {\n        assert_eq!(m(\"ab->a\").translate_to_axis_ops().unwrap(), vec!(AxisOp::Rm(1)));\n        assert_eq!(m(\"ba->a\").translate_to_axis_ops().unwrap(), vec!(AxisOp::Rm(0)));\n        assert_eq!(\n            m(\"ab->c\").translate_to_axis_ops().unwrap(),\n            vec!(AxisOp::Rm(1), AxisOp::Rm(0), AxisOp::Add(0))\n        );\n    }\n\n    #[test]\n    fn test_translate_to_ops_add_0() {\n        assert_eq!(\n            m(\"bacmn->bmn\").translate_to_axis_ops().unwrap(),\n            vec!(AxisOp::Rm(2), AxisOp::Rm(1))\n        );\n    }\n\n    #[test]\n    fn test_translate_to_ops_move() {\n        assert_eq!(m(\"ab->ba\").translate_to_axis_ops().unwrap(), vec!(AxisOp::Move(1, 0)));\n    }\n\n    #[test]\n    fn test_translate_to_ops_move_20() {\n        assert_eq!(m(\"abc->cab\").translate_to_axis_ops().unwrap(), vec!(AxisOp::Move(2, 0)));\n    }\n\n    #[test]\n    fn test_translate_to_ops_complex() {\n        assert_eq!(\n            m(\"anbck->backn\").translate_to_axis_ops().unwrap(),\n            vec!(AxisOp::Move(2, 0), AxisOp::Move(2, 4))\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/axes/mod.rs",
    "content": "use crate::internal::*;\n\nmod mapping;\nmod model;\n\npub use mapping::AxesMapping;\npub use model::{for_model, full_axis_tracking};\n\n#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)]\npub struct Axis {\n    pub inputs: TVec<TVec<usize>>,\n    pub outputs: TVec<TVec<usize>>,\n    pub repr: char,\n}\n\nimpl Axis {\n    pub fn new(repr: char, inputs: usize, outputs: usize) -> Axis {\n        Axis { repr, inputs: tvec!(tvec!(); inputs), outputs: tvec!(tvec!(); outputs) }\n    }\n\n    pub fn natural(inputs: usize, outputs: usize, repr: char, axis_id: usize) -> Axis {\n        let inputs = tvec!(tvec!(axis_id); inputs);\n        let outputs = tvec!(tvec!(axis_id); outputs);\n        Axis { inputs, outputs, repr }\n    }\n\n    #[allow(dead_code)]\n    pub fn input(mut self, input_id: usize, axis: usize) -> Axis {\n        self.add_input(input_id, axis);\n        self\n    }\n\n    pub fn output(mut self, output_id: usize, axis: usize) -> Axis {\n        self.add_output(output_id, axis);\n        self\n    }\n\n    pub fn inputs_count(mut self, inputs: usize) -> Axis {\n        self.inputs.resize(inputs, tvec!());\n        self\n    }\n\n    pub fn outputs_count(mut self, outputs: usize) -> Axis {\n        self.outputs.resize(outputs, tvec!());\n        self\n    }\n\n    pub fn ensure_inputs_count(&mut self, inputs: usize) {\n        if self.inputs.len() < inputs {\n            self.inputs.resize(inputs, tvec!())\n        }\n    }\n\n    pub fn ensure_outputs_count(&mut self, outputs: usize) {\n        if self.outputs.len() < outputs {\n            self.outputs.resize(outputs, tvec!())\n        }\n    }\n\n    pub fn add_input(&mut self, input_id: usize, axis: usize) {\n        self.ensure_inputs_count(input_id + 1);\n        self.inputs[input_id].push(axis);\n    }\n\n    pub fn add_output(&mut self, output_id: usize, axis: usize) {\n        self.ensure_outputs_count(output_id + 1);\n        self.outputs[output_id].push(axis);\n    }\n\n    pub fn interface(&self, io: InOut) -> &[usize] {\n        match io {\n            InOut::In(ix) => &self.inputs[ix],\n            InOut::Out(ix) => &self.outputs[ix],\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/axes/model.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, Default)]\npub struct OutletMap<T>(Vec<TVec<Option<T>>>);\n\nimpl<T: Clone> OutletMap<T> {\n    fn insert(&mut self, outlet: OutletId, t: T) {\n        if outlet.node >= self.0.len() {\n            self.0.resize_with(outlet.node + 1, || tvec!());\n        }\n        let node = &mut self.0[outlet.node];\n        if outlet.slot >= node.len() {\n            node.resize(outlet.slot + 1, None);\n        }\n        node[outlet.slot] = Some(t)\n    }\n}\n\nimpl<T> OutletMap<T> {\n    fn remove(&mut self, outlet: &OutletId) -> Option<T> {\n        if let Some(node) = self.0.get_mut(outlet.node)\n            && let Some(slot) = node.get_mut(outlet.slot)\n        {\n            return slot.take();\n        }\n        None\n    }\n\n    pub fn get(&self, outlet: &OutletId) -> Option<&T> {\n        if let Some(node) = self.0.get(outlet.node)\n            && let Some(slot) = node.get(outlet.slot)\n        {\n            return slot.as_ref();\n        }\n        None\n    }\n\n    pub fn keys(&self) -> OutletMapKeysIter<'_, T> {\n        OutletMapKeysIter(self, (0, 0).into())\n    }\n}\n\nimpl<'a, T: Clone> std::ops::Index<&'a OutletId> for OutletMap<T> {\n    type Output = T;\n    fn index(&self, index: &'a OutletId) -> &Self::Output {\n        self.get(index).unwrap()\n    }\n}\n\npub struct OutletMapKeysIter<'a, T>(&'a OutletMap<T>, OutletId);\n\nimpl<T> std::iter::Iterator for OutletMapKeysIter<'_, T> {\n    type Item = OutletId;\n    fn next(&mut self) -> Option<Self::Item> {\n        loop {\n            if self.1.node >= (self.0).0.len() {\n                return None;\n            }\n            if self.1.slot >= (self.0).0[self.1.node].len() {\n                self.1.slot = 0;\n                self.1.node += 1;\n                continue;\n            }\n            let current = self.1;\n            self.1.slot += 1;\n            if self.0.get(&current).is_some() {\n                return Some(current);\n            }\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct AxisTracking {\n    pub creators: TVec<OutletId>,\n    pub destructors: TVec<InletId>,\n    pub outlets: OutletMap<usize>,\n}\n\nimpl AxisTracking {\n    pub fn for_outlet_and_axis(\n        model: &TypedModel,\n        outlet: OutletId,\n        axis: usize,\n    ) -> TractResult<Option<AxisTracking>> {\n        let mut mapped_outlets = OutletMap::default();\n        let mut todo = OutletMap::default();\n        let mut creators = tvec!();\n        let mut destructors = tvec!();\n        mapped_outlets.insert(outlet, axis);\n        todo.insert(outlet, ());\n        while let Some(wire) = todo.keys().next() {\n            todo.remove(&wire);\n            let axis = mapped_outlets[&wire];\n            let emiter_node = model.node(wire.node);\n            let mut nodes = vec![];\n            let (input_facts, output_facts) = model.node_facts(emiter_node.id)?;\n            let map = emiter_node\n                .op\n                .axes_mapping(&input_facts, &output_facts)\n                .with_context(|| format!(\"Computing axes mapping for {emiter_node}\"))?;\n            let info = map.axis((InOut::Out(wire.slot), axis)).with_context(|| {\n                format!(\n                    \"Axes mapping for {} is {map}, need output axis {:?} from slot {}\",\n                    emiter_node, axis, wire.slot,\n                )\n            })?;\n\n            if info.inputs.iter().any(|i| i.len() > 0) {\n                nodes.push((wire.node, info.clone()));\n            } else {\n                creators.push(wire);\n            };\n            for succ in &emiter_node.outputs[wire.slot].successors {\n                let succ_node = model.node(succ.node);\n                let (input_facts, output_facts) = model.node_facts(succ_node.id)?;\n                let map = succ_node.op.axes_mapping(&input_facts, &output_facts)?;\n                let info = map.axis((InOut::In(succ.slot), axis)).with_context(|| {\n                    format!(\n                        \"Axes mapping for {succ_node} is {map}, need input axis {:?} from slot {}\",\n                        axis, succ.slot,\n                    )\n                })?;\n                if info.outputs.iter().any(|o| o.len() > 0) {\n                    nodes.push((succ_node.id, info.clone()));\n                } else {\n                    destructors.push(*succ);\n                };\n            }\n            let mut new_outlets = vec![];\n            for (n, axes) in nodes {\n                let node = model.node(n);\n                for slot in 0..node.outputs.len() {\n                    if let &[axis] = &*axes.outputs[slot] {\n                        new_outlets.push((OutletId::new(n, slot), axis));\n                    }\n                }\n                for slot in 0..node.inputs.len() {\n                    if let &[axis] = &*axes.inputs[slot] {\n                        new_outlets.push((node.inputs[slot], axis));\n                    }\n                }\n            }\n            for (outlet, axis) in new_outlets {\n                if let Some(prev) = mapped_outlets.get(&outlet) {\n                    rule_if!(*prev == axis);\n                } else {\n                    mapped_outlets.insert(outlet, axis);\n                    todo.insert(outlet, ());\n                }\n            }\n        }\n        Ok(Some(AxisTracking { creators, destructors, outlets: mapped_outlets }))\n    }\n}\n\npub fn full_axis_tracking(model: &TypedModel) -> TractResult<Vec<AxisTracking>> {\n    let mut axes: Vec<AxisTracking> = vec![];\n    for node in model.eval_order()? {\n        for slot in 0..model.node(node).outputs.len() {\n            let outlet = OutletId::new(node, slot);\n            let input_fact = model.outlet_fact(outlet)?;\n            'axis: for axis in 0..input_fact.rank() {\n                if axes.iter().any(|tracking| tracking.outlets.get(&outlet) == Some(&axis)) {\n                    continue 'axis;\n                }\n                if let Some(tracker) = AxisTracking::for_outlet_and_axis(model, outlet, axis)? {\n                    axes.push(tracker);\n                }\n            }\n        }\n    }\n    Ok(axes)\n}\n\npub fn for_model(model: &TypedModel) -> TractResult<AxesMapping> {\n    let input_ranks = model\n        .input_outlets()?\n        .iter()\n        .map(|io| model.outlet_fact(*io).map(|f| f.rank()))\n        .collect::<TractResult<TVec<usize>>>()?;\n    let output_ranks = model\n        .output_outlets()?\n        .iter()\n        .map(|io| model.outlet_fact(*io).map(|f| f.rank()))\n        .collect::<TractResult<TVec<usize>>>()?;\n    let mut result = AxesMapping::disconnected_for_ranks(&input_ranks, &output_ranks)?;\n    for tracking in full_axis_tracking(model)? {\n        let mut reprs: Vec<char> = vec![];\n        for (ix, outlet) in model.input_outlets()?.iter().enumerate() {\n            if let Some(appearance) = tracking.outlets.get(outlet) {\n                reprs.push(result.axis((InOut::In(ix), *appearance)).unwrap().repr);\n            }\n        }\n        for (ix, outlet) in model.output_outlets()?.iter().enumerate() {\n            if let Some(appearance) = tracking.outlets.get(outlet) {\n                reprs.push(result.axis((InOut::Out(ix), *appearance)).unwrap().repr);\n            }\n        }\n        if reprs.len() > 1 {\n            for other in &reprs[1..] {\n                result = result.linking(reprs[0], *other)?;\n            }\n        }\n    }\n    result.relabel()\n}\n"
  },
  {
    "path": "core/src/broadcast.rs",
    "content": "//! N-way tensor broadcast\nuse tract_data::internal::*;\n\n/// Computes a shape, if any, to which all shapes can be broadcasted.\npub fn multi_broadcast<D>(shapes: &[impl AsRef<[D]>]) -> TractResult<TVec<D>>\nwhere\n    D: DimLike,\n{\n    let one = D::one();\n    let Some(len) = shapes.iter().map(|shape| shape.as_ref().len()).max() else {\n        return Ok(tvec!());\n    };\n    let mut shape: TVec<D> = tvec!();\n    for i in 0..len {\n        let mut wanted_size = D::one();\n        for shape in shapes {\n            let len = shape.as_ref().len();\n            let dim = if i < len { &shape.as_ref()[len - i - 1] } else { &one };\n            wanted_size = wanted_size.broadcast(dim.clone())?;\n        }\n        shape.push(wanted_size)\n    }\n    shape.reverse();\n    Ok(shape)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn onnx_1() {\n        assert_eq!(multi_broadcast(&tvec![tvec![2, 3, 4, 5], tvec![]]).unwrap(), tvec![2, 3, 4, 5])\n    }\n\n    #[test]\n    fn onnx_2() {\n        assert_eq!(multi_broadcast(&tvec![tvec![2, 3, 4, 5], tvec![5]]).unwrap(), tvec![2, 3, 4, 5])\n    }\n\n    #[test]\n    fn onnx_3() {\n        assert_eq!(\n            multi_broadcast(&tvec![tvec![4, 5], tvec![2, 3, 4, 5]]).unwrap(),\n            tvec![2, 3, 4, 5]\n        )\n    }\n\n    #[test]\n    fn onnx_4() {\n        assert_eq!(\n            multi_broadcast(&tvec![tvec![1, 4, 5], tvec![2, 3, 4, 1]]).unwrap(),\n            tvec![2, 3, 4, 5]\n        )\n    }\n\n    #[test]\n    fn onnx_5() {\n        assert_eq!(\n            multi_broadcast(&tvec![tvec![3, 4, 5], tvec![2, 1, 1, 1]]).unwrap(),\n            tvec![2, 3, 4, 5]\n        )\n    }\n}\n"
  },
  {
    "path": "core/src/floats.rs",
    "content": "use crate::internal::translator::Translate;\nuse crate::internal::*;\nuse crate::ops::array::{Pad, PadMode};\nuse crate::ops::binary::TypedBinOp;\nuse crate::ops::cast::{Cast, cast};\nuse crate::ops::einsum::EinSum;\nuse crate::ops::element_wise::ElementWiseOp;\nuse crate::ops::konst::Const;\nuse crate::ops::scan::Scan;\nuse crate::ops::source::TypedSource;\nuse crate::transform::ModelTransform;\n\npub struct FloatPrecisionTranslator {\n    from_dt: DatumType,\n    to_dt: DatumType,\n    #[allow(clippy::type_complexity)]\n    node_predicate: Option<Box<dyn Fn(&TypedNode) -> bool>>,\n}\n\nimpl FloatPrecisionTranslator {\n    pub fn new(from_dt: DatumType, to_dt: DatumType) -> Self {\n        Self { from_dt, to_dt, node_predicate: None }\n    }\n\n    pub fn with_filter(\n        from_dt: DatumType,\n        to_dt: DatumType,\n        node_predicate: impl Fn(&TypedNode) -> bool + 'static,\n    ) -> Self {\n        Self { from_dt, to_dt, node_predicate: Some(Box::new(node_predicate)) }\n    }\n\n    fn should_translate_node(&self, node: &TypedNode) -> bool {\n        self.node_predicate.as_ref().map(|it| (it)(node)).unwrap_or(true)\n    }\n\n    /// Cast node inputs to the working float precision for the operator\n    /// Only input using float datumtype are impacted. This will add cast operations\n    /// in the model. The function return the new input outlet ids.\n    fn cast_inputs_if_required(\n        &self,\n        model: &mut TypedModel,\n        node: &TypedNode,\n        mapping: &HashMap<OutletId, OutletId>,\n        op_float_dt: DatumType,\n    ) -> TractResult<TVec<OutletId>> {\n        let original_op_float_dt =\n            if op_float_dt == self.from_dt { self.to_dt } else { self.from_dt };\n\n        let mut mapped_inputs = tvec![];\n        for (i_idx, i) in node.inputs.iter().enumerate() {\n            let fact = model.outlet_fact(mapping[i])?;\n            if fact.datum_type == original_op_float_dt && fact.is_plain() {\n                let casted_mapped_input = model.wire_node(\n                    format!(\"{}.cast-{i_idx}\", node.name),\n                    Cast { to: op_float_dt },\n                    &[mapping[i]],\n                )?[0];\n                mapped_inputs.push(casted_mapped_input);\n            } else {\n                mapped_inputs.push(mapping[i])\n            }\n        }\n        Ok(mapped_inputs)\n    }\n\n    /// Cast node output outlet ids to the destination float precision,\n    /// after insertion in the target mode. This preserves the model output float\n    /// precision.\n    fn cast_model_outputs_if_required(\n        &self,\n        source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        target_node_outlet_ids: TVec<OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        let mut outputs = tvec![];\n        for (o_idx, o) in target_node_outlet_ids.into_iter().enumerate() {\n            // Add Cast op for model output\n            let is_source_output = source.outputs.contains(&OutletId::new(node.id, o_idx));\n            let fact = target.outlet_fact(o)?;\n            if fact.datum_type == self.from_dt && fact.is_plain() && is_source_output {\n                let casted_output = target.wire_node(\n                    format!(\"{}.cast-out-{o_idx}\", node.name),\n                    Cast { to: self.to_dt },\n                    &[o],\n                )?[0];\n                outputs.push(casted_output);\n            } else {\n                outputs.push(o)\n            }\n        }\n        Ok(outputs)\n    }\n}\n\nimpl std::fmt::Debug for FloatPrecisionTranslator {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"FloatPrecisionTranslator\")\n            .field(\"from\", &self.from_dt)\n            .field(\"to\", &self.to_dt)\n            .finish()\n    }\n}\n\nimpl ModelTransform for FloatPrecisionTranslator {\n    fn name(&self) -> StaticName {\n        format!(\"{:?}-to-{:?}\", self.from_dt, self.to_dt).into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        let new = self.translate_model(model)?;\n        *model = new;\n        Ok(())\n    }\n}\n\nimpl Translate<TypedFact, Box<dyn TypedOp>, TypedFact, Box<dyn TypedOp>>\n    for FloatPrecisionTranslator\n{\n    fn translate_node(\n        &self,\n        source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        let is_source = node.op_as::<TypedSource>().is_some();\n        if !self.should_translate_node(node) && !is_source {\n            let new_op = node.op.clone();\n\n            let casted_inputs =\n                self.cast_inputs_if_required(target, node, mapping, self.from_dt)?;\n            let target_node_outlet_ids = target.wire_node(&node.name, new_op, &casted_inputs)?;\n\n            self.cast_model_outputs_if_required(source, node, target, target_node_outlet_ids)\n        } else {\n            let casted_inputs = self.cast_inputs_if_required(target, node, mapping, self.to_dt)?;\n\n            let new_op = if let Some(source_op) = node.op_as::<TypedSource>() {\n                let mut fact = source_op.fact.clone();\n                if fact.datum_type == self.from_dt {\n                    fact.datum_type = self.to_dt;\n                }\n                Box::new(TypedSource::new(fact))\n            } else if let Some(konst) = node.op_as::<Const>() {\n                if konst.val().datum_type() == self.from_dt && konst.val().is_plain() {\n                    let wire = target.add_const(\n                        format!(\"{}.{:?}\", node.name, self.from_dt),\n                        konst.val().clone(),\n                    )?;\n                    return target.wire_node(&node.name, cast(self.to_dt), &[wire]);\n                } else {\n                    node.op.clone()\n                }\n            } else if let Some(cast_op) = node.op_as::<Cast>() {\n                if cast_op.to == self.from_dt {\n                    Box::new(Cast { to: self.to_dt })\n                } else {\n                    node.op.clone()\n                }\n            } else if let Some(ew) = node.op_as::<ElementWiseOp>() {\n                if ew.1 == Some(self.from_dt) {\n                    Box::new(ElementWiseOp(ew.0.clone(), Some(self.to_dt)))\n                } else {\n                    node.op.clone()\n                }\n            } else if let Some(bin) = node.op_as::<TypedBinOp>() {\n                if bin.1 == Some(self.from_dt) {\n                    Box::new(TypedBinOp(bin.0.clone(), Some(self.to_dt)))\n                } else {\n                    node.op.clone()\n                }\n            } else if let Some(op) = node.op_as::<Scan>() {\n                let body = FloatPrecisionTranslator::new(self.from_dt, self.to_dt)\n                    .translate_model(&op.body)?;\n                Box::new(Scan { body, ..op.clone() })\n            } else if let Some(op) = node.op_as::<EinSum>() {\n                let operating_dt =\n                    if op.operating_dt == self.from_dt { self.to_dt } else { op.operating_dt };\n                Box::new(EinSum { operating_dt, ..op.clone() })\n            } else if let Some(op) = node.op_as::<Pad>() {\n                if let PadMode::Constant(t) = &op.mode {\n                    let new_t = if t.datum_type() == self.from_dt {\n                        t.cast_to_dt(self.to_dt)?.into_owned().into_arc_tensor()\n                    } else {\n                        Arc::clone(t)\n                    };\n                    Box::new(Pad { mode: PadMode::Constant(new_t), ..op.clone() })\n                } else {\n                    Box::new(op.clone())\n                }\n            } else {\n                node.op.clone()\n            };\n            target.wire_node(&node.name, new_op, &casted_inputs)\n        }\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::ops::math;\n    use tract_data::prelude::f16;\n\n    fn build_f32_model() -> TractResult<TypedModel> {\n        // F32 model definition\n        let mut model = TypedModel::default();\n        let a = model.add_source(\"source\", f32::fact([1])).unwrap();\n        let multiplier = model.add_const(\"multiplier\", tensor1(&[1.0f32]))?;\n        let neg_infinity = model.add_const(\"neg_infinity\", tensor1(&[f32::NEG_INFINITY]))?;\n        let pow_factor = model.add_const(\"pow_factor\", tensor1(&[10.0f32]))?;\n        let add = model.wire_node(\"layer.0/add\", math::add(), &[a, a]).unwrap()[0];\n        let mul = model.wire_node(\"layer.0/mul\", math::mul(), &[add, multiplier]).unwrap()[0];\n        let pow = model.wire_node(\"layer.1/pow\", math::pow(), &[mul, pow_factor]).unwrap()[0];\n        let _output = model\n            .wire_node(\"layer.1/add_neg_infinity\", math::add(), &[pow, neg_infinity])\n            .unwrap()[0];\n        model.auto_outputs()?;\n        Ok(model)\n    }\n\n    #[test]\n    fn test_high_level_f16_transform_with_filter() -> TractResult<()> {\n        // F32 model definition\n        let model = build_f32_model()?;\n\n        // Execution in F32\n        let runnable_model = model.clone().into_runnable()?;\n        assert_eq!(\n            runnable_model.run(tvec![tensor1(&[5.0f32]).into()])?[0],\n            tensor1(&[f32::NEG_INFINITY]).into()\n        );\n\n        // Execution in F16 with returns NaN\n        let runnable_model = &crate::transform::get_transform(\"f32_to_f16\")?\n            .unwrap()\n            .transform_into(model.clone())?\n            .into_runnable()?;\n        assert!(\n            runnable_model.run(tvec![tensor1(&[f16::from_f32(5.0)]).into()])?[0]\n                .try_as_plain()?\n                .to_scalar::<f16>()?\n                .is_nan()\n        );\n\n        // Execution in F16 with filter that returns the good output.\n        let runnable_model = &crate::transform::build_float_translator(\n            f32::datum_type(),\n            f16::datum_type(),\n            crate::transform::NodeFilter {\n                exclude: Some(vec![\"layer.1\".into()]),\n                ..Default::default()\n            },\n        )\n        .transform_into(model.clone())?\n        .into_runnable()?;\n        assert_eq!(\n            runnable_model.run(tvec![tensor1(&[f16::from_f32(5.0)]).into()])?[0],\n            tensor1(&[f16::NEG_INFINITY]).into()\n        );\n\n        // Execution in F16 with returns NaN despite the filter.\n        let runnable_model = &crate::transform::build_float_translator(\n            f32::datum_type(),\n            f16::datum_type(),\n            crate::transform::NodeFilter {\n                exclude: Some(vec![\"layer.0\".into()]),\n                ..Default::default()\n            },\n        )\n        .transform_into(model)?\n        .into_runnable()?;\n        assert!(\n            runnable_model.run(tvec![tensor1(&[f16::from_f32(5.0)]).into()])?[0]\n                .try_as_plain()?\n                .to_scalar::<f16>()?\n                .is_nan()\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_f16_transform_with_filter() -> TractResult<()> {\n        // F32 model definition\n        let model = build_f32_model()?;\n\n        // Execution in F32\n        let runnable_model = model.clone().into_runnable()?;\n        assert_eq!(\n            runnable_model.run(tvec![tensor1(&[5.0f32]).into()])?[0],\n            tensor1(&[f32::NEG_INFINITY]).into()\n        );\n\n        // Execution in F16 with returns NaN\n        let mut model_f16 = model.clone();\n        model_f16\n            .transform(&FloatPrecisionTranslator::new(f32::datum_type(), f16::datum_type()))?;\n        let runnable_model_f16 = model_f16.clone().into_runnable()?;\n        assert!(\n            runnable_model_f16.run(tvec![tensor1(&[f16::from_f32(5.0)]).into()])?[0]\n                .try_as_plain()?\n                .to_scalar::<f16>()?\n                .is_nan()\n        );\n\n        // Execution in F16 with filter that returns the good output.\n        let mut model_f16_with_filter = model.clone();\n        model_f16_with_filter.transform(&FloatPrecisionTranslator::with_filter(\n            f32::datum_type(),\n            f16::datum_type(),\n            |node| !node.name.contains(\"layer.1\"),\n        ))?;\n        let runnable_model_f16 = model_f16_with_filter.clone().into_runnable()?;\n        assert_eq!(\n            runnable_model_f16.run(tvec![tensor1(&[f16::from_f32(5.0)]).into()])?[0],\n            tensor1(&[f16::NEG_INFINITY]).into()\n        );\n        let mut model_f16_with_filter = model.clone();\n        model_f16_with_filter.transform(&FloatPrecisionTranslator::with_filter(\n            f32::datum_type(),\n            f16::datum_type(),\n            |node| !node.name.contains(\"layer.0\"),\n        ))?;\n        let runnable_model_f16 = model_f16_with_filter.clone().into_runnable()?;\n        assert!(\n            runnable_model_f16.run(tvec![tensor1(&[f16::from_f32(5.0)]).into()])?[0]\n                .try_as_plain()?\n                .to_scalar::<f16>()?\n                .is_nan()\n        );\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/framework.rs",
    "content": "//! Enforce consistent API between the implemented Frameworks importers.\nuse crate::internal::*;\nuse std::fmt::Debug;\nuse std::io::Read;\nuse std::path::Path;\n\n/// A Framework that translate its model to tract core model.\n///\n/// The ProtoModel is the parsed representation of the imported model. It does\n/// not have to be Protobuf based.\npub trait Framework<ProtoModel, Model>: Send + Sync\nwhere\n    ProtoModel: Debug,\n    Model: Default,\n{\n    /// Parse a proto model from a reader.\n    fn proto_model_for_read(&self, reader: &mut dyn Read) -> TractResult<ProtoModel>;\n\n    /// Translate a proto model into a model.\n    fn model_for_proto_model(&self, proto: &ProtoModel) -> TractResult<Model> {\n        self.model_for_proto_model_with_model_template(proto, Model::default())\n    }\n\n    /// Translate a proto model into a model, with some symbols already listed.\n    fn model_for_proto_model_with_model_template(\n        &self,\n        proto: &ProtoModel,\n        template: Model,\n    ) -> TractResult<Model>;\n\n    /// Read a proto model from a filename.\n    fn proto_model_for_path(&self, p: impl AsRef<Path>) -> TractResult<ProtoModel> {\n        let mut r = std::fs::File::open(p.as_ref())\n            .with_context(|| format!(\"Could not open {:?}\", p.as_ref()))?;\n        self.proto_model_for_read(&mut r)\n    }\n\n    /// Read a model from a reader\n    fn model_for_read(&self, r: &mut dyn Read) -> TractResult<Model> {\n        let proto_model = self.proto_model_for_read(r).context(\"Reading proto model\")?;\n        self.model_for_proto_model(&proto_model).context(\"Translating proto model to model\")\n    }\n\n    /// Build a model from a filename.\n    fn model_for_path(&self, p: impl AsRef<Path>) -> TractResult<Model> {\n        let mut r = std::fs::File::open(p.as_ref())\n            .with_context(|| format!(\"Could not open {:?}\", p.as_ref()))?;\n        self.model_for_read(&mut r)\n    }\n}\n"
  },
  {
    "path": "core/src/late_bind.rs",
    "content": "use crate::prelude::TractResult;\nuse std::borrow::Cow;\n\npub trait ResolveTo<Concrete> {\n    type Param: ?Sized;\n    fn resolve(&self, param: &Self::Param) -> TractResult<Concrete>;\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub enum GeometryBound<Symbolic, Concrete> {\n    Symbolic(Symbolic),\n    Concrete(Concrete),\n}\n\nimpl<S: ResolveTo<C>, C: Clone> GeometryBound<S, C> {\n    pub fn is_concrete(&self) -> bool {\n        match self {\n            GeometryBound::Concrete { .. } => true,\n            GeometryBound::Symbolic { .. } => false,\n        }\n    }\n\n    pub fn into_concrete(self, param: &S::Param) -> TractResult<Self> {\n        match self {\n            Self::Symbolic(sym) => Ok(Self::Concrete(sym.resolve(param)?)),\n            Self::Concrete(conc) => Ok(Self::Concrete(conc)),\n        }\n    }\n\n    pub fn to_concrete(&self, param: &S::Param) -> TractResult<Cow<'_, C>> {\n        match self {\n            Self::Symbolic(sym) => Ok(Cow::Owned(sym.resolve(param)?)),\n            Self::Concrete(conc) => Ok(Cow::Borrowed(conc)),\n        }\n    }\n\n    pub fn as_concrete(&self) -> Option<&C> {\n        if let Self::Concrete(conc) = self { Some(conc) } else { None }\n    }\n\n    pub fn optimize_if(self, param: Option<&S::Param>) -> TractResult<Self> {\n        if let Some(param) = param { self.into_concrete(param) } else { Ok(self) }\n    }\n}\n\nimpl<S, C> From<S> for GeometryBound<S, C> {\n    fn from(s: S) -> Self {\n        GeometryBound::Symbolic(s)\n    }\n}\n"
  },
  {
    "path": "core/src/lib.rs",
    "content": "#![allow(clippy::len_zero)]\n#![allow(clippy::missing_safety_doc)]\n#![allow(clippy::redundant_closure_call)]\n//! # Tract\n//!\n//! Tiny, no-nonsense, self contained, portable TensorFlow and ONNX inference.\n//!\n//! ## Example\n//!\n//! ```\n//! # extern crate tract_core;\n//! # fn main() {\n//! use tract_core::internal::*;\n//!\n//! // build a simple model that just add 3 to each input component\n//! let mut model = TypedModel::default();\n//!\n//! let input_fact = f32::fact(&[3]);\n//! let input = model.add_source(\"input\", input_fact).unwrap();\n//! let three = model.add_const(\"three\".to_string(), tensor1(&[3f32])).unwrap();\n//! let add = model.wire_node(\"add\".to_string(),\n//!     tract_core::ops::math::add(),\n//!     [input, three].as_ref()\n//!     ).unwrap();\n//!\n//! model.auto_outputs().unwrap();\n//!\n//! // We build an execution plan. Default inputs and outputs are inferred from\n//! // the model graph.\n//! let plan = model.into_runnable().unwrap();\n//!\n//! // run the computation.\n//! let input = tensor1(&[1.0f32, 2.5, 5.0]);\n//! let mut outputs = plan.run(tvec![input.into()]).unwrap();\n//!\n//! // take the first and only output tensor\n//! let mut tensor = outputs.pop().unwrap();\n//!\n//! assert_eq!(tensor, tensor1(&[4.0f32, 5.5, 8.0]).into());\n//! # }\n//! ```\n//!\n//! While creating a model from Rust code is useful for testing the library,\n//! real-life use-cases will usually load a TensorFlow or ONNX model using\n//! tract-tensorflow or tract-onnx crates.\n//!\n\n#[cfg(feature = \"accelerate\")]\nextern crate accelerate_src;\n#[cfg(feature = \"blis\")]\nextern crate blis_src;\n#[cfg(feature = \"blas\")]\nextern crate cblas;\n#[cfg(feature = \"openblas\")]\nextern crate openblas_src;\n\nextern crate bit_set;\n#[macro_use]\nextern crate derive_new;\n#[macro_use]\npub extern crate downcast_rs;\n#[allow(unused_imports)]\n#[macro_use]\nextern crate log;\n#[allow(unused_imports)]\n#[macro_use]\npub extern crate ndarray;\n#[cfg(test)]\nextern crate env_logger;\npub extern crate num_traits;\n#[cfg(test)]\nextern crate proptest;\n\npub extern crate tract_data;\npub extern crate tract_linalg;\n\n#[macro_use]\npub mod macros;\n#[macro_use]\npub mod ops;\n\npub mod axes;\npub mod broadcast;\npub mod floats;\npub mod framework;\npub mod model;\npub mod optim;\npub mod plan;\n#[macro_use]\npub mod runtime;\n#[macro_use]\npub mod transform;\npub mod value;\n\npub use dyn_clone;\n\nmod late_bind;\n\n/// This prelude is meant for code using tract.\npub mod prelude {\n    pub use crate::framework::Framework;\n    pub use crate::model::*;\n    pub use crate::runtime::{\n        FrozenState, RunOptions, Runnable, Runtime, State, runtime_for_name, runtimes,\n    };\n    pub use crate::value::{IntoTValue, TValue};\n    pub use std::sync::Arc;\n    pub use tract_data::prelude::*;\n\n    pub use ndarray as tract_ndarray;\n    pub use num_traits as tract_num_traits;\n    pub use tract_data;\n    pub use tract_linalg;\n    pub use tract_linalg::multithread;\n}\n\n/// This prelude is meant for code extending tract (like implementing new ops).\npub mod internal {\n    pub extern crate inventory;\n    pub use crate::axes::{AxesMapping, Axis};\n    pub use crate::late_bind::*;\n    pub use crate::model::*;\n    pub use crate::ops::change_axes::*;\n    pub use crate::ops::element_wise::ElementWiseMiniOp;\n    pub use crate::ops::{Cost, EvalOp, FrozenOpState, Op, OpState, Validation};\n    pub use crate::plan::{SessionStateHandler, SimplePlan, SimpleState, TurnState};\n    pub use crate::prelude::*;\n    pub use crate::runtime::{\n        DefaultRuntime, Runnable, Runtime, State, runtime_for_name, runtimes,\n    };\n    pub use dims;\n    pub use downcast_rs as tract_downcast_rs;\n    pub use register_model_transform;\n    pub use register_runtime;\n    pub use register_simple_model_transform;\n    pub use std::borrow::Cow;\n    pub use std::collections::HashMap;\n    pub use std::hash::Hash;\n    pub use std::marker::PhantomData;\n    pub use tract_data::internal::*;\n    pub use tract_data::{\n        dispatch_copy, dispatch_datum, dispatch_datum_by_size, dispatch_floatlike, dispatch_numbers,\n    };\n    pub use tvec;\n    pub use {args_1, args_2, args_3, args_4, args_5, args_6, args_7, args_8};\n    pub use {as_op, not_a_typed_op, op_as_typed_op};\n    pub use {bin_to_super_type, element_wise, element_wise_oop};\n    pub use {rule_if, rule_if_let, rule_if_some};\n}\n\n#[cfg(test)]\n#[allow(dead_code)]\nfn setup_test_logger() {\n    let _ = env_logger::Builder::from_env(\"TRACT_LOG\").try_init();\n}\n"
  },
  {
    "path": "core/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! dims {\n    ($($dim:expr),*) => {\n        ShapeFact::from(&[$(TDim::from($dim.clone())),*])\n    }\n}\n"
  },
  {
    "path": "core/src/model/fact.rs",
    "content": "//! Partial and complete tensor types representations.\nuse crate::internal::*;\nuse downcast_rs::Downcast;\nuse dyn_eq::DynEq;\nuse std::fmt;\nuse tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage};\n\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct ShapeFact {\n    dims: TVec<TDim>,\n    concrete: Option<TVec<usize>>,\n}\n\nimpl ShapeFact {\n    #[inline]\n    pub fn rank(&self) -> usize {\n        self.dims.len()\n    }\n\n    fn compute_concrete(&mut self) {\n        assert!(self.dims.iter().all(|d| d.to_isize().map(|d| d >= 0).unwrap_or(true)));\n        self.concrete =\n            self.dims.iter().map(|d| d.to_usize()).collect::<TractResult<TVec<_>>>().ok()\n    }\n\n    /// Shape of the tensor, unless it has symbolic dimensions.\n    #[inline]\n    pub fn as_concrete(&self) -> Option<&[usize]> {\n        self.concrete.as_deref()\n    }\n\n    /// Do we have a symbol-less value ?\n    #[inline]\n    pub fn is_concrete(&self) -> bool {\n        self.concrete.is_some()\n    }\n\n    /// Convert the shape to an array of extended dimensions.\n    #[inline]\n    pub fn to_tvec(&self) -> TVec<TDim> {\n        self.dims.clone()\n    }\n\n    /// Compute the volume of the tensor.\n    #[inline]\n    pub fn volume(&self) -> TDim {\n        self.dims.iter().product()\n    }\n\n    #[inline]\n    pub fn eval(&self, values: &SymbolValues) -> TractResult<Cow<'_, ShapeFact>> {\n        if self.is_concrete() {\n            Ok(Cow::Borrowed(self))\n        } else {\n            Ok(Cow::Owned(self.iter().map(|d| d.eval(values)).collect::<ShapeFact>()))\n        }\n    }\n\n    #[inline]\n    pub fn eval_to_usize(&self, values: &SymbolValues) -> TractResult<Cow<'_, TVec<usize>>> {\n        if let Some(c) = &self.concrete {\n            Ok(Cow::Borrowed(c))\n        } else {\n            Ok(Cow::Owned(\n                self.iter()\n                    .map(|d| d.eval_to_i64(values).map(|d| d as usize))\n                    .collect::<TractResult<TVec<_>>>()?,\n            ))\n        }\n    }\n\n    #[inline]\n    pub fn eval_to_isize(&self, values: &SymbolValues) -> TractResult<Cow<'_, TVec<isize>>> {\n        if let Some(c) = &self.concrete {\n            #[allow(unknown_lints, clippy::missing_transmute_annotations)]\n            // TVec<usize> -> TVec<isize>\n            Ok(unsafe { std::mem::transmute(Cow::Borrowed(c)) })\n        } else {\n            Ok(Cow::Owned(\n                self.iter()\n                    .map(|d| d.eval_to_i64(values).map(|d| d as isize))\n                    .collect::<TractResult<TVec<_>>>()?,\n            ))\n        }\n    }\n\n    pub fn from_dims<D: ToDim, T: IntoIterator<Item = D>>(it: T) -> ShapeFact {\n        let mut dims =\n            ShapeFact { dims: it.into_iter().map(|d| d.to_dim()).collect(), concrete: None };\n        dims.compute_concrete();\n        dims\n    }\n\n    pub fn dims(&self) -> &[TDim] {\n        self.dims.as_slice()\n    }\n\n    pub fn set(&mut self, ix: usize, dim: TDim) {\n        self.dims[ix] = dim;\n        self.compute_concrete();\n    }\n\n    pub fn insert_axis(&mut self, axis: usize) -> TractResult<()> {\n        self.dims.insert(axis, 1.into());\n        if let Some(concrete) = &mut self.concrete {\n            concrete.insert(axis, 1);\n        }\n        Ok(())\n    }\n\n    pub fn remove_axis(&mut self, axis: usize) -> TractResult<()> {\n        self.dims.remove(axis);\n        if let Some(concrete) = &mut self.concrete {\n            concrete.remove(axis);\n        } else {\n            self.compute_concrete();\n        };\n        Ok(())\n    }\n\n    pub fn compatible_with(&self, _other: &ShapeFact) -> bool {\n        if self.rank() == _other.rank() {\n            self.dims\n                .iter()\n                .zip(_other.dims.iter())\n                .all(|(dim, other_dim)| dim.compatible_with(other_dim))\n        } else {\n            false\n        }\n    }\n\n    pub fn scalar() -> ShapeFact {\n        let void: &[usize] = &[];\n        Self::from(void)\n    }\n\n    pub fn consistent(&self) -> TractResult<()> {\n        ensure!(\n            self.concrete\n                == self.dims.iter().map(|d| d.to_usize()).collect::<TractResult<TVec<_>>>().ok()\n        );\n        Ok(())\n    }\n}\n\nimpl std::ops::Deref for ShapeFact {\n    type Target = [TDim];\n    fn deref(&self) -> &[TDim] {\n        &self.dims\n    }\n}\n\nimpl<D: ToDim, T: IntoIterator<Item = D>> From<T> for ShapeFact {\n    fn from(it: T) -> ShapeFact {\n        ShapeFact::from_dims(it)\n    }\n}\n\n/// Type information about a tensor: shape, and element type, in various state\n/// of determination.\npub trait Fact:\n    std::fmt::Debug + Downcast + dyn_clone::DynClone + dyn_eq::DynEq + Send + Sync + 'static\n{\n    fn to_typed_fact(&self) -> TractResult<Cow<'_, TypedFact>>;\n\n    fn matches(&self, t: &Tensor, symbols: Option<&SymbolValues>) -> TractResult<bool> {\n        self.to_typed_fact()?.matches(t, symbols)\n    }\n\n    /// Ensure that self is same type as another fact or a subtype\n    fn compatible_with(&self, _other: &dyn Fact) -> bool;\n\n    fn datum_type(&self) -> Option<DatumType>;\n}\n\nimpl_downcast!(Fact);\ndyn_clone::clone_trait_object!(Fact);\ndyn_eq::eq_trait_object!(Fact);\n\nimpl<D: ToDim> std::iter::FromIterator<D> for ShapeFact {\n    fn from_iter<T: IntoIterator<Item = D>>(iter: T) -> Self {\n        ShapeFact::from_dims(iter.into_iter().map(|d| d.to_dim()))\n    }\n}\n\nimpl fmt::Debug for ShapeFact {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        use tract_itertools::Itertools;\n        write!(fmt, \"{}\", self.iter().join(\",\"))\n    }\n}\n\nimpl AsRef<[TDim]> for ShapeFact {\n    fn as_ref(&self) -> &[TDim] {\n        &self.dims\n    }\n}\n\n/// Fully determined tensor information for TypedModel.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct TypedFact {\n    /// tensor element type\n    pub datum_type: DatumType,\n    /// tensor shape\n    pub shape: ShapeFact,\n    /// optional constant value\n    pub konst: Option<Arc<Tensor>>,\n    /// optional uniform value\n    pub uniform: Option<Arc<Tensor>>,\n    /// optional exotic fact\n    pub exotic_fact: Option<Box<dyn ExoticFact>>,\n    /// Symbolic per-element value as a TDim expression, possibly involving\n    /// coordinate symbols 🎯0,🎯1,… and/or model symbols.\n    /// `None` means \"unknown / not tracked\".\n    pub uniform_tdim: Option<TDim>,\n    /// Boolean TDim expression in coordinate symbols defining which positions\n    /// in the tensor are relevant to downstream consumers.\n    /// `None` means \"all positions matter\" (no demand annotation).\n    pub region_of_interest: Option<TDim>,\n}\n\nimpl TypedFact {\n    pub fn scalar<T>() -> TypedFact\n    where\n        T: Datum,\n    {\n        Self::dt_scalar(T::datum_type())\n    }\n\n    pub fn shape<T, S>(shape: S) -> TypedFact\n    where\n        T: Datum,\n        S: Into<ShapeFact>,\n    {\n        Self::dt_shape(T::datum_type(), shape)\n    }\n\n    pub fn shape_and_dt_of(t: &Tensor) -> TypedFact {\n        debug_assert!(\n            t.is_plain(),\n            \"shape_and_dt_of called on exotic tensor, exotic_fact will be lost\"\n        );\n        TypedFact {\n            datum_type: t.datum_type(),\n            shape: ShapeFact::from_dims(t.shape().iter().map(TDim::from)),\n            uniform: None,\n            konst: None,\n            exotic_fact: None,\n            uniform_tdim: None,\n            region_of_interest: None,\n        }\n    }\n\n    pub fn mem_size(&self) -> TDim {\n        self.shape.volume() * self.datum_type.size_of()\n            + self.exotic_fact().iter().flat_map(|it| it.buffer_sizes()).sum::<TDim>()\n    }\n\n    pub fn dt_scalar(datum_type: DatumType) -> TypedFact {\n        TypedFact {\n            datum_type,\n            shape: ShapeFact::scalar(),\n            konst: None,\n            uniform: None,\n            exotic_fact: None,\n            uniform_tdim: None,\n            region_of_interest: None,\n        }\n    }\n\n    pub fn dt_shape<S>(datum_type: DatumType, shape: S) -> TypedFact\n    where\n        S: Into<ShapeFact>,\n    {\n        TypedFact {\n            datum_type,\n            shape: shape.into(),\n            konst: None,\n            uniform: None,\n            exotic_fact: None,\n            uniform_tdim: None,\n            region_of_interest: None,\n        }\n    }\n\n    pub fn rank(&self) -> usize {\n        if cfg!(debug_assertions) {\n            self.consistent().unwrap();\n        }\n        self.shape.rank()\n    }\n\n    fn format_dt_shape_nocheck(&self) -> String {\n        if self.shape.rank() > 0 {\n            format!(\"{:?},{:?}\", self.shape, self.datum_type)\n        } else {\n            format!(\"{:?}\", self.datum_type)\n        }\n    }\n\n    pub fn format_dt_shape(&self) -> String {\n        if cfg!(debug_assertions) {\n            self.consistent().unwrap()\n        }\n        self.format_dt_shape_nocheck()\n    }\n\n    pub fn consistent(&self) -> TractResult<()> {\n        self.shape.consistent()?;\n        if let Some(k) = &self.konst {\n            if !self.matches(k.as_ref(), None)? {\n                bail!(\"fact says {}, constant is {:?}\", self.format_dt_shape_nocheck(), k);\n            }\n            if let Some(bqf) = self.exotic_fact().and_then(|of| of.downcast_ref::<BlockQuantFact>())\n            {\n                if let Some(bqs) = k.storage_as::<BlockQuantStorage>() {\n                    let inner_bqf =\n                        BlockQuantFact::new(dyn_clone::clone_box(bqs.format()), k.shape().into());\n                    ensure!(&inner_bqf == bqf, \"BlockQuantStorage fact mismatch\");\n                }\n            }\n        }\n        if let Some(u) = &self.uniform\n            && self.datum_type != u.datum_type()\n        {\n            bail!(\"fact as uniform value {:?}, but is of type {:?}\", u, self.datum_type);\n        }\n        if let (Some(u), Some(k)) = (self.uniform.as_deref(), self.konst.as_deref()) {\n            if let Some(k) = k.as_uniform() {\n                if &k != u {\n                    bail!(\n                        \"Uniform value and uniform constant mismatch: value:{u:?}, uniform:{k:?}\",\n                    );\n                }\n            } else {\n                bail!(\"Fact said to be uniform ({:?}) and equal to {:?} which is not.\", u, k);\n            }\n        }\n        Ok(())\n    }\n\n    pub fn without_value(&self) -> Self {\n        let mut new = self.clone();\n        new.konst = None;\n        new.uniform = None;\n        new.uniform_tdim = None;\n        new.region_of_interest = None;\n        new\n    }\n\n    pub fn with_exotic_fact<O: Into<Box<dyn ExoticFact>>>(mut self, exotic_fact: O) -> Self {\n        self.exotic_fact = Some(exotic_fact.into());\n        self\n    }\n\n    pub fn exotic_fact(&self) -> Option<&dyn ExoticFact> {\n        self.exotic_fact.as_deref()\n    }\n\n    #[inline]\n    pub fn is_exotic(&self) -> bool {\n        self.exotic_fact.is_some()\n    }\n\n    #[inline]\n    pub fn is_plain(&self) -> bool {\n        self.exotic_fact.is_none()\n    }\n}\n\nimpl Fact for TypedFact {\n    fn to_typed_fact(&self) -> TractResult<Cow<'_, TypedFact>> {\n        if cfg!(debug_assertions) {\n            self.consistent()?\n        }\n        Ok(Cow::Borrowed(self))\n    }\n\n    fn matches(&self, t: &Tensor, symbols: Option<&SymbolValues>) -> TractResult<bool> {\n        if self.datum_type != t.datum_type() || self.shape.len() != t.rank() {\n            return Ok(false);\n        }\n        for i in 0..t.rank() {\n            if let Ok(dim) =\n                self.shape[i].eval(symbols.unwrap_or(&SymbolValues::default())).to_usize()\n                && dim != t.shape()[i]\n            {\n                return Ok(false);\n            }\n        }\n        Ok(true)\n    }\n\n    fn compatible_with(&self, other: &dyn Fact) -> bool {\n        if cfg!(debug_assertions) {\n            self.consistent().unwrap()\n        }\n        if let Some(other) = other.downcast_ref::<Self>() {\n            if cfg!(debug_assertions) {\n                other.consistent().unwrap()\n            }\n            self.datum_type == other.datum_type\n                && self.shape.compatible_with(&other.shape)\n                && self\n                    .exotic_fact()\n                    .zip(other.exotic_fact())\n                    .map(|(a, b)| a.compatible_with(b))\n                    .unwrap_or(true)\n        } else {\n            false\n        }\n    }\n\n    fn datum_type(&self) -> Option<DatumType> {\n        Some(self.datum_type)\n    }\n}\n\nimpl TryFrom<Tensor> for TypedFact {\n    type Error = TractError;\n    fn try_from(t: Tensor) -> TractResult<TypedFact> {\n        TypedFact::try_from(t.into_arc_tensor())\n    }\n}\n\nimpl TryFrom<Arc<Tensor>> for TypedFact {\n    type Error = TractError;\n    fn try_from(t: Arc<Tensor>) -> TractResult<TypedFact> {\n        let exotic_fact = t.exotic_fact()?;\n        let uniform_tdim = if t.datum_type() == TDim::datum_type() && t.len() == 1 {\n            t.try_as_plain().ok().and_then(|d| d.as_slice::<TDim>().ok()).map(|s| s[0].clone())\n        } else if t.len() == 1\n            && t.try_as_plain().is_ok()\n            && (t.datum_type().is_integer() || t.datum_type().is::<bool>())\n        {\n            t.cast_to_scalar::<i64>().ok().map(TDim::Val)\n        } else {\n            None\n        };\n        Ok(TypedFact {\n            datum_type: t.datum_type(),\n            shape: ShapeFact::from_dims(t.shape().iter().map(TDim::from)),\n            uniform: t.as_uniform().map(Arc::new),\n            exotic_fact,\n            konst: Some(t),\n            uniform_tdim,\n            region_of_interest: None,\n        })\n    }\n}\n\nimpl From<&TypedFact> for TypedFact {\n    fn from(fact: &TypedFact) -> TypedFact {\n        fact.clone()\n    }\n}\n\nimpl<'a> TryFrom<&'a Arc<Tensor>> for TypedFact {\n    type Error = TractError;\n    fn try_from(t: &'a Arc<Tensor>) -> TractResult<TypedFact> {\n        TypedFact::try_from(Arc::clone(t))\n    }\n}\n\nimpl fmt::Debug for TypedFact {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \"{:?},{:?}\", self.shape, self.datum_type)?;\n        if self.is_exotic() {\n            if let Some(of) = &self.exotic_fact {\n                write!(fmt, \" 🔍 {of:?} \")?\n            } else {\n                write!(fmt, \" 🔍 <no exotic fact> \")?\n            }\n        }\n        if let Some(k) = &self.konst {\n            write!(fmt, \"🟰 {k:?}\")?\n        }\n        if let Some(u) = &self.uniform {\n            write!(fmt, \" ◻️{u:?}\")?\n        }\n        if let Some(u) = &self.uniform_tdim {\n            write!(fmt, \" 📐{u}\")?\n        }\n        if let Some(r) = &self.region_of_interest {\n            write!(fmt, \" 🬳 {r}\")?\n        }\n        Ok(())\n    }\n}\n\npub trait DatumExt {\n    fn scalar_fact() -> TypedFact;\n    fn fact<S>(shape: S) -> TypedFact\n    where\n        S: Into<ShapeFact>;\n}\n\nimpl<T: Datum> DatumExt for T {\n    #[allow(clippy::needless_borrow)]\n    fn scalar_fact() -> TypedFact {\n        TypedFact::shape::<Self, &[usize]>(&[])\n    }\n\n    fn fact<S>(shape: S) -> TypedFact\n    where\n        S: Into<ShapeFact>,\n    {\n        TypedFact::shape::<Self, _>(shape)\n    }\n}\n\npub trait DatumTypeExt {\n    fn scalar_fact(&self) -> TypedFact;\n    fn fact<S>(&self, shape: S) -> TypedFact\n    where\n        S: Into<ShapeFact>;\n}\n\nimpl DatumTypeExt for DatumType {\n    #[allow(clippy::needless_borrow)]\n    fn scalar_fact(&self) -> TypedFact {\n        TypedFact::dt_shape::<&[usize]>(*self, &[])\n    }\n\n    fn fact<S>(&self, shape: S) -> TypedFact\n    where\n        S: Into<ShapeFact>,\n    {\n        TypedFact::dt_shape(*self, shape)\n    }\n}\n"
  },
  {
    "path": "core/src/model/graph.rs",
    "content": "use super::*;\nuse crate::internal::*;\nuse crate::ops::Op;\nuse crate::prelude::*;\nuse crate::runtime::RunOptions;\n\nuse std::fmt;\nuse tract_data::internal::*;\nuse tract_itertools::Itertools;\n\npub trait SpecialOps<F, O> {\n    fn create_dummy(&self) -> O;\n    fn create_source(&self, fact: F) -> O;\n    fn is_source(op: &O) -> bool;\n    fn wire_node(\n        &mut self,\n        name: impl Into<String>,\n        op: impl Into<O>,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>>;\n    fn add_const(\n        &mut self,\n        name: impl Into<String>,\n        v: impl IntoArcTensor,\n    ) -> TractResult<OutletId>;\n}\n\n/// Main model class\n///\n/// Parameterized by a Fact class.\n#[derive(Clone, Debug)]\npub struct Graph<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: fmt::Debug + fmt::Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    /// all nodes in the model\n    pub nodes: Vec<Node<F, O>>,\n    /// model inputs\n    pub inputs: Vec<OutletId>,\n    /// model outputs\n    pub outputs: Vec<OutletId>,\n    /// outlet labels\n    pub outlet_labels: HashMap<OutletId, String>,\n    /// model properties\n    pub properties: HashMap<String, Arc<Tensor>>,\n    /// symbol scope, including table\n    pub symbols: SymbolScope,\n}\n\nimpl<F, O> Default for Graph<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: fmt::Debug + fmt::Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    fn default() -> Graph<F, O> {\n        Graph {\n            nodes: vec![],\n            inputs: vec![],\n            outputs: vec![],\n            outlet_labels: HashMap::new(),\n            properties: HashMap::new(),\n            symbols: Default::default(),\n        }\n    }\n}\n\nimpl<F, O> Graph<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: fmt::Debug + fmt::Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n    Graph<F, O>: SpecialOps<F, O>,\n{\n    pub fn add_source(&mut self, name: impl Into<String>, fact: F) -> TractResult<OutletId> {\n        let source = self.create_source(fact.clone());\n        let id = self.add_node(name, source, tvec!(fact))?;\n        let id = OutletId::new(id, 0);\n        self.inputs.push(id);\n        Ok(id)\n    }\n}\n\nimpl<F, O> Graph<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: fmt::Debug + fmt::Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    pub fn add_node(\n        &mut self,\n        name: impl Into<String>,\n        op: impl Into<O>,\n        output_facts: TVec<F>,\n    ) -> TractResult<usize> {\n        let op = op.into();\n        let name = name.into();\n        let id = self.nodes.len();\n        let outputs =\n            output_facts.into_iter().map(|fact| Outlet { fact, successors: tvec!() }).collect();\n        let node = Node { id, name, op, inputs: vec![], outputs };\n        self.nodes.push(node);\n        Ok(id)\n    }\n\n    /// Connect a node outlet to a node inlet.\n    pub fn add_edge(&mut self, outlet: OutletId, inlet: InletId) -> TractResult<()> {\n        if let Some(previous) = self.nodes[inlet.node].inputs.get(inlet.slot).cloned() {\n            self.nodes[previous.node].outputs[previous.slot]\n                .successors\n                .retain(|&mut succ| succ != inlet);\n        }\n        {\n            let prec = &mut self.nodes[outlet.node];\n            prec.outputs[outlet.slot].successors.push(inlet);\n        }\n        let succ = &mut self.nodes[inlet.node];\n        #[allow(clippy::comparison_chain)]\n        if inlet.slot == succ.inputs.len() {\n            succ.inputs.push(outlet);\n        } else if inlet.slot < succ.inputs.len() {\n            succ.inputs[inlet.slot] = outlet;\n        } else {\n            bail!(\n                \"Edges must be added in order and consecutive. Trying to connect input {:?} of node {:?} \",\n                inlet.slot,\n                succ\n            )\n        }\n        Ok(())\n    }\n\n    // Inputs\n\n    /// Get model inputs.\n    pub fn input_outlets(&self) -> TractResult<&[OutletId]> {\n        Ok(&self.inputs)\n    }\n\n    /// Change model inputs.\n    pub fn set_input_outlets(&mut self, inputs: &[OutletId]) -> TractResult<()> {\n        self.inputs = inputs.to_vec();\n        Ok(())\n    }\n\n    /// Change model inputs and return `self`.\n    pub fn with_input_outlets(mut self, inputs: &[OutletId]) -> TractResult<Self> {\n        self.set_input_outlets(inputs)?;\n        Ok(self)\n    }\n\n    /// Set model inputs by the node name.\n    pub fn set_input_names(\n        &mut self,\n        inputs: impl IntoIterator<Item = impl AsRef<str>>,\n    ) -> TractResult<()> {\n        let mut ids = vec![];\n        for i in inputs.into_iter() {\n            let node = self.node_by_name(&i)?;\n            for o in 0..node.outputs.len() {\n                ids.push(OutletId::new(node.id, o))\n            }\n        }\n        self.inputs = ids;\n        Ok(())\n    }\n\n    /// Set model inputs by the node name and return `self`.\n    pub fn with_input_names(\n        mut self,\n        inputs: impl IntoIterator<Item = impl AsRef<str>>,\n    ) -> TractResult<Self> {\n        self.set_input_names(inputs)?;\n        Ok(self)\n    }\n\n    /// Get the `ix`-th input tensor type information.\n    pub fn input_fact(&self, ix: usize) -> TractResult<&F> {\n        let input = self.input_outlets()?[ix];\n        self.outlet_fact(input)\n    }\n\n    /// Get the `ix`-th input tensor type information, mutably.\n    pub fn input_fact_mut(&mut self, ix: usize) -> TractResult<&mut F> {\n        let input = self.input_outlets()?[ix];\n        self.outlet_fact_mut(input)\n    }\n\n    /// Set the `ix`-th input tensor type information.\n    pub fn set_input_fact(&mut self, input: usize, fact: F) -> TractResult<()> {\n        let outlet = self.inputs[input];\n        self.set_outlet_fact(outlet, fact)\n    }\n\n    /// Set the `ix`-th input tensor type information and return `self`.\n    pub fn with_input_fact(mut self, input: usize, fact: F) -> TractResult<Self> {\n        self.set_input_fact(input, fact)?;\n        Ok(self)\n    }\n\n    // Outputs\n    /// Get model outputs.\n    pub fn output_outlets(&self) -> TractResult<&[OutletId]> {\n        Ok(&self.outputs)\n    }\n\n    /// Guess outputs from the topology: node or nodes with no successors.\n    pub fn auto_outputs(&mut self) -> TractResult<()> {\n        let outputs = self\n            .nodes\n            .iter()\n            .flat_map(|n| {\n                let id = n.id;\n                n.outputs.iter().enumerate().map(move |(ix, output_fact)| {\n                    (OutletId::new(id, ix), output_fact.successors.len())\n                })\n            })\n            .filter(|(_f, succs)| *succs == 0)\n            .map(|(f, _)| f)\n            .collect();\n        self.outputs = outputs;\n        Ok(())\n    }\n\n    /// Change model outputs.\n    pub fn select_output_outlets(&mut self, outputs: &[OutletId]) -> TractResult<()> {\n        self.outputs = outputs.to_vec();\n        Ok(())\n    }\n\n    /// Change model outputs and return `self`.\n    pub fn with_output_outlets(mut self, outputs: &[OutletId]) -> TractResult<Self> {\n        self.select_output_outlets(outputs)?;\n        Ok(self)\n    }\n\n    /// Set model outputs by node names.\n    pub fn select_outputs_by_name(\n        &mut self,\n        outputs: impl IntoIterator<Item = impl AsRef<str>>,\n    ) -> TractResult<()> {\n        let mut labels: HashMap<StaticName, OutletId> =\n            self.outlet_labels.iter().map(|(o, s)| (Cow::Owned((*s).to_string()), *o)).collect();\n        for n in self.nodes() {\n            for ix in 0..n.outputs.len() {\n                labels.insert(Cow::Owned(format!(\"{}:{}\", &n.name, ix)), OutletId::new(n.id, ix));\n            }\n        }\n        let ids: Vec<OutletId> = outputs\n            .into_iter()\n            .map(|s| {\n                let s = s.as_ref();\n                labels\n                    .get(s)\n                    .cloned()\n                    .or_else(|| self.nodes.iter().find(|n| n.name == s).map(|n| n.id.into()))\n                    .ok_or_else(|| format_err!(\"Node {} not found\", s))\n            })\n            .collect::<TractResult<_>>()?;\n        self.outputs = ids;\n        Ok(())\n    }\n\n    /// Set model outputs by node names and return `self`.\n    pub fn with_outputs_by_name(\n        mut self,\n        outputs: impl IntoIterator<Item = impl AsRef<str>>,\n    ) -> TractResult<Self> {\n        self.select_outputs_by_name(outputs)?;\n        Ok(self)\n    }\n\n    /// Get the `ix`-th input tensor type information.\n    pub fn output_fact(&self, ix: usize) -> TractResult<&F> {\n        let output = self.output_outlets()?[ix];\n        self.outlet_fact(output)\n    }\n\n    /// Get the `ix`-th input tensor type information, mutably.\n    pub fn output_fact_mut(&mut self, ix: usize) -> TractResult<&mut F> {\n        let output = self.output_outlets()?[ix];\n        self.outlet_fact_mut(output)\n    }\n\n    /// Set the `ix`-th output tensor type information.\n    pub fn set_output_fact(&mut self, output: usize, fact: F) -> TractResult<()> {\n        let outlet = self.outputs[output];\n        self.set_outlet_fact(outlet, fact)\n    }\n\n    /// Set the `ix`-th output tensor type information and return `self`.\n    pub fn with_output_fact(mut self, output: usize, fact: F) -> TractResult<Self> {\n        self.set_output_fact(output, fact)?;\n        Ok(self)\n    }\n\n    // nodes and their facts\n\n    /// Iterate over all node names.\n    pub fn node_names(&self) -> impl Iterator<Item = &str> {\n        self.nodes.iter().map(|s| &*s.name)\n    }\n\n    pub fn node_id_by_name(&self, name: &str) -> TractResult<usize> {\n        self.nodes\n            .iter()\n            .find(|n| n.name == name)\n            .map(|n| n.id)\n            .with_context(|| format!(\"No node found for name: \\\"{name}\\\"\"))\n    }\n\n    /// Find a node by its name.\n    pub fn node_by_name(&self, name: impl AsRef<str>) -> TractResult<&Node<F, O>> {\n        let id: usize = self.node_id_by_name(name.as_ref())?;\n        Ok(&self.nodes[id])\n    }\n\n    /// Borrow mutably a node by its name.\n    pub fn node_by_name_mut(&mut self, name: impl AsRef<str>) -> TractResult<&mut Node<F, O>> {\n        let id: usize = self.node_id_by_name(name.as_ref())?;\n        Ok(&mut self.nodes[id])\n    }\n\n    pub fn rename_node(&mut self, id: usize, name: &str) -> TractResult<()> {\n        self.node_mut(id).name = name.to_string();\n        Ok(())\n    }\n\n    /// Find a node by its id.\n    pub fn node(&self, id: usize) -> &Node<F, O> {\n        &self.nodes[id]\n    }\n\n    /// Find a node by its id.\n    pub fn node_mut(&mut self, id: usize) -> &mut Node<F, O> {\n        &mut self.nodes[id]\n    }\n\n    /// Access the nodes table.\n    pub fn nodes(&self) -> &[Node<F, O>] {\n        &self.nodes\n    }\n\n    /// Access the nodes table.\n    pub fn nodes_mut(&mut self) -> &mut [Node<F, O>] {\n        &mut self.nodes\n    }\n\n    /// Get input and output tensor information for a node.\n    pub fn node_facts(&self, id: usize) -> TractResult<(TVec<&F>, TVec<&F>)> {\n        Ok((self.node_input_facts(id)?, self.node_output_facts(id)?))\n    }\n\n    /// Get input tensor information for a node.\n    pub fn node_input_facts(&self, node_id: usize) -> TractResult<TVec<&F>> {\n        self.nodes[node_id].inputs.iter().map(|o| self.outlet_fact(*o)).collect()\n    }\n\n    /// Get output tensor information for a node.\n    pub fn node_output_facts(&self, node_id: usize) -> TractResult<TVec<&F>> {\n        Ok(self.nodes[node_id].outputs.iter().map(|o| &o.fact).collect())\n    }\n\n    // outlets\n\n    /// Get tensor information for a single outlet.\n    pub fn outlet_fact(&self, outlet: OutletId) -> TractResult<&F> {\n        ensure!(outlet.node < self.nodes.len(), \"Invalid outlet for graph\");\n        let outlets = &self.nodes[outlet.node].outputs;\n        outlets\n            .get(outlet.slot)\n            .map(|o| &o.fact)\n            .with_context(|| format!(\"Invalid outlet reference: {outlet:?}\"))\n    }\n\n    /// Get tensor information for a single outlet.\n    pub fn outlet_fact_mut(&mut self, outlet: OutletId) -> TractResult<&mut F> {\n        let outlets = &mut self.nodes[outlet.node].outputs;\n        outlets\n            .get_mut(outlet.slot)\n            .map(|o| &mut o.fact)\n            .with_context(|| format!(\"Invalid outlet reference: {outlet:?}\"))\n    }\n\n    /// Get multiple mutable tensor information for outlets.\n    pub fn outlets_fact_mut(&mut self, outlets: &[OutletId]) -> TractResult<TVec<&mut F>> {\n        assert!(outlets.iter().tuple_combinations().all(|(a, b)| a != b));\n        unsafe {\n            outlets\n                .iter()\n                .map(|o| Ok((self.outlet_fact(*o)? as *const F as *mut F).as_mut().unwrap()))\n                .collect()\n        }\n    }\n\n    /// Set tensor information for a single outlet.\n    pub fn set_outlet_fact(&mut self, outlet: OutletId, fact: F) -> TractResult<()> {\n        let outlets = &mut self.nodes[outlet.node].outputs;\n        if outlets.len() <= outlet.slot {\n            bail!(\"Invalid outlet refererence: {:?}\", outlet)\n        }\n        outlets[outlet.slot].fact = fact;\n        Ok(())\n    }\n\n    /// Set tensor information for a single outlet and return `self`.\n    pub fn with_outlet_fact(mut self, outlet: OutletId, fact: F) -> TractResult<Self> {\n        self.set_outlet_fact(outlet, fact)?;\n        Ok(self)\n    }\n\n    // outlet labels\n\n    /// Get label for an outlet.\n    pub fn outlet_label(&self, outlet: OutletId) -> Option<&str> {\n        self.outlet_labels.get(&outlet).map(|s| &**s)\n    }\n\n    /// Set label for an outlet.\n    pub fn set_outlet_label(&mut self, outlet: OutletId, label: String) -> TractResult<()> {\n        self.outlet_labels.insert(outlet, label);\n        Ok(())\n    }\n\n    /// Set label for an outlet and return `self`.\n    pub fn with_outlet_label(mut self, outlet: OutletId, label: String) -> TractResult<Self> {\n        self.set_outlet_label(outlet, label)?;\n        Ok(self)\n    }\n\n    /// Find outlet by label.\n    pub fn find_outlet_label(&self, label: &str) -> Option<OutletId> {\n        self.outlet_labels.iter().find(|(_k, v)| **v == label).map(|(k, _v)| *k)\n    }\n\n    // misc\n\n    /// Computes an evalutation order for the graph inputs and outputs\n    pub fn eval_order(&self) -> TractResult<Vec<usize>> {\n        super::order::eval_order(self)\n    }\n\n    /// Computes an evalutation order for the graph inputs and outputs. This order will minimize\n    /// temporary buffers.\n    pub fn eval_order_opt_ram(&self) -> TractResult<Vec<usize>> {\n        super::order::eval_order_opt_ram(self)\n    }\n\n    #[cfg(not(all(debug_assertions, feature = \"paranoid_assertions\")))]\n    #[inline]\n    pub fn check_edges(&self) -> TractResult<()> {\n        Ok(())\n    }\n\n    /// Performs a sanity check on network connections.\n    #[cfg(all(debug_assertions, feature = \"paranoid_assertions\"))]\n    pub fn check_edges(&self) -> TractResult<()> {\n        for node_id in self.eval_order()? {\n            let node = &self.nodes[node_id];\n            for (ix, input) in node.inputs.iter().enumerate() {\n                let prec = &self.nodes[input.node];\n                if !prec.outputs[input.slot].successors.contains(&InletId::new(node.id, ix)) {\n                    bail!(\n                        \"Mismatched oncoming edge, node:{} input:{} to {:?} not reciprocated\",\n                        node.id,\n                        ix,\n                        prec\n                    )\n                }\n            }\n            for (ix, output) in node.outputs.iter().enumerate() {\n                for succ in &output.successors {\n                    if self.nodes[succ.node].inputs[succ.slot] != OutletId::new(node.id, ix) {\n                        bail!(\n                            \"Mismatched outgoing edge, node:{} output:{} to {:?} not reciprocated\",\n                            node.id,\n                            ix,\n                            succ\n                        )\n                    }\n                }\n            }\n        }\n        Ok(())\n    }\n\n    /// Evaluate temporary memory usage with its related node at each step of the given order.\n    pub fn eval_tmp_memory_usage<Flushable>(\n        &self,\n        order: &[usize],\n        flushable: Flushable,\n    ) -> TractResult<TVec<(usize, TDim)>>\n    where\n        Flushable: Fn(&Node<F, O>) -> bool,\n    {\n        super::memory::eval_tmp_memory_usage(self, order, flushable)\n    }\n\n    #[cfg(not(all(debug_assertions, feature = \"paranoid_assertions\")))]\n    #[inline]\n    pub fn check_names(&self) -> TractResult<()> {\n        Ok(())\n    }\n\n    /// Performs a sanity check on network connections.\n    #[cfg(all(debug_assertions, feature = \"paranoid_assertions\"))]\n    pub fn check_names(&self) -> TractResult<()> {\n        let dups =\n            self.eval_order()?.iter().map(|n| &self.nodes[*n].name).duplicates().collect_vec();\n        ensure!(dups.len() == 0, \"Duplicate node name(s) : {:?}\\n{}\", dups, &self);\n        Ok(())\n    }\n\n    // Converts the model into a `RunnableModel` to actually process user data.\n    // pub fn into_runnable(self) -> TractResult<Arc<RunnableModel<F, O>>> {\n    //     crate::plan::SimplePlan::new_with_options(self, &PlanOptions::default())\n    // }\n\n    /// Converts the model into a `RunnableModel` to actually process user data. This variant\n    /// accepts options.\n    pub fn into_runnable_with_options(\n        self,\n        options: &RunOptions,\n    ) -> TractResult<Arc<RunnableModel<F, O>>> {\n        crate::plan::SimplePlan::new_with_options(self, options)\n    }\n\n    pub fn linear_prec(&self, id: usize) -> TractResult<Option<&Node<F, O>>> {\n        let node = &self.nodes()[id];\n        rule_if!(node.inputs.len() == 1);\n        let prec = &self.nodes()[node.inputs[0].node];\n        rule_if!(prec.outputs.iter().map(|of| of.successors.len()).sum::<usize>() == 1);\n        Ok(Some(prec))\n    }\n\n    pub fn single_prec(&self, id: usize) -> TractResult<Option<&Node<F, O>>> {\n        let node = &self.nodes()[id];\n        rule_if!(node.inputs.len() == 1);\n        let prec = &self.nodes()[node.inputs[0].node];\n        Ok(Some(prec))\n    }\n\n    pub fn all_prec(&self, id: usize) -> TractResult<Option<TVec<&Node<F, O>>>> {\n        let node = &self.nodes()[id];\n        rule_if!(node.inputs.len() > 0);\n        Ok(Some(node.inputs.iter().map(|n| &self.nodes()[n.node]).collect()))\n    }\n\n    /// linear_succ is only intended for optimisation of simple operators\n    /// with 1 output, and only 1 output successors (successor with only 1 input)\n    pub fn linear_succ(&self, id: usize) -> TractResult<Option<&Node<F, O>>> {\n        let node = &self.nodes()[id];\n\n        rule_if!(node.outputs.len() == 1);\n        rule_if!(node.outputs[0].successors.len() == 1);\n        let succ = node.outputs[0].successors[0];\n        let succ = &self.nodes()[succ.node];\n        rule_if!(succ.inputs.len() == 1);\n        Ok(Some(succ))\n    }\n\n    pub fn single_succ(&self, id: usize) -> TractResult<Option<&Node<F, O>>> {\n        let node = &self.nodes()[id];\n\n        rule_if!(node.outputs.len() == 1);\n        rule_if!(node.outputs[0].successors.len() == 1);\n        let succ = node.outputs[0].successors[0];\n        Ok(Some(&self.nodes()[succ.node]))\n    }\n\n    pub fn all_succ(&self, id: usize) -> TractResult<Option<TVec<&Node<F, O>>>> {\n        let node = &self.nodes()[id];\n        rule_if!(!node.outputs.is_empty());\n\n        Ok(Some(\n            node.outputs\n                .iter()\n                .flat_map(|o| {\n                    o.successors.iter().map(|succ| &self.nodes()[succ.node]).collect::<Vec<_>>()\n                })\n                .collect(),\n        ))\n    }\n\n    pub fn outlet_successors(&self, outlet: OutletId) -> &[InletId] {\n        &self.nodes[outlet.node].outputs[outlet.slot].successors\n    }\n\n    /// retrieve of create a symbol\n    pub fn sym(&self, s: &str) -> Symbol {\n        self.symbols.sym(s)\n    }\n\n    /// create a new symbol with the prefix\n    pub fn new_sym_with_prefix(&self, prefix: &str) -> Symbol {\n        self.symbols.new_with_prefix(prefix)\n    }\n\n    /// generates a name for a new node in the model that will not conflict (by suffixing with a\n    /// dot and number)\n    pub fn unique_name<'n>(&self, prefix: impl Into<Cow<'n, str>>) -> Cow<'n, str> {\n        let prefix = prefix.into();\n        if self.nodes.iter().all(|n| n.name != *prefix) {\n            return prefix;\n        }\n        for i in 1.. {\n            let s = format!(\"{prefix}.{i}\");\n            if self.nodes.iter().all(|n| n.name != s) {\n                return Cow::Owned(s);\n            }\n        }\n        unreachable!();\n    }\n}\n\nimpl<F, O> fmt::Display for Graph<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: fmt::Debug + fmt::Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {\n        for i in 0..self.nodes.len() {\n            let input_1 =\n                self.nodes[i].inputs.first().map(|o| format!(\"{o:?}\")).unwrap_or_default();\n            let input_2 = self.nodes[i].inputs.get(1).map(|o| format!(\"{o:?}\")).unwrap_or_default();\n            let successors = self.nodes[i]\n                .outputs\n                .first()\n                .iter()\n                .flat_map(|o| o.successors.iter())\n                .collect_vec();\n            let output_1 = successors.first().map(|o| format!(\"{o:?}\")).unwrap_or_default();\n            let output_2 = successors.get(1).map(|o| format!(\"{o:?}\")).unwrap_or_default();\n            writeln!(\n                fmt,\n                \"{:5} | {:8} {:8} -> {:8} {:8} | {:25} {:50} {} => {}\",\n                i,\n                input_1,\n                input_2,\n                output_1,\n                output_2,\n                self.nodes[i].op().name(),\n                self.nodes[i].name,\n                self.node_input_facts(i).unwrap().iter().map(|f| format!(\"{f:?}\")).join(\" ; \"),\n                self.node_output_facts(i).unwrap().iter().map(|f| format!(\"{f:?}\")).join(\" ; \"),\n            )?;\n            if self.nodes[i].inputs.len() > 2 {\n                writeln!(\n                    fmt,\n                    \"                                               |   * inputs: {}\",\n                    self.nodes[i].inputs.iter().map(|s| format!(\"{s:?}\")).join(\", \")\n                )?;\n            }\n            if self.nodes[i].outputs.len() > 1\n                || successors.len() > 2\n                || (self.outlet_label(i.into()).is_some()\n                    && self.outlet_label(i.into()).unwrap() != self.nodes[i].name)\n            {\n                for o in 0..self.nodes[i].outputs.len() {\n                    if self.outlet_successors((i, o).into()).len() > 0 {\n                        writeln!(\n                            fmt,\n                            \"                                               |   * output #{}: {} {}\",\n                            o,\n                            self.outlet_label((i, o).into()).unwrap_or(\"\"),\n                            self.outlet_successors((i, o).into())\n                                .iter()\n                                .map(|s| format!(\"{s:?}\"))\n                                .join(\", \"),\n                        )?;\n                    }\n                }\n            }\n        }\n        writeln!(fmt, \"outputs: {}\", self.outputs.iter().map(|o| format!(\"{o:?}\")).join(\", \"))?;\n        Ok(())\n    }\n}\n\nimpl<F, O> Graph<F, O>\nwhere\n    F: Fact + Clone + 'static + for<'a> std::convert::From<&'a F>,\n    O: std::fmt::Display\n        + std::fmt::Debug\n        + Clone\n        + AsRef<dyn Op>\n        + AsMut<dyn Op>\n        + Clone\n        + 'static\n        + for<'a> std::convert::From<&'a O>,\n    Graph<F, O>: SpecialOps<F, O>,\n{\n    #[cfg(debug_assertions)]\n    pub fn check_compact(&self) -> TractResult<()> {\n        let order = self.eval_order()?;\n        let useless_sources = self\n            .input_outlets()?\n            .iter()\n            .filter(|io| {\n                self.outlet_successors(**io).len() == 0\n                    && !self.output_outlets().unwrap().contains(io)\n            })\n            .count();\n        if order.len() + useless_sources != self.nodes.len() {\n            bail!(\n                \"Eval order is {} long, nodes are {}, including {} unused sources\",\n                order.len(),\n                self.nodes.len(),\n                useless_sources\n            );\n        }\n        if (0..order.len()).any(|ix| order[ix] != ix) {\n            bail!(\"eval order is not trivial\");\n        }\n        let mut seen = std::collections::HashSet::new();\n        for (ix, n) in self.nodes.iter().enumerate() {\n            if ix != n.id {\n                bail!(\"Invalid node id: position is {}, node is {}\", ix, n);\n            }\n            if seen.contains(&n.name) {\n                bail!(\"duplicate name for node {n}\");\n            }\n            seen.insert(&n.name);\n        }\n        Ok(())\n    }\n\n    pub fn compact(&mut self) -> TractResult<()> {\n        let mut order = self.eval_order()?;\n        if order.len() == self.nodes.len() && order.iter().enumerate().all(|(a, b)| a == *b) {\n            return Ok(());\n        }\n        for i in &self.inputs {\n            if !order.contains(&i.node) {\n                order.push(i.node);\n            }\n        }\n        let mut old_to_new = vec![usize::MAX; self.nodes.len()];\n        let mut new_nodes = vec![\n            Node {\n                id: self.nodes.len(),\n                name: \"\".to_string(),\n                inputs: vec![],\n                op: self.create_dummy(),\n                outputs: tvec!(),\n            };\n            order.len()\n        ];\n        for (ix, id) in order.iter().enumerate() {\n            old_to_new[*id] = ix;\n            std::mem::swap(&mut new_nodes[ix], &mut self.nodes[*id]);\n        }\n        for node in &mut new_nodes {\n            if self.inputs.iter().any(|n| n.node == node.id) && !Self::is_source(&node.op) {\n                node.inputs.clear();\n                node.op = self.create_source(node.outputs[0].fact.clone());\n            }\n            node.id = old_to_new[node.id];\n            for input in &mut node.inputs {\n                assert!(old_to_new[input.node] < order.len());\n                input.node = old_to_new[input.node];\n            }\n            for output in &mut node.outputs {\n                for succ in &mut output.successors {\n                    succ.node = old_to_new[succ.node];\n                }\n                output.successors.retain(|s| s.node < order.len());\n                output.successors.sort();\n            }\n        }\n        self.nodes = new_nodes;\n        for input in &mut self.inputs {\n            assert!(old_to_new[input.node] < order.len());\n            input.node = old_to_new[input.node];\n        }\n        for output in &mut self.outputs {\n            assert!(old_to_new[output.node] < order.len());\n            output.node = old_to_new[output.node];\n        }\n        self.outlet_labels = std::mem::take(&mut self.outlet_labels)\n            .into_iter()\n            .map(|(k, v)| (OutletId::new(old_to_new[k.node], k.slot), v))\n            .filter(|(k, _)| k.node < order.len())\n            .collect();\n        ensure!(self.nodes.iter().enumerate().all(|(ix, n)| n.id == ix));\n        #[cfg(debug_assertions)]\n        {\n            self.check_compact().context(\"after graph compaction\")?;\n        }\n        Ok(())\n    }\n\n    pub fn into_compact(mut self) -> TractResult<Self> {\n        self.compact()?;\n        Ok(self)\n    }\n}\n\npub trait IntoRunnable<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: fmt::Debug + fmt::Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    fn into_runnable(self) -> TractResult<Arc<RunnableModel<F, O>>>;\n}\n\nimpl<G, F, O> IntoRunnable<F, O> for G\nwhere\n    F: Fact + Clone + 'static,\n    O: fmt::Debug + fmt::Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n    G: Into<Arc<Graph<F, O>>>,\n{\n    fn into_runnable(self) -> TractResult<Arc<RunnableModel<F, O>>> {\n        SimplePlan::new(self)\n    }\n}\n"
  },
  {
    "path": "core/src/model/helpers.rs",
    "content": "use crate::ops::binary::{BinMiniOp, TypedBinOp};\nuse crate::ops::konst::Const;\nuse crate::prelude::*;\nuse tract_data::internal::Approximation;\n\npub trait TypedModelHelpers {\n    fn next_node(&self, node: &TypedNode) -> Option<&TypedNode>;\n    fn previous_node(&self, node: &TypedNode) -> Option<&TypedNode>;\n    fn previous_nodes(&self, node: &TypedNode) -> TVec<&TypedNode>;\n    fn collect_const_inputs<'a>(&'a self, node: &TypedNode) -> TVec<&'a Const>;\n    fn single_prev_node_as<O: TypedOp>(&self, node: &TypedNode) -> Option<(usize, &TypedNode)>;\n    fn matches_single_input_const(&self, node: &TypedNode, konst: f32) -> bool;\n    fn find_succ_bin_with_const<B: BinMiniOp>(\n        &self,\n        node: &TypedNode,\n        konst: f32,\n    ) -> Option<&TypedNode>;\n    fn find_succ_bin_with_outlet<B: BinMiniOp>(\n        &self,\n        node: &TypedNode,\n        outlet_id: &OutletId,\n    ) -> Option<&TypedNode>;\n}\n\nimpl TypedModelHelpers for TypedModel {\n    fn next_node(&self, node: &TypedNode) -> Option<&TypedNode> {\n        if node.outputs.iter().map(|of| of.successors.len()).sum::<usize>() != 1 {\n            return None;\n        }\n        let succ = node.outputs[0].successors[0];\n        Some(&self.nodes()[succ.node])\n    }\n\n    fn previous_node(&self, node: &TypedNode) -> Option<&TypedNode> {\n        if node.inputs.len() != 1 {\n            return None;\n        }\n        Some(&self.nodes()[node.inputs[0].node])\n    }\n\n    fn previous_nodes(&self, node: &TypedNode) -> TVec<&TypedNode> {\n        node.inputs.iter().map(|n| &self.nodes()[n.node]).collect()\n    }\n\n    fn collect_const_inputs<'a>(&'a self, node: &TypedNode) -> TVec<&'a Const> {\n        node.inputs\n            .iter()\n            .filter_map(|i| {\n                let prec = &self.nodes()[i.node];\n                prec.op_as::<Const>()\n            })\n            .collect::<TVec<_>>()\n    }\n\n    fn single_prev_node_as<O: TypedOp>(&self, node: &TypedNode) -> Option<(usize, &TypedNode)> {\n        let prev_nodes = node\n            .inputs\n            .iter()\n            .enumerate()\n            .filter_map(|(in_idx, i)| {\n                let prec = &self.nodes()[i.node];\n                prec.op_is::<O>().then_some((in_idx, prec))\n            })\n            .collect::<TVec<_>>();\n\n        if prev_nodes.len() != 1 { None } else { Some(prev_nodes[0]) }\n    }\n\n    fn matches_single_input_const(&self, node: &TypedNode, konst: f32) -> bool {\n        let consts = self.collect_const_inputs(node);\n        if consts.len() != 1 {\n            return false;\n        }\n        let Ok(in_const) = consts[0].val().cast_to_dt(DatumType::F32) else {\n            return false;\n        };\n        let Ok(in_const) = in_const.to_scalar_tensor() else {\n            return false;\n        };\n        in_const\n            .close_enough(&tract_data::prelude::tensor0(konst), Approximation::Approximate)\n            .is_ok()\n    }\n\n    fn find_succ_bin_with_const<B: BinMiniOp>(\n        &self,\n        node: &TypedNode,\n        konst: f32,\n    ) -> Option<&TypedNode> {\n        let succ = self.single_succ(node.id).ok()??;\n        let succ_op = succ.op_as::<TypedBinOp>()?;\n        (succ_op.0.is::<B>() && self.matches_single_input_const(succ, konst)).then_some(succ)\n    }\n\n    fn find_succ_bin_with_outlet<B: BinMiniOp>(\n        &self,\n        node: &TypedNode,\n        outlet_id: &OutletId,\n    ) -> Option<&TypedNode> {\n        let succ = self.single_succ(node.id).ok()??;\n        let succ_op = succ.op_as::<TypedBinOp>()?;\n        (succ_op.0.is::<B>() && succ.inputs.contains(outlet_id)).then_some(succ)\n    }\n}\n"
  },
  {
    "path": "core/src/model/memory.rs",
    "content": "use super::*;\nuse crate::prelude::*;\nuse std::collections::HashSet;\nuse std::fmt::Debug;\nuse std::fmt::Display;\nuse tract_data::internal::*;\n\n/// Evaluate temporary memory usage with its related node at each step of the given order.\npub fn eval_tmp_memory_usage<F, O, Flushable>(\n    model: &Graph<F, O>,\n    order: &[usize],\n    flushable: Flushable,\n) -> TractResult<TVec<(usize, TDim)>>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n    Flushable: Fn(&Node<F, O>) -> bool,\n{\n    let outputs = model.output_outlets()?.to_vec();\n\n    let flush_lists = super::order::build_flush_list(model, order, &outputs, &flushable);\n    let mut values: TVec<bool> = tvec![false; model.nodes.len()];\n\n    let mut mem_by_steps: TVec<_> = tvec![(0, 0.into()); order.len()];\n\n    let flushable_nodes = model\n        .nodes()\n        .iter()\n        .filter(|node| (flushable)(node))\n        .map(|it| it.id)\n        .collect::<HashSet<_>>();\n\n    for (step, n) in order.iter().enumerate() {\n        let node = model.node(*n);\n\n        for flush in flush_lists[step].iter() {\n            values[*flush] = false;\n        }\n\n        // Active nodes are node that has not been flushed + inputs of the current node and current node.\n        let mut step_active_nodes: HashSet<_> =\n            values.iter().enumerate().filter_map(|(n, active)| active.then_some(n)).collect();\n\n        step_active_nodes.extend(node.inputs.iter().map(|it| it.node));\n        step_active_nodes.insert(*n);\n\n        values[*n] = true;\n\n        // Keep only flushable nodes.\n        let step_active_flushable_nodes = step_active_nodes.intersection(&flushable_nodes);\n\n        mem_by_steps[step] = (*n, 0.into());\n\n        for n in step_active_flushable_nodes {\n            let out_facts = model\n                .node_output_facts(*n)?\n                .into_iter()\n                .map(|it| it.to_typed_fact())\n                .collect::<TractResult<TVec<_>>>()?;\n            mem_by_steps[step].1 += out_facts.iter().map(|it| it.mem_size()).sum::<TDim>();\n        }\n    }\n    Ok(mem_by_steps)\n}\n"
  },
  {
    "path": "core/src/model/mod.rs",
    "content": "//! ## Models and their lifecycle\n//!\n//! In order to reason on the model and performs optimisations, a model needs\n//! to be `typed`. This means all tensor exchanged between the nodes have a\n//! well defined element type (f32, i32, etc) and a shape ([1, 12, 53, 13]).\n//!\n//! A model typically starts as an `InferenceModel`, with minimum or partial\n//! tensor type information. At this stage, the application developper can add\n//! types and shapes hints (like the model inputs and output element types\n//! and shapes), then `tract` will perform type inference propagating this\n//! information. Hopefully `tract` will be able to infer a type and shape for\n//! all tensors in the model graph.\n//!\n//! At this stage, the model can be converted into a `TypedModel`.\n//!\n//! InferanceModel and TypeModel are two variants of `Graph`, Parameterized\n//! by a Fact implementation: TypedModel uses TypedFact, enforcing\n//! complete determination of element type and shape, and allowing a constant\n//! value for the tensor. InferenceModel uses InferenceFact, which can handle\n//! partial information.\n//!\n//! We call `declutter` the process getting the network closer to a normal\n//! form:.  This normal form is akin to an IR in compiler technologies. This is\n//! the favourite form on which tract optimisation is implemented.\n//!\n//! For instance an Add node adding a constant input to a variable\n//! tensor input would be replaced by an unary Add operator taking only the\n//! variable input and for which the constant to add is a fixed construction\n//! attribute. In the same decluttering process, we try and replace proprietary\n//! operators (eg, from TensorFlow) by tract core operators: it is not always\n//! possible to simply map TensorFlow operators to tract-core while loading the\n//! network: their interfaces can be different (what is an input, what is an\n//! attribute) and constant propagation may be necessary before the right\n//! core operator could be chosen.\n//!\nuse std::collections::HashMap;\nuse std::str;\n\nmod fact;\nmod graph;\nmod helpers;\npub mod memory;\nmod node;\npub mod order;\nmod patch;\nmod rewriter;\npub mod translator;\npub mod typed;\npub use self::helpers::*;\n\npub use self::fact::*;\npub use self::graph::*;\npub use self::node::*;\npub use self::patch::ModelPatch;\npub use self::rewriter::Rewriter;\npub use crate::ops::{Op, TypedOp};\n\npub use typed::*;\n"
  },
  {
    "path": "core/src/model/node.rs",
    "content": "use super::*;\nuse crate::internal::*;\nuse crate::ops::Op;\nuse std::fmt;\nuse std::fmt::{Debug, Display};\nuse tract_itertools::Itertools;\n\n/// A Node in an Model.\n///\n/// Parameterized by a Fact implementation matching the one used in the\n/// model.\n#[derive(Debug, Clone)]\npub struct Node<F: Fact, O> {\n    /// node id in the model\n    ///\n    /// Caution: this id will not be persistent during networks transformation\n    pub id: usize,\n    /// name of the node\n    ///\n    /// This will usually come from the importing framework. `tract`\n    /// transformation try to maintain the names accross transformations.\n    pub name: String,\n    /// A list of incoming tensors, identified by the node outlet that creates\n    /// them.\n    pub inputs: Vec<OutletId>,\n    /// The actual operation the node performs.\n    pub op: O,\n    /// List of ouputs, with their descendant and tensor type information.\n    pub outputs: TVec<Outlet<F>>,\n}\n\nimpl<F: Fact, O: std::fmt::Display> fmt::Display for Node<F, O> {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \"#{} \\\"{}\\\" {}\", self.id, self.name, self.op)\n    }\n}\n\nimpl<F, NodeOp> Node<F, NodeOp>\nwhere\n    F: Fact,\n    NodeOp: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + AsMut<dyn Op>,\n{\n    /// Access the op of the node\n    pub fn op(&self) -> &dyn Op {\n        self.op.as_ref()\n    }\n\n    /// Try to downcast the node operation to O.\n    pub fn op_as<O: Op>(&self) -> Option<&O> {\n        self.op().downcast_ref::<O>()\n    }\n\n    /// Try to downcast the node operation to O.\n    pub fn op_as_mut<O: Op>(&mut self) -> Option<&mut O> {\n        self.op.as_mut().downcast_mut::<O>()\n    }\n\n    /// Check if the node operation is of type O.\n    pub fn op_is<O: Op>(&self) -> bool {\n        self.op_as::<O>().is_some()\n    }\n\n    /// Check that this node produce the same outputs as `other`.\n    pub fn same_as(&self, other: &Node<F, NodeOp>) -> bool {\n        self.inputs == other.inputs && self.op().dyn_eq(other.op())\n    }\n}\n\n/// Information for each outlet of a node\n#[derive(Clone, Default)]\npub struct Outlet<F: Fact> {\n    /// the tensor type information\n    pub fact: F,\n    /// where this outlet is used.\n    pub successors: TVec<InletId>,\n}\n\nimpl<F: Fact> fmt::Debug for Outlet<F> {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(\n            fmt,\n            \"{:?} {}\",\n            self.fact,\n            self.successors.iter().map(|o| format!(\"{o:?}\")).join(\" \")\n        )\n    }\n}\n\n/// Identifier for a node output in the graph.\n///\n/// This happens to be a unique identifier of any variable tensor in the graph\n/// (as the graph typically connect one single node output to one or several\n/// inputs slots)\n#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, new)]\npub struct OutletId {\n    /// node identifier in the graph\n    pub node: usize,\n    /// rank of the input in the node\n    pub slot: usize,\n}\n\nimpl fmt::Debug for OutletId {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \"{}/{}>\", self.node, self.slot)\n    }\n}\n\nimpl From<usize> for OutletId {\n    fn from(node: usize) -> OutletId {\n        OutletId::new(node, 0)\n    }\n}\n\nimpl From<(usize, usize)> for OutletId {\n    fn from(pair: (usize, usize)) -> OutletId {\n        OutletId::new(pair.0, pair.1)\n    }\n}\n\n/// Identifier for a node input in the graph.\n#[derive(Clone, Copy, PartialEq, Eq, Hash, new, Ord, PartialOrd)]\npub struct InletId {\n    /// node identifier in the graph\n    pub node: usize,\n    /// rank of the input in the node\n    pub slot: usize,\n}\n\nimpl fmt::Debug for InletId {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \">{}/{}\", self.node, self.slot)\n    }\n}\n"
  },
  {
    "path": "core/src/model/order.rs",
    "content": "//! Evaluation order for nodes.\nuse crate::internal::*;\nuse bit_set::BitSet;\nuse std::collections::VecDeque;\nuse std::fmt::{Debug, Display};\nuse tract_itertools::Itertools;\n\n/// Find an evaluation order for a model, using its default inputs and outputs\n/// as boundaries.\npub fn eval_order<F, O>(model: &super::Graph<F, O>) -> TractResult<Vec<usize>>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    let inputs = model.input_outlets()?.iter().map(|n| n.node).collect_vec();\n    let targets = model.output_outlets()?.iter().map(|n| n.node).collect_vec();\n    eval_order_for_nodes(model.nodes(), &inputs, &targets, &[])\n}\n\n/// Find a working evaluation order for a list of nodes.\n/// This algorithm starts from the outputs, so it will only compute what is necessary.\npub fn eval_order_for_nodes<F, O>(\n    nodes: &[Node<F, O>],\n    model_inputs: &[usize],\n    model_outputs: &[usize],\n    more_dependencies: &[(usize, usize)],\n) -> TractResult<Vec<usize>>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    let mut done = BitSet::with_capacity(nodes.len());\n    let mut order: Vec<usize> = vec![];\n    for &model_target in model_outputs {\n        if done.contains(model_target) {\n            continue;\n        }\n        let mut current_stack: Vec<(usize, usize)> = vec![(model_target, 0)];\n        let mut pending = BitSet::with_capacity(nodes.len());\n        while let Some((current_node, current_input)) = current_stack.pop() {\n            let deps_from_inputs = nodes[current_node].inputs.len();\n            let all_deps_count =\n                deps_from_inputs + more_dependencies.iter().filter(|a| a.0 == current_node).count();\n            if model_inputs.contains(&current_node) || current_input == all_deps_count {\n                order.push(current_node);\n                done.insert(current_node);\n                pending.remove(current_node);\n            } else {\n                let precursor: usize = nodes[current_node]\n                    .inputs\n                    .iter()\n                    .filter(|n| nodes[n.node].inputs.len() > 0)\n                    .map(|n| n.node)\n                    .chain(more_dependencies.iter().filter(|a| a.0 == current_node).map(|n| n.1))\n                    .chain(\n                        nodes[current_node]\n                            .inputs\n                            .iter()\n                            .filter(|n| nodes[n.node].inputs.len() == 0)\n                            .map(|n| n.node),\n                    )\n                    .nth(current_input)\n                    .unwrap();\n                if done.contains(precursor) {\n                    current_stack.push((current_node, current_input + 1));\n                } else if pending.contains(precursor) {\n                    if log_enabled!(log::Level::Debug) {\n                        debug!(\"Loop detected:\");\n                        current_stack\n                            .iter()\n                            .skip_while(|s| s.0 != precursor)\n                            .for_each(|n| debug!(\"  {}\", nodes[n.0]));\n                    }\n                    bail!(\"Loop detected\")\n                } else {\n                    pending.insert(precursor);\n                    current_stack.push((current_node, current_input));\n                    current_stack.push((precursor, 0));\n                }\n            }\n        }\n    }\n    Ok(order)\n}\n\npub fn build_flush_list<F, O, Flushable>(\n    model: &Graph<F, O>,\n    order: &[usize],\n    outputs: &[OutletId],\n    flushable: Flushable,\n) -> Vec<TVec<usize>>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n    Flushable: Fn(&Node<F, O>) -> bool,\n{\n    let mut values_needed_until_step = vec![0; model.nodes().len()];\n    for (step, node) in order.iter().enumerate() {\n        for i in &model.node(*node).inputs {\n            values_needed_until_step[i.node] = step;\n        }\n    }\n    for o in outputs.iter() {\n        values_needed_until_step[o.node] = order.len();\n    }\n    let mut flush_lists: Vec<TVec<usize>> = vec![tvec!(); order.len() + 1];\n\n    for (node, &flush_at) in values_needed_until_step.iter().enumerate() {\n        if flush_at != 0 && (flushable)(model.node(node)) {\n            flush_lists[flush_at].push(node)\n        }\n    }\n    flush_lists\n}\n\n/// Find an evaluation order for a list of model trying to minimize memory occupation.\npub fn eval_order_opt_ram<F, O>(model: &super::Graph<F, O>) -> TractResult<Vec<usize>>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    let inputs = model.input_outlets()?.iter().map(|n| n.node).collect_vec();\n    let targets = model.output_outlets()?.iter().map(|n| n.node).collect_vec();\n    eval_order_opt_ram_for_nodes(model.nodes(), &inputs, &targets, &[])\n}\n\n/// Find an evaluation order for a list of nodes trying to minimize memory occupation.\npub fn eval_order_opt_ram_for_nodes<F, O>(\n    nodes: &[Node<F, O>],\n    model_inputs: &[usize],\n    model_outputs: &[usize],\n    more_dependencies: &[(usize, usize)],\n) -> TractResult<Vec<usize>>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    let tocompute: BitSet =\n        eval_order_for_nodes(nodes, model_inputs, model_outputs, more_dependencies)?\n            .into_iter()\n            .collect();\n\n    let mut ups = vec![tvec!(); nodes.len()];\n    let mut downs = vec![tvec!(); nodes.len()];\n    for ix in tocompute.iter() {\n        for input in &nodes[ix].inputs {\n            if !ups[ix].contains(&input.node) {\n                ups[ix].push(input.node);\n                downs[input.node].push(ix);\n            }\n        }\n    }\n    for (down, up) in more_dependencies {\n        if !ups[*down].contains(up) {\n            ups[*down].push(*up);\n            downs[*up].push(*down);\n        }\n    }\n\n    #[derive(Debug)]\n    struct Dfs {\n        ups: Vec<TVec<usize>>,\n        downs: Vec<TVec<usize>>,\n    }\n\n    let dfs = Dfs { ups, downs };\n\n    #[derive(Debug, Clone, PartialEq, Eq)]\n    struct Path {\n        order: Vec<usize>,\n        done: BitSet,\n        alive: BitSet,\n        candidates: BitSet,\n        cache_upstream: Vec<Option<(usize, BitSet)>>,\n    }\n\n    impl Path {\n        fn with_size(nodes: usize) -> Path {\n            Path {\n                order: Vec::with_capacity(nodes),\n                done: BitSet::with_capacity(nodes),\n                alive: BitSet::with_capacity(nodes),\n                candidates: BitSet::with_capacity(nodes),\n                cache_upstream: vec![None; nodes],\n            }\n        }\n\n        fn follow_one(&mut self, dfs: &Dfs, next: usize) {\n            assert!(!self.done.contains(next));\n            self.order.push(next);\n            self.done.insert(next);\n            self.alive.insert(next);\n            self.candidates.remove(next);\n            for &succ in &dfs.downs[next] {\n                self.candidates.insert(succ);\n            }\n            for &maybe_dead in &dfs.ups[next] {\n                if dfs.downs[maybe_dead].iter().all(|n| self.done.contains(*n)) {\n                    self.alive.remove(maybe_dead);\n                }\n            }\n            self.cache_upstream[next] = None;\n            for c in &self.candidates {\n                if let Some(upstream) = self.cache_upstream[c].as_mut() {\n                    upstream.0 -= upstream.1.remove(next) as usize;\n                }\n            }\n        }\n\n        fn best_upstream_starter(&mut self, dfs: &Dfs) -> Option<usize> {\n            for from in self.candidates.iter() {\n                if self.cache_upstream[from].is_none() {\n                    let mut found = BitSet::with_capacity(self.done.capacity());\n                    let mut visited = self.done.clone();\n                    let mut todo = VecDeque::<usize>::new();\n                    todo.push_back(from);\n                    visited.insert(from);\n                    while let Some(next) = todo.pop_front() {\n                        if dfs.ups[next].len() == 0 {\n                            found.insert(next);\n                        }\n                        for up in &dfs.ups[next] {\n                            if visited.insert(*up) {\n                                todo.push_back(*up);\n                            }\n                        }\n                    }\n                    debug_assert!(found.count() > 0);\n                    self.cache_upstream[from] = Some((found.count(), found));\n                }\n            }\n            self.candidates\n                .iter()\n                .map(|n| self.cache_upstream[n].as_ref().unwrap())\n                .min_by_key(|s| s.0)\n                .map(|s| s.1.iter().next().unwrap())\n        }\n    }\n\n    let mut done: Path = Path::with_size(nodes.len());\n    for i in model_inputs {\n        if tocompute.contains(*i) {\n            done.follow_one(&dfs, *i);\n        }\n    }\n\n    while !model_outputs.iter().all(|o| done.done.contains(*o)) {\n        let next = if let Some(next) =\n            done.candidates.iter().find(|n| dfs.ups[*n].iter().all(|n| done.done.contains(*n)))\n        {\n            next\n        } else if let Some(next) = done.best_upstream_starter(&dfs) {\n            next\n        } else {\n            tocompute\n                .difference(&done.done)\n                .find(|n| dfs.ups[*n].iter().all(|n| done.done.contains(*n)))\n                .unwrap()\n        };\n        done.follow_one(&dfs, next);\n    }\n\n    Ok(done.order.clone())\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::internal::*;\n    use crate::ops::array::Gather;\n    use crate::ops::math;\n\n    #[test]\n    fn simple() {\n        let mut model = TypedModel::default();\n        let a = model.add_source(\"a\", f32::fact([1])).unwrap();\n        let b = model.add_const(\"b\", tensor1(&[12.0f32])).unwrap();\n        let add = model.wire_node(\"add\", math::add(), &[a, b]).unwrap()[0];\n        model.auto_outputs().unwrap();\n        assert_eq!(model.eval_order().unwrap(), vec!(a.node, b.node, add.node));\n        assert_eq!(model.eval_order_opt_ram().unwrap(), vec!(a.node, b.node, add.node));\n    }\n\n    #[test]\n    fn diamond() {\n        let mut model = TypedModel::default();\n        let a = model.add_source(\"a\", f32::fact([1])).unwrap();\n        let add = model.wire_node(\"add\", math::add(), &[a, a]).unwrap()[0];\n        model.auto_outputs().unwrap();\n        assert_eq!(model.eval_order().unwrap(), vec!(a.node, add.node));\n        assert_eq!(model.eval_order_opt_ram().unwrap(), vec!(a.node, add.node));\n    }\n\n    // The test is disabled on Wasm because it uses threads.\n    #[cfg(not(target_family = \"wasm\"))]\n    #[test]\n    fn dodge_loop() {\n        let mut model = TypedModel::default();\n        let a = model.add_source(\"a\", f32::fact([1])).unwrap();\n        let add = model.wire_node(\"add\", math::add(), &[a, a]).unwrap()[0];\n        let neg = model.wire_node(\"neg\", math::add(), &[add, a]).unwrap()[0];\n        model.add_edge(neg, InletId::new(add.node, 1)).unwrap();\n        model.select_output_outlets(&[neg]).unwrap();\n        let cloned = model.clone();\n        let (rx, tx) = std::sync::mpsc::channel();\n        std::thread::spawn(move || {\n            rx.send(cloned.eval_order()).unwrap();\n        });\n        assert!(tx.recv_timeout(std::time::Duration::from_secs(1)).unwrap().is_err());\n        let (rx, tx) = std::sync::mpsc::channel();\n        std::thread::spawn(move || {\n            rx.send(model.eval_order_opt_ram()).unwrap();\n        });\n        assert!(tx.recv_timeout(std::time::Duration::from_secs(1)).unwrap().is_err());\n    }\n\n    #[test]\n    fn opt_ram() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let b = model.add_const(\"b\", tensor1(&[0i64; 1000]))?;\n        let d = model.add_const(\"d\", tensor1(&[0i64; 100]))?;\n        let a = model.add_source(\"a\", i32::fact([10]))?;\n        let c = model.wire_node(\"c\", Gather::new(0), &[a, b])?[0];\n        let e = model.wire_node(\"e\", Gather::new(0), &[c, d])?[0];\n        model.select_output_outlets(&[e]).unwrap();\n        eprintln!(\"{model}\");\n        assert!(model.eval_order_opt_ram()?[2..] == [c.node, d.node, e.node]);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/model/patch.rs",
    "content": "use std::collections::HashSet;\nuse std::fmt::{Debug, Display};\nuse std::ops::{Deref, DerefMut};\n\nuse tract_data::itertools::{Itertools, izip};\n\nuse crate::internal::*;\nuse crate::model::*;\n\n/// A change to apply to a model.\n///\n/// Actually structured around a model that represent the new nodes to be\n/// inserted, plus information about how to connect these new nodes to the\n/// pre-existing graph.\n#[derive(Clone, Debug)]\npub struct ModelPatch<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Display + Debug + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    /// patch label for auditing and debugging\n    pub context: Vec<String>,\n    /// optimizer will ignore this patch in node to node loop if it was already\n    /// encountered\n    pub dont_apply_twice: Option<String>,\n    /// the model-like 'patch' of nodes to add to the model\n    pub model: Graph<F, O>,\n    /// map of replaced inputs (patch node id to model node id)\n    pub inputs: HashMap<usize, usize>,\n    /// map of patch inputs to model wires\n    pub taps: HashMap<OutletId, OutletId>,\n    /// map of old model wires to be replaced by wires from the patch\n    pub shunts: HashMap<OutletId, OutletId>,\n    /// operations to discard from the model\n    pub obliterate: Vec<usize>,\n}\n\nimpl<F, O> Default for ModelPatch<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Display + Debug + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    fn default() -> ModelPatch<F, O> {\n        ModelPatch {\n            context: vec![],\n            dont_apply_twice: None,\n            model: Graph::default(),\n            inputs: HashMap::default(),\n            taps: HashMap::new(),\n            shunts: HashMap::new(),\n            obliterate: vec![],\n        }\n    }\n}\n\nimpl<F, O> Deref for ModelPatch<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Display + Debug + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    type Target = Graph<F, O>;\n    fn deref(&self) -> &Graph<F, O> {\n        &self.model\n    }\n}\n\nimpl<F, O> DerefMut for ModelPatch<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Display + Debug + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    fn deref_mut(&mut self) -> &mut Graph<F, O> {\n        &mut self.model\n    }\n}\n\nimpl<F, O> ModelPatch<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Display + Debug + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n    Graph<F, O>: SpecialOps<F, O>,\n{\n    pub fn new(s: impl Into<String>) -> Self {\n        Self::default().with_context(s)\n    }\n\n    pub fn push_context(&mut self, s: impl Into<String>) {\n        self.context.push(s.into());\n    }\n\n    pub fn with_context(mut self, s: impl Into<String>) -> Self {\n        self.context.push(s.into());\n        self\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.model.nodes.is_empty() && self.shunts.is_empty() && self.obliterate.is_empty()\n    }\n\n    /// Draw a tap from a preexisting node.\n    ///\n    /// returns an OutletId usable in the little \"patch\" model\n    pub fn tap_model(&mut self, model: &Graph<F, O>, outlet: OutletId) -> TractResult<OutletId> {\n        let fact = model.outlet_fact(outlet)?;\n        let id = self.add_source(\n            format!(\"tap.{}-{}/{}\", model.node(outlet.node).name, outlet.node, outlet.slot),\n            dyn_clone::clone(fact),\n        )?;\n        self.taps.insert(id, outlet);\n        Ok(id)\n    }\n\n    /// Draw taps from a preexisting node.\n    ///\n    /// returns an OutletId usable in the little \"patch\" model\n    pub fn taps<'a>(\n        &mut self,\n        model: &Graph<F, O>,\n        outlets: impl IntoIterator<Item = &'a OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        outlets.into_iter().map(|o| self.tap_model(model, *o)).collect::<TractResult<TVec<_>>>()\n    }\n\n    pub unsafe fn shunt_outside_unchecked(\n        &mut self,\n        outlet: OutletId,\n        by: OutletId,\n    ) -> TractResult<()> {\n        self.shunts.insert(outlet, by);\n        Ok(())\n    }\n\n    /// Replace an Outlet in the target model by one from the patch.\n    pub fn shunt_outside(\n        &mut self,\n        model: &Graph<F, O>,\n        outlet: OutletId,\n        by: OutletId,\n    ) -> TractResult<()> {\n        let original_fact = model.outlet_fact(outlet)?;\n        let new_fact = self.model.outlet_fact(by)?;\n        if !original_fact.compatible_with(new_fact) {\n            bail!(\n                \"Trying to substitute a {:?} by {:?} as output #{} of {}.\\n{:?}\",\n                original_fact,\n                new_fact,\n                outlet.slot,\n                model.node(outlet.node),\n                self\n            );\n        }\n        self.shunts.insert(outlet, by);\n        Ok(())\n    }\n\n    pub fn obliterate(&mut self, node: usize) -> TractResult<()> {\n        self.obliterate.push(node);\n        Ok(())\n    }\n\n    /// Convenience method creating a patch that replaces a single operation.\n    pub fn replace_single_op<IO: Into<O>>(\n        patched_model: &Graph<F, O>,\n        node: &Node<F, O>,\n        inputs: &[OutletId],\n        new_op: IO,\n    ) -> TractResult<ModelPatch<F, O>> {\n        let mut patch = ModelPatch::default();\n        let new_op = new_op.into();\n        let inputs = patch.taps(patched_model, inputs)?;\n        let wires = patch.wire_node(&node.name, new_op, &inputs)?;\n        for (ix, o) in wires.iter().enumerate() {\n            patch.shunt_outside(patched_model, OutletId::new(node.id, ix), *o)?;\n        }\n        patch.obliterate(node.id)?;\n        Ok(patch)\n    }\n\n    /// Convenience method creating a patch that fuses an op with the next one.\n    pub fn fuse_with_next<IO: Into<O>>(\n        patched_model: &Graph<F, O>,\n        node: &Node<F, O>,\n        new_op: IO,\n    ) -> TractResult<ModelPatch<F, O>> {\n        let mut patch = ModelPatch::default();\n        let succ = if let Some(succ) = patched_model.single_succ(node.id)? {\n            succ\n        } else {\n            bail!(\"Non single successor fuse attempt\")\n        };\n        let inputs = patch.taps(patched_model, &node.inputs)?;\n        let output = patch.wire_node(&node.name, new_op.into(), &inputs)?;\n        patch.shunt_outside(patched_model, succ.id.into(), output[0])?;\n        Ok(patch)\n    }\n\n    /// Convenience method creating a patch that shunts the given node.\n    pub fn shunt_one_op(\n        patched_model: &Graph<F, O>,\n        node: &Node<F, O>,\n    ) -> TractResult<Option<ModelPatch<F, O>>> {\n        ensure!(node.inputs.len() == 1);\n        ensure!(node.outputs.len() == 1);\n        if patched_model.outputs.contains(&node.id.into())\n            && patched_model.outputs.contains(&node.inputs[0])\n        {\n            Ok(None)\n        } else {\n            Self::rewire(patched_model, &node.inputs, &[node.id.into()], &|_p, xs| Ok(xs.into()))\n                .with_context(|| format!(\"Shunting {node}\"))\n                .map(Some)\n        }\n    }\n\n    #[allow(clippy::type_complexity)]\n    pub fn rewire(\n        patched_model: &Graph<F, O>,\n        from: &[OutletId],\n        to: &[OutletId],\n        wiring: &dyn Fn(&mut Self, &[OutletId]) -> TractResult<TVec<OutletId>>,\n    ) -> TractResult<ModelPatch<F, O>> {\n        let mut patch = ModelPatch::default();\n        let taps = patch.taps(patched_model, from)?;\n        let news = wiring(&mut patch, &taps)?;\n        if news.len() != to.len() {\n            bail!(\n                \"Wrong number of outputs for rewiring, expected {}, function returned {}\",\n                to.len(),\n                news.len()\n            );\n        }\n        for (new, &old) in izip!(news, to) {\n            patch.shunt_outside(patched_model, old, new)?;\n        }\n        Ok(patch)\n    }\n\n    /// Convenience method creating a patch that replace a single unary operation.\n    pub fn single_unary_op<IO: Into<O>>(\n        patched_model: &Graph<F, O>,\n        node: &Node<F, O>,\n        new_op: IO,\n    ) -> TractResult<ModelPatch<F, O>> {\n        Self::replace_single_op(patched_model, node, &[node.inputs[0]], new_op)\n    }\n\n    /// Convenience method creating a patch that insert an unary op on an outlet.\n    pub fn intercept<IO: Into<O>>(\n        patched_model: &Graph<F, O>,\n        outlet: OutletId,\n        name: impl Into<String>,\n        new_op: IO,\n        fact: F,\n    ) -> TractResult<ModelPatch<F, O>> {\n        let mut patch = ModelPatch::default();\n        let tap = patch.tap_model(patched_model, outlet)?;\n        let new_id = patch.add_node(name, new_op, tvec!(fact))?;\n        patch.add_edge(tap, InletId::new(new_id, 0))?;\n        patch.shunt_outside(patched_model, outlet, OutletId::new(new_id, 0))?;\n        Ok(patch)\n    }\n\n    pub fn wire_node(\n        &mut self,\n        name: impl Into<String>,\n        op: impl Into<O>,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let mut name = name.into();\n        if self.nodes.iter().any(|n| n.name == *name) {\n            for i in 1.. {\n                let s = format!(\"{name}#{i}\");\n                if self.nodes.iter().all(|n| n.name != s) {\n                    name = s;\n                    break;\n                }\n            }\n        }\n        self.model.wire_node(name, op.into(), inputs)\n    }\n\n    /// Apply all changes in the patch to the target model.\n    pub fn apply(self, target: &mut Graph<F, O>) -> TractResult<()> {\n        let prior_target_inputs = target.input_outlets()?.len();\n        let prior_target_outputs = target.output_outlets()?.len();\n        let ModelPatch {\n            model: patch,\n            taps: mut mapping,\n            shunts: shunt_outlet_by,\n            obliterate,\n            inputs: replaced_inputs,\n            ..\n        } = self;\n        let mut all_inputs = HashMap::new(); // new_node_id_in_model -> [ patch_outlet_id ]\n        let mut model_input_outlets = target.input_outlets()?.to_vec();\n        let mut new_nodes = HashSet::new();\n        for node in patch.nodes {\n            if <Graph<F, O>>::is_source(&node.op)\n                && mapping.contains_key(&OutletId::new(node.id, 0))\n            {\n                // this is a tap\n                continue;\n            }\n            let Node { id: patch_node_id, name, inputs, op, outputs } = node;\n            let n_outputs = outputs.len();\n            for dup in 0..target.nodes.len() {\n                if target.node(dup).op().dyn_eq(op.as_ref())\n                    && inputs.len() == target.node(dup).inputs.len()\n                    && inputs\n                        .iter()\n                        .zip(target.node(dup).inputs.iter())\n                        .all(|(patch_input, d)| mapping[patch_input] == *d)\n                {\n                    for ix in 0..n_outputs {\n                        mapping.insert(OutletId::new(patch_node_id, ix), OutletId::new(dup, ix));\n                    }\n                    continue;\n                }\n            }\n            let facts = outputs.into_iter().map(|of| of.fact).collect();\n            let added_node_id = target.add_node(name, op, facts)?;\n            new_nodes.insert(added_node_id);\n            for ix in 0..n_outputs {\n                mapping.insert(OutletId::new(patch_node_id, ix), OutletId::new(added_node_id, ix));\n            }\n            all_inputs.insert(added_node_id, inputs);\n            if <Graph<F, O>>::is_source(&target.node(added_node_id).op) {\n                // this is actually an input replacement\n                model_input_outlets.iter_mut().for_each(|oo| {\n                    if oo.node == replaced_inputs[&patch_node_id] {\n                        oo.node = added_node_id;\n                    }\n                });\n            }\n        }\n        debug_assert_eq!(target.input_outlets()?.len(), prior_target_inputs);\n        debug_assert_eq!(target.output_outlets()?.len(), prior_target_outputs);\n        for (&outlet, &by) in shunt_outlet_by.iter().sorted() {\n            let replace_by = mapping[&by];\n            let succs = target.nodes()[outlet.node].outputs[outlet.slot].successors.clone();\n            for succ in succs {\n                target.add_edge(replace_by, succ)?;\n            }\n            for o in target.outputs.iter_mut() {\n                if *o == outlet {\n                    *o = replace_by;\n                }\n            }\n            if let Some(label) = target.outlet_labels.remove(&outlet) {\n                target.set_outlet_label(replace_by, label)?;\n            }\n        }\n        debug_assert_eq!(target.input_outlets()?.len(), prior_target_inputs);\n        debug_assert_eq!(target.output_outlets()?.len(), prior_target_outputs);\n        for (&node, inputs) in all_inputs.iter().sorted() {\n            for (ix, input) in inputs.iter().enumerate() {\n                target.add_edge(mapping[input], InletId::new(node, ix))?;\n            }\n        }\n        debug_assert_eq!(target.input_outlets()?.len(), prior_target_inputs);\n        debug_assert_eq!(target.output_outlets()?.len(), prior_target_outputs);\n        for node in obliterate {\n            target.node_mut(node).op = target.create_dummy();\n        }\n        debug_assert_eq!(target.input_outlets()?.len(), prior_target_inputs);\n        debug_assert_eq!(target.output_outlets()?.len(), prior_target_outputs);\n        target.set_input_outlets(&model_input_outlets)?;\n        let mut maybe_garbage: HashSet<usize> = shunt_outlet_by.iter().map(|o| o.0.node).collect();\n        while let Some(&maybe) = maybe_garbage.iter().next() {\n            maybe_garbage.remove(&maybe);\n            if !target.outputs.iter().any(|output| output.node == maybe)\n                && !target.inputs.iter().any(|input| input.node == maybe)\n                && target.node(maybe).outputs.iter().all(|of| of.successors.is_empty())\n            {\n                target.node_mut(maybe).op = target.create_dummy();\n                target.node_mut(maybe).name = format!(\"Dummy-node-{maybe}\");\n                target.node_mut(maybe).outputs.clear(); // necessary to drop facts and consts\n                let inputs = std::mem::take(&mut target.node_mut(maybe).inputs);\n                for &i in &inputs {\n                    target.node_mut(i.node).outputs[i.slot].successors.retain(|s| s.node != maybe);\n                    maybe_garbage.insert(i.node);\n                }\n                target.check_edges()?;\n            }\n        }\n        for n in new_nodes.iter() {\n            if let Some((prefix, _)) = target.nodes[*n].name.split_once('#') {\n                target.nodes[*n].name = target.unique_name(prefix).into();\n            } else if target\n                .nodes\n                .iter()\n                .any(|node| node.id != *n && target.nodes[*n].name == node.name)\n            {\n                target.nodes[*n].name = target.unique_name(&target.nodes[*n].name).to_string();\n            }\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/model/rewriter.rs",
    "content": "use std::any::TypeId;\n\nuse crate::internal::*;\n\ntype GenRewriteRule<Ctx> =\n    Box<dyn Fn(&Ctx, &TypedModel, &TypedNode) -> TractResult<Option<TypedModelPatch>>>;\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\npub struct Rewriter<Ctx> {\n    rules: HashMap<TypeId, Vec<(Cow<'static, str>, GenRewriteRule<Ctx>)>>,\n}\n\nimpl<Ctx> Rewriter<Ctx> {\n    pub fn with_rule_for<O: Op + 'static>(\n        mut self,\n        name: impl Into<Cow<'static, str>>,\n        rule: impl Fn(&Ctx, &TypedModel, &TypedNode, &str, &O) -> TractResult<Option<TypedModelPatch>>\n        + 'static,\n    ) -> Self {\n        self.rules.entry(TypeId::of::<O>()).or_default().push((\n            name.into(),\n            Box::new(move |c: &Ctx, m: &TypedModel, n: &TypedNode| {\n                if let Some(o) = n.op_as::<O>() { rule(c, m, n, &n.name, o) } else { Ok(None) }\n            }),\n        ));\n        self\n    }\n\n    pub fn rewrite(&self, ctx: &Ctx, model: &mut TypedModel) -> TractResult<()> {\n        loop {\n            let mut done_anything = false;\n            for n in model.eval_order()? {\n                if let Some(rules) = self.rules.get(&(*model.node(n).op).type_id()) {\n                    for (name, rule) in rules {\n                        if let Some(patch) =\n                            (rule)(ctx, model, model.node(n)).with_context(|| {\n                                format!(\n                                    \"Evaluating rewriting rule \\\"{name}\\\" on node {}\",\n                                    model.node(n)\n                                )\n                            })?\n                        {\n                            patch.apply(model).with_context(|| {\n                                format!(\n                                    \"Applying patch for rewriting rule \\\"{name}\\\" on node {}\",\n                                    model.node(n)\n                                )\n                            })?;\n                            done_anything = true;\n                        }\n                    }\n                }\n            }\n            if done_anything {\n                model.prop_consts()?;\n                model.compact()?;\n            } else {\n                return Ok(());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/model/translator.rs",
    "content": "use crate::internal::*;\nuse crate::model::{Fact, Graph, OutletId};\nuse std::collections::HashMap;\nuse std::convert::*;\nuse std::fmt;\n\npub trait Translate<TI1, O1, TI2, O2>: fmt::Debug\nwhere\n    TI1: Fact + Clone + 'static,\n    TI2: Fact + Clone + 'static,\n    O1: fmt::Display + fmt::Debug + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n    O2: fmt::Display + fmt::Debug + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    fn translate_node(\n        &self,\n        source: &Graph<TI1, O1>,\n        node: &Node<TI1, O1>,\n        target: &mut Graph<TI2, O2>,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>>;\n\n    fn translate_model(&self, source: &Graph<TI1, O1>) -> TractResult<Graph<TI2, O2>> {\n        Ok(self.translate_model_with_mappings(source)?.0)\n    }\n\n    fn translate_model_with_mappings(\n        &self,\n        source: &Graph<TI1, O1>,\n    ) -> TractResult<(Graph<TI2, O2>, HashMap<OutletId, OutletId>)> {\n        let _proof_session = source.symbols.proof_cache_session();\n        let mut target = Graph { symbols: source.symbols.clone(), ..Graph::default() };\n        let mut mapping = HashMap::new();\n        for old_id in source.eval_order()? {\n            let node = source.node(old_id);\n            let outlets = self\n                .translate_node(source, node, &mut target, &mapping)\n                .with_context(|| format!(\"Translating node {node} {self:?}\"))?;\n            for (ix, outlet) in outlets.into_iter().enumerate() {\n                mapping.insert(OutletId::new(node.id, ix), outlet);\n                if let Some(label) = source.outlet_label(OutletId::new(node.id, ix)) {\n                    target.set_outlet_label(outlet, label.to_string())?;\n                }\n            }\n        }\n        // do not drop inputs, even if they are useless, to maintain interface\n        for i in source.input_outlets()? {\n            if !mapping.contains_key(i) {\n                let node = source.node(i.node);\n                trace!(\"Translate useless source {node}\");\n                let outlets = self\n                    .translate_node(source, node, &mut target, &mapping)\n                    .with_context(|| format!(\"Translating input {node} {self:?}\"))?;\n                mapping.insert(*i, outlets[0]);\n            }\n        }\n        // maintaining order of i/o interface\n        target.inputs = source.input_outlets()?.iter().map(|i| mapping[i]).collect();\n        target.outputs = source.output_outlets()?.iter().map(|o| mapping[o]).collect();\n        target.properties.clone_from(&source.properties);\n        Ok((target, mapping))\n    }\n}\n\n#[derive(Debug)]\npub struct IntoTranslator;\nimpl<TI1, O1, TI2, O2, EO, ETI> Translate<TI1, O1, TI2, O2> for IntoTranslator\nwhere\n    TractError: From<EO> + From<ETI>,\n    TI1: Fact + Clone + 'static,\n    TI2: Fact + for<'a> TryFrom<&'a TI1, Error = EO> + Clone + 'static,\n    O1: fmt::Display + fmt::Debug + Clone + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n    O2: fmt::Display\n        + for<'a> TryFrom<&'a O1, Error = ETI>\n        + fmt::Debug\n        + AsRef<dyn Op>\n        + AsMut<dyn Op>\n        + Clone\n        + 'static,\n    Graph<TI2, O2>: SpecialOps<TI2, O2>,\n{\n    fn translate_node(\n        &self,\n        source: &Graph<TI1, O1>,\n        node: &Node<TI1, O1>,\n        target: &mut Graph<TI2, O2>,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        let node_is_input =\n            (0..node.outputs.len()).all(|o| source.inputs.contains(&(node.id, o).into()));\n        if node_is_input {\n            (0..node.outputs.len())\n                .map(|i| {\n                    target.add_source(\n                        if node.outputs.len() > 1 {\n                            format!(\"{}-{}\", node.name, i)\n                        } else {\n                            node.name.to_string()\n                        },\n                        TI2::try_from(&node.outputs[i].fact)?,\n                    )\n                })\n                .collect()\n        } else {\n            let new_op = O2::try_from(&node.op)?;\n            let facts = node\n                .outputs\n                .iter()\n                .map(|of| Ok(TI2::try_from(&of.fact)?))\n                .collect::<TractResult<TVec<_>>>()?;\n            let new_id = target.add_node(node.name.clone(), new_op, facts)?;\n            for (ix, o) in node.inputs.iter().enumerate() {\n                target.add_edge(mapping[o], InletId::new(new_id, ix))?\n            }\n            Ok(node.outputs.iter().enumerate().map(|(ix, _)| OutletId::new(new_id, ix)).collect())\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/model/typed.rs",
    "content": "use crate::internal::*;\nuse crate::model::*;\nuse crate::ops;\nuse crate::ops::konst::Const;\nuse crate::optim::OptimizerSession;\nuse crate::plan::{FrozenSimpleState, SimplePlan, SimpleState};\nuse crate::transform::ModelTransform;\nuse tract_data::TooEarly;\nuse tract_num_traits::Zero;\n\n/// A model with completely determined types and shapes.\npub type TypedModel = Graph<TypedFact, Box<dyn TypedOp>>;\n/// Node for TypedModel graph\npub type TypedNode = Node<TypedFact, Box<dyn TypedOp>>;\n/// A ModelPatch for TypedModel.\npub type TypedModelPatch = ModelPatch<TypedFact, Box<dyn TypedOp>>;\n/// An execution plan for TypedModel.\npub type TypedSimplePlan = SimplePlan<TypedFact, Box<dyn TypedOp>>;\n/// A runnable TypedModel (new name for SimplePlan).\npub type TypedRunnableModel = RunnableModel<TypedFact, Box<dyn TypedOp>>;\n/// An execution state for TypedModel.\npub type TypedSimpleState = SimpleState<TypedFact, Box<dyn TypedOp>>;\n/// An execution state for TypedModel, frozen (and Send).\npub type TypedFrozenSimpleState = FrozenSimpleState<TypedFact, Box<dyn TypedOp>>;\n\n/// A runnable model with fixed inputs and outputs.\npub type RunnableModel<F, O> = SimplePlan<F, O>;\n\nimpl SpecialOps<TypedFact, Box<dyn TypedOp>> for TypedModel {\n    fn is_source(op: &Box<dyn TypedOp>) -> bool {\n        op.as_op().downcast_ref::<ops::source::TypedSource>().is_some()\n    }\n\n    fn create_dummy(&self) -> Box<dyn TypedOp> {\n        Box::new(crate::ops::dummy::Dummy::new())\n    }\n\n    fn create_source(&self, fact: TypedFact) -> Box<dyn TypedOp> {\n        Box::new(crate::ops::source::TypedSource::new(fact))\n    }\n\n    fn wire_node(\n        &mut self,\n        name: impl Into<String>,\n        op: impl Into<Box<dyn TypedOp>>,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let op = op.into();\n        let name = name.into();\n        if let Some(konst) = op.downcast_ref::<Const>() {\n            for node in &self.nodes {\n                if node.op_as::<Const>().is_some_and(|other| other == konst) {\n                    return Ok(tvec!(node.id.into()));\n                }\n            }\n        }\n        if self.nodes.iter().any(|n| n.name == name) {\n            bail!(\"Duplicate node name: {name}\");\n        }\n        {\n            let input_facts = inputs\n                .iter()\n                .map(|o| self.outlet_fact(*o).cloned())\n                .collect::<TractResult<TVec<_>>>()?;\n\n            let input_facts: TVec<_> = input_facts.iter().collect();\n            let mut output_facts = op\n                .output_facts(&input_facts)\n                .with_context(|| format!(\"in output_facts invocation for {name}: {}\", op.name()))?;\n\n            #[cfg(all(debug_assertions, feature = \"paranoid_assertions\"))]\n            for o in &output_facts {\n                o.consistent()?;\n            }\n\n            if op.is_stateless()\n                && input_facts.len() > 0\n                && let Some(tensors) = input_facts\n                    .iter()\n                    .map(|f| {\n                        f.konst\n                            .as_ref()\n                            .filter(|k| k.volume() < 16 && k.is_plain())\n                            .cloned()\n                            .map(|t| t.into_tvalue())\n                    })\n                    .collect::<Option<TVec<_>>>()\n                && let Ok(outputs) =\n                    op.eval_with_session(usize::MAX, &TurnState::default(), tensors)\n            {\n                return outputs\n                    .into_iter()\n                    .enumerate()\n                    .map(|(ix, o)| {\n                        let name = if ix == 0 { name.clone() } else { format!(\"{name}.{ix}\") };\n                        self.wire_node(\n                            name.clone(),\n                            Const::new_with_opt_exotic_fact(\n                                o.into_tensor().into(),\n                                output_facts[ix].exotic_fact.clone(),\n                            )?,\n                            &[],\n                        )\n                        .with_context(|| format!(\"Eager const-folding {name}\"))\n                        .map(|vec| vec[0])\n                    })\n                    .collect::<TractResult<TVec<OutletId>>>();\n            }\n\n            for fact in &mut output_facts {\n                if fact.konst.is_none()\n                    && fact.exotic_fact.is_none()\n                    && fact.shape.is_concrete()\n                    && fact.shape.volume().is_zero()\n                {\n                    let tensor =\n                        Tensor::zero_dt(fact.datum_type, fact.shape.as_concrete().unwrap())?;\n                    fact.konst = Some(tensor.into_arc_tensor());\n                }\n            }\n            let id = self.add_node(&name, &op, output_facts)?;\n            inputs\n                .iter()\n                .enumerate()\n                .try_for_each(|(ix, i)| self.add_edge(*i, InletId::new(id, ix)))?;\n            TractResult::Ok(\n                self.node(id)\n                    .outputs\n                    .iter()\n                    .enumerate()\n                    .map(|(ix, _)| OutletId::new(id, ix))\n                    .collect(),\n            )\n        }\n        .with_context(|| format!(\"Wiring node \\\"{name}\\\", {op:?}\"))\n    }\n\n    fn add_const(\n        &mut self,\n        name: impl Into<String>,\n        v: impl IntoArcTensor,\n    ) -> TractResult<OutletId> {\n        let v = v.into_arc_tensor();\n        for node in &self.nodes {\n            if node.op_is::<Const>() && node.outputs[0].fact.konst.as_ref() == Some(&v) {\n                return Ok(node.id.into());\n            }\n        }\n        let fact = TypedFact::try_from(v.clone())?;\n        let name = name.into();\n        let op = if let Some(exotic) = &fact.exotic_fact {\n            crate::ops::konst::Const::new_with_exotic_fact(v, exotic.clone())?\n        } else {\n            crate::ops::konst::Const::new(v)?\n        };\n        self.add_node(name, op, tvec!(fact)).map(|id| id.into())\n    }\n}\n\nimpl TypedModel {\n    pub fn into_optimized(mut self) -> TractResult<TypedModel> {\n        self.declutter()?;\n        self.optimize()?;\n        Ok(self)\n    }\n    #[cfg(not(all(debug_assertions, feature = \"paranoid_assertions\")))]\n    #[inline]\n    pub fn check_consistency(&self) -> TractResult<()> {\n        Ok(())\n    }\n\n    #[cfg(all(debug_assertions, feature = \"paranoid_assertions\"))]\n    pub fn check_consistency(&self) -> TractResult<()> {\n        self.check_edges()?;\n        for node_id in &self.eval_order()? {\n            let input_facts = self.node_input_facts(*node_id)?;\n            let node = &self.nodes[*node_id];\n            if node.id != *node_id {\n                bail!(\"Node at position {} has id {}\", node_id, node.id);\n            }\n            let output_facts = node.op.output_facts(&input_facts)?;\n            if node.outputs.len() != output_facts.len() {\n                bail!(\n                    \"Inconsistent model, node output count mismatch. Op says {}, node says {}. {}\",\n                    output_facts.len(),\n                    node.outputs.len(),\n                    node\n                );\n            }\n            if node\n                .outputs\n                .iter()\n                .map(|o| &o.fact)\n                .zip(output_facts.iter())\n                .any(|(a, b)| a.datum_type != b.datum_type || a.shape != b.shape)\n            {\n                bail!(\n                    \"Inconsistent model, output types mismatch. Op says: {:?}, node says: {:?}. {} with inputs {:?}. {}\",\n                    output_facts,\n                    node.outputs.iter().map(|o| &o.fact).collect::<Vec<_>>(),\n                    node,\n                    input_facts,\n                    node\n                )\n            }\n        }\n        for node in &self.nodes {\n            for (ix, output) in node.outputs.iter().enumerate() {\n                output.fact.consistent().with_context(|| {\n                    format!(\"Inconsistent fact {:?}: {:?}\", OutletId::new(node.id, ix), output.fact)\n                })?\n            }\n        }\n        self.axes_mapping().context(\"Checking model axes mapping\")?;\n        Ok(())\n    }\n\n    /// Re-run `output_facts` for every node in topological order, updating stored facts in-place.\n    ///\n    /// Call this after operations that rewire graph topology (e.g. `TypedModelPatch::apply`)\n    /// without rebuilding the model, so that derived fact fields like `uniform_tdim` reflect\n    /// the new input facts.\n    pub fn refresh_output_facts(&mut self) -> TractResult<()> {\n        let order = self.eval_order()?;\n        for node_id in order {\n            let output_facts = {\n                let input_facts = self.node_input_facts(node_id)?;\n                self.nodes[node_id].op.output_facts(&input_facts)?\n            };\n            for (ix, fact) in output_facts.into_iter().enumerate() {\n                self.set_outlet_fact(OutletId::new(node_id, ix), fact)?;\n            }\n        }\n        Ok(())\n    }\n\n    pub fn into_decluttered(mut self) -> TractResult<TypedModel> {\n        self.declutter()?;\n        Ok(self)\n    }\n\n    /// Perform declutter passes on the network.\n    pub fn transform(&mut self, transform: &dyn ModelTransform) -> TractResult<()> {\n        transform.transform(self)\n    }\n\n    /// Perform declutter passes on the network.\n    pub fn declutter(&mut self) -> TractResult<()> {\n        crate::optim::Optimizer::declutter().session().optimize(self)\n    }\n\n    /// Perform optimization passes on the model, using a given optimizer session.\n    pub fn optimize_with_session(&mut self, session: &mut OptimizerSession) -> TractResult<()> {\n        session.optimize(self)?;\n        self.properties.insert(\"tract_stage\".to_string(), rctensor0(\"optimized\".to_string()));\n        Ok(())\n    }\n\n    pub fn concretize_dims(&self, values: &SymbolValues) -> TractResult<TypedModel> {\n        values.translate_model(self)\n    }\n\n    pub fn prop_consts(&mut self) -> TractResult<()> {\n        crate::optim::Optimizer::prop_consts().optimize(self)\n    }\n\n    /// Translate the graph to locally optimized operators (LIR or MIR ops).\n    pub fn optimize(&mut self) -> TractResult<()> {\n        crate::optim::Optimizer::codegen().optimize(self)\n    }\n\n    pub fn node_axes_mapping(&self, id: usize) -> TractResult<AxesMapping> {\n        let (inputs, outputs) = self.node_facts(id)?;\n        self.nodes[id].op.axes_mapping(&inputs, &outputs)\n    }\n\n    pub fn axes_mapping(&self) -> TractResult<AxesMapping> {\n        crate::axes::for_model(self)\n    }\n\n    pub fn compute_const_facts(&mut self) -> TractResult<()> {\n        for n in self.eval_order()? {\n            let node = self.node(n);\n            let (inputs, outputs) = self.node_facts(n)?;\n            if node.op.is_stateless()\n                && inputs.iter().all(|i| i.konst.as_ref().is_some_and(|k| k.is_plain()))\n                && outputs.iter().any(|o| o.konst.is_none())\n            {\n                let inputs_ref =\n                    inputs.iter().map(|f| f.konst.clone().unwrap().into_tvalue()).collect();\n                match node.op.eval_with_session(node.id, &TurnState::default(), inputs_ref) {\n                    Ok(res) => {\n                        drop(inputs);\n                        drop(outputs);\n                        for (ix, output) in res.into_iter().enumerate() {\n                            self.nodes[n].outputs[ix].fact.konst = Some(output.into_arc_tensor());\n                        }\n                    }\n                    Err(e) => {\n                        if !e.root_cause().is::<TooEarly>() {\n                            Err(e).with_context(|| {\n                                format!(\"Eager eval {} during const fact computation\", self.node(n))\n                            })?;\n                        }\n                    }\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\nuse crate::model::translator::Translate;\nimpl Translate<TypedFact, Box<dyn TypedOp>, TypedFact, Box<dyn TypedOp>> for SymbolValues {\n    fn translate_node(\n        &self,\n        source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        target.check_consistency()?;\n        let outlets = node.op.concretize_dims(source, node, target, mapping, self)?;\n        for &outlet in &outlets {\n            let fact = &mut target.nodes[outlet.node].outputs[outlet.slot].fact;\n            if fact.shape.volume().is_zero()\n                && let Some(shape) = fact.shape.as_concrete()\n            {\n                let tensor = Tensor::zero_dt(fact.datum_type, shape)?;\n                fact.konst = Some(tensor.into_arc_tensor());\n            }\n            fact.consistent()?;\n        }\n        Ok(outlets)\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test() {\n        fn is_sync<T: Sync>() {}\n        is_sync::<TypedModel>();\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/broadcast.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct MultiBroadcastTo {\n    pub shape: ShapeFact,\n}\n\nimpl Op for MultiBroadcastTo {\n    fn name(&self) -> StaticName {\n        \"MultiBroadcastTo\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for MultiBroadcastTo {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let shape = self.shape.eval_to_usize(&session.resolved_symbols)?;\n        Ok(tvec!(inputs[0].broadcast_to_shape(&shape)?.into_tvalue()))\n    }\n}\n\nimpl TypedOp for MultiBroadcastTo {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() == 1);\n        let mut fact = inputs[0].datum_type.fact(self.shape.clone());\n        fact.uniform.clone_from(&inputs[0].uniform);\n        fact.uniform_tdim = inputs[0].uniform_tdim.clone();\n        Ok(tvec!(fact))\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let input = mapping[&node.inputs[0]];\n        let op =\n            Self { shape: self.shape.iter().map(|d| d.eval(values)).collect::<TVec<_>>().into() };\n        target.wire_node(&node.name, op, &[input])\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let input_fact = model.outlet_fact(node.inputs[0])?;\n        if input_fact.shape == self.shape {\n            TypedModelPatch::shunt_one_op(model, node)\n        } else {\n            Ok(None)\n        }\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/array/concat.rs",
    "content": "use tract_data::itertools::Itertools;\nuse tract_num_traits::Zero;\n\nuse crate::internal::*;\n\nuse super::Slice;\n\n#[derive(new, Debug, Clone, Hash, PartialEq, Eq)]\npub struct TypedConcat {\n    pub axis: usize,\n}\n\nimpl TypedConcat {\n    pub fn offsets(&self, inputs: &[&TypedFact]) -> TractResult<Vec<TDim>> {\n        let mut offsets = vec![0.to_dim()];\n        for slice in inputs {\n            let len = slice.shape[self.axis].clone();\n            let offset = len + offsets.last().unwrap();\n            offsets.push(offset)\n        }\n        Ok(offsets)\n    }\n}\n\nimpl Op for TypedConcat {\n    fn name(&self) -> StaticName {\n        \"Concat\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis: {}\", self.axis)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl TypedOp for TypedConcat {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() > 0);\n        let mut fact = inputs[0].without_value();\n        for input in inputs {\n            if input.rank() != fact.rank()\n                || input\n                    .shape\n                    .iter()\n                    .zip(fact.shape.iter())\n                    .enumerate()\n                    .filter(|(ax, _)| *ax != self.axis)\n                    .any(|(_, (i, f))| i != f)\n            {\n                bail!(\"Inconsistent concat {:?} inputs: {:?}\", self, inputs);\n            }\n        }\n        fact.shape.set(self.axis, self.offsets(inputs)?.pop().unwrap());\n        Ok(tvec!(fact))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let mut axes = AxesMapping::disconnected(inputs, outputs)?;\n        for ax in 0..outputs[0].rank() {\n            if ax != self.axis {\n                for i in 0..inputs.len() {\n                    axes = axes.linking((InOut::Out(0), ax), (InOut::In(i), ax))?;\n                }\n            }\n        }\n        Ok(axes)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        rule_if_some!(axis = change.transform_axis(self.axis));\n        let op = TypedConcat { axis };\n        Ok(Some(AxisChangeConsequence::new(model, node, Some(Box::new(op)), change)))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if node.inputs.len() == 1 {\n            return TypedModelPatch::shunt_one_op(model, node);\n        }\n        let inputs = model.node_input_facts(node.id)?;\n        if let Some(pos) = inputs.iter().position(|f| f.shape.volume().is_zero()) {\n            let mut inputs = node.inputs.clone();\n            inputs.remove(pos);\n            return Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &inputs,\n                self.clone(),\n            )?));\n        }\n        Ok(None)\n    }\n\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        _model: &TypedModel,\n        _node: &TypedNode,\n        prefix: &str,\n        inputs: &[OutletId],\n        output_axis: usize,\n        start: &TDim,\n        end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        if output_axis != self.axis {\n            return Ok(Some(patch.wire_node(prefix, self.clone(), inputs)?));\n        }\n        let facts =\n            inputs.iter().map(|o| patch.outlet_fact(*o)).collect::<TractResult<TVec<_>>>()?;\n        let offsets = self.offsets(&facts)?;\n        std::mem::drop(facts);\n        for (ix, (slice_start, slice_end)) in offsets.iter().tuple_windows().enumerate() {\n            if (start.clone() - slice_start).prove_positive_or_zero()\n                && (slice_end.clone() - end).prove_positive_or_zero()\n            {\n                return patch\n                    .wire_node(\n                        format!(\"{prefix}.slice-{output_axis}.{start}..{end}\"),\n                        Slice {\n                            axis: output_axis,\n                            start: (start.clone() - slice_start),\n                            end: (end.clone() - slice_start),\n                        },\n                        &[inputs[ix]],\n                    )\n                    .map(Some);\n            }\n        }\n        Ok(None)\n    }\n}\n\nimpl EvalOp for TypedConcat {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let result = Tensor::stack_tensors(self.axis, &inputs)?;\n        Ok(tvec![result.into_tvalue()])\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/dyn_slice.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, new)]\npub struct DynSlice {\n    pub axis: usize,\n    pub len: TDim,\n}\n\nimpl DynSlice {\n    pub fn suffix(&self) -> String {\n        format!(\"axis{}\", self.axis)\n    }\n}\n\nimpl Op for DynSlice {\n    fn name(&self) -> StaticName {\n        \"DynSlice\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis: {}\", self.axis)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DynSlice {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let start = inputs[1]\n            .cast_to::<TDim>()?\n            .try_as_plain()?\n            .to_scalar::<TDim>()?\n            .eval(&session.resolved_symbols)\n            .to_usize()?;\n        let end = inputs[2]\n            .cast_to::<TDim>()?\n            .try_as_plain()?\n            .to_scalar::<TDim>()?\n            .eval(&session.resolved_symbols)\n            .to_usize()?;\n        ensure!(start <= end);\n        if let Ok(len) = self.len.eval(&session.resolved_symbols).to_usize() {\n            ensure!(start + len == end);\n        }\n        let slice = inputs[0].slice(self.axis, start, end)?;\n        Ok(tvec!(slice.into()))\n    }\n}\n\nimpl TypedOp for DynSlice {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() == 3);\n        let mut fact = inputs[0].without_value();\n        fact.shape.set(self.axis, self.len.clone());\n        Ok(tvec!(fact))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        _outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural_for_rank(1, 1, inputs[0].rank())?\n            .with_extra_input(1)?\n            .with_extra_input(2)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        rule_if!(io != InOut::In(1) && io != InOut::In(2));\n        rule_if_some!(axis = change.transform_axis(self.axis));\n        if axis != self.axis {\n            Ok(Some(AxisChangeConsequence::new(\n                model,\n                node,\n                Some(Box::new(DynSlice { axis, ..self.clone() }) as _),\n                change,\n            )))\n        } else {\n            Ok(Some(AxisChangeConsequence::new(model, node, None, change)))\n        }\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let inputs = model.node_input_facts(node.id)?;\n        rule_if_some!(start = &inputs[1].konst);\n        rule_if_some!(end = &inputs[2].konst);\n        let start = start.cast_to::<TDim>()?.try_as_plain()?.to_scalar::<TDim>()?.clone();\n        let end = end.cast_to::<TDim>()?.try_as_plain()?.to_scalar::<TDim>()?.clone();\n\n        Ok(Some(TypedModelPatch::replace_single_op(\n            model,\n            node,\n            &[node.inputs[0]],\n            crate::ops::array::Slice { axis: self.axis, start, end },\n        )?))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/array/gather.rs",
    "content": "use crate::internal::*;\nuse crate::ops::einsum::block_quant_aware_input_shape;\nuse crate::ops::matmul::pack::OptSimpleMatMulPack;\nuse ndarray::*;\nuse tract_linalg::block_quant::BlockQuantStorage;\nuse tract_linalg::mmm::{MMMInputValue, PackedMatrixStorage};\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct Gather {\n    pub axis: usize,\n    pub output_type: Option<DatumType>,\n}\n\nimpl Op for Gather {\n    fn name(&self) -> StaticName {\n        \"Gather\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl Gather {\n    pub fn new(axis: usize) -> Gather {\n        Gather { axis, output_type: None }\n    }\n\n    pub fn compute_output_shape<D: DimLike>(\n        &self,\n        input_shape: &[D],\n        indices_shape: &[D],\n    ) -> TractResult<TVec<D>> {\n        ensure!(input_shape.len() > self.axis);\n        let mut output_shape: TVec<D> = input_shape[..self.axis].into();\n        output_shape.extend(indices_shape.iter().cloned());\n        output_shape.extend(input_shape[self.axis + 1..].iter().cloned());\n        Ok(output_shape)\n    }\n\n    fn eval_t<T: Datum>(&self, data: TValue, indices: &TValue) -> TractResult<Tensor> {\n        let data_plain = data.try_as_plain()?;\n        let data_view = unsafe { data_plain.to_array_view_unchecked::<T>() };\n        let indices = indices.to_plain_array_view::<i64>()?;\n        let output_shape = &*self.compute_output_shape(data.shape(), indices.shape())?;\n        let mut output = unsafe { Tensor::uninitialized::<T>(output_shape)? };\n        let mut output_plain = output.try_as_plain_mut()?;\n        let mut output_view = output_plain.to_array_view_mut::<T>()?;\n\n        let data_shape = data.shape();\n        let data_axis = self.axis;\n\n        let block_len = data_shape[data_axis + 1..].iter().product::<usize>();\n\n        let can_block_copy = data_shape[..data_axis].iter().all(|&d| d == 1)\n            && output_shape[..data_axis].iter().all(|&d| d == 1)\n            && data_view.is_standard_layout()\n            && output_view.is_standard_layout();\n\n        if can_block_copy {\n            let mut out_offset = 0;\n            let input_slice = data_view.as_slice().unwrap();\n            let output_slice = &mut output_view.as_slice_mut().unwrap();\n            for idx_coords in indices.indexed_iter() {\n                let index = *idx_coords.1;\n                let axis_len = data_shape[data_axis] as i64;\n                let resolved_index = if index < 0 { index + axis_len } else { index };\n                let resolved_index = resolved_index as usize;\n\n                let input_offset = resolved_index * block_len;\n\n                output_slice[out_offset..out_offset + block_len]\n                    .clone_from_slice(&input_slice[input_offset..input_offset + block_len]);\n                out_offset += block_len;\n            }\n        } else {\n            let ic_len = self.axis + 1 + output_shape.len() - (self.axis + indices.ndim());\n            let mut icoords = vec![0; ic_len];\n            let axis = self.axis;\n            for coords in tract_ndarray::indices(output_shape) {\n                let ocoords = coords.as_array_view();\n                let ocoords = ocoords.as_slice().unwrap();\n\n                let kcoords = &ocoords[self.axis..][..indices.ndim()];\n                let k = indices[kcoords];\n                let k = if k < 0 { k + data_view.shape()[self.axis] as i64 } else { k } as usize;\n                icoords[0..axis].copy_from_slice(&ocoords[..self.axis]);\n                icoords[self.axis] = k;\n                icoords[self.axis + 1..].clone_from_slice(&ocoords[self.axis + indices.ndim()..]);\n                output_view[ocoords] =\n                    data_view.get(&*icoords).cloned().context(\"Invalid gather\")?;\n            }\n            unsafe { output.set_datum_type(data.datum_type()) };\n        }\n        Ok(output)\n    }\n\n    fn eval_bq<F: Datum>(\n        &self,\n        data: &BlockQuantStorage,\n        m: usize,\n        k: usize,\n        indices: &TValue,\n    ) -> TractResult<Tensor> {\n        ensure!(self.axis == 0);\n        let data_shape = &[m, k];\n        let output_shape = &*self.compute_output_shape(data_shape, indices.shape())?;\n        let mut output = unsafe { Tensor::uninitialized::<F>(output_shape)? };\n        let indices_plain = indices.try_as_plain()?;\n        let indices_slice = indices_plain.as_slice::<i64>()?;\n        let vector_len = k;\n        let blob = data.value();\n\n        let block_len = data.format().block_len();\n        let block_bytes = data.format().block_bytes();\n        if F::datum_type() == f16::datum_type() {\n            let mut output_plain = output.try_as_plain_mut()?;\n            let output_slice = output_plain.as_slice_mut::<f16>()?;\n            for (pos, ix) in indices_slice.iter().enumerate() {\n                let slice = &mut output_slice[pos * vector_len..][..vector_len];\n                for i in (0..vector_len).step_by(block_len) {\n                    let offset = k * *ix as usize + i;\n                    let block_id = offset / block_len;\n                    data.format().dequant_block_f16(\n                        &blob[block_id * block_bytes..][..block_bytes],\n                        &mut slice[i..i + block_len],\n                    );\n                }\n            }\n        } else {\n            let mut output_plain = output.try_as_plain_mut()?;\n            let output_slice = output_plain.as_slice_mut::<f32>()?;\n            for (pos, ix) in indices_slice.iter().enumerate() {\n                let slice = &mut output_slice[pos * vector_len..][..vector_len];\n                for i in (0..vector_len).step_by(block_len) {\n                    let offset = k * *ix as usize + i;\n                    let block_id = offset / block_len;\n                    data.format().dequant_block_f32(\n                        &blob[block_id * block_bytes..][..block_bytes],\n                        &mut slice[i..i + block_len],\n                    );\n                }\n            }\n        }\n        Ok(output)\n    }\n\n    fn eval_input_store<F: Datum>(\n        &self,\n        data: &dyn MMMInputValue,\n        indices: &TValue,\n    ) -> TractResult<Tensor> {\n        ensure!(self.axis == 0);\n        let data_shape = &[data.mn(), data.k()];\n        let output_shape = &*self.compute_output_shape(data_shape, indices.shape())?;\n        let mut output = unsafe { Tensor::uninitialized::<F>(output_shape)? };\n        let indices_plain = indices.try_as_plain()?;\n        let indices_slice = indices_plain.as_slice::<i64>()?;\n        let vector_len = data_shape[1];\n        if F::datum_type() == f16::datum_type() {\n            let mut output_plain = output.try_as_plain_mut()?;\n            let output_slice = output_plain.as_slice_mut::<f16>()?;\n            for (pos, m) in indices_slice.iter().enumerate() {\n                let slice = &mut output_slice[pos * vector_len..][..vector_len];\n                data.extract_at_mn_f16(*m as usize, slice)?;\n            }\n        } else {\n            let mut output_plain = output.try_as_plain_mut()?;\n            let output_slice = output_plain.as_slice_mut::<f32>()?;\n            for (pos, m) in indices_slice.iter().enumerate() {\n                let slice = &mut output_slice[pos * vector_len..][..vector_len];\n                data.extract_at_mn_f32(*m as usize, slice)?;\n            }\n        }\n        Ok(output)\n    }\n}\n\nimpl TypedOp for Gather {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        if let Some(dt) = self.output_type {\n            ensure!(\n                inputs[0].is_exotic() || inputs[0].datum_type == dt,\n                \"Inconsistent datum_type in Gather: attribute is {:?}, but inputs[0] is {:?}\",\n                dt,\n                inputs[0].datum_type\n            );\n        } else {\n            ensure!(\n                inputs[0].is_plain(),\n                \"Gather applied to compressed data requires an explicit datum_type attribute for its output\"\n            );\n        }\n        ensure!(inputs[1].datum_type == i64::datum_type());\n        if inputs[0].is_exotic() {\n            let data_shape = block_quant_aware_input_shape(inputs[0])?;\n            Ok(tvec!(\n                self.output_type\n                    .unwrap()\n                    .fact(&*self.compute_output_shape(&data_shape, &inputs[1].shape)?)\n            ))\n        } else {\n            Ok(tvec!(\n                inputs[0]\n                    .datum_type\n                    .fact(&*self.compute_output_shape(&inputs[0].shape, &inputs[1].shape)?)\n            ))\n        }\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let (input_fact, indices_fact) = args_2!(model.node_input_facts(node.id)?);\n        if let Some(indices) = indices_fact.konst.as_ref()\n            && indices.rank() == 1\n            && indices.len() == 1\n            && input_fact.is_plain()\n            && input_fact.datum_type.is_number()\n        {\n            let mut patch = TypedModelPatch::default();\n            let mut wire = patch.tap_model(model, node.inputs[0])?;\n            let index = indices.cast_to_scalar::<i64>()?;\n            let index = if index < 0 {\n                let data_fact = model.outlet_fact(node.inputs[0])?;\n                data_fact.shape[self.axis].clone() + index.to_dim()\n            } else {\n                index.to_dim()\n            };\n            wire = patch.wire_node(\n                format!(\"{}.slice\", node.name),\n                crate::ops::array::Slice { axis: self.axis, start: index.clone(), end: index + 1 },\n                &[wire],\n            )?[0];\n            patch.shunt_outside(model, node.id.into(), wire)?;\n            return Ok(Some(patch));\n        }\n        if input_fact.konst.is_some() {\n            // look for a OptSimpleMatMulPack *sibling*\n            if let Some(sibling) = model\n                .outlet_successors(node.inputs[0])\n                .iter()\n                .find(|o| o.node != node.id && model.node(o.node).op_is::<OptSimpleMatMulPack>())\n            {\n                let mut patch = TypedModelPatch::default();\n                let mut taps = patch.taps(model, &node.inputs)?;\n                taps[0] = patch.tap_model(model, sibling.node.into())?;\n                let wire = patch.wire_node(&node.name, self.clone(), &taps)?[0];\n                patch.shunt_outside(model, node.id.into(), wire)?;\n                return Ok(Some(patch));\n            }\n        }\n        Ok(None)\n    }\n}\n\nimpl EvalOp for Gather {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (data, indices) = args_2!(inputs);\n        let result = if let Some(bqs) = data.storage_as::<BlockQuantStorage>() {\n            let dt = self.output_type.unwrap();\n            let m = data.shape()[data.rank() - 2];\n            let k = *data.shape().last().unwrap();\n            dispatch_floatlike!(Self::eval_bq(dt)(self, bqs, m, k, &indices))?\n        } else if let Some(storage) = data.storage_as::<PackedMatrixStorage>()\n            && storage.batch_shape().is_empty()\n        {\n            let dt = self.output_type.unwrap();\n            let data_val = storage.value();\n            dispatch_floatlike!(Self::eval_input_store(dt)(self, data_val, &indices))?\n        } else {\n            dispatch_datum!(Self::eval_t(data.datum_type())(self, data, &indices))?\n        };\n        Ok(tvec!(result.into_tvalue()))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_should_gather_scalar_index() {\n        let data = Tensor::from(arr1(&[1i64, 2, 3]));\n        let gatherer = Gather::new(0);\n        for idx in 2..3 {\n            let index = Tensor::from(arr0(idx));\n            let outputs =\n                gatherer.eval(tvec![data.clone().into_tvalue(), index.into_tvalue()]).unwrap();\n            let output = &outputs[0];\n            assert_eq!(output.shape().len(), 0);\n            assert_eq!(*output.try_as_plain().unwrap().to_scalar::<i64>().unwrap(), idx + 1);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/gather_elements.rs",
    "content": "use crate::internal::*;\nuse ndarray::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct GatherElements {\n    pub axis: usize,\n}\n\nimpl Op for GatherElements {\n    fn name(&self) -> StaticName {\n        \"GatherElements\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl GatherElements {\n    unsafe fn eval_t<T: Datum>(\n        &self,\n        data: TValue,\n        indices: &ArrayViewD<i64>,\n    ) -> TractResult<TValue> {\n        let data_plain = data.try_as_plain()?;\n        let data_view = unsafe { data_plain.to_array_view_unchecked::<T>() };\n        let output = ArrayD::<T>::from_shape_fn(indices.shape(), |mut coords| {\n            let index = indices[&coords];\n            coords[self.axis] =\n                if index < 0 { index + data_view.shape()[self.axis] as i64 } else { index }\n                    as usize;\n            data_view[coords].clone()\n        });\n        let mut tensor = output.into_tensor();\n        unsafe { tensor.set_datum_type(data.datum_type()) };\n        Ok(tensor.into_tvalue())\n    }\n}\n\nimpl TypedOp for GatherElements {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].datum_type.fact(&*inputs[1].shape)))\n    }\n}\n\nimpl EvalOp for GatherElements {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (data, indices) = args_2!(inputs);\n        let indices = indices.cast_to::<i64>()?;\n        let indices = indices.to_plain_array_view::<i64>()?;\n        unsafe {\n            Ok(tvec!(dispatch_datum_by_size!(Self::eval_t(data.datum_type())(\n                self, data, &indices\n            ))?))\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/gather_nd.rs",
    "content": "use crate::internal::*;\nuse tract_ndarray::prelude::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct GatherNd {\n    pub batch_dims: usize,\n}\n\nimpl GatherNd {\n    fn compute_shape<D: DimLike>(\n        &self,\n        data_shape: &[D],\n        indices_shape: &[D],\n    ) -> TractResult<TVec<D>> {\n        let mut shape: TVec<D> = indices_shape.into();\n        let n = shape.pop().unwrap().to_usize()?;\n        shape.extend(data_shape[n + self.batch_dims..].iter().cloned());\n        Ok(shape)\n    }\n\n    unsafe fn eval_t<T: Datum>(\n        &self,\n        output: &mut Tensor,\n        data: &Tensor,\n        indices: &ArrayViewD<i32>,\n    ) -> TractResult<()> {\n        let batch_dims = self.batch_dims;\n        assert_eq!(output.shape()[..batch_dims], data.shape()[..batch_dims]);\n        assert_eq!(output.shape()[..batch_dims], indices.shape()[..batch_dims]);\n        let batch_size = data.shape().iter().take(batch_dims).product();\n        let n = indices.shape()[indices.ndim() - 1];\n\n        let remaining = indices.shape().iter().skip(batch_dims).rev().skip(1).product();\n        let indices_shape_op = tvec!(batch_size, remaining, n);\n        let reshaped_indices: ArrayViewD<i32> =\n            indices.view().into_shape_with_order(&*indices_shape_op).unwrap();\n\n        let mut data_shape_op: TVec<usize> =\n            data.shape().iter().skip(batch_dims).copied().collect();\n        data_shape_op.insert(0, batch_size);\n        let data_plain = data.try_as_plain()?;\n        let reshaped_data = unsafe {\n            data_plain\n                .to_array_view_unchecked::<T>()\n                .into_shape_with_order(&*data_shape_op)\n                .unwrap()\n        };\n\n        let mut output_shape_op: TVec<usize> =\n            data.shape().iter().skip(n + batch_dims).copied().collect();\n        output_shape_op.insert(0, batch_size * remaining);\n        let mut output_plain = output.try_as_plain_mut()?;\n        let mut output = unsafe {\n            output_plain\n                .to_array_view_mut_unchecked::<T>()\n                .into_shape_with_order(&*output_shape_op)\n                .unwrap()\n        };\n\n        for b in 0..batch_size {\n            let mut i = reshaped_data.view();\n            i.index_axis_inplace(Axis(0), b);\n            let mut coords = reshaped_indices.view();\n            coords.index_axis_inplace(Axis(0), b);\n\n            for ix in 0..remaining {\n                let mut coords = coords.view();\n                coords.index_axis_inplace(Axis(0), ix);\n\n                let mut i = i.view();\n                for x in coords {\n                    i.index_axis_inplace(Axis(0), *x as usize);\n                }\n\n                let mut o = output.view_mut();\n                o.index_axis_inplace(Axis(0), b * remaining + ix);\n                o.assign(&i);\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl Op for GatherNd {\n    fn name(&self) -> StaticName {\n        \"GatherNd\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GatherNd {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (data, indices) = args_2!(inputs);\n        let shape = self.compute_shape(data.shape(), indices.shape())?;\n        let indices = indices.cast_to::<i32>()?;\n        let indices = indices.to_plain_array_view::<i32>()?;\n        unsafe {\n            let mut output = Tensor::uninitialized_dt(data.datum_type(), &shape)?;\n            dispatch_datum_by_size!(Self::eval_t(data.datum_type())(\n                self,\n                &mut output,\n                &data,\n                &indices\n            ))?;\n            Ok(tvec!(output.into_tvalue()))\n        }\n    }\n}\n\nimpl TypedOp for GatherNd {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let shape = self.compute_shape(&inputs[0].shape.to_tvec(), &inputs[1].shape.to_tvec())?;\n        Ok(tvec!(inputs[0].datum_type.fact(&shape)))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if let Some(indices) = &model.outlet_fact(node.inputs[1])?.konst\n            && indices.rank() == 2\n            && indices.shape()[0] == 1\n        {\n            let mut patch = TypedModelPatch::default();\n            let mut wire = patch.tap_model(model, node.inputs[0])?;\n            for (axis, &i) in\n                indices.cast_to::<i32>()?.try_as_plain()?.as_slice::<i32>()?.iter().enumerate()\n            {\n                wire = patch.wire_node(\n                    format!(\"{}-slice-axis-{}\", node.name, axis),\n                    crate::ops::array::Slice::new(axis, i as usize, (i + 1) as usize),\n                    &[wire],\n                )?[0];\n            }\n            for i in (0..indices.shape()[1]).rev() {\n                wire = patch.wire_node(\n                    format!(\"{}-remove_axis_{}\", node.name, i),\n                    crate::ops::change_axes::AxisOp::Rm(i),\n                    &[wire],\n                )?[0];\n            }\n            wire = patch.wire_node(\n                format!(\"{}-add_axis\", node.name),\n                crate::ops::change_axes::AxisOp::Add(0),\n                &[wire],\n            )?[0];\n            patch.shunt_outside(model, node.id.into(), wire)?;\n            return Ok(Some(patch));\n        }\n        Ok(None)\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/mod.rs",
    "content": "/// # Operators on array and shapes\nmod broadcast;\npub(crate) mod concat;\npub mod dyn_slice;\nmod gather;\nmod gather_elements;\nmod gather_nd;\nmod one_hot;\nmod pad;\nmod range;\nmod reshape;\nmod scatter_elements;\nmod scatter_nd;\nmod slice;\npub mod strided_slice;\nmod tile;\nmod topk;\nmod trilu;\n\npub use self::broadcast::MultiBroadcastTo;\npub use self::concat::TypedConcat;\npub use self::dyn_slice::DynSlice;\npub use self::gather::Gather;\npub use self::gather_elements::GatherElements;\npub use self::gather_nd::GatherNd;\npub use self::one_hot::OneHot;\npub use self::pad::{Pad, PadMode};\npub use self::range::Range;\npub use self::reshape::FiniteReshape;\npub use self::scatter_elements::ScatterElements;\npub use self::scatter_nd::{ScatterNd, ScatterReduction};\npub use self::slice::Slice;\npub use self::strided_slice::StridedSlice;\npub use self::tile::{DynTile, Tile};\npub use self::topk::Topk;\npub use self::trilu::Trilu;\n"
  },
  {
    "path": "core/src/ops/array/one_hot.rs",
    "content": "use tract_data::itertools::Itertools;\n\nuse crate::internal::*;\n\n#[derive(Debug, PartialEq, Eq, Clone, Hash)]\npub struct OneHot {\n    pub axis: usize,\n    pub dim: usize,\n    pub off: Arc<Tensor>,\n    pub on: Arc<Tensor>,\n}\n\nimpl Op for OneHot {\n    fn name(&self) -> StaticName {\n        \"Onehot\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl TypedOp for OneHot {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut shape = inputs[0].shape.to_tvec();\n        shape.insert(self.axis, self.dim.to_dim());\n        Ok(tvec!(self.off.datum_type().fact(&*shape)))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let axes = (0..inputs[0].rank())\n            .zip('a'..)\n            .map(|(i, repr)| {\n                Axis::new(repr, inputs.len(), outputs.len())\n                    .input(0, i)\n                    .output(0, i + (i >= self.axis) as usize)\n            })\n            .chain(std::iter::once(\n                Axis::new('Z', inputs.len(), outputs.len()).output(0, self.axis),\n            ))\n            .collect_vec();\n        AxesMapping::new(inputs.len(), outputs.len(), axes)\n    }\n\n    as_op!();\n}\n\nimpl EvalOp for OneHot {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let mut shape: TVec<usize> = input.shape().into();\n        shape.insert(self.axis, self.dim);\n        unsafe {\n            let mut output = self.off.broadcast_scalar_to_shape(&shape)?;\n            dispatch_datum_by_size!(Self::eval_t(self.off.datum_type())(\n                self,\n                &input,\n                &mut output\n            ))?;\n            Ok(tvec!(output.into_tvalue()))\n        }\n    }\n}\n\nimpl OneHot {\n    unsafe fn eval_t<T: Datum + Clone>(\n        &self,\n        input: &Tensor,\n        output: &mut Tensor,\n    ) -> TractResult<()> {\n        let on_plain = self.on.try_as_plain()?;\n        let on = unsafe { on_plain.to_scalar_unchecked::<T>() };\n        let mut shape: TVec<usize> = input.shape().into();\n        shape.insert(self.axis, self.dim);\n        let mut output_plain = output.try_as_plain_mut()?;\n        let mut array = unsafe { output_plain.to_array_view_mut_unchecked::<T>() };\n        let input = input.cast_to::<i32>()?;\n        let input = input.to_plain_array_view::<i32>()?;\n        for icoord in tract_ndarray::indices_of(&input) {\n            use tract_ndarray::Dimension;\n            let mut ocoord: Vec<usize> = icoord.slice().into();\n            let coord = input[&icoord];\n            let coord = if coord < 0 { coord + self.dim as i32 } else { coord } as usize;\n            ocoord.insert(self.axis, coord);\n            array[&*ocoord] = on.clone();\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/pad.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub enum PadMode {\n    Constant(Arc<Tensor>),\n    Reflect,\n    Edge,\n}\n\nimpl Default for PadMode {\n    fn default() -> PadMode {\n        PadMode::Constant(Arc::new(0.0f32.into()))\n    }\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Pad {\n    pub pads: Vec<(usize, usize)>,\n    pub mode: PadMode,\n}\n\nimpl Pad {\n    fn eval_t<T>(&self, input_tensor: TValue) -> TractResult<TValue>\n    where\n        T: Copy + Datum,\n    {\n        use tract_ndarray::*;\n        let input = input_tensor.to_plain_array_view::<T>()?;\n        let output_shape: Vec<usize> =\n            input.shape().iter().zip(self.pads.iter()).map(|(&d, &(a, b))| d + a + b).collect();\n        let element = match &self.mode {\n            PadMode::Constant(f) => f.cast_to_scalar::<T>()?,\n            _ => T::default(),\n        };\n        let mut output = ArrayD::<T>::from_elem(output_shape, element);\n        let slice_spec: Vec<SliceInfoElem> = self\n            .pads\n            .iter()\n            .map(|&(a, b)| SliceInfoElem::Slice {\n                start: a as isize,\n                end: if b != 0 { Some(-(b as isize)) } else { None },\n                step: 1,\n            })\n            .collect();\n        let slice_info = SliceInfo::<_, IxDyn, IxDyn>::try_from(slice_spec).unwrap();\n        output.slice_mut(slice_info.as_ref()).assign(&input);\n        if self.mode == PadMode::Reflect || self.mode == PadMode::Edge {\n            for (ax, &(bef, aft)) in self.pads.iter().enumerate() {\n                let axis = Axis(ax);\n                let dim = output.shape()[ax];\n                {\n                    let (mut pad, data) = output.view_mut().split_at(axis, bef);\n                    for i in 0..bef {\n                        let mut target = pad.slice_axis_mut(axis, Slice::from(i..i + 1));\n                        let source_slice = match self.mode {\n                            PadMode::Edge => 0,\n                            PadMode::Reflect => bef - i,\n                            _ => panic!(),\n                        };\n                        let source =\n                            data.slice_axis(axis, Slice::from(source_slice..source_slice + 1));\n                        target.assign(&source);\n                    }\n                }\n                {\n                    let (data, mut pad) = output.view_mut().split_at(axis, dim - aft);\n                    for i in 0..aft {\n                        let mut target = pad.slice_axis_mut(axis, Slice::from(i..i + 1));\n                        let source_slice = match self.mode {\n                            PadMode::Edge => dim - aft - 1,\n                            PadMode::Reflect => dim - aft - 2 - i,\n                            _ => panic!(),\n                        };\n                        let source =\n                            data.slice_axis(axis, Slice::from(source_slice..source_slice + 1));\n                        target.assign(&source);\n                    }\n                }\n            }\n        }\n        let mut output = output.into_tensor();\n        unsafe { output.set_datum_type(input_tensor.datum_type()) }\n        Ok(output.into_tvalue())\n    }\n}\n\nimpl Op for Pad {\n    fn name(&self) -> StaticName {\n        \"Pad\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"Mode: {:?}, pads: {:?})\", self.mode, self.pads,)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Pad {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        Ok(tvec!(dispatch_numbers!(Self::eval_t(input.datum_type())(self, input))?))\n    }\n}\n\nimpl TypedOp for Pad {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact = inputs[0].without_value();\n        if self.pads.len() != fact.rank() {\n            bail!(\"Inconsistent pad: input of rank {}, pads are: {:?}\", fact.rank(), self.pads);\n        }\n        for (ix, (b, e)) in self.pads.iter().enumerate() {\n            fact.shape.set(ix, fact.shape[ix].clone() + *b + *e);\n        }\n        Ok(tvec!(fact))\n    }\n\n    fn input_roi(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TVec<Option<TDim>>>> {\n        let output_fact = model.outlet_fact(OutletId::new(node.id, 0))?;\n        let Some(roi) = &output_fact.region_of_interest else { return Ok(None) };\n        // For each padded axis, substitute 🎯axis → 🎯axis - before\n        let mut input_roi = roi.clone();\n        for (axis, &(before, _)) in self.pads.iter().enumerate() {\n            if before == 0 {\n                continue;\n            }\n            if let Some(sym) = input_roi\n                .symbols()\n                .into_iter()\n                .find(|s| crate::ops::logic::sym_to_coord_axis(s) == Some(axis))\n            {\n                let shifted = TDim::Sym(sym.clone()) - TDim::Val(before as i64);\n                input_roi = input_roi.substitute(&sym, &shifted).unwrap_or(input_roi);\n            }\n        }\n        Ok(Some(tvec![Some(input_roi)]))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let mut result = AxesMapping::disconnected(inputs, outputs)?;\n        for (ix, pads) in self.pads.iter().enumerate() {\n            if pads == &(0, 0) {\n                result = result.linking((InOut::In(0), ix), (InOut::Out(0), ix))?;\n            }\n        }\n        Ok(result)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        let mut new_op = self.clone();\n        if let (InOut::In(0), AxisOp::Rm(ix)) = (io, change)\n            && new_op.pads.remove(*ix) == (0, 0)\n        {\n            return Ok(Some(AxisChangeConsequence::new(\n                model,\n                node,\n                Some(Box::new(new_op)),\n                change,\n            )));\n        }\n        if let (InOut::In(0), AxisOp::Add(ix)) = (io, change) {\n            new_op.pads.insert(*ix, (0, 0));\n            return Ok(Some(AxisChangeConsequence::new(\n                model,\n                node,\n                Some(Box::new(new_op)),\n                change,\n            )));\n        }\n        Ok(None)\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if self.pads.iter().all(|p| p.0 == 0 && p.1 == 0) {\n            TypedModelPatch::shunt_one_op(model, node)\n        } else {\n            Ok(None)\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/range.rs",
    "content": "use crate::ops::cast::Cast;\nuse tract_num_traits::AsPrimitive;\nuse tract_num_traits::Zero;\n\nuse crate::internal::*;\n\nuse super::Slice;\n\n#[derive(Debug, Default, Clone, new, Hash, PartialEq, Eq)]\npub struct Range {\n    len: TDim,\n}\n\nimpl Op for Range {\n    fn name(&self) -> StaticName {\n        \"Range\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Range {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let (start, end, step) = args_3!(inputs);\n        Ok(tvec!(self.make(&start, &end, &step, &session.resolved_symbols)?.into_tvalue()))\n    }\n}\n\nimpl Range {\n    fn make_t<T: Datum + for<'a> std::ops::Add<&'a T, Output = T>>(\n        start: &Tensor,\n        step: &Tensor,\n        len: usize,\n    ) -> TractResult<Tensor> {\n        unsafe {\n            let mut result = Tensor::uninitialized::<T>(&[len])?;\n            let mut v = start.try_as_plain()?.to_scalar::<T>()?.clone();\n            let step = step.try_as_plain()?.to_scalar::<T>()?;\n            {\n                let mut result_plain = result.try_as_plain_mut()?;\n                for i in 0..len {\n                    result_plain.as_slice_mut_unchecked::<T>()[i] = v.clone();\n                    v = v + step;\n                }\n            }\n            Ok(result)\n        }\n    }\n\n    fn make(\n        &self,\n        start: &Tensor,\n        end: &Tensor,\n        step: &Tensor,\n        values: &SymbolValues,\n    ) -> TractResult<Tensor> {\n        if start.datum_type() == TDim::datum_type() {\n            let start = start.try_as_plain()?.to_scalar::<TDim>()?.eval(values).to_i64()?;\n            let step = step.try_as_plain()?.to_scalar::<TDim>()?.eval(values).to_i64()?;\n            let len = {\n                let end = end.try_as_plain()?.to_scalar::<TDim>()?.eval(values).to_i64()?;\n                #[allow(clippy::cast_abs_to_unsigned)]\n                ((end - start).abs() as usize).divceil(step.abs() as usize)\n            };\n            Self::make_t::<i64>(&tensor0(start), &tensor0(step), len)\n        } else {\n            let len = dispatch_numbers!(Self::len_for_numbers(start.datum_type())(\n                self, start, end, step\n            ))?;\n            dispatch_numbers!(Self::make_t(start.datum_type())(start, step, len))\n        }\n    }\n\n    fn len_for_numbers<T: Datum + AsPrimitive<f64>>(\n        &self,\n        start: &Tensor,\n        end: &Tensor,\n        step: &Tensor,\n    ) -> TractResult<usize> {\n        let start = start.try_as_plain()?.to_scalar::<T>()?;\n        let end = end.try_as_plain()?.to_scalar::<T>()?;\n        let step = step.try_as_plain()?.to_scalar::<T>()?;\n        Ok(((end.as_() - start.as_()) / (step.as_())).ceil() as usize)\n    }\n}\n\nimpl TypedOp for Range {\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        rule_if_some!(succ = model.single_succ(node.id)?);\n        rule_if_some!(slice = succ.op_as::<Slice>());\n        rule_if!(slice.start.is_zero());\n        rule_if!(slice.end.is_zero());\n\n        let mut patch = TypedModelPatch::default();\n        let mut wire = patch.tap_model(model, node.inputs[0])?;\n        if model.outlet_fact(node.inputs[0])?.datum_type.is_tdim() {\n            wire = patch.wire_node(\n                format!(\"{}.cast-tdim\", node.name),\n                Cast { to: DatumType::I64 },\n                &[wire],\n            )?[0];\n        }\n        let wire = patch.wire_node(&node.name, AxisOp::Add(0), &[wire])?;\n        patch.shunt_outside(model, succ.id.into(), wire[0])?;\n        Ok(Some(patch))\n    }\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let [start, end, step] = inputs else {\n            bail!(\"Expects three inputs\");\n        };\n        ensure!(start.datum_type() == end.datum_type());\n        ensure!(start.datum_type() == step.datum_type());\n        ensure!(start.shape.volume().is_one());\n        ensure!(end.shape.volume().is_one());\n        ensure!(step.shape.volume().is_one());\n        if let (Some(start), Some(end), Some(step)) = (&start.konst, &end.konst, &step.konst) {\n            if start.datum_type() == TDim::datum_type() {\n                let start_tdim = start.try_as_plain()?.to_scalar::<TDim>()?.clone();\n                let end_tdim = end.try_as_plain()?.to_scalar::<TDim>()?;\n                let step = step.cast_to_scalar::<i64>()?;\n                let len = if step < 0 {\n                    (start_tdim.clone() - end_tdim).divceil(-step as usize)\n                } else {\n                    (end_tdim.clone() - start_tdim.clone()).divceil(step as usize)\n                };\n                let mut fact = DatumType::I64.fact([len]);\n                if let Some(scope) = start_tdim.find_scope().or_else(|| end_tdim.find_scope()) {\n                    let x0 = TDim::Sym(scope.coord_sym(0));\n                    let term = if step == 1 { x0 } else { TDim::MulInt(step, Box::new(x0)) };\n                    fact.uniform_tdim = Some((start_tdim + term).reduce());\n                }\n                Ok(tvec!(fact))\n            } else {\n                let len = dispatch_numbers!(Self::len_for_numbers(start.datum_type())(\n                    self, start, end, step\n                ))?\n                .to_dim();\n                Ok(tvec!(start.datum_type().fact([len])))\n            }\n        } else {\n            let mut fact = start.datum_type.fact(std::slice::from_ref(&self.len));\n            if let (Some(s), Some(k)) = (&start.uniform_tdim, &step.uniform_tdim) {\n                if let Some(scope) = self.len.find_scope() {\n                    let x0 = TDim::Sym(scope.coord_sym(0));\n                    let term = match k {\n                        TDim::Val(1) => x0,\n                        TDim::Val(v) => TDim::MulInt(*v, Box::new(x0)),\n                        other => TDim::Mul(vec![other.clone(), x0]),\n                    };\n                    fact.uniform_tdim = Some((s.clone() + term).reduce());\n                }\n            }\n            Ok(tvec!(fact))\n        }\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/array/reshape.rs",
    "content": "use crate::internal::*;\nuse tract_itertools::Itertools;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct FiniteReshape {\n    pub shape: TVec<usize>,\n}\n\nimpl Op for FiniteReshape {\n    fn name(&self) -> StaticName {\n        \"Reshape\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"to shape: {}\", self.shape.iter().join(\",\"))])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for FiniteReshape {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let mut tensor = input.into_tensor();\n        unsafe {\n            tensor.set_shape_unchecked(&self.shape);\n        }\n        Ok(tvec!(tensor.into_tvalue()))\n    }\n}\n\nimpl TypedOp for FiniteReshape {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].datum_type.fact(&self.shape)))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/array/scatter_elements.rs",
    "content": "use super::scatter_nd::ScatterReduction;\nuse crate::internal::*;\nuse ndarray::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct ScatterElements {\n    pub axis: usize,\n    pub reduction: ScatterReduction,\n}\n\nimpl Op for ScatterElements {\n    fn name(&self) -> StaticName {\n        \"ScatterElements\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl ScatterElements {\n    unsafe fn eval_t<T: Datum>(\n        data: TValue,\n        indices: &ArrayViewD<i64>,\n        updates: TValue,\n        axis: usize,\n    ) -> TractResult<TValue> {\n        let mut data = unsafe { data.into_tensor().into_array_unchecked::<T>() };\n        let updates_plain = updates.try_as_plain()?;\n        let updates_view = unsafe { updates_plain.to_array_view_unchecked::<T>() };\n        for (mut coords, value) in updates_view.indexed_iter() {\n            let index = indices[&coords];\n            coords[axis] =\n                if index < 0 { index + data.shape()[axis] as i64 } else { index } as usize;\n            data[coords] = value.clone()\n        }\n        let mut tensor = data.into_tensor();\n        unsafe { tensor.set_datum_type(updates.datum_type()) };\n        Ok(tensor.into_tvalue())\n    }\n\n    unsafe fn eval_t_reduce<T: Datum + PartialOrd + std::ops::AddAssign + std::ops::MulAssign>(\n        data: TValue,\n        indices: &ArrayViewD<i64>,\n        updates: TValue,\n        axis: usize,\n        reduction: ScatterReduction,\n    ) -> TractResult<TValue> {\n        let mut data = unsafe { data.into_tensor().into_array_unchecked::<T>() };\n        let updates_plain = updates.try_as_plain()?;\n        let updates_view = unsafe { updates_plain.to_array_view_unchecked::<T>() };\n        for (mut coords, value) in updates_view.indexed_iter() {\n            let index = indices[&coords];\n            coords[axis] =\n                if index < 0 { index + data.shape()[axis] as i64 } else { index } as usize;\n            let d = &mut data[coords];\n            match reduction {\n                ScatterReduction::Add => *d += value.clone(),\n                ScatterReduction::Mul => *d *= value.clone(),\n                ScatterReduction::Min => {\n                    if value < d {\n                        *d = value.clone()\n                    }\n                }\n                ScatterReduction::Max => {\n                    if value > d {\n                        *d = value.clone()\n                    }\n                }\n                ScatterReduction::None => unreachable!(),\n            }\n        }\n        let mut tensor = data.into_tensor();\n        unsafe { tensor.set_datum_type(updates.datum_type()) };\n        Ok(tensor.into_tvalue())\n    }\n}\n\nimpl TypedOp for ScatterElements {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].datum_type.fact(inputs[0].shape.clone())))\n    }\n}\n\nimpl EvalOp for ScatterElements {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (data, indices, updates) = args_3!(inputs);\n        let indices = indices.cast_to::<i64>()?;\n        let indices = indices.to_plain_array_view::<i64>()?;\n        if data.datum_type() != updates.datum_type() {\n            bail!(\n                \"Data and update must be of the same type, got {:?} and {:?}\",\n                data.datum_type(),\n                updates.datum_type()\n            );\n        }\n        unsafe {\n            match self.reduction {\n                ScatterReduction::None => {\n                    Ok(tvec!(dispatch_datum_by_size!(Self::eval_t(data.datum_type())(\n                        data, &indices, updates, self.axis\n                    ))?))\n                }\n                reduction => Ok(tvec!(dispatch_numbers!(Self::eval_t_reduce(data.datum_type())(\n                    data, &indices, updates, self.axis, reduction\n                ))?)),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/scatter_nd.rs",
    "content": "use crate::internal::*;\nuse ndarray::*;\n\n#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]\npub enum ScatterReduction {\n    #[default]\n    None,\n    Add,\n    Mul,\n    Min,\n    Max,\n}\n\nimpl ScatterReduction {\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            ScatterReduction::None => \"none\",\n            ScatterReduction::Add => \"add\",\n            ScatterReduction::Mul => \"mul\",\n            ScatterReduction::Min => \"min\",\n            ScatterReduction::Max => \"max\",\n        }\n    }\n\n    pub fn parse(s: &str) -> TractResult<Self> {\n        Ok(match s {\n            \"none\" => ScatterReduction::None,\n            \"add\" => ScatterReduction::Add,\n            \"mul\" => ScatterReduction::Mul,\n            \"min\" => ScatterReduction::Min,\n            \"max\" => ScatterReduction::Max,\n            s => bail!(\"Unknown scatter reduction: {s}\"),\n        })\n    }\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct ScatterNd {\n    pub reduction: ScatterReduction,\n}\n\nimpl Op for ScatterNd {\n    fn name(&self) -> StaticName {\n        \"ScatterNd\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl ScatterNd {\n    unsafe fn eval_t<T: Datum>(\n        data: TValue,\n        indices: &ArrayViewD<i64>,\n        updates: TValue,\n    ) -> TractResult<TValue> {\n        let mut data = unsafe { data.into_tensor().into_array_unchecked::<T>() };\n        let updates_plain = updates.try_as_plain()?;\n        let updates_view = unsafe { updates_plain.to_array_view_unchecked::<T>() };\n        for coords in tract_ndarray::indices(&indices.shape()[..indices.ndim() - 1]) {\n            let mut indices_into_data = indices.view();\n            let mut updates = updates_view.view();\n            for x in coords.slice() {\n                indices_into_data.index_axis_inplace(Axis(0), *x);\n                updates.index_axis_inplace(Axis(0), *x);\n            }\n            let mut data = data.view_mut();\n            for x in indices_into_data {\n                data.index_axis_inplace(Axis(0), *x as usize);\n            }\n            data.assign(&updates)\n        }\n        let mut tensor = data.into_tensor();\n        unsafe { tensor.set_datum_type(updates.datum_type()) };\n        Ok(tensor.into_tvalue())\n    }\n\n    unsafe fn eval_t_reduce<T: Datum + PartialOrd + std::ops::AddAssign + std::ops::MulAssign>(\n        data: TValue,\n        indices: &ArrayViewD<i64>,\n        updates: TValue,\n        reduction: ScatterReduction,\n    ) -> TractResult<TValue> {\n        let mut data = unsafe { data.into_tensor().into_array_unchecked::<T>() };\n        let updates_plain = updates.try_as_plain()?;\n        let updates_view = unsafe { updates_plain.to_array_view_unchecked::<T>() };\n        for coords in tract_ndarray::indices(&indices.shape()[..indices.ndim() - 1]) {\n            let mut indices_into_data = indices.view();\n            let mut updates = updates_view.view();\n            for x in coords.slice() {\n                indices_into_data.index_axis_inplace(Axis(0), *x);\n                updates.index_axis_inplace(Axis(0), *x);\n            }\n            let mut data = data.view_mut();\n            for x in indices_into_data {\n                data.index_axis_inplace(Axis(0), *x as usize);\n            }\n            Zip::from(&mut data).and(&updates).for_each(|d, u| match reduction {\n                ScatterReduction::Add => *d += u.clone(),\n                ScatterReduction::Mul => *d *= u.clone(),\n                ScatterReduction::Min => {\n                    if u < d {\n                        *d = u.clone()\n                    }\n                }\n                ScatterReduction::Max => {\n                    if u > d {\n                        *d = u.clone()\n                    }\n                }\n                ScatterReduction::None => unreachable!(),\n            });\n        }\n        let mut tensor = data.into_tensor();\n        unsafe { tensor.set_datum_type(updates.datum_type()) };\n        Ok(tensor.into_tvalue())\n    }\n}\n\nimpl TypedOp for ScatterNd {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].datum_type.fact(inputs[0].shape.to_tvec())))\n    }\n}\n\nimpl EvalOp for ScatterNd {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (data, indices, updates) = args_3!(inputs);\n        let indices = indices.cast_to::<i64>()?;\n        let indices = indices.to_plain_array_view::<i64>()?;\n        if data.datum_type() != updates.datum_type() {\n            bail!(\n                \"Data and update must be of the same type, got {:?} and {:?}\",\n                data.datum_type(),\n                updates.datum_type()\n            );\n        }\n        unsafe {\n            match self.reduction {\n                ScatterReduction::None => {\n                    Ok(tvec!(dispatch_datum_by_size!(Self::eval_t(data.datum_type())(\n                        data, &indices, updates\n                    ))?))\n                }\n                reduction => Ok(tvec!(dispatch_numbers!(Self::eval_t_reduce(data.datum_type())(\n                    data, &indices, updates, reduction\n                ))?)),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/slice.rs",
    "content": "use crate::internal::*;\nuse crate::num_traits::Zero;\n\n#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]\npub struct Slice {\n    pub axis: usize,\n    pub start: TDim,\n    pub end: TDim,\n}\n\nimpl Slice {\n    pub fn new(axis: usize, start: impl ToDim, end: impl ToDim) -> Slice {\n        Slice { axis, start: start.to_dim(), end: end.to_dim() }\n    }\n\n    pub fn suffix(&self, name: &str) -> String {\n        format!(\"{}.axis{}_{}_{}\", name, self.axis, self.start, self.end)\n    }\n\n    pub fn declutter_slice_after_slice(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let prec = model.node(node.inputs[0].node);\n        if let Some(other) = prec.op_as::<Slice>()\n            && other.axis == self.axis\n        {\n            return TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &prec.inputs,\n                Slice {\n                    axis: self.axis,\n                    start: self.start.clone() + &other.start,\n                    end: self.end.clone() + &other.start,\n                },\n            )\n            .map(Some);\n        }\n        Ok(None)\n    }\n}\n\nimpl Op for Slice {\n    fn name(&self) -> StaticName {\n        \"Slice\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis: {}, {}..{}\", self.axis, self.start, self.end)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Slice {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let start = self.start.eval(&session.resolved_symbols).to_usize()?;\n        let end = self.end.eval(&session.resolved_symbols).to_usize()?;\n        eval_slice(&input, self.axis, start, end)\n    }\n}\n\nfn eval_slice(input: &Tensor, axis: usize, start: usize, end: usize) -> TractResult<TVec<TValue>> {\n    if end > input.shape()[axis] || start > end {\n        bail!(\"Invalid range {}..{} for slicing {:?} on axis {}\", start, end, input, axis);\n    }\n    unsafe {\n        let mut shape: TVec<_> = input.shape().into();\n        shape[axis] = end - start;\n        let mut tensor = Tensor::uninitialized_dt(input.datum_type(), &shape)?;\n        tensor.assign_slice_unchecked(.., input, start..end, axis);\n        Ok(tvec!(tensor.into_tvalue()))\n    }\n}\n\nimpl TypedOp for Slice {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        anyhow::ensure!(inputs.len() == 1, \"Slice has one single input\");\n        if let (Ok(start), Ok(end), Ok(len)) =\n            (self.start.to_usize(), self.end.to_usize(), inputs[0].shape[self.axis].to_usize())\n        {\n            ensure!(start <= end);\n            ensure!(end <= len);\n        }\n        let mut fact = inputs[0].without_value();\n        fact.shape.set(self.axis, (self.end.clone() - &self.start).to_dim());\n        Ok(tvec!(fact))\n    }\n\n    fn input_roi(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TVec<Option<TDim>>>> {\n        let output_fact = model.outlet_fact(OutletId::new(node.id, 0))?;\n        let Some(roi) = &output_fact.region_of_interest else { return Ok(None) };\n        if self.start.is_zero() {\n            return Ok(Some(tvec![Some(roi.clone())]));\n        }\n        // Remap: output 🎯axis = input 🎯axis - start, so substitute 🎯axis → 🎯axis + start\n        if let Some(sym) = roi\n            .symbols()\n            .into_iter()\n            .find(|s| crate::ops::logic::sym_to_coord_axis(s) == Some(self.axis))\n        {\n            let shifted = TDim::Sym(sym.clone()) + self.start.clone();\n            if let Ok(input_roi) = roi.substitute(&sym, &shifted) {\n                return Ok(Some(tvec![Some(input_roi)]));\n            }\n        }\n        // ROI doesn't mention the sliced axis — pass through unchanged\n        Ok(Some(tvec![Some(roi.clone())]))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let mut mapping = AxesMapping::disconnected(inputs, outputs)?;\n        for (axis, repr) in (0..inputs[0].rank()).zip('a'..) {\n            if self.axis != axis {\n                mapping = mapping\n                    .renaming((InOut::In(0), axis), repr)?\n                    .linking(repr, (InOut::Out(0), axis))?;\n            }\n        }\n        Ok(mapping)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        if let Some(axis) = change.transform_axis(self.axis) {\n            if axis != self.axis {\n                Ok(Some(AxisChangeConsequence::new(\n                    model,\n                    node,\n                    Some(Box::new(Slice { axis, ..self.clone() }) as _),\n                    change,\n                )))\n            } else {\n                Ok(Some(AxisChangeConsequence::new(model, node, None, change)))\n            }\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if self.start.is_zero() && (self.end == model.outlet_fact(node.inputs[0])?.shape[self.axis])\n        {\n            TypedModelPatch::shunt_one_op(model, node)\n        } else if let Some(p) = self.declutter_slice_after_slice(model, node)? {\n            Ok(Some(p))\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let op =\n            Slice { axis: self.axis, start: self.start.eval(values), end: self.end.eval(values) };\n        let inputs = node.inputs.iter().map(|i| mapping[i]).collect::<TVec<_>>();\n        target.wire_node(&node.name, op, &inputs)\n    }\n\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        _model: &TypedModel,\n        node: &TypedNode,\n        _prefix: &str,\n        inputs: &[OutletId],\n        _output_axis: usize,\n        _start: &TDim,\n        _end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        patch.wire_node(&node.name, &node.op, inputs).map(Some)\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/array/strided_slice.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct StridedSlice {\n    pub optional_axes_input: Option<usize>,\n    pub optional_steps_input: Option<usize>,\n    pub begin_mask: i64,\n    pub end_mask: i64,\n    pub shrink_axis_mask: i64,\n}\n\n#[derive(Debug, Clone, PartialEq)]\npub struct Dim {\n    // position of the first element to return\n    pub begin: TDim,\n    // position of the first element not to return\n    pub end: TDim,\n    pub stride: i32,\n    pub shrink: bool,\n}\n\nimpl Dim {\n    pub fn soft_len(&self) -> TractResult<TDim> {\n        if let Ok(len) = (self.end.clone() - &self.begin).to_isize() {\n            Ok((((self.stride.abs() - 1) + len.abs() as i32) / self.stride.abs()).to_dim())\n        } else if self.stride == 1 {\n            Ok(self.end.clone() - &self.begin)\n        } else {\n            bail!(\"Streaming dimensions with strides are not supported for now\")\n        }\n    }\n}\n\nimpl StridedSlice {\n    fn must_shrink(&self, ix: usize) -> bool {\n        self.shrink_axis_mask & (1 << ix) != 0\n    }\n    fn ignore_begin(&self, ix: usize) -> bool {\n        self.begin_mask & (1 << ix) != 0\n    }\n    fn ignore_end(&self, ix: usize) -> bool {\n        self.end_mask & (1 << ix) != 0\n    }\n    pub fn prepare_one_dim(\n        &self,\n        ix: usize,\n        dim: &TDim,\n        begin: &Tensor,\n        end: &Tensor,\n        strides: &[i32],\n    ) -> TractResult<Dim> {\n        // cast bouds to Option<Dim>, dealing with ignore from mask, and spec shorted than dim\n        // also for end, magic values in onnx :/\n        let mut begin: Option<TDim> = if ix >= begin.len() {\n            None\n        } else {\n            let begin = begin.cast_to::<TDim>()?;\n            begin.try_as_plain()?.as_slice::<TDim>()?.get(ix).cloned()\n        };\n\n        let mut end: Option<TDim> = if self.ignore_end(ix) || ix >= end.len() {\n            None\n        } else if end.datum_type() == i64::datum_type() {\n            let end = *end.try_as_plain()?.as_slice::<i64>()?.get(ix).unwrap();\n            if end == i64::MAX || end == i64::MIN || end == i64::MIN + 1 || end == (i32::MAX as i64)\n            {\n                None\n            } else {\n                Some(end.to_dim())\n            }\n        } else {\n            let end = end.cast_to::<TDim>()?;\n            end.try_as_plain()?.as_slice::<TDim>()?.get(ix).cloned()\n        };\n\n        let stride = strides.get(ix).cloned().unwrap_or(1);\n\n        // deal with negative indexing\n        fn fix_negative(bound: &mut TDim, dim: &TDim) {\n            let neg = if bound.prove_positive_or_zero() {\n                false\n            } else if bound.prove_negative_or_zero() {\n                true\n            } else {\n                #[allow(clippy::mutable_key_type)]\n                let symbols = bound.symbols();\n                if symbols.len() == 1 {\n                    let sym = symbols.into_iter().next().unwrap();\n                    let values = SymbolValues::default().with(&sym, 100_000_000);\n                    bound.eval(&values).to_isize().unwrap() < 0\n                } else {\n                    false\n                }\n            };\n            if neg {\n                *bound = bound.clone() + dim;\n            }\n        }\n        if let Some(begin) = begin.as_mut() {\n            fix_negative(begin, dim)\n        }\n        if let Some(end) = end.as_mut() {\n            fix_negative(end, dim)\n        }\n\n        if self.must_shrink(ix) {\n            return Ok(Dim {\n                begin: begin.clone().unwrap_or_else(|| 0.to_dim()),\n                end: begin.unwrap_or_else(|| 0.to_dim()) + 1,\n                stride: 1,\n                shrink: true,\n            });\n        }\n\n        // must happen after dealing with must_shrink :/\n        if self.ignore_begin(ix) {\n            begin = None;\n        }\n\n        let mut begin =\n            begin.unwrap_or_else(|| if stride > 0 { 0.to_dim() } else { dim.clone() - 1 });\n        if begin.to_isize().map(|b| b < 0).unwrap_or(false) {\n            if stride < 0 {\n                return Ok(Dim { begin: 0.to_dim(), end: 0.to_dim(), stride, shrink: false });\n            } else {\n                begin = 0.to_dim();\n            }\n        }\n        if let (Ok(b), Ok(d)) = (begin.to_isize(), dim.to_isize())\n            && b > d - 1\n        {\n            if stride > 0 {\n                return Ok(Dim { begin: 0.to_dim(), end: 0.to_dim(), stride, shrink: false });\n            } else {\n                begin = (d - 1).to_dim()\n            }\n        }\n\n        let mut end = end.unwrap_or_else(|| if stride > 0 { dim.clone() } else { (-1).to_dim() });\n        if end.to_isize().map(|e| e < 0).unwrap_or(false) {\n            if stride > 0 {\n                return Ok(Dim { begin: 0.to_dim(), end: 0.to_dim(), stride, shrink: false });\n            } else {\n                end = (-1).to_dim();\n            }\n        }\n        if let (Ok(e), Ok(d)) = (end.to_isize(), dim.to_isize())\n            && e > d - 1\n        {\n            if stride > 0 {\n                end = d.to_dim()\n            } else {\n                return Ok(Dim { begin: 0.to_dim(), end: 0.to_dim(), stride, shrink: false });\n            }\n        }\n        Ok(Dim { begin, end, stride, shrink: false })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let params: TVec<Option<Arc<Tensor>>> = inputs[1..]\n            .iter()\n            .map(|i| Ok(target.outlet_fact(*i)?.konst.clone()))\n            .collect::<TractResult<_>>()?;\n        let input_shape = target.outlet_fact(inputs[0])?.shape.clone();\n        let strides: TVec<i32> = if let Some(i) = self.optional_steps_input {\n            let strides = params[i - 1]\n                .as_ref()\n                .context(\"StridedSlice is typable only if stride is a const\")?\n                .cast_to::<i32>()?;\n            strides.try_as_plain()?.as_slice::<i32>()?.into()\n        } else {\n            tvec![1; input_shape.rank()]\n        };\n        let axes: TVec<usize> = if let Some(i) = self.optional_axes_input {\n            let axes = params[i - 1]\n                .as_ref()\n                .context(\"StridedSlice is typable only if axis is a const\")?\n                .cast_to::<i32>()?;\n            axes.try_as_plain()?\n                .as_slice::<i32>()?\n                .iter()\n                .map(|&i| if i < 0 { input_shape.rank() as i32 + i } else { i } as usize)\n                .collect()\n        } else {\n            (0..input_shape.rank()).collect()\n        };\n        let mut wire = inputs[0];\n        let begin = params[0].as_ref();\n        let end = params[1].as_ref();\n        for (ix, &axis) in axes.iter().enumerate() {\n            if let (Some(begin), Some(end)) = (begin, end) {\n                let d = &input_shape[axis];\n                let preped = self.prepare_one_dim(ix, d, begin, end, &strides)?;\n                let (left, right) = if preped.stride > 0 {\n                    (preped.begin, preped.end)\n                } else {\n                    (preped.end + 1, preped.begin + 1)\n                };\n                wire = target.wire_node(\n                    format!(\"{prefix}.slice-axis-{axis}\"),\n                    crate::ops::array::Slice::new(axis, left, right),\n                    [wire].as_ref(),\n                )?[0];\n                if preped.stride != 1 {\n                    wire = target.wire_node(\n                        format!(\"{prefix}.stride-axis-{axis}\"),\n                        crate::ops::downsample::Downsample::new(axis, preped.stride as isize, 0),\n                        [wire].as_ref(),\n                    )?[0];\n                }\n            } else if strides[ix] == 1 {\n                let left = target.wire_node(\n                    format!(\"{prefix}.slice-axis-{axis}-start\"),\n                    crate::ops::array::Slice::new(0, ix, ix + 1),\n                    &[inputs[1]],\n                )?;\n                let left = target.wire_node(\n                    format!(\"{prefix}.slice-axis-{axis}-start-rm-axis\"),\n                    AxisOp::Rm(0),\n                    &left,\n                )?[0];\n                let right = target.wire_node(\n                    format!(\"{prefix}.slice-axis-{axis}-end\"),\n                    crate::ops::array::Slice::new(0, ix, ix + 1),\n                    &[inputs[2]],\n                )?;\n                let right = target.wire_node(\n                    format!(\"{prefix}.slice-axis-{axis}-end-rm-axis\"),\n                    AxisOp::Rm(0),\n                    &right,\n                )?[0];\n                let sym = target.symbols.new_with_prefix(\"l\");\n                wire = target.wire_node(\n                    format!(\"{prefix}.slice-axis-{axis}\"),\n                    crate::ops::array::DynSlice::new(axis, sym.to_dim()),\n                    &[wire, left, right],\n                )?[0];\n            }\n        }\n        let mut shrink = input_shape\n            .iter()\n            .enumerate()\n            .filter(|(ix, _d)| self.must_shrink(*ix))\n            .map(|pair| pair.0)\n            .collect::<Vec<_>>();\n        shrink.sort();\n        for axis in shrink.iter().rev() {\n            wire = target.wire_node(\n                format!(\"{prefix}.RmDim-{axis}\"),\n                AxisOp::Rm(*axis),\n                [wire].as_ref(),\n            )?[0];\n        }\n        target.rename_node(wire.node, prefix)?;\n        Ok(tvec!(wire))\n    }\n}\n\nimpl Op for StridedSlice {\n    fn name(&self) -> StaticName {\n        \"StridedSlice\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for StridedSlice {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let mut model = TypedModel::default();\n        let scope = inputs.iter().find_map(|i| {\n            i.try_as_plain().ok().and_then(|d| {\n                d.as_slice::<TDim>()\n                    .ok()\n                    .and_then(|slice| slice.iter().find_map(|dim| dim.find_scope()))\n            })\n        });\n        model.symbols = scope.unwrap_or_default();\n        let mut source = tvec!();\n        for (ix, input) in inputs.iter().enumerate() {\n            source.push(model.add_source(\n                format!(\"adhoc_input.{ix}\"),\n                input.clone().into_arc_tensor().try_into()?,\n            )?);\n        }\n        let output = self.wire(\"adhoc\", &mut model, &source)?;\n        model.select_output_outlets(&output)?;\n        model.into_runnable()?.run(inputs)\n    }\n}\n\nimpl TypedOp for StridedSlice {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut model = TypedModel::default();\n        let mut source = tvec!();\n        for (ix, input) in inputs.iter().enumerate() {\n            source.push(model.add_source(format!(\"adhoc_input.{ix}\"), (*input).clone())?);\n        }\n        let output = self.wire(\"adhoc\", &mut model, &source)?;\n        model.select_output_outlets(&output)?;\n        Ok(tvec!(model.outlet_fact(output[0])?.clone()))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let mut patch = TypedModelPatch::default();\n        let mut source = tvec!();\n        for &input in &node.inputs {\n            source.push(patch.tap_model(model, input)?);\n        }\n        let output = self.wire(&node.name, &mut patch, &source)?;\n        patch.shunt_outside(model, node.id.into(), output[0])?;\n        Ok(Some(patch))\n    }\n\n    as_op!();\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn apply(\n        input: &[i32],\n        start: Option<isize>,\n        end: Option<isize>,\n        stride: Option<isize>,\n    ) -> TValue {\n        // [0,1,2,3,4,5][::2] => [0, 2, 4]\n        let op = StridedSlice {\n            optional_axes_input: None,\n            optional_steps_input: if stride.is_some() { Some(3) } else { None },\n            begin_mask: if start.is_some() { 0 } else { 1 },\n            end_mask: if end.is_some() { 0 } else { 1 },\n            shrink_axis_mask: 0,\n        };\n        let mut inputs = tvec!(\n            tensor1(input).into(),\n            tensor1(&[start.unwrap_or(0) as i32]).into(),\n            tensor1(&[end.unwrap_or(0) as i32]).into(),\n        );\n        if let Some(stride) = stride {\n            inputs.push(tensor1(&[stride as i32]).into());\n        }\n        op.eval(inputs).unwrap().remove(0)\n    }\n\n    #[test]\n    fn numpy_pos_stride() {\n        // [0,1,2,3][::2] => [0, 2]\n        assert_eq!(apply(&[0, 1, 2, 3], None, None, Some(2)), tensor1(&[0, 2]).into());\n    }\n\n    #[test]\n    fn numpy_neg_stride() {\n        // [0,1,2,3][::-2] => [3, 1]\n        assert_eq!(apply(&[0, 1, 2, 3], None, None, Some(-2)), tensor1(&[3, 1]).into());\n    }\n\n    #[test]\n    fn numpy_neg_stride_with_start_even() {\n        // [0,1,2,3][-1::-2] => [3, 1]\n        assert_eq!(apply(&[0, 1, 2, 3], Some(-1), None, Some(-2)), tensor1(&[3, 1]).into());\n    }\n\n    #[test]\n    fn numpy_neg_stride_with_start_odd() {\n        // [0,1,2,3][-1::-2] => [3, 1]\n        assert_eq!(apply(&[0, 1, 2, 3, 4], Some(-1), None, Some(-2)), tensor1(&[4, 2, 0]).into());\n    }\n}\n"
  },
  {
    "path": "core/src/ops/array/tile.rs",
    "content": "use crate::internal::*;\nuse ndarray::*;\n\nuse super::MultiBroadcastTo;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Tile {\n    pub multipliers: TVec<TDim>,\n}\n\nimpl Op for Tile {\n    fn name(&self) -> StaticName {\n        \"Tile\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"multipliers: {:?}\", self.multipliers)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Tile {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let multipliers: TVec<usize> = self\n            .multipliers\n            .iter()\n            .map(|m| m.eval(&session.resolved_symbols).to_usize())\n            .collect::<TractResult<_>>()?;\n        let result =\n            dispatch_datum_by_size!(eval_t(inputs[0].datum_type())(&inputs[0], &multipliers))?;\n        Ok(tvec!(result))\n    }\n}\n\nimpl TypedOp for Tile {\n    as_op!();\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let multipliers = self.multipliers.iter().map(|m| m.eval(values)).collect();\n        target.wire_node(&node.name, Self { multipliers }, &[mapping[&node.inputs[0]]])\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let input_fact = model.outlet_fact(node.inputs[0])?;\n        if input_fact\n            .shape\n            .iter()\n            .zip(self.multipliers.iter())\n            .all(|(i, m)| i.is_one() || m.is_one())\n        {\n            let output_fact = self.output_facts(&[input_fact])?.remove(0);\n            TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &node.inputs[0..1],\n                MultiBroadcastTo { shape: output_fact.shape },\n            )\n            .map(Some)\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let shape = inputs[0]\n            .shape\n            .iter()\n            .zip(self.multipliers.iter())\n            .map(|(a, b)| a.clone() * b)\n            .collect::<TVec<_>>();\n        Ok(tvec!(inputs[0].datum_type.fact(shape)))\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct DynTile {\n    pub multiplier_placeholders: TVec<TDim>,\n}\n\nimpl DynTile {\n    pub fn new(scope: &SymbolScope, rank: usize) -> DynTile {\n        let multiplier_placeholders =\n            (0..rank).map(|_| scope.new_with_prefix(\"_tile_mult_\").to_dim()).collect();\n        DynTile { multiplier_placeholders }\n    }\n}\n\nimpl Op for DynTile {\n    fn name(&self) -> StaticName {\n        \"DynTile\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DynTile {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let multipliers = inputs[1].cast_to::<TDim>()?;\n        let multipliers: TVec<usize> = multipliers\n            .try_as_plain()?\n            .as_slice::<TDim>()?\n            .iter()\n            .map(|m| Ok(m.eval_to_i64(&session.resolved_symbols)? as usize))\n            .collect::<TractResult<_>>()?;\n        let result =\n            dispatch_datum_by_size!(eval_t(inputs[0].datum_type())(&inputs[0], &multipliers))?;\n        Ok(tvec!(result))\n    }\n}\n\nimpl TypedOp for DynTile {\n    as_op!();\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if let Some(mult) = &model.outlet_fact(node.inputs[1])?.konst {\n            let multipliers = mult\n                .cast_to::<TDim>()?\n                .try_as_plain()?\n                .as_slice::<TDim>()?\n                .iter()\n                .cloned()\n                .collect();\n            return TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &node.inputs,\n                Tile { multipliers },\n            )\n            .map(Some);\n        }\n        Ok(None)\n    }\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let multipliers = if let Some(k) = &inputs[1].konst {\n            k.cast_to::<TDim>()?.try_as_plain()?.as_slice::<TDim>()?.iter().cloned().collect()\n        } else {\n            self.multiplier_placeholders.clone()\n        };\n        let shape =\n            inputs[0].shape.iter().zip(multipliers).map(|(a, b)| b * a).collect::<TVec<_>>();\n        Ok(tvec!(inputs[0].datum_type.fact(shape)))\n    }\n}\n\nfn eval_t<T: Datum>(data: &TValue, multipliers: &[usize]) -> TractResult<TValue> {\n    let data_plain = data.try_as_plain()?;\n    let view = unsafe { data_plain.to_array_view_unchecked::<T>() };\n    let output_shape: TVec<usize> =\n        view.shape().iter().zip(multipliers.iter()).map(|(&d, &m)| d * m).collect();\n    let output = ndarray::ArrayD::from_shape_fn(&*output_shape, |coords| {\n        let coords: TVec<usize> =\n            coords.slice().iter().zip(data.shape().iter()).map(|(&x, &d)| x % d).collect();\n        view[&*coords].clone()\n    });\n    let mut output = output.into_tensor();\n    unsafe {\n        output.set_datum_type(data.datum_type());\n    }\n\n    Ok(output.into_tvalue())\n}\n"
  },
  {
    "path": "core/src/ops/array/topk.rs",
    "content": "use std::cmp::Ordering;\n\nuse tract_data::itertools::Itertools;\nuse tract_ndarray::{ArrayViewMutD, Axis, Dimension};\n\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Topk {\n    pub axis: usize,\n    pub largest: bool,\n    pub fallback_k: TDim,\n}\n\nimpl Op for Topk {\n    fn name(&self) -> StaticName {\n        \"Topk\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Topk {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (input, k) = args_2!(inputs);\n        let mut output_shape: TVec<usize> = input.shape().into();\n        let k = k.cast_to_scalar::<i64>()? as usize;\n        output_shape[self.axis] = k;\n        let dt = input.datum_type();\n        let mut output_values = Tensor::zero_dt(dt, &output_shape)?;\n        let mut output_indices = Tensor::zero::<i64>(&output_shape)?;\n        let mut iterating_shape = output_shape.clone();\n        iterating_shape[self.axis] = 1;\n        let mut output_indices_plain = output_indices.try_as_plain_mut()?;\n        let mut output_indices_view = output_indices_plain.to_array_view_mut::<i64>()?;\n        for coords in tract_ndarray::indices(&*iterating_shape) {\n            let mut coords: TVec<usize> = coords.as_array_view().as_slice().unwrap().into();\n            dispatch_numbers!(Self::inner_loop_t(dt)(\n                self,\n                &mut coords,\n                &input,\n                &mut output_values,\n                &mut output_indices_view,\n                k\n            ))?;\n        }\n        Ok(tvec!(output_values.into_tvalue(), output_indices.into_tvalue()))\n    }\n}\n\nimpl Topk {\n    fn inner_loop_t<T: Datum + PartialOrd>(\n        &self,\n        coords: &mut [usize],\n        input: &Tensor,\n        output_values: &mut Tensor,\n        output_indices_view: &mut ArrayViewMutD<i64>,\n        k: usize,\n    ) -> TractResult<()> {\n        let mut output_values_plain = output_values.try_as_plain_mut()?;\n        let mut output_values_view = output_values_plain.to_array_view_mut::<T>()?;\n        let mut view = input.to_plain_array_view::<T>()?;\n        for (ix, x) in coords.iter().enumerate() {\n            if ix != self.axis {\n                view.collapse_axis(Axis(ix), *x);\n            }\n        }\n        for (ix, (argmax, max)) in view\n            .iter()\n            .cloned()\n            .enumerate()\n            .sorted_by(|a, b| {\n                let ord = { a.1.partial_cmp(&b.1).unwrap_or(Ordering::Less) };\n                if self.largest { ord.reverse() } else { ord }\n            })\n            .take(k)\n            .enumerate()\n        {\n            coords[self.axis] = ix;\n            output_values_view[&*coords] = max;\n            output_indices_view[&*coords] = argmax as i64;\n        }\n        Ok(())\n    }\n}\n\nimpl TypedOp for Topk {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact_values = inputs[0].without_value();\n        let mut fact_indices = inputs[0].without_value();\n        let k: TDim = if let Some(k) = &inputs[1].konst {\n            k.cast_to::<TDim>()?.try_as_plain()?.to_scalar::<TDim>()?.clone()\n        } else {\n            self.fallback_k.clone()\n        };\n        fact_values.shape.set(self.axis, k.clone());\n        fact_indices.shape.set(self.axis, k);\n        fact_indices.datum_type = i64::datum_type();\n        Ok(tvec!(fact_values, fact_indices))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/array/trilu.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct Trilu {\n    pub upper: bool,\n}\n\nimpl Op for Trilu {\n    fn name(&self) -> StaticName {\n        \"Trilu\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Trilu {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (input, k) = args_2!(inputs);\n        let mut input = input.into_tensor();\n        let k = *k.try_as_plain()?.to_scalar::<i64>()?;\n        fn eval_t<T: Datum>(tensor: &mut Tensor, upper: bool, k: i64) -> TractResult<()> {\n            let mut tensor_plain = tensor.try_as_plain_mut()?;\n            let mut view = tensor_plain.to_array_view_mut::<T>()?;\n            for coords in tract_ndarray::indices(view.shape()) {\n                let row = coords[view.ndim() - 2] as i64;\n                let col = coords[view.ndim() - 1] as i64;\n                if upper {\n                    if col < row + k {\n                        view[coords] = T::default();\n                    }\n                } else if col > row + k {\n                    view[coords] = T::default();\n                }\n            }\n            Ok(())\n        }\n        dispatch_datum!(eval_t(input.datum_type())(&mut input, self.upper, k))?;\n        Ok(tvec!(input.into_tvalue()))\n    }\n}\n\nimpl TypedOp for Trilu {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].without_value()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/binary.rs",
    "content": "use crate::internal::*;\nuse crate::ndarray::Dimension;\nuse downcast_rs::Downcast;\nuse dyn_eq::DynEq;\nuse std::fmt::{self, Debug};\nuse tract_data::itertools::izip;\nuse tract_itertools::Itertools;\nuse tract_linalg::{BinOp, LinalgFn};\n\nuse super::math::{Add, Max, Min, Mul, Sub};\nuse super::{cast::cast, math::SubF};\n\npub trait BinMiniOp:\n    fmt::Debug + dyn_clone::DynClone + dyn_eq::DynEq + Send + Sync + 'static + Downcast\n{\n    fn name(&self) -> &'static str;\n    fn validation(&self) -> Validation {\n        Validation::Accurate\n    }\n    fn operating_datum_type(&self, a: DatumType, b: DatumType) -> TractResult<DatumType> {\n        a.common_super_type(b).with_context(|| format_err!(\"No super type for {:?} and {:?}\", a, b))\n    }\n    fn result_datum_type(&self, a: DatumType, b: DatumType) -> TractResult<DatumType>;\n    fn eval_in_a(&self, a: &mut Tensor, b: &Tensor) -> TractResult<()>;\n    fn eval_out_of_place(&self, c: &mut Tensor, a: &Tensor, b: &Tensor) -> TractResult<()>;\n\n    fn is_commutative(&self) -> bool {\n        true\n    }\n    fn neutral_element(&self) -> Option<i64> {\n        None\n    }\n    fn absorbing_element(&self) -> Option<i64> {\n        None\n    }\n\n    #[allow(unused_variables)]\n    fn maybe_eval_qbinary_as_float_op(\n        &self,\n        a: &TValue,\n        b: &TValue,\n        c_dt: &DatumType,\n    ) -> TractResult<Option<Tensor>> {\n        Ok(None)\n    }\n\n    fn generic_eval(&self, a: TValue, b: TValue, c_dt: DatumType) -> TractResult<Tensor> {\n        if let Some(tensor) = self.maybe_eval_qbinary_as_float_op(&a, &b, &c_dt)? {\n            Ok(tensor)\n        } else {\n            let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()])?;\n            if &*c_shape == a.shape() && c_dt == a.datum_type() {\n                let mut a = a.into_tensor();\n                self.eval_in_a(&mut a, &b)?;\n                Ok(a)\n            } else {\n                let mut c = unsafe { Tensor::uninitialized_dt(c_dt, &c_shape)? };\n                self.eval_out_of_place(&mut c, &a, &b)?;\n                Ok(c)\n            }\n        }\n    }\n    fn eval(&self, a: TValue, b: TValue, c_dt: DatumType) -> TractResult<Tensor> {\n        self.generic_eval(a, b, c_dt)\n    }\n    #[allow(unused_variables)]\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        Ok(None)\n    }\n    #[allow(unused_variables)]\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        Ok(None)\n    }\n    #[allow(unused_variables)]\n    fn cost_per_element(&self, dt: DatumType) -> TVec<(Cost, usize)> {\n        tvec!()\n    }\n    fn as_linalg_binop(&self) -> Option<tract_linalg::BinOp> {\n        None\n    }\n\n    /// Override for ops that can evaluate symbolic TDim inputs (comparisons).\n    #[allow(unused_variables)]\n    fn eval_symbolic(\n        &self,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<Option<TVec<TValue>>> {\n        Ok(None)\n    }\n\n    /// Override for ops that produce TDim-level comparison expressions (comparisons).\n    #[allow(unused_variables)]\n    fn uniform_tdim_comparison(&self, a: &TDim, b: &TDim) -> Option<TDim> {\n        None\n    }\n}\ndyn_clone::clone_trait_object!(BinMiniOp);\ndyn_eq::eq_trait_object!(BinMiniOp);\ndowncast_rs::impl_downcast!(BinMiniOp);\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct TypedBinOp(pub Box<dyn BinMiniOp>, pub Option<DatumType>);\n\nimpl Op for TypedBinOp {\n    fn name(&self) -> StaticName {\n        self.0.name().into()\n    }\n\n    fn validation(&self) -> Validation {\n        self.0.validation()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl TypedBinOp {\n    fn output_datum_type(&self, a_dt: DatumType, b_dt: DatumType) -> TractResult<DatumType> {\n        if let Some(dt) = self.1 { Ok(dt) } else { self.0.result_datum_type(a_dt, b_dt) }\n    }\n}\n\nimpl EvalOp for TypedBinOp {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        if let Some(result) = self.0.eval_symbolic(session, inputs.clone())? {\n            return Ok(result);\n        }\n        let (a, b) = args_2!(inputs);\n        ensure!(a.rank() == b.rank());\n        let c_dt = self.output_datum_type(a.datum_type(), b.datum_type())?;\n        Ok(tvec!(self.0.eval(a, b, c_dt)?.into_tvalue()))\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (a, b) = args_2!(inputs);\n        ensure!(a.rank() == b.rank());\n        let c_dt = self.output_datum_type(a.datum_type(), b.datum_type())?;\n        Ok(tvec!(self.0.eval(a, b, c_dt)?.into_tvalue()))\n    }\n}\n\nimpl TypedBinOp {\n    fn combine_uniform_tdim(&self, a: &TDim, b: &TDim) -> Option<TDim> {\n        // Comparison ops provide their own TDim combination\n        if let Some(result) = self.0.uniform_tdim_comparison(a, b) {\n            return Some(result);\n        }\n        let a = tensor0(a.clone()).into_tvalue();\n        let b = tensor0(b.clone()).into_tvalue();\n        let result = self.0.eval(a, b, TDim::datum_type()).ok()?;\n        result\n            .try_as_plain()\n            .ok()\n            .and_then(|d| d.as_slice::<TDim>().ok())\n            .and_then(|s| s.first())\n            .cloned()\n            .map(|d| d.reduce())\n    }\n\n    fn combine_uniform_tdim_with_konst(&self, a: &TDim, konst: &Tensor) -> Option<TDim> {\n        if konst.len() != 1 {\n            return None;\n        }\n        // Integer-valued scalar (including float constants like 2.0, 1.0, 3.0)\n        let b_int: Option<i64> =\n            if konst.datum_type().is_integer() || konst.datum_type().is::<bool>() {\n                konst.cast_to_scalar::<i64>().ok()\n            } else if konst.datum_type().is_float() {\n                konst.cast_to_scalar::<f64>().ok().and_then(|f| {\n                    if (f - f.round()).abs() < 1e-6 { Some(f.round() as i64) } else { None }\n                })\n            } else {\n                None\n            };\n        if let Some(b) = b_int {\n            return self.combine_uniform_tdim(a, &TDim::Val(b));\n        }\n        // Mul by reciprocal of integer (e.g. ×0.5 → Div(a, 2))\n        if self.0.neutral_element() == Some(1)\n            && let Some(f) = konst.cast_to_scalar::<f64>().ok().filter(|&f| f > 0.0)\n        {\n            let n = (1.0 / f).round() as u64;\n            if n >= 2 && (f * n as f64 - 1.0).abs() < 1e-6 {\n                return Some(TDim::Div(Box::new(a.clone()), n).reduce());\n            }\n        }\n        None\n    }\n}\n\nimpl TypedOp for TypedBinOp {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        if inputs[0].rank() != inputs[1].rank() {\n            bail!(\n                \"Typed ops require rank match. Invalid inputs for {}: {}\",\n                self.name(),\n                inputs.iter().map(|s| format!(\"{s:?}\")).join(\" ; \")\n            );\n        }\n        let out_dt = self.output_datum_type(inputs[0].datum_type, inputs[1].datum_type)?;\n        let mut fact = out_dt.fact(&*crate::broadcast::multi_broadcast(&[\n            &inputs[0].shape.to_tvec(),\n            &inputs[1].shape.to_tvec(),\n        ])?);\n        if let (Some(a), Some(b)) = (&inputs[0].uniform_tdim, &inputs[1].uniform_tdim) {\n            fact.uniform_tdim = self.combine_uniform_tdim(a, b);\n            // And(a,b) has no TDim kernel; for 0/1 booleans And == Mul\n            if fact.uniform_tdim.is_none() && self.0.is::<crate::ops::logic::And>() {\n                fact.uniform_tdim = Some(TDim::Mul(vec![a.clone(), b.clone()]).reduce());\n            }\n        }\n        // Fallback: one side has uniform_tdim, the other is a scalar constant\n        if fact.uniform_tdim.is_none() {\n            for (expr, konst_fact) in [\n                (inputs[0].uniform_tdim.as_ref(), inputs[1]),\n                (inputs[1].uniform_tdim.as_ref(), inputs[0]),\n            ] {\n                let Some(a) = expr else { continue };\n                let Some(konst) = konst_fact.konst.as_ref() else { continue };\n                fact.uniform_tdim = self.combine_uniform_tdim_with_konst(a, konst);\n                if fact.uniform_tdim.is_some() {\n                    break;\n                }\n            }\n        }\n        Ok(tvec!(fact))\n    }\n\n    fn input_roi(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TVec<Option<TDim>>>> {\n        // Introduction: Mul (or any op with neutral_element=1) with a mask\n        // that has uniform_tdim → the other input gets that expression as ROI.\n        if self.0.neutral_element() == Some(1) {\n            for (mask_ix, other_ix) in [(0usize, 1usize), (1, 0)] {\n                let fact = model.outlet_fact(node.inputs[mask_ix])?;\n                if let Some(mask_expr) = &fact.uniform_tdim {\n                    let mut rois = tvec![None; node.inputs.len()];\n                    rois[other_ix] = Some(mask_expr.clone());\n                    return Ok(Some(rois));\n                }\n            }\n        }\n        // Bubbling: delegate to the natural blanket implementation.\n        crate::optim::propagate_roi::bubble_roi(model, node)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        if let AxisOp::Rm(rm) = change {\n            let (inputs, outputs) = model.node_facts(node.id)?;\n            if inputs.len() >= 2\n                && outputs.len() >= 1\n                && inputs[0].rank() > *rm\n                && inputs[1].rank() > *rm\n                && outputs[0].rank() > *rm\n            {\n                rule_if!(inputs[0].shape[*rm].is_one());\n                rule_if!(inputs[1].shape[*rm].is_one());\n                rule_if!(outputs[0].shape[*rm].is_one());\n            }\n        }\n        Ok(Some(AxisChangeConsequence::new(model, node, None, change)))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let count: TDim = self.output_facts(inputs)?[0].shape.iter().product();\n        Ok(self\n            .0\n            .cost_per_element(inputs[0].datum_type)\n            .into_iter()\n            .map(|(c, n)| (c, count.clone() * n))\n            .collect())\n    }\n\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        _model: &TypedModel,\n        _node: &TypedNode,\n        prefix: &str,\n        inputs: &[OutletId],\n        _output_axis: usize,\n        _start: &TDim,\n        _end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        Ok(Some(patch.wire_node(prefix, self.clone(), inputs)?))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let (a_dt, b_dt) = if let &[a, b] = &*model.node_input_facts(node.id)? {\n            (a.datum_type().unwrap(), b.datum_type().unwrap())\n        } else {\n            unreachable!(\"TypedBinOp has two inputs.\")\n        };\n        if let Some(neutral_patch) =\n            declutter_neutral(model, node, self.0.as_ref(), self.output_datum_type(a_dt, b_dt)?)?\n        {\n            return Ok(Some(neutral_patch));\n        }\n        if let Some(absorbing_patch) = declutter_absorbing(model, node, self.0.as_ref())? {\n            return Ok(Some(absorbing_patch));\n        }\n        if let Some(broadcast_patch) =\n            declutter_broadcasting_operand_1(model, node, self.0.clone())?\n        {\n            return Ok(Some(broadcast_patch));\n        }\n        self.0.declutter(model, node)\n    }\n\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if let Some(linalg_bin_op) = self.0.as_linalg_binop() {\n            let input_facts = model.node_input_facts(node.id)?;\n            let must_swap_inputs =\n                input_facts.iter().collect_tuple().is_some_and(|(a_fact, b_fact)| {\n                    (a_fact.shape.volume() - b_fact.shape.volume()).prove_strict_negative()\n                });\n            let (operand_1, operand_2) = if must_swap_inputs {\n                (input_facts[1], input_facts[0])\n            } else {\n                (input_facts[0], input_facts[1])\n            };\n\n            let (by_scalar_should_be_efficient, unicast_should_be_efficient) =\n                find_most_efficient_config(model, node, must_swap_inputs)?;\n\n            // Check if op is quantized\n            let c_dt = self.output_datum_type(operand_1.datum_type, operand_2.datum_type)?;\n            let op_is_quant = c_dt.is_quantized()\n                || operand_1.datum_type.is_quantized()\n                || operand_2.datum_type.is_quantized();\n\n            // Check if it can be evaluated in a\n            let c_dt = self.output_datum_type(operand_1.datum_type, operand_2.datum_type)?;\n            let c_shape = crate::broadcast::multi_broadcast(&[\n                operand_1.shape.clone(),\n                operand_2.shape.clone(),\n            ])?;\n            let can_eval_in_a =\n                (c_shape.to_vec() == operand_1.shape.to_vec()) && (c_dt == operand_1.datum_type);\n\n            // Swap input if required\n            let inputs = if must_swap_inputs {\n                let mut swap_input = node.inputs.clone();\n                swap_input.swap(0, 1);\n                swap_input\n            } else {\n                node.inputs.clone()\n            };\n            let actual_linalg_op =\n                if must_swap_inputs { linalg_bin_op.flip() } else { linalg_bin_op };\n            let actual_core_op = core_op_for_linalg_op(&actual_linalg_op);\n\n            let dt = model.node_input_facts(node.id)?[0].datum_type;\n            if by_scalar_should_be_efficient & can_eval_in_a & !op_is_quant {\n                rule_if_some!(func = tract_linalg::bin_by_scalar(dt, actual_linalg_op));\n                let eval_fn = Arc::from(func);\n                return Ok(Some(\n                    TypedModelPatch::replace_single_op(\n                        model,\n                        node,\n                        &inputs,\n                        OptBinByScalar { binop: actual_core_op, eval_fn },\n                    )?\n                    .with_context(\"ByScalar\"),\n                ));\n            }\n\n            if unicast_should_be_efficient & can_eval_in_a & !op_is_quant {\n                rule_if_some!(func = tract_linalg::bin_unicast(dt, actual_linalg_op));\n                let eval_fn = Arc::from(func);\n                return Ok(Some(\n                    TypedModelPatch::replace_single_op(\n                        model,\n                        node,\n                        &inputs,\n                        OptBinUnicast { binop: actual_core_op, eval_fn },\n                    )?\n                    .with_context(\"Unicast\"),\n                ));\n            }\n        }\n\n        Ok(None)\n    }\n    as_op!();\n}\n\nfn core_op_for_linalg_op(linalg: &BinOp) -> Box<dyn BinMiniOp> {\n    match linalg {\n        BinOp::Min => Box::new(Min),\n        BinOp::Max => Box::new(Max),\n        BinOp::Add => Box::new(Add),\n        BinOp::Mul => Box::new(Mul),\n        BinOp::Sub => Box::new(Sub),\n        BinOp::SubF => Box::new(SubF),\n    }\n}\nfn declutter_broadcasting_operand_1(\n    model: &TypedModel,\n    node: &TypedNode,\n    mini_op: Box<dyn BinMiniOp>,\n) -> TractResult<Option<TypedModelPatch>> {\n    let (a_shape, b_shape) = if let &[a, b] = &*model.node_input_facts(node.id)? {\n        (a.shape.clone(), b.shape.clone())\n    } else {\n        unreachable!(\"TypedBinOp has two inputs.\")\n    };\n\n    let a_num_elements = a_shape.iter().product::<TDim>();\n    let b_num_elements = b_shape.iter().product::<TDim>();\n    let a_should_be_broadcast = (a_num_elements - b_num_elements).prove_strict_negative();\n    if a_should_be_broadcast & mini_op.is_commutative() {\n        let mut swap_input = node.inputs.clone();\n        swap_input.swap(0, 1);\n        return Ok(Some(TypedModelPatch::replace_single_op(\n            model,\n            node,\n            &swap_input,\n            TypedBinOp(mini_op, None),\n        )?));\n    }\n\n    Ok(None)\n}\n\nfn declutter_neutral(\n    model: &TypedModel,\n    node: &TypedNode,\n    mini_op: &dyn BinMiniOp,\n    out_dt: DatumType,\n) -> TractResult<Option<TypedModelPatch>> {\n    if let Some(uniform) = crate::ops::binary::one_input_is_uniform(model, node)? {\n        let is_neutral = mini_op\n            .neutral_element()\n            .map(|neutral| tensor0(neutral).close_enough(&uniform.uni, false).is_ok())\n            .unwrap_or(false);\n\n        // For some operand neural element can be the left one while for other\n        // it is not the case (neutral - 1 -> not ok, 1 - neutal -> ok)\n        let pos_checked = mini_op.is_commutative() || !uniform.left_is_uniform;\n\n        if is_neutral && pos_checked {\n            // Neutral decluttering for quant values is special.\n            // - if (fa) (a-az)*as + (fb = 0) (b-bz)*bs = (fc) (c-cz)*cs\n            // - then even if fa = fc, quant params needs to be updated (a != c).\n            // So it's not a no_op.\n            if uniform.uni.datum_type().is_quantized() {\n                return Ok(Some(TypedModelPatch::replace_single_op(\n                    model,\n                    node,\n                    &[node.inputs[0]],\n                    cast(out_dt),\n                )?));\n            // In the non quantized case, it's a no_op.\n            } else {\n                return Ok(Some(TypedModelPatch::rewire(\n                    model,\n                    &[uniform.var],\n                    &[node.id.into()],\n                    &|_, inputs| Ok(inputs.into()),\n                )?));\n            }\n        }\n    }\n    Ok(None)\n}\n\n/// When one input is the absorbing element (e.g. 0 for Mul, false for And),\n/// replace the entire op with the uniform (absorbing) input.\nfn declutter_absorbing(\n    model: &TypedModel,\n    node: &TypedNode,\n    mini_op: &dyn BinMiniOp,\n) -> TractResult<Option<TypedModelPatch>> {\n    if let Some(uniform) = crate::ops::binary::one_input_is_uniform(model, node)? {\n        let is_absorbing = mini_op\n            .absorbing_element()\n            .map(|absorb| tensor0(absorb).close_enough(&uniform.uni, false).is_ok())\n            .unwrap_or(false);\n        if is_absorbing {\n            let uni_inlet = if uniform.left_is_uniform { 0 } else { 1 };\n            return Ok(Some(TypedModelPatch::rewire(\n                model,\n                &[node.inputs[uni_inlet]],\n                &[node.id.into()],\n                &|_, inputs| Ok(inputs.into()),\n            )?));\n        }\n    }\n    Ok(None)\n}\n\nfn find_most_efficient_config(\n    model: &TypedModel,\n    node: &TypedNode,\n    swap_input: bool,\n) -> TractResult<(bool, bool)> {\n    if let &[a, b] = &*model.node_input_facts(node.id)? {\n        let a_shape = if swap_input { b.shape.clone() } else { a.shape.clone() };\n        let b_shape = if swap_input { a.shape.clone() } else { b.shape.clone() };\n\n        let by_scalar_is_possible = OptBinByScalar::check_input_shapes(&a_shape, &b_shape);\n        let num_by_scalar_elements = if by_scalar_is_possible {\n            a_shape\n                .iter()\n                .zip(b_shape.iter())\n                .rev()\n                .take_while(|(_, rev_b_dim)| **rev_b_dim == TDim::Val(1))\n                .map(|(rev_a_dim, _)| rev_a_dim)\n                .product::<TDim>()\n        } else {\n            TDim::Val(0)\n        };\n\n        let unicast_is_possible = OptBinUnicast::check_input_shapes(&a_shape, &b_shape);\n        let num_unicast_elements = if unicast_is_possible {\n            a_shape\n                .iter()\n                .zip(b_shape.iter())\n                .rev()\n                .take_while(|(a_dim, b_dim)| a_dim == b_dim)\n                .map(|(a_dim, _)| a_dim)\n                .product::<TDim>()\n        } else {\n            TDim::Val(0)\n        };\n\n        let min_num_elements = 32;\n        let by_scalar_should_be_efficient = gt_tdim(num_by_scalar_elements, min_num_elements);\n        let unicast_should_be_efficient = gt_tdim(num_unicast_elements, min_num_elements);\n        return Ok((by_scalar_should_be_efficient, unicast_should_be_efficient));\n    }\n    Ok((false, false))\n}\n\npub fn gt_tdim(x: TDim, min_val: i64) -> bool {\n    TDim::Val(min_val).mini(x).to_i64().is_ok_and(|v| v == min_val)\n}\n\n#[derive(Clone)]\npub struct OptBinByScalar {\n    pub binop: Box<dyn BinMiniOp>,\n    eval_fn: Arc<LinalgFn>,\n}\n\nimpl Debug for OptBinByScalar {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {\n        f.debug_struct(\"OptBinByScalar\").field(\"binop\", &self.binop).finish()\n    }\n}\n\nimpl OptBinByScalar {\n    fn check_input_shapes(a_shape: &[TDim], b_shape: &[TDim]) -> bool {\n        if a_shape.len() != b_shape.len() {\n            return false;\n        };\n\n        a_shape\n            .iter()\n            .zip(b_shape.iter())\n            .skip_while(|(a_dim, b_dim)| a_dim == b_dim)\n            .all(|(_, b_dim)| *b_dim == 1.to_dim())\n    }\n}\n\nimpl PartialEq for OptBinByScalar {\n    fn eq(&self, other: &Self) -> bool {\n        *self.binop == *other.binop\n    }\n}\nimpl Eq for OptBinByScalar {}\n\nimpl Op for OptBinByScalar {\n    fn name(&self) -> StaticName {\n        format!(\"Opt{}ByScalar\", self.binop.name()).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for OptBinByScalar {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (a, b) = args_2!(inputs);\n        // Not a requirement as TensorView doesn't require a owned tensor but in reality\n        // \"a \"should be mutable (it's omitted here as Rust compiler advise to remove it)\n        let a = a.into_tensor();\n        let b_shape = b.shape();\n\n        let first_unary_axis = b_shape\n            .iter()\n            .enumerate()\n            .rev()\n            .take_while(|&(_, &dim)| dim == 1)\n            .map(|(i, _)| i)\n            .last()\n            .context(\"Cannot use by_scalar when no trailing dimensions are unary\")?;\n\n        let iterating_shape = &a.shape()[..first_unary_axis];\n        if !iterating_shape.is_empty() {\n            for it_coords in tract_ndarray::indices(iterating_shape) {\n                let mut view = TensorView::at_prefix(&a, it_coords.slice())?;\n                let b_view = TensorView::at_prefix(&b, it_coords.slice())?;\n                debug_assert_eq!(b_view.shape().iter().product::<usize>(), 1);\n                (self.eval_fn)(&mut view, &b_view)?;\n            }\n        } else {\n            let mut view = a.view();\n            let b_view = b.view();\n            debug_assert_eq!(b_view.shape().iter().product::<usize>(), 1);\n            (self.eval_fn)(&mut view, &b_view)?;\n        }\n        Ok(tvec!(a.into_tvalue()))\n    }\n}\n\nimpl TypedOp for OptBinByScalar {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(Self::check_input_shapes(&inputs[0].shape, &inputs[1].shape));\n        let out_dt = self.binop.result_datum_type(inputs[0].datum_type, inputs[1].datum_type)?;\n        let out_shape = inputs[0].shape.clone();\n        Ok(tvec!(out_dt.fact(out_shape)))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let count: TDim = self.output_facts(inputs)?[0].shape.iter().product();\n        Ok(self\n            .binop\n            .cost_per_element(inputs[0].datum_type)\n            .into_iter()\n            .map(|(c, n)| (c, count.clone() * n))\n            .collect())\n    }\n\n    as_op!();\n}\n\n#[derive(Clone)]\npub struct OptBinUnicast {\n    pub binop: Box<dyn BinMiniOp>,\n    eval_fn: Arc<LinalgFn>,\n}\n\nimpl Debug for OptBinUnicast {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {\n        f.debug_struct(\"OptBinUnicast\").field(\"binop\", &self.binop).finish()\n    }\n}\n\nimpl OptBinUnicast {\n    fn check_b_alignement(a_shape: &[TDim], b_shape: &[TDim]) -> bool {\n        let num_iterations: TDim = a_shape\n            .iter()\n            .zip(b_shape.iter())\n            .take_while(|(_, b_dim)| **b_dim == 1.to_dim())\n            .map(|(a_dim, _)| a_dim)\n            .product();\n\n        if num_iterations.is_one() {\n            return true;\n        }\n\n        let elements_per_iteration: TDim = a_shape\n            .iter()\n            .zip(b_shape.iter())\n            .skip_while(|(_, b_dim)| **b_dim == 1.to_dim())\n            .map(|(_, b_dim)| b_dim)\n            .product();\n\n        if let Ok(num_element) = elements_per_iteration.to_i64() {\n            let required_alignment = vector_size();\n            (num_element as usize).is_multiple_of(required_alignment)\n        } else {\n            false\n        }\n    }\n    fn check_input_shapes(a_shape: &[TDim], b_shape: &[TDim]) -> bool {\n        if a_shape.len() != b_shape.len() {\n            return false;\n        };\n\n        let unicast_possible = a_shape\n            .iter()\n            .zip(b_shape.iter())\n            .skip_while(|(_, b_dim)| **b_dim == 1.to_dim())\n            .all(|(a_dim, b_dim)| a_dim == b_dim);\n        let unicast_is_aligned = Self::check_b_alignement(a_shape, b_shape);\n\n        unicast_possible && unicast_is_aligned\n    }\n}\n\nimpl PartialEq for OptBinUnicast {\n    fn eq(&self, other: &Self) -> bool {\n        *self.binop == *other.binop\n    }\n}\nimpl Eq for OptBinUnicast {}\n\nimpl Op for OptBinUnicast {\n    fn name(&self) -> StaticName {\n        format!(\"Opt{}Unicast\", self.binop.name()).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for OptBinUnicast {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (a, b) = args_2!(inputs);\n        // Not a requirement as TensorView doesn't require a owned tensor but in reality\n        // \"a \"should be mutable (it's omitted here as Rust compiler advise to remove it)\n        let a = a.into_tensor();\n        let b_shape = b.shape();\n        let b_view = b.view();\n        let first_non_unary_axis =\n            b_shape.iter().enumerate().take_while(|&(_, &dim)| dim == 1).map(|(i, _)| i + 1).last();\n\n        if let Some(first_non_unary_axis) = first_non_unary_axis {\n            // Iterate on outter dimensions and evaluate with unicast subviews\n            let iterating_shape = a.shape()[..first_non_unary_axis].to_vec();\n            for it_coords in tract_ndarray::indices(iterating_shape) {\n                let mut view = TensorView::at_prefix(&a, it_coords.slice())?;\n                debug_assert_eq!(view.shape(), &b_view.shape()[it_coords.slice().len()..]);\n                (self.eval_fn)(&mut view, &b_view)?;\n            }\n        } else {\n            let mut view = a.view();\n            debug_assert_eq!(view.shape(), b_view.shape());\n            (self.eval_fn)(&mut view, &b_view)?;\n        }\n\n        Ok(tvec!(a.into_tvalue()))\n    }\n}\n\nimpl TypedOp for OptBinUnicast {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(Self::check_input_shapes(&inputs[0].shape, &inputs[1].shape));\n        let out_dt = self.binop.result_datum_type(inputs[0].datum_type, inputs[1].datum_type)?;\n        let out_shape = inputs[0].shape.clone();\n        Ok(tvec!(out_dt.fact(out_shape)))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let count: TDim = self.output_facts(inputs)?[0].shape.iter().product();\n        Ok(self\n            .binop\n            .cost_per_element(inputs[0].datum_type)\n            .into_iter()\n            .map(|(c, n)| (c, count.clone() * n))\n            .collect())\n    }\n\n    as_op!();\n}\n\n#[macro_export]\nmacro_rules! bin_to_super_type {\n    ($func:ident, $Op:ident,\n     $(codegen: $codegen:expr,)?\n     $(cost: $cost:expr,)?\n     $(declutter: $declutter:expr,)?\n     $(eval_in_a: $eval_in_a:expr,)?\n     $(eval_override: $eval_override: expr,)?\n     $(linalg: $linalg:ident,)?\n     $(operating_datum_type: $operating_datum_type:expr,)?\n     $(is_commutative: $is_commutative:expr,)?\n     $(neutral_element: $neutral_element:expr,)?\n     $(absorbing_element: $absorbing_element:expr,)?\n     $(out_of_place: $out_of_place:expr,)?\n     $(validation: $validation:expr,)?\n     $(q: $([$($typ_dt:ident),*] => $cab_dt:expr),* ;)?\n     $(q_op_on_f32: $q_op_on_f32:expr,)?\n     $( [$($typ:ident),*] => $cab:expr),*) => {\n        #[derive(Debug, Clone, Hash, PartialEq, Eq)]\n        pub struct $Op;\n        #[allow(clippy::redundant_closure_call)]\n        impl $crate::ops::binary::BinMiniOp for $Op {\n            fn name(&self) -> &'static str {\n                stringify!($Op)\n            }\n\n            fn eval_out_of_place(&self, c: &mut Tensor, a: &Tensor, b: &Tensor) -> TractResult<()> {\n                $(if $out_of_place(c, a, b)? { return Ok(()) } )?\n                    $(\n                        $(if c.datum_type() == $typ::datum_type() {\n                            let a = a.to_plain_array_view::<$typ>()?;\n                            let b = b.to_plain_array_view::<$typ>()?;\n                            let mut c_plain = c.try_as_plain_mut()?;\n                            let mut c = c_plain.to_array_view_mut::<$typ>()?;\n                            $crate::ndarray::Zip::from(&mut c).and_broadcast(a).and_broadcast(b).for_each($cab);\n                            return Ok(())\n                        })*\n                     )*\n                    $(\n                        $(\n                            $(if a.datum_type().unquantized() == <$typ_dt>::datum_type().unquantized() {\n                                let cab: fn(&mut $typ_dt, &$typ_dt, &$typ_dt, i32, f32) -> () = $cab_dt;\n                                let (zp, scale) = a.datum_type().qparams().map(|q| q.zp_scale()).unwrap_or((0, 1.));\n                                let a = a.to_plain_array_view::<$typ_dt>()?;\n                                let b = b.to_plain_array_view::<$typ_dt>()?;\n                                let mut c_plain = c.try_as_plain_mut()?;\n                                let mut c = c_plain.to_array_view_mut::<$typ_dt>()?;\n                                $crate::ndarray::Zip::from(&mut c).and_broadcast(a).and_broadcast(b).for_each(|c, a, b| cab(c, a, b, zp, scale));\n                                return Ok(())\n                            }\n                            )*\n                         )*\n                     )?\n                    bail!(\"{} does not support {:?} (out of place)\", self.name(), c.datum_type());\n            }\n\n            $(fn is_commutative(&self) -> bool {\n                $is_commutative\n            })?\n            $(fn neutral_element(&self) -> Option<i64> {\n                Some($neutral_element)\n            })?\n            $(fn absorbing_element(&self) -> Option<i64> {\n                Some($absorbing_element)\n            })?\n            fn eval_in_a(&self, a: &mut Tensor, b: &Tensor) -> TractResult<()> {\n                // c and a are same type\n                $(if $eval_in_a(a, b)? { return Ok(()) } )?\n                $(\n                    $(if b.datum_type() == $typ::datum_type() {\n                        let cab: fn(&mut $typ, &$typ, &$typ) -> () = $cab;\n                        let b = b.to_plain_array_view::<$typ>()?;\n                        let mut a_plain = a.try_as_plain_mut()?;\n                        let mut a = a_plain.to_array_view_mut::<$typ>()?;\n                        $crate::ndarray::Zip::from(&mut a).and_broadcast(b).for_each(|a, b| cab(a, &a.clone(), b));\n                        return Ok(())\n                    })*\n                )*\n                $(\n                    $(\n                        $(if a.datum_type().unquantized() == <$typ_dt>::datum_type().unquantized() {\n                            let cab: fn(&mut $typ_dt, &$typ_dt, &$typ_dt, i32, f32) -> () = $cab_dt;\n                            let (zp, scale) = a.datum_type().qparams().map(|q| q.zp_scale()).unwrap_or((0, 1.));\n                            let mut a_plain = a.try_as_plain_mut()?;\n                            let mut a = a_plain.to_array_view_mut::<$typ_dt>()?;\n                            let b = b.to_plain_array_view::<$typ_dt>()?;\n                            $crate::ndarray::Zip::from(&mut a).and_broadcast(b).for_each(|a, b| {\n                                cab(a, &(a.clone()), b, zp, scale)\n                            });\n                            return Ok(())\n                        })*\n                    )*\n                )?\n                bail!(\"{} does not support {:?} (eval in a)\", self.name(), a.datum_type());\n            }\n\n            $(fn eval(&self, a: TValue, b: TValue, c_dt: DatumType) -> TractResult<Tensor> {\n                $eval_override(a, b, c_dt)\n            })?\n\n            fn result_datum_type(&self, a: DatumType, b: DatumType) -> TractResult<DatumType> {\n                if a.unquantized() == b.unquantized() {\n                    if a.is_quantized() || !b.is_quantized() {\n                        return Ok(a)\n                    }\n                    else {\n                        return Ok(b)\n                    }\n                }\n                self.operating_datum_type(a, b)\n            }\n\n                $(\n                    fn declutter(\n                        &self,\n                        model: &TypedModel,\n                        node: &TypedNode,\n                        ) -> TractResult<Option<TypedModelPatch>> {\n                        ($declutter)(self, model, node)\n                    }\n                 )?\n                $(\n                    fn codegen(\n                        &self,\n                        model: &TypedModel,\n                        node: &TypedNode,\n                        a: &Arc<Tensor>,\n                        ) -> TractResult<Option<TypedModelPatch>> {\n                        ($codegen)(self, model, node, a)\n                    }\n                 )?\n                $(\n                    fn cost_per_element(&self, dt: DatumType) -> TVec<(Cost, usize)> {\n                        ($cost)(dt)\n                    }\n                 )?\n                $(\n                    fn validation(&self) -> Validation {\n                        $validation\n                    }\n                 )?\n                $(\n                    fn as_linalg_binop(&self) -> Option<tract_linalg::BinOp> {\n                        Some(tract_linalg::BinOp::$linalg)\n                    }\n                 )?\n                $(\n                    fn operating_datum_type(&self, a: DatumType, b: DatumType) -> TractResult<DatumType> {\n                        ($operating_datum_type)(a, b)\n                    })?\n\n\n            /// Default simple binary operation for QFormat where\n            /// we dequantise & apply requested operation in float & requantize it\n            /// several implementation are provided with pro & con\n            #[allow(unused_variables)]\n            fn maybe_eval_qbinary_as_float_op(\n                &self,\n                a: &TValue,\n                b: &TValue,\n                c_dt: &DatumType,\n            ) -> TractResult<Option<Tensor>> {\n                $(\n                    /// Implementation strive to minimise memory allocation and access\n                    /// we apply only if type is QU8 zp_scale datum type\n                    /// maybe more suited for large models tensors\n                    fn memory_optimised_q_binary_as_float_op(\n                        a: &TValue,\n                        b: &TValue,\n                        c_dt: &DatumType,\n                    ) -> TractResult<Option<Tensor>> {\n                        if let (DatumType::QU8(QParams::ZpScale {zero_point: a_zp, scale: a_scale}),\n                                DatumType::QU8(QParams::ZpScale {zero_point: b_zp, scale: b_scale}),\n                                DatumType::QU8(QParams::ZpScale {zero_point: c_zp, scale: c_scale})) =\n                            (a.datum_type(), b.datum_type(), c_dt)\n                        {\n                            let c_inv_scale = 1.0 / c_scale;\n                            let a = a.to_plain_array_view::<u8>()?;\n                            let b = b.to_plain_array_view::<u8>()?;\n                            let c_shape = $crate::broadcast::multi_broadcast(&[a.shape(), b.shape()])?;\n                            let mut c = Tensor::zero_dt(*c_dt, &c_shape)?;\n                            let mut c_plain = c.try_as_plain_mut()?;\n                            let view = c_plain.to_array_view_mut::<u8>()?;\n                            $crate::ndarray::Zip::from(view).and_broadcast(a).and_broadcast(b).for_each(|c, a, b| {\n                                *c = (scale_by($q_op_on_f32(\n                                            ((*a as i32 - a_zp as i32) as f32 * a_scale),\n                                            ((*b as i32 - b_zp as i32) as f32 * b_scale),\n                                ), c_inv_scale) as i32\n                                    + *c_zp as i32)\n                                    .clamp_cast()\n                            });\n                            return Ok(Some(c));\n                        }\n                        Ok(None)\n                    }\n\n                    /// Apply to all Q types\n                    /// Take more memory but hopefully faster than memory_optimised_q_binary_as_float_op\n                    /// especially once cast_to_dt will have will have vectorized implementations\n                    fn generic_q_binary_as_float_op(\n                        a: &TValue,\n                        b: &TValue,\n                        c_dt: &DatumType,\n                        accumulator_dt: DatumType\n                    ) -> TractResult<Option<Tensor>> {\n                        if a.datum_type().is_quantized() && b.datum_type().is_quantized() && c_dt.is_quantized() {\n                            let a = a.cast_to_dt(accumulator_dt)?.into_owned();\n                            let b = b.cast_to_dt(accumulator_dt)?.into_owned();\n                            let c_shape = $crate::broadcast::multi_broadcast(&[a.shape(), b.shape()])?;\n                            let mut c = Tensor::zero_dt(accumulator_dt, &c_shape)?;\n                            match accumulator_dt {\n                                DatumType::F32 => {\n                                    let mut c_plain = c.try_as_plain_mut()?;\n                                    let view = c_plain.to_array_view_mut::<f32>()?;\n                                    $crate::ndarray::Zip::from(view).and_broadcast(a.try_as_plain()?.to_array_view()?).and_broadcast(b.try_as_plain()?.to_array_view()?).for_each(|c, a, b| {\n                                        *c = $q_op_on_f32(*a,*b);\n                                    })\n                                },\n                                other => bail!(\"unexpected accumulator data type as {:?}\", other)\n                            };\n\n                            return Ok(Some(c.cast_to_dt(*c_dt)?.into_owned()));\n                        }\n                        Ok(None)\n                    }\n\n                    if let Some(c) = memory_optimised_q_binary_as_float_op(a, b, c_dt)? {\n                        return Ok(Some(c));\n                    }\n                    if let Some(d) = generic_q_binary_as_float_op(a, b, c_dt, DatumType::F32)? {\n                        return Ok(Some(d));\n                    }\n                )?\n                Ok(None)\n            }\n        }\n\n        pub fn $func() -> $crate::ops::binary::TypedBinOp {\n            $crate::ops::binary::TypedBinOp(Box::new($Op), None)\n        }\n    };\n}\n\n#[derive(Debug)]\npub(crate) struct OneUniformInput {\n    pub uni: Arc<Tensor>,\n    pub var: OutletId,\n    pub left_is_uniform: bool,\n}\n\npub(crate) fn one_input_is_uniform(\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<OneUniformInput>> {\n    if let &[a, b] = &*model.node_input_facts(node.id)? {\n        let uni = if let Some(a) = &a.uniform {\n            OneUniformInput { uni: a.clone(), var: node.inputs[1], left_is_uniform: true }\n        } else if let Some(b) = &b.uniform {\n            OneUniformInput { uni: b.clone(), var: node.inputs[0], left_is_uniform: false }\n        } else {\n            return Ok(None);\n        };\n        let var_fact = [a, b][uni.left_is_uniform as usize];\n        let uni_fact = [a, b][!uni.left_is_uniform as usize];\n        if izip!(var_fact.shape.iter(), uni_fact.shape.iter()).all(|(v, u)| u.is_one() || u == v) {\n            return Ok(Some(uni));\n        }\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "core/src/ops/cast.rs",
    "content": "use crate::internal::*;\n\npub fn cast(to: DatumType) -> Cast {\n    Cast { to }\n}\n\npub fn wire_cast(\n    prefix: impl AsRef<str>,\n    target: &mut TypedModel,\n    inputs: &[OutletId],\n    operating_datum_type: DatumType,\n) -> TractResult<TVec<OutletId>> {\n    let prefix = prefix.as_ref();\n    let mut wires = tvec!();\n    for mut wire in inputs.iter().copied() {\n        if target.outlet_fact(wire)?.datum_type != operating_datum_type {\n            wire = target.wire_node(\n                target.unique_name(format!(\"{prefix}.cast\")),\n                crate::ops::cast::cast(operating_datum_type),\n                &[wire],\n            )?[0];\n        }\n        wires.push(wire);\n    }\n    Ok(wires)\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Cast {\n    pub to: DatumType,\n}\n\nimpl Op for Cast {\n    fn name(&self) -> StaticName {\n        \"Cast\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Cast {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        state: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        if input.datum_type() == self.to {\n            Ok(tvec!(input))\n        } else if input.datum_type() == TDim::datum_type() {\n            let mut tmp = Tensor::zero_dt(i64::datum_type(), input.shape())?;\n            let input_plain = input.try_as_plain()?;\n            let mut tmp_plain = tmp.try_as_plain_mut()?;\n            for (dim, i) in tract_itertools::izip!(\n                input_plain.as_slice::<TDim>()?,\n                tmp_plain.as_slice_mut::<i64>()?\n            ) {\n                *i = dim.eval(&state.resolved_symbols).to_i64()?\n            }\n            Ok(tvec!(tmp.cast_to_dt(self.to)?.into_owned().into_tvalue()))\n        } else {\n            Ok(tvec!(input.cast_to_dt(self.to)?.into_owned().into_tvalue()))\n        }\n    }\n}\n\nimpl TypedOp for Cast {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact = self.to.fact(inputs[0].shape.clone());\n        fact.uniform_tdim = inputs[0].uniform_tdim.clone();\n        if let Some(u) = &inputs[0].uniform {\n            if let Ok(cast_u) = u.cast_to_dt(self.to) {\n                fact.uniform = Some(std::sync::Arc::new(cast_u.into_owned()));\n            }\n        }\n        Ok(tvec!(fact))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if model.outlet_fact(node.inputs[0])?.datum_type == self.to {\n            TypedModelPatch::shunt_one_op(model, node)\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        Ok(Some(AxisChangeConsequence::new(model, node, None, change)))\n    }\n\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        _model: &TypedModel,\n        node: &TypedNode,\n        _prefix: &str,\n        inputs: &[OutletId],\n        _output_axis: usize,\n        _start: &TDim,\n        _end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        patch.wire_node(&node.name, &node.op, inputs).map(Some)\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/change_axes.rs",
    "content": "use std::borrow::Borrow;\nuse std::fmt::Debug;\n\nuse crate::internal::*;\nuse crate::model::{TypedModel, TypedNode};\nuse crate::ops::identity::Identity;\nuse AxisOp::*;\nuse num_traits::One;\nuse tract_itertools::Itertools;\nuse tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage};\nuse tract_ndarray::{ArrayViewD, ArrayViewMutD};\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\npub enum InOut {\n    Out(usize),\n    In(usize),\n}\n\nimpl InOut {\n    pub fn as_outlet<F: Clone + Fact, O: Clone>(&self, node: &Node<F, O>) -> OutletId {\n        match self {\n            InOut::In(ix) => node.inputs[*ix],\n            InOut::Out(ix) => OutletId::new(node.id, *ix),\n        }\n    }\n\n    pub fn is_input(&self) -> bool {\n        matches!(self, InOut::In(_))\n    }\n\n    pub fn is_output(&self) -> bool {\n        matches!(self, InOut::Out(_))\n    }\n\n    pub fn slot(&self) -> usize {\n        match self {\n            InOut::Out(o) => *o,\n            InOut::In(i) => *i,\n        }\n    }\n}\n\n#[derive(Clone, Hash, Eq)]\n#[allow(clippy::large_enum_variant)] // FIXME ?\n#[allow(clippy::derived_hash_with_manual_eq)] // FIXME. this one may be pretty bad. how about a.canonical() == b.canonical() ? need proper canonicalizeation of Reshape\npub enum AxisOp {\n    Add(usize),\n    Rm(usize),\n    Move(usize, usize),\n    Reshape(usize, TVec<TDim>, TVec<TDim>),\n}\n\nimpl Debug for AxisOp {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            AxisOp::Add(a) => write!(f, \"Add({a})\"),\n            AxisOp::Rm(a) => write!(f, \"Rm({a})\"),\n            AxisOp::Move(from, to) => write!(f, \"Move({from},{to})\"),\n            AxisOp::Reshape(at, from, to) => {\n                write!(f, \"Reshape({at}, [{}], [{}])\", from.iter().join(\",\"), to.iter().join(\",\"))\n            }\n        }\n    }\n}\n\nimpl PartialEq for AxisOp {\n    fn eq(&self, other: &AxisOp) -> bool {\n        if self.is_noop() && other.is_noop() {\n            true\n        } else if self.is_noop() != other.is_noop() {\n            false\n        } else {\n            match (self, other) {\n                (Add(a), Add(b)) | (Rm(a), Rm(b)) => a == b,\n                (Move(f1, t1), Move(f2, t2)) => {\n                    (f1 == f2 && t1 == t2)\n                        || ((*t1 == f1 + 1 || *f1 == t1 + 1) && t2 == f1 && t1 == f2)\n                }\n                (Reshape(at1, f1, t1), Reshape(at2, f2, t2)) => at1 == at2 && f1 == f2 && t1 == t2,\n                _ => false,\n            }\n        }\n    }\n}\n\nimpl AxisOp {\n    pub fn canonical(&self) -> Cow<'_, AxisOp> {\n        match self {\n            Move(from, to) if *from == to + 1 => Cow::Owned(Move(*to, *from)),\n            Reshape(at, from, to)\n                if from.len() == 1 && to.len() == 2 && from[0] == to[0] && to[1].is_one() =>\n            {\n                Cow::Owned(Add(*at + 1))\n            }\n            Reshape(at, from, to)\n                if from.len() == 1 && to.len() == 2 && from[0] == to[1] && to[0].is_one() =>\n            {\n                Cow::Owned(Add(*at))\n            }\n            Reshape(at, from, to)\n                if from.len() == 2 && to.len() == 1 && from[0] == to[0] && from[1].is_one() =>\n            {\n                Cow::Owned(Rm(*at + 1))\n            }\n            Reshape(at, from, to)\n                if from.len() == 2 && to.len() == 1 && from[1] == to[0] && from[0].is_one() =>\n            {\n                Cow::Owned(Rm(*at))\n            }\n            other => Cow::Borrowed(other),\n        }\n    }\n\n    pub fn simplify(&self) -> TVec<AxisOp> {\n        match self.canonical().borrow() {\n            Reshape(_, from, to) if from == to => tvec!(),\n            Reshape(at, from, to) if to.len() == 0 => tvec!(Rm(*at); from.len()),\n            Reshape(at, from, to) if from.len() == 0 => tvec!(Add(*at); to.len()),\n            Reshape(at, from, to) if from[0] == to[0] => {\n                Reshape(at + 1, from[1..].into(), to[1..].into()).simplify()\n            }\n            Reshape(at, from, to) if from[from.len() - 1] == to[to.len() - 1] => {\n                Reshape(*at, from[..from.len() - 1].into(), to[..to.len() - 1].into()).simplify()\n            }\n            Reshape(at, from, to) if from[0] == 1.to_dim() => std::iter::once(Rm(*at))\n                .chain(Reshape(*at, from[1..].into(), to.clone()).simplify())\n                .collect(),\n            Reshape(at, from, to) if to[0] == 1.to_dim() => {\n                Reshape(*at, from.clone(), to[1..].into())\n                    .simplify()\n                    .into_iter()\n                    .chain(std::iter::once(Add(*at)))\n                    .collect()\n            }\n            Reshape(at, from, to) if from[from.len() - 1] == 1.to_dim() => {\n                std::iter::once(Rm(at + from.len() - 1))\n                    .chain(Reshape(*at, from[..from.len() - 1].into(), to.clone()).simplify())\n                    .collect()\n            }\n            Reshape(at, from, to) if to[to.len() - 1] == 1.to_dim() => {\n                std::iter::once(Add(at + from.len()))\n                    .chain(Reshape(*at, from.clone(), to[..to.len() - 1].into()).simplify())\n                    .collect()\n            }\n            other => tvec!(other.clone()),\n        }\n    }\n\n    pub fn transform_axis(&self, axis: usize) -> Option<usize> {\n        match self.canonical().as_ref() {\n            Add(ix) => Some(axis + (axis >= *ix) as usize),\n            Rm(ix) => {\n                if axis == *ix {\n                    None\n                } else {\n                    Some(axis - (axis > *ix) as usize)\n                }\n            }\n            Move(from, to) if from < to => {\n                if axis < *from || axis > *to {\n                    Some(axis)\n                } else if axis == *from {\n                    Some(*to)\n                } else {\n                    Some(axis - 1)\n                }\n            }\n            Move(from, to) => {\n                if axis < *to || axis > *from {\n                    Some(axis)\n                } else if axis == *from {\n                    Some(*to)\n                } else {\n                    Some(axis + 1)\n                }\n            }\n            Reshape(at, _, _) if axis < *at => Some(axis),\n            Reshape(at, from, to) if axis >= at + from.len() => Some(axis + to.len() - from.len()),\n            Reshape(_, _, _) => None,\n        }\n    }\n\n    // if sucessful return Some()\n    // first item is the Op we want to be replaced by. if none, we are now identity.\n    // second item is the change to propagate. if none, the output is not\n    // changed\n    pub fn merge_incoming_change(\n        &self,\n        change: &AxisOp,\n    ) -> Option<(Option<AxisOp>, Option<AxisOp>)> {\n        match (self.canonical().as_ref(), change.canonical().as_ref()) {\n            (Add(op), Add(c)) => {\n                Some((Some(Add(op + (c < op) as usize)), Some(Add(c + (c >= op) as usize))))\n            }\n            (Add(op), Rm(c)) => {\n                Some((Some(Add(op - (c < op) as usize)), Some(Rm(c + (c >= op) as usize))))\n            }\n            (Rm(op), Add(c)) => {\n                Some((Some(Rm(op + (c <= op) as usize)), Some(Add(c - (op < c) as usize))))\n            }\n            (Rm(op), Rm(c)) => {\n                Some((Some(Rm(op - (c < op) as usize)), Some(Rm(c - (op <= c) as usize))))\n            }\n\n            (Add(x), Move(from, to)) => {\n                if x <= from.min(to) {\n                    Some((Some(self.clone()), Some(Move(from + 1, to + 1))))\n                } else if x > from.max(to) {\n                    Some((Some(self.clone()), Some(change.clone())))\n                } else {\n                    None\n                }\n            }\n\n            (Move(from, to), Add(x)) => {\n                if x <= from.min(to) {\n                    Some((Some(Move(from + 1, to + 1)), Some(Add(*x))))\n                } else if x > from.max(to) {\n                    Some((Some(Move(*from, *to)), Some(Add(*x))))\n                } else {\n                    None\n                }\n            }\n\n            (Rm(x), Move(from, to)) => {\n                if x == from {\n                    Some((Some(Rm(*to)), None))\n                } else if x < from.min(to) {\n                    Some((Some(self.clone()), Some(Move(from - 1, to - 1))))\n                } else if x > from.max(to) {\n                    Some((Some(self.clone()), Some(change.clone())))\n                } else if from + 1 == *to && x == to {\n                    Some((Some(Rm(*from)), None))\n                } else if from < to && x <= to {\n                    Some((Some(Rm(x - 1)), Some(Move(*from, *to - 1))))\n                } else {\n                    Some((Some(Rm(x + 1)), Some(Move(*from - 1, *to))))\n                }\n            }\n\n            (Move(from, to), Rm(x)) => {\n                if x < from.min(to) {\n                    Some((Some(Move(from - 1, to - 1)), Some(Rm(*x))))\n                } else if x > from.max(to) {\n                    Some((Some(Move(*from, *to)), Some(Rm(*x))))\n                } else {\n                    None\n                }\n            }\n\n            (Add(op), Reshape(at, from, to)) => {\n                if op <= at {\n                    Some((Some(Add(*op)), Some(Reshape(at + 1, from.clone(), to.clone()))))\n                } else if *op > at + from.len() {\n                    Some((\n                        Some(Add(*op + to.len() - from.len())),\n                        Some(Reshape(*at, from.clone(), to.clone())),\n                    ))\n                } else {\n                    None\n                }\n            }\n            (Rm(op), Reshape(at, from, to)) => {\n                if op < at {\n                    Some((Some(Rm(*op)), Some(Reshape(at - 1, from.clone(), to.clone()))))\n                } else if *op > at + from.len() {\n                    Some((\n                        Some(Rm(*op + to.len() - from.len())),\n                        Some(Reshape(*at, from.clone(), to.clone())),\n                    ))\n                } else {\n                    None\n                }\n            }\n            (Reshape(at, from, to), Add(change)) => {\n                if change < at {\n                    Some((Some(Reshape(at + 1, from.clone(), to.clone())), Some(Add(*change))))\n                } else if *change > *at + from.len() {\n                    Some((\n                        Some(Reshape(*at, from.clone(), to.clone())),\n                        Some(Add(change + to.len() - from.len())),\n                    ))\n                } else {\n                    None\n                }\n            }\n            (Reshape(at, from, to), Rm(change)) => {\n                if change < at {\n                    Some((Some(Reshape(at - 1, from.clone(), to.clone())), Some(Rm(*change))))\n                } else if *change > *at + from.len() {\n                    Some((\n                        Some(Reshape(*at, from.clone(), to.clone())),\n                        Some(Rm(change + to.len() - from.len())),\n                    ))\n                } else {\n                    None\n                }\n            }\n            (Reshape(_, _, _), Move(_, _)) => None, // todo, some are manageable\n            (Move(_, _), Reshape(_, _, _)) => None, // todo, some are manageable\n            (Reshape(_, _, _), Reshape(_, _, _)) => None, // todo, some are manageable\n            _ => None,\n        }\n    }\n\n    pub fn change_shape_array<D: DimLike>(\n        &self,\n        shape: &mut TVec<D>,\n        broadcasting: bool,\n    ) -> TractResult<()> {\n        match self.canonical().as_ref() {\n            Add(ix) => {\n                ensure!(*ix <= shape.len());\n                shape.insert(*ix, D::one());\n            }\n            Rm(ix) => {\n                ensure!(*ix < shape.len());\n                shape.remove(*ix);\n            }\n            Move(from, to) => {\n                ensure!(*from < shape.len());\n                ensure!(*to < shape.len());\n                let axis = shape.remove(*from);\n                shape.insert(*to, axis);\n            }\n            Reshape(at, from, to) => {\n                let from_volume = from.iter().product::<TDim>();\n                let to_volume = to.iter().product::<TDim>();\n                ensure!(from_volume == to_volume, \"{from_volume} should be equal to {to_volume}\");\n                ensure!(*at + from.len() <= shape.len());\n                if shape.len() >= from.len() + *at\n                    && tract_itertools::izip!(shape.iter().skip(*at), from)\n                        .all(|(shape, spec)| shape.to_dim() == *spec)\n                {\n                    for _ in from {\n                        shape.remove(*at);\n                    }\n                    for d in to.iter().rev() {\n                        shape.insert(*at, d.try_into()?);\n                    }\n                } else if broadcasting\n                    && shape.iter().skip(*at).take(from.len()).all(|d| d.to_dim() == 1.to_dim())\n                {\n                    for _ in from {\n                        shape.remove(*at);\n                    }\n                    for _ in to.iter().rev() {\n                        shape.insert(*at, 1.into());\n                    }\n                } else {\n                    bail!(\"Incompatible reshape for shape {:?} and {:?}\", shape, self);\n                }\n            }\n        }\n        Ok(())\n    }\n\n    pub fn change_shape(&self, shape: &mut ShapeFact, broadcasting: bool) -> TractResult<()> {\n        match self.canonical().as_ref() {\n            Add(ix) => shape.insert_axis(*ix),\n            Rm(ix) => {\n                if shape.rank() <= *ix {\n                    bail!(\"Attempt to remove axis #{} on shape {:?}\", ix, shape);\n                }\n                if shape[*ix] != 1.to_dim() {\n                    bail!(\"Removing non-trivial axis #{} of dim: {:?}\", ix, shape);\n                }\n                shape.remove_axis(*ix)\n            }\n            _ => {\n                let mut array = shape.to_tvec();\n                self.change_shape_array(&mut array, broadcasting)?;\n                let mut new_shape = ShapeFact::from_dims(array);\n                std::mem::swap(shape, &mut new_shape);\n                Ok(())\n            }\n        }\n    }\n\n    pub fn change_tensor(&self, tensor: &mut Tensor, broadcasting: bool) -> TractResult<()> {\n        if tensor.storage_as::<BlockQuantStorage>().is_some() {\n            let bqs = tensor.try_storage_as::<BlockQuantStorage>()?.clone();\n            let mut new_shape: TVec<usize> = tensor.shape().into();\n            self.change_shape_array(&mut new_shape, false)?;\n            let mut new_tensor = bqs.into_tensor_with_shape(tensor.datum_type(), &new_shape);\n            std::mem::swap(tensor, &mut new_tensor);\n            return Ok(());\n        }\n        ensure!(self.required_rank() <= tensor.rank());\n        match self.canonical().as_ref() {\n            Add(ix) => tensor.insert_axis(*ix),\n            Rm(ix) => tensor.remove_axis(*ix),\n            Move(from, to) => {\n                let mut tmp = tensor.clone().move_axis(*from, *to)?;\n                std::mem::swap(tensor, &mut tmp);\n                Ok(())\n            }\n            Reshape(at, from, to) => {\n                let mut shape: TVec<usize> = tensor.shape().into();\n                self.change_shape_array(&mut shape, true)?;\n                if tensor.set_shape(&shape).is_ok() {\n                    Ok(())\n                } else if broadcasting\n                    && tensor.shape().iter().skip(*at).take(from.len()).all(|d| *d == 1)\n                {\n                    if from.len() > to.len() {\n                        for _ in to.len()..from.len() {\n                            tensor.remove_axis(*at)?;\n                        }\n                    }\n                    if to.len() > from.len() {\n                        for _ in from.len()..to.len() {\n                            tensor.insert_axis(*at)?;\n                        }\n                    }\n                    Ok(())\n                } else {\n                    bail!(\n                        \"Invalid reshaping: {:?} on tensor {:?} (broadcasting allowed: {:?})\",\n                        self,\n                        tensor,\n                        broadcasting\n                    )\n                }\n            }\n        }\n    }\n\n    pub fn change_view<D>(&self, view: &mut ArrayViewD<D>) -> TractResult<()> {\n        use tract_ndarray::Axis;\n        match *self {\n            AxisOp::Rm(axis) => view.index_axis_inplace(Axis(axis), 0),\n            AxisOp::Add(axis) => view.insert_axis_inplace(Axis(axis)),\n            AxisOp::Move(from, to) if from < to => {\n                for left in from..to {\n                    view.swap_axes(left, left + 1);\n                }\n            }\n            AxisOp::Move(from, to) => {\n                for left in (to..from).rev() {\n                    view.swap_axes(left, left + 1);\n                }\n            }\n            AxisOp::Reshape(_, _, _) => bail!(\"Reshape can not change views in place\"),\n        }\n        Ok(())\n    }\n\n    pub fn change_view_mut<D>(&self, view: &mut ArrayViewMutD<D>) -> TractResult<()> {\n        use tract_ndarray::Axis;\n        match *self {\n            AxisOp::Rm(axis) => view.index_axis_inplace(Axis(axis), 0),\n            AxisOp::Add(axis) => view.insert_axis_inplace(Axis(axis)),\n            AxisOp::Move(from, to) if from < to => {\n                for left in from..to {\n                    view.swap_axes(left, left + 1);\n                }\n            }\n            AxisOp::Move(from, to) => {\n                for left in (to..from).rev() {\n                    view.swap_axes(left, left + 1);\n                }\n            }\n            AxisOp::Reshape(_, _, _) => bail!(\"Reshape can not change views in place\"),\n        }\n        Ok(())\n    }\n\n    pub fn recip(&self) -> AxisOp {\n        match self.canonical().as_ref() {\n            Add(ix) => Rm(*ix),\n            Rm(ix) => Add(*ix),\n            Move(from, to) if from == to => self.clone(),\n            Move(from, to) if *from + 1 == *to => self.clone(),\n            Move(from, to) if *from == *to + 1 => {\n                unreachable!();\n            }\n            Move(from, to) => Move(*to, *from),\n            Reshape(at, from, to) => Reshape(*at, to.clone(), from.clone()),\n        }\n    }\n\n    pub fn is_noop(&self) -> bool {\n        match self {\n            Move(f, t) if f == t => true,\n            Reshape(_, f, t) if f == t => true,\n            _ => false,\n        }\n    }\n\n    pub fn only_shape(&self) -> bool {\n        if self.is_noop() {\n            return true;\n        }\n        !matches!(self, Move(_, _))\n    }\n\n    pub fn wire_split_axis(\n        model: &mut TypedModel,\n        name: impl ToString,\n        outlet: OutletId,\n        axis: usize,\n        outer_dim: usize,\n    ) -> TractResult<TVec<OutletId>> {\n        let fact = model.outlet_fact(outlet)?;\n        let dim: TDim = fact.shape[axis].clone();\n        let inner_dim = dim.clone() / outer_dim;\n        let op = Self::Reshape(axis, tvec!(dim.clone()), tvec!(outer_dim.to_dim(), inner_dim));\n        model.wire_node(name.to_string(), op, &[outlet])\n    }\n\n    pub fn wire_collapse_axis(\n        model: &mut TypedModel,\n        name: impl ToString,\n        outlet: OutletId,\n        axis: usize,\n    ) -> TractResult<TVec<OutletId>> {\n        let fact = model.outlet_fact(outlet)?;\n        let dim: TDim = fact.shape[axis].clone();\n        let next_dim: TDim = fact.shape[axis + 1].clone();\n        let op = Self::Reshape(axis, tvec!(dim.clone(), next_dim.clone()), tvec!(dim * next_dim));\n        model.wire_node(name.to_string(), op, &[outlet])\n    }\n\n    #[inline]\n    pub fn required_rank(&self) -> usize {\n        match self {\n            Rm(r) => r + 1,\n            Add(a) => *a,\n            Reshape(at, from, _to) => at + from.len(),\n            Move(from, to) => *from.max(to),\n        }\n    }\n\n    pub fn trim_left(&self, prefix: usize) -> TractResult<AxisOp> {\n        Ok(match self {\n            Rm(r) if *r >= prefix => Rm(r - prefix),\n            Add(a) if *a >= prefix => Add(a - prefix),\n            Reshape(at, from, to) if *at >= prefix => {\n                Reshape(at - prefix, from.clone(), to.clone())\n            }\n            Move(from, to) if *from >= prefix && *to >= prefix => Move(from - prefix, to - prefix),\n            _ => bail!(\"Can no trim left {self:?} by {prefix}\"),\n        })\n    }\n}\n\npub fn wire_rank_broadcast(\n    prefix: impl AsRef<str>,\n    target: &mut TypedModel,\n    inputs: &[OutletId],\n) -> TractResult<TVec<OutletId>> {\n    let facts =\n        inputs.iter().map(|o| target.outlet_fact(*o).cloned()).collect::<TractResult<TVec<_>>>()?;\n    let max_rank = facts.iter().map(|f| f.rank()).max().unwrap();\n    let mut wires = tvec!();\n    for i in 0..inputs.len() {\n        let mut wire = inputs[i];\n        for _ in facts[i].rank()..max_rank {\n            let name = target.unique_name(prefix.as_ref().to_string() + \".fix-rank\");\n            wire = target.wire_node(name, AxisOp::Add(0), &[wire])?[0];\n        }\n        wires.push(wire);\n    }\n    Ok(wires)\n}\n\npub fn wire_with_rank_broadcast(\n    prefix: impl AsRef<str>,\n    target: &mut TypedModel,\n    op: impl Into<Box<dyn TypedOp>>,\n    inputs: &[OutletId],\n) -> TractResult<TVec<OutletId>> {\n    let prefix = prefix.as_ref();\n    let wires = wire_rank_broadcast(prefix, target, inputs)?;\n    target.wire_node(prefix, op.into(), &wires)\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct AxisChange {\n    pub outlet: OutletId,\n    pub op: AxisOp,\n}\n\n#[derive(Clone, Default, Debug)]\npub struct AxisChangeConsequence {\n    pub substitute_op: Option<Box<dyn TypedOp>>,\n    pub wire_changes: TVec<(InOut, AxisOp)>,\n}\n\nimpl AxisChangeConsequence {\n    pub fn new(\n        _model: &TypedModel,\n        node: &TypedNode,\n        op: Option<Box<dyn TypedOp>>,\n        axis_op: &AxisOp,\n    ) -> AxisChangeConsequence {\n        let mut wire_changes = tvec!();\n        for i in 0..node.inputs.len() {\n            wire_changes.push((InOut::In(i), axis_op.clone()));\n        }\n        for i in 0..node.outputs.len() {\n            wire_changes.push((InOut::Out(i), axis_op.clone()));\n        }\n        AxisChangeConsequence { wire_changes, substitute_op: op }\n    }\n}\n\nimpl Op for AxisOp {\n    fn name(&self) -> StaticName {\n        match self {\n            Add(_) => \"AddAxis\".into(),\n            Rm(_) => \"RmAxis\".into(),\n            Move(_, _) => \"MoveAxis\".into(),\n            Reshape(_, _, _) => \"Reshape\".into(),\n        }\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        match self {\n            Add(axis) | Rm(axis) => Ok(vec![format!(\"Axis: {axis}\")]),\n            Move(from, to) => Ok(vec![format!(\"Axis {from} to {to}\")]),\n            Reshape(at, from, to) => Ok(vec![format!(\n                \"Axes starting at {}: {:?} to {:?}\",\n                at,\n                from.iter().join(\",\"),\n                to.iter().join(\",\")\n            )]),\n        }\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for AxisOp {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let mut input = args_1!(inputs).into_tensor();\n        match self {\n            AxisOp::Reshape(skip, from, to) => {\n                let from = from.iter().map(|d| d.eval(&session.resolved_symbols)).collect();\n                let to = to.iter().map(|d| d.eval(&session.resolved_symbols)).collect();\n                AxisOp::Reshape(*skip, from, to).change_tensor(&mut input, false)?\n            }\n            _ => self.change_tensor(&mut input, false)?,\n        }\n        Ok(tvec!(input.into_tvalue()))\n    }\n}\n\n/// Remap coordinate symbols in a TDim expression according to an AxisOp.\n/// Returns None if the remapping cannot be determined (e.g. general reshape).\nfn remap_uniform_tdim(expr: &TDim, axis_op: &AxisOp) -> Option<TDim> {\n    let syms = expr.symbols();\n    let coord_syms: Vec<(usize, Symbol)> = syms\n        .into_iter()\n        .filter_map(|s| {\n            let name = format!(\"{s}\");\n            name.strip_prefix(\"🎯\").and_then(|rest| rest.parse::<usize>().ok()).map(|k| (k, s))\n        })\n        .collect();\n\n    if coord_syms.is_empty() {\n        // No coordinate symbols – the value is uniform across all positions; propagate as-is.\n        return Some(expr.clone());\n    }\n\n    // Reshape: only handle trivial all-ones case.\n    if let AxisOp::Reshape(_, from_dims, to_dims) = axis_op.canonical().as_ref() {\n        return if from_dims.iter().all(|d| d.is_one()) && to_dims.iter().all(|d| d.is_one()) {\n            Some(expr.clone())\n        } else {\n            None\n        };\n    }\n\n    // For Add/Rm/Move: use transform_axis and substitute all at once to avoid\n    // double-substitution when two axes swap positions (e.g. Move).\n    let map: HashMap<Symbol, TDim> = coord_syms\n        .into_iter()\n        .filter_map(|(k, sym)| {\n            let new_k = axis_op.transform_axis(k)?;\n            if new_k == k {\n                return None;\n            }\n            let scope = sym.scope()?;\n            Some((sym, TDim::Sym(scope.coord_sym(new_k))))\n        })\n        .collect();\n    if map.is_empty() {\n        return Some(expr.clone());\n    }\n    expr.substitute_all(&map).ok()\n}\n\nimpl TypedOp for AxisOp {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        if let Some(bqf) =\n            inputs[0].exotic_fact().and_then(|of| of.downcast_ref::<BlockQuantFact>())\n        {\n            let mut new_shape: TVec<usize> = bqf.shape().into();\n            self.change_shape_array(&mut new_shape, false)?;\n            let new_bqf = BlockQuantFact::new(bqf.format.clone(), new_shape.clone());\n            let shape: TVec<TDim> = new_shape.iter().map(|d| d.to_dim()).collect();\n            let mut new_fact = inputs[0].datum_type.fact(&*shape).with_exotic_fact(new_bqf);\n            if let Some(k) = &inputs[0].konst {\n                let mut new = k.clone().into_tensor();\n                self.change_tensor(&mut new, false)?;\n                new_fact.konst = Some(new.into());\n            }\n            return Ok(tvec!(new_fact));\n        }\n        let mut shape = inputs[0].shape.clone();\n        self.change_shape(&mut shape, false)?;\n        let mut fact = inputs[0].datum_type.fact(shape);\n        fact.exotic_fact.clone_from(&inputs[0].exotic_fact);\n        if let Some(tdim) = &inputs[0].uniform_tdim {\n            fact.uniform_tdim = remap_uniform_tdim(tdim, self);\n        }\n        Ok(tvec!(fact))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let mut axes: Vec<Axis> = (0..inputs[0].rank())\n            .zip('a'..)\n            .map(|(axis_id, repr)| {\n                let mut axis = Axis::new(repr, inputs.len(), outputs.len()).input(0, axis_id);\n                if let Some(out) = self.transform_axis(axis_id) {\n                    axis = axis.output(0, out);\n                }\n                axis\n            })\n            .collect();\n        for (axis, letter) in (0..outputs[0].rank()).zip('A'..) {\n            if self.recip().transform_axis(axis).is_none() {\n                axes.push(Axis::new(letter, inputs.len(), outputs.len()).output(0, axis));\n            }\n        }\n        AxesMapping::new(inputs.len(), outputs.len(), axes)\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if self.is_noop()\n            && let Some(p) = TypedModelPatch::shunt_one_op(model, node)?\n        {\n            return Ok(Some(p));\n        }\n        let simplified = self.simplify();\n        if simplified.len() != 1 || &simplified[0] != self {\n            let mut patch = TypedModelPatch::default();\n            let mut wire = patch.tap_model(model, node.inputs[0])?;\n            for (ix, op) in simplified.into_iter().enumerate() {\n                wire = patch.wire_node(format!(\"{}.{}\", node.name, ix), op, &[wire])?[0];\n            }\n            patch.shunt_outside(model, node.id.into(), wire)?;\n            Ok(Some(patch))\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn suggested_axis_changes(&self) -> TractResult<TVec<(InOut, AxisOp)>> {\n        Ok(tvec!((InOut::Out(0), self.recip()), (InOut::In(0), self.clone())))\n    }\n\n    fn change_axes(\n        &self,\n        _model: &TypedModel,\n        _node: &TypedNode,\n        io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        let op = if let InOut::Out(0) = io {\n            rule_if_some!(more = self.recip().change_axes(_model, _node, InOut::In(0), change)?);\n            AxisChangeConsequence {\n                substitute_op: more.substitute_op.map(|op| {\n                    if let Some(op) = op.as_op().downcast_ref::<AxisOp>() {\n                        Box::new(op.recip())\n                    } else {\n                        op // have to be identity\n                    }\n                }),\n                wire_changes: more\n                    .wire_changes\n                    .into_iter()\n                    .map(|wc| {\n                        (if wc.0 == InOut::In(0) { InOut::Out(0) } else { InOut::In(0) }, wc.1)\n                    })\n                    .collect(),\n            }\n        } else if change == self {\n            AxisChangeConsequence { substitute_op: Some(Box::new(Identity)), wire_changes: tvec!() }\n        } else {\n            rule_if_some!((new_op, new_change) = self.merge_incoming_change(change));\n            trace!(\"  Change:{change:?} self:{self:?} -> change:{new_change:?} op:{new_op:?}\");\n            let substitute_op: Box<dyn TypedOp> =\n                if let Some(o) = new_op { Box::new(o) as _ } else { Box::new(Identity) };\n            let mut wire_changes = tvec!();\n            if !change.is_noop() {\n                wire_changes.push((InOut::In(0), change.clone()))\n            }\n            if let Some(new_change) = new_change {\n                wire_changes.push((InOut::Out(0), new_change))\n            }\n            AxisChangeConsequence { substitute_op: Some(substitute_op), wire_changes }\n        };\n        Ok(Some(op))\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let op = if let AxisOp::Reshape(axis, from, to) = self {\n            AxisOp::Reshape(\n                *axis,\n                from.iter().map(|d| d.eval(values)).collect(),\n                to.iter().map(|d| d.eval(values)).collect(),\n            )\n        } else {\n            self.clone()\n        };\n        target.wire_node(&node.name, op, &[mapping[&node.inputs[0]]])\n    }\n\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        _model: &TypedModel,\n        node: &TypedNode,\n        _prefix: &str,\n        inputs: &[OutletId],\n        output_axis: usize,\n        _start: &TDim,\n        _end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        // is this test really useful ? or axis mapping preempt this ?\n        if let Reshape(pos, _from, to) = self\n            && output_axis >= *pos\n            && output_axis < pos + to.len()\n        {\n            return Ok(None);\n        }\n        patch.wire_node(&node.name, &node.op, inputs).map(Some)\n    }\n\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if node.outputs[0].fact.exotic_fact.is_some() {\n            return Ok(None);\n        }\n        if let Some(shape) = node.outputs[0].fact.shape.as_concrete()\n            && !matches!(self, AxisOp::Move(_, _))\n        {\n            let (inputs, outputs) = model.node_facts(node.id)?;\n            let mapping = self.axes_mapping(&inputs, &outputs)?;\n            let op = IntoShape {\n                mapping,\n                len: shape.iter().product(),\n                strides: Tensor::natural_strides(shape),\n                dims: shape.into(),\n            };\n            return Ok(Some(TypedModelPatch::replace_single_op(model, node, &node.inputs, op)?));\n        }\n        Ok(None)\n    }\n}\n\n// a, b, c is a <- b, b <- c, c <- a\nfn perm_to_cycles(perm: &[usize]) -> TVec<TVec<usize>> {\n    let mut cycles: TVec<TVec<usize>> = tvec!();\n    let mut done = 0;\n    while done < perm.len() {\n        if perm[done] == done || cycles.iter().any(|c| c.contains(&done)) {\n            done += 1;\n            continue;\n        }\n        let mut cycle = tvec!();\n        let mut current = done;\n        loop {\n            cycle.push(current);\n            current = perm[current];\n            if current == done {\n                break;\n            }\n        }\n        cycles.push(cycle)\n    }\n    cycles\n}\n\nfn is_rotation_cycle(cycle: &[usize]) -> Option<(usize, usize)> {\n    if cycle.windows(2).all(|w| w[0] + 1 == w[1]) {\n        Some((cycle[0], cycle[cycle.len() - 1]))\n    } else if cycle[1..cycle.len()].windows(2).all(|w| w[0] - 1 == w[1])\n        && cycle[cycle.len() - 1] - 1 == cycle[0]\n    {\n        Some((cycle[1], cycle[0]))\n    } else {\n        None\n    }\n}\n\nfn perm_to_atoms(input: &[usize]) -> TVec<(usize, usize)> {\n    let mut changes: TVec<(usize, usize)> = tvec!();\n    'top: loop {\n        let mut reached: TVec<usize> = (0..input.len()).collect();\n        changes.iter().for_each(|(f, t)| {\n            let axis = reached.remove(*f);\n            reached.insert(*t, axis);\n        });\n        if &*reached == input {\n            return changes;\n        }\n        let remaining: TVec<usize> =\n            input.iter().map(|x| reached.iter().position(|y| y == x).unwrap()).collect();\n        let cycles = perm_to_cycles(&remaining);\n        for cycle in &cycles {\n            if let Some(rot) = is_rotation_cycle(cycle) {\n                changes.push(rot);\n                continue 'top;\n            }\n        }\n        changes.push((cycles[0][1], cycles[0][0]));\n    }\n}\n\npub fn perm_to_ops(input: &[usize]) -> TVec<AxisOp> {\n    perm_to_atoms(input).into_iter().map(|pair| AxisOp::Move(pair.0, pair.1)).collect()\n}\n\npub fn compute_shape_with_tf_rules(input: &[TDim], shape_spec: &[TDim]) -> TractResult<TVec<TDim>> {\n    let mut shape: TVec<TDim> = shape_spec.into();\n    fn deal_with_zero<'a>(\n        mut input_dims: std::iter::Peekable<impl Iterator<Item = &'a TDim>>,\n        shape: &mut [TDim],\n    ) -> TractResult<()> {\n        let mut remaining_dim_input = 1.to_dim();\n        for slot in shape.iter_mut() {\n            if *slot == (-1).into() {\n                break;\n            }\n            if *slot == 0.into() {\n                if remaining_dim_input != TDim::one() {\n                    bail!(\"Invalid remaining dim\");\n                }\n                *slot = (*input_dims.peek().context(\"Invalid\")?).clone();\n            }\n            loop {\n                let quotient = remaining_dim_input.maybe_div(slot);\n                if quotient.is_err() || quotient.as_ref().unwrap().1 != 1 {\n                    remaining_dim_input *= input_dims.next().context(\"Invalid\")?;\n                } else {\n                    break;\n                }\n            }\n            remaining_dim_input = remaining_dim_input.maybe_div(slot)?.0;\n        }\n        Ok(())\n    }\n\n    deal_with_zero(input.iter().peekable(), &mut shape)?;\n    shape.reverse();\n    deal_with_zero(input.iter().rev().peekable(), &mut shape)?;\n    shape.reverse();\n\n    if let Some(pos) = shape.iter().position(|d| *d == (-1).into()) {\n        let input_vol: TDim = input.iter().product();\n        let shape_vol: TDim = shape.iter().filter(|d| **d != (-1).into()).product();\n        let div = input_vol.maybe_div(&shape_vol)?;\n        if div.1 != 1 {\n            bail!(\"invalid\")\n        }\n        shape[pos] = div.0;\n    }\n    Ok(shape)\n}\n\npub fn to_axis_ops_with_tf_rules(\n    input_orig: &[TDim],\n    output_spec: &[TDim],\n) -> TractResult<TVec<AxisOp>> {\n    let final_output = compute_shape_with_tf_rules(input_orig, output_spec)?;\n    let mut stack: TVec<AxisOp> = tvec!();\n    'top: loop {\n        let current_input =\n            stack.iter().try_fold(TVec::from(input_orig), |mut shape, op| -> TractResult<_> {\n                op.change_shape_array(&mut shape, false)?;\n                Ok(shape)\n            })?;\n        if current_input == final_output {\n            return Ok(stack);\n        }\n        if let Some(common) =\n            current_input.iter().zip(final_output.iter()).position(|(a, b)| a != b)\n        {\n            if current_input[common].is_one() {\n                stack.push(AxisOp::Rm(common));\n            } else if final_output[common].is_one() {\n                stack.push(AxisOp::Add(common));\n            } else {\n                // actual regrouping. search for a match. this is quadratic, but\n                // rank is expected to be somewhat reasonable\n                for i in common..current_input.len() {\n                    let i_group = &current_input[common..i + 1];\n                    let i_volume: TDim = i_group.iter().product();\n                    for o in common..final_output.len() {\n                        let o_group = &final_output[common..o + 1];\n                        let o_volume: TDim = o_group.iter().product();\n                        if i_volume == o_volume {\n                            stack.push(AxisOp::Reshape(common, i_group.into(), o_group.into()));\n                            continue 'top;\n                        }\n                    }\n                }\n                todo!()\n            }\n        } else if final_output.len() > current_input.len() {\n            stack.push(AxisOp::Add(current_input.len()));\n        } else {\n            stack.push(AxisOp::Rm(current_input.len() - 1));\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct IntoShape {\n    pub mapping: AxesMapping,\n    pub len: usize,\n    pub dims: TVec<usize>,\n    pub strides: TVec<isize>,\n}\n\nimpl Op for IntoShape {\n    fn name(&self) -> StaticName {\n        \"IntoShape\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"{}\", self.mapping)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for IntoShape {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let mut input = args_1!(inputs).into_tensor();\n        ensure!(input.len() == self.len);\n        unsafe { input.set_geometry_unchecked(&self.dims, &self.strides) };\n        Ok(tvec!(input.into_tvalue()))\n    }\n}\n\nimpl TypedOp for IntoShape {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact = inputs[0].datum_type.fact(&self.dims);\n        if let Some(of) = &inputs[0].exotic_fact {\n            fact = fact.with_exotic_fact(of.clone());\n        }\n        Ok(tvec!(fact))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let input = model.outlet_fact(node.inputs[0])?;\n        if input.shape.as_concrete().is_some_and(|shape| shape == &*self.dims) {\n            return TypedModelPatch::shunt_one_op(model, node);\n        }\n        if let Some(succ) = model.single_succ(node.id)?\n            && let Some(into_shape) = succ.op_as::<IntoShape>()\n        {\n            let op =\n                Self { mapping: self.mapping.compose(&into_shape.mapping)?, ..into_shape.clone() };\n            return Ok(Some(TypedModelPatch::fuse_with_next(model, node, op)?));\n        }\n        Ok(None)\n    }\n\n    as_op!();\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_perm_to_cycles() {\n        assert_eq!(perm_to_cycles(&[1, 2, 0]), tvec!(tvec!(0, 1, 2)));\n        assert_eq!(perm_to_cycles(&[2, 0, 1]), tvec!(tvec!(0, 2, 1)));\n        assert_eq!(perm_to_cycles(&[1, 2, 3, 0]), tvec!(tvec!(0, 1, 2, 3)));\n        assert_eq!(perm_to_cycles(&[3, 0, 1, 2]), tvec!(tvec!(0, 3, 2, 1)));\n        assert_eq!(perm_to_cycles(&[3, 1, 2, 0, 4]), tvec!(tvec!(0, 3)));\n    }\n\n    #[test]\n    fn is_rotation() {\n        assert_eq!(is_rotation_cycle(&[0, 1, 2]), Some((0, 2)));\n        assert_eq!(is_rotation_cycle(&[0, 2, 1]), Some((2, 0)));\n    }\n\n    #[test]\n    fn test_perm_one_rotation() {\n        assert_eq!(perm_to_atoms(&[1, 2, 0, 3, 4]), tvec!((0, 2)));\n    }\n\n    #[test]\n    fn test_perm_two_rotations() {\n        assert_eq!(perm_to_atoms(&[1, 2, 0, 4, 3]), tvec!((0, 2), (3, 4)));\n    }\n\n    #[test]\n    fn test_perm_complex() {\n        assert_eq!(perm_to_atoms(&[3, 1, 2, 0, 4]), tvec!((3, 0), (1, 3)));\n    }\n\n    // ADD-ADD\n\n    //                          Op\n    //           b,c   ------|Add(0)|----->        n,b,c\n    //   Add(0)                                            Add(1)\n    //         a,b,c   ------|Add(0)|----->        a,n,b,c\n    #[test]\n    pub fn transform_op_add_0_add_0() {\n        let change = Add(0);\n        let op = Add(0);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Add(1)))));\n    }\n\n    //                          Op\n    //           b,c   ------|Add(1)|----->        b,n,c\n    //   Add(0)                                                 Add(0)\n    //         a,b,c   ------|Add(2)|----->        a,b,n,c\n    #[test]\n    pub fn transform_op_add_0_add_1() {\n        let change = Add(0);\n        let op = Add(1);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(2)), Some(Add(0)))));\n    }\n\n    //                          Op\n    //           a,c   ------|Add(0)|----->        n,a,c\n    //   Add(1)                                                 Add(2)\n    //         a,b,c   ------|Add(0)|----->        n,a,b,c\n    #[test]\n    pub fn transform_op_add_1_add_0() {\n        let change = Add(1);\n        let op = Add(0);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Add(2)))));\n    }\n\n    //                          Op\n    //         a,b,c   ------|Rm(1)|----->         a,c\n    //   Rm(0)                                             Rm(0)\n    //           b,c   ------|Rm(0)|----->         c\n    #[test]\n    pub fn transform_op_rm_0_rm_1() {\n        let change = Rm(0);\n        let op = Rm(1);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(0)), Some(Rm(0)))));\n    }\n\n    //                          Op\n    //         a,b,c   ------|Rm(0)|----->         b,c\n    //   Rm(1)                                             Rm(0)\n    //           a,c   ------|Rm(0)|----->         c\n    #[test]\n    pub fn transform_op_rm_1_rm_0() {\n        let change = Rm(1);\n        let op = Rm(0);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(0)), Some(Rm(0)))));\n    }\n\n    // ADD - RM\n\n    //                          Op\n    //          b,c     ------|Rm(0)|------>        c\n    //   Add(0)                                                 Add(0)\n    //          a,b,c   ------|Rm(1)|----->         a,c\n    #[test]\n    pub fn transform_op_add_0_rm_0() {\n        let change = Add(0);\n        let op = Rm(0);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(1)), Some(Add(0)))));\n    }\n\n    //                          Op\n    //          b,c     ------|Rm(1)|------>        b\n    //   Add(0)                                                 Add(0)\n    //          a,b,c   ------|Rm(2)|----->         a,b\n    #[test]\n    pub fn transform_op_add_0_rm_1() {\n        let change = Add(0);\n        let op = Rm(1);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(2)), Some(Add(0)))));\n    }\n\n    //                          Op\n    //          a,c     ------|Rm(0)|------>        c\n    //   Add(1)                                                 Add(0)\n    //          a,b,c   ------|Rm(0)|----->         b,c\n    #[test]\n    pub fn transform_op_add_1_rm_0() {\n        let change = Add(1);\n        let op = Rm(0);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(0)), Some(Add(0)))));\n    }\n\n    // RM - ADD\n\n    //                          Op\n    //         a,b,c   ------|Add(0)|----->        X,a,b,c\n    //   Rm(1)                                                 Rm(2)\n    //           a,c   ------|Add(0)|----->        X,a,c\n    #[test]\n    pub fn transform_op_rm_1_add_0() {\n        let change = Rm(1);\n        let op = Add(0);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Rm(2)))));\n    }\n\n    //                          Op\n    //         a,b,c   ------|Add(1)|----->        a,X,b,c\n    //   Rm(0)                                                 Rm(0)\n    //           b,c   ------|Add(0)|----->        X,b,c\n    #[test]\n    pub fn transform_op_rm_0_add_1() {\n        let change = Rm(0);\n        let op = Add(1);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Add(0)), Some(Rm(0)))));\n    }\n\n    //                          Op\n    //         a,b,c   ------|Rm(2)|----->        a,b\n    //   Move(0, 2)                                           Move(0,1)\n    //         b,c,a   ------|Rm(1)|----->        b,a\n    #[test]\n    pub fn transform_op_mv_02_rm_2() {\n        let change = Move(0, 2);\n        let op = Rm(2);\n        assert_eq!(op.merge_incoming_change(&change), Some((Some(Rm(1)), Some(Move(0, 1)))));\n    }\n}\n\n#[cfg(test)]\nmod proptests {\n    use super::*;\n    use proptest::prelude::*;\n\n    #[derive(Debug)]\n    struct ComposeProblem {\n        input: TVec<usize>,\n        ops: TVec<AxisOp>,\n    }\n\n    impl Arbitrary for AxisOp {\n        type Parameters = TVec<usize>;\n        type Strategy = BoxedStrategy<AxisOp>;\n        fn arbitrary_with(shape: TVec<usize>) -> Self::Strategy {\n            let mut ops: BoxedStrategy<AxisOp> = (0usize..shape.len() + 1).prop_map(Add).boxed();\n            if shape.len() > 1 {\n                ops = ops\n                    .prop_union(\n                        (0..shape.len(), 0..shape.len() - 1)\n                            .prop_map(|(a, b)| Move(a, b + (b >= a) as usize))\n                            .boxed(),\n                    )\n                    .boxed()\n            }\n            let rms = (0..shape.len()).filter(|&ax| shape[ax] == 1).map(Rm).collect::<Vec<_>>();\n            if rms.len() > 0 {\n                ops = ops\n                    .prop_union((0..rms.len()).prop_map(move |rm| rms[rm].clone()).boxed())\n                    .boxed()\n            }\n            let mergeable: Vec<AxisOp> = shape\n                .windows(2)\n                .enumerate()\n                .filter(|(_, w)| w[0] > 1 && w[1] > 1)\n                .map(|(ix, w)| {\n                    Reshape(ix, tvec!(w[0].to_dim(), w[1].to_dim()), tvec!((w[0] * w[1]).to_dim()))\n                })\n                .collect();\n            if mergeable.len() > 1 {\n                ops = ops\n                    .prop_union(\n                        (0..mergeable.len()).prop_map(move |ix| mergeable[ix].clone()).boxed(),\n                    )\n                    .boxed()\n            }\n            ops\n        }\n    }\n\n    impl Arbitrary for ComposeProblem {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<ComposeProblem>;\n        fn arbitrary_with(_args: ()) -> Self::Strategy {\n            let input = proptest::collection::vec(1usize..4, 1usize..4);\n            fn tail(len: usize, shape: TVec<usize>) -> BoxedStrategy<TVec<AxisOp>> {\n                if len == 0 {\n                    Just(tvec!()).boxed()\n                } else {\n                    AxisOp::arbitrary_with(shape.clone())\n                        .prop_flat_map(move |op| {\n                            let mut shape = shape.clone();\n                            op.change_shape_array(&mut shape, false).unwrap();\n                            tail(len - 1, shape.clone()).prop_map(move |mut t| {\n                                t.insert(0, op.clone());\n                                t\n                            })\n                        })\n                        .boxed()\n                }\n            }\n            (input, 1usize..=5)\n                .prop_flat_map(|(input, len)| (Just(input.clone()), tail(len, input.into())))\n                .prop_map(|(input, ops)| ComposeProblem { input: input.into(), ops })\n                .boxed()\n        }\n    }\n\n    impl ComposeProblem {\n        pub fn model(&self) -> TractResult<TypedModel> {\n            let mut model = TypedModel::default();\n            let mut wire = model.add_source(\"source\", i64::fact(&self.input))?;\n            for (ix, op) in self.ops.iter().enumerate() {\n                wire = model.wire_node(format!(\"op_{ix}\"), op.clone(), &[wire])?[0];\n            }\n            model.select_output_outlets(&[wire])?;\n            Ok(model)\n        }\n\n        fn input(&self) -> TractResult<Tensor> {\n            unsafe {\n                let mut t = Tensor::uninitialized::<i64>(&self.input)?;\n                for i in 0..t.len() {\n                    t.try_as_plain_mut().unwrap().as_slice_mut().unwrap()[i] = i as i64;\n                }\n                Ok(t)\n            }\n        }\n\n        fn check(&self) -> TractResult<()> {\n            crate::setup_test_logger();\n            let input = self.input()?;\n            let model = self.model()?;\n            let raw = model.into_runnable()?.run(tvec!(input.clone().into_tvalue()))?;\n            let optimized = self.model()?.into_decluttered()?;\n            let opt = optimized.into_runnable()?.run(tvec!(input.into_tvalue()))?;\n            opt[0].close_enough(&raw[0], false)\n        }\n    }\n\n    proptest! {\n        #[test]\n        fn recip(pb in any::<AxisOp>()) {\n            assert_eq!(pb.recip().recip(), pb);\n        }\n\n        #[test]\n        fn axis_ops(pb in any::<ComposeProblem>()) {\n            pb.check().unwrap()\n        }\n    }\n\n    #[test]\n    fn add_0_rm_0() {\n        let pb = ComposeProblem { input: tvec![1], ops: tvec![Add(0), Rm(0)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_0_move_01() {\n        let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Move(0, 1)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_0_move_01_add_1() {\n        let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Move(0, 1), Add(1)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn recip_move_01() {\n        let op = Move(1, 0);\n        assert_eq!(op.recip().recip(), op);\n    }\n\n    #[test]\n    fn recip_move_20() {\n        let op = Move(2, 0);\n        assert_eq!(op.recip().recip(), op);\n    }\n\n    #[test]\n    fn recip_move_02() {\n        let op = Move(0, 2);\n        assert_eq!(op.recip().recip(), op);\n    }\n\n    #[test]\n    fn add_0_add_1_move_02() {\n        let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(1), Move(0, 2)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_0_add_0() {\n        let pb = ComposeProblem { input: tvec![1], ops: tvec![Add(0), Add(0)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_0_add_0_move_02() {\n        let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(0), Move(0, 2)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_0_add_2_move_12() {\n        let pb = ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(2), Move(1, 2)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_0_add_0_move_02_rm_0() {\n        let pb = ComposeProblem { input: tvec![1], ops: tvec![Add(0), Add(0), Move(0, 2), Rm(0)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_0_add_0_move_20_move_20() {\n        let pb =\n            ComposeProblem { input: tvec![2], ops: tvec![Add(0), Add(0), Move(2, 0), Move(2, 0)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn move_01_add_0() {\n        let pb = ComposeProblem { input: tvec![1, 1], ops: tvec![Move(0, 1), Add(0)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_0_move_02_move_02() {\n        let pb = ComposeProblem { input: tvec![1, 1], ops: tvec![Add(0), Move(0, 2), Move(0, 2),] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_0_add_2_move_20_move_12_rm_2() {\n        let pb = ComposeProblem {\n            input: tvec![3],\n            ops: tvec![Add(0), Add(2), Move(2, 0), Move(1, 2), Rm(2)],\n        };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn move_02_move_02() {\n        let pb = ComposeProblem { input: tvec![2, 1, 1], ops: tvec![Move(0, 2), Move(0, 2)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn rm_1_perm_10_add_0() {\n        let pb = ComposeProblem { input: tvec![1, 1, 2], ops: tvec![Rm(1), Move(0, 1), Add(0)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn add_2_move_02_move_02() {\n        let pb = ComposeProblem { input: tvec![3, 2], ops: tvec![Add(2), Move(0, 2), Move(0, 2)] };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn move_01_move_20_move_20() {\n        let pb = ComposeProblem {\n            input: tvec![2, 3, 2],\n            ops: tvec![Move(0, 1), Move(2, 0), Move(2, 0)],\n        };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn reshape_axes_tracking() {\n        let pb = ComposeProblem {\n            input: tvec![2, 2, 2],\n            ops: tvec![Reshape(0, tvec!(2.to_dim(), 2.to_dim()), tvec!(4.to_dim()))],\n        };\n        pb.check().unwrap();\n    }\n\n    #[test]\n    fn simplify_reshape() {\n        macro_rules! d {\n            ($($dim: expr),*) =>  { tvec!($($dim.to_dim()),*) }\n        }\n        assert_eq!(Reshape(3, d!(), d!()).simplify(), tvec!());\n        assert_eq!(Reshape(3, d!(2, 3), d!(2, 3)).simplify(), tvec!());\n        assert_eq!(Reshape(3, d!(1), d!()).simplify(), tvec!(Rm(3)));\n        assert_eq!(Reshape(3, d!(), d!(1)).simplify(), tvec!(Add(3)));\n        assert_eq!(\n            Reshape(3, d!(2, 3, 4), d!(2, 4, 3)).simplify(),\n            tvec!(Reshape(4, d!(3, 4), d!(4, 3)))\n        );\n        assert_eq!(\n            Reshape(3, d!(3, 4, 2), d!(4, 3, 2)).simplify(),\n            tvec!(Reshape(3, d!(3, 4), d!(4, 3)))\n        );\n        assert_eq!(\n            Reshape(3, d!(1, 2, 3), d!(3, 2)).simplify(),\n            tvec!(Rm(3), Reshape(3, d!(2, 3), d!(3, 2)))\n        );\n        assert_eq!(\n            Reshape(3, d!(2, 3), d!(1, 3, 2)).simplify(),\n            tvec!(Reshape(3, d!(2, 3), d!(3, 2)), Add(3))\n        );\n        assert_eq!(\n            Reshape(3, d!(2, 3, 1), d!(3, 2)).simplify(),\n            tvec!(Rm(5), Reshape(3, d!(2, 3), d!(3, 2)))\n        );\n        assert_eq!(\n            Reshape(3, d!(2, 3), d!(3, 2, 1)).simplify(),\n            tvec!(Add(5), Reshape(3, d!(2, 3), d!(3, 2)))\n        );\n        assert_eq!(\n            Reshape(2, d!(2, 2, 1), d!(4)).simplify(),\n            tvec!(Rm(4), Reshape(2, d!(2, 2), d!(4)))\n        );\n        assert_eq!(Reshape(1, d!(1, 2), d!(2)).simplify(), tvec!(Rm(1)));\n    }\n\n    macro_rules! s {\n        ($($a:expr),*) => {&[ $($a.clone().into()),* ]}\n    }\n\n    macro_rules! r {\n        ($at: expr ; $($from:expr),* => $($to:expr),*) => {\n            AxisOp::Reshape($at, tvec!($($from.into()),*),  tvec!($($to.into()),*))\n        }\n    }\n\n    #[test]\n    fn compute_invalid() {\n        assert!(compute_shape_with_tf_rules(s![3, 4, 5], s!(100)).is_err());\n    }\n\n    #[test]\n    fn compute_with_leading_zero() {\n        assert_eq!(&*compute_shape_with_tf_rules(s![3, 4, 5], s!(0, 0, 5)).unwrap(), s![3, 4, 5])\n    }\n\n    #[test]\n    fn compute_with_leading_zero_with_flatten() {\n        assert_eq!(\n            &*compute_shape_with_tf_rules(s![2, 3, 5, 7], s!(2, 0, 35)).unwrap(),\n            s![2, 3, 35]\n        )\n    }\n\n    #[test]\n    fn compute_with_trailing_zero() {\n        assert_eq!(&*compute_shape_with_tf_rules(s![3, 4, 5], s!(3, -1, 0)).unwrap(), s![3, 4, 5])\n    }\n\n    #[test]\n    fn compute_bug_1() {\n        let table = SymbolScope::default();\n        let s = table.new_with_prefix(\"S\");\n        assert_eq!(\n            &*compute_shape_with_tf_rules(s![s, 1, 2, 128], s!(0, 0, -1)).unwrap(),\n            s![s, 1, 256]\n        )\n    }\n\n    #[test]\n    fn compute_bug_2() {\n        let table = SymbolScope::default();\n        let b = table.new_with_prefix(\"B\");\n        let s = table.new_with_prefix(\"S\");\n        assert_eq!(\n            &*compute_shape_with_tf_rules(s![s, b, 2, 128], s!(0, 0, -1)).unwrap(),\n            s![s, b, 256]\n        )\n    }\n\n    #[test]\n    fn axis_op_rm_begin() {\n        assert_eq!(&*to_axis_ops_with_tf_rules(s![1, 2, 3], s!(2, 3)).unwrap(), &[Rm(0)])\n    }\n\n    #[test]\n    fn axis_op_rm_end() {\n        assert_eq!(&*to_axis_ops_with_tf_rules(s![2, 3, 1], s!(2, 3)).unwrap(), &[Rm(2)])\n    }\n\n    #[test]\n    fn axis_op_insert_begin() {\n        assert_eq!(&*to_axis_ops_with_tf_rules(s![2, 3], s!(1, 2, 3)).unwrap(), &[Add(0)])\n    }\n\n    #[test]\n    fn axis_op_insert_end() {\n        assert_eq!(&*to_axis_ops_with_tf_rules(s![2, 3], s!(2, 3, 1)).unwrap(), &[Add(2)])\n    }\n\n    #[test]\n    fn axis_op_merge() {\n        assert_eq!(\n            &*to_axis_ops_with_tf_rules(s![2, 3, 5, 7], s!(2, 0, 35)).unwrap(),\n            &[r!(2 ; 5,7 => 35 )]\n        )\n    }\n\n    #[test]\n    fn axis_op_complex() {\n        assert_eq!(\n            &*to_axis_ops_with_tf_rules(s![1, 2, 3, 5, 7], s!(2, 1, 3, 35, 1)).unwrap(),\n            &[Rm(0), Add(1), r!(3 ; 5,7 => 35 ), Add(4)]\n        )\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/conv/block_quant.rs",
    "content": "use tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage};\n\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct BlockQuantIntoShape {\n    pub shape: TVec<usize>,\n}\n\nimpl Op for BlockQuantIntoShape {\n    fn name(&self) -> StaticName {\n        \"BlockQuantIntoShape\".into()\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for BlockQuantIntoShape {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(None)\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs).into_tensor();\n        let g = input.shape()[0];\n        let bqs = input.try_storage_as::<BlockQuantStorage>()?.clone();\n        let new_m = self.shape[0];\n        let new_k: usize = self.shape[1..].iter().product();\n        Ok(tvec!(bqs.into_tensor_with_shape(input.datum_type(), &[g, new_m, new_k]).into_tvalue()))\n    }\n}\n\nimpl TypedOp for BlockQuantIntoShape {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let input = inputs[0];\n        let old = input\n            .exotic_fact\n            .as_ref()\n            .and_then(|of| of.downcast_ref::<BlockQuantFact>())\n            .context(\"Expects BlockQuantFact\")?;\n        let g: usize = input.shape[0].to_usize()?;\n        let new_m = self.shape[0];\n        let new_k: usize = self.shape[1..].iter().product();\n        let bqf_shape = tvec!(g, new_m, new_k);\n        let new = BlockQuantFact::new(old.format.clone(), bqf_shape.clone());\n        let shape: TVec<TDim> = bqf_shape.iter().map(|d| d.to_dim()).collect();\n        let fact = inputs[0].datum_type.fact(&*shape).with_exotic_fact(new);\n        Ok(tvec!(fact))\n    }\n    as_op!();\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct SplitGroupBlockQuant {\n    pub group: usize,\n}\n\nimpl Op for SplitGroupBlockQuant {\n    fn name(&self) -> StaticName {\n        \"SplitGroupBlockQuant\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for SplitGroupBlockQuant {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(None)\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let bqs = input.try_storage_as::<BlockQuantStorage>()?.clone();\n        let mut new_shape: TVec<usize> = input.shape().into();\n        let o = new_shape[0];\n        new_shape[0] = o / self.group;\n        new_shape.insert(0, self.group);\n        Ok(tvec!(bqs.into_tensor_with_shape(input.datum_type(), &new_shape).into_tvalue()))\n    }\n}\n\nimpl TypedOp for SplitGroupBlockQuant {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let input = inputs[0];\n        let bqf = input\n            .exotic_fact\n            .as_ref()\n            .and_then(|of| of.downcast_ref::<BlockQuantFact>())\n            .context(\"Expect BlockQuantFact\")?;\n        let o: usize = input.shape[0].to_usize()?;\n        ensure!(o % self.group == 0);\n        let mut new_shape: TVec<usize> =\n            input.shape.iter().map(|d| d.to_usize()).collect::<TractResult<_>>()?;\n        new_shape[0] = o / self.group;\n        new_shape.insert(0, self.group);\n        let exotic_fact = BlockQuantFact::new(bqf.format.clone(), new_shape.clone());\n        let fact = inputs[0]\n            .datum_type\n            .fact(&*new_shape.iter().map(|d| d.to_dim()).collect::<TVec<_>>())\n            .with_exotic_fact(exotic_fact);\n        Ok(tvec!(fact))\n    }\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/cnn/conv/conv.rs",
    "content": "use tract_data::itertools::izip;\nuse tract_linalg::WeightType;\nuse tract_linalg::block_quant::{BlockQuantFact, PackedBlockQuantFormat};\nuse tract_num_traits::Zero;\n\nuse crate::internal::*;\nuse crate::model::*;\nuse crate::ops;\nuse crate::ops::array::Pad;\nuse crate::ops::array::PadMode;\nuse crate::ops::binary::TypedBinOp;\nuse crate::ops::cast::cast;\nuse crate::ops::cnn::PaddingSpec::*;\nuse crate::ops::cnn::conv::block_quant::{BlockQuantIntoShape, SplitGroupBlockQuant};\nuse crate::ops::cnn::conv::lazy_im2col::LazyIm2Col;\nuse crate::ops::cnn::conv::lazy_im2col::LazyIm2colParams;\nuse crate::ops::cnn::wire_reshape_bias_for_bin;\nuse crate::ops::einsum::EinSum;\nuse crate::ops::math::{Add, Div, Mul, Sub};\nuse crate::ops::math::{add, div, mul, sub};\nuse crate::ops::matmul::ModePicker;\nuse crate::ops::matmul::optimized::AddMatMulGeometry;\nuse crate::ops::matmul::optimized::MapOutputAxisToInput;\nuse crate::ops::matmul::pack::{OptMatMulPack, OptSimpleMatMulPack};\nuse crate::ops::matmul::quant::wire_ensure_q8_flavour;\nuse crate::ops::nn::Reduce;\n\nuse super::depth_wise::DepthWise;\nuse super::im2col::Im2Col;\nuse crate::ops::cnn::conv::KernelFormat;\nuse crate::ops::cnn::pools::{ConcretePoolGeometry, PoolGeometry, PoolSpec};\nuse crate::ops::matmul::optimized::{OptMatMul, ProtoFusedSpec};\nuse crate::ops::nn::{BaseDataShape, DataFormat, DataShape};\n\nuse tract_linalg::mmm::{MMMInputFormat, MatMatMul};\nuse tract_linalg::pack::PackedFormat;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Conv {\n    pub pool_spec: PoolSpec,\n    pub kernel_fmt: KernelFormat,\n    pub group: usize,\n    // None -> floats\n    // Some(I32) -> output is I32 (use quantized kernels, but output will be i32). last 2 Q inputs\n    // are ignored\n    // Some(QXX) -> quantized XX, but parameters are ignored (I8, U8, or I32) in favor of last 2 Q inputs\n    pub q_params: Option<DatumType>,\n}\n\nimpl Conv {\n    pub fn input_channels(&self) -> usize {\n        self.pool_spec.input_channels\n    }\n\n    pub fn output_channels(&self) -> usize {\n        self.pool_spec.output_channels\n    }\n\n    pub fn wire_kernel_as_g_o_ihw(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        mut kernel: OutletId,\n    ) -> TractResult<TVec<OutletId>> {\n        let fact = model.outlet_fact(kernel)?;\n        if fact.is_exotic() {\n            ensure!(self.kernel_fmt == KernelFormat::OIHW && fact.rank() >= 2);\n            kernel = model.wire_node(\n                format!(\"{name}.prep_kernel.g\"),\n                SplitGroupBlockQuant { group: self.group },\n                &[kernel],\n            )?[0];\n            kernel = model.wire_node(\n                format!(\"{name}.prep_kernel.ihw\"),\n                BlockQuantIntoShape {\n                    shape: tvec!(\n                        self.output_channels() / self.group,\n                        self.input_channels() / self.group\n                            * self.pool_spec.kernel_shape.iter().product::<usize>(),\n                    ),\n                },\n                &[kernel],\n            )?[0];\n            Ok(tvec!(kernel))\n        } else {\n            for (ix, op) in self\n                .kernel_fmt\n                .kernel_as_group_o_ihw_ops(&fact.shape, self.group)\n                .into_iter()\n                .enumerate()\n            {\n                kernel = model.wire_node(format!(\"{name}.prep_kernel.{ix}\"), op, &[kernel])?[0];\n            }\n            Ok(tvec!(kernel))\n        }\n    }\n\n    fn wire_pack_g_o_ihw(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        format: &dyn MMMInputFormat,\n        kernel: OutletId,\n    ) -> TractResult<OutletId> {\n        let fact = model.outlet_fact(kernel)?;\n        let wire = if fact.is_exotic() {\n            let fact = model\n                .outlet_fact(kernel)?\n                .exotic_fact\n                .as_ref()\n                .and_then(|of| of.downcast_ref::<BlockQuantFact>())\n                .context(\"Only manage BlockQuant\")?;\n            model.wire_node(\n                format!(\"{name}.prep_kernel.pack\"),\n                OptSimpleMatMulPack {\n                    packed_format: format\n                        .downcast_ref::<PackedBlockQuantFormat>()\n                        .context(\"Expect a block quant format\")?\n                        .clone(),\n                    k: fact.k(),\n                    m: fact.m(),\n                },\n                &[kernel],\n            )?\n        } else {\n            let format = format\n                .downcast_ref::<PackedFormat>()\n                .context(\"Expect regular packing for numeric weights\")?;\n            model.wire_node(\n                format!(\"{name}.prep_kernel.pack\"),\n                OptMatMulPack {\n                    packers: vec![format.clone()],\n                    k_axis: 2,\n                    mn_axis: 1,\n                    mode_picker: ModePicker::Single,\n                },\n                &[kernel],\n            )?\n        };\n        Ok(wire[0])\n    }\n\n    // group,bias\n    fn wire_bias_as_non_linear(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        bias: OutletId,\n        c_group_axis: usize,\n    ) -> TractResult<(ProtoFusedSpec, OutletId)> {\n        use tract_linalg::BinOp::Add;\n        let fact = model.outlet_fact(bias)?;\n        if fact.shape.volume().is_one() {\n            Ok((ProtoFusedSpec::BinScalar(2, Add), bias))\n        } else {\n            let bias = AxisOp::wire_split_axis(\n                model,\n                format!(\"{name}.reformat_bias\"),\n                bias,\n                0,\n                self.group,\n            )?[0];\n            let pfs =\n                ProtoFusedSpec::BinPerRow(2, Add, MapOutputAxisToInput(tvec!((c_group_axis, 0))));\n            Ok((pfs, bias))\n        }\n    }\n\n    pub unsafe fn wire_as_quant_im2col(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        wires: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        ensure!(self.q_params.is_some());\n        use crate::ops::matmul::quant as qmm;\n\n        let c_dt = self.q_params.unwrap();\n        let &[mut x, mut kernel, bias, mut x0, x_scale, mut k0, mut k_scale, y0, y_scale] = wires\n        else {\n            bail!(\"Wrong number of inputs\")\n        };\n        wire_ensure_q8_flavour(model, name, &mut kernel, \"k\", &mut k0, i8::datum_type())?;\n        wire_ensure_q8_flavour(model, name, &mut x, \"x\", &mut x0, i8::datum_type())?;\n\n        let a_fact = model.outlet_fact(kernel)?.clone();\n        let b_fact = model.outlet_fact(x)?.clone();\n\n        let (_geo, m, k, n) = self.compute_geo(&b_fact)?;\n        let (mmm, packing) = self.choose_impl(&b_fact, &a_fact, m, k, &n)?;\n        let output_shape = self.pool_spec.output_shape(&b_fact.shape)?;\n\n        if !model.outlet_fact(k_scale)?.shape.volume().is_one() {\n            // requant is performed before geo_reshape, so we need at most one geo axis to the\n            // right\n            if !output_shape.fmt.c_is_last() {\n                k_scale = model.wire_node(\n                    format!(\"{name}.a_scale_axis_fix\"),\n                    AxisOp::Add(1),\n                    &[k_scale],\n                )?[0];\n            }\n        }\n\n        let abc_scale = qmm::combine_scales(model, name, k_scale, x_scale, y_scale)?;\n\n        let im2col = model.wire_node(\n            format!(\"{name}.im2col\"),\n            Im2Col::new(\n                self.pool_spec.clone(),\n                self.group,\n                k,\n                &b_fact.shape,\n                mmm.clone(),\n                packing,\n            )?,\n            &[x, x0],\n        )?[0];\n\n        let g_o_ihw = self.wire_kernel_as_g_o_ihw(model, name, kernel)?;\n        let g_o_ihw_as_i32 =\n            model.wire_node(format!(\"{name}.kernel_as_i32\"), cast(i32::datum_type()), &g_o_ihw)?;\n        let sum_ker_g_c_k = model.wire_node(\n            format!(\"{name}.sum_ker_g_c_k\"),\n            Reduce::new(tvec!(2), ops::nn::Reducer::Sum),\n            &g_o_ihw_as_i32,\n        )?;\n        let sum_ker_a_g_c =\n            model.wire_node(format!(\"{name}.rm_k\"), AxisOp::Rm(2), &sum_ker_g_c_k)?;\n        // align sum_A from G,C to \"C\" shape: N,HW,G,C (or N,G,C,HW)\n        let sum_ker_n_g_c = model.wire_node(\n            format!(\"{name}.sum_ker_n_g_c.axis_0\"),\n            AxisOp::Add(0),\n            &sum_ker_a_g_c,\n        )?;\n        let hw_position = if self.pool_spec.data_format.c_is_last() { 1 } else { 3 };\n        let sum_ker = model.wire_node(\n            format!(\"{name}.sum_ker_n_g_c\"),\n            AxisOp::Add(hw_position),\n            &sum_ker_n_g_c,\n        )?;\n\n        ensure!(mmm.packings()[packing].1.downcast_ref::<PackedFormat>().is_some());\n        let mut sum_x = model.wire_node(\n            format!(\"{name}.sum_x\"),\n            super::QSumB { dt: b_fact.datum_type, n, r: mmm.nr(), k },\n            &[im2col],\n        )?;\n        // sum_b is N,G,HW. make it N,HW,G,C or N,G,C,HW\n        sum_x = model.wire_node(format!(\"{name}.add_c\"), AxisOp::Add(2), &sum_x)?;\n        if self.pool_spec.data_format.c_is_last() {\n            sum_x =\n                model.wire_node(format!(\"{name}.transpose_sum_b\"), AxisOp::Move(3, 1), &sum_x)?;\n        }\n\n        let (mmm_output_shape, c_axis, h_axis) = self.mmm_output_shape(&output_shape)?;\n        let bias_name = &model.node(bias.node).name;\n        let bias =\n            model.wire_node(format!(\"{bias_name}.cast\"), cast(mmm.internal_type()), &[bias])?[0];\n        let wire = self.wire_mm_weights_bias(\n            model,\n            name,\n            im2col,\n            g_o_ihw[0],\n            bias,\n            mmm,\n            packing,\n            i32::datum_type(),\n            mmm_output_shape.clone().into(),\n            k,\n            c_axis,\n            h_axis,\n        )?;\n\n        let wire = qmm::compensate_zero_points(\n            model,\n            name,\n            wire[0],\n            k.to_dim(),\n            k0,\n            x0,\n            sum_ker[0],\n            sum_x[0],\n        )?;\n\n        let wire = self.wire_remove_group(model, name, &[wire], &mmm_output_shape, c_axis)?;\n        let wire = self.wire_rm_n_if_needed(model, name, &wire)?;\n        let wire = qmm::requant(model, name, wire[0], c_dt, abc_scale, y0)?;\n        Self::wire_geo_reshape(model, name, &[wire], &output_shape)\n    }\n\n    pub fn wire_remove_group<D: DimLike>(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        wire: &[OutletId],\n        mmm_output_shape: &[D],\n        c_axis: usize,\n    ) -> TractResult<TVec<OutletId>> {\n        let m = &mmm_output_shape[c_axis];\n        let op = if self.group == 1 {\n            AxisOp::Rm(c_axis - 1)\n        } else {\n            AxisOp::Reshape(\n                c_axis - 1,\n                tvec!(self.group.to_dim(), m.to_dim()),\n                tvec!(m.to_dim() * self.group),\n            )\n        };\n        model.wire_node(format!(\"{name}.reshape_group\"), op, wire)\n    }\n\n    pub unsafe fn wire_as_im2col_pair(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        wire: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let &[x, w, bias] = wire else { bail!(\"Wrong number of inputs\") };\n        let x_fact = model.outlet_fact(x)?.clone();\n        let w_fact = model.outlet_fact(w)?.clone();\n        let c_dt = crate::ops::matmul::output_type(x_fact.datum_type);\n\n        let (_, m, k, n) = self.compute_geo(&x_fact)?;\n        let (mmm, packing) = self.choose_impl(&x_fact, &w_fact, m, k, &n)?;\n        let geo_output_shape = self.pool_spec.output_shape(&x_fact.shape)?;\n        let (mmm_output_shape, c_axis, h_axis) = self.mmm_output_shape(&geo_output_shape)?;\n\n        let padding =\n            model.add_const(format!(\"{name}.b0\"), Tensor::zero_scalar_dt(x_fact.datum_type)?)?;\n\n        let mut wire: TVec<_> = wire.into();\n        wire[0] = model.wire_node(\n            format!(\"{name}.im2col\"),\n            Im2Col::new(\n                self.pool_spec.clone(),\n                self.group,\n                k,\n                &x_fact.shape,\n                mmm.clone(),\n                packing,\n            )?,\n            &[wire[0], padding],\n        )?[0];\n\n        let g_o_ihw = self.wire_kernel_as_g_o_ihw(model, name, wire[1])?;\n\n        let wire = self\n            .wire_mm_weights_bias(\n                model,\n                name,\n                wire[0],\n                g_o_ihw[0],\n                bias,\n                mmm,\n                packing,\n                c_dt,\n                mmm_output_shape.clone().into(),\n                k.to_usize().unwrap(),\n                c_axis,\n                h_axis,\n            )\n            .context(\"in wire_opt_matmul\")?;\n\n        let wire = self.wire_remove_group(model, name, &wire, &mmm_output_shape, c_axis)?;\n        let wire = self.wire_rm_n_if_needed(model, name, &wire)?;\n        Self::wire_geo_reshape(model, name, &wire, &geo_output_shape)\n    }\n\n    // always have N and G. G is right before C, c_axis point to C, c_axis-1 points to G\n    fn mmm_output_shape<D: DimLike>(\n        &self,\n        output_shape: &BaseDataShape<D, TVec<D>>,\n    ) -> TractResult<(TVec<D>, usize, usize)> {\n        let geo_collapsed_out: D = output_shape.hw_dims().iter().cloned().product();\n        let shape: BaseDataShape<D, TVec<D>> = output_shape.fmt.with_n().from_n_c_hw(\n            output_shape.n().cloned().unwrap_or_else(|| 1.into()),\n            output_shape.c().clone(),\n            tvec!(geo_collapsed_out),\n        )?;\n        let mut mmm_output_shape: TVec<D> = shape.shape.clone();\n        let mut c_axis = shape.c_axis();\n        let mut h_axis = shape.h_axis();\n        mmm_output_shape[shape.c_axis()] = mmm_output_shape[c_axis].clone() / self.group;\n        mmm_output_shape.insert(c_axis, self.group.into());\n        if h_axis > c_axis {\n            h_axis += 1;\n        }\n        c_axis += 1;\n        Ok((mmm_output_shape, c_axis, h_axis))\n    }\n\n    fn wire_rm_n_if_needed(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        wire: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if self.pool_spec.data_format.has_n() {\n            Ok(wire.into())\n        } else {\n            model.wire_node(format!(\"{name}.rm_n\"), AxisOp::Rm(0), wire)\n        }\n    }\n\n    fn wire_geo_reshape<D: DimLike>(\n        model: &mut TypedModel,\n        name: &str,\n        wire: &[OutletId],\n        output_shape: &BaseDataShape<D, TVec<D>>,\n    ) -> TractResult<TVec<OutletId>> {\n        let geo_collapsed_out: D = output_shape.hw_dims().iter().cloned().product();\n        model\n            .wire_node(\n                name,\n                AxisOp::Reshape(\n                    output_shape.h_axis(),\n                    tvec!(geo_collapsed_out.to_dim()),\n                    output_shape.hw_dims().iter().map(|d| d.to_dim()).collect(),\n                ),\n                wire,\n            )\n            .context(\"in wire_geo_reshape\")\n    }\n\n    pub unsafe fn wire_as_lazy_im2col(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        wire: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let &[mut x, kernel, bias] = wire else { bail!(\"Wrong number of inputs\") };\n        let mut x_fact = model.outlet_fact(x)?.clone();\n        let w_fact = model.outlet_fact(kernel)?.clone();\n        let (geo, m, k, n) = self.compute_geo(&x_fact)?;\n        let (mmm, packing) = self.choose_impl(&x_fact, &w_fact, m, k, &n)?;\n        debug!(\"{name} as lazy_im2col: m={m} k={k} n={n} {mmm:?}\");\n        let input_shape = x_fact.shape.as_concrete().unwrap().to_vec();\n        let mut geo = geo.to_concrete(&input_shape)?.into_owned();\n        let mut input_shape: DataShape = self.pool_spec.data_format.shape(input_shape.into())?;\n        let padding = self.pool_spec.computed_padding(input_shape.hw_dims());\n        if padding.iter().any(|axis| axis.pad_before != 0 || axis.pad_after != 0) {\n            let mut pads = vec![(0, 0); x_fact.rank()];\n            for (ix, ax) in padding.iter().enumerate() {\n                pads[input_shape.h_axis() + ix] = (ax.pad_before, ax.pad_after);\n            }\n            let op = crate::ops::array::Pad {\n                mode: crate::ops::array::PadMode::Constant(\n                    Tensor::zero_scalar_dt(x_fact.datum_type)?.into_arc_tensor(),\n                ),\n                pads,\n            };\n            x = model.wire_node(format!(\"{name}.pad\"), op, &[x])?[0];\n            let valid_pool_spec = PoolSpec { padding: Valid, ..self.pool_spec.clone() };\n            x_fact = model.outlet_fact(x)?.clone();\n            let concrete_shape = x_fact.shape.as_concrete().unwrap();\n            input_shape = valid_pool_spec.data_format.shape(concrete_shape.into())?;\n            geo = valid_pool_spec\n                .compute_geo(&x_fact.shape)?\n                .to_concrete(concrete_shape)?\n                .into_owned();\n        }\n        let c_dt = crate::ops::matmul::output_type(x_fact.datum_type);\n        let c_stride = input_shape.c_stride();\n        let size_of_b = x_fact.datum_type.size_of() as isize;\n        let n_byte_offsets: Vec<isize> =\n            geo.patch.centers_offsets().into_iter().map(|x| x * size_of_b).collect();\n        let k_byte_offsets: Vec<isize> = (0..self.input_channels())\n            .flat_map(|ici| {\n                geo.patch\n                    .standard_layout_data_field\n                    .iter()\n                    .map(move |x| (x + (ici * c_stride) as isize) * size_of_b)\n            })\n            .collect();\n        let (mmm_output_shape, c_axis, h_axis) = self.mmm_output_shape(&geo.output_shape)?;\n        let packer = mmm.packings()[packing]\n            .1\n            .downcast_ref::<PackedFormat>()\n            .with_context(|| {\n                format_err!(\n                    \"Quand Im2Col expects regular packed format, got {:?}\",\n                    mmm.packings()[packing].1\n                )\n            })?\n            .clone();\n        let params = LazyIm2colParams { packer, n_byte_offsets, k_byte_offsets };\n        let x = model.wire_node(\n            format!(\"{name}.lazyIm2col\"),\n            LazyIm2Col { params: Arc::new(params) },\n            &[x],\n        )?[0];\n\n        let kernel = self.wire_kernel_as_g_o_ihw(model, name, kernel)?[0];\n        let wire = self.wire_mm_weights_bias(\n            model,\n            name,\n            x,\n            kernel,\n            bias,\n            mmm,\n            packing,\n            c_dt,\n            mmm_output_shape.clone().into(),\n            k,\n            c_axis,\n            h_axis,\n        )?;\n\n        let wire = self.wire_remove_group(model, name, &wire, &mmm_output_shape, c_axis)?;\n        let wire = self.wire_rm_n_if_needed(model, name, &wire)?;\n        Self::wire_geo_reshape(model, name, &wire, &geo.output_shape)\n    }\n\n    #[allow(clippy::type_complexity)]\n    fn compute_geo(\n        &self,\n        input_fact: &TypedFact,\n    ) -> TractResult<(PoolGeometry, usize, usize, TDim)> {\n        let geo = self.pool_spec.compute_geo(&input_fact.shape)?;\n\n        trace!(\"output channels: {:?}\", self.output_channels());\n        let m = self.output_channels() / self.group;\n        let k = self.input_channels() * self.pool_spec.kernel_shape.iter().product::<usize>()\n            / self.group;\n        let n: TDim =\n            self.pool_spec.output_shape(&input_fact.shape)?.hw_dims().iter().cloned().product();\n        Ok((geo, m, k, n))\n    }\n\n    fn choose_impl(\n        &self,\n        input_fact: &TypedFact,\n        weight_fact: &TypedFact,\n        m: usize,\n        k: usize,\n        n: &TDim,\n    ) -> TractResult<(Box<dyn MatMatMul>, usize)> {\n        let w_dt = weight_fact.datum_type;\n        let x_dt = input_fact.datum_type;\n\n        let acc = if x_dt.is_float() { x_dt } else { i32::datum_type() };\n        if weight_fact.is_exotic() {\n            let bqf = weight_fact\n                .exotic_fact\n                .as_ref()\n                .and_then(|of| of.downcast_ref::<BlockQuantFact>())\n                .unwrap();\n            let weight_type = WeightType::BlockQuant(bqf.format.clone());\n            tract_linalg::ops()\n                .mmm_impls()\n                .iter()\n                .filter(|mmm| mmm.internal_type() == acc)\n                .flat_map(|mmm| {\n                    mmm.packings().iter().enumerate().map(move |(ix, p)| (mmm, ix, &p.0, &p.1))\n                })\n                .filter(|(_, _, pa, pb)| {\n                    pb.precursor() == x_dt.into() && pa.precursor() == weight_type\n                })\n                .map(|(mmm, p, _, _)| (mmm.clone(), p))\n                .min_by_key(|(mmm, _)| {\n                    mmm.quality().cost() as isize * 1000 - (mmm.mr() * mmm.nr()) as isize\n                })\n                .context(\"Not matmu found\")\n        } else {\n            let mmm = tract_linalg::ops()\n                .mmm(acc, Some(m), Some(k), n.to_usize().ok())\n                .context(\"No matmul found\")?;\n            let packing = mmm\n                .packings()\n                .iter()\n                .position(|p| {\n                    p.0.precursor() == w_dt.unquantized().into()\n                        && p.1.precursor() == x_dt.unquantized().into()\n                })\n                .context(\"No packing found\")?;\n            Ok((mmm, packing))\n        }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn wire_mm_weights_bias(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        input: OutletId,\n        g_o_ihw: OutletId,\n        bias: OutletId,\n        mmm: Box<dyn MatMatMul>,\n        packing: usize,\n        c_datum_type: DatumType,\n        mmm_output_shape: ShapeFact,\n        k: usize,\n        c_m_axis: usize,\n        c_n_axis: usize,\n    ) -> TractResult<TVec<OutletId>> {\n        ensure!(model.outlet_fact(bias)?.datum_type == mmm.internal_type());\n        let a_pack = &mmm.packings()[packing].0;\n        let packed_ker = self\n            .wire_pack_g_o_ihw(model, name, &**a_pack, g_o_ihw)\n            .context(\"in kernel_as_packed_as\")?;\n        let (mut c_to_a_axis_mapping, mut c_to_b_axis_mapping) = (tvec!(), tvec!());\n\n        c_to_a_axis_mapping.push((c_m_axis - 1, 0)); // Group\n        c_to_b_axis_mapping.push((0, 0)); // Batch\n        c_to_b_axis_mapping.push((c_m_axis - 1, 1)); // Group\n\n        let geo = AddMatMulGeometry {\n            k: k.to_dim(),\n            c_to_a_axis_mapping: MapOutputAxisToInput(c_to_a_axis_mapping),\n            c_to_b_axis_mapping: MapOutputAxisToInput(c_to_b_axis_mapping),\n        };\n        let mut ops: Vec<ProtoFusedSpec> =\n            vec![ProtoFusedSpec::AddMatMul { geo, a: 1, b: 0, packings: vec![(packing, None)] }];\n        let mut wires: TVec<OutletId> = tvec!(input, packed_ker);\n        let bias_fact = model.outlet_fact(bias)?;\n        if bias_fact.konst.is_none() || !bias_fact.konst.as_ref().unwrap().is_all_zero()? {\n            let (fused, bias) = self.wire_bias_as_non_linear(model, name, bias, c_m_axis - 1)?;\n            wires.push(bias);\n            ops.push(fused);\n        }\n        ops.push(ProtoFusedSpec::Store(vec![unsafe {\n            mmm.c_view(Some(c_m_axis), Some(c_n_axis))\n        }]));\n        model.wire_node(\n            format!(\"{name}.matmatmul\"),\n            OptMatMul::new(\n                vec![mmm],\n                ModePicker::Single,\n                c_datum_type.fact(mmm_output_shape),\n                Some(c_m_axis),\n                Some(c_n_axis),\n                ops,\n                packing == 0 && self.group == 1,\n            )?,\n            &wires,\n        )\n    }\n\n    pub fn wire_as_depth_wise(\n        &self,\n        model: &mut TypedModel,\n        name: &str,\n        wire: &[OutletId],\n    ) -> TractResult<OutletId> {\n        let &[x, kernel, mut bias] = wire else { bail!(\"Wrong number of inputs\") };\n        let x_fact = model.outlet_fact(x)?.clone();\n        let x_shape = x_fact.shape.as_concrete().unwrap();\n        let ConcretePoolGeometry { input_shape, patch, output_shape } =\n            self.pool_spec.compute_geo(&x_fact.shape)?.to_concrete(x_shape)?.into_owned();\n        let kernel = self.wire_kernel_as_g_o_ihw(model, name, kernel)?;\n        let c_axis = self.pool_spec.data_format.shape(x_shape)?.c_axis();\n        bias = wire_reshape_bias_for_bin(\n            model,\n            name,\n            bias,\n            x_fact.rank(),\n            c_axis,\n            self.output_channels(),\n        )?[0];\n        let op = DepthWise::new(patch, input_shape, output_shape);\n        Ok(model.wire_node(name, op, &[x, kernel[0], bias])?[0])\n    }\n\n    fn declutter_stride_slice_to_downsample(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let spatial_rank = self.pool_spec.rank();\n        if let Some(axis) = (0..spatial_rank).find(|&ax| {\n            self.pool_spec.stride(ax) > 1\n                && self.pool_spec.padding.valid_dim(ax, self.pool_spec.stride(ax) == 1)\n                && (self.pool_spec.kernel_shape[ax] == 1\n                    || self.pool_spec.dilation(ax).is_multiple_of(self.pool_spec.stride(ax)))\n        }) {\n            let input_fact = model.outlet_fact(node.inputs[0])?;\n            let downsample_factor = self.pool_spec.stride(axis);\n            let mut new_op = self.clone();\n            if new_op.pool_spec.dilation(axis) > 1 {\n                new_op.pool_spec.dilations.as_mut().unwrap()[axis] =\n                    new_op.pool_spec.dilations.as_mut().unwrap()[axis].divceil(downsample_factor);\n            }\n            new_op.pool_spec.strides.as_mut().unwrap()[axis] /= downsample_factor;\n            let mut patch = TypedModelPatch::default();\n            let mut taps = patch.taps(model, &node.inputs)?;\n            let shape = self.pool_spec.data_format.shape(&input_fact.shape)?;\n            taps[0] = patch.wire_node(\n                format!(\"{}.downsample.{}\", node.name, axis),\n                crate::ops::Downsample::new(axis + shape.h_axis(), downsample_factor as isize, 0),\n                &[taps[0]],\n            )?[0];\n            let id = patch.wire_node(&*node.name, new_op, &taps)?[0];\n            patch.shunt_outside(model, OutletId::new(node.id, 0), id)?;\n            return Ok(Some(patch));\n        }\n        Ok(None)\n    }\n\n    fn declutter_as_einsum(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let (input_facts, output_facts) = model.node_facts(node.id)?;\n        let full_input_shape = input_facts[0].shape.to_tvec();\n        let input_shape = self.pool_spec.data_format.shape(&full_input_shape)?;\n        if self.group == 1\n            && self.pool_spec.strides().iter().all(|s| *s == 1)\n            && self.pool_spec.dilations().iter().all(|d| *d == 1)\n            && self.pool_spec.kernel_shape.iter().product::<usize>() == 1\n            && self\n                .pool_spec\n                .computed_padding(input_shape.hw_dims())\n                .iter()\n                .all(|pad| pad.pad_after.is_zero() && pad.pad_before.is_zero())\n        {\n            let mut axes = self.axes_mapping(&input_facts, &output_facts)?;\n            let mut patch = TypedModelPatch::new(\"declutter_as_einsum\");\n            let mut taps = patch.taps(model, &node.inputs)?;\n            let name = &node.name;\n            let co = self.output_channels();\n            taps[1] =\n                self.wire_kernel_as_g_o_ihw(&mut patch, &format!(\"{name}.filters\"), taps[1])?[0];\n            taps[1] =\n                patch.wire_node(format!(\"{name}.filters_as_co_ci\"), AxisOp::Rm(0), &[taps[1]])?[0];\n\n            while axes.rank(InOut::In(1)) > 0 {\n                axes = axes.remove_axis_occurency(InOut::In(1), 0)?;\n            }\n            axes = axes\n                .with_extra_axis_occurency('O', InOut::In(1), 0)?\n                .with_extra_axis_occurency('I', InOut::In(1), 1)?;\n\n            let bias_fact = input_facts[2];\n            let wire = if self.q_params.is_some() {\n                if bias_fact.rank() == 1 {\n                    axes = axes.linking('O', (InOut::In(2), 0))?;\n                }\n                let op = EinSum { axes, operating_dt: i32::datum_type(), q_params: self.q_params };\n                patch.wire_node(format!(\"{name}.einsum\"), op, &taps)?[0]\n            } else {\n                axes = axes.remove_slot(InOut::In(2))?;\n                let op = EinSum { axes, operating_dt: input_facts[0].datum_type, q_params: None };\n                let mut wire = patch.wire_node(format!(\"{name}.einsum\"), op, &taps[0..2])?[0];\n\n                if !bias_fact.konst.as_ref().map(|f| f.is_zero()).transpose()?.unwrap_or(false) {\n                    let bias_current_shape =\n                        if bias_fact.rank() == 0 { tvec!() } else { tvec!(co.to_dim()) };\n                    let mut bias_shape = tvec!(1.to_dim(); input_shape.rank());\n                    if bias_fact.rank() > 0 {\n                        bias_shape[input_shape.c_axis()] = co.to_dim();\n                    }\n                    let b = patch.wire_node(\n                        format!(\"{name}.bias.reshape\"),\n                        AxisOp::Reshape(0, bias_current_shape, bias_shape),\n                        &[taps[2]],\n                    )?[0];\n                    wire = patch.wire_node(\n                        format!(\"{name}.bias\"),\n                        crate::ops::math::add(),\n                        &[wire, b],\n                    )?[0];\n                }\n                wire\n            };\n            patch.node_mut(wire.node).name = node.name.to_string();\n            patch.shunt_outside(model, node.id.into(), wire)?;\n            return Ok(Some(patch));\n        }\n        Ok(None)\n    }\n\n    fn declutter_precursor_padding(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        rule_if!(!matches!(\n            self.pool_spec.padding,\n            ExplicitOnnxPool(_, _, _) | SameLower | SameUpper\n        ));\n        let prec = model.node(node.inputs[0].node);\n        rule_if_some!(pad = prec.op_as::<Pad>());\n        rule_if_let!(PadMode::Constant(value) = &pad.mode);\n        let shape = self.pool_spec.data_format.shape(&model.outlet_fact(node.inputs[0])?.shape)?;\n        rule_if!(value.is_zero()?);\n        rule_if!(pad.pads[shape.c_axis()] == (0, 0));\n        if self.pool_spec.data_format.has_n() {\n            rule_if!(pad.pads[0] == (0, 0));\n        }\n        let mut before: TVec<usize> = pad.pads[shape.hw_axes()].iter().map(|pair| pair.0).collect();\n        let mut after: TVec<usize> = pad.pads[shape.hw_axes()].iter().map(|pair| pair.1).collect();\n        if let Explicit(bef, aft) = &self.pool_spec.padding {\n            izip!(&mut before, bef).for_each(|(pad, cv)| *pad += cv);\n            izip!(&mut after, aft).for_each(|(pad, cv)| *pad += cv);\n        }\n        let padding = Explicit(before, after);\n        let mut new = self.clone();\n        new.pool_spec.padding = padding;\n        let mut patch = TypedModelPatch::default();\n        let mut wire = patch.taps(model, &node.inputs)?;\n        wire[0] = patch.tap_model(model, prec.inputs[0])?;\n        let wire = patch.wire_node(&node.name, new, &wire)?;\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        Ok(Some(patch))\n    }\n\n    fn declutter_channel_arithmetic_succ(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        rule_if!(self.q_params.is_none());\n        rule_if!(self.group == 1);\n        rule_if_let!(&[succ_outlet] = &*node.outputs[0].successors);\n        let succ = model.node(succ_outlet.node);\n        rule_if_some!(bin = succ.op_as::<TypedBinOp>());\n        let other_input = succ.inputs[1 - succ_outlet.slot];\n        let axes_mapping = model.node_axes_mapping(succ.id)?;\n        let input_shape =\n            self.pool_spec.data_format.shape(&model.outlet_fact(node.inputs[0])?.shape)?;\n        let conv_c_axis = input_shape.c_axis();\n        rule_if!(\n            axes_mapping.axis((InOut::In(succ_outlet.slot), conv_c_axis))?.inputs\n                [1 - succ_outlet.slot]\n                .len()\n                == 1\n        );\n        let mut other_expected_shape = tvec!(1.to_dim(); input_shape.rank());\n        other_expected_shape[conv_c_axis] = self.output_channels().to_dim();\n        rule_if!(*other_expected_shape == *model.outlet_fact(other_input)?.shape);\n\n        let mut patch = TypedModelPatch::default();\n        let [input, mut kernel, mut bias] = *patch.taps(model, &node.inputs)? else {\n            panic!(\"Expect three inputs\");\n        };\n        let name = &node.name;\n        let succ_name = &succ.name;\n\n        let operand = patch.tap_model(model, other_input)?;\n\n        let renamed_bias = format!(\"{name}.{succ_name}.bias\");\n        let renamed_kernel = format!(\"{name}.{succ_name}.kernel\");\n        bias = wire_reshape_bias_for_bin(\n            &mut patch,\n            format!(\"{renamed_bias}.reshape\"),\n            bias,\n            1,\n            0,\n            self.output_channels(),\n        )?[0];\n\n        let operand = wire_reshape_bias_for_bin(\n            &mut patch,\n            format!(\"{renamed_bias}.reshape_operand\"),\n            operand,\n            1,\n            0,\n            self.output_channels(),\n        )?[0];\n\n        let operand_fact = patch.outlet_fact(operand)?.shape.to_tvec();\n        let kernel_fact = patch.outlet_fact(kernel)?;\n        let mut operand_shape_for_kernel = tvec!(1.to_dim(); 2 + input_shape.hw_rank());\n        operand_shape_for_kernel[self.kernel_fmt.o_axis(&kernel_fact.shape)] =\n            self.output_channels().to_dim();\n        let operand_for_kernel = patch.wire_node(\n            format!(\"{renamed_kernel}.reshape_operand\"),\n            AxisOp::Reshape(0, operand_fact, operand_shape_for_kernel),\n            &[operand],\n        )?[0];\n\n        if bin.0.is::<Sub>() && succ_outlet.slot == 0 {\n            bias = patch.wire_node(&renamed_bias, sub(), &[bias, operand])?[0];\n        } else if bin.0.is::<Sub>() {\n            bias = patch.wire_node(&renamed_bias, sub(), &[operand, bias])?[0];\n        } else if bin.0.is::<Div>() && succ_outlet.slot == 0 {\n            bias = patch.wire_node(&renamed_bias, div(), &[bias, operand])?[0];\n            kernel = patch.wire_node(&renamed_kernel, div(), &[kernel, operand_for_kernel])?[0];\n        } else if bin.0.is::<Div>() {\n            bias = patch.wire_node(&renamed_bias, div(), &[operand, bias])?[0];\n            kernel = patch.wire_node(&renamed_kernel, div(), &[operand_for_kernel, kernel])?[0];\n        } else if bin.0.is::<Add>() {\n            bias = patch.wire_node(&renamed_bias, add(), &[bias, operand])?[0];\n        } else if bin.0.is::<Mul>() {\n            bias = patch.wire_node(&renamed_bias, mul(), &[bias, operand])?[0];\n            kernel = patch.wire_node(&renamed_kernel, mul(), &[kernel, operand_for_kernel])?[0];\n        } else {\n            return Ok(None);\n        };\n        let wire = patch.wire_node(&node.name, self.clone(), &[input, kernel, bias])?[0];\n        patch.shunt_outside(model, succ_outlet.node.into(), wire)?;\n        Ok(Some(patch))\n    }\n}\n\nimpl Op for Conv {\n    fn name(&self) -> StaticName {\n        \"Conv\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut info = self.pool_spec.info();\n        info.push(format!(\"Kernel {:?} (groups:{})\", self.kernel_fmt, self.group));\n        Ok(info)\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Rounding\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Conv {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let mut model = TypedModel::default();\n        let wire: TVec<OutletId> = inputs\n            .iter()\n            .enumerate()\n            .map(|(ix, v)| model.add_source(format!(\"source.{ix}\"), v.datum_type().fact(v.shape())))\n            .collect::<TractResult<_>>()?;\n        let wire = unsafe {\n            if self.q_params.is_some() {\n                self.wire_as_quant_im2col(&mut model, \"im2col-adhoc\", &wire)?\n            } else {\n                self.wire_as_im2col_pair(&mut model, \"im2col-adhoc\", &wire)?\n            }\n        };\n        model.select_output_outlets(&wire)?;\n        model.into_runnable()?.run(inputs)\n    }\n}\n\nimpl TypedOp for Conv {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(self.q_params.is_some() || inputs[0].datum_type.is_float());\n        let q_inputs = if self.q_params.is_some() { 6 } else { 0 };\n        ensure!(inputs[1].datum_type.is_number() || self.kernel_fmt == KernelFormat::OIHW);\n        if inputs.len() != 3 + q_inputs {\n            bail!(\"Wrong number of inputs: expected {} got {}\", 3 + q_inputs, inputs.len());\n        }\n        if self.q_params.is_some() {\n            ensure!(inputs[2].datum_type == i32::datum_type());\n            ensure!(inputs[3].datum_type == i32::datum_type());\n            ensure!(inputs[4].datum_type.is_float());\n            ensure!(inputs[5].datum_type == i32::datum_type());\n            ensure!(inputs[6].datum_type.is_float());\n            ensure!(inputs[7].datum_type == i32::datum_type());\n            ensure!(inputs[8].datum_type.is_float());\n        }\n        ensure!(self.pool_spec.rank() + 2 == inputs[1].shape.len());\n        if self.pool_spec.data_format.shape(&*inputs[0].shape)?.c()\n            != &self.input_channels().to_dim()\n        {\n            bail!(\n                \"Inconsistent convolution: input is {:?}, but kernel expects {} input channels.\\n{:?}\",\n                inputs[0],\n                self.input_channels(),\n                self\n            );\n        }\n        if let ExplicitOnnxPool(bef, after, _) | Explicit(bef, after) = &self.pool_spec.padding {\n            anyhow::ensure!(bef.len() == self.pool_spec.rank());\n            anyhow::ensure!(after.len() == self.pool_spec.rank());\n        }\n        ensure!(\n            inputs[2].rank() == 0\n                || (inputs[2].rank() == 1\n                    && inputs[2].shape.volume() == self.output_channels().to_dim()),\n            \"Bias should be scalar or a vector with one value per output channel. Output channels is {}, bias is {:?}\",\n            self.output_channels(),\n            inputs[2]\n        );\n        let mut fact = self.pool_spec.output_facts(inputs)?.remove(0);\n        if let Some(dt) = self.q_params {\n            fact.datum_type = dt;\n        } else {\n            ensure!(\n                inputs[1].is_exotic() || inputs[0].datum_type == inputs[1].datum_type,\n                \"Convolution input, weights and bias must have the same type, got {inputs:?}\",\n            )\n        }\n        Ok(tvec!(fact))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let fact = &inputs[0];\n        let shape = self.pool_spec.data_format.shape(&fact.shape)?;\n        let mut axes = AxesMapping::disconnected(inputs, outputs)?\n            .renaming((InOut::In(0), shape.c_axis()), 'I')?\n            .renaming((InOut::Out(0), shape.c_axis()), 'O')?;\n        if let Some(n_axis) = shape.n_axis() {\n            axes = axes\n                .renaming((InOut::In(0), n_axis), 'N')?\n                .linking('N', (InOut::Out(0), n_axis))?;\n        }\n        let h_axis = shape.h_axis();\n        let geo = \"HWXYZ\".chars().chain('a'..);\n        let kernel_spatial_shape = &self.pool_spec.kernel_shape;\n        let padding = self.pool_spec.computed_padding(shape.hw_dims());\n        for ((ix, &dim), repr) in kernel_spatial_shape.iter().enumerate().zip(geo) {\n            if dim == 1\n                && self.pool_spec.dilation(ix) == 1\n                && self.pool_spec.stride(ix) == 1\n                && padding[ix].pad_before.is_zero()\n                && padding[ix].pad_after.is_zero()\n            {\n                axes = axes\n                    .renaming((InOut::In(0), ix + h_axis), repr)?\n                    .linking(repr, (InOut::Out(0), ix + h_axis))?;\n            }\n        }\n        if self.q_params.is_some() {\n            for (qp_ix, qp) in inputs.iter().enumerate().skip(3) {\n                if qp.rank() == 1 {\n                    axes = match qp_ix {\n                        3 | 4 => axes.linking('I', (InOut::In(qp_ix), 0))?,\n                        5 | 6 => axes.linking('O', (InOut::In(qp_ix), 0))?,\n                        7 | 8 => axes.linking('O', (InOut::In(qp_ix), 0))?,\n                        _ => unreachable!(),\n                    };\n                }\n            }\n        }\n        Ok(axes)\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        macro_rules! pass {\n            ($func:ident) => {\n                if let Some(mut r) = self.$func(model, node).context(stringify!($func))? {\n                    trace!(stringify!($func));\n                    r.push_context(stringify!($func));\n                    return Ok(Some(r));\n                }\n            };\n        }\n        pass!(declutter_stride_slice_to_downsample);\n        pass!(declutter_as_einsum);\n        pass!(declutter_channel_arithmetic_succ);\n        pass!(declutter_precursor_padding);\n        Ok(None)\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let shape = self.pool_spec.data_format.shape(inputs[0].shape.to_tvec())?;\n        let kernel_spatial_shape = &self.pool_spec.kernel_shape;\n        let output_dims = self.pool_spec.padding.compute(\n            shape.hw_dims(),\n            kernel_spatial_shape,\n            &self\n                .pool_spec\n                .dilations\n                .clone()\n                .unwrap_or_else(|| tvec!(1; kernel_spatial_shape.len())),\n            &self.pool_spec.strides.clone().unwrap_or_else(|| tvec!(1; kernel_spatial_shape.len())),\n        );\n        let n_output_points: TDim =\n            output_dims.iter().map(|d| d.convoluted.clone()).product::<TDim>();\n        let n_output_channels = self.output_channels().to_dim();\n        let kernel_surface = kernel_spatial_shape.iter().product::<usize>().to_dim();\n        let one = 1.to_dim();\n        Ok(tvec!((\n            Cost::FMA(inputs[0].datum_type),\n            shape.n().cloned().unwrap_or(one)\n                * shape.c()\n                * n_output_channels\n                * n_output_points\n                * kernel_surface\n                / self.group\n        )))\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        if io == InOut::In(1) {\n            return Ok(None);\n        }\n        if io == InOut::In(2)\n            && let &AxisOp::Rm(_) = change\n        {\n            return Ok(Some(AxisChangeConsequence {\n                substitute_op: Some(Box::new(self.clone())),\n                wire_changes: tvec!(),\n            }));\n        }\n        let full_input_shape = model.outlet_fact(node.inputs[0])?.shape.to_tvec();\n        let shape = self.pool_spec.data_format.shape(full_input_shape.clone())?;\n        // remove n\n        if let Some(n) = shape.n_axis() {\n            assert_eq!(n, 0);\n            if change == &AxisOp::Rm(n) {\n                let op = Conv { pool_spec: self.pool_spec.dispose_n_axis(), ..self.clone() };\n                return Ok(Some(AxisChangeConsequence {\n                    substitute_op: Some(Box::new(op)),\n                    wire_changes: tvec!(\n                        (InOut::In(0), change.clone()),\n                        (InOut::Out(0), change.clone())\n                    ),\n                }));\n            }\n            if change.transform_axis(n).map(|axis| axis > 0).unwrap_or(true) {\n                return Ok(None);\n            }\n        }\n        // format swap: chw <-> hwc\n        let (new_format, axis_move) = match self.pool_spec.data_format {\n            DataFormat::NCHW => {\n                (DataFormat::NHWC, AxisOp::Move(shape.c_axis(), full_input_shape.len() - 1))\n            }\n            DataFormat::CHW => {\n                (DataFormat::HWC, AxisOp::Move(shape.c_axis(), full_input_shape.len() - 1))\n            }\n            DataFormat::NHWC => (DataFormat::NCHW, AxisOp::Move(shape.c_axis(), 1)),\n            DataFormat::HWC => (DataFormat::CHW, AxisOp::Move(shape.c_axis(), 0)),\n        };\n        if *change == axis_move {\n            let mut new_op = self.clone();\n            new_op.pool_spec.data_format = new_format;\n            return Ok(Some(AxisChangeConsequence {\n                substitute_op: Some(Box::new(new_op)),\n                wire_changes: tvec!(\n                    (InOut::In(0), change.clone()),\n                    (InOut::Out(0), change.clone())\n                ),\n            }));\n        }\n        // geo axis manips\n        if model.node_input_facts(node.id)?[1].is_exotic() {\n            return Ok(None);\n        }\n        use AxisOp::*;\n        let h_axis = shape.h_axis();\n        let hw_axes = shape.hw_axes();\n        let kh_axis = self.kernel_fmt.h_axis();\n        let (geo_adjusted, kernel_adjusted) = match change {\n            Rm(a)\n                if hw_axes.contains(a)\n                    && hw_axes.len() > 1\n                    && self.pool_spec.dilation(a - h_axis) == 1\n                    && self.pool_spec.stride(a - h_axis) == 1\n                    && self.pool_spec.kernel_shape[a - h_axis] == 1 =>\n            {\n                let geo_axis = a - h_axis;\n                (Rm(geo_axis), Rm(kh_axis + geo_axis))\n            }\n            Add(a) if hw_axes.contains(a) => (Add(a - h_axis), Add(a - h_axis + kh_axis)),\n            Move(f, t) if hw_axes.contains(f) && hw_axes.contains(t) => {\n                (Move(f - h_axis, t - h_axis), Move(f - h_axis + kh_axis, t - h_axis + kh_axis))\n            }\n            _ => return Ok(None),\n        };\n        let pool_spec = self.pool_spec.change_geo_axes(&geo_adjusted)?;\n        let new_op = Conv { pool_spec, ..self.clone() };\n        Ok(Some(AxisChangeConsequence {\n            substitute_op: Some(Box::new(new_op)),\n            wire_changes: tvec!(\n                (InOut::In(0), change.clone()),\n                (InOut::In(1), kernel_adjusted),\n                (InOut::Out(0), change.clone())\n            ),\n        }))\n    }\n\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let input_fact = model.outlet_fact(node.inputs[0])?;\n        unsafe {\n            if self.q_params.is_some() {\n                let mut patch = TypedModelPatch::new(\"quantized-codegen\");\n                let inputs = patch.taps(model, &node.inputs)?;\n                let wire = self\n                    .wire_as_quant_im2col(&mut patch, &node.name, &inputs)\n                    .context(\"in wire_as_quant_im2col\")?;\n                patch.shunt_outside(model, node.id.into(), wire[0])?;\n                patch.obliterate(node.id)?;\n                Ok(Some(patch))\n            } else if input_fact\n                .shape\n                .as_concrete()\n                .map(|s| {\n                    should_use_lazy(\n                        &self.pool_spec.data_format.shape(s.into()).unwrap(),\n                        &self.pool_spec,\n                        self.group,\n                    )\n                })\n                .unwrap_or(false)\n            {\n                let mut patch = TypedModelPatch::new(\"lazy-im2col\");\n                let inputs = patch.taps(model, &node.inputs)?;\n                let wire = self\n                    .wire_as_lazy_im2col(&mut patch, &node.name, &inputs)\n                    .context(\"wire_as_lazy_im2col\")?[0];\n                patch.shunt_outside(model, OutletId::new(node.id, 0), wire)?;\n                patch.obliterate(node.id)?;\n                Ok(Some(patch))\n            } else if self.group != 1\n                && self.group == self.output_channels()\n                && self.group == self.input_channels()\n                && input_fact.shape.as_concrete().is_some()\n            {\n                let mut patch = TypedModelPatch::new(\"depth_wise\");\n                let inputs = patch.taps(model, &node.inputs)?;\n                let wire = self\n                    .wire_as_depth_wise(&mut patch, &node.name, &inputs)\n                    .context(\"wire_as_depth_wise\")?;\n                patch.shunt_outside(model, OutletId::new(node.id, 0), wire)?;\n                patch.obliterate(node.id)?;\n                Ok(Some(patch))\n            } else {\n                let mut patch = TypedModelPatch::new(\"im2col\");\n                let inputs = patch.taps(model, &node.inputs)?;\n                let wire = self\n                    .wire_as_im2col_pair(&mut patch, &node.name, &inputs)\n                    .context(\"in wire_as_im2col_pair\")?[0];\n                patch.shunt_outside(model, OutletId::new(node.id, 0), wire)?;\n                patch.obliterate(node.id)?;\n                Ok(Some(patch))\n            }\n        }\n    }\n\n    as_op!();\n}\n\nfn should_use_lazy(input_shape: &DataShape, pool_spec: &PoolSpec, group: usize) -> bool {\n    input_shape.n().unwrap_or(&1) == &1\n        && group == 1\n        && pool_spec.kernel_shape.iter().product::<usize>() > 5\n}\n\n#[allow(non_snake_case)]\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::ops::array::Pad;\n    use DataFormat::*;\n\n    #[test]\n    fn onnx_basic_convinteger() {\n        let op = Conv {\n            pool_spec: PoolSpec {\n                data_format: NCHW,\n                kernel_shape: tvec!(2, 2),\n                padding: Valid,\n                dilations: None,\n                strides: None,\n                input_channels: 1,\n                output_channels: 1,\n            },\n            kernel_fmt: KernelFormat::OIHW,\n            group: 1,\n            q_params: Some(i32::datum_type()),\n        };\n        let input = tvec!(\n            rctensor4(&[[[[1u8, 2, 3], [4, 5, 6], [7, 8, 9]]]]),\n            rctensor4(&[[[[1u8, 1], [1, 1]]]]),\n            rctensor0(0u32),\n            rctensor0(1u8),\n            rctensor0(1.0f32),\n            rctensor0(0u8),\n            rctensor0(1.0f32),\n            rctensor0(0i32),\n            rctensor0(1.0f32),\n        );\n        let input = input.into_iter().map(IntoTValue::into_tvalue).collect::<TVec<_>>();\n        let output = op.eval(input).unwrap();\n        assert_eq!(*output[0], tensor4(&[[[[8i32, 12], [20, 24]]]]));\n    }\n\n    #[test]\n    fn valid_conv_absorbs_precursor_pad() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let wire = tvec!(model.add_source(\"source\", f32::fact(dims!(1, 10)))?);\n        let wire = model.wire_node(\n            \"pad\",\n            Pad {\n                pads: vec![(0, 0), (1, 0)],\n                mode: ops::array::PadMode::Constant(rctensor0(0f32)),\n            },\n            &wire,\n        )?;\n        let kernel = model.add_const(\"kernel\", rctensor3(&[[[1f32, 2f32]]]))?;\n        let bias = model.add_const(\"bias\", rctensor0(0f32))?;\n        let wire = model.wire_node(\n            \"conv\",\n            Conv {\n                pool_spec: PoolSpec {\n                    data_format: crate::ops::nn::DataFormat::CHW,\n                    dilations: None,\n                    strides: None,\n                    kernel_shape: tvec![2],\n                    padding: Explicit(tvec![0], tvec![0]),\n                    input_channels: 1,\n                    output_channels: 1,\n                },\n                kernel_fmt: crate::ops::cnn::KernelFormat::OIHW,\n                group: 1,\n                q_params: None,\n            },\n            &[wire[0], kernel, bias],\n        )?;\n        model.select_output_outlets(&wire)?;\n        model.declutter()?;\n        assert_eq!(model.nodes().len(), 4); // source + conv + kernel + bias\n        let cv = model.nodes()[3].op_as::<Conv>().unwrap();\n        assert_eq!(cv.pool_spec.padding, Explicit(tvec![1], tvec![0])); // source + conv\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/conv/depth_wise.rs",
    "content": "use crate::internal::*;\nuse crate::ops::cnn::Patch;\nuse crate::ops::cnn::patches::{Zone, ZoneScanner};\nuse crate::ops::nn::DataShape;\nuse num_traits::Zero;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct DepthWise {\n    patch: Patch,\n    input_shape: DataShape,\n    output_shape: DataShape,\n}\n\nimpl Op for DepthWise {\n    fn name(&self) -> StaticName {\n        \"DepthWiseConv\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"{:?}\", self.patch)])\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Rounding\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DepthWise {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let dt = inputs[0].datum_type();\n        #[cfg(target_arch = \"aarch64\")]\n        if dt == f16::datum_type() && tract_linalg::arm64::has_fp16() {\n            return unsafe {\n                eval_t_aarch64fp16::<f16>(\n                    self,\n                    inputs,\n                    |a, b| tract_linalg::arm64::add_f16(a, b),\n                    |a, b| tract_linalg::arm64::mul_f16(a, b),\n                )\n            };\n        }\n        dispatch_floatlike!(Self::eval_gen(dt)(self, inputs))\n    }\n}\n\nimpl DepthWise {\n    fn eval_gen<T: Datum + Copy + num_traits::Zero + ndarray::LinalgScalar>(\n        &self,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        unsafe { eval_t_generic::<T>(self, inputs, |a, b| a + b, |a, b| a * b) }\n    }\n}\n\nimpl TypedOp for DepthWise {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        anyhow::ensure!(inputs.len() == 3);\n        anyhow::ensure!(\n            self.input_shape.c() == self.output_shape.c(),\n            \"DepthWiseConv must have same input and output channels\"\n        );\n        anyhow::ensure!(\n            self.input_shape.c().to_dim() == inputs[2].shape.volume(),\n            \"DepthWiseConv data has {} channels, bias has {}\",\n            self.input_shape.c(),\n            inputs[2].shape.len()\n        );\n        Ok(tvec!(inputs[0].datum_type.fact(&self.output_shape.shape)))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let [_input, kernel, _bias] = inputs else {\n            bail!(\"Depthwise expects three inputs\");\n        };\n        let n_output_points = self.patch.output_shape.iter().cloned().product::<usize>();\n        Ok(tvec!((\n            Cost::FMA(inputs[0].datum_type),\n            kernel.shape.volume() * self.input_shape.n().unwrap_or(&1) * n_output_points\n        )))\n    }\n\n    as_op!();\n}\n\nmacro_rules! impl_eval {\n    ($(#[$meta: meta])* $suffix: ident ) => {\n        pastey::paste! {\n            $(#[$meta])*\n            unsafe fn [<eval_t_ $suffix>]<T: Datum + Copy + num_traits::Zero + ndarray::LinalgScalar>(\n                dw: &DepthWise,\n                inputs: TVec<TValue>,\n                add: impl Fn(T, T) -> T + Copy + 'static,\n                mul: impl Fn(T, T) -> T + Copy + 'static,\n            ) -> TractResult<TVec<TValue>> {\n                let (img, kernel, bias) = args_3!(inputs);\n                let mut output = unsafe { Tensor::uninitialized::<T>(&dw.output_shape.shape)? };\n                let iptr = img.as_ptr::<T>()?;\n                let optr = output.as_ptr_mut::<T>()?;\n                let k_stride_i = kernel.strides()[1];\n                let n = *dw.input_shape.n().unwrap_or(&1);\n                let n_stride_i = *dw.input_shape.n_stride().unwrap_or(&0) as isize;\n                let n_stride_o = *dw.output_shape.n_stride().unwrap_or(&0) as isize;\n                let c_stride_i = *dw.input_shape.c_stride() as isize;\n                let c_stride_o = *dw.output_shape.c_stride() as isize;\n                let bias = bias.as_ptr::<T>()?;\n                let kptr = kernel.as_ptr::<T>()?;\n                unsafe {\n                    for n in 0..n as isize {\n                        let iptr = iptr.offset(n_stride_i * n);\n                        let optr = optr.offset(n_stride_o * n);\n                        for zone in &dw.patch.zones {\n                            [<process_zone_ $suffix>](\n                                dw, zone, c_stride_i, c_stride_o, k_stride_i, iptr, kptr, bias, optr,\n                                add, mul,\n                            )\n                        }\n                    }\n                }\n                Ok(tvec!(output.into_tvalue()))\n            }\n\n            #[inline(never)]\n            #[allow(clippy::too_many_arguments)]\n            $(#[$meta])*\n            unsafe fn [<process_zone_ $suffix>]<T: Datum + Copy + Zero>(\n                dw: &DepthWise,\n                zone: &Zone,\n                c_stride_i: isize,\n                c_stride_o: isize,\n                k_stride_i: isize,\n                iptr: *const T,\n                kptr: *const T,\n                bias: *const T,\n                optr: *mut T,\n                add: impl Fn(T, T) -> T + Copy + 'static,\n                mul: impl Fn(T, T) -> T + Copy + 'static,\n                ) { unsafe {\n                /*\n                   if zone.values_offsets.len() == 2 {\n                   self.process_zone_n::<T, 2, 4>(\n                   zone, c_stride_i, c_stride_o, k_stride_i, iptr, kptr, bias, optr,\n                   )\n                   } else if zone.values_offsets.len() == 3 {\n                   dw.process_zone_n::<T, 3, 4>(\n                   zone, c_stride_i, c_stride_o, k_stride_i, iptr, kptr, bias, optr,\n                   )\n                   } else */\n                if zone.values_offsets.len() == 4 {\n                    [<process_zone_n_ $suffix>]::<T, 4, 4>(\n                        dw, zone, c_stride_i, c_stride_o, k_stride_i, iptr, kptr, bias, optr, add, mul,\n                        )\n                        /*\n                           } else if zone.values_offsets.len() == 5 {\n                           dw.process_zone_n::<T, 5, 2>(\n                           zone, c_stride_i, c_stride_o, k_stride_i, iptr, kptr, bias, optr,\n                           )\n                           } else if zone.values_offsets.len() == 9 {\n                           dw.process_zone_n::<T, 9, 1>(\n                           zone, c_stride_i, c_stride_o, k_stride_i, iptr, kptr, bias, optr,\n                           )\n                           */\n                } else {\n                    zone.visit_output(&dw.patch, |visitor| {\n                        for c in 0..*dw.input_shape.c() as isize {\n                            let iptr = iptr.offset(c_stride_i * c);\n                            let optr = optr.offset(c_stride_o * c);\n                            let kptr = kptr.offset(k_stride_i * c);\n                            [<inner_loop_ $suffix>]::<T>(iptr, kptr, bias, optr, c, visitor, add, mul)\n                        }\n                    })\n                }\n            }}\n\n            #[inline(never)]\n            #[allow(clippy::too_many_arguments)]\n            $(#[$meta])*\n            unsafe fn [<process_zone_n_ $suffix>]<T: Datum + Copy + Zero, const N: usize, const UNROLL: usize>(\n                dw: &DepthWise,\n                zone: &Zone,\n                c_stride_i: isize,\n                c_stride_o: isize,\n                k_stride_i: isize,\n                iptr: *const T,\n                kptr: *const T,\n                bias: *const T,\n                optr: *mut T,\n                add: impl Fn(T, T) -> T,\n                mul: impl Fn(T, T) -> T,\n                ) { unsafe {\n                let mut visitor = ZoneScanner::new(zone, &dw.patch);\n                let mut ioffset = [0isize; N];\n                for i in 0..N {\n                    ioffset[i] = zone.values_offsets[i].1;\n                }\n                let mut k = [T::zero(); N];\n                for c in 0..*dw.input_shape.c() as isize {\n                    visitor.reset();\n                    let iptr = iptr.offset(c_stride_i * c);\n                    let optr = optr.offset(c_stride_o * c);\n                    for n in 0..N {\n                        k[n] = *kptr.offset(k_stride_i * c).add(zone.values_offsets[n].0);\n                    }\n                    let bias = *bias.offset(c);\n                    while !visitor.done {\n                        let iptr = iptr.offset(visitor.input_center_offset);\n                        let optr = optr.offset(visitor.output_offset);\n                        let mut i = 0isize;\n                        while i + (UNROLL as isize) < visitor.inner_loop_len as isize {\n                            let iptr = iptr.offset(visitor.inner_loop_input_full_stride * i);\n                            let optr = optr.offset(visitor.inner_loop_output_stride * i);\n                            let mut iptrs = [std::ptr::null(); UNROLL];\n                            for u in 0..UNROLL {\n                                iptrs[u] = iptr.offset(visitor.inner_loop_input_full_stride * u as isize);\n                            }\n                            let mut optrs = [std::ptr::null_mut(); UNROLL];\n                            for u in 0..UNROLL {\n                                optrs[u] = optr.offset(visitor.inner_loop_output_stride * u as isize);\n                            }\n                            let mut is = [[T::zero(); N]; UNROLL];\n                            for u in 0..UNROLL {\n                                for n in 0..N {\n                                    is[u][n] = *iptrs[u].offset(ioffset[n]);\n                                }\n                            }\n                            let mut ps = [[T::zero(); N]; UNROLL];\n                            for u in 0..UNROLL {\n                                for n in 0..N {\n                                    ps[u][n] = mul(is[u][n], k[n]);\n                                }\n                            }\n                            for u in 0..UNROLL {\n                                let mut sum = bias;\n                                for n in 0..N {\n                                    sum = add(sum, ps[u][n]);\n                                }\n                                *optrs[u] = sum;\n                            }\n                            i += UNROLL as isize;\n                        }\n                        while i < visitor.inner_loop_len as isize {\n                            let iptr = iptr.offset(visitor.inner_loop_input_full_stride * i);\n                            let optr = optr.offset(visitor.inner_loop_output_stride * i);\n                            let mut is = [T::zero(); N];\n                            for n in 0..N {\n                                is[n] = *iptr.offset(ioffset[n]);\n                            }\n                            let mut p = [T::zero(); N];\n                            for n in 0..N {\n                                p[n] = mul(is[n], k[n]);\n                            }\n                            let mut sum = bias;\n                            for n in 0..N {\n                                sum = add(sum, p[n]);\n                            }\n                            *optr = sum;\n                            i += 1;\n                        }\n                        visitor.next_non_inner_axis()\n                    }\n                }\n            }}\n\n            #[inline(never)]\n            #[allow(clippy::too_many_arguments)]\n            $(#[$meta])*\n            unsafe fn [<inner_loop_ $suffix>]<T: Datum + Copy>(\n                iptr: *const T,\n                kptr: *const T,\n                bias: *const T,\n                optr: *mut T,\n                c: isize,\n                visitor: &ZoneScanner,\n                add: impl Fn(T, T) -> T,\n                mul: impl Fn(T, T) -> T,\n                ) { unsafe {\n                let mut sum = *bias.offset(c);\n                let mut iter = visitor.valid_offsets_ker_in();\n                if iter.size_hint() == (3, Some(3)) {\n                    let (ix, v) = iter.next().unwrap();\n                    let k0 = *kptr.add(ix);\n                    let i0 = *iptr.offset(v);\n                    let (ix, v) = iter.next().unwrap();\n                    let k1 = *kptr.add(ix);\n                    let i1 = *iptr.offset(v);\n                    let (ix, v) = iter.next().unwrap();\n                    let k2 = *kptr.add(ix);\n                    let i2 = *iptr.offset(v);\n                    sum = add(add(add(sum, mul(k0, i0)), mul(k1, i1)), mul(k2, i2));\n                } else {\n                    for (ix, v) in iter {\n                        let k = *kptr.add(ix);\n                        let i = *iptr.offset(v);\n                        sum = add(sum, mul(k, i));\n                    }\n                }\n                let optr = optr.offset(visitor.output_offset);\n                *optr = sum;\n            }}\n        }\n    }\n}\n\nimpl_eval!(generic);\nimpl_eval! {\n#[target_feature(enable = \"fp16\")]\n#[cfg(target_arch = \"aarch64\")]\naarch64fp16\n}\n//#[target_feature(enable = \"fp16\")] impl_eval!(aarch64fp16);\n\n/* partial alternative impl that may be relevant when simd gets better */\n\n/*\n#[inline(never)]\nunsafe fn process_zone_4_f32(\n&self,\nzone: &Zone,\nc_stride_i: isize,\nc_stride_o: isize,\nk_stride_i: isize,\niptr: *const f32,\nkptr: *const f32,\nbias: *const f32,\noptr: *mut f32,\n) {\nuse std::simd::*;\nlet mut visitor = ZoneScanner::new(zone, &self.patch);\nlet ioffset0 = zone.values_offsets[0].1;\nlet ioffset1 = zone.values_offsets[1].1;\nlet ioffset2 = zone.values_offsets[2].1;\nlet ioffset3 = zone.values_offsets[3].1;\nfor c in 0..*self.input_shape.c() as isize {\nvisitor.reset();\nlet kptr = kptr.offset(k_stride_i * c);\nlet iptr = iptr.offset(c_stride_i * c);\nlet optr = optr.offset(c_stride_o * c);\nlet k0 = *kptr.offset(zone.values_offsets[0].0 as isize);\nlet k1 = *kptr.offset(zone.values_offsets[1].0 as isize);\nlet k2 = *kptr.offset(zone.values_offsets[2].0 as isize);\nlet k3 = *kptr.offset(zone.values_offsets[3].0 as isize);\nlet k0 = f32x4::splat(k0);\nlet k1 = f32x4::splat(k1);\nlet k2 = f32x4::splat(k2);\nlet k3 = f32x4::splat(k3);\nlet bias = f32x4::splat(*bias.offset(c));\nwhile !visitor.done {\nlet iptr = iptr.offset(visitor.input_center_offset);\nlet optr = optr.offset(visitor.output_offset);\nlet mut i  = 0;\nwhile i + 4 <\nfor i in 0..visitor.inner_loop_len as isize {\nlet iptr = iptr.offset(visitor.inner_loop_input_full_stride * i);\nlet optr = optr.offset(visitor.inner_loop_output_stride * i);\nlet i0 = *iptr.offset(ioffset0);\nlet i1 = *iptr.offset(ioffset1);\nlet i2 = *iptr.offset(ioffset2);\nlet i3 = *iptr.offset(ioffset3);\nlet i = f32x4::from_array([i0, i1, i2, i3]);\nlet p = (i * k).reduce_sum();\nlet sum = bias + p;\n     *optr = sum\n     }\n     visitor.next_non_inner_axis()\n     }\n     }\n     }\n     */\n\n/*\n#[inline(never)]\nunsafe fn process_zone_4_f32(\n&self,\nzone: &Zone,\nc_stride_i: isize,\nc_stride_o: isize,\nk_stride_i: isize,\niptr: *const f32,\nkptr: *const f32,\nbias: *const f32,\noptr: *mut f32,\n) {\nuse std::simd::*;\nlet mut visitor = ZoneScanner::new(zone, &self.patch);\nlet ioffset0 = zone.values_offsets[0].1;\nlet ioffset1 = zone.values_offsets[1].1;\nlet ioffset2 = zone.values_offsets[2].1;\nlet ioffset3 = zone.values_offsets[3].1;\nfor c in 0..*self.input_shape.c() as isize {\nvisitor.reset();\nlet kptr = kptr.offset(k_stride_i * c);\nlet iptr = iptr.offset(c_stride_i * c);\nlet optr = optr.offset(c_stride_o * c);\nlet k0 = *kptr.offset(zone.values_offsets[0].0 as isize);\nlet k1 = *kptr.offset(zone.values_offsets[1].0 as isize);\nlet k2 = *kptr.offset(zone.values_offsets[2].0 as isize);\nlet k3 = *kptr.offset(zone.values_offsets[3].0 as isize);\nlet k = f32x4::from_array([k0, k1, k2, k3]);\nlet bias = *bias.offset(c);\nwhile !visitor.done {\nlet iptr = iptr.offset(visitor.input_center_offset);\nlet optr = optr.offset(visitor.output_offset);\nfor i in 0..visitor.inner_loop_len as isize {\nlet iptr = iptr.offset(visitor.inner_loop_input_full_stride * i);\nlet optr = optr.offset(visitor.inner_loop_output_stride * i);\nlet i0 = *iptr.offset(ioffset0);\nlet i1 = *iptr.offset(ioffset1);\nlet i2 = *iptr.offset(ioffset2);\nlet i3 = *iptr.offset(ioffset3);\nlet i = f32x4::from_array([i0, i1, i2, i3]);\nlet p = (i * k).reduce_sum();\nlet sum = bias + p;\n     *optr = sum\n     }\n     visitor.next_non_inner_axis()\n     }\n     }\n     }\n     */\n\n/*\n#[inline(never)]\nunsafe fn process_zone_4<T: Datum + Copy + ndarray::LinalgScalar>(\n&self,\nzone: &Zone,\nc_stride_i: isize,\nc_stride_o: isize,\nk_stride_i: isize,\niptr: *const T,\nkptr: *const T,\nbias: *const T,\noptr: *mut T,\n) {\nlet mut visitor = ZoneScanner::new(zone, &self.patch);\nlet ioffset0 = zone.values_offsets[0].1;\nlet ioffset1 = zone.values_offsets[1].1;\nlet ioffset2 = zone.values_offsets[2].1;\nlet ioffset3 = zone.values_offsets[3].1;\nfor c in 0..*self.input_shape.c() as isize {\nvisitor.reset();\nlet kptr = kptr.offset(k_stride_i * c);\nlet iptr = iptr.offset(c_stride_i * c);\nlet optr = optr.offset(c_stride_o * c);\nlet k0 = *kptr.offset(zone.values_offsets[0].0 as isize);\nlet k1 = *kptr.offset(zone.values_offsets[1].0 as isize);\nlet k2 = *kptr.offset(zone.values_offsets[2].0 as isize);\nlet k3 = *kptr.offset(zone.values_offsets[3].0 as isize);\nlet bias = *bias.offset(c);\nwhile !visitor.done {\nlet iptr = iptr.offset(visitor.input_center_offset);\nlet optr = optr.offset(visitor.output_offset);\nfor i in 0..visitor.inner_loop_len as isize {\nlet iptr = iptr.offset(visitor.inner_loop_input_full_stride * i);\nlet optr = optr.offset(visitor.inner_loop_output_stride * i);\nlet i0 = *iptr.offset(ioffset0);\nlet i1 = *iptr.offset(ioffset1);\nlet i2 = *iptr.offset(ioffset2);\nlet i3 = *iptr.offset(ioffset3);\nlet p0 = i0 * k0;\nlet p1 = i1 * k1;\nlet p2 = i2 * k2;\nlet p3 = i3 * k3;\nlet sum = bias + p0 + p1 + p2 + p3;\n     *optr = sum\n     }\n     visitor.next_non_inner_axis()\n     }\n     }\n     }\n     */\n"
  },
  {
    "path": "core/src/ops/cnn/conv/im2col.rs",
    "content": "use tract_linalg::mmm::{\n    EagerPackedInput, MMMInputValue, MatMatMul, PackedExoticFact, PackedMatrixStorage,\n};\nuse tract_linalg::pack::{PackedFormat, PackingWriter};\n\nuse crate::internal::*;\nuse ndarray::prelude::*;\nuse num_integer::Integer;\n\nuse crate::ops::cnn::pools::{ConcretePoolGeometry, PoolGeometry};\nuse crate::ops::cnn::{GeometryBound, PoolSpec, ResolveTo};\nuse crate::ops::nn::{BaseDataShape, DataFormat, DataShape};\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct Im2Col {\n    pub pool_spec: PoolSpec,\n    pub group: usize,\n    geometry: GeometryBound<SymbolicGeometry, ConcreteGeometry>,\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct SymbolicGeometry {\n    group: usize,\n    pool_spec: PoolSpec,\n    pool_geometry: PoolGeometry,\n    b_pack: PackedFormat,\n    k: usize,\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct ConcreteGeometry {\n    pool: ConcretePoolGeometry,\n    pub n: usize,\n    k: usize,\n    pub b_pack: PackedFormat,\n    pub ci_per_group: usize,\n    patcher: Patcher,\n    input_shape_with_n: DataShape,\n    packed_shape: TVec<usize>, // always Batch,Group\n}\n\nimpl GeometryBound<SymbolicGeometry, ConcreteGeometry> {\n    pub fn b_pack(&self) -> &PackedFormat {\n        match self {\n            GeometryBound::Symbolic(s) => &s.b_pack,\n            GeometryBound::Concrete(s) => &s.b_pack,\n        }\n    }\n    pub fn k(&self) -> usize {\n        match self {\n            GeometryBound::Symbolic(s) => s.k,\n            GeometryBound::Concrete(s) => s.k,\n        }\n    }\n}\n\nimpl ResolveTo<ConcreteGeometry> for SymbolicGeometry {\n    type Param = [usize];\n    fn resolve(&self, input_full_shape: &[usize]) -> TractResult<ConcreteGeometry> {\n        let pool = self.pool_geometry.to_concrete(input_full_shape)?.into_owned();\n        let patcher = if !pool.patch.padded && pool.patch.rank() == 2 {\n            Patcher::Valid2d\n        } else if pool.patch.rank() == 2 {\n            Patcher::Padded2d\n        } else if !pool.patch.padded && pool.patch.rank() == 1 {\n            Patcher::Valid1d\n        } else {\n            Patcher::Generic\n        };\n        let ci_per_group = pool.input_shape.c_dim() / self.group;\n        let n = pool.output_shape.hw_dims().iter().product();\n        let input_shape_with_n = match self.pool_spec.data_format {\n            DataFormat::HWC => DataFormat::NHWC.from_n_c_hw(\n                1,\n                *pool.input_shape.c(),\n                pool.input_shape.hw_dims(),\n            )?,\n            DataFormat::CHW => DataFormat::NCHW.from_n_c_hw(\n                1,\n                *pool.input_shape.c(),\n                pool.input_shape.hw_dims(),\n            )?,\n            _ => pool.input_shape.clone(),\n        };\n        let packed_shape = Im2Col::packed_shape(&pool.input_shape, self.group)?;\n        Ok(ConcreteGeometry {\n            pool,\n            n,\n            k: self.k,\n            ci_per_group,\n            b_pack: self.b_pack.clone(),\n            patcher,\n            input_shape_with_n,\n            packed_shape,\n        })\n    }\n}\n\nimpl Im2Col {\n    pub fn new(\n        pool_spec: PoolSpec,\n        group: usize,\n        k: usize,\n        input_full_shape: &ShapeFact,\n        mmm: Box<dyn MatMatMul>,\n        packing: usize,\n    ) -> TractResult<Im2Col> {\n        let b_pack = mmm.packings()[packing]\n            .1\n            .downcast_ref::<PackedFormat>()\n            .context(\"Im2Col expects regular packed format\")?\n            .clone();\n\n        let pool_geometry = pool_spec.compute_geo(input_full_shape)?;\n        let geometry: GeometryBound<_, _> =\n            SymbolicGeometry { group, pool_spec: pool_spec.clone(), pool_geometry, b_pack, k }\n                .into();\n        let geometry = geometry.optimize_if(input_full_shape.as_concrete())?;\n        Ok(Im2Col { pool_spec, group, geometry })\n    }\n\n    // packed shape is Batch,Group\n    fn packed_shape<D: DimLike>(\n        input_shape: &BaseDataShape<D, TVec<D>>,\n        group: usize,\n    ) -> TractResult<TVec<D>> {\n        let mut output_shape: TVec<D> = tvec!();\n        output_shape.push(input_shape.n().cloned().unwrap_or_else(|| 1.into()));\n        output_shape.push(group.into());\n        Ok(output_shape)\n    }\n}\n\nimpl Op for Im2Col {\n    fn name(&self) -> StaticName {\n        \"Im2col\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"groups:{}\", self.group)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Im2Col {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, mut inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let geometry = self.geometry.to_concrete(inputs[0].shape())?;\n        unsafe {\n            let mut input = inputs.remove(0).into_tensor();\n            let pad_value: Option<&Tensor> = if inputs.len() > 0 { Some(&inputs[0]) } else { None };\n            if !self.pool_spec.data_format.has_n() {\n                input.insert_axis(0)?;\n            }\n            let panel_bytes =\n                geometry.b_pack.single_panel_len(geometry.k) * input.datum_type().size_of();\n\n            let n_batches = *geometry.input_shape_with_n.n().unwrap_or(&1);\n            let n_groups = self.group;\n            let mut values: Vec<Box<dyn MMMInputValue>> = Vec::with_capacity(n_batches * n_groups);\n\n            for i in 0..n_batches {\n                let input = input.view_at_prefix(&[i])?;\n                for g in 0..n_groups {\n                    let n =\n                        if geometry.pool.output_shape.shape.contains(&0) { 0 } else { geometry.n };\n                    let mut data = Tensor::uninitialized_aligned_dt(\n                        input.datum_type(),\n                        &[geometry.b_pack.len(geometry.k, n)],\n                        geometry.b_pack.alignment(),\n                    )?;\n                    if n > 0 {\n                        dispatch_copy_by_size!(Patcher::patch(input.datum_type())(\n                            &geometry.patcher,\n                            &geometry,\n                            &input,\n                            &mut data.view_mut(),\n                            g,\n                            pad_value\n                        ))?;\n                    }\n                    values.push(Box::new(EagerPackedInput {\n                        fact: PackedExoticFact {\n                            format: Box::new(geometry.b_pack.clone()),\n                            k: geometry.k,\n                            mn: n.to_dim(),\n                        },\n                        packed: data.into_blob()?.into(),\n                        panel_bytes: if n > 0 { panel_bytes } else { 0 },\n                        mn: n,\n                    }));\n                }\n            }\n\n            let output = PackedMatrixStorage::new_batched(&geometry.packed_shape, values)\n                .into_tensor(input.datum_type());\n            Ok(tvec!(output.into_tvalue()))\n        }\n    }\n}\n\nimpl TypedOp for Im2Col {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let input_shape = self.pool_spec.data_format.shape(inputs[0].shape.to_tvec())?;\n        let output_shape = self.pool_spec.output_shape(&inputs[0].shape)?;\n        let mn = output_shape.hw_dims().iter().product::<TDim>();\n        let pof = PackedExoticFact {\n            format: Box::new(self.geometry.b_pack().clone()),\n            k: self.geometry.k(),\n            mn,\n        };\n        Ok(tvec!(\n            inputs[0]\n                .datum_type\n                .fact(&[input_shape.n().cloned().unwrap_or(1.into()), self.group.into()])\n                .with_exotic_fact(pof)\n        ))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let input_fact = model.outlet_fact(node.inputs[0])?;\n        if node.inputs.len() == 2\n            && model.outlet_fact(node.inputs[1])?.konst.as_ref().and_then(|t| t.as_uniform())\n                == Some(Tensor::zero_scalar_dt(input_fact.datum_type)?)\n        {\n            Ok(Some(\n                TypedModelPatch::replace_single_op(model, node, &node.inputs[0..1], self.clone())?\n                    .with_context(\"b0 is zero\"),\n            ))\n        } else {\n            Ok(None)\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]\nenum Patcher {\n    Generic,\n    Valid1d,\n    Valid2d,\n    Padded2d,\n}\n\nimpl Patcher {\n    fn patch<'p, T: Copy + Datum + num_traits::Zero>(\n        &self,\n        geo: &'p ConcreteGeometry,\n        input: &TensorView,\n        pack: &'p mut TensorView,\n        g: usize,\n        pad_value: Option<&Tensor>,\n    ) -> TractResult<()> {\n        match self {\n            Patcher::Valid1d => Self::valid_1d::<T>(geo, input, pack, g),\n            Patcher::Valid2d => Self::valid_2d::<T>(geo, input, pack, g),\n            Patcher::Padded2d => Self::padded_2d::<T>(\n                geo,\n                input,\n                pack,\n                g,\n                pad_value.unwrap_or(&Tensor::zero_scalar::<T>()?),\n            ),\n            _ => Self::generic::<T>(\n                geo,\n                input,\n                pack,\n                g,\n                pad_value.unwrap_or(&Tensor::zero_scalar::<T>()?),\n            ),\n        }\n    }\n\n    #[inline(never)]\n    fn generic<'p, T: Copy + Datum>(\n        geometry: &'p ConcreteGeometry,\n        input: &TensorView,\n        pack: &'p mut TensorView,\n        g: usize,\n        pad_value: &Tensor,\n    ) -> TractResult<()> {\n        unsafe {\n            let pad_value = *pad_value.to_scalar_unchecked();\n            let mut mega_matrix = Tensor::uninitialized::<T>(&[geometry.k, geometry.n])?;\n            let mut mega_matrix_view = mega_matrix.to_array_view_mut_unchecked::<T>();\n            let ptr = input.as_ptr_unchecked::<T>();\n            let ptr = ptr.add(geometry.input_shape_with_n.c_stride() * (g * geometry.ci_per_group));\n            for (spatial, mut col) in ndarray::indices(&*geometry.pool.patch.output_shape)\n                .into_iter()\n                .zip(mega_matrix_view.axis_iter_mut(Axis(1)))\n            {\n                let mut col = col.iter_mut();\n                for ci in 0..geometry.ci_per_group {\n                    let ptr = ptr.add(geometry.input_shape_with_n.c_stride() * ci);\n                    for v in geometry.pool.patch.at(spatial.slice()) {\n                        *col.next().expect(\"geometry error in conv\") =\n                            v.map(|o| *ptr.offset(o)).unwrap_or(pad_value);\n                    }\n                }\n            }\n            geometry.b_pack.pack(pack, mega_matrix.view(), 0, 1);\n            Ok(())\n        }\n    }\n\n    #[inline(never)]\n    fn valid_1d<'p, T: Copy + Datum>(\n        geometry: &'p ConcreteGeometry,\n        input: &TensorView,\n        pack: &'p mut TensorView,\n        g: usize,\n    ) -> TractResult<()> {\n        unsafe {\n            let x_stride = *geometry.input_shape_with_n.h_stride() as isize\n                * geometry.pool.patch.spec.strides[0] as isize;\n            let c_stride = *geometry.input_shape_with_n.c_stride() as isize;\n            let pack = pack.as_slice_mut_unchecked::<T>();\n            let mut writer =\n                geometry.b_pack.write_with_k_outer(pack.as_mut_ptr(), geometry.k, geometry.n);\n            let iptr = input.as_ptr_unchecked::<T>();\n            let iptr = iptr.add(g * geometry.ci_per_group * geometry.input_shape_with_n.c_stride());\n            for ci in 0..geometry.ci_per_group {\n                let iptr = iptr.offset(ci as isize * c_stride);\n                for koffset in &geometry.pool.patch.standard_layout_data_field {\n                    let iptr = iptr.offset(*koffset);\n                    for x in 0..*geometry.pool.patch.output_shape.get_unchecked(0) {\n                        writer.write(*iptr.offset(x as isize * x_stride));\n                    }\n                }\n            }\n            Ok(())\n        }\n    }\n\n    #[inline(never)]\n    fn padded_2d<'p, T: Copy + Datum>(\n        geometry: &'p ConcreteGeometry,\n        input: &TensorView,\n        pack: &'p mut TensorView,\n        g: usize,\n        pad_value: &Tensor,\n    ) -> TractResult<()> {\n        unsafe {\n            let pad_value = *pad_value.to_scalar_unchecked();\n            let pack = pack.as_slice_mut_unchecked::<T>();\n            let y_stride = geometry.pool.patch.spec.strides[0] as isize;\n            let x_stride = geometry.pool.patch.spec.strides[1] as isize;\n            let shape = &geometry.input_shape_with_n;\n            let y_stride_ptr = y_stride * *shape.h_stride() as isize;\n            let x_stride_ptr = x_stride * *shape.w_stride() as isize;\n            let c_stride_ptr = *shape.c_stride() as isize;\n            let input_heigth = shape.hw_dims()[0] as isize;\n            let input_width = shape.hw_dims()[1] as isize;\n            let kernel_len = geometry.pool.patch.standard_layout_data_field.len();\n            let mut writer =\n                geometry.b_pack.write_with_k_outer(pack.as_mut_ptr(), geometry.k, geometry.n);\n            let iptr = input.as_ptr_unchecked::<T>();\n            let iptr = iptr.add(g * geometry.ci_per_group * shape.c_stride());\n            let output_width = *geometry.pool.patch.output_shape.get_unchecked(1);\n            for ci in 0..geometry.ci_per_group {\n                let iptr = iptr.offset(ci as isize * c_stride_ptr);\n                for kitem in 0..kernel_len {\n                    let dy = *geometry.pool.patch.data_field.as_ptr().offset(kitem as isize * 2);\n                    let dx =\n                        *geometry.pool.patch.data_field.as_ptr().offset(1 + kitem as isize * 2);\n                    let valid_x_start =\n                        Integer::div_ceil(&-dx, &x_stride).max(0).min(output_width as _);\n                    let valid_x_end = Integer::div_ceil(&(input_width - dx), &x_stride)\n                        .max(0)\n                        .min(output_width as _);\n\n                    let iptr = iptr.offset(\n                        *geometry.pool.patch.standard_layout_data_field.get_unchecked(kitem),\n                    );\n                    for yo in 0..*geometry.pool.patch.output_shape.get_unchecked(0) {\n                        let y = yo as isize * y_stride + dy;\n                        let iptr = iptr.offset(yo as isize * y_stride_ptr);\n                        if y >= 0 && y < input_heigth {\n                            Self::padded_2d_invalid_x_loop(\n                                valid_x_start as usize,\n                                pad_value,\n                                &mut writer,\n                            );\n                            Self::padded_2d_valid_x_loop(\n                                valid_x_start,\n                                valid_x_end,\n                                x_stride_ptr,\n                                iptr,\n                                &mut writer,\n                            );\n                            Self::padded_2d_invalid_x_loop(\n                                output_width - valid_x_end as usize,\n                                pad_value,\n                                &mut writer,\n                            );\n                        } else {\n                            Self::padded_2d_invalid_x_loop(output_width, pad_value, &mut writer);\n                        }\n                    }\n                }\n            }\n        }\n        Ok(())\n    }\n\n    #[inline(never)]\n    unsafe fn padded_2d_invalid_x_loop<T: Copy + Datum>(\n        count: usize,\n        pad_value: T,\n        writer: &mut tract_linalg::pack::KOutWriter<T>,\n    ) {\n        for _ in 0..count {\n            writer.write(pad_value);\n        }\n    }\n\n    #[inline(never)]\n    unsafe fn padded_2d_valid_x_loop<T: Copy + Datum>(\n        x_min: isize,\n        x_max: isize,\n        x_stride_ptr: isize,\n        iptr: *const T,\n        writer: &mut tract_linalg::pack::KOutWriter<T>,\n    ) {\n        for x in x_min..x_max {\n            writer.write(unsafe { *iptr.offset(x * x_stride_ptr) });\n        }\n    }\n\n    #[inline(never)]\n    fn valid_2d<'p, T: Copy + Datum>(\n        geometry: &'p ConcreteGeometry,\n        input: &TensorView,\n        pack: &'p mut TensorView,\n        g: usize,\n    ) -> TractResult<()> {\n        unsafe {\n            let pack = pack.as_slice_mut_unchecked::<T>();\n            let shape = &geometry.input_shape_with_n;\n            let y_stride = geometry.pool.patch.spec.strides[0] as isize;\n            let x_stride = geometry.pool.patch.spec.strides[1] as isize;\n            let y_stride_ptr = y_stride * *shape.h_stride() as isize;\n            let x_stride_ptr = x_stride * *shape.w_stride() as isize;\n            let c_stride_ptr = *shape.c_stride() as isize;\n            let mut writer =\n                geometry.b_pack.write_with_k_outer(pack.as_mut_ptr(), geometry.k, geometry.n);\n            let iptr = input.as_ptr_unchecked::<T>();\n            let iptr = iptr.add(g * geometry.ci_per_group * shape.c_stride());\n            for ci in 0..geometry.ci_per_group {\n                let iptr = iptr.offset(ci as isize * c_stride_ptr);\n                for koffset in &geometry.pool.patch.standard_layout_data_field {\n                    let iptr = iptr.offset(*koffset);\n                    for y in 0..*geometry.pool.patch.output_shape.get_unchecked(0) {\n                        let iptr = iptr.offset(y as isize * y_stride_ptr);\n                        for x in 0..*geometry.pool.patch.output_shape.get_unchecked(1) {\n                            writer.write(*iptr.offset(x as isize * x_stride_ptr));\n                        }\n                    }\n                }\n            }\n            Ok(())\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/conv/lazy_im2col.rs",
    "content": "use crate::internal::*;\nuse crate::ops::matmul::pack::DynPackedExoticFact;\nuse std::fmt::{Debug, Display};\nuse std::ops::Range;\nuse tract_linalg::WeightType;\nuse tract_linalg::mmm::{MMMInputFormat, MMMInputValue, PackedMatrixStorage};\nuse tract_linalg::pack::{PackedFormat, PackingWriter};\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct LazyIm2colParams {\n    pub packer: PackedFormat,\n    pub n_byte_offsets: Vec<isize>,\n    pub k_byte_offsets: Vec<isize>,\n}\n\nimpl MMMInputFormat for LazyIm2colParams {\n    fn r(&self) -> usize {\n        self.packer.r\n    }\n\n    fn precursor(&self) -> WeightType {\n        self.packer.precursor()\n    }\n\n    fn prepare_tensor(&self, _t: &Tensor, _k_axis: usize, _mn_axis: usize) -> TractResult<Tensor> {\n        bail!(\"Unexpected call to prepare_tensor on LazyIm2Col\")\n    }\n\n    fn k_alignment(&self) -> usize {\n        1\n    }\n\n    fn mem_size(&self, k: TDim, mn: TDim) -> TDim {\n        k * mn * self.packer.dt.size_of()\n    }\n\n    fn extract_at_mn_f16(\n        &self,\n        _data: &tract_linalg::mmm::EagerPackedInput,\n        _mn: usize,\n        _slice: &mut [f16],\n    ) -> TractResult<()> {\n        unimplemented!()\n    }\n    fn extract_at_mn_f32(\n        &self,\n        _data: &tract_linalg::mmm::EagerPackedInput,\n        _mn: usize,\n        _slice: &mut [f32],\n    ) -> TractResult<()> {\n        unimplemented!()\n    }\n\n    fn prepare_one(\n        &self,\n        _t: &Tensor,\n        _k_axis: usize,\n        _mn_axis: usize,\n    ) -> TractResult<Box<dyn MMMInputValue>> {\n        bail!(\"Unexpected call to prepare_one on LazyIm2Col\")\n    }\n}\n\nimpl Display for LazyIm2colParams {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"LazyIm2Col\")\n    }\n}\n\nimpl ExoticFact for LazyIm2colParams {\n    fn buffer_sizes(&self) -> TVec<TDim> {\n        tvec!(MMMInputFormat::mem_size(\n            self,\n            self.k_byte_offsets.len().to_dim(),\n            self.n_byte_offsets.len().to_dim(),\n        ))\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct LazyIm2Col {\n    pub params: Arc<LazyIm2colParams>,\n}\n\nimpl Op for LazyIm2Col {\n    fn name(&self) -> StaticName {\n        \"LazyIm2col\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for LazyIm2Col {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let tensor = args_1!(inputs);\n        let dt = tensor.datum_type();\n        let input: Box<dyn MMMInputValue> =\n            Box::new(LazyIm2colInput { tensor, im2col: self.params.clone() });\n        let output = PackedMatrixStorage::new_batched(&[1, 1], vec![input]).into_tensor(dt);\n        Ok(tvec!(output.into_tvalue()))\n    }\n}\n\nimpl TypedOp for LazyIm2Col {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let exotic_fact = DynPackedExoticFact {\n            k: self.params.k_byte_offsets.len().to_dim(),\n            mn: self.params.n_byte_offsets.len().to_dim(),\n            packers: vec![self.params.packer.clone()],\n        };\n        Ok(tvec!(inputs[0].datum_type.fact([1, 1]).with_exotic_fact(exotic_fact)))\n    }\n\n    as_op!();\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\nstruct LazyIm2colInput {\n    tensor: TValue,\n    im2col: Arc<LazyIm2colParams>,\n}\n\nimpl Display for LazyIm2colInput {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl Hash for LazyIm2colInput {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        (self.tensor.as_bytes(), &self.im2col).hash(state);\n    }\n}\n\nunsafe impl Send for LazyIm2colInput {}\nunsafe impl Sync for LazyIm2colInput {}\n\nimpl LazyIm2colInput {\n    fn input_8n<T: Datum + Copy>(\n        &self,\n        writer: &mut impl PackingWriter<T>,\n        k_range: Range<isize>,\n        n: isize,\n    ) {\n        let k_byte_offsets = self.im2col.k_byte_offsets.as_ptr();\n        let n_byte_offsets = self.im2col.n_byte_offsets.as_ptr();\n        unsafe {\n            let ptr = self.tensor.as_ptr_unchecked::<u8>();\n            let o1 = *n_byte_offsets.offset(n);\n            let o2 = *n_byte_offsets.offset(n + 1);\n            let o3 = *n_byte_offsets.offset(n + 2);\n            let o4 = *n_byte_offsets.offset(n + 3);\n            let o5 = *n_byte_offsets.offset(n + 4);\n            let o6 = *n_byte_offsets.offset(n + 5);\n            let o7 = *n_byte_offsets.offset(n + 6);\n            let o8 = *n_byte_offsets.offset(n + 7);\n            for k in k_range.start..k_range.end {\n                let ptr = ptr.offset(*k_byte_offsets.offset(k));\n                let v1 = *(ptr.offset(o1) as *const T);\n                let v2 = *(ptr.offset(o2) as *const T);\n                let v3 = *(ptr.offset(o3) as *const T);\n                let v4 = *(ptr.offset(o4) as *const T);\n                let v5 = *(ptr.offset(o5) as *const T);\n                let v6 = *(ptr.offset(o6) as *const T);\n                let v7 = *(ptr.offset(o7) as *const T);\n                let v8 = *(ptr.offset(o8) as *const T);\n                writer.write(v1);\n                writer.write(v2);\n                writer.write(v3);\n                writer.write(v4);\n                writer.write(v5);\n                writer.write(v6);\n                writer.write(v7);\n                writer.write(v8);\n            }\n        }\n    }\n\n    fn input_6n<T: Datum + Copy>(\n        &self,\n        writer: &mut impl PackingWriter<T>,\n        k_range: Range<isize>,\n        n: isize,\n    ) {\n        unsafe {\n            let ptr = self.tensor.as_ptr_unchecked::<u8>();\n            let k_byte_offsets = self.im2col.k_byte_offsets.as_ptr();\n            let n_byte_offsets = self.im2col.n_byte_offsets.as_ptr();\n            let o1 = *n_byte_offsets.offset(n);\n            let o2 = *n_byte_offsets.offset(n + 1);\n            let o3 = *n_byte_offsets.offset(n + 2);\n            let o4 = *n_byte_offsets.offset(n + 3);\n            let o5 = *n_byte_offsets.offset(n + 4);\n            let o6 = *n_byte_offsets.offset(n + 5);\n            for k in k_range.start..k_range.end {\n                let ptr = ptr.offset(*k_byte_offsets.offset(k));\n                let v1 = *(ptr.offset(o1) as *const T);\n                let v2 = *(ptr.offset(o2) as *const T);\n                let v3 = *(ptr.offset(o3) as *const T);\n                let v4 = *(ptr.offset(o4) as *const T);\n                let v5 = *(ptr.offset(o5) as *const T);\n                let v6 = *(ptr.offset(o6) as *const T);\n                writer.write(v1);\n                writer.write(v2);\n                writer.write(v3);\n                writer.write(v4);\n                writer.write(v5);\n                writer.write(v6);\n            }\n        }\n    }\n\n    fn input_4n<T: Datum + Copy>(\n        &self,\n        writer: &mut impl PackingWriter<T>,\n        k_range: Range<isize>,\n        n: isize,\n    ) {\n        unsafe {\n            let ptr = self.tensor.as_ptr_unchecked::<u8>();\n            let k_byte_offsets = self.im2col.k_byte_offsets.as_ptr();\n            let n_byte_offsets = self.im2col.n_byte_offsets.as_ptr();\n            let o1 = *n_byte_offsets.offset(n);\n            let o2 = *n_byte_offsets.offset(n + 1);\n            let o3 = *n_byte_offsets.offset(n + 2);\n            let o4 = *n_byte_offsets.offset(n + 3);\n            for k in k_range.start..k_range.end {\n                let ptr = ptr.offset(*k_byte_offsets.offset(k));\n                let v1 = *(ptr.offset(o1) as *const T);\n                let v2 = *(ptr.offset(o2) as *const T);\n                let v3 = *(ptr.offset(o3) as *const T);\n                let v4 = *(ptr.offset(o4) as *const T);\n                writer.write(v1);\n                writer.write(v2);\n                writer.write(v3);\n                writer.write(v4);\n            }\n        }\n    }\n\n    fn input_2n<T: Datum + Copy>(\n        &self,\n        writer: &mut impl PackingWriter<T>,\n        k_range: Range<isize>,\n        n: isize,\n    ) {\n        unsafe {\n            let ptr = self.tensor.as_ptr_unchecked::<u8>();\n            let k_byte_offsets = self.im2col.k_byte_offsets.as_ptr();\n            let n_byte_offsets = self.im2col.n_byte_offsets.as_ptr();\n            let o1 = *n_byte_offsets.offset(n);\n            let o2 = *n_byte_offsets.offset(n + 1);\n            for k in k_range.start..k_range.end {\n                let ptr = ptr.offset(*k_byte_offsets.offset(k));\n                let v1 = *(ptr.offset(o1) as *const T);\n                let v2 = *(ptr.offset(o2) as *const T);\n                writer.write(v1);\n                writer.write(v2);\n            }\n        }\n    }\n\n    fn write<T: Datum + Copy>(\n        &self,\n        writer: &mut impl PackingWriter<T>,\n        k_range: std::ops::Range<isize>,\n        mn_range: std::ops::Range<isize>,\n    ) {\n        let mn_end = mn_range.end.min(self.im2col.n_byte_offsets.len() as isize);\n        let n_range = mn_range.start..mn_end;\n        match n_range.len() {\n            8 => return self.input_8n(writer, k_range, n_range.start),\n            6 => return self.input_6n(writer, k_range, n_range.start),\n            4 => return self.input_4n(writer, k_range, n_range.start),\n            2 => return self.input_2n(writer, k_range, n_range.start),\n            _ => (),\n        }\n        unsafe {\n            let ptr = self.tensor.as_ptr_unchecked::<u8>();\n            let k_byte_offsets = self.im2col.k_byte_offsets.as_ptr();\n            let n_byte_offsets = self.im2col.n_byte_offsets.as_ptr();\n            for k in k_range.start..k_range.end {\n                let ptr = ptr.offset(*k_byte_offsets.offset(k));\n                let mut n = n_range.start;\n                while n + 8 <= n_range.end {\n                    let o1 = *n_byte_offsets.offset(n);\n                    let o2 = *n_byte_offsets.offset(n + 1);\n                    let o3 = *n_byte_offsets.offset(n + 2);\n                    let o4 = *n_byte_offsets.offset(n + 3);\n                    let o5 = *n_byte_offsets.offset(n + 4);\n                    let o6 = *n_byte_offsets.offset(n + 5);\n                    let o7 = *n_byte_offsets.offset(n + 6);\n                    let o8 = *n_byte_offsets.offset(n + 7);\n                    let v1 = *(ptr.offset(o1) as *const T);\n                    let v2 = *(ptr.offset(o2) as *const T);\n                    let v3 = *(ptr.offset(o3) as *const T);\n                    let v4 = *(ptr.offset(o4) as *const T);\n                    let v5 = *(ptr.offset(o5) as *const T);\n                    let v6 = *(ptr.offset(o6) as *const T);\n                    let v7 = *(ptr.offset(o7) as *const T);\n                    let v8 = *(ptr.offset(o8) as *const T);\n                    writer.write(v1);\n                    writer.write(v2);\n                    writer.write(v3);\n                    writer.write(v4);\n                    writer.write(v5);\n                    writer.write(v6);\n                    writer.write(v7);\n                    writer.write(v8);\n                    n += 8;\n                }\n                while n + 6 <= n_range.end {\n                    let o1 = *n_byte_offsets.offset(n);\n                    let o2 = *n_byte_offsets.offset(n + 1);\n                    let o3 = *n_byte_offsets.offset(n + 2);\n                    let o4 = *n_byte_offsets.offset(n + 3);\n                    let o5 = *n_byte_offsets.offset(n + 4);\n                    let o6 = *n_byte_offsets.offset(n + 5);\n                    let v1 = *(ptr.offset(o1) as *const T);\n                    let v2 = *(ptr.offset(o2) as *const T);\n                    let v3 = *(ptr.offset(o3) as *const T);\n                    let v4 = *(ptr.offset(o4) as *const T);\n                    let v5 = *(ptr.offset(o5) as *const T);\n                    let v6 = *(ptr.offset(o6) as *const T);\n                    writer.write(v1);\n                    writer.write(v2);\n                    writer.write(v3);\n                    writer.write(v4);\n                    writer.write(v5);\n                    writer.write(v6);\n                    n += 6;\n                }\n                while n + 4 <= n_range.end {\n                    let o1 = *n_byte_offsets.offset(n);\n                    let o2 = *n_byte_offsets.offset(n + 1);\n                    let o3 = *n_byte_offsets.offset(n + 2);\n                    let o4 = *n_byte_offsets.offset(n + 3);\n                    let v1 = *(ptr.offset(o1) as *const T);\n                    let v2 = *(ptr.offset(o2) as *const T);\n                    let v3 = *(ptr.offset(o3) as *const T);\n                    let v4 = *(ptr.offset(o4) as *const T);\n                    writer.write(v1);\n                    writer.write(v2);\n                    writer.write(v3);\n                    writer.write(v4);\n                    n += 4;\n                }\n                while n < n_range.end {\n                    let o1 = *n_byte_offsets.offset(n);\n                    let v1 = *(ptr.offset(o1) as *const T);\n                    writer.write(v1);\n                    n += 1;\n                }\n            }\n        }\n    }\n}\n\nimpl MMMInputValue for LazyIm2colInput {\n    fn scratch_panel_buffer_layout(&self) -> Option<std::alloc::Layout> {\n        let k = self.im2col.k_byte_offsets.len();\n        Some(self.im2col.packer.single_panel_layout(k, self.tensor.datum_type().size_of()))\n    }\n\n    fn panel_bytes(&self, i: usize, buffer: Option<*mut u8>) -> TractResult<*const u8> {\n        Ok(dispatch_copy!(Self::do_panel(self.tensor.datum_type())(self, i, buffer)))\n    }\n\n    fn k(&self) -> usize {\n        self.im2col.k_byte_offsets.len()\n    }\n\n    fn mn(&self) -> usize {\n        self.im2col.n_byte_offsets.len()\n    }\n\n    fn format(&self) -> &dyn MMMInputFormat {\n        &*self.im2col\n    }\n\n    fn exotic_fact(&self) -> &dyn ExoticFact {\n        &*self.im2col\n    }\n    fn extract_at_mn_f16(&self, _mn: usize, _slice: &mut [f16]) -> TractResult<()> {\n        unimplemented!()\n    }\n    fn extract_at_mn_f32(&self, _mn: usize, _slice: &mut [f32]) -> TractResult<()> {\n        unimplemented!()\n    }\n}\n\nimpl LazyIm2colInput {\n    fn do_panel<T: Datum + Copy>(&self, i: usize, buffer: Option<*mut u8>) -> *const u8 {\n        let r = self.im2col.packer.r;\n        let mn_start = i * r;\n        let mn_end = (mn_start + self.im2col.packer.r).min(self.im2col.n_byte_offsets.len());\n        let k = self.im2col.k_byte_offsets.len();\n        let mn_range = mn_start as isize..mn_end as isize;\n        let k_range = 0..k as isize;\n        let packed = buffer.unwrap();\n        if mn_range.len() == r && mn_start.is_multiple_of(r) {\n            let mut writer = self.im2col.packer.write_single_panel_with_k_outer(packed as *mut T);\n            self.write(&mut writer, k_range, mn_range);\n        } else {\n            let mut writer = self.im2col.packer.write_with_k_outer(\n                packed as *mut T,\n                k_range.len(),\n                mn_range.len(),\n            );\n            self.write(&mut writer, k_range, mn_range);\n        }\n        packed\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/conv/mod.rs",
    "content": "mod block_quant;\n#[allow(clippy::module_inception)]\nmod conv;\nmod depth_wise;\nmod im2col;\nmod lazy_im2col;\nmod q_sum_b;\n\nuse crate::internal::*;\nuse crate::ops::cnn::Deconv;\n\npub use self::conv::Conv;\npub use self::im2col::Im2Col;\npub(crate) use self::q_sum_b::QSumB;\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]\npub enum KernelFormat {\n    #[default]\n    OIHW,\n    HWIO,\n    OHWI,\n}\n\nimpl KernelFormat {\n    pub fn h_axis(&self) -> usize {\n        match self {\n            KernelFormat::OIHW => 2,\n            KernelFormat::HWIO => 0,\n            KernelFormat::OHWI => 1,\n        }\n    }\n\n    pub fn spatial_shape<'a, D>(&self, full_shape: &'a [D]) -> &'a [D] {\n        &full_shape[self.h_axis()..][..full_shape.len() - 2]\n    }\n\n    pub fn hw<'a, D>(&self, full_shape: &'a [D]) -> &'a [D] {\n        self.spatial_shape(full_shape)\n    }\n\n    pub fn i<'a, D>(&self, full_shape: &'a [D]) -> &'a D {\n        match self {\n            KernelFormat::OIHW => &full_shape[1],\n            KernelFormat::HWIO => &full_shape[full_shape.len() - 2],\n            KernelFormat::OHWI => &full_shape[full_shape.len() - 1],\n        }\n    }\n\n    pub fn o_axis<D>(&self, full_shape: &[D]) -> usize {\n        match self {\n            KernelFormat::OIHW | KernelFormat::OHWI => 0,\n            KernelFormat::HWIO => full_shape.len() - 1,\n        }\n    }\n\n    pub fn i_axis<D>(&self, full_shape: &[D]) -> usize {\n        match self {\n            KernelFormat::OIHW => 1,\n            KernelFormat::OHWI => full_shape.len() - 1,\n            KernelFormat::HWIO => full_shape.len() - 2,\n        }\n    }\n\n    pub fn o<'a, D>(&self, full_shape: &'a [D]) -> &'a D {\n        &full_shape[self.o_axis(full_shape)]\n    }\n\n    pub fn input_channels<'s, D: DimLike>(\n        &self,\n        full_kernel_shape: &'s [D],\n        group: usize,\n    ) -> Cow<'s, D> {\n        match self {\n            KernelFormat::OIHW => Cow::Owned(self.i(full_kernel_shape).clone() * group),\n            KernelFormat::HWIO | KernelFormat::OHWI => Cow::Borrowed(self.i(full_kernel_shape)),\n        }\n    }\n\n    pub fn output_channels<'s, D: DimLike>(\n        &self,\n        full_kernel_shape: &'s [D],\n        group: usize,\n    ) -> Cow<'s, D> {\n        match self {\n            KernelFormat::OIHW => Cow::Borrowed(self.o(full_kernel_shape)),\n            KernelFormat::HWIO | KernelFormat::OHWI => {\n                Cow::Owned(self.o(full_kernel_shape).clone() * group)\n            }\n        }\n    }\n\n    pub fn kernel_as_group_o_i_h_w_ops(\n        &self,\n        full_shape: &[impl DimLike],\n        group: usize,\n    ) -> TVec<AxisOp> {\n        let geo_rank = full_shape.len() - 2;\n        match self {\n            // g is on i\n            KernelFormat::HWIO => {\n                tvec!(\n                    AxisOp::Reshape(\n                        geo_rank,\n                        tvec!(self.i(full_shape).to_dim()),\n                        tvec!(group.to_dim(), self.i(full_shape).to_dim() / group),\n                    ), // h w g i o\n                    AxisOp::Move(geo_rank, 0),     // g h w i o\n                    AxisOp::Move(geo_rank + 2, 1), // g o h w i\n                    AxisOp::Move(geo_rank + 2, 2)\n                ) // g o i h w\n            }\n            // g is on o\n            KernelFormat::OIHW => {\n                tvec!(AxisOp::Reshape(\n                    0,\n                    tvec!(self.o(full_shape).to_dim()),\n                    tvec!(group.to_dim(), self.o(full_shape).to_dim() / group),\n                ))\n            }\n            // g is on i\n            KernelFormat::OHWI => {\n                tvec!(\n                    AxisOp::Reshape(\n                        geo_rank + 1,\n                        tvec!(self.i(full_shape).to_dim()),\n                        tvec!(group.to_dim(), self.i(full_shape).to_dim() / group),\n                    ), // o h w g i\n                    AxisOp::Move(geo_rank + 1, 0), // g o h w i\n                    AxisOp::Move(geo_rank + 2, 2)\n                )\n            }\n        }\n    }\n\n    pub fn kernel_as_group_o_i_hw_ops(\n        &self,\n        full_shape: &[impl DimLike],\n        group: usize,\n    ) -> TVec<AxisOp> {\n        let mut ops = self.kernel_as_group_o_i_h_w_ops(full_shape, group);\n        if self.hw(full_shape).len() > 1 {\n            ops.push(AxisOp::Reshape(\n                3,\n                self.hw(full_shape).iter().map(|t| t.to_dim()).collect(),\n                tvec!(self.hw(full_shape).iter().map(|t| t.to_dim()).product()),\n            ));\n        }\n        ops\n    }\n\n    pub fn kernel_as_group_o_ihw_ops(\n        &self,\n        full_shape: &[impl DimLike],\n        group: usize,\n    ) -> TVec<AxisOp> {\n        let i = (self.input_channels(full_shape, group).into_owned() / group).to_dim();\n        let hw = self.hw(full_shape).iter().map(|t| t.to_dim()).product::<TDim>();\n        let mut ops = self.kernel_as_group_o_i_hw_ops(full_shape, group);\n        ops.push(AxisOp::Reshape(2, tvec!(i.clone(), hw.clone()), tvec!(i * hw)));\n        ops\n    }\n\n    pub fn kernel_as_group_o_i_hw(&self, kernel: &Tensor, group: usize) -> TractResult<Tensor> {\n        let mut kernel = kernel.clone();\n        let ops = self.kernel_as_group_o_i_hw_ops(kernel.shape(), group);\n        for op in &ops {\n            op.change_tensor(&mut kernel, false)?;\n        }\n        Ok(kernel)\n    }\n\n    pub fn kernel_as_group_o_ihw(&self, kernel: &Tensor, group: usize) -> TractResult<Tensor> {\n        let group_o_i_hw = self.kernel_as_group_o_i_hw(kernel, group)?;\n        Ok(group_o_i_hw.collapse_axis_with_next(2))\n    }\n}\n\npub fn rewrite_kernel_conv_in_oihw(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    conv: &Conv,\n) -> TractResult<Option<TypedModelPatch>> {\n    rewrite_kernel_in_oihw(\n        model,\n        node,\n        name,\n        conv.kernel_fmt,\n        conv.group,\n        Box::new(Conv { kernel_fmt: KernelFormat::OIHW, ..conv.clone() }),\n    )\n}\n\npub fn rewrite_kernel_deconv_in_oihw(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    conv: &Deconv,\n) -> TractResult<Option<TypedModelPatch>> {\n    rewrite_kernel_in_oihw(\n        model,\n        node,\n        name,\n        conv.kernel_format,\n        conv.group,\n        Box::new(Deconv { kernel_format: KernelFormat::OIHW, ..conv.clone() }),\n    )\n}\n\nfn rewrite_kernel_in_oihw(\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    fmt: KernelFormat,\n    group: usize,\n    new: Box<dyn TypedOp>,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(fmt != KernelFormat::OIHW);\n    let mut patch = TypedModelPatch::default();\n    let mut wire = patch.taps(model, &node.inputs)?;\n    let prefix = format!(\"{name}.kernel_reorg\");\n    for (ix, op) in fmt\n        .kernel_as_group_o_i_h_w_ops(&patch.outlet_fact(wire[1])?.shape, group)\n        .into_iter()\n        .enumerate()\n    {\n        wire[1] = patch.wire_node(format!(\"{prefix}.{ix}\"), op, &[wire[1]])?[0];\n    }\n    wire[1] =\n        AxisOp::wire_collapse_axis(&mut patch, format!(\"{name}.kernel_reorg_go\"), wire[1], 0)?[0];\n    wire = patch.wire_node(name, new, &wire)?;\n    patch.shunt_outside(model, node.id.into(), wire[0])?;\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "core/src/ops/cnn/conv/q_sum_b.rs",
    "content": "use crate::internal::*;\nuse tract_linalg::mmm::{MMMInputValue, PackedMatrixStorage};\nuse tract_ndarray::prelude::*;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct QSumB {\n    pub dt: DatumType,\n    pub r: usize,\n    pub n: TDim,\n    pub k: usize,\n}\n\nimpl Op for QSumB {\n    fn name(&self) -> StaticName {\n        \"QSumB\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"r:{}, n:{}, k:{}\", self.r, self.n, self.k)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for QSumB {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let n = self.n.eval_to_i64(&session.resolved_symbols)? as usize;\n        self.eval(inputs, n)\n    }\n}\n\nimpl TypedOp for QSumB {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut shape: TVec<TDim> = inputs[0].shape.to_tvec();\n        shape.push(self.n.to_dim());\n        Ok(tvec!(i32::fact(shape)))\n    }\n}\n\nimpl QSumB {\n    fn eval(&self, inputs: TVec<TValue>, n: usize) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let storage = input\n            .try_storage_as::<PackedMatrixStorage>()\n            .context(\"Expected PackedMatrixStorage\")?;\n        let batch_shape = storage.batch_shape();\n        let mut shape: TVec<usize> = batch_shape.into();\n        shape.push(n);\n        let mut output = ArrayD::<i32>::zeros(&*shape);\n        for b in 0..batch_shape[0] {\n            let mut output_view = output.index_axis_mut(Axis(0), b);\n            for g in 0..batch_shape[1] {\n                let mut output_view = output_view.index_axis_mut(Axis(0), g);\n                let output_slice = output_view.as_slice_mut().unwrap();\n                let payload = storage.value_at(&[b, g]);\n                match self.dt.unquantized() {\n                    DatumType::I8 => self.eval_t::<i8>(payload, output_slice)?,\n                    DatumType::U8 => self.eval_t::<u8>(payload, output_slice)?,\n                    dt => bail!(\"Unsupported input type in quantized operation ({:?})\", dt),\n                }\n            }\n        }\n        Ok(tvec!(output.into_tvalue()))\n    }\n\n    fn eval_t<T: Datum + tract_num_traits::AsPrimitive<i32>>(\n        &self,\n        input: &dyn MMMInputValue,\n        output: &mut [i32],\n    ) -> TractResult<()> {\n        let (r, k, n) = (input.format().r(), input.k(), input.mn());\n        let panels = n.divceil(r);\n        for ipanel in 0..panels {\n            let panel = input.panel_bytes(ipanel, None)?;\n            let panel: &[T] = unsafe { std::slice::from_raw_parts(panel as *const T, r * k) };\n            let mut vec = vec![0i32; r];\n            for ik in 0..k {\n                for ir in 0..r {\n                    vec[ir] += panel[ik * r + ir].as_();\n                }\n            }\n            let len = r.min(n - r * ipanel);\n            output[r * ipanel..][..len].copy_from_slice(&vec[..len]);\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/deconv/deconv.rs",
    "content": "use crate::internal::*;\nuse crate::ops::array::MultiBroadcastTo;\nuse crate::ops::cnn::KernelFormat;\nuse crate::ops::cnn::PoolSpec;\nuse crate::ops::cnn::wire_reshape_bias_for_bin;\nuse crate::ops::einsum::EinSum;\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\npub struct Deconv {\n    pub pool_spec: PoolSpec,\n    pub kernel_format: KernelFormat,\n    pub adjustments: TVec<usize>,\n    pub group: usize,\n}\n\nimpl Deconv {\n    fn wire_with_deconv_sum(\n        &self,\n        name: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input_shape = target.outlet_fact(inputs[0])?.shape.clone();\n        let shape = self.pool_spec.data_format.shape(input_shape.to_tvec())?;\n        let geo_dim = shape.hw_dims().iter().product();\n\n        // collapse H and W together in input: (N) I HW or (N) HW I\n        let mut input = target.wire_node(\n            format!(\"{name}.reshaped_input\"),\n            AxisOp::Reshape(shape.h_axis(), shape.hw_dims().into(), tvec!(geo_dim)),\n            &[inputs[0]],\n        )?;\n\n        // rework input to (N) (G) I/G HW or (N) (G) HW I/G\n        if self.group != 1 {\n            // input is (N) HW I or (N) I HW\n            let i_axis = self.pool_spec.data_format.has_n() as usize\n                + self.pool_spec.data_format.c_is_last() as usize;\n            let i_dim = target.outlet_fact(input[0])?.shape[i_axis].clone();\n            input = target.wire_node(\n                format!(\"{name}.reshaped_input_for_group\"),\n                AxisOp::Reshape(\n                    i_axis,\n                    tvec![i_dim.clone()],\n                    tvec!(self.group.to_dim(), i_dim / self.group),\n                ),\n                &input,\n            )?;\n            if self.pool_spec.data_format.c_is_last() {\n                input = target.wire_node(\n                    format!(\"{name}.group_axis_left\"),\n                    AxisOp::Move(\n                        self.pool_spec.data_format.has_n() as usize + 1,\n                        self.pool_spec.data_format.has_n() as usize,\n                    ),\n                    &input,\n                )?;\n            }\n        }\n\n        let mut kernel = tvec!(inputs[1]);\n        let kernel_fact = target.outlet_fact(kernel[0])?.clone();\n        for (ix, op) in self\n            .kernel_format\n            .kernel_as_group_o_i_hw_ops(&kernel_fact.shape, self.group)\n            .into_iter()\n            .enumerate()\n        {\n            kernel = target.wire_node(format!(\"{name}.kernel.{ix}\"), op, &kernel)?;\n        }\n\n        kernel = target.wire_node(format!(\"{name}.kernel.mv_i\"), AxisOp::Move(2, 3), &kernel)?;\n        kernel =\n            AxisOp::wire_collapse_axis(target, format!(\"{name}.kernel.col_ohw\"), kernel[0], 1)?;\n        if self.group == 1 {\n            kernel = target.wire_node(format!(\"{name}.kernel.rm_g\"), AxisOp::Rm(0), &kernel)?;\n        }\n        let mut expr = if self.pool_spec.data_format.c_is_last() {\n            \"gmk,Ngnk->Ngmn\".to_string()\n        } else {\n            \"gmk,Ngkn->Ngmn\".to_string()\n        };\n        if !self.pool_spec.data_format.has_n() {\n            expr = expr.replace('N', \"\");\n        }\n        if self.group == 1 {\n            expr = expr.replace('g', \"\");\n        }\n        let einsum = target.wire_node(\n            format!(\"{name}.einsum\"),\n            EinSum { axes: expr.parse()?, operating_dt: kernel_fact.datum_type, q_params: None },\n            &[kernel[0], input[0]],\n        )?;\n\n        let mut bias = wire_reshape_bias_for_bin(\n            target,\n            format!(\"{name}.reshape_bias\"),\n            inputs[2],\n            shape.rank(),\n            shape.c_axis(),\n            self.pool_spec.output_channels,\n        )?[0];\n        let output_shape = super::output_shape(&self.pool_spec, &shape.shape, &self.adjustments)?;\n        bias = target.wire_node(\n            format!(\"{name}.broadcast_bias\"),\n            MultiBroadcastTo { shape: output_shape.into() },\n            &[bias],\n        )?[0];\n\n        // einsum must be (N_)CHkWk_HW\n        let deconv_sum = target.wire_node(\n            format!(\"{name}.deconv_sum\"),\n            super::deconv_sum::DeconvSum::new(\n                self.pool_spec.clone(),\n                self.kernel_format,\n                input_shape,\n                self.adjustments.clone(),\n                self.group,\n            ),\n            &[einsum[0], bias],\n        )?;\n        Ok(deconv_sum)\n    }\n}\n\nimpl Op for Deconv {\n    fn name(&self) -> StaticName {\n        \"Deconv\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"{:?}\", self.pool_spec)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Deconv {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        ensure!(inputs.len() == 3);\n        let mut model = TypedModel::default();\n        let inputs = inputs\n            .into_iter()\n            .enumerate()\n            .map(|(ix, input)| model.add_const(format!(\"s{ix}\"), input.into_tensor()))\n            .collect::<TractResult<TVec<OutletId>>>()?;\n        let output = self.wire_with_deconv_sum(\"adhoc\", &mut model, &inputs)?;\n        model.select_output_outlets(&output)?;\n        model.into_runnable()?.run(tvec![]).context(\"In adhoc deconvolution eval\")\n    }\n}\n\nimpl TypedOp for Deconv {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() == 3);\n        let x_fact = inputs[0];\n        let k_fact = inputs[1];\n        ensure!(\n            &self.pool_spec.input_channels.to_dim()\n                == self.pool_spec.data_format.shape(&inputs[0].shape)?.c()\n        );\n        ensure!(\n            self.pool_spec.input_channels.to_dim()\n                == *self.kernel_format.input_channels(&k_fact.shape, self.group)\n        );\n        let output_shape = super::output_shape(&self.pool_spec, &x_fact.shape, &self.adjustments)?;\n        Ok(tvec!(x_fact.datum_type.fact(&output_shape)))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let fact = &inputs[0];\n        let k_fact = &inputs[1];\n        let shape = self.pool_spec.data_format.shape(&fact.shape)?;\n        let mut axes = AxesMapping::disconnected(inputs, outputs)?\n            .renaming((InOut::In(0), shape.c_axis()), 'I')?\n            .renaming((InOut::Out(0), shape.c_axis()), 'O')?;\n        if let Some(n_axis) = shape.n_axis() {\n            axes = axes\n                .renaming((InOut::In(0), n_axis), 'N')?\n                .linking('N', (InOut::Out(0), n_axis))?;\n        }\n        let h_axis = shape.h_axis();\n        let geo = \"HWXYZ\".chars().chain('a'..);\n        let kernel_spatial_shape = self.kernel_format.spatial_shape(&k_fact.shape);\n        for ((ix, dim), repr) in kernel_spatial_shape.iter().enumerate().zip(geo) {\n            if dim.is_one()\n                && self.pool_spec.stride(ix) == 1\n                && self.pool_spec.padding.valid_dim(ix, true)\n                && self.adjustments[ix] == 0\n            {\n                axes = axes\n                    .renaming((InOut::In(0), ix + h_axis), repr)?\n                    .linking((InOut::In(0), ix + h_axis), (InOut::Out(0), ix + h_axis))?;\n            }\n        }\n        Ok(axes)\n    }\n\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let mut patch = TypedModelPatch::default();\n        let inputs = patch.taps(model, &node.inputs)?;\n        let output = self\n            .wire_with_deconv_sum(&node.name, &mut patch, &inputs)\n            .context(\"In wire_with_deconv_sum\")?;\n        patch.shunt_outside(model, node.id.into(), output[0])?;\n        Ok(Some(patch))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/cnn/deconv/deconv_sum.rs",
    "content": "#![allow(dead_code)]\n\nuse std::ops::AddAssign;\n\nuse crate::internal::*;\nuse crate::ops::cnn::padding::ComputedPaddedDim;\nuse crate::ops::cnn::{KernelFormat, PoolSpec};\nuse crate::ops::nn::DataShape;\nuse tract_ndarray::prelude::*;\nuse tract_num_traits::Float;\n\n/*\n(N) (G) C   H   W\nReshaped Input (N) (G) C   HW\nKernel         (N) (G) OHkWk   C\nGemm           (N) (G) OHkWk   HW              (Gemm: m: OHkWk k:C n:HW)\nDeconvSum      (N) (G) O   H'  W'\n*/\n\n// f32, ndarray::indices in order\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\npub struct DeconvSum {\n    pub pool_spec: PoolSpec,\n    pub kernel_format: KernelFormat,\n    /// shape of the deconvolution input\n    pub input_shape: ShapeFact,\n    pub adjustments: TVec<usize>,\n    pub group: usize,\n}\n\nimpl Op for DeconvSum {\n    fn name(&self) -> StaticName {\n        \"DeconvSum\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DeconvSum {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        self.eval_with_values(inputs, &session.resolved_symbols)\n    }\n}\n\nimpl DeconvSum {\n    fn eval_with_values(\n        &self,\n        inputs: TVec<TValue>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<TValue>> {\n        let (gemm, bias) = args_2!(inputs);\n        let input_shape = self.input_shape.eval_to_usize(values)?.into_owned();\n        let input_shape = self.pool_spec.data_format.shape(input_shape)?;\n        let output_shape =\n            super::output_shape(&self.pool_spec, &input_shape.shape, &self.adjustments)?;\n        let output_shape = self.pool_spec.data_format.shape(output_shape)?;\n        let spatial_output_details = self.pool_spec.padding.compute_for_deconv(\n            input_shape.hw_dims(),\n            &self.pool_spec.kernel_shape,\n            &self.pool_spec.dilations(),\n            &self.pool_spec.strides(),\n            &self.adjustments,\n        )?;\n        let mut tensor = bias.into_tensor();\n        let hw = *gemm.shape().last().unwrap();\n        let n = *output_shape.n().unwrap_or(&1);\n        let n_o_hkwk_hw = gemm.into_tensor().into_shape(&[\n            n,\n            *output_shape.c(),\n            self.pool_spec.kernel_shape.iter().product(),\n            hw,\n        ])?;\n        if !self.pool_spec.data_format.has_n() {\n            tensor.insert_axis(0)?;\n        }\n        eval(\n            self,\n            &input_shape,\n            &output_shape,\n            &spatial_output_details,\n            &n_o_hkwk_hw,\n            &mut tensor,\n        )?;\n        if !self.pool_spec.data_format.has_n() {\n            tensor.remove_axis(0)?;\n        }\n        Ok(tvec!(tensor.into_tvalue()))\n    }\n}\n\nimpl TypedOp for DeconvSum {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() == 2);\n        let shape = super::output_shape(&self.pool_spec, &self.input_shape, &self.adjustments)?;\n        ensure!(*inputs[1].shape == *shape);\n        Ok(tvec!(inputs[0].datum_type.fact(shape)))\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        target.wire_node(\n            &node.name,\n            Self { input_shape: self.input_shape.eval(values)?.into_owned(), ..self.clone() },\n            &[mapping[&node.inputs[0]], mapping[&node.inputs[1]]],\n        )\n    }\n\n    as_op!();\n}\n\nfn eval(\n    op: &DeconvSum,\n    input_shape: &DataShape,\n    output_shape: &DataShape,\n    spatial_output_details: &[ComputedPaddedDim<usize>],\n    n_o_hkwk_hw: &Tensor,\n    output: &mut Tensor,\n) -> TractResult<()> {\n    let dt = output.datum_type();\n    unsafe {\n        #[cfg(target_arch = \"aarch64\")]\n        if dt == f16::datum_type() && tract_linalg::arm64::has_fp16() {\n            return eval_t_aarch64fp16::<f16>(\n                op,\n                input_shape,\n                output_shape,\n                spatial_output_details,\n                n_o_hkwk_hw,\n                output,\n                |a, b| tract_linalg::arm64::add_f16(a, b),\n            );\n        }\n        dispatch_floatlike!(eval_t_generic(dt)(\n            op,\n            input_shape,\n            output_shape,\n            spatial_output_details,\n            n_o_hkwk_hw,\n            output,\n            |a, b| a + b\n        ))\n    }\n}\n\nmacro_rules! impl_eval {\n        ($(#[$meta: meta])* $suffix: ident) => {\n            pastey::paste! {\n                $(#[$meta])*\n                    unsafe fn [<eval_t_ $suffix>]<T: Datum + Float + Copy + AddAssign<T>>(\n                        op: &DeconvSum,\n                        input_shape: &DataShape,\n                        output_shape: &DataShape,\n                        spatial_output_details: &[ComputedPaddedDim<usize>],\n                        n_o_hkwk_hw: &Tensor,\n                        output: &mut Tensor,\n                        add: impl Fn(T, T) -> T + Copy + 'static,\n                        ) -> TractResult<()> {\n                        let mut output_plain = output.try_as_plain_mut()?;\n                        let output = output_plain.to_array_view_mut::<T>()?;\n                        let n_o_hkwk_hw: ArrayView4<T> = n_o_hkwk_hw.to_plain_array_view::<T>()?.into_dimensionality()?;\n                        match input_shape.hw_rank() {\n                            1 => [<main_loop_1d_ $suffix>](\n                                op,\n                                input_shape,\n                                output_shape,\n                                spatial_output_details,\n                                &n_o_hkwk_hw,\n                                &mut output.into_dimensionality().unwrap(),\n                                add,\n                                ),\n                            2 => [<main_loop_2d_ $suffix>](\n                                op,\n                                input_shape,\n                                output_shape,\n                                spatial_output_details,\n                                &n_o_hkwk_hw,\n                                &mut output.into_dimensionality().unwrap(),\n                                add,\n                                ),\n                            3 => [<main_loop_3d_ $suffix>](\n                                op,\n                                input_shape,\n                                output_shape,\n                                spatial_output_details,\n                                &n_o_hkwk_hw,\n                                &mut output.into_dimensionality().unwrap(),\n                                add,\n                                ),\n                            _ => [<main_loop_ $suffix>](\n                                op,\n                                input_shape,\n                                output_shape,\n                                spatial_output_details,\n                                &n_o_hkwk_hw,\n                                &mut output.into_dimensionality().unwrap(),\n                                add,\n                                ),\n                        }\n                    }\n\n                pub fn [<main_loop_1d_ $suffix>]<T: Datum + Float>(\n                    op: &DeconvSum,\n                    input_shape: &DataShape,\n                    output_shape: &DataShape,\n                    spatial_output_details: &[ComputedPaddedDim<usize>],\n                    n_o_hkwk_hw: &ArrayView4<T>,\n                    output: &mut ArrayViewMut3<T>,\n                    add: impl Fn(T, T) -> T + Copy + 'static,\n                    ) -> TractResult<()> {\n                    let n = *output_shape.n().unwrap_or(&1);\n                    let kernel_len = op.pool_spec.kernel_shape[0];\n                    let geo_input_len = input_shape.hw_dims()[0];\n                    let geo_output_len = output_shape.hw_dims()[0];\n                    let x_stride = op.pool_spec.strides().as_ref()[0];\n                    let x_dil = op.pool_spec.dilations().as_ref()[0];\n                    let x_pad = spatial_output_details[0].pad_before as isize;\n                    for n in 0..n {\n                        for o in 0..*output_shape.c() {\n                            for kx in 0..kernel_len {\n                                for gx in 0..geo_input_len {\n                                    let x = (kx * x_dil + gx * x_stride) as isize - x_pad;\n                                    if x < 0 || x >= geo_output_len as isize {\n                                        continue;\n                                    }\n                                    let coord = if op.pool_spec.data_format.c_is_last() {\n                                        [n, x as usize, o]\n                                    } else {\n                                        [n, o, x as usize]\n                                    };\n                                    unsafe {\n                                        let value = *n_o_hkwk_hw.uget((n, o, kx, gx));\n                                        *output.uget_mut(coord) = add(*output.uget(coord), value);\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    Ok(())\n                }\n\n                pub fn [<main_loop_2d_ $suffix>]<T: Datum + Float>(\n                    op: &DeconvSum,\n                    input_shape: &DataShape,\n                    output_shape: &DataShape,\n                    spatial_output_details: &[ComputedPaddedDim<usize>],\n                    n_o_hkwk_hw: &ArrayView4<T>,\n                    output: &mut ArrayViewMut4<T>,\n                    add: impl Fn(T, T) -> T + Copy + 'static,\n                    ) -> TractResult<()> {\n                    let n = *output_shape.n().unwrap_or(&1);\n                    let x_stride = op.pool_spec.strides().as_ref()[0];\n                    let y_stride = op.pool_spec.strides().as_ref()[1];\n                    let x_dil = op.pool_spec.dilations().as_ref()[0];\n                    let y_dil = op.pool_spec.dilations().as_ref()[1];\n                    let x_pad = spatial_output_details[0].pad_before as isize;\n                    let y_pad = spatial_output_details[1].pad_before as isize;\n                    let output_c = *output_shape.c();\n                    let output_c_stride = *output_shape.c_stride() as isize;\n                    let output_x_stride = output_shape.hw_strides()[0] as isize;\n                    let output_y_stride = output_shape.hw_strides()[1] as isize;\n                    let temp_n_stride = n_o_hkwk_hw.strides()[0];\n                    let temp_o_stride = n_o_hkwk_hw.strides()[1];\n                    let temp_k_stride = n_o_hkwk_hw.strides()[2];\n                    let temp_i_stride = n_o_hkwk_hw.strides()[3];\n                    let ox_len = output_shape.hw_dims()[0];\n                    let oy_len = output_shape.hw_dims()[1];\n                    let ix_len = input_shape.hw_dims()[0];\n                    let iy_len = input_shape.hw_dims()[1];\n                    let kx_len = op.pool_spec.kernel_shape[0];\n                    let ky_len = op.pool_spec.kernel_shape[1];\n                    unsafe {\n                        for n in 0..n {\n                            let output = output.as_mut_ptr().add(n * *output_shape.n_stride().unwrap_or(&0));\n                            let temp = n_o_hkwk_hw.as_ptr().offset(n as isize * temp_n_stride);\n                            for kx in 0..kx_len {\n                                let temp = temp.offset((kx * ky_len) as isize * temp_k_stride);\n                                for ix in 0..ix_len {\n                                    let ox = (kx * x_dil + ix * x_stride) as isize - x_pad;\n                                    if ox < 0 || ox >= ox_len as isize {\n                                        continue;\n                                    }\n                                    let temp = temp.offset((ix * iy_len) as isize * temp_i_stride);\n                                    let output = output.offset(ox * output_x_stride);\n                                    for ky in 0..ky_len {\n                                        let temp = temp.offset(ky as isize * temp_k_stride);\n                                        let oy = (ky * y_dil) as isize - y_pad;\n                                        for iy in 0..iy_len {\n                                            let oy = oy + (iy * y_stride) as isize;\n                                            if oy < 0 || oy >= oy_len as isize {\n                                                continue;\n                                            }\n                                            let temp = temp.offset(iy as isize * temp_i_stride);\n                                            let output = output.offset(oy * output_y_stride);\n                                            [<main_loop_2d_inner_ $suffix>](\n                                                output_c,\n                                                temp,\n                                                temp_o_stride,\n                                                output,\n                                                output_c_stride,\n                                                add,\n                                                )\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    Ok(())\n                }\n\n                #[inline(never)]\n                #[allow(clippy::erasing_op)]\n                #[allow(clippy::identity_op)]\n                unsafe fn [<main_loop_2d_inner_ $suffix>]<T: Datum + Float>(\n                    output_c: usize,\n                    temp: *const T,\n                    temp_o_stride: isize,\n                    output: *mut T,\n                    output_c_stride: isize,\n                    add: impl Fn(T, T) -> T + Copy + 'static,\n                    ) { unsafe {\n                    let mut c = 0;\n                    let mut right = temp;\n                    let mut left = output;\n                    while c + 8 < output_c {\n                        let mut left0 = *left.offset(0 * output_c_stride);\n                        let mut left1 = *left.offset(1 * output_c_stride);\n                        let mut left2 = *left.offset(2 * output_c_stride);\n                        let mut left3 = *left.offset(3 * output_c_stride);\n                        let mut left4 = *left.offset(4 * output_c_stride);\n                        let mut left5 = *left.offset(5 * output_c_stride);\n                        let mut left6 = *left.offset(6 * output_c_stride);\n                        let mut left7 = *left.offset(7 * output_c_stride);\n                        let right0 = *right.offset(0 * temp_o_stride);\n                        let right1 = *right.offset(1 * temp_o_stride);\n                        let right2 = *right.offset(2 * temp_o_stride);\n                        let right3 = *right.offset(3 * temp_o_stride);\n                        let right4 = *right.offset(4 * temp_o_stride);\n                        let right5 = *right.offset(5 * temp_o_stride);\n                        let right6 = *right.offset(6 * temp_o_stride);\n                        let right7 = *right.offset(7 * temp_o_stride);\n                        left0 = add(left0, right0);\n                        left1 = add(left1, right1);\n                        left2 = add(left2, right2);\n                        left3 = add(left3, right3);\n                        left4 = add(left4, right4);\n                        left5 = add(left5, right5);\n                        left6 = add(left6, right6);\n                        left7 = add(left7, right7);\n                        *left.offset(0 * output_c_stride) = left0;\n                        *left.offset(1 * output_c_stride) = left1;\n                        *left.offset(2 * output_c_stride) = left2;\n                        *left.offset(3 * output_c_stride) = left3;\n                        *left.offset(4 * output_c_stride) = left4;\n                        *left.offset(5 * output_c_stride) = left5;\n                        *left.offset(6 * output_c_stride) = left6;\n                        *left.offset(7 * output_c_stride) = left7;\n                        c += 8;\n                        left = left.offset(8 * output_c_stride);\n                        right = right.offset(8 * temp_o_stride);\n                    }\n                    for c in c..output_c {\n                        let value = *temp.offset(c as isize * temp_o_stride);\n                        let ptr = output.offset(c as isize * output_c_stride);\n                        *ptr = add(*ptr, value);\n                    }\n                }}\n\n                pub fn [<main_loop_3d_ $suffix>]<T: Datum + Float>(\n                    op: &DeconvSum,\n                    input_shape: &DataShape,\n                    output_shape: &DataShape,\n                    spatial_output_details: &[ComputedPaddedDim<usize>],\n                    n_o_hkwk_hw: &ArrayView4<T>,\n                    output: &mut ArrayViewMut5<T>,\n                    add: impl Fn(T, T) -> T + Copy + 'static,\n                    ) -> TractResult<()> {\n                    let n = *output_shape.n().unwrap_or(&1);\n                    let kernel_shape: [usize; 3] =\n                        [op.pool_spec.kernel_shape[0], op.pool_spec.kernel_shape[1], op.pool_spec.kernel_shape[2]];\n                    let geo_input_shape: [usize; 3] =\n                        [input_shape.hw_dims()[0], input_shape.hw_dims()[1], input_shape.hw_dims()[2]];\n                    let geo_output_shape: [usize; 3] =\n                        [output_shape.hw_dims()[0], output_shape.hw_dims()[1], output_shape.hw_dims()[2]];\n                    let x_stride = op.pool_spec.strides().as_ref()[0];\n                    let y_stride = op.pool_spec.strides().as_ref()[1];\n                    let z_stride = op.pool_spec.strides().as_ref()[2];\n                    let x_dil = op.pool_spec.dilations().as_ref()[0];\n                    let y_dil = op.pool_spec.dilations().as_ref()[1];\n                    let z_dil = op.pool_spec.dilations().as_ref()[2];\n                    let x_pad = spatial_output_details[0].pad_before as isize;\n                    let y_pad = spatial_output_details[1].pad_before as isize;\n                    let z_pad = spatial_output_details[2].pad_before as isize;\n                    for n in 0..n {\n                        for o in 0..*output_shape.c() {\n                            for (kix, (kx, ky, kz)) in tract_ndarray::indices(kernel_shape).into_iter().enumerate()\n                            {\n                                for (gix, (gx, gy, gz)) in\n                                    tract_ndarray::indices(geo_input_shape).into_iter().enumerate()\n                                    {\n                                        let x = (kx * x_dil + gx * x_stride) as isize - x_pad;\n                                        let y = (ky * y_dil + gy * y_stride) as isize - y_pad;\n                                        let z = (kz * z_dil + gz * z_stride) as isize - z_pad;\n                                        if x < 0\n                                            || y < 0\n                                                || z < 0\n                                                || x >= geo_output_shape[0] as isize\n                                                || y >= geo_output_shape[1] as isize\n                                                || z >= geo_output_shape[2] as isize\n                                                {\n                                                    continue;\n                                                }\n                                        let coord = if op.pool_spec.data_format.c_is_last() {\n                                            [n, x as usize, y as usize, z as usize, o]\n                                        } else {\n                                            [n, o, x as usize, y as usize, z as usize]\n                                        };\n                                        unsafe {\n                                            let value = *n_o_hkwk_hw.uget((n, o, kix, gix));\n                                            *output.uget_mut(coord) = add(*output.uget(coord), value);\n                                        }\n                                    }\n                            }\n                        }\n                    }\n                    Ok(())\n                }\n\n                pub fn [<main_loop_ $suffix>]<T: Datum + Float>(\n                    op: &DeconvSum,\n                    input_shape: &DataShape,\n                    output_shape: &DataShape,\n                    spatial_output_details: &[ComputedPaddedDim<usize>],\n                    n_o_hkwk_hw: &ArrayView4<T>,\n                    output: &mut ArrayViewMutD<T>,\n                    add: impl Fn(T, T) -> T + Copy + 'static,\n                    ) -> TractResult<()> {\n                    let n = *output_shape.n().unwrap_or(&1);\n                    let strides = op.pool_spec.strides();\n                    let dilations = op.pool_spec.dilations();\n                    for n in 0..n {\n                        for o in 0..*output_shape.c() {\n                            for (kix, kcoords) in\n                                tract_ndarray::indices(&*op.pool_spec.kernel_shape).into_iter().enumerate()\n                                {\n                                    for (gix, gcoords) in\n                                        tract_ndarray::indices(input_shape.hw_dims()).into_iter().enumerate()\n                                        {\n                                            // h' = stride * hg + dil * hk\n                                            let ocoord: TVec<isize> = tract_itertools::izip!(\n                                                kcoords.slice(),\n                                                gcoords.slice(),\n                                                strides.as_ref(),\n                                                dilations.as_ref(),\n                                                spatial_output_details\n                                                )\n                                                .map(|(k, g, s, d, details)| {\n                                                    (k * d + g * s) as isize - details.pad_before as isize\n                                                })\n                                            .collect();\n                                            if ocoord\n                                                .iter()\n                                                    .zip(output_shape.hw_dims().iter())\n                                                    .all(|(x, dim)| *x >= 0 && (*x as usize) < *dim)\n                                                    {\n                                                        let ocoord = ocoord.iter().map(|x| *x as usize).collect::<TVec<_>>();\n                                                        let ocoord = op.pool_spec.data_format.with_n().from_n_c_hw(n, o, ocoord)?;\n                                                        let value = n_o_hkwk_hw[(n, o, kix, gix)];\n                                                        output[&*ocoord.shape] = add(output[&*ocoord.shape], value)\n                                                    }\n                                        }\n                                }\n                        }\n                    }\n                    Ok(())\n                }\n            }\n        }\n    }\n\nimpl_eval!(generic);\nimpl_eval! {\n#[target_feature(enable = \"fp16\")]\n#[cfg(target_arch = \"aarch64\")]\n        aarch64fp16\n    }\n"
  },
  {
    "path": "core/src/ops/cnn/deconv/mod.rs",
    "content": "use crate::internal::*;\nuse crate::ops::cnn::{PaddingSpec, PoolSpec};\n\n#[allow(clippy::module_inception)]\nmod deconv;\nmod deconv_sum;\n\npub use deconv::Deconv;\n\npub fn output_shape<D: DimLike>(\n    pool_spec: &PoolSpec,\n    x_shape: &[D],\n    adjustments: &[usize],\n) -> TractResult<TVec<D>> {\n    let x_shape = pool_spec.data_format.shape(x_shape)?;\n    let spatial_input_shape = x_shape.hw_dims();\n    let spatial_output_details = pool_spec.padding.compute_for_deconv(\n        spatial_input_shape,\n        &pool_spec.kernel_shape,\n        &pool_spec.dilations(),\n        &pool_spec.strides(),\n        adjustments,\n    )?;\n    let deconv_shape: TVec<D> =\n        spatial_output_details.iter().map(|comp| comp.deconvoluted.clone()).collect();\n    let output_shape = pool_spec.data_format.from_n_c_hw(\n        x_shape.n().cloned().unwrap_or_else(|| 1.into()),\n        pool_spec.output_channels.into(),\n        deconv_shape,\n    )?;\n    Ok(output_shape.shape)\n}\n\npub fn adjustments(\n    pool_spec: &PoolSpec,\n    input_geo: &[usize],\n    output_geo: &[usize],\n) -> TractResult<TVec<usize>> {\n    debug_assert_eq!(pool_spec.rank(), pool_spec.strides().len());\n    debug_assert_eq!(pool_spec.rank(), pool_spec.dilations().len());\n    debug_assert_eq!(pool_spec.rank(), pool_spec.kernel_shape.len());\n    debug_assert_eq!(pool_spec.rank(), input_geo.len());\n    debug_assert_eq!(pool_spec.rank(), output_geo.len());\n    let rank = pool_spec.rank();\n    let pad: TVec<usize> = match &pool_spec.padding {\n        PaddingSpec::Explicit(beg, end) => (0..rank).map(|r| beg[r] + end[r]).collect(),\n        PaddingSpec::Valid => tvec!(0; rank),\n        pad => todo!(\"Unsupported padding in deconvolution arguments {pad:?}\"),\n    };\n    let strides = pool_spec.strides();\n    let dilations = pool_spec.dilations();\n    tract_itertools::izip!(\n        input_geo,\n        &pool_spec.kernel_shape,\n        output_geo,\n        strides.as_ref(),\n        dilations.as_ref(),\n        pad,\n    )\n    .map(|(x, k, y, s, d, p)| {\n        let adj = y.to_usize()? + p - s * (x.to_usize()? - 1) - (k.to_usize()? - 1) * d - 1;\n        Ok(adj)\n    })\n    .collect::<TractResult<TVec<usize>>>()\n}\n"
  },
  {
    "path": "core/src/ops/cnn/maxpool.rs",
    "content": "use crate::internal::*;\nuse ndarray::prelude::*;\n\nuse crate::ops::cnn::pools::{ConcretePoolGeometry, PoolGeometry, PoolSpec};\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct MaxPool {\n    pub pool_spec: PoolSpec,\n    pub with_index_outputs: Option<DatumType>,\n}\n\nimpl Op for MaxPool {\n    fn name(&self) -> StaticName {\n        \"MaxPool\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(self.pool_spec.info())\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for MaxPool {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let shape: TVec<TDim> = inputs[0].shape().iter().map(|d| d.to_dim()).collect();\n        self.to_optimized(&shape)?.eval(inputs)\n    }\n}\n\nimpl TypedOp for MaxPool {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut facts = self.pool_spec.output_facts(inputs)?;\n        if let Some(idt) = self.with_index_outputs {\n            facts.push(facts[0].clone());\n            facts[1].datum_type = idt;\n        }\n        Ok(facts)\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if self.with_index_outputs.is_some()\n            && node.outputs[1].successors.len() == 0\n            && !model.output_outlets()?.contains(&OutletId::new(node.id, 1))\n        {\n            let op = Self { with_index_outputs: None, ..self.clone() };\n            let mut patch = TypedModelPatch::default();\n            let mut wire = patch.tap_model(model, node.inputs[0])?;\n            wire = patch.wire_node(&node.name, op, &[wire])?[0];\n            patch.shunt_outside(model, node.id.into(), wire)?;\n            return Ok(Some(patch));\n        }\n        let fact = model.outlet_fact(node.inputs[0])?;\n        if let Some(pool_spec) = self.pool_spec.declutter(&fact.shape)? {\n            return Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &node.inputs,\n                Self { pool_spec, ..self.clone() },\n            )?));\n        }\n        Ok(None)\n    }\n\n    as_op!();\n}\n\nimpl MaxPool {\n    fn to_optimized(&self, input_shape: &[TDim]) -> TractResult<OptMaxPool> {\n        Ok(OptMaxPool {\n            pool_spec: self.pool_spec.clone(),\n            with_index_outputs: self.with_index_outputs,\n            geometry: self.pool_spec.compute_geo(input_shape)?,\n        })\n    }\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct OptMaxPool {\n    pub pool_spec: PoolSpec,\n    pub with_index_outputs: Option<DatumType>,\n    pub geometry: PoolGeometry,\n}\n\nimpl Op for OptMaxPool {\n    fn name(&self) -> StaticName {\n        \"OptMaxPool\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(self.pool_spec.info())\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for OptMaxPool {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let geo = self.geometry.to_concrete(input.shape())?;\n        dispatch_numbers!(Self::eval_t(input.datum_type())(self, &*input, geo.as_ref()))\n    }\n}\n\nimpl TypedOp for OptMaxPool {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut facts = self.pool_spec.output_facts(inputs)?;\n        if let Some(idt) = self.with_index_outputs {\n            facts.push(facts[0].clone());\n            facts[1].datum_type = idt;\n        }\n        Ok(facts)\n    }\n\n    as_op!();\n}\n\nimpl OptMaxPool {\n    fn eval_t<T: Datum + Copy + num_traits::Bounded + PartialOrd>(\n        &self,\n        input: &Tensor,\n        geo: &ConcretePoolGeometry,\n    ) -> TractResult<TVec<TValue>> {\n        let input_dt = input.datum_type();\n        let input_plain = input.try_as_plain()?;\n        let input: ArrayViewD<T> = input_plain.to_array_view()?;\n        let input_ptr = input.as_ptr();\n\n        let mut values = unsafe { ArrayD::<T>::uninit(&*geo.output_shape.shape).assume_init() };\n        let mut indices = if self.with_index_outputs.is_some() {\n            Some(unsafe { ArrayD::<i32>::uninit(&*geo.output_shape.shape).assume_init() })\n        } else {\n            None\n        };\n        let n = *geo.input_shape.n().unwrap_or(&1);\n        let n_stride_i = geo.input_shape.n_stride().unwrap_or(&0);\n        let n_stride_o = geo.output_shape.n_stride().unwrap_or(&0);\n        unsafe {\n            geo.patch.visit_output(|visitor| {\n                for n in 0..n {\n                    let input_offset = n * n_stride_i;\n                    let output_offset = n * n_stride_o;\n                    for c in 0..*geo.input_shape.c() {\n                        let input_offset = input_offset + geo.input_shape.c_stride() * c;\n                        let output_offset = output_offset + geo.output_shape.c_stride() * c;\n                        let max = visitor\n                            .valid_offsets()\n                            .map(|v| (v, *input_ptr.offset(v + input_offset as isize)))\n                            .fold((0, T::min_value()), |acc, v| if acc.1 < v.1 { v } else { acc });\n                        *values\n                            .as_mut_ptr()\n                            .offset(output_offset as isize + visitor.output_offset) = max.1;\n                        if let Some(ref mut indices) = indices {\n                            *indices\n                                .as_mut_ptr()\n                                .offset(output_offset as isize + visitor.output_offset) =\n                                max.0 as i32 / geo.patch.spec.output_inner_stride as i32;\n                        }\n                    }\n                }\n            });\n        }\n        let mut values = values.into_tensor();\n        unsafe {\n            values.set_datum_type(input_dt);\n        }\n        if let Some(dt) = self.with_index_outputs {\n            Ok(tvec!(\n                values.into_tvalue(),\n                indices.unwrap().into_tensor().cast_to_dt(dt)?.into_owned().into_tvalue()\n            ))\n        } else {\n            Ok(tvec!(values.into_tvalue()))\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/mod.rs",
    "content": "use crate::internal::*;\n\npub mod conv;\npub mod deconv;\nmod maxpool;\nmod padding;\nmod patch_axis;\nmod patches;\npub mod pools;\nmod sumpool;\n\npub use self::conv::{Conv, KernelFormat};\npub use self::deconv::Deconv;\npub use self::maxpool::MaxPool;\npub use self::padding::PaddingSpec;\npub use self::patch_axis::PatchAxis;\npub use self::patches::{Patch, PatchSpec};\npub use self::pools::PoolSpec;\npub use self::sumpool::SumPool;\n\nuse super::array::MultiBroadcastTo;\n\npub fn wire_reshape_bias_as_vector(\n    model: &mut TypedModel,\n    name: impl AsRef<str>,\n    outlet: OutletId,\n    output_channels: usize,\n) -> TractResult<TVec<OutletId>> {\n    let name = name.as_ref();\n    let mut bias = tvec!(outlet);\n    let fact = model.outlet_fact(outlet)?.clone();\n    if fact.shape.volume().is_one() && fact.rank() > 0 {\n        bias = model.wire_node(\n            format!(\"{name}.bias.make_scalar\"),\n            AxisOp::Reshape(0, fact.shape.to_tvec(), tvec![]),\n            &bias,\n        )?;\n    }\n    if model.outlet_fact(bias[0])?.rank() == 0 {\n        bias = model.wire_node(\n            format!(\"{name}.bias.broadcast\"),\n            MultiBroadcastTo { shape: tvec!(output_channels).into() },\n            &bias,\n        )?;\n    }\n    Ok(bias)\n}\n\npub fn wire_reshape_bias_for_bin(\n    model: &mut TypedModel,\n    name: impl AsRef<str>,\n    outlet: OutletId,\n    rank: usize,\n    c_axis: usize,\n    output_channels: usize,\n) -> TractResult<TVec<OutletId>> {\n    let name = name.as_ref();\n    let mut bias = wire_reshape_bias_as_vector(model, name, outlet, output_channels)?;\n    let fact = model.outlet_fact(bias[0])?.clone();\n    let mut bias_final_shape = tvec![1.to_dim(); rank];\n    bias_final_shape[c_axis] = output_channels.to_dim();\n    if *bias_final_shape != *fact.shape {\n        bias = model.wire_node(\n            format!(\"{name}.bias\"),\n            AxisOp::Reshape(0, fact.shape.to_tvec(), bias_final_shape),\n            &bias,\n        )?;\n    }\n    Ok(bias)\n}\n\npub fn rewrite_conv_with_n_axis(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    conv: &Conv,\n) -> TractResult<Option<TypedModelPatch>> {\n    if !conv.pool_spec.data_format.has_n() {\n        let mut new = conv.clone();\n        new.pool_spec.data_format = conv.pool_spec.data_format.with_n();\n        let mut patch = TypedModelPatch::default();\n        let mut wire = patch.taps(model, &node.inputs)?;\n        wire[0] = patch.wire_node(format!(\"{name}.add_n\"), AxisOp::Add(0), &[wire[0]])?[0];\n        wire = patch.wire_node(name, new, &wire)?;\n        wire = patch.wire_node(format!(\"{name}.rm_n\"), AxisOp::Rm(0), &wire)?;\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n\npub fn rewrite_deconv_with_n_axis(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    deconv: &Deconv,\n) -> TractResult<Option<TypedModelPatch>> {\n    if !deconv.pool_spec.data_format.has_n() {\n        let mut new = deconv.clone();\n        new.pool_spec.data_format = deconv.pool_spec.data_format.with_n();\n        let mut patch = TypedModelPatch::default();\n        let mut wire = patch.taps(model, &node.inputs)?;\n        wire[0] = patch.wire_node(format!(\"{name}.add_n\"), AxisOp::Add(0), &[wire[0]])?[0];\n        wire = patch.wire_node(name, new, &wire)?;\n        wire = patch.wire_node(format!(\"{name}.rm_n\"), AxisOp::Rm(0), &wire)?;\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "core/src/ops/cnn/padding.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]\npub enum PaddingSpec {\n    Explicit(TVec<usize>, TVec<usize>),\n    ExplicitOnnxPool(TVec<usize>, TVec<usize>, bool),\n    #[default]\n    Valid,\n    SameUpper,\n    SameLower,\n}\n\nuse PaddingSpec::*;\n\n#[derive(Debug, Clone, new, PartialEq, Eq)]\npub struct ComputedPaddedDim<D: DimLike> {\n    pub deconvoluted: D,\n    pub convoluted: D,\n    pub pad_before: D,\n    pub pad_after: D,\n}\n\nimpl PaddingSpec {\n    pub fn valid_dim(&self, d: usize, stride_is_one: bool) -> bool {\n        match self {\n            Valid => true,\n            Explicit(bef, aft) => bef[d] == 0 && aft[d] == 0,\n            ExplicitOnnxPool(a, b, ceil_mode) => {\n                (*ceil_mode || stride_is_one) && a[d] == 0 && b[d] == 0\n            }\n            _ => false,\n        }\n    }\n\n    pub fn change_geo_axes(&self, op: &AxisOp) -> TractResult<PaddingSpec> {\n        match &self {\n            ExplicitOnnxPool(before, after, round) => {\n                let mut before: TVec<usize> = before.clone();\n                let mut after: TVec<usize> = after.clone();\n                op.change_shape_array(&mut before, false)?;\n                op.change_shape_array(&mut after, false)?;\n                if let AxisOp::Add(add) = op {\n                    before[*add] = 0;\n                    after[*add] = 0;\n                }\n                Ok(ExplicitOnnxPool(before, after, *round))\n            }\n            Explicit(before, after) => {\n                let mut before: TVec<usize> = before.clone();\n                let mut after: TVec<usize> = after.clone();\n                op.change_shape_array(&mut before, false)?;\n                op.change_shape_array(&mut after, false)?;\n                if let AxisOp::Add(add) = op {\n                    before[*add] = 0;\n                    after[*add] = 0;\n                }\n                Ok(Explicit(before, after))\n            }\n            Valid | SameLower | SameUpper => Ok(self.clone()),\n        }\n    }\n\n    pub fn compute<D: DimLike>(\n        &self,\n        input_spatial_shape: &[D],\n        kernel_spatial_shape: &[usize],\n        dilations: &[usize],\n        strides: &[usize],\n    ) -> TVec<ComputedPaddedDim<D>> {\n        (0..input_spatial_shape.len())\n            .map(|d| {\n                self.compute_one(\n                    d,\n                    &input_spatial_shape[d],\n                    kernel_spatial_shape[d],\n                    dilations[d],\n                    strides[d],\n                )\n            })\n            .collect()\n    }\n\n    pub fn compute_for_deconv<D: DimLike>(\n        &self,\n        conv_spatial_shape: &[D],\n        kernel_spatial_shape: &[usize],\n        dilations: &[usize],\n        strides: &[usize],\n        adjustments: &[usize],\n    ) -> TractResult<TVec<ComputedPaddedDim<D>>> {\n        (0..conv_spatial_shape.len())\n            .map(|d| {\n                self.compute_one_for_deconv(\n                    d,\n                    &conv_spatial_shape[d],\n                    kernel_spatial_shape[d],\n                    dilations[d],\n                    strides[d],\n                    adjustments[d],\n                )\n            })\n            .collect()\n    }\n\n    pub fn compute_one<D: DimLike>(\n        &self,\n        axis: usize,\n        input: &D,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n    ) -> ComputedPaddedDim<D> {\n        match self {\n            Valid => Self::valid(input, kernel, dilation, stride),\n            Explicit(bef, aft) => {\n                Self::explicit(input, kernel, dilation, stride, bef[axis], aft[axis])\n            }\n            ExplicitOnnxPool(bef, aft, ceil_mode) => Self::explicit_onnx_pool(\n                input, kernel, dilation, stride, bef[axis], aft[axis], *ceil_mode,\n            ),\n            SameUpper => Self::same(input, kernel, dilation, stride, true),\n            SameLower => Self::same(input, kernel, dilation, stride, false),\n        }\n    }\n\n    pub fn compute_one_for_deconv<D: DimLike>(\n        &self,\n        axis: usize,\n        input: &D,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n        adjustment: usize,\n    ) -> TractResult<ComputedPaddedDim<D>> {\n        match self {\n            Valid => Self::valid_for_deconv(input, kernel, dilation, stride, adjustment),\n            SameUpper => Self::same_for_deconv(input, kernel, dilation, stride, adjustment, true),\n            SameLower => Self::same_for_deconv(input, kernel, dilation, stride, adjustment, false),\n            Explicit(bef, aft) => Self::explicit_for_deconv(\n                input, kernel, dilation, stride, bef[axis], aft[axis], adjustment,\n            ),\n            // unreachable ?\n            ExplicitOnnxPool(bef, aft, _ceil_mode) => Self::explicit_for_deconv(\n                input, kernel, dilation, stride, bef[axis], aft[axis], adjustment,\n            ),\n        }\n    }\n\n    fn valid<D: DimLike>(\n        input: &D,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n    ) -> ComputedPaddedDim<D> {\n        let kernel_field = (kernel - 1) * dilation + 1;\n        let output = if let Ok(int) = input.to_usize() {\n            D::from((int + 1).saturating_sub(kernel_field).divceil(stride))\n        } else {\n            (input.clone() + 1 - kernel_field).divceil(stride)\n        };\n        ComputedPaddedDim::new(input.clone(), output, 0.into(), 0.into())\n    }\n\n    fn valid_for_deconv<D: DimLike>(\n        convoluted: &D,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n        adjustment: usize,\n    ) -> TractResult<ComputedPaddedDim<D>> {\n        let kernel_field = (kernel - 1) * dilation + 1;\n        let deconvoluted = (convoluted.clone() - 1) * stride + kernel_field + adjustment;\n        Ok(ComputedPaddedDim::new(deconvoluted, convoluted.clone(), 0.into(), 0.into()))\n    }\n\n    fn explicit<D: DimLike>(\n        input: &D,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n        bef: usize,\n        aft: usize,\n    ) -> ComputedPaddedDim<D> {\n        if let Ok(i) = input.to_dim().to_usize() {\n            let ints = Self::explicit_usize(i, kernel, dilation, stride, bef, aft);\n            ComputedPaddedDim::new(\n                input.clone(),\n                ints.convoluted.into(),\n                ints.pad_before.into(),\n                ints.pad_after.into(),\n            )\n        } else {\n            let kernel_field = (kernel - 1) * dilation + 1;\n            let dividend = input.clone() + bef + aft - kernel_field;\n            let output = dividend.div(stride) + 1;\n            ComputedPaddedDim::new(input.clone(), output, bef.into(), aft.into())\n        }\n    }\n\n    fn explicit_usize(\n        input: usize,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n        bef: usize,\n        aft: usize,\n    ) -> ComputedPaddedDim<usize> {\n        let kernel_field = (kernel - 1) * dilation + 1;\n        let dividend = (input + bef + aft + 1).saturating_sub(kernel_field);\n        let output = dividend.divceil(stride);\n        ComputedPaddedDim::new(input, output, bef, aft)\n    }\n\n    fn explicit_onnx_pool<D: DimLike>(\n        input: &D,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n        bef: usize,\n        aft: usize,\n        ceil_mode: bool,\n    ) -> ComputedPaddedDim<D> {\n        if let Ok(i) = input.to_dim().to_usize() {\n            let ints =\n                Self::explicit_onnx_pool_usize(i, kernel, dilation, stride, bef, aft, ceil_mode);\n            ComputedPaddedDim::new(\n                input.clone(),\n                ints.convoluted.into(),\n                ints.pad_before.into(),\n                ints.pad_after.into(),\n            )\n        } else {\n            // output_spatial_shape[i] = ceil((input_spatial_shape[i] + pad_shape[i] - ((kernel_spatial_shape[i] - 1) * dilations[i] + 1)) / strides_spatial_shape[i] + 1)\n            let kernel_field = (kernel - 1) * dilation + 1;\n            let dividend = input.clone() + bef + aft - kernel_field;\n            let output =\n                if ceil_mode { dividend.divceil(stride) } else { dividend.div(stride) } + 1;\n            ComputedPaddedDim::new(input.clone(), output, bef.into(), aft.into())\n        }\n    }\n\n    fn explicit_onnx_pool_usize(\n        input: usize,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n        bef: usize,\n        aft: usize,\n        ceil_mode: bool,\n    ) -> ComputedPaddedDim<usize> {\n        // output_spatial_shape[i] = ceil((input_spatial_shape[i] + pad_shape[i] - ((kernel_spatial_shape[i] - 1) * dilations[i] + 1)) / strides_spatial_shape[i] + 1)\n        let kernel_field = (kernel - 1) * dilation + 1;\n        let dividend = (input + bef + aft).saturating_sub(kernel_field);\n        let mut output = if ceil_mode { dividend.divceil(stride) } else { dividend / stride } + 1;\n        if ceil_mode {\n            // ensure that the last pooling starts inside the image\n            // needed to avoid problems in ceil mode\n            if (output - 1) * stride >= input + bef {\n                output -= 1;\n            }\n        }\n        ComputedPaddedDim::new(input, output, bef, aft)\n    }\n\n    fn explicit_for_deconv<D: DimLike>(\n        convoluted: &D,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n        bef: usize,\n        aft: usize,\n        adjustment: usize,\n    ) -> TractResult<ComputedPaddedDim<D>> {\n        let kernel_field = (kernel - 1) * dilation + 1;\n        let deconvoluted =\n            (convoluted.clone() - 1) * stride + kernel_field - bef - aft + adjustment;\n        Ok(ComputedPaddedDim::new(deconvoluted, convoluted.clone(), bef.into(), aft.into()))\n    }\n\n    fn same<D: DimLike>(\n        input: &D,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n        upper: bool,\n    ) -> ComputedPaddedDim<D> {\n        let output = input.divceil(stride);\n        let kernel_field = (kernel - 1) * dilation + 1;\n        let pad = if let Ok(input) = input.to_usize() {\n            let pad = (((output.clone() - 1) * stride + kernel_field).to_usize().unwrap())\n                .saturating_sub(input);\n            pad.into()\n        } else {\n            (output.clone() - 1) * stride + kernel_field - input\n        };\n        let lower_pad = pad.clone() / 2;\n        let higher_pad = pad - &lower_pad;\n        let (before, after) = if upper { (lower_pad, higher_pad) } else { (higher_pad, lower_pad) };\n        ComputedPaddedDim::new(input.clone(), output, before, after) // TODO input is wrong for stride != 1\n    }\n\n    fn same_for_deconv<D: DimLike>(\n        convoluted: &D,\n        kernel: usize,\n        dilation: usize,\n        stride: usize,\n        adjustment: usize,\n        upper: bool,\n    ) -> TractResult<ComputedPaddedDim<D>> {\n        if (kernel - 1) * dilation < stride {\n            bail!(\n                \"Invalid axis geometry for SAME padding: expect (kernel_len - 1) * dilation > stride - 1\"\n            );\n        }\n        let kernel_field = (kernel - 1) * dilation + 1;\n        let crop = kernel_field + adjustment - stride;\n        let lower_crop = crop / 2;\n        let higher_crop = crop - lower_crop;\n        let (before, after) =\n            if upper { (lower_crop, higher_crop) } else { (higher_crop, lower_crop) };\n        let deconvoluted = (convoluted.clone() - 1) * stride + kernel_field - before - after;\n        Ok(ComputedPaddedDim::new(deconvoluted, convoluted.clone(), before.into(), after.into()))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use PaddingSpec as PS;\n\n    #[test]\n    fn same_stride_1() {\n        assert_eq!(PS::same(&1usize, 2usize, 1, 1, true), ComputedPaddedDim::new(1, 1, 0, 1));\n        assert_eq!(PS::same(&2usize, 2usize, 1, 1, true), ComputedPaddedDim::new(2, 2, 0, 1));\n        assert_eq!(PS::same(&3usize, 2usize, 1, 1, true), ComputedPaddedDim::new(3, 3, 0, 1));\n        assert_eq!(PS::same(&4usize, 2usize, 1, 1, true), ComputedPaddedDim::new(4, 4, 0, 1));\n    }\n\n    #[test]\n    fn same_stride_2() {\n        assert_eq!(PS::same(&1usize, 2usize, 1, 2, true), ComputedPaddedDim::new(1, 1, 0, 1));\n        assert_eq!(PS::same(&2usize, 2usize, 1, 2, true), ComputedPaddedDim::new(2, 1, 0, 0));\n        assert_eq!(PS::same(&3usize, 2usize, 1, 2, true), ComputedPaddedDim::new(3, 2, 0, 1));\n        assert_eq!(PS::same(&4usize, 2usize, 1, 2, true), ComputedPaddedDim::new(4, 2, 0, 0));\n    }\n\n    #[test]\n    fn same_1() {\n        assert_eq!(PS::same(&6usize, 1usize, 1, 2, true), ComputedPaddedDim::new(6, 3, 0, 0));\n    }\n\n    #[test]\n    fn same_lower() {\n        assert_eq!(PS::same(&10usize, 2usize, 1, 3, false), ComputedPaddedDim::new(10, 4, 1, 0));\n    }\n\n    #[test]\n    fn same_ker_3() {\n        assert_eq!(PS::same(&1usize, 3usize, 1, 1, true), ComputedPaddedDim::new(1, 1, 1, 1));\n        assert_eq!(PS::same(&2usize, 3usize, 1, 1, true), ComputedPaddedDim::new(2, 2, 1, 1));\n        assert_eq!(PS::same(&3usize, 3usize, 1, 1, true), ComputedPaddedDim::new(3, 3, 1, 1));\n        assert_eq!(PS::same(&4usize, 3usize, 1, 1, true), ComputedPaddedDim::new(4, 4, 1, 1));\n    }\n\n    #[test]\n    fn same_ker_3_stride_3() {\n        assert_eq!(PS::same(&3usize, 3usize, 1, 3, true), ComputedPaddedDim::new(3, 1, 0, 0));\n    }\n\n    #[test]\n    fn valid_1() {\n        assert_eq!(PS::valid(&10usize, 2usize, 1, 3), ComputedPaddedDim::new(10, 3, 0, 0));\n    }\n\n    #[test]\n    fn explicit_2() {\n        assert_eq!(\n            PS::explicit_onnx_pool(&28usize, 3usize, 1, 1, 2, 2, true),\n            ComputedPaddedDim::new(28, 30, 2, 2)\n        );\n    }\n\n    #[test]\n    #[ignore = \"ONNX weird output computation for explicit\"]\n    fn explicit_3() {\n        assert_eq!(\n            PS::explicit_onnx_pool(&2usize, 1usize, 1, 2, 0, 0, true),\n            ComputedPaddedDim::new(2, 2, 0, 0)\n        );\n    }\n\n    #[test]\n    fn same_upper() {\n        assert_eq!(PS::same(&7usize, 1usize, 1, 2, true), ComputedPaddedDim::new(7, 4, 0, 0));\n    }\n\n    // 0 1 2 3 4 5 6 7 8 9 a b\n    // 012 345 678 9ab\n    #[test]\n    fn bug_explicit_stride() {\n        assert_eq!(\n            PS::explicit_onnx_pool(&12usize, 3usize, 1, 3, 0, 0, false),\n            ComputedPaddedDim::new(12, 4, 0, 0)\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/patch_axis.rs",
    "content": "use crate::internal::*;\n\nuse std::ops::Range;\nuse tract_itertools::Itertools;\n\n#[derive(Clone, Debug, new, PartialEq, Eq)]\npub struct Region {\n    pub range: Range<usize>,\n    pub mask: Option<TVec<bool>>,\n}\n\n#[derive(Clone, Debug, new, PartialEq, Eq)]\npub struct PatchAxis {\n    pub input_dim: usize,\n    pub kernel_dim: usize,\n    pub pad_before: usize,\n    pub pad_after: usize,\n    pub output_dim: usize,\n    pub stride: usize,\n    pub dilation: usize,\n}\n\nimpl PatchAxis {\n    fn valid_range(&self) -> Option<Range<usize>> {\n        let field = (self.kernel_dim - 1) * self.dilation + 1;\n        if field > self.input_dim {\n            return None;\n        }\n        let min = self.pad_before.divceil(self.stride);\n        let max = (self.input_dim + self.pad_before).saturating_sub(field) / self.stride;\n        if max >= min { Some(min..(max + 1)) } else { None }\n    }\n\n    fn invalid_at_left(&self, pos: usize) -> usize {\n        let center_pos = pos * self.stride;\n        self.pad_before.saturating_sub(center_pos).divceil(self.dilation).min(self.kernel_dim)\n    }\n\n    fn invalid_at_right(&self, pos: usize) -> usize {\n        let center_pos = pos * self.stride;\n        let last_valid = self.input_dim + self.pad_before;\n        let valid = last_valid.saturating_sub(center_pos).divceil(self.dilation);\n        self.kernel_dim.saturating_sub(valid)\n    }\n\n    fn make_invalid_regions(&self, range: Range<usize>) -> TVec<Region> {\n        range\n            .map(move |ix| (ix, (self.invalid_at_left(ix), self.invalid_at_right(ix))))\n            .chunk_by(|&pair| pair.1)\n            .into_iter()\n            .map(move |(invalid, pairs)| {\n                let (min, max) = pairs.map(|p| p.0).minmax().into_option().unwrap();\n                let mut mask = tvec!(false; self.kernel_dim);\n                for i in 0..invalid.0 {\n                    mask[i] = true;\n                }\n                for i in 0..invalid.1 {\n                    mask[self.kernel_dim - 1 - i] = true;\n                }\n                Region::new(min..max + 1, Some(mask))\n            })\n            .collect()\n    }\n\n    pub fn regions(&self) -> TVec<Region> {\n        let mut regions = tvec!();\n        if let Some(valid_range) = self.valid_range() {\n            if valid_range.start > 0 {\n                regions.extend(self.make_invalid_regions(0..valid_range.start));\n            }\n            if valid_range.start != valid_range.end {\n                regions.push(Region::new(valid_range.clone(), None));\n            }\n            if valid_range.end < self.output_dim {\n                regions.extend(self.make_invalid_regions(valid_range.end..self.output_dim));\n            }\n        } else {\n            regions.extend(self.make_invalid_regions(0..self.output_dim));\n        }\n        regions\n    }\n}\n\n#[cfg(test)]\npub mod test {\n    use super::*;\n\n    // • 0 1 2 3 4 • -> 3 -> (0) 1 2 3 (4)\n    fn axis_5_3() -> PatchAxis {\n        PatchAxis::new(5, 3, 1, 1, 5, 1, 1)\n    }\n\n    // • • 0 1 2 3 4 • -> 4 -> (0) (1) 2 3 (4)\n    fn axis_5_4() -> PatchAxis {\n        PatchAxis::new(5, 4, 2, 1, 5, 1, 1)\n    }\n\n    // • • 0 1 2 3 4 • • -> 4 -> (0) (1) 2 (3) (4)\n    fn axis_5_5() -> PatchAxis {\n        PatchAxis::new(5, 5, 2, 2, 5, 1, 1)\n    }\n\n    // • 0 1 2 3 4 • -> 3 -> (0) 2 (4)\n    fn axis_5_3_s2() -> PatchAxis {\n        PatchAxis::new(5, 3, 1, 1, 3, 2, 1)\n    }\n\n    // • • 0 1 2 3 4 • • -> 3x2 -> (0) (1) 2 (3) (4)\n    fn axis_5_3_d2() -> PatchAxis {\n        PatchAxis::new(5, 3, 2, 2, 5, 1, 2)\n    }\n\n    // 0 1 2 3 4 5 6 7 8 9 -> 2 -> 0 3 6\n    fn axis_10_2_s3_valid() -> PatchAxis {\n        PatchAxis::new(10, 2, 0, 0, 3, 3, 1)\n    }\n\n    #[test]\n    fn axis_valid_ranges() {\n        assert_eq!(axis_5_3().valid_range(), Some(1..4));\n        assert_eq!(axis_5_4().valid_range(), Some(2..4));\n        assert_eq!(axis_5_5().valid_range(), Some(2..3));\n        assert_eq!(axis_5_3_s2().valid_range(), Some(1..2));\n        assert_eq!(axis_5_3_d2().valid_range(), Some(2..3));\n    }\n\n    #[test]\n    fn axis_invalid_at_left() {\n        assert_eq!(axis_5_3().invalid_at_left(0), 1);\n        assert_eq!(axis_5_3().invalid_at_left(1), 0);\n        assert_eq!(axis_5_3().invalid_at_left(2), 0);\n\n        assert_eq!(axis_5_4().invalid_at_left(0), 2);\n        assert_eq!(axis_5_4().invalid_at_left(1), 1);\n        assert_eq!(axis_5_4().invalid_at_left(2), 0);\n\n        assert_eq!(axis_5_5().invalid_at_left(0), 2);\n        assert_eq!(axis_5_5().invalid_at_left(1), 1);\n        assert_eq!(axis_5_5().invalid_at_left(2), 0);\n\n        assert_eq!(axis_5_3_d2().invalid_at_left(0), 1);\n        assert_eq!(axis_5_3_d2().invalid_at_left(1), 1);\n        assert_eq!(axis_5_3_d2().invalid_at_left(2), 0);\n    }\n\n    #[test]\n    fn axis_invalid_at_right() {\n        assert_eq!(axis_5_3().invalid_at_right(0), 0);\n        assert_eq!(axis_5_3().invalid_at_right(3), 0);\n        assert_eq!(axis_5_3().invalid_at_right(4), 1);\n\n        assert_eq!(axis_5_4().invalid_at_right(0), 0);\n        assert_eq!(axis_5_4().invalid_at_right(3), 0);\n        assert_eq!(axis_5_4().invalid_at_right(4), 1);\n\n        assert_eq!(axis_5_5().invalid_at_right(0), 0);\n        assert_eq!(axis_5_5().invalid_at_right(3), 1);\n        assert_eq!(axis_5_5().invalid_at_right(4), 2);\n    }\n\n    #[test]\n    fn axis_5_3_regions() {\n        let regions = axis_5_3().regions();\n        assert_eq!(\n            regions,\n            tvec!(\n                Region::new(0..1, Some(tvec!(true, false, false))),\n                Region::new(1..4, None),\n                Region::new(4..5, Some(tvec!(false, false, true)))\n            )\n        );\n    }\n\n    #[test]\n    fn axis_5_3_s2_regions() {\n        let regions = axis_5_3_s2().regions();\n        assert_eq!(\n            regions,\n            tvec!(\n                Region::new(0..1, Some(tvec!(true, false, false))),\n                Region::new(1..2, None),\n                Region::new(2..3, Some(tvec!(false, false, true)))\n            )\n        );\n    }\n\n    #[test]\n    fn axis_5_3_d2_regions() {\n        let regions = axis_5_3_d2().regions();\n        assert_eq!(\n            regions,\n            tvec!(\n                Region::new(0..2, Some(tvec!(true, false, false))),\n                Region::new(2..3, None),\n                Region::new(3..5, Some(tvec!(false, false, true)))\n            )\n        );\n    }\n\n    #[test]\n    fn axis_10_2_s3_valid_regions() {\n        let regions = axis_10_2_s3_valid().regions();\n        assert_eq!(regions, tvec!(Region::new(0..3, None),));\n    }\n\n    #[test]\n    fn axis_7_3_s2_regions() {\n        // • 0 1 2 3 4 5 6 • -> 3 -> (0) 2 4 (6)\n        let regions = PatchAxis::new(7, 3, 1, 1, 4, 2, 1).regions();\n        assert_eq!(\n            regions,\n            tvec!(\n                Region::new(0..1, Some(tvec!(true, false, false))),\n                Region::new(1..3, None),\n                Region::new(3..4, Some(tvec!(false, false, true)))\n            )\n        );\n    }\n\n    #[test]\n    fn axis_5_2_s2_regions() {\n        // • 0 1 2 3 4 • -> 2 -> (0) 2 4\n        let regions = PatchAxis::new(5, 2, 1, 1, 3, 2, 1).regions();\n        assert_eq!(\n            regions,\n            tvec!(Region::new(0..1, Some(tvec!(true, false))), Region::new(1..3, None),)\n        );\n    }\n\n    #[test]\n    fn axis_28_3_very_padded_regions() {\n        // • • 0 1 2 3 ... 26 27 • • -> 2 -> (-1) (0) (1) 2 3 4 ... 26 (27) (28) (29)\n        let regions = PatchAxis::new(28, 3, 2, 2, 30, 1, 1).regions();\n        assert_eq!(\n            regions,\n            tvec!(\n                Region::new(0..1, Some(tvec!(true, true, false))),\n                Region::new(1..2, Some(tvec!(true, false, false))),\n                Region::new(2..28, None),\n                Region::new(28..29, Some(tvec!(false, false, true))),\n                Region::new(29..30, Some(tvec!(false, true, true))),\n            )\n        );\n    }\n\n    #[test]\n    fn axis_7_1_s2_regions() {\n        // 0 1 2 3 4 5 6 -> 1 -> 0 2 4 6\n        let regions = PatchAxis::new(7, 1, 0, 0, 4, 2, 1).regions();\n        assert_eq!(regions, tvec!(Region::new(0..4, None),));\n    }\n\n    #[test]\n    fn axis_1_2_regions() {\n        // 0 -> 2 -> (0)\n        let regions = PatchAxis::new(1, 2, 0, 1, 1, 1, 1).regions();\n        assert_eq!(regions, tvec!(Region::new(0..1, Some(tvec!(false, true))),));\n    }\n\n    #[test]\n    fn axis_dnn_left_pad() {\n        let regions = PatchAxis::new(1, 1, 2, 0, 3, 1, 1).regions();\n        assert_eq!(regions, tvec!(Region::new(0..2, Some(tvec!(true))), Region::new(2..3, None)));\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/patches.rs",
    "content": "use crate::internal::*;\nuse crate::ops::cnn::PaddingSpec;\nuse crate::ops::nn::{DataFormat, DataShape};\nuse ndarray::prelude::*;\n\nuse super::PatchAxis;\n\nuse std::fmt::Debug;\nuse std::ops::Range;\n\nuse tract_itertools::{Itertools, izip};\n\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct PatchSpec {\n    pub input_shape: TVec<usize>,\n    pub input_inner_stride: usize,\n    pub output_inner_stride: usize,\n    pub kernel_shape: TVec<usize>,\n    pub strides: TVec<usize>,\n    pub dilations: TVec<usize>,\n    pub padding: PaddingSpec,\n}\n\nimpl Debug for PatchSpec {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"input: {} kernel: {} strides: {} dil: {} pad: {:?}\",\n            self.input_shape.iter().join(\",\"),\n            self.kernel_shape.iter().join(\",\"),\n            self.strides.iter().join(\",\"),\n            self.dilations.iter().join(\",\"),\n            self.padding\n        )\n    }\n}\n\nimpl PatchSpec {\n    pub fn for_full_shape(\n        data_format: DataFormat,\n        input_full_shape: &[usize],\n    ) -> TractResult<PatchSpec> {\n        let shape = data_format.shape(input_full_shape.into())?;\n        Ok(Self::for_data_shape(shape))\n    }\n\n    pub fn for_data_shape(data_shape: DataShape) -> PatchSpec {\n        let input_shape: TVec<usize> = data_shape.hw_dims().into();\n        PatchSpec {\n            kernel_shape: tvec!(1; input_shape.len()),\n            input_inner_stride: *data_shape.w_stride(),\n            output_inner_stride: 1,\n            strides: tvec!(1; input_shape.len()),\n            dilations: tvec!(1; input_shape.len()),\n            padding: PaddingSpec::Valid,\n            input_shape,\n        }\n    }\n\n    pub fn with_kernel_shape(self, kernel_shape: TVec<usize>) -> PatchSpec {\n        PatchSpec { kernel_shape, ..self }\n    }\n\n    pub fn with_dilations(self, dilations: TVec<usize>) -> PatchSpec {\n        PatchSpec { dilations, ..self }\n    }\n\n    pub fn with_strides(self, strides: TVec<usize>) -> PatchSpec {\n        PatchSpec { strides, ..self }\n    }\n\n    pub fn with_padding(self, padding: PaddingSpec) -> PatchSpec {\n        PatchSpec { padding, ..self }\n    }\n\n    pub fn with_output_inner_stride(self, output_inner_stride: usize) -> PatchSpec {\n        PatchSpec { output_inner_stride, ..self }\n    }\n\n    pub fn into_patch(self) -> Patch {\n        let dims = self.padding.compute(\n            &self.input_shape,\n            &self.kernel_shape,\n            &self.dilations,\n            &self.strides,\n        );\n        let output: TVec<usize> = dims.iter().map(|d| d.convoluted).collect();\n        let pad_before: TVec<usize> = dims.iter().map(|d| d.pad_before).collect();\n        let pad_after: TVec<usize> = dims.iter().map(|d| d.pad_after).collect();\n\n        let data_field: Vec<isize> = ::ndarray::indices(&*self.kernel_shape)\n            .into_iter()\n            .flat_map(|coords| {\n                #[allow(clippy::unnecessary_to_owned)] // I think this one is a clippy bug.\n                coords\n                    .slice()\n                    .to_vec()\n                    .into_iter()\n                    .enumerate()\n                    .map(|(ix, c)| (c * self.dilations[ix]) as isize - pad_before[ix] as isize)\n            })\n            .collect();\n        let data_field = Array2::from_shape_vec(\n            (self.kernel_shape.iter().cloned().product(), self.kernel_shape.len()),\n            data_field,\n        )\n        .unwrap();\n        let data_field_min_max: TVec<_> = data_field\n            .columns()\n            .into_iter()\n            .map(|col| (col.iter().min().cloned().unwrap(), col.iter().max().cloned().unwrap()))\n            .collect();\n\n        fn strides(shape: &[usize], inner: usize) -> TVec<isize> {\n            let mut strides: TVec<isize> = tvec![inner as isize];\n            for dim in shape.iter().skip(1).rev() {\n                let previous = *strides.last().unwrap();\n                strides.push(*dim as isize * previous);\n            }\n            strides.reverse();\n            strides\n        }\n\n        let input_storage_strides = strides(&self.input_shape, self.input_inner_stride);\n        let output_storage_strides = strides(&output, self.output_inner_stride);\n\n        let standard_layout_data_field: Vec<isize> = data_field\n            .outer_iter()\n            .map(|coords| izip!(coords, &input_storage_strides).map(|(a, b)| a * b).sum::<isize>())\n            .collect();\n\n        // regions[axis][range+mask]\n        let regions: TVec<TVec<_>> = dims\n            .iter()\n            .enumerate()\n            .map(|(ix, d)| {\n                PatchAxis {\n                    input_dim: self.input_shape[ix],\n                    kernel_dim: self.kernel_shape[ix],\n                    pad_before: d.pad_before,\n                    pad_after: d.pad_after,\n                    output_dim: d.convoluted,\n                    stride: self.strides[ix],\n                    dilation: self.dilations[ix],\n                }\n                .regions()\n            })\n            .collect::<TVec<_>>();\n\n        let zone_strides = strides(&regions.iter().map(|d| d.len()).collect::<TVec<_>>(), 1);\n\n        let zones: Vec<Zone> = regions\n            .iter()\n            .multi_cartesian_product()\n            .map(|regions| Zone {\n                input_zone_offset: 0,\n                output_ranges: regions.iter().map(|reg| reg.range.clone()).collect(),\n                output_shape: regions.iter().map(|reg| reg.range.end - reg.range.start).collect(),\n                output_zone_offset: izip!(&regions, &output_storage_strides)\n                    .map(|(reg, &stride)| reg.range.start as isize * stride)\n                    .sum::<isize>(),\n                valid: regions.iter().all(|reg| reg.mask.is_none()),\n                values_offsets: izip!(\n                    0..,\n                    ndarray::indices(&*self.kernel_shape),\n                    &standard_layout_data_field\n                )\n                .filter(|(_ix, coords, _offset)| {\n                    izip!(coords.slice(), &regions)\n                        .all(|(&x, axis)| !axis.mask.as_ref().map(|mask| mask[x]).unwrap_or(false))\n                })\n                .map(|(ix, _coords, &window_offset)| (ix, window_offset))\n                .collect(),\n            })\n            .collect();\n\n        let valid_zone = zones.iter().position(|z| z.valid);\n\n        let mut valid_output_zone = tvec!();\n        let mut invalid_output_zones = tvec!();\n        for ix in 0..self.input_shape.len() {\n            let min_max = data_field_min_max[ix];\n            let min = (-min_max.0 as usize).divceil(self.strides[ix]);\n            let max =\n                (self.input_shape[ix].saturating_sub(min_max.1 as usize)).divceil(self.strides[ix]);\n            if min != 0 {\n                let mut invalid = valid_output_zone.clone();\n                invalid.push(0..min);\n                while invalid.len() < output.len() {\n                    invalid.push(0..output[invalid.len()])\n                }\n                invalid_output_zones.push(invalid);\n            }\n            if max < output[ix] {\n                let mut invalid = valid_output_zone.clone();\n                invalid.push(max..output[ix]);\n                while invalid.len() < output.len() {\n                    invalid.push(0..output[invalid.len()])\n                }\n                invalid_output_zones.push(invalid);\n            }\n            valid_output_zone.push(min..max)\n        }\n\n        let op_strides_times_input_storage_strides =\n            izip!(&self.strides, &input_storage_strides).map(|(a, b)| *a as isize * b).collect();\n\n        Patch {\n            spec: self,\n            padded: pad_before.iter().any(|&p| p != 0) || pad_after.iter().any(|&p| p != 0),\n            pad_before,\n            pad_after,\n            output_shape: output,\n            data_field,\n            data_field_min_max,\n            standard_layout_data_field,\n            input_storage_strides,\n            output_storage_strides,\n            op_strides_times_input_storage_strides,\n            valid_output_zone,\n            invalid_output_zones,\n            zones,\n            valid_zone_id: valid_zone,\n            zone_strides,\n        }\n    }\n}\n\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct Patch {\n    pub spec: PatchSpec,\n    pub pad_before: TVec<usize>,\n    pub pad_after: TVec<usize>,\n    pub padded: bool,\n    pub output_shape: TVec<usize>,\n    pub data_field: Array2<isize>,\n    pub data_field_min_max: TVec<(isize, isize)>,\n    pub standard_layout_data_field: Vec<isize>,\n    pub op_strides_times_input_storage_strides: TVec<isize>,\n    pub valid_output_zone: TVec<Range<usize>>,\n    pub invalid_output_zones: TVec<TVec<Range<usize>>>,\n    pub zones: Vec<Zone>,\n    pub valid_zone_id: Option<usize>,\n    pub zone_strides: TVec<isize>,\n    pub input_storage_strides: TVec<isize>,\n    pub output_storage_strides: TVec<isize>,\n}\n\nimpl Debug for Patch {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{:?}\", self.spec)\n    }\n}\n\nimpl Patch {\n    #[inline]\n    pub fn rank(&self) -> usize {\n        self.spec.input_shape.len()\n    }\n\n    unsafe fn is_valid(&self, coords: &[usize]) -> bool {\n        unsafe {\n            for ix in 0..self.rank() {\n                let c = *coords.get_unchecked(ix) as isize;\n                let strides = *self.spec.strides.get_unchecked(ix) as isize;\n                let pos = c * strides;\n                let min_max = self.data_field_min_max.get_unchecked(ix);\n                if pos + min_max.0 < 0\n                    || pos + min_max.1 >= *self.spec.input_shape.get_unchecked(ix) as isize\n                {\n                    return false;\n                }\n            }\n            true\n        }\n    }\n\n    pub fn valid_zone(&self) -> Option<&Zone> {\n        self.valid_zone_id.map(|id| &self.zones[id])\n    }\n\n    #[inline]\n    pub fn visit_output(&self, mut acceptor: impl FnMut(&Scanner)) {\n        if self.zones.len() == 0 {\n            return;\n        }\n        let mut scanner = Scanner::new(self);\n        while !scanner.done() {\n            acceptor(&scanner);\n            scanner.next();\n        }\n    }\n\n    pub fn centers_offsets(&self) -> Vec<isize> {\n        if self.zones.len() == 0 {\n            return vec![];\n        }\n        let mut scanner = Scanner::new(self);\n        let len = self.output_shape.iter().cloned().product();\n        let mut v = Vec::with_capacity(len);\n        for _ in 0..len {\n            v.push(scanner.input_center_offset);\n            scanner.next()\n        }\n        v\n    }\n\n    pub fn at<'p>(&'p self, coords: &[usize]) -> PatchIterator<'p> {\n        self.at_hint(coords, None)\n    }\n\n    pub fn at_hint<'p>(&'p self, coords: &[usize], hint: Option<bool>) -> PatchIterator<'p> {\n        unsafe {\n            assert_eq!(coords.len(), self.spec.kernel_shape.len());\n            let mut center = 0;\n            for i in 0..self.op_strides_times_input_storage_strides.len() {\n                center += *self.op_strides_times_input_storage_strides.get_unchecked(i)\n                    * *coords.get_unchecked(i) as isize;\n            }\n            let valid = hint.unwrap_or_else(|| !self.padded || self.is_valid(coords));\n            if valid {\n                PatchIterator::Fast(FastPatchIterator { patch: self, center, item: 0 })\n            } else {\n                let mut input_patch_center: TVec<_> = coords.into();\n                input_patch_center\n                    .iter_mut()\n                    .zip(self.spec.strides.iter())\n                    .for_each(|(a, &b)| *a *= b);\n                PatchIterator::Safe(SafePatchIterator {\n                    patch: self,\n                    item: 0,\n                    input_patch_center,\n                    center,\n                })\n            }\n        }\n    }\n\n    pub fn global_offset_for(&self, coords: &[usize], patch_index: usize) -> usize {\n        assert_eq!(coords.len(), self.spec.kernel_shape.len());\n        let center = izip!(coords, &self.op_strides_times_input_storage_strides)\n            .map(|(a, b)| *a as isize * *b)\n            .sum::<isize>();\n        (center + self.standard_layout_data_field[patch_index]) as usize\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct Zone {\n    pub valid: bool,\n    pub input_zone_offset: isize,\n    pub output_zone_offset: isize,\n    pub output_ranges: Box<[Range<usize>]>,\n    pub output_shape: Box<[usize]>,\n    /// (index in kernel, offset from center in image)\n    pub values_offsets: Box<[(usize, isize)]>,\n}\n\nimpl Zone {\n    pub fn contains_output(&self, coords: &[usize]) -> bool {\n        self.output_ranges.iter().zip(coords).all(|(range, &x)| x >= range.start && x < range.end)\n    }\n\n    #[inline]\n    pub fn visit_output(&self, patch: &Patch, mut acceptor: impl FnMut(&ZoneScanner)) {\n        let mut scanner = ZoneScanner::new(self, patch);\n        while !scanner.done() {\n            acceptor(&scanner);\n            scanner.next();\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct ZoneScanner<'p> {\n    pub patch: &'p Patch,\n    pub zone: &'p Zone,\n    pub output_offset: isize,\n    pub output_coords: Box<[usize]>,\n    pub input_center_offset: isize,\n    pub inner_loop_axis: usize,\n    pub inner_loop_len: usize,\n    pub inner_loop_output_range: Range<usize>,\n    pub inner_loop_output_stride: isize,\n    pub inner_loop_input_full_stride: isize,\n    pub done: bool,\n}\n\nimpl<'p> ZoneScanner<'p> {\n    pub fn new(zone: &'p Zone, patch: &'p Patch) -> ZoneScanner<'p> {\n        let inner_loop_axis =\n            zone.output_shape.iter().enumerate().max_by_key(|(_, dim)| *dim).unwrap().0;\n        let inner_loop_output_range = zone.output_ranges[inner_loop_axis].clone();\n        let inner_loop_output_stride = patch.output_storage_strides[inner_loop_axis];\n        let inner_loop_input_full_stride =\n            patch.op_strides_times_input_storage_strides[inner_loop_axis];\n        let mut scan = ZoneScanner {\n            patch,\n            zone,\n            output_offset: 0,\n            input_center_offset: 0,\n            inner_loop_axis,\n            inner_loop_len: inner_loop_output_range.len(),\n            inner_loop_output_range,\n            inner_loop_output_stride,\n            inner_loop_input_full_stride,\n            output_coords: zone.output_ranges.iter().map(|r| r.start).collect(),\n            done: false,\n        };\n        scan.refresh_dependent();\n        scan\n    }\n\n    #[inline]\n    pub fn valid_offsets_ker_in(&self) -> impl Iterator<Item = (usize, isize)> + '_ {\n        self.zone.values_offsets.iter().map(move |pair| (pair.0, pair.1 + self.input_center_offset))\n    }\n\n    pub unsafe fn next_non_inner_axis(&mut self) {\n        unsafe {\n            let rank = self.patch.rank();\n            let inner_loop_axis = self.inner_loop_axis;\n            for axis in (0..rank).rev() {\n                if axis == inner_loop_axis {\n                    continue;\n                }\n                *self.output_coords.get_unchecked_mut(axis) += 1;\n                if *self.output_coords.get_unchecked_mut(axis)\n                    < self.zone.output_ranges.get_unchecked(axis).end\n                {\n                    self.refresh_dependent();\n                    return;\n                }\n                *self.output_coords.get_unchecked_mut(axis) =\n                    self.zone.output_ranges.get_unchecked(axis).start;\n            }\n            self.done = true;\n        }\n    }\n\n    pub unsafe fn reset(&mut self) {\n        unsafe {\n            self.output_offset = 0;\n            self.input_center_offset = 0;\n            for ix in 0..self.output_coords.len() {\n                *self.output_coords.get_unchecked_mut(ix) =\n                    self.zone.output_ranges.get_unchecked(ix).start;\n            }\n            self.done = false;\n            self.refresh_dependent()\n        }\n    }\n\n    #[inline(never)]\n    fn refresh_dependent(&mut self) {\n        self.input_center_offset = self\n            .patch\n            .op_strides_times_input_storage_strides\n            .iter()\n            .zip(self.output_coords.iter())\n            .map(|(a, b)| *a * *b as isize)\n            .sum();\n        self.output_offset = self\n            .patch\n            .output_storage_strides\n            .iter()\n            .zip(self.output_coords.iter())\n            .map(|(a, b)| a * *b as isize)\n            .sum();\n    }\n\n    #[inline]\n    pub fn next(&mut self) {\n        let inner_loop_axis = self.inner_loop_axis;\n        unsafe {\n            *self.output_coords.get_unchecked_mut(inner_loop_axis) += 1;\n            if *self.output_coords.get_unchecked(inner_loop_axis) < self.inner_loop_output_range.end\n            {\n                self.input_center_offset += self.inner_loop_input_full_stride;\n                self.output_offset += self.inner_loop_output_stride;\n            } else {\n                *self.output_coords.get_unchecked_mut(inner_loop_axis) =\n                    self.inner_loop_output_range.start;\n                self.next_non_inner_axis();\n            }\n        }\n    }\n\n    pub fn done(&self) -> bool {\n        self.done\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Scanner<'p> {\n    pub patch: &'p Patch,\n    pub zone_id: usize,\n    pub zone_coords: TVec<usize>,\n    pub zone: &'p Zone,\n    pub output_offset: isize,\n    pub output_coords: TVec<usize>,\n    pub input_coords: TVec<usize>,\n    pub input_center_offset: isize,\n    done: bool,\n}\n\nimpl<'p> Scanner<'p> {\n    fn new(patch: &'p Patch) -> Scanner<'p> {\n        let rank = patch.rank();\n        let zone = &patch.zones[0];\n        Scanner {\n            patch,\n            zone_coords: tvec!(0; rank),\n            zone,\n            zone_id: 0,\n            output_offset: 0,\n            input_center_offset: 0,\n            input_coords: tvec!(0; rank),\n            output_coords: tvec!(0; rank),\n            done: false,\n        }\n    }\n\n    #[inline]\n    pub fn valid_count(&self) -> usize {\n        self.zone.values_offsets.len()\n    }\n\n    #[inline]\n    pub fn valid_offsets(&self) -> impl Iterator<Item = isize> + '_ {\n        self.zone.values_offsets.iter().map(move |pair| pair.1 + self.input_center_offset)\n    }\n\n    #[inline]\n    pub fn valid_offsets_ker_in(&self) -> impl Iterator<Item = (usize, isize)> + '_ {\n        self.zone.values_offsets.iter().map(move |pair| (pair.0, pair.1 + self.input_center_offset))\n    }\n\n    #[inline]\n    pub fn next(&mut self) {\n        let rank = self.patch.rank();\n        let inner_dim = rank - 1;\n        unsafe {\n            *self.output_coords.get_unchecked_mut(inner_dim) += 1;\n            *self.input_coords.get_unchecked_mut(inner_dim) +=\n                *self.patch.spec.strides.get_unchecked(inner_dim);\n            self.output_offset += self.patch.spec.output_inner_stride as isize;\n            self.input_center_offset +=\n                self.patch.op_strides_times_input_storage_strides.get_unchecked(inner_dim);\n            if *self.output_coords.get_unchecked(inner_dim)\n                < self.zone.output_ranges.get_unchecked(inner_dim).end\n            {\n                return;\n            }\n            if self.output_coords.get_unchecked(inner_dim)\n                < self.patch.output_shape.get_unchecked(inner_dim)\n            {\n                self.zone_id += 1;\n                *self.zone_coords.get_unchecked_mut(inner_dim) += 1;\n                self.zone = self.patch.zones.get_unchecked(self.zone_id);\n            } else {\n                for axis in (0..rank - 1).rev() {\n                    *self.output_coords.get_unchecked_mut(axis + 1) = 0;\n                    *self.input_coords.get_unchecked_mut(axis + 1) = 0;\n                    *self.output_coords.get_unchecked_mut(axis) += 1;\n                    *self.input_coords.get_unchecked_mut(axis) +=\n                        self.patch.spec.strides.get_unchecked(axis);\n                    *self.zone_coords.get_unchecked_mut(axis + 1) = 0;\n                    if *self.output_coords.get_unchecked(axis)\n                        == self.zone.output_ranges.get_unchecked(axis).end\n                    {\n                        *self.zone_coords.get_unchecked_mut(axis) += 1;\n                    }\n                    if *self.output_coords.get_unchecked(axis)\n                        < *self.patch.output_shape.get_unchecked(axis)\n                    {\n                        break;\n                    }\n                }\n                if self.output_coords.get_unchecked(0) == self.patch.output_shape.get_unchecked(0) {\n                    self.done = true;\n                    return;\n                }\n                self.zone_id = 0;\n                self.input_center_offset = 0;\n                for i in 0..rank {\n                    self.zone_id += *self.zone_coords.get_unchecked(i)\n                        * *self.patch.zone_strides.get_unchecked(i) as usize;\n                    self.input_center_offset += *self.input_coords.get_unchecked(i) as isize\n                        * *self.patch.input_storage_strides.get_unchecked(i);\n                }\n                self.zone = self.patch.zones.get_unchecked(self.zone_id);\n            }\n        }\n    }\n\n    pub fn done(&self) -> bool {\n        self.done\n    }\n}\n\n#[derive(Debug)]\npub enum PatchIterator<'p> {\n    Fast(FastPatchIterator<'p>),\n    Safe(SafePatchIterator<'p>),\n}\n\nimpl Iterator for PatchIterator<'_> {\n    type Item = Option<isize>;\n    #[inline(always)]\n    fn next(&mut self) -> Option<Option<isize>> {\n        match self {\n            PatchIterator::Fast(it) => it.next(),\n            PatchIterator::Safe(it) => it.next(),\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct FastPatchIterator<'p> {\n    patch: &'p Patch,\n    center: isize,\n    item: usize,\n}\n\nimpl Iterator for FastPatchIterator<'_> {\n    type Item = Option<isize>;\n    #[inline(always)]\n    fn next(&mut self) -> Option<Option<isize>> {\n        if self.item == self.patch.standard_layout_data_field.len() {\n            return None;\n        }\n        unsafe {\n            let position =\n                self.center + self.patch.standard_layout_data_field.get_unchecked(self.item);\n            self.item += 1;\n            Some(Some(position))\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct SafePatchIterator<'p> {\n    patch: &'p Patch,\n    item: usize,\n    input_patch_center: TVec<usize>,\n    center: isize,\n}\n\nimpl Iterator for SafePatchIterator<'_> {\n    type Item = Option<isize>;\n    fn next(&mut self) -> Option<Option<isize>> {\n        unsafe {\n            if self.item == self.patch.standard_layout_data_field.len() {\n                return None;\n            }\n            let input_shape = &self.patch.spec.input_shape;\n            let img_offset = self.patch.data_field.as_ptr().add(self.item * input_shape.len());\n\n            for ix in 0..input_shape.len() {\n                let pos = *self.input_patch_center.get_unchecked(ix) as isize + *img_offset.add(ix);\n                if pos < 0 || pos as usize >= *input_shape.get_unchecked(ix) {\n                    self.item += 1;\n                    return Some(None);\n                }\n            }\n            let position =\n                self.center + self.patch.standard_layout_data_field.get_unchecked(self.item);\n            self.item += 1;\n            Some(Some(position))\n        }\n    }\n}\n\n#[cfg(test)]\npub mod test {\n    use super::*;\n    use crate::ops::nn::DataFormat::*;\n    use proptest::prelude::*;\n    use proptest::*;\n\n    fn compute_output_spatial_dim(\n        input: usize,\n        dilation: usize,\n        kdim: usize,\n        pad_before: usize,\n        bad_after: usize,\n        stride: usize,\n    ) -> usize {\n        let patch = PatchSpec::for_full_shape(NCHW, &[1, 1, input])\n            .unwrap()\n            .with_dilations(tvec!(dilation))\n            .with_kernel_shape(tvec!(kdim))\n            .with_padding(PaddingSpec::ExplicitOnnxPool(tvec![pad_before], tvec![bad_after], true))\n            .with_strides(tvec![stride])\n            .into_patch();\n        patch.output_shape[0]\n    }\n\n    #[test]\n    fn basic() {\n        assert_eq!(compute_output_spatial_dim(5, 1, 3, 0, 0, 1), 3);\n    }\n\n    #[test]\n    fn strides() {\n        assert_eq!(compute_output_spatial_dim(7, 1, 3, 0, 0, 2), 3);\n    }\n\n    #[test]\n    fn padding() {\n        assert_eq!(compute_output_spatial_dim(5, 1, 3, 1, 1, 1), 5);\n    }\n\n    #[test]\n    fn strides_and_padding() {\n        assert_eq!(compute_output_spatial_dim(7, 1, 3, 1, 1, 2), 4);\n    }\n\n    fn field(kdim: &[usize], dilations: &[usize]) -> Array2<isize> {\n        let patch =\n            PatchSpec::for_data_shape(NCHW.from_n_c_hw(1, 1, tvec![10; kdim.len()]).unwrap())\n                .with_dilations(dilations.into())\n                .with_kernel_shape(kdim.into())\n                .with_strides(tvec![1; kdim.len()])\n                .into_patch();\n        patch.data_field\n    }\n\n    #[test]\n    fn test_field() {\n        assert_eq!(field(&[3], &[1]), arr2(&[[0], [1], [2]]));\n        assert_eq!(field(&[3], &[2]), arr2(&[[0], [2], [4]]));\n        assert_eq!(field(&[2, 2], &[1, 1]), arr2(&[[0, 0], [0, 1], [1, 0], [1, 1]]));\n        assert_eq!(field(&[2, 2], &[2, 1]), arr2(&[[0, 0], [0, 1], [2, 0], [2, 1]]));\n    }\n\n    pub fn tensor(shape: &[usize]) -> BoxedStrategy<Tensor> {\n        let len = shape.iter().product::<usize>();\n        let shape = shape.to_vec();\n        proptest::collection::vec(any::<i8>().prop_map(|i| i as f32), len..=len)\n            .prop_map(move |vec| ArrayD::from_shape_vec(shape.clone(), vec).unwrap().into_tensor())\n            .boxed()\n    }\n\n    #[derive(Debug)]\n    struct Problem {\n        patch: Patch,\n        input: Tensor,\n        data_format: DataFormat,\n    }\n\n    impl Arbitrary for Problem {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Problem>;\n        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {\n            (\n                prop_oneof!(Just(NCHW), Just(NHWC)),\n                (1usize..3, 1usize..3),\n                1usize..3,\n                (1usize..3, 1usize..3),\n                prop_oneof![\n                    Just(PaddingSpec::Valid),\n                    Just(PaddingSpec::SameLower),\n                    Just(PaddingSpec::SameUpper)\n                ],\n                (1usize..4, 1usize..4),\n            )\n                .prop_flat_map(|p| {\n                    let dil = p.1;\n                    let ks = p.3;\n                    let strides = p.5;\n                    let min_size: (usize, usize) = (1 + (ks.0 - 1) * dil.0, 1 + (ks.1 - 1) * dil.1);\n                    (\n                        Just(p),\n                        (min_size.0..min_size.0 + strides.0 * 3),\n                        (min_size.1..min_size.1 + strides.1 * 3),\n                    )\n                })\n                .prop_flat_map(|(p, h, w)| {\n                    let input_shape = p.0.from_n_c_hw(1, p.2, [h, w]).unwrap();\n                    let input = tensor(&input_shape.shape);\n                    (Just(p), input)\n                })\n                .prop_map(|((fmt, dil, c, ks, pad, strides), input)| {\n                    let output_inner_stride = if fmt.c_is_last() { c } else { 1 };\n                    Problem {\n                        patch: PatchSpec::for_full_shape(fmt, input.shape())\n                            .unwrap()\n                            .with_dilations(tvec!(dil.0, dil.1))\n                            .with_kernel_shape(tvec!(ks.0, ks.1))\n                            .with_padding(pad)\n                            .with_strides(tvec![strides.0, strides.1])\n                            .with_output_inner_stride(output_inner_stride)\n                            .into_patch(),\n                        input,\n                        data_format: fmt,\n                    }\n                })\n                .boxed()\n        }\n    }\n\n    impl Problem {\n        fn input_shape(&self) -> DataShape {\n            self.data_format.shape(self.input.shape().into()).unwrap()\n        }\n\n        fn output_shape(&self) -> DataShape {\n            self.data_format\n                .from_n_c_hw(\n                    self.input_shape().n().cloned().unwrap_or(1),\n                    *self.input_shape().c(),\n                    &*self.patch.output_shape,\n                )\n                .unwrap()\n        }\n\n        fn reference_sumpool(&self) -> Tensor {\n            let input_shape = self.input_shape();\n            let output_shape = self.output_shape();\n            let mut output = Tensor::zero::<f32>(&output_shape.shape).unwrap();\n            for geo_out in tract_ndarray::indices(output_shape.hw_dims()) {\n                for geo_ker in tract_ndarray::indices(&*self.patch.spec.kernel_shape) {\n                    let geo_in: TVec<isize> = izip!(\n                        geo_out.slice(),\n                        geo_ker.slice(),\n                        &self.patch.spec.strides,\n                        &self.patch.spec.dilations,\n                        &self.patch.pad_before\n                    )\n                    .map(|(o, k, s, d, p)| (o * s + k * d) as isize - *p as isize)\n                    .collect();\n                    if izip!(&geo_in, input_shape.hw_dims())\n                        .any(|(g, i)| *g >= *i as isize || *g < 0)\n                    {\n                        continue;\n                    }\n                    let geo_in: TVec<usize> = geo_in.into_iter().map(|x| x as usize).collect();\n                    for c in 0..*output_shape.c() {\n                        let ocoords = self.data_format.from_n_c_hw(0, c, geo_out.slice()).unwrap();\n                        let icoords = self.data_format.from_n_c_hw(0, c, &geo_in).unwrap();\n                        output.to_plain_array_view_mut::<f32>().unwrap()[&*ocoords.shape] +=\n                            self.input.to_plain_array_view::<f32>().unwrap()[&*icoords.shape];\n                    }\n                }\n            }\n            output\n        }\n\n        fn check_visitor(&self) {\n            let input_shape = self.input_shape();\n            let output_shape = self.output_shape();\n            let mut output = Tensor::zero::<f32>(&output_shape.shape).unwrap();\n            self.patch.visit_output(|visitor| {\n                for (_k, offset_in) in visitor.valid_offsets_ker_in() {\n                    for c in 0..*output_shape.c() {\n                        output.try_as_plain_mut().unwrap().as_slice_mut::<f32>().unwrap()\n                            [visitor.output_offset as usize + c * output_shape.c_stride()] +=\n                            self.input.try_as_plain().unwrap().as_slice::<f32>().unwrap()\n                                [offset_in as usize + c * input_shape.c_stride()];\n                    }\n                }\n            });\n            assert_eq!(output, self.reference_sumpool());\n        }\n\n        fn check_zone_visitor(&self) {\n            let input_shape = self.input_shape();\n            let output_shape = self.output_shape();\n            let mut output = Tensor::zero::<f32>(&output_shape.shape).unwrap();\n            for zone in &self.patch.zones {\n                zone.visit_output(&self.patch, |visitor| {\n                    for (_k, offset_in) in visitor.valid_offsets_ker_in() {\n                        for c in 0..*output_shape.c() {\n                            output.try_as_plain_mut().unwrap().as_slice_mut::<f32>().unwrap()\n                                [visitor.output_offset as usize + c * output_shape.c_stride()] +=\n                                self.input.try_as_plain().unwrap().as_slice::<f32>().unwrap()\n                                    [offset_in as usize + c * input_shape.c_stride()];\n                        }\n                    }\n                });\n            }\n            assert_eq!(output, self.reference_sumpool());\n        }\n\n        fn check_zoning(&self) {\n            fn in_zone(full_coords: &[usize], h_axis: usize, zone: &[Range<usize>]) -> bool {\n                for a in 0..zone.len() {\n                    if full_coords[h_axis + a] < zone[a].start\n                        || full_coords[h_axis + a] >= zone[a].end\n                    {\n                        return false;\n                    }\n                }\n                true\n            }\n\n            let valid_zone = &self.patch.valid_output_zone;\n            let invalid_zones = &self.patch.invalid_output_zones;\n            let output_full_shape = self.output_shape();\n            let h_axis = self.input_shape().h_axis();\n            for coords in ndarray::indices(&*output_full_shape.shape) {\n                let inside_valid = in_zone(coords.slice(), h_axis, valid_zone);\n                let invalid_count =\n                    invalid_zones.iter().filter(|z| in_zone(coords.slice(), h_axis, z)).count();\n                unsafe {\n                    assert_eq!(\n                        inside_valid,\n                        self.patch.is_valid(&coords.slice()[self.input_shape().hw_axes()]),\n                        \"coords {:?}, valid_zone: {:?} inside_valid: {:?}\",\n                        coords.slice(),\n                        valid_zone,\n                        inside_valid\n                    );\n                }\n                if inside_valid {\n                    assert_eq!(invalid_count, 0);\n                } else {\n                    assert_eq!(\n                        invalid_count,\n                        1,\n                        \"coords {:?}, valid_zone: {:?} inside_valid: {:?} invalid_zones: {:?}\",\n                        coords.slice(),\n                        valid_zone,\n                        inside_valid,\n                        invalid_zones\n                    );\n                }\n            }\n        }\n    }\n\n    proptest! {\n        #[test]\n        fn test_visitor(pb in any::<Problem>()) {\n            pb.check_visitor();\n        }\n\n        #[test]\n        fn test_zone_visitor(pb in any::<Problem>()) {\n            pb.check_zone_visitor();\n        }\n\n        #[test]\n        fn test_zoning(pb in any::<Problem>()) {\n            pb.check_zoning();\n        }\n    }\n\n    #[test]\n    fn test_visitor_1() {\n        let input_shape = NCHW.from_n_c_hw(1, 1, [2, 2]).unwrap();\n        let input = Tensor::zero::<f32>(&input_shape.shape).unwrap();\n        let patch = PatchSpec::for_data_shape(input_shape.clone())\n            .with_kernel_shape(tvec![2, 1])\n            .with_padding(PaddingSpec::SameLower)\n            .with_strides(tvec![1, 2])\n            .into_patch();\n        Problem { patch, input, data_format: input_shape.fmt }.check_visitor();\n    }\n\n    #[test]\n    fn test_visitor_2() {\n        let input_shape = NCHW.from_n_c_hw(1, 2, [1, 1]).unwrap();\n        let input = tensor4(&[[[[0.]], [[1f32]]]]);\n        assert_eq!(input.shape(), &*input_shape.shape);\n        let patch =\n            PatchSpec::for_data_shape(input_shape.clone()).with_output_inner_stride(2).into_patch();\n        Problem { patch, input, data_format: input_shape.fmt }.check_visitor();\n    }\n\n    #[test]\n    fn test_visitor_3() {\n        let input_shape = NHWC.from_n_c_hw(1, 2, [2, 1]).unwrap();\n        let input = tensor4(&[[[[0., 0.]], [[1., 0f32]]]]);\n        assert_eq!(input.shape(), &*input_shape.shape);\n        let patch =\n            PatchSpec::for_data_shape(input_shape.clone()).with_output_inner_stride(2).into_patch();\n        Problem { patch, input, data_format: input_shape.fmt }.check_visitor();\n    }\n\n    #[test]\n    fn test_visitor_4() {\n        let input_shape = NCHW.from_n_c_hw(1, 1, [1, 2]).unwrap();\n        let input = tensor4(&[[[[0., 1f32]]]]);\n        assert_eq!(input.shape(), &*input_shape.shape);\n        let patch = PatchSpec::for_data_shape(input_shape.clone())\n            .with_kernel_shape(tvec!(1, 2))\n            .with_output_inner_stride(1)\n            .with_padding(PaddingSpec::SameLower)\n            .into_patch();\n        Problem { patch, input, data_format: input_shape.fmt }.check_visitor();\n    }\n\n    #[test]\n    fn test_zone_visitor_1() {\n        let input_shape = NCHW.from_n_c_hw(1, 1, [2, 1]).unwrap();\n        let input = tensor4(&[[[[0.], [1f32]]]]);\n        assert_eq!(input.shape(), &*input_shape.shape);\n        let patch = PatchSpec::for_data_shape(input_shape.clone()).into_patch();\n        Problem { patch, input, data_format: input_shape.fmt }.check_zone_visitor();\n    }\n\n    #[test]\n    fn test_zone_visitor_2() {\n        let input_shape = NCHW.from_n_c_hw(1, 1, [1, 2]).unwrap();\n        let input = tensor4(&[[[[0., 1f32]]]]);\n        assert_eq!(input.shape(), &*input_shape.shape);\n        let patch = PatchSpec::for_data_shape(input_shape.clone()).into_patch();\n        Problem { patch, input, data_format: input_shape.fmt }.check_zone_visitor();\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/pools.rs",
    "content": "use crate::internal::*;\n\nuse crate::ops::cnn::{PaddingSpec, Patch, PatchSpec};\nuse crate::ops::nn::{BaseDataShape, DataFormat, DataShape, SymDataShape};\n\nuse super::padding::ComputedPaddedDim;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct PoolSpec {\n    pub data_format: DataFormat,\n    pub kernel_shape: TVec<usize>,\n    pub padding: PaddingSpec,\n    pub dilations: Option<TVec<usize>>,\n    pub strides: Option<TVec<usize>>,\n    pub input_channels: usize,\n    pub output_channels: usize,\n}\n\nimpl PoolSpec {\n    pub fn info(&self) -> Vec<String> {\n        vec![\n            format!(\"Data format: {:?}\", self.data_format),\n            format!(\n                \"Kernel shape:{:?} (strides:{:?}, padding:{:?}, dilations:{:?})\",\n                self.kernel_shape, self.strides, self.padding, self.dilations,\n            ),\n        ]\n    }\n\n    pub fn rank(&self) -> usize {\n        self.kernel_shape.len()\n    }\n\n    pub fn dilation(&self, geo_axis: usize) -> usize {\n        self.dilations.as_ref().map(|d| d[geo_axis]).unwrap_or(1)\n    }\n\n    pub fn dilations(&self) -> Cow<'_, [usize]> {\n        self.dilations\n            .as_deref()\n            .map_or_else(|| vec![1; self.kernel_shape.len()].into(), |d| d.into())\n    }\n\n    pub fn stride(&self, geo_axis: usize) -> usize {\n        self.strides.as_ref().map(|s| s[geo_axis]).unwrap_or(1)\n    }\n\n    pub fn strides(&self) -> Cow<'_, [usize]> {\n        self.strides\n            .as_deref()\n            .map_or_else(|| vec![1; self.kernel_shape.len()].into(), |d| d.into())\n    }\n\n    pub fn computed_padding<D: DimLike>(&self, input_hw: &[D]) -> TVec<ComputedPaddedDim<D>> {\n        self.padding.compute(input_hw, &self.kernel_shape, &self.dilations(), &self.strides())\n    }\n\n    pub fn output_shape<D: DimLike>(&self, input: &[D]) -> TractResult<BaseDataShape<D, TVec<D>>> {\n        let ishape: BaseDataShape<D, TVec<D>> = self.data_format.shape(input.into())?;\n        ensure!(ishape.c().to_dim() == self.input_channels.to_dim());\n        let computed = self.computed_padding(ishape.hw_dims());\n        let spatial_dims = computed.into_iter().map(|d| d.convoluted).collect::<TVec<D>>();\n        let oshape = self.data_format.from_n_c_hw(\n            ishape.n().cloned().unwrap_or_else(|| 1.into()),\n            self.output_channels.into(),\n            spatial_dims,\n        )?;\n        Ok(oshape)\n    }\n\n    pub fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let oshape = self.output_shape(&inputs[0].shape)?;\n        Ok(tvec!(inputs[0].datum_type.fact(oshape.shape)))\n    }\n\n    pub fn dispose_n_axis(&self) -> PoolSpec {\n        PoolSpec { data_format: self.data_format.dispose_n_axis(), ..self.clone() }\n    }\n\n    pub fn compute_geo(&self, input_full_shape: &[TDim]) -> TractResult<PoolGeometry> {\n        let output_shape = self.output_shape(input_full_shape)?;\n        let input_shape: SymDataShape = self.data_format.shape(input_full_shape.into())?;\n        Ok(PoolGeometry::Symbolic(SymbolicPoolGeometry {\n            pool_spec: self.clone(),\n            input_shape,\n            output_shape,\n        }))\n    }\n\n    pub fn change_geo_axes(&self, op: &AxisOp) -> TractResult<PoolSpec> {\n        let mut dilations = self.dilations().into_owned().into();\n        op.change_shape_array(&mut dilations, false)?;\n        let mut kernel_shape = self.kernel_shape.clone();\n        op.change_shape_array(&mut kernel_shape, false)?;\n        let mut strides = self.strides().into_owned().into();\n        op.change_shape_array(&mut strides, false)?;\n        let padding = self.padding.change_geo_axes(op)?;\n        Ok(PoolSpec {\n            kernel_shape,\n            padding,\n            dilations: Some(dilations),\n            strides: Some(strides),\n            ..self.clone()\n        })\n    }\n\n    pub fn declutter(&self, input: &[TDim]) -> TractResult<Option<PoolSpec>> {\n        if let PaddingSpec::ExplicitOnnxPool(before, after, _) = &self.padding {\n            let input = self.data_format.shape(input)?;\n            let input_hw = input.hw_dims();\n            let reference = self.computed_padding(input_hw);\n            for replacement in [\n                PaddingSpec::Valid,\n                PaddingSpec::SameUpper,\n                PaddingSpec::SameLower,\n                PaddingSpec::Explicit(before.clone(), after.clone()),\n            ] {\n                let new_pool_spec = PoolSpec { padding: replacement, ..self.clone() };\n                if new_pool_spec.computed_padding(input_hw) == reference {\n                    return Ok(Some(new_pool_spec));\n                }\n            }\n        }\n        Ok(None)\n    }\n}\n\npub type PoolGeometry = super::GeometryBound<SymbolicPoolGeometry, ConcretePoolGeometry>;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct SymbolicPoolGeometry {\n    pub pool_spec: PoolSpec,\n    pub input_shape: SymDataShape,\n    pub output_shape: SymDataShape,\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct ConcretePoolGeometry {\n    pub input_shape: DataShape,\n    pub patch: Patch,\n    pub output_shape: DataShape,\n}\n\nimpl super::ResolveTo<ConcretePoolGeometry> for SymbolicPoolGeometry {\n    type Param = [usize];\n    fn resolve(&self, input_full_shape: &[usize]) -> TractResult<ConcretePoolGeometry> {\n        let input_shape = self.pool_spec.data_format.shape(input_full_shape.into())?;\n        let output_inner_stride = match self.pool_spec.data_format {\n            DataFormat::NCHW | DataFormat::CHW => 1,\n            DataFormat::NHWC | DataFormat::HWC => self.pool_spec.output_channels,\n        };\n        let mut spec = PatchSpec::for_full_shape(self.pool_spec.data_format, input_full_shape)?\n            .with_output_inner_stride(output_inner_stride)\n            .with_kernel_shape(self.pool_spec.kernel_shape.clone())\n            .with_padding(self.pool_spec.padding.clone());\n        if let Some(strides) = self.pool_spec.strides.clone() {\n            spec = spec.with_strides(strides);\n        }\n        if let Some(dilations) = self.pool_spec.dilations.clone() {\n            spec = spec.with_dilations(dilations);\n        }\n        let patch = spec.into_patch();\n        let output_shape = input_shape.fmt.from_n_c_hw(\n            *input_shape.n().unwrap_or(&1),\n            self.pool_spec.output_channels,\n            &*patch.output_shape,\n        )?;\n        Ok(ConcretePoolGeometry { input_shape, patch, output_shape })\n    }\n}\n"
  },
  {
    "path": "core/src/ops/cnn/sumpool.rs",
    "content": "use crate::internal::*;\nuse num_traits::AsPrimitive;\nuse std::iter::Sum;\n\nuse crate::ops::cnn::pools::{ConcretePoolGeometry, PoolGeometry, PoolSpec};\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct SumPool {\n    pub pool_spec: PoolSpec,\n    pub count_include_pad: bool,\n    pub normalize: bool,\n}\n\nimpl Op for SumPool {\n    fn name(&self) -> StaticName {\n        \"SumPool\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(self.pool_spec.info())\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Rounding\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for SumPool {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let shape: TVec<TDim> = inputs[0].shape().iter().map(|d| d.to_dim()).collect();\n        self.to_optimized(&shape)?.eval(inputs)\n    }\n}\n\nimpl TypedOp for SumPool {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        self.pool_spec.output_facts(inputs)\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let fact = model.outlet_fact(node.inputs[0])?;\n        if let Some(pool_spec) = self.pool_spec.declutter(&fact.shape)? {\n            return Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &node.inputs,\n                Self { pool_spec, ..self.clone() },\n            )?));\n        }\n        Ok(None)\n    }\n\n    as_op!();\n}\n\nimpl SumPool {\n    fn to_optimized(&self, input_shape: &[TDim]) -> TractResult<OptSumPool> {\n        Ok(OptSumPool {\n            pool_spec: self.pool_spec.clone(),\n            count_include_pad: self.count_include_pad,\n            normalize: self.normalize,\n            geometry: self.pool_spec.compute_geo(input_shape)?,\n        })\n    }\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct OptSumPool {\n    pub pool_spec: PoolSpec,\n    pub count_include_pad: bool,\n    pub normalize: bool,\n    pub geometry: PoolGeometry,\n}\n\nimpl Op for OptSumPool {\n    fn name(&self) -> StaticName {\n        \"OptSumPool\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(self.pool_spec.info())\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Rounding\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for OptSumPool {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let geo = self.geometry.to_concrete(input.shape())?;\n        let values = if input.datum_type().is_float() {\n            let mut values =\n                unsafe { Tensor::uninitialized_dt(input.datum_type(), &geo.output_shape.shape)? };\n            dispatch_floatlike!(Self::eval_t(input.datum_type())(\n                self,\n                &*input,\n                values.as_ptr_mut()?,\n                geo.as_ref()\n            ))?;\n            values\n        } else {\n            let mut values =\n                unsafe { Tensor::uninitialized_dt(DatumType::F32, &geo.output_shape.shape)? };\n            let input_f32 = input.cast_to_dt(DatumType::F32)?;\n            self.eval_t::<f32>(input_f32.as_ref(), values.as_ptr_mut()?, geo.as_ref())?;\n            values.cast_to_dt(input.datum_type())?.into_owned()\n        };\n\n        Ok(tvec!(values.into_tvalue()))\n    }\n}\n\nimpl TypedOp for OptSumPool {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        self.pool_spec.output_facts(inputs)\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let fact = model.outlet_fact(node.inputs[0])?;\n        if let Some(pool_spec) = self.pool_spec.declutter(&fact.shape)? {\n            return Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &node.inputs,\n                Self { pool_spec, ..self.clone() },\n            )?));\n        }\n        Ok(None)\n    }\n\n    as_op!();\n}\n\nimpl OptSumPool {\n    fn eval_t<T: Copy + Datum + Sum + num_traits::Float>(\n        &self,\n        input: &Tensor,\n        values_ptr: *mut T,\n        geo: &ConcretePoolGeometry,\n    ) -> TractResult<()>\n    where\n        usize: AsPrimitive<T>,\n    {\n        let input_ptr = input.as_ptr::<T>()?;\n\n        let n = *geo.input_shape.n().unwrap_or(&1);\n        let n_stride_i = geo.input_shape.n_stride().unwrap_or(&0);\n        let n_stride_o = geo.output_shape.n_stride().unwrap_or(&0);\n        unsafe {\n            geo.patch.visit_output(|visitor| {\n                let div: Option<T> = if self.normalize {\n                    Some(\n                        if self.count_include_pad {\n                            geo.patch.standard_layout_data_field.len().as_()\n                        } else {\n                            visitor.valid_count().as_()\n                        }\n                        .recip(),\n                    )\n                } else {\n                    None\n                };\n                for n in 0..n {\n                    let input_offset = n * n_stride_i;\n                    let output_offset = n * n_stride_o;\n                    for c in 0..*geo.input_shape.c() {\n                        let input_offset = input_offset + geo.input_shape.c_stride() * c;\n                        let output_offset = output_offset + geo.output_shape.c_stride() * c;\n                        let sum = visitor\n                            .valid_offsets()\n                            .map(|v| *input_ptr.offset(v + input_offset as isize))\n                            .sum::<T>();\n\n                        if let Some(div) = div {\n                            *values_ptr.offset(output_offset as isize + visitor.output_offset) =\n                                sum * div;\n                        }\n                    }\n                }\n            });\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/downsample/array.rs",
    "content": "use super::Downsample;\nuse crate::internal::*;\nuse crate::ops;\n\npub fn pull_downsample_over_slice(\n    model: &TypedModel,\n    slice_node: &TypedNode,\n    slice_op: &ops::array::Slice,\n    down_node: &TypedNode,\n    down_op: &Downsample,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(down_op.axis == slice_op.axis);\n    rule_if!(down_op.stride >= 0);\n    let modulo = (down_op.modulo + slice_op.start.to_usize()?) % down_op.stride as usize;\n    let left = (down_op.modulo + slice_op.start.to_usize()?) / down_op.stride as usize;\n    let mut patch = TypedModelPatch::default();\n    let tap = patch.tap_model(model, slice_node.inputs[0])?;\n    let final_len = down_node.outputs[0].fact.shape[down_op.axis].clone();\n    let new_down = Downsample::new(down_op.axis, down_op.stride, modulo);\n    let ds = patch.wire_node(&*down_node.name, new_down, [tap].as_ref())?;\n    let new_start = left;\n\n    let new_end = final_len + left;\n    let op = ops::array::Slice::new(slice_op.axis, new_start.to_dim(), new_end);\n    let new_slice = patch.wire_node(&*slice_node.name, op, &ds)?[0];\n\n    patch.shunt_outside(model, OutletId::new(down_node.id, 0), new_slice)?;\n    Ok(Some(patch))\n}\n\npub fn pull_downsample_over_axis_op(\n    model: &TypedModel,\n    axis_node: &TypedNode,\n    axis_op: &AxisOp,\n    down_node: &TypedNode,\n    down_op: &Downsample,\n) -> TractResult<Option<TypedModelPatch>> {\n    let mut patch = TypedModelPatch::default();\n    let tap = patch.tap_model(model, axis_node.inputs[0])?;\n    let mut new_down = down_op.clone();\n    new_down.axis =\n        axis_op.recip().transform_axis(down_op.axis).ok_or_else(|| format_err!(\"Invalid axis\"))?;\n    let wire = patch.wire_node(&*down_node.name, new_down, [tap].as_ref())?;\n    let wire = patch.wire_node(&*axis_node.name, axis_op.clone(), &wire)?[0];\n    patch.shunt_outside(model, OutletId::new(down_node.id, 0), wire)?;\n    Ok(Some(patch))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::ops;\n    use proptest::prelude::*;\n    use proptest::test_runner::TestCaseResult;\n\n    fn crop_then_down_strat() -> BoxedStrategy<(usize, usize, usize, usize, usize)> {\n        (1usize..5, 1usize..5)\n            .prop_flat_map(|(cropped, stride)| {\n                (Just(cropped), 0..=cropped, Just(stride), (cropped + 15)..=(cropped + 15))\n            })\n            .prop_flat_map(|(cropped, left, stride, len)| {\n                (Just(len), Just(left), Just(cropped - left), Just(stride), 0..stride)\n            })\n            .boxed()\n    }\n\n    fn crop_then_down(\n        len: usize,\n        left: usize,\n        right: usize,\n        stride: usize,\n        modulo: usize,\n    ) -> TestCaseResult {\n        let _ = env_logger::Builder::from_env(\"TRACT_LOG\").try_init();\n        let mut model = {\n            let mut model = TypedModel::default();\n            let input = model.add_source(\"input\", i32::fact([len])).unwrap();\n            let crop = model\n                .wire_node(\n                    \"crop\",\n                    ops::array::Slice::new(0, left.to_dim(), (len - right).to_dim()),\n                    &[input],\n                )\n                .unwrap();\n            let down = model\n                .wire_node(\"down\", Downsample::new(0, stride as isize, modulo), &crop)\n                .unwrap();\n            model.select_output_outlets(&down).unwrap();\n            model\n        };\n        trace!(\"{model:#?}\");\n        prop_assert!(model.node(model.output_outlets().unwrap()[0].node).op_is::<Downsample>());\n        let input = tensor1(&(0i32..len as _).collect::<Vec<_>>());\n        let expected =\n            SimplePlan::new(model.clone()).unwrap().run(tvec!(input.clone().into())).unwrap();\n\n        info!(\"Decluttering\");\n        model.declutter().unwrap();\n        trace!(\"{model:#?}\");\n        let order = model.eval_order().unwrap();\n        prop_assert!(\n            model.node(order[1]).op_is::<Downsample>()\n                || !model.nodes().iter().any(|n| n.op_is::<Downsample>())\n        );\n        let found = SimplePlan::new(model).unwrap().run(tvec!(input.into())).unwrap();\n        prop_assert_eq!(found, expected);\n        Ok(())\n    }\n\n    proptest! {\n        #[test]\n        fn crop_then_down_prop((len, left, right, stride, modulo) in crop_then_down_strat()) {\n            crop_then_down(len, left, right, stride, modulo).unwrap()\n        }\n    }\n\n    #[test]\n    fn crop_then_down_1() {\n        crop_then_down(1, 0, 0, 2, 0).unwrap()\n    }\n\n    #[test]\n    fn crop_then_down_2() {\n        crop_then_down(2, 0, 1, 2, 0).unwrap()\n    }\n\n    #[test]\n    fn crop_then_down_5() {\n        crop_then_down(16, 0, 1, 2, 1).unwrap()\n    }\n}\n"
  },
  {
    "path": "core/src/ops/downsample/conv.rs",
    "content": "use super::Downsample;\nuse crate::internal::*;\nuse crate::ops;\n\n// trivial cases (sampling on N, mat-mul-as-conv) is handled by invariants\npub fn fuse_downsample_into_conv(\n    model: &TypedModel,\n    conv_node: &TypedNode,\n    conv_op: &ops::cnn::conv::Conv,\n    down_node: &TypedNode,\n    down_op: &Downsample,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(down_op.stride >= 0);\n    let input_fact = model.outlet_fact(conv_node.inputs[0])?;\n    let input_shape = conv_op.pool_spec.data_format.shape(input_fact.shape.to_tvec())?;\n    rule_if!(down_op.axis >= input_shape.h_axis());\n    let geo_axis = down_op.axis - input_shape.h_axis();\n    rule_if!(geo_axis < input_shape.rank());\n    let mut new_conv = conv_op.clone();\n    if new_conv.pool_spec.strides.is_none() {\n        new_conv.pool_spec.strides = Some(tvec!(1; input_shape.hw_rank()));\n    }\n    new_conv.pool_spec.strides.as_mut().unwrap()[geo_axis] *= down_op.stride as usize;\n\n    let mut patch = TypedModelPatch::default();\n    let taps = patch.taps(model, &conv_node.inputs)?;\n    let new_output = patch.wire_node(&*conv_node.name, new_conv, &taps)?[0];\n    patch.shunt_outside(model, OutletId::new(down_node.id, 0), new_output)?;\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "core/src/ops/downsample/mod.rs",
    "content": "use crate::internal::*;\nuse crate::ops;\nuse ndarray::prelude::*;\n\nuse super::identity::Identity;\n\nmod array;\nmod conv;\nmod scan;\n\n#[derive(Debug, Clone, new, Default, PartialEq, Eq, Hash)]\npub struct Downsample {\n    pub axis: usize,\n    pub stride: isize,\n    pub modulo: usize,\n}\n\nimpl Downsample {\n    pub(crate) fn transform_dim(&self, input_dim: &TDim) -> TDim {\n        (input_dim.clone() - self.modulo).div_ceil(self.stride.unsigned_abs() as u64)\n    }\n\n    pub(crate) fn transform_fact(&self, input_fact: &TypedFact) -> TractResult<TypedFact> {\n        let mut downed = input_fact.clone();\n        let down_len = self.transform_dim(&input_fact.shape[self.axis]);\n        downed.shape.set(self.axis, down_len);\n        if let Some(k) = downed.konst {\n            let mut outputs = self.eval(tvec!(k.into_tvalue()))?;\n            downed.konst = Some(outputs.remove(0).into_arc_tensor())\n        }\n        if cfg!(debug_assertions) {\n            downed.consistent()?;\n        }\n        Ok(downed)\n    }\n}\n\nimpl Op for Downsample {\n    fn name(&self) -> StaticName {\n        \"Downsample\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis:{} stride:{} modulo:{}\", self.axis, self.stride, self.modulo)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Downsample {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        unsafe {\n            let t = if self.modulo > input.shape()[self.axis] {\n                let mut shape: TVec<usize> = input.shape().into();\n                shape[self.axis] = 0;\n                Tensor::uninitialized_dt(input.datum_type(), &shape)?\n            } else {\n                let slice = ndarray::Slice::new(self.modulo as isize, None, self.stride);\n                unsafe fn do_slice<T: Datum>(\n                    t: &Tensor,\n                    axis: usize,\n                    slice: ndarray::Slice,\n                ) -> Tensor {\n                    unsafe {\n                        let dt = t.datum_type();\n                        let mut t2 = t\n                            .to_array_view_unchecked::<T>()\n                            .slice_axis(Axis(axis), slice)\n                            .into_owned()\n                            .into_tensor();\n                        t2.set_datum_type(dt);\n                        t2\n                    }\n                }\n                dispatch_datum_by_size!(do_slice(input.datum_type())(&*input, self.axis, slice))\n            };\n            Ok(tvec!(t.into_tvalue()))\n        }\n    }\n}\n\nimpl TypedOp for Downsample {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(self.axis < inputs[0].rank());\n        ensure!(\n            self.modulo == 0 || self.stride > 0,\n            \"non-zero modulo is only defined with forward strides\"\n        );\n        let mut downed = inputs[0].without_value();\n        let down_len = self.transform_dim(&downed.shape[self.axis]);\n        downed.shape.set(self.axis, down_len);\n        Ok(tvec!(downed))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if self.stride == 1 {\n            return Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &node.inputs,\n                Identity,\n            )?));\n        }\n        pull_downsample_up(model, node)\n            .with_context(|| format!(\"Pulling {} over {}\", node, model.node(node.inputs[0].node)))\n    }\n\n    as_op!();\n}\n\nfn pull_downsample_up(\n    model: &TypedModel,\n    down_node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    model.check_consistency()?;\n    let down_op = down_node.op_as::<Downsample>().unwrap();\n    if let Some(prec) = model.linear_prec(down_node.id)? {\n        let (input_facts, output_facts) = model.node_facts(prec.id)?;\n        let axes_mapping = prec.op.axes_mapping(&input_facts, &output_facts)?;\n        debug!(\"Consider pull {down_op:?} over {prec:?} (invariants: {axes_mapping:?})\");\n        if let Some(slice_op) = prec.op_as::<ops::array::Slice>() {\n            if let Some(p) =\n                array::pull_downsample_over_slice(model, prec, slice_op, down_node, down_op)?\n            {\n                return Ok(Some(p));\n            }\n        } else if let Some(other_op) = prec.op_as::<AxisOp>() {\n            return array::pull_downsample_over_axis_op(model, prec, other_op, down_node, down_op);\n        } else if let Some(conv_op) = prec.op_as::<ops::cnn::conv::Conv>() {\n            return conv::fuse_downsample_into_conv(model, prec, conv_op, down_node, down_op);\n        } else if let Some(other_op) = prec.op_as::<ops::scan::Scan>() {\n            return scan::pull_downsample_over_scan(model, prec, other_op, down_node, down_op);\n        }\n        rule_if!(prec.outputs.len() <= 1 && prec.inputs.len() > 0);\n        let axis_info = axes_mapping.axis((InOut::Out(0), down_op.axis))?;\n        let mut patch = TypedModelPatch::default();\n        let mut inputs = vec![];\n        for (ix, (outlet, axis_info)) in prec.inputs.iter().zip(&axis_info.inputs).enumerate() {\n            let mut wire = patch.tap_model(model, *outlet)?;\n            if let &[axis] = &**axis_info {\n                if !patch.outlet_fact(wire)?.shape[axis].is_one() {\n                    let mut op = down_op.clone();\n                    op.axis = axis;\n                    wire = patch.wire_node(\n                        format!(\"{}.{}-{}\", down_node.name, prec.name, ix),\n                        op,\n                        &[wire],\n                    )?[0];\n                }\n            } else {\n                return Ok(None);\n            }\n            inputs.push(wire);\n        }\n        let other = patch.wire_node(&prec.name, prec.op.clone(), &inputs)?;\n        patch.shunt_outside(model, OutletId::new(down_node.id, 0), other[0])?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "core/src/ops/downsample/scan.rs",
    "content": "use super::Downsample;\nuse crate::internal::*;\nuse crate::ops;\nuse crate::ops::identity::Identity;\nuse crate::ops::scan::*;\n\npub fn pull_downsample_over_scan(\n    model: &TypedModel,\n    scan_node: &TypedNode,\n    scan_op: &ops::scan::Scan,\n    down_node: &TypedNode,\n    down_op: &Downsample,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(down_op.stride >= 0);\n\n    // introduce downsample at end of body\n    let mut downsampled_body = scan_op.body.clone();\n    downsampled_body.check_consistency()?;\n    let outputs = downsampled_body.output_outlets()?.to_owned();\n    let downsample_outputs = outputs\n        .into_iter()\n        .enumerate()\n        .map(|(ix, oo)| {\n            Ok(downsampled_body.wire_node(\n                format!(\"{}-{}\", &down_node.name, ix),\n                down_op.clone(),\n                &[oo],\n            )?[0])\n        })\n        .collect::<TractResult<Vec<_>>>()?;\n    downsampled_body.select_output_outlets(&downsample_outputs)?;\n    downsampled_body.declutter()?;\n    downsampled_body.check_consistency()?;\n\n    // check if downsample ops introduced at end have swimmed up to scan inputs during declutter\n    for input in downsampled_body.input_outlets()? {\n        let input = downsampled_body.node(input.node);\n        rule_if!(\n            input.outputs[0]\n                .successors\n                .iter()\n                .all(|succ| downsampled_body.node(succ.node).op().dyn_eq(down_op))\n        )\n    }\n\n    let inputs = downsampled_body.input_outlets()?.to_vec();\n    for input in inputs {\n        let node = &mut downsampled_body.node_mut(input.node);\n        let fact = &mut node.outputs[0].fact;\n        *fact = down_op.transform_fact(fact)?;\n        node.op_as_mut::<crate::ops::source::TypedSource>().unwrap().fact = fact.clone();\n        let downsamples = downsampled_body.node(input.node).outputs[0].successors.clone();\n        for ds in downsamples {\n            TypedModelPatch::replace_single_op(\n                &downsampled_body,\n                downsampled_body.node(ds.node),\n                &downsampled_body.node(ds.node).inputs,\n                Identity,\n            )?\n            .apply(&mut downsampled_body)?;\n        }\n    }\n\n    downsampled_body.check_consistency()?;\n    let inner_model = downsampled_body.into_decluttered()?;\n\n    let mut new_scan = scan_op.clone();\n    new_scan.body = inner_model;\n\n    let mut patch = TypedModelPatch::default();\n    let mut inputs = tvec!();\n    for (slot, input) in &mut new_scan.input_mapping.iter_mut().enumerate() {\n        match input {\n            InputMapping::State => {\n                let init = patch.tap_model(model, scan_node.inputs[slot])?;\n                let ds = patch.wire_node(\n                    format!(\"{}-{}\", down_node.name, slot),\n                    down_op.clone(),\n                    &[init],\n                )?[0];\n                inputs.push(ds);\n            }\n            InputMapping::Scan(info) => {\n                if info.chunk > 0 && !(info.chunk as usize).is_multiple_of(down_op.stride as usize)\n                {\n                    return Ok(None);\n                }\n                info.chunk = info.chunk.unsigned_abs().divceil(down_op.stride as usize) as isize\n                    * info.chunk.signum();\n                let tap = patch.tap_model(model, scan_node.inputs[slot])?;\n                let ds = patch.wire_node(\n                    format!(\"{}-{}\", down_node.name, slot),\n                    down_op.clone(),\n                    &[tap],\n                )?[0];\n                inputs.push(ds);\n            }\n            _ => (),\n        }\n    }\n\n    for output in &mut new_scan.output_mapping {\n        if let Some(d) = output.full_dim_hint.as_mut() {\n            *d = down_op.transform_dim(d)\n        }\n        if let Some((_slot, info)) = &mut output.scan {\n            rule_if!((info.chunk as usize).is_multiple_of(down_op.stride as usize));\n            info.chunk = info.chunk.unsigned_abs().divceil(down_op.stride as usize) as isize\n                * info.chunk.signum()\n        }\n    }\n\n    let scan = patch.wire_node(&*scan_node.name, new_scan, &inputs)?;\n    for ix in 0..scan_node.outputs.len() {\n        // FIXME need to check earlier on that all output are followed by a ds\n        let succ = scan_node.outputs[ix].successors[0].node;\n        patch.shunt_outside(model, OutletId::new(succ, 0), scan[ix])?;\n    }\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "core/src/ops/dummy.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Dummy;\n\nimpl Op for Dummy {\n    fn name(&self) -> StaticName {\n        \"Dummy\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Dummy {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn eval(&self, _inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        bail!(\"eval() called on a Dummy op. This is a bug.\")\n    }\n}\n\nimpl TypedOp for Dummy {\n    as_op!();\n\n    fn output_facts(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/einsum/as_blas.rs",
    "content": "use tract_ndarray::Dimension;\n\nuse crate::transform::ModelTransform;\nuse crate::{broadcast, internal::*};\nuse std::fmt::Debug;\n\nuse super::prefix_matmul::{PrefixMatMul, rewrite_einsum_to_prefix_matmul};\n\n#[derive(Debug, Default)]\npub struct AsBlas;\n\nimpl ModelTransform for AsBlas {\n    fn name(&self) -> StaticName {\n        \"as_blas\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        rewrite_einsum_to_prefix_matmul(model, true)?;\n        Rewriter::default()\n            .with_rule_for(\"matmul-to-sgemm\", matmul_to_sgemm)\n            .rewrite(&(), model)?;\n        Ok(())\n    }\n}\n\nfn matmul_to_sgemm(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    _node_name: &str,\n    op: &PrefixMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    if !op.transpose_a\n        && !op.transpose_b\n        && !op.transpose_c\n        && op.quantize_output.is_none()\n        && model.node_input_facts(node.id)?.iter().all(|f| f.datum_type == f32::datum_type())\n    {\n        TypedModelPatch::replace_single_op(model, node, &node.inputs, SGemm::default()).map(Some)\n    } else {\n        Ok(None)\n    }\n}\n\n#[derive(Debug, Default, Clone, PartialEq, Eq)]\npub struct SGemm {}\n\nimpl Op for SGemm {\n    fn name(&self) -> StaticName {\n        \"SGemm\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl SGemm {\n    fn output_shape<D: DimLike>(&self, a: &[D], b: &[D]) -> TractResult<TVec<D>> {\n        ensure!(a.len() == b.len());\n        let a_rank = a.len();\n        let b_rank = b.len();\n        let m = a[a_rank - 2].clone();\n        let n = b[b_rank - 1].clone();\n        let mut c_shape = broadcast::multi_broadcast(&[&a[..a_rank - 2], &b[..b_rank - 2]])\n            .context(\"Unable to broadcast\")?;\n        c_shape.push(m);\n        c_shape.push(n);\n        Ok(c_shape)\n    }\n}\n\nimpl EvalOp for SGemm {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (a, b) = args_2!(inputs);\n        let a_ptr = a.as_ptr::<f32>()?;\n        let b_ptr = b.as_ptr::<f32>()?;\n        let c_shape = self.output_shape(a.shape(), b.shape())?;\n        let rank = c_shape.len();\n        let m = c_shape[rank - 2];\n        let n = c_shape[rank - 1];\n        let k = a.shape()[rank - 1];\n        unsafe {\n            let mut c = Tensor::uninitialized::<f32>(&c_shape)?;\n            let c_ptr = c.as_ptr_mut::<f32>()?;\n            let silent_a_axis = c.rank() - a.rank();\n            let silent_b_axis = c.rank() - b.rank();\n            for prefix in ndarray::indices(&c_shape[0..rank - 2]) {\n                let mut a_ptr = a_ptr;\n                let mut b_ptr = b_ptr;\n                let mut c_ptr = c_ptr;\n                for (axis, x) in prefix.as_array_view().iter().enumerate() {\n                    if axis >= silent_a_axis && a.shape()[axis - silent_a_axis] != 1 {\n                        a_ptr = a_ptr.offset(*x as isize * a.strides()[axis - silent_a_axis]);\n                    }\n                    if axis >= silent_b_axis && b.shape()[axis - silent_b_axis] != 1 {\n                        b_ptr = b_ptr.offset(*x as isize * b.strides()[axis - silent_b_axis]);\n                    }\n                    c_ptr = c_ptr.offset(*x as isize * c.strides()[axis]);\n                }\n                if m == 1 {\n                    cblas::sgemv(\n                        cblas::Layout::RowMajor,\n                        cblas::Transpose::Ordinary,\n                        k as _,\n                        n as _,\n                        1.0,\n                        std::slice::from_raw_parts(b_ptr, n * k),\n                        n as _,\n                        std::slice::from_raw_parts(a_ptr, k),\n                        1,\n                        0.0,\n                        std::slice::from_raw_parts_mut(c_ptr, n),\n                        1,\n                    )\n                } else if n == 1 {\n                    cblas::sgemv(\n                        cblas::Layout::RowMajor,\n                        cblas::Transpose::None,\n                        m as _,\n                        k as _,\n                        1.0,\n                        std::slice::from_raw_parts(a_ptr, m * k),\n                        k as _,\n                        std::slice::from_raw_parts(b_ptr, k),\n                        1,\n                        0.0,\n                        std::slice::from_raw_parts_mut(c_ptr, m),\n                        1,\n                    )\n                } else {\n                    cblas::sgemm(\n                        cblas::Layout::RowMajor,\n                        cblas::Transpose::None,\n                        cblas::Transpose::None,\n                        m as _,\n                        n as _,\n                        k as _,\n                        1.0,\n                        std::slice::from_raw_parts(a_ptr, m * k),\n                        k as _,\n                        std::slice::from_raw_parts(b_ptr, k * n),\n                        n as _,\n                        0.0,\n                        std::slice::from_raw_parts_mut(c_ptr, m * n),\n                        n as _,\n                    )\n                }\n            }\n\n            Ok(tvec!(c.into_tvalue()))\n        }\n    }\n}\n\nimpl TypedOp for SGemm {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs[0].datum_type == f32::datum_type());\n        ensure!(inputs[1].datum_type == f32::datum_type());\n        Ok(tvec!(f32::fact(&self.output_shape(&inputs[0].shape, &inputs[1].shape)?)))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let fma = self.output_shape(&inputs[0].shape, &inputs[1].shape)?.iter().product::<TDim>()\n            * inputs[0].shape.last().unwrap();\n        Ok(tvec!((Cost::FMA(f32::datum_type()), fma)))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/einsum/einsum_matmul.rs",
    "content": "use std::fmt::Formatter;\nuse std::ops::Deref;\n\nuse tract_itertools::{izip, multiunzip};\nuse tract_linalg::block_quant::PackedBlockQuantFormat;\nuse tract_linalg::pack::PackedFormat;\n\nuse super::*;\nuse crate::ops::cast::cast;\nuse crate::ops::math::add;\nuse crate::ops::matmul::ModePicker;\nuse crate::ops::matmul::optimized::{\n    AddMatMulGeometry, MapOutputAxisToInput, OptMatMul, ProtoFusedSpec,\n};\nuse crate::ops::matmul::pack::{OptMatMulPack, OptSimpleMatMulPack};\nuse crate::ops::matmul::quant::{\n    combine_scales, compensate_zero_points, requant, wire_ensure_q8_flavour,\n};\nuse crate::ops::nn::{Reduce, Reducer};\n\npub fn merge_consecutive_same_role_axes(model: &mut TypedModel) -> TractResult<()> {\n    Rewriter::default()\n        .with_rule_for(\"merge-same-role-axes\", merge_same_role_axes_rule)\n        .rewrite(&(), model)\n}\n\nfn merge_same_role_axes_rule(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &EinSum,\n) -> TractResult<Option<TypedModelPatch>> {\n    // Only handle 2-input EinSums (matmul-like)\n    rule_if!(node.inputs.len() == 2);\n\n    // Compute role signature for each axis: (in_input_0, in_input_1, in_output)\n    type Role = (bool, bool, bool);\n    let axes: Vec<(char, Role)> = op\n        .axes\n        .iter_all_axes()\n        .map(|a| {\n            (a.repr, (!a.inputs[0].is_empty(), !a.inputs[1].is_empty(), !a.outputs[0].is_empty()))\n        })\n        .collect();\n\n    // For each input/output slot, get the axis order\n    let a_order: Vec<char> = op.axes.axes(InOut::In(0)).map(|a| a.repr).collect();\n    let b_order: Vec<char> = op.axes.axes(InOut::In(1)).map(|a| a.repr).collect();\n    let c_order: Vec<char> = op.axes.axes(InOut::Out(0)).map(|a| a.repr).collect();\n\n    // Find first group of 2+ same-role axes that are consecutive in all inputs.\n    // Scan each input's axis order for runs of same-role axes.\n    let role_map: std::collections::HashMap<char, Role> = axes.iter().cloned().collect();\n    let mut best_group: Option<Vec<char>> = None;\n\n    // Try each input order as the primary scan order\n    let all_orders = [&a_order, &b_order];\n    for (primary_idx, primary_order) in all_orders.iter().enumerate() {\n        let mut i = 0;\n        while i < primary_order.len() {\n            let first = primary_order[i];\n            let first_role = role_map[&first];\n            let mut group = vec![first];\n            let mut j = i + 1;\n            while j < primary_order.len() {\n                let candidate = primary_order[j];\n                if role_map[&candidate] != first_role {\n                    break;\n                }\n                // Check consecutive in the OTHER input too\n                let consecutive_in_others = all_orders\n                    .iter()\n                    .enumerate()\n                    .filter(|(idx, _)| *idx != primary_idx)\n                    .all(|(_, order)| {\n                        let positions: Vec<usize> = group\n                            .iter()\n                            .chain(std::iter::once(&candidate))\n                            .filter_map(|c| order.iter().position(|x| x == c))\n                            .collect();\n                        if positions.len() <= 1 {\n                            return true;\n                        }\n                        let mut sorted = positions.clone();\n                        sorted.sort();\n                        sorted == positions\n                            && sorted.last().unwrap() - sorted.first().unwrap() == sorted.len() - 1\n                    });\n                if !consecutive_in_others {\n                    break;\n                }\n                group.push(candidate);\n                j += 1;\n            }\n            if group.len() >= 2 && best_group.as_ref().map_or(true, |bg| group.len() > bg.len()) {\n                best_group = Some(group);\n            }\n            i = j;\n        }\n    }\n\n    if let Some(group) = best_group {\n        // Found a mergeable group. Emit the patch.\n        let input_facts = model.node_input_facts(node.id)?;\n        let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;\n        let output_shape = super::eval::output_shape(&op.axes, &input_shapes)?;\n\n        let drop_set: Vec<char> = group[1..].to_vec();\n\n        let mut patch = TypedModelPatch::default();\n        let mut wires: TVec<OutletId> = patch.taps(model, &node.inputs)?;\n\n        // Reshape each input to merge the group\n        for (slot, order) in [(0, &a_order), (1, &b_order)] {\n            let positions: Vec<usize> =\n                group.iter().filter_map(|c| order.iter().position(|x| x == c)).collect();\n            if positions.len() < 2 {\n                continue;\n            }\n            let start = positions[0];\n            let from_dims: TVec<TDim> =\n                positions.iter().map(|&p| input_shapes[slot][p].clone()).collect();\n            let merged: TDim = from_dims.iter().product();\n            wires[slot] = patch.wire_node(\n                format!(\"{node_name}.merge_in{slot}\"),\n                AxisOp::Reshape(start, from_dims, tvec![merged]),\n                &[wires[slot]],\n            )?[0];\n        }\n\n        // If group axes aren't consecutive in C, reorder the EinSum output\n        let c_positions: Vec<usize> =\n            group.iter().filter_map(|c| c_order.iter().position(|x| x == c)).collect();\n        let c_needs_reorder = c_positions.len() >= 2 && {\n            let mut sorted = c_positions.clone();\n            sorted.sort();\n            sorted.last().unwrap() - sorted.first().unwrap() != sorted.len() - 1\n                || sorted != c_positions\n        };\n        let mut adjusted_c_order = c_order.clone();\n        if c_needs_reorder {\n            // Move group axes together (put second next to first)\n            for k in 1..c_positions.len() {\n                let cur_pos = adjusted_c_order.iter().position(|&c| c == group[k]).unwrap();\n                let target_pos =\n                    adjusted_c_order.iter().position(|&c| c == group[k - 1]).unwrap() + 1;\n                if cur_pos != target_pos {\n                    let removed = adjusted_c_order.remove(cur_pos);\n                    let insert_at = if cur_pos < target_pos { target_pos - 1 } else { target_pos };\n                    adjusted_c_order.insert(insert_at, removed);\n                }\n            }\n        }\n\n        // Rebuild EinSum formula with adjusted output and dropped axes\n        let in0: String = a_order.iter().collect();\n        let in1: String = b_order.iter().collect();\n        let out: String = adjusted_c_order.iter().collect();\n        let expr = format!(\"{in0},{in1}->{out}\");\n        let mut new_axes: AxesMapping = expr.parse()?;\n        for &drop in &drop_set {\n            new_axes = new_axes.remove_axis(drop)?;\n        }\n        let new_op =\n            EinSum { axes: new_axes, operating_dt: op.operating_dt, q_params: op.q_params };\n        let mut result = patch.wire_node(node_name, new_op, &wires)?;\n\n        // Reshape output to split the merged axis back\n        let merged_c_positions: Vec<usize> =\n            group.iter().filter_map(|c| adjusted_c_order.iter().position(|x| x == c)).collect();\n        if merged_c_positions.len() >= 2 {\n            let start = merged_c_positions[0];\n            // Use original output dims for the group axes\n            let original_c_positions: Vec<usize> =\n                group.iter().filter_map(|c| c_order.iter().position(|x| x == c)).collect();\n            let original_dims: TVec<TDim> =\n                original_c_positions.iter().map(|&p| output_shape[p].clone()).collect();\n            let merged: TDim = original_dims.iter().product();\n            result[0] = patch.wire_node(\n                format!(\"{node_name}.unmerge_out\"),\n                AxisOp::Reshape(start, tvec![merged], original_dims),\n                &[result[0]],\n            )?[0];\n        }\n\n        // Restore original output order if we reordered\n        if c_needs_reorder {\n            // After unmerge, axes are in adjusted_c_order (but with group expanded).\n            // Need to permute back to c_order.\n            // Build the unmerged adjusted order\n            let mut unmerged_adj: Vec<char> = Vec::new();\n            for &c in &adjusted_c_order {\n                if c == group[0] {\n                    unmerged_adj.extend(&group);\n                } else if !group.contains(&c) {\n                    unmerged_adj.push(c);\n                }\n            }\n            // Find what moves are needed to get from unmerged_adj to c_order\n            for target_pos in 0..c_order.len() {\n                let cur_pos = unmerged_adj.iter().position(|&c| c == c_order[target_pos]).unwrap();\n                if cur_pos != target_pos {\n                    result[0] = patch.wire_node(\n                        format!(\"{node_name}.restore_out_{target_pos}\"),\n                        AxisOp::Move(cur_pos, target_pos),\n                        &[result[0]],\n                    )?[0];\n                    let removed = unmerged_adj.remove(cur_pos);\n                    unmerged_adj.insert(target_pos, removed);\n                }\n            }\n        }\n\n        patch.shunt_outside(model, node.id.into(), result[0])?;\n        return Ok(Some(patch));\n    }\n\n    // Second pass: look for same-role pairs separated by exactly one k-like axis\n    // in a single input. Insert a MoveAxis to push the separator to the end.\n    let k_role: Role = (true, true, false); // present in both inputs, absent from output\n    let role_of = |c: char| axes.iter().find(|(ch, _)| *ch == c).map(|(_, r)| *r);\n\n    for (slot, order) in [(0usize, &a_order), (1, &b_order)] {\n        // Find three consecutive axes in this input where the outer two share a role\n        // and the middle one is a k-axis\n        for w in order.windows(3) {\n            let (left, mid, right) = (w[0], w[1], w[2]);\n            let left_role = role_of(left);\n            let mid_role = role_of(mid);\n            let right_role = role_of(right);\n            if left_role != right_role || mid_role != Some(k_role) {\n                continue;\n            }\n            // left and right must also be consecutive in other inputs\n            // (output order is handled by the EinSum formula)\n            let other_input_orders: Vec<&Vec<char>> = [(0, &a_order), (1, &b_order)]\n                .iter()\n                .filter(|(s, _)| *s != slot)\n                .map(|(_, o)| *o)\n                .collect();\n            let consecutive_elsewhere = other_input_orders.iter().all(|order| {\n                let lp = order.iter().position(|&c| c == left);\n                let rp = order.iter().position(|&c| c == right);\n                match (lp, rp) {\n                    (Some(l), Some(r)) => r == l + 1,\n                    _ => true, // one or both absent — no constraint\n                }\n            });\n            if !consecutive_elsewhere {\n                continue;\n            }\n\n            // Move the k-axis to the inner (last) position in inputs and\n            // make left,right adjacent in the output too.\n            let mid_pos = order.iter().position(|&c| c == mid).unwrap();\n            let end_pos = order.len() - 1;\n            if mid_pos == end_pos {\n                continue;\n            }\n\n            // Use change_axes to update the EinSum formula for the input move\n            let move_op = AxisOp::Move(mid_pos, end_pos);\n            let Some(AxisChangeConsequence { substitute_op, .. }) =\n                op.change_axes(model, node, InOut::In(slot), &move_op)?\n            else {\n                continue;\n            };\n            let mut current_op = *substitute_op\n                .unwrap()\n                .downcast::<EinSum>()\n                .map_err(|_| anyhow!(\"expected EinSum\"))?;\n\n            // Also make left,right adjacent in the output if needed\n            let new_c: Vec<char> = current_op.axes.axes(InOut::Out(0)).map(|a| a.repr).collect();\n            let left_c = new_c.iter().position(|&c| c == left);\n            let right_c = new_c.iter().position(|&c| c == right);\n            let need_output_fix = matches!((left_c, right_c), (Some(l), Some(r)) if r != l + 1);\n            if need_output_fix {\n                let r_pos = right_c.unwrap();\n                let l_pos = left_c.unwrap();\n                let target = if r_pos < l_pos { l_pos } else { l_pos + 1 };\n                if let Some(AxisChangeConsequence { substitute_op, .. }) = current_op.change_axes(\n                    model,\n                    node,\n                    InOut::Out(0),\n                    &AxisOp::Move(r_pos, target),\n                )? {\n                    current_op = *substitute_op\n                        .unwrap()\n                        .downcast::<EinSum>()\n                        .map_err(|_| anyhow!(\"expected EinSum\"))?;\n                }\n            }\n\n            let mut patch = TypedModelPatch::default();\n            let mut wires: TVec<OutletId> = patch.taps(model, &node.inputs)?;\n\n            wires[slot] =\n                patch.wire_node(format!(\"{node_name}.move_k_in{slot}\"), move_op, &[wires[slot]])?\n                    [0];\n\n            let final_c: Vec<char> = current_op.axes.axes(InOut::Out(0)).map(|a| a.repr).collect();\n            let mut result = patch.wire_node(node_name, current_op, &wires)?;\n\n            // Restore original output order\n            if need_output_fix {\n                let r_cur = final_c.iter().position(|&c| c == right).unwrap();\n                let r_orig = c_order.iter().position(|&c| c == right).unwrap();\n                if r_cur != r_orig {\n                    result[0] = patch.wire_node(\n                        format!(\"{node_name}.restore_out\"),\n                        AxisOp::Move(r_cur, r_orig),\n                        &[result[0]],\n                    )?[0];\n                }\n            }\n\n            patch.shunt_outside(model, node.id.into(), result[0])?;\n            return Ok(Some(patch));\n        }\n    }\n\n    Ok(None)\n}\n\npub fn detect_all(model: &mut TypedModel) -> TractResult<()> {\n    Rewriter::default().with_rule_for(\"detect-matmul-einsum\", detect_rule).rewrite(&(), model)\n}\n\npub fn flatten_all(model: &mut TypedModel) -> TractResult<()> {\n    Rewriter::default().with_rule_for(\"flatten-matmul-einsum\", flatten_rule).rewrite(&(), model)\n}\n\n#[derive(Clone, Hash, PartialEq, Eq)]\npub struct EinSumMatMul {\n    pub op: EinSum,\n    pub m_axis: char,\n    pub k_axis: char,\n    pub n_axis: char,\n    pub m: TDim,\n    pub k: TDim,\n    pub n: TDim,\n}\n\nimpl EinSumMatMul {\n    pub fn m_axis(&self) -> &Axis {\n        self.op.axes.axis(self.m_axis).unwrap()\n    }\n    pub fn k_axis(&self) -> &Axis {\n        self.op.axes.axis(self.k_axis).unwrap()\n    }\n    pub fn n_axis(&self) -> &Axis {\n        self.op.axes.axis(self.n_axis).unwrap()\n    }\n    pub fn a_m(&self) -> usize {\n        self.m_axis().inputs[0][0]\n    }\n    pub fn a_k(&self) -> usize {\n        self.k_axis().inputs[0][0]\n    }\n    pub fn b_k(&self) -> usize {\n        self.k_axis().inputs[1][0]\n    }\n    pub fn b_n(&self) -> usize {\n        self.n_axis().inputs[1][0]\n    }\n    pub fn c_m(&self) -> Option<usize> {\n        self.m_axis().outputs[0].first().cloned()\n    }\n    pub fn c_n(&self) -> Option<usize> {\n        self.n_axis().outputs[0].first().cloned()\n    }\n\n    fn new(\n        op: EinSum,\n        m_axis: char,\n        k_axis: char,\n        n_axis: char,\n        m: TDim,\n        k: TDim,\n        n: TDim,\n    ) -> Self {\n        Self { op, m_axis, k_axis, n_axis, m, k, n }\n    }\n}\n\nimpl Debug for EinSumMatMul {\n    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {\n        write!(\n            f,\n            \"EinsumMatMul: {} {:?} m: {}={}; k: {}={}; n: {}={}\",\n            self.op.axes,\n            self.op.operating_dt,\n            self.m_axis,\n            self.m,\n            self.k_axis,\n            self.k,\n            self.n_axis,\n            self.n\n        )\n    }\n}\n\nimpl Deref for EinSumMatMul {\n    type Target = EinSum;\n    fn deref(&self) -> &Self::Target {\n        &self.op\n    }\n}\n\nimpl Op for EinSumMatMul {\n    fn name(&self) -> StaticName {\n        \"EinSumMatMul\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for EinSumMatMul {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        self.op.eval_with_session(node_id, session, inputs)\n    }\n}\n\nimpl TypedOp for EinSumMatMul {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        self.op.output_facts(inputs)\n    }\n\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        // deal with parametric quantization extra inputs\n        if node.inputs.len() == 9 {\n            ensure!(self.op.q_params.is_some());\n            return dequant(model, node, self).map(Some);\n        }\n        ensure!(node.inputs.len() == 2);\n        let (a, b) = model.node_input_facts(node.id)?.into_iter().collect_tuple().unwrap();\n        // at this stage a and b must NOT be packed yet. if they are exotic, we can assume it's just compression\n        let must_transpose = if let Some(of) = a.exotic_fact() {\n            ensure!(of.is::<BlockQuantFact>());\n            false\n        } else if let Some(of) = b.exotic_fact() {\n            ensure!(of.is::<BlockQuantFact>());\n            true\n        } else if self.m == self.n {\n            false\n        } else {\n            match (self.m.as_i64(), self.n.as_i64()) {\n                (Some(m), Some(n)) => m < n,\n                (None, Some(n)) => n >= 8,\n                (Some(_), _) => false,\n                _ => (self.n.clone() - &self.m).prove_positive_or_zero(),\n            }\n        };\n        if must_transpose {\n            let mut op = self.clone();\n            op.op.axes.iter_all_axes_mut().for_each(|axis| axis.inputs.swap(0, 1));\n            std::mem::swap(&mut op.m_axis, &mut op.n_axis);\n            std::mem::swap(&mut op.m, &mut op.n);\n            return TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &[node.inputs[1], node.inputs[0]],\n                op,\n            )\n            .map(|p| Some(p.with_context(\"transposing\")));\n        }\n        // opt mat mul assumes we have at least one m or n\n        if self.c_m().is_some() || self.c_n().is_some() {\n            return optimized_mat_mul(model, node, self)\n                .map(|opt| opt.map(|p| p.with_context(\"optimizing\")));\n        }\n        Ok(None)\n    }\n\n    as_op!();\n}\n\npub(crate) fn detect_rule(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    _name: &str,\n    op: &EinSum,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(node.inputs.len() == (2 + op.q_params.is_some() as usize * 7));\n    let input_facts = model.node_input_facts(node.id)?;\n    let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;\n    let output_shape = super::eval::output_shape(&op.axes, &input_shapes)?;\n    let k_axes: TVec<&Axis> = op\n        .axes\n        .iter_all_axes()\n        // Filter possible candidates (should be one time in each inputs but not in output)\n        .filter(|a| a.inputs[0].len() == 1 && a.inputs[1].len() == 1 && a.outputs[0].is_empty())\n        .collect();\n\n    let non_trivial_k_axis = k_axes\n        .iter()\n        .filter(|a| {\n            !input_shapes[0][a.inputs[0][0]].is_one() || !input_shapes[1][a.inputs[1][0]].is_one()\n        })\n        .copied()\n        .collect::<TVec<_>>();\n\n    let k_axis = if non_trivial_k_axis.len() > 1 {\n        return regroup_k_axes(op, model, node, non_trivial_k_axis);\n    } else {\n        non_trivial_k_axis.first().or_else(|| k_axes.first()).copied()\n    };\n    let Some(k_axis) = k_axis else { return inject_k_axis(op, model, node).map(Some) };\n\n    let mut possible_m_axes: Vec<_> = op\n        .axes\n        .iter_all_axes()\n        .filter(|a| {\n            a.inputs[0].len() == 1\n                && (a.inputs[1].is_empty() || input_shapes[1][a.inputs[1][0]].is_one())\n                && (a.outputs[0].len() == 1\n                    || (input_shapes[0][a.inputs[0][0]].is_one() && a.inputs[1].is_empty()))\n        })\n        .collect();\n\n    // Prioritize obvious m-axes\n    if possible_m_axes.iter().any(|a| !a.outputs[0].is_empty()) {\n        possible_m_axes.retain(|a| !a.outputs[0].is_empty());\n    }\n\n    let m_axis = possible_m_axes\n        .into_iter()\n        .max_by_key(|a| input_shapes[0][a.inputs[0][0]].as_i64().unwrap_or(i64::MAX));\n\n    let Some(m_axis) = m_axis else {\n        return inject_m_or_n_axis(op, model, node, false).map(Some);\n    };\n\n    let n_axis = op\n        .axes\n        .iter_all_axes()\n        .filter(|a| {\n            (a.inputs[0].is_empty() || input_shapes[0][a.inputs[0][0]].is_one())\n                && a.inputs[1].len() == 1\n                && a.outputs[0].len() == 1\n                && *a != m_axis\n        })\n        .max_by_key(|a| input_shapes[1][a.inputs[1][0]].as_i64().unwrap_or(i64::MAX));\n    let Some(n_axis) = n_axis else {\n        return inject_m_or_n_axis(op, model, node, true).map(Some);\n    };\n    for axis in op.axes.iter_all_axes() {\n        let one = TDim::one();\n        let in_left =\n            axis.inputs[0].first().map(|pos| &input_shapes[0][*pos]).unwrap_or(&one) != &one;\n        let in_right =\n            axis.inputs[1].first().map(|pos| &input_shapes[1][*pos]).unwrap_or(&one) != &one;\n        let in_out = axis.outputs[0].first().map(|pos| &output_shape[*pos]).unwrap_or(&one) != &one;\n        if (in_left ^ in_right) && !in_out {\n            return Ok(None);\n            // return Ok(AxesOrPatch::NotAMatMul(\n            //     \"non trivial single-side disappearing axis\",\n            //     vec![axis],\n            // ));\n        }\n    }\n    let m = input_shapes[0][m_axis.inputs[0][0]].clone();\n    let k = input_shapes[0][k_axis.inputs[0][0]].clone();\n    let n = input_shapes[1][n_axis.inputs[1][0]].clone();\n    TypedModelPatch::replace_single_op(\n        model,\n        node,\n        &node.inputs,\n        EinSumMatMul::new(op.clone(), m_axis.repr, k_axis.repr, n_axis.repr, m, k, n),\n    )\n    .map(Some)\n}\n\npub(super) fn inject_k_axis(\n    op: &EinSum,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<TypedModelPatch> {\n    let mut new_axes = op.axes.clone();\n    let name = &node.name;\n    let mut patch = TypedModelPatch::new(\"inject k axis\");\n    let mut wire = patch.taps(model, &node.inputs)?;\n    let repr = new_axes.available_label();\n    new_axes = new_axes.with_extra_axis(repr, InOut::In(0), 0)?.with_extra_axis_occurency(\n        repr,\n        InOut::In(1),\n        0,\n    )?;\n    wire[0] = patch.wire_node(format!(\"{name}.add_k.0\"), AxisOp::Add(0), &[wire[0]])?[0];\n    wire[1] = patch.wire_node(format!(\"{name}.add_k.1\"), AxisOp::Add(0), &[wire[1]])?[0];\n    wire = patch.wire_node(&node.name, EinSum { axes: new_axes, ..op.clone() }, &wire)?;\n    patch.shunt_outside(model, node.id.into(), wire[0])?;\n    Ok(patch)\n}\n\npub(super) fn regroup_k_axes(\n    op: &EinSum,\n    model: &TypedModel,\n    node: &TypedNode,\n    mut k_axes: TVec<&Axis>,\n) -> TractResult<Option<TypedModelPatch>> {\n    let input_facts = model.node_input_facts(node.id)?;\n    let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;\n    let contig_in_a = k_axes\n        .iter()\n        .map(|axis| axis.inputs[0][0])\n        .sorted()\n        .tuple_windows()\n        .all(|(a, b)| a + 1 == b);\n    if contig_in_a {\n        k_axes.sort_by_key(|ax| ax.inputs[0][0]);\n    } else {\n        k_axes.sort_by_key(|ax| ax.inputs[1][0]);\n    }\n    let k_dims: TVec<_> =\n        k_axes.iter().map(|ax| input_shapes[0][ax.inputs[0][0]].clone()).collect();\n    let k: TDim = k_dims.iter().product();\n    let mut patch = TypedModelPatch::default();\n    let mut wires = patch.taps(model, &node.inputs)?;\n    let mut exprs: Vec<String> =\n        (0..2).map(|slot| op.axes.axes(InOut::In(slot)).map(|ax| ax.repr).join(\"\")).collect();\n    for slot in 0..2 {\n        if k_axes.iter().map(|ax| ax.inputs[slot][0]).tuple_windows().any(|(a, b)| a + 1 != b) {\n            let after = op\n                .axes\n                .axes(InOut::In(slot))\n                .filter(|ax| !k_axes.contains(ax))\n                .chain(k_axes.iter().copied())\n                .map(|ax| ax.repr)\n                .join(\"\");\n            let transpose =\n                AxesMapping::from_strs(&[&exprs[slot]], &[&after])?.translate_to_axis_ops()?;\n            for (ix, op) in transpose.into_iter().enumerate() {\n                wires[slot] = patch.wire_node(\n                    format!(\"{}.transpose_input_{}.{}\", &node.name, slot, ix),\n                    op,\n                    &[wires[slot]],\n                )?[0];\n            }\n            exprs[slot] = after;\n        }\n        let pos = exprs[slot].chars().position(|c| k_axes[0].repr == c).unwrap();\n        wires[slot] = patch.wire_node(\n            format!(\"{}.fold_k_in_input_{}\", &node.name, slot),\n            AxisOp::Reshape(pos, k_dims.clone(), tvec!(k.clone())),\n            &[wires[slot]],\n        )?[0];\n        exprs[slot] =\n            exprs[slot].chars().filter(|c| !k_axes.iter().any(|k| k.repr == *c)).collect();\n        exprs[slot].insert(pos, k_axes[0].repr);\n    }\n    let old = op.axes.to_string();\n    let (iexpr, oexpr) = old.split_once(\"->\").unwrap();\n    let mut expr: String = exprs.iter().join(\",\");\n    if node.inputs.len() > 2 {\n        expr = expr + \",\" + &iexpr.split(\",\").skip(2).join(\",\");\n    }\n    expr = expr + \"->\" + oexpr;\n    let wire = patch.wire_node(\n        &node.name,\n        EinSum { axes: expr.parse().unwrap(), ..op.clone() },\n        &wires,\n    )?[0];\n    patch.shunt_outside(model, node.id.into(), wire)?;\n    Ok(Some(patch))\n}\n\npub(super) fn inject_m_or_n_axis(\n    op: &EinSum,\n    model: &TypedModel,\n    node: &TypedNode,\n    is_n: bool,\n) -> TractResult<TypedModelPatch> {\n    let input_to_fix = is_n as usize;\n    let label = if is_n { \"n\" } else { \"m\" };\n    let name = &node.name;\n    let mut patch = TypedModelPatch::new(\"Injecting m or n axis\");\n    let mut wire = patch.taps(model, &node.inputs)?;\n    let repr = op.axes.available_label();\n    let new_axes = op\n        .axes\n        .clone()\n        .with_extra_axis(repr, InOut::In(input_to_fix), 0)?\n        .with_extra_axis_occurency(repr, InOut::Out(0), 0)?;\n    wire[input_to_fix] =\n        patch.wire_node(format!(\"{name}.add_{label}\"), AxisOp::Add(0), &[wire[input_to_fix]])?[0];\n    wire = patch.wire_node(name, EinSum { axes: new_axes, ..op.clone() }, &wire)?;\n    wire = patch.wire_node(&node.name, AxisOp::Rm(0), &wire)?;\n    patch.shunt_outside(model, node.id.into(), wire[0])?;\n    Ok(patch)\n}\n\nfn wire_axes_fix(\n    patch: &mut TypedModelPatch,\n    name: &str,\n    var: &str,\n    mapping: &AxesMapping,\n    mut outlet: TVec<OutletId>,\n) -> TractResult<TVec<OutletId>> {\n    for (ix, axis_op) in mapping.translate_to_axis_ops()?.into_iter().enumerate() {\n        outlet = patch.wire_node(format!(\"{name}.fix_{var}.{ix})\"), axis_op, &outlet)?;\n    }\n    Ok(outlet)\n}\n\nfn dequant(\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &EinSumMatMul,\n) -> TractResult<TypedModelPatch> {\n    let name = &node.name;\n    let mut patch = TypedModelPatch::new(\"Dequantizing einsum\");\n\n    let k_axis = op.k_axis();\n\n    let mut taps = patch.taps(model, &node.inputs)?;\n    for ab in [0, 1] {\n        let scale_input = 4 + ab * 2;\n        if !patch.outlet_fact(taps[scale_input])?.shape.volume().is_one() {\n            let q_axis_in_output = op.axes.axis((InOut::In(scale_input), 0))?.outputs[0][0];\n            let output_rank = node.outputs[0].fact.rank();\n            for i in 1..(output_rank - q_axis_in_output) {\n                taps[scale_input] = patch.wire_node(\n                    format!(\"{name}.scale_input{ab}_axis_fix_{i}\"),\n                    AxisOp::Add(i),\n                    &[taps[scale_input]],\n                )?[0];\n            }\n        }\n    }\n\n    let [mut a, mut b, bias, mut a0, a_scale, mut b0, b_scale, c0, c_scale] = *taps else {\n        bail!(\"Expect exactly 9 inputs\")\n    };\n\n    wire_ensure_q8_flavour(&mut patch, &node.name, &mut a, \"a\", &mut a0, i8::datum_type())?;\n    wire_ensure_q8_flavour(&mut patch, &node.name, &mut b, \"b\", &mut b0, i8::datum_type())?;\n\n    let mut output = patch.wire_node(\n        &node.name,\n        EinSum {\n            q_params: None,\n            axes: op.axes.extract_sub_mapping(&[0, 1], &[0])?,\n            operating_dt: op.operating_dt,\n        },\n        &[a, b],\n    )?;\n\n    let a_i32 = patch.wire_node(format!(\"{name}.a_as_i32\"), cast(i32::datum_type()), &[a])?[0];\n    let b_i32 = patch.wire_node(format!(\"{name}.b_as_i32\"), cast(i32::datum_type()), &[b])?[0];\n    let sum_a = patch.wire_node(\n        format!(\"{name}.sum_a\"),\n        Reduce::new(tvec!(k_axis.inputs[0][0]), Reducer::Sum),\n        &[a_i32],\n    )?;\n    let sum_b = patch.wire_node(\n        format!(\"{name}.sum_b\"),\n        Reduce::new(tvec!(k_axis.inputs[1][0]), Reducer::Sum),\n        &[b_i32],\n    )?;\n\n    let sum_a =\n        wire_axes_fix(&mut patch, name, \"sum_a\", &op.axes.extract_sub_mapping(&[0], &[0])?, sum_a)?;\n    let sum_b =\n        wire_axes_fix(&mut patch, name, \"sum_b\", &op.axes.extract_sub_mapping(&[1], &[0])?, sum_b)?;\n    let bias = tvec!(bias);\n    let bias =\n        wire_axes_fix(&mut patch, name, \"bias\", &op.axes.extract_sub_mapping(&[2], &[0])?, bias)?;\n\n    let abc_scale = combine_scales(&mut patch, name, a_scale, b_scale, c_scale)?;\n\n    output = patch.wire_node(format!(\"{name}.add_bias\"), add(), &[output[0], bias[0]])?;\n\n    let k = model.outlet_fact(node.inputs[0])?.shape[k_axis.inputs[0][0]].clone();\n    let output = compensate_zero_points(&mut patch, name, output[0], k, a0, b0, sum_a[0], sum_b[0])\n        .context(\"Zero point compensation\")?;\n    let output = requant(&mut patch, name, output, op.q_params.unwrap(), abc_scale, c0)?;\n    patch.shunt_outside(model, node.id.into(), output)?;\n    Ok(patch)\n}\n\nfn flatten_rule(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    _name: &str,\n    op: &EinSumMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    TypedModelPatch::replace_single_op(model, node, &node.inputs, op.op.clone()).map(Some)\n}\n\nfn optimized_mat_mul(\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &EinSumMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    let (mode_picker, left_pack, impls) = kernel_selection::strategize(model, node, op)?;\n    let input_facts = model.node_input_facts(node.id)?;\n    let input_shapes = op.actual_input_shapes_from_facts(&input_facts)?;\n    let prefix = &node.name;\n\n    let mut patch = TypedModelPatch::new(\"Einsum to OptMatMul\");\n    let taps = patch.taps(model, &node.inputs)?;\n    let name = &node.name;\n\n    let pack_a: Box<dyn TypedOp> = if input_facts[0].konst.is_some() {\n        if let Some(pf) = left_pack.downcast_ref::<PackedFormat>() {\n            Box::new(OptMatMulPack {\n                packers: vec![pf.clone()],\n                mode_picker: ModePicker::Single,\n                k_axis: op.a_k(),\n                mn_axis: op.a_m(),\n            })\n        } else if let Some(packed_format) =\n            left_pack.downcast_ref::<PackedBlockQuantFormat>().cloned()\n        {\n            Box::new(OptSimpleMatMulPack {\n                packed_format,\n                k: input_shapes[0][op.a_k()].to_usize().unwrap(),\n                m: input_shapes[0][op.a_m()].to_usize().unwrap(),\n            })\n        } else {\n            bail!(\"Unexpected static input format {left_pack:?}\");\n        }\n    } else {\n        Box::new(OptMatMulPack {\n            packers: impls\n                .iter()\n                .map(|(mmm, p, pe)| {\n                    pe.as_ref()\n                        .map(|pe| &pe.from)\n                        .unwrap_or(&mmm.packings()[*p].0)\n                        .downcast_ref::<PackedFormat>()\n                        .unwrap()\n                        .clone()\n                })\n                .collect(),\n            mode_picker: mode_picker.clone(),\n            k_axis: op.a_k(),\n            mn_axis: op.a_m(),\n        })\n    };\n    let pa = patch.wire_node(format!(\"{prefix}.pack_a\"), pack_a, &[taps[0]])?[0];\n\n    let pb = patch.wire_node(\n        format!(\"{prefix}.pack_b\"),\n        OptMatMulPack {\n            k_axis: op.b_k(),\n            mn_axis: op.b_n(),\n            packers: impls\n                .iter()\n                .map(|(mmm, p, _)| {\n                    mmm.packings()[*p].1.downcast_ref::<PackedFormat>().unwrap().clone()\n                })\n                .collect(),\n            mode_picker: mode_picker.clone(),\n        },\n        &[taps[1]],\n    )?[0];\n\n    let mut c_to_a_axis_mapping = tvec!();\n    let mut c_to_b_axis_mapping = tvec!();\n    for axis in op\n        .op\n        .axes\n        .iter_all_axes()\n        .filter(|&axis| ![op.m_axis, op.k_axis, op.n_axis].contains(&axis.repr))\n    {\n        if let (&[c], &[a]) = (&*axis.outputs[0], &*axis.inputs[0])\n            && input_shapes[0][a] != 1.to_dim()\n        {\n            let a = a - (a > op.a_m()) as usize - (a > op.a_k()) as usize;\n            c_to_a_axis_mapping.push((c, a));\n        }\n        if let (&[c], &[b]) = (&*axis.outputs[0], &*axis.inputs[1])\n            && input_shapes[1][b] != 1.to_dim()\n        {\n            let b = b - (b > op.b_n()) as usize - (b > op.b_k()) as usize;\n            c_to_b_axis_mapping.push((c, b));\n        }\n    }\n\n    let c_fact = op.output_facts(&input_facts)?.remove(0);\n    let geo = AddMatMulGeometry {\n        k: op.k.clone(),\n        c_to_a_axis_mapping: MapOutputAxisToInput(c_to_a_axis_mapping),\n        c_to_b_axis_mapping: MapOutputAxisToInput(c_to_b_axis_mapping),\n    };\n    let (mmms, packings, extractor): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(impls);\n    let outputs = mmms.iter().map(|mmm| unsafe { mmm.c_view(op.c_m(), op.c_n()) }).collect();\n    let trivial_packing = mmms.len() == 1\n        && packings[0] == 0\n        && extractor[0].is_none()\n        && input_facts[0].exotic_fact.is_none();\n    let opt = OptMatMul::new(\n        mmms,\n        mode_picker,\n        c_fact,\n        op.c_m(),\n        op.c_n(),\n        vec![\n            ProtoFusedSpec::AddMatMul {\n                geo,\n                a: 0,\n                b: 1,\n                packings: izip!(packings, extractor).collect_vec(),\n            },\n            ProtoFusedSpec::Store(outputs),\n        ],\n        trivial_packing,\n    )\n    .context(\"Creating OptMatMul\")?;\n    let output = patch.wire_node(name, opt, &[pa, pb])?[0];\n    patch.shunt_outside(model, node.id.into(), output)?;\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "core/src/ops/einsum/eval.rs",
    "content": "use super::AxesMapping;\nuse crate::internal::*;\nuse ndarray::{ArrayViewD, Zip};\nuse tract_data::itertools::Itertools;\nuse tract_linalg::block_quant::{BlockQuantStorage, block_quant_slice};\nuse tract_ndarray::{Axis, Dimension};\nuse tract_num_traits::{One, Zero};\n\npub fn output_shape<D: DimLike>(\n    expr: &AxesMapping,\n    inputs: &[impl AsRef<[D]>],\n) -> TractResult<TVec<D>> {\n    Ok(expr\n        .iter_all_axes()\n        .filter(|a| a.outputs[0].len() > 0)\n        .sorted_by_key(|axis| axis.outputs[0][0])\n        .map(|axis| {\n            axis.inputs[0..inputs.len()]\n                .iter()\n                .enumerate()\n                .flat_map(|(input_id, positions)| {\n                    positions.iter().map(move |p| inputs[input_id].as_ref()[*p].clone())\n                })\n                .find(|x| x != &1.into())\n                .unwrap_or_else(|| 1.into())\n        })\n        .collect())\n}\n\npub fn dequant_inputs(acc: DatumType, input: TVec<TValue>) -> TractResult<TVec<TValue>> {\n    input\n        .into_iter()\n        .map(|i| {\n            if i.is_plain() && i.datum_type().is_number() {\n                Ok(i)\n            } else {\n                let s = i.shape();\n                let k = *s.last().unwrap();\n                // Leading dims are group/batch dims; last two are [M, K]\n                let num_groups: usize =\n                    if s.len() > 2 { s[..s.len() - 2].iter().product() } else { 1 };\n                let m_per_group: usize = if s.len() >= 2 { s[s.len() - 2] } else { 1 };\n                let bqs = i.try_storage_as::<BlockQuantStorage>()?;\n                let mut unpacked: Vec<Tensor> = if acc.is::<f16>() {\n                    (0..num_groups)\n                        .map(|g| {\n                            let slice =\n                                block_quant_slice(bqs.value(), bqs.format(), m_per_group, k, g);\n                            bqs.format().dequant_f16(slice)\n                        })\n                        .collect::<TractResult<_>>()?\n                } else if acc.is::<f32>() {\n                    (0..num_groups)\n                        .map(|g| {\n                            let slice =\n                                block_quant_slice(bqs.value(), bqs.format(), m_per_group, k, g);\n                            bqs.format().dequant_f32(slice)\n                        })\n                        .collect::<TractResult<_>>()?\n                } else {\n                    bail!(\n                        \"Only f32 and f16 accumulators are compatible with BlockQuantValue inputs\"\n                    );\n                };\n                unpacked.iter_mut().try_for_each(|t| t.insert_axis(0))?;\n                let stacked = if unpacked.len() > 1 {\n                    Tensor::stack_tensors(0, &unpacked)?\n                } else {\n                    unpacked.into_iter().next().unwrap()\n                };\n                Ok(stacked.into_shape(s)?.into_tvalue())\n            }\n        })\n        .collect::<TractResult<TVec<TValue>>>()\n}\n\npub fn eval_t<Acc: Datum + Zero + One>(\n    expr: &AxesMapping,\n    inputs: TVec<TValue>,\n) -> TractResult<Tensor> {\n    let inputs = dequant_inputs(Acc::datum_type(), inputs)?;\n    let shapes: TVec<_> = inputs.iter().map(|t| t.shape()).collect();\n    let output_shape = output_shape(expr, &shapes)?;\n    let inputs: TVec<Cow<Tensor>> =\n        inputs.iter().map(|t| t.cast_to::<Acc>()).collect::<TractResult<_>>()?;\n    let inputs: TVec<tract_ndarray::ArrayViewD<Acc>> =\n        inputs.iter().map(|t| t.to_plain_array_view::<Acc>()).collect::<TractResult<_>>()?;\n    let summing_axes: TVec<_> = expr\n        .iter_all_axes()\n        .filter(|a| {\n            a.outputs[0].len() == 0 && a.inputs[0..inputs.len()].iter().any(|i| i.len() > 0)\n        })\n        .collect();\n    let summing_shape: TVec<usize> = summing_axes\n        .iter()\n        .map(|axis| {\n            axis.inputs\n                .iter()\n                .take(inputs.len())\n                .enumerate()\n                .find_map(|(input_id, positions)| {\n                    if positions.len() > 0 {\n                        Some(inputs[input_id].shape()[positions[0]])\n                    } else {\n                        None\n                    }\n                })\n                .unwrap()\n        })\n        .collect();\n    let output = tract_ndarray::ArrayD::<Acc>::from_shape_fn(&*output_shape, |coords| {\n        let coords = coords.as_array_view();\n        let mut views = inputs.clone();\n        for (axis, x) in expr\n            .iter_all_axes()\n            .filter(|a| a.outputs[0].len() > 0)\n            .sorted_by_key(|axis| axis.outputs[0][0])\n            .zip(coords)\n        {\n            for (input_id, input_axis_positions) in axis.inputs[0..inputs.len()].iter().enumerate()\n            {\n                for position in input_axis_positions {\n                    let x = if views[input_id].shape()[*position] == 1 { 0 } else { *x };\n                    views[input_id]\n                        .slice_axis_inplace(tract_ndarray::Axis(*position), (x..=x).into());\n                }\n            }\n        }\n        let mut sum: Acc = Acc::zero();\n        for sum_coords in tract_ndarray::indices(&*summing_shape) {\n            let mut views = views.clone();\n            let sum_coords = sum_coords.as_array_view();\n            for (axis, x) in summing_axes.iter().zip(sum_coords) {\n                for (input_id, input_axis_positions) in\n                    axis.inputs.iter().take(inputs.len()).enumerate()\n                {\n                    for position in input_axis_positions {\n                        views[input_id].slice_axis_inplace(Axis(*position), (*x..=*x).into())\n                    }\n                }\n            }\n            let mut product = Acc::one();\n            for v in &views {\n                debug_assert_eq!(v.len(), 1);\n                product = product * v.iter().next().unwrap().clone();\n            }\n            sum = sum + product;\n        }\n        sum\n    });\n    Ok(output.into_tensor())\n}\n\npub fn eval_q(expr: &AxesMapping, qp: DatumType, inputs: TVec<TValue>) -> TractResult<Tensor> {\n    fn reshape_param<'a>(\n        expr: &AxesMapping,\n        data_slot: InOut,\n        qp: &'a Tensor,\n        qp_slot: InOut,\n    ) -> TractResult<ArrayViewD<'a, f32>> {\n        if qp.rank() == 0 {\n            qp.try_as_plain()?.to_array_view()\n        } else {\n            let data_rank = expr.rank(data_slot);\n\n            // Handle case where axis is not present in input (qp.len is necessarily 1)\n            let pos_in_input =\n                expr.axis((qp_slot, 0))?.interface(data_slot).first().cloned().unwrap_or(0);\n\n            let mut shape = vec![1; data_rank];\n            shape[pos_in_input] = qp.len();\n            Ok(qp.try_as_plain()?.to_array_view()?.into_shape_with_order(shape)?)\n        }\n    }\n    let [a, b, bias, a0, a_scale, b0, b_scale, c0, c_scale] = &*inputs else {\n        bail!(\"Expect exactly 9 inputs\")\n    };\n\n    let mut a = a.cast_to::<i32>()?.cast_to::<f32>()?.into_owned();\n    let mut b = b.cast_to::<i32>()?.cast_to::<f32>()?.into_owned();\n\n    let a0 = a0.cast_to::<f32>()?;\n    let b0 = b0.cast_to::<f32>()?;\n    let c0 = c0.cast_to::<f32>()?;\n    let a_scale = a_scale.cast_to::<f32>()?;\n    let b_scale = b_scale.cast_to::<f32>()?;\n    let c_scale = c_scale.cast_to::<f32>()?;\n    let bias = bias.cast_to::<f32>()?;\n    ensure!(a0.rank() < 2);\n    ensure!(b0.rank() < 2);\n    ensure!(c0.rank() < 2);\n    ensure!(a_scale.rank() < 2);\n    ensure!(b_scale.rank() < 2);\n    ensure!(c_scale.rank() < 2);\n    ensure!(bias.rank() < 2);\n\n    Zip::from(a.to_plain_array_view_mut::<f32>()?)\n        .and_broadcast(reshape_param(expr, InOut::In(0), &a0, InOut::In(3))?)\n        .and_broadcast(reshape_param(expr, InOut::In(0), &a_scale, InOut::In(4))?)\n        .for_each(|a, a0, a_scale| *a = a_scale * (*a - a0));\n\n    Zip::from(b.to_plain_array_view_mut::<f32>()?)\n        .and_broadcast(reshape_param(expr, InOut::In(1), &b0, InOut::In(5))?)\n        .and_broadcast(reshape_param(expr, InOut::In(1), &b_scale, InOut::In(6))?)\n        .for_each(|b, b0, b_scale| *b = b_scale * (*b - b0));\n\n    let mut output =\n        eval_t::<f32>(expr, tvec!(a.into_tvalue(), b.into_tvalue()))?.into_plain_array::<f32>()?;\n\n    Zip::from(&mut output)\n        .and_broadcast(reshape_param(expr, InOut::Out(0), &bias, InOut::In(2))?)\n        .and_broadcast(reshape_param(expr, InOut::Out(0), &c0, InOut::In(7))?)\n        .and_broadcast(reshape_param(expr, InOut::Out(0), &c_scale, InOut::In(8))?)\n        .and_broadcast(reshape_param(expr, InOut::Out(0), &a_scale, InOut::In(4))?)\n        .and_broadcast(reshape_param(expr, InOut::Out(0), &b_scale, InOut::In(6))?)\n        .for_each(|c, bias, c0, c_scale, a_scale, b_scale| {\n            *c = ((*c + bias * a_scale * b_scale) / c_scale + c0).round()\n        });\n\n    if qp.unquantized() == i8::datum_type() {\n        output.mapv_inplace(|x| x.clamp(i8::MIN as _, i8::MAX as _))\n    } else if qp.unquantized() == u8::datum_type() {\n        output.mapv_inplace(|x| x.clamp(u8::MIN as _, u8::MAX as _))\n    }\n    Ok(output.into_tensor().cast_to::<i32>()?.cast_to_dt(qp)?.into_owned())\n}\n"
  },
  {
    "path": "core/src/ops/einsum/kernel_selection.rs",
    "content": "#![allow(clippy::type_complexity)]\n\nuse dyn_clone::clone_box;\nuse dyn_eq::DynEq;\nuse tract_itertools::Itertools;\nuse tract_linalg::WeightType;\nuse tract_linalg::block_quant::BlockQuantFact;\nuse tract_linalg::mmm::{ImplementationQuality, MMMInputFormat, MatMatMul, PanelExtractor};\n\nuse crate::internal::*;\nuse crate::ops::matmul::ModePicker;\n\nuse super::einsum_matmul::EinSumMatMul;\n\npub type Impl = (Box<dyn MatMatMul>, usize, Option<PanelExtractor>);\npub type Strat = (ModePicker, Box<dyn MMMInputFormat>, Vec<Impl>);\n\nfn single_strat(it: Impl) -> Strat {\n    (ModePicker::Single, it.0.packings()[it.1].0.clone(), vec![it])\n}\n\npub fn strategize(model: &TypedModel, node: &TypedNode, op: &EinSumMatMul) -> TractResult<Strat> {\n    let input_facts = model.node_input_facts(node.id)?;\n    if let (Some(m), Some(k), Some(n)) = (op.m.as_i64(), op.k.as_i64(), op.n.as_i64())\n        && input_facts[0].is_plain()\n        && input_facts[1].is_plain()\n        && op.op.operating_dt == input_facts[0].datum_type\n        && op.op.operating_dt == input_facts[1].datum_type\n        && let Some(mmm) = tract_linalg::ops().mmm(\n            op.operating_dt,\n            Some(m as usize),\n            Some(k as usize),\n            Some(n as usize),\n        )\n        && mmm.quality() == ImplementationQuality::ManuallyOptimized\n    {\n        return Ok((ModePicker::Single, mmm.packings()[0].0.clone(), vec![(mmm, 0, None)]));\n    };\n\n    let mut impls = list_impls(model, node, op)?;\n    ensure!(impls.len() > 0);\n    fn score(mmm: &dyn MatMatMul) -> isize {\n        -(mmm.quality().cost() as isize * 1000) + mmm.dynamic_boost()\n    }\n    let wanted_quality = impls.iter().map(|(mmm, _, _)| score(&**mmm)).max().unwrap();\n    impls.retain(|(mmm, _, _)| score(&**mmm) == wanted_quality);\n    if impls.len() == 1 {\n        return Ok(single_strat(impls.remove(0)));\n    }\n    if op.n.is_one() {\n        let it =\n            impls.into_iter().max_by_key(|(m, _, pe)| (m.nr() == 1, pe.is_none(), m.mr())).unwrap();\n        return Ok(single_strat(it));\n    }\n    if op.n.as_i64().is_some_and(|n| n > 1) {\n        let it =\n            impls.into_iter().max_by_key(|(m, _, pe)| (pe.is_none(), m.nr() * m.mr())).unwrap();\n        return Ok(single_strat(it));\n    }\n    let mut grouped_by_left_packing = Vec::<(&dyn MMMInputFormat, Vec<_>)>::new();\n    'mmm: for (m, p, pe) in &impls {\n        let left_packing: &dyn MMMInputFormat =\n            pe.as_ref().map(|pe| &*pe.from).unwrap_or(&*m.packings()[*p].0);\n        for kit in &mut grouped_by_left_packing {\n            if let Some(merged) = kit.0.merge_with(left_packing) {\n                kit.0 = merged;\n                kit.1.push((m, p, pe));\n                continue 'mmm;\n            }\n        }\n        grouped_by_left_packing.push((left_packing, vec![(m, p, pe)]));\n    }\n    let (p, mmv, mmm) = grouped_by_left_packing\n        .iter()\n        .map(|(p, kit)| {\n            let best_for_mmv =\n                kit.iter().max_by_key(|(m, _, pe)| (m.nr() == 1, pe.is_none())).unwrap();\n            let best_for_mmm = kit.iter().max_by_key(|(m, _, _)| m.nr()).unwrap();\n            (p, best_for_mmv, best_for_mmm)\n        })\n        .max_by_key(|(_, mmv, mmm)| {\n            (mmv.0.nr() == 1 && mmm.0.nr() > 1, mmv.2.is_none(), mmm.0.mr(), mmm.0.nr())\n        })\n        .unwrap();\n\n    if mmm == mmv {\n        Ok((ModePicker::Single, clone_box(*p), vec![(mmv.0.clone(), *mmv.1, mmv.2.clone())]))\n    } else {\n        Ok((\n            ModePicker::VecVsMat,\n            clone_box(*p),\n            vec![(mmv.0.clone(), *mmv.1, mmv.2.clone()), (mmm.0.clone(), *mmm.1, mmm.2.clone())],\n        ))\n    }\n}\n\npub fn list_impls(\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &EinSumMatMul,\n) -> TractResult<Vec<Impl>> {\n    let (a_fact, b_fact) = model.node_input_facts(node.id)?.into_iter().collect_tuple().unwrap();\n    let a_dt = a_fact.datum_type;\n    let b_dt = b_fact.datum_type;\n\n    let a_weight: WeightType = if let Some(of) = a_fact.exotic_fact() {\n        if let Some(bqf) = of.downcast_ref::<BlockQuantFact>() {\n            WeightType::BlockQuant(bqf.format.clone())\n        } else {\n            bail!(\"Can not translate to matmul operand {a_fact:?}\");\n        }\n    } else {\n        a_dt.into()\n    };\n\n    let impls = tract_linalg::ops()\n        .mmm_impls()\n        .iter()\n        .filter(|mmm| {\n            op.acceptable_accumulators().contains(&mmm.internal_type())\n                && mmm.stores().contains(&op.operating_dt.unquantized())\n        })\n        .flat_map(move |mmm| {\n            mmm.packings().iter().enumerate().map(|(ix, p)| (mmm.clone(), ix, &p.0, &p.1))\n        })\n        .filter_map(|(m, p, pa, pb)| {\n            if pb.precursor().as_dt().is_none_or(|dt| dt != b_dt.unquantized()) {\n                return None;\n            }\n            if pa.precursor() == a_weight {\n                Some((m, p, None))\n            } else {\n                tract_linalg::ops()\n                    .panel_extractors()\n                    .iter()\n                    .find(|pe| pe.from.precursor() == a_weight && pe.to.dyn_eq(&**pa))\n                    .map(|pe| (m, p, Some(pe.clone())))\n            }\n        })\n        .collect_vec();\n    Ok(impls)\n}\n"
  },
  {
    "path": "core/src/ops/einsum/mod.rs",
    "content": "use std::borrow::Borrow;\nuse std::fmt::Debug;\n\nuse crate::internal::*;\nuse crate::ops::array::MultiBroadcastTo;\nuse crate::tract_data::itertools::Itertools;\n\nmod eval;\n\n#[cfg(feature = \"blas\")]\npub mod as_blas;\npub mod einsum_matmul;\npub mod kernel_selection;\npub mod prefix_matmul;\n\n#[cfg(test)]\nmod proptest;\n\nuse num_traits::One;\nuse tract_linalg::block_quant::{BlockQuantFact, PackedBlockQuantFact};\nuse tract_linalg::mmm::PackedExoticFact;\n\npub fn block_quant_aware_input_shape(fact: &TypedFact) -> TractResult<Cow<'_, [TDim]>> {\n    if fact.is_plain() {\n        return Ok(Cow::Borrowed(&*fact.shape));\n    }\n    let Some(exotic_fact) = fact.exotic_fact() else {\n        bail!(\"Datum fact is exotic, but no exotic fact was found.\")\n    };\n    if let Some(_bqf) = exotic_fact.downcast_ref::<BlockQuantFact>() {\n        Ok(Cow::Borrowed(&*fact.shape))\n    } else if let Some(pof) = exotic_fact.downcast_ref::<PackedBlockQuantFact>() {\n        Ok(Cow::Owned(\n            fact.shape.iter().cloned().chain(pof.shape.iter().map(|i| i.to_dim())).collect_vec(),\n        ))\n    } else if let Some(pof) = exotic_fact.downcast_ref::<PackedExoticFact>() {\n        Ok(Cow::Owned(\n            fact.shape.iter().cloned().chain([pof.mn.clone(), pof.k.to_dim()]).collect_vec(),\n        ))\n    } else {\n        bail!(\"Unsupported exotic fact {exotic_fact:?}\")\n    }\n}\n\n#[derive(Clone, Hash, PartialEq, Eq)]\npub struct EinSum {\n    pub axes: AxesMapping,\n    pub operating_dt: DatumType,\n    // if present, assume we're a binary op.\n    // 9 inputs are: A,B,bias, A0,Ascale, B0,BScale, C0,Cscale\n    pub q_params: Option<DatumType>,\n}\n\nimpl EinSum {\n    pub fn new(axes: AxesMapping, operating_dt: DatumType) -> EinSum {\n        EinSum { axes, operating_dt, q_params: None }\n    }\n\n    pub fn newq(axes: AxesMapping, operating_dt: DatumType, output_type: DatumType) -> EinSum {\n        EinSum { axes, operating_dt, q_params: Some(output_type) }\n    }\n\n    pub fn actual_input_shapes_from_facts<'m>(\n        &self,\n        inputs: &'m [impl Borrow<TypedFact>],\n    ) -> TractResult<TVec<Cow<'m, [TDim]>>> {\n        ensure!(inputs.len() == self.axes.input_count());\n        let shapes: TVec<Cow<[TDim]>> = inputs\n            .iter()\n            .map(|t| block_quant_aware_input_shape(t.borrow()))\n            .collect::<TractResult<_>>()?;\n        ensure!(\n            shapes.iter().enumerate().all(|(ix, fact)| fact.len() == self.axes.rank(InOut::In(ix)))\n        );\n        Ok(shapes)\n    }\n\n    #[allow(unused_variables)]\n    pub(crate) fn propagate_axis(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        io: InOut,\n        axis: usize,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let mut new_axis = self.axes.axis((io, axis))?.clone();\n        let repr = new_axis.repr;\n        let mut patch = TypedModelPatch::new(format!(\"Propagate axis {}\", new_axis.repr));\n        let mut taps = tvec!();\n        for (ix, input) in node.inputs.iter().enumerate() {\n            let mut tap = patch.tap_model(model, *input)?;\n            if new_axis.inputs[ix].len() > 1 {\n                return Ok(None); // FIXME maybe\n            } else if new_axis.inputs[ix].is_empty() {\n                let insert_at = self.axes.rank(InOut::In(ix));\n                tap = patch.wire_node(\n                    format!(\"{}.prop_axis.{}.input_{}\", &node.name, new_axis.repr, ix),\n                    AxisOp::Add(insert_at),\n                    &[tap],\n                )?[0];\n                new_axis.inputs[ix].push(insert_at);\n            }\n            taps.push(tap);\n        }\n        let must_rm_axis: Option<usize> = if new_axis.outputs[0].len() == 0 {\n            let insert_at = self.axes.rank(InOut::Out(0));\n            new_axis.outputs[0].push(insert_at);\n            Some(insert_at)\n        } else {\n            None\n        };\n        let new_expr = self\n            .axes\n            .iter_all_axes()\n            .map(|it| if it.repr == new_axis.repr { new_axis.clone() } else { it.clone() })\n            .collect_vec();\n        let axes = AxesMapping::new(node.inputs.len(), 1, new_expr)?;\n        let mut wire = patch.wire_node(&node.name, Self { axes, ..self.clone() }, &taps)?;\n        if let Some(position) = must_rm_axis {\n            wire = patch.wire_node(\n                format!(\"{}.prop_axis.{}.output\", &node.name, repr),\n                AxisOp::Rm(position),\n                &wire,\n            )?;\n        }\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        Ok(Some(patch))\n    }\n\n    pub fn acceptable_accumulators(&self) -> TVec<DatumType> {\n        if self.operating_dt.is_integer() {\n            tvec!(i32::datum_type())\n        } else if self.operating_dt == f16::datum_type() {\n            tvec!(f16::datum_type(), f32::datum_type())\n        } else {\n            tvec!(self.operating_dt)\n        }\n    }\n}\n\nimpl Debug for EinSum {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"EinSum {} ({:?})\", self.axes, self.operating_dt)\n    }\n}\n\nimpl Op for EinSum {\n    fn name(&self) -> StaticName {\n        \"EinSum\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut info = vec![format!(\"{} ({:?})\", self.axes, self.operating_dt)];\n        if let Some(qp) = self.q_params {\n            info.push(format!(\"Quantized output: {qp:?}\"));\n        }\n        Ok(info)\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for EinSum {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        if inputs.iter().all(|i| i.datum_type().is_number() && i.is_plain()) {\n            let mut adhoc_model = TypedModel::default();\n            let mut wires = tvec!();\n            for (ix, input) in inputs.iter().enumerate() {\n                let fact = TypedFact::shape_and_dt_of(input);\n                let wire = adhoc_model.add_source(format!(\"input.{ix}\"), fact)?;\n                wires.push(wire);\n            }\n            let output = adhoc_model.wire_node(\"einsum\", self.clone(), &wires)?;\n            adhoc_model.select_output_outlets(&output)?;\n            let opti = adhoc_model.into_optimized()?;\n            if opti.nodes.iter().all(|node| !node.op_is::<Self>()) {\n                return opti.into_runnable()?.run(inputs);\n            }\n        }\n\n        let output = if let Some(qp) = self.q_params {\n            eval::eval_q(&self.axes, qp, inputs)\n        } else {\n            dispatch_numbers!(eval::eval_t(self.operating_dt)(&self.axes, inputs))\n        }?;\n        Ok(tvec!(output.into_tvalue()))\n    }\n}\n\nimpl TypedOp for EinSum {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let shapes = self.actual_input_shapes_from_facts(inputs)?;\n        for i in 0..inputs.len() {\n            ensure!(shapes[i].len() == self.axes.rank(InOut::In(i)));\n        }\n        for axis in self.axes.iter_all_axes() {\n            assert!(\n                shapes\n                    .iter()\n                    .enumerate()\n                    .flat_map(|(slot, shape)| axis.inputs[slot].iter().map(|a| &shape[*a]))\n                    .try_fold(TDim::one(), |a, b| TDim::broadcast(a, b.clone()))\n                    .is_ok()\n            );\n        }\n        if let Some(qp) = self.q_params {\n            ensure!(inputs.len() == 9);\n            Ok(tvec!(qp.fact(eval::output_shape(&self.axes, &shapes[0..2])?)))\n        } else {\n            Ok(tvec!(TypedFact::dt_shape(\n                self.operating_dt,\n                eval::output_shape(&self.axes, &shapes)?\n            )))\n        }\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        _outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let mut axes = self.axes.clone();\n        for (slot, i) in inputs.iter().enumerate() {\n            if i.is_exotic()\n                && (i.exotic_fact().is_some_and(|of| {\n                    of.is::<PackedExoticFact>() || of.is::<PackedBlockQuantFact>()\n                }))\n            {\n                axes = axes\n                    .remove_axis_occurency(InOut::In(slot), i.rank())?\n                    .remove_axis_occurency(InOut::In(slot), i.rank())?;\n            }\n        }\n        Ok(axes)\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let shapes = self.actual_input_shapes_from_facts(inputs)?;\n        let oshape = eval::output_shape(&self.axes, &shapes)?;\n        let ks = self\n            .axes\n            .iter_all_axes()\n            .filter(|axis| axis.outputs[0].len() == 0)\n            .map(|axis| {\n                axis.inputs\n                    .iter()\n                    .enumerate()\n                    .flat_map(|(ix, axes)| {\n                        axes.iter()\n                            .map(|axis| shapes[ix][*axis].clone())\n                            .collect::<TVec<_>>()\n                            .into_iter()\n                    })\n                    .find(|d| !d.is_one())\n                    .unwrap_or_else(|| 1.to_dim())\n            })\n            .product::<TDim>();\n        Ok(tvec!((Cost::FMA(self.operating_dt), oshape.iter().product::<TDim>() * ks)))\n    }\n\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        model: &TypedModel,\n        node: &TypedNode,\n        prefix: &str,\n        inputs: &[OutletId],\n        output_axis: usize,\n        _start: &TDim,\n        _end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        let facts = model.node_input_facts(node.id)?;\n        let axis = self.axes.axis((InOut::Out(0), output_axis))?;\n        if facts\n            .iter()\n            .enumerate()\n            .any(|(slot, fact)| axis.inputs[slot].len() > 0 && fact.is_exotic())\n        {\n            Ok(None)\n        } else {\n            patch.wire_node(prefix, self.clone(), inputs).map(Some)\n        }\n    }\n\n    #[allow(unused_variables)]\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        let (mut inputs, mut outputs) = self.axes.to_strs();\n        let interface: &mut String = match io {\n            InOut::In(i) => &mut inputs[i],\n            InOut::Out(o) => &mut outputs[o],\n        };\n        let mut axes: Vec<char> = interface.chars().collect();\n        match change {\n            AxisOp::Rm(rm) => {\n                axes.remove(*rm);\n            }\n            AxisOp::Add(add) => axes.insert(*add, self.axes.available_label()),\n            AxisOp::Move(from, to) => {\n                let c = axes.remove(*from);\n                axes.insert(*to, c);\n            }\n            _ => {\n                return Ok(None);\n            }\n        };\n        *interface = axes.into_iter().collect();\n        let axes = AxesMapping::from_strs(&inputs, &outputs)?;\n        Ok(Some(AxisChangeConsequence {\n            substitute_op: Some(Box::new(EinSum { axes, ..self.clone() })),\n            wire_changes: tvec!((io, change.clone())),\n        }))\n    }\n\n    fn declutter_with_session(\n        &self,\n        session: &mut crate::optim::OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if let Some(patch) = declutter_reshape_folding_input_axis(self, session, model, node)? {\n            return Ok(Some(patch));\n        }\n        if let Some(patch) = declutter_broadcast(self, session, model, node)? {\n            return Ok(Some(patch));\n        }\n        Ok(None)\n    }\n\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        rule_if!(\n            (self.q_params.is_none() && node.inputs.len() == 2)\n                || (self.q_params.is_some() && node.inputs.len() == 9)\n        );\n        einsum_matmul::detect_rule(&(), model, node, &node.name, self)\n    }\n\n    as_op!();\n}\n\nfn declutter_reshape_folding_input_axis(\n    op: &EinSum,\n    _session: &mut crate::optim::OptimizerSession,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    for (slot, prec) in node.inputs.iter().map(|n| model.node(n.node)).enumerate() {\n        let Some(&AxisOp::Reshape(at, ref from, ref to)) = prec.op_as() else { continue };\n        if to.len() > 1 {\n            continue;\n        }\n        let mut axes = op.axes.clone();\n        let extra_labels = axes.available_labels().take(from.len() - 1).collect_vec();\n        // add a temporary input to axes to hold the extra axes\n        let extra_input = node.inputs.len();\n        axes = axes.with_extra_input(extra_input)?;\n        for label in &extra_labels {\n            axes = axes.with_extra_axis(*label, InOut::In(extra_input), 0)?;\n        }\n        let folded_axis = op.axes.axis((InOut::In(slot), at))?;\n        if folded_axis.outputs[0].len() > 1 {\n            return Ok(None);\n        };\n        let mut patch = TypedModelPatch::default();\n        let mut taps = patch.taps(model, &node.inputs)?;\n        for (input, tap) in taps.iter_mut().enumerate() {\n            if folded_axis.inputs[input].len() == 0 {\n                continue;\n            };\n            if folded_axis.inputs[input].len() > 1 {\n                return Ok(None);\n            };\n            let pos = folded_axis.inputs[input][0];\n            for label in &extra_labels {\n                axes = axes.with_extra_axis_occurency(*label, InOut::In(input), pos)?;\n            }\n            *tap = patch.wire_node(\n                format!(\"{}.reshape_folded_input_{}\", node.name, input),\n                AxisOp::Reshape(pos, to.clone(), from.clone()),\n                &[*tap],\n            )?[0];\n        }\n        if folded_axis.outputs[0].len() == 1 {\n            let pos = folded_axis.outputs[0][0];\n            for label in &extra_labels {\n                axes = axes.with_extra_axis_occurency(*label, InOut::Out(0), pos)?;\n            }\n        }\n        axes = axes.remove_slot(InOut::In(extra_input))?;\n        let mut wire = patch.wire_node(&node.name, EinSum { axes, ..op.clone() }, &taps)?;\n        if folded_axis.outputs[0].len() == 1 {\n            let pos = folded_axis.outputs[0][0];\n            wire = patch.wire_node(\n                format!(\"{}.reshape_folded_output\", node.name),\n                AxisOp::Reshape(pos, from.clone(), to.clone()),\n                &wire,\n            )?;\n        }\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n\nfn declutter_broadcast(\n    op: &EinSum,\n    _session: &mut crate::optim::OptimizerSession,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    for (ix, outlet) in node.inputs.iter().enumerate() {\n        let prec = model.node(outlet.node);\n        if prec.op_is::<MultiBroadcastTo>() && prec.outputs[0].successors.len() == 1 {\n            let mut patch = TypedModelPatch::default();\n            let mut wires = patch.taps(model, &node.inputs)?;\n            wires[ix] = patch.tap_model(model, prec.inputs[0])?;\n            let wire = patch.wire_node(&node.name, op.clone(), &wires)?[0];\n            patch.shunt_outside(model, node.id.into(), wire)?;\n            return Ok(Some(patch));\n        }\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "core/src/ops/einsum/prefix_matmul.rs",
    "content": "use tract_data::itertools::Itertools;\nuse tract_linalg::Scaler;\nuse tract_ndarray::Ix2;\nuse tract_num_traits::One;\n\nuse super::einsum_matmul::EinSumMatMul;\nuse super::eval::dequant_inputs;\nuse crate::internal::*;\nuse crate::ops::einsum::block_quant_aware_input_shape;\nuse crate::ops::konst::Const;\n\n#[derive(Debug, Default)]\npub struct EinSumToPrefixMatmulCtx {\n    pub ensure_strict_matmul_semantic: bool,\n}\n\npub fn rewrite_einsum_to_prefix_matmul(\n    model: &mut TypedModel,\n    ensure_strict_matmul_semantic: bool,\n) -> TractResult<()> {\n    super::einsum_matmul::merge_consecutive_same_role_axes(model)?;\n    super::einsum_matmul::detect_all(model)?;\n    let ctx = EinSumToPrefixMatmulCtx { ensure_strict_matmul_semantic };\n    Rewriter::default().with_rule_for(\"einsum-to-prefix-matmul\", rule).rewrite(&ctx, model)\n}\n\nfn rule(\n    ctx: &EinSumToPrefixMatmulCtx,\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &EinSumMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    // F: 2 inputs\n    // Q: 9 inputs\n    let is_fp_mm = op.q_params.is_none() && node.inputs.len() == 2;\n    let is_q_mm = op.q_params.is_some() && node.inputs.len() == 9;\n    rule_if!(is_fp_mm || is_q_mm);\n    rule_if!(\n        op.q_params.is_none()\n            || model.node_input_facts(node.id)?.iter().skip(3).all(|i| i.konst.is_some())\n    );\n    let prefix: String = op\n        .axes\n        .iter_all_axes()\n        .filter(|a| ![op.m_axis, op.k_axis, op.n_axis].contains(&a.repr))\n        .map(|a| a.repr)\n        .collect();\n    let mut patch = TypedModelPatch::default();\n    let inputs = patch.taps(model, &node.inputs)?;\n    let mut wire = tvec!(inputs[0], inputs[1]);\n\n    let (m, k, n) = (op.m_axis, op.k_axis, op.n_axis);\n    let a_order_es: String = op.axes.axes(InOut::In(0)).map(|a| a.repr).collect();\n    let a_order_mm = format!(\"{prefix}{m}{k}\");\n    let a_order_mm_t = format!(\"{prefix}{k}{m}\");\n    let a_transform =\n        format!(\"{a_order_es}->{a_order_mm}\").parse::<AxesMapping>()?.translate_to_axis_ops()?;\n    let a_transform_t =\n        format!(\"{a_order_es}->{a_order_mm_t}\").parse::<AxesMapping>()?.translate_to_axis_ops()?;\n    let transpose_a = a_transform.len() > a_transform_t.len();\n    let a_transform = if transpose_a { a_transform_t } else { a_transform };\n    let name = format!(\"{node_name}.fix_a\");\n    for op in a_transform {\n        wire[0] = patch.wire_node(&name, op, &[wire[0]])?[0];\n    }\n    // terrible hack to maintain exotic fact through eager propagation of constant through the\n    // axes transformation\n    if let Some(op) = patch.node_mut(wire[0].node).op_as_mut::<Const>() {\n        *op = Const::new_with_opt_exotic_fact(\n            op.val().clone(),\n            model.outlet_fact(node.inputs[0])?.exotic_fact.clone(),\n        )?;\n    }\n    patch\n        .outlet_fact_mut(wire[0])?\n        .exotic_fact\n        .clone_from(&model.outlet_fact(node.inputs[0])?.exotic_fact);\n    // end of hack\n\n    let b_order_es: String = op.axes.axes(InOut::In(1)).map(|a| a.repr).collect();\n    let b_order_mm = format!(\"{prefix}{k}{n}\");\n    let b_order_mm_t = format!(\"{prefix}{n}{k}\");\n    let b_transform =\n        format!(\"{b_order_es}->{b_order_mm}\").parse::<AxesMapping>()?.translate_to_axis_ops()?;\n    let b_transform_t =\n        format!(\"{b_order_es}->{b_order_mm_t}\").parse::<AxesMapping>()?.translate_to_axis_ops()?;\n    let transpose_b = b_transform.len() > b_transform_t.len();\n    let b_transform = if transpose_b { b_transform_t } else { b_transform };\n    let name = format!(\"{node_name}.fix_b\");\n    for op in b_transform {\n        wire[1] = patch.wire_node(&name, op, &[wire[1]])?[0];\n    }\n\n    let c_order_es: String = op.axes.axes(InOut::Out(0)).map(|a| a.repr).collect();\n    let c_order_mm = format!(\"{prefix}{m}{n}\");\n    let c_order_mm_t = format!(\"{prefix}{n}{m}\");\n    let c_transform =\n        format!(\"{c_order_mm}->{c_order_es}\").parse::<AxesMapping>()?.translate_to_axis_ops()?;\n    let c_transform_t =\n        format!(\"{c_order_mm_t}->{c_order_es}\").parse::<AxesMapping>()?.translate_to_axis_ops()?;\n    let transpose_c = c_transform.len() > c_transform_t.len();\n    let c_transform = if transpose_c { c_transform_t } else { c_transform };\n    let quantize_output = if let Some(qp) = op.q_params {\n        let qparams: Vec<&Tensor> = inputs[3..9]\n            .iter()\n            .map(|f| {\n                patch\n                    .outlet_fact(*f)?\n                    .konst\n                    .as_deref()\n                    .context(\"Can only translate fixed scalar quantization\")\n            })\n            .try_collect()?;\n        Some(qp.with_qparams(QParams::ZpScale {\n            zero_point: qparams[4].cast_to_scalar::<i32>()?,\n            scale: qparams[5].cast_to_scalar::<f32>()?,\n        }))\n    } else {\n        None\n    };\n\n    let operating_dt = if ctx.ensure_strict_matmul_semantic {\n        let input_facts = model.node_input_facts(node.id)?;\n        let a_dt = input_facts[0].datum_type;\n        let b_dt = input_facts[1].datum_type;\n        let operating_dt = quantize_output.unwrap_or(op.operating_dt);\n        let a_plain = input_facts[0].is_plain();\n        let b_plain = input_facts[1].is_plain();\n        let allowed_dt = matmul_semantic_output_dt(&a_dt, a_plain, &b_dt, b_plain);\n\n        ensure!(\n            operating_dt == allowed_dt,\n            format!(\n                \"Strict matmul semantic require operating_dt to be {allowed_dt:?} \\\n                for (a: {a_dt:?}, b:{b_dt:?}) but got {:?}.\",\n                op.operating_dt\n            )\n        );\n\n        None\n    } else {\n        Some(op.operating_dt)\n    };\n\n    wire = patch.wire_node(\n        node_name,\n        PrefixMatMul { transpose_a, transpose_b, transpose_c, quantize_output, operating_dt },\n        &wire,\n    )?;\n\n    for (ix, op) in c_transform.into_iter().enumerate() {\n        wire = patch.wire_node(format!(\"{node_name}.fix_c.{ix}\"), op, &wire)?;\n    }\n    patch.shunt_outside(model, node.id.into(), wire[0])?;\n    Ok(Some(patch))\n}\n\nfn matmul_semantic_output_dt(\n    a_dt: &DatumType,\n    a_plain: bool,\n    b_dt: &DatumType,\n    b_plain: bool,\n) -> DatumType {\n    if a_plain && a_dt.is_number() {\n        *a_dt\n    } else if b_plain && b_dt.is_number() {\n        *b_dt\n    } else if a_dt.is_number() {\n        *a_dt\n    } else if b_dt.is_number() {\n        *b_dt\n    } else {\n        f32::datum_type()\n    }\n}\n\n#[derive(Clone, Debug, Copy, PartialEq, Eq)]\npub struct PrefixMatMul {\n    pub transpose_a: bool,\n    pub transpose_b: bool,\n    pub transpose_c: bool,\n    pub quantize_output: Option<DatumType>,\n    pub operating_dt: Option<DatumType>,\n}\n\nimpl PrefixMatMul {\n    fn output_shape<D: DimLike + One>(&self, a: &[D], b: &[D]) -> TVec<D> {\n        let rank = a.len();\n        let mut output: TVec<D> = (0..rank - 2)\n            .map(|ix| if a[ix].is_one() { b[ix].clone() } else { a[ix].clone() })\n            .collect();\n        output.push(a[rank - 2 + self.transpose_a as usize].clone());\n        output.push(b[rank - 2 + !self.transpose_b as usize].clone());\n        if self.transpose_c {\n            output.swap(rank - 2, rank - 1);\n        }\n        output\n    }\n\n    fn mm<Acc: Datum + tract_ndarray::LinalgScalar>(\n        &self,\n        acc: &mut Tensor,\n        a: &Tensor,\n        b: &Tensor,\n    ) -> TractResult<()> {\n        use crate::ndarray::Dimension;\n        let casted_a = a.cast_to::<Acc>()?;\n        let a = casted_a.to_plain_array_view::<Acc>()?;\n        let casted_b = b.cast_to::<Acc>()?;\n        let b = casted_b.to_plain_array_view::<Acc>()?;\n        let mut c_plain = acc.try_as_plain_mut()?;\n        let mut c = c_plain.to_array_view_mut::<Acc>()?;\n        for prefix in tract_ndarray::indices(&c.shape()[..c.ndim() - 2]) {\n            let mut a = a.view();\n            let mut b = b.view();\n            let mut c = c.view_mut();\n            for &d in prefix.slice().iter() {\n                a.index_axis_inplace(tract_ndarray::Axis(0), d.min(a.shape()[0] - 1));\n                b.index_axis_inplace(tract_ndarray::Axis(0), d.min(b.shape()[0] - 1));\n                c.index_axis_inplace(tract_ndarray::Axis(0), d);\n            }\n            let a = a.into_dimensionality::<Ix2>().unwrap();\n            let b = b.into_dimensionality::<Ix2>().unwrap();\n            let mut c = c.into_dimensionality::<Ix2>().unwrap();\n            let a = if self.transpose_a { a.t() } else { a };\n            let b = if self.transpose_b { b.t() } else { b };\n            if self.transpose_c { c.assign(&b.t().dot(&a.t())) } else { c.assign(&a.dot(&b)) }\n        }\n        Ok(())\n    }\n}\n\nimpl Op for PrefixMatMul {\n    fn name(&self) -> StaticName {\n        \"PrefixMatMul\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\n            \"transpose_a: {} transpose_b: {} transpose_c: {} q: {:?}\",\n            self.transpose_a, self.transpose_b, self.transpose_c, self.quantize_output\n        )])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for PrefixMatMul {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let c_dt = self.operating_dt.unwrap_or_else(|| {\n            let a_dt = inputs[0].datum_type();\n            let b_dt = inputs[1].datum_type();\n            matmul_semantic_output_dt(&a_dt, inputs[0].is_plain(), &b_dt, inputs[1].is_plain())\n        });\n\n        let inputs = dequant_inputs(c_dt, inputs)?;\n\n        let output_shape = self.output_shape(inputs[0].shape(), inputs[1].shape());\n\n        if let Some(qp) = self.quantize_output {\n            let mut acc = Tensor::zero_dt(i32::datum_type(), &output_shape)?;\n            let mut a_i32 = inputs[0].cast_to::<i32>()?.into_owned();\n            a_i32\n                .try_as_plain_mut()?\n                .as_slice_mut::<i32>()?\n                .iter_mut()\n                .for_each(|x| *x -= inputs[0].datum_type().zp_scale().0);\n            let mut b_i32 = inputs[1].cast_to::<i32>()?.into_owned();\n            b_i32\n                .try_as_plain_mut()?\n                .as_slice_mut::<i32>()?\n                .iter_mut()\n                .for_each(|x| *x -= inputs[1].datum_type().zp_scale().0);\n            self.mm::<i32>(&mut acc, &a_i32, &b_i32)?;\n            let scale = inputs[0].datum_type().zp_scale().1 * inputs[1].datum_type().zp_scale().1\n                / qp.zp_scale().1;\n            let scaler = Scaler::new(scale, tract_linalg::mmm::RoundingPolicy::Even);\n            acc.to_plain_array_view_mut::<i32>()?.iter_mut().for_each(|x| *x = *x * scaler);\n            let mut c: Tensor = acc.cast_to_dt(qp.unquantized())?.into_owned();\n            unsafe { c.set_datum_type(qp) };\n            Ok(tvec!(c.into_tvalue()))\n        } else {\n            let mut c = Tensor::zero_dt(c_dt, &output_shape)?;\n            dispatch_floatlike!(Self::mm(c_dt)(self, &mut c, &inputs[0], &inputs[1]))?;\n            Ok(tvec!(c.into_tvalue()))\n        }\n    }\n}\n\nimpl TypedOp for PrefixMatMul {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let [a, b] = inputs else {\n            bail!(\"Expects 2 inputs\");\n        };\n        let a_shape = block_quant_aware_input_shape(a)?;\n        let b_shape = block_quant_aware_input_shape(b)?;\n        let dt = self.quantize_output.or(self.operating_dt).unwrap_or(matmul_semantic_output_dt(\n            &a.datum_type,\n            a.is_plain(),\n            &b.datum_type,\n            b.is_plain(),\n        ));\n        Ok(tvec!(dt.fact(self.output_shape(&a_shape, &b_shape))))\n    }\n\n    as_op!();\n}\n\n#[cfg(test)]\nmod test {\n    use crate::ops::einsum::EinSum;\n\n    use super::*;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use proptest::test_runner::{TestCaseResult, TestRunner};\n    use tract_data::itertools::Itertools;\n\n    pub fn tensor(shape: &[usize]) -> BoxedStrategy<Tensor> {\n        let shape = shape.to_vec();\n        let len = shape.iter().product::<usize>();\n        vec((-10i8..=10i8).prop_map(|i| i as f32), len..=len)\n            .prop_map(move |vec| tensor1(&vec).into_shape(&shape).unwrap())\n            .boxed()\n    }\n\n    fn full_shapes(e: &AxesMapping) -> BoxedStrategy<(Vec<usize>, Vec<usize>)> {\n        let e = e.clone();\n        let inputs_axes = e\n            .iter_all_axes()\n            .filter(|axis| axis.inputs[0].len() + axis.inputs[1].len() > 0)\n            .cloned()\n            .collect_vec();\n        let dims = vec![2usize..6; inputs_axes.len()];\n        dims.prop_map(move |dims| {\n            let a: Vec<usize> = e\n                .axes(InOut::In(0))\n                .map(|a| dims[inputs_axes.iter().position(|b| a == b).unwrap()])\n                .collect_vec();\n            let b: Vec<usize> = e\n                .axes(InOut::In(1))\n                .map(|a| dims[inputs_axes.iter().position(|b| a == b).unwrap()])\n                .collect_vec();\n            (a, b)\n        })\n        .boxed()\n    }\n\n    fn test_expr(expr: &str) -> TestCaseResult {\n        let expr = expr.to_string();\n        let mut runner = TestRunner::default();\n        let axes: AxesMapping = expr.parse().unwrap();\n        fn is_k(axes: &AxesMapping, input: usize, position: usize) -> bool {\n            let axis = axes.axis((InOut::In(input), position)).unwrap();\n            axis.inputs[1 - input].len() == 1 && axis.outputs[0].len() == 0\n        }\n        fn is_disapearing_axis(axes: &AxesMapping, input: usize, position: usize) -> bool {\n            let axis = axes.axis((InOut::In(input), position)).unwrap();\n            axis.outputs[0].len() == 0\n        }\n        let cases = full_shapes(&axes)\n            .prop_flat_map(|(a, b)| {\n                (\n                    a.iter()\n                        .enumerate()\n                        .map(|(ix, d)| {\n                            if is_k(&axes, 0, ix) {\n                                prop_oneof![Just(*d)].boxed()\n                            } else if is_disapearing_axis(&axes, 0, ix) {\n                                Just(1).boxed()\n                            } else {\n                                prop_oneof![Just(1usize), Just(*d)].boxed()\n                            }\n                        })\n                        .collect_vec(),\n                    b.iter()\n                        .enumerate()\n                        .map(|(ix, d)| {\n                            if is_k(&axes, 1, ix) {\n                                prop_oneof![Just(*d)].boxed()\n                            } else if is_disapearing_axis(&axes, 1, ix) {\n                                Just(1).boxed()\n                            } else {\n                                prop_oneof![Just(1usize), Just(*d)].boxed()\n                            }\n                        })\n                        .collect_vec(),\n                )\n            })\n            .prop_flat_map(|(a_shape, b_shape)| (tensor(&a_shape), tensor(&b_shape)))\n            .prop_map(|(a, b)| EinSumProblem { expr: expr.clone(), a, b });\n        runner.run(&cases, |pb| pb.check().map_err(|e| TestCaseError::fail(e.to_string())))?;\n        Ok(())\n    }\n\n    #[derive(Debug, Clone, PartialEq, Eq)]\n    struct EinSumProblem {\n        expr: String,\n        a: Tensor,\n        b: Tensor,\n    }\n\n    impl EinSumProblem {\n        fn check(&self) -> TractResult<()> {\n            let mut model = TypedModel::default();\n            let sa = model.add_source(\"a\", f32::fact(self.a.shape()))?;\n            let sb = model.add_source(\"b\", f32::fact(self.b.shape()))?;\n            let einsum = model.wire_node(\n                \"einsum\",\n                EinSum::new(self.expr.parse().unwrap(), f32::datum_type()),\n                &[sa, sb],\n            )?;\n            model.select_output_outlets(&einsum)?;\n            let a = self.a.clone().into_tvalue();\n            let b = self.b.clone().into_tvalue();\n            let inputs = tvec!(a, b);\n            let reference = TypedRunnableModel::new(model.clone())?.run(inputs.clone())?.remove(0);\n            rewrite_einsum_to_prefix_matmul(&mut model, true)?;\n            assert!(model.nodes.iter().all(|n| !n.op_is::<EinSum>()));\n            let test = TypedRunnableModel::new(model)?.run(inputs)?.remove(0);\n            reference.close_enough(&test, true)\n        }\n    }\n\n    #[rustfmt::skip] #[test] fn prop_mk_kn_mn() -> TestCaseResult { test_expr(\"mk,kn->mn\") }\n    #[rustfmt::skip] #[test] fn prop_km_kn_mn() -> TestCaseResult { test_expr(\"km,kn->mn\") }\n    #[rustfmt::skip] #[test] fn prop_mk_nk_mn() -> TestCaseResult { test_expr(\"mk,nk->mn\") }\n    #[rustfmt::skip] #[test] fn prop_mk_kn_nm() -> TestCaseResult { test_expr(\"mk,kn->nm\") }\n    #[rustfmt::skip] #[test] fn prop_k_kn_mn() -> TestCaseResult { test_expr(\"k,kn->mn\") }\n    #[rustfmt::skip] #[test] fn prop_mk_k_mn() -> TestCaseResult { test_expr(\"mk,k->mn\") }\n    #[rustfmt::skip] #[test] fn prop_m_n_mn() -> TestCaseResult { test_expr(\"m,n->mn\") }\n    #[rustfmt::skip] #[test] fn prop_amk_akn_amn() -> TestCaseResult { test_expr(\"amk,akn->amn\") }\n    #[rustfmt::skip] #[test] fn prop_mk_akn_amn() -> TestCaseResult { test_expr(\"mk,akn->amn\") }\n    #[rustfmt::skip] #[test] fn prop_btgi_gih_tgh() -> TestCaseResult { test_expr(\"btgi,gih->tgh\") }\n    #[rustfmt::skip] #[test] fn prop_tgi_gih_btgh() -> TestCaseResult { test_expr(\"tgi,gih->btgh\") }\n\n    #[test]\n    fn k_kn_mn_0() -> TractResult<()> {\n        EinSumProblem {\n            expr: \"k,kn->mn\".to_string(),\n            a: tensor1(&[0f32, 0f32]),\n            b: tensor2(&[[0f32, 0.], [0., 0.]]),\n        }\n        .check()\n    }\n\n    #[test]\n    fn mk_k_mn_0() -> TractResult<()> {\n        EinSumProblem {\n            expr: \"mk,k->mn\".to_string(),\n            a: Tensor::zero::<f32>(&[2, 2]).unwrap(),\n            b: Tensor::zero::<f32>(&[2]).unwrap(),\n        }\n        .check()\n    }\n\n    #[test]\n    fn mk_k_mn_1() -> TractResult<()> {\n        EinSumProblem {\n            expr: \"mk,k->mn\".to_string(),\n            a: Tensor::zero::<f32>(&[1, 2]).unwrap(),\n            b: Tensor::zero::<f32>(&[2]).unwrap(),\n        }\n        .check()\n    }\n\n    #[test]\n    fn mk_kn_nm_0() -> TractResult<()> {\n        EinSumProblem {\n            expr: \"mk,kn->mn\".to_string(),\n            a: Tensor::zero::<f32>(&[3, 2]).unwrap(),\n            b: Tensor::zero::<f32>(&[2, 2]).unwrap(),\n        }\n        .check()\n    }\n\n    #[test]\n    fn amk_akn_amn_0() -> TractResult<()> {\n        EinSumProblem {\n            expr: \"amk,akn->amn\".to_string(),\n            a: Tensor::zero::<f32>(&[1, 1, 2]).unwrap(),\n            b: Tensor::zero::<f32>(&[1, 2, 1]).unwrap(),\n        }\n        .check()\n    }\n\n    #[test]\n    fn amk_akn_amn_1() -> TractResult<()> {\n        EinSumProblem {\n            expr: \"amk,akn->amn\".to_string(),\n            a: Tensor::zero::<f32>(&[2, 1, 2]).unwrap(),\n            b: Tensor::zero::<f32>(&[1, 2, 1]).unwrap(),\n        }\n        .check()\n    }\n\n    #[test]\n    fn amk_akn_amn_2() -> TractResult<()> {\n        EinSumProblem {\n            expr: \"amk,akn->amn\".to_string(),\n            a: Tensor::zero::<f32>(&[1, 1, 2]).unwrap(),\n            b: Tensor::zero::<f32>(&[2, 2, 2]).unwrap(),\n        }\n        .check()\n    }\n\n    #[test]\n    fn amk_akn_amn_3() -> TractResult<()> {\n        EinSumProblem {\n            expr: \"amk,akn->amn\".to_string(),\n            a: Tensor::zero::<f32>(&[1, 1, 2]).unwrap(),\n            b: Tensor::zero::<f32>(&[2, 2, 1]).unwrap(),\n        }\n        .check()\n    }\n\n    #[test]\n    fn km_anbck_bmn_0() -> TractResult<()> {\n        EinSumProblem {\n            expr: \"km,anbck->bmn\".to_string(),\n            a: Tensor::zero::<f32>(&[2, 1]).unwrap(),\n            b: Tensor::zero::<f32>(&[1, 1, 1, 1, 2]).unwrap(),\n        }\n        .check()\n    }\n\n    #[test]\n    fn q() -> TractResult<()> {\n        let qp = QParams::ZpScale { zero_point: 0, scale: 0.1 };\n        let op = EinSum {\n            axes: \"mk,kn,m,,,,,,->mn\".parse()?,\n            operating_dt: i32::datum_type(),\n            q_params: Some(DatumType::QI8(qp)),\n        };\n        let mut model = TypedModelPatch::default();\n        let inputs = [\n            model.add_source(\"a\", DatumType::QI8(qp).fact([3, 2]))?,\n            model.add_source(\"b\", DatumType::QI8(qp).fact([2, 4]))?,\n            model.add_source(\"bias\", i32::datum_type().fact([3]))?,\n            model.add_const(\"a0\", tensor0(qp.zp_scale().0))?,\n            model.add_const(\"a_scale\", tensor0(qp.zp_scale().1))?,\n            model.add_const(\"b0\", tensor0(qp.zp_scale().0))?,\n            model.add_const(\"b_scale\", tensor0(qp.zp_scale().1))?,\n            model.add_const(\"c0\", tensor0(qp.zp_scale().0))?,\n            model.add_const(\"c_scale\", tensor0(qp.zp_scale().1))?,\n        ];\n        let wire = model.wire_node(\"einsum\", op.clone(), &inputs)?;\n        model.select_output_outlets(&wire)?;\n        rewrite_einsum_to_prefix_matmul(&mut model, true)?;\n        assert!(model.nodes.iter().all(|n| !n.op_is::<EinSum>()));\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/einsum/proptest.rs",
    "content": "use std::fmt;\n\nuse crate::internal::*;\nuse proptest::prelude::*;\nuse proptest::strategy::BoxedStrategy;\nuse tract_ndarray::ArrayD;\n\nuse crate::axes::AxesMapping;\n\nuse super::EinSum;\n\n#[derive(Clone)]\nstruct BinEinsumProblem {\n    expr: AxesMapping,\n    a: Tensor,\n    b: Tensor,\n    a_constant: bool,\n    b_constant: bool,\n    unicast_add_constant: Option<Tensor>,\n}\n\nimpl std::fmt::Debug for BinEinsumProblem {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{} A:{:?} B:{:?} a_constant:{:?} b_constant:{:?} unicast_add_constant:{:?}\",\n            self.expr, self.a, self.b, self.a_constant, self.b_constant, self.unicast_add_constant\n        )\n    }\n}\n\nimpl Arbitrary for BinEinsumProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<BinEinsumProblem>;\n\n    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {\n        (1..3usize, 1..3usize, 0..3usize)\n            .prop_map(|(m_axes, n_axes, iter_axes)| {\n                let m_axes: String = ('a'..).take(m_axes).collect();\n                let n_axes: String = ('g'..).take(n_axes).collect();\n                let iter_axes: String = ('w'..).take(iter_axes).collect();\n                let a_axes: Vec<char> = (m_axes.clone() + &iter_axes + \"k\").chars().collect();\n                let b_axes: Vec<char> = (n_axes.clone() + &iter_axes + \"k\").chars().collect();\n                let c_axes: Vec<char> = (m_axes + &n_axes + &iter_axes).chars().collect();\n                (Just(a_axes), Just(b_axes), Just(c_axes))\n            })\n            .prop_flat_map(|(a, b, c)| (a.prop_shuffle(), b.prop_shuffle(), c.prop_shuffle()))\n            .prop_map(|(a, b, c)| {\n                let a: String = a.into_iter().collect();\n                let b: String = b.into_iter().collect();\n                let c: String = c.into_iter().collect();\n                let expr: AxesMapping = format!(\"{a},{b}->{c}\").parse().unwrap();\n                expr\n            })\n            .prop_flat_map(|expr| {\n                let dims = expr.iter_all_axes().count();\n                (Just(expr), proptest::collection::vec(1..4usize, dims..=dims))\n            })\n            .prop_flat_map(|(expr, axis_dims)| {\n                let shape_a: TVec<usize> = expr\n                    .axes(InOut::In(0))\n                    .map(|axis| expr.iter_all_axes().position(|x| x == axis).unwrap())\n                    .map(|dim| axis_dims[dim])\n                    .collect();\n                let shape_b: TVec<usize> = expr\n                    .axes(InOut::In(1))\n                    .map(|axis| expr.iter_all_axes().position(|x| x == axis).unwrap())\n                    .map(|dim| axis_dims[dim])\n                    .collect();\n                let shape_output: TVec<usize> = expr\n                    .axes(InOut::Out(0))\n                    .map(|axis| expr.iter_all_axes().position(|x| x == axis).unwrap())\n                    .map(|dim| axis_dims[dim])\n                    .collect();\n                let unicast_add_constant = proptest::option::of(tensor(&shape_output));\n                (Just(expr), tensor(&shape_a), tensor(&shape_b), 0..3usize, unicast_add_constant)\n            })\n            .prop_map(|(expr, a, b, a_b_constant, unicast_add_constant)| {\n                let a_constant = (a_b_constant & 0x1) != 0;\n                let b_constant = (a_b_constant & 0x2) != 0;\n                BinEinsumProblem { expr, a, b, a_constant, b_constant, unicast_add_constant }\n            })\n            .boxed()\n    }\n}\n\npub fn tensor(shape: &[usize]) -> BoxedStrategy<Tensor> {\n    let len = shape.iter().product::<usize>();\n    let shape: Vec<usize> = shape.into();\n    proptest::collection::vec((-10i8..=10i8).prop_map(|i| i as f32), len..=len)\n        .prop_map(move |vec| ArrayD::from_shape_vec(shape.clone(), vec).unwrap().into_tensor())\n        .boxed()\n}\n\nimpl BinEinsumProblem {\n    fn check(&self) -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let mut inputs = tvec!();\n        let a = if self.a_constant {\n            model.add_const(\"a\", self.a.clone())?\n        } else {\n            inputs.push(self.a.clone().into_tvalue());\n            model.add_source(\"a\", TypedFact::shape_and_dt_of(&self.a))?\n        };\n        let b = if self.b_constant {\n            model.add_const(\"b\", self.b.clone())?\n        } else {\n            inputs.push(self.b.clone().into_tvalue());\n            model.add_source(\"b\", TypedFact::shape_and_dt_of(&self.b))?\n        };\n        let mut output = model.wire_node(\n            \"einsum\",\n            EinSum { axes: self.expr.clone(), operating_dt: f32::datum_type(), q_params: None },\n            &[a, b],\n        )?;\n        if let Some(c) = &self.unicast_add_constant {\n            let c = model.add_const(\"c\", c.clone())?;\n            output = model.wire_node(\"add\", crate::ops::math::add(), &[output[0], c])?;\n        }\n        model.select_output_outlets(&output)?;\n        model = model.into_decluttered()?;\n        let expected = model.clone().into_runnable()?.run(inputs.clone())?.remove(0);\n        let optimised = model.clone().into_optimized()?;\n        //dbg!(&optimised);\n        let found = optimised.into_runnable()?.run(inputs.clone())?.remove(0);\n        found.close_enough(&expected, Approximation::Close)\n    }\n}\n\nproptest::proptest! {\n    #[test]\n    fn prop(pb in any::<BinEinsumProblem>()) {\n        pb.check().unwrap();\n    }\n}\n\n#[test]\nfn unicast_0() {\n    BinEinsumProblem {\n        expr: \"ak,gk->ag\".parse().unwrap(),\n        a: Tensor::zero::<f32>(&[1, 2]).unwrap(),\n        b: Tensor::zero::<f32>(&[1, 2]).unwrap(),\n        a_constant: false,\n        b_constant: false,\n        unicast_add_constant: Some(Tensor::zero::<f32>(&[1, 1]).unwrap()),\n    }\n    .check()\n    .unwrap()\n}\n\n#[test]\nfn unicast_1() {\n    BinEinsumProblem {\n        expr: \"ak,gk->ag\".parse().unwrap(),\n        a: Tensor::zero::<f32>(&[2, 1]).unwrap(),\n        b: Tensor::zero::<f32>(&[2, 1]).unwrap(),\n        a_constant: false,\n        b_constant: false,\n        unicast_add_constant: Some(tensor2(&[[0f32, 0.], [0., 1.]])),\n    }\n    .check()\n    .unwrap()\n}\n\n#[test]\nfn unicast_2() {\n    BinEinsumProblem {\n        expr: \"abk,gk->abg\".parse().unwrap(),\n        a: Tensor::zero::<f32>(&[2, 2, 1]).unwrap(),\n        b: Tensor::zero::<f32>(&[1, 1]).unwrap(),\n        a_constant: false,\n        b_constant: false,\n        unicast_add_constant: Some(tensor3(&[[[0f32], [0.]], [[0.], [1.]]])),\n    }\n    .check()\n    .unwrap()\n}\n"
  },
  {
    "path": "core/src/ops/element_wise.rs",
    "content": "use crate::internal::*;\nuse downcast_rs::Downcast;\nuse dyn_eq::DynEq;\nuse std::fmt;\n\npub trait ElementWiseMiniOp:\n    fmt::Debug + dyn_clone::DynClone + dyn_eq::DynEq + Send + Sync + 'static + Downcast\n{\n    fn name(&self) -> String;\n    fn prefix(&self) -> &'static str {\n        \"\"\n    }\n    fn validation(&self) -> Validation {\n        Validation::Accurate\n    }\n    #[allow(unused_variables)]\n    fn output_type(&self, input_type: DatumType) -> Option<DatumType> {\n        None\n    }\n    #[allow(unused_variables)]\n    fn eval_in_place(&self, t: &mut Tensor, out_dt: Option<DatumType>) -> TractResult<()> {\n        bail!(\"Element wise eval in-place not defined\");\n    }\n    #[allow(unused_variables)]\n    fn eval_out_of_place(&self, t: &Tensor, out_dt: Option<DatumType>) -> TractResult<Tensor> {\n        bail!(\"Element wise eval out-of-place place not defined\");\n    }\n    #[allow(unused_variables)]\n    fn cost_per_element(&self, dt: DatumType) -> TVec<(Cost, usize)> {\n        tvec!()\n    }\n    #[allow(unused_variables)]\n    fn operating_datum_type(&self, dt: DatumType) -> DatumType {\n        dt\n    }\n    #[allow(unused_variables)]\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        Ok(None)\n    }\n\n    #[allow(unused_variables)]\n    fn quantize(\n        &self,\n        dt: DatumType,\n        scale: f32,\n        zero_point: i32,\n    ) -> TractResult<Option<Box<dyn ElementWiseMiniOp>>> {\n        Ok(None)\n    }\n    #[allow(unused_variables)]\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![])\n    }\n}\n\ndyn_clone::clone_trait_object!(ElementWiseMiniOp);\ndyn_eq::eq_trait_object!(ElementWiseMiniOp);\ndowncast_rs::impl_downcast!(ElementWiseMiniOp);\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct ElementWiseOp(pub Box<dyn ElementWiseMiniOp>, pub Option<DatumType>);\n\nimpl ElementWiseOp {\n    fn output_datum_type(&self, input_dt: DatumType) -> DatumType {\n        self.1.unwrap_or(self.0.operating_datum_type(input_dt))\n    }\n}\n\nimpl Op for ElementWiseOp {\n    fn name(&self) -> StaticName {\n        self.0.name().into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        self.0.info()\n    }\n\n    fn validation(&self) -> Validation {\n        self.0.validation()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for ElementWiseOp {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, mut inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        if let Some(_dt) = self.0.output_type(inputs[0].datum_type()) {\n            Ok(tvec!(self.0.eval_out_of_place(&inputs[0], self.1)?.into_tvalue()))\n        } else {\n            let mut m = inputs.remove(0).into_tensor();\n            self.0.eval_in_place(&mut m, self.1)?;\n            Ok(tvec!(m.into()))\n        }\n    }\n}\n\nimpl TypedOp for ElementWiseOp {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact = inputs[0].clone().without_value();\n        let dt = self.output_datum_type(fact.datum_type);\n        if let Some(dt) = self.1 {\n            fact.datum_type = dt;\n        } else if let Some(dt) = self.0.output_type(dt) {\n            fact.datum_type = dt;\n        }\n        // Propagate uniform_tdim through this op.\n        if let Some(tdim) = &inputs[0].uniform_tdim {\n            // Logical NOT on bool tensors: NOT(x) = 1 - x for 0/1 values.\n            // Not is bool-only by definition. BitNot is bitwise (valid on integers\n            // where ~x ≠ 1-x), so only apply this for bool input.\n            let is_logical_not = self.0.downcast_ref::<crate::ops::logic::Not>().is_some()\n                || (self.0.downcast_ref::<crate::ops::logic::BitNot>().is_some()\n                    && inputs[0].datum_type == bool::datum_type());\n            if is_logical_not {\n                fact.uniform_tdim = Some((TDim::Val(1) - tdim.clone()).reduce());\n            } else {\n                // General path: evaluate the op on a TDim scalar.\n                // Ops with a TDim arm (e.g. Floor → identity) pass the value through;\n                // ops without one return an error and uniform_tdim stays None.\n                let mut tmp = tensor0(tdim.clone());\n                if self.0.eval_in_place(&mut tmp, None).is_ok() {\n                    fact.uniform_tdim = tmp\n                        .try_as_plain()\n                        .ok()\n                        .and_then(|d| d.as_slice::<TDim>().ok())\n                        .and_then(|s| s.first())\n                        .cloned()\n                        .map(|d| d.reduce());\n                }\n            }\n        }\n        Ok(tvec!(fact))\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        Ok(Some(AxisChangeConsequence::new(model, node, None, change)))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if let Some(prec) = model.single_prec(node.id)?\n            && (prec.op_is::<AxisOp>() || prec.op_is::<IntoShape>())\n        {\n            let mut patch = TypedModelPatch::default();\n            let mut wire = tvec!(patch.tap_model(model, prec.inputs[0])?);\n            wire = patch.wire_node(&node.name, &node.op, &wire)?;\n            wire = patch.wire_node(&prec.name, &prec.op, &wire)?;\n            patch.shunt_outside(model, node.id.into(), wire[0])?;\n            return Ok(Some(patch));\n        }\n        self.0.declutter(model, node)\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let count: TDim = inputs[0].shape.iter().product();\n        Ok(self\n            .0\n            .cost_per_element(inputs[0].datum_type)\n            .into_iter()\n            .map(|(c, n)| (c, count.clone() * n))\n            .collect())\n    }\n\n    fn quantize(\n        &self,\n        _model: &TypedModel,\n        _node: &TypedNode,\n        dt: DatumType,\n        scale: f32,\n        zero_point: i32,\n    ) -> TractResult<Option<Box<dyn TypedOp>>> {\n        if let Some(mini) = self.0.quantize(dt, scale, zero_point)? {\n            Ok(Some(Box::new(ElementWiseOp(mini, self.1))))\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        _model: &TypedModel,\n        node: &TypedNode,\n        _prefix: &str,\n        inputs: &[OutletId],\n        _output_axis: usize,\n        _start: &TDim,\n        _end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        patch.wire_node(&node.name, &node.op, inputs).map(Some)\n    }\n\n    as_op!();\n}\n\n#[macro_export]\nmacro_rules! element_wise {\n    ($func:ident, $Op:ident $({$( $(#[$meta: meta])? $var: ident : $var_typ: path),*})?,\n        $([$($typ:ident),*] => $f:expr ),*\n        $(; q: $( [$($typ_dt:ident),*] => $f_f32:expr),*)?\n        $(; cost: $cost:expr )?\n        $(; declutter: $declutter:expr )?\n        $(; operating_datum_type: $operating_datum_type:expr )?\n        $(; prefix: $prefix:expr )?\n        $(; quantize: $quantize:expr )?\n        $(; validation: $validation:expr )?\n    ) => {\n        #[derive(Debug, Clone, PartialEq)]\n        pub struct $Op { $( $( $(#[$meta])? pub $var: $var_typ),* )? }\n        impl Eq for $Op {}\n        impl $crate::ops::element_wise::ElementWiseMiniOp for $Op {\n            fn name(&self) -> String {\n                format!(\"{}{}\", self.prefix(), stringify!($Op))\n            }\n            fn eval_in_place(&self, t: &mut Tensor, out_dt: Option<DatumType>) -> TractResult<()> {\n                $(\n                    $(if out_dt.unwrap_or(t.datum_type()) == $typ::datum_type() {\n                        let mut t_plain = t.try_as_plain_mut()?;\n                        let t: &mut[$typ] = t_plain.as_slice_mut::<$typ>()?;\n                        let f: fn(&Self, &mut[$typ]) -> TractResult<()> = $f;\n                        f(self, t)?;\n                        return Ok(())\n                    }\n                    )*\n                )*\n                $(\n                    $(\n                       $(\n                        let mut input_dt = t.datum_type();\n                        let sout_dt = out_dt.unwrap_or(input_dt);\n                        if sout_dt.unquantized() == <$typ_dt>::datum_type().unquantized() {\n                           if input_dt.unquantized() != sout_dt.unquantized() {\n                               // align unquantized input type to unquantized output type\n                               *t = match input_dt.unquantized() {\n                                   DatumType::U8 => t.clone().into_arc_tensor().offset_u8_as_i8(),\n                                   DatumType::I8 => t.clone().into_arc_tensor().offset_i8_as_u8(),\n                                   unknown_dt => bail!(\"unexpected quantization input dt {:?}\", unknown_dt)\n                               }.into_tensor();\n                               input_dt = t.datum_type(); // because zero_point change\n                           }\n                           unsafe { t.set_datum_type(sout_dt) } // force cast\n                           let mut t_plain = t.try_as_plain_mut()?;\n                           let t: &mut[$typ_dt] = t_plain.as_slice_mut::<$typ_dt>()?;\n                           let f: fn(&Self, &mut[$typ_dt], DatumType, DatumType) -> TractResult<()> = |_, xs, input_dt, out_dt| {\n                               let (izp, iscale) = input_dt.zp_scale();\n                               let (ozp, oscale) = out_dt.zp_scale();\n                               xs.iter_mut().for_each(|x| {\n                                   let x_f32 = (*x as f32 - izp as f32) * iscale;\n                                   *x = (($f_f32(x_f32) / oscale) + ozp as f32).as_()\n                               });\n                               Ok(())\n                           };\n                           f(self, t, input_dt, sout_dt)?;\n                           return Ok(())\n                       }\n                       )*\n                   )*\n                )?\n                bail!(\"{} does not support {:?}\", self.name(), out_dt.unwrap_or(t.datum_type()));\n            }\n            $(\n            fn cost_per_element(&self, dt: DatumType) -> TVec<(Cost, usize)> {\n                $cost(dt)\n            }\n            )?\n            $(\n                fn declutter(\n                    &self,\n                    model: &TypedModel,\n                    node: &TypedNode,\n                ) -> TractResult<Option<TypedModelPatch>> {\n                    $declutter(model, node)\n                }\n            )?\n            $(\n            fn prefix(&self) -> &'static str {\n                $prefix\n            }\n            )?\n            $(\n            fn quantize(\n                &self,\n                dt: DatumType,\n                scale: f32,\n                zero_point: i32) -> TractResult<Option<Box<dyn ElementWiseMiniOp>>> {\n                    $quantize(&self, dt, scale, zero_point)\n            }\n            )?\n            $(\n            fn validation(&self) -> Validation {\n                $validation\n            }\n            )?\n            $(\n            fn operating_datum_type(&self, dt: DatumType) -> DatumType {\n                ($operating_datum_type)(dt)\n            }\n            )?\n        }\n        pub fn $func($( $($var: $var_typ),* )?) -> $crate::ops::element_wise::ElementWiseOp {\n            $crate::ops::element_wise::ElementWiseOp(Box::new($Op { $( $($var),* )? }), None)\n        }\n    }\n}\n\n#[macro_export]\nmacro_rules! element_wise_oop {\n    ($(#[$fmeta:meta])* $func:ident, $Op:ident $({$( $(#[$meta: meta])? $var: ident : $var_typ: path),*})?,\n        $( [$($typ:ident),*] => $typ_dst:ident $f:expr ),*\n        $(; cost: $cost:expr )?\n        $(; info: $info:expr )?\n        $(; operating_datum_type: $operating_datum_type:expr )?\n        $(; prefix: $prefix:expr )?\n        $(; quantize: $quantize:expr )?\n        $(; validation: $validation:expr )?\n    ) => {\n        #[derive(Debug, Clone)]\n        pub struct $Op { $( $($(#[$meta])? pub $var: $var_typ),* )? }\n        impl PartialEq for $Op {\n            #[allow(unused_variables)]\n            fn eq(&self, other: &Self) -> bool {\n                $( $( if &self.$var != &other.$var { return false; })* )?\n                true\n            }\n        }\n        impl Eq for $Op {}\n        impl $crate::ops::element_wise::ElementWiseMiniOp for $Op {\n            fn name(&self) -> String {\n                format!(\"{}{}\", self.prefix(), stringify!($Op))\n            }\n            fn output_type(&self, input_type: DatumType) -> Option<DatumType> {\n                $(\n                    $(if input_type == $typ::datum_type() {\n                        return Some(<$typ_dst>::datum_type())\n                    }\n                    )*\n                )*\n                None\n            }\n            fn eval_out_of_place(&self, t: &Tensor, _out_dt: Option<DatumType>) -> TractResult<Tensor> {\n                $(\n                    let mut dst = unsafe { Tensor::uninitialized_dt(<$typ_dst>::datum_type(), &t.shape())? };\n                    $(if t.datum_type() == $typ::datum_type() {\n                        let f: fn(&Self, &[$typ], &mut[$typ_dst]) -> TractResult<()> = $f;\n                        let mut dst_plain = dst.try_as_plain_mut()?;\n                        f(self, t.try_as_plain()?.as_slice::<$typ>()?, dst_plain.as_slice_mut::<$typ_dst>()?)?;\n                        return Ok(dst)\n                    }\n                    )*\n                )*\n                bail!(\"{} does not support {:?}\", self.name(), t.datum_type());\n            }\n            $(\n            fn cost_per_element(&self, dt: DatumType) -> TVec<(Cost, usize)> {\n                $cost(dt)\n            }\n            )?\n            $(\n            fn info(&self) -> TractResult<Vec<String>> {\n                $info(self)\n            }\n            )?\n            $(\n            fn prefix(&self) -> &'static str {\n                $prefix\n            }\n            )?\n            $(\n            fn quantize(\n                &self,\n                dt: DatumType,\n                scale: f32,\n                zero_point: i32) -> TractResult<Option<Box<dyn ElementWiseMiniOp>>> {\n                    $quantize(ft, scale, zero_point)\n            }\n            )?\n            $(\n            fn validation(&self) -> Validation {\n                $validation\n            }\n            )?\n            $(\n            fn operating_datum_type(&self, dt: DatumType) -> DatumType {\n                ($operating_datum_type)(dt)\n            }\n            )?\n        }\n        $(#[$fmeta])*\n        pub fn $func($( $($var: $var_typ),* )?) -> $crate::ops::element_wise::ElementWiseOp {\n            $crate::ops::element_wise::ElementWiseOp(Box::new($Op { $( $($var),* )? }), None)\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/fft.rs",
    "content": "use crate::internal::*;\nuse num_complex::Complex;\nuse rustfft::num_traits::{Float, FromPrimitive};\nuse rustfft::{FftDirection, FftNum};\nuse tract_data::itertools::Itertools;\nuse tract_ndarray::Axis;\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct Fft {\n    pub axis: usize,\n    pub inverse: bool,\n}\n\nimpl Fft {\n    fn eval_t<T: Datum + FftNum + FromPrimitive + Float>(\n        &self,\n        tensor: &mut Tensor,\n    ) -> TractResult<()> {\n        let mut iterator_shape: TVec<usize> = tensor.shape().into();\n        iterator_shape.pop(); // last dim is [re, im]\n        iterator_shape[self.axis] = 1;\n        let len = tensor.shape()[self.axis];\n        let direction = if self.inverse { FftDirection::Inverse } else { FftDirection::Forward };\n        let fft = rustfft::FftPlanner::new().plan_fft(len, direction);\n        let mut tensor_plain = tensor.try_as_plain_mut()?;\n        let mut array = tensor_plain.to_array_view_mut::<T>()?;\n        let mut v = Vec::with_capacity(len);\n        for coords in tract_ndarray::indices(&*iterator_shape) {\n            v.clear();\n            let mut slice = array.slice_each_axis_mut(|ax| {\n                if ax.axis.index() == self.axis || ax.stride == 1 {\n                    // ax.stride == 1 => last dim\n                    (..).into()\n                } else {\n                    let c = coords[ax.axis.index()] as isize;\n                    (c..=c).into()\n                }\n            });\n            v.extend(slice.iter().tuples().map(|(r, i)| Complex::new(*r, *i)));\n            fft.process(&mut v);\n            slice\n                .iter_mut()\n                .zip(v.iter().flat_map(|cmpl| [cmpl.re, cmpl.im].into_iter()))\n                .for_each(|(s, v)| *s = v);\n        }\n        Ok(())\n    }\n}\n\nimpl Op for Fft {\n    fn name(&self) -> StaticName {\n        \"Fft\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![if self.inverse { \"inverse\" } else { \"forward\" }.into()])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Fft {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let mut tensor = args_1!(inputs).into_tensor();\n        match tensor.datum_type() {\n            DatumType::F16 => {\n                let mut temp = tensor.cast_to::<f32>()?.into_owned();\n                self.eval_t::<f32>(&mut temp)?;\n                tensor = temp.cast_to::<f16>()?.into_owned();\n            }\n            DatumType::F32 => self.eval_t::<f32>(&mut tensor)?,\n            DatumType::F64 => self.eval_t::<f64>(&mut tensor)?,\n            _ => bail!(\"FFT not implemented for type {:?}\", tensor.datum_type()),\n        }\n        Ok(tvec!(tensor.into_tvalue()))\n    }\n}\n\nimpl TypedOp for Fft {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        anyhow::ensure!(\n            inputs[0].rank() >= 2,\n            \"Expect rank 2 (one for fft dimension, one for complex dimension\"\n        );\n        anyhow::ensure!(\n            inputs[0].shape.last().unwrap() == &2.to_dim(),\n            \"Fft operators expect inner (last) dimension to be 2 for real and imaginary part\"\n        );\n        Ok(tvec!(inputs[0].without_value()))\n    }\n\n    as_op!();\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct Stft {\n    pub axis: usize,\n    pub frame: usize,\n    pub stride: usize,\n    pub window: Option<Arc<Tensor>>,\n}\n\nimpl Stft {\n    fn eval_t<T: Datum + FftNum + FromPrimitive + Float>(\n        &self,\n        input: &Tensor,\n    ) -> TractResult<Tensor> {\n        let mut iterator_shape: TVec<usize> = input.shape().into();\n        iterator_shape.pop(); // [re,im]\n        iterator_shape[self.axis] = 1;\n        let mut output_shape: TVec<usize> = input.shape().into();\n        let frames = (input.shape()[self.axis] - self.frame) / self.stride + 1;\n        output_shape.insert(self.axis, frames);\n        output_shape[self.axis + 1] = self.frame;\n        let mut output = unsafe { Tensor::uninitialized::<T>(&output_shape)? };\n        let fft = rustfft::FftPlanner::new().plan_fft_forward(self.frame);\n        let input = input.to_plain_array_view::<T>()?;\n        let mut output_plain = output.try_as_plain_mut()?;\n        let mut oview = output_plain.to_array_view_mut::<T>()?;\n        let mut v = Vec::with_capacity(self.frame);\n        for coords in tract_ndarray::indices(&*iterator_shape) {\n            let islice = input.slice_each_axis(|ax| {\n                if ax.axis.index() == self.axis || ax.stride == 1 {\n                    (..).into()\n                } else {\n                    let c = coords[ax.axis.index()] as isize;\n                    (c..=c).into()\n                }\n            });\n            let mut oslice = oview.slice_each_axis_mut(|ax| {\n                if ax.stride == 1 {\n                    (..).into()\n                } else if ax.axis.index() < self.axis {\n                    let c = coords[ax.axis.index()] as isize;\n                    (c..=c).into()\n                } else if ax.axis.index() == self.axis || ax.axis.index() == self.axis + 1 {\n                    (..).into()\n                } else {\n                    let c = coords[ax.axis.index() - 1] as isize;\n                    (c..=c).into()\n                }\n            });\n            for f in 0..frames {\n                v.clear();\n                v.extend(\n                    islice\n                        .iter()\n                        .tuples()\n                        .skip(self.stride * f)\n                        .take(self.frame)\n                        .map(|(re, im)| Complex::new(*re, *im)),\n                );\n                if let Some(win) = &self.window {\n                    let win = win.try_as_plain()?.as_slice::<T>()?;\n                    // symmetric padding in case window is smaller than frames (aka n fft)\n                    let pad_left = (self.frame - win.len()) / 2;\n                    v.iter_mut().enumerate().for_each(|(ix, v)| {\n                        *v = if ix < pad_left || ix >= pad_left + win.len() {\n                            Complex::new(T::zero(), T::zero())\n                        } else {\n                            *v * Complex::new(win[ix - pad_left], T::zero())\n                        }\n                    });\n                }\n                fft.process(&mut v);\n                oslice\n                    .index_axis_mut(Axis(self.axis), f)\n                    .iter_mut()\n                    .zip(v.iter().flat_map(|cmpl| [cmpl.re, cmpl.im].into_iter()))\n                    .for_each(|(s, v)| *s = v);\n            }\n        }\n        Ok(output)\n    }\n}\n\nimpl Op for Stft {\n    fn name(&self) -> StaticName {\n        \"STFT\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Stft {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let output = match input.datum_type() {\n            DatumType::F16 => {\n                let temp = input.cast_to::<f32>()?;\n                self.eval_t::<f32>(&temp)?.cast_to::<f16>()?.into_owned()\n            }\n            DatumType::F32 => self.eval_t::<f32>(&input)?,\n            DatumType::F64 => self.eval_t::<f64>(&input)?,\n            _ => bail!(\"FFT not implemented for type {:?}\", input.datum_type()),\n        };\n        Ok(tvec!(output.into_tvalue()))\n    }\n}\n\nimpl TypedOp for Stft {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        anyhow::ensure!(\n            inputs[0].rank() >= 2,\n            \"Expect rank 2 (one for fft dimension, one for complex dimension\"\n        );\n        anyhow::ensure!(\n            inputs[0].shape.last().unwrap() == &2.to_dim(),\n            \"Fft operators expect inner (last) dimension to be 2 for real and imaginary part\"\n        );\n        let mut shape = inputs[0].shape.to_tvec();\n        let frames = (inputs[0].shape[self.axis].clone() - self.frame) / self.stride + 1;\n        shape[self.axis] = frames;\n        shape.insert(self.axis + 1, self.frame.to_dim());\n        Ok(tvec!(inputs[0].datum_type.fact(shape)))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/identity.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\npub struct Identity;\n\nimpl Op for Identity {\n    fn name(&self) -> StaticName {\n        \"Identity\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Identity {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    /// Evaluates the operation given the input tensors.\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(inputs)\n    }\n}\n\nimpl TypedOp for Identity {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        TypedModelPatch::shunt_one_op(model, node)\n    }\n\n    fn fuse(&self, model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {\n        TypedModelPatch::shunt_one_op(model, node)\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n\n    as_op!();\n}\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\npub struct PinConst;\n\nimpl Op for PinConst {\n    fn name(&self) -> StaticName {\n        \"PinConst\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for PinConst {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    /// Evaluates the operation given the input tensors.\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(inputs)\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(self.clone())))\n    }\n}\n\nimpl OpState for PinConst {\n    fn eval(\n        &mut self,\n        _session: &mut TurnState,\n        _op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        Ok(inputs)\n    }\n}\n\nimpl TypedOp for PinConst {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].without_value()))\n    }\n\n    as_op!();\n}\n\ntrivial_op_state_freeze!(PinConst);\n"
  },
  {
    "path": "core/src/ops/konst.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, Hash, Eq, PartialEq)]\npub struct Const(Arc<Tensor>, Option<Box<dyn ExoticFact>>);\n\nimpl Const {\n    pub fn new(tensor: Arc<Tensor>) -> TractResult<Const> {\n        Self::new_with_opt_exotic_fact(tensor, None)\n    }\n\n    pub fn new_with_exotic_fact(\n        tensor: Arc<Tensor>,\n        fact: Box<dyn ExoticFact>,\n    ) -> TractResult<Const> {\n        Self::new_with_opt_exotic_fact(tensor, Some(fact))\n    }\n\n    pub fn new_with_opt_exotic_fact(\n        tensor: Arc<Tensor>,\n        fact: Option<Box<dyn ExoticFact>>,\n    ) -> TractResult<Const> {\n        ensure!(fact.is_some() || tensor.is_plain(), \"Exotic tensor requires an exotic_fact\");\n        Ok(Const(tensor, fact))\n    }\n\n    pub fn val(&self) -> &Arc<Tensor> {\n        &self.0\n    }\n\n    pub fn exotic_fact(&self) -> Option<&dyn ExoticFact> {\n        self.1.as_deref()\n    }\n}\n\nimpl Op for Const {\n    fn name(&self) -> StaticName {\n        \"Const\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Const {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, _inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(tvec![Arc::clone(&self.0).into_tvalue()])\n    }\n}\n\nimpl TypedOp for Const {\n    as_op!();\n\n    fn output_facts(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let fact = if self.1.is_some() {\n            // Exotic const tensors (e.g. device-backed) may have storage that\n            // cannot produce an ExoticFact (like DeviceTensor). Build the fact\n            // from dt/shape and attach the explicit exotic_fact from self.1.\n            let mut f = TypedFact::dt_shape(\n                self.0.datum_type(),\n                ShapeFact::from_dims(self.0.shape().iter().map(TDim::from)),\n            );\n            f.konst = Some(Arc::clone(&self.0));\n            f.exotic_fact.clone_from(&self.1);\n            f\n        } else {\n            // Plain tensor: TryFrom sets uniform, uniform_tdim, exotic_fact from storage.\n            TypedFact::try_from(&self.0)?\n        };\n        Ok(tvec!(fact))\n    }\n\n    fn cost(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        Ok(tvec!((Cost::Params(self.0.datum_type().unquantized()), self.0.len().into())))\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        _mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let op = if self.0.datum_type() == TDim::datum_type() {\n            let mut tensor = self.0.clone().into_tensor();\n            for d in tensor.try_as_plain_mut()?.as_slice_mut::<TDim>()? {\n                *d = d.eval(values);\n            }\n            Const(tensor.into_arc_tensor(), self.1.clone())\n        } else {\n            self.clone()\n        };\n        target.wire_node(&node.name, op, &[])\n    }\n\n    fn change_axes(\n        &self,\n        _model: &TypedModel,\n        _node: &TypedNode,\n        io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        anyhow::ensure!(io == InOut::Out(0));\n        let mut new_tensor = self.0.clone().into_tensor();\n        if change.change_tensor(&mut new_tensor, false).is_ok() {\n            let mut sub = Const(new_tensor.into_arc_tensor(), None);\n            if self.1.is_some() {\n                let my_fact = self.output_facts(&[])?;\n                let changed_fact = change.output_facts(&[&my_fact[0]])?;\n                sub.1 = changed_fact[0].exotic_fact.clone();\n            }\n            Ok(Some(AxisChangeConsequence {\n                substitute_op: Some(Box::new(sub)),\n                wire_changes: tvec!((io, change.clone())),\n            }))\n        } else {\n            Ok(None)\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/logic/comparison.rs",
    "content": "use crate::broadcast::multi_broadcast;\nuse crate::internal::*;\nuse crate::ndarray::Zip;\nuse crate::ops::binary::BinMiniOp;\n\nuse tract_data::TooEarly;\n\n// Helper for eval_out_of_place dispatch\nfn eval_comp_oop<T: Datum + PartialOrd>(\n    a: &Tensor,\n    b: &Tensor,\n    f: impl Fn(&T, &T) -> bool,\n) -> TractResult<Tensor> {\n    let a = a.to_plain_array_view::<T>()?;\n    let b = b.to_plain_array_view::<T>()?;\n    let shape = multi_broadcast(&[a.shape(), b.shape()])?;\n    let mut c = unsafe { Tensor::uninitialized::<bool>(&shape)? };\n    let mut c_plain = c.try_as_plain_mut()?;\n    let mut view = c_plain.to_array_view_mut::<bool>()?;\n    Zip::from(&mut view).and_broadcast(&a).and_broadcast(&b).for_each(|c, a, b| *c = f(a, b));\n    Ok(c)\n}\n\n// Helper for TDim symbolic eval\nfn eval_tdim_symbolic(\n    session: &TurnState,\n    inputs: &TVec<TValue>,\n    prove: impl Fn(&TDim, &TDim) -> TractResult<bool>,\n) -> TractResult<Option<TVec<TValue>>> {\n    if inputs[0].datum_type() != TDim::datum_type() {\n        return Ok(None);\n    }\n    let mut a = inputs[0].clone().into_tensor();\n    let mut b = inputs[1].clone().into_tensor();\n    for a in a.try_as_plain_mut()?.as_slice_mut::<TDim>()? {\n        *a = a.eval(&session.resolved_symbols);\n    }\n    for b in b.try_as_plain_mut()?.as_slice_mut::<TDim>()? {\n        *b = b.eval(&session.resolved_symbols);\n    }\n    if let (Ok(a_i64), Ok(b_i64)) = (a.cast_to::<i64>(), b.cast_to::<i64>()) {\n        let result = eval_comp_oop::<i64>(&a_i64, &b_i64, |a, b| {\n            prove(&(*a).into(), &(*b).into()).unwrap_or(false)\n        })?;\n        return Ok(Some(tvec!(result.into_tvalue())));\n    }\n    let a_view = inputs[0].to_plain_array_view::<TDim>()?;\n    let b_view = inputs[1].to_plain_array_view::<TDim>()?;\n    let shape = multi_broadcast(&[a_view.shape(), b_view.shape()])?;\n    let mut c = unsafe { Tensor::uninitialized::<bool>(&shape)? };\n    let mut c_plain = c.try_as_plain_mut()?;\n    let mut view = c_plain.to_array_view_mut::<bool>()?;\n    let a_bc = a_view.broadcast(&*shape).unwrap();\n    let b_bc = b_view.broadcast(&*shape).unwrap();\n    for ixs in tract_ndarray::indices(&*shape) {\n        view[&ixs] = prove(&a_bc[&ixs], &b_bc[&ixs])?;\n    }\n    Ok(Some(tvec!(c.into_tvalue())))\n}\n\nmacro_rules! comp_bin_mini_op {\n    ($Op:ident, $name:literal, $cmp:tt, $prove_tdim:expr, $uniform_tdim:expr) => {\n        #[derive(Debug, Clone, Hash, PartialEq, Eq)]\n        pub struct $Op;\n\n        impl BinMiniOp for $Op {\n            fn name(&self) -> &'static str {\n                $name\n            }\n\n            fn result_datum_type(&self, _a: DatumType, _b: DatumType) -> TractResult<DatumType> {\n                Ok(bool::datum_type())\n            }\n\n            fn is_commutative(&self) -> bool {\n                false\n            }\n\n            fn eval_in_a(&self, _a: &mut Tensor, _b: &Tensor) -> TractResult<()> {\n                bail!(\"Comparison changes datum type, eval_in_a not supported\")\n            }\n\n            fn eval_out_of_place(\n                &self,\n                c: &mut Tensor,\n                a: &Tensor,\n                b: &Tensor,\n            ) -> TractResult<()> {\n                let dt = a.datum_type();\n                if dt == String::datum_type() {\n                    let a = a.to_plain_array_view::<String>()?;\n                    let b = b.to_plain_array_view::<String>()?;\n                    let mut c_plain = c.try_as_plain_mut()?;\n                    let mut view = c_plain.to_array_view_mut::<bool>()?;\n                    Zip::from(&mut view).and_broadcast(&a).and_broadcast(&b)\n                        .for_each(|c, a, b| *c = a $cmp b);\n                    return Ok(());\n                }\n                fn inner<T: Datum + PartialOrd>(c: &mut Tensor, a: &Tensor, b: &Tensor, f: impl Fn(&T, &T) -> bool) -> TractResult<()> {\n                    let a = a.to_plain_array_view::<T>()?;\n                    let b = b.to_plain_array_view::<T>()?;\n                    let mut c_plain = c.try_as_plain_mut()?;\n                    let mut view = c_plain.to_array_view_mut::<bool>()?;\n                    Zip::from(&mut view).and_broadcast(&a).and_broadcast(&b)\n                        .for_each(|c, a, b| *c = f(a, b));\n                    Ok(())\n                }\n                dispatch_numbers!(inner(dt)(c, a, b, |a: &_, b: &_| a $cmp b))\n            }\n\n            fn eval(&self, a: TValue, b: TValue, c_dt: DatumType) -> TractResult<Tensor> {\n                let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()])?;\n                let mut c = unsafe { Tensor::uninitialized_dt(c_dt, &c_shape)? };\n                self.eval_out_of_place(&mut c, &a, &b)?;\n                Ok(c)\n            }\n\n            fn eval_symbolic(\n                &self,\n                session: &TurnState,\n                inputs: TVec<TValue>,\n            ) -> TractResult<Option<TVec<TValue>>> {\n                eval_tdim_symbolic(session, &inputs, $prove_tdim)\n            }\n\n            fn uniform_tdim_comparison(\n                &self,\n                a: &TDim,\n                b: &TDim,\n            ) -> Option<TDim> {\n                Some(($uniform_tdim)(a, b))\n            }\n        }\n    };\n}\n\nfn prove_eq(a: &TDim, b: &TDim) -> TractResult<bool> {\n    Ok(a == b)\n}\n\nfn prove_ne(a: &TDim, b: &TDim) -> TractResult<bool> {\n    Ok(a != b)\n}\n\nfn prove_gte(a: &TDim, b: &TDim) -> TractResult<bool> {\n    let diff = a.clone() - b;\n    if diff.prove_positive_or_zero() {\n        Ok(true)\n    } else if diff.prove_strict_negative() {\n        Ok(false)\n    } else {\n        bail!(TooEarly::UndeterminedSymbol(diff.to_string()))\n    }\n}\n\nfn prove_gt(a: &TDim, b: &TDim) -> TractResult<bool> {\n    let diff = a.clone() - b;\n    if diff.prove_strict_positive() {\n        Ok(true)\n    } else if diff.prove_negative_or_zero() {\n        Ok(false)\n    } else {\n        bail!(TooEarly::UndeterminedSymbol(diff.to_string()))\n    }\n}\n\nfn prove_lte(a: &TDim, b: &TDim) -> TractResult<bool> {\n    prove_gte(b, a)\n}\n\nfn prove_lt(a: &TDim, b: &TDim) -> TractResult<bool> {\n    prove_gt(b, a)\n}\n\ncomp_bin_mini_op!(CompEq, \"Eq\", ==, prove_eq, |a: &TDim, b: &TDim|\n    TDim::Eq(Box::new(a.clone()), Box::new(b.clone())).reduce()\n);\n\ncomp_bin_mini_op!(CompNE, \"NE\", !=, prove_ne, |a: &TDim, b: &TDim|\n    (TDim::Val(1) - TDim::Eq(Box::new(a.clone()), Box::new(b.clone()))).reduce()\n);\n\ncomp_bin_mini_op!(CompLT, \"LT\", <, prove_lt, |a: &TDim, b: &TDim|\n    TDim::Ge(Box::new(b.clone()), Box::new((a.clone() + TDim::Val(1)).reduce())).reduce()\n);\n\ncomp_bin_mini_op!(CompGT, \"GT\", >, prove_gt, |a: &TDim, b: &TDim|\n    TDim::Ge(Box::new((a.clone() + TDim::Val(1)).reduce()), Box::new(b.clone())).reduce()\n);\n\ncomp_bin_mini_op!(CompLTE, \"LTE\", <=, prove_lte, |a: &TDim, b: &TDim|\n    TDim::Ge(Box::new(b.clone()), Box::new(a.clone())).reduce()\n);\n\ncomp_bin_mini_op!(CompGTE, \"GTE\", >=, prove_gte, |a: &TDim, b: &TDim|\n    TDim::Ge(Box::new(a.clone()), Box::new(b.clone())).reduce()\n);\n\n// Factory functions\npub fn comp_eq() -> Box<dyn BinMiniOp> {\n    Box::new(CompEq)\n}\npub fn comp_ne() -> Box<dyn BinMiniOp> {\n    Box::new(CompNE)\n}\npub fn comp_lt() -> Box<dyn BinMiniOp> {\n    Box::new(CompLT)\n}\npub fn comp_gt() -> Box<dyn BinMiniOp> {\n    Box::new(CompGT)\n}\npub fn comp_lte() -> Box<dyn BinMiniOp> {\n    Box::new(CompLTE)\n}\npub fn comp_gte() -> Box<dyn BinMiniOp> {\n    Box::new(CompGTE)\n}\n"
  },
  {
    "path": "core/src/ops/logic/ite.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, Default)]\npub struct IfThenElse {\n    pub then_body: TypedModel,\n    pub then_input_mapping: Vec<usize>,\n    pub else_body: TypedModel,\n    pub else_input_mapping: Vec<usize>,\n}\n\nimpl PartialEq for IfThenElse {\n    fn eq(&self, _other: &Self) -> bool {\n        false\n    }\n}\nimpl Eq for IfThenElse {}\n\nimpl Op for IfThenElse {\n    fn name(&self) -> StaticName {\n        \"IfThenElse\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl TypedOp for IfThenElse {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs[0].datum_type == bool::datum_type());\n        ensure!(inputs[0].shape.volume() == 1.to_dim());\n        ensure!(self.then_body.inputs.len() == self.then_input_mapping.len());\n        ensure!(self.else_body.inputs.len() == self.else_input_mapping.len());\n        let mut facts = tvec!();\n        for i in 0..self.then_body.outputs.len() {\n            ensure!(\n                self.then_body.output_fact(i)?.without_value()\n                    == self.else_body.output_fact(i)?.without_value()\n            );\n            facts.push(self.then_body.output_fact(i)?.clone());\n        }\n        Ok(facts)\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if let Some(cond) = &model.outlet_fact(node.inputs[0])?.konst {\n            let cond = cond.cast_to_scalar::<bool>()?;\n            let (body, input_mapping) = if cond {\n                (&self.then_body, &self.then_input_mapping)\n            } else {\n                (&self.else_body, &self.else_input_mapping)\n            };\n            let mut inner_mapping: HashMap<OutletId, OutletId> = HashMap::default();\n            let mut patch = TypedModelPatch::default();\n            for (input_ix, outlet) in tract_itertools::izip!(input_mapping, body.input_outlets()?) {\n                let tap = patch.tap_model(model, node.inputs[*input_ix])?;\n                inner_mapping.insert(*outlet, tap);\n            }\n            for node in body.eval_order()? {\n                if Graph::is_source(&body.node(node).op) {\n                    continue;\n                }\n                let node_inputs =\n                    body.node(node).inputs.iter().map(|o| inner_mapping[o]).collect::<TVec<_>>();\n                let node_outputs =\n                    patch.wire_node(&body.node(node).name, &body.node(node).op, &node_inputs)?;\n                for (slot_ix, outlet) in node_outputs.iter().enumerate() {\n                    inner_mapping.insert((node, slot_ix).into(), *outlet);\n                }\n            }\n            for (ix, output) in body.outputs.iter().enumerate() {\n                patch.shunt_outside(model, OutletId::new(node.id, ix), inner_mapping[output])?;\n            }\n            Ok(Some(patch))\n        } else {\n            Ok(None)\n        }\n    }\n\n    as_op!();\n}\n\nimpl EvalOp for IfThenElse {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let cond = inputs[0].cast_to_scalar::<bool>()?;\n        let (input_mapping, body) = if cond {\n            (&self.then_input_mapping, &self.then_body)\n        } else {\n            (&self.else_input_mapping, &self.else_body)\n        };\n        let inputs: TVec<TValue> = input_mapping.iter().map(|&ix| inputs[ix].clone()).collect();\n        body.clone().into_runnable()?.run(inputs)\n    }\n}\n"
  },
  {
    "path": "core/src/ops/logic.rs",
    "content": "#![allow(clippy::bool_comparison)]\n#![allow(clippy::unnecessary_cast)]\n\nmod comparison;\nmod ite;\npub use comparison::{CompEq, CompGT, CompGTE, CompLT, CompLTE, CompNE};\npub use comparison::{comp_eq, comp_gt, comp_gte, comp_lt, comp_lte, comp_ne};\npub use ite::IfThenElse;\n\nuse ndarray::*;\n\nuse crate::broadcast::multi_broadcast;\nuse crate::internal::*;\n\nbin_to_super_type!(and, And,\n                   neutral_element: 1,\n                   absorbing_element: 0,\n                   [bool, u8, u16, u32, u64, i8, i16, i32, i64] => |c, &a, &b| *c = (a as i64 != 0 && b as i64 != 0) as _);\nbin_to_super_type!(or, Or,\n                   neutral_element: 0,\n                   absorbing_element: 1,\n                   [bool, u8, u16, u32, u64, i8, i16, i32, i64] => |c, &a, &b| *c = (a as i64 != 0 || b as i64 != 0) as _);\nbin_to_super_type!(xor, Xor, declutter: declutter_xor, neutral_element: 0, [bool] => |c, &a, &b| *c = a ^ b);\n\nfn declutter_xor(\n    _op: &Xor,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    // Xor(x, 1) = Not(x)\n    if let Some(uniform) = crate::ops::binary::one_input_is_uniform(model, node)? {\n        if tensor0(1i64).close_enough(&uniform.uni, false).is_ok() {\n            return Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &[uniform.var],\n                crate::ops::element_wise::ElementWiseOp(Box::new(Not {}), None),\n            )?));\n        }\n    }\n    Ok(None)\n}\n\nelement_wise!(not, Not, [bool] => |_, vs| {\n    vs.iter_mut().for_each(|a| *a = !*a);\n    Ok(())\n});\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Iff;\n\nimpl Iff {\n    pub unsafe fn eval_t<T: Datum>(\n        cond: &ArrayViewD<bool>,\n        out: &mut Tensor,\n        t: &Tensor,\n        f: &Tensor,\n    ) {\n        unsafe {\n            Zip::from(out.to_array_view_mut_unchecked::<T>())\n                .and_broadcast(cond)\n                .and_broadcast(t.to_array_view_unchecked::<T>())\n                .and_broadcast(f.to_array_view_unchecked::<T>())\n                .for_each(|r, c, t, f| *r = if *c { t.clone() } else { f.clone() })\n        }\n    }\n}\n\nimpl Op for Iff {\n    fn name(&self) -> StaticName {\n        \"Iff\".into()\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Iff {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (cond, t, f) = args_3!(inputs);\n        anyhow::ensure!(t.datum_type() == f.datum_type());\n        let shape: TVec<usize> = multi_broadcast(&[cond.shape(), t.shape(), f.shape()])?;\n        unsafe {\n            let mut result = Tensor::uninitialized_dt(t.datum_type(), &shape)?;\n            let cond = cond.to_plain_array_view::<bool>()?;\n            dispatch_datum_by_size!(Self::eval_t(t.datum_type())(&cond, &mut result, &t, &f));\n            Ok(tvec!(result.into_tvalue()))\n        }\n    }\n}\n\npub fn sym_to_coord_axis(sym: &Symbol) -> Option<usize> {\n    format!(\"{sym}\").strip_prefix(\"🎯\")?.parse::<usize>().ok()\n}\n\npub(crate) fn coord_bound_assertions(expr: &TDim, shape: &ShapeFact) -> Vec<Assertion> {\n    expr.symbols()\n        .into_iter()\n        .filter_map(|s| sym_to_coord_axis(&s).filter(|k| *k < shape.rank()).map(|k| (k, s)))\n        .flat_map(|(k, sym)| {\n            [\n                Assertion::GTE(TDim::Sym(sym.clone()), TDim::Val(0)),\n                Assertion::LTE(TDim::Sym(sym), shape[k].clone() - TDim::Val(1)),\n            ]\n        })\n        .collect()\n}\n\npub(crate) fn is_provably_all_false(expr: &TDim, shape: &ShapeFact) -> bool {\n    let extra = coord_bound_assertions(expr, shape);\n    expr.clone().simplify_with_extra_assertions(&extra) == TDim::Val(0)\n}\n\npub(crate) fn is_provably_all_true(expr: &TDim, shape: &ShapeFact) -> bool {\n    let extra = coord_bound_assertions(expr, shape);\n    expr.clone().simplify_with_extra_assertions(&extra) == TDim::Val(1)\n}\n\n/// The interval of indices along one axis where a boolean condition is true.\n///\n/// `None` on a bound means \"open\" — start defaults to 0, end defaults to `dim`.\n///\n/// | `start`   | `end`        | meaning                           |\n/// |-----------|--------------|-----------------------------------|\n/// | `None`    | `None`       | whole dimension (AllTrue)         |\n/// | `None`    | `Some(0)`    | empty (AllFalse)                  |\n/// | `None`    | `Some(e)`    | `[0, e)` — lower region true      |\n/// | `Some(s)` | `None`       | `[s, dim)` — upper region true    |\n/// | `Some(s)` | `Some(e)`    | `[s, e)` — three zones            |\n#[derive(Debug, Clone)]\npub(crate) struct TrueRange {\n    pub axis: usize,\n    pub start: Option<TDim>, // None = 0\n    pub end: Option<TDim>,   // None = dim\n}\n\nimpl TrueRange {\n    /// Condition is true for the entire dimension.\n    pub fn is_full(&self) -> bool {\n        self.start.is_none() && self.end.is_none()\n    }\n    /// Condition is never true (empty range).\n    pub fn is_empty(&self) -> bool {\n        match (&self.start, &self.end) {\n            (None, Some(e)) => *e == TDim::Val(0),\n            (Some(s), Some(e)) => s == e,\n            _ => false,\n        }\n    }\n}\n\npub(crate) fn classify_true_range(expr: &TDim, shape: &ShapeFact) -> Option<TrueRange> {\n    fn try_ge(ge: &TDim, shape: &ShapeFact) -> Option<(usize, TDim)> {\n        if let TDim::Ge(lhs, rhs) = ge {\n            if let TDim::Sym(sym) = &**lhs {\n                let k = sym_to_coord_axis(sym)?;\n                if k < shape.rank() && !rhs.symbols().contains(sym) {\n                    return Some((k, *rhs.clone()));\n                }\n            }\n        }\n        None\n    }\n\n    let simplified = expr.clone().simplify();\n    // All-false: empty range on axis 0\n    if simplified == TDim::Val(0) || is_provably_all_false(&simplified, shape) {\n        return Some(TrueRange { axis: 0, start: None, end: Some(TDim::Val(0)) });\n    }\n    // All-true: open (unbounded) range on axis 0\n    if simplified == TDim::Val(1) || is_provably_all_true(&simplified, shape) {\n        return Some(TrueRange { axis: 0, start: None, end: None });\n    }\n    // Ge(x_k, split): true when x_k >= split → [split, dim)\n    if let Some((axis, split)) = try_ge(&simplified, shape) {\n        return Some(TrueRange { axis, start: Some(split), end: None });\n    }\n    // 1 - Ge(x_k, split): true when x_k < split → [0, split)\n    let flipped = (TDim::Val(1) - simplified).simplify();\n    if let Some((axis, split)) = try_ge(&flipped, shape) {\n        return Some(TrueRange { axis, start: None, end: Some(split) });\n    }\n    None\n}\n\nimpl TypedOp for Iff {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() == 3, \"Iff expects 3 intputs.\");\n        ensure!(inputs[1].datum_type == inputs[2].datum_type);\n        ensure!(inputs[0].datum_type.is::<bool>());\n        ensure!(inputs[0].rank() == inputs[1].rank());\n        ensure!(inputs[0].rank() == inputs[2].rank());\n        let shape = multi_broadcast(&[\n            inputs[0].shape.to_tvec(),\n            inputs[1].shape.to_tvec(),\n            inputs[2].shape.to_tvec(),\n        ])\n        .unwrap();\n        let mut fact = inputs[1].datum_type.fact(shape);\n        // Propagate uniform_tdim when condition is provably constant\n        fact.uniform_tdim = match inputs[0].uniform_tdim.as_ref().map(|d| d.clone().simplify()) {\n            Some(TDim::Val(0)) => inputs[2].uniform_tdim.clone(), // always false → false branch\n            Some(TDim::Val(_)) => inputs[1].uniform_tdim.clone(), // always true → true branch\n            _ => None,\n        };\n        Ok(tvec!(fact))\n    }\n\n    fn input_roi(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TVec<Option<TDim>>>> {\n        // Introduction: condition's uniform_tdim defines which positions matter\n        // for the true-branch (scores) input.\n        let cond_fact = model.outlet_fact(node.inputs[0])?;\n        if let Some(mask_expr) = &cond_fact.uniform_tdim {\n            return Ok(Some(tvec![None, Some(mask_expr.clone()), None]));\n        }\n        // Bubbling: delegate to the natural blanket implementation.\n        crate::optim::propagate_roi::bubble_roi(model, node)\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        // Fold Iff(const, t, f) → t or f.\n        // Symbolic uniform_tdim cases are handled upstream by FoldUniformMask,\n        // which injects a concrete Const(0/1) that this rule then folds.\n        let cond_fact = model.outlet_fact(node.inputs[0])?;\n        rule_if_some!(uniform = &cond_fact.uniform);\n        let Ok(cond_val) = uniform.cast_to_scalar::<bool>() else { return Ok(None) };\n        let branch = if cond_val { node.inputs[1] } else { node.inputs[2] };\n        let mut patch = TypedModelPatch::default();\n        let wire = patch.tap_model(model, branch)?;\n        patch.shunt_outside(model, node.id.into(), wire)?;\n        Ok(Some(patch))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n}\n\nbin_to_super_type!(bitand, BitAnd,\n                   absorbing_element: 0,\n                   [bool, u8, u16, u32, u64, i8, i16, i32, i64] => |c, &a, &b| *c = a & b);\nbin_to_super_type!(bitor, BitOr,\n                   neutral_element: 0,\n                   [bool, u8, u16, u32, u64, i8, i16, i32, i64] => |c, &a, &b| *c = a | b);\nbin_to_super_type!(bitxor, BitXor,\n                   declutter: declutter_bitxor,\n                   neutral_element: 0,\n                   [bool, u8, u16, u32, u64, i8, i16, i32, i64] => |c, &a, &b| *c = a ^ b);\n\nfn declutter_bitxor(\n    _op: &BitXor,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    // BitXor(x, all_ones) = BitNot(x) — for bool, all_ones = 1\n    if let Some(uniform) = crate::ops::binary::one_input_is_uniform(model, node)? {\n        let var_dt = model.outlet_fact(uniform.var)?.datum_type;\n        let is_all_ones = if var_dt.is::<bool>() {\n            tensor0(1i64).close_enough(&uniform.uni, false).is_ok()\n        } else {\n            tensor0(-1i64).close_enough(&uniform.uni, false).is_ok()\n        };\n        if is_all_ones {\n            return Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &[uniform.var],\n                crate::ops::element_wise::ElementWiseOp(Box::new(BitNot {}), None),\n            )?));\n        }\n    }\n    Ok(None)\n}\n\nelement_wise!(bitnot, BitNot, [bool, u8, u16, u32, u64, i8, i16, i32, i64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = !*x);\n    Ok(())\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::ops::array::TypedConcat;\n    use crate::ops::binary::TypedBinOp;\n    use crate::ops::change_axes::AxisOp;\n\n    /// Test Case 1: Iff where condition is Eq(T, 0) with T >= 1 assertion.\n    /// After declutter, the Iff should fold to the false branch (inputs[2]).\n    #[test]\n    fn iff_fold_case1_eq_t_zero() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        model.symbols.add_assertion(\"T >= 1\")?;\n        let t_sym = model.symbols.sym(\"T\");\n        let t_dim = TDim::Sym(t_sym.clone());\n\n        // Const T (scalar TDim)\n        let t_wire = model.wire_node(\n            \"T\",\n            crate::ops::konst::Const::new(tensor0(t_dim.clone()).into_arc_tensor())?,\n            &[],\n        )?[0];\n\n        // Const 0 (scalar TDim)\n        let zero_wire = model.wire_node(\n            \"zero\",\n            crate::ops::konst::Const::new(tensor0(TDim::Val(0)).into_arc_tensor())?,\n            &[],\n        )?[0];\n\n        // Eq(T, 0) → bool scalar\n        let eq_wire = model.wire_node(\"eq\", TypedBinOp(comp_eq(), None), &[t_wire, zero_wire])?[0];\n\n        // Some data wire for the false branch\n        let data_wire = model.add_source(\"data\", TDim::datum_type().scalar_fact())?;\n\n        // Iff(eq, zero, data) — zero is \"true\" branch, data is \"false\" branch\n        let iff_wire = model.wire_node(\"iff\", Iff, &[eq_wire, zero_wire, data_wire])?[0];\n        model.select_output_outlets(&[iff_wire])?;\n\n        let model = model.into_decluttered()?;\n\n        // The Iff should have been folded away (condition is always false given T >= 1)\n        let iff_count = model.nodes().iter().filter(|n| n.op_as::<Iff>().is_some()).count();\n        assert_eq!(iff_count, 0, \"Expected Iff to be folded, but found {iff_count} Iff nodes\");\n        Ok(())\n    }\n\n    /// Test Case 2: range(0,T,1) → unsqueeze(0) → lt(_, T_unsqueezed) → bitnot → Iff\n    /// The bitnot produces Ge(x1, T), all-false for x1 in [0, T-1].\n    /// After declutter, the Iff should fold to the false branch (data input).\n    #[test]\n    fn iff_fold_case2_not_lt_x1_t() -> TractResult<()> {\n        use crate::ops::array::Range;\n\n        let mut model = TypedModel::default();\n        model.symbols.add_assertion(\"T >= 1\")?;\n        let t_sym = model.symbols.sym(\"T\");\n        let t_dim = TDim::Sym(t_sym.clone());\n\n        // Const start=0 (TDim) and step=1 (TDim) — these get uniform_tdim set in output_facts\n        let start = model.wire_node(\n            \"start\",\n            crate::ops::konst::Const::new(tensor0(TDim::Val(0)).into_arc_tensor())?,\n            &[],\n        )?[0];\n        let step = model.wire_node(\n            \"step\",\n            crate::ops::konst::Const::new(tensor0(TDim::Val(1)).into_arc_tensor())?,\n            &[],\n        )?[0];\n        // T is a dynamic TDim input (not a Const) so Range takes the else branch and\n        // sets uniform_tdim = start + step * x0 = x0\n        let end = model.add_source(\"T_dyn\", TDim::datum_type().scalar_fact())?;\n\n        // Range(start=0, end=T, step=1) → [T] TDim with uniform_tdim = x0\n        let range = model.wire_node(\"range\", Range::new(t_dim.clone()), &[start, end, step])?[0];\n\n        // unsqueeze(0) → [1, T] TDim, remap x0→x1 → uniform_tdim = x1\n        let range_unsq = model.wire_node(\"range_unsq\", AxisOp::Add(0), &[range])?[0];\n\n        // T const for comparison, scalar TDim with uniform_tdim = Sym(T)\n        let t_const = model.wire_node(\n            \"T_const\",\n            crate::ops::konst::Const::new(tensor0(t_dim.clone()).into_arc_tensor())?,\n            &[],\n        )?[0];\n        // unsqueeze T_const → [1,1] TDim to match range_unsq rank\n        let t_unsq = model.wire_node(\"T_unsq\", AxisOp::Add(0), &[t_const])?[0];\n        let t_unsq2 = model.wire_node(\"T_unsq2\", AxisOp::Add(0), &[t_unsq])?[0];\n\n        // lt(range_unsq=[1,T], t_unsq2=[1,1]) → bool [1,T], uniform_tdim = Lt(x1,T)\n        let lt = model.wire_node(\"lt\", TypedBinOp(comp_lt(), None), &[range_unsq, t_unsq2])?[0];\n\n        // bitnot(lt): BitNot doesn't propagate uniform_tdim in output_facts,\n        // but Iff::declutter traces through it to get Not(Lt(x1,T))=Ge(x1,T)\n        let bn = model.wire_node(\"bitnot\", bitnot(), &[lt])?[0];\n\n        // Data source [1, T]\n        let data_shape = tvec![TDim::Val(1), t_dim.clone()];\n        let data = model.add_source(\"data\", TDim::datum_type().fact(data_shape.clone()))?;\n\n        // zeros broadcast to [1, T], uniform_tdim = Val(0)\n        let zero_scalar = model.wire_node(\n            \"zero_s\",\n            crate::ops::konst::Const::new(tensor0(TDim::Val(0)).into_arc_tensor())?,\n            &[],\n        )?[0];\n        let zeros = model.wire_node(\n            \"zeros\",\n            crate::ops::array::MultiBroadcastTo {\n                shape: ShapeFact::from_dims(data_shape.iter().cloned()),\n            },\n            &[zero_scalar],\n        )?[0];\n\n        // Iff(bn, zeros, data): condition Ge(x1,T) is all-false → fold to data\n        let iff = model.wire_node(\"iff\", Iff, &[bn, zeros, data])?[0];\n        model.select_output_outlets(&[iff])?;\n\n        let model = model.into_decluttered()?;\n\n        let iff_count = model.nodes().iter().filter(|n| n.op_as::<Iff>().is_some()).count();\n        assert_eq!(iff_count, 0, \"Expected Iff to be folded, but found {iff_count} Iff nodes\");\n        Ok(())\n    }\n\n    /// Rule 2: condition ge(x2, T/160) over [1,1,1+T/160] → slice+concat, no Iff remaining.\n    #[test]\n    fn iff_split_to_slice_concat() -> TractResult<()> {\n        use crate::ops::array::Range;\n\n        let mut model = TypedModel::default();\n        model.symbols.add_assertion(\"T >= 160\")?;\n        let t_sym = model.symbols.sym(\"T\");\n        let t_dim = TDim::Sym(t_sym.clone());\n\n        // split = T/160\n        let split = t_dim.clone() / 160;\n        // output shape: [1, 1, 1 + T/160]\n        let out_len = TDim::Val(1) + split.clone();\n\n        // Build condition: Range over [0, 1+T/160) on axis 2, then compare >= T/160\n        // We'll construct it directly as a source with the right uniform_tdim.\n        // Simpler: use Range + unsqueeze twice + Ge comparison.\n\n        // Range(0, 1+T/160, 1) → [1+T/160] with uniform_tdim = x0\n        let start = model.wire_node(\n            \"start\",\n            crate::ops::konst::Const::new(tensor0(TDim::Val(0)).into_arc_tensor())?,\n            &[],\n        )?[0];\n        let step = model.wire_node(\n            \"step\",\n            crate::ops::konst::Const::new(tensor0(TDim::Val(1)).into_arc_tensor())?,\n            &[],\n        )?[0];\n        let end_val = model.wire_node(\n            \"end_val\",\n            crate::ops::konst::Const::new(tensor0(out_len.clone()).into_arc_tensor())?,\n            &[],\n        )?[0];\n        let range =\n            model.wire_node(\"range\", Range::new(out_len.clone()), &[start, end_val, step])?[0];\n        // unsqueeze(0): [1, 1+T/160], x0 → x1\n        let r1 = model.wire_node(\"r1\", AxisOp::Add(0), &[range])?[0];\n        // unsqueeze(0): [1, 1, 1+T/160], x1 → x2\n        let r2 = model.wire_node(\"r2\", AxisOp::Add(0), &[r1])?[0];\n\n        // split const\n        let split_const = model.wire_node(\n            \"split_const\",\n            crate::ops::konst::Const::new(tensor0(split.clone()).into_arc_tensor())?,\n            &[],\n        )?[0];\n        // unsqueeze three times so it can broadcast against [1,1,1+T/160]\n        let sc1 = model.wire_node(\"sc1\", AxisOp::Add(0), &[split_const])?[0];\n        let sc2 = model.wire_node(\"sc2\", AxisOp::Add(0), &[sc1])?[0];\n        let sc2 = model.wire_node(\"sc3\", AxisOp::Add(0), &[sc2])?[0];\n\n        // Ge(range_3d, split_3d) → bool [1,1,1+T/160], uniform_tdim = Ge(x2, T/160)\n        let cond = model.wire_node(\"cond\", TypedBinOp(comp_gte(), None), &[r2, sc2])?[0];\n\n        // true and false branches: shape [1,1,1+T/160]\n        let true_branch = model.add_source(\n            \"true_b\",\n            TDim::datum_type().fact(tvec![TDim::Val(1), TDim::Val(1), out_len.clone()]),\n        )?;\n        let false_branch = model.add_source(\n            \"false_b\",\n            TDim::datum_type().fact(tvec![TDim::Val(1), TDim::Val(1), out_len.clone()]),\n        )?;\n\n        let iff = model.wire_node(\"iff\", Iff, &[cond, true_branch, false_branch])?[0];\n        model.select_output_outlets(&[iff])?;\n\n        let model = model.into_decluttered()?;\n\n        let iff_count = model.nodes().iter().filter(|n| n.op_as::<Iff>().is_some()).count();\n        assert_eq!(iff_count, 0, \"Expected no Iff nodes after declutter, found {iff_count}\");\n\n        let concat_count =\n            model.nodes().iter().filter(|n| n.op_as::<TypedConcat>().is_some()).count();\n        assert!(concat_count > 0, \"Expected at least one Concat node after declutter\");\n\n        Ok(())\n    }\n\n    /// Verify that uniform_tdim propagation produces the expected values at each stage.\n    #[test]\n    fn verify_uniform_tdim_propagation() -> TractResult<()> {\n        use crate::ops::array::Range;\n\n        let mut model = TypedModel::default();\n        model.symbols.add_assertion(\"T >= 1\")?;\n        let t_sym = model.symbols.sym(\"T\");\n        let t_dim = TDim::Sym(t_sym.clone());\n\n        let start = model.wire_node(\n            \"start\",\n            crate::ops::konst::Const::new(tensor0(TDim::Val(0)).into_arc_tensor())?,\n            &[],\n        )?[0];\n        let step = model.wire_node(\n            \"step\",\n            crate::ops::konst::Const::new(tensor0(TDim::Val(1)).into_arc_tensor())?,\n            &[],\n        )?[0];\n        let end = model.add_source(\"T_dyn\", TDim::datum_type().scalar_fact())?;\n        let range = model.wire_node(\"range\", Range::new(t_dim.clone()), &[start, end, step])?[0];\n        let range_unsq = model.wire_node(\"range_unsq\", AxisOp::Add(0), &[range])?[0];\n        let t_const = model.wire_node(\n            \"T_const\",\n            crate::ops::konst::Const::new(tensor0(t_dim.clone()).into_arc_tensor())?,\n            &[],\n        )?[0];\n        let t_unsq = model.wire_node(\"T_unsq\", AxisOp::Add(0), &[t_const])?[0];\n        let t_unsq2 = model.wire_node(\"T_unsq2\", AxisOp::Add(0), &[t_unsq])?[0];\n        let lt = model.wire_node(\"lt\", TypedBinOp(comp_lt(), None), &[range_unsq, t_unsq2])?[0];\n\n        let range_fact = model.outlet_fact(range)?;\n        let range_unsq_fact = model.outlet_fact(range_unsq)?;\n        let t_unsq_fact = model.outlet_fact(t_unsq)?;\n        let lt_fact = model.outlet_fact(lt)?;\n\n        assert!(range_fact.uniform_tdim.is_some(), \"range should have uniform_tdim\");\n        assert!(range_unsq_fact.uniform_tdim.is_some(), \"range_unsq should have uniform_tdim\");\n        assert!(t_unsq_fact.uniform_tdim.is_some(), \"t_unsq should have uniform_tdim\");\n        assert!(lt_fact.uniform_tdim.is_some(), \"lt should have uniform_tdim\");\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/macros.rs",
    "content": "#[macro_export]\nmacro_rules! as_op {\n    () => {\n        fn as_op(&self) -> &dyn Op {\n            self\n        }\n\n        fn as_op_mut(&mut self) -> &mut dyn Op {\n            self\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! op_as_typed_op {\n    () => {\n        fn as_typed(&self) -> Option<&dyn TypedOp> {\n            Some(self)\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! not_a_typed_op {\n    () => {\n        fn as_typed(&self) -> Option<&dyn TypedOp> {\n            None\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! args_1 {\n    ($inputs:expr) => {{\n        let mut inputs = $inputs;\n        if inputs.len() != 1 {\n            $crate::internal::bail!(\"Expected 1 arg, got {:?}\", inputs)\n        }\n        let result = inputs.pop().unwrap();\n        result\n    }};\n}\n\n#[macro_export]\nmacro_rules! args_2 {\n    ($inputs:expr) => {{\n        let mut inputs = $inputs;\n        if inputs.len() != 2 {\n            $crate::internal::bail!(\"Expected 2 arg, got {:?}\", inputs)\n        }\n        inputs.reverse();\n        let result = (inputs.pop().unwrap(), inputs.pop().unwrap());\n        result\n    }};\n}\n\n#[allow(unused_macros)]\n#[macro_export]\nmacro_rules! args_3 {\n    ($inputs:expr) => {{\n        let mut inputs = $inputs;\n        if inputs.len() != 3 {\n            $crate::internal::bail!(\"Expected 3 arg, got {:?}\", inputs)\n        }\n        inputs.reverse();\n        let result = (inputs.pop().unwrap(), inputs.pop().unwrap(), inputs.pop().unwrap());\n        result\n    }};\n}\n\n#[allow(unused_macros)]\n#[macro_export]\nmacro_rules! args_4 {\n    ($inputs:expr) => {{\n        let mut inputs = $inputs;\n        if inputs.len() != 4 {\n            $crate::internal::bail!(\"Expected 4 arg, got {:?}\", inputs)\n        }\n        inputs.reverse();\n        let result = (\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n        );\n        result\n    }};\n}\n\n#[allow(unused_macros)]\n#[macro_export]\nmacro_rules! args_5 {\n    ($inputs:expr) => {{\n        let mut inputs = $inputs;\n        if inputs.len() != 5 {\n            $crate::internal::bail!(\"Expected 5 arg, got {:?}\", inputs)\n        }\n        inputs.reverse();\n        let result = (\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n        );\n        result\n    }};\n}\n\n#[allow(unused_macros)]\n#[macro_export]\nmacro_rules! args_6 {\n    ($inputs:expr) => {{\n        let mut inputs = $inputs;\n        if inputs.len() != 6 {\n            $crate::internal::bail!(\"Expected 6 arg, got {:?}\", inputs)\n        }\n        inputs.reverse();\n        let result = (\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n        );\n        result\n    }};\n}\n\n#[allow(unused_macros)]\n#[macro_export]\nmacro_rules! args_7 {\n    ($inputs:expr) => {{\n        let mut inputs = $inputs;\n        if inputs.len() != 7 {\n            $crate::internal::bail!(\"Expected 7 arg, got {:?}\", inputs)\n        }\n        inputs.reverse();\n        let result = (\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n        );\n        result\n    }};\n}\n\n#[allow(unused_macros)]\n#[macro_export]\nmacro_rules! args_8 {\n    ($inputs:expr) => {{\n        let mut inputs = $inputs;\n        if inputs.len() != 8 {\n            $crate::internal::bail!(\"Expected 8 arg, got {:?}\", inputs)\n        }\n        inputs.reverse();\n        let result = (\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n            inputs.pop().unwrap(),\n        );\n        result\n    }};\n}\n\n#[macro_export]\nmacro_rules! assert_close {\n    ($left:expr, $right:expr) => ({\n        match (&$left, &$right) {\n            (left_val, right_val) => {\n                if let Err(e) = left_val.close_enough(right_val, true) {\n                    panic!(r#\"assertion failed: `(left ~ right)`\n  left: `{:?}`,\n right: `{:?}`\n {:?}\"#, left_val, right_val, e)\n                }\n            }\n        }\n    });\n    ($left:expr, $right:expr,) => ({\n        assert_eq!($left, $right)\n    });\n    ($left:expr, $right:expr, $($arg:tt)+) => ({\n        match (&($left), &($right)) {\n            (left_val, right_val) => {\n                if let Err(e) = left_val.close_enough(right_val, true) {\n                    panic!(r#\"assertion failed: `(left ~ right)`\n  left: `{:?}`,\n right: `{:?}`: {}\n {:?}\"#, left_val, right_val,\n                           format_args!($($arg)+), e)\n                }\n            }\n        }\n    });\n}\n\n#[macro_export]\nmacro_rules! trivial_op_state_freeze {\n    ($state:ty) => {\n        impl $crate::ops::FrozenOpState for $state {\n            fn unfreeze(&self) -> Box<dyn OpState> {\n                Box::new(self.clone())\n            }\n        }\n        impl $crate::ops::OpStateFreeze for $state {\n            fn freeze(&self) -> Box<dyn $crate::ops::FrozenOpState> {\n                Box::new(self.clone())\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "core/src/ops/math/complex.rs",
    "content": "use std::iter::once;\n\nuse crate::internal::*;\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct InnerDimToComplex;\n\nimpl Op for InnerDimToComplex {\n    fn name(&self) -> StaticName {\n        \"InnerDimToComplex\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for InnerDimToComplex {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, mut inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(tvec!(reinterpret_inner_dim_as_complex(inputs.remove(0).into_tensor())?.into_tvalue()))\n    }\n}\n\nimpl TypedOp for InnerDimToComplex {\n    #[allow(clippy::into_iter_on_ref)]\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact = inputs[0].without_value();\n        if fact.shape.last() != Some(&2.to_dim()) {\n            bail!(\"Expect inner tensor dimension to be 2\")\n        }\n        fact.shape = fact.shape.into_iter().rev().skip(1).rev().collect();\n        fact.datum_type = fact.datum_type.complexify()?;\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct ComplexToInnerDim;\n\nimpl Op for ComplexToInnerDim {\n    fn name(&self) -> StaticName {\n        \"ComplexToInnerDim\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for ComplexToInnerDim {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, mut inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(tvec!(reinterpret_complex_as_inner_dim(inputs.remove(0).into_tensor())?.into_tvalue()))\n    }\n}\n\nimpl TypedOp for ComplexToInnerDim {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact = inputs[0].without_value();\n        fact.shape = fact.shape.iter().cloned().chain(once(2.to_dim())).into();\n        fact.datum_type = fact.datum_type.decomplexify()?;\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/math/mod.rs",
    "content": "#![allow(clippy::clone_on_copy)]\n#![allow(clippy::unnecessary_cast)]\n#![allow(clippy::blocks_in_conditions)]\n\nuse super::array::MultiBroadcastTo;\nuse super::binary::TypedBinOp;\nuse crate::internal::*;\nuse crate::ops::quant::scale_by;\nuse num_traits::bounds::Bounded;\nuse num_traits::int::PrimInt;\nuse num_traits::{Float, Zero};\nuse tract_data::internal::ClampCast;\nuse tract_data::itertools::Itertools;\npub use tract_data::prelude::round_ties_to_even;\nuse tract_linalg::{ScaleShiftAndRound, Scaler};\nuse tract_num_traits::AsPrimitive;\n\n#[cfg(feature = \"complex\")]\nmod complex;\n#[cfg(feature = \"complex\")]\npub use complex::{ComplexToInnerDim, InnerDimToComplex};\n\nbin_to_super_type!(add, Add,\n                   linalg: Add,\n                   neutral_element: 0,\n                   validation: Validation::Rounding,\n                   q: [i8, u8, i32, i32] => add_quant;\n                   q_op_on_f32: |a: f32, b: f32| -> f32 {a+b},\n                   [f32, i8, i16, i32, i64, u8, u16, u32, u64, f16, f64, TDim, String] => |c, a, b| *c = a.clone() + b);\n\nfn add_quant<T>(c: &mut T, a: &T, b: &T, zp: i32, _: f32)\nwhere\n    T: PrimInt + Bounded + AsPrimitive<i64> + Datum,\n    i64: AsPrimitive<T>,\n{\n    *c = (a.as_() + b.as_() - zp as i64).clamp_cast()\n}\n\nbin_to_super_type!(sub, Sub,\n                   linalg:Sub,\n                   is_commutative: false,\n                   neutral_element: 0,\n                   q: [i8, u8, i32, i32] => sub_quant;\n                   q_op_on_f32: |a: f32, b: f32| -> f32 {a-b},\n                   [f32, i8, i16, i32, i64, u8, u16, u32, u64, f16, f64, TDim] => |c, a, b| *c = a.clone() - b);\n\nbin_to_super_type!(subf, SubF,\n                   linalg:SubF,\n                   is_commutative: false,\n                   neutral_element: 0,\n                   q: [i8, u8, i32, i32] => subf_quant;\n                   q_op_on_f32: |a: f32, b: f32| -> f32 {b - a},\n                   [f32, i8, i16, i32, i64, u8, u16, u32, u64, f16, f64, TDim] => |c, a, b| *c = b.clone() - a);\n\nfn sub_quant<T>(c: &mut T, a: &T, b: &T, zp: i32, _: f32)\nwhere\n    T: PrimInt + Bounded + AsPrimitive<i16> + Datum,\n    i16: AsPrimitive<T>,\n{\n    *c = (a.as_() - b.as_() + zp as i16).clamp_cast()\n}\n\nfn subf_quant<T>(c: &mut T, a: &T, b: &T, zp: i32, _: f32)\nwhere\n    T: PrimInt + Bounded + AsPrimitive<i16> + Datum,\n    i16: AsPrimitive<T>,\n{\n    *c = (b.as_() - a.as_() + zp as i16).clamp_cast()\n}\n\nbin_to_super_type!(mul, Mul,\n                   cost: |dt| tvec!((Cost::FMA(dt), 1)),\n                   declutter: declutter_mul,\n                   eval_override: |a:TValue, b: TValue, c_dt: DatumType| -> TractResult<Tensor> {\n                    // we apply only if type is QU8 zp_scale datum type\n                    if let (DatumType::QU8(QParams::ZpScale {zero_point: a_zp, scale: a_scale}),\n                            DatumType::QU8(QParams::ZpScale {zero_point: b_zp, scale: b_scale}),\n                            DatumType::QU8(QParams::ZpScale {zero_point: c_zp, scale: c_scale})) =\n                        (a.datum_type(), b.datum_type(), c_dt)\n                    {\n                           let multiplier = a_scale  * b_scale * (1.0/ c_scale);\n                           let a = a.to_plain_array_view::<u8>()?;\n                           let b = b.to_plain_array_view::<u8>()?;\n                           let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()]).context(\"no broadcast solution\")?;\n                           let mut c = Tensor::zero_dt(c_dt, &c_shape)?;\n                           let mut c_plain = c.try_as_plain_mut()?;\n                           let view = c_plain.to_array_view_mut::<u8>()?;\n                           crate::ndarray::Zip::from(view)\n                               .and_broadcast(a)\n                               .and_broadcast(b)\n                               .for_each(|c,a,b| *c = (scale_by((*a as i32 - a_zp as i32) * (*b as i32 - b_zp as i32), multiplier) + c_zp as i32).clamp_cast());\n                           Ok(c)\n                        } else {\n                            Mul.generic_eval(a, b, c_dt)\n                        }\n                    },\n                   linalg: Mul,\n                   neutral_element: 1,\n                   out_of_place: |c:&mut Tensor, a:&Tensor, b: &Tensor| -> TractResult<bool> {\n                       if c.datum_type() == TDim::datum_type() &&\n                           a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {\n                               let a = a.to_plain_array_view::<TDim>()?;\n                               let b = b.cast_to::<i32>()?;\n                               let b = b.to_plain_array_view::<i32>()?;\n                               let mut c_plain = c.try_as_plain_mut()?;\n                               let c = c_plain.to_array_view_mut::<TDim>()?;\n                               crate::ndarray::Zip::from(c).and_broadcast(a).and_broadcast(b).for_each(|c,a,b| *c = a.clone() * *b);\n                               Ok(true)\n                           }\n                       else {\n                           match c.datum_type() {\n                               DatumType::QI8(params) => {\n                                   let (zp, scale) = params.zp_scale();\n                                   let a = a.to_plain_array_view::<i8>()?;\n                                   let b = b.to_plain_array_view::<i8>()?;\n                                   let mut c_plain = c.try_as_plain_mut()?;\n                                   let c = c_plain.to_array_view_mut::<i8>()?;\n                                   crate::ndarray::Zip::from(c)\n                                       .and_broadcast(a)\n                                       .and_broadcast(b)\n                                       .for_each(|c,a,b| *c = (scale_by((*a as i16 - zp as i16) * (*b as i16 - zp as i16), scale) + zp as i16).clamp_cast());\n                                   Ok(true)\n                               }\n                               DatumType::QU8(params) => {\n                                   let (zp, scale) = params.zp_scale();\n                                   let a = a.to_plain_array_view::<u8>()?;\n                                   let b = b.to_plain_array_view::<u8>()?;\n                                   let mut c_plain = c.try_as_plain_mut()?;\n                                   let c = c_plain.to_array_view_mut::<u8>()?;\n                                   crate::ndarray::Zip::from(c)\n                                       .and_broadcast(a)\n                                       .and_broadcast(b)\n                                       .for_each(|c,a,b| *c = (scale_by((*a as i32 - zp as i32) * (*b as i32 - zp as i32), scale) + zp as i32).clamp_cast());\n                                   Ok(true)\n                               }\n                               _ => Ok(false)\n                           }\n                       }\n                   },\n                   q: [i8, u8, i32] => |c, a, b, zp, scale| {\n                    *c = (scale_by((a.clone() as i32 - zp as i32) * (*b as i32 - zp as i32) , scale) + zp as i32).clamp_cast()\n                   };\n                   q_op_on_f32: |a: f32, b: f32| a * b,\n                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = a.wrapping_mul(*b),\n                   [f32, f16, f64] => |c, a, b| *c = a * b,\n                   [TDim] => |c, a, b| *c = a.clone() * b\n);\n\nbin_to_super_type!(div, Div,\ncost: |dt| tvec!((Cost::Div(dt), 1)),\ndeclutter: declutter_div,\neval_override: |a:TValue, b: TValue, c_dt: DatumType| -> TractResult<Tensor> {\n    if\n        a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {\n            let a = a.to_plain_array_view::<TDim>()?;\n            let b = b.to_plain_array_view::<TDim>()?;\n            let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()]).context(\"no broadcast solution\")?;\n            unsafe {\n                let a = a.broadcast(&*c_shape).unwrap();\n                let b = b.broadcast(&*c_shape).unwrap();\n                let mut c = Tensor::uninitialized_dt(DatumType::TDim, &c_shape)?;\n                let mut c_plain = c.try_as_plain_mut()?;\n                let mut view = c_plain.to_array_view_mut::<TDim>()?;\n                for coords in crate::ndarray::indices(&*c_shape) {\n                    let (p, q) = a[&coords].maybe_div(&b[&coords])?;\n                    view[&coords] = p/q;\n                }\n                Ok(c)\n            }\n        } else if let (DatumType::QU8(QParams::ZpScale {zero_point: a_zp, scale: a_scale}),\n                       DatumType::QU8(QParams::ZpScale {zero_point: b_zp, scale: b_scale}),\n                       DatumType::QU8(QParams::ZpScale {zero_point: c_zp, scale: c_scale})) =\n                (a.datum_type(), b.datum_type(), c_dt) {\n\n               let multiplier = a_scale / (b_scale * c_scale);\n                let a = a.to_plain_array_view::<u8>()?;\n                let b = b.to_plain_array_view::<u8>()?;\n                let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()]).context(\"no broadcast solution\")?;\n                let mut c = Tensor::zero_dt(c_dt, &c_shape)?;\n                let mut c_plain = c.try_as_plain_mut()?;\n                let view = c_plain.to_array_view_mut::<u8>()?;\n                crate::ndarray::Zip::from(view)\n                    .and_broadcast(a)\n                    .and_broadcast(b)\n                    // maintain division in f32 before rescale to maintain high accuracy\n                    .for_each(|c,a,b| *c = (\n                            scale_by(\n                                (*a as i32 - a_zp as i32) as f32 / (*b as i32 - b_zp as i32) as f32, multiplier\n                            ) as i32 + c_zp as i32\n                        ).clamp_cast());\n                Ok(c)\n        } else {\n            Div.generic_eval(a, b, c_dt)\n        }\n},\nis_commutative: false,\nneutral_element: 1,\nout_of_place: |c:&mut Tensor, a:&Tensor, b: &Tensor| -> TractResult<bool> {\n    if c.datum_type() == TDim::datum_type() &&\n        a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {\n            let a = a.to_plain_array_view::<TDim>()?;\n            let b = b.cast_to::<i32>()?;\n            let b = b.to_plain_array_view::<i32>()?;\n            let mut c_plain = c.try_as_plain_mut()?;\n            let c = c_plain.to_array_view_mut::<TDim>()?;\n            crate::ndarray::Zip::from(c).and_broadcast(a).and_broadcast(b).for_each(|c,a,b| *c = a.clone() / *b);\n            Ok(true)\n        } else if c.datum_type().is_quantized() || b.datum_type().is_quantized() || a.datum_type().is_quantized() {\n            let a_f32 = a.cast_to::<f32>()?;\n            let a_f32 = a_f32.to_plain_array_view::<f32>()?;\n            let b_f32 = b.cast_to::<f32>()?;\n            let b_f32 = b_f32.to_plain_array_view::<f32>()?;\n            let c_f32 = &a_f32 / &b_f32;\n            *c = c_f32.into_tensor().cast_to_dt(c.datum_type())?.into_owned();\n            Ok(true)\n        } else {\n            Ok(false)\n        }\n},\nq_op_on_f32: |a: f32, b: f32| a / b,\n[f32, i8, i16, i32, i64, u8, u16, u32, u64, f16, f64] => |c, a, b| *c = a.clone() / b\n);\n\nbin_to_super_type!(rem, Rem,\n                                      eval_override: |a:TValue, b: TValue, c_dt: DatumType| -> TractResult<Tensor> {\n                                          if\n                                              a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {\n                                                  let a = a.to_plain_array_view::<TDim>()?;\n                                                  let b = b.cast_to::<i32>()?;\n                                                  let b = b.to_plain_array_view::<i32>()?;\n                                                  let c_shape = crate::broadcast::multi_broadcast(&[a.shape(), b.shape()]).context(\"no broadcast solution\")?;\n                                                  unsafe {\n                                                      let mut c = Tensor::uninitialized_dt(DatumType::TDim, &c_shape)?;\n                                                      let mut c_plain = c.try_as_plain_mut()?;\n                                                      let view = c_plain.to_array_view_mut::<TDim>()?;\n                                                      crate::ndarray::Zip::from(view).and_broadcast(a).and_broadcast(b).for_each(|c,a,b| *c = a.clone() % *b);\n                                                      Ok(c)\n                                                  }\n                                              } else {\n                                                  Rem.generic_eval(a,b, c_dt)\n                                              }\n                                      },\n                                      out_of_place: |c:&mut Tensor, a:&Tensor, b: &Tensor| -> TractResult<bool> {\n                                          if c.datum_type() == TDim::datum_type() &&\n                                              a.datum_type() == TDim::datum_type() && b.datum_type() == TDim::datum_type() {\n                                                  let a = a.to_plain_array_view::<TDim>()?;\n                                                  let b = b.cast_to::<i32>()?;\n                                                  let b = b.to_plain_array_view::<i32>()?;\n                                                  let mut c_plain = c.try_as_plain_mut()?;\n                                                  let c = c_plain.to_array_view_mut::<TDim>()?;\n                                                  crate::ndarray::Zip::from(c).and_broadcast(a).and_broadcast(b).for_each(|c,a,b| *c = a.clone() % *b);\n                                                  Ok(true)\n                                              } else {\n                                                  Ok(false)\n                                              }\n                                      },\n                                      [f32, i8, i16, i32, i64, u8, u16, u32, u64, f16, f64] => |c, a, b| *c = a.clone() % b);\n\nbin_to_super_type!(min, Min, linalg:Min,\n                   q: [i8, u8, i32] => |c, a, b, _, _| *c = if a < b { *a } else { *b };\n                   q_op_on_f32: |a: f32, b: f32| a.min(b),\n                   [f16, f32, f64] => |c,a,b| *c = a.min(*b),\n                   [TDim] => |c,a,b| *c = a.clone().mini(b.clone()),\n                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = *a.min(b));\n\nbin_to_super_type!(max, Max,\n                   eval_override: |a:TValue, b: TValue, c_dt: DatumType| -> TractResult<Tensor> {\n                   // Attempt to optimize relu case\n                    if let (DatumType::QU8(QParams::ZpScale {zero_point: a_zp, scale: a_scale}),\n                            DatumType::QU8(QParams::ZpScale {zero_point: b_zp, scale: b_scale}),\n                            DatumType::QU8(QParams::ZpScale {zero_point: c_zp, scale: c_scale})) =\n                        (a.datum_type(), b.datum_type(), c_dt)\n                        && (a.is_uniform() || b.is_uniform()) {\n                            // select e between a and b as uniform if exist\n                            // and d remaining a or b\n                            let (d, d_zp, d_scale, e, e_zp, e_scale) = if a.is_uniform() && !b.is_uniform() {\n                                (&b, &b_zp, &b_scale, &a, &a_zp, &a_scale)\n                            } else {\n                                (&a, &a_zp, &a_scale, &b, &b_zp, &b_scale)\n                            };\n                            if e.is_uniform() { // may be relu or any scalar\n                                let e = e.cast_to::<u8>()?.try_as_plain()?.as_slice::<u8>()?[0];\n                                let e_val_as_d_aligned: i32 = scale_by(e as i32 - e_zp, e_scale / d_scale);\n                                let multiplier = d_scale  * (1.0/ c_scale);\n                                let d = d.to_plain_array_view::<u8>()?;\n                                let mut c = Tensor::zero_dt(c_dt, d.shape())?;\n                                let mut c_plain = c.try_as_plain_mut()?;\n                                let view = c_plain.to_array_view_mut::<u8>()?;\n                                crate::ndarray::Zip::from(view)\n                                    .and_broadcast(d)\n                                    .for_each(|c,d| {\n                                        let d_min_zp = *d as i32 - *d_zp as i32;\n                                        let c_val: i32 = if d_min_zp < e_val_as_d_aligned {\n                                            e_val_as_d_aligned\n                                        } else {\n                                            d_min_zp\n                                        };\n                                        *c = (scale_by(c_val, multiplier) + c_zp as i32).clamp_cast();\n                                    });\n                                return Ok(c)\n                            }\n                        }\n                    Max.generic_eval(a, b, c_dt)\n                   },\n                   linalg:Max,\n                   q: [i8, u8, i32] => |c, a, b, _, _| *c = if a < b { *b } else { *a };\n                   q_op_on_f32: |a: f32, b: f32| -> f32 {a.max(b)},\n                   [f16, f32, f64] => |c,a,b| *c = a.max(*b),\n                   [TDim] => |c,a,b| *c = a.clone().maxi(b.clone()),\n                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = *a.max(b));\n\nbin_to_super_type!(pow, Pow,\n                   declutter: declutter_pow,\n                   is_commutative: false,\n                   neutral_element: 1,\n                   q_op_on_f32: |a: f32, b: f32| -> f32 {a.powf(b)},\n                   [f16, f32, f64] => |c,a,b| *c = a.powf(*b),\n                   [i32, i64] => |c,a,b| *c = a.pow(*b as u32));\n\nbin_to_super_type!(shift_left, ShiftLeft,\n                   is_commutative: false,\n                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = *a << *b);\nbin_to_super_type!(shift_right, ShiftRight,\n                   is_commutative: false,\n                   [i8, i16, i32, i64, u8, u16, u32, u64] => |c, a, b| *c = *a >> *b);\n\nfn declutter_mul(\n    _op: &Mul,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    if node.inputs[0] == node.inputs[1] && !node.outputs[0].fact.datum_type.is_quantized() {\n        return Ok(Some(TypedModelPatch::replace_single_op(\n            model,\n            node,\n            &node.inputs[0..1],\n            square(),\n        )?));\n    }\n\n    if let Some(uniform) = crate::ops::binary::one_input_is_uniform(model, node)? {\n        let var_fact = model.outlet_fact(uniform.var)?;\n        if uniform.uni.cast_to_scalar::<f64>()? == 0.0 {\n            let shapes =\n                model.node_input_facts(node.id)?.iter().map(|f| &f.shape).collect::<TVec<_>>();\n            let shape: ShapeFact =\n                crate::broadcast::multi_broadcast(&shapes).context(\"Failed to broadcast\")?.into();\n            return Ok(Some(TypedModelPatch::rewire(\n                model,\n                &[],\n                &[node.id.into()],\n                &|patch, _| {\n                    let scalar = patch.add_const(\n                        format!(\"{}.zero\", node.name),\n                        if uniform.uni.datum_type().is_quantized() {\n                            let output_dt = node.outputs[0].fact.datum_type;\n                            Arc::new(uniform.uni.clone().cast_to_dt(output_dt)?.into_owned())\n                        } else {\n                            uniform.uni.clone()\n                        },\n                    )?;\n                    let op = MultiBroadcastTo::new(shape.clone());\n                    patch.wire_node(&node.name, op, &[scalar])\n                },\n            )?));\n        }\n        let dt = uniform.uni.datum_type();\n        if !dt.is_quantized() {\n            // avoid cast potential with Q tensor\n            let integer = uniform.uni.cast_to_scalar::<i64>()?;\n            if tensor0(integer)\n                .cast_to_dt(uniform.uni.datum_type())?\n                .close_enough(&uniform.uni, false)\n                .is_ok()\n                && uniform.uni.cast_to_scalar::<i64>()?.count_ones() == 1\n                && dt.is_integer()\n            {\n                let shift = integer.trailing_zeros();\n                return Ok(Some(TypedModelPatch::rewire(\n                    model,\n                    &[uniform.var],\n                    &[node.id.into()],\n                    &|patch, taps| {\n                        let shift = patch.add_const(\n                            format!(\"{}.shift\", node.name),\n                            tensor0(shift)\n                                .cast_to_dt(dt)?\n                                .into_owned()\n                                .broadcast_into_rank(var_fact.rank())?,\n                        )?;\n                        patch.wire_node(&node.name, shift_left(), &[taps[0], shift])\n                    },\n                )?));\n            }\n        }\n    }\n    if let Some(patch) = declutter_mul_const_mul_const(model, node)? {\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n\nfn declutter_mul_const_mul_const(\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    let input_facts = model.node_input_facts(node.id)?;\n    rule_if_some!(const_slot = input_facts.iter().position(|f| f.konst.is_some()));\n    let prec = model.node(node.inputs[1 - const_slot].node);\n    rule_if_some!(prec_mul = prec.op_as::<TypedBinOp>());\n    rule_if!(prec.outputs[0].successors.len() <= 1);\n    rule_if!(prec_mul.0.is::<Mul>());\n    let prec_input_facts = model.node_input_facts(prec.id)?;\n    rule_if_some!(prec_const_slot = prec_input_facts.iter().position(|f| f.konst.is_some()));\n\n    let const_fact = model.outlet_fact(node.inputs[const_slot])?;\n    let prec_const_fact = model.outlet_fact(prec.inputs[prec_const_slot])?;\n    // todo: extend to anything broadcast compatible\n    rule_if!(const_fact.shape.volume().is_one() || prec_const_fact.shape.volume().is_one());\n    rule_if!(const_fact.datum_type.is_float());\n    let result = mul()\n        .eval(tvec!(\n            const_fact.konst.clone().unwrap().into_tvalue(),\n            prec_const_fact.konst.clone().unwrap().into_tvalue()\n        ))?\n        .remove(0)\n        .into_arc_tensor();\n    let mut patch = TypedModelPatch::default();\n    let konst = patch.add_const(&prec.name, result)?;\n    let input_tap = patch.tap_model(model, prec.inputs[1 - prec_const_slot])?;\n    let wire = patch.wire_node(&node.name, mul(), &[konst, input_tap])?;\n    patch.shunt_outside(model, node.id.into(), wire[0])?;\n    Ok(Some(patch))\n}\n\nfn declutter_div(\n    _op: &Div,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    if let &[p, q] = &*model.node_input_facts(node.id)? {\n        let dt = q.datum_type;\n        if let Some(q) = &q.uniform\n            && let Ok(integer) = q.cast_to_scalar::<i64>()\n            && tensor0(integer).cast_to_dt(dt)?.close_enough(q, false).is_ok()\n            && dt.is_integer()\n            && q.cast_to_scalar::<i64>()?.count_ones() == 1\n        {\n            let shift = integer.trailing_zeros();\n            return Ok(Some(TypedModelPatch::rewire(\n                model,\n                &[node.inputs[0]],\n                &[node.id.into()],\n                &|patch, taps| {\n                    let shift = patch.add_const(\n                        format!(\"{}.shift\", node.name),\n                        tensor0(shift)\n                            .cast_to_dt(dt)?\n                            .into_owned()\n                            .broadcast_into_rank(p.rank())?,\n                    )?;\n                    patch.wire_node(&node.name, shift_right(), &[taps[0], shift])\n                },\n            )?));\n        }\n        if dt.is_float() {\n            return Ok(Some(TypedModelPatch::rewire(\n                model,\n                &node.inputs,\n                &[node.id.into()],\n                &|patch, taps| {\n                    let q =\n                        patch.wire_node(format!(\"{}-recip\", node.name), recip(), &[taps[1]])?[0];\n                    patch.wire_node(&node.name, mul(), &[taps[0], q])\n                },\n            )?));\n        }\n    }\n    Ok(None)\n}\n\nfn declutter_pow(\n    _op: &Pow,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    let b = model.outlet_fact(node.inputs[1])?;\n    if let Some(b) = &b.uniform {\n        let b = b.cast_to_scalar::<f32>()?;\n        if b == 2.0 {\n            return Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &[node.inputs[0]],\n                square(),\n            )?));\n        } else if b == 0.5 {\n            return Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &[node.inputs[0]],\n                sqrt(),\n            )?));\n        }\n    }\n    crate::ops::nn::gelu_approximate::detect_gelu_approx(_op, model, node)\n}\n\nelement_wise!(abs, Abs, [i8, i16, i32, i64, f16, f32, i32] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.abs());\n    Ok(())\n};\nq: [i8, u8, i32, i32] => f32::abs;\noperating_datum_type: |dt| if dt == TDim::datum_type() { i64::datum_type() } else { dt }\n);\n\nelement_wise!(exp, Exp, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.exp());\n    Ok(())\n};\nq: [i8, u8, i32, i32] => f32::exp;\nvalidation: Validation::Rounding\n);\n\nelement_wise!(ln, Ln, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.ln());\n    Ok(())\n};\nq: [i8, u8, i32, i32] => f32::ln;\nvalidation: Validation::Rounding\n);\n\nelement_wise!(square, Square, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.powi(2));\n    Ok(())\n};\nq: [i8, u8, i32, i32] => |f : f32| f.powi(2);\ndeclutter: declutter_square;\nvalidation: Validation::Rounding\n);\n\nfn declutter_square(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {\n    use super::element_wise::*;\n    // Square(Sqrt(x)) → x (Sqrt output is non-negative, so Square is exact inverse)\n    if let Some(prec) = model.linear_prec(node.id)?\n        && let Some(ew) = prec.op_as::<ElementWiseOp>()\n        && ew.0.is::<Sqrt>()\n    {\n        let mut patch = TypedModelPatch::default();\n        let tap = patch.tap_model(model, prec.inputs[0])?;\n        patch.shunt_outside(model, node.id.into(), tap)?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n\nelement_wise!(sqrt, Sqrt, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.sqrt());\n    Ok(())\n};\nq: [i8, u8, i32, i32] => f32::sqrt;\nvalidation: Validation::Rounding\n);\n\nelement_wise!(recip, Recip, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.recip());\n    Ok(())\n};\nq: [i8, u8, i32, i32] => f32::recip;\ncost: |dt| {tvec!((Cost::Div(dt), 1))};\ndeclutter: declutter_recip;\nvalidation: Validation::Rounding\n);\n\nfn declutter_recip(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {\n    use super::element_wise::*;\n    if let Some(prec) = model.linear_prec(node.id)?\n        && let Some(ew) = prec.op_as::<ElementWiseOp>()\n    {\n        let repl = if ew.0.is::<Sqrt>() {\n            Some(rsqrt())\n        } else if ew.0.is::<Rsqrt>() {\n            Some(sqrt())\n        } else {\n            None\n        };\n        if let Some(repl) = repl {\n            let mut patch = TypedModelPatch::default();\n            let mut wire = patch.tap_model(model, prec.inputs[0])?;\n            wire = patch.wire_node(&node.name, repl, &[wire])?[0];\n            patch.shunt_outside(model, node.id.into(), wire)?;\n            return Ok(Some(patch));\n        }\n    }\n    Ok(None)\n}\n\nelement_wise!(rsqrt, Rsqrt, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.sqrt().recip());\n    Ok(())\n};\nq: [i8, u8, i32] => |x : f32| x.sqrt().recip();\nvalidation: Validation::Rounding\n);\n\nelement_wise!(ceil, Ceil, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.ceil());\n    Ok(())\n}, [i8, i16,i32, i64, u8, u16, u32, u64, TDim] => |_, _| Ok(());\nq: [i8, u8, i32] => f32::recip);\n\nelement_wise!(floor, Floor, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.floor());\n    Ok(())\n}, [i8, i16,i32, i64, u8, u16, u32, u64, TDim] => |_, _| Ok(());\nq: [i8, u8, i32] => f32::floor);\n\nelement_wise!(round, Round, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.round());\n    Ok(())\n}, [i8, i16,i32, i64, u8, u16, u32, u64, TDim] => |_, _| Ok(());\nq: [i8, u8, i32] => f32::round);\n\nelement_wise!(q_scale, QScale{scaler: Scaler},[i32] => |op, xs| {\n    xs.iter_mut().for_each(|x| *x = x.q_scale(op.scaler));\n    Ok(())\n});\n\nelement_wise!(round_half_to_even, RoundHalfToEven,\n[f32] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = round_ties_to_even(*x));\n    Ok(())\n},\n[f16] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = f16::from_f32(round_ties_to_even(x.to_f32())));\n    Ok(())\n};\nq: [i8, u8, i32] => round_ties_to_even);\n\nelement_wise!(cos, Cos, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.cos());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::cos);\n\nelement_wise!(sin, Sin, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.sin());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::sin);\n\nelement_wise!(tan, Tan, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.tan());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::tan);\n\nelement_wise!(acos, Acos, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.acos());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::acos);\n\nelement_wise!(asin, Asin, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.asin());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::asin);\n\nelement_wise!(atan, Atan, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.atan());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::atan);\n\nelement_wise!(cosh, Cosh, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.cosh());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::cosh);\n\nelement_wise!(sinh, Sinh, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.sinh());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::sinh);\n\nelement_wise!(tanh, Tanh,\n [f16] => |_, xs| { (tract_linalg::ops().tanh_f16)().run(xs) },\n [f32] => |_, xs| { (tract_linalg::ops().tanh_f32)().run(xs) },\n [f64] => |_, xs| { xs.iter_mut().for_each(|x| *x = x.tanh()); Ok(()) };\n q: [i8, u8, i32] => f32::tanh;\n cost: |dt| {tvec!((Cost::FMA(dt), 11), (Cost::Div(dt), 1))}\n);\n\nelement_wise!(erf, Erf,\n [f32] => |_, xs| { (tract_linalg::ops().erf_f32)().run(xs) },\n [f16] => |_, xs| {\n     let mut f32s = xs.iter().map(|x| x.to_f32()).collect_vec();\n     (tract_linalg::ops().erf_f32)().run(&mut f32s)?;\n     xs.iter_mut().zip(f32s.into_iter()).for_each(|(x, f)| *x = f16::from_f32(f));\n     Ok(())\n};\n cost: |dt| {tvec!((Cost::FMA(dt), 11), (Cost::Div(dt), 1))}\n);\n\nelement_wise!(acosh, Acosh, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.acosh());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::acosh);\nelement_wise!(asinh, Asinh, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.asinh());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::asinh);\nelement_wise!(atanh, Atanh, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = x.atanh());\n    Ok(())\n};\nq: [i8, u8, i32] => f32::atanh);\n\nelement_wise!(neg, Neg, [i8, i16, i32, i64, f16, f32, f64, TDim] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = -x.clone());\n    Ok(())\n};\nq: [i8, u8, i32] => |x: f32| -x);\n\nelement_wise!(sign, Sign, [f16, f32, f64] => |_, xs| {\n    xs.iter_mut().for_each(|x| *x = if x.is_zero() { *x } else { x.signum() });\n    Ok(())\n};\nq: [i8, u8, i32] => f32::signum);\n\nelement_wise_oop!(is_inf, IsInf { detect_positive: bool, detect_negative: bool },\n    [f32] => bool |op, xs, ys| {\n        xs.iter().zip(ys.iter_mut()).for_each(|(x,y)|\n            *y = (op.detect_positive && *x == f32::INFINITY) || (op.detect_negative && *x == f32::NEG_INFINITY)\n        );\n        Ok(())\n    },\n    [f16] => bool |op, xs, ys| {\n        xs.iter().zip(ys.iter_mut()).for_each(|(x,y)|\n            *y = (op.detect_positive && *x == f16::INFINITY) || (op.detect_negative && *x == f16::NEG_INFINITY)\n        );\n        Ok(())\n    }\n);\n\nelement_wise_oop!(is_nan, IsNan,\n    [f16, f32] => bool |_, xs, ys| {\n        xs.iter().zip(ys.iter_mut()).for_each(|(x,y)| *y = x.is_nan());\n        Ok(())\n    }\n);\n\n#[cfg(test)]\nmod tests {\n    use crate::ops::binary::TypedBinOp;\n\n    use super::*;\n    use ndarray::arr2;\n\n    #[test]\n    fn test_mul() {\n        let a = arr2(&[[1., 2.], [3., 4.]]);\n        let b = arr2(&[[1., 0.], [0., 0.]]);\n        assert_eq!(a * b, arr2(&[[1., 0.], [0., 0.]]));\n    }\n\n    #[test]\n    fn dot() {\n        let a = arr2(&[[1., 2.], [3., 4.]]);\n        let b = arr2(&[[1., 0.], [0., 0.]]);\n        assert_eq!(a.dot(&b), arr2(&[[1., 0.], [3., 0.]]));\n    }\n\n    #[test]\n    fn mul_as_shift_left() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let x = model.add_source(\"x\", i32::fact([2usize, 2]))?;\n        let a = model.add_const(\"a\", tensor0(4i32).broadcast_into_rank(2)?.into_arc_tensor())?;\n        let y = model.wire_node(\"y\", mul(), &[x, a])?[0];\n        model.select_output_outlets(&[y])?;\n        let result =\n            SimplePlan::new(model.clone())?.run(tvec!(tensor2(&[[1, 2], [3, 4]]).into()))?;\n        assert_eq!(*result[0], tensor2(&[[4, 8], [12, 16]]));\n        let decluttered = model.into_decluttered()?;\n        let result =\n            SimplePlan::new(decluttered.clone())?.run(tvec!(tensor2(&[[1, 2], [3, 4]]).into()))?;\n        assert_eq!(*result[0], tensor2(&[[4, 8], [12, 16]]));\n        let op = decluttered\n            .node(decluttered.output_outlets()?[0].node)\n            .op()\n            .downcast_ref::<TypedBinOp>()\n            .unwrap();\n        assert!(op.0.downcast_ref::<ShiftLeft>().is_some());\n        Ok(())\n    }\n\n    #[test]\n    fn div_as_shift() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let x = model.add_source(\"a\", i32::fact([2usize, 2]))?;\n        let s = model.add_const(\"shift\", tensor2(&[[4]]))?;\n        let y = model.wire_node(\"c\", div(), [x, s].as_ref())?[0];\n        model.select_output_outlets(&[y])?;\n        let result =\n            SimplePlan::new(model.clone())?.run(tvec!(tensor2(&[[16, 32], [64, 68]]).into()))?;\n        assert_eq!(*result[0], tensor2(&[[4, 8], [16, 17]]));\n        let decluttered = model.into_decluttered()?;\n        let result = SimplePlan::new(decluttered.clone())?\n            .run(tvec!(tensor2(&[[16, 32], [64, 68]]).into()))?;\n        assert_eq!(*result[0], tensor2(&[[4, 8], [16, 17]]));\n        let op = decluttered\n            .node(decluttered.output_outlets()?[0].node)\n            .op()\n            .downcast_ref::<TypedBinOp>()\n            .unwrap();\n        assert!(op.0.downcast_ref::<ShiftRight>().is_some());\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/matmul/de_block_quant.rs",
    "content": "use tract_linalg::block_quant::{BlockQuant, BlockQuantFact, BlockQuantStorage, Q4_0};\n\nuse crate::internal::*;\nuse crate::ops::einsum::einsum_matmul::EinSumMatMul;\nuse crate::ops::konst::Const;\nuse crate::transform::ModelTransform;\n\n#[derive(Debug)]\npub struct BlockQuantTransform;\n\nimpl ModelTransform for BlockQuantTransform {\n    fn name(&self) -> StaticName {\n        \"block_quant\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        crate::ops::einsum::einsum_matmul::detect_all(model)?;\n        Rewriter::<()>::default()\n            .with_rule_for(\"block_quant_einsum_weights\", block_quant_einsum_weights)\n            .rewrite(&(), model)?;\n        crate::ops::einsum::einsum_matmul::flatten_all(model)?;\n        Ok(())\n    }\n}\n\nfn block_quant_einsum_weights(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    prefix: &str,\n    op: &EinSumMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(node.inputs.len() == 2);\n    for (slot, fact) in model.node_input_facts(node.id)?.iter().enumerate() {\n        let Some(a) = fact.konst.as_ref() else { continue };\n        if a.rank() != 2 {\n            continue;\n        };\n        if op.k_axis().inputs[slot][0] == 0 {\n            let mut patch = TypedModelPatch::default();\n            let mut taps = patch.taps(model, &node.inputs)?;\n            taps[slot] = patch.wire_node(\n                format!(\"{}.t_{}\", &node.name, slot),\n                AxisOp::Move(1, 0),\n                &[taps[slot]],\n            )?[0];\n            let mut new_op = op.clone();\n            new_op.op.axes = op\n                .op\n                .axes\n                .clone()\n                .remove_axis_occurency(InOut::In(slot), 0)?\n                .with_extra_axis_occurency(op.k_axis, InOut::In(slot), 1)?;\n            let output = patch.wire_node(prefix, new_op, &taps)?;\n            patch.shunt_outside(model, node.id.into(), output[0])?;\n            return Ok(Some(patch));\n        }\n        let format = Q4_0;\n        let mut patch = TypedModelPatch::default();\n        let weights = if a.datum_type() == f16::datum_type() {\n            format.quant_f16(a.try_as_plain()?.as_slice::<f16>()?)?\n        } else {\n            format.quant_f32(a.cast_to::<f32>()?.try_as_plain()?.as_slice::<f32>()?)?\n        };\n        let name = &model.node(node.inputs[0].node).name;\n        let m = a.shape()[0];\n        let k = a.shape()[1];\n        let bqs = BlockQuantStorage::new(Box::new(format), m, k, Arc::new(weights))?;\n        let fact =\n            Box::new(BlockQuantFact::new(dyn_clone::clone_box(bqs.format()), tvec!(1, m, k)));\n        let weights = patch.wire_node(\n            format!(\"{name}.bq\"),\n            Const::new_with_exotic_fact(\n                Arc::new(bqs.into_tensor_with_shape(a.datum_type(), &[1, m, k])),\n                fact,\n            )?,\n            &[],\n        )?;\n        let tap = patch.tap_model(model, node.inputs[1])?;\n        // Block-quant tensor is rank 3 [G=1, M, K]; add a group dim to the axes\n        let mut new_op = op.op.clone();\n        new_op.axes = new_op.axes.with_extra_axis('G', InOut::In(slot), 0)?;\n        let wire = patch.wire_node(prefix, new_op, &[weights[0], tap])?;\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "core/src/ops/matmul/mod.rs",
    "content": "pub mod de_block_quant;\npub mod optimized;\npub mod pack;\npub mod quant;\n\nuse crate::internal::*;\n\npub fn output_type(input: DatumType) -> DatumType {\n    if input.is_float() { input } else { i32::datum_type() }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub enum ModePicker {\n    Single,\n    VecVsMat,\n}\n\nimpl ModePicker {\n    #[inline]\n    pub fn pick(&self, n: usize) -> TractResult<usize> {\n        match self {\n            ModePicker::Single => Ok(0),\n            ModePicker::VecVsMat => Ok((n > 1) as usize),\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/matmul/optimized.rs",
    "content": "use crate::internal::*;\nuse crate::ops::cast::{Cast, cast};\nuse crate::ops::change_axes::wire_with_rank_broadcast;\nuse crate::ops::nn::LeakyRelu;\nuse ndarray::*;\nuse tract_itertools::Itertools;\n\nuse tract_linalg::mmm::{\n    AsInputValue, EagerPackedInput, FusedSpec, MatMatMul, OutputStoreSpec, PackedMatrixStorage,\n    PanelExtractInput, PanelExtractor,\n};\nuse tract_linalg::pack::PackedFormat;\nuse tract_linalg::{BinOp, Scaler};\nuse tract_smallvec::ToSmallVec;\n\nuse super::ModePicker;\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum ProtoFusedSpec {\n    AddMatMul {\n        geo: AddMatMulGeometry,\n        a: usize,\n        b: usize,\n        packings: Vec<(usize, Option<PanelExtractor>)>,\n    },\n    BinScalar(usize, BinOp),\n    LeakyRelu(usize),\n    BinPerRow(usize, BinOp, MapOutputAxisToInput),\n    BinPerCol(usize, BinOp, MapOutputAxisToInput),\n    AddRowColProducts(usize, usize),\n    AddUnicast(OutputStoreSpec, usize, MapOutputAxisToInput),\n    Scaler(Scaler),\n    Store(Vec<OutputStoreSpec>),\n}\n\nimpl ProtoFusedSpec {\n    pub fn format(&self, mmm: &dyn MatMatMul, mode: usize) -> String {\n        use ProtoFusedSpec::*;\n        match self {\n            AddMatMul { geo, packings: packing, .. } => {\n                let (a, b) = &mmm.packings()[packing[mode].0];\n                format!(\"matmul(k={}, {a:?}•{b:?})\", geo.k)\n            }\n            BinScalar(_, op) => format!(\"scalar{op:?}\"),\n            LeakyRelu(alpha) => format!(\"leaky_relu({alpha:?})\"),\n            BinPerRow(_, op, _) => format!(\"row{op:?}\"),\n            BinPerCol(_, op, _) => format!(\"col{op:?}\"),\n            AddRowColProducts(_, _) => \"add_row_col_product\".to_string(),\n            AddUnicast(_, _, _) => \"add_to_matrix\".to_string(),\n            Scaler(s) => format!(\"scale({})\", 1f32 * *s),\n            Store(_oss) => \"store\".to_string(),\n        }\n    }\n\n    pub fn resolve<'t>(\n        &'t self,\n        inputs: &'t [TValue],\n        output_coords: &[usize],\n        output: &Tensor,\n        mmm: &dyn MatMatMul,\n        mode: usize,\n    ) -> FusedSpec<'t> {\n        #[allow(clippy::let_and_return)]\n        let fs = match self {\n            ProtoFusedSpec::AddMatMul { geo, a, b, packings } => {\n                let a_tensor = &inputs[*a];\n                let a_storage = a_tensor.try_storage_as::<PackedMatrixStorage>().unwrap();\n                let a_idx =\n                    geo.c_to_a_axis_mapping.flat_index(output_coords, a_storage.batch_strides());\n                let a = a_storage.value_at_flat(a_idx);\n\n                let b_tensor = &inputs[*b];\n                let b_storage = b_tensor.try_storage_as::<PackedMatrixStorage>().unwrap();\n                let b_idx =\n                    geo.c_to_b_axis_mapping.flat_index(output_coords, b_storage.batch_strides());\n                let b = b_storage.value_at_flat(b_idx);\n\n                let (_a_packing, b_packing) = &mmm.packings()[packings[mode].0];\n                let pa = if let Some(extractor) = &packings[mode].1 {\n                    let data = a.downcast_ref::<EagerPackedInput>().unwrap();\n                    AsInputValue::Owned(Box::new(PanelExtractInput {\n                        format: extractor.clone(),\n                        data: data.clone(),\n                    }))\n                } else {\n                    AsInputValue::Borrowed(a)\n                };\n                assert!(\n                    b_packing.dyn_eq(b.format())\n                        || (b_packing.is::<PackedFormat>() && b_packing.r() == b.format().r())\n                );\n                debug_assert!(pa.k().to_dim().compatible_with(&geo.k.to_dim()));\n                debug_assert!(b.k().to_dim().compatible_with(&geo.k.to_dim()));\n                FusedSpec::AddMatMul {\n                    a: pa,\n                    b: AsInputValue::Borrowed(b),\n                    packing: packings[mode].0,\n                }\n            }\n            ProtoFusedSpec::BinScalar(v, op) => FusedSpec::BinScalar(&inputs[*v], *op),\n            ProtoFusedSpec::LeakyRelu(v) => FusedSpec::LeakyRelu(&inputs[*v]),\n            ProtoFusedSpec::BinPerRow(v, op, map) => {\n                let mut v = inputs[*v].view();\n                unsafe { map.translate_view(output_coords, &mut v) }\n                FusedSpec::BinPerRow(v, *op)\n            }\n            ProtoFusedSpec::BinPerCol(v, op, map) => {\n                let mut v = inputs[*v].view();\n                unsafe { map.translate_view(output_coords, &mut v) }\n                FusedSpec::BinPerCol(v, *op)\n            }\n            ProtoFusedSpec::AddRowColProducts(row, col) => {\n                FusedSpec::AddRowColProducts(&inputs[*row], &inputs[*col])\n            }\n            ProtoFusedSpec::AddUnicast(store, v, map) => unsafe {\n                let mut view = inputs[*v].view();\n                map.translate_view(output_coords, &mut view);\n                FusedSpec::AddUnicast(store.wrap(&view))\n            },\n            ProtoFusedSpec::Scaler(scaler) => scaler.as_fused_spec(),\n            ProtoFusedSpec::Store(oss) => unsafe {\n                let view = output.view_offsetting_unchecked(output_coords);\n                FusedSpec::Store(oss[mode].wrap(&view))\n            },\n        };\n        fs\n    }\n\n    pub fn is_trivial(&self) -> bool {\n        match self {\n            ProtoFusedSpec::AddMatMul { geo, .. } => geo.k.as_i64().is_some(),\n            _ => true,\n        }\n    }\n\n    pub fn resolve_trivial<'t>(\n        &'t self,\n        inputs: &'t [TValue],\n        output: &mut Tensor,\n        _mmm: &dyn MatMatMul,\n        mode: usize,\n    ) -> FusedSpec<'t> {\n        #[allow(clippy::let_and_return)]\n        let fs = match self {\n            ProtoFusedSpec::AddMatMul { a, b, packings, .. } => unsafe {\n                debug_assert!(inputs.get(*a).is_some());\n                debug_assert!(inputs.get(*b).is_some());\n                let a = inputs.get_unchecked(*a);\n                let b = inputs.get_unchecked(*b);\n                debug_assert!(a.is_exotic());\n                debug_assert!(a.len() == 1);\n                debug_assert!(b.is_exotic());\n                debug_assert!(b.len() == 1);\n                let a_storage = a.try_storage_as::<PackedMatrixStorage>().unwrap_unchecked();\n                let b_storage = b.try_storage_as::<PackedMatrixStorage>().unwrap_unchecked();\n                let a = a_storage.value();\n                let b = b_storage.value();\n                debug_assert!(packings.len() == 1);\n                debug_assert!(packings[0].1.is_none()); // no panel extraction\n                #[cfg(debug_assertions)]\n                {\n                    let (a_packing, b_packing) = &_mmm.packings()[packings[mode].0];\n                    debug_assert!(\n                        a_packing.dyn_eq(a.format())\n                            || (a_packing.is::<PackedFormat>() && a_packing.r() == a.format().r())\n                    );\n                    debug_assert!(\n                        b_packing.dyn_eq(b.format())\n                            || (b_packing.is::<PackedFormat>() && b_packing.r() == b.format().r())\n                    );\n                }\n                FusedSpec::AddMatMul {\n                    a: AsInputValue::Borrowed(a),\n                    b: AsInputValue::Borrowed(b),\n                    packing: packings[mode].0,\n                }\n            },\n            ProtoFusedSpec::BinScalar(v, op) => FusedSpec::BinScalar(&inputs[*v], *op),\n            ProtoFusedSpec::LeakyRelu(v) => FusedSpec::LeakyRelu(&inputs[*v]),\n            ProtoFusedSpec::BinPerRow(v, op, _) => {\n                let v = inputs[*v].view();\n                FusedSpec::BinPerRow(v, *op)\n            }\n            ProtoFusedSpec::BinPerCol(v, op, _) => {\n                let v = inputs[*v].view();\n                FusedSpec::BinPerCol(v, *op)\n            }\n            ProtoFusedSpec::AddRowColProducts(row, col) => {\n                FusedSpec::AddRowColProducts(&inputs[*row], &inputs[*col])\n            }\n            ProtoFusedSpec::AddUnicast(store, v, _) => unsafe {\n                let view = inputs[*v].view();\n                FusedSpec::AddUnicast(store.wrap(&view))\n            },\n            ProtoFusedSpec::Scaler(scaler) => scaler.as_fused_spec(),\n            ProtoFusedSpec::Store(oss) => unsafe {\n                FusedSpec::Store(oss[mode].wrap(&output.view_mut()))\n            },\n        };\n        fs\n    }\n\n    fn check_inputs(&self, inputs: &[&TypedFact]) -> TractResult<()> {\n        use ProtoFusedSpec::*;\n        match self {\n            AddMatMul { a, b, .. } => {\n                ensure!(inputs[*a].is_exotic());\n                ensure!(inputs[*b].is_exotic());\n            }\n            BinScalar(v, _)\n            | LeakyRelu(v)\n            | BinPerCol(v, _, _)\n            | BinPerRow(v, _, _)\n            | AddUnicast(_, v, _) => {\n                ensure!(inputs[*v].datum_type.is_number());\n            }\n            AddRowColProducts(row, col) => {\n                ensure!(inputs[*row].datum_type.is_number());\n                ensure!(inputs[*col].datum_type.is_number());\n            }\n            _ => (),\n        };\n        Ok(())\n    }\n\n    fn cost(&self, m: &TDim, n: &TDim, idt: DatumType) -> TVec<(Cost, TDim)> {\n        match self {\n            ProtoFusedSpec::AddMatMul { geo, .. } => {\n                tvec!((Cost::FMA(idt), m.clone() * n * &geo.k))\n            }\n            _ => tvec!(), /* FIXME maybe */\n        }\n    }\n\n    fn rm_c_axis(&mut self, axis: usize) {\n        use ProtoFusedSpec::*;\n        match self {\n            AddMatMul { geo, .. } => {\n                geo.c_to_a_axis_mapping.rm_c_axis(axis);\n                geo.c_to_b_axis_mapping.rm_c_axis(axis);\n            }\n            BinScalar(..) | Scaler(..) | AddRowColProducts(_, _) | LeakyRelu(_) => {}\n            BinPerRow(_, _, map) | BinPerCol(_, _, map) => map.rm_c_axis(axis),\n            AddUnicast(_, _, map) => {\n                map.rm_c_axis(axis);\n            }\n            Store(oss, ..) => {\n                for oss in oss {\n                    match oss {\n                        OutputStoreSpec::View { m_axis, n_axis, .. } => {\n                            if let Some(m) = m_axis {\n                                *m -= (*m > axis) as usize\n                            };\n                            if let Some(n) = n_axis {\n                                *n -= (*n > axis) as usize\n                            }\n                        }\n                        OutputStoreSpec::Strides { .. } => {}\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct MapOutputAxisToInput(pub TVec<(usize, usize)>);\n\nimpl MapOutputAxisToInput {\n    #[inline]\n    unsafe fn translate_view(&self, output_coords: &[usize], v: &mut TensorView) {\n        for &(out_axis, in_axis) in &self.0 {\n            unsafe { v.offset_axis(in_axis, output_coords[out_axis] as isize) }\n        }\n    }\n\n    #[inline]\n    fn rm_c_axis(&mut self, axis: usize) {\n        for (c, _) in &mut self.0 {\n            *c -= (*c > axis) as usize;\n        }\n    }\n\n    /// Compute a flat index into a PackedMatrixStorage from output coordinates and batch strides.\n    #[inline]\n    pub fn flat_index(&self, output_coords: &[usize], batch_strides: &[isize]) -> usize {\n        self.0\n            .iter()\n            .map(|&(out_axis, in_axis)| output_coords[out_axis] * batch_strides[in_axis] as usize)\n            .sum()\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct AddMatMulGeometry {\n    pub k: TDim,\n    pub c_to_a_axis_mapping: MapOutputAxisToInput,\n    pub c_to_b_axis_mapping: MapOutputAxisToInput,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct OptMatMul {\n    pub c_fact: TypedFact,\n    pub micro_ops: Vec<ProtoFusedSpec>,\n    pub mmm: Vec<Box<dyn MatMatMul>>,\n    pub mode_picker: ModePicker,\n    pub c_m_axis: Option<usize>,\n    pub c_n_axis: Option<usize>,\n    pub trivial_packing: bool,\n    pub trivial_path: bool,\n}\n\nimpl Op for OptMatMul {\n    fn name(&self) -> StaticName {\n        \"OptMatMul\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let m = self.c_m_axis.map(|ix| &self.c_fact.shape[ix]).unwrap_or(&TDim::Val(1));\n        let n = self.c_n_axis.map(|ix| &self.c_fact.shape[ix]).unwrap_or(&TDim::Val(1));\n        let mut infos = vec![format!(\n            \"c_shape:{:?}, c_m_axis:{:?} c_n_axis:{:?} m:{} n:{}\",\n            self.c_fact, self.c_m_axis, self.c_n_axis, m, n,\n        )];\n        if let Some(k) = self.guess_k() {\n            infos.push(format!(\"Mult: m:{} k:{} n:{} with {:?}\", m, k, n, self.mmm));\n        } else {\n            infos.push(format!(\"Mult: {:?}\", self.mmm));\n        }\n        for (mode, mmm) in self.mmm.iter().enumerate() {\n            infos.push(format!(\n                \"Ops: {}\",\n                self.micro_ops.iter().map(|o| o.format(&**mmm, mode)).join(\" >>> \")\n            ));\n        }\n        Ok(infos)\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for OptMatMul {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        unsafe {\n            let c_shape = self.c_fact.shape.eval_to_usize(&session.resolved_symbols)?;\n            let mut c = Tensor::uninitialized_dt(self.c_fact.datum_type, &c_shape)?;\n            let m = self.c_m_axis.map(|c_m| c.shape()[c_m]).unwrap_or(1);\n            let n = self.c_n_axis.map(|c_n| c.shape()[c_n]).unwrap_or(1);\n            let mode = self.mode_picker.pick(n)?;\n            let mmm = &*self.mmm[mode];\n            let mut cell = session.cached_mmm_scratch_space.borrow_mut();\n            if !cell.as_ref().is_some_and(|scratch| mmm.can_use_scratch_space(&**scratch)) {\n                *cell = None\n            }\n            let scratch = cell.get_or_insert_with(|| mmm.allocate_scratch_space());\n            if self.trivial_path {\n                let uops: Vec<FusedSpec> = self\n                    .micro_ops\n                    .iter()\n                    .map(|o| o.resolve_trivial(&inputs, &mut c, mmm, mode))\n                    .collect();\n                mmm.run_with_scratch_space(m, n, scratch.as_mut(), &uops)?;\n                Ok(tvec!(c.into_tvalue()))\n            } else {\n                let mut uops = vec![FusedSpec::ShiftLeft(0); self.micro_ops.len()];\n                let mut looping_shape: TVec<usize> = c_shape.to_smallvec();\n                if let Some(ax) = self.c_m_axis {\n                    looping_shape[ax] = 1;\n                }\n                if let Some(ax) = self.c_n_axis {\n                    looping_shape[ax] = 1;\n                }\n                for c_coords in indices(&*looping_shape) {\n                    for ix in 0..self.micro_ops.len() {\n                        *uops.get_unchecked_mut(ix) = self.micro_ops.get_unchecked(ix).resolve(\n                            &inputs,\n                            c_coords.slice(),\n                            &c,\n                            mmm,\n                            mode,\n                        );\n                    }\n                    mmm.run_with_scratch_space(m, n, scratch.as_mut(), &uops)\n                        .context(\"In mmm.run_with_scratch_space\")?;\n                }\n                Ok(tvec!(c.into_tvalue()))\n            }\n        }\n    }\n}\n\nimpl TypedOp for OptMatMul {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(self.c_m_axis.map(|ax| ax < self.c_fact.rank()).unwrap_or(true));\n        ensure!(self.c_n_axis.map(|ax| ax < self.c_fact.rank()).unwrap_or(true));\n        ensure!(self.trivial_path == self.can_use_trivial_path());\n        ensure!(self.mmm.iter().map(|mmm| mmm.internal_type()).all_equal());\n        for op in &self.micro_ops {\n            op.check_inputs(inputs)?;\n        }\n        Ok(tvec!(self.c_fact.clone()))\n    }\n\n    fn cost(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let mut sums = HashMap::new();\n        for op in &self.micro_ops {\n            for (cost, count) in op.cost(self.m(), self.n(), self.mmm[0].internal_type()) {\n                *sums.entry(cost).or_default() += count;\n            }\n        }\n        let loops = self\n            .c_fact\n            .shape\n            .iter()\n            .enumerate()\n            .map(|(ix, d)| {\n                if Some(ix) == self.c_m_axis || Some(ix) == self.c_n_axis {\n                    1.to_dim()\n                } else {\n                    d.clone()\n                }\n            })\n            .product::<TDim>();\n        for s in &mut sums.values_mut() {\n            *s *= &loops;\n        }\n        Ok(sums.into_iter().collect())\n    }\n\n    fn fuse(&self, model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {\n        use crate::ops;\n        rule_if!(node.outputs.len() == 1);\n        rule_if!(node.outputs[0].successors.len() == 1);\n        rule_if!(!model.output_outlets()?.contains(&node.id.into()));\n        let succ = model.node(node.outputs[0].successors[0].node);\n        let mut patch = TypedModelPatch::new(format!(\"fusing {succ}\"));\n\n        if let Some(op) = succ.op_as::<ops::binary::TypedBinOp>() {\n            rule_if_some!(mut binop = op.0.as_linalg_binop());\n            let flipped = succ.inputs[0].node == node.id;\n            if flipped {\n                binop = binop.flip();\n            }\n            let other_outlet = succ.inputs[flipped as usize];\n            return self.fuse_binary(model, node, patch, other_outlet, binop);\n        }\n        if let Some(op) = succ.op_as::<ops::binary::OptBinByScalar>() {\n            rule_if_some!(mut binop = op.binop.as_linalg_binop());\n            let flipped = succ.inputs[0].node == node.id;\n            if flipped {\n                binop = binop.flip();\n            }\n            let other_outlet = succ.inputs[flipped as usize];\n            return self.fuse_binary(model, node, patch, other_outlet, binop);\n        }\n\n        if let Some(op) = succ.op_as::<ops::element_wise::ElementWiseOp>().map(|ew| ew.0.as_ref()) {\n            if let Some(op) = op.downcast_ref::<ops::math::QScale>() {\n                return self.fuse_op(\n                    model,\n                    node,\n                    patch,\n                    vec![ProtoFusedSpec::Scaler(op.scaler)],\n                    &[],\n                );\n            }\n            if let Some(op) = op.downcast_ref::<LeakyRelu>() {\n                rule_if!(\n                    self.mmm\n                        .iter()\n                        .all(|mmm| mmm.can_fuse(&FusedSpec::LeakyRelu(&tensor0(op.alpha))))\n                );\n                let alpha = patch.add_const(\n                    node.name.to_string() + \".alpha\",\n                    tensor0(op.alpha).cast_to_dt(self.mmm[0].internal_type())?.into_owned(),\n                )?;\n                return self.fuse_op(\n                    model,\n                    node,\n                    patch,\n                    vec![ProtoFusedSpec::LeakyRelu(node.inputs.len())],\n                    &[alpha],\n                );\n            }\n        }\n        if let Some(cast_to) = succ.op_as::<ops::cast::Cast>().map(|cast| cast.to)\n            && (((cast_to.unquantized() == i8::datum_type()\n                || cast_to.unquantized() == u8::datum_type())\n                && self.c_fact.datum_type == i32::datum_type())\n                || self.mmm.iter().all(|m| m.stores().contains(&cast_to)))\n            && let Some(ProtoFusedSpec::Store(stores)) = self.micro_ops.last()\n        {\n            if stores.iter().any(|s| matches!(s, OutputStoreSpec::Strides { .. })) {\n                return Ok(None);\n            }\n            let c_fact = cast_to.fact(self.c_fact.shape.clone());\n            let mut patch =\n                TypedModelPatch::fuse_with_next(model, node, Self { c_fact, ..self.clone() })?;\n            patch.dont_apply_twice = Some(format!(\"Fuse {succ} into {node}\"));\n            return Ok(Some(patch));\n        }\n        if let Some(AxisOp::Rm(axis)) = succ.op_as::<ops::AxisOp>() {\n            rule_if!(Some(*axis) != self.c_m_axis);\n            rule_if!(Some(*axis) != self.c_n_axis);\n            let mut new_op = self.clone();\n            new_op.c_fact.shape.remove_axis(*axis)?;\n            if let Some(c_m_axis) = &mut new_op.c_m_axis {\n                *c_m_axis -= (*c_m_axis > *axis) as usize;\n            }\n            if let Some(c_n_axis) = &mut new_op.c_n_axis {\n                *c_n_axis -= (*c_n_axis > *axis) as usize;\n            }\n            for uop in &mut new_op.micro_ops {\n                uop.rm_c_axis(*axis);\n            }\n            let mut patch = TypedModelPatch::fuse_with_next(model, node, new_op)?;\n            patch.dont_apply_twice = Some(format!(\"Fuse {succ} into {node}\"));\n            return Ok(Some(patch));\n        }\n        if (succ.op_is::<AxisOp>() || succ.op_is::<IntoShape>())\n            && let &[next] = &*succ.outputs[0].successors\n        {\n            let next_node = model.node(next.node);\n            if let Some(cast) = next_node.op_as::<Cast>() {\n                let mut patch = TypedModelPatch::default();\n                let mut wire = patch.tap_model(model, node.id.into())?;\n                wire = patch.wire_node(&next_node.name, cast.clone(), &[wire])?[0];\n                wire = patch.wire_node(&succ.name, succ.op.clone(), &[wire])?[0];\n                patch.shunt_outside(model, next_node.id.into(), wire)?;\n                return Ok(Some(patch));\n            } else if let Some(op) = next_node.op_as::<ops::binary::TypedBinOp>() {\n                rule_if!(op.0.as_linalg_binop().is_some());\n                let flipped = succ.inputs[0].node == node.id;\n                let other_outlet = next_node.inputs[flipped as usize];\n                if let Some(uni) = &model.outlet_fact(other_outlet)?.uniform {\n                    let mut patch = TypedModelPatch::default();\n                    let cst = patch.add_const(&model.node(other_outlet.node).name, uni.clone())?;\n                    let output = patch.tap_model(model, node.id.into())?;\n                    let wire = wire_with_rank_broadcast(\n                        &next_node.name,\n                        &mut patch,\n                        op.clone(),\n                        &if flipped { [output, cst] } else { [cst, output] },\n                    )?;\n                    let wire = patch.wire_node(&succ.name, succ.op.clone(), &wire)?[0];\n                    patch.shunt_outside(model, next_node.id.into(), wire)?;\n                    return Ok(Some(patch));\n                }\n            }\n        }\n        if let Some(op) = succ.op_as::<ops::binary::OptBinUnicast>() {\n            let in_1_fact = model.outlet_fact(succ.inputs[0])?;\n            let in_2_fact = model.outlet_fact(succ.inputs[1])?;\n            if op.binop.is::<ops::math::Add>()\n                && self.mmm.len() == 1\n                && in_1_fact.without_value() == in_2_fact.without_value()\n            {\n                let other_slot = 1 - node.outputs[0].successors[0].slot;\n                let other_input = succ.inputs[other_slot];\n                let other_input = patch.tap_model(model, other_input)?;\n                let other_fact = patch.outlet_fact(other_input)?;\n\n                if other_fact.shape == self.c_fact.shape {\n                    let other_storage = unsafe { self.mmm[0].c_view(self.c_m_axis, self.c_n_axis) };\n                    let mapping =\n                        MapOutputAxisToInput((0..other_fact.rank()).map(|x| (x, x)).collect());\n                    return self.fuse_op(\n                        model,\n                        node,\n                        patch,\n                        vec![ProtoFusedSpec::AddUnicast(other_storage, node.inputs.len(), mapping)],\n                        &[other_input],\n                    );\n                }\n            } else {\n                rule_if_some!(mut binop = op.binop.as_linalg_binop());\n                let flipped = succ.inputs[0].node == node.id;\n                if flipped {\n                    binop = binop.flip();\n                }\n                let other_outlet = succ.inputs[flipped as usize];\n                return self.fuse_binary(model, node, patch, other_outlet, binop);\n            }\n        };\n        Ok(None)\n    }\n\n    as_op!();\n}\n\nimpl OptMatMul {\n    pub fn new(\n        mmm: Vec<Box<dyn MatMatMul>>,\n        mode_picker: ModePicker,\n        c_fact: TypedFact,\n        c_m_axis: Option<usize>,\n        c_n_axis: Option<usize>,\n        micro_ops: Vec<ProtoFusedSpec>,\n        trivial_packing: bool,\n    ) -> TractResult<Self> {\n        if let Some(m) = c_m_axis {\n            ensure!(m < c_fact.rank());\n        }\n        if let Some(n) = c_n_axis {\n            ensure!(n < c_fact.rank());\n        }\n        let mut it = OptMatMul {\n            mmm,\n            mode_picker,\n            c_fact,\n            c_m_axis,\n            c_n_axis,\n            micro_ops,\n            trivial_path: false,\n            trivial_packing,\n        };\n        it.update_trivial_path();\n        Ok(it)\n    }\n\n    // for auditing only (may return None if no AddMatMul is found)\n    pub fn guess_k(&self) -> Option<TDim> {\n        self.micro_ops\n            .iter()\n            .find_map(\n                |o| {\n                    if let ProtoFusedSpec::AddMatMul { geo, .. } = o { Some(geo) } else { None }\n                },\n            )\n            .map(|geo| geo.k.clone())\n    }\n\n    #[inline]\n    pub fn m(&self) -> &TDim {\n        self.c_m_axis.map(|ax| &self.c_fact.shape[ax]).unwrap_or(&TDim::Val(1))\n    }\n\n    #[inline]\n    pub fn n(&self) -> &TDim {\n        self.c_n_axis.map(|ax| &self.c_fact.shape[ax]).unwrap_or(&TDim::Val(1))\n    }\n\n    fn update_trivial_path(&mut self) {\n        self.trivial_path = self.can_use_trivial_path();\n    }\n\n    fn can_use_trivial_path(&self) -> bool {\n        self.c_fact.shape.is_concrete()\n            && self.c_fact.shape.iter().enumerate().all(|(ax, dim)| {\n                Some(ax) == self.c_m_axis || Some(ax) == self.c_n_axis || dim.is_one()\n            })\n            && self.trivial_packing\n            && self.micro_ops.iter().all(|o| o.is_trivial())\n    }\n\n    fn fuse_op(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        mut patch: TypedModelPatch,\n        fused_micro_op: Vec<ProtoFusedSpec>,\n        additional_inputs: &[OutletId],\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let succ = model.node(node.outputs[0].successors[0].node);\n        let mut new_op = self.clone();\n        let before_last = new_op.micro_ops.len() - 1..new_op.micro_ops.len() - 1;\n        new_op.micro_ops.splice(before_last, fused_micro_op);\n        new_op.c_fact = succ.outputs[0].fact.clone();\n        new_op.update_trivial_path();\n        let mut inputs = patch.taps(model, &node.inputs)?;\n        inputs.extend(additional_inputs.iter().cloned());\n        let output = patch.wire_node(&succ.name, new_op, &inputs)?;\n        patch.shunt_outside(model, succ.id.into(), output[0])?;\n        Ok(Some(patch))\n    }\n\n    fn fuse_binary(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        mut patch: TypedModelPatch,\n        value: OutletId,\n        binop: BinOp,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let fact = model.outlet_fact(value)?;\n        let mut v = patch.tap_model(model, value)?;\n        if fact.datum_type != self.mmm[0].internal_type() {\n            v = patch.wire_node(\n                format!(\"{}.cast-input-{}\", node.name, node.inputs.len()),\n                cast(self.mmm[0].internal_type()),\n                &[v],\n            )?[0];\n        }\n        let value = node.inputs.len();\n        let additional_input = tvec!(v);\n        if fact.shape.volume() == 1.to_dim() {\n            return self.fuse_op(\n                model,\n                node,\n                patch,\n                vec![ProtoFusedSpec::BinScalar(value, binop)],\n                &additional_input,\n            );\n        }\n        let other_shape = fact.shape.to_owned();\n        if self.c_m_axis.is_some_and(|ax| {\n            other_shape[ax] == self.c_fact.shape[ax] && other_shape[ax] == other_shape.volume()\n        }) {\n            return self.fuse_op(\n                model,\n                node,\n                patch,\n                vec![ProtoFusedSpec::BinPerRow(\n                    value,\n                    binop,\n                    MapOutputAxisToInput(tvec!((self.c_m_axis.unwrap(), self.c_m_axis.unwrap()))),\n                )],\n                &additional_input,\n            );\n        }\n        if self.c_n_axis.is_some_and(|ax| {\n            other_shape[ax] == self.c_fact.shape[ax] && other_shape[ax] == other_shape.volume()\n        }) {\n            return self.fuse_op(\n                model,\n                node,\n                patch,\n                vec![ProtoFusedSpec::BinPerCol(\n                    value,\n                    binop,\n                    MapOutputAxisToInput(tvec!((self.c_n_axis.unwrap(), self.c_n_axis.unwrap()))),\n                )],\n                &additional_input,\n            );\n        }\n        Ok(None)\n    }\n}\n"
  },
  {
    "path": "core/src/ops/matmul/pack.rs",
    "content": "use crate::axes::Axis;\nuse crate::internal::*;\nuse ndarray::*;\nuse tract_linalg::block_quant::{\n    BlockQuantStorage, PackedBlockQuantFact, PackedBlockQuantFormat, block_quant_slice,\n};\nuse tract_linalg::mmm::{MMMInputValue, PackedMatrixStorage};\nuse tract_linalg::pack::PackedFormat;\n\nuse super::ModePicker;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct OptMatMulPack {\n    pub(crate) packers: Vec<PackedFormat>,\n    pub(crate) mode_picker: ModePicker,\n    pub(crate) k_axis: usize,\n    pub(crate) mn_axis: usize,\n}\n\nimpl Op for OptMatMulPack {\n    fn name(&self) -> StaticName {\n        \"OptMatMulPack\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"{:?}. k axis: {}, mn axis: {}\", self.packers, self.k_axis, self.mn_axis)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for OptMatMulPack {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        _node_id: usize,\n        session: &TurnState,\n        mut inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        self.do_eval(session, inputs.remove(0))\n    }\n}\n\nimpl TypedOp for OptMatMulPack {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        match self.mode_picker {\n            ModePicker::Single => ensure!(self.packers.len() == 1),\n            ModePicker::VecVsMat => ensure!(self.packers.len() == 2),\n        }\n        let k = inputs[0].shape[self.k_axis].clone();\n        let mn = inputs[0].shape[self.mn_axis].clone();\n        let exotic_fact = DynPackedExoticFact { k, mn, packers: self.packers.clone() };\n        Ok(tvec!(\n            inputs[0]\n                .datum_type\n                .fact(self.output_shape(&inputs[0].shape))\n                .with_exotic_fact(exotic_fact)\n        ))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let mut axes: Vec<Axis> = (0..inputs[0].rank())\n            .filter(|&ix| ix != self.k_axis && ix != self.mn_axis)\n            .enumerate()\n            .zip('a'..)\n            .map(|((o, i), repr)| Axis::new(repr, 1, 1).input(0, i).output(0, o))\n            .collect();\n        axes.push(Axis::new('K', 1, 1).input(0, self.k_axis));\n        axes.push(Axis::new('M', 1, 1).input(0, self.mn_axis));\n        axes.push(Axis::new('P', 1, 1).output(0, outputs[0].rank()));\n        AxesMapping::new(1, 1, axes)\n    }\n\n    as_op!();\n}\n\nimpl OptMatMulPack {\n    fn do_eval(&self, _session: &TurnState, input: TValue) -> TractResult<TVec<TValue>> {\n        unsafe {\n            let mode = self.mode_picker.pick(input.shape()[self.mn_axis])?;\n            let packer = &self.packers[mode];\n            let output_shape: TVec<usize> = self.output_shape(input.shape());\n            let stores = if output_shape.iter().all(|d| *d == 1) {\n                let packed = packer.pack_tensor_view(&input.view(), self.k_axis, self.mn_axis)?;\n                PackedMatrixStorage::new_batched(&output_shape, vec![packed])\n                    .into_tensor(input.datum_type())\n            } else {\n                let mut bc_shape: TVec<usize> = input.shape().into();\n                bc_shape[self.k_axis] = 1;\n                bc_shape[self.mn_axis] = 1;\n\n                let mut values: Vec<Box<dyn MMMInputValue>> =\n                    Vec::with_capacity(output_shape.iter().product());\n                for coord in indices(&*bc_shape) {\n                    let offset = coord\n                        .as_array_view()\n                        .iter()\n                        .zip(input.strides())\n                        .map(|(x, s)| *x as isize * s)\n                        .sum::<isize>()\n                        * input.datum_type().size_of() as isize;\n                    values.push(packer.pack_tensor_view(\n                        &TensorView::from_bytes(&input, offset, input.shape(), input.strides()),\n                        self.k_axis,\n                        self.mn_axis,\n                    )?);\n                }\n                PackedMatrixStorage::new_batched(&output_shape, values)\n                    .into_tensor(input.datum_type())\n            };\n            Ok(tvec!(stores.into_tvalue()))\n        }\n    }\n\n    pub fn output_shape<D: DimLike>(&self, input: &[D]) -> TVec<D> {\n        let mut packed_shape: TVec<D> = input.into();\n        packed_shape.remove(self.mn_axis.max(self.k_axis));\n        packed_shape.remove(self.mn_axis.min(self.k_axis));\n        packed_shape\n    }\n}\n\n#[derive(Hash, Clone, Debug, PartialEq, Eq)]\npub struct DynPackedExoticFact {\n    pub k: TDim,\n    pub mn: TDim,\n    pub packers: Vec<PackedFormat>,\n}\n\nimpl ExoticFact for DynPackedExoticFact {\n    fn buffer_sizes(&self) -> TVec<TDim> {\n        tvec!(self.k.clone() * &self.mn * self.packers[0].dt.size_of())\n    }\n}\n\n#[derive(Debug, Clone, Hash, Eq, PartialEq)]\npub struct OptSimpleMatMulPack {\n    pub(crate) packed_format: PackedBlockQuantFormat,\n    pub(crate) k: usize,\n    pub(crate) m: usize,\n}\n\nimpl Op for OptSimpleMatMulPack {\n    fn name(&self) -> StaticName {\n        \"OptSimpleMatMulPack\".into()\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for OptSimpleMatMulPack {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(None)\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let bqs = input.try_storage_as::<BlockQuantStorage>()?;\n        // Leading dims before the last 2 (M, K) are batch/group dims\n        let num_groups: usize = input.shape()[..input.rank().saturating_sub(2)].iter().product();\n        let m_per_group = input.shape()[input.rank() - 2];\n        let k = *input.shape().last().unwrap();\n        let values = (0..num_groups)\n            .map(|g| {\n                let slice = block_quant_slice(bqs.value(), bqs.format(), m_per_group, k, g);\n                let iv: Box<dyn MMMInputValue> = Box::new(self.packed_format.pack(slice, k)?);\n                Ok(iv)\n            })\n            .collect::<TractResult<Vec<_>>>()?;\n        let leading_shape = &input.shape()[..input.rank().saturating_sub(2)];\n        let output =\n            PackedMatrixStorage::new_batched(leading_shape, values).into_tensor(input.datum_type());\n        Ok(tvec!(output.into_tvalue()))\n    }\n}\n\nimpl TypedOp for OptSimpleMatMulPack {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let input = inputs[0];\n        // Input shape is [G, M, K] — output removes M and K, keeping leading dims\n        let output_shape: TVec<TDim> = if input.rank() > 2 {\n            input.shape[..input.rank() - 2].to_vec().into()\n        } else {\n            tvec!()\n        };\n        let fact =\n            inputs[0].datum_type.fact(&*output_shape).with_exotic_fact(PackedBlockQuantFact {\n                format: self.packed_format.clone(),\n                shape: tvec!(self.m, self.k),\n            });\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/matmul/quant.rs",
    "content": "use anyhow::ensure;\nuse ops::change_axes::wire_with_rank_broadcast;\n\nuse crate::internal::*;\nuse crate::ops;\nuse crate::ops::cast::cast;\n\n/// Wires the offsetting of a matrix and zero point node.\n///\n/// Only wires nodes of u8 type and leaves nodes of different type untouched.\npub fn wire_ensure_q8_flavour(\n    model: &mut TypedModel,\n    prefix: &str,\n    input: &mut OutletId,\n    input_name: &str,\n    zero_point: &mut OutletId,\n    wanted_raw_dt: DatumType,\n) -> TractResult<()> {\n    ensure!(wanted_raw_dt.qparams().is_none());\n    ensure!(wanted_raw_dt.size_of() == 1);\n    let current = model.outlet_fact(*input)?.datum_type.unquantized();\n    ensure!(current.size_of() == 1);\n    ensure!(wanted_raw_dt.size_of() == 1);\n    if model.outlet_fact(*zero_point)?.datum_type != i32::datum_type() {\n        *zero_point = model.wire_node(\n            format!(\"{prefix}.{input_name}_zp.cast\"),\n            cast(i32::datum_type()),\n            &[*zero_point],\n        )?[0];\n    }\n    if current == wanted_raw_dt {\n        return Ok(());\n    }\n    let zp_rank = model.outlet_fact(*zero_point)?.rank();\n    let i32_128 = model.add_const(\n        format!(\"{prefix}.{input_name}.128\"),\n        tensor0(128i32).broadcast_into_rank(zp_rank)?,\n    )?;\n    if current.unquantized().is_signed() {\n        *zero_point = model.wire_node(\n            format!(\"{prefix}.offset_{input_name}_zp_as_u8\"),\n            ops::math::add(),\n            &[*zero_point, i32_128],\n        )?[0];\n        *input = model.wire_node(\n            format!(\"{prefix}.offset_{input_name}_as_u8\"),\n            ops::quant::offset_i8_as_u8(),\n            &[*input],\n        )?[0];\n    } else {\n        *zero_point = model.wire_node(\n            format!(\"{prefix}.offset_{input_name}_zp_as_i8\"),\n            ops::math::sub(),\n            &[*zero_point, i32_128],\n        )?[0];\n        *input = model.wire_node(\n            format!(\"{prefix}.offset_{input_name}_as_i8\"),\n            ops::quant::offset_u8_as_i8(),\n            &[*input],\n        )?[0];\n    }\n    Ok(())\n}\n\npub(crate) fn combine_scales(\n    model: &mut TypedModel,\n    name: &str,\n    a_scale: OutletId,\n    b_scale: OutletId,\n    c_scale: OutletId,\n) -> TractResult<OutletId> {\n    let ab_scale = wire_with_rank_broadcast(\n        format!(\"{name}.ab_scale\"),\n        model,\n        ops::math::mul(),\n        &[a_scale, b_scale],\n    )?[0];\n    let abc_scale = wire_with_rank_broadcast(\n        format!(\"{name}.abc_scales\"),\n        model,\n        ops::math::div(),\n        &[ab_scale, c_scale],\n    )?[0];\n    Ok(abc_scale)\n}\n\n#[allow(clippy::too_many_arguments)]\npub(crate) fn compensate_zero_points(\n    model: &mut TypedModel,\n    name: &str,\n    result: OutletId,\n    k: TDim,\n    a0: OutletId,\n    b0: OutletId,\n    sum_a: OutletId,\n    sum_b: OutletId,\n) -> TractResult<OutletId> {\n    let output_rank = model.outlet_fact(result)?.rank();\n    ensure!(model.outlet_fact(sum_a)?.rank() == output_rank);\n    ensure!(model.outlet_fact(sum_b)?.rank() == output_rank);\n\n    let a0 =\n        model.wire_node(format!(\"{name}.cast_a0\"), ops::cast::cast(i32::datum_type()), &[a0])?[0];\n\n    let b0 =\n        model.wire_node(format!(\"{name}.cast_b0\"), ops::cast::cast(i32::datum_type()), &[b0])?[0];\n\n    let k = model.add_const(format!(\"{name}.k\"), rctensor0(k))?;\n    let k = model.wire_node(format!(\"{name}.cast_k\"), ops::cast::cast(i32::datum_type()), &[k])?[0];\n\n    let a0_sum_b = wire_with_rank_broadcast(\n        format!(\"{name}.a0_sum_b\"),\n        model,\n        ops::math::mul(),\n        &[a0, sum_b],\n    )?[0];\n\n    let b0_sum_a = wire_with_rank_broadcast(\n        format!(\"{name}.b0_sum_a\"),\n        model,\n        ops::math::mul(),\n        &[b0, sum_a],\n    )?[0];\n\n    let a0_k =\n        wire_with_rank_broadcast(format!(\"{name}.a0_k\"), model, ops::math::mul(), &[a0, k])?[0];\n\n    let a0_k_b0 =\n        wire_with_rank_broadcast(format!(\"{name}.a0_k_b0\"), model, ops::math::mul(), &[a0_k, b0])?\n            [0];\n\n    let result = wire_with_rank_broadcast(\n        format!(\"{}.minus_a0_B\", &name),\n        model,\n        ops::math::sub(),\n        &[result, a0_sum_b],\n    )?[0];\n    let result = wire_with_rank_broadcast(\n        format!(\"{}.minus_b0_A\", &name),\n        model,\n        ops::math::sub(),\n        &[result, b0_sum_a],\n    )?[0];\n\n    let result = wire_with_rank_broadcast(\n        format!(\"{}.plus_a0_k_b0\", &name),\n        model,\n        ops::math::add(),\n        &[result, a0_k_b0],\n    )?[0];\n\n    Ok(result)\n}\n\npub(crate) fn requant(\n    model: &mut TypedModel,\n    name: &str,\n    wire: OutletId,\n    dt: DatumType,\n    scale: OutletId,\n    zero_point: OutletId,\n) -> TractResult<OutletId> {\n    let wire = wire_with_rank_broadcast(\n        format!(\"{name}.scale\"),\n        model,\n        ops::quant::scale(),\n        &[scale, wire],\n    )?[0];\n\n    let zero_point = model.wire_node(\n        format!(\"{name}.cast_c0\"),\n        ops::cast::cast(i32::datum_type()),\n        &[zero_point],\n    )?[0];\n\n    let wire = wire_with_rank_broadcast(\n        format!(\"{name}.zeropoint\"),\n        model,\n        ops::math::add(),\n        &[wire, zero_point],\n    )?[0];\n\n    clamp_and_cast_to(model, name, dt, wire)\n}\n\npub(crate) fn clamp_and_cast_to(\n    model: &mut TypedModel,\n    name: &str,\n    dt: DatumType,\n    wire: OutletId,\n) -> TractResult<OutletId> {\n    if dt == i32::datum_type() {\n        return Ok(wire);\n    }\n    let rank = model.outlet_fact(wire)?.rank();\n    let inf = dt\n        .unquantized()\n        .min_value()\n        .cast_to_dt(DatumType::I32)?\n        .into_owned()\n        .broadcast_into_rank(rank)?\n        .into_arc_tensor();\n    let inf = model.add_const(format!(\"{name}.min.const\"), inf)?;\n    let sup = dt\n        .unquantized()\n        .max_value()\n        .cast_to_dt(DatumType::I32)?\n        .into_owned()\n        .broadcast_into_rank(rank)?\n        .into_arc_tensor();\n    let sup = model.add_const(format!(\"{name}.max.const\"), sup)?;\n    let wire = model.wire_node(format!(\"{name}.min\"), ops::math::min(), &[wire, sup])?;\n    let wire = model.wire_node(format!(\"{name}.max\"), ops::math::max(), &[wire[0], inf])?;\n    let wire = model.wire_node(format!(\"{name}.cast\"), ops::cast::cast(dt), &wire)?;\n    Ok(wire[0])\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_ndarray::prelude::*;\n\n    proptest! {\n        #[test]\n        fn prop_i8_i8_i8(pb in any::<QMatMulProblemI8I8I8>()) {\n            pb.check();\n        }\n\n        #[test]\n        fn prop_i8_i8_u8(pb in any::<QMatMulProblemI8I8U8>()) {\n            pb.check();\n        }\n\n        #[test]\n        fn prop_i8_u8_i8(pb in any::<QMatMulProblemI8U8I8>()) {\n            pb.check();\n        }\n\n        #[test]\n        fn prop_u8_i8_i8(pb in any::<QMatMulProblemU8I8I8>()) {\n            pb.check();\n        }\n\n        #[test]\n        fn prop_i8_u8_u8(pb in any::<QMatMulProblemI8U8U8>()) {\n            pb.check();\n        }\n\n        #[test]\n        fn prop_u8_i8_u8(pb in any::<QMatMulProblemU8I8U8>()) {\n            pb.check();\n        }\n\n        #[test]\n        fn prop_u8_u8_i8(pb in any::<QMatMulProblemU8U8I8>()) {\n            pb.check();\n        }\n\n        #[test]\n        fn prop_u8_u8_u8(pb in any::<QMatMulProblemU8U8U8>()) {\n            pb.check();\n        }\n    }\n\n    #[test]\n    fn c0() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[0]]),\n            b: arr2(&[[0]]),\n            bias: tensor0(0i32),\n            a0: 0,\n            b0: 0,\n            c0: 1,\n            a_scale: 1.0,\n            b_scale: 1.0,\n            c_scale: 1.0,\n        }\n        .check();\n    }\n\n    #[test]\n    fn b_scale() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[0]]),\n            b: arr2(&[[0]]),\n            bias: tensor0(0i32),\n            a0: 0,\n            b0: 0,\n            c0: 1,\n            a_scale: 1.0,\n            b_scale: 2.0,\n            c_scale: 1.0,\n        }\n        .check();\n    }\n\n    #[test]\n    fn sat() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[0]]),\n            b: arr2(&[[34]]),\n            bias: tensor0(0i32),\n            a0: -17,\n            b0: 1,\n            c0: 0,\n            a_scale: 1.0,\n            b_scale: 0.05,\n            c_scale: 0.25,\n        }\n        .check();\n    }\n\n    #[test]\n    fn rounding() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[26]]),\n            b: arr2(&[[0]]),\n            bias: tensor0(0i32),\n            a0: 27,\n            b0: -1,\n            c0: 1,\n            a_scale: 1.0,\n            b_scale: 0.05,\n            c_scale: 1.0,\n        }\n        .check();\n    }\n\n    #[test]\n    fn neg_rounding() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[-23]]),\n            b: arr2(&[[-2]]),\n            bias: tensor0(0i32),\n            a0: -11,\n            b0: -45,\n            c0: 0,\n            a_scale: 0.1,\n            b_scale: 1.0,\n            c_scale: 1.0,\n        }\n        .check();\n    }\n\n    #[test]\n    fn rounding_ties_2() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[47], [0]]),\n            b: arr2(&[[1, 0, 30]]),\n            bias: tensor0(0i32),\n            a0: 86,\n            b0: 19,\n            c0: 0,\n            a_scale: 0.1,\n            b_scale: 1.0,\n            c_scale: 0.6,\n        }\n        .check();\n    }\n\n    #[test]\n    fn rounding_ties_3() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[-30]]),\n            b: arr2(&[[0, 107, 0]]),\n            bias: tensor0(0i32),\n            a0: -59,\n            b0: 117,\n            c0: 0,\n            a_scale: 1.0,\n            b_scale: 0.15,\n            c_scale: 0.6,\n        }\n        .check();\n    }\n\n    #[test]\n    fn onnx_test_matmulinteger() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[11, 7, 3], [10, 6, 2], [9, 5, 1], [8, 4, 0]]),\n            b: arr2(&[[1, 4], [2, 5], [3, 6]]),\n            bias: tensor0(0i32),\n            a0: 12,\n            b0: 0,\n            c0: 0,\n            a_scale: 1.0,\n            b_scale: 1.0,\n            c_scale: 1.0,\n        }\n        .check();\n    }\n\n    fn round_ties_to_right(x: f32) -> i32 {\n        (x + 0.5).floor() as i32\n    }\n\n    fn scale() -> BoxedStrategy<f32> {\n        prop_oneof![Just(1.0), (1i32..=20).prop_map(|x| x as f32 / 20.0)].boxed()\n    }\n\n    macro_rules! impl_qmmp {\n        ($name:ident, $a:ty, $b:ty, $c:ty $(,)?) => {\n            #[derive(Debug)]\n            struct $name {\n                a: Array2<$a>,\n                b: Array2<$b>,\n                bias: Tensor,\n                a0: $a,\n                b0: $b,\n                c0: $c,\n                a_scale: f32,\n                b_scale: f32,\n                c_scale: f32,\n            }\n\n            impl $name {\n                fn check(&self) {\n                    let check_with = |r: &Array2<$c>, opt: bool, qp: bool| {\n                        let t = self.tract(opt, qp);\n                        assert!(\n                            r.iter().zip(t.iter()).all(|(r, t)| r.max(t) - r.min(t) <= 1),\n                            \"mismatch! optimized plan: {}, dynamic qparams: {}, reference: {:?}, tract: {:?}\",\n                            opt,\n                            qp,\n                            r,\n                            t,\n                            );\n                    };\n\n                    let r = self.reference();\n                    check_with(&r, false, false);\n                    check_with(&r, false, true);\n                    check_with(&r, true, false);\n                    check_with(&r, true, true);\n                }\n\n                fn reference(&self) -> Array2<$c> {\n                    let a = self.a.map(|&x| (x as f32 - self.a0 as f32) * self.a_scale);\n                    let b = self.b.map(|&x| (x as f32 - self.b0 as f32) * self.b_scale);\n                    let c = a.dot(&b);\n                    let c = c.map(|&x| round_ties_to_right(x / self.c_scale) + self.c0 as i32);\n                    c.map(|&x| x.max(<$c>::MIN as i32).min(<$c>::MAX as i32) as $c)\n                }\n\n                fn tract(&self, opt: bool, qp: bool) -> Array2<$c> {\n                    let mut model = TypedModel::default();\n                    let mut inputs = tvec![];\n                    inputs.push(\n                        model\n                        .add_source(\"a\", <$a>::fact( &[self.a.nrows(), self.a.ncols()]))\n                        .unwrap(),\n                        );\n                    inputs.push(\n                        model\n                        .add_source(\"b\", <$b>::fact(&[self.b.nrows(), self.b.ncols()]))\n                        .unwrap(),\n                        );\n                    inputs.push(\n                        model\n                        .add_source(\"bias\", i32::fact(self.bias.shape()))\n                        .unwrap(),\n                        );\n                    if qp {\n                        inputs.push(model.add_source(\"a0\", TypedFact::scalar::<$a>()).unwrap());\n                        inputs\n                            .push(model.add_source(\"a_scale\", TypedFact::scalar::<f32>()).unwrap());\n                        inputs.push(model.add_source(\"b0\", TypedFact::scalar::<$b>()).unwrap());\n                        inputs\n                            .push(model.add_source(\"b_scale\", TypedFact::scalar::<f32>()).unwrap());\n                        inputs.push(model.add_source(\"c0\", TypedFact::scalar::<$c>()).unwrap());\n                        inputs\n                            .push(model.add_source(\"c_scale\", TypedFact::scalar::<f32>()).unwrap());\n                    } else {\n                        inputs.push(model.add_const(\"a0\", rctensor0(self.a0)).unwrap());\n                        inputs\n                            .push(model.add_const(\"a_scale\", rctensor0(self.a_scale)).unwrap());\n                        inputs.push(model.add_const(\"b0\", rctensor0(self.b0)).unwrap());\n                        inputs\n                            .push(model.add_const(\"b_scale\", rctensor0(self.b_scale)).unwrap());\n                        inputs.push(model.add_const(\"c0\", rctensor0(self.c0)).unwrap());\n                        inputs\n                            .push(model.add_const(\"c_scale\", rctensor0(self.c_scale)).unwrap());\n                    };\n                    let result = model\n                        .wire_node(\n                            \"einsum\",\n                            crate::ops::einsum::EinSum {\n                                axes: \"mk,kn,,,,,,,->mn\".parse().unwrap(),\n                                operating_dt: i32::datum_type(),\n                                q_params: Some(<$c>::datum_type())\n                            },\n                            &inputs,\n                        ).unwrap();\n                    model.select_output_outlets(&result).unwrap();\n\n                    let inputs = if qp {\n                        tvec![\n                            self.a.clone().into_tensor(),\n                            self.b.clone().into_tensor(),\n                            self.bias.clone(),\n                            self.a0.into(),\n                            self.a_scale.into(),\n                            self.b0.into(),\n                            self.b_scale.into(),\n                            self.c0.into(),\n                            self.c_scale.into(),\n                        ]\n                    } else {\n                        tvec![\n                            self.a.clone().into_tensor(),\n                            self.b.clone().into_tensor(),\n                            self.bias.clone(),\n                        ]\n                    };\n                    let inputs = inputs.into_iter().map(|t| t.into_tvalue()).collect();\n                    let optimized = if opt { model.into_optimized().unwrap() } else { model };\n                    let mut outputs = optimized.into_runnable()\n                        .unwrap()\n                        .run(inputs)\n                        .unwrap();\n                    outputs\n                        .remove(0)\n                        .into_tensor()\n                        .into_plain_array::<$c>()\n                        .unwrap()\n                        .into_dimensionality()\n                        .unwrap()\n                }\n            }\n\n            impl Arbitrary for $name {\n                type Parameters = ();\n                type Strategy = BoxedStrategy<$name>;\n                fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {\n                    (1usize..=4, 1usize..=4, 1usize..=4)\n                        .prop_flat_map(|(m, k, n)| {\n                            (\n                                Just((m, k, n)),\n                                vec(any::<$a>(), m * k..=m * k),\n                                vec(any::<$b>(), k * n..=k * n),\n                                any::<$a>(),\n                                any::<$b>(),\n                                any::<$c>(),\n                                scale(),\n                                scale(),\n                                scale(),\n                                )\n                        })\n                    .prop_map(|((m, k, n), a, b, a0, b0, c0, a_scale, b_scale, c_scale)| {\n                        $name {\n                            a: Array2::from_shape_vec((m, k), a).unwrap(),\n                            b: Array2::from_shape_vec((k, n), b).unwrap(),\n                            bias: tensor0(0i32),\n                            a0,\n                            b0,\n                            c0,\n                            a_scale,\n                            b_scale,\n                            c_scale,\n                        }\n                    })\n                    .boxed()\n                }\n            }\n        };\n    }\n\n    impl_qmmp! { QMatMulProblemI8I8I8, i8, i8, i8 }\n    impl_qmmp! { QMatMulProblemI8I8U8, i8, i8, u8 }\n    impl_qmmp! { QMatMulProblemI8U8I8, i8, u8, i8 }\n    impl_qmmp! { QMatMulProblemU8I8I8, u8, i8, i8 }\n    impl_qmmp! { QMatMulProblemI8U8U8, i8, u8, u8 }\n    impl_qmmp! { QMatMulProblemU8I8U8, u8, i8, u8 }\n    impl_qmmp! { QMatMulProblemU8U8I8, u8, u8, i8 }\n    impl_qmmp! { QMatMulProblemU8U8U8, u8, u8, u8 }\n\n    #[test]\n    fn test_qmmp_i8_i8_i8_0() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[76, 76, 76], [127, -127, 102]]),\n            b: arr2(&[[25, 51, 76, 102, 127], [-51, -25, 0, 25, 51], [-25, -51, -76, -102, -127]]),\n            bias: tensor0(0i32),\n            a0: 51,\n            b0: 0,\n            c0: -31,\n            a_scale: 0.039215688,\n            b_scale: 0.039215688,\n            c_scale: 0.09411765,\n        }\n        .check(); // c: [[-52, -41, -31, -21, -10], [127, 64, 0, -62, -126]]\n    }\n\n    #[test]\n    fn test_qmmp_i8_i8_i8_1() {\n        QMatMulProblemI8I8I8 {\n            a: arr2(&[[-34, -2]]),\n            b: arr2(&[[-79], [21]]),\n            bias: tensor0(0i32),\n            a0: -87,\n            b0: -17,\n            c0: 0,\n            a_scale: 1.0,\n            b_scale: 1.0,\n            c_scale: 1.0,\n        }\n        .check(); // c: [[-52, -41, -31, -21, -10], [127, 64, 0, -62, -126]]\n    }\n\n    #[test]\n    fn test_qmmp_i8_i8_u8() {\n        QMatMulProblemI8I8U8 {\n            a: arr2(&[[76, 76, 76], [127, -127, 102]]),\n            b: arr2(&[[25, 51, 76, 102, 127], [-51, -25, 0, 25, 51], [-25, -51, -76, -102, -127]]),\n            bias: tensor0(0i32),\n            a0: 51,\n            b0: 0,\n            c0: 96,\n            a_scale: 0.039215688,\n            b_scale: 0.039215688,\n            c_scale: 0.09411765,\n        }\n        .check(); // c: [[75, 86, 96, 106, 117], [255, 191, 127, 65, 1]]\n    }\n\n    #[test]\n    fn test_qmmp_i8_u8_i8() {\n        QMatMulProblemI8U8I8 {\n            a: arr2(&[[76, 76, 76], [127, -127, 102]]),\n            b: arr2(&[[152, 178, 203, 229, 254], [76, 102, 127, 152, 178], [102, 76, 51, 25, 0]]),\n            bias: tensor0(0i32),\n            a0: 51,\n            b0: 127,\n            c0: -31,\n            a_scale: 0.039215688,\n            b_scale: 0.039215688,\n            c_scale: 0.09411765,\n        }\n        .check(); // c: [[-52, -41, -31, -21, -10], [127, 64, 0, -62, -126]]\n    }\n\n    #[test]\n    fn test_qmmp_u8_i8_i8() {\n        QMatMulProblemU8I8I8 {\n            a: arr2(&[[204, 204, 204], [255, 1, 230]]),\n            b: arr2(&[[25, 51, 76, 102, 127], [-51, -25, 0, 25, 51], [-25, -51, -76, -102, -127]]),\n            bias: tensor0(0i32),\n            a0: 179,\n            b0: 0,\n            c0: -31,\n            a_scale: 0.039215688,\n            b_scale: 0.039215688,\n            c_scale: 0.09411765,\n        }\n        .check(); // c: [[-52, -41, -31, -21, -10], [127, 64, 0, -62, -126]]\n    }\n\n    #[test]\n    fn test_qmmp_i8_u8_u8() {\n        QMatMulProblemI8U8U8 {\n            a: arr2(&[[76, 76, 76], [127, -127, 102]]),\n            b: arr2(&[[152, 178, 203, 229, 254], [76, 102, 127, 152, 178], [102, 76, 51, 25, 0]]),\n            bias: tensor0(0i32),\n            a0: 51,\n            b0: 127,\n            c0: 96,\n            a_scale: 0.039215688,\n            b_scale: 0.039215688,\n            c_scale: 0.09411765,\n        }\n        .check(); // c: [[75, 86, 96, 106, 117], [255, 191, 127, 65, 1]]\n    }\n\n    #[test]\n    fn test_qmmp_u8_i8_u8() {\n        QMatMulProblemU8I8U8 {\n            a: arr2(&[[204, 204, 204], [255, 1, 230]]),\n            b: arr2(&[[25, 51, 76, 102, 127], [-51, -25, 0, 25, 51], [-25, -51, -76, -102, -127]]),\n            bias: tensor0(0i32),\n            a0: 179,\n            b0: 0,\n            c0: 96,\n            a_scale: 0.039215688,\n            b_scale: 0.039215688,\n            c_scale: 0.09411765,\n        }\n        .check(); // c: [[75, 86, 96, 106, 117], [255, 191, 127, 65, 1]]\n    }\n\n    #[test]\n    fn test_qmmp_u8_u8_i8() {\n        QMatMulProblemU8U8I8 {\n            a: arr2(&[[204, 204, 204], [255, 1, 230]]),\n            b: arr2(&[[152, 178, 203, 229, 254], [76, 102, 127, 152, 178], [102, 76, 51, 25, 0]]),\n            bias: tensor0(0i32),\n            a0: 179,\n            b0: 127,\n            c0: -31,\n            a_scale: 0.039215688,\n            b_scale: 0.039215688,\n            c_scale: 0.09411765,\n        }\n        .check(); // c: [[-52, -41, -31, -21, -10], [127, 64, 0, -62, -126]]\n    }\n\n    #[test]\n    fn test_qmmp_u8_u8_u8() {\n        QMatMulProblemU8U8U8 {\n            a: arr2(&[[204, 204, 204], [255, 1, 230]]),\n            b: arr2(&[[152, 178, 203, 229, 254], [76, 102, 127, 152, 178], [102, 76, 51, 25, 0]]),\n            bias: tensor0(0i32),\n            a0: 179,\n            b0: 127,\n            c0: 96,\n            a_scale: 0.039215688,\n            b_scale: 0.039215688,\n            c_scale: 0.09411765,\n        }\n        .check(); // c: [[75, 86, 96, 106, 117], [255, 191, 127, 65, 1]]\n    }\n}\n"
  },
  {
    "path": "core/src/ops/memory/force_eval.rs",
    "content": "use crate::internal::*;\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct ForceEval {\n    pub slots: Vec<usize>,\n}\n\nimpl ForceEval {\n    pub fn new(slots: Vec<usize>) -> ForceEval {\n        ForceEval { slots }\n    }\n}\n\nimpl Op for ForceEval {\n    fn name(&self) -> StaticName {\n        \"ForceEval\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"slots: {:?}\", self.slots)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for ForceEval {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(None)\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let max_slot_idx = self.slots.iter().copied().max().unwrap_or(0);\n        ensure!(\n            inputs.len() > max_slot_idx,\n            format!(\n                \"Expected at least {} inputs given the slot indexes that needs to be forced eval: {:?}\",\n                max_slot_idx + 1,\n                self.slots\n            )\n        );\n        let outputs = inputs\n            .into_iter()\n            .enumerate()\n            .filter_map(|(idx, val)| if !self.slots.contains(&idx) { Some(val) } else { None })\n            .collect::<TVec<_>>();\n        Ok(outputs)\n    }\n}\n\nimpl TypedOp for ForceEval {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let output_facts = inputs\n            .iter()\n            .enumerate()\n            .filter_map(\n                |(idx, fact)| {\n                    if !self.slots.contains(&idx) { Some((*fact).clone()) } else { None }\n                },\n            )\n            .collect::<TVec<_>>();\n        Ok(output_facts)\n    }\n}\n"
  },
  {
    "path": "core/src/ops/memory/load.rs",
    "content": "use crate::internal::*;\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Load {\n    pub id: String,\n}\n\nimpl Load {\n    pub fn new(id: &str) -> Load {\n        Load { id: id.to_string() }\n    }\n}\n\nimpl Op for Load {\n    fn name(&self) -> StaticName {\n        \"Load\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"id: {:?}\", self.id)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Load {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(self.clone())))\n    }\n}\n\nimpl TypedOp for Load {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() == 1, \"Expected one input (default value) for Load op\");\n        // New typed fact are created to avoid propagating const information\n        let input_facts = inputs\n            .iter()\n            .map(|it| TypedFact::dt_shape(it.datum_type, it.shape.clone()))\n            .collect::<TVec<_>>();\n        Ok(input_facts)\n    }\n}\n\nimpl OpState for Load {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        _op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let tensor = session\n            .tensors\n            .get(&self.id)\n            .map_or_else(\n                || -> TractResult<TVec<TValue>> { Ok(tvec!(input.clone())) },\n                |it| {\n                    // Checks\n                    ensure!(\n                        it.datum_type() == input.datum_type(),\n                        anyhow!(\n                            \"Expected datum {:?}, found {:?}\",\n                            input.datum_type(),\n                            it.datum_type()\n                        )\n                    );\n                    ensure!(\n                        it.shape() == input.shape(),\n                        anyhow!(\"Expected shape {:?}, found {:?}\", input.shape(), it.shape())\n                    );\n                    Ok(tvec!(it.clone().into_tvalue()))\n                },\n            )\n            .with_context(|| anyhow!(\"While loading tensor from session\"))?;\n\n        Ok(tensor)\n    }\n}\n\ntrivial_op_state_freeze!(Load);\n"
  },
  {
    "path": "core/src/ops/memory/mod.rs",
    "content": "pub mod force_eval;\npub mod load;\npub mod store;\n"
  },
  {
    "path": "core/src/ops/memory/store.rs",
    "content": "use crate::internal::*;\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Store {\n    pub id: String,\n}\n\nimpl Store {\n    pub fn new(id: &str) -> Store {\n        Store { id: id.to_string() }\n    }\n}\n\nimpl Op for Store {\n    fn name(&self) -> StaticName {\n        \"Store\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"id: {:?}\", self.id)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Store {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(self.clone())))\n    }\n}\n\nimpl TypedOp for Store {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(\n            inputs.len() == 2,\n            \"Expected two inputs (input to propagate and state to store) for Store op\"\n        );\n        Ok(tvec![inputs[0].clone()])\n    }\n}\n\nimpl OpState for Store {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        _op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let (input, state) = args_2!(inputs);\n        session.tensors.insert(self.id.clone(), state.into_tensor());\n        Ok(tvec![input])\n    }\n}\n\ntrivial_op_state_freeze!(Store);\n"
  },
  {
    "path": "core/src/ops/mod.rs",
    "content": "//! Ops\nuse std::fmt;\n\nuse downcast_rs::Downcast;\n\nuse dyn_clone;\nuse dyn_eq::DynEq;\n\n#[macro_use]\npub mod macros;\n#[macro_use]\npub mod element_wise;\n#[macro_use]\npub mod binary;\n\npub mod array;\npub mod cast;\npub mod change_axes;\npub mod cnn;\npub mod downsample;\npub mod dummy;\npub mod einsum;\npub mod fft;\npub mod identity;\npub mod konst;\npub mod logic;\npub mod math;\npub mod matmul;\n// pub mod memory;\npub mod nn;\npub mod quant;\npub mod scan;\npub mod source;\npub mod submodel;\npub mod unimpl;\n\npub use downsample::Downsample;\npub use memory::*;\n\nuse crate::internal::*;\nuse crate::optim::OptimizerSession;\n\n/// Level of precision to be expected in implementations comparisons.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum Validation {\n    /// Output is random\n    Random,\n    /// Implementation may induce rounding errors\n    Rounding,\n    /// Implementation must be accurate\n    Accurate,\n}\n\n#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]\npub enum Cost {\n    Div(DatumType),\n    FMA(DatumType),\n    Buffer(DatumType),\n    Params(DatumType),\n    Custom(bool, String),\n}\n\nimpl Cost {\n    pub fn is_compute(&self) -> bool {\n        use Cost::*;\n        match self {\n            FMA(_) | Div(_) => true,\n            Buffer(_) | Params(_) => false,\n            Custom(compute, _) => *compute,\n        }\n    }\n}\n\nimpl std::fmt::Debug for Cost {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        use Cost::*;\n        match self {\n            Div(dt) => write!(f, \"Div({dt:?})\"),\n            FMA(dt) => write!(f, \"FMA({dt:?})\"),\n            Buffer(dt) => write!(f, \"Buffer({dt:?})\"),\n            Params(dt) => write!(f, \"Params({dt:?})\"),\n            Custom(_, name) => write!(f, \"{name}\"),\n        }\n    }\n}\n\npub trait FrozenOpState: fmt::Debug + dyn_clone::DynClone + Send + 'static {\n    fn unfreeze(&self) -> Box<dyn OpState>;\n}\n\npub trait OpStateFreeze {\n    fn freeze(&self) -> Box<dyn FrozenOpState>;\n}\n\ndyn_clone::clone_trait_object!(FrozenOpState);\n\npub trait OpState: fmt::Debug + dyn_clone::DynClone + OpStateFreeze + Downcast {\n    fn load_from(\n        &mut self,\n        _: &mut TurnState,\n        _: &mut dyn Iterator<Item = TValue>,\n    ) -> TractResult<()> {\n        Ok(())\n    }\n\n    fn save_to(&self, _: &mut Vec<TValue>) -> TractResult<()> {\n        Ok(())\n    }\n\n    fn init_tensor_fact(&self) -> Option<(String, TypedFact)> {\n        None\n    }\n\n    fn resolve_symbols(&mut self, _: &mut TurnState) -> TractResult<()> {\n        Ok(())\n    }\n\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>>;\n}\ndyn_clone::clone_trait_object!(OpState);\nimpl_downcast!(OpState);\n\npub trait EvalOp {\n    #[allow(unused_variables)]\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        bail!(\"stateless evaluation not implemented\")\n    }\n\n    #[allow(unused_variables)]\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        self.eval(inputs).context(\"Running legacy eval\")\n    }\n\n    #[allow(unused_variables)]\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(None)\n    }\n\n    fn is_stateless(&self) -> bool;\n}\n\n/// A base operation\npub trait Op:\n    fmt::Debug + dyn_clone::DynClone + dyn_eq::DynEq + Send + Sync + 'static + Downcast + EvalOp\n{\n    fn name(&self) -> StaticName;\n\n    /// The kind of accuracy check that should be performed on operation when\n    /// testing them.\n    fn validation(&self) -> Validation {\n        Validation::Accurate\n    }\n\n    /// Short (one-line) strings giving hints on internal implementation or\n    /// important configuration details to be displayed in dumps.\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![])\n    }\n\n    fn as_typed(&self) -> Option<&dyn TypedOp>;\n}\n\nimpl_downcast!(Op);\ndyn_clone::clone_trait_object!(Op);\ndyn_eq::eq_trait_object!(Op);\n\npub trait TypedOp:\n    Op + fmt::Debug + dyn_clone::DynClone + Send + Sync + 'static + Downcast + EvalOp\n{\n    /// Reinterpret the TypedOp as an Op.\n    fn as_op(&self) -> &dyn Op;\n\n    /// Reinterpret the TypedOp as an Op, mutably.\n    fn as_op_mut(&mut self) -> &mut dyn Op;\n\n    /// Deduce output facts from input facts.\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>>;\n\n    #[allow(unused_variables)]\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::disconnected(inputs, outputs)\n    }\n\n    /// Fuse op after codegen to deal with local optimisations.\n    fn fuse(&self, _model: &TypedModel, _node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {\n        Ok(None)\n    }\n\n    /// Declutter the op to the tract_core operator set as much as possible.\n    #[allow(unused_variables)]\n    fn declutter_with_session(\n        &self,\n        session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        self.declutter(model, node)\n    }\n\n    /// Declutter the op to the tract_core operator set as much as possible.\n    #[allow(unused_variables)]\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        Ok(None)\n    }\n\n    /// Computes a cost hint of the operation.\n    ///\n    /// Each pair is a type of operation and a number per call on eval.\n    fn cost(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        Ok(tvec!())\n    }\n\n    /// Derive ROI (region of interest) expressions for this node's inputs.\n    /// Called by the PropagateRoi pass. The default implementation uses\n    /// axes_mapping to bubble output ROI to inputs with coordinate remapping.\n    /// Override for ops that introduce ROIs (e.g. Iff, masked softmax) or\n    /// that need arithmetic coordinate transforms (e.g. Slice, Pad).\n    fn input_roi(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TVec<Option<TDim>>>> {\n        crate::optim::propagate_roi::bubble_roi(model, node)\n    }\n\n    #[allow(unused_variables)]\n    fn suggested_axis_changes(&self) -> TractResult<TVec<(InOut, AxisOp)>> {\n        Ok(tvec!())\n    }\n\n    #[allow(unused_variables)]\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        Ok(None)\n    }\n\n    #[allow(unused_variables)]\n    #[allow(clippy::too_many_arguments)]\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        model: &TypedModel,\n        node: &TypedNode,\n        prefix: &str,\n        inputs: &[OutletId],\n        output_axis: usize,\n        start: &TDim,\n        end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        Ok(None)\n    }\n\n    /// Transforms the op in an equivalent one, operating on dt (i8 or u8).\n    ///\n    /// Returns None if the op can not be translated.\n    #[allow(unused_variables)]\n    fn quantize(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        dt: DatumType,\n        scale: f32,\n        zero_point: i32,\n    ) -> TractResult<Option<Box<dyn TypedOp>>> {\n        Ok(None)\n    }\n\n    /// Transform the op into by providing a value to one or more symbols.\n    #[allow(unused_variables)]\n    fn concretize_dims(\n        &self,\n        source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let inputs = node.inputs.iter().map(|i| mapping[i]).collect::<TVec<_>>();\n        target.wire_node(&node.name, node.op.clone(), &inputs)\n    }\n\n    /// Translate the op into the most efficient form possible for execution.\n    ///\n    /// This transformation is supposed to be final, no more pass are expected\n    /// to be run on the codegen networks.\n    #[allow(unused_variables)]\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        Ok(None)\n    }\n\n    /// Nested model multipliers, with label (for profiling).\n    #[allow(unused_variables)]\n    fn nested_model_multipliers(&self, inputs: &[&TypedFact]) -> Vec<(StaticName, TDim)> {\n        vec![]\n    }\n}\n\nimpl_downcast!(TypedOp);\ndyn_clone::clone_trait_object!(TypedOp);\ndyn_eq::eq_trait_object!(TypedOp);\n\nimpl<O: Op> From<O> for Box<dyn Op> {\n    fn from(it: O) -> Box<dyn Op> {\n        Box::new(it)\n    }\n}\n\nimpl<O: TypedOp> From<O> for Box<dyn TypedOp> {\n    fn from(it: O) -> Box<dyn TypedOp> {\n        Box::new(it)\n    }\n}\n\nimpl<'a> From<&'a Box<dyn TypedOp>> for Box<dyn TypedOp> {\n    fn from(it: &'a Box<dyn TypedOp>) -> Box<dyn TypedOp> {\n        it.clone()\n    }\n}\n\nimpl AsRef<dyn Op> for dyn TypedOp {\n    fn as_ref(&self) -> &dyn Op {\n        self.as_op()\n    }\n}\n\nimpl AsRef<dyn Op> for Box<dyn TypedOp> {\n    fn as_ref(&self) -> &dyn Op {\n        self.as_op()\n    }\n}\n\nimpl AsMut<dyn Op> for dyn TypedOp {\n    fn as_mut(&mut self) -> &mut dyn Op {\n        self.as_op_mut()\n    }\n}\n\nimpl AsMut<dyn Op> for Box<dyn TypedOp> {\n    fn as_mut(&mut self) -> &mut dyn Op {\n        self.as_op_mut()\n    }\n}\n\nimpl std::fmt::Display for Box<dyn Op> {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \"{}\", self.name())\n    }\n}\n\nimpl std::fmt::Display for Box<dyn TypedOp> {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \"{}\", self.name())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/nn/data_formats.rs",
    "content": "use crate::internal::*;\nuse std::fmt;\nuse tract_itertools::Itertools;\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]\npub enum DataFormat {\n    #[default]\n    NCHW,\n    NHWC,\n    CHW,\n    HWC,\n}\n\nimpl DataFormat {\n    pub fn dispose_n_axis(&self) -> DataFormat {\n        match self {\n            DataFormat::NCHW => DataFormat::CHW,\n            DataFormat::NHWC => DataFormat::HWC,\n            _ => panic!(\"Attempt at removing N axis on {self:?}\"),\n        }\n    }\n\n    pub fn shape<D, S>(&self, shape: S) -> TractResult<BaseDataShape<D, S>>\n    where\n        D: DimLike,\n        S: AsRef<[D]> + fmt::Debug,\n    {\n        let mut strides: TVec<D> = tvec![D::one()];\n        for dim in shape.as_ref().iter().skip(1).rev() {\n            let previous = strides.last().unwrap().clone();\n            strides.push(previous * dim)\n        }\n        strides.reverse();\n        Ok(BaseDataShape { fmt: *self, shape, strides })\n    }\n\n    pub fn from_n_c_hw<D, S>(&self, n: D, c: D, shape: S) -> TractResult<BaseDataShape<D, TVec<D>>>\n    where\n        D: DimLike,\n        S: AsRef<[D]> + fmt::Debug,\n    {\n        let mut me = tvec!();\n        if *self == DataFormat::NCHW || *self == DataFormat::NHWC {\n            me.push(n);\n        }\n        if *self == DataFormat::NCHW || *self == DataFormat::CHW {\n            me.push(c.clone());\n        }\n        me.extend(shape.as_ref().iter().cloned());\n        if *self == DataFormat::NHWC || *self == DataFormat::HWC {\n            me.push(c);\n        }\n        self.shape(me)\n    }\n\n    pub fn has_n(&self) -> bool {\n        *self == DataFormat::NHWC || *self == DataFormat::NCHW\n    }\n\n    pub fn c_is_last(&self) -> bool {\n        *self == DataFormat::NHWC || *self == DataFormat::HWC\n    }\n\n    pub fn h_axis(&self) -> usize {\n        self.has_n() as usize + !self.c_is_last() as usize\n    }\n\n    pub fn with_n(&self) -> DataFormat {\n        match self {\n            DataFormat::CHW => DataFormat::NCHW,\n            DataFormat::HWC => DataFormat::NHWC,\n            _ => *self,\n        }\n    }\n}\n\npub type SymDataShape = BaseDataShape<TDim, TVec<TDim>>;\npub type DataShape = BaseDataShape<usize, TVec<usize>>;\n\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct BaseDataShape<D, S>\nwhere\n    D: DimLike,\n    S: AsRef<[D]> + fmt::Debug,\n{\n    pub fmt: DataFormat,\n    pub shape: S,\n    pub strides: TVec<D>,\n}\n\nimpl<D, S> fmt::Debug for BaseDataShape<D, S>\nwhere\n    D: DimLike,\n    S: AsRef<[D]> + fmt::Debug,\n{\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{:?} {} (strides: {})\",\n            self.fmt,\n            self.shape.as_ref().iter().join(\",\"),\n            self.strides.iter().join(\",\")\n        )\n    }\n}\n\nimpl<D, S> BaseDataShape<D, S>\nwhere\n    D: DimLike,\n    S: AsRef<[D]> + fmt::Debug,\n{\n    #[inline]\n    pub fn rank(&self) -> usize {\n        self.shape.as_ref().len()\n    }\n\n    #[inline]\n    pub fn hw_rank(&self) -> usize {\n        self.rank() - 1 - self.n_axis().is_some() as usize\n    }\n\n    #[inline]\n    pub fn n_axis(&self) -> Option<usize> {\n        match self.fmt {\n            DataFormat::NHWC | DataFormat::NCHW => Some(0),\n            DataFormat::HWC | DataFormat::CHW => None,\n        }\n    }\n\n    #[inline]\n    pub fn c_axis(&self) -> usize {\n        match self.fmt {\n            DataFormat::NHWC | DataFormat::HWC => self.shape.as_ref().len() - 1,\n            DataFormat::NCHW => 1,\n            DataFormat::CHW => 0,\n        }\n    }\n\n    #[inline]\n    pub fn h_axis(&self) -> usize {\n        match self.fmt {\n            DataFormat::HWC => 0,\n            DataFormat::NHWC | DataFormat::CHW => 1,\n            DataFormat::NCHW => 2,\n        }\n    }\n\n    #[inline]\n    pub fn hw_axes(&self) -> ::std::ops::Range<usize> {\n        self.h_axis()..self.h_axis() + self.hw_rank()\n    }\n\n    #[inline]\n    pub fn n_dim(&self) -> Option<&D> {\n        self.n()\n    }\n\n    #[inline]\n    pub fn c_dim(&self) -> &D {\n        self.c()\n    }\n\n    #[inline]\n    pub fn hw_dims(&self) -> &[D] {\n        unsafe { self.shape.as_ref().get_unchecked(self.hw_axes()) }\n    }\n\n    #[inline]\n    pub fn n(&self) -> Option<&D> {\n        unsafe { self.n_axis().map(|axis| self.shape.as_ref().get_unchecked(axis)) }\n    }\n\n    #[inline]\n    pub fn c(&self) -> &D {\n        unsafe { self.shape.as_ref().get_unchecked(self.c_axis()) }\n    }\n\n    #[inline]\n    pub fn n_stride(&self) -> Option<&D> {\n        unsafe { self.n_axis().map(|axis| self.strides.get_unchecked(axis)) }\n    }\n\n    #[inline]\n    pub fn h_stride(&self) -> &D {\n        unsafe { self.hw_strides().get_unchecked(0) }\n    }\n\n    #[inline]\n    pub fn hw_strides(&self) -> &[D] {\n        unsafe { self.strides.get_unchecked(self.hw_axes()) }\n    }\n\n    #[inline]\n    pub fn w_stride(&self) -> &D {\n        unsafe { self.hw_strides().get_unchecked(self.hw_rank() - 1) }\n    }\n\n    #[inline]\n    pub fn c_stride(&self) -> &D {\n        unsafe { self.strides.get_unchecked(self.c_axis()) }\n    }\n}\n"
  },
  {
    "path": "core/src/ops/nn/gelu_approximate.rs",
    "content": "use crate::internal::*;\nuse crate::ops::binary::TypedBinOp;\nuse crate::ops::element_wise::ElementWiseOp;\nuse crate::ops::math::{Add, Mul, Pow, Tanh};\n\nuse tract_data::half::f16;\n\nfn gelu_approx_f32(x: f32, pow: i32) -> f32 {\n    let sqrt_2_over_pi = (2.0 / std::f32::consts::PI).sqrt();\n    0.5 * x * (1.0 + f32::tanh(sqrt_2_over_pi * (x + 0.044715 * x.powi(pow))))\n}\n\nelement_wise!(gelu_approximate, GeluApproximate { fast_impl: bool },\n    [f16] => |op, xs| {\n        let pow = if op.fast_impl { 2 } else { 3 };\n        xs.iter_mut().for_each(|x| {\n            *x = f16::from_f32(gelu_approx_f32(x.to_f32(), pow));\n        });\n        Ok(())\n    },\n    [f32] => |op, xs| {\n        let pow = if op.fast_impl { 2 } else { 3 };\n        xs.iter_mut().for_each(|x| {\n            *x = gelu_approx_f32(*x, pow);\n        });\n        Ok(())\n    };\n    cost: |dt| {tvec!((Cost::FMA(dt), 15))}\n);\n\n/// Search pattern => NEW_GELU(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^N))); N ∈ {2, 3}\npub fn detect_gelu_approx(\n    _op: &Pow,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    let pow_node = node;\n\n    let in_fact = model.node_input_facts(pow_node.id)?[0];\n    let dt = in_fact.datum_type;\n\n    // Only F16 and F32 is supported.\n    rule_if!(matches!(dt, DatumType::F32 | DatumType::F16));\n\n    rule_if!(\n        model.matches_single_input_const(pow_node, 3.0)\n            || model.matches_single_input_const(pow_node, 2.0)\n    );\n    let fast_impl = model.matches_single_input_const(pow_node, 2.0);\n\n    // 0.044715 * x^N\n    rule_if_some!(mul_coef_a = model.find_succ_bin_with_const::<Mul>(pow_node, 0.044715));\n\n    // x + 0.044715 * x^N\n    rule_if_some!(\n        x_plus_mul_coef_a = model.find_succ_bin_with_outlet::<Add>(mul_coef_a, &pow_node.inputs[0])\n    );\n\n    // sqrt(2/pi) * (x + 0.044715 * x^N)\n    let sqrt_2_over_pi = (2.0 / std::f32::consts::PI).sqrt();\n    rule_if_some!(\n        mul_sqrt_2_over_pi =\n            model.find_succ_bin_with_const::<Mul>(x_plus_mul_coef_a, sqrt_2_over_pi)\n    );\n\n    // tanh(sqrt(2/pi) * (x + 0.044715 * x^N))\n    rule_if_some!(tanh_succ = model.single_succ(mul_sqrt_2_over_pi.id)?);\n    rule_if_some!(tanh_succ_op = tanh_succ.op_as::<ElementWiseOp>());\n    rule_if!(tanh_succ_op.0.is::<Tanh>());\n\n    // 1.0 + tanh(sqrt(2/pi) * (x + 0.044715 * x^N)) N ∈ {2, 3}\n    rule_if_some!(tanh_plus_1 = model.find_succ_bin_with_const::<Add>(tanh_succ, 1.0));\n\n    // Identify Mul\n    rule_if_some!(mul_succ = model.single_succ(tanh_plus_1.id)?);\n    rule_if_some!(mul_succ_op = mul_succ.op_as::<TypedBinOp>());\n    rule_if!(mul_succ_op.0.is::<Mul>());\n\n    // Search first\n    // tmp = x * (1.0 + tanh(sqrt(2/pi) * (x + 0.044715 * x^N)))\n    // out = 0.5 * tmp\n    let last_node_id = if mul_succ.inputs.contains(&pow_node.inputs[0]) {\n        // 0.5 * x * (1.0 + tanh(sqrt(2/pi) * (x + 0.044715 * x^N)))\n        rule_if_some!(last_mul_with_0_5 = model.find_succ_bin_with_const::<Mul>(mul_succ, 0.5));\n        last_mul_with_0_5.id\n    } else {\n        // tmp = 0.5 * x\n        // out = tmp * (1.0 + tanh(sqrt(2/pi) * (x + 0.044715 * x^N))) N ∈ {2, 3}\n        rule_if_some!(\n            x_mul_0_5 = mul_succ\n                .inputs\n                .iter()\n                .filter_map(|i| {\n                    let n = &model.nodes()[i.node];\n                    let op = n.op_as::<TypedBinOp>()?;\n                    op.0.is::<Mul>().then_some(n)\n                })\n                .next()\n        );\n        rule_if!(model.matches_single_input_const(x_mul_0_5, 0.5));\n        rule_if!(x_mul_0_5.inputs.contains(&pow_node.inputs[0]));\n        mul_succ.id\n    };\n\n    let mut patch = TypedModelPatch::default();\n    let gelu_approx_input = patch.taps(model, &pow_node.inputs)?;\n    let out = patch.wire_node(\n        format!(\"{}.gelu_approx\", pow_node.name),\n        gelu_approximate(fast_impl),\n        &[gelu_approx_input[0]],\n    )?;\n    patch.shunt_outside(model, last_node_id.into(), out[0])?;\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "core/src/ops/nn/mod.rs",
    "content": "mod data_formats;\npub mod gelu_approximate;\nmod reduce;\npub mod rms_norm;\npub mod silu;\nmod softmax;\n\npub use self::data_formats::{BaseDataShape, DataFormat, DataShape, SymDataShape};\npub use self::gelu_approximate::GeluApproximate;\npub use self::reduce::{Reduce, Reducer, expand_mean_of_squares};\npub use self::rms_norm::RmsNorm;\npub use self::silu::Silu;\npub use self::softmax::{Softmax, SoftmaxExp, SoftmaxKind};\n\npub use crate::internal::*;\n\nuse tract_num_traits::AsPrimitive;\n\nelement_wise!(sigmoid, Sigmoid,\n [f16] => |_, xs| { (tract_linalg::ops().sigmoid_f16)().run(xs) },\n [f32] => |_, xs| { (tract_linalg::ops().sigmoid_f32)().run(xs) };\n q: [i8, u8, i32, i32] => |x: f32| 1.0 / (1.0+(-x).exp());\n cost: |dt| {tvec!((Cost::FMA(dt), 11), (Cost::Div(dt), 1))};\n declutter: silu::detect_silu\n);\n\nelement_wise!(hard_swish, HardSwish,\n[f16] => |_, xs| { xs.iter_mut().for_each(|x| *x = *x * f16::from_f32(0.0).max(f16::from_f32(1.0).min(f16::from_f32(1. / 6.) * *x + f16::from_f32(0.5)))); Ok(()) },\n[f32] => |_, xs| { xs.iter_mut().for_each(|x| *x = *x * 0f32.max(1f32.min((1. / 6.) * *x + 0.5))); Ok(()) }\n                                         );\n\nelement_wise!(leaky_relu, LeakyRelu { alpha: f32 },\n [f16] => |op, xs| { (tract_linalg::ops().leaky_relu_f16)().run_with_params(xs, f16::from_f32(op.alpha)) },\n [f32] => |op, xs| { (tract_linalg::ops().leaky_relu_f32)().run_with_params(xs, op.alpha) }\n);\n"
  },
  {
    "path": "core/src/ops/nn/reduce.rs",
    "content": "use crate::internal::Axis;\nuse crate::internal::*;\nuse crate::ops::binary::TypedBinOp;\nuse crate::ops::cast::cast;\nuse crate::ops::change_axes::wire_with_rank_broadcast;\nuse crate::ops::element_wise::ElementWiseOp;\nuse crate::ops::math::{Mul, Square, div, square};\nuse std::convert::TryFrom;\nuse std::iter::Sum;\nuse std::mem::transmute;\nuse tract_data::internal::ClampCast;\nuse tract_data::itertools::Itertools;\nuse tract_ndarray::prelude::*;\nuse tract_num_traits::{AsPrimitive, Bounded};\n\nmacro_rules! r {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => {\n        match $dt {\n            DatumType::U8   => $($path)::*::<u8,_,_,_>($($args),*),\n            DatumType::I8   => $($path)::*::<i8,_,_,_>($($args),*),\n            DatumType::U16  => $($path)::*::<u16,_,_,_>($($args),*),\n            DatumType::I16  => $($path)::*::<i16,_,_,_>($($args),*),\n            DatumType::I32  => $($path)::*::<i32,_,_,_>($($args),*),\n            DatumType::I64  => $($path)::*::<i64,_,_,_>($($args),*),\n            DatumType::F16  => $($path)::*::<f16,_,_,_>($($args),*),\n            DatumType::F32  => $($path)::*::<f32,_,_,_>($($args),*),\n            DatumType::F64  => $($path)::*::<f64,_,_,_>($($args),*),\n            DatumType::QI8(_)  => $($path)::*::<i8,_,_,_>($($args),*),\n            DatumType::QU8(_)  => $($path)::*::<u8,_,_,_>($($args),*),\n            _ => bail!(\"{:?} is not a number\", $dt)\n        }\n    };\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*); $($q_path:ident)::* ($($q_args:expr),*)) => {\n        match $dt {\n            DatumType::U8   => $($path)::*::<u8,_,_,_>($($args),*),\n            DatumType::I8   => $($path)::*::<i8,_,_,_>($($args),*),\n            DatumType::U16  => $($path)::*::<u16,_,_,_>($($args),*),\n            DatumType::I16  => $($path)::*::<i16,_,_,_>($($args),*),\n            DatumType::I32  => $($path)::*::<i32,_,_,_>($($args),*),\n            DatumType::I64  => $($path)::*::<i64,_,_,_>($($args),*),\n            DatumType::F16  => $($path)::*::<f16,_,_,_>($($args),*),\n            DatumType::F32  => $($path)::*::<f32,_,_,_>($($args),*),\n            DatumType::F64  => $($path)::*::<f64,_,_,_>($($args),*),\n            DatumType::QI8(_)  => $($q_path)::*::<i8,_,_,_>($($q_args),*),\n            DatumType::QU8(_)  => $($q_path)::*::<u8,_,_,_>($($q_args),*),\n            _ => bail!(\"{:?} is not a number\", $dt)\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]\npub enum Reducer {\n    ArgMax(bool), // take last\n    ArgMin(bool),\n    Max,\n    Min,\n    Prod,\n    Sum,\n    MeanOfSquares,\n    All,\n    Any,\n}\n\nimpl Reducer {\n    pub fn reduce(&self, axes: &[usize], input: &Tensor) -> TractResult<Tensor> {\n        use Reducer::*;\n        let dt = input.datum_type();\n        let output_shape: Vec<usize> = input\n            .shape()\n            .iter()\n            .enumerate()\n            .map(|(ax, &d)| if axes.contains(&ax) { 1 } else { d })\n            .collect();\n        let (zp, scale) = input.datum_type().zp_scale();\n        unsafe {\n            let mut t = match self {\n                ArgMax(last) => {\n                    r!(Self::reduce_t(dt)(self, axes, &output_shape, input, argmax_t, *last))\n                }\n                ArgMin(last) => {\n                    r!(Self::reduce_t(dt)(self, axes, &output_shape, input, argmin_t, *last))\n                }\n                Min => r!(Self::reduce_t(dt)(self, axes, &output_shape, input, min_t, ())),\n                Max => r!(Self::reduce_t(dt)(self, axes, &output_shape, input, max_t, ())),\n                Prod => {\n                    r!(Self::reduce_t(dt)(self, axes, &output_shape, input, prod_t, ()); Self::reduce_t(self, axes, &output_shape, input, q_prod_t, (zp, scale)))\n                }\n                Sum => {\n                    if dt.is_float() {\n                        dispatch_floatlike!(Self::sum(dt)(self, axes, input))\n                    } else {\n                        r!(Self::reduce_t(dt)(\n                            self,\n                            axes,\n                            &output_shape,\n                            input,\n                            q_sum_t,\n                            (zp, scale)\n                        ))\n                    }\n                }\n                MeanOfSquares => self.mean_of_squares(axes, input)?,\n                All => Self::reduce_t(self, axes, &output_shape, input, all_bool, ()),\n                Any => Self::reduce_t(self, axes, &output_shape, input, any_bool, ()),\n            };\n            if input.datum_type().is_quantized()\n                && input.datum_type().unquantized() == t.datum_type().unquantized()\n            {\n                t.set_datum_type(input.datum_type());\n            }\n            Ok(t)\n        }\n    }\n\n    unsafe fn reduce_t<T, TO, F, A>(\n        &self,\n        axes: &[usize],\n        output_shape: &[usize],\n        input_tensor: &Tensor,\n        f: F,\n        args: A,\n    ) -> Tensor\n    where\n        F: for<'a> Fn(ArrayViewD<'a, T>, A) -> TO,\n        T: Copy + Datum,\n        TO: Copy + Datum,\n        A: Copy,\n    {\n        use ndarray::*;\n        let input = unsafe { input_tensor.to_array_view_unchecked::<T>() };\n        let result = Array::from_shape_fn(output_shape, |coords| {\n            let slice_spec: Vec<SliceInfoElem> = coords\n                .slice()\n                .iter()\n                .enumerate()\n                .map(|(ax, &d)| if axes.contains(&ax) { (..).into() } else { d.into() })\n                .collect();\n            let slice_info = SliceInfo::<_, IxDyn, IxDyn>::try_from(slice_spec).unwrap();\n            let slice = input.slice(&slice_info);\n            f(slice, args)\n        });\n        result.into_tensor()\n    }\n\n    // sum is a special citizen: enough activity that it gets \"special\"\n    // treatment. we could use the same \"algo\" for min, max and prod, to the\n    // price of more code in the library. argmax and argmin are more\n    // tricky (not associative)\n    unsafe fn sum<T>(&self, axes: &[usize], input: &Tensor) -> Tensor\n    where\n        T: Copy + Datum + num_traits::Zero + Sum,\n        f16: AsPrimitive<T>,\n        f32: AsPrimitive<T>,\n    {\n        if axes.len() == 0 {\n            return input.to_owned();\n        }\n\n        // use tract-optimized path only when single reuction axis and is at end\n        if axes.len() > 1 || axes[0] != input.rank() - 1 {\n            let mut operative_axes = vec![];\n            let mut operative_shape: Vec<usize> = vec![];\n            for (ix, dim) in input.shape().iter().enumerate() {\n                // axis is reduced, but is not the first of a series of reduced axes\n                if ix > 0 && axes.contains(&ix) && axes.contains(&(ix - 1)) {\n                    *operative_shape.last_mut().unwrap() *= *dim;\n                } else if axes.contains(&ix) {\n                    operative_axes.push(operative_shape.len());\n                    operative_shape.push(*dim);\n                } else {\n                    operative_shape.push(*dim);\n                }\n            }\n            let mut output = unsafe {\n                input\n                    .to_array_view_unchecked::<T>()\n                    .into_shape_with_order(operative_shape)\n                    .unwrap()\n                    .sum_axis(Axis(*operative_axes.iter().max().unwrap()))\n            };\n\n            for axis in operative_axes.iter().rev().skip(1) {\n                output = output.sum_axis(Axis(*axis));\n            }\n\n            let mut output = output.into_tensor();\n\n            for &axis in axes {\n                output.insert_axis(axis).unwrap();\n            }\n\n            output\n        } else {\n            let mut output: Option<ArrayD<T>> = None;\n            for axis in axes.iter().copied() {\n                let input_view = output\n                    .as_ref()\n                    .map(|o| o.view())\n                    .unwrap_or_else(|| unsafe { input.to_array_view_unchecked::<T>() });\n\n                // Create array that will contain intermidiate result\n                let reduced_dim = input_view.shape()[axis];\n                let input_stride = input_view.strides()[axis] as usize;\n                let output_shape = input_view\n                    .shape()\n                    .iter()\n                    .enumerate()\n                    .map(|(idx, dim)| if idx != axis { *dim } else { 1 })\n                    .collect_vec();\n\n                output = Some(ArrayD::from_shape_fn(output_shape.clone(), |coords| {\n                    let mut view = input_view.view();\n                    for ix in 0..output_shape.len() {\n                        if ix != axis {\n                            view.collapse_axis(Axis(ix), coords[ix]);\n                        }\n                    }\n\n                    if let Some(slice) = view.as_slice() {\n                        if T::datum_type() == f16::datum_type() {\n                            let slice: &[f16] = unsafe { std::mem::transmute(slice) };\n                            (tract_linalg::ops().sum_f16)()\n                                .run_with_params(slice, ())\n                                .unwrap()\n                                .as_()\n                        } else if T::datum_type() == f32::datum_type() {\n                            let slice: &[f32] = unsafe { std::mem::transmute(slice) };\n                            (tract_linalg::ops().sum_f32)()\n                                .run_with_params(slice, ())\n                                .unwrap()\n                                .as_()\n                        } else {\n                            slice.iter().cloned().sum::<T>()\n                        }\n                    } else {\n                        let first: *const T = &input_view[coords];\n                        let mut sum = T::zero();\n                        for i in 0..reduced_dim {\n                            sum = sum + unsafe { *(first.add(i * input_stride)) };\n                        }\n                        sum\n                    }\n                }));\n            }\n            output.unwrap().into_tensor()\n        }\n    }\n\n    fn mean_of_squares(&self, axis: &[usize], input: &Tensor) -> TractResult<Tensor> {\n        let dt = input.datum_type();\n        let mut input = input.cast_to::<f32>()?.into_owned();\n        input.try_as_plain_mut()?.as_slice_mut::<f32>()?.iter_mut().for_each(|x| *x = *x * *x);\n        let mut output = unsafe { self.sum::<f32>(axis, &input) };\n        let norm = output.len() as f32 / input.len() as f32;\n        output.try_as_plain_mut()?.as_slice_mut::<f32>()?.iter_mut().for_each(|x| *x *= norm);\n        Ok(output.cast_to_dt(dt)?.into_owned())\n    }\n}\n\nfn argmax_t<T>(v: ArrayViewD<T>, last: bool) -> i64\nwhere\n    T: Copy + Datum + num_traits::Bounded + ::std::cmp::PartialOrd,\n{\n    v.iter()\n        .copied()\n        .enumerate()\n        .fold(\n            (0usize, T::min_value()),\n            |acc, v| {\n                if v.1 > acc.1 || (last && acc.1 == v.1) { v } else { acc }\n            },\n        )\n        .0 as i64\n}\n\nfn argmin_t<T>(v: ArrayViewD<T>, last: bool) -> i64\nwhere\n    T: Copy + Datum + num_traits::Bounded + ::std::cmp::PartialOrd,\n{\n    v.iter()\n        .copied()\n        .enumerate()\n        .fold(\n            (0usize, T::max_value()),\n            |acc, v| {\n                if v.1 < acc.1 || (last && acc.1 == v.1) { v } else { acc }\n            },\n        )\n        .0 as i64\n}\n\nfn max_t<T>(v: ArrayViewD<T>, _: ()) -> T\nwhere\n    T: Copy + Datum + num_traits::Bounded + ::std::cmp::PartialOrd,\n{\n    if T::datum_type() == f32::datum_type()\n        && let Some(slice) = v.as_slice()\n    {\n        let slice = unsafe { transmute::<&[T], &[f32]>(slice) };\n        (tract_linalg::ops().max_f32)().run(slice).unwrap();\n    }\n    v.fold(T::min_value(), |acc, &v| if acc > v { acc } else { v })\n}\n\nfn min_t<T>(v: ArrayViewD<T>, _: ()) -> T\nwhere\n    T: Copy + Datum + num_traits::Bounded + ::std::cmp::PartialOrd,\n{\n    v.fold(T::max_value(), |acc, &v| if acc < v { acc } else { v })\n}\n\nfn prod_t<T>(v: ArrayViewD<T>, _: ()) -> T\nwhere\n    T: Copy + Datum + num_traits::One,\n{\n    v.fold(T::one(), |acc, &v| acc * v)\n}\n\nfn q_prod_t<T>(v: ArrayViewD<T>, zp_scale: (i32, f32)) -> T\nwhere\n    T: Copy + num_traits::AsPrimitive<f32> + Bounded + Datum,\n    f32: num_traits::AsPrimitive<T>,\n{\n    let (zp, scale) = zp_scale;\n    (v.fold(1f32, |acc, &v| acc * (v.as_() - zp as f32)) * scale.powi(v.len() as i32 - 1)\n        + zp as f32)\n        .clamp_cast()\n}\n\nfn q_sum_t<T>(v: ArrayViewD<T>, zp_scale: (i32, f32)) -> T\nwhere\n    T: Copy + Bounded + num_traits::AsPrimitive<i32> + Datum,\n    i32: num_traits::AsPrimitive<T>,\n{\n    let (zp, _) = zp_scale;\n    (v.fold(0i32, |acc, &v| acc + v.as_()) - zp * (v.len() as i32 - 1)).clamp_cast()\n}\n\nfn all_bool(v: ArrayViewD<bool>, _: ()) -> bool {\n    v.iter().all(|v| *v)\n}\n\nfn any_bool(v: ArrayViewD<bool>, _: ()) -> bool {\n    v.iter().any(|v| *v)\n}\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\npub struct Reduce {\n    pub axes: TVec<usize>,\n    pub reducer: Reducer,\n}\n\nimpl Op for Reduce {\n    fn name(&self) -> StaticName {\n        format!(\"Reduce<{:?}>\", self.reducer).into()\n    }\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axes: {:?}\", self.axes)])\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Reduce {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(tvec!(self.reducer.reduce(&self.axes, &inputs[0])?.into()))\n    }\n}\n\nimpl TypedOp for Reduce {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(self.axes.iter().tuple_windows().all(|(a, b)| a < b));\n        if inputs[0].datum_type == TDim::datum_type() {\n            bail!(\"Reduce input must be cast from TDim to i64 beforehand\")\n        }\n        let mut shape: TVec<_> = inputs[0].shape.to_tvec();\n        for &ax in &self.axes {\n            shape[ax] = 1.to_dim();\n        }\n        let dt = if let Reducer::ArgMax(_) | Reducer::ArgMin(_) = self.reducer {\n            DatumType::I64\n        } else {\n            inputs[0].datum_type\n        };\n        Ok(tvec!(dt.fact(shape)))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if let Some(patch) = self.declutter_mean_of_square(model, node)? {\n            return Ok(Some(patch));\n        }\n        if let Some(patch) = self.declutter_scalar_mul_then_sum(model, node)? {\n            return Ok(Some(patch));\n        }\n        if let Some(patch) = self.declutter_reduce_reduce(model, node)? {\n            return Ok(Some(patch));\n        }\n        if let Some(patch) = super::rms_norm::detect_rms_norm(self, model, node)? {\n            return Ok(Some(patch));\n        }\n        Ok(None)\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let dt = inputs[0].datum_type;\n        let count: TDim = inputs[0].shape.iter().product();\n        match self.reducer {\n            Reducer::Sum\n            | Reducer::Prod\n            | Reducer::Min\n            | Reducer::Max\n            | Reducer::All\n            | Reducer::Any => Ok(tvec!((Cost::FMA(dt), count))),\n            Reducer::MeanOfSquares => Ok(tvec!((Cost::FMA(dt), count * 2))),\n            Reducer::ArgMax(_) | Reducer::ArgMin(_) => Ok(tvec!((Cost::FMA(dt), count))),\n        }\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let mut letters = 'a'..;\n        let axes = (0..inputs[0].rank())\n            .flat_map(|ix| {\n                if self.axes.contains(&ix) {\n                    tvec!(\n                        Axis::new(letters.next().unwrap(), inputs.len(), outputs.len())\n                            .input(0, ix),\n                        Axis::new(letters.next().unwrap(), inputs.len(), outputs.len())\n                            .output(0, ix),\n                    )\n                } else {\n                    tvec!(\n                        Axis::new(letters.next().unwrap(), inputs.len(), outputs.len())\n                            .input(0, ix)\n                            .output(0, ix)\n                    )\n                }\n                .into_iter()\n            })\n            .collect_vec();\n        AxesMapping::new(1, 1, axes)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        let mut axes = tvec!();\n        for reduced in &self.axes {\n            rule_if_some!(axis = change.transform_axis(*reduced));\n            axes.push(axis);\n        }\n        axes.sort();\n        let op = Some(Box::new(Self { axes, ..self.clone() }) as _);\n        Ok(Some(AxisChangeConsequence::new(model, node, op, change)))\n    }\n\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        _model: &TypedModel,\n        node: &TypedNode,\n        _prefix: &str,\n        inputs: &[OutletId],\n        output_axis: usize,\n        _start: &TDim,\n        _end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        rule_if!(!self.axes.contains(&output_axis));\n        patch.wire_node(&node.name, &node.op, inputs).map(Some)\n    }\n\n    as_op!();\n}\n\nimpl Reduce {\n    fn declutter_reduce_reduce(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        use Reducer::*;\n        rule_if_some!(prec = model.linear_prec(node.id)?);\n        rule_if_some!(prec_reduce = prec.op_as::<Self>());\n        rule_if!(prec_reduce.reducer == self.reducer);\n        rule_if!([Sum, Prod, Min, Max].contains(&self.reducer));\n        let mut patch = TypedModelPatch::default();\n        let wire = patch.tap_model(model, prec.inputs[0])?;\n        let wire = patch.wire_node(\n            &node.name,\n            Self {\n                reducer: self.reducer,\n                axes: prec_reduce\n                    .axes\n                    .iter()\n                    .chain(self.axes.iter())\n                    .copied()\n                    .sorted()\n                    .dedup()\n                    .collect(),\n            },\n            &[wire],\n        )?;\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        Ok(Some(patch))\n    }\n\n    fn declutter_scalar_mul_then_sum(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if self.reducer == Reducer::Sum {\n            rule_if_some!(prec = model.linear_prec(node.id)?);\n            rule_if_some!(prec_bin = prec.op_as::<TypedBinOp>());\n            rule_if!(prec_bin.0.is::<Mul>());\n            let mul_input_fact = model.node_input_facts(prec.id)?;\n            rule_if_some!(\n                scalar_slot = mul_input_fact\n                    .iter()\n                    .position(|f| f.konst.as_ref().is_some_and(|k| k.volume() == 1))\n            );\n            let mut patch = TypedModelPatch::default();\n            let scalar = patch.tap_model(model, prec.inputs[scalar_slot])?;\n            let wire = patch.tap_model(model, prec.inputs[1 - scalar_slot])?;\n            let wire = patch.wire_node(&node.name, self.clone(), &[wire])?[0];\n            let wire = patch.wire_node(&prec.name, prec_bin.clone(), &[wire, scalar])?[0];\n            patch.shunt_outside(model, node.id.into(), wire)?;\n            return Ok(Some(patch));\n        }\n        Ok(None)\n    }\n\n    fn declutter_mean_of_square(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if self.reducer == Reducer::Sum {\n            rule_if_some!(prec = model.linear_prec(node.id)?);\n            rule_if_some!(prec_ew = prec.op_as::<ElementWiseOp>());\n            rule_if!(prec_ew.0.is::<Square>());\n            rule_if!(node.outputs.len() == 1);\n            rule_if!(node.outputs[0].successors.len() == 1);\n            let our_inlet = node.outputs[0].successors[0];\n            let succ = model.node(our_inlet.node);\n            rule_if_some!(succ_bin = succ.op_as::<TypedBinOp>());\n            rule_if!(succ_bin.0.is::<Mul>());\n            let other = succ.inputs[1 - our_inlet.slot];\n            rule_if_some!(other_konst = model.outlet_fact(other)?.uniform.as_ref());\n            let norm: TDim = self.axes.iter().map(|&ax| &prec.outputs[0].fact.shape[ax]).product();\n            rule_if_some!(norm = norm.as_i64());\n            rule_if!(norm > 0);\n            let norm = tensor0((norm as f32).recip());\n            if other_konst.close_enough(&norm, Approximation::Close).is_ok() {\n                let mut patch = TypedModelPatch::default();\n                let wire = patch.tap_model(model, prec.inputs[0])?;\n                let wire = patch.wire_node(\n                    &node.name,\n                    Reduce::new(self.axes.clone(), Reducer::MeanOfSquares),\n                    &[wire],\n                )?[0];\n                patch.shunt_outside(model, succ.id.into(), wire)?;\n                return Ok(Some(patch));\n            }\n        }\n        Ok(None)\n    }\n}\n\npub fn expand_mean_of_squares(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    op: &Reduce,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(op.reducer == Reducer::MeanOfSquares);\n    let mut patch = TypedModelPatch::default();\n    let mut wire = tvec!(patch.tap_model(model, node.inputs[0])?);\n    let input_fact = model.outlet_fact(node.inputs[0])?;\n    let dt = input_fact.datum_type;\n    if dt != f32::datum_type() {\n        wire = patch.wire_node(format!(\"{name}.to_f32\"), cast(f32::datum_type()), &wire)?;\n    }\n    wire = patch.wire_node(format!(\"{name}.sqr\"), square(), &wire)?;\n    wire = patch.wire_node(\n        format!(\"{name}.sum\"),\n        Reduce::new(op.axes.clone(), Reducer::Sum),\n        &wire,\n    )?;\n    let card = input_fact\n        .shape\n        .iter()\n        .enumerate()\n        .filter(|(ix, _dim)| op.axes.contains(ix))\n        .map(|(_ix, dim)| dim)\n        .product::<TDim>();\n    let card = patch.add_const(format!(\"{name}.card\"), tensor0(card))?;\n    let card = patch.wire_node(format!(\"{name}.card_to_f32\"), cast(f32::datum_type()), &[card])?;\n\n    wire =\n        wire_with_rank_broadcast(format!(\"{name}.norm\"), &mut patch, div(), &[wire[0], card[0]])?;\n    if dt != f32::datum_type() {\n        wire = patch.wire_node(format!(\"{name}.from_f32\"), cast(dt), &wire)?;\n    }\n    patch.shunt_outside(model, node.id.into(), wire[0])?;\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "core/src/ops/nn/rms_norm.rs",
    "content": "use crate::internal::*;\nuse crate::ops::binary::{BinMiniOp, TypedBinOp};\nuse crate::ops::element_wise::ElementWiseOp;\nuse crate::ops::math::{Add, Mul, Rsqrt};\nuse crate::ops::nn::{Reduce, Reducer};\nuse tract_itertools::Itertools;\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct RmsNorm {\n    pub axis: usize,\n    pub eps: Arc<Tensor>,\n}\n\nimpl Op for RmsNorm {\n    fn name(&self) -> StaticName {\n        \"RmsNorm\".to_string().into()\n    }\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis: {:?}, eps: {:?}\", self.axis, self.eps)])\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for RmsNorm {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n\n        let input_f32 = input.cast_to::<f32>()?.into_owned();\n        let a1 = Reducer::MeanOfSquares.reduce(&[self.axis], &input_f32)?;\n        let mut a2 = Add.eval(a1.into_tvalue(), self.eps.clone().into_tvalue(), DatumType::F32)?;\n        Rsqrt {}.eval_in_place(&mut a2, None)?;\n        let a3 = Mul.eval(a2.into_tvalue(), input_f32.into_tvalue(), DatumType::F32)?;\n        Ok(tvec![a3.cast_to_dt(input.datum_type())?.into_owned().into()])\n    }\n}\n\nimpl TypedOp for RmsNorm {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(self.eps.rank() == 0, \"RmsNorm: eps must be a rank-0 tensor\");\n        ensure!(\n            self.axis < inputs[0].rank(),\n            \"RmsNorm: axis {} is out of bounds for input rank {}\",\n            self.axis,\n            inputs[0].rank()\n        );\n        let dt = inputs[0].datum_type;\n        let fact = dt.fact(inputs[0].shape.clone());\n        Ok(tvec!(fact))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        _outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let rank = inputs[0].rank();\n        let mut letters = 'a'..;\n        let axes = (0..rank)\n            .map(|ix| {\n                Axis::new(letters.next().unwrap(), inputs.len(), 1).input(0, ix).output(0, ix)\n            })\n            .collect_vec();\n        AxesMapping::new(1, 1, axes)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        if let Some(axis) = change.transform_axis(self.axis) {\n            let op = Some(Box::new(RmsNorm { axis, eps: self.eps.clone() }) as _);\n            Ok(Some(AxisChangeConsequence::new(model, node, op, change)))\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn slice(\n        &self,\n        patch: &mut TypedModelPatch,\n        _model: &TypedModel,\n        node: &TypedNode,\n        _prefix: &str,\n        inputs: &[OutletId],\n        output_axis: usize,\n        _start: &TDim,\n        _end: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        if output_axis == self.axis {\n            return Ok(None);\n        }\n        patch.wire_node(&node.name, self.clone(), inputs).map(Some)\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let dt = inputs[0].datum_type;\n        let count: TDim = inputs[0].shape.iter().product();\n        // per element: square + accumulate + mul by rsqrt ≈ 3 FMA\n        // per reduction group: 1 div (rsqrt)\n        let groups: TDim = inputs[0]\n            .shape\n            .iter()\n            .enumerate()\n            .filter(|(i, _)| *i != self.axis)\n            .map(|(_, d)| d)\n            .product();\n        Ok(tvec!((Cost::FMA(dt), count * 3), (Cost::Div(dt), groups)))\n    }\n\n    as_op!();\n}\n\n/// Search pattern => A = A * RSQRT(MEAN_OF_SQUARES(A) + EPS)\npub fn detect_rms_norm(\n    op: &Reduce,\n    model: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(op.reducer == Reducer::MeanOfSquares);\n    rule_if!(op.axes.len() == 1);\n    let axis = op.axes[0];\n\n    let in_fact = model.node_input_facts(node.id)?[0];\n    let dt = in_fact.datum_type;\n\n    // Only F16 and F32 is supported.\n    rule_if!(matches!(dt, DatumType::F32 | DatumType::F16));\n\n    // Identify Add operator\n    rule_if_some!(add_succ = model.single_succ(node.id)?);\n    rule_if_some!(add_succ_op = add_succ.op_as::<TypedBinOp>());\n    rule_if!(add_succ_op.0.is::<Add>());\n\n    // Retrieve epsilon\n    let add_consts = model.collect_const_inputs(add_succ);\n    rule_if!(add_consts.len() == 1);\n    let eps = add_consts[0].val().clone();\n    rule_if!(eps.len() == 1);\n    rule_if!(eps.datum_type() == dt);\n    let eps = eps.into_tensor().into_shape(&[])?.into_arc_tensor();\n\n    // Identify Rsqrt\n    rule_if_some!(rsqrt_succ = model.single_succ(add_succ.id)?);\n    rule_if_some!(rsqrt_succ_op = rsqrt_succ.op_as::<ElementWiseOp>());\n    rule_if!(rsqrt_succ_op.0.is::<Rsqrt>());\n\n    // Identify Mul: RSQRT(...) * A\n    rule_if_some!(mul_succ = model.find_succ_bin_with_outlet::<Mul>(rsqrt_succ, &node.inputs[0]));\n\n    let mut patch = TypedModelPatch::default();\n    let rsm_input = patch.taps(model, &node.inputs)?;\n    let out =\n        patch.wire_node(format!(\"{}.rms_norm\", node.name), RmsNorm { axis, eps }, &rsm_input)?;\n\n    patch.shunt_outside(model, mul_succ.id.into(), out[0])?;\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "core/src/ops/nn/silu.rs",
    "content": "use crate::internal::*;\nuse crate::ops::element_wise::ElementWiseOp;\nuse crate::ops::math::Mul;\nuse crate::ops::nn::Sigmoid;\n\nuse tract_data::half::f16;\n\nelement_wise!(silu, Silu,\n    [f16] => |_, xs| {\n        xs.iter_mut().for_each(|x| {\n            let xf = x.to_f32();\n            *x = f16::from_f32(xf / (1.0 + (-xf).exp()));\n        });\n        Ok(())\n    },\n    [f32] => |_, xs| {\n        let mut sigmoid = xs.to_vec();\n        (tract_linalg::ops().sigmoid_f32)().run(&mut sigmoid)?;\n        xs.iter_mut().zip(sigmoid).for_each(|(x, s)| *x *= s);\n        Ok(())\n    };\n    cost: |dt| {tvec!((Cost::FMA(dt), 12), (Cost::Div(dt), 1))};\n    declutter: detect_silu\n);\n\n/// Search pattern => A = A * SIGMOID(A)\npub fn detect_silu(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(node.op_as::<ElementWiseOp>().is_some_and(|op| op.0.is::<Sigmoid>()));\n\n    let in_fact = model.node_input_facts(node.id)?[0];\n    let dt = in_fact.datum_type;\n\n    // Only F16 and F32 is supported.\n    rule_if!(matches!(dt, DatumType::F32 | DatumType::F16));\n\n    // Identify Mul successor: Sigmoid(A) * A\n    rule_if_some!(mul_succ = model.find_succ_bin_with_outlet::<Mul>(node, &node.inputs[0]));\n\n    let mut patch = TypedModelPatch::default();\n    let silu_input = patch.taps(model, &node.inputs)?;\n    let out = patch.wire_node(format!(\"{}.silu\", node.name), silu(), &silu_input)?;\n    patch.shunt_outside(model, mul_succ.id.into(), out[0])?;\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "core/src/ops/nn/softmax/fixedpoint.rs",
    "content": "pub use num_traits::{AsPrimitive, PrimInt};\nuse std::fmt::{Binary, Debug, LowerHex};\n\nuse super::math::*;\n\nmacro_rules! impl_fixed_point_func_unary {\n    ($func_name: ident) => {\n        #[allow(dead_code)]\n        pub fn $func_name(&self) -> Self {\n            Self::from_raw($func_name(self.as_raw()))\n        }\n    };\n}\n\nmacro_rules! impl_fixed_point_func_binary {\n    ($func_name: ident) => {\n        pub fn $func_name(&self, b: Self) -> Self {\n            Self::from_raw($func_name(self.as_raw(), b.as_raw()))\n        }\n    };\n}\n\npub type Q0_31 = FixedPoint<i32, 0>;\npub type Q1_30 = FixedPoint<i32, 1>;\npub type Q2_29 = FixedPoint<i32, 2>;\npub type Q5_26 = FixedPoint<i32, 5>;\n\n#[derive(PartialEq, Eq, PartialOrd, Copy, Clone)]\npub struct FixedPoint<T: PrimInt, const INTEGER_BITS: usize>(T);\n\nimpl<T, const INTEGER_BITS: usize> FixedPoint<T, INTEGER_BITS>\nwhere\n    T: PrimInt,\n{\n    pub fn from_raw(x: T) -> Self {\n        Self(x)\n    }\n\n    pub fn one() -> Self {\n        if INTEGER_BITS == 0 {\n            Self(T::max_value())\n        } else {\n            Self(T::one() << Self::fractional_bits())\n        }\n    }\n\n    pub fn fractional_bits() -> usize {\n        if Self::is_signed() {\n            std::mem::size_of::<T>() * 8 - 1 - INTEGER_BITS\n        } else {\n            std::mem::size_of::<T>() * 8 - INTEGER_BITS\n        }\n    }\n\n    #[allow(dead_code)]\n    pub fn zero() -> Self {\n        Self(T::zero())\n    }\n\n    pub fn as_raw(&self) -> T {\n        self.0\n    }\n\n    pub fn is_signed() -> bool {\n        is_signed::<T>()\n    }\n}\n\nimpl<T: 'static, const INTEGER_BITS: usize> FixedPoint<T, INTEGER_BITS>\nwhere\n    T: PrimInt + Debug,\n    usize: AsPrimitive<T>,\n{\n    pub fn constant_pot(exponent: isize) -> Self {\n        let offset = (Self::fractional_bits() as isize + exponent) as usize;\n        assert!(offset < 31);\n        Self(1_usize.as_() << offset)\n    }\n}\n\nimpl FixedPoint<i32, 0> {\n    impl_fixed_point_func_unary!(exp_on_interval_between_negative_one_quarter_and_0_excl);\n    impl_fixed_point_func_unary!(one_over_one_plus_x_for_x_in_0_1);\n}\n\nimpl FixedPoint<i32, 5> {\n    #[allow(dead_code)]\n    pub fn exp_on_negative_values(&self) -> FixedPoint<i32, 0> {\n        FixedPoint::<i32, 0>::from_raw(exp_on_negative_values(self.as_raw()))\n    }\n}\n\nimpl<const INTEGER_BITS: usize> FixedPoint<i32, INTEGER_BITS> {\n    impl_fixed_point_func_unary!(mask_if_non_zero);\n    impl_fixed_point_func_unary!(mask_if_zero);\n    impl_fixed_point_func_binary!(rounding_half_sum);\n\n    pub fn saturating_rounding_multiply_by_pot(&self, exponent: i32) -> Self {\n        Self::from_raw(saturating_rounding_multiply_by_pot(self.as_raw(), exponent))\n    }\n\n    #[allow(dead_code)]\n    pub fn rounding_divide_by_pot(&self, exponent: i32) -> Self {\n        Self::from_raw(rounding_divide_by_pot(self.as_raw(), exponent))\n    }\n\n    pub fn select_using_mask(mask: i32, a: Self, b: Self) -> Self {\n        Self::from_raw(select_using_mask(mask, a.as_raw(), b.as_raw()))\n    }\n\n    pub fn rescale<const DST_INTEGER_BITS: usize>(&self) -> FixedPoint<i32, DST_INTEGER_BITS> {\n        FixedPoint::<i32, DST_INTEGER_BITS>::from_raw(rescale(\n            self.as_raw(),\n            INTEGER_BITS,\n            DST_INTEGER_BITS,\n        ))\n    }\n\n    #[allow(dead_code)]\n    pub fn get_reciprocal(&self) -> (FixedPoint<i32, 0>, usize) {\n        let (raw_res, num_bits_over_units) = get_reciprocal(self.as_raw(), INTEGER_BITS);\n        (FixedPoint::<i32, 0>::from_raw(raw_res), num_bits_over_units)\n    }\n}\n\nimpl<T, const INTEGER_BITS: usize> Debug for FixedPoint<T, INTEGER_BITS>\nwhere\n    T: AsPrimitive<f32> + PrimInt + LowerHex + Debug + Binary,\n    f32: AsPrimitive<T>,\n{\n    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(fmt, \"{:032b}({:?})({})\", self.0, self.0, self.as_f32())\n    }\n}\n\nimpl<T, const INTEGER_BITS: usize> FixedPoint<T, INTEGER_BITS>\nwhere\n    T: AsPrimitive<f32> + PrimInt,\n{\n    pub fn as_f32(&self) -> f32 {\n        self.0.as_() / 2_f32.powi(Self::fractional_bits() as i32)\n    }\n}\n\nimpl<T, const INTEGER_BITS: usize> FixedPoint<T, INTEGER_BITS>\nwhere\n    T: AsPrimitive<f32> + PrimInt,\n    f32: AsPrimitive<T>,\n{\n    #[allow(dead_code)]\n    pub fn from_f32(x: f32) -> Self {\n        Self::from_raw(\n            f32::min(\n                f32::max(\n                    f32::round(x * 2f32.powi(Self::fractional_bits().as_())),\n                    T::min_value().as_(),\n                ),\n                T::max_value().as_(),\n            )\n            .as_(),\n        )\n    }\n}\n\nimpl<T: PrimInt, const INTEGER_BITS: usize> std::ops::Add for FixedPoint<T, INTEGER_BITS> {\n    type Output = FixedPoint<T, INTEGER_BITS>;\n    fn add(self, rhs: Self) -> Self::Output {\n        Self::from_raw(self.0 + rhs.0)\n    }\n}\n\nimpl<T: PrimInt, const INTEGER_BITS: usize> std::ops::Sub for FixedPoint<T, INTEGER_BITS> {\n    type Output = FixedPoint<T, INTEGER_BITS>;\n    fn sub(self, rhs: Self) -> Self::Output {\n        Self::from_raw(self.0 - rhs.0)\n    }\n}\n\nimpl<T: PrimInt, const INTEGER_BITS: usize> std::ops::Shl<usize> for FixedPoint<T, INTEGER_BITS> {\n    type Output = FixedPoint<T, INTEGER_BITS>;\n    fn shl(self, rhs: usize) -> Self::Output {\n        Self::from_raw(self.0 << rhs)\n    }\n}\n\nimpl<T: PrimInt, const INTEGER_BITS: usize> std::ops::Shr<usize> for FixedPoint<T, INTEGER_BITS> {\n    type Output = FixedPoint<T, INTEGER_BITS>;\n    fn shr(self, rhs: usize) -> Self::Output {\n        Self::from_raw(self.0 >> rhs)\n    }\n}\n\nimpl<T: PrimInt, const INTEGER_BITS: usize> std::ops::BitAnd for FixedPoint<T, INTEGER_BITS> {\n    type Output = FixedPoint<T, INTEGER_BITS>;\n    fn bitand(self, rhs: Self) -> Self::Output {\n        Self::from_raw(self.0 & rhs.0)\n    }\n}\n\nmacro_rules! impl_mul {\n    ($T: ty, $LHS_INTEGER_BITS: literal, $RHS_INTEGER_BITS: literal, $OUT_INTEGER_BITS: literal) => {\n        impl std::ops::Mul<FixedPoint<$T, $RHS_INTEGER_BITS>>\n            for FixedPoint<$T, $LHS_INTEGER_BITS>\n        {\n            type Output = FixedPoint<$T, $OUT_INTEGER_BITS>;\n            fn mul(self, rhs: FixedPoint<$T, $RHS_INTEGER_BITS>) -> Self::Output {\n                Self::Output::from_raw(saturating_rounding_doubling_high_mul(self.0, rhs.0))\n            }\n        }\n    };\n}\n\nimpl_mul!(i32, 0, 0, 0);\nimpl_mul!(i32, 0, 2, 2);\nimpl_mul!(i32, 2, 0, 2);\nimpl_mul!(i32, 2, 2, 4);\nimpl_mul!(i32, 5, 5, 10);\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use approx::assert_abs_diff_eq;\n    pub type Q10_21 = FixedPoint<i32, 10>;\n    pub type Q12_19 = FixedPoint<i32, 12>;\n    pub type Q26_5 = FixedPoint<i32, 26>;\n    type Q0_7 = FixedPoint<i8, 0>;\n\n    #[test]\n    fn test_to_f32() {\n        let x = Q26_5::from_raw(32);\n        assert_eq!(x.as_f32(), 1.0);\n    }\n\n    #[test]\n    fn test_to_f32_1() {\n        let x = Q0_7::from_raw(32);\n        assert_eq!(x.as_f32(), 0.25);\n    }\n\n    #[test]\n    fn test_one() {\n        let x = Q26_5::one();\n        assert_eq!(x, Q26_5::from_raw(32));\n    }\n\n    #[test]\n    fn test_one_limit() {\n        let x = Q0_31::one();\n        assert_eq!(x, Q0_31::from_raw(i32::MAX));\n    }\n\n    #[test]\n    fn test_mul_1() {\n        let a = Q5_26::from_f32(8.0); // 00000001\n        let b = Q5_26::from_f32(3.0); // 01000000\n        let product = a * b;\n        let expected = Q10_21::from_f32(24.0);\n\n        assert_eq!(product, expected);\n    }\n\n    #[test]\n    fn test_add() {\n        let a = Q5_26::from_f32(16.0);\n        let b = Q5_26::from_f32(5.0);\n        let sum = a + b;\n        let expected = Q5_26::from_f32(21.0);\n        assert_eq!(sum, expected);\n    }\n\n    #[test]\n    fn test_one_over_one_plus_x_for_x_in_0_1() {\n        let a = Q0_31::from_f32(0.75);\n        let expected_res = Q0_31::from_f32(1.0 / 1.75);\n        let res = a.one_over_one_plus_x_for_x_in_0_1();\n        assert_eq!(res.as_f32(), expected_res.as_f32());\n    }\n\n    #[test]\n    fn test_one_over_one_plus_x_for_x_in_0_1_1() {\n        let a = Q0_31::from_f32(0.0);\n        let expected_res = Q0_31::from_f32(1.0 / 1.0);\n        let res = a.one_over_one_plus_x_for_x_in_0_1();\n        assert_eq!(res.as_f32(), expected_res.as_f32());\n    }\n\n    #[test]\n    fn test_get_reciprocal_1() {\n        let a = Q5_26::from_f32(4.5);\n        let expected_res = Q0_31::from_f32(1.0 / 4.5);\n        let (shifted_res, num_bits_over_unit) = a.get_reciprocal();\n        let res = shifted_res.rounding_divide_by_pot(num_bits_over_unit as i32);\n        assert_eq!(res.as_f32(), expected_res.as_f32());\n        assert_eq!(num_bits_over_unit, 2);\n    }\n\n    #[test]\n    fn test_get_reciprocal_2() {\n        let a = Q5_26::from_f32(4.5);\n        let expected_res = Q0_31::from_f32(1.0 / 4.5);\n        let (shifted_res, num_bits_over_unit) = a.get_reciprocal();\n        let res = shifted_res.rounding_divide_by_pot(num_bits_over_unit as i32);\n        assert_eq!(res.as_f32(), expected_res.as_f32());\n        assert_eq!(num_bits_over_unit, 2);\n    }\n\n    #[test]\n    fn test_get_reciprocal_3() {\n        let a = Q12_19::from_f32(2.0);\n        let expected_res = Q0_31::from_f32(1.0 / 2.0);\n        let (shifted_res, num_bits_over_unit) = a.get_reciprocal();\n        let res = shifted_res.rounding_divide_by_pot(num_bits_over_unit as i32);\n        assert_eq!(res.as_f32(), expected_res.as_f32());\n        assert_eq!(num_bits_over_unit, 1);\n    }\n\n    #[test]\n    fn test_rescale_1() {\n        let a = Q0_31::from_f32(0.75);\n        let expeted_res = Q12_19::from_f32(0.75);\n        let res = a.rescale::<12>();\n        assert_eq!(res, expeted_res);\n    }\n\n    #[test]\n    fn test_exp_on_interval_between_negative_one_quarter_and_0_excl() {\n        let a = Q0_31::from_f32(-0.125);\n        let expected_res = Q0_31::from_f32((-0.125_f32).exp());\n        let res = a.exp_on_interval_between_negative_one_quarter_and_0_excl();\n        assert_eq!(res.as_f32(), expected_res.as_f32());\n    }\n\n    #[test]\n    fn test_exp_on_negative_values_1() {\n        let a = Q5_26::from_f32(-0.125);\n        let expected_res = Q0_31::from_f32((-0.125_f32).exp());\n        let res = a.exp_on_negative_values();\n        assert_abs_diff_eq!(res.as_f32(), expected_res.as_f32(), epsilon = 0.00001);\n    }\n\n    #[test]\n    fn test_exp_on_negative_values_2() {\n        let a = Q5_26::from_f32(0.0);\n        let expected_res = Q0_31::from_f32((0_f32).exp());\n        let res = a.exp_on_negative_values();\n        assert_abs_diff_eq!(res.as_f32(), expected_res.as_f32(), epsilon = 0.00001);\n    }\n\n    #[test]\n    fn test_exp_on_negative_values_3() {\n        let a = Q5_26::from_f32(-0.25);\n        let expected_res = Q0_31::from_f32((-0.25_f32).exp());\n        let res = a.exp_on_negative_values();\n        assert_abs_diff_eq!(res.as_f32(), expected_res.as_f32(), epsilon = 0.00001);\n    }\n\n    #[test]\n    fn test_exp_on_negative_values_4() {\n        let a = Q5_26::from_f32(-1.1875_f32);\n        let expected_res = Q0_31::from_f32((-1.1875_f32).exp());\n        let res = a.exp_on_negative_values();\n        assert_abs_diff_eq!(res.as_f32(), expected_res.as_f32(), epsilon = 0.00001);\n    }\n}\n"
  },
  {
    "path": "core/src/ops/nn/softmax/math.rs",
    "content": "use super::fixedpoint::{Q0_31, Q1_30, Q2_29, Q5_26};\nuse num_traits::PrimInt;\n\n// This function convert a scale (actually the inverse of an integer 1/D)\n// into an integer multiplier and a shift (the multiplier being 1/D in Q0_31).\npub fn convert_scale_to_mult_shift(scale: f32) -> Option<(i32, isize)> {\n    if scale <= 0.0 {\n        return None;\n    }\n\n    let scale_bits = scale.to_bits();\n\n    // We extract the exponent value\n    let current_exponent = scale_bits >> 23;\n\n    // We extract the fractional part of the float\n    let fractional_part = scale_bits & 0x007fffff;\n\n    if fractional_part == 0 {\n        let shift = 127 - current_exponent as isize;\n        Some((0, shift))\n    } else {\n        let bumped_multi = f32::from_bits(fractional_part | 0x3f000000);\n        let int_multi = (bumped_multi * (1u32 << 31) as f32).round() as i32;\n        let shift = 127 - current_exponent as isize - 1;\n        Some((int_multi, shift))\n    }\n}\n\n// Get inverse of X with a result as Q0.31\n// Here we expect X to have at least fixed point = 1 and to be >= 1 -> required to have an output in Q0_31.\n// https://github.com/tensorflow/tensorflow/blob/8c6f391a2282684a25cbfec7687bd5d35261a209/tensorflow/lite/kernels/internal/common.h#L765\npub(crate) fn get_reciprocal(x: i32, fixed_point: usize) -> (i32, usize) {\n    assert!(fixed_point > 0);\n    //assert!(x as f32 / 2_f32.powi((31 - fixed_point) as i32) > 1.0);\n    // Sounds like we compute the smallest amount of integer bits needed to represent\n    // the integer part of the number.\n    let headroom_plus_one = (x as u32).leading_zeros() as usize;\n    let num_bits_over_unit = fixed_point - headroom_plus_one;\n\n    let shifted_sum_minus_one = ((x as u32) << headroom_plus_one) - (1_u32 << 31);\n    let shifted_scale =\n        Q0_31::from_raw(shifted_sum_minus_one as i32).one_over_one_plus_x_for_x_in_0_1();\n    (shifted_scale.as_raw(), num_bits_over_unit)\n}\n\n// Returns 1 / (1 + x) for x in (0, 1).\n// We expect input to be in Q0_31 and output to be in Q0_31.\n// https://github.com/google/gemmlowp/blob/e844ffd17118c1e17d94e1ba4354c075a4577b88/fixedpoint/fixedpoint.h#L854\npub fn one_over_one_plus_x_for_x_in_0_1(a: i32) -> i32 {\n    let a_in_q0_31 = Q0_31::from_raw(a);\n    let constant_48_over_17 = Q2_29::from_raw(1515870810);\n    let constant_neg_32_over_17 = Q2_29::from_raw(-1010580540);\n\n    // 1.0 cannot be represented so we set it to 0.99999999\n    // which is all bits to 1.\n    let one_in_q0_31 = Q0_31::one();\n    let one_in_q2_29 = Q2_29::one();\n\n    // We are in Q0.31\n    // Let's call D = a + 1 do stick with the Newton–Raphson naming.\n    // D is in [1, 2] so we consider D'(half_denominator) = D / 2 is in [0.5, 1]\n    let half_denominator = a_in_q0_31.rounding_half_sum(one_in_q0_31);\n\n    // We are in Q2.29 (because Q0.31 * Q2.29 is a Q2.29)\n    let x_0: Q2_29 = constant_48_over_17 + half_denominator * constant_neg_32_over_17;\n\n    // The formulae used here is:\n    // Xn+1 = Xn + Xn(1 - D' * Xn)\n    let mut x_n = x_0;\n    for _ in 0..3 {\n        // We are in Q2.29 (because Q0.31 * Q2.29 is a Q2.29)\n        let half_denominator_times_x_n = half_denominator * x_n;\n\n        // We are in Q2.29 (because Q2.29 - Q2.29 is a Q2.29)\n        let one_minus_half_denominator_times_x_n = one_in_q2_29 - half_denominator_times_x_n;\n        // We are in Q4.27 !! (because Q2.29 - Q2.29 is a Q4.27)\n        let x_times_one_minus_half_denominator_times_x_n =\n            x_n * one_minus_half_denominator_times_x_n;\n\n        // We are in Q4.27 so we need to rescale to be in Q2.29\n        let rescaled_x_n = x_times_one_minus_half_denominator_times_x_n.rescale::<2>();\n        x_n = x_n + rescaled_x_n;\n    }\n\n    // We now have a value for 1/D' = 2/D = 2/(a+1)\n    // Instead of doing the division by two, we just pretend that\n    // this value is in Q1.30\n    let half_x_n = Q1_30::from_raw(x_n.as_raw());\n\n    // We rescale in Q0.31\n    let res_in_q0_31 = half_x_n.rescale::<0>();\n    res_in_q0_31.as_raw()\n}\n\n// Rescale changes the number of IntegerBits and updates the underlying raw integer value accordingly\n// https://github.com/google/gemmlowp/blob/13d57703abca3005d97b19df1f2db731607a7dc2/fixedpoint/fixedpoint.h#L678\npub(crate) fn rescale(x: i32, src_integer_bits: usize, dst_integer_bits: usize) -> i32 {\n    let exponent = src_integer_bits as i32 - dst_integer_bits as i32;\n    saturating_rounding_multiply_by_pot(x, exponent)\n}\n\n// Here we assume the input to be a Q5.26\npub fn exp_on_negative_values(a: i32) -> i32 {\n    let a_q5_36 = Q5_26::from_raw(a);\n    let k_one_quarter = Q5_26::constant_pot(-2); // 1/4\n    let mask = k_one_quarter - Q5_26::from_raw(1); // 1/4 - 1/2^26 in Q5.26\n    let a_mod_quarter_minus_one_quarter = (a_q5_36 & mask) - k_one_quarter;\n\n    // We need to rescale from Q5.26 to Q0.31 as we compute the exp in this range\n    let rescaled_a_mod_quarter_minus_one_quarter = a_mod_quarter_minus_one_quarter.rescale::<0>();\n\n    // We are in Q0.31\n    let mut result = rescaled_a_mod_quarter_minus_one_quarter\n        .exp_on_interval_between_negative_one_quarter_and_0_excl();\n    let remainder = (a_mod_quarter_minus_one_quarter - a_q5_36).as_raw();\n\n    macro_rules! exp_barrel_shifter {\n        ($exponent: expr, $quantized_value: expr) => {\n            if 5 > $exponent {\n                // equivalent to one_in_q5_26 << exponent when exponent > 0.\n                // but handle the case exponent < 0.\n                let k_shift_amount = 26 + $exponent;\n                let mask = mask_if_non_zero(remainder & (1 << k_shift_amount));\n                result = Q0_31::select_using_mask(\n                    mask,\n                    result * Q0_31::from_raw($quantized_value),\n                    result,\n                );\n            }\n        };\n    }\n\n    exp_barrel_shifter!(-2, 1672461947); // exp(-1/4)\n    exp_barrel_shifter!(-1, 1302514674); // exp(-1/2)\n    exp_barrel_shifter!(0, 790015084); // exp(-1)\n    exp_barrel_shifter!(1, 290630308); // exp(-2)\n    exp_barrel_shifter!(2, 39332535); // exp(-4)\n    exp_barrel_shifter!(3, 720401); // exp(-8)\n    exp_barrel_shifter!(4, 242); // exp(-16)\n\n    let mask = a_q5_36.mask_if_zero();\n    let res_in_q0_31 = Q0_31::select_using_mask(mask.as_raw(), Q0_31::one(), result);\n    res_in_q0_31.as_raw()\n}\n\n// Here we assume the input to be a Q0.31\n// Valid for x in [-1/4, 0).\npub(crate) fn exp_on_interval_between_negative_one_quarter_and_0_excl(a: i32) -> i32 {\n    let a_in_q0_31 = Q0_31::from_raw(a);\n    // We are in Q0.31 and this represents exp(-1/8)\n    let exp_minus_one_over_eight = Q0_31::from_raw(1895147668);\n    // We are in Q0.31 and this represents 1/3\n    let constant_1_over_3 = Q0_31::from_raw(715827883);\n\n    // We estimate the value by doing a taylor expansion around -1/8\n    // so we do the change of variable x = a + 1/8\n    let x = a_in_q0_31 + Q0_31::constant_pot(-3);\n    let x2 = x * x;\n    let x3 = x2 * x;\n    let x4 = x2 * x2;\n    let x4_over_4 = x4.saturating_rounding_multiply_by_pot(-2);\n    let x4_over_24_plus_x3_over_6_plus_x2_over_2 =\n        Q0_31::from_raw(saturating_rounding_multiply_by_pot(\n            (((x4_over_4 + x3) * constant_1_over_3) + x2).as_raw(),\n            -1,\n        ));\n    let res_in_q0_31 = exp_minus_one_over_eight\n        + exp_minus_one_over_eight * (x + x4_over_24_plus_x3_over_6_plus_x2_over_2);\n    res_in_q0_31.as_raw()\n}\n\n// Correctly-rounded-to-nearest division by a power-of-two.\n// Also known as a rounding arithmetic right shift.\n// https://github.com/google/gemmlowp/blob/13d57703abca3005d97b19df1f2db731607a7dc2/fixedpoint/fixedpoint.h#L368\n//\n// Taken from here:\n// https://github.com/ARM-software/CMSIS_5/blob/61a22cd0eac4f22aa19314125f663198736afa3f/CMSIS/NN/Include/arm_nnsupportfunctions.h#L924\npub fn rounding_divide_by_pot(x: i32, exponent: i32) -> i32 {\n    // Exponent is a bit shift so it should be in [0,31[\n    assert!(exponent >= 0);\n    assert!(exponent <= 31);\n\n    let mask = ((1_i64 << exponent) - 1) as i32;\n    let remainder = x & mask;\n\n    // Basic division\n    let mut result = x >> exponent as usize;\n\n    // Adjust 'result' for rounding (mid point away from zero)\n    let mut threshold = mask >> 1;\n    if result < 0 {\n        threshold += 1;\n    }\n    if remainder > threshold {\n        result += 1;\n    }\n\n    result\n}\n\n// https://github.com/google/gemmlowp/blob/13d57703abca3005d97b19df1f2db731607a7dc2/fixedpoint/fixedpoint.h#L385\n#[allow(clippy::comparison_chain)] // nah\npub fn saturating_rounding_multiply_by_pot(x: i32, exponent: i32) -> i32 {\n    if exponent == 0 {\n        x\n    } else if exponent < 0 {\n        rounding_divide_by_pot(x, -exponent)\n    } else {\n        let min = i32::MIN;\n        let max = i32::MAX;\n        let threshold = (1 << (32 - 1 - exponent)) - 1;\n        let positive_mask = mask_if_non_zero((x > threshold) as i32);\n        let negative_mask = mask_if_non_zero((x < -threshold) as i32);\n        let mut result = x << exponent as usize;\n        result = select_using_mask(positive_mask, max, result);\n        result = select_using_mask(negative_mask, min, result);\n        result\n    }\n}\n\n// https://github.com/google/gemmlowp/blob/e844ffd17118c1e17d94e1ba4354c075a4577b88/fixedpoint/fixedpoint.h#L237\npub fn rounding_half_sum(a: i32, b: i32) -> i32 {\n    let sum = (a as i64) + (b as i64);\n    let sign: i64 = if sum >= 0 { 1 } else { -1 };\n    ((sum + sign) / 2) as i32\n}\n\npub fn mask_if_non_zero(x: i32) -> i32 {\n    if x != 0 { !0 } else { 0 }\n}\n\npub fn mask_if_zero(x: i32) -> i32 {\n    if x == 0 { !0 } else { 0 }\n}\n\npub fn select_using_mask(mask: i32, a: i32, b: i32) -> i32 {\n    (mask & a) ^ (!mask & b)\n}\n\npub fn saturating_rounding_doubling_high_mul(a: i32, b: i32) -> i32 {\n    let overflow = a == b && a == i32::MIN;\n    let product = (a as i64) * (b as i64);\n\n    let nudge = if product >= 0 { 1 << 30 } else { 1 - (1 << 30) };\n    let product_x2_high32 = ((product + nudge) / (1_i64 << 31)) as i32;\n\n    if overflow { i32::MAX } else { product_x2_high32 }\n}\n\npub fn is_signed<T: PrimInt>() -> bool {\n    let mv = T::min_value();\n    let z = T::zero();\n    mv < z\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_rounding_divide_by_pot_1() {\n        let x = 128;\n        let res = rounding_divide_by_pot(x, 2);\n        assert_eq!(res, 32);\n    }\n\n    #[test]\n    fn test_rounding_divide_by_pot_2() {\n        let x = 129;\n        let res = rounding_divide_by_pot(x, 2);\n        assert_eq!(res, 32);\n    }\n\n    #[test]\n    fn test_rounding_half_sum_1() {\n        let a = 22;\n        let b = 22;\n        let res = rounding_half_sum(a, b);\n        assert_eq!(res, 22)\n    }\n\n    #[test]\n    fn test_rounding_half_sum_2() {\n        let a = 6; // 0.75 in Q28.3\n        let b = 1_i32 << 3; // 1.0 in Q28.3\n        let expected_res = 7; // (1.75 / 2 = 0.875) in Q28.3\n        let res = rounding_half_sum(a, b);\n        assert_eq!(res, expected_res)\n    }\n\n    #[test]\n    fn test_rounding_half_sum_3() {\n        let a = 1610612736; // 0.75 in Q0.31\n        let b = i32::MAX; // 1.0 in Q0.31\n        let expected_res = 1879048192; // (1.75 / 2 = 0.875) in Q0.31\n        let res = rounding_half_sum(a, b);\n        assert_eq!(res, expected_res)\n    }\n\n    #[test]\n    fn test_saturating_rounding_doubling_high_mul() {\n        let a: i32 = 1879048192; // (1.75 / 2 = 0.875) in Q0.31\n        let b: i32 = -631612838; //\n        let expected_res = -552661233;\n        let res = saturating_rounding_doubling_high_mul(a, b);\n        assert_eq!(res, expected_res);\n    }\n}\n"
  },
  {
    "path": "core/src/ops/nn/softmax/mod.rs",
    "content": "mod fixedpoint;\npub mod math;\n\nuse math::{\n    convert_scale_to_mult_shift, exp_on_negative_values, get_reciprocal, rescale,\n    rounding_divide_by_pot, saturating_rounding_doubling_high_mul,\n    saturating_rounding_multiply_by_pot,\n};\nuse num_traits::Float;\nuse std::fmt::Debug;\nuse tract_num_traits::Zero;\n\nuse crate::internal::*;\nuse ndarray::prelude::*;\n\n#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]\npub enum SoftmaxKind {\n    Softmax(SoftmaxExp),\n    LogSoftmax,\n}\n\nimpl Default for SoftmaxKind {\n    fn default() -> Self {\n        SoftmaxKind::Softmax(SoftmaxExp::default())\n    }\n}\n\n#[derive(Debug, Copy, Clone, Hash, Default, PartialEq, Eq)]\npub enum SoftmaxExp {\n    #[default]\n    Libc,\n    // https://nic.schraudolph.org/pubs/Schraudolph99.pdf\n    FastCompact,\n}\n\n#[derive(Debug, Clone, new, Hash, Default, PartialEq, Eq)]\npub struct Softmax {\n    pub axes: TVec<usize>,\n    pub quant_output_dt: Option<DatumType>,\n    pub kind: SoftmaxKind,\n}\n\nimpl Op for Softmax {\n    fn name(&self) -> StaticName {\n        match self.kind {\n            SoftmaxKind::Softmax(_) => \"Softmax\".into(),\n            SoftmaxKind::LogSoftmax => \"LogSoftmax\".into(),\n        }\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut infos = vec![format!(\"Axis: {:?}\", self.axes)];\n        if let SoftmaxKind::Softmax(exp) = self.kind {\n            infos.push(format!(\"Exp impl: {exp:?}\"))\n        };\n        Ok(infos)\n    }\n\n    op_as_typed_op!();\n}\n\nimpl TypedOp for Softmax {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let dt = inputs[0].datum_type;\n        if dt.is_float() {\n            ensure!(\n                self.quant_output_dt.is_none(),\n                \"Float softmax should not have quant_output_dt, have {:?}\",\n                self.quant_output_dt\n            );\n        } else if dt.is_quantized() {\n            ensure!(\n                self.quant_output_dt.map(|q| q.is_quantized()).unwrap_or(false),\n                \"Quantized softmax should have a quantized output type (got {:?})\",\n                self.quant_output_dt\n            );\n        } else {\n            bail!(\n                \"Unsupported datum type in softmax: input type {:?}, output type {:?}\",\n                dt,\n                self.quant_output_dt\n            );\n        }\n\n        let fact = self.quant_output_dt.unwrap_or(dt).fact(inputs[0].shape.clone());\n        Ok(tvec!(fact))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        let axes: Option<TVec<usize>> =\n            self.axes.iter().map(|it| change.transform_axis(*it)).collect();\n        if let Some(axes) = axes {\n            Ok(Some(AxisChangeConsequence::new(\n                model,\n                node,\n                Some(Box::new(Softmax { axes, ..self.clone() })),\n                change,\n            )))\n        } else {\n            Ok(None)\n        }\n    }\n\n    as_op!();\n}\n\nimpl EvalOp for Softmax {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let dt = input.datum_type();\n\n        let output = match dt {\n            DatumType::F64 => self.eval_t::<f64>(input)?,\n            DatumType::F32 => self.eval_t::<f32>(input)?,\n            DatumType::F16 => self.eval_t::<f16>(input)?,\n            DatumType::QI8(_) | DatumType::QU8(_) => self.eval_quant(input)?,\n            dt => bail!(\"Unsupported type {dt:?}\"),\n        };\n        Ok(output)\n    }\n}\n\nimpl Softmax {\n    fn eval_t<T>(&self, input: TValue) -> TractResult<TVec<TValue>>\n    where\n        T: Float + Datum + std::iter::Sum,\n    {\n        let mut iterating_shape: TVec<usize> = input.shape().into();\n\n        for i in 0..iterating_shape.len() {\n            if self.axes.contains(&i) {\n                iterating_shape[i] = 1\n            }\n        }\n\n        let mut output = input.into_tensor();\n        let mut output_plain = output.try_as_plain_mut()?;\n        let mut view = output_plain.to_array_view_mut::<T>()?;\n\n        for it_coords in tract_ndarray::indices(&*iterating_shape) {\n            let mut view = view.view_mut();\n            for ix in 0..iterating_shape.len() {\n                if !self.axes.contains(&ix) {\n                    view.collapse_axis(Axis(ix), it_coords[ix]);\n                }\n            }\n            if let Some(slice) =\n                view.as_slice_mut().filter(|_| T::datum_type() == f32::datum_type())\n            {\n                let slice: &mut [f32] = unsafe { std::mem::transmute(slice) };\n                self.softmax_inner_slice_f32(slice, self.kind)?;\n            } else if let Some(slice) =\n                view.as_slice_mut().filter(|_| T::datum_type() == f16::datum_type())\n            {\n                let slice: &mut [f16] = unsafe { std::mem::transmute(slice) };\n                self.softmax_inner_slice_f16(slice, self.kind)?;\n            } else {\n                softmax_inner(view, self.kind);\n            }\n        }\n\n        Ok(tvec!(output.into_tvalue()))\n    }\n\n    fn eval_quant(&self, input: TValue) -> TractResult<TVec<TValue>> {\n        if self.kind == SoftmaxKind::LogSoftmax {\n            bail!(\"Quantized LogSoftmax is not supported\")\n        }\n        let mut iterating_shape: TVec<usize> = input.shape().into();\n        let output_dt =\n            self.quant_output_dt.context(\"Quandized softmax eval with no output type\")?;\n\n        for i in 0..iterating_shape.len() {\n            if self.axes.contains(&i) {\n                iterating_shape[i] = 1\n            }\n        }\n\n        // All operations will be done in u8, we will cast the result appropriately afterward.\n        let src_is_signed = input.datum_type().is_signed();\n        let out_is_signed = output_dt.is_signed();\n        let in_qp = input.datum_type().qparams().unwrap(); // Checked as we are in the quant case\n        let out_qp = output_dt.qparams().unwrap(); // Checked as we are in the quant case\n        let mut output = unsafe { input.into_tensor().into_array_unchecked::<u8>() };\n\n        for it_coords in tract_ndarray::indices(&*iterating_shape) {\n            let mut view = output.view_mut();\n            for ix in 0..iterating_shape.len() {\n                if !self.axes.contains(&ix) {\n                    view.collapse_axis(Axis(ix), it_coords[ix]);\n                }\n            }\n            softmax_quant_inner(view, src_is_signed, in_qp, out_is_signed, out_qp);\n        }\n\n        let mut output_tensor = output.into_tensor();\n        unsafe { output_tensor.set_datum_type(output_dt) };\n        Ok(tvec!(output_tensor.into_tvalue()))\n    }\n\n    fn softmax_inner_slice_f16(&self, slice: &mut [f16], kind: SoftmaxKind) -> TractResult<()> {\n        let max = (tract_linalg::ops().max_f16)().run(slice)?;\n        match kind {\n            SoftmaxKind::Softmax(exp_impl) => {\n                let sum = match exp_impl {\n                    SoftmaxExp::Libc => {\n                        let mut s = f16::zero();\n                        slice.iter_mut().for_each(|x| {\n                            *x = (*x - max).exp();\n                            s += *x;\n                        });\n                        s\n                    }\n                    SoftmaxExp::FastCompact => (tract_linalg::ops().softmax2_fastcompact_f16)()\n                        .run_with_params(slice, max)?,\n                };\n                let rsum = sum.recip();\n                (tract_linalg::ops().mul_by_scalar_f16)().run_with_params(slice, rsum)?;\n            }\n            SoftmaxKind::LogSoftmax => {\n                let mut exp_sum = f16::zero();\n                slice.iter_mut().for_each(|x| {\n                    *x -= max;\n                    exp_sum += x.exp();\n                });\n                let log_sum = exp_sum.ln();\n                slice.iter_mut().for_each(|x| *x -= log_sum);\n            }\n        }\n        Ok(())\n    }\n\n    fn softmax_inner_slice_f32(&self, slice: &mut [f32], kind: SoftmaxKind) -> TractResult<()> {\n        let max = (tract_linalg::ops().max_f32)().run(slice)?;\n        match kind {\n            SoftmaxKind::Softmax(exp_impl) => {\n                let sum = match exp_impl {\n                    SoftmaxExp::Libc => {\n                        let mut s = f32::zero();\n                        slice.iter_mut().for_each(|x| {\n                            *x = (*x - max).exp();\n                            s += *x;\n                        });\n                        s\n                    }\n                    SoftmaxExp::FastCompact => (tract_linalg::ops().softmax2_fastcompact_f32)()\n                        .run_with_params(slice, max)?,\n                };\n                let rsum = sum.recip();\n                (tract_linalg::ops().mul_by_scalar_f32)().run_with_params(slice, rsum)?;\n            }\n            SoftmaxKind::LogSoftmax => {\n                let mut exp_sum = f32::zero();\n                slice.iter_mut().for_each(|x| {\n                    *x -= max;\n                    exp_sum += x.exp();\n                });\n                let log_sum = exp_sum.ln();\n                slice.iter_mut().for_each(|x| *x -= log_sum);\n            }\n        }\n        Ok(())\n    }\n}\n\nfn softmax_inner<T: Float + Datum + std::iter::Sum, D: Dimension>(\n    mut view: ArrayViewMut<T, D>,\n    kind: SoftmaxKind,\n) {\n    let max =\n        *view.iter().max_by(|i, j| i.partial_cmp(j).unwrap_or(std::cmp::Ordering::Less)).unwrap();\n    view.mapv_inplace(|x| x - max);\n    let exp_sum = view.iter().map(|&x| x.exp()).sum();\n    match kind {\n        SoftmaxKind::Softmax(_) => {\n            view.mapv_inplace(|x| x.exp() / exp_sum);\n        }\n        SoftmaxKind::LogSoftmax => {\n            let log_sum = exp_sum.ln();\n            view.mapv_inplace(|x| x - log_sum);\n        }\n    }\n}\n\nfn softmax_quant_inner<D: Dimension>(\n    mut view: ArrayViewMut<u8, D>,\n    src_is_signed: bool,\n    in_qp: QParams,\n    out_is_signed: bool,\n    out_qp: QParams,\n) {\n    let (_, in_scale) = in_qp.zp_scale();\n    let (scale_in_multiplier, scale_in_shift) = convert_scale_to_mult_shift(in_scale).unwrap();\n    let (_, out_scale) = out_qp.zp_scale();\n    let (scale_out_multiplier, scale_out_shift) = convert_scale_to_mult_shift(out_scale).unwrap();\n    let shift = 26 - scale_in_shift;\n\n    // Compute the exponentials x - max\n    let mut buffer = vec![0_i32; view.len()];\n\n    // Handle the case were we considered an i8 as an u8 and still get the right x - max.\n    let safe_u8 = if src_is_signed { |x: &u8| x.wrapping_add(128) } else { |x: &u8| *x };\n\n    let max = view.iter().map(safe_u8).max().unwrap();\n    view.iter().zip(buffer.iter_mut()).for_each(|(x, exp)| {\n        let input_diff = safe_u8(x) as i32 - max as i32;\n\n        // We scale the input to be in Q5_26\n        let scaled_input_diff = if scale_in_multiplier != 0 {\n            saturating_rounding_multiply_by_pot(\n                saturating_rounding_doubling_high_mul(input_diff, scale_in_multiplier),\n                shift as i32,\n            )\n        } else {\n            saturating_rounding_multiply_by_pot(input_diff, shift as i32)\n        };\n\n        // It expects an input from Q5_26 and returns an output in Q0_31\n        *exp = exp_on_negative_values(scaled_input_diff);\n    });\n\n    // Compute sum of exp\n    // The sum is stored as an Q12_19 that's why we need to recale from Q0_31 to Q12_19 before summing.\n    let sum_of_exp = buffer.iter().map(|it| rescale(*it, 0, 12)).sum();\n\n    // Compute 1/sum_of_exp\n    // The result of this function is in Q0_31\n    let (inv_sum_of_exp, num_bits_over_unit) = get_reciprocal(sum_of_exp, 12);\n\n    // Compute the exponent value needed to be in Q24_8 before the final rescaling\n    let exponent = num_bits_over_unit as isize + 31 - 8;\n\n    view.iter_mut().zip(buffer.iter()).for_each(|(it, exp)| {\n        // Compute the product of exp * 1/sum_of_exp and scale the result in Q24_8\n        let unsat_output = rounding_divide_by_pot(\n            saturating_rounding_doubling_high_mul(inv_sum_of_exp, *exp),\n            exponent as i32,\n        );\n\n        // Scale the final result in the output scale range\n        let unsat_scaled_output = {\n            if scale_out_multiplier != 0 {\n                let (inv_multiplier, num_bits) = get_reciprocal(scale_out_multiplier, 1);\n                rounding_divide_by_pot(\n                    saturating_rounding_doubling_high_mul(unsat_output, inv_multiplier),\n                    (8 - scale_out_shift - 1 - num_bits as isize) as i32,\n                )\n            } else {\n                rounding_divide_by_pot(unsat_output, (8 - scale_out_shift) as i32)\n            }\n        };\n\n        // Return the final result by clipping the computed value within its range\n        // and casting it to u8 in any case.\n        #[allow(unknown_lints, unnecessary_transmutes)]\n        if out_is_signed {\n            *it = unsafe {\n                std::mem::transmute::<i8, u8>(i32::max(\n                    i32::min(unsat_scaled_output, i8::MAX as i32),\n                    i8::MIN as i32,\n                ) as i8)\n            };\n        } else {\n            *it = i32::max(i32::min(unsat_scaled_output, u8::MAX as i32), u8::MIN as i32) as u8;\n        }\n    });\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::ops::nn::DataFormat::NCHW;\n    use anyhow::Result;\n    use num_traits::PrimInt;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_data::internal::QParams::ZpScale;\n\n    fn assert_is_close(found: f32, expected: f32, in_dt: DatumType, out_dt: DatumType) {\n        let (_, in_epsilon) = in_dt.zp_scale();\n        let (_, out_epsilon) = out_dt.zp_scale();\n        let epsilon = in_epsilon + out_epsilon;\n        let error = (found - expected).abs();\n        assert!(\n            error <= epsilon,\n            \"epsilon eq failed: |{found:?}-{expected:?}|={error} should be <= {epsilon}\"\n        );\n    }\n\n    // Generate a random tensor with a quantized datum type\n    fn qtensor<T: PrimInt + Datum + Arbitrary>(shape: Vec<usize>) -> BoxedStrategy<Tensor> {\n        let len = shape.iter().product::<usize>();\n        let dt = q_datum::<T>((0.0001f32..0.1).boxed());\n        (vec(any::<T>(), len..=len), dt)\n            .prop_map(move |(vec, dt)| (ArrayD::from_shape_vec(shape.clone(), vec).unwrap(), dt))\n            .prop_map(move |(array, dt)| {\n                let mut tensor = array.into_tensor();\n                unsafe { tensor.set_datum_type(dt) };\n                tensor\n            })\n            .boxed()\n    }\n\n    // Generate a random quantized datum type\n    fn q_datum<T: PrimInt + Datum>(range: BoxedStrategy<f32>) -> BoxedStrategy<DatumType> {\n        let max_integer_bits = std::mem::size_of::<T>() * 8 - T::datum_type().is_signed() as usize;\n        prop_oneof![\n            (1usize..max_integer_bits).prop_map(|fixed_point| { 2f32.powi(-(fixed_point as i32)) }),\n            range\n        ]\n        .prop_map(|scale| {\n            if T::datum_type().is_signed() {\n                DatumType::QI8(ZpScale { zero_point: 0, scale })\n            } else {\n                DatumType::QU8(ZpScale { zero_point: 0, scale })\n            }\n        })\n        .boxed()\n    }\n\n    #[derive(Debug)]\n    struct SoftmaxProblem {\n        data: Tensor,\n        axes: TVec<usize>,\n        output_dt: DatumType,\n    }\n\n    impl SoftmaxProblem {\n        fn check(&self) -> Result<()> {\n            let inputs = tvec!(self.data.clone().into_tvalue());\n            let quant_output_dt = Some(self.output_dt).filter(|dt| !dt.is_float());\n            let softmax =\n                Softmax { axes: self.axes.clone(), quant_output_dt, ..Softmax::default() };\n\n            // Compute quantized output\n            let result = softmax.eval(inputs)?;\n            let result = args_1!(result);\n            let result_float = result.cast_to::<f32>()?;\n\n            // Compute reference output\n            let input_float = self.data.cast_to::<f32>()?;\n            let inputs_float = tvec!(input_float.into_owned().into_tvalue());\n            let softmax_float = Softmax { axes: self.axes.clone(), ..Softmax::default() };\n            let reference_float = softmax_float.eval(inputs_float)?;\n            let reference_array = args_1!(reference_float);\n            let reference = reference_array.to_plain_array_view::<f32>()?;\n\n            result_float\n                .to_plain_array_view::<f32>()?\n                .iter()\n                .zip(reference.iter())\n                .for_each(|(a, b)| assert_is_close(*a, *b, self.data.datum_type(), self.output_dt));\n            Ok(())\n        }\n    }\n\n    impl Arbitrary for SoftmaxProblem {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<SoftmaxProblem>;\n        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {\n            (1usize..2, 1usize..2, 1usize..5, 1usize..5, 0usize..4)\n                .prop_flat_map(|(n, c, h, w, axis)| {\n                    let shape_in: Vec<usize> =\n                        NCHW.from_n_c_hw(n, c, [h, w]).unwrap().shape.to_vec();\n                    (\n                        prop_oneof![qtensor::<i8>(shape_in.clone()), qtensor::<u8>(shape_in)],\n                        Just(tvec![axis]),\n                        prop_oneof![\n                            q_datum::<u8>((0.008f32..0.1).boxed()),\n                            q_datum::<i8>((0.008f32..0.1).boxed())\n                        ],\n                    )\n                })\n                .prop_map(|(data, axes, output_dt)| SoftmaxProblem { data, axes, output_dt })\n                .boxed()\n        }\n    }\n\n    #[derive(Debug)]\n    pub struct InnerSoftmaxProblem {\n        in_qp: QParams,\n        out_qp: QParams,\n        data: Vec<i8>,\n    }\n\n    impl InnerSoftmaxProblem {\n        fn check(&self) -> Result<()> {\n            let quantized = self.quantized();\n            let reference = self.reference();\n            assert!(quantized.iter().zip(reference.iter()).all(|(quantized, expected)| {\n                let abs_diff = if *quantized > *expected {\n                    quantized - *expected\n                } else {\n                    expected - *quantized\n                };\n                abs_diff <= 1\n            }));\n            Ok(())\n        }\n\n        fn reference(&self) -> Vec<u8> {\n            let (in_zero_point, in_scale) = self.in_qp.zp_scale();\n            let (out_zero_point, out_scale) = self.out_qp.zp_scale();\n            let in_float =\n                self.data.iter().map(|it| (*it as f32 - in_zero_point as f32) * in_scale).collect();\n            let mut in_float_array = Array1::from_vec(in_float);\n            softmax_inner(in_float_array.view_mut(), SoftmaxKind::default());\n            let rescaled_output = in_float_array\n                .iter()\n                .map(|it| {\n                    ((*it / out_scale).round() as i32 + out_zero_point)\n                        .max(u8::MIN as i32)\n                        .min(u8::MAX as i32) as u8\n                })\n                .collect();\n            rescaled_output\n        }\n\n        fn quantized(&self) -> Vec<u8> {\n            let in_data: Vec<u8> = unsafe { std::mem::transmute(self.data.clone()) };\n            let mut in_array = Array1::from_vec(in_data);\n            softmax_quant_inner(in_array.view_mut(), true, self.in_qp, false, self.out_qp);\n            in_array.to_vec()\n        }\n    }\n\n    impl Arbitrary for InnerSoftmaxProblem {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<InnerSoftmaxProblem>;\n        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {\n            (\n                prop_oneof![\n                    q_datum::<i8>((0.0001f32..0.01).boxed()),\n                    q_datum::<u8>((0.0001f32..0.01).boxed())\n                ],\n                prop_oneof![\n                    q_datum::<u8>((0.008f32..0.1).boxed()),\n                    q_datum::<i8>((0.008f32..0.1).boxed())\n                ],\n                vec(any::<i8>(), 1..10),\n            )\n                .prop_map(|(in_qp, out_qp, data)| InnerSoftmaxProblem {\n                    in_qp: in_qp.qparams().unwrap(),\n                    out_qp: out_qp.qparams().unwrap(),\n                    data,\n                })\n                .boxed()\n        }\n    }\n\n    proptest::proptest! {\n        #![proptest_config(ProptestConfig::with_cases(1000))]\n        #[test]\n        fn test_softmax_inner_prop(pb in any::<InnerSoftmaxProblem>()) {\n            pb.check().unwrap()\n        }\n    }\n\n    proptest::proptest! {\n        #![proptest_config(ProptestConfig::with_cases(1000))]\n        #[test]\n        fn test_softmax_prop(pb in any::<SoftmaxProblem>()) {\n            pb.check().unwrap()\n        }\n    }\n\n    #[test]\n    // We test QU8 -> QU8\n    fn test_softmax_trivial_0() -> Result<()> {\n        let input_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.03125 }); // Q3_5\n        let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.00390625 }); // Q0_8;\n        let mut data = Tensor::from_shape(&[1, 1, 2, 2], &[0_u8, 0, 0, 4])?;\n        unsafe { data.set_datum_type(input_dt) };\n\n        let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    // We test QI8 -> QU8\n    fn test_softmax_trivial_1() -> Result<()> {\n        let input_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0625 }); // Q3_4\n        let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.00390625 }); // Q0_8;\n        let mut data = Tensor::from_shape(&[1, 1, 2, 2], &[0_i8, 0, 0, 4])?;\n        unsafe { data.set_datum_type(input_dt) };\n\n        let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    // We test QI8 -> QI8\n    fn test_softmax_trivial_2() -> Result<()> {\n        let input_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0625 }); // Q3_4\n        let output_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0078125 }); // Q0_7;\n        let mut data = Tensor::from_shape(&[1, 1, 2, 2], &[0_i8, 0, 0, -4])?;\n        unsafe { data.set_datum_type(input_dt) };\n\n        let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    // We test QU8 -> QI8\n    fn test_softmax_trivial_3() -> Result<()> {\n        let input_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.03125 }); // Q3_5\n        let output_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0078125 }); // Q0_7;\n        let mut data = Tensor::from_shape(&[1, 1, 2, 2], &[0_u8, 0, 0, 4])?;\n        unsafe { data.set_datum_type(input_dt) };\n\n        let prob = SoftmaxProblem { data, axes: tvec![2], output_dt };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_softmax_1() -> Result<()> {\n        let input_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.5 }); // Q6_1\n        let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.5 }); // Q7_1\n        let mut data = Tensor::from_shape(&[1, 1, 1, 2], &[115_i8, 115])?;\n        unsafe { data.set_datum_type(input_dt) };\n\n        let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_softmax_2() -> Result<()> {\n        let input_dt = DatumType::QI8(ZpScale { zero_point: 0, scale: 0.0001 });\n        let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.008 });\n        let mut data = Tensor::from_shape(&[1, 1, 1, 2], &[115_i8, 115])?;\n        unsafe { data.set_datum_type(input_dt) };\n\n        let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_softmax_3() -> Result<()> {\n        let input_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.6220956 });\n        let output_dt = DatumType::QU8(ZpScale { zero_point: 0, scale: 0.5187921 });\n        let mut data = Tensor::from_shape(&[1, 1, 1, 2], &[13_u8, 218])?;\n        unsafe { data.set_datum_type(input_dt) };\n\n        let prob = SoftmaxProblem { data, axes: tvec![3], output_dt };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_inner_softmax_1() -> Result<()> {\n        let in_qp = ZpScale { zero_point: 0, scale: 0.03125 };\n        let out_qp = ZpScale { zero_point: 0, scale: 0.5 };\n        let data = vec![0_i8, 1];\n\n        let prob = InnerSoftmaxProblem { in_qp, out_qp, data };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_inner_softmax_2() -> Result<()> {\n        let in_qp = ZpScale { zero_point: 0, scale: 0.5 };\n        let out_qp = ZpScale { zero_point: 0, scale: 0.03125 };\n        let data = vec![100i8, -28];\n\n        let prob = InnerSoftmaxProblem { in_qp, out_qp, data };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_inner_softmax_not_pow_2_1() -> Result<()> {\n        let in_qp = ZpScale { zero_point: 0, scale: 0.7298456 };\n        let out_qp = ZpScale { zero_point: 0, scale: 0.03125 };\n        let data = vec![100i8, -28];\n\n        let prob = InnerSoftmaxProblem { in_qp, out_qp, data };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    #[ignore]\n    // Fails but the difference is quite low and the sum still give exactly one:\n    // quantized: 110(0.88), 15(0.12)\n    // expected: 112(0.896), 13(0.104)\n    fn test_inner_softmax_not_pow_2_2() -> Result<()> {\n        let in_qp = ZpScale { zero_point: 0, scale: 0.2123116 };\n        let out_qp = ZpScale { zero_point: 0, scale: 0.008 };\n        let data = vec![118i8, 108];\n\n        let prob = InnerSoftmaxProblem { in_qp, out_qp, data };\n        prob.check()?;\n        Ok(())\n    }\n\n    #[test]\n    #[ignore]\n    // Fails but the difference is quite low and the sum still give exactly one:\n    // quantized: 40(0.625), 24(0.375)\n    // expected: 42(0.65625), 22(0.34375)\n    fn test_inner_softmax_not_pow_2_3() -> Result<()> {\n        let in_qp = ZpScale { zero_point: 0, scale: 0.33034274 };\n        let out_qp = ZpScale { zero_point: 0, scale: 0.015625 };\n        let data = vec![45i8, 43];\n\n        let prob = InnerSoftmaxProblem { in_qp, out_qp, data };\n        prob.check()?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/quant.rs",
    "content": "#![allow(clippy::unnecessary_cast)]\n\nuse crate::internal::*;\nuse crate::ops::element_wise::ElementWiseOp;\nuse crate::ops::math::QScale;\nuse num_traits::AsPrimitive;\nuse tract_linalg::Scaler;\nuse tract_linalg::lut::Lut;\nuse tract_linalg::mmm::RoundingPolicy;\n\nuse super::binary::TypedBinOp;\nuse super::math::round_ties_to_even;\n\npub fn quantize_linear_f32_u8(x: f32, scale: f32, zero_point: i32) -> u8 {\n    (((x * scale).round() as i32) + zero_point).clamp(u8::MIN as i32, u8::MAX as i32) as u8\n}\n\npub fn quantize_linear_f32_i8(x: f32, scale: f32, zero_point: i32) -> i8 {\n    (((x * scale).round() as i32) + zero_point).clamp(i8::MIN as i32, i8::MAX as i32) as i8\n}\n\nelement_wise_oop!(quantize_linear_u8,\n QuantizeLinearU8 {\n     scale: f32,\n     zero_point: u8\n },\n [f16] => u8 |op, xs, ys| {\n     xs.iter().zip(ys.iter_mut()).for_each(|(x,y)|\n                                           *y = quantize_linear_f32_u8(x.to_f32(), op.scale, op.zero_point as i32)\n                                          );\n     Ok(())\n },\n [f32,i32] => u8 |op, xs, ys| {\n     xs.iter().zip(ys.iter_mut()).for_each(|(x,y)|\n                                           *y = quantize_linear_f32_u8(*x as f32, op.scale, op.zero_point as i32)\n                                          );\n     Ok(())\n };\n info: info_quantize_linear_u8\n);\n\nfn info_quantize_linear_u8(q: &QuantizeLinearU8) -> TractResult<Vec<String>> {\n    Ok(vec![format!(\n        \"scale: {} zero_point: {} 1/scale: {}\",\n        q.scale,\n        q.zero_point,\n        q.scale.recip()\n    )])\n}\n\nelement_wise_oop!(quantize_linear_i8,\n QuantizeLinearI8 {\n     scale: f32,\n     zero_point: i8\n },\n [f32,i32] => i8 |op, xs, ys| {\n     xs.iter().zip(ys.iter_mut()).for_each(|(x,y)|\n                                           *y = quantize_linear_f32_i8(*x as f32, op.scale, op.zero_point as i32)\n                                          );\n     Ok(())\n };\n info: info_quantize_linear_i8\n);\n\nfn info_quantize_linear_i8(q: &QuantizeLinearI8) -> TractResult<Vec<String>> {\n    Ok(vec![format!(\n        \"scale: {} zero_point: {} 1/scale: {}\",\n        q.scale,\n        q.zero_point,\n        q.scale.recip()\n    )])\n}\n\n#[derive(Clone, Debug, new, PartialEq)]\npub struct DequantizeLinearF32 {\n    pub scale: f32,\n    pub zero_point: i32,\n}\n\nimpl Eq for DequantizeLinearF32 {}\n\nimpl DequantizeLinearF32 {\n    fn eval_t<T: Datum + AsPrimitive<i32>>(&self, input: &Tensor) -> TractResult<Tensor> {\n        let mut output = unsafe { Tensor::uninitialized::<f32>(input.shape())? };\n        input\n            .try_as_plain()?\n            .as_slice::<T>()?\n            .iter()\n            .zip(output.try_as_plain_mut()?.as_slice_mut::<f32>()?.iter_mut())\n            .for_each(|(x, y)| *y = (x.as_() - self.zero_point) as f32 * self.scale);\n        Ok(output)\n    }\n}\n\nimpl Op for DequantizeLinearF32 {\n    fn name(&self) -> StaticName {\n        \"DequantizeLinearF32\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"scale: {} zero_point: {}\", self.scale, self.zero_point)])\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Accurate\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DequantizeLinearF32 {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let output = match inputs[0].datum_type() {\n            DatumType::I8 => self.eval_t::<i8>(&inputs[0])?,\n            DatumType::I32 => self.eval_t::<i32>(&inputs[0])?,\n            DatumType::U8 => self.eval_t::<u8>(&inputs[0])?,\n            dt => bail!(\"Unsupported type {:?}\", dt),\n        };\n        Ok(tvec!(output.into_tvalue()))\n    }\n}\n\nimpl TypedOp for DequantizeLinearF32 {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact = inputs[0].clone();\n        fact.datum_type = f32::datum_type();\n        Ok(tvec!(fact))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        Ok(Some(AxisChangeConsequence::new(model, node, None, change)))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        dequant: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let mut current = dequant;\n        let incoming_dt = model.node_input_facts(dequant.id)?[0].datum_type;\n        while let Some(quant) = model.single_succ(current.id)? {\n            let q_params = if let Some(op) = quant.op_as::<ElementWiseOp>() {\n                if let Some(mop) = op.0.downcast_ref::<QuantizeLinearU8>() {\n                    Some((mop.scale, mop.zero_point as i32, u8::datum_type()))\n                } else {\n                    op.0.downcast_ref::<QuantizeLinearI8>()\n                        .map(|mop| (mop.scale, mop.zero_point as i32, i8::datum_type()))\n                }\n            } else {\n                None\n            };\n            if let Some((scale, zero_point, dt)) = q_params {\n                // first, try Op::quantize() on all ops in the chain\n                let mut patch = TypedModelPatch::default();\n                let mut wire: OutletId = patch.tap_model(model, dequant.inputs[0])?;\n                let mut next = model.single_succ(dequant.id)?.unwrap();\n                loop {\n                    if let Some(op) = next\n                        .op\n                        .quantize(model, dequant, dt, scale, zero_point)\n                        .with_context(|| format!(\"Quantizing {next}\"))?\n                    {\n                        wire = patch.wire_node(&*next.name, op, [wire].as_ref())?[0];\n                    } else {\n                        break;\n                    }\n                    if next.id == current.id {\n                        patch.shunt_outside(model, OutletId::new(quant.id, 0), wire)?;\n                        return Ok(Some(patch));\n                    } else {\n                        next = model.single_succ(next.id)?.unwrap();\n                    }\n                }\n                // or else make a lookup table\n                if incoming_dt == DatumType::I8 || incoming_dt == DatumType::U8 {\n                    let mut adhoc_model = TypedModel::default();\n                    let mut wire = adhoc_model.add_source(\"ad-hoc\", dt.fact([256]))?;\n                    let mut next = model.single_succ(dequant.id)?.unwrap();\n                    let mut name = None;\n                    // plug in dequant\n                    wire = adhoc_model.wire_node(\n                        &*dequant.name,\n                        dequant.op.clone(),\n                        [wire].as_ref(),\n                    )?[0];\n                    while next.id != quant.id {\n                        name.get_or_insert(&*next.name);\n                        wire =\n                            adhoc_model.wire_node(&*next.name, next.op.clone(), [wire].as_ref())?\n                                [0];\n                        next = model.single_succ(next.id)?.unwrap();\n                    }\n                    // plug in quant\n                    wire =\n                        adhoc_model.wire_node(&*quant.name, quant.op.clone(), [wire].as_ref())?[0];\n                    adhoc_model.select_output_outlets(&[wire])?;\n                    let input = (0u8..=255).collect::<Vec<u8>>();\n                    let input = match dt {\n                        DatumType::I8 => unsafe {\n                            tensor1(std::mem::transmute::<&[u8], &[i8]>(&*input))\n                        },\n                        DatumType::U8 => tensor1(&input),\n                        _ => unreachable!(),\n                    };\n                    let output =\n                        SimplePlan::new(adhoc_model)?.run(tvec!(input.into_tvalue()))?.remove(0);\n                    let table: &[u8] = match dt {\n                        DatumType::I8 => unsafe {\n                            std::mem::transmute::<&[i8], &[u8]>(\n                                output.try_as_plain()?.as_slice::<i8>()?,\n                            )\n                        },\n                        DatumType::U8 => output.try_as_plain()?.as_slice::<u8>()?,\n                        _ => unreachable!(),\n                    };\n                    let op = lookup_table((tract_linalg::ops().lut_u8)(table));\n                    let mut patch = TypedModelPatch::default();\n                    let mut wire: OutletId = patch.tap_model(model, dequant.inputs[0])?;\n\n                    wire = patch.wire_node(name.unwrap_or(&*dequant.name), op, [wire].as_ref())?[0];\n                    patch.shunt_outside(model, OutletId::new(quant.id, 0), wire)?;\n                    return Ok(Some(patch));\n                }\n            }\n            let (input_facts, output_facts) = model.node_facts(quant.id)?;\n            let invariants = quant\n                .op\n                .axes_mapping(&input_facts, &output_facts)\n                .with_context(|| format!(\"Querying invariants for {quant}\"))?;\n            if invariants.is_element_wise_unary() {\n                current = quant;\n            } else {\n                break;\n            }\n        }\n        Ok(None)\n    }\n\n    as_op!();\n}\n\nelement_wise_oop!(lookup_table,\n LookupTable {\n     table: Box<dyn Lut>\n },\n [i8] => i8 |op, xs, ys| {\n     ys.copy_from_slice(xs);\n     unsafe {\n         let casted = std::slice::from_raw_parts_mut(ys.as_mut_ptr() as *mut u8, ys.len());\n         op.table.run(casted);\n     }\n     Ok(())\n },\n [u8] => u8 |op, xs, ys| {\n     ys.copy_from_slice(xs);\n     op.table.run(ys);\n     Ok(())\n }\n);\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct Scale;\n\nimpl crate::ops::binary::BinMiniOp for Scale {\n    fn name(&self) -> &'static str {\n        \"Scale\"\n    }\n    fn result_datum_type(&self, a: DatumType, b: DatumType) -> TractResult<DatumType> {\n        if !a.is_float() {\n            bail!(\"Scale left operand must be float, got {:?}\", a);\n        }\n        Ok(b)\n    }\n\n    fn operating_datum_type(&self, a: DatumType, b: DatumType) -> TractResult<DatumType> {\n        if !a.is_float() {\n            bail!(\"Scale left operand must be float, got {:?}\", a);\n        }\n        Ok(b)\n    }\n\n    fn eval_out_of_place(&self, c: &mut Tensor, a: &Tensor, b: &Tensor) -> TractResult<()> {\n        let a = a.cast_to::<f32>()?;\n        let a = a.to_plain_array_view::<f32>()?;\n        unsafe fn eval_out_of_place_t<T: Datum + AsPrimitive<f32>>(\n            c: &mut Tensor,\n            a: &ndarray::ArrayViewD<f32>,\n            b: &Tensor,\n        ) where\n            f32: AsPrimitive<T>,\n        {\n            let b = unsafe { b.to_array_view_unchecked::<T>() };\n            let mut c = unsafe { c.to_array_view_mut_unchecked::<T>() };\n            ndarray::Zip::from(&mut c)\n                .and_broadcast(a)\n                .and_broadcast(b)\n                .for_each(|c, a, b| *c = scale_by(*b, *a))\n        }\n        unsafe { dispatch_numbers!(eval_out_of_place_t(b.datum_type())(c, &a, b)) }\n        Ok(())\n    }\n\n    fn eval_in_a(&self, a: &mut Tensor, b: &Tensor) -> TractResult<()> {\n        let mut a_plain = a.try_as_plain_mut()?;\n        let a = a_plain.to_array_view_mut::<f32>()?;\n        let b = b.to_plain_array_view::<f32>()?;\n        ndarray::Zip::from(a).and_broadcast(b).for_each(|a, b| *a = scale_by(*b, *a));\n        Ok(())\n    }\n\n    fn is_commutative(&self) -> bool {\n        false\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let a = model.outlet_fact(node.inputs[0])?;\n        if let Some(a) = &a.uniform {\n            if a.cast_to_scalar::<f32>()? == 1. {\n                return Ok(Some(TypedModelPatch::rewire(\n                    model,\n                    &node.inputs[1..2],\n                    &[node.id.into()],\n                    &|_p, x| Ok(x.into()),\n                )?));\n            } else if node.outputs[0].fact.datum_type == DatumType::I32 {\n                let factor = a.cast_to_scalar::<f32>()?;\n                let scaler = Scaler::new(factor, RoundingPolicy::Even);\n\n                let op = ElementWiseOp(Box::new(QScale { scaler }), None);\n                let patch =\n                    TypedModelPatch::replace_single_op(model, node, &node.inputs[1..2], op)?;\n\n                return Ok(Some(patch));\n            }\n        }\n        Ok(None)\n    }\n}\n\n#[inline]\npub(crate) fn scale_by<T: Datum + AsPrimitive<f32>>(b: T, a: f32) -> T\nwhere\n    f32: AsPrimitive<T>,\n{\n    let b = b.as_();\n    (round_ties_to_even(b.abs() * a) * b.signum()).as_()\n}\n\npub fn scale() -> TypedBinOp {\n    TypedBinOp(Box::new(Scale), None)\n}\n\n/// Offsets i8 integers as u8 integers.\npub(crate) fn offset_i8_as_u8_elementwise(x: i8) -> u8 {\n    (x as u8).wrapping_add(128)\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct OffsetI8asU8;\nimpl ElementWiseMiniOp for OffsetI8asU8 {\n    fn name(&self) -> String {\n        format!(\"{}{}\", self.prefix(), stringify!(OffsetI8asU8))\n    }\n    fn output_type(&self, input_type: DatumType) -> Option<DatumType> {\n        Some(if let DatumType::QI8(qp) = input_type {\n            let (zp, scale) = qp.zp_scale();\n            DatumType::QU8(QParams::ZpScale { zero_point: zp + 128, scale })\n        } else if input_type == DatumType::I8 {\n            DatumType::U8\n        } else {\n            input_type\n        })\n    }\n    fn eval_out_of_place(&self, t: &Tensor, out_dt: Option<DatumType>) -> TractResult<Tensor> {\n        let output_type = out_dt.unwrap_or(self.output_type(t.datum_type()).unwrap());\n        let mut dst = unsafe { Tensor::uninitialized_dt(output_type, t.shape())? };\n        if t.datum_type().unquantized() == i8::datum_type() {\n            t.try_as_plain()?\n                .as_slice::<i8>()?\n                .iter()\n                .zip(dst.try_as_plain_mut()?.as_slice_mut::<u8>()?.iter_mut())\n                .for_each(|(x, y)| *y = offset_i8_as_u8_elementwise(*x));\n            return Ok(dst);\n        }\n\n        bail!(\"{} does not support {:?}\", self.name(), t.datum_type());\n    }\n}\n\npub fn offset_i8_as_u8() -> ElementWiseOp {\n    ElementWiseOp(Box::new(OffsetI8asU8 {}), None)\n}\n\n/// Offsets u8 integers as i8 integers.\npub(crate) fn offset_u8_as_i8_elementwise(x: u8) -> i8 {\n    x.wrapping_sub(128) as i8\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct OffsetU8asI8;\nimpl ElementWiseMiniOp for OffsetU8asI8 {\n    fn name(&self) -> String {\n        format!(\"{}{}\", self.prefix(), stringify!(OffsetU8asI8))\n    }\n    fn output_type(&self, input_type: DatumType) -> Option<DatumType> {\n        Some(if let DatumType::QU8(qp) = input_type {\n            let (zp, scale) = qp.zp_scale();\n            DatumType::QI8(QParams::ZpScale { zero_point: zp - 128, scale })\n        } else if input_type == DatumType::U8 {\n            DatumType::I8\n        } else {\n            input_type\n        })\n    }\n    fn eval_out_of_place(&self, t: &Tensor, out_dt: Option<DatumType>) -> TractResult<Tensor> {\n        let output_type = out_dt.unwrap_or(self.output_type(t.datum_type()).unwrap());\n        let mut dst = unsafe { Tensor::uninitialized_dt(output_type, t.shape())? };\n        if t.datum_type().unquantized() == u8::datum_type() {\n            t.try_as_plain()?\n                .as_slice::<u8>()?\n                .iter()\n                .zip(dst.try_as_plain_mut()?.as_slice_mut::<i8>()?.iter_mut())\n                .for_each(|(x, y)| *y = offset_u8_as_i8_elementwise(*x));\n            return Ok(dst);\n        }\n\n        bail!(\"{} does not support {:?}\", self.name(), t.datum_type());\n    }\n}\npub fn offset_u8_as_i8() -> ElementWiseOp {\n    ElementWiseOp(Box::new(OffsetU8asI8 {}), None)\n}\n\n#[cfg(test)]\npub mod scale {\n    use crate::internal::*;\n    use crate::ops::einsum::EinSum;\n    use crate::ops::math::round_ties_to_even;\n    use proptest::prelude::*;\n\n    fn test_scale(a: i8, b: i8, scale: f32) {\n        let expected = (((a as i32) * (b as i32)) as f32) / scale;\n        let expected = round_ties_to_even(expected.abs()) * expected.signum();\n        let expected = (expected as i32).clamp(-128, 127);\n        let expected = tensor2(&[[expected as i8]]);\n\n        let input = tvec!(tensor2(&[[b]]).into_tvalue());\n        let mut model = TypedModel::default();\n        let a = model.add_const(\"a\", tensor2(&[[a]])).unwrap();\n        let b = model.add_source(\"b\", i8::fact([1, 1])).unwrap();\n        let bias = model.add_const(\"bias\", tensor0(0i32)).unwrap();\n        let a0 = model.add_const(\"a0\", tensor0(0i8)).unwrap();\n        let a_scale = model.add_const(\"a_scale\", tensor0(1f32)).unwrap();\n        let b0 = model.add_const(\"b0\", tensor0(0i8)).unwrap();\n        let b_scale = model.add_const(\"b_scale\", tensor0(1f32)).unwrap();\n        let c0 = model.add_const(\"c0\", tensor0(0i8)).unwrap();\n        let c_scale = model.add_const(\"c_scale\", tensor0(scale)).unwrap();\n        let op = EinSum {\n            axes: \"mk,kn,,,,,,,->mn\".parse().unwrap(),\n            operating_dt: i32::datum_type(),\n            q_params: Some(i8::datum_type()),\n        };\n        let output = model\n            .wire_node(\"mmm\", op, &[a, b, bias, a0, a_scale, b0, b_scale, c0, c_scale])\n            .unwrap();\n        model.select_output_outlets(&output).unwrap();\n\n        let plain = model.clone().into_runnable().unwrap().run(input.clone()).unwrap();\n        assert_eq!(*plain[0], expected);\n\n        let optim = model.into_optimized().unwrap().into_runnable().unwrap().run(input).unwrap();\n        assert_eq!(*optim[0], expected);\n    }\n\n    proptest! {\n        #[test]\n        fn prop(a in any::<i8>(), b in any::<i8>(), scale in 0.00001f32..1000.) {\n            test_scale(a, b, scale);\n        }\n    }\n\n    #[test]\n    fn t1() {\n        test_scale(-117, 15, 37.753822);\n    }\n\n    #[test]\n    fn t2() {\n        test_scale(-4, -60, 475.21674);\n    }\n}\n"
  },
  {
    "path": "core/src/ops/scan/decluttered.rs",
    "content": "use std::collections::HashSet;\n\nuse crate::ops::einsum::EinSum;\nuse crate::ops::konst::Const;\nuse crate::optim::OptimizerSession;\n\nuse super::optimized::{OptScan, ScanOpParams};\nuse tract_data::internal::*;\nuse tract_data::itertools::izip;\n\nuse super::*;\n\n#[derive(Debug, Clone, Default)]\npub struct Scan {\n    pub skip: usize,\n    pub reset_every_turn: bool,\n    pub body: TypedModel,\n    pub decluttered: bool,\n    pub input_mapping: Vec<InputMapping>,\n    pub output_mapping: Vec<OutputMapping<TDim>>,\n}\n\nimpl PartialEq for Scan {\n    fn eq(&self, _other: &Self) -> bool {\n        false\n    }\n}\nimpl Eq for Scan {}\n\nimpl Scan {\n    pub fn to_codegen_op(&self, optimize_inner: bool) -> TractResult<OptScan> {\n        let mut model = self.body.clone();\n        if optimize_inner {\n            model = model.into_optimized()?;\n        }\n        let plan = SimplePlan::new(model)?;\n\n        Ok(OptScan::new(Arc::new(ScanOpParams::new(\n            self.skip,\n            self.reset_every_turn,\n            plan,\n            self.input_mapping.clone(),\n            self.output_mapping.clone(),\n        ))))\n    }\n\n    pub fn new(\n        body: TypedModel,\n        input_mapping: Vec<InputMapping>,\n        output_mapping: Vec<OutputMapping<TDim>>,\n        skip: usize,\n    ) -> TractResult<Scan> {\n        body.check_consistency()?;\n        ensure!(input_mapping.len() == body.input_outlets()?.len());\n        ensure!(output_mapping.len() == body.output_outlets()?.len());\n        Ok(Scan {\n            skip,\n            reset_every_turn: false,\n            body,\n            decluttered: false,\n            input_mapping,\n            output_mapping,\n        })\n    }\n\n    pub fn iteration_count(&self, inputs: &[&TypedFact]) -> Option<TDim> {\n        self.to_codegen_op(false).unwrap().iteration_count(inputs)\n    }\n\n    fn declutter_body(\n        &self,\n        session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        rule_if!(!self.decluttered);\n        let mut new = self.clone();\n        let mut body = self.body.clone();\n        session.optimize(&mut body)?;\n        new.body = body;\n        new.decluttered = true;\n        Ok(Some(TypedModelPatch::replace_single_op(model, node, &node.inputs, new)?))\n    }\n\n    fn declutter_single_loop(\n        &self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let inputs = model.node_input_facts(node.id)?;\n        let iters =\n            super::iteration_count(&self.input_mapping, &inputs).context(\"No scan input\")?;\n        if !iters.is_one() {\n            return Ok(None);\n        }\n        let mut patch = TypedModelPatch::new(\"Inline single loop scan\");\n        patch.model = self.body.clone();\n        for (outer_wire, inner_wire) in izip!(&node.inputs, &self.body.inputs) {\n            patch.taps.insert(*inner_wire, *outer_wire);\n        }\n        for (inner_wire, mapping) in izip!(&self.body.outputs, &self.output_mapping) {\n            if let Some((slot, _)) = mapping.scan {\n                patch.shunt_outside(model, (node.id, slot).into(), *inner_wire)?;\n            }\n            if let Some(slot) = mapping.last_value_slot {\n                patch.shunt_outside(model, (node.id, slot).into(), *inner_wire)?;\n            }\n        }\n        Ok(Some(patch))\n    }\n\n    fn declutter_body_axes(\n        &self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let mut suggestions = vec![];\n        for n in self.body.eval_order()? {\n            let node = self.body.node(n);\n            for suggestion in node.op.suggested_axis_changes()? {\n                let outlet = suggestion.0.as_outlet(node);\n                suggestions.push(AxisChange { outlet, op: suggestion.1 })\n            }\n            for (slot, fact) in node.outputs.iter().enumerate() {\n                for (ix, dim) in fact.fact.shape.iter().enumerate() {\n                    if dim.is_one() {\n                        suggestions.push(AxisChange {\n                            outlet: OutletId::new(n, slot),\n                            op: AxisOp::Rm(ix),\n                        });\n                    }\n                }\n            }\n        }\n        let node_input_facts = model.node_input_facts(node.id)?;\n        for suggestion in suggestions.into_iter() {\n            if let Some(conseq) = self.try_body_axes_change(suggestion, true, &node_input_facts)? {\n                let mut patch = TypedModelPatch::default();\n                let mut inputs = tvec!();\n                for outlet in &node.inputs {\n                    inputs.push(patch.tap_model(model, *outlet)?);\n                }\n                for change in conseq.wire_changes {\n                    if let InOut::In(i) = change.0 {\n                        let mut value = patch\n                            .outlet_fact(inputs[i])?\n                            .konst\n                            .clone()\n                            .context(\"Will only reshape constants\")?\n                            .into_tensor();\n                        change.1.change_tensor(&mut value, false)?;\n                        let konst_name = patch.node(inputs[i].node).name.clone();\n                        inputs[i] = patch.add_const(konst_name, value)?;\n                    }\n                }\n                let wires = patch.wire_node(\n                    &node.name,\n                    conseq.substitute_op.unwrap_or_else(|| Box::new(self.clone())),\n                    &inputs,\n                )?;\n                for (ix, new) in wires.into_iter().enumerate() {\n                    patch.shunt_outside(model, OutletId::new(node.id, ix), new)?;\n                }\n                return Ok(Some(patch));\n            }\n        }\n        Ok(None)\n    }\n\n    fn remove_outer_output_from_mappings(\n        mappings: &[OutputMapping<TDim>],\n        discarded: usize,\n    ) -> Vec<OutputMapping<TDim>> {\n        mappings\n            .iter()\n            .map(|m| OutputMapping {\n                scan: m.scan.map(|(slot, info)| (slot - (slot > discarded) as usize, info)),\n                last_value_slot: m.last_value_slot.map(|n| n - (n > discarded) as usize),\n                full_dim_hint: m.full_dim_hint.clone(),\n                state: m.state,\n            })\n            .collect()\n    }\n\n    fn declutter_const_input(\n        &self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let inputs = model.node_input_facts(node.id)?;\n        for (slot, mapping) in self.input_mapping.iter().enumerate() {\n            if let InputMapping::Full = mapping\n                && let Some(konst) = inputs[slot].konst.as_ref()\n            {\n                let mut op = self.clone();\n                let src = op.body.inputs[slot];\n                op.body.inputs.remove(slot);\n                op.body.nodes[src.node].inputs.clear();\n                op.body.nodes[src.node].op = Box::new(Const::new(konst.clone())?);\n                op.input_mapping.remove(slot);\n                let mut inputs = node.inputs.clone();\n                inputs.remove(slot);\n                return Ok(Some(TypedModelPatch::replace_single_op(model, node, &inputs, op)?));\n            }\n        }\n        Ok(None)\n    }\n\n    fn declutter_discard_unused_input(\n        &self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        for (slot, input) in self.body.input_outlets()?.iter().enumerate() {\n            let source_node = self.body.node(input.node);\n            if source_node.outputs[0].successors.len() == 0\n                && !self.body.output_outlets()?.contains(input)\n            {\n                let mut new_inputs = node.inputs.clone();\n                new_inputs.remove(slot);\n                let mut new_mappings: Vec<_> = self.input_mapping.clone();\n                new_mappings.remove(slot);\n                let mut model_inputs = self.body.input_outlets()?.to_vec();\n                model_inputs.remove(slot);\n                let mut body = self.body.clone();\n                let mut patch = TypedModelPatch::default();\n                patch.obliterate(source_node.id)?;\n                patch.apply(&mut body)?;\n                body.set_input_outlets(&model_inputs)?;\n                body.declutter()?;\n                let op =\n                    Self { body, input_mapping: new_mappings, decluttered: true, ..self.clone() };\n                return Ok(Some(TypedModelPatch::replace_single_op(model, node, &new_inputs, op)?));\n            }\n        }\n        Ok(None)\n    }\n\n    fn declutter_discard_useless_outer_output(\n        &self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        for (ix, o) in node.outputs.iter().enumerate() {\n            if o.successors.len() == 0\n                && !model.output_outlets()?.contains(&OutletId::new(node.id, ix))\n            {\n                let mappings = self\n                    .output_mapping\n                    .iter()\n                    .map(|m| OutputMapping {\n                        scan: m.scan.filter(|(slot, _info)| *slot != ix),\n                        last_value_slot: m.last_value_slot.filter(|s| *s != ix),\n                        full_dim_hint: m.full_dim_hint.clone(),\n                        state: m.state,\n                    })\n                    .collect::<Vec<_>>();\n                let mut op = self.clone();\n                op.output_mapping = Self::remove_outer_output_from_mappings(&mappings, ix);\n                let mut patch = TypedModelPatch::default();\n                let inputs = node\n                    .inputs\n                    .iter()\n                    .map(|&i| patch.tap_model(model, i))\n                    .collect::<TractResult<Vec<_>>>()?;\n                let wires = patch.wire_node(&*node.name, op, &inputs)?;\n                for oix in 0..node.outputs.len() {\n                    if oix != ix {\n                        patch.shunt_outside(\n                            model,\n                            OutletId::new(node.id, oix),\n                            wires[oix - (oix > ix) as usize],\n                        )?;\n                    }\n                }\n                return Ok(Some(patch));\n            }\n        }\n        Ok(None)\n    }\n\n    fn declutter_discard_empty_output_mapping_with_body_output(\n        &self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        for (ix, om) in self.output_mapping.iter().enumerate() {\n            if om.last_value_slot.is_none() && om.scan.is_none() && !om.state {\n                let mut new_op = self.clone();\n                new_op.output_mapping.remove(ix);\n                new_op.body.outputs.remove(ix);\n                new_op.decluttered = false;\n                return Ok(Some(TypedModelPatch::replace_single_op(\n                    model,\n                    node,\n                    &node.inputs,\n                    new_op,\n                )?));\n            }\n        }\n        Ok(None)\n    }\n\n    fn declutter_pull_batcheable_input(\n        &self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        'candidate: for (slot, input) in self.input_mapping.iter().enumerate() {\n            if let Some(scan_info) = input.as_scan() {\n                let scan_source = self.body.input_outlets()?[slot];\n                let scan_source_node = self.body.node(scan_source.node);\n                for mut succ in &scan_source_node.outputs[0].successors {\n                    for &succ_input in &self.body.node(succ.node).inputs {\n                        if succ_input != scan_source\n                            && self.body.outlet_fact(succ_input)?.konst.is_none()\n                        {\n                            continue 'candidate;\n                        }\n                    }\n                    if self.body.node(succ.node).outputs.len() != 1 {\n                        continue;\n                    }\n                    let mut new_body = self.body.clone();\n                    // insert propagate axis on einsum\n                    if let Some(einsum) = new_body.node(succ.node).op_as::<EinSum>()\n                        && let Some(patch) = einsum\n                            .propagate_axis(\n                                &new_body,\n                                new_body.node(succ.node),\n                                InOut::In(succ.slot),\n                                scan_info.axis,\n                            )\n                            .context(\"building axis propagating patch\")?\n                    {\n                        patch.apply(&mut new_body)?;\n                        new_body.compute_const_facts()?;\n                        // propagate axis injects new nodes at the end. last successor of input\n                        // in new net will be the new succ\n                        let new_body_scan_input = new_body.input_outlets()?[slot];\n                        succ = new_body.node(new_body_scan_input.node).outputs[0]\n                            .successors\n                            .last()\n                            .unwrap();\n                    }\n\n                    let axes_mapping = {\n                        let (input_facts, output_facts) =\n                            new_body.node_facts(new_body.node(succ.node).id)?;\n                        new_body.node(succ.node).op.axes_mapping(&input_facts, &output_facts)?\n                    };\n                    let axis_info = axes_mapping.axis((InOut::In(succ.slot), scan_info.axis))?;\n                    if let &[axis_after] = &*axis_info.outputs[0] {\n                        let mut outside_patch = TypedModelPatch::new(format!(\n                            \"Outer patch for input extraction of {}\",\n                            new_body.node(succ.node)\n                        ));\n                        let mut patch_inputs = node\n                            .inputs\n                            .iter()\n                            .map(|&i| outside_patch.tap_model(model, i))\n                            .collect::<TractResult<TVec<_>>>()?;\n                        let mut extracted_op_inputs = tvec!();\n                        for (ix, outlet) in new_body.node(succ.node).inputs.iter().enumerate() {\n                            let wire = if ix == succ.slot {\n                                patch_inputs[slot]\n                            } else if let Some(konst) =\n                                new_body.outlet_fact(*outlet)?.konst.as_ref()\n                            {\n                                outside_patch.add_const(\n                                    format!(\n                                        \"{}.extracted.{}\",\n                                        node.name,\n                                        new_body.node(outlet.node).name\n                                    ),\n                                    konst.clone(),\n                                )?\n                            } else {\n                                unreachable!();\n                            };\n                            extracted_op_inputs.push(wire);\n                        }\n                        let new_input_wire = outside_patch.wire_node(\n                            format!(\"{}.extracted.{}\", node.name, new_body.node(succ.node).name),\n                            new_body.node(succ.node).op.clone(),\n                            &extracted_op_inputs,\n                        )?[0];\n                        patch_inputs.push(new_input_wire);\n                        let new_input_outer_fact = outside_patch.outlet_fact(new_input_wire)?;\n                        let mut new_input_inner_fact = new_input_outer_fact.clone();\n                        new_input_inner_fact.shape.set(axis_after, scan_info.chunk.abs().to_dim());\n\n                        let mut new_body = new_body.clone();\n                        let new_source_wire = new_body.add_source(\n                            format!(\"{}.extracted.{}\", node.name, new_body.node(succ.node).name),\n                            new_input_inner_fact,\n                        )?;\n                        let mut inner_patch = TypedModelPatch::new(format!(\n                            \"Inner body patch for extraction of {}\",\n                            new_body.node(succ.node)\n                        ));\n                        let new_source_wire_in_patch =\n                            inner_patch.tap_model(&new_body, new_source_wire)?;\n                        inner_patch\n                            .shunt_outside(\n                                &new_body,\n                                OutletId::new(succ.node, 0),\n                                new_source_wire_in_patch,\n                            )\n                            .with_context(|| \"patching inner model\")?;\n                        inner_patch.apply(&mut new_body)?;\n\n                        let mut input_mapping = self.input_mapping.clone();\n                        input_mapping.push(InputMapping::Scan(ScanInfo {\n                            axis: axis_after,\n                            chunk: scan_info.chunk,\n                        }));\n\n                        let new_op = Self {\n                            input_mapping,\n                            decluttered: false,\n                            body: new_body,\n                            ..self.clone()\n                        };\n                        let output_wires =\n                            outside_patch.wire_node(&*node.name, new_op, &patch_inputs)?;\n                        for w in output_wires {\n                            outside_patch\n                                .shunt_outside(model, OutletId::new(node.id, w.slot), w)\n                                .with_context(|| \"patching outer model\")?;\n                        }\n                        return Ok(Some(outside_patch));\n                    }\n                }\n            }\n        }\n        Ok(None)\n    }\n\n    fn declutter_pull_constant_outputs(\n        &self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        for (model_output_ix, mapping) in self.output_mapping.iter().enumerate() {\n            if let Some(slot) = mapping.last_value_slot\n                && let Some(k) = self.body.output_fact(model_output_ix)?.konst.clone()\n            {\n                let inner_node = self.body.output_outlets()?[model_output_ix].node;\n                let inner_node = self.body.node(inner_node);\n                let mut patch = TypedModelPatch::new(format!(\"Extract const node {inner_node}\"));\n                let cst = patch.add_const(format!(\"{}.{}\", &node.name, &inner_node.name), k)?;\n                patch.shunt_outside(model, OutletId::new(node.id, slot), cst)?;\n                return Ok(Some(patch));\n            }\n        }\n        Ok(None)\n    }\n\n    fn declutter_pull_batcheable_output(\n        &self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        for (mapping_ix, mapping) in self.output_mapping.iter().enumerate() {\n            if let Some((_, scan_info)) = mapping.scan {\n                let emitter_outlet = self.body.output_outlets()?[mapping_ix];\n                if self.body.node(emitter_outlet.node).outputs[emitter_outlet.slot].successors.len()\n                    > 0\n                    || self.body.inputs.contains(&emitter_outlet)\n                    || mapping.state\n                    || mapping.scan.map(|(_slot, i)| i.chunk > 1).unwrap_or(true)\n                {\n                    // continue if both last_value and full values are exported\n                    continue;\n                }\n                let mut new_body = self.body.clone();\n                if let Some(einsum) = new_body.node(emitter_outlet.node).op_as::<EinSum>()\n                    && let Some(patch) = einsum\n                        .propagate_axis(\n                            &new_body,\n                            new_body.node(emitter_outlet.node),\n                            InOut::Out(0),\n                            scan_info.axis,\n                        )\n                        .context(\"building axis propagating patch\")?\n                {\n                    patch.apply(&mut new_body)?;\n                    new_body.prop_consts()?;\n                }\n                let emitter_outlet = new_body.output_outlets()?[mapping_ix];\n                let invariants = {\n                    let (input_facts, output_facts) = new_body.node_facts(emitter_outlet.node)?;\n                    new_body\n                        .node(emitter_outlet.node)\n                        .op\n                        .axes_mapping(&input_facts, &output_facts)?\n                };\n                let axis_tracking =\n                    invariants.axis((InOut::Out(emitter_outlet.slot), scan_info.axis))?;\n                rule_if!(axis_tracking.outputs.iter().all(|o| o.len() == 1));\n                let mut new_output_mapping = self.output_mapping.clone();\n                let mut new_scan_outputs = node.outputs.len();\n                let mut outer_slots = vec![];\n\n                // rewire input of the extracted node through the scan outlet boundary\n                for (input_slot, input) in\n                    new_body.node(emitter_outlet.node).inputs.clone().iter().enumerate()\n                {\n                    if new_body.outputs.iter().all(|o| o != input) {\n                        new_output_mapping.push(OutputMapping::default());\n                        new_body.outputs.push(*input);\n                    }\n                    let body_output_id = new_body.outputs.iter().position(|o| o == input).unwrap();\n                    let mapping = &mut new_output_mapping[body_output_id];\n                    let outer_slot = if new_body.outlet_fact(*input)?.konst.is_some() {\n                        if mapping.last_value_slot.is_none() {\n                            mapping.last_value_slot = Some(new_scan_outputs);\n                            new_scan_outputs += 1;\n                        }\n                        mapping.last_value_slot.unwrap()\n                    } else if let &[axis] = &*axis_tracking.inputs[input_slot] {\n                        if mapping.scan.is_none() {\n                            mapping.scan =\n                                Some((new_scan_outputs, ScanInfo { axis, chunk: scan_info.chunk }));\n                            new_scan_outputs += 1;\n                        }\n                        mapping.scan.unwrap().0\n                    } else {\n                        return Ok(None);\n                    };\n                    outer_slots.push(outer_slot);\n                }\n                let mut outside_patch = TypedModelPatch::new(format!(\n                    \"Outside patch for output extraction of {}\",\n                    new_body.node(emitter_outlet.node)\n                ));\n                let inputs = node\n                    .inputs\n                    .iter()\n                    .map(|&i| outside_patch.tap_model(model, i))\n                    .collect::<TractResult<TVec<_>>>()?;\n                let new_op = Self {\n                    output_mapping: new_output_mapping,\n                    decluttered: false,\n                    body: new_body.clone(), // FIXME maybe remove clone\n                    ..self.clone()\n                };\n                let scan_outputs = outside_patch.wire_node(&node.name, new_op, &inputs)?;\n                let output = mapping.scan.unwrap();\n                let inputs =\n                    outer_slots.iter().map(|slot| scan_outputs[*slot]).collect::<TVec<_>>();\n                let wire = outside_patch.wire_node(\n                    &new_body.node(emitter_outlet.node).name,\n                    new_body.node(emitter_outlet.node).op.clone(),\n                    &inputs,\n                )?[0];\n                outside_patch.shunt_outside(model, OutletId::new(node.id, output.0), wire)?;\n                for output_slot in 0..node.outputs.len() {\n                    if output_slot != output.0 {\n                        outside_patch.shunt_outside(\n                            model,\n                            OutletId::new(node.id, output_slot),\n                            OutletId::new(scan_outputs[0].node, output_slot),\n                        )?;\n                    }\n                }\n                return Ok(Some(outside_patch));\n            }\n        }\n        Ok(None)\n    }\n\n    fn body_bounds(&self) -> TractResult<TVec<TVec<OutletId>>> {\n        let input_state_outlets = self\n            .input_mapping\n            .iter()\n            .zip(self.body.input_outlets()?.iter())\n            .filter(|(m, _)| m.is_state())\n            .map(|(_, o)| o);\n        let output_state_outlets = self\n            .output_mapping\n            .iter()\n            .zip(self.body.output_outlets()?.iter())\n            .filter(|(m, _)| m.state)\n            .map(|(_, o)| o);\n        Ok(input_state_outlets.zip(output_state_outlets).map(|(&i, &o)| tvec!(i, o)).collect())\n    }\n\n    fn body_locked_outlets(&self, node_input_facts: &[&TypedFact]) -> TractResult<TVec<OutletId>> {\n        let input_outlets =\n            self.body.input_outlets()?.iter().enumerate().filter_map(|(slot, o)| {\n                if node_input_facts[slot].konst.is_none() { Some(o) } else { None }\n            });\n        let output_outlets = self\n            .output_mapping\n            .iter()\n            .zip(self.body.output_outlets()?.iter())\n            .filter(|(m, _)| !m.invisible())\n            .map(|(_, o)| o);\n        Ok(input_outlets.chain(output_outlets).cloned().collect())\n    }\n\n    fn try_body_axes_change(\n        &self,\n        change: AxisChange,\n        locked_interface: bool,\n        node_input_facts: &[&TypedFact],\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        self.body.check_consistency()?;\n        let locked_outlets = self.body_locked_outlets(node_input_facts)?;\n        let mut explored: HashSet<AxisChange> = Default::default();\n        rule_if_some!(\n            (body_patch, body_changed_wires) = crate::optim::change_axes::change_axes(\n                &self.body,\n                &change,\n                if locked_interface { &locked_outlets } else { &[] },\n                &self.body_bounds()?,\n                &mut explored,\n            )?\n        );\n        let mut body = self.body.clone();\n        body_patch.apply(&mut body)?;\n        body.compact()?;\n        let mut wire_changes = tvec!();\n        let mut input_mapping: Vec<InputMapping> = self.input_mapping.clone();\n        for (slot, m) in input_mapping.iter_mut().enumerate() {\n            if let Some(change) = body_changed_wires\n                .iter()\n                .find(|(iface, _change)| iface == &InOut::In(slot))\n                .map(|pair| pair.1.clone())\n            {\n                wire_changes.push((InOut::In(slot), change.clone()));\n                if let InputMapping::Scan(info) = m {\n                    rule_if_some!(axis = change.transform_axis(info.axis));\n                    info.axis = axis;\n                };\n            }\n        }\n        let mut output_mapping: Vec<OutputMapping<TDim>> = self.output_mapping.clone();\n        for (ix, m) in output_mapping.iter_mut().enumerate() {\n            if let Some(change) = body_changed_wires\n                .iter()\n                .find(|(iface, _change)| iface == &InOut::Out(ix))\n                .map(|pair| pair.1.clone())\n            {\n                if let Some((slot, info)) = m.scan.as_mut() {\n                    rule_if_some!(new_axis = change.transform_axis(info.axis));\n                    info.axis = new_axis;\n                    wire_changes.push((InOut::Out(*slot), change.clone()));\n                }\n                if let Some(slot) = m.last_value_slot {\n                    wire_changes.push((InOut::Out(slot), change.clone()));\n                }\n            };\n        }\n        body.check_consistency()?;\n        let op = Some(Box::new(Scan {\n            body,\n            input_mapping,\n            output_mapping,\n            decluttered: false,\n            ..self.clone()\n        }) as _);\n        Ok(Some(AxisChangeConsequence { substitute_op: op, wire_changes }))\n    }\n}\n\nimpl Op for Scan {\n    fn name(&self) -> StaticName {\n        \"Scan\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut lines = vec![];\n        for (ix, im) in self.input_mapping.iter().enumerate() {\n            lines.push(format!(\"Model input  #{ix}: {im:?}\"));\n        }\n        for (ix, om) in self.output_mapping.iter().enumerate() {\n            lines.push(format!(\"Model output #{ix}: {om:?}\"));\n        }\n        lines.push(format!(\"skip:{} reset_every_turn:{:?}\", self.skip, self.reset_every_turn));\n        Ok(lines)\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Rounding\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Scan {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        self.to_codegen_op(false)?.state(session, node_id)\n    }\n}\n\nimpl TypedOp for Scan {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        anyhow::ensure!(inputs.len() == self.body.inputs.len());\n        anyhow::ensure!(self.input_mapping.len() == self.body.inputs.len());\n        anyhow::ensure!(\n            self.input_mapping.iter().filter(|m| m.is_state()).count()\n                == self.output_mapping.iter().filter(|m| m.state).count()\n        );\n        for (i, o) in\n            self.input_mapping.iter().enumerate().filter(|(_, m)| m.is_state()).map(|(i, _)| i).zip(\n                self.output_mapping.iter().enumerate().filter(|(_, m)| m.state).map(|(o, _)| o),\n            )\n        {\n            let ifact = self.body.outlet_fact(self.body.inputs[i])?;\n            let ofact = self.body.outlet_fact(self.body.outputs[o])?;\n            anyhow::ensure!(\n                ifact == ofact,\n                \"inconsistent fact: body input {i} is {ifact:?} and body output {o} is {ofact:?}\\n{}\",\n                self.body\n            )\n        }\n        let mut outputs = tvec!();\n        let iters = super::iteration_count(&self.input_mapping, inputs).context(\"No scan input\")?;\n        for (ix, output) in self.output_mapping.iter().enumerate() {\n            let fact = self.body.output_fact(ix)?;\n            if let Some((slot, info)) = output.scan {\n                let mut shape = fact.shape.clone();\n                let scanning_dim =\n                    output.full_dim_hint.clone().unwrap_or(shape[info.axis].clone() * &iters);\n                shape.set(info.axis, scanning_dim);\n                outputs.push((slot, fact.datum_type.fact(shape)));\n            }\n            if let Some(slot) = output.last_value_slot {\n                outputs.push((slot, fact.datum_type.fact(fact.shape.clone())));\n            }\n        }\n        outputs.sort_by_key(|a| a.0);\n        anyhow::ensure!(outputs.iter().enumerate().all(|(ix, (slot, _))| ix == *slot));\n        let outputs: TVec<_> = outputs.into_iter().map(|(_slot, v)| v).collect();\n        Ok(outputs)\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let mut mappings = vec![];\n        let body_invs = self.body.axes_mapping().with_context(|| \"Computing body axes mapping\")?;\n        for body_axis in body_invs.iter_all_axes() {\n            let mut info = Axis::new(body_axis.repr, inputs.len(), outputs.len());\n            info.inputs.clone_from(&body_axis.inputs);\n            for (ix, output_mapping) in self.output_mapping.iter().enumerate() {\n                let mut slots = vec![];\n                if let Some((slot, _scan)) = output_mapping.scan {\n                    slots.push(slot);\n                }\n                if let Some(slot) = output_mapping.last_value_slot {\n                    slots.push(slot);\n                }\n                for slot in slots {\n                    info.outputs[slot].clone_from(&body_axis.outputs[ix]);\n                }\n            }\n            if info.inputs.iter().any(|i| i.len() > 0) || info.outputs.iter().any(|i| i.len() > 0) {\n                mappings.push(info);\n            }\n        }\n        AxesMapping::new(inputs.len(), outputs.len(), mappings)\n    }\n\n    fn suggested_axis_changes(&self) -> TractResult<TVec<(InOut, AxisOp)>> {\n        let mut suggestions = tvec!();\n        for (slot, input) in self.input_mapping.iter().enumerate() {\n            if let InputMapping::Scan(info) = input\n                && info.axis != 0\n            {\n                suggestions.push((InOut::In(slot), AxisOp::Move(info.axis, 0)))\n            }\n        }\n        for output in &self.output_mapping {\n            if let Some((slot, scan)) = output.scan\n                && scan.axis != 0\n            {\n                suggestions.push((InOut::Out(slot), AxisOp::Move(scan.axis, 0)))\n            }\n        }\n        Ok(suggestions)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        trace!(\"Propagating through {node}: {io:?} {change:?}\");\n        let body_leading_outlet = match io {\n            InOut::In(ix) => self.body.input_outlets()?[ix],\n            InOut::Out(slot) => {\n                let output = self\n                    .output_mapping\n                    .iter()\n                    .position(|im| {\n                        im.scan.map(|(slot, _i)| slot) == Some(slot)\n                            || im.last_value_slot == Some(slot)\n                    })\n                    .unwrap();\n                self.body.output_outlets()?[output]\n            }\n        };\n        let axis_change = AxisChange { outlet: body_leading_outlet, op: change.clone() };\n        let node_input_facts = model.node_input_facts(node.id)?;\n        let result = self\n            .try_body_axes_change(axis_change, false, &node_input_facts)\n            .with_context(|| \"Attemping to run change through scan body\".to_string())?;\n        if result.is_some() {\n            trace!(\"{node} accepted axis change\");\n        } else {\n            trace!(\"{node} rejected axis change\");\n        }\n        Ok(result)\n    }\n\n    fn declutter_with_session(\n        &self,\n        session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        macro_rules! pass {\n            ($func:ident) => {\n                if let Some(mut r) = self\n                    .$func(session, model, node)\n                    .with_context(|| format!(\"{}\", stringify!($func)))?\n                {\n                    trace!(stringify!($func));\n                    r.push_context(stringify!($func));\n                    return Ok(Some(r));\n                }\n            };\n        }\n        pass!(declutter_single_loop);\n        pass!(declutter_const_input);\n        pass!(declutter_discard_unused_input);\n        pass!(declutter_discard_useless_outer_output);\n        pass!(declutter_discard_empty_output_mapping_with_body_output);\n        pass!(declutter_body);\n        pass!(declutter_body_axes);\n        pass!(declutter_pull_constant_outputs);\n        pass!(declutter_pull_batcheable_input);\n        pass!(declutter_pull_batcheable_output);\n        Ok(None)\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let inputs = node.inputs.iter().map(|o| mapping[o]).collect::<TVec<_>>();\n        let op = Self {\n            output_mapping: self\n                .output_mapping\n                .iter()\n                .map(|om| om.concretize_dims(values))\n                .collect::<TractResult<Vec<_>>>()?,\n            body: self.body.concretize_dims(values)?,\n            ..self.clone()\n        };\n        target.wire_node(&node.name, op, &inputs)\n    }\n\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        Ok(Some(TypedModelPatch::replace_single_op(\n            model,\n            node,\n            &node.inputs,\n            self.to_codegen_op(true)?,\n        )?))\n    }\n}\n"
  },
  {
    "path": "core/src/ops/scan/mod.rs",
    "content": "use crate::internal::*;\nuse std::fmt;\n\nmod decluttered;\nmod optimized;\n\npub use decluttered::Scan;\npub use optimized::{OptScan, State};\n\n#[derive(Clone, new, Hash, Eq, PartialEq, Copy, Debug)]\npub struct ScanInfo {\n    pub axis: usize,\n    pub chunk: isize,\n}\n\n#[derive(Clone, new, Hash, Debug, PartialEq, Eq)]\npub enum InputMapping {\n    Full,\n    State,\n    Scan(ScanInfo),\n}\n\nimpl InputMapping {\n    pub fn is_state(&self) -> bool {\n        matches!(self, InputMapping::State)\n    }\n\n    pub fn is_scan(&self) -> bool {\n        self.as_scan().is_some()\n    }\n\n    pub fn as_scan(&self) -> Option<&ScanInfo> {\n        match self {\n            InputMapping::Scan(s) => Some(s),\n            _ => None,\n        }\n    }\n}\n\n#[derive(Clone, new, Hash, Default, PartialEq, Eq)]\npub struct OutputMapping<F: Clone> {\n    pub scan: Option<(usize, ScanInfo)>,\n    pub full_dim_hint: Option<F>,\n    pub last_value_slot: Option<usize>,\n    pub state: bool,\n}\n\nimpl<F: Clone> OutputMapping<F> {\n    pub fn invisible(&self) -> bool {\n        self.scan.is_none() && self.last_value_slot.is_none()\n    }\n}\n\nimpl<F: Clone + DimLike> OutputMapping<F> {\n    pub fn concretize_dims(&self, values: &SymbolValues) -> TractResult<OutputMapping<F>> {\n        Ok(Self {\n            full_dim_hint: self.full_dim_hint.as_ref().map(|h| h.eval(values)),\n            ..self.clone()\n        })\n    }\n}\n\nimpl<F: Clone + fmt::Display> fmt::Debug for OutputMapping<F> {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        if self.state {\n            write!(fmt, \"State. \")?;\n        }\n        if let Some(last_value_slot) = self.last_value_slot {\n            write!(fmt, \"Last value to outlet {last_value_slot}. \")?;\n        }\n        if let Some((slot, info)) = self.scan {\n            write!(fmt, \"Full value to outlet {} (axis: {}). \", slot, info.axis)?;\n        }\n        if let Some(full_dim_hint) = &self.full_dim_hint {\n            write!(fmt, \"Full len {full_dim_hint}. \")?;\n        }\n        Ok(())\n    }\n}\n\npub fn iteration_count(input_mapping: &[InputMapping], inputs: &[&TypedFact]) -> Option<TDim> {\n    let (slot, info) = input_mapping\n        .iter()\n        .enumerate()\n        .find_map(|(slot, im)| im.as_scan().map(|scan| (slot, scan)))?;\n    let outside_dim = inputs[slot].shape[info.axis].clone();\n    Some(outside_dim.div_ceil(info.chunk.unsigned_abs() as u64))\n}\n"
  },
  {
    "path": "core/src/ops/scan/optimized.rs",
    "content": "use crate::ops::OpStateFreeze;\n\nuse super::*;\nuse tract_data::internal::*;\n\n#[derive(Debug, Clone, new)]\npub struct ScanOpParams {\n    pub skip: usize,\n    pub reset_every_turn: bool,\n    pub plan: Arc<TypedSimplePlan>,\n    pub input_mapping: Vec<InputMapping>,\n    pub output_mapping: Vec<OutputMapping<TDim>>,\n}\n\n#[derive(Debug, Clone, new)]\npub struct OptScan(Arc<ScanOpParams>);\n\nimpl std::ops::Deref for OptScan {\n    type Target = ScanOpParams;\n    fn deref(&self) -> &ScanOpParams {\n        &self.0\n    }\n}\n\nimpl PartialEq for OptScan {\n    fn eq(&self, _other: &Self) -> bool {\n        false\n    }\n}\nimpl Eq for OptScan {}\n\nimpl OptScan {\n    pub fn iteration_count(&self, inputs: &[&TypedFact]) -> Option<TDim> {\n        super::iteration_count(&self.input_mapping, inputs)\n    }\n}\n\nimpl Op for OptScan {\n    fn name(&self) -> StaticName {\n        \"Scan\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut lines = vec![];\n        for (ix, im) in self.input_mapping.iter().enumerate() {\n            lines.push(format!(\"Model input  #{ix}: {im:?}\"));\n        }\n        for (ix, om) in self.output_mapping.iter().enumerate() {\n            lines.push(format!(\"Model output #{ix}: {om:?}\"));\n        }\n        Ok(lines)\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for OptScan {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(State {\n            position: 0,\n            hidden_state: tvec!(),\n            model_state: self.plan.spawn()?,\n            op: Arc::clone(&self.0),\n        })))\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct State {\n    op: Arc<ScanOpParams>,\n    position: usize,\n    hidden_state: TVec<TValue>,\n    pub model_state: TypedSimpleState,\n}\n\n#[derive(Debug, Clone)]\nstruct FrozenState {\n    op: Arc<ScanOpParams>,\n    position: usize,\n    hidden_state: TVec<Tensor>,\n    model_state: TypedFrozenSimpleState,\n}\n\nimpl OpStateFreeze for State {\n    fn freeze(&self) -> Box<dyn FrozenOpState> {\n        Box::new(FrozenState {\n            op: self.op.clone(),\n            position: self.position,\n            hidden_state: self.hidden_state.iter().map(|t| t.clone().into_tensor()).collect(),\n            model_state: self.model_state.freeze(),\n        })\n    }\n}\n\nimpl FrozenOpState for FrozenState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(State {\n            op: self.op.clone(),\n            position: self.position,\n            hidden_state: self.hidden_state.iter().map(|t| t.clone().into_tvalue()).collect(),\n            model_state: self.model_state.unfreeze(),\n        })\n    }\n}\n\nimpl State {\n    pub fn iteration_count(&self, inputs: &TVec<TValue>) -> usize {\n        let (slot, info) = self\n            .op\n            .input_mapping\n            .iter()\n            .enumerate()\n            .find_map(|(ix, it)| it.as_scan().map(|scan| (ix, scan)))\n            .unwrap();\n        inputs[slot].shape()[info.axis].divceil(info.chunk.unsigned_abs())\n    }\n\n    pub(super) fn slice_input(\n        input: &Tensor,\n        axis: usize,\n        chunk_ix: usize,\n        chunk_dim: isize,\n    ) -> TractResult<Tensor> {\n        unsafe {\n            let full_len = input.shape()[axis];\n            let mut shape: TVec<usize> = input.shape().into();\n            shape[axis] = chunk_dim.unsigned_abs();\n            let mut t = Tensor::uninitialized_dt(input.datum_type(), &shape)?;\n            if chunk_dim < 0 {\n                let chunk_dim = (-chunk_dim) as usize;\n                for i in 0..chunk_dim {\n                    if chunk_dim * chunk_ix + i < full_len {\n                        let dst_ix = chunk_dim - i - 1;\n                        let src_ix = full_len - 1 - (chunk_ix * chunk_dim + i);\n                        t.assign_slice_unchecked(dst_ix..=dst_ix, input, src_ix..=src_ix, axis);\n                    }\n                }\n            } else if (chunk_ix + 1) * chunk_dim as usize > full_len {\n                let chunk_dim = chunk_dim as usize;\n                let remain = full_len - chunk_ix * chunk_dim;\n                let mut shape: TVec<usize> = input.shape().into();\n                shape[axis] = chunk_dim;\n                t.assign_slice_unchecked(..remain, input, chunk_ix * chunk_dim.., axis);\n            } else {\n                let start = chunk_dim as usize * chunk_ix;\n                let end = start + chunk_dim as usize;\n                t.assign_slice_unchecked(.., input, start..end, axis);\n            }\n            Ok(t)\n        }\n    }\n\n    pub(super) fn assign_output(\n        output: &mut Tensor,\n        axis: usize,\n        element_value: &Tensor,\n        i: usize,\n        backward: bool,\n    ) {\n        let full_len = output.shape()[axis];\n        let offset = if backward {\n            full_len - 1 - i * element_value.shape()[axis]\n        } else {\n            i * element_value.shape()[axis]\n        };\n        let count = element_value.shape()[axis].min(output.shape()[axis] - offset);\n        unsafe {\n            output.assign_slice_unchecked(offset..offset + count, element_value, ..count, axis)\n        };\n    }\n}\n\nimpl OpState for State {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        _op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let iters = self.iteration_count(&inputs);\n\n        let &mut State { ref op, ref mut hidden_state, ref mut position, ref mut model_state } =\n            self;\n\n        // initialize state at first pass, or when forced\n        if op.reset_every_turn {\n            hidden_state.clear()\n        }\n        if hidden_state.len() == 0 {\n            for (slot, input) in op.input_mapping.iter().enumerate() {\n                if input.is_state() {\n                    hidden_state.push(inputs[slot].clone());\n                }\n            }\n        }\n\n        let mut outputs = tvec!();\n        for (ix, output) in op.output_mapping.iter().enumerate() {\n            if let Some((slot, info)) = output.scan {\n                let fact = op.plan.model().output_fact(ix)?;\n                let mut shape: TVec<usize> =\n                    fact.shape.eval_to_usize(&session.resolved_symbols)?.into_owned();\n                let scanning_dim = output\n                    .full_dim_hint\n                    .as_ref()\n                    .and_then(|d| d.to_usize().ok())\n                    .unwrap_or(shape[info.axis] * iters);\n                shape[info.axis] = scanning_dim;\n                let t = unsafe { Tensor::uninitialized_dt(fact.datum_type, &shape)? };\n                outputs.push((slot, t));\n            }\n            if let Some(slot) = output.last_value_slot {\n                outputs.push((slot, Tensor::default()));\n            }\n        }\n        outputs.sort_by_key(|a| a.0);\n        let mut outputs: TVec<Tensor> = outputs.into_iter().map(|(_slot, v)| v).collect();\n\n        for i in 0..iters {\n            *position += 1;\n            if *position <= op.skip {\n                continue;\n            }\n            hidden_state.reverse();\n\n            let iter_inputs: TVec<TValue> = op\n                .input_mapping\n                .iter()\n                .enumerate()\n                .map(|(slot, m)| {\n                    Ok(match m {\n                        InputMapping::State => Some(hidden_state.pop().unwrap()),\n                        InputMapping::Scan(info) => Some(\n                            Self::slice_input(&inputs[slot], info.axis, i, info.chunk)?\n                                .into_tvalue(),\n                        ),\n                        InputMapping::Full => Some(inputs[slot].clone()),\n                    })\n                })\n                .collect::<TractResult<Vec<_>>>()?\n                .into_iter()\n                .flatten()\n                .collect();\n\n            trace!(\"iter_inputs #{i}: {iter_inputs:?}\");\n            let iter_outputs =\n                model_state.run(iter_inputs).with_context(|| \"Evaluating inner body\")?;\n            trace!(\"iter_outputs #{i}: {iter_outputs:?}\");\n\n            for (v, mapping) in iter_outputs.into_iter().zip(&op.output_mapping) {\n                if let Some((slot, info)) = mapping.scan {\n                    Self::assign_output(&mut outputs[slot], info.axis, &v, i, info.chunk < 0);\n                }\n                if i == iters - 1\n                    && let Some(slot) = mapping.last_value_slot\n                {\n                    outputs[slot] = v.clone().into_tensor();\n                }\n                if mapping.state {\n                    hidden_state.push(v);\n                }\n            }\n        }\n\n        Ok(outputs.into_iter().map(|t| t.into_tvalue()).collect())\n    }\n}\n\nimpl TypedOp for OptScan {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut outputs = tvec!();\n        let iters = super::iteration_count(&self.input_mapping, inputs).unwrap();\n        for (ix, output) in self.output_mapping.iter().enumerate() {\n            let fact = self.plan.model().output_fact(ix)?;\n            if let Some(slot) = output.last_value_slot {\n                outputs.push((slot, fact.datum_type.fact(fact.shape.clone())));\n            }\n            if let Some((slot, info)) = output.scan {\n                let mut shape = fact.shape.clone();\n                let scanning_dim =\n                    output.full_dim_hint.clone().unwrap_or(shape[info.axis].clone() * &iters);\n                shape.set(info.axis, scanning_dim);\n                outputs.push((slot, fact.datum_type.fact(shape)));\n            }\n        }\n        outputs.sort_by_key(|a| a.0);\n        let outputs: TVec<_> = outputs.into_iter().map(|(_slot, v)| v).collect();\n        Ok(outputs)\n    }\n\n    fn nested_model_multipliers(&self, inputs: &[&TypedFact]) -> Vec<(StaticName, TDim)> {\n        vec![(\n            \"loop\".into(),\n            super::iteration_count(&self.input_mapping, inputs).unwrap_or_else(|| 1.to_dim()),\n        )]\n    }\n}\n"
  },
  {
    "path": "core/src/ops/source.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, new)]\npub struct SourceState(pub usize);\ntrivial_op_state_freeze!(SourceState);\n\nimpl OpState for SourceState {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        _op: &dyn Op,\n        _inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        Ok(tvec!(\n            session\n                .values\n                .get(self.0)\n                .and_then(|v| v.as_ref())\n                .and_then(|vs| vs.first().cloned())\n                .with_context(|| format!(\"Input for node {} is missing\", self.0))?\n        ))\n    }\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct TypedSource {\n    pub fact: TypedFact,\n}\n\nimpl Op for TypedSource {\n    fn name(&self) -> StaticName {\n        \"Source\".into()\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for TypedSource {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(&self, _session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(SourceState(node_id))))\n    }\n}\n\nimpl TypedOp for TypedSource {\n    fn output_facts(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(self.fact.clone()))\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        let mut fact = self.fact.clone();\n        change.change_shape(&mut fact.shape, false)?;\n        Ok(Some(AxisChangeConsequence::new(\n            model,\n            node,\n            Some(Box::new(TypedSource::new(fact))),\n            change,\n        )))\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        _mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let shape: TVec<_> = self.fact.shape.iter().map(|d| d.eval(values)).collect();\n        target.wire_node(&node.name, Self { fact: self.fact.datum_type.fact(&*shape) }, &[])\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "core/src/ops/submodel.rs",
    "content": "use std::fmt::Debug;\n\nuse tract_downcast_rs::Downcast;\n\nuse crate::{internal::*, ops::OpStateFreeze};\n\n#[derive(Debug, Clone)]\npub struct SubmodelOp {\n    pub model: Box<dyn InnerModel>,\n    label: String,\n    decluttered: bool,\n    codegen: bool,\n}\n\nimpl PartialEq for SubmodelOp {\n    fn eq(&self, _other: &Self) -> bool {\n        false\n    }\n}\nimpl Eq for SubmodelOp {}\n\nimpl SubmodelOp {\n    pub fn new(model: Box<dyn InnerModel>, label: &str) -> TractResult<Self> {\n        Ok(Self { model, label: label.to_string(), decluttered: false, codegen: false })\n    }\n\n    pub fn iteration_count(&self, _inputs: &[&TypedFact]) -> Option<TDim> {\n        None\n    }\n\n    pub fn model(&self) -> &TypedModel {\n        self.model.as_typed()\n    }\n\n    pub fn label(&self) -> &str {\n        self.label.as_str()\n    }\n}\n\nimpl Op for SubmodelOp {\n    fn name(&self) -> StaticName {\n        \"SubmodelOp\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for SubmodelOp {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        self.model.state(session, node_id)\n    }\n}\n\nimpl TypedOp for SubmodelOp {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let facts = self.model.output_facts(inputs)?;\n        Ok(facts)\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if !self.decluttered {\n            let mut new = self.clone();\n            new.model.declutter()?;\n            new.decluttered = true;\n            Ok(Some(TypedModelPatch::replace_single_op(model, node, &node.inputs, new)?))\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if !self.codegen {\n            let mut new = self.clone();\n            new.model.codegen()?;\n            new.codegen = true;\n            Ok(Some(TypedModelPatch::replace_single_op(model, node, &node.inputs, new)?))\n        } else {\n            Ok(None)\n        }\n    }\n\n    as_op!();\n}\n\npub trait InnerModel: Debug + dyn_clone::DynClone + Downcast + Sync + Send + 'static {\n    #[allow(unused_variables)]\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>>;\n    fn is_stateless(&self) -> bool;\n\n    #[allow(unused_variables)]\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(None)\n    }\n\n    #[allow(unused_variables)]\n    fn declutter(&mut self) -> TractResult<()>;\n\n    fn codegen(&mut self) -> TractResult<()>;\n\n    fn as_typed(&self) -> &TypedModel;\n}\n\ndyn_clone::clone_trait_object!(InnerModel);\ndowncast_rs::impl_downcast!(InnerModel);\n\nimpl InnerModel for TypedModel {\n    fn output_facts(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let facts = self\n            .output_outlets()?\n            .iter()\n            .map(|outlet| self.outlet_fact(*outlet).cloned())\n            .collect::<TractResult<TVec<_>>>()?;\n        Ok(facts)\n    }\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    #[allow(unused_variables)]\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        let plan = self.clone().into_runnable()?;\n        let state = plan.spawn()?;\n        Ok(Some(Box::new(state)))\n    }\n\n    #[allow(unused_variables)]\n    fn declutter(&mut self) -> TractResult<()> {\n        self.declutter()\n    }\n\n    fn codegen(&mut self) -> TractResult<()> {\n        self.optimize()\n    }\n\n    fn as_typed(&self) -> &TypedModel {\n        self\n    }\n}\n\npub type TypedModelOpState = TypedSimpleState;\n\nimpl OpState for TypedModelOpState {\n    fn eval(\n        &mut self,\n        _session: &mut TurnState,\n        _op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let inference_out = self.run(inputs)?;\n        Ok(inference_out)\n    }\n}\n\npub type FrozenSubmodelOpState = TypedFrozenSimpleState;\n\nimpl FrozenOpState for FrozenSubmodelOpState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(self.unfreeze())\n    }\n}\n\nimpl OpStateFreeze for TypedModelOpState {\n    fn freeze(&self) -> Box<dyn FrozenOpState> {\n        Box::new(self.freeze())\n    }\n}\n"
  },
  {
    "path": "core/src/ops/unimpl.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct UnimplementedOp {\n    outputs: usize,\n    name: String,\n    message: String,\n}\n\nimpl UnimplementedOp {\n    pub fn new(outputs: usize, name: impl AsRef<str>, message: impl AsRef<str>) -> UnimplementedOp {\n        UnimplementedOp {\n            outputs,\n            name: name.as_ref().to_string(),\n            message: message.as_ref().to_string(),\n        }\n    }\n}\n\nimpl Op for UnimplementedOp {\n    fn name(&self) -> StaticName {\n        format!(\"Unimplemented({})\", self.name).into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for UnimplementedOp {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n}\n"
  },
  {
    "path": "core/src/optim/change_axes.rs",
    "content": "use super::OptimizerSession;\nuse super::TypedPass;\nuse crate::internal::*;\nuse crate::model::*;\nuse crate::ops::dummy::Dummy;\nuse crate::ops::einsum::EinSum;\nuse crate::ops::konst::Const;\nuse std::collections::HashSet;\nuse std::collections::hash_map::Entry;\nuse std::fmt::Debug;\n\nuse crate::ops::change_axes::*;\n\n#[derive(Clone, Default)]\npub struct ChangeAxes(HashSet<crate::ops::change_axes::AxisChange>, usize);\n\nimpl Debug for ChangeAxes {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"ChangeAxes\")\n    }\n}\n\nimpl TypedPass for ChangeAxes {\n    fn reset(&mut self) -> TractResult<()> {\n        self.0.clear();\n        self.1 = 0;\n        Ok(())\n    }\n    fn next(\n        &mut self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let mut explored: HashSet<AxisChange> = Default::default();\n        let mut interfaces = model.output_outlets()?.to_vec();\n        interfaces.extend(model.input_outlets()?.iter());\n        for node in &model.nodes[self.1..] {\n            if node.op_is::<Dummy>() {\n                continue;\n            }\n            for suggestion in node.op.suggested_axis_changes()? {\n                let outlet = suggestion.0.as_outlet(node);\n                let change = AxisChange { outlet, op: suggestion.1 };\n                if self.0.insert(change.clone())\n                    && let Some((patch, _)) =\n                        change_axes(model, &change, &interfaces, &[], &mut explored)\n                            .with_context(|| format!(\"Making patch for {change:?} from {node}\"))?\n                {\n                    self.1 = node.id;\n                    return Ok(Some(patch));\n                }\n            }\n            for (slot, fact) in node.outputs.iter().enumerate() {\n                for (ix, dim) in fact.fact.shape.iter().enumerate() {\n                    if dim.is_one() {\n                        let change =\n                            AxisChange { outlet: OutletId::new(node.id, slot), op: AxisOp::Rm(ix) };\n                        if self.0.insert(change.clone())\n                            && let Some((patch, _)) =\n                                change_axes(model, &change, &interfaces, &[], &mut explored)\n                                    .with_context(|| {\n                                        format!(\"Making patch for {change:?} from {node}\")\n                                    })?\n                        {\n                            self.1 = node.id;\n                            return Ok(Some(patch));\n                        }\n                    }\n                }\n            }\n        }\n        Ok(None)\n    }\n}\n\n#[allow(clippy::type_complexity)]\npub fn change_axes(\n    model: &TypedModel,\n    change: &AxisChange,\n    locked: &[OutletId],\n    bounds: &[TVec<OutletId>],\n    explored: &mut HashSet<AxisChange>,\n) -> TractResult<Option<(TypedModelPatch, TVec<(InOut, AxisOp)>)>> {\n    if explored.contains(change) {\n        debug!(\"  Not considering change because deja vu {change:?}\");\n        return Ok(None);\n    }\n    if model\n        .node(change.outlet.node)\n        .op_as::<Const>()\n        .is_some_and(|c| c.val().volume() == 1 && c.val().is_plain())\n    {\n        debug!(\"  Not considering change from const {change:?}\");\n        return Ok(None);\n    }\n    debug!(\"  Considering change {change:?}\");\n    let mut todo_changes = vec![(change.clone(), None)];\n    let mut changed_wires: HashMap<TVec<OutletId>, AxisOp> = HashMap::new();\n    let bound_outlets = |o: OutletId| -> TVec<OutletId> {\n        bounds.iter().find(|b| b.contains(&o)).cloned().unwrap_or_else(|| tvec!(o))\n    };\n    changed_wires.insert(bound_outlets(change.outlet), change.op.clone());\n    let mut changed_ops: HashMap<usize, Box<dyn TypedOp>> = HashMap::new();\n    let mut rewired_scalar_input: HashMap<InletId, (OutletId, AxisOp)> = Default::default();\n    while let Some((change, emitter)) = todo_changes.pop() {\n        rule_if!(explored.insert(change.clone()));\n        let outlet_group = bound_outlets(change.outlet);\n        for &outlet in &outlet_group {\n            if locked.contains(&outlet) {\n                debug!(\"    Change {change:?} blocked by locked interface {outlet:?}\");\n                return Ok(None);\n            }\n            let mut interfaces: Vec<(usize, InOut)> = vec![(outlet.node, InOut::Out(outlet.slot))];\n            for inlet in model.outlet_successors(outlet) {\n                interfaces.push((inlet.node, InOut::In(inlet.slot)));\n            }\n            for (node_id, io) in interfaces {\n                if Some(node_id) == emitter {\n                    continue;\n                }\n                let node = model.node(node_id);\n                // if this is a revisit...\n                let op = if let Some(op) = changed_ops.get(&node_id) {\n                    trace!(\"  Change {:?} revisiting {}\", change, model.node(node_id));\n                    if op.is::<EinSum>() {\n                        // FIXME Einsum can swallow any combination of axis change on all interfaces\n                        op\n                    } else {\n                        debug!(\"  Change {:?} blocked: revisiting {}\", change, model.node(node_id));\n                        return Ok(None);\n                    }\n                } else {\n                    &node.op\n                };\n                let more = op\n                    .change_axes(model, node, io, &change.op)\n                    .with_context(|| format!(\"Propagating {change:?} to node {node}\"))?;\n                if more.is_none() {\n                    debug!(\"    Propagation of {change:?} blocked by {node}\");\n                    return Ok(None);\n                }\n                let AxisChangeConsequence { substitute_op, wire_changes } = more.unwrap();\n                trace!(\"    Change {:?} enters {} from {:?}\", change.op, node, io);\n                trace!(\"       propagates as {wire_changes:?}\");\n                if let Some(op) = substitute_op {\n                    trace!(\"       replace op by {op:?}\");\n                    changed_ops.insert(node.id, op);\n                }\n                for (wire, op) in wire_changes.into_iter() {\n                    let outlet = wire.as_outlet(node);\n                    // stop upstram propagation to a scalar constant: we will clone it and alter it\n                    // at patch generation time\n                    if let InOut::In(inlet) = wire\n                        && model\n                            .node(outlet.node)\n                            .op_as::<Const>()\n                            .is_some_and(|k| k.val().volume() == 1)\n                    {\n                        rewired_scalar_input.insert(InletId::new(node.id, inlet), (outlet, op));\n                        continue;\n                    }\n                    let outlet_group = bound_outlets(wire.as_outlet(node));\n                    match changed_wires.entry(outlet_group.clone()) {\n                        Entry::Vacant(entry) => {\n                            trace!(\"         {wire:?} {op:?} change on {outlet_group:?} is new\");\n                            entry.insert(op.clone());\n                            todo_changes\n                                .push((AxisChange { outlet: outlet_group[0], op }, Some(node_id)));\n                        }\n                        Entry::Occupied(previous) => {\n                            if *previous.get() == op {\n                                trace!(\n                                    \"         {wire:?} {op:?} change on {outlet_group:?} already done\"\n                                );\n                            } else {\n                                debug!(\n                                    \"         {wire:?} {op:?} change on {outlet_group:?} conflicting with {previous:?}. Blocked.\"\n                                );\n                                return Ok(None);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n    debug!(\"Translating {change:?} to patch\");\n    let mut patch = TypedModelPatch::new(format!(\"{change:?}\"));\n    let mut replaced_wires: HashMap<OutletId, OutletId> = HashMap::default();\n    let nodes_to_replace = changed_wires\n        .keys()\n        .flat_map(|outlets| outlets.iter().map(|o| o.node))\n        .chain(changed_ops.keys().copied())\n        .collect::<std::collections::HashSet<usize>>();\n    for node_id in model.eval_order()? {\n        let node = model.node(node_id);\n        if nodes_to_replace.contains(&node_id) {\n            let mut inputs = tvec!();\n            for (slot, orig) in node.inputs.iter().enumerate() {\n                let tgt = if let Some((outlet, alteration)) =\n                    rewired_scalar_input.get(&InletId::new(node_id, slot))\n                {\n                    let const_node = model.node(outlet.node);\n                    let mut value =\n                        const_node.op_as::<Const>().unwrap().val().clone().into_tensor();\n                    alteration.change_tensor(&mut value, false)?;\n                    let name = model.unique_name(&const_node.name);\n                    patch.add_const(name, value)?\n                } else {\n                    *replaced_wires\n                        .entry(*orig)\n                        .or_insert_with(|| patch.tap_model(model, *orig).unwrap())\n                };\n                inputs.push(tgt);\n            }\n            let op: Box<dyn TypedOp> =\n                changed_ops.get(&node_id).cloned().unwrap_or_else(|| node.op.clone());\n            let new_wires = patch\n                .wire_node(&node.name, op.clone(), &inputs)\n                .with_context(|| format!(\"wriring changed_op {op:?}\"))?;\n            if new_wires.len() == 1\n                && patch.node(new_wires[0].node).op_is::<crate::ops::source::TypedSource>()\n            {\n                patch.inputs.insert(new_wires[0].node, node_id);\n            }\n            for (ix, w) in new_wires.iter().enumerate() {\n                replaced_wires.insert((node_id, ix).into(), *w);\n            }\n        } else {\n            for orig in &node.inputs {\n                if let Some(replacement) = replaced_wires.get(orig) {\n                    patch.shunt_outside(model, *orig, *replacement)?;\n                }\n            }\n        }\n    }\n    for output in model.output_outlets()? {\n        if let Some(replacement) = replaced_wires.get(output) {\n            unsafe {\n                patch.shunt_outside_unchecked(*output, *replacement)?;\n            }\n        }\n    }\n    let mut interface_change = tvec!();\n    for (ix, input) in model.input_outlets()?.iter().enumerate() {\n        if let Some(change) = changed_wires.get(&bound_outlets(*input)) {\n            interface_change.push((InOut::In(ix), change.clone()));\n        }\n    }\n    for (ix, output) in model.output_outlets()?.iter().enumerate() {\n        if let Some(change) = changed_wires.get(&bound_outlets(*output)) {\n            interface_change.push((InOut::Out(ix), change.clone()));\n        }\n    }\n    debug!(\"Patch ready for {change:?}\");\n    Ok(Some((patch, interface_change)))\n}\n"
  },
  {
    "path": "core/src/optim/concat_then_einsum.rs",
    "content": "use crate::internal::*;\n\nuse crate::ops::array::{Slice, TypedConcat};\nuse crate::ops::einsum::EinSum;\nuse crate::ops::math::add;\nuse crate::optim::OptimizerSession;\nuse tract_itertools::Itertools;\n\n#[derive(Clone, Debug, Default)]\npub struct ConcatThenEinsum(Option<InletId>);\n\nimpl super::TypedPass for ConcatThenEinsum {\n    fn reset(&mut self) -> TractResult<()> {\n        self.0 = Default::default();\n        Ok(())\n    }\n\n    #[allow(clippy::comparison_chain)]\n    fn next(\n        &mut self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        'outer: loop {\n            self.0 = if let Some(previous) = self.0 {\n                rule_if_some!(next = next_inlet(model, &previous));\n                Some(next)\n            } else if let Some(first) =\n                model.nodes.iter().find(|n| n.inputs.len() > 0).map(|n| InletId::new(n.id, 0))\n            {\n                Some(first)\n            } else {\n                return Ok(None);\n            };\n            let inlet = self.0.unwrap();\n            let outlet = model.nodes[inlet.node].inputs[inlet.slot];\n            let concat_node = model.node(outlet.node);\n            if model.outputs.contains(&concat_node.id.into()) {\n                continue;\n            }\n            let einsum_node = &model.nodes[inlet.node];\n            if einsum_node.inputs.len() != 2 {\n                // should we try and apply this on quantized einsums ?\n                continue;\n            }\n            if let (Some(concat), Some(einsum)) =\n                (concat_node.op_as::<TypedConcat>(), einsum_node.op_as::<EinSum>())\n            {\n                let offsets = concat.offsets(&model.node_input_facts(concat_node.id)?)?;\n                let axis_info = einsum.axes.axis((InOut::In(inlet.slot), concat.axis))?;\n                // only split if axis is a summing axis\n                if axis_info.outputs[0].len() > 0 {\n                    continue;\n                }\n                let mut patch = TypedModelPatch::new(format!(\n                    \"Split Einsum for concat on axis {}\",\n                    axis_info.repr\n                ));\n                // inputs[einsum_input_slot][concated_slice]. concated_slice = 0 for broadcast\n                let mut inputs: TVec<TVec<OutletId>> = tvec!();\n                for (slot, input) in einsum_node.inputs.iter().enumerate() {\n                    let tap = patch.tap_model(model, *input)?;\n                    if axis_info.inputs[slot].len() > 1 {\n                        continue 'outer;\n                    } else if axis_info.inputs[slot].len() == 1 {\n                        let mut slices = tvec!();\n                        for (start, end) in offsets.iter().cloned().tuple_windows() {\n                            let wire = patch.wire_node(\n                                format!(\n                                    \"{}.concat-einsum-slice-{}.{}.{}..{}\",\n                                    einsum_node.name, axis_info.repr, slot, start, end\n                                ),\n                                Slice { axis: axis_info.inputs[slot][0], start, end },\n                                &[tap],\n                            )?;\n                            slices.push(wire[0]);\n                        }\n                        inputs.push(slices);\n                    } else {\n                        inputs.push(tvec!(tap)); // broadcast\n                    };\n                }\n                let mut einsums = tvec!();\n                for (ix, (start, end)) in offsets.iter().tuple_windows().enumerate() {\n                    let mut einsum_inputs = tvec!();\n                    for input_ix in 0..einsum_node.inputs.len() {\n                        einsum_inputs\n                            .push(inputs[input_ix].get(ix).cloned().unwrap_or(inputs[input_ix][0]));\n                    }\n                    let einsum = patch.wire_node(\n                        format!(\n                            \"{}.concat-einsum-{}.{}..{}\",\n                            einsum_node.name, axis_info.repr, start, end\n                        ),\n                        einsum.clone(),\n                        &einsum_inputs,\n                    )?[0];\n                    einsums.push(einsum);\n                }\n                let wire = if let Some(axis) = axis_info.outputs[0].first().cloned() {\n                    patch.wire_node(\n                        format!(\"{}.concat-einsum-{}.concat\", einsum_node.name, axis_info.repr),\n                        TypedConcat { axis },\n                        &einsums,\n                    )?[0]\n                } else {\n                    let mut wire = einsums[0];\n                    for ix in 1..einsums.len() {\n                        wire = patch.wire_node(\n                            format!(\n                                \"{}.concat-einsum-{}.add-{}\",\n                                einsum_node.name, axis_info.repr, ix\n                            ),\n                            add(),\n                            &[wire, einsums[ix]],\n                        )?[0]\n                    }\n                    wire\n                };\n                patch.shunt_outside(model, einsum_node.id.into(), wire)?;\n                return Ok(Some(patch));\n            }\n        }\n    }\n}\n\nfn next_inlet(model: &TypedModel, inlet: &InletId) -> Option<InletId> {\n    if inlet.slot + 1 < model.nodes[inlet.node].inputs.len() {\n        Some(InletId::new(inlet.node, inlet.slot + 1))\n    } else {\n        model.nodes[inlet.node + 1..]\n            .iter()\n            .find(|n| n.inputs.len() > 0)\n            .map(|n| InletId::new(n.id, 0))\n    }\n}\n"
  },
  {
    "path": "core/src/optim/mod.rs",
    "content": "use crate::internal::*;\nuse std::collections::HashSet;\nuse std::fmt::Debug;\nuse tract_itertools::Itertools;\n\npub mod change_axes;\nmod concat_then_einsum;\nmod op_optim;\nmod prop_const;\npub mod propagate_roi;\nmod push_split_down;\nmod slice;\nmod uniform_mask;\n\nuse self::change_axes::ChangeAxes;\nuse self::prop_const::PropConst;\nuse self::propagate_roi::PropagateRoi;\nuse self::push_split_down::PushSplitDown;\nuse self::slice::PushSliceUp;\nuse self::uniform_mask::FoldUniformMask;\nuse op_optim::OpOptim;\n\npub trait TypedPass: Debug + Send + Sync + dyn_clone::DynClone {\n    fn reset(&mut self) -> TractResult<()>;\n    fn next(\n        &mut self,\n        session: &mut OptimizerSession,\n        model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>>;\n    /// In-place model mutation hook. Returns true if the model was changed.\n    fn run_direct(&mut self, _model: &mut TypedModel) -> TractResult<bool> {\n        Ok(false)\n    }\n}\n\ndyn_clone::clone_trait_object!(TypedPass);\n\n#[derive(Debug)]\npub struct Optimizer {\n    pub passes: Vec<Box<dyn TypedPass>>,\n    pub steps: Option<usize>,\n}\n\nimpl Optimizer {\n    fn passes(passes: Vec<Box<dyn TypedPass>>) -> Optimizer {\n        Optimizer { passes, steps: None }\n    }\n\n    pub fn add_pass(&mut self, idx: usize, pass: Box<dyn TypedPass>) {\n        let num_pass = self.passes.len();\n        if idx > num_pass {\n            log::warn!(\n                \"Cannot add new pass {pass:?} at index {idx}. Optimizer currently as {num_pass} passes, pass will be added as the last pass.\"\n            );\n            self.passes.push(pass);\n        } else {\n            self.passes.insert(idx, pass);\n        }\n    }\n\n    pub fn stopping_at(self, steps: usize) -> Optimizer {\n        Optimizer { steps: Some(steps), ..self }\n    }\n\n    pub fn prop_consts() -> Optimizer {\n        Optimizer::passes(vec![Box::<PropConst>::default()])\n    }\n\n    pub fn declutter() -> Optimizer {\n        Optimizer::passes(vec![\n            Box::<PropConst>::default(),\n            Box::<PropagateRoi>::default(),\n            Box::<FoldUniformMask>::default(),\n            Box::new(OpOptim(\"declutter\", TypedOp::declutter_with_session, 0)),\n            Box::new(PushSliceUp),\n            Box::new(PushSplitDown),\n            Box::<concat_then_einsum::ConcatThenEinsum>::default(),\n            Box::<ChangeAxes>::default(),\n        ])\n    }\n\n    pub fn codegen() -> Optimizer {\n        Optimizer::passes(vec![\n            Box::<PropConst>::default(),\n            Box::new(OpOptim(\n                \"codegen\",\n                |op, _session, model, node| TypedOp::codegen(op, model, node),\n                0,\n            )),\n            Box::new(OpOptim(\"declutter\", TypedOp::declutter_with_session, 0)),\n            Box::new(PushSplitDown),\n            Box::new(OpOptim(\n                \"fuse\",\n                |op, _session, model, node| TypedOp::fuse(op, model, node),\n                0,\n            )),\n        ])\n    }\n\n    pub fn optimize(&self, model: &mut TypedModel) -> TractResult<()> {\n        self.session().optimize(model)\n    }\n\n    pub fn session(&self) -> OptimizerSession<'_> {\n        OptimizerSession { optimizer: self, counter: 0, seen: Default::default() }\n    }\n}\n\n#[derive(Debug)]\npub struct OptimizerSession<'o> {\n    optimizer: &'o Optimizer,\n    counter: usize,\n    seen: HashSet<String>,\n}\n\nimpl OptimizerSession<'_> {\n    pub fn optimize(&mut self, model: &mut TypedModel) -> TractResult<()> {\n        let _proof_session = model.symbols.proof_cache_session();\n        model.check_consistency().context(\"during optimizer preflight check\")?;\n        model.compact().context(\"during optimizer preflight compaction\")?;\n        model.check_names().context(\"after optimizer preflight compaction\")?;\n        for i in 0.. {\n            let old = self.counter;\n            self.run_all_passes(i, model)?;\n            if old == self.counter {\n                return Ok(());\n            }\n            model.compact()?;\n        }\n        unreachable!()\n    }\n\n    pub fn run_all_passes(&mut self, i: usize, model: &mut TypedModel) -> TractResult<()> {\n        let mut passes = self.optimizer.passes.clone();\n        for p in passes.iter_mut() {\n            self.run_one_pass_outer(i, p.as_mut(), model)\n                .with_context(|| format!(\"running pass {p:?}\"))?;\n            model.compact()?;\n            model\n                .check_consistency()\n                .with_context(|| format!(\"consistency check after pass {p:?}\"))?;\n        }\n        Ok(())\n    }\n\n    pub fn run_one_pass_outer(\n        &mut self,\n        i: usize,\n        p: &mut dyn TypedPass,\n        model: &mut TypedModel,\n    ) -> TractResult<()> {\n        loop {\n            let old_counter = self.counter;\n            self.run_one_pass_inner(i, p, model)?;\n            if self.counter == old_counter {\n                return Ok(());\n            }\n            model.compact().with_context(|| format!(\"after pass {p:?}\"))?;\n        }\n    }\n\n    pub fn run_one_pass_inner(\n        &mut self,\n        i: usize,\n        p: &mut dyn TypedPass,\n        model: &mut TypedModel,\n    ) -> TractResult<()> {\n        p.reset()?;\n        if let Some(steps) = self.optimizer.steps\n            && self.counter >= steps\n        {\n            return Ok(());\n        }\n        while let Some(mut patch) = p.next(self, model)? {\n            patch.push_context(format!(\"{p:?}/{i}\"));\n            patch.model.check_consistency().context(\"checking patch internal consistency\")?;\n            model\n                .check_consistency()\n                .context(\"Checking target model consistency before patching\")?;\n            if let Some(watchdog) = patch.dont_apply_twice.take() {\n                if self.seen.contains(&watchdog) {\n                    debug!(\"Loop detected: {watchdog} seen before\");\n                    continue;\n                } else {\n                    self.seen.insert(watchdog);\n                }\n            }\n            let patch_name = patch.context.iter().rev().join(\" >> \");\n            debug!(\"applying patch #{}: {patch_name}\", self.counter);\n            patch.apply(model).with_context(|| format!(\"Applying patch {patch_name}\"))?;\n            model\n                .check_consistency()\n                .context(\"Checking target model consistency after patching\")?;\n            self.counter += 1;\n            if let Some(steps) = self.optimizer.steps\n                && self.counter >= steps\n            {\n                return Ok(());\n            }\n        }\n        if p.run_direct(model)? {\n            model.check_consistency().with_context(|| format!(\"after run_direct {p:?}\"))?;\n        }\n        model.check_consistency().with_context(|| format!(\"after pass {p:?}\"))?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "core/src/optim/op_optim.rs",
    "content": "use crate::internal::*;\nuse crate::ops::dummy::Dummy;\n\nuse super::OptimizerSession;\n\n#[derive(Clone)]\npub struct OpOptim(\n    pub &'static str,\n    pub  fn(\n        op: &dyn TypedOp,\n        session: &mut OptimizerSession,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>>,\n    pub usize,\n);\n\nimpl OpOptim {\n    fn full_pass(\n        &mut self,\n        session: &mut OptimizerSession,\n        model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        for node in &model.nodes[self.2..] {\n            if node.op_is::<Dummy>() {\n                continue;\n            }\n            let patch = (self.1)(node.op.as_ref(), session, model, node)\n                .with_context(|| format!(\"{self:?} node {node}\"))?;\n            if let Some(mut p) = patch {\n                p.push_context(format!(\"{self:?} {node}\"));\n                self.2 = node.id + p.dont_apply_twice.is_some() as usize;\n                return Ok(Some(p));\n            }\n        }\n        Ok(None)\n    }\n}\n\nimpl std::fmt::Debug for OpOptim {\n    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(fmt, \"{}\", self.0)\n    }\n}\n\nimpl super::TypedPass for OpOptim {\n    fn reset(&mut self) -> TractResult<()> {\n        self.2 = 0;\n        Ok(())\n    }\n\n    fn next(\n        &mut self,\n        session: &mut OptimizerSession,\n        model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        self.full_pass(session, model)\n    }\n}\n"
  },
  {
    "path": "core/src/optim/prop_const.rs",
    "content": "use tract_data::TooEarly;\n\nuse crate::internal::*;\nuse crate::ops::array::Slice;\nuse crate::ops::dummy::Dummy;\nuse crate::ops::konst::Const;\nuse crate::ops::source::TypedSource;\nuse crate::optim::OptimizerSession;\n\n#[derive(Clone, Debug, Default)]\npub struct PropConst(usize);\n\nimpl super::TypedPass for PropConst {\n    fn reset(&mut self) -> TractResult<()> {\n        self.0 = 0;\n        Ok(())\n    }\n    fn next(\n        &mut self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        for node in &model.nodes[self.0..] {\n            if node.op_is::<Const>() && node.outputs[0].fact.konst.is_none() {\n                self.0 = node.id;\n                let mut patch = TypedModelPatch::default();\n                let wire =\n                    patch.add_const(&node.name, node.op_as::<Const>().unwrap().val().clone())?;\n                patch.shunt_outside(model, node.id.into(), wire)?;\n                return Ok(Some(patch));\n            }\n            let inputs = model.node_input_facts(node.id)?;\n            if !node.op_is::<Const>()\n                && !node.op_is::<Dummy>()\n                && !node.op_is::<TypedSource>()\n                && node.op.is_stateless()\n                && inputs.iter().zip(&node.inputs).all(|(fact, outlet)| {\n                    fact.konst.is_some()\n                        && (model.node(outlet.node).outputs[outlet.slot].successors.len() == 1\n                            || node.op_is::<Slice>()\n                            || (fact.datum_type.is_number()\n                                && fact.shape.volume().as_i64().is_some_and(|d| d < 1024)))\n                })\n            {\n                let inputs =\n                    inputs.iter().map(|f| f.konst.clone().unwrap().into_tvalue()).collect();\n                let input_mem: u64 = model\n                    .node_input_facts(node.id)?\n                    .iter()\n                    .map(|f| f.mem_size().to_i64().unwrap_or(i64::MAX) as u64)\n                    .sum();\n                match node.op.eval_with_session(node.id, &TurnState::default(), inputs) {\n                    Ok(mut res) => {\n                        self.0 = node.id;\n                        let output_mem: u64 = res\n                            .iter()\n                            .map(|t| (t.datum_type().size_of() * t.volume()) as u64)\n                            .sum();\n                        if output_mem > input_mem.max(1 << 20) {\n                            continue;\n                        }\n                        let mut node = node;\n                        loop {\n                            let Some(succ) = model.single_succ(node.id)? else {\n                                break;\n                            };\n                            if succ.inputs.len() > 1 || !succ.op.is_stateless() {\n                                break;\n                            }\n                            let Ok(succ_res) = succ.op.eval_with_session(\n                                node.id,\n                                &TurnState::default(),\n                                res.clone(),\n                            ) else {\n                                break;\n                            };\n                            let succ_mem: u64 = succ_res\n                                .iter()\n                                .map(|t| (t.datum_type().size_of() * t.volume()) as u64)\n                                .sum();\n                            if succ_mem > input_mem.max(1 << 20) {\n                                break;\n                            }\n                            res = succ_res;\n                            node = succ;\n                        }\n                        let mut patch = TypedModelPatch::default();\n                        for (ix, output) in res.into_iter().enumerate() {\n                            let exotic_fact =\n                                model.outlet_fact(OutletId::new(node.id, ix))?.exotic_fact.clone();\n\n                            let name = if ix > 0 {\n                                format!(\"{}.{ix}\", node.name)\n                            } else {\n                                node.name.clone()\n                            };\n                            let wire = patch.wire_node(\n                                name,\n                                Const::new_with_opt_exotic_fact(\n                                    output.into_arc_tensor(),\n                                    exotic_fact,\n                                )?,\n                                &[],\n                            )?[0];\n                            patch.shunt_outside(model, (node.id, ix).into(), wire)?;\n                        }\n                        self.0 = node.id;\n                        return Ok(Some(patch));\n                    }\n                    Err(e) => {\n                        if !e.root_cause().is::<TooEarly>() {\n                            Err(e).with_context(|| {\n                                format!(\"Eager eval {node} during optimisation\")\n                            })?;\n                        }\n                    }\n                }\n            }\n        }\n        Ok(None)\n    }\n}\n"
  },
  {
    "path": "core/src/optim/propagate_roi.rs",
    "content": "use crate::internal::*;\nuse crate::ops::logic::sym_to_coord_axis;\nuse crate::optim::OptimizerSession;\n\n/// Backward pass that propagates `region_of_interest` annotations by\n/// calling `TypedOp::input_roi` on each node.\n///\n/// Ops can **introduce** ROIs (e.g. Iff reads its mask's uniform_tdim and\n/// creates a ROI on the scores input) or **bubble** them (e.g. element-wise\n/// ops pass an output ROI through to their inputs).\n///\n/// When multiple consumers of a wire produce different ROIs, they are merged\n/// via boolean OR using De Morgan: `a ∨ b = a + b - a * b`.\n/// If any consumer returns `None` for a wire (needs all positions), that wire\n/// gets no ROI.\n///\n/// The pass iterates to fixpoint: introductions may enable further bubbling.\n#[derive(Clone, Debug, Default)]\npub struct PropagateRoi;\n\n/// Merge two ROI expressions via boolean OR: `a ∨ b = a + b - a * b`.\nfn roi_union(a: &TDim, b: &TDim) -> TDim {\n    if a == b {\n        return a.clone();\n    }\n    a.clone() + b.clone() - a.clone() * b.clone()\n}\n\n/// Bubble output ROI to inputs using the op's axes_mapping.\n///\n/// For each input, builds a coordinate substitution map from the axes mapping:\n/// each output axis that appears in this input gets 🎯{out_pos} → 🎯{in_pos}.\n/// If any ROI coordinate symbol has no corresponding input axis (contracted,\n/// broadcast from dim=1, or absent), returns None for that input.\npub fn bubble_roi(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TVec<Option<TDim>>>> {\n    let output_fact = model.outlet_fact(OutletId::new(node.id, 0))?;\n    let Some(roi) = &output_fact.region_of_interest else { return Ok(None) };\n\n    let input_facts: TVec<&TypedFact> =\n        node.inputs.iter().map(|i| model.outlet_fact(*i)).collect::<TractResult<_>>()?;\n    let output_facts = tvec![output_fact];\n    let inputs_ref: Vec<&TypedFact> = input_facts.iter().copied().collect();\n    let outputs_ref: Vec<&TypedFact> = output_facts.iter().copied().collect();\n    let mapping = node.op.as_typed().unwrap().axes_mapping(&inputs_ref, &outputs_ref)?;\n\n    // Collect ROI coordinate symbols and their output axis positions.\n    let roi_coord_syms: Vec<(usize, Symbol)> =\n        roi.symbols().into_iter().filter_map(|s| sym_to_coord_axis(&s).map(|k| (k, s))).collect();\n\n    let remap_for_input = |input_ix: usize| -> Option<TDim> {\n        let mut sub_map: HashMap<Symbol, TDim> = HashMap::new();\n        for (out_pos, sym) in &roi_coord_syms {\n            let logical = mapping\n                .iter_all_axes()\n                .find(|a| a.outputs.first().is_some_and(|o| o.contains(out_pos)))?;\n            if logical.inputs[input_ix].is_empty() {\n                return None;\n            }\n            let in_pos = logical.inputs[input_ix][0];\n            if input_facts[input_ix].shape[in_pos] != output_fact.shape[*out_pos] {\n                return None;\n            }\n            if in_pos != *out_pos {\n                let scope = sym.scope()?;\n                sub_map.insert(sym.clone(), TDim::Sym(scope.coord_sym(in_pos)));\n            }\n        }\n        if sub_map.is_empty() { Some(roi.clone()) } else { roi.substitute_all(&sub_map).ok() }\n    };\n    let result: TVec<Option<TDim>> = (0..node.inputs.len()).map(|ix| remap_for_input(ix)).collect();\n    Ok(Some(result))\n}\n\nimpl super::TypedPass for PropagateRoi {\n    fn reset(&mut self) -> TractResult<()> {\n        Ok(())\n    }\n\n    fn next(\n        &mut self,\n        _session: &mut OptimizerSession,\n        _model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        Ok(None)\n    }\n\n    fn run_direct(&mut self, model: &mut TypedModel) -> TractResult<bool> {\n        let mut any_changed = false;\n        loop {\n            let order = model.eval_order()?;\n            let mut changed = false;\n\n            // Collect ROI demands from all nodes.\n            let mut demands: HashMap<OutletId, Option<TDim>> = HashMap::new();\n\n            for &node_id in &order {\n                let node = &model.nodes()[node_id];\n                let Some(input_rois) = node.op.as_typed().unwrap().input_roi(model, node)? else {\n                    continue;\n                };\n                for (ix, roi) in input_rois.into_iter().enumerate() {\n                    let outlet = node.inputs[ix];\n                    match (demands.get(&outlet), &roi) {\n                        (_, None) => {\n                            demands.insert(outlet, None);\n                        }\n                        (Option::None, Some(roi)) => {\n                            demands.insert(outlet, Some(roi.clone()));\n                        }\n                        (Some(None), Some(_)) => {}\n                        (Some(Some(existing)), Some(new)) => {\n                            demands.insert(outlet, Some(roi_union(existing, new)));\n                        }\n                    }\n                }\n            }\n\n            // Apply demands to model facts.\n            for (outlet, demand) in demands {\n                if let Some(roi) = demand {\n                    let fact = &mut model.nodes_mut()[outlet.node].outputs[outlet.slot].fact;\n                    if fact.region_of_interest.as_ref() != Some(&roi) {\n                        fact.region_of_interest = Some(roi);\n                        changed = true;\n                    }\n                }\n            }\n\n            any_changed |= changed;\n            if !changed {\n                break;\n            }\n        }\n        Ok(any_changed)\n    }\n}\n"
  },
  {
    "path": "core/src/optim/push_split_down.rs",
    "content": "use crate::internal::*;\n\nuse crate::optim::OptimizerSession;\nuse tract_itertools::Itertools;\n\n#[derive(Clone, Debug)]\npub struct PushSplitDown;\n\nimpl super::TypedPass for PushSplitDown {\n    fn reset(&mut self) -> TractResult<()> {\n        Ok(())\n    }\n    fn next(\n        &mut self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let mut patch = TypedModelPatch::default();\n        for node in model.eval_order()? {\n            for output in &model.node(node).outputs {\n                for (a, b) in output.successors.iter().tuple_combinations() {\n                    if a.node == b.node {\n                        // found where a square is implemented using a mul with duplicate input\n                        continue;\n                    }\n                    if patch.obliterate.contains(&b.node) {\n                        continue;\n                    }\n                    // dont merge outputs.\n                    if model.outputs.contains(&a.node.into())\n                        && model.outputs.contains(&b.node.into())\n                    {\n                        continue;\n                    }\n                    let a = model.node(a.node);\n                    let b = model.node(b.node);\n                    if a.same_as(b) {\n                        for slot in 0..b.outputs.len() {\n                            let tap = patch.tap_model(model, OutletId::new(a.id, slot))?;\n                            patch.shunt_outside(model, OutletId::new(b.id, slot), tap)?;\n                            patch.obliterate(b.id)?;\n                        }\n                    }\n                }\n            }\n        }\n        Ok(Some(patch).filter(|p| !p.is_empty()))\n    }\n}\n"
  },
  {
    "path": "core/src/optim/slice.rs",
    "content": "use tract_itertools::Itertools;\n\nuse crate::internal::*;\nuse crate::ops::array::Slice;\nuse crate::optim::OptimizerSession;\n\n#[derive(Clone, Debug)]\npub struct PushSliceUp;\n\nimpl super::TypedPass for PushSliceUp {\n    fn reset(&mut self) -> TractResult<()> {\n        Ok(())\n    }\n\n    fn next(\n        &mut self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let eval_order = model.eval_order()?;\n        for &n in &eval_order {\n            let node = model.node(n);\n            if model.node(n).outputs.len() != 1 {\n                continue;\n            }\n            for axis in 0..node.outputs[0].fact.rank() {\n                if let Some(succ) = model.single_succ(n)? {\n                    let Some(slice) = succ.op_as::<Slice>() else { continue };\n                    let full_len = &node.outputs[0].fact.shape[axis];\n                    if slice.axis != axis {\n                        continue;\n                    }\n                    if let Some(me) = node.op_as::<Slice>() {\n                        if me.axis == slice.axis {\n                            let start = me.start.clone() + &slice.start;\n                            let len = slice.end.clone() - &slice.start;\n                            let end = start.clone() + len;\n                            let new = Slice { axis, start, end };\n                            return TypedModelPatch::fuse_with_next(model, node, new).map(Some);\n                        } else {\n                            let my_len = &node.outputs[0].fact.shape[me.axis];\n                            let slice_len = &succ.outputs[0].fact.shape[slice.axis];\n                            if !(my_len.clone() - slice_len).prove_strict_positive() {\n                                continue;\n                            }\n                        }\n                    }\n                    let boundaries =\n                        tvec!(0.to_dim(), slice.start.clone(), slice.end.clone(), full_len.clone());\n                    let Some((mut patch, splits)) =\n                        op_slices_to_slice_op(model, node, axis, &boundaries)?\n                    else {\n                        continue;\n                    };\n                    ensure!(splits.len() == 3);\n                    // ignore first split (0..start)\n                    let wire = splits[1];\n                    patch.shunt_outside(model, succ.id.into(), wire)?;\n\n                    return Ok(Some(patch));\n                // handle multiple slicing successors in fan-out fashion (think LSTM post linear op)\n                // limited to concrete interger slicing boundaries for ordering\n                // (it may actually work with generic TDim with ordering)\n                } else if let Some(boundaries) =\n                    should_slice_output(model, node, axis, &eval_order)?\n                {\n                    let boundaries_dim: TVec<TDim> =\n                        boundaries.iter().map(|d| d.to_dim()).collect();\n                    let Some((mut patch, splits)) =\n                        op_slices_to_slice_op(model, node, axis, &boundaries_dim)?\n                    else {\n                        continue;\n                    };\n                    ensure!(splits.len() == boundaries.len() - 1);\n                    rewire_sliced_outputs(model, node, axis, &mut patch, &boundaries, &splits)\n                        .context(\"Rewiring sliced outputs\")?;\n                    return Ok(Some(patch));\n                }\n            }\n        }\n        Ok(None)\n    }\n}\n\nfn op_slices_to_slice_op(\n    model: &TypedModel,\n    node: &TypedNode,\n    axis: usize,\n    boundaries: &[TDim],\n) -> TractResult<Option<(TypedModelPatch, TVec<OutletId>)>> {\n    let (ifacts, ofacts) = model.node_facts(node.id)?;\n    let invariants = node\n        .op\n        .axes_mapping(&ifacts, &ofacts)\n        .with_context(|| format!(\"Mapping axes for {node}\"))?;\n    let mut splits = tvec!();\n    let mut patch = TypedModelPatch::new(format!(\"Slice {node} by {boundaries:?}\"));\n    let inputs = patch.taps(model, &node.inputs)?;\n    let len = &node.outputs[0].fact.shape[axis];\n    ensure!(boundaries[0] == 0.to_dim());\n    ensure!(boundaries.last().as_ref().unwrap() == &len);\n    let axis_info = invariants.axis((InOut::Out(0), axis)).unwrap();\n    for (start, end) in boundaries.iter().tuple_windows() {\n        let mut wires = tvec!();\n        for input_ix in 0..inputs.len() {\n            let mut wire = inputs[input_ix];\n            if let &[input_axis] = &*axis_info.inputs[input_ix] {\n                // dont propagate slice up if input looks like a broadcasting input\n                if !patch.outlet_fact(wire)?.shape[input_axis].is_one() {\n                    wire = patch.wire_node(\n                        format!(\n                            \"{}.split-{}-over-{}.{}..{}.slice\",\n                            &node.name, input_ix, input_axis, start, end\n                        ),\n                        Slice { axis: input_axis, start: start.to_dim(), end: end.to_dim() },\n                        &[wire],\n                    )?[0];\n                }\n            }\n            wires.push(wire);\n        }\n        rule_if_some!(\n            wire = node\n                .op\n                .slice(\n                    &mut patch,\n                    model,\n                    node,\n                    &format!(\"{}.split-over-{}.{}..{}\", &node.name, axis, start, end),\n                    &wires,\n                    axis,\n                    start,\n                    end,\n                )\n                .with_context(|| format!(\"Calling slice on {node}\"))?\n        );\n        splits.push(wire[0]);\n    }\n    Ok(Some((patch, splits)))\n}\n\nfn should_slice_output(\n    model: &TypedModel,\n    node: &TypedNode,\n    axis: usize,\n    eval_order: &[usize],\n) -> TractResult<Option<TVec<usize>>> {\n    rule_if!(node.outputs[0].successors.len() > 0);\n    rule_if!(!node.op_is::<Slice>());\n    let slicers: TVec<usize> = node.outputs[0]\n        .successors\n        .iter()\n        .filter(|inlet| {\n            model.node(inlet.node).op_as::<Slice>().filter(|slice| slice.axis == axis).is_some()\n        })\n        .map(|inlet| inlet.node)\n        .collect();\n    /* aggressive: 1 slice as succesor => we propagate it */\n    /*\n    let Some(slice) = node.outputs[0].successors.iter().find_map(|inlet| {\n        model.node(inlet.node).op_as::<Slice>().filter(|slice| slice.axis == axis).map(|_| inlet.node)\n    }) else {\n        return Ok(None)\n    };\n    */\n    /* non-aggressive: we need all consumers to be slice */\n    rule_if!(slicers.len() >= node.outputs[0].successors.len());\n    let slice = node.outputs[0].successors[0].node;\n\n    rule_if!(eval_order.contains(&slice));\n    let slice_op = model.node(slice).op_as::<Slice>().unwrap();\n    let axis = slice_op.axis;\n    let mut boundaries = tvec!();\n    for succ in &node.outputs[0].successors {\n        if let Some(slice) = model.node(succ.node).op_as::<Slice>()\n            && slice.axis == axis\n        {\n            boundaries.push(slice.start.clone());\n            boundaries.push(slice.end.clone());\n        }\n    }\n    rule_if_let!(Ok(mut boundaries) =\n        boundaries.iter().map(|x| x.to_usize()).collect::<TractResult<TVec<usize>>>());\n    rule_if_let!(Ok(end) = node.outputs[0].fact.shape[axis].to_usize());\n    boundaries.push(end);\n    boundaries.sort();\n    boundaries.dedup();\n    rule_if!(boundaries.len() != 2);\n    Ok(Some(boundaries))\n}\n\npub fn rewire_sliced_outputs(\n    model: &TypedModel,\n    node: &TypedNode,\n    axis: usize,\n    patch: &mut TypedModelPatch,\n    boundaries: &[usize],\n    splits: &[OutletId],\n) -> TractResult<()> {\n    let full = patch.wire_node(\n        format!(\"{}.concat-{}\", node.name, axis),\n        crate::ops::array::TypedConcat::new(axis),\n        splits,\n    )?[0];\n    patch.shunt_outside(model, node.id.into(), full)?;\n    let zero = patch.add_const(\n        format!(\"{}.zero\", node.name),\n        Tensor::zero_scalar_dt(node.outputs[0].fact.datum_type)?,\n    )?;\n    for (ix, succ) in node.outputs[0].successors.iter().enumerate() {\n        if let Some(slice) =\n            model.node(succ.node).op_as::<Slice>().filter(|slice| slice.axis == axis)\n        {\n            // example: boundaries: 2, 3, wanted: 0..2 -> [0]\n            let slices: TVec<OutletId> = boundaries\n                .iter()\n                .tuple_windows()\n                .zip(splits.iter())\n                .filter_map(|((_down, up), split)| {\n                    if *up > slice.start.to_usize().unwrap() && *up <= slice.end.to_usize().unwrap()\n                    {\n                        Some(*split)\n                    } else {\n                        None\n                    }\n                })\n                .collect();\n            let wire = if slices.len() == 0 {\n                let mut empty_shape = node.outputs[0].fact.shape.clone();\n                empty_shape.set(axis, 0.to_dim());\n                patch.wire_node(\n                    format!(\"{}.concat-m{}..{}..{}\", node.name, ix, slice.start, slice.end),\n                    crate::ops::array::MultiBroadcastTo::new(empty_shape),\n                    &[zero],\n                )?[0]\n            } else if slices.len() > 1 {\n                patch.wire_node(\n                    format!(\"{}.concat-m{}..{}..{}\", node.name, ix, slice.start, slice.end),\n                    crate::ops::array::TypedConcat::new(axis),\n                    &slices,\n                )?[0]\n            } else {\n                slices[0]\n            };\n            patch.shunt_outside(model, succ.node.into(), wire)?;\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "core/src/optim/uniform_mask.rs",
    "content": "use crate::internal::*;\nuse crate::ops::array::{Slice, TypedConcat};\nuse crate::ops::binary::TypedBinOp;\nuse crate::ops::logic::{And, Iff, classify_true_range};\nuse crate::optim::OptimizerSession;\n\n/// Optimizer pass that exploits boolean-valued `uniform_tdim` on wires feeding `Iff` and `Mul`.\n///\n/// For each such wire it injects concrete constants or restructures the graph:\n///\n/// - **AllTrue / AllFalse** on any supported op → inject `Const(is_true, [1]×rank)`;\n///   subsequent declutter folds the op away (e.g. `Iff::declutter`, `declutter_neutral`).\n/// - **TwoRegions** on any supported op → slice all other inputs along the split axis,\n///   duplicate the op once per region each with a concrete bool const, concat results.\n///\n/// The per-op logic is limited to `try_fold_node` (which input indices may carry a bool\n/// `uniform_tdim`). All transformation logic in `try_fold_uniform_bool_input` and\n/// `split_op_two_regions` is op-agnostic.\n#[derive(Clone, Debug, Default)]\npub struct FoldUniformMask(usize);\n\nimpl super::TypedPass for FoldUniformMask {\n    fn reset(&mut self) -> TractResult<()> {\n        self.0 = 0;\n        Ok(())\n    }\n\n    fn next(\n        &mut self,\n        _session: &mut OptimizerSession,\n        model: &TypedModel,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        for node in &model.nodes[self.0..] {\n            self.0 = node.id + 1;\n            if let Some(patch) = try_fold_node(model, node)? {\n                return Ok(Some(patch));\n            }\n        }\n        Ok(None)\n    }\n}\n\nfn try_fold_node(model: &TypedModel, node: &TypedNode) -> TractResult<Option<TypedModelPatch>> {\n    let bool_indices: &[usize] = if node.op_is::<Iff>() {\n        &[0] // only the condition wire can carry bool uniform_tdim\n    } else if let Some(bin_op) = node.op_as::<TypedBinOp>() {\n        let out_bool = model.outlet_fact(node.id.into())?.datum_type == bool::datum_type();\n        if (bin_op.0.is::<And>() && out_bool) || bin_op.0.neutral_element() == Some(1) {\n            &[0, 1]\n        } else {\n            return Ok(None);\n        }\n    } else {\n        return Ok(None);\n    };\n\n    for &bool_ix in bool_indices {\n        if let Some(patch) = try_fold_uniform_bool_input(model, node, bool_ix)? {\n            return Ok(Some(patch));\n        }\n    }\n    Ok(None)\n}\n\n// ── Op-agnostic transformation ────────────────────────────────────────────────\n\nfn try_fold_uniform_bool_input(\n    model: &TypedModel,\n    node: &TypedNode,\n    bool_ix: usize,\n) -> TractResult<Option<TypedModelPatch>> {\n    let bool_fact = model.outlet_fact(node.inputs[bool_ix])?;\n    // If the input is already a concrete constant, the op's own declutter handles folding.\n    // Skipping here prevents an infinite loop (inject const → same uniform_tdim → inject again).\n    rule_if!(bool_fact.konst.is_none());\n    rule_if_some!(expr = &bool_fact.uniform_tdim);\n    rule_if_some!(range = classify_true_range(expr, &bool_fact.shape));\n\n    let dt = bool_fact.datum_type;\n    let rank = bool_fact.rank();\n    if range.is_full() {\n        return inject_scalar_const(model, node, bool_ix, dt, rank, true);\n    }\n    if range.is_empty() {\n        return inject_scalar_const(model, node, bool_ix, dt, rank, false);\n    }\n    split_op_regions(model, node, bool_ix, dt, rank, &range)\n}\n\n/// Replace the bool input at `bool_ix` with `Const(is_true ? 1 : 0, [1]*rank)` and rewire.\n/// Subsequent declutter folds the op:\n/// - `Iff(const_true, x, y) → x` via `Iff::declutter`\n/// - `Mul(signal, const_1) → signal` via `declutter_neutral`\n/// - `Mul(signal, const_0) → zeros` via `declutter_mul`\n/// - `And(signal, const_true) → signal` via `declutter_neutral`\nfn inject_scalar_const(\n    model: &TypedModel,\n    node: &TypedNode,\n    bool_ix: usize,\n    bool_dt: DatumType,\n    bool_rank: usize,\n    is_true: bool,\n) -> TractResult<Option<TypedModelPatch>> {\n    let const_tensor = uniform_const(bool_dt, bool_rank, is_true)?;\n    let mut patch = TypedModelPatch::default();\n    let const_wire = patch.wire_node(\n        format!(\"{}.bool_const\", node.name),\n        crate::ops::konst::Const::new(const_tensor.into_arc_tensor())?,\n        &[],\n    )?[0];\n    let mut new_inputs = tvec![];\n    for (ix, &outlet) in node.inputs.iter().enumerate() {\n        new_inputs.push(if ix == bool_ix { const_wire } else { patch.tap_model(model, outlet)? });\n    }\n    let out = patch.wire_node(&node.name, node.op.clone(), &new_inputs)?[0];\n    patch.shunt_outside(model, node.id.into(), out)?;\n    Ok(Some(patch))\n}\n\n/// Slice all inputs along `range.axis`, duplicate the op once per region\n/// (each with a concrete bool const for the bool input), then concat the outputs.\n///\n/// Handles two-region (one bound is None) and three-region (both bounds are Some) cases.\n/// For each non-bool input:\n/// - `shape[axis] == 1` → broadcast dimension; share the same wire across all regions.\n/// - `shape[axis] == out_dim` → slice per region.\n/// - anything else → bail (`Ok(None)`).\nfn split_op_regions(\n    model: &TypedModel,\n    node: &TypedNode,\n    bool_ix: usize,\n    bool_dt: DatumType,\n    bool_rank: usize,\n    range: &crate::ops::logic::TrueRange,\n) -> TractResult<Option<TypedModelPatch>> {\n    let axis = range.axis;\n    let out_dim = model.outlet_fact(node.id.into())?.shape[axis].clone();\n\n    // Build the list of (start, end, is_true) regions.\n    let regions: TVec<(TDim, TDim, bool)> = match (&range.start, &range.end) {\n        (None, Some(e)) => {\n            tvec![(TDim::Val(0), e.clone(), true), (e.clone(), out_dim.clone(), false),]\n        }\n        (Some(s), None) => {\n            tvec![(TDim::Val(0), s.clone(), false), (s.clone(), out_dim.clone(), true),]\n        }\n        (Some(s), Some(e)) => tvec![\n            (TDim::Val(0), s.clone(), false),\n            (s.clone(), e.clone(), true),\n            (e.clone(), out_dim.clone(), false),\n        ],\n        _ => return Ok(None), // full or empty — handled by caller\n    };\n\n    let mut patch = TypedModelPatch::default();\n    let mut region_outs = tvec![];\n\n    for (r_start, r_end, is_true) in &regions {\n        let mut region_inputs = tvec![OutletId::new(0, 0); node.inputs.len()];\n        for (ix, &outlet) in node.inputs.iter().enumerate() {\n            if ix == bool_ix {\n                let c = uniform_const(bool_dt, bool_rank, *is_true)?;\n                region_inputs[ix] = patch.wire_node(\n                    format!(\"{}.bool_{r_start}\", node.name),\n                    crate::ops::konst::Const::new(c.into_arc_tensor())?,\n                    &[],\n                )?[0];\n            } else {\n                let fact = model.outlet_fact(outlet)?;\n                let wire = patch.tap_model(model, outlet)?;\n                if fact.shape[axis].is_one() {\n                    region_inputs[ix] = wire;\n                } else if fact.shape[axis] == out_dim {\n                    region_inputs[ix] = patch.wire_node(\n                        format!(\"{}.slice_{r_start}_{ix}\", node.name),\n                        Slice::new(axis, r_start.clone(), r_end.clone()),\n                        &[wire],\n                    )?[0];\n                } else {\n                    return Ok(None);\n                }\n            }\n        }\n        region_outs.push(\n            patch.wire_node(\n                format!(\"{}.region_{r_start}\", node.name),\n                node.op.clone(),\n                &region_inputs,\n            )?[0],\n        );\n    }\n\n    let result =\n        patch.wire_node(format!(\"{}.concat\", node.name), TypedConcat::new(axis), &region_outs)?[0];\n    patch.shunt_outside(model, node.id.into(), result)?;\n    Ok(Some(patch))\n}\n\n// ── Shared helpers ────────────────────────────────────────────────────────────\n\n/// Build a tensor of shape `[1]*rank` with every element equal to `1` (if `is_true`) or `0`.\nfn uniform_const(dt: DatumType, rank: usize, is_true: bool) -> TractResult<Tensor> {\n    tensor0(is_true as i64).cast_to_dt(dt)?.into_owned().broadcast_into_rank(rank)\n}\n"
  },
  {
    "path": "core/src/plan.rs",
    "content": "use std::borrow::Borrow;\nuse std::cell::RefCell;\nuse std::fmt::{Debug, Display};\n\nuse multithread::Executor;\n\nuse crate::internal::*;\nuse crate::model::{Fact, Graph, OutletId};\nuse crate::ops::FrozenOpState;\nuse crate::ops::konst::Const;\nuse crate::runtime::RunOptions;\n\nuse self::order::{build_flush_list, eval_order_for_nodes, eval_order_opt_ram_for_nodes};\n\npub struct TurnState {\n    pub resolved_symbols: SymbolValues,\n    pub scenario: Option<usize>,\n    pub cached_mmm_scratch_space: RefCell<Option<Box<dyn tract_linalg::mmm::ScratchSpace>>>,\n    pub scratch_extensions: anymap3::Map,\n    pub values: Vec<Option<TVec<TValue>>>,\n}\n\nimpl Default for TurnState {\n    fn default() -> Self {\n        TurnState {\n            resolved_symbols: SymbolValues::default(),\n            scenario: None,\n            cached_mmm_scratch_space: None.into(),\n            scratch_extensions: anymap3::Map::new(),\n            values: vec![],\n        }\n    }\n}\n\nimpl Clone for TurnState {\n    fn clone(&self) -> Self {\n        TurnState {\n            resolved_symbols: self.resolved_symbols.clone(),\n            scenario: self.scenario,\n            cached_mmm_scratch_space: None.into(),\n            scratch_extensions: anymap3::Map::new(),\n            values: vec![],\n        }\n    }\n}\n\npub trait SessionStateHandler: Send + Sync + Debug {\n    fn before_plan_eval(&self, session_state: &mut TurnState) -> TractResult<()>;\n    fn after_plan_eval(&self, session_state: &mut TurnState) -> TractResult<()>;\n}\n\nimpl Debug for TurnState {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"SessionState({:?})\", self.resolved_symbols)\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct SimplePlan<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    pub(crate) model: Arc<Graph<F, O>>,\n    outputs: Vec<OutletId>,\n    order: Vec<usize>,\n    flush_lists: Vec<TVec<usize>>,\n    has_unresolved_symbols: bool,\n    executor: Option<Executor>,\n    session_handler: Option<Arc<dyn SessionStateHandler + 'static>>,\n}\n\nimpl<F, O> SimplePlan<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    /// This contructor returns a plan that will compute all the model default outputs in one pass.\n    pub fn new(model: impl Into<Arc<Graph<F, O>>>) -> TractResult<Arc<SimplePlan<F, O>>> {\n        let model = model.into();\n        Self::build(model, &RunOptions::default()).map(Arc::new)\n    }\n\n    /// This contructor returns a plan that will compute all the model default outputs in one pass.\n    pub fn new_with_options(\n        model: impl Into<Arc<Graph<F, O>>>,\n        options: &RunOptions,\n    ) -> TractResult<Arc<SimplePlan<F, O>>> {\n        let model = model.into();\n        Self::build(model, options).map(Arc::new)\n    }\n\n    /// This contructor returns a plan that will compute the specified output.\n    #[deprecated]\n    pub fn new_for_output(\n        model: Graph<F, O>,\n        output: OutletId,\n    ) -> TractResult<Arc<SimplePlan<F, O>>> {\n        #[allow(deprecated)]\n        Self::build_with_outputs_and_deps(model, &[output], &[], &RunOptions::default())\n            .map(Arc::new)\n    }\n\n    /// This contructor returns a plan that will compute all specified outputs in one pass.\n    #[deprecated]\n    pub fn new_for_outputs(\n        model: impl Into<Arc<Graph<F, O>>>,\n        outputs: &[OutletId],\n    ) -> TractResult<Arc<SimplePlan<F, O>>> {\n        #[allow(deprecated)]\n        Self::build_with_outputs_and_deps(model, outputs, &[], &RunOptions::default()).map(Arc::new)\n    }\n\n    pub fn with_session_handler<H: SessionStateHandler + 'static>(\n        mut self,\n        session_handler: H,\n    ) -> Self {\n        self.session_handler = Some(Arc::new(session_handler));\n        self\n    }\n\n    #[deprecated]\n    pub fn new_for_outputs_and_deps(\n        model: impl Into<Arc<Graph<F, O>>>,\n        outputs: &[OutletId],\n        deps: &[(usize, usize)],\n    ) -> TractResult<Arc<SimplePlan<F, O>>> {\n        #[allow(deprecated)]\n        Self::build_with_outputs_and_deps(model, outputs, deps, &RunOptions::default())\n            .map(Arc::new)\n    }\n\n    pub fn build(\n        model: impl Into<Arc<Graph<F, O>>>,\n        options: &RunOptions,\n    ) -> TractResult<SimplePlan<F, O>> {\n        let model = model.into();\n        let outputs = model.outputs.clone();\n        #[allow(deprecated)]\n        Self::build_with_outputs_and_deps(model, &outputs, &[], options)\n    }\n\n    #[deprecated]\n    pub fn build_with_outputs_and_deps(\n        model: impl Into<Arc<Graph<F, O>>>,\n        outputs: &[OutletId],\n        deps: &[(usize, usize)],\n        options: &RunOptions,\n    ) -> TractResult<SimplePlan<F, O>> {\n        let model = model.into();\n        let inputs = model.input_outlets()?.iter().map(|n| n.node).collect::<Vec<usize>>();\n        let outputs_nodes = outputs.iter().map(|n| n.node).collect::<Vec<usize>>();\n        let mut order = if options.skip_order_opt_ram {\n            eval_order_for_nodes(model.nodes(), &inputs, &outputs_nodes, deps)?\n        } else {\n            eval_order_opt_ram_for_nodes(model.nodes(), &inputs, &outputs_nodes, deps)?\n        };\n        order.retain(|node| !model.node(*node).op_is::<Const>());\n        let flush_lists = build_flush_list(&*model, &order, outputs, |n| !n.op_is::<Const>());\n\n        #[allow(clippy::mutable_key_type)]\n        let mut symbols: std::collections::HashSet<Symbol> = Default::default();\n        for node in &model.nodes {\n            for output in &node.outputs {\n                if let Ok(fact) = output.fact.to_typed_fact() {\n                    symbols.extend(fact.shape.iter().flat_map(|d| d.symbols()))\n                }\n            }\n        }\n        Ok(SimplePlan {\n            model,\n            order,\n            flush_lists,\n            outputs: outputs.to_vec(),\n            has_unresolved_symbols: !symbols.is_empty(),\n            executor: options.executor.clone(),\n            session_handler: None,\n        })\n    }\n\n    pub fn order_without_consts(&self) -> &[usize] {\n        &self.order\n    }\n\n    pub fn run(self: &Arc<Self>, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let mut state = self.spawn()?;\n        state.run(inputs)\n    }\n\n    pub fn model(&self) -> &Graph<F, O> {\n        self.model.borrow()\n    }\n\n    pub fn spawn(self: &Arc<Self>) -> TractResult<SimpleState<F, O>> {\n        SimpleState::new(self)\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct SimpleState<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    pub(crate) plan: Arc<SimplePlan<F, O>>,\n    pub op_states: Vec<Option<Box<dyn OpState>>>,\n    pub turn_state: TurnState,\n}\n\nimpl<F, O> SimpleState<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    pub fn new(plan: &Arc<SimplePlan<F, O>>) -> TractResult<SimpleState<F, O>> {\n        let plan = Arc::clone(plan);\n        let turn = TurnState::default();\n        let model = plan.model();\n        let states: Vec<Option<Box<dyn OpState>>> = vec![None; model.nodes.len()];\n        let mut state = SimpleState { plan, op_states: states, turn_state: turn };\n        state.reset_op_states()?;\n        Ok(state)\n    }\n\n    pub fn new_from_inputs(\n        plan: &Arc<SimplePlan<F, O>>,\n        inputs: TVec<TValue>,\n    ) -> TractResult<SimpleState<F, O>> {\n        let mut state = SimpleState::new(plan)?;\n        state.set_inputs(inputs)?;\n        state.resolve_symbols_with_states()?;\n\n        Ok(state)\n    }\n\n    fn ready_turn(&mut self) {\n        if self.turn_state.values.len() == 0 {\n            self.turn_state.values = vec![None; self.plan.model.nodes().len()];\n            for node in &self.plan.model.nodes {\n                if let Some(k) = node.op_as::<Const>() {\n                    self.turn_state.values[node.id] = Some(tvec!(k.val().clone().into_tvalue()));\n                }\n            }\n        }\n    }\n    /// Reset wires state.\n    pub fn reset_turn(&mut self) -> TractResult<()> {\n        for node in &self.plan.order {\n            self.turn_state.values[*node] = None;\n        }\n        self.turn_state.resolved_symbols = SymbolValues::default();\n        Ok(())\n    }\n\n    /// Reset op inner state.\n    fn reset_op_states(&mut self) -> TractResult<()> {\n        let &mut SimpleState { ref plan, ref mut turn_state, op_states: ref mut states, .. } = self;\n        for (ix, n) in plan.model.nodes.iter().enumerate() {\n            states[ix] = if n.op().is_stateless() { None } else { n.op().state(turn_state, ix)? };\n        }\n        Ok(())\n    }\n\n    fn resolve_symbols_with_states(&mut self) -> TractResult<()> {\n        for state in self\n            .op_states\n            .iter_mut()\n            .filter_map(Option::as_mut)\n            .filter(|s| s.init_tensor_fact().is_some())\n        {\n            state.resolve_symbols(&mut self.turn_state)?;\n        }\n        Ok(())\n    }\n\n    pub fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        self.run_plan_with_eval(inputs, self::eval)\n    }\n\n    pub fn exec(&mut self) -> TractResult<()> {\n        self.exec_plan_with_eval(self::eval)\n    }\n\n    pub fn run_plan_with_eval<Eval, E>(\n        &mut self,\n        inputs: TVec<TValue>,\n        eval: Eval,\n    ) -> TractResult<TVec<TValue>>\n    where\n        Eval: for<'a, 'b, 'c> FnMut(\n            &'a mut TurnState,\n            Option<&'b mut (dyn OpState + 'static)>,\n            &'c Node<F, O>,\n            TVec<TValue>,\n        ) -> Result<TVec<TValue>, E>,\n        E: Into<anyhow::Error> + Send + Sync + 'static,\n    {\n        self.set_inputs(inputs)?;\n        self.resolve_symbols_with_states()?;\n        self.exec_plan_with_eval(eval)?;\n        let outputs = self.outputs()?;\n        self.reset_turn()?;\n        Ok(outputs)\n    }\n\n    pub fn exec_plan_with_eval<Eval, E>(&mut self, eval: Eval) -> TractResult<()>\n    where\n        Eval: for<'a, 'b, 'c> FnMut(\n            &'a mut TurnState,\n            Option<&'b mut (dyn OpState + 'static)>,\n            &'c Node<F, O>,\n            TVec<TValue>,\n        ) -> Result<TVec<TValue>, E>,\n        E: Into<anyhow::Error> + Send + Sync + 'static,\n    {\n        if let Some(executor) = self.plan().executor.as_ref() {\n            tract_linalg::multithread::multithread_tract_scope(executor.clone(), || {\n                self.do_exec_plan_with_eval(eval)\n            })\n        } else {\n            self.do_exec_plan_with_eval(eval)\n        }\n    }\n\n    fn do_exec_plan_with_eval<Eval, E>(&mut self, mut eval: Eval) -> TractResult<()>\n    where\n        Eval: for<'a, 'b, 'c> FnMut(\n            &'a mut TurnState,\n            Option<&'b mut (dyn OpState + 'static)>,\n            &'c Node<F, O>,\n            TVec<TValue>,\n        ) -> Result<TVec<TValue>, E>,\n        E: Into<anyhow::Error> + Send + Sync + 'static,\n    {\n        {\n            self.ready_turn();\n            self.plan\n                .session_handler\n                .as_ref()\n                .map(|it| it.before_plan_eval(&mut self.turn_state))\n                .transpose()?;\n\n            for (step, n) in self.plan.order.iter().enumerate() {\n                let node = self.plan.model.node(*n);\n                trace!(\"Running step {step}, node {node}\");\n                let mut inputs: TVec<TValue> = tvec![];\n                for i in &node.inputs {\n                    trace!(\"  use input {i:?}\");\n                    let prec_node = self.plan.model.node(i.node);\n                    let prec = self.turn_state.values[i.node].as_ref().ok_or_else(|| {\n                        format_err!(\"Computing {}, precursor {} not done:\", node, prec_node)\n                    })?;\n                    inputs.push(prec[i.slot].clone())\n                }\n\n                for flush in &self.plan.flush_lists[step] {\n                    trace!(\"  Ran {} can now flush {}\", node, self.plan.model.node(*flush));\n                    self.turn_state.values[*flush] = None;\n                }\n\n                if cfg!(debug_assertions) {\n                    let facts = self.plan.model.node_input_facts(node.id)?;\n                    if facts.len() != inputs.len() {\n                        bail!(\n                            \"Evaluating {}: expected {} inputs, got {}\",\n                            node,\n                            facts.len(),\n                            inputs.len()\n                        );\n                    }\n                    for (ix, (v, f)) in inputs.iter().zip(facts.iter()).enumerate() {\n                        if !f.matches(v, Some(&self.turn_state.resolved_symbols))? {\n                            bail!(\n                                \"Evaluating {}: input {:?}, expected {:?}, got {:?}\",\n                                node,\n                                ix,\n                                f,\n                                v\n                            );\n                        }\n                    }\n                }\n\n                let vs = eval(\n                    &mut self.turn_state,\n                    self.op_states[node.id].as_deref_mut(),\n                    node,\n                    inputs,\n                )\n                .map_err(|e| e.into())?;\n\n                if self.plan.has_unresolved_symbols {\n                    for (o, v) in node.outputs.iter().zip(vs.iter()) {\n                        if let Ok(f) = o.fact.to_typed_fact() {\n                            for (dim_abstract, dim_concrete) in f.shape.iter().zip(v.shape()) {\n                                Self::resolve(\n                                    &mut self.turn_state,\n                                    dim_abstract,\n                                    *dim_concrete as i64,\n                                )?;\n                            }\n                        }\n                    }\n                }\n                if cfg!(debug_assertions) {\n                    let facts = self.plan.model.node_output_facts(node.id)?;\n                    if facts.len() != vs.len() {\n                        bail!(\n                            \"Evaluating {}: expected {} outputs, got {}\",\n                            node,\n                            facts.len(),\n                            vs.len()\n                        );\n                    }\n                    for (ix, (v, f)) in vs.iter().zip(facts.iter()).enumerate() {\n                        if node.outputs[ix].successors.len() == 0 {\n                            continue;\n                        }\n                        if !f.matches(v, Some(&self.turn_state.resolved_symbols))? {\n                            bail!(\n                                \"Evaluating {}: output {:?}, expected {:?}, got {:?}\",\n                                node,\n                                ix,\n                                f,\n                                v\n                            );\n                        }\n                    }\n                }\n\n                self.turn_state.values[node.id] = Some(vs);\n            }\n            self.plan\n                .session_handler\n                .as_ref()\n                .map(|it| it.after_plan_eval(&mut self.turn_state))\n                .transpose()?;\n        }\n        Ok(())\n    }\n\n    pub fn set_inputs(&mut self, inputs: TVec<TValue>) -> TractResult<()> {\n        ensure!(\n            inputs.len() == self.model().inputs.len(),\n            \"Wrong number of inputs for model. Expected {} got {}\",\n            self.model().inputs.len(),\n            inputs.len()\n        );\n\n        for (ix, t) in inputs.into_iter().enumerate() {\n            self.set_input(ix, t)?\n        }\n        Ok(())\n    }\n\n    fn resolve(state: &mut TurnState, expression: &TDim, provided: i64) -> TractResult<()> {\n        let expected = expression.eval(&state.resolved_symbols);\n        if let Ok(x) = expected.to_i64()\n            && x != provided\n        {\n            bail!(\"Clashing resolution for expression. {expression}={x} != {provided}. ({state:?})\")\n        }\n        if expected.symbols().len() == 1 {\n            let sym = expected.symbols().into_iter().next().unwrap();\n            if let Some(v) = solve_for(&sym, &expected, &provided.to_dim()) {\n                debug!(\"Determined symbol {sym}={v}\");\n                state.resolved_symbols.set(&sym, v.to_i64().unwrap());\n            }\n            if state.scenario.is_none() {\n                let scope = sym\n                    .scope()\n                    .with_context(|| format!(\"Symbol {sym:?} points to an invalid (dead ?) SymbolScope. Make sure to create symbols using the model-managed SymbolScope.\"))?;\n                state.scenario = scope.guess_scenario(&state.resolved_symbols)?;\n            }\n        }\n        Ok(())\n    }\n\n    pub fn set_input(&mut self, input: usize, t: TValue) -> TractResult<()> {\n        let outlet: OutletId = *self\n            .model()\n            .input_outlets()?\n            .get(input)\n            .with_context(|| format!(\"Invalid input id for model ({input}).\"))?;\n        if let Ok(fact) = self.plan.model.outlet_fact(outlet)?.to_typed_fact() {\n            for (expected, provided) in fact.shape.iter().zip(t.shape()) {\n                Self::resolve(&mut self.turn_state, expected, *provided as i64)?;\n            }\n        }\n        let fact = self.plan.model.outlet_fact(outlet)?;\n        ensure!(\n            fact.matches(&t, Some(&self.turn_state.resolved_symbols))\n                .with_context(|| format!(\"Setting input {input}\"))?,\n            \"Input at index {input} has incorrect dtype or shape (got {t:?}, expected to match fact {fact:?})\",\n        );\n        self.ready_turn();\n        self.turn_state.values[outlet.node] = Some(tvec!(t));\n        Ok(())\n    }\n\n    pub fn output(&self, id: usize) -> TractResult<&TValue> {\n        let outlet = self.model().output_outlets()?.get(id).with_context(|| {\n            format!(\n                \"Required output {}, only have {}\",\n                id,\n                self.model().output_outlets().unwrap().len()\n            )\n        })?;\n        let value: &TValue = self\n            .turn_state\n            .values\n            .get(outlet.node)\n            .context(\"node id for output beyond node values array\")?\n            .as_ref()\n            .context(\"node is not an output\")?\n            .get(outlet.slot)\n            .context(\"slot id too high\")?;\n        Ok(value)\n    }\n\n    pub fn outputs(&mut self) -> TractResult<TVec<TValue>> {\n        let &mut SimpleState { ref plan, ref mut turn_state, .. } = self;\n        let mut v = tvec![];\n        for o in plan.outputs.iter() {\n            let vs = turn_state.values[o.node].as_mut().ok_or_else(|| {\n                format_err!(\"Outputs of {:?} are not computed\", &plan.model.nodes()[o.node])\n            })?;\n            v.push(vs[o.slot].clone())\n        }\n        Ok(v)\n    }\n\n    pub fn set_values(&mut self, id: usize, values: TVec<TValue>) -> TractResult<()> {\n        self.turn_state.values[id] = Some(values);\n        Ok(())\n    }\n\n    pub fn set_value(&mut self, id: usize, value: TValue) -> TractResult<()> {\n        self.set_values(id, tvec!(value))\n    }\n\n    pub fn prepare_inputs(&self, node: usize) -> TractResult<TVec<TValue>> {\n        let SimpleState { plan, turn_state, .. } = self;\n        let nodes = plan.model.nodes();\n        let node = &nodes[node];\n        let mut inputs: TVec<TValue> = tvec![];\n        for i in &node.inputs {\n            let prec_node = &nodes[i.node];\n            let prec = turn_state.values[i.node].as_ref().ok_or_else(|| {\n                format_err!(\"Computing {}, precursor {} not done.\", node, prec_node)\n            })?;\n            inputs.push(prec[i.slot].clone())\n        }\n        Ok(inputs)\n    }\n\n    pub fn compute_one(&mut self, node: usize) -> TractResult<()> {\n        let inputs = self.prepare_inputs(node)?;\n        self.compute_one_with_inputs(node, inputs)\n    }\n\n    pub fn compute_one_with_inputs(\n        &mut self,\n        node: usize,\n        inputs: TVec<TValue>,\n    ) -> TractResult<()> {\n        let &mut SimpleState { ref plan, ref mut turn_state, op_states: ref mut states, .. } = self;\n        let nodes = plan.model.nodes();\n        let node = &nodes[node];\n        let vs = eval(turn_state, states[node.id].as_deref_mut(), node, inputs)?;\n        turn_state.values[node.id] = Some(vs);\n        Ok(())\n    }\n\n    pub fn compute_recursively(&mut self, node: usize) -> TractResult<&[TValue]> {\n        let values = {\n            #[allow(clippy::needless_collect)] // clippy bug ?\n            let precs: Vec<usize> =\n                self.model().nodes()[node].inputs.iter().map(|i| i.node).collect();\n            for i in precs.into_iter() {\n                if self.turn_state.values[i].is_none() {\n                    let _ = self.compute_recursively(i)?;\n                }\n            }\n            let mut inputs: TVec<TValue> = tvec![];\n            {\n                let node = &self.model().nodes()[node];\n                for i in &node.inputs {\n                    inputs.push(self.turn_state.values[i.node].as_ref().unwrap()[i.slot].clone())\n                }\n            }\n            let &mut Self {\n                op_states: ref mut states,\n                turn_state: ref mut session_state,\n                ref plan,\n                ..\n            } = self;\n            eval(session_state, states[node].as_deref_mut(), &plan.model().nodes[node], inputs)?\n        };\n        self.turn_state.values[node] = Some(values);\n        Ok(self.turn_state.values[node].as_ref().unwrap())\n    }\n\n    pub fn take_by_name(&mut self, name: &str) -> TractResult<TVec<Tensor>> {\n        let id = self.model().node_by_name(name)?.id;\n        Self::take(self, id)\n    }\n\n    pub fn take(&mut self, id: usize) -> TractResult<TVec<Tensor>> {\n        Ok(self.turn_state.values[id]\n            .take()\n            .ok_or_else(|| format_err!(\"Node is not computed\"))?\n            .into_iter()\n            .map(|v| v.into_tensor())\n            .collect())\n    }\n\n    pub fn plan(&self) -> &Arc<SimplePlan<F, O>> {\n        &self.plan\n    }\n\n    pub fn model(&self) -> &Graph<F, O> {\n        &self.plan.model\n    }\n\n    pub fn freeze(&self) -> FrozenSimpleState<F, O> {\n        FrozenSimpleState {\n            plan: self.plan.clone(),\n            resolved_symbols: self.turn_state.resolved_symbols.clone(),\n            scenario: self.turn_state.scenario,\n            states: self.op_states.iter().map(|s| s.as_ref().map(|s| s.freeze())).collect(),\n            values: self\n                .turn_state\n                .values\n                .iter()\n                .enumerate()\n                .map(|(ix, t)| {\n                    if self.model().nodes[ix].op_is::<Const>() {\n                        t.as_ref().map(|t| t.iter().map(|t| t.clone().into_tensor()).collect())\n                    } else {\n                        None\n                    }\n                })\n                .collect(),\n        }\n    }\n}\n\npub fn eval<F, O>(\n    session_state: &mut TurnState,\n    mut state: Option<&mut (dyn OpState + 'static)>,\n    node: &Node<F, O>,\n    input: TVec<TValue>,\n) -> TractResult<TVec<TValue>>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    // eprint!(\"{node} {input:?}\");\n    #[allow(clippy::let_and_return)]\n    let r = match state {\n        Some(ref mut state) => state.eval(session_state, node.op(), input),\n        None => node.op().eval_with_session(node.id, session_state, input),\n    }\n    .with_context(|| format!(\"Evaluating {node}\"));\n    // eprintln!(\" ==> {}\", r.as_ref().unwrap()[0].dump(true)?);\n    r\n}\n\n#[derive(Clone, Debug)]\npub struct FrozenSimpleState<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    plan: Arc<SimplePlan<F, O>>,\n    pub resolved_symbols: SymbolValues,\n    pub scenario: Option<usize>,\n    pub states: Vec<Option<Box<dyn FrozenOpState>>>,\n    pub values: Vec<Option<TVec<Tensor>>>,\n}\n\nimpl<F, O> FrozenSimpleState<F, O>\nwhere\n    F: Fact + Clone + 'static,\n    O: Debug + Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n{\n    pub fn unfreeze(&self) -> SimpleState<F, O> {\n        SimpleState {\n            plan: self.plan.clone(),\n            turn_state: TurnState {\n                resolved_symbols: self.resolved_symbols.clone(),\n                scenario: self.scenario,\n                cached_mmm_scratch_space: None.into(),\n                scratch_extensions: anymap3::Map::new(),\n                values: self\n                    .values\n                    .iter()\n                    .map(|t| {\n                        t.as_ref().map(|t| t.iter().map(|t| t.clone().into_tvalue()).collect())\n                    })\n                    .collect(),\n            },\n            op_states: self.states.iter().map(|s| s.as_ref().map(|s| s.unfreeze())).collect(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    fn is_send<T: Send>() {}\n    fn is_sync<T: Sync>() {}\n\n    #[test]\n    fn type_model_is_sync() {\n        is_sync::<TypedModel>();\n    }\n\n    #[test]\n    fn type_model_is_send() {\n        is_send::<TypedModel>();\n    }\n\n    #[test]\n    fn type_plan_is_send() {\n        is_send::<TypedSimplePlan>();\n    }\n\n    #[test]\n    fn type_plan_is_sync() {\n        is_sync::<TypedSimplePlan>();\n    }\n\n    #[test]\n    fn frozen_type_state_is_send() {\n        is_send::<TypedFrozenSimpleState>();\n    }\n}\n"
  },
  {
    "path": "core/src/runtime.rs",
    "content": "use std::any::Any;\nuse std::fmt::Debug;\n\nuse downcast_rs::Downcast;\nuse dyn_clone::DynClone;\nuse lazy_static::lazy_static;\nuse tract_linalg::multithread::Executor;\n\nuse crate::internal::*;\n\n#[derive(Clone, Debug, Default)]\npub struct RunOptions {\n    /// Use the simple ordering instead of the newer memory friendly one\n    pub skip_order_opt_ram: bool,\n\n    /// Override default global executor\n    pub executor: Option<Executor>,\n\n    /// Memory sizing hints\n    pub memory_sizing_hints: Option<SymbolValues>,\n}\n\npub trait Runtime: Debug + Send + Sync + 'static {\n    fn name(&self) -> StaticName;\n    fn prepare(&self, model: TypedModel) -> TractResult<Box<dyn Runnable>> {\n        self.prepare_with_options(model, &Default::default())\n    }\n    fn check(&self) -> TractResult<()>;\n    fn prepare_with_options(\n        &self,\n        model: TypedModel,\n        options: &RunOptions,\n    ) -> TractResult<Box<dyn Runnable>>;\n}\n\npub trait Runnable: Any + Downcast + Debug + Send + Sync + 'static {\n    fn run(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        self.spawn()?.run(inputs)\n    }\n    fn spawn(&self) -> TractResult<Box<dyn State>>;\n    fn input_count(&self) -> usize {\n        self.typed_model().context(\"Fallback implementation on typed_model()\").unwrap().inputs.len()\n    }\n    fn output_count(&self) -> usize {\n        self.typed_model()\n            .context(\"Fallback implementation on typed_model()\")\n            .unwrap()\n            .outputs\n            .len()\n    }\n    fn input_fact(&self, ix: usize) -> TractResult<&TypedFact> {\n        self.typed_model()\n            .context(\"Fallback implementation on typed_model()\")\n            .unwrap()\n            .input_fact(ix)\n    }\n    fn output_fact(&self, ix: usize) -> TractResult<&TypedFact> {\n        self.typed_model()\n            .context(\"Fallback implementation on typed_model()\")\n            .unwrap()\n            .output_fact(ix)\n    }\n    fn properties(&self) -> &HashMap<String, Arc<Tensor>> {\n        lazy_static! {\n            static ref NO_PROPERTIES: HashMap<String, Arc<Tensor>> = Default::default();\n        };\n        self.typed_model().map(|model| &model.properties).unwrap_or(&NO_PROPERTIES)\n    }\n\n    fn typed_plan(&self) -> Option<&Arc<TypedSimplePlan>>;\n    fn typed_model(&self) -> Option<&Arc<TypedModel>>;\n}\nimpl_downcast!(Runnable);\n\npub trait State: Any + Downcast + Debug + 'static {\n    fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>>;\n\n    fn runnable(&self) -> &dyn Runnable;\n\n    fn input_count(&self) -> usize {\n        self.runnable().input_count()\n    }\n\n    fn output_count(&self) -> usize {\n        self.runnable().output_count()\n    }\n\n    fn freeze(&self) -> Box<dyn FrozenState>;\n}\nimpl_downcast!(State);\n\npub trait FrozenState: Any + Debug + DynClone + Send {\n    fn unfreeze(&self) -> Box<dyn State>;\n}\ndyn_clone::clone_trait_object!(FrozenState);\n\n#[derive(Debug)]\npub struct DefaultRuntime;\n\nimpl Runtime for DefaultRuntime {\n    fn name(&self) -> StaticName {\n        Cow::Borrowed(\"default\")\n    }\n\n    fn prepare_with_options(\n        &self,\n        model: TypedModel,\n        options: &RunOptions,\n    ) -> TractResult<Box<dyn Runnable>> {\n        let model = model.into_optimized()?;\n        Ok(Box::new(TypedSimplePlan::new_with_options(model, options)?))\n    }\n\n    fn check(&self) -> TractResult<()> {\n        Ok(())\n    }\n}\n\nimpl Runnable for Arc<TypedRunnableModel> {\n    fn spawn(&self) -> TractResult<Box<dyn State>> {\n        Ok(Box::new(self.spawn()?))\n    }\n\n    fn typed_plan(&self) -> Option<&Self> {\n        Some(self)\n    }\n\n    fn typed_model(&self) -> Option<&Arc<TypedModel>> {\n        Some(&self.model)\n    }\n\n    fn input_count(&self) -> usize {\n        self.model.inputs.len()\n    }\n\n    fn output_count(&self) -> usize {\n        self.model.outputs.len()\n    }\n\n    fn input_fact(&self, ix: usize) -> TractResult<&TypedFact> {\n        self.model.input_fact(ix)\n    }\n    fn output_fact(&self, ix: usize) -> TractResult<&TypedFact> {\n        self.model.output_fact(ix)\n    }\n}\n\nimpl State for TypedSimpleState {\n    fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        self.run(inputs)\n    }\n\n    fn runnable(&self) -> &dyn Runnable {\n        &self.plan\n    }\n\n    fn freeze(&self) -> Box<dyn FrozenState> {\n        Box::new(TypedSimpleState::freeze(self))\n    }\n}\n\nimpl FrozenState for TypedFrozenSimpleState {\n    fn unfreeze(&self) -> Box<dyn State> {\n        Box::new(TypedFrozenSimpleState::unfreeze(self))\n    }\n}\n\npub struct InventorizedRuntime(pub &'static dyn Runtime);\n\nimpl Runtime for InventorizedRuntime {\n    fn name(&self) -> StaticName {\n        self.0.name()\n    }\n\n    fn prepare_with_options(\n        &self,\n        model: TypedModel,\n        options: &RunOptions,\n    ) -> TractResult<Box<dyn Runnable>> {\n        self.0.prepare_with_options(model, options)\n    }\n\n    fn check(&self) -> TractResult<()> {\n        self.0.check()\n    }\n}\n\nimpl Debug for InventorizedRuntime {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.0.fmt(f)\n    }\n}\n\ninventory::collect!(InventorizedRuntime);\n\npub fn runtimes() -> impl Iterator<Item = &'static dyn Runtime> {\n    inventory::iter::<InventorizedRuntime>().filter(|rt| rt.check().is_ok()).map(|ir| ir.0)\n}\n\npub fn runtime_for_name(s: &str) -> Option<&'static dyn Runtime> {\n    runtimes().find(|rt| rt.name() == s)\n}\n\n#[macro_export]\nmacro_rules! register_runtime {\n    ($type: ty= $val:expr) => {\n        static D: $type = $val;\n        inventory::submit! { $crate::runtime::InventorizedRuntime(&D) }\n    };\n}\n\nregister_runtime!(DefaultRuntime = DefaultRuntime);\n"
  },
  {
    "path": "core/src/transform.rs",
    "content": "use std::borrow::Cow;\n\nuse crate::internal::*;\n#[cfg(feature = \"blas\")]\nuse crate::ops::einsum::as_blas::AsBlas;\nuse crate::ops::matmul::de_block_quant::BlockQuantTransform;\nuse std::fmt::Debug;\n\nuse tract_data::TractResult;\n\nuse crate::floats::FloatPrecisionTranslator;\nuse crate::ops::nn::{Softmax, SoftmaxExp, SoftmaxKind, TypedModel};\n\n#[macro_export]\nmacro_rules! rule_if {\n    ($cond:expr) => {\n        if !$cond {\n            return Ok(None);\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! rule_if_let {\n    ($pat:pat = $expr:expr) => {\n        let $pat = $expr else {\n            return Ok(None);\n        };\n    };\n}\n\n#[macro_export]\nmacro_rules! rule_if_some {\n    ($pat:pat = $expr:expr) => {\n        let Some($pat) = $expr else {\n            return Ok(None);\n        };\n    };\n}\n\n/// Structured include/exclude filter for node names.\n///\n/// If `include` is `None`, all nodes are candidates; if `Some`, only nodes matching\n/// at least one pattern are included. `exclude` then removes from that set.\n#[derive(Debug, Clone, Default)]\npub struct NodeFilter {\n    pub include: Option<Vec<String>>,\n    pub exclude: Option<Vec<String>>,\n}\n\nimpl NodeFilter {\n    /// Returns `true` if the given node name passes the filter.\n    pub fn matches(&self, name: &str) -> bool {\n        let dominated = match &self.include {\n            Some(patterns) => patterns.iter().any(|p| name.contains(p)),\n            None => true,\n        };\n        if !dominated {\n            return false;\n        }\n        match &self.exclude {\n            Some(patterns) => !patterns.iter().any(|p| name.contains(p)),\n            None => true,\n        }\n    }\n\n    /// Returns `true` when neither include nor exclude is set.\n    pub fn is_pass_through(&self) -> bool {\n        self.include.is_none() && self.exclude.is_none()\n    }\n}\n\n/// Parse a legacy filter string (`\"!=...\"` / `\"==...\"`) into a `NodeFilter`.\npub fn parse_legacy_filter(filter: Option<&str>) -> TractResult<NodeFilter> {\n    let Some(filter) = filter.filter(|f| !f.is_empty()) else {\n        return Ok(NodeFilter::default());\n    };\n    if let Some(patterns) = filter.strip_prefix(\"!=\") {\n        let patterns = patterns.split(',').map(|it| it.trim().to_string()).collect();\n        Ok(NodeFilter { exclude: Some(patterns), ..Default::default() })\n    } else if let Some(patterns) = filter.strip_prefix(\"==\") {\n        let patterns = patterns.split(',').map(|it| it.trim().to_string()).collect();\n        Ok(NodeFilter { include: Some(patterns), ..Default::default() })\n    } else {\n        Ok(NodeFilter::default())\n    }\n}\n\n/// Build Float precision translator given a `NodeFilter`. If the filter is pass-through,\n/// all nodes will be translated during the transformation.\npub fn build_float_translator(\n    from_dt: DatumType,\n    to_dt: DatumType,\n    filter: NodeFilter,\n) -> Box<dyn ModelTransform> {\n    if filter.is_pass_through() {\n        return Box::new(FloatPrecisionTranslator::new(from_dt, to_dt));\n    }\n    Box::new(FloatPrecisionTranslator::with_filter(from_dt, to_dt, move |node| {\n        filter.matches(&node.name)\n    }))\n}\n\npub trait ModelTransform: Debug {\n    fn name(&self) -> StaticName;\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()>;\n    fn transform_into(&self, mut model: TypedModel) -> TractResult<TypedModel> {\n        self.transform(&mut model)?;\n        Ok(model)\n    }\n}\n\n#[derive(Debug)]\nstruct SoftmaxFastCompact;\n\nimpl ModelTransform for SoftmaxFastCompact {\n    fn name(&self) -> StaticName {\n        \"softmax_fast_compact\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        for node in &mut model.nodes {\n            if let Some(softmax) = node.op_as_mut::<Softmax>()\n                && let SoftmaxKind::Softmax(kind) = &mut softmax.kind\n            {\n                *kind = SoftmaxExp::FastCompact\n            }\n        }\n        Ok(())\n    }\n}\n\n/// Config for float precision transforms (f32_to_f16, f16_to_f32).\n#[derive(Debug, Default, serde::Deserialize)]\npub struct FloatTranslatorConfig {\n    /// Legacy filter string (`\"!=...\"` / `\"==...\"`).\n    #[serde(default)]\n    pub filter: Option<String>,\n    /// Include patterns — only nodes matching at least one pattern are translated.\n    #[serde(default)]\n    pub include: Option<Vec<String>>,\n    /// Exclude patterns — matching nodes are excluded from translation.\n    #[serde(default)]\n    pub exclude: Option<Vec<String>>,\n}\n\nimpl FloatTranslatorConfig {\n    pub fn into_node_filter(self) -> TractResult<NodeFilter> {\n        if self.include.is_some() || self.exclude.is_some() {\n            Ok(NodeFilter { include: self.include, exclude: self.exclude })\n        } else {\n            parse_legacy_filter(self.filter.as_deref())\n        }\n    }\n}\n\n/// Config for the `float_precision` transform.\n#[derive(Debug, serde::Deserialize)]\npub struct FloatPrecisionConfig {\n    pub from: String,\n    pub to: String,\n    /// Include patterns — only nodes matching at least one pattern are translated.\n    #[serde(default)]\n    pub include: Option<Vec<String>>,\n    /// Exclude patterns — matching nodes are excluded from translation.\n    #[serde(default)]\n    pub exclude: Option<Vec<String>>,\n}\n\npub struct ModelTransformFactory {\n    pub name: &'static str,\n    /// Build with default config (no params).\n    pub build_default: fn() -> TractResult<Box<dyn ModelTransform>>,\n    /// Build from a type-erased deserializer.\n    pub build: fn(&mut dyn erased_serde::Deserializer) -> TractResult<Box<dyn ModelTransform>>,\n}\n\ninventory::collect!(ModelTransformFactory);\n\n#[macro_export]\nmacro_rules! register_simple_model_transform {\n    ($name: expr, $type: expr) => {\n        $crate::internal::inventory::submit! {\n            $crate::transform::ModelTransformFactory {\n                name: $name,\n                build_default: || Ok(Box::new($type)),\n                build: |_de| Ok(Box::new($type)),\n            }\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! register_model_transform {\n    ($name:expr, $config:ty, $builder:expr) => {\n        $crate::internal::inventory::submit! {\n            $crate::transform::ModelTransformFactory {\n                name: $name,\n                build_default: || {\n                    let config = <$config>::default();\n                    let builder: fn($config) -> $crate::prelude::TractResult<Box<dyn $crate::transform::ModelTransform>> = $builder;\n                    builder(config)\n                },\n                build: |de: &mut dyn erased_serde::Deserializer| {\n                    let config: $config = erased_serde::deserialize(de)\n                        .map_err(|e| $crate::internal::anyhow!(\"deserializing transform config: {e}\"))?;\n                    let builder: fn($config) -> $crate::prelude::TractResult<Box<dyn $crate::transform::ModelTransform>> = $builder;\n                    builder(config)\n                },\n            }\n        }\n    };\n}\n\n/// Split a transform spec like `\"f32_to_f16(filter: \\\"!=layer.norm\\\")\"` into name and params.\npub fn split_spec(spec: &str) -> (Cow<'_, str>, &str) {\n    if let Some(pos) = spec.find('(') {\n        (Cow::Borrowed(&spec[..pos]), &spec[pos..])\n    } else if spec.contains('-') {\n        // Backward compat: simple name with no params, convert kebab→snake\n        (Cow::Owned(spec.replace('-', \"_\")), \"\")\n    } else {\n        (Cow::Borrowed(spec), \"\")\n    }\n}\n\n/// Look up a transform by name, using default config.\npub fn get_transform(name: &str) -> TractResult<Option<Box<dyn ModelTransform>>> {\n    let (name, _) = split_spec(name);\n    for factory in inventory::iter::<ModelTransformFactory>() {\n        if factory.name == &*name {\n            return Ok(Some((factory.build_default)()?));\n        }\n    }\n    Ok(None)\n}\n\n/// Look up a transform by name, deserializing config from the given deserializer.\npub fn get_transform_with_params(\n    name: &str,\n    de: &mut dyn erased_serde::Deserializer,\n) -> TractResult<Option<Box<dyn ModelTransform>>> {\n    for factory in inventory::iter::<ModelTransformFactory>() {\n        if factory.name == name {\n            return Ok(Some((factory.build)(de)?));\n        }\n    }\n    Ok(None)\n}\n\n#[derive(Debug, Default, serde::Deserialize)]\npub struct ConcretizeSymbolsConfig {\n    pub values: std::collections::HashMap<String, i64>,\n}\n\n#[derive(Debug)]\nstruct ConcretizeSymbolsTransform(ConcretizeSymbolsConfig);\n\nimpl ModelTransform for ConcretizeSymbolsTransform {\n    fn name(&self) -> StaticName {\n        \"concretize_symbols\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        let mut table = SymbolValues::default();\n        for (k, v) in &self.0.values {\n            table = table.with(&model.symbols.sym(k), *v);\n        }\n        *model = model.concretize_dims(&table)?;\n        Ok(())\n    }\n}\n\nregister_model_transform!(\"concretize_symbols\", ConcretizeSymbolsConfig, |config| Ok(Box::new(\n    ConcretizeSymbolsTransform(config)\n)));\n\nregister_simple_model_transform!(\"softmax_fast_compact\", SoftmaxFastCompact);\n#[cfg(feature = \"blas\")]\nregister_simple_model_transform!(\"as_blas\", AsBlas);\nregister_simple_model_transform!(\"block_quant\", BlockQuantTransform);\n\n#[derive(Debug, serde::Deserialize, Default)]\npub struct SelectOutputsConfig {\n    pub outputs: Vec<String>,\n}\n\n#[derive(Debug)]\nstruct SelectOutputsTransform(SelectOutputsConfig);\n\nimpl ModelTransform for SelectOutputsTransform {\n    fn name(&self) -> StaticName {\n        \"select_outputs\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        model.select_outputs_by_name(self.0.outputs.iter())\n    }\n}\n\nregister_model_transform!(\"select_outputs\", SelectOutputsConfig, |config| Ok(Box::new(\n    SelectOutputsTransform(config)\n)));\n\ninventory::submit! {\n    ModelTransformFactory {\n        name: \"f32_to_f16\",\n        build_default: || Ok(build_float_translator(DatumType::F32, DatumType::F16, NodeFilter::default())),\n        build: |de| {\n            let config: FloatTranslatorConfig = erased_serde::deserialize(de)\n                .map_err(|e| anyhow::anyhow!(\"deserializing f32_to_f16 config: {e}\"))?;\n            Ok(build_float_translator(DatumType::F32, DatumType::F16, config.into_node_filter()?))\n        },\n    }\n}\n\ninventory::submit! {\n    ModelTransformFactory {\n        name: \"f16_to_f32\",\n        build_default: || Ok(build_float_translator(DatumType::F16, DatumType::F32, NodeFilter::default())),\n        build: |de| {\n            let config: FloatTranslatorConfig = erased_serde::deserialize(de)\n                .map_err(|e| anyhow::anyhow!(\"deserializing f16_to_f32 config: {e}\"))?;\n            Ok(build_float_translator(DatumType::F16, DatumType::F32, config.into_node_filter()?))\n        },\n    }\n}\n\ninventory::submit! {\n    ModelTransformFactory {\n        name: \"float_precision\",\n        build_default: || {\n            anyhow::bail!(\"float_precision transform requires 'from' and 'to' parameters\")\n        },\n        build: |de| {\n            let config: FloatPrecisionConfig = erased_serde::deserialize(de)\n                .map_err(|e| anyhow::anyhow!(\"deserializing float_precision config: {e}\"))?;\n            let from_dt: DatumType = config.from.parse()\n                .map_err(|e| anyhow::anyhow!(\"parsing 'from' datum type: {e}\"))?;\n            let to_dt: DatumType = config.to.parse()\n                .map_err(|e| anyhow::anyhow!(\"parsing 'to' datum type: {e}\"))?;\n            let filter = NodeFilter { include: config.include, exclude: config.exclude };\n            Ok(build_float_translator(from_dt, to_dt, filter))\n        },\n    }\n}\n"
  },
  {
    "path": "core/src/value.rs",
    "content": "use crate::internal::*;\nuse std::ops::Deref;\nuse std::rc::Rc;\n\nuse TValue::*;\nuse tract_ndarray::Array;\n\n#[derive(Clone, Eq)]\npub enum TValue {\n    Const(Arc<Tensor>),\n    Var(Rc<Tensor>),\n}\n\nimpl std::fmt::Debug for TValue {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        (**self).fmt(f)\n    }\n}\n\nimpl PartialEq for TValue {\n    fn eq(&self, other: &Self) -> bool {\n        self.deref() == other.deref()\n    }\n}\n\nimpl TValue {\n    pub fn is_exclusive(&self) -> bool {\n        match self {\n            Var(it) => Rc::strong_count(it) == 1,\n            Const(_) => false,\n        }\n    }\n\n    pub fn from_const(t: Arc<Tensor>) -> Self {\n        Const(t)\n    }\n\n    pub fn as_arc_tensor(&self) -> Option<&Arc<Tensor>> {\n        match self {\n            Const(t) => Some(t),\n            Var(_) => None,\n        }\n    }\n}\n\nimpl From<Tensor> for TValue {\n    fn from(t: Tensor) -> Self {\n        TValue::Var(std::rc::Rc::new(t))\n    }\n}\n\nimpl std::ops::Deref for TValue {\n    type Target = Tensor;\n    fn deref(&self) -> &Self::Target {\n        match self {\n            Const(it) => it,\n            Var(it) => it,\n        }\n    }\n}\n\nimpl std::borrow::Borrow<Tensor> for TValue {\n    fn borrow(&self) -> &Tensor {\n        self\n    }\n}\n\nimpl IntoTensor for TValue {\n    fn into_tensor(self) -> Tensor {\n        match self {\n            Var(it) => Rc::try_unwrap(it).unwrap_or_else(|t| (*t).clone()),\n            Const(it) => it.into_tensor(),\n        }\n    }\n}\n\nimpl IntoArcTensor for TValue {\n    fn into_arc_tensor(self) -> Arc<Tensor> {\n        match self {\n            Var(ref _it) => self.into_tensor().into_arc_tensor(),\n            Const(t) => t,\n        }\n    }\n}\n\npub trait IntoTValue {\n    fn into_tvalue(self) -> TValue;\n}\n\nimpl IntoTValue for Tensor {\n    fn into_tvalue(self) -> TValue {\n        self.into_tensor().into()\n    }\n}\n\nimpl IntoTValue for Arc<Tensor> {\n    fn into_tvalue(self) -> TValue {\n        Const(self)\n    }\n}\n\nimpl<D: ::ndarray::Dimension, T: Datum> IntoTValue for Array<T, D> {\n    fn into_tvalue(self) -> TValue {\n        Tensor::from(self).into_tvalue()\n    }\n}\n"
  },
  {
    "path": "core/test_data/test_data.cfg",
    "content": ""
  },
  {
    "path": "cuda/Cargo.toml",
    "content": "[package]\nname = \"tract-cuda\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\n\t\"Louis Chouraki <louis.chouraki@sonos.com>\",\n\t\"Mathieu Poumeyrol <kali@zoy.org>\",\n]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\", \"CUDA\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nanyhow.workspace = true\nderive-new.workspace = true\ndyn-eq.workspace = true\ndirs.workspace = true\ndowncast-rs.workspace = true\nliquid.workspace = true\nliquid-core.workspace = true\nnum-traits.workspace = true\ntract-core.workspace = true\ntract-pulse-opl.workspace = true\ntract-transformers.workspace = true\ntract-gpu.workspace = true\nlog.workspace = true\ncudarc.workspace = true\ninventory.workspace = true\nlibloading.workspace = true\n\n\n[dev-dependencies]\ncriterion.workspace = true\nproptest.workspace = true\nrand.workspace = true\n\n[features]\ncuda-12060 = [\"cudarc/cuda-12060\"]\ndefault = [\"cuda-12060\"]\n\n[[bench]]\nname = \"cuda_flash\"\nharness = false\n"
  },
  {
    "path": "cuda/benches/cuda_flash.rs",
    "content": "use criterion::measurement::WallTime;\nuse criterion::*;\n\nuse tract_core::internal::*;\nuse tract_cuda::kernels::flash_attn::CudaFlashAttn;\nuse tract_cuda::kernels::ggml_flash_attn::GgmlFlashAttn;\nuse tract_cuda::with_cuda_stream;\nuse tract_gpu::tensor::IntoDevice;\n\npub fn cuda_ggml_flash(\n    crit: &mut BenchmarkGroup<WallTime>,\n    batch: usize,\n    q_heads: usize,\n    kv_heads: usize,\n    past_seq_len: usize,\n    seq_len: usize,\n    out_dim: usize,\n) {\n    with_cuda_stream(|stream| {\n        let q = Tensor::zero_dt(DatumType::F32, &[batch, q_heads, seq_len, out_dim]).unwrap();\n        let k = Tensor::zero_dt(\n            DatumType::F16,\n            &[batch, kv_heads, (past_seq_len + seq_len).next_multiple_of(256), out_dim],\n        )\n        .unwrap();\n        let v = Tensor::zero_dt(\n            DatumType::F16,\n            &[batch, kv_heads, (past_seq_len + seq_len).next_multiple_of(256), out_dim],\n        )\n        .unwrap();\n        let mask = Tensor::zero_dt(\n            DatumType::F16,\n            &[1, 1, seq_len.next_multiple_of(16), (past_seq_len + seq_len).next_multiple_of(256)],\n        )\n        .unwrap();\n\n        let cuda_q = q.into_device().unwrap();\n        let cuda_k = k.into_device().unwrap();\n        let cuda_v = v.into_device().unwrap();\n        let cuda_mask = mask.into_device().unwrap();\n\n        crit.bench_function(&format!(\"tract_cuda_ggml_flash\"), |be| {\n            be.iter(|| {\n                let _ =\n                    GgmlFlashAttn.eval(stream, &cuda_q, &cuda_k, &cuda_v, &cuda_mask, 1.0).unwrap();\n            });\n        });\n        Ok(())\n    })\n    .unwrap()\n}\n\npub fn cuda_minimal_flash(\n    crit: &mut BenchmarkGroup<WallTime>,\n    batch: usize,\n    q_heads: usize,\n    kv_heads: usize,\n    past_seq_len: usize,\n    seq_len: usize,\n    out_dim: usize,\n) {\n    with_cuda_stream(|stream| {\n        let q = Tensor::zero_dt(DatumType::F16, &[batch, q_heads, seq_len, out_dim]).unwrap();\n        let k =\n            Tensor::zero_dt(DatumType::F16, &[batch, kv_heads, past_seq_len + seq_len, out_dim])\n                .unwrap();\n        let v =\n            Tensor::zero_dt(DatumType::F16, &[batch, kv_heads, past_seq_len + seq_len, out_dim])\n                .unwrap();\n        let mask =\n            Tensor::zero_dt(DatumType::F16, &[1, 1, seq_len, past_seq_len + seq_len]).unwrap();\n\n        let cuda_q = q.into_device().unwrap();\n        let cuda_k = k.into_device().unwrap();\n        let cuda_v = v.into_device().unwrap();\n        let cuda_mask = mask.into_device().unwrap();\n\n        crit.bench_function(&format!(\"tract_cuda_minimal_flash\"), |be| {\n            be.iter(|| {\n                let _ = CudaFlashAttn\n                    .eval(stream, &cuda_q, &cuda_k, &cuda_v, Some(&cuda_mask), 1.0, false)\n                    .unwrap();\n            });\n        });\n        Ok(())\n    })\n    .unwrap()\n}\n\nfn flash_attn(\n    c: &mut Criterion,\n    b: usize,\n    qh: usize,\n    kh: usize,\n    p: usize,\n    s: usize,\n    out_dim: usize,\n) {\n    let mut c = c.benchmark_group(format!(\n        \"Batch: {b}. Q_head: {qh} KV_head: {kh} P: {p} S {s} d: {out_dim}\"\n    ));\n    c.throughput(Throughput::Elements((4 * b * qh * s * (s + p) * out_dim) as _));\n\n    cuda_ggml_flash(&mut c, b, qh, kh, p, s, out_dim);\n    cuda_minimal_flash(&mut c, b, qh, kh, p, s, out_dim);\n    c.finish();\n}\n\n#[allow(unused)]\nfn small(c: &mut Criterion) {\n    let shapes = vec![\n        (1, 1, 1, 0, 128, 128),\n        (1, 1, 1, 64, 64, 128),\n        (1, 1, 1, 32, 128, 128),\n        (1, 1, 1, 128, 128, 128),\n        (1, 1, 1, 64, 64, 128),\n        (1, 1, 1, 0, 256, 128),\n        (1, 32, 4, 0, 512, 128),\n        (2, 32, 4, 0, 512, 128),\n        (1, 32, 4, 256, 256, 64),\n    ];\n    for (b, qh, kh, p, s, out_dim) in shapes {\n        flash_attn(c, b, qh, kh, p, s, out_dim);\n    }\n}\n\n#[allow(unused)]\nfn benched_models_pp(c: &mut Criterion) {\n    let shapes = vec![\n        // Llama3.2-1B\n        (1, 32, 8, 0, 512, 64),\n        (1, 32, 8, 0, 2048, 64),\n        (1, 32, 8, 0, 1, 64),\n        (1, 32, 8, 127, 1, 64),\n        // Llama3.1-8B / Qwen3-7B\n        (1, 32, 8, 0, 512, 128),\n        (1, 32, 8, 0, 2048, 128),\n        (1, 32, 8, 0, 1, 128),\n        (1, 32, 8, 127, 1, 128),\n        // OpenELM-270M\n        (1, 20, 20, 0, 512, 64),\n        (1, 20, 20, 0, 2048, 64),\n        (1, 20, 20, 0, 1, 64),\n        (1, 20, 20, 127, 1, 64),\n        // Qwen2.5-7B\n        (1, 28, 4, 0, 512, 128),\n        (1, 28, 4, 0, 2048, 128),\n        (1, 28, 4, 0, 1, 128),\n        (1, 28, 4, 127, 1, 128),\n        // Qwen3-1.7B\n        (1, 16, 8, 0, 512, 128),\n        (1, 16, 8, 0, 2048, 128),\n        (1, 16, 8, 0, 1, 128),\n        (1, 16, 8, 127, 1, 128),\n    ];\n    for (b, qh, kh, p, s, out_dim) in shapes {\n        flash_attn(c, b, qh, kh, p, s, out_dim);\n    }\n}\n\ncriterion_group!(benches, benched_models_pp);\ncriterion_main!(benches);\n"
  },
  {
    "path": "cuda/src/context.rs",
    "content": "use cudarc::cublas::CudaBlas;\nuse cudarc::cudnn::Cudnn;\nuse cudarc::nvrtc::Ptx;\nuse cudarc::runtime::result::device::get_device_prop;\nuse cudarc::runtime::sys::cudaDeviceProp;\nuse tract_gpu::device::DeviceContext;\nuse tract_gpu::tensor::{DeviceTensor, OwnedDeviceTensor};\n\nuse std::cell::{Cell, RefCell};\nuse std::mem::MaybeUninit;\nuse std::ops::Deref;\nuse std::sync::{OnceLock, RwLock};\n\nuse tract_core::internal::*;\n\nuse cudarc::driver::{CudaContext, CudaEvent, CudaFunction, CudaModule, CudaStream};\n\nuse crate::kernels::{COMMON_H, LibraryName, cubin_dir};\nuse crate::tensor::CudaTensor;\n\nuse cudarc::nvrtc::result::{compile_program, destroy_program, get_program_log};\nuse cudarc::nvrtc::sys::{\n    nvrtcCreateProgram, nvrtcGetCUBIN, nvrtcGetCUBINSize, nvrtcProgram, nvrtcResult,\n};\nuse std::ffi::{CStr, CString, c_char};\nuse std::path::{Path, PathBuf};\n\npub fn cuda_context() -> &'static TractCudaContext {\n    static INSTANCE: OnceLock<TractCudaContext> = OnceLock::new();\n    INSTANCE.get_or_init(|| {\n        let ctxt = TractCudaContext::new().expect(\"Could not create CUDA context\");\n        tract_gpu::device::set_context(Box::new(ctxt.clone())).expect(\"Could not set CUDA context\");\n        ctxt\n    })\n}\n\nthread_local! {\n    static CUDA_STREAM: TractCudaStream = TractCudaStream::new().expect(\"Could not create Cuda Stream\");\n}\n\npub fn with_cuda_stream<R>(f: impl FnOnce(&TractCudaStream) -> TractResult<R>) -> TractResult<R> {\n    CUDA_STREAM.with(|cell| f(cell))\n}\n\n#[derive(Debug, Clone)]\npub struct TractCudaContext {\n    inner: Arc<CudaContext>,\n    device_properties: cudaDeviceProp,\n    cached_modules: Arc<RwLock<HashMap<LibraryName, Arc<CudaModule>>>>,\n    #[allow(clippy::type_complexity)]\n    cached_pipelines: Arc<RwLock<HashMap<(LibraryName, String), Arc<CudaFunction>>>>,\n}\n\nimpl Deref for TractCudaContext {\n    type Target = Arc<CudaContext>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\nimpl TractCudaContext {\n    pub fn new() -> TractResult<Self> {\n        let context =\n            CudaContext::new(0).with_context(|| \"Could not find system default CUDA device\")?;\n\n        let prop = get_device_prop(0)?;\n        let ctxt = Self {\n            inner: context,\n            device_properties: prop,\n            cached_modules: Arc::new(RwLock::new(HashMap::new())),\n            cached_pipelines: Arc::new(RwLock::new(HashMap::new())),\n        };\n        ctxt.compile_cubins()?;\n        ctxt.preload_pipelines()?;\n        Ok(ctxt)\n    }\n\n    pub fn properties(&self) -> &cudaDeviceProp {\n        &self.device_properties\n    }\n\n    pub fn compile_cubins(&self) -> TractResult<()> {\n        let cubin_dir = cubin_dir();\n        if !cubin_dir.exists() {\n            log::info!(\"Creating cache folder for CUDA cubins at {}\", cubin_dir.display());\n            std::fs::create_dir_all(cubin_dir)\n                .with_context(|| format!(\"Failed to create {}\", cubin_dir.display()))?;\n        }\n\n        let nvrtc_opts = self.build_nvrtc_opts()?;\n\n        for lib in LibraryName::ALL {\n            let out_path = lib.cubin_path();\n            if out_path.exists() {\n                continue;\n            }\n\n            log::info!(\"Compiling {:?} to {}…\", lib, out_path.display());\n\n            let mut input = lib.content().to_string();\n            if input.contains(\"// liquid:true\") {\n                let parser = liquid::ParserBuilder::with_stdlib().build()?;\n                let tmpl = parser.parse(lib.content())?;\n                let globals = liquid::object!({});\n                input = tmpl.render(&globals)?;\n            }\n\n            let c_src = CString::new(input).context(\"Failed to make CString from CUDA source\")?;\n            let prog = unsafe {\n                let mut prog = MaybeUninit::uninit();\n                nvrtcCreateProgram(\n                    prog.as_mut_ptr(),\n                    c_src.as_ptr(),\n                    std::ptr::null(),\n                    1,\n                    &CString::new(COMMON_H)\n                        .context(\"Failed to make CString from CUDA header\")?\n                        .as_ptr(),\n                    &CString::new(\"common.cuh\")\n                        .context(\"Failed to make CString from CUDA header name\")?\n                        .as_ptr(),\n                )\n                .result()?;\n                prog.assume_init()\n            };\n\n            if let Err(_e) = unsafe { compile_program::<String>(prog, &nvrtc_opts) } {\n                let log = self.read_nvrtc_log(prog).unwrap_or_else(|_| \"<no log>\".into());\n                let _ = unsafe { destroy_program(prog) };\n                return Err(anyhow!(\"NVRTC compilation failed for {:?}:\\n{}\", lib, log));\n            }\n\n            let cubin = unsafe { self.get_cubin_bytes(prog) }\n                .with_context(|| format!(\"Failed to extract CUBIN for {:?}\", lib))?;\n\n            unsafe { destroy_program(prog) }.context(\"nvrtcDestroyProgram failed\")?;\n\n            std::fs::write(&out_path, &cubin)\n                .with_context(|| format!(\"Failed to write {:?}\", out_path))?;\n        }\n\n        Ok(())\n    }\n\n    /// Build NVRTC options: include paths + GPU arch.\n    fn build_nvrtc_opts(&self) -> TractResult<Vec<String>> {\n        let cuda_home = std::env::var_os(\"CUDA_HOME\")\n            .map(PathBuf::from)\n            .or_else(|| std::env::var_os(\"CUDA_PATH\").map(PathBuf::from)) // Windows\n            .or_else(|| {\n                let p = Path::new(\"/usr/local/cuda\");\n                if p.exists() { Some(p.to_path_buf()) } else { None }\n            })\n            .or_else(|| {\n                // Last resort: infer from nvcc location\n                 std::process::Command::new(\"which\").arg(\"nvcc\").output().ok()\n                    .and_then(|nvcc| Path::new(&String::from_utf8(nvcc.stdout).unwrap()).parent().and_then(|bin| bin.parent()).map(|p| p.to_path_buf()))\n            });\n\n        let cuda_inc = cuda_home.unwrap().join(\"include\");\n        if !cuda_inc.exists() {\n            return Err(anyhow!(\"CUDA include dir not found at {:?}\", cuda_inc));\n        }\n\n        let arch = format!(\n            \"--gpu-architecture=sm_{}{}\",\n            self.device_properties.major, self.device_properties.minor\n        );\n\n        Ok(vec![\"--std=c++17\".into(), arch, format!(\"-I{}\", cuda_inc.display())])\n    }\n\n    /// Read the NVRTC program log as String.\n    fn read_nvrtc_log(&self, prog: nvrtcProgram) -> TractResult<String> {\n        let buf: Vec<c_char> =\n            unsafe { get_program_log(prog).context(\"nvrtcGetProgramLog failed\") }?;\n\n        let bytes = unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len()) };\n\n        match CStr::from_bytes_until_nul(bytes) {\n            Ok(cstr) => Ok(cstr.to_string_lossy().into_owned()),\n            Err(_) => Ok(String::from_utf8_lossy(bytes).into_owned()),\n        }\n    }\n\n    /// Extract CUBIN bytes from an NVRTC program.\n    unsafe fn get_cubin_bytes(&self, prog: nvrtcProgram) -> TractResult<Vec<u8>> {\n        let mut len: usize = 0;\n        let res = unsafe { nvrtcGetCUBINSize(prog, &mut len as *mut usize) };\n        if res != nvrtcResult::NVRTC_SUCCESS {\n            return Err(anyhow!(\"nvrtcGetCUBINSize failed ({:?})\", res));\n        }\n\n        let mut cubin = vec![0u8; len];\n        let res = unsafe { nvrtcGetCUBIN(prog, cubin.as_mut_ptr() as *mut c_char) };\n        if res != nvrtcResult::NVRTC_SUCCESS {\n            return Err(anyhow!(\"nvrtcGetCUBIN failed ({:?})\", res));\n        }\n        Ok(cubin)\n    }\n\n    pub fn preload_pipelines(&self) -> TractResult<()> {\n        for ew_func in crate::kernels::element_wise::all_functions() {\n            let _ = self.load_pipeline(LibraryName::ElementWise, ew_func);\n        }\n\n        for bin_func in crate::kernels::binary::all_functions() {\n            let _ = self.load_pipeline(LibraryName::Binary, bin_func);\n        }\n\n        for arr_func in crate::kernels::array::all_functions() {\n            let _ = self.load_pipeline(LibraryName::Array, arr_func);\n        }\n\n        for nn_func in crate::kernels::nn::all_functions() {\n            let _ = self.load_pipeline(LibraryName::NN, nn_func);\n        }\n\n        Ok(())\n    }\n\n    pub fn load_library(&self, name: &LibraryName) -> TractResult<Arc<CudaModule>> {\n        {\n            let cache = self.cached_modules.read().map_err(|e| anyhow!(\"{:?}\", e))?;\n            if let Some(module) = cache.get(name) {\n                return Ok(module.clone());\n            }\n        }\n\n        let module = self.inner.load_module(Ptx::from_file(name.cubin_path()))?;\n\n        let mut cache = self.cached_modules.write().map_err(|e| anyhow!(\"{:?}\", e))?;\n        cache.insert(*name, module.clone());\n\n        Ok(module)\n    }\n\n    pub fn load_pipeline(\n        &self,\n        library_name: LibraryName,\n        func_name: String,\n    ) -> TractResult<Arc<CudaFunction>> {\n        // Check pipeline cache\n        let key = (library_name, func_name.to_string());\n        {\n            let cache = self.cached_pipelines.read().map_err(|e| anyhow!(\"{:?}\", e))?;\n            if let Some(f) = cache.get(&key) {\n                return Ok(f.clone());\n            }\n        }\n\n        // Load module + function\n        let module = self.load_library(&library_name)?;\n        let func =\n            module.load_function(&func_name).map_err(|e| anyhow!(\"{e}\")).with_context(|| {\n                format!(\n                    \"Failed to load function `{func_name}` from library `{}`\",\n                    library_name.cubin_path().display()\n                )\n            })?;\n\n        let func = Arc::new(func);\n\n        // Store in cache\n        let mut cache = self.cached_pipelines.write().map_err(|e| anyhow!(\"{:?}\", e))?;\n        cache.insert(key, func.clone());\n\n        Ok(func)\n    }\n}\n\nimpl DeviceContext for TractCudaContext {\n    fn synchronize(&self) -> TractResult<()> {\n        with_cuda_stream(|stream| stream.synchronize().map_err(|e| e.into()))\n    }\n\n    fn tensor_to_device(&self, tensor: TValue) -> TractResult<Box<dyn OwnedDeviceTensor>> {\n        ensure!(DeviceTensor::is_supported_dt(tensor.datum_type()));\n        Ok(Box::new(CudaTensor::from_tensor(tensor.view().tensor)?))\n    }\n\n    fn uninitialized_device_tensor(\n        &self,\n        shape: &[usize],\n        dt: DatumType,\n    ) -> TractResult<Box<dyn OwnedDeviceTensor>> {\n        Ok(Box::new(CudaTensor::uninitialized_dt(shape, dt)?))\n    }\n\n    fn uninitialized_device_exotic_tensor(\n        &self,\n        exotic_fact: Box<dyn ExoticFact>,\n    ) -> TractResult<Box<dyn OwnedDeviceTensor>> {\n        Ok(Box::new(CudaTensor::uninitialized_exotic(exotic_fact)?))\n    }\n\n    fn copy_nd(\n        &self,\n        input: &DeviceTensor,\n        input_offset: usize,\n        input_strides: &[isize],\n        output: &DeviceTensor,\n        output_offset: usize,\n        output_shape: &[usize],\n        output_strides: &[isize],\n    ) -> TractResult<()> {\n        crate::kernels::array::cuda_copy_nd_dispatch(\n            input,\n            input_offset,\n            input_strides,\n            output,\n            output_offset,\n            output_shape,\n            output_strides,\n        )\n    }\n}\n\n/// A recorded GPU kernel timing entry: start/end events tagged with a node_id.\npub struct GpuProfileEntry {\n    pub node_id: usize,\n    pub start: CudaEvent,\n    pub end: CudaEvent,\n}\n\npub struct TractCudaStream {\n    inner: Arc<CudaStream>,\n    cublas: CudaBlas,\n    cudnn: Arc<Cudnn>,\n    /// When Some, kernel launches record start/end events here.\n    profile_log: RefCell<Option<Vec<GpuProfileEntry>>>,\n    /// The node_id currently being evaluated (set by the profiling harness).\n    current_node_id: Cell<usize>,\n}\n\nimpl TractCudaStream {\n    fn new() -> TractResult<TractCudaStream> {\n        let stream = cuda_context().default_stream();\n        let cublas = CudaBlas::new(stream.clone())?;\n        let cudnn = Cudnn::new(stream.clone())?;\n        Ok(TractCudaStream {\n            inner: stream,\n            cublas,\n            cudnn,\n            profile_log: RefCell::new(None),\n            current_node_id: Cell::new(0),\n        })\n    }\n\n    pub fn cublas(&self) -> &CudaBlas {\n        &self.cublas\n    }\n\n    pub fn cudnn(&self) -> &Arc<Cudnn> {\n        &self.cudnn\n    }\n\n    /// Enable GPU profiling. Kernel launches will record timing events.\n    pub fn enable_profiling(&self) {\n        *self.profile_log.borrow_mut() = Some(Vec::new());\n    }\n\n    /// Set the current node being evaluated (used by the profiling harness).\n    pub fn set_current_node(&self, node_id: usize) {\n        self.current_node_id.set(node_id);\n    }\n\n    /// Returns true if profiling is active.\n    pub fn is_profiling(&self) -> bool {\n        self.profile_log.borrow().is_some()\n    }\n\n    /// Record a start/end event pair around a kernel launch.\n    /// Call this from `TractLaunchArgs::launch()` when profiling is active.\n    pub fn record_profile_events(&self) -> TractResult<Option<(CudaEvent, CudaEvent)>> {\n        if !self.is_profiling() {\n            return Ok(None);\n        }\n        let flags = Some(cudarc::driver::sys::CUevent_flags::CU_EVENT_DEFAULT);\n        let start = self.inner.record_event(flags)?;\n        Ok(Some((start, self.inner.context().new_event(flags)?)))\n    }\n\n    /// Finish recording a profile entry (call after kernel launch).\n    pub fn finish_profile_entry(&self, start: CudaEvent, end: CudaEvent) -> TractResult<()> {\n        end.record(&self.inner)?;\n        let node_id = self.current_node_id.get();\n        self.profile_log.borrow_mut().as_mut().unwrap().push(GpuProfileEntry {\n            node_id,\n            start,\n            end,\n        });\n        Ok(())\n    }\n\n    /// Drain all recorded profile entries. Caller should synchronize first.\n    pub fn drain_profile(&self) -> Option<Vec<GpuProfileEntry>> {\n        self.profile_log.borrow_mut().as_mut().map(|log| std::mem::take(log))\n    }\n}\n\nimpl Deref for TractCudaStream {\n    type Target = Arc<CudaStream>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/array/cast.rs",
    "content": "use cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse derive_new::new;\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{LibraryName, get_cuda_view, launch_args};\n\n#[derive(Debug, Clone, new, PartialEq, Eq, Hash)]\npub struct Cast;\n\nimpl fmt::Display for Cast {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl Cast {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(\n            dt,\n            DatumType::F32\n                | DatumType::F16\n                | DatumType::U8\n                | DatumType::U16\n                | DatumType::U32\n                | DatumType::U64\n                | DatumType::I8\n                | DatumType::I16\n                | DatumType::I32\n                | DatumType::I64\n                | DatumType::Bool\n        )\n    }\n\n    pub fn kernel_name(&self, from_dt: DatumType, to_dt: DatumType) -> TractResult<String> {\n        ensure!(\n            Self::is_supported_dt(from_dt),\n            \"Unsupported from_dt {:?} for cuda castop\",\n            from_dt\n        );\n        ensure!(Self::is_supported_dt(to_dt), \"Unsupported to_dt {:?} for cuda castop\", to_dt);\n        let from_tname = DeviceTensor::tname(from_dt)?;\n        let to_tname = DeviceTensor::tname(to_dt)?;\n        Ok(format!(\"cast_{from_tname}_{to_tname}\"))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        to_dt: DatumType,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(to_dt, input.shape())? };\n        self.dispatch_eval(stream, input, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(\n            input.shape() == output.shape(),\n            \"Cast I/O don't have the same shape in: {:?}, out: {:?}\",\n            input.shape(),\n            output.shape()\n        );\n\n        let kernel_name = self.kernel_name(input.datum_type(), output.datum_type())?;\n\n        let i_view = get_cuda_view(input);\n        let o_view = get_cuda_view(output);\n        let len = output.len();\n        let func = cuda_context().load_pipeline(LibraryName::Array, kernel_name)?;\n\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_i32(len);\n        let cfg = LaunchConfig::for_num_elems(len as _);\n\n        launch_args.launch(cfg)\n    }\n}\n\npub fn cuda_cast_dispatch(input: &DeviceTensor, output: &DeviceTensor) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| Cast.dispatch_eval(stream, input, output))\n}\n\ncrate::register_cuda_op!(tract_core::ops::cast::Cast, |_source, _node, op| {\n    Ok(crate::transform::cuda_cast_new(op.to).map(|c| Box::new(c) as _))\n});\n\n#[cfg(test)]\nmod tests {\n\n    use super::*;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_itertools::Itertools;\n\n    use num_traits::{FromPrimitive, Zero};\n\n    use tract_core::internal::Tensor;\n\n    fn run_test_case<T0: Datum + Copy + FromPrimitive, T1: Datum>(\n        shape: &[usize],\n    ) -> TractResult<()> {\n        crate::with_cuda_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n            let data = (0..len).map(|f| T0::from_f32(f as f32 / 2.).unwrap()).collect::<Vec<_>>();\n            let input = Tensor::from_shape(shape, &data)?;\n\n            let output = Cast {}.eval(stream, &input.clone().into_device()?, T1::datum_type())?;\n\n            assert_eq!(\n                output.to_host()?.into_tensor(),\n                input.cast_to_dt(T1::datum_type())?.into_owned()\n            );\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_cast() -> TractResult<()> {\n        run_test_case::<f16, f32>(&[3, 4])?;\n        run_test_case::<u8, f32>(&[2, 5])?;\n        run_test_case::<f16, u32>(&[3, 2, 2])?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/array/copy.rs",
    "content": "use cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse derive_new::new;\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::{LibraryName, get_cuda_view, get_cuda_view_mut, get_sliced_cuda_view};\n\n#[derive(Debug, Clone, new, PartialEq, Eq, Hash)]\npub struct Memcpy;\n\nimpl fmt::Display for Memcpy {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl Memcpy {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(\n            dt,\n            DatumType::F32\n                | DatumType::F16\n                | DatumType::U8\n                | DatumType::U16\n                | DatumType::U32\n                | DatumType::U64\n                | DatumType::I8\n                | DatumType::I16\n                | DatumType::I32\n                | DatumType::I64\n                | DatumType::Bool\n        )\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        input_offset: usize,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(input_offset % input.datum_type().size_of() == 0);\n        ensure!(output.len() <= input.len() - (input_offset / input.datum_type().size_of()));\n        ensure!(\n            Self::is_supported_dt(input.datum_type()),\n            \"Unsupported dt {:?} for cuda memcpy\",\n            input.datum_type()\n        );\n\n        let i_view = get_sliced_cuda_view(\n            input,\n            input_offset,\n            input.len() * input.datum_type().size_of() - input_offset,\n        )?;\n        let mut o_view = get_cuda_view_mut(output);\n        let len = output.len();\n        stream.memcpy_dtod(&i_view, &mut o_view);\n\n        Ok(())\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        input_offset: usize,\n        output_shape: &[usize],\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), output_shape)? };\n        self.dispatch_eval(stream, input, input_offset, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n}\n\npub fn cuda_memcpy_dispatch(\n    input: &DeviceTensor,\n    input_offset: usize,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| Memcpy.dispatch_eval(stream, input, input_offset, output))\n}\n\n#[cfg(test)]\nmod tests {\n\n    use super::*;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_itertools::Itertools;\n\n    use num_traits::Zero;\n\n    use tract_core::internal::Tensor;\n\n    fn run_test_case(shape: &[usize], offset: usize) -> TractResult<()> {\n        crate::with_cuda_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n            let data = (0..len).map(|f| f as f32).collect::<Vec<_>>();\n            let input = Tensor::from_shape(shape, &data)?;\n\n            let output = Memcpy {}.eval(\n                stream,\n                &input.clone().into_device()?,\n                offset,\n                &[len - (offset / size_of::<f32>())],\n            )?;\n\n            assert_eq!(\n                output.to_host()?.into_tensor(),\n                input.into_shape(&[len])?.slice(0, offset / size_of::<f32>(), len)?\n            );\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_cpy() -> TractResult<()> {\n        run_test_case(&[3, 4], 0)?;\n        run_test_case(&[2, 5], 2 * size_of::<f32>())?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/array/dispatch.rs",
    "content": "use crate::context::cuda_context;\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::utils::cuda_launch_cfg_for_cpy;\nuse crate::kernels::{BroadcastKind, LibraryName, get_sliced_cuda_view};\nuse cudarc::driver::PushKernelArg;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n/// Single dispatch function for all copy_nd kernel launches.\n/// Used by GpuMultiBroadcastTo, GpuSlice, GpuConcat, and GpuAxisOp.\npub fn cuda_copy_nd_dispatch(\n    input: &DeviceTensor,\n    input_offset: usize,\n    input_strides: &[isize],\n    output: &DeviceTensor,\n    output_offset: usize,\n    output_shape: &[usize],\n    output_strides: &[isize],\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| {\n        let kernel_name = BroadcastKind::from_rank(output_shape.len())?\n            .copy_kernel_name(input.datum_type(), \"\")?;\n        let func = cuda_context().load_pipeline(LibraryName::Array, kernel_name)?;\n\n        let i_view = get_sliced_cuda_view(\n            input,\n            input_offset,\n            input.len() * input.datum_type().size_of() - input_offset,\n        )?;\n        let o_view = get_sliced_cuda_view(\n            output,\n            output_offset,\n            output.len() * output.datum_type().size_of() - output_offset,\n        )?;\n\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_slice_i32(input_strides);\n        launch_args.push_slice_i32(output_shape);\n        launch_args.push_slice_i32(output_strides);\n\n        let cfg = cuda_launch_cfg_for_cpy(output_shape);\n        launch_args.launch(cfg)\n    })\n}\n"
  },
  {
    "path": "cuda/src/kernels/array/mod.rs",
    "content": "mod cast;\nmod copy;\nmod dispatch;\nmod rotate_half;\n\npub use cast::Cast;\npub use cast::cuda_cast_dispatch;\npub use copy::Memcpy;\npub use dispatch::cuda_copy_nd_dispatch;\npub use rotate_half::RotateHalf;\npub use rotate_half::cuda_rotate_half_dispatch;\n\npub fn all_functions() -> Vec<String> {\n    use std::collections::HashSet;\n    use tract_gpu::utils::BroadcastKind;\n    let mut functions = HashSet::<String>::new();\n\n    functions.extend(BroadcastKind::all_copy_kernel_names(\"\"));\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt1| {\n                tract_gpu::tensor::DeviceTensor::SUPPORTED_DT.into_iter().map(move |dt2| (dt1, dt2))\n            })\n            .flat_map(|(dt1, dt2)| Cast.kernel_name(dt1, dt2).into_iter()),\n    );\n\n    functions.into_iter().collect()\n}\n"
  },
  {
    "path": "cuda/src/kernels/array/rotate_half.rs",
    "content": "use crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{LibraryName, get_cuda_view, utils};\nuse anyhow::ensure;\nuse cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct RotateHalf;\n\nimpl fmt::Display for RotateHalf {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl RotateHalf {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(\n            dt,\n            DatumType::F32\n                | DatumType::F16\n                | DatumType::I8\n                | DatumType::I16\n                | DatumType::I32\n                | DatumType::I64\n        )\n    }\n\n    pub fn kernel_name(&self, dt: DatumType) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for cuda rotate halfop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        Ok(format!(\"rotate_half_nd2_{tname}\"))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        let shape_nd2 = utils::reshape_to_rank_2(input.shape(), input.rank() - 1);\n        ensure!(\n            shape_nd2[1].is_multiple_of(2),\n            \"Rotate half required most inner dimension to be a multiple of 2: {:?}\",\n            input.shape()\n        );\n        let strides_nd2 = Tensor::natural_strides(&shape_nd2);\n\n        let kernel_name = self.kernel_name(input.datum_type())?;\n\n        let func = cuda_context().load_pipeline(LibraryName::Array, kernel_name)?;\n\n        let i_view = get_cuda_view(input);\n        let o_view = get_cuda_view(output);\n\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_slice_i32(&shape_nd2);\n        launch_args.push_slice_i32(&strides_nd2);\n\n        let cfg = LaunchConfig {\n            grid_dim: ((shape_nd2[1] / 2) as _, shape_nd2[0] as _, 1),\n            block_dim: (1, 1, 1),\n            shared_mem_bytes: 0,\n        };\n        launch_args.launch(cfg)\n    }\n}\n\npub fn cuda_rotate_half_dispatch(input: &DeviceTensor, output: &DeviceTensor) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| RotateHalf.dispatch_eval(stream, input, output))\n}\n\ncrate::register_cuda_op!(tract_transformers::ops::apply_rope::RotateHalf, |source, node, _op| {\n    rule_if!(RotateHalf::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::rotate_half::GpuRotateHalf::new(\n        \"Cuda\",\n        cuda_rotate_half_dispatch,\n    ))))\n});\n\n#[cfg(test)]\nmod tests {\n\n    use super::*;\n    use num_traits::AsPrimitive;\n    use tract_core::internal::Tensor;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_transformers::ops::apply_rope;\n\n    fn run_test_case<F>(shape: &[usize]) -> TractResult<()>\n    where\n        F: Copy + 'static + Datum,\n        usize: AsPrimitive<F>,\n    {\n        crate::with_cuda_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a =\n                Tensor::from_shape(shape, &(0..len).map(|f| -> F { f.as_() }).collect::<Vec<_>>())?;\n\n            let cuda_a = a.clone().into_device()?;\n\n            let cpu_output =\n                apply_rope::RotateHalf.eval(tvec![a.clone().into()])?[0].clone().into_tensor();\n            let cuda_output = RotateHalf.eval(stream, &cuda_a)?;\n\n            cpu_output\n                .close_enough(&cuda_output.to_host()?.into_tensor(), Approximation::Exact)\n                .with_context(|| {\n                    format!(\n                        \"Input: {:?} Cpu: {:?}, Cuda: {:?}\",\n                        a.dump(true),\n                        cpu_output.dump(true),\n                        cuda_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_rotate_half() -> TractResult<()> {\n        run_test_case::<f32>(&[2, 2])?;\n        run_test_case::<f32>(&[512, 512])?;\n        run_test_case::<f32>(&[10, 8, 8])?;\n        run_test_case::<f32>(&[10, 512, 1024])?;\n        run_test_case::<f32>(&[10, 512, 1024])?;\n        run_test_case::<f16>(&[10, 256, 4])?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/binary.rs",
    "content": "use cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse tract_core::internal::*;\nuse tract_core::ops::binary::BinMiniOp;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{LibraryName, get_cuda_view};\n\nstatic BINARY_MAX_RANK: usize = 5;\n\nconst ALL_OP_NAMES: &[&str] = &[\n    \"mul\", \"add\", \"div\", \"sub\", \"pow\", \"min\", \"max\", \"gt\", \"gte\", \"eq\", \"ne\", \"lt\", \"lte\", \"and\",\n    \"or\", \"bitor\", \"bitand\", \"bitxor\",\n];\n\npub fn all_functions() -> Vec<String> {\n    ALL_OP_NAMES\n        .iter()\n        .flat_map(|kname| {\n            DeviceTensor::SUPPORTED_DT.into_iter().flat_map(move |dt| {\n                let tname = DeviceTensor::tname(dt).ok()?;\n                Some(\n                    [\"large\", \"generic\"]\n                        .into_iter()\n                        .map(move |variant| format!(\"binary_{kname}_{variant}_{tname}\")),\n                )\n            })\n        })\n        .flatten()\n        .collect()\n}\n\npub fn is_supported(mini_op: &dyn BinMiniOp, dt: DatumType) -> bool {\n    ALL_OP_NAMES.contains(&mini_op.name().to_lowercase().as_str())\n        && (dt.is_number() || dt.is::<bool>())\n}\n\npub fn dispatch_eval(\n    stream: &TractCudaStream,\n    mini_op: &dyn BinMiniOp,\n    lhs: &DeviceTensor,\n    rhs: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    let rank = lhs.rank();\n    ensure!(rank == rhs.rank());\n    ensure!(rank <= BINARY_MAX_RANK);\n\n    let rank_offset = BINARY_MAX_RANK - rank;\n    let mut lhs_shape = [1usize; BINARY_MAX_RANK];\n    let mut rhs_shape = [1usize; BINARY_MAX_RANK];\n    let mut out_shape = [1usize; BINARY_MAX_RANK];\n    let mut lhs_strides = [0isize; BINARY_MAX_RANK];\n    let mut rhs_strides = [0isize; BINARY_MAX_RANK];\n    let mut out_strides = [0isize; BINARY_MAX_RANK];\n\n    let base_l_shape = lhs.shape();\n    let base_r_shape = rhs.shape();\n    let base_o_shape = output.shape();\n    let base_l_strides = lhs.strides();\n    let base_r_strides = rhs.strides();\n    let base_o_strides = output.strides();\n    for i in 0..rank {\n        let dst = rank_offset + i;\n        lhs_shape[dst] = base_l_shape[i];\n        rhs_shape[dst] = base_r_shape[i];\n        out_shape[dst] = base_o_shape[i];\n        lhs_strides[dst] =\n            if base_l_shape[i] == 1 && base_r_shape[i] != 1 { 0 } else { base_l_strides[i] };\n        rhs_strides[dst] =\n            if base_r_shape[i] == 1 && base_l_shape[i] != 1 { 0 } else { base_r_strides[i] };\n        out_strides[dst] = base_o_strides[i];\n    }\n\n    let total_elems: usize = out_shape.iter().product();\n    let block_dim = (128_u32, 1, 1);\n    let (grid_dim, variant) = if out_shape[BINARY_MAX_RANK - 1] >= 256 && total_elems >= 4096 {\n        (\n            (\n                out_shape[BINARY_MAX_RANK - 2] as u32,\n                out_shape[BINARY_MAX_RANK - 3] as u32,\n                out_shape[..BINARY_MAX_RANK - 3].iter().product::<usize>() as u32,\n            ),\n            \"large\",\n        )\n    } else {\n        ((total_elems.div_ceil(block_dim.0 as usize) as u32, 1, 1), \"generic\")\n    };\n\n    let op_name = mini_op.name().to_lowercase();\n    let tname = DeviceTensor::tname(lhs.datum_type())?;\n    let kname = format!(\"binary_{op_name}_{variant}_{tname}\");\n    let func = cuda_context().load_pipeline(LibraryName::Binary, kname)?;\n\n    let cfg = LaunchConfig { grid_dim, block_dim, shared_mem_bytes: 0 };\n\n    let lhs_view = get_cuda_view(lhs);\n    let rhs_view = get_cuda_view(rhs);\n    let out_view = get_cuda_view(output);\n\n    let mut launch_args = TractLaunchArgs::new(stream, &func);\n    launch_args.push_view(&lhs_view);\n    launch_args.push_view(&rhs_view);\n    launch_args.push_view(&out_view);\n    launch_args.push_slice_i32(&rhs_shape);\n    launch_args.push_slice_i32(&out_shape);\n    launch_args.push_slice_i32(&lhs_strides);\n    launch_args.push_slice_i32(&rhs_strides);\n    launch_args.push_slice_i32(&out_strides);\n\n    launch_args.launch(cfg)?;\n\n    Ok(())\n}\n\npub fn cuda_bin_op_dispatch(\n    mini_op: &dyn BinMiniOp,\n    lhs: &DeviceTensor,\n    rhs: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| dispatch_eval(stream, mini_op, lhs, rhs, output))\n}\n\npub fn cuda_bin_op(mini_op: Box<dyn BinMiniOp>) -> tract_gpu::ops::binary::GpuBinOp {\n    tract_gpu::ops::binary::GpuBinOp::new(mini_op, \"Cuda\", cuda_bin_op_dispatch)\n}\n\ncrate::register_cuda_op!(tract_core::ops::binary::TypedBinOp, |source, node, op| {\n    rule_if!(is_supported(&*op.0, source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(cuda_bin_op(op.0.clone()))))\n});\n\n#[cfg(test)]\nmod tests {\n    use tract_gpu::tensor::IntoDevice;\n\n    use super::*;\n    use crate::with_cuda_stream;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n\n    fn test_case<F>(\n        mini_op: &dyn BinMiniOp,\n        shape: &[usize],\n        offset: f32,\n        scale: f32,\n    ) -> TractResult<()>\n    where\n        F: Float + Datum,\n        usize: AsPrimitive<f32>,\n        f32: AsPrimitive<F>,\n    {\n        with_cuda_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v * scale + offset).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let b = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v * scale + offset + 1.0).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let out_dt = mini_op.result_datum_type(a.datum_type(), b.datum_type())?;\n            let output = unsafe { DeviceTensor::uninitialized_dt(out_dt, shape)? };\n            dispatch_eval(stream, mini_op, &a, &b, &output)?;\n            stream.synchronize()?;\n\n            let out = output.to_host()?.into_tensor();\n            assert_eq!(out.shape(), shape);\n            Ok(())\n        })\n    }\n\n    use tract_core::ops::math;\n\n    #[test]\n    fn test_binary_add() -> TractResult<()> {\n        test_case::<f32>(&math::Add, &[4, 4], 0.0, 1.0)?;\n        test_case::<f16>(&math::Add, &[4, 4], 0.0, 1.0 / 100.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_binary_mul() -> TractResult<()> {\n        test_case::<f32>(&math::Mul, &[4, 4], 0.0, 1.0)?;\n        test_case::<f16>(&math::Mul, &[4, 4], 0.0, 1.0 / 100.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_binary_sub() -> TractResult<()> {\n        test_case::<f32>(&math::Sub, &[4, 4], 0.0, 1.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_binary_min() -> TractResult<()> {\n        test_case::<f32>(&math::Min, &[4, 4], 0.0, 1.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_binary_max() -> TractResult<()> {\n        test_case::<f32>(&math::Max, &[4, 4], 0.0, 1.0)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/conv.rs",
    "content": "use crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{WARP_SIZE, get_cuda_view};\nuse cudarc::driver::{LaunchArgs, LaunchConfig, PushKernelArg};\nuse downcast_rs::{Downcast, impl_downcast};\nuse dyn_eq::DynEq;\nuse std::any::Any;\nuse std::fmt::Debug;\nuse tract_core::dyn_clone::{self, DynClone};\nuse tract_core::internal::*;\nuse tract_core::ops::cnn::Conv;\nuse tract_gpu::tensor::DeviceTensor;\n\npub trait ConvKernelScratch: Debug + Downcast {}\nimpl_downcast!(ConvKernelScratch);\n\npub trait ConvKernel: 'static + Send + Sync + Debug + DynClone + DynEq {\n    fn name(&self) -> StaticName;\n    #[allow(clippy::too_many_arguments)]\n    fn state(&self) -> Box<dyn ConvKernelScratch>;\n    #[allow(clippy::too_many_arguments)]\n    fn dispatch(\n        &self,\n        state: &mut dyn ConvKernelScratch,\n        node_id: usize,\n        op: &Conv,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        weights: &DeviceTensor,\n        bias: Option<&DeviceTensor>,\n        output: &DeviceTensor,\n    ) -> TractResult<()>;\n}\ndyn_clone::clone_trait_object!(ConvKernel);\ndyn_eq::eq_trait_object!(ConvKernel);\n\nimpl ConvKernelScratch for () {}\n\n#[derive(Hash, Clone, Debug, PartialEq, Eq)]\npub struct ConvGeneric;\n\nimpl ConvKernel for ConvGeneric {\n    fn name(&self) -> StaticName {\n        \"Generic\".into()\n    }\n\n    fn state(&self) -> Box<dyn ConvKernelScratch> {\n        Box::new(())\n    }\n\n    fn dispatch(\n        &self,\n        _state: &mut dyn ConvKernelScratch,\n        _node_id: usize,\n        op: &Conv,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        weights: &DeviceTensor,\n        bias: Option<&DeviceTensor>,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        let input_shape = op.pool_spec.data_format.shape(input.shape())?;\n\n        let ctx = cuda_context();\n        let dt_name = if input.datum_type() == DatumType::F16 { \"f16\" } else { \"f32\" };\n        let func_name = format!(\"conv{}d_{}_generic\", input_shape.hw_rank(), dt_name);\n        let func = ctx.load_pipeline(crate::kernels::LibraryName::Cnn, func_name)?;\n        let null = stream.null::<u8>()?;\n        let null_view = null.as_view();\n\n        let mut launcher = TractLaunchArgs::new(stream, &func);\n\n        let input = get_cuda_view(input);\n\n        launcher.push_view(&input);\n        launcher.push_i32(*input_shape.n().unwrap_or(&1));\n        launcher.push_i32(*input_shape.c());\n        launcher.push_slice_i32(input_shape.hw_dims());\n\n        launcher.push_i32(*input_shape.n_stride().unwrap_or(&0));\n        launcher.push_i32(*input_shape.c_stride());\n        launcher.push_slice_i32(input_shape.hw_strides());\n\n        let kfmt = op.kernel_fmt;\n        let co_per_group = op.pool_spec.output_channels / op.group;\n        let ci_per_group = op.pool_spec.input_channels / op.group;\n\n        let weights_view = get_cuda_view(weights);\n        launcher.push_view(&weights_view);\n        // split go_i_h_w in g_o_i_h_w\n        launcher.push_i32(op.group);\n        launcher.push_i32(co_per_group);\n        launcher.push_slice_i32(&weights.shape()[1..]);\n\n        let group_stride = weights.strides()[0] as usize * co_per_group;\n        launcher.push_i32(group_stride);\n        launcher.push_slice_i32(weights.strides());\n\n        let mut bias_view = None;\n        if let Some(bias) = &bias {\n            bias_view = Some(get_cuda_view(bias));\n            launcher.push_view(bias_view.as_ref().unwrap());\n            launcher.push_i32(if bias.rank() == 0 {\n                0 // scalar bias: stride = 0 is broadcasting\n            } else {\n                1\n            });\n        } else {\n            launcher.push_view(&null_view);\n            launcher.push_i32(0);\n        }\n\n        let padding = op.pool_spec.computed_padding(input_shape.hw_dims());\n        for d in 0..input_shape.hw_rank() {\n            launcher.push_i32(padding[d].pad_before);\n        }\n\n        let strides = op.pool_spec.strides();\n        launcher.push_slice_i32(&strides);\n\n        let dilations = op.pool_spec.dilations();\n        launcher.push_slice_i32(&dilations);\n\n        let output_shape = op.pool_spec.data_format.shape(output.shape())?;\n        let output = get_cuda_view(output);\n        launcher.push_view(&output);\n        launcher.push_i32(*output_shape.n().unwrap_or(&1));\n        launcher.push_i32(*output_shape.c());\n        launcher.push_slice_i32(output_shape.hw_dims());\n\n        launcher.push_i32(*output_shape.n_stride().unwrap_or(&0));\n        launcher.push_i32(*output_shape.c_stride());\n        launcher.push_slice_i32(output_shape.hw_strides());\n\n        let cfg = LaunchConfig {\n            grid_dim: (\n                output_shape.hw_dims().iter().product::<usize>().div_ceil(WARP_SIZE) as u32,\n                *output_shape.c() as u32,\n                input_shape.n().copied().unwrap_or(1) as u32,\n            ),\n            block_dim: (WARP_SIZE as u32, 1, 1),\n            shared_mem_bytes: 0,\n        };\n\n        launcher.launch(cfg)\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/conv_cudnn.rs",
    "content": "use crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::conv::{ConvKernel, ConvKernelScratch};\nuse crate::kernels::{WARP_SIZE, get_cuda_view, get_cuda_view_mut};\nuse cudarc::cudnn::{\n    ConvDescriptor, ConvForward, CudnnDataType, FilterDescriptor, TensorDescriptor,\n};\nuse cudarc::driver::{CudaStream, LaunchArgs, LaunchConfig, PushKernelArg};\nuse std::any::Any;\nuse std::cell::RefCell;\nuse std::fmt::Debug;\nuse std::ops::Deref;\nuse std::sync::Weak;\nuse std::thread::LocalKey;\nuse tract_core::dyn_clone::{self, DynClone};\nuse tract_core::internal::*;\nuse tract_core::ops::cnn::{Conv, KernelFormat};\nuse tract_core::tract_data::half::f16;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug)]\nstruct TypedDescriptors<T: CudnnDataType> {\n    conv: ConvDescriptor<T>,\n    x: TensorDescriptor<T>,\n    w: FilterDescriptor<T>,\n    y: TensorDescriptor<T>,\n}\n\nunsafe impl<T: CudnnDataType> Send for TypedDescriptors<T> {}\n\nfn build_descriptors<T: CudnnDataType>(\n    stream: &TractCudaStream,\n    op: &Conv,\n    input_shape_array: &[usize],\n    weight_shape_array: &[usize],\n    output_shape_array: &[usize],\n) -> TractResult<TypedDescriptors<T>> {\n    ensure!(op.pool_spec.data_format.has_n());\n    ensure!(op.kernel_fmt == KernelFormat::OIHW);\n    let input_shape = op.pool_spec.data_format.shape(input_shape_array)?;\n    let output_shape = op.pool_spec.data_format.shape(output_shape_array)?;\n    ensure!(input_shape.hw_rank() <= 6);\n\n    let cudnn = stream.cudnn();\n    let mut pads = op\n        .pool_spec\n        .computed_padding(input_shape.hw_dims())\n        .iter()\n        .map(|p| p.pad_before as i32)\n        .collect_vec();\n    let mut strides = op.pool_spec.strides().iter().map(|s| *s as i32).collect_vec();\n    let mut dilations = op.pool_spec.dilations().iter().map(|d| *d as i32).collect_vec();\n    if input_shape.hw_rank() == 1 {\n        strides.push(1);\n        dilations.push(1);\n        pads.push(0);\n    }\n    let mut conv_descriptor = cudnn\n        .create_convnd::<T>(\n            &pads,\n            &strides,\n            &dilations,\n            cudarc::cudnn::sys::cudnnConvolutionMode_t::CUDNN_CROSS_CORRELATION,\n        )\n        .context(\"in create_convnd\")?;\n    conv_descriptor.set_group_count(op.group as i32);\n\n    let mut input_dims = input_shape.hw_dims().iter().map(|d| *d as i32).collect_vec();\n    if input_dims.len() == 1 {\n        input_dims.push(1);\n    }\n    input_dims.insert(0, *input_shape.n().unwrap() as i32);\n    input_dims.insert(1, *input_shape.c() as i32);\n\n    let mut input_strides = input_shape.hw_strides().iter().map(|s| *s as i32).collect_vec();\n    if input_strides.len() == 1 {\n        input_strides.push(*input_shape.w_stride() as i32);\n    }\n    input_strides.insert(0, *input_shape.n_stride().unwrap() as i32);\n    input_strides.insert(1, *input_shape.c_stride() as i32);\n\n    let input_descriptor = cudnn\n        .create_nd_tensor::<T>(&input_dims, &input_strides)\n        .context(\"in create_nd_tensor for input\")?;\n\n    let mut filter_dims = weight_shape_array.iter().map(|d| *d as i32).collect_vec();\n    if filter_dims.len() == 3 {\n        filter_dims.push(1);\n    }\n    let filter_descriptor = cudnn\n        .create_nd_filter::<T>(\n            cudarc::cudnn::sys::cudnnTensorFormat_t::CUDNN_TENSOR_NCHW,\n            &filter_dims,\n        )\n        .context(\"in create_nd_filter\")?;\n\n    let mut output_dims = output_shape.hw_dims().iter().map(|d| *d as i32).collect_vec();\n    if output_dims.len() == 1 {\n        output_dims.push(1);\n    }\n    output_dims.insert(0, *output_shape.n().unwrap() as i32);\n    output_dims.insert(1, *output_shape.c() as i32);\n\n    let mut output_strides = output_shape.hw_strides().iter().map(|s| *s as i32).collect_vec();\n    if output_strides.len() == 1 {\n        output_strides.push(*output_shape.w_stride() as i32);\n    }\n    output_strides.insert(0, *output_shape.n_stride().unwrap() as i32);\n    output_strides.insert(1, *output_shape.c_stride() as i32);\n\n    let output_descriptor = cudnn\n        .create_nd_tensor::<T>(&output_dims, &output_strides)\n        .context(\"in create_nd_tensor for output\")?;\n\n    Ok(TypedDescriptors {\n        conv: conv_descriptor,\n        x: input_descriptor,\n        w: filter_descriptor,\n        y: output_descriptor,\n    })\n}\n\nfn get_algo_and_workspace<T: CudnnDataType>(\n    desc: &TypedDescriptors<T>,\n) -> TractResult<(cudarc::cudnn::sys::cudnnConvolutionFwdAlgo_t, usize)> {\n    let conv_fwd = ConvForward { conv: &desc.conv, x: &desc.x, w: &desc.w, y: &desc.y };\n    let algo = cudarc::cudnn::sys::cudnnConvolutionFwdAlgo_t::CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM;\n    let workspace_size = conv_fwd.get_workspace_size(algo).context(\"in get_workspace_size()\")?;\n    Ok((algo, workspace_size))\n}\n\n#[derive(Debug)]\nenum CudnnConvDescriptors {\n    F32 {\n        input_shape: TVec<usize>,\n        desc: TypedDescriptors<f32>,\n        algo: cudarc::cudnn::sys::cudnnConvolutionFwdAlgo_t,\n        workspace_size: usize,\n    },\n    F16 {\n        input_shape: TVec<usize>,\n        desc: TypedDescriptors<f16>,\n        algo: cudarc::cudnn::sys::cudnnConvolutionFwdAlgo_t,\n        workspace_size: usize,\n    },\n}\n\nimpl CudnnConvDescriptors {\n    fn stored_input_shape(&self) -> &[usize] {\n        match self {\n            CudnnConvDescriptors::F32 { input_shape, .. }\n            | CudnnConvDescriptors::F16 { input_shape, .. } => input_shape,\n        }\n    }\n\n    fn new(\n        stream: &TractCudaStream,\n        op: &Conv,\n        dt: DatumType,\n        input_shape_array: &[usize],\n        weight_shape_array: &[usize],\n        output_shape_array: &[usize],\n    ) -> TractResult<Self> {\n        if dt == DatumType::F16 {\n            let desc = build_descriptors::<f16>(\n                stream,\n                op,\n                input_shape_array,\n                weight_shape_array,\n                output_shape_array,\n            )?;\n            let (algo, workspace_size) = get_algo_and_workspace(&desc)?;\n            Ok(CudnnConvDescriptors::F16 {\n                input_shape: input_shape_array.into(),\n                desc,\n                algo,\n                workspace_size,\n            })\n        } else {\n            let desc = build_descriptors::<f32>(\n                stream,\n                op,\n                input_shape_array,\n                weight_shape_array,\n                output_shape_array,\n            )?;\n            let (algo, workspace_size) = get_algo_and_workspace(&desc)?;\n            Ok(CudnnConvDescriptors::F32 {\n                input_shape: input_shape_array.into(),\n                desc,\n                algo,\n                workspace_size,\n            })\n        }\n    }\n}\n\nimpl ConvKernelScratch for Option<CudnnConvDescriptors> {}\n\n#[derive(Debug, Default, Clone, PartialEq, Eq)]\npub struct ConvCudnn;\n\nimpl ConvKernel for ConvCudnn {\n    fn name(&self) -> StaticName {\n        \"ConvCudnn\".into()\n    }\n\n    fn state(&self) -> Box<dyn ConvKernelScratch> {\n        Box::<Option<CudnnConvDescriptors>>::default()\n    }\n\n    fn dispatch(\n        &self,\n        state: &mut dyn ConvKernelScratch,\n        node_id: usize,\n        op: &Conv,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        weights: &DeviceTensor,\n        bias: Option<&DeviceTensor>,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(bias.is_none());\n\n        let state: &mut Option<CudnnConvDescriptors> =\n            state.downcast_mut().context(\"Wrong state\")?;\n\n        if state.as_ref().is_none_or(|desc| desc.stored_input_shape() != input.shape()) {\n            *state = Some(CudnnConvDescriptors::new(\n                stream,\n                op,\n                input.datum_type(),\n                input.shape(),\n                weights.shape(),\n                output.shape(),\n            )?);\n        }\n\n        let input_view = get_cuda_view(input);\n        let weights_view = get_cuda_view(weights);\n        let mut output_view = get_cuda_view_mut(output);\n\n        match state.as_ref().unwrap() {\n            CudnnConvDescriptors::F32 { desc, algo, workspace_size, .. } => {\n                let conv_forward =\n                    ConvForward { conv: &desc.conv, x: &desc.x, w: &desc.w, y: &desc.y };\n                unsafe {\n                    let mut workspace = if *workspace_size > 0 {\n                        Some(stream.alloc::<u8>(*workspace_size)?)\n                    } else {\n                        None\n                    };\n                    let src =\n                        input_view.transmute::<f32>(input_view.len() / size_of::<f32>()).unwrap();\n                    let w = weights_view\n                        .transmute::<f32>(weights_view.len() / size_of::<f32>())\n                        .unwrap();\n                    let mut dst = output_view\n                        .transmute_mut::<f32>(output_view.len() / size_of::<f32>())\n                        .unwrap();\n                    conv_forward\n                        .launch(\n                            *algo,\n                            workspace.as_mut().map(|w| w.as_view_mut()).as_mut(),\n                            (1.0f32, 0.0f32),\n                            &src,\n                            &w,\n                            &mut dst,\n                        )\n                        .context(\"in launch()\")?;\n                }\n            }\n            CudnnConvDescriptors::F16 { desc, algo, workspace_size, .. } => {\n                let conv_forward =\n                    ConvForward { conv: &desc.conv, x: &desc.x, w: &desc.w, y: &desc.y };\n                unsafe {\n                    let mut workspace = if *workspace_size > 0 {\n                        Some(stream.alloc::<u8>(*workspace_size)?)\n                    } else {\n                        None\n                    };\n                    let src =\n                        input_view.transmute::<f16>(input_view.len() / size_of::<f16>()).unwrap();\n                    let w = weights_view\n                        .transmute::<f16>(weights_view.len() / size_of::<f16>())\n                        .unwrap();\n                    let mut dst = output_view\n                        .transmute_mut::<f16>(output_view.len() / size_of::<f16>())\n                        .unwrap();\n                    conv_forward\n                        .launch(\n                            *algo,\n                            workspace.as_mut().map(|w| w.as_view_mut()).as_mut(),\n                            (f16::from_f32(1.0), f16::from_f32(0.0)),\n                            &src,\n                            &w,\n                            &mut dst,\n                        )\n                        .context(\"in launch()\")?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/cu/array.cu",
    "content": "#include \"common.cuh\"\n\ntemplate <typename T>\nstatic __device__ void pad_constant(\n    const T* __restrict__ in_ptr,\n    T* __restrict__ out_ptr,\n    int32_t in_shape0,  int32_t in_shape1,  int32_t in_shape2,  int32_t in_shape3,  int32_t in_shape4,\n    int32_t out_shape0, int32_t out_shape1, int32_t out_shape2, int32_t out_shape3, int32_t out_shape4,\n    int32_t in_stride0, int32_t in_stride1, int32_t in_stride2, int32_t in_stride3, int32_t in_stride4,\n    int32_t pad_before0, int32_t pad_before1, int32_t pad_before2, int32_t pad_before3, int32_t pad_before4,\n    T fill,\n    int32_t total_out_elems\n) {\n    int o = blockIdx.x * blockDim.x + threadIdx.x;\n    if (o >= total_out_elems) return;\n\n    int idx = o;\n\n    int c4 = idx % out_shape4; idx /= out_shape4;\n    int c3 = idx % out_shape3; idx /= out_shape3;\n    int c2 = idx % out_shape2; idx /= out_shape2;\n    int c1 = idx % out_shape1; idx /= out_shape1;\n    int c0 = idx % out_shape0;\n\n    int i4 = c4 - pad_before4;\n    int i3 = c3 - pad_before3;\n    int i2 = c2 - pad_before2;\n    int i1 = c1 - pad_before1;\n    int i0 = c0 - pad_before0;\n\n    bool in_bounds =\n        (i4 >= 0 && i4 < in_shape4) &&\n        (i3 >= 0 && i3 < in_shape3) &&\n        (i2 >= 0 && i2 < in_shape2) &&\n        (i1 >= 0 && i1 < in_shape1) &&\n        (i0 >= 0 && i0 < in_shape0);\n\n    int in_off = 0;\n    if (in_bounds) {\n        in_off += i4 * in_stride4;\n        in_off += i3 * in_stride3;\n        in_off += i2 * in_stride2;\n        in_off += i1 * in_stride1;\n        in_off += i0 * in_stride0;\n        out_ptr[o] = in_ptr[in_off];\n    } else {\n        out_ptr[o] = fill;\n    }\n}\n\n#define INSTANTIATE_PAD_CONSTANT(name, T)                                              \\\n   extern \"C\" __global__ void pad_constant_##name(                                     \\\n        const T* __restrict__ in_ptr, \\\n        T* __restrict__ out_ptr, \\\n        int32_t in_shape0, int32_t in_shape1, int32_t in_shape2, int32_t in_shape3, int32_t in_shape4, \\\n        int32_t out_shape0, int32_t out_shape1, int32_t out_shape2, int32_t out_shape3, int32_t out_shape4, \\\n        int32_t in_stride0, int32_t in_stride1, int32_t in_stride2, int32_t in_stride3, int32_t in_stride4, \\\n        int32_t pad_before0, int32_t pad_before1, int32_t pad_before2, int32_t pad_before3, int32_t pad_before4, \\\n        T fill,                                                                              \\\n        int32_t total_out_elems) {                                    \\\n      pad_constant<T>(in_ptr, out_ptr, in_shape0, in_shape1, in_shape2, in_shape3, in_shape4, \\\n        out_shape0, out_shape1, out_shape2, out_shape3, out_shape4, \\\n        in_stride0, in_stride1, in_stride2, in_stride3, in_stride4, \\\n        pad_before0, pad_before1, pad_before2, pad_before3, pad_before4, \\\n        fill, total_out_elems);                        \\\n    }\n\n#define INSTANTIATE_ROTATE_HALF(name, T)                                       \\\n  extern \"C\" __global__ void rotate_half_nd2_##name(                           \\\n      const T *input, T *output, int32_t shape_0, int32_t shape_1, int32_t strides_0,      \\\n      int32_t strides_1) {                                                         \\\n    int thread_idx_x = blockIdx.x * blockDim.x + threadIdx.x;                  \\\n    int thread_idx_y = blockIdx.y * blockDim.y + threadIdx.y;                  \\\n                                                                               \\\n    int rotated_idx =                                                          \\\n        (thread_idx_x + shape_1 / 2) * strides_1 + thread_idx_y * strides_0;   \\\n    int idx = thread_idx_x * strides_1 + thread_idx_y * strides_0;             \\\n                                                                               \\\n    output[idx] = -input[rotated_idx];                                         \\\n    output[rotated_idx] = input[idx];                                          \\\n  }\n\n#define INSTANTIATE_CAST_OP(name, T_in, T_out)                                 \\\n  extern \"C\" __global__ void cast_##name(const T_in *input, T_out *output,     \\\n                                         int32_t len) {                            \\\n    int idx = blockIdx.x * blockDim.x + threadIdx.x;                           \\\n    if (idx < len) {                                                           \\\n      output[idx] = (T_out)input[idx];                                         \\\n    }                                                                          \\\n  }\n\n#define INSTANTIATE_COPY(name, T)                                              \\\n  extern \"C\" __global__ void copy_nd1_##name(                                  \\\n      const T *input, T *output, int32_t in_strides_0, int32_t out_shape_0,            \\\n      int32_t out_strides_0) {                                                     \\\n    for (int i = threadIdx.x; i < out_shape_0; i += MAX_THREADS) {  \\\n      output[i * out_strides_0] = input[i * in_strides_0];                     \\\n    }                                                                          \\\n  }                                                                            \\\n                                                                               \\\n  extern \"C\" __global__ void copy_nd2_##name(                                  \\\n      const T *input, T *output, int32_t in_strides_0, int32_t in_strides_1,           \\\n      int32_t out_shape_0, int32_t out_shape_1, int32_t out_strides_0,                     \\\n      int32_t out_strides_1) {                                                     \\\n    int in_offset = blockIdx.x * in_strides_0;                                 \\\n    int out_offset = blockIdx.x * out_strides_0;                               \\\n    for (int i = threadIdx.x; i < out_shape_1; i += MAX_THREADS) {  \\\n      output[out_offset + i * out_strides_1] =                                 \\\n          input[in_offset + i * in_strides_1];                                 \\\n    }                                                                          \\\n  }                                                                            \\\n                                                                               \\\n  extern \"C\" __global__ void copy_nd3_##name(                                  \\\n      const T *input, T *output, int32_t in_strides_0, int32_t in_strides_1,           \\\n      int32_t in_strides_2, int32_t out_shape_0, int32_t out_shape_1, int32_t out_shape_2,     \\\n      int32_t out_strides_0, int32_t out_strides_1, int32_t out_strides_2) {               \\\n    int in_offset = blockIdx.x * in_strides_1 + blockIdx.y * in_strides_0;     \\\n    int out_offset = blockIdx.x * out_strides_1 + blockIdx.y * out_strides_0;  \\\n    for (int i = threadIdx.x; i < out_shape_2; i += MAX_THREADS) {  \\\n      output[out_offset + i * out_strides_2] =                                 \\\n          input[in_offset + i * in_strides_2];                                 \\\n    }                                                                          \\\n  }                                                                            \\\n                                                                               \\\n  extern \"C\" __global__ void copy_nd4_##name(                                  \\\n      const T *input, T *output, int32_t in_strides_0, int32_t in_strides_1,           \\\n      int32_t in_strides_2, int32_t in_strides_3, int32_t out_shape_0, int32_t out_shape_1,    \\\n      int32_t out_shape_2, int32_t out_shape_3, int32_t out_strides_0, int32_t out_strides_1,  \\\n      int32_t out_strides_2, int32_t out_strides_3) {                                  \\\n    int in_offset = blockIdx.x * in_strides_2 + blockIdx.y * in_strides_1 +    \\\n                    blockIdx.z * in_strides_0;                                 \\\n    int out_offset = blockIdx.x * out_strides_2 + blockIdx.y * out_strides_1 + \\\n                     blockIdx.z * out_strides_0;                               \\\n    for (int i = threadIdx.x; i < out_shape_3; i += MAX_THREADS) {             \\\n      output[out_offset + i * out_strides_3] =                                 \\\n          input[in_offset + i * in_strides_3];                                 \\\n    }                                                                          \\\n  }                                                                            \\\n                                                                               \\\n  /* nd5: z=d0, y=d1, x=d2*d3 (packed), threads=d4 */                         \\\n  extern \"C\" __global__ void copy_nd5_##name(                                  \\\n      const T *input, T *output, int32_t in_strides_0, int32_t in_strides_1,           \\\n      int32_t in_strides_2, int32_t in_strides_3, int32_t in_strides_4, int32_t out_shape_0,   \\\n      int32_t out_shape_1, int32_t out_shape_2, int32_t out_shape_3, int32_t out_shape_4,      \\\n      int32_t out_strides_0, int32_t out_strides_1, int32_t out_strides_2,                 \\\n      int32_t out_strides_3, int32_t out_strides_4) {                                  \\\n    int block_idx_x = blockIdx.x;                                              \\\n    int idx_3 = block_idx_x % out_shape_3;                                     \\\n    block_idx_x /= out_shape_3;                                                \\\n    int idx_2 = block_idx_x;                                                   \\\n    int in_offset = blockIdx.z * in_strides_0 + blockIdx.y * in_strides_1 +    \\\n                    idx_2 * in_strides_2 + idx_3 * in_strides_3;               \\\n    int out_offset = blockIdx.z * out_strides_0 + blockIdx.y * out_strides_1 + \\\n                     idx_2 * out_strides_2 + idx_3 * out_strides_3;            \\\n    for (int i = threadIdx.x; i < out_shape_4; i += MAX_THREADS) {             \\\n      output[out_offset + i * out_strides_4] =                                 \\\n          input[in_offset + i * in_strides_4];                                 \\\n    }                                                                          \\\n  }                                                                            \\\n                                                                               \\\n  /* nd6: z=d0, y=d1, x=d2*d3*d4 (packed), threads=d5 */                      \\\n  extern \"C\" __global__ void copy_nd6_##name(                                  \\\n      const T *input, T *output, int32_t in_strides_0, int32_t in_strides_1,           \\\n      int32_t in_strides_2, int32_t in_strides_3, int32_t in_strides_4, int32_t in_strides_5,  \\\n      int32_t out_shape_0, int32_t out_shape_1, int32_t out_shape_2, int32_t out_shape_3,      \\\n      int32_t out_shape_4, int32_t out_shape_5, int32_t out_strides_0, int32_t out_strides_1,  \\\n      int32_t out_strides_2, int32_t out_strides_3, int32_t out_strides_4,                 \\\n      int32_t out_strides_5) {                                                     \\\n    int block_idx_x = blockIdx.x;                                              \\\n    int idx_4 = block_idx_x % out_shape_4;                                     \\\n    block_idx_x /= out_shape_4;                                                \\\n    int idx_3 = block_idx_x % out_shape_3;                                     \\\n    block_idx_x /= out_shape_3;                                                \\\n    int idx_2 = block_idx_x;                                                   \\\n    int in_offset = blockIdx.z * in_strides_0 + blockIdx.y * in_strides_1 +    \\\n                    idx_2 * in_strides_2 + idx_3 * in_strides_3 +              \\\n                    idx_4 * in_strides_4;                                      \\\n    int out_offset = blockIdx.z * out_strides_0 + blockIdx.y * out_strides_1 + \\\n                     idx_2 * out_strides_2 + idx_3 * out_strides_3 +           \\\n                     idx_4 * out_strides_4;                                    \\\n    for (int i = threadIdx.x; i < out_shape_5; i += MAX_THREADS) {             \\\n      output[out_offset + i * out_strides_5] =                                 \\\n          input[in_offset + i * in_strides_5];                                 \\\n    }                                                                          \\\n  }\n\n#define INSTANTIATE_CAST_FROM(tname, type)                                     \\\n  INSTANTIATE_CAST_OP(tname##_bool, type, bool)                                \\\n  INSTANTIATE_CAST_OP(tname##_f32, type, float)                                \\\n  INSTANTIATE_CAST_OP(tname##_f16, type, __half)                               \\\n  INSTANTIATE_CAST_OP(tname##_u8, type, uint8_t)                               \\\n  INSTANTIATE_CAST_OP(tname##_u16, type, uint16_t)                             \\\n  INSTANTIATE_CAST_OP(tname##_u32, type, uint32_t)                             \\\n  INSTANTIATE_CAST_OP(tname##_u64, type, uint64_t)                             \\\n  INSTANTIATE_CAST_OP(tname##_i8, type, int8_t)                                \\\n  INSTANTIATE_CAST_OP(tname##_i16, type, int16_t)                              \\\n  INSTANTIATE_CAST_OP(tname##_i32, type, int32_t)                              \\\n  INSTANTIATE_CAST_OP(tname##_i64, type, int64_t)\n\n// Copy kernels: only u8/u16/u32/u64 (copy is type-size based)\nINSTANTIATE_COPY(u8, uint8_t)\nINSTANTIATE_COPY(u16, uint16_t)\nINSTANTIATE_COPY(u32, uint32_t)\nINSTANTIATE_COPY(u64, uint64_t)\n\n// Cast kernels: all types\nINSTANTIATE_CAST_FROM(bool, bool)\nINSTANTIATE_CAST_FROM(f32, float)\nINSTANTIATE_CAST_FROM(f16, __half)\nINSTANTIATE_CAST_FROM(i8, int8_t)\nINSTANTIATE_CAST_FROM(i16, int16_t)\nINSTANTIATE_CAST_FROM(i32, int32_t)\nINSTANTIATE_CAST_FROM(i64, int64_t)\nINSTANTIATE_CAST_FROM(u8, uint8_t)\nINSTANTIATE_CAST_FROM(u16, uint16_t)\nINSTANTIATE_CAST_FROM(u32, uint32_t)\nINSTANTIATE_CAST_FROM(u64, uint64_t)\n\n// Rotate half: only float types\nINSTANTIATE_ROTATE_HALF(f32, float)\nINSTANTIATE_ROTATE_HALF(f16, __half)\n"
  },
  {
    "path": "cuda/src/kernels/cu/binary.cu",
    "content": "#include \"common.cuh\"\n#include <cuda_runtime.h>\n\ntemplate <typename T> struct OpAdd {\n    __device__ __forceinline__ T operator()(T a, T b) const { return a + b; }\n};\n\ntemplate <typename T> struct OpSub {\n    __device__ __forceinline__ T operator()(T a, T b) const { return a - b; }\n};\n\ntemplate <typename T> struct OpMul {\n    __device__ __forceinline__ T operator()(T a, T b) const { return a * b; }\n};\n\ntemplate <typename T> struct OpDiv {\n    __device__ __forceinline__ T operator()(T a, T b) const { return a / b; }\n};\n\ntemplate <typename T> struct OpPow {\n    __device__ __forceinline__ T operator()(T a, T b) const { return (T)powf((float)a, (float)b); }\n};\n\ntemplate <typename T> struct OpMin {\n    __device__ __forceinline__ T operator()(T a, T b) const { return (T)fmin((float)a, (float)b); }\n};\n\ntemplate <typename T> struct OpMax {\n    __device__ __forceinline__ T operator()(T a, T b) const { return (T)fmax((float)a, (float)b); }\n};\n\ntemplate <typename T> struct OpLess {\n    __device__ __forceinline__ bool operator()(T a, T b) const { return a < b; }\n};\n\ntemplate <typename T> struct OpLessEqual {\n    __device__ __forceinline__ bool operator()(T a, T b) const { return a <= b; }\n};\n\ntemplate <typename T> struct OpGreater {\n    __device__ __forceinline__ bool operator()(T a, T b) const { return a > b; }\n};\n\ntemplate <typename T> struct OpGreaterEqual {\n    __device__ __forceinline__ bool operator()(T a, T b) const { return a >= b; }\n};\n\ntemplate <typename T> struct OpEquals {\n    __device__ __forceinline__ bool operator()(T a, T b) const { return a == b; }\n};\n\ntemplate <typename T> struct OpNotEquals {\n    __device__ __forceinline__ bool operator()(T a, T b) const { return a != b; }\n};\n\nstruct OpAnd {\n    __device__ __forceinline__ bool operator()(bool a, bool b) const { return a && b; }\n};\n\nstruct OpOr {\n    __device__ __forceinline__ bool operator()(bool a, bool b) const { return a || b; }\n};\n\ntemplate <typename T> struct OpBitOr {\n    __device__ __forceinline__ T operator()(T a, T b) const { return a | b; }\n};\n\ntemplate <typename T> struct OpBitAnd {\n    __device__ __forceinline__ T operator()(T a, T b) const { return a & b; }\n};\n\ntemplate <typename T> struct OpBitXor {\n    __device__ __forceinline__ T operator()(T a, T b) const { return a ^ b; }\n};\n\ntemplate <typename T_in, typename T_out, typename Op>\n__device__ __forceinline__ void\nbin_op_generic(const T_in *__restrict__ a, const T_in *__restrict__ b, T_out *__restrict__ out,\n               int32_t b_shape_0, int32_t b_shape_1, int32_t b_shape_2, int32_t b_shape_3,\n               int32_t b_shape_4, int32_t out_shape_0, int32_t out_shape_1, int32_t out_shape_2,\n               int32_t out_shape_3, int32_t out_shape_4, int32_t a_strides_0, int32_t a_strides_1,\n               int32_t a_strides_2, int32_t a_strides_3, int32_t a_strides_4, int32_t b_strides_0,\n               int32_t b_strides_1, int32_t b_strides_2, int32_t b_strides_3, int32_t b_strides_4,\n               int32_t o_strides_0, int32_t o_strides_1, int32_t o_strides_2, int32_t o_strides_3,\n               int32_t o_strides_4, Op op) {\n    const int32_t n0 = out_shape_0;\n    const int32_t n1 = out_shape_1;\n    const int32_t n2 = out_shape_2;\n    const int32_t n3 = out_shape_3;\n    const int32_t n4 = out_shape_4;\n\n    const int32_t total = n0 * n1 * n2 * n3 * n4;\n\n    int32_t tmp = blockIdx.x * blockDim.x + threadIdx.x;\n    if (tmp >= total) {\n        return;\n    }\n\n    const int32_t i4 = tmp % n4;\n    tmp /= n4;\n    const int32_t i3 = tmp % n3;\n    tmp /= n3;\n    const int32_t i2 = tmp % n2;\n    tmp /= n2;\n    const int32_t i1 = tmp % n1;\n    tmp /= n1;\n    const int32_t i0 = tmp;\n\n    const int32_t ia = i0 * a_strides_0 + i1 * a_strides_1 + i2 * a_strides_2 + i3 * a_strides_3 +\n                       i4 * a_strides_4;\n\n    const int32_t ib = i0 * b_strides_0 + i1 * b_strides_1 + i2 * b_strides_2 + i3 * b_strides_3 +\n                       i4 * b_strides_4;\n\n    const int32_t io = i0 * o_strides_0 + i1 * o_strides_1 + i2 * o_strides_2 + i3 * o_strides_3 +\n                       i4 * o_strides_4;\n\n    out[io] = op(a[ia], b[ib]);\n}\n\ntemplate <typename T_in, typename T_out, typename Op>\n__device__ __forceinline__ void\nbin_op_large(const T_in *__restrict__ a, const T_in *__restrict__ b, T_out *__restrict__ out,\n             int32_t b_shape_0, int32_t b_shape_1, int32_t b_shape_2, int32_t b_shape_3,\n             int32_t b_shape_4, int32_t out_shape_0, int32_t out_shape_1, int32_t out_shape_2,\n             int32_t out_shape_3, int32_t out_shape_4, int32_t a_strides_0, int32_t a_strides_1,\n             int32_t a_strides_2, int32_t a_strides_3, int32_t a_strides_4, int32_t b_strides_0,\n             int32_t b_strides_1, int32_t b_strides_2, int32_t b_strides_3, int32_t b_strides_4,\n             int32_t o_strides_0, int32_t o_strides_1, int32_t o_strides_2, int32_t o_strides_3,\n             int32_t o_strides_4, Op op) {\n    const int32_t n0 = out_shape_0;\n    const int32_t n1 = out_shape_1;\n    const int32_t n2 = out_shape_2;\n    const int32_t n3 = out_shape_3;\n    const int32_t n4 = out_shape_4;\n\n    const int32_t i3 = blockIdx.x;\n    const int32_t i2 = blockIdx.y;\n    const int32_t bz = blockIdx.z;\n\n    const int32_t i1 = bz % n1;\n    const int32_t i0 = bz / n1;\n\n    if (i0 >= n0 || i1 >= n1 || i2 >= n2 || i3 >= n3)\n        return;\n\n    // Base offsets for (i0,i1,i2,i3)\n    const int32_t ia_base =\n        i0 * a_strides_0 + i1 * a_strides_1 + i2 * a_strides_2 + i3 * a_strides_3;\n    const int32_t ib_base =\n        i0 * b_strides_0 + i1 * b_strides_1 + i2 * b_strides_2 + i3 * b_strides_3;\n    const int32_t io_base =\n        i0 * o_strides_0 + i1 * o_strides_1 + i2 * o_strides_2 + i3 * o_strides_3;\n\n    // Each thread handles a strided subset of i4\n    for (int32_t i4 = threadIdx.x; i4 < n4; i4 += blockDim.x) {\n        const int32_t ia = ia_base + i4 * a_strides_4;\n        const int32_t ib = ib_base + i4 * b_strides_4;\n        const int32_t io = io_base + i4 * o_strides_4;\n\n        out[io] = op(a[ia], b[ib]);\n    }\n}\n\n#define DEFINE_BINARY_KERNEL(name, tname, T_in, T_out, OP_TYPE)                                    \\\n    extern \"C\" {                                                                                   \\\n    __global__ void name##_generic_##tname(                                                        \\\n        const T_in *__restrict__ a, const T_in *__restrict__ b, T_out *__restrict__ out,           \\\n        int32_t b_shape_0, int32_t b_shape_1, int32_t b_shape_2, int32_t b_shape_3,                \\\n        int32_t b_shape_4, int32_t out_shape_0, int32_t out_shape_1, int32_t out_shape_2,          \\\n        int32_t out_shape_3, int32_t out_shape_4, int32_t a_strides_0, int32_t a_strides_1,        \\\n        int32_t a_strides_2, int32_t a_strides_3, int32_t a_strides_4, int32_t b_strides_0,        \\\n        int32_t b_strides_1, int32_t b_strides_2, int32_t b_strides_3, int32_t b_strides_4,        \\\n        int32_t o_strides_0, int32_t o_strides_1, int32_t o_strides_2, int32_t o_strides_3,        \\\n        int32_t o_strides_4) {                                                                     \\\n        bin_op_generic<T_in, T_out, OP_TYPE>(                                                      \\\n            a, b, out, b_shape_0, b_shape_1, b_shape_2, b_shape_3, b_shape_4, out_shape_0,         \\\n            out_shape_1, out_shape_2, out_shape_3, out_shape_4, a_strides_0, a_strides_1,          \\\n            a_strides_2, a_strides_3, a_strides_4, b_strides_0, b_strides_1, b_strides_2,          \\\n            b_strides_3, b_strides_4, o_strides_0, o_strides_1, o_strides_2, o_strides_3,          \\\n            o_strides_4, OP_TYPE{});                                                               \\\n    }                                                                                              \\\n                                                                                                   \\\n    __global__ void name##_large_##tname(                                                          \\\n        const T_in *__restrict__ a, const T_in *__restrict__ b, T_out *__restrict__ out,           \\\n        int32_t b_shape_0, int32_t b_shape_1, int32_t b_shape_2, int32_t b_shape_3,                \\\n        int32_t b_shape_4, int32_t out_shape_0, int32_t out_shape_1, int32_t out_shape_2,          \\\n        int32_t out_shape_3, int32_t out_shape_4, int32_t a_strides_0, int32_t a_strides_1,        \\\n        int32_t a_strides_2, int32_t a_strides_3, int32_t a_strides_4, int32_t b_strides_0,        \\\n        int32_t b_strides_1, int32_t b_strides_2, int32_t b_strides_3, int32_t b_strides_4,        \\\n        int32_t o_strides_0, int32_t o_strides_1, int32_t o_strides_2, int32_t o_strides_3,        \\\n        int32_t o_strides_4) {                                                                     \\\n        bin_op_large<T_in, T_out, OP_TYPE>(                                                        \\\n            a, b, out, b_shape_0, b_shape_1, b_shape_2, b_shape_3, b_shape_4, out_shape_0,         \\\n            out_shape_1, out_shape_2, out_shape_3, out_shape_4, a_strides_0, a_strides_1,          \\\n            a_strides_2, a_strides_3, a_strides_4, b_strides_0, b_strides_1, b_strides_2,          \\\n            b_strides_3, b_strides_4, o_strides_0, o_strides_1, o_strides_2, o_strides_3,          \\\n            o_strides_4, OP_TYPE{});                                                               \\\n    }                                                                                              \\\n    }\n\n#define DEFINE_ARITHMETIC_OP(name, OP)                                                             \\\n    DEFINE_BINARY_KERNEL(name, f32, float, float, OP<float>)                                       \\\n    DEFINE_BINARY_KERNEL(name, f16, __half, __half, OP<half>)                                      \\\n    DEFINE_BINARY_KERNEL(name, u8, uint8_t, uint8_t, OP<uint8_t>)                                  \\\n    DEFINE_BINARY_KERNEL(name, u16, uint16_t, uint16_t, OP<uint16_t>)                              \\\n    DEFINE_BINARY_KERNEL(name, u32, uint32_t, uint32_t, OP<uint32_t>)                              \\\n    DEFINE_BINARY_KERNEL(name, u64, uint64_t, uint64_t, OP<uint64_t>)                              \\\n    DEFINE_BINARY_KERNEL(name, i8, int8_t, int8_t, OP<int8_t>)                                     \\\n    DEFINE_BINARY_KERNEL(name, i16, int16_t, int16_t, OP<int16_t>)                                 \\\n    DEFINE_BINARY_KERNEL(name, i32, int32_t, int32_t, OP<int32_t>)                                 \\\n    DEFINE_BINARY_KERNEL(name, i64, int64_t, int64_t, OP<int64_t>)\n\n#define DEFINE_BIT_OP(name, OP)                                                                    \\\n    DEFINE_BINARY_KERNEL(name, u8, uint8_t, uint8_t, OP<uint8_t>)                                  \\\n    DEFINE_BINARY_KERNEL(name, u16, uint16_t, uint16_t, OP<uint16_t>)                              \\\n    DEFINE_BINARY_KERNEL(name, u32, uint32_t, uint32_t, OP<uint32_t>)                              \\\n    DEFINE_BINARY_KERNEL(name, u64, uint64_t, uint64_t, OP<uint64_t>)                              \\\n    DEFINE_BINARY_KERNEL(name, i8, int8_t, int8_t, OP<int8_t>)                                     \\\n    DEFINE_BINARY_KERNEL(name, i16, int16_t, int16_t, OP<int16_t>)                                 \\\n    DEFINE_BINARY_KERNEL(name, i32, int32_t, int32_t, OP<int32_t>)                                 \\\n    DEFINE_BINARY_KERNEL(name, i64, int64_t, int64_t, OP<int64_t>)\n\n#define DEFINE_COMP_OP(name, OP)                                                                   \\\n    DEFINE_BINARY_KERNEL(name, f32, float, bool, OP<float>)                                        \\\n    DEFINE_BINARY_KERNEL(name, f16, __half, bool, OP<half>)                                        \\\n    DEFINE_BINARY_KERNEL(name, u8, uint8_t, bool, OP<uint8_t>)                                     \\\n    DEFINE_BINARY_KERNEL(name, u16, uint16_t, bool, OP<uint16_t>)                                  \\\n    DEFINE_BINARY_KERNEL(name, u32, uint32_t, bool, OP<uint32_t>)                                  \\\n    DEFINE_BINARY_KERNEL(name, u64, uint64_t, bool, OP<uint64_t>)                                  \\\n    DEFINE_BINARY_KERNEL(name, i8, int8_t, bool, OP<int8_t>)                                       \\\n    DEFINE_BINARY_KERNEL(name, i16, int16_t, bool, OP<int16_t>)                                    \\\n    DEFINE_BINARY_KERNEL(name, i32, int32_t, bool, OP<int32_t>)                                    \\\n    DEFINE_BINARY_KERNEL(name, i64, int64_t, bool, OP<int64_t>)\n\n#define DEFINE_LOGIC_OP(name, OP) DEFINE_BINARY_KERNEL(name, bool, bool, bool, OP)\n\nDEFINE_ARITHMETIC_OP(binary_add, OpAdd)\nDEFINE_ARITHMETIC_OP(binary_sub, OpSub)\nDEFINE_ARITHMETIC_OP(binary_mul, OpMul)\nDEFINE_ARITHMETIC_OP(binary_div, OpDiv)\nDEFINE_ARITHMETIC_OP(binary_pow, OpPow)\nDEFINE_ARITHMETIC_OP(binary_min, OpMin)\nDEFINE_ARITHMETIC_OP(binary_max, OpMax)\n\nDEFINE_BIT_OP(binary_bitor, OpBitOr)\nDEFINE_BIT_OP(binary_bitand, OpBitAnd)\nDEFINE_BIT_OP(binary_bitxor, OpBitXor)\n\nDEFINE_COMP_OP(binary_lt, OpLess)\nDEFINE_COMP_OP(binary_lte, OpLessEqual)\nDEFINE_COMP_OP(binary_gt, OpGreater)\nDEFINE_COMP_OP(binary_gte, OpGreaterEqual)\nDEFINE_COMP_OP(binary_eq, OpEquals)\nDEFINE_COMP_OP(binary_ne, OpNotEquals)\n\nDEFINE_LOGIC_OP(binary_and, OpAnd)\nDEFINE_LOGIC_OP(binary_or, OpOr)\n\ntemplate <typename T>\n__device__ __forceinline__ void iff_generic(\n    const bool *__restrict__ cond, const T *__restrict__ then_values,\n    const T *__restrict__ else_values, T *__restrict__ out, int32_t out_shape_0,\n    int32_t out_shape_1, int32_t out_shape_2, int32_t out_shape_3, int32_t out_shape_4,\n    int32_t cond_strides_0, int32_t cond_strides_1, int32_t cond_strides_2, int32_t cond_strides_3,\n    int32_t cond_strides_4, int32_t then_strides_0, int32_t then_strides_1, int32_t then_strides_2,\n    int32_t then_strides_3, int32_t then_strides_4, int32_t else_strides_0, int32_t else_strides_1,\n    int32_t else_strides_2, int32_t else_strides_3, int32_t else_strides_4, int32_t o_strides_0,\n    int32_t o_strides_1, int32_t o_strides_2, int32_t o_strides_3, int32_t o_strides_4) {\n    const int32_t n0 = out_shape_0;\n    const int32_t n1 = out_shape_1;\n    const int32_t n2 = out_shape_2;\n    const int32_t n3 = out_shape_3;\n    const int32_t n4 = out_shape_4;\n\n    const int32_t total = n0 * n1 * n2 * n3 * n4;\n\n    int32_t tmp = blockIdx.x * blockDim.x + threadIdx.x;\n    if (tmp >= total) {\n        return;\n    }\n\n    const int32_t i4 = tmp % n4;\n    tmp /= n4;\n    const int32_t i3 = tmp % n3;\n    tmp /= n3;\n    const int32_t i2 = tmp % n2;\n    tmp /= n2;\n    const int32_t i1 = tmp % n1;\n    tmp /= n1;\n    const int32_t i0 = tmp;\n\n    const uint32_t icond = i0 * cond_strides_0 + i1 * cond_strides_1 + i2 * cond_strides_2 +\n                           i3 * cond_strides_3 + i4 * cond_strides_4;\n    bool pick = cond[icond];\n\n    const int32_t offset = i0 * (pick ? then_strides_0 : else_strides_0) +\n                           i1 * (pick ? then_strides_1 : else_strides_1) +\n                           i2 * (pick ? then_strides_2 : else_strides_2) +\n                           i3 * (pick ? then_strides_3 : else_strides_3) +\n                           i4 * (pick ? then_strides_4 : else_strides_4);\n\n    const int32_t io = i0 * o_strides_0 + i1 * o_strides_1 + i2 * o_strides_2 + i3 * o_strides_3 +\n                       i4 * o_strides_4;\n\n    out[io] = (pick ? then_values : else_values)[offset];\n}\n\n#define DEFINE_IFF_KERNEL(tname, T)                                                                \\\n    extern \"C\" {                                                                                   \\\n    __global__ void iff_generic_##tname(                                                           \\\n        const bool *__restrict__ cond_values, const T *__restrict__ then_values,                   \\\n        const T *__restrict__ else_values, T *__restrict__ out, int32_t out_shape_0,               \\\n        int32_t out_shape_1, int32_t out_shape_2, int32_t out_shape_3, int32_t out_shape_4,        \\\n        int32_t cond_strides_0, int32_t cond_strides_1, int32_t cond_strides_2,                    \\\n        int32_t cond_strides_3, int32_t cond_strides_4, int32_t then_strides_0,                    \\\n        int32_t then_strides_1, int32_t then_strides_2, int32_t then_strides_3,                    \\\n        int32_t then_strides_4, int32_t else_strides_0, int32_t else_strides_1,                    \\\n        int32_t else_strides_2, int32_t else_strides_3, int32_t else_strides_4,                    \\\n        int32_t o_strides_0, int32_t o_strides_1, int32_t o_strides_2, int32_t o_strides_3,        \\\n        int32_t o_strides_4) {                                                                     \\\n        iff_generic(cond_values, then_values, else_values, out, out_shape_0, out_shape_1,          \\\n                    out_shape_2, out_shape_3, out_shape_4, cond_strides_0, cond_strides_1,         \\\n                    cond_strides_2, cond_strides_3, cond_strides_4, then_strides_0,                \\\n                    then_strides_1, then_strides_2, then_strides_3, then_strides_4,                \\\n                    else_strides_0, else_strides_1, else_strides_2, else_strides_3,                \\\n                    else_strides_4, o_strides_0, o_strides_1, o_strides_2, o_strides_3,            \\\n                    o_strides_4);                                                                  \\\n    }                                                                                              \\\n    }\n\nDEFINE_IFF_KERNEL(f16, half);\nDEFINE_IFF_KERNEL(f32, float);\nDEFINE_IFF_KERNEL(i8, int8_t);\nDEFINE_IFF_KERNEL(i16, int16_t);\nDEFINE_IFF_KERNEL(i32, int32_t);\nDEFINE_IFF_KERNEL(i64, int64_t);\nDEFINE_IFF_KERNEL(u8, uint8_t);\nDEFINE_IFF_KERNEL(u16, uint16_t);\nDEFINE_IFF_KERNEL(u32, uint32_t);\nDEFINE_IFF_KERNEL(u64, uint64_t);\n"
  },
  {
    "path": "cuda/src/kernels/cu/cnn.cu",
    "content": "#include <cuda_runtime.h>\n#include <math_constants.h>\n#include \"common.cuh\"\n\n// liquid:true\n\n{% assign types = \"f32,f16\" | split: \",\" %}\n\n{% for type in types %}\n{% if type == \"f32\" %}\n  {% assign T = \"float\" %}\n  {% assign load = \"\" %}\n  {% assign store = \"\" %}\n{% else %}\n  {% assign T = \"__half\" %}\n  {% assign load = \"__half2float(\" %}\n  {% assign store = \"__float2half(\" %}\n{% endif %}\n\n{% for georank in (1..4) %}\n\nextern \"C\" __global__ void conv{{georank}}d_{{type}}_generic(\n    const {{T}} *input,\n    int32_t in_n, int32_t in_c,\n    {% for i in (1..georank) %} int32_t in_{{i}}, {% endfor %}\n    int32_t in_n_stride, int32_t in_c_stride,\n    {% for i in (1..georank) %} int32_t in_{{i}}_stride, {% endfor %}\n\n    const {{T}} *kernel,\n    int32_t groups, int32_t co_per_group, int32_t ci_per_group,\n    {% for i in (1..georank) %} int32_t ker_{{i}}, {% endfor %}\n    int32_t ker_g_stride, int32_t ker_o_stride, int32_t ker_i_stride,\n    {% for i in (1..georank) %} int32_t ker_{{i}}_stride, {% endfor %}\n\n    const {{T}} *bias,\n    int32_t bias_stride,\n\n    {% for i in (1..georank) %} int32_t pad_{{i}}, {% endfor %}\n    {% for i in (1..georank) %} int32_t stride_{{i}}, {% endfor %}\n    {% for i in (1..georank) %} int32_t dil_{{i}}, {% endfor %}\n\n    {{T}} *output,\n    int32_t out_n, int32_t out_c,\n    {% for i in (1..georank) %} int32_t out_{{i}}, {% endfor %}\n    int32_t out_n_stride, int32_t out_c_stride\n    {% for i in (1..georank) %}, int32_t out_{{i}}_stride {% endfor %}\n) {\n  assert(in_n == gridDim.z);\n  assert(out_n == gridDim.z);\n  assert(blockDim.z == 1);\n\n  assert(blockDim.y == 1);\n\n  size_t n = blockIdx.z;\n  size_t co = blockIdx.y;\n  size_t group = co / co_per_group;\n  size_t xyz = blockIdx.x * blockDim.x + threadIdx.x;\n  {% capture georank_minus_1 %}{{georank|minus:1}}{%endcapture%}\n  {% for i in (1..georank_minus_1) reversed %}\n     size_t ox_{{i}} = xyz % out_{{i}};\n     xyz = xyz / out_{{i}};\n  {% endfor %}\n  size_t ox_{{georank}} = xyz;\n\n\n  {% for i in (1..georank) %}\n     if (ox_{{i}} >= out_{{i}}) {\n        return;\n     }\n  {% endfor %}\n\n  const {{T}} *pfi = input + n * in_n_stride + ci_per_group * group * in_c_stride;\n  const {{T}} *pfk = kernel + co * ker_o_stride;\n\n  float sum = 0;\n  if(bias) {\n    sum = {{load}}*(bias + co * bias_stride){% if type == \"f16\" %}){% endif %};\n  }\n\n  for(int ci = 0; ci < ci_per_group; ci++ ) {\n  {% for i in (1..georank) %}\n    for(int k_{{i}} = 0; k_{{i}} < ker_{{i}}; k_{{i}}++) {\n      int x_{{i}} = ox_{{i}} * stride_{{i}} + k_{{i}} * dil_{{i}} - pad_{{i}};\n      if (x_{{i}} < 0 || x_{{i}} >= in_{{i}}) {\n        continue;\n      }\n  {% endfor %}\n\n        float i = {{load}}*(pfi + ci * in_c_stride\n        {% for i in (1..georank) %} + x_{{i}} * in_{{i}}_stride {%endfor%}){% if type == \"f16\" %}){% endif %};\n        float k = {{load}}*(pfk + ci * ker_i_stride +\n        {% for i in (1..georank) %} + k_{{i}} * ker_{{i}}_stride {%endfor%}){% if type == \"f16\" %}){% endif %};\n        sum += i*k;\n    {% for i in (1..georank) %} } {%endfor%} // nested georank loops\n  } // ci loop\n\n  size_t poffset = n * out_n_stride + co * out_c_stride\n      {% for i in (1..georank) %} + ox_{{i}} * out_{{i}}_stride {%endfor%};\n  {% if type == \"f16\" %}\n  *(output + poffset) = __float2half(sum);\n  {% else %}\n  *(output + poffset) = sum;\n  {% endif %}\n\n}\n\n{% endfor %}\n{% endfor %}\n"
  },
  {
    "path": "cuda/src/kernels/cu/common.cuh",
    "content": "#include <cuda/std/cstdint>\n#include <cuda_fp16.h>\n#include <cuda/std/type_traits>\n\n#define CAT2(a,b) a##b\n#define CAT(a,b)  CAT2(a,b)\n#define CAT3(a,b,c) CAT(CAT(a,b),c)\n#define CAT4(a,b,c,d) CAT(CAT3(a,b,c),d)\n#define CAT5(a,b,c,d,e) CAT(CAT(CAT3(a,b,c),d),e)\n\n#define CUDA_CC_TURING 750\n#define CUDA_CC_AMPERE 800\n\n#define FLT_MAX 3.40282347e+38F\n\n#define MAX_THREADS 1024\n#define WARP_SIZE 32\n\n#define QK8_1 32\n#define QI8_1 (QK8_1 / (4 * QR8_1))\n#define QR8_1 1\n\n#define QK4_0 32\n#define QI4_0 (QK4_0 / (4 * QR4_0))\n#define QR4_0 2\n\n#define QK8_0 32\n#define QI8_0 (QK8_0 / (4 * QR8_0))\n#define QR8_0 1\n\ntypedef struct {\n  half d;                // delta\n  uint8_t qs[QK4_0 / 2]; // nibbles / quants\n} block_q4_0;\n\ntypedef struct {\n  half2 ds;\n  int8_t qs[QK8_1]; // quants\n} block_q8_1;\nstatic_assert(sizeof(block_q8_1) == 2 * sizeof(half) + QK8_1,\n              \"wrong q8_1 block size/padding\");\n\nstruct block_q8_1_mmq {\n  // The y float data is converted to a data layout that can simply be copied to\n  // shared memory as a contiguous block. The y float data is first grouped as\n  // blocks of 128 values. These blocks are then treated as individual data\n  // values and transposed.\n  //\n  // To avoid shared memory bank conflicts each block is padded with 16 bytes.\n  // This padding is also used to store block scales/partial sums.\n  // The scales multiplied with the quantized data are equal to the unquantized\n  // values. The partial sums are obtained by summing up a subgroup of the\n  // contained values (prior to quantization)\n  //     and are only needed for performance reasons.\n  half2 ds4[4]; // 1 16 bit scale + 1 16 bit partial sum per 32 values, stored\n                // as d0,s0,d1,s1,d2,s2,d3,s3\n  int8_t qs[4 * QK8_1]; // 128 values quantized to 8 bit each\n};\nstatic_assert(sizeof(block_q8_1_mmq) == 4 * QK8_1 + 4 * sizeof(half2),\n              \"Unexpected block_q8_1_mmq size\");\n\ntemplate <int width = WARP_SIZE>\nstatic __device__ __forceinline__ float warp_reduce_sum(float x) {\n#pragma unroll\n  for (int offset = width / 2; offset > 0; offset >>= 1) {\n    x += __shfl_xor_sync(0xffffffff, x, offset, width);\n  }\n  return x;\n}\n\ntemplate <int width = WARP_SIZE>\nstatic __device__ __forceinline__ __half warp_reduce_sum(__half x) {\n#pragma unroll\n  for (int offset = width / 2; offset > 0; offset >>= 1) {\n    x += __shfl_xor_sync(0xffffffff, x, offset, width);\n  }\n  return x;\n}\n\ntemplate <int width = WARP_SIZE>\nstatic __device__ __forceinline__ float warp_reduce_max(float x) {\n#pragma unroll\n  for (int offset = width / 2; offset > 0; offset >>= 1) {\n    x = fmaxf(x, __shfl_xor_sync(0xffffffff, x, offset, width));\n  }\n  return x;\n}\n\ntemplate <int width = WARP_SIZE>\nstatic __device__ __forceinline__ __half warp_reduce_max(__half x) {\n#pragma unroll\n  for (int offset = width / 2; offset > 0; offset >>= 1) {\n    x = __hmax(x, __shfl_xor_sync(0xffffffff, x, offset, width));\n  }\n  return x;\n}\n\ntemplate <int width = WARP_SIZE>\nstatic __device__ __forceinline__ float warp_reduce_min(float x) {\n#pragma unroll\n  for (int offset = width / 2; offset > 0; offset >>= 1) {\n    x = fminf(x, __shfl_xor_sync(0xffffffff, x, offset, width));\n  }\n  return x;\n}\n\ntemplate <int width = WARP_SIZE>\nstatic __device__ __forceinline__ __half warp_reduce_min(__half x) {\n#pragma unroll\n  for (int offset = width / 2; offset > 0; offset >>= 1) {\n    x = __hmin(x, __shfl_xor_sync(0xffffffff, x, offset, width));\n  }\n  return x;\n}\n\ntemplate <int width = WARP_SIZE>\nstatic __device__ __forceinline__ float warp_reduce_prod(float x) {\n#pragma unroll\n  for (int offset = width / 2; offset > 0; offset >>= 1) {\n    x *= __shfl_xor_sync(0xffffffff, x, offset, width);\n  }\n  return x;\n}\n\ntemplate <int width = WARP_SIZE>\nstatic __device__ __forceinline__ __half warp_reduce_prod(__half x) {\n#pragma unroll\n  for (int offset = width / 2; offset > 0; offset >>= 1) {\n    x = __hmul(x, __shfl_xor_sync(0xffffffff, x, offset, width));\n  }\n  return x;\n}\n\ntemplate<int width = WARP_SIZE>\nstatic __device__ __forceinline__ int warp_reduce_all(int x) {\n  return __all_sync(0xffffffff, x);\n}\n\nnamespace cuda_mma {\n\ntemplate <int I_, int J_, typename T> struct tile {\n  static constexpr int I = I_;\n  static constexpr int J = J_;\n  static constexpr int ne = I * J / WARP_SIZE;\n  T x[ne] = {0};\n\n  static __device__ __forceinline__ int get_i(const int l) {\n    if constexpr (I == 8 && (J == 4 || J == 8)) {\n      return threadIdx.x / 4;\n    } else if constexpr (I == 16 && J == 8) {\n      return (l / 2) * 8 + threadIdx.x / 4;\n    } else if constexpr (I == 16 && J == 16) {\n      return ((l / 2) % 2) * 8 + threadIdx.x / 4;\n    } else {\n      static_assert(I == -1 && J == -1,\n                    \"template specialization not implemented\");\n    }\n  }\n\n  static __device__ __forceinline__ int get_j(const int l) {\n    if constexpr (I == 8 && J == 4) {\n      return threadIdx.x % 4;\n    } else if constexpr (I == 8 && J == 8) {\n      return 4 * l + threadIdx.x % 4;\n    } else if constexpr (I == 16 && J == 8) {\n      return 2 * (threadIdx.x % 4) + l % 2;\n    } else if constexpr (I == 16 && J == 16) {\n      return 8 * (l / 4) + 2 * (threadIdx.x % 4) + l % 2;\n    } else {\n      static_assert(I == -1 && J == -1,\n                    \"template specialization not implemented\");\n    }\n  }\n};\n\ntemplate <int I_, int J_> struct tile<I_, J_, half2> {\n  static constexpr int I = I_;\n  static constexpr int J = J_;\n  static constexpr int ne = I * J / WARP_SIZE;\n  half2 x[ne] = {{0.0f, 0.0f}};\n\n  static __device__ __forceinline__ int get_i(const int l) {\n    if constexpr (I == 8 && J == 8) {\n      return threadIdx.x / 4;\n    } else if constexpr (I == 16 && J == 4) {\n      return l * 8 + threadIdx.x / 4;\n    } else if constexpr (I == 16 && J == 8) {\n      return (l % 2) * 8 + threadIdx.x / 4;\n    } else {\n      static_assert(I == -1 && J == -1,\n                    \"template specialization not implemented\");\n    }\n  }\n\n  static __device__ __forceinline__ int get_j(const int l) {\n    if constexpr (I == 8 && J == 8) {\n      return l * 4 + threadIdx.x % 4;\n    } else if constexpr (I == 16 && J == 4) {\n      return threadIdx.x % 4;\n    } else if constexpr (I == 16 && J == 8) {\n      return (l / 2) * 4 + threadIdx.x % 4;\n    } else {\n      static_assert(I == -1 && J == -1,\n                    \"template specialization not implemented\");\n    }\n  }\n};\n\ntemplate <int I, int J>\nstatic __device__ __forceinline__ tile<I, J / 2, half2>\nget_half2(const tile<I, J, float> &tile_float) {\n  tile<I, J / 2, half2> ret;\n#pragma unroll\n  for (int l0 = 0; l0 < tile_float.ne; l0 += 2) {\n    ret.x[l0 / 2] = make_half2(tile_float.x[l0 + 0], tile_float.x[l0 + 1]);\n  }\n  return ret;\n}\n\ntemplate <int I, int J, typename T>\nstatic __device__ __forceinline__ void\nload_generic(tile<I, J, T> &t, const T *__restrict__ xs0, const int stride) {\n#pragma unroll\n  for (int l = 0; l < t.ne; ++l) {\n    t.x[l] = xs0[t.get_i(l) * stride + t.get_j(l)];\n  }\n}\n\ntemplate <typename T>\nstatic __device__ __forceinline__ void\nload_ldmatrix(tile<8, 8, T> &t, const T *__restrict__ xs0, const int stride) {\n  int *xi = (int *)t.x;\n  const int *xs = (const int *)xs0 + (threadIdx.x % t.I) * stride +\n                  ((threadIdx.x / t.I) * (t.J / 2)) % t.J;\n  asm volatile(\"ldmatrix.sync.aligned.m8n8.x2.b16 {%0, %1}, [%2];\"\n               : \"=r\"(xi[0]), \"=r\"(xi[1])\n               : \"l\"(xs));\n}\n\ntemplate <typename T>\nstatic __device__ __forceinline__ void\nload_ldmatrix(tile<16, 4, T> &t, const T *__restrict__ xs0, const int stride) {\n  int *xi = (int *)t.x;\n  const int *xs = (const int *)xs0 + (threadIdx.x % t.I) * stride;\n  asm volatile(\"ldmatrix.sync.aligned.m8n8.x2.b16 {%0, %1}, [%2];\"\n               : \"=r\"(xi[0]), \"=r\"(xi[1])\n               : \"l\"(xs));\n}\n\ntemplate <typename T>\nstatic __device__ __forceinline__ void\nload_ldmatrix(tile<16, 8, T> &t, const T *__restrict__ xs0, const int stride) {\n  int *xi = (int *)t.x;\n  const int *xs = (const int *)xs0 + (threadIdx.x % t.I) * stride +\n                  (threadIdx.x / t.I) * (t.J / 2);\n  asm volatile(\"ldmatrix.sync.aligned.m8n8.x4.b16 {%0, %1, %2, %3}, [%4];\"\n               : \"=r\"(xi[0]), \"=r\"(xi[1]), \"=r\"(xi[2]), \"=r\"(xi[3])\n               : \"l\"(xs));\n}\n\ntemplate <typename T>\nstatic __device__ __forceinline__ void\nload_ldmatrix_trans(tile<16, 8, T> &t, const T *__restrict__ xs0,\n                    const int stride) {\n  int *xi = (int *)t.x;\n  const int *xs = (const int *)xs0 + (threadIdx.x % t.I) * stride +\n                  (threadIdx.x / t.I) * (t.J / 2);\n  asm volatile(\"ldmatrix.sync.aligned.m8n8.x4.trans.b16 {%0, %1, %2, %3}, [%4];\"\n               : \"=r\"(xi[0]), \"=r\"(xi[2]), \"=r\"(xi[1]), \"=r\"(xi[3])\n               : \"l\"(xs));\n}\n\n    static __device__ __forceinline__ void mma(\n            tile<16, 8, int> & D, const tile<16, 4, int> & A, const tile<8, 4, int> & B) {\n#if __CUDA_ARCH__ >= CUDA_CC_AMPERE\n        asm(\"mma.sync.aligned.m16n8k16.row.col.s32.s8.s8.s32 {%0, %1, %2, %3}, {%4, %5}, {%6}, {%0, %1, %2, %3};\"\n            : \"+r\"(D.x[0]), \"+r\"(D.x[1]), \"+r\"(D.x[2]), \"+r\"(D.x[3])\n            : \"r\"(A.x[0]), \"r\"(A.x[1]), \"r\"(B.x[0]));\n#else\n        // On Turing m16n8k16 mma is not available, use 2x m8n8k16 mma instead:\n        asm(\"mma.sync.aligned.m8n8k16.row.col.s32.s8.s8.s32 {%0, %1}, {%2}, {%3}, {%0, %1};\"\n            : \"+r\"(D.x[0]), \"+r\"(D.x[1])\n            : \"r\"(A.x[0]), \"r\"(B.x[0]));\n        asm(\"mma.sync.aligned.m8n8k16.row.col.s32.s8.s8.s32 {%0, %1}, {%2}, {%3}, {%0, %1};\"\n            : \"+r\"(D.x[2]), \"+r\"(D.x[3])\n            : \"r\"(A.x[1]), \"r\"(B.x[0]));\n#endif // __CUDA_ARCH__ >= CUDA_CC_AMPERE\n\n    }\n\n    static __device__ __forceinline__ void mma(\n            tile<16, 8, int> & D, const tile<16, 8, int> & A, const tile<8, 8, int> & B) {\n#if __CUDA_ARCH__ >= CUDA_CC_AMPERE\n        asm(\"mma.sync.aligned.m16n8k32.row.col.s32.s8.s8.s32 {%0, %1, %2, %3}, {%4, %5, %6, %7}, {%8, %9}, {%0, %1, %2, %3};\"\n            : \"+r\"(D.x[0]), \"+r\"(D.x[1]), \"+r\"(D.x[2]), \"+r\"(D.x[3])\n            : \"r\"(A.x[0]), \"r\"(A.x[1]), \"r\"(A.x[2]), \"r\"(A.x[3]), \"r\"(B.x[0]), \"r\"(B.x[1]));\n#else\n        // On Turing m16n8k32 mma is not available, use 4x m8n8k16 mma instead:\n        asm(\"mma.sync.aligned.m8n8k16.row.col.s32.s8.s8.s32 {%0, %1}, {%2}, {%3}, {%0, %1};\"\n            : \"+r\"(D.x[0]), \"+r\"(D.x[1])\n            : \"r\"(A.x[0]), \"r\"(B.x[0]));\n        asm(\"mma.sync.aligned.m8n8k16.row.col.s32.s8.s8.s32 {%0, %1}, {%2}, {%3}, {%0, %1};\"\n            : \"+r\"(D.x[2]), \"+r\"(D.x[3])\n            : \"r\"(A.x[1]), \"r\"(B.x[0]));\n        asm(\"mma.sync.aligned.m8n8k16.row.col.s32.s8.s8.s32 {%0, %1}, {%2}, {%3}, {%0, %1};\"\n            : \"+r\"(D.x[0]), \"+r\"(D.x[1])\n            : \"r\"(A.x[2]), \"r\"(B.x[1]));\n        asm(\"mma.sync.aligned.m8n8k16.row.col.s32.s8.s8.s32 {%0, %1}, {%2}, {%3}, {%0, %1};\"\n            : \"+r\"(D.x[2]), \"+r\"(D.x[3])\n            : \"r\"(A.x[3]), \"r\"(B.x[1]));\n#endif // __CUDA_ARCH__ >= CUDA_CC_AMPERE\n    }\n\n    static __device__ __forceinline__ void mma(\n            tile<16, 4, half2> & D, const tile<16, 8, half2> & A, const tile<8, 8, half2> & B) {\n        const int * Axi = (const int *) A.x;\n        const int * Bxi = (const int *) B.x;\n        int       * Dxi = (int       *) D.x;\n#if __CUDA_ARCH__ >= CUDA_CC_AMPERE\n        asm(\"mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3, %4, %5}, {%6, %7}, {%0, %1};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[0]), \"r\"(Bxi[1]));\n#else\n        // On Turing m16n8k16 mma is not available, use 2x m8n8k8 mma instead:\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3}, {%4}, {%0, %1};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Bxi[0]));\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3}, {%4}, {%0, %1};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1])\n            : \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[1]));\n#endif // __CUDA_ARCH__ >= CUDA_CC_AMPERE\n    }\n\n    static __device__ __forceinline__ void mma(\n            tile<16, 8, half2> & D, const tile<16, 8, half2> & A, const tile<16, 8, half2> & B) {\n        const int * Axi = (const int *) A.x;\n        const int * Bxi = (const int *) B.x;\n        int       * Dxi = (int       *) D.x;\n#if __CUDA_ARCH__ >= CUDA_CC_AMPERE\n        asm(\"mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3, %4, %5}, {%6, %7}, {%0, %1};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[0]), \"r\"(Bxi[2]));\n        asm(\"mma.sync.aligned.m16n8k16.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3, %4, %5}, {%6, %7}, {%0, %1};\"\n            : \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[1]), \"r\"(Bxi[3]));\n#else\n        // On Turing m16n8k16 mma is not available, use 4x m8n8k8 mma instead:\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3}, {%4}, {%0, %1};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Bxi[0]));\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3}, {%4}, {%0, %1};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1])\n            : \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[2]));\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3}, {%4}, {%0, %1};\"\n            : \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Bxi[1]));\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f16.f16.f16.f16 {%0, %1}, {%2, %3}, {%4}, {%0, %1};\"\n            : \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[3]));\n#endif // __CUDA_ARCH__ >= CUDA_CC_AMPERE\n    }\n\n    static __device__ __forceinline__ void mma(\n            tile<16, 8, float> & D, const tile<16, 8, float> & A, const tile<8, 8, float> & B) {\n        const int * Axi = (const int *) A.x;\n        const int * Bxi = (const int *) B.x;\n        int       * Dxi = (int       *) D.x;\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f32.tf32.tf32.f32 {%0, %1, %2, %3}, {%4, %5, %6, %7}, {%8, %9}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1]), \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[0]), \"r\"(Bxi[1]));\n    }\n\n    static __device__ __forceinline__ void mma(\n            tile<16, 8, float> & D, const tile<16, 8, half2> & A, const tile<8, 8, half2> & B) {\n        const int * Axi = (const int *) A.x;\n        const int * Bxi = (const int *) B.x;\n        int       * Dxi = (int       *) D.x;\n#if __CUDA_ARCH__ >= CUDA_CC_AMPERE\n        asm(\"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 {%0, %1, %2, %3}, {%4, %5, %6, %7}, {%8, %9}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1]), \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[0]), \"r\"(Bxi[1]));\n#else\n        // On Turing m16n8k16 mma is not available, use 2x m8n8k8 mma instead:\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f32.f16.f16.f32 {%0, %1, %2, %3}, {%4, %5}, {%6}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1]), \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Bxi[0]));\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f32.f16.f16.f32 {%0, %1, %2, %3}, {%4, %5}, {%6}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1]), \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[1]));\n#endif // __CUDA_ARCH__ >= CUDA_CC_AMPERE\n    }\n\n    static __device__ __forceinline__ void mma(\n            tile<16, 16, float> & D, const tile<16, 8, half2> & A, const tile<16, 8, half2> & B) {\n        const int * Axi = (const int *) A.x;\n        const int * Bxi = (const int *) B.x;\n        int       * Dxi = (int       *) D.x;\n#if __CUDA_ARCH__ >= CUDA_CC_AMPERE\n        asm(\"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 {%0, %1, %2, %3}, {%4, %5, %6, %7}, {%8, %9}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1]), \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[0]), \"r\"(Bxi[2]));\n        asm(\"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 {%0, %1, %2, %3}, {%4, %5, %6, %7}, {%8, %9}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[4]), \"+r\"(Dxi[5]), \"+r\"(Dxi[6]), \"+r\"(Dxi[7])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[1]), \"r\"(Bxi[3]));\n#else\n        // On Turing m16n8k16 mma is not available, use 4x m8n8k8 mma instead:\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f32.f16.f16.f32 {%0, %1, %2, %3}, {%4, %5}, {%6}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1]), \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Bxi[0]));\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f32.f16.f16.f32 {%0, %1, %2, %3}, {%4, %5}, {%6}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[0]), \"+r\"(Dxi[1]), \"+r\"(Dxi[2]), \"+r\"(Dxi[3])\n            : \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[2]));\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f32.f16.f16.f32 {%0, %1, %2, %3}, {%4, %5}, {%6}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[4]), \"+r\"(Dxi[5]), \"+r\"(Dxi[6]), \"+r\"(Dxi[7])\n            : \"r\"(Axi[0]), \"r\"(Axi[1]), \"r\"(Bxi[1]));\n        asm(\"mma.sync.aligned.m16n8k8.row.col.f32.f16.f16.f32 {%0, %1, %2, %3}, {%4, %5}, {%6}, {%0, %1, %2, %3};\"\n            : \"+r\"(Dxi[4]), \"+r\"(Dxi[5]), \"+r\"(Dxi[6]), \"+r\"(Dxi[7])\n            : \"r\"(Axi[2]), \"r\"(Axi[3]), \"r\"(Bxi[3]));\n#endif // __CUDA_ARCH__ >= CUDA_CC_AMPERE\n    }\n} // namespace cuda_mma\n\n#if CUDART_VERSION >= 11080\n\nstatic __device__ __forceinline__ int cuda_movmatrix(const int x) {\n    int ret = 0;\n\n    asm(\"movmatrix.sync.aligned.m8n8.trans.b16 %0, %1;\"\n        : \"=r\"(ret) : \"r\"(x));\n    return ret;\n}\n\n#else\n\nstatic __device__ __forceinline__ int cuda_movmatrix(const int x) {\n    // Imagine transposing row-major matrix to column-major matrix.\n    const int src_i_low  = 2 * (threadIdx.x % 4);\n    const int src_i_high = src_i_low + 1;\n    const int src_j      = threadIdx.x / 4;\n\n    const int src_laneid_low  = src_i_low  * 4 + src_j / 2;\n    const int src_laneid_high = src_i_high * 4 + src_j / 2;\n\n    const int shift_low  = ((src_j + 0) % 2) * 16;\n    const int shift_high = ((src_j + 1) % 2) * 16;\n\n    const int ret_low  = (__shfl_sync(0xFFFFFFFF, x, src_laneid_low,  WARP_SIZE) >> shift_low)  & 0x0000FFFF;\n    const int ret_high = (__shfl_sync(0xFFFFFFFF, x, src_laneid_high, WARP_SIZE) << shift_high) & 0xFFFF0000;\n\n    return ret_low | ret_high;\n}\n\n#endif // CUDART_VERSION >= 11080\n\nstatic __device__ __forceinline__ half2 cuda_movmatrix(const half2 x) {\n    half2 ret;\n    *((int *) &ret) = cuda_movmatrix(*((const int *) &x));\n    return ret;\n}\n\n\n// The compiler is always able to unroll loops if they contain continue expressions.\n// In such cases loop unrolling can still be achieved via recursion:\ntemplate <int n>\nstruct cuda_unroll {\n    template <typename Func, typename... Args>\n    __device__ void operator()(const Func & f, Args... args) const {\n        f(n - 1, args...);\n        cuda_unroll<n - 1>{}(f, args...);\n    }\n};\n\ntemplate <>\nstruct cuda_unroll<1> {\n    template <typename Func, typename... Args>\n    __device__ void operator()(const Func & f, Args... args) const {\n        f(0, args...);\n    }\n};\n"
  },
  {
    "path": "cuda/src/kernels/cu/element_wise.cu",
    "content": "#include \"common.cuh\"\n#include <cuda_fp16.h>\n#include <cuda_runtime.h>\n\nstatic __device__ __forceinline__ float op_neg(float x) { return -x; }\nstatic __device__ __forceinline__ __half op_neg(__half x) { return -x; }\n\nstatic __device__ __forceinline__ float op_abs(float x) { return fabsf(x); }\nstatic __device__ __forceinline__ __half op_abs(__half x) { return __habs(x); }\n\nstatic __device__ __forceinline__ float op_sqr(float x) { return x * x; }\nstatic __device__ __forceinline__ __half op_sqr(__half x) { return __hmul(x, x); }\n\nstatic __device__ __forceinline__ float op_sqrt(float x) { return sqrtf(x); }\nstatic __device__ __forceinline__ __half op_sqrt(__half x) { return hsqrt(x); }\n\nstatic __device__ __forceinline__ float op_rsqrt(float x) { return 1.0f / sqrtf(x); }\nstatic __device__ __forceinline__ __half op_rsqrt(__half x) { return hrsqrt(x); }\n\nstatic __device__ __forceinline__ float op_recip(float x) { return 1.0f / x; }\nstatic __device__ __forceinline__ __half op_recip(__half x) { return hrcp(x); }\n\nstatic __device__ __forceinline__ float op_ceil(float x) { return ceilf(x); }\nstatic __device__ __forceinline__ __half op_ceil(__half x) { return hceil(x); }\n\nstatic __device__ __forceinline__ float op_floor(float x) { return floorf(x); }\nstatic __device__ __forceinline__ __half op_floor(__half x) { return hfloor(x); }\n\nstatic __device__ __forceinline__ float op_round(float x) { return round(x); }\nstatic __device__ __forceinline__ __half op_round(__half x) {\n    return (x < (__half)0.0f) ? hceil(x - (__half)0.5f) : hfloor(x + (__half)0.5f);\n}\n\nstatic __device__ __forceinline__ float op_rint(float x) { return rint(x); }\nstatic __device__ __forceinline__ __half op_rint(__half x) { return hrint(x); }\n\nstatic __device__ __forceinline__ float op_exp(float x) { return expf(x); }\nstatic __device__ __forceinline__ __half op_exp(__half x) { return hexp(x); }\n\nstatic __device__ __forceinline__ float op_sin(float x) { return sinf(x); }\nstatic __device__ __forceinline__ __half op_sin(__half x) { return hsin(x); }\n\nstatic __device__ __forceinline__ float op_sinh(float x) { return sinhf(x); }\nstatic __device__ __forceinline__ __half op_sinh(__half x) {\n    return (hexp(x) - hexp(-x)) / (__half)2.0f;\n}\n\nstatic __device__ __forceinline__ float op_asin(float x) { return asinf(x); }\nstatic __device__ __forceinline__ __half op_asin(__half x) { return (__half)asinf((float)x); }\n\nstatic __device__ __forceinline__ float op_asinh(float x) { return asinhf(x); }\nstatic __device__ __forceinline__ __half op_asinh(__half x) { return (__half)asinhf((float)x); }\n\nstatic __device__ __forceinline__ float op_cos(float x) { return cosf(x); }\nstatic __device__ __forceinline__ __half op_cos(__half x) { return hcos(x); }\n\nstatic __device__ __forceinline__ float op_cosh(float x) { return coshf(x); }\nstatic __device__ __forceinline__ __half op_cosh(__half x) {\n    return (hexp(x) + hexp(-x)) / (__half)2.0f;\n}\n\nstatic __device__ __forceinline__ float op_acos(float x) { return acosf(x); }\nstatic __device__ __forceinline__ __half op_acos(__half x) { return (__half)acosf((float)x); }\n\nstatic __device__ __forceinline__ float op_acosh(float x) { return acoshf(x); }\nstatic __device__ __forceinline__ __half op_acosh(__half x) { return (__half)acoshf((float)x); }\n\nstatic __device__ __forceinline__ float op_tan(float x) { return tanf(x); }\nstatic __device__ __forceinline__ __half op_tan(__half x) { return hsin(x) / hcos(x); }\n\nstatic __device__ __forceinline__ float op_tanh(float x) { return tanhf(x); }\nstatic __device__ __forceinline__ __half op_tanh(__half x) { return (__half)tanhf((float)x); }\n\nstatic __device__ __forceinline__ float op_atan(float x) { return atanf(x); }\nstatic __device__ __forceinline__ __half op_atan(__half x) { return (__half)atanf((float)x); }\n\nstatic __device__ __forceinline__ float op_atanh(float x) { return atanhf(x); }\nstatic __device__ __forceinline__ __half op_atanh(__half x) { return (__half)atanhf((float)x); }\n\nstatic __device__ __forceinline__ float op_sigmoid(float x) {\n    float y = 1.0f / (1.0f + expf(-fabsf(x)));\n    return (x < 0.0f) ? 1.0f - y : y;\n}\n\nstatic __device__ __forceinline__ __half op_sigmoid(__half x) {\n    __half y = (__half)1.0f / ((__half)1.0f + hexp(-__habs(x)));\n    return (x < (__half)0.0f) ? (__half)1.0f - y : y;\n    ;\n}\n\nstatic __device__ __forceinline__ float op_ln(float x) { return logf(x); }\nstatic __device__ __forceinline__ __half op_ln(__half x) { return hlog(x); }\n\nstatic __device__ __forceinline__ float op_erf(float x) { return erff(x); }\nstatic __device__ __forceinline__ __half op_erf(__half x) { return (__half)erff((float)x); }\n\nstatic __device__ __forceinline__ float op_silu(float x) { return (x / (1.0f + expf(-x))); }\nstatic __device__ __forceinline__ __half op_silu(__half x) {\n    return (x / ((__half)1.0f + hexp(-x)));\n}\nstatic __device__ __forceinline__ float op_sign(float x) {\n    return (x > 0.0f) ? 1.0f : ((x < 0.0f) ? -1.0f : 0.0f);\n}\nstatic __device__ __forceinline__ __half op_sign(__half x) {\n    return (x > (__half)0.0f) ? (__half)1.0f : ((x < (__half)0.0f) ? (__half)-1.0f : (__half)0.0f);\n}\n\nstatic __device__ __forceinline__ float op_hard_swish(float x) {\n    return x * fmaxf(0.0f, fminf(1.0f, x / 6.0f + 0.5f));\n}\nstatic __device__ __forceinline__ __half op_hard_swish(__half x) {\n    __half zero = (__half)0.0f;\n    __half one = (__half)1.0f;\n    __half six = (__half)6.0f;\n    __half half_val = (__half)0.5f;\n    __half t = __hmax(zero, __hmin(one, __hdiv(x, six) + half_val));\n    return __hmul(x, t);\n}\n\ntemplate <typename T> static __device__ __forceinline__ T op_bitnot(T x) { return ~x; }\n\n#define DEFINE_UNARY_KERNEL(name, tname, T, OP)                                                    \\\n    extern \"C\" __global__ void name##_##tname(const T *x, T *dst, int32_t k) {                     \\\n        int i = blockIdx.x * blockDim.x + threadIdx.x;                                             \\\n        if (i < k) {                                                                               \\\n            dst[i] = OP(x[i]);                                                                     \\\n        }                                                                                          \\\n    }\n\n#define DEFINE_OP_FOR_ALL_TYPES(name, OP)                                                          \\\n    DEFINE_UNARY_KERNEL(name, f32, float, OP)                                                      \\\n    DEFINE_UNARY_KERNEL(name, f16, __half, OP)\n\nDEFINE_OP_FOR_ALL_TYPES(element_wise_neg, op_neg)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_abs, op_abs)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_square, op_sqr)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_sqrt, op_sqrt)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_rsqrt, op_rsqrt)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_recip, op_recip)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_ceil, op_ceil)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_floor, op_floor)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_round, op_round)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_roundhalftoeven, op_rint)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_sin, op_sin)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_sinh, op_sinh)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_asin, op_asin)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_asinh, op_asinh)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_cos, op_cos)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_cosh, op_cosh)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_acos, op_acos)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_acosh, op_acosh)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_tan, op_tan)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_tanh, op_tanh)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_atan, op_atan)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_atanh, op_atanh)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_exp, op_exp)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_sigmoid, op_sigmoid)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_ln, op_ln)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_erf, op_erf)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_silu, op_silu)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_sign, op_sign)\nDEFINE_OP_FOR_ALL_TYPES(element_wise_hardswish, op_hard_swish)\n\nDEFINE_UNARY_KERNEL(element_wise_bitnot, u8, uint8_t, op_bitnot);\nDEFINE_UNARY_KERNEL(element_wise_bitnot, u16, uint16_t, op_bitnot);\nDEFINE_UNARY_KERNEL(element_wise_bitnot, u32, uint32_t, op_bitnot);\nDEFINE_UNARY_KERNEL(element_wise_bitnot, u64, uint64_t, op_bitnot);\nDEFINE_UNARY_KERNEL(element_wise_bitnot, i8, int8_t, op_bitnot);\nDEFINE_UNARY_KERNEL(element_wise_bitnot, i16, int16_t, op_bitnot);\nDEFINE_UNARY_KERNEL(element_wise_bitnot, i32, int32_t, op_bitnot);\nDEFINE_UNARY_KERNEL(element_wise_bitnot, i64, int64_t, op_bitnot);\n\nstatic __device__ __forceinline__ bool op_bitnot_bool(bool x) { return !x; }\nDEFINE_UNARY_KERNEL(element_wise_bitnot, bool, bool, op_bitnot_bool);\n"
  },
  {
    "path": "cuda/src/kernels/cu/flash_attn.cu",
    "content": "#include \"common.cuh\"\n#include <math_constants.h>\n\n#define EXPF __expf\n\n// NOTE: stride in bytes\ntemplate <int STRIDE> static __device__ __forceinline__ uint32_t swizzle(uint32_t index) {\n    // no need swizzling\n    if constexpr (STRIDE == 16)\n        return index;\n\n    uint32_t row_idx = (index / STRIDE) % 8;\n    uint32_t bits_to_xor = row_idx / max(64 / STRIDE, 1);\n    return index ^ (bits_to_xor << 4);\n}\n\nstatic __device__ inline void cp_async_cg_16B(uint32_t dst, const void *src) {\n    asm volatile(\"cp.async.cg.shared.global [%0], [%1], 16;\" ::\"r\"(dst), \"l\"(src));\n}\n\nstatic __device__ inline void cp_async_cg_16B_pred(uint32_t dst, const void *src, bool pred) {\n#if __CUDA_ARCH__ >= 800\n    asm volatile(\"{\\n\\t\"\n                 \".reg .pred p;\\n\\t\"\n                 \".reg .b32 z;\\n\\t\"\n                 \"mov.b32 z, 0;\\n\\t\"\n                 \"setp.ne.b32 p, %2, 0;\\n\\t\"                          // p = (pred != 0)\n                 \"@p   cp.async.cg.shared.global [%0], [%1], 16;\\n\\t\" // valid: async\n                                                                      // copy 16B\n                 \"@!p  st.shared.v4.b32 [%0], {z, z, z, z};\\n\\t\"      // invalid: zero-fill\n                                                                      // 16B\n                 \"}\\n\" ::\"r\"(dst),\n                 \"l\"(src), \"r\"((int)pred));\n#else\n    // Fallback path if you ever build for pre-SM80\n    if (pred) {\n        asm volatile(\"cp.async.cg.shared.global [%0], [%1], 16;\" ::\"r\"(dst), \"l\"(src));\n    } else {\n        int z = 0;\n        asm volatile(\"st.shared.v4.b32 [%0], {%1,%1,%1,%1};\" ::\"r\"(dst), \"r\"(z));\n    }\n#endif\n}\n\n// ============================================================================\n// Global->shared loaders\n// ============================================================================\n\ntemplate <int HEIGHT, int DIM, int PADDED_DIM, int TB_SIZE>\nstatic __device__ __forceinline__ void\nglobal_to_shared_swizzle_pad(uint32_t dst, const half *__restrict__ src, int tid, int valid_rows) {\n    static_assert(DIM % 8 == 0, \"DIM must be multiple of 8 (16B segments)\");\n    static_assert(PADDED_DIM % 8 == 0, \"PADDED_DIM must be multiple of 8\");\n    static_assert(PADDED_DIM >= DIM, \"PADDED_DIM must be >= DIM\");\n\n    constexpr int segs_dst = PADDED_DIM / 8; // 16B segments per padded row\n    constexpr int segs_src = DIM / 8;\n\n    constexpr int total_segs = HEIGHT * segs_dst;\n    static_assert(total_segs % TB_SIZE == 0, \"total_segs must divide TB_SIZE\");\n    constexpr int iters = total_segs / TB_SIZE;\n\n    _Pragma(\"unroll\") for (int it = 0; it < iters; ++it) {\n        const int seg_idx = it * TB_SIZE + tid; // 0..total_segs-1\n        const int row = seg_idx / segs_dst;\n        const int seg = seg_idx % segs_dst;\n        const int col = seg * 8; // in half elements\n\n        const uint32_t dst_addr =\n            swizzle<PADDED_DIM * sizeof(half)>(dst + (row * PADDED_DIM + col) * sizeof(half));\n        const bool pred = (row < valid_rows) && (seg < segs_src);\n        // apparently, \"forming\" an invalid pointer in c++ UB, even not dereferrencing it.\n        const half *__restrict__ src_addr = pred ? (src + (row * DIM + col)) : src;\n\n        cp_async_cg_16B_pred(dst_addr, src_addr, pred);\n    }\n}\n\n// ============================================================================\n// Tensor Core helpers\n// ============================================================================\nstatic __device__ __forceinline__ void ldmatrix_x4(uint32_t regs[4], uint32_t addr) {\n    asm volatile(\"ldmatrix.sync.aligned.m8n8.x4.shared.b16 {%0, %1, %2, %3}, [%4];\"\n                 : \"=r\"(regs[0]), \"=r\"(regs[1]), \"=r\"(regs[2]), \"=r\"(regs[3])\n                 : \"r\"(addr));\n}\nstatic __device__ __forceinline__ void ldmatrix_x4_trans(uint32_t regs[4], uint32_t addr) {\n    asm volatile(\"ldmatrix.sync.aligned.m8n8.x4.trans.shared.b16 {%0, %1, %2, %3}, [%4];\"\n                 : \"=r\"(regs[0]), \"=r\"(regs[1]), \"=r\"(regs[2]), \"=r\"(regs[3])\n                 : \"r\"(addr));\n}\nstatic __device__ __forceinline__ void ldmatrix_x2(uint32_t regs[2], uint32_t addr) {\n    asm volatile(\"ldmatrix.sync.aligned.m8n8.x2.shared.b16 {%0, %1}, [%2];\"\n                 : \"=r\"(regs[0]), \"=r\"(regs[1])\n                 : \"r\"(addr));\n}\nstatic __device__ __forceinline__ void ldmatrix_x2_trans(uint32_t regs[2], uint32_t addr) {\n    asm volatile(\"ldmatrix.sync.aligned.m8n8.x2.trans.shared.b16 {%0, %1}, [%2];\"\n                 : \"=r\"(regs[0]), \"=r\"(regs[1])\n                 : \"r\"(addr));\n}\nstatic __device__ __forceinline__ void mma_m16n8k16(uint32_t A[4], uint32_t B[2], float D[4]) {\n    asm volatile(\"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 \"\n                 \"{%0, %1, %2, %3}, \"\n                 \"{%4, %5, %6, %7}, \"\n                 \"{%8, %9}, \"\n                 \"{%10, %11, %12, %13};\"\n                 : \"=f\"(D[0]), \"=f\"(D[1]), \"=f\"(D[2]), \"=f\"(D[3])\n                 : \"r\"(A[0]), \"r\"(A[1]), \"r\"(A[2]), \"r\"(A[3]), \"r\"(B[0]), \"r\"(B[1]), \"f\"(D[0]),\n                   \"f\"(D[1]), \"f\"(D[2]), \"f\"(D[3]));\n}\n\n// ============================================================================\n// Select and predicated global store\n// ============================================================================\nstatic __device__ __forceinline__ float fsel_neg_inf(bool keep, float x) {\n    float out;\n    asm volatile(\"{\\n\\t\"\n                 \".reg .pred p;\\n\\t\"\n                 \"setp.ne.b32 p, %2, 0;\\n\\t\"\n                 \"selp.f32 %0, %1, %3, p;\\n\\t\"\n                 \"}\\n\"\n                 : \"=f\"(out)\n                 : \"f\"(x), \"r\"((int)keep), \"f\"(-CUDART_INF_F));\n    return out;\n}\n\nstatic __device__ __forceinline__ void st_global_half2_pred(half2 *addr, half2 val, bool pred) {\n#if __CUDA_ARCH__ >= 700\n    uint32_t v;\n    memcpy(&v, &val, sizeof(v)); // pack half2 to 32b\n    asm volatile(\"{\\n\\t\"\n                 \".reg .pred p;\\n\\t\"\n                 \"setp.ne.b32 p, %2, 0;\\n\\t\"\n                 \"@p st.global.b32 [%0], %1;\\n\\t\"\n                 \"}\\n\" ::\"l\"(addr),\n                 \"r\"(v), \"r\"((int)pred));\n#else\n    if (pred) {\n        *addr = val;\n    }\n#endif\n}\n\nenum MaskMode { nomask = 0, mask = 1, causal = 2 };\nenum QTileMode { fullq = 0, tailq = 1 };\n\n// ======= kv_iter_body (invariants hoisted, tight scopes) =====================\ntemplate <int BLOCK_Q, int BLOCK_KV, int DIM, int PADDED_DIM, int NUM_WARPS, MaskMode MASK_MODE,\n          QTileMode Q_TILE_MODE, bool kv_tail>\nstatic __device__ __forceinline__ void\nkv_iter_body(const int kv_tile_base, const int len_q, const int len_kv, const int past,\n             const int q_block_base, const int warp_id, const int lane_id, float scale,\n             const half *__restrict__ MaskBase,\n             float S_rmem[(BLOCK_Q / NUM_WARPS) / 16][BLOCK_KV / 8][4],\n             uint32_t (&P_rmem)[(BLOCK_Q / NUM_WARPS) / 16][BLOCK_KV / 16][4],\n             float (&O_rmem)[(BLOCK_Q / NUM_WARPS) / 16][PADDED_DIM / 8][4],\n             float (&rowmax)[(BLOCK_Q / NUM_WARPS) / 16][2],\n             float (&rowsumexp)[(BLOCK_Q / NUM_WARPS) / 16][2]) {\n    static_assert(BLOCK_Q <= 2 * BLOCK_KV);\n    constexpr int WARP_Q = BLOCK_Q / NUM_WARPS;\n    constexpr int MMA_M = 16, MMA_N = 8;\n\n    // hoisted per-lane constants\n    const int lane_row0 = (lane_id >> 2); // 0..7\n    const int lane_row1 = lane_row0 + 8;\n    const int j_pair0 = (lane_id & 3) << 1;\n\n    // per-qi\n    _Pragma(\"unroll\") for (int qi = 0; qi < WARP_Q / MMA_M; ++qi) {\n        // hoisted per-qi invariants\n        const int base_i_block = warp_id * (BLOCK_Q / NUM_WARPS) + qi * MMA_M;\n        const int i0 = base_i_block + lane_row0;\n        const int i1 = base_i_block + lane_row1;\n        const int i0g = q_block_base + i0;\n        const int i1g = q_block_base + i1;\n        int jlim0 = 0, jlim1 = 0;\n        if constexpr (MASK_MODE == causal) {\n            jlim0 = past + i0g;\n            jlim1 = past + i1g;\n        }\n\n        // ---- scale, (mask/causal), rowmax (tight scope for S_rmem live range)\n        // ---\n        float this_rowmax0 = -CUDART_INF_F;\n        float this_rowmax1 = -CUDART_INF_F;\n\n        _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_N; ++kvt) {\n            float *r = S_rmem[qi][kvt];\n            // scale\n            r[0] *= scale;\n            r[1] *= scale;\n            r[2] *= scale;\n            r[3] *= scale;\n\n            const int col_base_tile = kv_tile_base + kvt * MMA_N;\n            const int j0 = col_base_tile + j_pair0 + 0;\n            const int j1 = col_base_tile + j_pair0 + 1;\n\n            if constexpr (kv_tail) {\n                // tail validity\n                const bool i0_valid = Q_TILE_MODE == fullq ? true : (i0g < len_q);\n                const bool i1_valid = Q_TILE_MODE == fullq ? true : (i1g < len_q);\n                const bool j0_valid = (j0 < len_kv);\n                const bool j1_valid = (j1 < len_kv);\n                bool c00 = j0_valid & i0_valid;\n                bool c01 = j1_valid & i0_valid;\n                bool c10 = j0_valid & i1_valid;\n                bool c11 = j1_valid & i1_valid;\n\n                if constexpr (MASK_MODE == causal) {\n                    c00 &= (j0 <= jlim0);\n                    c01 &= (j1 <= jlim0);\n                    c10 &= (j0 <= jlim1);\n                    c11 &= (j1 <= jlim1);\n                } else if constexpr (MASK_MODE == mask) {\n                    if (i0_valid && j0_valid)\n                        r[0] += (float)MaskBase[(size_t)i0 * len_kv + j0];\n                    if (i0_valid && j1_valid)\n                        r[1] += (float)MaskBase[(size_t)i0 * len_kv + j1];\n                    if (i1_valid && j0_valid)\n                        r[2] += (float)MaskBase[(size_t)i1 * len_kv + j0];\n                    if (i1_valid && j1_valid)\n                        r[3] += (float)MaskBase[(size_t)i1 * len_kv + j1];\n                }\n\n                r[0] = fsel_neg_inf(c00, r[0]);\n                r[1] = fsel_neg_inf(c01, r[1]);\n                r[2] = fsel_neg_inf(c10, r[2]);\n                r[3] = fsel_neg_inf(c11, r[3]);\n            } else {\n                // full tiles: no row/col bounds\n                if constexpr (MASK_MODE == causal) {\n                    if (j0 > jlim0)\n                        r[0] = -CUDART_INF_F;\n                    if (j1 > jlim0)\n                        r[1] = -CUDART_INF_F;\n                    if (j0 > jlim1)\n                        r[2] = -CUDART_INF_F;\n                    if (j1 > jlim1)\n                        r[3] = -CUDART_INF_F;\n                } else if constexpr (MASK_MODE == mask) {\n                    r[0] += (float)MaskBase[(size_t)i0 * len_kv + j0];\n                    r[1] += (float)MaskBase[(size_t)i0 * len_kv + j1];\n                    r[2] += (float)MaskBase[(size_t)i1 * len_kv + j0];\n                    r[3] += (float)MaskBase[(size_t)i1 * len_kv + j1];\n                }\n            }\n\n            // rowmax accumulate\n            this_rowmax0 = fmaxf(this_rowmax0, fmaxf(r[0], r[1]));\n            this_rowmax1 = fmaxf(this_rowmax1, fmaxf(r[2], r[3]));\n        }\n\n        // small warp reduce (xor tree 1,2)\n        this_rowmax0 = fmaxf(this_rowmax0, __shfl_xor_sync(0xFFFFFFFF, this_rowmax0, 1));\n        this_rowmax0 = fmaxf(this_rowmax0, __shfl_xor_sync(0xFFFFFFFF, this_rowmax0, 2));\n        this_rowmax1 = fmaxf(this_rowmax1, __shfl_xor_sync(0xFFFFFFFF, this_rowmax1, 1));\n        this_rowmax1 = fmaxf(this_rowmax1, __shfl_xor_sync(0xFFFFFFFF, this_rowmax1, 2));\n\n        this_rowmax0 = fmaxf(this_rowmax0, rowmax[qi][0]);\n        this_rowmax1 = fmaxf(this_rowmax1, rowmax[qi][1]);\n        // rescale accumulators with EXACT same factor you'll use for\n        // denominators\n        const float rescale0 = EXPF(rowmax[qi][0] - this_rowmax0);\n        const float rescale1 = EXPF(rowmax[qi][1] - this_rowmax1);\n\n        _Pragma(\"unroll\") for (int d = 0; d < PADDED_DIM / MMA_N; ++d) {\n            O_rmem[qi][d][0] *= rescale0;\n            O_rmem[qi][d][1] *= rescale0;\n            O_rmem[qi][d][2] *= rescale1;\n            O_rmem[qi][d][3] *= rescale1;\n        }\n        rowmax[qi][0] = this_rowmax0;\n        rowmax[qi][1] = this_rowmax1;\n        float this_rowsumexp0 = 0.f, this_rowsumexp1 = 0.f;\n\n        // exponentiate, pack directly into P_rmem (keep S_rmem live only here)\n        _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_N; ++kvt) {\n            float *r = S_rmem[qi][kvt];\n            r[0] = EXPF(r[0] - this_rowmax0);\n            r[1] = EXPF(r[1] - this_rowmax0);\n            r[2] = EXPF(r[2] - this_rowmax1);\n            r[3] = EXPF(r[3] - this_rowmax1);\n\n            this_rowsumexp0 += (r[0] + r[1]);\n            this_rowsumexp1 += (r[2] + r[3]);\n\n            half2 *Ppack = reinterpret_cast<half2 *>(P_rmem[qi][kvt / 2]);\n            Ppack[(kvt & 1) * 2] = __float22half2_rn({r[0], r[1]});\n            Ppack[(kvt & 1) * 2 + 1] = __float22half2_rn({r[2], r[3]});\n        }\n\n        // reduce sums (xor 1,2)\n        this_rowsumexp0 += __shfl_xor_sync(0xFFFFFFFF, this_rowsumexp0, 1);\n        this_rowsumexp0 += __shfl_xor_sync(0xFFFFFFFF, this_rowsumexp0, 2);\n        this_rowsumexp1 += __shfl_xor_sync(0xFFFFFFFF, this_rowsumexp1, 1);\n        this_rowsumexp1 += __shfl_xor_sync(0xFFFFFFFF, this_rowsumexp1, 2);\n\n        rowsumexp[qi][0] = rowsumexp[qi][0] * rescale0 + this_rowsumexp0;\n        rowsumexp[qi][1] = rowsumexp[qi][1] * rescale1 + this_rowsumexp1;\n    }\n}\n\n// ============================================================================\n// Kernel. Adapted from:\n// https://github.com/gau-nernst/learn-cuda/blob/main/07_attention/attention_v5.cu\n// ============================================================================\ntemplate <int BLOCK_Q, int BLOCK_KV, int DIM, int PADDED_DIM, int NUM_WARPS, MaskMode MASK_MODE,\n          QTileMode Q_TILE_MODE>\nstatic __device__ void attention_kernel(const half *__restrict__ Q, // [bs, len_q, DIM]\n                                        const half *__restrict__ K, // [bs, len_kv, DIM]\n                                        const half *__restrict__ V, // [bs, len_kv, DIM]\n                                        const half *__restrict__ M, // [bs, len_q, len_kv]\n                                        half *__restrict__ O,       // [bs, len_q, DIM]\n                                        int32_t bs, int32_t qh, int32_t head_ratio, int32_t len_q,\n                                        int32_t len_kv, int32_t mask_b_stride,\n                                        int32_t mask_h_stride, float scale) {\n    constexpr int TB_SIZE = NUM_WARPS * WARP_SIZE;\n    constexpr int WARP_Q = BLOCK_Q / NUM_WARPS;\n    constexpr int MMA_M = 16, MMA_N = 8, MMA_K = 16;\n\n    const int bid = blockIdx.z;\n    const int hid = blockIdx.y;\n    const int q_block_id = blockIdx.x;\n\n    const int tid = threadIdx.x;\n    const int warp_id = tid / WARP_SIZE;\n    const int lane_id = tid % WARP_SIZE;\n\n    int q_block_id_offset = 0;\n    if constexpr (Q_TILE_MODE == tailq) {\n        q_block_id_offset = (len_q / BLOCK_Q);\n    }\n    const int q_block_base = (q_block_id + q_block_id_offset) * BLOCK_Q;\n    const int q_valid = max(0, min(BLOCK_Q, (int)len_q - q_block_base));\n    const int past = len_kv - len_q; // causal base\n\n    const int q_heads = qh;\n    const int kv_heads = q_heads / head_ratio;\n    const int kv_head_id = hid / head_ratio;\n\n    // Base pointers\n    const half *Qptr = Q + (((size_t)bid * q_heads + hid) * (size_t)len_q + q_block_base) * DIM;\n    const half *Kptr = K + (((size_t)bid * kv_heads + kv_head_id) * (size_t)len_kv) * DIM;\n    const half *Vptr = V + (((size_t)bid * kv_heads + kv_head_id) * (size_t)len_kv) * DIM;\n    half *Optr = O + (((size_t)bid * q_heads + hid) * (size_t)len_q + q_block_base) * DIM;\n\n    const half *__restrict__ MaskBase = nullptr;\n    if constexpr (MASK_MODE == mask) {\n        if (M) {\n            MaskBase =\n                M + (size_t)(q_block_base * len_kv + mask_b_stride * bid + mask_h_stride * hid);\n        }\n    }\n\n    // Shared memory layout:\n    // Q_smem (BLOCK_Q x DIM) overlaps K_smem (2 * BLOCK_KV x DIM), plus V_smem\n    // (BLOCK_KV x DIM)\n    extern __shared__ half smem[];\n    const uint32_t Q_smem = __cvta_generic_to_shared(smem);\n    const uint32_t K_smem = Q_smem; // double buffer for K\n    const uint32_t V_smem = K_smem + 2 * BLOCK_KV * PADDED_DIM * sizeof(half);\n\n    // Per-thread swizzled bases\n    uint32_t Q_smem_thread, K_smem_thread, V_smem_thread;\n    {\n        const int row_off = warp_id * WARP_Q + (lane_id % 16);\n        const int col_off = (lane_id / 16) * 8;\n        Q_smem_thread = swizzle<PADDED_DIM * sizeof(half)>(\n            Q_smem + (row_off * PADDED_DIM + col_off) * sizeof(half));\n    }\n    {\n        const int row_off = lane_id % 8;\n        const int col_off = (lane_id / 8) * 8;\n        K_smem_thread = swizzle<PADDED_DIM * sizeof(half)>(\n            K_smem + (row_off * PADDED_DIM + col_off) * sizeof(half));\n    }\n    {\n        const int row_off = lane_id % 16;\n        const int col_off = (lane_id / 16) * 8;\n        V_smem_thread = swizzle<PADDED_DIM * sizeof(half)>(\n            V_smem + (row_off * PADDED_DIM + col_off) * sizeof(half));\n    }\n\n    // Registers\n    uint32_t Q_rmem[WARP_Q / MMA_M][PADDED_DIM / MMA_K][4];\n    uint32_t K_rmem[BLOCK_KV / MMA_N][PADDED_DIM / MMA_K][2];\n    uint32_t V_rmem[BLOCK_KV / MMA_K][PADDED_DIM / MMA_N][2];\n    uint32_t P_rmem[WARP_Q / MMA_M][BLOCK_KV / MMA_K][4];\n    // Softmax accumulators\n    float O_rmem[WARP_Q / MMA_M][PADDED_DIM / MMA_N][4] = {};\n    float rowmax[WARP_Q / MMA_M][2];\n    float rowsumexp[WARP_Q / MMA_M][2] = {};\n    _Pragma(\"unroll\") for (int qi = 0; qi < WARP_Q / MMA_M; ++qi) {\n        rowmax[qi][0] = -FLT_MAX;\n        rowmax[qi][1] = -FLT_MAX;\n    }\n\n    // ------------------ Load Q (tail-safe for last block only)\n    // -----------------\n    global_to_shared_swizzle_pad<BLOCK_Q, DIM, PADDED_DIM, TB_SIZE>(\n        Q_smem, Qptr, tid, (Q_TILE_MODE == fullq ? BLOCK_Q : q_valid));\n    asm volatile(\"cp.async.commit_group;\");\n    asm volatile(\"cp.async.wait_all;\");\n    __syncthreads();\n\n    // Q: shared -> regs\n    _Pragma(\"unroll\") for (int qi = 0; qi < WARP_Q / MMA_M; ++qi)\n        _Pragma(\"unroll\") for (int dk = 0; dk < PADDED_DIM / MMA_K; ++dk) {\n        uint32_t addr = Q_smem_thread + qi * MMA_M * PADDED_DIM * sizeof(half);\n        addr ^= dk * MMA_K * sizeof(half);\n        ldmatrix_x4(Q_rmem[qi][dk], addr);\n    }\n    __syncthreads();\n\n    // ------------------ KV split: full tiles then optional tail\n    // ----------------\n    const int kv_full_iters = len_kv / BLOCK_KV; // exact full tiles\n\n    // ---- Prefetch K0 for FULL loop (unguarded) ----\n    const half *Kcur = Kptr;\n    const half *Vcur = Vptr;\n\n    if (kv_full_iters > 0) {\n        global_to_shared_swizzle_pad<BLOCK_KV, DIM, PADDED_DIM, TB_SIZE>(K_smem + 0, Kcur, tid,\n                                                                         BLOCK_KV);\n        Kcur += (size_t)BLOCK_KV * DIM;\n        asm volatile(\"cp.async.commit_group;\");\n    }\n\n    // ----------------------------- FULL KV LOOP\n    // -------------------------------\n    for (int kv_id = 0; kv_id < kv_full_iters; ++kv_id) {\n        const int kv_tile_base = kv_id * BLOCK_KV; // columns start for this tile\n\n        // Prefetch V (unguarded)\n        __syncthreads();\n        global_to_shared_swizzle_pad<BLOCK_KV, DIM, PADDED_DIM, TB_SIZE>(V_smem, Vcur, tid,\n                                                                         BLOCK_KV);\n        Vcur += (size_t)BLOCK_KV * DIM;\n        asm volatile(\"cp.async.commit_group;\");\n\n        // Wait K, load K into regs\n        asm volatile(\"cp.async.wait_group 1;\");\n        __syncthreads();\n        _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_N; ++kvt)\n            _Pragma(\"unroll\") for (int dk = 0; dk < PADDED_DIM / MMA_K; dk++) {\n            uint32_t addr = K_smem_thread + (kv_id % 2) * (BLOCK_KV * PADDED_DIM * sizeof(half));\n            addr += kvt * MMA_N * PADDED_DIM * sizeof(half);\n            addr ^= dk * MMA_K * sizeof(half);\n            ldmatrix_x2(K_rmem[kvt][dk], addr);\n        }\n\n        {\n            // S = Q @ K^T\n            float S_rmem[WARP_Q / MMA_M][BLOCK_KV / MMA_N][4] = {};\n            _Pragma(\"unroll\") for (int qi = 0; qi < WARP_Q / MMA_M; ++qi)\n                _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_N; ++kvt)\n                    _Pragma(\"unroll\") for (int dk = 0; dk < PADDED_DIM / MMA_K; ++dk)\n                        mma_m16n8k16(Q_rmem[qi][dk], K_rmem[kvt][dk], S_rmem[qi][kvt]);\n\n            // Prefetch next FULL K (unguarded)\n            if (kv_id + 1 < kv_full_iters) {\n                const uint32_t Kdst =\n                    K_smem + ((kv_id + 1) % 2) * (BLOCK_KV * PADDED_DIM * sizeof(half));\n                global_to_shared_swizzle_pad<BLOCK_KV, DIM, PADDED_DIM, TB_SIZE>(Kdst, Kcur, tid,\n                                                                                 BLOCK_KV);\n                Kcur += (size_t)BLOCK_KV * DIM;\n                asm volatile(\"cp.async.commit_group;\");\n            }\n\n            kv_iter_body<BLOCK_Q, BLOCK_KV, DIM, PADDED_DIM, NUM_WARPS, MASK_MODE, Q_TILE_MODE,\n                         false>(kv_tile_base, len_q, len_kv, past, q_block_base, warp_id, lane_id,\n                                scale, MaskBase, S_rmem, P_rmem, O_rmem, rowmax, rowsumexp);\n        }\n\n        // Wait V, load V and do O += P@V\n        asm volatile(\"cp.async.wait_group 1;\");\n        __syncthreads();\n\n        _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_K; ++kvt)\n            _Pragma(\"unroll\") for (int d = 0; d < PADDED_DIM / MMA_N; d++) {\n            uint32_t addr = V_smem_thread + kvt * MMA_K * PADDED_DIM * sizeof(half);\n            addr ^= d * MMA_N * sizeof(half);\n            ldmatrix_x2_trans(V_rmem[kvt][d], addr);\n        }\n\n        _Pragma(\"unroll\") for (int qi = 0; qi < WARP_Q / MMA_M; ++qi)\n            _Pragma(\"unroll\") for (int d = 0; d < PADDED_DIM / MMA_N; ++d)\n                _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_K; ++kvt) {\n            mma_m16n8k16(P_rmem[qi][kvt], V_rmem[kvt][d], O_rmem[qi][d]);\n        }\n    }\n\n    // ----------------------------- KV TAIL (optional)\n    // --------------------------\n    const int kv_rem = len_kv % BLOCK_KV;\n    if (kv_rem > 0) {\n        const int kv_tile_base = kv_full_iters * BLOCK_KV;\n\n        // Prefetch tail K (predicated)\n        const uint32_t Kdst = K_smem + (kv_full_iters % 2) * (BLOCK_KV * PADDED_DIM * sizeof(half));\n        global_to_shared_swizzle_pad<BLOCK_KV, DIM, PADDED_DIM, TB_SIZE>(Kdst, Kcur, tid, kv_rem);\n        Kcur += (size_t)BLOCK_KV * DIM;\n        asm volatile(\"cp.async.commit_group;\");\n\n        __syncthreads();\n        // Prefetch tail V (predicated)\n        global_to_shared_swizzle_pad<BLOCK_KV, DIM, PADDED_DIM, TB_SIZE>(V_smem, Vcur, tid, kv_rem);\n        Vcur += (size_t)BLOCK_KV * DIM;\n        asm volatile(\"cp.async.commit_group;\");\n\n        // Wait K, load K regs\n        asm volatile(\"cp.async.wait_group 1;\");\n        __syncthreads();\n        _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_N; ++kvt)\n            _Pragma(\"unroll\") for (int dk = 0; dk < PADDED_DIM / MMA_K; dk++) {\n            uint32_t addr =\n                K_smem_thread + (kv_full_iters % 2) * (BLOCK_KV * PADDED_DIM * sizeof(half));\n            addr += kvt * MMA_N * PADDED_DIM * sizeof(half);\n            addr ^= dk * MMA_K * sizeof(half);\n            ldmatrix_x2(K_rmem[kvt][dk], addr);\n        }\n\n        {\n            // S = Q @ K^T\n            float S_rmem[WARP_Q / MMA_M][BLOCK_KV / MMA_N][4] = {};\n            _Pragma(\"unroll\") for (int qi = 0; qi < WARP_Q / MMA_M; ++qi)\n                _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_N; ++kvt)\n                    _Pragma(\"unroll\") for (int dk = 0; dk < PADDED_DIM / MMA_K; ++dk)\n                        mma_m16n8k16(Q_rmem[qi][dk], K_rmem[kvt][dk], S_rmem[qi][kvt]);\n\n            kv_iter_body<BLOCK_Q, BLOCK_KV, DIM, PADDED_DIM, NUM_WARPS, MASK_MODE, Q_TILE_MODE,\n                         true>(kv_tile_base, len_q, len_kv, past, q_block_base, warp_id, lane_id,\n                               scale, MaskBase, S_rmem, P_rmem, O_rmem, rowmax, rowsumexp);\n        }\n        // Wait V and finish O += P@V (tail)\n        asm volatile(\"cp.async.wait_group 1;\");\n        __syncthreads();\n        _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_K; ++kvt)\n            _Pragma(\"unroll\") for (int d = 0; d < PADDED_DIM / MMA_N; d++) {\n            uint32_t addr = V_smem_thread + kvt * MMA_K * PADDED_DIM * sizeof(half);\n            addr ^= d * MMA_N * sizeof(half);\n            ldmatrix_x2_trans(V_rmem[kvt][d], addr);\n        }\n\n        // O += P @ V\n        _Pragma(\"unroll\") for (int qi = 0; qi < WARP_Q / MMA_M; ++qi)\n            _Pragma(\"unroll\") for (int d = 0; d < PADDED_DIM / MMA_N; ++d)\n                _Pragma(\"unroll\") for (int kvt = 0; kvt < BLOCK_KV / MMA_K; ++kvt)\n                    mma_m16n8k16(P_rmem[qi][kvt], V_rmem[kvt][d], O_rmem[qi][d]);\n    }\n\n    // ----------------------------- Writeback (Q tail-safe)\n    // ---------------------\n    _Pragma(\"unroll\") for (int qi = 0; qi < WARP_Q / MMA_M; ++qi)\n        _Pragma(\"unroll\") for (int d = 0; d < DIM / MMA_N; ++d) {\n        const int row0 = warp_id * WARP_Q + qi * MMA_M + (lane_id / 4);\n        const int col = d * MMA_N + (lane_id % 4) * 2;\n\n        float *regs = O_rmem[qi][d];\n        regs[0] /= rowsumexp[qi][0];\n        regs[1] /= rowsumexp[qi][0];\n        regs[2] /= rowsumexp[qi][1];\n        regs[3] /= rowsumexp[qi][1];\n\n        half2 v0 = __float22half2_rn({regs[0], regs[1]});\n        half2 v1 = __float22half2_rn({regs[2], regs[3]});\n\n        half2 *p0 = reinterpret_cast<half2 *>(Optr + (row0 + 0) * DIM + col);\n        half2 *p1 = reinterpret_cast<half2 *>(Optr + (row0 + 8) * DIM + col);\n\n        if constexpr (Q_TILE_MODE == fullq) {\n            // No predicates in full Q tiles\n            *p0 = v0;\n            *p1 = v1;\n        } else {\n            const bool valid0 = (q_block_base + row0) < len_q;\n            const bool valid1 = (q_block_base + row0 + 8) < len_q;\n            st_global_half2_pred(p0, v0, valid0);\n            st_global_half2_pred(p1, v1, valid1);\n        }\n    }\n}\n\n#define DEFINE_ATTN_KERNEL(name, BLOCK_Q, BLOCK_KV, D, PADDED_D, mask_mode, q_tile_mode)           \\\n    extern \"C\" {                                                                                   \\\n    __launch_bounds__(4 * WARP_SIZE) __global__                                                    \\\n        void name(const half *__restrict__ Q, const half *__restrict__ K,                          \\\n                  const half *__restrict__ V, const half *__restrict__ M, half *__restrict__ O,    \\\n                  int32_t bs, int32_t qh, int32_t head_ratio, int32_t len_q, int32_t len_kv,       \\\n                  int32_t mask_b_stride, int32_t mask_h_stride, float scale) {                     \\\n        attention_kernel<BLOCK_Q, BLOCK_KV, D, PADDED_D, 4, mask_mode, q_tile_mode>(               \\\n            Q, K, V, M, O, bs, qh, head_ratio, len_q, len_kv, mask_b_stride, mask_h_stride,        \\\n            scale);                                                                                \\\n    }                                                                                              \\\n    }\n\n#define INSTANTIATE_FLASH_ATTN_FOR_MASK_STRATEGY(BQ, BKV, D, PADDED_D, mask_mode)                  \\\n    DEFINE_ATTN_KERNEL(attention_fullq_##BQ##_##BKV##_##D##_##mask_mode, BQ, BKV, D, PADDED_D,     \\\n                       mask_mode, fullq)                                                           \\\n    DEFINE_ATTN_KERNEL(attention_tailq_##BQ##_##BKV##_##D##_##mask_mode, BQ, BKV, D, PADDED_D,     \\\n                       mask_mode, tailq)\n\n#define INSTANTIATE_FLASH_ATTN_FOR_D(block_q, block_kv, D, PADDED_D)                               \\\n    INSTANTIATE_FLASH_ATTN_FOR_MASK_STRATEGY(block_q, block_kv, D, PADDED_D, nomask)               \\\n    INSTANTIATE_FLASH_ATTN_FOR_MASK_STRATEGY(block_q, block_kv, D, PADDED_D, mask)                 \\\n    INSTANTIATE_FLASH_ATTN_FOR_MASK_STRATEGY(block_q, block_kv, D, PADDED_D, causal)\n\n#define INSTANTIATE_FLASH_ATTN_FOR_BLOCK_KV(block_q, block_kv)                                     \\\n    INSTANTIATE_FLASH_ATTN_FOR_D(block_q, block_kv, 64, 64)                                        \\\n    INSTANTIATE_FLASH_ATTN_FOR_D(block_q, block_kv, 128, 128)                                      \\\n    /* not a typo, pad 80 to 128 ! */                                                              \\\n    INSTANTIATE_FLASH_ATTN_FOR_D(block_q, block_kv, 80, 128)                                       \\\n    // Other supported D value.\n// Never encountered in practice so commented to keep compilation fast\n//                                           INSTANTIATE_FLASH_ATTN_FOR_D(block_q, block_kv, 96)\n//                                                                                                                                       \\\n  //INSTANTIATE_FLASH_ATTN_FOR_D(block_q, block_kv, 112) \\\n  //INSTANTIATE_FLASH_ATTN_FOR_D(block_q, block_kv, 256) \\\n\n#define INSTANTIATE_FLASH_ATTN_FOR_BLOCK_Q(block_q) INSTANTIATE_FLASH_ATTN_FOR_BLOCK_KV(block_q, 32)\n\n#define INSTANTIATE_FLASH_ATTN() INSTANTIATE_FLASH_ATTN_FOR_BLOCK_Q(64)\n\nINSTANTIATE_FLASH_ATTN()\n"
  },
  {
    "path": "cuda/src/kernels/cu/ggml_flash_attn.cu",
    "content": "#include \"common.cuh\"\n\n// Check CC Version\n#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < CUDA_CC_TURING)\n#error \"Requires GPU with compute capability 7.5 or higher\"\n#endif // __CUDA_ARCH__ >= CUDA_CC_TURING\n\n#if __CUDA_ARCH__ >= CUDA_CC_AMPERE\n#define CP_ASYNC_AVAILABLE\n#endif // __CUDA_ARCH__ >= CUDA_CC_AMPERE\n\n#define FATTN_KQ_STRIDE       256\n#define SOFTMAX_FTZ_THRESHOLD -20.0f\n\n#define CUDA_UNUSED(x) (void)(x)\n\ntemplate<typename... Args>\n__host__ __device__ constexpr inline void cuda_unused_vars_impl(Args&&...) noexcept {}\n#define CUDA_UNUSED_VARS(...) cuda_unused_vars_impl(__VA_ARGS__)\n\nenum cuda_type {\n    CUDA_TYPE_F32     = 0,\n    CUDA_TYPE_F16     = 1,\n    CUDA_TYPE_Q4_0    = 2,\n};\n\n// Aligned memory transfers of 8/16 bytes can be faster than 2 transfers with 4 bytes, especially on AMD.\ntemplate <int nbytes, int alignment = 0>\nstatic __device__ __forceinline__ void cuda_memcpy_1(void * __restrict__ dst, const void * __restrict__ src) {\n    if constexpr (alignment != 0) {\n        static_assert(nbytes % alignment == 0, \"bad alignment\");\n    }\n    constexpr int nb_per_cpy = alignment == 0 ? nbytes : alignment;\n\n#pragma unroll\n    for (int i = 0; i < nbytes/nb_per_cpy; ++i) {\n        if constexpr (nb_per_cpy == 1) {\n            ((char *) dst)[i] = ((const char *) src)[i];\n        } else if constexpr (nb_per_cpy == 2) {\n            ((short *) dst)[i] = ((const short *) src)[i];\n        } else if constexpr (nb_per_cpy == 4) {\n            ((int *) dst)[i] = ((const int *) src)[i];\n        } else if constexpr (nb_per_cpy == 8) {\n            ((int2 *) dst)[i] = ((const int2 *) src)[i];\n        } else if constexpr (nb_per_cpy == 16) {\n            ((int4 *) dst)[i] = ((const int4 *) src)[i];\n        } else {\n            static_assert(nbytes == 0 && nbytes == -1, \"bad nbytes\");\n        }\n    }\n}\n\ntypedef void (*dequantize_V_t)(const void *, void *, const int64_t);\ntypedef float (*vec_dot_KQ_t)(\n    const char * __restrict__ K_c, const void * __restrict__ Q_v, const int * __restrict__ Q_q8 , const void * __restrict__ Q_ds);\n\ntemplate <typename T, int ne>\nstatic __device__ __forceinline__ void dequantize_V_f16(const void * __restrict__ vx, void * __restrict__ dst, const int64_t i0) {\n    if constexpr (cuda::std::is_same_v<T, half>) {\n        cuda_memcpy_1<ne*sizeof(half)>(dst, (const half *) vx + i0);\n    } else if constexpr (cuda::std::is_same_v<T, float>) {\n        static_assert(ne % 2 == 0, \"bad ne\");\n        half2 tmp[ne/2];\n        cuda_memcpy_1<ne*sizeof(half)>(tmp, (const half *) vx + i0);\n        float2 * dst_f2 = (float2 *) dst;\n#pragma unroll\n        for (int l = 0; l < ne/2; ++l) {\n            dst_f2[l] = __half22float2(tmp[l]);\n        }\n    } else {\n        static_assert(cuda::std::is_same_v<T, void>, \"unsupported type\");\n    }\n}\n\ntemplate <typename T, int ne>\nstatic __device__ __forceinline__ void dequantize_V_q4_0(const void * __restrict__ vx, void * __restrict__ dst, const int64_t i0) {\n    const block_q4_0 * x = (const block_q4_0 *) vx;\n\n    const int64_t ib    =  i0          /  QK4_0;\n    const int     iqs   =  i0          % (QK4_0/2);\n    const int     shift = (i0 % QK4_0) / (QK4_0/2);\n\n    int q;\n    static_assert(ne == 2 || ne == 4, \"bad ne\");\n    cuda_memcpy_1<ne, 2>(&q, x[ib].qs + iqs);\n    q >>= 4*shift;\n    q &= 0x0F0F0F0F;\n    q = __vsubss4(q, 0x08080808);\n\n    const int8_t * q8 = (const int8_t *) &q;\n\n    if constexpr (cuda::std::is_same_v<T, half>) {\n        const half2 d = __half2half2(x[ib].d);\n\n#pragma unroll\n        for (int l0 = 0; l0 < ne; l0 += 2) {\n            ((half2 *) dst)[l0/2] = d * make_half2(q8[l0 + 0], q8[l0 + 1]);\n        }\n    } else\n\n    if constexpr (cuda::std::is_same_v<T, float>) {\n        const float d = x[ib].d;\n\n#pragma unroll\n        for (int l = 0; l < ne; ++l) {\n            ((float *) dst)[l] = d * q8[l];\n        }\n    } else {\n        static_assert(cuda::std::is_same_v<T, void>, \"bad type\");\n    }\n}\n\nstatic __device__ __forceinline__ void cuda_mad(float & acc, const half2 v, const half2 u) {\n    const float2 tmp = __half22float2(v*u);\n    acc += tmp.x + tmp.y;\n}\n\n//static __device__ __forceinline__ void cuda_mad(half2 & acc, const half2 v, const half2 u) {\n//    acc += v*u;\n//}\n\ntemplate <int D, int nthreads>\nstatic __device__ __forceinline__ float vec_dot_fattn_vec_KQ_f16(\n    const char * __restrict__ K_c, const void * __restrict__ Q_v, const int * __restrict__ Q_q8 , const void * __restrict__ Q_ds_v) {\n\n    const half2 * K_h2 = (const half2 *) K_c;\n    CUDA_UNUSED(Q_q8);\n    CUDA_UNUSED(Q_ds_v);\n\n    constexpr int cpy_ne = 4;\n\n    float sum = 0.0f;\n\n#pragma unroll\n    for (int k_KQ_0 = 0; k_KQ_0 < D/2; k_KQ_0 += nthreads*cpy_ne) {\n        half2 tmp[cpy_ne];\n        cuda_memcpy_1<sizeof(tmp)>(tmp, K_h2 + k_KQ_0 + (threadIdx.x % nthreads)*cpy_ne);\n#pragma unroll\n        for (int k_KQ_1 = 0; k_KQ_1 < cpy_ne; ++k_KQ_1) {\n            cuda_mad(sum, tmp[k_KQ_1], ((const half2  *) Q_v)[k_KQ_0/nthreads + k_KQ_1]);\n        }\n    }\n\n    return sum;\n}\n\ntemplate<int D, int nthreads>\nstatic __device__ __forceinline__ float vec_dot_fattn_vec_KQ_q4_0(\n    const char * __restrict__ K_c, const void * __restrict__ Q_v, const int * __restrict__ Q_q8, const void * __restrict__ Q_ds_v) {\n\n    const block_q4_0 * K_q4_0 = (const block_q4_0 *) K_c;\n    CUDA_UNUSED(Q_v);\n\n    float sum = 0.0f;\n\n#pragma unroll\n    for (int k_KQ_0 = 0; k_KQ_0 < int(D/sizeof(int)); k_KQ_0 += nthreads) {\n        const int k_KQ = k_KQ_0 + (nthreads == WARP_SIZE ? threadIdx.x : threadIdx.x % nthreads);\n\n        const int ib    = k_KQ /  QI8_1;\n        const int iqs4  = k_KQ %  QI4_0;\n        const int shift = k_KQ & (QI8_1/2);\n\n        int v;\n        cuda_memcpy_1<sizeof(int), 2>(&v, K_q4_0[ib].qs + sizeof(int)*iqs4);\n        v = (v >> shift) & 0x0F0F0F0F;\n        const int u = Q_q8[k_KQ_0/nthreads];\n\n        const int sumi = __dp4a(v, u, 0);\n\n        const float2 Q_ds = ((const float2 *) Q_ds_v)[k_KQ_0/nthreads];\n        sum += __half2float(K_q4_0[ib].d) * (sumi*Q_ds.x - (8/QI8_1)*Q_ds.y);\n    }\n\n    return sum;\n}\n\ntemplate <cuda_type type_V, typename T, int ne>\nconstexpr __device__ dequantize_V_t get_dequantize_V() {\n    if constexpr (type_V == CUDA_TYPE_F16) {\n        return dequantize_V_f16<T, ne>;\n    } else if constexpr (type_V == CUDA_TYPE_Q4_0) {\n        return dequantize_V_q4_0<T, ne>;\n    } else {\n        static_assert(type_V == -1, \"bad type\");\n        return nullptr;\n    }\n}\n\ntemplate <cuda_type type_K, int D, int nthreads>\nconstexpr __device__ vec_dot_KQ_t get_vec_dot_KQ() {\n    if constexpr (type_K == CUDA_TYPE_F16) {\n        return vec_dot_fattn_vec_KQ_f16<D, nthreads>;\n    } else if constexpr (type_K == CUDA_TYPE_Q4_0) {\n        return vec_dot_fattn_vec_KQ_q4_0<D, nthreads>;\n    } else {\n        static_assert(type_K == -1, \"bad type\");\n        return nullptr;\n    }\n}\n\ntemplate <typename Tds, int ni>\nstatic __device__ __forceinline__ void quantize_q8_1_to_shared(\n    const float * __restrict__ x, const float scale, int * __restrict__ yq32, void * __restrict__ yds) {\n\n    float vals[sizeof(int)] = {0.0f};\n#pragma unroll\n    for (int l = 0; l < int(sizeof(int)); ++l) {\n        vals[l] = (ni == WARP_SIZE || threadIdx.x < ni) ? scale * x[4*threadIdx.x + l] : 0.0f;\n    }\n\n    float amax = fabsf(vals[0]);\n    float sum  = vals[0];\n#pragma unroll\n    for (int l = 1; l < int(sizeof(int)); ++l) {\n        amax = fmaxf(amax, fabsf(vals[l]));\n        sum += vals[l];\n    }\n#pragma unroll\n    for (int mask = QI8_1/2; mask > 0; mask >>= 1) {\n        amax = fmaxf(amax, __shfl_xor_sync(0xFFFFFFFF, amax, mask, 32));\n        sum +=             __shfl_xor_sync(0xFFFFFFFF, sum,  mask, 32);\n    }\n\n    const float d = amax / 127;\n    int q32 = 0;\n    int8_t * q8 = (int8_t *) &q32;\n\n    if (d != 0.0f) {\n#pragma unroll\n        for (int l = 0; l < int(sizeof(int)); ++l) {\n            q8[l] = roundf(vals[l] / d);\n        }\n    }\n\n    yq32[threadIdx.x] = q32;\n    if (threadIdx.x % QI8_1 == 0 && (ni == WARP_SIZE || threadIdx.x < ni)) {\n        if (cuda::std::is_same<Tds, half2>::value) {\n            ((half2  *) yds)[threadIdx.x/QI8_1] =  make_half2(d, sum);\n        } else {\n            ((float2 *) yds)[threadIdx.x/QI8_1] = make_float2(d, sum);\n        }\n    }\n}\n\ntemplate<int D, int ncols, cuda_type type_K, cuda_type type_V> // D == head size\nstatic __device__ void flash_attn_ext_vec(\n        const char * __restrict__ Q,\n        const char * __restrict__ K,\n        const char * __restrict__ V,\n        const char * __restrict__ mask,\n        const int  * __restrict__ KV_max,\n        float      * __restrict__ dst,\n        float2     * __restrict__ dst_meta,\n        const float scale,\n        const int32_t ne00, const int32_t ne01, const int32_t ne02, const int32_t ne03,\n                            const int32_t nb01, const int32_t nb02, const int32_t nb03,\n        const int32_t ne10, const int32_t ne11, const int32_t ne12, const int32_t ne13,\n                            const int32_t nb11, const int32_t nb12, const int32_t nb13,\n                            const int32_t nb21, const int32_t nb22, const int32_t nb23,\n                            const int32_t ne31, const int32_t ne32, const int32_t ne33,\n                            const int32_t nb31, const int32_t nb32, const int32_t nb33) {\n    //In this kernel Q, K, V are matrices while i, j, k are matrix indices.\n    constexpr int cpy_nb = 16;\n    constexpr int cpy_ne = cpy_nb / 4;\n\n    constexpr int nthreads_KQ_q = (D/4 < 32 ? D/4 : 32);\n    constexpr int nthreads_V_q  = (D/4 < 32 ? D/4 : 32);\n\n    constexpr int nthreads    = 128;\n    constexpr int nthreads_KQ = type_K == CUDA_TYPE_F16 ? 128 / cpy_nb : nthreads_KQ_q;\n    constexpr int nthreads_V  = type_V == CUDA_TYPE_F16 ? 128 / cpy_nb : nthreads_V_q;\n\n    static_assert(WARP_SIZE % nthreads_KQ == 0, \"bad nthreads_K\");\n    static_assert(WARP_SIZE % nthreads_V  == 0, \"bad nthreads_V\");\n\n    constexpr int V_rows_per_thread = type_V == CUDA_TYPE_F16 ? 2*cpy_ne : 4;\n    constexpr int V_cols_per_iter   = WARP_SIZE / nthreads_V;\n\n    constexpr vec_dot_KQ_t vec_dot_KQ = get_vec_dot_KQ<type_K, D, nthreads_KQ>();\n    constexpr bool Q_q8_1 = type_K != CUDA_TYPE_F16;\n\n    constexpr dequantize_V_t dequantize_V = get_dequantize_V<type_V, half,  V_rows_per_thread>();\n\n    const int ic0 = blockIdx.x * ncols; // Index of the Q/QKV column to work on.\n\n    const int sequence = blockIdx.z / ne02;\n    const int head = blockIdx.z - sequence*ne02;\n    const int gqa_ratio = ne02 / ne12; // With grouped query attention there are > 1 Q matrices per K, V matrix.\n    Q += nb03*sequence + nb02* head              + nb01*ic0;\n    K += nb13*sequence + nb12*(head / gqa_ratio);\n    V += nb23*sequence + nb22*(head / gqa_ratio);\n\n    const half * maskh  = (const half  *) (mask + nb33*(sequence % ne33) + nb31*ic0);\n\n    static_assert(D % (2*WARP_SIZE) == 0, \"D not divisible by 2*WARP_SIZE == 64.\");\n    constexpr int nwarps = nthreads / WARP_SIZE;\n    const int tid = WARP_SIZE*threadIdx.y + threadIdx.x;\n    __builtin_assume(tid < nthreads);\n\n    constexpr int ne_KQ      = ncols*D;\n    constexpr int ne_combine = nwarps*V_cols_per_iter*D;\n\n    half2            VKQ[ncols][(D/2)/nthreads_V] = {{{0.0f, 0.0f}}};\n    __shared__ half   KQ[ne_KQ > ne_combine ? ne_KQ : ne_combine];\n\n    float KQ_max[ncols];\n    float KQ_sum[ncols];\n#pragma unroll\n    for (int j = 0; j < ncols; ++j) {\n        KQ_max[j] = -FLT_MAX/2.0f;\n        KQ_sum[j] = 0.0f;\n    }\n\n    // Convert Q to float2 (f16 K) or q8_1 (quantized K) and store in registers:\n    half2  Q_reg[ncols][(D/2)/nthreads_KQ]; // Will be initialized completely.\n\n    int    Q_i32[ncols][1 > D/(sizeof(int)*nthreads_KQ) ? 1 : D/(sizeof(int)*nthreads_KQ)];\n    float2  Q_ds[ncols][1 > D/(sizeof(int)*nthreads_KQ) ? 1 : D/(sizeof(int)*nthreads_KQ)];\n    if constexpr (Q_q8_1) {\n#pragma unroll\n        for (int j0 = 0; j0 < ncols; j0 += nwarps) {\n            const int j = j0 + threadIdx.y;\n\n            if (j0 + nwarps > ncols && j >= ncols) {\n                break;\n            }\n\n            // Reuse KQ as temporary storage for converting Q to q8_1:\n            int    * tmp_q_i32 = (int    *) &KQ[j*D];\n            float2 * tmp_q_ds  = (float2 *) (tmp_q_i32 + D/sizeof(int));\n\n            // Set memory to zero if out of bounds:\n            if (ncols > 1 && ic0 + j >= ne01) {\n#pragma unroll\n                for (int i0 = 0; i0 < int(D/sizeof(int)); i0 += WARP_SIZE) {\n                    const int i = i0 + threadIdx.x;\n\n                    if (i0 + WARP_SIZE <= D/sizeof(int) || i < D/sizeof(int)) {\n                        tmp_q_i32[i] = 0;\n                    }\n                }\n                if (threadIdx.x < D/QK8_1) {\n                    tmp_q_ds[threadIdx.x] = make_float2(0.0f, 0.0f);\n                }\n            } else {\n                const float * Q_f = (const float *) (Q + j*nb01);\n                constexpr int nthreads_quantize = D/sizeof(int) < WARP_SIZE ? D/sizeof(int) : WARP_SIZE;\n#pragma unroll\n                for (int i0 = 0; i0 < int(D/sizeof(int)); i0 += nthreads_quantize) {\n                    quantize_q8_1_to_shared<float2, nthreads_quantize>\n                        (Q_f + i0*sizeof(int), scale, tmp_q_i32 + i0, tmp_q_ds + i0/QI8_1);\n                }\n            }\n        }\n\n        __syncthreads();\n\n#pragma unroll\n        for (int j = 0; j < ncols; ++j) {\n            int    * tmp_q_i32 = (int    *) &KQ[j*D];\n            float2 * tmp_q_ds  = (float2 *) (tmp_q_i32 + D/sizeof(int));\n\n#pragma unroll\n            for (int i0 = 0; i0 < int(D/sizeof(int)); i0 += nthreads_KQ) {\n                const int i = i0 + (nthreads_KQ == WARP_SIZE ? threadIdx.x : threadIdx.x % nthreads_KQ);\n\n                Q_i32[j][i0/nthreads_KQ] = tmp_q_i32[i];\n                Q_ds[j][i0/nthreads_KQ]  = tmp_q_ds[i/QI8_1];\n            }\n        }\n\n        __syncthreads();\n    } else {\n        const half2 scale_h2 = make_half2(scale, scale);\n#pragma unroll\n        for (int j = 0; j < ncols; ++j) {\n            const float2 * Q_j = (const float2 *) (Q + j*nb01);\n#pragma unroll\n            for (int i0 = 0; i0 < D/2; i0 += nthreads_KQ*cpy_ne) {\n                const int i = i0 + (nthreads_KQ == WARP_SIZE ? threadIdx.x : threadIdx.x % nthreads_KQ)*cpy_ne;\n\n                float2 tmp[cpy_ne] = {{0.0f, 0.0f}};\n                if (ncols == 1 || ic0 + j < ne01) {\n                    cuda_memcpy_1<cpy_nb>(tmp,            &Q_j[i]);\n                    cuda_memcpy_1<cpy_nb>(tmp + cpy_ne/2, &Q_j[i + cpy_ne/2]);\n                }\n#pragma unroll\n                for (int i1 = 0; i1 < cpy_ne; ++i1) {\n                    Q_reg[j][i0/nthreads_KQ + i1] = make_half2(tmp[i1].x, tmp[i1].y);\n                }\n            }\n#pragma unroll\n            for (int k = 0; k < (D/2)/nthreads_KQ; ++k) {\n                Q_reg[j][k] *= scale_h2;\n            }\n        }\n    }\n\n    const int k_VKQ_max = KV_max ? KV_max[sequence*gridDim.x + blockIdx.x] : ne11;\n    K     += blockIdx.y*nthreads * nb11;\n    V     += blockIdx.y*nthreads * nb21;\n    maskh += blockIdx.y*nthreads;\n    for (int k_VKQ_0 = blockIdx.y*nthreads; k_VKQ_0 < k_VKQ_max; k_VKQ_0 += gridDim.y*nthreads,\n             // Increment pointers after each loop:\n             K += gridDim.y*nthreads*nb11, V += gridDim.y*nthreads*nb21, maskh += gridDim.y*nthreads) {\n\n        // Calculate KQ tile and keep track of new maximum KQ values:\n        float KQ_reg[ncols]; // KQ in registers.\n\n        float KQ_max_new[ncols];\n#pragma unroll\n        for (int j = 0; j < ncols; ++j) {\n            KQ_max_new[j] = KQ_max[j];\n        }\n\n#pragma unroll\n        for (int i_KQ_0 = 0; i_KQ_0 < nthreads_KQ; ++i_KQ_0) {\n            const int i_KQ = threadIdx.y*WARP_SIZE + (nthreads_KQ == WARP_SIZE ? 0 : (threadIdx.x & ~(nthreads_KQ-1))) + i_KQ_0;\n\n#pragma unroll\n            for (int j = 0; j < ncols; ++j) {\n                float sum = vec_dot_KQ(K + i_KQ*nb11, Q_reg[j], Q_i32[j], Q_ds[j]);\n                sum = warp_reduce_sum<nthreads_KQ>(sum);\n\n                if (mask) {\n                    sum += __half2float(maskh[j*ne11 + i_KQ]);\n                }\n\n                KQ_max_new[j] = fmaxf(KQ_max_new[j], sum);\n\n                if ((nthreads_KQ == WARP_SIZE ? threadIdx.x : threadIdx.x % nthreads_KQ) == i_KQ_0) {\n                    KQ_reg[j] = sum;\n                }\n            }\n        }\n\n#pragma unroll\n        for (int j = 0; j < ncols; ++j) {\n#pragma unroll\n            for (int offset = nthreads_KQ; offset < WARP_SIZE; offset <<= 1) {\n                KQ_max_new[j] = fmaxf(KQ_max_new[j], __shfl_xor_sync(0xFFFFFFFF, KQ_max_new[j], offset, WARP_SIZE));\n            }\n            const float KQ_max_scale = expf(KQ_max[j] - KQ_max_new[j]);\n            KQ_max[j] = KQ_max_new[j];\n\n            KQ_reg[j] = expf(KQ_reg[j] - KQ_max[j]);\n            KQ_sum[j] = KQ_sum[j]*KQ_max_scale + KQ_reg[j];\n            KQ[j*nthreads + tid] = KQ_reg[j];\n\n            const half2 KQ_max_scale_h2 = make_half2(KQ_max_scale, KQ_max_scale);\n#pragma unroll\n            for (int i_VKQ_0 = 0; i_VKQ_0 < D/2; i_VKQ_0 += nthreads_V) {\n                VKQ[j][i_VKQ_0/nthreads_V] *= KQ_max_scale_h2;\n            }\n        }\n\n        __syncwarp();\n\n#pragma unroll\n        for (int k0 = 0; k0 < WARP_SIZE; k0 += V_cols_per_iter) {\n            const int k = threadIdx.y*WARP_SIZE + k0 + (nthreads_V == WARP_SIZE ? 0 : threadIdx.x / nthreads_V);\n\n            half2 KQ_k[ncols];\n#pragma unroll\n            for (int j = 0; j < ncols; ++j) {\n                KQ_k[j] = __half2half2(KQ[j*nthreads + k]);\n            }\n#pragma unroll\n            for (int i_VKQ_0 = 0; i_VKQ_0 < D/2; i_VKQ_0 += nthreads_V*V_rows_per_thread/2) {\n                half2 tmp[V_rows_per_thread/2];\n                dequantize_V(V + k*nb21, tmp,\n                    2*i_VKQ_0 + (nthreads_V == WARP_SIZE ? threadIdx.x : threadIdx.x % nthreads_V)*V_rows_per_thread);\n#pragma unroll\n                for (int i_VKQ_1 = 0; i_VKQ_1 < V_rows_per_thread/2; ++i_VKQ_1) {\n#pragma unroll\n                    for (int j = 0; j < ncols; ++j) {\n                        VKQ[j][i_VKQ_0/nthreads_V + i_VKQ_1] += tmp[i_VKQ_1]*KQ_k[j];\n                    }\n                }\n            }\n        }\n    }\n\n    __shared__ float KQ_max_shared[ncols][WARP_SIZE];\n    __shared__ float KQ_sum_shared[ncols][WARP_SIZE];\n#pragma unroll\n    for (int j = 0; j < ncols; ++j) {\n        if (threadIdx.y == 0) {\n            KQ_max_shared[j][threadIdx.x] = -FLT_MAX/2.0f;\n            KQ_sum_shared[j][threadIdx.x] = 0.0f;\n        }\n    }\n\n    __syncthreads();\n\n#pragma unroll\n    for (int j = 0; j < ncols; ++j) {\n        if (threadIdx.x == 0) {\n            KQ_max_shared[j][threadIdx.y] = KQ_max[j];\n        }\n    }\n    __syncthreads();\n\n#pragma unroll\n    for (int j_VKQ = 0; j_VKQ < ncols; ++j_VKQ) {\n        if (ncols > 1 && ic0 + j_VKQ >= ne01) {\n            break;\n        }\n\n        float kqmax_new = KQ_max_shared[j_VKQ][threadIdx.x];\n        kqmax_new = warp_reduce_max(kqmax_new);\n        const float kqmax_scale = expf(KQ_max[j_VKQ] - kqmax_new);\n        KQ_max[j_VKQ] = kqmax_new;\n\n        half2 * VKQ_tmp = (half2 *) KQ + threadIdx.y*(V_cols_per_iter*D/2)\n            + (nthreads_V == WARP_SIZE ? 0 : threadIdx.x / nthreads_V)*(D/2);\n\n        const half2 kqmax_scale_h2 = make_half2(kqmax_scale, kqmax_scale);\n#pragma unroll\n        for (int i_VKQ_0 = 0; i_VKQ_0 < D/2; i_VKQ_0 += nthreads_V) {\n            VKQ[j_VKQ][i_VKQ_0/nthreads_V] *= kqmax_scale_h2;\n        }\n#pragma unroll\n        for (int i_VKQ_0 = 0; i_VKQ_0 < D/2; i_VKQ_0 += nthreads_V*V_rows_per_thread/2) {\n            const int i_VKQ = i_VKQ_0 + (nthreads_V == WARP_SIZE ? threadIdx.x : threadIdx.x % nthreads_V)*(V_rows_per_thread/2);\n\n            cuda_memcpy_1<V_rows_per_thread*sizeof(half)>(VKQ_tmp + i_VKQ, &VKQ[j_VKQ][i_VKQ_0/nthreads_V]);\n        }\n\n        KQ_sum[j_VKQ] *= kqmax_scale;\n        KQ_sum[j_VKQ] = warp_reduce_sum(KQ_sum[j_VKQ]);\n        if (threadIdx.x == 0) {\n            KQ_sum_shared[j_VKQ][threadIdx.y] = KQ_sum[j_VKQ];\n        }\n\n        __syncthreads();\n\n        if (nthreads <= D || tid < D) {\n            KQ_sum[j_VKQ] = KQ_sum_shared[j_VKQ][threadIdx.x];\n            KQ_sum[j_VKQ] = warp_reduce_sum(KQ_sum[j_VKQ]);\n\n#pragma unroll\n            for (int i0 = 0; i0 < D; i0 += nthreads) {\n                float dst_val = 0;\n#pragma unroll\n                for (int w = 0; w < nwarps; ++w) {\n#pragma unroll\n                    for (int v = 0; v < V_cols_per_iter; ++v) {\n                        dst_val += float(KQ[w*V_cols_per_iter*D + v*D + i0 + tid]);\n                    }\n                }\n                if (gridDim.y == 1) {\n                    dst_val /= KQ_sum[j_VKQ];\n                }\n                dst[(((sequence*ne02 + ic0 + j_VKQ)*ne01 + head)*gridDim.y + blockIdx.y)*D + i0 + tid] = dst_val;\n            }\n        }\n\n        if (j_VKQ < ncols-1) {\n            __syncthreads();\n        }\n\n    }\n\n    if (gridDim.y != 1 && tid < ncols && (ncols == 1 || ic0 + tid < ne01)) {\n        dst_meta[((sequence*ne01 + ic0 + tid)*ne02 + head)*gridDim.y + blockIdx.y] = make_float2(KQ_max[tid], KQ_sum[tid]);\n    }\n}\n\ntemplate<int D> // D == head size\nstatic __device__ void flash_attn_combine_results(\n        const float  * __restrict__ VKQ_parts,\n        const float2 * __restrict__ VKQ_meta,\n        float * __restrict__ dst,\n        const int32_t parallel_blocks) {\n    // Dimension 0: threadIdx.x\n    // Dimension 1: blockIdx.x\n    // Dimension 2: blockIdx.y\n    // Dimension 3: blockIdx.z\n    // Memory layout is permuted with [0, 2, 1, 3]\n\n    const int ne01 = gridDim.x;\n    const int ne02 = gridDim.y;\n\n    const int col      = blockIdx.x;\n    const int head     = blockIdx.y;\n    const int sequence = blockIdx.z;\n\n    const int j_dst_unrolled = (sequence*ne01 + col)*ne02 + head;\n\n    VKQ_parts += j_dst_unrolled * parallel_blocks*D;\n    VKQ_meta  += j_dst_unrolled * parallel_blocks;\n    dst       += j_dst_unrolled *                 D;\n\n    const int tid = threadIdx.x;\n    __builtin_assume(tid < D);\n\n    extern __shared__ float2 meta[];\n    for (int i = tid; i < 2*parallel_blocks; i += D) {\n        ((float *) meta)[i] = ((const float *)VKQ_meta) [i];\n    }\n\n    __syncthreads();\n\n    float kqmax = meta[0].x;\n    for (int l = 1; l < parallel_blocks; ++l) {\n        kqmax = max(kqmax, meta[l].x);\n    }\n\n    float VKQ_numerator   = 0.0f;\n    float VKQ_denominator = 0.0f;\n    for (int l = 0; l < parallel_blocks; ++l) {\n        const float KQ_max_scale = expf(meta[l].x - kqmax);\n\n        VKQ_numerator   += KQ_max_scale * VKQ_parts[l*D + tid];\n        VKQ_denominator += KQ_max_scale * meta[l].y;\n    }\n\n    dst[tid] = VKQ_numerator / VKQ_denominator;\n}\n\n#define INSTANTIATE_FLASH_ATTN_VEC_FOR_TYPES(D, K_type, K_type_name, V_type, V_type_name) \\\n    extern \"C\" {                                                                                  \\\n        __launch_bounds__(128, 1)                                                                 \\\n        __global__ void flash_attn_vec_##D##_1##_##K_type_name##_##V_type_name(            \\\n            const char * __restrict__ Q, \\\n            const char * __restrict__ K, \\\n            const char * __restrict__ V, \\\n            const char * __restrict__ mask, \\\n            const int  * __restrict__ KV_max, \\\n            float      * __restrict__ dst, \\\n            float2     * __restrict__ dst_meta, \\\n            const float scale, \\\n            const int32_t ne03, const int32_t ne02, const int32_t ne01, const int32_t ne00,  \\\n                                const int32_t nb03, const int32_t nb02, const int32_t nb01,  \\\n            const int32_t ne13, const int32_t ne12, const int32_t ne11, const int32_t ne10,  \\\n                                const int32_t nb13, const int32_t nb12, const int32_t nb11,  \\\n                                const int32_t nb23, const int32_t nb22, const int32_t nb21,  \\\n                                const int32_t ne33, const int32_t ne32, const int32_t ne31,  \\\n                                const int32_t nb33, const int32_t nb32, const int32_t nb31){ \\\n            flash_attn_ext_vec<D, 1, K_type, V_type>(                                   \\\n                            Q, K, V, mask, KV_max, dst, dst_meta, scale,                     \\\n                            ne00, ne01, ne02, ne03,                                          \\\n                            nb01, nb02, nb03,                                                \\\n                            ne10, ne11, ne12, ne13,                                          \\\n                            nb11, nb12, nb13,                                                \\\n                            nb21, nb22, nb23,                                                \\\n                            ne31, ne32, ne33,                                                \\\n                            nb31, nb32, nb33);                                               \\\n        }                                                                                    \\\n                                                                                             \\\n        __launch_bounds__(128, 1)                                                            \\\n        __global__ void flash_attn_vec_##D##_2##_##K_type_name##_##V_type_name(            \\\n            const char * __restrict__ Q, \\\n            const char * __restrict__ K, \\\n            const char * __restrict__ V, \\\n            const char * __restrict__ mask, \\\n            const int  * __restrict__ KV_max, \\\n            float      * __restrict__ dst, \\\n            float2     * __restrict__ dst_meta, \\\n            const float scale, \\\n            const int32_t ne03, const int32_t ne02, const int32_t ne01, const int32_t ne00,  \\\n                                const int32_t nb03, const int32_t nb02, const int32_t nb01,  \\\n            const int32_t ne13, const int32_t ne12, const int32_t ne11, const int32_t ne10,  \\\n                                const int32_t nb13, const int32_t nb12, const int32_t nb11,  \\\n                                const int32_t nb23, const int32_t nb22, const int32_t nb21,  \\\n                                const int32_t ne33, const int32_t ne32, const int32_t ne31,  \\\n                                const int32_t nb33, const int32_t nb32, const int32_t nb31){ \\\n            flash_attn_ext_vec<D, 2, K_type, V_type>(                                        \\\n                            Q, K, V, mask, KV_max, dst, dst_meta, scale,                     \\\n                            ne00, ne01, ne02, ne03,                                          \\\n                            nb01, nb02, nb03,                                                \\\n                            ne10, ne11, ne12, ne13,                                          \\\n                            nb11, nb12, nb13,                                                \\\n                            nb21, nb22, nb23,                                                \\\n                            ne31, ne32, ne33,                                                \\\n                            nb31, nb32, nb33);                                               \\\n        }                                                                                    \\\n                                                                                             \\\n        __launch_bounds__(D, 1)                                                              \\\n        __global__ void flash_attn_combine_results_##D(                                      \\\n            const float  * __restrict__ VKQ_parts, const float2 * __restrict__ VKQ_meta,     \\\n            float * __restrict__ dst, const int parallel_blocks){                            \\\n            flash_attn_combine_results<D>(                                                   \\\n                VKQ_parts, VKQ_meta, dst, parallel_blocks);                                  \\\n            }                                                                                \\\n    }\n\n#define INSTANTIATE_FLASH_ATTN_VEC_FOR_D_NCOLS1(D) \\\n    INSTANTIATE_FLASH_ATTN_VEC_FOR_TYPES(D, CUDA_TYPE_F16, f16, CUDA_TYPE_F16, f16) \\\n    //INSTANTIATE_FLASH_ATTN_VEC_FOR_TYPES(D, CUDA_TYPE_Q4_0, q40, CUDA_TYPE_F16, f16) \\\n    //INSTANTIATE_FLASH_ATTN_VEC_FOR_TYPES(D, CUDA_TYPE_F16, f16, CUDA_TYPE_Q4_0, q40) \\\n    //INSTANTIATE_FLASH_ATTN_VEC_FOR_TYPES(D, CUDA_TYPE_Q4_0, q40, CUDA_TYPE_Q4_0, q40) \\\n\n\n#define INSTANTIATE_FLASH_ATTN_VEC() \\\n    INSTANTIATE_FLASH_ATTN_VEC_FOR_D_NCOLS1(64) \\\n    INSTANTIATE_FLASH_ATTN_VEC_FOR_D_NCOLS1(128) \\\n    //INSTANTIATE_FLASH_ATTN_VEC_FOR_D_NCOLS1(256) \\\n\nINSTANTIATE_FLASH_ATTN_VEC()\n\n/*--------------------------------------------------------------------------------------------------------------------------*/\nusing namespace cuda_mma;\n\nstatic __device__ __forceinline__ tile<8, 8, half2> get_transposed(const tile<16, 4, half2> & t) {\n    tile<8, 8, half2> ret;\n    ret.x[0] = cuda_movmatrix(t.x[0]);\n    ret.x[1] = cuda_movmatrix(t.x[1]);\n\n    return ret;\n}\n\ntypedef tile<16,  8, half2> tile_A;\ntypedef tile< 8,  8, half2> tile_B;\ntypedef tile<16,  8, half2> tile_B_16;\ntypedef tile<16,  8, float> tile_C_KQ;\ntypedef tile<16, 16, float> tile_C_KQ_16;\ntypedef tile<16,  4, half2> tile_C_VKQ;\ntypedef tile<16,  8, half2> tile_C_VKQ_16;\n\n// Config options for specific head sizes.\n// Should not affect results, only speed/register pressure/shared memory use.\n//\n// nbatch_fa:      number of KV rows per softmax rescaling of KQ rowsums and VKQ accumulators.\n// nwarps_max:     maximum number of warps per CUDA block, up to 8 warps in total can run per SM (given enough shared memory).\n// Q_in_reg:       whether the Q values should be kept permanently in registers.\n// nstages_target: targeted number of pipeline stages for cp_async (if available), 0 means synchronous data loading.\n// nbatch_K2:      number of K half2 values in direction of D to load in parallel.\n// nbatch_V2:      number of V half2 values in direction of D to load in parallel.\n// nbatch_combine: number of VKQ half2 values in direction of D to combine in parallel.\n\ntemplate <int D>\nstruct fattn_mma_f16_config;\n\ntemplate <>\nstruct fattn_mma_f16_config<64> {\n    static constexpr int  nbatch_fa      = 64;\n    static constexpr int  nwarps_max     = 4;\n    static constexpr bool Q_in_reg       = true;\n    static constexpr int  nstages_target = 2;\n    static constexpr __device__ int get_nbatch_K2_device(int /*ncols*/) {\n        return 32;\n    }\n\n    static constexpr __device__ int get_nbatch_V2_device(int /*ncols*/) {\n        return 32;\n    }\n\n    static constexpr __device__ int get_nbatch_combine_device(int /*ncols*/) {\n        return 32;\n    }\n};\n\ntemplate <>\nstruct fattn_mma_f16_config<80> {\n    static constexpr int  nbatch_fa      = 64;\n    static constexpr int  nwarps_max     = 4;\n    static constexpr bool Q_in_reg       = true;\n    static constexpr int  nstages_target = 2;\n\n\n    static constexpr __device__ int get_nbatch_K2_device(int /*ncols*/) {\n        return 40;\n    }\n\n    static constexpr __device__ int get_nbatch_V2_device(int /*ncols*/) {\n        return 40;\n    }\n\n    static constexpr __device__ int get_nbatch_combine_device(int /*ncols*/) {\n        return 40;\n    }\n};\n\ntemplate <>\nstruct fattn_mma_f16_config<96> {\n    static constexpr int  nbatch_fa      = 64;\n    static constexpr int  nwarps_max     = 4;\n    static constexpr bool Q_in_reg       = true;\n    static constexpr int  nstages_target = 2;\n\n    static __device__ constexpr __device__ int get_nbatch_K2_device(int /*ncols*/) {\n        return 48;\n    }\n\n    static constexpr __device__ int get_nbatch_V2_device(int /*ncols*/) {\n        return 48;\n    }\n\n    static constexpr __device__ int get_nbatch_combine_device(int /*ncols*/) {\n        return 48;\n    }\n};\n\ntemplate <>\nstruct fattn_mma_f16_config<112> {\n    static constexpr int  nbatch_fa      = 64;\n    static constexpr int  nwarps_max     = 4;\n    static constexpr bool Q_in_reg       = true;\n    static constexpr int  nstages_target = 2;\n\n    static constexpr __device__ int get_nbatch_K2_device(int /*ncols*/) {\n        return 56;\n    }\n\n    static constexpr __device__ int get_nbatch_V2_device(int /*ncols*/) {\n        return 56;\n    }\n\n    static constexpr __device__ int get_nbatch_combine_device(int /*ncols*/) {\n        return 56;\n    }\n};\n\ntemplate <>\nstruct fattn_mma_f16_config<128> {\n    static constexpr int  nbatch_fa      = 64;\n    static constexpr int  nwarps_max     = 4;\n    static constexpr bool Q_in_reg       = true;\n    static constexpr int  nstages_target = 2;\n\n    static constexpr __device__ int get_nbatch_K2_device(int /*ncols*/) {\n        return 64;\n    }\n\n    static constexpr __device__ int get_nbatch_V2_device(int /*ncols*/) {\n        return 64;\n    }\n\n    static constexpr __device__ int get_nbatch_combine_device(int /*ncols*/) {\n        return 64;\n    }\n};\n\ntemplate <>\nstruct fattn_mma_f16_config<256> {\n    static constexpr int  nbatch_fa      = 32;\n    static constexpr int  nwarps_max     = 4;\n    static constexpr bool Q_in_reg       = true;\n    static constexpr int  nstages_target = 2;\n\n    static constexpr __device__ int get_nbatch_K2_device(int /*ncols*/) {\n        return 128;\n    }\n\n    static constexpr __device__ int get_nbatch_V2_device(int /*ncols*/) {\n        return 128;\n    }\n\n    static constexpr __device__ int get_nbatch_combine_device(int ncols) {\n#if __CUDA_ARCH__ == cuda_CC_TURING\n        return ncols <= 16 ? 128 : 64;\n#else\n        CUDA_UNUSED(ncols);\n        return 128;\n#endif // __CUDA_ARCH__ == CUDA_CC_TURING\n    }\n};\n\nstatic __device__ __forceinline__ unsigned int cuda_cvta_generic_to_shared(void * generic_ptr) {\n    return __cvta_generic_to_shared(generic_ptr);\n}\n\n// Copies data from global to shared memory, cg == cache global.\n// Both the src and dst pointers must be aligned to 16 bit.\n// Shared memory uses 32 bit addressing, the pointer is passed as unsigned int.\n// Generic pointers can be converted to 32 bit shared memory pointers using __cvta_generic_to_shared.\n// Only the 16 bit copy is exposed because 4 and 8 bit copies did not yield performance improvements.\ntemplate <int preload>\nstatic __device__ __forceinline__ void cp_async_cg_16(const unsigned int dst, const void * src) {\n    static_assert(preload == 0 || preload == 64 || preload == 128 || preload == 256, \"bad preload\");\n#if CUDART_VERSION >= 11040\n    if (preload == 256) {\n        asm volatile(\"cp.async.cg.shared.global.L2::256B [%0], [%1], 16;\"\n            : : \"r\"(dst), \"l\"(src));\n    } else if (preload == 128) {\n        asm volatile(\"cp.async.cg.shared.global.L2::128B [%0], [%1], 16;\"\n            : : \"r\"(dst), \"l\"(src));\n    } else if (preload == 64) {\n        asm volatile(\"cp.async.cg.shared.global.L2::64B [%0], [%1], 16;\"\n            : : \"r\"(dst), \"l\"(src));\n    } else\n#endif // CUDART_VERSION >= 11040\n    {\n        asm volatile(\"cp.async.cg.shared.global [%0], [%1], 16;\"\n            : : \"r\"(dst), \"l\"(src));\n    }\n}\n\n// Makes each thread wait until its asynchronous data copies are done.\n// This does NOT provide any additional synchronization.\n// In particular, when copying data with multiple warps a call to __syncthreads will be needed.\nstatic __device__ __forceinline__ void cp_async_wait_all() {\n    asm volatile(\"cp.async.wait_all;\");\n}\n\n\ntemplate<int stride_tile, int nwarps, int nbatch_fa, int N>\nstatic __device__ __forceinline__\nvoid load_cp_async_once(const half2* __restrict__ KV,\n                        unsigned int tile_KV_32,\n                        int chunks_per_row, int stride_KV) {\n    constexpr int preload       = 64;\n    constexpr int h2_per_chunk  = 16 / sizeof(half2);\n    constexpr int stride_k      = WARP_SIZE >> N;\n    constexpr int stride_i      = WARP_SIZE / stride_k;\n\n    const int k0_start = stride_k == WARP_SIZE ? 0 : chunks_per_row - chunks_per_row % (2*stride_k);\n    const int k0_stop  =                             chunks_per_row - chunks_per_row % (1*stride_k);\n    if (k0_start == k0_stop) return;\n\n    #pragma unroll\n    for (int i0 = 0; i0 < nbatch_fa; i0 += nwarps*stride_i) {\n        const int i = i0 + threadIdx.y*stride_i + (stride_k == WARP_SIZE ? 0 : threadIdx.x / stride_k);\n        if (i0 + nwarps*stride_i > nbatch_fa && i >= nbatch_fa) break;\n\n        #pragma unroll\n        for (int k0 = k0_start; k0 < k0_stop; k0 += stride_k) {\n            const int k = k0 + (stride_k == WARP_SIZE ? threadIdx.x : threadIdx.x % stride_k);\n            cp_async_cg_16<preload>(tile_KV_32 + i*(stride_tile*sizeof(half2)) + k*16,\n                                    KV + i*stride_KV + k*h2_per_chunk);\n        }\n    }\n}\n\ntemplate<int stride_tile, int nwarps, int nbatch_fa, int N, int MAX>\nstatic __device__ __forceinline__\nvoid unroll_cp_async(const half2* __restrict__ KV, unsigned int tile_KV_32,\n                     int chunks_per_row, int stride_KV) {\n    if constexpr (N < MAX) {\n        load_cp_async_once<stride_tile, nwarps, nbatch_fa, N>(KV, tile_KV_32, chunks_per_row, stride_KV);\n        unroll_cp_async<stride_tile, nwarps, nbatch_fa, N+1, MAX>(KV, tile_KV_32, chunks_per_row, stride_KV);\n    }\n}\n\ntemplate<int stride_tile, int nwarps, int nbatch_fa, int N>\nstatic __device__ __forceinline__\nvoid load_sync_once(const half2* __restrict__ KV, half2* __restrict__ tile_KV,\n                    int D2, int stride_KV) {\n    constexpr int stride_k = WARP_SIZE >> N;\n    constexpr int stride_i = WARP_SIZE / stride_k;\n\n    const int k0_start = stride_k == WARP_SIZE ? 0 : D2 - D2 % (2*stride_k);\n    const int k0_stop  =                             D2 - D2 % (1*stride_k);\n    if (k0_start == k0_stop) return;\n\n    #pragma unroll\n    for (int i0 = 0; i0 < nbatch_fa; i0 += nwarps*stride_i) {\n        const int i = i0 + threadIdx.y*stride_i + (stride_k == WARP_SIZE ? 0 : threadIdx.x / stride_k);\n        if (i0 + nwarps*stride_i > nbatch_fa && i >= nbatch_fa) break;\n\n        #pragma unroll\n        for (int k0 = k0_start; k0 < k0_stop; k0 += stride_k) {\n            const int k = k0 + (stride_k == WARP_SIZE ? threadIdx.x : threadIdx.x % stride_k);\n            tile_KV[i*stride_tile + k] = KV[i*stride_KV + k];\n        }\n    }\n}\n\ntemplate<int stride_tile, int nwarps, int nbatch_fa, int N, int MAX>\nstatic __device__ __forceinline__\nvoid unroll_sync(const half2* __restrict__ KV, half2* __restrict__ tile_KV,\n                 int D2, int stride_KV) {\n    if constexpr (N < MAX) {\n        load_sync_once<stride_tile, nwarps, nbatch_fa, N>(KV, tile_KV, D2, stride_KV);\n        unroll_sync<stride_tile, nwarps, nbatch_fa, N+1, MAX>(KV, tile_KV, D2, stride_KV);\n    }\n}\n\ntemplate<int stride_tile, int nwarps, int nbatch_fa, bool use_cp_async>\nstatic __device__ __forceinline__\nvoid flash_attn_ext_f16_load_tile(const half2* __restrict__ KV,\n                                  half2* __restrict__ tile_KV,\n                                  int D2, int stride_KV) {\n    if constexpr (use_cp_async) {\n        const int  chunks_per_row  = D2 / (16 / sizeof(half2));\n        const unsigned tile_KV_32  = cuda_cvta_generic_to_shared(tile_KV);\n        unroll_cp_async<stride_tile, nwarps, nbatch_fa, 0, 5>(KV, tile_KV_32, chunks_per_row, stride_KV);\n    } else {\n        static_assert(nbatch_fa % (4*nwarps) == 0, \"out of bounds\");\n        unroll_sync<stride_tile, nwarps, nbatch_fa, 0, 3>(KV, tile_KV, D2, stride_KV);\n    }\n}\n\ntemplate<int ncols1, int nwarps, int nbatch_fa, bool use_cp_async>\nstatic __device__ __forceinline__ void flash_attn_ext_f16_load_mask(\n        const half2 * const __restrict__ mask_h2, half2 * const __restrict__ tile_mask, const int stride_mask) {\n    static_assert(nbatch_fa == 2*WARP_SIZE || WARP_SIZE % nbatch_fa == 0, \"bad KQ_per_iter\");\n\n    if (use_cp_async) {\n        constexpr int preload = nbatch_fa >= 32 ? nbatch_fa * sizeof(half) : 64;\n        constexpr int cols_per_warp = 8*WARP_SIZE/nbatch_fa;\n        constexpr int stride_j = nwarps * cols_per_warp;\n\n        const unsigned int tile_mask_32 = cuda_cvta_generic_to_shared(tile_mask);\n\n#pragma unroll\n        for (int j0 = 0; j0 < ncols1; j0 += stride_j) {\n            const int j = j0 + threadIdx.y*cols_per_warp +\n                (nbatch_fa == 2*WARP_SIZE ? threadIdx.x / (WARP_SIZE/4) : threadIdx.x / (WARP_SIZE/cols_per_warp));\n\n            if (j0 + stride_j > ncols1 && j >= ncols1) {\n                break;\n            }\n\n            const int i = 4 * (threadIdx.x % (nbatch_fa/8));\n\n            cp_async_cg_16<preload>(tile_mask_32 + j*(nbatch_fa*sizeof(half) + 16) + i*sizeof(half2), mask_h2 + j*stride_mask + i);\n        }\n        return;\n    }\n\n    constexpr int cols_per_warp = 2*WARP_SIZE/nbatch_fa;\n    constexpr int stride_j = nwarps * cols_per_warp;\n#pragma unroll\n    for (int j0 = 0; j0 < ncols1; j0 += stride_j) {\n        const int j = j0 + threadIdx.y*cols_per_warp + (nbatch_fa == 2*WARP_SIZE ? 0 : threadIdx.x / (WARP_SIZE/cols_per_warp));\n\n        if (j0 + stride_j > ncols1 && j >= ncols1) {\n            break;\n        }\n\n        const int i = nbatch_fa == 2*WARP_SIZE ? threadIdx.x : threadIdx.x % (WARP_SIZE/cols_per_warp);\n\n        tile_mask[j*(nbatch_fa/2 + 4) + i] = mask_h2[j*stride_mask + i];\n    }\n}\n\ntemplate<int D, int ncols1, int ncols2, int nwarps, int ntiles,\n    bool needs_fixup, bool is_fixup, bool last_iter>\nstatic __device__ __forceinline__ void flash_attn_ext_f16_iter(\n        const float2 * const __restrict__ Q_f2,\n        const half2  * const __restrict__ K_h2,\n        const half2  * const __restrict__ V_h2,\n        const half2  * const __restrict__ mask_h2,\n        float2       * const __restrict__ dstk,\n        float2       * const __restrict__ dstk_fixup,\n        const float scale,\n        const int ne01,\n        const int ne02,\n        const int stride_K,\n        const int stride_V,\n        const int stride_mask,\n        half2        * const __restrict__ tile_Q,\n        half2        * const __restrict__ tile_K,\n        half2        * const __restrict__ tile_V,\n        half2        * const __restrict__ tile_mask,\n        const tile_B * const __restrict__ Q_B,\n        tile_C_VKQ   * const __restrict__ VKQ_C,\n        float        * const __restrict__ KQ_max,\n        float        * const __restrict__ KQ_rowsum,\n        const int kb0) {\n    typedef fattn_mma_f16_config<D> c;\n\n#ifdef CP_ASYNC_AVAILABLE\n    constexpr int nstages = c::nstages_target;\n#else\n    constexpr int nstages = 0;\n#endif // CP_ASYNC_AVAILABLE\n\n    constexpr int cols_per_warp   = ntiles * tile_B::I;\n    constexpr int cols_per_thread = ntiles == 1 ? 2 : ntiles;\n    constexpr int np              = nwarps * (cols_per_warp/ncols2) / ncols1; // Number of parallel CUDA warps per Q column.\n    constexpr int ncols           = ncols1 * ncols2;\n    constexpr int nbatch_K2       = c::get_nbatch_K2_device(ncols);\n    constexpr int nbatch_V2       = c::get_nbatch_V2_device(ncols);\n\n    constexpr int stride_tile_Q = D/2     + 4;\n    constexpr int stride_tile_K = nbatch_K2 + 4;\n\n    constexpr int stride_tile_V = nbatch_V2 + 4;\n\n    const int k_VKQ_0 = kb0 * c::nbatch_fa;\n    tile_C_KQ KQ_C[c::nbatch_fa/(np*tile_C_KQ::I) * ntiles];\n\n    // Use wide variants of tiles if ntiles >= 2.\n    tile_B_16     * Q_B_16   = (tile_B_16     *) Q_B;\n    tile_C_VKQ_16 * VKQ_C_16 = (tile_C_VKQ_16 *) VKQ_C;\n    tile_C_KQ_16  * KQ_C_16  = (tile_C_KQ_16  *) KQ_C;\n\n    if constexpr (nstages > 1) {\n        static_assert(nbatch_K2 == D/2, \"batching not implemented for multi stage loading\");\n        constexpr bool use_cp_async = true;\n        cp_async_wait_all();\n        __syncthreads();\n        flash_attn_ext_f16_load_tile<stride_tile_V, nwarps, c::nbatch_fa, use_cp_async>\n            (V_h2 + int64_t(k_VKQ_0)*stride_V, tile_V, nbatch_V2, stride_V);\n    } else {\n        constexpr bool use_cp_async = nstages == 1;\n        if (ncols2 > 1 || mask_h2) {\n            flash_attn_ext_f16_load_mask<ncols1, nwarps, c::nbatch_fa, use_cp_async>(mask_h2 + k_VKQ_0/2, tile_mask, stride_mask);\n        }\n    }\n\n#pragma unroll\n    for (int k0_start = 0; k0_start < D/2; k0_start += nbatch_K2) {\n        const int k0_stop = k0_start + nbatch_K2 < D/2 ? k0_start + nbatch_K2 : D/2;\n        const int k0_diff = k0_stop - k0_start;\n\n        if (nstages <= 1) {\n            constexpr bool use_cp_async = nstages == 1;\n            flash_attn_ext_f16_load_tile<stride_tile_K, nwarps, c::nbatch_fa, use_cp_async>\n                (K_h2 + int64_t(k_VKQ_0)*stride_K + k0_start, tile_K, k0_diff, stride_K);\n            if (use_cp_async) {\n                cp_async_wait_all();\n            }\n            __syncthreads();\n        }\n\n        // Calculate tile of KQ:\n        if constexpr (c::Q_in_reg) {\n#pragma unroll\n            for (int i_KQ_00 = 0; i_KQ_00 < c::nbatch_fa; i_KQ_00 += np*tile_A::I) {\n                const int i_KQ_0 = i_KQ_00 + (threadIdx.y % np)*tile_A::I;\n#pragma unroll\n                for (int k_KQ_0 = k0_start; k_KQ_0 < k0_stop; k_KQ_0 += tile_A::J) {\n                    tile_A K_A;\n                    load_ldmatrix(K_A, tile_K + i_KQ_0*stride_tile_K + (k_KQ_0 - k0_start), stride_tile_K);\n                    if (ntiles == 1) {\n                        mma(KQ_C[i_KQ_00/(np*tile_A::I)], K_A, Q_B[k_KQ_0/tile_A::J]);\n                    } else {\n#pragma unroll\n                        for (int t = 0; t < ntiles/2; ++t) {\n                            // Wide version of KQ_C is column-major => swap A and B.\n                            mma(KQ_C_16[i_KQ_00/(np*tile_A::I) * ntiles/2 + t], Q_B_16[k_KQ_0/tile_A::J * ntiles/2 + t], K_A);\n                        }\n                    }\n                }\n            }\n        } else {\n            static_assert(ntiles == 2, \"ntiles != 2 not implemented\");\n#pragma unroll\n            for (int k_KQ_0 = k0_start; k_KQ_0 < k0_stop; k_KQ_0 += tile_A::J) {\n                load_ldmatrix(Q_B_16[0], tile_Q + (threadIdx.y / np)*(tile_B_16::I*stride_tile_Q) + k_KQ_0, stride_tile_Q);\n\n#pragma unroll\n                for (int i_KQ_00 = 0; i_KQ_00 < c::nbatch_fa; i_KQ_00 += np*tile_A::I) {\n                    const int i_KQ_0 = i_KQ_00 + (threadIdx.y % np)*tile_A::I;\n\n                    tile_A K_A;\n                    load_ldmatrix(K_A, tile_K + i_KQ_0*stride_tile_K + (k_KQ_0 - k0_start), stride_tile_K);\n\n                    // Wide version of KQ_C is column-major => swap A and B.\n                    mma(KQ_C_16[i_KQ_00/(np*tile_A::I)], Q_B_16[0], K_A);\n                }\n            }\n        }\n\n        if (nstages <= 1) {\n            __syncthreads(); // Only needed if tile_K == tile_V.\n        }\n    }\n\n    float KQ_max_new[cols_per_thread];\n#pragma unroll\n    for (int col = 0; col < cols_per_thread; ++col) {\n        KQ_max_new[col] = KQ_max[col];\n    }\n    float KQ_rowsum_add[cols_per_thread] = {0.0f};\n\n    if (ntiles == 1) {\n        if (ncols2 > 1 || mask_h2) {\n#pragma unroll\n            for (int i00 = 0; i00 < c::nbatch_fa; i00 += np*tile_C_KQ::I) {\n                const int i0 = i00 + (threadIdx.y % np)*tile_C_KQ::I;\n#pragma unroll\n                for (int l = 0; l < tile_C_KQ::ne; ++l) {\n                    const int i = i0 + tile_C_KQ::get_i(l);\n                    const int j = ((threadIdx.y / np)*tile_C_KQ::J + tile_C_KQ::get_j(l)) / ncols2;\n\n                    KQ_C[i00/(np*tile_C_KQ::I)].x[l] +=\n                        __half2float(((const half *) tile_mask)[j*(c::nbatch_fa + 8) + i]);\n                }\n            }\n        }\n\n        // Calculate softmax for each KQ column using the current max. value.\n        // The divisor is stored in KQ_rowsum and will be applied at the end.\n        static_assert(c::nbatch_fa % (np*tile_C_KQ::I) == 0, \"bad loop size\");\n#pragma unroll\n        for (int k = 0; k < c::nbatch_fa/(np*tile_C_KQ::I); ++k) {\n#pragma unroll\n            for (int l = 0; l < tile_C_KQ::ne; ++l) {\n                KQ_max_new[l % 2] = fmaxf(KQ_max_new[l % 2], KQ_C[k].x[l]);\n            }\n        }\n\n        // Values per KQ column are spread across 8 threads, does not need full warp reduce:\n#pragma unroll\n        for (int col = 0; col < cols_per_thread; ++col) {\n#pragma unroll\n            for (int offset = 16; offset >= 4; offset >>= 1) {\n                KQ_max_new[col] = fmaxf(KQ_max_new[col], __shfl_xor_sync(0xFFFFFFFF, KQ_max_new[col], offset, WARP_SIZE));\n            }\n        }\n\n        static_assert(c::nbatch_fa % (np*tile_C_KQ::I) == 0, \"bad loop size\");\n#pragma unroll\n        for (int k = 0; k < c::nbatch_fa/(np*tile_C_KQ::I); ++k) {\n#pragma unroll\n            for (int l = 0; l < tile_C_KQ::ne; ++l) {\n                KQ_C[k].x[l] = expf(KQ_C[k].x[l] - KQ_max_new[l % 2]);\n\n                KQ_rowsum_add[l % 2] += KQ_C[k].x[l];\n            }\n        }\n    } else { // ntiles > 1\n        if (ncols2 > 1 || mask_h2) {\n#pragma unroll\n            for (int i00 = 0; i00 < c::nbatch_fa; i00 += np*tile_C_KQ_16::J) {\n                const int i0 = i00 + (threadIdx.y % np)*tile_C_KQ_16::J;\n#pragma unroll\n                for (int t = 0; t < ntiles/2; ++t) {\n#pragma unroll\n                    for (int l0 = 0; l0 < tile_C_KQ_16::ne; l0 += 2) {\n                        const int i = (i0 + tile_C_KQ_16::get_j(l0)) / 2;\n                        const int j = ((threadIdx.y / np)*cols_per_warp + t*tile_C_KQ_16::I + tile_C_KQ_16::get_i(l0)) / ncols2;\n\n                        const float2 tmp = __half22float2(tile_mask[j*(c::nbatch_fa/2 + 4) + i]);\n                        const int KQ_index = i00/(np*tile_C_KQ_16::J) * ntiles/2 + t;\n                        KQ_C_16[KQ_index].x[l0 + 0] += tmp.x;\n                        KQ_C_16[KQ_index].x[l0 + 1] += tmp.y;\n                    }\n                }\n            }\n        }\n\n        // Calculate softmax for each KQ column using the current max. value.\n        // The divisor is stored in KQ_rowsum and will be applied at the end.\n        static_assert(c::nbatch_fa % (np*tile_C_KQ::I) == 0, \"bad loop size\");\n#pragma unroll\n        for (int k = 0; k < c::nbatch_fa/(np*tile_C_KQ_16::J); ++k) {\n#pragma unroll\n            for (int t = 0; t < ntiles/2; ++t) {\n#pragma unroll\n                for (int l = 0; l < tile_C_KQ_16::ne; ++l) {\n                    const int KQ_index = 2*t + (l/2) % 2;\n                    KQ_max_new[KQ_index] = fmaxf(KQ_max_new[KQ_index], KQ_C_16[k*ntiles/2 + t].x[l]);\n                }\n            }\n        }\n\n        // Values per KQ column are spread across 4 threads, does not need full warp reduce:\n#pragma unroll\n        for (int col = 0; col < cols_per_thread; ++col) {\n#pragma unroll\n            for (int offset = 2; offset >= 1; offset >>= 1) {\n                KQ_max_new[col] = fmaxf(KQ_max_new[col], __shfl_xor_sync(0xFFFFFFFF, KQ_max_new[col], offset, WARP_SIZE));\n            }\n        }\n\n        static_assert(c::nbatch_fa % (np*tile_C_KQ_16::J) == 0, \"bad loop size\");\n#pragma unroll\n        for (int k = 0; k < c::nbatch_fa/(np*tile_C_KQ_16::J); ++k) {\n#pragma unroll\n            for (int t = 0; t < ntiles/2; ++t) {\n#pragma unroll\n                for (int l = 0; l < tile_C_KQ_16::ne; ++l) {\n                    const int KQ_index = 2*t + (l/2) % 2;\n\n                    KQ_C_16[k*ntiles/2 + t].x[l] = expf(KQ_C_16[k*ntiles/2 + t].x[l] - KQ_max_new[KQ_index]);\n\n                    KQ_rowsum_add[KQ_index] += KQ_C_16[k*ntiles/2 + t].x[l];\n                }\n            }\n        }\n    }\n\n    {\n        float KQ_max_scale[cols_per_thread];\n#pragma unroll\n        for (int col = 0; col < cols_per_thread; ++col) {\n            const float KQ_max_diff = KQ_max[col] - KQ_max_new[col];\n            KQ_max_scale[col] = expf(KQ_max_diff);\n            KQ_max[col] = KQ_max_new[col];\n\n            *((uint32_t *) &KQ_max_scale[col]) *= KQ_max_diff >= SOFTMAX_FTZ_THRESHOLD;\n\n            // Scale previous KQ_rowsum to account for a potential increase in KQ_max:\n            KQ_rowsum[col] = KQ_max_scale[col]*KQ_rowsum[col] + KQ_rowsum_add[col];\n        }\n\n        if (ntiles == 1) {\n            const half2 KQ_max_scale_h2 = make_half2(KQ_max_scale[0], KQ_max_scale[1]);\n#pragma unroll\n            for (int i = 0; i < D/tile_C_VKQ::I; ++i) {\n#pragma unroll\n                for (int l = 0; l < tile_C_VKQ::ne; ++l) {\n                    VKQ_C[i].x[l] *= KQ_max_scale_h2;\n                }\n            }\n        } else {\n#pragma unroll\n            for (int col = 0; col < cols_per_thread; ++col) {\n                const half2 KQ_max_scale_h2 = make_half2(KQ_max_scale[col], KQ_max_scale[col]);\n#pragma unroll\n                for (int i = 0; i < D/tile_C_VKQ_16::J; ++i) {\n#pragma unroll\n                    for (int l0 = 0; l0 < tile_C_VKQ_16::ne; l0 += 2) {\n                        VKQ_C_16[i*ntiles/2 + col/2].x[l0 + col % 2] *= KQ_max_scale_h2;\n                    }\n                }\n            }\n        }\n    }\n\n    // Convert KQ C tiles into B tiles for VKQ calculation:\n    tile_B B[c::nbatch_fa/(np*2*tile_B::J) * ntiles];\n    tile_B_16 * B_16 = (tile_B_16 *) B;\n    static_assert(c::nbatch_fa % (np*2*tile_B::J) == 0, \"bad loop size\");\n    if (ntiles == 1) {\n#pragma unroll\n        for (int k = 0; k < c::nbatch_fa/(np*2*tile_B::J); ++k) {\n            B[k] = get_transposed(get_half2(KQ_C[k]));\n        }\n    } else {\n        for (int k = 0; k < c::nbatch_fa/(np*2*tile_B_16::J); ++k) {\n#pragma unroll\n            for (int t = 0; t < ntiles/2; ++t) {\n                B_16[k*ntiles/2 + t] = get_half2(KQ_C_16[k*ntiles/2 + t]);\n            }\n        }\n    }\n\n    if (nstages > 1) {\n        // Preload K tile for next iteration:\n        constexpr bool use_cp_async = true;\n        cp_async_wait_all();\n        __syncthreads();\n        if (!last_iter) {\n            if (ncols2 > 1 || mask_h2) {\n                flash_attn_ext_f16_load_mask<ncols1, nwarps, c::nbatch_fa, use_cp_async>\n                    (mask_h2 + (k_VKQ_0 + c::nbatch_fa)/2, tile_mask, stride_mask);\n            }\n            flash_attn_ext_f16_load_tile<stride_tile_K, nwarps, c::nbatch_fa, use_cp_async>\n                (K_h2 + int64_t(k_VKQ_0 + c::nbatch_fa)*stride_K, tile_K, nbatch_K2, stride_K);\n        }\n    }\n\n    constexpr int reusable_cutoff = D;\n#pragma unroll\n    for (int i0_stop = D; i0_stop > 0; i0_stop -= 2*nbatch_V2) {\n        const int i0_start = i0_stop - 2*nbatch_V2 > 0 ? i0_stop - 2*nbatch_V2 : 0;\n        const int i0_diff  = i0_stop - i0_start;\n\n        if (nstages <= 1 && i0_start < reusable_cutoff) {\n            constexpr bool use_cp_async = nstages == 1;\n            flash_attn_ext_f16_load_tile<stride_tile_V, nwarps, c::nbatch_fa, use_cp_async>\n                (V_h2 + int64_t(k_VKQ_0)*stride_V + i0_start/2, tile_V, i0_diff/2, stride_V);\n            if (use_cp_async) {\n                cp_async_wait_all();\n            }\n            __syncthreads();\n        }\n        const half2 * tile_V_i = i0_start < reusable_cutoff ? tile_V : tile_V + (i0_start - reusable_cutoff)/2;\n\n        // Calculate VKQ tile:\n#pragma unroll\n        for (int i_VKQ_0 = i0_start; i_VKQ_0 < i0_stop; i_VKQ_0 += tile_C_VKQ::I) {\n            static_assert((c::nbatch_fa/2) % (np*tile_A::J) == 0, \"bad loop size\");\n#pragma unroll\n            for (int k00 = 0; k00 < c::nbatch_fa/2; k00 += np*tile_A::J) {\n                const int k0 = k00 + (threadIdx.y % np)*tile_A::J;\n\n                tile_A A;\n                load_ldmatrix_trans(A, tile_V_i + 2*k0*stride_tile_V + (i_VKQ_0 - i0_start)/2, stride_tile_V);\n                if (ntiles == 1) {\n                    mma(VKQ_C[i_VKQ_0/tile_C_VKQ::I], A, B[k00/(np*tile_A::J)]);\n                } else {\n#pragma unroll\n                    for (int t = 0; t < ntiles/2; ++t) {\n                        // Wide version of VKQ_C is column-major => swap A and B.\n                        mma(VKQ_C_16[i_VKQ_0/tile_C_VKQ::I * ntiles/2 + t], B_16[k00/(np*tile_A::J) * ntiles/2 + t], A);\n                    }\n                }\n            }\n        }\n\n        if (nstages <= 1) {\n            __syncthreads(); // Only needed if tile_K == tile_V.\n        }\n    }\n}\n\ntemplate<int D, int ncols1, int ncols2, int nwarps, int ntiles, bool needs_fixup, bool is_fixup>\nstatic __device__ __forceinline__ void flash_attn_ext_f16_process_tile(\n        const float2 * const __restrict__ Q_f2,\n        const half2  * const __restrict__ K_h2,\n        const half2  * const __restrict__ V_h2,\n        const half2  * const __restrict__ mask_h2,\n        float2       * const __restrict__ dstk,\n        float2       * const __restrict__ dstk_fixup,\n        const float scale,\n        const int ne01,\n        const int ne02,\n        const int stride_Q1,\n        const int stride_Q2,\n        const int stride_K,\n        const int stride_V,\n        const int stride_mask,\n        const int jt,\n        const int kb0_start,\n        const int kb0_stop) {\n    //In this kernel Q, K, V are matrices while i, j, k are matrix indices.\n\n    typedef fattn_mma_f16_config<D> c;\n\n#ifdef CP_ASYNC_AVAILABLE\n    constexpr int nstages = c::nstages_target;\n#else\n    constexpr int nstages = 0;\n#endif // CP_ASYNC_AVAILABLE\n\n    constexpr int ncols           = ncols1 * ncols2;\n    constexpr int cols_per_warp   = ntiles * tile_B::I;\n    constexpr int cols_per_thread = ntiles == 1 ? 2 : ntiles;\n    constexpr int np              = nwarps * (cols_per_warp/ncols2) / ncols1; // Number of parallel CUDA warps per Q column.\n    constexpr int nbatch_K2       = c::get_nbatch_K2_device(ncols);\n    constexpr int nbatch_V2       = c::get_nbatch_V2_device(ncols);\n\n    static_assert(nwarps * (cols_per_warp/ncols2) % ncols1 == 0, \"bad nwarps\");\n\n    constexpr int stride_tile_Q = D/2     + 4;\n    constexpr int stride_tile_K = nbatch_K2 + 4;\n\n    constexpr int stride_tile_V = nbatch_V2 + 4;\n    constexpr int stride_tile_KV_max = stride_tile_K > stride_tile_V ? stride_tile_K : stride_tile_V;\n\n    extern __shared__ half2 tile_Q[];\n    half2 * tile_K    = c::Q_in_reg ? tile_Q                                : tile_Q + ncols        * stride_tile_Q;\n    half2 * tile_V    = nstages > 1 ? tile_K + c::nbatch_fa * stride_tile_K : tile_K;\n    half2 * tile_mask = nstages > 1 ? tile_V + c::nbatch_fa * stride_tile_V : tile_V + c::nbatch_fa * stride_tile_KV_max;\n\n    tile_B       Q_B[(c::Q_in_reg ? D/(2*tile_B::J) : 1) * ntiles];\n    tile_C_VKQ VKQ_C[D/tile_C_VKQ::I  * ntiles];\n\n    tile_B_16     * Q_B_16   = (tile_B_16     *) Q_B;\n    tile_C_VKQ_16 * VKQ_C_16 = (tile_C_VKQ_16 *) VKQ_C;\n\n    float KQ_rowsum[cols_per_thread] = {0.0f};\n    float KQ_max[cols_per_thread];\n#pragma unroll\n    for (int col = 0; col < cols_per_thread; ++col) {\n        KQ_max[col] = -FLT_MAX/2.0f;\n    }\n\n    // Load Q data into tile_Q, either temporarily or permanently.\n    // Q in registers is faster, but register pressure is the biggest bottleneck.\n    // The loading is done with decreasing granularity for D for better memory bandwidth.\n    const half2 scale_h2 = make_half2(scale, scale);\n#pragma unroll\n    for (int stride_k : {WARP_SIZE, WARP_SIZE/2, WARP_SIZE/4}) {\n        const int k0_start  = stride_k == WARP_SIZE ? 0 : D/2 - (D/2) % (2*stride_k);\n        const int k0_stop   =                             D/2 - (D/2) % (1*stride_k);\n        const int stride_jc = WARP_SIZE / stride_k;\n\n        if (k0_start == k0_stop) {\n            continue;\n        }\n\n#pragma unroll\n        for (int jc0 = 0; jc0 < ncols; jc0 += nwarps*stride_jc) {\n            const int jc = jc0 + threadIdx.y*stride_jc + (stride_k == WARP_SIZE ? 0 : threadIdx.x / stride_k);\n\n            if (jc0 + nwarps*stride_jc > ncols && jc >= ncols) {\n                break;\n            }\n\n            const int j = jc / ncols2;\n            const int c = jc % ncols2;\n\n            if (jt*ncols1 + j < ne01) {\n#pragma unroll\n                for (int k0 = k0_start; k0 < k0_stop; k0 += stride_k) {\n                    const int k = k0 + (stride_k == WARP_SIZE ? threadIdx.x : threadIdx.x % stride_k);\n\n                    const float2 tmp = Q_f2[(jt*ncols1 + j)*stride_Q1 + c*stride_Q2 + k];\n                    tile_Q[jc*stride_tile_Q + k] = scale_h2 * make_half2(tmp.x, tmp.y);\n                }\n            } else {\n#pragma unroll\n                for (int k0 = k0_start; k0 < k0_stop; k0 += stride_k) {\n                    const int k = k0 + (stride_k == WARP_SIZE ? threadIdx.x : threadIdx.x % stride_k);\n\n                    tile_Q[jc*stride_tile_Q + k] = make_half2(0.0f, 0.0f);\n                }\n            }\n        }\n    }\n\n    __syncthreads();\n\n    if (c::Q_in_reg) {\n        const int j0 = (threadIdx.y / np) * cols_per_warp;\n\n#pragma unroll\n        for (int k0 = 0; k0 < D/2; k0 += tile_B::J) {\n            if (ntiles == 1) {\n                load_ldmatrix(Q_B[k0/tile_B::J], tile_Q + j0*stride_tile_Q + k0, stride_tile_Q);\n            } else {\n#pragma unroll\n                for (int t = 0; t < ntiles/2; ++t) {\n                    load_ldmatrix(Q_B_16[k0/tile_B_16::J * ntiles/2 + t],\n                        tile_Q + (j0 + t*tile_B_16::I)*stride_tile_Q + k0, stride_tile_Q);\n                }\n            }\n        }\n    }\n\n    __syncthreads();\n\n    // Preload mask and K data for first iteration when using cp_async with multiple stages:\n    if constexpr (nstages > 1) {\n        static_assert(nbatch_K2 == D/2, \"batching not implemented for multi-stage pipeline\");\n        constexpr bool use_cp_async = true;\n        if (ncols2 > 1 || mask_h2) {\n            flash_attn_ext_f16_load_mask<ncols1, nwarps, c::nbatch_fa, use_cp_async>\n                (mask_h2 + kb0_start*c::nbatch_fa/2, tile_mask, stride_mask);\n        }\n        flash_attn_ext_f16_load_tile<stride_tile_K, nwarps, c::nbatch_fa, use_cp_async>\n            (K_h2 + int64_t(kb0_start)*c::nbatch_fa*stride_K, tile_K, nbatch_K2, stride_K);\n    }\n\n    // Iterate over ne11 == previous tokens:\n    int kb0 = kb0_start;\n    for (; kb0 < kb0_stop-1; ++kb0) {\n        constexpr bool last_iter = false;\n        flash_attn_ext_f16_iter<D, ncols1, ncols2, nwarps, ntiles, needs_fixup, is_fixup, last_iter>\n            (Q_f2, K_h2, V_h2, mask_h2, dstk, dstk_fixup, scale,\n             ne01, ne02, stride_K, stride_V, stride_mask, tile_Q, tile_K, tile_V, tile_mask, Q_B, VKQ_C, KQ_max, KQ_rowsum, kb0);\n    }\n    { // kb0_start is always < kb0_stop so the last iter can be executed unconditionally.\n        constexpr bool last_iter = true;\n        flash_attn_ext_f16_iter<D, ncols1, ncols2, nwarps, ntiles, needs_fixup, is_fixup, last_iter>\n            (Q_f2, K_h2, V_h2, mask_h2, dstk, dstk_fixup, scale,\n             ne01, ne02, stride_K, stride_V, stride_mask, tile_Q, tile_K, tile_V, tile_mask, Q_B, VKQ_C, KQ_max, KQ_rowsum, kb0);\n    }\n\n    // With multi-stage loading there is no __syncthreads at the end of the iter,\n    //     there can be a race condition on shared memory access for combining/writing back results.\n    if (nstages > 1 && nwarps*cols_per_warp > c::nbatch_fa) {\n        __syncthreads();\n    }\n\n    // Finally, sum up partial KQ rowsums.\n    // The partial sums are spread across 8/4 threads each, does not need full reduce.\n    {\n        constexpr int offset_first = ntiles == 1 ? 16 : 2;\n        constexpr int offset_last  = ntiles == 1 ?  4 : 1;\n#pragma unroll\n        for (int col = 0; col < cols_per_thread; ++col) {\n#pragma unroll\n            for (int offset = offset_first; offset >= offset_last; offset >>= 1) {\n                KQ_rowsum[col] += __shfl_xor_sync(0xFFFFFFFF, KQ_rowsum[col], offset, WARP_SIZE);\n            }\n        }\n    }\n\n    // Combine VKQ accumulator values if np > 1.\n    // It's also faster to do small writes to shared memory, then large write to VRAM than to do small writes to VRAM.\n    // So also write VKQ accumulators to shared memory in column-major format if np == 1.\n\n    constexpr int nbatch_combine = c::get_nbatch_combine_device(ncols);\n    constexpr int tile_stride    = nbatch_combine + 4;\n    static_assert((D/2) % nbatch_combine == 0, \"bad nbatch_combine\");\n\n    if constexpr (ntiles == 1) {\n        const int jc_cwmo = (threadIdx.x % (2*tile_C_VKQ::J)) / tile_C_VKQ::J; // jc combine write meta offset\n        const int jc_cwm = threadIdx.y*(2*tile_C_VKQ::J) + 2*tile_C_VKQ::get_j(-1) + jc_cwmo; // jc combine write meta\n        const float2 KQ_cmr = make_float2(KQ_max[jc_cwmo], KQ_rowsum[jc_cwmo]); // KQ combine max rowsum\n\n        if (((!needs_fixup && !is_fixup) || np > 1) && threadIdx.x < 2*tile_C_VKQ::J) {\n            // Use the 16 bytes of padding in each row to store the meta data: KQ max, KQ rowsum, KQ max scale.\n            ((float2 *) tile_Q)[jc_cwm*(tile_stride/2) + nbatch_combine/2] = KQ_cmr;\n        }\n\n        __syncthreads();\n\n        if (np == 1) {\n            // No combination is needed, the meta data can be directly written from registers to VRAM.\n            if (needs_fixup && threadIdx.x < tile_B::I) {\n                float2 * dstk_fixup_meta = dstk_fixup + blockIdx.x*ncols;\n                dstk_fixup_meta[jc_cwm] = KQ_cmr;\n            }\n            if (is_fixup && threadIdx.x < tile_B::I) {\n                float2 * dstk_fixup_meta = dstk_fixup + (gridDim.x + blockIdx.x)*ncols;\n                dstk_fixup_meta[jc_cwm] = KQ_cmr;\n            }\n        }\n    } else {\n        static_assert(ntiles == 2 || ntiles == 4, \"bad ntiles\");\n        const int jc_cwm = threadIdx.y*cols_per_warp // jc combine write meta\n            + (ntiles == 4 ? ((threadIdx.x % 4) / 2) * tile_C_VKQ_16::I : 0)\n            + tile_C_VKQ_16::get_i(threadIdx.x % 4);\n        const float2 KQ_cmr = make_float2(KQ_max[threadIdx.x % cols_per_thread], KQ_rowsum[threadIdx.x % cols_per_thread]); // KQ combine max rowsum\n\n        if (((!needs_fixup && !is_fixup) || np > 1) && (ntiles == 4 || threadIdx.x % 4 < cols_per_thread)) {\n            // Use the 16 bytes of padding in each row to store the meta data: KQ max, KQ rowsum, KQ max scale.\n            ((float2 *) tile_Q)[jc_cwm*(tile_stride/2) + nbatch_combine/2] = KQ_cmr;\n        }\n\n        __syncthreads();\n\n        if (np == 1) {\n            // No combination is needed, the meta data can be directly written from registers to VRAM.\n            if (needs_fixup && (ntiles == 4 || threadIdx.x % 4 < ntiles)) {\n                float2 * dstk_fixup_meta = dstk_fixup + blockIdx.x*ncols;\n                dstk_fixup_meta[jc_cwm] = KQ_cmr;\n            }\n            if (is_fixup && (ntiles == 4 || threadIdx.x % 4 < ntiles)) {\n                float2 * dstk_fixup_meta = dstk_fixup + (gridDim.x + blockIdx.x)*ncols;\n                dstk_fixup_meta[jc_cwm] = KQ_cmr;\n            }\n        }\n    }\n\n    static_assert(np == 1 || ntiles == 1 || ntiles == 2, \"bad ntiles\");\n    if (np > 1 && threadIdx.y % np == 0) {\n        // Combine the meta data for parallel warps via shared memory.\n        // Warps with threadIdx.y % np != 0 must NOT return early.\n        // All threads must return simultaneously to avoid race conditions with work on the next tile.\n\n        constexpr int nmeta = np*cols_per_warp >= WARP_SIZE ? np*cols_per_warp/WARP_SIZE : 1;\n\n        const int jc_meta = threadIdx.y*cols_per_warp + (np*cols_per_warp < WARP_SIZE ? threadIdx.x % (np*cols_per_warp) : threadIdx.x);\n        float2 * const meta_ptr = ((float2 *) tile_Q) + jc_meta*(tile_stride/2) + nbatch_combine/2;\n        float2 meta[nmeta];\n#pragma unroll\n        for (int imeta = 0; imeta < nmeta; ++imeta) {\n            meta[imeta] = meta_ptr[imeta * WARP_SIZE * tile_stride/2];\n        }\n\n        float KQ_cmn = meta[0].x; // KQ combine max new, max between all parallel warps.\n#pragma unroll\n        for (int imeta = 1; imeta < nmeta; ++imeta) {\n            KQ_cmn = fmaxf(KQ_cmn, meta[imeta].x);\n        }\n#pragma unroll\n        for (int offset = np*cols_per_warp/2; offset >= cols_per_warp; offset >>= 1) {\n            if (offset < WARP_SIZE) {\n                KQ_cmn = fmaxf(KQ_cmn, __shfl_xor_sync(0xFFFFFFFF, KQ_cmn, offset, WARP_SIZE));\n            }\n        }\n\n        float KQ_cms[nmeta]; // KQ combine max scale per warp.\n#pragma unroll\n        for (int imeta = 0; imeta < nmeta; ++imeta) {\n            KQ_cms[imeta] = expf(meta[imeta].x - KQ_cmn);\n        }\n\n        float KQ_crs = KQ_cms[0]*meta[0].y; // KQ combine rowsum, scaled sum of all parallel warps.\n#pragma unroll\n        for (int imeta = 1; imeta < nmeta; ++imeta) {\n            KQ_crs += KQ_cms[imeta]*meta[imeta].y;\n        }\n#pragma unroll\n        for (int offset = np*cols_per_warp/2; offset >= cols_per_warp; offset >>= 1) {\n            if (offset < WARP_SIZE) {\n                KQ_crs += __shfl_xor_sync(0xFFFFFFFF, KQ_crs, offset, WARP_SIZE);\n            }\n        }\n\n        __syncthreads();\n\n        // Write back combined meta data:\n#pragma unroll\n        for (int imeta = 0; imeta < nmeta; ++imeta) {\n            if (np*cols_per_warp >= WARP_SIZE || threadIdx.x < np*cols_per_warp) {\n                // Combined KQ max scale + rowsum.\n                meta_ptr[imeta * WARP_SIZE * tile_stride/2] = make_float2(KQ_cms[imeta], KQ_crs);\n            }\n        }\n\n        // Combined KQ max + rowsum.\n        static_assert(cols_per_warp <= WARP_SIZE);\n        if (needs_fixup && (cols_per_warp == WARP_SIZE || threadIdx.x < cols_per_warp)) {\n            float2 * dstk_fixup_meta = dstk_fixup + blockIdx.x*ncols;\n            dstk_fixup_meta[(threadIdx.y/np)*cols_per_warp + threadIdx.x] = make_float2(KQ_cmn, KQ_crs);\n        }\n        if (is_fixup && (cols_per_warp == WARP_SIZE || threadIdx.x < cols_per_warp)) {\n            float2 * dstk_fixup_meta = dstk_fixup + (gridDim.x + blockIdx.x)*ncols;\n            dstk_fixup_meta[(threadIdx.y/np)*cols_per_warp + threadIdx.x] = make_float2(KQ_cmn, KQ_crs);\n        }\n    } else if (np > 1) {\n        // Warps with threadIdx.y % np == 0 execute a __syncthreads() in the if branch.\n        // Therefore, all other warps also need to execute a __syncthreads().\n        // Otherwise the points at which warps synchronize with each other would become misaligned.\n        __syncthreads();\n    }\n\n#pragma unroll\n    for (int k00 = 0; k00 < D/2; k00 += nbatch_combine) {\n        if (ntiles == 1) {\n            const int jc_cwd = threadIdx.y*tile_B::I + tile_B::get_i(-1); // jc combine write data\n#pragma unroll\n            for (int k0 = 0; k0 < nbatch_combine; k0 += tile_B::J) {\n                const tile_B B = get_transposed(VKQ_C[(k00 + k0)/tile_B::J]); // Conversion of C to B matrix puts it in column-major format.\n\n#pragma unroll\n                for (int l = 0; l < tile_B::ne; ++l) {\n                    const int k = k0 + tile_B::get_j(l);\n\n                    tile_Q[jc_cwd*tile_stride + k] = B.x[l];\n                }\n            }\n        } else {\n#pragma unroll\n            for (int t = 0; t < ntiles/2; ++t) {\n                const int j0 = threadIdx.y*cols_per_warp + t*tile_C_VKQ_16::I;\n#pragma unroll\n                for (int k0 = 0; k0 < nbatch_combine; k0 += tile_C_VKQ_16::J) {\n#pragma unroll\n                    for (int l = 0; l < tile_C_VKQ_16::ne; ++l) {\n                        const int j = j0 + tile_C_VKQ_16::get_i(l);\n                        const int k = k0 + tile_C_VKQ_16::get_j(l);\n\n                        tile_Q[j*tile_stride + k] = VKQ_C_16[(k00 + k0)/tile_C_VKQ_16::J * ntiles/2 + t].x[l];\n                    }\n                }\n            }\n        }\n\n        __syncthreads();\n\n        if (np == 1 || threadIdx.y % np == 0) {\n            // The first 2*2*gridDim.x*ncols floats in dstk_fixup are for storing max. values and row sums.\n            // The values after that are for the partial results of the individual blocks.\n            float2 * dstk_fixup_data = dstk_fixup + gridDim.x*(2*ncols) + blockIdx.x*(ncols*(D/2));\n\n#pragma unroll\n            for (int stride_k : {WARP_SIZE, WARP_SIZE/2, WARP_SIZE/4}) {\n                const int k0_start  = stride_k == WARP_SIZE ? 0 : nbatch_combine - nbatch_combine % (2*stride_k);\n                const int k0_stop   =                             nbatch_combine - nbatch_combine % (1*stride_k);\n                const int stride_jc = WARP_SIZE / stride_k;\n\n                if (k0_start == k0_stop) {\n                    continue;\n                }\n\n#pragma unroll\n                for (int jc0_dst = 0; jc0_dst < ncols; jc0_dst += (nwarps/np)*stride_jc) {\n                    const int jc_dst = jc0_dst + (threadIdx.y/np)*stride_jc + (stride_k == WARP_SIZE ? 0 : threadIdx.x / stride_k);\n\n                    if (jc0_dst + (nwarps/np)*stride_jc > ncols && jc_dst >= ncols) {\n                        break;\n                    }\n\n                    const int jc_tile_K = (jc_dst/cols_per_warp)*(np*cols_per_warp) + jc_dst % cols_per_warp;\n\n                    const int j_dst = jc_dst / ncols2;\n                    const int c_dst = jc_dst % ncols2;\n\n                    if (!is_fixup && jt*ncols1 + j_dst >= ne01) {\n                        continue;\n                    }\n\n                    const float * meta_j = (const float *) tile_Q + jc_tile_K*tile_stride + nbatch_combine;\n#pragma unroll\n                    for (int k0 = k0_start; k0 < k0_stop; k0 += stride_k) {\n                        const int k = k0 + (stride_k == WARP_SIZE ? threadIdx.x : threadIdx.x % stride_k);\n\n                        float2 dstk_val = make_float2(0.0f, 0.0f);\n#pragma unroll\n                        for (int ip = 0; ip < np; ++ip) {\n                            const float KQ_crs = np == 1 ? 1.0f : meta_j[ip*cols_per_warp * tile_stride + 0];\n                            const float2 dstk_val_add = __half22float2(tile_Q[(jc_tile_K + ip*cols_per_warp) * tile_stride + k]);\n                            dstk_val.x += dstk_val_add.x*KQ_crs;\n                            dstk_val.y += dstk_val_add.y*KQ_crs;\n                        }\n\n                        if (!needs_fixup && !is_fixup) {\n                            const float KQ_rowsum_j = meta_j[1];\n                            dstk_val.x /= KQ_rowsum_j;\n                            dstk_val.y /= KQ_rowsum_j;\n                        }\n\n                        if (is_fixup) {\n                            dstk_fixup_data[jc_dst*(D/2) + k00 + k] = dstk_val;\n                        } else {\n                            dstk[((c_dst * ne01) + (jt*ncols1 + j_dst))*(D/2) + k00 + k] = dstk_val;\n                        }\n                    }\n                }\n            }\n        }\n        if (np > 1) {\n            __syncthreads();\n        }\n    }\n}\n\n\ntemplate<int D, int ncols1, int ncols2, int nwarps, int ntiles>\n__launch_bounds__(nwarps*WARP_SIZE, 1)\nstatic __global__ void flash_attn_ext_f16(\n        const char * __restrict__ Q,\n        const char * __restrict__ K,\n        const char * __restrict__ V,\n        const char * __restrict__ mask,\n        const int  * __restrict__ KV_max,\n        float      * __restrict__ dst,\n        float2     * __restrict__ dst_meta,\n        const float scale,\n        const int32_t ne00, const int32_t ne01, const int32_t ne02, const int32_t ne03,\n                            const int32_t nb01, const int32_t nb02, const int32_t nb03,\n        const int32_t ne10, const int32_t ne11, const int32_t ne12, const int32_t ne13,\n                            const int32_t nb11, const int32_t nb12, const int32_t nb13,\n                            const int32_t nb21, const int32_t nb22, const int32_t nb23,\n                            const int32_t ne31, const int32_t ne32, const int32_t ne33,\n                            const int32_t nb31, const int32_t nb32, const int32_t nb33) {\n    typedef fattn_mma_f16_config<D> c;\n\n    const int gqa_ratio = ne02 / ne12; // With grouped query attention there are > 1 Q matrices per K, V matrix.\n\n    const int stride_Q1   = nb01 / sizeof(float2);\n    const int stride_Q2   = nb02 / sizeof(float2);\n    const int stride_K    = nb11 / sizeof(half2);\n    const int stride_mask = nb31 / sizeof(half2);\n\n    const int stride_V = nb21 / sizeof(half2);\n\n    const int iter_k = ne11 / FATTN_KQ_STRIDE;\n    const int iter_j = (ne01 + (ncols1 - 1)) / ncols1;\n\n    constexpr int kb_niter = FATTN_KQ_STRIDE / c::nbatch_fa; // Number of kernel iterations per assigned KQ slice.\n\n    // kbc == k block continuous, current index in continuous ijk space.\n    int       kbc      = (blockIdx.x + 0)*(iter_k*iter_j*(ne02/ncols2)*ne03) / gridDim.x;\n    const int kbc_stop = (blockIdx.x + 1)*(iter_k*iter_j*(ne02/ncols2)*ne03) / gridDim.x;\n\n    // If the seams of 2 CUDA blocks fall within an output tile their results need to be combined.\n    // For this we need to track both the block that starts the tile (needs_fixup) and the block that finishes the tile (is_fixup).\n    // In the most general case >2 seams can fall into the same tile.\n\n    // kb0 == k start index when in the output tile.\n    int kb0_start = kbc % iter_k;\n    int kb0_stop  = min(iter_k, kb0_start + kbc_stop - kbc);\n\n    while (kbc < kbc_stop && kb0_stop == iter_k) {\n        const int sequence = kbc / (iter_k*iter_j*(ne02/ncols2));\n        const int zt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence) / (iter_k*iter_j); // head in units of ncols2\n        const int jt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence - iter_k*iter_j*zt) / iter_k; // j index of current tile.\n\n        const int head0 = zt * ncols2;\n\n        const float2 * Q_f2    = (const float2 *) (Q + nb03*sequence + nb02* head0);\n        const half2  * K_h2    = (const half2  *) (K + nb13*sequence + nb12*(head0 / gqa_ratio));\n        const half2  * mask_h2 = ncols2 == 1 && !mask ? nullptr :\n            (const half2  *) (mask + nb33*(sequence % ne33) + nb31*jt*ncols1);\n        float2       * dstk    = ((float2 *) dst) + ((sequence*ne02 + head0) * ne01) * (D/2);\n\n        const half2 * V_h2 = (const half2 *) (V + nb23*sequence + nb22*(head0 / gqa_ratio));\n\n        const int kb0_start_kernel = kb0_start * kb_niter;\n        int       kb0_stop_kernel  = kb0_stop  * kb_niter;\n\n        if (KV_max) {\n            kb0_stop_kernel = min(kb0_stop_kernel, KV_max[sequence*iter_j + jt] / c::nbatch_fa);\n        }\n\n        constexpr bool is_fixup = false; // All but (potentially) the last iterations write their data to dst rather than the fixup buffer.\n        if (kb0_start == 0) {\n            constexpr bool needs_fixup = false; // CUDA block is working on an entire tile.\n            flash_attn_ext_f16_process_tile<D, ncols1, ncols2, nwarps, ntiles, needs_fixup, is_fixup>\n                (Q_f2, K_h2, V_h2, mask_h2, dstk, dst_meta, scale,\n                 ne01, ne02, stride_Q1, stride_Q2, stride_K, stride_V, stride_mask, jt, kb0_start_kernel, kb0_stop_kernel);\n        } else {\n            constexpr bool needs_fixup = true; // CUDA block is working on the beginning of a tile.\n            flash_attn_ext_f16_process_tile<D, ncols1, ncols2, nwarps, ntiles, needs_fixup, is_fixup>\n                (Q_f2, K_h2, V_h2, mask_h2, dstk, dst_meta, scale,\n                 ne01, ne02, stride_Q1, stride_Q2, stride_K, stride_V, stride_mask, jt, kb0_start_kernel, kb0_stop_kernel);\n        }\n\n        kbc += iter_k;\n        kbc -= kbc % iter_k;\n\n        kb0_start = 0;\n        kb0_stop  = min(iter_k, kbc_stop - kbc);\n    }\n\n    if (kbc >= kbc_stop) {\n        return;\n    }\n\n    const int sequence = kbc / (iter_k*iter_j*(ne02/ncols2));\n    const int zt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence) / (iter_k*iter_j); // head in units of ncols2\n    const int jt = (kbc - iter_k*iter_j*(ne02/ncols2)*sequence - iter_k*iter_j*zt) / iter_k; // j index of current tile.\n\n    const int head0 = zt * ncols2;\n\n    const float2 * Q_f2    = (const float2 *) (Q + nb03*sequence + nb02* head0);\n    const half2  * K_h2    = (const half2  *) (K + nb13*sequence + nb12*(head0 / gqa_ratio));\n    const half2  * mask_h2 = ncols2 == 1 && !mask ? nullptr :\n        (const half2  *) (mask + nb33*(sequence % ne33) + nb31*jt*ncols1);\n    float2       * dstk    = ((float2 *) dst) + ((sequence*ne02 + head0) * ne01) * (D/2);\n\n    const half2 * V_h2 = (const half2 *) (V + nb23*sequence + nb22*(head0 / gqa_ratio));\n\n    const int kb0_start_kernel = kb0_start * kb_niter;\n    int       kb0_stop_kernel  = kb0_stop  * kb_niter;\n\n    if (KV_max) {\n        kb0_stop_kernel = min(kb0_stop_kernel, KV_max[sequence*iter_j + jt] / c::nbatch_fa);\n    }\n\n    constexpr bool is_fixup = true; // Last index writes its data to fixup buffer to avoid data races with other blocks.\n    constexpr bool needs_fixup = false;\n    flash_attn_ext_f16_process_tile<D, ncols1, ncols2, nwarps, ntiles, needs_fixup, is_fixup>\n        (Q_f2, K_h2, V_h2, mask_h2, dstk, dst_meta, scale,\n         ne01, ne02, stride_Q1, stride_Q2, stride_K, stride_V, stride_mask, jt, kb0_start_kernel, kb0_stop_kernel);\n}\n\ntemplate<int D, int ncols1, int ncols2> // D == head size\nstatic __device__ void flash_attn_stream_k_fixup(\n        float * __restrict__ dst, const float2 * __restrict__ dst_fixup, const int32_t ne01, const int32_t ne02, const int32_t ne03, const int32_t ne11) {\n    constexpr int ncols = ncols1*ncols2;\n\n    const int bidx0 = blockIdx.x;\n    const int j     = blockIdx.y;\n    const int c     = blockIdx.z;\n    const int jc    = j*ncols2 + c;\n    const int tid   = threadIdx.x;\n\n    const float * dst_fixup_data = ((const float *) dst_fixup) + gridDim.x*(2*2*ncols);\n\n    const int iter_k = ne11 / FATTN_KQ_STRIDE;\n    const int iter_j = (ne01 + (ncols1 - 1)) / ncols1;\n\n    const int kbc0      = (bidx0 + 0)*(iter_k*iter_j*(ne02/ncols2)*ne03) / gridDim.x;\n    const int kbc0_stop = (bidx0 + 1)*(iter_k*iter_j*(ne02/ncols2)*ne03) / gridDim.x;\n\n    const bool did_not_have_any_data   = kbc0 == kbc0_stop;\n    const bool wrote_beginning_of_tile = kbc0 % iter_k == 0;\n    const bool did_not_write_last      = kbc0/iter_k == kbc0_stop/iter_k && kbc0_stop % iter_k != 0;\n    if (did_not_have_any_data || wrote_beginning_of_tile || did_not_write_last) {\n        return;\n    }\n\n    const int sequence = kbc0 / (iter_k*iter_j*(ne02/ncols2));\n    const int head = (kbc0 - iter_k*iter_j*(ne02/ncols2)*sequence) / (iter_k*iter_j);\n    const int jt = (kbc0 - iter_k*iter_j*(ne02/ncols2)*sequence - iter_k*iter_j*head) / iter_k; // j index of current tile.\n\n    if (jt*ncols1 + j >= ne01) {\n        return;\n    }\n\n    dst += sequence*ne02*ne01*D\n        + (head*ncols2 + c)*ne01*D\n        + (jt*ncols1 + j)*D\n        + tid;\n    // Load the partial result that needs a fixup:\n    float dst_val = 0.0f;\n    float max_val = 0.0f;\n    float rowsum  = 0.0f;\n    {\n        dst_val = *dst;\n\n        const float2 tmp = dst_fixup[bidx0*ncols + jc];\n        max_val = tmp.x;\n        rowsum  = tmp.y;\n    }\n\n    // Iterate over previous blocks and compute the combined results.\n    // All CUDA blocks that get here must have a previous block that needs a fixup.\n    int bidx = bidx0 - 1;\n    int kbc_stop = kbc0;\n    while(true) {\n        const int kbc = bidx*(iter_k*iter_j*(ne02/ncols2)*ne03) / gridDim.x;\n        if (kbc == kbc_stop) { // Did not have any data.\n            bidx--;\n            kbc_stop = kbc;\n            continue;\n        }\n\n        const float dst_add = dst_fixup_data[bidx*ncols*D + jc*D + tid];\n\n        const float2 tmp = dst_fixup[(gridDim.x + bidx)*ncols + jc];\n\n        // Scale the current and new value accumulators depending on the max. values.\n        const float max_val_new = fmaxf(max_val, tmp.x);\n\n        const float diff_val = max_val - max_val_new;\n        const float diff_add = tmp.x   - max_val_new;\n\n        const float scale_val = diff_val >= SOFTMAX_FTZ_THRESHOLD ? expf(diff_val) : 0.0f;\n        const float scale_add = diff_add >= SOFTMAX_FTZ_THRESHOLD ? expf(diff_add) : 0.0f;\n\n        dst_val = scale_val*dst_val + scale_add*dst_add;\n        rowsum  = scale_val*rowsum  + scale_add*tmp.y;\n\n        max_val = max_val_new;\n\n        // If this block started in a previous tile we are done and don't need to combine additional partial results.\n        if (kbc % iter_k == 0 || kbc/iter_k < kbc0/iter_k) {\n            break;\n        }\n        bidx--;\n        kbc_stop = kbc;\n    }\n\n    // Write back final result:\n    *dst = dst_val / rowsum;\n}\n\n#define INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, ncols, ncols2) \\\n    extern \"C\" {                                                                                                   \\\n        __global__ void flash_attn_ext_f16_##D##_##ncols##_##ncols2(                                               \\\n        const char * __restrict__ Q,                                                                               \\\n        const char * __restrict__ K,                                                                               \\\n        const char * __restrict__ V,                                                                               \\\n        const char * __restrict__ mask,                                                                            \\\n        const int  * __restrict__ KV_max,                                                                          \\\n        float      * __restrict__ dst,                                                                             \\\n        float2     * __restrict__ dst_meta,                                                                        \\\n        const float scale,                                                                                         \\\n        const int32_t ne03, const int32_t ne02, const int32_t ne01, const int32_t ne00,                            \\\n                            const int32_t nb03, const int32_t nb02, const int32_t nb01,                            \\\n        const int32_t ne13, const int32_t ne12, const int32_t ne11, const int32_t ne10,                            \\\n                            const int32_t nb13, const int32_t nb12, const int32_t nb11,                            \\\n                            const int32_t nb23, const int32_t nb22, const int32_t nb21,                            \\\n                            const int32_t ne33, const int32_t ne32, const int32_t ne31,                            \\\n                            const int32_t nb33, const int32_t nb32, const int32_t nb31)    { \\\n            typedef fattn_mma_f16_config<D> c; \\\n \\\n            constexpr int ncols1         = ncols / ncols2; \\\n            constexpr int ntiles        = ncols <= 8 ? 1 : 2; \\\n            constexpr int cols_per_warp = ntiles * tile_B::I; \\\n            constexpr int nwarps_max_x  = ncols / cols_per_warp; \\\n            constexpr int nwarps_max_y  = c::nbatch_fa / tile_A::I; \\\n            constexpr int nwarps        = nwarps_max_x*nwarps_max_y <= c::nwarps_max ? nwarps_max_x*nwarps_max_y : c::nwarps_max; \\\n            flash_attn_ext_f16<D, ncols1, ncols2, nwarps, ntiles>(                                   \\\n                            Q, K, V, mask, KV_max, dst, dst_meta, scale,                     \\\n                            ne00, ne01, ne02, ne03,                                          \\\n                            nb01, nb02, nb03,                                                \\\n                            ne10, ne11, ne12, ne13,                                          \\\n                            nb11, nb12, nb13,                                                \\\n                            nb21, nb22, nb23,                                                \\\n                            ne31, ne32, ne33,                                                \\\n                            nb31, nb32, nb33);                                               \\\n        }                                                                                    \\\n                                                                                             \\\n        __launch_bounds__(D, 1)                                                              \\\n        __global__ void flash_attn_stream_k_fixup_##D##_##ncols##_##ncols2(                  \\\n            float * __restrict__ dst, const float2 * __restrict__ dst_fixup,                 \\\n            const int32_t ne03, const int32_t ne02, const int32_t ne01, const int32_t ne11){ \\\n            constexpr int ncols1         = ncols / ncols2;                                   \\\n            flash_attn_stream_k_fixup<D, ncols1, ncols2>(                                    \\\n                dst, dst_fixup, ne01, ne02, ne03, ne11);                                     \\\n            }                                                                                \\\n    }\n\n#define INSTANTIATE_FLASH_ATTN_FIXUP_FOR_NCOLS(D, ncols, ncols2)                      \\\nextern \"C\" {                                                                            \\\n                                                                             \\\n    }\n\n#define INSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX_FOR_NCOLS1(ncols1)              \\\nextern \"C\" {                                                                  \\\n    __launch_bounds__(FATTN_KQ_STRIDE/2, 1)                                   \\\n    __global__ void flash_attn_mask_to_KV_max_##ncols1(                       \\\n        const half2 * __restrict__ mask, int * __restrict__ KV_max,           \\\n        const int ne30, const int s31, const int s33) {                       \\\n            flash_attn_mask_to_KV_max<ncols1>(mask, KV_max, ne30, s31, s33);  \\\n        }                                                                     \\\n}\n\n#define INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS(D, ncols) \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, ncols , 1)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, ncols , 2)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, ncols , 4)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, ncols , 8)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, ncols , 16)  \\\n\n#define INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_D(D)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, 8 , 1)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, 8 , 2)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, 8 , 4)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS2(D, 8 , 8)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS(D, 16)     \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS(D, 32)     \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_NCOLS(D, 64)     \\\n\n#define INSTANTIATE_FLASH_ATTN_MMA_F16()      \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_D(64)  \\\n    INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_D(128) \\\n    //INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_D(80)  \\\n    //INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_D(96)  \\\n    //INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_D(112) \\\n    //INSTANTIATE_FLASH_ATTN_MMA_F16_FOR_D(256) \\\n\nINSTANTIATE_FLASH_ATTN_MMA_F16()\n\n/*----------------------------------------------------------------------------------------------------------------------*/\ntemplate <int ncols1>\nstatic __device__ void flash_attn_mask_to_KV_max(\n        const half2 * __restrict__ mask, int * __restrict__ KV_max, const int32_t ne30, const int32_t s31, const int32_t s33) {\n    const int ne31     = gridDim.x;\n    const int tid      = threadIdx.x;\n    const int sequence = blockIdx.y;\n    const int jt       = blockIdx.x;\n\n    // For batched mask support: mask += sequence*s33 + jt*ncols1*s31;\n    mask += jt*ncols1*s31;\n\n    __shared__ int buf_iw[WARP_SIZE];\n    if (tid < WARP_SIZE) {\n        buf_iw[tid] = 1;\n    }\n    __syncthreads();\n\n    int KV_max_sj = (ne30 - 1) * FATTN_KQ_STRIDE;\n    for (; KV_max_sj >= 0; KV_max_sj -= FATTN_KQ_STRIDE) {\n        int all_inf = 1;\n\n#pragma unroll\n        for (int j = 0; j < ncols1; ++j) {\n            const float2 tmp = __half22float2(mask[j*s31 + KV_max_sj/2 + tid]);\n            all_inf = all_inf && int(isinf(tmp.x)) && int(isinf(tmp.y));\n        }\n\n        all_inf = warp_reduce_all(all_inf);\n        if (tid % WARP_SIZE == 0) {\n            buf_iw[tid / WARP_SIZE] = all_inf;\n        }\n        __syncthreads();\n        all_inf = buf_iw[tid % WARP_SIZE];\n        __syncthreads();\n        all_inf = warp_reduce_all(all_inf);\n\n        if (!all_inf) {\n            break;\n        }\n    }\n\n    // If the break in the loop was not triggered, KV_max_sj is now -FATTN_KQ_STRIDE.\n    // If the break was triggered it's the lower edge of the tile with the first non-masked values.\n    // In either case, walk back the decrementation by FATTN_KQ_STRIDE.\n    KV_max_sj += FATTN_KQ_STRIDE;\n\n    if (threadIdx.x != 0) {\n        return;\n    }\n\n    KV_max[sequence*ne31 + jt] = KV_max_sj;\n}\n\n#define INSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX()          \\\n    INSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX_FOR_NCOLS1(1)  \\\n    INSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX_FOR_NCOLS1(2)  \\\n    INSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX_FOR_NCOLS1(4)  \\\n    INSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX_FOR_NCOLS1(8)  \\\n    INSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX_FOR_NCOLS1(16) \\\n    INSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX_FOR_NCOLS1(32) \\\n    INSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX_FOR_NCOLS1(64) \\\n\nINSTANTIATE_FLASH_ATTN_MASK_TO_KV_MAX()"
  },
  {
    "path": "cuda/src/kernels/cu/mm_mv.cu",
    "content": "\n#include <cuda_runtime.h>\n#include \"common.cuh\"\n\ntemplate <typename T, int ncols_dst, int block_size>\nstatic __device__ void\nmul_mat_vec(const T *__restrict__ x, const T *__restrict__ y,\n            T *__restrict__ dst, const int32_t ncols2, const int32_t nchannels_y,\n            const int32_t stride_row, const int32_t stride_col_y2,\n            const int32_t stride_col_dst, const int32_t channel_ratio,\n            const int32_t stride_channel_x, const int32_t stride_channel_y,\n            const int32_t stride_channel_dst) {\n  const int row = blockIdx.x;\n  const int channel_dst = blockIdx.y;\n  const int channel_x = channel_dst / channel_ratio;\n  const int channel_y = channel_dst;\n  const int tid = threadIdx.x;\n\n  x += channel_x * stride_channel_x + row * stride_row;\n  y += channel_y * stride_channel_y;\n  dst += channel_dst * stride_channel_dst;\n\n  extern __shared__ char data_mmv[];\n  float *buf_iw = (float *)data_mmv;\n\n  if (block_size > WARP_SIZE) {\n    if (tid < WARP_SIZE) {\n      buf_iw[tid] = 0.0f;\n    }\n    __syncthreads();\n  }\n\n  float sumf[ncols_dst] = {0.0f};\n\n  if constexpr (cuda::std::is_same_v<T, float>) {\n    const float2 *x2 = (const float2 *)x;\n    const float2 *y2 = (const float2 *)y;\n    for (int col2 = tid; col2 < ncols2; col2 += block_size) {\n      const float2 tmpx = x2[col2];\n\n#pragma unroll\n      for (int j = 0; j < ncols_dst; ++j) {\n        const float2 tmpy = y2[j * stride_col_y2 + col2];\n        sumf[j] += tmpx.x * tmpy.x;\n        sumf[j] += tmpx.y * tmpy.y;\n      }\n    }\n  } else if constexpr (cuda::std::is_same_v<T, half>) {\n    const half2 *x2 = (const half2 *)x;\n    const half2 *y2 = (const half2 *)y;\n    half2 sumh2[ncols_dst] = {{0.0f, 0.0f}};\n\n    for (int col2 = tid; col2 < ncols2; col2 += block_size) {\n      const half2 tmpx = x2[col2];\n\n#pragma unroll\n      for (int j = 0; j < ncols_dst; ++j) {\n        const half2 tmpy = y2[j * stride_col_y2 + col2];\n        sumh2[j] += tmpx * make_half2(tmpy.x, tmpy.y);\n      }\n    }\n\n#pragma unroll\n    for (int j = 0; j < ncols_dst; ++j) {\n      sumf[j] = __low2float(sumh2[j]) + __high2float(sumh2[j]);\n    }\n  } else {\n    static_assert(cuda::std::is_same_v<T, void>, \"unsupported type\");\n  }\n\n#pragma unroll\n  for (int j = 0; j < ncols_dst; ++j) {\n    sumf[j] = warp_reduce_sum<WARP_SIZE>(sumf[j]);\n\n    if (block_size > WARP_SIZE) {\n      buf_iw[tid / WARP_SIZE] = sumf[j];\n      __syncthreads();\n      if (tid < WARP_SIZE) {\n        sumf[j] = buf_iw[tid];\n        sumf[j] = warp_reduce_sum<WARP_SIZE>(sumf[j]);\n      }\n      if (j < ncols_dst) {\n        __syncthreads();\n      }\n    }\n  }\n\n  if (tid >= ncols_dst) {\n    return;\n  }\n\n  dst[tid * stride_col_dst + row] = sumf[tid];\n}\n\n#define INSTANTIATE_MAT_VEC(type_name, T, ncols_dst, block_size)               \\\n  extern \"C\" __global__ void                                                   \\\n      ggml_matvec_##type_name##_ncols_##ncols_dst##_bs_##block_size(           \\\n          const T *__restrict__ x, const T *__restrict__ y,                    \\\n          T *__restrict__ dst, const int32_t ncols2, const int32_t nchannels_y,        \\\n          const int32_t stride_row, const int32_t stride_col_y2,                       \\\n          const int32_t stride_col_dst, const int32_t channel_ratio,                   \\\n          const int32_t stride_channel_x, const int32_t stride_channel_y,              \\\n          const int32_t stride_channel_dst) {                                      \\\n    mul_mat_vec<T, ncols_dst, block_size>(                                     \\\n        x, y, dst, ncols2, nchannels_y, stride_row, stride_col_y2,             \\\n        stride_col_dst, channel_ratio, stride_channel_x, stride_channel_y,     \\\n        stride_channel_dst);                                                   \\\n  }\n\n#define INSTANTIATE_MAT_VEC_FOR_BS(name, T, blocksize)                         \\\n  INSTANTIATE_MAT_VEC(name, T, 1, blocksize)                                   \\\n  INSTANTIATE_MAT_VEC(name, T, 2, blocksize)                                   \\\n  INSTANTIATE_MAT_VEC(name, T, 3, blocksize)                                   \\\n  INSTANTIATE_MAT_VEC(name, T, 4, blocksize)                                   \\\n  INSTANTIATE_MAT_VEC(name, T, 5, blocksize)                                   \\\n  INSTANTIATE_MAT_VEC(name, T, 6, blocksize)                                   \\\n  INSTANTIATE_MAT_VEC(name, T, 7, blocksize)                                   \\\n  INSTANTIATE_MAT_VEC(name, T, 8, blocksize)\n\n#define INSTANTIATE_MAT_VEC_FOR_T(name, T)                                     \\\n  INSTANTIATE_MAT_VEC_FOR_BS(name, T, 32)                                      \\\n  INSTANTIATE_MAT_VEC_FOR_BS(name, T, 64)                                      \\\n  INSTANTIATE_MAT_VEC_FOR_BS(name, T, 96)                                      \\\n  INSTANTIATE_MAT_VEC_FOR_BS(name, T, 128)                                     \\\n  INSTANTIATE_MAT_VEC_FOR_BS(name, T, 160)                                     \\\n  INSTANTIATE_MAT_VEC_FOR_BS(name, T, 192)                                     \\\n  INSTANTIATE_MAT_VEC_FOR_BS(name, T, 224)                                     \\\n  INSTANTIATE_MAT_VEC_FOR_BS(name, T, 256)\n\nINSTANTIATE_MAT_VEC_FOR_T(f32, float)\nINSTANTIATE_MAT_VEC_FOR_T(f16, half)\n"
  },
  {
    "path": "cuda/src/kernels/cu/mm_mv_q.cu",
    "content": "#include \"common.cuh\"\n\n// Check CC Version\n#define CUDA_CC_TURING 750\n#if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < CUDA_CC_TURING)\n#error \"Requires GPU with compute capability 7.5 or higher\"\n#endif\n\n#define VDR_Q4_0_Q8_1_MMVQ 2\n#define VDR_Q4_0_Q8_1_MMQ 4\n\n#define N_WARPS 8\n\n#define MMQ_Y 128\n\n#define MMQ_ITER_K 256\n#define MMQ_TILE_Y_K (WARP_SIZE + WARP_SIZE / QI8_1)\n\n#define PAD(x, n) (((x) + (n) - 1) & ~((n) - 1))\n#define MMQ_MMA_TILE_X_K_Q8_0 (2 * WARP_SIZE + 2 * WARP_SIZE / QI8_0 + 4)\n\n\nstatic __device__ __forceinline__ int get_int_b2(const void *x,\n                                                 const int &i32) {\n  const uint16_t *x16 = (const uint16_t *)x; // assume at least 2 byte alignment\n\n  int x32 = x16[2 * i32 + 0] << 0;\n  x32 |= x16[2 * i32 + 1] << 16;\n\n  return x32;\n}\n\nstatic __device__ __forceinline__ int get_int_b4(const void *x,\n                                                 const int &i32) {\n  return ((const int *)x)[i32]; // assume at least 4 byte alignment\n}\n\nusing namespace cuda_mma;\n\ntypedef void (*load_tiles_mmq_t)(const char *__restrict__ x, int *x_tile,\n                                 const int kbx0, const int i_max,\n                                 const int stride);\ntypedef void (*vec_dot_mmq_t)(const int *__restrict__ x,\n                              const int *__restrict__ y,\n                              float *__restrict__ sum, const int k00);\ntypedef void (*mmq_write_back_t)(const float *__restrict__ sum,\n                                 const int32_t *__restrict__ get_rows_to_sorted,\n                                 float *__restrict__ dst, const int stride,\n                                 const int i_max, const int j_max);\n\nstatic constexpr __device__ int mmq_get_granularity_device(const int mmq_x) {\n  return mmq_x >= 48 ? 16 : 8;\n}\n\ntemplate <int mmq_y, int nwarps, bool need_check>\nstatic __device__ __forceinline__ void\nload_tiles_q4_0(const char *__restrict__ x, int *__restrict__ x_tile,\n                const int kbx0, const int i_max, const int stride) {\n\n  int *x_qs = (int *)x_tile;\n  float *x_df = (float *)(x_qs + 2 * WARP_SIZE);\n\n  const int kbx = threadIdx.x / QI4_0;\n  const int kqsx = threadIdx.x % QI4_0;\n\n  _Pragma(\"unroll\") for (int i0 = 0; i0 < mmq_y; i0 += N_WARPS) {\n    int i = i0 + threadIdx.y;\n\n    if (need_check) {\n      i = min(i, i_max);\n    }\n    const block_q4_0 *bxi = (const block_q4_0 *)x + kbx0 + i * stride + kbx;\n    const int qs0 = get_int_b2(bxi->qs, kqsx);\n\n    x_qs[i * MMQ_MMA_TILE_X_K_Q8_0 + kbx * (2 * QI4_0) + kqsx + 0] =\n        __vsubss4((qs0 >> 0) & 0x0F0F0F0F, 0x08080808);\n    x_qs[i * MMQ_MMA_TILE_X_K_Q8_0 + kbx * (2 * QI4_0) + kqsx + QI4_0] =\n        __vsubss4((qs0 >> 4) & 0x0F0F0F0F, 0x08080808);\n  }\n\n  const int blocks_per_tile_x_row = WARP_SIZE / QI4_0;\n  const int kbxd = threadIdx.x % blocks_per_tile_x_row;\n\n  _Pragma(\"unroll\") for (int i0 = 0; i0 < MMQ_Y; i0 += N_WARPS * QI4_0) {\n    int i = i0 + threadIdx.y * QI4_0 + threadIdx.x / blocks_per_tile_x_row;\n\n    if (need_check) {\n      i = min(i, i_max);\n    }\n\n    const block_q4_0 *bxi = (const block_q4_0 *)x + kbx0 + i * stride + kbxd;\n\n    x_df[i * MMQ_MMA_TILE_X_K_Q8_0 + kbxd] = bxi->d;\n  }\n}\n\ntemplate <int mmq_x, int mmq_y, int nwarps>\nstatic __device__ __forceinline__ void\nvec_dot_q8_0_q8_1_mma(const int *__restrict__ x, const int *__restrict__ y,\n                      float *__restrict__ sum, const int k00) {\n\n  typedef tile<16, 8, int> tile_A;\n  typedef tile<8, 8, int> tile_B;\n  typedef tile<16, 8, int> tile_C;\n\n  constexpr int granularity = mmq_get_granularity_device(mmq_x);\n  constexpr int rows_per_warp = 2 * granularity;\n  constexpr int ntx =\n      rows_per_warp / tile_C::I; // Number of x minitiles per warp.\n\n  y += (threadIdx.y % ntx) * (tile_B::I * MMQ_TILE_Y_K);\n\n  const int *x_qs = (const int *)x;\n  const float *x_df = (const float *)x_qs + 2 * WARP_SIZE;\n  const int *y_qs = (const int *)y + 4;\n  const half2 *y_ds = (const half2 *)y;\n\n  tile_A A[ntx][WARP_SIZE / QI8_0];\n  float dA[ntx][tile_C::ne / 2][WARP_SIZE / QI8_0];\n\n  const int i0 = (threadIdx.y / ntx) * rows_per_warp;\n\n  _Pragma(\"unroll\") for (int n = 0; n < ntx; ++n) {\n    _Pragma(\"unroll\") for (int k01 = 0; k01 < WARP_SIZE; k01 += QI8_0) {\n      const int k0 = k00 + k01;\n\n      load_ldmatrix(A[n][k01 / QI8_0],\n                    x_qs + (i0 + n * tile_A::I) * MMQ_MMA_TILE_X_K_Q8_0 + k0,\n                    MMQ_MMA_TILE_X_K_Q8_0);\n    }\n\n    _Pragma(\"unroll\") for (int l = 0; l < tile_C::ne / 2; ++l) {\n      const int i = i0 + n * tile_A::I + tile_C::get_i(2 * l);\n\n      _Pragma(\"unroll\") for (int k01 = 0; k01 < WARP_SIZE; k01 += QI8_0) {\n        const int k0 = k00 + k01;\n\n        dA[n][l][k01 / QI8_0] = x_df[i * MMQ_MMA_TILE_X_K_Q8_0 + k0 / QI8_0];\n      }\n    }\n  }\n\n  _Pragma(\"unroll\") for (int j0 = 0; j0 < mmq_x; j0 += ntx * tile_C::J) {\n    _Pragma(\"unroll\") for (int k01 = 0; k01 < WARP_SIZE; k01 += QI8_0) {\n      tile_B B;\n      float dB[tile_C::ne / 2];\n\n      load_generic(B, y_qs + j0 * MMQ_TILE_Y_K + k01,\n                   MMQ_TILE_Y_K); // faster than load_ldmatrix\n\n      _Pragma(\"unroll\") for (int l = 0; l < tile_C::ne / 2; ++l) {\n        const int j = j0 + tile_C::get_j(l);\n\n        dB[l] = __low2float(y_ds[j * MMQ_TILE_Y_K + k01 / QI8_1]);\n      }\n\n      _Pragma(\"unroll\") for (int n = 0; n < ntx; ++n) {\n        tile_C C;\n        mma(C, A[n][k01 / QI8_0], B);\n\n        _Pragma(\"unroll\") for (int l = 0; l < tile_C::ne; ++l) {\n          sum[(j0 / tile_C::J + n) * tile_C::ne + l] +=\n              C.x[l] * dA[n][l / 2][k01 / QI8_0] * dB[l % 2];\n        }\n      }\n    }\n  }\n}\n\ntemplate <int mmq_x, int mmq_y, int nwarps, bool need_check>\nstatic __device__ __forceinline__ void\nmmq_write_back_mma(const float *__restrict__ sum,\n                   const int *__restrict__ ids_dst, float *__restrict__ dst,\n                   const int stride, const int i_max, const int j_max) {\n  typedef tile<16, 8, int> tile_C;\n\n  constexpr int granularity = mmq_get_granularity_device(mmq_x);\n  constexpr int rows_per_warp = 2 * granularity;\n  constexpr int ntx =\n      rows_per_warp / tile_C::I; // Number of x minitiles per warp.\n\n  const int i0 = (threadIdx.y / ntx) * (ntx * tile_C::I);\n  static_assert(nwarps * tile_C::I == mmq_y, \"nwarps*tile_C::I != mmq_y\");\n\n#pragma unroll\n  for (int j0 = 0; j0 < mmq_x; j0 += ntx * tile_C::J) {\n#pragma unroll\n    for (int n = 0; n < ntx; ++n) {\n#pragma unroll\n      for (int l = 0; l < tile_C::ne; ++l) {\n        const int j = j0 + (threadIdx.y % ntx) * tile_C::J + tile_C::get_j(l);\n\n        if (j > j_max) {\n          continue;\n        }\n\n        const int i = i0 + n * tile_C::I + tile_C::get_i(l);\n\n        if (need_check && i > i_max) {\n          continue;\n        }\n\n        dst[ids_dst[j] * stride + i] =\n            sum[(j0 / tile_C::J + n) * tile_C::ne + l];\n      }\n    }\n  }\n}\n\ntemplate <int mmq_x, int mmq_y, int nwarps, bool need_check>\nstruct mmq_type_traits {\n  static constexpr int vdr = VDR_Q4_0_Q8_1_MMQ;\n  static constexpr load_tiles_mmq_t load_tiles =\n      load_tiles_q4_0<mmq_y, nwarps, need_check>;\n  static constexpr vec_dot_mmq_t vec_dot_mma =\n      vec_dot_q8_0_q8_1_mma<mmq_x, mmq_y, nwarps>;\n};\n\ntemplate <int mmq_x, int nwarps, bool need_check, bool fixup>\nstatic __device__ __forceinline__ void mul_mat_q_process_tile(\n    const char *__restrict__ x, const int offset_x, const int *__restrict__ y,\n    const int *__restrict__ ids_dst, float *__restrict__ dst,\n    float *__restrict__ tmp_fixup, const int stride_row_x, const int ncols_y,\n    const int stride_col_dst, const int tile_x_max_i, const int tile_y_max_j,\n    const int kb0_start, const int kb0_stop) {\n\n  constexpr int qk = QK4_0;\n  constexpr load_tiles_mmq_t load_tiles =\n      mmq_type_traits<mmq_x, MMQ_Y, N_WARPS, need_check>::load_tiles;\n\n  extern __shared__ int data_mul_mat_q[];\n  int *tile_y = data_mul_mat_q + mmq_x;\n  int *tile_x = tile_y + PAD(mmq_x * (WARP_SIZE + WARP_SIZE / QI8_1),\n                             N_WARPS * WARP_SIZE);\n\n  constexpr vec_dot_mmq_t vec_dot =\n      mmq_type_traits<mmq_x, MMQ_Y, N_WARPS, need_check>::vec_dot_mma;\n  constexpr mmq_write_back_t write_back =\n      mmq_write_back_mma<mmq_x, MMQ_Y, N_WARPS, need_check>;\n\n  constexpr int blocks_per_iter = MMQ_ITER_K / qk;\n\n  float sum[mmq_x * MMQ_Y / (N_WARPS * WARP_SIZE)] = {0.0f};\n\n  for (int kb0 = kb0_start; kb0 < kb0_stop; kb0 += blocks_per_iter) {\n    load_tiles(x, tile_x, offset_x + kb0, tile_x_max_i, stride_row_x);\n\n    {\n      const int *by0 = y + ncols_y * (kb0 * (qk * sizeof(block_q8_1_mmq) /\n                                             (4 * QK8_1 * sizeof(int))) +\n                                      0 * sizeof(block_q8_1_mmq) / sizeof(int));\n      _Pragma(\"unroll\") for (int l0 = 0; l0 < mmq_x * MMQ_TILE_Y_K;\n                             l0 += N_WARPS * WARP_SIZE) {\n        int l = l0 + threadIdx.y * WARP_SIZE + threadIdx.x;\n\n        tile_y[l] = by0[l];\n      }\n    }\n\n    __syncthreads();\n\n    vec_dot(tile_x, tile_y, sum, 0);\n\n    __syncthreads();\n\n    {\n      const int *by0 = y + ncols_y * (kb0 * (qk * sizeof(block_q8_1_mmq) /\n                                             (4 * QK8_1 * sizeof(int))) +\n                                      1 * sizeof(block_q8_1_mmq) / sizeof(int));\n      _Pragma(\"unroll\") for (int l0 = 0; l0 < mmq_x * MMQ_TILE_Y_K;\n                             l0 += N_WARPS * WARP_SIZE) {\n        int l = l0 + threadIdx.y * WARP_SIZE + threadIdx.x;\n\n        tile_y[l] = by0[l];\n      }\n    }\n\n    __syncthreads();\n\n    vec_dot(tile_x, tile_y, sum, WARP_SIZE);\n\n    __syncthreads();\n  }\n\n  if (fixup) {\n    write_back(sum, ids_dst, tmp_fixup + blockIdx.x * (mmq_x * MMQ_Y), MMQ_Y,\n               MMQ_Y, mmq_x);\n  } else {\n    write_back(sum, ids_dst, dst, stride_col_dst, tile_x_max_i, tile_y_max_j);\n  }\n}\n\n// The mul_mat_q kernel implements \"stream-k\" work partitioning as described in\n// https://arxiv.org/abs/2301.03598\n\ntemplate <int mmq_x, int nwarps, bool need_check>\nstatic __device__ __forceinline__ void\nmul_mat_q(const char *__restrict__ x, const int *__restrict__ y,\n          float *__restrict__ dst, float *__restrict__ tmp_fixup,\n          const int32_t ncols_x, const int32_t nrows_x, const int32_t ncols_dst,\n          const int32_t stride_row_x, const int32_t ncols_y, const int32_t stride_col_dst,\n          const int32_t channel_ratio, const int32_t nchannels_y,\n          const int32_t stride_channel_x, const int32_t stride_channel_y,\n          const int32_t stride_channel_dst) {\n  constexpr int qk = QK4_0;\n\n  const int ntx = (ncols_dst + mmq_x - 1) / mmq_x; // Number of tiles x\n  const int nty = (nrows_x + MMQ_Y - 1) / MMQ_Y;   // Number of tiles y\n\n  // Initialize the ids for writing back data with just the index.\n  // For regular matrix multiplications this is never changed.\n  // For MoE the correct indices are loaded from ids_dst.\n  extern __shared__ int\n      ids_dst_shared[]; // Stored at beginning of shared memory.\n  _Pragma(\"unroll\") for (int j0 = 0; j0 < mmq_x; j0 += nwarps * WARP_SIZE) {\n    const int j = j0 + threadIdx.y * WARP_SIZE + threadIdx.x;\n\n    if (j0 + nwarps * WARP_SIZE > mmq_x && j >= mmq_x) {\n      break;\n    }\n\n    ids_dst_shared[j] = j;\n  }\n  __syncthreads();\n\n  const int64_t blocks_per_ne00 = ncols_x / qk;\n  constexpr int blocks_per_iter = MMQ_ITER_K / qk;\n\n  // kbc == k block continuous, current index in continuous ijk space.\n  int64_t kbc = (int64_t)blockIdx.x * nchannels_y * ntx * nty *\n                blocks_per_ne00 / gridDim.x;\n  int64_t kbc_stop = (int64_t)(blockIdx.x + 1) * nchannels_y * ntx * nty *\n                     blocks_per_ne00 / gridDim.x;\n\n  kbc -= (kbc % blocks_per_ne00) % blocks_per_iter;\n  kbc_stop -= (kbc_stop % blocks_per_ne00) % blocks_per_iter;\n\n  // kb0 == k index when doing the matrix multiplication for an output tile.\n  int kb0_start = kbc % blocks_per_ne00;\n  int kb0_stop = min(blocks_per_ne00, kb0_start + kbc_stop - kbc);\n\n  while (kbc < kbc_stop && kb0_stop == blocks_per_ne00) {\n    int tmp = kbc;\n    const int it = tmp / (nchannels_y * ntx * blocks_per_ne00);\n    tmp -= it * (nchannels_y * ntx * blocks_per_ne00);\n    const int zt = tmp / (ntx * blocks_per_ne00);\n    tmp -= zt * (ntx * blocks_per_ne00);\n    const int jt = tmp / blocks_per_ne00;\n\n    // Defaults for regular matrix multiplication:\n    int offset_y = zt * stride_channel_y;\n    int offset_dst = zt * stride_channel_dst + jt * mmq_x * stride_col_dst;\n\n    offset_y += jt * mmq_x * (sizeof(block_q8_1_mmq) / sizeof(int));\n    offset_dst += it * MMQ_Y;\n\n    const int tile_x_max_i = nrows_x - it * MMQ_Y - 1;\n    const int tile_y_max_j = ncols_dst - jt * mmq_x - 1;\n\n    const int offset_x =\n        (zt / channel_ratio) * stride_channel_x + it * MMQ_Y * stride_row_x;\n\n    constexpr bool fixup =\n        false; // All but (potentially) the last iterations write their data to\n               // dst rather than the fixup buffer.\n    mul_mat_q_process_tile<mmq_x, nwarps, need_check, fixup>(\n        x, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup,\n        stride_row_x, ncols_y, stride_col_dst, tile_x_max_i, tile_y_max_j,\n        kb0_start, kb0_stop);\n\n    kbc += blocks_per_ne00;\n    kbc -= kbc % blocks_per_ne00;\n\n    kb0_start = 0;\n    kb0_stop = min(blocks_per_ne00, kbc_stop - kbc);\n  }\n\n  if (kbc >= kbc_stop) {\n    return;\n  }\n\n  int tmp = kbc;\n  const int it = tmp / (nchannels_y * ntx * blocks_per_ne00);\n  tmp -= it * (nchannels_y * ntx * blocks_per_ne00);\n  const int zt = tmp / (ntx * blocks_per_ne00);\n  tmp -= zt * (ntx * blocks_per_ne00);\n  const int jt = tmp / blocks_per_ne00;\n\n  // Defaults for regular matrix multiplication:\n  int offset_y = zt * stride_channel_y;\n  int offset_dst = zt * stride_channel_dst + jt * mmq_x * stride_col_dst;\n\n  offset_y += jt * mmq_x * (sizeof(block_q8_1_mmq) / sizeof(int));\n  offset_dst += it * MMQ_Y;\n\n  const int tile_x_max_i = nrows_x - it * MMQ_Y - 1;\n  const int tile_y_max_j = ncols_dst - jt * mmq_x - 1;\n\n  const int offset_x =\n      (zt / channel_ratio) * stride_channel_x + it * MMQ_Y * stride_row_x;\n\n  constexpr bool fixup = true; // Last index writes its data to fixup buffer to\n                               // avoid data races with other blocks.\n  mul_mat_q_process_tile<mmq_x, nwarps, need_check, fixup>(\n      x, offset_x, y + offset_y, ids_dst_shared, dst + offset_dst, tmp_fixup,\n      stride_row_x, ncols_y, stride_col_dst, tile_x_max_i, tile_y_max_j,\n      kb0_start, kb0_stop);\n}\n\ntemplate <int mmq_x, int nwarps, bool need_check>\nstatic __device__ __forceinline__ void\nmul_mat_q_stream_k_fixup(float *__restrict__ dst,\n                         const float *__restrict__ tmp_last_tile,\n                         const int32_t ncols_x, const int32_t nrows_x,\n                         const int32_t ncols_dst, const int32_t stride_col_dst,\n                         const int32_t nchannels_y, const int32_t stride_channel_dst) {\n  constexpr int qk = QK4_0;\n  constexpr int blocks_per_iter = MMQ_ITER_K / qk;\n  const int64_t blocks_per_ne00 = ncols_x / qk;\n\n  float sum[mmq_x * MMQ_Y / (nwarps * WARP_SIZE)] = {0.0f};\n\n  const int ntx = (ncols_dst + mmq_x - 1) / mmq_x;\n  const int nty = (nrows_x + MMQ_Y - 1) / MMQ_Y;\n\n  const int bidx0 = blockIdx.x;\n\n  // kbc == k block continuous, current index in continuous ijk space.\n  int64_t kbc0 =\n      (int64_t)bidx0 * nchannels_y * ntx * nty * blocks_per_ne00 / gridDim.x;\n  int64_t kbc0_stop = (int64_t)(bidx0 + 1) * nchannels_y * ntx * nty *\n                      blocks_per_ne00 / gridDim.x;\n\n  kbc0 -= (kbc0 % blocks_per_ne00) % blocks_per_iter;\n  kbc0_stop -= (kbc0_stop % blocks_per_ne00) % blocks_per_iter;\n\n  const bool did_not_have_any_data = kbc0 == kbc0_stop;\n  const bool wrote_beginning_of_tile = kbc0 % blocks_per_ne00 == 0;\n  const bool did_not_write_last =\n      kbc0 / blocks_per_ne00 == kbc0_stop / blocks_per_ne00 &&\n      kbc0_stop % blocks_per_ne00 != 0;\n  if (did_not_have_any_data || wrote_beginning_of_tile || did_not_write_last) {\n    return;\n  }\n\n  bool any_fixup = false;\n\n  // Iterate over previous blocks and sum up partial sums written to fixup\n  // buffer. All CUDA blocks that get here must have a previous block that needs\n  // a fixup.\n  int64_t bidx = bidx0 - 1;\n  int64_t kbc_stop = kbc0;\n  while (true) {\n    int64_t kbc = bidx * nchannels_y * ntx * nty * blocks_per_ne00 / gridDim.x;\n    kbc -= (kbc % blocks_per_ne00) % blocks_per_iter;\n\n    if (kbc == kbc_stop) { // Did not have any data.\n      bidx--;\n      kbc_stop = kbc;\n      continue;\n    }\n\n    any_fixup = true;\n\n    _Pragma(\"unroll\") for (int j0 = 0; j0 < mmq_x; j0 += nwarps) {\n      const int j = j0 + threadIdx.y;\n\n      _Pragma(\"unroll\") for (int i0 = 0; i0 < MMQ_Y; i0 += WARP_SIZE) {\n        const int i = i0 + threadIdx.x;\n\n        sum[(j0 / nwarps) * (MMQ_Y / WARP_SIZE) + i0 / WARP_SIZE] +=\n            tmp_last_tile[bidx * (mmq_x * MMQ_Y) + j * MMQ_Y + i];\n      }\n    }\n\n    // If this block started in a previous tile we are done and don't need to\n    // combine additional partial results.\n    if (kbc % blocks_per_ne00 == 0 ||\n        kbc / blocks_per_ne00 < kbc0 / blocks_per_ne00) {\n      break;\n    }\n    bidx--;\n    kbc_stop = kbc;\n  }\n\n  if (!any_fixup) {\n    return;\n  }\n\n  int tmp = kbc0;\n  const int it = tmp / (nchannels_y * ntx * blocks_per_ne00);\n  tmp -= it * (nchannels_y * ntx * blocks_per_ne00);\n  const int zt = tmp / (ntx * blocks_per_ne00);\n  tmp -= zt * (ntx * blocks_per_ne00);\n  const int jt = tmp / blocks_per_ne00;\n\n  const int offset_dst =\n      zt * stride_channel_dst + jt * mmq_x * stride_col_dst + it * MMQ_Y;\n  dst += offset_dst;\n\n  const int i_max = nrows_x - it * MMQ_Y - 1;\n  const int j_max = ncols_dst - jt * mmq_x - 1;\n\n  _Pragma(\"unroll\") for (int j0 = 0; j0 < mmq_x; j0 += nwarps) {\n    const int j = j0 + threadIdx.y;\n\n    if (j > j_max) {\n      return;\n    }\n\n    _Pragma(\"unroll\") for (int i0 = 0; i0 < MMQ_Y; i0 += WARP_SIZE) {\n      const int i = i0 + threadIdx.x;\n\n      if (need_check && i > i_max) {\n        continue;\n      }\n\n      dst[j * stride_col_dst + i] +=\n          sum[(j0 / nwarps) * (MMQ_Y / WARP_SIZE) + i0 / WARP_SIZE];\n    }\n  }\n}\n\n#define INSTANTIATE_MMQ_KERNEL(mmq_x, nwarps, need_check)                      \\\n  extern \"C\" {                                                                 \\\n  __launch_bounds__(WARP_SIZE *nwarps, 1) __global__ void mul_mat_q40##_       \\\n      ##mmq_x##_##nwarps##_                                                    \\\n      ##need_check(const char *__restrict__ x, const int *__restrict__ y,      \\\n                   float *__restrict__ dst, float *__restrict__ tmp_fixup,     \\\n                   const int32_t ncols_x, const int32_t nrows_x, const int32_t ncols_dst,  \\\n                   const int32_t stride_row_x, const int32_t ncols_y,                  \\\n                   const int32_t stride_col_dst, const int32_t channel_ratio,          \\\n                   const int32_t nchannels_y, const int32_t stride_channel_x,          \\\n                   const int32_t stride_channel_y, const int32_t stride_channel_dst) { \\\n    mul_mat_q<mmq_x, nwarps, need_check>(                                      \\\n        x, y, dst, tmp_fixup, ncols_x, nrows_x, ncols_dst, stride_row_x,       \\\n        ncols_y, stride_col_dst, channel_ratio, nchannels_y, stride_channel_x, \\\n        stride_channel_y, stride_channel_dst);                                 \\\n  }                                                                            \\\n                                                                               \\\n  __global__ void                                                              \\\n      mul_mat_q40_stream_k_fixup##_##mmq_x##_##nwarps##_##need_check(          \\\n          float *__restrict__ dst, const float *__restrict__ tmp_last_tile,    \\\n          const int32_t ncols_x, const int32_t nrows_x, const int32_t ncols_dst,           \\\n          const int32_t stride_col_dst, const int32_t nchannels_y,                     \\\n          const int32_t stride_channel_dst) {                                      \\\n    mul_mat_q_stream_k_fixup<mmq_x, nwarps, need_check>(                       \\\n        dst, tmp_last_tile, ncols_x, nrows_x, ncols_dst, stride_col_dst,       \\\n        nchannels_y, stride_channel_dst);                                      \\\n  }                                                                            \\\n  }\n\n#define INSTANTIATE_MMQ_KERNEL_FOR_T(n_warps, needs_check)                     \\\n  INSTANTIATE_MMQ_KERNEL(8, n_warps, needs_check)                              \\\n  INSTANTIATE_MMQ_KERNEL(16, n_warps, needs_check)                             \\\n  INSTANTIATE_MMQ_KERNEL(24, n_warps, needs_check)                             \\\n  INSTANTIATE_MMQ_KERNEL(32, n_warps, needs_check)                             \\\n  INSTANTIATE_MMQ_KERNEL(40, n_warps, needs_check)                             \\\n  INSTANTIATE_MMQ_KERNEL(48, n_warps, needs_check)                             \\\n  INSTANTIATE_MMQ_KERNEL(64, n_warps, needs_check)                             \\\n  INSTANTIATE_MMQ_KERNEL(80, n_warps, needs_check)                             \\\n  INSTANTIATE_MMQ_KERNEL(96, n_warps, needs_check)                             \\\n  INSTANTIATE_MMQ_KERNEL(112, n_warps, needs_check)                            \\\n  INSTANTIATE_MMQ_KERNEL(128, n_warps, needs_check)\n\nINSTANTIATE_MMQ_KERNEL_FOR_T(N_WARPS, true)\nINSTANTIATE_MMQ_KERNEL_FOR_T(N_WARPS, false)\n\nstatic constexpr __host__ __device__ int calc_nwarps(int ncols_dst) {\n  switch (ncols_dst) {\n  case 1:\n  case 2:\n  case 3:\n  case 4:\n    return 4;\n  case 5:\n  case 6:\n  case 7:\n  case 8:\n    return 2;\n  default:\n    return 1;\n  }\n}\n\ntemplate <int vdr>\nstatic __device__ __forceinline__ float\nvec_dot_q4_0_q8_1_impl(const int *v, const int *u, const float &d4,\n                       const half2 &ds8) {\n\n  int sumi = 0;\n\n#pragma unroll\n  for (int i = 0; i < vdr; ++i) {\n    const int vi0 = (v[i] >> 0) & 0x0F0F0F0F;\n    const int vi1 = (v[i] >> 4) & 0x0F0F0F0F;\n\n    // SIMD dot product of quantized values\n    sumi = __dp4a(vi0, u[2 * i + 0], sumi);\n    sumi = __dp4a(vi1, u[2 * i + 1], sumi);\n  }\n\n  const float2 ds8f = __half22float2(ds8);\n\n  // second part effectively subtracts 8 from each quant value\n  return d4 * (sumi * ds8f.x - (8 * vdr / QI4_0) * ds8f.y);\n}\n\nstatic __device__ __forceinline__ float\nvec_dot_q4_0_q8_1(const void *__restrict__ vbq,\n                  const block_q8_1 *__restrict__ bq8_1, const int &kbx,\n                  const int &iqs) {\n\n  const block_q4_0 *bq4_0 = (const block_q4_0 *)vbq + kbx;\n\n  int v[VDR_Q4_0_Q8_1_MMVQ];\n  int u[2 * VDR_Q4_0_Q8_1_MMVQ];\n\n#pragma unroll\n  for (int i = 0; i < VDR_Q4_0_Q8_1_MMVQ; ++i) {\n    v[i] = get_int_b2(bq4_0->qs, iqs + i);\n    u[2 * i + 0] = get_int_b4(bq8_1->qs, iqs + i);\n    u[2 * i + 1] = get_int_b4(bq8_1->qs, iqs + i + QI4_0);\n  }\n\n  return vec_dot_q4_0_q8_1_impl<VDR_Q4_0_Q8_1_MMVQ>(v, u, bq4_0->d, bq8_1->ds);\n}\n\ntemplate <int ncols_dst>\nstatic __device__ void\nmul_mat_vec_q(const void *__restrict__ vx, const void *__restrict__ vy,\n              float *__restrict__ dst, const int ncols_x, const int nchannels_y,\n              const int stride_row_x, const int stride_col_y,\n              const int stride_col_dst, const int channel_ratio,\n              const int stride_channel_x, const int stride_channel_y,\n              const int stride_channel_dst) {\n\n  constexpr int qk = QK4_0;\n  constexpr int qi = QI4_0;\n  constexpr int vdr = VDR_Q4_0_Q8_1_MMVQ;\n  constexpr int nwarps = calc_nwarps(ncols_dst);\n  constexpr int rows_per_cuda_block = ncols_dst == 1 ? 1 : 2;\n\n  const int tid = WARP_SIZE * threadIdx.y + threadIdx.x;\n  const int row0 = rows_per_cuda_block * blockIdx.x;\n  const int blocks_per_row_x = ncols_x / qk;\n  constexpr int blocks_per_iter = vdr * nwarps * WARP_SIZE / qi;\n\n  const int channel_dst = blockIdx.y;\n  const int channel_x = channel_dst / channel_ratio;\n  const int channel_y = channel_dst;\n\n  // partial sum for each thread\n  float tmp[ncols_dst][rows_per_cuda_block] = {{0.0f}};\n\n  const block_q8_1 *y = ((const block_q8_1 *)vy) + channel_y * stride_channel_y;\n  const int kbx_offset = channel_x * stride_channel_x;\n\n  for (int kbx = tid / (qi / vdr); kbx < blocks_per_row_x;\n       kbx += blocks_per_iter) {\n    const int kby = kbx * (qk / QK8_1); // y block index that aligns with kbx\n\n    // x block quant index when casting the quants to int\n    const int kqs = vdr * (tid % (qi / vdr));\n\n#pragma unroll\n    for (int j = 0; j < ncols_dst; ++j) {\n#pragma unroll\n      for (int i0 = 0; i0 < rows_per_cuda_block; ++i0) {\n        int i = i0 + row0;\n        if (i >= stride_col_dst) { // Avoid OOB read from Q4_0\n          break;\n        }\n        tmp[j][i0] +=\n            vec_dot_q4_0_q8_1(vx, &y[j * stride_col_y + kby],\n                              kbx_offset + i * stride_row_x + kbx, kqs);\n      }\n    }\n  }\n\n  __shared__ float tmp_shared[nwarps - 1 > 0 ? nwarps - 1 : 1][ncols_dst]\n                             [rows_per_cuda_block][WARP_SIZE];\n  if (threadIdx.y > 0) {\n#pragma unroll\n    for (int j = 0; j < ncols_dst; ++j) {\n#pragma unroll\n      for (int i = 0; i < rows_per_cuda_block; ++i) {\n        tmp_shared[threadIdx.y - 1][j][i][threadIdx.x] = tmp[j][i];\n      }\n    }\n  }\n  __syncthreads();\n  if (threadIdx.y > 0) {\n    return;\n  }\n\n  dst += channel_dst * stride_channel_dst + row0;\n\n  // sum up partial sums and write back result\n#pragma unroll\n  for (int j = 0; j < ncols_dst; ++j) {\n#pragma unroll\n    for (int i = 0; i < rows_per_cuda_block; ++i) {\n#pragma unroll\n      for (int l = 0; l < nwarps - 1; ++l) {\n        tmp[j][i] += tmp_shared[l][j][i][threadIdx.x];\n      }\n      tmp[j][i] = warp_reduce_sum<WARP_SIZE>(tmp[j][i]);\n    }\n\n    if (threadIdx.x < rows_per_cuda_block &&\n        (rows_per_cuda_block == 1 ||\n         row0 + int(threadIdx.x) < stride_col_dst)) {\n      dst[j * stride_col_dst + threadIdx.x] = tmp[j][threadIdx.x];\n    }\n  }\n}\n\n#define INSTANTIATE_MMQV_KERNEL(ncols_dst)                                     \\\n  extern \"C\" {                                                                 \\\n  __launch_bounds__(calc_nwarps(ncols_dst) * WARP_SIZE, 1) __global__          \\\n      void mul_vec_q40_m_                                                      \\\n      ##ncols_dst(const void *__restrict__ vx, const void *__restrict__ vy,    \\\n                  float *__restrict__ dst, const int32_t ncols_x,                  \\\n                  const int32_t nchannels_y, const int32_t stride_row_x,               \\\n                  const int32_t stride_col_y, const int32_t stride_col_dst,            \\\n                  const int32_t channel_ratio, const int32_t stride_channel_x,         \\\n                  const int32_t stride_channel_y, const int32_t stride_channel_dst) {  \\\n    mul_mat_vec_q<ncols_dst>(vx, vy, dst, ncols_x, nchannels_y, stride_row_x,  \\\n                             stride_col_y, stride_col_dst, channel_ratio,      \\\n                             stride_channel_x, stride_channel_y,               \\\n                             stride_channel_dst);                              \\\n  }                                                                            \\\n  }\n\nINSTANTIATE_MMQV_KERNEL(1)\nINSTANTIATE_MMQV_KERNEL(2)\nINSTANTIATE_MMQV_KERNEL(3)\nINSTANTIATE_MMQV_KERNEL(4)\nINSTANTIATE_MMQV_KERNEL(5)\nINSTANTIATE_MMQV_KERNEL(6)\nINSTANTIATE_MMQV_KERNEL(7)\nINSTANTIATE_MMQV_KERNEL(8)\n"
  },
  {
    "path": "cuda/src/kernels/cu/nn.cu",
    "content": "#include \"common.cuh\"\n#include <cuda_runtime.h>\n#include <math_constants.h>\n\n#define GELU_COEF_A 0.044715f\n#define SQRT_2_OVER_PI 0.79788456080286535587989211986876f\n\ntemplate <typename Acc> struct MaxOp {\n    using acc_t = Acc;\n    __device__ __forceinline__ static acc_t identity() { return -CUDART_INF_F; }\n    __device__ __forceinline__ static acc_t pre(acc_t a) { return a; }\n    __device__ __forceinline__ static acc_t combine(acc_t a, acc_t b) {\n        return a > b ? a : b;\n    }\n    __device__ __forceinline__ static acc_t norm(acc_t a, int32_t size) {\n        return a;\n    }\n};\n\ntemplate <typename Acc> struct MinOp {\n    using acc_t = Acc;\n    __device__ __forceinline__ static acc_t identity() { return CUDART_INF_F; }\n    __device__ __forceinline__ static acc_t pre(acc_t a) { return a; }\n    __device__ __forceinline__ static acc_t combine(acc_t a, acc_t b) {\n        return a < b ? a : b;\n    }\n    __device__ __forceinline__ static acc_t norm(acc_t a, int32_t size) {\n        return a;\n    }\n};\n\ntemplate <typename Acc> struct AddOp {\n    using acc_t = Acc;\n    __device__ __forceinline__ static acc_t identity() { return acc_t(0); }\n    __device__ __forceinline__ static acc_t pre(acc_t a) { return a; }\n    __device__ __forceinline__ static acc_t combine(acc_t a, acc_t b) {\n        return a + b;\n    }\n    __device__ __forceinline__ static acc_t norm(acc_t a, int32_t size) {\n        return a;\n    }\n};\n\ntemplate <typename Acc> struct MulOp {\n    using acc_t = Acc;\n    __device__ __forceinline__ static acc_t identity() { return acc_t(1); }\n    __device__ __forceinline__ static acc_t pre(acc_t a) { return a; }\n    __device__ __forceinline__ static acc_t combine(acc_t a, acc_t b) {\n        return a * b;\n    }\n    __device__ __forceinline__ static acc_t norm(acc_t a, int32_t size) {\n        return a;\n    }\n};\n\ntemplate <typename Acc> struct MeanOfSquaresOp {\n    using acc_t = Acc;\n    __device__ __forceinline__ static acc_t identity() { return acc_t(0); }\n    __device__ __forceinline__ static acc_t pre(acc_t a) { return a * a; }\n    __device__ __forceinline__ static acc_t combine(acc_t a, acc_t b) {\n        return a + b;\n    }\n    __device__ __forceinline__ static acc_t norm(acc_t a, int32_t size) {\n        return a / (acc_t)size;\n    }\n};\n\ntemplate <typename Acc> struct BoolOrOp {\n    using acc_t = Acc;\n    __device__ __forceinline__ static acc_t identity() { return acc_t(0); }\n    __device__ __forceinline__ static acc_t pre(acc_t a) { return a; }\n    __device__ __forceinline__ static acc_t combine(acc_t a, acc_t b) {\n        return a || b;\n    }\n    __device__ __forceinline__ static acc_t norm(acc_t a, int32_t size) {\n        return a;\n    }\n};\n\ntemplate <typename Acc> struct BoolAndOp {\n    using acc_t = Acc;\n    __device__ __forceinline__ static acc_t identity() { return acc_t(1); }\n    __device__ __forceinline__ static acc_t pre(acc_t a) { return a; }\n    __device__ __forceinline__ static acc_t combine(acc_t a, acc_t b) {\n        return a && b;\n    }\n    __device__ __forceinline__ static acc_t norm(acc_t a, int32_t size) {\n        return a;\n    }\n};\n\ntemplate <class Op>\n__device__ __forceinline__ typename Op::acc_t\nwarp_reduce(typename Op::acc_t v) {\n#pragma unroll\n    for (int offset = WARP_SIZE / 2; offset > 0; offset >>= 1) {\n        auto other = __shfl_xor_sync(0xffffffff, v, offset, WARP_SIZE);\n        v = Op::combine(v, other);\n    }\n    return v;\n}\n\ntemplate <typename T, int BLOCK_THREADS, class Op>\n__device__ void reduce(const T *input, T *output, const int32_t shape_0,\n                       const int32_t shape_1, const int32_t shape_2,\n                       const int32_t in_stride_0, const int32_t in_stride_1,\n                       const int32_t in_stride_2, const int32_t out_stride_0,\n                       const int32_t out_stride_1, const int32_t out_stride_2) {\n\n    using Acc = typename Op::acc_t;\n\n    int64_t linear =\n        (int64_t)blockIdx.x + (int64_t)blockIdx.y * (int64_t)gridDim.x;\n    int64_t total = (int64_t)shape_0 * (int64_t)shape_2;\n    if (linear >= total)\n        return;\n\n    int32_t y = (int32_t)(linear / shape_2);\n    int32_t x = (int32_t)(linear - (int64_t)y * shape_2);\n\n    input += (int64_t)y * in_stride_0 + (int64_t)x * in_stride_2;\n    output += (int64_t)y * out_stride_0 + (int64_t)x * out_stride_2;\n\n    Acc accu = Op::identity();\n    // each thread computes reduction over BLOCK_SIZE values\n    _Pragma(\"unroll\") for (int i = threadIdx.x; i < shape_1;\n                           i += BLOCK_THREADS) {\n        accu = Op::combine(accu, Op::pre(input[i * in_stride_1]));\n    }\n\n    // each warp reduce the values of its 32 threads\n    // (every wrap member converge to the value)\n    accu = warp_reduce<Op>(accu);\n\n    constexpr int NUM_WARPS = (BLOCK_THREADS + WARP_SIZE - 1) / WARP_SIZE;\n    if constexpr (NUM_WARPS == 1) {\n        if (threadIdx.x == 0) {\n            *output = Op::norm(accu, shape_1);\n        }\n    } else {\n        const int warp_id = threadIdx.x / WARP_SIZE;\n        const int lane_id = threadIdx.x % WARP_SIZE;\n\n        __shared__ Acc shared[NUM_WARPS];\n\n        if (lane_id == 0) {\n            shared[warp_id] = accu;\n        }\n        __syncthreads();\n\n        if (warp_id == 0) {\n            accu = (lane_id < NUM_WARPS) ? shared[lane_id] : Op::identity();\n            accu = warp_reduce<Op>(accu);\n            if (lane_id == 0) {\n                *output = Op::norm(accu, shape_1);\n            }\n        }\n    }\n}\n\n#define INSTANTIATE_REDUCE_1(op_name, name, T, Op, bname, block_size)          \\\n    extern \"C\" __global__ void CAT5(reduce_, op_name, _, bname, name)(         \\\n        const T *input, T *output, const int32_t shape_0,                      \\\n        const int32_t shape_1, const int32_t shape_2,                          \\\n        const int32_t in_stride_0, const int32_t in_stride_1,                  \\\n        const int32_t in_stride_2, const int32_t out_stride_0,                 \\\n        const int32_t out_stride_1, const int32_t out_stride_2) {              \\\n        reduce<T, block_size, Op<T>>(input, output, shape_0, shape_1, shape_2, \\\n                                     in_stride_0, in_stride_1, in_stride_2,    \\\n                                     out_stride_0, out_stride_1,               \\\n                                     out_stride_2);                            \\\n    }\n\n#define INSTANTIATE_REDUCE(name, T, bname, block_size)                         \\\n    INSTANTIATE_REDUCE_1(max, name, T, MaxOp, bname, block_size)               \\\n    INSTANTIATE_REDUCE_1(min, name, T, MinOp, bname, block_size)               \\\n    INSTANTIATE_REDUCE_1(sum, name, T, AddOp, bname, block_size)               \\\n    INSTANTIATE_REDUCE_1(prod, name, T, MulOp, bname, block_size)              \\\n    INSTANTIATE_REDUCE_1(mean_of_squares, name, T, MeanOfSquaresOp, bname,     \\\n                         block_size)\n\nextern \"C\" __global__ void gelu_approx_f32(const float *input, float *output,\n                                           int32_t len) {\n    int i = blockIdx.x * blockDim.x + threadIdx.x;\n    if (i < len) {\n        float x = input[i];\n        float output_f32 = 0.5 * x *\n                           (1.0 + tanhf(SQRT_2_OVER_PI *\n                                        (x + GELU_COEF_A * powf(x, (float)3))));\n        output[i] = output_f32;\n    }\n}\n\nextern \"C\" __global__ void gelu_approx_f16(const __half *input, __half *output,\n                                           int32_t len) {\n    int i = blockIdx.x * blockDim.x + threadIdx.x;\n    if (i < len) {\n        float x = (float)input[i];\n        float output_f32 = 0.5 * x *\n                           (1.0 + tanhf(SQRT_2_OVER_PI *\n                                        (x + GELU_COEF_A * powf(x, (float)3))));\n        output[i] = (__half)output_f32;\n    }\n}\n\nextern \"C\" __global__ void gelu_approx_fast_f32(const float *input,\n                                                float *output, int32_t len) {\n    int i = blockIdx.x * blockDim.x + threadIdx.x;\n    if (i < len) {\n        float x = input[i];\n        float output_f32 = 0.5 * x *\n                           (1.0 + tanhf(SQRT_2_OVER_PI *\n                                        (x + GELU_COEF_A * powf(x, (float)2))));\n        output[i] = output_f32;\n    }\n}\n\nextern \"C\" __global__ void gelu_approx_fast_f16(const __half *input,\n                                                __half *output, int32_t len) {\n    int i = blockIdx.x * blockDim.x + threadIdx.x;\n    if (i < len) {\n        float x = (float)input[i];\n        float output_f32 = 0.5 * x *\n                           (1.0 + tanhf(SQRT_2_OVER_PI *\n                                        (x + GELU_COEF_A * powf(x, (float)2))));\n        output[i] = (__half)output_f32;\n    }\n}\n\nextern \"C\" __global__ void leaky_relu_f32(const float *input, float *output,\n                                          int32_t len, float alpha) {\n    int i = blockIdx.x * blockDim.x + threadIdx.x;\n    if (i < len) {\n        float x = input[i];\n        output[i] = x * (x < 0 ? alpha : 1.0);\n    }\n}\n\nextern \"C\" __global__ void leaky_relu_f16(const __half *input, __half *output,\n                                          int32_t len, float alpha) {\n    int i = blockIdx.x * blockDim.x + threadIdx.x;\n    __half alpha_f16 = (__half)alpha;\n    if (i < len) {\n        __half x = input[i];\n        output[i] = x * (x < (__half)0.0 ? alpha_f16 : (__half)1.0);\n    }\n}\n\nstatic __device__ __forceinline__ int\nindices_to_idx_2(int x, int y, int x_strides, int y_strides) {\n    return x * x_strides + y * y_strides;\n}\n\nstatic __device__ __forceinline__ int indices_to_idx_3(int x, int y, int z,\n                                                       int x_strides,\n                                                       int y_strides,\n                                                       int z_strides) {\n    return x * x_strides + y * y_strides + z * z_strides;\n}\n\nstatic __device__ __forceinline__ int\nindices_to_idx_4(int x, int y, int z, int x_shape, int y_shape, int z_shape,\n                 int b_shape, int x_strides, int y_strides, int z_strides,\n                 int b_strides) {\n    int idx = x * x_strides + y * y_strides;\n    idx += (z % z_shape) * z_strides;\n    z /= z_shape;\n    idx += z * b_strides;\n    return idx;\n}\n\n#define INSTANTIATE_APPLY_ROPE(name, T)                                        \\\n    extern \"C\" __global__ void apply_rope_nd2_##name(                          \\\n        const T *input, const T *cos, const T *sin, T *output,                 \\\n        int32_t in_shape_0, int32_t in_shape_1, int32_t in_strides_0,          \\\n        int32_t in_strides_1, int32_t cos_sin_strides_0,                       \\\n        int32_t cos_sin_strides_1, int32_t out_strides_0,                      \\\n        int32_t out_strides_1) {                                               \\\n        int thread_idx_x = blockIdx.x * blockDim.x + threadIdx.x;              \\\n        int thread_idx_y = blockIdx.y * blockDim.y + threadIdx.y;              \\\n        if (thread_idx_x >= in_shape_1 / 2 || thread_idx_y >= in_shape_0) {    \\\n            return;                                                            \\\n        }                                                                      \\\n        int rotated_idx_x = thread_idx_x + in_shape_1 / 2;                     \\\n                                                                               \\\n        int idx = indices_to_idx_2(thread_idx_x, thread_idx_y, in_strides_1,   \\\n                                   in_strides_0);                              \\\n        int rot_idx = indices_to_idx_2(rotated_idx_x, thread_idx_y,            \\\n                                       in_strides_1, in_strides_0);            \\\n        int out_idx = indices_to_idx_2(thread_idx_x, thread_idx_y,             \\\n                                       out_strides_1, out_strides_0);          \\\n        int out_rot_idx = indices_to_idx_2(rotated_idx_x, thread_idx_y,        \\\n                                           out_strides_1, out_strides_0);      \\\n                                                                               \\\n        int cos_sin_idx = indices_to_idx_2(                                    \\\n            thread_idx_x, thread_idx_y, cos_sin_strides_1, cos_sin_strides_0); \\\n        int rot_cos_sin_idx =                                                  \\\n            indices_to_idx_2(rotated_idx_x, thread_idx_y, cos_sin_strides_1,   \\\n                             cos_sin_strides_0);                               \\\n                                                                               \\\n        output[out_idx] =                                                      \\\n            input[idx] * cos[cos_sin_idx] - input[rot_idx] * sin[cos_sin_idx]; \\\n        output[out_rot_idx] = input[rot_idx] * cos[rot_cos_sin_idx] +          \\\n                              input[idx] * sin[rot_cos_sin_idx];               \\\n    }                                                                          \\\n                                                                               \\\n    extern \"C\" __global__ void apply_rope_nd3_##name(                          \\\n        const T *input, const T *cos, const T *sin, T *output,                 \\\n        int32_t in_shape_0, int32_t in_shape_1, int32_t in_shape_2,            \\\n        int32_t in_strides_0, int32_t in_strides_1, int32_t in_strides_2,      \\\n        int32_t cos_sin_strides_0, int32_t cos_sin_strides_1,                  \\\n        int32_t cos_sin_strides_2, int32_t out_strides_0,                      \\\n        int32_t out_strides_1, int32_t out_strides_2) {                        \\\n        int thread_idx_x = blockIdx.x * blockDim.x + threadIdx.x;              \\\n        int thread_idx_y = blockIdx.y * blockDim.y + threadIdx.y;              \\\n        int thread_idx_z = blockIdx.z * blockDim.z + threadIdx.z;              \\\n        if (thread_idx_x >= in_shape_2 / 2 || thread_idx_y >= in_shape_1 ||    \\\n            thread_idx_z >= in_shape_0) {                                      \\\n            return;                                                            \\\n        }                                                                      \\\n        int rotated_idx_x = thread_idx_x + in_shape_2 / 2;                     \\\n                                                                               \\\n        int idx = indices_to_idx_3(thread_idx_x, thread_idx_y, thread_idx_z,   \\\n                                   in_strides_2, in_strides_1, in_strides_0);  \\\n        int rot_idx =                                                          \\\n            indices_to_idx_3(rotated_idx_x, thread_idx_y, thread_idx_z,        \\\n                             in_strides_2, in_strides_1, in_strides_0);        \\\n        int out_idx =                                                          \\\n            indices_to_idx_3(thread_idx_x, thread_idx_y, thread_idx_z,         \\\n                             out_strides_2, out_strides_1, out_strides_0);     \\\n        int out_rot_idx =                                                      \\\n            indices_to_idx_3(rotated_idx_x, thread_idx_y, thread_idx_z,        \\\n                             out_strides_2, out_strides_1, out_strides_0);     \\\n                                                                               \\\n        int cos_sin_idx = indices_to_idx_3(                                    \\\n            thread_idx_x, thread_idx_y, thread_idx_z, cos_sin_strides_2,       \\\n            cos_sin_strides_1, cos_sin_strides_0);                             \\\n        int rot_cos_sin_idx = indices_to_idx_3(                                \\\n            rotated_idx_x, thread_idx_y, thread_idx_z, cos_sin_strides_2,      \\\n            cos_sin_strides_1, cos_sin_strides_0);                             \\\n                                                                               \\\n        output[out_idx] =                                                      \\\n            input[idx] * cos[cos_sin_idx] - input[rot_idx] * sin[cos_sin_idx]; \\\n        output[out_rot_idx] = input[rot_idx] * cos[rot_cos_sin_idx] +          \\\n                              input[idx] * sin[rot_cos_sin_idx];               \\\n    }                                                                          \\\n                                                                               \\\n    extern \"C\" __global__ void apply_rope_nd4_##name(                          \\\n        const T *input, const T *cos, const T *sin, T *output,                 \\\n        int32_t in_shape_0, int32_t in_shape_1, int32_t in_shape_2,            \\\n        int32_t in_shape_3, int32_t in_strides_0, int32_t in_strides_1,        \\\n        int32_t in_strides_2, int32_t in_strides_3, int32_t cos_sin_strides_0, \\\n        int32_t cos_sin_strides_1, int32_t cos_sin_strides_2,                  \\\n        int32_t cos_sin_strides_3, int32_t out_strides_0,                      \\\n        int32_t out_strides_1, int32_t out_strides_2, int32_t out_strides_3) { \\\n        int thread_idx_x = blockIdx.x * blockDim.x + threadIdx.x;              \\\n        int thread_idx_y = blockIdx.y * blockDim.y + threadIdx.y;              \\\n        int thread_idx_z = blockIdx.z * blockDim.z + threadIdx.z;              \\\n        if (thread_idx_x >= in_shape_3 / 2 || thread_idx_y >= in_shape_2 ||    \\\n            thread_idx_z >= (in_shape_1 * in_shape_0)) {                       \\\n            return;                                                            \\\n        }                                                                      \\\n        int rotated_idx_x = thread_idx_x + in_shape_3 / 2;                     \\\n                                                                               \\\n        int idx = indices_to_idx_4(thread_idx_x, thread_idx_y, thread_idx_z,   \\\n                                   in_shape_3, in_shape_2, in_shape_1,         \\\n                                   in_shape_0, in_strides_3, in_strides_2,     \\\n                                   in_strides_1, in_strides_0);                \\\n        int rot_idx = indices_to_idx_4(                                        \\\n            rotated_idx_x, thread_idx_y, thread_idx_z, in_shape_3, in_shape_2, \\\n            in_shape_1, in_shape_0, in_strides_3, in_strides_2, in_strides_1,  \\\n            in_strides_0);                                                     \\\n        int out_idx = indices_to_idx_4(                                        \\\n            thread_idx_x, thread_idx_y, thread_idx_z, in_shape_3, in_shape_2,  \\\n            in_shape_1, in_shape_0, out_strides_3, out_strides_2,              \\\n            out_strides_1, out_strides_0);                                     \\\n        int out_rot_idx = indices_to_idx_4(                                    \\\n            rotated_idx_x, thread_idx_y, thread_idx_z, in_shape_3, in_shape_2, \\\n            in_shape_1, in_shape_0, out_strides_3, out_strides_2,              \\\n            out_strides_1, out_strides_0);                                     \\\n                                                                               \\\n        int cos_sin_idx = indices_to_idx_4(                                    \\\n            thread_idx_x, thread_idx_y, thread_idx_z, in_shape_3, in_shape_2,  \\\n            in_shape_1, in_shape_0, cos_sin_strides_3, cos_sin_strides_2,      \\\n            cos_sin_strides_1, cos_sin_strides_0);                             \\\n        int rot_cos_sin_idx = indices_to_idx_4(                                \\\n            rotated_idx_x, thread_idx_y, thread_idx_z, in_shape_3, in_shape_2, \\\n            in_shape_1, in_shape_0, cos_sin_strides_3, cos_sin_strides_2,      \\\n            cos_sin_strides_1, cos_sin_strides_0);                             \\\n                                                                               \\\n        output[out_idx] =                                                      \\\n            input[idx] * cos[cos_sin_idx] - input[rot_idx] * sin[cos_sin_idx]; \\\n        output[out_rot_idx] = input[rot_idx] * cos[rot_cos_sin_idx] +          \\\n                              input[idx] * sin[rot_cos_sin_idx];               \\\n    }\n\n#define INSTANTIATE_SOFTMAX(name, T, bname, block_size)                        \\\n    extern \"C\" __global__ void softmax_##bname##name(                          \\\n        const T *x, T *dst, const int32_t shape_0, const int32_t shape_1,      \\\n        const int32_t shape_2, const int32_t stride_0, const int32_t stride_1, \\\n        const int32_t stride_2) {                                              \\\n        int offset = (blockIdx.x % shape_2) * stride_2 +                       \\\n                     (blockIdx.x / shape_2) * stride_0;                        \\\n        x += offset;                                                           \\\n        dst += offset;                                                         \\\n                                                                               \\\n        const int warp_id = threadIdx.x / WARP_SIZE;                           \\\n        const int lane_id = threadIdx.x % WARP_SIZE;                           \\\n                                                                               \\\n        float max_val = -CUDART_INF_F;                                         \\\n        _Pragma(\"unroll\") for (int i = threadIdx.x; i < shape_1;               \\\n                               i += blockDim.x) {                              \\\n            max_val = max(max_val, x[i * stride_1]);                           \\\n        }                                                                      \\\n                                                                               \\\n        max_val = warp_reduce_max(max_val);                                    \\\n        if (block_size > WARP_SIZE) {                                          \\\n            __shared__ float s_max[32];                                        \\\n            if (warp_id == 0) {                                                \\\n                s_max[lane_id] = -CUDART_INF_F;                                \\\n            }                                                                  \\\n            __syncthreads();                                                   \\\n                                                                               \\\n            if (lane_id == 0) {                                                \\\n                s_max[warp_id] = max_val;                                      \\\n            }                                                                  \\\n            __syncthreads();                                                   \\\n                                                                               \\\n            max_val = s_max[lane_id];                                          \\\n            max_val = warp_reduce_max(max_val);                                \\\n        }                                                                      \\\n                                                                               \\\n        float tmp = 0.0f;                                                      \\\n        for (int i = threadIdx.x; i < shape_1; i += blockDim.x) {              \\\n            float el = x[i * stride_1];                                        \\\n            const float val = expf(el - max_val);                              \\\n            tmp += val;                                                        \\\n            dst[i * stride_1] = val;                                           \\\n        }                                                                      \\\n                                                                               \\\n        tmp = warp_reduce_sum(tmp);                                            \\\n        if (block_size > WARP_SIZE) {                                          \\\n            __shared__ float s_sum[32];                                        \\\n            if (warp_id == 0) {                                                \\\n                s_sum[lane_id] = 0.0f;                                         \\\n            }                                                                  \\\n            __syncthreads();                                                   \\\n                                                                               \\\n            if (lane_id == 0) {                                                \\\n                s_sum[warp_id] = tmp;                                          \\\n            }                                                                  \\\n            __syncthreads();                                                   \\\n                                                                               \\\n            tmp = s_sum[lane_id];                                              \\\n            tmp = warp_reduce_sum(tmp);                                        \\\n        }                                                                      \\\n                                                                               \\\n        const float inv_sum = 1.0f / tmp;                                      \\\n                                                                               \\\n        for (int i = threadIdx.x; i < shape_1; i += blockDim.x) {              \\\n            dst[i * stride_1] *= inv_sum;                                      \\\n        }                                                                      \\\n    }\n\n/// basic 4D f32-masked softmax, doing softmax on last axis (*_3)\n// if input is [ b, h, rows, cols ], softmax is performed alongside the col\n// dimension\n// supports mask broadcast by mask_stride tuning\n// grid dim is (rows, h, b)\n// block_size is WARP_SIZE * 2^n\n// shared_mem: (32 + next_poswer_of_2(cols) * sizeof(T)\ntemplate <typename T, int BLOCK_SIZE>\n__device__ void scaled_masked_softmax(\n    const T *x, const T *mask, const float scale, T *dst, const int32_t shape_0,\n    const int32_t shape_1, const int32_t shape_2, const int32_t shape_3,\n    const int32_t shape_4, const int32_t stride_0, const int32_t stride_1,\n    const int32_t stride_2, const int32_t stride_3, const int32_t stride_4,\n    const int32_t mask_stride_0, const int32_t mask_stride_1,\n    const int32_t mask_stride_2, const int32_t mask_stride_3,\n    const int32_t mask_stride_4, const int32_t out_stride_0,\n    const int32_t out_stride_1, const int32_t out_stride_2,\n    const int32_t out_stride_3, const int32_t out_stride_4) {\n    int32_t z0 = blockIdx.z / shape_1;\n    int32_t z1 = blockIdx.z % shape_1;\n    x += blockIdx.x * stride_3 + blockIdx.y * stride_2 + z1 * stride_1 +\n         z0 * stride_0;\n    mask += mask ? blockIdx.x * mask_stride_3 + blockIdx.y * mask_stride_2 +\n                       z1 * mask_stride_1 + z0 * mask_stride_0\n                 : 0;\n    dst += blockIdx.x * out_stride_3 + blockIdx.y * out_stride_2 +\n           z1 * out_stride_1 + z0 * out_stride_0;\n\n    const int block_size = BLOCK_SIZE == 0 ? blockDim.x : BLOCK_SIZE;\n\n    const int warp_id = threadIdx.x / WARP_SIZE;\n    const int lane_id = threadIdx.x % WARP_SIZE;\n\n    extern __shared__ float data_soft_max_f32[];\n    float *buf_iw = data_soft_max_f32;\n    float *vals = buf_iw + WARP_SIZE;\n\n    float max_val = -CUDART_INF_F;\n    _Pragma(\"unroll\") for (int col0 = 0; col0 < shape_4; col0 += block_size) {\n        const int col = col0 + threadIdx.x;\n        if (col >= shape_4) {\n            break;\n        }\n\n        const float m = mask ? (float)mask[col * mask_stride_4] : 0.0f;\n        const float val = ((float)x[col * stride_4]) * scale + m;\n        vals[col] = val;\n        max_val = max(max_val, val);\n    }\n\n    max_val = warp_reduce_max(max_val);\n    if (block_size > WARP_SIZE) {\n        if (warp_id == 0) {\n            buf_iw[lane_id] = -CUDART_INF_F;\n        }\n        __syncthreads();\n\n        if (lane_id == 0) {\n            buf_iw[warp_id] = max_val;\n        }\n        __syncthreads();\n\n        max_val = buf_iw[lane_id];\n        max_val = warp_reduce_max(max_val);\n    }\n\n    float tmp = 0.0f;\n    _Pragma(\"unroll\") for (int col0 = 0; col0 < shape_4; col0 += block_size) {\n        const int col = col0 + threadIdx.x;\n        if (col >= shape_4) {\n            break;\n        }\n\n        const float val = expf(vals[col] - max_val);\n        tmp += val;\n        vals[col] = val;\n    }\n\n    tmp = warp_reduce_sum(tmp);\n    if (block_size > WARP_SIZE) {\n        __syncthreads();\n        if (warp_id == 0) {\n            buf_iw[lane_id] = 0.0f;\n        }\n        __syncthreads();\n\n        if (lane_id == 0) {\n            buf_iw[warp_id] = tmp;\n        }\n        __syncthreads();\n\n        tmp = buf_iw[lane_id];\n        tmp = warp_reduce_sum(tmp);\n    }\n\n    const float inv_sum = 1.0f / tmp;\n\n    _Pragma(\"unroll\") for (int col0 = 0; col0 < shape_4; col0 += block_size) {\n        const int col = col0 + threadIdx.x;\n        if (col >= shape_4) {\n            return;\n        }\n        dst[col * out_stride_4] = vals[col] * inv_sum;\n    }\n}\n\n#define INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, bname, block_size_template) \\\n    extern \"C\" __global__ void scaled_masked_softmax_##bname##name(            \\\n        const T *x, const T *mask, const float scale, T *dst,                  \\\n        const int32_t shape_0, const int32_t shape_1, const int32_t shape_2,   \\\n        const int32_t shape_3, const int32_t shape_4, const int32_t stride_0,  \\\n        const int32_t stride_1, const int32_t stride_2,                        \\\n        const int32_t stride_3, const int32_t stride_4,                        \\\n        const int32_t mask_stride_0, const int32_t mask_stride_1,              \\\n        const int32_t mask_stride_2, const int32_t mask_stride_3,              \\\n        const int32_t mask_stride_4, const int32_t out_stride_0,               \\\n        const int32_t out_stride_1, const int32_t out_stride_2,                \\\n        const int32_t out_stride_3, const int32_t out_stride_4) {              \\\n        scaled_masked_softmax<T, block_size_template>(                         \\\n            x, mask, scale, dst, shape_0, shape_1, shape_2, shape_3, shape_4,  \\\n            stride_0, stride_1, stride_2, stride_3, stride_4, mask_stride_0,   \\\n            mask_stride_1, mask_stride_2, mask_stride_3, mask_stride_4,        \\\n            out_stride_0, out_stride_1, out_stride_2, out_stride_3,            \\\n            out_stride_4);                                                     \\\n    }\n\n#define INSTANTIATE_RMS_NORM(name, T, bname, block_size)                       \\\n    extern \"C\" __global__ void rms_norm_##bname##name(                         \\\n        const T *x, T *dst, const int32_t shape_0, const int32_t shape_1,      \\\n        const int32_t shape_2, const int32_t strides_0,                        \\\n        const int32_t strides_1, const int32_t strides_2, const float eps) {   \\\n        int base_idx = (blockIdx.x % shape_2) * strides_2 +                    \\\n                       (blockIdx.x / shape_2) * strides_0;                     \\\n                                                                               \\\n        float tmp = 0.0f;                                                      \\\n                                                                               \\\n        for (int i = threadIdx.x; i < shape_1; i += blockDim.x) {              \\\n            const float xi = x[base_idx + i * strides_1];                      \\\n            tmp += xi * xi;                                                    \\\n        }                                                                      \\\n                                                                               \\\n        tmp = warp_reduce_sum(tmp);                                            \\\n        if constexpr (block_size > WARP_SIZE) {                                \\\n            __shared__ float s_sum[32];                                        \\\n            const int warp_id = threadIdx.x / WARP_SIZE;                       \\\n            const int lane_id = threadIdx.x % WARP_SIZE;                       \\\n            if (lane_id == 0) {                                                \\\n                s_sum[warp_id] = tmp;                                          \\\n            }                                                                  \\\n            __syncthreads();                                                   \\\n            tmp = s_sum[lane_id];                                              \\\n            tmp = warp_reduce_sum(tmp);                                        \\\n        }                                                                      \\\n                                                                               \\\n        const float mean = tmp / shape_1;                                      \\\n        const float scale = rsqrtf(mean + eps);                                \\\n                                                                               \\\n        for (int i = threadIdx.x; i < shape_1; i += blockDim.x) {              \\\n            int idx = base_idx + i * strides_1;                                \\\n            dst[idx] = scale * (float)x[idx];                                  \\\n        }                                                                      \\\n    }\n\nINSTANTIATE_APPLY_ROPE(f32, float)\nINSTANTIATE_APPLY_ROPE(f16, __half)\n\nINSTANTIATE_RMS_NORM(f32, float, small_, 32)\nINSTANTIATE_RMS_NORM(f32, float, , 1024)\nINSTANTIATE_RMS_NORM(f16, __half, small_, 32)\nINSTANTIATE_RMS_NORM(f16, __half, , 1024)\n\nINSTANTIATE_SOFTMAX(f32, float, small_, 32)\nINSTANTIATE_SOFTMAX(f32, float, , 1024)\nINSTANTIATE_SOFTMAX(f16, __half, small_, 32)\nINSTANTIATE_SOFTMAX(f16, __half, , 1024)\n\n#define INSTANTIATE_SCALED_MASKED_SOFTMAX_FOR_T(name, T)                       \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 32_, 32)                        \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 64_, 64)                        \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 128_, 126)                      \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 256_, 256)                      \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 512_, 512)                      \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 1024_, 1024)                    \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 2048_, 1024)                    \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 4096_, 1024)                    \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 8192_, 1024)                    \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 16384_, 1024)                   \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 32768_, 1024)                   \\\n    INSTANTIATE_SCALED_MASKED_SOFTMAX(name, T, 0_, 0)\n\nINSTANTIATE_SCALED_MASKED_SOFTMAX_FOR_T(f32, float)\nINSTANTIATE_SCALED_MASKED_SOFTMAX_FOR_T(f16, __half)\n\nINSTANTIATE_REDUCE(f32, float, small_, 32)\nINSTANTIATE_REDUCE(f32, float, , 1024)\nINSTANTIATE_REDUCE(f16, __half, small_, 32)\nINSTANTIATE_REDUCE(f16, __half, , 1024)\nINSTANTIATE_REDUCE_1(any, bool, char, BoolOrOp, small_, 32)\nINSTANTIATE_REDUCE_1(any, bool, char, BoolOrOp, , 1024)\nINSTANTIATE_REDUCE_1(all, bool, char, BoolAndOp, small_, 32)\nINSTANTIATE_REDUCE_1(all, bool, char, BoolAndOp, , 1024)\nINSTANTIATE_REDUCE_1(sum, i64, int64_t, AddOp, small_, 32)\nINSTANTIATE_REDUCE_1(sum, i64, int64_t, AddOp, , 1024)\nINSTANTIATE_REDUCE_1(prod, i64, int64_t, MulOp, small_, 32)\nINSTANTIATE_REDUCE_1(prod, i64, int64_t, MulOp, , 1024)\n"
  },
  {
    "path": "cuda/src/kernels/cu/quantize.cu",
    "content": "#include \"common.cuh\"\n\nstatic __device__ __forceinline__ void\ncompute_mmq_q81_block(float4 xi, int64_t ib, int64_t iqs, block_q8_1_mmq *y) {\n  constexpr int vals_per_scale = 32;\n  constexpr int vals_per_sum = 32;\n\n  float amax = fabsf(xi.x);\n  amax = fmaxf(amax, fabsf(xi.y));\n  amax = fmaxf(amax, fabsf(xi.z));\n  amax = fmaxf(amax, fabsf(xi.w));\n\n// Exchange max. abs. value between vals_per_scale/4 threads.\n#pragma unroll\n  for (int offset = vals_per_scale / 8; offset > 0; offset >>= 1) {\n    amax = fmaxf(amax, __shfl_xor_sync(0xFFFFFFFF, amax, offset, WARP_SIZE));\n  }\n\n  float sum;\n  sum = xi.x + xi.y + xi.z + xi.w;\n\n// Calculate sums across vals_per_sum/4 threads.\n#pragma unroll\n  for (int offset = vals_per_sum / 8; offset > 0; offset >>= 1) {\n    sum += __shfl_xor_sync(0xFFFFFFFF, sum, offset, WARP_SIZE);\n  }\n\n  float d_inv = (amax > 0.f) ? 127.f / amax : 0.f;\n  char4 q;\n  q.x = roundf(xi.x * d_inv);\n  q.y = roundf(xi.y * d_inv);\n  q.z = roundf(xi.z * d_inv);\n  q.w = roundf(xi.w * d_inv);\n\n  // Write back 4 int8 values as a single 32 bit value for better memroy\n  // bandwidth:\n  char4 *yqs4 = (char4 *)y[ib].qs;\n  yqs4[iqs / 4] = q;\n\n  if (iqs % 32 != 0) {\n    return;\n  }\n\n  const float d = (d_inv > 0.0f) ? (1.0f / d_inv) : 0.0f;\n  y[ib].ds4[iqs / 32] = make_half2(d, sum);\n}\n\nextern \"C\" __global__ void quantize_mmq_q8_1_fast_nd2(\n    const float *__restrict__ x, void *__restrict__ vy, const int32_t k,\n    const int32_t in_strides_0, const int32_t in_strides_1,\n    const int32_t padded_k) {\n\n  const int64_t i0 = ((int64_t)blockDim.x * blockIdx.y + threadIdx.x) * 4;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.x;\n\n  const int64_t i00 = i0;\n  const int64_t i01 = i1;\n\n  const float4 *x4 = (const float4 *)x;\n\n  const int64_t ib0 =\n      blockIdx.z * ((int64_t)gridDim.x * gridDim.y * blockDim.x /\n                    QK8_1); // first block of channel\n  const int64_t ib = ib0 + (i0 / (4 * QK8_1)) * gridDim.x +\n                     blockIdx.x;        // block index in channel\n  const int64_t iqs = i0 % (4 * QK8_1); // quant index in block\n\n  // Load 4 floats per thread and calculate max. abs. value between them:\n  const float4 xi = i0 < k ? x4[(i01 * in_strides_0 + i00) / 4]\n                           : make_float4(0.0f, 0.0f, 0.0f, 0.0f);\n\n  compute_mmq_q81_block(xi, ib, iqs, (block_q8_1_mmq *)vy);\n}\n\nextern \"C\" __global__ void quantize_mmq_q8_1_fast_nd3(\n    const float *__restrict__ x, void *__restrict__ vy, const int32_t k,\n    const int32_t in_strides_0, const int32_t in_strides_1,\n    const int32_t in_strides_2,\n    const int32_t out_shape_1, const int32_t padded_k) {\n\n  const int64_t i0 = ((int64_t)blockDim.x * blockIdx.y + threadIdx.x) * 4;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.x;\n  const int64_t i2 = blockIdx.z;\n\n  const int64_t i00 = i0;\n  const int64_t i01 = i1;\n  const int64_t i02 = i2;\n\n  const float4 *x4 = (const float4 *)x;\n\n  const int64_t ib0 =\n      blockIdx.z * ((int64_t)gridDim.x * gridDim.y * blockDim.x /\n                    QK8_1); // first block of channel\n  const int64_t ib = ib0 + (i0 / (4 * QK8_1)) * out_shape_1 +\n                     blockIdx.x;        // block index in channel\n  const int64_t iqs = i0 % (4 * QK8_1); // quant index in block\n\n  // Load 4 floats per thread and calculate max. abs. value between them:\n  const float4 xi = i0 < k ? x4[(i02 * in_strides_0 +\n                                 i01 * in_strides_1 + i00) /\n                                4]\n                           : make_float4(0.0f, 0.0f, 0.0f, 0.0f);\n\n  compute_mmq_q81_block(xi, ib, iqs, (block_q8_1_mmq *)vy);\n}\n\nextern \"C\" __global__ void quantize_mmq_q8_1_fast_nd4(\n    const float *__restrict__ x, void *__restrict__ vy, const int32_t k,\n    const int32_t in_strides_0, const int32_t in_strides_1,\n    const int32_t in_strides_2, const int32_t in_strides_3,\n    const int32_t out_shape_1, const int32_t out_shape_2, const int32_t padded_k) {\n\n  const int64_t i0 = ((int64_t)blockDim.x * blockIdx.y + threadIdx.x) * 4;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.x;\n  const int64_t i2 = blockIdx.z % out_shape_1;\n  const int64_t i3 = blockIdx.z / out_shape_1;\n\n  const int64_t i00 = i0;\n  const int64_t i01 = i1;\n  const int64_t i02 = i2;\n  const int64_t i03 = i3;\n\n  const float4 *x4 = (const float4 *)x;\n\n  const int64_t ib0 =\n      blockIdx.z * ((int64_t)gridDim.x * gridDim.y * blockDim.x /\n                    QK8_1); // first block of channel\n  const int64_t ib = ib0 + (i0 / (4 * QK8_1)) * out_shape_2 +\n                     blockIdx.x;        // block index in channel\n  const int64_t iqs = i0 % (4 * QK8_1); // quant index in block\n\n  // Load 4 floats per thread and calculate max. abs. value between them:\n  const float4 xi = i0 < k ? x4[(i03 * in_strides_0 + i02 * in_strides_1 +\n                                 i01 * in_strides_2 + i00) /\n                                4]\n                           : make_float4(0.0f, 0.0f, 0.0f, 0.0f);\n\n  compute_mmq_q81_block(xi, ib, iqs, (block_q8_1_mmq *)vy);\n}\n\nextern \"C\" __global__ void quantize_mmq_q8_1_fast_nd5(\n    const float *__restrict__ x, void *__restrict__ vy, const int32_t k,\n    const int32_t in_strides_0, const int32_t in_strides_1,\n    const int32_t in_strides_2, const int32_t in_strides_3, const int32_t in_strides_4,\n    const int32_t out_shape_1, const int32_t out_shape_2, const int32_t out_shape_3, const int32_t padded_k) {\n\n  const int64_t i0 = ((int64_t)blockDim.x * blockIdx.y + threadIdx.x) * 4;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.x;\n  const int64_t i2 = blockIdx.z % out_shape_2;\n  const int64_t i3 = (blockIdx.z / out_shape_2) % out_shape_1;\n  const int64_t i4 = blockIdx.z / (out_shape_2 * out_shape_1);\n\n  const int64_t i00 = i0;\n  const int64_t i01 = i1;\n  const int64_t i02 = i2;\n  const int64_t i03 = i3;\n  const int64_t i04 = i4;\n\n  const float4 *x4 = (const float4 *)x;\n\n  const int64_t ib0 =\n      blockIdx.z * ((int64_t)gridDim.x * gridDim.y * blockDim.x /\n                    QK8_1); // first block of channel\n  const int64_t ib = ib0 + (i0 / (4 * QK8_1)) * out_shape_3 +\n                     blockIdx.x;        // block index in channel\n  const int64_t iqs = i0 % (4 * QK8_1); // quant index in block\n\n  // Load 4 floats per thread and calculate max. abs. value between them:\n  const float4 xi = i0 < k ? x4[(i04 * in_strides_4 + i03 * in_strides_1 +\n                                 i02 * in_strides_2 + i01 * in_strides_3 + i00) /\n                                4]\n                           : make_float4(0.0f, 0.0f, 0.0f, 0.0f);\n\n  compute_mmq_q81_block(xi, ib, iqs, (block_q8_1_mmq *)vy);\n}\n\nextern \"C\" __global__ void\nquantize_mmq_q8_1_nd2(const float *__restrict__ x, void *__restrict__ vy,\n                      const int64_t k, const int64_t in_strides_0,\n                      const int64_t in_strides_1, const int64_t padded_k) {\n\n  const int64_t i0 = ((int64_t)blockDim.x * blockIdx.y + threadIdx.x) * 4;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.x;\n\n  float4 xi = make_float4(0.0f, 0.0f, 0.0f, 0.0f);\n  const int64_t base = i1 * in_strides_0 + i0 * in_strides_1;\n\n  xi.x = (i0 + 0 < k) ? x[base + 0 * in_strides_1] : 0.0f;\n  xi.y = (i0 + 1 < k) ? x[base + 1 * in_strides_1] : 0.0f;\n  xi.z = (i0 + 2 < k) ? x[base + 2 * in_strides_1] : 0.0f;\n  xi.w = (i0 + 3 < k) ? x[base + 3 * in_strides_1] : 0.0f;\n\n  const int64_t ib0 =\n      blockIdx.z * ((int64_t)gridDim.x * gridDim.y * blockDim.x /\n                    QK8_1); // first block of channel\n  const int64_t ib = ib0 + (i0 / (4 * QK8_1)) * gridDim.x +\n                     blockIdx.x;        // block index in channel\n  const int64_t iqs = i0 % (4 * QK8_1); // quant index in block\n\n  compute_mmq_q81_block(xi, ib, iqs, (block_q8_1_mmq *)vy);\n}\n\nextern \"C\" __global__ void\nquantize_mmq_q8_1_nd3(const float *__restrict__ x, void *__restrict__ vy,\n                      const int32_t k, const int32_t in_strides_0,\n                      const int32_t in_strides_1, const int32_t in_strides_2,\n                      const int32_t out_shape_1, const int32_t padded_k) {\n\n  const int64_t i0 = ((int64_t)blockDim.x * blockIdx.y + threadIdx.x) * 4;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.x;\n  const int64_t i2 = blockIdx.z;\n\n  float4 xi = make_float4(0.0f, 0.0f, 0.0f, 0.0f);\n  const int64_t base =\n      i2 * in_strides_0 + i1 * in_strides_1 + i0 * in_strides_2;\n\n  xi.x = (i0 + 0 < k) ? x[base + 0 * in_strides_2] : 0.0f;\n  xi.y = (i0 + 1 < k) ? x[base + 1 * in_strides_2] : 0.0f;\n  xi.z = (i0 + 2 < k) ? x[base + 2 * in_strides_2] : 0.0f;\n  xi.w = (i0 + 3 < k) ? x[base + 3 * in_strides_2] : 0.0f;\n\n  const int64_t ib0 =\n      blockIdx.z * ((int64_t)gridDim.x * gridDim.y * blockDim.x /\n                    QK8_1); // first block of channel\n  const int64_t ib = ib0 + (i0 / (4 * QK8_1)) * out_shape_1 +\n                     blockIdx.x;        // block index in channel\n  const int64_t iqs = i0 % (4 * QK8_1); // quant index in block\n\n  compute_mmq_q81_block(xi, ib, iqs, (block_q8_1_mmq *)vy);\n}\n\nextern \"C\" __global__ void\nquantize_mmq_q8_1_nd4(const float *__restrict__ x, void *__restrict__ vy,\n                      const int32_t k, const int32_t in_strides_0,\n                      const int32_t in_strides_1, const int32_t in_strides_2,\n                      const int32_t in_strides_3, const int32_t out_shape_1,\n                      const int32_t out_shape_2, const int32_t padded_k) {\n\n  const int64_t i0 = ((int64_t)blockDim.x * blockIdx.y + threadIdx.x) * 4;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.x;\n  const int64_t i2 = blockIdx.z % out_shape_1;\n  const int64_t i3 = blockIdx.z / out_shape_1;\n\n  float4 xi = make_float4(0.0f, 0.0f, 0.0f, 0.0f);\n  const int64_t base = i3 * in_strides_0 + i2 * in_strides_1 +\n                       i1 * in_strides_2 + i0 * in_strides_3;\n\n  xi.x = (i0 + 0 < k) ? x[base + 0 * in_strides_3] : 0.0f;\n  xi.y = (i0 + 1 < k) ? x[base + 1 * in_strides_3] : 0.0f;\n  xi.z = (i0 + 2 < k) ? x[base + 2 * in_strides_3] : 0.0f;\n  xi.w = (i0 + 3 < k) ? x[base + 3 * in_strides_3] : 0.0f;\n\n  const int64_t ib0 =\n      blockIdx.z * ((int64_t)gridDim.x * gridDim.y * blockDim.x /\n                    QK8_1); // first block of channel\n  const int64_t ib = ib0 + (i0 / (4 * QK8_1)) * out_shape_2 +\n                     blockIdx.x;        // block index in channel\n  const int64_t iqs = i0 % (4 * QK8_1); // quant index in block\n\n  compute_mmq_q81_block(xi, ib, iqs, (block_q8_1_mmq *)vy);\n}\n\nextern \"C\" __global__ void\nquantize_mmq_q8_1_nd5(const float *__restrict__ x, void *__restrict__ vy,\n                      const int32_t k, const int32_t in_strides_0,\n                      const int32_t in_strides_1, const int32_t in_strides_2,\n                      const int32_t in_strides_3, const int32_t in_strides_4,\n                      const int32_t out_shape_1, const int32_t out_shape_2,\n                      const int32_t out_shape_3, const int32_t padded_k) {\n\n  const int64_t i0 = ((int64_t)blockDim.x * blockIdx.y + threadIdx.x) * 4;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.x;\n  const int64_t i2 = blockIdx.z % out_shape_2;\n  const int64_t i3 = (blockIdx.z / out_shape_2) % out_shape_1;\n  const int64_t i4 = blockIdx.z / (out_shape_2 * out_shape_1);\n\n  float4 xi = make_float4(0.0f, 0.0f, 0.0f, 0.0f);\n  const int64_t base = i4 * in_strides_0 + i3 * in_strides_1 +\n                       i2 * in_strides_2 + i1 * in_strides_3 +\n                       i0 * in_strides_4;\n\n  xi.x = (i0 + 0 < k) ? x[base + 0 * in_strides_4] : 0.0f;\n  xi.y = (i0 + 1 < k) ? x[base + 1 * in_strides_4] : 0.0f;\n  xi.z = (i0 + 2 < k) ? x[base + 2 * in_strides_4] : 0.0f;\n  xi.w = (i0 + 3 < k) ? x[base + 3 * in_strides_4] : 0.0f;\n\n  const int64_t ib0 =\n      blockIdx.z * ((int64_t)gridDim.x * gridDim.y * blockDim.x /\n                    QK8_1); // first block of channel\n  const int64_t ib = ib0 + (i0 / (4 * QK8_1)) * out_shape_3 +\n                     blockIdx.x;        // block index in channel\n  const int64_t iqs = i0 % (4 * QK8_1); // quant index in block\n\n  compute_mmq_q81_block(xi, ib, iqs, (block_q8_1_mmq *)vy);\n}\n\nstatic __device__ __forceinline__ void\ncompute_q81_block(float xi, int64_t i_cont, block_q8_1 **y_ptr) {\n  block_q8_1 *y = *y_ptr;\n  const int64_t ib = i_cont / QK8_1;  // block index\n  const int64_t iqs = i_cont % QK8_1; // quant index\n  float amax = fabsf(xi);\n  float sum = xi;\n\n  amax = warp_reduce_max(amax);\n  sum = warp_reduce_sum(sum);\n\n  const float d = amax / 127;\n  const int8_t q = amax == 0.0f ? 0 : roundf(xi / d);\n\n  y[ib].qs[iqs] = q;\n\n  if (iqs > 0) {\n    return;\n  }\n\n  reinterpret_cast<half &>(y[ib].ds.x) = d;\n  reinterpret_cast<half &>(y[ib].ds.y) = sum;\n}\n\nextern \"C\" __global__ void\nquantize_q8_1_nd2(const float *__restrict__ x, void *__restrict__ vy,\n                  const int32_t k, const int32_t in_strides_0,\n                  const int32_t in_strides_1, const int32_t padded_k) {\n  const int64_t i0 = (int64_t)blockDim.x * blockIdx.x + threadIdx.x;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.y;\n\n  const int64_t &i00 = i0;\n  const int64_t &i01 = i1;\n\n  const int64_t i_cont = i1 * padded_k + i0;\n\n  const float xi = i0 < k ? x[i01 * in_strides_0 + i00 * in_strides_1] : 0.0f;\n\n  compute_q81_block(xi, i_cont, (block_q8_1 **)&vy);\n}\n\nextern \"C\" __global__ void\nquantize_q8_1_nd3(const float *__restrict__ x, void *__restrict__ vy,\n                  const int32_t k, const int32_t in_strides_0,\n                  const int32_t in_strides_1, const int32_t in_strides_2,\n                  const int32_t out_shape_1, const int32_t padded_k) {\n  const int64_t i0 = (int64_t)blockDim.x * blockIdx.x + threadIdx.x;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.y;\n  const int64_t i2 = blockIdx.z;\n\n  const int64_t &i00 = i0;\n  const int64_t &i01 = i1;\n  const int64_t &i02 = i2;\n\n  const int64_t i_cont = (i2 * out_shape_1 + i1) * padded_k + i0;\n\n  const float xi =\n      i0 < k ? x[i02 * in_strides_0 + i01 * in_strides_1 + i00 * in_strides_2]\n             : 0.0f;\n\n  compute_q81_block(xi, i_cont, (block_q8_1 **)&vy);\n}\n\nextern \"C\" __global__ void\nquantize_q8_1_nd4(const float *__restrict__ x, void *__restrict__ vy,\n                  const int32_t k, const int32_t in_strides_0,\n                  const int32_t in_strides_1, const int32_t in_strides_2,\n                  const int32_t in_strides_3, const int32_t out_shape_1,\n                  const int32_t out_shape_2, const int32_t padded_k) {\n  const int64_t i0 = (int64_t)blockDim.x * blockIdx.x + threadIdx.x;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.y;\n  const int64_t i2 = blockIdx.z % out_shape_1;\n  const int64_t i3 = blockIdx.z / out_shape_1;\n\n  const int64_t &i00 = i0;\n  const int64_t &i01 = i1;\n  const int64_t &i02 = i2;\n  const int64_t &i03 = i3;\n\n  const int64_t i_cont =\n      ((i3 * out_shape_1 + i2) * out_shape_2 + i1) * padded_k + i0;\n\n  const float xi = i0 < k ? x[i03 * in_strides_0 + i02 * in_strides_1 +\n                              i01 * in_strides_2 + i00 * in_strides_3]\n                          : 0.0f;\n\n  compute_q81_block(xi, i_cont, (block_q8_1 **)&vy);\n}\n\nextern \"C\" __global__ void\nquantize_q8_1_nd5(const float *__restrict__ x, void *__restrict__ vy,\n                  const int32_t k, const int32_t in_strides_4,\n                  const int32_t in_strides_3, const int32_t in_strides_2,\n                  const int32_t in_strides_1, const int32_t in_strides_0,\n                  const int32_t out_shape_1, const int32_t out_shape_2,\n                  const int32_t out_shape_3, const int32_t padded_k) {\n  const int64_t i0 = (int64_t)blockDim.x * blockIdx.x + threadIdx.x;\n\n  if (i0 >= padded_k) {\n    return;\n  }\n\n  const int64_t i1 = blockIdx.y;\n  const int64_t i2 = blockIdx.z % out_shape_2;\n  const int64_t i3 = (blockIdx.z / out_shape_2) % out_shape_1;\n  const int64_t i4 = blockIdx.z / (out_shape_2 * out_shape_1);\n\n  const int64_t &i00 = i0;\n  const int64_t &i01 = i1;\n  const int64_t &i02 = i2;\n  const int64_t &i03 = i3;\n  const int64_t &i04 = i4;\n\n  const int64_t i_cont =\n      (((i4 * out_shape_1 + i3) * out_shape_2 + i2) * out_shape_3 + i1) *\n          padded_k +\n      i0;\n\n  const float xi =\n      i0 < k ? x[i04 * in_strides_0 + i03 * in_strides_1 + i02 * in_strides_2 +\n                 i01 * in_strides_3 + i00 * in_strides_4]\n             : 0.0f;\n\n  compute_q81_block(xi, i_cont, (block_q8_1 **)&vy);\n}"
  },
  {
    "path": "cuda/src/kernels/element_wise.rs",
    "content": "use cudarc::driver::{LaunchConfig, PushKernelArg};\nuse tract_core::internal::*;\nuse tract_core::ops::element_wise::ElementWiseMiniOp;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::*;\n\nconst ALL_OP_NAMES: &[&str] = &[\n    \"neg\",\n    \"abs\",\n    \"square\",\n    \"sqrt\",\n    \"rsqrt\",\n    \"recip\",\n    \"ceil\",\n    \"floor\",\n    \"round\",\n    \"roundhalftoeven\",\n    \"exp\",\n    \"sigmoid\",\n    \"sin\",\n    \"sinh\",\n    \"asin\",\n    \"asinh\",\n    \"cos\",\n    \"cosh\",\n    \"acos\",\n    \"acosh\",\n    \"tan\",\n    \"tanh\",\n    \"atan\",\n    \"atanh\",\n    \"erf\",\n    \"ln\",\n    \"silu\",\n    \"sign\",\n    \"hardswish\",\n    \"bitnot\",\n];\n\npub fn all_functions() -> Vec<String> {\n    ALL_OP_NAMES\n        .iter()\n        .flat_map(|kname| {\n            DeviceTensor::SUPPORTED_DT.into_iter().flat_map(move |dt| {\n                let tname = DeviceTensor::tname(dt).ok()?;\n                Some(format!(\"element_wise_{kname}_{tname}\"))\n            })\n        })\n        .collect()\n}\n\npub fn is_supported(mini_op: &dyn ElementWiseMiniOp, dt: DatumType) -> bool {\n    let name = mini_op.name().to_lowercase();\n    ALL_OP_NAMES.contains(&name.as_str())\n        && if name == \"bitnot\" {\n            dt.is_integer() || dt.is::<bool>()\n        } else {\n            matches!(dt, DatumType::F32 | DatumType::F16)\n        }\n}\n\npub fn dispatch_eval(\n    stream: &TractCudaStream,\n    mini_op: &dyn ElementWiseMiniOp,\n    input: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    ensure!(output.shape() == input.shape());\n    ensure!(output.datum_type() == input.datum_type());\n\n    let op_name = mini_op.name().to_lowercase();\n    let tname = DeviceTensor::tname(input.datum_type())?;\n    let kname = format!(\"element_wise_{op_name}_{tname}\");\n\n    let func = cuda_context().load_pipeline(LibraryName::ElementWise, kname)?;\n\n    let len = input.len();\n\n    let i_view = get_cuda_view(input);\n    let o_view = get_cuda_view(output);\n\n    let cfg = LaunchConfig::for_num_elems(len as _);\n    let mut launch_args = TractLaunchArgs::new(stream, &func);\n    launch_args.push_view(&i_view);\n    launch_args.push_view(&o_view);\n    launch_args.push_i32(len);\n\n    launch_args.launch(cfg)\n}\n\npub fn cuda_element_wise_dispatch(\n    mini_op: &dyn ElementWiseMiniOp,\n    input: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| dispatch_eval(stream, mini_op, input, output))\n}\n\npub fn cuda_element_wise_op(\n    mini_op: Box<dyn ElementWiseMiniOp>,\n) -> tract_gpu::ops::element_wise::GpuElementWise {\n    tract_gpu::ops::element_wise::GpuElementWise::new(mini_op, \"Cuda\", cuda_element_wise_dispatch)\n}\n\n// Generic element-wise fallback — checked after LeakyRelu, GeluApproximate.\ncrate::register_cuda_op!(tract_core::ops::element_wise::ElementWiseOp, |source, node, op| {\n    rule_if!(is_supported(&*op.0, source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(cuda_element_wise_op(op.0.clone()))))\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::with_cuda_stream;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use tract_gpu::tensor::IntoDevice;\n\n    fn test_case<F>(mini_op: &dyn ElementWiseMiniOp, shape: &[usize]) -> TractResult<()>\n    where\n        F: Float + Datum,\n        usize: AsPrimitive<f32>,\n        f32: AsPrimitive<F>,\n    {\n        with_cuda_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n            let input = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v / len as f32).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let output =\n                unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n            dispatch_eval(stream, mini_op, &input, &output)?;\n            stream.synchronize()?;\n\n            let out = output.to_host()?.into_tensor();\n            assert_eq!(out.shape(), shape);\n            Ok(())\n        })\n    }\n\n    use tract_core::ops::math;\n    use tract_core::ops::nn;\n\n    #[test]\n    fn test_element_wise_exp() -> TractResult<()> {\n        test_case::<f32>(&math::Exp {}, &[4, 4])?;\n        test_case::<f16>(&math::Exp {}, &[4, 4])?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_element_wise_sigmoid() -> TractResult<()> {\n        test_case::<f32>(&nn::Sigmoid {}, &[4, 4])?;\n        test_case::<f16>(&nn::Sigmoid {}, &[4, 4])?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_element_wise_abs() -> TractResult<()> {\n        test_case::<f32>(&math::Abs {}, &[4, 4])?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/flash_attn.rs",
    "content": "use cudarc::driver::sys::CUfunction_attribute;\nuse cudarc::driver::{CudaFunction, LaunchArgs, LaunchConfig, PushKernelArg};\nuse num_traits::One;\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_gpu::tensor::{DeviceTensor, IntoDevice};\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::utils::compute_broadcast_strides;\nuse crate::kernels::{LibraryName, WARP_SIZE, get_cuda_view, launch_args};\n\n#[derive(Debug, Clone)]\npub struct CudaFlashAttn;\n\nimpl fmt::Display for CudaFlashAttn {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl CudaFlashAttn {\n    pub fn name(&self) -> Cow<'_, str> {\n        format!(\"{self}\").into()\n    }\n\n    pub fn output_shape<D: DimLike + One>(\n        &self,\n        q: &[D],\n        k: &[D],\n        v: &[D],\n    ) -> TractResult<TVec<D>> {\n        ensure!(q.len() == 4, \"Q rank must be 4 (got {})\", q.len());\n        ensure!(\n            k.len() == q.len() && v.len() == q.len(),\n            \"K and V must have the same rank as Q (Q={}, K={}, V={})\",\n            q.len(),\n            k.len(),\n            v.len()\n        );\n\n        match (q, k, v) {\n            ([b, qh, s, _], [_, kh, _, _], [_, vh, _, d]) => {\n                let (qh_i, kh_i, vh_i) = (qh.to_i64()?, kh.to_i64()?, vh.to_i64()?);\n                ensure!(kh_i == vh_i, \"K and V heads mismatch (K={}, V={})\", kh_i, vh_i);\n                ensure!(kh_i > 0, \"K/V heads must be > 0 (got {kh_i})\");\n                ensure!(\n                    qh_i % kh_i == 0,\n                    \"Q heads ({qh_i}) must be a multiple of K/V heads ({kh_i})\"\n                );\n                Ok(tvec![b.clone(), qh.clone(), s.clone(), d.clone()])\n            }\n            _ => bail!(\"Inconsistent shapes: expected [B,H,S,D] for Q/K/V.\"),\n        }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        q: &DeviceTensor,\n        k: &DeviceTensor,\n        v: &DeviceTensor,\n        mask: Option<&DeviceTensor>,\n        scale: f32,\n        is_causal: bool,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe {\n            DeviceTensor::uninitialized_dt(\n                q.datum_type(),\n                &self.output_shape(q.shape(), k.shape(), v.shape())?,\n            )?\n        };\n\n        self.dispatch_eval(stream, q, k, v, mask, scale, &output, is_causal)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        q: &DeviceTensor,\n        k: &DeviceTensor,\n        v: &DeviceTensor,\n        mask: Option<&DeviceTensor>,\n        scale: f32,\n        out: &DeviceTensor,\n        is_causal: bool,\n    ) -> TractResult<()> {\n        ensure!(q.datum_type() == DatumType::F16 && q.datum_type() == out.datum_type());\n        ensure!(k.datum_type() == DatumType::F16 && k.datum_type() == v.datum_type());\n\n        ensure!(out.shape() == self.output_shape(q.shape(), k.shape(), v.shape())?.as_slice());\n        ensure!(!is_causal || mask.is_none());\n        ensure!(mask.is_none_or(|m| m.datum_type() == DatumType::F16));\n\n        let ctxt = cuda_context();\n        let q_shape = q.shape();\n\n        let b = q_shape[0];\n        let n_qh = q_shape[1];\n        let len_q = q_shape[2];\n        let len_kv = k.shape()[2];\n        let d = q_shape[3];\n\n        ensure!(n_qh % k.shape()[1] == 0);\n        ensure!(k.shape()[0] == b);\n\n        let head_ratio = n_qh / k.shape()[1];\n        let block_q = 64;\n        let block_kv = 32;\n\n        let n_warps = 4;\n\n        let num_full_q_blocks = len_q / block_q;\n        let tb_size = n_warps * WARP_SIZE;\n        let smem_size = block_q.max(block_kv * 3) * d * size_of::<f16>();\n\n        let mask_mode = if is_causal {\n            \"causal\"\n        } else if mask.is_some() {\n            \"mask\"\n        } else {\n            \"nomask\"\n        };\n\n        let null_ptr = stream.null()?;\n\n        let q_view = get_cuda_view(q);\n        let k_view = get_cuda_view(k);\n        let v_view = get_cuda_view(v);\n        let m_view = mask.map(get_cuda_view).unwrap_or_else(|| null_ptr.as_view());\n        let o_view = get_cuda_view(out);\n\n        let mask_strides = if let Some(m) = mask {\n            let strides = compute_broadcast_strides(m.shape(), m.strides())?;\n            (strides[0], strides[1])\n        } else {\n            (0, 0)\n        };\n\n        let kernel_launcher = |suffix: &str, num_q_blocks: usize| -> TractResult<()> {\n            let func = ctxt.load_pipeline(\n                LibraryName::FlashAttn,\n                format!(\"attention_{suffix}{block_q}_{block_kv}_{d}_{mask_mode}\"),\n            )?;\n\n            func.set_attribute(\n                CUfunction_attribute::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,\n                smem_size as _,\n            )?;\n\n            let mut launch_args = TractLaunchArgs::new(stream, &func);\n            launch_args.push_view(&q_view);\n            launch_args.push_view(&k_view);\n            launch_args.push_view(&v_view);\n            launch_args.push_view(&m_view);\n            launch_args.push_view(&o_view);\n            launch_args.push_i32(b);\n            launch_args.push_i32(n_qh);\n            launch_args.push_i32(head_ratio);\n            launch_args.push_i32(len_q);\n            launch_args.push_i32(k.shape()[2]);\n            launch_args.push_i32(mask_strides.0);\n            launch_args.push_i32(mask_strides.1);\n            launch_args.push::<f32>(scale);\n\n            let cfg = LaunchConfig {\n                grid_dim: (num_q_blocks as _, n_qh as _, b as _),\n                block_dim: (tb_size as _, 1, 1),\n                shared_mem_bytes: smem_size as _,\n            };\n\n            launch_args.launch(cfg)\n        };\n\n        if num_full_q_blocks > 0 {\n            kernel_launcher(\"fullq_\", num_full_q_blocks)?;\n        }\n\n        if !len_q.is_multiple_of(block_q) {\n            kernel_launcher(\"tailq_\", 1)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use num_traits::Float;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_transformers::ops::sdpa::Sdpa;\n\n    use super::*;\n\n    fn run_test_case(\n        batch: usize,\n        q_heads: usize,\n        kv_heads: usize,\n        past_seq_len: usize,\n        seq_len: usize,\n        out_dim: usize,\n        scale: f32,\n        is_causal: bool,\n        create_mask: bool,\n    ) -> TractResult<()> {\n        ensure!(!(create_mask && is_causal));\n        crate::with_cuda_stream(|stream| {\n            let q_shape = [batch, q_heads, seq_len, out_dim];\n            let kv_shape = [batch, kv_heads, past_seq_len + seq_len, out_dim];\n            let m_shape = [1, 1, seq_len, past_seq_len + seq_len];\n\n            let q_len = q_shape.iter().product::<usize>();\n            let kv_len = kv_shape.iter().product::<usize>();\n            let m_len = m_shape.iter().product::<usize>();\n\n            let q = Tensor::from_shape(\n                &q_shape,\n                &(0..q_len).map(|f| f16::from_f32(f as f32 / q_len as f32)).collect::<Vec<_>>(),\n            )?;\n\n            let k = Tensor::from_shape(\n                &kv_shape,\n                &(0..kv_len).map(|f| f16::from_f32(f as f32 / kv_len as f32)).collect::<Vec<_>>(),\n            )?;\n\n            let v = Tensor::from_shape(\n                &kv_shape,\n                &(0..kv_len).map(|f| f16::from_f32(f as f32 / kv_len as f32)).collect::<Vec<_>>(),\n            )?;\n\n            let m = if create_mask {\n                Tensor::from_shape(\n                    &m_shape,\n                    &(0..m_len).map(|f| f16::from_f32(1f32)).collect::<Vec<_>>(),\n                )?\n            } else {\n                tensor0(0.0f32) // Unused \n            };\n\n            let cuda_m = m.clone().into_device()?;\n            let cuda_output = CudaFlashAttn.eval(\n                stream,\n                &q.clone().into_device()?,\n                &k.clone().into_device()?,\n                &v.clone().into_device()?,\n                if create_mask { Some(&cuda_m) } else { None },\n                scale,\n                is_causal,\n            )?;\n\n            let mut ref_inputs = tvec!(q.into(), k.into(), v.into());\n\n            if create_mask {\n                ref_inputs.push(m.into())\n            };\n            let ref_output = Sdpa {\n                scale: Some(scale.into()),\n                datum_type: DatumType::F16,\n                acc_datum_type: DatumType::F32,\n                is_causal,\n            }\n            .eval(ref_inputs)?;\n\n            cuda_output.to_host()?.close_enough(&ref_output[0], Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_nernst_fattn() -> TractResult<()> {\n        run_test_case(1, 1, 1, 64, 1, 128, 1.0f32, false, false)?;\n        run_test_case(1, 2, 1, 64, 1, 128, 1.0f32, false, true)?;\n        run_test_case(1, 2, 2, 0, 1, 64, 1.0f32, false, false)?;\n        run_test_case(2, 4, 2, 123, 1, 64, 1.0f32, false, false)?;\n        run_test_case(1, 2, 2, 0, 1, 64, 1.0f32, true, false)?;\n        run_test_case(1, 1, 1, 64, 64, 128, 1.0f32, false, false)?;\n        run_test_case(2, 32, 4, 64, 64, 128, 1.0f32, false, false)?;\n        run_test_case(1, 1, 1, 64, 64, 128, 1.0f32, false, true)?;\n        run_test_case(1, 1, 1, 64, 64, 128, 1.0f32, true, false)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/ggml_flash_attn.rs",
    "content": "use cudarc::driver::sys::CUfunction_attribute;\nuse cudarc::driver::{CudaFunction, LaunchConfig, PushKernelArg};\nuse num_traits::One;\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{LibraryName, WARP_SIZE, get_cuda_view, launch_args};\n\nconst CUDA_CC_TURING: i32 = 750;\nconst CUDA_CC_AMPERE: i32 = 800;\nconst FATTN_KQ_STRIDE: usize = 256;\n\n#[derive(Debug, Clone)]\npub struct GgmlFlashAttn;\n\nimpl fmt::Display for GgmlFlashAttn {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\nenum FlashAttnImpl {\n    Vec,\n    MmaF16,\n}\n\n/* ------------------------- small helpers ------------------------- */\n\n#[inline]\nfn cc() -> i32 {\n    let p = cuda_context().properties();\n    p.major * 100 + p.minor * 10\n}\n\n#[inline]\nfn newer_than_lovelace() -> bool {\n    let p = cuda_context().properties();\n    p.major > 8 || (p.major == 8 && p.minor >= 9)\n}\n\n#[inline]\nfn bytes_of<T>() -> usize {\n    std::mem::size_of::<T>()\n}\n\n#[inline]\nfn to_i32(shape: &[usize]) -> TVec<i32> {\n    shape.iter().map(|&s| s as i32).collect()\n}\n\n#[inline]\nfn strides_to_bytes_i32<T>(strides_elems: &[isize]) -> TVec<i32> {\n    let el = bytes_of::<T>() as i32;\n    strides_elems.iter().map(|&s| (s as i32) * el).collect()\n}\n\n#[derive(Debug, Clone)]\nstruct FlashAttnParams {\n    imp: FlashAttnImpl,\n    // tiling\n    d: usize,\n    ncols1: usize,\n    ncols2: usize,\n    nwarps: usize,\n    // shared\n    shm_bytes: usize,\n    // scheduling\n    kq_row_granularity: usize,\n    // derived\n    kernel: Arc<CudaFunction>,\n}\n\nimpl GgmlFlashAttn {\n    pub fn is_supported_dts(k_dt: DatumType, v_dt: DatumType) -> bool {\n        (k_dt == v_dt) && matches!(k_dt, DatumType::F16)\n    }\n\n    pub fn name(&self) -> Cow<'_, str> {\n        format!(\"{self}\").into()\n    }\n\n    pub fn vec_kernel_name(\n        &self,\n        d: usize,\n        ncols1: usize,\n        k_dt: DatumType,\n        v_dt: DatumType,\n    ) -> TractResult<String> {\n        ensure!(\n            Self::is_supported_dts(k_dt, v_dt),\n            \"Unsupported dts K: {:?} V: {:?} for Cuda Flash Attention Op\",\n            k_dt,\n            v_dt\n        );\n        Ok(format!(\n            \"flash_attn_vec_{}_{}_{}_{}\",\n            d,\n            ncols1,\n            DeviceTensor::tname(k_dt)?,\n            DeviceTensor::tname(v_dt)?\n        ))\n    }\n\n    pub fn output_shape<D: DimLike + One>(\n        &self,\n        q: &[D],\n        k: &[D],\n        v: &[D],\n    ) -> TractResult<TVec<D>> {\n        ensure!(q.len() == 4, \"Q rank must be 4 (got {})\", q.len());\n        ensure!(\n            k.len() == q.len() && v.len() == q.len(),\n            \"K and V must have the same rank as Q (Q={}, K={}, V={})\",\n            q.len(),\n            k.len(),\n            v.len()\n        );\n\n        match (q, k, v) {\n            ([b, qh, s, _], [_, kh, _, _], [_, vh, _, d]) => {\n                let (qh_i, kh_i, vh_i) = (qh.to_i64()?, kh.to_i64()?, vh.to_i64()?);\n                ensure!(kh_i == vh_i, \"K and V heads mismatch (K={}, V={})\", kh_i, vh_i);\n                ensure!(kh_i > 0, \"K/V heads must be > 0 (got {kh_i})\");\n                ensure!(\n                    qh_i % kh_i == 0,\n                    \"Q heads ({qh_i}) must be a multiple of K/V heads ({kh_i})\"\n                );\n                Ok(tvec![b.clone(), qh.clone(), s.clone(), d.clone()])\n            }\n            _ => bail!(\"Inconsistent shapes: expected [B,H,S,D] for Q/K/V.\"),\n        }\n    }\n\n    /* ------------------------- kernel planning ------------------------- */\n    fn pick_ncols2(head_ratio: usize) -> usize {\n        if head_ratio.is_multiple_of(8) {\n            8\n        } else if head_ratio.is_multiple_of(4) {\n            4\n        } else if head_ratio.is_multiple_of(2) {\n            2\n        } else {\n            1\n        }\n    }\n    fn pick_ncols1(ncols2: usize, seq_q: usize, cc: i32) -> usize {\n        if ncols2 <= 8 && seq_q <= 8 / ncols2 {\n            8 / ncols2\n        } else if seq_q <= 16 / ncols2 {\n            16 / ncols2\n        } else if cc == CUDA_CC_TURING || seq_q <= 32 / ncols2 {\n            32 / ncols2\n        } else {\n            64 / ncols2\n        }\n    }\n\n    fn get_flash_attn_vec_params(\n        &self,\n        q: &DeviceTensor,\n        k: &DeviceTensor,\n        v: &DeviceTensor,\n    ) -> TractResult<FlashAttnParams> {\n        let qs = q.shape();\n        let rank = qs.len();\n        ensure!(matches!(qs[rank - 2], 1 | 2));\n        let ncols1 = qs[rank - 2];\n        let ncols2 = 1;\n        let d = qs[rank - 1];\n\n        let kernel_name = self.vec_kernel_name(d, ncols1, k.datum_type(), v.datum_type())?;\n        let kernel = cuda_context().load_pipeline(LibraryName::GgmlFlashAttn, kernel_name)?;\n\n        Ok(FlashAttnParams {\n            imp: FlashAttnImpl::Vec,\n            d,\n            ncols1,\n            ncols2,\n            nwarps: 128 / WARP_SIZE,\n            shm_bytes: 0,\n            kq_row_granularity: d,\n            kernel,\n        })\n    }\n\n    fn get_flash_attn_mma_f16_params(\n        &self,\n        q: &DeviceTensor,\n        k: &DeviceTensor,\n    ) -> TractResult<FlashAttnParams> {\n        let cc = cc();\n        let qs = q.shape();\n        let ks = k.shape();\n\n        let d = qs[3];\n        ensure!(d.is_multiple_of(8), \"flash-attn f16: head dimension (D) must be multiple of 8\");\n\n        let head_ratio = qs[1] / ks[1];\n        let ncols2 = Self::pick_ncols2(head_ratio);\n        let ncols1 = Self::pick_ncols1(ncols2, qs[2], cc);\n        let ncols = ncols1 * ncols2;\n\n        let ntiles = if ncols <= 8 { 1 } else { 2 };\n        let cols_per_warp = ntiles * 8;\n        ensure!(ncols.is_multiple_of(cols_per_warp), \"bad ncols vs cols_per_warp\");\n\n        let nbatch_fa = if d != 256 { 64 } else { 32 };\n        let nwarps_max_x = ncols / cols_per_warp;\n        let nwarps_max_y = nbatch_fa / 16;\n        let nwarps = (nwarps_max_x * nwarps_max_y).min(4);\n\n        // shared bytes\n        let nbatch_k2 = d / 2;\n        let nbatch_v2 = d / 2;\n        let nbatch_combine = if cc == CUDA_CC_TURING && ncols1 <= 128 { 128 } else { 64 };\n        let shm_kv_1stage = nbatch_fa * (nbatch_k2 + 4).max(nbatch_v2 + 4) * bytes_of::<f32>();\n        let shm_kv_2stage = nbatch_fa * (nbatch_k2 + 4 + nbatch_v2 + 4) * bytes_of::<f32>();\n        let shm_q = ncols * (d / 2 + 4) * bytes_of::<f32>();\n        let shm_mask = ncols1 * (nbatch_fa / 2 + 4) * bytes_of::<f32>();\n        let shm_combine = nwarps * cols_per_warp * (nbatch_combine + 4) * bytes_of::<f32>();\n        let shm_kv = if cc >= CUDA_CC_AMPERE { shm_kv_2stage } else { shm_kv_1stage };\n        let shm_bytes = shm_combine.max(shm_q.max(shm_kv + shm_mask));\n\n        let kernel_name = format!(\"flash_attn_ext_f16_{}_{}_{}\", d, ncols, ncols2);\n        let kernel = cuda_context().load_pipeline(LibraryName::GgmlFlashAttn, kernel_name)?;\n        kernel.set_attribute(\n            CUfunction_attribute::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,\n            shm_bytes as i32,\n        )?;\n\n        Ok(FlashAttnParams {\n            imp: FlashAttnImpl::MmaF16,\n            d,\n            ncols1,\n            ncols2,\n            nwarps,\n            shm_bytes,\n            kq_row_granularity: FATTN_KQ_STRIDE,\n            kernel,\n        })\n    }\n\n    fn choose_impl(\n        q: &DeviceTensor,\n        k: &DeviceTensor,\n        v: &DeviceTensor,\n        m: &DeviceTensor,\n    ) -> TractResult<FlashAttnImpl> {\n        let qh = q.shape()[1];\n        let kh = k.shape()[1];\n        ensure!(qh % kh == 0);\n        let head_ratio = qh / kh;\n\n        let newer = newer_than_lovelace();\n\n        ensure!(\n            matches!(k.shape()[3], 64 | 80 | 96 | 112 | 128 | 256) && k.shape()[3] == v.shape()[3],\n            \"No kernel for K/V D={} (must match and be one of 64|80|96|112|128|256)\",\n            k.shape()[3]\n        );\n\n        // Batched mask support could be done by modifying KV_max kernel\n        ensure!(m.shape()[..2] == [1, 1]);\n\n        let can_vec = q.shape()[3].is_multiple_of(64);\n        let mut best = FlashAttnImpl::MmaF16;\n\n        if can_vec {\n            if newer\n                && q.shape()[2] == 1\n                && q.shape()[0] == 1\n                && !(head_ratio > 4 && k.shape()[2] >= 8192)\n            {\n                best = FlashAttnImpl::Vec;\n            }\n            if !head_ratio.is_multiple_of(2) && q.shape()[2] == 1 {\n                best = FlashAttnImpl::Vec; // GQA-specific case\n            }\n        }\n        Ok(best)\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        q: &DeviceTensor,\n        k: &DeviceTensor,\n        v: &DeviceTensor,\n        mask: &DeviceTensor,\n        scale: f32,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe {\n            DeviceTensor::uninitialized_dt(\n                q.datum_type(),\n                &self.output_shape(q.shape(), k.shape(), v.shape())?,\n            )?\n        };\n\n        self.dispatch_eval(stream, q, k, v, mask, scale, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        q: &DeviceTensor,\n        k: &DeviceTensor,\n        v: &DeviceTensor,\n        mask: &DeviceTensor,\n        scale: f32,\n        out: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(q.datum_type() == DatumType::F32 && q.datum_type() == out.datum_type());\n        ensure!(out.shape() == self.output_shape(q.shape(), k.shape(), v.shape())?.as_slice());\n\n        match Self::choose_impl(q, k, v, mask)? {\n            FlashAttnImpl::Vec => {\n                let params = self.get_flash_attn_vec_params(q, k, v)?;\n                self.launch_with_plan(stream, q, k, v, mask, scale, out, params)\n            }\n            FlashAttnImpl::MmaF16 => {\n                let params = self.get_flash_attn_mma_f16_params(q, k)?;\n                self.launch_with_plan(stream, q, k, v, mask, scale, out, params)\n            }\n        }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn launch_with_plan(\n        &self,\n        stream: &TractCudaStream,\n        q: &DeviceTensor,\n        k: &DeviceTensor,\n        v: &DeviceTensor,\n        mask: &DeviceTensor,\n        scale: f32,\n        out: &DeviceTensor,\n        params: FlashAttnParams,\n    ) -> TractResult<()> {\n        // quick invariants shared by both variants\n        ensure!(mask.shape()[2] >= q.shape()[2].next_multiple_of(16));\n        ensure!(k.shape()[2].is_multiple_of(FATTN_KQ_STRIDE), \"Incorrect KV cache padding\");\n\n        let qv = get_cuda_view(q);\n        let kv = get_cuda_view(k);\n        let vv = get_cuda_view(v);\n        let mv = get_cuda_view(mask);\n        let ov = get_cuda_view(out);\n\n        // Grid/block sizing & occupancy\n        let ncols = params.ncols1 * params.ncols2;\n        let ntiles_x = q.shape()[2].div_ceil(params.ncols1);\n        let ntiles_total = ntiles_x * (q.shape()[1] / params.ncols2) * q.shape()[0];\n\n        // mask-to-KV-max\n        let kv_max = if q.shape()[2] >= 1024 || q.shape()[0] > 1 {\n            let mask_s2_div2 = mask.strides()[2] / 2;\n            let mask_s0_div2 = mask.strides()[0] / 2;\n\n            let blocks_num = (ntiles_x as _, q.shape()[0] as _, 1);\n            let blocks_dim = ((FATTN_KQ_STRIDE / 2) as _, 1, 1);\n            let ne_kv_max = ntiles_x * q.shape()[0];\n            let iter_k = k.shape()[2] / FATTN_KQ_STRIDE;\n\n            let kv_max = DeviceTensor::uninitialized_dt(DatumType::I64, &[ne_kv_max])?;\n            let kv_max_v = get_cuda_view(&kv_max);\n            let func = cuda_context().load_pipeline(\n                LibraryName::GgmlFlashAttn,\n                format!(\"flash_attn_mask_to_KV_max_{}\", params.ncols1),\n            )?;\n\n            let mut la = TractLaunchArgs::new(stream, &func);\n            la.push_view(&mv);\n            la.push_view(&kv_max_v);\n            la.push_i32(iter_k);\n            la.push_i32(mask_s2_div2);\n            la.push_i32(mask_s0_div2);\n            let cfg =\n                LaunchConfig { grid_dim: blocks_num, block_dim: blocks_dim, shared_mem_bytes: 0 };\n\n            unsafe {\n                la.launch(cfg);\n            }\n            Some(kv_max)\n        } else {\n            None\n        };\n\n        // occupancy & parallel layout\n        let props = cuda_context().properties();\n        let nsm = props.multiProcessorCount as usize;\n        let block_dim = (WARP_SIZE as u32, params.nwarps as u32, 1);\n\n        let max_blocks_per_sm = params.kernel.occupancy_max_active_blocks_per_multiprocessor(\n            WARP_SIZE as u32 * params.nwarps as u32,\n            params.shm_bytes,\n            None,\n        )? as usize;\n\n        let mut parallel_blocks = max_blocks_per_sm;\n\n        let (blocks_num, dst_tmp, dst_tmp_meta) = if matches!(params.imp, FlashAttnImpl::MmaF16) {\n            let max_blocks = max_blocks_per_sm * nsm;\n            let tiles_nwaves = ntiles_total.div_ceil(max_blocks);\n            let tiles_eff_pct = 100 * ntiles_total / (max_blocks * tiles_nwaves);\n            let newer = newer_than_lovelace();\n            let use_stream_k = newer || tiles_eff_pct < 75;\n\n            let blocks_num = (if use_stream_k { max_blocks } else { ntiles_total } as u32, 1, 1);\n            let dst_tmp_meta = DeviceTensor::uninitialized_dt(\n                DatumType::F32,\n                &[2 * blocks_num.0 as usize * ncols * (2 * 2 + params.d) * bytes_of::<f32>()],\n            )?;\n            (blocks_num, None, Some(dst_tmp_meta))\n        } else {\n            ensure!(k.shape()[k.rank() - 2] % params.kq_row_granularity == 0);\n\n            let ntiles_kq = k.shape()[k.rank() - 2].div_ceil(params.kq_row_granularity);\n            let pb_min = parallel_blocks.min(ntiles_kq);\n\n            // try to improve tail efficiency\n            let blocks_per_wave = nsm * max_blocks_per_sm;\n            let mut nwaves_best = 0;\n            let mut eff_best = 0;\n            for pb in (pb_min..=ntiles_kq) {\n                let nblocks_total = ntiles_total * pb;\n                let nwaves = nblocks_total.div_ceil(blocks_per_wave);\n                let eff = 100 * nblocks_total / (nwaves * blocks_per_wave);\n                if eff_best >= 95 && nwaves > nwaves_best {\n                    break;\n                }\n                if eff > eff_best {\n                    nwaves_best = nwaves;\n                    eff_best = eff;\n                    parallel_blocks = pb;\n                }\n            }\n\n            ensure!(\n                parallel_blocks > 1,\n                \"Unsupported config: Output won't be untransposed if we don't enter vec fixup kernel\"\n            );\n            let blocks_num = (\n                ntiles_x as u32,\n                parallel_blocks as u32,\n                (q.shape()[1] / params.ncols2 * q.shape()[0]) as u32,\n            );\n\n            (\n                blocks_num,\n                Some(DeviceTensor::uninitialized_dt(\n                    DatumType::F32,\n                    &[parallel_blocks * out.shape().iter().product::<usize>()],\n                )?),\n                Some(DeviceTensor::uninitialized_dt(\n                    DatumType::F32,\n                    &[2 * parallel_blocks * out.shape()[..3].iter().product::<usize>()],\n                )?),\n            )\n        };\n\n        ensure!(block_dim.0 % WARP_SIZE as u32 == 0);\n\n        // Shapes/strides for kernel\n        let q_shape_i32 = to_i32(q.shape());\n        let k_shape_i32 = to_i32(k.shape());\n        let mask_shape_i32 = to_i32(mask.shape());\n        let q_strides_b = strides_to_bytes_i32::<f32>(q.strides());\n        let k_strides_b = strides_to_bytes_i32::<f16>(k.strides());\n        let v_strides_b = strides_to_bytes_i32::<f16>(v.strides());\n        let mask_strides_b = strides_to_bytes_i32::<f16>(mask.strides());\n\n        let null_ptr = stream.null()?;\n        let kv_max_v = kv_max.as_ref().map(get_cuda_view).unwrap_or_else(|| null_ptr.as_view());\n        let dst_tmp_v = dst_tmp.as_ref().map(get_cuda_view).unwrap_or_else(|| null_ptr.as_view());\n        let dst_tmp_meta_v =\n            dst_tmp_meta.as_ref().map(get_cuda_view).unwrap_or_else(|| null_ptr.as_view());\n\n        // main kernel\n        let mut la = TractLaunchArgs::new(stream, &params.kernel);\n        la.push_view(&qv);\n        la.push_view(&kv);\n        la.push_view(&vv);\n        la.push_view(&mv);\n        la.push_view(&kv_max_v);\n        la.push_view(if matches!(params.imp, FlashAttnImpl::Vec) { &dst_tmp_v } else { &ov });\n        la.push_view(&dst_tmp_meta_v);\n        la.push::<f32>(scale);\n        la.push_slice_i32(&q_shape_i32);\n        la.push_slice_i32(&q_strides_b[..3]);\n        la.push_slice_i32(&k_shape_i32);\n        la.push_slice_i32(&k_strides_b[..3]);\n        la.push_slice_i32(&v_strides_b[..3]);\n        la.push_slice_i32(&mask_shape_i32[..3]);\n        la.push_slice_i32(&mask_strides_b[..3]);\n\n        let cfg = LaunchConfig {\n            grid_dim: blocks_num,\n            block_dim,\n            shared_mem_bytes: params.shm_bytes as u32,\n        };\n\n        la.launch(cfg)?;\n\n        // fixups\n        if matches!(params.imp, FlashAttnImpl::MmaF16) {\n            if !(ntiles_total as u32).is_multiple_of(cfg.grid_dim.0) {\n                let f = cuda_context().load_pipeline(\n                    LibraryName::GgmlFlashAttn,\n                    format!(\"flash_attn_stream_k_fixup_{}_{}_{}\", params.d, ncols, params.ncols2),\n                )?;\n                let mut la = TractLaunchArgs::new(stream, &f);\n                la.push_view(&ov);\n                la.push_view(&dst_tmp_meta_v);\n                la.push_slice_i32(&q_shape_i32[..3]);\n                la.push_i32(k_shape_i32[2]);\n                let cfg = LaunchConfig {\n                    grid_dim: (cfg.grid_dim.0, params.ncols1 as _, params.ncols2 as _),\n                    block_dim: (params.d as _, 1, 1),\n                    shared_mem_bytes: 0,\n                };\n                la.launch(cfg)?;\n            }\n        } else {\n            let f = cuda_context().load_pipeline(\n                LibraryName::GgmlFlashAttn,\n                format!(\"flash_attn_combine_results_{}\", params.d),\n            )?;\n            let mut la = TractLaunchArgs::new(stream, &f);\n            la.push_view(&dst_tmp_v);\n            la.push_view(&dst_tmp_meta_v);\n            la.push_view(&ov);\n            la.push_i32(parallel_blocks);\n            let cfg = LaunchConfig {\n                grid_dim: (q_shape_i32[2] as _, q_shape_i32[1] as _, q_shape_i32[0] as _),\n                block_dim: (params.d as _, 1, 1),\n                shared_mem_bytes: (parallel_blocks * 2 * bytes_of::<f32>()) as u32,\n            };\n\n            la.launch(cfg);\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use num_traits::Float;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_transformers::ops::sdpa::Sdpa;\n\n    use super::*;\n\n    fn pad_f16_tensor(\n        a: &Tensor,\n        axis: usize,\n        block_size: usize,\n        value: f16,\n    ) -> TractResult<Tensor> {\n        let mut shape = a.shape().to_owned();\n        let old_value = shape[axis];\n        shape[axis] = shape[axis].next_multiple_of(block_size);\n        let mut padded_a = Tensor::zero::<f16>(&shape)?;\n        padded_a.fill_t(value);\n        padded_a\n            .to_plain_array_view_mut::<f16>()?\n            .slice_axis_move(tract_ndarray::Axis(axis), (0..old_value).into())\n            .assign(&a.to_plain_array_view::<f16>()?);\n        Ok(padded_a)\n    }\n    fn run_test_case(\n        batch: usize,\n        q_heads: usize,\n        kv_heads: usize,\n        past_seq_len: usize,\n        seq_len: usize,\n        out_dim: usize,\n        scale: f32,\n    ) -> TractResult<()> {\n        crate::with_cuda_stream(|stream| {\n            let q_shape = [batch, q_heads, seq_len, out_dim];\n            let kv_shape = [batch, kv_heads, past_seq_len + seq_len, out_dim];\n            let m_shape = [1, 1, seq_len, past_seq_len + seq_len];\n\n            let q_len = q_shape.iter().product::<usize>();\n            let kv_len = kv_shape.iter().product::<usize>();\n            let m_len = m_shape.iter().product::<usize>();\n\n            let q = Tensor::from_shape(\n                &q_shape,\n                &(0..q_len).map(|f| f as f32 / q_len as f32).collect::<Vec<_>>(),\n            )?;\n\n            let k = Tensor::from_shape(\n                &kv_shape,\n                &(0..kv_len).map(|f| f16::from_f32(f as f32 / kv_len as f32)).collect::<Vec<_>>(),\n            )?;\n\n            let v = Tensor::from_shape(\n                &kv_shape,\n                &(0..kv_len).map(|f| f16::from_f32(f as f32 / kv_len as f32)).collect::<Vec<_>>(),\n            )?;\n\n            let m = Tensor::from_shape(\n                &m_shape,\n                &(0..m_len).map(|f| f16::from_f32(1f32)).collect::<Vec<_>>(),\n            )?;\n\n            let cuda_output = GgmlFlashAttn.eval(\n                stream,\n                &q.clone().into_device()?,\n                &pad_f16_tensor(&k, 2, FATTN_KQ_STRIDE, f16::from_f32(0f32))?.into_device()?,\n                &pad_f16_tensor(&v, 2, FATTN_KQ_STRIDE, f16::from_f32(0f32))?.into_device()?,\n                &&pad_f16_tensor(\n                    &pad_f16_tensor(&m, 3, FATTN_KQ_STRIDE, -f16::infinity())?,\n                    2,\n                    16,\n                    -f16::infinity(),\n                )?\n                .into_device()?,\n                scale,\n            )?;\n\n            let ref_output = Sdpa {\n                scale: Some(scale.into()),\n                datum_type: DatumType::F32,\n                acc_datum_type: DatumType::F32,\n                is_causal: false,\n            }\n            .eval(tvec!(q.into(), k.into(), v.into(), m.into()))?;\n\n            cuda_output.to_host()?.close_enough(&ref_output[0], Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_fattn_vec() -> TractResult<()> {\n        run_test_case(1, 1, 1, 0, 1, 64, 1.0f32)?;\n        run_test_case(1, 32, 8, 0, 1, 64, 1.0f32)?;\n        run_test_case(1, 8, 8, 0, 1, 128, 1.0f32)?;\n        run_test_case(2, 8, 8, 0, 1, 64, 1.0f32)?;\n        run_test_case(1, 1, 1, 3, 1, 128, 1.0f32)?;\n        run_test_case(2, 24, 8, 3, 1, 64, 1.0f32)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_fattn_mma_f16() -> TractResult<()> {\n        run_test_case(1, 1, 1, 0, 1, 64, 1.0f32)?;\n        run_test_case(1, 8, 8, 1, 1, 64, 1.0f32)?;\n        run_test_case(2, 4, 2, 1, 1, 128, 1.0f32)?;\n        run_test_case(2, 8, 8, 0, 1, 128, 1.0f32)?;\n        run_test_case(1, 1, 1, 3, 2, 64, 1.0f32)?;\n        run_test_case(2, 2, 1, 3, 1, 128, 1.0f32)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/iff.rs",
    "content": "use cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::utils::compute_broadcast_strides;\nuse crate::kernels::{LibraryName, MAX_THREADS, get_cuda_view};\n\nstatic TERNARY_MAX_RANK: usize = 5;\n\n#[derive(Debug, PartialEq)]\npub struct Iff;\n\nimpl fmt::Display for Iff {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"Iff\")\n    }\n}\n\nimpl Iff {\n    pub fn name(&self) -> Cow<'_, str> {\n        format!(\"{self}\").into()\n    }\n\n    pub fn output_shape<D: DimLike>(&self, a: &[D], b: &[D], c: &[D]) -> TractResult<TVec<D>> {\n        tract_core::broadcast::multi_broadcast(&[a, b, c])\n            .with_context(|| format!(\"Error while broadcasting {a:?} {b:?} {c:?}\"))\n    }\n\n    pub fn kernel_name(&self, dt: DatumType, variant: &str) -> TractResult<String> {\n        Ok(format!(\"iff_{variant}_{}\", tract_gpu::utils::BroadcastKind::copy_tname(dt)))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        cond: &DeviceTensor,\n        then_value: &DeviceTensor,\n        else_value: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        let out_shape = self.output_shape(cond.shape(), then_value.shape(), else_value.shape())?;\n        ensure!(then_value.datum_type() == else_value.datum_type());\n        let out_dt = then_value.datum_type();\n        let output = unsafe { DeviceTensor::uninitialized_dt(out_dt, &out_shape)? };\n\n        self.dispatch_eval(stream, cond, then_value, else_value, &output)?;\n\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        cond: &DeviceTensor,\n        then_value: &DeviceTensor,\n        else_value: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        let inputs = [cond, then_value, else_value];\n        let rank = *[cond.rank(), then_value.rank(), else_value.rank()].iter().max().unwrap();\n        ensure!(rank <= TERNARY_MAX_RANK);\n\n        let rank_pad = TERNARY_MAX_RANK - rank;\n        let mut strides = [[0isize; TERNARY_MAX_RANK]; 3];\n        let mut out_shape = [1usize; TERNARY_MAX_RANK];\n        let mut out_strides = [0isize; TERNARY_MAX_RANK];\n\n        for axis in 0..rank {\n            out_shape[rank_pad + axis] = output.shape()[axis];\n            out_strides[rank_pad + axis] = output.strides()[axis];\n            for input in 0..3 {\n                strides[input][rank_pad + axis] =\n                    if inputs[input].shape()[axis] < output.shape()[axis] {\n                        0\n                    } else {\n                        inputs[input].strides()[axis]\n                    };\n            }\n        }\n\n        let total_elems: usize = out_shape.iter().product();\n        let block_dim = (128_u32, 1, 1);\n        let (grid_dim, variant) =\n        //     if out_shape[TERNARY_MAX_RANK - 1] >= 256 && total_elems >= 4096 {\n        //     (\n        //         (\n        //             out_shape[TERNARY_MAX_RANK - 2] as u32,\n        //             out_shape[TERNARY_MAX_RANK - 3] as u32,\n        //             out_shape[..TERNARY_MAX_RANK - 3].iter().product::<usize>() as u32,\n        //         ),\n        //         \"large\",\n        //     )\n        // } else {\n            ((total_elems.div_ceil(block_dim.0 as usize) as u32, 1, 1), \"generic\")\n        // };\n        ;\n\n        let kernel_name = self.kernel_name(output.datum_type(), variant)?;\n        let func = cuda_context().load_pipeline(LibraryName::Binary, kernel_name)?;\n\n        let cfg = LaunchConfig { grid_dim, block_dim, shared_mem_bytes: 0 };\n\n        let cond_view = get_cuda_view(cond);\n        let then_view = get_cuda_view(then_value);\n        let else_view = get_cuda_view(else_value);\n        let o_view = get_cuda_view(output);\n\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&cond_view);\n        launch_args.push_view(&then_view);\n        launch_args.push_view(&else_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_slice_i32(&out_shape);\n        for stride in &strides {\n            launch_args.push_slice_i32(stride);\n        }\n        launch_args.push_slice_i32(&out_strides);\n\n        launch_args.launch(cfg)?;\n\n        Ok(())\n    }\n}\n\npub fn cuda_iff_dispatch(\n    cond: &DeviceTensor,\n    then_value: &DeviceTensor,\n    else_value: &DeviceTensor,\n    cond_strides: &[isize],\n    then_strides: &[isize],\n    else_strides: &[isize],\n    output: &DeviceTensor,\n    output_shape: &[usize],\n    output_strides: &[isize],\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| {\n        let total_elems: usize = output_shape.iter().product();\n        let block_dim = (128_u32, 1, 1);\n        let grid_dim = (total_elems.div_ceil(block_dim.0 as usize) as u32, 1, 1);\n\n        let kernel_name = format!(\n            \"iff_generic_{}\",\n            tract_gpu::utils::BroadcastKind::copy_tname(output.datum_type())\n        );\n        let func = cuda_context().load_pipeline(LibraryName::Binary, kernel_name)?;\n        let cfg = LaunchConfig { grid_dim, block_dim, shared_mem_bytes: 0 };\n\n        let cond_view = get_cuda_view(cond);\n        let then_view = get_cuda_view(then_value);\n        let else_view = get_cuda_view(else_value);\n        let o_view = get_cuda_view(output);\n\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&cond_view);\n        launch_args.push_view(&then_view);\n        launch_args.push_view(&else_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_slice_i32(output_shape);\n        launch_args.push_slice_i32(cond_strides);\n        launch_args.push_slice_i32(then_strides);\n        launch_args.push_slice_i32(else_strides);\n        launch_args.push_slice_i32(output_strides);\n\n        launch_args.launch(cfg)\n    })\n}\n\ncrate::register_cuda_op!(tract_core::ops::logic::Iff, |_source, _node, _op| {\n    Ok(Some(Box::new(tract_gpu::ops::iff::GpuIff::new(\"Cuda\", cuda_iff_dispatch))))\n});\n"
  },
  {
    "path": "cuda/src/kernels/launch_args.rs",
    "content": "use core::{mem::size_of, ptr};\nuse cudarc::driver::{CudaFunction, CudaView, DeviceRepr, LaunchArgs, LaunchConfig, PushKernelArg};\nuse num_traits::AsPrimitive;\nuse std::ops::Deref;\nuse tract_core::prelude::TractResult;\n\nuse crate::context::TractCudaStream;\n\nstatic VEC_CAPACITY: usize = 1024;\n\n/// A LaunchArgs that can take by-value params by stashing owned bytes\n/// and handing `&'a T` refs to `inner.arg(...)`.\npub struct TractLaunchArgs<'a> {\n    inner: LaunchArgs<'a>,\n    stream: &'a TractCudaStream,\n    keepalive: Vec<u8>,\n    keepalive_overflow: Vec<Box<[u8]>>,\n}\n\nimpl<'a> TractLaunchArgs<'a> {\n    pub fn new(stream: &'a TractCudaStream, func: &'a CudaFunction) -> Self {\n        Self {\n            inner: stream.launch_builder(func),\n            stream,\n            keepalive: Vec::with_capacity(VEC_CAPACITY),\n            keepalive_overflow: Vec::new(),\n        }\n    }\n\n    fn arg_typed<T: DeviceRepr + Copy + 'a>(&mut self, v: T) {\n        unsafe {\n            let slice = std::slice::from_raw_parts((&v) as *const T as *const u8, size_of::<T>());\n            if self.keepalive.len() + slice.len() < VEC_CAPACITY {\n                let arg: *const T = self.keepalive.as_ptr().add(self.keepalive.len()) as *const T;\n                self.keepalive.extend(slice);\n                self.inner.arg(arg.as_ref().unwrap());\n            } else {\n                let mut buf = slice.to_vec().into_boxed_slice();\n\n                let r: &'a T = &*(buf.as_ptr() as *const T);\n                self.inner.arg(r);\n\n                self.keepalive_overflow.push(buf);\n            }\n        }\n    }\n\n    pub fn push_slice<U>(&mut self, slice: &[impl AsPrimitive<U>])\n    where\n        U: DeviceRepr + Copy + 'static,\n    {\n        for s in slice.iter().copied() {\n            self.arg_typed::<U>(s.as_());\n        }\n    }\n\n    pub fn push_slice_i32(&mut self, slice: &[impl AsPrimitive<i32>]) {\n        for s in slice.iter().copied() {\n            self.arg_typed::<i32>(s.as_());\n        }\n    }\n\n    pub fn push<U>(&mut self, x: impl AsPrimitive<U>)\n    where\n        U: DeviceRepr + Copy + 'static,\n    {\n        self.arg_typed::<U>(x.as_());\n    }\n\n    pub fn push_i32(&mut self, x: impl AsPrimitive<i32>) {\n        self.arg_typed::<i32>(x.as_());\n    }\n\n    pub fn push_view<T>(&mut self, x: &'a CudaView<'_, T>) {\n        self.inner.arg(x);\n    }\n\n    pub fn launch(&mut self, cfg: LaunchConfig) -> TractResult<()> {\n        if let Some((start, end)) = self.stream.record_profile_events()? {\n            unsafe {\n                self.inner.launch(cfg)?;\n            }\n            self.stream.finish_profile_entry(start, end)?;\n        } else {\n            unsafe {\n                self.inner.launch(cfg)?;\n            }\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/matmul/mod.rs",
    "content": "pub(crate) mod quant_act_q81;\n\nuse cudarc::cublas::{self, CudaBlas, Gemm};\nuse cudarc::driver::sys::CUfunction_attribute;\nuse cudarc::driver::{CudaView, CudaViewMut, LaunchConfig, PushKernelArg};\nuse tract_core::tract_linalg::block_quant::{BlockQuant, Q4_0, Q8_1};\n\nuse num_traits::{Float, One};\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\nuse tract_gpu::utils::{as_quant_fact, get_quant_fact};\n\nuse crate::Q40_ROW_PADDING;\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::matmul::quant_act_q81::{QUANTIZE_BLOCK_SIZE, QUANTIZE_BLOCK_SIZE_MMQ};\nuse crate::kernels::{\n    LibraryName, get_cuda_view, get_cuda_view_mut, get_sliced_cuda_view, get_sliced_cuda_view_mut,\n};\nuse crate::utils::get_ggml_q81_fact;\n\nuse DatumType::{F16, F32};\n\nstatic N_WARPS: usize = 8;\nstatic WARP_SIZE: usize = 32;\n\nstatic MMQ_X_MAX: usize = 128;\n\nstatic QK8_0: usize = 32;\nstatic QI8_0: usize = QK8_0 / (4 * QR8_0);\nstatic QR8_0: usize = 1;\n\nstatic MMQ_MMA_TILE_X_K_Q8_0: usize = (2 * WARP_SIZE + 2 * WARP_SIZE / QI8_0 + 4);\n\n// Squeeze batch axes and return a shape with a rank of 3.\nfn squeeze_batch_axes(s: &[usize]) -> TractResult<TVec<usize>> {\n    ensure!(s.len() >= 2);\n    let rank = s.len();\n    if s.len() == 2 {\n        return Ok(tvec![1, s[rank - 2], s[rank - 1]]);\n    }\n    let rank = s.len();\n    Ok(tvec![s[..rank - 2].iter().product(), s[rank - 2], s[rank - 1],])\n}\n\nfn mmq_get_nbytes_shared_q40(mmq_weights: usize, mmq_act: usize) -> usize {\n    let nb_ids = mmq_weights * size_of::<i32>();\n    let mmq_tile_w_l = MMQ_MMA_TILE_X_K_Q8_0;\n    let nbs_w = mmq_act * mmq_tile_w_l * size_of::<i32>();\n    let nbs_act = mmq_weights * 144;\n\n    let pad = N_WARPS * WARP_SIZE * size_of::<i32>();\n    nb_ids + nbs_w + nbs_act.next_multiple_of(pad)\n}\n\npub fn get_concrete_shapes(\n    a: &DeviceTensor,\n    b: &DeviceTensor,\n) -> TractResult<(Vec<usize>, Vec<usize>)> {\n    let q81_a = get_ggml_q81_fact(a);\n    let q40_b = get_quant_fact(b, &Q4_0);\n    ensure!(q40_b.is_none() || (q40_b.is_some() && q81_a.is_some()));\n\n    let a_shape = match q81_a {\n        Some(bqf) => {\n            let concrete = bqf.concrete_in_shape()?;\n            a.shape().iter().copied().chain(concrete.iter().copied()).collect()\n        }\n        None => a.shape().to_vec(),\n    };\n\n    // For q40 weights the tensor shape already carries the full logical\n    // dimensions [batch, n, k].  No need to chain with the fact shape.\n    let b_shape = b.shape().to_vec();\n\n    Ok((a_shape, b_shape))\n}\n\nfn find_block_size(k: usize) -> usize {\n    let mut block_size_best = WARP_SIZE;\n    let mut best_niter = k.div_ceil(2 * WARP_SIZE);\n\n    for block_size in (2 * WARP_SIZE..=256).step_by(WARP_SIZE) {\n        let niter = k.div_ceil(2 * block_size);\n        if niter < best_niter {\n            best_niter = niter;\n            block_size_best = block_size;\n        }\n    }\n\n    block_size_best\n}\n\n#[derive(Debug, PartialEq, Eq, Hash, Clone)]\npub struct GemmParams {\n    pub dts: [DatumType; 3],\n    pub w_batch: usize,\n    pub act_batch: usize,\n    pub m: usize,\n    pub k: usize,\n    pub n: usize,\n    pub w_strides: TVec<isize>,\n    pub act_strides: TVec<isize>,\n    pub out_strides: TVec<isize>,\n}\n\nimpl GemmParams {\n    pub fn compute_gemm_params(\n        dts: [DatumType; 3],\n        w_shape: &[usize],\n        act_shape: &[usize],\n        out_shape: &[usize],\n    ) -> TractResult<GemmParams> {\n        let rank = out_shape.len();\n        let squeezed_w_shape = squeeze_batch_axes(w_shape)?;\n        let squeezed_act_shape = squeeze_batch_axes(act_shape)?;\n        let squeezed_out_shape = squeeze_batch_axes(out_shape)?;\n\n        let w_batch = squeezed_w_shape[0];\n        let act_batch = squeezed_act_shape[0];\n\n        ensure!(squeezed_out_shape[0] == act_batch || squeezed_out_shape[0] == w_batch);\n\n        let m = out_shape[rank - 2];\n        let n = out_shape[rank - 1];\n        let k = act_shape[act_shape.len() - 1];\n\n        ensure!((act_batch % w_batch == 0) || (act_batch == 1));\n        let w_strides = natural_strides(&[w_batch, n, k]);\n        let act_strides = natural_strides(&[act_batch, m, k]);\n        let out_strides = natural_strides(&[act_batch.max(w_batch), m, n]);\n\n        Ok(GemmParams { dts, act_batch, w_batch, m, n, k, act_strides, w_strides, out_strides })\n    }\n}\n\n#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]\npub struct GgmlGemm;\n\n#[derive(Debug, PartialEq, Eq, Hash, Clone)]\npub struct CublasDispatchParams {\n    pub w_batch: usize,\n    pub act_batch: usize,\n    pub m: usize,\n    pub act_offset: usize,\n    pub w_offset: usize,\n    pub c_offset: usize,\n}\n\nimpl CublasDispatchParams {\n    pub fn compute_dispatch_params(params: &GemmParams) -> TractResult<Vec<CublasDispatchParams>> {\n        match (params.act_batch, params.w_batch) {\n            (act_batch, 1) if act_batch != 1 => Ok(vec![CublasDispatchParams {\n                w_batch: 1,\n                act_batch: 1,\n                m: params.m * params.act_batch,\n                act_offset: 0,\n                w_offset: 0,\n                c_offset: 0,\n            }]),\n            (1, w_batch) if w_batch != 1 => Ok((0..w_batch)\n                .map(|w_batch_idx| CublasDispatchParams {\n                    w_batch: 1,\n                    act_batch: 1,\n                    m: params.m,\n                    act_offset: 0,\n                    w_offset: w_batch_idx * params.dts[1].size_of() * params.w_strides[0] as usize,\n                    c_offset: w_batch_idx * params.m * params.n * params.dts[2].size_of(),\n                })\n                .collect()),\n            (act_batch, w_batch) => {\n                ensure!(\n                    act_batch == w_batch,\n                    \"Only support equal batches or either batch == 1 for Cublas MM\"\n                );\n                Ok(vec![CublasDispatchParams {\n                    w_batch: params.w_batch,\n                    act_batch: params.act_batch,\n                    m: params.m,\n                    act_offset: 0,\n                    w_offset: 0,\n                    c_offset: 0,\n                }])\n            }\n        }\n    }\n}\n\nfn kernel_name_mat_vec(dt: DatumType, n_cols: usize, block_size: usize) -> TractResult<String> {\n    Ok(format!(\"ggml_matvec_{}_ncols_{}_bs_{}\", DeviceTensor::tname(dt)?, n_cols, block_size))\n}\n\nfn dispatch_ggml_matvec(\n    stream: &TractCudaStream,\n    weights: &DeviceTensor,\n    activs: &DeviceTensor,\n    output: &DeviceTensor,\n    params: GemmParams,\n) -> TractResult<()> {\n    ensure!(params.act_batch % params.w_batch == 0);\n\n    let w_view = get_cuda_view(weights);\n    let act_view = get_cuda_view(activs);\n    let output_view = get_cuda_view(output);\n\n    let k_div_2 = params.k / 2;\n    let ncols_act_div_2 = params.act_strides[1] / 2;\n    ensure!(k_div_2 == ncols_act_div_2 as usize);\n    let block_size = find_block_size(params.k);\n\n    let batch_ratio = params.act_batch / params.w_batch;\n\n    let kernel_name = kernel_name_mat_vec(params.dts[0], params.m, block_size)?;\n    let mut func = cuda_context().load_pipeline(LibraryName::Ggml, kernel_name)?;\n    let mut launch_args = TractLaunchArgs::new(stream, &func);\n    launch_args.push_view(&w_view);\n    launch_args.push_view(&act_view);\n    launch_args.push_view(&output_view);\n    launch_args.push_i32(k_div_2);\n    launch_args.push_i32(params.act_batch);\n    launch_args.push_i32(params.w_strides[1]);\n    launch_args.push_i32(ncols_act_div_2);\n    launch_args.push_i32(params.out_strides[1]);\n    launch_args.push_i32(batch_ratio);\n    launch_args.push_i32(params.w_strides[0]);\n    launch_args.push_i32(params.act_strides[0]);\n    launch_args.push_i32(params.out_strides[0]);\n\n    let cfg = LaunchConfig {\n        grid_dim: (params.n as _, params.act_batch as _, 1),\n        block_dim: (block_size as _, 1, 1),\n        shared_mem_bytes: (WARP_SIZE * size_of::<f32>()) as u32,\n    };\n\n    unsafe { launch_args.launch(cfg) };\n    Ok(())\n}\n\nfn dispatch_cublas_gemm<F: Datum + Float>(\n    stream: &TractCudaStream,\n    weights: &DeviceTensor,\n    activs: &DeviceTensor,\n    c: &DeviceTensor,\n    params: GemmParams,\n) -> TractResult<()>\nwhere\n    CudaBlas: Gemm<F>,\n{\n    let events = stream.record_profile_events()?;\n    let dispatch_params = CublasDispatchParams::compute_dispatch_params(&params)?;\n    for d in dispatch_params {\n        let act_len = params.act_strides[0] as usize * d.act_batch * params.dts[0].size_of();\n        let w_len = params.w_strides[0] as usize * d.w_batch * params.dts[1].size_of();\n        let act_view = get_sliced_cuda_view(activs, d.act_offset, act_len)?;\n        let w_view = get_sliced_cuda_view(weights, d.w_offset, w_len)?;\n        let mut c_view = get_sliced_cuda_view_mut(\n            c,\n            d.c_offset,\n            params.out_strides[0] as usize * d.act_batch.max(d.w_batch) * params.dts[2].size_of(),\n        )?;\n\n        let cublas_gemm_cfg = cublas::GemmConfig {\n            transa: cublas::sys::cublasOperation_t::CUBLAS_OP_T,\n            transb: cublas::sys::cublasOperation_t::CUBLAS_OP_N,\n            m: params.n as i32,\n            n: d.m as i32,\n            k: params.k as i32,\n            alpha: F::from(1.0f32).unwrap(),\n            lda: params.k as i32,\n            ldb: params.k as i32,\n            beta: F::from(0.0f32).unwrap(),\n            ldc: params.n as i32,\n        };\n\n        let gemm_batched_strided_cfg = cublas::StridedBatchedConfig {\n            gemm: cublas_gemm_cfg,\n            batch_size: d.act_batch as i32,\n            stride_a: params.w_strides[0] as _,\n            stride_b: params.act_strides[0] as _,\n            stride_c: params.out_strides[0] as _,\n        };\n\n        unsafe {\n            stream.cublas().gemm_strided_batched(\n                gemm_batched_strided_cfg,\n                &w_view.transmute::<F>(w_view.len() / size_of::<F>()).unwrap(),\n                &act_view.transmute::<F>(act_view.len() / size_of::<F>()).unwrap(),\n                &mut c_view.transmute_mut::<F>(c_view.len() / size_of::<F>()).unwrap(),\n            )\n        };\n    }\n\n    if let Some((start, end)) = events {\n        stream.finish_profile_entry(start, end)?;\n    }\n    Ok(())\n}\n\nfn kernel_name_q40(\n    params: &GemmParams,\n    mmq_w: usize,\n    mmq_act: usize,\n    fixup: bool,\n) -> TractResult<String> {\n    let need_check = !params.n.is_multiple_of(mmq_act);\n    let fixup_str = if fixup { \"stream_k_fixup_\" } else { \"\" };\n    Ok(format!(\"mul_mat_q40_{fixup_str}{mmq_w}_8_{need_check}\"))\n}\n\nfn find_best_mmq_w(smbpo: usize, m: usize) -> usize {\n    let mut mmq_w_best = 0;\n    let mut ntiles_w_best = usize::MAX;\n\n    let mut mmq_w = 0;\n    while mmq_w <= MMQ_X_MAX && ntiles_w_best > 1 {\n        mmq_w += 8;\n        let granularity = if mmq_w >= 48 { 16 } else { 8 };\n        if (mmq_w % granularity != 0 || mmq_get_nbytes_shared_q40(mmq_w, MMQ_X_MAX) > smbpo) {\n            continue;\n        }\n        let ntiles_w = m.div_ceil(mmq_w);\n        if (ntiles_w < ntiles_w_best) {\n            mmq_w_best = mmq_w;\n            ntiles_w_best = ntiles_w;\n        }\n    }\n    mmq_w_best\n}\n\n#[allow(clippy::too_many_arguments)]\nfn launch_matmul_q40(\n    stream: &TractCudaStream,\n    weights: &CudaView<'_, u8>,\n    quant_activ: &CudaView<'_, u8>,\n    output: &CudaView<'_, u8>,\n    fixup_tens: &CudaView<'_, u8>,\n    params: &GemmParams,\n    act_batch_stride: usize,\n    w_batch_stride: usize,\n    batch_ratio: usize,\n    mmq_w_best: usize,\n    nbytes_shared: usize,\n) -> TractResult<()> {\n    let n_blocks = w_batch_stride / params.n;\n    let kernel_name = kernel_name_q40(params, mmq_w_best, MMQ_X_MAX, false)?;\n\n    let context = cuda_context();\n    let props = context.properties();\n    let func = context.load_pipeline(LibraryName::GgmlQ, kernel_name)?;\n    func.set_attribute(\n        CUfunction_attribute::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,\n        nbytes_shared as i32,\n    )?;\n    let mut launch_args = TractLaunchArgs::new(stream, &func);\n    launch_args.push_view(weights);\n    launch_args.push_view(quant_activ);\n    launch_args.push_view(output);\n    launch_args.push_view(fixup_tens);\n    launch_args.push_i32(params.k);\n    launch_args.push_i32(params.n);\n    launch_args.push_i32(params.m);\n    launch_args.push_i32(n_blocks);\n    launch_args.push_i32(params.m);\n    launch_args.push_i32(params.n);\n    launch_args.push_i32(batch_ratio);\n    launch_args.push_i32(params.act_batch);\n    launch_args.push_i32(w_batch_stride);\n    launch_args.push_i32(act_batch_stride);\n    launch_args.push_i32(params.out_strides[0]);\n\n    let cfg = LaunchConfig {\n        grid_dim: (props.multiProcessorCount as usize as _, 1, 1),\n        block_dim: (WARP_SIZE as _, N_WARPS as _, 1),\n        shared_mem_bytes: nbytes_shared as _,\n    };\n\n    unsafe {\n        launch_args.launch(cfg);\n    }\n    Ok(())\n}\n\nfn launch_fixup_q40(\n    stream: &TractCudaStream,\n    output: &CudaView<'_, u8>,\n    fixup_tens: &CudaView<'_, u8>,\n    params: &GemmParams,\n    mmq_w_best: usize,\n) -> TractResult<()> {\n    let kernel_name = kernel_name_q40(params, mmq_w_best, MMQ_X_MAX, true)?;\n\n    let context = cuda_context();\n    let props = context.properties();\n    let func = context.load_pipeline(LibraryName::GgmlQ, kernel_name)?;\n    let mut launch_args = TractLaunchArgs::new(stream, &func);\n    launch_args.push_view(output);\n    launch_args.push_view(fixup_tens);\n    launch_args.push_i32(params.k);\n    launch_args.push_i32(params.n);\n    launch_args.push_i32(params.m);\n    launch_args.push_i32(params.n);\n    launch_args.push_i32(params.act_batch);\n    launch_args.push_i32(params.out_strides[0]);\n\n    let cfg = LaunchConfig {\n        grid_dim: (props.multiProcessorCount as usize as _, 1, 1),\n        block_dim: (WARP_SIZE as _, N_WARPS as _, 1),\n        shared_mem_bytes: 0,\n    };\n\n    unsafe {\n        launch_args.launch(cfg);\n    }\n    Ok(())\n}\n\nfn dispatch_ggml_matmul_q40(\n    stream: &TractCudaStream,\n    weights: &CudaView<'_, u8>,\n    activs: &CudaView<'_, u8>,\n    output: &CudaView<'_, u8>,\n    params: GemmParams,\n) -> TractResult<()> {\n    ensure!(params.act_batch % params.w_batch == 0);\n\n    let context = cuda_context();\n    let props = context.properties();\n\n    let null_ptr = stream.null()?;\n\n    let padded_k = params.k.next_multiple_of(Q40_ROW_PADDING);\n    let n_blocks = padded_k / Q4_0.block_len(); // padded Q40 weights\n\n    let n_mmq_blocks = padded_k / (Q8_1.block_len() * 4);\n    let act_batch_stride = n_mmq_blocks * params.m * Q8_1.block_bytes();\n    let w_batch_stride = n_blocks * params.n;\n    let batch_ratio = params.act_batch / params.w_batch;\n\n    let mmq_w_best = find_best_mmq_w(props.sharedMemPerBlockOptin, params.m);\n    let nbytes_shared = mmq_get_nbytes_shared_q40(mmq_w_best, MMQ_X_MAX);\n\n    let ntx = params.m.div_ceil(mmq_w_best);\n    let nty = params.n.div_ceil(MMQ_X_MAX);\n\n    let fixup_tensor = {\n        let needs_fixup =\n            !(ntx * nty * params.act_batch).is_multiple_of(props.multiProcessorCount as usize);\n\n        needs_fixup\n            .then(|| {\n                let fixup_shape = props.multiProcessorCount as usize * mmq_w_best * MMQ_X_MAX;\n                unsafe { DeviceTensor::uninitialized_dt(DatumType::F32, &[fixup_shape]) }\n            })\n            .transpose()?\n    };\n\n    let fixup_view = fixup_tensor.as_ref().map(|t| get_cuda_view(t)).unwrap_or(null_ptr.as_view());\n\n    launch_matmul_q40(\n        stream,\n        weights,\n        activs,\n        output,\n        &fixup_view,\n        &params,\n        act_batch_stride,\n        w_batch_stride,\n        batch_ratio,\n        mmq_w_best,\n        nbytes_shared,\n    )?;\n\n    if let Some(ref fixup) = fixup_tensor {\n        launch_fixup_q40(stream, output, &fixup_view, &params, mmq_w_best)?;\n    }\n\n    Ok(())\n}\n\nfn dispatch_ggml_matvec_q40(\n    stream: &TractCudaStream,\n    weights: &CudaView<'_, u8>,\n    activs: &CudaView<'_, u8>,\n    output: &CudaView<'_, u8>,\n    params: GemmParams,\n) -> TractResult<()> {\n    ensure!(params.act_batch % params.w_batch == 0);\n\n    let context = cuda_context();\n    let null_ptr = stream.null::<u8>()?;\n\n    let padded_k = params.k.next_multiple_of(Q40_ROW_PADDING);\n\n    let n_blocks = padded_k / Q4_0.block_len();\n    let stride_col_act = padded_k / Q8_1.block_len();\n    let stride_col_out = params.n;\n    let stride_channel_w = n_blocks * params.n;\n    let stride_channel_act = stride_col_act * params.m;\n    let stride_channel_out = params.m * params.n;\n\n    let batch_ratio = params.act_batch / params.w_batch;\n\n    let func = context.load_pipeline(LibraryName::GgmlQ, format!(\"mul_vec_q40_m_{}\", params.m))?;\n    let mut launch_args = TractLaunchArgs::new(stream, &func);\n    launch_args.push_view(weights);\n    launch_args.push_view(activs);\n    launch_args.push_view(output);\n    launch_args.push_i32(params.k);\n    launch_args.push_i32(params.act_batch);\n    launch_args.push_i32(n_blocks);\n    launch_args.push_i32(stride_col_act);\n    launch_args.push_i32(stride_col_out);\n    launch_args.push_i32(batch_ratio);\n    launch_args.push_i32(stride_channel_w);\n    launch_args.push_i32(stride_channel_act);\n    launch_args.push_i32(stride_channel_out);\n\n    let rows_per_block = if params.m == 1 { 1 } else { 2 };\n    let n_warps = if params.m <= 4 { 4 } else { 2 };\n    let cfg = LaunchConfig {\n        grid_dim: (params.n.div_ceil(rows_per_block) as _, params.act_batch as _, 1 as _),\n        block_dim: (WARP_SIZE as _, n_warps, 1),\n        shared_mem_bytes: 0,\n    };\n\n    launch_args.launch(cfg)\n}\n\nimpl GgmlGemm {\n    fn output_dt(&self, activ_dt: DatumType, weight_dt: DatumType) -> TractResult<DatumType> {\n        ensure!(weight_dt == activ_dt);\n        Ok(activ_dt)\n    }\n\n    pub fn output_shape<D: DimLike + One>(&self, a: &[D], b: &[D]) -> TVec<D> {\n        // A: [b, n, k] B: [b, m, k]\n        let rank = a.len();\n        let mut output: TVec<D> = (0..rank - 2)\n            .map(|ix| if a[ix].is_one() { b[ix].clone() } else { a[ix].clone() })\n            .collect();\n        output.push(a[rank - 2].clone());\n        output.push(b[rank - 2].clone());\n        output\n    }\n\n    pub fn output_facts(\n        &self,\n        shape: &[TDim],\n        a_dt: DatumType,\n        b_dt: DatumType,\n    ) -> TractResult<TVec<TypedFact>> {\n        let out_dt = self.output_dt(a_dt, b_dt)?;\n        ensure!([DatumType::F16, DatumType::F32].contains(&out_dt));\n        Ok(tvec!(out_dt.fact(shape)))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        activs: &DeviceTensor,\n        weights: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        let (act_shape, w_shape) = get_concrete_shapes(activs, weights)?;\n\n        let c_dt = self.output_dt(activs.datum_type(), weights.datum_type())?;\n        let c_shape = self.output_shape(&act_shape, &w_shape);\n        let c = unsafe { DeviceTensor::uninitialized_dt(c_dt, &c_shape)? };\n\n        self.dispatch_eval(stream, activs, weights, &c)?;\n        stream.synchronize()?;\n        Ok(c)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        activs: &DeviceTensor,\n        weights: &DeviceTensor,\n        out: &DeviceTensor,\n    ) -> TractResult<()> {\n        // Note: All the following MM/MV kernels transpose the output.\n        // We name 'm' and 'n' according to the output\n        // This means Weights: [n, k] x Activ: [m, k] => C: [m, n]\n\n        let (act_shape, w_shape) = get_concrete_shapes(activs, weights)?;\n\n        ensure!(out.shape() == self.output_shape(&act_shape, &w_shape).as_slice());\n\n        if out.shape().iter().product::<usize>() == 0 {\n            return Ok(());\n        }\n\n        let params = GemmParams::compute_gemm_params(\n            [activs.datum_type(), weights.datum_type(), out.datum_type()],\n            &w_shape,\n            &act_shape,\n            out.shape(),\n        )?;\n\n        if get_quant_fact(weights, &Q4_0).is_some() {\n            let act_view = get_cuda_view(activs);\n            let w_view = get_cuda_view(weights);\n            let out_view = get_cuda_view(out);\n            if params.m <= 8 {\n                dispatch_ggml_matvec_q40(stream, &w_view, &act_view, &out_view, params)?;\n            } else {\n                dispatch_ggml_matmul_q40(stream, &w_view, &act_view, &out_view, params)?;\n            }\n        } else if (params.k % 2 == 0) && params.m <= 8 {\n            dispatch_ggml_matvec(stream, weights, activs, out, params)?;\n        } else if activs.datum_type() == DatumType::F32 {\n            dispatch_cublas_gemm::<f32>(stream, weights, activs, out, params)?;\n        } else {\n            ensure!(activs.datum_type() == F16);\n            dispatch_cublas_gemm::<f16>(stream, weights, activs, out, params)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use crate::kernels::matmul::quant_act_q81::GgmlQuantQ81;\n    use crate::ops::GgmlQuantQ81Fact;\n    use crate::utils::pad_q40;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\n    use tract_core::tract_data::itertools::Itertools;\n    use tract_core::tract_linalg::block_quant::{BlockQuant, BlockQuantStorage, Q4_0};\n    use tract_gpu::tensor::IntoDevice;\n\n    pub(crate) fn run_mmm_test_case(\n        (act_batch, w_batch, m, k, n): (usize, usize, usize, usize, usize),\n        transpose_a: bool,\n        transpose_b: bool,\n        act_dt: DatumType,\n        w_dt: DatumType,\n    ) -> TractResult<()> {\n        crate::with_cuda_stream(|stream| {\n            let act_shape = if !transpose_a { [act_batch, m, k] } else { [act_batch, k, m] };\n            let w_shape = if !transpose_b { [w_batch, k, n] } else { [w_batch, n, k] };\n            let mut activs = if act_dt == DatumType::F16 {\n                Tensor::from_shape(\n                    &act_shape,\n                    &(0..act_batch * m * k)\n                        .map(|f| f16::from_f32(f as f32 / (act_batch * m * k) as f32))\n                        .collect::<Vec<_>>(),\n                )?\n            } else {\n                Tensor::from_shape(\n                    &act_shape,\n                    &(0..act_batch * m * k)\n                        .map(|f| f as f32 / (act_batch * m * k) as f32)\n                        .collect::<Vec<_>>(),\n                )?\n            };\n\n            let mut weights = if w_dt == DatumType::F16 {\n                Tensor::from_shape(\n                    &w_shape,\n                    &(0..w_batch * k * n)\n                        .map(|f| f16::from_f32(f as f32 / (w_batch * n * k) as f32))\n                        .collect::<Vec<_>>(),\n                )?\n            } else {\n                Tensor::from_shape(\n                    &w_shape,\n                    &(0..w_batch * k * n)\n                        .map(|f| f as f32 / (w_batch * m * k) as f32)\n                        .collect::<Vec<_>>(),\n                )?\n            };\n\n            let cuda_output = GgmlGemm.eval(\n                stream,\n                &activs.clone().into_device()?,\n                &weights.clone().into_device()?,\n            )?;\n\n            let matmul = PrefixMatMul {\n                transpose_a,\n                transpose_b,\n                transpose_c: false,\n                quantize_output: None,\n                operating_dt: Some(act_dt),\n            };\n\n            // Compare to full precision\n            if act_dt == DatumType::F16 && !(w_dt == DatumType::F16) {\n                activs = activs.clone().cast_to_dt(DatumType::F32).unwrap().into_owned();\n            }\n            if w_dt == DatumType::F16 && !(act_dt == DatumType::F16) {\n                weights = weights.clone().cast_to_dt(DatumType::F32).unwrap().into_owned();\n            }\n\n            let output = args_1!(matmul.eval(tvec![activs.into_tvalue(), weights.into_tvalue()])?);\n            cuda_output.to_host()?.close_enough(&output, Approximation::VeryApproximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_mat_vec() -> TractResult<()> {\n        // f32_f32\n        run_mmm_test_case((2, 1, 1, 2, 1), false, true, F32, F32)?;\n        run_mmm_test_case((2, 1, 3, 60, 2), false, true, F32, F32)?;\n        run_mmm_test_case((2, 2, 2, 128, 7), false, true, F32, F32)?;\n        run_mmm_test_case((4, 1, 7, 2, 1), false, true, F32, F32)?;\n\n        //// f16_f16\n        run_mmm_test_case((1, 1, 5, 2, 1), false, true, F16, F16)?;\n        run_mmm_test_case((2, 1, 8, 62, 2), false, true, F16, F16)?;\n        run_mmm_test_case((2, 2, 2, 128, 9), false, true, F16, F16)?;\n        run_mmm_test_case((4, 1, 1, 128, 9), false, true, F16, F16)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_mat_mul() -> TractResult<()> {\n        // f32_f32\n        run_mmm_test_case((1, 1, 9, 4, 2), false, true, F32, F32)?;\n        run_mmm_test_case((1, 1, 11, 2, 3), false, true, F32, F32)?;\n        run_mmm_test_case((2, 2, 15, 1, 2), false, true, F32, F32)?;\n        run_mmm_test_case((2, 1, 10, 32, 2), false, true, F32, F32)?;\n        run_mmm_test_case((1, 2, 12, 1, 2), false, true, F32, F32)?;\n\n        // f16_f16\n        run_mmm_test_case((1, 1, 12, 7, 2), false, true, F16, F16)?;\n        run_mmm_test_case((1, 1, 9, 61, 2), false, true, F16, F16)?;\n        run_mmm_test_case((2, 1, 10, 127, 9), false, true, F16, F16)?;\n        run_mmm_test_case((1, 2, 16, 127, 9), false, true, F16, F16)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_squeeze_batch_axes() -> TractResult<()> {\n        assert_eq!(squeeze_batch_axes(&[1, 2, 3, 4])?, tvec![2, 3, 4]);\n        assert_eq!(squeeze_batch_axes(&[3, 2, 3, 4])?, tvec![6, 3, 4]);\n        assert_eq!(squeeze_batch_axes(&[3, 1, 2, 3, 4])?, tvec![6, 3, 4]);\n        assert!(squeeze_batch_axes(&[1]).is_err());\n        assert_eq!(squeeze_batch_axes(&[1, 1, 3, 4])?, tvec![1, 3, 4]);\n        Ok(())\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn mmm_ggml_prop_f32(pb in <MmmProblem<f32>>::arbitrary_with(\n            MmmProblemParams {\n                force_k_as_inner_axis: true,\n                q4_0_weights: false,\n            }\n        )) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::Approximate).is_ok())\n        }\n\n        #[test]\n        fn mmm_ggml_prop_f16(pb in <MmmProblem<f16>>::arbitrary_with(\n            MmmProblemParams {\n                force_k_as_inner_axis: true,\n                q4_0_weights: false,\n            }\n        )) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::VeryApproximate).is_ok())\n        }\n\n        #[test]\n        fn mmm_ggml_prop_q4(pb in <MmmProblem<f32>>::arbitrary_with(\n            MmmProblemParams {\n                force_k_as_inner_axis: true,\n                q4_0_weights: true,\n            }\n        )) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::VeryApproximate).is_ok())\n        }\n    }\n\n    #[derive(Default, Debug, Clone)]\n    pub struct MmmProblemParams {\n        pub force_k_as_inner_axis: bool,\n        pub q4_0_weights: bool,\n    }\n\n    #[derive(Debug)]\n    pub struct MmmProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        f32: AsPrimitive<F>,\n    {\n        pub b: usize,\n        pub m: usize,\n        pub k: usize,\n        pub n: usize,\n        pub lhs: Vec<F>,\n        pub transpose_lhs: bool,\n        pub rhs: Vec<F>,\n        pub transpose_rhs: bool,\n        pub q4_0: bool,\n    }\n\n    impl<F> Arbitrary for MmmProblem<F>\n    where\n        F: Datum + Float,\n        f32: AsPrimitive<F>,\n    {\n        type Parameters = MmmProblemParams;\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(params: MmmProblemParams) -> Self::Strategy {\n            (1usize..4, 1usize..16, 1usize..128, 1usize..16)\n                .prop_flat_map(move |(b, m, mut k, n)| {\n                    if params.q4_0_weights {\n                        k = k.div_ceil(32) * 32\n                    };\n\n                    let lhs_len = b * m * k;\n                    let rhs_len = b * n * k;\n                    let datum = (0f32..1f32).prop_map(|x| x.as_());\n                    (\n                        Just(b),\n                        Just(m),\n                        Just(k),\n                        Just(n),\n                        vec(datum.clone(), lhs_len..=lhs_len),\n                        proptest::bool::ANY,\n                        vec(datum, rhs_len..=rhs_len),\n                        proptest::bool::ANY,\n                    )\n                })\n                .prop_map(move |(b, m, k, n, lhs, mut transpose_lhs, rhs, mut transpose_rhs)| {\n                    if params.force_k_as_inner_axis {\n                        (transpose_lhs, transpose_rhs) = (false, true);\n                    }\n                    Self {\n                        b,\n                        m,\n                        k,\n                        n,\n                        lhs,\n                        transpose_lhs,\n                        rhs,\n                        transpose_rhs,\n                        q4_0: params.q4_0_weights,\n                    }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> MmmProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        f32: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let matmul = PrefixMatMul {\n                transpose_a: self.transpose_lhs,\n                transpose_b: self.transpose_rhs,\n                transpose_c: false,\n                quantize_output: None,\n                operating_dt: Some(F::datum_type()),\n            };\n\n            let lhs_tensor = if self.transpose_lhs {\n                Tensor::from_shape(&[self.b, self.k, self.m], &self.lhs)?\n            } else {\n                Tensor::from_shape(&[self.b, self.m, self.k], &self.lhs)?\n            };\n            let mut rhs_tensor = if self.transpose_rhs {\n                Tensor::from_shape(&[self.b, self.n, self.k], &self.rhs)?\n            } else {\n                Tensor::from_shape(&[self.b, self.k, self.n], &self.rhs)?\n            };\n\n            if self.q4_0 {\n                rhs_tensor = Q4_0.simulate_precision_loss(rhs_tensor, 2)?\n            };\n            let output = matmul.eval(tvec![lhs_tensor.into_tvalue(), rhs_tensor.into_tvalue()])?;\n\n            Ok(output[0].clone().into_tensor())\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            crate::with_cuda_stream(|stream| {\n                let lhs = if self.transpose_lhs {\n                    Tensor::from_shape(&[self.b, self.k, self.m], &self.lhs)?.into_device()?\n                } else {\n                    let mut lhs =\n                        Tensor::from_shape(&[self.b, self.m, self.k], &self.lhs)?.into_device()?;\n                    if self.q4_0 {\n                        let act_shape_tdim: ShapeFact = tvec![\n                            TDim::Val(self.b as i64),\n                            TDim::Val(self.m as i64),\n                            TDim::Val(self.k as i64)\n                        ]\n                        .into();\n\n                        let io_facts = GgmlQuantQ81Fact {\n                            in_fact: act_shape_tdim.clone(),\n                            out_fact: GgmlQuantQ81::output_shape_fact(&act_shape_tdim)?,\n                        };\n\n                        lhs = GgmlQuantQ81.eval(stream, &lhs, io_facts)?;\n                    }\n                    lhs\n                };\n                let rhs = if self.transpose_rhs {\n                    if !self.q4_0 {\n                        Tensor::from_shape(&[self.b, self.n, self.k], &self.rhs)?\n                    } else {\n                        let w_quant = Q4_0.quant_f32(\n                            &self\n                                .rhs\n                                .clone()\n                                .into_iter()\n                                .map(|x| x.to_f32().unwrap())\n                                .collect_vec(),\n                        )?;\n                        let bqs = BlockQuantStorage::new(\n                            Box::new(Q4_0),\n                            self.b * self.n,\n                            self.k,\n                            Arc::new(w_quant),\n                        )?;\n                        let padded_q40 = pad_q40(&bqs, self.b * self.n, self.k)?;\n                        let padded_k = self.k.next_multiple_of(crate::Q40_ROW_PADDING);\n                        padded_q40\n                            .into_tensor_with_shape(f32::datum_type(), &[self.b, self.n, padded_k])\n                    }\n                } else {\n                    Tensor::from_shape(&[self.b, self.k, self.n], &self.rhs)?\n                }\n                .into_device()?;\n\n                let c = GgmlGemm.eval(stream, &lhs, &rhs)?;\n                Ok(c.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/matmul/quant_act_q81.rs",
    "content": "use cudarc::driver::{LaunchConfig, PushKernelArg};\nuse derive_new::new;\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_core::tract_linalg::block_quant::{BlockQuant, Q8_1};\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::Q40_ROW_PADDING;\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::matmul::{MMQ_X_MAX, squeeze_batch_axes};\nuse crate::kernels::{LibraryName, get_cuda_view};\nuse crate::ops::GgmlQuantQ81Fact;\n\npub(crate) const QK8_1: usize = 32;\n\npub(crate) const QUANTIZE_BLOCK_SIZE: usize = 256;\npub(crate) const QUANTIZE_BLOCK_SIZE_MMQ: usize = 128;\n\n#[derive(Debug, Clone, new, PartialEq, Eq, Hash)]\npub struct GgmlQuantQ81;\n\nimpl fmt::Display for GgmlQuantQ81 {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl GgmlQuantQ81 {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn output_shape_fact(shape: &ShapeFact) -> TractResult<ShapeFact> {\n        let mut o_shape = shape.dims().to_owned();\n        let rank = o_shape.len();\n        let k = o_shape[rank - 1].as_i64().context(\"Expected concrete k\")? as usize;\n        let padded_k = k.next_multiple_of(Q40_ROW_PADDING);\n        o_shape[rank - 1] = TDim::Val(padded_k as i64);\n        o_shape[rank - 2] += (MMQ_X_MAX * 4 * Q8_1.block_bytes()).div_ceil(padded_k);\n\n        Ok(ShapeFact::from_dims(o_shape))\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        let context = cuda_context();\n        let i_view = get_cuda_view(input);\n        let o_view = get_cuda_view(output);\n\n        let rank = input.rank();\n        let squeezed_shape = squeeze_batch_axes(input.shape())?;\n\n        let a_batch = squeezed_shape[0];\n        let m = squeezed_shape[1];\n        let k = squeezed_shape[2];\n\n        let padded_k = k.next_multiple_of(Q40_ROW_PADDING);\n        let mut out_shape = input.shape().to_owned();\n        out_shape[rank - 1] = padded_k;\n        if m > 8 {\n            let in_strides = input.strides();\n            let fast_path_str = if in_strides[rank - 1] == 1 { \"fast_\" } else { \"\" };\n            let func = cuda_context().load_pipeline(\n                LibraryName::Quant,\n                format!(\"quantize_mmq_q8_1_{fast_path_str}nd{}\", input.rank()),\n            )?;\n            let mut launch_args = TractLaunchArgs::new(stream, &func);\n            launch_args.push_view(&i_view);\n            launch_args.push_view(&o_view);\n            launch_args.push::<u64>(k);\n            launch_args.push_slice_i32(in_strides);\n            launch_args.push_slice_i32(&out_shape[1..]);\n\n            let cfg = LaunchConfig {\n                grid_dim: (\n                    m as _,\n                    padded_k.div_ceil(4 * QUANTIZE_BLOCK_SIZE_MMQ) as _,\n                    a_batch as _,\n                ),\n                block_dim: (128, 1, 1),\n                shared_mem_bytes: 0,\n            };\n            unsafe { launch_args.launch(cfg) };\n        } else {\n            let func = context\n                .load_pipeline(LibraryName::Quant, format!(\"quantize_q8_1_nd{}\", input.rank()))?;\n            let mut launch_args = TractLaunchArgs::new(stream, &func);\n            launch_args.push_view(&i_view);\n            launch_args.push_view(&o_view);\n            launch_args.push::<u64>(k);\n            launch_args.push_slice_i32(input.strides());\n            launch_args.push_slice_i32(&out_shape[1..]);\n\n            let cfg = LaunchConfig {\n                grid_dim: (padded_k.div_ceil(QUANTIZE_BLOCK_SIZE) as _, m as _, a_batch as _),\n                block_dim: (QUANTIZE_BLOCK_SIZE as _, 1, 1),\n                shared_mem_bytes: 0,\n            };\n            unsafe { launch_args.launch(cfg) };\n        }\n        Ok(())\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        output_fact: GgmlQuantQ81Fact,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_exotic(Box::new(output_fact))? };\n        self.dispatch_eval(stream, input, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/mod.rs",
    "content": "#![allow(unused)]\n\npub mod array;\npub mod binary;\npub mod conv;\npub mod conv_cudnn;\npub mod element_wise;\npub mod flash_attn;\npub mod ggml_flash_attn;\nmod iff;\npub(crate) mod launch_args;\npub mod matmul;\npub mod nn;\npub(crate) mod utils;\n\nuse std::env;\nuse std::path::{Path, PathBuf};\nuse std::sync::OnceLock;\n\nuse crate::ops::GgmlQuantQ81Fact;\nuse crate::tensor::{CudaBuffer, CudaTensor};\nuse anyhow::{bail, ensure};\nuse cudarc::driver::{CudaView, CudaViewMut};\npub use iff::Iff;\nuse tract_core::internal::ExoticFact;\nuse tract_core::prelude::{TDim, TractResult};\nuse tract_core::tract_linalg::block_quant::{BlockQuant, BlockQuantFact, Q4_0, Q8_1};\nuse tract_gpu::tensor::{DeviceTensor, OwnedDeviceTensor};\nuse tract_gpu::utils::as_q40_tensor;\n\nconst MAX_THREADS: usize = 1024;\nconst WARP_SIZE: usize = 32;\n\nstatic CUBIN_FOLDER: OnceLock<PathBuf> = OnceLock::new();\n\npub fn cubin_dir() -> &'static Path {\n    CUBIN_FOLDER\n        .get_or_init(|| {\n            dirs::cache_dir()\n                .unwrap_or_else(|| \".cache\".into())\n                .join(\"tract\")\n                .join(env!(\"CARGO_PKG_VERSION\"))\n                .join(\"cuda\")\n                .join(\"cubins\")\n        })\n        .as_path()\n}\n\nconst ELEMENT_WISE_OPS: &str = include_str!(\"cu/element_wise.cu\");\nconst BINARY_OPS: &str = include_str!(\"cu/binary.cu\");\nconst ARRAY_OPS: &str = include_str!(\"cu/array.cu\");\nconst NN_OPS: &str = include_str!(\"cu/nn.cu\");\nconst CNN_OPS: &str = include_str!(\"cu/cnn.cu\");\nconst GGML_MM_MV: &str = include_str!(\"cu/mm_mv.cu\");\nconst GGML_MM_MV_Q: &str = include_str!(\"cu/mm_mv_q.cu\");\nconst GGML_QUANTIZE: &str = include_str!(\"cu/quantize.cu\");\nconst GGML_FLASH_ATTN: &str = include_str!(\"cu/ggml_flash_attn.cu\");\nconst FLASH_ATTN: &str = include_str!(\"cu/flash_attn.cu\");\npub const COMMON_H: &str = include_str!(\"cu/common.cuh\");\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum LibraryName {\n    ElementWise,\n    Binary,\n    Array,\n    NN,\n    Cnn,\n    Ggml,\n    GgmlQ,\n    Quant,\n    GgmlFlashAttn,\n    FlashAttn,\n}\n\nfn fnv1a64(text: &str) -> u64 {\n    const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;\n    const FNV_PRIME: u64 = 0x00000100000001B3;\n\n    let mut hash = FNV_OFFSET_BASIS;\n    for b in text.as_bytes() {\n        hash ^= *b as u64;\n        hash = hash.wrapping_mul(FNV_PRIME);\n    }\n    hash\n}\n\nimpl LibraryName {\n    pub const ALL: [LibraryName; 10] = [\n        Self::FlashAttn,\n        Self::GgmlFlashAttn,\n        Self::ElementWise,\n        Self::Binary,\n        Self::Array,\n        Self::NN,\n        Self::Cnn,\n        Self::Ggml,\n        Self::GgmlQ,\n        Self::Quant,\n    ];\n\n    pub fn content(&self) -> &str {\n        match self {\n            Self::ElementWise => ELEMENT_WISE_OPS,\n            Self::Binary => BINARY_OPS,\n            Self::Array => ARRAY_OPS,\n            Self::NN => NN_OPS,\n            Self::Cnn => CNN_OPS,\n            Self::Ggml => GGML_MM_MV,\n            Self::GgmlQ => GGML_MM_MV_Q,\n            Self::Quant => GGML_QUANTIZE,\n            Self::GgmlFlashAttn => GGML_FLASH_ATTN,\n            Self::FlashAttn => FLASH_ATTN,\n        }\n    }\n\n    pub fn cubin_path(&self) -> PathBuf {\n        let basename = match self {\n            Self::ElementWise => \"element_wise\",\n            Self::Binary => \"binary\",\n            Self::Array => \"array\",\n            Self::NN => \"nn\",\n            Self::Cnn => \"cnn\",\n            Self::Ggml => \"mm_mv\",\n            Self::GgmlQ => \"mm_mv_q\",\n            Self::Quant => \"quantize\",\n            Self::GgmlFlashAttn => \"flash_attn\",\n            Self::FlashAttn => \"minimal_flash_attn\",\n        };\n        let hash = fnv1a64(self.content());\n        cubin_dir().join(format!(\"{}_{}.cubin\", basename, hash))\n    }\n}\n\npub use tract_gpu::utils::BroadcastKind;\n\nfn tensor_size(t: &DeviceTensor) -> usize {\n    let exotic_fact: Option<&dyn ExoticFact> = match t {\n        DeviceTensor::Owned(ot) => {\n            let cuda_tensor =\n                ot.downcast_ref::<CudaTensor>().expect(\"Non Cuda-Tensor in a Cuda Context\");\n            cuda_tensor.exotic_fact()\n        }\n        DeviceTensor::ArenaView(av) => av.exotic_fact(),\n    };\n\n    if let Some(of) = exotic_fact {\n        of.buffer_sizes()\n            .iter()\n            .sum::<TDim>()\n            .as_i64()\n            .expect(\"Symbols should be resolved at this point\") as usize\n    } else {\n        t.len() * t.datum_type().size_of()\n    }\n}\n\npub fn get_cuda_view(t: &DeviceTensor) -> CudaView<'_, u8> {\n    let size = tensor_size(t);\n    get_sliced_cuda_view(t, 0, size).unwrap()\n}\n\n// NOTE: offset and len are in bytes\npub fn get_sliced_cuda_view(\n    t: &DeviceTensor,\n    offset: usize,\n    len: usize,\n) -> TractResult<CudaView<'_, u8>> {\n    ensure!(offset + len <= tensor_size(t));\n    let buffer = t.device_buffer().downcast_ref::<CudaBuffer>().unwrap();\n    let offset = t.buffer_offset::<usize>() + offset;\n    Ok(buffer.slice(offset..(offset + len)))\n}\n\npub fn get_cuda_view_mut(t: &DeviceTensor) -> CudaViewMut<'_, u8> {\n    let size = t.len() * t.datum_type().size_of();\n    get_sliced_cuda_view_mut(t, 0, size).unwrap()\n}\n\n// NOTE: offset and len are in bytes\npub fn get_sliced_cuda_view_mut(\n    t: &DeviceTensor,\n    offset: usize,\n    len: usize,\n) -> TractResult<CudaViewMut<'_, u8>> {\n    ensure!(offset + len <= t.len() * t.datum_type().size_of());\n    let buffer: &CudaBuffer = t.device_buffer().downcast_ref::<CudaBuffer>().unwrap();\n    let offset = t.buffer_offset::<usize>() + offset;\n    let ptr: *const CudaBuffer = buffer;\n    let mut_buffer: &mut CudaBuffer = unsafe { (ptr as *mut CudaBuffer).as_mut().unwrap() };\n    Ok(mut_buffer.inner.slice_mut(offset..(offset + len)))\n}\n"
  },
  {
    "path": "cuda/src/kernels/nn/apply_rope.rs",
    "content": "use crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::utils::compute_broadcast_strides;\nuse crate::kernels::{BroadcastKind, LibraryName, get_cuda_view, utils};\nuse anyhow::ensure;\nuse cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct ApplyRope;\n\nimpl fmt::Display for ApplyRope {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl ApplyRope {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn is_supported_broadcast(broadcast_kind: BroadcastKind) -> bool {\n        matches!(broadcast_kind, BroadcastKind::Nd2 | BroadcastKind::Nd3 | BroadcastKind::Nd4)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType, broadcast_kind: BroadcastKind) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for cuda apply rope\", dt);\n        ensure!(\n            Self::is_supported_broadcast(broadcast_kind),\n            \"Unsupported broadcast kind {:?} for cuda apply rope\",\n            broadcast_kind\n        );\n        let tname = DeviceTensor::tname(dt)?;\n        let broadcast_name = broadcast_kind.name();\n        Ok(format!(\"apply_rope_{broadcast_name}_{tname}\"))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        cos: &DeviceTensor,\n        sin: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, cos, sin, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        cos: &DeviceTensor,\n        sin: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(input.datum_type() == cos.datum_type());\n        ensure!(input.datum_type() == sin.datum_type());\n\n        ensure!(cos.shape() == sin.shape());\n\n        ensure!(input.rank() >= 2 && input.rank() <= 4);\n        ensure!(cos.rank() <= input.rank());\n\n        let padded_shape = [&tvec![1; input.rank() - cos.rank()], cos.shape()].concat();\n        let (padded_cos, padded_sin) =\n            (cos.reshaped(padded_shape.clone().into())?, sin.reshaped(padded_shape.into())?);\n\n        ensure!(\n            input.shape()[input.rank() - 1].is_multiple_of(2),\n            \"Rotate half required most inner dimension to be a multiple of 2: {:?}\",\n            input.shape()\n        );\n\n        let cos_sin_strides =\n            compute_broadcast_strides::<usize>(padded_cos.shape(), padded_sin.strides())?;\n\n        let broadcast_kind = BroadcastKind::from_rank(input.rank())\n            .with_context(|| format!(\"Unsupported rank for ApplyRope op: {:?}\", input.shape(),))?;\n\n        let kernel_name = self.kernel_name(input.datum_type(), broadcast_kind)?;\n\n        let i_view = get_cuda_view(input);\n        let cos_view = get_cuda_view(&padded_cos);\n        let sin_view = get_cuda_view(&padded_sin);\n        let o_view = get_cuda_view(output);\n\n        let func = cuda_context().load_pipeline(LibraryName::NN, kernel_name)?;\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&cos_view);\n        launch_args.push_view(&sin_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_slice_i32(input.shape());\n        launch_args.push_slice_i32(input.strides());\n        launch_args.push_slice_i32(&cos_sin_strides);\n        launch_args.push_slice_i32(output.strides());\n\n        let shape = input.shape();\n\n        let block_dim = 32;\n        let mut grid = match shape.len() {\n            0 => panic!(\"Unexpected empty shape while build grid size\"),\n            1 => (shape[0] as _, 1, 1),\n            2 => (shape[1] as _, shape[0] as _, 1),\n            3.. => (\n                shape[shape.len() - 1],\n                shape[shape.len() - 2],\n                (shape[..shape.len() - 2].iter().product::<usize>()),\n            ),\n        };\n        grid.0 /= 2;\n\n        let cfg = LaunchConfig {\n            grid_dim: (\n                grid.0.div_ceil(block_dim) as _,\n                grid.1.div_ceil(block_dim) as _,\n                grid.2 as _,\n            ),\n            block_dim: (block_dim as _, block_dim as _, 1),\n            shared_mem_bytes: 0,\n        };\n\n        launch_args.launch(cfg)\n    }\n}\n\npub fn cuda_apply_rope_dispatch(\n    input: &DeviceTensor,\n    cos: &DeviceTensor,\n    sin: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| ApplyRope.dispatch_eval(stream, input, cos, sin, output))\n}\n\ncrate::register_cuda_op!(tract_transformers::ops::apply_rope::ApplyRope, |source, node, _op| {\n    rule_if!(ApplyRope::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::apply_rope::GpuApplyRope::new(\n        \"Cuda\",\n        cuda_apply_rope_dispatch,\n    ))))\n});\n\n#[cfg(test)]\nmod tests {\n    use std::f32::consts::PI;\n\n    use super::*;\n    use tract_core::internal::Tensor;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_transformers::ops::apply_rope;\n\n    fn run_test_case(shape: &[usize]) -> TractResult<()> {\n        crate::with_cuda_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len).map(|f| f as f32 / 1000.0).collect::<Vec<_>>(),\n            )?;\n\n            let cos =\n                Tensor::from_shape(shape, &(0..len).map(|f| (f as f32).cos()).collect::<Vec<_>>())?;\n\n            let sin =\n                Tensor::from_shape(shape, &(0..len).map(|f| (f as f32).sin()).collect::<Vec<_>>())?;\n\n            let cuda_a = a.clone().into_device()?;\n            let cuda_sin = sin.clone().into_device()?;\n            let cuda_cos = cos.clone().into_device()?;\n\n            let cpu_output = apply_rope::ApplyRope.eval(tvec![\n                a.clone().into(),\n                cos.clone().into(),\n                sin.clone().into(),\n            ])?[0]\n                .clone()\n                .into_tensor();\n            let cuda_output = ApplyRope.eval(stream, &cuda_a, &cuda_cos, &cuda_sin)?;\n\n            cpu_output\n                .close_enough(&cuda_output.to_host()?.into_tensor(), Approximation::Approximate)\n                .with_context(|| {\n                    format!(\n                        \"Input: {:?} Cpu: {:?}, Cuda: {:?}\",\n                        a.dump(true),\n                        cpu_output.dump(true),\n                        cuda_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_apply_rope() -> TractResult<()> {\n        run_test_case(&[2, 1, 2, 2])?;\n        run_test_case(&[2, 4, 4])?;\n        run_test_case(&[2, 1, 512, 10])?;\n        run_test_case(&[8, 8])?;\n        run_test_case(&[1, 10, 512, 24])?;\n        run_test_case(&[3, 10, 512, 24])?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/nn/gelu_approximate.rs",
    "content": "use cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{LibraryName, get_cuda_view};\n\n#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]\npub struct GeluApproximate {\n    pub fast_impl: bool,\n}\n\nimpl GeluApproximate {\n    pub fn fast() -> Self {\n        Self { fast_impl: true }\n    }\n\n    pub fn accurate() -> Self {\n        Self { fast_impl: false }\n    }\n\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for cuda geluop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        if self.fast_impl {\n            Ok(format!(\"gelu_approx_fast_{tname}\"))\n        } else {\n            Ok(format!(\"gelu_approx_{tname}\"))\n        }\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(output.shape() == input.shape());\n        ensure!(output.datum_type() == input.datum_type());\n\n        let kernel_name = self.kernel_name(input.datum_type())?;\n\n        let i_view = get_cuda_view(input);\n        let o_view = get_cuda_view(output);\n        let len = output.len();\n\n        let func = cuda_context().load_pipeline(LibraryName::NN, kernel_name)?;\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_i32(len);\n\n        let cfg = LaunchConfig::for_num_elems(input.len() as _);\n        unsafe {\n            launch_args.launch(cfg);\n        }\n        Ok(())\n    }\n}\n\npub fn cuda_gelu_approximate_dispatch(\n    fast_impl: bool,\n    input: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| {\n        GeluApproximate { fast_impl }.dispatch_eval(stream, input, output)\n    })\n}\n\n// GeluApproximate is an ElementWiseMiniOp, so we register under ElementWiseOp's TypeId.\ncrate::register_cuda_op!(tract_core::ops::element_wise::ElementWiseOp, |source, node, op| {\n    rule_if_some!(\n        ew = op.0.downcast_ref::<tract_transformers::ops::gelu_approximate::GeluApproximate>()\n    );\n    rule_if!(GeluApproximate::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::gelu_approximate::GpuGeluApproximate::new(\n        ew.fast_impl,\n        \"Cuda\",\n        cuda_gelu_approximate_dispatch,\n    ))))\n});\n\n#[cfg(test)]\nmod tests {\n\n    use super::*;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_transformers::ops::gelu_approximate;\n\n    fn test_case<F>(\n        gelu_approx: GeluApproximate,\n        shape: &[usize],\n        offset: f32,\n        scale: f32,\n        approximate: Approximation,\n    ) -> TractResult<()>\n    where\n        F: Float + Datum,\n        usize: AsPrimitive<f32>,\n        f32: AsPrimitive<F>,\n    {\n        crate::with_cuda_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v * scale + offset).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu_output = gelu_approximate::gelu_approximate(false)\n                .eval(tvec![a.to_host()?.into_tvalue()])?[0]\n                .clone()\n                .into_tensor();\n            let cuda_output = gelu_approx.eval(stream, &a)?;\n\n            cpu_output\n                .close_enough(&cuda_output.to_host()?.into_tensor(), approximate)\n                .with_context(|| {\n                    format!(\n                        \"Input: {:?}, scale: {:?} Cpu: {:?}, Cuda: {:?}\",\n                        a.to_host().and_then(|it| it.dump(true)),\n                        scale,\n                        cpu_output.dump(true),\n                        cuda_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_gelu_approx() -> TractResult<()> {\n        test_case::<f32>(\n            GeluApproximate::accurate(),\n            &[4, 4],\n            -0.0,\n            1.0 / 100.0,\n            Approximation::Approximate,\n        )?;\n        test_case::<f32>(\n            GeluApproximate::accurate(),\n            &[4, 4],\n            -6.0,\n            1.0 / 1000.0,\n            Approximation::Approximate,\n        )?;\n        test_case::<f16>(\n            GeluApproximate::accurate(),\n            &[4, 4],\n            -6.0,\n            1.0 / 1000.0,\n            Approximation::SuperApproximate,\n        )?;\n        Ok(())\n    }\n    #[test]\n    fn test_gelu_approx_fast() -> TractResult<()> {\n        test_case::<f32>(\n            GeluApproximate::fast(),\n            &[4, 4],\n            -0.0,\n            1.0 / 100.0,\n            Approximation::SuperApproximate,\n        )?;\n        test_case::<f32>(\n            GeluApproximate::fast(),\n            &[4, 4],\n            -6.0,\n            1.0 / 1000.0,\n            Approximation::SuperApproximate,\n        )?;\n        test_case::<f16>(\n            GeluApproximate::fast(),\n            &[4, 4],\n            -6.0,\n            1.0 / 1000.0,\n            Approximation::SuperApproximate,\n        )?;\n        Ok(())\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn gelu_approx_prop_f32(pb in any::<GeluProblem<f32>>()) {\n            fn run(pb: GeluProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn gelu_approx_prop_f16(pb in any::<GeluProblem<f16>>()) {\n            fn run(pb: GeluProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct GeluProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub shape: Vec<usize>,\n        pub input: Vec<F>,\n    }\n\n    impl<F> Arbitrary for GeluProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            (0usize..3, 0usize..3)\n                .prop_flat_map(|(left, right)| {\n                    let shape_len = usize::min(left + right + 1, 4);\n                    let shape = 1usize..10;\n                    vec(shape, shape_len..=shape_len)\n                })\n                .prop_map(|shape| {\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { shape, input }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> GeluProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n\n            let cpu_output = gelu_approximate::gelu_approximate(false)\n                .eval(tvec![a.into_tvalue()])?[0]\n                .clone()\n                .into_tensor();\n\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            crate::with_cuda_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let cuda_output = GeluApproximate::accurate().eval(stream, &a)?;\n                Ok(cuda_output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/nn/leaky_relu.rs",
    "content": "use cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{LibraryName, get_cuda_view};\n\n#[derive(Debug, Clone, Default, PartialEq)]\npub struct LeakyRelu;\n\nimpl LeakyRelu {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for cuda geluop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        Ok(format!(\"leaky_relu_{tname}\"))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        alpha: f32,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, alpha, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        alpha: f32,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(output.shape() == input.shape());\n        ensure!(output.datum_type() == input.datum_type());\n\n        let kernel_name = self.kernel_name(input.datum_type())?;\n\n        let i_view = get_cuda_view(input);\n        let o_view = get_cuda_view(output);\n        let len = output.len();\n\n        let func = cuda_context().load_pipeline(LibraryName::NN, kernel_name)?;\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_i32(len);\n        launch_args.push::<f32>(alpha);\n\n        let cfg = LaunchConfig::for_num_elems(input.len() as _);\n        unsafe {\n            launch_args.launch(cfg);\n        }\n        Ok(())\n    }\n}\n\npub fn cuda_leaky_relu_dispatch(\n    alpha: f32,\n    input: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| LeakyRelu.dispatch_eval(stream, input, alpha, output))\n}\n\n// LeakyRelu is an ElementWiseMiniOp, so we register under ElementWiseOp's TypeId.\ncrate::register_cuda_op!(tract_core::ops::element_wise::ElementWiseOp, |_source, _node, op| {\n    rule_if_some!(leaky = op.0.downcast_ref::<tract_core::ops::nn::LeakyRelu>());\n    Ok(Some(Box::new(tract_gpu::ops::leaky_relu::GpuLeakyRelu::new(\n        leaky.alpha,\n        \"Cuda\",\n        cuda_leaky_relu_dispatch,\n    ))))\n});\n"
  },
  {
    "path": "cuda/src/kernels/nn/mod.rs",
    "content": "mod apply_rope;\nmod gelu_approximate;\nmod leaky_relu;\nmod reduce;\nmod rms_norm;\nmod scaled_masked_softmax;\nmod softmax;\n\npub use apply_rope::{ApplyRope, cuda_apply_rope_dispatch};\npub use gelu_approximate::GeluApproximate;\npub use gelu_approximate::cuda_gelu_approximate_dispatch;\npub use leaky_relu::LeakyRelu;\npub use leaky_relu::cuda_leaky_relu_dispatch;\npub use reduce::{Reducer, cuda_reduce_launch};\npub use rms_norm::RmsNorm;\npub use rms_norm::cuda_rms_norm_dispatch;\npub use scaled_masked_softmax::{ScaledMaskedSoftmax, cuda_scaled_masked_softmax_dispatch};\npub use softmax::Softmax;\npub use softmax::cuda_softmax_dispatch;\n\nuse crate::kernels::{BroadcastKind, MAX_THREADS};\n\nfn sms_block_sizes() -> Vec<i32> {\n    let mut range = vec![0i32];\n\n    for i in 5..=15 {\n        range.push(2i32.pow(i));\n    }\n\n    range\n}\n\npub fn all_functions() -> Vec<String> {\n    use std::collections::HashSet;\n    let mut functions = HashSet::<String>::new();\n\n    functions.extend(\n        Reducer::ALL\n            .into_iter()\n            .flat_map(|op| {\n                tract_gpu::tensor::DeviceTensor::SUPPORTED_DT.into_iter().map(move |dt| (op, dt))\n            })\n            .flat_map(|(op, dt)| [0, MAX_THREADS].into_iter().map(move |n_cols| (op, dt, n_cols)))\n            .flat_map(|(op, dt, n_cols)| reduce::kernel_name(&op, dt, n_cols).into_iter()),\n    );\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| [0, MAX_THREADS].into_iter().map(move |n_cols| (dt, n_cols)))\n            .flat_map(|(dt, n_cols)| Softmax.kernel_name(dt, n_cols).into_iter()),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| sms_block_sizes().into_iter().map(move |bs| (dt, bs as usize)))\n            .flat_map(|(dt, bs)| ScaledMaskedSoftmax.kernel_name(dt, bs).into_iter()),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| [0, MAX_THREADS].into_iter().map(move |n_cols| (dt, n_cols)))\n            .flat_map(|(dt, n_cols)| RmsNorm.kernel_name(dt, n_cols).into_iter()),\n    );\n\n    functions.extend(\n        BroadcastKind::ALL\n            .into_iter()\n            .flat_map(|brdcast| {\n                tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n                    .into_iter()\n                    .map(move |dt| (dt, brdcast))\n            })\n            .flat_map(|(dt, brdcast)| ApplyRope.kernel_name(dt, brdcast).into_iter()),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| [true, false].into_iter().map(move |fast_impl| (dt, fast_impl)))\n            .flat_map(|(dt, fast_impl)| GeluApproximate { fast_impl }.kernel_name(dt).into_iter()),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| LeakyRelu.kernel_name(dt).into_iter()),\n    );\n\n    functions.into_iter().collect()\n}\n"
  },
  {
    "path": "cuda/src/kernels/nn/reduce.rs",
    "content": "use crate::context::cuda_context;\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{LibraryName, MAX_THREADS, get_cuda_view, launch_args, utils};\nuse cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\npub use tract_gpu::ops::reduce::Reducer;\n\nfn cuda_reduce_is_supported_dt(reducer: &Reducer, dt: DatumType) -> bool {\n    reducer.is_supported_dt(dt)\n        || (matches!(reducer, Reducer::Sum | Reducer::Prod) && dt.is::<i64>())\n}\n\npub fn kernel_name(reducer: &Reducer, dt: DatumType, n_cols: usize) -> TractResult<String> {\n    ensure!(\n        cuda_reduce_is_supported_dt(reducer, dt),\n        \"Unsupported dt {dt:?} for cuda reduceop {:?}\",\n        reducer\n    );\n    let tname = DeviceTensor::tname(dt)?;\n    if n_cols < 1024 {\n        Ok(format!(\"reduce_{}_small_{tname}\", reducer))\n    } else {\n        Ok(format!(\"reduce_{}_{tname}\", reducer))\n    }\n}\n\npub fn cuda_reduce_launch(\n    reducer: &Reducer,\n    input: &DeviceTensor,\n    axis: usize,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| {\n        ensure!(output.datum_type() == input.datum_type());\n        ensure!(output.shape()[axis] == 1);\n\n        let input_shape_nd3 = utils::reshape_to_rank_3(input.shape(), axis);\n        let input_strides_nd3 = Tensor::natural_strides(&input_shape_nd3);\n        let output_shape_nd3 = utils::reshape_to_rank_3(output.shape(), axis);\n        let output_strides_nd3 = Tensor::natural_strides(&output_shape_nd3);\n\n        let total = (input_shape_nd3[0] as u64) * (input_shape_nd3[2] as u64);\n\n        let i_view = get_cuda_view(input);\n        let o_view = get_cuda_view(output);\n\n        let func = cuda_context().load_pipeline(\n            LibraryName::NN,\n            kernel_name(reducer, input.datum_type(), input_shape_nd3[1])?,\n        )?;\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_slice_i32(&input_shape_nd3);\n        launch_args.push_slice_i32(&input_strides_nd3);\n        launch_args.push_slice_i32(&output_strides_nd3);\n\n        let cfg = LaunchConfig {\n            grid_dim: (total as u32, 1, 1),\n            block_dim: if input_shape_nd3[1] < MAX_THREADS {\n                (32, 1, 1)\n            } else {\n                (MAX_THREADS as _, 1, 1)\n            },\n            shared_mem_bytes: 0,\n        };\n\n        launch_args.launch(cfg)\n    })\n}\n\ncrate::register_cuda_op!(tract_core::ops::nn::Reduce, |source, node, op| {\n    let dt = source.node_input_facts(node.id)?[0].datum_type;\n    if let Ok(gpu_op) =\n        tract_gpu::ops::reduce::GpuReduce::from_tract_core(op, \"Cuda\", cuda_reduce_launch)\n    {\n        if cuda_reduce_is_supported_dt(&gpu_op.reducer, dt) {\n            return Ok(Some(Box::new(gpu_op)));\n        }\n    }\n    Ok(None)\n});\n\n#[cfg(test)]\nmod tests {\n\n    use super::*;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n    use tract_core::ops::nn::Reducer as TractReducer;\n    use tract_core::tract_data::itertools::Itertools;\n    use tract_gpu::tensor::IntoDevice;\n\n    fn test_case<F>(\n        reducer: Reducer,\n        tract_reducer: TractReducer,\n        shape: &[usize],\n        axis: usize,\n        scale: f32,\n    ) -> TractResult<()>\n    where\n        F: Float + Datum,\n        usize: AsPrimitive<f32>,\n        f32: AsPrimitive<F>,\n    {\n        crate::with_cuda_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v * scale).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu_output = tract_reducer.reduce(&[axis], &a.to_host()?.into_tensor())?;\n            let mut o_shape = a.shape().to_vec();\n            o_shape[axis] = 1;\n            let cuda_output_dt =\n                unsafe { DeviceTensor::uninitialized_dt(a.datum_type(), &o_shape)? };\n            cuda_reduce_launch(&reducer, &a, axis, &cuda_output_dt)?;\n            stream.synchronize()?;\n            let cuda_output = cuda_output_dt;\n            cpu_output\n                .close_enough(&cuda_output.to_host()?.into_tensor(), Approximation::Approximate)\n                .with_context(|| {\n                    format!(\n                        \"A: {:?}, scale: {:?} Cpu: {:?}, Cuda: {:?}\",\n                        a.to_host().and_then(|it| it.dump(true)),\n                        scale,\n                        cpu_output.dump(true),\n                        cuda_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_reduce_mean_of_squares() -> TractResult<()> {\n        test_case::<f32>(Reducer::MeanOfSquares, TractReducer::MeanOfSquares, &[4, 4], 1, 1.0)?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[4, 4],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[1, 10],\n            0,\n            1.0 / 100.0,\n        )?;\n        test_case::<f32>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[1, 10],\n            0,\n            1.0 / 100.0,\n        )?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 1],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f32>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 1],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 2, 82, 38],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 2, 82, 38],\n            2,\n            1.0 / 100.0,\n        )?;\n        test_case::<f32>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 2, 82, 38],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f32>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 2, 82, 38],\n            2,\n            1.0 / 100.0,\n        )?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_reduce_sum() -> TractResult<()> {\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[4, 4], 1, 1.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[4, 4], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_reduce_prod() -> TractResult<()> {\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[4, 4], 1, 1.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[4, 4], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[2, 2, 82, 38], 2, 1.0 / 100000.0)?;\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[2, 2, 82, 38], 2, 1.0 / 1000.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_reduce_max() -> TractResult<()> {\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[2, 2], 1, 1.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[4, 4], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[1, 10], 0, -1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[2, 1], 1, -1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[2, 2, 82, 38], 1, -1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[2, 2, 82, 38], 2, -1.0 / 100.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_reduce_min() -> TractResult<()> {\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[4, 4], 1, 1.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[4, 4], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[1, 10], 0, -1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[2, 2, 82, 38], 1, -1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[2, 2, 82, 38], 1, -1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        Ok(())\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn reduce_prop_f32(pb in any::<ReduceProblem<f32>>()) {\n            fn run(pb: ReduceProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn reduce_prop_f16(pb in any::<ReduceProblem<f16>>()) {\n            fn run(pb: ReduceProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct ReduceProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        pub op: Reducer,\n        pub shape: Vec<usize>,\n        pub axis: usize,\n        pub input: Vec<F>,\n    }\n\n    impl<F> Arbitrary for ReduceProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            let reducers = Reducer::ALL.into_iter().filter(|r| !r.is_logic()).collect_vec();\n            (0..reducers.len(), 0usize..3, 0usize..3)\n                .prop_flat_map(move |(op_ix, left, right)| {\n                    let axis = left;\n                    let shape_len = usize::min(left + right + 1, 4);\n                    let shape = 1usize..10;\n                    (Just(reducers[op_ix]), vec(shape, shape_len..=shape_len), Just(axis))\n                })\n                .prop_map(|(op, shape, axis)| {\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { op, shape, axis, input }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> ReduceProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n            let cpu_output = match self.op {\n                Reducer::Sum => TractReducer::Sum.reduce(&[self.axis], &a)?,\n                Reducer::Prod => TractReducer::Prod.reduce(&[self.axis], &a)?,\n                Reducer::MeanOfSquares => TractReducer::MeanOfSquares.reduce(&[self.axis], &a)?,\n                Reducer::Min => TractReducer::Min.reduce(&[self.axis], &a)?,\n                Reducer::Max => TractReducer::Max.reduce(&[self.axis], &a)?,\n                Reducer::Any => TractReducer::Any.reduce(&[self.axis], &a)?,\n                Reducer::All => TractReducer::All.reduce(&[self.axis], &a)?,\n            };\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            crate::with_cuda_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let mut o_shape = a.shape().to_vec();\n                o_shape[self.axis] = 1;\n                let output = unsafe { DeviceTensor::uninitialized_dt(a.datum_type(), &o_shape)? };\n                cuda_reduce_launch(&self.op, &a, self.axis, &output)?;\n                stream.synchronize()?;\n                Ok(output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/nn/rms_norm.rs",
    "content": "use crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{LibraryName, MAX_THREADS, WARP_SIZE, get_cuda_view, utils};\nuse cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct RmsNorm;\n\nimpl RmsNorm {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType, n_cols: usize) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for cuda rmsop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        if n_cols < MAX_THREADS {\n            Ok(format!(\"rms_norm_small_{tname}\"))\n        } else {\n            Ok(format!(\"rms_norm_{tname}\"))\n        }\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        axis: usize,\n        eps: &Tensor,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, axis, eps, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        axis: usize,\n        eps: &Tensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(output.shape() == input.shape());\n        ensure!(output.datum_type() == input.datum_type());\n\n        let shape_nd3 = utils::reshape_to_rank_3(input.shape(), axis);\n        let strides_nd3 = Tensor::natural_strides(&shape_nd3);\n\n        let kernel_name = self.kernel_name(input.datum_type(), shape_nd3[1])?;\n\n        let i_view = get_cuda_view(input);\n        let o_view = get_cuda_view(output);\n\n        let func = cuda_context().load_pipeline(LibraryName::NN, kernel_name)?;\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_slice_i32(&shape_nd3);\n        launch_args.push_slice_i32(&strides_nd3);\n        launch_args.push::<f32>(*eps.try_as_plain()?.to_scalar::<f32>()?);\n\n        let cfg = LaunchConfig {\n            grid_dim: ((shape_nd3[2] * shape_nd3[0]) as _, 1, 1),\n            block_dim: if shape_nd3[1] < MAX_THREADS {\n                (WARP_SIZE as _, 1, 1)\n            } else {\n                (MAX_THREADS as _, 1, 1)\n            },\n            shared_mem_bytes: 0,\n        };\n\n        launch_args.launch(cfg)\n    }\n}\n\npub fn cuda_rms_norm_dispatch(\n    input: &DeviceTensor,\n    axis: usize,\n    eps: &Tensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| RmsNorm.dispatch_eval(stream, input, axis, eps, output))\n}\n\ncrate::register_cuda_op!(tract_transformers::ops::rms_norm::RmsNorm, |source, node, op| {\n    rule_if!(RmsNorm::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::rms_norm::GpuRmsNorm::new(\n        op.axis,\n        op.eps.clone(),\n        \"Cuda\",\n        cuda_rms_norm_dispatch,\n    ))))\n});\n\n#[cfg(test)]\nmod tests {\n    use tract_gpu::tensor::IntoDevice;\n\n    use super::*;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n    use tract_transformers::ops::rms_norm;\n\n    fn test_case<F>(shape: &[usize], axis: usize, offset: f32, scale: f32) -> TractResult<()>\n    where\n        F: Float + Datum,\n        usize: AsPrimitive<f32>,\n        f32: AsPrimitive<F>,\n    {\n        crate::with_cuda_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v * scale + offset).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let eps = Arc::new(tensor0(0.0001f32));\n            let cpu_rms = rms_norm::RmsNorm { axis, eps: Arc::clone(&eps) };\n\n            let cpu_output =\n                cpu_rms.eval(tvec![a.to_host()?.into_tvalue()])?[0].clone().into_tensor();\n            let cuda_output = RmsNorm.eval(stream, &a, axis, &eps)?;\n\n            cpu_output\n                .close_enough(&cuda_output.to_host()?.into_tensor(), Approximation::Approximate)\n                .with_context(|| {\n                    format!(\n                        \"Input: {:?}, Cpu: {:?}, Cuda: {:?}\",\n                        a.to_host().and_then(|it| it.dump(true)),\n                        cpu_output.dump(true),\n                        cuda_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_rms() -> TractResult<()> {\n        test_case::<f32>(&[2, 2], 1, -0.0, 1.0 / 100.0)?;\n        test_case::<f16>(&[2, 7], 0, -0.0, 1.0 / 100.0)?;\n        test_case::<f32>(&[2, 124], 1, -0.0, 1.0 / 100.0)?;\n        test_case::<f16>(&[1026, 7], 0, -0.0, 1.0 / 100.0)?;\n        Ok(())\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn rms_prop_f32(pb in any::<RmsNormProblem<f32>>()) {\n            fn run(pb: RmsNormProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn rms_prop_f16(pb in any::<RmsNormProblem<f16>>()) {\n            fn run(pb: RmsNormProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct RmsNormProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub shape: Vec<usize>,\n        pub axis: usize,\n        pub input: Vec<F>,\n        pub eps: Arc<Tensor>,\n    }\n\n    impl<F> Arbitrary for RmsNormProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            (0usize..5, 0usize..1)\n                .prop_flat_map(|(left, right)| {\n                    let axis = left;\n                    let shape_len = usize::min(left + right, 4);\n                    let iter_ax_dim = 1usize..1024;\n                    let other_dim = 1usize..10;\n                    (iter_ax_dim, vec(other_dim, shape_len..=shape_len), Just(axis))\n                })\n                .prop_map(|(iter_dim, mut shape, axis)| {\n                    shape.insert(axis, iter_dim);\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { shape, axis, input, eps: Arc::new(tensor0(0.0001f32)) }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> RmsNormProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n\n            let cpu_rms = rms_norm::RmsNorm { axis: self.axis, eps: Arc::clone(&self.eps) };\n\n            let cpu_output = cpu_rms.eval(tvec![a.into_tvalue()])?[0].clone().into_tensor();\n\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            crate::with_cuda_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let cuda_output = RmsNorm.eval(stream, &a, self.axis, &self.eps)?;\n                Ok(cuda_output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/nn/scaled_masked_softmax.rs",
    "content": "use std::iter::repeat_n;\n\nuse crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::utils::compute_broadcast_strides;\nuse crate::kernels::{LibraryName, MAX_THREADS, get_cuda_view, launch_args};\nuse cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse num_traits::AsPrimitive;\nuse tract_core::internal::*;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct ScaledMaskedSoftmax;\n\nimpl ScaledMaskedSoftmax {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType, block_size: usize) -> TractResult<String> {\n        ensure!(\n            Self::is_supported_dt(dt),\n            \"Unsupported dt {:?} for cuda scaled masked softmaxop\",\n            dt\n        );\n        let tname = DeviceTensor::tname(dt)?;\n        Ok(format!(\"scaled_masked_softmax_{block_size}_{tname}\"))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        scale: &Tensor,\n        mask: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, scale, mask, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        scale: &Tensor,\n        mask: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(output.shape() == input.shape());\n        ensure!(input.rank() >= 2 && input.rank() <= 5);\n        ensure!(mask.rank() == input.rank());\n        ensure!(output.datum_type() == input.datum_type());\n        ensure!(mask.datum_type() == input.datum_type());\n\n        let shape = pad(input.shape(), 1);\n        let strides = pad(input.strides(), 0);\n        let mask_strides = pad(&compute_broadcast_strides::<i32>(mask.shape(), mask.strides())?, 0);\n        let output_strides = pad(output.strides(), 0);\n        let inner_len = shape[4];\n\n        let i_view = get_cuda_view(input);\n        let mask_view = get_cuda_view(mask);\n        let o_view = get_cuda_view(output);\n\n        let inner_len = shape[4] as usize;\n        let mut nth = 32;\n        while nth < inner_len && nth < MAX_THREADS {\n            nth *= 2;\n        }\n\n        let block_size =\n            if inner_len.is_power_of_two() && inner_len > 32 { inner_len.min(1024) } else { 0 };\n\n        let func = cuda_context()\n            .load_pipeline(LibraryName::NN, self.kernel_name(input.datum_type(), block_size)?)?;\n\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&mask_view);\n        launch_args.push::<f32>(scale.cast_to_scalar::<f32>()?);\n        launch_args.push_view(&o_view);\n        launch_args.push_slice_i32(&shape);\n        launch_args.push_slice_i32(&strides);\n        launch_args.push_slice_i32(&mask_strides);\n        launch_args.push_slice_i32(&output_strides);\n\n        // input is [b, kh, gh, row, col]\n        // grid_dim= (row, gh, b*kh)\n        let cfg = LaunchConfig {\n            grid_dim: (shape[3] as _, shape[2] as _, (shape[0] * shape[1]) as _),\n            block_dim: (nth as _, 1, 1),\n            shared_mem_bytes: ((inner_len.next_power_of_two() + 32) * size_of::<f32>()) as u32,\n        };\n\n        launch_args.launch(cfg)\n    }\n}\n\nfn pad(vals: &[impl AsPrimitive<i32>], neutral: i32) -> [i32; 5] {\n    let mut it = [neutral; 5];\n    for (ix, val) in vals.iter().enumerate() {\n        it[ix + 5 - vals.len()] = val.as_();\n    }\n    it\n}\n\npub fn cuda_scaled_masked_softmax_dispatch(\n    input: &DeviceTensor,\n    scale: &Tensor,\n    mask: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| {\n        ScaledMaskedSoftmax.dispatch_eval(stream, input, scale, mask, output)\n    })\n}\n\ncrate::register_cuda_op!(\n    tract_transformers::ops::scaled_masked_softmax::ScaledMaskedSoftmax,\n    |source, node, op| {\n        rule_if!(!op.post_softmax_mask);\n        rule_if!(ScaledMaskedSoftmax::is_supported_dt(\n            source.node_input_facts(node.id)?[0].datum_type\n        ));\n        Ok(Some(Box::new(tract_gpu::ops::scaled_masked_softmax::GpuScaledMaskedSoftmax::new(\n            op.scale.clone(),\n            \"Cuda\",\n            cuda_scaled_masked_softmax_dispatch,\n        ))))\n    }\n);\n\n#[cfg(test)]\nmod tests {\n    use tract_gpu::tensor::IntoDevice;\n\n    use super::*;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use proptest::strategy::Strategy;\n    use tract_core::internal::Tensor;\n    use tract_transformers::ops::scaled_masked_softmax;\n\n    #[test]\n    fn test_scaled_masked_softmax_f32() -> TractResult<()> {\n        crate::with_cuda_stream(|stream| {\n            let m = 6;\n            let n = 33;\n            let scale: Arc<_> = tensor0(0.125f32).into();\n            let mask = Tensor::from_shape(&[1, 1, m, n], &vec![-1000f32; m * n])?.into_device()?;\n\n            let a = Tensor::from_shape(\n                &[4, 1, m, n],\n                &(0..4 * m * n).map(|f| f as f32).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu = scaled_masked_softmax::ScaledMaskedSoftmax {\n                scale: scale.clone(),\n                post_softmax_mask: false,\n            };\n\n            let cpu_output = cpu\n                .eval(tvec![a.to_host()?.into_tvalue(), mask.to_host()?.into_tvalue()])?[0]\n                .clone()\n                .into_tensor();\n            let cuda_output = ScaledMaskedSoftmax.eval(stream, &a, &scale, &mask)?;\n            cpu_output\n                .close_enough(&cuda_output.to_host()?.into_tensor(), Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn scaled_masked_softmax_prop_f32(pb in any::<ScaledMaskedSoftmaxProblem<f32>>()) {\n            fn run(pb: ScaledMaskedSoftmaxProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn scaled_masked_softmax_prop_f16(pb in any::<ScaledMaskedSoftmaxProblem<f16>>()) {\n            fn run(pb: ScaledMaskedSoftmaxProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct ScaledMaskedSoftmaxProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        pub shape: Vec<usize>,\n        pub mask_shape: Vec<usize>,\n        pub input: Vec<F>,\n        pub mask: Vec<F>,\n    }\n\n    impl<F> Arbitrary for ScaledMaskedSoftmaxProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            vec(1usize..10, 4..=4)\n                .prop_map(|shape| {\n                    let mut mask_shape = shape.clone();\n                    mask_shape[0] = 1;\n                    mask_shape[1] = 1;\n\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n\n                    let mask = (0..mask_shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { shape, input, mask_shape, mask }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> ScaledMaskedSoftmaxProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n            let mask = Tensor::from_shape(self.mask_shape.as_slice(), &self.mask)?;\n            let scale: Arc<_> = tensor0::<F>(0.125f32.as_()).into();\n\n            let cpu_output =\n                scaled_masked_softmax::ScaledMaskedSoftmax { scale, post_softmax_mask: false }\n                    .eval(tvec![a.into_tvalue(), mask.into_tvalue()])?[0]\n                    .clone()\n                    .into_tensor();\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            crate::with_cuda_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let mask =\n                    Tensor::from_shape(self.mask_shape.as_slice(), &self.mask)?.into_device()?;\n                let scale: Arc<_> = tensor0::<F>(0.125f32.as_()).into();\n                let cuda_output = ScaledMaskedSoftmax.eval(stream, &a, &scale, &mask)?;\n                Ok(cuda_output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/nn/softmax.rs",
    "content": "use crate::context::{TractCudaStream, cuda_context};\nuse crate::kernels::launch_args::TractLaunchArgs;\nuse crate::kernels::{LibraryName, MAX_THREADS, get_cuda_view, launch_args, utils};\nuse cudarc::driver::{CudaStream, LaunchConfig, PushKernelArg};\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct Softmax;\n\nimpl Softmax {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType, n_cols: usize) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for cuda softmaxop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        if n_cols < MAX_THREADS {\n            Ok(format!(\"softmax_small_{tname}\"))\n        } else {\n            Ok(format!(\"softmax_{tname}\"))\n        }\n    }\n\n    pub fn eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        axis: usize,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, axis, &output)?;\n        stream.synchronize()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &TractCudaStream,\n        input: &DeviceTensor,\n        axis: usize,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(output.shape() == input.shape());\n        ensure!(output.datum_type() == input.datum_type());\n\n        let shape_nd3 = utils::reshape_to_rank_3(input.shape(), axis);\n        let strides_nd3 = Tensor::natural_strides(&shape_nd3);\n\n        let i_view = get_cuda_view(input);\n        let o_view = get_cuda_view(output);\n\n        let func = cuda_context()\n            .load_pipeline(LibraryName::NN, self.kernel_name(input.datum_type(), shape_nd3[1])?)?;\n        let mut launch_args = TractLaunchArgs::new(stream, &func);\n        launch_args.push_view(&i_view);\n        launch_args.push_view(&o_view);\n        launch_args.push_slice_i32(&shape_nd3);\n        launch_args.push_slice_i32(&strides_nd3);\n\n        let cfg = LaunchConfig {\n            grid_dim: ((shape_nd3[0] * shape_nd3[2]) as _, 1, 1),\n            block_dim: if shape_nd3[1] < MAX_THREADS {\n                (32, 1, 1)\n            } else {\n                (MAX_THREADS as _, 1, 1)\n            },\n            shared_mem_bytes: 0,\n        };\n\n        launch_args.launch(cfg)\n    }\n}\n\npub fn cuda_softmax_dispatch(\n    input: &DeviceTensor,\n    axis: usize,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| Softmax.dispatch_eval(stream, input, axis, output))\n}\n\ncrate::register_cuda_op!(tract_core::ops::nn::Softmax, |source, node, op| {\n    rule_if!(Softmax::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::softmax::GpuSoftmax::from_tract_core(\n        op,\n        \"Cuda\",\n        cuda_softmax_dispatch,\n    )?)))\n});\n\n#[cfg(test)]\nmod tests {\n\n    use super::*;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n    use tract_core::ops::nn::Softmax as TractSoftmax;\n    use tract_core::ops::nn::{SoftmaxExp, SoftmaxKind};\n    use tract_gpu::tensor::IntoDevice;\n\n    #[test]\n    fn test_softmax_f32() -> TractResult<()> {\n        crate::with_cuda_stream(|stream| {\n            let m = 2;\n            let k = 3;\n            let axis = 1;\n\n            let a = Tensor::from_shape(&[m, k], &(0..m * k).map(|f| f as f32).collect::<Vec<_>>())?\n                .into_device()?;\n\n            let cpu_softmax = TractSoftmax {\n                axes: tvec![axis],\n                quant_output_dt: None,\n                kind: SoftmaxKind::Softmax(SoftmaxExp::Libc),\n            };\n\n            let cpu_output =\n                cpu_softmax.eval(tvec![a.to_host()?.into_tvalue()])?[0].clone().into_tensor();\n            let cuda_output = Softmax.eval(stream, &a, axis)?;\n\n            cpu_output\n                .close_enough(&cuda_output.to_host()?.into_tensor(), Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_softmax_f32_2() -> TractResult<()> {\n        crate::with_cuda_stream(|stream| {\n            let shape = [8, 4, 3];\n            let num_elements = shape.iter().product();\n            let axis = 0;\n\n            let a = Tensor::from_shape(\n                &shape,\n                &(0..num_elements).map(|f| f as f32 / 1000.0).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu_softmax = TractSoftmax {\n                axes: tvec![axis],\n                quant_output_dt: None,\n                kind: SoftmaxKind::Softmax(SoftmaxExp::Libc),\n            };\n\n            let cpu_output =\n                cpu_softmax.eval(tvec![a.to_host()?.into_tvalue()])?[0].clone().into_tensor();\n            let cuda_output = Softmax.eval(stream, &a, axis)?;\n            cpu_output\n                .close_enough(&cuda_output.to_host()?.into_tensor(), Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_softmax_f16() -> TractResult<()> {\n        crate::with_cuda_stream(|stream| {\n            let m = 4;\n            let k = 4;\n            let axis = 1;\n\n            let a = Tensor::from_shape(\n                &[m, k],\n                &(0..m * k).map(|f| -> f16 { f.as_() }).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu_softmax = TractSoftmax {\n                axes: tvec![axis],\n                quant_output_dt: None,\n                kind: SoftmaxKind::Softmax(SoftmaxExp::Libc),\n            };\n\n            let cpu_output =\n                cpu_softmax.eval(tvec![a.to_host()?.into_tvalue()])?[0].clone().into_tensor();\n            let cuda_output = Softmax.eval(stream, &a, axis)?;\n            cpu_output\n                .close_enough(&cuda_output.to_host()?.into_tensor(), Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn softmax_prop_f32(pb in any::<SoftmaxProblem<f32>>()) {\n            fn run(pb: SoftmaxProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn softmax_prop_f16(pb in any::<SoftmaxProblem<f16>>()) {\n            fn run(pb: SoftmaxProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Cuda: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct SoftmaxProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        pub shape: Vec<usize>,\n        pub axis: usize,\n        pub input: Vec<F>,\n    }\n\n    impl<F> Arbitrary for SoftmaxProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            (0usize..3, 0usize..3)\n                .prop_flat_map(|(left, right)| {\n                    let axis = left;\n                    let shape_len = usize::min(left + right + 1, 4);\n                    let shape = 1usize..10;\n                    (vec(shape, shape_len..=shape_len), Just(axis))\n                })\n                .prop_map(|(shape, axis)| {\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { shape, axis, input }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> SoftmaxProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n\n            let cpu_softmax = TractSoftmax {\n                axes: tvec![self.axis],\n                quant_output_dt: None,\n                kind: SoftmaxKind::Softmax(SoftmaxExp::Libc),\n            };\n            let cpu_output = cpu_softmax.eval(tvec![a.into_tvalue()])?[0].clone().into_tensor();\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            crate::with_cuda_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let cuda_output = Softmax.eval(stream, &a, self.axis)?;\n                Ok(cuda_output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "cuda/src/kernels/utils.rs",
    "content": "use cudarc::driver::LaunchConfig;\n\nuse crate::kernels::MAX_THREADS;\n\npub use tract_gpu::utils::{compute_broadcast_strides, reshape_to_rank_2, reshape_to_rank_3};\n\npub fn cuda_launch_cfg_for_cpy(shape: &[usize]) -> LaunchConfig {\n    // Grid layout: z=dim0, y=dim1, x=product(middle dims), threads=innermost\n    // nd1: x=1, threads=d0\n    // nd2: x=d0, threads=d1\n    // nd3: x=d0*d1, threads=d2\n    // nd4: z=d0, x=d1*d2, threads=d3\n    // nd5: z=d0, y=d1, x=d2*d3, threads=d4\n    // nd6: z=d0, y=d1, x=d2*d3*d4, threads=d5\n    let rank = shape.len();\n    let grid_dim = match rank {\n        0 => panic!(\"Unexpected empty shape while build grid size\"),\n        1 => (1, 1, 1),\n        2 => (shape[0] as _, 1, 1),\n        3 => (shape[1] as _, shape[0] as _, 1),\n        4 => (shape[2] as _, shape[1] as _, shape[0] as _),\n        5 => (shape[2] as u32 * shape[3] as u32, shape[1] as _, shape[0] as _),\n        6 => (shape[2] as u32 * shape[3] as u32 * shape[4] as u32, shape[1] as _, shape[0] as _),\n        _ => panic!(\"Unsupported rank {rank} for cuda copy launch config\"),\n    };\n    LaunchConfig {\n        grid_dim,\n        block_dim: (shape[rank - 1].min(MAX_THREADS) as _, 1, 1),\n        shared_mem_bytes: 0,\n    }\n}\n"
  },
  {
    "path": "cuda/src/lib.rs",
    "content": "mod context;\npub mod kernels;\npub mod ops;\nmod rewrite_rules;\nmod tensor;\nmod transform;\npub mod utils;\n\npub use context::with_cuda_stream;\nuse tract_core::internal::*;\nuse tract_core::transform::ModelTransform;\npub use transform::CudaTransform;\n\nuse crate::utils::ensure_cuda_runtime_dependencies;\nconst Q40_ROW_PADDING: usize = 512;\n\n#[derive(Debug)]\nstruct CudaRuntime;\n\nimpl Runtime for CudaRuntime {\n    fn name(&self) -> StaticName {\n        \"cuda\".into()\n    }\n\n    fn prepare_with_options(\n        &self,\n        mut model: TypedModel,\n        options: &RunOptions,\n    ) -> TractResult<Box<dyn Runnable>> {\n        ensure_cuda_runtime_dependencies(\"cuda runtime supported dependencies not found.\")?;\n        context::cuda_context();\n        CudaTransform.transform(&mut model)?;\n        model.optimize()?;\n\n        let options = RunOptions { skip_order_opt_ram: true, ..options.clone() };\n\n        let mut runnable = TypedSimplePlan::build(model, &options)?;\n        if let Some(hints) = options.memory_sizing_hints {\n            let session_handler =\n                tract_gpu::session_handler::DeviceSessionHandler::from_plan(&runnable, &hints)\n                    .context(\"While sizing memory arena. Missing hint ?\")?;\n            runnable = runnable.with_session_handler(session_handler);\n        }\n\n        Ok(Box::new(Arc::new(runnable)))\n    }\n\n    fn check(&self) -> TractResult<()> {\n        ensure_cuda_runtime_dependencies(\"cuda runtime supported dependencies not found.\")\n    }\n}\n\nregister_runtime!(CudaRuntime = CudaRuntime);\n"
  },
  {
    "path": "cuda/src/ops/conv.rs",
    "content": "use crate::kernels::conv::{ConvGeneric, ConvKernel, ConvKernelScratch};\nuse crate::kernels::conv_cudnn::ConvCudnn;\nuse tract_core::internal::*;\nuse tract_core::ops::OpStateFreeze;\nuse tract_core::ops::cnn::Conv;\nuse tract_gpu::ops::change_axes::GpuAxisOp;\nuse tract_gpu::tensor::DeviceTensorExt;\n\npub fn wire_cuda_conv(\n    source: &TypedModel,\n    node: &TypedNode,\n    target: &mut TypedModel,\n    inputs: &[OutletId],\n    op: &Conv,\n) -> TractResult<TVec<OutletId>> {\n    let facts = source.node_input_facts(node.id)?;\n    let data_shape = op.pool_spec.data_format.shape(&facts[0].shape)?;\n    let hw_rank = data_shape.hw_rank();\n    let is_f16 = facts[0].datum_type.is::<f16>();\n    if facts.iter().all(|f| f.datum_type.is::<f32>() || f.datum_type.is::<f16>())\n        && hw_rank <= if is_f16 { 2 } else { 6 }\n        && op\n            .pool_spec\n            .computed_padding(data_shape.hw_dims())\n            .iter()\n            .all(|paddings| paddings.pad_before == paddings.pad_after)\n    {\n        let prefix = &node.name;\n        let bias = &facts[2];\n        let need_bias = !(bias.konst.is_some() && bias.konst.as_ref().unwrap().is_all_zero()?);\n        let conv_name = format!(\"{prefix}.conv\");\n        let mut conv_wire = target.wire_node(\n            if need_bias { &conv_name } else { &node.name },\n            CudaConv { op: op.clone(), kernel: Box::new(ConvCudnn) },\n            &inputs[0..2],\n        )?[0];\n        if need_bias {\n            let mut needed_shape = tvec![1.to_dim(); node.outputs[0].fact.rank()];\n            needed_shape[data_shape.c_axis()] = op.pool_spec.output_channels.to_dim();\n            let reshaped = target.wire_node(\n                format!(\"{prefix}.bias_reshaped\"),\n                GpuAxisOp::new(AxisOp::Reshape(0, bias.shape.to_tvec(), needed_shape)),\n                &[inputs[2]],\n            )?[0];\n            conv_wire = target.wire_node(\n                prefix,\n                crate::kernels::binary::cuda_bin_op(Box::new(tract_core::ops::math::Add)),\n                &[conv_wire, reshaped],\n            )?[0];\n        }\n        Ok(tvec!(conv_wire))\n    } else {\n        target.wire_node(\n            &node.name,\n            CudaConv { op: op.clone(), kernel: Box::new(ConvGeneric) },\n            inputs,\n        )\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct CudaConv {\n    op: Conv,\n    kernel: Box<dyn ConvKernel>,\n}\n\nimpl Op for CudaConv {\n    fn name(&self) -> StaticName {\n        \"CudaConv\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut info = self.op.info()?;\n        info.push(format!(\"kernel: {}\", self.kernel.name()));\n        Ok(info)\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for CudaConv {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(&self, _session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(CudaConvState(node_id, None))))\n    }\n}\n\nimpl TypedOp for CudaConv {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        tract_gpu::utils::facts_to_device_facts(inputs, |facts| {\n            let zero = facts[0].datum_type.scalar_fact();\n            let mut facts: TVec<&TypedFact> = facts.into();\n            if facts.len() == 2 {\n                facts.push(&zero);\n            }\n            self.op.output_facts(&facts)\n        })\n        .with_context(|| format!(\"Error while computing facts for Conv/{:?}\", self.kernel.name()))\n    }\n}\n\n#[derive(Debug)]\nstruct CudaConvState(usize, Option<Box<dyn ConvKernelScratch>>);\n\nimpl Clone for CudaConvState {\n    fn clone(&self) -> Self {\n        CudaConvState(self.0, None)\n    }\n}\n\nimpl OpState for CudaConvState {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let op: &CudaConv = op.downcast_ref().context(\"Wrong op\")?;\n        let inputs =\n            inputs.iter().map(|it| it.to_device_tensor()).collect::<TractResult<TVec<_>>>()?;\n        let output_shape = op.op.pool_spec.output_shape(inputs[0].shape())?;\n        let output = tract_gpu::session_handler::make_tensor_for_node(\n            session,\n            self.0,\n            inputs[0].datum_type(),\n            &output_shape.shape,\n        )?;\n\n        if self.1.is_none() {\n            self.1 = Some(op.kernel.state());\n        }\n\n        if output.len() > 0 {\n            crate::with_cuda_stream(|stream| {\n                op.kernel.dispatch(\n                    &mut **self.1.as_mut().unwrap(),\n                    self.0,\n                    &op.op,\n                    stream,\n                    inputs[0],\n                    inputs[1],\n                    inputs.get(2).cloned(),\n                    &output,\n                )\n            })?;\n        }\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\n#[derive(Debug, Clone)]\nstruct FrozenCudaConvState(usize);\n\nimpl OpStateFreeze for CudaConvState {\n    fn freeze(&self) -> Box<dyn FrozenOpState> {\n        Box::new(FrozenCudaConvState(self.0))\n    }\n}\n\nimpl FrozenOpState for FrozenCudaConvState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(CudaConvState(self.0, None))\n    }\n}\n"
  },
  {
    "path": "cuda/src/ops/flash_attn.rs",
    "content": "use crate::kernels::flash_attn::CudaFlashAttn;\nuse derive_new::new;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensorExt;\n\n#[derive(Clone, Debug, new, PartialEq)]\npub struct CudaFlashAttention {\n    scale: f32,\n    is_causal: bool,\n}\nimpl Eq for CudaFlashAttention {}\n\nimpl Op for CudaFlashAttention {\n    fn name(&self) -> StaticName {\n        \"CudaFlashAttention\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for CudaFlashAttention {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        crate::with_cuda_stream(|stream| {\n            ensure!(inputs.len() >= 3, \"flash-attn expects [q, k, v, (mask)]\");\n\n            let q = inputs[0].to_device_tensor()?;\n            let k = inputs[1].to_device_tensor()?;\n            let v = inputs[2].to_device_tensor()?;\n            let mask = inputs.get(3).map(|m| m.to_device_tensor()).transpose()?;\n            let output = tract_gpu::session_handler::make_tensor_for_node(\n                session,\n                node_id,\n                q.datum_type(),\n                &CudaFlashAttn.output_shape(q.shape(), k.shape(), v.shape())?,\n            )?;\n            CudaFlashAttn.dispatch_eval(\n                stream,\n                q,\n                k,\n                v,\n                mask,\n                self.scale,\n                &output,\n                self.is_causal,\n            )?;\n            Ok(tvec!(output.into_tensor().into_tvalue()))\n        })\n    }\n}\n\nimpl TypedOp for CudaFlashAttention {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        tract_gpu::utils::facts_to_device_facts(inputs, |facts| {\n            ensure!(facts.len() >= 3);\n            let dt = facts[0].datum_type;\n\n            ensure!(facts.iter().all(|f| f.rank() == 4));\n            let shape =\n                CudaFlashAttn.output_shape(&facts[0].shape, &facts[1].shape, &facts[2].shape)?;\n            let fact = dt.fact(shape);\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "cuda/src/ops/fused_axis_op.rs",
    "content": "use derive_new::new;\nuse tract_core::internal::tract_smallvec::ToSmallVec;\nuse tract_core::internal::*;\nuse tract_core::ops::OpStateFreeze;\nuse tract_gpu::ops::change_axes::GpuAxisOp;\nuse tract_gpu::tensor::{DeviceTensor, DeviceTensorExt};\n\n#[derive(Clone, Debug, new, PartialEq, Eq)]\npub struct CudaFusedAxisOp {\n    /// List of axis ops to apply for each op inputs\n    /// Length of the list is equal to number of inputs\n    pub grouped_axis_ops: TVec<TVec<GpuAxisOp>>,\n    pub op: Box<dyn TypedOp>,\n}\n\n#[derive(Debug, Clone, new)]\npub struct CudaFusedAxisOpState {\n    pub op_state: Box<dyn OpState>,\n}\n\nfn compute_reshaped_inputs(\n    inputs: TVec<TValue>,\n    grouped_axis_ops: &TVec<TVec<GpuAxisOp>>,\n    session: &TurnState,\n) -> TractResult<TVec<TValue>> {\n    // Apply Axis Ops per input\n\n    inputs\n        .into_iter()\n        .zip(grouped_axis_ops.iter())\n        .map(|(input, axis_ops)| {\n            if axis_ops.is_empty() {\n                return Ok(input);\n            };\n            let m_input = input.to_device_tensor()?;\n            let reshaped_input = axis_ops.iter().try_fold(\n                m_input.clone(),\n                |t, axis_op| -> TractResult<DeviceTensor> {\n                    let new_shape = match &axis_op.inner {\n                        AxisOp::Reshape(skip, from, to) => {\n                            let from =\n                                from.iter().map(|d| d.eval(&session.resolved_symbols)).collect();\n                            let to = to.iter().map(|d| d.eval(&session.resolved_symbols)).collect();\n                            let mut shape: TVec<usize> = t.shape().into();\n                            AxisOp::Reshape(*skip, from, to)\n                                .change_shape_array(&mut shape, false)?;\n                            shape\n                        }\n                        AxisOp::Add(_) | AxisOp::Rm(_) | AxisOp::Move(..) => {\n                            let mut shape: TVec<usize> = t.shape().into();\n                            axis_op.inner.change_shape_array(&mut shape, false)?;\n                            shape\n                        }\n                    };\n                    if let AxisOp::Move(from, to) = axis_op.inner {\n                        let mut out_strides: TVec<isize> = t.strides().to_smallvec();\n                        let removed_stride = out_strides.remove(from);\n                        out_strides.insert(to, removed_stride);\n                        let tmp_t = t.reshaped(new_shape)?;\n                        tmp_t.restrided(out_strides)\n                    } else {\n                        t.reshaped(new_shape)\n                    }\n                },\n            )?;\n\n            Ok(reshaped_input.into_tensor().into())\n        })\n        .collect::<TractResult<TVec<_>>>()\n}\n\nimpl OpState for CudaFusedAxisOpState {\n    fn init_tensor_fact(&self) -> Option<(String, TypedFact)> {\n        self.op_state.init_tensor_fact()\n    }\n\n    fn load_from(\n        &mut self,\n        session: &mut TurnState,\n        states: &mut dyn Iterator<Item = tract_core::value::TValue>,\n    ) -> TractResult<()> {\n        self.op_state.load_from(session, states)\n    }\n\n    fn save_to(&self, states: &mut Vec<TValue>) -> TractResult<()> {\n        self.op_state.save_to(states)\n    }\n\n    fn resolve_symbols(&mut self, session: &mut TurnState) -> TractResult<()> {\n        self.op_state.resolve_symbols(session)\n    }\n\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let fused_axis_op = op.downcast_ref::<CudaFusedAxisOp>().unwrap();\n        let inputs = compute_reshaped_inputs(inputs, &fused_axis_op.grouped_axis_ops, session)?;\n        // Runner inner op\n        self.op_state.eval(session, fused_axis_op.op.as_op(), inputs)\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct FrozenCudaFusedAxisOpState {\n    pub op_state: Box<dyn FrozenOpState>,\n}\n\nimpl OpStateFreeze for CudaFusedAxisOpState {\n    fn freeze(&self) -> Box<dyn FrozenOpState + 'static> {\n        Box::new(FrozenCudaFusedAxisOpState { op_state: self.op_state.freeze() })\n    }\n}\n\nimpl FrozenOpState for FrozenCudaFusedAxisOpState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(CudaFusedAxisOpState { op_state: self.op_state.unfreeze() })\n    }\n}\n\nimpl Op for CudaFusedAxisOp {\n    fn name(&self) -> StaticName {\n        self.op.name()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut info = self.op.info()?;\n        for (idx, axis_ops) in self.grouped_axis_ops.iter().enumerate() {\n            if !axis_ops.is_empty() {\n                info.push(format!(\n                    \"Fused axis Op on Input #{idx}: {}\",\n                    axis_ops\n                        .iter()\n                        .map(|axis_op| Ok(format!(\n                            \"{} - {}\",\n                            axis_op.name(),\n                            axis_op.info()?.join(\" | \")\n                        )))\n                        .collect::<TractResult<TVec<_>>>()?\n                        .join(\" | \")\n                ));\n            }\n        }\n        Ok(info)\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for CudaFusedAxisOp {\n    fn is_stateless(&self) -> bool {\n        self.op.is_stateless()\n    }\n\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        if let Some(state) = self.op.state(session, node_id)? {\n            Ok(Some(Box::new(CudaFusedAxisOpState { op_state: state })))\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let inputs = compute_reshaped_inputs(inputs, &self.grouped_axis_ops, session)?;\n        // Runner inner op\n        self.op.eval_with_session(node_id, session, inputs)\n    }\n}\n\nimpl TypedOp for CudaFusedAxisOp {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(\n            inputs.len() == self.grouped_axis_ops.len(),\n            \"Number of inputs and fused axis ops are not aligned\"\n        );\n        // Apply AxisOp\n        let inputs = inputs\n            .iter()\n            .zip(self.grouped_axis_ops.iter())\n            .map(|(i, axis_ops)| {\n                axis_ops.iter().try_fold((*i).clone(), |reshaped_i, axis_op| {\n                    Ok(axis_op.output_facts(&[&reshaped_i])?[0].clone())\n                })\n            })\n            .collect::<TractResult<TVec<_>>>()?;\n\n        let inputs_ref = inputs.iter().collect::<TVec<_>>();\n        // Apply Op\n        self.op.output_facts(&inputs_ref)\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "cuda/src/ops/gemm.rs",
    "content": "use crate::kernels::matmul::GgmlGemm;\nuse crate::ops::GgmlQuantQ81Fact;\nuse crate::utils::get_ggml_q81_fact;\n\nuse anyhow::{bail, ensure};\nuse derive_new::new;\nuse tract_core::internal::*;\nuse tract_core::tract_linalg::block_quant::Q4_0;\nuse tract_gpu::tensor::DeviceTensorExt;\nuse tract_gpu::utils::as_quant_fact;\n\n#[derive(Debug, new, Default, Clone, PartialEq, Eq)]\npub struct CudaGgmlGemm;\n\nimpl Op for CudaGgmlGemm {\n    fn name(&self) -> StaticName {\n        \"CudaGgmlGemm\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl CudaGgmlGemm {\n    fn resolve_output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let [a, b] = inputs else {\n            bail!(\"Expects 2 inputs\");\n        };\n\n        if a.datum_type.is_number() && b.datum_type.is_number() && a.is_plain() && b.is_plain() {\n            ensure!(a.rank() == b.rank());\n            ensure!(a.rank() >= 2);\n            ensure!(a.shape[a.rank() - 1] == b.shape[b.rank() - 1]);\n            let out_shape = GgmlGemm.output_shape(&a.shape, &b.shape);\n            Ok(tvec![a.datum_type().unwrap().fact(out_shape)])\n        } else if as_quant_fact(inputs[1], &Q4_0).is_some() {\n            let Some(b_ggml_qf) =\n                inputs[0].exotic_fact.as_ref().and_then(|of| of.downcast_ref::<GgmlQuantQ81Fact>())\n            else {\n                bail!(\"Expected GGML Q81 activations for Q40 MM\")\n            };\n            let a_shape: ShapeFact =\n                a.shape.iter().cloned().chain(b_ggml_qf.in_shape().to_owned()).collect();\n            // b.shape already carries the full logical dimensions after the\n            // exotic-storage refactoring — no need to chain with BlockQuantFact.shape().\n            let out_shape = GgmlGemm.output_shape(&a_shape, &b.shape);\n            Ok(tvec![DatumType::F32.fact(out_shape)])\n        } else {\n            bail!(\"Unsupported datum type configuration for GEMM\")\n        }\n    }\n}\nimpl EvalOp for CudaGgmlGemm {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let (act_raw, weights_raw) = args_2!(inputs);\n        let activs = act_raw\n            .to_device_tensor()\n            .with_context(|| format!(\"A tensor is not a cuda tensor: {act_raw:?}\"))?;\n        let weights = weights_raw\n            .to_device_tensor()\n            .with_context(|| format!(\"B tensor is not a cuda tensor {weights_raw:?}\"))?;\n\n        let (activ_shape, weights_shape) =\n            crate::kernels::matmul::get_concrete_shapes(activs, weights)?;\n\n        let out_shape = GgmlGemm.output_shape(&activ_shape, &weights_shape);\n        let out_dt =\n            if get_ggml_q81_fact(activs).is_some() { DatumType::F32 } else { activs.datum_type() };\n        let out =\n            tract_gpu::session_handler::make_tensor_for_node(session, node_id, out_dt, &out_shape)?;\n\n        crate::with_cuda_stream(|stream| GgmlGemm.dispatch_eval(stream, activs, weights, &out))?;\n\n        Ok(tvec![out.into_tensor().into_tvalue()])\n    }\n}\n\nimpl TypedOp for CudaGgmlGemm {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        tract_gpu::utils::facts_to_device_facts(inputs, |input_facts| {\n            self.resolve_output_facts(input_facts)\n        })\n        .with_context(|| format!(\"Error while computing output facts for {}\", self.name()))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        tract_gpu::utils::get_device_facts(inputs, |input_facts| {\n            let fma = self.resolve_output_facts(input_facts)?[0].shape.iter().product::<TDim>()\n                * input_facts[0].shape.last().unwrap();\n            if input_facts[0].datum_type == f16::datum_type() {\n                Ok(tvec!((Cost::FMA(f16::datum_type()), fma)))\n            } else {\n                Ok(tvec!((Cost::FMA(f32::datum_type()), fma)))\n            }\n        })\n        .with_context(|| format!(\"Error while computing cost for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "cuda/src/ops/ggml_flash_attn.rs",
    "content": "use crate::kernels::ggml_flash_attn::GgmlFlashAttn;\nuse derive_new::new;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensorExt;\n\n#[derive(Clone, Debug, new)]\npub struct CudaFlashAttention {\n    scale: f32,\n    _is_causal: bool,\n}\n\nimpl Op for CudaFlashAttention {\n    fn name(&self) -> StaticName {\n        \"CudaFlashAttention\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for CudaFlashAttention {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &SessionState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        crate::with_cuda_stream(|stream| {\n            ensure!(inputs.len() == 4, \"flash-attn expects [q, k, v, mask]\");\n\n            let q = inputs[0].to_device_tensor()?;\n            let k = inputs[1].to_device_tensor()?;\n            let v = inputs[2].to_device_tensor()?;\n            let mask = inputs[3].to_device_tensor()?;\n\n            let output = tract_gpu::session_handler::make_tensor_for_node(\n                session,\n                node_id,\n                q.datum_type(),\n                &GgmlFlashAttn.output_shape(q.shape(), k.shape(), v.shape())?,\n            )?;\n            GgmlFlashAttn.dispatch_eval(stream, q, k, v, mask, self.scale, &output)?;\n            Ok(tvec!(output.into_tensor().into_tvalue()))\n        })\n    }\n}\n\nimpl TypedOp for CudaFlashAttention {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        tract_gpu::utils::facts_to_device_facts(inputs, |facts| {\n            ensure!(facts.len() == 4);\n            let dt = facts[0].datum_type;\n\n            ensure!(facts.iter().all(|f| f.rank() == 4));\n            let shape =\n                GgmlFlashAttn.output_shape(&facts[0].shape, &facts[1].shape, &facts[2].shape)?;\n            let fact = dt.fact(shape);\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n\n// Transform code copied here to keep transform.rs clean\n\n//.with_rule_for(\"causal_mask_as_extern\", causal_mask_as_extern)\n//.with_rule_for(\"full_attn_mask_as_neutral\", neutral_mask_for_full_attn)\n\n//fn convert_sdpa_to_cuda_flash_attn(\n//    model: &TypedModel,\n//    node: &TypedNode,\n//    target: &mut TypedModel,\n//    inputs: &mut [OutletId],\n//    op: &Sdpa,\n//) -> TractResult<TVec<OutletId>> {\n//    // ---- Facts & quick guards -------------------------------------------------\n//    let facts = model.node_input_facts(node.id)?;\n//    ensure!(!op.is_causal && facts.len() == 4, \"FlashAttn requires non-causal SDPA with 4 inputs\");\n//\n//    let [qf, kf, vf, mf] = [facts[0], facts[1], facts[2], facts[3]];\n//    ensure!(kf.datum_type() == vf.datum_type(), \"K/V dtypes must match\");\n//\n//    // split inputs as (q, k, v, m)\n//    let [q, k, v, m, ..] = &mut inputs[..] else {\n//        bail!(\"need at least 4 inputs\");\n//    };\n//\n//    // ---- Small helpers --------------------------------------------------------\n//    fn name(base: &str, suffix: &str) -> String {\n//        format!(\"{base}{suffix}\")\n//    }\n//\n//    fn mut_cast(\n//        target: &mut TypedModel,\n//        node_name: &str,\n//        dst: &mut OutletId,\n//        have: DatumType,\n//        want: DatumType,\n//        suffix: &str,\n//    ) -> TractResult<()> {\n//        if have != want {\n//            *dst = target.wire_node(\n//                name(node_name, suffix),\n//                ops::CudaCast::new(want).unwrap(),\n//                &[*dst],\n//            )?[0];\n//        }\n//        Ok(())\n//    }\n//\n//    fn add_head_axis_if_rank3(\n//        target: &mut TypedModel,\n//        node_name: &str,\n//        dst: &mut OutletId,\n//        fact: &TypedFact,\n//        suffix: &str,\n//    ) -> TractResult<bool> {\n//        if fact.rank() == 3 {\n//            let ax = ops::CudaAxisOp::from_tract_core(AxisOp::Add(1));\n//            *dst = target.wire_node(name(node_name, suffix), ax, &[*dst])?[0];\n//            Ok(true)\n//        } else {\n//            ensure!(fact.rank() == 4, \"Q/K/V must be rank 3 or 4\");\n//            Ok(false)\n//        }\n//    }\n//\n//    fn pad_last_two_dims(\n//        target: &mut TypedModel,\n//        node_name: &str,\n//        dst: &mut OutletId,\n//        pad_s: TDim,\n//        pad_sp: TDim,\n//        fill: Tensor,\n//        suffix: &str,\n//    ) -> TractResult<()> {\n//        let mut pads = vec![(TDim::Val(0), TDim::Val(0)); 4];\n//        pads[2].1 = pad_s;\n//        pads[3].1 = pad_sp;\n//        *dst = target.wire_node(\n//            name(node_name, suffix),\n//            ops::CudaPad::new(pads, PadMode::Constant(fill.into()))?,\n//            &[*dst],\n//        )?[0];\n//        Ok(())\n//    }\n//\n//    // ----- casts\n//    let q_dt = qf.datum_type().unwrap();\n//    let kv_dt = kf.datum_type().unwrap();\n//    mut_cast(target, &node.name, k, kv_dt, DatumType::F16, \".cast_k\")?;\n//    mut_cast(target, &node.name, v, kv_dt, DatumType::F16, \".cast_v\")?;\n//    mut_cast(target, &node.name, q, q_dt, DatumType::F32, \".cast_q\")?;\n//\n//    // ----- rank normalize (sequential to avoid overlapping borrows)\n//    let mut added_head_axis = false;\n//    added_head_axis |= add_head_axis_if_rank3(target, &node.name, q, qf, \".reshape_q\")?;\n//    added_head_axis |= add_head_axis_if_rank3(target, &node.name, k, kf, \".reshape_k\")?;\n//    added_head_axis |= add_head_axis_if_rank3(target, &node.name, v, vf, \".reshape_v\")?;\n//\n//    let out_dim = kf.shape[kf.rank() - 1].to_i64()?;\n//    ensure!(\n//        matches!(out_dim, 64 | 80 | 96 | 112 | 128 | 256),\n//        \"Unsupported head dim (D): {out_dim}\"\n//    );\n//    ensure!(kf.shape == vf.shape, \"K and V shapes must be identical\");\n//\n//    // ----- pad K/V seq to multiple of 256\n//    let s_plus_p = kf.shape.dims()[qf.rank() - 2].clone();\n//    let s_plus_p_to_256 = ((s_plus_p.clone() + 255) / 256) * 256 - s_plus_p;\n//\n//    let zero_f16: Arc<Tensor> = tensor0(f16::from_f32(0.0)).into();\n//    // Only pad dim=2 (S+P) for K/V\n//    let mut pads_kv = vec![(TDim::Val(0), TDim::Val(0)); 4];\n//    pads_kv[2].1 = s_plus_p_to_256.clone();\n//    *k = target.wire_node(\n//        name(&node.name, \".pad_k\"),\n//        ops::CudaPad::new(pads_kv.clone(), PadMode::Constant(zero_f16.clone()))?,\n//        &[*k],\n//    )?[0];\n//    *v = target.wire_node(\n//        name(&node.name, \".pad_v\"),\n//        ops::CudaPad::new(pads_kv, PadMode::Constant(zero_f16))?,\n//        &[*v],\n//    )?[0];\n//\n//    // ----- mask: cast→reshape→pad\n//    mut_cast(target, &node.name, m, mf.datum_type().unwrap(), DatumType::F16, \".cast_m\")?;\n//    if mf.rank() != 4 {\n//        let add = 4 - mf.rank();\n//        let ax =\n//            ops::CudaAxisOp::from_tract_core(AxisOp::Reshape(0, tvec![], tvec![TDim::Val(1); add]));\n//        *m = target.wire_node(name(&node.name, \".reshape_m\"), ax, &[*m])?[0];\n//    }\n//    let s = qf.shape.dims()[qf.rank() - 2].clone();\n//    let pad_s_to_16 = ((s.clone() + 15) / 16) * 16 - s;\n//    let neg_inf_f16 = tensor0(-f16::infinity());\n//    pad_last_two_dims(target, &node.name, m, pad_s_to_16, s_plus_p_to_256, neg_inf_f16, \".pad_m\")?;\n//\n//    // ----- scale & op\n//    let scale = op\n//        .scale\n//        .as_ref()\n//        .map(|s| *s.to_scalar::<f32>().unwrap())\n//        .unwrap_or(1.0 / (out_dim as f32).sqrt());\n//    let sdpa = ops::CudaFlashAttention::new(scale, false);\n//\n//    let mut out = target.wire_node(node.name.clone(), sdpa, inputs)?;\n//\n//    if added_head_axis {\n//        out = target.wire_node(\n//            name(&node.name, \".reshape_out\"),\n//            ops::CudaAxisOp::from_tract_core(AxisOp::Rm(1)),\n//            &out,\n//        )?;\n//    }\n//    if q_dt != DatumType::F32 {\n//        out = target.wire_node(\n//            name(&node.name, \".cast_out\"),\n//            ops::CudaCast::new(q_dt).unwrap(),\n//            &out,\n//        )?;\n//    }\n//\n//    Ok(out)\n//}\n"
  },
  {
    "path": "cuda/src/ops/iff.rs",
    "content": "use crate::kernels::Iff;\nuse tract_core::broadcast::multi_broadcast;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensorExt;\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct CudaIff;\n\nimpl Op for CudaIff {\n    fn name(&self) -> StaticName {\n        \"CudaIff\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for CudaIff {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let (cond_val, then_val, else_val) = args_3!(inputs);\n\n        let cond = cond_val.to_device_tensor()?;\n        let then_t = then_val.to_device_tensor()?;\n        let else_t = else_val.to_device_tensor()?;\n        ensure!(cond.rank() == then_t.rank());\n        ensure!(cond.rank() == else_t.rank());\n        ensure!(then_t.datum_type() == else_t.datum_type());\n\n        let out_shape = multi_broadcast(&[cond.shape(), then_t.shape(), else_t.shape()])\n            .context(\"No broadcasting solution found\")?;\n        let out_dt = then_t.datum_type();\n        let output =\n            tract_gpu::session_handler::make_tensor_for_node(session, node_id, out_dt, &out_shape)?;\n\n        if output.len() > 0 {\n            crate::with_cuda_stream(|stream| {\n                Iff.dispatch_eval(stream, cond, then_t, else_t, &output)\n                    .with_context(|| \"Error while dispatching eval for CudaIff\")\n            })?;\n        }\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for CudaIff {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        tract_gpu::utils::facts_to_device_facts(inputs, |inputs| {\n            let out_shape =\n                multi_broadcast(&[&*inputs[0].shape, &*inputs[1].shape, &*inputs[2].shape])\n                    .context(\"No broadcasting solution found\")?;\n            let out_dt = inputs[1].datum_type;\n            Ok(tvec!(out_dt.fact(out_shape)))\n        })\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "cuda/src/ops/mod.rs",
    "content": "mod conv;\nmod flash_attn;\nmod fused_axis_op;\nmod gemm;\nmod iff;\nmod quant_q81;\npub use conv::{CudaConv, wire_cuda_conv};\npub use flash_attn::CudaFlashAttention;\npub use fused_axis_op::CudaFusedAxisOp;\npub use gemm::CudaGgmlGemm;\npub use iff::CudaIff;\npub use quant_q81::{CudaGgmlQuantQ81, GgmlQuantQ81Fact};\n"
  },
  {
    "path": "cuda/src/ops/quant_q81.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::tract_linalg::block_quant::{BlockQuant, Q8_1};\nuse tract_gpu::session_handler::make_scalar_exotic_tensor_for_node;\nuse tract_gpu::tensor::DeviceTensorExt;\n\nuse crate::kernels::matmul::quant_act_q81::GgmlQuantQ81;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct GgmlQuantQ81Fact {\n    pub in_fact: ShapeFact,\n    pub out_fact: ShapeFact,\n}\n\nimpl GgmlQuantQ81Fact {\n    pub fn in_shape(&self) -> &[TDim] {\n        self.in_fact.dims()\n    }\n\n    pub fn out_shape(&self) -> &[TDim] {\n        self.out_fact.dims()\n    }\n\n    pub fn concrete_in_shape(&self) -> TractResult<&[usize]> {\n        self.in_fact.as_concrete().context(\"Expected concrete shape\")\n    }\n\n    pub fn concrete_out_shape(&self) -> TractResult<&[usize]> {\n        self.out_fact.as_concrete().context(\"Expected concrete shape\")\n    }\n\n    pub fn eval(&self, values: &SymbolValues) -> TractResult<Self> {\n        Ok(Self {\n            in_fact: self.in_fact.eval(values)?.into_owned(),\n            out_fact: self.out_fact.eval(values)?.into_owned(),\n        })\n    }\n}\n\nimpl ExoticFact for GgmlQuantQ81Fact {\n    fn buffer_sizes(&self) -> TVec<TDim> {\n        tvec!(self.out_fact.iter().product::<TDim>() * Q8_1.block_bytes() / Q8_1.block_len())\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct CudaGgmlQuantQ81 {\n    io_facts: GgmlQuantQ81Fact,\n}\n\nimpl CudaGgmlQuantQ81 {\n    pub fn new(in_fact: ShapeFact) -> TractResult<Self> {\n        let out_fact = GgmlQuantQ81::output_shape_fact(&in_fact)?;\n        let io_facts = GgmlQuantQ81Fact { in_fact, out_fact };\n        Ok(Self { io_facts })\n    }\n}\nimpl Op for CudaGgmlQuantQ81 {\n    fn name(&self) -> StaticName {\n        \"CudaGgmlQuantQ81Op\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for CudaGgmlQuantQ81 {\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        crate::with_cuda_stream(|stream| {\n            let input_value = args_1!(inputs);\n            let input = input_value.to_device_tensor()?;\n\n            let resolved_io_facts = self.io_facts.eval(&session.resolved_symbols)?;\n\n            let output = make_scalar_exotic_tensor_for_node(\n                session,\n                node_id,\n                input.datum_type(),\n                Box::new(resolved_io_facts),\n            )?;\n\n            GgmlQuantQ81.dispatch_eval(stream, input, &output)?;\n\n            Ok(tvec!(output.into_tensor().into_tvalue()))\n        })\n    }\n\n    fn is_stateless(&self) -> bool {\n        true\n    }\n}\n\nimpl TypedOp for CudaGgmlQuantQ81 {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() == 1);\n        tract_gpu::utils::facts_to_device_facts(inputs, |input_facts| {\n            let dt = input_facts[0].datum_type;\n            let fact = TypedFact::dt_scalar(dt).with_exotic_fact(self.io_facts.clone());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let op = Self::new(self.io_facts.in_fact.eval(values)?.into_owned())?;\n        target.wire_node(&node.name, op, &[mapping[&node.inputs[0]]])\n    }\n    as_op!();\n}\n"
  },
  {
    "path": "cuda/src/rewrite_rules/add_matmul_broadcast.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::ops::array::MultiBroadcastTo;\nuse tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\nuse tract_gpu::rule_ensure;\n\npub fn add_broadcast_pre_matmul(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &PrefixMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    let in_facts = model.node_input_facts(node.id)?;\n    // GGML supports broadcast\n    rule_ensure!(in_facts[1].rank() > 2);\n    let b_rank = in_facts[1].rank();\n    rule_ensure!(\n        !(in_facts[1].shape[b_rank - 2 + !op.transpose_a as usize]\n            .as_i64()\n            .is_some_and(|k| k % 2 == 0)\n            && in_facts[1].shape[b_rank - 2 + op.transpose_b as usize]\n                .as_i64()\n                .is_some_and(|m| m == 1))\n    );\n\n    // Detect broadcast\n    let a_shape = &in_facts[0].shape;\n    let b_shape = &in_facts[1].shape;\n    let a_rank = a_shape.rank();\n\n    let a_batch = &a_shape[..a_rank - 2];\n    let b_batch = &b_shape[..a_rank - 2];\n\n    // Remove from batch_dim array all symbolic dimensions also present in the other batch_dim array\n    // Symbolic Dimensions will be considered as 1 in gcd() so this allows identifying a\n    // symbolic broadcast factor.\n    let a_batch_dims: Vec<_> = a_batch\n        .iter()\n        .filter(|tdim| !matches!(tdim, TDim::Sym(_)) || b_batch.contains(tdim))\n        .cloned()\n        .collect();\n\n    let b_batch_dims: Vec<_> = b_batch\n        .iter()\n        .filter(|tdim| !matches!(tdim, TDim::Sym(_)) || a_batch.contains(tdim))\n        .cloned()\n        .collect();\n\n    let symb_in_a = a_batch_dims != a_batch;\n    let symb_in_b = b_batch_dims != b_batch;\n\n    let a_batch_size = a_batch_dims.iter().product::<TDim>().gcd();\n    let b_batch_size = b_batch_dims.iter().product::<TDim>().gcd();\n\n    let (activ_slot, weight_slot) = if (a_batch_size % b_batch_size == 0)\n        && ((a_batch_size != b_batch_size) || symb_in_a)\n    {\n        (0, 1)\n    } else if (b_batch_size % a_batch_size == 0) && ((a_batch_size != b_batch_size) || symb_in_b) {\n        (1, 0)\n    } else {\n        return Ok(None);\n    };\n\n    let mut patch = TypedModelPatch::default();\n    let activ = patch.tap_model(model, node.inputs[activ_slot])?;\n    let weights = patch.tap_model(model, node.inputs[weight_slot])?;\n    let brd_shape = ShapeFact::from_dims(\n        [\n            in_facts[activ_slot].shape.dims()[..a_rank - 2].to_vec(),\n            in_facts[weight_slot].shape.dims()[a_rank - 2..].to_vec(),\n        ]\n        .concat(),\n    );\n    let brd = MultiBroadcastTo { shape: brd_shape };\n\n    let brd_out = patch.wire_node(format!(\"{node_name}.broadcast\"), brd, &[weights])?[0];\n\n    let inputs = if activ_slot == 1 { [brd_out, activ] } else { [activ, brd_out] };\n    let mm_out = patch.wire_node(node_name, *op, &inputs)?[0];\n\n    patch.shunt_outside(model, node.id.into(), mm_out)?;\n\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "cuda/src/rewrite_rules/fuse_axis_op.rs",
    "content": "use crate::ops::CudaFusedAxisOp;\nuse tract_core::internal::*;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_gpu::fact::DeviceTypedFactExt;\nuse tract_gpu::ops::change_axes::GpuAxisOp;\nuse tract_gpu::rule_ensure;\n\nfn is_supported_axis_op(op: &GpuAxisOp) -> bool {\n    matches!(op.inner, AxisOp::Add(_) | AxisOp::Rm(_) | AxisOp::Reshape(..))\n}\n\n// these are operators that can handle arbitrarty strides\nfn can_fuse_move(model: &TypedModel, axis_node: &TypedNode) -> bool {\n    model.single_succ(axis_node.id).unwrap().is_some_and(|node| {\n        node.op_is::<tract_gpu::ops::concat::GpuConcat>()\n            || node.op_is::<tract_gpu::ops::apply_rope::GpuApplyRope>()\n            || node.op_is::<tract_gpu::ops::scaled_masked_softmax::GpuScaledMaskedSoftmax>()\n            || node.op_is::<tract_gpu::ops::slice::GpuSlice>()\n            || node.op_is::<tract_gpu::ops::broadcast::GpuMultiBroadcastTo>()\n            || node.op_is::<tract_gpu::ops::dyn_kv_cache::GpuDynKVCache>()\n            || node.op_is::<crate::ops::CudaGgmlQuantQ81>()\n            || node.op_is::<tract_gpu::ops::pulse::GpuDelay>()\n            || node.op_is::<tract_gpu::ops::pulse::GpuPulsePad>()\n            || node.op_is::<tract_gpu::ops::binary::GpuBinOp>()\n            || node.op_is::<crate::ops::CudaIff>()\n    })\n}\n\npub fn collect_chain_of_axis_ops<'a>(\n    model: &'a TypedModel,\n    mut cursor: &'a TypedNode,\n) -> TractResult<Option<(TVec<GpuAxisOp>, &'a TypedNode)>> {\n    let mut acc_axis_ops = tvec![];\n    let mut head_of_chain = cursor;\n\n    while let Some(axis_op) = cursor.op_as::<GpuAxisOp>().filter(|o| {\n        is_supported_axis_op(o)\n            || (matches!(o.inner, AxisOp::Move(..)) && can_fuse_move(model, cursor))\n    }) {\n        acc_axis_ops.push(axis_op.clone());\n        head_of_chain = cursor;\n\n        if let Some(prev) = model.single_prec(cursor.id)? {\n            cursor = prev;\n        } else {\n            break;\n        }\n    }\n\n    Ok(if acc_axis_ops.is_empty() {\n        None\n    } else {\n        Some((acc_axis_ops.into_iter().rev().collect(), head_of_chain))\n    })\n}\n\nfn split_succs(\n    model: &TypedModel,\n    axis_node: &TypedNode,\n    axis_node_name: &str,\n    axis_op: &GpuAxisOp,\n) -> TractResult<Option<TypedModelPatch>> {\n    let succs = model.all_succ(axis_node.id)?.context(\"Expected node with successors\")?;\n\n    let mut patch = TypedModelPatch::default();\n    let input = patch.tap_model(model, axis_node.inputs[0])?;\n\n    for (i, succ) in succs.iter().enumerate() {\n        let axis_out =\n            patch.wire_node(format!(\"{axis_node_name}.{i}\"), axis_op.clone(), &[input])?[0];\n\n        let mut op_ins = patch.taps(model, &succ.inputs)?;\n\n        let (idx, _) = succ\n            .inputs\n            .iter()\n            .enumerate()\n            .find(|(_, inlet)| inlet.node == axis_node.id)\n            .context(\"Axis node not found in its successor inputs\")?;\n\n        op_ins[idx] = axis_out;\n\n        let op_outs = patch.wire_node(succ.name.clone(), succ.op.clone(), &op_ins)?;\n        for out in op_outs {\n            patch.shunt_outside(model, succ.id.into(), out)?;\n        }\n    }\n\n    Ok(Some(patch))\n}\n\npub fn fuse_axis_op(\n    _ctx: &(),\n    model: &TypedModel,\n    axis_node: &TypedNode,\n    axis_node_name: &str,\n    axis_op: &GpuAxisOp,\n) -> TractResult<Option<TypedModelPatch>> {\n    // Only support certain axis ops (or a Move, which is handled specially below)\n    rule_ensure!(is_supported_axis_op(axis_op) || matches!(axis_op.inner, AxisOp::Move(..)));\n\n    let Some(node) = model.single_succ(axis_node.id)? else {\n        return split_succs(model, axis_node, axis_node_name, axis_op);\n    };\n\n    // Disallow fusing when the successor is already an axis/fused op or a sync,\n    // *unless* it's a Move AxisOp (we allow that via the early-quit branch).\n    let is_axis_like = node.op_is::<GpuAxisOp>() || node.op_is::<CudaFusedAxisOp>();\n    let is_allowed_move =\n        node.op_as::<GpuAxisOp>().is_some_and(|op| matches!(op.inner, AxisOp::Move(..)));\n\n    rule_ensure!(!is_axis_like || is_allowed_move);\n\n    let node_name = &node.name;\n\n    let Some(in_nodes) = model.all_prec(node.id)? else {\n        return Ok(None);\n    };\n\n    let mut grouped_axis_ops: TVec<TVec<GpuAxisOp>> = tvec![];\n    let mut tap_inputs = tvec![];\n    let mut patch = TypedModelPatch::default();\n\n    for (in_idx, in_node) in in_nodes.into_iter().enumerate() {\n        match collect_chain_of_axis_ops(model, in_node)? {\n            Some((acc_axis_ops, head_of_chain)) => {\n                grouped_axis_ops.push(acc_axis_ops);\n                tap_inputs.push(patch.tap_model(model, head_of_chain.inputs[0])?);\n            }\n            None => {\n                grouped_axis_ops.push(tvec![]);\n                tap_inputs.push(patch.tap_model(model, node.inputs[in_idx])?);\n            }\n        }\n    }\n\n    // If the successor is a Move, we may fuse it now or defer.\n    if let Some(op) = node.op_as::<GpuAxisOp>()\n        && matches!(op.inner, AxisOp::Move(..))\n    {\n        let should_defer_move = !grouped_axis_ops[0].is_empty() && !can_fuse_move(model, node);\n        if should_defer_move {\n            let out = patch.wire_node(\n                format!(\"{node_name}.fused_axis_op\"),\n                CudaFusedAxisOp { grouped_axis_ops, op: Box::new(op.clone()) },\n                &tap_inputs,\n            )?;\n            patch.shunt_outside(model, node.id.into(), out[0])?;\n            return Ok(Some(patch));\n        } else {\n            // Nothing to do right now; we’ll fuse on a later pass.\n            return Ok(None);\n        }\n    }\n\n    // General case: fuse using the successor's op.\n    let out = patch.wire_node(\n        format!(\"{node_name}.fused_axis_op\"),\n        CudaFusedAxisOp { grouped_axis_ops, op: node.op.clone() },\n        &tap_inputs,\n    )?;\n    patch.shunt_outside(model, node.id.into(), out[0])?;\n    Ok(Some(patch))\n}\n\npub fn fuse_move_axis(\n    _ctx: &(),\n    model: &TypedModel,\n    axis_node: &TypedNode,\n    axis_node_name: &str,\n    axis_op: &GpuAxisOp,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_ensure!(matches!(axis_op.inner, AxisOp::Move(..)));\n\n    let in_fact = model.node_input_facts(axis_node.id)?[0];\n    let in_shape =\n        in_fact.as_device_fact().map(|mf| mf.shape.clone()).unwrap_or(in_fact.shape.clone());\n\n    let out_fact = model.node_output_facts(axis_node.id)?[0];\n    let out_shape =\n        out_fact.as_device_fact().map(|mf| mf.shape.clone()).unwrap_or(out_fact.shape.clone());\n\n    // Checks if MoveAxis has no impact on shape + layout\n    if in_shape == out_shape\n        && let (Some(in_strides), AxisOp::Move(from, to)) =\n            (in_shape.as_concrete().map(Tensor::natural_strides), axis_op.inner.clone())\n    {\n        let mut out_strides = in_strides.clone();\n        let remove_stride = out_strides.remove(from);\n        out_strides.insert(to, remove_stride);\n        if in_strides == out_strides {\n            return TypedModelPatch::shunt_one_op(model, axis_node);\n        }\n    }\n\n    // Reshape are always fusable. Change Move by Reshape if possible\n    let simpl_op = GpuAxisOp::simplify_axis_op(axis_op.inner.clone(), in_shape.dims());\n    if simpl_op != *axis_op {\n        return Ok(Some(TypedModelPatch::replace_single_op(\n            model,\n            axis_node,\n            &[axis_node.inputs[0]],\n            simpl_op,\n        )?));\n    }\n\n    // Fuse consecutive MoveAxis if possible\n    let Some(cursor) = model.single_succ(axis_node.id)? else { return Ok(None) };\n    if let (AxisOp::Move(from_1, to_1), AxisOp::Move(from_2, to_2)) = (\n        axis_op.inner.clone(),\n        cursor.op_as::<GpuAxisOp>().map(|ax_op| ax_op.inner.clone()).unwrap_or(AxisOp::Add(0)),\n    ) {\n        let max_rank = [from_1, from_2, to_1, to_2].iter().max().unwrap() + 1;\n        let mut perm: TVec<usize> = (0..max_rank).collect_vec().into();\n\n        AxisOp::Move(from_1, to_1).change_shape_array(&mut perm, false)?;\n        AxisOp::Move(from_2, to_2).change_shape_array(&mut perm, false)?;\n        let new_axis_ops = perm_to_ops(&perm);\n        if new_axis_ops.len() == 1 {\n            let mut patch = TypedModelPatch::default();\n            let inputs = patch.taps(model, &axis_node.inputs)?;\n            let out = patch.wire_node(\n                format!(\"{axis_node_name}.fused_move_axis\"),\n                GpuAxisOp::new(new_axis_ops[0].clone()),\n                &inputs,\n            )?;\n            patch.shunt_outside(model, cursor.id.into(), out[0])?;\n            return Ok(Some(patch));\n        }\n    }\n\n    // Add(x) -> Move(x, y)\n    let Some(cursor) = model.single_prec(axis_node.id)? else { return Ok(None) };\n    if let (AxisOp::Move(from_1, to_1), AxisOp::Add(ax)) = (\n        axis_op.inner.clone(),\n        cursor.op_as::<GpuAxisOp>().map(|ax_op| ax_op.inner.clone()).unwrap_or(AxisOp::Rm(0)),\n    ) && ax == from_1\n    {\n        let mut patch = TypedModelPatch::default();\n        let inputs = patch.taps(model, &cursor.inputs)?;\n        let out =\n            patch.wire_node(cursor.name.clone(), GpuAxisOp::new(AxisOp::Add(to_1)), &inputs)?;\n        patch.shunt_outside(model, axis_node.id.into(), out[0])?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "cuda/src/rewrite_rules/mod.rs",
    "content": "mod add_matmul_broadcast;\nmod fuse_axis_op;\nmod pad_q40_weights;\nmod untranspose_matmul_output;\n\npub use add_matmul_broadcast::add_broadcast_pre_matmul;\npub use fuse_axis_op::{fuse_axis_op, fuse_move_axis};\npub use pad_q40_weights::pad_q40_weights;\npub use untranspose_matmul_output::untranspose_matmul_output;\n"
  },
  {
    "path": "cuda/src/rewrite_rules/pad_q40_weights.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::ops::konst::Const;\nuse tract_core::tract_linalg::block_quant::*;\nuse tract_gpu::fact::DeviceFact;\nuse tract_gpu::rule_ensure;\nuse tract_gpu::tensor::{DeviceTensor, DeviceTensorExt, IntoDevice, OwnedDeviceTensor};\nuse tract_gpu::utils::as_q40_tensor;\n\nuse crate::Q40_ROW_PADDING;\nuse crate::ops::{CudaFusedAxisOp, CudaGgmlGemm};\nuse crate::tensor::CudaTensor;\nuse crate::utils::pad_q40;\nuse tract_gpu::ops::change_axes::GpuAxisOp;\n\n/// Walk the successor chain from a Const node to find a GEMM consumer.\n/// Returns the effective weight shape as seen by the GEMM kernel (after all\n/// axis ops, including CudaFusedAxisOp internal reshapes).  The last\n/// dimension of the returned shape is the k dimension.\nfn effective_gemm_shape(\n    model: &TypedModel,\n    node: &TypedNode,\n    shape: &[usize],\n) -> TractResult<Option<TVec<usize>>> {\n    let mut cursor = node;\n    let mut effective_shape: TVec<usize> = shape.into();\n    while let Some(succ) = model.single_succ(cursor.id)? {\n        if succ.op_is::<CudaGgmlGemm>() {\n            return Ok(Some(effective_shape));\n        }\n        if let Some(fao) = succ.op_as::<CudaFusedAxisOp>() {\n            if fao.op.is::<CudaGgmlGemm>() {\n                // Apply the fused axis ops for the weight input slot.\n                let weight_inlet = succ.inputs.iter().position(|i| i.node == cursor.id).unwrap();\n                for axis_op in &fao.grouped_axis_ops[weight_inlet] {\n                    axis_op.inner.change_shape_array(&mut effective_shape, false)?;\n                }\n                return Ok(Some(effective_shape));\n            }\n        }\n        if let Some(axis_op) = succ.op_as::<GpuAxisOp>() {\n            axis_op.inner.change_shape_array(&mut effective_shape, false)?;\n            cursor = succ;\n            continue;\n        }\n        break;\n    }\n    Ok(None)\n}\n\n// This rule is necessary GGML Q40 Matmul kernels that requires k % 512 == 0\npub fn pad_q40_weights(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    _node_name: &str,\n    op: &Const,\n) -> TractResult<Option<TypedModelPatch>> {\n    let Some(dev_tensor) = op.val().to_device_tensor().ok() else {\n        return Ok(None);\n    };\n\n    let DeviceTensor::Owned(t) = dev_tensor else {\n        return Ok(None);\n    };\n    let Some(cuda_tensor) = t.downcast_ref::<CudaTensor>() else {\n        return Ok(None);\n    };\n\n    let bqf = cuda_tensor\n        .exotic_fact()\n        .and_then(|of| of.downcast_ref::<BlockQuantFact>())\n        .filter(|bqf| bqf.format.dyn_eq(&Q4_0));\n    rule_ensure!(bqf.is_some());\n    let bqf = bqf.unwrap();\n\n    // Compute the effective weight shape as seen by the GEMM kernel (after\n    // all axis ops and CudaFusedAxisOp internal reshapes).  The raw tensor\n    // may have a per-head shape like [m, num_heads, head_dim] that gets\n    // collapsed before the GEMM.\n    let Some(effective_shape) = effective_gemm_shape(model, node, bqf.shape())? else {\n        return Ok(None);\n    };\n    let effective_k = *effective_shape.last().unwrap();\n    rule_ensure!(effective_k % Q40_ROW_PADDING != 0);\n\n    let host_tensor = dev_tensor.to_host()?.into_tensor();\n    let bqs = as_q40_tensor(&host_tensor).expect(\"expected Q4_0 tensor view\");\n    // Compute flat (m, k) matching the contiguous Q4_0 data layout.\n    // The data can be viewed as flat_m rows of effective_k elements each.\n    let total_elements: usize = bqf.shape().iter().product();\n    let flat_m = total_elements / effective_k;\n    let padded_bqs = pad_q40(bqs, flat_m, effective_k)?;\n\n    // Build padded shape preserving the rank and batch structure expected by\n    // the GEMM.  effective_shape already incorporates all GpuAxisOp and\n    // CudaFusedAxisOp transformations, so we just replace k with padded_k.\n    let padded_k = effective_k.next_multiple_of(Q40_ROW_PADDING);\n    let mut padded_shape = effective_shape.clone();\n    *padded_shape.last_mut().unwrap() = padded_k;\n    let padded_bqf = BlockQuantFact::new(\n        tract_core::dyn_clone::clone_box(padded_bqs.format()),\n        padded_shape.clone(),\n    );\n    let padded_fact =\n        TypedFact::dt_shape(f32::datum_type(), &padded_shape).with_exotic_fact(padded_bqf);\n\n    let padded_tensor =\n        padded_bqs.into_tensor_with_shape(f32::datum_type(), &padded_shape).into_arc_tensor();\n\n    let new_const = Const::new_with_exotic_fact(\n        padded_tensor.into_device()?.into_tensor().into_arc_tensor(),\n        Box::new(DeviceFact::from_host(padded_fact)?),\n    )?;\n\n    let mut patch = TypedModelPatch::default();\n    let wire = patch.wire_node(&node.name, new_const, &[])?[0];\n\n    // The padded const already has the effective GEMM shape (with padded k),\n    // so skip the GpuAxisOp chain — those transforms are baked in.\n    // Collect the intermediate node ids to obliterate.\n    let mut cursor = node;\n    let mut obliterate_ids: TVec<usize> = tvec![node.id];\n    while let Some(succ) = model.single_succ(cursor.id)? {\n        if succ.op_is::<GpuAxisOp>() {\n            obliterate_ids.push(succ.id);\n            cursor = succ;\n            continue;\n        }\n        break;\n    }\n\n    // Rewire the downstream GEMM node.  If the consumer is a CudaFusedAxisOp,\n    // clear the weight input's axis ops since the padded tensor is already in\n    // the expected shape.\n    let gemm_succ = model.single_succ(cursor.id)?.unwrap();\n    let weight_outlet: OutletId = cursor.id.into();\n    let weight_inlet = gemm_succ\n        .inputs\n        .iter()\n        .position(|i| i.node == weight_outlet.node && i.slot == weight_outlet.slot)\n        .unwrap();\n    let mut gemm_inputs: TVec<OutletId> = tvec![];\n    for (ix, input) in gemm_succ.inputs.iter().enumerate() {\n        if ix == weight_inlet {\n            gemm_inputs.push(wire);\n        } else {\n            gemm_inputs.push(patch.tap_model(model, *input)?);\n        }\n    }\n    let gemm_op: Box<dyn TypedOp> = if let Some(fao) = gemm_succ.op_as::<CudaFusedAxisOp>() {\n        let mut axis_ops = fao.grouped_axis_ops.clone();\n        axis_ops[weight_inlet].clear();\n        Box::new(CudaFusedAxisOp::new(axis_ops, fao.op.clone()))\n    } else {\n        gemm_succ.op.clone()\n    };\n    let gemm_out = patch.wire_node(&gemm_succ.name, gemm_op, &gemm_inputs)?;\n    patch.shunt_outside(model, gemm_succ.id.into(), gemm_out[0])?;\n    obliterate_ids.push(gemm_succ.id);\n    for id in obliterate_ids {\n        patch.obliterate(id)?;\n    }\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "cuda/src/rewrite_rules/untranspose_matmul_output.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\nuse tract_gpu::rule_ensure;\n\n/// Rewrite BasicMatMul { .. transpose_c: true } to BasicMatMul { .. transpose_c: false}\npub fn untranspose_matmul_output(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    _node_name: &str,\n    op: &PrefixMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_ensure!(op.transpose_c);\n\n    let new_matmul = PrefixMatMul {\n        transpose_a: !op.transpose_b,\n        transpose_b: !op.transpose_a,\n        transpose_c: false,\n        ..*op\n    };\n\n    TypedModelPatch::replace_single_op(model, node, &[node.inputs[1], node.inputs[0]], new_matmul)\n        .map(Some)\n}\n"
  },
  {
    "path": "cuda/src/tensor.rs",
    "content": "use std::ops::{Deref, DerefMut};\n\nuse cudarc::driver::{CudaSlice, DevicePtr};\nuse tract_core::internal::tract_smallvec::ToSmallVec;\nuse tract_core::internal::*;\nuse tract_core::prelude::{DatumType, TVec};\nuse tract_core::tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage, Q8_1};\nuse tract_gpu::device::DeviceBuffer;\nuse tract_gpu::tensor::{DeviceTensor, OwnedDeviceTensor};\nuse tract_gpu::utils::{as_q40_tensor, check_strides_validity};\n\nuse crate::ops::GgmlQuantQ81Fact;\n\n#[derive(Debug, Clone)]\npub struct CudaBuffer {\n    pub inner: CudaSlice<u8>,\n}\n\nimpl DeviceBuffer for CudaBuffer {\n    fn ptr(&self) -> *const std::ffi::c_void {\n        crate::with_cuda_stream(|stream| Ok(self.inner.device_ptr(stream).0 as _)).unwrap()\n    }\n}\nimpl Deref for CudaBuffer {\n    type Target = CudaSlice<u8>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\nimpl DerefMut for CudaBuffer {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.inner\n    }\n}\n\nimpl PartialEq for CudaBuffer {\n    fn eq(&self, other: &Self) -> bool {\n        self.ptr() == other.ptr() && self.inner.len() == other.inner.len()\n    }\n}\nimpl Eq for CudaBuffer {}\n\n#[derive(Clone, PartialEq, Eq)]\npub struct CudaTensor {\n    buffer: Arc<CudaBuffer>,\n    datum_type: DatumType,\n    shape: TVec<usize>,\n    strides: TVec<isize>,\n    exotic_fact: Option<Box<dyn ExoticFact>>,\n}\n\nimpl CudaTensor {\n    pub fn from_tensor(tensor: &Tensor) -> TractResult<Self> {\n        if let Some(bqs) = as_q40_tensor(tensor) {\n            let bqf = BlockQuantFact::new(\n                tract_core::dyn_clone::clone_box(bqs.format()),\n                tensor.shape().into(),\n            );\n            let data = bqs.value().as_bytes();\n            crate::with_cuda_stream(|stream| {\n                let device_data = stream\n                    .clone_htod(data)\n                    .with_context(|| format!(\"Data address: {:?}\", data.as_ptr()))?;\n                let buffer = Arc::new(CudaBuffer { inner: device_data });\n                Ok(CudaTensor {\n                    buffer,\n                    datum_type: tensor.datum_type(),\n                    shape: tensor.shape().into(),\n                    strides: tensor.strides().into(),\n                    exotic_fact: Some(Box::new(bqf)),\n                })\n            })\n        } else {\n            let data = tensor.as_bytes();\n            crate::with_cuda_stream(|stream| {\n                let device_data = stream\n                    .clone_htod(data)\n                    .with_context(|| format!(\"Data address: {:?}\", data.as_ptr()))?;\n                let buffer = Arc::new(CudaBuffer { inner: device_data });\n                Ok(CudaTensor {\n                    buffer,\n                    datum_type: tensor.datum_type(),\n                    shape: tensor.shape().into(),\n                    strides: tensor.strides().into(),\n                    exotic_fact: None,\n                })\n            })\n        }\n    }\n\n    pub fn uninitialized_dt(shape: &[usize], dt: DatumType) -> TractResult<Self> {\n        crate::with_cuda_stream(|stream| unsafe {\n            let device_data = stream.alloc(shape.iter().product::<usize>() * dt.size_of()).unwrap();\n            let buffer = Arc::new(CudaBuffer { inner: device_data });\n            Ok(CudaTensor {\n                buffer,\n                datum_type: dt,\n                shape: shape.to_smallvec(),\n                strides: natural_strides(shape),\n                exotic_fact: None,\n            })\n        })\n    }\n\n    pub fn uninitialized_exotic(exotic_fact: Box<dyn ExoticFact>) -> TractResult<Self> {\n        if let Some(bqf) = exotic_fact.downcast_ref::<BlockQuantFact>() {\n            let shape = bqf.shape();\n            let format = bqf.format.clone();\n            let len = shape.iter().product::<usize>();\n            ensure!(len % format.block_len() == 0);\n            crate::with_cuda_stream(|stream| unsafe {\n                let device_data = stream.alloc(len * format.block_bytes() / format.block_len())?;\n                let buffer = Arc::new(CudaBuffer { inner: device_data });\n                Ok(CudaTensor {\n                    buffer,\n                    datum_type: f32::datum_type(),\n                    shape: tvec!(),\n                    strides: tvec!(),\n                    exotic_fact: Some(Box::new(bqf.clone())),\n                })\n            })\n        } else if let Some(ggml_q81_fact) = exotic_fact.downcast_ref::<GgmlQuantQ81Fact>() {\n            let mem_size = ggml_q81_fact.mem_size().as_i64().unwrap() as usize;\n\n            crate::with_cuda_stream(|stream| unsafe {\n                let device_data = stream.alloc(mem_size)?;\n                let buffer = Arc::new(CudaBuffer { inner: device_data });\n                Ok(CudaTensor {\n                    buffer,\n                    datum_type: f32::datum_type(),\n                    shape: tvec!(),\n                    strides: tvec!(),\n                    exotic_fact: Some(Box::new(ggml_q81_fact.clone())),\n                })\n            })\n        } else {\n            bail!(\"Unsupported exotic type\")\n        }\n    }\n}\n\nimpl std::fmt::Debug for CudaTensor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"CudaTensor\")\n            .field(\"datum_type\", &self.datum_type)\n            .field(\"shape\", &self.shape)\n            .field(\"block_quant_fact\", &self.exotic_fact)\n            .finish()\n    }\n}\n\nimpl OwnedDeviceTensor for CudaTensor {\n    fn datum_type(&self) -> DatumType {\n        self.datum_type\n    }\n\n    fn shape(&self) -> &[usize] {\n        &self.shape\n    }\n\n    fn strides(&self) -> &[isize] {\n        &self.strides\n    }\n\n    fn reshaped(&self, shape: TVec<usize>) -> TractResult<DeviceTensor> {\n        if self.len() != shape.iter().product::<usize>() {\n            bail!(\"Invalid reshape {:?} to {:?}\", self.shape(), shape);\n        }\n        if shape.as_slice() != self.shape() {\n            Ok(DeviceTensor::Owned(Box::new(CudaTensor {\n                strides: Tensor::natural_strides(&shape),\n                shape,\n                ..self.clone()\n            })))\n        } else {\n            Ok(DeviceTensor::Owned(Box::new(self.clone())))\n        }\n    }\n\n    fn restrided(&self, strides: TVec<isize>) -> TractResult<DeviceTensor> {\n        check_strides_validity(self.shape().into(), strides.clone())?;\n        if strides.as_slice() != self.strides() {\n            Ok(DeviceTensor::Owned(Box::new(CudaTensor { strides, ..self.clone() })))\n        } else {\n            Ok(DeviceTensor::Owned(Box::new(self.clone())))\n        }\n    }\n\n    fn device_buffer(&self) -> &dyn tract_gpu::device::DeviceBuffer {\n        self.buffer.as_ref()\n    }\n\n    fn to_host(&self) -> TractResult<Arc<Tensor>> {\n        crate::with_cuda_stream(|stream| {\n            let t: Tensor = if let Some(of) = &self.exotic_fact {\n                let mut blob =\n                    unsafe { Blob::new_for_size_and_align(self.buffer.len(), vector_size()) };\n                stream.memcpy_dtoh(&self.buffer.inner, blob.as_bytes_mut())?;\n                let bqf = if let Some(bqf) = of.downcast_ref::<BlockQuantFact>() {\n                    (*bqf).clone()\n                } else if let Some(ggml_q81) = of.downcast_ref::<GgmlQuantQ81Fact>() {\n                    let out_shape = ggml_q81.concrete_out_shape()?;\n                    BlockQuantFact::new(Box::new(Q8_1), out_shape.into())\n                } else {\n                    bail!(\"Unknown exotic fact\")\n                };\n                let total_m = bqf.m();\n                let k = bqf.k();\n                BlockQuantStorage::new(bqf.format.clone(), total_m, k, Arc::new(blob))?\n                    .into_tensor_with_shape(self.datum_type, &self.shape)\n            } else {\n                let mut tensor = unsafe { Tensor::uninitialized_dt(self.datum_type, &self.shape)? };\n                stream.memcpy_dtoh(&self.buffer.inner, tensor.as_bytes_mut())?;\n                tensor\n            };\n\n            Ok(Arc::new(t))\n        })\n    }\n\n    fn exotic_fact(&self) -> Option<&dyn ExoticFact> {\n        self.exotic_fact.as_deref()\n    }\n\n    fn get_bytes_slice(&self, offset: usize, len: usize) -> Vec<u8> {\n        crate::with_cuda_stream(|stream| {\n            Ok(stream.clone_dtoh(&self.buffer.slice(offset..offset + len)).unwrap())\n        })\n        .unwrap()\n    }\n}\n"
  },
  {
    "path": "cuda/src/transform.rs",
    "content": "use std::any::TypeId;\nuse std::collections::HashMap;\nuse std::sync::OnceLock;\n\nuse crate::context::cuda_context;\nuse crate::ops::wire_cuda_conv;\nuse crate::{kernels, ops, rewrite_rules};\nuse DatumType::{F16, F32};\nuse tract_core::dyn_clone::clone_box;\nuse tract_core::internal::*;\nuse tract_core::model::translator::Translate;\nuse tract_core::ops::cnn::conv::rewrite_kernel_conv_in_oihw;\nuse tract_core::ops::cnn::{Conv, rewrite_conv_with_n_axis};\nuse tract_core::ops::einsum::prefix_matmul::{PrefixMatMul, rewrite_einsum_to_prefix_matmul};\nuse tract_core::ops::konst::Const;\nuse tract_core::ops::nn::Reduce;\nuse tract_core::tract_linalg::block_quant::Q4_0;\nuse tract_core::transform::ModelTransform;\nuse tract_gpu::fact::{DeviceFact, DeviceTypedFactExt};\nuse tract_gpu::rewrite_rules::rewire_syncs::rewire_syncs;\nuse tract_gpu::rewrite_rules::rms_norm::remove_rms_norm_cast;\nuse tract_gpu::sync::{DeviceSyncKind, sync_inputs_if_required, sync_model_outputs_if_required};\nuse tract_gpu::tensor::{DeviceTensor, IntoDevice};\nuse tract_gpu::utils::as_quant_fact;\nuse tract_transformers::ops::sdpa::Sdpa;\n\n/// A registered translator that can convert a core op into a CUDA GPU op.\n/// Each kernel module submits one (or more) of these via [`register_cuda_op!`].\npub struct CudaOpTranslator {\n    pub type_id: TypeId,\n    pub try_make: fn(&TypedModel, &TypedNode) -> TractResult<Option<Box<dyn TypedOp>>>,\n}\n\ninventory::collect!(CudaOpTranslator);\n\n/// Register a translator for a core op type. The closure receives `(source, node, op)`\n/// where `op` is already downcast to `$op_type`. Return `Ok(Some(gpu_op))` to translate,\n/// `Ok(None)` to skip.\n#[macro_export]\nmacro_rules! register_cuda_op {\n    ($op_type:ty, |$source:ident, $node:ident, $op:ident| $body:expr) => {\n        inventory::submit! {\n            $crate::transform::CudaOpTranslator {\n                type_id: std::any::TypeId::of::<$op_type>(),\n                try_make: |$source, $node| {\n                    let Some($op) = $node.op_as::<$op_type>() else {\n                        return Ok(None);\n                    };\n                    $body\n                },\n            }\n        }\n    };\n}\n\n#[derive(Debug, Default)]\npub struct CudaTransform;\n\nimpl ModelTransform for CudaTransform {\n    fn name(&self) -> StaticName {\n        \"cuda-transform\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        self.transform_up_to_phase(model, usize::MAX)\n    }\n}\n\nimpl CudaTransform {\n    pub fn transform_up_to_phase(\n        &self,\n        model: &mut TypedModel,\n        stop_at_phase: usize,\n    ) -> TractResult<()> {\n        // Init CUDA Context if not done previously\n        cuda_context();\n\n        rewrite_einsum_to_prefix_matmul(model, false)?;\n        if stop_at_phase == 0 {\n            return Ok(());\n        }\n\n        Rewriter::default()\n            .with_rule_for(\"untranspose_matmul_output\", rewrite_rules::untranspose_matmul_output)\n            .with_rule_for(\"add_broadcast_pre_matmul\", rewrite_rules::add_broadcast_pre_matmul)\n            .with_rule_for(\"rewrite_kernel_conv_in_oihw\", rewrite_kernel_conv_in_oihw)\n            .with_rule_for(\"rewrite_conv_with_n_axis\", rewrite_conv_with_n_axis)\n            .rewrite(&(), model)?;\n\n        Rewriter::default()\n            .with_rule_for(\"remove_rms_norm_cast\", remove_rms_norm_cast)\n            .with_rule_for(\"split_multi_axis_reduce\", split_multi_axis_reduce)\n            .rewrite(&(), model)?;\n\n        if stop_at_phase == 1 {\n            return Ok(());\n        }\n\n        *model = self.translate_model(model)?;\n\n        if stop_at_phase == 2 {\n            return Ok(());\n        }\n\n        Rewriter::default()\n            .with_rule_for(\"fuse_move_axis\", rewrite_rules::fuse_move_axis)\n            .rewrite(&(), model)?;\n        Rewriter::default()\n            .with_rule_for(\"fuse_axis_op\", rewrite_rules::fuse_axis_op)\n            .rewrite(&(), model)?;\n\n        rewire_syncs(model)?;\n\n        Rewriter::default()\n            .with_rule_for(\"pad_q40_weights\", rewrite_rules::pad_q40_weights)\n            .rewrite(&(), model)?;\n        Ok(())\n    }\n}\n\n/// Looks up the node's op TypeId in the inventory of registered `CudaOpTranslator`s.\n/// Returns `Some(gpu_op)` if a translator matches and succeeds, `None` otherwise.\nfn try_make_cuda_op(\n    source: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<Box<dyn TypedOp>>> {\n    type TranslateFn = fn(&TypedModel, &TypedNode) -> TractResult<Option<Box<dyn TypedOp>>>;\n    static MAP: OnceLock<HashMap<TypeId, Vec<TranslateFn>>> = OnceLock::new();\n    let map = MAP.get_or_init(|| {\n        let mut m: HashMap<TypeId, Vec<TranslateFn>> = HashMap::new();\n        for t in inventory::iter::<CudaOpTranslator> {\n            m.entry(t.type_id).or_default().push(t.try_make);\n        }\n        m\n    });\n\n    let input_facts = source.node_input_facts(node.id)?;\n    if !input_facts.iter().all(|f| DeviceTensor::is_supported_dt(f.datum_type)) {\n        return Ok(None);\n    }\n\n    // Copy-based ops are fully generic (no backend-specific dispatch needed).\n    if let Some(op) = tract_gpu::ops::copy_based::try_make_copy_based_op(source, node)? {\n        return Ok(Some(op));\n    }\n\n    if let Some(fns) = map.get(&(*node.op).type_id()) {\n        for f in fns {\n            if let Some(op) = f(source, node)? {\n                return Ok(Some(op));\n            }\n        }\n    }\n    Ok(None)\n}\n\nfn convert_const(op: &Const) -> TractResult<Const> {\n    let typed_fact: TypedFact = Arc::clone(op.val()).try_into()?;\n    let cuda_fact = if let Some(of) = op.exotic_fact() {\n        DeviceFact::from_host(typed_fact.with_exotic_fact(clone_box(of)))?\n    } else {\n        DeviceFact::from_host(typed_fact)?\n    };\n\n    let cuda_const = op.val().clone().into_device()?.into_tensor().into_arc_tensor();\n    Const::new_with_exotic_fact(cuda_const, Box::new(cuda_fact))\n}\n\npub(crate) fn cuda_cast_new(to: DatumType) -> Option<tract_gpu::ops::cast::GpuCast> {\n    tract_gpu::ops::cast::GpuCast::new(\n        to,\n        \"Cuda\",\n        kernels::array::cuda_cast_dispatch,\n        kernels::array::Cast::is_supported_dt,\n    )\n}\n\nfn can_convert_to_cuda_gemm(facts: &[TypedFact]) -> bool {\n    assert!(facts.len() == 2, \"Ggml: Expected 2 inputs for Matmul\");\n\n    let regular_types_support = facts[0].is_plain()\n        && facts[1].is_plain()\n        && matches!(\n            (facts[0].datum_type, facts[1].datum_type),\n            (F32, F32) | (F16, F16) | (F16, F32)\n        );\n\n    regular_types_support\n        || (as_quant_fact(&facts[1], &Q4_0).is_some() && matches!(facts[0].datum_type, F16 | F32))\n}\n\nfn convert_matmul_to_cuda(\n    model: &TypedModel,\n    node: &TypedNode,\n    target: &mut TypedModel,\n    inputs: &mut [OutletId],\n    op: &PrefixMatMul,\n) -> TractResult<TVec<OutletId>> {\n    let mut input_facts = model.node_input_facts(node.id)?;\n    // GGML kernel expects weights in second position and activations in first position\n    // This avoid output transposition due to GGML column-major data expectations\n\n    let mut swap_inputs = false;\n    if !can_convert_to_cuda_gemm(&[input_facts[0].clone(), input_facts[1].clone()])\n        && can_convert_to_cuda_gemm(&[input_facts[1].clone(), input_facts[0].clone()])\n    {\n        input_facts.swap(0, 1);\n        inputs.swap(0, 1);\n        swap_inputs = true;\n    }\n\n    let act_fact = input_facts[0];\n    let weight_fact = input_facts[1];\n    let outlets = inputs.split_at_mut(1);\n    let act_outlet = &mut outlets.0[0];\n    let weights_outlet = &mut outlets.1[0];\n\n    let transpose_act = if swap_inputs { !op.transpose_b } else { op.transpose_a };\n    let transpose_weight = if swap_inputs { !op.transpose_a } else { op.transpose_b };\n\n    if transpose_act {\n        let rank = act_fact.rank();\n        let perm_act_op =\n            tract_gpu::ops::change_axes::GpuAxisOp::new(AxisOp::Move(rank - 2, rank - 1));\n        let perm_act_name = node.name.clone() + \".perm_activs\";\n        *act_outlet = target.wire_node(perm_act_name, perm_act_op, &[*act_outlet])?[0];\n    }\n\n    if act_fact.datum_type == DatumType::F16 && as_quant_fact(weight_fact, &Q4_0).is_some() {\n        let in_cast_op = cuda_cast_new(DatumType::F32).unwrap();\n        *act_outlet =\n            target.wire_node(node.name.clone() + \".in_cast\", in_cast_op, &[*act_outlet])?[0];\n    } else if act_fact.datum_type == DatumType::F16 && weight_fact.datum_type == DatumType::F32 {\n        let in_cast_op = cuda_cast_new(DatumType::F16).unwrap();\n        *weights_outlet =\n            target.wire_node(node.name.clone() + \".in_cast\", in_cast_op, &[*weights_outlet])?[0];\n    }\n\n    if !transpose_weight {\n        ensure!(as_quant_fact(weight_fact, &Q4_0).is_none(), \"Cannot transpose Q40 tensor\");\n\n        let rank = weight_fact.rank();\n        let perm_weights_op =\n            tract_gpu::ops::change_axes::GpuAxisOp::new(AxisOp::Move(rank - 2, rank - 1));\n        let perm_weights_name = node.name.clone() + \".perm_weights\";\n        *weights_outlet =\n            target.wire_node(perm_weights_name, perm_weights_op, &[*weights_outlet])?[0];\n    }\n\n    if as_quant_fact(weight_fact, &Q4_0).is_some() {\n        let device_fact = target.outlet_fact(*act_outlet)?.to_device_fact()?;\n        let quant_op = ops::CudaGgmlQuantQ81::new(device_fact.shape.clone())?;\n        *act_outlet =\n            target.wire_node(node.name.clone() + \".quant_activs\", quant_op, &[*act_outlet])?[0];\n    }\n    let mut matmul_output =\n        target.wire_node(node.name.clone(), *Box::new(ops::CudaGgmlGemm), inputs)?;\n\n    if swap_inputs {\n        let out_fact = target.outlet_fact(matmul_output[0])?;\n        let rank = &out_fact\n            .exotic_fact\n            .clone()\n            .map(|fact| fact.clarify_dt_shape().unwrap().1.len())\n            .unwrap();\n\n        let perm_out_op =\n            tract_gpu::ops::change_axes::GpuAxisOp::new(AxisOp::Move(rank - 2, rank - 1));\n        matmul_output =\n            target.wire_node(node.name.clone() + \".perm_out\", perm_out_op, &matmul_output)?;\n    }\n\n    let out_fact = target.outlet_fact(matmul_output[0])?;\n    let out_dt = out_fact.as_device_fact().map(|f| f.datum_type).unwrap_or(out_fact.datum_type);\n\n    let expected_dt = model.node_output_facts(node.id)?[0].datum_type;\n    if out_dt != expected_dt {\n        ensure!(\n            kernels::array::Cast::is_supported_dt(out_dt),\n            \"Matmul output type cannot be casted to expected type\"\n        );\n        let cast_op = cuda_cast_new(model.node_output_facts(node.id)?[0].datum_type).unwrap();\n        matmul_output =\n            target.wire_node(node.name.clone() + \".out_cast\", cast_op, &matmul_output)?\n    }\n    Ok(matmul_output)\n}\n\nfn convert_sdpa_to_cuda_flash_attn(\n    model: &TypedModel,\n    node: &TypedNode,\n    target: &mut TypedModel,\n    inputs: &mut [OutletId],\n    op: &Sdpa,\n) -> TractResult<TVec<OutletId>> {\n    let facts = model.node_input_facts(node.id)?;\n\n    let [qf, kf, vf] = [facts[0], facts[1], facts[2]];\n    ensure!(kf.datum_type() == vf.datum_type(), \"K/V dtypes must match\");\n\n    let mask_fact = if facts.len() == 4 { Some(facts[3]) } else { None };\n\n    let (q, k, v, m_opt) = match &mut inputs[..] {\n        [q, k, v, m, ..] => (q, k, v, Some(m)),\n        [q, k, v] => (q, k, v, None),\n        _ => bail!(\"unexpected number of inputs\"),\n    };\n\n    fn name(base: &str, suffix: &str) -> String {\n        format!(\"{base}{suffix}\")\n    }\n\n    fn mut_cast(\n        target: &mut TypedModel,\n        node_name: &str,\n        dst: &mut OutletId,\n        have: DatumType,\n        want: DatumType,\n        suffix: &str,\n    ) -> TractResult<()> {\n        if have != want {\n            *dst =\n                target.wire_node(name(node_name, suffix), cuda_cast_new(want).unwrap(), &[*dst])?\n                    [0];\n        }\n        Ok(())\n    }\n\n    fn add_head_axis_if_rank3(\n        target: &mut TypedModel,\n        node_name: &str,\n        dst: &mut OutletId,\n        fact: &TypedFact,\n        suffix: &str,\n    ) -> TractResult<bool> {\n        if fact.rank() == 3 {\n            let ax = tract_gpu::ops::change_axes::GpuAxisOp::new(AxisOp::Add(1));\n            *dst = target.wire_node(name(node_name, suffix), ax, &[*dst])?[0];\n            Ok(true)\n        } else {\n            ensure!(fact.rank() == 4, \"Q/K/V must be rank 3 or 4\");\n            Ok(false)\n        }\n    }\n\n    // ----- casts\n    let q_dt = qf.datum_type().unwrap();\n    let kv_dt = kf.datum_type().unwrap();\n    mut_cast(target, &node.name, k, kv_dt, DatumType::F16, \".cast_k\")?;\n    mut_cast(target, &node.name, v, kv_dt, DatumType::F16, \".cast_v\")?;\n    mut_cast(target, &node.name, q, q_dt, DatumType::F16, \".cast_q\")?;\n\n    // ----- rank normalize\n    let mut added_head_axis = false;\n    added_head_axis |= add_head_axis_if_rank3(target, &node.name, q, qf, \".reshape_q\")?;\n    added_head_axis |= add_head_axis_if_rank3(target, &node.name, k, kf, \".reshape_k\")?;\n    added_head_axis |= add_head_axis_if_rank3(target, &node.name, v, vf, \".reshape_v\")?;\n\n    let out_dim = kf.shape[kf.rank() - 1].to_i64()?;\n    ensure!(matches!(out_dim, 64 | 128), \"Unsupported head dim (D): {out_dim}\");\n    ensure!(kf.shape == vf.shape, \"K and V shapes must be identical\");\n\n    // ----- mask: cast & reshape\n    if let Some(mf) = mask_fact {\n        let m = m_opt.unwrap();\n        mut_cast(target, &node.name, m, mf.datum_type().unwrap(), DatumType::F16, \".cast_m\")?;\n        if mf.rank() != 4 {\n            let ax = tract_gpu::ops::change_axes::GpuAxisOp::new(AxisOp::Add(1));\n            *m = target.wire_node(name(&node.name, \".reshape_m\"), ax, &[*m])?[0];\n        }\n    }\n\n    // ----- scale & op\n    let scale = op\n        .scale\n        .as_ref()\n        .map(|s| *s.try_as_plain().unwrap().to_scalar::<f32>().unwrap())\n        .unwrap_or(1.0 / (out_dim as f32).sqrt());\n    let sdpa = ops::CudaFlashAttention::new(scale, op.is_causal);\n\n    let mut out = target.wire_node(node.name.clone(), sdpa, inputs)?;\n\n    if added_head_axis {\n        out = target.wire_node(\n            name(&node.name, \".reshape_out\"),\n            tract_gpu::ops::change_axes::GpuAxisOp::new(AxisOp::Rm(1)),\n            &out,\n        )?;\n    }\n\n    if q_dt != DatumType::F16 {\n        out =\n            target.wire_node(name(&node.name, \".cast_out\"), cuda_cast_new(q_dt).unwrap(), &out)?;\n    }\n\n    Ok(out)\n}\n\nimpl Translate<TypedFact, Box<dyn TypedOp>, TypedFact, Box<dyn TypedOp>> for CudaTransform {\n    fn translate_node(\n        &self,\n        source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        // Special multi-node ops handled first\n        let input_facts = source.node_input_facts(node.id)?;\n        if let Some(op) = node.op_as::<PrefixMatMul>() {\n            let facts: Vec<TypedFact> = input_facts.iter().map(|f| (*f).clone()).collect();\n            if !op.transpose_c\n                && op.quantize_output.is_none()\n                && (can_convert_to_cuda_gemm(&facts)\n                    || can_convert_to_cuda_gemm(&[facts[1].clone(), facts[0].clone()]))\n            {\n                let mut device_inputs =\n                    sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToDevice)?;\n                let outlet_ids =\n                    convert_matmul_to_cuda(source, node, target, &mut device_inputs, op)?;\n                return sync_model_outputs_if_required(source, node, target, outlet_ids);\n            }\n        }\n        if let Some(op) = node.op_as::<Sdpa>() {\n            let mut device_inputs =\n                sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToDevice)?;\n            let outlet_ids =\n                convert_sdpa_to_cuda_flash_attn(source, node, target, &mut device_inputs, op)?;\n            return sync_model_outputs_if_required(source, node, target, outlet_ids);\n        }\n        if let Some(conv) = node.op_as::<Conv>() {\n            if input_facts.iter().all(|f| DeviceTensor::is_supported_dt(f.datum_type))\n                && matches!(input_facts[0].datum_type, F16 | F32)\n            {\n                let device_inputs =\n                    sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToDevice)?;\n                let outlet_ids = wire_cuda_conv(source, node, target, &device_inputs, conv)?;\n                return sync_model_outputs_if_required(source, node, target, outlet_ids);\n            }\n        }\n        // Const: inline conversion, not a GPU op\n        if let Some(op) = node.op_as::<Const>() {\n            if DeviceTensor::is_supported_dt(op.val().datum_type()) {\n                let device_inputs =\n                    sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToDevice)?;\n                let outlet_ids =\n                    target.wire_node(node.name.clone(), convert_const(op)?, &device_inputs)?;\n                return sync_model_outputs_if_required(source, node, target, outlet_ids);\n            }\n        }\n\n        // Single-op translation\n        if let Some(gpu_op) = try_make_cuda_op(source, node)? {\n            let device_inputs =\n                sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToDevice)?;\n            let outlet_ids = target.wire_node(node.name.clone(), gpu_op, &device_inputs)?;\n            sync_model_outputs_if_required(source, node, target, outlet_ids)\n        } else {\n            let cpu_inputs =\n                sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToHost)?;\n            target.wire_node(&node.name, node.op.clone(), &cpu_inputs)\n        }\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_prefix_matmul_transform_f32_f16() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let (b, m, k, n) = (1, 16, 128, 32);\n\n        let a_fact = TypedFact::dt_shape(DatumType::F32, &[b, m, k]);\n        let b_fact = TypedFact::dt_shape(DatumType::F16, &[b, k, n]);\n\n        let source_a = model.add_source(\"a\", a_fact)?;\n        let source_b = model.add_source(\"b\", b_fact)?;\n\n        let op = PrefixMatMul {\n            transpose_a: false,\n            transpose_b: false,\n            transpose_c: false,\n            quantize_output: None,\n            operating_dt: Some(DatumType::F32),\n        };\n\n        let matmul_out = model.wire_node(\"matmul\", op, &[source_a, source_b])?;\n        model.select_output_outlets(&matmul_out)?;\n\n        let tensor_a = Tensor::zero::<f32>(&[b, m, k])?;\n        let tensor_b = Tensor::zero::<f16>(&[b, k, n])?;\n        let inputs = tvec!(tensor_a.into(), tensor_b.into());\n\n        let transform = CudaTransform::default();\n        transform.transform(&mut model)?;\n\n        let cuda_runnable = model.into_runnable()?;\n        let _ = cuda_runnable.run(inputs)?;\n        Ok(())\n    }\n}\n\nfn split_multi_axis_reduce(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &Reduce,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(op.axes.len() > 1);\n    use tract_core::ops::nn::Reducer::*;\n    rule_if!(matches!(op.reducer, Sum | Prod | Min | Max | Any | All));\n    let mut patch = TypedModelPatch::default();\n    let mut wire = patch.tap_model(model, node.inputs[0])?;\n    // Reduce axes from highest to lowest so indices stay valid\n    let mut axes = op.axes.clone();\n    axes.sort();\n    for (i, &axis) in axes.iter().rev().enumerate() {\n        let single = Reduce { axes: tvec![axis], reducer: op.reducer };\n        wire = patch.wire_node(format!(\"{node_name}.axis_{i}\"), single, &[wire])?[0];\n    }\n    patch.shunt_outside(model, node.id.into(), wire)?;\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "cuda/src/utils.rs",
    "content": "use std::ffi::c_int;\nuse std::sync::OnceLock;\n\nuse anyhow::Context;\nuse libloading::{Library, Symbol};\n\nuse tract_core::internal::*;\nuse tract_core::tract_linalg::block_quant::*;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::Q40_ROW_PADDING;\nuse crate::ops::GgmlQuantQ81Fact;\n\nstatic CULIBS_PRESENT: OnceLock<bool> = OnceLock::new();\n\n// Ensure exactly cuda-12060 is enabled.\n// Prevent accidental change of feature gate without\n// updating this required API version used for compatibility check.\n// please update the 3 references bellow if cudarc gate is updated to a newer version.\npub const REQUIRED_CUDA_API: i32 = 12060;\n#[cfg(not(feature = \"cuda-12060\"))]\ncompile_error!(\n    \"Tract CUDA backend currently supports only cudarc feature 'cuda-12060'. \\\n        Enabled in Cargo features.\",\n);\n\n/// CUDA Driver API status code type (CUresult is an enum, but it's ABI-compatible with int).\ntype CuResult = c_int;\n\ntype CuInitFn = unsafe extern \"C\" fn(flags: u32) -> CuResult;\ntype CuDriverGetVersionFn = unsafe extern \"C\" fn(version: *mut c_int) -> CuResult;\n\n#[cfg(target_os = \"linux\")]\nconst CUDA_DRIVER_LIB_CANDIDATES: [&str; 2] = [\"libcuda.so.1\", \"libcuda.so\"];\n\n#[cfg(target_os = \"windows\")]\nconst CUDA_DRIVER_LIB_CANDIDATES: [&str; 1] = [\"nvcuda.dll\"];\n\n#[cfg(not(any(target_os = \"linux\", target_os = \"windows\")))]\nconst CUDA_DRIVER_LIB_CANDIDATES: [&str; 0] = [];\n\nfn format_cuda_version(v: i32) -> (i32, i32) {\n    (v / 1000, (v % 1000) / 10)\n}\n\nfn load_first_found_cuda_lib() -> TractResult<Library> {\n    for name in CUDA_DRIVER_LIB_CANDIDATES {\n        if let Ok(lib) = unsafe { Library::new(name) } {\n            return Ok(lib);\n        }\n    }\n\n    bail!(\n        \"CUDA driver library not found. Tried: {:?}. \\\n         Is an NVIDIA driver installed \\\n         (and, in containers, did you run with GPU passthrough)?\",\n        CUDA_DRIVER_LIB_CANDIDATES\n    )\n}\n\n/// Checks that the installed CUDA driver is present and new enough to satisfy REQUIRED_CUDA_API.\n///\n/// IMPORTANT: call this before touching `cudarc::driver::sys` or any cudarc context creation,\n/// otherwise cudarc may panic while eagerly binding symbols.\nfn ensure_cuda_driver_compatible() -> TractResult<()> {\n    // Load driver library without involving cudarc.\n    let lib = load_first_found_cuda_lib()?;\n\n    unsafe {\n        // Resolve symbols we need. If these are missing, the driver install is broken or not NVIDIA.\n        let cu_init: Symbol<CuInitFn> = lib.get(b\"cuInit\\0\").map_err(|e| {\n            format_err!(\n                \"CUDA driver library loaded, but symbol cuInit is missing. \\\n                 This does not look like a functional NVIDIA driver installation. Details: {e}\"\n            )\n        })?;\n\n        let cu_driver_get_version: Symbol<CuDriverGetVersionFn> =\n            lib.get(b\"cuDriverGetVersion\\0\").map_err(|e| {\n                format_err!(\n                    \"CUDA driver library loaded, but symbol cuDriverGetVersion is missing. \\\n                     Driver is too old or installation is corrupted. Details: {e}\"\n                )\n            })?;\n\n        // Initialize the driver. This also surfaces \"no device / permission / container\" issues early.\n        let init_res = cu_init(0);\n        if init_res != 0 {\n            // Don't assume specific numeric codes here; just report the CUresult.\n            bail!(\n                \"CUDA driver initialization failed (cuInit returned {}). \\\n                 Possible causes: no CUDA-capable device exposed to this process, \\\n                 missing /dev/nvidia* nodes (container), insufficient permissions, \\\n                 or a broken driver install.\",\n                init_res\n            );\n        }\n\n        // Query driver API version.\n        let mut version: c_int = 0;\n        let ver_res = cu_driver_get_version(&mut version as *mut _);\n        if ver_res != 0 {\n            bail!(\n                \"cuDriverGetVersion failed (returned {}). \\\n                 NVIDIA driver may be corrupted or not functioning properly.\",\n                ver_res\n            );\n        }\n\n        // Compare against required API based on feature gate.\n        if version < REQUIRED_CUDA_API {\n            let (req_major, req_minor) = format_cuda_version(REQUIRED_CUDA_API);\n            let (found_major, found_minor) = format_cuda_version(version);\n\n            bail!(\n                \"CUDA driver too old.\\n\\\n                 Built with cudarc feature cuda-{} (requires driver API >= {}.{}).\\n\\\n                 Found driver API {}.{}.\\n\\\n                 Fix: upgrade the NVIDIA driver, or rebuild tract-cuda with a lower cuda-XXXXX gate.\",\n                REQUIRED_CUDA_API,\n                req_major,\n                req_minor,\n                found_major,\n                found_minor\n            );\n        }\n    }\n\n    Ok(())\n}\n\nfn are_cudarc_required_culibs_present() -> bool {\n    *CULIBS_PRESENT.get_or_init(|| unsafe {\n        cudarc::driver::sys::is_culib_present()\n            && cudarc::runtime::sys::is_culib_present()\n            && cudarc::nvrtc::sys::is_culib_present()\n            && cudarc::cublas::sys::is_culib_present()\n    })\n}\n\npub fn ensure_cuda_runtime_dependencies(context_msg: &'static str) -> TractResult<()> {\n    ensure_cuda_driver_compatible()\n        .context(\"CUDA driver validation failed\")\n        .context(context_msg)?;\n\n    ensure!(\n        are_cudarc_required_culibs_present(),\n        \"{}: CUDA runtime sub-libraries missing/missaligned with cudarc (nvrtc,cublas,cudart).\",\n        context_msg\n    );\n\n    Ok(())\n}\n\npub fn get_ggml_q81_fact(t: &DeviceTensor) -> Option<GgmlQuantQ81Fact> {\n    if let DeviceTensor::Owned(t) = t {\n        t.exotic_fact().and_then(|of| of.downcast_ref::<GgmlQuantQ81Fact>()).cloned()\n    } else if let DeviceTensor::ArenaView(t) = t {\n        t.exotic_fact().and_then(|of| of.downcast_ref::<GgmlQuantQ81Fact>()).cloned()\n    } else {\n        None\n    }\n}\n\npub fn pad_q40(bqs: &BlockQuantStorage, m: usize, k: usize) -> TractResult<BlockQuantStorage> {\n    ensure!(k % 32 == 0);\n\n    let to_pad = k.next_multiple_of(Q40_ROW_PADDING) - k;\n    if to_pad == 0 {\n        return Ok(bqs.clone()); // No padding needed\n    }\n\n    let row_bytes = k * Q4_0.block_bytes() / Q4_0.block_len();\n\n    let pad_quant = Q4_0.quant_f32(&vec![0f32; to_pad])?;\n    let pad_bytes = pad_quant.len();\n\n    let mut new_data = Vec::with_capacity(m * (row_bytes + pad_bytes));\n    let old_bytes = bqs.value().as_bytes();\n\n    for row in 0..m {\n        let start = row * row_bytes;\n        new_data.extend_from_slice(&old_bytes[start..start + row_bytes]);\n        new_data.extend_from_slice(&pad_quant);\n    }\n\n    BlockQuantStorage::new(\n        tract_core::dyn_clone::clone_box(bqs.format()),\n        m,\n        k + to_pad,\n        Arc::new(Blob::from_bytes(&new_data)?),\n    )\n}\n"
  },
  {
    "path": "data/Cargo.toml",
    "content": "[package]\nname = \"tract-data\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nanyhow.workspace = true\ndowncast-rs.workspace = true\ndyn-clone.workspace = true\ndyn-eq.workspace = true\ndyn-hash.workspace = true\nhalf.workspace = true\nitertools.workspace = true\nlibm.workspace = true\nmaplit.workspace = true\nndarray.workspace = true\nnom.workspace = true\nnom-language.workspace = true\nnum-complex = { workspace = true, optional = true }\nnum-integer.workspace = true\nnum-traits.workspace = true\nsmallvec.workspace = true\nlazy_static.workspace = true\nscan_fmt.workspace = true\nstring-interner.workspace = true\nparking_lot = \"0.12.3\"\n\n[target.'cfg(not(target_family = \"wasm\"))'.dev-dependencies]\ncriterion.workspace = true\nproptest.workspace = true\n\n[target.'cfg(target_family = \"wasm\")'.dev-dependencies]\n# Wasm doesn't support the `rayon` feature of criterion\ncriterion = { version = \"0.8\", default-features = false, features = [\"plotters\", \"cargo_bench_support\"] }\n# Wasm doesn't support the `fork` feature of proptest.\nproptest = { version = \"1.0.0\", default-features = false, features = [\"std\", \"bit-set\"] }\n\n[features]\ncomplex = [ \"num-complex\" ]\n\n[[bench]]\nname = \"tensor_from_datum\"\nharness = false\n\n[[bench]]\nname = \"stack_tensors\"\nharness = false\n"
  },
  {
    "path": "data/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "data/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "data/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "data/benches/stack_tensors.rs",
    "content": "#[macro_use]\nextern crate criterion;\nextern crate tract_data;\n\nuse criterion::Criterion;\nuse tract_data::internal::*;\n\nfn inceptionv3_5b(c: &mut Criterion) {\n    c.bench_function(\"inceptionv3_5b\", |b| {\n        b.iter_with_setup(\n            || unsafe {\n                vec![\n                    Tensor::uninitialized_dt(DatumType::F32, &[1, 35, 35, 64]).unwrap(),\n                    Tensor::uninitialized_dt(DatumType::F32, &[1, 35, 35, 64]).unwrap(),\n                    Tensor::uninitialized_dt(DatumType::F32, &[1, 35, 35, 96]).unwrap(),\n                    Tensor::uninitialized_dt(DatumType::F32, &[1, 35, 35, 32]).unwrap(),\n                ]\n            },\n            |input| Tensor::stack_tensors(3, &input),\n        );\n    });\n}\n\ncriterion_group!(benches, inceptionv3_5b);\ncriterion_main!(benches);\n"
  },
  {
    "path": "data/benches/tensor_from_datum.rs",
    "content": "#[macro_use]\nextern crate criterion;\nextern crate tract_data;\n\nuse criterion::Criterion;\nuse tract_data::internal::*;\n\nfn rank_4(c: &mut Criterion) {\n    c.bench_function(\"rank_4\", |b| {\n        b.iter_with_setup(\n            || {\n                tract_ndarray::Array4::from_shape_simple_fn((256, 35, 35, 1), || 1.0f32)\n                    .permuted_axes([3, 2, 1, 0])\n            },\n            Tensor::from,\n        );\n    });\n}\n\ncriterion_group!(benches, rank_4);\ncriterion_main!(benches);\n"
  },
  {
    "path": "data/src/blob.rs",
    "content": "use num_traits::Zero;\n\nuse crate::{TractError, TractResult};\nuse std::alloc::*;\nuse std::fmt::Display;\nuse std::hash::Hash;\nuse std::ptr::null_mut;\n\n#[derive(Eq)]\npub struct Blob {\n    layout: std::alloc::Layout,\n    data: *mut u8,\n}\n\nimpl Default for Blob {\n    #[inline]\n    fn default() -> Blob {\n        Blob::from_bytes(&[]).unwrap()\n    }\n}\n\nimpl Clone for Blob {\n    #[inline]\n    fn clone(&self) -> Self {\n        Blob::from_bytes_alignment(self, self.layout.align()).unwrap()\n    }\n}\n\nimpl Drop for Blob {\n    #[inline]\n    fn drop(&mut self) {\n        if !self.data.is_null() {\n            unsafe { dealloc(self.data, self.layout) }\n        }\n    }\n}\n\nimpl PartialEq for Blob {\n    #[inline]\n    fn eq(&self, other: &Self) -> bool {\n        self.layout == other.layout && self.as_bytes() == other.as_bytes()\n    }\n}\n\nimpl Hash for Blob {\n    #[inline]\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.layout.align().hash(state);\n        self.as_bytes().hash(state);\n    }\n}\n\nimpl Blob {\n    #[inline]\n    pub unsafe fn new_for_size_and_align(size: usize, align: usize) -> Blob {\n        unsafe { Self::for_layout(Layout::from_size_align_unchecked(size, align)) }\n    }\n\n    #[inline]\n    pub unsafe fn ensure_size_and_align(&mut self, size: usize, align: usize) {\n        if size > self.layout.size() || align > self.layout.align() {\n            if !self.data.is_null() {\n                unsafe { std::alloc::dealloc(self.data as _, self.layout) };\n            }\n            self.layout = unsafe { Layout::from_size_align_unchecked(size, align) };\n            self.data = unsafe { std::alloc::alloc(self.layout) };\n            assert!(!self.data.is_null());\n        }\n    }\n\n    #[inline]\n    pub unsafe fn for_layout(layout: Layout) -> Blob {\n        let mut data = null_mut();\n        if layout.size() > 0 {\n            data = unsafe { alloc(layout) };\n            assert!(!data.is_null(), \"failed to allocate {layout:?}\");\n        }\n        Blob { layout, data }\n    }\n\n    #[inline]\n    pub fn from_bytes(s: &[u8]) -> TractResult<Blob> {\n        Self::from_bytes_alignment(s, 128)\n    }\n\n    #[inline]\n    pub fn as_bytes(&self) -> &[u8] {\n        if self.data.is_null() {\n            &[]\n        } else {\n            unsafe { std::slice::from_raw_parts(self.data, self.layout.size()) }\n        }\n    }\n\n    #[inline]\n    pub fn as_bytes_mut(&mut self) -> &mut [u8] {\n        if self.data.is_null() {\n            &mut []\n        } else {\n            unsafe { std::slice::from_raw_parts_mut(self.data, self.layout.size()) }\n        }\n    }\n\n    #[inline]\n    pub fn from_bytes_alignment(s: &[u8], alignment: usize) -> TractResult<Blob> {\n        unsafe {\n            let layout = Layout::from_size_align(s.len(), alignment)?;\n            let blob = Self::for_layout(layout);\n            if s.len() > 0 {\n                std::ptr::copy_nonoverlapping(s.as_ptr(), blob.data, s.len());\n            }\n            Ok(blob)\n        }\n    }\n\n    #[inline]\n    pub fn layout(&self) -> &Layout {\n        &self.layout\n    }\n}\n\nimpl std::ops::Deref for Blob {\n    type Target = [u8];\n    #[inline]\n    fn deref(&self) -> &[u8] {\n        self.as_bytes()\n    }\n}\n\nimpl std::ops::DerefMut for Blob {\n    #[inline]\n    fn deref_mut(&mut self) -> &mut [u8] {\n        self.as_bytes_mut()\n    }\n}\n\nimpl std::fmt::Display for Blob {\n    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {\n        assert!(self.data.is_null() == self.layout.size().is_zero());\n        write!(\n            fmt,\n            \"Blob of {} bytes (align @{}): {} {}\",\n            self.len(),\n            self.layout.align(),\n            String::from_utf8(\n                self.iter()\n                    .take(20)\n                    .copied()\n                    .flat_map(std::ascii::escape_default)\n                    .collect::<Vec<u8>>()\n            )\n            .unwrap(),\n            if self.len() >= 20 { \"[...]\" } else { \"\" }\n        )\n    }\n}\n\nimpl std::fmt::Debug for Blob {\n    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {\n        <Self as Display>::fmt(self, fmt)\n    }\n}\n\nimpl TryFrom<&[u8]> for Blob {\n    type Error = TractError;\n    #[inline]\n    fn try_from(s: &[u8]) -> Result<Blob, Self::Error> {\n        Blob::from_bytes(s)\n    }\n}\n\nunsafe impl Send for Blob {}\nunsafe impl Sync for Blob {}\n"
  },
  {
    "path": "data/src/datum.rs",
    "content": "//! `Tensor` is the main data container for tract\nuse crate::TVec;\nuse crate::dim::TDim;\nuse crate::internal::*;\nuse crate::tensor::Tensor;\nuse half::f16;\n#[cfg(feature = \"complex\")]\nuse num_complex::Complex;\nuse scan_fmt::scan_fmt;\nuse std::fmt;\nuse std::hash::Hash;\n\nuse num_traits::AsPrimitive;\n\n#[derive(Copy, Clone, PartialEq)]\npub enum QParams {\n    MinMax { min: f32, max: f32 },\n    ZpScale { zero_point: i32, scale: f32 },\n}\n\nimpl Eq for QParams {}\n\nimpl Ord for QParams {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        use QParams::*;\n        match (self, other) {\n            (MinMax { .. }, ZpScale { .. }) => std::cmp::Ordering::Less,\n            (ZpScale { .. }, MinMax { .. }) => std::cmp::Ordering::Greater,\n            (MinMax { min: min1, max: max1 }, MinMax { min: min2, max: max2 }) => {\n                min1.total_cmp(min2).then_with(|| max1.total_cmp(max2))\n            }\n            (\n                Self::ZpScale { zero_point: zp1, scale: s1 },\n                Self::ZpScale { zero_point: zp2, scale: s2 },\n            ) => zp1.cmp(zp2).then_with(|| s1.total_cmp(s2)),\n        }\n    }\n}\n\nimpl PartialOrd for QParams {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Default for QParams {\n    fn default() -> Self {\n        QParams::ZpScale { zero_point: 0, scale: 1. }\n    }\n}\n\n#[allow(clippy::derived_hash_with_manual_eq)]\nimpl Hash for QParams {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        match self {\n            QParams::MinMax { min, max } => {\n                0.hash(state);\n                min.to_bits().hash(state);\n                max.to_bits().hash(state);\n            }\n            QParams::ZpScale { zero_point, scale } => {\n                1.hash(state);\n                zero_point.hash(state);\n                scale.to_bits().hash(state);\n            }\n        }\n    }\n}\n\nimpl QParams {\n    pub fn zp_scale(&self) -> (i32, f32) {\n        match self {\n            QParams::MinMax { min, max } => {\n                let scale = (max - min) / 255.;\n                ((-(min + max) / 2. / scale) as i32, scale)\n            }\n            QParams::ZpScale { zero_point, scale } => (*zero_point, *scale),\n        }\n    }\n\n    pub fn q(&self, f: f32) -> i32 {\n        let (zp, scale) = self.zp_scale();\n        (f / scale) as i32 + zp\n    }\n\n    pub fn dq(&self, i: i32) -> f32 {\n        let (zp, scale) = self.zp_scale();\n        (i - zp) as f32 * scale\n    }\n}\n\nimpl std::fmt::Debug for QParams {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let (zp, scale) = self.zp_scale();\n        write!(f, \"Z:{zp} S:{scale}\")\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]\npub enum DatumType {\n    Bool,\n    U8,\n    U16,\n    U32,\n    U64,\n    I8,\n    I16,\n    I32,\n    I64,\n    F16,\n    F32,\n    F64,\n    TDim,\n    Blob,\n    String,\n    QI8(QParams),\n    QU8(QParams),\n    QI32(QParams),\n    #[cfg(feature = \"complex\")]\n    ComplexI16,\n    #[cfg(feature = \"complex\")]\n    ComplexI32,\n    #[cfg(feature = \"complex\")]\n    ComplexI64,\n    #[cfg(feature = \"complex\")]\n    ComplexF16,\n    #[cfg(feature = \"complex\")]\n    ComplexF32,\n    #[cfg(feature = \"complex\")]\n    ComplexF64,\n}\n\nimpl DatumType {\n    pub fn super_types(&self) -> TVec<DatumType> {\n        use DatumType::*;\n        if *self == String || *self == TDim || *self == Blob || *self == Bool || self.is_quantized()\n        {\n            return tvec!(*self);\n        }\n        #[cfg(feature = \"complex\")]\n        if self.is_complex_float() {\n            return [ComplexF16, ComplexF32, ComplexF64]\n                .iter()\n                .filter(|s| s.size_of() >= self.size_of())\n                .copied()\n                .collect();\n        } else if self.is_complex_signed() {\n            return [ComplexI16, ComplexI32, ComplexI64]\n                .iter()\n                .filter(|s| s.size_of() >= self.size_of())\n                .copied()\n                .collect();\n        }\n        if self.is_float() {\n            [F16, F32, F64].iter().filter(|s| s.size_of() >= self.size_of()).copied().collect()\n        } else if self.is_signed() {\n            [I8, I16, I32, I64, TDim]\n                .iter()\n                .filter(|s| s.size_of() >= self.size_of())\n                .copied()\n                .collect()\n        } else {\n            [U8, U16, U32, U64].iter().filter(|s| s.size_of() >= self.size_of()).copied().collect()\n        }\n    }\n\n    pub fn super_type_for(\n        i: impl IntoIterator<Item = impl std::borrow::Borrow<DatumType>>,\n    ) -> Option<DatumType> {\n        let mut iter = i.into_iter();\n        let mut current = match iter.next() {\n            None => return None,\n            Some(it) => *it.borrow(),\n        };\n        for n in iter {\n            match current.common_super_type(*n.borrow()) {\n                None => return None,\n                Some(it) => current = it,\n            }\n        }\n        Some(current)\n    }\n\n    pub fn common_super_type(&self, rhs: DatumType) -> Option<DatumType> {\n        for mine in self.super_types() {\n            for theirs in rhs.super_types() {\n                if mine == theirs {\n                    return Some(mine);\n                }\n            }\n        }\n        None\n    }\n\n    pub fn is_unsigned(&self) -> bool {\n        matches!(\n            self.unquantized(),\n            DatumType::U8 | DatumType::U16 | DatumType::U32 | DatumType::U64\n        )\n    }\n\n    pub fn is_signed(&self) -> bool {\n        matches!(\n            self.unquantized(),\n            DatumType::I8 | DatumType::I16 | DatumType::I32 | DatumType::I64\n        )\n    }\n\n    pub fn is_float(&self) -> bool {\n        matches!(self, DatumType::F16 | DatumType::F32 | DatumType::F64)\n    }\n\n    pub fn is_number(&self) -> bool {\n        self.is_signed() | self.is_unsigned() | self.is_float() | self.is_quantized()\n    }\n\n    pub fn is_tdim(&self) -> bool {\n        *self == DatumType::TDim\n    }\n\n    #[cfg(feature = \"complex\")]\n    pub fn is_complex(&self) -> bool {\n        self.is_complex_float() || self.is_complex_signed()\n    }\n\n    #[cfg(feature = \"complex\")]\n    pub fn is_complex_float(&self) -> bool {\n        matches!(self, DatumType::ComplexF16 | DatumType::ComplexF32 | DatumType::ComplexF64)\n    }\n\n    #[cfg(feature = \"complex\")]\n    pub fn is_complex_signed(&self) -> bool {\n        matches!(self, DatumType::ComplexI16 | DatumType::ComplexI32 | DatumType::ComplexI64)\n    }\n\n    #[cfg(feature = \"complex\")]\n    pub fn complexify(&self) -> TractResult<DatumType> {\n        match *self {\n            DatumType::I16 => Ok(DatumType::ComplexI16),\n            DatumType::I32 => Ok(DatumType::ComplexI32),\n            DatumType::I64 => Ok(DatumType::ComplexI64),\n            DatumType::F16 => Ok(DatumType::ComplexF16),\n            DatumType::F32 => Ok(DatumType::ComplexF32),\n            DatumType::F64 => Ok(DatumType::ComplexF64),\n            _ => bail!(\"No complex datum type formed on {:?}\", self),\n        }\n    }\n\n    #[cfg(feature = \"complex\")]\n    pub fn decomplexify(&self) -> TractResult<DatumType> {\n        match *self {\n            DatumType::ComplexI16 => Ok(DatumType::I16),\n            DatumType::ComplexI32 => Ok(DatumType::I32),\n            DatumType::ComplexI64 => Ok(DatumType::I64),\n            DatumType::ComplexF16 => Ok(DatumType::F16),\n            DatumType::ComplexF32 => Ok(DatumType::F32),\n            DatumType::ComplexF64 => Ok(DatumType::F64),\n            _ => bail!(\"{:?} is not a complex type\", self),\n        }\n    }\n\n    pub fn is_copy(&self) -> bool {\n        #[cfg(feature = \"complex\")]\n        if self.is_complex() {\n            return true;\n        }\n        *self == DatumType::Bool || self.is_unsigned() || self.is_signed() || self.is_float()\n    }\n\n    pub fn is_quantized(&self) -> bool {\n        self.qparams().is_some()\n    }\n\n    pub fn qparams(&self) -> Option<QParams> {\n        match self {\n            DatumType::QI8(qparams) | DatumType::QU8(qparams) | DatumType::QI32(qparams) => {\n                Some(*qparams)\n            }\n            _ => None,\n        }\n    }\n\n    pub fn with_qparams(&self, qparams: QParams) -> DatumType {\n        match self {\n            DatumType::QI8(_) => DatumType::QI8(qparams),\n            DatumType::QU8(_) => DatumType::QI8(qparams),\n            DatumType::QI32(_) => DatumType::QI32(qparams),\n            _ => *self,\n        }\n    }\n\n    pub fn quantize(&self, qparams: QParams) -> DatumType {\n        match self {\n            DatumType::I8 => DatumType::QI8(qparams),\n            DatumType::U8 => DatumType::QU8(qparams),\n            DatumType::I32 => DatumType::QI32(qparams),\n            DatumType::QI8(_) => DatumType::QI8(qparams),\n            DatumType::QU8(_) => DatumType::QU8(qparams),\n            DatumType::QI32(_) => DatumType::QI32(qparams),\n            _ => panic!(\"Can't quantize {self:?}\"),\n        }\n    }\n\n    #[inline(always)]\n    pub fn zp_scale(&self) -> (i32, f32) {\n        self.qparams().map(|q| q.zp_scale()).unwrap_or((0, 1.))\n    }\n\n    #[inline(always)]\n    pub fn with_zp_scale(&self, zero_point: i32, scale: f32) -> DatumType {\n        self.quantize(QParams::ZpScale { zero_point, scale })\n    }\n\n    pub fn unquantized(&self) -> DatumType {\n        match self {\n            DatumType::QI8(_) => DatumType::I8,\n            DatumType::QU8(_) => DatumType::U8,\n            DatumType::QI32(_) => DatumType::I32,\n            _ => *self,\n        }\n    }\n\n    pub fn integer(signed: bool, size: usize) -> Self {\n        use DatumType::*;\n        match (signed, size) {\n            (false, 8) => U8,\n            (false, 16) => U16,\n            (false, 32) => U32,\n            (false, 64) => U64,\n            (true, 8) => U8,\n            (true, 16) => U16,\n            (true, 32) => U32,\n            (true, 64) => U64,\n            _ => panic!(\"No integer for signed:{signed} size:{size}\"),\n        }\n    }\n\n    pub fn is_integer(&self) -> bool {\n        self.is_signed() || self.is_unsigned()\n    }\n\n    #[inline]\n    pub fn size_of(&self) -> usize {\n        dispatch_datum!(std::mem::size_of(self)())\n    }\n\n    pub fn min_value(&self) -> Tensor {\n        match self {\n            DatumType::QU8(_)\n            | DatumType::U8\n            | DatumType::U16\n            | DatumType::U32\n            | DatumType::U64 => Tensor::zero_dt(*self, &[1]).unwrap(),\n            DatumType::I8 | DatumType::QI8(_) => tensor0(i8::MIN),\n            DatumType::QI32(_) => tensor0(i32::MIN),\n            DatumType::I16 => tensor0(i16::MIN),\n            DatumType::I32 => tensor0(i32::MIN),\n            DatumType::I64 => tensor0(i64::MIN),\n            DatumType::F16 => tensor0(f16::MIN),\n            DatumType::F32 => tensor0(f32::MIN),\n            DatumType::F64 => tensor0(f64::MIN),\n            _ => panic!(\"No min value for datum type {self:?}\"),\n        }\n    }\n    pub fn max_value(&self) -> Tensor {\n        match self {\n            DatumType::U8 | DatumType::QU8(_) => tensor0(u8::MAX),\n            DatumType::U16 => tensor0(u16::MAX),\n            DatumType::U32 => tensor0(u32::MAX),\n            DatumType::U64 => tensor0(u64::MAX),\n            DatumType::I8 | DatumType::QI8(_) => tensor0(i8::MAX),\n            DatumType::I16 => tensor0(i16::MAX),\n            DatumType::I32 => tensor0(i32::MAX),\n            DatumType::I64 => tensor0(i64::MAX),\n            DatumType::QI32(_) => tensor0(i32::MAX),\n            DatumType::F16 => tensor0(f16::MAX),\n            DatumType::F32 => tensor0(f32::MAX),\n            DatumType::F64 => tensor0(f64::MAX),\n            _ => panic!(\"No max value for datum type {self:?}\"),\n        }\n    }\n\n    pub fn is<D: Datum>(&self) -> bool {\n        *self == D::datum_type()\n    }\n}\n\nimpl std::str::FromStr for DatumType {\n    type Err = TractError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        if let Ok((z, s)) = scan_fmt!(s, \"QU8(Z:{d} S:{f})\", i32, f32) {\n            Ok(DatumType::QU8(QParams::ZpScale { zero_point: z, scale: s }))\n        } else if let Ok((z, s)) = scan_fmt!(s, \"QI8(Z:{d} S:{f})\", i32, f32) {\n            Ok(DatumType::QI8(QParams::ZpScale { zero_point: z, scale: s }))\n        } else if let Ok((z, s)) = scan_fmt!(s, \"QI32(Z:{d} S:{f})\", i32, f32) {\n            Ok(DatumType::QI32(QParams::ZpScale { zero_point: z, scale: s }))\n        } else {\n            match s {\n                \"I8\" | \"i8\" => Ok(DatumType::I8),\n                \"I16\" | \"i16\" => Ok(DatumType::I16),\n                \"I32\" | \"i32\" => Ok(DatumType::I32),\n                \"I64\" | \"i64\" => Ok(DatumType::I64),\n                \"U8\" | \"u8\" => Ok(DatumType::U8),\n                \"U16\" | \"u16\" => Ok(DatumType::U16),\n                \"U32\" | \"u32\" => Ok(DatumType::U32),\n                \"U64\" | \"u64\" => Ok(DatumType::U64),\n                \"F16\" | \"f16\" => Ok(DatumType::F16),\n                \"F32\" | \"f32\" => Ok(DatumType::F32),\n                \"F64\" | \"f64\" => Ok(DatumType::F64),\n                \"Bool\" | \"bool\" => Ok(DatumType::Bool),\n                \"Blob\" | \"blob\" => Ok(DatumType::Blob),\n                \"String\" | \"string\" => Ok(DatumType::String),\n                \"TDim\" | \"tdim\" => Ok(DatumType::TDim),\n                #[cfg(feature = \"complex\")]\n                \"ComplexI16\" | \"complexi16\" => Ok(DatumType::ComplexI16),\n                #[cfg(feature = \"complex\")]\n                \"ComplexI32\" | \"complexi32\" => Ok(DatumType::ComplexI32),\n                #[cfg(feature = \"complex\")]\n                \"ComplexI64\" | \"complexi64\" => Ok(DatumType::ComplexI64),\n                #[cfg(feature = \"complex\")]\n                \"ComplexF16\" | \"complexf16\" => Ok(DatumType::ComplexF16),\n                #[cfg(feature = \"complex\")]\n                \"ComplexF32\" | \"complexf32\" => Ok(DatumType::ComplexF32),\n                #[cfg(feature = \"complex\")]\n                \"ComplexF64\" | \"complexf64\" => Ok(DatumType::ComplexF64),\n                _ => bail!(\"Unknown type {}\", s),\n            }\n        }\n    }\n}\n\nconst TOINT: f32 = 1.0f32 / f32::EPSILON;\n\npub fn round_ties_to_even(x: f32) -> f32 {\n    let u = x.to_bits();\n    let e = (u >> 23) & 0xff;\n    if e >= 0x7f + 23 {\n        return x;\n    }\n    let s = u >> 31;\n    let y = if s == 1 { x - TOINT + TOINT } else { x + TOINT - TOINT };\n    if y == 0.0 { if s == 1 { -0f32 } else { 0f32 } } else { y }\n}\n\n#[inline]\npub fn scale_by<T: Datum + AsPrimitive<f32>>(b: T, a: f32) -> T\nwhere\n    f32: AsPrimitive<T>,\n{\n    let b = b.as_();\n    (round_ties_to_even(b.abs() * a) * b.signum()).as_()\n}\n\npub trait ClampCast: PartialOrd + Copy + 'static {\n    #[inline(always)]\n    fn clamp_cast<O>(self) -> O\n    where\n        Self: AsPrimitive<O> + Datum,\n        O: AsPrimitive<Self> + num_traits::Bounded + Datum,\n    {\n        // this fails if we're upcasting, in which case clamping is useless\n        if O::min_value().as_() < O::max_value().as_() {\n            num_traits::clamp(self, O::min_value().as_(), O::max_value().as_()).as_()\n        } else {\n            self.as_()\n        }\n    }\n}\nimpl<T: PartialOrd + Copy + 'static> ClampCast for T {}\n\npub trait Datum:\n    Clone + Send + Sync + fmt::Debug + fmt::Display + Default + 'static + PartialEq\n{\n    fn name() -> &'static str;\n    fn datum_type() -> DatumType;\n    fn is<D: Datum>() -> bool;\n}\n\nmacro_rules! datum {\n    ($t:ty, $v:ident) => {\n        impl From<$t> for Tensor {\n            fn from(it: $t) -> Tensor {\n                tensor0(it)\n            }\n        }\n\n        impl Datum for $t {\n            fn name() -> &'static str {\n                stringify!($t)\n            }\n\n            fn datum_type() -> DatumType {\n                DatumType::$v\n            }\n\n            fn is<D: Datum>() -> bool {\n                Self::datum_type() == D::datum_type()\n            }\n        }\n    };\n}\n\ndatum!(bool, Bool);\ndatum!(f16, F16);\ndatum!(f32, F32);\ndatum!(f64, F64);\ndatum!(i8, I8);\ndatum!(i16, I16);\ndatum!(i32, I32);\ndatum!(i64, I64);\ndatum!(u8, U8);\ndatum!(u16, U16);\ndatum!(u32, U32);\ndatum!(u64, U64);\ndatum!(TDim, TDim);\ndatum!(String, String);\ndatum!(crate::blob::Blob, Blob);\n#[cfg(feature = \"complex\")]\ndatum!(Complex<i16>, ComplexI16);\n#[cfg(feature = \"complex\")]\ndatum!(Complex<i32>, ComplexI32);\n#[cfg(feature = \"complex\")]\ndatum!(Complex<i64>, ComplexI64);\n#[cfg(feature = \"complex\")]\ndatum!(Complex<f16>, ComplexF16);\n#[cfg(feature = \"complex\")]\ndatum!(Complex<f32>, ComplexF32);\n#[cfg(feature = \"complex\")]\ndatum!(Complex<f64>, ComplexF64);\n\n#[cfg(test)]\nmod tests {\n    use crate::internal::*;\n    use ndarray::arr1;\n\n    #[test]\n    fn test_array_to_tensor_to_array() {\n        let array = arr1(&[12i32, 42]);\n        let tensor = Tensor::from(array.clone());\n        let view = tensor.to_plain_array_view::<i32>().unwrap();\n        assert_eq!(array, view.into_dimensionality().unwrap());\n    }\n\n    #[test]\n    fn test_cast_dim_to_dim() {\n        let t_dim: Tensor = tensor1(&[12isize.to_dim(), 42isize.to_dim()]);\n        let t_i32 = t_dim.cast_to::<i32>().unwrap();\n        let t_dim_2 = t_i32.cast_to::<TDim>().unwrap().into_owned();\n        assert_eq!(t_dim, t_dim_2);\n    }\n\n    #[test]\n    fn test_cast_i32_to_dim() {\n        let t_i32: Tensor = tensor1(&[0i32, 12]);\n        t_i32.cast_to::<TDim>().unwrap();\n    }\n\n    #[test]\n    fn test_cast_i64_to_bool() {\n        let t_i64: Tensor = tensor1(&[0i64]);\n        t_i64.cast_to::<bool>().unwrap();\n    }\n\n    #[test]\n    fn test_parse_qu8() {\n        assert_eq!(\n            \"QU8(Z:128 S:0.01)\".parse::<DatumType>().unwrap(),\n            DatumType::QU8(QParams::ZpScale { zero_point: 128, scale: 0.01 })\n        );\n    }\n}\n"
  },
  {
    "path": "data/src/dim/assertion.rs",
    "content": "use fmt::Display;\n\nuse super::*;\n\n#[derive(Debug, PartialEq, Clone, Hash)]\n#[allow(clippy::upper_case_acronyms)]\npub enum Assertion {\n    Eq(TDim, TDim),\n    LT(TDim, TDim),\n    GT(TDim, TDim),\n    LTE(TDim, TDim),\n    GTE(TDim, TDim),\n}\n\nimpl Display for Assertion {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        use Assertion::*;\n        match self {\n            Eq(l, r) => write!(f, \"{l} == {r}\"),\n            LT(l, r) => write!(f, \"{l} < {r}\"),\n            GT(l, r) => write!(f, \"{l} > {r}\"),\n            LTE(l, r) => write!(f, \"{l} <= {r}\"),\n            GTE(l, r) => write!(f, \"{l} >= {r}\"),\n        }\n    }\n}\n\nimpl Assertion {\n    pub fn as_known_positive(&self) -> Option<TDim> {\n        use Assertion::*;\n        match self {\n            Eq(left, right) => Some(left.clone() - right),\n            GTE(left, right) => Some(left.clone() - right),\n            GT(left, right) => Some(left.clone() - 1 - right),\n            LTE(left, right) => Some(right.clone() - left),\n            LT(left, right) => Some(right.clone() - 1 - left),\n        }\n    }\n\n    pub fn check(&self, values: &SymbolValues) -> Option<bool> {\n        use Assertion::*;\n        match self {\n            Eq(left, right) => {\n                (left.eval(values) - right.eval(values)).to_i64().ok().map(|d| d == 0)\n            }\n            GTE(left, right) => {\n                (left.eval(values) - right.eval(values)).to_i64().ok().map(|d| d >= 0)\n            }\n            GT(left, right) => {\n                (left.eval(values) - right.eval(values)).to_i64().ok().map(|d| d > 0)\n            }\n            LTE(left, right) => {\n                (left.eval(values) - right.eval(values)).to_i64().ok().map(|d| d <= 0)\n            }\n            LT(left, right) => {\n                (left.eval(values) - right.eval(values)).to_i64().ok().map(|d| d < 0)\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    #[test]\n    fn use_equalities() {\n        let s = SymbolScope::default();\n        s.add_assertion(\"s==0\").unwrap();\n        assert!(s.parse_tdim(\"s\").unwrap().simplify().is_zero());\n    }\n\n    #[test]\n    fn prove_positive_with_axiom() {\n        let s = SymbolScope::default();\n        s.add_assertion(\"s>=0\").unwrap();\n        assert!(s.parse_tdim(\"s\").unwrap().prove_positive_or_zero());\n    }\n\n    #[test]\n    fn prove_positive_with_axiom_2() {\n        let s = SymbolScope::default();\n        s.add_assertion(\"s>=0\").unwrap();\n        s.add_assertion(\"p>=0\").unwrap();\n        s.add_assertion(\"p+s<4096\").unwrap();\n        assert!(s.parse_tdim(\"4096-p\").unwrap().prove_positive_or_zero());\n    }\n\n    #[test]\n    fn min_max_with_axiom() {\n        let symbols = SymbolScope::default();\n        symbols.add_assertion(\"a>=0\").unwrap();\n        assert_eq!(symbols.parse_tdim(\"min(a,0)\").unwrap().simplify(), 0.into());\n        assert_eq!(\n            symbols.parse_tdim(\"max(a,0)\").unwrap().simplify(),\n            symbols.parse_tdim(\"a\").unwrap()\n        );\n    }\n\n    #[test]\n    fn low_bound_0() -> TractResult<()> {\n        let symbols = SymbolScope::default().with_assertion(\"S>=0\")?;\n        let s = symbols.parse_tdim(\"S\").unwrap();\n        assert_eq!(s.low_inclusive_bound(), Some(0));\n        Ok(())\n    }\n\n    #[test]\n    fn low_bound_1() -> TractResult<()> {\n        let symbols = SymbolScope::default().with_assertion(\"S>0\")?;\n        assert_eq!(symbols.parse_tdim(\"S\").unwrap().low_inclusive_bound(), Some(1));\n        Ok(())\n    }\n\n    #[test]\n    fn low_bound_2() -> TractResult<()> {\n        let symbols = SymbolScope::default().with_assertion(\"S>0\")?;\n        assert_eq!(symbols.parse_tdim(\"S + 1\").unwrap().low_inclusive_bound(), Some(2));\n        Ok(())\n    }\n\n    #[test]\n    fn low_bound_3() -> TractResult<()> {\n        let symbols = SymbolScope::default().with_assertion(\"S>0\")?;\n        assert_eq!(symbols.parse_tdim(\"4*S\").unwrap().low_inclusive_bound(), Some(4));\n        Ok(())\n    }\n\n    #[test]\n    fn low_bound_4() -> TractResult<()> {\n        let symbols = SymbolScope::default().with_assertion(\"S>0\")?.with_assertion(\"S>5\")?;\n        assert_eq!(symbols.parse_tdim(\"S + 3\").unwrap().low_inclusive_bound(), Some(9));\n        Ok(())\n    }\n\n    #[test]\n    fn max_bug_1() {\n        let symbols = SymbolScope::default();\n        symbols.add_assertion(\"S>8\").unwrap();\n        assert_eq!(\n            symbols.parse_tdim(\"max(1,-1+(S+1)/4)\").unwrap().simplify(),\n            symbols.parse_tdim(\"-1+(S+1)/4\").unwrap(),\n        );\n    }\n\n    #[test]\n    fn min_bug_1() {\n        let symbols = SymbolScope::default();\n        symbols.add_assertion(\"S>8\").unwrap();\n        assert_eq!(\n            symbols.parse_tdim(\"min(1,-1+(S+1)/4)\").unwrap().simplify(),\n            symbols.parse_tdim(\"1\").unwrap()\n        );\n    }\n\n    #[test]\n    fn min_bug_2() {\n        let symbols = SymbolScope::default();\n        symbols.add_assertion(\"S>50\").unwrap();\n        assert_eq!(\n            symbols.parse_tdim(\"min(-3+2*(S+1)/4,-1+(S+1)/4)\").unwrap().simplify(),\n            symbols.parse_tdim(\"-1+(S+1)/4\").unwrap()\n        );\n    }\n\n    #[test]\n    fn min_bug_3() {\n        let symbols = SymbolScope::default();\n        symbols.add_assertion(\"S>=0\").unwrap();\n        symbols.add_assertion(\"P>=0\").unwrap();\n        assert_eq!(\n            symbols.parse_tdim(\"min(0,(S)#(P+S))\").unwrap().simplify(),\n            symbols.parse_tdim(\"0\").unwrap()\n        );\n    }\n\n    #[test]\n    fn guess_scenario() -> TractResult<()> {\n        let symbols = SymbolScope::default()\n            .with_assertion(\"S>=0\")?\n            .with_assertion(\"P>=0\")?\n            .with_scenario_assertion(\"tg\", \"S==1\")?\n            .with_scenario_assertion(\"pp\", \"P==0\")?;\n        let s = symbols.sym(\"S\");\n        let p = symbols.sym(\"P\");\n        assert_eq!(symbols.guess_scenario(&SymbolValues::default())?, None);\n        assert_eq!(symbols.guess_scenario(&SymbolValues::default().with(&s, 50))?, Some(1));\n        assert_eq!(symbols.guess_scenario(&SymbolValues::default().with(&p, 50))?, Some(0));\n        assert!(\n            symbols.guess_scenario(&SymbolValues::default().with(&p, 50).with(&s, 50)).is_err()\n        );\n        Ok(())\n    }\n\n    #[test]\n    fn min_llm_0() -> TractResult<()> {\n        let symbols = SymbolScope::default()\n            .with_assertion(\"S>=0\")?\n            .with_assertion(\"P>=0\")?\n            .with_scenario_assertion(\"tg\", \"S==1\")?\n            .with_scenario_assertion(\"pp\", \"P==0\")?;\n        assert_eq!(\n            symbols.parse_tdim(\"min(P,(S)#(P+S))\").unwrap().simplify(),\n            symbols.parse_tdim(\"P\").unwrap()\n        );\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "data/src/dim/mod.rs",
    "content": "//! Extended dimension support\nuse crate::internal::*;\nuse num_traits::{One, Zero};\nuse std::fmt;\nuse std::ops;\n\nmod assertion;\nmod parse;\nmod resolve;\nmod sym;\nmod tree;\n\npub use self::assertion::Assertion;\npub use self::parse::parse_tdim;\npub use self::resolve::solve_for;\npub use self::sym::{Symbol, SymbolScope, SymbolValues};\npub use self::tree::{TDim, TooEarly};\n\nuse crate::{TractError, TractResult};\n\n/// A super-trait for value acting as tensor dimensions in tract.\n///\n/// Implemented by:\n///\n/// * `usize` for regular dimensions\n/// * `TDim` supporting regular and streaming dimensions\npub trait DimLike:\n    Clone\n    + Default\n    + PartialEq\n    + From<usize>\n    + for<'a> std::convert::TryFrom<&'a TDim, Error = TractError>\n    + ::num_traits::Zero\n    + fmt::Debug\n    + fmt::Display\n    + std::hash::Hash\n    + ops::Add<Self, Output = Self>\n    + ops::Add<usize, Output = Self>\n    + for<'a> ops::Add<&'a Self, Output = Self>\n    + ops::Sub<Self, Output = Self>\n    + ops::Sub<usize, Output = Self>\n    + for<'a> ops::Sub<&'a Self, Output = Self>\n    + ops::Mul<Self, Output = Self>\n    + ops::Mul<usize, Output = Self>\n    + for<'a> ops::Mul<&'a Self, Output = Self>\n    + ops::Div<usize, Output = Self>\n    + ops::Rem<usize, Output = Self>\n    + Send\n    + Sync\n    + 'static\n    + std::iter::Sum\n    + std::iter::Product\n    + ToDim\n    + One\n{\n    fn maybe_div(&self, other: &Self) -> TractResult<(Self, u64)>;\n\n    /// Integer divise, rounding up to next integer.\n    fn divceil(&self, other: usize) -> Self {\n        (self.clone() + other - 1) / other\n    }\n\n    /// Convert to regular integer.\n    fn to_i64(&self) -> TractResult<i64>;\n\n    fn to_usize(&self) -> TractResult<usize> {\n        self.to_i64().map(|d| d as usize)\n    }\n\n    fn to_isize(&self) -> TractResult<isize> {\n        self.to_i64().map(|d| d as isize)\n    }\n\n    fn to_i32(&self) -> TractResult<i32> {\n        self.to_i64().map(|d| d as i32)\n    }\n\n    /// Substitute as many symbols as possible in the dim value.\n    fn eval(&self, values: &SymbolValues) -> Self;\n\n    /// Full evaluation of the symbol, failing if a symbol is missing\n    fn eval_to_i64(&self, values: &SymbolValues) -> TractResult<i64>;\n\n    fn substitute(&self, from: &Symbol, to: &Self) -> TractResult<Self>;\n    fn substitute_all(&self, map: &std::collections::HashMap<Symbol, Self>) -> TractResult<Self>;\n\n    fn broadcast(self, other: Self) -> TractResult<Self>;\n    fn mini(self, other: Self) -> Self;\n    fn maxi(self, other: Self) -> Self;\n\n    fn compatible_with(&self, other: &Self) -> bool;\n}\n\nimpl DimLike for TDim {\n    fn maybe_div(&self, other: &Self) -> TractResult<(Self, u64)> {\n        if self.is_zero() {\n            return Ok((TDim::zero(), 1));\n        } else if other.is_zero() {\n            bail!(\"Division by zero\")\n        }\n        fn expand(dim: &TDim) -> (i64, Vec<TDim>) {\n            match dim {\n                TDim::Mul(terms) => terms.iter().map(expand).fold((1i64, vec![]), |acc, t| {\n                    (acc.0 * t.0, acc.1.into_iter().chain(t.1).collect())\n                }),\n                TDim::MulInt(a, terms) => {\n                    let (b, v) = expand(terms);\n                    (a * b, v)\n                }\n                TDim::Val(x) => (*x, vec![]),\n                TDim::Add(terms) => {\n                    let gcd =\n                        terms.iter().map(expand).map(|(n, _)| n).reduce(|a, b| a.gcd(&b)).unwrap();\n                    (\n                        gcd,\n                        vec![TDim::Add(terms.iter().map(|t| t.clone() / gcd).collect()).simplify()],\n                    )\n                }\n                it => (1, vec![it.clone()]),\n            }\n        }\n        let (mut num_int, mut num) = expand(self);\n        let (mut denum_int, mut denum) = expand(other);\n        if num == denum {\n            num = vec![];\n            denum = vec![];\n        }\n        for it in denum {\n            if let Some(pos) = num.iter().position(|n| n == &it) {\n                num.remove(pos);\n            } else {\n                bail!(\"Can't divide {} by {}\", self, other)\n            }\n        }\n        use num_integer::Integer;\n        if denum_int < 0 {\n            num_int *= -1;\n            denum_int *= -1;\n        }\n        let gcd = num_int.gcd(&denum_int);\n        num_int /= gcd;\n        denum_int /= gcd;\n        Ok(((TDim::Mul(num) * num_int).reduce(), denum_int as u64))\n    }\n\n    fn to_i64(&self) -> TractResult<i64> {\n        TDim::to_i64(self)\n    }\n\n    fn eval(&self, values: &SymbolValues) -> Self {\n        self.eval(values)\n    }\n\n    fn substitute(&self, from: &Symbol, to: &Self) -> TractResult<Self> {\n        self.substitute(from, to)\n    }\n\n    fn substitute_all(&self, map: &std::collections::HashMap<Symbol, Self>) -> TractResult<Self> {\n        TDim::substitute_all(self, map)\n    }\n\n    fn eval_to_i64(&self, values: &SymbolValues) -> TractResult<i64> {\n        TDim::eval_to_i64(self, values)\n    }\n\n    fn broadcast(self, other: Self) -> TractResult<Self> {\n        if self.is_one() {\n            Ok(other)\n        } else if other.is_one() {\n            Ok(self)\n        } else {\n            Ok(TDim::Broadcast(vec![self, other]).simplify())\n        }\n    }\n\n    fn compatible_with(&self, other: &Self) -> bool {\n        self.compatible_with(other)\n    }\n\n    fn mini(self, other: Self) -> Self {\n        TDim::Min(vec![self, other]).simplify()\n    }\n\n    fn maxi(self, other: Self) -> Self {\n        TDim::Max(vec![self, other]).simplify()\n    }\n}\n\nimpl<'a> std::convert::TryFrom<&'a TDim> for TDim {\n    type Error = TractError;\n    fn try_from(d: &'a TDim) -> TractResult<TDim> {\n        Ok(d.clone())\n    }\n}\n\nimpl DimLike for usize {\n    fn maybe_div(&self, other: &Self) -> TractResult<(Self, u64)> {\n        use num_integer::Integer;\n        let gcd = self.gcd(other);\n        Ok((self / gcd, (other / gcd) as u64))\n    }\n\n    fn to_i64(&self) -> TractResult<i64> {\n        Ok(*self as i64)\n    }\n\n    fn eval(&self, _values: &SymbolValues) -> Self {\n        *self\n    }\n\n    fn substitute(&self, _from: &Symbol, _to: &Self) -> TractResult<Self> {\n        Ok(*self)\n    }\n\n    fn substitute_all(&self, _map: &std::collections::HashMap<Symbol, Self>) -> TractResult<Self> {\n        Ok(*self)\n    }\n\n    fn eval_to_i64(&self, _: &SymbolValues) -> TractResult<i64> {\n        Ok(*self as i64)\n    }\n\n    fn broadcast(self, other: Self) -> TractResult<Self> {\n        if self == 1 || self == other {\n            Ok(other)\n        } else if other == 1 {\n            Ok(self)\n        } else {\n            bail!(\"Can not broadcast {self} against {other}\")\n        }\n    }\n\n    fn compatible_with(&self, other: &Self) -> bool {\n        self == other\n    }\n\n    fn mini(self, other: Self) -> Self {\n        if self < other { self } else { other }\n    }\n\n    fn maxi(self, other: Self) -> Self {\n        if self > other { self } else { other }\n    }\n}\n\nimpl<'a> std::convert::TryFrom<&'a TDim> for usize {\n    type Error = TractError;\n    fn try_from(d: &'a TDim) -> TractResult<usize> {\n        d.to_usize()\n    }\n}\n\n/// Convenience trait to convert values to TDim.\npub trait ToDim {\n    /// Convert self to a TDim.\n    fn to_dim(&self) -> TDim;\n}\n\nimpl<I: Into<TDim> + Clone> ToDim for I {\n    fn to_dim(&self) -> TDim {\n        self.clone().into()\n    }\n}\n\nimpl ToDim for &TDim {\n    fn to_dim(&self) -> TDim {\n        (*self).clone()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    lazy_static::lazy_static! {\n        static ref S: (SymbolScope, Symbol) = {\n            let table = SymbolScope::default();\n            let s = table.new_with_prefix(\"S\");\n            (table, s)\n        };\n    }\n\n    pub fn s() -> TDim {\n        S.1.clone().into()\n    }\n\n    #[test]\n    fn div() {\n        assert_eq!(TDim::from(12).maybe_div(&TDim::from(4)).unwrap(), (3.into(), 1));\n    }\n\n    #[test]\n    fn div_sym_int() {\n        assert_eq!((s() * 12).maybe_div(&TDim::from(4)).unwrap(), (s() * 3, 1));\n    }\n\n    #[test]\n    fn div_sym_sym() {\n        assert_eq!((s() * 12).maybe_div(&(s() * 4)).unwrap(), (3.into(), 1));\n    }\n\n    #[test]\n    fn div_sym_sym_ratio() {\n        assert_eq!((s() * 13).maybe_div(&(s() * 4)).unwrap(), (13.into(), 4));\n    }\n\n    #[test]\n    fn div_sym_sym_rem() {\n        assert!((s() + 1).maybe_div(&(s() * 4)).is_err());\n    }\n\n    #[test]\n    fn div_sym_sym_simply_1() {\n        assert_eq!((s()).maybe_div(&(s())).unwrap(), (TDim::Val(1), 1));\n    }\n\n    #[test]\n    fn div_sym_sym_complex() {\n        let s = s();\n        let b = S.0.sym(\"b\");\n        assert_eq!(\n            (256.to_dim() * &s * &b).maybe_div(&(1.to_dim() * &s * &b)).unwrap(),\n            (256.into(), 1)\n        );\n    }\n\n    #[test]\n    fn div_sym_sym_with_add() {\n        assert_eq!((s() * 80 - 160).maybe_div(&(s() - 2)).unwrap(), (80.into(), 1));\n    }\n}\n"
  },
  {
    "path": "data/src/dim/parse.rs",
    "content": "use super::*;\nuse nom::branch::alt;\nuse nom::bytes::complete::tag;\nuse nom::character::complete::{alpha1, alphanumeric1, digit1, one_of};\nuse nom::combinator::{all_consuming, map, map_res, recognize};\nuse nom::multi::{fold, many0, separated_list0};\nuse nom::sequence::{delimited, pair, preceded, separated_pair};\nuse nom::{IResult, Parser};\nuse nom_language::error::VerboseError;\n\ntype R<'i, O> = IResult<&'i str, O, VerboseError<&'i str>>;\n\npub fn parse_tdim(symbol_table: &SymbolScope, input: &str) -> TractResult<TDim> {\n    match all_consuming(|i| expr(symbol_table, i)).parse(input) {\n        Ok(pair) => Ok(pair.1),\n        Err(e) => bail!(\"Failed to parse {:?}, {:?}\", input, e),\n    }\n}\n\npub fn parse_assertion(symbol_table: &SymbolScope, input: &str) -> TractResult<Assertion> {\n    match all_consuming(|i| assertion(symbol_table, i)).parse(input) {\n        Ok(pair) => Ok(pair.1),\n        Err(e) => bail!(\"Failed to parse {:?}, {:?}\", input, e),\n    }\n}\n\nfn assertion<'i>(s: &SymbolScope, i: &'i str) -> R<'i, Assertion> {\n    delimited(\n        spaces,\n        alt((\n            map(separated_pair(|i| expr(s, i), stag(\"==\"), |i| expr(s, i)), |(a, b)| {\n                Assertion::Eq(a, b)\n            }),\n            map(separated_pair(|i| expr(s, i), stag(\"<=\"), |i| expr(s, i)), |(a, b)| {\n                Assertion::LTE(a, b)\n            }),\n            map(separated_pair(|i| expr(s, i), stag(\">=\"), |i| expr(s, i)), |(a, b)| {\n                Assertion::GTE(a, b)\n            }),\n            map(separated_pair(|i| expr(s, i), stag(\"<\"), |i| expr(s, i)), |(a, b)| {\n                Assertion::LT(a, b)\n            }),\n            map(separated_pair(|i| expr(s, i), stag(\">\"), |i| expr(s, i)), |(a, b)| {\n                Assertion::GT(a, b)\n            }),\n        )),\n        spaces,\n    )\n    .parse(i)\n}\n\nfn expr<'i>(symbol_table: &SymbolScope, i: &'i str) -> R<'i, TDim> {\n    broadcast(symbol_table, i)\n}\n\nfn broadcast<'i>(symbol_table: &SymbolScope, input: &'i str) -> R<'i, TDim> {\n    let s = symbol_table;\n    let (mut input, mut result) = add(s, input)?;\n    while let Ok((i, _)) = stag(\"#\").parse(input) {\n        let (i, next) = map_res(|i| add(s, i), |v| result.clone().broadcast(v)).parse(i)?;\n        (input, result) = (i, next);\n    }\n    Ok((input, result))\n}\n\nmacro_rules! bin {\n    ($name: ident, $left: expr, $right: expr, $op: expr, $builder: expr) => {\n        fn $name<'i>(symbol_table: &SymbolScope, input: &'i str) -> R<'i, TDim> {\n            let s = symbol_table;\n            let (input, result) = $left(s, input)?;\n            fold(0.., preceded(stag($op), |i| $right(s, i)), move || result.clone(), $builder)\n                .parse(input)\n        }\n    };\n}\n\nbin!(add, sub, sub, \"+\", |a, b| a + b);\nbin!(sub, mul, mul, \"-\", |a, b| a - b);\nbin!(mul, div, div, \"*\", |a, b| a * b);\nbin!(div, atom, |_s, i| numeric(i), \"/\", |a, b| a / b);\n\nfn atom<'i>(symbol_table: &SymbolScope, i: &'i str) -> R<'i, TDim> {\n    alt((\n        map(numeric, TDim::Val),\n        map(|i| func(symbol_table, \"min\", i), TDim::Min),\n        map(|i| func(symbol_table, \"max\", i), TDim::Max),\n        map(|i| func(symbol_table, \"floor\", i), |xs| xs[0].clone()),\n        map(|i| identifier(symbol_table, i), TDim::Sym),\n        map(pair(recognize(stag(\"-\")), |i| atom(symbol_table, i)), |(_, dim)| dim * -1),\n        delimited(stag(\"(\"), |i| expr(symbol_table, i), stag(\")\")),\n    ))\n    .parse(i)\n}\n\nfn func<'i>(symbol_table: &SymbolScope, name: &'static str, i: &'i str) -> R<'i, Vec<TDim>> {\n    preceded(\n        stag(name),\n        delimited(stag(\"(\"), separated_list0(stag(\",\"), |i| expr(symbol_table, i)), stag(\")\")),\n    )\n    .parse(i)\n}\n\nfn identifier<'i>(symbol_table: &SymbolScope, i: &'i str) -> R<'i, Symbol> {\n    map(\n        recognize(pair(alt((alpha1, tag(\"_\"))), many0(alt((alphanumeric1, tag(\"_\"), tag(\".\")))))),\n        |s| symbol_table.sym(s),\n    )\n    .parse(i)\n}\n\nfn numeric(i: &str) -> R<'_, i64> {\n    map_res(digit1, std::str::FromStr::from_str).parse(i)\n}\n\nfn spaces(i: &str) -> R<'_, ()> {\n    map(many0(one_of(\" \\t\\n\\r\")), |_| ()).parse(i)\n}\n\nfn spaced<'s, O, P>(it: P) -> impl Parser<&'s str, Output = O, Error = VerboseError<&'s str>>\nwhere\n    P: Parser<&'s str, Output = O, Error = VerboseError<&'s str>>,\n{\n    delimited(spaces, it, spaces)\n}\n\npub(super) fn stag<'s>(\n    t: &'static str,\n) -> impl Parser<&'s str, Output = &'s str, Error = VerboseError<&'s str>> {\n    spaced(tag(t))\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn parse_int() {\n        let table = SymbolScope::default();\n        assert_eq!(parse_tdim(&table, \"12\").unwrap(), TDim::Val(12));\n        assert_eq!(parse_tdim(&table, \"-12\").unwrap(), TDim::Val(-12));\n    }\n\n    #[test]\n    fn parse_sym() {\n        let table = SymbolScope::default();\n        assert_eq!(parse_tdim(&table, \"x\").unwrap(), TDim::Sym(table.sym(\"x\")));\n        assert_eq!(\n            parse_tdim(&table, \"-y\").unwrap(),\n            TDim::MulInt(-1, Box::new(table.sym(\"y\").into()))\n        );\n    }\n\n    #[test]\n    fn parse_bin() {\n        let table = SymbolScope::default();\n        assert_eq!(parse_tdim(&table, \"1+2\").unwrap(), 3.into());\n        assert_eq!(parse_tdim(&table, \"1-2\").unwrap(), (-1).into());\n        assert_eq!(parse_tdim(&table, \"1*2\").unwrap(), 2.into());\n        assert_eq!(parse_tdim(&table, \"1/2\").unwrap(), 0.into());\n    }\n\n    #[test]\n    fn parse_prio() {\n        let table = SymbolScope::default();\n        assert_eq!(parse_tdim(&table, \"1+2*3\").unwrap(), 7.into());\n        assert_eq!(parse_tdim(&table, \"1*2+3\").unwrap(), 5.into());\n    }\n\n    #[test]\n    fn parse_min() {\n        let table = SymbolScope::default();\n        assert_eq!(\n            parse_tdim(&table, \"min(P,S)\").unwrap(),\n            TDim::Min(vec!(table.sym(\"P\").into(), table.sym(\"S\").into()))\n        );\n    }\n\n    #[test]\n    fn parse_inequality_0() {\n        let table = SymbolScope::default();\n        assert_eq!(\n            parse_assertion(&table, \"P+S<4096\").unwrap(),\n            Assertion::LT(parse_tdim(&table, \"P+S\").unwrap(), 4096.to_dim())\n        );\n    }\n\n    #[test]\n    fn parse_dot_ids() {\n        let table = SymbolScope::default();\n        assert_eq!(parse_tdim(&table, \"dot.0\").unwrap(), table.sym(\"dot.0\").into());\n    }\n\n    #[test]\n    fn parse_dot_ids_arith() {\n        let table = SymbolScope::default();\n        assert_eq!(parse_tdim(&table, \"dot.0/2\").unwrap(), table.sym(\"dot.0\").to_dim() / 2);\n    }\n\n    #[test]\n    fn parse_floors() {\n        let table = SymbolScope::default();\n        assert_eq!(parse_tdim(&table, \"floor(a)\").unwrap(), table.sym(\"a\").to_dim());\n    }\n}\n"
  },
  {
    "path": "data/src/dim/resolve.rs",
    "content": "use tract_num_traits::Zero;\n\nuse crate::internal::*;\n\npub fn solve_for(sym: &Symbol, left: &TDim, right: &TDim) -> Option<TDim> {\n    if !left.symbols().contains(sym) && !right.symbols().contains(sym) {\n        return None;\n    }\n    if right.symbols().contains(sym) {\n        return solve_for(sym, &(left.clone() - right), &0.to_dim());\n    }\n    match left {\n        TDim::Sym(s) => {\n            if s == sym {\n                Some(right.clone())\n            } else {\n                None\n            }\n        }\n        TDim::Add(terms) => {\n            let consts: TDim = terms.iter().filter(|t| !t.symbols().contains(sym)).sum();\n            if consts.is_zero() {\n                None\n            } else {\n                solve_for(sym, &(left.clone() - &consts), &(right.clone() - &consts))\n            }\n        }\n        TDim::MulInt(z, a) => {\n            let gcd = right.gcd();\n            if gcd % z.unsigned_abs() == 0 {\n                solve_for(sym, a, &(right.clone() / *z))\n            } else {\n                None\n            }\n        }\n        _ => None,\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use super::{SymbolScope, parse_tdim};\n\n    lazy_static::lazy_static!(\n        static ref TABLE:SymbolScope = SymbolScope::default();\n        static ref A:Symbol = TABLE.sym(\"a\");\n    );\n\n    fn p(s: &str) -> TDim {\n        parse_tdim(&TABLE, s).unwrap()\n    }\n\n    #[test]\n    fn trivial() {\n        assert_eq!(solve_for(&A, &p(\"a\"), &p(\"3\")), Some(3i32.to_dim()));\n    }\n\n    #[test]\n    fn negative() {\n        assert_eq!(solve_for(&A, &p(\"a + 3\"), &p(\"0\")), Some(-(3i32.to_dim())));\n    }\n\n    #[test]\n    fn swap() {\n        assert_eq!(solve_for(&A, &p(\"3\"), &p(\"a\")), Some(3i32.to_dim()));\n    }\n\n    #[test]\n    fn scale() {\n        assert_eq!(solve_for(&A, &p(\"3 * a\"), &p(\"6\")), Some(2.to_dim()));\n    }\n\n    #[test]\n    fn ax_plus_b() {\n        assert_eq!(solve_for(&A, &p(\"3 * a + 1\"), &p(\"7\")), Some(2.to_dim()));\n    }\n\n    #[test]\n    fn both_sides() {\n        assert_eq!(solve_for(&A, &p(\"3 * a + 1\"), &p(\"2 * a\")), Some((-1).to_dim()));\n    }\n\n    #[test]\n    fn x_over_n() {\n        assert_eq!(solve_for(&A, &p(\"a/4\"), &p(\"2\")), None);\n    }\n\n    #[test]\n    fn with_symbols() {\n        assert_eq!(solve_for(&A, &p(\"a + 1\"), &p(\"b\")), Some(p(\"b-1\")));\n    }\n}\n"
  },
  {
    "path": "data/src/dim/sym.rs",
    "content": "use itertools::Itertools;\nuse parking_lot::ReentrantMutex;\nuse std::cell::RefCell;\nuse std::collections::HashMap;\nuse std::fmt::{self, Display};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::{Arc, Weak};\nuse string_interner::DefaultStringInterner;\nuse string_interner::Symbol as _;\n\nuse crate::TractResult;\n\nuse super::parse::parse_assertion;\nuse super::{Assertion, TDim, parse_tdim};\n\nstatic SCOPE_COUNTER: AtomicUsize = AtomicUsize::new(0);\n\n#[derive(Clone, Default)]\npub struct SymbolScope(pub Arc<ReentrantMutex<RefCell<SymbolScopeData>>>);\n\nimpl PartialEq for SymbolScope {\n    fn eq(&self, other: &Self) -> bool {\n        Arc::ptr_eq(&self.0, &other.0)\n    }\n}\n\nimpl Eq for SymbolScope {}\n\npub struct SymbolScopeData {\n    id: usize,\n    table: DefaultStringInterner,\n    assertions: Vec<Assertion>,\n    scenarios: Vec<(String, Vec<Assertion>)>,\n}\n\nimpl Default for SymbolScopeData {\n    fn default() -> Self {\n        SymbolScopeData {\n            id: SCOPE_COUNTER.fetch_add(1, Ordering::Relaxed),\n            table: DefaultStringInterner::default(),\n            assertions: Vec::new(),\n            scenarios: Vec::new(),\n        }\n    }\n}\n\nimpl SymbolScope {\n    pub fn id(&self) -> usize {\n        let locked = self.0.lock();\n        let locked = locked.borrow();\n        locked.id\n    }\n\n    pub fn proof_cache_session(&self) -> ProofCacheSession {\n        ProofCacheSession::new(self.id())\n    }\n\n    pub fn get(&self, name: &str) -> Option<Symbol> {\n        let locked = self.0.lock();\n        let locked = locked.borrow();\n        locked.table.get(name).map(|sym| Symbol(Arc::downgrade(&self.0), sym))\n    }\n\n    /// Get or create the coordinate symbol for axis `k` (named \"🎯{k}\").\n    pub fn coord_sym(&self, k: usize) -> Symbol {\n        self.sym(&format!(\"🎯{k}\"))\n    }\n\n    pub fn sym(&self, name: &str) -> Symbol {\n        let locked = self.0.lock();\n        let mut locked = locked.borrow_mut();\n        let sym = locked.table.get_or_intern(name);\n        Symbol(Arc::downgrade(&self.0), sym)\n    }\n\n    pub fn new_with_prefix(&self, prefix: &str) -> Symbol {\n        let locked = self.0.lock();\n        let mut locked = locked.borrow_mut();\n        let sym = if locked.table.get(prefix).is_none() {\n            locked.table.get_or_intern(prefix)\n        } else {\n            let mut i = 0;\n            loop {\n                let s = format!(\"{prefix}_{i}\");\n                if locked.table.get(&s).is_none() {\n                    break locked.table.get_or_intern(s);\n                }\n                i += 1;\n            }\n        };\n        Symbol(Arc::downgrade(&self.0), sym)\n    }\n\n    pub fn parse_tdim(&self, input: impl AsRef<str>) -> TractResult<TDim> {\n        parse_tdim(self, input.as_ref())\n    }\n\n    pub fn add_assertion(&self, assert: impl Into<String>) -> TractResult<()> {\n        let assert = assert.into();\n        let assert = parse_assertion(self, &assert)?;\n        let locked = self.0.lock();\n        let mut locked = locked.borrow_mut();\n        locked.assertions.push(assert);\n        Ok(())\n    }\n\n    pub fn with_assertion(self, assert: impl Into<String>) -> TractResult<Self> {\n        self.add_assertion(assert)?;\n        Ok(self)\n    }\n\n    pub fn all_assertions(&self) -> Vec<Assertion> {\n        let locked = self.0.lock();\n        let locked = locked.borrow();\n        locked.assertions.clone()\n    }\n\n    pub fn all_scenarios(&self) -> impl IntoIterator<Item = (String, Vec<Assertion>)> {\n        let locked = self.0.lock();\n        let locked = locked.borrow();\n        locked.scenarios.clone()\n    }\n\n    pub fn add_scenario(&self, scenario: impl Into<String>) -> TractResult<()> {\n        let locked = self.0.lock();\n        let mut locked = locked.borrow_mut();\n        let s = scenario.into();\n        if !locked.scenarios.iter().any(|sc| sc.0 == s) {\n            locked.scenarios.push((s, vec![]));\n        }\n        Ok(())\n    }\n\n    pub fn add_scenario_assertion(\n        &self,\n        scenario: impl Into<String>,\n        assertion: impl Into<String>,\n    ) -> TractResult<()> {\n        let assert = parse_assertion(self, &assertion.into())?;\n        let s = scenario.into();\n        let locked = self.0.lock();\n        let mut locked = locked.borrow_mut();\n        if let Some(s) = locked.scenarios.iter_mut().find(|sc| sc.0 == s) {\n            s.1.push(assert);\n        } else {\n            locked.scenarios.push((s, vec![assert]));\n        }\n        Ok(())\n    }\n\n    pub fn with_scenario_assertion(\n        self,\n        scenario: impl Into<String>,\n        assertion: impl Into<String>,\n    ) -> TractResult<Self> {\n        self.add_scenario_assertion(scenario, assertion)?;\n        Ok(self)\n    }\n\n    pub fn with_scenario(self, scenario: impl Into<String>) -> TractResult<Self> {\n        self.add_scenario(scenario)?;\n        Ok(self)\n    }\n\n    pub fn all_symbols(&self) -> Vec<Symbol> {\n        self.0\n            .lock()\n            .borrow()\n            .table\n            .into_iter()\n            .map(|is| Symbol(Arc::downgrade(&self.0), is.0))\n            .collect()\n    }\n\n    pub fn guess_scenario(&self, values: &SymbolValues) -> TractResult<Option<usize>> {\n        let locked = self.0.lock();\n        let locked = locked.borrow();\n        if locked.scenarios.len() == 0 {\n            return Ok(None);\n        }\n        let mut maybe = None;\n        for (ix, (_name, assertions)) in locked.scenarios.iter().enumerate() {\n            if assertions.iter().any(|a| a.check(values) == Some(false)) {\n                continue;\n            } else if assertions.iter().all(|a| a.check(values) == Some(true)) {\n                return Ok(Some(ix));\n            } else if maybe.is_none() {\n                maybe = Some(ix);\n            } else {\n                return Ok(None);\n            }\n        }\n        if maybe.is_some() {\n            Ok(maybe)\n        } else {\n            anyhow::bail!(\"No possible scenario\");\n        }\n    }\n}\n\nthread_local! {\n    static PROOF_CACHE: RefCell<Option<ProofCache>> = const { RefCell::new(None) };\n}\n\nstruct ProofCache {\n    scope_id: usize,\n    depth: usize,\n    cache: HashMap<TDim, bool>,\n}\n\npub struct ProofCacheSession {\n    active: bool,\n}\n\nimpl ProofCacheSession {\n    pub fn new(scope_id: usize) -> Self {\n        let active = PROOF_CACHE.with(|cell| {\n            let mut borrow = cell.borrow_mut();\n            match &mut *borrow {\n                None => {\n                    *borrow = Some(ProofCache { scope_id, depth: 1, cache: HashMap::new() });\n                    true\n                }\n                Some(pc) if pc.scope_id == scope_id => {\n                    pc.depth += 1;\n                    true\n                }\n                Some(_) => false,\n            }\n        });\n        ProofCacheSession { active }\n    }\n}\n\nimpl Drop for ProofCacheSession {\n    fn drop(&mut self) {\n        if !self.active {\n            return;\n        }\n        PROOF_CACHE.with(|cell| {\n            let mut borrow = cell.borrow_mut();\n            if let Some(pc) = &mut *borrow {\n                pc.depth -= 1;\n                if pc.depth == 0 {\n                    *borrow = None;\n                }\n            }\n        });\n    }\n}\n\nimpl SymbolScopeData {\n    pub fn all_assertions(&self) -> &[Assertion] {\n        &self.assertions\n    }\n\n    pub fn assertions(&self, scenario: Option<&str>) -> impl Iterator<Item = &'_ Assertion> {\n        self.assertions.iter().chain(\n            scenario\n                .and_then(|s| self.scenarios.iter().find(|s2| s2.0 == s))\n                .map(|s| &*s.1)\n                .unwrap_or(&[])\n                .iter(),\n        )\n    }\n\n    pub fn scenarios(&self) -> impl Iterator<Item = &'_ str> {\n        self.scenarios.iter().map(|s| &*s.0)\n    }\n\n    pub fn scenario(&self, s: &str) -> impl Iterator<Item = &'_ Assertion> {\n        self.scenarios.iter().find(|sc| sc.0 == s).map(|sc| &*sc.1).unwrap_or(&[]).iter()\n    }\n\n    pub fn resolving<R>(&self, sym: &Symbol, f: impl FnOnce(&str) -> R) -> Option<R> {\n        self.table.resolve(sym.1).map(f)\n    }\n\n    #[allow(clippy::mutable_key_type)]\n    pub fn prove_positive_or_zero(&self, t: &TDim) -> bool {\n        if let TDim::Val(v) = t {\n            return *v >= 0;\n        }\n        let cached = PROOF_CACHE.with(|cell| {\n            let borrow = cell.borrow();\n            if let Some(pc) = &*borrow {\n                debug_assert_eq!(pc.scope_id, self.id, \"ProofCacheSession scope_id mismatch\");\n                pc.cache.get(t).copied()\n            } else {\n                None\n            }\n        });\n        if let Some(result) = cached {\n            return result;\n        }\n        let result = self.prove_positive_or_zero_inner(t);\n        PROOF_CACHE.with(|cell| {\n            let mut borrow = cell.borrow_mut();\n            if let Some(pc) = &mut *borrow {\n                pc.cache.insert(t.clone(), result);\n            }\n        });\n        result\n    }\n\n    #[allow(clippy::mutable_key_type)]\n    fn prove_positive_or_zero_inner(&self, t: &TDim) -> bool {\n        self.prove_positive_or_zero_inner_with_extra(t, &[])\n    }\n\n    #[allow(clippy::mutable_key_type)]\n    fn prove_positive_or_zero_inner_with_extra(&self, t: &TDim, extra: &[Assertion]) -> bool {\n        let positives = self\n            .assertions\n            .iter()\n            .chain(extra.iter())\n            .filter_map(|i| i.as_known_positive())\n            .collect_vec();\n        let mut visited = vec![];\n        let mut todo = vec![t.clone()];\n        while let Some(t) = todo.pop() {\n            if t.to_i64().is_ok_and(|i| i >= 0) {\n                return true;\n            }\n            if t.inclusive_bound(self, false).is_some_and(|l| l >= 0) {\n                return true;\n            }\n            // Div(a, q) with q >= 1 is non-negative whenever a is non-negative.\n            if let TDim::Div(a, q) = &t {\n                if *q >= 1 && self.prove_positive_or_zero_inner_with_extra(a, extra) {\n                    return true;\n                }\n            }\n            let syms = t.symbols();\n            for s in syms {\n                let me = t.guess_slope(&s);\n                for pos in &positives {\n                    if pos.symbols().contains(&s) {\n                        let other = pos.guess_slope(&s);\n                        if me.0.signum() == other.0.signum() {\n                            let new = t.clone() * me.1 * other.0.abs()\n                                - pos.clone() * me.0.abs() * other.1;\n                            if !visited.contains(&new) {\n                                todo.push(new);\n                            }\n                        }\n                    }\n                }\n            }\n            visited.push(t);\n            if visited.len() > 10 {\n                break;\n            }\n        }\n        false\n    }\n\n    pub(crate) fn prove_positive_or_zero_with_extra(&self, t: &TDim, extra: &[Assertion]) -> bool {\n        if let TDim::Val(v) = t {\n            return *v >= 0;\n        }\n        // Skip the proof cache for extra-assertion calls (cache is keyed without extra context)\n        self.prove_positive_or_zero_inner_with_extra(t, extra)\n    }\n\n    pub(crate) fn prove_strict_positive_with_extra(&self, b: &TDim, extra: &[Assertion]) -> bool {\n        self.prove_positive_or_zero_with_extra(&(b.clone() - 1), extra)\n    }\n}\n\nimpl fmt::Debug for SymbolScope {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let locked = self.0.lock();\n        let locked = locked.borrow();\n        write!(\n            f,\n            \"symbols: {}; assertions: {}; {}\",\n            locked.table.into_iter().map(|(_, s)| s).sorted().join(\", \"),\n            locked.assertions.iter().map(|s| s.to_string()).sorted().join(\", \"),\n            locked\n                .scenarios\n                .iter()\n                .map(|s| format!(\n                    \"{}: {}\",\n                    s.0,\n                    s.1.iter().map(|s| s.to_string()).sorted().join(\", \")\n                ))\n                .join(\" ; \"),\n        )\n    }\n}\n\n#[derive(Clone)]\npub struct Symbol(Weak<ReentrantMutex<RefCell<SymbolScopeData>>>, string_interner::DefaultSymbol);\n\nimpl Eq for Symbol {}\n\nimpl PartialEq for Symbol {\n    fn eq(&self, other: &Self) -> bool {\n        self.1 == other.1\n    }\n}\n\nimpl Symbol {\n    pub fn scope(&self) -> Option<SymbolScope> {\n        self.0.upgrade().map(SymbolScope)\n    }\n}\n\nimpl PartialOrd for Symbol {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for Symbol {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.1.cmp(&other.1)\n    }\n}\n\nimpl std::hash::Hash for Symbol {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.1.hash(state)\n    }\n}\n\nimpl std::fmt::Display for Symbol {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        if let Some(scope) = self.scope() {\n            let lock = scope.0.lock();\n            let lock = lock.borrow();\n            if let Some(s) = lock.table.resolve(self.1) {\n                return write!(f, \"{s}\");\n            }\n        }\n        write!(f, \"<Sym{}>\", self.1.to_usize())\n    }\n}\n\nimpl fmt::Debug for Symbol {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        Display::fmt(&self, f)\n    }\n}\n\n#[derive(Clone, Debug, Default)]\npub struct SymbolValues {\n    values: HashMap<Symbol, i64>,\n}\n\nimpl SymbolValues {\n    pub fn with(mut self, s: &Symbol, v: i64) -> Self {\n        self.set(s, v);\n        self\n    }\n\n    pub fn set(&mut self, s: &Symbol, v: i64) {\n        self.values.insert(s.clone(), v);\n    }\n\n    pub fn get(&self, s: &Symbol) -> Option<i64> {\n        self.values.get(s).copied()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn as_known_positive_gte() {\n        let s = SymbolScope::default();\n        assert_eq!(\n            parse_assertion(&s, \"S>=0\").unwrap().as_known_positive(),\n            Some(s.parse_tdim(\"S\").unwrap())\n        );\n    }\n\n    #[test]\n    fn as_known_positive_gt() {\n        let s = SymbolScope::default();\n        assert_eq!(\n            parse_assertion(&s, \"S>0\").unwrap().as_known_positive(),\n            Some(s.parse_tdim(\"S-1\").unwrap())\n        );\n    }\n\n    #[test]\n    fn as_known_positive_lte() {\n        let s = SymbolScope::default();\n        assert_eq!(\n            parse_assertion(&s, \"S<=0\").unwrap().as_known_positive(),\n            Some(s.parse_tdim(\"-S\").unwrap())\n        );\n    }\n\n    #[test]\n    fn as_known_positive_lt() {\n        let s = SymbolScope::default();\n        assert_eq!(\n            parse_assertion(&s, \"S<0\").unwrap().as_known_positive(),\n            Some(s.parse_tdim(\"-S - 1\").unwrap())\n        );\n    }\n\n    #[test]\n    fn prove_positive_0() {\n        let s = SymbolScope::default();\n        assert!(s.parse_tdim(\"0\").unwrap().prove_positive_or_zero());\n    }\n\n    #[test]\n    fn prove_positive_1() {\n        let s = SymbolScope::default();\n        assert!(s.parse_tdim(\"1\").unwrap().prove_positive_or_zero());\n    }\n\n    #[test]\n    fn prove_positive_neg1() {\n        let s = SymbolScope::default();\n        assert!(!s.parse_tdim(\"-1\").unwrap().prove_positive_or_zero());\n    }\n\n    #[test]\n    fn prove_positive_add_0() {\n        let s = SymbolScope::default();\n        assert!(!s.parse_tdim(\"s+1\").unwrap().prove_positive_or_zero());\n    }\n}\n"
  },
  {
    "path": "data/src/dim/tree.rs",
    "content": "use crate::dim::Assertion;\nuse crate::internal::*;\n\nuse super::{DimLike, sym::*};\nuse itertools::Itertools;\nuse num_integer::Integer;\nuse num_traits::{AsPrimitive, PrimInt, Zero};\nuse std::cmp::Ordering;\nuse std::collections::{HashMap, HashSet};\nuse std::fmt::Debug;\nuse std::ops::Neg;\nuse std::{fmt, ops};\n\n#[derive(Debug)]\npub enum TooEarly {\n    UndeterminedSymbol(String),\n    Other(String),\n}\n\nimpl std::fmt::Display for TooEarly {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            TooEarly::UndeterminedSymbol(s) => write!(f, \"Undetermined symbol in expression: {s}\"),\n            TooEarly::Other(s) => write!(f, \"{s}\"),\n        }\n    }\n}\n\nimpl std::error::Error for TooEarly {}\n\nmacro_rules! b( ($e:expr) => { Box::new($e) } );\n\n#[derive(Clone, PartialEq, Eq, Hash, Debug)]\npub enum TDim {\n    Val(i64),\n    Sym(Symbol),\n    Add(Vec<TDim>),\n    Mul(Vec<TDim>),\n    MulInt(i64, Box<TDim>),\n    Div(Box<TDim>, u64),\n    Broadcast(Vec<TDim>),\n    Min(Vec<TDim>),\n    Max(Vec<TDim>),\n    /// Comparison: evaluates to 1 (true) or 0 (false). lhs >= rhs\n    Ge(Box<TDim>, Box<TDim>),\n    /// Comparison: evaluates to 1 (true) or 0 (false). lhs == rhs\n    Eq(Box<TDim>, Box<TDim>),\n}\n\nuse TDim::*;\n\nfn tdim_lexi_order(a: &TDim, b: &TDim) -> Ordering {\n    match (a, b) {\n        (Sym(a), Sym(b)) => a.cmp(b),\n        (Val(a), Val(b)) => a.cmp(b),\n        (Add(a), Add(b))\n        | (Mul(a), Mul(b))\n        | (Broadcast(a), Broadcast(b))\n        | (Min(a), Min(b))\n        | (Max(a), Max(b)) => a.len().cmp(&b.len()).then(\n            a.iter()\n                .zip(b.iter())\n                .fold(Ordering::Equal, |acc, (a, b)| acc.then_with(|| tdim_lexi_order(a, b))),\n        ),\n        (MulInt(p, d), MulInt(q, e)) => p.cmp(q).then_with(|| tdim_lexi_order(d, e)),\n        (Div(d, p), Div(e, q)) => p.cmp(q).then_with(|| tdim_lexi_order(d, e)),\n        (Sym(_), _) => Ordering::Less,\n        (_, Sym(_)) => Ordering::Greater,\n        (Val(_), _) => Ordering::Less,\n        (_, Val(_)) => Ordering::Greater,\n        (Add(_), _) => Ordering::Less,\n        (_, Add(_)) => Ordering::Greater,\n        (Mul(_), _) => Ordering::Less,\n        (_, Mul(_)) => Ordering::Greater,\n        (MulInt(_, _), _) => Ordering::Less,\n        (_, MulInt(_, _)) => Ordering::Greater,\n        (Broadcast(_), _) => Ordering::Less,\n        (_, Broadcast(_)) => Ordering::Greater,\n        (Min(_), _) => Ordering::Less,\n        (_, Min(_)) => Ordering::Greater,\n        (Max(_), _) => Ordering::Less,\n        (_, Max(_)) => Ordering::Greater,\n        (Ge(a1, b1), Ge(a2, b2)) | (Eq(a1, b1), Eq(a2, b2)) => {\n            tdim_lexi_order(a1, a2).then_with(|| tdim_lexi_order(b1, b2))\n        }\n        (Ge(_, _) | Eq(_, _), _) => Ordering::Less,\n        (_, Ge(_, _) | Eq(_, _)) => Ordering::Greater,\n    }\n}\n\nimpl fmt::Display for TDim {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        match &self {\n            Sym(sym) => write!(fmt, \"{sym}\"),\n            Val(it) => write!(fmt, \"{it}\"),\n            Add(it) => write!(fmt, \"{}\", it.iter().map(|x| format!(\"{x}\")).join(\"+\")),\n            Mul(it) => write!(fmt, \"{}\", it.iter().map(|x| format!(\"({x})\")).join(\"*\")),\n            Broadcast(it) => write!(fmt, \"{}\", it.iter().map(|x| format!(\"({x})\")).join(\"#\")),\n            Min(it) => write!(fmt, \"min({})\", it.iter().map(|x| format!(\"{x}\")).join(\",\")),\n            Max(it) => write!(fmt, \"max({})\", it.iter().map(|x| format!(\"{x}\")).join(\",\")),\n            MulInt(a, b) => write!(fmt, \"{a}*{b}\"),\n            Div(a, b) => write!(fmt, \"({a})/{b}\"),\n            Ge(a, b) => write!(fmt, \"({a}>={b})\"),\n            Eq(a, b) => write!(fmt, \"({a}=={b})\"),\n        }\n    }\n}\n\nimpl TDim {\n    #[inline]\n    pub fn is_one(&self) -> bool {\n        matches!(self, Val(1))\n    }\n\n    #[inline]\n    pub fn to_i64(&self) -> TractResult<i64> {\n        if let Val(v) = self {\n            Ok(*v)\n        } else {\n            Err(TooEarly::UndeterminedSymbol(self.to_string()))?\n        }\n    }\n\n    #[inline]\n    pub fn as_i64(&self) -> Option<i64> {\n        if let Val(v) = self { Some(*v) } else { None }\n    }\n\n    pub fn eval_to_i64(&self, values: &SymbolValues) -> TractResult<i64> {\n        match self {\n            Sym(sym) => {\n                let Some(v) = values.get(sym) else {\n                    Err(TooEarly::UndeterminedSymbol(self.to_string()))?\n                };\n                Ok(v)\n            }\n            Val(v) => Ok(*v),\n            Add(terms) => terms.iter().try_fold(0i64, |acc, it| {\n                let x = it.eval_to_i64(values)?;\n                acc.checked_add(x)\n                    .with_context(|| format!(\"Overflow in TDim addition ({acc} + {x})\"))\n            }),\n            Mul(terms) => terms.iter().try_fold(1i64, |acc, it| {\n                let x = it.eval_to_i64(values)?;\n                acc.checked_mul(x)\n                    .with_context(|| format!(\"Overflow in TDim multiplication ({acc} * {x})\"))\n            }),\n            Min(terms) => terms\n                .iter()\n                .try_fold(i64::MAX, |acc, it| it.eval_to_i64(values).map(|x| acc.min(x))),\n            Max(terms) => terms\n                .iter()\n                .try_fold(i64::MIN, |acc, it| it.eval_to_i64(values).map(|x| acc.max(x))),\n            Broadcast(terms) => terms.iter().try_fold(1i64, |acc, it| {\n                it.eval_to_i64(values)\n                    .and_then(|x| ((acc as usize).broadcast(x as usize)).map(|x| x as i64))\n            }),\n            Div(a, q) => Ok(a.eval_to_i64(values)? / *q as i64),\n            MulInt(p, a) => {\n                let x = a.eval_to_i64(values)?;\n                x.checked_mul(*p)\n                    .with_context(|| format!(\"Overflow in TDim multiplication ({x} * {p})\"))\n            }\n            Ge(a, b) => Ok(if a.eval_to_i64(values)? >= b.eval_to_i64(values)? { 1 } else { 0 }),\n            Eq(a, b) => Ok(if a.eval_to_i64(values)? == b.eval_to_i64(values)? { 1 } else { 0 }),\n        }\n    }\n\n    pub fn eval(&self, values: &SymbolValues) -> TDim {\n        match self {\n            Sym(sym) => values.get(sym).map(Val).unwrap_or_else(|| Sym(sym.clone())),\n            Val(v) => Val(*v),\n            Add(terms) => terms.iter().fold(Val(0), |acc, it| -> TDim { acc + it.eval(values) }),\n            Mul(terms) => terms.iter().fold(Val(1), |acc, it| -> TDim { acc * it.eval(values) }),\n            Min(terms) => {\n                terms.iter().fold(Val(i64::MAX), |acc, it| -> TDim { acc.mini(it.eval(values)) })\n            }\n            Max(terms) => {\n                terms.iter().fold(Val(i64::MIN), |acc, it| -> TDim { acc.maxi(it.eval(values)) })\n            }\n            Broadcast(terms) => terms.iter().fold(Val(1), |acc, it| -> TDim {\n                acc.broadcast(it.eval(values)).unwrap_or_else(|_| self.clone())\n            }),\n            Div(a, q) => a.eval(values) / *q as i64,\n            MulInt(p, a) => a.eval(values) * *p,\n            Ge(a, b) => {\n                let a2 = a.eval(values);\n                let b2 = b.eval(values);\n                if let (Val(av), Val(bv)) = (&a2, &b2) {\n                    Val(if av >= bv { 1 } else { 0 })\n                } else {\n                    Ge(b!(a2), b!(b2))\n                }\n            }\n            Eq(a, b) => {\n                let a2 = a.eval(values);\n                let b2 = b.eval(values);\n                if let (Val(av), Val(bv)) = (&a2, &b2) {\n                    Val(if av == bv { 1 } else { 0 })\n                } else {\n                    Eq(b!(a2), b!(b2))\n                }\n            }\n        }\n    }\n\n    pub fn eval_with_scenario(&self, scenario: &str) -> TDim {\n        if let Val(v) = self {\n            return Val(*v);\n        }\n        let scope = self.find_scope().unwrap();\n        let scope = scope.0;\n        let locked = scope.lock();\n        let scope = locked.borrow();\n        self.clone().simplify_rec(&scope, Some(scenario), &[])\n    }\n\n    pub fn substitute(&self, from: &Symbol, to: &Self) -> TractResult<Self> {\n        self.substitute_all(&std::collections::HashMap::from([(from.clone(), to.clone())]))\n    }\n\n    pub fn substitute_all(\n        &self,\n        map: &std::collections::HashMap<Symbol, Self>,\n    ) -> TractResult<Self> {\n        match self {\n            Sym(sym) => Ok(map.get(sym).cloned().unwrap_or_else(|| self.clone())),\n            Val(v) => Ok(Val(*v)),\n            Add(terms) => terms.iter().try_fold(Val(0), |acc, it| -> TractResult<TDim> {\n                Ok(acc + it.substitute_all(map)?)\n            }),\n            Mul(terms) => terms.iter().try_fold(Val(1), |acc, it| -> TractResult<TDim> {\n                Ok(acc * it.substitute_all(map)?)\n            }),\n            Broadcast(terms) => terms.iter().try_fold(Val(1), |acc, it| -> TractResult<TDim> {\n                acc.broadcast(it.substitute_all(map)?)\n            }),\n            Min(terms) => terms.iter().try_fold(Val(i64::MAX), |acc, it| -> TractResult<TDim> {\n                Ok(acc.mini(it.substitute_all(map)?))\n            }),\n            Max(terms) => terms.iter().try_fold(Val(i64::MIN), |acc, it| -> TractResult<TDim> {\n                Ok(acc.maxi(it.substitute_all(map)?))\n            }),\n            Div(a, q) => Ok(a.substitute_all(map)? / *q as i64),\n            MulInt(p, a) => Ok(a.substitute_all(map)? * *p),\n            Ge(a, b) => Ok(Ge(b!(a.substitute_all(map)?), b!(b.substitute_all(map)?))),\n            Eq(a, b) => Ok(Eq(b!(a.substitute_all(map)?), b!(b.substitute_all(map)?))),\n        }\n    }\n\n    pub fn reduce(self) -> TDim {\n        self.simplify()\n            .wiggle()\n            .into_iter()\n            .sorted_by(tdim_lexi_order)\n            .unique()\n            .map(|e| e.simplify())\n            .min_by_key(|e| e.cost())\n            .unwrap()\n    }\n\n    fn cost(&self) -> usize {\n        use self::TDim::*;\n        match self {\n            Sym(_) | Val(_) => 1,\n            Add(terms) => 2 * terms.iter().map(TDim::cost).sum::<usize>(),\n            Mul(terms) => 3 * terms.iter().map(TDim::cost).sum::<usize>(),\n            Broadcast(terms) => 4 * terms.iter().map(TDim::cost).sum::<usize>(),\n            Min(terms) | Max(terms) => 5 * terms.iter().map(TDim::cost).sum::<usize>(),\n            Div(a, _) => 3 * a.cost(),\n            MulInt(_, a) => 2 * a.cost(),\n            Ge(a, b) | Eq(a, b) => 5 * (a.cost() + b.cost()),\n        }\n    }\n\n    fn wiggle(&self) -> Vec<TDim> {\n        use self::TDim::*;\n        match self {\n            Sym(_) | Val(_) | Mul(_) | Broadcast(_) | Min(_) | Max(_) | Ge(_, _) | Eq(_, _) => {\n                vec![self.clone()]\n            }\n            Add(terms) => {\n                let mut forms = vec![];\n                let sub_exprs = terms.iter().map(|e| e.wiggle()).multi_cartesian_product();\n\n                fn first_div_term(terms: &[TDim]) -> Option<(usize, &TDim, u64)> {\n                    terms.iter().enumerate().find_map(|(index, t)| match t {\n                        Div(numerator, quotient) => Some((index, &**numerator, *quotient)),\n                        _ => None,\n                    })\n                }\n\n                fn generate_new_numerator(\n                    div_index: usize,\n                    numerator: &TDim,\n                    quotient: u64,\n                    expr: &[TDim],\n                ) -> Vec<TDim> {\n                    expr.iter()\n                        .enumerate()\n                        .map(|(index, term)| {\n                            if index == div_index {\n                                numerator.clone()\n                            } else {\n                                MulInt(quotient as i64, Box::new(term.clone()))\n                            }\n                        })\n                        .collect()\n                }\n\n                for expr in sub_exprs {\n                    if let Some((div_index, numerator, quotient)) = first_div_term(&expr) {\n                        let new_numerator =\n                            generate_new_numerator(div_index, numerator, quotient, &expr);\n                        forms.push(Div(Box::new(Add(new_numerator)), quotient))\n                    }\n\n                    forms.push(Add(expr));\n                }\n                forms\n            }\n            MulInt(p, a) => a.wiggle().into_iter().map(|a| MulInt(*p, b!(a))).collect(),\n            Div(a, q) => {\n                let mut forms = vec![];\n                for num in a.wiggle() {\n                    if let Add(terms) = &num {\n                        let (integer, non_integer): (Vec<_>, Vec<_>) =\n                            terms.iter().cloned().partition(|a| a.gcd() % q == 0);\n                        let mut new_terms = integer.iter().map(|i| i.div(*q)).collect::<Vec<_>>();\n                        if non_integer.len() > 0 {\n                            new_terms.push(Div(b!(Add(non_integer)), *q));\n                        }\n                        forms.push(Add(new_terms))\n                    }\n                    forms.push(Div(b!(num), *q))\n                }\n                forms\n            }\n        }\n    }\n\n    fn find_any_sym(tdim: &TDim) -> Option<&Symbol> {\n        match tdim {\n            Val(_) => None,\n            Sym(s) => Some(s),\n            Add(terms) | Mul(terms) | Min(terms) | Max(terms) | Broadcast(terms) => {\n                terms.iter().find_map(Self::find_any_sym)\n            }\n            MulInt(_, t) | Div(t, _) => Self::find_any_sym(t),\n            Ge(a, b) | Eq(a, b) => Self::find_any_sym(a).or_else(|| Self::find_any_sym(b)),\n        }\n    }\n\n    pub fn find_scope(&self) -> Option<SymbolScope> {\n        Self::find_any_sym(self).and_then(|s| s.scope().clone())\n    }\n\n    pub fn simplify(self) -> TDim {\n        use self::TDim::*;\n        if let Ok(v) = self.eval_to_i64(&SymbolValues::default()) {\n            return Val(v);\n        }\n        let Some(scope) = self.find_scope() else {\n            return self;\n        };\n        let scope = scope.0;\n        let locked = scope.lock();\n        let scope = locked.borrow();\n        let it = self.simplify_rec(&scope, None, &[]);\n        let mut current: Option<TDim> = None;\n        for scenario in scope.scenarios() {\n            let v = it.clone().simplify_rec(&scope, Some(scenario), &[]);\n            if current.is_some_and(|c| c != v) {\n                return it;\n            } else {\n                current = Some(v);\n            }\n        }\n        current.unwrap_or(it)\n    }\n\n    pub fn simplify_with_extra_assertions(self, extra: &[Assertion]) -> TDim {\n        use self::TDim::*;\n        if extra.is_empty() {\n            return self.simplify();\n        }\n        if let Ok(v) = self.eval_to_i64(&SymbolValues::default()) {\n            return Val(v);\n        }\n        let Some(scope) = self.find_scope() else {\n            return self;\n        };\n        let scope = scope.0;\n        let locked = scope.lock();\n        let scope = locked.borrow();\n        let it = self.simplify_rec(&scope, None, extra);\n        let mut current: Option<TDim> = None;\n        for scenario in scope.scenarios() {\n            let v = it.clone().simplify_rec(&scope, Some(scenario), extra);\n            if current.is_some_and(|c| c != v) {\n                return it;\n            } else {\n                current = Some(v);\n            }\n        }\n        current.unwrap_or(it)\n    }\n\n    fn simplify_rec(\n        self,\n        scope: &SymbolScopeData,\n        scenario: Option<&str>,\n        extra: &[Assertion],\n    ) -> TDim {\n        match self {\n            Add(mut terms) => {\n                #[allow(clippy::mutable_key_type)]\n                let mut simplified_terms: HashMap<TDim, i64> = HashMap::new();\n                // factorize common sub-expr\n                while let Some(term) = terms.pop() {\n                    let simplified = term.simplify_rec(scope, scenario, extra);\n                    match simplified {\n                        Val(0) => {} // ignore\n                        Add(members) => {\n                            terms.extend(members);\n                            continue;\n                        }\n                        Val(value) => *simplified_terms.entry(Val(1)).or_insert(0) += value,\n                        MulInt(value, factor) => {\n                            *simplified_terms.entry((*factor).clone()).or_insert(0) += value;\n                        }\n                        n => *simplified_terms.entry(n).or_insert(0) += 1,\n                    };\n                }\n\n                pub fn evaluate_count(term: TDim, count: i64) -> Option<TDim> {\n                    match count {\n                        0 => None,\n                        _ if term == TDim::Val(1) => Some(TDim::Val(count)),\n                        1 => Some(term),\n                        _ => Some(TDim::MulInt(count, Box::new(term))),\n                    }\n                }\n\n                let mut members: Vec<TDim> = simplified_terms\n                    .into_iter()\n                    .filter_map(|(term, count)| evaluate_count(term, count))\n                    .collect();\n                members.sort_by(tdim_lexi_order);\n\n                match members.len() {\n                    0 => TDim::Val(0),\n                    1 => members.into_iter().next().unwrap(),\n                    _ => TDim::Add(members),\n                }\n            }\n            Mul(terms) => {\n                // in case a term is a multiplication itself, flatten it\n                // e.g., (a*b)*c => a*b*c\n                let mut flattened_terms = vec![];\n                for t in terms {\n                    if let Mul(inner_terms) = t.clone().reduce() {\n                        flattened_terms.extend(inner_terms);\n                    } else {\n                        flattened_terms.push(t);\n                    }\n                }\n                let mut terms = flattened_terms;\n\n                let mut gcd = Mul(terms.clone()).gcd() as i64;\n                if gcd == 0 {\n                    return Val(0);\n                }\n                terms = if gcd != 1 {\n                    terms\n                        .into_iter()\n                        .map(|t| {\n                            let gcd = t.gcd();\n                            (t / gcd).simplify_rec(scope, scenario, extra)\n                        })\n                        .collect()\n                } else {\n                    terms\n                };\n                if terms.iter().filter(|t| t == &&Val(-1)).count() % 2 == 1 {\n                    gcd = -gcd;\n                }\n                terms.retain(|t| !t.is_one() && t != &Val(-1));\n                terms.sort_by(tdim_lexi_order);\n\n                match (gcd, terms.len()) {\n                    (_, 0) => Val(gcd), // Case #1: If 0 variables, return product\n                    (0, _) => Val(0),   // Case #2: Result is 0 if coef is 0 (actually\n                    // unreachable as we check at the beginning)\n                    (1, 1) => terms.remove(0), // Case #3: Product is 1, so return the only term\n                    (1, _) => Mul(terms), // Case #4: Product is 1, so return the non-integer terms\n                    (_, 1) => MulInt(gcd, Box::new(terms.remove(0))), // Case #5: Single variable, convert to 1 MulInt\n                    _ => MulInt(gcd, Box::new(Mul(terms))), // Case #6: Multiple variables, convert to MulInt\n                }\n            }\n            MulInt(coef, expr) => {\n                match *expr {\n                    MulInt(c2, inner) => {\n                        if let Some(c) = coef.checked_mul(c2) {\n                            return MulInt(c, inner).simplify_rec(scope, scenario, extra);\n                        } else {\n                            return MulInt(coef, Box::new(MulInt(c2, inner)));\n                        }\n                    }\n                    Val(v) => {\n                        return coef\n                            .checked_mul(v)\n                            .map(Val)\n                            .unwrap_or_else(|| MulInt(coef, Box::new(Val(v))));\n                    }\n                    _ => {}\n                }\n\n                let simplified = expr.simplify_rec(scope, scenario, extra);\n                match (coef, simplified) {\n                    (0, _) => Val(0), // Case #1: If coef is 0, return 0\n                    (1, s) => s,      // Case #2: If coef is 1, return the simplified expression\n                    (_, Add(terms)) => Add(terms\n                        .into_iter()\n                        .map(|term| {\n                            MulInt(coef, Box::new(term)).simplify_rec(scope, scenario, extra)\n                        })\n                        .collect()), // Case #3: If expression is an addition, distribute the coef\n                    (c, Val(v)) => {\n                        c.checked_mul(v).map(Val).unwrap_or_else(|| MulInt(c, Box::new(Val(v))))\n                    } // Case #4: If expression is a value, combine coefs\n                    (c, MulInt(v, inner)) => {\n                        if let Some(cv) = c.checked_mul(v) {\n                            MulInt(cv, inner) // Case #5: If expression is a MulInt, combine coefs\n                        } else {\n                            MulInt(c, Box::new(MulInt(v, inner)))\n                        }\n                    }\n                    (_, s) => MulInt(coef, Box::new(s)), // Case #6: Otherwise, return the original\n                }\n            }\n            Div(a, q) => {\n                if q == 1 {\n                    return a.simplify_rec(scope, scenario, extra);\n                } else if let Div(a, q2) = *a {\n                    return Div(a, q * q2).simplify_rec(scope, scenario, extra);\n                }\n                let a = a.simplify_rec(scope, scenario, extra);\n                if let Val(a) = a {\n                    Val(a / q as i64)\n                } else if let MulInt(-1, a) = a {\n                    MulInt(-1, b!(Div(a, q)))\n                } else if let Add(mut terms) = a {\n                    if terms\n                        .iter()\n                        .any(|t| if let MulInt(-1, s) = t { matches!(&**s, Sym(_)) } else { false })\n                    {\n                        MulInt(\n                            -1,\n                            b!(Div(\n                                b!(Add(terms.into_iter().map(|t| MulInt(-1, b!(t))).collect())\n                                    .simplify_rec(scope, scenario, extra)),\n                                q\n                            )),\n                        )\n                    } else if let Some(v) =\n                        terms.iter().find_map(|t| if let Val(v) = t { Some(*v) } else { None })\n                    {\n                        let offset = if v >= q as i64 {\n                            Some(v / q as i64)\n                        } else if v < 0 {\n                            Some(-Integer::div_ceil(&-v, &(q as i64)))\n                        } else {\n                            None\n                        };\n                        if let Some(val) = offset {\n                            terms.push(Val(-val * q as i64));\n                            Add(vec![\n                                Val(val),\n                                Div(b!(Add(terms).simplify_rec(scope, scenario, extra)), q),\n                            ])\n                        } else {\n                            Div(b!(Add(terms)), q)\n                        }\n                    } else {\n                        Div(b!(Add(terms)), q)\n                    }\n                } else if let MulInt(p, a) = a {\n                    if p == q as i64 {\n                        a.simplify()\n                    } else {\n                        let gcd = p.abs().gcd(&(q as i64));\n                        if gcd == p {\n                            Div(a, q / gcd as u64)\n                        } else if gcd == q as i64 {\n                            MulInt(p / gcd, a)\n                        } else if gcd > 1 {\n                            Div(b!(MulInt(p / gcd, a)), q / gcd as u64)\n                                .simplify_rec(scope, scenario, extra)\n                        } else {\n                            Div(b!(MulInt(p, a)), q)\n                        }\n                    }\n                } else {\n                    Div(b!(a), q)\n                }\n            }\n            Broadcast(terms) => {\n                let mut terms: Vec<TDim> = terms\n                    .iter()\n                    .map(|s| s.clone().simplify_rec(scope, scenario, extra))\n                    .flat_map(|t| if let Broadcast(t) = t { t } else { vec![t] })\n                    .filter(|t| !t.is_one())\n                    .sorted_by(tdim_lexi_order)\n                    .dedup()\n                    .collect_vec();\n                // a#min(a,b) if a>0 && b>0 => a\n                match &*terms {\n                    [] => Val(1),\n                    [_] => terms.remove(0),\n                    [a, Min(m)] | [Min(m), a]\n                        if m.contains(a)\n                            && m.iter()\n                                .all(|t| scope.prove_strict_positive_with_extra(t, extra)) =>\n                    {\n                        a.clone()\n                    }\n                    _ => Broadcast(terms),\n                }\n            }\n\n            Min(terms) => {\n                let mut flatten: Vec<TDim> = terms\n                    .into_iter()\n                    .map(|t| t.simplify_rec(scope, scenario, extra))\n                    .flat_map(|t| if let Min(t) = t { t } else { vec![t] })\n                    .filter(|t| t != &Val(i64::MAX))\n                    .sorted_by(tdim_lexi_order)\n                    .dedup()\n                    .collect();\n                #[allow(clippy::mutable_key_type)]\n                let mut redundant = HashSet::<TDim>::default();\n                for pair in flatten.iter().permutations(2) {\n                    let (a, b) = (pair[0], pair[1]);\n                    if redundant.contains(a) || redundant.contains(b) {\n                        continue;\n                    }\n                    let diff = a.clone() - b;\n                    if diff.as_i64().is_some_and(|i| i >= 0)\n                        || scope.prove_positive_or_zero_with_extra(&diff, extra)\n                    {\n                        redundant.insert(a.clone());\n                    }\n                }\n                flatten.retain(|t| !redundant.contains(t));\n                if flatten.len() == 0 {\n                    i64::MAX.to_dim()\n                } else if flatten.len() == 1 {\n                    flatten.into_iter().next().unwrap()\n                } else {\n                    Min(flatten)\n                }\n            }\n            Max(terms) => {\n                let mut flatten: Vec<TDim> = terms\n                    .into_iter()\n                    .map(|t| t.simplify_rec(scope, scenario, extra))\n                    .flat_map(|t| if let Max(t) = t { t } else { vec![t] })\n                    .filter(|t| t != &Val(i64::MIN))\n                    .sorted_by(tdim_lexi_order)\n                    .dedup()\n                    .collect();\n                #[allow(clippy::mutable_key_type)]\n                let mut redundant = HashSet::<TDim>::default();\n                for pair in flatten.iter().permutations(2) {\n                    let (a, b) = (pair[0], pair[1]);\n                    if redundant.contains(a) || redundant.contains(b) {\n                        continue;\n                    }\n                    let diff = a.clone() - b;\n                    if diff.as_i64().is_some_and(|i| i >= 0)\n                        || scope.prove_positive_or_zero_with_extra(&diff, extra)\n                    {\n                        redundant.insert(b.clone());\n                    }\n                }\n                flatten.retain(|t| !redundant.contains(t));\n                if flatten.len() == 0 {\n                    i64::MIN.to_dim()\n                } else if flatten.len() == 1 {\n                    flatten.into_iter().next().unwrap()\n                } else {\n                    Max(flatten)\n                }\n            }\n            Sym(s) => scope\n                .assertions(scenario)\n                .find_map(|a| match a {\n                    Assertion::Eq(Sym(sym), v) if sym == &s => Some(v.clone()),\n                    _ => None,\n                })\n                .unwrap_or(Sym(s)),\n            Val(_) => self,\n            Ge(a, b) => {\n                let a = a.simplify_rec(scope, scenario, extra);\n                let b = b.simplify_rec(scope, scenario, extra);\n                match (&a, &b) {\n                    (Val(av), Val(bv)) => Val(if av >= bv { 1 } else { 0 }),\n                    _ => {\n                        let diff = a.clone() - b.clone();\n                        if scope.prove_positive_or_zero_with_extra(&diff, extra) {\n                            Val(1)\n                        } else if scope\n                            .prove_strict_positive_with_extra(&(b.clone() - a.clone()), extra)\n                        {\n                            Val(0)\n                        } else {\n                            Ge(b!(a), b!(b))\n                        }\n                    }\n                }\n            }\n            Eq(a, b) => {\n                let a = a.simplify_rec(scope, scenario, extra);\n                let b = b.simplify_rec(scope, scenario, extra);\n                match (&a, &b) {\n                    (Val(av), Val(bv)) => Val(if av == bv { 1 } else { 0 }),\n                    _ => {\n                        let diff = a.clone() - b.clone();\n                        if scope.prove_strict_positive_with_extra(&diff, extra)\n                            || scope\n                                .prove_strict_positive_with_extra(&(b.clone() - a.clone()), extra)\n                        {\n                            Val(0)\n                        } else {\n                            Eq(b!(a), b!(b))\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    pub(super) fn inclusive_bound(&self, scope: &SymbolScopeData, upper: bool) -> Option<i64> {\n        use self::TDim::*;\n        match self {\n            Val(n) => Some(*n),\n            Sym(_) => {\n                if upper {\n                    scope\n                        .all_assertions()\n                        .iter()\n                        .filter_map(|assert| match &assert {\n                            Assertion::LT(left, right)\n                                if left == self && right.as_i64().is_some() =>\n                            {\n                                Some(right.as_i64().unwrap() - 1)\n                            }\n                            Assertion::LTE(left, right)\n                                if left == self && right.as_i64().is_some() =>\n                            {\n                                Some(right.as_i64().unwrap())\n                            }\n                            _ => None,\n                        })\n                        .min()\n                } else {\n                    scope\n                        .all_assertions()\n                        .iter()\n                        .filter_map(|assert| match &assert {\n                            Assertion::GT(left, right)\n                                if left == self && right.as_i64().is_some() =>\n                            {\n                                Some(right.as_i64().unwrap() + 1)\n                            }\n                            Assertion::GTE(left, right)\n                                if left == self && right.as_i64().is_some() =>\n                            {\n                                Some(right.as_i64().unwrap())\n                            }\n                            _ => None,\n                        })\n                        .max()\n                }\n            }\n            Add(terms) => {\n                let mut bound: i64 = 0;\n                for t in terms {\n                    if let Some(b) = t.inclusive_bound(scope, upper) {\n                        bound = bound.checked_add(b)?;\n                    } else {\n                        return None;\n                    }\n                }\n                Some(bound)\n            }\n            MulInt(p, a) => match p.cmp(&0) {\n                Ordering::Equal => Some(0),\n                Ordering::Greater => {\n                    a.inclusive_bound(scope, upper).and_then(|x| x.checked_mul(*p))\n                }\n                Ordering::Less => a.inclusive_bound(scope, !upper).and_then(|x| x.checked_mul(*p)),\n            },\n            Mul(_) => None,\n            Min(terms) if !upper => {\n                terms.iter().filter_map(|t| t.inclusive_bound(scope, false)).min()\n            }\n            Max(terms) if upper => {\n                terms.iter().filter_map(|t| t.inclusive_bound(scope, true)).max()\n            }\n            Div(a, q) => a.inclusive_bound(scope, upper).map(|x| x / (*q as i64)),\n            Broadcast(terms) => {\n                if upper {\n                    Max(terms.clone()).inclusive_bound(scope, true)\n                } else {\n                    Min(terms.clone()).inclusive_bound(scope, false)\n                }\n            }\n            Ge(_, _) | Eq(_, _) => {\n                if upper {\n                    Some(1)\n                } else {\n                    Some(0)\n                }\n            }\n            _ => None,\n        }\n    }\n\n    pub fn low_inclusive_bound(&self) -> Option<i64> {\n        if let TDim::Val(v) = self {\n            return Some(*v);\n        }\n        let scope = self.find_scope()?;\n        let data = scope.0.lock();\n        let data = data.borrow();\n        self.inclusive_bound(&data, false)\n    }\n\n    pub fn high_inclusive_bound(&self) -> Option<i64> {\n        if let TDim::Val(v) = self {\n            return Some(*v);\n        }\n        let scope = self.find_scope()?;\n        let data = scope.0.lock();\n        let data = data.borrow();\n        self.inclusive_bound(&data, true)\n    }\n\n    pub fn prove_positive_or_zero(&self) -> bool {\n        if let TDim::Val(v) = self {\n            return *v >= 0;\n        }\n        let Some(scope) = self.find_scope() else { return false };\n        let data = scope.0.lock();\n        let data = data.borrow();\n        data.prove_positive_or_zero(self)\n    }\n\n    pub fn prove_strict_positive(&self) -> bool {\n        if let TDim::Val(v) = self {\n            return *v > 0;\n        }\n        (self.clone() - 1).prove_positive_or_zero()\n    }\n\n    pub fn prove_negative_or_zero(&self) -> bool {\n        if let TDim::Val(v) = self {\n            return *v <= 0;\n        }\n        self.clone().neg().prove_positive_or_zero()\n    }\n\n    pub fn prove_strict_negative(&self) -> bool {\n        if let TDim::Val(v) = self {\n            return *v < 0;\n        }\n        self.clone().neg().prove_strict_positive()\n    }\n\n    pub fn gcd(&self) -> u64 {\n        use self::TDim::*;\n        match self {\n            Val(v) => v.unsigned_abs(),\n            Sym(_) => 1,\n            Add(terms) => {\n                let (head, tail) = terms.split_first().unwrap();\n                tail.iter().fold(head.gcd(), |a, b| a.gcd(&b.gcd()))\n            }\n            MulInt(p, a) => a.gcd().saturating_mul(p.unsigned_abs()),\n            Mul(terms) => terms.iter().map(|t| t.gcd()).fold(1u64, |a, b| a.saturating_mul(b)),\n            Min(terms) => terms.iter().map(|t| t.gcd()).reduce(|a, b| a.gcd(&b)).unwrap(),\n            Max(terms) => terms.iter().map(|t| t.gcd()).reduce(|a, b| a.gcd(&b)).unwrap(),\n            Div(a, q) => {\n                if a.gcd() % *q == 0 {\n                    a.gcd() / *q\n                } else {\n                    1\n                }\n            }\n            Broadcast(terms) => terms.iter().map(|t| t.gcd()).reduce(|a, b| a.gcd(&b)).unwrap_or(1),\n            Ge(_, _) | Eq(_, _) => 1,\n        }\n    }\n\n    fn div(&self, d: u64) -> TDim {\n        use self::TDim::*;\n        if d == 1 {\n            return self.clone();\n        }\n        match self {\n            Val(v) => Val(v / d as i64),\n            Sym(_) => panic!(),\n            Add(terms) => Add(terms.iter().map(|t| t.div(d)).collect()),\n            Min(terms) => Min(terms.iter().map(|t| t.div(d)).collect()),\n            Max(terms) => Max(terms.iter().map(|t| t.div(d)).collect()),\n            Broadcast(terms) => Broadcast(terms.iter().map(|t| t.div(d)).collect()),\n            Mul(_) => Div(Box::new(self.clone()), d),\n            MulInt(p, a) => {\n                if *p == d as i64 {\n                    (**a).clone()\n                } else {\n                    let gcd = p.unsigned_abs().gcd(&d);\n                    MulInt(p / gcd as i64, b!(a.div(d / gcd)))\n                }\n            }\n            Div(a, q) => Div(a.clone(), q * d),\n            Ge(_, _) | Eq(_, _) => Div(Box::new(self.clone()), d),\n        }\n    }\n\n    pub fn div_ceil(self, rhs: u64) -> TDim {\n        TDim::Div(Box::new(Add(vec![self, Val(rhs as i64 - 1)])), rhs).reduce()\n    }\n\n    pub(super) fn guess_slope(&self, sym: &Symbol) -> (i64, u64) {\n        fn slope_rec(d: &TDim, sym: &Symbol) -> (i64, i64) {\n            match d {\n                Val(_) => (0, 1),\n                Sym(s) => ((sym == s) as i64, 1),\n                Add(terms) => terms\n                    .iter()\n                    .map(|d| slope_rec(d, sym))\n                    .fold((0, 1), |a, b| ((a.0 * b.1 + a.1 * b.0), (b.1 * a.1))),\n                Mul(terms) => terms\n                    .iter()\n                    .map(|d| slope_rec(d, sym))\n                    .fold((1, 1), |a, b| ((a.0 * b.0), (b.1 * a.1))),\n                MulInt(p, a) => {\n                    let (n, d) = slope_rec(a, sym);\n                    (p * n, d)\n                }\n                Div(a, q) => {\n                    let (n, d) = slope_rec(a, sym);\n                    (n, d * *q as i64)\n                }\n                Broadcast(terms) => slope_rec(&terms[0], sym),\n                Min(terms) => slope_rec(&terms[0], sym),\n                Max(terms) => slope_rec(&terms[0], sym),\n                Ge(_, _) | Eq(_, _) => (0, 1),\n            }\n        }\n        let (p, q) = slope_rec(self, sym);\n        reduce_ratio(p, q)\n    }\n\n    #[allow(clippy::mutable_key_type)]\n    pub fn symbols(&self) -> std::collections::HashSet<Symbol> {\n        match self {\n            Val(_) => maplit::hashset!(),\n            Sym(s) => maplit::hashset!(s.clone()),\n            Add(terms) | Mul(terms) | Broadcast(terms) | Min(terms) | Max(terms) => {\n                terms.iter().fold(maplit::hashset!(), |mut set, v| {\n                    set.extend(v.symbols());\n                    set\n                })\n            }\n            MulInt(_, a) => a.symbols(),\n            Div(a, _) => a.symbols(),\n            Ge(a, b) | Eq(a, b) => {\n                let mut set = a.symbols();\n                set.extend(b.symbols());\n                set\n            }\n        }\n    }\n\n    pub fn compatible_with(&self, other: &TDim) -> bool {\n        if let Ok(x) = (self.clone() - other).to_i64() {\n            return x == 0;\n        }\n        true // maybe ? :)\n    }\n}\n\npub(super) fn reduce_ratio(mut p: i64, mut q: i64) -> (i64, u64) {\n    let gcd = p.abs().gcd(&q.abs());\n    if gcd > 1 {\n        p /= gcd;\n        q /= gcd;\n    }\n    if q < 0 { (-p, (-q) as u64) } else { (p, q as u64) }\n}\n\nimpl Zero for TDim {\n    fn zero() -> Self {\n        Val(0)\n    }\n    fn is_zero(&self) -> bool {\n        matches!(self, Val(0))\n    }\n}\n\nimpl Default for TDim {\n    fn default() -> TDim {\n        Val(0)\n    }\n}\n\nimpl num_traits::Bounded for TDim {\n    fn min_value() -> Self {\n        TDim::Val(i64::MIN)\n    }\n\n    fn max_value() -> Self {\n        TDim::Val(i64::MAX)\n    }\n}\n\nimpl num_traits::One for TDim {\n    fn one() -> Self {\n        TDim::Val(1)\n    }\n}\n\nimpl ::std::iter::Sum for TDim {\n    fn sum<I: Iterator<Item = TDim>>(iter: I) -> TDim {\n        iter.fold(0.into(), |a, b| a + b)\n    }\n}\n\nimpl<'a> ::std::iter::Sum<&'a TDim> for TDim {\n    fn sum<I: Iterator<Item = &'a TDim>>(iter: I) -> TDim {\n        iter.fold(0.into(), |a, b| a + b)\n    }\n}\n\nimpl std::iter::Product for TDim {\n    fn product<I: Iterator<Item = TDim>>(iter: I) -> Self {\n        iter.fold(TDim::Val(1), |a, b| a * b)\n    }\n}\n\nimpl<'a> ::std::iter::Product<&'a TDim> for TDim {\n    fn product<I: Iterator<Item = &'a TDim>>(iter: I) -> TDim {\n        iter.fold(1.into(), |a, b| a * b)\n    }\n}\n\nmacro_rules! from_i {\n    ($i: ty) => {\n        impl From<$i> for TDim {\n            fn from(v: $i) -> TDim {\n                TDim::Val(v as _)\n            }\n        }\n        impl<'a> From<&'a $i> for TDim {\n            fn from(v: &'a $i) -> TDim {\n                TDim::Val(*v as _)\n            }\n        }\n    };\n}\n\nfrom_i!(i32);\nfrom_i!(i64);\nfrom_i!(u64);\nfrom_i!(isize);\nfrom_i!(usize);\n\nimpl From<Symbol> for TDim {\n    fn from(it: Symbol) -> Self {\n        TDim::Sym(it)\n    }\n}\n\nimpl<'a> From<&'a Symbol> for TDim {\n    fn from(it: &'a Symbol) -> Self {\n        TDim::Sym(it.clone())\n    }\n}\n\nimpl ops::Neg for TDim {\n    type Output = Self;\n    fn neg(self) -> Self {\n        if let Val(v) = self { Val(-v) } else { TDim::MulInt(-1, Box::new(self)).reduce() }\n    }\n}\n\nimpl<'a> ops::AddAssign<&'a TDim> for TDim {\n    fn add_assign(&mut self, rhs: &'a TDim) {\n        if rhs.is_zero() {\n        } else if self.is_zero() {\n            *self = rhs.clone();\n        } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {\n            *s += o;\n        } else {\n            *self = TDim::Add(vec![std::mem::take(self), rhs.clone()]).reduce()\n        }\n    }\n}\n\nimpl<I> ops::AddAssign<I> for TDim\nwhere\n    I: Into<TDim>,\n{\n    fn add_assign(&mut self, rhs: I) {\n        let rhs = rhs.into();\n        if rhs.is_zero() {\n        } else if self.is_zero() {\n            *self = rhs;\n        } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {\n            *s += o;\n        } else {\n            *self = TDim::Add(vec![std::mem::take(self), rhs]).reduce()\n        }\n    }\n}\n\nimpl<I> ops::Add<I> for TDim\nwhere\n    I: Into<TDim>,\n{\n    type Output = Self;\n    fn add(mut self, rhs: I) -> Self {\n        self += rhs;\n        self\n    }\n}\n\nimpl<'a> ops::Add<&'a TDim> for TDim {\n    type Output = Self;\n    fn add(mut self, rhs: &'a TDim) -> Self {\n        self += rhs;\n        self\n    }\n}\n\n#[allow(clippy::suspicious_op_assign_impl)]\nimpl<'a> ops::SubAssign<&'a TDim> for TDim {\n    fn sub_assign(&mut self, rhs: &'a TDim) {\n        if rhs.is_zero() {\n        } else if self.is_zero() {\n            *self = rhs.clone().neg();\n        } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {\n            *s -= o;\n        } else {\n            *self = TDim::Add(vec![std::mem::take(self), rhs.clone().neg()]).reduce()\n        }\n    }\n}\n\nimpl<I> ops::SubAssign<I> for TDim\nwhere\n    I: Into<TDim>,\n{\n    fn sub_assign(&mut self, rhs: I) {\n        let rhs = rhs.into();\n        if rhs.is_zero() {\n        } else if self.is_zero() {\n            *self = rhs.neg();\n        } else if let (Val(s), Val(o)) = (&mut *self, &rhs) {\n            *s -= o;\n        } else {\n            *self = TDim::Add(vec![std::mem::take(self), rhs.neg()]).reduce()\n        }\n    }\n}\n\nimpl<I> ops::Sub<I> for TDim\nwhere\n    I: Into<TDim>,\n{\n    type Output = Self;\n    fn sub(mut self, rhs: I) -> Self {\n        self -= rhs;\n        self\n    }\n}\n\nimpl<'a> ops::Sub<&'a TDim> for TDim {\n    type Output = Self;\n    fn sub(mut self, rhs: &'a TDim) -> Self {\n        self -= rhs;\n        self\n    }\n}\n\nimpl<I: Into<TDim>> ops::MulAssign<I> for TDim {\n    fn mul_assign(&mut self, rhs: I) {\n        let rhs = rhs.into();\n        if self.is_one() {\n            *self = rhs\n        } else if rhs.is_one() {\n        } else {\n            *self = TDim::Mul(vec![rhs, std::mem::take(self)]).reduce()\n        }\n    }\n}\n\nimpl<'a> ops::MulAssign<&'a TDim> for TDim {\n    fn mul_assign(&mut self, rhs: &'a TDim) {\n        if self.is_one() {\n            *self = rhs.clone()\n        } else if rhs.is_one() {\n        } else {\n            *self = TDim::Mul(vec![std::mem::take(self), rhs.clone()]).reduce()\n        }\n    }\n}\n\nimpl<I: Into<TDim>> ops::Mul<I> for TDim {\n    type Output = Self;\n    fn mul(mut self, rhs: I) -> Self {\n        self *= rhs.into();\n        self\n    }\n}\n\nimpl<'a> ops::Mul<&'a TDim> for TDim {\n    type Output = Self;\n    fn mul(mut self, rhs: &'a TDim) -> Self {\n        self *= rhs;\n        self\n    }\n}\n\nimpl<I: AsPrimitive<u64> + PrimInt> ops::DivAssign<I> for TDim {\n    fn div_assign(&mut self, rhs: I) {\n        *self = TDim::Div(Box::new(std::mem::take(self)), rhs.as_()).reduce()\n    }\n}\n\nimpl<I: AsPrimitive<u64> + PrimInt> ops::Div<I> for TDim {\n    type Output = Self;\n    fn div(mut self, rhs: I) -> Self {\n        self /= rhs.as_();\n        self\n    }\n}\n\nimpl<I: AsPrimitive<u64> + PrimInt> ops::RemAssign<I> for TDim {\n    fn rem_assign(&mut self, rhs: I) {\n        *self += -(self.clone() / rhs.as_() * rhs.as_());\n    }\n}\n\nimpl<I: AsPrimitive<u64> + PrimInt> ops::Rem<I> for TDim {\n    type Output = Self;\n    fn rem(mut self, rhs: I) -> Self {\n        self %= rhs;\n        self\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    macro_rules! b( ($e:expr) => { Box::new($e) } );\n\n    lazy_static::lazy_static! {\n        static ref table: SymbolScope = SymbolScope::default();\n        static ref A: Symbol = table.sym(\"a\");\n        static ref B: Symbol = table.sym(\"b\");\n        static ref C: Symbol = table.sym(\"c\");\n        static ref D: Symbol = table.sym(\"d\");\n        static ref E: Symbol = table.sym(\"e\");\n    }\n\n    fn neg(a: &TDim) -> TDim {\n        mul(-1, a)\n    }\n\n    fn add(a: &TDim, b: &TDim) -> TDim {\n        TDim::Add(vec![a.clone(), b.clone()])\n    }\n\n    fn mul(a: i64, b: &TDim) -> TDim {\n        TDim::MulInt(a, b![b.clone()])\n    }\n\n    fn div(a: &TDim, b: u64) -> TDim {\n        TDim::Div(b!(a.clone()), b)\n    }\n\n    #[test]\n    fn reduce_add() {\n        assert_eq!(add(&A.to_dim(), &neg(&A.to_dim())).reduce(), Val(0))\n    }\n\n    #[test]\n    fn reduce_neg_mul() {\n        assert_eq!(neg(&mul(2, &A.to_dim())).reduce(), mul(-2, &A.to_dim()))\n    }\n\n    #[test]\n    fn reduce_cplx_ex_2() {\n        assert_eq!(\n            add(\n                &add(&Val(-4), &mul(-2, &div(&A.to_dim(), 4))),\n                &mul(-2, &mul(-1, &div(&A.to_dim(), 4)))\n            )\n            .reduce(),\n            Val(-4)\n        )\n    }\n\n    #[test]\n    fn reduce_cplx_ex_3() {\n        assert_eq!(div(&MulInt(1, b!(MulInt(4, b!(A.to_dim())))), 4).reduce(), A.to_dim())\n    }\n\n    #[test]\n    fn reduce_cplx_ex_4() {\n        // (S+1)/2 + (1-S)/2 == 1\n        assert_eq!(\n            add(&div(&add(&A.to_dim(), &Val(1)), 2), &div(&add(&neg(&A.to_dim()), &Val(1)), 2))\n                .reduce(),\n            1.into()\n        );\n    }\n\n    #[test]\n    fn reduce_mul_mul_1() {\n        assert_eq!(mul(3, &mul(2, &A.to_dim())).reduce(), mul(6, &A.to_dim()))\n    }\n\n    #[test]\n    fn reduce_mul_mul_2() {\n        assert_eq!(mul(-2, &mul(-1, &A.to_dim())).reduce(), mul(2, &A.to_dim()))\n    }\n\n    #[test]\n    fn reduce_mul_div_1() {\n        assert_eq!(mul(2, &div(&mul(-1, &A.to_dim()), 3)).reduce(), mul(-2, &div(&A.to_dim(), 3)))\n    }\n\n    #[test]\n    fn const_and_add() {\n        let e: TDim = 2i64.into();\n        assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), 2);\n        let e: TDim = TDim::from(2) + 3;\n        assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), 5);\n        let e: TDim = TDim::from(2) - 3;\n        assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), -1);\n        let e: TDim = -TDim::from(2);\n        assert_eq!(e.eval(&SymbolValues::default()).to_i64().unwrap(), -2);\n    }\n\n    #[test]\n    fn substitution() {\n        let a: TDim = A.to_dim();\n        assert_eq!(a.eval(&SymbolValues::default().with(&A, 2)).to_i64().unwrap(), 2);\n        let e = a + 3;\n        assert_eq!(e.eval(&SymbolValues::default().with(&A, 2)).to_i64().unwrap(), 5);\n    }\n\n    #[test]\n    fn reduce_adds() {\n        let e: TDim = TDim::from(2) + 1;\n        assert_eq!(e, TDim::from(3));\n        let e: TDim = TDim::from(3) + 2;\n        assert_eq!(e, TDim::from(5));\n        let e: TDim = TDim::from(3) + 0;\n        assert_eq!(e, TDim::from(3));\n        let e: TDim = TDim::from(3) + 2 + 1;\n        assert_eq!(e, TDim::from(6));\n    }\n\n    #[test]\n    fn reduce_muls() {\n        let e: TDim = Val(1) * A.to_dim();\n        assert_eq!(e, A.to_dim());\n        let e: TDim = A.to_dim() * &B.to_dim() * 1;\n        assert_eq!(e, A.to_dim() * &B.to_dim());\n    }\n\n    #[test]\n    fn reduce_divs() {\n        let e: TDim = TDim::from(2) / 1;\n        assert_eq!(e, TDim::from(2));\n        let e: TDim = TDim::from(3) / 2;\n        assert_eq!(e, TDim::from(1));\n        let e: TDim = TDim::from(3) % 2;\n        assert_eq!(e, TDim::from(1));\n        let e: TDim = TDim::from(5) / 2;\n        assert_eq!(e, TDim::from(2));\n        let e: TDim = TDim::from(5) % 2;\n        assert_eq!(e, TDim::from(1));\n    }\n\n    #[test]\n    fn reduce_div_bug_0() {\n        let e1: TDim = (A.to_dim() + 23) / 2 - 1;\n        let e2: TDim = (A.to_dim() + 21) / 2;\n        assert_eq!(e1, e2);\n    }\n\n    #[test]\n    fn reduce_div_bug_1() {\n        let e1: TDim = (A.to_dim() + -1) / 2;\n        let e2: TDim = (A.to_dim() + 1) / 2 - 1;\n        assert_eq!(e1, e2);\n    }\n\n    #[test]\n    fn reduce_div_bug_2() {\n        let e1: TDim = ((A.to_dim() + 1) / 2 + 1) / 2;\n        let e2: TDim = (A.to_dim() + 3) / 4;\n        assert_eq!(e1, e2);\n    }\n\n    #[test]\n    fn reduce_div_bug_3() {\n        let e1: TDim = (A.to_dim() / 2) * -4;\n        let e2: TDim = (A.to_dim() / 2) * -4 / 1;\n        assert_eq!(e1, e2);\n    }\n\n    #[test]\n    fn reduce_mul_div() {\n        let e: TDim = A.to_dim() * 2 / 2;\n        assert_eq!(e, A.to_dim());\n    }\n\n    #[test]\n    fn reduce_div_mul() {\n        let e: TDim = A.to_dim() / 2 * 2;\n        assert_ne!(e, A.to_dim());\n    }\n\n    #[test]\n    fn reduce_add_div() {\n        let e: TDim = A.to_dim() / 2 + 1;\n        assert_eq!(e, ((A.to_dim() + 2) / 2));\n    }\n\n    #[test]\n    fn reduce_neg_mul_() {\n        let e: TDim = TDim::from(1) - A.to_dim() * 2;\n        assert_eq!(e, TDim::from(1) + A.to_dim() * -2);\n    }\n\n    #[test]\n    fn reduce_add_rem_1() {\n        assert_eq!(((A.to_dim() + 4) % 2), (A.to_dim() % 2));\n    }\n\n    #[test]\n    fn reduce_add_rem_2() {\n        assert_eq!(((A.to_dim() - 4) % 2), (A.to_dim() % 2));\n    }\n\n    #[test]\n    fn reduce_rem_div() {\n        let e: TDim = A.to_dim() % 2 / 2;\n        assert_eq!(e, TDim::from(0));\n    }\n\n    #[test]\n    fn conv2d_ex_1() {\n        let e = (TDim::from(1) - 1 + 1).div_ceil(1);\n        assert_eq!(e, TDim::from(1));\n    }\n\n    #[test]\n    fn conv2d_ex_2() {\n        let e = (A.to_dim() - 3 + 1).div_ceil(1);\n        assert_eq!(e, A.to_dim() + -2);\n    }\n\n    #[test]\n    fn extract_int_gcd_from_muls() {\n        let term = (A.to_dim() + 1) / 4;\n        let mul = (term.clone() * 24 - 24) * (term.clone() * 2 - 2);\n        let target = (term.clone() - 1) * (term.clone() - 1) * 48;\n        assert_eq!(mul, target);\n    }\n\n    #[test]\n    fn equality_of_muls() {\n        let term = (A.to_dim() + 1) / 4;\n        let mul1 = (term.clone() * 2 - 3) * (term.clone() - 1);\n        let mul2 = (term.clone() - 1) * (term.clone() * 2 - 3);\n        assert_eq!(mul1, mul2);\n    }\n\n    #[test]\n    fn factorize_complex_expr_times_int() {\n        let term = (A.to_dim() + 1) / 4;\n        let e = term.clone() * 2 - &term - 1;\n        assert_eq!(e, term - 1);\n    }\n\n    #[test]\n    fn broadcast_over_min() {\n        // assuming a>0, b>0 then a#min(a,b) can be replaced by a\n        // proof:\n        //    if b == 1 => min(a,b)=1 => a#1=a => ok\n        //    if a <= b => min(a,b)=a => ok\n        //    if 1 < B < A => expression was invalid, we're generalizing over the non-domain and ignoring the constraint\n        for a in 1..5 {\n            for b in 1..5 {\n                if b > 1 && a > b {\n                    assert!(a.broadcast(a.min(b)).is_err());\n                } else {\n                    assert_eq!(a.broadcast(a.min(b)).unwrap(), a);\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn min_ints_1() {\n        assert_eq!(2.to_dim().mini(1.to_dim()), 1.to_dim());\n    }\n\n    #[test]\n    fn min_ints_2() {\n        assert_eq!(1.to_dim().mini(2.to_dim()), 1.to_dim());\n    }\n\n    #[test]\n    fn min_same() {\n        assert_eq!(A.to_dim().mini(A.to_dim()), A.to_dim());\n    }\n\n    #[test]\n    fn min_noop() {\n        assert_eq!(A.to_dim().mini(1.to_dim()), A.to_dim().mini(1.to_dim()));\n    }\n\n    #[test]\n    fn min_diff_1() {\n        assert_eq!((A.to_dim() + 1).mini(A.to_dim() + 2), A.to_dim() + 1);\n    }\n\n    #[test]\n    fn slope_0() {\n        assert_eq!(12.to_dim().guess_slope(&A), (0, 1));\n    }\n\n    #[test]\n    fn slope_1() {\n        assert_eq!(A.to_dim().guess_slope(&A), (1, 1));\n    }\n\n    #[test]\n    fn slope_2() {\n        assert_eq!((A.to_dim() * 2).guess_slope(&A), (2, 1));\n    }\n\n    #[test]\n    fn slope_3() {\n        assert_eq!((A.to_dim() * 2 + A.to_dim() / 2).guess_slope(&A), (5, 2));\n    }\n\n    #[test]\n    fn slope_4() {\n        assert_eq!((A.to_dim()).guess_slope(&B), (0, 1));\n    }\n\n    #[test]\n    fn slope_5() {\n        assert_eq!((A.to_dim() + 1).guess_slope(&A), (1, 1));\n        assert_eq!((A.to_dim() + 1).guess_slope(&B), (0, 1));\n    }\n\n    #[test]\n    fn slope_6() {\n        assert_eq!((A.to_dim() + 1).guess_slope(&A), (1, 1));\n        assert_eq!((A.to_dim() + B.to_dim()).guess_slope(&B), (1, 1));\n    }\n\n    #[test]\n    fn min_0() -> TractResult<()> {\n        let symbols = SymbolScope::default();\n        assert_eq!(\n            symbols.parse_tdim(\"min(S+3, S+2)\").unwrap().simplify(),\n            symbols.parse_tdim(\"S+2\").unwrap(),\n        );\n        Ok(())\n    }\n\n    #[test]\n    fn commutative_mul_parens() -> TractResult<()> {\n        let symbols = SymbolScope::default();\n        assert_eq!(\n            symbols.parse_tdim(\"A*(B*C)\").unwrap().simplify(),\n            symbols.parse_tdim(\"(B*A)*C\").unwrap().simplify(),\n        );\n        Ok(())\n    }\n\n    #[test]\n    fn commutative_in_nemo_parakeet_model() -> TractResult<()> {\n        let symbols = SymbolScope::default();\n        assert_eq!(\n            symbols\n                .parse_tdim(\"8*(1+-1*max(0,5000+-1*(S+7)/8)+max(0,4999+(S+7)/8))*((B)*((S+7)/8))\")\n                .unwrap()\n                .simplify(),\n            symbols\n                .parse_tdim(\"8*((B)*(1+-1*max(0,5000+-1*(S+7)/8)+max(0,4999+(S+7)/8)))*((S+7)/8)\")\n                .unwrap()\n                .simplify(),\n        );\n        Ok(())\n    }\n\n    #[test]\n    fn commutative_mul_parens_deep() -> TractResult<()> {\n        let symbols = SymbolScope::default();\n        let deep_tdim = Mul(vec![\n            Mul(vec![Mul(vec![Mul(vec![A.to_dim(), B.to_dim()]), C.to_dim()]), D.to_dim()]),\n            E.to_dim(),\n        ])\n        .simplify();\n        assert_eq!(deep_tdim, symbols.parse_tdim(\"a*b*c*d*e\").unwrap().simplify());\n        Ok(())\n    }\n\n    // ---- Tests for new comparison/not TDim variants ----\n\n    #[test]\n    fn ge_concrete_true() {\n        assert_eq!(Ge(b!(Val(5)), b!(Val(3))).reduce(), Val(1));\n    }\n\n    #[test]\n    fn ge_concrete_false() {\n        assert_eq!(Ge(b!(Val(2)), b!(Val(3))).reduce(), Val(0));\n    }\n\n    #[test]\n    fn lt_concrete_true() {\n        // Lt(2,3) normalizes to Ge(3, 2+1) = Ge(3, 3)\n        assert_eq!(Ge(b!(Val(3)), b!(Val(3))).reduce(), Val(1));\n    }\n\n    #[test]\n    fn lt_concrete_false() {\n        // Lt(5,3) normalizes to Ge(3, 5+1) = Ge(3, 6)\n        assert_eq!(Ge(b!(Val(3)), b!(Val(6))).reduce(), Val(0));\n    }\n\n    #[test]\n    fn eq_concrete_true() {\n        assert_eq!(Eq(b!(Val(3)), b!(Val(3))).reduce(), Val(1));\n    }\n\n    #[test]\n    fn eq_concrete_false() {\n        assert_eq!(Eq(b!(Val(3)), b!(Val(4))).reduce(), Val(0));\n    }\n\n    #[test]\n    fn not_val_0() {\n        // not(0) = 1 - 0 = 1\n        assert_eq!((Val(1) - Val(0)).reduce(), Val(1));\n    }\n\n    #[test]\n    fn not_val_1() {\n        // not(1) = 1 - 1 = 0\n        assert_eq!((Val(1) - Val(1)).reduce(), Val(0));\n    }\n\n    #[test]\n    fn not_lt_becomes_ge() {\n        // not(Lt(x1, T)) = 1 - Ge(T, x1+1); check it evaluates correctly at boundary\n        let s = SymbolScope::default();\n        let t = s.sym(\"T\");\n        let x1 = s.sym(\"x1\");\n        // at x1 = T (boundary), Ge(T, T+1) = 0, so 1 - 0 = 1 (not-lt is true when x1 >= T)\n        let expr = Val(1) - Ge(b!(Sym(t.clone())), b!(Sym(x1.clone()) + Val(1)));\n        let at_boundary = expr.substitute(&x1, &Sym(t.clone())).unwrap().simplify();\n        assert_eq!(at_boundary, Val(1));\n    }\n\n    #[test]\n    fn eq_with_assertion_proves_false() {\n        // Eq(T, 0) should reduce to Val(0) when T >= 1\n        let s = SymbolScope::default();\n        s.add_assertion(\"T >= 1\").unwrap();\n        let t = s.sym(\"T\");\n        let expr = Eq(b!(Sym(t)), b!(Val(0)));\n        assert_eq!(expr.simplify(), Val(0));\n    }\n\n    #[test]\n    fn ge_coord_at_extremes() {\n        // Ge(x1, T) should not simplify without coordinate substitution\n        let s = SymbolScope::default();\n        s.add_assertion(\"T >= 1\").unwrap();\n        let t = s.sym(\"T\");\n        let x1 = s.sym(\"x1\");\n        let expr = Ge(b!(Sym(x1.clone())), b!(Sym(t.clone())));\n        // simplify() alone can't prove this false (x1 could be > T)\n        // but with coordinate substitution (x1 = T-1), Ge(T-1, T) = 0\n        let at_max = expr.substitute(&x1, &(Sym(t.clone()) - Val(1))).unwrap().simplify();\n        assert_eq!(at_max, Val(0));\n    }\n\n    #[test]\n    fn eval_to_i64_new_variants() {\n        use super::super::sym::SymbolValues;\n        let sv = SymbolValues::default();\n        assert_eq!(Ge(b!(Val(5)), b!(Val(3))).eval_to_i64(&sv).unwrap(), 1);\n        assert_eq!(Ge(b!(Val(3)), b!(Val(5))).eval_to_i64(&sv).unwrap(), 0);\n        assert_eq!(Eq(b!(Val(3)), b!(Val(3))).eval_to_i64(&sv).unwrap(), 1);\n        assert_eq!(Eq(b!(Val(3)), b!(Val(4))).eval_to_i64(&sv).unwrap(), 0);\n    }\n}\n"
  },
  {
    "path": "data/src/exotic.rs",
    "content": "#![allow(clippy::derived_hash_with_manual_eq)]\nuse crate::datum::DatumType;\nuse crate::dim::TDim;\nuse crate::internal::TVec;\nuse std::fmt::Debug;\n\nuse downcast_rs::{Downcast, impl_downcast};\nuse dyn_eq::DynEq;\nuse dyn_hash::DynHash;\n\npub trait ExoticFact:\n    DynHash + dyn_eq::DynEq + Send + Sync + Debug + dyn_clone::DynClone + Downcast\n{\n    /// Whether or not it is acceptable for a Patch to substitute `self` by `other`.\n    ///\n    /// In other terms, all operators consuming `self` MUST accept also accept `other` without being altered.\n    fn compatible_with(&self, other: &dyn ExoticFact) -> bool {\n        self.dyn_eq(other)\n    }\n\n    fn clarify_dt_shape(&self) -> Option<(DatumType, TVec<TDim>)> {\n        None\n    }\n\n    fn buffer_sizes(&self) -> TVec<TDim>;\n\n    fn mem_size(&self) -> TDim {\n        self.buffer_sizes().iter().sum::<TDim>()\n    }\n}\n\nimpl_downcast!(ExoticFact);\ndyn_hash::hash_trait_object!(ExoticFact);\ndyn_clone::clone_trait_object!(ExoticFact);\ndyn_eq::eq_trait_object!(ExoticFact);\n\nimpl<T: ExoticFact> From<T> for Box<dyn ExoticFact> {\n    fn from(v: T) -> Self {\n        Box::new(v)\n    }\n}\n\nimpl ExoticFact for TVec<Box<dyn ExoticFact>> {\n    fn buffer_sizes(&self) -> TVec<TDim> {\n        self.iter().flat_map(|it| it.buffer_sizes()).collect()\n    }\n}\nimpl ExoticFact for TVec<Option<Box<dyn ExoticFact>>> {\n    fn buffer_sizes(&self) -> TVec<TDim> {\n        self.iter().flatten().flat_map(|it| it.buffer_sizes()).collect()\n    }\n}\n"
  },
  {
    "path": "data/src/lib.rs",
    "content": "#![allow(clippy::len_zero)]\n#![allow(clippy::missing_safety_doc)]\n\npub extern crate itertools;\n\n#[macro_use]\nmod macros;\n\n/// A Smallvec instantiation with 4 embeddable values.\n///\n/// Used about everywhere in tract, for node inputs and outputs, or\n/// tensor dimensions.\npub type TVec<T> = smallvec::SmallVec<[T; 4]>;\n\npub type TractError = anyhow::Error;\npub type TractResult<T> = anyhow::Result<T>;\n\npub mod prelude {\n    pub use crate::TVec;\n    pub use crate::blob::Blob;\n    pub use crate::datum::{Datum, DatumType, QParams, round_ties_to_even};\n    pub use crate::dim::{Assertion, Symbol, SymbolScope, SymbolValues, TDim, ToDim};\n    pub use crate::tensor::litteral::*;\n    pub use crate::tensor::plain_view::{PlainView, PlainViewMut};\n    pub use crate::tensor::storage::{PlainStorage, TensorStorage};\n    pub use crate::tensor::{IntoArcTensor, IntoTensor, Tensor, natural_strides};\n    #[cfg(feature = \"complex\")]\n    pub use crate::tensor::{reinterpret_complex_as_inner_dim, reinterpret_inner_dim_as_complex};\n    pub use crate::tvec;\n    pub use crate::{TractError, TractResult};\n    pub use crate::{\n        dispatch_copy, dispatch_copy_by_size, dispatch_datum, dispatch_datum_by_size,\n        dispatch_floatlike, dispatch_hash, dispatch_numbers, dispatch_signed,\n    };\n    pub use half::f16;\n    pub use itertools as tract_itertools;\n    #[cfg(feature = \"complex\")]\n    pub use num_complex::Complex;\n}\n\npub mod internal {\n    pub use crate::datum::ClampCast;\n    pub use crate::dim::{DimLike, parse_tdim, solve_for};\n    pub use crate::exotic::ExoticFact;\n    pub use crate::prelude::*;\n    pub use crate::tensor::Approximation;\n    pub use crate::tensor::view::TensorView;\n    pub use crate::tensor::{clip_range_bounds, vector_size};\n    pub use anyhow::{Context as TractErrorContext, anyhow, bail, ensure, format_err};\n    pub use ndarray as tract_ndarray;\n    pub use num_integer;\n    pub use num_traits as tract_num_traits;\n    pub use smallvec as tract_smallvec;\n    pub type StaticName = std::borrow::Cow<'static, str>;\n}\n\npub use dim::TooEarly;\npub use half;\n\nmod blob;\nmod datum;\nmod dim;\nmod exotic;\nmod scatter;\nmod tensor;\n"
  },
  {
    "path": "data/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! tvec {\n    // count helper: transform any expression into 1\n    (@one $x:expr) => (1usize);\n    ($elem:expr; $n:expr) => ({\n        $crate::TVec::from_elem($elem, $n)\n    });\n    ($($x:expr),*$(,)*) => ({\n        let count = 0usize $(+ tvec!(@one $x))*;\n        #[allow(unused_mut)]\n        let mut vec = $crate::TVec::new();\n        if count <= vec.inline_size() {\n            $(vec.push($x);)*\n            vec\n        } else {\n            $crate::TVec::from_vec(vec![$($x,)*])\n        }\n    });\n}\n\n#[macro_export]\nmacro_rules! dispatch_datum {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => { {\n        use $crate::prelude::DatumType;\n        #[allow(unexpected_cfgs)]\n        match $dt {\n            DatumType::Bool => $($path)::*::<bool>($($args),*),\n            DatumType::U8   => $($path)::*::<u8>($($args),*),\n            DatumType::U16  => $($path)::*::<u16>($($args),*),\n            DatumType::U32  => $($path)::*::<u32>($($args),*),\n            DatumType::U64  => $($path)::*::<u64>($($args),*),\n            DatumType::I8   => $($path)::*::<i8>($($args),*),\n            DatumType::I16  => $($path)::*::<i16>($($args),*),\n            DatumType::I32  => $($path)::*::<i32>($($args),*),\n            DatumType::I64  => $($path)::*::<i64>($($args),*),\n            DatumType::F16  => $($path)::*::<f16>($($args),*),\n            DatumType::F32  => $($path)::*::<f32>($($args),*),\n            DatumType::F64  => $($path)::*::<f64>($($args),*),\n            DatumType::Blob => $($path)::*::<$crate::prelude::Blob>($($args),*),\n            DatumType::TDim => $($path)::*::<TDim>($($args),*),\n            DatumType::String => $($path)::*::<String>($($args),*),\n            DatumType::QI8(_) => $($path)::*::<i8>($($args),*),\n            DatumType::QU8(_) => $($path)::*::<u8>($($args),*),\n            DatumType::QI32(_) => $($path)::*::<i32>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI16 => $($path)::*::<Complex<i16>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI32 => $($path)::*::<Complex<i32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI64 => $($path)::*::<Complex<i64>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF16 => $($path)::*::<Complex<f16>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF32 => $($path)::*::<Complex<f32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF64 => $($path)::*::<Complex<f64>>($($args),*),\n        }\n    } }\n}\n\n#[macro_export]\nmacro_rules! dispatch_datum_by_size {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => { {\n        use $crate::prelude::DatumType;\n        #[allow(unexpected_cfgs)]\n        match $dt {\n            DatumType::Bool => $($path)::*::<i8>($($args),*),\n            DatumType::U8   => $($path)::*::<i8>($($args),*),\n            DatumType::U16  => $($path)::*::<i16>($($args),*),\n            DatumType::U32  => $($path)::*::<i32>($($args),*),\n            DatumType::U64  => $($path)::*::<i64>($($args),*),\n            DatumType::I8   => $($path)::*::<i8>($($args),*),\n            DatumType::I16  => $($path)::*::<i16>($($args),*),\n            DatumType::I32  => $($path)::*::<i32>($($args),*),\n            DatumType::I64  => $($path)::*::<i64>($($args),*),\n            DatumType::F16  => $($path)::*::<i16>($($args),*),\n            DatumType::F32  => $($path)::*::<i32>($($args),*),\n            DatumType::F64  => $($path)::*::<i64>($($args),*),\n            DatumType::Blob => $($path)::*::<Blob>($($args),*),\n            DatumType::TDim => $($path)::*::<TDim>($($args),*),\n            DatumType::String => $($path)::*::<String>($($args),*),\n            DatumType::QI8(_)   => $($path)::*::<i8>($($args),*),\n            DatumType::QU8(_)   => $($path)::*::<u8>($($args),*),\n            DatumType::QI32(_)   => $($path)::*::<i32>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI16 => $($path)::*::<Complex<i16>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI32 => $($path)::*::<Complex<i32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI64 => $($path)::*::<Complex<i64>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF16 => $($path)::*::<Complex<f16>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF32 => $($path)::*::<Complex<f32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF64 => $($path)::*::<Complex<f64>>($($args),*),\n        }\n    } }\n}\n\n#[macro_export]\nmacro_rules! dispatch_copy {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => { {\n        use $crate::prelude::DatumType;\n        #[allow(unexpected_cfgs)]\n        match $dt {\n            DatumType::Bool => $($path)::*::<bool>($($args),*),\n            DatumType::U8   => $($path)::*::<u8>($($args),*),\n            DatumType::U16  => $($path)::*::<u16>($($args),*),\n            DatumType::U32  => $($path)::*::<u32>($($args),*),\n            DatumType::U64  => $($path)::*::<u64>($($args),*),\n            DatumType::I8   => $($path)::*::<i8>($($args),*),\n            DatumType::I16  => $($path)::*::<i16>($($args),*),\n            DatumType::I32  => $($path)::*::<i32>($($args),*),\n            DatumType::I64  => $($path)::*::<i64>($($args),*),\n            DatumType::F16  => $($path)::*::<f16>($($args),*),\n            DatumType::F32  => $($path)::*::<f32>($($args),*),\n            DatumType::F64  => $($path)::*::<f64>($($args),*),\n            DatumType::QI8(_)  => $($path)::*::<i8>($($args),*),\n            DatumType::QU8(_)  => $($path)::*::<u8>($($args),*),\n            DatumType::QI32(_)  => $($path)::*::<u8>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI16 => $($path)::*::<Complex<i16>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI32 => $($path)::*::<Complex<i32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI64 => $($path)::*::<Complex<i64>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF16 => $($path)::*::<Complex<f16>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF32 => $($path)::*::<Complex<f32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF64 => $($path)::*::<Complex<f64>>($($args),*),\n            _ => panic!(\"{:?} is not Copy\", $dt)\n        }\n    } }\n}\n\n#[macro_export]\nmacro_rules! dispatch_copy_by_size {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => { {\n        use $crate::prelude::DatumType;\n        #[allow(unexpected_cfgs)]\n        match $dt {\n            DatumType::Bool => $($path)::*::<i8>($($args),*),\n            DatumType::U8   => $($path)::*::<i8>($($args),*),\n            DatumType::U16  => $($path)::*::<i16>($($args),*),\n            DatumType::U32  => $($path)::*::<i32>($($args),*),\n            DatumType::U64  => $($path)::*::<i64>($($args),*),\n            DatumType::I8   => $($path)::*::<i8>($($args),*),\n            DatumType::I16  => $($path)::*::<i16>($($args),*),\n            DatumType::I32  => $($path)::*::<i32>($($args),*),\n            DatumType::I64  => $($path)::*::<i64>($($args),*),\n            DatumType::F16  => $($path)::*::<i16>($($args),*),\n            DatumType::F32  => $($path)::*::<i32>($($args),*),\n            DatumType::F64  => $($path)::*::<i64>($($args),*),\n            DatumType::QI8(_)  => $($path)::*::<i8>($($args),*),\n            DatumType::QU8(_)  => $($path)::*::<u8>($($args),*),\n            DatumType::QI32(_)  => $($path)::*::<i32>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI32 => $($path)::*::<Complex<i32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI64 => $($path)::*::<Complex<i64>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF16 => $($path)::*::<Complex<f16>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF32 => $($path)::*::<Complex<f32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF64 => $($path)::*::<Complex<f64>>($($args),*),\n            _ => panic!(\"{:?} is not Copy\", $dt)\n        }\n    } }\n}\n\n#[macro_export]\nmacro_rules! dispatch_numbers {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => { {\n        use $crate::prelude::DatumType;\n        match $dt {\n            DatumType::U8   => $($path)::*::<u8>($($args),*),\n            DatumType::U16  => $($path)::*::<u16>($($args),*),\n            DatumType::U32  => $($path)::*::<u32>($($args),*),\n            DatumType::U64  => $($path)::*::<u64>($($args),*),\n            DatumType::I8   => $($path)::*::<i8>($($args),*),\n            DatumType::I16  => $($path)::*::<i16>($($args),*),\n            DatumType::I32  => $($path)::*::<i32>($($args),*),\n            DatumType::I64  => $($path)::*::<i64>($($args),*),\n            DatumType::F16  => $($path)::*::<f16>($($args),*),\n            DatumType::F32  => $($path)::*::<f32>($($args),*),\n            DatumType::F64  => $($path)::*::<f64>($($args),*),\n            DatumType::QI8(_)  => $($path)::*::<i8>($($args),*),\n            DatumType::QU8(_)  => $($path)::*::<u8>($($args),*),\n            DatumType::QI32(_)  => $($path)::*::<i32>($($args),*),\n            _ => $crate::internal::bail!(\"{:?} is not a number\", $dt)\n        }\n    } }\n}\n\n#[macro_export]\nmacro_rules! dispatch_zerolike {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => { {\n        use $crate::prelude::DatumType;\n        match $dt {\n            DatumType::TDim => $($path)::*::<TDim>($($args),*),\n            DatumType::U8   => $($path)::*::<u8>($($args),*),\n            DatumType::U16  => $($path)::*::<u16>($($args),*),\n            DatumType::U32  => $($path)::*::<u32>($($args),*),\n            DatumType::U64  => $($path)::*::<u64>($($args),*),\n            DatumType::I8   => $($path)::*::<i8>($($args),*),\n            DatumType::I16  => $($path)::*::<i16>($($args),*),\n            DatumType::I32  => $($path)::*::<i32>($($args),*),\n            DatumType::I64  => $($path)::*::<i64>($($args),*),\n            DatumType::F16  => $($path)::*::<f16>($($args),*),\n            DatumType::F32  => $($path)::*::<f32>($($args),*),\n            DatumType::F64  => $($path)::*::<f64>($($args),*),\n            DatumType::QI8(_)  => $($path)::*::<i8>($($args),*),\n            DatumType::QU8(_)  => $($path)::*::<u8>($($args),*),\n            DatumType::QI32(_)  => $($path)::*::<i32>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI32 => $($path)::*::<Complex<i32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI64 => $($path)::*::<Complex<i64>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF16 => $($path)::*::<Complex<f16>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF32 => $($path)::*::<Complex<f32>>($($args),*),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF64 => $($path)::*::<Complex<f64>>($($args),*),\n            _ => $crate::internal::bail!(\"{:?} doesn't implement num_traits::Zero\", $dt)\n        }\n    } }\n}\n\n#[macro_export]\nmacro_rules! dispatch_floatlike {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => { {\n        use $crate::prelude::DatumType;\n        match $dt {\n            DatumType::F16  => $($path)::*::<f16>($($args),*),\n            DatumType::F32  => $($path)::*::<f32>($($args),*),\n            DatumType::F64  => $($path)::*::<f64>($($args),*),\n            _ => $crate::internal::bail!(\"{:?} is not float-like\", $dt)\n        }\n    } }\n}\n\n#[macro_export]\nmacro_rules! dispatch_signed {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => { {\n        use $crate::prelude::DatumType;\n        match $dt {\n            DatumType::F16  => $($path)::*::<f16>($($args),*),\n            DatumType::F32  => $($path)::*::<f32>($($args),*),\n            DatumType::F64  => $($path)::*::<f64>($($args),*),\n            DatumType::I8   => $($path)::*::<i8>($($args),*),\n            DatumType::I16  => $($path)::*::<i16>($($args),*),\n            DatumType::I32  => $($path)::*::<i32>($($args),*),\n            DatumType::I64  => $($path)::*::<i64>($($args),*),\n            DatumType::TDim => $($path)::*::<TDim>($($args),*),\n            _ => $crate::internal::bail!(\"{:?} is not signed\", $dt)\n        }\n    } }\n}\n\n#[macro_export]\nmacro_rules! dispatch_hash {\n    ($($path:ident)::* ($dt:expr) ($($args:expr),*)) => { {\n        use $crate::prelude::DatumType;\n        #[allow(unexpected_cfgs)]\n        match $dt {\n            DatumType::Bool => $($path)::*::<bool>($($args),*),\n            DatumType::U8   => $($path)::*::<u8>($($args),*),\n            DatumType::U16  => $($path)::*::<u16>($($args),*),\n            DatumType::U32  => $($path)::*::<u32>($($args),*),\n            DatumType::U64  => $($path)::*::<u64>($($args),*),\n            DatumType::I8   => $($path)::*::<i8>($($args),*),\n            DatumType::I16  => $($path)::*::<i16>($($args),*),\n            DatumType::I32  => $($path)::*::<i32>($($args),*),\n            DatumType::I64  => $($path)::*::<i64>($($args),*),\n            DatumType::Blob => $($path)::*::<Blob>($($args),*),\n            DatumType::TDim => $($path)::*::<TDim>($($args),*),\n            DatumType::String => $($path)::*::<String>($($args),*),\n            #[cfg(feature=\"complex\")]\n            DatumType::ComplexI16 => $($path)::*::<Complex<i16>>($($args),*),\n            #[cfg(feature=\"complex\")]\n            DatumType::ComplexI32 => $($path)::*::<Complex<i32>>($($args),*),\n            #[cfg(feature=\"complex\")]\n            DatumType::ComplexI64 => $($path)::*::<Complex<i64>>($($args),*),\n            _ => $crate::internal::bail!(\"{:?} is not Hash\", $dt)\n        }\n    } }\n}\n"
  },
  {
    "path": "data/src/scatter.rs",
    "content": "use crate::prelude::*;\nuse ndarray::Dimension;\n\npub(crate) unsafe fn scatter_contig_data<T: Datum>(\n    mut src: *const T,\n    dst: *mut T,\n    dst_len_and_strides: &[(usize, usize)],\n) {\n    unsafe {\n        match *dst_len_and_strides {\n            [(len_a, stride_a)] => {\n                for a in 0..len_a {\n                    *dst.add(a * stride_a) = (*src).clone();\n                    src = src.offset(1);\n                }\n            }\n            [(len_a, stride_a), (len_b, stride_b)] => {\n                for a in 0..len_a {\n                    for b in 0..len_b {\n                        *dst.add(a * stride_a + b * stride_b) = (*src).clone();\n                        src = src.offset(1);\n                    }\n                }\n            }\n            [(len_a, stride_a), (len_b, stride_b), (len_c, stride_c)] => {\n                for a in 0..len_a {\n                    for b in 0..len_b {\n                        for c in 0..len_c {\n                            *dst.add(a * stride_a + b * stride_b + c * stride_c) = (*src).clone();\n                            src = src.offset(1);\n                        }\n                    }\n                }\n            }\n            [(len_a, stride_a), (len_b, stride_b), (len_c, stride_c), (len_d, stride_d)] => {\n                for a in 0..len_a {\n                    for b in 0..len_b {\n                        for c in 0..len_c {\n                            for d in 0..len_d {\n                                *dst.add(\n                                    a * stride_a + b * stride_b + c * stride_c + d * stride_d,\n                                ) = (*src).clone();\n                                src = src.offset(1);\n                            }\n                        }\n                    }\n                }\n            }\n            [\n                (len_a, stride_a),\n                (len_b, stride_b),\n                (len_c, stride_c),\n                (len_d, stride_d),\n                (len_e, stride_e),\n            ] => {\n                for a in 0..len_a {\n                    for b in 0..len_b {\n                        for c in 0..len_c {\n                            for d in 0..len_d {\n                                for e in 0..len_e {\n                                    *dst.add(\n                                        a * stride_a\n                                            + b * stride_b\n                                            + c * stride_c\n                                            + d * stride_d\n                                            + e * stride_e,\n                                    ) = (*src).clone();\n                                    src = src.offset(1);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n            _ => {\n                let shape: TVec<usize> = dst_len_and_strides.iter().map(|pair| pair.0).collect();\n                for coords in ndarray::indices(&*shape) {\n                    let offset = coords\n                        .slice()\n                        .iter()\n                        .zip(dst_len_and_strides.iter())\n                        .map(|(x, (_len, stride))| x * stride)\n                        .sum::<usize>();\n                    *dst.add(offset) = (*src).clone();\n                    src = src.offset(1);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/tensor/litteral.rs",
    "content": "use super::Tensor;\nuse crate::datum::Datum;\nuse ndarray::*;\nuse std::sync::Arc;\n\npub fn arr4<A, const N: usize, const M: usize, const T: usize>(xs: &[[[[A; T]; M]; N]]) -> Array4<A>\nwhere\n    A: Clone,\n{\n    use ndarray::*;\n    let xs = xs.to_vec();\n    let dim = Ix4(xs.len(), N, M, T);\n    let len = xs.len();\n    let cap = xs.capacity();\n    let expand_len = len * N * M * T;\n    let ptr = Box::into_raw(xs.into_boxed_slice());\n    unsafe {\n        let v = if ::std::mem::size_of::<A>() == 0 {\n            Vec::from_raw_parts(ptr as *mut A, expand_len, expand_len)\n        } else if N == 0 || M == 0 || T == 0 {\n            Vec::new()\n        } else {\n            let expand_cap = cap * N * M * T;\n            Vec::from_raw_parts(ptr as *mut A, expand_len, expand_cap)\n        };\n        ArrayBase::from_shape_vec_unchecked(dim, v)\n    }\n}\n\npub fn tensor0<A: Datum>(x: A) -> Tensor {\n    unsafe {\n        let mut tensor = Tensor::uninitialized::<A>(&[]).unwrap();\n        tensor.as_slice_mut_unchecked::<A>()[0] = x;\n        tensor\n    }\n}\n\npub fn tensor1<A: Datum>(xs: &[A]) -> Tensor {\n    Tensor::from(arr1(xs))\n}\n\npub fn tensor2<A: Datum, const N: usize>(xs: &[[A; N]]) -> Tensor {\n    Tensor::from(arr2(xs))\n}\n\npub fn tensor3<A: Datum, const N: usize, const M: usize>(xs: &[[[A; M]; N]]) -> Tensor {\n    Tensor::from(arr3(xs))\n}\n\npub fn tensor4<A: Datum, const N: usize, const M: usize, const T: usize>(\n    xs: &[[[[A; T]; M]; N]],\n) -> Tensor {\n    Tensor::from(arr4(xs))\n}\n\npub fn rctensor0<A: Datum>(x: A) -> Arc<Tensor> {\n    Arc::new(Tensor::from(arr0(x)))\n}\n\npub fn rctensor1<A: Datum>(xs: &[A]) -> Arc<Tensor> {\n    Arc::new(Tensor::from(arr1(xs)))\n}\n\npub fn rctensor2<A: Datum, const N: usize>(xs: &[[A; N]]) -> Arc<Tensor> {\n    Arc::new(Tensor::from(arr2(xs)))\n}\n\npub fn rctensor3<A: Datum, const N: usize, const M: usize>(xs: &[[[A; M]; N]]) -> Arc<Tensor> {\n    Arc::new(Tensor::from(arr3(xs)))\n}\n\npub fn rctensor4<A: Datum, const N: usize, const M: usize, const T: usize>(\n    xs: &[[[[A; T]; M]; N]],\n) -> Arc<Tensor> {\n    Arc::new(Tensor::from(arr4(xs)))\n}\n"
  },
  {
    "path": "data/src/tensor/plain_view.rs",
    "content": "use std::alloc::Layout;\n\nuse ndarray::prelude::*;\n\nuse crate::datum::{Datum, DatumType};\nuse crate::internal::*;\nuse crate::tensor::Tensor;\n\nuse super::storage::PlainStorage;\n\nfn check_for_access<D: Datum>(dt: DatumType) -> TractResult<()> {\n    ensure!(\n        dt.unquantized() == D::datum_type().unquantized(),\n        \"Tensor datum type error: tensor is {:?}, accessed as {:?}\",\n        dt,\n        D::datum_type(),\n    );\n    Ok(())\n}\n\n/// Immutable view into a [`Tensor`] verified to have plain storage.\n///\n/// Construction is the single point of failure (`Tensor::as_plain()` returns\n/// `Option`). Once constructed, all data access is infallible with no\n/// `unwrap()`/`expect()` on the plain codepath.\npub struct PlainView<'a> {\n    tensor: &'a Tensor,\n    storage: &'a PlainStorage,\n}\n\nimpl<'a> PlainView<'a> {\n    /// Private constructor used by `Tensor::as_plain()`.\n    #[inline]\n    pub(crate) fn new(tensor: &'a Tensor, storage: &'a PlainStorage) -> Self {\n        PlainView { tensor, storage }\n    }\n\n    // -- Metadata (delegated to tensor) --\n\n    #[inline]\n    pub fn tensor(&self) -> &Tensor {\n        self.tensor\n    }\n\n    #[inline]\n    pub fn datum_type(&self) -> DatumType {\n        self.tensor.datum_type()\n    }\n\n    #[inline]\n    pub fn shape(&self) -> &[usize] {\n        self.tensor.shape()\n    }\n\n    #[inline]\n    pub fn strides(&self) -> &[isize] {\n        self.tensor.strides()\n    }\n\n    #[inline]\n    pub fn rank(&self) -> usize {\n        self.tensor.rank()\n    }\n\n    #[inline]\n    pub fn len(&self) -> usize {\n        self.tensor.len()\n    }\n\n    // -- Plain-specific (direct storage access, no dispatch) --\n\n    #[inline]\n    pub fn as_bytes(&self) -> &'a [u8] {\n        self.storage.as_bytes()\n    }\n\n    #[inline]\n    pub fn layout(&self) -> &Layout {\n        self.storage.layout()\n    }\n\n    // -- Typed access --\n    // TractResult is for datum-type check only, NOT plain check.\n\n    #[inline]\n    pub fn as_ptr<D: Datum>(&self) -> TractResult<*const D> {\n        check_for_access::<D>(self.datum_type())?;\n        unsafe { Ok(self.as_ptr_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn as_ptr_unchecked<D: Datum>(&self) -> *const D {\n        self.storage.as_ptr() as *const D\n    }\n\n    #[inline]\n    pub fn as_slice<D: Datum>(&self) -> TractResult<&'a [D]> {\n        check_for_access::<D>(self.datum_type())?;\n        unsafe { Ok(self.as_slice_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn as_slice_unchecked<D: Datum>(&self) -> &'a [D] {\n        if self.storage.is_empty() {\n            &[]\n        } else {\n            unsafe { std::slice::from_raw_parts(self.as_ptr_unchecked(), self.len()) }\n        }\n    }\n\n    #[inline]\n    pub fn to_scalar<D: Datum>(&self) -> TractResult<&'a D> {\n        check_for_access::<D>(self.datum_type())?;\n        unsafe { Ok(self.to_scalar_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn to_scalar_unchecked<D: Datum>(&self) -> &'a D {\n        unsafe { &*(self.storage.as_ptr() as *const D) }\n    }\n\n    #[inline]\n    pub fn to_array_view<D: Datum>(&self) -> TractResult<ArrayViewD<'a, D>> {\n        check_for_access::<D>(self.datum_type())?;\n        unsafe { Ok(self.to_array_view_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn to_array_view_unchecked<D: Datum>(&self) -> ArrayViewD<'a, D> {\n        if self.len() != 0 {\n            unsafe { ArrayViewD::from_shape_ptr(self.shape(), self.storage.as_ptr() as *const D) }\n        } else {\n            ArrayViewD::from_shape(self.shape(), &[]).unwrap()\n        }\n    }\n}\n\n/// Mutable view into a [`Tensor`] verified to have plain storage.\n///\n/// Fields are split to satisfy the borrow checker: mutable storage +\n/// immutable metadata borrowed from the same Tensor.\npub struct PlainViewMut<'a> {\n    dt: DatumType,\n    shape: &'a [usize],\n    strides: &'a [isize],\n    len: usize,\n    storage: &'a mut PlainStorage,\n}\n\nimpl<'a> PlainViewMut<'a> {\n    /// Private constructor used by `Tensor::as_plain_mut()`.\n    #[inline]\n    pub(crate) fn new(\n        dt: DatumType,\n        shape: &'a [usize],\n        strides: &'a [isize],\n        len: usize,\n        storage: &'a mut PlainStorage,\n    ) -> Self {\n        PlainViewMut { dt, shape, strides, len, storage }\n    }\n\n    // -- Metadata --\n\n    #[inline]\n    pub fn datum_type(&self) -> DatumType {\n        self.dt\n    }\n\n    #[inline]\n    pub fn shape(&self) -> &[usize] {\n        self.shape\n    }\n\n    #[inline]\n    pub fn strides(&self) -> &[isize] {\n        self.strides\n    }\n\n    #[inline]\n    pub fn rank(&self) -> usize {\n        self.shape.len()\n    }\n\n    #[inline]\n    pub fn len(&self) -> usize {\n        self.len\n    }\n\n    // -- Read access (same as PlainView, self.storage reborrows as &PlainStorage) --\n\n    #[inline]\n    pub fn as_bytes(&self) -> &[u8] {\n        self.storage.as_bytes()\n    }\n\n    #[inline]\n    pub fn layout(&self) -> &Layout {\n        self.storage.layout()\n    }\n\n    #[inline]\n    pub fn as_ptr<D: Datum>(&self) -> TractResult<*const D> {\n        check_for_access::<D>(self.dt)?;\n        unsafe { Ok(self.as_ptr_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn as_ptr_unchecked<D: Datum>(&self) -> *const D {\n        self.storage.as_ptr() as *const D\n    }\n\n    #[inline]\n    pub fn as_slice<D: Datum>(&self) -> TractResult<&[D]> {\n        check_for_access::<D>(self.dt)?;\n        unsafe { Ok(self.as_slice_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn as_slice_unchecked<D: Datum>(&self) -> &[D] {\n        if self.storage.is_empty() {\n            &[]\n        } else {\n            unsafe { std::slice::from_raw_parts(self.as_ptr_unchecked(), self.len) }\n        }\n    }\n\n    #[inline]\n    pub fn to_scalar<D: Datum>(&self) -> TractResult<&D> {\n        check_for_access::<D>(self.dt)?;\n        unsafe { Ok(self.to_scalar_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn to_scalar_unchecked<D: Datum>(&self) -> &D {\n        unsafe { &*(self.storage.as_ptr() as *const D) }\n    }\n\n    #[inline]\n    pub fn to_array_view<D: Datum>(&self) -> TractResult<ArrayViewD<'_, D>> {\n        check_for_access::<D>(self.dt)?;\n        unsafe { Ok(self.to_array_view_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn to_array_view_unchecked<D: Datum>(&self) -> ArrayViewD<'_, D> {\n        if self.len != 0 {\n            unsafe { ArrayViewD::from_shape_ptr(self.shape, self.storage.as_ptr() as *const D) }\n        } else {\n            ArrayViewD::from_shape(self.shape, &[]).unwrap()\n        }\n    }\n\n    // -- Mutable access --\n\n    #[inline]\n    pub fn as_bytes_mut(&mut self) -> &mut [u8] {\n        self.storage.as_bytes_mut()\n    }\n\n    #[inline]\n    pub fn as_ptr_mut<D: Datum>(&mut self) -> TractResult<*mut D> {\n        check_for_access::<D>(self.dt)?;\n        unsafe { Ok(self.as_ptr_mut_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn as_ptr_mut_unchecked<D: Datum>(&mut self) -> *mut D {\n        self.storage.as_mut_ptr() as *mut D\n    }\n\n    #[inline]\n    pub fn as_slice_mut<D: Datum>(&mut self) -> TractResult<&mut [D]> {\n        check_for_access::<D>(self.dt)?;\n        unsafe { Ok(self.as_slice_mut_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn as_slice_mut_unchecked<D: Datum>(&mut self) -> &mut [D] {\n        if self.storage.is_empty() {\n            &mut []\n        } else {\n            let len = self.len;\n            unsafe { std::slice::from_raw_parts_mut(self.as_ptr_mut_unchecked(), len) }\n        }\n    }\n\n    #[inline]\n    pub fn to_scalar_mut<D: Datum>(&mut self) -> TractResult<&mut D> {\n        check_for_access::<D>(self.dt)?;\n        unsafe { Ok(self.to_scalar_mut_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn to_scalar_mut_unchecked<D: Datum>(&mut self) -> &mut D {\n        unsafe { &mut *(self.storage.as_mut_ptr() as *mut D) }\n    }\n\n    #[inline]\n    pub fn to_array_view_mut<D: Datum>(&mut self) -> TractResult<ArrayViewMutD<'_, D>> {\n        check_for_access::<D>(self.dt)?;\n        unsafe { Ok(self.to_array_view_mut_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn to_array_view_mut_unchecked<D: Datum>(&mut self) -> ArrayViewMutD<'_, D> {\n        if self.len != 0 {\n            unsafe {\n                ArrayViewMutD::from_shape_ptr(self.shape, self.storage.as_mut_ptr() as *mut D)\n            }\n        } else {\n            ArrayViewMutD::from_shape(self.shape, &mut []).unwrap()\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/tensor/storage.rs",
    "content": "use std::alloc::Layout;\nuse std::fmt;\nuse std::hash::Hash;\n\nuse crate::TractResult;\nuse crate::blob::Blob;\nuse crate::exotic::ExoticFact;\nuse downcast_rs::{Downcast, impl_downcast};\nuse dyn_eq::DynEq;\n\n/// Trait abstracting over tensor storage backends.\n///\n/// `PlainStorage` is the primary implementation backed by a contiguous `Blob`.\n/// Non-plain backends are held behind `StorageKind::Exotic(Box<dyn TensorStorage>)`.\npub trait TensorStorage:\n    Send + Sync + fmt::Debug + fmt::Display + dyn_eq::DynEq + Downcast\n{\n    fn byte_len(&self) -> usize;\n    fn is_empty(&self) -> bool;\n    fn deep_clone(&self) -> Box<dyn TensorStorage>;\n    fn as_plain(&self) -> Option<&PlainStorage>;\n    fn as_plain_mut(&mut self) -> Option<&mut PlainStorage>;\n    fn into_plain(self: Box<Self>) -> Option<PlainStorage>;\n    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher);\n    /// Build the `ExoticFact` that describes this storage for use in `TypedFact`.\n    ///\n    /// Plain storage returns `None`. Exotic storages should return the\n    /// appropriate fact so that `From<Arc<Tensor>> for TypedFact` preserves\n    /// exotic-ness.\n    fn exotic_fact(&self, shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>>;\n}\nimpl_downcast!(TensorStorage);\ndyn_eq::eq_trait_object!(TensorStorage);\n\n/// Plain, contiguous storage backed by a `Blob`.\n#[derive(Eq)]\npub struct PlainStorage(pub(crate) Blob);\n\nimpl PlainStorage {\n    #[inline]\n    pub fn layout(&self) -> &Layout {\n        self.0.layout()\n    }\n\n    #[inline]\n    pub fn as_bytes(&self) -> &[u8] {\n        self.0.as_bytes()\n    }\n\n    #[inline]\n    pub fn as_bytes_mut(&mut self) -> &mut [u8] {\n        self.0.as_bytes_mut()\n    }\n\n    #[inline]\n    pub fn as_ptr(&self) -> *const u8 {\n        self.0.as_bytes().as_ptr()\n    }\n\n    #[inline]\n    pub fn as_mut_ptr(&mut self) -> *mut u8 {\n        self.0.as_bytes_mut().as_mut_ptr()\n    }\n\n    #[inline]\n    pub fn into_blob(self) -> Blob {\n        self.0\n    }\n}\n\nimpl Default for PlainStorage {\n    #[inline]\n    fn default() -> Self {\n        PlainStorage(Blob::default())\n    }\n}\n\nimpl Clone for PlainStorage {\n    #[inline]\n    fn clone(&self) -> Self {\n        PlainStorage(self.0.clone())\n    }\n}\n\nimpl Hash for PlainStorage {\n    #[inline]\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.0.hash(state);\n    }\n}\n\nimpl PartialEq for PlainStorage {\n    #[inline]\n    fn eq(&self, other: &Self) -> bool {\n        self.0 == other.0\n    }\n}\n\nimpl From<Blob> for PlainStorage {\n    #[inline]\n    fn from(blob: Blob) -> Self {\n        PlainStorage(blob)\n    }\n}\n\nimpl std::ops::Deref for PlainStorage {\n    type Target = [u8];\n    #[inline]\n    fn deref(&self) -> &[u8] {\n        self.0.as_bytes()\n    }\n}\n\nimpl std::ops::DerefMut for PlainStorage {\n    #[inline]\n    fn deref_mut(&mut self) -> &mut [u8] {\n        self.0.as_bytes_mut()\n    }\n}\n\nimpl fmt::Debug for PlainStorage {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        fmt::Debug::fmt(&self.0, f)\n    }\n}\n\nimpl fmt::Display for PlainStorage {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        fmt::Display::fmt(&self.0, f)\n    }\n}\n\nimpl TensorStorage for PlainStorage {\n    #[inline]\n    fn is_empty(&self) -> bool {\n        self.0.is_empty()\n    }\n\n    #[inline]\n    fn byte_len(&self) -> usize {\n        self.0.len()\n    }\n\n    fn deep_clone(&self) -> Box<dyn TensorStorage> {\n        Box::new(PlainStorage(self.0.clone()))\n    }\n\n    fn as_plain(&self) -> Option<&PlainStorage> {\n        Some(self)\n    }\n\n    fn as_plain_mut(&mut self) -> Option<&mut PlainStorage> {\n        Some(self)\n    }\n\n    fn into_plain(self: Box<Self>) -> Option<PlainStorage> {\n        Some(*self)\n    }\n\n    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {\n        state.write_u8(0);\n        state.write(self.0.as_bytes());\n    }\n\n    fn exotic_fact(&self, _shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {\n        Ok(None)\n    }\n}\n\n/// Inline enum replacing `Box<dyn TensorStorage>`.\n///\n/// The common `Plain` case stays inline (no heap alloc, no vtable indirection).\n/// `Exotic` covers non-plain backends behind a single Box indirection.\n#[derive(Debug, PartialEq, Eq)]\n#[allow(dead_code)]\npub(crate) enum StorageKind {\n    Plain(PlainStorage),\n    Exotic(Box<dyn TensorStorage>),\n}\n\nimpl StorageKind {\n    #[inline]\n    pub fn as_plain(&self) -> Option<&PlainStorage> {\n        match self {\n            StorageKind::Plain(d) => Some(d),\n            StorageKind::Exotic(o) => o.as_plain(),\n        }\n    }\n\n    #[inline]\n    pub fn as_plain_mut(&mut self) -> Option<&mut PlainStorage> {\n        match self {\n            StorageKind::Plain(d) => Some(d),\n            StorageKind::Exotic(o) => o.as_plain_mut(),\n        }\n    }\n\n    #[inline]\n    pub fn into_plain(self) -> Option<PlainStorage> {\n        match self {\n            StorageKind::Plain(d) => Some(d),\n            StorageKind::Exotic(o) => o.into_plain(),\n        }\n    }\n\n    #[inline]\n    pub fn byte_len(&self) -> usize {\n        match self {\n            StorageKind::Plain(d) => d.0.len(),\n            StorageKind::Exotic(o) => o.byte_len(),\n        }\n    }\n\n    #[inline]\n    pub fn is_empty(&self) -> bool {\n        match self {\n            StorageKind::Plain(d) => d.0.is_empty(),\n            StorageKind::Exotic(o) => o.is_empty(),\n        }\n    }\n\n    #[inline]\n    #[allow(dead_code)]\n    pub fn deep_clone(&self) -> StorageKind {\n        match self {\n            StorageKind::Plain(d) => StorageKind::Plain(d.clone()),\n            StorageKind::Exotic(o) => StorageKind::Exotic(o.deep_clone()),\n        }\n    }\n\n    #[inline]\n    pub fn as_storage(&self) -> &dyn TensorStorage {\n        match self {\n            StorageKind::Plain(d) => d,\n            StorageKind::Exotic(o) => o.as_ref(),\n        }\n    }\n\n    #[inline]\n    #[allow(dead_code)]\n    pub fn as_storage_mut(&mut self) -> &mut dyn TensorStorage {\n        match self {\n            StorageKind::Plain(d) => d,\n            StorageKind::Exotic(o) => o.as_mut(),\n        }\n    }\n\n    pub fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {\n        match self {\n            StorageKind::Plain(d) => {\n                state.write_u8(0);\n                state.write(d.as_bytes())\n            }\n            StorageKind::Exotic(o) => o.dyn_hash(state),\n        }\n    }\n}\n"
  },
  {
    "path": "data/src/tensor/view.rs",
    "content": "use super::*;\nuse crate::internal::*;\n\n#[derive(Clone, Debug)]\nenum Indexing<'a> {\n    Prefix(usize),\n    Custom { shape: &'a [usize], strides: &'a [isize] },\n}\n\n#[derive(Clone, Debug)]\npub struct TensorView<'a> {\n    pub tensor: &'a Tensor,\n    offset_bytes: isize,\n    indexing: Indexing<'a>,\n}\n\nimpl<'a> TensorView<'a> {\n    pub unsafe fn from_bytes(\n        tensor: &'a Tensor,\n        offset_bytes: isize,\n        shape: &'a [usize],\n        strides: &'a [isize],\n    ) -> TensorView<'a> {\n        TensorView { tensor, offset_bytes, indexing: Indexing::Custom { shape, strides } }\n    }\n\n    pub fn offsetting(tensor: &'a Tensor, coords: &[usize]) -> TractResult<TensorView<'a>> {\n        ensure!(\n            coords.len() == tensor.rank() && coords.iter().zip(tensor.shape()).all(|(p, d)| p < d),\n            \"Invalid coords {:?} for shape {:?}\",\n            coords,\n            tensor.shape()\n        );\n        unsafe { Ok(Self::offsetting_unchecked(tensor, coords)) }\n    }\n\n    pub unsafe fn offsetting_unchecked(tensor: &'a Tensor, coords: &[usize]) -> TensorView<'a> {\n        let offset_bytes =\n            coords.iter().zip(tensor.strides()).map(|(a, b)| *a as isize * b).sum::<isize>()\n                * tensor.datum_type().size_of() as isize;\n        TensorView {\n            tensor,\n            offset_bytes,\n            indexing: Indexing::Custom { shape: &tensor.shape, strides: &tensor.strides },\n        }\n    }\n\n    pub fn at_prefix(tensor: &'a Tensor, prefix: &[usize]) -> TractResult<TensorView<'a>> {\n        ensure!(\n            prefix.len() <= tensor.rank() && prefix.iter().zip(tensor.shape()).all(|(p, d)| p < d),\n            \"Invalid prefix {:?} for shape {:?}\",\n            prefix,\n            tensor.shape()\n        );\n        unsafe { Ok(Self::at_prefix_unchecked(tensor, prefix)) }\n    }\n\n    pub unsafe fn at_prefix_unchecked(tensor: &'a Tensor, prefix: &[usize]) -> TensorView<'a> {\n        let offset_bytes =\n            prefix.iter().zip(tensor.strides()).map(|(a, b)| *a as isize * b).sum::<isize>()\n                * tensor.datum_type().size_of() as isize;\n        TensorView { tensor, offset_bytes, indexing: Indexing::Prefix(prefix.len()) }\n    }\n\n    #[inline]\n    pub unsafe fn view(tensor: &'a Tensor) -> TensorView<'a> {\n        TensorView { tensor, offset_bytes: 0, indexing: Indexing::Prefix(0) }\n    }\n\n    #[inline]\n    pub fn datum_type(&self) -> DatumType {\n        self.tensor.datum_type()\n    }\n\n    #[inline]\n    pub fn shape(&self) -> &[usize] {\n        match &self.indexing {\n            Indexing::Prefix(i) => &self.tensor.shape()[*i..],\n            Indexing::Custom { shape, .. } => shape,\n        }\n    }\n\n    #[inline]\n    pub fn strides(&self) -> &[isize] {\n        match &self.indexing {\n            Indexing::Prefix(i) => &self.tensor.strides()[*i..],\n            Indexing::Custom { strides, .. } => strides,\n        }\n    }\n\n    #[inline]\n    #[allow(clippy::len_without_is_empty)]\n    pub fn len(&self) -> usize {\n        match &self.indexing {\n            Indexing::Prefix(i) => {\n                if *i == 0 {\n                    self.tensor.len()\n                } else {\n                    self.tensor.strides[*i - 1] as usize\n                }\n            }\n            Indexing::Custom { shape, .. } => shape.iter().product(),\n        }\n    }\n\n    #[inline]\n    #[allow(clippy::len_without_is_empty)]\n    pub fn valid_bytes(&self) -> usize {\n        self.tensor.plain_storage().layout().size() - self.offset_bytes as usize\n    }\n\n    #[inline]\n    pub fn rank(&self) -> usize {\n        match &self.indexing {\n            Indexing::Prefix(i) => self.tensor.rank() - i,\n            Indexing::Custom { shape, .. } => shape.len(),\n        }\n    }\n\n    fn check_dt<D: Datum>(&self) -> TractResult<()> {\n        self.tensor.check_for_access::<D>()\n    }\n\n    fn check_coords(&self, coords: &[usize]) -> TractResult<()> {\n        ensure!(\n            coords.len() == self.rank()\n                && coords.iter().zip(self.shape()).all(|(&x, &dim)| x < dim),\n            \"Can't access coordinates {:?} of TensorView of shape {:?}\",\n            coords,\n            self.shape(),\n        );\n        Ok(())\n    }\n\n    /// Access the data as a pointer.\n    #[inline]\n    pub fn as_ptr<D: Datum>(&self) -> TractResult<*const D> {\n        self.check_dt::<D>()?;\n        Ok(unsafe { self.as_ptr_unchecked() })\n    }\n\n    /// Access the data as a pointer.\n    #[inline]\n    pub unsafe fn as_ptr_unchecked<D: Datum>(&self) -> *const D {\n        unsafe { self.tensor.as_ptr_unchecked::<u8>().offset(self.offset_bytes) as *const D }\n    }\n\n    /// Access the data as a pointer.\n    #[inline]\n    pub unsafe fn as_ptr_mut_unchecked<D: Datum>(&mut self) -> *mut D {\n        unsafe { self.as_ptr_unchecked::<D>() as *mut D }\n    }\n\n    /// Access the data as a mutable pointer.\n    #[inline]\n    pub fn as_ptr_mut<D: Datum>(&mut self) -> TractResult<*mut D> {\n        Ok(self.as_ptr::<D>()? as *mut D)\n    }\n\n    /// Access the data as a slice.\n    #[inline]\n    pub unsafe fn as_slice_unchecked<D: Datum>(&self) -> &'a [D] {\n        unsafe { std::slice::from_raw_parts::<D>(self.as_ptr_unchecked(), self.len()) }\n    }\n\n    /// Access the data as a slice.\n    #[inline]\n    pub fn as_slice<D: Datum>(&self) -> TractResult<&'a [D]> {\n        self.check_dt::<D>()?;\n        unsafe { Ok(self.as_slice_unchecked()) }\n    }\n\n    /// Access the data as a mutable slice.\n    #[inline]\n    pub unsafe fn as_slice_mut_unchecked<D: Datum>(&mut self) -> &mut [D] {\n        unsafe { std::slice::from_raw_parts_mut::<D>(self.as_ptr_mut_unchecked(), self.len()) }\n    }\n\n    /// Access the data as a mutable slice.\n    #[inline]\n    pub fn as_slice_mut<D: Datum>(&mut self) -> TractResult<&mut [D]> {\n        self.check_dt::<D>()?;\n        unsafe { Ok(self.as_slice_mut_unchecked()) }\n    }\n\n    #[inline]\n    pub unsafe fn offset_bytes(&mut self, offset: isize) {\n        self.offset_bytes += offset\n    }\n\n    #[inline]\n    pub unsafe fn offset_axis_unchecked(&mut self, axis: usize, pos: isize) {\n        let stride = self.strides()[axis] * self.datum_type().size_of() as isize;\n        unsafe { self.offset_bytes(stride * pos) }\n    }\n\n    #[inline]\n    pub unsafe fn offset_axis(&mut self, axis: usize, pos: isize) {\n        let stride = self.strides()[axis] * self.datum_type().size_of() as isize;\n        unsafe { self.offset_bytes(stride * pos) }\n    }\n\n    #[inline]\n    fn offset_for_coords(&self, coords: &[usize]) -> isize {\n        self.strides().iter().zip(coords.as_ref()).map(|(s, c)| *s * *c as isize).sum::<isize>()\n    }\n\n    #[inline]\n    pub unsafe fn at_unchecked<T: Datum>(&self, coords: impl AsRef<[usize]>) -> &T {\n        unsafe {\n            self.as_ptr_unchecked::<T>()\n                .offset(self.offset_for_coords(coords.as_ref()))\n                .as_ref()\n                .unwrap()\n        }\n    }\n\n    #[inline]\n    pub unsafe fn at_mut_unchecked<T: Datum>(&mut self, coords: impl AsRef<[usize]>) -> &mut T {\n        unsafe {\n            self.as_ptr_mut_unchecked::<T>()\n                .offset(self.offset_for_coords(coords.as_ref()))\n                .as_mut()\n                .unwrap()\n        }\n    }\n\n    #[inline]\n    pub fn at<T: Datum>(&self, coords: impl AsRef<[usize]>) -> TractResult<&T> {\n        self.check_dt::<T>()?;\n        let coords = coords.as_ref();\n        self.check_coords(coords)?;\n        unsafe { Ok(self.at_unchecked(coords)) }\n    }\n\n    #[inline]\n    pub fn at_mut<T: Datum>(&mut self, coords: impl AsRef<[usize]>) -> TractResult<&mut T> {\n        self.check_dt::<T>()?;\n        let coords = coords.as_ref();\n        self.check_coords(coords)?;\n        unsafe { Ok(self.at_mut_unchecked(coords)) }\n    }\n\n    /*\n      pub unsafe fn reshaped(&self, shape: impl AsRef<[usize]>) -> TensorView<'a> {\n      let shape = shape.as_ref();\n      let mut strides: TVec<isize> = shape\n      .iter()\n      .rev()\n      .scan(1, |state, d| {\n      let old = *state;\n    *state = *state * d;\n    Some(old as isize)\n    })\n    .collect();\n    strides.reverse();\n    TensorView { shape: shape.into(), strides, ..*self }\n    }\n    */\n}\n\n#[cfg(test)]\nmod test {\n    use super::TensorView;\n    use crate::prelude::Tensor;\n\n    #[test]\n    fn test_at_prefix() {\n        let a = Tensor::from_shape(&[2, 2], &[1, 2, 3, 4]).unwrap();\n        let a_view = TensorView::at_prefix(&a, &[1]).unwrap();\n        assert_eq!(a_view.shape(), &[2]);\n        assert_eq!(a_view.as_slice::<i32>().unwrap(), &[3, 4]);\n    }\n}\n"
  },
  {
    "path": "data/src/tensor.rs",
    "content": "//! `Tensor`, tract main data object of interest.\nuse crate::TVec;\nuse crate::blob::Blob;\nuse crate::datum::{ClampCast, Datum, DatumType, QParams, round_ties_to_even, scale_by};\nuse crate::dim::TDim;\nuse crate::internal::*;\nuse half::f16;\nuse itertools::{Itertools, izip};\nuse ndarray::prelude::*;\n#[cfg(feature = \"complex\")]\nuse num_complex::Complex;\nuse num_traits::{Float, Zero};\nuse std::borrow::Cow;\nuse std::fmt;\nuse std::hash::Hash;\nuse std::ops::Range;\nuse std::sync::Arc;\n\npub mod litteral;\npub mod plain_view;\npub mod storage;\npub mod view;\n\npub use plain_view::{PlainView, PlainViewMut};\nuse storage::{PlainStorage, StorageKind, TensorStorage};\n\n#[derive(Copy, Clone, Default, Debug)]\npub enum Approximation {\n    Exact,\n    #[default]\n    Close,\n    Approximate,\n    VeryApproximate,\n    SuperApproximate,\n    UltraApproximate,\n    Custom(f32, f32, f32),\n}\n\nimpl PartialEq for Approximation {\n    fn eq(&self, other: &Self) -> bool {\n        use Approximation::Custom;\n        if let (Custom(aa, ar, ao), Custom(ba, br, bo)) = (self, other) {\n            aa == ba && ar == br && bo == ao\n        } else {\n            std::mem::discriminant(self) == std::mem::discriminant(other)\n        }\n    }\n}\n\nimpl Eq for Approximation {}\n\nimpl From<bool> for Approximation {\n    fn from(b: bool) -> Self {\n        if b { Self::Approximate } else { Self::Exact }\n    }\n}\n\nimpl Approximation {\n    fn atol_rtol_outliers(&self, dt: &DatumType) -> (f64, f64, f64) {\n        use Approximation::*;\n        match (self, dt) {\n            (Exact, _) => (0.0, 0.0, 0.0),\n            (Close, DatumType::F16) => (1e-3, 1e-3, 0.0),\n            (Approximate, DatumType::F16) => (1e-3, 5e-3, 0.0),\n            (Approximate, qp) if qp.is_quantized() => (qp.zp_scale().1 as f64, 0., 0.0),\n            (Close, _) => (1e-7, 1e-7, 0.0),\n            (Approximate, _) => (1e-4, 5e-4, 0.0),\n            (VeryApproximate, _) => (5e-2, 1e-2, 0.0),\n            (SuperApproximate, _) => (0.1, 0.05, 0.0001),\n            (UltraApproximate, _) => (0.2, 0.1, 0.0005),\n            (Custom(atol, rtol, out), _) => (*atol as _, *rtol as _, *out as _),\n        }\n    }\n}\n\n/// Tensor is a concrete tensor in tract.\npub struct Tensor {\n    dt: DatumType,\n    shape: TVec<usize>,\n    strides: TVec<isize>,\n    len: usize,\n    storage: StorageKind,\n}\n\nunsafe impl Send for Tensor {}\nunsafe impl Sync for Tensor {}\n\nimpl Hash for Tensor {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        use DatumType::*;\n        self.dt.hash(state);\n        self.shape.hash(state);\n        if let Some(plain) = self.storage.as_plain() {\n            plain.layout().align().hash(state);\n            unsafe {\n                match self.dt {\n                    Bool => self.as_slice_unchecked::<bool>().hash(state),\n                    I8 => self.as_slice_unchecked::<i8>().hash(state),\n                    I16 => self.as_slice_unchecked::<i16>().hash(state),\n                    I32 => self.as_slice_unchecked::<i32>().hash(state),\n                    I64 => self.as_slice_unchecked::<i64>().hash(state),\n                    U8 => self.as_slice_unchecked::<u8>().hash(state),\n                    U16 => self.as_slice_unchecked::<u16>().hash(state),\n                    U32 => self.as_slice_unchecked::<u32>().hash(state),\n                    U64 => self.as_slice_unchecked::<u64>().hash(state),\n                    F16 => self.as_slice_unchecked::<i16>().hash(state),\n                    F32 => self.as_slice_unchecked::<i32>().hash(state),\n                    F64 => self.as_slice_unchecked::<i64>().hash(state),\n                    TDim => self.as_slice_unchecked::<crate::dim::TDim>().hash(state),\n                    String => self.as_slice_unchecked::<std::string::String>().hash(state),\n                    Blob => self.as_slice_unchecked::<crate::blob::Blob>().hash(state),\n                    QI8(_) => self.as_slice_unchecked::<i8>().hash(state),\n                    QU8(_) => self.as_slice_unchecked::<u8>().hash(state),\n                    QI32(_) => self.as_slice_unchecked::<i32>().hash(state),\n                    #[cfg(feature = \"complex\")]\n                    ComplexI16 => self.as_slice_unchecked::<Complex<i16>>().hash(state),\n                    #[cfg(feature = \"complex\")]\n                    ComplexI32 => self.as_slice_unchecked::<Complex<i32>>().hash(state),\n                    #[cfg(feature = \"complex\")]\n                    ComplexI64 => self.as_slice_unchecked::<Complex<i64>>().hash(state),\n                    #[cfg(feature = \"complex\")]\n                    ComplexF16 => self.as_slice_unchecked::<Complex<i16>>().hash(state),\n                    #[cfg(feature = \"complex\")]\n                    ComplexF32 => self.as_slice_unchecked::<Complex<i32>>().hash(state),\n                    #[cfg(feature = \"complex\")]\n                    ComplexF64 => self.as_slice_unchecked::<Complex<i64>>().hash(state),\n                }\n            }\n        } else {\n            self.storage.dyn_hash(state);\n        }\n    }\n}\n\nimpl Clone for Tensor {\n    fn clone(&self) -> Tensor {\n        self.deep_clone()\n    }\n}\n\nimpl Default for Tensor {\n    fn default() -> Tensor {\n        litteral::tensor0(0f32)\n    }\n}\n\nimpl Drop for Tensor {\n    fn drop(&mut self) {\n        if self.is_plain() {\n            macro_rules! drop_in_place {\n                ($t: ty) => {\n                    if self.dt == <$t>::datum_type() {\n                        unsafe {\n                            let slice = self.as_slice_mut_unchecked::<$t>();\n                            std::ptr::drop_in_place(slice as *mut [$t]);\n                        }\n                    }\n                };\n            }\n            drop_in_place!(Blob);\n            drop_in_place!(String);\n            drop_in_place!(TDim);\n        }\n        // StorageKind::Exotic drops via Box<dyn TensorStorage> automatically\n    }\n}\n\n#[allow(unreachable_code)]\npub fn vector_size() -> usize {\n    #[cfg(target_arch = \"x86_64\")]\n    {\n        return if is_x86_feature_detected!(\"avx512f\") { 512 / 8 } else { 256 / 8 };\n    }\n    128 / 8\n}\n\nimpl Tensor {\n    #[inline]\n    fn plain_storage(&self) -> &PlainStorage {\n        self.storage.as_plain().expect(\"Non-plain storage\")\n    }\n\n    #[inline]\n    fn plain_storage_mut(&mut self) -> &mut PlainStorage {\n        self.storage.as_plain_mut().expect(\"Non-plain storage\")\n    }\n\n    pub fn storage_as<T: TensorStorage>(&self) -> Option<&T> {\n        self.storage.as_storage().downcast_ref::<T>()\n    }\n\n    pub fn try_storage_as<T: TensorStorage>(&self) -> TractResult<&T> {\n        self.storage_as::<T>().context(\"Unexpected tensor storage type\")\n    }\n\n    pub fn from_storage(\n        dt: DatumType,\n        shape: &[usize],\n        storage: impl TensorStorage + 'static,\n    ) -> Tensor {\n        let len = shape.iter().product::<usize>();\n        let strides = Self::natural_strides(shape);\n        Tensor {\n            dt,\n            shape: shape.into(),\n            strides,\n            len,\n            storage: StorageKind::Exotic(Box::new(storage)),\n        }\n    }\n\n    /// Returns an immutable [`PlainView`] if this tensor has plain storage.\n    #[inline]\n    pub fn as_plain(&self) -> Option<PlainView<'_>> {\n        let storage = self.storage.as_plain()?;\n        Some(PlainView::new(self, storage))\n    }\n\n    /// Returns an immutable [`PlainView`], or an error if storage is not plain.\n    #[inline]\n    pub fn try_as_plain(&self) -> TractResult<PlainView<'_>> {\n        self.as_plain().context(\"Tensor storage is not plain\")\n    }\n\n    /// Returns `true` if this tensor uses plain (contiguous) storage.\n    #[inline]\n    pub fn is_plain(&self) -> bool {\n        self.storage.as_plain().is_some()\n    }\n\n    /// Returns `true` if this tensor uses exotic (non-plain) storage.\n    #[inline]\n    pub fn is_exotic(&self) -> bool {\n        !self.is_plain()\n    }\n\n    /// Build the `ExoticFact` matching this tensor's storage, or `None` for plain tensors.\n    pub fn exotic_fact(&self) -> TractResult<Option<Box<dyn crate::exotic::ExoticFact>>> {\n        self.storage.as_storage().exotic_fact(&self.shape)\n    }\n\n    /// Returns a mutable [`PlainViewMut`] if this tensor has plain storage.\n    #[inline]\n    pub fn as_plain_mut(&mut self) -> Option<PlainViewMut<'_>> {\n        let storage = self.storage.as_plain_mut()?;\n        Some(PlainViewMut::new(self.dt, &self.shape, &self.strides, self.len, storage))\n    }\n\n    /// Returns a mutable [`PlainViewMut`], or an error if storage is not plain.\n    #[inline]\n    pub fn try_as_plain_mut(&mut self) -> TractResult<PlainViewMut<'_>> {\n        self.as_plain_mut().context(\"Tensor storage is not plain\")\n    }\n\n    /// Create an uninitialized tensor (dt as type paramater).\n    #[inline]\n    pub unsafe fn uninitialized<T: Datum>(shape: &[usize]) -> TractResult<Tensor> {\n        unsafe { Self::uninitialized_dt(T::datum_type(), shape) }\n    }\n\n    /// Create an uninitialized tensor (dt as regular parameter).\n    #[inline]\n    pub unsafe fn uninitialized_dt(dt: DatumType, shape: &[usize]) -> TractResult<Tensor> {\n        unsafe { Self::uninitialized_aligned_dt(dt, shape, vector_size()) }\n    }\n\n    /// Create an uninitialized tensor with a given alignment (in bytes).\n    #[inline]\n    pub unsafe fn uninitialized_aligned<T: Datum>(\n        shape: &[usize],\n        alignment: usize,\n    ) -> TractResult<Tensor> {\n        unsafe { Self::uninitialized_aligned_dt(T::datum_type(), shape, alignment) }\n    }\n\n    /// Create an uninitialized tensor with a given alignment (in bytes).\n    pub unsafe fn uninitialized_aligned_dt(\n        dt: DatumType,\n        shape: &[usize],\n        alignment: usize,\n    ) -> TractResult<Tensor> {\n        let bytes = shape.iter().cloned().product::<usize>() * dt.size_of();\n        let storage = StorageKind::Plain(PlainStorage::from(unsafe {\n            Blob::new_for_size_and_align(bytes, alignment)\n        }));\n        let mut tensor = Tensor { strides: tvec!(), dt, shape: shape.into(), storage, len: 0 };\n        if tensor.shape.len() == 0 {\n            tensor.len = 1;\n        } else {\n            tensor.update_strides_and_len();\n        }\n        if !tensor.storage.is_empty() {\n            if dt == String::datum_type() || dt == Blob::datum_type() {\n                // assumes zero-initialized string and blob are valid\n                tensor.plain_storage_mut().as_bytes_mut().fill(0);\n            } else if dt == TDim::datum_type() {\n                unsafe {\n                    tensor\n                        .as_slice_mut_unchecked::<TDim>()\n                        .iter_mut()\n                        .for_each(|dim| std::ptr::write(dim, TDim::zero()))\n                }\n            } else if cfg!(debug_assertions) {\n                assert!(dt.is_copy());\n                if dt == DatumType::F32 {\n                    tensor.fill_t(f32::NAN).unwrap();\n                } else {\n                    // safe, non copy types have been dealt with\n                    tensor.as_bytes_mut().iter_mut().for_each(|x| *x = (-1i8) as u8);\n                }\n            }\n        }\n        Ok(tensor)\n    }\n\n    pub fn stack_tensors(\n        axis: usize,\n        tensors: &[impl std::borrow::Borrow<Tensor>],\n    ) -> TractResult<Tensor> {\n        ensure!(tensors.len() > 0);\n        let rank = tensors[0].borrow().rank();\n        ensure!(axis < rank);\n        ensure!(tensors.iter().all(|t| t.borrow().rank() == rank));\n        let dt = tensors[0].borrow().datum_type();\n        ensure!(tensors.iter().all(|t| t.borrow().datum_type() == dt));\n        let mut shape: TVec<usize> = tensors[0].borrow().shape().into();\n        for ax in 0..rank {\n            if ax != axis {\n                ensure!(tensors.iter().all(|t| t.borrow().shape()[ax] == shape[ax]));\n            }\n        }\n        shape[axis] = tensors.iter().map(|v| v.borrow().shape()[axis]).sum();\n        unsafe {\n            let mut result = Tensor::uninitialized_dt(dt, &shape)?;\n            if dt.is_copy() && shape[..axis].iter().all(|d| *d == 1) {\n                let mut offset = 0isize;\n                for v in tensors {\n                    let v = v.borrow();\n                    let len = v.storage.byte_len();\n                    std::ptr::copy_nonoverlapping(\n                        v.plain_storage().as_ptr(),\n                        result.plain_storage_mut().as_mut_ptr().offset(offset),\n                        len,\n                    );\n                    offset += len as isize;\n                }\n            } else {\n                let mut offset = 0;\n                for t in tensors {\n                    let t = t.borrow();\n                    let len = t.shape()[axis];\n                    result.assign_slice_from_resolved(offset..offset + len, t, 0..len, axis);\n                    offset += len;\n                }\n            }\n\n            Ok(result)\n        }\n    }\n\n    pub fn clear<T: Datum + num_traits::Zero + Clone>(&mut self) -> TractResult<()> {\n        self.fill_t(T::zero())\n    }\n\n    pub fn zero<T: Datum + num_traits::Zero>(shape: &[usize]) -> TractResult<Tensor> {\n        unsafe {\n            let mut t = Tensor::uninitialized::<T>(shape)?;\n            t.clear::<T>()?;\n            Ok(t)\n        }\n    }\n\n    pub fn zero_scalar<T: Datum + num_traits::Zero>() -> TractResult<Tensor> {\n        Tensor::zero::<T>(&[])\n    }\n\n    pub fn zero_scalar_dt(dt: DatumType) -> TractResult<Tensor> {\n        Tensor::zero_dt(dt, &[])\n    }\n\n    pub fn zero_dt(dt: DatumType, shape: &[usize]) -> TractResult<Tensor> {\n        Tensor::zero_aligned_dt(dt, shape, vector_size())\n    }\n\n    pub fn fill_t<T: Datum + Clone>(&mut self, value: T) -> TractResult<()> {\n        self.try_as_plain_mut()?\n            .as_slice_mut::<T>()?\n            .iter_mut()\n            .for_each(|item| *item = value.clone());\n        Ok(())\n    }\n\n    pub fn zero_aligned_dt(\n        dt: DatumType,\n        shape: &[usize],\n        alignment: usize,\n    ) -> TractResult<Tensor> {\n        if shape.iter().product::<usize>() == 0 {\n            unsafe { return Tensor::uninitialized_dt(dt, shape) };\n        }\n        if dt.is_quantized() {\n            unsafe {\n                let mut t = Tensor::uninitialized_dt(dt, shape)?;\n                let zp = dt.zp_scale().0;\n                match dt.unquantized() {\n                    DatumType::I8 => t\n                        .try_as_plain_mut()?\n                        .as_slice_mut::<i8>()?\n                        .iter_mut()\n                        .for_each(|item| *item = zp as _),\n                    DatumType::U8 => t\n                        .try_as_plain_mut()?\n                        .as_slice_mut::<u8>()?\n                        .iter_mut()\n                        .for_each(|item| *item = zp as _),\n                    DatumType::I32 => t\n                        .try_as_plain_mut()?\n                        .as_slice_mut::<i32>()?\n                        .iter_mut()\n                        .for_each(|item| *item = zp as _),\n                    _ => unreachable!(),\n                }\n                Ok(t)\n            }\n        } else if dt == DatumType::Bool {\n            let mut t = unsafe { Tensor::uninitialized_dt(dt, shape)? };\n            t.fill_t::<bool>(false)?;\n            Ok(t)\n        } else {\n            dispatch_zerolike!(Self::zero_aligned(dt)(shape, alignment))\n        }\n    }\n\n    pub fn zero_aligned<T: Datum + num_traits::Zero>(\n        shape: &[usize],\n        alignment: usize,\n    ) -> TractResult<Tensor> {\n        unsafe {\n            let mut tensor = Self::uninitialized_aligned::<T>(shape, alignment)?;\n            tensor.clear::<T>()?;\n            Ok(tensor)\n        }\n    }\n\n    /// Create a tensor with a given shape and a slice of elements.\n    /// The data is copied and aligned to size of T.\n    pub fn from_shape<T: Datum + Copy>(shape: &[usize], data: &[T]) -> TractResult<Tensor> {\n        Self::from_shape_align(shape, data, vector_size())\n    }\n\n    /// Create a tensor with a given shape and a slice of elements.\n    /// The data is copied and aligned to given alignment.\n    pub fn from_shape_align<T: Datum + Copy>(\n        shape: &[usize],\n        data: &[T],\n        align: usize,\n    ) -> TractResult<Tensor> {\n        ensure!(\n            data.len() == shape.iter().product::<usize>(),\n            \"Shape product must be equal to data length\"\n        );\n        unsafe {\n            let bytes = std::slice::from_raw_parts(\n                data.as_ptr() as *const u8,\n                data.len() * T::datum_type().size_of(),\n            );\n            let dt = T::datum_type();\n            Self::from_raw_dt_align(dt, shape, bytes, align)\n        }\n    }\n\n    /// Create a tensor from raw data.\n    ///\n    /// It copies the data, aligning it to the size of T.\n    pub unsafe fn from_raw<T: Datum>(shape: &[usize], content: &[u8]) -> TractResult<Tensor> {\n        unsafe { Tensor::from_raw_dt(T::datum_type(), shape, content) }\n    }\n\n    pub unsafe fn from_raw_aligned<T: Datum>(\n        shape: &[usize],\n        content: &[u8],\n        align: usize,\n    ) -> TractResult<Tensor> {\n        unsafe { Tensor::from_raw_dt_align(T::datum_type(), shape, content, align) }\n    }\n\n    pub unsafe fn from_raw_dt(\n        dt: DatumType,\n        shape: &[usize],\n        content: &[u8],\n    ) -> TractResult<Tensor> {\n        unsafe { Self::from_raw_dt_align(dt, shape, content, vector_size()) }\n    }\n\n    pub unsafe fn from_raw_dt_align(\n        dt: DatumType,\n        shape: &[usize],\n        content: &[u8],\n        align: usize,\n    ) -> TractResult<Tensor> {\n        let mut tensor = unsafe { Tensor::uninitialized_aligned_dt(dt, shape, align) }?;\n        tensor.as_bytes_mut().copy_from_slice(content);\n        Ok(tensor)\n    }\n\n    pub unsafe fn from_slice_align<T: Datum>(content: &[T], align: usize) -> TractResult<Tensor> {\n        let bytes = if content.len() == 0 {\n            &[]\n        } else {\n            unsafe {\n                std::slice::from_raw_parts(\n                    content.as_ptr() as *const u8,\n                    content.len() * T::datum_type().size_of(),\n                )\n            }\n        };\n        unsafe { Self::from_raw_dt_align(T::datum_type(), &[content.len()], bytes, align) }\n    }\n\n    /// Get the number of dimensions (or axes) of the tensor.\n    #[inline]\n    pub fn rank(&self) -> usize {\n        self.shape.len()\n    }\n\n    /// Get the shape of the tensor.\n    #[inline]\n    pub fn shape(&self) -> &[usize] {\n        &self.shape\n    }\n\n    /// Get the number of values in the tensor.\n    #[inline]\n    #[allow(clippy::len_without_is_empty)]\n    pub fn len(&self) -> usize {\n        self.len\n    }\n\n    /// Get the number of valeus in the tensor.\n    #[inline]\n    #[allow(clippy::len_without_is_empty)]\n    pub fn volume(&self) -> usize {\n        self.len\n    }\n\n    /// Get the shape of the tensor.\n    #[inline]\n    pub fn strides(&self) -> &[isize] {\n        &self.strides\n    }\n\n    fn update_strides_and_len(&mut self) {\n        self.strides.clear();\n        if self.shape.len() == 0 {\n            self.len = 1;\n            return;\n        }\n        compute_natural_stride_to(&mut self.strides, &self.shape);\n        self.len = unsafe { *self.strides.get_unchecked(0) as usize * self.shape.get_unchecked(0) };\n    }\n\n    /// Force the tensor shape, no consistency check.\n    pub unsafe fn set_shape_unchecked(&mut self, shape: &[usize]) {\n        if shape != &*self.shape {\n            self.shape.clear();\n            self.shape.extend_from_slice(shape);\n            self.update_strides_and_len();\n        }\n    }\n\n    /// Force the tensor shape and strides, no consistency check.\n    pub unsafe fn set_geometry_unchecked(&mut self, shape: &[usize], strides: &[isize]) {\n        self.shape.clear();\n        self.shape.extend_from_slice(shape);\n        self.strides.clear();\n        self.strides.extend_from_slice(strides);\n    }\n\n    /// Force the tensor shape.\n    pub fn set_shape(&mut self, shape: &[usize]) -> TractResult<()> {\n        if self.len() != shape.iter().product::<usize>() {\n            bail!(\"Invalid reshape {:?} to {:?}\", self.shape, shape);\n        }\n        unsafe { self.set_shape_unchecked(shape) }\n        Ok(())\n    }\n\n    pub fn permute_axes(self, axes: &[usize]) -> TractResult<Tensor> {\n        ensure!(axes.iter().duplicates().next().is_none());\n        ensure!(axes.iter().all(|a| *a < self.rank()));\n        unsafe {\n            #[inline]\n            unsafe fn permute<T: Datum>(axes: &[usize], input: Tensor) -> Tensor {\n                unsafe { input.into_array_unchecked::<T>().permuted_axes(axes).into_tensor() }\n            }\n            let dt = self.datum_type();\n            let mut t = dispatch_datum_by_size!(permute(self.datum_type())(axes, self));\n            t.set_datum_type(dt);\n            Ok(t)\n        }\n    }\n\n    pub fn move_axis(self, from: usize, to: usize) -> TractResult<Tensor> {\n        let mut permutation: Vec<usize> = (0..self.rank()).collect();\n        permutation.remove(from);\n        permutation.insert(to, from);\n        self.permute_axes(&permutation)\n    }\n\n    pub fn collapse_axis_with_next(mut self, axis: usize) -> Tensor {\n        let removed = self.shape.remove(axis + 1);\n        self.shape[axis] *= removed;\n        self.update_strides_and_len();\n        self\n    }\n\n    pub fn split_axis(mut self, axis: usize, outer_dim: usize) -> TractResult<Tensor> {\n        if self.shape[axis] % outer_dim != 0 {\n            bail!(\n                \"Invalid axis split, shape is {:?}, axis split at {}, outer {}\",\n                self.shape,\n                axis,\n                outer_dim\n            );\n        }\n        self.shape.insert(axis + 1, self.shape[axis] / outer_dim);\n        self.shape[axis] = outer_dim;\n        self.update_strides_and_len();\n        Ok(self)\n    }\n\n    /// Reshape the tensor to `shape`.\n    pub fn into_shape(mut self, shape: &[usize]) -> TractResult<Tensor> {\n        self.set_shape(shape)?;\n        Ok(self)\n    }\n\n    pub fn insert_axis(&mut self, axis: usize) -> TractResult<()> {\n        self.shape.insert(axis, 1);\n        self.strides.insert(axis, self.strides.get(axis).copied().unwrap_or(1));\n        Ok(())\n    }\n\n    pub fn remove_axis(&mut self, axis: usize) -> TractResult<()> {\n        ensure!(self.shape[axis] == 1, \"Remove a non-1 axis: axis {} in {:?}\", axis, self);\n        self.shape.remove(axis);\n        self.strides.remove(axis);\n        Ok(())\n    }\n\n    pub fn broadcast_into_rank(mut self, rank: usize) -> TractResult<Tensor> {\n        self.broadcast_to_rank(rank)?;\n        self.update_strides_and_len();\n        Ok(self)\n    }\n\n    pub fn broadcast_to_rank(&mut self, rank: usize) -> TractResult<()> {\n        if rank < self.rank() {\n            bail!(\"Can only broadcast to higher rank\")\n        }\n        while self.shape.len() < rank {\n            self.shape.insert(0, 1)\n        }\n        self.update_strides_and_len();\n        Ok(())\n    }\n\n    pub fn broadcast_scalar_to_shape(&self, shape: &[usize]) -> TractResult<Tensor> {\n        if self.rank() > 0 {\n            bail!(\"broadcast_scalar_to_shape called on {:?}, which is not a salar\", self);\n        }\n        unsafe fn make<T: Datum>(src: &Tensor, dst: &mut Tensor) {\n            unsafe {\n                let value: &T = src.to_scalar_unchecked::<T>();\n                dst.as_slice_mut_unchecked::<T>().iter_mut().for_each(|item| *item = value.clone())\n            };\n        }\n        unsafe {\n            let mut t = Tensor::uninitialized_dt(self.datum_type(), shape)?;\n            dispatch_datum_by_size!(make(self.datum_type())(self, &mut t));\n            Ok(t)\n        }\n    }\n\n    fn broadcast_to_shape_t<T: Datum>(&self, shape: &[usize]) -> TractResult<Tensor> {\n        unsafe {\n            let view = self.to_array_view_unchecked::<T>();\n            let mut output = view\n                .broadcast(shape)\n                .with_context(|| format!(\"Broadcasting {view:?} to {shape:?}\"))?\n                .into_owned()\n                .into_tensor();\n            output.set_datum_type(self.datum_type());\n            Ok(output)\n        }\n    }\n\n    pub fn broadcast_to_shape(&self, shape: &[usize]) -> TractResult<Tensor> {\n        dispatch_datum!(Self::broadcast_to_shape_t(self.dt)(self, shape))\n    }\n\n    pub fn broadcast_vector_to_shape(&self, shape: &[usize], axis: usize) -> TractResult<Tensor> {\n        ensure!(self.rank() == 1);\n        ensure!(shape[axis] == self.len());\n        if !self.datum_type().is_copy() {\n            let mut vec_shape = vec![1; shape.len()];\n            vec_shape[axis] = self.len();\n            return self.clone().into_shape(&vec_shape)?.broadcast_to_shape(shape);\n        }\n        unsafe {\n            let mut output = Tensor::uninitialized_dt(self.datum_type(), shape)?;\n            if output.len() == 0 {\n                return Ok(output);\n            }\n            let inner_len = shape[axis + 1..].iter().product::<usize>();\n\n            unsafe fn splat<T>(input: &Tensor, output: &mut Tensor, inner_len: usize)\n            where\n                T: Datum + Copy,\n            {\n                unsafe {\n                    for ix in 0..input.len() {\n                        let value: T = input.as_slice_unchecked()[ix];\n                        output.as_slice_mut_unchecked::<T>()[ix * inner_len..(ix + 1) * inner_len]\n                            .iter_mut()\n                            .for_each(|item| *item = value);\n                    }\n                }\n            }\n            dispatch_copy_by_size!(splat(self.datum_type())(&self, &mut output, inner_len));\n\n            let outer_len = shape[0..axis].iter().product::<usize>();\n            let repeat_bytes_len = inner_len * self.as_bytes().len();\n            let bytes = output.as_bytes_mut();\n            for ix in 1..outer_len {\n                bytes.copy_within(0..repeat_bytes_len, ix * repeat_bytes_len);\n            }\n\n            Ok(output)\n        }\n    }\n\n    pub fn assign_slice(\n        &mut self,\n        range: impl std::ops::RangeBounds<usize>,\n        src: &Tensor,\n        src_range: impl std::ops::RangeBounds<usize>,\n        axis: usize,\n    ) -> TractResult<()> {\n        ensure!(self.rank() == src.rank());\n        ensure!(axis < self.rank());\n        let range = clip_range_bounds(self.shape[axis], range);\n        let src_range = clip_range_bounds(src.shape[axis], src_range);\n        ensure!(\n            src.datum_type() == self.datum_type(),\n            \"Attempt to assign into {:?} from {:?}, datum type mismatch\",\n            self.datum_type(),\n            src.datum_type()\n        );\n        ensure!(\n            src_range.len() == range.len(),\n            \"Attempt to assign a range of {:?} from a range of {:?}\",\n            range,\n            src_range,\n        );\n        ensure!(\n            itertools::izip!(0.., self.shape(), src.shape())\n                .all(|(ix, dst, src)| ix == axis || src == dst),\n            \"Attempt to assign a {}-axis range of {:?} from a range of {:?}\",\n            axis,\n            self,\n            src\n        );\n        ensure!(\n            src_range.end <= src.shape()[axis],\n            \"Assigning from invalid slice (axis {}, {:?}) of {:?}\",\n            axis,\n            src_range,\n            src\n        );\n        ensure!(\n            range.end <= self.shape()[axis],\n            \"Assigning to invalid slice (axis {}, {:?}) of {:?}\",\n            axis,\n            range,\n            self\n        );\n        unsafe { self.assign_slice_from_resolved(range, src, src_range, axis) };\n        Ok(())\n    }\n\n    pub unsafe fn assign_slice_unchecked(\n        &mut self,\n        range: impl std::ops::RangeBounds<usize>,\n        src: &Tensor,\n        src_range: impl std::ops::RangeBounds<usize>,\n        axis: usize,\n    ) {\n        let range = clip_range_bounds(self.shape[axis], range);\n        let src_range = clip_range_bounds(src.shape[axis], src_range);\n        unsafe { self.assign_slice_from_resolved(range, src, src_range, axis) };\n    }\n\n    #[allow(clippy::ptr_eq)]\n    unsafe fn assign_slice_from_resolved(\n        &mut self,\n        range: std::ops::Range<usize>,\n        src: &Tensor,\n        src_range: std::ops::Range<usize>,\n        axis: usize,\n    ) {\n        unsafe {\n            use ndarray::Slice;\n            unsafe fn assign_slice_t<T: Datum>(\n                to: &mut Tensor,\n                to_range: Range<usize>,\n                from: &Tensor,\n                from_range: Range<usize>,\n                axis: usize,\n            ) {\n                unsafe {\n                    to.to_array_view_mut_unchecked::<T>()\n                        .slice_axis_mut(Axis(axis), Slice::from(to_range))\n                        .assign(\n                            &from\n                                .to_array_view_unchecked::<T>()\n                                .slice_axis(Axis(axis), Slice::from(from_range)),\n                        )\n                }\n            }\n            if self.datum_type().is_copy() && self.shape[..axis].iter().all(|d| *d == 1) {\n                let stride = self.strides[axis] as usize * self.datum_type().size_of();\n                let dst_start = (stride * range.start) as isize;\n                let src_start = (stride * src_range.start) as isize;\n                let len = stride * range.len();\n                if len > 0 {\n                    if self.plain_storage().as_ptr() != src.plain_storage().as_ptr() {\n                        std::ptr::copy_nonoverlapping(\n                            src.plain_storage().as_ptr().offset(src_start),\n                            self.plain_storage_mut().as_mut_ptr().offset(dst_start),\n                            len,\n                        );\n                    } else {\n                        std::ptr::copy(\n                            src.plain_storage().as_ptr().offset(src_start),\n                            self.plain_storage_mut().as_mut_ptr().offset(dst_start),\n                            len,\n                        );\n                    }\n                }\n            } else {\n                dispatch_datum!(assign_slice_t(self.datum_type())(\n                    self, range, src, src_range, axis\n                ));\n            }\n        }\n    }\n\n    /// Get the datum type of the tensor.\n    #[inline]\n    pub fn datum_type(&self) -> DatumType {\n        self.dt\n    }\n\n    /// Set the datum type of the tensor.\n    #[inline]\n    pub unsafe fn set_datum_type(&mut self, dt: DatumType) {\n        self.dt = dt\n    }\n\n    /// Dump the tensor in a human readable form.\n    ///\n    /// `force_full` will force the tensor to be dump in full even if it is big.\n    pub fn dump(&self, force_full: bool) -> TractResult<String> {\n        if self.is_exotic() {\n            return Ok(format!(\n                \"{},{:?} (non-plain storage)\",\n                self.shape.iter().join(\",\"),\n                self.dt,\n            ));\n        }\n        unsafe fn dump_t<D: Datum>(tensor: &Tensor, n: usize) -> String {\n            unsafe {\n                if let Some(qp) = tensor.datum_type().qparams() {\n                    let integers = tensor.cast_to::<i32>().unwrap();\n                    integers.as_slice_unchecked::<i32>()[0..n]\n                        .iter()\n                        .map(|x| format!(\"[{}]({})\", x, qp.dq(*x)))\n                        .join(\", \")\n                } else {\n                    tensor.as_slice_unchecked::<D>()[0..n].iter().join(\", \")\n                }\n            }\n        }\n        unsafe {\n            let trunc = self.len() > 12 && !force_full;\n            let data = dispatch_datum!(dump_t(self.datum_type())(\n                self,\n                if trunc { 12 } else { self.len() }\n            ));\n            Ok(format!(\n                \"{},{:?} {}{}\",\n                self.shape.iter().join(\",\"),\n                self.dt,\n                data,\n                if trunc { \"...\" } else { \"\" }\n            ))\n        }\n    }\n\n    /// Compare two tensors, allowing for rounding errors.\n    pub fn close_enough(\n        &self,\n        other: &Self,\n        approx: impl Into<Approximation> + std::fmt::Debug,\n    ) -> TractResult<()> {\n        let approx = approx.into();\n        if self.shape() != other.shape() {\n            bail!(\"Shape mismatch {:?} != {:?}\", self.shape(), other.shape())\n        }\n        let (atol, rtol, outliers) = approx.atol_rtol_outliers(&self.datum_type());\n        let ma = self.cast_to::<f32>()?;\n        let ma = ma.to_plain_array_view::<f32>()?;\n        let mb = other.cast_to::<f32>()?;\n        let mb = mb.to_plain_array_view::<f32>()?;\n        let mut first_outlier = None;\n        let mut outliers_count = 0;\n        ndarray::indices_of(&ma).into_iter().for_each(|indices| {\n            let a = ma[&indices];\n            let b = mb[&indices];\n            if !((a.is_nan() && b.is_nan())\n                || (a.is_infinite() && b.is_infinite() && a.signum() == b.signum())\n                || (a - b).abs() <= atol as f32 + rtol as f32 * b.abs())\n            {\n                if outliers_count == 0 {\n                    first_outlier = Some(indices.as_array_view().to_vec());\n                }\n                outliers_count += 1;\n            }\n        });\n        if self.volume() > 0 && outliers_count as f64 / self.volume() as f64 > outliers {\n            let indices = first_outlier.unwrap();\n            let a = ma[&*indices];\n            let b = mb[&*indices];\n            bail!(\n                \"Mismatch. First outlier: {:?} for {:?}) at {:?} {} != {}. Outliers: {} / {} = {:0.5} > {:0.5}.\",\n                approx,\n                self.datum_type(),\n                indices,\n                a,\n                b,\n                outliers_count,\n                self.volume(),\n                outliers_count as f64 / self.volume() as f64,\n                outliers\n            );\n        }\n        Ok(())\n    }\n\n    /// Transform the tensor into a `ndarray::Array`.\n    pub fn into_plain_array<D: Datum>(self) -> TractResult<ArrayD<D>> {\n        Ok(self.to_plain_array_view::<D>()?.to_owned())\n    }\n\n    /// Transform the tensor into a `ndarray::Array`.\n    pub unsafe fn into_array_unchecked<D: Datum>(self) -> ArrayD<D> {\n        unsafe { self.to_array_view_unchecked::<D>().to_owned() }\n    }\n\n    /// Returns a plain array view of the tensor.\n    ///\n    /// Errors if the storage is not plain or the datum type does not match `D`.\n    #[inline]\n    pub fn to_plain_array_view<D: Datum>(&self) -> TractResult<ArrayViewD<'_, D>> {\n        self.try_as_plain()?.to_array_view::<D>()\n    }\n\n    /// Returns a mutable plain array view of the tensor.\n    ///\n    /// Errors if the storage is not plain or the datum type does not match `D`.\n    #[inline]\n    pub fn to_plain_array_view_mut<D: Datum>(&mut self) -> TractResult<ArrayViewMutD<'_, D>> {\n        self.check_for_access::<D>()?;\n        ensure!(self.storage.as_plain_mut().is_some(), \"Tensor storage is not plain\");\n        unsafe { Ok(self.to_array_view_mut_unchecked()) }\n    }\n\n    fn check_for_access<D: Datum>(&self) -> TractResult<()> {\n        ensure!(\n            self.datum_type().unquantized() == D::datum_type().unquantized(),\n            \"Tensor datum type error: tensor is {:?}, accessed as {:?}\",\n            self.datum_type(),\n            D::datum_type(),\n        );\n        Ok(())\n    }\n\n    /// Transform the data as a `ndarray::Array`.\n    pub unsafe fn to_array_view_unchecked<D: Datum>(&self) -> ArrayViewD<'_, D> {\n        if self.len() != 0 {\n            unsafe {\n                ArrayViewD::from_shape_ptr(&*self.shape, self.plain_storage().as_ptr() as *const D)\n            }\n        } else {\n            ArrayViewD::from_shape(&*self.shape, &[]).unwrap()\n        }\n    }\n\n    /// Transform the data as a mutable `ndarray::Array`.\n    pub unsafe fn to_array_view_mut_unchecked<D: Datum>(&mut self) -> ArrayViewMutD<'_, D> {\n        if self.len() != 0 {\n            unsafe {\n                let ptr = self.plain_storage_mut().as_mut_ptr() as *mut D;\n                ArrayViewMutD::from_shape_ptr(&*self.shape, ptr)\n            }\n        } else {\n            ArrayViewMutD::from_shape(&*self.shape, &mut []).unwrap()\n        }\n    }\n\n    /// Access the data as a pointer.\n    pub fn as_ptr<D: Datum>(&self) -> TractResult<*const D> {\n        self.check_for_access::<D>()?;\n        Ok(self.plain_storage().as_ptr() as *const D)\n    }\n\n    /// Access the data as a pointer.\n    pub unsafe fn as_ptr_unchecked<D: Datum>(&self) -> *const D {\n        self.plain_storage().as_ptr() as *const D\n    }\n\n    /// Access the data as a pointer.\n    pub unsafe fn as_ptr_mut_unchecked<D: Datum>(&mut self) -> *mut D {\n        self.plain_storage_mut().as_mut_ptr() as *mut D\n    }\n\n    /// Access the data as a mutable pointer.\n    pub fn as_ptr_mut<D: Datum>(&mut self) -> TractResult<*mut D> {\n        self.as_ptr::<D>().map(|p| p as *mut D)\n    }\n\n    /// Access the data as a slice.\n    pub unsafe fn as_slice_unchecked<D: Datum>(&self) -> &[D] {\n        if self.storage.byte_len() == 0 {\n            &[]\n        } else {\n            unsafe { std::slice::from_raw_parts::<D>(self.as_ptr_unchecked(), self.len()) }\n        }\n    }\n\n    /// Access the data as a mutable slice.\n    pub unsafe fn as_slice_mut_unchecked<D: Datum>(&mut self) -> &mut [D] {\n        if self.storage.byte_len() == 0 {\n            &mut []\n        } else {\n            unsafe { std::slice::from_raw_parts_mut::<D>(self.as_ptr_mut_unchecked(), self.len()) }\n        }\n    }\n\n    /// Make the tensor a scalar tensor (assumes it contains a single value).\n    pub fn to_scalar_tensor(&self) -> TractResult<Tensor> {\n        fn to_scalar_tensor_t<D: Datum>(t: &Tensor) -> TractResult<Tensor> {\n            Ok(litteral::tensor0(t.try_as_plain()?.to_scalar::<D>()?.clone()))\n        }\n        dispatch_datum!(to_scalar_tensor_t(self.datum_type())(self))\n    }\n\n    /// Access the data as a scalar.\n    pub unsafe fn to_scalar_unchecked<D: Datum>(&self) -> &D {\n        unsafe { &*(self.plain_storage().as_ptr() as *const D) }\n    }\n\n    /// Mutable access the data as a scalar.\n    pub fn to_scalar_mut<D: Datum>(&mut self) -> TractResult<&mut D> {\n        self.check_for_access::<D>()?;\n        if self.len() == 0 {\n            bail!(\"to_scalar_mut called on empty tensor ({:?})\", self)\n        }\n        if self.len() > 1 {\n            bail!(\"to_scalar called on a tensor with multiple values ({:?})\", self)\n        }\n        unsafe { Ok(self.to_scalar_mut_unchecked()) }\n    }\n\n    /// Mutable access the data as a scalar.\n    pub unsafe fn to_scalar_mut_unchecked<D: Datum>(&mut self) -> &mut D {\n        unsafe { &mut *(self.plain_storage_mut().as_mut_ptr() as *mut D) }\n    }\n\n    pub fn as_bytes(&self) -> &[u8] {\n        self.plain_storage().as_bytes()\n    }\n\n    pub fn as_bytes_mut(&mut self) -> &mut [u8] {\n        self.plain_storage_mut().as_bytes_mut()\n    }\n\n    unsafe fn is_uniform_t<T: Datum>(&self) -> bool {\n        let slice = unsafe { self.as_slice_unchecked::<T>() };\n        slice[1..].iter().all(|x| x == &slice[0])\n    }\n\n    pub fn is_uniform(&self) -> bool {\n        if self.is_exotic() {\n            return false;\n        }\n        if self.len() <= 1 {\n            return true;\n        }\n        unsafe { dispatch_datum!(Tensor::is_uniform_t(self.datum_type())(self)) }\n    }\n\n    unsafe fn as_uniform_t<T: Datum>(&self) -> Tensor {\n        let v: T = unsafe { self.as_slice_unchecked::<T>() }[0].clone();\n        litteral::tensor0(v)\n    }\n\n    pub fn as_uniform(&self) -> Option<Tensor> {\n        if self.len() >= 1 && self.is_uniform() {\n            unsafe {\n                let mut t = dispatch_datum!(Tensor::as_uniform_t(self.datum_type())(self));\n                t.set_datum_type(self.datum_type());\n                Some(t)\n            }\n        } else {\n            None\n        }\n    }\n\n    pub fn is_all_zero(&self) -> TractResult<bool> {\n        Ok(self.len() == 0 || self.as_uniform().map(|t| t.is_zero().unwrap()).unwrap_or(false))\n    }\n\n    pub fn is_zero(&self) -> TractResult<bool> {\n        Ok(self == &Tensor::zero_scalar_dt(self.dt)?)\n    }\n\n    unsafe fn natural_cast<\n        Source: Datum + num_traits::AsPrimitive<Target>,\n        Target: Datum + Copy,\n    >(\n        &self,\n        other: &mut Tensor,\n    ) {\n        unsafe {\n            self.as_slice_unchecked::<Source>()\n                .iter()\n                .zip(other.as_slice_mut_unchecked::<Target>().iter_mut())\n                .for_each(|(s, d)| *d = s.as_())\n        };\n    }\n\n    unsafe fn cast_number_to_bool<Source: Datum + num_traits::Zero>(&self, other: &mut Tensor) {\n        unsafe {\n            self.as_slice_unchecked::<Source>()\n                .iter()\n                .zip(other.as_slice_mut_unchecked::<bool>().iter_mut())\n                .for_each(|(s, d)| *d = !s.is_zero());\n        }\n    }\n\n    unsafe fn cast_from_string<Target: Datum + core::str::FromStr>(\n        &self,\n        other: &mut Tensor,\n    ) -> TractResult<()> {\n        unsafe {\n            for (s, d) in self\n                .as_slice_unchecked::<String>()\n                .iter()\n                .zip(other.as_slice_mut_unchecked::<Target>().iter_mut())\n            {\n                *d = s\n                    .parse()\n                    .map_err(|_| format_err!(\"Can not parse as {:?}\", Target::datum_type()))?;\n            }\n            Ok(())\n        }\n    }\n\n    unsafe fn cast_to_string<Source: Datum>(&self, other: &mut Tensor) {\n        unsafe {\n            for (s, d) in self\n                .as_slice_unchecked::<Source>()\n                .iter()\n                .zip(other.as_slice_mut_unchecked::<String>().iter_mut())\n            {\n                *d = s.to_string()\n            }\n        }\n    }\n\n    /// Optionnaly convert data to a tensor for a new DatumType.\n    pub fn cast_to<D: Datum>(&self) -> TractResult<Cow<'_, Tensor>> {\n        self.cast_to_dt(D::datum_type())\n    }\n\n    /// Optionnaly convert data to a tensor for a new DatumType.\n    #[allow(clippy::redundant_closure_call)]\n    pub fn cast_to_dt(&self, dst_dt: DatumType) -> TractResult<Cow<'_, Tensor>> {\n        unsafe {\n            if self.dt == dst_dt {\n                return Ok(Cow::Borrowed(self));\n            }\n            if self.dt == TDim::datum_type() && (dst_dt.is_integer() || dst_dt.is_float()) {\n                let slice = self.as_slice_unchecked::<TDim>();\n                let mut ints = Self::uninitialized::<i64>(&self.shape)?;\n                let ints_slice = ints.as_slice_mut_unchecked::<i64>();\n                for i in 0..self.len() {\n                    ints_slice[i] = slice[i].to_i64()?;\n                }\n                return Ok(Cow::Owned(ints.cast_to_dt(dst_dt)?.into_owned()));\n            }\n            if self.dt == bool::datum_type()\n                && (dst_dt.is_integer() || dst_dt.is_float() || dst_dt == TDim::datum_type())\n            {\n                let slice = self.as_slice_unchecked::<bool>();\n                let mut ints = Self::uninitialized::<i8>(&self.shape)?;\n                let ints_slice = ints.as_slice_mut_unchecked::<i8>();\n                for i in 0..self.len() {\n                    ints_slice[i] = slice[i] as usize as i8;\n                }\n                return Ok(Cow::Owned(ints.cast_to_dt(dst_dt)?.into_owned()));\n            }\n            let mut result = Self::uninitialized_dt(dst_dt, &self.shape)?;\n            if self.dt == DatumType::String {\n                dispatch_numbers!(Self::cast_from_string(dst_dt)(self, &mut result))?;\n                return Ok(Cow::Owned(result));\n            }\n            if dst_dt == DatumType::String {\n                dispatch_datum!(Self::cast_to_string(self.dt)(self, &mut result));\n                return Ok(Cow::Owned(result));\n            }\n            macro_rules! n {\n                ($source:ty) => {\n                    if <$source>::datum_type() == self.datum_type() {\n                        match dst_dt {\n                            DatumType::I8 => self.natural_cast::<$source, i8>(&mut result),\n                            DatumType::I16 => self.natural_cast::<$source, i16>(&mut result),\n                            DatumType::I32 => self.natural_cast::<$source, i32>(&mut result),\n                            DatumType::I64 => self.natural_cast::<$source, i64>(&mut result),\n                            DatumType::U8 => self.natural_cast::<$source, u8>(&mut result),\n                            DatumType::U16 => self.natural_cast::<$source, u16>(&mut result),\n                            DatumType::U32 => self.natural_cast::<$source, u32>(&mut result),\n                            DatumType::U64 => self.natural_cast::<$source, u64>(&mut result),\n                            DatumType::F16 => self.natural_cast::<$source, f16>(&mut result),\n                            DatumType::F32 => self.natural_cast::<$source, f32>(&mut result),\n                            DatumType::F64 => self.natural_cast::<$source, f64>(&mut result),\n                            DatumType::TDim => {\n                                let ints = self.cast_to::<i32>()?;\n                                let slice = ints.as_slice_unchecked::<i32>();\n                                let result = result.as_slice_mut_unchecked::<TDim>();\n                                for i in 0..self.len() {\n                                    result[i] = slice[i].into();\n                                }\n                            }\n                            DatumType::Bool => self.cast_number_to_bool::<$source>(&mut result),\n                            _ => todo!(),\n                        }\n                        return Ok(Cow::Owned(result));\n                    };\n                };\n            }\n            //If there is no quantization\n            if !dst_dt.is_quantized() && !self.datum_type().is_quantized() {\n                n!(u8);\n                n!(u16);\n                n!(u32);\n                n!(u64);\n                n!(i8);\n                n!(i16);\n                n!(i32);\n                n!(i64);\n                n!(f16);\n                n!(f32);\n                n!(f64);\n            } else {\n                let (s_zp, s_scale) = self.datum_type().zp_scale();\n                let (d_zp, d_scale) = dst_dt.zp_scale();\n                if self.datum_type().is_quantized() && dst_dt.is_float() {\n                    macro_rules! q_to_fp {\n                        ($source:ty, $dest:ty) => {\n                            if <$source>::datum_type().unquantized()\n                                == self.datum_type().unquantized()\n                                && <$dest>::datum_type().unquantized() == dst_dt.unquantized()\n                            {\n                                self.as_slice_unchecked::<$source>()\n                                    .iter()\n                                    .zip(result.as_slice_mut_unchecked::<$dest>().iter_mut())\n                                    .for_each(|(&s, d)| {\n                                        *d = (s as $dest - s_zp as $dest) * s_scale as $dest;\n                                    });\n                                return Ok(Cow::Owned(result));\n                            }\n                        };\n                    }\n                    q_to_fp!(i8, f64);\n                    q_to_fp!(i8, f32);\n                    q_to_fp!(u8, f64);\n                    q_to_fp!(u8, f32);\n                }\n                //TODO: optimize scale_by\n                macro_rules! q8_to_q8 {\n                    ($typ:ty) => {\n                        if dst_dt.unquantized() == <$typ>::datum_type() {\n                            self.as_slice_unchecked::<$typ>()\n                                .iter()\n                                .zip(result.as_slice_mut_unchecked::<$typ>().iter_mut())\n                                .for_each(|(&s, d)| {\n                                    *d = (d_zp as i32\n                                        + scale_by(s as i32 - s_zp as i32, s_scale / d_scale))\n                                    .clamp_cast()\n                                });\n                            return Ok(Cow::Owned(result));\n                        }\n                    };\n                }\n\n                macro_rules! q_via_f32 {\n                    ($source:ty, $dest:ty, $round:expr) => {\n                        if <$source>::datum_type().unquantized() == self.datum_type().unquantized()\n                            && <$dest>::datum_type().unquantized() == dst_dt.unquantized()\n                        {\n                            self.as_slice_unchecked::<$source>()\n                                .iter()\n                                .zip(result.as_slice_mut_unchecked::<$dest>().iter_mut())\n                                .for_each(|(&s, d)| {\n                                    let s_float = (s as f32 - s_zp as f32) * s_scale as f32;\n                                    let d_float = s_float as f32 / d_scale as f32 + d_zp as f32;\n                                    *d = $round(d_float);\n                                });\n                            return Ok(Cow::Owned(result));\n                        }\n                    };\n                }\n\n                macro_rules! q_n {\n                    (clamp $source:ty, $dest:ty) => {{\n                        if <$source>::datum_type().unquantized() == self.datum_type().unquantized()\n                            && <$dest>::datum_type().unquantized() == dst_dt.unquantized()\n                        {\n                            self.as_slice_unchecked::<$source>()\n                                .iter()\n                                .zip(result.as_slice_mut_unchecked::<$dest>().iter_mut())\n                                .for_each(|(&s, d)| {\n                                    *d = s.clamp_cast();\n                                });\n                            return Ok(Cow::Owned(result));\n                        }\n                    }};\n                    ($source:ty, $dest:ty) => {{\n                        if <$source>::datum_type().unquantized() == self.datum_type().unquantized()\n                            && <$dest>::datum_type().unquantized() == dst_dt.unquantized()\n                        {\n                            self.as_slice_unchecked::<$source>()\n                                .iter()\n                                .zip(result.as_slice_mut_unchecked::<$dest>().iter_mut())\n                                .for_each(|(&s, d)| {\n                                    *d = s as $dest;\n                                });\n                            return Ok(Cow::Owned(result));\n                        }\n                    }};\n                }\n\n                if dst_dt.unquantized() == self.datum_type().unquantized()\n                    && dst_dt.is_quantized()\n                    && self.datum_type().is_quantized()\n                {\n                    q8_to_q8!(i8);\n                    q8_to_q8!(u8);\n                }\n\n                q_via_f32!(f32, i8, |f| round_ties_to_even(f).clamp_cast());\n                q_via_f32!(f32, u8, |f| round_ties_to_even(f).clamp_cast());\n                q_via_f32!(f32, i32, |f| round_ties_to_even(f).clamp_cast());\n                q_via_f32!(i8, f32, |f| f);\n                q_via_f32!(u8, f32, |f| f);\n                q_via_f32!(i32, f32, |f| f);\n\n                if dst_dt.is_quantized() && self.datum_type().is_quantized() {\n                    q_via_f32!(u8, i8, |f| round_ties_to_even(f).clamp_cast());\n                    q_via_f32!(i8, u8, |f| round_ties_to_even(f).clamp_cast());\n                    q_via_f32!(i32, u8, |f| round_ties_to_even(f).clamp_cast());\n                    q_via_f32!(i32, i8, |f| round_ties_to_even(f).clamp_cast());\n                    q_via_f32!(u8, i32, |f| round_ties_to_even(f).clamp_cast());\n                    q_via_f32!(i8, i32, |f| round_ties_to_even(f).clamp_cast());\n\n                    // ensure cast to different scale offset work\n                    q_via_f32!(i8, i8, |f| round_ties_to_even(f).clamp_cast());\n                    q_via_f32!(u8, u8, |f| round_ties_to_even(f).clamp_cast());\n                }\n\n                q_n!(i8, i32);\n                q_n!(i8, u32);\n                q_n!(u8, i32);\n                q_n!(u8, u32);\n                q_n!(clamp i32, i8);\n                q_n!(clamp i32, u8);\n                q_n!(clamp u32, i8);\n                q_n!(clamp u32, u8);\n                q_n!(i8, i8);\n                q_n!(u8, u8);\n                q_n!(i32, i32);\n                q_n!(u32, u32);\n            }\n\n            bail!(\"Unsupported cast from {:?} to {:?}\", self.dt, dst_dt)\n        }\n    }\n\n    /// Access the data as a scalar, after a cast.\n    pub fn cast_to_scalar<D: Datum + Copy>(&self) -> TractResult<D> {\n        let casted = self.cast_to::<D>()?;\n        casted.try_as_plain()?.to_scalar::<D>().copied()\n    }\n\n    /// Access the nth element of the tensor, returned as a 0-rank Tensor\n    pub fn nth(&self, nth: usize) -> TractResult<Tensor> {\n        if nth >= self.len() {\n            bail!(\n                \"nth called with {}th element on a tensor of len {} ({:?}\",\n                nth,\n                self.len(),\n                self\n            );\n        }\n        unsafe fn nth_t<T: Datum>(me: &Tensor, nth: usize, output: &mut Tensor) {\n            unsafe {\n                let value = me.as_slice_unchecked::<T>()[nth].clone();\n                output.as_slice_mut_unchecked::<T>()[0] = value;\n            }\n        }\n        unsafe {\n            let mut output = Tensor::uninitialized_dt(self.datum_type(), &[])?;\n            dispatch_datum_by_size!(nth_t(self.datum_type())(self, nth, &mut output));\n            Ok(output)\n        }\n    }\n\n    /// Strict equality test on tensors.\n    fn eq_dt(&self, other: &Tensor) -> TractResult<bool> {\n        unsafe fn eq_t<D: Datum>(me: &Tensor, other: &Tensor) -> TractResult<bool> {\n            unsafe {\n                if D::datum_type().is_float() {\n                    return dispatch_floatlike!(float_eq_t(D::datum_type())(me, other));\n                }\n                Ok(izip!(me.as_slice_unchecked::<D>(), other.as_slice_unchecked::<D>())\n                    .all(|(a, b)| a == b))\n            }\n        }\n\n        unsafe fn float_eq_t<D: Datum + Float>(me: &Tensor, other: &Tensor) -> TractResult<bool> {\n            unsafe {\n                Ok(izip!(me.as_slice_unchecked::<D>(), other.as_slice_unchecked::<D>())\n                    .all(|(a, b)| (a.is_nan() && b.is_nan()) || a == b))\n            }\n        }\n\n        unsafe {\n            Ok(self.datum_type() == other.datum_type()\n                && self.shape() == other.shape()\n                && dispatch_datum!(eq_t(self.dt)(self, other))?)\n        }\n    }\n\n    fn from_datum<T: Datum>(mut it: ArrayD<T>) -> Tensor {\n        unsafe {\n            let mut t = Self::uninitialized::<T>(it.shape()).unwrap();\n            if let Some(slice) = it.as_slice_mut() {\n                if t.datum_type().is_copy() {\n                    std::ptr::copy_nonoverlapping(\n                        slice.as_ptr() as *const i8,\n                        t.as_ptr_mut_unchecked(),\n                        t.plain_storage().layout().size(),\n                    );\n                } else {\n                    t.as_slice_mut_unchecked::<T>()\n                        .iter_mut()\n                        .zip(slice.iter_mut())\n                        .for_each(|(t, s)| *t = std::mem::take(s));\n                }\n                return t;\n            }\n            if it.strides().iter().all(|&s| s > 0) && it.as_slice_memory_order().is_some() {\n                let mut len_and_strides: TVec<(usize, usize)> = tvec!();\n                for (len, stride) in itertools::izip!(it.shape(), it.strides(), t.strides())\n                    .sorted_by_key(|(_, src, _)| *src)\n                    .map(|(l, _, dst)| (*l as isize, *dst))\n                {\n                    if !len_and_strides.is_empty()\n                        && len_and_strides.last().unwrap().1 * len_and_strides.last().unwrap().0\n                            == stride as usize\n                    {\n                        len_and_strides.last_mut().unwrap().0 *= len as usize;\n                    } else {\n                        len_and_strides.push((len as usize, stride as usize));\n                    }\n                }\n                len_and_strides.reverse();\n                crate::scatter::scatter_contig_data(\n                    it.as_ptr(),\n                    t.as_ptr_mut_unchecked(),\n                    &len_and_strides,\n                );\n                return t;\n            }\n            // finally use ndarray into_iter()\n            t.as_slice_mut_unchecked().iter_mut().zip(it).for_each(|(t, a)| *t = a);\n            t\n        }\n    }\n\n    pub fn deep_clone(&self) -> Tensor {\n        if self.is_exotic() {\n            return Tensor {\n                dt: self.dt,\n                shape: self.shape.clone(),\n                strides: self.strides.clone(),\n                len: self.len,\n                storage: self.storage.deep_clone(),\n            };\n        }\n        unsafe {\n            let mut tensor = Tensor::uninitialized_dt(self.datum_type(), self.shape()).unwrap();\n            if self.len() > 0 {\n                if self.dt.is_copy() {\n                    self.plain_storage().as_ptr().copy_to_nonoverlapping(\n                        tensor.as_bytes_mut().as_mut_ptr(),\n                        self.plain_storage().layout().size(),\n                    )\n                } else if self.dt == DatumType::String {\n                    tensor\n                        .as_slice_mut_unchecked::<String>()\n                        .clone_from_slice(self.as_slice_unchecked());\n                } else if self.dt == DatumType::Blob {\n                    tensor\n                        .as_slice_mut_unchecked::<Blob>()\n                        .clone_from_slice(self.as_slice_unchecked());\n                } else if self.dt == DatumType::TDim {\n                    tensor\n                        .as_slice_mut_unchecked::<TDim>()\n                        .clone_from_slice(self.as_slice_unchecked());\n                }\n            }\n            tensor\n        }\n    }\n\n    pub fn slice(&self, axis: usize, start: usize, end: usize) -> TractResult<Tensor> {\n        if axis >= self.rank() {\n            bail!(\"Can not slice at axis {} tensor {:?}\", axis, self);\n        }\n        if start > self.shape[axis] || end > self.shape[axis] || start >= end {\n            bail!(\"Invalid slicing range {start}..{end} on axis {axis} for {self:?}\");\n        }\n        fn slice_t<T: Datum>(\n            t: &Tensor,\n            axis: usize,\n            start: usize,\n            end: usize,\n        ) -> TractResult<Tensor> {\n            Ok(t.to_plain_array_view::<T>()?\n                .slice_axis(ndarray::Axis(axis), (start..end).into())\n                .into_owned()\n                .into_tensor())\n        }\n        dispatch_datum!(slice_t(self.datum_type())(self, axis, start, end))\n    }\n\n    #[inline]\n    pub fn view(&self) -> view::TensorView<'_> {\n        unsafe { view::TensorView::view(self) }\n    }\n\n    #[inline]\n    pub fn view_at_prefix(&self, prefix: &[usize]) -> TractResult<view::TensorView<'_>> {\n        view::TensorView::at_prefix(self, prefix)\n    }\n\n    #[inline]\n    pub fn view_offsetting(&self, coords: &[usize]) -> TractResult<view::TensorView<'_>> {\n        view::TensorView::offsetting(self, coords)\n    }\n\n    #[inline]\n    pub unsafe fn view_offsetting_unchecked(&self, coords: &[usize]) -> view::TensorView<'_> {\n        unsafe { view::TensorView::offsetting_unchecked(self, coords) }\n    }\n\n    #[inline]\n    pub fn view_mut(&mut self) -> view::TensorView<'_> {\n        unsafe { view::TensorView::view(self) }\n    }\n\n    #[inline]\n    pub fn view_at_prefix_mut(&mut self, prefix: &[usize]) -> TractResult<view::TensorView<'_>> {\n        view::TensorView::at_prefix(self, prefix)\n    }\n\n    #[inline]\n    pub fn view_offsetting_mut(&mut self, coords: &[usize]) -> TractResult<view::TensorView<'_>> {\n        view::TensorView::offsetting(self, coords)\n    }\n\n    /// Offsets the tensor as an i8 type if it's an u8 type, otherwise passes it unchanged.\n    pub fn offset_u8_as_i8(self: &Arc<Self>) -> Arc<Self> {\n        let mut t = if let DatumType::U8 = self.dt.unquantized() {\n            self.try_as_plain()\n                .unwrap()\n                .to_array_view::<u8>()\n                .unwrap()\n                .mapv(|v| v.wrapping_sub(128) as i8)\n                .into_tensor()\n        } else {\n            return self.clone();\n        };\n\n        if let DatumType::QU8(qp) = self.dt {\n            if let QParams::ZpScale { zero_point, scale } = qp {\n                t.dt = DatumType::QI8(QParams::ZpScale { zero_point: zero_point - 128, scale });\n            } else {\n                t.dt = DatumType::QI8(qp);\n            }\n        }\n\n        t.into_arc_tensor()\n    }\n\n    /// Offsets the tensor as an u8 type if it's an i8 type, otherwise passes it unchanged.\n    pub fn offset_i8_as_u8(self: &Arc<Self>) -> Arc<Self> {\n        let mut t = if let DatumType::I8 = self.dt.unquantized() {\n            self.try_as_plain()\n                .unwrap()\n                .to_array_view::<i8>()\n                .unwrap()\n                .mapv(|v| (v as u8).wrapping_add(128))\n                .into_tensor()\n        } else {\n            return self.clone();\n        };\n\n        if let DatumType::QI8(qp) = self.dt {\n            if let QParams::ZpScale { zero_point, scale } = qp {\n                t.dt = DatumType::QU8(QParams::ZpScale { zero_point: zero_point + 128, scale });\n            } else {\n                t.dt = DatumType::QU8(qp);\n            }\n        }\n        t.into_arc_tensor()\n    }\n\n    pub fn to_aligned_default(&self) -> TractResult<Self> {\n        if self.dt.is_copy() {\n            unsafe {\n                let mut t = Self::uninitialized_dt(self.dt, &self.shape)?;\n                t.as_bytes_mut().copy_from_slice(self.as_bytes());\n                Ok(t)\n            }\n        } else {\n            let mut t = Self::zero_dt(self.dt, &self.shape)?;\n            if self.dt == String::datum_type() {\n                t.try_as_plain_mut()?\n                    .as_slice_mut::<String>()?\n                    .clone_from_slice(self.try_as_plain()?.as_slice()?);\n            } else if self.dt == Blob::datum_type() {\n                t.try_as_plain_mut()?\n                    .as_slice_mut::<Blob>()?\n                    .clone_from_slice(self.try_as_plain()?.as_slice()?);\n            } else if self.dt == TDim::datum_type() {\n                t.try_as_plain_mut()?\n                    .as_slice_mut::<TDim>()?\n                    .clone_from_slice(self.try_as_plain()?.as_slice()?);\n            }\n            Ok(t)\n        }\n    }\n\n    pub fn natural_strides(shape: &[usize]) -> TVec<isize> {\n        let mut strides = tvec!();\n        compute_natural_stride_to(&mut strides, shape);\n        strides\n    }\n\n    pub fn into_blob(mut self) -> TractResult<Blob> {\n        ensure!(self.dt.is_copy());\n        let storage =\n            std::mem::replace(&mut self.storage, StorageKind::Plain(PlainStorage::default()));\n        Ok(storage.into_plain().context(\"Storage is not plain\")?.into_blob())\n    }\n}\n\nimpl PartialEq for Tensor {\n    fn eq(&self, other: &Tensor) -> bool {\n        if self.dt != other.dt || self.shape != other.shape {\n            return false;\n        }\n        match (self.storage.as_plain(), other.storage.as_plain()) {\n            (Some(_), Some(_)) => self.eq_dt(other).unwrap_or(false),\n            (None, None) => self.storage == other.storage,\n            _ => false,\n        }\n    }\n}\n\nimpl Eq for Tensor {}\n\nimpl fmt::Debug for Tensor {\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        let content = self.dump(false).unwrap_or_else(|e| format!(\"Error : {e:?}\"));\n        write!(formatter, \"{content}\")\n    }\n}\n\n#[cfg(feature = \"complex\")]\npub fn reinterpret_inner_dim_as_complex(mut t: Tensor) -> TractResult<Tensor> {\n    ensure!(\n        t.shape().last() == Some(&2),\n        \"The last dimension in the tensor shape {:?} must be 2\",\n        t.shape()\n    );\n    unsafe {\n        t.shape.pop();\n        t.set_datum_type(t.datum_type().complexify()?);\n        t.update_strides_and_len();\n        Ok(t)\n    }\n}\n\n#[cfg(feature = \"complex\")]\npub fn reinterpret_complex_as_inner_dim(mut t: Tensor) -> TractResult<Tensor> {\n    unsafe {\n        t.shape.push(2);\n        t.set_datum_type(t.datum_type().decomplexify()?);\n        t.update_strides_and_len();\n        Ok(t)\n    }\n}\n\npub fn clip_range_bounds(len: usize, range: impl std::ops::RangeBounds<usize>) -> Range<usize> {\n    use std::ops::Bound;\n    let start = match range.start_bound() {\n        Bound::Included(ix) => *ix,\n        Bound::Excluded(ix) => ix + 1,\n        Bound::Unbounded => 0,\n    };\n    let end = match range.end_bound() {\n        Bound::Included(ix) => *ix + 1,\n        Bound::Excluded(ix) => *ix,\n        Bound::Unbounded => len,\n    };\n    start..end\n}\n\npub fn natural_strides(shape: &[usize]) -> TVec<isize> {\n    let mut strides = tvec!();\n    compute_natural_stride_to(&mut strides, shape);\n    strides\n}\n\nfn compute_natural_stride_to(strides: &mut TVec<isize>, shape: &[usize]) {\n    match shape.len() {\n        0 => (),\n        1 => strides.push(1),\n        2 => strides.extend_from_slice(&[shape[1] as isize, 1]),\n        3 => strides.extend_from_slice(&[(shape[1] * shape[2]) as isize, shape[2] as _, 1]),\n        4 => strides.extend_from_slice(&[\n            (shape[1] * shape[2] * shape[3]) as isize,\n            (shape[2] * shape[3]) as _,\n            shape[3] as _,\n            1,\n        ]),\n        _ => {\n            strides.push(1);\n            for dim in shape.as_ref().iter().skip(1).rev() {\n                let previous = *strides.last().unwrap();\n                strides.push(previous * *dim as isize)\n            }\n            strides.reverse();\n        }\n    }\n}\n\nimpl<D: ::ndarray::Dimension, T: Datum> From<Array<T, D>> for Tensor {\n    fn from(it: Array<T, D>) -> Tensor {\n        Tensor::from_datum(it.into_dyn())\n    }\n}\n\n/// Convenient conversion to Tensor.\npub trait IntoTensor: Sized {\n    /// Convert Self to a Tensor.\n    ///\n    /// May perform a copy\n    fn into_tensor(self) -> Tensor;\n}\n\n/// Convenient conversion to Arc<Tensor>.\npub trait IntoArcTensor: Sized {\n    /// Convert Self to a Arc<Tensor>.\n    ///\n    /// May perform a copy\n    fn into_arc_tensor(self) -> Arc<Tensor>;\n}\n\nimpl<D: ::ndarray::Dimension, T: Datum> IntoTensor for Array<T, D> {\n    fn into_tensor(self) -> Tensor {\n        Tensor::from(self)\n    }\n}\n\nimpl<D: ::ndarray::Dimension, T: Datum> IntoArcTensor for Array<T, D> {\n    fn into_arc_tensor(self) -> Arc<Tensor> {\n        Arc::new(Tensor::from(self))\n    }\n}\n\nimpl IntoTensor for Tensor {\n    fn into_tensor(self) -> Tensor {\n        self\n    }\n}\n\nimpl IntoTensor for Arc<Tensor> {\n    fn into_tensor(self) -> Tensor {\n        Arc::try_unwrap(self).unwrap_or_else(|t| (*t).clone())\n    }\n}\n\nimpl IntoArcTensor for Tensor {\n    fn into_arc_tensor(self) -> Arc<Tensor> {\n        Arc::new(self)\n    }\n}\n\nimpl IntoArcTensor for Arc<Tensor> {\n    fn into_arc_tensor(self) -> Arc<Tensor> {\n        self\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::dim::SymbolScope;\n    use crate::prelude::tensor1;\n\n    use super::*;\n    use litteral::tensor0;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n\n    #[derive(Debug)]\n    struct PermuteAxisProblem {\n        shape: Vec<usize>,\n        permutation: Vec<usize>,\n    }\n\n    impl Arbitrary for PermuteAxisProblem {\n        type Strategy = BoxedStrategy<PermuteAxisProblem>;\n        type Parameters = ();\n\n        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {\n            (0..8usize)\n                .prop_flat_map(|rank| {\n                    let permute: Vec<usize> = (0..rank).collect();\n                    (proptest::collection::vec(1..5usize, rank), Just(permute).prop_shuffle())\n                })\n                .prop_map(|(shape, permutation)| PermuteAxisProblem { shape, permutation })\n                .boxed()\n        }\n    }\n\n    impl PermuteAxisProblem {\n        fn input(&self) -> ArrayD<i32> {\n            let mut i = 0;\n            ArrayD::from_shape_simple_fn(&*self.shape, || {\n                i += 1;\n                i\n            })\n            .permuted_axes(&*self.permutation)\n        }\n\n        fn reference(&self) -> Tensor {\n            let values: Vec<i32> = self.input().iter().copied().collect();\n            let shape = self.permutation.iter().map(|ix| self.shape[*ix]).collect::<TVec<usize>>();\n            super::litteral::tensor1(&values).into_shape(&shape).unwrap()\n        }\n\n        fn tract(&self) -> Tensor {\n            Tensor::from(self.input())\n        }\n\n        fn check(&self) -> proptest::test_runner::TestCaseResult {\n            prop_assert_eq!(self.tract(), self.reference());\n            Ok(())\n        }\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn prop(pb: PermuteAxisProblem) {\n            pb.check().unwrap();\n        }\n    }\n\n    #[test]\n    fn t_1_2() {\n        PermuteAxisProblem { shape: vec![2, 1], permutation: vec![1, 0] }.check().unwrap();\n    }\n\n    #[test]\n    fn t_2_2() {\n        PermuteAxisProblem { shape: vec![2, 2], permutation: vec![1, 0] }.check().unwrap();\n    }\n\n    #[derive(Debug)]\n    struct BroadcastVecToShape {\n        vec: Vec<f32>,\n        axis: usize,\n        shape: TVec<usize>,\n    }\n\n    impl BroadcastVecToShape {\n        fn check(&self) -> proptest::test_runner::TestCaseResult {\n            let input = tensor1(&self.vec);\n            let mut intermediate = tvec![1usize; self.shape.len()];\n            intermediate[self.axis] = self.vec.len();\n            let reference = input\n                .clone()\n                .into_shape(&intermediate)\n                .unwrap()\n                .broadcast_to_shape(&self.shape)\n                .unwrap();\n            prop_assert_eq!(\n                reference,\n                input.broadcast_vector_to_shape(&self.shape, self.axis).unwrap()\n            );\n            Ok(())\n        }\n    }\n\n    impl Arbitrary for BroadcastVecToShape {\n        type Strategy = BoxedStrategy<BroadcastVecToShape>;\n        type Parameters = ();\n\n        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {\n            vec(0usize..5, 0usize..4)\n                .prop_flat_map(|shape| {\n                    (vec(-10f32..10f32, 0usize..5), Just(shape.clone()), 0..shape.len() + 1)\n                })\n                .prop_map(|(vec, mut shape, axis)| {\n                    shape.insert(axis, vec.len());\n                    BroadcastVecToShape { vec, shape: shape.into(), axis }\n                })\n                .boxed()\n        }\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn broadcast_vector_to_shape_prop(pb: BroadcastVecToShape) {\n            pb.check().unwrap()\n        }\n    }\n\n    #[test]\n    #[cfg(feature = \"complex\")]\n    fn test_reinterpret_inner_dim_as_complex() -> TractResult<()> {\n        let input = crate::internal::tensor2(&[[1.0f32, 2.0], [3.0, 4.0], [5.0, 6.0]]);\n        let cplx_input = reinterpret_inner_dim_as_complex(input)?;\n        let expected = crate::internal::tensor1(&[\n            Complex::new(1.0f32, 2.0),\n            Complex::new(3.0, 4.0),\n            Complex::new(5.0, 6.0),\n        ]);\n        assert_eq!(expected, cplx_input);\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"complex\")]\n    fn test_reinterpret_inner_dim_as_complex_2() -> TractResult<()> {\n        let input =\n            crate::internal::tensor3(&[[[1i32, 2], [1, 2]], [[3, 4], [3, 4]], [[5, 6], [5, 6]]]);\n        let cplx_input = reinterpret_inner_dim_as_complex(input)?;\n        let expected = crate::internal::tensor2(&[\n            [Complex::new(1i32, 2), Complex::new(1, 2)],\n            [Complex::new(3, 4), Complex::new(3, 4)],\n            [Complex::new(5, 6), Complex::new(5, 6)],\n        ]);\n        assert_eq!(expected, cplx_input);\n        Ok(())\n    }\n\n    #[test]\n    fn clone_tdim_tensor() {\n        let symbols = SymbolScope::default();\n        let a = symbols.sym(\"a\");\n        let t = tensor0(TDim::from(a));\n        let _ = t.clone();\n    }\n}\n"
  },
  {
    "path": "deny.toml",
    "content": "\n# add whatever else we support.\n[graph]\ntargets = [\n    { triple = \"x86_64-unknown-linux-gnu\" },\n    { triple = \"x86_64-unknown-linux-musl\" },\n    { triple = \"x86_64-apple-darwin\" },\n    { triple = \"x86_64-pc-windows-msvc\" },\n    { triple = \"aarch64-linux-android\" },\n    { triple = \"aarch64-unknown-linux-gnu\" },\n    { triple = \"aarch64-unknown-linux-musl\" },\n    { triple = \"aarch64-apple-ios\" },\n    { triple = \"aarch64-apple-darwin\" },\n    { triple = \"armv7-unknown-linux-gnueabihf\" },\n    { triple = \"armv7-unknown-linux-musleabi\" },\n    { triple = \"arm-unknown-linux-gnueabihf\" },\n    { triple = \"wasm32-unknown-unknown\" },\n]\n\n[advisories]\ngit-fetch-with-cli = true\nyanked = \"deny\"\nignore = [\n    \"RUSTSEC-2024-0436\", # paste unmaintained — transitive dep from metal, tokenizers, rav1e\n]\n\n[bans]\nmultiple-versions = \"deny\"\nwildcards = \"allow\"\ndeny = [\n    # List crates we don't want in our dependency tree here.\n]\n\n# Skip some multiple-versions checks, until they can be fixed.\nskip = [\n    { name = \"hashbrown\", version=\"<0.16\" },\n    { name = \"foldhash\", version=\"<0.2\" },\n    { name = \"cpufeatures\", version=\"<0.3\" },\n    { name = \"cfg-if\", version=\"<1\" },\n    { name = \"getrandom\", version=\"<0.4\" },\n    { name = \"rand\", version=\"<0.10\" },\n    { name = \"rand_core\", version=\"<0.10\" },\n    { name = \"rand_distr\", version=\"<0.6\" },\n]\n\n[sources]\n# trusted git sources.\nallow-git = [\n    \"https://github.com/rustformers/llm.git\",\n]\n\n[licenses]\nallow = [\n    \"Apache-2.0\",                     # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)\n    \"MIT\",                            # https://tldrlegal.com/license/mit-license\n    \"Unicode-3.0\",                    # https://spdx.org/licenses/Unicode-3.0.html\n    \"Zlib\",                           # https://tldrlegal.com/license/zlib-libpng-license\n    \"ISC\",                            # https://tldrlegal.com/license/isc-license\n    \"MPL-2.0\",                        # https://tldrlegal.com/license/mozilla-public-license-2.0-(mpl-2)\n]\n\nclarify = [\n]\n"
  },
  {
    "path": "doc/README.md",
    "content": "# Tract internals documentation\n\nThis kind of documentation does not tend to age well. Use with caution.\n\n* a [tract crates introduction](intro.md)\n* a [tract command line cookbook](cli-recipe.md)\n* [graph, model, node, op, facts](graph.md)\n"
  },
  {
    "path": "doc/cli-recipe.md",
    "content": "# tract cli recipes\n\n`tract` command line is meant to be an auditing, debugging, profiling tool, for CI and\ninteractive usage.\n\nPlease do not make assumptions on the exact forms of its outputs. We do not commit on\nany form of stability suitable to script writing.\n\nWe are going to use [ONNX mobilenet](../examples/onnx-mobilenet-v2) as examples in these notes. See its\ncode and README to download a the model.\n\n## Install `tract`\n\n* build latest release: \n\n```\ncargo install tract\n```\n\n* download a prebuilt binary fo MacOs Intel, linux Intel, Armv7 or Armv8\n\n```\nhttps://github.com/sonos/tract/releases/latest\n```\n\n* run from source\n\n```\ngit clone https://github.com/sonos/tract\ncd cli\ncargo run\n```\n\n## Model loading\n\nFirst, `tract` needs to load a model and pipeline it to the preferred `tract-opl` form.\nThis is equivalent to using the API to load an onnx file, and setting input shapes with\nInferenceFact and with_input_fact.\n\n```bash\ntract mobilenetv2-7.onnx -i 1,3,224,224\n```\n\nloads the model like:\n\n```rust\ntract_onnx::onnx()\n            .model_for_path(\"mobilenetv2-7.onnx\")?\n            .with_input_fact(0, f32.fact(&[1, 3, 224, 224]).into())?\n            .into_decluttered()?\n```\n\n## Model import pipeline\n\nOnce a model is loaded, tract default behaviour is to call the the `dump` subcommand, so the\nprevious example is equivalent to:\n\n```bash\ntract mobilenetv2-7.onnx -i 1,3,224,224,f32 dump\n```\n\nThe displayed form is `tract-opl` intermediate representation. It is *decluttered* of most\ntraining artefacts, in a form meant to be simple to reason about and as stripped down as\npossible.\n\nThis is not the \"optimised\" form: `tract-opl` form is meant to be platform independant, can\nbe serialized to nnef. The optimised form is just meant to be as fast as possible on a given\nCPU.\n\nThe `.into_optimized()` transformation can be performed by passing `-O` to the command line.\n\n```bash\ntract -O mobilenetv2-7.onnx -i 1,3,224,224,f32 dump\n```\n\nSeveral other intermediate network \"stages\" can be reached by using `--pass XXX` instead of `-O`.\n`--pass load` and `--pass analyse` are interesting as they can dump a network for which inputs are\nunknown (maybe to try and figure out what they could be).\n\n## Benching a network\n\nWe can get a reading of tract performance on a model by running the `bench` or`criterion`\nsubcommands.\n\n```\ntract -O mobilenetv2-7.onnx -i 1,3,224,224,f32 bench\ntract -O mobilenetv2-7.onnx -i 1,3,224,224,f32 criterion\n```\n\nThe first one is a simple bench runner customized for tract specific needs, the second one\nuses the [criterion](https://docs.rs/criterion) crate.\n\n## Profiling a network\n\nGetting a raw performance number is a first step, but tract can also profile a network execution.\nA goto command to get a first glimpse can be:\n\n```\ntract -O mobilenetv2-7.onnx -i 1,3,224,224,f32 dump --profile --cost\n```\n\nThis will show running time for each operator, its relative weight. For some critical operations,\nit will also give a number of arithmetic operations per seconds (typically Flops).\n\nNote that\nwe only count item multiplications, whereas many projects in the HPC field count both\nmultiplications and additions. So for matrix multiplication, convolution and the like, you may need\nto double tract Flops number before comparing with, say, BLAS implementations.\n\nPlease do not parse this output. At least use the `--json` output. We do not commit on its stability\nbut it's less susceptible to changes.\n\n## Running a test case\n\n`tract` command line can also be use to build test-case, either for non-regression insurance\nof debugging purposes.\n\n`--input-facts-from-bundle` takes a `.npz` file, and will set the input facts (dtype, shape) according to the tensors\nin the npz file. This is useful when your model does not have any input type information embedded within it.\n\nThe `run` subcommand accepts an `--input-from-bundle` that also takes a `.npz` file, but it\nwill not set any input fact, it will only take the tensor values.\nThis will also supersede the `-i` option: we will take the input shapes and tensor\nfrom the input itself.\n\nThe `run` subcommand also accepts an `--assert-output-bundle`. This time, the tensors names are\nmatched with the model output names. `tract` will run over the input and check that its finding\nare the same to the expected output (with some leeway for rounding differences).\n\n[Example here](/onnx/test_cases/qtanh_1) for a quantized tanh in onnx.\n\n```sh\ntract model.onnx -O run --input-from-bundle io.npz --assert-output-bundle io.npz\n```\n\nIf we want to make sure we actually check something, `-v` can help:\n\n```\ntract -v model.onnx -O run --input-from-bundle io.npz --assert-output-bundle io.npz\n```\n\nThe log displays \"Checked output #0, ok.\" (among other information).\n\n[generate_io.py here](/onnx/test_cases/transformer-mlm/generate_io.py) contains an example building a\ntestcase for a BERT model from huggingface for inspiration.\n"
  },
  {
    "path": "doc/graph.md",
    "content": "# Tract internals - Graph, Node, Fact and Op\n\nThese are a few notes about the way tract represents Neural Networks.\n\n## Graph, Node, and OutletId\n\nNeural Network natural structure is roughly a Directed Acyclic Graph.\nIn tract, the network structure is materialized mostly by Graph and BaseNode.\n\nFrom core/src/model/{ graph, node }.rs, with some edits\n\n```rust\npub struct Graph<F, O> {\n    pub nodes: Vec<BaseNode<F, O>>,\n    pub inputs: Vec<OutletId>,\n    pub outputs: Vec<OutletId>,\n    /* [...] */\n}\n\npub struct BaseNode<F, O> {\n    pub inputs: Vec<OutletId>,\n    pub op: O,\n    pub outputs: Vec<Outlet<F>>,\n}\n\npub struct OutletId {\n    pub node: usize,\n    pub slot: usize,\n}\n\npub struct Outlet<F: Fact + Hash> {\n    pub fact: F,\n    pub successors: Vec<InletId>,\n}\n```\n\n`Graph` contains a list of `BaseNode` (the `nodes` field).\n\nEach node contains an `Op`, which describes and implements the operator\nthe Node will apply to the data. Op can be a convolution, addition, etc. \nEach node has zero or more inputs referred (rarely) as *inlets*, and zero or\nmore outputs referred (often) as *outlets*. Most operators have one single\noutlet.\n\nWhen the network is ran, tract executes the nodes in the order induced by the\nlinks: a node can only be ran all when tract knows the value of all its inputs.\n\nNote that links between nodes (often called *wires*) are not symetrical: a\ntensor produced by a node after its op is ran, can be fed to more than one\noperator. An outlet can be connected to one or several inlets. On the other\nhand one inlet can only have its value set by one outlet. A wire connects\n*one single unique* outlet to several inlets.\n\nAn OutletId is a pair made of a node id and a *slot* which is the output number\nof the Op. As most of the ops have a single output, the OutletId slot are\nfrequently zero.\n\nAs a consequence, OutletId is a the identifier for the wires in the graph,\nconnecting the designated outlet to successor inlets, and which tensor value\nis set by the op from the node owning the outlet.\n\nThe `BaseNode` incoming wires are materialized by the `inputs` field, as a\nsimple vector.\n\nFor easier graph traversal, tract also maintains in `BaseNode` a redundant list\nof outgoing wires: `outputs.successors` field. Additionaly, BaseNode stores a\n`Fact` for each of its outgoing wires. Fact is the topic of the next section.\n\nFinally, `Graph` owns two lists of OutletId designating the model inputs and\noutputs: it is required that nodes pointed but the inputs' OutletId have the\nspecific Op `Source`. On the other hand, the output of a model can be any\nnode,\n\nWith this terminology, we can say that \"running a network\" involves finding\nthe value (a tensor) of each wire in the graph, \n\n    1. Source nodes have their value set by the network caller\n    2. compute node outputs for unvisited nodes which have all their inputs\n        known until all nodes are computed\n    3. extract the model outputs from the wires pointed by `Graph.outputs`\n\n## Facts\n\ntract actually does much more than running the network as described in previous\nsection. It is capable of performing several optimisations, at the single node\nor at the graph level. It can also perfom specific transformation, like\nconverting a streaming network to a pulsing network.\n\nIn order to perform these network rewrites, tract needs to be able to reason\nabout the *datum type* and the *shape* of each wire in the graph. Datum type,\ndespite the pedantic name, is just the type of each element of the tensor (like\nf32, i8, or String).\n\nThings get a bit more complicated here, because we often need to manipulate and\nreason with graph with partial shape information. For instance, in TensorFlow\nfrozen network format, input shapes are usually not explicit, so we need to be\nable to load graphs with partial facts before completing the shape and type\nanalysis.\n\n`Fact` is a trait abstracting over the level of knowledge the `Graph` has about\nthe model types and shapes. The `F` type parameter in Graph is the kind of Fact\nthat *nodes* in the graph will contains as `outputs.fact`.\n\nThere are two major implementation of Fact in tract. The first one is\n`TypedFact` in tract-core, the second one is `InferenceFact` in tract-hir.\n\nFrom core/src/model/fact.rs with some edits.\n\n```rust\npub struct TypedFact {\n    pub datum_type: DatumType,\n    pub shape: ShapeFact,\n    pub konst: Option<Arc<Tensor>>,\n}\n\npub struct ShapeFact(Vec<TDim>);\n```\n\nDatumType is a simple enumeration of the various element type a Tensor can hold\n(like DatumType::F32, or DatumType::I8...). ShapeFact is a basically a vector\nof TDim. Let's assume TDim is an integer for now.\n\nTypedFact has also a optional constant value: if a tensor in the graph has a\nconstant value regardless of the network inputs, this is information the\noptimiser may be able to use to simplify the network.\n\nAs we implied beforehand, this type is not suitable to reprensent network where\nsome wires have an unknown datum type, or partial shape information. In order\nto reason about these networks, we need a more flexible Fact:\n\nFrom hir/sr/infer/fact.rs\n\n```rust\npub struct InferenceFact {\n    pub datum_type: TypeFactoid,\n    pub shape: ShapeFactoid,\n    pub value: ValueFact,\n}\n```\n\n*Inference* here takes the Computer Science meaning (as in \"type inference\"),\nnot the machine learning sense (synonym with *prediction*).\n\nThe InferenceFact main purpose is to support full shape and datum type\ndiscovery of a graph. When this full *analysis* is done, the network facts\nare all converted to TypedFact and the graph can undergo optimisation and\nother transformations.\n\nWithout entering into too many details here, `TypeFactoid` is basically \nan `Option<DatumType>`, where the `None` value means \"unknown\". Similarly\n`ShapeFact` can be seen as a `Vec<Option<TDim>>`, along with a boolean to\ndenote if the tensor rank (its number of dimension) is known or not.\n\n## Fact inference, model pipeline and ops\n\nThe preferred way of running efficiently a network in tract is to make sure we\nget it to a TypedModel (a model which Fact is a TypedFact), and let tract-core\noptimise it.\n\nFor TensorFlow frozen networks, and to some extent for ONNX networks, the\nanalysis process will traverse the graph looking for incomplete shape or datum\ntype, and collaborate with the Op in the connecting nodes to \"infer\" more\ninformation about the wires facts.\n\nThis collaboration is handled through the `InferenceOp` trait. All Ops used in\nan InferenceModel must implement it. This is actually what the second type\nparameter (`O`) of the Graph structure represent.\n\ntract defines the `InferenceModel` type alias in hir/src/infer/mod.rs:\n\n```rust\npub type InferenceModel = Graph<InferenceFact, Box<dyn InferenceOp>>;\n```\n\nInferenceOp interface is relatively complex to implement, as the code needs to\nbe able to operate on partial information. Once a model is \"typed\" we no longer\nneed this complex logic, but can work with a stricter, but easier to implement\nvariant of the InferenceOp contract, the `TypedOp`.\n\nIn core/src/model/types, we define:\n\n```rust\npub type TypedModel = Graph<TypedFact, Box<dyn TypedOp>>;\n```\n\nThe `TypedOp` contract is simple: an operation, given the TypedFacts (datum \ntype, full shape, optional value) of all its input *must* be able to compute\nthe TypeFacts for its outputs.\n\nin core/srs/ops/mod.rs:\n\n```rust\ntrait TypedOp /* [...] */ {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<Vec<TypedFact>>;\n    /* [...] */\n}\n```\n\nFor most operations, implementing it is trivial. This simple contract is enough\nto build a graph from the Source inputs (assuming we know their Fact) to its\noutputs, discovering the full facts operation after operation.\n\nThe InferenceOp interface is a bit more involved, but allow to build the graph\nin any aritrary order, leaving some facts partially determined, then running\nthe analyse to complete the missing bits.\n\n```rust\ntrait InferenceOp /* [...] */ {\n    fn infer(\n        &mut self,\n        inputs: TVec<&InferenceFact>,\n        outputs: TVec<&InferenceFact>,\n    ) -> TractResult<(Vec<InferenceFact>, Vec<InferenceFact>)> {\n}\n```\n\nHere, the analyser will call the `infer` method providing all the known\ninformation accumulated, and the op must do its best to return more determined\nfacts. Note that in the case of `TypedOp`, the typing information strictly goes\nfrom inputs to outputs, whereas the `InferenceOp` allow operation to infer\ninputs facts from output facts. This allows the framework to infer factoids\nabout model inputs, which can be useful when you're handed over a model without\nexplicit information about the input structure.\n\n## EvalOp\n\nOf course, the purpose of all of this is ultimately to *compute*. Once tract\nhas make sense of a model, it will just have to call the `eval` method on\neach op in the graph in a determined order, keeping track of each tensor\nalready computed, handing them over to the successor ops.\n\n```rust\npub trait EvalOp {\n    fn eval(&self, inputs: Vec<Arc<Tensor>>) -> TractResult<Vec<Arc<Tensor>>>;\n    /* .. */\n}\n```\n"
  },
  {
    "path": "doc/intro.md",
    "content": "# Tract\n\ntract is a neural network inference library. It takes trained networks from higher-level\nframeworks (Tensorflow, PyTorch, etc.), converts them to an intermediate representation\nand runs them on the end-user data. It is designed to be very portable and embedding\nfriendly. We believe in running Neural Network Inference on the Edge, on a\nbrowser or a small embeddable CPU.\n\n## How to use tract ?\n\n* tract-onnx is a Rust library that can load and run an ONNX network. About 85%\n    of ONNX operators are supported.\n* tract-tensorflow is a Rust library that can load and run a TensorFlow 1\n    network. Because of the huge size of TensorFlow, a smaller portion of the\n    operator set is supported.\n* tract-nnef is a Rust lbrary that can load and run NNEF networks. Most of\n    NNEF is supported (missing deconv, ROI operations and quantization).\n* tract is the main command line interface (can be installed with \"cargo install\").\n    It can load network in any of the previously listed formats, dump them in a\n    user friendly form, bench and profile a network.\n    Additionaly, the tract command line can be used to convert a network to\n    NNEF (with some extensions). tract-nnef is significanly smaller and\n    lighter to start than tract-onnx or tract-tensorflow, so this conversion\n    is useful for embedded situations.\n\n## Crates\n\n### tract-data\n\nContains the Tensor struct, DatumType enum, and TDim (symbolic dimension\nvalue type).\n\n### tract-linalg\n\nIt is bit of a misnomer: this crate contains the low-level optimised\nroutines for fast computation (actually not restricted to LINear ALGebra).\nBeyond Intel, we payed specific attention to the ARM 6, 7 and 8 use\nplatforms. It is not meant to be used directly.\n\n### tract-core\n\nThe heart of tract. It contains\n\n    * the network graph representation manipulation (Graph, Node)\n    * the \"core\" operator set of tract\n    * most of the network optimisation logic.\n\ntract-core depends on tract-linalg only, and is usually not used directly.\n\n### tract-nnef\n\nIt support for NNEF format and maps its operator set to tract-core operators,\nIt also contains some tract-core proprietary extension to NNEF.\nThis crate depends on tract-core (thus tract-linalg transitively).\nIt is the entry-point for embedded situations where NNEF is preferred to ONNX\nor tensorflow formats (requiring model translation to NNEF before hand).\n\n### tract-hir\n\nPython-based training frameworks (TensorFlow or ONNX) have to support lots of\n\"python-isms\" or \"numpy-isms\". While they are helpful at model design time,\nthey can be a burden at inference time. As a consequence, we try to have most\nof them translated before getting into tract-core. This allow us to comply with\nONNX or TensorFlow semantics while keeping tract-core complexity more\nmanageable.\n\nExamples of such patterns are: negative indexing, negative rank indexing, rank\nbroadcasting.\n\nIt features the InferenceModel, InferenceFact (and friends), along with the\n\"analyser\" that can work from the partial types and shapes included in the\ntraining frameworks formats, to the stricter expectations of tract-core.\n\nIt also contains translation to tract-core logic for operators which have\nclose enough semantics between TensorFlow and ONNX.\n\nThis crate is not meant to be used directly.\n\n### tract-onnx and tract-onnx-opl\n\nSupport for ONNX protobuf format and mapping of ONNX operators to tract-hir,\ntract-core or ad-hoc operators.\n\ntract-onnx-opl depends only on tract-core and tract-nnef. It contains\noperators implementation from ONNX operators which do not have an equivalent\nin tract-core, including dumping to / loading from OPL.\n\ntract-onnx is the library to use to load and run an ONNX network. It uses\ntract-hir for type inference and translate ONNX operators to operators from\ntract-core and tract-onnx-opl.\n\n### tract-tensorflow\n\nSupport for TensorFlow 1 frozen model format, similar to the ONNX crates.\n\nNB: The split between tract-tensorflow (tensorflow parser, tensorflow operators\nmapping to core) and tract-tensorflow-opl (ad-hoc implementation of operators)\nhas not been done yet.\n\n### tract-pulse and tract-pulse-opl\n\nImplements translation of streaming networks to pulsing network (tract-pulse)\nincluding runtime support (ad-hoc operatrs in tract-pulse-opl).\n\n### tract-kaldi\n\nPartial support for kaldi framework model. Consider it very experimental, it\nmay disappear at any time.\n\n### tract\n\nIn the `cli/` sub-directory, implements the command line tool.\n\n## tract-OPL\n\nTract OPL (for Operation Programming Language) is an intermediate\nrepresentation of a Neural Network. It is based on NNEF. NNEF is a\nspecification aiming to be for *inference* applications what ONNX is\nto *training* frameworks. As it turns out, inference implementations and\ntraining frameworks have widely divergent objectives.\n\nTract can be used as a monolithic library, accepting an ONNX or\nTensorFlow model, loading it and optimising it on the fly (using\ntract-onnx API).\n\nWe have recently added support for (most of) NNEF. As this format is\ndesigned for inference, translating it to tract-core operator set is\nvery straightforward.\n\nWe have built tract OPL on top the tract NNEF support: we have extended\nNNEF to support tract operators that are not present in NNEF. The same\nextension mechanism can be used to extend NNEF with operators belonging\nto ONNX that we chose not to include in tract-core. That way it is\npossible to reduce runtime footprint and startup time:\n    * tract command line includes tract-onnx. It can be used to translate\nan onnx network to a tract-core-plus-extensions model in memory, then dump\nthis network in NNEF form. This is done once, right after training.\n    * At runtime we only to tract-core, tract-nnef (for the\nformat parser) and optionaly tract-onnx-opl if the network used one of the\nhandful of ONNX operations that are not supported natively by tract-core.\n\nThe split between translation time and runtime have also been done for the\nstreaming (aka pulse) capabilities. We only need tract-pulse to preprocess\nthe network (which we can do with the command line) but only ship\n`tract-pulse-opl`.\n\nIt could (and should) be done with tract-tensorflow too.\n\nNote that tract-OPL format is machine independant. We still need to call\ninto_optimized() on the loaded NNEF network to get the most efficient network\npossible, but this operation is actually much lighter than the \"decluttering\"\nof the network from the training formats to the tract-core/NNEF semantics.\n\nWe are playing with the idea of adding another similar split (tract R for\ntract Runtime). The machine optimized network form would be stored at this\ntime, shedding most of the optimisation code from tract-core and making\nnetworks even faster to load.\n"
  },
  {
    "path": "doc/kernel-notes.md",
    "content": "# Notes about implementing and working with the kernels\n\nKernels in tract-linalg are built using templated assembly and via `extern \"C\"` calling conventions.\n\nThe templates are stored in `linalg/$arch`, and in general the file\nand main entrypoint share name stem. However, the proc name has a\nsuffix based on the package version. In order to skip maintaining this\nthe `extern_kernel!` macro declares the matching function and\nre-exports it sans suffix.\n\nKernels work like a VM. When dispatching a kernel there's a list of\ninstructions from `FusedKerSpec` that's dispatched in a jump\ntable. For example; as of writing a MatMatmUl is roughly encoded as\n`[Clear, AddMatMul, Store, Done]`. The dispatch is called `non_linear_loop`.\n\nWhen iterating on assembly; building the code and looking at the\ngenerated assembly under\n`target/debug/build/tract-linalg-***/out/fma_mmm_*.S` can be much\neasier than tracking the flow through each macro.\n\nIf one needs to debug a kernel a useful workflow is to simply insert a\n`mov rNN, [0]` at the appropriate point, and configure GDB with\n`handle SIGSEGV stop nopass`. This'll pause in GDB but not send the\nsignal to the program.\n"
  },
  {
    "path": "doc/nnef/tract-core.nnef",
    "content": "# Extension `tract_core` exposes NNEF fragments for using\n# operator defined by tract-core crate.\n# \n# Add `extension tract_core` to `graph.nnef`\n\nfragment tract_core_round_even( x: tensor<scalar> ) -> (y: tensor<scalar>);\nfragment tract_core_erf( x: tensor<scalar> ) -> (y: tensor<scalar>);\nfragment tract_core_hard_swish( x: tensor<scalar> ) -> (y: tensor<scalar>);\nfragment tract_core_bitnot( x: tensor<scalar> ) -> (y: tensor<scalar>);\n\nfragment tract_core_argmax_reduce_last(\n    input: tensor<scalar>,\n    axes: integer[]\n) -> (output: tensor<scalar>);\n\nfragment tract_core_argmin_reduce_last(\n    input: tensor<scalar>,\n    axes: integer[]\n) -> (output: tensor<scalar>);\n\nfragment tract_core_broadcast(\n    input: tensor<scalar>,\n    shape: integer[]\n) -> (output: tensor<scalar>);\n\nfragment tract_core_cast(\n    input: tensor<scalar>,\n    to: string\n) -> (output: tensor<scalar>);\n\nfragment tract_core_downsample(\n    input: tensor<scalar>,\n    axis: integer,\n    stride: integer,\n    modulo: integer = 0\n) -> (output: tensor<scalar>);\n\nfragment tract_core_dyn_slice(\n    input: tensor<scalar>,\n    start: integer,\n    end: integer,\n    len: integer,\n    axis: integer\n) -> (output: tensor<scalar>);\n\nfragment tract_core_einsum(\n    inputs: tensor<scalar>[],\n    expr: string,\n    acc: string,\n    output: string = \"\"\n) -> (output: tensor<scalar>);\n\nfragment tract_core_einsum_q(\n    inputs: tensor<scalar>[],\n    expr: string,\n    acc: string,\n    output: string = \"\",\n    bias: tensor<scalar> = 0,\n    a0: tensor<integer>,\n    a_scale: tensor<scalar>,\n    b0: tensor<integer>,\n    b_scale: tensor<scalar>,\n    c0: tensor<integer>,\n    c_scale: tensor<scalar>\n) -> (output: tensor<scalar>);\n\nfragment tract_core_external(\n    datum_type: string,\n    shape: integer[]\n) -> (output: tensor<?>);\n\nfragment tract_core_fft(\n    input: tensor<scalar>,\n    axis: integer,\n    inverse: logical\n) -> (output: tensor<scalar>);\n\nfragment tract_core_force_eval(\n    inputs: tensor<scalar>[],\n    slots: integer[]\n) -> (output: tensor<scalar>);\n\nfragment tract_core_gather(\n    input: tensor<scalar>,\n    indices: tensor<scalar>,\n    axis: integer\n) -> (output: tensor<scalar>);\n\nfragment tract_core_gather_elements(\n    input: tensor<scalar>,\n    indices: tensor<scalar>,\n    axis: integer\n) -> (output: tensor<scalar>);\n\nfragment tract_core_gather_nd(\n    input: tensor<scalar>,\n    indices: tensor<scalar>,\n    batch_dims: integer\n) -> (output: tensor<scalar>);\n\nfragment tract_core_load(\n    input: tensor<scalar>[],\n    id: string\n) -> (output: tensor<scalar>);\n\nfragment tract_core_matmul(\n    A: tensor<scalar>,\n    B: tensor<scalar>,\n    axes: integer[]\n) -> (output: tensor<scalar>);\n\nfragment tract_core_one_hot(\n    input: tensor<scalar>,\n    axis: integer,\n    dim: integer,\n    value_off: scalar = 0.0,\n    value_on: scalar = 1.0\n) -> (output: tensor<scalar>);\n\nfragment tract_core_product_reduce(\n    input: tensor<scalar>,\n    axes: integer[]\n) -> (output: tensor<scalar>);\n\nfragment tract_core_qconv(\n    input: tensor<scalar>,\n    filter: tensor<scalar>,\n    bias: tensor<scalar> = 0,\n    group: integer,\n    dilation: integer[],\n    stride: integer[],\n    padding: integer[][],\n    border: string,\n    a0: integer,\n    a_scale: scalar,\n    b0: integer,\n    b_scale: scalar,\n    c0: integer,\n    c_scale: scalar\n) -> (output: tensor<scalar>);\n\nfragment tract_core_qmatmul(\n    A: tensor<scalar>,\n    B: tensor<scalar>,\n    bias: tensor<scalar> = 0,\n    axes: integer[],\n    a0: integer,\n    a_scale: scalar,\n    b0: integer,\n    b_scale: scalar,\n    c0: integer,\n    c_scale: scalar,\n    output_type: string\n) -> (output: tensor<scalar>);\n\nfragment tract_core_range(\n    start: integer,\n    end: integer,\n    step: integer\n) -> (output: tensor<scalar>);\n\nfragment tract_core_scan(\n    body: string,\n    scan: (string, tensor<scalar>, integer, integer)[],\n    full: (string, tensor<scalar>)[],\n    state: (string, tensor<scalar>, string)[],\n    output: (string, string, integer, integer)[],\n    skip: integer = 0,\n    reset_every_turn: integer = 0\n) -> (outputs: tensor<scalar>[]);\n\nfragment tract_core_scatter_elements(\n    input: tensor<scalar>,\n    indices: tensor<scalar>,\n    updates: tensor<scalar>,\n    axis: integer\n) -> (output: tensor<scalar>);\n\nfragment tract_core_scatter_nd(\n    input: tensor<scalar>,\n    indices: tensor<scalar>,\n    updates: tensor<scalar>\n) -> (output: tensor<scalar>);\n\nfragment tract_core_shape_of(\n    input: tensor<scalar>\n) -> (output: tensor<integer>);\n\nfragment tract_core_softmax(\n    x: tensor<scalar>,\n    axes: tensor<integer>,\n    exp: string\n) -> (output: tensor<scalar>);\n\nfragment tract_core_stft(\n    input: tensor<scalar>,\n    axis: integer,\n    frame: integer,\n    stride: integer,\n    window: tensor<scalar> = false\n) -> (output: tensor<scalar>);\n\nfragment tract_core_store(\n    input: tensor<scalar>,\n    state: tensor<scalar>,\n    id: string\n) -> (output: tensor<scalar>);\n\nfragment tract_core_submodel(\n    input: tensor<scalar>[],\n    label: string\n) -> (outputs: tensor<?>[]);\n\nfragment tract_core_topk(\n    input: tensor<scalar>,\n    k: tensor<integer>,\n    axis: integer,\n    largest: logical\n) -> (values: tensor<scalar>, indices: tensor<integer>);\n\nfragment tract_core_trilu(\n    input: tensor<scalar>,\n    k: tensor<integer>,\n    upper: logical\n) -> (output: tensor<scalar>);\n\n"
  },
  {
    "path": "doc/nnef/tract-onnx.nnef",
    "content": "# Extension `tract_onnx` extends NNEF for supporting some corner case ONNX operators.\n# \n# Add `extension tract_onnx` to `graph.nnef`\n\nfragment tract_onnx_is_nan( x: tensor<scalar> ) -> (y: tensor<scalar>);\n\nfragment tract_onnx_isinf(\n    input: tensor<scalar>,\n    detect_positive: logical = true,\n    detect_negative: logical = true\n) -> (output: tensor<?>)fragment tract_onnx_lrn(\n    input: tensor<scalar>,\n    alpha: scalar = 0.0001,\n    beta: scalar = 0.75,\n    bias: scalar = 1.0,\n    size: integer\n) -> (output: tensor<scalar>);\n\nfragment tract_onnx_ml_direct_lookup(\n    input: tensor<string>,\n    values: tensor<scalar>,\n    fallback: tensor<scalar>\n) -> (output: tensor<scalar>);\n\nfragment tract_onnx_ml_reverse_lookup(\n    input: tensor<scalar>,\n    keys: tensor<scalar>,\n    fallback: scalar\n) -> (output: tensor<scalar>);\n\nfragment tract_onnx_ml_tree_ensemble_classifier(\n    input: tensor<scalar>,\n    trees: tensor<scalar>,\n    nodes: tensor<scalar>,\n    leaves: tensor<scalar>,\n    max_used_feature: integer,\n    n_classes: integer,\n    aggregate_fn: string\n) -> (output: tensor<scalar>);\n\nfragment tract_onnx_multinomial(\n    input: tensor<integer>,\n    dtype: integer = 6,\n    sample_size: integer = 1,\n    seed: integer\n) -> (output: tensor<scalar>);\n\nfragment tract_onnx_non_max_suppression(\n    boxes: tensor<integer>,\n    scores: tensor<scalar>,\n    max_output_boxes_per_class: integer = 0,\n    iou_threshold: scalar = 0.0,\n    score_threshold: scalar,\n    center_point_box: integer = 0\n) -> (output: tensor<integer>);\n\nfragment tract_onnx_random(\n    datum_type: string,\n    shape: integer[],\n    dist: string,\n    parameters: scalar[],\n    seed: integer\n) -> (output: tensor<scalar>);\n\n"
  },
  {
    "path": "doc/nnef/tract-pulse.nnef",
    "content": "# Extension `tract_resource` extends NNEF with operators\n# for pulsified networks.\n# \n# Add `extension tract_pulse` to `graph.nnef`\n\n\nfragment tract_pulse_delay(\n    input: tensor<scalar>,\n    axis: integer,\n    delay: integer,\n    overlap: integer\n) -> (output: tensor<scalar>);\n\nfragment tract_pulse_mask(\n    input: tensor<scalar>,\n    axis: integer,\n    begin: integer,\n    end: integer,\n    value: scalar\n) -> (output: tensor<scalar>);\n\nfragment tract_pulse_pulse_pad(\n    input: tensor<scalar>,\n    axis: integer,\n    before: integer,\n    after: integer,\n    begin_input: integer,\n    end_input: integer,\n    border: string,\n    value: scalar,\n    overlap: integer\n) -> (output: tensor<scalar>);\n\n"
  },
  {
    "path": "doc/nnef/tract-resource.nnef",
    "content": "# Extension `tract_resource` exposes NNEF fragments for accessing\n# resources files in NNEF folder or archive.\n# \n# Add `extension tract_resource` to `graph.nnef`\n\n\n# Access embedded resource by key\nfragment tract_resource_get(\n    # Resource label to access\n    label: string,\n    # Key path in resource\n    key: string\n) -> (output: tensor<?>);\n\n"
  },
  {
    "path": "doc/op.md",
    "content": "# Anatomy of an Op\n\nOperators, Op for short are central players in tract. They are of course\nresponsible for doing the actual computations and transformations on the\ntensor, but also must collaborate with the loading and optimisation\nframework, and sometimes also with their peers to analyse, validate or reduce\nthe model to a simple form.\n\nThese tasks are varying widely in complexity depending on the actual op, and\nhow much effort have been put into optimising them.\n\ntract defines, in `core` and `hir` a few traits that an operation can or must\nimplement.\n\n## tract-core Op trait\n\nThis trait contains the minimal metadata that all ops will share.\n\n```rust\npub trait Op:\n    fmt::Debug + dyn_clone::DynClone + Send + Sync + 'static + Downcast + EvalOp + DynHash\n{\n    fn name(&self) -> StaticName;\n\n    fn validation(&self) -> Validation {\n        Validation::Accurate\n    }\n\n    fn same_as(&self, _other: &dyn Op) -> bool {\n        false\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![])\n    }\n\n    fn as_typed(&self) -> Option<&dyn TypedOp>;\n}\n```\n\n`name()` and `info()` are mostly useful for debugging and\nauditing (the command line interface and error messages will use these entry\npoints).\n\n`validation` is a hint, for debugging and auditing purposed that this\noperator is likely to generates rounding errors. If we run network side to side\nwith a reference implementation (TensorFlow for example) do we expect the\nresults to be exactly the same, to the last bit, or should we use a more\nlenient way or comparing the results.\n\nWe rely a lot on Rust's *trait object* mecanism around Ops. It can be\nsurprising to the new comer that things such as Clone become complicated in the\ncontext of trait objects, but their are a few crates that offer workaround:\ntract Op leverage `dyn_clone` crate functionality, mimicks it with the DynHash\ntrait and macros to be able to get a hash of an `&dyn Op`. `same_as` is another\nworkaround (maybe a missed opportunity for a DynHash-like mecanism actualy)\nas PartialEq is incompatible with trait objects.\n\n`as_typed()` allows the framework to dynamicaly ask the Op to cast itself to\n&TypedOp. rust offers no \"QueryInterface\"-like mecanism to switch from trait to\ntrait in a family of trait, so there are a few casting methods here and there\nin tract op traits. Most of them are implemented using trivial functions\n(`return Some(&self);`) that we generate with macros.\n\nWe also need `Op` to implement Downcast. There are actually quite a few\nsituations in tract where we need to Downcast, like implementing `same_as()`.\nAs we said, Op pushes Rust dynamic typing where it does not like to go :)\n\n## tract-core EvalOp trait\n\nWhile Op is mostly metadata, EvalOp is at the other end of spectrum with the\nbusiness side of things.\n\n```rust\npub trait EvalOp {\n     fn eval(&self, inputs: TVec<Arc<Tensor>>) -> TractResult<TVec<Arc<Tensor>>> {\n         bail!(\"stateless evaluation not implemented\")\n     }\n\n     fn state(\n         &self,\n         session: &mut SessionState,\n         node_id: usize,\n     ) -> TractResult<Option<Box<dyn OpState>>> {\n         Ok(None)\n     }\n\n     fn is_stateless(&self) -> bool;\n}\n```\n\nThe EvalOp realize the actual computation the Operator is supposed to perform. It\nsupports both *stateful* and *stateless* operators. Most of them are stateless:\nthey should just implement `eval` method and say so in `is_stateless()`. The\nhandful of stateful operators will implements `state()` instead and return\n`false` is is_stateless: the framework will call `state()` during the network\ninitialization, then will call `eval()` on the obtained `OpState` instead:\n\n```rust\npub trait OpState: fmt::Debug + Send + dyn_clone::DynClone {\n    fn eval(\n        &mut self,\n        session: &mut SessionState,\n        op: &dyn Op,\n        inputs: TVec<Arc<Tensor>>,\n    ) -> TractResult<TVec<Arc<Tensor>>>;\n}\n```\n\nHere the eval implementation is free to mute some operation internal state if\nrequired, or access the `SessionState`.\n\nBut most operators are stateless anyway.\n\n## tract-core TypedOp trait\n\n`Op` is metadata, `EvalOp` is runtime, `TypedOp` is about reasoning on the\nmodel. Most optimisations and transformations will operate on TypedOp\nimplementors. TypedOp has a minimal handful of methods that are required to be\nimplemented and many optional. While they are not strictly required, a missing,\nor partial implementation may prevent the optimiser to perform optimisations\nthat require an Op to \"collaborate\" with its peers.\n\n```rust\npub trait TypedOp:\n    Op + fmt::Debug + dyn_clone::DynClone + Send + Sync + 'static + Downcast + EvalOp + DynHash\n{\n    fn as_op(&self) -> &dyn Op;\n    fn as_op_mut(&mut self) -> &mut dyn Op;\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>>;\n\n    /*[...]*/\n}\n```\n\nFirst we have two more cross-trait casting methods. Once again, we have macros\nthat do a trivial return self.\n\nThen comes `output_facts()`. This is a pivotal method in TypedOp. As discussed\nin the [graph write-up](graph.md) it is the lighter version of the type\ninference, just enough to build networks from source to outputs while\nmaintaining known shapes and data types.\n\nWhen building a `TypedModel`, we repeatedly create new `TypedOp`s, retrieving\nfrom the network the `TypedFact` for their inputs.  Then output_facts() is\ncalled, and the TypedOp must return the output facts.  The framework can then\ncreate the `TypedNode` and append it to the partial TypedModel before repeating\nthe process. This also makes output_fact() a great place to run as many\nvalidation checks and detect inconsistency soon instead of discovering them at\nruntime (in eval).\n\n## trait-hir InferenceOp trait\n\nSitting between the tract-core and the training frameworks loaders, tract-hir\ncontains everything needed to translate networks from ONNX or TensorFlow to\ntract-core. Tract-hir role is to map the training framworks' very expressive\noperators set to the more constrained tract-core. In order to perform this\ntranslation, the partial type information contained in ONNX or TensorFlow\nprotobuf files must be used to infer the full types and shapes of the network.\n\nThe contract here is significantly more complex than the one required by\nTypedOp.\n\n```rust\npub trait InferenceOp:\n    Op + fmt::Debug + tract_core::dyn_clone::DynClone + Send + Sync + 'static + Downcast + EvalOp + DynHash\n{\n    fn infer(\n        &mut self,\n        inputs: TVec<&InferenceFact>,\n        outputs: TVec<&InferenceFact>,\n        observed: TVec<&InferenceFact>,\n    ) -> TractResult<(TVec<InferenceFact>, TVec<InferenceFact>, TVec<InferenceFact>)>;\n    /*[...]*/\n\n    fn to_typed(\n        &self,\n        source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n  ) -> TractResult<TVec<OutletId>>;\n}\n```\n\nThe InferenceFact is a partially determined version of TypedFact: typically,\nTypedFact has a DatumType field to represent the type of the tensor element,\nwhile InferenceFact datum_type is akin to an `Option<DatumType>` where `None`\nmeans unknown. In a similar fashion, the shape can be partially known, with\nrank determined or not and individual dimensions known or not.\n\nThe framework will try to propagate type information accross the graph,\nrefining incrementally its knowledge of all the inference facts. It will do\nso by calling the `infer()` method on operators which interfaces are not fully\ndetermined. The operator receives as paramaters the current information on its\ninputs and outputs, try to improve them and returns the refined versions. The\nthird paramaters and result (`observed`) is out of scope here.\n\nOnce a network has been entirely typed, it can be translated to a TypedOp. The\nframework will visit the entire network and call the `to_typed()` method on\neach operator. The operator is responsible to \"wire\" itself into the target\nTypedModel (creating a node) and return its output(s) wires.\n\n## trait-hir InferenceRulesOp trait\n\nImplementing the `infer()` method by hand is tedious, mostly because\ntype information constraint can flow in both directions, from an input to\noutput, but also the other way around, or even between two inputs. We\ndevelopped an alternative, which makes the process more declarative. It is a\nsyntax heavy, but less error-prone once rustc is happy.\n\n```rust\ntrait InferenceOp {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()>;\n    /*[...]*/\n}\n```\n\nHere, we can use a more declarative approach and load the \"solver\" with rules\nspecifying relations between inputs and outputs. This make it possible to\npropagate information in all directions with one single rule.\n\nThe rules solver syntax is a bit old and arcane, and could certainly be\nimproved or simplified, but it is still much easier to use than writing rules\nby hand.\n\n## Loading from the frameworks\n\nTraining frameworks (TensorFlow and Onnx) use protobuf as a serialization\nformat. tract-tensorflow (and tract-onnx) can read these and build the neural \nnetwork as an InferenceModel in memory. When the framework parses a node, the\noperation type is manifested by its name. Then the way to interpret and plug-in\nthe various attribute depends on the Operator itself.\n\nWhen loading the framework object, tract builds a mapping of\noperator names to operator constructor functions that is responsible for\nextracting the attributes from the parsed protobuf.\n\nModules containing operators typically expose a register_all_ops function that\nfeeds this map. Here is an example from `onnx/src/ops/nn/mod.rs`.\n\n```rust\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"ArgMax\", arg_max_min);\n    reg.insert(\"ArgMin\", arg_max_min);\n    reg.insert(\"AveragePool\", average_pool);\n    reg.insert(\"BatchNormalization\", batch_normalization);\n    /* [...] */\n}\n```\n\nThe `batch_normalization` function looks like this:\n\n```rust\npub fn batch_normalization(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let epsilon = node.get_attr_opt(\"epsilon\")?.unwrap_or(1e-5);\n    let spatial = node.get_attr_opt(\"spatial\")?.unwrap_or(1);\n    if spatial != 1 {\n        bail!(\"BatchNormalization: attribute 'spatial' is not supported (deprecated by ONNX operator set 9)\")\n    }\n    Ok((expand(batch_norm::BatchNorm::new(nn::DataFormat::NCHW, epsilon, spatial != 0)), vec![]))\n}\n```\n\nIt is given the protobuf parsed Node, and extracts two attributes from it.\nThen it buidls an actual operation (as an Expansion, more on this later).\n\n## Dumping as OPL, loading from OPL\n\n\"Good citizen\" operators know how to dump themselves in OPL, and load from\nthem.\n\nOPL is tract NNEF-based format. Some operators are compatible with NNEF, in\nwhich case they can use NNEF standard form, but many operators from ONNX and\nTensorFlow can only be handled with extensions.\n\nNNEF compatible operators are dumped and loaded by code in \n`nnef/src/ops/nnef/mod.rs`.\n\nEach OPL module (`nnef`, `pulse-opl`, `onnx-opl`) defines a registry of\noperators, containing both OPL loaders and OPL dumpers.\n\n```rust\npub struct Registry {\n    pub id: String,\n    pub fragments: HashMap<String, FragmentDef>,\n    pub primitives: HashMap<String, (Vec<ast::Parameter>, ToTract)>,\n    pub unit_element_wise_ops: Vec<(String, Box<dyn ElementWiseMiniOp>)>,\n    pub element_wise_ops: Vec<(String, TypeId, FromTract, Vec<ast::Parameter>, ToTract)>,\n    pub binary_ops: Vec<(String, Box<dyn BinMiniOp>)>,\n    pub from_tract: HashMap<TypeId, FromTract>,\n}\npub type ToTract = fn(&mut ModelBuilder, &ResolvedInvocation) -> TractResult<TVec<OutletId>>;\npub type FromTract = fn(&mut IntoAst, node: &TypedNode) -> TractResult<Option<Arc<RValue>>>;\n```\n\nThe generic dumping mecanism relies on `from_tract` HashMap: it maps a rust\nTypeId (the one for the TypedOp we need to dump) to a FromTract dumping\nfunction. The mutable IntoAst is modified by the callback to store the\nrepresentation of the Op. The callback can add NNEF fragments (NNEF lingo for\nfunctions) to the NNEF document but its main responsibility is to translate \nthe node and its op to some NNEF ast nodes.\n\n## Expansions, and rules wrapper\n"
  },
  {
    "path": "examples/.gitignore",
    "content": "*.onnx\n*.nnef.tgz\n*.tar.gz\n"
  },
  {
    "path": "examples/causal_llm/Cargo.toml",
    "content": "[package]\nname = \"causal_llm\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nfloat-ord = \"0.3.2\"\ntract.workspace = true\naxum = \"0.8.4\"\ntokio = { version = \"1.47.1\", features = [\"rt-multi-thread\"] }\nclap = { version = \"4\", features = [\"derive\"] }\naxum-macros = \"0.5.0\"\nreqwest.workspace = true\nserde.workspace = true\nserde_json.workspace = true\nlog.workspace = true\nenv_logger.workspace = true\nndarray-npy.workspace = true\ntokio-scoped = \"0.2.0\"\nrand.workspace = true\n\n\n# tokenizer special handle for wasm\n[target.'cfg(not(target_arch = \"wasm32\"))'.dependencies]\ntokenizers.workspace = true\n\n[target.'cfg(target_arch = \"wasm32\")'.dependencies]\ntokenizers = { version = \"0.22\", default-features = false, features = [\n  \"unstable_wasm\",\n] }\n"
  },
  {
    "path": "examples/causal_llm/README.md",
    "content": "# causal_llm\n\nA causal language model inference example using tract. Loads an NNEF model and a HuggingFace tokenizer to run text completion, with optional OpenAI-compatible HTTP serving.\n\n## Binaries\n\n- **complete** — Generate text from a prompt on the command line.\n- **serve** — OpenAI-compatible completion server on `http://0.0.0.0:3000/v1/completions`.\n- **client** — Send completion requests to the server.\n\n## Usage\n\nYou need a tokenizer (`tokenizer.json`) and an NNEF model (`.nnef.tgz`).\n\n### Obtaining a model\n\n```sh\n# NNEF model (Qwen3-1.7B, 4-bit quantized)\nwget https://s3.amazonaws.com/tract-ci-builds/tests/llm/541/Qwen--Qwen3-1.7B-q40ef16/Qwen--Qwen3-1.7B-q40ef16.nnef.tgz\n# Tokenizer\nwget https://huggingface.co/Qwen/Qwen3-1.7B/resolve/main/tokenizer.json\n```\n\n### Text completion\n\n```sh\ncargo run --bin complete --release -- \\\n    -t path/to/tokenizer.json \\\n    -m path/to/model.nnef.tgz \\\n    -n 50 \\\n    \"The capital of France is\"\n```\n\n### Completion server\n\n```sh\ncargo run --bin serve --release -- \\\n    -t path/to/tokenizer.json \\\n    -m path/to/model.nnef.tgz\n```\n\nThen query it:\n\n```sh\ncargo run --bin client --release -- \"The capital of France is\"\n```\n\n## Options\n\n- `-n <N>` — Number of tokens to generate (default: 20)\n- `--force-cpu` — Disable CUDA/Metal, run on CPU only\n\n## Library\n\nThe `CausalLlmModel` struct can be used as a library. It handles KV cache detection, runtime selection (CUDA > Metal > CPU), and prompt chunking automatically.\n\n```rust\nlet llm = CausalLlmModel::from_paths(\"tokenizer.json\", \"model.nnef.tgz\")?;\nlet mut state = llm.spawn()?;\nstate.append_text(\"Hello world\")?;\nfor _ in 0..20 {\n    state.generate_next_token()?;\n}\n```\n"
  },
  {
    "path": "examples/causal_llm/ci.sh",
    "content": "#!/bin/bash\n\nset -e\nset -o pipefail\nset -x\n\nwget https://s3.amazonaws.com/tract-ci-builds/tests/llm/541/Qwen--Qwen3-1.7B-q40ef16/Qwen--Qwen3-1.7B-q40ef16.nnef.tgz\nwget https://huggingface.co/Qwen/Qwen3-1.7B/resolve/main/tokenizer.json\n\nOUTPUT=$(cargo run -p causal_llm --bin complete --profile opt-no-lto -- \\\n    -t \"tokenizer.json\" -m Qwen--Qwen3-1.7B-q40ef16.nnef.tgz -n 20 \"The capital of France is\")\n\necho \"Output: $OUTPUT\"\n\nif [ -z \"$OUTPUT\" ]\nthen\n    echo \"ERROR: empty output\"\n    exit 1\nfi\n"
  },
  {
    "path": "examples/causal_llm/scripts/generate_ci_llm_assets.sh",
    "content": "#!/bin/bash\n\nset -ex\n\ntmp=$(echo ${TMPDIR:-${TEMP:-${TMP:-/tmp}}})\nvenv=$tmp/venv-for-tract-assets\n\nif [ ! -d $venv ]\nthen\n    virtualenv -p python3.13 $venv\n    source $venv/bin/activate\n    pip install 'torch-to-nnef[llm-tract]>=0.20.0' 'transformers>=4.51,<4.52'\nelse\n    source $venv/bin/activate\nfi\n\nMODELS=\nMODELS=\"$MODELS apple/OpenELM-270M\"\nMODELS=\"$MODELS meta-llama/Llama-3.2-1B-Instruct\"\nMODELS=\"$MODELS meta-llama/Llama-3.2-3B-Instruct\"\nMODELS=\"$MODELS meta-llama/Llama-3.1-8B-Instruct\"\nMODELS=\"$MODELS Qwen/Qwen2.5-7B-Instruct\"\nMODELS=\"$MODELS Qwen/Qwen3-1.7B\"\nMODELS=\"$MODELS Qwen/Qwen3-8B\"\n\nMODELS_F32_ALLOWED=\"meta-llama/Llama-3.2-1B-Instruct\"\n\nVARIANTS=\"f32f32 f16f16 q40ef16 \"\n\nfor hf_id in $MODELS\ndo\n    local_id=$(echo $hf_id | sed \"s/\\//--/g\")\n\n    for q in $VARIANTS\n    do\n\t if [ \"$q\" = \"f32f32\" ] && ! echo \"$MODELS_F32_ALLOWED\" | grep -q -w \"$hf_id\"; then\n         echo \"INFO: Skipping f32f32 for model $hf_id\"\n            continue\n         fi\n         model=$local_id-$q\n         rm -rf $model\n         case $q in\n             f32f32) EXPORT_ARGS= ;;\n             f16f16) EXPORT_ARGS=\"-dt f16\" ;;\n             q40f32) EXPORT_ARGS=\"-c min_max_q4_0\" ;;\n             q40ef32) EXPORT_ARGS=\"-c min_max_q4_0_with_embeddings\" ;;\n             q40ef16) EXPORT_ARGS=\"-c min_max_q4_0_with_embeddings -dt f16\" ;;\n         esac\n\n         t2n_export_llm_to_tract \\\n             --sample-generation-total-size 100 \\\n             --no-verify \\\n             -s $hf_id -e $model $EXPORT_ARGS \\\n\t     --reify-sdpa-operator\n\n          aws s3 cp $model/model/model.nnef.tgz s3://tract-ci-builds/tests/llm/current/$model/$model.nnef.tgz\n          aws s3 cp $model/tests/prompt_io.npz s3://tract-ci-builds/tests/llm/current/$model/$model.p0s100.io.npz\n          aws s3 cp $model/tests/text_generation_io.npz s3://tract-ci-builds/tests/llm/current/$model/$model.p99s1.io.npz\n          if [ -e $model/tests/prompt_with_past_io.npz ]\n          then\n            aws s3 cp $model/tests/prompt_with_past_io.npz s3://tract-ci-builds/tests/llm/current/$model/$model.p50s50.io.npz\n          fi\n    done\n\ndone\n"
  },
  {
    "path": "examples/causal_llm/src/bin/client.rs",
    "content": "use std::io::Write;\nuse std::sync::Arc;\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::time::{Duration, Instant};\n\nuse anyhow::{Result, bail};\nuse clap::Parser;\nuse reqwest::Client;\nuse tokenizers::Tokenizer;\nuse tokio::sync::broadcast::Receiver;\n\n#[allow(dead_code)]\nmod common;\nuse common::*;\n\n#[derive(Parser, Debug)]\nstruct Args {\n    /// tokenizer to use for token counts\n    #[arg(long, short, required(true))]\n    tokenizers: String,\n\n    /// /completions endpoint to bench\n    #[arg(long, default_value = \"http://localhost:3000/v1/completions\")]\n    endpoint: String,\n\n    /// model id\n    #[arg(long, default_value = \"meta-llama/Llama-3.1-8B\")]\n    model: String,\n\n    #[command(subcommand)]\n    command: Commands,\n}\n\n#[derive(Debug, Clone)]\nenum Api {\n    OpenAICompletions(Client, String),\n    OllamaGenerate(Client, String),\n}\n\nstruct GenericCompletion {\n    text: String,\n    generated_tokens: usize,\n    prompt_tokens: usize,\n}\n\nimpl Api {\n    fn new(endpoint: &str) -> Api {\n        if endpoint.ends_with(\"generate\") {\n            Api::OllamaGenerate(Client::new(), endpoint.to_string())\n        } else {\n            Api::OpenAICompletions(Client::new(), endpoint.to_string())\n        }\n    }\n\n    async fn generate(\n        &self,\n        model: &str,\n        prompt: impl Into<String>,\n        max_tokens: usize,\n    ) -> Result<GenericCompletion> {\n        match &self {\n            Api::OpenAICompletions(client, endpoint) => {\n                let query = OpenAICompletionQuery {\n                    prompt: prompt.into(),\n                    model: model.to_string(),\n                    max_tokens,\n                    stop: vec![],\n                };\n                let response = client\n                    .post(endpoint)\n                    .body(serde_json::to_string(&query)?)\n                    .header(\"content-type\", \"application/json\")\n                    .send()\n                    .await?;\n                if response.status().is_success() {\n                    let it: OpenAICompletionReply = serde_json::from_str(&response.text().await?)?;\n                    Ok(GenericCompletion {\n                        text: it.choices[0].text.clone(),\n                        generated_tokens: it.usage.completion_tokens,\n                        prompt_tokens: it.usage.prompt_tokens,\n                    })\n                } else {\n                    let error = response.text().await.unwrap();\n                    anyhow::bail!(error)\n                }\n            }\n            Api::OllamaGenerate(client, endpoint) => {\n                let query = OllamaCompletionQuery {\n                    prompt: prompt.into(),\n                    model: model.to_string(),\n                    options: OllamaCompletionOptions { num_predict: max_tokens },\n                };\n\n                let response = client\n                    .post(endpoint)\n                    .body(serde_json::to_string(&query)?)\n                    .header(\"content-type\", \"application/json\")\n                    .send()\n                    .await?;\n\n                if response.status().is_success() {\n                    todo!()\n                    // let it: Oll = serde_json::from_str(response.text().awai?)?;\n                    // Ok(GenericCompletion {\n                    //     text: it.choices[0].text,\n                    //     generated_tokens: it.usage.completion_tokens,\n                    //     prompt_tokens: it.usage.prompt_tokens,\n                    // })\n                } else {\n                    let error = response.text().await.unwrap();\n                    anyhow::bail!(error)\n                }\n            }\n        }\n    }\n}\n\n#[derive(Parser, Debug)]\nenum Commands {\n    GenerateBench(GenerateBenchArgs),\n    Stress(StressArgs),\n    Scalability(ScalabilityArgs),\n    Complete(CompleteArgs),\n    Prompts(PromptsArgs),\n}\n\n#[derive(Parser, Debug)]\nstruct GenerateBenchArgs {\n    #[structopt(value_delimiter = ',')]\n    pp: Vec<usize>,\n    #[structopt(value_delimiter = ',')]\n    tg: Vec<usize>,\n    #[structopt(long)]\n    time: Option<f32>,\n}\n\nimpl GenerateBenchArgs {\n    async fn handle(&self, clients: &Clients) -> Result<()> {\n        for tg in &self.tg {\n            print!(\"\\t{tg}\");\n        }\n        println!();\n        for &pp in &self.pp {\n            print!(\"{pp}\");\n            for &tg in &self.tg {\n                let dur = self.run_one(clients, pp, tg).await?;\n                print!(\"\\t{:.3}\", dur.as_secs_f32());\n                let _ = std::io::stdout().flush();\n            }\n            println!();\n        }\n        Ok(())\n    }\n\n    async fn run_one(&self, clients: &Clients, pp: usize, tg: usize) -> Result<Duration> {\n        // this is wall clock to stop the benching loop\n        let start = Instant::now();\n        // this will measure duration for successul runs  only\n        let mut duration: Duration = Default::default();\n        for ran in 1.. {\n            duration += clients.bench_one_generate(pp, tg).await?;\n            if !self.time.is_some_and(|limit| start.elapsed().as_secs_f32() < limit) {\n                return Ok(duration / ran);\n            }\n        }\n        unreachable!()\n    }\n}\n\n#[derive(Parser, Debug)]\nstruct StressArgs {\n    concurrency: usize,\n    avg_pp: usize,\n    avg_tg: usize,\n    /// do not report aggregated performance\n    #[arg(long)]\n    quiet: bool,\n}\n\nimpl StressArgs {\n    async fn handle(&self, clients: &Clients) -> Result<()> {\n        self.stress(clients, None).await\n    }\n\n    async fn stress(&self, clients: &Clients, keepalive: Option<Receiver<()>>) -> Result<()> {\n        use Ordering::Relaxed;\n        let total_pp = Arc::new(AtomicUsize::default());\n        let total_tg = Arc::new(AtomicUsize::default());\n        let report = Duration::from_secs(10);\n        tokio_scoped::scope(|scope| {\n            if !self.quiet {\n                scope.spawn(async {\n                    let mut interval = tokio::time::interval(report);\n                    loop {\n                        interval.tick().await;\n                        eprintln!(\n                            \"PP:{:.0}/s TG:{:.0}/s\",\n                            total_pp.swap(0, Relaxed) as f32 / report.as_secs_f32(),\n                            total_tg.swap(0, Relaxed) as f32 / report.as_secs_f32(),\n                        );\n                        if let Some(keepalive) = &keepalive {\n                            if keepalive.is_closed() {\n                                break;\n                            }\n                        }\n                    }\n                });\n            }\n            for _ in 0..self.concurrency {\n                scope.spawn(async {\n                    loop {\n                        let pp =\n                            ((self.avg_pp as f32) * (0.8 + 0.4 * rand::random::<f32>())) as usize;\n                        let tg = (((self.avg_tg as f32) * (0.8 + 0.4 * rand::random::<f32>()))\n                            as usize)\n                            .max(1);\n                        if let Ok(it) = clients.run_one_generate(pp, tg).await {\n                            total_pp.fetch_add(it.prompt_tokens, Relaxed);\n                            total_tg.fetch_add(it.generated_tokens, Relaxed);\n                        }\n                        if let Some(keepalive) = &keepalive {\n                            if keepalive.is_closed() {\n                                break;\n                            }\n                        }\n                    }\n                });\n            }\n        });\n        Ok(())\n    }\n}\n\nfn clear_terminal_line() {\n    eprint!(\"\\x1b[2K\\r\");\n}\n\n#[derive(Parser, Debug)]\nstruct ScalabilityArgs {\n    avg_pp: usize,\n    avg_tg: usize,\n    #[arg(value_delimiter = ',')]\n    concurrency: Vec<usize>,\n}\n\nimpl ScalabilityArgs {\n    async fn handle(&self, clients: &Clients) -> Result<()> {\n        eprint!(\"Server and model warmup...\");\n        let bench =\n            GenerateBenchArgs { pp: vec![self.avg_pp], tg: vec![self.avg_tg], time: Some(10.0) };\n        let _dur = bench.run_one(clients, self.avg_pp, self.avg_tg).await?;\n        clear_terminal_line();\n        for &concurrency in &self.concurrency {\n            tokio_scoped::scope(|scope| {\n                eprint!(\"Starting stress loading with {concurrency} and warming up...\");\n                let (tx, rx) = tokio::sync::broadcast::channel::<()>(1);\n                let stress = StressArgs {\n                    quiet: true,\n                    concurrency,\n                    avg_pp: self.avg_pp,\n                    avg_tg: self.avg_tg,\n                };\n                scope.spawn(async move {\n                    let _ = stress.stress(clients, Some(rx)).await;\n                });\n                scope.spawn(async move {\n                    tokio::time::sleep(Duration::from_secs(5)).await;\n                    clear_terminal_line();\n                    eprint!(\"Running actual bench\");\n                    let bench = GenerateBenchArgs {\n                        pp: vec![self.avg_pp],\n                        tg: vec![self.avg_tg],\n                        time: Some(10.0),\n                    };\n                    let dur = bench.run_one(clients, self.avg_pp, self.avg_tg).await.unwrap();\n                    clear_terminal_line();\n                    println!(\"{concurrency} {:.3}\", dur.as_secs_f32());\n                    std::mem::drop(tx);\n                });\n            })\n        }\n        Ok(())\n    }\n}\n\n#[derive(Parser, Debug)]\nstruct CompleteArgs {\n    prompt: String,\n    #[arg(short('n'), default_value = \"50\")]\n    max_tokens: usize,\n}\n\nimpl CompleteArgs {\n    async fn handle(&self, clients: &Clients) -> Result<()> {\n        let reply = clients.complete(&self.prompt, self.max_tokens).await?;\n        println!(\"{}\", reply.text);\n        eprintln!(\"prompt:{:?} generated:{}\", reply.prompt_tokens, reply.generated_tokens);\n        Ok(())\n    }\n}\n\n#[derive(Parser, Debug)]\nstruct PromptsArgs {\n    #[arg(short, long, default_value = \"50\")]\n    len: usize,\n\n    #[arg(short, long, default_value = \"50\")]\n    count: usize,\n}\n\nimpl PromptsArgs {\n    async fn handle(&self, clients: &Clients) -> Result<()> {\n        for _ in 0..self.count {\n            let prompt = clients.get_one_prompt(self.len);\n            println!(\"{}\", prompt.replace(\"\\n\", \" \"));\n        }\n        Ok(())\n    }\n}\n\nimpl Commands {\n    async fn run(&self, clients: &Clients) -> Result<()> {\n        match self {\n            Self::GenerateBench(args) => args.handle(clients).await?,\n            Self::Stress(args) => args.handle(clients).await?,\n            Self::Scalability(args) => args.handle(clients).await?,\n            Self::Complete(args) => args.handle(clients).await?,\n            Self::Prompts(args) => args.handle(clients).await?,\n        }\n        Ok(())\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct Clients {\n    model: String,\n    tokens: Vec<u32>,\n    tokenizer: Tokenizer,\n    api: Api,\n}\n\nimpl Clients {\n    fn from_args(args: &Args) -> Self {\n        let tokenizer = Tokenizer::from_file(&args.tokenizers).unwrap();\n        const BASE_TEXT: &str = include_str!(\"../lib.rs\");\n        let tokens: Vec<u32> = tokenizer.encode_fast(BASE_TEXT, true).unwrap().get_ids().into();\n        let api = Api::new(&args.endpoint);\n        Clients { api, model: args.model.clone(), tokens, tokenizer }\n    }\n    fn get_one_prompt(&self, len: usize) -> String {\n        assert!(len < self.tokens.len());\n        let start: usize = rand::random::<u32>() as usize % (self.tokens.len() - len);\n        self.tokenizer.decode(&self.tokens[start..][..len.saturating_sub(1)], true).unwrap()\n    }\n\n    async fn run_one_generate(&self, pp: usize, tg: usize) -> Result<GenericCompletion> {\n        self.api.generate(&self.model, &self.get_one_prompt(pp), tg).await\n    }\n\n    async fn complete(\n        &self,\n        prompt: impl Into<String>,\n        max_tokens: usize,\n    ) -> Result<GenericCompletion> {\n        self.api.generate(&self.model, prompt.into(), max_tokens).await\n    }\n\n    async fn bench_one_generate(&self, pp: usize, tg: usize) -> Result<Duration> {\n        for _attempt in 0..10 {\n            let start = Instant::now();\n            let it = self.run_one_generate(pp, tg).await?;\n            if it.generated_tokens == tg {\n                return Ok(start.elapsed());\n            };\n        }\n        bail!(\"Failed to obtain enough tokens from generate after multiple attempts\")\n    }\n}\n\n#[tokio::main(flavor = \"multi_thread\", worker_threads = 8)]\nasync fn main() -> anyhow::Result<()> {\n    let cli = Args::parse();\n    let clients = Clients::from_args(&cli);\n\n    let start = Instant::now();\n    cli.command.run(&clients).await?;\n\n    let total = start.elapsed();\n    dbg!(total);\n    Ok(())\n}\n"
  },
  {
    "path": "examples/causal_llm/src/bin/common/mod.rs",
    "content": "use serde::{Deserialize, Serialize};\n\n#[derive(Deserialize, Serialize, Debug, Clone)]\npub struct OpenAICompletionQuery {\n    pub prompt: String,\n    pub model: String,\n    pub max_tokens: usize,\n    pub stop: Vec<String>,\n}\n\n#[derive(Deserialize, Serialize, Debug, Clone)]\npub struct OpenAICompletionReply {\n    pub id: String,\n    pub choices: Vec<OpenAICompletionReplyChoice>,\n    pub usage: OpenAICompletionReplyUsage,\n}\n\n#[derive(Deserialize, Serialize, Debug, Clone)]\npub struct OpenAICompletionReplyChoice {\n    pub text: String,\n}\n\n#[derive(Deserialize, Serialize, Debug, Clone)]\npub struct OpenAICompletionReplyUsage {\n    pub prompt_tokens: usize,\n    pub total_tokens: usize,\n    pub completion_tokens: usize,\n}\n\n#[derive(Serialize, Debug, Clone)]\npub struct OllamaCompletionQuery {\n    pub prompt: String,\n    pub model: String,\n    pub options: OllamaCompletionOptions,\n}\n\n#[derive(Serialize, Debug, Clone)]\npub struct OllamaCompletionOptions {\n    pub num_predict: usize,\n}\n"
  },
  {
    "path": "examples/causal_llm/src/bin/complete.rs",
    "content": "use anyhow::Result;\nuse causal_llm::{CausalLlmModel, CausalLlmModelConfig};\nuse clap::Parser;\n\n#[derive(Parser, Debug)]\n#[command(about = \"Run text completion on a causal LLM\")]\nstruct Args {\n    /// Path to tokenizer.json\n    #[arg(short, long)]\n    tokenizer: String,\n\n    /// Path to NNEF model (.nnef.tgz or directory)\n    #[arg(short, long)]\n    model: String,\n\n    /// Number of tokens to generate\n    #[arg(short, default_value = \"20\")]\n    n: usize,\n\n    /// Force CPU execution (no CUDA or Metal)\n    #[arg(long)]\n    force_cpu: bool,\n\n    /// Prompts to complete\n    prompts: Vec<String>,\n}\n\nfn main() -> Result<()> {\n    env_logger::init();\n    let args = Args::parse();\n\n    let conf = CausalLlmModelConfig { force_cpu: args.force_cpu };\n    let llm = CausalLlmModel::from_paths_and_conf(&args.tokenizer, &args.model, conf)?;\n\n    for prompt in &args.prompts {\n        let mut state = llm.spawn()?;\n        state.append_text(prompt)?;\n        for _ in 0..args.n {\n            state.generate_next_token()?;\n        }\n        let prompt_len = state.encode(prompt, true)?.len();\n        let generated = state.decode(&state.seq[prompt_len..], true)?;\n        println!(\"{prompt}{generated}\");\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "examples/causal_llm/src/bin/serve.rs",
    "content": "use std::sync::Arc;\nuse std::sync::atomic::AtomicUsize;\n\nuse anyhow::Result as TractResult;\nuse axum::extract::State;\nuse axum::http::StatusCode;\nuse axum::response::IntoResponse;\nuse axum::routing::{get, post};\nuse axum::{Json, Router};\nuse axum_macros::debug_handler;\nuse causal_llm::{CausalLlmModel, CausalLlmStateConfig};\nuse clap::Parser;\nuse log::{debug, info};\n\n#[allow(dead_code)]\nmod common;\nuse common::*;\n\nmacro_rules! http_ensure {\n    ($expr: expr, $msg: expr) => {\n        if !$expr {\n            return Err(anyhow::anyhow!($msg).into());\n        }\n    };\n}\ntype Result<A> = std::result::Result<A, AppError>;\nstruct AppError(anyhow::Error);\n\nimpl IntoResponse for AppError {\n    fn into_response(self) -> axum::response::Response {\n        (StatusCode::INTERNAL_SERVER_ERROR, format!(\"Something went wrong: {}\", self.0))\n            .into_response()\n    }\n}\n\nimpl<E> From<E> for AppError\nwhere\n    E: Into<anyhow::Error>,\n{\n    fn from(err: E) -> Self {\n        Self(err.into())\n    }\n}\n\n#[derive(Parser, Debug)]\n#[command(version, about, long_about = None)]\nstruct Args {\n    /// path to tokenizes.json.file\n    #[arg(short, long, required = true)]\n    tokenizers: String,\n\n    /// path to nnef neural net model\n    #[arg(short, long, required = true)]\n    model: String,\n\n    /// Turn debugging information on\n    #[arg(short, long, action = clap::ArgAction::Count)]\n    verbose: u8,\n\n    /// Force execution on CPU (no cuda or metal)\n    #[arg(long)]\n    force_cpu: bool,\n\n    /// Disable prefill chunking\n    #[arg(long)]\n    no_prefill_chunk: bool,\n\n    /// Prefill chunking\n    #[arg(long, default_value = \"512\")]\n    prefill_chunk: usize,\n}\n\nstruct Context {\n    args: Args,\n    llm: Arc<CausalLlmModel>,\n}\n\n#[tokio::main]\nasync fn main() -> TractResult<()> {\n    let args = Args::parse();\n\n    if ::std::env::var(\"TRACT_SERVE_LOG\").is_err() {\n        let level = match args.verbose {\n            0 => \"serve=warn,causal_llm=warn\",\n            1 => \"serve=info,causal_llm=info\",\n            2 => \"serve=debug,causal_llm=debug\",\n            _ => \"serve=trace,causal_llm=trace\",\n        };\n        unsafe {\n            std::env::set_var(\"TRACT_SERVE_LOG\", level);\n        }\n    }\n\n    let env = env_logger::Env::default().filter_or(\"TRACT_SERVE_LOG\", \"warn\");\n\n    env_logger::Builder::from_env(env).format_timestamp_nanos().init();\n\n    let conf = causal_llm::CausalLlmModelConfig { force_cpu: args.force_cpu };\n    info!(\"Loading model...\");\n    let llm = CausalLlmModel::from_paths_and_conf(&args.tokenizers, &args.model, conf)?;\n    info!(\"Loaded model.\");\n\n    let context = Context { llm, args };\n\n    let app = Router::new()\n        .route(\"/\", get(root))\n        .route(\"/v1/completions\", post(completions))\n        .with_state(Arc::new(context));\n\n    let listen = \"0.0.0.0:3000\";\n    let listener = tokio::net::TcpListener::bind(&listen).await.unwrap();\n    info!(\"Serving on http://{listen}/v1/completions\");\n    axum::serve(listener, app).await?;\n    Ok(())\n}\n\nasync fn root() -> &'static str {\n    \"tract mini llm completions server\\n\"\n}\n\n#[debug_handler]\nasync fn completions(\n    State(global): State<Arc<Context>>,\n    Json(query): Json<OpenAICompletionQuery>,\n) -> Result<Json<OpenAICompletionReply>> {\n    http_ensure!(query.max_tokens > 0, \"max_tokens must be at least 1\");\n\n    static COUNTER: AtomicUsize = AtomicUsize::new(1);\n    let id = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed).to_string();\n    let s = tokio::task::spawn_blocking(move || -> Result<OpenAICompletionReply> {\n        let mut state = global.llm.spawn_with_config(CausalLlmStateConfig {\n            prompt_chunk_size: Some(global.args.prefill_chunk)\n                .filter(|_| !global.args.no_prefill_chunk),\n            ..Default::default()\n        })?;\n        debug!(\"prompt [{id}] << {}\", query.prompt);\n        state.append_text(&query.prompt)?;\n        let prompt_len = state.seq.len();\n\n        while state.seq.len() - prompt_len < query.max_tokens {\n            state.generate_next_token()?;\n        }\n        let generated = state.decode(&state.seq[prompt_len..], true)?;\n        debug!(\"gen   [{id}] >> {}\", generated);\n        Ok(OpenAICompletionReply {\n            id,\n            choices: vec![OpenAICompletionReplyChoice { text: generated }],\n            usage: OpenAICompletionReplyUsage {\n                prompt_tokens: prompt_len,\n                total_tokens: state.seq.len(),\n                completion_tokens: state.seq.len() - prompt_len,\n            },\n        })\n    })\n    .await??;\n    Ok(Json(s))\n}\n"
  },
  {
    "path": "examples/causal_llm/src/lib.rs",
    "content": "use std::collections::HashSet;\nuse std::path::Path;\nuse std::sync::Arc;\nuse std::time::Instant;\n\nuse anyhow::{Context, ensure};\nuse float_ord::FloatOrd;\nuse log::{info, trace};\nuse tokenizers::Tokenizer;\nuse tract::prelude::*;\n\n#[derive(Clone, Debug)]\nstruct KvCacheInfo {\n    axis: usize,\n    dt: DatumType,\n    /// Concrete shape for an empty cache (seq_len=0 on the cache axis)\n    empty_shape: Vec<usize>,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct CausalLlmModelConfig {\n    pub force_cpu: bool,\n}\n\n#[derive(Clone, Debug)]\npub struct CausalLlmModel {\n    pub tokenizer: Tokenizer,\n    pub nn: Runnable,\n    pub conf: CausalLlmModelConfig,\n    kv_caches: Vec<KvCacheInfo>,\n    n_regular_outputs: usize,\n}\n\nimpl CausalLlmModel {\n    fn from_tokenizer_and_model(\n        tokenizer: tokenizers::Tokenizer,\n        nn: Model,\n        conf: CausalLlmModelConfig,\n    ) -> anyhow::Result<Arc<CausalLlmModel>> {\n        let mut nn = nn;\n\n        // Detect transformer patterns (creates DynKeyValueCache ops)\n        nn.transform(\"transformers_detect_all\")?;\n\n        let n_regular_outputs = nn.output_count()?;\n\n        // Unfold KV caches into explicit model I/O\n        nn.transform(\"unfold-kv-cache\")?;\n\n        // Detect KV cache inputs by comparing pre/post unfold input counts.\n        // Token input (idx 0) is integer, KV cache inputs are float.\n        let token_fact = nn.input_fact(0)?;\n        let mut token_symbols = HashSet::new();\n        for axis in 0..token_fact.rank()? {\n            let dim = token_fact.dim(axis)?;\n            if dim.to_int64().is_err() {\n                token_symbols.insert(format!(\"{dim}\"));\n            }\n        }\n\n        let n_inputs = nn.input_count()?;\n        let mut kv_infos = Vec::new();\n        for i in 1..n_inputs {\n            let fact = nn.input_fact(i)?;\n            let dt = fact.datum_type()?;\n            let mut cache_axis = None;\n            let mut empty_shape = vec![];\n            for axis in 0..fact.rank()? {\n                let dim = fact.dim(axis)?;\n                match dim.to_int64() {\n                    Ok(v) => empty_shape.push(v as usize),\n                    Err(_) => {\n                        let sym_name = format!(\"{dim}\");\n                        if token_symbols.contains(&sym_name) {\n                            // Batch dim — default to 1\n                            empty_shape.push(1);\n                        } else {\n                            // Cache axis (past sequence) — start at 0\n                            cache_axis = Some(axis);\n                            empty_shape.push(0);\n                        }\n                    }\n                }\n            }\n            kv_infos.push(KvCacheInfo {\n                axis: cache_axis.context(\"no symbolic cache axis found in KV cache input\")?,\n                dt,\n                empty_shape,\n            });\n        }\n\n        // Try runtimes in order of preference\n        let runtimes =\n            if conf.force_cpu { vec![\"default\"] } else { vec![\"cuda\", \"metal\", \"default\"] };\n        let mut runnable = None;\n        for rt_name in &runtimes {\n            match runtime_for_name(rt_name) {\n                Ok(rt) => match rt.prepare(nn.clone()) {\n                    Ok(r) => {\n                        info!(\"Using runtime {rt_name}\");\n                        runnable = Some(r);\n                        break;\n                    }\n                    Err(e) => info!(\"{rt_name} {e:?}\"),\n                },\n                Err(_) => continue,\n            }\n        }\n        let nn = runnable.context(\"No suitable runtime\")?;\n        Ok(Arc::new(CausalLlmModel { tokenizer, nn, conf, kv_caches: kv_infos, n_regular_outputs }))\n    }\n\n    pub fn from_paths_and_conf(\n        tokenizer: impl AsRef<Path>,\n        nn: impl AsRef<Path>,\n        conf: CausalLlmModelConfig,\n    ) -> anyhow::Result<Arc<CausalLlmModel>> {\n        let nnef = tract::nnef()?.with_tract_transformers()?;\n        let tokenizer =\n            tokenizers::Tokenizer::from_file(tokenizer).map_err(|e| anyhow::anyhow!(e))?;\n        let nn = nnef.load(nn)?;\n        CausalLlmModel::from_tokenizer_and_model(tokenizer, nn, conf)\n    }\n\n    pub fn from_paths(\n        tokenizer: impl AsRef<Path>,\n        nn: impl AsRef<Path>,\n    ) -> anyhow::Result<Arc<CausalLlmModel>> {\n        Self::from_paths_and_conf(tokenizer, nn, Default::default())\n    }\n\n    pub fn from_bytes_and_conf(\n        tokenizer_bytes: impl AsRef<[u8]>,\n        llm_model_bytes: impl AsRef<[u8]>,\n        conf: CausalLlmModelConfig,\n    ) -> anyhow::Result<Arc<CausalLlmModel>> {\n        let nnef = tract::nnef()?.with_tract_transformers()?;\n        let tokenizer = Tokenizer::from_bytes(tokenizer_bytes).map_err(|e| anyhow::anyhow!(e))?;\n        let nn = nnef.load_buffer(llm_model_bytes.as_ref())?;\n        CausalLlmModel::from_tokenizer_and_model(tokenizer, nn, conf)\n    }\n\n    pub fn from_bytes(\n        tokenizer_bytes: impl AsRef<[u8]>,\n        llm_model_bytes: impl AsRef<[u8]>,\n    ) -> anyhow::Result<Arc<CausalLlmModel>> {\n        Self::from_bytes_and_conf(tokenizer_bytes, llm_model_bytes, Default::default())\n    }\n\n    fn make_empty_kv_caches(&self) -> anyhow::Result<Vec<Tensor>> {\n        self.kv_caches\n            .iter()\n            .map(|info| {\n                let n_bytes = info.empty_shape.iter().product::<usize>() * info.dt.size_of();\n                Tensor::from_bytes(info.dt, &info.empty_shape, &vec![0u8; n_bytes])\n            })\n            .collect()\n    }\n\n    pub fn spawn(self: &Arc<Self>) -> anyhow::Result<CausalLlmState> {\n        let kv_caches = self.make_empty_kv_caches()?;\n        Ok(CausalLlmState {\n            model: self.clone(),\n            kv_caches,\n            seq: vec![],\n            processed_tokens: 0,\n            config: CausalLlmStateConfig::default(),\n        })\n    }\n\n    pub fn spawn_with_config(\n        self: &Arc<Self>,\n        config: CausalLlmStateConfig,\n    ) -> anyhow::Result<CausalLlmState> {\n        let kv_caches = self.make_empty_kv_caches()?;\n        Ok(CausalLlmState {\n            model: self.clone(),\n            kv_caches,\n            seq: vec![],\n            processed_tokens: 0,\n            config,\n        })\n    }\n}\n\n/// Config of the state containing specific behavior requested\n/// prompt chunking, sampling strategy, ...\n#[derive(Debug, Clone, PartialEq)]\npub struct CausalLlmStateConfig {\n    /// split long prompt into chunks of fixed number of tokens,\n    /// reducing RAM usage\n    pub prompt_chunk_size: Option<usize>,\n    pub repeat_penalty: f32,\n    pub repeat_last_n: usize,\n}\nimpl Default for CausalLlmStateConfig {\n    fn default() -> Self {\n        Self { prompt_chunk_size: Some(512), repeat_penalty: 1.0, repeat_last_n: 64 }\n    }\n}\n\nimpl CausalLlmStateConfig {\n    pub fn new(\n        prompt_chunk_size: Option<usize>,\n        repeat_penalty: f32,\n        repeat_last_n: usize,\n    ) -> anyhow::Result<Self> {\n        let conf = Self { prompt_chunk_size, repeat_penalty, repeat_last_n };\n        conf.validate()?;\n        Ok(conf)\n    }\n\n    pub fn validate(&self) -> anyhow::Result<()> {\n        if let Some(chunk_size) = self.prompt_chunk_size {\n            anyhow::ensure!(\n                chunk_size > 0,\n                format!(\n                    \"prompt_chunk_size cannot be set to '{chunk_size}', set None to deactivate it.\"\n                )\n            )\n        }\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub struct CausalLlmState {\n    pub model: Arc<CausalLlmModel>,\n    kv_caches: Vec<Tensor>,\n    pub seq: Vec<u32>,\n    pub processed_tokens: usize,\n    pub config: CausalLlmStateConfig,\n}\n\nimpl CausalLlmState {\n    pub fn append_text(&mut self, prompt: &str) -> anyhow::Result<()> {\n        self.seq.extend(self.encode(prompt, true)?);\n        Ok(())\n    }\n\n    pub fn generate_next_token(&mut self) -> anyhow::Result<()> {\n        let tokens = &self.seq[self.processed_tokens..];\n        ensure!(tokens.len() > 0);\n        let chunk_size = self.config.prompt_chunk_size.unwrap_or(usize::MAX);\n        let output = tokens\n            .chunks(chunk_size)\n            .map(|chunk| -> anyhow::Result<Tensor> {\n                let start = Instant::now();\n                let token_data: Vec<i64> = chunk.iter().map(|t| *t as i64).collect();\n                let input: Tensor =\n                    tract_ndarray::Array2::from_shape_vec((1, chunk.len()), token_data)?\n                        .try_into()?;\n                // Build inputs: token_ids + current kv caches\n                let mut inputs: Vec<Tensor> = vec![input];\n                inputs.extend(self.kv_caches.iter().cloned());\n                let mut results = self.model.nn.run(inputs)?;\n                // Extract updated KV caches from outputs\n                // outputs layout: [regular_outputs..., kv_cache_0, kv_cache_1, ...]\n                let n_regular = self.model.n_regular_outputs;\n                for (i, kv) in results.drain(n_regular..).enumerate() {\n                    self.kv_caches[i] = kv;\n                }\n                trace!(\"Processed {} tokens in {:?}\", chunk.len(), start.elapsed());\n                Ok(results.remove(0))\n            })\n            .last()\n            .unwrap()?;\n\n        let start_at = self.seq.len().saturating_sub(self.config.repeat_last_n);\n\n        let output_f32 = output.convert_to(DatumType::F32)?;\n        let mut last_token_logits: Vec<f32> = output_f32.as_slice::<f32>()?.to_vec();\n\n        apply_repeat_penalty(\n            last_token_logits.as_mut_slice(),\n            self.config.repeat_penalty,\n            &self.seq[start_at..],\n        );\n\n        let next_tok = last_token_logits\n            .iter()\n            .enumerate()\n            .max_by_key(|(_ix, v)| FloatOrd(**v))\n            .context(\"no tokens in output ?\")?\n            .0 as u32;\n\n        self.processed_tokens += tokens.len();\n        self.seq.push(next_tok);\n        Ok(())\n    }\n\n    pub fn tokenizer(&self) -> &Tokenizer {\n        &self.model.tokenizer\n    }\n\n    pub fn decode(&self, tokens: &[u32], skip_special_tokens: bool) -> anyhow::Result<String> {\n        self.tokenizer().decode(tokens, skip_special_tokens).map_err(|e| anyhow::anyhow!(e))\n    }\n\n    pub fn encode(&self, text: &str, add_special_tokens: bool) -> anyhow::Result<Vec<u32>> {\n        Ok(self\n            .tokenizer()\n            .encode(text, add_special_tokens)\n            .map_err(|e| anyhow::anyhow!(e))?\n            .get_ids()\n            .to_vec())\n    }\n\n    pub fn freeze(self) -> FrozenCausalLlmState {\n        FrozenCausalLlmState {\n            model: self.model,\n            kv_caches: self.kv_caches,\n            seq: self.seq,\n            config: self.config,\n        }\n    }\n\n    pub fn truncate(&mut self, len: usize) -> anyhow::Result<()> {\n        anyhow::ensure!(len > 0, \"cannot truncate to 0\");\n        self.seq.truncate(len);\n        // Slice KV caches to len-1: the last token will be re-processed by\n        // the next generate_next_token call to produce output logits.\n        let cache_len = len - 1;\n        self.processed_tokens = self.processed_tokens.min(cache_len);\n        for (kv, info) in self.kv_caches.iter_mut().zip(&self.model.kv_caches) {\n            *kv = slice_value(kv, info.axis, 0, cache_len)?;\n        }\n        Ok(())\n    }\n}\n\n/// Slice a Value along an axis, extracting elements [start..end].\nfn slice_value(value: &Tensor, axis: usize, start: usize, end: usize) -> anyhow::Result<Tensor> {\n    let (dt, shape, data) = value.as_bytes()?;\n    let elem_size = dt.size_of();\n    let new_len = end - start;\n    let mut new_shape = shape.to_vec();\n    new_shape[axis] = new_len;\n\n    let inner_size: usize = shape[axis + 1..].iter().product::<usize>() * elem_size;\n    let axis_stride = shape[axis] * inner_size;\n    let outer_count: usize = shape[..axis].iter().product::<usize>().max(1);\n\n    let new_data_len = outer_count * new_len * inner_size;\n    let mut new_data = vec![0u8; new_data_len];\n\n    for outer in 0..outer_count {\n        let src_offset = outer * axis_stride + start * inner_size;\n        let dst_offset = outer * new_len * inner_size;\n        new_data[dst_offset..dst_offset + new_len * inner_size]\n            .copy_from_slice(&data[src_offset..src_offset + new_len * inner_size]);\n    }\n\n    Tensor::from_bytes(dt, &new_shape, &new_data)\n}\n\n#[derive(Clone, Debug)]\npub struct FrozenCausalLlmState {\n    pub model: Arc<CausalLlmModel>,\n    kv_caches: Vec<Tensor>,\n    pub seq: Vec<u32>,\n    pub config: CausalLlmStateConfig,\n}\n\nimpl FrozenCausalLlmState {\n    pub fn unfreeze(self) -> anyhow::Result<CausalLlmState> {\n        Ok(CausalLlmState {\n            model: self.model,\n            kv_caches: self.kv_caches,\n            seq: self.seq,\n            processed_tokens: 0,\n            config: self.config,\n        })\n    }\n}\n\n/// Cheap way to avoid repeating model (mostly usefull for tiny models)\npub fn apply_repeat_penalty(logits: &mut [f32], penalty: f32, context: &[u32]) {\n    if penalty == 1.0 {\n        return;\n    }\n    let context: std::collections::HashSet<_> = context.iter().collect();\n    for (token_id, logit) in logits.iter_mut().enumerate() {\n        if context.contains(&(token_id as u32)) {\n            if *logit >= 0. { *logit /= penalty } else { *logit *= penalty }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::FrozenCausalLlmState;\n    use tract::prelude::*;\n\n    fn is_send<T: Send>() {}\n\n    #[test]\n    fn frozen_state_is_send() {\n        is_send::<FrozenCausalLlmState>();\n    }\n\n    #[test]\n    fn truncate_prefix_cache_hit() -> anyhow::Result<()> {\n        let model_dir = std::path::Path::new(env!(\"CARGO_MANIFEST_DIR\"))\n            .join(\"../../.cached/llm/541/meta-llama--Llama-3.2-1B-Instruct-q40ef16\");\n        if !model_dir.exists() {\n            eprintln!(\"Skipping: model not found at {model_dir:?}\");\n            return Ok(());\n        }\n        let llm = crate::CausalLlmModel::from_paths(\n            model_dir.join(\"tokenizer.json\"),\n            model_dir.join(\"meta-llama--Llama-3.2-1B-Instruct-q40ef16.nnef.tgz\"),\n        )?;\n\n        // Generate from \"Hello world\" for 5 tokens\n        let mut state = llm.spawn()?;\n        state.append_text(\"Hello world\")?;\n        for _ in 0..5 {\n            state.generate_next_token()?;\n        }\n        let full_seq = state.seq.clone();\n        let prompt_len = state.encode(\"Hello world\", true)?.len();\n\n        // Truncate back to just the prompt (simulating prefix cache hit)\n        // KV cache is sliced to prompt_len-1, last token will be re-processed\n        state.truncate(prompt_len)?;\n        assert_eq!(state.seq.len(), prompt_len);\n        assert_eq!(state.processed_tokens, prompt_len - 1);\n\n        // Generate one token and verify KV cache grew by exactly 1\n        // (proving the truncated prefix was preserved, not recomputed from scratch)\n        state.generate_next_token()?;\n        {\n            let shape = state.kv_caches[0].shape()?;\n            let cache_axis = state.model.kv_caches[0].axis;\n            assert_eq!(\n                shape[cache_axis], prompt_len,\n                \"KV cache should have prompt_len entries after truncate + 1 generate\"\n            );\n        }\n\n        // Generate 4 more tokens\n        for _ in 0..4 {\n            state.generate_next_token()?;\n        }\n        let regenerated_seq = state.seq.clone();\n\n        // A fresh state from the same prompt should produce identical output\n        let mut fresh = llm.spawn()?;\n        fresh.append_text(\"Hello world\")?;\n        for _ in 0..5 {\n            fresh.generate_next_token()?;\n        }\n\n        // The truncated-and-regenerated sequence should match the fresh sequence\n        assert_eq!(\n            regenerated_seq, fresh.seq,\n            \"truncate + regenerate should match fresh generation\"\n        );\n        // And should also match the original since generation is deterministic\n        assert_eq!(full_seq, fresh.seq, \"generation should be deterministic\");\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "examples/face_detection_yolov8onnx_example/.gitignore",
    "content": "venv/\n*.onnx\n*.pt\n*.pt.1\n"
  },
  {
    "path": "examples/face_detection_yolov8onnx_example/Cargo.toml",
    "content": "[package]\nname = \"face_detection_yolov8onnx_example\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nclap = { version = \"4.5.9\", features = [\"derive\"] }\nimage.workspace = true \ntract.workspace = true\n"
  },
  {
    "path": "examples/face_detection_yolov8onnx_example/README.md",
    "content": "# tract onnx face detection demo yolov8\r\nface deteciton demo using yolov8 , using models converted from [derronqi's repo](https://github.com/derronqi/yolov8-face)\r\n\r\n# getting the models\r\n1. you can get the model and convert them yourself from [here](https://github.com/derronqi/yolov8-face) , you can follow conversion instructions [here](https://docs.ultralytics.com/integrations/onnx/)\r\n2. you can get it preconverted from\r\n[google drive](https://drive.google.com/file/d/1PYAG1ypAuwh_rDROaUF0OdLmBqOefBGL/view?usp=sharing)\r\n\r\n# to use \r\nrun `cargo run -- --input-image /path/to/image --weights /path/to/weights`\r\n"
  },
  {
    "path": "examples/face_detection_yolov8onnx_example/ci.sh",
    "content": "#!/bin/bash\n\nset -ex\n# download pre-exported onnx model\nwget -Nq \"https://tract-ci-builds.s3.amazonaws.com/model/yolov8n-face.onnx\"\n\n# on win/linux \ncargo run -- --input-image grace_hopper.jpg --weights yolov8n-face.onnx \n\nwasmtime -V || curl https://wasmtime.dev/install.sh -sSf | bash # install wasmtime\nPATH=$PATH:$HOME/.wasmtime/bin\nrustup target install wasm32-wasip1\ncargo build --target wasm32-wasip1 --release\nwasmtime --dir . ../../target/wasm32-wasip1/release/face_detection_yolov8onnx_example.wasm --input-image grace_hopper.jpg --weights yolov8n-face.onnx\n\nrm yolov8n-face.onnx\n"
  },
  {
    "path": "examples/face_detection_yolov8onnx_example/src/main.rs",
    "content": "use anyhow::{Error, Result};\nuse clap::Parser;\nuse image::DynamicImage;\nuse std::cmp::Ordering;\nuse std::cmp::PartialOrd;\nuse tract::prelude::*;\nuse tract_ndarray::s;\n\n#[derive(Parser)]\nstruct CliArgs {\n    #[arg(long)]\n    input_image: String,\n\n    #[arg(long)]\n    weights: String,\n}\n\n#[derive(Debug, Clone)]\npub struct Bbox {\n    pub x1: f32,\n    pub y1: f32,\n    pub x2: f32,\n    pub y2: f32,\n    pub confidence: f32,\n}\n\nimpl Bbox {\n    pub fn new(x1: f32, y1: f32, x2: f32, y2: f32, confidence: f32) -> Bbox {\n        Bbox { x1, y1, x2, y2, confidence }\n    }\n    pub fn apply_image_scale(\n        &mut self,\n        original_image: &DynamicImage,\n        x_scale: f32,\n        y_scale: f32,\n    ) -> Bbox {\n        let normalized_x1 = self.x1 / x_scale;\n        let normalized_x2 = self.x2 / x_scale;\n        let normalized_y1 = self.y1 / y_scale;\n        let normalized_y2 = self.y2 / y_scale;\n\n        let cart_x1 = original_image.width() as f32 * normalized_x1;\n        let cart_x2 = original_image.width() as f32 * normalized_x2;\n        let cart_y1 = original_image.height() as f32 * normalized_y1;\n        let cart_y2 = original_image.height() as f32 * normalized_y2;\n\n        Bbox { x1: cart_x1, y1: cart_y1, x2: cart_x2, y2: cart_y2, confidence: self.confidence }\n    }\n    pub fn crop_bbox(&self, original_image: &DynamicImage) -> Result<DynamicImage, Error> {\n        let bbox_width = (self.x2 - self.x1) as u32;\n        let bbox_height = (self.y2 - self.y1) as u32;\n        Ok(original_image.to_owned().crop_imm(\n            self.x1 as u32,\n            self.y1 as u32,\n            bbox_width,\n            bbox_height,\n        ))\n    }\n}\n\npub fn non_maximum_suppression(mut boxes: Vec<Bbox>, iou_threshold: f32) -> Vec<Bbox> {\n    boxes.sort_by(|a, b| a.confidence.partial_cmp(&b.confidence).unwrap_or(Ordering::Equal));\n    let mut keep = Vec::new();\n    while !boxes.is_empty() {\n        let current = boxes.remove(0);\n        keep.push(current.clone());\n        boxes.retain(|box_| calculate_iou(&current, box_) <= iou_threshold);\n    }\n\n    keep\n}\n\nfn calculate_iou(box1: &Bbox, box2: &Bbox) -> f32 {\n    let x1 = box1.x1.max(box2.x1);\n    let y1 = box1.y1.max(box2.y1);\n    let x2 = box1.x2.min(box2.x2);\n    let y2 = box1.y2.min(box2.y2);\n\n    let intersection = (x2 - x1).max(0.0) * (y2 - y1).max(0.0);\n    let area1 = (box1.x2 - box1.x1) * (box1.y2 - box1.y1);\n    let area2 = (box2.x2 - box2.x1) * (box2.y2 - box2.y1);\n    let union = area1 + area2 - intersection;\n    intersection / union\n}\n\nfn main() -> Result<(), Error> {\n    let args = CliArgs::parse();\n    let mut model = tract::onnx()?.load(args.weights)?;\n    model.set_input_fact(0, \"1,3,640,640,f32\")?;\n    let model = model.into_model()?.into_runnable()?;\n    let raw_image = image::open(args.input_image)?;\n\n    // scale the image with black padding\n    let width = raw_image.width();\n    let height = raw_image.height();\n    let scale = 640.0 / width.max(height) as f32;\n    let new_width = (width as f32 * scale) as u32;\n    let new_height = (height as f32 * scale) as u32;\n    let resized = image::imageops::resize(\n        &raw_image.to_rgb8(),\n        new_width,\n        new_height,\n        image::imageops::FilterType::Triangle,\n    );\n    let mut padded = image::RgbImage::new(640, 640);\n    image::imageops::replace(\n        &mut padded,\n        &resized,\n        (640 - new_width as i64) / 2,\n        (640 - new_height as i64) / 2,\n    );\n    let input = tract_ndarray::Array4::from_shape_fn((1, 3, 640, 640), |(_, c, y, x)| {\n        padded.get_pixel(x as u32, y as u32)[c] as f32 / 255.0\n    });\n\n    //run model\n    let forward = model.run([input])?;\n    let results = forward[0].view::<f32>()?.t().into_owned();\n    let mut bbox_vec: Vec<Bbox> = vec![];\n    for i in 0..results.len_of(tract_ndarray::Axis(0)) {\n        let row = results.slice(s![i, .., ..]);\n        let confidence = row[[4, 0]];\n\n        if confidence >= 0.5 {\n            let x = row[[0, 0]];\n            let y = row[[1, 0]];\n            let w = row[[2, 0]];\n            let h = row[[3, 0]];\n            let x1 = x - w / 2.0;\n            let y1 = y - h / 2.0;\n            let x2 = x + w / 2.0;\n            let y2 = y + h / 2.0;\n            let bbox =\n                Bbox::new(x1, y1, x2, y2, confidence).apply_image_scale(&raw_image, 640.0, 640.0);\n            bbox_vec.push(bbox);\n        }\n    }\n    // uncomment below to save preview face\n    // let test_save = bbox_vec[0].crop_bbox(&raw_image)?.save(\"test_crop.png\");\n\n    println!(\"bboxes: {bbox_vec:?}\");\n\n    Ok(())\n}\n"
  },
  {
    "path": "examples/face_similarity_arcface_onnx/.gitignore",
    "content": "*.onnx\n"
  },
  {
    "path": "examples/face_similarity_arcface_onnx/Cargo.toml",
    "content": "[package]\nname = \"face_similarity_arcface_onnx\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nclap = { version = \"4.5.9\", features = [\"derive\"] }\nimage.workspace = true \ntract-onnx.workspace = true\ntract-core.workspace = true\n\n"
  },
  {
    "path": "examples/face_similarity_arcface_onnx/README.md",
    "content": "# tract onnx face similarity comparison using arcface\r\nface deteciton demo using yolov8 , using models converted from [derronqi's repo](https://github.com/derronqi/yolov8-face), and then using ArcFace to get face embeddings and then compare them with cosine similarity.\r\n\r\n# getting the models\r\n## yolov8-face\r\n1. you can get the model and convert them yourself from [here](https://github.com/derronqi/yolov8-face) , you can follow conversion instructions [here](https://docs.ultralytics.com/integrations/onnx/)\r\n2. you can get it preconverted from\r\n[google drive](https://drive.google.com/file/d/1PYAG1ypAuwh_rDROaUF0OdLmBqOefBGL/view?usp=sharing)\r\n\r\n## arcface \r\nyou can get onnx converted models [here](https://github.com/onnx/models/tree/main/validated/vision/body_analysis/arcface)\r\n\r\n\r\n# to use \r\nrun `cargo run -- --face1 path/to/image1 --face2 path/to/image2`\r\n"
  },
  {
    "path": "examples/face_similarity_arcface_onnx/ci.sh",
    "content": "#!/bin/bash\n\nset -ex\n\n# download pre-exported onnx model\nwget -Nq \"https://tract-ci-builds.s3.amazonaws.com/model/yolov8n-face.onnx\"\nwget -Nq \"https://tract-ci-builds.s3.amazonaws.com/model/arcfaceresnet100-8.onnx\"\n\n# on win/linux \ncargo run --release -- --face1 grace_hopper.jpeg --face2 grace_hopper2.jpeg \n\n# disabled wasm test, it OOM with recent wasm. is this model too big for wasm32 ?\n\n# wasmtime -V || curl https://wasmtime.dev/install.sh -sSf | bash # install wasmtime\n# PATH=$PATH:$HOME/.wasmtime/bin\n# rustup target install wasm32-wasip1\n# cargo build --target wasm32-wasip1 --release\n# wasmtime --dir . ../../target/wasm32-wasip1/release/face_similarity_arcface_onnx.wasm --face1 grace_hopper.jpeg --face2 grace_hopper2.jpeg\n\nrm yolov8n-face.onnx\nrm arcfaceresnet100-8.onnx\n"
  },
  {
    "path": "examples/face_similarity_arcface_onnx/src/arc_face.rs",
    "content": "use anyhow::{Error, Result};\nuse image::{DynamicImage, imageops};\nuse tract_ndarray::{Array1, Array3};\nuse tract_ndarray::{ArrayBase, OwnedRepr};\nuse tract_onnx::prelude::*;\n\n#[allow(clippy::type_complexity)]\npub struct ArcFace {\n    model: Arc<TypedRunnableModel>,\n}\n\nimpl ArcFace {\n    /// returns a ndarray of length 512 representing a face\n    /// please crop face before using !\n    pub fn get_face_embedding(\n        &self,\n        input_image: &DynamicImage,\n    ) -> Result<ArrayBase<OwnedRepr<f32>, tract_core::ndarray::Dim<[usize; 1]>>, Error> {\n        let preprocess_image = preprocess_arcface(input_image, 112)?;\n        let forward = self.model.run(tvec![preprocess_image.to_owned().into()])?;\n        println!(\"FORWARD {forward:?}\");\n        let results = forward[0].to_plain_array_view::<f32>()?.to_shape(512)?.to_owned();\n        Ok(results)\n    }\n}\n\npub fn load_arcface_model(model_path: &str, input_size: i32) -> ArcFace {\n    let load_model = tract_onnx::onnx()\n        .model_for_path(model_path)\n        .unwrap()\n        .with_input_fact(0, f32::fact([1, 3, input_size, input_size]).into())\n        .unwrap()\n        .incorporate()\n        .unwrap()\n        .into_optimized()\n        .unwrap()\n        .into_runnable()\n        .unwrap();\n    ArcFace { model: load_model }\n}\n\nfn image_to_tract_tensor(img: &DynamicImage) -> Array3<f32> {\n    let height = img.height();\n    let width = img.width();\n    let img_buffer = img.to_rgb8();\n    Array3::from_shape_vec((height as usize, width as usize, 3), img_buffer.into_raw())\n        .expect(\"cannot convert image to ndarray\")\n        .mapv(|x| x as f32)\n}\n\npub fn preprocess_arcface(input_image: &DynamicImage, target_size: u32) -> Result<Tensor, Error> {\n    let resize = input_image.resize_exact(target_size, target_size, imageops::FilterType::Triangle);\n    let ndarray_img = image_to_tract_tensor(&resize);\n    // ndarray_img *= 1.0 / 127.5;\n    // ndarray_img -= 127.5;\n    let mut _final: Tensor = ndarray_img.permuted_axes((2, 0, 1)).into();\n    _final.insert_axis(0).unwrap();\n    Ok(_final)\n}\n\npub fn cosine_similarity(a: &Array1<f32>, b: &Array1<f32>) -> f32 {\n    let dotprod = a.dot(b);\n    let norm_a = a.dot(a).sqrt();\n    let norm_b = b.dot(b).sqrt();\n    println!(\"dotprod {dotprod:?} norm_a {norm_a:?} norm_b {norm_b:?}\");\n    dotprod / (norm_a * norm_b)\n}\n"
  },
  {
    "path": "examples/face_similarity_arcface_onnx/src/main.rs",
    "content": "mod arc_face;\nmod yolo_face;\nuse anyhow::{Error, Result};\nuse arc_face::{ArcFace, cosine_similarity, load_arcface_model};\nuse clap::Parser;\nuse yolo_face::{YoloFace, load_yolo_model, sort_conf_bbox};\n\n#[derive(Parser)]\nstruct CliArgs {\n    #[arg(long)]\n    face1: String,\n\n    #[arg(long)]\n    face2: String,\n}\n\nfn main() -> Result<(), Error> {\n    let args = CliArgs::parse();\n    println!(\"face1 {:#?},\\nface2 {:?}\", &args.face1, &args.face2);\n    let face1 = image::open(&args.face1)?;\n    let face2 = image::open(&args.face2)?;\n\n    let yolo_model: YoloFace = load_yolo_model(\"yolov8n-face.onnx\", (640, 640));\n    let arcface_model: ArcFace = load_arcface_model(\"arcfaceresnet100-8.onnx\", 112);\n\n    let mut face1_bbox = yolo_model.get_faces_bbox(&face1, 0.5, 0.5)?;\n    let mut face2_bbox = yolo_model.get_faces_bbox(&face2, 0.5, 0.5)?;\n\n    let f1_sorted_bbox = sort_conf_bbox(&mut face1_bbox);\n    let f2_sorted_bbox = sort_conf_bbox(&mut face2_bbox);\n\n    let f1_crop = f1_sorted_bbox[0].crop_bbox(&face1)?;\n    let f2_crop = f2_sorted_bbox[0].crop_bbox(&face2)?;\n\n    let f1_embed = arcface_model.get_face_embedding(&f1_crop)?;\n    let f2_embed = arcface_model.get_face_embedding(&f2_crop)?;\n\n    let similarity = cosine_similarity(&f1_embed, &f2_embed);\n    println!(\"SIMILARITY {similarity:#?}\");\n    Ok(())\n}\n"
  },
  {
    "path": "examples/face_similarity_arcface_onnx/src/yolo_face.rs",
    "content": "use anyhow::{Error, Result};\nuse image::DynamicImage;\nuse std::cmp::Ordering;\nuse std::cmp::PartialOrd;\nuse tract_ndarray::s;\nuse tract_onnx::prelude::*;\n\n#[allow(clippy::type_complexity)]\npub struct YoloFace {\n    model: Arc<TypedRunnableModel>,\n    width: i32,\n    height: i32,\n}\n\npub fn sort_conf_bbox(input_bbox: &mut [Bbox]) -> Vec<Bbox> {\n    input_bbox.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());\n    input_bbox.to_vec()\n}\n\nimpl YoloFace {\n    pub fn get_faces_bbox(\n        &self,\n        input_image: &DynamicImage,\n        confidence_threshold: f32,\n        iou_threshold: f32,\n    ) -> Result<Vec<Bbox>, Error> {\n        // assuming that model has input shape of a square\n        let preprocess_image = preprocess_yoloface_square(input_image, self.width as f32);\n\n        // run forward pass and then convert result to f32\n        let forward = self.model.run(tvec![preprocess_image.to_owned().into()])?;\n        let results = forward[0].to_plain_array_view::<f32>()?.view().t().into_owned();\n\n        // process results\n        let mut bbox_vec: Vec<Bbox> = vec![];\n        for i in 0..results.len_of(tract_ndarray::Axis(0)) {\n            let row = results.slice(s![i, .., ..]);\n            let confidence = row[[4, 0]];\n\n            if confidence >= confidence_threshold {\n                let x = row[[0, 0]];\n                let y = row[[1, 0]];\n                let w = row[[2, 0]];\n                let h = row[[3, 0]];\n                let x1 = x - w / 2.0;\n                let y1 = y - h / 2.0;\n                let x2 = x + w / 2.0;\n                let y2 = y + h / 2.0;\n                let bbox = Bbox::new(x1, y1, x2, y2, confidence).apply_image_scale(\n                    input_image,\n                    self.width as f32,\n                    self.height as f32,\n                );\n                bbox_vec.push(bbox);\n            }\n        }\n        Ok(non_maximum_suppression(bbox_vec, iou_threshold))\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct Bbox {\n    pub x1: f32,\n    pub y1: f32,\n    pub x2: f32,\n    pub y2: f32,\n    pub confidence: f32,\n}\n\nimpl Bbox {\n    pub fn new(x1: f32, y1: f32, x2: f32, y2: f32, confidence: f32) -> Bbox {\n        Bbox { x1, y1, x2, y2, confidence }\n    }\n    pub fn apply_image_scale(\n        &mut self,\n        original_image: &DynamicImage,\n        x_scale: f32,\n        y_scale: f32,\n    ) -> Bbox {\n        let normalized_x1 = self.x1 / x_scale;\n        let normalized_x2 = self.x2 / x_scale;\n        let normalized_y1 = self.y1 / y_scale;\n        let normalized_y2 = self.y2 / y_scale;\n\n        let cart_x1 = original_image.width() as f32 * normalized_x1;\n        let cart_x2 = original_image.width() as f32 * normalized_x2;\n        let cart_y1 = original_image.height() as f32 * normalized_y1;\n        let cart_y2 = original_image.height() as f32 * normalized_y2;\n\n        Bbox { x1: cart_x1, y1: cart_y1, x2: cart_x2, y2: cart_y2, confidence: self.confidence }\n    }\n\n    pub fn crop_bbox(&self, original_image: &DynamicImage) -> Result<DynamicImage, Error> {\n        let bbox_width = (self.x2 - self.x1) as u32;\n        let bbox_height = (self.y2 - self.y1) as u32;\n        Ok(original_image.to_owned().crop_imm(\n            self.x1 as u32,\n            self.y1 as u32,\n            bbox_width,\n            bbox_height,\n        ))\n    }\n}\n\n/// loads model, panic on failure.\npub fn load_yolo_model(model_path: &str, input_size: (i32, i32)) -> YoloFace {\n    let load_model = tract_onnx::onnx()\n        .model_for_path(model_path)\n        .unwrap()\n        .with_input_fact(0, f32::fact([1, 3, input_size.0, input_size.1]).into())\n        .unwrap()\n        .into_optimized()\n        .unwrap()\n        .into_runnable()\n        .unwrap();\n    YoloFace { model: load_model, width: input_size.0, height: input_size.1 }\n}\n\nfn non_maximum_suppression(mut boxes: Vec<Bbox>, iou_threshold: f32) -> Vec<Bbox> {\n    boxes.sort_by(|a, b| a.confidence.partial_cmp(&b.confidence).unwrap_or(Ordering::Equal));\n    let mut keep = Vec::new();\n    while !boxes.is_empty() {\n        let current = boxes.remove(0);\n        keep.push(current.clone());\n        boxes.retain(|box_| calculate_iou(&current, box_) <= iou_threshold);\n    }\n    keep\n}\n\nfn calculate_iou(box1: &Bbox, box2: &Bbox) -> f32 {\n    let x1 = box1.x1.max(box2.x1);\n    let y1 = box1.y1.max(box2.y1);\n    let x2 = box1.x2.min(box2.x2);\n    let y2 = box1.y2.min(box2.y2);\n\n    let intersection = (x2 - x1).max(0.0) * (y2 - y1).max(0.0);\n    let area1 = (box1.x2 - box1.x1) * (box1.y2 - box1.y1);\n    let area2 = (box2.x2 - box2.x1) * (box2.y2 - box2.y1);\n    let union = area1 + area2 - intersection;\n    intersection / union\n}\n\n/// scales the image to target dims with black padding.\nfn preprocess_yoloface_square(input_image: &DynamicImage, target_size: f32) -> Tensor {\n    let width = input_image.width();\n    let height = input_image.height();\n    let scale = target_size / (width.max(height) as f32);\n    let new_width = (width as f32 * scale) as u32;\n    let new_height = (height as f32 * scale) as u32;\n    let resized = image::imageops::resize(\n        &input_image.to_rgb8(),\n        new_width,\n        new_height,\n        image::imageops::FilterType::Triangle,\n    );\n    let mut padded = image::RgbImage::new(target_size as u32, target_size as u32);\n    image::imageops::replace(\n        &mut padded,\n        &resized,\n        (target_size as u32 - new_width) as i64 / 2,\n        (target_size as u32 - new_height) as i64 / 2,\n    );\n    let image: Tensor = tract_ndarray::Array4::from_shape_fn(\n        (1, 3, target_size as usize, target_size as usize),\n        |(_, c, y, x)| padded.get_pixel(x as u32, y as u32)[c] as f32 / 255.0,\n    )\n    .into();\n    image\n}\n"
  },
  {
    "path": "examples/keras-tract-tf2/.gitignore",
    "content": ".ipynb_checkpoints/\nAnaconda3-2022.10-Linux-x86_64.sh\nanaconda/\nvenv\n"
  },
  {
    "path": "examples/keras-tract-tf2/Cargo.toml",
    "content": "[package]\nname = \"keras-tract-tf2\"\nversion = \"0.20.7-pre\"\nauthors = [\"Matthew Alhonte <mattalhonte@gmail.com>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n\ntract-core.workspace = true\ntract-onnx.workspace = true\nndarray-npy.workspace = true\n\n"
  },
  {
    "path": "examples/keras-tract-tf2/README.md",
    "content": "A simple example of training a Tensorflow model with Python, check the model with tract python API, then [loading it into `tract`](src/main.rs) and compare predictions.\n\n# Python side training\n\nSetup [environment](requirements.txt).\n\n```\npip install -r requirements.txt\n```\n\n[Train](example.py) a model, export it to ONNX along with a input and output example.\n\n```\npython example.py\n```\n\n(Outputs are commited to git, you don't need to run the python step at all.)\n\n# Rust side inference\n\n[Run](src/main.rs) the model and double check the output.\n\n```\ncargo run\n```\n"
  },
  {
    "path": "examples/keras-tract-tf2/ci.sh.nope",
    "content": "#!/bin/sh\n\nset -e\n\n\nsudo apt-get update\n\n# Install required libraries\n# sudo apt-get install -y libgl1-mesa-glx libegl1-mesa libxrandr2 libxss1 libxcursor1 libxcomposite1 libasound2 libxi6 libxtst6 python3 python3-venv\nsudo apt-get install -y libxrandr2 libxss1 libxcursor1 libxcomposite1 libxi6 libxtst6 python3 python3-venv\n\npython3 -m venv venv\n. venv/bin/activate\n\n\npip install -r requirements.txt\npython example.py\ncargo run\n"
  },
  {
    "path": "examples/keras-tract-tf2/example.py",
    "content": "import numpy as np\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense, Activation\nimport tf2onnx\nimport tract\n\n# Define a simple demo model and training data\nmodel = Sequential([\n    Dense(32, activation='relu', input_dim=100,  name='main_input'),\n    Dense(1, activation='sigmoid', name=\"dense_1\"),\n])\nmodel.compile(optimizer='rmsprop',\n              loss='binary_crossentropy',\n              metrics=['accuracy'])\n\ndata = np.random.random((1000, 100))\nlabels = np.random.randint(2, size=(1000, 1))\n\n# Train the model, iterating on the data in batches of 32 samples\nmodel.fit(data, labels, epochs=10, batch_size=32)\n\n# Save the model in ONNX format to pass to tract\nmodel_proto, _ = tf2onnx.convert.from_keras(model, output_path=\"example.onnx\")\n\n# Generate a demo input, and run the model in Tensorflow\ninput = np.random.random((1,100)).astype(np.float32)\ntf_output = model.predict(input)\n\n# Run the model in tract and check output against TensorFlow\ntract_model = tract.onnx().model_for_path(\"example.onnx\")\ntract_model.set_output_fact(0, None)\ntract_output = tract_model.into_optimized().into_runnable().run([input])[0].to_numpy()\nassert(np.allclose(tf_output, tract_output))\n\n# Save input and reference output for Rust demo\nnp.savez(\"io.npz\", input=input, output=tf_output)\n"
  },
  {
    "path": "examples/keras-tract-tf2/requirements.txt",
    "content": "tensorflow==2.18.0\nnumpy==1.26.0\ntf2onnx==1.16.1\ntract==0.21.6\n"
  },
  {
    "path": "examples/keras-tract-tf2/src/main.rs",
    "content": "use tract_core::ndarray::{IxDyn, OwnedRepr};\nuse tract_onnx::prelude::*;\n\nfn main() -> TractResult<()> {\n    let model = tract_onnx::onnx()\n    // load the model\n    .model_for_path(\"example.onnx\")?\n    // optimize graph\n    .into_optimized()?\n    // make the model runnable and fix its inputs and outputs\n    .into_runnable()?;\n\n    // load input and expected output from npz file into tract tensors\n    let io = std::fs::File::open(\"io.npz\").unwrap();\n    let mut npz = ndarray_npy::NpzReader::new(io)?;\n    let input = npz.by_name::<OwnedRepr<f32>, IxDyn>(\"input.npy\").unwrap().into_tensor();\n    let expected = npz.by_name::<OwnedRepr<f32>, IxDyn>(\"output.npy\").unwrap().into_tensor();\n\n    // Input the generated data into the model\n    let found = model.run(tvec![input.into()]).unwrap().remove(0);\n    assert!(found.close_enough(&expected, true).is_ok());\n    Ok(())\n}\n"
  },
  {
    "path": "examples/nemo-nemotron-asr/.gitignore",
    "content": "assets\nreference\n.venv\n"
  },
  {
    "path": "examples/nemo-nemotron-asr/Cargo.toml",
    "content": "[package]\nname = \"nemo-nemotron-asr\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nfloat-ord.workspace = true\nhound = \"3.5.1\"\nitertools.workspace = true\nserde_json.workspace = true\ntract.workspace = true\n"
  },
  {
    "path": "examples/nemo-nemotron-asr/ci.sh",
    "content": "#!/bin/bash\n\nset -x\n\n[ -e .venv ] || python3 -m venv .venv\nsource .venv/bin/activate\n\npip install \"nemo-toolkit[asr]\" \"torch_to_nnef[nemo_tract]\"\n\nmkdir -p assets\nwget -qN https://dldata-public.s3.us-east-2.amazonaws.com/2086-149220-0033.wav -O assets/2086-149220-0033.wav\nrm -rf assets/model\nt2n_export_nemo -s nvidia/nemotron-speech-streaming-en-0.6b -e assets/model -tt skip --split-joint-decoder\n\n# Inject missing upper bound assertion into encoder model (~6.7min at 100Hz)\nenc_tgz=assets/model/encoder.nnef.tgz\ntmpdir=$(mktemp -d)\ntar xzf \"$enc_tgz\" -C \"$tmpdir\"\nsed -i '/^extension tract_symbol AUDIO_SIGNAL__TIME;/a extension tract_assert AUDIO_SIGNAL__TIME<=39993;' \"$tmpdir/graph.nnef\"\ntar czf \"$enc_tgz\" -C \"$tmpdir\" .\nrm -rf \"$tmpdir\"\n\ncargo run --release\nrm -rf assets\n"
  },
  {
    "path": "examples/nemo-nemotron-asr/nemotron.py",
    "content": "import numpy\nimport soundfile as sf\nimport torch\nimport os\nimport nemo.collections.asr as nemo_asr\n\nmodel_name = \"nvidia/nemotron-speech-streaming-en-0.6b\"\n\nasr = nemo_asr.models.ASRModel.from_pretrained(model_name=model_name)\n\nasr.eval()\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nasr = asr.to(device)\n\ndata, sr = sf.read(\"examples/nemo-nemotron-asr/assets/2086-149220-0033.wav\", dtype=\"float32\")\nsig = torch.tensor(data).unsqueeze(0)  # [1, T]\n\nsignal = sig.to(device)\nlength = torch.tensor([signal.shape[1]], device=device, dtype=torch.int64)\n\nwith torch.no_grad():\n    proc_out, proc_len = asr.preprocessor(\n        input_signal=signal, length=length\n    )\n    enc_out, enc_len = asr.encoder(audio_signal=proc_out, length=proc_len)\n\nos.makedirs(model_name + \"/preprocessor\", exist_ok=True)\nos.makedirs(model_name + \"/encoder\", exist_ok=True)\nos.makedirs(model_name + \"/decoder\", exist_ok=True)\nos.makedirs(model_name + \"/joint\", exist_ok=True)\n\nnumpy.savez(model_name + \"/preprocessor/io.npz\",\n    input_signal=signal.cpu(),\n    length=length.cpu(),\n    processed_signal=proc_out.cpu(),\n    processed_length=proc_len.cpu()\n)\nnumpy.savez(model_name + \"/encoder/io.npz\",\n    audio_signal=proc_out.cpu(),\n    length=proc_len.cpu(),\n    encoded_lengths=enc_len.cpu(),\n    outputs=enc_out.cpu()\n)\n\nencoded = enc_out.transpose(1, 2)\n\nT = int(enc_len[0].item())\nt = 0\np = 0\nj = 0\nmax_output_len = 6 * T + 10\nhyp = []\n\nvocab = asr.joint.vocabulary\nvocab_size = len(vocab)\nblank_id = asr.decoding.blank_id\n\nprint(f\"vocab_size={vocab_size} blank_id={blank_id}\")\n\nwith torch.no_grad():\n    prediction, state = asr.decoder.predict(add_sos=True, batch_size=1)\n    numpy.savez(model_name + \"/decoder/warmup-io.npz\", **{\n        \"targets\": numpy.array([[blank_id]], dtype=numpy.int32),\n        \"states_0\": numpy.zeros([2, 1, 640], dtype=numpy.float32),\n        \"states_1\": numpy.zeros([2, 1, 640], dtype=numpy.float32),\n        \"outputs\": prediction.transpose(1, 2).cpu().numpy(),\n        \"out_states_0\": state[0].cpu().numpy(),\n        \"out_states_1\": state[1].cpu().numpy(),\n    })\n\n    while t < T and len(hyp) < max_output_len:\n        enc_frame = encoded[:, t:t+1, :]\n        joint_logits = asr.joint.joint(enc_frame, prediction[:, -1:, :])\n        numpy.savez(f\"{model_name}/joint/turn-{j}-io.npz\",\n            encoder_outputs=enc_frame.transpose(1, 2).cpu(),\n            decoder_outputs=prediction.transpose(1, 2)[:, :, -1:].cpu(),\n            outputs=joint_logits.cpu()\n        )\n        j += 1\n        k = int(torch.argmax(joint_logits[..., :(vocab_size + 1)], dim=-1).item())\n        print(f\"t={t} k={k}\")\n        if k == blank_id:\n            # Standard RNNT: advance by 1 frame on blank\n            t += 1\n        else:\n            p += 1\n            hyp.append(k)\n            last_token = torch.tensor([[k]], device=device, dtype=torch.int32)\n            prediction, new_state = asr.decoder.predict(y=last_token, add_sos=False, state=state)\n            numpy.savez(f\"{model_name}/decoder/turn-{p}-io.npz\", **{\n                \"targets\": last_token.cpu(),\n                \"states_0\": state[0].cpu(),\n                \"states_1\": state[1].cpu(),\n                \"outputs\": prediction.transpose(1, 2).cpu(),\n                \"out_states_0\": new_state[0].cpu(),\n                \"out_states_1\": new_state[1].cpu(),\n            })\n            state = new_state\n\nprint(hyp)\nprint(f\"p={p} j={j}\")\npieces = [vocab[i] for i in hyp if 0 <= i < len(vocab)]\ntext = \"\".join(pieces)\nprint(text)\n"
  },
  {
    "path": "examples/nemo-nemotron-asr/src/main.rs",
    "content": "use std::fs::File;\n\nuse anyhow::*;\nuse float_ord::FloatOrd;\nuse itertools::Itertools;\nuse tract::prelude::tract_ndarray::prelude::*;\nuse tract::prelude::*;\n\nfn argmax(slice: &[f32]) -> Option<usize> {\n    slice.into_iter().position_max_by_key(|x| FloatOrd(**x))\n}\n\nfn concretize_batch(mut model: Model) -> anyhow::Result<Model> {\n    model.transform(ConcretizeSymbols::new().value(\"BATCH\", 1))?;\n    Ok(model)\n}\n\nfn remove_length_input(mut model: Model) -> anyhow::Result<Model> {\n    model\n        .transform(r#\"{\"name\":\"patch\",\"body\":\"length = tract_core_shape_of(input_signal)[1];\"}\"#)?;\n    Ok(model)\n}\n\nfn main() -> anyhow::Result<()> {\n    let config: serde_json::Value =\n        serde_json::from_reader(File::open(\"assets/model/model_config.json\")?)?;\n    let blank_id = config.pointer(\"/decoder/vocab_size\").unwrap().as_i64().unwrap() as usize;\n    let vocab = config.pointer(\"/joint/vocabulary\").unwrap().as_array().unwrap();\n    let vocab: Vec<&str> = vocab.iter().map(|v| v.as_str().unwrap()).collect();\n\n    let nnef = tract::nnef()?.with_tract_core()?.with_tract_transformers()?;\n    let gpu = [\"cuda\", \"metal\", \"default\"]\n        .iter()\n        .find_map(|rt| tract::runtime_for_name(rt).ok())\n        .unwrap();\n\n    let patched_preprocessor =\n        remove_length_input(concretize_batch(nnef.load(\"assets/model/preprocessor.nnef.tgz\")?)?)?;\n    nnef.write_model_to_tar_gz(\n        \"assets/model/preprocessor_patched.nnef.tgz\",\n        &patched_preprocessor,\n    )?;\n    let preprocessor = patched_preprocessor.into_runnable()?;\n\n    let mut encoder = nnef.load(\"assets/model/encoder.nnef.tgz\")?;\n    encoder.transform(\"transformers_detect_all\")?;\n    let encoder = gpu.prepare(concretize_batch(encoder)?)?;\n\n    let decoder = gpu.prepare(concretize_batch(nnef.load(\"assets/model/decoder.nnef.tgz\")?)?)?;\n    let joint = gpu.prepare(concretize_batch(nnef.load(\"assets/model/joint.nnef.tgz\")?)?)?;\n\n    // soundfile (Python) normalizes i16 PCM to [-1, 1]; match that here\n    let wav: Vec<f32> = hound::WavReader::open(\"assets/2086-149220-0033.wav\")?\n        .samples::<i16>()\n        .map(|x| x.unwrap() as f32 / 32768.0)\n        .collect();\n    let samples = Tensor::from_slice(&[1, wav.len()], &wav)?;\n\n    let [features, feat_len] = preprocessor.run([samples])?.try_into().unwrap();\n    let [encoded, _lens] = encoder.run([features, feat_len])?.try_into().unwrap();\n\n    let encoded: ArrayD<f32> = encoded.view()?.into_owned();\n\n    let max_frames = encoded.shape()[2];\n    let max_len = max_frames * 6 + 10;\n\n    let mut hyp = vec![];\n    let mut frame_ix = 0;\n\n    // Warm-up: NeMo's predict(add_sos=True, y=None) prepends a zero vector then processes\n    // one zero embedding — 2 steps of zero input total. Token blank_id has zero embedding\n    // (padding_idx=blank_id), so pass [blank_id, blank_id] and take the last output step.\n    let warmup_tokens = Tensor::from_slice(&[1, 2], &[blank_id as i32, blank_id as i32])?;\n    let state_0 = tensor(Array3::<f32>::zeros([2, 1, 640]))?;\n    let state_1 = tensor(Array3::<f32>::zeros([2, 1, 640]))?;\n    let [warmup_out, mut state_0, mut state_1] =\n        decoder.run([warmup_tokens, state_0, state_1])?.try_into().unwrap();\n    // warmup_out shape: [1, 640, 2] — take last timestep → [1, 640, 1]\n    let warmup_out: ArrayD<f32> = warmup_out.view()?.into_owned();\n    let mut token: Tensor = tensor(warmup_out.slice_axis(Axis(2), (1..2).into()).to_owned())?;\n\n    while hyp.len() < max_len && frame_ix < max_frames {\n        let frame: Tensor = tensor(encoded.slice_axis(Axis(2), (frame_ix..frame_ix + 1).into()))?;\n        let [logits] = joint.run([frame, token.clone()])?.try_into().unwrap();\n        let logits = logits.view::<f32>()?;\n        let logits = logits.as_slice().unwrap();\n        let token_id = argmax(logits).unwrap();\n        if token_id == blank_id {\n            frame_ix += 1;\n        } else {\n            hyp.push(token_id);\n            token = Tensor::from_slice(&[1, 1], &[token_id as i32])?;\n            [token, state_0, state_1] = decoder.run([token, state_0, state_1])?.try_into().unwrap();\n        }\n    }\n\n    let transcript = hyp.into_iter().map(|t| vocab[t]).join(\"\");\n    println!(\"Transcript: {transcript}\");\n    assert_eq!(\n        transcript,\n        \"▁well▁I▁don't▁wish▁to▁see▁it▁any▁more▁observed▁Phoebe,▁turning▁away▁her▁eyes.▁It▁is▁certainly▁very▁like▁the▁old▁portrait\"\n    );\n    Ok(())\n}\n"
  },
  {
    "path": "examples/nemo-parakeet-asr/.gitignore",
    "content": "assets\n"
  },
  {
    "path": "examples/nemo-parakeet-asr/Cargo.toml",
    "content": "[package]\nname = \"nemo-parakeet-asr\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nfloat-ord.workspace = true\nhound = \"3.5.1\"\nitertools.workspace = true\nserde_json.workspace = true\ntract.workspace = true\n"
  },
  {
    "path": "examples/nemo-parakeet-asr/ci.sh",
    "content": "#!/bin/bash\n\nset -x\n\n[ -e .venv ] || python3 -m venv .venv\nsource .venv/bin/activate\n\n pip install  \"nemo-toolkit[asr]\" \"torch_to_nnef[nemo_tract]\" \n\nmkdir -p assets\nwget -qN https://dldata-public.s3.us-east-2.amazonaws.com/2086-149220-0033.wav -O  assets/2086-149220-0033.wav\nrm -rf assets/model\nt2n_export_nemo -s nvidia/parakeet-tdt-0.6b-v3 -e assets/model -tt skip --split-joint-decoder\n\n# Inject missing upper bound assertion into encoder model (~6.7min at 100Hz)\nenc_tgz=assets/model/encoder.nnef.tgz\ntmpdir=$(mktemp -d)\ntar xzf \"$enc_tgz\" -C \"$tmpdir\"\nsed -i '/^extension tract_symbol S;/a extension tract_assert S<=39993;' \"$tmpdir/graph.nnef\"\ntar czf \"$enc_tgz\" -C \"$tmpdir\" .\nrm -rf \"$tmpdir\"\n\ncargo run --release\nrm -rf assets\n"
  },
  {
    "path": "examples/nemo-parakeet-asr/parakeet.py",
    "content": "import numpy\nimport torchaudio\nimport torch\nimport os\nimport nemo.collections.asr as nemo_asr\n\n# model_name = \"nvidia/parakeet-rnnt-1.1b\"\n# model_name = \"nvidia/parakeet-tdt-0.6b-v2\"\nmodel_name = \"nvidia/parakeet-tdt-0.6b-v3\"\n \nasr = nemo_asr.models.ASRModel.from_pretrained(model_name=model_name)\n\n# os.makedirs(model_name + \"/encoder\", exist_ok=True)\n# os.makedirs(model_name + \"/decoder\", exist_ok=True)\n# os.makedirs(model_name + \"/joint\", exist_ok=True)\n\n# asr.encoder.export(model_name + \"/encoder/encoder.onnx\")\n# asr.decoder.export(model_name + \"/decoder/decoder.onnx\")\n# asr.joint.export(model_name + \"/joint/joint.onnx\")\n\nasr.eval()\ndevice = \"cuda\"\n\n# ▁well▁i▁don't▁wish▁to▁see▁it▁any▁more▁observed▁phoebe▁turning▁away▁her▁eyes▁it▁is▁certainly▁very▁like▁the▁old▁portrait\nsig, sr = torchaudio.load(\"2086-149220-0033.wav\")\n\nsignal = sig.to(\"cuda\")\nlength = torch.tensor([signal.shape[1]], device=device, dtype=torch.int64)\n\nwith torch.no_grad():\n    proc_out, proc_len = asr.preprocessor(\n        input_signal=signal, length=length\n    )\n    enc_out, enc_len = asr.encoder(audio_signal=proc_out, length=proc_len)\n\nnumpy.savez(model_name + \"/featurizer.npz\", { \"signal\": sig, \"features\": proc_out })\nnumpy.savez(model_name + \"/encoder/io.npz\",\n    audio_signal=proc_out.to(\"cpu\"),\n    length= proc_len.to(\"cpu\"),\n    encoded_lengths= enc_len.to(\"cpu\"),\n    outputs= enc_out.to(\"cpu\")\n)\n\nencoded = enc_out.transpose(1,2)\n\nT = int(enc_len[0].item())\nt = 0\np = 0\nj = 0\nmax_output_len = 6 * T + 10\nhyp = []\n\nvocab = asr.joint.vocabulary\nvocab_size = len(vocab)\nblank_id = asr.decoding.blank_id\n\nprint(vocab_size, blank_id)\n\nwith torch.no_grad():\n    prediction, state = asr.decoder.predict(add_sos=True, batch_size=1)\n\n    while t < T and len(hyp) < max_output_len:\n        enc_frame = encoded[:, t:t+1, :]\n        joint_logits = asr.joint.joint(enc_frame, prediction[:,-1:,:])\n        numpy.savez(f\"{model_name}/joint/turn-{j}-io.npz\",\n            encoder_outputs=enc_frame.transpose(1,2).cpu(),\n            decoder_outputs=prediction.transpose(1,2)[:, :, -1:].cpu(),\n            outputs=joint_logits.cpu()\n            \n        )\n        j += 1\n        k = int(torch.argmax(joint_logits[...,:(vocab_size+1)], dim=-1).item())\n        print(f\"t={t} k={k}\")\n        if k == asr.decoding.blank_id:\n            dur_logits = joint_logits[..., (vocab_size+1):]\n            if dur_logits.shape[-1] > 0:\n                t += int(torch.argmax(dur_logits, dim=-1).item())\n            else:\n                t += 1\n        else:\n            p += 1\n            hyp.append(k)\n            last_token = torch.tensor([[k]], device=device, dtype=torch.long)\n            prediction, new_state = asr.decoder.predict(y = last_token, add_sos = False, state=state)\n            numpy.savez(f\"{model_name}/decoder/turn-{p}-io.npz\",\n                **{ \"targets\": last_token.cpu(),\n                \"target_length\": [1],\n                \"states.1\": state[0].cpu(),\n                \"onnx::Slice_3\":state[1].cpu(),\n                \"outputs\": prediction.transpose(1,2).cpu(),\n                # \"prednet_length\": [[1]],\n                \"prednet_length\": [[prediction.shape[1]]],\n                \"states\": new_state[0].cpu(),\n                \"162\": new_state[1].cpu(),\n            })\n            state = new_state\nprint(hyp)\nprint(\"p\", p, \"j\", j)\nvocab = asr.joint.vocabulary\npieces = [vocab[i] for i in hyp if 0 <= i < len(vocab)]\ntext = \"\".join(pieces)\nprint(text)\n\n\n"
  },
  {
    "path": "examples/nemo-parakeet-asr/src/main.rs",
    "content": "use std::fs::File;\n\nuse anyhow::*;\nuse float_ord::FloatOrd;\nuse itertools::Itertools;\nuse tract::prelude::tract_ndarray::prelude::*;\nuse tract::prelude::*;\n\nfn argmax(slice: &[f32]) -> Option<usize> {\n    slice.into_iter().position_max_by_key(|x| FloatOrd(**x))\n}\n\nfn main() -> anyhow::Result<()> {\n    let config: serde_json::Value =\n        serde_json::from_reader(File::open(\"assets/model/model_config.json\")?)?;\n    let blank_id = config.pointer(\"/decoder/vocab_size\").unwrap().as_i64().unwrap() as usize;\n    let vocab = config.pointer(\"/joint/vocabulary\").unwrap().as_array().unwrap();\n    let vocab: Vec<&str> = vocab.iter().map(|v| v.as_str().unwrap()).collect();\n\n    let nnef = tract::nnef()?.with_tract_core()?.with_tract_transformers()?;\n    let gpu = [\"cuda\", \"metal\", \"default\"]\n        .iter()\n        .find_map(|rt| tract::runtime_for_name(rt).ok())\n        .unwrap();\n\n    let preprocessor = nnef.load(\"assets/model/preprocessor.nnef.tgz\")?.into_runnable()?;\n\n    let mut encoder = nnef.load(\"assets/model/encoder.nnef.tgz\")?;\n    encoder.transform(\"transformers_detect_all\")?;\n    let encoder = gpu.prepare(encoder)?;\n\n    let decoder = nnef.load(\"assets/model/decoder.nnef.tgz\")?;\n    let decoder = gpu.prepare(decoder)?;\n\n    let joint = nnef.load(\"assets/model/joint.nnef.tgz\")?;\n    let joint = gpu.prepare(joint)?;\n\n    let wav: Vec<f32> = hound::WavReader::open(\"assets/2086-149220-0033.wav\")?\n        .samples::<i16>()\n        .map(|x| x.unwrap() as f32)\n        .collect();\n    let samples = Tensor::from_slice(&[1, wav.len()], &wav)?;\n    let len = tensor(arr1(&[wav.len() as i64]))?;\n\n    let [features, feat_len] = preprocessor.run([samples, len])?.try_into().unwrap();\n    let [encoded, _lens] = encoder.run([features, feat_len])?.try_into().unwrap();\n\n    let encoded: ArrayD<f32> = encoded.view()?.into_owned();\n\n    let max_frames = encoded.shape()[2];\n    let max_len = max_frames * 6 + 10;\n\n    let mut hyp = vec![];\n    let mut frame_ix = 0;\n    let mut token = Tensor::from_slice(&[1, 1], &[0i32])?;\n    let mut state_0 = tensor(Array3::<f32>::zeros([2, 1, 640]))?;\n    let mut state_1 = tensor(Array3::<f32>::zeros([2, 1, 640]))?;\n\n    [token, state_0, state_1] = decoder.run([token, state_0, state_1])?.try_into().unwrap();\n    while hyp.len() < max_len && frame_ix < max_frames {\n        let frame = tensor(encoded.slice_axis(Axis(2), (frame_ix..frame_ix + 1).into()))?;\n        let [logits] = joint.run([frame, token.clone()])?.try_into().unwrap();\n        let logits = logits.view::<f32>()?;\n        let logits = logits.as_slice().unwrap();\n        let token_id = argmax(&logits[0..blank_id + 1]).unwrap();\n        if token_id == blank_id {\n            frame_ix += argmax(&logits[blank_id + 1..]).unwrap_or(0).max(1);\n        } else {\n            hyp.push(token_id);\n            token = Tensor::from_slice(&[1, 1], &[token_id as i32])?;\n            [token, state_0, state_1] = decoder.run([token, state_0, state_1])?.try_into().unwrap();\n        }\n    }\n\n    let transcript = hyp.into_iter().map(|t| vocab[t]).join(\"\");\n    println!(\"Transcript: {transcript}\");\n    assert_eq!(\n        transcript,\n        \"▁Well,▁I▁don't▁wish▁to▁see▁it▁any▁more,▁observed▁Phoebe,▁turning▁away▁her▁eyes.\"\n    );\n    Ok(())\n}\n"
  },
  {
    "path": "examples/nnef-dump-mobilenet-v2/.gitignore",
    "content": "mobilenet*\n"
  },
  {
    "path": "examples/nnef-dump-mobilenet-v2/Cargo.toml",
    "content": "[package]\nname = \"example-dump-nnef-mobilenet-v2\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nimage.workspace = true\ntract.workspace = true\n"
  },
  {
    "path": "examples/nnef-dump-mobilenet-v2/README.md",
    "content": "# Tract Example: Translating to NNEF \n\nUsing NNEF instead of TensorFlow or ONNX allow for faster loading time and\nsmaller binaries, but require precompilation of the networks.\n\nThis example shows how to translate a Neural Network to NNEF (with extensions\nif needed), then use this translated network for prediction.\n\n```sh\ngit clone https://github.com/snipsco/tract\ncd tract/examples/nnef-mobilenet-v2/\n```\n\n## Installing tract command line tool\n\nThis one is going to take a while.\n\n```sh\ncargo install tract\n```\n\n## Obtaining the model \n\nMobileNet is a response to the ImageNet challenge. The goal is to categorize\nimages and associate them with one of 1000 labels. In other words, recognize a\ndog, a cat, a rabbit, or a military uniform.\n\nYou will need to download the models. For instance:\n\n```sh\nwget https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.4_224.tgz\ntar zxf mobilenet_v2_1.4_224.tgz\n```\n\nThis expands a half-dozen files in the directory. The only one of interest\nfor us is the frozen TensorFlow model: `mobilenet_v2_1.4_224_frozen.pb`.\n\n## Converting the network\n\nWe need to tell tract about the input shape and types. TensorFlow uses the NHWC\nconvention, and the variant of Mobilenet we picked operates on inputs of\n224x224 pixels:\n\n```sh\ntract mobilenet_v2_1.4_224_frozen.pb -i 1,224,224,3,f32 dump --nnef mobilenet.nnef.tgz\n```\n\n## Running with tract_nnef\n\n```\nuse tract_nnef::prelude::*;\n\nfn main() -> TractResult<()> {\n    let model = tract_nnef::nnef()\n        .model_for_path(\"mobilenet.nnef.tgz\")?\n        // optimize the model\n        .into_optimized()?\n        // make the model runnable and fix its inputs and outputs\n        .into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let image: Tensor = tract_ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    })\n    .into();\n\n    // run the model on the input\n    let result = model.run(tvec!(image.into()))?;\n\n    // find and display the max value with its index\n    let best = result[0]\n        .to_array_view::<f32>()?\n        .iter()\n        .cloned()\n        .zip(1..)\n        .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap());\n    println!(\"result: {:?}\", best);\n    Ok(())\n}\n```\n\nCompared to running the original mobilenet TensorFlow model (see\n[../tensorflow-mobilenet-v2](tensorflow example), there is no longer need to\ngive the input size hint (they are now embedded in the graph description).\nSimilarly, the InferenceModel and InferenceFact have disappeared: we no longer\nneed to operate with partially typed networks.\n"
  },
  {
    "path": "examples/nnef-dump-mobilenet-v2/ci.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nwget -nc -q https://s3.amazonaws.com/tract-ci-builds/model/mobilenet_v2_1.4_224.tgz\ntar zxf mobilenet_v2_1.4_224.tgz\n\ncargo run -p tract-cli -- mobilenet_v2_1.4_224_frozen.pb -i 1,224,224,3,f32 dump --nnef mobilenet.nnef.tgz\ncargo run\n"
  },
  {
    "path": "examples/nnef-dump-mobilenet-v2/src/main.rs",
    "content": "use anyhow::Result;\nuse tract::prelude::*;\n\nfn main() -> Result<()> {\n    let model = tract::nnef()?.with_tract_core()?.load(\"mobilenet.nnef.tgz\")?.into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let input = tract_ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    });\n\n    // run the model on the input\n    let result = model.run([input])?;\n\n    // find and display the max value with its index\n    let best =\n        result[0].as_slice::<f32>()?.iter().zip(1..).max_by(|a, b| a.0.partial_cmp(b.0).unwrap());\n    println!(\"result: {best:?}\");\n    Ok(())\n}\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2/.gitignore",
    "content": "mobilenet_v2_1.0.onnx.nnef.tgz\nmobilenetv2-7.onnx\n\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2/Cargo.toml",
    "content": "[package]\nname = \"example-nnef-mobilenet-v2\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nimage.workspace = true\ntract.workspace = true\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2/ci.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nwget -q https://sfo2.digitaloceanspaces.com/nnef-public/mobilenet_v2_1.0.onnx.nnef.tgz -O mobilenet_v2_1.0.onnx.nnef.tgz\n\ncargo run\nrm mobilenet_v2_1.0.onnx.nnef.tgz\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2/imagenet_slim_labels.txt",
    "content": "dummy\ntench\ngoldfish\ngreat white shark\ntiger shark\nhammerhead\nelectric ray\nstingray\ncock\nhen\nostrich\nbrambling\ngoldfinch\nhouse finch\njunco\nindigo bunting\nrobin\nbulbul\njay\nmagpie\nchickadee\nwater ouzel\nkite\nbald eagle\nvulture\ngreat grey owl\nEuropean fire salamander\ncommon newt\neft\nspotted salamander\naxolotl\nbullfrog\ntree frog\ntailed frog\nloggerhead\nleatherback turtle\nmud turtle\nterrapin\nbox turtle\nbanded gecko\ncommon iguana\nAmerican chameleon\nwhiptail\nagama\nfrilled lizard\nalligator lizard\nGila monster\ngreen lizard\nAfrican chameleon\nKomodo dragon\nAfrican crocodile\nAmerican alligator\ntriceratops\nthunder snake\nringneck snake\nhognose snake\ngreen snake\nking snake\ngarter snake\nwater snake\nvine snake\nnight snake\nboa constrictor\nrock python\nIndian cobra\ngreen mamba\nsea snake\nhorned viper\ndiamondback\nsidewinder\ntrilobite\nharvestman\nscorpion\nblack and gold garden spider\nbarn spider\ngarden spider\nblack widow\ntarantula\nwolf spider\ntick\ncentipede\nblack grouse\nptarmigan\nruffed grouse\nprairie chicken\npeacock\nquail\npartridge\nAfrican grey\nmacaw\nsulphur-crested cockatoo\nlorikeet\ncoucal\nbee eater\nhornbill\nhummingbird\njacamar\ntoucan\ndrake\nred-breasted merganser\ngoose\nblack swan\ntusker\nechidna\nplatypus\nwallaby\nkoala\nwombat\njellyfish\nsea anemone\nbrain coral\nflatworm\nnematode\nconch\nsnail\nslug\nsea slug\nchiton\nchambered nautilus\nDungeness crab\nrock crab\nfiddler crab\nking crab\nAmerican lobster\nspiny lobster\ncrayfish\nhermit crab\nisopod\nwhite stork\nblack stork\nspoonbill\nflamingo\nlittle blue heron\nAmerican egret\nbittern\ncrane\nlimpkin\nEuropean gallinule\nAmerican coot\nbustard\nruddy turnstone\nred-backed sandpiper\nredshank\ndowitcher\noystercatcher\npelican\nking penguin\nalbatross\ngrey whale\nkiller whale\ndugong\nsea lion\nChihuahua\nJapanese spaniel\nMaltese dog\nPekinese\nShih-Tzu\nBlenheim spaniel\npapillon\ntoy terrier\nRhodesian ridgeback\nAfghan hound\nbasset\nbeagle\nbloodhound\nbluetick\nblack-and-tan coonhound\nWalker hound\nEnglish foxhound\nredbone\nborzoi\nIrish wolfhound\nItalian greyhound\nwhippet\nIbizan hound\nNorwegian elkhound\notterhound\nSaluki\nScottish deerhound\nWeimaraner\nStaffordshire bullterrier\nAmerican Staffordshire terrier\nBedlington terrier\nBorder terrier\nKerry blue terrier\nIrish terrier\nNorfolk terrier\nNorwich terrier\nYorkshire terrier\nwire-haired fox terrier\nLakeland terrier\nSealyham terrier\nAiredale\ncairn\nAustralian terrier\nDandie Dinmont\nBoston bull\nminiature schnauzer\ngiant schnauzer\nstandard schnauzer\nScotch terrier\nTibetan terrier\nsilky terrier\nsoft-coated wheaten terrier\nWest Highland white terrier\nLhasa\nflat-coated retriever\ncurly-coated retriever\ngolden retriever\nLabrador retriever\nChesapeake Bay retriever\nGerman short-haired pointer\nvizsla\nEnglish setter\nIrish setter\nGordon setter\nBrittany spaniel\nclumber\nEnglish springer\nWelsh springer spaniel\ncocker spaniel\nSussex spaniel\nIrish water spaniel\nkuvasz\nschipperke\ngroenendael\nmalinois\nbriard\nkelpie\nkomondor\nOld English sheepdog\nShetland sheepdog\ncollie\nBorder collie\nBouvier des Flandres\nRottweiler\nGerman shepherd\nDoberman\nminiature pinscher\nGreater Swiss Mountain dog\nBernese mountain dog\nAppenzeller\nEntleBucher\nboxer\nbull mastiff\nTibetan mastiff\nFrench bulldog\nGreat Dane\nSaint Bernard\nEskimo dog\nmalamute\nSiberian husky\ndalmatian\naffenpinscher\nbasenji\npug\nLeonberg\nNewfoundland\nGreat Pyrenees\nSamoyed\nPomeranian\nchow\nkeeshond\nBrabancon griffon\nPembroke\nCardigan\ntoy poodle\nminiature poodle\nstandard poodle\nMexican hairless\ntimber wolf\nwhite wolf\nred wolf\ncoyote\ndingo\ndhole\nAfrican hunting dog\nhyena\nred fox\nkit fox\nArctic fox\ngrey fox\ntabby\ntiger cat\nPersian cat\nSiamese cat\nEgyptian cat\ncougar\nlynx\nleopard\nsnow leopard\njaguar\nlion\ntiger\ncheetah\nbrown bear\nAmerican black bear\nice bear\nsloth bear\nmongoose\nmeerkat\ntiger beetle\nladybug\nground beetle\nlong-horned beetle\nleaf beetle\ndung beetle\nrhinoceros beetle\nweevil\nfly\nbee\nant\ngrasshopper\ncricket\nwalking stick\ncockroach\nmantis\ncicada\nleafhopper\nlacewing\ndragonfly\ndamselfly\nadmiral\nringlet\nmonarch\ncabbage butterfly\nsulphur butterfly\nlycaenid\nstarfish\nsea urchin\nsea cucumber\nwood rabbit\nhare\nAngora\nhamster\nporcupine\nfox squirrel\nmarmot\nbeaver\nguinea pig\nsorrel\nzebra\nhog\nwild boar\nwarthog\nhippopotamus\nox\nwater buffalo\nbison\nram\nbighorn\nibex\nhartebeest\nimpala\ngazelle\nArabian camel\nllama\nweasel\nmink\npolecat\nblack-footed ferret\notter\nskunk\nbadger\narmadillo\nthree-toed sloth\norangutan\ngorilla\nchimpanzee\ngibbon\nsiamang\nguenon\npatas\nbaboon\nmacaque\nlangur\ncolobus\nproboscis monkey\nmarmoset\ncapuchin\nhowler monkey\ntiti\nspider monkey\nsquirrel monkey\nMadagascar cat\nindri\nIndian elephant\nAfrican elephant\nlesser panda\ngiant panda\nbarracouta\neel\ncoho\nrock beauty\nanemone fish\nsturgeon\ngar\nlionfish\npuffer\nabacus\nabaya\nacademic gown\naccordion\nacoustic guitar\naircraft carrier\nairliner\nairship\naltar\nambulance\namphibian\nanalog clock\napiary\napron\nashcan\nassault rifle\nbackpack\nbakery\nbalance beam\nballoon\nballpoint\nBand Aid\nbanjo\nbannister\nbarbell\nbarber chair\nbarbershop\nbarn\nbarometer\nbarrel\nbarrow\nbaseball\nbasketball\nbassinet\nbassoon\nbathing cap\nbath towel\nbathtub\nbeach wagon\nbeacon\nbeaker\nbearskin\nbeer bottle\nbeer glass\nbell cote\nbib\nbicycle-built-for-two\nbikini\nbinder\nbinoculars\nbirdhouse\nboathouse\nbobsled\nbolo tie\nbonnet\nbookcase\nbookshop\nbottlecap\nbow\nbow tie\nbrass\nbrassiere\nbreakwater\nbreastplate\nbroom\nbucket\nbuckle\nbulletproof vest\nbullet train\nbutcher shop\ncab\ncaldron\ncandle\ncannon\ncanoe\ncan opener\ncardigan\ncar mirror\ncarousel\ncarpenter's kit\ncarton\ncar wheel\ncash machine\ncassette\ncassette player\ncastle\ncatamaran\nCD player\ncello\ncellular telephone\nchain\nchainlink fence\nchain mail\nchain saw\nchest\nchiffonier\nchime\nchina cabinet\nChristmas stocking\nchurch\ncinema\ncleaver\ncliff dwelling\ncloak\nclog\ncocktail shaker\ncoffee mug\ncoffeepot\ncoil\ncombination lock\ncomputer keyboard\nconfectionery\ncontainer ship\nconvertible\ncorkscrew\ncornet\ncowboy boot\ncowboy hat\ncradle\ncrane\ncrash helmet\ncrate\ncrib\nCrock Pot\ncroquet ball\ncrutch\ncuirass\ndam\ndesk\ndesktop computer\ndial telephone\ndiaper\ndigital clock\ndigital watch\ndining table\ndishrag\ndishwasher\ndisk brake\ndock\ndogsled\ndome\ndoormat\ndrilling platform\ndrum\ndrumstick\ndumbbell\nDutch oven\nelectric fan\nelectric guitar\nelectric locomotive\nentertainment center\nenvelope\nespresso maker\nface powder\nfeather boa\nfile\nfireboat\nfire engine\nfire screen\nflagpole\nflute\nfolding chair\nfootball helmet\nforklift\nfountain\nfountain pen\nfour-poster\nfreight car\nFrench horn\nfrying pan\nfur coat\ngarbage truck\ngasmask\ngas pump\ngoblet\ngo-kart\ngolf ball\ngolfcart\ngondola\ngong\ngown\ngrand piano\ngreenhouse\ngrille\ngrocery store\nguillotine\nhair slide\nhair spray\nhalf track\nhammer\nhamper\nhand blower\nhand-held computer\nhandkerchief\nhard disc\nharmonica\nharp\nharvester\nhatchet\nholster\nhome theater\nhoneycomb\nhook\nhoopskirt\nhorizontal bar\nhorse cart\nhourglass\niPod\niron\njack-o'-lantern\njean\njeep\njersey\njigsaw puzzle\njinrikisha\njoystick\nkimono\nknee pad\nknot\nlab coat\nladle\nlampshade\nlaptop\nlawn mower\nlens cap\nletter opener\nlibrary\nlifeboat\nlighter\nlimousine\nliner\nlipstick\nLoafer\nlotion\nloudspeaker\nloupe\nlumbermill\nmagnetic compass\nmailbag\nmailbox\nmaillot\nmaillot\nmanhole cover\nmaraca\nmarimba\nmask\nmatchstick\nmaypole\nmaze\nmeasuring cup\nmedicine chest\nmegalith\nmicrophone\nmicrowave\nmilitary uniform\nmilk can\nminibus\nminiskirt\nminivan\nmissile\nmitten\nmixing bowl\nmobile home\nModel T\nmodem\nmonastery\nmonitor\nmoped\nmortar\nmortarboard\nmosque\nmosquito net\nmotor scooter\nmountain bike\nmountain tent\nmouse\nmousetrap\nmoving van\nmuzzle\nnail\nneck brace\nnecklace\nnipple\nnotebook\nobelisk\noboe\nocarina\nodometer\noil filter\norgan\noscilloscope\noverskirt\noxcart\noxygen mask\npacket\npaddle\npaddlewheel\npadlock\npaintbrush\npajama\npalace\npanpipe\npaper towel\nparachute\nparallel bars\npark bench\nparking meter\npassenger car\npatio\npay-phone\npedestal\npencil box\npencil sharpener\nperfume\nPetri dish\nphotocopier\npick\npickelhaube\npicket fence\npickup\npier\npiggy bank\npill bottle\npillow\nping-pong ball\npinwheel\npirate\npitcher\nplane\nplanetarium\nplastic bag\nplate rack\nplow\nplunger\nPolaroid camera\npole\npolice van\nponcho\npool table\npop bottle\npot\npotter's wheel\npower drill\nprayer rug\nprinter\nprison\nprojectile\nprojector\npuck\npunching bag\npurse\nquill\nquilt\nracer\nracket\nradiator\nradio\nradio telescope\nrain barrel\nrecreational vehicle\nreel\nreflex camera\nrefrigerator\nremote control\nrestaurant\nrevolver\nrifle\nrocking chair\nrotisserie\nrubber eraser\nrugby ball\nrule\nrunning shoe\nsafe\nsafety pin\nsaltshaker\nsandal\nsarong\nsax\nscabbard\nscale\nschool bus\nschooner\nscoreboard\nscreen\nscrew\nscrewdriver\nseat belt\nsewing machine\nshield\nshoe shop\nshoji\nshopping basket\nshopping cart\nshovel\nshower cap\nshower curtain\nski\nski mask\nsleeping bag\nslide rule\nsliding door\nslot\nsnorkel\nsnowmobile\nsnowplow\nsoap dispenser\nsoccer ball\nsock\nsolar dish\nsombrero\nsoup bowl\nspace bar\nspace heater\nspace shuttle\nspatula\nspeedboat\nspider web\nspindle\nsports car\nspotlight\nstage\nsteam locomotive\nsteel arch bridge\nsteel drum\nstethoscope\nstole\nstone wall\nstopwatch\nstove\nstrainer\nstreetcar\nstretcher\nstudio couch\nstupa\nsubmarine\nsuit\nsundial\nsunglass\nsunglasses\nsunscreen\nsuspension bridge\nswab\nsweatshirt\nswimming trunks\nswing\nswitch\nsyringe\ntable lamp\ntank\ntape player\nteapot\nteddy\ntelevision\ntennis ball\nthatch\ntheater curtain\nthimble\nthresher\nthrone\ntile roof\ntoaster\ntobacco shop\ntoilet seat\ntorch\ntotem pole\ntow truck\ntoyshop\ntractor\ntrailer truck\ntray\ntrench coat\ntricycle\ntrimaran\ntripod\ntriumphal arch\ntrolleybus\ntrombone\ntub\nturnstile\ntypewriter keyboard\numbrella\nunicycle\nupright\nvacuum\nvase\nvault\nvelvet\nvending machine\nvestment\nviaduct\nviolin\nvolleyball\nwaffle iron\nwall clock\nwallet\nwardrobe\nwarplane\nwashbasin\nwasher\nwater bottle\nwater jug\nwater tower\nwhiskey jug\nwhistle\nwig\nwindow screen\nwindow shade\nWindsor tie\nwine bottle\nwing\nwok\nwooden spoon\nwool\nworm fence\nwreck\nyawl\nyurt\nweb site\ncomic book\ncrossword puzzle\nstreet sign\ntraffic light\nbook jacket\nmenu\nplate\nguacamole\nconsomme\nhot pot\ntrifle\nice cream\nice lolly\nFrench loaf\nbagel\npretzel\ncheeseburger\nhotdog\nmashed potato\nhead cabbage\nbroccoli\ncauliflower\nzucchini\nspaghetti squash\nacorn squash\nbutternut squash\ncucumber\nartichoke\nbell pepper\ncardoon\nmushroom\nGranny Smith\nstrawberry\norange\nlemon\nfig\npineapple\nbanana\njackfruit\ncustard apple\npomegranate\nhay\ncarbonara\nchocolate sauce\ndough\nmeat loaf\npizza\npotpie\nburrito\nred wine\nespresso\ncup\neggnog\nalp\nbubble\ncliff\ncoral reef\ngeyser\nlakeside\npromontory\nsandbar\nseashore\nvalley\nvolcano\nballplayer\ngroom\nscuba diver\nrapeseed\ndaisy\nyellow lady's slipper\ncorn\nacorn\nhip\nbuckeye\ncoral fungus\nagaric\ngyromitra\nstinkhorn\nearthstar\nhen-of-the-woods\nbolete\near\ntoilet tissue\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2/src/main.rs",
    "content": "use anyhow::Result;\nuse tract::prelude::*;\n\nfn main() -> Result<()> {\n    let model = tract::nnef()?.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?.into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let input = tract_ndarray::Array4::from_shape_fn((1, 3, 224, 224), |(_, c, y, x)| {\n        let mean = [0.485, 0.456, 0.406][c];\n        let std = [0.229, 0.224, 0.225][c];\n        (resized[(x as _, y as _)][c] as f32 / 255.0 - mean) / std\n    });\n\n    // run the model on the input\n    let result = model.run([input])?;\n\n    // find and display the max value with its index\n    let best =\n        result[0].as_slice::<f32>()?.iter().zip(2..).max_by(|a, b| a.0.partial_cmp(b.0).unwrap());\n    println!(\"result: {best:?}\");\n    Ok(())\n}\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2-api/.gitignore",
    "content": "mobilenet_v2_1.0.onnx.nnef.tgz\nmobilenetv2-7.onnx\n\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2-api/Cargo.toml",
    "content": "[package]\nname = \"example-nnef-mobilenet-v2-api\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nimage.workspace = true\ntract.workspace = true\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2-api/ci.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nwget -q https://sfo2.digitaloceanspaces.com/nnef-public/mobilenet_v2_1.0.onnx.nnef.tgz -O mobilenet_v2_1.0.onnx.nnef.tgz\n\ncargo run\nrm mobilenet_v2_1.0.onnx.nnef.tgz\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2-api/imagenet_slim_labels.txt",
    "content": "dummy\ntench\ngoldfish\ngreat white shark\ntiger shark\nhammerhead\nelectric ray\nstingray\ncock\nhen\nostrich\nbrambling\ngoldfinch\nhouse finch\njunco\nindigo bunting\nrobin\nbulbul\njay\nmagpie\nchickadee\nwater ouzel\nkite\nbald eagle\nvulture\ngreat grey owl\nEuropean fire salamander\ncommon newt\neft\nspotted salamander\naxolotl\nbullfrog\ntree frog\ntailed frog\nloggerhead\nleatherback turtle\nmud turtle\nterrapin\nbox turtle\nbanded gecko\ncommon iguana\nAmerican chameleon\nwhiptail\nagama\nfrilled lizard\nalligator lizard\nGila monster\ngreen lizard\nAfrican chameleon\nKomodo dragon\nAfrican crocodile\nAmerican alligator\ntriceratops\nthunder snake\nringneck snake\nhognose snake\ngreen snake\nking snake\ngarter snake\nwater snake\nvine snake\nnight snake\nboa constrictor\nrock python\nIndian cobra\ngreen mamba\nsea snake\nhorned viper\ndiamondback\nsidewinder\ntrilobite\nharvestman\nscorpion\nblack and gold garden spider\nbarn spider\ngarden spider\nblack widow\ntarantula\nwolf spider\ntick\ncentipede\nblack grouse\nptarmigan\nruffed grouse\nprairie chicken\npeacock\nquail\npartridge\nAfrican grey\nmacaw\nsulphur-crested cockatoo\nlorikeet\ncoucal\nbee eater\nhornbill\nhummingbird\njacamar\ntoucan\ndrake\nred-breasted merganser\ngoose\nblack swan\ntusker\nechidna\nplatypus\nwallaby\nkoala\nwombat\njellyfish\nsea anemone\nbrain coral\nflatworm\nnematode\nconch\nsnail\nslug\nsea slug\nchiton\nchambered nautilus\nDungeness crab\nrock crab\nfiddler crab\nking crab\nAmerican lobster\nspiny lobster\ncrayfish\nhermit crab\nisopod\nwhite stork\nblack stork\nspoonbill\nflamingo\nlittle blue heron\nAmerican egret\nbittern\ncrane\nlimpkin\nEuropean gallinule\nAmerican coot\nbustard\nruddy turnstone\nred-backed sandpiper\nredshank\ndowitcher\noystercatcher\npelican\nking penguin\nalbatross\ngrey whale\nkiller whale\ndugong\nsea lion\nChihuahua\nJapanese spaniel\nMaltese dog\nPekinese\nShih-Tzu\nBlenheim spaniel\npapillon\ntoy terrier\nRhodesian ridgeback\nAfghan hound\nbasset\nbeagle\nbloodhound\nbluetick\nblack-and-tan coonhound\nWalker hound\nEnglish foxhound\nredbone\nborzoi\nIrish wolfhound\nItalian greyhound\nwhippet\nIbizan hound\nNorwegian elkhound\notterhound\nSaluki\nScottish deerhound\nWeimaraner\nStaffordshire bullterrier\nAmerican Staffordshire terrier\nBedlington terrier\nBorder terrier\nKerry blue terrier\nIrish terrier\nNorfolk terrier\nNorwich terrier\nYorkshire terrier\nwire-haired fox terrier\nLakeland terrier\nSealyham terrier\nAiredale\ncairn\nAustralian terrier\nDandie Dinmont\nBoston bull\nminiature schnauzer\ngiant schnauzer\nstandard schnauzer\nScotch terrier\nTibetan terrier\nsilky terrier\nsoft-coated wheaten terrier\nWest Highland white terrier\nLhasa\nflat-coated retriever\ncurly-coated retriever\ngolden retriever\nLabrador retriever\nChesapeake Bay retriever\nGerman short-haired pointer\nvizsla\nEnglish setter\nIrish setter\nGordon setter\nBrittany spaniel\nclumber\nEnglish springer\nWelsh springer spaniel\ncocker spaniel\nSussex spaniel\nIrish water spaniel\nkuvasz\nschipperke\ngroenendael\nmalinois\nbriard\nkelpie\nkomondor\nOld English sheepdog\nShetland sheepdog\ncollie\nBorder collie\nBouvier des Flandres\nRottweiler\nGerman shepherd\nDoberman\nminiature pinscher\nGreater Swiss Mountain dog\nBernese mountain dog\nAppenzeller\nEntleBucher\nboxer\nbull mastiff\nTibetan mastiff\nFrench bulldog\nGreat Dane\nSaint Bernard\nEskimo dog\nmalamute\nSiberian husky\ndalmatian\naffenpinscher\nbasenji\npug\nLeonberg\nNewfoundland\nGreat Pyrenees\nSamoyed\nPomeranian\nchow\nkeeshond\nBrabancon griffon\nPembroke\nCardigan\ntoy poodle\nminiature poodle\nstandard poodle\nMexican hairless\ntimber wolf\nwhite wolf\nred wolf\ncoyote\ndingo\ndhole\nAfrican hunting dog\nhyena\nred fox\nkit fox\nArctic fox\ngrey fox\ntabby\ntiger cat\nPersian cat\nSiamese cat\nEgyptian cat\ncougar\nlynx\nleopard\nsnow leopard\njaguar\nlion\ntiger\ncheetah\nbrown bear\nAmerican black bear\nice bear\nsloth bear\nmongoose\nmeerkat\ntiger beetle\nladybug\nground beetle\nlong-horned beetle\nleaf beetle\ndung beetle\nrhinoceros beetle\nweevil\nfly\nbee\nant\ngrasshopper\ncricket\nwalking stick\ncockroach\nmantis\ncicada\nleafhopper\nlacewing\ndragonfly\ndamselfly\nadmiral\nringlet\nmonarch\ncabbage butterfly\nsulphur butterfly\nlycaenid\nstarfish\nsea urchin\nsea cucumber\nwood rabbit\nhare\nAngora\nhamster\nporcupine\nfox squirrel\nmarmot\nbeaver\nguinea pig\nsorrel\nzebra\nhog\nwild boar\nwarthog\nhippopotamus\nox\nwater buffalo\nbison\nram\nbighorn\nibex\nhartebeest\nimpala\ngazelle\nArabian camel\nllama\nweasel\nmink\npolecat\nblack-footed ferret\notter\nskunk\nbadger\narmadillo\nthree-toed sloth\norangutan\ngorilla\nchimpanzee\ngibbon\nsiamang\nguenon\npatas\nbaboon\nmacaque\nlangur\ncolobus\nproboscis monkey\nmarmoset\ncapuchin\nhowler monkey\ntiti\nspider monkey\nsquirrel monkey\nMadagascar cat\nindri\nIndian elephant\nAfrican elephant\nlesser panda\ngiant panda\nbarracouta\neel\ncoho\nrock beauty\nanemone fish\nsturgeon\ngar\nlionfish\npuffer\nabacus\nabaya\nacademic gown\naccordion\nacoustic guitar\naircraft carrier\nairliner\nairship\naltar\nambulance\namphibian\nanalog clock\napiary\napron\nashcan\nassault rifle\nbackpack\nbakery\nbalance beam\nballoon\nballpoint\nBand Aid\nbanjo\nbannister\nbarbell\nbarber chair\nbarbershop\nbarn\nbarometer\nbarrel\nbarrow\nbaseball\nbasketball\nbassinet\nbassoon\nbathing cap\nbath towel\nbathtub\nbeach wagon\nbeacon\nbeaker\nbearskin\nbeer bottle\nbeer glass\nbell cote\nbib\nbicycle-built-for-two\nbikini\nbinder\nbinoculars\nbirdhouse\nboathouse\nbobsled\nbolo tie\nbonnet\nbookcase\nbookshop\nbottlecap\nbow\nbow tie\nbrass\nbrassiere\nbreakwater\nbreastplate\nbroom\nbucket\nbuckle\nbulletproof vest\nbullet train\nbutcher shop\ncab\ncaldron\ncandle\ncannon\ncanoe\ncan opener\ncardigan\ncar mirror\ncarousel\ncarpenter's kit\ncarton\ncar wheel\ncash machine\ncassette\ncassette player\ncastle\ncatamaran\nCD player\ncello\ncellular telephone\nchain\nchainlink fence\nchain mail\nchain saw\nchest\nchiffonier\nchime\nchina cabinet\nChristmas stocking\nchurch\ncinema\ncleaver\ncliff dwelling\ncloak\nclog\ncocktail shaker\ncoffee mug\ncoffeepot\ncoil\ncombination lock\ncomputer keyboard\nconfectionery\ncontainer ship\nconvertible\ncorkscrew\ncornet\ncowboy boot\ncowboy hat\ncradle\ncrane\ncrash helmet\ncrate\ncrib\nCrock Pot\ncroquet ball\ncrutch\ncuirass\ndam\ndesk\ndesktop computer\ndial telephone\ndiaper\ndigital clock\ndigital watch\ndining table\ndishrag\ndishwasher\ndisk brake\ndock\ndogsled\ndome\ndoormat\ndrilling platform\ndrum\ndrumstick\ndumbbell\nDutch oven\nelectric fan\nelectric guitar\nelectric locomotive\nentertainment center\nenvelope\nespresso maker\nface powder\nfeather boa\nfile\nfireboat\nfire engine\nfire screen\nflagpole\nflute\nfolding chair\nfootball helmet\nforklift\nfountain\nfountain pen\nfour-poster\nfreight car\nFrench horn\nfrying pan\nfur coat\ngarbage truck\ngasmask\ngas pump\ngoblet\ngo-kart\ngolf ball\ngolfcart\ngondola\ngong\ngown\ngrand piano\ngreenhouse\ngrille\ngrocery store\nguillotine\nhair slide\nhair spray\nhalf track\nhammer\nhamper\nhand blower\nhand-held computer\nhandkerchief\nhard disc\nharmonica\nharp\nharvester\nhatchet\nholster\nhome theater\nhoneycomb\nhook\nhoopskirt\nhorizontal bar\nhorse cart\nhourglass\niPod\niron\njack-o'-lantern\njean\njeep\njersey\njigsaw puzzle\njinrikisha\njoystick\nkimono\nknee pad\nknot\nlab coat\nladle\nlampshade\nlaptop\nlawn mower\nlens cap\nletter opener\nlibrary\nlifeboat\nlighter\nlimousine\nliner\nlipstick\nLoafer\nlotion\nloudspeaker\nloupe\nlumbermill\nmagnetic compass\nmailbag\nmailbox\nmaillot\nmaillot\nmanhole cover\nmaraca\nmarimba\nmask\nmatchstick\nmaypole\nmaze\nmeasuring cup\nmedicine chest\nmegalith\nmicrophone\nmicrowave\nmilitary uniform\nmilk can\nminibus\nminiskirt\nminivan\nmissile\nmitten\nmixing bowl\nmobile home\nModel T\nmodem\nmonastery\nmonitor\nmoped\nmortar\nmortarboard\nmosque\nmosquito net\nmotor scooter\nmountain bike\nmountain tent\nmouse\nmousetrap\nmoving van\nmuzzle\nnail\nneck brace\nnecklace\nnipple\nnotebook\nobelisk\noboe\nocarina\nodometer\noil filter\norgan\noscilloscope\noverskirt\noxcart\noxygen mask\npacket\npaddle\npaddlewheel\npadlock\npaintbrush\npajama\npalace\npanpipe\npaper towel\nparachute\nparallel bars\npark bench\nparking meter\npassenger car\npatio\npay-phone\npedestal\npencil box\npencil sharpener\nperfume\nPetri dish\nphotocopier\npick\npickelhaube\npicket fence\npickup\npier\npiggy bank\npill bottle\npillow\nping-pong ball\npinwheel\npirate\npitcher\nplane\nplanetarium\nplastic bag\nplate rack\nplow\nplunger\nPolaroid camera\npole\npolice van\nponcho\npool table\npop bottle\npot\npotter's wheel\npower drill\nprayer rug\nprinter\nprison\nprojectile\nprojector\npuck\npunching bag\npurse\nquill\nquilt\nracer\nracket\nradiator\nradio\nradio telescope\nrain barrel\nrecreational vehicle\nreel\nreflex camera\nrefrigerator\nremote control\nrestaurant\nrevolver\nrifle\nrocking chair\nrotisserie\nrubber eraser\nrugby ball\nrule\nrunning shoe\nsafe\nsafety pin\nsaltshaker\nsandal\nsarong\nsax\nscabbard\nscale\nschool bus\nschooner\nscoreboard\nscreen\nscrew\nscrewdriver\nseat belt\nsewing machine\nshield\nshoe shop\nshoji\nshopping basket\nshopping cart\nshovel\nshower cap\nshower curtain\nski\nski mask\nsleeping bag\nslide rule\nsliding door\nslot\nsnorkel\nsnowmobile\nsnowplow\nsoap dispenser\nsoccer ball\nsock\nsolar dish\nsombrero\nsoup bowl\nspace bar\nspace heater\nspace shuttle\nspatula\nspeedboat\nspider web\nspindle\nsports car\nspotlight\nstage\nsteam locomotive\nsteel arch bridge\nsteel drum\nstethoscope\nstole\nstone wall\nstopwatch\nstove\nstrainer\nstreetcar\nstretcher\nstudio couch\nstupa\nsubmarine\nsuit\nsundial\nsunglass\nsunglasses\nsunscreen\nsuspension bridge\nswab\nsweatshirt\nswimming trunks\nswing\nswitch\nsyringe\ntable lamp\ntank\ntape player\nteapot\nteddy\ntelevision\ntennis ball\nthatch\ntheater curtain\nthimble\nthresher\nthrone\ntile roof\ntoaster\ntobacco shop\ntoilet seat\ntorch\ntotem pole\ntow truck\ntoyshop\ntractor\ntrailer truck\ntray\ntrench coat\ntricycle\ntrimaran\ntripod\ntriumphal arch\ntrolleybus\ntrombone\ntub\nturnstile\ntypewriter keyboard\numbrella\nunicycle\nupright\nvacuum\nvase\nvault\nvelvet\nvending machine\nvestment\nviaduct\nviolin\nvolleyball\nwaffle iron\nwall clock\nwallet\nwardrobe\nwarplane\nwashbasin\nwasher\nwater bottle\nwater jug\nwater tower\nwhiskey jug\nwhistle\nwig\nwindow screen\nwindow shade\nWindsor tie\nwine bottle\nwing\nwok\nwooden spoon\nwool\nworm fence\nwreck\nyawl\nyurt\nweb site\ncomic book\ncrossword puzzle\nstreet sign\ntraffic light\nbook jacket\nmenu\nplate\nguacamole\nconsomme\nhot pot\ntrifle\nice cream\nice lolly\nFrench loaf\nbagel\npretzel\ncheeseburger\nhotdog\nmashed potato\nhead cabbage\nbroccoli\ncauliflower\nzucchini\nspaghetti squash\nacorn squash\nbutternut squash\ncucumber\nartichoke\nbell pepper\ncardoon\nmushroom\nGranny Smith\nstrawberry\norange\nlemon\nfig\npineapple\nbanana\njackfruit\ncustard apple\npomegranate\nhay\ncarbonara\nchocolate sauce\ndough\nmeat loaf\npizza\npotpie\nburrito\nred wine\nespresso\ncup\neggnog\nalp\nbubble\ncliff\ncoral reef\ngeyser\nlakeside\npromontory\nsandbar\nseashore\nvalley\nvolcano\nballplayer\ngroom\nscuba diver\nrapeseed\ndaisy\nyellow lady's slipper\ncorn\nacorn\nhip\nbuckeye\ncoral fungus\nagaric\ngyromitra\nstinkhorn\nearthstar\nhen-of-the-woods\nbolete\near\ntoilet tissue\n"
  },
  {
    "path": "examples/nnef-mobilenet-v2-api/src/main.rs",
    "content": "use anyhow::Result;\nuse tract::prelude::*;\n\nfn main() -> Result<()> {\n    let nnef = tract::nnef()?;\n    let model = nnef.load(\"mobilenet_v2_1.0.onnx.nnef.tgz\")?.into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let input = tract_ndarray::Array4::from_shape_fn((1, 3, 224, 224), |(_, c, y, x)| {\n        let mean = [0.485, 0.456, 0.406][c];\n        let std = [0.229, 0.224, 0.225][c];\n        (resized[(x as _, y as _)][c] as f32 / 255.0 - mean) / std\n    });\n\n    // run the model on the input\n    let result = model.run([input])?;\n\n    // find and display the max value with its index\n    let best =\n        &result[0].as_slice::<f32>()?.iter().zip(2..).max_by(|a, b| a.0.partial_cmp(b.0).unwrap());\n    println!(\"result: {best:?}\");\n    Ok(())\n}\n"
  },
  {
    "path": "examples/onnx-mobilenet-v2/.gitignore",
    "content": "mobilenetv2-7.onnx\n"
  },
  {
    "path": "examples/onnx-mobilenet-v2/Cargo.toml",
    "content": "[package]\nname = \"example-onnx-mobilenet-v2\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\ndefault-run = \"example-onnx-mobilenet-v2\"\n\n[dependencies]\nanyhow.workspace = true\nimage.workspace = true\ntract.workspace = true\n"
  },
  {
    "path": "examples/onnx-mobilenet-v2/README.md",
    "content": "# Tract examples: ONNX MobileNet v2\n\nThis project is a simple project with minimal code showing how to use tract to\nprocess an image with MobileNetV2.\n\nThe example assume the following command are run in the directory of this\nexample project, where this README lives.\n\n```sh\ngit clone https://github.com/snipsco/tract\ncd tract/examples/onnx-mobilenet-v2/\n```\n\n## Obtaining the model \n\nMobileNet is a response to the ImageNet challenge. The goal is to categorize\nimages and associate them with one of 1000 labels. In other words, recognize a\ndog, a cat, a rabbit, or a military uniform.\n\nSee https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet for more information.\n\nModels can get a big heavy, so we chose not to include them in tract git repository. \nYou will need to download the models. For instance:\n\n```sh\nwget https://s3.amazonaws.com/tract-ci-builds/tests/mobilenetv2-7.onnx \n```\n\n## Input image\n\nWe will use a portrait of Grace Hopper in uniform (included in the repository).\n\n```\ngrace_hopper.jpg: JPEG image data, JFIF standard 1.02, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 517x606, components 3\n```\n\n## Try it\n\n`cargo run` should print a lot of things, and ultimately: `result: Some((11.4773035, 654))`.\n\nThis is actually good. It is the rank (654) and a confidence indicator (11.4773035)\nof the inferred label.\n\n```\n$ cat -n imagenet_slim_labels.txt | grep -C 3 654\n   651  megalith\n   652  microphone\n   653  microwave\n   654  military uniform\n   655  milk can\n   656  minibus\n   657  miniskirt\n```\n\n## A look at the code\n\nEverything happens in [src/main.rs](src/main.rs).\n\n\n```rust\nuse tract_onnx::prelude::*;\n\nfn main() -> TractResult<()> {\n    let model = tract_onnx::onnx()\n        // load the model\n        .model_for_path(\"mobilenetv2-7.onnx\")?\n        // optimize the model\n        .into_optimized()?\n        // make the model runnable and fix its inputs and outputs\n        .into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let image: Tensor = tract_ndarray::Array4::from_shape_fn((1, 3, 224, 224), |(_, c, y, x)| {\n        let mean = [0.485, 0.456, 0.406][c];\n        let std = [0.229, 0.224, 0.225][c];\n        (resized[(x as _, y as _)][c] as f32 / 255.0 - mean) / std\n    })\n    .into();\n\n    // run the model on the input\n    let result = model.run(tvec!(image.into()))?;\n\n    // find and display the max value with its index\n    let best = result[0]\n        .to_array_view::<f32>()?\n        .iter()\n        .cloned()\n        .zip(2..)\n        .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap());\n    println!(\"result: {:?}\", best);\n    Ok(())\n}\n```\n\nIt uses the tract-onnx as an entry point. Other options are available (tensorflow, or nnef):\nI also use the `image` crate to load and resize the JPEG portrait.\n\n### Loading the model\n\nThis line creates a tract-onnx context, and uses it to load the protobuf\nmodel.\n\n```rust\n    let model = tract_onnx::onnx()\n        .model_for_path(\"mobilenet_v2_1.4_224_frozen.pb\")?\n        .into_optimized()?\n        .into_runnable()?;\n    // ..\n```\n\nNow the model is ready to run, we have an execution plan, so let's prepare the\nimage.\n\n### Conditioning the input\n\nWe use the `image` crate to load the `.jpg` image, resize is to 224x224. Then\nwe build an 4-dimension array in the right NHWC shape, with `f32` obtained by\nnormalizing the `u8` input to the `0..1` range. This array is then converted\ninto a Tensor. We apply a color normalization on the fly, which is standard for\nMobileNet models.\n\n```rust\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb();\n    let resized = image::imageops::resize(&image, 224, 224, ::image::FilterType::Triangle);\n    let image: Tensor = tract_ndarray::Array4::from_shape_fn((1, 3, 224, 224), |(_, c, y, x)| {\n        let mean = [0.485, 0.456, 0.406][c];\n        let std = [0.229, 0.224, 0.225][c];\n        (resized[(x as _, y as _)][c] as f32 / 255.0 - mean) / std\n```\n\nNote that `tract` crates re-export the excellent `ndarray` crate as `tract_ndarray`so that \nit is easy to get the right version for tract conversions to work.\n\n### Run the network!\n\n```rust\n    let result = model.run(tvec!(image.into()))?;\n```\n\n### Interpret the result\n\nFinally we grab the single Tensor output by the plan execution, convert it to a\nndarray ArrayView of f32 values. It is a single dimension (a vector...) of 1001\ncategory scores (1000 labels plus the dummy one). We need pick the maximum\nscore, with its index, and display it...\n\n```rust\n    let best = result[0]\n        .to_array_view::<f32>()?\n        .iter()\n        .cloned()\n        .enumerate()\n        .zip(1..)\n        .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap());\n    println!(\"result: {:?}\", best);\n```\n\n## Try it on WebAssembly\n\nYou can also compile the example into webassembly and run it on wasm runtime like [wasmtime](https://github.com/bytecodealliance/wasmtime).\nYou need to [install wasmtime](https://docs.wasmtime.dev/cli-install.html) and add wasm as a target with rustup.\n\n```sh\nrustup target add wasm32-wasi\n```\n\nBuild the example with the `wasm32-wasi` target.\n\n```sh\ncargo build --target wasm32-wasi\n```\n\nThen run the example with wasmtime, use `--dir=.` to specify the directory path used in the example.\n\n```sh\nwasmtime --dir . ../../target/wasm32-wasi/debug/example-onnx-mobilenet-v2.wasm\n```\n\nYou should see the same result as the native one.\n\n# Dynamic shape example\n\nA [second example](src/bin/dyn-shape.rs) shows how to use dynamic shapes and symbols. Right after loading the model, it creates a symbol in the model scope, and use it to override\nthe input shape before analysing and optimizing the model as in the regular example.\n\nAt runtime, our example simulates batches of different number (N=1, 3, then 5) of images by duplicating the grace_hopper input image N times. tract lazily discovers the batch size\nat each turn without requiring model re-optimization.\n\nThe example can be run by\n\n```sh\ncargo run --bin dyn-shape\n```\n\nIt should display three batches of different size (1, 3 then 5), with the same results of the normal test (as we are just duplicating the same input image).\n"
  },
  {
    "path": "examples/onnx-mobilenet-v2/ci.sh",
    "content": "#!/bin/sh\n\nset -ex\n\n[ -e mobilenetv2-7.onnx ] || \\\n    wget -q https://s3.amazonaws.com/tract-ci-builds/tests/mobilenetv2-7.onnx -O mobilenetv2-7.onnx\n\n# on win/linux\ncargo run\n# on wasm\nwasmtime -V || curl https://wasmtime.dev/install.sh -sSf | bash # install wasmtime\nPATH=$PATH:$HOME/.wasmtime/bin\nrustup target install wasm32-wasip1\ncargo build --target wasm32-wasip1\nwasmtime --dir . ../../target/wasm32-wasip1/debug/example-onnx-mobilenet-v2.wasm\n\ncargo run --bin dyn-shape\n\nrm  mobilenetv2-7.onnx\n"
  },
  {
    "path": "examples/onnx-mobilenet-v2/imagenet_slim_labels.txt",
    "content": "dummy\ntench\ngoldfish\ngreat white shark\ntiger shark\nhammerhead\nelectric ray\nstingray\ncock\nhen\nostrich\nbrambling\ngoldfinch\nhouse finch\njunco\nindigo bunting\nrobin\nbulbul\njay\nmagpie\nchickadee\nwater ouzel\nkite\nbald eagle\nvulture\ngreat grey owl\nEuropean fire salamander\ncommon newt\neft\nspotted salamander\naxolotl\nbullfrog\ntree frog\ntailed frog\nloggerhead\nleatherback turtle\nmud turtle\nterrapin\nbox turtle\nbanded gecko\ncommon iguana\nAmerican chameleon\nwhiptail\nagama\nfrilled lizard\nalligator lizard\nGila monster\ngreen lizard\nAfrican chameleon\nKomodo dragon\nAfrican crocodile\nAmerican alligator\ntriceratops\nthunder snake\nringneck snake\nhognose snake\ngreen snake\nking snake\ngarter snake\nwater snake\nvine snake\nnight snake\nboa constrictor\nrock python\nIndian cobra\ngreen mamba\nsea snake\nhorned viper\ndiamondback\nsidewinder\ntrilobite\nharvestman\nscorpion\nblack and gold garden spider\nbarn spider\ngarden spider\nblack widow\ntarantula\nwolf spider\ntick\ncentipede\nblack grouse\nptarmigan\nruffed grouse\nprairie chicken\npeacock\nquail\npartridge\nAfrican grey\nmacaw\nsulphur-crested cockatoo\nlorikeet\ncoucal\nbee eater\nhornbill\nhummingbird\njacamar\ntoucan\ndrake\nred-breasted merganser\ngoose\nblack swan\ntusker\nechidna\nplatypus\nwallaby\nkoala\nwombat\njellyfish\nsea anemone\nbrain coral\nflatworm\nnematode\nconch\nsnail\nslug\nsea slug\nchiton\nchambered nautilus\nDungeness crab\nrock crab\nfiddler crab\nking crab\nAmerican lobster\nspiny lobster\ncrayfish\nhermit crab\nisopod\nwhite stork\nblack stork\nspoonbill\nflamingo\nlittle blue heron\nAmerican egret\nbittern\ncrane\nlimpkin\nEuropean gallinule\nAmerican coot\nbustard\nruddy turnstone\nred-backed sandpiper\nredshank\ndowitcher\noystercatcher\npelican\nking penguin\nalbatross\ngrey whale\nkiller whale\ndugong\nsea lion\nChihuahua\nJapanese spaniel\nMaltese dog\nPekinese\nShih-Tzu\nBlenheim spaniel\npapillon\ntoy terrier\nRhodesian ridgeback\nAfghan hound\nbasset\nbeagle\nbloodhound\nbluetick\nblack-and-tan coonhound\nWalker hound\nEnglish foxhound\nredbone\nborzoi\nIrish wolfhound\nItalian greyhound\nwhippet\nIbizan hound\nNorwegian elkhound\notterhound\nSaluki\nScottish deerhound\nWeimaraner\nStaffordshire bullterrier\nAmerican Staffordshire terrier\nBedlington terrier\nBorder terrier\nKerry blue terrier\nIrish terrier\nNorfolk terrier\nNorwich terrier\nYorkshire terrier\nwire-haired fox terrier\nLakeland terrier\nSealyham terrier\nAiredale\ncairn\nAustralian terrier\nDandie Dinmont\nBoston bull\nminiature schnauzer\ngiant schnauzer\nstandard schnauzer\nScotch terrier\nTibetan terrier\nsilky terrier\nsoft-coated wheaten terrier\nWest Highland white terrier\nLhasa\nflat-coated retriever\ncurly-coated retriever\ngolden retriever\nLabrador retriever\nChesapeake Bay retriever\nGerman short-haired pointer\nvizsla\nEnglish setter\nIrish setter\nGordon setter\nBrittany spaniel\nclumber\nEnglish springer\nWelsh springer spaniel\ncocker spaniel\nSussex spaniel\nIrish water spaniel\nkuvasz\nschipperke\ngroenendael\nmalinois\nbriard\nkelpie\nkomondor\nOld English sheepdog\nShetland sheepdog\ncollie\nBorder collie\nBouvier des Flandres\nRottweiler\nGerman shepherd\nDoberman\nminiature pinscher\nGreater Swiss Mountain dog\nBernese mountain dog\nAppenzeller\nEntleBucher\nboxer\nbull mastiff\nTibetan mastiff\nFrench bulldog\nGreat Dane\nSaint Bernard\nEskimo dog\nmalamute\nSiberian husky\ndalmatian\naffenpinscher\nbasenji\npug\nLeonberg\nNewfoundland\nGreat Pyrenees\nSamoyed\nPomeranian\nchow\nkeeshond\nBrabancon griffon\nPembroke\nCardigan\ntoy poodle\nminiature poodle\nstandard poodle\nMexican hairless\ntimber wolf\nwhite wolf\nred wolf\ncoyote\ndingo\ndhole\nAfrican hunting dog\nhyena\nred fox\nkit fox\nArctic fox\ngrey fox\ntabby\ntiger cat\nPersian cat\nSiamese cat\nEgyptian cat\ncougar\nlynx\nleopard\nsnow leopard\njaguar\nlion\ntiger\ncheetah\nbrown bear\nAmerican black bear\nice bear\nsloth bear\nmongoose\nmeerkat\ntiger beetle\nladybug\nground beetle\nlong-horned beetle\nleaf beetle\ndung beetle\nrhinoceros beetle\nweevil\nfly\nbee\nant\ngrasshopper\ncricket\nwalking stick\ncockroach\nmantis\ncicada\nleafhopper\nlacewing\ndragonfly\ndamselfly\nadmiral\nringlet\nmonarch\ncabbage butterfly\nsulphur butterfly\nlycaenid\nstarfish\nsea urchin\nsea cucumber\nwood rabbit\nhare\nAngora\nhamster\nporcupine\nfox squirrel\nmarmot\nbeaver\nguinea pig\nsorrel\nzebra\nhog\nwild boar\nwarthog\nhippopotamus\nox\nwater buffalo\nbison\nram\nbighorn\nibex\nhartebeest\nimpala\ngazelle\nArabian camel\nllama\nweasel\nmink\npolecat\nblack-footed ferret\notter\nskunk\nbadger\narmadillo\nthree-toed sloth\norangutan\ngorilla\nchimpanzee\ngibbon\nsiamang\nguenon\npatas\nbaboon\nmacaque\nlangur\ncolobus\nproboscis monkey\nmarmoset\ncapuchin\nhowler monkey\ntiti\nspider monkey\nsquirrel monkey\nMadagascar cat\nindri\nIndian elephant\nAfrican elephant\nlesser panda\ngiant panda\nbarracouta\neel\ncoho\nrock beauty\nanemone fish\nsturgeon\ngar\nlionfish\npuffer\nabacus\nabaya\nacademic gown\naccordion\nacoustic guitar\naircraft carrier\nairliner\nairship\naltar\nambulance\namphibian\nanalog clock\napiary\napron\nashcan\nassault rifle\nbackpack\nbakery\nbalance beam\nballoon\nballpoint\nBand Aid\nbanjo\nbannister\nbarbell\nbarber chair\nbarbershop\nbarn\nbarometer\nbarrel\nbarrow\nbaseball\nbasketball\nbassinet\nbassoon\nbathing cap\nbath towel\nbathtub\nbeach wagon\nbeacon\nbeaker\nbearskin\nbeer bottle\nbeer glass\nbell cote\nbib\nbicycle-built-for-two\nbikini\nbinder\nbinoculars\nbirdhouse\nboathouse\nbobsled\nbolo tie\nbonnet\nbookcase\nbookshop\nbottlecap\nbow\nbow tie\nbrass\nbrassiere\nbreakwater\nbreastplate\nbroom\nbucket\nbuckle\nbulletproof vest\nbullet train\nbutcher shop\ncab\ncaldron\ncandle\ncannon\ncanoe\ncan opener\ncardigan\ncar mirror\ncarousel\ncarpenter's kit\ncarton\ncar wheel\ncash machine\ncassette\ncassette player\ncastle\ncatamaran\nCD player\ncello\ncellular telephone\nchain\nchainlink fence\nchain mail\nchain saw\nchest\nchiffonier\nchime\nchina cabinet\nChristmas stocking\nchurch\ncinema\ncleaver\ncliff dwelling\ncloak\nclog\ncocktail shaker\ncoffee mug\ncoffeepot\ncoil\ncombination lock\ncomputer keyboard\nconfectionery\ncontainer ship\nconvertible\ncorkscrew\ncornet\ncowboy boot\ncowboy hat\ncradle\ncrane\ncrash helmet\ncrate\ncrib\nCrock Pot\ncroquet ball\ncrutch\ncuirass\ndam\ndesk\ndesktop computer\ndial telephone\ndiaper\ndigital clock\ndigital watch\ndining table\ndishrag\ndishwasher\ndisk brake\ndock\ndogsled\ndome\ndoormat\ndrilling platform\ndrum\ndrumstick\ndumbbell\nDutch oven\nelectric fan\nelectric guitar\nelectric locomotive\nentertainment center\nenvelope\nespresso maker\nface powder\nfeather boa\nfile\nfireboat\nfire engine\nfire screen\nflagpole\nflute\nfolding chair\nfootball helmet\nforklift\nfountain\nfountain pen\nfour-poster\nfreight car\nFrench horn\nfrying pan\nfur coat\ngarbage truck\ngasmask\ngas pump\ngoblet\ngo-kart\ngolf ball\ngolfcart\ngondola\ngong\ngown\ngrand piano\ngreenhouse\ngrille\ngrocery store\nguillotine\nhair slide\nhair spray\nhalf track\nhammer\nhamper\nhand blower\nhand-held computer\nhandkerchief\nhard disc\nharmonica\nharp\nharvester\nhatchet\nholster\nhome theater\nhoneycomb\nhook\nhoopskirt\nhorizontal bar\nhorse cart\nhourglass\niPod\niron\njack-o'-lantern\njean\njeep\njersey\njigsaw puzzle\njinrikisha\njoystick\nkimono\nknee pad\nknot\nlab coat\nladle\nlampshade\nlaptop\nlawn mower\nlens cap\nletter opener\nlibrary\nlifeboat\nlighter\nlimousine\nliner\nlipstick\nLoafer\nlotion\nloudspeaker\nloupe\nlumbermill\nmagnetic compass\nmailbag\nmailbox\nmaillot\nmaillot\nmanhole cover\nmaraca\nmarimba\nmask\nmatchstick\nmaypole\nmaze\nmeasuring cup\nmedicine chest\nmegalith\nmicrophone\nmicrowave\nmilitary uniform\nmilk can\nminibus\nminiskirt\nminivan\nmissile\nmitten\nmixing bowl\nmobile home\nModel T\nmodem\nmonastery\nmonitor\nmoped\nmortar\nmortarboard\nmosque\nmosquito net\nmotor scooter\nmountain bike\nmountain tent\nmouse\nmousetrap\nmoving van\nmuzzle\nnail\nneck brace\nnecklace\nnipple\nnotebook\nobelisk\noboe\nocarina\nodometer\noil filter\norgan\noscilloscope\noverskirt\noxcart\noxygen mask\npacket\npaddle\npaddlewheel\npadlock\npaintbrush\npajama\npalace\npanpipe\npaper towel\nparachute\nparallel bars\npark bench\nparking meter\npassenger car\npatio\npay-phone\npedestal\npencil box\npencil sharpener\nperfume\nPetri dish\nphotocopier\npick\npickelhaube\npicket fence\npickup\npier\npiggy bank\npill bottle\npillow\nping-pong ball\npinwheel\npirate\npitcher\nplane\nplanetarium\nplastic bag\nplate rack\nplow\nplunger\nPolaroid camera\npole\npolice van\nponcho\npool table\npop bottle\npot\npotter's wheel\npower drill\nprayer rug\nprinter\nprison\nprojectile\nprojector\npuck\npunching bag\npurse\nquill\nquilt\nracer\nracket\nradiator\nradio\nradio telescope\nrain barrel\nrecreational vehicle\nreel\nreflex camera\nrefrigerator\nremote control\nrestaurant\nrevolver\nrifle\nrocking chair\nrotisserie\nrubber eraser\nrugby ball\nrule\nrunning shoe\nsafe\nsafety pin\nsaltshaker\nsandal\nsarong\nsax\nscabbard\nscale\nschool bus\nschooner\nscoreboard\nscreen\nscrew\nscrewdriver\nseat belt\nsewing machine\nshield\nshoe shop\nshoji\nshopping basket\nshopping cart\nshovel\nshower cap\nshower curtain\nski\nski mask\nsleeping bag\nslide rule\nsliding door\nslot\nsnorkel\nsnowmobile\nsnowplow\nsoap dispenser\nsoccer ball\nsock\nsolar dish\nsombrero\nsoup bowl\nspace bar\nspace heater\nspace shuttle\nspatula\nspeedboat\nspider web\nspindle\nsports car\nspotlight\nstage\nsteam locomotive\nsteel arch bridge\nsteel drum\nstethoscope\nstole\nstone wall\nstopwatch\nstove\nstrainer\nstreetcar\nstretcher\nstudio couch\nstupa\nsubmarine\nsuit\nsundial\nsunglass\nsunglasses\nsunscreen\nsuspension bridge\nswab\nsweatshirt\nswimming trunks\nswing\nswitch\nsyringe\ntable lamp\ntank\ntape player\nteapot\nteddy\ntelevision\ntennis ball\nthatch\ntheater curtain\nthimble\nthresher\nthrone\ntile roof\ntoaster\ntobacco shop\ntoilet seat\ntorch\ntotem pole\ntow truck\ntoyshop\ntractor\ntrailer truck\ntray\ntrench coat\ntricycle\ntrimaran\ntripod\ntriumphal arch\ntrolleybus\ntrombone\ntub\nturnstile\ntypewriter keyboard\numbrella\nunicycle\nupright\nvacuum\nvase\nvault\nvelvet\nvending machine\nvestment\nviaduct\nviolin\nvolleyball\nwaffle iron\nwall clock\nwallet\nwardrobe\nwarplane\nwashbasin\nwasher\nwater bottle\nwater jug\nwater tower\nwhiskey jug\nwhistle\nwig\nwindow screen\nwindow shade\nWindsor tie\nwine bottle\nwing\nwok\nwooden spoon\nwool\nworm fence\nwreck\nyawl\nyurt\nweb site\ncomic book\ncrossword puzzle\nstreet sign\ntraffic light\nbook jacket\nmenu\nplate\nguacamole\nconsomme\nhot pot\ntrifle\nice cream\nice lolly\nFrench loaf\nbagel\npretzel\ncheeseburger\nhotdog\nmashed potato\nhead cabbage\nbroccoli\ncauliflower\nzucchini\nspaghetti squash\nacorn squash\nbutternut squash\ncucumber\nartichoke\nbell pepper\ncardoon\nmushroom\nGranny Smith\nstrawberry\norange\nlemon\nfig\npineapple\nbanana\njackfruit\ncustard apple\npomegranate\nhay\ncarbonara\nchocolate sauce\ndough\nmeat loaf\npizza\npotpie\nburrito\nred wine\nespresso\ncup\neggnog\nalp\nbubble\ncliff\ncoral reef\ngeyser\nlakeside\npromontory\nsandbar\nseashore\nvalley\nvolcano\nballplayer\ngroom\nscuba diver\nrapeseed\ndaisy\nyellow lady's slipper\ncorn\nacorn\nhip\nbuckeye\ncoral fungus\nagaric\ngyromitra\nstinkhorn\nearthstar\nhen-of-the-woods\nbolete\near\ntoilet tissue\n"
  },
  {
    "path": "examples/onnx-mobilenet-v2/src/bin/dyn-shape.rs",
    "content": "use anyhow::Result;\nuse tract::prelude::*;\n\nfn main() -> Result<()> {\n    // load the model\n    let mut model = tract::onnx()?.load(\"mobilenetv2-7.onnx\")?;\n\n    // Create a symbolic batch dimension\n    model.set_input_fact(0, \"N,3,224,224,f32\")?;\n\n    let model = model.into_model()?.into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n\n    for n in [1, 3, 5] {\n        println!(\"batch of {n} images:\");\n\n        // we put the same image n times to simulate a batch of size pictures\n        let images = tract_ndarray::Array4::from_shape_fn((n, 3, 224, 224), |(_, c, y, x)| {\n            let mean = [0.485, 0.456, 0.406][c];\n            let std = [0.229, 0.224, 0.225][c];\n            (resized[(x as _, y as _)][c] as f32 / 255.0 - mean) / std\n        });\n        // run the model on the input\n        let results = model.run([images])?;\n\n        // loop over the batch\n        for image in results[0].view::<f32>()?.outer_iter() {\n            // find and display the max value with its index\n            let best = image.iter().zip(2..).max_by(|a, b| a.0.partial_cmp(b.0).unwrap());\n            println!(\"  result: {best:?}\");\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "examples/onnx-mobilenet-v2/src/main.rs",
    "content": "use anyhow::Result;\nuse tract::prelude::*;\n\nfn main() -> Result<()> {\n    let model = tract::onnx()?.load(\"mobilenetv2-7.onnx\")?.into_model()?.into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let input = tract_ndarray::Array4::from_shape_fn((1, 3, 224, 224), |(_, c, y, x)| {\n        let mean = [0.485, 0.456, 0.406][c];\n        let std = [0.229, 0.224, 0.225][c];\n        (resized[(x as _, y as _)][c] as f32 / 255.0 - mean) / std\n    });\n\n    // run the model on the input\n    let result = model.run([input])?;\n\n    // find and display the max value with its index\n    let best =\n        result[0].as_slice::<f32>()?.iter().zip(2..).max_by(|a, b| a.0.partial_cmp(b.0).unwrap());\n    println!(\"result: {best:?}\");\n    Ok(())\n}\n"
  },
  {
    "path": "examples/pytorch-albert-v2/.gitignore",
    "content": "albert\n"
  },
  {
    "path": "examples/pytorch-albert-v2/Cargo.toml",
    "content": "[package]\nname = \"pytorch-albert-v2\"\nversion = \"0.20.7-pre\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nanstyle.workspace = true\nanstyle-parse.workspace = true\nanstyle-query.workspace = true\ntokenizers.workspace = true\nclap_builder = { version = \"4.4\" }\nclap_lex = { version = \"1.1\" }\ntract.workspace = true\n"
  },
  {
    "path": "examples/pytorch-albert-v2/README.md",
    "content": "A simple example of exporting a [transformer](https://huggingface.co/docs/transformers/index) model with Python, then loading it into tract to make predictions.\n\n# To Use\n\nFirst export the pre-trained transformer model using Python and PyTorch\n\n``` shell\npython export.py\n```\n\nthe exported model and tokenizer are saved in `./albert`. Then load the model into tract and make prediction.\n\n``` rust\ncargo run --release\n```\n\nThe output for input sentence \"Paris is the [MASK] of France\" should look like\n\n``` text\nResult: Some(\"▁capital\")\n```\n"
  },
  {
    "path": "examples/pytorch-albert-v2/ci.sh",
    "content": "#!/bin/sh\n\nset -e\nsudo apt-get install -y python3-virtualenv\nvirtualenv venv\n. ./venv/bin/activate\npip install -q torch \"transformers<5\" onnx accelerate onnxscript\npython export.py\ncargo run --release\nrm -rf venv\n"
  },
  {
    "path": "examples/pytorch-albert-v2/export.py",
    "content": "# -*- coding: utf-8 -*-\n\nimport os\n\nimport torch\nfrom transformers import AutoModelForMaskedLM, AutoTokenizer\nfrom torch.export import Dim\n\nmodel_name = \"albert-base-v2\"\n\ntokenizer = AutoTokenizer.from_pretrained(model_name)\nmodel = AutoModelForMaskedLM.from_pretrained(model_name)\n\ntext = \"Paris is the [MASK] of France.\"\ntokenizer_output = tokenizer(text, return_tensors=\"pt\")\n\ninput_ids = tokenizer_output[\"input_ids\"]\nattention_mask = tokenizer_output[\"attention_mask\"]\ntoken_type_ids = tokenizer_output[\"token_type_ids\"]\n\nbatch = Dim(\"batch\")\nseq = Dim(\"seq\")\n\noutput_dir = \"./albert\"\nos.makedirs(output_dir, exist_ok=True)\ntorch.onnx.export(\n    model,\n    (input_ids, attention_mask, token_type_ids),\n    os.path.join(output_dir, \"model.onnx\"),\n    input_names=[\"input_ids\", \"attention_mask\", \"token_type_ids\"],\n    output_names=[\"logits\"],\n    dynamic_shapes={\n        \"input_ids\": (batch, seq),\n        \"attention_mask\": (batch, seq),\n        \"token_type_ids\": (batch, seq),\n    }, \n)\n\ntokenizer.save_pretrained(output_dir)\n"
  },
  {
    "path": "examples/pytorch-albert-v2/src/main.rs",
    "content": "use std::{\n    path::{Path, PathBuf},\n    str::FromStr,\n};\nuse tokenizers::tokenizer::{Result, Tokenizer};\nuse tract::prelude::*;\nuse tract_ndarray::s;\n\nfn main() -> Result<()> {\n    let model_dir = PathBuf::from_str(\"./albert\")?;\n    let tokenizer = Tokenizer::from_file(Path::join(&model_dir, \"tokenizer.json\"))?;\n\n    let text = \"Paris is the [MASK] of France.\";\n\n    let tokenizer_output = tokenizer.encode(text, true)?;\n    let input_ids = tokenizer_output.get_ids();\n    let attention_mask = tokenizer_output.get_attention_mask();\n    let token_type_ids = tokenizer_output.get_type_ids();\n    let length = input_ids.len();\n    let mask_pos =\n        input_ids.iter().position(|&x| x == tokenizer.token_to_id(\"[MASK]\").unwrap()).unwrap();\n\n    let model =\n        tract::onnx()?.load(Path::join(&model_dir, \"model.onnx\"))?.into_model()?.into_runnable()?;\n\n    let input_ids = tract_ndarray::Array2::from_shape_vec(\n        (1, length),\n        input_ids.iter().map(|&x| x as i64).collect(),\n    )?;\n    let attention_mask = tract_ndarray::Array2::from_shape_vec(\n        (1, length),\n        attention_mask.iter().map(|&x| x as i64).collect(),\n    )?;\n    let token_type_ids = tract_ndarray::Array2::from_shape_vec(\n        (1, length),\n        token_type_ids.iter().map(|&x| x as i64).collect(),\n    )?;\n\n    let outputs = model.run((input_ids, attention_mask, token_type_ids))?;\n    let logits = outputs[0].view::<f32>()?;\n    let logits = logits.slice(s![0, mask_pos, ..]);\n    let word_id = logits.iter().zip(0..).max_by(|a, b| a.0.partial_cmp(b.0).unwrap()).unwrap().1;\n    let word = tokenizer.id_to_token(word_id);\n    println!(\"Result: {word:?}\");\n\n    Ok(())\n}\n"
  },
  {
    "path": "examples/pytorch-resnet/.gitignore",
    "content": "resnet.onnx\n"
  },
  {
    "path": "examples/pytorch-resnet/Cargo.toml",
    "content": "[package]\nname = \"example-pytorch-resnet\"\nversion = \"0.20.7-pre\"\nauthors = [\"Teddy Koker <teddy.koker@gmail.com>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nanyhow.workspace = true\nimage.workspace = true\ntract.workspace = true\n"
  },
  {
    "path": "examples/pytorch-resnet/README.md",
    "content": "Example of exporting a PyTorch to ONNX format, then performing inference with\ntract.\n\n**Export Model:**\n```bash\npython export.py\n```\n\n**Inference on `elephants.jpg`:**\n```\ncargo run\nresult: Some((22.08386, 102))\n```\n\nPredicts class 102 (`tusker`).\n"
  },
  {
    "path": "examples/pytorch-resnet/ci.sh",
    "content": "#!/bin/sh\n\nset -e\nsudo apt-get install -y python3-virtualenv\nvirtualenv venv\n. ./venv/bin/activate\npip install -q torch torchvision onnx onnxscript\npython export.py\ncargo run\nrm -rf venv\n"
  },
  {
    "path": "examples/pytorch-resnet/export.py",
    "content": "import torch\nimport torchvision\nfrom torchvision.models import resnet18, ResNet18_Weights\n\ndef export():\n    dummy_input = torch.randn(1, 3, 224, 224)\n    model = resnet18(weights=ResNet18_Weights.DEFAULT).eval()\n    torch.onnx.export(model, (dummy_input,), \"resnet.onnx\")\n\nif __name__ == \"__main__\":\n    export()\n"
  },
  {
    "path": "examples/pytorch-resnet/requirements.txt",
    "content": "torch==2.6.0\ntorchvision==0.4.2\n"
  },
  {
    "path": "examples/pytorch-resnet/src/main.rs",
    "content": "use anyhow::Result;\nuse tract::prelude::*;\nuse tract_ndarray::Array;\n\nfn main() -> Result<()> {\n    let mut model = tract::onnx()?.load(\"resnet.onnx\")?;\n    model.set_input_fact(0, \"1,3,224,224,f32\")?;\n    let model = model.into_model()?.into_runnable()?;\n\n    // Imagenet mean and standard deviation\n    let mean = Array::from_shape_vec((1, 3, 1, 1), vec![0.485, 0.456, 0.406])?;\n    let std = Array::from_shape_vec((1, 3, 1, 1), vec![0.229, 0.224, 0.225])?;\n\n    let img = image::open(\"elephants.jpg\").unwrap().to_rgb8();\n    let resized = image::imageops::resize(&img, 224, 224, ::image::imageops::FilterType::Triangle);\n    let input = (tract_ndarray::Array4::from_shape_fn((1, 3, 224, 224), |(_, c, y, x)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    }) - mean)\n        / std;\n\n    let result = model.run([input])?;\n\n    // find and display the max value with its index\n    let best =\n        result[0].as_slice::<f32>()?.iter().zip(1..).max_by(|a, b| a.0.partial_cmp(b.0).unwrap());\n    println!(\"result: {best:?}\");\n    Ok(())\n}\n"
  },
  {
    "path": "examples/stable-diffusion/Cargo.toml",
    "content": "[package]\nname = \"stable-diffusion\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nclap = { workspace = true, features = [\"derive\"] }\nkdam = \"0.6\"\nimage = \"0.25\"\nndarray.workspace = true\nndarray-npy.workspace = true\nrand = \"0.9\"\nrand_distr = \"0.5\"\ntokenizers = \"0.22\"\ntract.workspace = true\n"
  },
  {
    "path": "examples/stable-diffusion/README.md",
    "content": "# Stable Diffusion 1.5 with tract\n\nA minimal text-to-image pipeline running entirely in Rust using [tract](https://github.com/sonos/tract). All three SD 1.5 components (text encoder, UNet, VAE decoder) run on GPU via tract's CUDA backend.\n\n## Quick start\n\n```bash\n# Export ONNX models and tokenizer (one-time setup)\npip install torch diffusers transformers accelerate onnxscript onnx Pillow\npython export.py\n\n# Generate images\ncargo run --release -- -p \"a photo of a cat\" -o cat.png\n```\n\n## Usage\n\n```\nstable-diffusion [OPTIONS]\n\nOptions:\n  -p, --prompt <PROMPT>            Text prompt [default: \"a photo of a cat\"]\n  -n, --num-images <NUM_IMAGES>    Number of images [default: 1]\n  -s, --steps <STEPS>              Denoising steps [default: 20]\n      --seed <SEED>                Random seed [default: 42]\n  -g, --guidance-scale <SCALE>     CFG scale [default: 7.5]\n  -o, --output <OUTPUT>            Output file [default: output.png]\n      --assets <ASSETS>            Model directory [default: assets]\n  -h, --help                       Print help\n```\n\nMultiple images get numbered: `output_0.png`, `output_1.png`, etc.\n\nImages display inline on iTerm2/WezTerm (including through tmux).\n\n## Performance\n\nOn an NVIDIA GPU (32GB VRAM):\n\n| Images | Steps | Time | Notes |\n|--------|-------|------|-------|\n| 1 | 20 | ~8s | Single image |\n| 4 | 20 | ~20s | Batched UNet, ~5s/image |\n| 1 | 10 | ~5s | Fewer steps, lower quality |\n\nAll three models run on CUDA. The UNet uses batched inference for classifier-free guidance (batch=2N for N images).\n\n## Architecture\n\n```\nPrompt → [Tokenizer] → [CLIP Text Encoder] → text embeddings\n                                                    ↓\nRandom noise → [Euler Scheduler + UNet × steps] → denoised latent\n                                                    ↓\n                              [VAE Decoder] → 512×512 RGB image\n```\n\n- **Tokenizer**: HuggingFace `tokenizers` crate (loads `tokenizer.json`)\n- **Text Encoder**: CLIP ViT-L/14, ONNX, runs on GPU\n- **UNet**: SD 1.5, ONNX with dynamic batch, runs on GPU\n- **VAE Decoder**: SD 1.5, ONNX, runs on GPU\n- **Scheduler**: Euler discrete, computed in Rust from the SD 1.5 noise schedule constants\n\n## Model export\n\n`export.py` exports the three model components from HuggingFace diffusers to ONNX. The UNet is exported with dynamic batch axes so tract can run classifier-free guidance in a single batched call.\n\n`reference.py` generates per-model input/output reference data for validation. The CI script validates each model against these references using `tract --assert-output-bundle`.\n\n## Files\n\n| File | Description |\n|------|-------------|\n| `src/main.rs` | Rust pipeline: tokenize, encode, denoise, decode, save |\n| `export.py` | Export SD 1.5 to ONNX |\n| `reference.py` | Generate validation data |\n| `ci.sh` | End-to-end CI: export + validate + generate |\n| `assets/` | Models, tokenizer (generated, not checked in) |\n"
  },
  {
    "path": "examples/stable-diffusion/ci-gpu.sh",
    "content": "#!/bin/bash\n\nset -ex\n\nSCRIPT_DIR=$(cd \"$(dirname \"$0\")\" && pwd)\ncd \"$SCRIPT_DIR\"\n\n# Create venv\nif [ ! -e .venv ]; then\n    if python3 -m venv .venv 2>/dev/null; then\n        true\n    else\n        ../../api/py/.venv/bin/virtualenv .venv\n    fi\nfi\nsource .venv/bin/activate\n\npip install -q torch diffusers \"transformers>=4.44,<4.50\" accelerate onnxscript onnx Pillow\n\n# Export models to ONNX\nmkdir -p assets\npython export.py\n\n# Generate reference I/O data for validation\npython reference.py\n\n# Validate each model against Python reference\n# tract-cli is pre-built by CI, fall back to building locally\nTRACT=../../target/opt-no-lto/tract\nif [ ! -x \"$TRACT\" ]; then\n    cargo build --profile opt-no-lto -p tract-cli\nfi\n\nif nvidia-smi > /dev/null 2>&1; then\n    RUNTIME=\"--cuda\"\n    GPU_ASSERT=\"--assert-op-only Cuda*,Gpu*,DeviceSync*,Const,Source,IsNan,Gather*,Reduce*\"\nelif [ \"$(uname)\" = \"Darwin\" ] && system_profiler SPDisplaysDataType 2>/dev/null | grep -qi metal; then\n    RUNTIME=\"--metal\"\n    GPU_ASSERT=\"--assert-op-only Metal*,Gpu*,DeviceSync*,Const,Source,IsNan,Gather*,Reduce*\"\nelse\n    RUNTIME=\"-O\"\n    GPU_ASSERT=\"\"\nfi\n\necho \"Validating text encoder ($RUNTIME)...\"\n$TRACT assets/text_encoder.onnx $RUNTIME run \\\n    --input-from-bundle assets/text_encoder.io.npz \\\n    --assert-output-bundle assets/text_encoder.io.npz --approx very $GPU_ASSERT\n\necho \"Validating VAE decoder ($RUNTIME)...\"\n$TRACT assets/vae_decoder.onnx $RUNTIME run \\\n    --input-from-bundle assets/vae_decoder.io.npz \\\n    --assert-output-bundle assets/vae_decoder.io.npz --approx very $GPU_ASSERT\n\necho \"Validating UNet ($RUNTIME)...\"\n$TRACT assets/unet.onnx $RUNTIME run \\\n    --input-from-bundle assets/unet.io.npz \\\n    --assert-output-bundle assets/unet.io.npz --approx very $GPU_ASSERT\n\n# Run the Rust example\ncargo run -p stable-diffusion --profile opt-no-lto -- \\\n    -p \"a photo of a cat\" -s 10 --seed 42 \\\n    -o assets/test_output.png \\\n    --assets assets\n\ntest -f assets/test_output.png\necho \"CI passed: test_output.png generated\"\n\nrm -rf assets .venv\n"
  },
  {
    "path": "examples/stable-diffusion/export.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Export Stable Diffusion 1.5 components to ONNX.\"\"\"\n\nimport os\nimport torch\nimport numpy as np\nfrom pathlib import Path\n\nASSETS = Path(\"assets\")\nMODEL_ID = \"stable-diffusion-v1-5/stable-diffusion-v1-5\"\n\n\ndef main():\n    from diffusers import StableDiffusionPipeline\n\n    component = os.environ.get(\"EXPORT_COMPONENT\", \"all\")\n\n    print(f\"Loading {MODEL_ID}...\")\n    pipe = StableDiffusionPipeline.from_pretrained(MODEL_ID, torch_dtype=torch.float32)\n    pipe = pipe.to(\"cpu\")\n\n    ASSETS.mkdir(parents=True, exist_ok=True)\n\n    if component in (\"all\", \"vae_decoder\"):\n        print(\"Exporting vae_decoder...\")\n        vae = pipe.vae\n        vae.eval()\n\n        class VaeDecoder(torch.nn.Module):\n            def __init__(self, vae):\n                super().__init__()\n                self.decoder = vae.decoder\n                self.post_quant_conv = vae.post_quant_conv\n\n            def forward(self, latent):\n                latent = self.post_quant_conv(latent)\n                return self.decoder(latent)\n\n        vae_decoder = VaeDecoder(vae)\n        latent = torch.randn(1, 4, 64, 64)\n        with torch.no_grad():\n            torch.onnx.export(\n                vae_decoder,\n                (latent,),\n                str(ASSETS / \"vae_decoder.onnx\"),\n                input_names=[\"latent\"],\n                output_names=[\"image\"],\n                opset_version=17,\n            )\n        print(f\"  Exported to {ASSETS / 'vae_decoder.onnx'}\")\n\n    if component in (\"all\", \"text_encoder\"):\n        print(\"Exporting text_encoder...\")\n        text_encoder = pipe.text_encoder\n        text_encoder.eval()\n        input_ids = torch.zeros(1, 77, dtype=torch.int64)\n        with torch.no_grad():\n            torch.onnx.export(\n                text_encoder,\n                (input_ids,),\n                str(ASSETS / \"text_encoder.onnx\"),\n                input_names=[\"input_ids\"],\n                output_names=[\"last_hidden_state\", \"pooler_output\"],\n                opset_version=17,\n            )\n        print(f\"  Exported to {ASSETS / 'text_encoder.onnx'}\")\n\n    if component in (\"all\", \"unet\"):\n        print(\"Exporting unet...\")\n        unet = pipe.unet\n        unet.eval()\n        sample = torch.randn(2, 4, 64, 64)\n        timestep = torch.tensor([999, 999], dtype=torch.int64)\n        encoder_hidden_states = torch.randn(2, 77, 768)\n        with torch.no_grad():\n            torch.onnx.export(\n                unet,\n                (sample, timestep, encoder_hidden_states),\n                str(ASSETS / \"unet.onnx\"),\n                input_names=[\"sample\", \"timestep\", \"encoder_hidden_states\"],\n                output_names=[\"noise_pred\"],\n                opset_version=17,\n                dynamic_axes={\n                    \"sample\": {0: \"batch\"},\n                    \"timestep\": {0: \"batch\"},\n                    \"encoder_hidden_states\": {0: \"batch\"},\n                    \"noise_pred\": {0: \"batch\"},\n                },\n            )\n        print(f\"  Exported to {ASSETS / 'unet.onnx'}\")\n\n    # Save fast tokenizer (with tokenizer.json) for Rust side\n    print(\"Saving tokenizer...\")\n    from transformers import CLIPTokenizerFast\n    tok = CLIPTokenizerFast.from_pretrained(MODEL_ID, subfolder=\"tokenizer\")\n    tok.save_pretrained(str(ASSETS / \"tokenizer\"))\n\n    print(\"Export complete.\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/stable-diffusion/reference.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Generate reference data for Stable Diffusion 1.5 validation.\"\"\"\n\nimport torch\nimport numpy as np\nfrom pathlib import Path\nfrom diffusers import StableDiffusionPipeline, EulerDiscreteScheduler\n\nASSETS = Path(\"assets\")\nMODEL_ID = \"stable-diffusion-v1-5/stable-diffusion-v1-5\"\nPROMPT = \"a photo of a cat\"\nSEED = 42\nNUM_STEPS = 20\nGUIDANCE_SCALE = 7.5\n\ndef main():\n    print(f\"Loading {MODEL_ID}...\")\n    pipe = StableDiffusionPipeline.from_pretrained(MODEL_ID, torch_dtype=torch.float32)\n    pipe.scheduler = EulerDiscreteScheduler.from_config(pipe.scheduler.config)\n    pipe = pipe.to(\"cpu\")\n\n    # Tokenize\n    tokenizer = pipe.tokenizer\n    tokens = tokenizer(\n        PROMPT,\n        padding=\"max_length\",\n        max_length=77,\n        truncation=True,\n        return_tensors=\"pt\",\n    )\n    input_ids = tokens.input_ids  # (1, 77)\n    print(f\"Token IDs shape: {input_ids.shape}\")\n\n    # Unconditional tokens (empty prompt for classifier-free guidance)\n    uncond_tokens = tokenizer(\n        \"\",\n        padding=\"max_length\",\n        max_length=77,\n        truncation=True,\n        return_tensors=\"pt\",\n    )\n    uncond_input_ids = uncond_tokens.input_ids\n\n    # Text encoder\n    with torch.no_grad():\n        text_output = pipe.text_encoder(input_ids)\n        text_embeddings = text_output[0]  # last_hidden_state (1, 77, 768)\n        uncond_output = pipe.text_encoder(uncond_input_ids)\n        uncond_embeddings = uncond_output[0]\n\n    # Concat for classifier-free guidance: [uncond, cond]\n    text_embeddings_cfg = torch.cat([uncond_embeddings, text_embeddings])  # (2, 77, 768)\n    print(f\"Text embeddings shape: {text_embeddings_cfg.shape}\")\n\n    # Scheduler setup\n    pipe.scheduler.set_timesteps(NUM_STEPS)\n    timesteps = pipe.scheduler.timesteps\n    print(f\"Timesteps: {timesteps}\")\n\n    # Initial latent\n    generator = torch.Generator().manual_seed(SEED)\n    latent = torch.randn(1, 4, 64, 64, generator=generator)\n    latent = latent * pipe.scheduler.init_noise_sigma\n    print(f\"Initial latent shape: {latent.shape}\")\n\n    # Denoising loop\n    with torch.no_grad():\n        for i, t in enumerate(timesteps):\n            latent_model_input = torch.cat([latent] * 2)  # (2, 4, 64, 64)\n            latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)\n\n            noise_pred = pipe.unet(\n                latent_model_input,\n                t,\n                encoder_hidden_states=text_embeddings_cfg,\n            ).sample\n\n            # Classifier-free guidance\n            noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n            noise_pred = noise_pred_uncond + GUIDANCE_SCALE * (noise_pred_text - noise_pred_uncond)\n\n            # Scheduler step\n            latent = pipe.scheduler.step(noise_pred, t, latent).prev_sample\n\n            if i % 5 == 0:\n                print(f\"  Step {i}/{NUM_STEPS}, t={t.item()}\")\n\n    print(f\"Final latent shape: {latent.shape}\")\n\n    # VAE decode\n    with torch.no_grad():\n        latent_scaled = latent / pipe.vae.config.scaling_factor\n        image = pipe.vae.decode(latent_scaled).sample\n\n    print(f\"Image shape: {image.shape}\")\n\n    # --- Save per-model I/O for validation ---\n    # Text encoder (has two outputs: last_hidden_state and pooler_output)\n    with torch.no_grad():\n        te_out = pipe.text_encoder(input_ids)\n    np.savez(str(ASSETS / \"text_encoder.io.npz\"),\n        input_ids=input_ids.numpy(),\n        last_hidden_state=te_out[0].numpy(),\n        pooler_output=te_out[1].numpy(),\n    )\n    print(\"Saved text_encoder.io.npz\")\n\n    # One UNet step (first step)\n    pipe.scheduler.set_timesteps(NUM_STEPS)\n    ts = pipe.scheduler.timesteps\n    generator2 = torch.Generator().manual_seed(SEED)\n    lat = torch.randn(1, 4, 64, 64, generator=generator2) * pipe.scheduler.init_noise_sigma\n    t0 = ts[0]\n    lat_in = pipe.scheduler.scale_model_input(lat, t0)\n    with torch.no_grad():\n        unet_out = pipe.unet(lat_in, t0, encoder_hidden_states=text_embeddings).sample\n    np.savez(str(ASSETS / \"unet.io.npz\"),\n        sample=lat_in.numpy(),\n        timestep=np.array([t0.item()], dtype=np.int64),\n        encoder_hidden_states=text_embeddings.numpy(),\n        noise_pred=unet_out.numpy(),\n    )\n    print(\"Saved unet.io.npz\")\n\n    # VAE decoder\n    np.savez(str(ASSETS / \"vae_decoder.io.npz\"),\n        latent=latent_scaled.numpy(),\n        image=image.numpy(),\n    )\n    print(\"Saved vae_decoder.io.npz\")\n\n    # --- Save all data for Rust in a single npz ---\n    pipe.scheduler.set_timesteps(NUM_STEPS)\n    sigma = pipe.scheduler.init_noise_sigma\n    sigma_val = sigma.item() if hasattr(sigma, 'item') else float(sigma)\n    sigmas = pipe.scheduler.sigmas.numpy().astype(np.float32)\n\n    np.savez(str(ASSETS / \"pipeline.npz\"),\n        input_ids=input_ids.numpy().astype(np.int64),\n        uncond_input_ids=uncond_input_ids.numpy().astype(np.int64),\n        initial_latent=torch.randn(1, 4, 64, 64, generator=torch.Generator().manual_seed(SEED)).numpy(),\n        timesteps=timesteps.numpy().astype(np.int64),\n        sigmas=sigmas,\n        vae_scaling_factor=np.array([pipe.vae.config.scaling_factor], dtype=np.float32),\n        init_noise_sigma=np.array([sigma_val], dtype=np.float32),\n    )\n    print(f\"Saved pipeline.npz (sigmas: {sigmas.shape}, timesteps: {timesteps.shape})\")\n\n    # Also save image as PNG for visual inspection\n    image_np = ((image[0].permute(1, 2, 0).clamp(-1, 1) + 1) / 2 * 255).byte().numpy()\n    from PIL import Image\n    Image.fromarray(image_np).save(str(ASSETS / \"reference.png\"))\n    print(f\"Saved reference image to {ASSETS / 'reference.png'}\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/stable-diffusion/src/main.rs",
    "content": "use anyhow::*;\nuse tract::prelude::*;\n\n/// SD 1.5 Euler discrete scheduler — compute sigmas from scratch.\nstruct EulerScheduler {\n    timesteps: Vec<i64>,\n    sigmas: Vec<f32>,\n}\n\nimpl EulerScheduler {\n    fn new(num_inference_steps: usize) -> Self {\n        use tract_ndarray::{Array1, Axis, concatenate};\n\n        // SD 1.5 config: scaled_linear beta schedule, 1000 training steps\n        let num_train = 1000;\n        let betas = Array1::linspace(0.00085f64.sqrt(), 0.012f64.sqrt(), num_train).mapv(|b| b * b);\n        let alphas = betas.mapv(|b| 1.0 - b);\n\n        // cumulative product → sigmas\n        let mut alphas_cumprod = alphas.clone();\n        for i in 1..num_train {\n            alphas_cumprod[i] *= alphas_cumprod[i - 1];\n        }\n        let all_sigmas = alphas_cumprod.mapv(|a| ((1.0 - a) / a).sqrt());\n\n        // Timesteps: linspace num_train-1 → 0\n        let timesteps = Array1::linspace((num_train - 1) as f64, 0.0, num_inference_steps)\n            .mapv(|t| t.round() as i64);\n\n        // Interpolate sigmas at timestep positions, append 0\n        let sigmas_at_t = timesteps.mapv(|t| {\n            let t = t as f64;\n            let lo = t.floor() as usize;\n            let hi = (lo + 1).min(num_train - 1);\n            let frac = t - lo as f64;\n            (all_sigmas[lo] * (1.0 - frac) + all_sigmas[hi] * frac) as f32\n        });\n        let sigmas =\n            concatenate(Axis(0), &[sigmas_at_t.view(), Array1::from_vec(vec![0.0f32]).view()])\n                .unwrap();\n\n        EulerScheduler { timesteps: timesteps.to_vec(), sigmas: sigmas.to_vec() }\n    }\n\n    fn init_noise_sigma(&self) -> f32 {\n        self.sigmas[0]\n    }\n\n    fn scale_factor(&self, step: usize) -> f32 {\n        let sigma = self.sigmas[step];\n        1.0 / (sigma * sigma + 1.0).sqrt()\n    }\n\n    fn dt(&self, step: usize) -> f32 {\n        self.sigmas[step + 1] - self.sigmas[step]\n    }\n}\n\n/// SD 1.5 VAE scaling factor\nconst VAE_SCALING_FACTOR: f32 = 0.18215;\n\nfn base64_encode(data: &[u8]) -> String {\n    const CHARS: &[u8] = b\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n    let mut out = String::with_capacity((data.len() + 2) / 3 * 4);\n    for chunk in data.chunks(3) {\n        let b = match chunk.len() {\n            3 => [chunk[0], chunk[1], chunk[2]],\n            2 => [chunk[0], chunk[1], 0],\n            1 => [chunk[0], 0, 0],\n            _ => unreachable!(),\n        };\n        let n = (b[0] as u32) << 16 | (b[1] as u32) << 8 | b[2] as u32;\n        out.push(CHARS[(n >> 18 & 63) as usize] as char);\n        out.push(CHARS[(n >> 12 & 63) as usize] as char);\n        if chunk.len() > 1 {\n            out.push(CHARS[(n >> 6 & 63) as usize] as char);\n        } else {\n            out.push('=');\n        }\n        if chunk.len() > 2 {\n            out.push(CHARS[(n & 63) as usize] as char);\n        } else {\n            out.push('=');\n        }\n    }\n    out\n}\n\n/// Stable Diffusion 1.5 image generation with tract\n#[derive(clap::Parser)]\nstruct Args {\n    /// Text prompt for image generation\n    #[arg(short, long, default_value = \"a photo of a cat\")]\n    prompt: String,\n\n    /// Number of images to generate\n    #[arg(short, long, default_value_t = 1)]\n    num_images: usize,\n\n    /// Number of denoising steps\n    #[arg(short, long, default_value_t = 20)]\n    steps: usize,\n\n    /// Random seed\n    #[arg(long, default_value_t = 42)]\n    seed: u64,\n\n    /// Classifier-free guidance scale\n    #[arg(short, long, default_value_t = 7.5)]\n    guidance_scale: f32,\n\n    /// Output filename (or prefix for multiple images)\n    #[arg(short, long, default_value = \"output.png\")]\n    output: String,\n\n    /// Path to model assets directory\n    #[arg(long, default_value = \"assets\")]\n    assets: std::path::PathBuf,\n}\n\nfn main() -> Result<()> {\n    use clap::Parser as _;\n    let args = Args::parse();\n\n    let prompt = &args.prompt;\n    let num_images = args.num_images;\n    let num_steps = args.steps;\n    let seed = args.seed;\n    let guidance_scale = args.guidance_scale;\n    let output = &args.output;\n    let assets = &args.assets;\n\n    eprintln!(\n        \"Prompt: \\\"{prompt}\\\", images: {num_images}, steps: {num_steps}, seed: {seed}, guidance: {guidance_scale}\"\n    );\n    let tokenizer = tokenizers::Tokenizer::from_file(assets.join(\"tokenizer/tokenizer.json\"))\n        .map_err(|e| anyhow!(\"{e}\"))?;\n    let encode = |text: &str| -> Result<ndarray::Array2<i64>> {\n        let enc = tokenizer.encode(text, true).map_err(|e| anyhow!(\"{e}\"))?;\n        let mut ids: Vec<i64> = enc.get_ids().iter().map(|&id| id as i64).collect();\n        ids.resize(77, tokenizer.token_to_id(\"<|endoftext|>\").unwrap_or(49407) as i64);\n        Ok(ndarray::Array2::from_shape_vec((1, 77), ids)?)\n    };\n    let input_ids = encode(prompt)?;\n    let uncond_input_ids = encode(\"\")?;\n\n    use rand::SeedableRng;\n    use rand_distr::{Distribution, StandardNormal};\n    let mut rng = rand::rngs::StdRng::seed_from_u64(seed);\n\n    // Build scheduler from scratch\n    let scheduler = EulerScheduler::new(num_steps);\n    let init_noise_sigma = scheduler.init_noise_sigma();\n    eprintln!(\"  Scheduler: {num_steps} steps, init_sigma={init_noise_sigma:.4}\");\n\n    // Pick best available runtime (CUDA > Metal > CPU)\n    let gpu = [\"cuda\", \"metal\", \"default\"]\n        .iter()\n        .find_map(|rt| tract::runtime_for_name(rt).ok())\n        .unwrap();\n    eprintln!(\"Using runtime: {gpu:?}\");\n\n    eprintln!(\"Loading models...\");\n    let onnx = tract::onnx()?;\n    let text_encoder = gpu.prepare(onnx.load(assets.join(\"text_encoder.onnx\"))?.into_model()?)?;\n    let unet = gpu.prepare(onnx.load(assets.join(\"unet.onnx\"))?.into_model()?)?;\n    let vae_decoder = gpu.prepare(onnx.load(assets.join(\"vae_decoder.onnx\"))?.into_model()?)?;\n\n    // --- Text encoding ---\n    eprintln!(\"Running text encoder...\");\n    let cond_emb = text_encoder.run([input_ids])?;\n    let uncond_emb = text_encoder.run([uncond_input_ids])?;\n\n    let cond = cond_emb[0].view::<f32>()?;\n    let uncond = uncond_emb[0].view::<f32>()?;\n    let text_embeddings =\n        tract_ndarray::concatenate(tract_ndarray::Axis(0), &[uncond.view(), cond.view()])?;\n    eprintln!(\"  Text embeddings: {:?}\", text_embeddings.shape());\n\n    // Build batched text embeddings: [uncond×N, cond×N] → (2N, 77, 768)\n    let uncond_slice = uncond_emb[0].as_slice::<f32>()?;\n    let cond_slice = cond_emb[0].as_slice::<f32>()?;\n    let emb_size = 77 * 768;\n    let mut emb_data = Vec::with_capacity(2 * num_images * emb_size);\n    for _ in 0..num_images {\n        emb_data.extend_from_slice(uncond_slice);\n    }\n    for _ in 0..num_images {\n        emb_data.extend_from_slice(cond_slice);\n    }\n    let b2 = 2 * num_images;\n    let text_emb = tract_ndarray::ArrayD::from_shape_vec(vec![b2, 77, 768], emb_data)?;\n\n    // Generate initial latent noise for all images\n    let latent_size = 4 * 64 * 64;\n    let mut latents: Vec<f32> = (0..num_images * latent_size)\n        .map(|_| {\n            <StandardNormal as Distribution<f32>>::sample(&StandardNormal, &mut rng)\n                * init_noise_sigma\n        })\n        .collect();\n\n    // --- Batched denoising loop ---\n    use kdam::BarExt as _;\n    let mut pb = kdam::Bar::builder()\n        .total(num_steps)\n        .desc(format!(\"Denoising {num_images} image(s)\"))\n        .build()\n        .unwrap();\n    for (i, &t) in scheduler.timesteps.iter().enumerate() {\n        let scale = scheduler.scale_factor(i);\n\n        // Build UNet input: [scaled_latents×N, scaled_latents×N] → (2N, 4, 64, 64)\n        let mut sample_data = Vec::with_capacity(b2 * latent_size);\n        for &x in &latents {\n            sample_data.push(x * scale);\n        }\n        for &x in &latents {\n            sample_data.push(x * scale);\n        }\n        let sample = tract_ndarray::ArrayD::from_shape_vec(vec![b2, 4, 64, 64], sample_data)?;\n        let timestep = tract_ndarray::Array1::from_vec(vec![t; b2]).into_dyn();\n\n        let noise_pred =\n            unet.run(vec![tensor(sample)?, tensor(timestep)?, tensor(text_emb.clone())?])?;\n        let pred = noise_pred[0].as_slice::<f32>()?;\n\n        // CFG: split uncond (first N) / cond (last N), combine\n        let batch_latent_size = num_images * latent_size;\n        let dt = scheduler.dt(i);\n        for j in 0..batch_latent_size {\n            let u = pred[j];\n            let c = pred[batch_latent_size + j];\n            let eps = u + guidance_scale * (c - u);\n            latents[j] += eps * dt;\n        }\n\n        let sigma = scheduler.sigmas[i];\n        pb.set_postfix(format!(\"t={t} σ={sigma:.2}\"));\n        pb.update(1).ok();\n    }\n    eprintln!();\n\n    // --- VAE decode + save each image ---\n    let (h, w) = (512usize, 512usize);\n    for n in 0..num_images {\n        let img_latent: Vec<f32> = latents[n * latent_size..(n + 1) * latent_size]\n            .iter()\n            .map(|&x| x / VAE_SCALING_FACTOR)\n            .collect();\n        let latent_arr = tract_ndarray::ArrayD::from_shape_vec(vec![1, 4, 64, 64], img_latent)?;\n        let image_result = vae_decoder.run([latent_arr])?;\n        let image_data = image_result[0].as_slice::<f32>()?;\n\n        let mut pixels = vec![0u8; h * w * 3];\n        for y in 0..h {\n            for x in 0..w {\n                for ch in 0..3 {\n                    let val =\n                        (image_data[ch * h * w + y * w + x].clamp(-1.0, 1.0) + 1.0) / 2.0 * 255.0;\n                    pixels[(y * w + x) * 3 + ch] = val as u8;\n                }\n            }\n        }\n        let path = if num_images == 1 {\n            std::path::PathBuf::from(output)\n        } else {\n            let stem = std::path::Path::new(output)\n                .file_stem()\n                .unwrap_or_default()\n                .to_str()\n                .unwrap_or(\"output\");\n            let ext = std::path::Path::new(output)\n                .extension()\n                .unwrap_or_default()\n                .to_str()\n                .unwrap_or(\"png\");\n            std::path::PathBuf::from(format!(\"{stem}_{n}.{ext}\"))\n        };\n        image::save_buffer(&path, &pixels, w as u32, h as u32, image::ColorType::Rgb8)?;\n        eprintln!(\"Saved {}\", path.display());\n\n        // Display inline in iTerm2/WezTerm/compatible terminals (with tmux passthrough)\n        {\n            use std::io::Write as _;\n            if let std::result::Result::Ok(png_data) = std::fs::read(&path) {\n                let b64 = base64_encode(&png_data);\n                let in_tmux = std::env::var(\"TMUX\").is_ok();\n                let osc = if in_tmux { \"\\x1bPtmux;\\x1b\\x1b]\" } else { \"\\x1b]\" };\n                let st = if in_tmux { \"\\x07\\x1b\\\\\" } else { \"\\x07\" };\n                let _ = write!(\n                    std::io::stderr(),\n                    \"{osc}1337;File=inline=1;width=20;preserveAspectRatio=1:{b64}{st}\\n\"\n                );\n            }\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "examples/stable-diffusion-3/Cargo.toml",
    "content": "[package]\nname = \"stable-diffusion-3\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nclap = { workspace = true, features = [\"derive\"] }\nimage = \"0.25\"\nkdam = \"0.6\"\nndarray.workspace = true\nrand = \"0.9\"\nrand_distr = \"0.5\"\ntokenizers = \"0.22\"\ntract.workspace = true\n"
  },
  {
    "path": "examples/stable-diffusion-3/export.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Export Stable Diffusion 3 Medium components to ONNX.\"\"\"\n\nimport torch\nfrom pathlib import Path\nfrom transformers import CLIPTokenizerFast\n\nASSETS = Path(\"assets\")\nMODEL_ID = \"stabilityai/stable-diffusion-3-medium-diffusers\"\n\n\ndef main():\n    import argparse\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--with-t5\", action=\"store_true\", help=\"Include T5-XXL encoder (~18GB)\")\n    args = parser.parse_args()\n\n    from diffusers import StableDiffusion3Pipeline\n\n    extra = {} if args.with_t5 else dict(text_encoder_3=None, tokenizer_3=None)\n    print(f\"Loading {MODEL_ID} ({'with' if args.with_t5 else 'without'} T5)...\")\n    pipe = StableDiffusion3Pipeline.from_pretrained(\n        MODEL_ID, torch_dtype=torch.float32, **extra,\n    )\n    pipe = pipe.to(\"cpu\")\n\n    ASSETS.mkdir(parents=True, exist_ok=True)\n\n    # --- Text Encoder 1 (CLIP-L/14, hidden_size=768) ---\n    print(\"Exporting text_encoder (CLIP-L)...\")\n    text_encoder = pipe.text_encoder\n    text_encoder.eval()\n    input_ids = torch.zeros(1, 77, dtype=torch.int64)\n    with torch.no_grad():\n        torch.onnx.export(\n            text_encoder,\n            (input_ids,),\n            str(ASSETS / \"text_encoder.onnx\"),\n            input_names=[\"input_ids\"],\n            output_names=[\"text_embeds\", \"last_hidden_state\"],\n            opset_version=18,\n        )\n    print(f\"  Exported to {ASSETS / 'text_encoder.onnx'}\")\n\n    # --- Text Encoder 2 (OpenCLIP bigG/14, hidden_size=1280) ---\n    print(\"Exporting text_encoder_2 (CLIP-G)...\")\n    text_encoder_2 = pipe.text_encoder_2\n    text_encoder_2.eval()\n    with torch.no_grad():\n        torch.onnx.export(\n            text_encoder_2,\n            (input_ids,),\n            str(ASSETS / \"text_encoder_2.onnx\"),\n            input_names=[\"input_ids\"],\n            output_names=[\"text_embeds\", \"last_hidden_state\"],\n            opset_version=18,\n        )\n    print(f\"  Exported to {ASSETS / 'text_encoder_2.onnx'}\")\n\n    # --- Text Encoder 3 (T5-XXL, hidden_size=4096, optional) ---\n    if args.with_t5:\n        print(\"Exporting text_encoder_3 (T5-XXL)...\")\n        text_encoder_3 = pipe.text_encoder_3\n        text_encoder_3.eval()\n        t5_input_ids = torch.zeros(1, 256, dtype=torch.int64)\n        with torch.no_grad():\n            torch.onnx.export(\n                text_encoder_3,\n                (t5_input_ids,),\n                str(ASSETS / \"text_encoder_3.onnx\"),\n                input_names=[\"input_ids\"],\n                output_names=[\"last_hidden_state\"],\n                opset_version=18,\n            )\n        print(f\"  Exported to {ASSETS / 'text_encoder_3.onnx'}\")\n\n    # --- VAE Decoder ---\n    print(\"Exporting vae_decoder...\")\n    vae = pipe.vae\n    vae.eval()\n\n    class VaeDecoder(torch.nn.Module):\n        def __init__(self, vae):\n            super().__init__()\n            self.vae = vae\n\n        def forward(self, latent):\n            return self.vae.decode(latent).sample\n\n    vae_decoder = VaeDecoder(vae)\n    latent = torch.randn(1, 16, 128, 128)\n    with torch.no_grad():\n        torch.onnx.export(\n            vae_decoder,\n            (latent,),\n            str(ASSETS / \"vae_decoder.onnx\"),\n            input_names=[\"latent\"],\n            output_names=[\"image\"],\n            opset_version=18,\n        )\n    print(f\"  Exported to {ASSETS / 'vae_decoder.onnx'}\")\n\n    # --- Transformer (MMDiT) ---\n    seq_len = 333 if args.with_t5 else 77\n    print(f\"Exporting transformer (MMDiT, seq={seq_len})...\")\n    transformer = pipe.transformer\n    transformer.eval()\n\n    hidden_states = torch.randn(2, 16, 128, 128)\n    encoder_hidden_states = torch.randn(2, seq_len, 4096)\n    pooled_projections = torch.randn(2, 2048)\n    timestep = torch.tensor([500.0, 500.0])\n\n    class TransformerWrapper(torch.nn.Module):\n        def __init__(self, transformer):\n            super().__init__()\n            self.transformer = transformer\n\n        def forward(self, hidden_states, encoder_hidden_states, pooled_projections, timestep):\n            return self.transformer(\n                hidden_states=hidden_states,\n                encoder_hidden_states=encoder_hidden_states,\n                pooled_projections=pooled_projections,\n                timestep=timestep,\n            ).sample\n\n    wrapper = TransformerWrapper(transformer)\n    with torch.no_grad():\n        torch.onnx.export(\n            wrapper,\n            (hidden_states, encoder_hidden_states, pooled_projections, timestep),\n            str(ASSETS / \"transformer.onnx\"),\n            input_names=[\"hidden_states\", \"encoder_hidden_states\", \"pooled_projections\", \"timestep\"],\n            output_names=[\"output\"],\n            opset_version=18,\n            dynamic_axes={\n                \"hidden_states\": {0: \"batch\"},\n                \"encoder_hidden_states\": {0: \"batch\"},\n                \"pooled_projections\": {0: \"batch\"},\n                \"timestep\": {0: \"batch\"},\n                \"output\": {0: \"batch\"},\n            },\n        )\n    print(f\"  Exported to {ASSETS / 'transformer.onnx'}\")\n\n    # Save tokenizers\n    print(\"Saving tokenizers...\")\n    tok = CLIPTokenizerFast.from_pretrained(MODEL_ID, subfolder=\"tokenizer\")\n    tok.save_pretrained(str(ASSETS / \"tokenizer\"))\n    if args.with_t5:\n        from transformers import AutoTokenizer\n        tok3 = AutoTokenizer.from_pretrained(MODEL_ID, subfolder=\"tokenizer_3\")\n        tok3.save_pretrained(str(ASSETS / \"tokenizer_3\"))\n\n    print(\"Export complete.\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/stable-diffusion-3/reference.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Generate reference I/O data for SD3 model validation.\"\"\"\n\nimport torch\nimport numpy as np\nfrom pathlib import Path\nfrom diffusers import StableDiffusion3Pipeline, FlowMatchEulerDiscreteScheduler\n\nASSETS = Path(\"assets\")\nMODEL_ID = \"stabilityai/stable-diffusion-3-medium-diffusers\"\nPROMPT = \"a photo of a cat\"\nSEED = 42\nNUM_STEPS = 28\n\n\ndef main():\n    print(f\"Loading {MODEL_ID} (without T5)...\")\n    pipe = StableDiffusion3Pipeline.from_pretrained(\n        MODEL_ID, torch_dtype=torch.float32, text_encoder_3=None, tokenizer_3=None,\n    )\n    pipe.scheduler = FlowMatchEulerDiscreteScheduler.from_config(pipe.scheduler.config)\n    pipe = pipe.to(\"cpu\")\n\n    tokenizer = pipe.tokenizer\n    tokenizer_2 = pipe.tokenizer_2\n\n    # Tokenize\n    tokens = tokenizer(PROMPT, padding=\"max_length\", max_length=77, truncation=True, return_tensors=\"pt\")\n    uncond_tokens = tokenizer(\"\", padding=\"max_length\", max_length=77, truncation=True, return_tensors=\"pt\")\n    tokens_2 = tokenizer_2(PROMPT, padding=\"max_length\", max_length=77, truncation=True, return_tensors=\"pt\")\n    uncond_tokens_2 = tokenizer_2(\"\", padding=\"max_length\", max_length=77, truncation=True, return_tensors=\"pt\")\n\n    input_ids = tokens.input_ids\n    uncond_input_ids = uncond_tokens.input_ids\n    input_ids_2 = tokens_2.input_ids\n    uncond_input_ids_2 = uncond_tokens_2.input_ids\n\n    # --- Text Encoder 1 (CLIP-L) ---\n    with torch.no_grad():\n        te1_cond = pipe.text_encoder(input_ids, output_hidden_states=True)\n        te1_uncond = pipe.text_encoder(uncond_input_ids, output_hidden_states=True)\n\n    # CLIPTextModelWithProjection: text_embeds = projected pooled, hidden_states[-2] = penultimate\n    np.savez(str(ASSETS / \"text_encoder.io.npz\"),\n        input_ids=input_ids.numpy(),\n        text_embeds=te1_cond.text_embeds.numpy(),\n        last_hidden_state=te1_cond.last_hidden_state.numpy(),\n    )\n    print(\"Saved text_encoder.io.npz\")\n\n    # --- Text Encoder 2 (CLIP-G) ---\n    with torch.no_grad():\n        te2_cond = pipe.text_encoder_2(input_ids_2, output_hidden_states=True)\n        te2_uncond = pipe.text_encoder_2(uncond_input_ids_2, output_hidden_states=True)\n\n    np.savez(str(ASSETS / \"text_encoder_2.io.npz\"),\n        input_ids=input_ids_2.numpy(),\n        text_embeds=te2_cond.text_embeds.numpy(),\n        last_hidden_state=te2_cond.last_hidden_state.numpy(),\n    )\n    print(\"Saved text_encoder_2.io.npz\")\n\n    # --- Concat text embeddings (without T5) ---\n    # Penultimate hidden states from both CLIPs\n    cond_h1 = te1_cond.hidden_states[-2]   # (1, 77, 768)\n    cond_h2 = te2_cond.hidden_states[-2]   # (1, 77, 1280)\n    uncond_h1 = te1_uncond.hidden_states[-2]\n    uncond_h2 = te2_uncond.hidden_states[-2]\n\n    # Concatenate along feature dim then pad to 4096\n    cond_clip = torch.cat([cond_h1, cond_h2], dim=-1)      # (1, 77, 2048)\n    uncond_clip = torch.cat([uncond_h1, uncond_h2], dim=-1)\n    cond_embeds = torch.nn.functional.pad(cond_clip, (0, 4096 - 2048))    # (1, 77, 4096)\n    uncond_embeds = torch.nn.functional.pad(uncond_clip, (0, 4096 - 2048))\n\n    # Pooled: concatenate CLIP pooled outputs\n    cond_pooled = torch.cat([te1_cond.text_embeds, te2_cond.text_embeds], dim=-1)      # (1, 2048)\n    uncond_pooled = torch.cat([te1_uncond.text_embeds, te2_uncond.text_embeds], dim=-1)\n\n    print(f\"Text embeddings: {cond_embeds.shape}, pooled: {cond_pooled.shape}\")\n\n    # --- Transformer (one step) ---\n    pipe.scheduler.set_timesteps(NUM_STEPS)\n    ts = pipe.scheduler.timesteps\n    sigmas = pipe.scheduler.sigmas\n    generator = torch.Generator().manual_seed(SEED)\n    latent = torch.randn(1, 16, 128, 128, generator=generator)\n    # Scale initial noise by first sigma\n    latent = latent * sigmas[0]\n\n    t0 = ts[0]\n    sigma0 = sigmas[0]\n    lat_in = latent / ((sigma0**2 + 1) ** 0.5)\n\n    with torch.no_grad():\n        pred = pipe.transformer(\n            hidden_states=lat_in,\n            encoder_hidden_states=cond_embeds,\n            pooled_projections=cond_pooled,\n            timestep=t0.unsqueeze(0),\n        ).sample\n\n    np.savez(str(ASSETS / \"transformer.io.npz\"),\n        hidden_states=lat_in.numpy(),\n        encoder_hidden_states=cond_embeds.numpy(),\n        pooled_projections=cond_pooled.numpy(),\n        timestep=np.array([t0.item()], dtype=np.float32),\n        output=pred.numpy(),\n    )\n    print(\"Saved transformer.io.npz\")\n\n    # --- Full denoising loop ---\n    print(f\"Running full pipeline ({NUM_STEPS} steps)...\")\n    latent = torch.randn(1, 16, 128, 128, generator=torch.Generator().manual_seed(SEED))\n    latent = latent * sigmas[0]\n\n    with torch.no_grad():\n        for i, t in enumerate(ts):\n            lat_uncond = latent.clone()\n            lat_cond = latent.clone()\n            lat_in = torch.cat([lat_uncond, lat_cond])\n\n            pred = pipe.transformer(\n                hidden_states=lat_in,\n                encoder_hidden_states=torch.cat([uncond_embeds, cond_embeds]),\n                pooled_projections=torch.cat([uncond_pooled, cond_pooled]),\n                timestep=t.expand(2),\n            ).sample\n\n            pred_uncond, pred_cond = pred.chunk(2)\n            pred_guided = pred_uncond + 7.0 * (pred_cond - pred_uncond)\n\n            sigma = sigmas[i]\n            sigma_next = sigmas[i + 1]\n            latent = latent + (sigma_next - sigma) * pred_guided\n\n            if i % 7 == 0:\n                print(f\"  Step {i}/{NUM_STEPS}, t={t.item():.1f}\")\n\n    # --- VAE decode ---\n    scaling_factor = pipe.vae.config.scaling_factor   # 1.5305\n    shift_factor = pipe.vae.config.shift_factor       # 0.0609\n    latent_scaled = latent / scaling_factor + shift_factor\n\n    with torch.no_grad():\n        image = pipe.vae.decode(latent_scaled).sample\n\n    np.savez(str(ASSETS / \"vae_decoder.io.npz\"),\n        latent=latent_scaled.numpy(),\n        image=image.numpy(),\n    )\n    print(\"Saved vae_decoder.io.npz\")\n\n    # Save reference image\n    image_np = ((image[0].permute(1, 2, 0).clamp(-1, 1) + 1) / 2 * 255).byte().numpy()\n    from PIL import Image\n    Image.fromarray(image_np).save(str(ASSETS / \"reference.png\"))\n    print(f\"Saved reference image to {ASSETS / 'reference.png'}\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/stable-diffusion-3/runme.sh",
    "content": "#!/bin/bash\n\nset -ex\n\nSCRIPT_DIR=$(cd \"$(dirname \"$0\")\" && pwd)\ncd \"$SCRIPT_DIR\"\n\n# Create venv\nif [ ! -e .venv ]; then\n    if python3 -m venv .venv 2>/dev/null; then\n        true\n    else\n        ../../api/py/.venv/bin/virtualenv .venv\n    fi\nfi\nsource .venv/bin/activate\n\npip install -q torch diffusers transformers accelerate onnxscript onnx Pillow sentencepiece protobuf\n\n# Export models + reference I/O\nmkdir -p assets\npython export.py\npython reference.py\n\n# Validate each model against Python reference\n# tract-cli is pre-built by CI, fall back to building locally\nTRACT=../../target/opt-no-lto/tract\nif [ ! -x \"$TRACT\" ]; then\n    cargo build --profile opt-no-lto -p tract-cli\nfi\n\nif nvidia-smi > /dev/null 2>&1; then\n    RUNTIME=\"--cuda\"\nelse\n    RUNTIME=\"-O\"\nfi\n\necho \"Validating text encoder 1 ($RUNTIME)...\"\n$TRACT assets/text_encoder.onnx $RUNTIME run \\\n    --input-from-bundle assets/text_encoder.io.npz \\\n    --assert-output-bundle assets/text_encoder.io.npz --approx very\n\necho \"Validating text encoder 2 ($RUNTIME)...\"\n$TRACT assets/text_encoder_2.onnx $RUNTIME run \\\n    --input-from-bundle assets/text_encoder_2.io.npz \\\n    --assert-output-bundle assets/text_encoder_2.io.npz --approx very\n\necho \"Validating transformer ($RUNTIME)...\"\n$TRACT assets/transformer.onnx $RUNTIME run \\\n    --input-from-bundle assets/transformer.io.npz \\\n    --assert-output-bundle assets/transformer.io.npz --approx very\n\necho \"Validating VAE decoder ($RUNTIME)...\"\n$TRACT assets/vae_decoder.onnx $RUNTIME run \\\n    --input-from-bundle assets/vae_decoder.io.npz \\\n    --assert-output-bundle assets/vae_decoder.io.npz --approx very\n\n# Run the Rust example\ncargo run -p stable-diffusion-3 --profile opt-no-lto -- \\\n    -p \"a photo of a cat\" -s 10 --seed 42 \\\n    -o assets/test_output.png \\\n    --assets assets\n\ntest -f assets/test_output.png\necho \"CI passed: test_output.png generated\"\n\nrm -rf assets .venv\n"
  },
  {
    "path": "examples/stable-diffusion-3/src/main.rs",
    "content": "use anyhow::*;\nuse tract::prelude::*;\n\n/// Flow-matching Euler scheduler for SD3.\nstruct FlowMatchScheduler {\n    timesteps: Vec<f32>,\n    sigmas: Vec<f32>,\n}\n\nimpl FlowMatchScheduler {\n    fn new(num_inference_steps: usize, shift: f32) -> Self {\n        let n = num_inference_steps;\n        // Linearly spaced sigmas from 1.0 to 0.0\n        let sigmas_unshifted: Vec<f32> = (0..=n).map(|i| 1.0 - i as f32 / n as f32).collect();\n        // Apply shift: sigma_shifted = shift * sigma / (1 + (shift - 1) * sigma)\n        let sigmas: Vec<f32> =\n            sigmas_unshifted.iter().map(|&s| shift * s / (1.0 + (shift - 1.0) * s)).collect();\n        // Timesteps = sigmas[0..n] * 1000\n        let timesteps: Vec<f32> = sigmas[..n].iter().map(|&s| s * 1000.0).collect();\n        FlowMatchScheduler { timesteps, sigmas }\n    }\n\n    fn init_noise_sigma(&self) -> f32 {\n        self.sigmas[0]\n    }\n\n    fn dt(&self, step: usize) -> f32 {\n        self.sigmas[step + 1] - self.sigmas[step]\n    }\n}\n\nconst VAE_SCALING_FACTOR: f32 = 1.5305;\nconst VAE_SHIFT_FACTOR: f32 = 0.0609;\n\nfn base64_encode(data: &[u8]) -> String {\n    const CHARS: &[u8] = b\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n    let mut out = String::with_capacity((data.len() + 2) / 3 * 4);\n    for chunk in data.chunks(3) {\n        let b = match chunk.len() {\n            3 => [chunk[0], chunk[1], chunk[2]],\n            2 => [chunk[0], chunk[1], 0],\n            _ => [chunk[0], 0, 0],\n        };\n        let n = (b[0] as u32) << 16 | (b[1] as u32) << 8 | b[2] as u32;\n        out.push(CHARS[(n >> 18 & 63) as usize] as char);\n        out.push(CHARS[(n >> 12 & 63) as usize] as char);\n        out.push(if chunk.len() > 1 { CHARS[(n >> 6 & 63) as usize] as char } else { '=' });\n        out.push(if chunk.len() > 2 { CHARS[(n & 63) as usize] as char } else { '=' });\n    }\n    out\n}\n\nfn display_inline(path: &std::path::Path) {\n    use std::io::Write as _;\n    let img = match image::open(path) {\n        std::result::Result::Ok(img) => img,\n        _ => return,\n    };\n    let thumb = img.thumbnail(256, 256);\n    let mut png_data = Vec::new();\n    let encoder = image::codecs::png::PngEncoder::new(std::io::Cursor::new(&mut png_data));\n    if thumb.write_with_encoder(encoder).is_err() {\n        return;\n    }\n    let b64 = base64_encode(&png_data);\n    let in_tmux = std::env::var(\"TMUX\").is_ok();\n    let osc = if in_tmux { \"\\x1bPtmux;\\x1b\\x1b]\" } else { \"\\x1b]\" };\n    let st = if in_tmux { \"\\x07\\x1b\\\\\" } else { \"\\x07\" };\n    let _ = write!(\n        std::io::stderr(),\n        \"{osc}1337;File=inline=1;width=20;preserveAspectRatio=1:{b64}{st}\\n\"\n    );\n}\n\n/// Stable Diffusion 3 Medium image generation with tract\n#[derive(clap::Parser)]\nstruct Args {\n    /// Text prompt\n    #[arg(short, long, default_value = \"a photo of a cat\")]\n    prompt: String,\n\n    /// Number of images\n    #[arg(short, long, default_value_t = 1)]\n    num_images: usize,\n\n    /// Denoising steps\n    #[arg(short, long, default_value_t = 28)]\n    steps: usize,\n\n    /// Random seed\n    #[arg(long, default_value_t = 42)]\n    seed: u64,\n\n    /// Classifier-free guidance scale\n    #[arg(short, long, default_value_t = 7.0)]\n    guidance_scale: f32,\n\n    /// Output filename\n    #[arg(short, long, default_value = \"output.png\")]\n    output: String,\n\n    /// Model assets directory\n    #[arg(long, default_value = \"assets\")]\n    assets: std::path::PathBuf,\n}\n\nfn main() -> Result<()> {\n    use clap::Parser as _;\n    let args = Args::parse();\n    let num_images = args.num_images;\n    let num_steps = args.steps;\n    let guidance_scale = args.guidance_scale;\n    let assets = &args.assets;\n\n    eprintln!(\n        \"SD3: \\\"{}\\\", images: {num_images}, steps: {num_steps}, seed: {}, guidance: {guidance_scale}\",\n        args.prompt, args.seed\n    );\n\n    // --- Tokenize CLIP (shared tokenizer for CLIP-L and CLIP-G) ---\n    let tokenizer = tokenizers::Tokenizer::from_file(assets.join(\"tokenizer/tokenizer.json\"))\n        .map_err(|e| anyhow!(\"{e}\"))?;\n    let encode_clip = |text: &str| -> Result<ndarray::Array2<i64>> {\n        let enc = tokenizer.encode(text, true).map_err(|e| anyhow!(\"{e}\"))?;\n        let mut ids: Vec<i64> = enc.get_ids().iter().map(|&id| id as i64).collect();\n        ids.resize(77, tokenizer.token_to_id(\"<|endoftext|>\").unwrap_or(49407) as i64);\n        Ok(ndarray::Array2::from_shape_vec((1, 77), ids)?)\n    };\n    let input_ids = encode_clip(&args.prompt)?;\n    let uncond_input_ids = encode_clip(\"\")?;\n\n    // --- Tokenize T5 (optional, if tokenizer_3 exists) ---\n    let t5_tokenizer =\n        tokenizers::Tokenizer::from_file(assets.join(\"tokenizer_3/tokenizer.json\")).ok();\n    let has_t5 = t5_tokenizer.is_some() && assets.join(\"text_encoder_3.onnx\").exists();\n    let encode_t5 = |text: &str| -> Result<ndarray::Array2<i64>> {\n        let tok = t5_tokenizer.as_ref().unwrap();\n        let enc = tok.encode(text, true).map_err(|e| anyhow!(\"{e}\"))?;\n        let mut ids: Vec<i64> = enc.get_ids().iter().map(|&id| id as i64).collect();\n        // T5 pad token is 0, max length 256\n        ids.resize(256, 0);\n        Ok(ndarray::Array2::from_shape_vec((1, 256), ids)?)\n    };\n    let t5_input_ids = if has_t5 { Some(encode_t5(&args.prompt)?) } else { None };\n    let t5_uncond_ids = if has_t5 { Some(encode_t5(\"\")?) } else { None };\n\n    use rand::SeedableRng;\n    use rand_distr::{Distribution, StandardNormal};\n    let mut rng = rand::rngs::StdRng::seed_from_u64(args.seed);\n\n    let scheduler = FlowMatchScheduler::new(num_steps, 3.0);\n    let init_sigma = scheduler.init_noise_sigma();\n    eprintln!(\"  Scheduler: {num_steps} steps, shift=3.0, init_sigma={init_sigma:.4}\");\n\n    // --- Pick runtime ---\n    let gpu = [\"cuda\", \"metal\", \"default\"]\n        .iter()\n        .find_map(|rt| tract::runtime_for_name(rt).ok())\n        .unwrap();\n    eprintln!(\"Using runtime: {gpu:?}\");\n\n    // --- Load models ---\n    // Text encoders run on CPU (T5-XXL is 18GB alone); transformer + VAE go on GPU.\n    eprintln!(\"Loading models{}...\", if has_t5 { \" (with T5)\" } else { \"\" });\n    let onnx = tract::onnx()?;\n    let cpu = tract::runtime_for_name(\"default\")?;\n    let text_encoder = cpu.prepare(onnx.load(assets.join(\"text_encoder.onnx\"))?.into_model()?)?;\n    let text_encoder_2 =\n        cpu.prepare(onnx.load(assets.join(\"text_encoder_2.onnx\"))?.into_model()?)?;\n    let text_encoder_3 = if has_t5 {\n        Some(cpu.prepare(onnx.load(assets.join(\"text_encoder_3.onnx\"))?.into_model()?)?)\n    } else {\n        None\n    };\n    let transformer = gpu.prepare(onnx.load(assets.join(\"transformer.onnx\"))?.into_model()?)?;\n    let vae_decoder = gpu.prepare(onnx.load(assets.join(\"vae_decoder.onnx\"))?.into_model()?)?;\n\n    // --- Text encoding ---\n    eprintln!(\"Running text encoders...\");\n    let cond1 = text_encoder.run([input_ids.clone()])?;\n    let uncond1 = text_encoder.run([uncond_input_ids.clone()])?;\n    let cond2 = text_encoder_2.run([input_ids])?;\n    let uncond2 = text_encoder_2.run([uncond_input_ids])?;\n\n    // CLIPTextModelWithProjection outputs:\n    //   output[0] = text_embeds (pooled projected), output[1] = last_hidden_state\n    let cond_h1 = cond1[1].view::<f32>()?; // (1, 77, 768)\n    let cond_h2 = cond2[1].view::<f32>()?; // (1, 77, 1280)\n    let uncond_h1 = uncond1[1].view::<f32>()?;\n    let uncond_h2 = uncond2[1].view::<f32>()?;\n\n    // Concatenate CLIP hidden states: (1,77,768) + (1,77,1280) → (1,77,2048), pad to 4096\n    let cond_clip =\n        tract_ndarray::concatenate(tract_ndarray::Axis(2), &[cond_h1.view(), cond_h2.view()])?\n            .as_standard_layout()\n            .into_owned();\n    let uncond_clip =\n        tract_ndarray::concatenate(tract_ndarray::Axis(2), &[uncond_h1.view(), uncond_h2.view()])?\n            .as_standard_layout()\n            .into_owned();\n\n    // Pad CLIP to 4096: (1, 77, 2048) → (77 * 4096) flat\n    let pad_clip = |arr: &ndarray::ArrayBase<\n        ndarray::OwnedRepr<f32>,\n        ndarray::Dim<ndarray::IxDynImpl>,\n    >|\n     -> Vec<f32> {\n        let sl = arr.as_slice().unwrap();\n        let mut out = Vec::with_capacity(77 * 4096);\n        for token in 0..77 {\n            let base = token * 2048;\n            out.extend_from_slice(&sl[base..base + 2048]);\n            out.extend(std::iter::repeat_n(0.0f32, 2048));\n        }\n        out\n    };\n    let cond_clip_padded = pad_clip(&cond_clip);\n    let uncond_clip_padded = pad_clip(&uncond_clip);\n\n    // Run T5 if available: (1, 256) → (1, 256, 4096)\n    let (cond_t5, uncond_t5) = if let Some(te3) = &text_encoder_3 {\n        eprintln!(\"Running T5 encoder...\");\n        let c = te3.run([t5_input_ids.unwrap()])?;\n        let u = te3.run([t5_uncond_ids.unwrap()])?;\n        let c_sl = c[0].as_slice::<f32>()?.to_vec();\n        let u_sl = u[0].as_slice::<f32>()?.to_vec();\n        (Some(c_sl), Some(u_sl))\n    } else {\n        (None, None)\n    };\n\n    // Build combined embeddings:\n    //   Without T5: (seq=77, dim=4096)\n    //   With T5:    (seq=77+256=333, dim=4096)\n    let seq_len = if has_t5 { 77 + 256 } else { 77 };\n    let b2 = 2 * num_images;\n    let emb_size = seq_len * 4096;\n\n    let build_emb = |clip_padded: &[f32], t5_data: &Option<Vec<f32>>| -> Vec<f32> {\n        let mut out = Vec::with_capacity(emb_size);\n        out.extend_from_slice(clip_padded);\n        if let Some(t5) = t5_data {\n            out.extend_from_slice(t5);\n        }\n        out\n    };\n    let cond_emb = build_emb(&cond_clip_padded, &cond_t5);\n    let uncond_emb = build_emb(&uncond_clip_padded, &uncond_t5);\n\n    let mut emb_data = Vec::with_capacity(b2 * emb_size);\n    for _ in 0..num_images {\n        emb_data.extend_from_slice(&uncond_emb);\n    }\n    for _ in 0..num_images {\n        emb_data.extend_from_slice(&cond_emb);\n    }\n    let text_emb = tract_ndarray::ArrayD::from_shape_vec(vec![b2, seq_len, 4096], emb_data)?;\n    eprintln!(\"  Text embeddings: {:?}\", text_emb.shape());\n\n    // Pooled: cat CLIP-L pooled (768) + CLIP-G pooled (1280) → (1, 2048)\n    let cond_p1 = cond1[0].as_slice::<f32>()?;\n    let cond_p2 = cond2[0].as_slice::<f32>()?;\n    let uncond_p1 = uncond1[0].as_slice::<f32>()?;\n    let uncond_p2 = uncond2[0].as_slice::<f32>()?;\n    let pooled_dim = cond_p1.len() + cond_p2.len(); // 768 + 1280 = 2048\n    let mut pooled_data = Vec::with_capacity(b2 * pooled_dim);\n    for _ in 0..num_images {\n        pooled_data.extend_from_slice(uncond_p1);\n        pooled_data.extend_from_slice(uncond_p2);\n    }\n    for _ in 0..num_images {\n        pooled_data.extend_from_slice(cond_p1);\n        pooled_data.extend_from_slice(cond_p2);\n    }\n    let pooled = tract_ndarray::ArrayD::from_shape_vec(vec![b2, pooled_dim], pooled_data)?;\n\n    // --- Generate latent noise (16 channels for SD3) ---\n    let latent_size = 16 * 128 * 128;\n    let mut latents: Vec<f32> = (0..num_images * latent_size)\n        .map(|_| {\n            <StandardNormal as Distribution<f32>>::sample(&StandardNormal, &mut rng) * init_sigma\n        })\n        .collect();\n\n    // --- Batched denoising ---\n    use kdam::BarExt as _;\n    let mut pb = kdam::Bar::builder()\n        .total(num_steps)\n        .desc(format!(\"Denoising {num_images} image(s)\"))\n        .build()\n        .unwrap();\n    for (i, &t) in scheduler.timesteps.iter().enumerate() {\n        // Build input: [uncond latents, cond latents] — no scaling needed for flow matching\n        let mut sample_data = Vec::with_capacity(b2 * latent_size);\n        for &x in &latents {\n            sample_data.push(x);\n        }\n        for &x in &latents {\n            sample_data.push(x);\n        }\n        let sample = tract_ndarray::ArrayD::from_shape_vec(vec![b2, 16, 128, 128], sample_data)?;\n        let timestep = tract_ndarray::Array1::from_vec(vec![t; b2]).into_dyn();\n\n        // MMDiT: 4 inputs (hidden_states, encoder_hidden_states, pooled_projections, timestep)\n        let noise_pred = transformer.run(vec![\n            tensor(sample)?,\n            tensor(text_emb.clone())?,\n            tensor(pooled.clone())?,\n            tensor(timestep)?,\n        ])?;\n        let pred = noise_pred[0].as_slice::<f32>()?;\n\n        // Classifier-free guidance\n        let batch_latent_size = num_images * latent_size;\n        let dt = scheduler.dt(i);\n        for j in 0..batch_latent_size {\n            let u = pred[j];\n            let c = pred[batch_latent_size + j];\n            let eps = u + guidance_scale * (c - u);\n            latents[j] += eps * dt;\n        }\n\n        let sigma = scheduler.sigmas[i];\n        pb.set_postfix(format!(\"t={t:.0} σ={sigma:.3}\"));\n        pb.update(1).ok();\n    }\n    eprintln!();\n\n    // --- VAE decode + save ---\n    let (h, w) = (1024usize, 1024usize);\n    for n in 0..num_images {\n        let img_latent: Vec<f32> = latents[n * latent_size..(n + 1) * latent_size]\n            .iter()\n            .map(|&x| x / VAE_SCALING_FACTOR + VAE_SHIFT_FACTOR)\n            .collect();\n        let latent_arr = tract_ndarray::ArrayD::from_shape_vec(vec![1, 16, 128, 128], img_latent)?;\n        let image_result = vae_decoder.run([latent_arr])?;\n        let image_data = image_result[0].as_slice::<f32>()?;\n\n        let mut pixels = vec![0u8; h * w * 3];\n        for y in 0..h {\n            for x in 0..w {\n                for ch in 0..3 {\n                    let val =\n                        (image_data[ch * h * w + y * w + x].clamp(-1.0, 1.0) + 1.0) / 2.0 * 255.0;\n                    pixels[(y * w + x) * 3 + ch] = val as u8;\n                }\n            }\n        }\n        let path = if num_images == 1 {\n            std::path::PathBuf::from(&args.output)\n        } else {\n            let stem = std::path::Path::new(&args.output)\n                .file_stem()\n                .unwrap_or_default()\n                .to_str()\n                .unwrap_or(\"output\");\n            let ext = std::path::Path::new(&args.output)\n                .extension()\n                .unwrap_or_default()\n                .to_str()\n                .unwrap_or(\"png\");\n            std::path::PathBuf::from(format!(\"{stem}_{n}.{ext}\"))\n        };\n        image::save_buffer(&path, &pixels, w as u32, h as u32, image::ColorType::Rgb8)?;\n        eprintln!(\"Saved {}\", path.display());\n        display_inline(&path);\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "examples/stable-diffusion-xl/Cargo.toml",
    "content": "[package]\nname = \"stable-diffusion-xl\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow.workspace = true\nclap = { workspace = true, features = [\"derive\"] }\nimage = \"0.25\"\nkdam = \"0.6\"\nndarray.workspace = true\nrand = \"0.9\"\nrand_distr = \"0.5\"\ntokenizers = \"0.22\"\ntract.workspace = true\n"
  },
  {
    "path": "examples/stable-diffusion-xl/ci-gpu.sh",
    "content": "#!/bin/bash\n\nset -ex\n\nSCRIPT_DIR=$(cd \"$(dirname \"$0\")\" && pwd)\ncd \"$SCRIPT_DIR\"\n\n# Create venv\nif [ ! -e .venv ]; then\n    if python3 -m venv .venv 2>/dev/null; then\n        true\n    else\n        ../../api/py/.venv/bin/virtualenv .venv\n    fi\nfi\nsource .venv/bin/activate\n\npip install -q torch diffusers transformers accelerate onnxscript onnx Pillow\n\n# Export models + reference I/O\nmkdir -p assets\npython export.py\npython reference.py\n\n# Validate each model against Python reference\n# tract-cli is pre-built by CI, fall back to building locally\nTRACT=../../target/opt-no-lto/tract\nif [ ! -x \"$TRACT\" ]; then\n    cargo build --profile opt-no-lto -p tract-cli\nfi\n\nif nvidia-smi > /dev/null 2>&1; then\n    RUNTIME=\"--cuda\"\n    GPU_ASSERT=\"--assert-op-only Cuda*,Gpu*,DeviceSync*,Const,Source,IsNan,Gather*,Reduce*\"\nelif [ \"$(uname)\" = \"Darwin\" ] && system_profiler SPDisplaysDataType 2>/dev/null | grep -qi metal; then\n    RUNTIME=\"--metal\"\n    GPU_ASSERT=\"--assert-op-only Metal*,Gpu*,DeviceSync*,Const,Source,IsNan,Gather*,Reduce*\"\nelse\n    RUNTIME=\"-O\"\n    GPU_ASSERT=\"\"\nfi\n\necho \"Validating text encoder 1 ($RUNTIME)...\"\n$TRACT assets/text_encoder.onnx $RUNTIME run \\\n    --input-from-bundle assets/text_encoder.io.npz \\\n    --assert-output-bundle assets/text_encoder.io.npz --approx very $GPU_ASSERT\n\necho \"Validating text encoder 2 ($RUNTIME)...\"\n$TRACT assets/text_encoder_2.onnx $RUNTIME run \\\n    --input-from-bundle assets/text_encoder_2.io.npz \\\n    --assert-output-bundle assets/text_encoder_2.io.npz --approx very $GPU_ASSERT\n\n# Validate UNet — needs >=16GB VRAM for f32, skip on smaller GPUs\nif nvidia-smi > /dev/null 2>&1; then\n    GPU_MEM_MB=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -1 | tr -d ' ')\n    if [ \"$GPU_MEM_MB\" -ge 16000 ] 2>/dev/null; then\n        echo \"Validating UNet f32 ($RUNTIME)...\"\n        $TRACT assets/unet.onnx $RUNTIME run \\\n            --input-from-bundle assets/unet.io.npz \\\n            --assert-output-bundle assets/unet.io.npz --approx very $GPU_ASSERT\n    else\n        echo \"Skipping UNet validation (GPU has ${GPU_MEM_MB}MB, need >=16000MB for f32)\"\n    fi\nelse\n    echo \"Validating UNet f32 (CPU)...\"\n    $TRACT assets/unet.onnx -O run \\\n        --input-from-bundle assets/unet.io.npz \\\n        --assert-output-bundle assets/unet.io.npz --approx very\nfi\n\necho \"Validating VAE decoder ($RUNTIME)...\"\n$TRACT assets/vae_decoder.onnx $RUNTIME run \\\n    --input-from-bundle assets/vae_decoder.io.npz \\\n    --assert-output-bundle assets/vae_decoder.io.npz --approx very $GPU_ASSERT\n\n# Run the Rust example\ncargo run -p stable-diffusion-xl --profile opt-no-lto -- \\\n    -p \"a photo of a cat\" -s 10 --seed 42 \\\n    -o assets/test_output.png \\\n    --assets assets\n\ntest -f assets/test_output.png\necho \"CI passed: test_output.png generated\"\n\nrm -rf assets .venv\n"
  },
  {
    "path": "examples/stable-diffusion-xl/export.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Export Stable Diffusion XL 1.0 components to ONNX.\"\"\"\n\nimport os\nimport torch\nfrom pathlib import Path\nfrom transformers import CLIPTokenizerFast\n\nASSETS = Path(\"assets\")\nMODEL_ID = \"stabilityai/stable-diffusion-xl-base-1.0\"\n\n\ndef main():\n    from diffusers import StableDiffusionXLPipeline\n\n    print(f\"Loading {MODEL_ID}...\")\n    pipe = StableDiffusionXLPipeline.from_pretrained(MODEL_ID, torch_dtype=torch.float32, variant=\"fp16\")\n    pipe = pipe.to(\"cpu\")\n\n    ASSETS.mkdir(parents=True, exist_ok=True)\n\n    # --- Text Encoder 1 (CLIP ViT-L/14, hidden_size=768) ---\n    print(\"Exporting text_encoder...\")\n    text_encoder = pipe.text_encoder\n    text_encoder.eval()\n    input_ids = torch.zeros(1, 77, dtype=torch.int64)\n    with torch.no_grad():\n        torch.onnx.export(\n            text_encoder,\n            (input_ids,),\n            str(ASSETS / \"text_encoder.onnx\"),\n            input_names=[\"input_ids\"],\n            output_names=[\"last_hidden_state\", \"pooler_output\"],\n            opset_version=17,\n        )\n    print(f\"  Exported to {ASSETS / 'text_encoder.onnx'}\")\n\n    # --- Text Encoder 2 (OpenCLIP ViT-bigG, hidden_size=1280) ---\n    print(\"Exporting text_encoder_2...\")\n    text_encoder_2 = pipe.text_encoder_2\n    text_encoder_2.eval()\n    with torch.no_grad():\n        torch.onnx.export(\n            text_encoder_2,\n            (input_ids,),\n            str(ASSETS / \"text_encoder_2.onnx\"),\n            input_names=[\"input_ids\"],\n            output_names=[\"text_embeds\", \"last_hidden_state\"],\n            opset_version=17,\n        )\n    print(f\"  Exported to {ASSETS / 'text_encoder_2.onnx'}\")\n\n    # --- VAE Decoder ---\n    print(\"Exporting vae_decoder...\")\n    vae = pipe.vae\n    vae.eval()\n\n    class VaeDecoder(torch.nn.Module):\n        def __init__(self, vae):\n            super().__init__()\n            self.decoder = vae.decoder\n            self.post_quant_conv = vae.post_quant_conv\n\n        def forward(self, latent):\n            latent = self.post_quant_conv(latent)\n            return self.decoder(latent)\n\n    vae_decoder = VaeDecoder(vae)\n    latent = torch.randn(1, 4, 128, 128)\n    with torch.no_grad():\n        torch.onnx.export(\n            vae_decoder,\n            (latent,),\n            str(ASSETS / \"vae_decoder.onnx\"),\n            input_names=[\"latent\"],\n            output_names=[\"image\"],\n            opset_version=17,\n        )\n    print(f\"  Exported to {ASSETS / 'vae_decoder.onnx'}\")\n\n    # --- UNet (with added_cond_kwargs) ---\n    print(\"Exporting unet...\")\n    unet = pipe.unet\n    unet.eval()\n    sample = torch.randn(2, 4, 128, 128)\n    timestep = torch.tensor([999, 999], dtype=torch.int64)\n    encoder_hidden_states = torch.randn(2, 77, 2048)\n    # SDXL added conditions\n    time_ids = torch.zeros(2, 6)  # [orig_h, orig_w, crop_top, crop_left, target_h, target_w]\n    text_embeds = torch.randn(2, 1280)  # pooled from text_encoder_2\n\n    class UNetWrapper(torch.nn.Module):\n        def __init__(self, unet):\n            super().__init__()\n            self.unet = unet\n\n        def forward(self, sample, timestep, encoder_hidden_states, time_ids, text_embeds):\n            return self.unet(\n                sample,\n                timestep,\n                encoder_hidden_states=encoder_hidden_states,\n                added_cond_kwargs={\"time_ids\": time_ids, \"text_embeds\": text_embeds},\n            ).sample\n\n    wrapper = UNetWrapper(unet)\n    with torch.no_grad():\n        torch.onnx.export(\n            wrapper,\n            (sample, timestep, encoder_hidden_states, time_ids, text_embeds),\n            str(ASSETS / \"unet.onnx\"),\n            input_names=[\"sample\", \"timestep\", \"encoder_hidden_states\", \"time_ids\", \"text_embeds\"],\n            output_names=[\"noise_pred\"],\n            opset_version=17,\n            dynamic_axes={\n                \"sample\": {0: \"batch\"},\n                \"timestep\": {0: \"batch\"},\n                \"encoder_hidden_states\": {0: \"batch\"},\n                \"time_ids\": {0: \"batch\"},\n                \"text_embeds\": {0: \"batch\"},\n                \"noise_pred\": {0: \"batch\"},\n            },\n        )\n    print(f\"  Exported to {ASSETS / 'unet.onnx'}\")\n\n    # Save fast tokenizer (same for both text encoders in SDXL)\n    print(\"Saving tokenizer...\")\n    tok = CLIPTokenizerFast.from_pretrained(MODEL_ID, subfolder=\"tokenizer\")\n    tok.save_pretrained(str(ASSETS / \"tokenizer\"))\n\n    print(\"Export complete.\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/stable-diffusion-xl/reference.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Generate reference I/O data for SDXL model validation.\"\"\"\n\nimport torch\nimport numpy as np\nfrom pathlib import Path\nfrom diffusers import StableDiffusionXLPipeline, EulerDiscreteScheduler\n\nASSETS = Path(\"assets\")\nMODEL_ID = \"stabilityai/stable-diffusion-xl-base-1.0\"\nPROMPT = \"a photo of a cat\"\nSEED = 42\nNUM_STEPS = 20\n\ndef main():\n    print(f\"Loading {MODEL_ID}...\")\n    pipe = StableDiffusionXLPipeline.from_pretrained(MODEL_ID, torch_dtype=torch.float32, variant=\"fp16\")\n    pipe.scheduler = EulerDiscreteScheduler.from_config(pipe.scheduler.config)\n    pipe = pipe.to(\"cpu\")\n\n    tokenizer = pipe.tokenizer\n    tokenizer_2 = pipe.tokenizer_2\n\n    # Tokenize\n    tokens = tokenizer(PROMPT, padding=\"max_length\", max_length=77, truncation=True, return_tensors=\"pt\")\n    uncond_tokens = tokenizer(\"\", padding=\"max_length\", max_length=77, truncation=True, return_tensors=\"pt\")\n    tokens_2 = tokenizer_2(PROMPT, padding=\"max_length\", max_length=77, truncation=True, return_tensors=\"pt\")\n    uncond_tokens_2 = tokenizer_2(\"\", padding=\"max_length\", max_length=77, truncation=True, return_tensors=\"pt\")\n\n    input_ids = tokens.input_ids\n    uncond_input_ids = uncond_tokens.input_ids\n    input_ids_2 = tokens_2.input_ids\n    uncond_input_ids_2 = uncond_tokens_2.input_ids\n\n    print(f\"Token IDs: {input_ids.shape}, Token IDs 2: {input_ids_2.shape}\")\n\n    # --- Text Encoder 1 ---\n    with torch.no_grad():\n        te1_cond = pipe.text_encoder(input_ids)\n        te1_uncond = pipe.text_encoder(uncond_input_ids)\n\n    np.savez(str(ASSETS / \"text_encoder.io.npz\"),\n        input_ids=input_ids.numpy(),\n        last_hidden_state=te1_cond.last_hidden_state.numpy(),\n        pooler_output=te1_cond.pooler_output.numpy(),\n    )\n    print(\"Saved text_encoder.io.npz\")\n\n    # --- Text Encoder 2 ---\n    with torch.no_grad():\n        te2_cond = pipe.text_encoder_2(input_ids_2)\n        te2_uncond = pipe.text_encoder_2(uncond_input_ids_2)\n\n    np.savez(str(ASSETS / \"text_encoder_2.io.npz\"),\n        input_ids=input_ids_2.numpy(),\n        last_hidden_state=te2_cond.last_hidden_state.numpy(),\n        text_embeds=te2_cond.text_embeds.numpy(),\n    )\n    print(\"Saved text_encoder_2.io.npz\")\n\n    # --- Concat text embeddings ---\n    cond_hidden = torch.cat([te1_cond.last_hidden_state, te2_cond.last_hidden_state], dim=-1)  # (1,77,2048)\n    uncond_hidden = torch.cat([te1_uncond.last_hidden_state, te2_uncond.last_hidden_state], dim=-1)\n    text_embeddings = cond_hidden  # for single-image reference\n    print(f\"Text embeddings: {text_embeddings.shape}\")\n\n    # Pooled embeddings from text_encoder_2\n    cond_pooled = te2_cond.text_embeds  # (1, 1280)\n    uncond_pooled = te2_uncond.text_embeds\n\n    # time_ids: [original_h, original_w, crop_top, crop_left, target_h, target_w]\n    time_ids = torch.tensor([[1024., 1024., 0., 0., 1024., 1024.]])\n\n    # --- UNet (one step) ---\n    pipe.scheduler.set_timesteps(NUM_STEPS)\n    ts = pipe.scheduler.timesteps\n    generator = torch.Generator().manual_seed(SEED)\n    latent = torch.randn(1, 4, 128, 128, generator=generator) * pipe.scheduler.init_noise_sigma\n    t0 = ts[0]\n    lat_in = pipe.scheduler.scale_model_input(latent, t0)\n\n    with torch.no_grad():\n        unet_out = pipe.unet(\n            lat_in, t0,\n            encoder_hidden_states=text_embeddings,\n            added_cond_kwargs={\"text_embeds\": cond_pooled, \"time_ids\": time_ids},\n        ).sample\n\n    np.savez(str(ASSETS / \"unet.io.npz\"),\n        sample=lat_in.numpy(),\n        timestep=np.array([t0.item()], dtype=np.int64),\n        encoder_hidden_states=text_embeddings.numpy(),\n        time_ids=time_ids.numpy(),\n        text_embeds=cond_pooled.numpy(),\n        noise_pred=unet_out.numpy(),\n    )\n    print(\"Saved unet.io.npz\")\n\n    # --- Full denoising loop ---\n    print(f\"Running full pipeline ({NUM_STEPS} steps)...\")\n    latent = torch.randn(1, 4, 128, 128, generator=torch.Generator().manual_seed(SEED)) * pipe.scheduler.init_noise_sigma\n    with torch.no_grad():\n        for i, t in enumerate(ts):\n            latent_model_input = torch.cat([latent] * 2)\n            latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)\n            noise_pred = pipe.unet(\n                latent_model_input, t,\n                encoder_hidden_states=torch.cat([uncond_hidden, cond_hidden]),\n                added_cond_kwargs={\n                    \"text_embeds\": torch.cat([uncond_pooled, cond_pooled]),\n                    \"time_ids\": torch.cat([time_ids, time_ids]),\n                },\n            ).sample\n            noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n            noise_pred = noise_pred_uncond + 7.5 * (noise_pred_text - noise_pred_uncond)\n            latent = pipe.scheduler.step(noise_pred, t, latent).prev_sample\n            if i % 5 == 0:\n                print(f\"  Step {i}/{NUM_STEPS}, t={t.item()}\")\n\n    # --- VAE decode ---\n    with torch.no_grad():\n        latent_scaled = latent / pipe.vae.config.scaling_factor\n        image = pipe.vae.decode(latent_scaled).sample\n\n    np.savez(str(ASSETS / \"vae_decoder.io.npz\"),\n        latent=latent_scaled.numpy(),\n        image=image.numpy(),\n    )\n    print(\"Saved vae_decoder.io.npz\")\n\n    # Save reference image\n    image_np = ((image[0].permute(1, 2, 0).clamp(-1, 1) + 1) / 2 * 255).byte().numpy()\n    from PIL import Image\n    Image.fromarray(image_np).save(str(ASSETS / \"reference.png\"))\n    print(f\"Saved reference image to {ASSETS / 'reference.png'}\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/stable-diffusion-xl/src/main.rs",
    "content": "use anyhow::*;\nuse tract::prelude::*;\n\n/// Euler discrete scheduler — same noise schedule as SD 1.5 / SDXL.\nstruct EulerScheduler {\n    timesteps: Vec<i64>,\n    sigmas: Vec<f32>,\n}\n\nimpl EulerScheduler {\n    fn new(num_inference_steps: usize) -> Self {\n        use tract_ndarray::{Array1, Axis, concatenate};\n        let num_train = 1000;\n        let betas = Array1::linspace(0.00085f64.sqrt(), 0.012f64.sqrt(), num_train).mapv(|b| b * b);\n        let alphas = betas.mapv(|b| 1.0 - b);\n        let mut alphas_cumprod = alphas.clone();\n        for i in 1..num_train {\n            alphas_cumprod[i] *= alphas_cumprod[i - 1];\n        }\n        let all_sigmas = alphas_cumprod.mapv(|a| ((1.0 - a) / a).sqrt());\n        let timesteps = Array1::linspace((num_train - 1) as f64, 0.0, num_inference_steps)\n            .mapv(|t| t.round() as i64);\n        let sigmas_at_t = timesteps.mapv(|t| {\n            let t = t as f64;\n            let lo = t.floor() as usize;\n            let hi = (lo + 1).min(num_train - 1);\n            let frac = t - lo as f64;\n            (all_sigmas[lo] * (1.0 - frac) + all_sigmas[hi] * frac) as f32\n        });\n        let sigmas =\n            concatenate(Axis(0), &[sigmas_at_t.view(), Array1::from_vec(vec![0.0f32]).view()])\n                .unwrap();\n        EulerScheduler { timesteps: timesteps.to_vec(), sigmas: sigmas.to_vec() }\n    }\n\n    fn init_noise_sigma(&self) -> f32 {\n        self.sigmas[0]\n    }\n\n    fn scale_factor(&self, step: usize) -> f32 {\n        let sigma = self.sigmas[step];\n        1.0 / (sigma * sigma + 1.0).sqrt()\n    }\n\n    fn dt(&self, step: usize) -> f32 {\n        self.sigmas[step + 1] - self.sigmas[step]\n    }\n}\n\nconst VAE_SCALING_FACTOR: f32 = 0.13025; // SDXL\n\nfn base64_encode(data: &[u8]) -> String {\n    const CHARS: &[u8] = b\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n    let mut out = String::with_capacity((data.len() + 2) / 3 * 4);\n    for chunk in data.chunks(3) {\n        let b = match chunk.len() {\n            3 => [chunk[0], chunk[1], chunk[2]],\n            2 => [chunk[0], chunk[1], 0],\n            _ => [chunk[0], 0, 0],\n        };\n        let n = (b[0] as u32) << 16 | (b[1] as u32) << 8 | b[2] as u32;\n        out.push(CHARS[(n >> 18 & 63) as usize] as char);\n        out.push(CHARS[(n >> 12 & 63) as usize] as char);\n        out.push(if chunk.len() > 1 { CHARS[(n >> 6 & 63) as usize] as char } else { '=' });\n        out.push(if chunk.len() > 2 { CHARS[(n & 63) as usize] as char } else { '=' });\n    }\n    out\n}\n\nfn display_inline(path: &std::path::Path) {\n    use std::io::Write as _;\n    // Re-encode as a small PNG thumbnail to keep the escape sequence manageable.\n    let img = match image::open(path) {\n        std::result::Result::Ok(img) => img,\n        _ => return,\n    };\n    let thumb = img.thumbnail(256, 256);\n    let mut png_data = Vec::new();\n    let encoder = image::codecs::png::PngEncoder::new(std::io::Cursor::new(&mut png_data));\n    if thumb.write_with_encoder(encoder).is_err() {\n        return;\n    }\n    let b64 = base64_encode(&png_data);\n    let in_tmux = std::env::var(\"TMUX\").is_ok();\n    let osc = if in_tmux { \"\\x1bPtmux;\\x1b\\x1b]\" } else { \"\\x1b]\" };\n    let st = if in_tmux { \"\\x07\\x1b\\\\\" } else { \"\\x07\" };\n    let _ = write!(\n        std::io::stderr(),\n        \"{osc}1337;File=inline=1;width=20;preserveAspectRatio=1:{b64}{st}\\n\"\n    );\n}\n\n/// Stable Diffusion XL 1.0 image generation with tract\n#[derive(clap::Parser)]\nstruct Args {\n    /// Text prompt\n    #[arg(short, long, default_value = \"a photo of a cat\")]\n    prompt: String,\n\n    /// Number of images\n    #[arg(short, long, default_value_t = 1)]\n    num_images: usize,\n\n    /// Denoising steps\n    #[arg(short, long, default_value_t = 20)]\n    steps: usize,\n\n    /// Random seed\n    #[arg(long, default_value_t = 42)]\n    seed: u64,\n\n    /// Classifier-free guidance scale\n    #[arg(short, long, default_value_t = 7.5)]\n    guidance_scale: f32,\n\n    /// Output filename\n    #[arg(short, long, default_value = \"output.png\")]\n    output: String,\n\n    /// Model assets directory\n    #[arg(long, default_value = \"assets\")]\n    assets: std::path::PathBuf,\n}\n\nfn main() -> Result<()> {\n    use clap::Parser as _;\n    let args = Args::parse();\n    let num_images = args.num_images;\n    let num_steps = args.steps;\n    let guidance_scale = args.guidance_scale;\n    let assets = &args.assets;\n\n    eprintln!(\n        \"SDXL: \\\"{}\\\", images: {num_images}, steps: {num_steps}, seed: {}, guidance: {guidance_scale}\",\n        args.prompt, args.seed\n    );\n\n    // --- Tokenize (same tokenizer for both text encoders) ---\n    let tokenizer = tokenizers::Tokenizer::from_file(assets.join(\"tokenizer/tokenizer.json\"))\n        .map_err(|e| anyhow!(\"{e}\"))?;\n    let encode = |text: &str| -> Result<ndarray::Array2<i64>> {\n        let enc = tokenizer.encode(text, true).map_err(|e| anyhow!(\"{e}\"))?;\n        let mut ids: Vec<i64> = enc.get_ids().iter().map(|&id| id as i64).collect();\n        ids.resize(77, tokenizer.token_to_id(\"<|endoftext|>\").unwrap_or(49407) as i64);\n        Ok(ndarray::Array2::from_shape_vec((1, 77), ids)?)\n    };\n    let input_ids = encode(&args.prompt)?;\n    let uncond_input_ids = encode(\"\")?;\n\n    use rand::SeedableRng;\n    use rand_distr::{Distribution, StandardNormal};\n    let mut rng = rand::rngs::StdRng::seed_from_u64(args.seed);\n\n    let scheduler = EulerScheduler::new(num_steps);\n    let init_noise_sigma = scheduler.init_noise_sigma();\n    eprintln!(\"  Scheduler: {num_steps} steps, init_sigma={init_noise_sigma:.4}\");\n\n    // --- Pick runtime ---\n    let gpu = [\"cuda\", \"metal\", \"default\"]\n        .iter()\n        .find_map(|rt| tract::runtime_for_name(rt).ok())\n        .unwrap();\n    eprintln!(\"Using runtime: {gpu:?}\");\n\n    // --- Load models ---\n    eprintln!(\"Loading models...\");\n    let onnx = tract::onnx()?;\n    let text_encoder = gpu.prepare(onnx.load(assets.join(\"text_encoder.onnx\"))?.into_model()?)?;\n    let text_encoder_2 =\n        gpu.prepare(onnx.load(assets.join(\"text_encoder_2.onnx\"))?.into_model()?)?;\n    let unet = gpu.prepare(onnx.load(assets.join(\"unet.onnx\"))?.into_model()?)?;\n    let vae_decoder = gpu.prepare(onnx.load(assets.join(\"vae_decoder.onnx\"))?.into_model()?)?;\n\n    // --- Text encoding (two encoders, concatenated) ---\n    eprintln!(\"Running text encoders...\");\n    let cond1 = text_encoder.run([input_ids.clone()])?;\n    let uncond1 = text_encoder.run([uncond_input_ids.clone()])?;\n    let cond2 = text_encoder_2.run([input_ids])?;\n    let uncond2 = text_encoder_2.run([uncond_input_ids])?;\n\n    // Concatenate hidden states: (1,77,768) + (1,77,1280) → (1,77,2048)\n    // TE1: output[0] = last_hidden_state (1,77,768), output[1] = pooler (1,768)\n    // TE2: output[0] = text_embeds/pooled (1,1280), output[1] = last_hidden_state (1,77,1280)\n    let cond_h1 = cond1[0].view::<f32>()?;\n    let cond_h2 = cond2[1].view::<f32>()?;\n    let uncond_h1 = uncond1[0].view::<f32>()?;\n    let uncond_h2 = uncond2[1].view::<f32>()?;\n    let cond_cat =\n        tract_ndarray::concatenate(tract_ndarray::Axis(2), &[cond_h1.view(), cond_h2.view()])?\n            .as_standard_layout()\n            .into_owned();\n    let uncond_cat =\n        tract_ndarray::concatenate(tract_ndarray::Axis(2), &[uncond_h1.view(), uncond_h2.view()])?\n            .as_standard_layout()\n            .into_owned();\n\n    // Build batched: [uncond×N, cond×N] → (2N, 77, 2048)\n    let b2 = 2 * num_images;\n    let emb_dim = 2048;\n    let uncond_sl = uncond_cat.as_slice().unwrap();\n    let cond_sl = cond_cat.as_slice().unwrap();\n    let emb_size = 77 * emb_dim;\n    let mut emb_data = Vec::with_capacity(b2 * emb_size);\n    for _ in 0..num_images {\n        emb_data.extend_from_slice(uncond_sl);\n    }\n    for _ in 0..num_images {\n        emb_data.extend_from_slice(cond_sl);\n    }\n    let text_emb = tract_ndarray::ArrayD::from_shape_vec(vec![b2, 77, emb_dim], emb_data)?;\n    eprintln!(\"  Text embeddings: {:?}\", text_emb.shape());\n\n    // Pooled text embeddings from text_encoder_2 (output 0)\n    let cond_pooled = cond2[0].as_slice::<f32>()?;\n    let uncond_pooled = uncond2[0].as_slice::<f32>()?;\n    let pooled_dim = cond_pooled.len();\n    let mut pooled_data = Vec::with_capacity(b2 * pooled_dim);\n    for _ in 0..num_images {\n        pooled_data.extend_from_slice(uncond_pooled);\n    }\n    for _ in 0..num_images {\n        pooled_data.extend_from_slice(cond_pooled);\n    }\n    let pooled = tract_ndarray::ArrayD::from_shape_vec(vec![b2, pooled_dim], pooled_data)?;\n\n    // time_ids: [original_h, original_w, crop_top, crop_left, target_h, target_w]\n    let time_ids_single: Vec<f32> = vec![1024.0, 1024.0, 0.0, 0.0, 1024.0, 1024.0];\n    let mut time_ids_data = Vec::with_capacity(b2 * 6);\n    for _ in 0..b2 {\n        time_ids_data.extend_from_slice(&time_ids_single);\n    }\n    let time_ids = tract_ndarray::ArrayD::from_shape_vec(vec![b2, 6], time_ids_data)?;\n\n    // --- Generate latent noise ---\n    let latent_size = 4 * 128 * 128;\n    let mut latents: Vec<f32> = (0..num_images * latent_size)\n        .map(|_| {\n            <StandardNormal as Distribution<f32>>::sample(&StandardNormal, &mut rng)\n                * init_noise_sigma\n        })\n        .collect();\n\n    // --- Batched denoising ---\n    use kdam::BarExt as _;\n    let mut pb = kdam::Bar::builder()\n        .total(num_steps)\n        .desc(format!(\"Denoising {num_images} image(s)\"))\n        .build()\n        .unwrap();\n    for (i, &t) in scheduler.timesteps.iter().enumerate() {\n        let scale = scheduler.scale_factor(i);\n\n        let mut sample_data = Vec::with_capacity(b2 * latent_size);\n        for &x in &latents {\n            sample_data.push(x * scale);\n        }\n        for &x in &latents {\n            sample_data.push(x * scale);\n        }\n        let sample = tract_ndarray::ArrayD::from_shape_vec(vec![b2, 4, 128, 128], sample_data)?;\n        let timestep = tract_ndarray::Array1::from_vec(vec![t; b2]).into_dyn();\n\n        // SDXL UNet: 5 inputs (sample, timestep, encoder_hidden_states, time_ids, text_embeds)\n        let noise_pred = unet.run(vec![\n            tensor(sample)?,\n            tensor(timestep)?,\n            tensor(text_emb.clone())?,\n            tensor(time_ids.clone())?,\n            tensor(pooled.clone())?,\n        ])?;\n        let pred = noise_pred[0].as_slice::<f32>()?;\n\n        let batch_latent_size = num_images * latent_size;\n        let dt = scheduler.dt(i);\n        for j in 0..batch_latent_size {\n            let u = pred[j];\n            let c = pred[batch_latent_size + j];\n            let eps = u + guidance_scale * (c - u);\n            latents[j] += eps * dt;\n        }\n\n        let sigma = scheduler.sigmas[i];\n        pb.set_postfix(format!(\"t={t} σ={sigma:.2}\"));\n        pb.update(1).ok();\n    }\n    eprintln!();\n\n    // --- VAE decode + save ---\n    let (h, w) = (1024usize, 1024usize);\n    for n in 0..num_images {\n        let img_latent: Vec<f32> = latents[n * latent_size..(n + 1) * latent_size]\n            .iter()\n            .map(|&x| x / VAE_SCALING_FACTOR)\n            .collect();\n        let latent_arr = tract_ndarray::ArrayD::from_shape_vec(vec![1, 4, 128, 128], img_latent)?;\n        let image_result = vae_decoder.run([latent_arr])?;\n        let image_data = image_result[0].as_slice::<f32>()?;\n\n        let mut pixels = vec![0u8; h * w * 3];\n        for y in 0..h {\n            for x in 0..w {\n                for ch in 0..3 {\n                    let val =\n                        (image_data[ch * h * w + y * w + x].clamp(-1.0, 1.0) + 1.0) / 2.0 * 255.0;\n                    pixels[(y * w + x) * 3 + ch] = val as u8;\n                }\n            }\n        }\n        let path = if num_images == 1 {\n            std::path::PathBuf::from(&args.output)\n        } else {\n            let stem = std::path::Path::new(&args.output)\n                .file_stem()\n                .unwrap_or_default()\n                .to_str()\n                .unwrap_or(\"output\");\n            let ext = std::path::Path::new(&args.output)\n                .extension()\n                .unwrap_or_default()\n                .to_str()\n                .unwrap_or(\"png\");\n            std::path::PathBuf::from(format!(\"{stem}_{n}.{ext}\"))\n        };\n        image::save_buffer(&path, &pixels, w as u32, h as u32, image::ColorType::Rgb8)?;\n        eprintln!(\"Saved {}\", path.display());\n        display_inline(&path);\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "examples/tensorflow-mobilenet-v2/.gitignore",
    "content": "mobilenet_v2_1.4_224*\ntarget\n"
  },
  {
    "path": "examples/tensorflow-mobilenet-v2/Cargo.toml",
    "content": "[package]\nname = \"example-tensorflow-mobilenet-v2\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nimage.workspace = true\ntract-tensorflow.workspace = true\n"
  },
  {
    "path": "examples/tensorflow-mobilenet-v2/README.md",
    "content": "# Tract examples: Tensorflow MobileNet v2\n\nThis project is a simple project with minimal code showing how to use tract to\nprocess an image with MobileNetV2.\n\nThe example assume the following command are run in the directory of this\nexample project, where this README lives.\n\n```sh\ngit clone https://github.com/snipsco/tract\ncd tract/examples/tensorflow-mobilenet-v2/\n```\n\n## Obtaining the model \n\nMobileNet is a response to the ImageNet challenge. The goal is to categorize\nimages and associate them with one of 1000 labels. In other words, recognize a\ndog, a cat, a rabbit, or a military uniform.\n\nSee https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet for more information.\n\nYou will need to download the models. For instance:\n\n```sh\nwget https://storage.googleapis.com/mobilenet_v2/checkpoints/mobilenet_v2_1.4_224.tgz\ntar zxf mobilenet_v2_1.4_224.tgz\n```\n\nThis expands a half-dozen files in the directory. The only one of interest\nfor us is the frozen TensorFlow model: `mobilenet_v2_1.4_224_frozen.pb`.\n\n## Converting the input image\n\nAs in TensorFlow documentation, we will use a portrait of Grace Hopper\n(included with this example).\n\n```\ngrace_hopper.jpg: JPEG image data, JFIF standard 1.02, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 517x606, components 3\n```\n\n## Try it\n\n`cargo run` should print a lot of things, and ultimately: `result: Some((0.32560226, 654))`.\n\nThis is actually good. It is the rank (654) and a confidence indicator (0.32)\nof the inferred label.\n\n```\n$ cat -n imagenet_slim_labels.txt | grep -C 3 654\n   651  megalith\n   652  microphone\n   653  microwave\n   654  military uniform\n   655  milk can\n   656  minibus\n   657  miniskirt\n```\n\n## A look at the code\n\nEverything happens in [src/main.rs](src/main.rs).\n\n```rust\n    let model = tract_tensorflow::tensorflow()\n        // load the model\n        .model_for_path(\"mobilenet_v2_1.4_224_frozen.pb\")?\n        // specify input type and shape\n        .with_input_fact(0, f32::fact(&[1, 224, 224, 3]).into())?\n        // optimize the model\n        .into_optimized()?\n        // make the model runnable and fix its inputs and outputs\n        .into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let image: Tensor = tract_ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    })\n    .into();\n\n    // run the model on the input\n    let result = model.run(tvec!(image))?;\n\n    // find and display the max value with its index\n    let best = result[0]\n        .to_array_view::<f32>()?\n        .iter()\n        .cloned()\n        .zip(1..)\n        .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap());\n    println!(\"result: {:?}\", best);\n```\n\nIt uses three crates:\n\n* `tract-core` is the main tract crate. It contains tract operators, model\n    analysing and running infrastructure.\n* `tract-tensorflow` is the TensorFlow format parser. It translates most of\n    TensorFlow operators to core ones, or implements the one which are specific\n    to TensorFlow.\n* finally we use the `image` crate to load and resize the JPEG portrait.\n\n\n### Loading the model\n\nThis line creates a tract-tensorflow context, and uses it to load the protobuf\nmodel.\n\n```rust\n    let model = tract_tensorflow::tensorflow()\n        .model_for_path(\"mobilenet_v2_1.4_224_frozen.pb\")?\n    // ..\n```\n\n### Specifying input size and optimizing.\n\nTensorFlow models typically do not specify explicitely the input dimensions,\nbut a lot of optimization in `tract` depends on the knownledge of all tensors\ntypes and shapes in the network.\n\nMobileNet assumes its input in in the NHWC convention: [batch, height, width,\nchannels]. The MobileNet variant we have picked works with a 224x224 square\nRGB (C=3) pictures. We will only process one image at a time (N=1).\nAnd it operates on single precision floats (aka `f32`).\n\n```rust\n    // ..\n        .with_input_fact(0, f32::fact(&[1, 224, 224, 3]).into())?\n        .into_optimized()?\n        .into_runnable()?;\n```\n\nNow the model is ready to run, we have an execution plan, so let's prepare the\nimage.\n\n### Conditioning the input\n\nWe use the `image` crate to load the `.jpg` image, resize is to 224x224. Then\nwe build an 4-dimension array in the right NHWC shape, with `f32` obtained by\nnormalizing the `u8` input to the `0..1` range. This array is then converted\ninto a Tensor.\n\n```rust\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb();\n    let resized = image::imageops::resize(&image, 224, 224, ::image::FilterType::Triangle);\n    let image: Tensor = ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    }).into();\n```\n\nNote that `tract-core` re-export the excellent `ndarray` crate so that it is\neasy to get the right version for tract conversion to work.\n\n### Run the network!\n\n```rust\n    let result = model.run(tvec!(image))?;\n```\n\n### Interpret the result\n\nFinally we grab the single Tensor output by the plan execution, convert it to a\nndarray ArrayView of f32 values. It is a single dimension (a vector...) of 1001\ncategory scores (1000 labels plus the dummy one). We need pick the maximum\nscore, with its index, and display it...\n\n```rust\n    let best = result[0]\n        .to_array_view::<f32>()?\n        .iter()\n        .cloned()\n        .enumerate()\n        .zip(1..)\n        .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap());\n    println!(\"result: {:?}\", best);\n```\n"
  },
  {
    "path": "examples/tensorflow-mobilenet-v2/ci.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nwget -nc -q https://s3.amazonaws.com/tract-ci-builds/model/mobilenet_v2_1.4_224.tgz\ntar zxf mobilenet_v2_1.4_224.tgz\ncargo run\nrm -rf mobilenet*\n"
  },
  {
    "path": "examples/tensorflow-mobilenet-v2/imagenet_slim_labels.txt",
    "content": "dummy\ntench\ngoldfish\ngreat white shark\ntiger shark\nhammerhead\nelectric ray\nstingray\ncock\nhen\nostrich\nbrambling\ngoldfinch\nhouse finch\njunco\nindigo bunting\nrobin\nbulbul\njay\nmagpie\nchickadee\nwater ouzel\nkite\nbald eagle\nvulture\ngreat grey owl\nEuropean fire salamander\ncommon newt\neft\nspotted salamander\naxolotl\nbullfrog\ntree frog\ntailed frog\nloggerhead\nleatherback turtle\nmud turtle\nterrapin\nbox turtle\nbanded gecko\ncommon iguana\nAmerican chameleon\nwhiptail\nagama\nfrilled lizard\nalligator lizard\nGila monster\ngreen lizard\nAfrican chameleon\nKomodo dragon\nAfrican crocodile\nAmerican alligator\ntriceratops\nthunder snake\nringneck snake\nhognose snake\ngreen snake\nking snake\ngarter snake\nwater snake\nvine snake\nnight snake\nboa constrictor\nrock python\nIndian cobra\ngreen mamba\nsea snake\nhorned viper\ndiamondback\nsidewinder\ntrilobite\nharvestman\nscorpion\nblack and gold garden spider\nbarn spider\ngarden spider\nblack widow\ntarantula\nwolf spider\ntick\ncentipede\nblack grouse\nptarmigan\nruffed grouse\nprairie chicken\npeacock\nquail\npartridge\nAfrican grey\nmacaw\nsulphur-crested cockatoo\nlorikeet\ncoucal\nbee eater\nhornbill\nhummingbird\njacamar\ntoucan\ndrake\nred-breasted merganser\ngoose\nblack swan\ntusker\nechidna\nplatypus\nwallaby\nkoala\nwombat\njellyfish\nsea anemone\nbrain coral\nflatworm\nnematode\nconch\nsnail\nslug\nsea slug\nchiton\nchambered nautilus\nDungeness crab\nrock crab\nfiddler crab\nking crab\nAmerican lobster\nspiny lobster\ncrayfish\nhermit crab\nisopod\nwhite stork\nblack stork\nspoonbill\nflamingo\nlittle blue heron\nAmerican egret\nbittern\ncrane\nlimpkin\nEuropean gallinule\nAmerican coot\nbustard\nruddy turnstone\nred-backed sandpiper\nredshank\ndowitcher\noystercatcher\npelican\nking penguin\nalbatross\ngrey whale\nkiller whale\ndugong\nsea lion\nChihuahua\nJapanese spaniel\nMaltese dog\nPekinese\nShih-Tzu\nBlenheim spaniel\npapillon\ntoy terrier\nRhodesian ridgeback\nAfghan hound\nbasset\nbeagle\nbloodhound\nbluetick\nblack-and-tan coonhound\nWalker hound\nEnglish foxhound\nredbone\nborzoi\nIrish wolfhound\nItalian greyhound\nwhippet\nIbizan hound\nNorwegian elkhound\notterhound\nSaluki\nScottish deerhound\nWeimaraner\nStaffordshire bullterrier\nAmerican Staffordshire terrier\nBedlington terrier\nBorder terrier\nKerry blue terrier\nIrish terrier\nNorfolk terrier\nNorwich terrier\nYorkshire terrier\nwire-haired fox terrier\nLakeland terrier\nSealyham terrier\nAiredale\ncairn\nAustralian terrier\nDandie Dinmont\nBoston bull\nminiature schnauzer\ngiant schnauzer\nstandard schnauzer\nScotch terrier\nTibetan terrier\nsilky terrier\nsoft-coated wheaten terrier\nWest Highland white terrier\nLhasa\nflat-coated retriever\ncurly-coated retriever\ngolden retriever\nLabrador retriever\nChesapeake Bay retriever\nGerman short-haired pointer\nvizsla\nEnglish setter\nIrish setter\nGordon setter\nBrittany spaniel\nclumber\nEnglish springer\nWelsh springer spaniel\ncocker spaniel\nSussex spaniel\nIrish water spaniel\nkuvasz\nschipperke\ngroenendael\nmalinois\nbriard\nkelpie\nkomondor\nOld English sheepdog\nShetland sheepdog\ncollie\nBorder collie\nBouvier des Flandres\nRottweiler\nGerman shepherd\nDoberman\nminiature pinscher\nGreater Swiss Mountain dog\nBernese mountain dog\nAppenzeller\nEntleBucher\nboxer\nbull mastiff\nTibetan mastiff\nFrench bulldog\nGreat Dane\nSaint Bernard\nEskimo dog\nmalamute\nSiberian husky\ndalmatian\naffenpinscher\nbasenji\npug\nLeonberg\nNewfoundland\nGreat Pyrenees\nSamoyed\nPomeranian\nchow\nkeeshond\nBrabancon griffon\nPembroke\nCardigan\ntoy poodle\nminiature poodle\nstandard poodle\nMexican hairless\ntimber wolf\nwhite wolf\nred wolf\ncoyote\ndingo\ndhole\nAfrican hunting dog\nhyena\nred fox\nkit fox\nArctic fox\ngrey fox\ntabby\ntiger cat\nPersian cat\nSiamese cat\nEgyptian cat\ncougar\nlynx\nleopard\nsnow leopard\njaguar\nlion\ntiger\ncheetah\nbrown bear\nAmerican black bear\nice bear\nsloth bear\nmongoose\nmeerkat\ntiger beetle\nladybug\nground beetle\nlong-horned beetle\nleaf beetle\ndung beetle\nrhinoceros beetle\nweevil\nfly\nbee\nant\ngrasshopper\ncricket\nwalking stick\ncockroach\nmantis\ncicada\nleafhopper\nlacewing\ndragonfly\ndamselfly\nadmiral\nringlet\nmonarch\ncabbage butterfly\nsulphur butterfly\nlycaenid\nstarfish\nsea urchin\nsea cucumber\nwood rabbit\nhare\nAngora\nhamster\nporcupine\nfox squirrel\nmarmot\nbeaver\nguinea pig\nsorrel\nzebra\nhog\nwild boar\nwarthog\nhippopotamus\nox\nwater buffalo\nbison\nram\nbighorn\nibex\nhartebeest\nimpala\ngazelle\nArabian camel\nllama\nweasel\nmink\npolecat\nblack-footed ferret\notter\nskunk\nbadger\narmadillo\nthree-toed sloth\norangutan\ngorilla\nchimpanzee\ngibbon\nsiamang\nguenon\npatas\nbaboon\nmacaque\nlangur\ncolobus\nproboscis monkey\nmarmoset\ncapuchin\nhowler monkey\ntiti\nspider monkey\nsquirrel monkey\nMadagascar cat\nindri\nIndian elephant\nAfrican elephant\nlesser panda\ngiant panda\nbarracouta\neel\ncoho\nrock beauty\nanemone fish\nsturgeon\ngar\nlionfish\npuffer\nabacus\nabaya\nacademic gown\naccordion\nacoustic guitar\naircraft carrier\nairliner\nairship\naltar\nambulance\namphibian\nanalog clock\napiary\napron\nashcan\nassault rifle\nbackpack\nbakery\nbalance beam\nballoon\nballpoint\nBand Aid\nbanjo\nbannister\nbarbell\nbarber chair\nbarbershop\nbarn\nbarometer\nbarrel\nbarrow\nbaseball\nbasketball\nbassinet\nbassoon\nbathing cap\nbath towel\nbathtub\nbeach wagon\nbeacon\nbeaker\nbearskin\nbeer bottle\nbeer glass\nbell cote\nbib\nbicycle-built-for-two\nbikini\nbinder\nbinoculars\nbirdhouse\nboathouse\nbobsled\nbolo tie\nbonnet\nbookcase\nbookshop\nbottlecap\nbow\nbow tie\nbrass\nbrassiere\nbreakwater\nbreastplate\nbroom\nbucket\nbuckle\nbulletproof vest\nbullet train\nbutcher shop\ncab\ncaldron\ncandle\ncannon\ncanoe\ncan opener\ncardigan\ncar mirror\ncarousel\ncarpenter's kit\ncarton\ncar wheel\ncash machine\ncassette\ncassette player\ncastle\ncatamaran\nCD player\ncello\ncellular telephone\nchain\nchainlink fence\nchain mail\nchain saw\nchest\nchiffonier\nchime\nchina cabinet\nChristmas stocking\nchurch\ncinema\ncleaver\ncliff dwelling\ncloak\nclog\ncocktail shaker\ncoffee mug\ncoffeepot\ncoil\ncombination lock\ncomputer keyboard\nconfectionery\ncontainer ship\nconvertible\ncorkscrew\ncornet\ncowboy boot\ncowboy hat\ncradle\ncrane\ncrash helmet\ncrate\ncrib\nCrock Pot\ncroquet ball\ncrutch\ncuirass\ndam\ndesk\ndesktop computer\ndial telephone\ndiaper\ndigital clock\ndigital watch\ndining table\ndishrag\ndishwasher\ndisk brake\ndock\ndogsled\ndome\ndoormat\ndrilling platform\ndrum\ndrumstick\ndumbbell\nDutch oven\nelectric fan\nelectric guitar\nelectric locomotive\nentertainment center\nenvelope\nespresso maker\nface powder\nfeather boa\nfile\nfireboat\nfire engine\nfire screen\nflagpole\nflute\nfolding chair\nfootball helmet\nforklift\nfountain\nfountain pen\nfour-poster\nfreight car\nFrench horn\nfrying pan\nfur coat\ngarbage truck\ngasmask\ngas pump\ngoblet\ngo-kart\ngolf ball\ngolfcart\ngondola\ngong\ngown\ngrand piano\ngreenhouse\ngrille\ngrocery store\nguillotine\nhair slide\nhair spray\nhalf track\nhammer\nhamper\nhand blower\nhand-held computer\nhandkerchief\nhard disc\nharmonica\nharp\nharvester\nhatchet\nholster\nhome theater\nhoneycomb\nhook\nhoopskirt\nhorizontal bar\nhorse cart\nhourglass\niPod\niron\njack-o'-lantern\njean\njeep\njersey\njigsaw puzzle\njinrikisha\njoystick\nkimono\nknee pad\nknot\nlab coat\nladle\nlampshade\nlaptop\nlawn mower\nlens cap\nletter opener\nlibrary\nlifeboat\nlighter\nlimousine\nliner\nlipstick\nLoafer\nlotion\nloudspeaker\nloupe\nlumbermill\nmagnetic compass\nmailbag\nmailbox\nmaillot\nmaillot\nmanhole cover\nmaraca\nmarimba\nmask\nmatchstick\nmaypole\nmaze\nmeasuring cup\nmedicine chest\nmegalith\nmicrophone\nmicrowave\nmilitary uniform\nmilk can\nminibus\nminiskirt\nminivan\nmissile\nmitten\nmixing bowl\nmobile home\nModel T\nmodem\nmonastery\nmonitor\nmoped\nmortar\nmortarboard\nmosque\nmosquito net\nmotor scooter\nmountain bike\nmountain tent\nmouse\nmousetrap\nmoving van\nmuzzle\nnail\nneck brace\nnecklace\nnipple\nnotebook\nobelisk\noboe\nocarina\nodometer\noil filter\norgan\noscilloscope\noverskirt\noxcart\noxygen mask\npacket\npaddle\npaddlewheel\npadlock\npaintbrush\npajama\npalace\npanpipe\npaper towel\nparachute\nparallel bars\npark bench\nparking meter\npassenger car\npatio\npay-phone\npedestal\npencil box\npencil sharpener\nperfume\nPetri dish\nphotocopier\npick\npickelhaube\npicket fence\npickup\npier\npiggy bank\npill bottle\npillow\nping-pong ball\npinwheel\npirate\npitcher\nplane\nplanetarium\nplastic bag\nplate rack\nplow\nplunger\nPolaroid camera\npole\npolice van\nponcho\npool table\npop bottle\npot\npotter's wheel\npower drill\nprayer rug\nprinter\nprison\nprojectile\nprojector\npuck\npunching bag\npurse\nquill\nquilt\nracer\nracket\nradiator\nradio\nradio telescope\nrain barrel\nrecreational vehicle\nreel\nreflex camera\nrefrigerator\nremote control\nrestaurant\nrevolver\nrifle\nrocking chair\nrotisserie\nrubber eraser\nrugby ball\nrule\nrunning shoe\nsafe\nsafety pin\nsaltshaker\nsandal\nsarong\nsax\nscabbard\nscale\nschool bus\nschooner\nscoreboard\nscreen\nscrew\nscrewdriver\nseat belt\nsewing machine\nshield\nshoe shop\nshoji\nshopping basket\nshopping cart\nshovel\nshower cap\nshower curtain\nski\nski mask\nsleeping bag\nslide rule\nsliding door\nslot\nsnorkel\nsnowmobile\nsnowplow\nsoap dispenser\nsoccer ball\nsock\nsolar dish\nsombrero\nsoup bowl\nspace bar\nspace heater\nspace shuttle\nspatula\nspeedboat\nspider web\nspindle\nsports car\nspotlight\nstage\nsteam locomotive\nsteel arch bridge\nsteel drum\nstethoscope\nstole\nstone wall\nstopwatch\nstove\nstrainer\nstreetcar\nstretcher\nstudio couch\nstupa\nsubmarine\nsuit\nsundial\nsunglass\nsunglasses\nsunscreen\nsuspension bridge\nswab\nsweatshirt\nswimming trunks\nswing\nswitch\nsyringe\ntable lamp\ntank\ntape player\nteapot\nteddy\ntelevision\ntennis ball\nthatch\ntheater curtain\nthimble\nthresher\nthrone\ntile roof\ntoaster\ntobacco shop\ntoilet seat\ntorch\ntotem pole\ntow truck\ntoyshop\ntractor\ntrailer truck\ntray\ntrench coat\ntricycle\ntrimaran\ntripod\ntriumphal arch\ntrolleybus\ntrombone\ntub\nturnstile\ntypewriter keyboard\numbrella\nunicycle\nupright\nvacuum\nvase\nvault\nvelvet\nvending machine\nvestment\nviaduct\nviolin\nvolleyball\nwaffle iron\nwall clock\nwallet\nwardrobe\nwarplane\nwashbasin\nwasher\nwater bottle\nwater jug\nwater tower\nwhiskey jug\nwhistle\nwig\nwindow screen\nwindow shade\nWindsor tie\nwine bottle\nwing\nwok\nwooden spoon\nwool\nworm fence\nwreck\nyawl\nyurt\nweb site\ncomic book\ncrossword puzzle\nstreet sign\ntraffic light\nbook jacket\nmenu\nplate\nguacamole\nconsomme\nhot pot\ntrifle\nice cream\nice lolly\nFrench loaf\nbagel\npretzel\ncheeseburger\nhotdog\nmashed potato\nhead cabbage\nbroccoli\ncauliflower\nzucchini\nspaghetti squash\nacorn squash\nbutternut squash\ncucumber\nartichoke\nbell pepper\ncardoon\nmushroom\nGranny Smith\nstrawberry\norange\nlemon\nfig\npineapple\nbanana\njackfruit\ncustard apple\npomegranate\nhay\ncarbonara\nchocolate sauce\ndough\nmeat loaf\npizza\npotpie\nburrito\nred wine\nespresso\ncup\neggnog\nalp\nbubble\ncliff\ncoral reef\ngeyser\nlakeside\npromontory\nsandbar\nseashore\nvalley\nvolcano\nballplayer\ngroom\nscuba diver\nrapeseed\ndaisy\nyellow lady's slipper\ncorn\nacorn\nhip\nbuckeye\ncoral fungus\nagaric\ngyromitra\nstinkhorn\nearthstar\nhen-of-the-woods\nbolete\near\ntoilet tissue\n"
  },
  {
    "path": "examples/tensorflow-mobilenet-v2/src/main.rs",
    "content": "use tract_tensorflow::prelude::*;\n\nfn main() -> TractResult<()> {\n    let model = tract_tensorflow::tensorflow()\n        // load the model\n        .model_for_path(\"mobilenet_v2_1.4_224_frozen.pb\")?\n        // specify input type and shape\n        .with_input_fact(0, f32::fact([1, 224, 224, 3]).into())?\n        // optimize the model\n        .into_optimized()?\n        // make the model runnable and fix its inputs and outputs\n        .into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let image: Tensor = tract_ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    })\n    .into();\n\n    // run the model on the input\n    let result = model.run(tvec!(image.into()))?;\n\n    // find and display the max value with its index\n    let best = result[0]\n        .to_plain_array_view::<f32>()?\n        .iter()\n        .cloned()\n        .zip(1..)\n        .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap());\n    println!(\"result: {best:?}\");\n    Ok(())\n}\n"
  },
  {
    "path": "examples/tflite-mobilenet-v3/.gitignore",
    "content": "mobilenet_v2_1.4_224*\ntarget\n"
  },
  {
    "path": "examples/tflite-mobilenet-v3/Cargo.toml",
    "content": "[package]\nname = \"example-tflite-mobilenet-v3\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nimage.workspace = true\ntract-tflite.workspace = true\n"
  },
  {
    "path": "examples/tflite-mobilenet-v3/README.md",
    "content": "# Tract examples: TensorflowLite MobileNet v3\n\nThis project is a simple project with minimal code showing how to use tract to\nprocess an image with MobileNetV3.\n\nThe example assume the following command are run in the directory of this\nexample project, where this README lives.\n\n```sh\ngit clone https://github.com/snipsco/tract\ncd tract/examples/tflite-mobilenet-v3\n```\n\n## Obtaining the model \n\nMobileNet is a response to the ImageNet challenge. The goal is to categorize\nimages and associate them with one of 1000 labels. In other words, recognize a\ndog, a cat, a rabbit, or a military uniform.\n\nSee https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet for more information.\n\nYou will need to download the models. For instance:\n\n```sh\nwget -q https://tfhub.dev/google/lite-model/imagenet/mobilenet_v3_small_100_224/classification/5/default/1?lite-format=tflite -O mobilenet_v3_small_100_224.tflite\n```\n\n## Converting the input image\n\nAs in TensorFlow documentation, we will use a portrait of Grace Hopper\n(included with this example).\n\n```\ngrace_hopper.jpg: JPEG image data, JFIF standard 1.02, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 517x606, components 3\n```\n\n## Try it\n\n`cargo run` should print a lot of things, and ultimately: `result: Some((0.32560226, 654))`.\n\nThis is actually good. It is the rank (654) and a confidence indicator (0.32)\nof the inferred label.\n\n```\n$ cat -n imagenet_slim_labels.txt | grep -C 3 654\n   651  megalith\n   652  microphone\n   653  microwave\n   654  military uniform\n   655  milk can\n   656  minibus\n   657  miniskirt\n```\n\n## A look at the code\n\nEverything happens in [src/main.rs](src/main.rs).\n\n```rust\n    let model = tract_tflite::tflite()\n        // load the model\n        .model_for_path(\"./mobilenet_v3_small_100_224.tflite\")?\n        // optimize the model\n        .into_optimized()?\n        // make the model runnable and fix its inputs and outputs\n        .into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let image: Tensor = tract_ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    })\n    .into();\n\n    // run the model on the input\n    let result = model.run(tvec!(image.into()))?;\n\n    // find and display the max value with its index\n    let best = result[0]\n        .to_array_view::<f32>()?\n        .iter()\n        .cloned()\n        .zip(1..)\n        .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap());\n    println!(\"result: {:?}\", best);\n```\n\nIt uses three crates:\n\n* `tract-core` is the main tract crate. It contains tract operators, model\n    analysing and running infrastructure.\n* `tract-tflite` is the TensorFlow format parser. It translates most of\n    TensorFlow operators to core ones, or implements the one which are specific\n    to TensorFlow.\n* finally we use the `image` crate to load and resize the JPEG portrait.\n\n\n### Loading the model\n\nThis line creates a tract-tensorflow context, and uses it to load the protobuf\nmodel.\n\n```rust\n    let model = tract_tflite::tflite()\n        .model_for_path(\"./mobilenet_v3_small_100_224.tflite\")?\n        .into_optimized()?\n        .into_runnable()?;\n```\n\nNow the model is ready to run, we have an execution plan, so let's prepare the\nimage.\n\n### Conditioning the input\n\nMobileNet assumes its input in in the NHWC convention: [batch, height, width,\nchannels]. The MobileNet variant we have picked works with a 224x224 square\nRGB (C=3) pictures. We will only process one image at a time (N=1).\nAnd it operates on single precision floats (aka `f32`).\n\nWe use the `image` crate to load the `.jpg` image, resize is to 224x224. Then\nwe build an 4-dimension array in the right NHWC shape, with `f32` obtained by\nnormalizing the `u8` input to the `0..1` range. This array is then converted\ninto a Tensor.\n\n```rust\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb();\n    let resized = image::imageops::resize(&image, 224, 224, ::image::FilterType::Triangle);\n    let image: Tensor = ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    }).into();\n```\n\nNote that `tract-core` re-export the excellent `ndarray` crate so that it is\neasy to get the right version for tract conversion to work.\n\n### Run the network!\n\n```rust\n    let result = model.run(tvec!(image.into()))?;\n```\n\n### Interpret the result\n\nFinally we grab the single Tensor output by the plan execution, convert it to a\nndarray ArrayView of f32 values. It is a single dimension (a vector...) of 1001\ncategory scores (1000 labels plus the dummy one). We need pick the maximum\nscore, with its index, and display it...\n\n```rust\n    let best = result[0]\n        .to_array_view::<f32>()?\n        .iter()\n        .cloned()\n        .enumerate()\n        .zip(1..)\n        .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap());\n    println!(\"result: {:?}\", best);\n```\n"
  },
  {
    "path": "examples/tflite-mobilenet-v3/ci.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nwget -q \"https://tract-ci-builds.s3.amazonaws.com/model/mobilenet_v3_small_100_224.tflite\" -O mobilenet_v3_small_100_224.tflite\ncargo run\nrm -rf mobilenet*\n"
  },
  {
    "path": "examples/tflite-mobilenet-v3/imagenet_slim_labels.txt",
    "content": "dummy\ntench\ngoldfish\ngreat white shark\ntiger shark\nhammerhead\nelectric ray\nstingray\ncock\nhen\nostrich\nbrambling\ngoldfinch\nhouse finch\njunco\nindigo bunting\nrobin\nbulbul\njay\nmagpie\nchickadee\nwater ouzel\nkite\nbald eagle\nvulture\ngreat grey owl\nEuropean fire salamander\ncommon newt\neft\nspotted salamander\naxolotl\nbullfrog\ntree frog\ntailed frog\nloggerhead\nleatherback turtle\nmud turtle\nterrapin\nbox turtle\nbanded gecko\ncommon iguana\nAmerican chameleon\nwhiptail\nagama\nfrilled lizard\nalligator lizard\nGila monster\ngreen lizard\nAfrican chameleon\nKomodo dragon\nAfrican crocodile\nAmerican alligator\ntriceratops\nthunder snake\nringneck snake\nhognose snake\ngreen snake\nking snake\ngarter snake\nwater snake\nvine snake\nnight snake\nboa constrictor\nrock python\nIndian cobra\ngreen mamba\nsea snake\nhorned viper\ndiamondback\nsidewinder\ntrilobite\nharvestman\nscorpion\nblack and gold garden spider\nbarn spider\ngarden spider\nblack widow\ntarantula\nwolf spider\ntick\ncentipede\nblack grouse\nptarmigan\nruffed grouse\nprairie chicken\npeacock\nquail\npartridge\nAfrican grey\nmacaw\nsulphur-crested cockatoo\nlorikeet\ncoucal\nbee eater\nhornbill\nhummingbird\njacamar\ntoucan\ndrake\nred-breasted merganser\ngoose\nblack swan\ntusker\nechidna\nplatypus\nwallaby\nkoala\nwombat\njellyfish\nsea anemone\nbrain coral\nflatworm\nnematode\nconch\nsnail\nslug\nsea slug\nchiton\nchambered nautilus\nDungeness crab\nrock crab\nfiddler crab\nking crab\nAmerican lobster\nspiny lobster\ncrayfish\nhermit crab\nisopod\nwhite stork\nblack stork\nspoonbill\nflamingo\nlittle blue heron\nAmerican egret\nbittern\ncrane\nlimpkin\nEuropean gallinule\nAmerican coot\nbustard\nruddy turnstone\nred-backed sandpiper\nredshank\ndowitcher\noystercatcher\npelican\nking penguin\nalbatross\ngrey whale\nkiller whale\ndugong\nsea lion\nChihuahua\nJapanese spaniel\nMaltese dog\nPekinese\nShih-Tzu\nBlenheim spaniel\npapillon\ntoy terrier\nRhodesian ridgeback\nAfghan hound\nbasset\nbeagle\nbloodhound\nbluetick\nblack-and-tan coonhound\nWalker hound\nEnglish foxhound\nredbone\nborzoi\nIrish wolfhound\nItalian greyhound\nwhippet\nIbizan hound\nNorwegian elkhound\notterhound\nSaluki\nScottish deerhound\nWeimaraner\nStaffordshire bullterrier\nAmerican Staffordshire terrier\nBedlington terrier\nBorder terrier\nKerry blue terrier\nIrish terrier\nNorfolk terrier\nNorwich terrier\nYorkshire terrier\nwire-haired fox terrier\nLakeland terrier\nSealyham terrier\nAiredale\ncairn\nAustralian terrier\nDandie Dinmont\nBoston bull\nminiature schnauzer\ngiant schnauzer\nstandard schnauzer\nScotch terrier\nTibetan terrier\nsilky terrier\nsoft-coated wheaten terrier\nWest Highland white terrier\nLhasa\nflat-coated retriever\ncurly-coated retriever\ngolden retriever\nLabrador retriever\nChesapeake Bay retriever\nGerman short-haired pointer\nvizsla\nEnglish setter\nIrish setter\nGordon setter\nBrittany spaniel\nclumber\nEnglish springer\nWelsh springer spaniel\ncocker spaniel\nSussex spaniel\nIrish water spaniel\nkuvasz\nschipperke\ngroenendael\nmalinois\nbriard\nkelpie\nkomondor\nOld English sheepdog\nShetland sheepdog\ncollie\nBorder collie\nBouvier des Flandres\nRottweiler\nGerman shepherd\nDoberman\nminiature pinscher\nGreater Swiss Mountain dog\nBernese mountain dog\nAppenzeller\nEntleBucher\nboxer\nbull mastiff\nTibetan mastiff\nFrench bulldog\nGreat Dane\nSaint Bernard\nEskimo dog\nmalamute\nSiberian husky\ndalmatian\naffenpinscher\nbasenji\npug\nLeonberg\nNewfoundland\nGreat Pyrenees\nSamoyed\nPomeranian\nchow\nkeeshond\nBrabancon griffon\nPembroke\nCardigan\ntoy poodle\nminiature poodle\nstandard poodle\nMexican hairless\ntimber wolf\nwhite wolf\nred wolf\ncoyote\ndingo\ndhole\nAfrican hunting dog\nhyena\nred fox\nkit fox\nArctic fox\ngrey fox\ntabby\ntiger cat\nPersian cat\nSiamese cat\nEgyptian cat\ncougar\nlynx\nleopard\nsnow leopard\njaguar\nlion\ntiger\ncheetah\nbrown bear\nAmerican black bear\nice bear\nsloth bear\nmongoose\nmeerkat\ntiger beetle\nladybug\nground beetle\nlong-horned beetle\nleaf beetle\ndung beetle\nrhinoceros beetle\nweevil\nfly\nbee\nant\ngrasshopper\ncricket\nwalking stick\ncockroach\nmantis\ncicada\nleafhopper\nlacewing\ndragonfly\ndamselfly\nadmiral\nringlet\nmonarch\ncabbage butterfly\nsulphur butterfly\nlycaenid\nstarfish\nsea urchin\nsea cucumber\nwood rabbit\nhare\nAngora\nhamster\nporcupine\nfox squirrel\nmarmot\nbeaver\nguinea pig\nsorrel\nzebra\nhog\nwild boar\nwarthog\nhippopotamus\nox\nwater buffalo\nbison\nram\nbighorn\nibex\nhartebeest\nimpala\ngazelle\nArabian camel\nllama\nweasel\nmink\npolecat\nblack-footed ferret\notter\nskunk\nbadger\narmadillo\nthree-toed sloth\norangutan\ngorilla\nchimpanzee\ngibbon\nsiamang\nguenon\npatas\nbaboon\nmacaque\nlangur\ncolobus\nproboscis monkey\nmarmoset\ncapuchin\nhowler monkey\ntiti\nspider monkey\nsquirrel monkey\nMadagascar cat\nindri\nIndian elephant\nAfrican elephant\nlesser panda\ngiant panda\nbarracouta\neel\ncoho\nrock beauty\nanemone fish\nsturgeon\ngar\nlionfish\npuffer\nabacus\nabaya\nacademic gown\naccordion\nacoustic guitar\naircraft carrier\nairliner\nairship\naltar\nambulance\namphibian\nanalog clock\napiary\napron\nashcan\nassault rifle\nbackpack\nbakery\nbalance beam\nballoon\nballpoint\nBand Aid\nbanjo\nbannister\nbarbell\nbarber chair\nbarbershop\nbarn\nbarometer\nbarrel\nbarrow\nbaseball\nbasketball\nbassinet\nbassoon\nbathing cap\nbath towel\nbathtub\nbeach wagon\nbeacon\nbeaker\nbearskin\nbeer bottle\nbeer glass\nbell cote\nbib\nbicycle-built-for-two\nbikini\nbinder\nbinoculars\nbirdhouse\nboathouse\nbobsled\nbolo tie\nbonnet\nbookcase\nbookshop\nbottlecap\nbow\nbow tie\nbrass\nbrassiere\nbreakwater\nbreastplate\nbroom\nbucket\nbuckle\nbulletproof vest\nbullet train\nbutcher shop\ncab\ncaldron\ncandle\ncannon\ncanoe\ncan opener\ncardigan\ncar mirror\ncarousel\ncarpenter's kit\ncarton\ncar wheel\ncash machine\ncassette\ncassette player\ncastle\ncatamaran\nCD player\ncello\ncellular telephone\nchain\nchainlink fence\nchain mail\nchain saw\nchest\nchiffonier\nchime\nchina cabinet\nChristmas stocking\nchurch\ncinema\ncleaver\ncliff dwelling\ncloak\nclog\ncocktail shaker\ncoffee mug\ncoffeepot\ncoil\ncombination lock\ncomputer keyboard\nconfectionery\ncontainer ship\nconvertible\ncorkscrew\ncornet\ncowboy boot\ncowboy hat\ncradle\ncrane\ncrash helmet\ncrate\ncrib\nCrock Pot\ncroquet ball\ncrutch\ncuirass\ndam\ndesk\ndesktop computer\ndial telephone\ndiaper\ndigital clock\ndigital watch\ndining table\ndishrag\ndishwasher\ndisk brake\ndock\ndogsled\ndome\ndoormat\ndrilling platform\ndrum\ndrumstick\ndumbbell\nDutch oven\nelectric fan\nelectric guitar\nelectric locomotive\nentertainment center\nenvelope\nespresso maker\nface powder\nfeather boa\nfile\nfireboat\nfire engine\nfire screen\nflagpole\nflute\nfolding chair\nfootball helmet\nforklift\nfountain\nfountain pen\nfour-poster\nfreight car\nFrench horn\nfrying pan\nfur coat\ngarbage truck\ngasmask\ngas pump\ngoblet\ngo-kart\ngolf ball\ngolfcart\ngondola\ngong\ngown\ngrand piano\ngreenhouse\ngrille\ngrocery store\nguillotine\nhair slide\nhair spray\nhalf track\nhammer\nhamper\nhand blower\nhand-held computer\nhandkerchief\nhard disc\nharmonica\nharp\nharvester\nhatchet\nholster\nhome theater\nhoneycomb\nhook\nhoopskirt\nhorizontal bar\nhorse cart\nhourglass\niPod\niron\njack-o'-lantern\njean\njeep\njersey\njigsaw puzzle\njinrikisha\njoystick\nkimono\nknee pad\nknot\nlab coat\nladle\nlampshade\nlaptop\nlawn mower\nlens cap\nletter opener\nlibrary\nlifeboat\nlighter\nlimousine\nliner\nlipstick\nLoafer\nlotion\nloudspeaker\nloupe\nlumbermill\nmagnetic compass\nmailbag\nmailbox\nmaillot\nmaillot\nmanhole cover\nmaraca\nmarimba\nmask\nmatchstick\nmaypole\nmaze\nmeasuring cup\nmedicine chest\nmegalith\nmicrophone\nmicrowave\nmilitary uniform\nmilk can\nminibus\nminiskirt\nminivan\nmissile\nmitten\nmixing bowl\nmobile home\nModel T\nmodem\nmonastery\nmonitor\nmoped\nmortar\nmortarboard\nmosque\nmosquito net\nmotor scooter\nmountain bike\nmountain tent\nmouse\nmousetrap\nmoving van\nmuzzle\nnail\nneck brace\nnecklace\nnipple\nnotebook\nobelisk\noboe\nocarina\nodometer\noil filter\norgan\noscilloscope\noverskirt\noxcart\noxygen mask\npacket\npaddle\npaddlewheel\npadlock\npaintbrush\npajama\npalace\npanpipe\npaper towel\nparachute\nparallel bars\npark bench\nparking meter\npassenger car\npatio\npay-phone\npedestal\npencil box\npencil sharpener\nperfume\nPetri dish\nphotocopier\npick\npickelhaube\npicket fence\npickup\npier\npiggy bank\npill bottle\npillow\nping-pong ball\npinwheel\npirate\npitcher\nplane\nplanetarium\nplastic bag\nplate rack\nplow\nplunger\nPolaroid camera\npole\npolice van\nponcho\npool table\npop bottle\npot\npotter's wheel\npower drill\nprayer rug\nprinter\nprison\nprojectile\nprojector\npuck\npunching bag\npurse\nquill\nquilt\nracer\nracket\nradiator\nradio\nradio telescope\nrain barrel\nrecreational vehicle\nreel\nreflex camera\nrefrigerator\nremote control\nrestaurant\nrevolver\nrifle\nrocking chair\nrotisserie\nrubber eraser\nrugby ball\nrule\nrunning shoe\nsafe\nsafety pin\nsaltshaker\nsandal\nsarong\nsax\nscabbard\nscale\nschool bus\nschooner\nscoreboard\nscreen\nscrew\nscrewdriver\nseat belt\nsewing machine\nshield\nshoe shop\nshoji\nshopping basket\nshopping cart\nshovel\nshower cap\nshower curtain\nski\nski mask\nsleeping bag\nslide rule\nsliding door\nslot\nsnorkel\nsnowmobile\nsnowplow\nsoap dispenser\nsoccer ball\nsock\nsolar dish\nsombrero\nsoup bowl\nspace bar\nspace heater\nspace shuttle\nspatula\nspeedboat\nspider web\nspindle\nsports car\nspotlight\nstage\nsteam locomotive\nsteel arch bridge\nsteel drum\nstethoscope\nstole\nstone wall\nstopwatch\nstove\nstrainer\nstreetcar\nstretcher\nstudio couch\nstupa\nsubmarine\nsuit\nsundial\nsunglass\nsunglasses\nsunscreen\nsuspension bridge\nswab\nsweatshirt\nswimming trunks\nswing\nswitch\nsyringe\ntable lamp\ntank\ntape player\nteapot\nteddy\ntelevision\ntennis ball\nthatch\ntheater curtain\nthimble\nthresher\nthrone\ntile roof\ntoaster\ntobacco shop\ntoilet seat\ntorch\ntotem pole\ntow truck\ntoyshop\ntractor\ntrailer truck\ntray\ntrench coat\ntricycle\ntrimaran\ntripod\ntriumphal arch\ntrolleybus\ntrombone\ntub\nturnstile\ntypewriter keyboard\numbrella\nunicycle\nupright\nvacuum\nvase\nvault\nvelvet\nvending machine\nvestment\nviaduct\nviolin\nvolleyball\nwaffle iron\nwall clock\nwallet\nwardrobe\nwarplane\nwashbasin\nwasher\nwater bottle\nwater jug\nwater tower\nwhiskey jug\nwhistle\nwig\nwindow screen\nwindow shade\nWindsor tie\nwine bottle\nwing\nwok\nwooden spoon\nwool\nworm fence\nwreck\nyawl\nyurt\nweb site\ncomic book\ncrossword puzzle\nstreet sign\ntraffic light\nbook jacket\nmenu\nplate\nguacamole\nconsomme\nhot pot\ntrifle\nice cream\nice lolly\nFrench loaf\nbagel\npretzel\ncheeseburger\nhotdog\nmashed potato\nhead cabbage\nbroccoli\ncauliflower\nzucchini\nspaghetti squash\nacorn squash\nbutternut squash\ncucumber\nartichoke\nbell pepper\ncardoon\nmushroom\nGranny Smith\nstrawberry\norange\nlemon\nfig\npineapple\nbanana\njackfruit\ncustard apple\npomegranate\nhay\ncarbonara\nchocolate sauce\ndough\nmeat loaf\npizza\npotpie\nburrito\nred wine\nespresso\ncup\neggnog\nalp\nbubble\ncliff\ncoral reef\ngeyser\nlakeside\npromontory\nsandbar\nseashore\nvalley\nvolcano\nballplayer\ngroom\nscuba diver\nrapeseed\ndaisy\nyellow lady's slipper\ncorn\nacorn\nhip\nbuckeye\ncoral fungus\nagaric\ngyromitra\nstinkhorn\nearthstar\nhen-of-the-woods\nbolete\near\ntoilet tissue\n"
  },
  {
    "path": "examples/tflite-mobilenet-v3/src/main.rs",
    "content": "use tract_tflite::prelude::*;\n\nfn main() -> TractResult<()> {\n    let model = tract_tflite::tflite()\n        // load the model\n        .model_for_path(\"./mobilenet_v3_small_100_224.tflite\")?\n        // optimize the model\n        .into_optimized()?\n        // make the model runnable and fix its inputs and outputs\n        .into_runnable()?;\n\n    // open image, resize it and make a Tensor out of it\n    let image = image::open(\"grace_hopper.jpg\").unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 224, 224, ::image::imageops::FilterType::Triangle);\n    let image: Tensor = tract_ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    })\n    .into();\n\n    // run the model on the input\n    let result = model.run(tvec!(image.into()))?;\n\n    // find and display the max value with its index\n    let best = result[0]\n        .to_plain_array_view::<f32>()?\n        .iter()\n        .cloned()\n        .zip(1..)\n        .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap());\n    println!(\"result: {best:?}\");\n    Ok(())\n}\n"
  },
  {
    "path": "extra/Cargo.toml",
    "content": "[package]\nname = \"tract-extra\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\ntract-nnef.workspace = true\ntract-pulse.workspace = true\n\n[dev-dependencies]\ncriterion.workspace = true\nenv_logger.workspace = true\nlazy_static.workspace = true\nproptest.workspace = true\napprox.workspace = true\n"
  },
  {
    "path": "extra/src/exp_unit_norm.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef::tract_core::trivial_op_state_freeze;\nuse tract_pulse::PulsedOp;\nuse tract_pulse::model::PulsedModel;\nuse tract_pulse::ops::OpPulsifier;\nuse tract_pulse::{internal::*, pulsed_op_to_typed_op};\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_extra_exp_unit_norm\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Scalar.tensor().named(\"state\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Scalar.named(\"alpha\"),\n            TypeName::Integer.named(\"skip\").default(0),\n            TypeName::Logical.named(\"stateless\").default(false),\n            TypeName::Logical.named(\"complex\").default(false),\n            TypeName::Scalar.named(\"epsilon\").default(1e-14f32),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_eun,\n    );\n    registry.register_dumper(ser_eun);\n\n    registry.register_primitive(\n        \"tract_extra_exp_mean_norm\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Scalar.tensor().named(\"state\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Scalar.named(\"alpha\"),\n            TypeName::Integer.named(\"skip\").default(0),\n            TypeName::Logical.named(\"stateless\").default(false),\n            TypeName::Scalar.named(\"scaling_factor\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_eun,\n    );\n\n    OpPulsifier::register::<ExpUnitNorm>(pulsify).unwrap();\n}\n\nfn de_eun(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let state = invocation.named_arg_as(builder, \"state\")?;\n    let axis = invocation.named_arg_as::<i64>(builder, \"axis\")? as usize;\n    let alpha = invocation.named_arg_as(builder, \"alpha\")?;\n    let epsilon = invocation.get_named_arg_as(builder, \"epsilon\")?.unwrap_or(1e-14);\n    let stateless = invocation.named_arg_as::<bool>(builder, \"stateless\")?;\n    let complex = invocation.get_named_arg_as::<bool>(builder, \"complex\")?.unwrap_or(false);\n    let skip = invocation.named_arg_as::<i64>(builder, \"skip\")? as usize;\n    let scaling_factor = invocation.get_named_arg_as(builder, \"scaling_factor\")?.unwrap_or(1.0);\n    let mean = invocation.invocation.id == Identifier::from(\"tract_extra_exp_mean_norm\");\n    let op = ExpUnitNorm { alpha, axis, epsilon, stateless, skip, complex, scaling_factor, mean };\n    builder.wire(op, &[wire, state])\n}\n\nfn ser_eun(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ExpUnitNorm,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let state = ast.mapping[&node.inputs[1]].clone();\n    let mut attributes = vec![\n        (\"axis\", numeric(op.axis)),\n        (\"alpha\", numeric(op.alpha)),\n        (\"stateless\", logical(op.stateless)),\n        (\"skip\", numeric(op.skip)),\n    ];\n    if op.mean {\n        attributes.push((\"scaling_factor\", numeric(op.scaling_factor)));\n        Ok(Some(invocation(\"tract_extra_exp_mean_norm\", &[input, state], &attributes)))\n    } else {\n        attributes.push((\"epsilon\", numeric(op.epsilon)));\n        attributes.push((\"complex\", numeric(op.complex)));\n        Ok(Some(invocation(\"tract_extra_exp_unit_norm\", &[input, state], &attributes)))\n    }\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub struct ExpUnitNorm {\n    pub alpha: f32,\n    pub epsilon: f32,\n    pub axis: usize,\n    pub skip: usize,\n    pub stateless: bool,\n    pub complex: bool,\n    pub mean: bool,\n    pub scaling_factor: f32,\n}\nimpl Eq for ExpUnitNorm {}\n\n#[derive(Clone, Debug, PartialEq, Default)]\npub struct ExpUnitNormState {\n    hidden: Option<Tensor>,\n    index: usize,\n}\ntrivial_op_state_freeze!(ExpUnitNormState);\n\nimpl Op for ExpUnitNorm {\n    fn name(&self) -> StaticName {\n        \"ExpUnitNorm\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for ExpUnitNorm {\n    fn is_stateless(&self) -> bool {\n        self.stateless\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::<ExpUnitNormState>::default()))\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        ExpUnitNormState::default().eval(self, inputs)\n    }\n}\n\nimpl ExpUnitNormState {\n    fn eval(&mut self, op: &ExpUnitNorm, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        use tract_ndarray::Axis;\n        let (input, state0) = args_2!(inputs);\n        let mut input = input.into_tensor();\n        let mut input_plain = input.try_as_plain_mut()?;\n        let mut x_view = input_plain.to_array_view_mut::<f32>()?;\n        if self.hidden.is_none() || op.stateless {\n            self.hidden = Some(state0.into_tensor());\n        }\n        if op.complex {\n            ensure!(x_view.shape()[x_view.ndim() - 1] == 2);\n        }\n        let mut hidden_plain = self.hidden.as_mut().unwrap().try_as_plain_mut()?;\n        let mut state = hidden_plain.to_array_view_mut::<f32>()?;\n        for mut time_slice in x_view.axis_iter_mut(Axis(op.axis)) {\n            if self.index >= op.skip {\n                if op.mean {\n                    state.zip_mut_with(&time_slice, |s: &mut f32, x: &f32| {\n                        *s = x * (1f32 - op.alpha) + *s * op.alpha;\n                    });\n                } else {\n                    // unit norms\n                    let normed = if op.complex {\n                        time_slice\n                            .mapv(|x| x * x)\n                            .sum_axis(Axis(time_slice.ndim() - 1))\n                            .mapv(|x| x.sqrt())\n                    } else {\n                        time_slice.mapv(|x| x.abs())\n                    };\n                    state.zip_mut_with(&normed, |s: &mut f32, x: &f32| {\n                        *s = x.max(op.epsilon) * (1f32 - op.alpha) + *s * op.alpha;\n                    });\n                }\n            }\n            if op.mean {\n                time_slice.zip_mut_with(&state, |x, s| *x = (*x - s) / op.scaling_factor);\n            } else if op.complex {\n                let state_view = state.view().insert_axis(Axis(state.ndim()));\n                time_slice.zip_mut_with(&state_view, |x, s| *x /= s.sqrt());\n            } else {\n                time_slice.zip_mut_with(&state, |x, s| *x /= s.sqrt());\n            }\n            self.index += 1;\n        }\n        Ok(tvec!(input.into_tvalue()))\n    }\n}\n\nimpl OpState for ExpUnitNormState {\n    fn eval(\n        &mut self,\n        _session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let op = op.downcast_ref::<ExpUnitNorm>().context(\"Wrong op\")?;\n        Self::eval(self, op, inputs)\n    }\n}\n\nimpl TypedOp for ExpUnitNorm {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut state_shape = inputs[0].shape.clone();\n        state_shape.remove_axis(self.axis)?;\n        if self.complex {\n            ensure!(inputs[0].shape[inputs[0].rank() - 1] == 2.to_dim());\n            state_shape.remove_axis(state_shape.rank() - 1)?;\n        }\n        ensure!(inputs[1].without_value() == inputs[0].datum_type.fact(state_shape));\n        Ok(tvec!(inputs[0].without_value()))\n    }\n\n    as_op!();\n}\n\nimpl PulsedOp for ExpUnitNorm {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n\nfn pulsify(\n    _source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    symbol: &Symbol,\n    _pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let op = node.op_as::<ExpUnitNorm>().unwrap();\n    let (input, state0) = (mapping[&node.inputs[0]], mapping[&node.inputs[1]]);\n    let input_fact = target.outlet_fact(input)?;\n    let pulsing_input_axis = input_fact\n        .to_streaming_fact()\n        .shape\n        .iter()\n        .position(|dim| dim.symbols().contains(symbol))\n        .context(\"No pulsing axis found\")?;\n    if pulsing_input_axis == op.axis {\n        let pulsed_op =\n            ExpUnitNorm { skip: input_fact.stream.as_ref().unwrap().delay, ..op.clone() };\n        target.wire_node(&node.name, pulsed_op, &[input, state0]).map(Some)\n    } else {\n        target.wire_node(&node.name, op.clone(), &[input, state0]).map(Some)\n    }\n}\n"
  },
  {
    "path": "extra/src/lib.rs",
    "content": "use tract_nnef::internal::*;\n\nmod exp_unit_norm;\n\npub trait WithTractExtra {\n    fn enable_tract_extra(&mut self);\n    fn with_tract_extra(self) -> Self;\n}\n\nimpl WithTractExtra for tract_nnef::framework::Nnef {\n    fn enable_tract_extra(&mut self) {\n        self.enable_tract_core();\n        self.registries.push(tract_extra_registry());\n    }\n\n    fn with_tract_extra(mut self) -> Self {\n        self.enable_tract_extra();\n        self\n    }\n}\n\npub fn tract_extra_registry() -> Registry {\n    let mut reg = Registry::new(\"tract_extra\");\n    exp_unit_norm::register(&mut reg);\n    reg\n}\n\npub fn register_pulsifiers() {\n    let _ = tract_extra_registry();\n}\n"
  },
  {
    "path": "gpu/Cargo.toml",
    "content": "[package]\nname = \"tract-gpu\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\n\t\"Hubert de La Jonquiere <hubert.delajonquiere@sonos.com>\",\n\t\"Mathieu Poumeyrol <kali@zoy.org>\",\n\t\"Louis Chouraki <louis.chouraki@sonos.com>\",\n]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\", \"GPU\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nanyhow.workspace = true\nderive-new.workspace = true\ndowncast-rs.workspace = true\ndyn-eq.workspace = true\nnum-traits.workspace = true\ntract-core.workspace = true\ntract-pulse-opl.workspace = true\ntract-transformers.workspace = true\ndyn-hash.workspace = true\n"
  },
  {
    "path": "gpu/src/device.rs",
    "content": "use std::ffi::c_void;\nuse std::ops::Range;\nuse std::sync::Mutex;\n\nuse anyhow::{anyhow, bail};\nuse downcast_rs::{Downcast, impl_downcast};\nuse tract_core::dyn_clone;\nuse tract_core::internal::*;\nuse tract_core::value::TValue;\n\nuse crate::tensor::{DeviceTensor, OwnedDeviceTensor};\n\npub trait DeviceContext: Downcast + dyn_clone::DynClone + Send + Sync {\n    fn tensor_to_device(&self, tensor: TValue) -> TractResult<Box<dyn OwnedDeviceTensor>>;\n    fn uninitialized_device_tensor(\n        &self,\n        shape: &[usize],\n        dt: DatumType,\n    ) -> TractResult<Box<dyn OwnedDeviceTensor>>;\n    fn uninitialized_device_exotic_tensor(\n        &self,\n        exotic_fact: Box<dyn ExoticFact>,\n    ) -> TractResult<Box<dyn OwnedDeviceTensor>>;\n    fn synchronize(&self) -> TractResult<()>;\n    fn copy_nd(\n        &self,\n        input: &DeviceTensor,\n        input_offset: usize,\n        input_strides: &[isize],\n        output: &DeviceTensor,\n        output_offset: usize,\n        output_shape: &[usize],\n        output_strides: &[isize],\n    ) -> TractResult<()>;\n\n    /// Copy a slice along `axis` from `src[src_range]` into `dst[dst_range]`.\n    fn assign_slice(\n        &self,\n        dst: &DeviceTensor,\n        dst_range: Range<usize>,\n        src: &DeviceTensor,\n        src_range: Range<usize>,\n        axis: usize,\n    ) -> TractResult<()> {\n        let mut zone_shape: TVec<usize> = src.shape().into();\n        zone_shape[axis] = src_range.len();\n        if zone_shape.iter().product::<usize>() == 0 {\n            return Ok(());\n        }\n        let src_offset =\n            src_range.start * src.strides()[axis] as usize * src.datum_type().size_of();\n        let dst_offset =\n            dst_range.start * dst.strides()[axis] as usize * dst.datum_type().size_of();\n        self.copy_nd(src, src_offset, src.strides(), dst, dst_offset, &zone_shape, dst.strides())\n    }\n\n    /// Copy from `src` into `dst` with given origins and strides.\n    fn copy_with_origins(\n        &self,\n        zone_shape: &[usize],\n        dst: &DeviceTensor,\n        dst_origin: &[usize],\n        dst_strides: &[isize],\n        src: &DeviceTensor,\n        src_origin: &[usize],\n        src_strides: &[isize],\n    ) -> TractResult<()> {\n        if zone_shape.iter().product::<usize>() == 0 {\n            return Ok(());\n        }\n        let dt_size = src.datum_type().size_of();\n        let src_offset: usize =\n            src_origin.iter().zip(src_strides).map(|(o, s)| o * *s as usize).sum::<usize>()\n                * dt_size;\n        let dst_offset: usize =\n            dst_origin.iter().zip(dst_strides).map(|(o, s)| o * *s as usize).sum::<usize>()\n                * dt_size;\n        self.copy_nd(src, src_offset, src_strides, dst, dst_offset, zone_shape, dst_strides)\n    }\n\n    /// Flat memcpy of `byte_len` bytes.\n    fn flat_copy(\n        &self,\n        src: &DeviceTensor,\n        src_byte_offset: usize,\n        dst: &DeviceTensor,\n        dst_byte_offset: usize,\n        byte_len: usize,\n    ) -> TractResult<()> {\n        if byte_len == 0 {\n            return Ok(());\n        }\n        self.copy_nd(src, src_byte_offset, &[1], dst, dst_byte_offset, &[byte_len], &[1])\n    }\n}\n\nimpl_downcast!(DeviceContext);\ndyn_clone::clone_trait_object!(DeviceContext);\n\npub trait DeviceBuffer: Downcast + dyn_clone::DynClone + Send + Sync + std::fmt::Debug {\n    fn ptr(&self) -> *const c_void;\n}\n\nimpl_downcast!(DeviceBuffer);\ndyn_clone::clone_trait_object!(DeviceBuffer);\n\npub static DEVICE_CONTEXT: Mutex<Option<Box<dyn DeviceContext>>> = Mutex::new(None);\n\npub fn set_context(curr_context: Box<dyn DeviceContext>) -> TractResult<()> {\n    let mut context = DEVICE_CONTEXT.lock().unwrap();\n    if context.is_none() {\n        *context = Some(curr_context);\n        Ok(())\n    } else {\n        bail!(\"Context is already set\")\n    }\n}\n\npub fn get_context() -> TractResult<Box<dyn DeviceContext>> {\n    let guard = DEVICE_CONTEXT.lock().map_err(|_| anyhow!(\"Cannot read GPU Context\"))?;\n    guard.as_ref().cloned().ok_or_else(|| anyhow!(\"GPU Context not initialized\"))\n}\n"
  },
  {
    "path": "gpu/src/fact.rs",
    "content": "use std::fmt;\nuse tract_core::internal::*;\n\n/// Origin of the GPU tensor\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub enum DeviceTensorOrigin {\n    /// Tensor outputted by a device operator\n    /// Can be either a Host or ArenaView tensor\n    /// Note: Tensors marked as Device are from asynchronous operations.\n    FromDevice,\n    /// Tensor built from a CPU tensor (CPU op output or Const)\n    /// Can be only Host tensor.\n    /// Note: Tensors marked as Host are from synchronous operations.\n    FromHost,\n}\n\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct DeviceFact {\n    pub origin: DeviceTensorOrigin,\n    pub fact: TypedFact,\n    pub state_owned: bool,\n}\n\nimpl DeviceFact {\n    pub fn new(origin: DeviceTensorOrigin, fact: TypedFact) -> TractResult<Self> {\n        ensure!(fact.as_device_fact().is_none());\n        let new_fact = fact.without_value();\n        Ok(Self { origin, fact: new_fact, state_owned: false })\n    }\n\n    pub fn from_host(fact: TypedFact) -> TractResult<Self> {\n        Self::new(DeviceTensorOrigin::FromHost, fact)\n    }\n\n    pub fn is_from_device(&self) -> bool {\n        matches!(self.origin, DeviceTensorOrigin::FromDevice)\n    }\n\n    pub fn is_state_owned(&self) -> bool {\n        self.state_owned\n    }\n\n    pub fn is_from_host(&self) -> bool {\n        matches!(self.origin, DeviceTensorOrigin::FromHost)\n    }\n\n    pub fn into_typed_fact(self) -> TypedFact {\n        self.fact\n    }\n\n    pub fn into_exotic_fact(self) -> TypedFact {\n        let dt = self.fact.datum_type;\n        let shape = self.fact.shape.clone();\n        TypedFact::dt_shape(dt, shape).with_exotic_fact(self)\n    }\n}\n\nimpl ExoticFact for DeviceFact {\n    fn clarify_dt_shape(&self) -> Option<(DatumType, TVec<TDim>)> {\n        Some((self.fact.datum_type, self.fact.shape.to_tvec()))\n    }\n\n    fn buffer_sizes(&self) -> TVec<TDim> {\n        let inner_fact = &self.fact;\n        let mut sizes = tvec!(inner_fact.shape.volume() * inner_fact.datum_type.size_of());\n        if let Some(of) = inner_fact.exotic_fact() {\n            sizes.extend(of.buffer_sizes());\n        }\n        sizes\n    }\n    fn compatible_with(&self, other: &dyn ExoticFact) -> bool {\n        other.is::<Self>()\n    }\n}\n\nimpl fmt::Debug for DeviceFact {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        match self.origin {\n            DeviceTensorOrigin::FromHost => write!(fmt, \"FromHost({:?})\", self.without_value()),\n            DeviceTensorOrigin::FromDevice => {\n                write!(fmt, \"FromDevice({:?})\", self.fact.without_value())\n            }\n        }\n    }\n}\n\npub trait DeviceTypedFactExt {\n    fn to_device_fact(&self) -> TractResult<&DeviceFact>;\n    fn as_device_fact(&self) -> Option<&DeviceFact>;\n    fn as_device_fact_mut(&mut self) -> Option<&mut DeviceFact>;\n}\n\nimpl DeviceTypedFactExt for TypedFact {\n    fn to_device_fact(&self) -> TractResult<&DeviceFact> {\n        self.exotic_fact\n            .as_ref()\n            .and_then(|m| m.downcast_ref::<DeviceFact>())\n            .ok_or_else(|| anyhow!(\"DeviceFact not found\"))\n    }\n    fn as_device_fact(&self) -> Option<&DeviceFact> {\n        self.exotic_fact.as_ref().and_then(|m| m.downcast_ref::<DeviceFact>())\n    }\n    fn as_device_fact_mut(&mut self) -> Option<&mut DeviceFact> {\n        self.exotic_fact.as_mut().and_then(|m| m.downcast_mut::<DeviceFact>())\n    }\n}\n\nimpl std::ops::Deref for DeviceFact {\n    type Target = TypedFact;\n    fn deref(&self) -> &Self::Target {\n        &self.fact\n    }\n}\n\nimpl std::convert::AsRef<TypedFact> for DeviceFact {\n    fn as_ref(&self) -> &TypedFact {\n        &self.fact\n    }\n}\n"
  },
  {
    "path": "gpu/src/lib.rs",
    "content": "pub mod device;\npub mod fact;\npub mod memory;\npub mod ops;\npub mod rewrite_rules;\npub mod session_handler;\npub mod sync;\npub mod tensor;\npub mod utils;\n"
  },
  {
    "path": "gpu/src/memory/mod.rs",
    "content": "mod pool;\nmod schema;\n\npub use pool::DeviceMemoryPool;\npub use schema::{DeviceMemSchema, DeviceResolvedMemSchema};\n"
  },
  {
    "path": "gpu/src/memory/pool.rs",
    "content": "use crate::device::get_context;\nuse crate::memory::DeviceResolvedMemSchema;\nuse crate::tensor::DeviceArenaView;\nuse crate::tensor::DeviceTensor;\nuse crate::tensor::OwnedDeviceTensor;\n\nuse tract_core::internal::*;\n\n#[derive(Debug)]\npub struct DeviceMemoryPool {\n    storage: Arc<Box<dyn OwnedDeviceTensor>>,\n    resolved_schema: DeviceResolvedMemSchema,\n}\n\nimpl DeviceMemoryPool {\n    pub fn from_schema(resolved_schema: DeviceResolvedMemSchema) -> TractResult<Self> {\n        Ok(Self {\n            storage: Arc::new(\n                get_context()?\n                    .uninitialized_device_tensor(&[resolved_schema.memory_size], DatumType::U8)?,\n            ),\n            resolved_schema,\n        })\n    }\n\n    pub fn tensor_for_node(\n        &self,\n        node_id: usize,\n        dt: DatumType,\n        shape: &[usize],\n    ) -> TractResult<DeviceTensor> {\n        self.resolved_schema.offsets_by_node[node_id]\n            .as_ref()\n            .map(|offsets| {\n                ensure!(\n                    offsets.len() == 1 && offsets[0].len() == 1,\n                    \"'tensor_for_node' is for mono-output nodes only\"\n                );\n                Ok(DeviceArenaView {\n                    arena: Arc::clone(&self.storage),\n                    dt,\n                    len: shape.iter().product(),\n                    shape: shape.into(),\n                    strides: Tensor::natural_strides(shape),\n                    offset_bytes: offsets[0][0],\n                    exotic_fact: None,\n                }\n                .into())\n            })\n            .unwrap_or_else(|| DeviceTensor::uninitialized_dt(dt, shape))\n    }\n\n    pub fn scalar_exotic_tensor_for_node(\n        &self,\n        node_id: usize,\n        dt: DatumType,\n        exotic_fact: Box<dyn ExoticFact>,\n    ) -> TractResult<DeviceTensor> {\n        match self.resolved_schema.offsets_by_node[node_id].as_ref() {\n            Some(offsets) => {\n                ensure!(\n                    offsets.len() == 1 && offsets[0].len() == 2,\n                    \"'scalar_exotic_tensor_for_node' is for mono-output nodes only\"\n                );\n                Ok(DeviceArenaView {\n                    arena: Arc::clone(&self.storage),\n                    dt,\n                    len: 1,\n                    shape: tvec!(),\n                    strides: tvec!(),\n                    offset_bytes: offsets[0][1],\n                    exotic_fact: Some(exotic_fact.clone()),\n                }\n                .into())\n            }\n            None => DeviceTensor::uninitialized_exotic(exotic_fact),\n        }\n    }\n}\n"
  },
  {
    "path": "gpu/src/memory/schema.rs",
    "content": "use std::fmt;\nuse std::fmt::Debug;\nuse tract_core::internal::num_integer::Integer;\nuse tract_core::internal::*;\n\nuse crate::fact::DeviceTypedFactExt;\nuse crate::sync::{DeviceSync, DeviceSyncKind};\n\n/// Requirement for node outputs from a memory perspective.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct NodeMemReq {\n    pub outlet_id: OutletId,\n    pub lifetime: Lifetime,\n    pub mem_size: TDim,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct Lifetime {\n    pub start: usize,\n    pub end: usize,\n}\n\nimpl Lifetime {\n    pub fn is_disjoint(&self, other: &Lifetime) -> bool {\n        self.start >= other.end || other.start >= self.end\n    }\n\n    pub fn is_alive_at_step(&self, step: usize) -> bool {\n        self.start <= step && step < self.end\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    pub fn len(&self) -> usize {\n        self.end - self.start\n    }\n}\n\nfn next_nodes<'a>(model: &'a TypedModel, node: &TypedNode) -> Option<TVec<&'a TypedNode>> {\n    if node.outputs.is_empty() {\n        return None;\n    };\n\n    Some(\n        node.outputs\n            .iter()\n            .flat_map(|o| {\n                o.successors.iter().map(|succ| &model.nodes()[succ.node]).collect::<Vec<_>>()\n            })\n            .collect(),\n    )\n}\n\npub fn eval_device_mem_req_for_nodes(\n    model: &TypedModel,\n    order: &[usize],\n) -> TractResult<TVec<NodeMemReq>> {\n    let outputs = model.output_outlets()?.to_vec();\n    let flush_lists = order::build_flush_list(model, order, &outputs, |node| {\n        let Ok(facts) = model.node_output_facts(node.id) else { return false };\n\n        let cpu_sync_in_next_nodes = next_nodes(model, node).is_some_and(|nodes| {\n            nodes.iter().any(|it| {\n                it.op_as::<DeviceSync>().is_some_and(|op| op.kind == DeviceSyncKind::ToHost)\n            })\n        });\n\n        !cpu_sync_in_next_nodes\n            && facts.iter().any(|it| {\n                it.as_device_fact()\n                    .map(|it| it.is_from_device() && !it.is_state_owned())\n                    .unwrap_or(false)\n            })\n    });\n    let mut scoped_nodes = tvec![];\n\n    for (step, n) in order.iter().enumerate() {\n        let lifetime_start = step;\n\n        let lifetime_end = flush_lists\n            .iter()\n            .enumerate()\n            .find(|(_step, flush_list)| flush_list.contains(n))\n            .map(|it| usize::min(it.0 + 1, order.len()));\n        // Ignore nodes that won't be flushed from Device.\n        let Some(lifetime_end) = lifetime_end else {\n            continue;\n        };\n\n        let out_device_tmp_facts = model\n            .node_output_facts(*n)?\n            .into_iter()\n            .flat_map(|it| it.as_device_fact())\n            .filter(|it| it.is_from_device())\n            .collect::<TVec<_>>();\n\n        if out_device_tmp_facts.is_empty() {\n            continue;\n        }\n\n        for (slot, fact) in out_device_tmp_facts.iter().enumerate() {\n            let outlet_id = OutletId { node: *n, slot };\n            for buff_size in fact.buffer_sizes() {\n                scoped_nodes.push(NodeMemReq {\n                    outlet_id,\n                    lifetime: Lifetime { start: lifetime_start, end: lifetime_end },\n                    mem_size: buff_size,\n                })\n            }\n        }\n    }\n\n    Ok(scoped_nodes)\n}\n\nfn collect_exotic_facts(model: &TypedModel) -> TractResult<Vec<NodeExoticFacts>> {\n    let mut res: Vec<TVec<Option<Box<dyn ExoticFact>>>> = vec![];\n    for node in model.nodes() {\n        let mut tmp: TVec<Option<Box<dyn ExoticFact>>> = tvec![];\n        for fact in model.node_output_facts(node.id)? {\n            if let Some(dev_fact) = fact.as_device_fact() {\n                tmp.push(dev_fact.exotic_fact.clone());\n            }\n        }\n        res.push(tmp);\n    }\n    Ok(res)\n}\n\n/// A partition is a list of node that have disjoint memory requirement from a lifetime\n/// perspective.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct Partition {\n    pub nodes: Vec<NodeMemReq>,\n}\n\nimpl Partition {\n    pub fn eval_size_to_i64(&self, symbols: &SymbolValues) -> TractResult<i64> {\n        let mut max_size = self\n            .nodes\n            .iter()\n            .map(|it| it.mem_size.eval_to_i64(symbols))\n            .collect::<TractResult<Vec<_>>>()?\n            .into_iter()\n            .max()\n            .unwrap_or(0);\n        max_size = Integer::next_multiple_of(&max_size, &(vector_size() as i64));\n        Ok(max_size)\n    }\n\n    pub fn size(&self) -> TDim {\n        TDim::Max(self.nodes.iter().map(|s| s.mem_size.clone()).collect()).simplify()\n    }\n\n    pub fn has_no_conflict_with_lifetime(&self, lifetime: &Lifetime) -> bool {\n        self.nodes.iter().all(|n| n.lifetime.is_disjoint(lifetime))\n    }\n\n    pub fn find_node_alive_at_step(&self, step: usize) -> Option<&NodeMemReq> {\n        self.nodes.iter().find(|it| it.lifetime.is_alive_at_step(step))\n    }\n}\n\ntype NodeExoticFacts = TVec<Option<Box<dyn ExoticFact>>>;\n/// This struct represents a resolved memory schema for a model that contains\n/// GPU operators. This schema is concrete.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct DeviceResolvedMemSchema {\n    pub offsets_by_node: Vec<Option<TVec<TVec<usize>>>>,\n    pub memory_size: usize,\n}\n\n/// This struct represent a memory schema for node output memory that are handled\n/// by a GPU.\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct DeviceMemSchema {\n    /// Total numbef in the model.\n    pub model_num_nodes: usize,\n    pub by_partition: Vec<Partition>,\n    // vec![vec![Option<NodeMemReq>; num_partitions]; num_steps].\n    pub by_steps: Vec<Vec<Option<NodeMemReq>>>,\n    pub exotic_facts: Vec<NodeExoticFacts>,\n}\n\nimpl DeviceMemSchema {\n    /// Returns memory size of each inner partitions.\n    pub fn size_by_partition(&self) -> Vec<TDim> {\n        self.by_partition.iter().map(|it| it.size()).collect()\n    }\n\n    /// Evaluate memory size by partition for given symbol values.\n    pub fn eval_size_by_partition(&self, symbols: &SymbolValues) -> TractResult<Vec<i64>> {\n        self.by_partition.iter().map(|it| it.eval_size_to_i64(symbols)).collect()\n    }\n\n    /// Returns total memory size required for the schema.\n    pub fn memory_size(&self) -> TDim {\n        self.by_partition.iter().map(|it| it.size()).sum()\n    }\n\n    /// Evaluate memory size required for the schema for given symbol values.\n    pub fn eval_memory_size(&self, symbols: &SymbolValues) -> TractResult<i64> {\n        self.by_partition.iter().map(|it| it.eval_size_to_i64(symbols)).sum()\n    }\n\n    /// Compute offsets for each node for given symbols. Node ids\n    /// are indexes in the returned vector.\n    pub fn compute_offset_by_node(\n        &self,\n        symbols: &SymbolValues,\n    ) -> TractResult<Vec<Option<TVec<TVec<usize>>>>> {\n        let mut cursor = 0;\n        let mut offset_by_outlet: Vec<Option<TVec<TVec<usize>>>> = vec![None; self.model_num_nodes];\n\n        for partition in &self.by_partition {\n            for node_mem in &partition.nodes {\n                let node = node_mem.outlet_id.node;\n                let slot = node_mem.outlet_id.slot;\n\n                let slots: &mut TVec<TVec<usize>> =\n                    offset_by_outlet[node].get_or_insert_with(|| tvec![tvec!()]);\n\n                if slot < 1 {\n                    slots[slot].push(cursor);\n                } else {\n                    if slots.len() <= slot {\n                        slots.resize_with(slot + 1, TVec::<usize>::new);\n                    }\n                    slots[slot].push(cursor);\n                }\n            }\n            cursor += partition.eval_size_to_i64(symbols)? as usize;\n        }\n\n        Ok(offset_by_outlet)\n    }\n\n    /// Evaluate peak memory size for given symbols. The return value is lower or equal to the memory\n    /// size of the schema. The difference between peak memory size and memory size represents the\n    /// memory fragmentation introduced by the schema.\n    pub fn eval_peak_memory_size(&self, symbols: &SymbolValues) -> TractResult<i64> {\n        Ok(self\n            .by_steps\n            .iter()\n            .map(|active_nodes| {\n                active_nodes\n                    .iter()\n                    .flatten()\n                    .map(|it| it.mem_size.clone())\n                    .sum::<TDim>()\n                    .eval_to_i64(symbols)\n            })\n            .collect::<TractResult<Vec<_>>>()?\n            .into_iter()\n            .max()\n            .unwrap_or(0))\n    }\n\n    /// Evaluate the usage for given symbols as the ratio between\n    /// schema memory size and peak memory size. A value of 1.0 means\n    /// that the schema doesn't introduce memory fragmentation.\n    pub fn eval_usage(&self, symbols: &SymbolValues) -> TractResult<f32> {\n        let memory_size = self.eval_memory_size(symbols)? as f32;\n        let peak_memory_size = self.eval_peak_memory_size(symbols)? as f32;\n        Ok(peak_memory_size / memory_size)\n    }\n}\n\nimpl fmt::Display for DeviceMemSchema {\n    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {\n        for (step, mem_step) in self.by_steps.iter().enumerate() {\n            writeln!(\n                fmt,\n                \"step: {:5} => |{}|\",\n                step,\n                mem_step\n                    .iter()\n                    .map(|n| -> String {\n                        n.as_ref()\n                            .map(|it| format!(\"{:^7}/{:^7}\", it.outlet_id.node, it.outlet_id.slot))\n                            .unwrap_or(format!(\"{:^7}\", \"*\"))\n                    })\n                    .collect::<Vec<String>>()\n                    .join(\"|\")\n            )?;\n        }\n        writeln!(fmt, \"memory_size: {}\", self.memory_size())?;\n        Ok(())\n    }\n}\n\nimpl DeviceMemSchema {\n    /// Resolve Memory schema with given symbols.\n    pub fn resolve(&self, symbols: &SymbolValues) -> TractResult<DeviceResolvedMemSchema> {\n        Ok(DeviceResolvedMemSchema {\n            offsets_by_node: self.compute_offset_by_node(symbols)?,\n            memory_size: self.eval_memory_size(symbols)?.try_into()?,\n        })\n    }\n\n    /// Build a memory schema for given model and execution order. The hint is used to optimize\n    /// the memory schema because it is based on symbolic dimensions. That doesn't mean it will be\n    /// optimal for all possible values for symbolic dimensions.\n    pub fn build(\n        model: &TypedModel,\n        order: &[usize],\n        hint: &SymbolValues,\n    ) -> TractResult<DeviceMemSchema> {\n        let mut nodes_mem_req = eval_device_mem_req_for_nodes(model, order)?;\n\n        let exotic_facts = collect_exotic_facts(model)?;\n        let hinted_mem_size = nodes_mem_req\n            .iter()\n            .map(|node_mem| Ok((node_mem.outlet_id, node_mem.mem_size.eval_to_i64(hint)?)))\n            .collect::<TractResult<HashMap<OutletId, i64>>>()?;\n\n        nodes_mem_req.sort_by(|lhs, rhs| {\n            let lhs_hint_mem_size = hinted_mem_size.get(&lhs.outlet_id);\n            let rhs_hint_mem_size = hinted_mem_size.get(&rhs.outlet_id);\n            lhs_hint_mem_size.cmp(&rhs_hint_mem_size).reverse()\n        });\n\n        let mut partitions: Vec<Partition> = vec![];\n        for node_mem in nodes_mem_req {\n            // Find partitions where node lifetime is disjoint from existing.\n            let mut available = partitions\n                .iter_mut()\n                .filter(|it| it.has_no_conflict_with_lifetime(&node_mem.lifetime))\n                .collect::<Vec<_>>();\n\n            available.sort_by_cached_key(|n| {\n                -n.nodes.iter().flat_map(|it| hinted_mem_size.get(&it.outlet_id)).sum::<i64>()\n            });\n\n            match available.first_mut() {\n                Some(available) => {\n                    available.nodes.push(node_mem);\n                }\n                None => partitions.push(Partition { nodes: vec![node_mem] }),\n            }\n        }\n\n        let by_steps: Vec<Vec<Option<NodeMemReq>>> = (0..order.len())\n            .map(|step| {\n                let mem_step: Vec<_> =\n                    partitions.iter().map(|p| p.find_node_alive_at_step(step).cloned()).collect();\n                ensure!(mem_step.len() <= partitions.len());\n                Ok(mem_step)\n            })\n            .collect::<TractResult<Vec<_>>>()?;\n\n        Ok(DeviceMemSchema {\n            model_num_nodes: model.nodes().len(),\n            by_partition: partitions,\n            by_steps,\n            exotic_facts,\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_lifetime_is_disjoint() {\n        let l1 = Lifetime { start: 0, end: 5 };\n        let l2 = Lifetime { start: 5, end: 10 };\n        let l3 = Lifetime { start: 3, end: 7 };\n\n        assert!(l1.is_disjoint(&l2));\n        assert!(l2.is_disjoint(&l1));\n        assert!(!l1.is_disjoint(&l3));\n        assert!(!l3.is_disjoint(&l2));\n    }\n\n    #[test]\n    fn test_lifetime_is_alive_at_step() {\n        let lifetime = Lifetime { start: 5, end: 10 };\n\n        assert!(!lifetime.is_alive_at_step(4));\n        assert!(lifetime.is_alive_at_step(5));\n        assert!(lifetime.is_alive_at_step(7));\n        assert!(lifetime.is_alive_at_step(9));\n        assert!(!lifetime.is_alive_at_step(10));\n    }\n\n    #[test]\n    fn test_empty_lifetime() {\n        let lifetime = Lifetime { start: 5, end: 5 };\n        assert!(lifetime.is_empty());\n        assert_eq!(lifetime.len(), 0);\n    }\n\n    #[test]\n    fn test_node_mem_req_basic() {\n        let outlet_id = OutletId { node: 1, slot: 0 };\n        let req = NodeMemReq {\n            outlet_id,\n            lifetime: Lifetime { start: 0, end: 5 },\n            mem_size: 1000.into(),\n        };\n\n        assert_eq!(req.outlet_id.node, 1);\n        assert_eq!(req.lifetime.start, 0);\n        assert_eq!(req.lifetime.end, 5);\n        assert_eq!(req.mem_size.to_i64().unwrap(), 1000);\n    }\n\n    #[test]\n    fn test_partition_has_no_conflict() {\n        let outlet_id = OutletId { node: 1, slot: 0 };\n        let node1 = NodeMemReq {\n            outlet_id,\n            lifetime: Lifetime { start: 0, end: 5 },\n            mem_size: 1000.into(),\n        };\n\n        let partition = Partition { nodes: vec![node1] };\n\n        assert!(partition.has_no_conflict_with_lifetime(&Lifetime { start: 5, end: 10 }));\n        assert!(!partition.has_no_conflict_with_lifetime(&Lifetime { start: 3, end: 7 }));\n    }\n\n    #[test]\n    fn test_partition_find_node() {\n        let outlet_id = OutletId { node: 1, slot: 0 };\n        let node1 = NodeMemReq {\n            outlet_id,\n            lifetime: Lifetime { start: 0, end: 5 },\n            mem_size: 1000.into(),\n        };\n\n        let outlet_id = OutletId { node: 2, slot: 0 };\n        let node2 = NodeMemReq {\n            outlet_id,\n            lifetime: Lifetime { start: 5, end: 10 },\n            mem_size: 2000.into(),\n        };\n\n        let partition = Partition { nodes: vec![node1.clone(), node2.clone()] };\n\n        assert_eq!(partition.find_node_alive_at_step(3), Some(&node1));\n        assert_eq!(partition.find_node_alive_at_step(7), Some(&node2));\n        assert_eq!(partition.find_node_alive_at_step(10), None);\n    }\n}\n"
  },
  {
    "path": "gpu/src/ops/RECIPE.md",
    "content": "# Recipe: factorizing a GPU op between CUDA and Metal\n\nThis describes the pattern used to factorize the Reduce op. Follow the same\nsteps for other ops (binary, softmax, etc.).\n\n## Before\n\nEach backend had its own copy of:\n- The op enum (e.g. `Reducer` in both `cuda/src/kernels/nn/reduce.rs` and `metal/src/kernels/nn/reduce.rs`)\n- The op wrapper struct (e.g. `CudaReduce` / `MetalReduce`) with identical Op/EvalOp/TypedOp impls\n- A `from_tract_core()` that maps core ops to the backend op\n\nThe only real differences were:\n- How to launch the kernel (cudarc vs Metal command buffers)\n\n## After\n\n### 1. Shared enum in `gpu/src/ops/`\n\nMove the op enum (e.g. `Reducer`) into `gpu/src/ops/reduce.rs` with:\n- Variant definitions\n- `Display` impl (kernel name fragment — e.g. \"sum\", \"prod\", \"mean_of_squares\")\n- `is_supported_dt()`, `is_logic()`, and other predicate methods\n- `from_tract_core()` mapping from `core::ops::nn::Reducer`\n\n### 2. Shared op struct with fn pointer for dispatch\n\n`GpuReduce` in `gpu/src/ops/reduce.rs`:\n- Stores: `axes`, `reducer`, `backend_name: &'static str`, `dispatch: DispatchReduceFn`\n- `DispatchReduceFn = fn(&Reducer, &DeviceTensor, usize, &DeviceTensor) -> TractResult<()>`\n- Implements `Op`, `EvalOp`, `TypedOp` once — shared across backends\n- `eval_with_session` calls `(self.dispatch)(...)` directly\n- `PartialEq`/`Hash` are manual impls that ignore the fn pointer (compare by axes + reducer + backend_name)\n\n### 3. Stream access\n\nThe `gpu` crate has no stream concept. Each backend owns its stream in its\nown thread-local:\n- CUDA: `cuda/src/context.rs` has `CUDA_STREAM` TLS and `with_cuda_stream()`\n- Metal: `metal/src/context.rs` has `METAL_STREAM` TLS and `with_metal_stream()`\n\nDispatch functions access the stream internally — they do not receive it\nas a parameter.\n\n### 4. Backend kernel launch function\n\nIn `cuda/src/kernels/nn/reduce.rs`, the launch function accesses the\nstream via its own TLS:\n\n```rust\npub fn cuda_reduce_launch(\n    reducer: &Reducer,\n    input: &DeviceTensor,\n    axis: usize,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_cuda_stream(|stream| {\n        // ... kernel launch code\n    })\n}\n```\n\n### 5. Wiring in transform.rs\n\nNo per-op wrapper module needed. Transform calls `GpuReduce::from_tract_core`\ndirectly with the backend's launch function:\n\n```rust\n// in can_translate_to_cuda_op:\nGpuReduce::from_tract_core(op, \"Cuda\", cuda_reduce_launch)\n    .is_ok_and(|op| op.reducer.is_supported_dt(input_dts[0]))\n\n// in translate_node:\nBox::new(GpuReduce::from_tract_core(op, \"Cuda\", cuda_reduce_launch)?)\n```\n\n## Checklist for the next op\n\n1. Move enum + predicates + `from_tract_core` + `Display` to `gpu/src/ops/`\n2. Create `GpuXxx` struct with fn pointer dispatch, impl Op/EvalOp/TypedOp\n3. Define `DispatchXxxFn` type alias (no stream parameter)\n4. In each backend kernel file: make launch fn access its own stream TLS internally\n5. In each backend transform.rs: call `GpuXxx::from_tract_core(op, \"Backend\", launch_fn)`\n6. Delete the backend `ops/xxx.rs` wrapper\n"
  },
  {
    "path": "gpu/src/ops/apply_rope.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse derive_new::new;\nuse tract_core::internal::*;\n\npub type DispatchApplyRopeFn =\n    fn(&DeviceTensor, &DeviceTensor, &DeviceTensor, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone, new)]\npub struct GpuApplyRope {\n    pub backend_name: &'static str,\n    pub dispatch: DispatchApplyRopeFn,\n}\n\nimpl std::fmt::Debug for GpuApplyRope {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}ApplyRope\", self.backend_name)\n    }\n}\n\nimpl PartialEq for GpuApplyRope {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name\n    }\n}\nimpl Eq for GpuApplyRope {}\n\nimpl std::hash::Hash for GpuApplyRope {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n    }\n}\n\nimpl Op for GpuApplyRope {\n    fn name(&self) -> StaticName {\n        format!(\"{}ApplyRope\", self.backend_name).into()\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuApplyRope {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let (input_val, cos_val, sin_val) = args_3!(inputs);\n        let input = input_val.to_device_tensor()?;\n        let cos = cos_val.to_device_tensor()?;\n        let sin = sin_val.to_device_tensor()?;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            input.shape(),\n        )?;\n        (self.dispatch)(input, cos, sin, &output)?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuApplyRope {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let dt = facts[0].datum_type;\n            let fact = dt.fact(facts[0].shape.clone());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/binary.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse derive_new::new;\nuse tract_core::internal::*;\nuse tract_core::ops::binary::BinMiniOp;\n\npub type DispatchBinOpFn =\n    fn(&dyn BinMiniOp, &DeviceTensor, &DeviceTensor, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone, new)]\npub struct GpuBinOp {\n    pub mini_op: Box<dyn BinMiniOp>,\n    pub backend_name: &'static str,\n    pub dispatch: DispatchBinOpFn,\n}\n\nimpl std::fmt::Debug for GpuBinOp {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"GpuBinOp({}{:?})\", self.backend_name, self.mini_op)\n    }\n}\n\nimpl PartialEq for GpuBinOp {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name && self.mini_op == other.mini_op\n    }\n}\n\nimpl Eq for GpuBinOp {}\n\nimpl std::hash::Hash for GpuBinOp {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n        self.mini_op.name().hash(state);\n    }\n}\n\nimpl GpuBinOp {\n    fn resolve_output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let (a, b) = (inputs[0], inputs[1]);\n        if a.rank() != b.rank() {\n            bail!(\n                \"Typed ops require rank match. Invalid inputs for {}: {{a: {:?}, b: {:?}}}\",\n                self.name(),\n                a.shape,\n                b.shape\n            );\n        }\n        let out_shape = tract_core::broadcast::multi_broadcast(&[&a.shape, &b.shape])\n            .with_context(|| format!(\"Error while broadcasting {:?} {:?}\", a.shape, b.shape))?;\n        let out_dt = self.mini_op.result_datum_type(a.datum_type, b.datum_type)?;\n        Ok(tvec!(out_dt.fact(out_shape)))\n    }\n}\n\nimpl Op for GpuBinOp {\n    fn name(&self) -> StaticName {\n        format!(\"{}{}\", self.backend_name, self.mini_op.name()).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuBinOp {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let (a_val, b_val) = args_2!(inputs);\n        let a = a_val.to_device_tensor()?;\n        let b = b_val.to_device_tensor()?;\n        let out_shape = tract_core::broadcast::multi_broadcast(&[a.shape(), b.shape()])?;\n        let out_dt = self.mini_op.result_datum_type(a.datum_type(), b.datum_type())?;\n        let output =\n            crate::session_handler::make_tensor_for_node(session, node_id, out_dt, &out_shape)?;\n        if a.len() > 0 && b.len() > 0 {\n            (self.dispatch)(&*self.mini_op, a, b, &output)\n                .with_context(|| format!(\"Error while dispatching eval for {}\", self.name()))?;\n        }\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuBinOp {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| self.resolve_output_facts(facts))\n            .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/broadcast.rs",
    "content": "use crate::tensor::DeviceTensorExt;\nuse crate::utils::compute_broadcast_strides;\nuse tract_core::internal::*;\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct GpuMultiBroadcastTo {\n    pub shape: ShapeFact,\n}\n\nimpl GpuMultiBroadcastTo {\n    pub fn new(shape: ShapeFact) -> Self {\n        Self { shape }\n    }\n}\n\nimpl Op for GpuMultiBroadcastTo {\n    fn name(&self) -> StaticName {\n        \"GpuMultiBroadcastTo\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuMultiBroadcastTo {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n        let shape = self.shape.eval_to_usize(&session.resolved_symbols)?;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            &shape,\n        )?;\n\n        // Pad input shape/strides to output rank for broadcasting\n        let mut input_strides = vec![input.strides()[0]; output.rank() - input.rank()];\n        input_strides.extend(input.strides());\n        let mut input_shape = vec![1usize; output.rank() - input.rank()];\n        input_shape.extend(input.shape());\n        let broadcast_strides: TVec<isize> =\n            compute_broadcast_strides(&input_shape, &input_strides)?;\n\n        let ctx = crate::device::get_context()?;\n        ctx.copy_nd(input, 0, &broadcast_strides, &output, 0, output.shape(), output.strides())?;\n        Ok(tvec![output.into_tensor().into_tvalue()])\n    }\n}\n\nimpl TypedOp for GpuMultiBroadcastTo {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let mut fact = facts[0].datum_type.fact(self.shape.clone());\n            fact.uniform.clone_from(&inputs[0].uniform);\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/cast.rs",
    "content": "use crate::tensor::DeviceTensorExt;\nuse tract_core::internal::*;\n\nuse crate::tensor::DeviceTensor;\n\npub type DispatchCastFn = fn(&DeviceTensor, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone)]\npub struct GpuCast {\n    pub to: DatumType,\n    pub backend_name: &'static str,\n    pub dispatch: DispatchCastFn,\n    pub is_supported_dt: fn(DatumType) -> bool,\n}\n\nimpl GpuCast {\n    pub fn new(\n        to: DatumType,\n        backend_name: &'static str,\n        dispatch: DispatchCastFn,\n        is_supported_dt: fn(DatumType) -> bool,\n    ) -> Option<Self> {\n        is_supported_dt(to).then_some(Self { to, backend_name, dispatch, is_supported_dt })\n    }\n\n    pub fn is_supported_dt(&self, dt: DatumType) -> bool {\n        (self.is_supported_dt)(dt)\n    }\n}\n\nimpl std::fmt::Debug for GpuCast {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}Cast({:?})\", self.backend_name, self.to)\n    }\n}\n\nimpl PartialEq for GpuCast {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name && self.to == other.to\n    }\n}\n\nimpl Eq for GpuCast {}\n\nimpl std::hash::Hash for GpuCast {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n        self.to.hash(state);\n    }\n}\n\nimpl Op for GpuCast {\n    fn name(&self) -> StaticName {\n        format!(\"{}Cast\", self.backend_name).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuCast {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n        if input.datum_type() == self.to {\n            Ok(tvec!(input_value))\n        } else {\n            let output = crate::session_handler::make_tensor_for_node(\n                session,\n                node_id,\n                self.to,\n                input.shape(),\n            )?;\n            (self.dispatch)(input, &output)?;\n            Ok(tvec![output.into_tensor().into_tvalue()])\n        }\n    }\n}\n\nimpl TypedOp for GpuCast {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            Ok(tvec!(self.to.fact(facts[0].shape.clone())))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/change_axes.rs",
    "content": "use crate::tensor::DeviceTensorExt;\nuse tract_core::internal::*;\nuse tract_itertools::Itertools;\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct GpuAxisOp {\n    pub inner: AxisOp,\n}\n\nimpl GpuAxisOp {\n    pub fn new(inner: AxisOp) -> Self {\n        Self { inner }\n    }\n\n    pub fn simplify_axis_op(op: AxisOp, dims: &[TDim]) -> Self {\n        let inner = match op {\n            AxisOp::Move(from, to) if from.abs_diff(to) == 1 => {\n                if [&dims[from], &dims[to]].contains(&&1usize.into()) {\n                    if from < to {\n                        AxisOp::Reshape(\n                            from,\n                            tvec![dims[from].clone(), dims[to].clone()],\n                            tvec![dims[to].clone(), dims[from].clone()],\n                        )\n                    } else {\n                        AxisOp::Reshape(\n                            to,\n                            tvec![dims[to].clone(), dims[from].clone()],\n                            tvec![dims[from].clone(), dims[to].clone()],\n                        )\n                    }\n                } else {\n                    op\n                }\n            }\n            AxisOp::Move(from, to) if dims[from] == TDim::Val(1) => {\n                let (start, end) = if from < to { (from, to) } else { (to, from) };\n                let mut out_dims = dims[start..=end].to_vec();\n\n                if from < to {\n                    let tmp = out_dims.remove(0);\n                    out_dims.push(tmp);\n                } else {\n                    let tmp = out_dims.pop().unwrap();\n                    out_dims.insert(0, tmp);\n                }\n\n                AxisOp::Reshape(start, dims[start..=end].into(), out_dims.into())\n            }\n            _ => op,\n        };\n        Self { inner }\n    }\n\n    pub fn from_tract_core_with_fact(op: AxisOp, fact: &TypedFact) -> Self {\n        let dims = fact.shape.dims();\n        Self::simplify_axis_op(op, dims)\n    }\n}\n\nimpl Op for GpuAxisOp {\n    fn name(&self) -> StaticName {\n        format!(\"Gpu{}\", self.inner.name()).into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        self.inner.info()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuAxisOp {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let tensor = args_1!(inputs).into_tensor();\n        let input = tensor.to_device_tensor()?;\n        let shape = input.shape();\n\n        let simplified = Self::simplify_axis_op(\n            self.inner.clone(),\n            &shape.iter().map(|s| s.into()).collect_vec(),\n        );\n\n        let new_shape = match &simplified.inner {\n            AxisOp::Move(from, to) => {\n                let mut permutation: Vec<usize> = (0..input.rank()).collect();\n                permutation.remove(*from);\n                permutation.insert(*to, *from);\n\n                let out_shape = permute_output_shape(input.shape(), &permutation)?;\n                let output = crate::session_handler::make_tensor_for_node(\n                    session,\n                    node_id,\n                    input.datum_type(),\n                    &out_shape,\n                )?;\n                // Compute permuted input strides\n                let permuted_strides: TVec<isize> =\n                    permutation.iter().map(|&i| input.strides()[i]).collect();\n                let ctx = crate::device::get_context()?;\n                ctx.copy_nd(\n                    input,\n                    0,\n                    &permuted_strides,\n                    &output,\n                    0,\n                    output.shape(),\n                    output.strides(),\n                )?;\n                return Ok(tvec!(output.into_tensor().into_tvalue()));\n            }\n            AxisOp::Reshape(skip, from, to) => {\n                let from = from.iter().map(|d| d.eval(&session.resolved_symbols)).collect();\n                let to = to.iter().map(|d| d.eval(&session.resolved_symbols)).collect();\n                let mut shape: TVec<usize> = input.shape().into();\n                AxisOp::Reshape(*skip, from, to).change_shape_array(&mut shape, false)?;\n                shape\n            }\n            _ => {\n                let mut shape: TVec<usize> = input.shape().into();\n                self.inner.change_shape_array(&mut shape, false)?;\n                shape\n            }\n        };\n\n        // Memcpy path (Reshape/Add/Rm) — flat copy, treat as 1D\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            &new_shape,\n        )?;\n        let flat_len = input.len();\n        let ctx = crate::device::get_context()?;\n        ctx.copy_nd(input, 0, &[1], &output, 0, &[flat_len], &[1])?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuAxisOp {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| self.inner.output_facts(facts))\n            .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        let ref_inputs = crate::utils::get_device_facts(inputs, |facts| Ok(facts.to_vec()))?;\n        let ref_outputs = crate::utils::get_device_facts(outputs, |facts| Ok(facts.to_vec()))?;\n        self.inner.axes_mapping(&ref_inputs, &ref_outputs)\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let inner = if let AxisOp::Reshape(axis, from, to) = &self.inner {\n            AxisOp::Reshape(\n                *axis,\n                from.iter().map(|d| d.eval(values)).collect(),\n                to.iter().map(|d| d.eval(values)).collect(),\n            )\n        } else {\n            self.inner.clone()\n        };\n        let op = GpuAxisOp { inner };\n        target.wire_node(&node.name, op, &[mapping[&node.inputs[0]]])\n    }\n\n    as_op!();\n}\n\npub fn permute_output_shape(shape: &[usize], permutation: &[usize]) -> TractResult<TVec<usize>> {\n    ensure!(shape.len() == permutation.len());\n    Ok(permutation.iter().map(|&i| shape[i]).collect())\n}\n"
  },
  {
    "path": "gpu/src/ops/concat.rs",
    "content": "use crate::tensor::DeviceTensorExt;\nuse tract_core::internal::*;\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct GpuConcat {\n    pub axis: usize,\n}\n\nimpl GpuConcat {\n    pub fn new(axis: usize) -> Self {\n        Self { axis }\n    }\n\n    pub fn offsets(&self, inputs: &[&TypedFact]) -> TractResult<Vec<TDim>> {\n        let mut offsets = vec![0.to_dim()];\n        for slice in inputs {\n            let len = slice.shape[self.axis].clone();\n            let offset = len + offsets.last().unwrap();\n            offsets.push(offset)\n        }\n        Ok(offsets)\n    }\n}\n\nimpl Op for GpuConcat {\n    fn name(&self) -> StaticName {\n        \"GpuConcat\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis: {}\", self.axis)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuConcat {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let inputs =\n            inputs.iter().map(|it| it.to_device_tensor()).collect::<TractResult<TVec<_>>>()?;\n\n        let mut output_shape = inputs[0].shape().to_vec();\n        output_shape[self.axis] = inputs.iter().map(|it| it.shape()[self.axis]).sum();\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            inputs[0].datum_type(),\n            &output_shape,\n        )?;\n\n        let ctx = crate::device::get_context()?;\n        let mut cursor = 0usize;\n        for input in &inputs {\n            let slice_len = input.shape()[self.axis];\n            if slice_len == 0 {\n                continue;\n            }\n            // Build zone shape (same as input shape for this slice)\n            let zone_shape = input.shape();\n            // Output offset along concat axis\n            let dst_offset =\n                cursor * output.strides()[self.axis] as usize * output.datum_type().size_of();\n\n            ctx.copy_nd(\n                input,\n                0,\n                input.strides(),\n                &output,\n                dst_offset,\n                zone_shape,\n                output.strides(),\n            )\n            .with_context(|| {\n                format!(\n                    \"Error in concat dispatch for slice at offset {} (shape {:?})\",\n                    cursor, zone_shape\n                )\n            })?;\n            cursor += slice_len;\n        }\n\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuConcat {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let mut fact = facts[0].without_value();\n            for input in facts {\n                if input.rank() != fact.rank()\n                    || input\n                        .shape\n                        .iter()\n                        .zip(fact.shape.iter())\n                        .enumerate()\n                        .filter(|(ax, _)| *ax != self.axis)\n                        .any(|(_, (i, f))| i != f)\n                {\n                    bail!(\"Inconsistent {:?} inputs: {:?}\", self, facts);\n                }\n            }\n            fact.shape.set(self.axis, self.offsets(facts)?.pop().unwrap());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/copy_based.rs",
    "content": "//! Translators for ops that only need the generic copy_nd dispatch.\n//! These are fully backend-agnostic and can be constructed without\n//! any backend-specific arguments.\n\nuse tract_core::internal::*;\nuse tract_core::ops::array::{MultiBroadcastTo, Slice, TypedConcat};\nuse tract_pulse_opl::ops::{Delay, PulsePad};\nuse tract_transformers::ops::dyn_kv_cache::DynKeyValueCache;\n\n/// Try to translate a node into a copy-based GPU op.\n/// Returns `Some(gpu_op)` if the node is one of the 7 copy-based ops.\npub fn try_make_copy_based_op(\n    source: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<Box<dyn TypedOp>>> {\n    if let Some(op) = node.op_as::<MultiBroadcastTo>() {\n        return Ok(Some(Box::new(super::broadcast::GpuMultiBroadcastTo::new(op.shape.clone()))));\n    }\n    if let Some(op) = node.op_as::<AxisOp>() {\n        let in_fact = source.node_input_facts(node.id)?[0];\n        return Ok(Some(Box::new(super::change_axes::GpuAxisOp::from_tract_core_with_fact(\n            op.clone(),\n            in_fact,\n        ))));\n    }\n    if let Some(op) = node.op_as::<Slice>() {\n        return Ok(Some(Box::new(super::slice::GpuSlice::new(op.clone()))));\n    }\n    if let Some(op) = node.op_as::<TypedConcat>() {\n        return Ok(Some(Box::new(super::concat::GpuConcat::new(op.axis))));\n    }\n    if let Some(op) = node.op_as::<DynKeyValueCache>() {\n        return Ok(Some(Box::new(super::dyn_kv_cache::GpuDynKVCache::from_tract_transformers(op))));\n    }\n    if let Some(op) = node.op_as::<Delay>() {\n        return Ok(Some(Box::new(super::pulse::GpuDelay::new(op))));\n    }\n    if let Some(op) = node.op_as::<PulsePad>() {\n        return Ok(Some(Box::new(super::pulse::GpuPulsePad::new(op)?)));\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "gpu/src/ops/dyn_kv_cache.rs",
    "content": "use crate::fact::DeviceTypedFactExt;\nuse crate::tensor::{DeviceTensor, DeviceTensorExt, IntoDevice};\nuse derive_new::new;\nuse tract_core::internal::*;\nuse tract_core::ops::OpStateFreeze;\nuse tract_transformers::ops::dyn_kv_cache::{DynKeyValueCache, DynKeyValueCacheState};\n\n#[derive(Debug, Clone, new)]\npub struct GpuDynKVCacheState {\n    node_id: usize,\n    name: String,\n    axis: usize,\n    past_sequence_fact: TypedFact,\n    kv_cache: Option<TValue>,\n}\n\nimpl OpState for GpuDynKVCacheState {\n    fn load_from(\n        &mut self,\n        state: &mut TurnState,\n        states: &mut dyn Iterator<Item = TValue>,\n    ) -> TractResult<()> {\n        let kv_cache = states.next().context(\"Not enough state initializers\")?;\n        DynKeyValueCacheState::resolve_symbols(\n            state,\n            self.past_sequence_fact.clone(),\n            Some(kv_cache.shape()),\n        )?;\n        self.kv_cache = Some(kv_cache.into_tensor().into_device()?.into_tensor().into_tvalue());\n        Ok(())\n    }\n\n    fn save_to(&self, states: &mut Vec<TValue>) -> TractResult<()> {\n        if let Some(kv_cache) = &self.kv_cache {\n            states.push(kv_cache.to_device_tensor()?.to_host()?.into_tensor().into_tvalue());\n            Ok(())\n        } else {\n            bail!(\"KV cache {} was never initialized\", self.name)\n        }\n    }\n\n    fn init_tensor_fact(&self) -> Option<(String, TypedFact)> {\n        Some((self.name.clone(), self.past_sequence_fact.clone()))\n    }\n\n    fn resolve_symbols(&mut self, state: &mut TurnState) -> TractResult<()> {\n        let shape = self\n            .kv_cache\n            .as_ref()\n            .map(|kv_cache| kv_cache.to_device_tensor().expect(\"Expected GPU Tensor\").shape());\n        DynKeyValueCacheState::resolve_symbols(state, self.past_sequence_fact.clone(), shape)\n    }\n\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        ensure!(inputs.len() == 1);\n        let mut op_inputs = TVec::new();\n\n        if let Some(kv_cache) = self.kv_cache.take() {\n            op_inputs.push(kv_cache);\n        }\n\n        op_inputs.push(inputs.into_iter().next().unwrap());\n\n        let gpu_op =\n            op.downcast_ref::<GpuDynKVCache>().ok_or_else(|| format_err!(\"Wrong Op type\"))?;\n        let axis = gpu_op.axis;\n\n        let inputs =\n            op_inputs.iter().map(|it| it.to_device_tensor()).collect::<TractResult<TVec<_>>>()?;\n        let mut output_shape = inputs[0].shape().to_vec();\n        output_shape[axis] = inputs.iter().map(|it| it.shape()[axis]).sum();\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            self.node_id,\n            inputs[0].datum_type(),\n            &output_shape,\n        )?;\n\n        // Concat inputs into output\n        let ctx = crate::device::get_context()?;\n        let mut cursor = 0usize;\n        for input in &inputs {\n            let slice_len = input.shape()[axis];\n            if slice_len == 0 {\n                continue;\n            }\n            let dst_offset =\n                cursor * output.strides()[axis] as usize * output.datum_type().size_of();\n            ctx.copy_nd(\n                input,\n                0,\n                input.strides(),\n                &output,\n                dst_offset,\n                input.shape(),\n                output.strides(),\n            )?;\n            cursor += slice_len;\n        }\n\n        let res = output.into_tensor().into_tvalue();\n        self.kv_cache = Some(res.clone());\n        Ok(tvec!(res))\n    }\n}\n\nimpl GpuDynKVCacheState {\n    pub fn truncate(&mut self, len: usize) -> TractResult<()> {\n        if let Some(v) = &mut self.kv_cache {\n            let mut t: Tensor = v.to_device_tensor()?.to_host()?.into_tensor();\n            t = t.slice(self.axis, 0, len)?;\n            *v = t.into_device()?.into_tensor().into_tvalue();\n        }\n        Ok(())\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct FrozenGpuDynKVCacheState {\n    node_id: usize,\n    name: String,\n    axis: usize,\n    past_sequence_fact: TypedFact,\n    kv_cache: Option<DeviceTensor>,\n}\n\nimpl OpStateFreeze for GpuDynKVCacheState {\n    fn freeze(&self) -> Box<dyn FrozenOpState + 'static> {\n        Box::new(FrozenGpuDynKVCacheState {\n            node_id: self.node_id,\n            name: self.name.clone(),\n            axis: self.axis,\n            past_sequence_fact: self.past_sequence_fact.clone(),\n            kv_cache: self.kv_cache.clone().map(|t| t.to_device_tensor().cloned().unwrap()),\n        })\n    }\n}\n\nimpl FrozenOpState for FrozenGpuDynKVCacheState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(GpuDynKVCacheState {\n            node_id: self.node_id,\n            name: self.name.clone(),\n            axis: self.axis,\n            past_sequence_fact: self.past_sequence_fact.clone(),\n            kv_cache: self.kv_cache.clone().map(|t| t.into_tensor().into_tvalue()),\n        })\n    }\n}\n\n#[derive(Clone)]\npub struct GpuDynKVCache {\n    pub name: String,\n    pub past_sequence_fact: TypedFact,\n    pub input_sequence_fact: TypedFact,\n    pub axis: usize,\n}\n\nimpl GpuDynKVCache {\n    pub fn from_tract_transformers(op: &DynKeyValueCache) -> Self {\n        Self {\n            name: op.name.clone(),\n            axis: op.axis,\n            past_sequence_fact: op.past_sequence_fact.clone(),\n            input_sequence_fact: op.input_sequence_fact.clone(),\n        }\n    }\n}\n\nimpl std::fmt::Debug for GpuDynKVCache {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"GpuDynKVCache({}, axis={})\", self.name, self.axis)\n    }\n}\n\nimpl PartialEq for GpuDynKVCache {\n    fn eq(&self, other: &Self) -> bool {\n        self.name == other.name\n            && self.axis == other.axis\n            && self.past_sequence_fact == other.past_sequence_fact\n            && self.input_sequence_fact == other.input_sequence_fact\n    }\n}\n\nimpl Eq for GpuDynKVCache {}\n\nimpl std::hash::Hash for GpuDynKVCache {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.name.hash(state);\n        self.axis.hash(state);\n    }\n}\n\nimpl Op for GpuDynKVCache {\n    fn name(&self) -> StaticName {\n        \"GpuDynKVCache\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis: {}\", self.axis)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuDynKVCache {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(&self, _session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(GpuDynKVCacheState::new(\n            node_id,\n            self.name.clone(),\n            self.axis,\n            self.past_sequence_fact.clone(),\n            None,\n        ))))\n    }\n}\n\nimpl TypedOp for GpuDynKVCache {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() == 1);\n        let mut facts = crate::utils::facts_to_device_facts(inputs, |facts| {\n            let mut fact = facts[0].without_value();\n            fact.shape.set(\n                self.axis,\n                self.past_sequence_fact.shape.dims()[self.axis].clone()\n                    + self.input_sequence_fact.shape.dims()[self.axis].clone(),\n            );\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))?;\n        facts[0].as_device_fact_mut().unwrap().state_owned = true;\n        Ok(facts)\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/element_wise.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse derive_new::new;\nuse tract_core::internal::*;\nuse tract_core::ops::element_wise::ElementWiseMiniOp;\n\npub type DispatchElementWiseFn =\n    fn(&dyn ElementWiseMiniOp, &DeviceTensor, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone, new)]\npub struct GpuElementWise {\n    pub mini_op: Box<dyn ElementWiseMiniOp>,\n    pub backend_name: &'static str,\n    pub dispatch: DispatchElementWiseFn,\n}\n\nimpl std::fmt::Debug for GpuElementWise {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"GpuElementWise({}{:?})\", self.backend_name, self.mini_op)\n    }\n}\n\nimpl PartialEq for GpuElementWise {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name && self.mini_op == other.mini_op\n    }\n}\n\nimpl Eq for GpuElementWise {}\n\nimpl std::hash::Hash for GpuElementWise {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n        self.mini_op.name().hash(state);\n    }\n}\n\nimpl Op for GpuElementWise {\n    fn name(&self) -> StaticName {\n        format!(\"{}{}\", self.backend_name, self.mini_op.name()).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuElementWise {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            input.shape(),\n        )?;\n        (self.dispatch)(&*self.mini_op, input, &output)\n            .with_context(|| format!(\"Error while dispatching eval for {}\", self.name()))?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuElementWise {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let dt = facts[0].datum_type;\n            let fact = dt.fact(facts[0].shape.clone());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/gelu_approximate.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse derive_new::new;\nuse tract_core::internal::*;\n\npub type DispatchGeluApproximateFn = fn(bool, &DeviceTensor, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone, new)]\npub struct GpuGeluApproximate {\n    pub fast_impl: bool,\n    pub backend_name: &'static str,\n    pub dispatch: DispatchGeluApproximateFn,\n}\n\nimpl std::fmt::Debug for GpuGeluApproximate {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}GeluApproximate(fast_impl: {})\", self.backend_name, self.fast_impl)\n    }\n}\n\nimpl PartialEq for GpuGeluApproximate {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name && self.fast_impl == other.fast_impl\n    }\n}\n\nimpl Eq for GpuGeluApproximate {}\n\nimpl std::hash::Hash for GpuGeluApproximate {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n        self.fast_impl.hash(state);\n    }\n}\n\nimpl Op for GpuGeluApproximate {\n    fn name(&self) -> StaticName {\n        format!(\"{}GeluApproximate\", self.backend_name).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuGeluApproximate {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            input.shape(),\n        )?;\n        (self.dispatch)(self.fast_impl, input, &output)?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuGeluApproximate {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let dt = facts[0].datum_type;\n            let fact = dt.fact(facts[0].shape.clone());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/iff.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse derive_new::new;\nuse tract_core::broadcast::multi_broadcast;\nuse tract_core::internal::*;\n\nstatic IFF_MAX_RANK: usize = 5;\n\n/// Dispatch function for the iff (select) kernel.\n/// Args: cond, then, else tensors with pre-computed broadcast strides,\n/// output tensor, output shape and strides. All strides are padded to IFF_MAX_RANK.\npub type DispatchIffFn = fn(\n    cond: &DeviceTensor,\n    then_value: &DeviceTensor,\n    else_value: &DeviceTensor,\n    cond_strides: &[isize],\n    then_strides: &[isize],\n    else_strides: &[isize],\n    output: &DeviceTensor,\n    output_shape: &[usize],\n    output_strides: &[isize],\n) -> TractResult<()>;\n\n#[derive(Clone, new)]\npub struct GpuIff {\n    pub backend_name: &'static str,\n    pub dispatch: DispatchIffFn,\n}\n\nimpl std::fmt::Debug for GpuIff {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}Iff\", self.backend_name)\n    }\n}\n\nimpl PartialEq for GpuIff {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name\n    }\n}\n\nimpl Eq for GpuIff {}\n\nimpl std::hash::Hash for GpuIff {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n    }\n}\n\nimpl Op for GpuIff {\n    fn name(&self) -> StaticName {\n        format!(\"{}Iff\", self.backend_name).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuIff {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let (cond_val, then_val, else_val) = args_3!(inputs);\n\n        let cond = cond_val.to_device_tensor()?;\n        let then_t = then_val.to_device_tensor()?;\n        let else_t = else_val.to_device_tensor()?;\n        ensure!(cond.rank() == then_t.rank());\n        ensure!(cond.rank() == else_t.rank());\n        ensure!(then_t.datum_type() == else_t.datum_type());\n\n        let out_shape = multi_broadcast(&[cond.shape(), then_t.shape(), else_t.shape()])\n            .context(\"No broadcasting solution found\")?;\n        let out_dt = then_t.datum_type();\n        let output =\n            crate::session_handler::make_tensor_for_node(session, node_id, out_dt, &out_shape)?;\n\n        if output.len() > 0 {\n            let rank = cond.rank();\n            ensure!(rank <= IFF_MAX_RANK);\n            let rank_pad = IFF_MAX_RANK - rank;\n\n            let mut padded_cond_strides = [0isize; IFF_MAX_RANK];\n            let mut padded_then_strides = [0isize; IFF_MAX_RANK];\n            let mut padded_else_strides = [0isize; IFF_MAX_RANK];\n            let mut padded_out_shape = [1usize; IFF_MAX_RANK];\n            let mut padded_out_strides = [0isize; IFF_MAX_RANK];\n\n            for axis in 0..rank {\n                padded_out_shape[rank_pad + axis] = output.shape()[axis];\n                padded_out_strides[rank_pad + axis] = output.strides()[axis];\n                padded_cond_strides[rank_pad + axis] = if cond.shape()[axis] < output.shape()[axis]\n                {\n                    0\n                } else {\n                    cond.strides()[axis]\n                };\n                padded_then_strides[rank_pad + axis] =\n                    if then_t.shape()[axis] < output.shape()[axis] {\n                        0\n                    } else {\n                        then_t.strides()[axis]\n                    };\n                padded_else_strides[rank_pad + axis] =\n                    if else_t.shape()[axis] < output.shape()[axis] {\n                        0\n                    } else {\n                        else_t.strides()[axis]\n                    };\n            }\n\n            (self.dispatch)(\n                cond,\n                then_t,\n                else_t,\n                &padded_cond_strides,\n                &padded_then_strides,\n                &padded_else_strides,\n                &output,\n                &padded_out_shape,\n                &padded_out_strides,\n            )\n            .with_context(|| \"Error while dispatching eval for Iff\")?;\n        }\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuIff {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |inputs| {\n            let out_shape =\n                multi_broadcast(&[&*inputs[0].shape, &*inputs[1].shape, &*inputs[2].shape])\n                    .context(\"No broadcasting solution found\")?;\n            let out_dt = inputs[1].datum_type;\n            Ok(tvec!(out_dt.fact(out_shape)))\n        })\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/leaky_relu.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse derive_new::new;\nuse tract_core::internal::*;\n\npub type DispatchLeakyReluFn = fn(f32, &DeviceTensor, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone, new)]\npub struct GpuLeakyRelu {\n    pub alpha: f32,\n    pub backend_name: &'static str,\n    pub dispatch: DispatchLeakyReluFn,\n}\n\nimpl std::fmt::Debug for GpuLeakyRelu {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}LeakyRelu(alpha: {})\", self.backend_name, self.alpha)\n    }\n}\n\nimpl PartialEq for GpuLeakyRelu {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name && self.alpha == other.alpha\n    }\n}\n\nimpl Eq for GpuLeakyRelu {}\n\nimpl std::hash::Hash for GpuLeakyRelu {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n        self.alpha.to_bits().hash(state);\n    }\n}\n\nimpl Op for GpuLeakyRelu {\n    fn name(&self) -> StaticName {\n        format!(\"{}LeakyRelu\", self.backend_name).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuLeakyRelu {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            input.shape(),\n        )?;\n        (self.dispatch)(self.alpha, input, &output)?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuLeakyRelu {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let dt = facts[0].datum_type;\n            let fact = dt.fact(facts[0].shape.clone());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/mod.rs",
    "content": "pub mod apply_rope;\npub mod binary;\npub mod broadcast;\npub mod cast;\npub mod change_axes;\npub mod concat;\npub mod copy_based;\npub mod dyn_kv_cache;\npub mod element_wise;\npub mod gelu_approximate;\npub mod iff;\npub mod leaky_relu;\npub mod pulse;\npub mod reduce;\npub mod rms_norm;\npub mod rotate_half;\npub mod scaled_masked_softmax;\npub mod slice;\npub mod softmax;\n"
  },
  {
    "path": "gpu/src/ops/pulse.rs",
    "content": "#![allow(unpredictable_function_pointer_comparisons)]\nuse crate::device::{DeviceContext, get_context};\nuse crate::session_handler::make_tensor_for_node;\nuse crate::tensor::{DeviceTensor, DeviceTensorExt, IntoDevice};\nuse std::ops::Range;\nuse tract_core::internal::*;\nuse tract_core::ops::array::PadMode;\nuse tract_core::trivial_op_state_freeze;\nuse tract_pulse_opl::ops::{Delay, PulsePad};\n\n// ─── GpuDelay ────────────────────────────────────────────────────────────────\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct GpuDelay {\n    pub inner: Delay,\n}\n\nimpl GpuDelay {\n    pub fn new(inner: &Delay) -> Self {\n        Self { inner: inner.clone() }\n    }\n}\n\nimpl Op for GpuDelay {\n    fn name(&self) -> StaticName {\n        \"GpuDelay\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        self.inner.info()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuDelay {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(&self, _session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(GpuDelayState { node_id, buffer: None })))\n    }\n}\n\nimpl TypedOp for GpuDelay {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| self.inner.output_facts(facts))\n            .with_context(|| format!(\"Error while computing output facts for {}\", self.name()))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        crate::utils::get_device_facts(inputs, |facts| self.inner.cost(facts))\n    }\n\n    as_op!();\n}\n\n#[derive(Debug, Clone)]\npub struct GpuDelayState {\n    pub node_id: usize,\n    pub buffer: Option<DeviceTensor>,\n}\n\nimpl GpuDelayState {\n    unsafe fn apply_delay_unchecked(\n        &mut self,\n        ctx: &dyn DeviceContext,\n        op: &Delay,\n        input: &DeviceTensor,\n        output: &mut DeviceTensor,\n    ) -> TractResult<()> {\n        let buffered = op.delay + op.overlap;\n        let input_pulse = input.shape()[op.axis];\n        let output_pulse = input_pulse + op.overlap;\n        let buffer = self.buffer.as_mut().unwrap();\n\n        let from_input = input_pulse.saturating_sub(op.delay);\n        let from_buffer = output_pulse.saturating_sub(from_input);\n\n        // Copy from buffer to output\n        ctx.assign_slice(output, 0..from_buffer, buffer, 0..from_buffer, op.axis)?;\n        // Copy from input to output\n        ctx.assign_slice(output, from_buffer..output_pulse, input, 0..from_input, op.axis)?;\n\n        // Maintain buffer\n        if buffered < input_pulse {\n            ctx.assign_slice(\n                buffer,\n                0..buffered,\n                input,\n                (input_pulse - buffered)..input_pulse,\n                op.axis,\n            )?;\n        } else {\n            // Shift buffer left by input_pulse elements\n            let dt = input.datum_type();\n            let shift_bytes = buffer.strides()[op.axis] as usize * dt.size_of() * input_pulse;\n            let remaining = buffer.len() * dt.size_of() - shift_bytes;\n            ctx.flat_copy(buffer, shift_bytes, buffer, 0, remaining)?;\n            // Copy input to end of buffer\n            ctx.assign_slice(\n                buffer,\n                (buffered - input_pulse)..buffered,\n                input,\n                0..input_pulse,\n                op.axis,\n            )?;\n        }\n        Ok(())\n    }\n}\n\nimpl OpState for GpuDelayState {\n    fn eval(\n        &mut self,\n        state: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let op = &op.downcast_ref::<GpuDelay>().ok_or_else(|| format_err!(\"Wrong Op type\"))?.inner;\n        let buffered = op.delay + op.overlap;\n        let device_input = input.as_device_tensor().context(\"Expected a GPU tensor\")?;\n        let input_pulse = device_input.shape()[op.axis];\n        let output_pulse = input_pulse + op.overlap;\n        let mut output_shape: TVec<usize> = device_input.shape().into();\n        output_shape[op.axis] = output_pulse;\n        let dt = device_input.datum_type();\n        let ctx = get_context()?;\n        unsafe {\n            if self.buffer.is_none() {\n                let mut shape = device_input.shape().to_owned();\n                shape[op.axis] = buffered;\n                self.buffer = Some(DeviceTensor::uninitialized_dt(dt, &shape)?);\n            };\n            let mut output = make_tensor_for_node(state, self.node_id, dt, &output_shape)?;\n            self.apply_delay_unchecked(&*ctx, op, device_input, &mut output)?;\n            Ok(tvec!(output.into_tensor().into()))\n        }\n    }\n}\n\ntrivial_op_state_freeze!(GpuDelayState);\n\n// ─── GpuPulsePad ─────────────────────────────────────────────────────────────\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct GpuPulsePad {\n    pub op: PulsePad,\n    pub device_cst: Option<DeviceTensor>,\n}\n\nimpl GpuPulsePad {\n    pub fn new(op: &PulsePad) -> TractResult<Self> {\n        let device_cst =\n            if let PadMode::Constant(c) = &op.mode { Some(c.clone().into_device()?) } else { None };\n        Ok(Self { op: op.clone(), device_cst })\n    }\n}\n\nimpl std::hash::Hash for GpuPulsePad {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.op.hash(state);\n    }\n}\n\nimpl Op for GpuPulsePad {\n    fn name(&self) -> StaticName {\n        \"GpuPulsePad\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        self.op.info()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuPulsePad {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(&self, _session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(GpuPulsePadState { node_id, current_pos: 0, last_valid_frame: None })))\n    }\n}\n\nimpl TypedOp for GpuPulsePad {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| self.op.output_facts(facts))\n            .with_context(|| format!(\"Error while computing output facts for {}\", self.name()))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        crate::utils::get_device_facts(inputs, |facts| self.op.cost(facts))\n    }\n\n    as_op!();\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct GpuPulsePadState {\n    node_id: usize,\n    current_pos: usize,\n    last_valid_frame: Option<DeviceTensor>,\n}\n\nfn fill_slice_constant(\n    ctx: &dyn DeviceContext,\n    dst: &mut DeviceTensor,\n    cst: &DeviceTensor,\n    axis: usize,\n    range: Range<usize>,\n) -> TractResult<()> {\n    let mut zone_shape: TVec<usize> = dst.shape().into();\n    zone_shape[axis] = range.len();\n    let mut dst_origin = tvec!(0; dst.rank());\n    dst_origin[axis] = range.start;\n    ctx.copy_with_origins(\n        &zone_shape,\n        dst,\n        &dst_origin,\n        dst.strides(),\n        cst,\n        &tvec!(0; dst.rank()),\n        &tvec!(0; dst.rank()),\n    )\n}\n\nfn fill_slice_repeating_one_frame(\n    ctx: &dyn DeviceContext,\n    dst: &mut DeviceTensor,\n    src: &DeviceTensor,\n    axis: usize,\n    dst_range: Range<usize>,\n    src_frame: usize,\n) -> TractResult<()> {\n    let mut zone_shape: TVec<usize> = dst.shape().into();\n    zone_shape[axis] = dst_range.len();\n    let mut dst_origin = tvec!(0; dst.rank());\n    dst_origin[axis] = dst_range.start;\n    let mut src_origin = tvec!(0; src.rank());\n    src_origin[axis] = src_frame;\n    let mut src_strides: TVec<isize> = src.strides().into();\n    src_strides[axis] = 0;\n    ctx.copy_with_origins(\n        &zone_shape,\n        dst,\n        &dst_origin,\n        dst.strides(),\n        src,\n        &src_origin,\n        &src_strides,\n    )\n}\n\nimpl GpuPulsePadState {\n    fn save_frame(\n        &mut self,\n        ctx: &dyn DeviceContext,\n        op: &PulsePad,\n        input: &DeviceTensor,\n        frame: usize,\n    ) -> TractResult<()> {\n        let mut frame_shape: TVec<usize> = input.shape().into();\n        frame_shape[op.axis] = 1;\n        let last_valid_frame = DeviceTensor::uninitialized_dt(input.datum_type(), &frame_shape)?;\n        ctx.assign_slice(&last_valid_frame, 0..1, input, frame..frame + 1, op.axis)?;\n        self.last_valid_frame = Some(last_valid_frame);\n        Ok(())\n    }\n\n    fn pad(\n        &mut self,\n        session: &TurnState,\n        gpu_op: &GpuPulsePad,\n        input: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        let ctx = get_context()?;\n        let op = &gpu_op.op;\n        let pulse = input.shape()[op.axis];\n        let pulse_begin = self.current_pos;\n        let pulse_end = self.current_pos + pulse;\n        self.current_pos += pulse - op.overlap;\n        let end_input =\n            op.end_input.eval(&session.resolved_symbols).to_usize().unwrap_or(usize::MAX);\n        let after = op.after.eval(&session.resolved_symbols).to_usize().unwrap_or(usize::MAX);\n\n        if let PadMode::Edge = op.mode\n            && after != 0\n            && pulse_begin < end_input\n        {\n            let latest_valid_frame = (end_input - pulse_begin).min(pulse) - 1;\n            self.save_frame(&*ctx, op, input, latest_valid_frame)?;\n        }\n\n        // Start with a copy of input\n        let mut output =\n            make_tensor_for_node(session, self.node_id, input.datum_type(), input.shape())?;\n        let flat_len = input.len() * input.datum_type().size_of();\n        ctx.flat_copy(input, 0, &output, 0, flat_len)?;\n\n        // Quick return if entirely in valid or invalid range\n        if (pulse_begin >= op.begin_input && pulse_end <= end_input)\n            || (pulse_end <= op.begin_input - op.before\n                || pulse_begin >= end_input.saturating_add(after))\n        {\n            return Ok(output);\n        }\n\n        if pulse_begin < op.begin_input {\n            let fill_up_to = (op.begin_input - pulse_begin).min(pulse);\n            match &op.mode {\n                PadMode::Constant(_) => fill_slice_constant(\n                    &*ctx,\n                    &mut output,\n                    gpu_op.device_cst.as_ref().unwrap(),\n                    op.axis,\n                    0..fill_up_to,\n                )?,\n                PadMode::Edge => fill_slice_repeating_one_frame(\n                    &*ctx,\n                    &mut output,\n                    input,\n                    op.axis,\n                    0..fill_up_to,\n                    fill_up_to,\n                )?,\n                _ => unimplemented!(),\n            }\n        }\n\n        if pulse_end > end_input {\n            let fill_from = pulse - (pulse_end - end_input).min(pulse);\n            match &op.mode {\n                PadMode::Constant(_) => fill_slice_constant(\n                    &*ctx,\n                    &mut output,\n                    gpu_op.device_cst.as_ref().unwrap(),\n                    op.axis,\n                    fill_from..pulse,\n                )?,\n                PadMode::Edge => fill_slice_repeating_one_frame(\n                    &*ctx,\n                    &mut output,\n                    self.last_valid_frame.as_ref().unwrap(),\n                    op.axis,\n                    fill_from..pulse,\n                    0,\n                )?,\n                _ => unimplemented!(),\n            }\n        }\n        Ok(output)\n    }\n}\n\nimpl OpState for GpuPulsePadState {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let gpu_op =\n            op.downcast_ref::<GpuPulsePad>().ok_or_else(|| format_err!(\"Wrong Op type\"))?;\n        let device_input = input.as_device_tensor().context(\"Expected a GPU tensor\")?;\n        let output = self.pad(session, gpu_op, device_input)?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\ntrivial_op_state_freeze!(GpuPulsePadState);\n"
  },
  {
    "path": "gpu/src/ops/reduce.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_core::ops::nn as core_ops_nn;\nuse tract_itertools::Itertools;\n\npub type DispatchReduceFn = fn(&Reducer, &DeviceTensor, usize, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum Reducer {\n    MeanOfSquares,\n    Sum,\n    Prod,\n    Min,\n    Max,\n    All,\n    Any,\n}\n\nimpl fmt::Display for Reducer {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            Self::MeanOfSquares => write!(f, \"mean_of_squares\"),\n            Self::Sum => write!(f, \"sum\"),\n            Self::Prod => write!(f, \"prod\"),\n            Self::Min => write!(f, \"min\"),\n            Self::Max => write!(f, \"max\"),\n            Self::All => write!(f, \"all\"),\n            Self::Any => write!(f, \"any\"),\n        }\n    }\n}\n\nimpl Reducer {\n    pub const ALL: [Reducer; 7] =\n        [Self::MeanOfSquares, Self::Sum, Self::Prod, Self::Min, Self::Max, Self::All, Self::Any];\n\n    pub fn is_logic(&self) -> bool {\n        *self == Reducer::All || *self == Reducer::Any\n    }\n\n    pub fn is_supported_dt(&self, dt: DatumType) -> bool {\n        if self.is_logic() { dt.is::<bool>() } else { dt.is::<f32>() || dt.is::<f16>() }\n    }\n\n    pub fn from_tract_core(reducer: &core_ops_nn::Reducer) -> TractResult<Self> {\n        match reducer {\n            core_ops_nn::Reducer::Sum => Ok(Reducer::Sum),\n            core_ops_nn::Reducer::MeanOfSquares => Ok(Reducer::MeanOfSquares),\n            core_ops_nn::Reducer::Prod => Ok(Reducer::Prod),\n            core_ops_nn::Reducer::Min => Ok(Reducer::Min),\n            core_ops_nn::Reducer::Max => Ok(Reducer::Max),\n            core_ops_nn::Reducer::All => Ok(Reducer::All),\n            core_ops_nn::Reducer::Any => Ok(Reducer::Any),\n            _ => bail!(\"Unsupported reducer {:?} on GPU\", reducer),\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct GpuReduce {\n    pub axes: TVec<usize>,\n    pub reducer: Reducer,\n    pub backend_name: &'static str,\n    pub dispatch: DispatchReduceFn,\n}\n\nimpl PartialEq for GpuReduce {\n    fn eq(&self, other: &Self) -> bool {\n        self.axes == other.axes\n            && self.reducer == other.reducer\n            && self.backend_name == other.backend_name\n    }\n}\n\nimpl Eq for GpuReduce {}\n\nimpl std::hash::Hash for GpuReduce {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.axes.hash(state);\n        self.reducer.hash(state);\n        self.backend_name.hash(state);\n    }\n}\n\nimpl GpuReduce {\n    pub fn new(\n        axes: TVec<usize>,\n        reducer: Reducer,\n        backend_name: &'static str,\n        dispatch: DispatchReduceFn,\n    ) -> TractResult<Self> {\n        ensure!(axes.len() == 1, \"Only one axis of reduce is supported by {backend_name}Reduce\");\n        Ok(Self { axes, reducer, backend_name, dispatch })\n    }\n\n    pub fn from_tract_core(\n        core_reduce: &core_ops_nn::Reduce,\n        backend_name: &'static str,\n        dispatch: DispatchReduceFn,\n    ) -> TractResult<Self> {\n        let reducer = Reducer::from_tract_core(&core_reduce.reducer)?;\n        Self::new(core_reduce.axes.clone(), reducer, backend_name, dispatch)\n    }\n}\n\nimpl Op for GpuReduce {\n    fn name(&self) -> StaticName {\n        format!(\"{}Reduce<{:?}>\", self.backend_name, self.reducer).into()\n    }\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axes: {:?}\", self.axes)])\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuReduce {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n        let mut output_shape = input.shape().to_vec();\n        output_shape[self.axes[0]] = 1;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            &output_shape,\n        )?;\n        (self.dispatch)(&self.reducer, input, self.axes[0], &output)?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuReduce {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(self.axes.iter().tuple_windows().all(|(a, b)| a < b));\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let mut shape: TVec<_> = facts[0].shape.to_tvec();\n            for &ax in &self.axes {\n                shape[ax] = 1.to_dim();\n            }\n            let dt = facts[0].datum_type;\n            Ok(tvec!(dt.fact(shape)))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/rms_norm.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse derive_new::new;\nuse std::sync::Arc;\nuse tract_core::internal::*;\n\npub type DispatchRmsNormFn = fn(&DeviceTensor, usize, &Tensor, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone, new)]\npub struct GpuRmsNorm {\n    pub axis: usize,\n    pub eps: Arc<Tensor>,\n    pub backend_name: &'static str,\n    pub dispatch: DispatchRmsNormFn,\n}\n\nimpl std::fmt::Debug for GpuRmsNorm {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}RmsNorm(axis: {:?}, eps: {:?})\", self.backend_name, self.axis, self.eps)\n    }\n}\n\nimpl PartialEq for GpuRmsNorm {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name && self.axis == other.axis && self.eps == other.eps\n    }\n}\n\nimpl Eq for GpuRmsNorm {}\n\nimpl std::hash::Hash for GpuRmsNorm {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n        self.axis.hash(state);\n        self.eps.hash(state);\n    }\n}\n\nimpl Op for GpuRmsNorm {\n    fn name(&self) -> StaticName {\n        format!(\"{}RmsNorm\", self.backend_name).into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis: {:?}, eps: {:?}\", self.axis, self.eps)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuRmsNorm {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            input.shape(),\n        )?;\n        (self.dispatch)(input, self.axis, &self.eps, &output)?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuRmsNorm {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let dt = facts[0].datum_type;\n            let fact = dt.fact(facts[0].shape.clone());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/rotate_half.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse derive_new::new;\nuse tract_core::internal::*;\n\npub type DispatchRotateHalfFn = fn(&DeviceTensor, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone, new)]\npub struct GpuRotateHalf {\n    pub backend_name: &'static str,\n    pub dispatch: DispatchRotateHalfFn,\n}\n\nimpl std::fmt::Debug for GpuRotateHalf {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}RotateHalf\", self.backend_name)\n    }\n}\n\nimpl PartialEq for GpuRotateHalf {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name\n    }\n}\n\nimpl Eq for GpuRotateHalf {}\n\nimpl std::hash::Hash for GpuRotateHalf {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n    }\n}\n\nimpl Op for GpuRotateHalf {\n    fn name(&self) -> StaticName {\n        format!(\"{}RotateHalf\", self.backend_name).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuRotateHalf {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            input.shape(),\n        )?;\n        (self.dispatch)(input, &output)?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuRotateHalf {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let dt = facts[0].datum_type;\n            let fact = dt.fact(facts[0].shape.clone());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/scaled_masked_softmax.rs",
    "content": "use crate::tensor::{DeviceTensor, DeviceTensorExt};\nuse derive_new::new;\nuse tract_core::internal::*;\n\n/// A = SOFTMAX(INPUT * SCALE + MASK, AXIS=2)\n/// Only input of rank of 3 is supported\npub type DispatchScaledMaskedSoftmaxFn =\n    fn(&DeviceTensor, &Tensor, &DeviceTensor, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone, new)]\npub struct GpuScaledMaskedSoftmax {\n    pub scale: Arc<Tensor>,\n    pub backend_name: &'static str,\n    pub dispatch: DispatchScaledMaskedSoftmaxFn,\n}\n\nimpl std::fmt::Debug for GpuScaledMaskedSoftmax {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}ScaledMaskedSoftmax\", self.backend_name)\n    }\n}\n\nimpl PartialEq for GpuScaledMaskedSoftmax {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name && self.scale == other.scale\n    }\n}\nimpl Eq for GpuScaledMaskedSoftmax {}\n\nimpl std::hash::Hash for GpuScaledMaskedSoftmax {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n        self.scale.hash(state);\n    }\n}\n\nimpl Op for GpuScaledMaskedSoftmax {\n    fn name(&self) -> StaticName {\n        format!(\"{}ScaledMaskedSoftmax\", self.backend_name).into()\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuScaledMaskedSoftmax {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let (input_val, mask_val) = args_2!(inputs);\n        let input = input_val.to_device_tensor()?;\n        let mask = mask_val.to_device_tensor()?;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            input.shape(),\n        )?;\n        (self.dispatch)(input, &self.scale, mask, &output)?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuScaledMaskedSoftmax {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            ensure!(facts.len() == 2);\n            let dt = facts[0].datum_type;\n            ensure!(dt == facts[1].datum_type);\n            ensure!(facts[0].rank() <= 5);\n            ensure!(facts[0].rank() >= 2);\n            ensure!(facts[0].rank() == facts[1].rank());\n            let fact = dt.fact(facts[0].shape.clone());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/slice.rs",
    "content": "use crate::tensor::DeviceTensorExt;\nuse crate::utils::compute_broadcast_strides;\nuse tract_core::internal::*;\nuse tract_core::ops::array::Slice;\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct GpuSlice {\n    pub inner: Slice,\n}\n\nimpl GpuSlice {\n    pub fn new(inner: Slice) -> Self {\n        Self { inner }\n    }\n}\n\nimpl Op for GpuSlice {\n    fn name(&self) -> StaticName {\n        \"GpuSlice\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        self.inner.info()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuSlice {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n\n        let start = self.inner.start.eval(&session.resolved_symbols).to_usize()?;\n        let end = self.inner.end.eval(&session.resolved_symbols).to_usize()?;\n        let axis = self.inner.axis;\n\n        let input_shape = input.shape();\n        let input_strides = input.strides();\n        let input_dt = input.datum_type();\n\n        ensure!(\n            end <= input_shape[axis] && start <= end,\n            \"Invalid range {}..{} for slicing {:?} on axis {}\",\n            start,\n            end,\n            input,\n            axis\n        );\n\n        let mut o_shape: TVec<usize> = input_shape.into();\n        o_shape[axis] = end - start;\n\n        let offset = (start * input_strides[axis] as usize) * input_dt.size_of();\n\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            &o_shape,\n        )?;\n\n        if o_shape[axis] != 0 {\n            // Slice uses same strides as input (broadcast strides with matching shapes)\n            let broadcast_strides: TVec<isize> =\n                compute_broadcast_strides(&o_shape, input_strides)?;\n            let ctx = crate::device::get_context()?;\n            ctx.copy_nd(\n                input,\n                offset,\n                &broadcast_strides,\n                &output,\n                0,\n                output.shape(),\n                output.strides(),\n            )?;\n        }\n        Ok(tvec![output.into_tensor().into_tvalue()])\n    }\n}\n\nimpl TypedOp for GpuSlice {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| self.inner.output_facts(facts))\n            .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    fn concretize_dims(\n        &self,\n        _source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        values: &SymbolValues,\n    ) -> TractResult<TVec<OutletId>> {\n        let op = GpuSlice {\n            inner: Slice {\n                axis: self.inner.axis,\n                start: self.inner.start.eval(values),\n                end: self.inner.end.eval(values),\n            },\n        };\n        let inputs = node.inputs.iter().map(|i| mapping[i]).collect::<TVec<_>>();\n        target.wire_node(&node.name, op, &inputs)\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/ops/softmax.rs",
    "content": "use crate::tensor::DeviceTensorExt;\nuse tract_core::internal::*;\nuse tract_core::ops::nn as core_ops_nn;\n\nuse crate::tensor::DeviceTensor;\n\npub type DispatchSoftmaxFn = fn(&DeviceTensor, usize, &DeviceTensor) -> TractResult<()>;\n\n#[derive(Clone)]\npub struct GpuSoftmax {\n    pub axes: TVec<usize>,\n    pub backend_name: &'static str,\n    pub dispatch: DispatchSoftmaxFn,\n}\n\nimpl GpuSoftmax {\n    pub fn new(\n        axes: TVec<usize>,\n        backend_name: &'static str,\n        dispatch: DispatchSoftmaxFn,\n    ) -> TractResult<Self> {\n        ensure!(\n            axes.len() == 1,\n            \"Only one axis of softmax is supported by {}Softmax\",\n            backend_name\n        );\n        Ok(Self { axes, backend_name, dispatch })\n    }\n\n    pub fn from_tract_core(\n        core_softmax: &core_ops_nn::Softmax,\n        backend_name: &'static str,\n        dispatch: DispatchSoftmaxFn,\n    ) -> TractResult<Self> {\n        ensure!(core_softmax.quant_output_dt.is_none());\n        Self::new(core_softmax.axes.clone(), backend_name, dispatch)\n    }\n}\n\nimpl std::fmt::Debug for GpuSoftmax {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{}Softmax(axes: {:?})\", self.backend_name, self.axes)\n    }\n}\n\nimpl PartialEq for GpuSoftmax {\n    fn eq(&self, other: &Self) -> bool {\n        self.backend_name == other.backend_name && self.axes == other.axes\n    }\n}\n\nimpl Eq for GpuSoftmax {}\n\nimpl std::hash::Hash for GpuSoftmax {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.backend_name.hash(state);\n        self.axes.hash(state);\n    }\n}\n\nimpl Op for GpuSoftmax {\n    fn name(&self) -> StaticName {\n        format!(\"{}Softmax\", self.backend_name).into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axes: {:?}\", self.axes)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GpuSoftmax {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input_value = args_1!(inputs);\n        let input = input_value.to_device_tensor()?;\n        let output = crate::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            input.datum_type(),\n            input.shape(),\n        )?;\n        (self.dispatch)(input, self.axes[0], &output)?;\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GpuSoftmax {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        crate::utils::facts_to_device_facts(inputs, |facts| {\n            let dt = facts[0].datum_type;\n            let fact = dt.fact(facts[0].shape.clone());\n            Ok(tvec!(fact))\n        })\n        .with_context(|| format!(\"Error while computing facts for {:?}\", self.name()))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        let axes: Option<TVec<usize>> =\n            self.axes.iter().map(|it| change.transform_axis(*it)).collect();\n        if let Some(axes) = axes {\n            Ok(Some(AxisChangeConsequence::new(\n                model,\n                node,\n                Some(Box::new(GpuSoftmax {\n                    axes,\n                    backend_name: self.backend_name,\n                    dispatch: self.dispatch,\n                })),\n                change,\n            )))\n        } else {\n            Ok(None)\n        }\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "gpu/src/rewrite_rules/mod.rs",
    "content": "pub mod rewire_sdpa;\npub mod rewire_syncs;\npub mod rms_norm;\n\n#[macro_export]\nmacro_rules! rule_ensure {\n    ($cond:expr) => {\n        if !$cond {\n            return Ok(None);\n        }\n    };\n}\n"
  },
  {
    "path": "gpu/src/rewrite_rules/rewire_sdpa.rs",
    "content": "use tract_core::internal::*;\nuse tract_transformers::ops::sdpa::{Sdpa, SdpaMaskMode, wire_attention_mask};\n\npub fn rewire_sdpa(model: &mut TypedModel) -> TractResult<()> {\n    Rewriter::default().with_rule_for(\"flatten-sdpa\", rewire_sdpa_op).rewrite(&(), model)\n}\n\npub fn rewire_sdpa_op(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    _node_name: &str,\n    op: &Sdpa,\n) -> TractResult<Option<TypedModelPatch>> {\n    op.patch_sdpa(model, node)\n}\n\npub fn create_sdpa_mask_graph(\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &Sdpa,\n    mode: SdpaMaskMode,\n) -> TractResult<Option<TypedModelPatch>> {\n    let in_facts = model.node_input_facts(node.id)?;\n    let q_shape = &in_facts[0].shape;\n    let k_shape = &in_facts[1].shape;\n    let rank = q_shape.len();\n    ensure!(k_shape.len() == rank);\n\n    let q_len = &q_shape[rank - 2];\n    let k_len = &k_shape[rank - 2];\n\n    let mut patch = TypedModelPatch::default();\n    let mut inputs = patch.taps(model, &node.inputs)?;\n\n    let mask =\n        wire_attention_mask(&mut patch, &node.name, op.acc_datum_type, mode, rank, q_len, k_len)?;\n    inputs.push(mask);\n\n    let mut new_op = op.clone();\n    new_op.is_causal = false;\n    let new_sdpa = patch.wire_node(node_name, new_op, &inputs)?[0];\n    patch.shunt_outside(model, node.id.into(), new_sdpa)?;\n\n    Ok(Some(patch))\n}\n\npub fn neutral_mask_for_full_attn(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &Sdpa,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(!op.is_causal && node.inputs.len() == 3);\n    create_sdpa_mask_graph(model, node, node_name, op, SdpaMaskMode::Neutral)\n}\n\npub fn causal_mask_as_extern(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &Sdpa,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(op.is_causal);\n    create_sdpa_mask_graph(model, node, node_name, op, SdpaMaskMode::Causal)\n}\n"
  },
  {
    "path": "gpu/src/rewrite_rules/rewire_syncs.rs",
    "content": "use crate::rule_ensure;\nuse crate::sync::{DeviceSync, DeviceSyncKind};\nuse crate::tensor::DeviceTensorExt;\nuse tract_core::internal::*;\nuse tract_core::ops::konst::Const;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_core::tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage};\n\npub fn rewire_syncs(model: &mut TypedModel) -> TractResult<()> {\n    Rewriter::default()\n        .with_rule_for(\"remove-back-and-forth-sync\", rewire_back_and_forth_sync)\n        .with_rule_for(\"remove-sync-after-const\", rewire_sync_after_const)\n        .rewrite(&(), model)\n}\n\npub fn rewire_back_and_forth_sync(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    _node_name: &str,\n    op: &DeviceSync,\n) -> TractResult<Option<TypedModelPatch>> {\n    // Search pattern => ToHost => ToDevice\n    rule_ensure!(op.kind == DeviceSyncKind::ToDevice);\n\n    // Identify precessor ToHost\n    let Some(sync_to_host_prec) = model.single_prec(node.id)? else {\n        return Ok(None);\n    };\n    let Some(sync_to_host_prec_op) = sync_to_host_prec.op_as::<DeviceSync>() else {\n        return Ok(None);\n    };\n    rule_ensure!(sync_to_host_prec_op.kind == DeviceSyncKind::ToHost);\n\n    let patch =\n        TypedModelPatch::rewire(model, &sync_to_host_prec.inputs, &[node.id.into()], &|_p, xs| {\n            Ok(xs.into())\n        })?;\n    Ok(Some(patch))\n}\n\npub fn rewire_sync_after_const(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &Const,\n) -> TractResult<Option<TypedModelPatch>> {\n    // Search pattern => Const => ToHost\n\n    let Some(device_const) = op.val().as_device_tensor() else {\n        return Ok(None);\n    };\n\n    // Identify successors ToHost\n    let Some(next_nodes) = model.all_succ(node.id)? else {\n        return Ok(None);\n    };\n\n    let sync_to_hosts = next_nodes\n        .into_iter()\n        .filter(|n| n.op_as::<DeviceSync>().is_some_and(|sync| sync.kind == DeviceSyncKind::ToHost))\n        .collect_vec();\n\n    if sync_to_hosts.is_empty() {\n        return Ok(None);\n    };\n\n    let host_const = device_const.to_host()?;\n    let exotic_fact: Option<Box<dyn ExoticFact>> =\n        host_const.storage_as::<BlockQuantStorage>().map(|bqs| {\n            Box::new(BlockQuantFact::new(\n                tract_core::dyn_clone::clone_box(bqs.format()),\n                host_const.shape().into(),\n            )) as Box<dyn ExoticFact>\n        });\n\n    let mut patch = TypedModelPatch::default();\n    let out = patch.wire_node(\n        node_name.to_string(),\n        Const::new_with_opt_exotic_fact(host_const, exotic_fact)?,\n        &[],\n    )?;\n\n    for sync_node in sync_to_hosts {\n        patch.shunt_outside(model, sync_node.id.into(), out[0])?;\n    }\n\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "gpu/src/rewrite_rules/rms_norm.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::ops::cast::Cast;\nuse tract_transformers::ops::rms_norm::RmsNorm;\n\n/// Search pattern => A = CAST(RMS_NORM(CAST(A, F32)), F16)\npub fn remove_rms_norm_cast(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &RmsNorm,\n) -> TractResult<Option<TypedModelPatch>> {\n    // Identify Cast from F16 To F32\n    let Some(cast_in_node) = model\n        .single_prec(node.id)?\n        .and_then(|n| n.op_as::<Cast>().and_then(|cast| (cast.to == DatumType::F32).then_some(n)))\n        .filter(|n| {\n            model.node_input_facts(n.id).map(|i| i[0].datum_type == DatumType::F16).unwrap_or(false)\n        })\n    else {\n        return Ok(None);\n    };\n\n    // Identify Cast from F32 To F16\n    let Some(cast_out_node) = model\n        .single_succ(node.id)?\n        .and_then(|n| n.op_as::<Cast>().and_then(|cast| (cast.to == DatumType::F16).then_some(n)))\n        .filter(|n| {\n            model.node_input_facts(n.id).map(|i| i[0].datum_type == DatumType::F32).unwrap_or(false)\n        })\n    else {\n        return Ok(None);\n    };\n\n    let mut patch = TypedModelPatch::default();\n    let rsm_input = patch.taps(model, &cast_in_node.inputs)?;\n    let out = patch.wire_node(format!(\"{node_name}.without-cast\"), op.clone(), &rsm_input)?;\n    patch.shunt_outside(model, cast_out_node.id.into(), out[0])?;\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "gpu/src/session_handler.rs",
    "content": "use crate::memory::DeviceMemSchema;\nuse crate::memory::DeviceMemoryPool;\nuse crate::tensor::DeviceTensor;\nuse tract_core::internal::*;\n\n#[derive(Debug, Clone)]\npub struct DeviceSessionHandler {\n    pub mem_schema: DeviceMemSchema,\n}\n\nimpl DeviceSessionHandler {\n    pub fn from_plan(plan: &TypedSimplePlan, memory_hint: &SymbolValues) -> TractResult<Self> {\n        let mem_schema =\n            DeviceMemSchema::build(plan.model(), plan.order_without_consts(), memory_hint)?;\n        Ok(Self { mem_schema })\n    }\n}\n\nimpl SessionStateHandler for DeviceSessionHandler {\n    fn before_plan_eval(&self, session_state: &mut TurnState) -> TractResult<()> {\n        let resolved_mem_schema = self.mem_schema.resolve(&session_state.resolved_symbols)?;\n        let memory_pool = DeviceMemoryPool::from_schema(resolved_mem_schema)?;\n\n        session_state.scratch_extensions.insert(memory_pool);\n        ensure!(session_state.scratch_extensions.get::<DeviceMemoryPool>().is_some());\n        Ok(())\n    }\n\n    fn after_plan_eval(&self, session_state: &mut TurnState) -> TractResult<()> {\n        session_state.scratch_extensions.remove::<DeviceMemoryPool>();\n        Ok(())\n    }\n}\n\npub fn make_tensor_for_node(\n    session: &TurnState,\n    node_id: usize,\n    dt: DatumType,\n    shape: &[usize],\n) -> TractResult<DeviceTensor> {\n    session\n        .scratch_extensions\n        .get::<DeviceMemoryPool>()\n        .map(|mem| mem.tensor_for_node(node_id, dt, shape))\n        .unwrap_or_else(|| DeviceTensor::uninitialized_dt(dt, shape))\n}\n\npub fn make_scalar_exotic_tensor_for_node(\n    session: &TurnState,\n    node_id: usize,\n    dt: DatumType,\n    exotic_fact: Box<dyn ExoticFact>,\n) -> TractResult<DeviceTensor> {\n    match session.scratch_extensions.get::<DeviceMemoryPool>() {\n        Some(mem) => mem.scalar_exotic_tensor_for_node(node_id, dt, exotic_fact),\n        None => DeviceTensor::uninitialized_exotic(exotic_fact),\n    }\n}\n"
  },
  {
    "path": "gpu/src/sync.rs",
    "content": "use crate::fact::{DeviceFact, DeviceTypedFactExt};\nuse crate::tensor::{DeviceTensorExt, IntoDevice};\nuse derive_new::new;\nuse std::collections::HashMap;\nuse std::fmt;\nuse std::sync::Arc;\nuse tract_core::internal::*;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum DeviceSyncKind {\n    ToHost,\n    ToDevice,\n}\n\nimpl fmt::Display for DeviceSyncKind {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\n#[derive(Debug, Clone, new, Copy, PartialEq, Eq, Hash)]\npub struct DeviceSync {\n    pub kind: DeviceSyncKind,\n}\n\nimpl Op for DeviceSync {\n    fn name(&self) -> StaticName {\n        format!(\"DeviceSync{}\", self.kind).into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DeviceSync {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        match self.kind {\n            DeviceSyncKind::ToHost => {\n                let device_tensor = input.to_device_tensor()?;\n\n                let tensor = device_tensor\n                    .to_host()\n                    .with_context(|| \"Error while syncing device tensor to host\")?;\n                Ok(tvec![tensor.into_tvalue()])\n            }\n            DeviceSyncKind::ToDevice => {\n                let device_input = if let Some(t) = input.as_arc_tensor() {\n                    Arc::clone(t).into_device()?\n                } else {\n                    input.into_tensor().into_device()?\n                };\n                Ok(tvec![device_input.into_tensor().into()])\n            }\n        }\n    }\n}\n\nimpl TypedOp for DeviceSync {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let input = inputs[0];\n        match self.kind {\n            DeviceSyncKind::ToHost => {\n                let mut typed_fact = input\n                    .to_device_fact()\n                    .with_context(|| {\n                        \"Cannot sync to Host a tensor without DeviceFact as metadata in its TypedFact\"\n                    })?\n                    .clone()\n                    .into_typed_fact();\n                if let Some(konst) = input.konst.clone() {\n                    if let Some(dt) = konst.as_device_tensor() {\n                        typed_fact.konst = Some(dt.to_host()?);\n                    } else {\n                        typed_fact.konst = Some(konst);\n                    }\n                }\n                Ok(tvec!(typed_fact))\n            }\n            DeviceSyncKind::ToDevice => {\n                ensure!(\n                    input.as_device_fact().is_none(),\n                    \"Cannot sync to Device a tensor already on Device\"\n                );\n                Ok(tvec![DeviceFact::from_host(input.clone())?.into_exotic_fact()])\n            }\n        }\n    }\n\n    as_op!();\n}\n\n/// Map node inputs through the translation mapping, inserting DeviceSync nodes\n/// where needed to move tensors to/from the device.\npub fn sync_inputs_if_required(\n    model: &mut TypedModel,\n    node: &TypedNode,\n    mapping: &HashMap<OutletId, OutletId>,\n    sync_kind: DeviceSyncKind,\n) -> TractResult<TVec<OutletId>> {\n    let mut mapped_inputs = tvec![];\n    for (i_idx, i) in node.inputs.iter().enumerate() {\n        let in_fact = model.outlet_fact_mut(mapping[i])?;\n        match sync_kind {\n            DeviceSyncKind::ToHost if in_fact.as_device_fact().is_some() => {\n                mapped_inputs.push(\n                    model.wire_node(\n                        format!(\"{}.to-cpu-{i_idx}\", node.name),\n                        DeviceSync::new(sync_kind),\n                        &[mapping[i]],\n                    )?[0],\n                );\n            }\n            DeviceSyncKind::ToDevice if in_fact.as_device_fact().is_none() => {\n                if let Some(ref konst) = in_fact.konst\n                    && konst.as_device_tensor().is_none()\n                {\n                    let device_konst = konst.as_ref().clone().into_device()?.into_tensor();\n                    let device_fact = DeviceFact::from_host(in_fact.clone())?;\n\n                    *in_fact = device_fact.into_exotic_fact();\n\n                    in_fact.konst = Some(Arc::new(device_konst));\n                    mapped_inputs.push(mapping[i]);\n                    continue;\n                }\n                ensure!(\n                    in_fact.datum_type.is_copy(),\n                    \"Only copy DatumType can be sync to Device: {:?}\",\n                    in_fact.datum_type\n                );\n\n                mapped_inputs.push(\n                    model.wire_node(\n                        format!(\"{}.to-device-{i_idx}\", node.name),\n                        DeviceSync::new(sync_kind),\n                        &[mapping[i]],\n                    )?[0],\n                );\n            }\n            _ => mapped_inputs.push(mapping[i]),\n        }\n    }\n    Ok(mapped_inputs)\n}\n\n/// For model outputs that are on device, insert DeviceSync nodes to move them back to host.\npub fn sync_model_outputs_if_required(\n    src: &TypedModel,\n    node: &TypedNode,\n    target: &mut TypedModel,\n    target_node_outlet_ids: TVec<OutletId>,\n) -> TractResult<TVec<OutletId>> {\n    let mut outputs = tvec![];\n    for (o_idx, o) in target_node_outlet_ids.into_iter().enumerate() {\n        let is_src_output = src.outputs.contains(&OutletId::new(node.id, o_idx));\n        if target.outlet_fact(o)?.as_device_fact().is_some() && is_src_output {\n            let sync_output = target.wire_node(\n                format!(\"{}.to-host-{o_idx}-out\", node.name),\n                DeviceSync::new(DeviceSyncKind::ToHost),\n                &[o],\n            )?[0];\n            outputs.push(sync_output);\n        } else {\n            outputs.push(o)\n        }\n    }\n    Ok(outputs)\n}\n"
  },
  {
    "path": "gpu/src/tensor/arena_view.rs",
    "content": "use num_traits::AsPrimitive;\nuse std::ffi::c_void;\nuse std::fmt::Display;\nuse tract_core::internal::*;\nuse tract_core::tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage};\n\nuse crate::device::{DeviceBuffer, get_context};\nuse crate::utils::check_strides_validity;\n\nuse super::OwnedDeviceTensor;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct DeviceArenaView {\n    pub(crate) arena: Arc<Box<dyn OwnedDeviceTensor>>,\n    pub(crate) dt: DatumType,\n    pub(crate) len: usize,\n    pub(crate) shape: TVec<usize>,\n    pub(crate) strides: TVec<isize>,\n    pub(crate) offset_bytes: usize,\n    pub(crate) exotic_fact: Option<Box<dyn ExoticFact>>,\n}\n\nimpl DeviceArenaView {\n    #[inline]\n    pub fn shape(&self) -> &[usize] {\n        self.shape.as_slice()\n    }\n\n    /// Get the datum type of the tensor.\n    #[inline]\n    pub fn datum_type(&self) -> DatumType {\n        self.dt\n    }\n\n    #[inline]\n    pub fn strides(&self) -> &[isize] {\n        self.strides.as_slice()\n    }\n\n    /// Get underlying inner device buffer.\n    pub fn device_buffer(&self) -> &dyn DeviceBuffer {\n        self.arena.device_buffer()\n    }\n\n    pub fn device_buffer_ptr(&self) -> *const c_void {\n        self.arena.device_buffer().ptr()\n    }\n\n    /// Get underlying inner device buffer offset\n    pub fn buffer_offset<I: Copy + 'static>(&self) -> I\n    where\n        usize: AsPrimitive<I>,\n    {\n        self.offset_bytes.as_()\n    }\n\n    pub fn exotic_fact(&self) -> Option<&dyn ExoticFact> {\n        self.exotic_fact.as_deref()\n    }\n\n    /// Get the number of values in the tensor.\n    #[inline]\n    #[allow(clippy::len_without_is_empty)]\n    pub fn len(&self) -> usize {\n        self.len\n    }\n\n    pub fn as_bytes(&self) -> Vec<u8> {\n        let len = if let Some(of) = &self.exotic_fact {\n            of.mem_size().as_i64().unwrap() as usize\n        } else {\n            self.len() * self.dt.size_of()\n        };\n        self.arena.get_bytes_slice(self.offset_bytes, len)\n    }\n\n    /// Reshaped tensor with given shape.\n    pub fn reshaped(&self, shape: impl Into<TVec<usize>>) -> TractResult<Self> {\n        ensure!(self.exotic_fact.is_none(), \"Can't reshape exotic tensor\");\n        let shape = shape.into();\n        if self.len() != shape.iter().product::<usize>() {\n            bail!(\"Invalid reshape {:?} to {:?}\", self.shape(), shape);\n        }\n        if shape.as_slice() != self.shape() {\n            Ok(Self {\n                arena: Arc::clone(&self.arena),\n                dt: self.dt,\n                len: self.len,\n                strides: Tensor::natural_strides(&shape),\n                shape,\n                offset_bytes: self.offset_bytes,\n                exotic_fact: None,\n            })\n        } else {\n            Ok(self.clone())\n        }\n    }\n\n    pub fn restrided(&self, strides: impl Into<TVec<isize>>) -> TractResult<Self> {\n        ensure!(self.exotic_fact.is_none(), \"Can't restride exotic tensor\");\n        let strides = strides.into();\n        check_strides_validity(self.shape().into(), strides.clone())?;\n\n        if strides.as_slice() != self.strides() {\n            Ok(Self {\n                arena: Arc::clone(&self.arena),\n                dt: self.dt,\n                len: self.len,\n                strides,\n                shape: self.shape.clone(),\n                offset_bytes: self.offset_bytes,\n                exotic_fact: None,\n            })\n        } else {\n            Ok(self.clone())\n        }\n    }\n\n    pub fn to_host(&self) -> TractResult<Tensor> {\n        get_context()?.synchronize()?;\n        let content = self.as_bytes();\n        unsafe {\n            if let Some(bqf) =\n                self.exotic_fact.as_ref().and_then(|of| of.downcast_ref::<BlockQuantFact>())\n            {\n                Ok(BlockQuantStorage::new(\n                    bqf.format.clone(),\n                    bqf.m(),\n                    bqf.k(),\n                    Arc::new(Blob::from_bytes(&content)?),\n                )?\n                .into_tensor_with_shape(self.dt, bqf.shape()))\n            } else {\n                Tensor::from_raw_dt(self.dt, &self.shape, &content)\n            }\n        }\n    }\n}\n\nimpl Display for DeviceArenaView {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let content = self\n            .clone()\n            .to_host()\n            .unwrap()\n            .dump(false)\n            .unwrap_or_else(|e| format!(\"Error : {e:?}\"));\n        write!(f, \"DeviceArenaView: {{ {content} }}\")\n    }\n}\n"
  },
  {
    "path": "gpu/src/tensor/mod.rs",
    "content": "#![allow(clippy::missing_safety_doc)]\n#![allow(clippy::missing_transmute_annotations)]\n\nmod arena_view;\nmod owned;\n\npub use arena_view::*;\npub use owned::*;\n\nuse num_traits::AsPrimitive;\nuse std::ffi::c_void;\nuse std::fmt::Display;\nuse tract_core::internal::*;\nuse tract_data::itertools::Itertools;\n\nuse crate::device::{DeviceBuffer, get_context};\n\n/// This struct represents a GPU tensor that can be either a owned tensor\n/// or an arena view.\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub enum DeviceTensor {\n    Owned(Box<dyn OwnedDeviceTensor>),\n    ArenaView(DeviceArenaView),\n}\n\nimpl DeviceTensor {\n    pub const SUPPORTED_DT: [DatumType; 11] = [\n        DatumType::Bool,\n        DatumType::F32,\n        DatumType::F16,\n        DatumType::I8,\n        DatumType::U8,\n        DatumType::I16,\n        DatumType::U16,\n        DatumType::I32,\n        DatumType::U32,\n        DatumType::I64,\n        DatumType::U64,\n    ];\n\n    pub fn tname(dt: DatumType) -> TractResult<&'static str> {\n        Ok(match dt {\n            DatumType::F32 => \"f32\",\n            DatumType::F16 => \"f16\",\n            DatumType::U8 => \"u8\",\n            DatumType::U16 => \"u16\",\n            DatumType::U32 => \"u32\",\n            DatumType::U64 => \"u64\",\n            DatumType::I8 => \"i8\",\n            DatumType::I16 => \"i16\",\n            DatumType::I32 => \"i32\",\n            DatumType::I64 => \"i64\",\n            DatumType::Bool => \"bool\",\n            _ => bail!(\"Unsupported dt {:?} for GPU Tensor\", dt),\n        })\n    }\n\n    /// Create an uninitialized DeviceTensor\n    pub fn uninitialized_dt(dt: DatumType, shape: &[usize]) -> TractResult<DeviceTensor> {\n        Ok(DeviceTensor::Owned(get_context()?.uninitialized_device_tensor(shape, dt)?))\n    }\n\n    pub fn uninitialized<T: Datum>(shape: &[usize]) -> TractResult<DeviceTensor> {\n        Self::uninitialized_dt(T::datum_type(), shape)\n    }\n\n    pub fn uninitialized_exotic(exotic_fact: Box<dyn ExoticFact>) -> TractResult<DeviceTensor> {\n        Ok(DeviceTensor::Owned(get_context()?.uninitialized_device_exotic_tensor(exotic_fact)?))\n    }\n    // Create a device tensor with a given shape and a slice of elements. The data is copied and aligned to size of T.\n    pub fn from_shape<T: Copy + Datum>(shape: &[usize], data: &[T]) -> TractResult<DeviceTensor> {\n        Tensor::from_shape(shape, data)?.into_device()\n    }\n\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        Self::SUPPORTED_DT.contains(&dt)\n    }\n\n    /// Get the datum type of the tensor.\n    #[inline]\n    pub fn datum_type(&self) -> DatumType {\n        match self {\n            Self::Owned(owned) => owned.datum_type(),\n            Self::ArenaView(view) => view.datum_type(),\n        }\n    }\n\n    /// Get the number of dimensions (or axes) of the tensor.\n    #[inline]\n    pub fn rank(&self) -> usize {\n        self.shape().len()\n    }\n\n    /// Get the shape of the tensor.\n    #[inline]\n    pub fn shape(&self) -> &[usize] {\n        match self {\n            Self::Owned(t) => t.shape(),\n            Self::ArenaView(t) => t.shape(),\n        }\n    }\n\n    /// Get the number of values in the tensor.\n    #[inline]\n    #[allow(clippy::len_without_is_empty)]\n    pub fn len(&self) -> usize {\n        match self {\n            Self::Owned(t) => t.len(),\n            Self::ArenaView(t) => t.len(),\n        }\n    }\n\n    /// Get the strides of the tensor.\n    #[inline]\n    pub fn strides(&self) -> &[isize] {\n        match self {\n            Self::Owned(t) => t.strides(),\n            Self::ArenaView(t) => t.strides(),\n        }\n    }\n\n    /// Get underlying inner buffer.\n    pub fn device_buffer(&self) -> &dyn DeviceBuffer {\n        match self {\n            Self::Owned(t) => t.device_buffer(),\n            Self::ArenaView(t) => t.device_buffer(),\n        }\n    }\n\n    /// Get underlying inner buffer offset\n    pub fn buffer_offset<I: Copy + 'static>(&self) -> I\n    where\n        usize: AsPrimitive<I>,\n    {\n        match self {\n            Self::Owned(_) => 0.as_(),\n            Self::ArenaView(t) => t.buffer_offset(),\n        }\n    }\n\n    pub fn device_buffer_ptr(&self) -> *const c_void {\n        match self {\n            Self::Owned(t) => t.device_buffer().ptr(),\n            Self::ArenaView(t) => t.device_buffer().ptr(),\n        }\n    }\n\n    /// Returns short description of the inner tensor.\n    pub fn description(&self) -> String {\n        format!(\"|{},{:?}|\", self.shape().iter().join(\",\"), self.datum_type(),)\n    }\n\n    /// Reshaped tensor with given shape.\n    pub fn reshaped(&self, shape: TVec<usize>) -> TractResult<Self> {\n        match self {\n            Self::Owned(t) => Ok(t.reshaped(shape)?),\n            Self::ArenaView(t) => Ok(Self::ArenaView(t.reshaped(shape)?)),\n        }\n    }\n\n    pub fn restrided(&self, strides: TVec<isize>) -> TractResult<Self> {\n        match self {\n            Self::Owned(t) => Ok(t.restrided(strides)?),\n            Self::ArenaView(t) => Ok(Self::ArenaView(t.restrided(strides)?)),\n        }\n    }\n\n    /// Convert device tensor to a Tensor backed by device storage.\n    ///\n    /// The resulting tensor carries the real datum type and shape from the\n    /// device tensor (e.g. F32 / \\[2,3\\]), rather than an exotic scalar wrapper.\n    pub fn into_tensor(self) -> Tensor {\n        let dt = self.datum_type();\n        let shape: TVec<usize> = self.shape().into();\n        Tensor::from_storage(dt, &shape, self)\n    }\n\n    /// Synchronize the GPU Tensor by completing all current\n    /// commands on GPU and returns the inner tensor.\n    pub fn to_host(&self) -> TractResult<Arc<Tensor>> {\n        get_context()?.synchronize()?;\n\n        Ok(match self {\n            Self::Owned(o) => o.to_host()?,\n            Self::ArenaView(v) => v.to_host()?.into(),\n        })\n    }\n}\n\nimpl Display for DeviceTensor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Owned(o) => o.fmt(f),\n            Self::ArenaView(v) => {\n                let content =\n                    v.to_host().unwrap().dump(false).unwrap_or_else(|e| format!(\"Error : {e:?}\"));\n                write!(f, \"ArenaView: {{ {content} }}\")\n            }\n        }\n    }\n}\n\npub trait IntoDevice<T> {\n    fn into_device(self) -> TractResult<T>;\n}\n\nimpl IntoDevice<DeviceTensor> for Tensor {\n    fn into_device(self) -> TractResult<DeviceTensor> {\n        Ok(DeviceTensor::Owned(get_context()?.tensor_to_device(self.into_tvalue())?))\n    }\n}\n\nimpl IntoDevice<DeviceTensor> for Arc<Tensor> {\n    fn into_device(self) -> TractResult<DeviceTensor> {\n        Ok(DeviceTensor::Owned(get_context()?.tensor_to_device(self.into_tvalue())?))\n    }\n}\n\nimpl TensorStorage for DeviceTensor {\n    fn byte_len(&self) -> usize {\n        self.len() * self.datum_type().size_of()\n    }\n\n    fn is_empty(&self) -> bool {\n        self.byte_len() == 0\n    }\n\n    fn deep_clone(&self) -> Box<dyn TensorStorage> {\n        Box::new(self.clone())\n    }\n\n    fn as_plain(&self) -> Option<&PlainStorage> {\n        None\n    }\n\n    fn as_plain_mut(&mut self) -> Option<&mut PlainStorage> {\n        None\n    }\n\n    fn into_plain(self: Box<Self>) -> Option<PlainStorage> {\n        None\n    }\n\n    fn dyn_hash(&self, _state: &mut dyn std::hash::Hasher) {\n        // no meaningful hash for device memory\n    }\n\n    fn exotic_fact(&self, _shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {\n        bail!(\n            \"DeviceTensor cannot reconstruct a DeviceFact: origin (FromHost/FromDevice) is not carried by storage\"\n        )\n    }\n}\n\nimpl From<DeviceArenaView> for DeviceTensor {\n    fn from(view: DeviceArenaView) -> Self {\n        Self::ArenaView(view)\n    }\n}\n\npub trait DeviceTensorExt {\n    fn to_device_tensor(&self) -> TractResult<&DeviceTensor>;\n    fn as_device_tensor(&self) -> Option<&DeviceTensor>;\n}\n\nimpl DeviceTensorExt for Tensor {\n    fn to_device_tensor(&self) -> TractResult<&DeviceTensor> {\n        self.try_storage_as::<DeviceTensor>()\n    }\n\n    fn as_device_tensor(&self) -> Option<&DeviceTensor> {\n        self.storage_as::<DeviceTensor>()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_device_tensor() -> TractResult<()> {\n        let a = DeviceTensor::from_shape(&[1], &[0f32])?;\n        assert_eq!(a.to_host()?.try_as_plain()?.as_slice::<f32>()?, &[0.0]);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "gpu/src/tensor/owned.rs",
    "content": "use downcast_rs::{Downcast, impl_downcast};\nuse dyn_clone::DynClone;\nuse dyn_eq::DynEq;\nuse std::fmt::Debug;\nuse tract_core::dyn_clone;\nuse tract_core::internal::*;\n\nuse crate::device::DeviceBuffer;\n\nuse super::DeviceTensor;\n\n#[allow(clippy::len_without_is_empty)]\npub trait OwnedDeviceTensor: Downcast + DynClone + Send + Sync + Debug + DynEq {\n    fn datum_type(&self) -> DatumType;\n\n    fn shape(&self) -> &[usize];\n\n    fn strides(&self) -> &[isize];\n\n    #[inline]\n    fn len(&self) -> usize {\n        self.shape().iter().product()\n    }\n\n    fn reshaped(&self, shape: TVec<usize>) -> TractResult<DeviceTensor>;\n    fn restrided(&self, shape: TVec<isize>) -> TractResult<DeviceTensor>;\n\n    fn exotic_fact(&self) -> Option<&dyn ExoticFact>;\n    fn get_bytes_slice(&self, offset: usize, len: usize) -> Vec<u8>;\n    fn device_buffer(&self) -> &dyn DeviceBuffer;\n    fn to_host(&self) -> TractResult<Arc<Tensor>>;\n}\n\nimpl_downcast!(OwnedDeviceTensor);\ndyn_hash::hash_trait_object!(OwnedDeviceTensor);\ndyn_clone::clone_trait_object!(OwnedDeviceTensor);\ndyn_eq::eq_trait_object!(OwnedDeviceTensor);\n"
  },
  {
    "path": "gpu/src/utils.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::tract_linalg::block_quant::BlockQuant;\nuse tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage, Q4_0};\n\nuse crate::fact::*;\nuse crate::tensor::DeviceTensor;\n\npub fn facts_to_device_facts(\n    facts: &[&TypedFact],\n    resolve_facts: impl Fn(&[&TypedFact]) -> TractResult<TVec<TypedFact>>,\n) -> TractResult<TVec<TypedFact>> {\n    if facts.iter().all(|it| it.as_device_fact().is_some()) {\n        let device_facts = facts\n            .iter()\n            .map(|it| it.to_device_fact().map(|it| it.as_ref()))\n            .collect::<TractResult<TVec<_>>>()?;\n        let output_facts = (resolve_facts)(device_facts.as_slice())?;\n        Ok(output_facts\n            .into_iter()\n            .map(|it| Ok(DeviceFact::new(DeviceTensorOrigin::FromDevice, it)?.into_exotic_fact()))\n            .collect::<TractResult<_>>()?)\n    } else if facts.iter().all(|it| it.as_device_fact().is_none()) {\n        (resolve_facts)(facts)\n    } else {\n        bail!(\"Inconsistent facts: mix of device and host facts\");\n    }\n}\n\npub fn get_device_facts<'a, 'b: 'a, T>(\n    facts: &'a [&'b TypedFact],\n    map_facts: impl Fn(&[&'b TypedFact]) -> TractResult<T>,\n) -> TractResult<T> {\n    if facts.iter().all(|it| it.as_device_fact().is_some()) {\n        let device_facts = facts\n            .iter()\n            .map(|it| it.to_device_fact().map(|it| it.as_ref()))\n            .collect::<TractResult<TVec<_>>>()?;\n        (map_facts)(device_facts.as_slice())\n    } else if facts.iter().all(|it| it.as_device_fact().is_none()) {\n        (map_facts)(facts)\n    } else {\n        bail!(\"Inconsistent facts: mix of device and host facts\");\n    }\n}\n\npub fn get_device_fact<'a, T: 'a>(\n    fact: &'a TypedFact,\n    map_fact: impl Fn(&'a TypedFact) -> TractResult<T>,\n) -> TractResult<T> {\n    if fact.as_device_fact().is_some() {\n        (map_fact)(fact.to_device_fact()?)\n    } else {\n        (map_fact)(fact)\n    }\n}\n\npub fn as_quant_fact<'a>(\n    fact: &'a TypedFact,\n    format: &dyn BlockQuant,\n) -> Option<&'a BlockQuantFact> {\n    fact.exotic_fact\n        .as_ref()\n        .and_then(|of| of.downcast_ref::<BlockQuantFact>())\n        .and_then(|bqf| if bqf.format.dyn_eq(format) { Some(bqf) } else { None })\n}\n\npub fn as_q40_tensor(a: &Tensor) -> Option<&BlockQuantStorage> {\n    a.storage_as::<BlockQuantStorage>().filter(|bqs| bqs.format().dyn_eq(&Q4_0))\n}\n\npub fn get_quant_fact(t: &DeviceTensor, format: &dyn BlockQuant) -> Option<BlockQuantFact> {\n    if let DeviceTensor::Owned(t) = t {\n        t.exotic_fact()\n            .and_then(|of| of.downcast_ref::<BlockQuantFact>())\n            .cloned()\n            .filter(|bqf| bqf.format.dyn_eq(format))\n    } else {\n        None\n    }\n}\n\n// --- Shared array/copy utilities ---\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum BroadcastKind {\n    Unicast,\n    ByScalarLeft,\n    ByScalarRight,\n    Nd1,\n    Nd2,\n    Nd3,\n    Nd4,\n    Nd5,\n    Nd6,\n}\n\nimpl BroadcastKind {\n    pub const ALL: [BroadcastKind; 8] = [\n        Self::Unicast,\n        Self::ByScalarLeft,\n        Self::ByScalarRight,\n        Self::Nd1,\n        Self::Nd2,\n        Self::Nd3,\n        Self::Nd4,\n        Self::Nd5,\n    ];\n\n    pub fn from_rank(rank: usize) -> TractResult<Self> {\n        match rank {\n            1 => Ok(Self::Nd1),\n            2 => Ok(Self::Nd2),\n            3 => Ok(Self::Nd3),\n            4 => Ok(Self::Nd4),\n            5 => Ok(Self::Nd5),\n            6 => Ok(Self::Nd6),\n            _ => bail!(\"Unsupported rank {rank} for broadcasting\"),\n        }\n    }\n\n    pub fn name(&self) -> &'static str {\n        match self {\n            Self::Unicast => \"unicast\",\n            Self::ByScalarLeft => \"by_scalar_lhs\",\n            Self::ByScalarRight => \"by_scalar_rhs\",\n            Self::Nd1 => \"nd1\",\n            Self::Nd2 => \"nd2\",\n            Self::Nd3 => \"nd3\",\n            Self::Nd4 => \"nd4\",\n            Self::Nd5 => \"nd5\",\n            Self::Nd6 => \"nd6\",\n        }\n    }\n\n    /// Map datum type to the copy kernel type name based on element size.\n    /// Copy kernels only care about element size, not the actual type.\n    pub fn copy_tname(dt: DatumType) -> &'static str {\n        match dt.size_of() {\n            1 => \"u8\",\n            2 => \"u16\",\n            4 => \"u32\",\n            8 => \"u64\",\n            _ => panic!(\"Unsupported element size {} for copy kernel\", dt.size_of()),\n        }\n    }\n\n    pub fn copy_kernel_name(&self, dt: DatumType, prefix: &str) -> TractResult<String> {\n        Ok(format!(\"{prefix}copy_{}_{}\", self.name(), Self::copy_tname(dt)))\n    }\n\n    pub fn all_copy_kernel_names(prefix: &str) -> Vec<String> {\n        let copy_types = [\"u8\", \"u16\", \"u32\", \"u64\"];\n        Self::ALL\n            .into_iter()\n            .flat_map(|bk| {\n                copy_types\n                    .into_iter()\n                    .map(move |tname| format!(\"{prefix}copy_{}_{tname}\", bk.name()))\n            })\n            .collect()\n    }\n}\n\npub fn compute_broadcast_strides<T: num_traits::Zero + Copy + 'static>(\n    shape: &[usize],\n    strides: &[isize],\n) -> TractResult<TVec<T>>\nwhere\n    isize: num_traits::AsPrimitive<T>,\n{\n    use num_traits::AsPrimitive;\n    ensure!(\n        shape.len() == strides.len(),\n        \"Mismatch between shape and strides length while computing broadcast strides\"\n    );\n    Ok(strides\n        .iter()\n        .zip(shape)\n        .map(|(s, dim)| if *dim == 1 { T::zero() } else { s.as_() })\n        .collect::<TVec<T>>())\n}\n\npub fn reshape_to_rank_2(shape: &[usize], axis: usize) -> TVec<usize> {\n    let dim_axis_0 = shape[0..axis].iter().product::<usize>();\n    let dim_axis_2 = shape[axis..].iter().product::<usize>();\n    tvec![dim_axis_0, dim_axis_2]\n}\n\npub fn reshape_to_rank_3(shape: &[usize], axis: usize) -> TVec<usize> {\n    let dim_axis_0 = shape[0..axis].iter().product::<usize>();\n    let dim_axis_1 = shape[axis];\n    let dim_axis_2 = shape[axis + 1..].iter().product::<usize>();\n    tvec![dim_axis_0, dim_axis_1, dim_axis_2]\n}\n\npub fn check_strides_validity(shape: TVec<usize>, strides: TVec<isize>) -> TractResult<()> {\n    let mut zipped_shape_strides: Vec<_> = shape.into_iter().zip(strides).collect();\n    zipped_shape_strides.sort_by_key(|&(_, stride)| stride);\n\n    let mut prev_stride = 1;\n    for (dim, stride) in zipped_shape_strides {\n        ensure!((stride == prev_stride) || (dim == 1), \"Invalid strides\");\n        prev_stride *= dim as isize;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "harness/core-proptest-pulse/Cargo.toml",
    "content": "[package]\nname = \"core-proptest-pulse\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\ntract-core.workspace = true\ntract-pulse.workspace = true\n\n[dev-dependencies]\nlog.workspace = true\nproptest.workspace = true\nenv_logger.workspace = true\n"
  },
  {
    "path": "harness/core-proptest-pulse/src/conv_plus_conv.rs",
    "content": "use proptest::proptest;\nuse proptest::test_runner::TestCaseResult;\nuse tract_core::tract_data::itertools::Itertools;\n\nuse super::*;\n\n#[derive(Debug, Clone)]\nstruct ConvOp {\n    stride: usize,\n    dilation: usize,\n    ker: Tensor,\n    padding: PaddingSpec,\n}\n\nimpl ConvOp {\n    fn chain(&self, name: &str, model: &mut TypedModel, after: &[OutletId]) -> TVec<OutletId> {\n        let kernel = model.add_const(format!(\"{name}.k\"), self.ker.clone()).unwrap();\n        let bias = model.add_const(format!(\"{name}.b\"), tensor0(0f32)).unwrap();\n        model\n            .wire_node(\n                name,\n                Conv {\n                    pool_spec: PoolSpec {\n                        data_format: DataFormat::NCHW,\n                        kernel_shape: self.ker.shape()[2..].into(),\n                        padding: self.padding.clone(),\n                        dilations: Some(tvec!(self.dilation)),\n                        strides: Some(tvec!(self.stride)),\n                        input_channels: 1,\n                        output_channels: 1,\n                    },\n                    kernel_fmt: tract_core::ops::cnn::KernelFormat::OIHW,\n                    group: 1,\n                    q_params: None,\n                },\n                &[after[0], kernel, bias],\n            )\n            .unwrap()\n    }\n}\n\nimpl Arbitrary for ConvOp {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n\n    fn arbitrary_with(_: Self::Parameters) -> BoxedStrategy<Self> {\n        (1usize..3, 1usize..3, 1usize..4)\n            .prop_flat_map(|(stride, dil, ker)| {\n                let padding = (ker - 1) * dil;\n                let explicit = (0..=padding).prop_map(move |right| {\n                    PaddingSpec::ExplicitOnnxPool(tvec!(padding - right), tvec!(right), false)\n                });\n                (Just((stride, dil, ker)), prop_oneof![Just(PaddingSpec::Valid), explicit])\n            })\n            .prop_map(|((stride, dilation, ker), padding)| ConvOp {\n                stride,\n                dilation,\n                ker: t(ker),\n                padding,\n            })\n            .boxed()\n    }\n}\n\n#[derive(Debug, Clone)]\nstruct ConvPlusConvProblem {\n    input: Tensor,\n    pulse: usize,\n    convs: Vec<ConvOp>,\n}\n\nimpl Arbitrary for ConvPlusConvProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n\n    fn arbitrary_with(_: Self::Parameters) -> BoxedStrategy<Self> {\n        (proptest::collection::vec(ConvOp::arbitrary(), 1..4), 1usize..4)\n            .prop_flat_map(|(convs, pulse_factor)| {\n                let pulse = convs.iter().map(|cv| cv.stride).product::<usize>() * pulse_factor;\n                let min_input = Self::min_input_size(&convs);\n                (Just(convs), Just(pulse), min_input..3 * min_input)\n            })\n            .prop_map(|(convs, pulse, input)| ConvPlusConvProblem { input: t(input), pulse, convs })\n            .boxed()\n    }\n}\n\nimpl ConvPlusConvProblem {\n    pub fn min_input_size(ops: &[ConvOp]) -> usize {\n        let model = Self::model(ops);\n        let dims: Vec<&TDim> = model\n            .nodes\n            .iter()\n            .filter(|node| !node.outputs[0].fact.shape.is_concrete())\n            .map(|n| &n.outputs[0].fact.shape[2])\n            .collect();\n        for s in 0usize.. {\n            let symbols = SymbolValues::default().with(&model.symbols.get(\"S\").unwrap(), s as _);\n            if dims.iter().all(|d| d.eval(&symbols).to_isize().unwrap() > 0) {\n                return s;\n            }\n        }\n        unreachable!();\n    }\n\n    pub fn model(ops: &[ConvOp]) -> TypedModel {\n        let mut model = TypedModel::default();\n        let s = model.symbols.sym(\"S\");\n        let wire = model.add_source(\"a\", f32::fact(dims!(1, 1, s))).unwrap();\n        let mut wire = tvec!(wire);\n        for (ix, cv) in ops.iter().enumerate() {\n            wire = cv.chain(&format!(\"conv{ix}\"), &mut model, &wire);\n        }\n        model.select_output_outlets(&wire).unwrap();\n        model\n    }\n\n    pub fn run(&self) -> TestCaseResult {\n        proptest_regular_against_pulse(\n            Self::model(&self.convs),\n            self.pulse as _,\n            self.input.to_plain_array_view::<f32>().unwrap().to_owned(),\n            2,\n        )\n    }\n}\n\nproptest! {\n    #[test]\n    fn proptest(pb in ConvPlusConvProblem::arbitrary()) { pb.run().unwrap() }\n}\n\nfn t(n: usize) -> Tensor {\n    tensor1(&(0..n).map(|x| x as f32).collect_vec()).into_shape(&[1, 1, n]).unwrap()\n}\n\n#[test]\nfn prob_1() {\n    let cpc = ConvPlusConvProblem {\n        input: t(7),\n        pulse: 1,\n        convs: vec![\n            ConvOp {\n                stride: 1,\n                dilation: 1,\n                ker: tensor3(&[[[1f32]]]),\n                padding: PaddingSpec::Valid,\n            },\n            ConvOp {\n                stride: 1,\n                dilation: 2,\n                ker: tensor3(&[[[1f32, 2.0]]]),\n                padding: PaddingSpec::Valid,\n            },\n        ],\n    };\n    cpc.run().unwrap();\n}\n\n#[test]\nfn prob_2() {\n    let cpc = ConvPlusConvProblem {\n        input: t(10),\n        pulse: 2,\n        convs: vec![\n            ConvOp {\n                stride: 2,\n                dilation: 1,\n                ker: tensor3(&[[[0f32]]]),\n                padding: PaddingSpec::SameUpper,\n            },\n            ConvOp {\n                stride: 1,\n                dilation: 1,\n                ker: tensor3(&[[[1f32]]]),\n                padding: PaddingSpec::Valid,\n            },\n        ],\n    };\n    cpc.run().unwrap();\n}\n\n#[test]\nfn prob_3() {\n    let cpc = ConvPlusConvProblem {\n        input: t(10),\n        pulse: 1,\n        convs: vec![\n            ConvOp {\n                stride: 1,\n                dilation: 1,\n                ker: tensor3(&[[[0f32]]]),\n                padding: PaddingSpec::Valid,\n            },\n            ConvOp {\n                stride: 1,\n                dilation: 1,\n                ker: tensor3(&[[[1f32, 0f32]]]),\n                padding: PaddingSpec::SameUpper,\n            },\n        ],\n    };\n    cpc.run().unwrap();\n}\n\n#[test]\n#[ignore]\nfn prob_4() {\n    let cpc = ConvPlusConvProblem {\n        input: t(4),\n        pulse: 2,\n        convs: vec![\n            ConvOp {\n                stride: 1,\n                dilation: 1,\n                ker: tensor3(&[[[0f32]]]),\n                padding: PaddingSpec::Valid,\n            },\n            ConvOp {\n                stride: 2,\n                dilation: 1,\n                ker: tensor3(&[[[0f32, 0f32]]]),\n                padding: PaddingSpec::SameUpper,\n            },\n        ],\n    };\n    cpc.run().unwrap();\n}\n\n#[test]\nfn prob_7() {\n    let cpc = ConvPlusConvProblem {\n        input: t(4),\n        pulse: 4,\n        convs: vec![\n            ConvOp {\n                stride: 1,\n                dilation: 2,\n                ker: tensor3(&[[[0f32, 0.0]]]),\n                padding: PaddingSpec::Valid,\n            },\n            ConvOp {\n                stride: 2,\n                dilation: 1,\n                ker: tensor3(&[[[1f32]]]),\n                padding: PaddingSpec::Valid,\n            },\n        ],\n    };\n    cpc.run().unwrap();\n}\n\n#[test]\nfn same_upper() {\n    let cpc = ConvPlusConvProblem {\n        input: tensor3(&[[[0f32, 0., 0., 1.]]]),\n        pulse: 1,\n        convs: vec![ConvOp {\n            stride: 1,\n            dilation: 1,\n            ker: tensor3(&[[[1f32, 0.0]]]),\n            padding: PaddingSpec::SameUpper,\n        }],\n    };\n    cpc.run().unwrap();\n}\n\n#[test]\nfn stride() {\n    let cpc = ConvPlusConvProblem {\n        input: t(4),\n        pulse: 2,\n        convs: vec![ConvOp {\n            stride: 2,\n            dilation: 1,\n            ker: t(2),\n            padding: PaddingSpec::ExplicitOnnxPool(tvec!(1), tvec!(0), false),\n        }],\n    };\n    cpc.run().unwrap();\n}\n\n#[test]\nfn three() {\n    let cpc = ConvPlusConvProblem {\n        input: t(5),\n        pulse: 1,\n        convs: vec![\n            ConvOp { stride: 1, dilation: 2, ker: t(2), padding: PaddingSpec::Valid },\n            ConvOp { stride: 1, dilation: 1, ker: t(3), padding: PaddingSpec::Valid },\n            ConvOp {\n                stride: 1,\n                dilation: 1,\n                ker: t(2),\n                padding: PaddingSpec::ExplicitOnnxPool(tvec!(1), tvec!(0), false),\n            },\n        ],\n    };\n    cpc.run().unwrap();\n}\n\n#[test]\nfn three_stride() {\n    let cpc = ConvPlusConvProblem {\n        input: t(4),\n        pulse: 2,\n        convs: vec![\n            // 0 1 2 3\n            ConvOp { stride: 1, dilation: 1, ker: t(2), padding: PaddingSpec::Valid }, // overlap=1, 1 2 3  -> ∂=1\n            // pulse: x 1 | 2 3\n            ConvOp { stride: 1, dilation: 1, ker: t(1), padding: PaddingSpec::Valid }, // no delay, 0 0 0 -> ∂=1\n            // pulse: x 0 | 0 0\n            ConvOp { stride: 2, dilation: 2, ker: t(1), padding: PaddingSpec::Valid }, // 0 0\n                                                                                       // pulse 0 | 0\n        ],\n    };\n    cpc.run().unwrap();\n}\n"
  },
  {
    "path": "harness/core-proptest-pulse/src/deconv.rs",
    "content": "use proptest::proptest;\nuse proptest::test_runner::TestCaseResult;\n\nuse super::*;\n\n#[derive(Debug, Clone)]\nstruct DeconvOp {\n    stride: usize,\n    dilation: usize,\n    adj: usize,\n    ker: Array3<f32>,\n    padding: PaddingSpec,\n}\n\nimpl DeconvOp {\n    fn chain(&self, name: &str, model: &mut TypedModel, after: OutletId) -> OutletId {\n        let deconv = tract_core::ops::cnn::Deconv {\n            pool_spec: PoolSpec {\n                data_format: DataFormat::NCHW,\n                kernel_shape: tvec!(self.ker.shape()[2]),\n                padding: self.padding.clone(),\n                strides: Some(self.stride).filter(|d| *d > 1).map(|d| tvec!(d)),\n                dilations: Some(self.dilation).filter(|d| *d > 1).map(|d| tvec!(d)),\n                input_channels: self.ker.shape()[1],\n                output_channels: self.ker.shape()[0],\n            },\n            kernel_format: tract_core::ops::cnn::KernelFormat::OIHW,\n            adjustments: tvec!(self.adj),\n            group: 1,\n        };\n        let kernel = model.add_const(\"kernel\", self.ker.clone()).unwrap();\n        let bias = model.add_const(\"bias\", rctensor0(0f32)).unwrap();\n        model.wire_node(name, deconv, &[after, kernel, bias]).unwrap()[0]\n    }\n}\n\nimpl Arbitrary for DeconvOp {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n\n    fn arbitrary_with(_: Self::Parameters) -> BoxedStrategy<Self> {\n        (\n            1usize..4,\n            1usize..4,\n            0usize..4,\n            vec(1usize..4),\n            prop_oneof![\n                Just(PaddingSpec::Valid),\n                Just(PaddingSpec::SameUpper),\n                Just(PaddingSpec::SameLower)\n            ],\n        )\n            .prop_filter(\n                \"Same padding geometry constraint\",\n                |(stride, dilation, _adj, ker, padding)| {\n                    padding == &PaddingSpec::Valid || ((ker.len() - 1) * dilation > stride - 1)\n                },\n            )\n            .prop_map(|(stride, dilation, adj, ker, padding)| DeconvOp {\n                stride,\n                dilation,\n                adj,\n                ker: Array3::from_shape_vec((1, 1, ker.len()), ker).unwrap(),\n                padding,\n            })\n            .boxed()\n    }\n}\n\n#[derive(Debug, Clone)]\nstruct DeconvProblem {\n    input: Array3<f32>,\n    pulse: usize,\n    deconv: DeconvOp,\n}\n\nimpl Arbitrary for DeconvProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n\n    fn arbitrary_with(_: Self::Parameters) -> BoxedStrategy<Self> {\n        (DeconvOp::arbitrary(), 1usize..3)\n            .prop_flat_map(|(deconv, pulse_factor)| {\n                let pulse = deconv.stride * pulse_factor;\n                let min_input = 4usize;\n                (Just(deconv), Just(pulse), vec(min_input..3 * min_input))\n            })\n            .prop_map(|(deconv, pulse, input)| {\n                let input = Array3::from_shape_vec((1, 1, input.len()), input).unwrap(); // NCHW\n                DeconvProblem { input, pulse, deconv }\n            })\n            .boxed()\n    }\n}\n\nimpl DeconvProblem {\n    pub fn run(&self) -> TestCaseResult {\n        let mut model = TypedModel::default();\n        let mut fact = f32::fact(self.input.shape());\n        let s = model.symbols.sym(\"S\");\n        fact.shape.set(2, s.to_dim());\n        let input = model.add_source(\"a\", fact).unwrap();\n        let id = self.deconv.chain(\"deconv1\", &mut model, input);\n        model.select_output_outlets(&[id]).unwrap();\n        proptest_regular_against_pulse(model, self.pulse as _, self.input.clone().into_dyn(), 2)\n    }\n}\n\nproptest! {\n    #[test]\n    fn proptest(pb in DeconvProblem::arbitrary()) { pb.run().unwrap() }\n}\n\n#[test]\nfn example_0() {\n    let pb = DeconvProblem {\n        input: arr3(&[[[0.0f32, 0.0, 1.0, 0.0]]]),\n        pulse: 1,\n        deconv: DeconvOp {\n            stride: 1,\n            dilation: 1,\n            adj: 0,\n            ker: arr3(&[[[1.0f32]]]),\n            padding: PaddingSpec::Valid,\n        },\n    };\n    pb.run().unwrap()\n}\n\n#[test]\nfn example_1() {\n    let pb = DeconvProblem {\n        input: arr3(&[[[0.0f32, 0.0, 0.0, 0.0]]]),\n        pulse: 1,\n        deconv: DeconvOp {\n            stride: 1,\n            dilation: 1,\n            adj: 0,\n            ker: arr3(&[[[0.0f32, 0.0]]]),\n            padding: PaddingSpec::Valid,\n        },\n    };\n    pb.run().unwrap()\n}\n\n#[test]\nfn example_2() {\n    let pb = DeconvProblem {\n        input: arr3(&[[[0.0f32, 0.0, 0.0, 1.0]]]),\n        pulse: 1,\n        deconv: DeconvOp {\n            stride: 1,\n            dilation: 1,\n            adj: 0,\n            ker: arr3(&[[[0.0f32, 1.0]]]),\n            padding: PaddingSpec::Valid,\n        },\n    };\n    pb.run().unwrap()\n}\n\n#[test]\nfn example_3() {\n    let pb = DeconvProblem {\n        input: arr3(&[[[0.0f32, 0.0, 0.0, 0.0, 1.0]]]),\n        pulse: 2,\n        deconv: DeconvOp {\n            stride: 1,\n            dilation: 1,\n            adj: 0,\n            ker: arr3(&[[[0.0f32, 1.0]]]),\n            padding: PaddingSpec::Valid,\n        },\n    };\n    pb.run().unwrap()\n}\n\n#[test]\nfn dilation_0() {\n    let pb = DeconvProblem {\n        input: arr3(&[[[0.0f32, 0.0, 0.0, 0.0]]]),\n        pulse: 1,\n        deconv: DeconvOp {\n            stride: 1,\n            dilation: 2,\n            adj: 0,\n            ker: arr3(&[[[0.0f32, 0.0]]]),\n            padding: PaddingSpec::Valid,\n        },\n    };\n    pb.run().unwrap()\n}\n\n#[test]\nfn dilation_1() {\n    let pb = DeconvProblem {\n        input: arr3(&[[[0.0f32, 0.0, 1.0, 0.0]]]),\n        pulse: 1,\n        deconv: DeconvOp {\n            stride: 1,\n            dilation: 2,\n            adj: 0,\n            ker: arr3(&[[[0.0f32, 1.0]]]),\n            padding: PaddingSpec::SameUpper,\n        },\n    };\n    pb.run().unwrap()\n}\n\n#[test]\nfn stride_0() {\n    let pb = DeconvProblem {\n        input: arr3(&[[[0.0f32, 0.0, 0.0, 1.0]]]),\n        pulse: 2,\n        deconv: DeconvOp {\n            stride: 2,\n            dilation: 1,\n            adj: 0,\n            ker: arr3(&[[[1.0f32]]]),\n            padding: PaddingSpec::Valid,\n        },\n    };\n    pb.run().unwrap()\n}\n\n#[test]\nfn same_upper_0() {\n    let pb = DeconvProblem {\n        input: arr3(&[[[0.0f32, 0.0, 0.0, 1.0]]]),\n        pulse: 1,\n        deconv: DeconvOp {\n            stride: 1,\n            dilation: 1,\n            adj: 0,\n            ker: arr3(&[[[0.0f32, 1.0]]]),\n            padding: PaddingSpec::SameUpper,\n        },\n    };\n    pb.run().unwrap()\n}\n\n#[test]\nfn adj_0() {\n    let pb = DeconvProblem {\n        input: arr3(&[[[0.0f32, 0.0, 0.0, 0.0]]]),\n        pulse: 1,\n        deconv: DeconvOp {\n            stride: 1,\n            dilation: 1,\n            adj: 1,\n            ker: arr3(&[[[0.0f32]]]),\n            padding: PaddingSpec::Valid,\n        },\n    };\n    pb.run().unwrap()\n}\n\n#[test]\nfn deconv2d() {\n    let mut model = TypedModel::default();\n    let s = model.symbols.sym(\"S\");\n    let a = model.add_source(\"a\", f32::fact(dims!(1, 2, s, 8))).unwrap();\n    let mut kernel = Tensor::zero::<f32>(&[2, 2, 1, 3]).unwrap();\n    kernel\n        .try_as_plain_mut()\n        .unwrap()\n        .as_slice_mut::<f32>()\n        .unwrap()\n        .iter_mut()\n        .enumerate()\n        .for_each(|(ix, x)| *x = ix as f32);\n    let deconv = tract_core::ops::cnn::Deconv {\n        pool_spec: PoolSpec {\n            data_format: DataFormat::NCHW,\n            kernel_shape: tvec!(1, 3),\n            padding: PaddingSpec::Explicit(tvec!(0, 1), tvec!(0, 1)),\n            strides: Some(tvec!(1, 2)),\n            dilations: Some(tvec![1, 1]),\n            input_channels: 2,\n            output_channels: 2,\n        },\n        kernel_format: tract_core::ops::cnn::KernelFormat::OIHW,\n        adjustments: tvec!(0, 0),\n        group: 1,\n    };\n    let kernel = model.add_const(\"kernel\", kernel).unwrap();\n    let bias = model.add_const(\"bias\", rctensor0(0f32)).unwrap();\n    let deconv = model.wire_node(\"deconv\", deconv, &[a, kernel, bias]).unwrap();\n    model.select_output_outlets(&deconv).unwrap();\n    model.declutter().unwrap();\n\n    let mut input = Tensor::zero::<f32>(&[1, 2, 5, 8]).unwrap();\n    input\n        .try_as_plain_mut()\n        .unwrap()\n        .as_slice_mut::<f32>()\n        .unwrap()\n        .iter_mut()\n        .enumerate()\n        .for_each(|(ix, x)| *x = ix as f32);\n    proptest_regular_against_pulse(model, 1, input.into_plain_array().unwrap(), 2).unwrap()\n}\n"
  },
  {
    "path": "harness/core-proptest-pulse/src/delay_plus_downsample.rs",
    "content": "use proptest::proptest;\nuse proptest::test_runner::TestCaseResult;\nuse tract_core::ops::Downsample;\nuse tract_core::tract_data::itertools::Itertools;\n\nuse super::*;\n\n#[derive(Debug, Clone)]\nstruct DelayPlusDownsampleProblem {\n    input: usize,\n    pulse: usize,\n    delay: usize,\n    stride: usize,\n    modulo: usize,\n}\n\nfn t(n: usize) -> ArrayD<f32> {\n    arr1(&(0..n).map(|x| x as f32).collect_vec()).into_shape_with_order(vec![1, n, 1]).unwrap()\n}\n\nimpl Arbitrary for DelayPlusDownsampleProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n\n    fn arbitrary_with(_: Self::Parameters) -> BoxedStrategy<Self> {\n        (1usize..100, 1usize..4, 0usize..100, 1usize..4)\n            .prop_flat_map(|(input, pulse_mul, delay, stride)| {\n                (\n                    Just(input + stride + delay),\n                    Just(pulse_mul * stride),\n                    Just(delay),\n                    Just(stride),\n                    0..stride,\n                )\n            })\n            .prop_map(|(input, pulse, delay, stride, modulo)| DelayPlusDownsampleProblem {\n                input,\n                pulse,\n                delay,\n                stride,\n                modulo,\n            })\n            .boxed()\n    }\n}\n\nimpl DelayPlusDownsampleProblem {\n    pub fn run(&self) -> TestCaseResult {\n        let mut model = TypedModel::default();\n        let s = model.symbols.sym(\"S\");\n        let a = model.add_source(\"a\", f32::fact(dims!(1, s, 1))).unwrap();\n        let crop =\n//            model.wire_node(\"delay\", expand(array::Crop::new(1, self.delay, 0)), &[a]).unwrap();\n            model.wire_node(\"delay\", Slice::new(1, self.delay, s), &[a]).unwrap();\n        let ds = model\n            .wire_node(\n                \"ds\",\n                Downsample { axis: 1, stride: self.stride as isize, modulo: self.modulo },\n                &crop,\n            )\n            .unwrap();\n        model.select_output_outlets(&ds).unwrap();\n        proptest_regular_against_pulse(model, self.pulse as _, t(self.input), 1)\n    }\n}\n\nproptest! {\n    #[test]\n    fn proptest(pb in DelayPlusDownsampleProblem::arbitrary()) { pb.run().unwrap() }\n}\n\n#[test]\nfn test_modulo() {\n    DelayPlusDownsampleProblem { input: 3, pulse: 2, delay: 0, stride: 2, modulo: 1 }.run().unwrap()\n}\n\n#[test]\nfn test_delay() {\n    DelayPlusDownsampleProblem { input: 3, pulse: 2, delay: 1, stride: 2, modulo: 0 }.run().unwrap()\n}\n\n#[test]\nfn test_from_convs() {\n    DelayPlusDownsampleProblem { input: 5, pulse: 2, delay: 1, stride: 2, modulo: 0 }\n        .run()\n        .unwrap();\n}\n\n#[test]\nfn test_delayed_stride() {\n    DelayPlusDownsampleProblem { input: 9, pulse: 2, delay: 1, stride: 2, modulo: 0 }.run().unwrap()\n}\n\n#[test]\nfn test_big_delay() {\n    DelayPlusDownsampleProblem { input: 6, pulse: 1, delay: 4, stride: 1, modulo: 0 }.run().unwrap()\n}\n\n#[test]\nfn test_huge_delay() {\n    DelayPlusDownsampleProblem { input: 4, pulse: 2, delay: 1, stride: 2, modulo: 0 }.run().unwrap()\n}\n"
  },
  {
    "path": "harness/core-proptest-pulse/src/delay_plus_pool.rs",
    "content": "use proptest::proptest;\nuse proptest::test_runner::TestCaseResult;\nuse tract_core::ops::cnn::MaxPool;\n\nuse super::*;\n\n#[derive(Debug, Clone)]\nstruct DelayPlusPoolProblem {\n    input: Vec<f32>,\n    pulse: usize,\n    delay: usize,\n    stride: usize,\n    pool_window: usize,\n    padding: PaddingSpec,\n}\n\nimpl Arbitrary for DelayPlusPoolProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n\n    fn arbitrary_with(_: Self::Parameters) -> BoxedStrategy<Self> {\n        (1usize..4, 1usize..4, 0usize..5, 1usize..4)\n            .prop_flat_map(|(pool_window, factor, delay, stride)| {\n                let padding = pool_window - 1;\n                let explicit = (0..=padding).prop_map(move |right| {\n                    PaddingSpec::ExplicitOnnxPool(tvec!(padding - right), tvec!(right), false)\n                });\n                let min_input = delay + pool_window;\n                (\n                    Just(pool_window),\n                    Just(factor),\n                    Just(delay),\n                    Just(stride),\n                    vec(min_input..min_input + 10),\n                    prop_oneof![Just(PaddingSpec::Valid), explicit],\n                )\n            })\n            .prop_map(|(pool_window, factor, delay, stride, input, padding)| {\n                let pulse = factor * stride;\n                DelayPlusPoolProblem { input, pulse, delay, stride, pool_window, padding }\n            })\n            .boxed()\n    }\n}\n\nimpl DelayPlusPoolProblem {\n    pub fn run(&self) -> TestCaseResult {\n        let mut model = TypedModel::default();\n        let s = model.symbols.sym(\"S\");\n        let a = model.add_source(\"a\", f32::fact(dims!(1, s, 1))).unwrap();\n        let crop = model.wire_node(\"delay\", Slice::new(1, self.delay, s), &[a]).unwrap();\n        let pool_spec = PoolSpec::new(\n            DataFormat::NHWC,\n            tvec!(self.pool_window),\n            self.padding.clone(),\n            None,\n            Some(tvec!(self.stride)),\n            1,\n            1,\n        );\n        let pool = model.wire_node(\"pool\", MaxPool::new(pool_spec, None), &crop).unwrap();\n        model.select_output_outlets(&pool).unwrap();\n        let input =\n            arr1(&self.input).into_shape_with_order((1, self.input.len(), 1)).unwrap().into_dyn();\n        proptest_regular_against_pulse(model, self.pulse as _, input, 1)\n    }\n}\n\nproptest! {\n    #[test]\n    fn proptest(pb in DelayPlusPoolProblem::arbitrary()) { pb.run().unwrap() }\n}\n\n#[test]\nfn test_basic() {\n    DelayPlusPoolProblem {\n        input: vec![0.0, 0.0, 0.0, 0.0, 1.0],\n        pulse: 2,\n        delay: 0,\n        stride: 1,\n        pool_window: 2,\n        padding: PaddingSpec::Valid,\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn test_stride() {\n    DelayPlusPoolProblem {\n        input: vec![0.0, 0.0, 0.0],\n        pulse: 2,\n        delay: 0,\n        stride: 2,\n        pool_window: 1,\n        padding: PaddingSpec::Valid,\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn test_misaligned_stride() {\n    DelayPlusPoolProblem {\n        input: vec![0.0, 1.0],\n        pulse: 2,\n        delay: 1,\n        stride: 2,\n        pool_window: 1,\n        padding: PaddingSpec::Valid,\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn test_overlap() {\n    DelayPlusPoolProblem {\n        input: vec![0.0, 1.0],\n        pulse: 1,\n        delay: 0,\n        stride: 1,\n        pool_window: 2,\n        padding: PaddingSpec::Valid,\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn test_overlap_realign() {\n    DelayPlusPoolProblem {\n        input: vec![f32::NAN, 2.0, 3.0, 4.0, 5.0, 6.0],\n        pulse: 2,\n        delay: 1,\n        stride: 2,\n        pool_window: 3,\n        padding: PaddingSpec::Valid,\n    }\n    .run()\n    .unwrap();\n}\n\n#[test]\nfn test_long_overlap_1() {\n    DelayPlusPoolProblem {\n        input: vec![0.0, 0.0, 0.0],\n        pulse: 1,\n        delay: 0,\n        stride: 1,\n        pool_window: 3,\n        padding: PaddingSpec::Valid,\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn test_long_overlap_2() {\n    DelayPlusPoolProblem {\n        input: vec![0.0, 0.0, 0.0, 0.0],\n        pulse: 1,\n        delay: 2,\n        stride: 1,\n        pool_window: 2,\n        padding: PaddingSpec::Valid,\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn test_long_overlap_3() {\n    DelayPlusPoolProblem {\n        input: vec![-1.0, -1.0, 0.0],\n        pulse: 2,\n        delay: 0,\n        stride: 2,\n        pool_window: 3,\n        padding: PaddingSpec::Valid,\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn test_pad_right() {\n    DelayPlusPoolProblem {\n        input: vec![0.0, 0.0],\n        pulse: 1,\n        delay: 0,\n        stride: 1,\n        pool_window: 2,\n        padding: PaddingSpec::ExplicitOnnxPool(tvec!(0), tvec!(1), false),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn test_pad_right_2() {\n    DelayPlusPoolProblem {\n        input: vec![f32::NAN, 0.0, 1.0],\n        pulse: 2,\n        delay: 1,\n        stride: 2,\n        pool_window: 2,\n        padding: PaddingSpec::ExplicitOnnxPool(tvec!(0), tvec!(1), false),\n    }\n    .run()\n    .unwrap()\n}\n"
  },
  {
    "path": "harness/core-proptest-pulse/src/einsum.rs",
    "content": "use tract_core::ops::einsum::*;\n\nuse super::*;\n\n#[test]\nfn einsum_pulsedmm() {\n    let mut model = TypedModel::default();\n    let s = model.symbols.sym(\"S\");\n    let x = model.add_source(\"x\", f32::fact(dims!(s, 8, 2))).unwrap();\n    let w = model.add_const(\"w\", Tensor::zero::<f32>(&[8, 2, 4]).unwrap()).unwrap();\n\n    let expr = \"sij,ijk->sik\".parse().unwrap();\n    let einsum = EinSum { axes: expr, operating_dt: f32::datum_type(), q_params: None };\n\n    let einsum = model.wire_node(\"einsum\", einsum, &[x, w]).unwrap();\n    model.select_output_outlets(&einsum).unwrap();\n    model.declutter().unwrap();\n\n    let mut input = Tensor::zero::<f32>(&[5, 8, 2]).unwrap();\n    input\n        .try_as_plain_mut()\n        .unwrap()\n        .as_slice_mut::<f32>()\n        .unwrap()\n        .iter_mut()\n        .enumerate()\n        .for_each(|(ix, x)| *x = ix as f32);\n    proptest_regular_against_pulse(model, 1, input.into_plain_array().unwrap(), 0).unwrap()\n}\n"
  },
  {
    "path": "harness/core-proptest-pulse/src/lib.rs",
    "content": "#![cfg(test)]\n\n#[macro_use]\nextern crate log;\n\nuse proptest::prelude::*;\nuse proptest::proptest;\nuse proptest::test_runner::TestCaseResult;\nuse proptest::*;\nuse tract_core::ndarray::arr3;\nuse tract_core::num_traits::Zero;\nuse tract_core::ops::array::Pad;\nuse tract_core::ops::array::PadMode;\nuse tract_core::ops::array::Slice;\nuse tract_core::ops::cnn::Conv;\nuse tract_core::ops::cnn::KernelFormat;\nuse tract_core::ops::cnn::PaddingSpec;\nuse tract_core::ops::cnn::PoolSpec;\nuse tract_core::ops::nn::DataFormat;\nuse tract_ndarray::prelude::*;\nuse tract_pulse::internal::*;\n\nmod conv_plus_conv;\nmod deconv;\nmod delay_plus_downsample;\nmod delay_plus_pool;\nmod einsum;\nmod pad_plus_conv;\n\n#[allow(dead_code)]\nfn setup_test_logger() {\n    let _ = env_logger::Builder::from_env(\"TRACT_LOG\").try_init();\n}\n\nfn proptest_regular_against_pulse(\n    model: TypedModel,\n    pulse: usize,\n    input_array: tract_ndarray::ArrayD<f32>,\n    axis: usize,\n) -> TestCaseResult {\n    setup_test_logger();\n\n    let len = input_array.shape()[axis];\n    let model = model.into_decluttered().unwrap();\n    // dbg!(&model);\n    let s = model.symbols.sym(\"S\");\n    let symbols = SymbolValues::default().with(&s, len as i64);\n\n    let concrete = model.clone().concretize_dims(&symbols).unwrap();\n    if concrete.nodes.iter().any(|n| n.outputs.iter().any(|o| o.fact.shape.volume().is_zero())) {\n        return Err(TestCaseError::reject(\"too short input\"));\n    }\n    let runnable = concrete.into_runnable().unwrap();\n\n    // dbg!(&runnable);\n    let outputs = runnable.run(tvec!(input_array.clone().into_tvalue())).unwrap();\n    // dbg!(&outputs);\n    debug!(\"Build pulsing model\");\n    // dbg!(&model);\n    let pulsed = PulsedModel::new(&model, s.clone(), &pulse.to_dim()).unwrap();\n    // dbg!(&pulsed);\n    let output_fact = pulsed.output_fact(0).unwrap().clone();\n\n    let stream_info = output_fact.stream.as_ref().unwrap();\n    prop_assert!(stream_info.dim.eval(&symbols) == outputs[0].shape()[stream_info.axis].to_dim());\n    let output_stream_axis = stream_info.axis;\n    let delay = stream_info.delay;\n    let mut initial_output_shape = output_fact.shape.clone();\n    initial_output_shape.set(output_stream_axis, 0.to_dim());\n    let initial_output_shape: TVec<usize> =\n        initial_output_shape.iter().map(|d| d.to_usize().unwrap()).collect();\n\n    let pulsed_plan = SimplePlan::new(pulsed).unwrap();\n    let mut state = SimpleState::new(&pulsed_plan).unwrap();\n\n    let mut got: ArrayD<f32> = ArrayD::zeros(&*initial_output_shape);\n    let mut output_len = None;\n\n    debug!(\"Run pulsing model\");\n    //dbg!(pulsed_plan.model());\n    let mut written = 0;\n    loop {\n        let to_write_in_chunk = pulse.min(input_array.shape()[axis].saturating_sub(written));\n        let mut chunk: ArrayD<f32> = input_array\n            .slice_axis(Axis(axis), (written..written + to_write_in_chunk).into())\n            .to_owned();\n        written += to_write_in_chunk;\n        if to_write_in_chunk < pulse {\n            let mut filler_shape = input_array.shape().to_vec();\n            filler_shape[axis] = pulse - to_write_in_chunk;\n            chunk = tract_ndarray::concatenate(\n                Axis(axis),\n                &[chunk.view(), ArrayD::from_elem(filler_shape, f32::NAN).view()],\n            )\n            .unwrap();\n            state.turn_state.resolved_symbols.set(&s, written as i64);\n            output_len = stream_info\n                .dim\n                .eval(&state.turn_state.resolved_symbols)\n                .to_isize()\n                .ok()\n                .map(|n| n.max(0) as usize);\n        }\n        let mut outputs = state.run(tvec!(chunk.into_tensor().into_tvalue())).unwrap();\n        got = tract_ndarray::concatenate(\n            Axis(output_stream_axis),\n            &[got.view(), outputs.remove(0).to_plain_array_view::<f32>().unwrap()],\n        )\n        .unwrap();\n        eprintln!(\"GOT: {got}\");\n        if let Some(output_len) = output_len {\n            if got.shape()[output_stream_axis] >= output_len.max(0) + delay {\n                break;\n            }\n        }\n        eprintln!();\n    }\n\n    let pulsed_output = got\n        .slice_axis(\n            Axis(output_stream_axis),\n            (stream_info.delay..stream_info.delay + output_len.unwrap().max(0)).into(),\n        )\n        .to_owned()\n        .into_tensor();\n\n    prop_assert!(\n        &pulsed_output.close_enough(&outputs[0], true).is_ok(),\n        \"{:?} == {:?}\",\n        pulsed_output,\n        outputs[0]\n    );\n    Ok(())\n}\n\nproptest! {\n    #[test]\n    fn proptest_crop(pulse in 1i32..3, input_len in 0i32..10, begin in 0i32..3, end in 0i32..3) {\n        let full_len = input_len + begin + end;\n        let mut model = TypedModel::default();\n        let s = model.symbols.sym(\"S\");\n        let a = model.add_source(\"a\", f32::fact(&[s])).unwrap();\n        let slice = model.wire_node(\"slice\", Slice::new(0, begin as usize, (input_len + begin) as usize), &[a]).unwrap();\n        model.select_output_outlets(&slice).unwrap();\n\n        let input = Array1::range(1.0f32, full_len as f32 + 1.0, 1.0);\n        proptest_regular_against_pulse(model, pulse as _, input.into_dyn(), 0)?;\n    }\n\n    #[test]\n    fn proptest_pad(pulse in 1i32..3, input_len in 0i32..10, begin in 0i32..3, end in 0i32..3) {\n        let mut model = TypedModel::default();\n        let s = model.symbols.sym(\"S\");\n        let a = model.add_source(\"a\", f32::fact(&[s])).unwrap();\n        let pad = model.wire_node(\"pad\", Pad::new(vec![(begin as _, end as _)],\n        PadMode::Constant(Arc::new(Tensor::from(-1f32)))), &[a]).unwrap();\n        model.select_output_outlets(&pad).unwrap();\n\n        let input = Array1::range(1.0f32, input_len as f32 + 1.0, 1.0);\n        proptest_regular_against_pulse(model, pulse as _, input.into_dyn(), 0)?;\n    }\n}\n\nfn vec(len: impl Strategy<Value = usize>) -> impl Strategy<Value = Vec<f32>> {\n    len.prop_flat_map(|l| proptest::collection::vec(-5..5, l..=l))\n        .prop_map(|v| v.into_iter().map(|f| f as f32).collect())\n}\n\n#[test]\nfn test_simple_conv() {\n    let mut model = TypedModel::default();\n    let kernel = rctensor3(&[[[0.5f32, 1.0, -0.1]]]);\n    let s = model.symbols.sym(\"S\");\n    let a = model.add_source(\"a\", f32::fact(dims!(1, 1, s))).unwrap();\n    let kernel = model.add_const(\"kernel\", kernel).unwrap();\n    let bias = model.add_const(\"bias\", tensor0(0f32)).unwrap();\n\n    model\n        .wire_node(\n            \"conv\",\n            Conv {\n                pool_spec: PoolSpec {\n                    data_format: DataFormat::NCHW,\n                    kernel_shape: tvec!(3),\n                    padding: PaddingSpec::Valid,\n                    dilations: None,\n                    strides: None,\n                    input_channels: 1,\n                    output_channels: 1,\n                },\n                kernel_fmt: KernelFormat::OIHW,\n                group: 1,\n                q_params: None,\n            },\n            &[a, kernel, bias],\n        )\n        .unwrap();\n    model.auto_outputs().unwrap();\n\n    let input = arr3(&[[[1.0f32, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0]]]);\n    proptest_regular_against_pulse(model, 4, input.into_dyn(), 2).unwrap();\n}\n\n#[test]\nfn test_pad_before_1() {\n    let mut model = TypedModel::default();\n    let s = model.symbols.sym(\"S\");\n    let a = model.add_source(\"a\", f32::fact(&[s])).unwrap();\n    model\n        .wire_node(\n            \"pad\",\n            Pad::new(vec![(1, 0)], PadMode::Constant(Arc::new(Tensor::from(-1f32)))),\n            &[a],\n        )\n        .unwrap();\n    model.auto_outputs().unwrap();\n\n    let input = arr1(&[1.0]);\n    proptest_regular_against_pulse(model, 1, input.into_dyn(), 0).unwrap();\n}\n\n#[test]\nfn test_pad_before_2() {\n    let mut model = TypedModel::default();\n    let s = model.symbols.sym(\"S\");\n    let a = model.add_source(\"a\", f32::fact(&[s])).unwrap();\n    model\n        .wire_node(\n            \"pad\",\n            Pad::new(vec![(1, 0)], PadMode::Constant(Arc::new(Tensor::from(-1f32)))),\n            &[a],\n        )\n        .unwrap();\n    model.auto_outputs().unwrap();\n\n    let input = arr1(&[1.0, 2.0]);\n    proptest_regular_against_pulse(model, 2, input.into_dyn(), 0).unwrap();\n}\n"
  },
  {
    "path": "harness/core-proptest-pulse/src/pad_plus_conv.rs",
    "content": "use proptest::proptest;\nuse proptest::test_runner::TestCaseResult;\nuse proptest::*;\n\nuse super::*;\n\n#[derive(Debug, Clone)]\nstruct PadPlusConvProblem {\n    pad_before: usize,\n    pad_after: usize,\n    pad_mode: PadMode,\n    stride: usize,\n    dilation: usize,\n    pulse: usize,\n    ker: Array3<f32>,\n    input: Array3<f32>,\n}\n\nimpl Arbitrary for PadPlusConvProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n\n    fn arbitrary_with(_: Self::Parameters) -> BoxedStrategy<PadPlusConvProblem> {\n        (1usize..3, vec(1usize..3), 1usize..3, 0usize..15, 0usize..15, 1usize..3, bool::ANY)\n            .prop_flat_map(|(stride, ker, dil, pad_before, pad_after, pulse_factor, edge)| {\n                let min_input = (ker.len() * dil).max(pulse_factor * stride);\n                (\n                    Just(stride),\n                    Just(ker),\n                    Just(dil),\n                    Just(pad_before),\n                    Just(pad_after),\n                    Just(stride * pulse_factor),\n                    vec(min_input..3 * min_input),\n                    Just(edge),\n                )\n            })\n            .prop_map(|(stride, ker, dilation, pad_before, pad_after, pulse, input, edge)| {\n                let pad_mode = if edge && pad_before < pulse {\n                    PadMode::Edge\n                } else {\n                    PadMode::Constant(Tensor::from(9999f32).into())\n                };\n                let input = Array3::from_shape_vec((1, 1, input.len()), input).unwrap(); // NCHW\n                let ker = Array3::from_shape_vec((1, 1, ker.len()), ker).unwrap(); // OIHW\n                PadPlusConvProblem {\n                    pad_before,\n                    pad_after,\n                    pad_mode,\n                    stride,\n                    dilation,\n                    pulse,\n                    ker,\n                    input,\n                }\n            })\n            .boxed()\n    }\n}\n\nimpl PadPlusConvProblem {\n    pub fn run(&self) -> TestCaseResult {\n        let mut model = TypedModel::default();\n        let s = model.symbols.sym(\"S\");\n        let mut wire = model.add_source(\"a\", f32::fact(dims!(1, 1, s))).unwrap();\n        if self.pad_before > 0 || self.pad_after > 0 {\n            wire = model\n                .wire_node(\n                    \"pad\",\n                    Pad::new(\n                        vec![(0, 0), (0, 0), (self.pad_before, self.pad_after)],\n                        self.pad_mode.clone(),\n                    ),\n                    &[wire],\n                )\n                .unwrap()[0];\n        }\n        let kernel = model.add_const(\"kernel\", self.ker.clone()).unwrap();\n        let bias = model.add_const(\"bias\", tensor0(0f32)).unwrap();\n        let conv = model\n            .wire_node(\n                \"conv\",\n                Conv {\n                    pool_spec: PoolSpec {\n                        data_format: DataFormat::NCHW,\n                        kernel_shape: self.ker.shape()[2..].into(),\n                        padding: PaddingSpec::Valid,\n                        dilations: Some(tvec!(self.dilation)),\n                        strides: Some(tvec!(self.stride)),\n                        input_channels: 1,\n                        output_channels: 1,\n                    },\n                    kernel_fmt: tract_core::ops::cnn::KernelFormat::OIHW,\n                    group: 1,\n                    q_params: None,\n                },\n                &[wire, kernel, bias],\n            )\n            .unwrap();\n        model.select_output_outlets(&conv).unwrap();\n        proptest_regular_against_pulse(model, self.pulse as _, self.input.clone().into_dyn(), 2)\n    }\n}\n\nproptest! {\n    #[test]\n    fn proptest_conv(pb in PadPlusConvProblem::arbitrary()) { pb.run().unwrap() }\n}\n\n#[test]\nfn conv_1() {\n    PadPlusConvProblem {\n        pad_before: 0,\n        pad_after: 0,\n        pad_mode: PadMode::Constant(tensor0(9999f32).into()),\n        stride: 1,\n        dilation: 1,\n        pulse: 1,\n        ker: arr3(&[[[0.0f32]]]),\n        input: arr3(&[[[0.0f32, 0.0]]]),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn conv_2() {\n    PadPlusConvProblem {\n        pad_before: 0,\n        pad_after: 0,\n        pad_mode: PadMode::Constant(tensor0(9999f32).into()),\n        stride: 2,\n        dilation: 2,\n        pulse: 2,\n        ker: arr3(&[[[0.0f32]]]),\n        input: arr3(&[[[0.0f32, 0.0]]]),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn conv_3() {\n    PadPlusConvProblem {\n        pad_before: 0,\n        pad_after: 0,\n        pad_mode: PadMode::Constant(tensor0(9999f32).into()),\n        stride: 2,\n        dilation: 1,\n        pulse: 2,\n        ker: arr3(&[[[0.0f32]]]),\n        input: arr3(&[[[0.0f32, 0.0, 0.0]]]),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn conv_4() {\n    PadPlusConvProblem {\n        pad_before: 0,\n        pad_after: 0,\n        pad_mode: PadMode::Constant(tensor0(9999f32).into()),\n        stride: 2,\n        dilation: 2,\n        pulse: 2,\n        ker: arr3(&[[[0.0f32]]]),\n        input: arr3(&[[[0.0f32, 0.0, 0.0]]]),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn conv_5() {\n    PadPlusConvProblem {\n        pad_before: 2,\n        pad_after: 0,\n        pad_mode: PadMode::Constant(tensor0(9999f32).into()),\n        stride: 2,\n        dilation: 1,\n        pulse: 2,\n        ker: arr3(&[[[0.0f32, 1.0]]]),\n        input: arr3(&[[[1.0f32, 0.0]]]),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn conv_6() {\n    PadPlusConvProblem {\n        pad_before: 0,\n        pad_after: 0,\n        pad_mode: PadMode::Constant(tensor0(9999f32).into()),\n        stride: 2,\n        dilation: 1,\n        pulse: 2,\n        ker: arr3(&[[[0.0f32]]]),\n        input: arr3(&[[[0.0f32, 0.0, 0.0]]]),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn conv_7() {\n    PadPlusConvProblem {\n        pad_before: 0,\n        pad_after: 1,\n        pad_mode: PadMode::Edge,\n        stride: 1,\n        dilation: 1,\n        pulse: 1,\n        ker: arr3(&[[[0.0f32]]]),\n        input: arr3(&[[[0.0f32]]]),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn conv_8() {\n    PadPlusConvProblem {\n        pad_before: 1,\n        pad_after: 0,\n        pad_mode: PadMode::Edge,\n        stride: 2,\n        dilation: 2,\n        pulse: 2,\n        ker: arr3(&[[[0.0f32]]]),\n        input: arr3(&[[[0.0f32, 0.0f32]]]),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn conv_kaldi_librispeech() {\n    PadPlusConvProblem {\n        pad_before: 5,\n        pad_after: 15,\n        pad_mode: PadMode::Edge,\n        stride: 3,\n        dilation: 1,\n        pulse: 9,\n        ker: arr3(&[[[1f32, 0f32, 0f32, 0f32, 0f32]]]),\n        input: Array3::from_shape_vec((1, 1, 10), (1..=10).map(|i| i as f32).collect()).unwrap(),\n    }\n    .run()\n    .unwrap()\n}\n\n#[test]\nfn conv_9() {\n    PadPlusConvProblem {\n        pad_before: 13,\n        pad_after: 9,\n        pad_mode: PadMode::Constant(rctensor0(9999f32)),\n        stride: 2,\n        dilation: 2,\n        pulse: 2,\n        ker: arr3(&[[[0.0f32, 0.0]]]),\n        input: arr3(&[[[0.0f32, 0.0, 0.0, 0.0]]]),\n    }\n    .run()\n    .unwrap()\n}\n"
  },
  {
    "path": "harness/nemotron-speech-streaming-en-0.6b/ci.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nROOT=$(realpath $(dirname $(realpath $0))/../..)\n. $ROOT/.travis/ci-system-setup.sh\n\nMODEL=nvidia--nemotron-speech-streaming-en-0.6b-f32f32\nS3DIR=asr/613/$MODEL\n\nfor rt in $TRACT_RUNTIMES\ndo\n\tgpu_assert=\"\"\n\tcase \"$rt\" in\n\t\t--cuda) gpu_assert=\"--assert-op-only Cuda*,Gpu*,DeviceSync*,Const,Source,STFT,Pad,IsNan,Add,Range,Cast,Eq,Div,Sub,Scan,Gather\";;\n\t\t--metal) gpu_assert=\"--assert-op-only Metal*,Gpu*,DeviceSync*,Const,Source,STFT,Pad,IsNan,Add,Range,Cast,Eq,Div,Sub,Scan,Gather,Reduce*\";;\n\tesac\n\n\tfor m in preprocessor encoder decoder joint\n\tdo\n\t\t# Encoder uses a patched model with upper bound assertion on AUDIO_SIGNAL__TIME\n\t\tif [ \"$m\" = \"encoder\" ]; then\n\t\t\tnnef_file=$MODEL.$m.p1.nnef.tgz\n\t\telse\n\t\t\tnnef_file=$MODEL.$m.nnef.tgz\n\t\tfi\n\t\t$CACHE_FILE \\\n\t\t\t$S3DIR/$nnef_file \\\n\t\t\t$S3DIR/$MODEL.$m.io.npz\n\n\t\t$TRACT_RUN $MODELS/$S3DIR/$nnef_file $rt --nnef-tract-transformers -t transformers_detect_all run \\\n\t\t\t--input-from-bundle $MODELS/$S3DIR/$MODEL.$m.io.npz --assert-output-bundle $MODELS/$S3DIR/$MODEL.$m.io.npz \\\n\t\t\t--approx very $gpu_assert\n\tdone\ndone\n\nmodel_prefix=$MODELS/$S3DIR/$MODEL\n\n# Check that the patch transform eliminates all Iff nodes,\n# and that select_outputs can reduce the model to a single output\n$TRACT_RUN $model_prefix.preprocessor.nnef.tgz \\\n\t-t 'concretize_symbols(values: {\"BATCH\": 1})' \\\n\t-t 'patch(body: \"length = tract_core_shape_of(input_signal)[1];\")' \\\n\t-t 'select_outputs(outputs: [\"processed_signal\"])' \\\n\tdump -q \\\n\t--assert-op-count Iff 0\n\n# Check that the preprocessor can be pulsified\n$TRACT_RUN $model_prefix.preprocessor.nnef.tgz \\\n\t-t 'concretize_symbols(values: {\"BATCH\": 1})' \\\n\t-t 'patch(body: \"length = tract_core_shape_of(input_signal)[1];\")' \\\n\t-t 'select_outputs(outputs: [\"processed_signal\"])' \\\n\t-t 'pulse(symbol: Some(\"INPUT_SIGNAL__TIME\"), pulse: \"4800\")' \\\n\tdump -q\n"
  },
  {
    "path": "harness/nnef-inceptionv3/Cargo.toml",
    "content": "[package]\nname = \"nnef-inceptionv3\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nflate2.workspace = true\nimage.workspace = true\ntract-core.workspace = true\ntract-nnef.workspace = true\n\n[dev-dependencies]\ndinghy-test.workspace = true\nenv_logger.workspace = true\nlog.workspace = true\n"
  },
  {
    "path": "harness/nnef-inceptionv3/download.sh",
    "content": "#!/bin/sh\n\nMY_DIR=`dirname $0`\n\n$MY_DIR/../../.travis/cache_file.sh inception_v3.tfpb.nnef.tgz imagenet_slim_labels.txt\n"
  },
  {
    "path": "harness/nnef-inceptionv3/src/lib.rs",
    "content": "use std::{fs, io, path};\n\nuse tract_core::prelude::*;\n\nfn download() {\n    use std::sync::Once;\n    static START: Once = Once::new();\n\n    START.call_once(|| do_download().unwrap());\n}\n\nfn do_download() -> TractResult<()> {\n    let run = ::std::process::Command::new(\"./download.sh\").status().unwrap();\n    if !run.success() {\n        tract_core::internal::bail!(\"Failed to download inception model files\")\n    }\n    Ok(())\n}\n\npub fn load_labels() -> Vec<String> {\n    use std::io::BufRead;\n    io::BufReader::new(fs::File::open(imagenet_slim_labels()).unwrap())\n        .lines()\n        .collect::<::std::io::Result<Vec<String>>>()\n        .unwrap()\n}\n\nfn inception_v3_2016_08_28() -> path::PathBuf {\n    ::std::env::var(\"CACHEDIR\").ok().unwrap_or_else(|| \"../../.cached\".to_string()).into()\n}\n\npub fn inception_v3_tgz() -> path::PathBuf {\n    download();\n    inception_v3_2016_08_28().join(\"inception_v3.tfpb.nnef.tgz\")\n}\n\npub fn imagenet_slim_labels() -> path::PathBuf {\n    download();\n    inception_v3_2016_08_28().join(\"imagenet_slim_labels.txt\")\n}\n\npub fn load_image<P: AsRef<path::Path>>(p: P) -> Tensor {\n    let image = image::open(&p).unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 299, 299, ::image::imageops::FilterType::Triangle);\n    tract_ndarray::Array4::from_shape_fn((1, 3, 299, 299), |(_, c, y, x)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    })\n    .into_dyn()\n    .into()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use dinghy_test::test_project_path;\n    use std::path;\n\n    const HOPPER: &str = \"grace_hopper.jpg\";\n    pub fn hopper() -> path::PathBuf {\n        test_project_path().join(HOPPER)\n    }\n\n    #[allow(dead_code)]\n    pub fn setup_test_logger() {\n        env_logger::Builder::from_default_env().filter_level(log::LevelFilter::Trace).init();\n    }\n\n    #[test]\n    fn grace_hopper_is_a_military_uniform() -> TractResult<()> {\n        download();\n        // setup_test_logger();\n        let nnef = tract_nnef::nnef();\n        let mut tar = flate2::read::GzDecoder::new(fs::File::open(inception_v3_tgz())?);\n        let model = nnef.model_for_read(&mut tar)?.into_optimized()?.into_runnable()?;\n        let input = load_image(hopper());\n        let outputs = model.run(tvec![input.into()]).unwrap();\n        let labels = load_labels();\n        let label_id = outputs[0]\n            .try_as_plain()\n            .unwrap()\n            .to_array_view::<f32>()\n            .unwrap()\n            .iter()\n            .enumerate()\n            .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(0u32.cmp(&1)))\n            .unwrap()\n            .0;\n        let label = &labels[label_id];\n        assert_eq!(label, \"military uniform\");\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/.gitignore",
    "content": "found\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-bias/expected",
    "content": "version 1.0;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [(\"tract_nnef_ser_version\", \"0.18.3-pre\"), (\"tract_nnef_format_version\", \"beta1\")];\n}\n\ngraph network(input) -> (output) {\n  input = external(shape = [1, 1, 3]);\n  kernel1 = [[[0.0, 0.0, 0.0]], [[0.0, 0.0, 0.0]]];\n  output_bias_rm_0 = [1.0, 1.0];\n  output_conv = conv(input, kernel1, output_bias_rm_0, dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  output = output_conv;\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-bias/graph.nnef",
    "content": "version 1.0;\n\ngraph check_bias_loading(input) -> (output)\n{\n    input = external<scalar>(shape = [1, 1, 3]);\n    kernel1 = [[[0.0, 0.0, 0.0]], [[0.0, 0.0, 0.0]]];\n    bias1 = [[[1.0, 1.0]]];\n    output = conv(input, kernel1, bias1, padding = [(0, 0)], border = 'constant', stride = [1], dilation = [1]);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-bias/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . --no-nnef-tract-core dump -q --nnef-graph found\n\nversion=`cargo metadata --format-version 1 | jq -r '.packages | map(select( (.name) == \"tract-core\") | .version) | .[] '`\nperl -pi -e \"s/$version/0.18.3-pre/\" found\n\ndiff -u expected found\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv2d/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_base_kernel1/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_base_kernel3/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_base_kernel9/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_dilation2/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_dilation4/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_dilation8/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_groups2/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_groups4/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_insize128/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_insize64/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_stride2/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-q40/conv_stride3/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core model.nnef.tgz -O run --approx very --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-with-batch/graph.nnef",
    "content": "version 1.0;\n\ngraph net_2022_04_13T13_03_40(input_0) -> (output_0)\n{\n    input_0 = external<scalar>(shape = [2, 1, 3]);\n    v3_weight = [[[0.0, 0.0, 0.0],[0.0,0.0,0.0],[0.0,0.0,0.0]]];\n    v2 = linear(input_0, v3_weight);\n    output_0 = add(v2, input_0);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/conv-with-batch/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN -O . compare --allow-random-input --stage declutter\n"
  },
  {
    "path": "harness/nnef-test-cases/debox/debox_base/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\ngraph network(input_0) -> (output_0)\n{\n    input_0 = tract_core_external(shape = [1, 1, 2, 2], datum_type = 'f32');\n    output_0 = debox(input_0, size = [1, 1, 2, 2], stride = [1, 1, 2, 2], padding = [(0, 0), (0, 0), (0, 0), (0, 0)]);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/debox/debox_base/runme.sh",
    "content": "#!/bin/sh\n\ncd $(dirname $0)\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core . -O --input-facts-from-bundle ./io.npz run --input-from-bundle io.npz --assert-output-bundle io.npz --approx approximate\n"
  },
  {
    "path": "harness/nnef-test-cases/debox/debox_high_dim/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\ngraph network(input_0) -> (output_0)\n{\n    input_0 = tract_core_external(shape = [2, 3, 4, 5], datum_type = 'f32');\n    output_0 = debox(input_0, size = [1, 1, 2, 2], stride = [1, 1, 2, 2], padding = [(0, 0), (0, 0), (0, 0), (0, 0)]);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/debox/debox_high_dim/runme.sh",
    "content": "#!/bin/sh\n\ncd $(dirname $0)\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --nnef-tract-core . -O --input-facts-from-bundle ./io.npz run --input-from-bundle io.npz --assert-output-bundle io.npz --approx approximate\n"
  },
  {
    "path": "harness/nnef-test-cases/dyn_slice/graph.nnef",
    "content": "version 1.0;\n\nextension tract_pulse_streaming_symbol;\nextension tract_registry tract_core;\nextension KHR_enable_fragment_definitions;\nextension KHR_enable_operator_expressions;\n\n\n\nfragment trunc( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = select(x < 0, ceil(x), floor(x));\n}\n\n\n\n\ngraph net_2024_06_26T15_36_33(input_0) -> (output_0)\n{\n    input_0 = external<scalar>(shape = [3, 1, S]);\n    v1_shape = tract_core_shape_of(input_0);\n    v0_sliced = slice(v1_shape, axes = [0], begin = [2], end = [3], stride = [1]);\n    v1_shape_2 = squeeze(v0_sliced, axes = [0]);\n    v4_div = div(v1_shape_2, 2);\n    v4 = trunc(v4_div);\n    output_0 = slice(input_0, axes = [2], begin = [0], end = [v4], stride = [1]);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/dyn_slice/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . --nnef-tract-core dump -q\n"
  },
  {
    "path": "harness/nnef-test-cases/fixed_roll/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\ngraph network(input_0) -> (output_0)\n{\n    input_0 = tract_core_external(shape = [1, 2, 3], datum_type = 'f32');\n    output_0_roll_l0_p1 = slice(input_0, axes = [1], begin = [1], end = [2], stride = [1]);\n    output_0_roll_l0_p2 = slice(input_0, axes = [1], begin = [0], end = [1], stride = [1]);\n    output_0_roll_0 = concat([output_0_roll_l0_p1, output_0_roll_l0_p2], axis = 1);\n    output_0_roll_l1_p1 = slice(output_0_roll_0, axes = [2], begin = [2], end = [3], stride = [1]);\n    output_0_roll_l1_p2 = slice(output_0_roll_0, axes = [2], begin = [0], end = [2], stride = [1]);\n    output_0 = concat([output_0_roll_l1_p1, output_0_roll_l1_p2], axis = 2);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/fixed_roll/runme.sh",
    "content": "#!/bin/sh\n\ncd $(dirname $0)\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n# Check result is as expected\n# bug appear only if model optimized\n$TRACT_RUN --nnef-tract-core . -O run --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/memory-arena/expected.json",
    "content": "{\"memory_size\":\"3072*P+3584*S+max(2560,640*S+640*P)+max(1280*S+1280*P,2560*S)+max(10240,1536*S+1536*P,9728*S)+max(10240,2560*S+2560*P,9728*S)+max(20480,1536*S+1536*P,24*(S)*(S+P),32*(S)*(S+P),40*(S)*(S+P),19456*S)+max(40960,1280*S+1280*P,24*(S)*(S+P),32*(S)*(S+P),40*(S)*(S+P),38912*S)\",\"size_by_partition\":[\"max(40960,1280*S+1280*P,24*(S)*(S+P),32*(S)*(S+P),40*(S)*(S+P),38912*S)\",\"max(20480,1536*S+1536*P,24*(S)*(S+P),32*(S)*(S+P),40*(S)*(S+P),19456*S)\",\"max(10240,2560*S+2560*P,9728*S)\",\"max(10240,1536*S+1536*P,9728*S)\",\"2560*S+2560*P\",\"max(1280*S+1280*P,2560*S)\",\"max(2560,640*S+640*P)\",\"256*S\",\"256*S+256*P\",\"256*S\",\"256*S+256*P\"],\"pp\":{\"16\":{\"arena_memory_size\":1353728,\"peak_memory_size\":991232,\"peak_memory_usage\":0.7322239},\"32\":{\"arena_memory_size\":2707456,\"peak_memory_size\":1982464,\"peak_memory_usage\":0.7322239},\"48\":{\"arena_memory_size\":4061184,\"peak_memory_size\":2973696,\"peak_memory_usage\":0.7322239},\"64\":{\"arena_memory_size\":5414912,\"peak_memory_size\":3964928,\"peak_memory_usage\":0.7322239},\"80\":{\"arena_memory_size\":6768640,\"peak_memory_size\":4956160,\"peak_memory_usage\":0.7322239},\"96\":{\"arena_memory_size\":8122368,\"peak_memory_size\":5947392,\"peak_memory_usage\":0.7322239},\"112\":{\"arena_memory_size\":9476096,\"peak_memory_size\":6938624,\"peak_memory_usage\":0.7322239},\"128\":{\"arena_memory_size\":10829824,\"peak_memory_size\":7929856,\"peak_memory_usage\":0.7322239},\"144\":{\"arena_memory_size\":12183552,\"peak_memory_size\":8921088,\"peak_memory_usage\":0.7322239},\"160\":{\"arena_memory_size\":13537280,\"peak_memory_size\":9912320,\"peak_memory_usage\":0.7322239},\"176\":{\"arena_memory_size\":14891008,\"peak_memory_size\":10903552,\"peak_memory_usage\":0.7322239},\"192\":{\"arena_memory_size\":16244736,\"peak_memory_size\":11894784,\"peak_memory_usage\":0.7322239},\"208\":{\"arena_memory_size\":17598464,\"peak_memory_size\":12886016,\"peak_memory_usage\":0.7322239},\"224\":{\"arena_memory_size\":18952192,\"peak_memory_size\":13877248,\"peak_memory_usage\":0.7322239},\"240\":{\"arena_memory_size\":20305920,\"peak_memory_size\":14868480,\"peak_memory_usage\":0.7322239},\"256\":{\"arena_memory_size\":21659648,\"peak_memory_size\":15859712,\"peak_memory_usage\":0.7322239},\"272\":{\"arena_memory_size\":23013376,\"peak_memory_size\":16850944,\"peak_memory_usage\":0.7322239},\"288\":{\"arena_memory_size\":24367104,\"peak_memory_size\":17842176,\"peak_memory_usage\":0.7322239},\"304\":{\"arena_memory_size\":25720832,\"peak_memory_size\":18833408,\"peak_memory_usage\":0.7322239},\"320\":{\"arena_memory_size\":27074560,\"peak_memory_size\":19824640,\"peak_memory_usage\":0.7322239},\"336\":{\"arena_memory_size\":28428288,\"peak_memory_size\":20815872,\"peak_memory_usage\":0.7322239},\"352\":{\"arena_memory_size\":29782016,\"peak_memory_size\":21807104,\"peak_memory_usage\":0.7322239},\"368\":{\"arena_memory_size\":31135744,\"peak_memory_size\":22798336,\"peak_memory_usage\":0.7322239},\"384\":{\"arena_memory_size\":32489472,\"peak_memory_size\":23789568,\"peak_memory_usage\":0.7322239},\"400\":{\"arena_memory_size\":33843200,\"peak_memory_size\":24780800,\"peak_memory_usage\":0.7322239},\"416\":{\"arena_memory_size\":35196928,\"peak_memory_size\":25772032,\"peak_memory_usage\":0.7322239},\"432\":{\"arena_memory_size\":36550656,\"peak_memory_size\":26763264,\"peak_memory_usage\":0.7322239},\"448\":{\"arena_memory_size\":37904384,\"peak_memory_size\":27754496,\"peak_memory_usage\":0.7322239},\"464\":{\"arena_memory_size\":39258112,\"peak_memory_size\":28745728,\"peak_memory_usage\":0.7322239},\"480\":{\"arena_memory_size\":40611840,\"peak_memory_size\":29736960,\"peak_memory_usage\":0.7322239},\"496\":{\"arena_memory_size\":42156032,\"peak_memory_size\":30728192,\"peak_memory_usage\":0.7289157},\"512\":{\"arena_memory_size\":43843584,\"peak_memory_size\":31719424,\"peak_memory_usage\":0.7234679},\"528\":{\"arena_memory_size\":45551616,\"peak_memory_size\":32710656,\"peak_memory_usage\":0.7181009},\"544\":{\"arena_memory_size\":47280128,\"peak_memory_size\":33701888,\"peak_memory_usage\":0.71281296},\"560\":{\"arena_memory_size\":49029120,\"peak_memory_size\":34693120,\"peak_memory_usage\":0.7076023},\"576\":{\"arena_memory_size\":50798592,\"peak_memory_size\":35684352,\"peak_memory_usage\":0.7024673},\"592\":{\"arena_memory_size\":52588544,\"peak_memory_size\":36675584,\"peak_memory_usage\":0.69740635},\"608\":{\"arena_memory_size\":54398976,\"peak_memory_size\":37666816,\"peak_memory_usage\":0.69241774},\"624\":{\"arena_memory_size\":56229888,\"peak_memory_size\":38658048,\"peak_memory_usage\":0.6875},\"640\":{\"arena_memory_size\":58081280,\"peak_memory_size\":39649280,\"peak_memory_usage\":0.68265164},\"656\":{\"arena_memory_size\":59953152,\"peak_memory_size\":40640512,\"peak_memory_usage\":0.67787117},\"672\":{\"arena_memory_size\":61845504,\"peak_memory_size\":41631744,\"peak_memory_usage\":0.67315716},\"688\":{\"arena_memory_size\":63758336,\"peak_memory_size\":42622976,\"peak_memory_usage\":0.6685083},\"704\":{\"arena_memory_size\":65691648,\"peak_memory_size\":43974656,\"peak_memory_usage\":0.66941017},\"720\":{\"arena_memory_size\":67645440,\"peak_memory_size\":45895680,\"peak_memory_usage\":0.6784741},\"736\":{\"arena_memory_size\":69619712,\"peak_memory_size\":47857664,\"peak_memory_usage\":0.6874154},\"752\":{\"arena_memory_size\":71614464,\"peak_memory_size\":49860608,\"peak_memory_usage\":0.69623655},\"768\":{\"arena_memory_size\":73629696,\"peak_memory_size\":51904512,\"peak_memory_usage\":0.7049399},\"784\":{\"arena_memory_size\":75665408,\"peak_memory_size\":53989376,\"peak_memory_usage\":0.71352786},\"800\":{\"arena_memory_size\":77721600,\"peak_memory_size\":56115200,\"peak_memory_usage\":0.7220026},\"816\":{\"arena_memory_size\":79798272,\"peak_memory_size\":58281984,\"peak_memory_usage\":0.73036647},\"832\":{\"arena_memory_size\":81895424,\"peak_memory_size\":60489728,\"peak_memory_usage\":0.7386216},\"848\":{\"arena_memory_size\":84013056,\"peak_memory_size\":62738432,\"peak_memory_usage\":0.74677},\"864\":{\"arena_memory_size\":86151168,\"peak_memory_size\":65028096,\"peak_memory_usage\":0.75481385},\"880\":{\"arena_memory_size\":88309760,\"peak_memory_size\":67358720,\"peak_memory_usage\":0.7627551},\"896\":{\"arena_memory_size\":90488832,\"peak_memory_size\":69730304,\"peak_memory_usage\":0.77059567},\"912\":{\"arena_memory_size\":92688384,\"peak_memory_size\":72142848,\"peak_memory_usage\":0.77833754},\"928\":{\"arena_memory_size\":94908416,\"peak_memory_size\":74596352,\"peak_memory_usage\":0.7859825},\"944\":{\"arena_memory_size\":97148928,\"peak_memory_size\":77090816,\"peak_memory_usage\":0.7935323},\"960\":{\"arena_memory_size\":99409920,\"peak_memory_size\":79626240,\"peak_memory_usage\":0.80098885},\"976\":{\"arena_memory_size\":101816320,\"peak_memory_size\":82202624,\"peak_memory_usage\":0.80736196},\"992\":{\"arena_memory_size\":104755200,\"peak_memory_size\":84819968,\"peak_memory_usage\":0.809697},\"1008\":{\"arena_memory_size\":107735040,\"peak_memory_size\":87478272,\"peak_memory_usage\":0.8119761},\"1024\":{\"arena_memory_size\":110755840,\"peak_memory_size\":90177536,\"peak_memory_usage\":0.8142012},\"1040\":{\"arena_memory_size\":113817600,\"peak_memory_size\":92917760,\"peak_memory_usage\":0.81637424},\"1056\":{\"arena_memory_size\":116920320,\"peak_memory_size\":95698944,\"peak_memory_usage\":0.8184971},\"1072\":{\"arena_memory_size\":120064000,\"peak_memory_size\":98521088,\"peak_memory_usage\":0.8205714},\"1088\":{\"arena_memory_size\":123248640,\"peak_memory_size\":101384192,\"peak_memory_usage\":0.8225989},\"1104\":{\"arena_memory_size\":126474240,\"peak_memory_size\":104288256,\"peak_memory_usage\":0.824581},\"1120\":{\"arena_memory_size\":129740800,\"peak_memory_size\":107233280,\"peak_memory_usage\":0.8265193},\"1136\":{\"arena_memory_size\":133048320,\"peak_memory_size\":110219264,\"peak_memory_usage\":0.8284153},\"1152\":{\"arena_memory_size\":136396800,\"peak_memory_size\":113246208,\"peak_memory_usage\":0.8302703},\"1168\":{\"arena_memory_size\":139786240,\"peak_memory_size\":116314112,\"peak_memory_usage\":0.83208555},\"1184\":{\"arena_memory_size\":143216640,\"peak_memory_size\":119422976,\"peak_memory_usage\":0.8338624},\"1200\":{\"arena_memory_size\":146688000,\"peak_memory_size\":122572800,\"peak_memory_usage\":0.8356021},\"1216\":{\"arena_memory_size\":150200320,\"peak_memory_size\":125763584,\"peak_memory_usage\":0.8373057},\"1232\":{\"arena_memory_size\":153753600,\"peak_memory_size\":128995328,\"peak_memory_usage\":0.83897436},\"1248\":{\"arena_memory_size\":157347840,\"peak_memory_size\":132268032,\"peak_memory_usage\":0.84060913},\"1264\":{\"arena_memory_size\":160983040,\"peak_memory_size\":135581696,\"peak_memory_usage\":0.84221107},\"1280\":{\"arena_memory_size\":164659200,\"peak_memory_size\":138936320,\"peak_memory_usage\":0.8437811},\"1296\":{\"arena_memory_size\":168376320,\"peak_memory_size\":142331904,\"peak_memory_usage\":0.8453202},\"1312\":{\"arena_memory_size\":172134400,\"peak_memory_size\":145768448,\"peak_memory_usage\":0.8468293},\"1328\":{\"arena_memory_size\":175933440,\"peak_memory_size\":149245952,\"peak_memory_usage\":0.84830916},\"1344\":{\"arena_memory_size\":179773440,\"peak_memory_size\":152764416,\"peak_memory_usage\":0.8497608},\"1360\":{\"arena_memory_size\":183654400,\"peak_memory_size\":156323840,\"peak_memory_usage\":0.85118484},\"1376\":{\"arena_memory_size\":187576320,\"peak_memory_size\":159924224,\"peak_memory_usage\":0.85258216},\"1392\":{\"arena_memory_size\":191539200,\"peak_memory_size\":163565568,\"peak_memory_usage\":0.8539535},\"1408\":{\"arena_memory_size\":195543040,\"peak_memory_size\":167247872,\"peak_memory_usage\":0.85529953},\"1424\":{\"arena_memory_size\":199587840,\"peak_memory_size\":170971136,\"peak_memory_usage\":0.856621},\"1440\":{\"arena_memory_size\":203673600,\"peak_memory_size\":174735360,\"peak_memory_usage\":0.85791856},\"1456\":{\"arena_memory_size\":207800320,\"peak_memory_size\":178540544,\"peak_memory_usage\":0.85919285},\"1472\":{\"arena_memory_size\":211968000,\"peak_memory_size\":182386688,\"peak_memory_usage\":0.8604444},\"1488\":{\"arena_memory_size\":216176640,\"peak_memory_size\":186273792,\"peak_memory_usage\":0.861674},\"1504\":{\"arena_memory_size\":220426240,\"peak_memory_size\":190201856,\"peak_memory_usage\":0.8628821},\"1520\":{\"arena_memory_size\":224716800,\"peak_memory_size\":194170880,\"peak_memory_usage\":0.8640693},\"1536\":{\"arena_memory_size\":229048320,\"peak_memory_size\":198180864,\"peak_memory_usage\":0.86523604},\"1552\":{\"arena_memory_size\":233420800,\"peak_memory_size\":202231808,\"peak_memory_usage\":0.86638296},\"1568\":{\"arena_memory_size\":237834240,\"peak_memory_size\":206323712,\"peak_memory_usage\":0.86751056},\"1584\":{\"arena_memory_size\":242288640,\"peak_memory_size\":210456576,\"peak_memory_usage\":0.86861926},\"1600\":{\"arena_memory_size\":246784000,\"peak_memory_size\":214630400,\"peak_memory_usage\":0.86970955},\"1616\":{\"arena_memory_size\":251320320,\"peak_memory_size\":218845184,\"peak_memory_usage\":0.8707819},\"1632\":{\"arena_memory_size\":255897600,\"peak_memory_size\":223100928,\"peak_memory_usage\":0.8718367},\"1648\":{\"arena_memory_size\":260515840,\"peak_memory_size\":227397632,\"peak_memory_usage\":0.8728745},\"1664\":{\"arena_memory_size\":265175040,\"peak_memory_size\":231735296,\"peak_memory_usage\":0.8738956},\"1680\":{\"arena_memory_size\":269875200,\"peak_memory_size\":236113920,\"peak_memory_usage\":0.8749004},\"1696\":{\"arena_memory_size\":274616320,\"peak_memory_size\":240533504,\"peak_memory_usage\":0.8758893},\"1712\":{\"arena_memory_size\":279398400,\"peak_memory_size\":244994048,\"peak_memory_usage\":0.87686276},\"1728\":{\"arena_memory_size\":284221440,\"peak_memory_size\":249495552,\"peak_memory_usage\":0.877821},\"1744\":{\"arena_memory_size\":289085440,\"peak_memory_size\":254038016,\"peak_memory_usage\":0.87876445},\"1760\":{\"arena_memory_size\":293990400,\"peak_memory_size\":258621440,\"peak_memory_usage\":0.8796935},\"1776\":{\"arena_memory_size\":298936320,\"peak_memory_size\":263245824,\"peak_memory_usage\":0.8806084},\"1792\":{\"arena_memory_size\":303923200,\"peak_memory_size\":267911168,\"peak_memory_usage\":0.8815094},\"1808\":{\"arena_memory_size\":308951040,\"peak_memory_size\":272617472,\"peak_memory_usage\":0.882397},\"1824\":{\"arena_memory_size\":314019840,\"peak_memory_size\":277364736,\"peak_memory_usage\":0.8832714},\"1840\":{\"arena_memory_size\":319129600,\"peak_memory_size\":282152960,\"peak_memory_usage\":0.88413286},\"1856\":{\"arena_memory_size\":324280320,\"peak_memory_size\":286982144,\"peak_memory_usage\":0.8849817},\"1872\":{\"arena_memory_size\":329472000,\"peak_memory_size\":291852288,\"peak_memory_usage\":0.8858182},\"1888\":{\"arena_memory_size\":334704640,\"peak_memory_size\":296763392,\"peak_memory_usage\":0.8866426},\"1904\":{\"arena_memory_size\":339978240,\"peak_memory_size\":301715456,\"peak_memory_usage\":0.8874552},\"1920\":{\"arena_memory_size\":345292800,\"peak_memory_size\":306708480,\"peak_memory_usage\":0.88825625},\"1936\":{\"arena_memory_size\":350648320,\"peak_memory_size\":311742464,\"peak_memory_usage\":0.88904595},\"1952\":{\"arena_memory_size\":356044800,\"peak_memory_size\":316817408,\"peak_memory_usage\":0.88982457},\"1968\":{\"arena_memory_size\":361482240,\"peak_memory_size\":321933312,\"peak_memory_usage\":0.89059234},\"1984\":{\"arena_memory_size\":366960640,\"peak_memory_size\":327090176,\"peak_memory_usage\":0.8913495},\"2000\":{\"arena_memory_size\":372480000,\"peak_memory_size\":332288000,\"peak_memory_usage\":0.8920962},\"2016\":{\"arena_memory_size\":378040320,\"peak_memory_size\":337526784,\"peak_memory_usage\":0.89283276},\"2032\":{\"arena_memory_size\":383641600,\"peak_memory_size\":342806528,\"peak_memory_usage\":0.89355934},\"2048\":{\"arena_memory_size\":389283840,\"peak_memory_size\":348127232,\"peak_memory_usage\":0.8942761}},\"tg\":{\"0\":{\"arena_memory_size\":90624,\"peak_memory_size\":64000,\"peak_memory_usage\":0.70621467},\"16\":{\"arena_memory_size\":222080,\"peak_memory_size\":123136,\"peak_memory_usage\":0.55446684},\"32\":{\"arena_memory_size\":393344,\"peak_memory_size\":233728,\"peak_memory_usage\":0.59420764},\"48\":{\"arena_memory_size\":583808,\"peak_memory_size\":344320,\"peak_memory_usage\":0.58978295},\"64\":{\"arena_memory_size\":774272,\"peak_memory_size\":454912,\"peak_memory_usage\":0.58753514},\"80\":{\"arena_memory_size\":964736,\"peak_memory_size\":565504,\"peak_memory_usage\":0.58617485},\"96\":{\"arena_memory_size\":1155200,\"peak_memory_size\":676096,\"peak_memory_usage\":0.58526313},\"112\":{\"arena_memory_size\":1345664,\"peak_memory_size\":786688,\"peak_memory_usage\":0.5846095},\"128\":{\"arena_memory_size\":1536128,\"peak_memory_size\":897280,\"peak_memory_usage\":0.584118},\"144\":{\"arena_memory_size\":1726592,\"peak_memory_size\":1007872,\"peak_memory_usage\":0.58373487},\"160\":{\"arena_memory_size\":1917056,\"peak_memory_size\":1118464,\"peak_memory_usage\":0.5834279},\"176\":{\"arena_memory_size\":2107520,\"peak_memory_size\":1229056,\"peak_memory_usage\":0.58317643},\"192\":{\"arena_memory_size\":2297984,\"peak_memory_size\":1339648,\"peak_memory_usage\":0.5829666},\"208\":{\"arena_memory_size\":2488448,\"peak_memory_size\":1450240,\"peak_memory_usage\":0.58278894},\"224\":{\"arena_memory_size\":2678912,\"peak_memory_size\":1560832,\"peak_memory_usage\":0.58263654},\"240\":{\"arena_memory_size\":2869376,\"peak_memory_size\":1671424,\"peak_memory_usage\":0.58250433},\"256\":{\"arena_memory_size\":3059840,\"peak_memory_size\":1782016,\"peak_memory_usage\":0.58238864},\"272\":{\"arena_memory_size\":3250304,\"peak_memory_size\":1892608,\"peak_memory_usage\":0.5822865},\"288\":{\"arena_memory_size\":3440768,\"peak_memory_size\":2003200,\"peak_memory_usage\":0.5821956},\"304\":{\"arena_memory_size\":3631232,\"peak_memory_size\":2113792,\"peak_memory_usage\":0.5821143},\"320\":{\"arena_memory_size\":3821696,\"peak_memory_size\":2224384,\"peak_memory_usage\":0.5820411},\"336\":{\"arena_memory_size\":4012160,\"peak_memory_size\":2334976,\"peak_memory_usage\":0.5819748},\"352\":{\"arena_memory_size\":4202624,\"peak_memory_size\":2445568,\"peak_memory_usage\":0.58191454},\"368\":{\"arena_memory_size\":4393088,\"peak_memory_size\":2556160,\"peak_memory_usage\":0.5818595},\"384\":{\"arena_memory_size\":4583552,\"peak_memory_size\":2666752,\"peak_memory_usage\":0.58180904},\"400\":{\"arena_memory_size\":4774016,\"peak_memory_size\":2777344,\"peak_memory_usage\":0.5817626},\"416\":{\"arena_memory_size\":4964480,\"peak_memory_size\":2887936,\"peak_memory_usage\":0.58171976},\"432\":{\"arena_memory_size\":5154944,\"peak_memory_size\":2998528,\"peak_memory_usage\":0.58168006},\"448\":{\"arena_memory_size\":5345408,\"peak_memory_size\":3109120,\"peak_memory_usage\":0.58164316},\"464\":{\"arena_memory_size\":5535872,\"peak_memory_size\":3219712,\"peak_memory_usage\":0.58160883},\"480\":{\"arena_memory_size\":5726336,\"peak_memory_size\":3330304,\"peak_memory_usage\":0.58157676},\"496\":{\"arena_memory_size\":5916800,\"peak_memory_size\":3440896,\"peak_memory_usage\":0.5815468},\"512\":{\"arena_memory_size\":6107264,\"peak_memory_size\":3551488,\"peak_memory_usage\":0.58151865},\"528\":{\"arena_memory_size\":6297728,\"peak_memory_size\":3662080,\"peak_memory_usage\":0.58149225},\"544\":{\"arena_memory_size\":6488192,\"peak_memory_size\":3772672,\"peak_memory_usage\":0.5814674},\"560\":{\"arena_memory_size\":6678656,\"peak_memory_size\":3883264,\"peak_memory_usage\":0.5814439},\"576\":{\"arena_memory_size\":6869120,\"peak_memory_size\":3993856,\"peak_memory_usage\":0.5814218},\"592\":{\"arena_memory_size\":7059584,\"peak_memory_size\":4104448,\"peak_memory_usage\":0.5814008},\"608\":{\"arena_memory_size\":7250048,\"peak_memory_size\":4215040,\"peak_memory_usage\":0.58138096},\"624\":{\"arena_memory_size\":7440512,\"peak_memory_size\":4325632,\"peak_memory_usage\":0.5813621},\"640\":{\"arena_memory_size\":7630976,\"peak_memory_size\":4436224,\"peak_memory_usage\":0.58134425},\"656\":{\"arena_memory_size\":7821440,\"peak_memory_size\":4546816,\"peak_memory_usage\":0.5813272},\"672\":{\"arena_memory_size\":8011904,\"peak_memory_size\":4657408,\"peak_memory_usage\":0.581311},\"688\":{\"arena_memory_size\":8202368,\"peak_memory_size\":4768000,\"peak_memory_usage\":0.58129555},\"704\":{\"arena_memory_size\":8392832,\"peak_memory_size\":4878592,\"peak_memory_usage\":0.58128077},\"720\":{\"arena_memory_size\":8583296,\"peak_memory_size\":4989184,\"peak_memory_usage\":0.5812667},\"736\":{\"arena_memory_size\":8773760,\"peak_memory_size\":5099776,\"peak_memory_usage\":0.5812532},\"752\":{\"arena_memory_size\":8964224,\"peak_memory_size\":5210368,\"peak_memory_usage\":0.5812403},\"768\":{\"arena_memory_size\":9154688,\"peak_memory_size\":5320960,\"peak_memory_usage\":0.5812279},\"784\":{\"arena_memory_size\":9345152,\"peak_memory_size\":5431552,\"peak_memory_usage\":0.58121604},\"800\":{\"arena_memory_size\":9535616,\"peak_memory_size\":5542144,\"peak_memory_usage\":0.5812046},\"816\":{\"arena_memory_size\":9726080,\"peak_memory_size\":5652736,\"peak_memory_usage\":0.5811937},\"832\":{\"arena_memory_size\":9916544,\"peak_memory_size\":5763328,\"peak_memory_usage\":0.58118314},\"848\":{\"arena_memory_size\":10107008,\"peak_memory_size\":5873920,\"peak_memory_usage\":0.581173},\"864\":{\"arena_memory_size\":10297472,\"peak_memory_size\":5984512,\"peak_memory_usage\":0.5811632},\"880\":{\"arena_memory_size\":10487936,\"peak_memory_size\":6095104,\"peak_memory_usage\":0.5811538},\"896\":{\"arena_memory_size\":10678400,\"peak_memory_size\":6205696,\"peak_memory_usage\":0.58114475},\"912\":{\"arena_memory_size\":10868864,\"peak_memory_size\":6316288,\"peak_memory_usage\":0.581136},\"928\":{\"arena_memory_size\":11059328,\"peak_memory_size\":6426880,\"peak_memory_usage\":0.5811275},\"944\":{\"arena_memory_size\":11249792,\"peak_memory_size\":6537472,\"peak_memory_usage\":0.58111936},\"960\":{\"arena_memory_size\":11440256,\"peak_memory_size\":6648064,\"peak_memory_usage\":0.5811115},\"976\":{\"arena_memory_size\":11630720,\"peak_memory_size\":6758656,\"peak_memory_usage\":0.58110386},\"992\":{\"arena_memory_size\":11821184,\"peak_memory_size\":6869248,\"peak_memory_usage\":0.5810965},\"1008\":{\"arena_memory_size\":12011648,\"peak_memory_size\":6979840,\"peak_memory_usage\":0.5810893},\"1024\":{\"arena_memory_size\":12202112,\"peak_memory_size\":7090432,\"peak_memory_usage\":0.58108234},\"1040\":{\"arena_memory_size\":12392576,\"peak_memory_size\":7201024,\"peak_memory_usage\":0.5810756},\"1056\":{\"arena_memory_size\":12583040,\"peak_memory_size\":7311616,\"peak_memory_usage\":0.5810691},\"1072\":{\"arena_memory_size\":12773504,\"peak_memory_size\":7422208,\"peak_memory_usage\":0.5810628},\"1088\":{\"arena_memory_size\":12963968,\"peak_memory_size\":7532800,\"peak_memory_usage\":0.58105665},\"1104\":{\"arena_memory_size\":13154432,\"peak_memory_size\":7643392,\"peak_memory_usage\":0.5810507},\"1120\":{\"arena_memory_size\":13344896,\"peak_memory_size\":7753984,\"peak_memory_usage\":0.5810449},\"1136\":{\"arena_memory_size\":13535360,\"peak_memory_size\":7864576,\"peak_memory_usage\":0.5810393},\"1152\":{\"arena_memory_size\":13725824,\"peak_memory_size\":7975168,\"peak_memory_usage\":0.5810338},\"1168\":{\"arena_memory_size\":13916288,\"peak_memory_size\":8085760,\"peak_memory_usage\":0.5810285},\"1184\":{\"arena_memory_size\":14106752,\"peak_memory_size\":8196352,\"peak_memory_usage\":0.58102334},\"1200\":{\"arena_memory_size\":14297216,\"peak_memory_size\":8306944,\"peak_memory_usage\":0.58101827},\"1216\":{\"arena_memory_size\":14487680,\"peak_memory_size\":8417536,\"peak_memory_usage\":0.5810134},\"1232\":{\"arena_memory_size\":14678144,\"peak_memory_size\":8528128,\"peak_memory_usage\":0.5810086},\"1248\":{\"arena_memory_size\":14868608,\"peak_memory_size\":8638720,\"peak_memory_usage\":0.58100396},\"1264\":{\"arena_memory_size\":15059072,\"peak_memory_size\":8749312,\"peak_memory_usage\":0.58099943},\"1280\":{\"arena_memory_size\":15249536,\"peak_memory_size\":8859904,\"peak_memory_usage\":0.58099496},\"1296\":{\"arena_memory_size\":15440000,\"peak_memory_size\":8970496,\"peak_memory_usage\":0.5809907},\"1312\":{\"arena_memory_size\":15630464,\"peak_memory_size\":9081088,\"peak_memory_usage\":0.58098644},\"1328\":{\"arena_memory_size\":15820928,\"peak_memory_size\":9191680,\"peak_memory_usage\":0.5809823},\"1344\":{\"arena_memory_size\":16011392,\"peak_memory_size\":9302272,\"peak_memory_usage\":0.58097833},\"1360\":{\"arena_memory_size\":16201856,\"peak_memory_size\":9412864,\"peak_memory_usage\":0.5809744},\"1376\":{\"arena_memory_size\":16392320,\"peak_memory_size\":9523456,\"peak_memory_usage\":0.5809706},\"1392\":{\"arena_memory_size\":16582784,\"peak_memory_size\":9634048,\"peak_memory_usage\":0.5809669},\"1408\":{\"arena_memory_size\":16773248,\"peak_memory_size\":9744640,\"peak_memory_usage\":0.5809632},\"1424\":{\"arena_memory_size\":16963712,\"peak_memory_size\":9855232,\"peak_memory_usage\":0.5809596},\"1440\":{\"arena_memory_size\":17154176,\"peak_memory_size\":9965824,\"peak_memory_usage\":0.58095616},\"1456\":{\"arena_memory_size\":17344640,\"peak_memory_size\":10076416,\"peak_memory_usage\":0.5809527},\"1472\":{\"arena_memory_size\":17535104,\"peak_memory_size\":10187008,\"peak_memory_usage\":0.58094937},\"1488\":{\"arena_memory_size\":17725568,\"peak_memory_size\":10297600,\"peak_memory_usage\":0.58094615},\"1504\":{\"arena_memory_size\":17916032,\"peak_memory_size\":10408192,\"peak_memory_usage\":0.5809429},\"1520\":{\"arena_memory_size\":18106496,\"peak_memory_size\":10518784,\"peak_memory_usage\":0.58093977},\"1536\":{\"arena_memory_size\":18296960,\"peak_memory_size\":10629376,\"peak_memory_usage\":0.58093673},\"1552\":{\"arena_memory_size\":18487424,\"peak_memory_size\":10739968,\"peak_memory_usage\":0.58093375},\"1568\":{\"arena_memory_size\":18677888,\"peak_memory_size\":10850560,\"peak_memory_usage\":0.58093077},\"1584\":{\"arena_memory_size\":18868352,\"peak_memory_size\":10961152,\"peak_memory_usage\":0.5809279},\"1600\":{\"arena_memory_size\":19058816,\"peak_memory_size\":11071744,\"peak_memory_usage\":0.58092505},\"1616\":{\"arena_memory_size\":19249280,\"peak_memory_size\":11182336,\"peak_memory_usage\":0.5809223},\"1632\":{\"arena_memory_size\":19439744,\"peak_memory_size\":11292928,\"peak_memory_usage\":0.58091956},\"1648\":{\"arena_memory_size\":19630208,\"peak_memory_size\":11403520,\"peak_memory_usage\":0.58091694},\"1664\":{\"arena_memory_size\":19820672,\"peak_memory_size\":11514112,\"peak_memory_usage\":0.5809143},\"1680\":{\"arena_memory_size\":20011136,\"peak_memory_size\":11624704,\"peak_memory_usage\":0.58091176},\"1696\":{\"arena_memory_size\":20201600,\"peak_memory_size\":11735296,\"peak_memory_usage\":0.58090925},\"1712\":{\"arena_memory_size\":20392064,\"peak_memory_size\":11845888,\"peak_memory_usage\":0.58090675},\"1728\":{\"arena_memory_size\":20582528,\"peak_memory_size\":11956480,\"peak_memory_usage\":0.58090436},\"1744\":{\"arena_memory_size\":20772992,\"peak_memory_size\":12067072,\"peak_memory_usage\":0.580902},\"1760\":{\"arena_memory_size\":20963456,\"peak_memory_size\":12177664,\"peak_memory_usage\":0.58089966},\"1776\":{\"arena_memory_size\":21153920,\"peak_memory_size\":12288256,\"peak_memory_usage\":0.58089733},\"1792\":{\"arena_memory_size\":21344384,\"peak_memory_size\":12398848,\"peak_memory_usage\":0.5808951},\"1808\":{\"arena_memory_size\":21534848,\"peak_memory_size\":12509440,\"peak_memory_usage\":0.58089286},\"1824\":{\"arena_memory_size\":21725312,\"peak_memory_size\":12620032,\"peak_memory_usage\":0.5808907},\"1840\":{\"arena_memory_size\":21915776,\"peak_memory_size\":12730624,\"peak_memory_usage\":0.58088857},\"1856\":{\"arena_memory_size\":22106240,\"peak_memory_size\":12841216,\"peak_memory_usage\":0.5808865},\"1872\":{\"arena_memory_size\":22296704,\"peak_memory_size\":12951808,\"peak_memory_usage\":0.5808844},\"1888\":{\"arena_memory_size\":22487168,\"peak_memory_size\":13062400,\"peak_memory_usage\":0.5808824},\"1904\":{\"arena_memory_size\":22677632,\"peak_memory_size\":13172992,\"peak_memory_usage\":0.5808804},\"1920\":{\"arena_memory_size\":22868096,\"peak_memory_size\":13283584,\"peak_memory_usage\":0.58087844},\"1936\":{\"arena_memory_size\":23058560,\"peak_memory_size\":13394176,\"peak_memory_usage\":0.5808765},\"1952\":{\"arena_memory_size\":23249024,\"peak_memory_size\":13504768,\"peak_memory_usage\":0.5808746},\"1968\":{\"arena_memory_size\":23439488,\"peak_memory_size\":13615360,\"peak_memory_usage\":0.5808728},\"1984\":{\"arena_memory_size\":23629952,\"peak_memory_size\":13725952,\"peak_memory_usage\":0.5808709},\"2000\":{\"arena_memory_size\":23820416,\"peak_memory_size\":13836544,\"peak_memory_usage\":0.58086914},\"2016\":{\"arena_memory_size\":24010880,\"peak_memory_size\":13947136,\"peak_memory_usage\":0.58086735},\"2032\":{\"arena_memory_size\":24201344,\"peak_memory_size\":14057728,\"peak_memory_usage\":0.58086556},\"2048\":{\"arena_memory_size\":24391808,\"peak_memory_size\":14168320,\"peak_memory_usage\":0.5808639}},\"max_memory_size\":389283840,\"aggregate_usage\":0.82}"
  },
  {
    "path": "harness/nnef-test-cases/memory-arena/runme.sh",
    "content": "#!/bin/sh\n\nif [ `uname` = \"Darwin\" ] && ! ( sysctl -n machdep.cpu.brand_string | grep -q \"(Virtual)\" )\nthen\n\n  ROOT=$(dirname $(realpath $0))/../../..\n  . $ROOT/.travis/ci-system-setup.sh\n\n  cd `dirname $0`\n  set -ex\n\n  : ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n  model=OpenELM-270M\n  q=q40f16\n  id=\"apple--$model-$q\"\n  generation=current\n  nnef=\"llm/$generation/$id/$id.nnef.tgz\"\n  $CACHE_FILE $nnef\n\n  $TRACT_RUN -v --nnef-tract-core --metal $MODELS/$nnef dump --set S=1024 --set P=0 --memory-arena found.json\n\n  diff -u expected.json found.json\nelse\n  echo \"Skipped (memory arena test requires apple hardware)\"\nfi\n"
  },
  {
    "path": "harness/nnef-test-cases/pool-padding/graph.nnef",
    "content": "version 1.0;\n\ngraph check_full_padding(input) -> (output)\n{\n    input = external<scalar>(shape = [1, 1, 3, 3]);\n    max = max_pool(input, size = [1, 1, 3, 3],padding = [(0, 0), (0, 0), (1, 1), (1, 1)], border = 'constant', stride = [1, 1, 1, 1], dilation = [1, 1, 1, 1]);\n\t\tadd_value = [[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]]];\n\t\toutput = add(max, add_value);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/pool-padding/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . dump -q\n"
  },
  {
    "path": "harness/nnef-test-cases/q40_linear_followed_slice/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\ngraph net_2024_08_07_bug_slice_bubble_up(input_0) -> (output_0)\n{\n    input_0 = tract_core_external(shape = [10, 96], datum_type = 'f32');\n    linear__var0 = variable(label = 'linear__var0_q4_0', shape = [16, 96]);\n    linear0 = linear(input_0, linear__var0, 0.0);\n    slice_a = slice(linear0, axes = [1], begin = [0], end = [8], stride = [1]);\n    slice_b = slice(linear0, axes = [1], begin = [8], end = [16], stride = [1]);\n    output_0 = mul(slice_a, slice_b);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/q40_linear_followed_slice/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . --nnef-tract-core dump -q\n"
  },
  {
    "path": "harness/nnef-test-cases/qmul/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\ngraph net_2024_06_26T18_12_43(input_0) -> (output_0)\n{\n    input_0 = external<scalar>(shape = [2, 3]);\n    v2 = tract_core_cast(input_0);\n    v1 = mul(v2, v2);\n    output_0 = tract_core_cast(v1, to = 'f32');\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/qmul/graph.quant",
    "content": "\"v2\": zero_point_linear_quantize(zero_point = 0, scale = 0.13444413244724274, bits = 8, signed = false, symmetric = false);\n\"v1\": zero_point_linear_quantize(scale = 4.611433506011963, zero_point = 0, bits = 8, signed = false, symmetric = false);\n"
  },
  {
    "path": "harness/nnef-test-cases/qmul/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\nrm -rf found\n$TRACT_RUN --nnef-tract-core . run --input-from-bundle io.npz --steps --assert-output-bundle io.npz\n\n# version=`cargo metadata --format-version 1 | jq -r '.packages | map(select( (.name) == \"tract-core\") | .version) | .[] '`\n# perl -pi -e \"s/$version/0.16.10-pre/\" found/graph.nnef\n# \n# diff expected found\n"
  },
  {
    "path": "harness/nnef-test-cases/range-slice-dyn-tile/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN model.nnef.tgz --nnef-tract-core dump -q\n$TRACT_RUN model.nnef.tgz --nnef-tract-core --nnef-cycle dump -q\n"
  },
  {
    "path": "harness/nnef-test-cases/reshape/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\nextension tract_pulse_streaming_symbol;\n\ngraph net_2024_06_18T12_07_11(input_0) -> (output_0)\n{\n    input_0 = external<integer>(shape = [3, 1, 2]);\n    foo = [1, 2];\n    output_0 = reshape(input_0, shape = [1, foo, 3]);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/reshape/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . --nnef-tract-core dump -q\n"
  },
  {
    "path": "harness/nnef-test-cases/reshape_with_bc/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\ngraph net(a) -> (o)\n{\n    a = external<scalar>(shape = [3, 2]);\n    reshaped = reshape(a, shape = [2, 3]);\n    b = reshaped * 2.0;\n    o = reshape(b, shape = [3, 2]);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/reshape_with_bc/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . --nnef-tract-core dump -q --assert-op-count Reshape 0\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-causal-f32/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_transformers;\nextension tract_registry tract_core;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [\n    (\"tract_target_version\", \"0.21.14\"),\n    (\"torch_to_nnef_version\", \"0.19.1\"),\n    (\"torch_version\", \"2.6.0\"),\n    (\"transformers_version\", \"4.49.0\"),\n    (\"os\", \"Darwin SNS008481 24.6.0 Darwin Kernel Version 24.6.0: Mon Jul 14 11:28:30 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6030 arm64\"),\n    (\"hostname\", \"SNS008481\"),\n    (\"user\", \"emrick.sinitambirivo\"),\n    (\"py_version\", \"3.13.1 (main, Dec  3 2024, 17:59:52) [Clang 16.0.0 (clang-1600.0.26.4)] (64-bit runtime)\"),\n    (\"export_date\", \"2025-08-21 16:13:54.598072\"),\n    (\"exported_py_class\", \"FScaledDotProdAttn\"),\n    (\"export_cmd\", \"/Users/emrick.sinitambirivo/Documents/projects/svc-library-torch-to-nnef/.venv/bin/pytest tests/test_multi_head_att.py -rA\")\n  ];\n}\n\n\n\n\n\n\n\ngraph network(input_0) -> (output_0)\n{\n    input_0 = tract_core_external(shape = [1, 2, 3], datum_type = 'f32');\n    output_0 = tract_transformers_sdpa(input_0, input_0, input_0, datum_type = 'f32', acc_datum_type = 'f32', is_causal = true);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-causal-f32/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\nrm -rf found\n$TRACT_RUN --nnef-tract-core --nnef-tract-transformers . run --input-from-bundle io.npz --steps --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-f16/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_transformers;\nextension tract_registry tract_core;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [\n    (\"tract_target_version\", \"0.21.14\"),\n    (\"torch_to_nnef_version\", \"0.19.1\"),\n    (\"torch_version\", \"2.6.0\"),\n    (\"transformers_version\", \"4.49.0\"),\n    (\"os\", \"Darwin SNS008481 24.6.0 Darwin Kernel Version 24.6.0: Mon Jul 14 11:28:30 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6030 arm64\"),\n    (\"hostname\", \"SNS008481\"),\n    (\"user\", \"emrick.sinitambirivo\"),\n    (\"py_version\", \"3.13.1 (main, Dec  3 2024, 17:59:52) [Clang 16.0.0 (clang-1600.0.26.4)] (64-bit runtime)\"),\n    (\"export_date\", \"2025-08-21 16:13:57.709373\"),\n    (\"exported_py_class\", \"FScaledDotProdAttn\"),\n    (\"export_cmd\", \"/Users/emrick.sinitambirivo/Documents/projects/svc-library-torch-to-nnef/.venv/bin/pytest tests/test_multi_head_att.py -rA\")\n  ];\n}\n\n\n\n\n\n\n\ngraph network(input_0) -> (output_0)\n{\n    input_0 = tract_core_external(shape = [1, 2, 3, 4], datum_type = 'f32');\n    x = tract_core_cast(input_0, to = 'f16');\n    res = tract_transformers_sdpa(x, x, x, datum_type = 'f16', acc_datum_type = 'f32', is_causal = false);\n    output_0 = tract_core_cast(res, to = 'f32');\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-f16/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\nrm -rf found\n$TRACT_RUN --nnef-tract-core --nnef-tract-transformers . run --input-from-bundle io.npz --steps --assert-output-bundle io.npz --approx approximate\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-grouped-query-att-f32/runme.sh",
    "content": "#!/bin/sh\n\n# Model generated with the following configuration\n# LlamaConfig(\n#     hidden_size=8,\n#     intermediate_size=16,\n#     num_attention_heads=4,\n#     num_key_value_heads=2,\n#     num_hidden_layers=1,\n#     vocab_size=100,\n#     max_position_embeddings=256,\n# )\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\nrm -rf found\n$TRACT_RUN --nnef-tract-core --nnef-tract-transformers --transform transformers_detect_all --transform unfold-kv-cache model.nnef.tgz run --input-from-bundle io.npz --steps --assert-output-bundle io.npz --allow-missing-outputs\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-mask-f32/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_transformers;\nextension tract_registry tract_core;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [\n    (\"tract_target_version\", \"0.21.14\"),\n    (\"torch_to_nnef_version\", \"0.19.1\"),\n    (\"torch_version\", \"2.6.0\"),\n    (\"transformers_version\", \"4.49.0\"),\n    (\"os\", \"Darwin SNS008481 24.6.0 Darwin Kernel Version 24.6.0: Mon Jul 14 11:28:30 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6030 arm64\"),\n    (\"hostname\", \"SNS008481\"),\n    (\"user\", \"emrick.sinitambirivo\"),\n    (\"py_version\", \"3.13.1 (main, Dec  3 2024, 17:59:52) [Clang 16.0.0 (clang-1600.0.26.4)] (64-bit runtime)\"),\n    (\"export_date\", \"2025-08-21 16:13:52.221182\"),\n    (\"exported_py_class\", \"TernaryPrimitive\"),\n    (\"export_cmd\", \"/Users/emrick.sinitambirivo/Documents/projects/svc-library-torch-to-nnef/.venv/bin/pytest tests/test_multi_head_att.py -rA\")\n  ];\n}\n\n\n\n\n\n\n\ngraph network(input_0, input_1, input_2) -> (output_0)\n{\n    input_0 = tract_core_external(shape = [1, 2, 4], datum_type = 'f32');\n    input_1 = tract_core_external(shape = [1, 2, 4], datum_type = 'f32');\n    input_2 = tract_core_external(shape = [1, 2, 4], datum_type = 'f32');\n    v0 = variable<scalar>(label = 'v0', shape = [2, 1]);\n    v0_aligned_rank_expanded = unsqueeze(v0, axes = [0]);\n    output_0 = tract_transformers_sdpa(input_0, input_1, input_2, v0_aligned_rank_expanded, datum_type = 'f32', acc_datum_type = 'f32', is_causal = false);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-mask-f32/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\nrm -rf found\n$TRACT_RUN --nnef-tract-core --nnef-tract-transformers . run --input-from-bundle io.npz --steps --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-non-causal-f32/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_transformers;\nextension tract_registry tract_core;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [\n    (\"tract_target_version\", \"0.21.14\"),\n    (\"torch_to_nnef_version\", \"0.19.1\"),\n    (\"torch_version\", \"2.6.0\"),\n    (\"transformers_version\", \"4.49.0\"),\n    (\"os\", \"Darwin SNS008481 24.6.0 Darwin Kernel Version 24.6.0: Mon Jul 14 11:28:30 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6030 arm64\"),\n    (\"hostname\", \"SNS008481\"),\n    (\"user\", \"emrick.sinitambirivo\"),\n    (\"py_version\", \"3.13.1 (main, Dec  3 2024, 17:59:52) [Clang 16.0.0 (clang-1600.0.26.4)] (64-bit runtime)\"),\n    (\"export_date\", \"2025-08-21 16:13:52.940220\"),\n    (\"exported_py_class\", \"FScaledDotProdAttn\"),\n    (\"export_cmd\", \"/Users/emrick.sinitambirivo/Documents/projects/svc-library-torch-to-nnef/.venv/bin/pytest tests/test_multi_head_att.py -rA\")\n  ];\n}\n\n\n\n\n\n\n\ngraph network(input_0) -> (output_0)\n{\n    input_0 = tract_core_external(shape = [1, 2, 3], datum_type = 'f32');\n    output_0 = tract_transformers_sdpa(input_0, input_0, input_0, datum_type = 'f32', acc_datum_type = 'f32', is_causal = false);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-non-causal-f32/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\nrm -rf found\n$TRACT_RUN --nnef-tract-core --nnef-tract-transformers . run --input-from-bundle io.npz --steps --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-scale-f32/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\nextension tract_registry tract_transformers;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [\n    (\"tract_target_version\", \"0.21.14\"),\n    (\"torch_to_nnef_version\", \"0.19.1\"),\n    (\"torch_version\", \"2.6.0\"),\n    (\"transformers_version\", \"4.49.0\"),\n    (\"os\", \"Darwin SNS008481 24.6.0 Darwin Kernel Version 24.6.0: Mon Jul 14 11:28:30 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6030 arm64\"),\n    (\"hostname\", \"SNS008481\"),\n    (\"user\", \"emrick.sinitambirivo\"),\n    (\"py_version\", \"3.13.1 (main, Dec  3 2024, 17:59:52) [Clang 16.0.0 (clang-1600.0.26.4)] (64-bit runtime)\"),\n    (\"export_date\", \"2025-08-20 17:36:58.170637\"),\n    (\"exported_py_class\", \"FScaledDotProdAttn\"),\n    (\"export_cmd\", \"/Users/emrick.sinitambirivo/Documents/projects/svc-library-torch-to-nnef/.venv/bin/pytest tests/test_multi_head_att.py\")\n  ];\n}\n\n\n\n\n\n\n\ngraph network(input_0) -> (output_0)\n{\n    input_0 = tract_core_external(shape = [1, 2, 3], datum_type = 'f32');\n    output_0 = tract_transformers_sdpa(input_0, input_0, input_0, datum_type = 'f32', acc_datum_type = 'f32', is_causal = false, scale = 1.3);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/sdpa/simple-scale-f32/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\nrm -rf found\n$TRACT_RUN --nnef-tract-core --nnef-tract-transformers . run --input-from-bundle io.npz --steps --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/slice-over-slice-optim-loop/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\ngraph network(input) -> (o00, o01, o10, o11)\n{\n    input = tract_core_external(shape = [2, 2], datum_type = 'f32');\n    o0 = slice(input, axes = [0], begin = [0], end = [1], stride = [1]);\n    o1 = slice(input, axes = [0], begin = [1], end = [2], stride = [1]);\n    o00 = slice(o0, axes = [1], begin = [0], end = [1], stride = [1]);\n    o01 = slice(o0, axes = [1], begin = [1], end = [2], stride = [1]);\n    o10 = slice(o1, axes = [1], begin = [0], end = [1], stride = [1]);\n    o11 = slice(o1, axes = [1], begin = [1], end = [2], stride = [1]);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/slice-over-slice-optim-loop/runme.sh",
    "content": "#!/bin/sh\n\ncd $(dirname $0)\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n# no timeout during recompilation\n$TRACT_RUN --version\n\ntimeout 3 $TRACT_RUN --nnef-tract-core .\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-change-axis/expected",
    "content": "version 1.0;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [(\"tract_nnef_ser_version\", \"0.16.10-pre\"), (\"tract_nnef_format_version\", \"beta1\")];\n}\n\ngraph network(input) -> (output) {\n  input = external(shape = [2, 1, 3]);\n  softmax1 = softmax(input, axes = [2]);\n  output = softmax1;\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-change-axis/graph.nnef",
    "content": "version 1.0;\n\ngraph rm_softmax_add(input) -> (output)\n{\n    input = external<scalar>(shape = [2, 1, 3]);\n    tmp1 = squeeze( input, axes = [1] );\n    softmax1 = softmax( tmp1, axes = [1] );\n    output = unsqueeze( softmax1, axes = [1] );\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-change-axis/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --no-nnef-tract-core . dump -q --nnef-graph found\n\nversion=`cargo metadata --format-version 1 | jq -r '.packages | map(select( (.name) == \"tract-core\") | .version) | .[] '`\nperl -pi -e \"s/$version/0.16.10-pre/\" found\n\ndiff expected found\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-change-axis-1/expected",
    "content": "version 1.0;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [(\"tract_nnef_ser_version\", \"0.16.10-pre\"), (\"tract_nnef_format_version\", \"beta1\")];\n}\n\ngraph network(input) -> (output) {\n  input = external(shape = [2, 1, 3]);\n  softmax1 = softmax(input, axes = [2]);\n  output = softmax1;\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-change-axis-1/graph.nnef",
    "content": "version 1.0;\n\ngraph add_softmax_rm(input) -> (output)\n{\n    input = external<scalar>(shape = [2, 1, 3]);\n    tmp1 = unsqueeze( input, axes = [1] );\n    softmax1 = softmax( tmp1, axes = [3] );\n    output = squeeze( softmax1, axes = [1] );\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-change-axis-1/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --no-nnef-tract-core . dump -q --nnef-graph found\n\nversion=`cargo metadata --format-version 1 | jq -r '.packages | map(select( (.name) == \"tract-core\") | .version) | .[] '`\nperl -pi -e \"s/$version/0.16.10-pre/\" found\n\ndiff expected found\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-quant/expected/graph.nnef",
    "content": "version 1.0;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [(\"tract_nnef_ser_version\", \"0.16.10-pre\"), (\"tract_nnef_format_version\", \"beta1\")];\n}\n\ngraph network(input) -> (output) {\n  input = external(shape = [2, 1, 3]);\n  output = softmax(input, axes = [3]);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-quant/expected/graph.quant",
    "content": "\"input\": zero_point_linear_quantize(zero_point = 128, scale = 0.007812500, bits = 8, signed = false, symmetric = false);\n\"output\": zero_point_linear_quantize(zero_point = 0, scale = 0.003906250, bits = 8, signed = false, symmetric = true);\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-quant/model/graph.nnef",
    "content": "version 1.0;\n\ngraph softmax_quant(input) -> (output)\n{\n    input = external<scalar>(shape = [2, 1, 3]);\n    output = softmax( input, axes = [3] );\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-quant/model/graph.quant",
    "content": "\"input\": zero_point_linear_quantize(zero_point = 128, scale = 0.0078125, bits = 8, signed = false, symmetric = false);\n\"output\": zero_point_linear_quantize(zero_point = 0, scale = 0.00390625, bits = 8, signed = false, symmetric = false);"
  },
  {
    "path": "harness/nnef-test-cases/softmax/softmax-quant/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\nrm -rf found\n$TRACT_RUN --no-nnef-tract-core model dump -q --nnef-dir found\n\nversion=`cargo metadata --format-version 1 | jq -r '.packages | map(select( (.name) == \"tract-core\") | .version) | .[] '`\nperl -pi -e \"s/$version/0.16.10-pre/\" found/graph.nnef\n\ndiff expected found\n"
  },
  {
    "path": "harness/nnef-test-cases/submodel/expected",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [(\"tract_nnef_ser_version\", \"0.18.3-pre\"), (\"tract_nnef_format_version\", \"beta1\")];\n}\n\ngraph network(input) -> (output) {\n  input = external(shape = [1, 1, 5]);\n  nnet1 = tract_core_submodel(input, label = \"nnet1\");\n  nnet_1_out_quant = tract_core_cast(nnet1, to = \"u8\");\n  nnet2 = tract_core_submodel(nnet_1_out_quant, label = \"nnet2\");\n  nnet_2_out = tract_core_cast(nnet2, to = \"f32\");\n  output = nnet_2_out;\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/submodel/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\ngraph nnet(input) -> (output)\n{\n    input = external<scalar>(shape = [1, 1, 5]);\n\n    # First model is loaded from a tgz archive\n    nnet_1_out = tract_core_submodel(input, label = 'nnet1');\n    \n    # Second model is a quantized model and loaded from a subfolder\n    nnet_1_out_quant = tract_core_cast(nnet_1_out);\n    nnet_2_out_quant = tract_core_submodel(nnet_1_out_quant, label = 'nnet2');\n    nnet_2_out = tract_core_cast(nnet_2_out_quant, to = 'f32');\n\n    output = nnet_2_out;\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/submodel/graph.quant",
    "content": "\"nnet_1_out_quant\": zero_point_linear_quantize(zero_point = 0, scale = 0.064853981, bits = 8, signed = false, symmetric = true);\n\"nnet_2_out_quant\": zero_point_linear_quantize(zero_point = 0, scale = 0.064853981, bits = 8, signed = false, symmetric = true);\n"
  },
  {
    "path": "harness/nnef-test-cases/submodel/nnet2/graph.nnef",
    "content": "version 1.0;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [(\"tract_nnef_format_version\", \"beta1\"), (\"tract_nnef_ser_version\", \"0.18.4\"), (\"tract_nnef_ser_version\", \"0.18.4\"), (\"tract_nnef_format_version\", \"beta1\")];\n}\n\ngraph network( input ) -> ( output ) {\n  input = external(shape = [1, 2, 3]);\n  conv_weights = [[[0, 0, 0], [0, 0, 0]]];\n  conv_bias = [[[1]]];\n  output_conv = conv(input, conv_weights, conv_bias, dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  output = output_conv;\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/submodel/nnet2/graph.quant",
    "content": "\"output\": zero_point_linear_quantize(zero_point = 0, scale = 0.064853981, bits = 8, signed = false, symmetric = true);\n\"conv_weights\": zero_point_linear_quantize(zero_point = 128, scale = 0.000184978, bits = 8, signed = false, symmetric = false);\n\"input\": zero_point_linear_quantize(zero_point = 0, scale = 0.064853981, bits = 8, signed = false, symmetric = true);\n\"output_conv\": zero_point_linear_quantize(zero_point = 0, scale = 0.064853981, bits = 8, signed = false, symmetric = true);\n"
  },
  {
    "path": "harness/nnef-test-cases/submodel/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . --nnef-tract-core dump -q --nnef-graph found\n\nversion=`cargo metadata --format-version 1 | jq -r '.packages | map(select( (.name) == \"tract-core\") | .version) | .[] '`\nperl -pi -e \"s/$version/0.18.3-pre/\" found\n\ndiff -u expected found\n"
  },
  {
    "path": "harness/nnef-test-cases/tdim-cmp/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\n\ngraph dummy_net(input_0) -> (v90)\n{\n    input_0 = external<integer>(shape = [1, 6]);\n    v37 = tract_core_range(0, 6, step = 1);\n    v131 = add(v37, 1);\n    v90 = lt(v37, v131);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/tdim-cmp/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . --nnef-tract-core run --allow-random-input\n\n"
  },
  {
    "path": "harness/nnef-test-cases/test_all_reduce/runme.sh",
    "content": "#!/bin/sh\n\ncd $(dirname $0)\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n# Check result is as expected\n# bug appear only if model optimized and input-fact-from-bundle\n$TRACT_RUN --nnef-tract-core ./model.nnef.tgz -O --input-facts-from-bundle ./io.npz run --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/test_any_reduce/runme.sh",
    "content": "#!/bin/sh\n\ncd $(dirname $0)\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n# Check result is as expected\n# bug appear only if model optimized and input-fact-from-bundle\n$TRACT_RUN --nnef-tract-core ./model.nnef.tgz -O --input-facts-from-bundle ./io.npz run --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/test_manage_gru_states/runme.sh",
    "content": "#!/bin/sh\n\ncd $(dirname $0)\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n# Check result is as expected\n# bug appear only if model optimized and input-fact-from-bundle\n$TRACT_RUN --nnef-tract-core ./model.nnef.tgz -O --input-facts-from-bundle ./io.npz run --input-from-bundle io.npz --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/test_stft_smaller_win/runme.sh",
    "content": "#!/bin/sh\n\ncd $(dirname $0)\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n# Check result is as expected\n# bug appear only if model optimized and input-fact-from-bundle\n$TRACT_RUN --nnef-tract-core ./model.nnef.tgz -O --input-facts-from-bundle ./io.npz run --input-from-bundle io.npz --assert-output-bundle io.npz --approx approximate\n"
  },
  {
    "path": "harness/nnef-test-cases/test_upcast_f32_attn/runme.sh",
    "content": "#!/bin/sh\n\ncd $(dirname $0)\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n# Check result is as expected\n# bug appear only if model optimized\n$TRACT_RUN --nnef-tract-core ./model.nnef.tgz -O run --input-from-bundle io.npz --assert-output-bundle io.npz --allow-float-casts\n"
  },
  {
    "path": "harness/nnef-test-cases/tile-with-tdim/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\nextension tract_symbol B;\nextension tract_symbol S;\n\ngraph net_2024_07_25T14_48_31(input_0) -> (output_0)\n{\n    input_0 = external<scalar>(shape = [B, S, 4]);\n    reducedform_x_shape = tract_core_shape_of(input_0);\n    reducedform_8_sliced = slice(reducedform_x_shape, axes = [0], begin = [1], end = [2], stride = [1]);\n    reducedform_x_shape_1 = squeeze(reducedform_8_sliced, axes = [0]);\n    reducedform_mask_to_be_expanded = [[0.0]];\n    reducedform_mask = tile(reducedform_mask_to_be_expanded, repeats = [reducedform_x_shape_1, reducedform_x_shape_1]);\n    reducedform_mask_shape = tract_core_shape_of(reducedform_mask);\n    reducedform_23_sliced = slice(reducedform_mask_shape, axes = [0], begin = [1], end = [2], stride = [1]);\n    reducedform_mask_shape_1 = squeeze(reducedform_23_sliced, axes = [0]);\n    reducedform_mask_cond = tract_core_range(0, reducedform_mask_shape_1, step = 1);\n    reducedform_33 = add(reducedform_mask_cond, 1);\n    reducedform_40 = reshape(reducedform_33, shape = [reducedform_mask_shape_1, 1]);\n    reducedform_mask_cond_expanded = unsqueeze(reducedform_mask_cond, axes = [0]);\n    reducedform_41 = lt(reducedform_mask_cond_expanded, reducedform_40);\n    reducedform_43_shape_of_false = tract_core_shape_of(reducedform_mask);\n    reducedform_43_true_expanded = tile([[0.0]], repeats = reducedform_43_shape_of_false);\n    output_0 = select(reducedform_41, reducedform_43_true_expanded, reducedform_mask);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/tile-with-tdim/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . --nnef-tract-core dump -q\n\n"
  },
  {
    "path": "harness/nnef-test-cases/uniform-mul/expected",
    "content": "version 1.0;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [(\"tract_nnef_ser_version\", \"0.18.3-pre\"), (\"tract_nnef_format_version\", \"beta1\")];\n}\n\ngraph network(input) -> (output) {\n  input = external(shape = [2, 2, 33]);\n  a = [[[2.0], [3.0]]];\n  mul_1_0 = mul(input, a);\n  b = [[[2.0], [3.0]], [[2.0], [3.0]]];\n  mul_2_0 = mul(input, b);\n  output_1 = add(mul_1_0, mul_2_0);\n  output = output_1;\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/uniform-mul/graph.nnef",
    "content": "version 1.0;\n\ngraph check_uniform_mul_not_applied(input) -> (output)\n{\n    input = external<scalar>(shape = [2, 2, 33]);\n    \n    # a_shape = [1, 2, 1]\n    a = [[[2.0], [3.0]]];\n    # This mul shouldn't be optimized with a mul_by_scalar as input_dim[0] != a_dim[0]\n    mul_1 = mul(input, a);\n\n   # b_shape = [2, 2, 1]\n    b = [[[2.0], [3.0]], [[2.0], [3.0]]];\n    \n    # This mul should be optimized with a mul_by_scalar as input_dim[0] == b_dim[0] + num_elements > 32\n    mul_2 = mul(input, b);\n\n    output = mul_1 + mul_2;\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/uniform-mul/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN --no-nnef-tract-core . dump -q --nnef-graph found\n\nversion=`cargo metadata --format-version 1 | jq -r '.packages | map(select( (.name) == \"tract-core\") | .version) | .[] '`\nperl -pi -e \"s/$version/0.18.3-pre/\" found\n\ndiff -u expected found\n\n# Check result is as expected\n$TRACT_RUN --nnef-tract-core . run --input-from-bundle io.npz --steps --assert-output-bundle io.npz\n"
  },
  {
    "path": "harness/nnef-test-cases/variable-in-fragment/graph.nnef",
    "content": "version 1.0;\n\nfragment weights(input: tensor<scalar>) -> (weights: tensor<scalar>) {\n  weights = external(label =  weights, shape = [1,2,3]);\n}\n\ngraph network( input ) -> ( output ) {\n  input = external(shape = [1, 2, 3]);\n  output = weights(input);\n}\n"
  },
  {
    "path": "harness/nnef-test-cases/variable-in-fragment/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$TRACT_RUN . dump -q\n\n"
  },
  {
    "path": "harness/parakeet-tdt-600m-v3/ci.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nROOT=$(realpath $(dirname $(realpath $0))/../..)\n. $ROOT/.travis/ci-system-setup.sh\n\nfor rt in $TRACT_RUNTIMES\ndo\n\tgpu_assert=\"\"\n\tcase \"$rt\" in\n\t\t--cuda) gpu_assert=\"--assert-op-only Cuda*,Gpu*,DeviceSync*,Const,Source,STFT,Pad,IsNan,Add,Range,Cast,Eq,Div,Sub,Scan,Gather\";;\n\t\t--metal) gpu_assert=\"--assert-op-only Metal*,Gpu*,DeviceSync*,Const,Source,STFT,Pad,IsNan,Add,Range,Cast,Eq,Div,Sub,Scan,Gather,Reduce*\";;\n\tesac\n\n\tfor m in preprocessor encoder decoder joint\n\tdo\n\t\t# Encoder uses a patched model with upper bound assertion on S\n\t\tif [ \"$m\" = \"encoder\" ]; then\n\t\t\tnnef_file=nvidia--parakeet-tdt-0.6b-v3-f32f32.$m.p1.nnef.tgz\n\t\telse\n\t\t\tnnef_file=nvidia--parakeet-tdt-0.6b-v3-f32f32.$m.nnef.tgz\n\t\tfi\n\t\t$CACHE_FILE \\\n\t\t\tasr/608/nvidia--parakeet-tdt-0.6b-v3-f32f32/$nnef_file \\\n\t\t\tasr/608/nvidia--parakeet-tdt-0.6b-v3-f32f32/nvidia--parakeet-tdt-0.6b-v3-f32f32.$m.io.npz\n\n\t\t$TRACT_RUN $MODELS/asr/608/nvidia--parakeet-tdt-0.6b-v3-f32f32/$nnef_file $rt \\\n\t\t\t--nnef-tract-transformers -t transformers_detect_all run \\\n\t\t\t--input-from-bundle $MODELS/asr/608/nvidia--parakeet-tdt-0.6b-v3-f32f32/nvidia--parakeet-tdt-0.6b-v3-f32f32.$m.io.npz \\\n\t\t\t--assert-output-bundle $MODELS/asr/608/nvidia--parakeet-tdt-0.6b-v3-f32f32/nvidia--parakeet-tdt-0.6b-v3-f32f32.$m.io.npz \\\n\t\t\t--approx very $gpu_assert\n\t done\ndone\n"
  },
  {
    "path": "harness/pre-optimized-graphes/.gitignore",
    "content": "found\n"
  },
  {
    "path": "harness/pre-optimized-graphes/hey_snips_v4_model17/expected",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\nextension tract_registry tract_pulse;\nextension tract_symbol S;\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [(\"pulse.delay\", tract_core_cast([182], to = \"i64\")), (\"pulse.input_axes\", tract_core_cast([0], to = \"i64\")), (\"pulse.output_axes\", tract_core_cast([0], to = \"i64\")), (\"tract_nnef_ser_version\", \"0.19.3-pre\"), (\"tract_nnef_format_version\", \"beta1\")];\n}\n\ngraph network(input_node) -> (i\"wavenet_2/post_proc_2-1x1_conv-conv1d/convolution/Conv2D\") {\n  input_node = external(shape = [8, 20]);\n  i\"wavenet_2/input_batch_normalisation/batchnorm/mul.fix-rank.1\" = variable<scalar>(label = \"wavenet_2/input_batch_normalisation/batchnorm/mul.fix-rank.1\", shape = [1, 20]);\n  i\"wavenet_2/input_batch_normalisation/batchnorm/mul\" = mul(input_node, i\"wavenet_2/input_batch_normalisation/batchnorm/mul.fix-rank.1\");\n  i\"wavenet_2/input_batch_normalisation/batchnorm/add_1.fix-rank.1\" = variable<scalar>(label = \"wavenet_2/input_batch_normalisation/batchnorm/add_1.fix-rank.1\", shape = [1, 20]);\n  i\"wavenet_2/input_batch_normalisation/batchnorm/add_1\" = add(i\"wavenet_2/input_batch_normalisation/batchnorm/mul\", i\"wavenet_2/input_batch_normalisation/batchnorm/add_1.fix-rank.1\");\n  i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/input_batch_normalisation/batchnorm/add_1\", axis = 0, delay = 0, overlap = 2);\n  i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/pre_conv-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [16, 20, 3]);\n  i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\" = 0.0;\n  i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D_input\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.rm_n\", axis = 0, delay = 0, overlap = 2);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul\" = mul(i\"wavenet_2/dilation_layer_0-dilation_rate_1-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_0-dilation_rate_1-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.178..S+-4\" = matmul(i\"wavenet_2/mul\", i\"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul\", i\"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add\" = add(i\"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add\", axis = 0, delay = 0, overlap = 4);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_1\" = mul(i\"wavenet_2/dilation_layer_1-dilation_rate_2-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_1-dilation_rate_2-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.174..S+-8\" = matmul(i\"wavenet_2/mul_1\", i\"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.0\" = add(i\"wavenet_2/dilation_layer_0-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.178..S+-4\", i\"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.174..S+-8\");\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_1\", i\"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_1\" = add(i\"wavenet_2/dilation_layer_1-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add\");\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_1\", axis = 0, delay = 0, overlap = 8);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_2\" = mul(i\"wavenet_2/dilation_layer_2-dilation_rate_4-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_2-dilation_rate_4-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.166..S+-16\" = matmul(i\"wavenet_2/mul_2\", i\"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.1\" = add(i\"wavenet_2/AddN.0\", i\"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.166..S+-16\");\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_2\", i\"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_2\" = add(i\"wavenet_2/dilation_layer_2-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_1\");\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_2\", axis = 0, delay = 0, overlap = 16);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_3\" = mul(i\"wavenet_2/dilation_layer_3-dilation_rate_8-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_3-dilation_rate_8-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.150..S+-32\" = matmul(i\"wavenet_2/mul_3\", i\"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.2\" = add(i\"wavenet_2/AddN.1\", i\"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.150..S+-32\");\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_3\", i\"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_3\" = add(i\"wavenet_2/dilation_layer_3-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_2\");\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_3\", axis = 0, delay = 0, overlap = 2);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_4\" = mul(i\"wavenet_2/dilation_layer_4-dilation_rate_1-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_4-dilation_rate_1-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.148..S+-34\" = matmul(i\"wavenet_2/mul_4\", i\"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.3\" = add(i\"wavenet_2/AddN.2\", i\"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.148..S+-34\");\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_4\", i\"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_4\" = add(i\"wavenet_2/dilation_layer_4-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_3\");\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_4\", axis = 0, delay = 0, overlap = 4);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_5\" = mul(i\"wavenet_2/dilation_layer_5-dilation_rate_2-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_5-dilation_rate_2-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.144..S+-38\" = matmul(i\"wavenet_2/mul_5\", i\"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.4\" = add(i\"wavenet_2/AddN.3\", i\"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.144..S+-38\");\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_5\", i\"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_5\" = add(i\"wavenet_2/dilation_layer_5-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_4\");\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_5\", axis = 0, delay = 0, overlap = 8);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_6\" = mul(i\"wavenet_2/dilation_layer_6-dilation_rate_4-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_6-dilation_rate_4-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.136..S+-46\" = matmul(i\"wavenet_2/mul_6\", i\"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.5\" = add(i\"wavenet_2/AddN.4\", i\"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.136..S+-46\");\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_6\", i\"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_6\" = add(i\"wavenet_2/dilation_layer_6-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_5\");\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_6\", axis = 0, delay = 0, overlap = 16);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_7\" = mul(i\"wavenet_2/dilation_layer_7-dilation_rate_8-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_7-dilation_rate_8-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.120..S+-62\" = matmul(i\"wavenet_2/mul_7\", i\"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.6\" = add(i\"wavenet_2/AddN.5\", i\"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.120..S+-62\");\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_7\", i\"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_7\" = add(i\"wavenet_2/dilation_layer_7-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_6\");\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_7\", axis = 0, delay = 0, overlap = 2);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_8\" = mul(i\"wavenet_2/dilation_layer_8-dilation_rate_1-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_8-dilation_rate_1-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.118..S+-64\" = matmul(i\"wavenet_2/mul_8\", i\"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.7\" = add(i\"wavenet_2/AddN.6\", i\"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.118..S+-64\");\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_8\", i\"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_8\" = add(i\"wavenet_2/dilation_layer_8-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_7\");\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_8\", axis = 0, delay = 0, overlap = 4);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_9\" = mul(i\"wavenet_2/dilation_layer_9-dilation_rate_2-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_9-dilation_rate_2-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.114..S+-68\" = matmul(i\"wavenet_2/mul_9\", i\"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.8\" = add(i\"wavenet_2/AddN.7\", i\"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.114..S+-68\");\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_9\", i\"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_9\" = add(i\"wavenet_2/dilation_layer_9-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_8\");\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_9\", axis = 0, delay = 0, overlap = 8);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_10\" = mul(i\"wavenet_2/dilation_layer_10-dilation_rate_4-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_10-dilation_rate_4-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.106..S+-76\" = matmul(i\"wavenet_2/mul_10\", i\"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.9\" = add(i\"wavenet_2/AddN.8\", i\"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.106..S+-76\");\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_10\", i\"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_10\" = add(i\"wavenet_2/dilation_layer_10-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_9\");\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_10\", axis = 0, delay = 0, overlap = 16);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_11\" = mul(i\"wavenet_2/dilation_layer_11-dilation_rate_8-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_11-dilation_rate_8-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.90..S+-92\" = matmul(i\"wavenet_2/mul_11\", i\"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.10\" = add(i\"wavenet_2/AddN.9\", i\"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.90..S+-92\");\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_11\", i\"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_11\" = add(i\"wavenet_2/dilation_layer_11-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_10\");\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_11\", axis = 0, delay = 0, overlap = 2);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_12\" = mul(i\"wavenet_2/dilation_layer_12-dilation_rate_1-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_12-dilation_rate_1-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.88..S+-94\" = matmul(i\"wavenet_2/mul_12\", i\"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.11\" = add(i\"wavenet_2/AddN.10\", i\"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.88..S+-94\");\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_12\", i\"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_12\" = add(i\"wavenet_2/dilation_layer_12-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_11\");\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_12\", axis = 0, delay = 0, overlap = 4);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_13\" = mul(i\"wavenet_2/dilation_layer_13-dilation_rate_2-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_13-dilation_rate_2-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.84..S+-98\" = matmul(i\"wavenet_2/mul_13\", i\"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.12\" = add(i\"wavenet_2/AddN.11\", i\"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.84..S+-98\");\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_13\", i\"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_13\" = add(i\"wavenet_2/dilation_layer_13-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_12\");\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_13\", axis = 0, delay = 0, overlap = 8);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_14\" = mul(i\"wavenet_2/dilation_layer_14-dilation_rate_4-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_14-dilation_rate_4-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.76..S+-106\" = matmul(i\"wavenet_2/mul_14\", i\"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.13\" = add(i\"wavenet_2/AddN.12\", i\"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.76..S+-106\");\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_14\", i\"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_14\" = add(i\"wavenet_2/dilation_layer_14-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_13\");\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_14\", axis = 0, delay = 0, overlap = 16);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_15\" = mul(i\"wavenet_2/dilation_layer_15-dilation_rate_8-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_15-dilation_rate_8-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.60..S+-122\" = matmul(i\"wavenet_2/mul_15\", i\"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.14\" = add(i\"wavenet_2/AddN.13\", i\"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.60..S+-122\");\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_15\", i\"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_15\" = add(i\"wavenet_2/dilation_layer_15-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_14\");\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_15\", axis = 0, delay = 0, overlap = 2);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_16\" = mul(i\"wavenet_2/dilation_layer_16-dilation_rate_1-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_16-dilation_rate_1-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.58..S+-124\" = matmul(i\"wavenet_2/mul_16\", i\"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.15\" = add(i\"wavenet_2/AddN.14\", i\"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.58..S+-124\");\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_16\", i\"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_16\" = add(i\"wavenet_2/dilation_layer_16-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_15\");\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_16\", axis = 0, delay = 0, overlap = 4);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_17\" = mul(i\"wavenet_2/dilation_layer_17-dilation_rate_2-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_17-dilation_rate_2-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.54..S+-128\" = matmul(i\"wavenet_2/mul_17\", i\"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.16\" = add(i\"wavenet_2/AddN.15\", i\"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.54..S+-128\");\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_17\", i\"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_17\" = add(i\"wavenet_2/dilation_layer_17-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_16\");\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_17\", axis = 0, delay = 0, overlap = 8);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_18\" = mul(i\"wavenet_2/dilation_layer_18-dilation_rate_4-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_18-dilation_rate_4-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.46..S+-136\" = matmul(i\"wavenet_2/mul_18\", i\"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.17\" = add(i\"wavenet_2/AddN.16\", i\"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.46..S+-136\");\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_18\", i\"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_18\" = add(i\"wavenet_2/dilation_layer_18-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_17\");\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_18\", axis = 0, delay = 0, overlap = 16);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_19\" = mul(i\"wavenet_2/dilation_layer_19-dilation_rate_8-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_19-dilation_rate_8-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.30..S+-152\" = matmul(i\"wavenet_2/mul_19\", i\"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.18\" = add(i\"wavenet_2/AddN.17\", i\"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.30..S+-152\");\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_19\", i\"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_19\" = add(i\"wavenet_2/dilation_layer_19-dilation_rate_8-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_18\");\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_19\", axis = 0, delay = 0, overlap = 2);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_20\" = mul(i\"wavenet_2/dilation_layer_20-dilation_rate_1-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_20-dilation_rate_1-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.28..S+-154\" = matmul(i\"wavenet_2/mul_20\", i\"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.19\" = add(i\"wavenet_2/AddN.18\", i\"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.28..S+-154\");\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_20\", i\"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_20\" = add(i\"wavenet_2/dilation_layer_20-dilation_rate_1-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_19\");\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_20\", axis = 0, delay = 0, overlap = 4);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [2], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_21\" = mul(i\"wavenet_2/dilation_layer_21-dilation_rate_2-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_21-dilation_rate_2-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.24..S+-158\" = matmul(i\"wavenet_2/mul_21\", i\"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.20\" = add(i\"wavenet_2/AddN.19\", i\"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.24..S+-158\");\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_21\", i\"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_21\" = add(i\"wavenet_2/dilation_layer_21-dilation_rate_2-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_20\");\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_21\", axis = 0, delay = 0, overlap = 8);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [4], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_22\" = mul(i\"wavenet_2/dilation_layer_22-dilation_rate_4-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_22-dilation_rate_4-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.16..S+-166\" = matmul(i\"wavenet_2/mul_22\", i\"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.21\" = add(i\"wavenet_2/AddN.20\", i\"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_skip-conv1d/convolution/Conv2D.split-over-2.16..S+-166\");\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [16, 64]);\n  i\"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_22\", i\"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/add_22\" = add(i\"wavenet_2/dilation_layer_22-dilation_rate_4-1x1_conv_transform-conv1d/convolution/Conv2D\", i\"wavenet_2/add_21\");\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\" = tract_pulse_delay(i\"wavenet_2/add_22\", axis = 0, delay = 0, overlap = 16);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/Tanh\" = tanh(i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\" = unsqueeze(i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/convolution/Conv2D.delay\", axes = [0]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\" = variable<scalar>(label = \"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", shape = [64, 16, 3]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\" = transpose(i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D.add_n\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\" = conv(i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D_input\", i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D.kernel_reorg_go\", i\"wavenet_2/pre_conv-conv1d/convolution/Conv2D.bias\", dilation = [8], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D\" = transpose(i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D_conv\", axes = [0, 2, 1]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\" = squeeze(i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D\", axes = [0]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/Sigmoid\" = sigmoid(i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/convolution/Conv2D.rm_n\");\n  i\"wavenet_2/mul_23\" = mul(i\"wavenet_2/dilation_layer_23-dilation_rate_8-filter-conv1d/Tanh\", i\"wavenet_2/dilation_layer_23-dilation_rate_8-gate-conv1d/Sigmoid\");\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/dilation_layer_23-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 64]);\n  i\"wavenet_2/dilation_layer_23-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/mul_23\", i\"wavenet_2/dilation_layer_23-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/AddN.22\" = add(i\"wavenet_2/AddN.21\", i\"wavenet_2/dilation_layer_23-dilation_rate_8-1x1_conv_skip-conv1d/convolution/Conv2D\");\n  i\"wavenet_2/Relu.low.cst.1.1.1\" = [[0.0]];\n  i\"wavenet_2/Relu.low\" = max(i\"wavenet_2/AddN.22\", i\"wavenet_2/Relu.low.cst.1.1.1\");\n  i\"wavenet_2/post_proc_1-1x1_conv-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/post_proc_1-1x1_conv-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [32, 32]);\n  i\"wavenet_2/post_proc_1-1x1_conv-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/Relu.low\", i\"wavenet_2/post_proc_1-1x1_conv-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n  i\"wavenet_2/Relu.low.cst.2.1.1\" = [[0.0]];\n  i\"wavenet_2/post_proc_1-1x1_conv-conv1d/Relu.low\" = max(i\"wavenet_2/post_proc_1-1x1_conv-conv1d/convolution/Conv2D\", i\"wavenet_2/Relu.low.cst.2.1.1\");\n  i\"wavenet_2/post_proc_2-1x1_conv-conv1d/convolution/Conv2D.filters_as_co_ci\" = variable<scalar>(label = \"wavenet_2/post_proc_2-1x1_conv-conv1d/convolution/Conv2D.filters_as_co_ci\", shape = [2, 32]);\n  i\"wavenet_2/post_proc_2-1x1_conv-conv1d/convolution/Conv2D\" = matmul(i\"wavenet_2/post_proc_1-1x1_conv-conv1d/Relu.low\", i\"wavenet_2/post_proc_2-1x1_conv-conv1d/convolution/Conv2D.filters_as_co_ci\", transposeA = false, transposeB = true);\n}\n"
  },
  {
    "path": "harness/pre-optimized-graphes/hey_snips_v4_model17/runme.sh",
    "content": "#!/bin/sh\n\ncd `dirname $0`\n\nROOT=$(dirname $(realpath $0))/../../..\n. $ROOT/.travis/ci-system-setup.sh\n\nset -ex\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$CACHE_FILE hey_snips_v4_model17.pb\n$TRACT_RUN $MODELS/hey_snips_v4_model17.pb -i S,20,f32 --pulse 8 --nnef-tract-pulse --nnef-extended-identifier dump -q --nnef-graph found\n\nversion=`cargo metadata --format-version 1 | jq -r '.packages | map(select( (.name) == \"tract-core\") | .version) | .[] '`\nperl -pi -e \"s/$version/0.19.3-pre/\" found\n\ndiff -u expected found\n"
  },
  {
    "path": "harness/pre-optimized-graphes/mdl-en-2019-Q3-librispeech/expected",
    "content": "version 1.0;\n\nextension tract_registry tract_core;\nextension tract_registry tract_pulse;\nextension tract_symbol S;\n\nfragment scan_body_0(\n    i\"fastlstm1.c\": tensor<scalar>,\n    i\"fastlstm1.r\": tensor<scalar>,\n    i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\": tensor<scalar>,\n    i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\": tensor<scalar>,\n    i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\": tensor<scalar>,\n    i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\": tensor<scalar>,\n    i\"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\": tensor<scalar>,\n    i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\": tensor<scalar>,\n    i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\": tensor<scalar>,\n    i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\": tensor<scalar>,\n    i\"fastlstm1.four_parts.split-1-over-1.0..256.slice\": tensor<scalar>,\n    i\"fastlstm1.four_parts.split-1-over-1.256..512.slice\": tensor<scalar>,\n    i\"fastlstm1.four_parts.split-1-over-1.512..768.slice\": tensor<scalar>,\n    i\"fastlstm1.four_parts.split-1-over-1.768..1024.slice\": tensor<scalar>,\n    i\"fastlstm1.h_new.W.split-1-over-1.0..128.slice\": tensor<scalar>,\n    i\"fastlstm1.h_new.split-1-over-1.0..128.slice\": tensor<scalar>,\n    i\"fastlstm1.peephole0.mul.fix-rank\": tensor<scalar>,\n    i\"fastlstm1.peephole1.mul.fix-rank\": tensor<scalar>,\n    i\"fastlstm1.peephole2.mul.fix-rank\": tensor<scalar>\n) -> (i\"fastlstm1.c_new\": tensor<scalar>, i\"fastlstm1.r_new\": tensor<scalar>, i\"fastlstm1.h_new.W.prop_axis.a.input_0\": tensor<scalar>)\n{\n  i\"fastlstm1.peephole0.mul\" = mul(i\"fastlstm1.peephole0.mul.fix-rank\", i\"fastlstm1.c\");\n  i\"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.256..384\" = tract_core_einsum([i\"fastlstm1.r\", i\"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\"], expr = \"k,kn->bn\", acc = \"f32\", output = \"\");\n  i\"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.add-1\" = add(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\", i\"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.256..384\");\n  i\"fastlstm1.four_parts.split-over-1.0..256\" = add(i\"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.add-1\", i\"fastlstm1.four_parts.split-1-over-1.0..256.slice\");\n  i\"fastlstm1.peephole0.output\" = add(i\"fastlstm1.peephole0.mul\", i\"fastlstm1.four_parts.split-over-1.0..256\");\n  i\"fastlstm1.peephole0.output.nolin\" = sigmoid(i\"fastlstm1.peephole0.output\");\n  i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.prop_axis.a.output\" = squeeze(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\", axes = [0]);\n  i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.256..384\" = tract_core_einsum([i\"fastlstm1.r\", i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\"], expr = \"k,kn->bn\", acc = \"f32\", output = \"\");\n  i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.add-1\" = add(i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.prop_axis.a.output\", i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.256..384\");\n  i\"fastlstm1.four_parts.split-over-1.512..768\" = add(i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.add-1\", i\"fastlstm1.four_parts.split-1-over-1.512..768.slice\");\n  i\"fastlstm1.four_parts.j.nolin\" = tanh(i\"fastlstm1.four_parts.split-over-1.512..768\");\n  i\"fastlstm1.c_update\" = mul(i\"fastlstm1.peephole0.output.nolin\", i\"fastlstm1.four_parts.j.nolin\");\n  i\"fastlstm1.peephole1.mul\" = mul(i\"fastlstm1.peephole1.mul.fix-rank\", i\"fastlstm1.c\");\n  i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.prop_axis.a.output\" = squeeze(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\", axes = [0]);\n  i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.256..384\" = tract_core_einsum([i\"fastlstm1.r\", i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\"], expr = \"k,kn->bn\", acc = \"f32\", output = \"\");\n  i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.add-1\" = add(i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.prop_axis.a.output\", i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.256..384\");\n  i\"fastlstm1.four_parts.split-over-1.256..512\" = add(i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.add-1\", i\"fastlstm1.four_parts.split-1-over-1.256..512.slice\");\n  i\"fastlstm1.peephole1.output\" = add(i\"fastlstm1.peephole1.mul\", i\"fastlstm1.four_parts.split-over-1.256..512\");\n  i\"fastlstm1.peephole1.output.nolin\" = sigmoid(i\"fastlstm1.peephole1.output\");\n  i\"fastlstm1.c_prop\" = mul(i\"fastlstm1.peephole1.output.nolin\", i\"fastlstm1.c\");\n  i\"fastlstm1.c_new\" = add(i\"fastlstm1.c_update\", i\"fastlstm1.c_prop\");\n  i\"fastlstm1.tanh_c\" = tanh(i\"fastlstm1.c_new\");\n  i\"fastlstm1.peephole2.mul\" = mul(i\"fastlstm1.peephole2.mul.fix-rank\", i\"fastlstm1.c_new\");\n  i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.prop_axis.a.output\" = squeeze(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\", axes = [0]);\n  i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.256..384\" = tract_core_einsum([i\"fastlstm1.r\", i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\"], expr = \"k,kn->bn\", acc = \"f32\", output = \"\");\n  i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.add-1\" = add(i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.prop_axis.a.output\", i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.256..384\");\n  i\"fastlstm1.four_parts.split-over-1.768..1024\" = add(i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.add-1\", i\"fastlstm1.four_parts.split-1-over-1.768..1024.slice\");\n  i\"fastlstm1.peephole2.output\" = add(i\"fastlstm1.peephole2.mul\", i\"fastlstm1.four_parts.split-over-1.768..1024\");\n  i\"fastlstm1.peephole2.output.nolin\" = sigmoid(i\"fastlstm1.peephole2.output\");\n  i\"fastlstm1.m\" = mul(i\"fastlstm1.tanh_c\", i\"fastlstm1.peephole2.output.nolin\");\n  i\"fastlstm1.h_new.W.split-over-1.0..128\" = tract_core_einsum([i\"fastlstm1.m\", i\"fastlstm1.h_new.W.split-1-over-1.0..128.slice\"], expr = \"bk,kn->n\", acc = \"f32\", output = \"\");\n  i\"fastlstm1.h_new.split-over-1.0..128\" = add(i\"fastlstm1.h_new.W.split-over-1.0..128\", i\"fastlstm1.h_new.split-1-over-1.0..128.slice\");\n  i\"fastlstm1.h_new.W.prop_axis.a.input_0\" = unsqueeze(i\"fastlstm1.m\", axes = [0]);\n  i\"fastlstm1.r_new\" = i\"fastlstm1.h_new.split-over-1.0..128\";\n}\n\nfragment scan_body_1(\n    i\"fastlstm2.c\": tensor<scalar>,\n    i\"fastlstm2.r\": tensor<scalar>,\n    i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\": tensor<scalar>,\n    i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\": tensor<scalar>,\n    i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\": tensor<scalar>,\n    i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\": tensor<scalar>,\n    i\"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\": tensor<scalar>,\n    i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\": tensor<scalar>,\n    i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\": tensor<scalar>,\n    i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\": tensor<scalar>,\n    i\"fastlstm2.four_parts.split-1-over-1.0..256.slice\": tensor<scalar>,\n    i\"fastlstm2.four_parts.split-1-over-1.256..512.slice\": tensor<scalar>,\n    i\"fastlstm2.four_parts.split-1-over-1.512..768.slice\": tensor<scalar>,\n    i\"fastlstm2.four_parts.split-1-over-1.768..1024.slice\": tensor<scalar>,\n    i\"fastlstm2.h_new.W.split-1-over-1.0..128.slice\": tensor<scalar>,\n    i\"fastlstm2.h_new.split-1-over-1.0..128.slice\": tensor<scalar>,\n    i\"fastlstm2.peephole0.mul.fix-rank\": tensor<scalar>,\n    i\"fastlstm2.peephole1.mul.fix-rank\": tensor<scalar>,\n    i\"fastlstm2.peephole2.mul.fix-rank\": tensor<scalar>\n) -> (i\"fastlstm2.c_new\": tensor<scalar>, i\"fastlstm2.r_new\": tensor<scalar>, i\"fastlstm2.h_new.W.prop_axis.a.input_0\": tensor<scalar>)\n{\n  i\"fastlstm2.peephole0.mul\" = mul(i\"fastlstm2.peephole0.mul.fix-rank\", i\"fastlstm2.c\");\n  i\"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.256..384\" = tract_core_einsum([i\"fastlstm2.r\", i\"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\"], expr = \"k,kn->bn\", acc = \"f32\", output = \"\");\n  i\"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.add-1\" = add(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\", i\"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.256..384\");\n  i\"fastlstm2.four_parts.split-over-1.0..256\" = add(i\"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.add-1\", i\"fastlstm2.four_parts.split-1-over-1.0..256.slice\");\n  i\"fastlstm2.peephole0.output\" = add(i\"fastlstm2.peephole0.mul\", i\"fastlstm2.four_parts.split-over-1.0..256\");\n  i\"fastlstm2.peephole0.output.nolin\" = sigmoid(i\"fastlstm2.peephole0.output\");\n  i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.prop_axis.a.output\" = squeeze(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\", axes = [0]);\n  i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.256..384\" = tract_core_einsum([i\"fastlstm2.r\", i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\"], expr = \"k,kn->bn\", acc = \"f32\", output = \"\");\n  i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.add-1\" = add(i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.prop_axis.a.output\", i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.256..384\");\n  i\"fastlstm2.four_parts.split-over-1.512..768\" = add(i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.add-1\", i\"fastlstm2.four_parts.split-1-over-1.512..768.slice\");\n  i\"fastlstm2.four_parts.j.nolin\" = tanh(i\"fastlstm2.four_parts.split-over-1.512..768\");\n  i\"fastlstm2.c_update\" = mul(i\"fastlstm2.peephole0.output.nolin\", i\"fastlstm2.four_parts.j.nolin\");\n  i\"fastlstm2.peephole1.mul\" = mul(i\"fastlstm2.peephole1.mul.fix-rank\", i\"fastlstm2.c\");\n  i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.prop_axis.a.output\" = squeeze(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\", axes = [0]);\n  i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.256..384\" = tract_core_einsum([i\"fastlstm2.r\", i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\"], expr = \"k,kn->bn\", acc = \"f32\", output = \"\");\n  i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.add-1\" = add(i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.prop_axis.a.output\", i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.256..384\");\n  i\"fastlstm2.four_parts.split-over-1.256..512\" = add(i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.add-1\", i\"fastlstm2.four_parts.split-1-over-1.256..512.slice\");\n  i\"fastlstm2.peephole1.output\" = add(i\"fastlstm2.peephole1.mul\", i\"fastlstm2.four_parts.split-over-1.256..512\");\n  i\"fastlstm2.peephole1.output.nolin\" = sigmoid(i\"fastlstm2.peephole1.output\");\n  i\"fastlstm2.c_prop\" = mul(i\"fastlstm2.peephole1.output.nolin\", i\"fastlstm2.c\");\n  i\"fastlstm2.c_new\" = add(i\"fastlstm2.c_update\", i\"fastlstm2.c_prop\");\n  i\"fastlstm2.tanh_c\" = tanh(i\"fastlstm2.c_new\");\n  i\"fastlstm2.peephole2.mul\" = mul(i\"fastlstm2.peephole2.mul.fix-rank\", i\"fastlstm2.c_new\");\n  i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.prop_axis.a.output\" = squeeze(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\", axes = [0]);\n  i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.256..384\" = tract_core_einsum([i\"fastlstm2.r\", i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\"], expr = \"k,kn->bn\", acc = \"f32\", output = \"\");\n  i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.add-1\" = add(i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.prop_axis.a.output\", i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.256..384\");\n  i\"fastlstm2.four_parts.split-over-1.768..1024\" = add(i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.add-1\", i\"fastlstm2.four_parts.split-1-over-1.768..1024.slice\");\n  i\"fastlstm2.peephole2.output\" = add(i\"fastlstm2.peephole2.mul\", i\"fastlstm2.four_parts.split-over-1.768..1024\");\n  i\"fastlstm2.peephole2.output.nolin\" = sigmoid(i\"fastlstm2.peephole2.output\");\n  i\"fastlstm2.m\" = mul(i\"fastlstm2.tanh_c\", i\"fastlstm2.peephole2.output.nolin\");\n  i\"fastlstm2.h_new.W.split-over-1.0..128\" = tract_core_einsum([i\"fastlstm2.m\", i\"fastlstm2.h_new.W.split-1-over-1.0..128.slice\"], expr = \"bk,kn->n\", acc = \"f32\", output = \"\");\n  i\"fastlstm2.h_new.split-over-1.0..128\" = add(i\"fastlstm2.h_new.W.split-over-1.0..128\", i\"fastlstm2.h_new.split-1-over-1.0..128.slice\");\n  i\"fastlstm2.h_new.W.prop_axis.a.input_0\" = unsqueeze(i\"fastlstm2.m\", axes = [0]);\n  i\"fastlstm2.r_new\" = i\"fastlstm2.h_new.split-over-1.0..128\";\n}\n\nfragment tract_core_properties(\n) -> (properties: (string, tensor<scalar>)[])\n{\n  properties = [(\"pulse.delay\", tract_core_cast([6], to = \"i64\")), (\"pulse.input_axes\", tract_core_cast([0], to = \"i64\")), (\"pulse.output_axes\", tract_core_cast([0], to = \"i64\")), (\"tract_nnef_ser_version\", \"0.19.3-pre\"), (\"tract_nnef_format_version\", \"beta1\")];\n}\n\ngraph network(input) -> (output) {\n  input = external(shape = [24, 40]);\n  i\"lda.output.delay\" = tract_pulse_delay(input, axis = 0, delay = 0, overlap = 4);\n  i\"lda.output.add_n\" = unsqueeze(i\"lda.output.delay\", axes = [0]);\n  i\"lda.kernel.0\" = variable<scalar>(label = \"lda.kernel.0\", shape = [200, 40, 5]);\n  i\"lda.output.bias\" = variable<scalar>(label = \"lda.output.bias\", shape = [200]);\n  i\"lda.output_input\" = transpose(i\"lda.output.add_n\", axes = [0, 2, 1]);\n  i\"lda.output_conv\" = conv(i\"lda.output_input\", i\"lda.kernel.0\", i\"lda.output.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"lda.output\" = transpose(i\"lda.output_conv\", axes = [0, 2, 1]);\n  i\"lda.output.rm_n\" = squeeze(i\"lda.output\", axes = [0]);\n  i\"tdnn1.affine.output.filters_as_co_ci\" = variable<scalar>(label = \"tdnn1.affine.output.filters_as_co_ci\", shape = [256, 200]);\n  i\"tdnn1.affine.output.einsum\" = matmul(i\"tdnn1.affine.output.filters_as_co_ci\", i\"lda.output.rm_n\", transposeA = false, transposeB = true);\n  i\"tdnn1.affine.output.bias.reshape.1\" = variable<scalar>(label = \"tdnn1.affine.output.bias.reshape.1\", shape = [256, 1]);\n  i\"tdnn1.affine.output\" = add(i\"tdnn1.affine.output.einsum\", i\"tdnn1.affine.output.bias.reshape.1\");\n  i\"tdnn1.relu.output.low.cst.1\" = [[0.0]];\n  i\"tdnn1.relu.output.low\" = max(i\"tdnn1.affine.output\", i\"tdnn1.relu.output.low.cst.1\");\n  i\"tdnn1.renorm.reduced.sum.sqr\" = square(i\"tdnn1.relu.output.low\");\n  i\"tdnn1.renorm.reduced.sum.sum\" = sum_reduce(i\"tdnn1.renorm.reduced.sum.sqr\", axes = [0]);\n  i\"tdnn1.renorm.reduced.sum.norm.fix-rank.1\" = [[256.0]];\n  i\"tdnn1.renorm.reduced.sum.norm\" = div(i\"tdnn1.renorm.reduced.sum.sum\", i\"tdnn1.renorm.reduced.sum.norm.fix-rank.1\");\n  i\"tdnn1.renorm.output-recip\" = rsqrt(i\"tdnn1.renorm.reduced.sum.norm\");\n  i\"tdnn1.renorm.output\" = mul(i\"tdnn1.relu.output.low\", i\"tdnn1.renorm.output-recip\");\n  i\"tdnn2.affine.output.delay\" = tract_pulse_delay(i\"tdnn1.renorm.output\", axis = 1, delay = 0, overlap = 2);\n  i\"tdnn2.affine.output.add_n\" = unsqueeze(i\"tdnn2.affine.output.delay\", axes = [0]);\n  i\"tdnn2.affine.kernel.0\" = variable<scalar>(label = \"tdnn2.affine.kernel.0\", shape = [256, 256, 3]);\n  i\"tdnn2.affine.output.bias\" = variable<scalar>(label = \"tdnn2.affine.output.bias\", shape = [256]);\n  i\"tdnn2.affine.output_conv\" = conv(i\"tdnn2.affine.output.add_n\", i\"tdnn2.affine.kernel.0\", i\"tdnn2.affine.output.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"tdnn2.affine.output\" = i\"tdnn2.affine.output_conv\";\n  i\"tdnn2.affine.output.rm_n\" = squeeze(i\"tdnn2.affine.output\", axes = [0]);\n  i\"tdnn2.relu.output.low\" = max(i\"tdnn2.affine.output.rm_n\", i\"tdnn1.relu.output.low.cst.1\");\n  i\"tdnn2.renorm.reduced.sum.sqr\" = square(i\"tdnn2.relu.output.low\");\n  i\"tdnn2.renorm.reduced.sum.sum\" = sum_reduce(i\"tdnn2.renorm.reduced.sum.sqr\", axes = [0]);\n  i\"tdnn2.renorm.reduced.sum.norm.fix-rank.1\" = [[256.0]];\n  i\"tdnn2.renorm.reduced.sum.norm\" = div(i\"tdnn2.renorm.reduced.sum.sum\", i\"tdnn2.renorm.reduced.sum.norm.fix-rank.1\");\n  i\"tdnn2.renorm.output-recip\" = rsqrt(i\"tdnn2.renorm.reduced.sum.norm\");\n  i\"tdnn2.renorm.output\" = mul(i\"tdnn2.relu.output.low\", i\"tdnn2.renorm.output-recip\");\n  i\"tdnn3.affine.output.add_n\" = unsqueeze(i\"tdnn2.renorm.output\", axes = [0]);\n  i\"tdnn3.affine.kernel.0\" = variable<scalar>(label = \"tdnn3.affine.kernel.0\", shape = [256, 256, 3]);\n  i\"tdnn3.affine.output.bias\" = variable<scalar>(label = \"tdnn3.affine.output.bias\", shape = [256]);\n  i\"tdnn3.affine.output_conv\" = conv(i\"tdnn3.affine.output.add_n\", i\"tdnn3.affine.kernel.0\", i\"tdnn3.affine.output.bias\", dilation = [1], stride = [3], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"tdnn3.affine.output\" = i\"tdnn3.affine.output_conv\";\n  i\"tdnn3.affine.output.rm_n\" = squeeze(i\"tdnn3.affine.output\", axes = [0]);\n  i\"tdnn3.relu.output.low\" = max(i\"tdnn3.affine.output.rm_n\", i\"tdnn1.relu.output.low.cst.1\");\n  i\"tdnn3.renorm.reduced.sum.sqr\" = square(i\"tdnn3.relu.output.low\");\n  i\"tdnn3.renorm.reduced.sum.sum\" = sum_reduce(i\"tdnn3.renorm.reduced.sum.sqr\", axes = [0]);\n  i\"tdnn3.renorm.reduced.sum.norm.fix-rank.1\" = [[256.0]];\n  i\"tdnn3.renorm.reduced.sum.norm\" = div(i\"tdnn3.renorm.reduced.sum.sum\", i\"tdnn3.renorm.reduced.sum.norm.fix-rank.1\");\n  i\"tdnn3.renorm.output-recip\" = rsqrt(i\"tdnn3.renorm.reduced.sum.norm\");\n  i\"tdnn3.renorm.output\" = mul(i\"tdnn3.relu.output.low\", i\"tdnn3.renorm.output-recip\");\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256.prop_axis.a.input_1\" = variable<scalar>(label = \"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256.prop_axis.a.input_1\", shape = [256, 256]);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\" = matmul(i\"tdnn3.renorm.output\", i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256.prop_axis.a.input_1\", transposeA = true, transposeB = false);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_a\" = unsqueeze(i\"tdnn3.renorm.output\", axes = [0]);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_b\" = variable<scalar>(label = \"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_b\", shape = [1, 256, 256]);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\" = matmul(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_a\", i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_b\", transposeA = true, transposeB = false);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_c.0\" = transpose(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\", axes = [1, 0, 2]);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_a\" = unsqueeze(i\"tdnn3.renorm.output\", axes = [0]);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_b\" = variable<scalar>(label = \"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_b\", shape = [1, 256, 256]);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\" = matmul(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_a\", i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_b\", transposeA = true, transposeB = false);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_c.0\" = transpose(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\", axes = [1, 0, 2]);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_a\" = unsqueeze(i\"tdnn3.renorm.output\", axes = [0]);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_b\" = variable<scalar>(label = \"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_b\", shape = [1, 256, 256]);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\" = matmul(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_a\", i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_b\", transposeA = true, transposeB = false);\n  i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_c.0\" = transpose(i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\", axes = [1, 0, 2]);\n  i\"tap.tap.fastlstm1.c_init.0-35/0-104/0\" = variable<scalar>(label = \"tap.tap.fastlstm1.c_init.0-35/0-104/0\", shape = [1, 256]);\n  i\"tap.tap.tap.fastlstm1.r_init.0-36/0-110/0-164/0\" = variable<scalar>(label = \"tap.tap.tap.fastlstm1.r_init.0-36/0-110/0-164/0\", shape = [128]);\n  i\"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\" = variable<scalar>(label = \"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\", shape = [128, 256]);\n  i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\" = variable<scalar>(label = \"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\", shape = [128, 256]);\n  i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\" = variable<scalar>(label = \"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\", shape = [128, 256]);\n  i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\" = variable<scalar>(label = \"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\", shape = [128, 256]);\n  i\"fastlstm1.four_parts.split-1-over-1.0..256.slice\" = variable<scalar>(label = \"fastlstm1.four_parts.split-1-over-1.0..256.slice\", shape = [1, 256]);\n  i\"fastlstm1.four_parts.split-1-over-1.256..512.slice\" = variable<scalar>(label = \"fastlstm1.four_parts.split-1-over-1.256..512.slice\", shape = [1, 256]);\n  i\"fastlstm1.four_parts.split-1-over-1.512..768.slice\" = variable<scalar>(label = \"fastlstm1.four_parts.split-1-over-1.512..768.slice\", shape = [1, 256]);\n  i\"fastlstm1.four_parts.split-1-over-1.768..1024.slice\" = variable<scalar>(label = \"fastlstm1.four_parts.split-1-over-1.768..1024.slice\", shape = [1, 256]);\n  i\"fastlstm1.h_new.W.split-1-over-1.0..128.slice\" = variable<scalar>(label = \"fastlstm1.h_new.W.split-1-over-1.0..128.slice\", shape = [256, 128]);\n  i\"fastlstm1.h_new.split-1-over-1.0..128.slice\" = variable<scalar>(label = \"fastlstm1.h_new.split-1-over-1.0..128.slice\", shape = [128]);\n  i\"fastlstm1.peephole0.mul.fix-rank\" = variable<scalar>(label = \"fastlstm1.peephole0.mul.fix-rank\", shape = [1, 256]);\n  i\"fastlstm1.peephole1.mul.fix-rank\" = variable<scalar>(label = \"fastlstm1.peephole1.mul.fix-rank\", shape = [1, 256]);\n  i\"fastlstm1.peephole2.mul.fix-rank\" = variable<scalar>(label = \"fastlstm1.peephole2.mul.fix-rank\", shape = [1, 256]);\n  i\"fastlstm1.c_final\" = tract_core_scan(body = \"scan_body_0\", scan = [(\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\", i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\", 0, 1), (\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\", i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_c.0\", 0, 1), (\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\", i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_c.0\", 0, 1), (\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\", i\"fastlstm1.c_final.extracted.fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_c.0\", 0, 1)], full = [(\"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\", i\"fastlstm1.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\"), (\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\", i\"fastlstm1.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\"), (\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\", i\"fastlstm1.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\"), (\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\", i\"fastlstm1.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\"), (\"fastlstm1.four_parts.split-1-over-1.0..256.slice\", i\"fastlstm1.four_parts.split-1-over-1.0..256.slice\"), (\"fastlstm1.four_parts.split-1-over-1.256..512.slice\", i\"fastlstm1.four_parts.split-1-over-1.256..512.slice\"), (\"fastlstm1.four_parts.split-1-over-1.512..768.slice\", i\"fastlstm1.four_parts.split-1-over-1.512..768.slice\"), (\"fastlstm1.four_parts.split-1-over-1.768..1024.slice\", i\"fastlstm1.four_parts.split-1-over-1.768..1024.slice\"), (\"fastlstm1.h_new.W.split-1-over-1.0..128.slice\", i\"fastlstm1.h_new.W.split-1-over-1.0..128.slice\"), (\"fastlstm1.h_new.split-1-over-1.0..128.slice\", i\"fastlstm1.h_new.split-1-over-1.0..128.slice\"), (\"fastlstm1.peephole0.mul.fix-rank\", i\"fastlstm1.peephole0.mul.fix-rank\"), (\"fastlstm1.peephole1.mul.fix-rank\", i\"fastlstm1.peephole1.mul.fix-rank\"), (\"fastlstm1.peephole2.mul.fix-rank\", i\"fastlstm1.peephole2.mul.fix-rank\")], state = [(\"fastlstm1.c\", i\"tap.tap.fastlstm1.c_init.0-35/0-104/0\", \"fastlstm1.c_new\"), (\"fastlstm1.r\", i\"tap.tap.tap.fastlstm1.r_init.0-36/0-110/0-164/0\", \"fastlstm1.r_new\")], output = [(\"fastlstm1.h_new.W.prop_axis.a.input_0\", \"full\", 0, 1)], skip = 2, reset_every_turn = false);\n  i\"fastlstm1.h_new.W.fix_a\" = transpose(i\"fastlstm1.c_final\", axes = [1, 0, 2]);\n  i\"fastlstm1.h_new.W.fix_b\" = variable<scalar>(label = \"fastlstm1.h_new.W.fix_b\", shape = [1, 256, 256]);\n  i\"fastlstm1.h_new.W\" = matmul(i\"fastlstm1.h_new.W.fix_a\", i\"fastlstm1.h_new.W.fix_b\", transposeA = false, transposeB = false);\n  i\"fastlstm1.h_new.W.fix_c.0\" = squeeze(i\"fastlstm1.h_new.W\", axes = [0]);\n  i\"fastlstm1.c_final.fastlstm1.h_new.fix-rank\" = variable<scalar>(label = \"fastlstm1.c_final.fastlstm1.h_new.fix-rank\", shape = [1, 256]);\n  i\"fastlstm1.h_new\" = add(i\"fastlstm1.h_new.W.fix_c.0\", i\"fastlstm1.c_final.fastlstm1.h_new.fix-rank\");\n  i\"tdnn4.affine.output.delay\" = tract_pulse_delay(i\"fastlstm1.h_new\", axis = 0, delay = 0, overlap = 2);\n  i\"tdnn4.affine.output.add_n\" = unsqueeze(i\"tdnn4.affine.output.delay\", axes = [0]);\n  i\"tdnn4.affine.kernel.0\" = variable<scalar>(label = \"tdnn4.affine.kernel.0\", shape = [256, 256, 3]);\n  i\"tdnn4.affine.output.bias\" = variable<scalar>(label = \"tdnn4.affine.output.bias\", shape = [256]);\n  i\"tdnn4.affine.output_input\" = transpose(i\"tdnn4.affine.output.add_n\", axes = [0, 2, 1]);\n  i\"tdnn4.affine.output_conv\" = conv(i\"tdnn4.affine.output_input\", i\"tdnn4.affine.kernel.0\", i\"tdnn4.affine.output.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"tdnn4.affine.output\" = transpose(i\"tdnn4.affine.output_conv\", axes = [0, 2, 1]);\n  i\"tdnn4.affine.output.rm_n\" = squeeze(i\"tdnn4.affine.output\", axes = [0]);\n  i\"tdnn1.relu.output.low.cst.2.1\" = [[0.0]];\n  i\"tdnn4.relu.output.low\" = max(i\"tdnn4.affine.output.rm_n\", i\"tdnn1.relu.output.low.cst.2.1\");\n  i\"tdnn4.renorm.reduced.sum.sqr\" = square(i\"tdnn4.relu.output.low\");\n  i\"tdnn4.renorm.reduced.sum.sum\" = sum_reduce(i\"tdnn4.renorm.reduced.sum.sqr\", axes = [1]);\n  i\"tdnn4.renorm.reduced.sum.norm.fix-rank.1\" = [[256.0]];\n  i\"tdnn4.renorm.reduced.sum.norm\" = div(i\"tdnn4.renorm.reduced.sum.sum\", i\"tdnn4.renorm.reduced.sum.norm.fix-rank.1\");\n  i\"tdnn4.renorm.output-recip\" = rsqrt(i\"tdnn4.renorm.reduced.sum.norm\");\n  i\"tdnn4.renorm.output\" = mul(i\"tdnn4.relu.output.low\", i\"tdnn4.renorm.output-recip\");\n  i\"tdnn5.affine.output.delay\" = tract_pulse_delay(i\"tdnn4.renorm.output\", axis = 0, delay = 0, overlap = 2);\n  i\"tdnn5.affine.output.add_n\" = unsqueeze(i\"tdnn5.affine.output.delay\", axes = [0]);\n  i\"tdnn5.affine.kernel.0\" = variable<scalar>(label = \"tdnn5.affine.kernel.0\", shape = [256, 256, 3]);\n  i\"tdnn5.affine.output.bias\" = variable<scalar>(label = \"tdnn5.affine.output.bias\", shape = [256]);\n  i\"tdnn5.affine.output_input\" = transpose(i\"tdnn5.affine.output.add_n\", axes = [0, 2, 1]);\n  i\"tdnn5.affine.output_conv\" = conv(i\"tdnn5.affine.output_input\", i\"tdnn5.affine.kernel.0\", i\"tdnn5.affine.output.bias\", dilation = [1], stride = [1], border = \"constant\", groups = 1, padding = [(0, 0)]);\n  i\"tdnn5.affine.output\" = transpose(i\"tdnn5.affine.output_conv\", axes = [0, 2, 1]);\n  i\"tdnn5.affine.output.rm_n\" = squeeze(i\"tdnn5.affine.output\", axes = [0]);\n  i\"tdnn5.relu.output.low\" = max(i\"tdnn5.affine.output.rm_n\", i\"tdnn1.relu.output.low.cst.2.1\");\n  i\"tdnn5.renorm.reduced.sum.sqr\" = square(i\"tdnn5.relu.output.low\");\n  i\"tdnn5.renorm.reduced.sum.sum\" = sum_reduce(i\"tdnn5.renorm.reduced.sum.sqr\", axes = [1]);\n  i\"tdnn5.renorm.reduced.sum.norm.fix-rank.1\" = [[256.0]];\n  i\"tdnn5.renorm.reduced.sum.norm\" = div(i\"tdnn5.renorm.reduced.sum.sum\", i\"tdnn5.renorm.reduced.sum.norm.fix-rank.1\");\n  i\"tdnn5.renorm.output-recip\" = rsqrt(i\"tdnn5.renorm.reduced.sum.norm\");\n  i\"tdnn5.renorm.output\" = mul(i\"tdnn5.relu.output.low\", i\"tdnn5.renorm.output-recip\");\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256.prop_axis.a.input_1\" = variable<scalar>(label = \"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256.prop_axis.a.input_1\", shape = [256, 256]);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\" = matmul(i\"tdnn5.renorm.output\", i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256.prop_axis.a.input_1\", transposeA = false, transposeB = false);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_a\" = unsqueeze(i\"tdnn5.renorm.output\", axes = [0]);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_b\" = variable<scalar>(label = \"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_b\", shape = [1, 256, 256]);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\" = matmul(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_a\", i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_b\", transposeA = false, transposeB = false);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_c.0\" = transpose(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\", axes = [1, 0, 2]);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_a\" = unsqueeze(i\"tdnn5.renorm.output\", axes = [0]);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_b\" = variable<scalar>(label = \"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_b\", shape = [1, 256, 256]);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\" = matmul(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_a\", i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_b\", transposeA = false, transposeB = false);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_c.0\" = transpose(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\", axes = [1, 0, 2]);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_a\" = unsqueeze(i\"tdnn5.renorm.output\", axes = [0]);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_b\" = variable<scalar>(label = \"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_b\", shape = [1, 256, 256]);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\" = matmul(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_a\", i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_b\", transposeA = false, transposeB = false);\n  i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_c.0\" = transpose(i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\", axes = [1, 0, 2]);\n  i\"tap.tap.fastlstm1.c_init.0-35/0.1-106/0\" = variable<scalar>(label = \"tap.tap.fastlstm1.c_init.0-35/0.1-106/0\", shape = [1, 256]);\n  i\"tap.tap.tap.fastlstm1.r_init.0-36/0.1-112/0-166/0\" = variable<scalar>(label = \"tap.tap.tap.fastlstm1.r_init.0-36/0.1-112/0-166/0\", shape = [128]);\n  i\"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\" = variable<scalar>(label = \"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\", shape = [128, 256]);\n  i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\" = variable<scalar>(label = \"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\", shape = [128, 256]);\n  i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\" = variable<scalar>(label = \"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\", shape = [128, 256]);\n  i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\" = variable<scalar>(label = \"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\", shape = [128, 256]);\n  i\"fastlstm2.four_parts.split-1-over-1.0..256.slice\" = variable<scalar>(label = \"fastlstm2.four_parts.split-1-over-1.0..256.slice\", shape = [1, 256]);\n  i\"fastlstm2.four_parts.split-1-over-1.256..512.slice\" = variable<scalar>(label = \"fastlstm2.four_parts.split-1-over-1.256..512.slice\", shape = [1, 256]);\n  i\"fastlstm2.four_parts.split-1-over-1.512..768.slice\" = variable<scalar>(label = \"fastlstm2.four_parts.split-1-over-1.512..768.slice\", shape = [1, 256]);\n  i\"fastlstm2.four_parts.split-1-over-1.768..1024.slice\" = variable<scalar>(label = \"fastlstm2.four_parts.split-1-over-1.768..1024.slice\", shape = [1, 256]);\n  i\"fastlstm2.h_new.W.split-1-over-1.0..128.slice\" = variable<scalar>(label = \"fastlstm2.h_new.W.split-1-over-1.0..128.slice\", shape = [256, 128]);\n  i\"fastlstm2.h_new.split-1-over-1.0..128.slice\" = variable<scalar>(label = \"fastlstm2.h_new.split-1-over-1.0..128.slice\", shape = [128]);\n  i\"fastlstm2.peephole0.mul.fix-rank\" = variable<scalar>(label = \"fastlstm2.peephole0.mul.fix-rank\", shape = [1, 256]);\n  i\"fastlstm2.peephole1.mul.fix-rank\" = variable<scalar>(label = \"fastlstm2.peephole1.mul.fix-rank\", shape = [1, 256]);\n  i\"fastlstm2.peephole2.mul.fix-rank\" = variable<scalar>(label = \"fastlstm2.peephole2.mul.fix-rank\", shape = [1, 256]);\n  i\"fastlstm2.c_final\" = tract_core_scan(body = \"scan_body_1\", scan = [(\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\", i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-k.0..256\", 0, 1), (\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256\", i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-k.0..256.fix_c.0\", 0, 1), (\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256\", i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-k.0..256.fix_c.0\", 0, 1), (\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256\", i\"fastlstm2.c_final.extracted.fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-k.0..256.fix_c.0\", 0, 1)], full = [(\"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\", i\"fastlstm2.four_parts.W.split-over-1.0..256.concat-einsum-slice-k.1.256..384\"), (\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\", i\"fastlstm2.four_parts.W.split-over-1.256..512.concat-einsum-slice-k.1.256..384\"), (\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\", i\"fastlstm2.four_parts.W.split-over-1.512..768.concat-einsum-slice-k.1.256..384\"), (\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\", i\"fastlstm2.four_parts.W.split-over-1.768..1024.concat-einsum-slice-k.1.256..384\"), (\"fastlstm2.four_parts.split-1-over-1.0..256.slice\", i\"fastlstm2.four_parts.split-1-over-1.0..256.slice\"), (\"fastlstm2.four_parts.split-1-over-1.256..512.slice\", i\"fastlstm2.four_parts.split-1-over-1.256..512.slice\"), (\"fastlstm2.four_parts.split-1-over-1.512..768.slice\", i\"fastlstm2.four_parts.split-1-over-1.512..768.slice\"), (\"fastlstm2.four_parts.split-1-over-1.768..1024.slice\", i\"fastlstm2.four_parts.split-1-over-1.768..1024.slice\"), (\"fastlstm2.h_new.W.split-1-over-1.0..128.slice\", i\"fastlstm2.h_new.W.split-1-over-1.0..128.slice\"), (\"fastlstm2.h_new.split-1-over-1.0..128.slice\", i\"fastlstm2.h_new.split-1-over-1.0..128.slice\"), (\"fastlstm2.peephole0.mul.fix-rank\", i\"fastlstm2.peephole0.mul.fix-rank\"), (\"fastlstm2.peephole1.mul.fix-rank\", i\"fastlstm2.peephole1.mul.fix-rank\"), (\"fastlstm2.peephole2.mul.fix-rank\", i\"fastlstm2.peephole2.mul.fix-rank\")], state = [(\"fastlstm2.c\", i\"tap.tap.fastlstm1.c_init.0-35/0.1-106/0\", \"fastlstm2.c_new\"), (\"fastlstm2.r\", i\"tap.tap.tap.fastlstm1.r_init.0-36/0.1-112/0-166/0\", \"fastlstm2.r_new\")], output = [(\"fastlstm2.h_new.W.prop_axis.a.input_0\", \"full\", 0, 1)], skip = 6, reset_every_turn = false);\n  i\"fastlstm2.h_new.W.fix_a\" = transpose(i\"fastlstm2.c_final\", axes = [1, 0, 2]);\n  i\"fastlstm2.h_new.W.fix_b\" = variable<scalar>(label = \"fastlstm2.h_new.W.fix_b\", shape = [1, 256, 256]);\n  i\"fastlstm2.h_new.W\" = matmul(i\"fastlstm2.h_new.W.fix_b\", i\"fastlstm2.h_new.W.fix_a\", transposeA = true, transposeB = true);\n  i\"fastlstm2.h_new.W.fix_c.0\" = squeeze(i\"fastlstm2.h_new.W\", axes = [0]);\n  i\"fastlstm2.c_final.fastlstm2.h_new.fix-rank\" = variable<scalar>(label = \"fastlstm2.c_final.fastlstm2.h_new.fix-rank\", shape = [256, 1]);\n  i\"fastlstm2.h_new\" = add(i\"fastlstm2.h_new.W.fix_c.0\", i\"fastlstm2.c_final.fastlstm2.h_new.fix-rank\");\n  i\"output.affine.kernel.0\" = variable<scalar>(label = \"output.affine.kernel.0\", shape = [1690, 256]);\n  i\"output.affine.output.W\" = matmul(i\"fastlstm2.h_new\", i\"output.affine.kernel.0\", transposeA = true, transposeB = true);\n  i\"output.affine.bias.0\" = variable<scalar>(label = \"output.affine.bias.0\", shape = [1, 1690]);\n  i\"output.affine.output\" = add(i\"output.affine.output.W\", i\"output.affine.bias.0\");\n  output = i\"output.affine.output\";\n}\n"
  },
  {
    "path": "harness/pre-optimized-graphes/mdl-en-2019-Q3-librispeech/runme.sh",
    "content": "#!/bin/sh\n\nset -ex\n\ncd `dirname $0`\n\nROOT=$(dirname $(realpath $0))/../../..\n. $ROOT/.travis/ci-system-setup.sh\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\n$CACHE_FILE mdl-en-2019-Q3-librispeech.onnx\n$TRACT_RUN $MODELS/mdl-en-2019-Q3-librispeech.onnx -i S,40 --output-node output --pulse 24 --nnef-tract-pulse --nnef-extended-identifier dump -q --nnef-graph found\n\nversion=`cargo metadata --format-version 1 | jq -r '.packages | map(select( (.name) == \"tract-core\") | .version) | .[] '`\nperl -pi -e \"s/$version/0.19.3-pre/\" found\n\ndiff -u expected found\n"
  },
  {
    "path": "harness/tf-inceptionv3/Cargo.toml",
    "content": "[package]\nname = \"tf-inceptionv3\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nimage.workspace = true\ntract-tensorflow.workspace = true\n\n[features]\nconform = [ \"tract-tensorflow/conform\" ]\n\n[dev-dependencies]\ncriterion.workspace = true\ndinghy-test.workspace = true\nenv_logger.workspace = true\nlog.workspace = true\n\n[[bench]]\nharness = false\nname = \"inceptionv3\"\n"
  },
  {
    "path": "harness/tf-inceptionv3/benches/inceptionv3.rs",
    "content": "#[macro_use]\nextern crate criterion;\nextern crate dinghy_test;\nextern crate tf_inceptionv3;\nextern crate tract_tensorflow;\n\nuse tract_tensorflow::prelude::*;\n\nuse self::dinghy_test::test_project_path;\nuse criterion::Criterion;\n\nuse std::path;\n\nconst HOPPER: &str = \"grace_hopper.jpg\";\npub fn hopper() -> path::PathBuf {\n    test_project_path().join(HOPPER)\n}\n\n#[cfg(feature = \"conform\")]\nfn dummy(_bencher: &mut Criterion) {\n    tract_tensorflow::conform::tf::for_path(tf_inceptionv3::inception_v3_2016_08_28_frozen())\n        .unwrap();\n}\n\n#[cfg(feature = \"conform\")]\nfn tf(bencher: &mut Criterion) {\n    let mut tf =\n        tract_tensorflow::conform::tf::for_path(tf_inceptionv3::inception_v3_2016_08_28_frozen())\n            .unwrap();\n    let input = tf_inceptionv3::load_image(hopper());\n    bencher.bench_function(\"tensorflow\", move |b| {\n        b.iter(|| {\n            tf.run(vec![(\"input\", input.clone())], \"InceptionV3/Predictions/Reshape_1\").unwrap()\n        })\n    });\n}\n\nfn tract(bencher: &mut Criterion) {\n    let mut tfd =\n        tensorflow().model_for_path(tf_inceptionv3::inception_v3_2016_08_28_frozen()).unwrap();\n    tfd.set_input_fact(0, f32::fact([1, 299, 299, 3]).into()).unwrap();\n    let tfd = tfd.into_optimized().unwrap().into_runnable().unwrap();\n    let input = tf_inceptionv3::load_image(hopper());\n    bencher.bench_function(\"tract\", move |b| b.iter(|| tfd.run(tvec![input.clone()]).unwrap()));\n}\n\npub fn benches() {\n    let mut criterion: Criterion = Criterion::default().sample_size(3).configure_from_args();\n    #[cfg(feature = \"conform\")]\n    {\n        dummy(&mut criterion);\n        tf(&mut criterion);\n    }\n    tract(&mut criterion);\n}\ncriterion_main!(benches);\n"
  },
  {
    "path": "harness/tf-inceptionv3/download.sh",
    "content": "#!/bin/sh\n\nMY_DIR=`dirname $0`\n\n$MY_DIR/../../.travis/cache_file.sh inception_v3_2016_08_28_frozen.pb imagenet_slim_labels.txt\n"
  },
  {
    "path": "harness/tf-inceptionv3/src/lib.rs",
    "content": "extern crate image;\nextern crate tract_tensorflow;\n\nuse std::{fs, io, path};\n\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tract_core::internal::*;\n\nfn download() {\n    use std::sync::Once;\n    static START: Once = Once::new();\n\n    START.call_once(|| do_download().unwrap());\n}\n\nfn do_download() -> TractResult<()> {\n    let run = ::std::process::Command::new(\"./download.sh\").status().unwrap();\n    if !run.success() {\n        bail!(\"Failed to download inception model files\")\n    }\n    Ok(())\n}\n\npub fn load_labels() -> Vec<String> {\n    use std::io::BufRead;\n    io::BufReader::new(fs::File::open(imagenet_slim_labels()).unwrap())\n        .lines()\n        .collect::<::std::io::Result<Vec<String>>>()\n        .unwrap()\n}\n\nfn inception_v3_2016_08_28() -> path::PathBuf {\n    ::std::env::var(\"CACHEDIR\").ok().unwrap_or_else(|| \"../../.cached\".to_string()).into()\n}\n\npub fn inception_v3_2016_08_28_frozen() -> path::PathBuf {\n    download();\n    inception_v3_2016_08_28().join(\"inception_v3_2016_08_28_frozen.pb\")\n}\n\npub fn imagenet_slim_labels() -> path::PathBuf {\n    download();\n    inception_v3_2016_08_28().join(\"imagenet_slim_labels.txt\")\n}\n\npub fn load_image<P: AsRef<path::Path>>(p: P) -> TValue {\n    let image = image::open(&p).unwrap().to_rgb8();\n    let resized =\n        image::imageops::resize(&image, 299, 299, ::image::imageops::FilterType::Triangle);\n    tract_ndarray::Array4::from_shape_fn((1, 299, 299, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    })\n    .into_dyn()\n    .into_tvalue()\n}\n\n#[cfg(test)]\nmod tests {\n    extern crate dinghy_test;\n    use tract_tensorflow::prelude::*;\n\n    use self::dinghy_test::test_project_path;\n    use super::*;\n    use std::path;\n\n    const HOPPER: &str = \"grace_hopper.jpg\";\n    pub fn hopper() -> path::PathBuf {\n        test_project_path().join(HOPPER)\n    }\n\n    #[allow(dead_code)]\n    pub fn setup_test_logger() {\n        env_logger::Builder::from_default_env().filter_level(log::LevelFilter::Trace).init();\n    }\n\n    #[test]\n    fn grace_hopper_is_a_military_uniform() {\n        download();\n        // setup_test_logger();\n        println!(\"{:?}\", inception_v3_2016_08_28_frozen());\n        let tfd = tensorflow().model_for_path(inception_v3_2016_08_28_frozen()).unwrap();\n        let plan = SimplePlan::new(tfd).unwrap();\n        let input = load_image(hopper());\n        let outputs = plan.run(tvec![input]).unwrap();\n        let labels = load_labels();\n        let label_id = outputs[0]\n            .try_as_plain()\n            .unwrap()\n            .to_array_view::<f32>()\n            .unwrap()\n            .iter()\n            .enumerate()\n            .max_by(|a, b| a.1.total_cmp(b.1))\n            .unwrap()\n            .0;\n        let label = &labels[label_id];\n        assert_eq!(label, \"military uniform\");\n    }\n}\n"
  },
  {
    "path": "harness/tf-mobilenet-v2/Cargo.toml",
    "content": "[package]\nname = \"tf-mobilenet-v2\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nimage.workspace = true\ntract-tensorflow.workspace = true\n\n[dev-dependencies]\ndinghy-test.workspace = true\n"
  },
  {
    "path": "harness/tf-mobilenet-v2/download.sh",
    "content": "#!/bin/sh\n\nMY_DIR=`dirname $0`\n\n$MY_DIR/../../.travis/cache_file.sh \\\n    mobilenet_v2_1.4_224_frozen.pb \\\n    imagenet_slim_labels.txt \\\n    grace_hopper.jpg\n"
  },
  {
    "path": "harness/tf-mobilenet-v2/src/lib.rs",
    "content": "extern crate image;\nextern crate tract_tensorflow;\n\nuse std::{fs, path};\n\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tract_core::internal::*;\n\nfn download() {\n    use std::sync::Once;\n    static START: Once = std::sync::Once::new();\n\n    START.call_once(|| do_download().unwrap());\n}\n\nfn do_download() -> TractResult<()> {\n    let run = ::std::process::Command::new(\"./download.sh\").status().unwrap();\n    if !run.success() {\n        bail!(\"Failed to download model files\")\n    }\n    Ok(())\n}\n\nfn cachedir() -> path::PathBuf {\n    ::std::env::var(\"CACHEDIR\").ok().unwrap_or_else(|| \"../../.cached\".to_string()).into()\n}\n\npub fn load_labels() -> Vec<String> {\n    fs::read_to_string(imagenet_slim_labels()).unwrap().lines().map(|s| s.into()).collect()\n}\npub fn imagenet_slim_labels() -> path::PathBuf {\n    download();\n    cachedir().join(\"imagenet_slim_labels.txt\")\n}\n\npub fn grace_hopper() -> path::PathBuf {\n    download();\n    cachedir().join(\"grace_hopper.jpg\")\n}\n\npub fn load_image<P: AsRef<path::Path>>(p: P) -> Tensor {\n    let image = image::open(&p).unwrap().to_rgb8();\n    let resized = image::imageops::resize(&image, 224, 224, image::imageops::FilterType::Triangle);\n    tract_ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n        resized[(x as _, y as _)][c] as f32 / 255.0\n    })\n    .into_dyn()\n    .into()\n}\n\n#[cfg(test)]\nmod tests {\n    extern crate dinghy_test;\n    use tract_tensorflow::prelude::*;\n\n    use super::*;\n\n    fn mobilenet_v2() -> path::PathBuf {\n        download();\n        cachedir().join(\"mobilenet_v2_1.4_224_frozen.pb\")\n    }\n\n    fn run<F, O>(runnable: Arc<SimplePlan<F, O>>) -> TractResult<()>\n    where\n        F: Fact + Hash + Clone + 'static,\n        O: std::fmt::Debug + std::fmt::Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n    {\n        let input = load_image(grace_hopper());\n        let outputs = runnable.run(tvec![input.into()])?;\n        let label_id = outputs[0]\n            .try_as_plain()?\n            .as_slice::<f32>()?\n            .iter()\n            .enumerate()\n            .max_by(|a, b| a.1.total_cmp(b.1))\n            .unwrap()\n            .0;\n        let labels = load_labels();\n        let label = &labels[label_id];\n        assert_eq!(label, \"military uniform\");\n        Ok(())\n    }\n\n    #[test]\n    fn plain() -> TractResult<()> {\n        let tfd = tract_tensorflow::tensorflow().model_for_path(mobilenet_v2())?.into_runnable()?;\n        run(tfd)\n    }\n\n    #[test]\n    fn declutter() -> TractResult<()> {\n        let tfd = tract_tensorflow::tensorflow()\n            .model_for_path(mobilenet_v2())?\n            .with_input_fact(0, f32::fact([1, 224, 224, 3]).into())?\n            .into_typed()?\n            .into_decluttered()?\n            .into_runnable()?;\n        run(tfd)\n    }\n\n    #[test]\n    fn optimized() -> TractResult<()> {\n        let tfd = tract_tensorflow::tensorflow()\n            .model_for_path(mobilenet_v2())?\n            .with_input_fact(0, f32::fact([1, 224, 224, 3]).into())?\n            .into_optimized()?\n            .into_runnable()?;\n        run(tfd)\n    }\n}\n"
  },
  {
    "path": "harness/tfl-mobilenet-v2-q/Cargo.toml",
    "content": "[package]\nname = \"tfl-mobilenet-v2-q\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nimage.workspace = true\ntract-tflite.workspace = true\n\n[dev-dependencies]\ndinghy-test.workspace = true\n"
  },
  {
    "path": "harness/tfl-mobilenet-v2-q/download.sh",
    "content": "#!/bin/sh\n\nMY_DIR=`dirname $0`\n\n$MY_DIR/../../.travis/cache_file.sh \\\n    mobilenetv2_ptq_single_img.tflite \\\n    imagenet_slim_labels.txt \\\n    grace_hopper.jpg\n"
  },
  {
    "path": "harness/tfl-mobilenet-v2-q/src/lib.rs",
    "content": "use std::{fs, path};\n\nuse tract_tflite::internal::*;\n\nfn download() {\n    use std::sync::Once;\n    static START: Once = std::sync::Once::new();\n\n    START.call_once(|| do_download().unwrap());\n}\n\nfn do_download() -> TractResult<()> {\n    let run = std::process::Command::new(\"./download.sh\").status().unwrap();\n    if !run.success() {\n        bail!(\"Failed to download model files\")\n    }\n    Ok(())\n}\n\nfn cachedir() -> path::PathBuf {\n    std::env::var(\"CACHEDIR\").ok().unwrap_or_else(|| \"../../.cached\".to_string()).into()\n}\n\npub fn load_labels() -> Vec<String> {\n    fs::read_to_string(imagenet_slim_labels()).unwrap().lines().map(|s| s.into()).collect()\n}\npub fn imagenet_slim_labels() -> path::PathBuf {\n    download();\n    cachedir().join(\"imagenet_slim_labels.txt\")\n}\n\npub fn grace_hopper() -> path::PathBuf {\n    download();\n    cachedir().join(\"grace_hopper.jpg\")\n}\n\npub fn input_dt() -> DatumType {\n    i8::datum_type().with_zp_scale(-1, 0.007843138)\n}\n\npub fn load_image<P: AsRef<path::Path>>(p: P) -> Tensor {\n    let image = image::open(&p).unwrap().to_rgb8();\n    let resized = image::imageops::resize(&image, 224, 224, image::imageops::FilterType::Triangle);\n    let mut tensor: Tensor =\n        tract_ndarray::Array4::from_shape_fn((1, 224, 224, 3), |(_, y, x, c)| {\n            (resized[(x as _, y as _)][c] as i32 - 128) as i8\n        })\n        .into_dyn()\n        .into();\n    unsafe { tensor.set_datum_type(input_dt()) };\n    tensor\n}\n\n#[cfg(test)]\nmod tests {\n    extern crate dinghy_test;\n    use tract_tflite::prelude::*;\n\n    use super::*;\n\n    fn mobilenet_v2() -> path::PathBuf {\n        download();\n        cachedir().join(\"mobilenetv2_ptq_single_img.tflite\")\n    }\n\n    fn run<F, O>(runnable: Arc<SimplePlan<F, O>>) -> TractResult<()>\n    where\n        F: Fact + Hash + Clone + 'static,\n        O: std::fmt::Debug + std::fmt::Display + AsRef<dyn Op> + AsMut<dyn Op> + Clone + 'static,\n    {\n        let input = load_image(grace_hopper());\n        let outputs = runnable.run(tvec![input.into()])?;\n        let label_id =\n            outputs[0].try_as_plain()?.as_slice::<i8>()?.iter().enumerate().max().unwrap().0;\n        let labels = load_labels();\n        let label = &labels[label_id];\n        assert_eq!(label, \"military uniform\");\n        Ok(())\n    }\n\n    #[test]\n    #[ignore]\n    fn plain() -> TractResult<()> {\n        let tfd = tract_tflite::tflite().model_for_path(mobilenet_v2())?.into_runnable()?;\n        run(tfd)\n    }\n\n    #[test]\n    #[ignore]\n    fn declutter() -> TractResult<()> {\n        let tfd = tract_tflite::tflite()\n            .model_for_path(mobilenet_v2())?\n            .with_input_fact(0, input_dt().fact([1, 224, 224, 3]))?\n            .into_decluttered()?\n            .into_runnable()?;\n        run(tfd)\n    }\n\n    #[test]\n    #[ignore]\n    fn optimized() -> TractResult<()> {\n        let tfd = tract_tflite::tflite()\n            .model_for_path(mobilenet_v2())?\n            .with_input_fact(0, input_dt().fact([1, 224, 224, 3]))?\n            .into_optimized()?\n            .into_runnable()?;\n        run(tfd)\n    }\n}\n"
  },
  {
    "path": "hir/Cargo.toml",
    "content": "[package]\nname = \"tract-hir\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nderive-new.workspace = true\nlog.workspace = true\n\ntract-core.workspace = true\n\n[dev-dependencies]\nenv_logger.workspace = true\n"
  },
  {
    "path": "hir/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "hir/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "hir/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "hir/src/framework.rs",
    "content": "\n"
  },
  {
    "path": "hir/src/infer/analyser.rs",
    "content": "use super::InferenceModel;\nuse super::*;\nuse crate::prelude::*;\nuse std::borrow::BorrowMut;\nuse std::collections::{BTreeSet, HashMap};\n\n/// A graph analyser, along with its current state.\n#[derive(new)]\npub struct Analyser<M: BorrowMut<InferenceModel>> {\n    model: M,\n}\n\nimpl<M: BorrowMut<InferenceModel>> Analyser<M> {\n    /// Runs the entire analysis at once. Will not stop on error if obstinate is\n    /// true.\n    pub fn analyse_obstinate(&mut self, obstinate: bool) -> TractResult<bool> {\n        let mut nodes_to_visit: BTreeSet<usize> =\n            self.model.borrow().eval_order()?.iter().cloned().collect();\n        let mut observed_outlets: HashMap<usize, Vec<OutletId>> = HashMap::new();\n        let mut observers: HashMap<OutletId, TVec<usize>> = HashMap::new();\n        for node in self.model.borrow().nodes() {\n            if !nodes_to_visit.contains(&node.id) {\n                nodes_to_visit.insert(node.id);\n            }\n            let observed = node.op.observe_outlets(self.model.borrow(), node)?;\n            for outlet in &observed {\n                observers.entry(*outlet).or_insert(tvec!()).push(node.id);\n            }\n            observed_outlets.insert(node.id, observed);\n        }\n        let mut first_error = None;\n        let mut did_something = false;\n        while let Some(&node) = nodes_to_visit.iter().next() {\n            trace!(\"Remaining nodes {}, visiting {}\", nodes_to_visit.len(), node);\n            match self.analyse_one(node) {\n                Ok(changed_edges) => {\n                    for (edge, _fact) in changed_edges {\n                        did_something = true;\n                        trace!(\"Changed edge: {edge:?}\");\n                        for dst in self.model.borrow().nodes()[edge.node].outputs[edge.slot]\n                            .successors\n                            .iter()\n                        {\n                            if dst.node != edge.node {\n                                trace!(\"Inserting node dn {:?}\", dst.node);\n                                nodes_to_visit.insert(dst.node);\n                            }\n                        }\n                        if edge.node != node {\n                            trace!(\"Inserting node up {}\", edge.node);\n                            nodes_to_visit.insert(edge.node);\n                        }\n                        if let Some(observers) = observers.get(&edge) {\n                            for observer in observers {\n                                nodes_to_visit.insert(*observer);\n                            }\n                        }\n                    }\n                }\n                Err(e) => {\n                    let e = e.context(format!(\n                        \"Failed analyse for node {}\",\n                        self.model.borrow().node(node)\n                    ));\n                    if !obstinate {\n                        return Err(e);\n                    }\n                    debug!(\"{e:?}\");\n                    if first_error.is_none() {\n                        first_error = Some(e);\n                    }\n                }\n            }\n            nodes_to_visit.remove(&node);\n        }\n        trace!(\"analyse done\");\n        if let Some(e) = first_error {\n            Err(e)?\n        }\n        Ok(did_something)\n    }\n\n    /// Tries to run a single step of the analysis, and returns whether\n    /// there was any additional information gained during the step.\n    pub fn analyse_one(&mut self, node: usize) -> TractResult<Vec<(OutletId, InferenceFact)>> {\n        let mut changed_edges = vec![];\n        {\n            trace!(\"Starting step for {}\", self.model.borrow().node(node));\n            let observed_outlets: Vec<OutletId> = {\n                let model = self.model.borrow();\n                let node = model.node(node);\n                node.op.observe_outlets(model, node)?\n            };\n\n            let inferred = {\n                let (inputs, outputs) = self.model.borrow().node_facts(node)?;\n                if outputs.len() != self.model.borrow().node(node).op.nboutputs().unwrap() {\n                    bail!(\n                        \"Wrong number of outputs. Op says {}, node says {}.\",\n                        self.model.borrow().node(node).op.nboutputs().unwrap(),\n                        outputs.len(),\n                    )\n                }\n                let inputs: TVec<InferenceFact> = inputs.into_iter().cloned().collect();\n                let outputs: TVec<InferenceFact> = outputs.into_iter().cloned().collect();\n                let observed: TVec<(OutletId, InferenceFact)> = {\n                    let model = self.model.borrow();\n                    let node = model.node(node);\n                    node.op\n                        .observe_outlets(model, node)?\n                        .iter()\n                        .map(|o| model.outlet_fact(*o).map(|f| (*o, f.clone())))\n                        .collect::<TractResult<_>>()?\n                };\n                if log_enabled!(log::Level::Trace) {\n                    for (ix, i) in inputs.iter().enumerate() {\n                        trace!(\"  Input  #{ix}: {i:?}\");\n                    }\n                    for (ix, o) in outputs.iter().enumerate() {\n                        trace!(\"  Output #{ix}: {o:?}\");\n                    }\n                }\n\n                let inputs: TVec<&InferenceFact> = inputs.iter().collect();\n                let outputs: TVec<&InferenceFact> = outputs.iter().collect();\n                let observed: TVec<&InferenceFact> = observed.iter().map(|p| &p.1).collect();\n\n                self.model.borrow_mut().node_mut(node).op.infer(inputs, outputs, observed)?\n            };\n\n            let node = self.model.borrow().node(node);\n            for (ix, &outlet) in node.inputs.iter().enumerate() {\n                let inferred_fact = &inferred.0[ix];\n                let old_fact = self.model.borrow().outlet_fact(outlet)?;\n                let unified = inferred_fact\n                    .unify(old_fact)\n                    .with_context(|| format!(\"while unifying inputs of {node}\"))?;\n\n                if &unified != old_fact {\n                    debug!(\"  Refined {outlet:?}: {old_fact:?} -> {unified:?}\");\n                    changed_edges.push((outlet, unified));\n                }\n            }\n\n            for (ix, inferred_fact) in inferred.1.iter().enumerate() {\n                let old_fact = self.model.borrow().outlet_fact(OutletId::new(node.id, ix))?;\n                let unified = old_fact.unify(inferred_fact)?;\n\n                if &unified != old_fact {\n                    let outlet = OutletId::new(node.id, ix);\n                    debug!(\"  Refined {outlet:?}: {old_fact:?} -> {unified:?}\");\n                    changed_edges.push((outlet, unified));\n                }\n            }\n\n            for (ix, &outlet) in observed_outlets.iter().enumerate() {\n                let old_fact = self.model.borrow().outlet_fact(outlet)?;\n                let new_fact = &inferred.2[ix];\n                let unified = old_fact.unify(new_fact)?;\n                if &unified != old_fact {\n                    changed_edges.push((outlet, unified));\n                }\n            }\n        }\n        for (outlet, fact) in &changed_edges {\n            self.model.borrow_mut().set_outlet_fact(*outlet, fact.clone())?;\n        }\n        Ok(changed_edges)\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/fact.rs",
    "content": "use std::convert::TryFrom;\nuse std::fmt;\nuse std::sync::Arc;\n\nuse super::factoid::*;\nuse crate::internal::*;\n\n/// Partial information about a tensor.\n///\n/// The task of the analyser is to tag every edge in the graph with information\n/// about the tensors that flow through it - specifically their datum_type, their\n/// shape and possibly their value. During the analysis, however, we might only\n/// know some of that information (say, for instance, that an edge only carries\n/// tensors of rank 4, but without knowing their precise dimension).\n///\n/// This is where tensor facts come in: they hold partial information about the\n/// datum_type, shape and value of tensors that might flow through an edge of the\n/// graph. The analyser will first tag each edge with a fact, starting with the\n/// most general one and specializing it at each iteration. Eventually, it will\n/// reach a fixed point that - hopefully - holds enough information.\n#[derive(Clone, PartialEq, Eq, Default, Hash)]\npub struct InferenceFact {\n    pub datum_type: TypeFactoid,\n    pub shape: ShapeFactoid,\n    pub value: ValueFact,\n}\n\nimpl InferenceFact {\n    /// Constructs the most general tensor fact possible.\n    pub fn new() -> InferenceFact {\n        InferenceFact::default()\n    }\n\n    pub fn any() -> InferenceFact {\n        InferenceFact::default()\n    }\n\n    pub fn dt(dt: DatumType) -> InferenceFact {\n        InferenceFact::default().with_datum_type(dt)\n    }\n\n    pub fn dt_shape<S: Into<ShapeFactoid>>(dt: DatumType, shape: S) -> InferenceFact {\n        InferenceFact::dt(dt).with_shape(shape)\n    }\n\n    pub fn shape<S: Into<ShapeFactoid>>(shape: S) -> InferenceFact {\n        InferenceFact::default().with_shape(shape)\n    }\n\n    pub fn with_datum_type(self, dt: DatumType) -> InferenceFact {\n        InferenceFact { datum_type: dt.into(), ..self }\n    }\n\n    pub fn without_datum_type(self) -> InferenceFact {\n        InferenceFact { datum_type: TypeFactoid::Any, ..self }\n    }\n\n    pub fn with_shape<S: Into<ShapeFactoid>>(self, shape: S) -> InferenceFact {\n        InferenceFact { shape: shape.into(), ..self }\n    }\n\n    pub fn format_dt_shape(&self) -> String {\n        if !self.shape.open && self.shape.dims.len() == 0 {\n            self.datum_type\n                .concretize()\n                .map(|dt| format!(\"{dt:?}\"))\n                .unwrap_or_else(|| \"?\".to_string())\n        } else {\n            format!(\n                \"{:?},{}\",\n                self.shape,\n                self.datum_type\n                    .concretize()\n                    .map(|dt| format!(\"{dt:?}\"))\n                    .unwrap_or_else(|| \"?\".to_string())\n            )\n        }\n    }\n\n    pub fn dt_shape_from_tensor(t: &Tensor) -> InferenceFact {\n        InferenceFact::dt_shape(t.datum_type(), t.shape())\n    }\n\n    pub fn without_value(self) -> InferenceFact {\n        InferenceFact { value: GenericFactoid::Any, ..self }\n    }\n}\n\nimpl Factoid for InferenceFact {\n    type Concrete = Arc<Tensor>;\n\n    /// Tries to transform the fact into a concrete value.\n    fn concretize(&self) -> Option<Self::Concrete> {\n        self.value.concretize()\n    }\n\n    /// Tries to unify the fact with another fact of the same type.\n    fn unify(&self, other: &Self) -> TractResult<Self> {\n        let tensor = InferenceFact {\n            datum_type: self.datum_type.unify(&other.datum_type)?,\n            shape: self.shape.unify(&other.shape)?,\n            value: self.value.unify(&other.value)?,\n        };\n\n        trace!(\"Unifying {self:?} with {other:?} into {tensor:?}.\");\n\n        Ok(tensor)\n    }\n}\n\nimpl fmt::Debug for InferenceFact {\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        if let Some(t) = self.value.concretize() {\n            write!(formatter, \"{t:?}\")\n        } else {\n            write!(formatter, \"{}\", self.format_dt_shape())\n        }\n    }\n}\n\nuse crate::infer::factoid::Factoid;\n\nimpl Fact for InferenceFact {\n    fn to_typed_fact(&self) -> TractResult<Cow<'_, TypedFact>> {\n        Ok(Cow::Owned(TypedFact::try_from(self)?))\n    }\n\n    fn matches(&self, t: &Tensor, _symbols: Option<&SymbolValues>) -> TractResult<bool> {\n        if let Some(dt) = self.datum_type() {\n            if t.datum_type() != dt {\n                return Ok(false);\n            }\n        }\n        if let Some(shape) = self.shape.concretize() {\n            if *ShapeFact::from(t.shape()) != *shape {\n                return Ok(false);\n            }\n        }\n        if let Some(value) = self.value.concretize() {\n            if &*value != t {\n                return Ok(false);\n            }\n        }\n        Ok(true)\n    }\n\n    fn compatible_with(&self, other: &dyn Fact) -> bool {\n        if let Some(other) = other.downcast_ref::<Self>() {\n            self.unify(other).is_ok()\n        } else {\n            false\n        }\n    }\n\n    fn datum_type(&self) -> Option<DatumType> {\n        self.datum_type.concretize()\n    }\n}\n\nimpl TryFrom<&InferenceFact> for TypedFact {\n    type Error = TractError;\n    fn try_from(fact: &InferenceFact) -> TractResult<TypedFact> {\n        if let (Some(datum_type), Some(shape)) =\n            (fact.datum_type.concretize(), fact.shape.concretize())\n        {\n            let shape = ShapeFact::from_dims(shape);\n            let konst = fact.value.concretize();\n            let uniform = konst.as_ref().and_then(|k| k.as_uniform()).map(Arc::new);\n            Ok(TypedFact {\n                datum_type,\n                shape,\n                konst,\n                uniform,\n                exotic_fact: None,\n                uniform_tdim: None,\n                region_of_interest: None,\n            })\n        } else {\n            bail!(\"Can not make a TypedFact out of {:?}\", fact)\n        }\n    }\n}\n\nimpl<'a> From<&'a InferenceFact> for InferenceFact {\n    fn from(t: &'a InferenceFact) -> InferenceFact {\n        t.clone()\n    }\n}\n\nimpl<'a> From<&'a TypedFact> for InferenceFact {\n    fn from(t: &'a TypedFact) -> InferenceFact {\n        let mut fact = InferenceFact::dt_shape(t.datum_type, t.shape.iter());\n        if let Some(k) = &t.konst {\n            fact.value = Arc::clone(k).into();\n        }\n        fact\n    }\n}\n\nimpl From<TypedFact> for InferenceFact {\n    fn from(t: TypedFact) -> InferenceFact {\n        InferenceFact::from(&t)\n    }\n}\n\nimpl<'a> TryFrom<&'a Arc<Tensor>> for InferenceFact {\n    type Error = TractError;\n    fn try_from(t: &'a Arc<Tensor>) -> TractResult<InferenceFact> {\n        Ok(InferenceFact::from(&TypedFact::try_from(Arc::clone(t))?))\n    }\n}\n\nimpl TryFrom<Arc<Tensor>> for InferenceFact {\n    type Error = TractError;\n    fn try_from(t: Arc<Tensor>) -> TractResult<InferenceFact> {\n        Ok(InferenceFact::from(&TypedFact::try_from(t)?))\n    }\n}\n\nimpl From<Tensor> for InferenceFact {\n    fn from(t: Tensor) -> InferenceFact {\n        let mut fact = InferenceFact::dt_shape(t.datum_type(), t.shape());\n        fact.value = t.into_arc_tensor().into();\n        fact\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/factoid.rs",
    "content": "use std::fmt;\nuse std::iter::FromIterator;\nuse std::ops::{Add, Div, Mul, Neg, Rem, Sub};\n\nuse tract_num_traits::Zero;\n\nuse crate::internal::*;\n\n/// Partial information about any value.\npub trait Factoid: fmt::Debug + Clone + PartialEq + Default + Hash {\n    type Concrete: fmt::Debug;\n\n    /// Tries to transform the fact into a concrete value.\n    fn concretize(&self) -> Option<Self::Concrete>;\n\n    /// Returns whether the value is fully determined.\n    fn is_concrete(&self) -> bool {\n        self.concretize().is_some()\n    }\n\n    /// Tries to unify the fact with another fact of the same type.\n    fn unify(&self, other: &Self) -> TractResult<Self>;\n\n    /// Tries to unify the fact with another fact of the same type and update\n    /// self.\n    ///\n    /// Returns true if it actually changed something.\n    fn unify_with(&mut self, other: &Self) -> TractResult<bool> {\n        let new = self.unify(other)?;\n        let mut changed = false;\n        if &new != self {\n            changed = true;\n            *self = new;\n        }\n        Ok(changed)\n    }\n\n    /// Tries to unify the fact with another fact of the same type and update\n    /// both of them.\n    ///\n    /// Returns true if it actually changed something.\n    fn unify_with_mut(&mut self, other: &mut Self) -> TractResult<bool> {\n        let new = self.unify(other)?;\n        let mut changed = false;\n        if &new != self {\n            changed = true;\n            *self = new.clone();\n        }\n        if &new != other {\n            changed = true;\n            *other = new;\n        }\n        Ok(changed)\n    }\n\n    /// Tries to unify all facts in the list.\n    ///\n    ///\n    /// Returns true if it actually changed something.\n    fn unify_all(facts: &mut [&mut Self]) -> TractResult<bool> {\n        let mut overall_changed = false;\n        loop {\n            let mut changed = false;\n            for i in 0..facts.len() - 1 {\n                for j in i + 1..facts.len() {\n                    let (left, right) = facts.split_at_mut(j);\n                    let c = left[i].unify_with(right[0])?;\n                    changed = changed || c;\n                    overall_changed = changed || c;\n                }\n            }\n            if !changed {\n                return Ok(overall_changed);\n            }\n        }\n    }\n}\n\n/// Partial information about a value of type T.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub enum GenericFactoid<T: fmt::Debug + Clone + PartialEq + Hash> {\n    Only(T),\n    Any,\n}\n\n// if T is not Default, autoderive wont work\n#[allow(clippy::derivable_impls)]\nimpl<T: fmt::Debug + Clone + PartialEq + Hash> Default for GenericFactoid<T> {\n    fn default() -> Self {\n        GenericFactoid::Any\n    }\n}\n\nimpl<T: Copy + Clone + fmt::Debug + PartialEq + Hash> Copy for GenericFactoid<T> {}\n\nimpl<T: fmt::Debug + Clone + PartialEq + Hash> Factoid for GenericFactoid<T> {\n    type Concrete = T;\n\n    /// Tries to transform the fact into a concrete value.\n    fn concretize(&self) -> Option<T> {\n        match self {\n            GenericFactoid::Any => None,\n            GenericFactoid::Only(m) => Some(m.clone()),\n        }\n    }\n\n    /// Tries to unify the fact with another fact of the same type.\n    fn unify(&self, other: &Self) -> TractResult<Self> {\n        let fact = match (self, other) {\n            (_, GenericFactoid::Any) => self.clone(),\n            (GenericFactoid::Any, _) => other.clone(),\n            _ if self == other => self.clone(),\n            _ => bail!(\"Impossible to unify {:?} with {:?}.\", self, other),\n        };\n\n        Ok(fact)\n    }\n}\n\nimpl<T: fmt::Debug + Clone + PartialEq + Hash> From<T> for GenericFactoid<T> {\n    fn from(t: T) -> Self {\n        GenericFactoid::Only(t)\n    }\n}\n\nimpl<T: fmt::Display + fmt::Debug + Clone + PartialEq + Hash> fmt::Display for GenericFactoid<T> {\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            GenericFactoid::Any => write!(formatter, \"?\"),\n            GenericFactoid::Only(u) => write!(formatter, \"{u}\"),\n        }\n    }\n}\n\nimpl<T: fmt::Debug + Clone + PartialEq + Hash> fmt::Debug for GenericFactoid<T> {\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            GenericFactoid::Any => write!(formatter, \"?\"),\n            GenericFactoid::Only(u) => write!(formatter, \"{u:?}\"),\n        }\n    }\n}\n\n/// Partial information about a type.\npub type TypeFactoid = GenericFactoid<DatumType>;\n\n/// Partial information about a shape.\n///\n/// A basic example of a shape fact is `shapefactoid![1, 2]`, which corresponds to\n/// the shape `[1, 2]` in Arc<Tensor>. We can use `_` in facts to denote unknown\n/// dimensions (e.g. `shapefactoid![1, 2, _]` corresponds to any shape `[1, 2, k]`\n/// with `k` a non-negative integer). We can also use `..` at the end of a fact\n/// to only specify its first dimensions, so `shapefactoid![1, 2; ..]` matches any\n/// shape that starts with `[1, 2]` (e.g. `[1, 2, i]` or `[1, 2, i, j]`), while\n/// `shapefactoid![..]` matches any shape.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct ShapeFactoid {\n    pub(super) open: bool,\n    pub(super) dims: TVec<GenericFactoid<TDim>>,\n}\n\nimpl ShapeFactoid {\n    /// Constructs an open shape fact.\n    pub fn open(dims: TVec<DimFact>) -> ShapeFactoid {\n        ShapeFactoid { open: true, dims }\n    }\n\n    pub fn is_open(&self) -> bool {\n        self.open\n    }\n\n    /// Constructs a closed shape fact.\n    pub fn closed(dims: TVec<DimFact>) -> ShapeFactoid {\n        ShapeFactoid { open: false, dims }\n    }\n\n    pub fn rank(&self) -> IntFactoid {\n        if self.open { GenericFactoid::Any } else { GenericFactoid::Only(self.dims.len() as i64) }\n    }\n\n    pub fn ensure_rank_at_least(&mut self, n: usize) -> bool {\n        let mut changed = false;\n        while self.dims.len() <= n {\n            self.dims.push(GenericFactoid::Any);\n            changed = true;\n        }\n        changed\n    }\n\n    pub fn dim(&self, i: usize) -> Option<DimFact> {\n        self.dims().nth(i).cloned()\n    }\n\n    pub fn set_dim(&mut self, i: usize, d: TDim) -> bool {\n        let fact = GenericFactoid::Only(d.clone());\n        if self.dim(i).as_ref() == Some(&fact) {\n            return false;\n        }\n        self.dims[i] = GenericFactoid::Only(d);\n        true\n    }\n\n    pub fn dims(&self) -> impl Iterator<Item = &DimFact> {\n        self.dims.iter()\n    }\n\n    pub fn as_concrete_finite(&self) -> TractResult<Option<TVec<usize>>> {\n        if self.open {\n            return Ok(None);\n        }\n        Ok(self.dims.iter().map(|d| d.concretize().and_then(|d| d.to_usize().ok())).collect())\n    }\n\n    pub fn matches(&self, t: &Tensor, symbols: Option<&SymbolValues>) -> TractResult<bool> {\n        let rank_compatible =\n            if self.is_open() { self.dims.len() <= t.rank() } else { self.dims.len() == t.rank() };\n        if !rank_compatible {\n            return Ok(false);\n        }\n\n        for i in 0..t.rank() {\n            let dim = self.dims.get(i).and_then(|el| el.concretize());\n            if let Some(dim) = dim.and_then(|dim| {\n                dim.eval(symbols.unwrap_or(&SymbolValues::default())).to_usize().ok()\n            }) {\n                if dim != t.shape()[i] {\n                    return Ok(false);\n                }\n            }\n        }\n        Ok(true)\n    }\n}\n\nimpl Factoid for ShapeFactoid {\n    type Concrete = TVec<TDim>;\n\n    /// Tries to transform the fact into a `Vec<usize>`, or returns `None`.\n    fn concretize(self: &ShapeFactoid) -> Option<TVec<TDim>> {\n        if self.open {\n            return None;\n        }\n\n        let dims: TVec<_> = self.dims().filter_map(|d| d.concretize()).collect();\n\n        if dims.len() < self.dims.len() { None } else { Some(dims) }\n    }\n\n    /// Tries to unify the fact with another fact of the same type.\n    fn unify(&self, other: &Self) -> TractResult<Self> {\n        let (x, y) = (self, other);\n\n        use tract_itertools::EitherOrBoth::{Both, Left, Right};\n        use tract_itertools::Itertools;\n\n        let xi = x.dims();\n        let yi = y.dims();\n\n        let dimensions: TVec<_> = xi\n            .zip_longest(yi)\n            .map(|r| match r {\n                Both(a, b) => a.unify(b),\n                Left(d) if y.open => Ok(d.clone()),\n                Right(d) if x.open => Ok(d.clone()),\n\n                Left(_) | Right(_) => bail!(\n                    \"Impossible to unify closed shapes of different rank (found {:?} and {:?}).\",\n                    x,\n                    y\n                ),\n            })\n            .collect::<TractResult<_>>()\n            .with_context(|| format!(\"Unifying shapes {x:?} and {y:?}\"))?;\n\n        if x.open && y.open {\n            Ok(ShapeFactoid::open(dimensions))\n        } else {\n            Ok(ShapeFactoid::closed(dimensions))\n        }\n    }\n}\n\nimpl Default for ShapeFactoid {\n    /// Returns the most general shape fact possible.\n    fn default() -> ShapeFactoid {\n        ShapeFactoid::open(tvec![])\n    }\n}\n\nimpl FromIterator<TDim> for ShapeFactoid {\n    /// Converts an iterator over usize into a closed shape.\n    fn from_iter<I: IntoIterator<Item = TDim>>(iter: I) -> ShapeFactoid {\n        ShapeFactoid::closed(iter.into_iter().map(GenericFactoid::Only).collect())\n    }\n}\n\nimpl FromIterator<usize> for ShapeFactoid {\n    /// Converts an iterator over usize into a closed shape.\n    fn from_iter<I: IntoIterator<Item = usize>>(iter: I) -> ShapeFactoid {\n        ShapeFactoid::closed(iter.into_iter().map(|d| GenericFactoid::Only(d.to_dim())).collect())\n    }\n}\n\nimpl<D: ToDim, I: IntoIterator<Item = D>> From<I> for ShapeFactoid {\n    fn from(it: I) -> ShapeFactoid {\n        ShapeFactoid::closed(it.into_iter().map(|d| GenericFactoid::Only(d.to_dim())).collect())\n    }\n}\n\nimpl fmt::Debug for ShapeFactoid {\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        for (ix, d) in self.dims.iter().enumerate() {\n            if ix != 0 {\n                write!(formatter, \",\")?\n            }\n            write!(formatter, \"{d}\")?;\n        }\n        if self.open {\n            if self.dims.len() == 0 {\n                write!(formatter, \"..\")?;\n            } else {\n                write!(formatter, \",..\")?;\n            }\n        }\n        Ok(())\n    }\n}\n\npub type DimFact = GenericFactoid<TDim>;\n\n/// Partial information about a value.\npub type ValueFact = GenericFactoid<Arc<Tensor>>;\n\npub type IntFactoid = GenericFactoid<i64>;\n\nimpl<T> Zero for GenericFactoid<T>\nwhere\n    T: Add<T, Output = T> + Zero + PartialEq + Clone + ::std::fmt::Debug + Hash,\n{\n    fn zero() -> GenericFactoid<T> {\n        GenericFactoid::Only(T::zero())\n    }\n    fn is_zero(&self) -> bool {\n        match self {\n            GenericFactoid::Only(t) => t.is_zero(),\n            _ => false,\n        }\n    }\n}\n\nimpl<T> Neg for GenericFactoid<T>\nwhere\n    T: Neg<Output = T> + PartialEq + Clone + ::std::fmt::Debug + Hash,\n{\n    type Output = GenericFactoid<T>;\n    fn neg(self) -> GenericFactoid<T> {\n        match self {\n            GenericFactoid::Only(t) => GenericFactoid::Only(t.neg()),\n            any => any,\n        }\n    }\n}\n\nimpl<T, I> Add<I> for GenericFactoid<T>\nwhere\n    T: Add<T, Output = T> + PartialEq + Clone + ::std::fmt::Debug + Hash,\n    I: Into<GenericFactoid<T>>,\n{\n    type Output = GenericFactoid<T>;\n    fn add(self, rhs: I) -> Self::Output {\n        match (self.concretize(), rhs.into().concretize()) {\n            (Some(a), Some(b)) => GenericFactoid::Only(a + b),\n            _ => GenericFactoid::Any,\n        }\n    }\n}\n\nimpl<T> Sub<GenericFactoid<T>> for GenericFactoid<T>\nwhere\n    T: Sub<T, Output = T> + PartialEq + Clone + ::std::fmt::Debug + Hash,\n{\n    type Output = GenericFactoid<T>;\n    fn sub(self, rhs: GenericFactoid<T>) -> Self::Output {\n        match (self.concretize(), rhs.concretize()) {\n            (Some(a), Some(b)) => GenericFactoid::Only(a - b),\n            _ => GenericFactoid::Any,\n        }\n    }\n}\n\nimpl<T, R> Mul<R> for GenericFactoid<T>\nwhere\n    T: Mul<R, Output = T> + PartialEq + Clone + ::std::fmt::Debug + Hash,\n{\n    type Output = GenericFactoid<T>;\n    fn mul(self, rhs: R) -> Self::Output {\n        if let Some(a) = self.concretize() {\n            GenericFactoid::Only(a * rhs)\n        } else {\n            GenericFactoid::Any\n        }\n    }\n}\n\nimpl<T, R> Div<R> for GenericFactoid<T>\nwhere\n    T: Div<R, Output = T> + PartialEq + Clone + ::std::fmt::Debug + Hash,\n{\n    type Output = GenericFactoid<T>;\n    fn div(self, rhs: R) -> Self::Output {\n        if let Some(a) = self.concretize() {\n            GenericFactoid::Only(a / rhs)\n        } else {\n            GenericFactoid::Any\n        }\n    }\n}\n\nimpl<T, R> Rem<R> for GenericFactoid<T>\nwhere\n    T: Rem<R, Output = T> + PartialEq + Clone + ::std::fmt::Debug + Hash,\n{\n    type Output = GenericFactoid<T>;\n    fn rem(self, rhs: R) -> Self::Output {\n        if let Some(a) = self.concretize() {\n            GenericFactoid::Only(a % rhs)\n        } else {\n            GenericFactoid::Any\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::GenericFactoid::*;\n    use super::*;\n\n    #[test]\n    fn unify_same_datum_type() {\n        let dt = TypeFactoid::Only(DatumType::F32);\n        assert_eq!(dt.unify(&dt).unwrap(), dt);\n    }\n\n    #[test]\n    fn unify_different_datum_types_only() {\n        let dt1 = TypeFactoid::Only(DatumType::F32);\n        let dt2 = TypeFactoid::Only(DatumType::F64);\n        assert!(dt1.unify(&dt2).is_err());\n    }\n\n    #[test]\n    fn unify_different_datum_types_any_left() {\n        let dt = TypeFactoid::Only(DatumType::F32);\n        assert_eq!(TypeFactoid::Any.unify(&dt).unwrap(), dt);\n    }\n\n    #[test]\n    fn unify_different_datum_types_any_right() {\n        let dt = TypeFactoid::Only(DatumType::F32);\n        assert_eq!(dt.unify(&TypeFactoid::Any).unwrap(), dt);\n    }\n\n    #[test]\n    fn unify_same_shape_1() {\n        let s = ShapeFactoid::closed(tvec![]);\n        assert_eq!(s.unify(&s).unwrap(), s);\n    }\n\n    #[test]\n    fn unify_same_shape_2() {\n        let s = ShapeFactoid::closed(tvec![Any]);\n        assert_eq!(s.unify(&s).unwrap(), s);\n    }\n\n    #[test]\n    fn unify_same_shape_3() {\n        let s = ShapeFactoid::closed(tvec![Only(1.into()), Only(2.into())]);\n        assert_eq!(s.unify(&s).unwrap(), s);\n    }\n\n    #[test]\n    fn unify_different_shapes_1() {\n        let s1 = ShapeFactoid::closed(tvec![Only(1.into()), Only(2.into())]);\n        let s2 = ShapeFactoid::closed(tvec![Only(1.into())]);\n        assert!(s1.unify(&s2).is_err());\n    }\n\n    #[test]\n    fn unify_different_shapes_2() {\n        let s1 = ShapeFactoid::closed(tvec![Only(1.into()), Only(2.into())]);\n        let s2 = ShapeFactoid::closed(tvec![Any]);\n        assert!(s1.unify(&s2).is_err());\n    }\n\n    #[test]\n    fn unify_different_shapes_3() {\n        let s1 = ShapeFactoid::open(tvec![Only(1.into()), Only(2.into())]);\n        let s2 = ShapeFactoid::closed(tvec![Any]);\n        assert!(s1.unify(&s2).is_err());\n    }\n\n    #[test]\n    fn unify_different_shapes_4() {\n        let s1 = ShapeFactoid::closed(tvec![Any]);\n        let s2 = ShapeFactoid::closed(tvec![Any]);\n        let sr = ShapeFactoid::closed(tvec![Any]);\n        assert_eq!(s1.unify(&s2).unwrap(), sr);\n    }\n\n    #[test]\n    fn unify_different_shapes_5() {\n        let s1 = ShapeFactoid::closed(tvec![Any]);\n        let s2 = ShapeFactoid::closed(tvec![Only(1.into())]);\n        let sr = ShapeFactoid::closed(tvec![Only(1.into())]);\n        assert_eq!(s1.unify(&s2).unwrap(), sr);\n    }\n\n    #[test]\n    fn unify_different_shapes_6() {\n        let s1 = ShapeFactoid::open(tvec![]);\n        let s2 = ShapeFactoid::closed(tvec![Only(1.into())]);\n        let sr = ShapeFactoid::closed(tvec![Only(1.into())]);\n        assert_eq!(s1.unify(&s2).unwrap(), sr);\n    }\n\n    #[test]\n    fn unify_different_shapes_7() {\n        let s1 = ShapeFactoid::open(tvec![Any, Only(2.into())]);\n        let s2 = ShapeFactoid::closed(tvec![Only(1.into()), Any, Any]);\n        let sr = ShapeFactoid::closed(tvec![Only(1.into()), Only(2.into()), Any]);\n        assert_eq!(s1.unify(&s2).unwrap(), sr);\n    }\n\n    #[test]\n    fn unify_same_value() {\n        let t = ValueFact::Only(rctensor0(12f32));\n        assert_eq!(t.unify(&t).unwrap(), t);\n    }\n\n    #[test]\n    fn unify_different_values_only() {\n        let t1 = ValueFact::Only(rctensor1(&[12f32]));\n        let t2 = ValueFact::Only(rctensor1(&[12f32, 42.0]));\n        assert!(t1.unify(&t2).is_err());\n    }\n\n    #[test]\n    fn unify_different_values_any_left() {\n        let t1 = ValueFact::Only(rctensor1(&[12f32]));\n        assert_eq!(ValueFact::Any.unify(&t1).unwrap(), t1);\n    }\n\n    #[test]\n    fn unify_different_values_any_right() {\n        let t1 = ValueFact::Only(rctensor1(&[12f32]));\n        assert_eq!(t1.unify(&ValueFact::Any).unwrap(), t1);\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/helpers.rs",
    "content": "use super::factoid::*;\nuse super::*;\n\n/// Infers every possible fact when all the values are concrete.\npub fn infer_forward_concrete(\n    op: &dyn Op,\n    inputs: &[&InferenceFact],\n) -> TractResult<Option<TVec<InferenceFact>>> {\n    let input_values: TVec<_> = inputs.iter().filter_map(|t| t.value.concretize()).collect();\n\n    if input_values.len() < inputs.len() {\n        debug!(\"Can't infer value: some inputs are still unknown.\");\n        return Ok(None);\n    }\n    let input_values = input_values.iter().map(|t| t.clone().into_tvalue()).collect();\n    // If we know the value of all the inputs, we can deduce everything.\n    if op.is_stateless() {\n        let output_value = op.eval(input_values)?.pop().unwrap();\n        return Ok(Some(tvec![output_value.into_arc_tensor().try_into()?]));\n    }\n\n    Ok(None)\n}\n\n/// Infers basic shape facts in the case of broadcasting operators.\npub fn infer_shape_broadcasting(shapes: &[&ShapeFactoid]) -> TractResult<Option<ShapeFactoid>> {\n    if shapes.iter().any(|s| s.is_open()) {\n        debug!(\"Can't infer shape for broadcasting operators when some inputs have an open shape.\");\n        return Ok(None);\n    }\n\n    let bound = shapes.iter().map(|s| s.rank().concretize().unwrap()).max().unwrap() as usize;\n\n    let mut output_shape: TVec<DimFact> = tvec![];\n\n    for i in 0..bound {\n        let mut previous: Option<TDim> = None;\n        let mut unknown = 0;\n\n        for shape in shapes.iter() {\n            let rank = shape.rank().concretize().unwrap() as usize;\n            let shape: TVec<DimFact> = shape.dims().cloned().collect();\n            if i >= rank {\n                continue;\n            }\n\n            match &shape[rank - i - 1] {\n                GenericFactoid::Any => unknown += 1,\n                GenericFactoid::Only(d) if d.is_one() => (),\n                GenericFactoid::Only(d) => {\n                    if previous.is_some() && previous.as_ref() != Some(d) {\n                        bail!(\n                            \"Invalid shape (broadcasting): {:?} is not compatible with {:?}.\",\n                            d,\n                            previous\n                        )\n                    } else {\n                        previous = Some(d.clone())\n                    }\n                }\n            };\n        }\n\n        if unknown > 1 {\n            debug!(\n                \"Can't infer shape (broadcasting): there are multiple unknown values at same index.\"\n            );\n            return Ok(None);\n        } else if unknown == 1 && previous.is_some() {\n            debug!(\n                \"Can't infer shape (broadcasting): there are both unknown and known values at same index.\"\n            );\n            return Ok(None);\n        } else if unknown == 1 && previous.is_none() {\n            output_shape.push(GenericFactoid::Any);\n        } else if let Some(previous) = previous {\n            output_shape.push(GenericFactoid::Only(previous.clone()));\n        } else {\n            output_shape.push(GenericFactoid::Only(1.into()));\n        }\n    }\n\n    output_shape.reverse();\n\n    Ok(Some(ShapeFactoid::closed(output_shape)))\n}\n\n/// Infers basic facts in the case of unary or binary operators.\npub fn infer_forward_basic(\n    op: &dyn Op,\n    inputs: Vec<&InferenceFact>,\n) -> TractResult<Option<TVec<InferenceFact>>> {\n    if let Some(output) = infer_forward_concrete(op, &inputs)? {\n        return Ok(Some(output));\n    }\n\n    // Otherwise we can only deduce the type and shape of the output.\n    let input_shapes: Vec<_> = inputs.iter().map(|t| &t.shape).collect();\n\n    let datum_type = inputs\n        .iter()\n        .find_map(|i| i.datum_type.concretize())\n        .map(|t| typefact!(t))\n        .unwrap_or_else(|| typefact!(_));\n\n    let output = InferenceFact {\n        datum_type,\n        shape: infer_shape_broadcasting(&input_shapes)?.unwrap_or_else(|| shapefactoid![..]),\n        value: valuefact!(_),\n    };\n\n    Ok(Some(tvec![output]))\n}\n\n/// Returns the most specific closed shape out of an iterator.\npub fn most_specific_shape<'a, I: IntoIterator<Item = &'a ShapeFactoid>>(\n    iter: I,\n) -> TractResult<Option<&'a ShapeFactoid>> {\n    let mut prev_rank = None;\n    let mut prev_concrete = None;\n    let mut best = None;\n\n    for shape in iter {\n        if let Some(rank) = shape.rank().concretize() {\n            if prev_rank.is_some() && rank != prev_rank.unwrap() {\n                bail!(\"Rank mismatch between different shapes.\");\n            } else {\n                prev_rank = Some(rank);\n            }\n\n            let concrete = shape.dims().filter(|d| d.is_concrete()).count();\n\n            if prev_concrete.is_none() || concrete > prev_concrete.unwrap() {\n                prev_concrete = Some(concrete);\n                best = Some(shape)\n            }\n        }\n    }\n\n    Ok(best)\n}\n"
  },
  {
    "path": "hir/src/infer/mod.rs",
    "content": "use crate::internal::*;\n\n#[macro_use]\npub mod helpers;\n#[macro_use]\npub mod rules;\n\nmod analyser;\nmod fact;\nmod factoid;\nmod model;\nmod ops;\nmod optim;\n\npub use self::fact::InferenceFact;\npub use self::factoid::*;\npub use self::model::InferenceModelExt;\npub use self::ops::InferenceOp;\npub use self::rules::InferenceResult;\npub use self::rules::InferenceRulesOp;\npub use self::rules::Solver;\npub use self::rules::TensorProxy;\npub use self::rules::expr::IntoExp;\npub use self::rules::expr::ToDimExp;\n\npub fn check_input_arity(inputs: &[TensorProxy], expected: usize) -> TractResult<()> {\n    if inputs.len() != expected {\n        bail!(\"Wrong input number. Rules expect {}, node has {}.\", expected, inputs.len())\n    } else {\n        Ok(())\n    }\n}\n\npub fn check_output_arity(outputs: &[TensorProxy], expected: usize) -> TractResult<()> {\n    if outputs.len() != expected {\n        bail!(\"Wrong output number. Rules expect {}, node has {}.\", expected, outputs.len())\n    } else {\n        Ok(())\n    }\n}\n\n/// A model with partially types and shapes, as produced by parsing ONNX or\n/// Tensorflow graphs.\npub type InferenceModel = Graph<InferenceFact, Box<dyn InferenceOp>>;\n/// Node for InferenceModel graph\npub type InferenceNode = Node<InferenceFact, Box<dyn InferenceOp>>;\n/// A ModelPatch for InferenceModel.\npub type InferenceModelPatch = ModelPatch<InferenceFact, Box<dyn InferenceOp>>;\n/// An execution plan for InferenceModel.\npub type InferenceSimplePlan = SimplePlan<InferenceFact, Box<dyn InferenceOp>>;\n/// An execution state for InferenceModel.\npub type InferenceSimpleState = SimpleState<InferenceFact, Box<dyn InferenceOp>>;\n\nimpl<'a> From<&'a Box<dyn InferenceOp>> for Box<dyn InferenceOp> {\n    fn from(it: &'a Box<dyn InferenceOp>) -> Box<dyn InferenceOp> {\n        tract_core::dyn_clone::clone_box(it.as_ref())\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/model.rs",
    "content": "use std::collections::HashMap;\n\nuse tract_core::ops::konst::Const;\n\nuse super::factoid::Factoid;\nuse super::{InferenceFact, InferenceModel, InferenceNode, InferenceOp};\nuse crate::internal::*;\nuse crate::prelude::TVec;\n\npub trait InferenceModelExt {\n    /// Analyse all nodes of the graph.\n    ///\n    /// Will stop on first error unless `obstinate` is `true`.\n    fn analyse(&mut self, obstinate: bool) -> TractResult<bool>;\n\n    /// Perform early transformation before going typed.\n    fn incorporate(self) -> TractResult<InferenceModel>;\n\n    /// List OutletId with incomplete type information.\n    ///\n    /// Will stop on first error unless `obstinate` is `true`.\n    fn missing_type_shape(&self) -> TractResult<Vec<OutletId>>;\n\n    /// Eliminate seemingly dead branches of the graph.\n    ///\n    /// This may break stateful networks.\n    fn eliminate_dead_branches(self) -> TractResult<InferenceModel>;\n\n    /// Attempt full analyse and conversion to TypedModel.\n    fn into_typed(self) -> TractResult<TypedModel>;\n\n    /// Attempt full analyse, decluttering and mapping to optimized operations.\n    ///\n    /// This will work even if the network can not be normalized.\n    fn into_optimized(self) -> TractResult<TypedModel>;\n}\n\nimpl InferenceModelExt for InferenceModel {\n    /// Analyse all nodes of the graph.\n    ///\n    /// Will stop on first error unless `obstinate` is `true`.\n    fn analyse(&mut self, obstinate: bool) -> TractResult<bool> {\n        super::analyser::Analyser::new(self).analyse_obstinate(obstinate)\n    }\n\n    /// Perform early transformation before going typed.\n    fn incorporate(self) -> TractResult<InferenceModel> {\n        let mut model = self;\n        loop {\n            let mut done_something = false;\n            for p in crate::infer::optim::incorporate() {\n                done_something = done_something || p.pass(&mut model)?;\n                if cfg!(debug_assertions) {\n                    model.check_edges()?;\n                }\n            }\n            if !done_something {\n                break;\n            }\n        }\n        model = model.into_compact()?;\n        model.analyse(false)?;\n        Ok(model)\n    }\n\n    /// List OutletId with incomplete type information.\n    ///\n    /// Will stop on first error unless `obstinate` is `true`.\n    fn missing_type_shape(&self) -> TractResult<Vec<OutletId>> {\n        Ok(self\n            .eval_order()?\n            .iter()\n            .flat_map(|&node| {\n                self.nodes()[node]\n                    .outputs\n                    .iter()\n                    .enumerate()\n                    .map(move |(ix, outlet)| (OutletId::new(node, ix), outlet))\n            })\n            .filter(|(_, o)| !o.fact.datum_type.is_concrete() || !o.fact.shape.is_concrete())\n            .map(|(id, _)| id)\n            .collect())\n    }\n\n    /// Eliminate seemingly dead branches of the graph.\n    ///\n    /// This may break stateful networks.\n    fn eliminate_dead_branches(self) -> TractResult<InferenceModel> {\n        self.into_compact()\n    }\n\n    /// Attempt full analyse and conversion to TypedModel.\n    fn into_typed(mut self) -> TractResult<TypedModel> {\n        use tract_core::internal::translator::Translate;\n\n        self.analyse(false)?;\n        let m = self.incorporate()?;\n\n        #[derive(Debug)]\n        struct ToTypedTranslator;\n        impl Translate<InferenceFact, Box<dyn InferenceOp>, TypedFact, Box<dyn TypedOp>>\n            for ToTypedTranslator\n        {\n            fn translate_node(\n                &self,\n                source: &InferenceModel,\n                node: &InferenceNode,\n                target: &mut TypedModel,\n                mapping: &HashMap<OutletId, OutletId>,\n            ) -> TractResult<TVec<OutletId>> {\n                if node.op.is_stateless()\n                    && source.node_output_facts(node.id)?.iter().all(|f| f.value.is_concrete())\n                {\n                    (0..node.outputs.len())\n                        .map(|ix| {\n                            target.add_const(\n                                format!(\"{}.{}\", node.name, ix),\n                                node.outputs[ix].fact.value.concretize().unwrap(),\n                            )\n                        })\n                        .collect()\n                } else {\n                    let outputs = node.op.to_typed(source, node, target, mapping)?;\n                    for output in &outputs {\n                        let fact = target.outlet_fact(*output)?;\n                        fact.consistent().with_context(|| {\n                            format!(\n                                \"Checking oulet fact consistency for {:?}: {:?} after translating {:?}\",\n                                output,\n                                fact, node.op,\n                            )\n                        })?;\n                    }\n                    Ok(outputs)\n                }\n            }\n        }\n\n        ToTypedTranslator.translate_model(&m)\n    }\n\n    /// Attempt full analyse, decluttering and mapping to optimized operations.\n    ///\n    /// This is meant for \"simple\" networks, where no special model\n    /// transformation needs to happen. Aternaltively, use to_typed() and\n    /// manipulate the TypedModel for more control.\n    fn into_optimized(self) -> TractResult<TypedModel> {\n        self.into_typed()?.into_optimized()\n    }\n}\n\nimpl SpecialOps<InferenceFact, Box<dyn InferenceOp>> for InferenceModel {\n    fn is_source(op: &Box<dyn InferenceOp>) -> bool {\n        op.as_op().downcast_ref::<crate::ops::source::Source>().is_some()\n    }\n\n    fn create_dummy(&self) -> Box<dyn InferenceOp> {\n        Box::new(tract_core::ops::dummy::Dummy::new())\n    }\n\n    fn create_source(&self, _fact: InferenceFact) -> Box<dyn InferenceOp> {\n        Box::new(crate::ops::source::Source::new())\n    }\n\n    fn wire_node(\n        &mut self,\n        name: impl Into<String>,\n        op: impl Into<Box<dyn InferenceOp>>,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let op = op.into();\n        let output_facts: TVec<InferenceFact> =\n            (0..op.nboutputs()?).map(|_| InferenceFact::default()).collect();\n        let id = self.add_node(name, op, output_facts)?;\n        inputs\n            .iter()\n            .enumerate()\n            .try_for_each(|(ix, i)| self.add_edge(*i, InletId::new(id, ix)))?;\n        Ok(self.node(id).outputs.iter().enumerate().map(|(ix, _)| OutletId::new(id, ix)).collect())\n    }\n\n    fn add_const(\n        &mut self,\n        name: impl Into<String>,\n        v: impl IntoArcTensor,\n    ) -> TractResult<OutletId> {\n        let v = v.into_arc_tensor();\n        for node in &self.nodes {\n            if let Some(op) = node.op_as::<Const>() {\n                if op.val() == &v {\n                    return Ok(node.id.into());\n                }\n            }\n        }\n        let name = name.into();\n        let fact = TypedFact::try_from(v.clone())?;\n        self.add_node(name, crate::ops::konst::Const::new(v)?, tvec!(fact.into()))\n            .map(|id| id.into())\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test() {\n        fn is_sync<T: Sync>() {}\n        is_sync::<InferenceModel>();\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/ops.rs",
    "content": "use super::Factoid;\nuse crate::infer::*;\nuse std::fmt;\nuse tract_data::TooEarly;\n\ntract_core::dyn_clone::clone_trait_object!(InferenceOp);\n\n/// An operation with tensor type inference\npub trait InferenceOp: Op {\n    /// Infers properties about the input and output tensors.\n    ///\n    /// The `inputs` and `outputs` arguments correspond to properties about\n    /// the input and output tensors that are already known.\n    ///\n    /// The default implementation will call the private infer_facts method,\n    /// which is usually implemented using the InferenceRulesOp trait. It will\n    /// also try to eval() the op if its a EvalOp and if the inputs are\n    /// fully determined.\n    ///\n    /// Returns Err in case of an unrecoverable error during the inference,\n    /// and the refined properties about the inputs and outputs otherwise.\n    fn infer(\n        &mut self,\n        inputs: TVec<&InferenceFact>,\n        outputs: TVec<&InferenceFact>,\n        observed: TVec<&InferenceFact>,\n    ) -> TractResult<(TVec<InferenceFact>, TVec<InferenceFact>, TVec<InferenceFact>)> {\n        let (infered_inputs, infered_outputs, observed) =\n            self.infer_facts(inputs, outputs, observed).context(\"Infering facts\")?;\n\n        if self.is_stateless() && infered_inputs.iter().all(|i| i.value.is_concrete()) {\n            let input_values = infered_inputs\n                .iter()\n                .map(|i| i.value.concretize().unwrap().into_tvalue())\n                .collect(); // checked\n            match self.eval(input_values) {\n                Ok(values) => {\n                    let output_values = values\n                        .into_iter()\n                        .map(|t| t.into_arc_tensor().try_into())\n                        .collect::<TractResult<TVec<_>>>()?;\n                    return Ok((infered_inputs, output_values, observed));\n                }\n                Err(e) if e.root_cause().downcast_ref::<TooEarly>().is_some() => (),\n                Err(e) => return Err(e).context(\"Eager eval during inference\"),\n            }\n        }\n\n        Ok((infered_inputs, infered_outputs, observed))\n    }\n\n    /// Allow an op to specify a supplementary list of outlets facts that\n    /// will trigger inference again.\n    fn observe_outlets(\n        &self,\n        _model: &InferenceModel,\n        _node: &InferenceNode,\n    ) -> TractResult<Vec<OutletId>> {\n        Ok(vec![])\n    }\n\n    /// Infer properties about inputs and output tensors. This method does not\n    /// need to deal with the \"trivial\" stateless op with fully determined\n    /// inputs cases.\n    ///\n    /// Most of the time, it is implemented using InferenceRulesOp.\n    fn infer_facts(\n        &mut self,\n        inputs: TVec<&InferenceFact>,\n        outputs: TVec<&InferenceFact>,\n        observed: TVec<&InferenceFact>,\n    ) -> TractResult<(TVec<InferenceFact>, TVec<InferenceFact>, TVec<InferenceFact>)>;\n\n    /// Early pass on inference model, after analyse, but before translation to\n    /// typed network. Meant to deal with some framework idiosyncrasies that\n    /// manifest with temporaries nodes that can run some form of inference but\n    /// require refactoring the network before it can be evaluated.\n    ///\n    /// Called after succesful analyse, but before translating to typed model.\n    #[allow(unused_variables)]\n    fn incorporate(\n        &self,\n        model: &InferenceModel,\n        node: &InferenceNode,\n    ) -> TractResult<Option<InferenceModelPatch>> {\n        Ok(None)\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(1)\n    }\n\n    /// Reinterpret the InferenceOp as an Op.\n    fn as_op(&self) -> &dyn Op;\n\n    /// Reinterpret the InferenceOp as an Op, mutably.\n    fn as_op_mut(&mut self) -> &mut dyn Op;\n\n    /// Called during translation to TypedModel.\n    #[allow(unused_variables)]\n    fn to_typed(\n        &self,\n        source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        bail!(\"Operator can not be made a TypedOp.\")\n    }\n}\n\nimpl std::fmt::Display for Box<dyn InferenceOp> {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \"{}\", self.name())\n    }\n}\n\nimpl<O: InferenceOp> From<O> for Box<dyn InferenceOp> {\n    fn from(it: O) -> Box<dyn InferenceOp> {\n        Box::new(it)\n    }\n}\n\nimpl AsRef<dyn Op> for dyn InferenceOp {\n    fn as_ref(&self) -> &dyn Op {\n        self.as_op()\n    }\n}\n\nimpl AsRef<dyn Op> for Box<dyn InferenceOp> {\n    fn as_ref(&self) -> &dyn Op {\n        self.as_op()\n    }\n}\n\nimpl AsMut<dyn Op> for dyn InferenceOp {\n    fn as_mut(&mut self) -> &mut dyn Op {\n        self.as_op_mut()\n    }\n}\n\nimpl AsMut<dyn Op> for Box<dyn InferenceOp> {\n    fn as_mut(&mut self) -> &mut dyn Op {\n        self.as_op_mut()\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/optim.rs",
    "content": "use crate::infer::*;\nuse std::fmt;\n\npub fn incorporate() -> Vec<Box<dyn IncorporatePass>> {\n    vec![Box::new(IncorporateOps)]\n}\n\npub trait IncorporatePass: fmt::Debug + Send + Sync {\n    fn pass(&self, model: &mut InferenceModel) -> TractResult<bool>;\n}\n\n#[derive(Debug)]\npub struct IncorporateOps;\n\nimpl IncorporatePass for IncorporateOps {\n    fn pass(&self, model: &mut InferenceModel) -> TractResult<bool> {\n        let mut done_something = false;\n        loop {\n            let mut done_something_this_time = false;\n            for id in model.eval_order()? {\n                let reduced = {\n                    let node = &model.nodes()[id];\n                    trace!(\"Incorporate {node}\");\n                    node.op\n                        .incorporate(model, node)\n                        .with_context(|| format!(\"{self:?} node {node}\"))?\n                };\n                if let Some(red) = reduced {\n                    {\n                        let node = &model.nodes()[id];\n                        debug!(\"Apply a model patch for {self:?}: {node}\");\n                    }\n                    red.apply(model)?;\n                    if cfg!(debug_assertions) {\n                        model.check_edges()?;\n                    }\n                    done_something_this_time = true;\n                }\n            }\n            done_something = done_something || done_something_this_time;\n            if !done_something_this_time {\n                break;\n            }\n        }\n        Ok(done_something)\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/rules/cache.rs",
    "content": "use std::cell::RefCell;\nuse std::collections::HashMap;\nuse std::hash::Hash;\n\n/// An insert-only HashMap which doesn't require mutable references.\npub struct Cache<K: Eq + Hash, V>(\n    // We need to use a RefCell here because we need interior mutability for\n    // the cache. This way, the `get` method will only need `&self` (and not\n    // `&mut self`) but we'll still be able to insert new items dynamically.\n    RefCell<HashMap<K, Box<V>>>,\n);\n\nimpl<K: Eq + Hash, V> Cache<K, V> {\n    /// Creates a new Cache instance.\n    pub fn new() -> Cache<K, V> {\n        Cache(RefCell::new(HashMap::new()))\n    }\n\n    /// Returns a reference to the cached entry for a given key, or stores a\n    /// new entry on cache misses and then returns a reference to it.\n    pub fn get<F>(&self, index: K, default: F) -> &V\n    where\n        F: FnOnce() -> V,\n    {\n        // This is valid because we never remove anything from the cache, so\n        // the reference to the items that we return will always exist.\n        unsafe {\n            let cache = &mut *self.0.as_ptr();\n            cache.entry(index).or_insert_with(|| Box::new(default()))\n        }\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/rules/expr.rs",
    "content": "use std::fmt;\nuse std::marker::PhantomData;\nuse std::ops::{Add, Div, Mul, Neg, Sub};\n\nuse tract_num_traits::ToPrimitive;\nuse tract_num_traits::Zero;\n\nuse crate::internal::*;\n\nuse self::super::super::factoid::*;\nuse self::super::path::Path;\nuse self::super::proxies::*;\nuse self::super::solver::Context;\n\n/// A trait for values produced by expressions.\npub trait Output: fmt::Debug + Clone + PartialEq {\n    /// Wraps self in the Wrapped type.\n    fn wrap(self) -> Wrapped {\n        Self::into_wrapped(self)\n    }\n\n    /// Wraps the fact in the Wrapped type.\n    fn into_wrapped(source: Self) -> Wrapped;\n\n    /// Retrieves the fact from the Wrapped type.\n    /// Panics if wrapped doesn't have the right constructor.\n    fn from_wrapped(wrapped: Wrapped) -> TractResult<Self>;\n}\n\nmacro_rules! impl_output {\n    ($type:ty, $constr:ident, $name:expr) => {\n        impl Output for $type {\n            fn into_wrapped(source: Self) -> Wrapped {\n                Wrapped::$constr(source)\n            }\n\n            fn from_wrapped(wrapped: Wrapped) -> TractResult<$type> {\n                if let Wrapped::$constr(v) = wrapped {\n                    Ok(v)\n                } else {\n                    bail!(\"Tried to get a {} from {:?}.\", $name, wrapped);\n                }\n            }\n        }\n    };\n}\n\nimpl_output!(IntFactoid, Int, \"Int\");\nimpl_output!(TypeFactoid, Type, \"DatumType\");\nimpl_output!(ShapeFactoid, Shape, \"Shape\");\nimpl_output!(ValueFact, Tensor, \"Tensor\");\nimpl_output!(DimFact, Dim, \"TDim\");\n\n// Converts back and forth between Wrapped and usize.\nimpl Output for usize {\n    fn into_wrapped(source: usize) -> Wrapped {\n        IntFactoid::into_wrapped((source as i64).into())\n    }\n\n    fn from_wrapped(wrapped: Wrapped) -> TractResult<usize> {\n        IntFactoid::from_wrapped(wrapped.clone())?\n            .concretize()\n            .and_then(|u| u.to_usize())\n            .with_context(|| format!(\"Tried to convert {wrapped:?} to a usize.\"))\n    }\n}\n\n// Converts back and forth between Wrapped and i64.\nimpl Output for i64 {\n    fn into_wrapped(source: i64) -> Wrapped {\n        IntFactoid::into_wrapped(source.into())\n    }\n\n    fn from_wrapped(wrapped: Wrapped) -> TractResult<i64> {\n        IntFactoid::from_wrapped(wrapped.clone())?\n            .concretize()\n            .with_context(|| format!(\"Tried to convert {wrapped:?} to a i64.\"))\n    }\n}\n\n// Converts back and forth between Wrapped and Tensor.\nimpl Output for Arc<Tensor> {\n    fn into_wrapped(source: Arc<Tensor>) -> Wrapped {\n        ValueFact::into_wrapped(source.into())\n    }\n\n    fn from_wrapped(wrapped: Wrapped) -> TractResult<Arc<Tensor>> {\n        ValueFact::from_wrapped(wrapped.clone())?\n            .concretize()\n            .with_context(|| format_err!(\"Tried to convert {:?} to a tensor.\", wrapped))\n    }\n}\n\n// Converts back and forth between Wrapped and usize.\nimpl Output for TDim {\n    fn into_wrapped(source: TDim) -> Wrapped {\n        DimFact::into_wrapped(source.into())\n    }\n\n    fn from_wrapped(wrapped: Wrapped) -> TractResult<TDim> {\n        DimFact::from_wrapped(wrapped.clone())?\n            .concretize()\n            .with_context(|| format_err!(\"Tried to convert {:?} to a usize.\", wrapped))\n    }\n}\n\n/// A wrapper for all the types of values that expressions can produce.\n#[derive(Debug, Clone)]\npub enum Wrapped {\n    Int(IntFactoid),\n    Type(TypeFactoid),\n    Shape(ShapeFactoid),\n    Tensor(ValueFact),\n    Dim(DimFact),\n}\n\n/// An expression that can be compared by the solver.\npub trait TExp<T>: fmt::Debug {\n    /// Returns the current value of the expression in the given context.\n    fn get(&self, context: &Context) -> TractResult<T>;\n\n    /// Tries to set the value of the expression in the given context.\n    fn set(&self, context: &mut Context, value: T) -> TractResult<bool>;\n\n    /// Returns the paths that the expression depends on.\n    fn get_paths(&self) -> Vec<&Path>;\n}\n\npub struct Exp<T>(Box<dyn TExp<T>>);\nimpl<T: Factoid + Output + Clone + fmt::Debug> TExp<T> for Exp<T> {\n    /// Returns the current value of the expression in the given context.\n    fn get(&self, context: &Context) -> TractResult<T> {\n        self.0.get(context)\n    }\n\n    /// Tries to set the value of the expression in the given context.\n    fn set(&self, context: &mut Context, value: T) -> TractResult<bool> {\n        self.0.set(context, value)\n    }\n\n    /// Returns the paths that the expression depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        self.0.get_paths()\n    }\n}\n\nimpl<T> fmt::Debug for Exp<T>\nwhere\n    T: Factoid + Output + Clone + ::std::fmt::Debug,\n{\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        write!(formatter, \"{:?}\", self.0)\n    }\n}\n\npub trait IntoExp<T> {\n    /// Converts the value to an Expression.\n    fn bex(self) -> Exp<T>;\n}\n\n#[derive(new)]\npub struct SumExp<T>(Vec<Exp<T>>)\nwhere\n    T: Factoid + Output + Clone + ::std::fmt::Debug + 'static;\n\nimpl<T> TExp<T> for SumExp<T>\nwhere\n    T: Factoid + Output + Zero + Add<T> + Neg<Output = T> + Clone + ::std::fmt::Debug + 'static,\n{\n    /// Returns the current value of the expression in the given context.\n    fn get(&self, context: &Context) -> TractResult<T> {\n        self.0.iter().try_fold(T::zero(), |acc, it| Ok(acc + it.0.get(context)?))\n    }\n\n    /// Tries to set the value of the expression in the given context.\n    fn set(&self, context: &mut Context, value: T) -> TractResult<bool> {\n        let mut sum = T::zero();\n        let mut misses = vec![];\n\n        for item in &self.0 {\n            let fact = item.get(context)?;\n            if fact.is_concrete() {\n                sum = sum + fact;\n            } else {\n                misses.push(item);\n            }\n        }\n\n        if misses.len() > 1 {\n            Ok(false)\n        } else if misses.len() == 1 {\n            misses[0].set(context, value + -sum)?;\n            Ok(true)\n        } else if sum == value {\n            Ok(false)\n        } else {\n            bail!(\"{:?} set to {:?}, already is {:?}\", self, value, sum)\n        }\n    }\n\n    /// Returns the paths that the rule depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        self.0.iter().flat_map(|e| e.get_paths()).collect()\n    }\n}\n\nimpl<T> fmt::Debug for SumExp<T>\nwhere\n    T: Factoid + Output + Clone + ::std::fmt::Debug,\n{\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        for (ix, t) in self.0.iter().enumerate() {\n            if ix > 0 {\n                write!(formatter, \" + \")?;\n            }\n            t.fmt(formatter)?;\n        }\n        Ok(())\n    }\n}\n\n/// A constant expression (e.g. `2` or `DatumType::DT_INT32`).\npub struct ConstantExp<T>(T)\nwhere\n    T: Factoid + Output + Clone + ::std::fmt::Debug;\n\nimpl<T> TExp<T> for ConstantExp<T>\nwhere\n    T: Factoid + Output + Clone + ::std::fmt::Debug,\n{\n    /// Returns the current value of the expression in the given context.\n    fn get(&self, _: &Context) -> TractResult<T> {\n        Ok(self.0.clone())\n    }\n\n    /// Tries to set the value of the expression in the given context.\n    fn set(&self, _: &mut Context, value: T) -> TractResult<bool> {\n        self.0.unify(&value)?;\n        Ok(false)\n    }\n\n    /// Returns the paths that the expression depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        vec![]\n    }\n}\n\nimpl<T> fmt::Debug for ConstantExp<T>\nwhere\n    T: Factoid + Output + Clone + ::std::fmt::Debug,\n{\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        write!(formatter, \"{:?}\", self.0)\n    }\n}\n\n/// A reference to a variable.\n///\n/// For instance, `inputs[0].rank` is a reference to the rank of the first\n/// input. Internally, a reference holds a Vec<usize> called a path (see\n/// the documentation for `Proxy::get_path`).\npub struct VariableExp<T>(Path, PhantomData<T>)\nwhere\n    T: Factoid + Output + Clone + ::std::fmt::Debug;\n\nimpl<T> TExp<T> for VariableExp<T>\nwhere\n    T: Factoid + Output + Clone + ::std::fmt::Debug,\n{\n    /// Returns the current value of the expression in the given context.\n    fn get(&self, context: &Context) -> TractResult<T> {\n        context.get(&self.0).with_context(|| format!(\"while getting {:?}\", self.0))\n    }\n\n    /// Tries to set the value of the expression in the given context.\n    fn set(&self, context: &mut Context, value: T) -> TractResult<bool> {\n        let old = self.get(context)?;\n        let new = old.unify(&value)?;\n        let diff = old != new;\n        context.set(&self.0, new).with_context(|| format!(\"while setting {:?}\", self.0))?;\n        Ok(diff)\n    }\n\n    /// Returns the paths that the expression depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        vec![&self.0]\n    }\n}\n\nimpl<T> fmt::Debug for VariableExp<T>\nwhere\n    T: Factoid + Output + Clone + ::std::fmt::Debug,\n{\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        write!(formatter, \"{:?}\", self.0)\n    }\n}\n\n/// A scalar product between a constant and another expression.\npub struct ScaledExp<T>(i64, Exp<T>)\nwhere\n    T: Factoid + Output + Zero + Mul<i64, Output = T> + Div<i64, Output = T> + Clone;\n\nimpl<T> TExp<T> for ScaledExp<T>\nwhere\n    T: Factoid + Output + Zero + Mul<i64, Output = T> + Div<i64, Output = T> + Clone,\n{\n    /// Returns the current value of the expression in the given context.\n    fn get(&self, context: &Context) -> TractResult<T> {\n        let v: T = self.1.get(context)?;\n        Ok(v * self.0)\n    }\n\n    /// Tries to set the value of the expression in the given context.\n    fn set(&self, context: &mut Context, value: T) -> TractResult<bool> {\n        let k = &self.0;\n        let m = value;\n\n        if m.is_zero() && k.is_zero() {\n            // We want to set 0 * x <- 0, so we don't have to do anything.\n            Ok(false)\n        } else if m.is_zero() {\n            // We want to set k * x <- 0, where k != 0, so we have to set x <- 0.\n            self.1.set(context, T::zero())\n        } else {\n            /*\n            // We want to set k * x <- m, where k and m != 0, so we will try\n            // to set x <- m / k using a checked division. This way, if m is\n            // not divisible by k, we will return Err instead of panicking.\n            let div = m.div(&V::from(*k)).ok_or(format!(\n            \"Cannot set the value of ({:?}, _) to {:?} because \\\n            {:?} is not divisible by {:?}.\",\n            k, m, m, k\n            ))?;\n            */\n\n            let div = m.div(*k);\n            self.1.set(context, div)\n        }\n    }\n\n    /// Returns the paths that the expression depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        self.1.get_paths()\n    }\n}\n\nimpl<T> fmt::Debug for ScaledExp<T>\nwhere\n    T: Factoid + Output + Zero + Mul<i64, Output = T> + Div<i64, Output = T> + Clone,\n{\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        write!(formatter, \"{}*{{{:?}}}\", self.0, self.1)\n    }\n}\n\n/// Cast an IntFactoid into a DimFact\npub struct IntoDimExp(Exp<IntFactoid>);\n\nimpl TExp<DimFact> for IntoDimExp {\n    /// Returns the current value of the expression in the given context.\n    fn get(&self, context: &Context) -> TractResult<DimFact> {\n        let v: IntFactoid = self.0.get(context)?;\n        match v {\n            GenericFactoid::Only(i) => Ok(GenericFactoid::Only(i.to_dim())),\n            GenericFactoid::Any => Ok(GenericFactoid::Any),\n        }\n    }\n\n    /// Tries to set the value of the expression in the given context.\n    fn set(&self, context: &mut Context, value: DimFact) -> TractResult<bool> {\n        if let Some(concrete) = value.concretize() {\n            if let Ok(int) = concrete.to_i64() {\n                return self.0.set(context, GenericFactoid::Only(int));\n            }\n        }\n        Ok(false)\n    }\n\n    /// Returns the paths that the expression depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        self.0.get_paths()\n    }\n}\n\nimpl fmt::Debug for IntoDimExp {\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        write!(formatter, \"{{({:?}) as dim}}\", self.0)\n    }\n}\n\n// ops and cast on Exp\n\nimpl<T, E: TExp<T> + 'static> IntoExp<T> for E {\n    fn bex(self) -> Exp<T> {\n        Exp(Box::new(self))\n    }\n}\n\n// Type\n\nimpl IntoExp<TypeFactoid> for TypeProxy {\n    fn bex(self) -> Exp<TypeFactoid> {\n        VariableExp(self.get_path().clone(), PhantomData).bex()\n    }\n}\n\nimpl IntoExp<TypeFactoid> for &TypeProxy {\n    fn bex(self) -> Exp<TypeFactoid> {\n        VariableExp(self.get_path().clone(), PhantomData).bex()\n    }\n}\n\nimpl IntoExp<TypeFactoid> for DatumType {\n    fn bex(self) -> Exp<TypeFactoid> {\n        ConstantExp(self.into()).bex()\n    }\n}\n\nimpl IntoExp<TypeFactoid> for &DatumType {\n    fn bex(self) -> Exp<TypeFactoid> {\n        ConstantExp((*self).into()).bex()\n    }\n}\n\n// Int\n\nimpl IntoExp<IntFactoid> for &IntProxy {\n    fn bex(self) -> Exp<IntFactoid> {\n        VariableExp(self.get_path().clone(), PhantomData).bex()\n    }\n}\n\nimpl IntoExp<IntFactoid> for &ElementProxy {\n    fn bex(self) -> Exp<IntFactoid> {\n        VariableExp(self.get_path().clone(), PhantomData).bex()\n    }\n}\n\nimpl IntoExp<IntFactoid> for i64 {\n    fn bex(self) -> Exp<IntFactoid> {\n        ConstantExp(self.into()).bex()\n    }\n}\n\nimpl IntoExp<IntFactoid> for IntFactoid {\n    fn bex(self) -> Exp<IntFactoid> {\n        ConstantExp(self).bex()\n    }\n}\n\nimpl<IE: IntoExp<IntFactoid>> Add<IE> for Exp<IntFactoid> {\n    type Output = Exp<IntFactoid>;\n    fn add(self, other: IE) -> Exp<IntFactoid> {\n        SumExp(vec![self.bex(), other.bex()]).bex()\n    }\n}\n\nimpl<IE: IntoExp<IntFactoid>> Sub<IE> for Exp<IntFactoid> {\n    type Output = Exp<IntFactoid>;\n    fn sub(self, other: IE) -> Exp<IntFactoid> {\n        SumExp(vec![self.bex(), -1 * other.bex()]).bex()\n    }\n}\n\nimpl Mul<Exp<IntFactoid>> for i64 {\n    type Output = Exp<IntFactoid>;\n    fn mul(self, other: Exp<IntFactoid>) -> Exp<IntFactoid> {\n        ScaledExp(self, other).bex()\n    }\n}\n\n// Dim\n\nimpl IntoExp<DimFact> for &DimProxy {\n    fn bex(self) -> Exp<DimFact> {\n        VariableExp(self.get_path().clone(), PhantomData).bex()\n    }\n}\n\nimpl IntoExp<DimFact> for TDim {\n    fn bex(self) -> Exp<DimFact> {\n        ConstantExp(self.into()).bex()\n    }\n}\n\nimpl IntoExp<DimFact> for &TDim {\n    fn bex(self) -> Exp<DimFact> {\n        ConstantExp(self.clone().into()).bex()\n    }\n}\n\nimpl<IE: IntoExp<DimFact>> Add<IE> for Exp<DimFact> {\n    type Output = Exp<DimFact>;\n    fn add(self, other: IE) -> Exp<DimFact> {\n        SumExp(vec![self.bex(), other.bex()]).bex()\n    }\n}\n\nimpl<IE: IntoExp<DimFact>> Sub<IE> for Exp<DimFact> {\n    type Output = Exp<DimFact>;\n    fn sub(self, other: IE) -> Exp<DimFact> {\n        SumExp(vec![self.bex(), -1 * other.bex()]).bex()\n    }\n}\n\nimpl Mul<Exp<DimFact>> for i64 {\n    type Output = Exp<DimFact>;\n    fn mul(self, other: Exp<DimFact>) -> Exp<DimFact> {\n        ScaledExp(self, other).bex()\n    }\n}\n\nimpl IntoExp<DimFact> for GenericFactoid<TDim> {\n    fn bex(self) -> Exp<GenericFactoid<TDim>> {\n        ConstantExp(self).bex()\n    }\n}\n\n// Cast to dim\n\npub trait ToDimExp {\n    fn to_dim(self) -> Exp<DimFact>;\n}\n\nimpl ToDimExp for Exp<IntFactoid> {\n    fn to_dim(self) -> Exp<DimFact> {\n        IntoDimExp(self).bex()\n    }\n}\n\n// Shape\n\nimpl IntoExp<ShapeFactoid> for ShapeFactoid {\n    fn bex(self) -> Exp<ShapeFactoid> {\n        ConstantExp(self).bex()\n    }\n}\n\nimpl IntoExp<ShapeFactoid> for ShapeProxy {\n    fn bex(self) -> Exp<ShapeFactoid> {\n        VariableExp(self.get_path().clone(), PhantomData).bex()\n    }\n}\n\nimpl IntoExp<ShapeFactoid> for &ShapeProxy {\n    fn bex(self) -> Exp<ShapeFactoid> {\n        VariableExp(self.get_path().clone(), PhantomData).bex()\n    }\n}\n\nimpl IntoExp<ShapeFactoid> for TVec<TDim> {\n    fn bex(self) -> Exp<ShapeFactoid> {\n        ConstantExp(self.into_iter().collect()).bex()\n    }\n}\n\n// Arc<Tensor>\n\nimpl IntoExp<ValueFact> for ValueProxy {\n    fn bex(self) -> Exp<ValueFact> {\n        VariableExp(self.get_path().clone(), PhantomData).bex()\n    }\n}\n\nimpl IntoExp<ValueFact> for &ValueProxy {\n    fn bex(self) -> Exp<ValueFact> {\n        VariableExp(self.get_path().clone(), PhantomData).bex()\n    }\n}\n\nimpl IntoExp<ValueFact> for Arc<Tensor> {\n    fn bex(self) -> Exp<ValueFact> {\n        ConstantExp(self.into()).bex()\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/rules/mod.rs",
    "content": "//! A fluent interface for the analyser.\n//!\n//! This interface provides proxies for the different properties of tensors.\n//! This allows inference rules to be stated in a clear, declarative fashion\n//! inside the `rules` method of each operator.\n//!\n//! Take these rules for instance:\n//! ```text\n//! solver.equals(inputs.len(), 2);\n//! solver.equals(inputs[0].datum_type, outputs[0].datum_type);\n//! ```\n//! Here, `inputs.len`, `inputs[0].datum_type` and `outputs[0].datum_type` don't\n//! actually hold the values of the length and datum_types, but instead act as\n//! declarative placeholders for these values.\n\n#[macro_export]\nmacro_rules! wrap {\n    ($($x:expr),*) => ({\n        vec![$( $crate::infer::rules::expr::IntoExp::bex($x) ),*]\n    });\n\n    ($($x:expr,)*) => (wrap![$($x),*]);\n}\n\nuse crate::infer::*;\n\nmod cache;\npub mod expr;\nmod path;\nmod proxies;\nmod solver;\n\npub use self::proxies::*;\npub use self::solver::Solver;\n\npub type InferenceResult = TractResult<()>;\n\npub trait InferenceRulesOp {\n    /// Registers the inference rules of the operator.\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        solver: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult;\n\n    fn as_op(&self) -> &dyn Op;\n    fn as_op_mut(&mut self) -> &mut dyn Op;\n\n    #[allow(unused_variables)]\n    fn to_typed(\n        &self,\n        source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        bail!(\"Node {} can not be typed\", node)\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(1)\n    }\n\n    #[allow(unused_variables)]\n    fn incorporate(\n        &self,\n        model: &InferenceModel,\n        node: &InferenceNode,\n    ) -> TractResult<Option<InferenceModelPatch>> {\n        Ok(None)\n    }\n}\n\nimpl<O: InferenceRulesOp + Op> InferenceOp for O {\n    fn infer_facts(\n        &mut self,\n        inputs: TVec<&InferenceFact>,\n        outputs: TVec<&InferenceFact>,\n        observed: TVec<&InferenceFact>,\n    ) -> TractResult<(TVec<InferenceFact>, TVec<InferenceFact>, TVec<InferenceFact>)> {\n        let inputs_proxy: TVec<TensorProxy> =\n            (0..inputs.len()).map(|ix| TensorProxy::new(tvec!(0, ix as isize).into())).collect();\n        let outputs_proxy: TVec<TensorProxy> =\n            (0..outputs.len()).map(|ix| TensorProxy::new(tvec!(1, ix as isize).into())).collect();\n\n        trace!(\"Building rules for {self:?}\");\n        let mut solver = Solver::default();\n        self.rules(&mut solver, &inputs_proxy, &outputs_proxy)?;\n        trace!(\"Applying rules for {self:?}\");\n        let (input, output) = solver.infer_facts((inputs, outputs))?;\n        trace!(\"Solver done\");\n        Ok((input, output, observed.into_iter().cloned().collect()))\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        self.nboutputs()\n    }\n\n    fn observe_outlets(\n        &self,\n        _model: &InferenceModel,\n        _node: &InferenceNode,\n    ) -> TractResult<Vec<OutletId>> {\n        Ok(vec![])\n    }\n\n    fn as_op(&self) -> &dyn Op {\n        self.as_op()\n    }\n\n    fn as_op_mut(&mut self) -> &mut dyn Op {\n        self.as_op_mut()\n    }\n\n    fn to_typed(\n        &self,\n        source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        self.to_typed(source, node, target, mapping)\n    }\n\n    fn incorporate(\n        &self,\n        model: &InferenceModel,\n        node: &InferenceNode,\n    ) -> TractResult<Option<InferenceModelPatch>> {\n        self.incorporate(model, node)\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/rules/path.rs",
    "content": "use std::fmt;\n\nuse tract_num_traits::ToPrimitive;\n\nuse crate::infer::*;\n\nuse self::super::super::factoid::*;\nuse self::super::expr::*;\nuse self::super::solver::Context;\n\n/// A symbolic path for a value.\n#[derive(PartialEq, Eq, Clone)]\npub struct Path(TVec<isize>);\n\nimpl From<TVec<isize>> for Path {\n    fn from(v: TVec<isize>) -> Path {\n        Path(v)\n    }\n}\n\nimpl From<Vec<isize>> for Path {\n    fn from(v: Vec<isize>) -> Path {\n        Path(v.into())\n    }\n}\n\nimpl ::std::ops::Deref for Path {\n    type Target = [isize];\n    fn deref(&self) -> &[isize] {\n        &self.0\n    }\n}\n\nimpl fmt::Debug for Path {\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        debug_path(self, formatter)\n    }\n}\n\n/// Returns the value at the given path (starting from a context).\npub fn get_path(context: &Context, path: &[isize]) -> TractResult<Wrapped> {\n    match path[0] {\n        0 => get_tensorfacts_path(&context.inputs, &path[1..]),\n        1 => get_tensorfacts_path(&context.outputs, &path[1..]),\n        _ => bail!(\n            \"The first component of path {:?} should be 0 (for the `inputs` \\\n             set of facts) or 1 (for the `outputs` set of facts).\",\n            path\n        ),\n    }\n}\n\n/// Sets the value at the given path (starting from a context).\npub fn set_path(context: &mut Context, path: &[isize], value: Wrapped) -> TractResult<()> {\n    match path[0] {\n        0 => set_tensorfacts_path(&mut context.inputs, &path[1..], value),\n        1 => set_tensorfacts_path(&mut context.outputs, &path[1..], value),\n        _ => bail!(\n            \"The first component of path {:?} should be 0 (for the `inputs` \\\n             set of facts) or 1 (for the `outputs` set of facts).\",\n            path\n        ),\n    }\n}\n\nfn debug_path(path: &[isize], formatter: &mut fmt::Formatter) -> fmt::Result {\n    write!(\n        formatter,\n        \"{}\",\n        match path[0] {\n            0 => \"inputs\",\n            1 => \"outputs\",\n            _ => \"buggy_path\",\n        }\n    )?;\n    debug_tensorfacts_path(&path[1..], formatter)\n}\n\n/// Returns the value at the given path (starting from a set of InferenceFacts).\nfn get_tensorfacts_path(facts: &TVec<InferenceFact>, path: &[isize]) -> TractResult<Wrapped> {\n    match path {\n        // Get the number of facts in the set.\n        [-1] => Ok(facts.len().wrap()),\n\n        slice if slice[0] >= 0 => {\n            let k = slice[0].to_usize().unwrap(); // checked\n\n            if k < facts.len() {\n                get_tensorfact_path(&facts[k], &slice[1..])\n            } else {\n                bail!(\n                    \"There are only {:?} facts in the given set, so the index \\\n                     {:?} is not valid.\",\n                    facts.len(),\n                    k\n                )\n            }\n        }\n\n        _ => bail!(\n            \"The first component of subpath {:?} should either be -1 (for \\\n             the number of facts in the set) or a valid fact index.\",\n            path\n        ),\n    }\n}\n\n/// Sets the value at the given path (starting from a set of InferenceFacts).\nfn set_tensorfacts_path(\n    facts: &mut TVec<InferenceFact>,\n    path: &[isize],\n    value: Wrapped,\n) -> TractResult<()> {\n    match path {\n        // Set the number of facts in the set.\n        [-1] => {\n            // Conversion is checked.\n            let value =\n                IntFactoid::from_wrapped(value)?.concretize().map(|v| v.to_usize().unwrap());\n\n            if value.is_some() && value.unwrap() != facts.len() {\n                bail!(\n                    \"Can't set the length of the given set of facts to {:?} \\\n                     because it already has length {:?}.\",\n                    value,\n                    facts.len()\n                );\n            }\n\n            Ok(())\n        }\n\n        slice if slice[0] >= 0 => {\n            // Conversion is checked.\n            let k = slice[0].to_usize().unwrap();\n\n            if k < facts.len() {\n                set_tensorfact_path(&mut facts[k], &path[1..], value)\n            } else {\n                bail!(\n                    \"There are only {:?} facts in the given set, so the index \\\n                     {:?} is not valid.\",\n                    facts.len(),\n                    k\n                )\n            }\n        }\n\n        _ => bail!(\n            \"The first component of subpath {:?} should either be -1 (for \\\n             the number of facts in the set) or a valid fact index.\",\n            path\n        ),\n    }\n}\n\nfn debug_tensorfacts_path(path: &[isize], formatter: &mut fmt::Formatter) -> fmt::Result {\n    match path[0] {\n        -1 => write!(formatter, \".len\"),\n        n => {\n            write!(formatter, \"[{n}]\")?;\n            debug_tensorfact_path(&path[1..], formatter)\n        }\n    }\n}\n\n/// Returns the value at the given path (starting from a InferenceFact).\nfn get_tensorfact_path(fact: &InferenceFact, path: &[isize]) -> TractResult<Wrapped> {\n    match path {\n        // Get the type of the InferenceFact.\n        [0] => Ok(fact.datum_type.wrap()),\n\n        // Get the rank of the InferenceFact.\n        [1] => Ok(fact.shape.rank().wrap()),\n\n        slice if slice[0] == 2 => get_shape_path(&fact.shape, &slice[1..]),\n        slice if slice[0] == 3 => get_value_path(&fact.value, &slice[1..]),\n\n        _ => bail!(\n            \"The subpath {:?} should start with 0, 1, 2 or 3 (for the type, \\\n             rank, dimension or value of the fact respectively).\",\n            path\n        ),\n    }\n}\n\n/// Sets the value at the given path (starting from a InferenceFact).\nfn set_tensorfact_path(\n    fact: &mut InferenceFact,\n    path: &[isize],\n    value: Wrapped,\n) -> TractResult<()> {\n    match path {\n        // Set the type of the InferenceFact.\n        [0] => {\n            let value = TypeFactoid::from_wrapped(value)?;\n            fact.datum_type = value.unify(&fact.datum_type)?;\n            Ok(())\n        }\n\n        // Set the rank of the InferenceFact.\n        [1] => {\n            if let Some(k) = IntFactoid::from_wrapped(value)?.concretize() {\n                if k >= 0 {\n                    let k = k.to_usize().unwrap();\n                    fact.shape = fact.shape.unify(&ShapeFactoid::closed(tvec![dimfact!(_); k]))?;\n                } else {\n                    bail!(\"Infered a negative rank ({})\", k)\n                }\n            }\n\n            Ok(())\n        }\n\n        // Set the whole shape of the InferenceFact.\n        [2] => {\n            let shape = ShapeFactoid::from_wrapped(value)?;\n            fact.shape = shape.unify(&fact.shape)?;\n\n            Ok(())\n        }\n\n        // Set a precise dimension of the InferenceFact.\n        [2, k] => {\n            let k = k.to_usize().unwrap();\n            let dim = DimFact::from_wrapped(value)?;\n\n            let mut dims = tvec![dimfact!(_); k];\n            dims.push(dim);\n\n            fact.shape = fact.shape.unify(&ShapeFactoid::open(dims))?;\n\n            Ok(())\n        }\n\n        // Set full InferenceFact value, also unifying type and shape.\n        [3] => {\n            let value = ValueFact::from_wrapped(value)?;\n            fact.value = fact.value.unify(&value)?;\n            if let Some(tensor) = fact.value.concretize() {\n                fact.shape = fact.shape.unify(&ShapeFactoid::from(tensor.shape()))?;\n                fact.datum_type = fact.datum_type.unify(&TypeFactoid::from(tensor.datum_type()))?;\n            }\n            Ok(())\n        }\n\n        slice if slice[0] == 3 => {\n            debug!(\"FIXME Unimplemented set_value_path for individual value\");\n            Ok(())\n        }\n\n        _ => bail!(\n            \"The subpath {:?} should start with 0, 1, 2 or 3 (for the type, \\\n             rank, dimension or value of the fact respectively).\",\n            path\n        ),\n    }\n}\n\nfn debug_tensorfact_path(path: &[isize], formatter: &mut fmt::Formatter) -> fmt::Result {\n    match path {\n        [] => Ok(()),\n        [0] => write!(formatter, \".datum_type\"),\n        [1] => write!(formatter, \".rank\"),\n        [2] => write!(formatter, \".shape\"),\n        [2, k] => write!(formatter, \".shape[{k}]\"),\n        slice if slice[0] == 3 => debug_value_path(&path[1..], formatter),\n        _ => write!(formatter, \".invalid\"),\n    }\n}\n\n/// Returns the shape or dimension at the given path (starting from a ShapeFactoid).\nfn get_shape_path(shape: &ShapeFactoid, path: &[isize]) -> TractResult<Wrapped> {\n    match path {\n        // Get the whole shape.\n        [] => Ok(shape.clone().wrap()),\n\n        // Get a precise dimension.\n        [k] => {\n            let k = k.to_usize().unwrap();\n            if let Some(d) = shape.dims().nth(k) {\n                Ok(d.clone().wrap())\n            } else if shape.is_open() {\n                Ok(dimfact!(_).wrap())\n            } else {\n                bail!(\"{:?} has no {:?}-th dimension.\", shape, k);\n            }\n        }\n\n        _ => bail!(\n            \"The subpath {:?} for the shape should either be [] (for the \\\n             entire shape) or [k] with k the index of a dimension.\",\n            path\n        ),\n    }\n}\n\n/// Returns the value at the given path (starting from a ValueFact).\nfn get_value_path(value: &ValueFact, path: &[isize]) -> TractResult<Wrapped> {\n    trace!(\"get_value_path path:{path:?} value:{value:?}\");\n    // Return the whole tensor.\n    if path == [-1] || path.is_empty() {\n        return Ok(value.clone().wrap());\n    }\n\n    let returns = match value.concretize() {\n        None => Ok(IntFactoid::default().wrap()),\n        Some(tensor) => {\n            let path = path.iter().map(|i| *i as usize).collect::<TVec<usize>>();\n            if tensor.rank() == 0 && path == tvec!(0) {\n                Ok(tensor.cast_to_scalar::<i64>()?.wrap())\n            } else {\n                Ok(tensor.cast_to::<i64>()?.to_plain_array_view::<i64>()?[&*path].wrap())\n            }\n        }\n    };\n    trace!(\"returns: {returns:?}\");\n    returns\n}\n\nfn debug_value_path(path: &[isize], formatter: &mut fmt::Formatter) -> fmt::Result {\n    for p in path {\n        write!(formatter, \"[{p}]\")?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "hir/src/infer/rules/proxies.rs",
    "content": "use std::fmt;\nuse std::ops::Index;\n\nuse crate::tract_num_traits::ToPrimitive;\n\nuse crate::infer::factoid::*;\n\nuse self::super::cache::Cache;\nuse self::super::expr::Output;\nuse self::super::path::Path;\n\n/// A proxy for any value.\npub trait Proxy {\n    /// Returns the symbolic path to the value.\n    ///\n    /// Take the `inputs[0].shape[1]` proxy for instance: it represents the\n    /// second dimension of the shape of the first input. Because we encode\n    /// the \"inputs\" vectors as `0`, and the `shape` field as `2`, the path\n    /// for this proxy will be `vec![0, 0, 2, 1]`.\n    fn get_path(&self) -> &Path;\n}\n\n/// A proxy which can be used in a solver rule.\npub trait ComparableProxy: Proxy {\n    type Output: Output;\n}\n\n/// Generates the get_path method for structs which have a `path` field.\nmacro_rules! impl_proxy {\n    ($struct:ident) => {\n        impl Proxy for $struct {\n            /// Returns the symbolic path to the value.\n            fn get_path(&self) -> &Path {\n                &self.path\n            }\n        }\n\n        impl fmt::Debug for $struct {\n            fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n                write!(formatter, \"{:?}\", self.get_path())\n            }\n        }\n\n        impl<'a> Proxy for &'a $struct {\n            /// Returns the symbolic path to the value.\n            fn get_path(&self) -> &Path {\n                &self.path\n            }\n        }\n    };\n}\n\n/// Implements the ComparableProxy trait for the proxy and references to it.\nmacro_rules! impl_comparable_proxy {\n    ($struct:ident, $output:ident) => {\n        impl ComparableProxy for $struct {\n            type Output = $output;\n        }\n        impl<'a> ComparableProxy for &'a $struct {\n            type Output = $output;\n        }\n    };\n}\n\n/// A proxy for any integer-like value.\n#[derive(new)]\npub struct IntProxy {\n    path: Path,\n}\n\nimpl_proxy!(IntProxy);\nimpl_comparable_proxy!(IntProxy, IntFactoid);\n\n/// A proxy for a tensor.\n///\n/// This is used for rules involving the datum_type, rank, shape or value of a\n/// tensor. Here are a few examples of constraints that can be expressed:\n/// ```text\n/// solver.equals(input.datum_type, DTYPE_I32)\n/// solver.equals(input.rank, 2)\n/// solver.equals(input.shape[1], output.value[0][1])\n/// ```\npub struct TensorProxy {\n    pub datum_type: TypeProxy,\n    pub rank: IntProxy,\n    pub shape: ShapeProxy,\n    pub value: ValueProxy,\n    path: Path,\n}\n\nimpl TensorProxy {\n    /// Creates a new TensorProxy instance.\n    pub fn new(path: Path) -> TensorProxy {\n        TensorProxy {\n            datum_type: TypeProxy::new([&path[..], &[0]].concat().into()),\n            rank: IntProxy::new([&path[..], &[1]].concat().into()),\n            shape: ShapeProxy::new([&path[..], &[2]].concat().into()),\n            value: ValueProxy::new([&path[..], &[3]].concat().into()),\n            path,\n        }\n    }\n}\n\nimpl_proxy!(TensorProxy);\n\n/// A proxy for a tensor datum_type.\n#[derive(new)]\npub struct TypeProxy {\n    path: Path,\n}\n\nimpl_proxy!(TypeProxy);\nimpl_comparable_proxy!(TypeProxy, TypeFactoid);\n\n/// A proxy for a tensor shape.\npub struct ShapeProxy {\n    dims: Cache<usize, DimProxy>,\n    path: Path,\n}\n\nimpl ShapeProxy {\n    /// Creates a new ShapeProxy instance.\n    pub fn new(path: Path) -> ShapeProxy {\n        ShapeProxy { dims: Cache::new(), path }\n    }\n}\n\nimpl_proxy!(ShapeProxy);\nimpl_comparable_proxy!(ShapeProxy, ShapeFactoid);\n\nimpl Index<usize> for ShapeProxy {\n    type Output = DimProxy;\n\n    /// Returns the DimProxy corresponding to the given index.\n    fn index(&self, index: usize) -> &DimProxy {\n        let path = [&self.path[..], &[index.to_isize().unwrap()]].concat();\n        self.dims.get(index, || DimProxy::new(path.into()))\n    }\n}\n\n/// A proxy for a dimension of a shape.\n#[derive(new)]\npub struct DimProxy {\n    path: Path,\n}\n\nimpl_proxy!(DimProxy);\nimpl_comparable_proxy!(DimProxy, DimFact);\n\n/// A proxy for the whole tensor value.\n///\n/// This proxy is a bit special as it allows arbitrarily nested indexing, so\n/// that writing something like ```input.value[1][6][2]``` will always work.\n/// To make this work, each ValueProxy holds a cache which will generate new\n/// ValueProxys for nested items on the fly and store them.\npub struct ValueProxy {\n    sub: Cache<usize, ElementProxy>,\n    root: IntProxy,\n    path: Path,\n}\n\nimpl ValueProxy {\n    /// Creates a new RootValueProxy instance.\n    pub fn new(path: Path) -> ValueProxy {\n        let root = IntProxy::new([&path[..], &[-1]].concat().into());\n        ValueProxy { sub: Cache::new(), root, path }\n    }\n}\n\nimpl Index<()> for ValueProxy {\n    type Output = IntProxy;\n\n    /// Returns the RootValueProxy corresponding to the given index.\n    fn index(&self, _: ()) -> &IntProxy {\n        &self.root\n    }\n}\n\nimpl Index<usize> for ValueProxy {\n    type Output = ElementProxy;\n\n    /// Returns the ElementProxy corresponding to the given index.\n    fn index(&self, index: usize) -> &ElementProxy {\n        let path = [&self.path[..], &[index.to_isize().unwrap()]].concat();\n        self.sub.get(index, || ElementProxy::new(path.into()))\n    }\n}\n\nimpl_proxy!(ValueProxy);\nimpl_comparable_proxy!(ValueProxy, ValueFact);\n\n/// A proxy for a tensor element.\npub struct ElementProxy {\n    sub: Cache<usize, ElementProxy>,\n    path: Path,\n}\n\nimpl ElementProxy {\n    /// Creates a new ElementProxy instance.\n    pub fn new(path: Path) -> ElementProxy {\n        ElementProxy { sub: Cache::new(), path }\n    }\n}\n\nimpl Index<usize> for ElementProxy {\n    type Output = ElementProxy;\n\n    /// Returns the ElementProxy corresponding to the given index.\n    fn index(&self, index: usize) -> &ElementProxy {\n        let path = [&self.path[..], &[index.to_isize().unwrap()]].concat();\n        self.sub.get(index, || ElementProxy::new(path.into()))\n    }\n}\n\nimpl_proxy!(ElementProxy);\nimpl_comparable_proxy!(ElementProxy, IntFactoid);\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_tensor_proxy_datum_type() {\n        let input = TensorProxy::new(vec![0, 0].into());\n        assert_eq!(input.datum_type.get_path(), &vec![0, 0, 0].into());\n    }\n\n    #[test]\n    fn test_tensor_proxy_rank() {\n        let input = TensorProxy::new(vec![0, 0].into());\n        assert_eq!(input.rank.get_path(), &vec![0, 0, 1].into());\n    }\n\n    #[test]\n    fn test_tensor_proxy_shape() {\n        let input = TensorProxy::new(vec![0, 0].into());\n        assert_eq!(input.shape[0].get_path(), &vec![0, 0, 2, 0].into());\n        assert_eq!(input.shape[2].get_path(), &vec![0, 0, 2, 2].into());\n    }\n\n    #[test]\n    fn test_tensor_proxy_value() {\n        let input = TensorProxy::new(vec![0, 0].into());\n        assert_eq!(input.value.get_path(), &vec![0, 0, 3].into());\n        assert_eq!(input.value[()].get_path(), &vec![0, 0, 3, -1].into());\n        assert_eq!(input.value[0].get_path(), &vec![0, 0, 3, 0].into());\n        assert_eq!(input.value[0][1].get_path(), &vec![0, 0, 3, 0, 1].into());\n        assert_eq!(input.value[1][2][3].get_path(), &vec![0, 0, 3, 1, 2, 3].into());\n    }\n}\n"
  },
  {
    "path": "hir/src/infer/rules/solver.rs",
    "content": "use std::fmt;\nuse std::ops::{Add, Neg};\n\nuse tract_num_traits::Zero;\n\nuse crate::infer::*;\n\nuse self::super::InferenceResult;\nuse self::super::expr::{Exp, IntoExp, Output, TExp};\nuse self::super::path::{Path, get_path, set_path};\n\n/// A structure that holds the current sets of InferenceFacts.\n///\n/// This is used during inference (see `Solver::infer`) to let rules compute\n/// the value of expressions which involve tensor properties.\n#[derive(Debug, new)]\npub struct Context {\n    pub inputs: TVec<InferenceFact>,\n    pub outputs: TVec<InferenceFact>,\n}\n\nimpl Context {\n    /// Returns the current value of the variable at the given path.\n    pub fn get<T: Output>(&self, path: &Path) -> TractResult<T> {\n        let value = get_path(self, &path[..])?;\n        T::from_wrapped(value)\n    }\n\n    /// Tries to set the value of the variable at the given path.\n    pub fn set<T: Output>(&mut self, path: &Path, value: T) -> TractResult<()> {\n        set_path(self, &path[..], T::into_wrapped(value))?;\n        Ok(())\n    }\n}\n\n/// A rule that can be applied by the solver.\npub trait Rule<'rules>: fmt::Debug {\n    /// Tries to apply the rule to a given context.\n    ///\"\"\n    /// The method must return Ok(true) if the rule was applied successfully\n    /// (meaning that the Context was mutated), or Ok(false) if the rule was\n    /// not applied but didn't generate any errors.\n    fn apply(\n        &self,\n        context: &mut Context,\n    ) -> TractResult<(bool, Vec<Box<dyn Rule<'rules> + 'rules>>)>;\n\n    /// Returns the paths that the rule depends on.\n    fn get_paths(&self) -> Vec<&Path>;\n}\n\n/// The `equals` rule.\n/// It states that the given expressions must all be equal.\n///\n/// It can be added to the solver via the following two methods:\n/// ```text\n/// solver.equals(a, b);\n/// solver.equals_all(vec![a, b, ...]);\n/// ```\nstruct EqualsRule<T: Output + Factoid> {\n    items: Vec<Exp<T>>,\n}\n\nimpl<T: Output + Factoid> EqualsRule<T> {\n    /// Creates a new EqualsRule instance.\n    pub fn new(items: Vec<Exp<T>>) -> EqualsRule<T> {\n        EqualsRule { items }\n    }\n}\n\nimpl<'rules, T: Output + Factoid> Rule<'rules> for EqualsRule<T> {\n    /// Tries to apply the rule to a given context.\n    fn apply(\n        &self,\n        context: &mut Context,\n    ) -> TractResult<(bool, Vec<Box<dyn Rule<'rules> + 'rules>>)> {\n        let value =\n            self.items.iter().try_fold(T::default(), |acc, f| acc.unify(&f.get(context)?))?;\n        let mut changed = false;\n        for item in &self.items {\n            changed |= item.set(context, value.clone())?;\n        }\n        Ok((changed, vec![]))\n    }\n\n    /// Returns the paths that the rule depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        self.items.iter().flat_map(|e| e.get_paths()).collect()\n    }\n}\n\nimpl<T: Output + Factoid> fmt::Debug for EqualsRule<T> {\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        write!(formatter, \"{:?}\", self.items[0])?;\n        for item in &self.items[1..] {\n            write!(formatter, \" == {item:?}\")?;\n        }\n        Ok(())\n    }\n}\n\n/// The `equals_zero` rule.\n/// It states that the given expression must equal zero.\n///\n/// It can be added to the solver via the following method:\n/// ```text\n/// solver.equals_zero(vec![a, b, ...]);\n/// ```\nstruct EqualsZeroRule<F>(Exp<F>)\nwhere\n    F: Factoid + Zero + Add<F, Output = F> + Neg<Output = F> + Clone + ::std::fmt::Debug + Output;\n\nimpl<'rules, F> Rule<'rules> for EqualsZeroRule<F>\nwhere\n    F: Factoid + Zero + Add<F, Output = F> + Neg<Output = F> + Clone + ::std::fmt::Debug + Output,\n{\n    /// Tries to apply the rule to a given context.\n    fn apply(\n        &self,\n        context: &mut Context,\n    ) -> TractResult<(bool, Vec<Box<dyn Rule<'rules> + 'rules>>)> {\n        Ok((self.0.set(context, F::zero())?, vec![]))\n    }\n\n    /// Returns the paths that the rule depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        self.0.get_paths()\n    }\n}\n\nimpl<F> fmt::Debug for EqualsZeroRule<F>\nwhere\n    F: Factoid + Zero + Add<F, Output = F> + Neg<Output = F> + Clone + ::std::fmt::Debug + Output,\n{\n    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n        self.0.fmt(formatter)?;\n        write!(formatter, \" == 0\")\n    }\n}\n\n/// The `with` rule.\n/// It allows you to add more rules to the solver using what is known about an\n/// expression.using a closure that takes the value as parameter.\n///\n/// It can be added to the solver via the following method:\n/// ```text\n/// solver.with(input.rank, |solver, ir|\n///     // Add more rules to `solver` here.\n/// );\n/// ```\n#[allow(clippy::type_complexity)]\npub struct WithRule<'rules, T: Factoid> {\n    pub item: Exp<T>,\n    pub closure: Box<dyn Fn(&mut Solver<'rules>, T) -> InferenceResult + 'rules>,\n}\n\nimpl<'rules, T: Output + Factoid> WithRule<'rules, T> {\n    /// Creates a new GivenRule instance.\n    pub fn new<F>(item: Exp<T>, closure: F) -> WithRule<'rules, T>\n    where\n        F: Fn(&mut Solver<'rules>, T) -> InferenceResult + 'rules,\n    {\n        let closure = Box::new(closure);\n        WithRule { item, closure }\n    }\n}\n\nimpl<'rules, T: Output + Factoid> Rule<'rules> for WithRule<'rules, T> {\n    /// Tries to apply the rule to a given context.\n    fn apply(\n        &self,\n        context: &mut Context,\n    ) -> TractResult<(bool, Vec<Box<dyn Rule<'rules> + 'rules>>)> {\n        let value = self.item.get(context)?;\n        trace!(\"    With rule: {:?} is {:?}\", self.item, value);\n        let mut solver = Solver::default();\n        (self.closure)(&mut solver, value)?;\n        Ok((true, solver.take_rules()))\n    }\n\n    /// Returns the paths that the rule depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        self.item.get_paths()\n    }\n}\n\nimpl<T: Output + Factoid> fmt::Debug for WithRule<'_, T> {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"WithRule {{ {:?} }}\", self.item)\n    }\n}\n\n/// The `given` rule.\n/// It allows you to add more rules to the solver once the value of a given\n/// expression is known, using a closure that takes the value as parameter.\n///\n/// It can be added to the solver via the following method:\n/// ```text\n/// solver.given(input.rank, |solver, ir|\n///     // Add more rules to `solver` here.\n/// );\n/// ```\n#[allow(clippy::type_complexity)]\npub struct GivenRule<'rules, T: Factoid> {\n    pub item: Exp<T>,\n    pub closure: Box<dyn Fn(&mut Solver<'rules>, T::Concrete) -> InferenceResult + 'rules>,\n}\n\nimpl<'rules, T: Output + Factoid> GivenRule<'rules, T> {\n    /// Creates a new GivenRule instance.\n    pub fn new<F>(item: Exp<T>, closure: F) -> GivenRule<'rules, T>\n    where\n        F: Fn(&mut Solver<'rules>, T::Concrete) -> InferenceResult + 'rules,\n    {\n        let closure = Box::new(closure);\n\n        GivenRule { item, closure }\n    }\n}\n\nimpl<'rules, T: Output + Factoid> Rule<'rules> for GivenRule<'rules, T> {\n    /// Tries to apply the rule to a given context.\n    fn apply(\n        &self,\n        context: &mut Context,\n    ) -> TractResult<(bool, Vec<Box<dyn Rule<'rules> + 'rules>>)> {\n        let value = self.item.get(context)?;\n\n        if let Some(value) = value.concretize() {\n            trace!(\"    Given rule: {:?} is {:?}\", self.item, value);\n            // We create a new solver instance, which will be populated with\n            // new rules by the code inside the closure.\n            let mut solver = Solver::default();\n\n            (self.closure)(&mut solver, value)?;\n\n            Ok((true, solver.take_rules()))\n        } else {\n            trace!(\n                \"In {:?}, failed to convert {:?} to expected type\",\n                self,\n                self.item.get(context)?.wrap()\n            );\n            Ok((false, vec![]))\n        }\n    }\n\n    /// Returns the paths that the rule depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        self.item.get_paths()\n    }\n}\n\nimpl<T: Output + Factoid> fmt::Debug for GivenRule<'_, T> {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"GivenRule {{ {:?} }}\", self.item)\n    }\n}\n\n/// The `given` rule.\n/// It allows you to add more rules to the solver once the value of a given\n/// expression is known, using a closure that takes the value as parameter.\n///\n/// It can be added to the solver via the following method:\n/// ```text\n/// solver.given(input.rank, |solver, ir|\n///     // Add more rules to `solver` here.\n/// );\n/// ```\n#[allow(clippy::type_complexity)]\npub struct GivenAllRule<'rules, T: Factoid> {\n    pub items: Vec<Exp<T>>,\n    pub closure: Box<dyn Fn(&mut Solver<'rules>, Vec<T::Concrete>) -> InferenceResult + 'rules>,\n}\n\nimpl<'rules, T: Output + Factoid> GivenAllRule<'rules, T> {\n    /// Creates a new GivenRule instance.\n    pub fn new<F>(items: Vec<Exp<T>>, closure: F) -> GivenAllRule<'rules, T>\n    where\n        F: Fn(&mut Solver<'rules>, Vec<T::Concrete>) -> InferenceResult + 'rules,\n    {\n        let closure = Box::new(closure);\n\n        GivenAllRule { items, closure }\n    }\n}\n\nimpl<'rules, T: Output + Factoid> Rule<'rules> for GivenAllRule<'rules, T> {\n    /// Tries to apply the rule to a given context.\n    fn apply(\n        &self,\n        context: &mut Context,\n    ) -> TractResult<(bool, Vec<Box<dyn Rule<'rules> + 'rules>>)> {\n        let values: Vec<T> =\n            self.items.iter().map(|it| it.get(context)).collect::<TractResult<Vec<T>>>()?;\n        let concrete: Vec<_> = values.iter().filter_map(|it| it.concretize()).collect();\n\n        if concrete.len() == self.items.len() {\n            trace!(\"    Given all rule: {:?} is {:?}\", self.items, values);\n            // We create a new solver instance, which will be populated with\n            // new rules by the code inside the closure.\n            let mut solver = Solver::default();\n            (self.closure)(&mut solver, concrete)?;\n            Ok((true, solver.take_rules()))\n        } else {\n            Ok((false, vec![]))\n        }\n    }\n\n    /// Returns the paths that the rule depends on.\n    fn get_paths(&self) -> Vec<&Path> {\n        self.items.iter().flat_map(|it| it.get_paths()).collect()\n    }\n}\n\nimpl<T: Output + Factoid> fmt::Debug for GivenAllRule<'_, T> {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"GivenAllRule {:?}\", self.items)\n    }\n}\n\n/// A declarative constraint solver for tensors.\n#[derive(Default)]\npub struct Solver<'rules> {\n    // The rules used by the solver.\n    pub rules: Vec<Box<dyn Rule<'rules> + 'rules>>,\n}\n\nimpl<'rules> Solver<'rules> {\n    /// Consumes the solver and returns the rules that it uses.\n    pub fn take_rules(self) -> Vec<Box<dyn Rule<'rules> + 'rules>> {\n        self.rules\n    }\n\n    /// Runs the solver on a set of InferenceFacts.\n    ///\n    /// This method returns:\n    /// - Err(_) if a constraint couldn't be satisfied.\n    /// - Ok(None) if no more information about tensors could be deduced.\n    /// - Ok(Some(facts)) otherwise, with `facts` the new InferenceFacts.\n    pub fn infer_facts(\n        self,\n        facts: (TVec<&InferenceFact>, TVec<&InferenceFact>),\n    ) -> TractResult<(TVec<InferenceFact>, TVec<InferenceFact>)> {\n        let mut context = Context::new(\n            facts.0.into_iter().cloned().collect(),\n            facts.1.into_iter().cloned().collect(),\n        );\n\n        // Apply the rules until reaching a fixed point.\n        let mut changed = true;\n        let mut added_rules = vec![];\n        let mut rules: Vec<_> = self.rules.into_iter().map(|r| (false, r)).collect();\n\n        while changed {\n            changed = false;\n\n            for (used, rule) in &mut rules {\n                // Don't try to apply rules which have already been used.\n                if *used {\n                    continue;\n                }\n\n                trace!(\"  Applying rule {rule:?}\");\n                let (step_used, mut step_added) =\n                    rule.apply(&mut context).with_context(|| format!(\"Applying rule {rule:?}\"))?;\n                *used |= step_used;\n\n                // There is a change if the rule was used, or if it added new rules.\n                changed |= step_used;\n                changed |= step_added.len() > 0;\n\n                added_rules.append(&mut step_added);\n            }\n\n            trace!(\"  Applying all rules\");\n\n            for rule in added_rules.drain(..) {\n                rules.push((false, rule));\n            }\n        }\n\n        trace!(\"  Solver exiting {context:?}\");\n        Ok((context.inputs, context.outputs))\n    }\n\n    /// Ensures that two expressions are equal.\n    ///\n    /// For instance, one could write:\n    /// ```text\n    /// solver.equals(outputs[0].rank, inputs[1].shape[0]);\n    /// solver.equals(outputs[1].rank, 3);\n    /// ```\n    pub fn equals<T, A, B>(&mut self, left: A, right: B) -> InferenceResult\n    where\n        T: Output + Factoid + 'static,\n        A: IntoExp<T>,\n        B: IntoExp<T>,\n    {\n        let items: Vec<Exp<T>> = vec![left.bex(), right.bex()];\n\n        let rule = EqualsRule::new(items);\n        self.rules.push(Box::new(rule));\n        Ok(())\n    }\n\n    /// Ensures that several expressions are equal.\n    ///\n    /// For instance, one could write:\n    /// ```text\n    /// solver.equals_all(vec![\n    ///     outputs[0].rank.into(),\n    ///     inputs[1].shape[0].into(),\n    ///     3.into(),\n    /// ]);\n    /// ```\n    pub fn equals_all<T>(&mut self, items: Vec<Exp<T>>) -> InferenceResult\n    where\n        T: Output + Factoid + 'static,\n    {\n        let rule = EqualsRule::new(items);\n        self.rules.push(Box::new(rule));\n        Ok(())\n    }\n\n    /// Ensures that the sum of several expressions equals zero.\n    ///\n    /// For instance, one could write:\n    /// ```text\n    /// solver.equals_zero(vec![\n    ///     outputs[0].rank.into(),\n    ///     outputs[1].rank.into(),\n    ///     (-1, inputs[1].shape[0]).into(),\n    /// ]);\n    /// ```\n    pub fn equals_zero<F>(&mut self, items: Exp<F>) -> InferenceResult\n    where\n        F: Factoid\n            + Zero\n            + Add<F, Output = F>\n            + Neg<Output = F>\n            + Clone\n            + ::std::fmt::Debug\n            + Output\n            + 'rules,\n    {\n        let rule = EqualsZeroRule(items);\n        self.rules.push(Box::new(rule));\n        Ok(())\n    }\n\n    /// Adds rules to the solver with a partial value.\n    ///\n    /// For instance, one could write:\n    /// ```text\n    /// solver.given(input.rank, |solver, ir|\n    ///     (0..ir).map(|i| solver.equals(input.shape[ir], 0))\n    /// );\n    /// ```\n    pub fn with<T, A, F>(&mut self, item: A, closure: F) -> InferenceResult\n    where\n        T: Factoid + Output + 'static,\n        A: IntoExp<T>,\n        F: Fn(&mut Solver<'rules>, T) -> InferenceResult + 'rules,\n    {\n        let rule = WithRule::new(item.bex(), closure);\n        self.rules.push(Box::new(rule));\n        Ok(())\n    }\n\n    /// Adds rules to the solver once the value of an expression is known.\n    ///\n    /// For instance, one could write:\n    /// ```text\n    /// solver.given(input.rank, |solver, ir|\n    ///     (0..ir).map(|i| solver.equals(input.shape[ir], 0))\n    /// );\n    /// ```\n    pub fn given<T, A, F>(&mut self, item: A, closure: F) -> InferenceResult\n    where\n        T: Factoid + Output + 'static,\n        A: IntoExp<T>,\n        F: Fn(&mut Solver<'rules>, T::Concrete) -> InferenceResult + 'rules,\n    {\n        let rule = GivenRule::new(item.bex(), closure);\n        self.rules.push(Box::new(rule));\n        Ok(())\n    }\n\n    /// Adds rules to the solver once the value of all expressions are known.\n    ///\n    /// For instance, one could write:\n    /// ```text\n    /// solver.given(input.rank, |solver, ir|\n    ///     (0..ir).map(|i| solver.equals(input.shape[ir], 0))\n    /// );\n    /// ```\n    pub fn given_all<T, I, A, F>(&mut self, items: I, closure: F) -> InferenceResult\n    where\n        T: Factoid + Output + 'static,\n        A: IntoExp<T>,\n        I: IntoIterator<Item = A>,\n        F: Fn(&mut Solver<'rules>, Vec<T::Concrete>) -> InferenceResult + 'rules,\n    {\n        let rule = GivenAllRule::new(items.into_iter().map(|it| it.bex()).collect(), closure);\n        self.rules.push(Box::new(rule));\n        Ok(())\n    }\n}\n\nmacro_rules! given_tuple {\n    ($Name:ident, $name:ident, $($id:ident),*) => {\n        #[allow(non_camel_case_types)]\n        pub struct $Name<'rules, $($id: Factoid),*> {\n            $(pub $id: Exp<$id>,)*\n            pub closure: Box<dyn Fn(&mut Solver<'rules>, $($id::Concrete,)*) -> InferenceResult + 'rules>,\n        }\n\n        #[allow(non_camel_case_types)]\n        impl<'rules, $($id: Factoid + Output,)*> $Name<'rules, $($id,)*> {\n            pub fn new<F>($($id: Exp<$id>,)* closure: F) -> $Name<'rules, $($id,)*>\n            where\n                F: Fn(&mut Solver<'rules>, $($id::Concrete,)*) -> InferenceResult + 'rules,\n            {\n                $Name { $($id,)*\n                    closure: Box::new(closure),\n                }\n            }\n        }\n\n        #[allow(non_camel_case_types)]\n        impl<'rules, $($id: Factoid + Output,)*> Rule<'rules> for $Name<'rules, $($id,)*> {\n            /// Tries to apply the rule to a given context.\n            fn apply(&self, context: &mut Context) -> TractResult<(bool, Vec<Box<dyn Rule<'rules> + 'rules>>)> {\n                $(\n                let $id = if let Some(it) = self.$id.get(context)?.concretize() {\n                    it\n                } else {\n                    return Ok((false, vec![]));\n                };\n                )*\n\n                let mut solver = Solver::default();\n                (self.closure)(&mut solver, $($id,)*)?;\n                Ok((true, solver.take_rules()))\n            }\n\n            /// Returns the paths that the rule depends on.\n            fn get_paths(&self) -> Vec<&Path> {\n                let mut v = vec!();\n                $(v.extend(self.$id.get_paths());)*\n                v\n            }\n        }\n\n        #[allow(non_camel_case_types)]\n        impl<'s, $($id: Factoid + Output,)*> fmt::Debug for $Name<'s, $($id,)*> {\n            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n                write!(f, \"GivenRule {{ {:?} }}\", ($(&self.$id),*))\n            }\n        }\n\n    }\n}\n\ngiven_tuple!(Given2Rule, given_2, a, b);\nimpl<'rules> Solver<'rules> {\n    pub fn given_2<T1, T2, A1, A2, F>(\n        &mut self,\n        item_1: A1,\n        item_2: A2,\n        closure: F,\n    ) -> InferenceResult\n    where\n        A1: IntoExp<T1>,\n        T1: Factoid + Output + 'static,\n        A2: IntoExp<T2>,\n        T2: Factoid + Output + 'static,\n        F: Fn(&mut Solver<'rules>, T1::Concrete, T2::Concrete) -> InferenceResult + 'rules,\n    {\n        let rule = Given2Rule::new(item_1.bex(), item_2.bex(), closure);\n        self.rules.push(Box::new(rule));\n        Ok(())\n    }\n}\n\ngiven_tuple!(Given3Rule, given_3, a, b, c);\nimpl<'rules> Solver<'rules> {\n    pub fn given_3<T1, T2, T3, A1, A2, A3, F>(\n        &mut self,\n        item_1: A1,\n        item_2: A2,\n        item_3: A3,\n        closure: F,\n    ) -> InferenceResult\n    where\n        A1: IntoExp<T1>,\n        T1: Factoid + Output + 'static,\n        A2: IntoExp<T2>,\n        T2: Factoid + Output + 'static,\n        A3: IntoExp<T3>,\n        T3: Factoid + Output + 'static,\n        F: Fn(&mut Solver<'rules>, T1::Concrete, T2::Concrete, T3::Concrete) -> InferenceResult\n            + 'rules,\n    {\n        let rule = Given3Rule::new(item_1.bex(), item_2.bex(), item_3.bex(), closure);\n        self.rules.push(Box::new(rule));\n        Ok(())\n    }\n}\n\ngiven_tuple!(Given4Rule, given_4, a, b, c, d);\nimpl<'rules> Solver<'rules> {\n    pub fn given_4<T1, T2, T3, T4, A1, A2, A3, A4, F>(\n        &mut self,\n        item_1: A1,\n        item_2: A2,\n        item_3: A3,\n        item_4: A4,\n        closure: F,\n    ) -> InferenceResult\n    where\n        A1: IntoExp<T1>,\n        T1: Factoid + Output + 'static,\n        A2: IntoExp<T2>,\n        T2: Factoid + Output + 'static,\n        A3: IntoExp<T3>,\n        T3: Factoid + Output + 'static,\n        A4: IntoExp<T4>,\n        T4: Factoid + Output + 'static,\n        F: Fn(\n                &mut Solver<'rules>,\n                T1::Concrete,\n                T2::Concrete,\n                T3::Concrete,\n                T4::Concrete,\n            ) -> InferenceResult\n            + 'rules,\n    {\n        let rule = Given4Rule::new(item_1.bex(), item_2.bex(), item_3.bex(), item_4.bex(), closure);\n        self.rules.push(Box::new(rule));\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn bootstrap<'s>() -> (Solver<'s>, TVec<TensorProxy>, TVec<TensorProxy>) {\n        (\n            Solver::default(),\n            tvec!(TensorProxy::new(tvec![0, 0].into())),\n            tvec!(TensorProxy::new(tvec![1, 0].into())),\n        )\n    }\n\n    #[test]\n    #[should_panic]\n    fn solver_wrong_size_1() {\n        let (mut solver, inputs, _) = bootstrap();\n        solver.equals(&inputs[0].rank, 2).unwrap();\n        solver.infer_facts((tvec![], tvec![])).unwrap();\n    }\n\n    #[test]\n    fn solver_exact_size() {\n        let (solver, _, _) = bootstrap();\n        let any = InferenceFact::new();\n\n        let facts = solver.infer_facts((tvec![&any], tvec![])).unwrap();\n        assert_eq!(facts, (tvec![InferenceFact::new()], tvec![]));\n    }\n\n    #[test]\n    fn solver_exact_rank() {\n        let (mut solver, inputs, _) = bootstrap();\n        solver.equals(&inputs[0].rank, 2).unwrap();\n\n        let any = InferenceFact::new();\n        let facts = solver.infer_facts((tvec![&any], tvec![])).unwrap();\n        let expected =\n            (tvec![InferenceFact { shape: shapefactoid![_, _], ..InferenceFact::new() }], tvec![]);\n\n        assert_eq!(facts, expected);\n    }\n\n    #[test]\n    fn solver_dynamic_rank() {\n        let (mut solver, inputs, _) = bootstrap();\n        solver.equals(&inputs[0].shape[1], 0.to_dim()).unwrap();\n\n        let any = InferenceFact::new();\n        let facts = solver.infer_facts((tvec![&any], tvec![])).unwrap();\n        let expected = (\n            tvec![InferenceFact { shape: shapefactoid![_, 0; ..], ..InferenceFact::new() }],\n            tvec![],\n        );\n\n        assert_eq!(facts, expected);\n    }\n\n    #[test]\n    fn solver_ranks() {\n        let (mut solver, inputs, _) = bootstrap();\n        solver.equals(&inputs[0].rank, 3).unwrap();\n        solver.equals(&inputs[0].shape[0], &inputs[0].shape[1]).unwrap();\n        solver.equals(&inputs[0].shape[1], &inputs[0].shape[2]).unwrap();\n        solver.equals(&inputs[0].shape[1], 3.to_dim()).unwrap();\n\n        let any = InferenceFact::new();\n        let facts = solver.infer_facts((tvec![&any], tvec![])).unwrap();\n        let expected = (\n            tvec![InferenceFact { shape: shapefactoid![3, 3, 3], ..InferenceFact::new() }],\n            tvec![],\n        );\n\n        assert_eq!(facts, expected);\n    }\n\n    #[test]\n    #[should_panic]\n    fn solver_wrong_constant() {\n        let (mut solver, _, _) = bootstrap();\n        solver.equals(1, 2).unwrap();\n        solver.infer_facts((tvec![], tvec![])).unwrap();\n    }\n\n    #[test]\n    fn solver_right_constant() {\n        let (mut solver, _, _) = bootstrap();\n        solver.equals(2, 2).unwrap();\n        solver.infer_facts((tvec![], tvec![])).unwrap();\n    }\n\n    #[test]\n    fn solver_backward_1() {\n        let (mut solver, inputs, outputs) = bootstrap();\n        solver.equals(&inputs[0].shape[1], &outputs[0].shape[1]).unwrap();\n\n        let any = InferenceFact::new();\n        let facts = solver.infer_facts((tvec![&any], tvec![&any])).unwrap();\n        let expected = (\n            tvec![InferenceFact::shape(shapefactoid![_,_;..])],\n            tvec![InferenceFact::shape(shapefactoid![_,_;..])],\n        );\n        assert_eq!(facts, expected);\n    }\n\n    #[test]\n    fn solver_backward_2() {\n        let (mut solver, inputs, outputs) = bootstrap();\n        solver.equals(&inputs[0].shape[1], &outputs[0].shape[1]).unwrap();\n\n        let output = InferenceFact { shape: shapefactoid![_, 2, _], ..InferenceFact::new() };\n        let any = InferenceFact::new();\n        let facts = solver.infer_facts((tvec![&any], tvec![&output])).unwrap();\n        let expected = (\n            tvec![InferenceFact { shape: shapefactoid![_, 2; ..], ..InferenceFact::new() }],\n            tvec![output],\n        );\n\n        assert_eq!(facts, expected);\n    }\n}\n"
  },
  {
    "path": "hir/src/lib.rs",
    "content": "#![allow(clippy::len_zero)]\n#![allow(clippy::collapsible_if)]\n#[macro_use]\nextern crate derive_new;\n#[macro_use]\nextern crate log;\n\n#[macro_use]\npub mod macros;\npub mod framework;\n\npub mod infer;\n\npub extern crate tract_core;\n\npub use tract_core::prelude::tract_ndarray;\npub use tract_core::prelude::tract_num_traits;\n\npub mod ops;\n\npub mod prelude {\n    pub use crate::infer::InferenceFact;\n    pub use crate::infer::InferenceModel;\n    pub use crate::infer::InferenceModelExt;\n    pub use crate::infer::InferenceSimplePlan;\n    pub use tract_core::prelude::*;\n}\n\npub mod internal {\n    pub use super::prelude::*;\n    pub use crate::infer::*;\n    pub use crate::ops::binary::BinIntoHir;\n    pub use crate::ops::element_wise::ElementWiseIntoHir;\n    pub use crate::ops::expandable::{Expansion, expand, inference_wrap};\n    pub use tract_core;\n    pub use tract_core::internal::*;\n    pub use {shapefactoid, to_typed};\n}\n\n#[cfg(test)]\n#[allow(dead_code)]\nfn setup_test_logger() {\n    let _ = env_logger::Builder::from_env(\"TRACT_LOG\").try_init();\n}\n"
  },
  {
    "path": "hir/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! to_typed {\n    () => {\n        fn to_typed(\n            &self,\n            _source: &$crate::infer::InferenceModel,\n            node: &$crate::infer::InferenceNode,\n            target: &mut TypedModel,\n            mapping: &std::collections::HashMap<OutletId, OutletId>,\n        ) -> TractResult<TVec<OutletId>> {\n            let inputs = node.inputs.iter().map(|m| mapping[m]).collect::<TVec<_>>();\n            target.wire_node(&*node.name, self.clone(), &*inputs)\n        }\n    };\n}\n\n/// Constructs a type fact.\n#[macro_export]\nmacro_rules! typefact {\n    (_) => {\n        $crate::infer::TypeFactoid::default()\n    };\n    ($arg:expr) => {{\n        let fact: $crate::infer::TypeFactoid = $crate::infer::GenericFactoid::Only($arg);\n        fact\n    }};\n}\n\n/// Constructs a shape fact.\n#[macro_export]\nmacro_rules! shapefactoid {\n    () =>\n        ($crate::infer::ShapeFactoid::closed(tvec![]));\n    (..) =>\n        ($crate::infer::ShapeFactoid::open(tvec![]));\n    ($($arg:tt),+; ..) =>\n        ($crate::infer::ShapeFactoid::open(tvec![$($crate::dimfact!($arg)),+]));\n    ($($arg:tt),+) =>\n        ($crate::infer::ShapeFactoid::closed(tvec![$($crate::dimfact!($arg)),+]));\n}\n\n/// Constructs a dimension fact.\n#[macro_export]\nmacro_rules! dimfact {\n    (_) => {\n        $crate::infer::DimFact::default()\n    };\n    (S) => {\n        $crate::infer::GenericFactoid::Only(tract_pulse::internal::stream_dim())\n    };\n    ($arg:expr) => {\n        $crate::infer::GenericFactoid::Only($arg.to_dim())\n    };\n}\n\n/// Constructs an value fact.\n#[macro_export]\nmacro_rules! valuefact {\n    (_) => {\n        $crate::infer::ValueFact::default()\n    };\n    ($arg:expr) => {{\n        let fact: $crate::infer::ValueFact = $crate::infer::GenericFactoid::Only($arg);\n        fact\n    }};\n}\n"
  },
  {
    "path": "hir/src/ops/activations.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::binary::TypedBinOp;\nuse tract_core::ops::logic::{comp_gt, comp_lt};\nuse tract_core::ops::math::*;\n\nmacro_rules! activation {\n    ($op: ident, $wire:expr) => {\n        impl Expansion for $op {\n            fn name(&self) -> StaticName {\n                stringify!($op).into()\n            }\n\n            fn rules<'r, 'p: 'r, 's: 'r>(\n                &'s self,\n                s: &mut Solver<'r>,\n                inputs: &'p [TensorProxy],\n                outputs: &'p [TensorProxy],\n            ) -> InferenceResult {\n                simple_unary_rules(s, inputs, outputs)\n            }\n\n            fn wire(\n                &self,\n                name: &str,\n                model: &mut TypedModel,\n                inputs: &[OutletId],\n            ) -> TractResult<TVec<OutletId>> {\n                let wire: fn(\n                    &$op,\n                    &str,\n                    &mut TypedModel,\n                    &[OutletId],\n                ) -> TractResult<TVec<OutletId>> = $wire;\n                (wire)(self, name, model, inputs)\n            }\n        }\n    };\n}\n\nmacro_rules! cst {\n    ($model: expr, $inputs: expr, $name: expr, $id:ident, $value: expr) => {\n        let $id = broadcast_scalar($value, $model, $inputs)?;\n        let $id = $model.add_const($name.to_string() + \".\" + stringify!($id), $id)?;\n    };\n}\n\n#[derive(Debug, Clone, new)]\npub struct Clip(Option<f32>, Option<f32>);\n\nactivation!(Clip, |op, name: &str, model: &mut TypedModel, inputs| {\n    let mut wire: TVec<OutletId> = inputs.into();\n    if let Some(low) = op.0 {\n        let low = broadcast_scalar(low, model, inputs)?;\n        let low = model.add_const(name.to_string() + \".low.cst\", low)?;\n        wire = model.wire_node(name.to_string() + \".low\", max(), &[wire[0], low])?;\n    }\n    if let Some(high) = op.1 {\n        let high = broadcast_scalar(high, model, inputs)?;\n        let high = model.add_const(name.to_string() + \".high.cst\", high)?;\n        wire = model.wire_node(name.to_string() + \".high\", min(), &[wire[0], high])?;\n    }\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Softplus;\n\nactivation!(Softplus, |_op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, one, 1.0);\n    let wire = model.wire_node(name.to_string() + \".exp\", exp(), inputs)?;\n    let wire = model.wire_node(name.to_string() + \".plus_one\", add(), &[wire[0], one])?;\n    let wire = model.wire_node(name.to_string() + \".ln\", ln(), &wire)?;\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Softsign;\n\nactivation!(Softsign, |_op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, one, 1.0);\n    let x_abs = model.wire_node(name.to_string() + \".abs\", abs(), inputs)?;\n    let denum = model.wire_node(name.to_string() + \".plus_one\", add(), &[x_abs[0], one])?;\n    let wire = model.wire_node(name.to_string() + \".div\", div(), &[inputs[0], denum[0]])?;\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new)]\npub struct Celu(pub f32);\n\nactivation!(Celu, |op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, zero, 0.0);\n    cst!(model, inputs, name, one, 1.0);\n    cst!(model, inputs, name, alpha, op.0);\n    let x_over_alpha =\n        model.wire_node(name.to_string() + \".x_over_alpha\", div(), &[inputs[0], alpha])?;\n    let x_over_alpha_exp = model.wire_node(name.to_string() + \".exp\", exp(), &[x_over_alpha[0]])?;\n    let minus_one =\n        model.wire_node(name.to_string() + \".minus_one\", sub(), &[x_over_alpha_exp[0], one])?;\n    let wire = model.wire_node(name.to_string() + \".sat-zero\", min(), &[zero, minus_one[0]])?;\n    let relu = model.wire_node(name.to_string() + \".relu\", max(), &[zero, inputs[0]])?;\n    let wire = model.wire_node(name.to_string(), add(), &[relu[0], wire[0]])?;\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new)]\npub struct Elu(pub f32);\n\nactivation!(Elu, |op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, zero, 0.0);\n    cst!(model, inputs, name, one, 1.0);\n    cst!(model, inputs, name, alpha, op.0);\n    let x_exp = model.wire_node(name.to_string() + \".exp\", exp(), inputs)?;\n    let minus_one = model.wire_node(name.to_string() + \".minus_one\", sub(), &[x_exp[0], one])?;\n    let neg = model.wire_node(name.to_string() + \".mul_alpha\", mul(), &[alpha, minus_one[0]])?;\n    let test = model.wire_node(\n        name.to_string() + \".test\",\n        TypedBinOp(comp_lt(), None),\n        &[zero, inputs[0]],\n    )?;\n    let wire = model.wire_node(\n        name.to_string() + \".iff\",\n        tract_core::ops::logic::Iff,\n        &[test[0], inputs[0], neg[0]],\n    )?;\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new)]\npub struct HardSigmoid(pub f32, pub f32);\n\nactivation!(HardSigmoid, |op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, zero, 0.0);\n    cst!(model, inputs, name, one, 1.0);\n    cst!(model, inputs, name, alpha, op.0);\n    cst!(model, inputs, name, beta, op.1);\n    let wire = model.wire_node(name.to_string() + \".mul_alpha\", mul(), &[alpha, inputs[0]])?;\n    let wire = model.wire_node(name.to_string() + \".add_beta\", add(), &[beta, wire[0]])?;\n    let wire = model.wire_node(name.to_string() + \".sat-one\", min(), &[one, wire[0]])?;\n    let wire = model.wire_node(name.to_string() + \".sat-zero\", max(), &[zero, wire[0]])?;\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new)]\npub struct LeakyRelu(pub f32);\n\nactivation!(LeakyRelu, |op, name: &str, model: &mut TypedModel, inputs| {\n    model.wire_node(name, tract_core::ops::nn::leaky_relu(op.0), inputs)\n});\n\n#[derive(Debug, Clone, new)]\npub struct ParametricSoftplus(pub f32, pub f32);\n\nactivation!(ParametricSoftplus, |op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, one, 1.0);\n    cst!(model, inputs, name, alpha, op.0);\n    cst!(model, inputs, name, beta, op.1);\n    let wire = model.wire_node(name.to_string() + \".mul_beta\", mul(), &[beta, inputs[0]])?;\n    let wire = model.wire_node(name.to_string() + \".exp\", exp(), &wire)?;\n    let wire = model.wire_node(name.to_string() + \".plus_one\", add(), &[one, wire[0]])?;\n    let wire = model.wire_node(name.to_string() + \".ln\", ln(), &wire)?;\n    let wire = model.wire_node(name.to_string() + \".mul_alpha\", mul(), &[alpha, wire[0]])?;\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new)]\npub struct ScaledTanh(pub f32, pub f32);\n\nactivation!(ScaledTanh, |op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, alpha, op.0);\n    cst!(model, inputs, name, beta, op.1);\n    let wire = model.wire_node(name.to_string() + \".mul_beta\", mul(), &[beta, inputs[0]])?;\n    let wire = model.wire_node(name.to_string() + \".tanh\", tanh(), &wire)?;\n    let wire = model.wire_node(name.to_string() + \".mul_alpha\", mul(), &[alpha, wire[0]])?;\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new)]\npub struct Selu(pub f32, pub f32);\n\nactivation!(Selu, |op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, zero, 0.0);\n    cst!(model, inputs, name, alpha, op.0);\n    cst!(model, inputs, name, gamma, op.1);\n    let wire = model.wire_node(name.to_string() + \".exp\", exp(), inputs)?;\n    let wire = model.wire_node(name.to_string() + \".mul_alpha\", mul(), &[wire[0], alpha])?;\n    let wire = model.wire_node(name.to_string() + \".sub_alpha\", sub(), &[wire[0], alpha])?;\n    let test = model.wire_node(\n        name.to_string() + \".test\",\n        TypedBinOp(comp_lt(), None),\n        &[zero, inputs[0]],\n    )?;\n    let wire = model.wire_node(\n        name.to_string() + \".iff\",\n        tract_core::ops::logic::Iff,\n        &[test[0], inputs[0], wire[0]],\n    )?;\n    let wire = model.wire_node(name.to_string() + \".mul_gamma\", mul(), &[gamma, wire[0]])?;\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new)]\npub struct Shrink(pub f32, pub f32);\n\nactivation!(Shrink, |op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, bias, op.0);\n    cst!(model, inputs, name, lambda, op.1);\n    cst!(model, inputs, name, minus_lambda, -op.1);\n    let zero = broadcast_scalar(0.0, model, inputs)?;\n    let zero = model.add_const(name.to_string() + \".zero\", zero)?;\n    let test_pos = model.wire_node(\n        name.to_string() + \".test_pos\",\n        TypedBinOp(comp_lt(), None),\n        &[lambda, inputs[0]],\n    )?;\n    let pos = model.wire_node(\n        name.to_string() + \".pos\",\n        tract_core::ops::math::sub(),\n        &[inputs[0], bias],\n    )?;\n    let test_neg = model.wire_node(\n        name.to_string() + \".test_neg\",\n        TypedBinOp(comp_gt(), None),\n        &[minus_lambda, inputs[0]],\n    )?;\n    let neg = model.wire_node(\n        name.to_string() + \".neg\",\n        tract_core::ops::math::add(),\n        &[bias, inputs[0]],\n    )?;\n    let wire = model.wire_node(\n        name.to_string() + \".if_pos\",\n        tract_core::ops::logic::Iff,\n        &[test_pos[0], pos[0], zero],\n    )?;\n    let wire = model.wire_node(\n        name.to_string() + \".if_neg\",\n        tract_core::ops::logic::Iff,\n        &[test_neg[0], neg[0], wire[0]],\n    )?;\n    Ok(wire)\n});\n\n#[derive(Debug, Clone, new)]\npub struct ThresholdRelu(pub f32);\n\nactivation!(ThresholdRelu, |op, name: &str, model: &mut TypedModel, inputs| {\n    cst!(model, inputs, name, zero, 0.0);\n    cst!(model, inputs, name, alpha, op.0);\n    let test = model.wire_node(\n        name.to_string() + \".test\",\n        TypedBinOp(comp_lt(), None),\n        &[alpha, inputs[0]],\n    )?;\n    let wire = model.wire_node(\n        name.to_string() + \".iff\",\n        tract_core::ops::logic::Iff,\n        &[test[0], inputs[0], zero],\n    )?;\n    Ok(wire)\n});\n\nfn simple_unary_rules<'r, 'p: 'r, 's: 'r>(\n    s: &mut Solver<'r>,\n    inputs: &'p [TensorProxy],\n    outputs: &'p [TensorProxy],\n) -> InferenceResult {\n    check_input_arity(inputs, 1)?;\n    check_output_arity(outputs, 1)?;\n    s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n    s.equals(&inputs[0].shape, &outputs[0].shape)?;\n    Ok(())\n}\n\npub fn broadcast_scalar(\n    f: f32,\n    model: &TypedModel,\n    inputs: &[OutletId],\n) -> TractResult<Arc<Tensor>> {\n    let fact = model.outlet_fact(inputs[0])?;\n    let mut tensor = tensor0(f).cast_to_dt(fact.datum_type)?.into_owned();\n    while tensor.rank() < fact.rank() {\n        tensor.insert_axis(0)?;\n    }\n    Ok(tensor.into_arc_tensor())\n}\n"
  },
  {
    "path": "hir/src/ops/array/add_dims.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\nuse tract_itertools::Itertools;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct AddDims {\n    pub axes: Vec<isize>,\n}\n\nimpl AddDims {\n    pub fn output_shape<D: DimLike>(&self, input: &[D]) -> TVec<D> {\n        let rank = input.len() as isize;\n        let mut shape: TVec<D> = input.iter().cloned().collect();\n        let output_rank = rank + self.axes.len() as isize;\n        let axes = self\n            .axes\n            .iter()\n            .map(|&axis| if axis < 0 { axis + output_rank } else { axis } as usize)\n            .sorted();\n        for axis in axes {\n            shape.insert(axis, D::one())\n        }\n        shape\n    }\n}\n\nimpl Expansion for AddDims {\n    fn name(&self) -> StaticName {\n        \"AddDims\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"Axes: {:?}\", self.axes)])\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.equals(&outputs[0].rank, (&inputs[0].rank).bex() + self.axes.len() as i64)?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            let output_shape = self.output_shape(&shape);\n            s.equals(&outputs[0].shape, output_shape)\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let rank = model.outlet_fact(inputs[0])?.rank() as isize;\n        let mut wire: TVec<OutletId> = inputs.into();\n        let output_rank = rank + self.axes.len() as isize;\n        let axes = self\n            .axes\n            .iter()\n            .map(|&axis| if axis < 0 { axis + output_rank } else { axis } as usize)\n            .sorted();\n        for axis in axes {\n            wire = model.wire_node(format!(\"{prefix}.axis-{axis}\"), AxisOp::Add(axis), &wire)?;\n        }\n        Ok(wire)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/array_feature_extractor.rs",
    "content": "use tract_core::ops::array::Gather;\n\nuse crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct ArrayFeatureExtractor;\n\nimpl Expansion for ArrayFeatureExtractor {\n    fn name(&self) -> StaticName {\n        \"ArrayFeatureExtractor\".into()\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let last_axis = model.outlet_fact(inputs[0])?.rank() - 1;\n        let gather_op = Gather { axis: last_axis, output_type: None };\n\n        model.wire_node(prefix, gather_op, inputs)\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        // Expect two inputs:\n        // - X: data to be selected\n        // - Y: the indices that'll be applied to the last axis\n        check_input_arity(inputs, 2)?;\n\n        // We return one tensor containing the selection\n        check_output_arity(outputs, 1)?;\n\n        // Check types\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[1].datum_type, i64::datum_type())?;\n\n        // Check ranks\n        s.equals(inputs[0].rank.bex() - 1 + inputs[1].rank.bex(), outputs[0].rank.bex())?;\n\n        // Check shapes\n        s.given_2(&inputs[0].shape, &inputs[1].shape, move |s, input_shape, indices_shape| {\n            let input_rank = input_shape.len();\n            let mut output_shape = tvec![];\n            output_shape.extend(input_shape.iter().take(input_rank - 1).cloned());\n            output_shape.extend(indices_shape.iter().cloned());\n            s.equals(&outputs[0].shape, output_shape)?;\n            Ok(())\n        })?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/broadcast.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\nuse tract_core::ops::array::MultiBroadcastTo as Typed;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct MultiBroadcastTo;\n\nimpl MultiBroadcastTo {\n    fn wire_with_known_target_shape(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n        target_shape: &[TDim],\n    ) -> TractResult<TVec<OutletId>> {\n        let left_shape = model.outlet_fact(inputs[0])?.shape.to_tvec();\n        let dims = tract_core::broadcast::multi_broadcast(&[&*left_shape, target_shape])?;\n        let op = Typed::new(dims.into());\n        model.wire_node(prefix, op, &[inputs[0]])\n    }\n}\n\nimpl Expansion for MultiBroadcastTo {\n    fn name(&self) -> StaticName {\n        \"MultiBroadcastTo\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.equals(&inputs[1].rank, 1)?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            s.given(&inputs[1].value, move |s, dims| {\n                let dims = dims.cast_to::<TDim>()?;\n                let dims = tract_core::broadcast::multi_broadcast(&[\n                    dims.try_as_plain()?.as_slice::<TDim>()?,\n                    &shape,\n                ])?;\n                s.equals(&outputs[0].shape, ShapeFactoid::from(dims))\n            })\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(shape) = model.outlet_fact(inputs[1])?.konst.clone() {\n            let shape = shape.cast_to::<TDim>()?;\n            self.wire_with_known_target_shape(\n                prefix,\n                model,\n                inputs,\n                shape.try_as_plain()?.as_slice()?,\n            )\n        } else {\n            bail!(\"shape input is variable\")\n        }\n    }\n\n    fn wire_with_inference_model_and_node(\n        &self,\n        prefix: &str,\n        source: &InferenceModel,\n        node: &InferenceNode,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(shape) = model.outlet_fact(inputs[1])?.konst.clone() {\n            let shape = shape.cast_to::<TDim>()?;\n            self.wire_with_known_target_shape(\n                prefix,\n                model,\n                inputs,\n                shape.try_as_plain()?.as_slice()?,\n            )\n        } else if let Some(shape) = source.outlet_fact(node.id.into())?.shape.concretize() {\n            let op = Typed::new(shape.into());\n            model.wire_node(prefix, op, &[inputs[0]])\n        } else {\n            bail!(\"shape input is variable, of variable length (output can not have variable rank)\")\n        }\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/concat.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::array::TypedConcat;\nuse tract_core::ops::cast::wire_cast;\n\n/// Concat: high level concat op\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Concat {\n    axis: i64,\n}\n\nimpl Concat {\n    fn resolve_axis(&self, rank: i64) -> TractResult<usize> {\n        if 0 <= self.axis && self.axis < rank {\n            Ok(self.axis as usize)\n        } else if -rank <= self.axis && self.axis < 0 {\n            Ok((self.axis + rank) as usize)\n        } else {\n            bail!(\"Illegal combination of values for rank and axis: {} and {}\", rank, self.axis)\n        }\n    }\n}\n\nimpl Expansion for Concat {\n    fn name(&self) -> StaticName {\n        \"InferenceConcat\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].rank, &inputs[0].rank)?;\n        let n = inputs.len();\n        s.equals_all((0..n).map(|i| (&inputs[i].rank).bex()).collect())?;\n        s.given_all((0..n).map(|i| (&inputs[i].datum_type).bex()), move |s, dts| {\n            let super_type: DatumType = DatumType::super_type_for(&dts)\n                .with_context(|| format!(\"No supertype found for {dts:?}\"))?;\n            s.equals(&outputs[0].datum_type, super_type)\n        })?;\n        s.given(&inputs[0].rank, move |s, rank| {\n            let axis = self.resolve_axis(rank)?;\n            s.equals(\n                rules::expr::SumExp::new((0..n).map(|i| (&inputs[i].shape[axis]).bex()).collect()),\n                &outputs[0].shape[axis],\n            )?;\n            for axis in 0..axis {\n                s.equals(&outputs[0].shape[axis], &inputs[0].shape[axis])?;\n                s.equals_all((0..n).map(|i| inputs[i].shape[axis].bex()).collect())?;\n            }\n            for axis in (axis + 1)..(rank as usize) {\n                s.equals(&outputs[0].shape[axis], &inputs[0].shape[axis])?;\n                s.equals_all((0..n).map(|i| inputs[i].shape[axis].bex()).collect())?;\n            }\n            Ok(())\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let facts = inputs\n            .iter()\n            .map(|i| target.outlet_fact(*i).cloned())\n            .collect::<TractResult<TVec<_>>>()?;\n\n        let super_type = if let Some(super_type) =\n            DatumType::super_type_for(facts.iter().map(|x| x.datum_type))\n        {\n            super_type\n        } else {\n            bail!(\"Can not type op\");\n        };\n\n        let axis = self.resolve_axis(facts[0].shape.rank() as i64)?;\n\n        let inputs = wire_cast(prefix, target, inputs, super_type)?;\n        let op = TypedConcat::new(axis);\n        target.wire_node(prefix, op, &inputs)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/constant_like.rs",
    "content": "use crate::internal::*;\nuse tract_ndarray::*;\nuse tract_num_traits::{AsPrimitive, One, Zero};\n\n#[derive(Debug, Clone, new, Default)]\npub struct ConstantLike {\n    value: f32,\n}\n\nimpl PartialEq for ConstantLike {\n    fn eq(&self, other: &Self) -> bool {\n        self.value.to_bits() == other.value.to_bits()\n    }\n}\nimpl Eq for ConstantLike {}\n\nimpl Op for ConstantLike {\n    fn name(&self) -> StaticName {\n        \"ConstantLike\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for ConstantLike {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        Ok(tvec!(tensor0(self.value).broadcast_scalar_to_shape(input.shape())?.into_tvalue()))\n    }\n}\n\nimpl InferenceRulesOp for ConstantLike {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        s.given_2(&inputs[0].shape, &inputs[0].datum_type, move |s, shape, dt| {\n            if shape.iter().all(|d| d.to_usize().is_ok()) {\n                let shape: Vec<usize> = shape.iter().map(|d| d.to_usize().unwrap()).collect();\n                let value = tensor0(self.value)\n                    .cast_to_dt(dt)?\n                    .broadcast_scalar_to_shape(&shape)?\n                    .into_arc_tensor();\n                s.equals(&outputs[0].value, value)?;\n            }\n            Ok(())\n        })\n    }\n\n    as_op!();\n    to_typed!();\n}\n\nimpl TypedOp for ConstantLike {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct EyeLike {\n    dt: Option<DatumType>,\n    k: isize,\n}\n\nimpl EyeLike {\n    pub fn make<T>(&self, (r, c): (usize, usize)) -> TractResult<TValue>\n    where\n        T: Copy + Datum + One + Zero,\n        f32: AsPrimitive<T>,\n    {\n        let mut array = Array2::<T>::zeros((r, c));\n        for y in 0..r {\n            let x = y as isize + self.k;\n            if x >= 0 && x < c as isize {\n                array[(y, x as usize)] = T::one()\n            }\n        }\n        Ok(array.into_dyn().into_tvalue())\n    }\n}\n\nimpl Op for EyeLike {\n    fn name(&self) -> StaticName {\n        \"EyeLike\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for EyeLike {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let dt = self.dt.unwrap_or_else(|| input.datum_type());\n        Ok(tvec!(dispatch_numbers!(Self::make(dt)(self, (input.shape()[0], input.shape()[1])))?))\n    }\n}\n\nimpl InferenceRulesOp for EyeLike {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        if let Some(dt) = self.dt {\n            s.equals(&outputs[0].datum_type, dt)?;\n        } else {\n            s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        }\n        s.equals(&inputs[0].rank, 2)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            if let (Ok(r), Ok(c)) = (shape[0].to_usize(), shape[1].to_usize()) {\n                let shape = (r, c);\n                if let Some(dt) = self.dt {\n                    let value = dispatch_numbers!(Self::make(dt)(self, shape))?;\n                    s.equals(&outputs[0].value, value.into_arc_tensor())?;\n                } else {\n                    s.given(&inputs[0].datum_type, move |s, dt| {\n                        let value = dispatch_numbers!(Self::make(dt)(self, shape))?;\n                        s.equals(&outputs[0].value, value.into_arc_tensor())\n                    })?;\n                }\n            }\n            Ok(())\n        })\n    }\n\n    as_op!();\n    to_typed!();\n}\n\nimpl TypedOp for EyeLike {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(self.dt.unwrap_or(inputs[0].datum_type).fact(inputs[0].shape.iter())))\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/constant_of_shape.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct ConstantOfShape {\n    scalar: Arc<Tensor>,\n}\n\nimpl Expansion for ConstantOfShape {\n    fn name(&self) -> StaticName {\n        \"ConstantOfShape\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, self.scalar.datum_type())?;\n        s.equals(&inputs[0].rank, 1)?;\n        s.equals(&inputs[0].shape[0], outputs[0].rank.bex().to_dim())?;\n        s.given(&inputs[0].value, move |s, shape| {\n            let shape = shape.cast_to::<TDim>()?;\n            let shape = shape.try_as_plain()?.as_slice::<TDim>()?;\n            for (axis, dim) in shape.iter().enumerate() {\n                s.equals(&outputs[0].shape[axis], dim)?;\n            }\n            Ok(())\n        })?;\n        /* does not work .value assumes ints\n        s.given(&outputs[0].rank, move |s, rank| {\n            for axis in 0..rank as usize {\n                s.equals(&outputs[0].shape[axis], &inputs[0].value[axis].bex())?;\n            }\n            Ok(())\n        })?;\n        */\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(shape) = target.outlet_fact(inputs[0])?.konst.clone() {\n            let shape = shape.cast_to::<TDim>()?;\n            let shape = shape.try_as_plain()?.as_slice::<TDim>()?;\n            let scalar = target.add_const(format!(\"{prefix}.scalar\"), self.scalar.clone())?;\n            let op = tract_core::ops::array::MultiBroadcastTo::new(shape.into());\n            return target.wire_node(prefix, op, &[scalar]);\n        }\n        bail!(\"shape input is variable\")\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/crop.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Crop {\n    pub axis: usize,\n    pub start: usize,\n    pub end: usize,\n}\n\nimpl Expansion for Crop {\n    fn name(&self) -> StaticName {\n        \"Crop\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        s.given(&inputs[0].rank, move |s, rank| {\n            (0..rank as usize).try_for_each(|ax| {\n                if self.axis == ax {\n                    s.equals(\n                        &inputs[0].shape[ax],\n                        outputs[0].shape[ax].bex() + self.start.to_dim() + self.end.to_dim(),\n                    )\n                } else {\n                    s.equals(&inputs[0].shape[ax], &outputs[0].shape[ax])\n                }\n            })\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let len = target.outlet_fact(inputs[0])?.shape[self.axis].clone();\n        target.wire_node(\n            prefix,\n            crate::ops::array::Slice::new(self.axis, self.start.to_dim(), len - self.end.to_dim()),\n            inputs,\n        )\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/dyn_slice.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::array::DynSlice;\n\nimpl InferenceRulesOp for DynSlice {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut crate::infer::Solver<'r>,\n        inputs: &'p [crate::infer::TensorProxy],\n        outputs: &'p [crate::infer::TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        s.equals(&inputs[1].rank, 0)?;\n        s.equals(&inputs[2].rank, 0)?;\n        s.given(&inputs[0].rank, move |s, rank| {\n            for axis in 0..rank as usize {\n                if axis == self.axis {\n                    s.equals(&outputs[0].shape[axis], self.len.clone())?;\n                } else {\n                    s.equals(&outputs[0].shape[axis], &inputs[0].shape[axis])?;\n                }\n            }\n            Ok(())\n        })?;\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/array/flatten.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Flatten {\n    pub axis: i64,\n}\n\nimpl Flatten {\n    pub fn compute_shape<D: DimLike>(&self, shape: &[D]) -> TractResult<[D; 2]> {\n        if shape.iter().filter(|d| d.to_usize().is_err()).count() > 1 {\n            bail!(\"Can not compute a shape with square of symbols\")\n        }\n        let axis = if self.axis >= 0 { self.axis } else { self.axis + shape.len() as i64 } as usize;\n        Ok([shape[..axis].iter().cloned().product::<D>(), shape[axis..].iter().cloned().product()])\n    }\n}\n\nimpl Expansion for Flatten {\n    fn name(&self) -> StaticName {\n        \"Flatten\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            let [shape_0, shape_1] = self.compute_shape(&shape)?;\n            s.equals(&outputs[0].shape, ShapeFactoid::from(vec![shape_0, shape_1]))\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input_shape = model.outlet_fact(inputs[0])?.shape.to_tvec();\n        let output_shape = self.compute_shape(&input_shape)?;\n        let mut wire = tvec!(inputs[0]);\n        for (ix, op) in\n            tract_core::ops::change_axes::to_axis_ops_with_tf_rules(&input_shape, &output_shape)?\n                .into_iter()\n                .enumerate()\n        {\n            wire = model.wire_node(format!(\"{prefix}.{ix}\"), op, &wire)?;\n        }\n        Ok(wire)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/gather.rs",
    "content": "use tract_core::ops::cast::cast;\n\nuse crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Gather {\n    axis: i64,\n}\n\nimpl Gather {\n    pub fn to_type_op(&self, input_rank: usize) -> tract_core::ops::array::Gather {\n        let axis = if self.axis < 0 { self.axis + input_rank as i64 } else { self.axis } as usize;\n        tract_core::ops::array::Gather::new(axis)\n    }\n}\n\nimpl Expansion for Gather {\n    fn name(&self) -> StaticName {\n        \"Gather\".into()\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input_rank = model.outlet_fact(inputs[0])?.rank();\n        let mut inputs: TVec<OutletId> = inputs.into();\n        inputs[1] = model.wire_node(\n            format!(\"{prefix}.cast_to_i64\"),\n            cast(i64::datum_type()),\n            &[inputs[1]],\n        )?[0];\n        model.wire_node(prefix, self.to_type_op(input_rank), &inputs)\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(inputs[0].rank.bex() - 1 + inputs[1].rank.bex(), outputs[0].rank.bex())?;\n        s.given_2(&inputs[0].shape, &inputs[1].shape, move |s, input_shape, indices_shape| {\n            let rank = input_shape.len();\n            let output_shape =\n                self.to_type_op(rank).compute_output_shape(&input_shape, &indices_shape)?;\n            s.equals(&outputs[0].shape, output_shape)?;\n            Ok(())\n        })?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/gather_elements.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct GatherElements {\n    axis: i64,\n}\n\nimpl Expansion for GatherElements {\n    fn name(&self) -> StaticName {\n        \"GatherElements\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, &inputs[1].rank)?;\n        s.equals(&outputs[0].shape, &inputs[1].shape)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input_rank = model.outlet_fact(inputs[0])?.rank();\n        let axis = if self.axis < 0 { self.axis + input_rank as i64 } else { self.axis } as usize;\n        model.wire_node(prefix, tract_core::ops::array::GatherElements { axis }, inputs)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/gather_nd.rs",
    "content": "use crate::internal::*;\npub use tract_core::ops::array::GatherNd;\n\nimpl InferenceRulesOp for GatherNd {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.given(&inputs[1].rank, move |s, indices_rank| {\n            let indices_rank = indices_rank as usize;\n            for i in 0..(indices_rank - 1) {\n                s.equals(&outputs[0].shape[i], &inputs[1].shape[i])?;\n            }\n            s.given_2(\n                &inputs[1].shape[indices_rank - 1],\n                &inputs[1].rank,\n                move |s, n, input_rank| {\n                    if let Ok(n) = n.to_i64() {\n                        for i in 0..(input_rank - n) as usize {\n                            s.equals(&outputs[0].shape[indices_rank - 1 + i], &inputs[1].shape[i])?;\n                        }\n                    }\n                    Ok(())\n                },\n            )\n        })\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/array/mod.rs",
    "content": "mod add_dims;\nmod array_feature_extractor;\nmod broadcast;\nmod concat;\nmod constant_like;\nmod constant_of_shape;\nmod crop;\nmod dyn_slice;\nmod flatten;\nmod gather;\nmod gather_elements;\nmod gather_nd;\nmod pad;\npub mod permute_axes;\nmod range;\nmod reshape;\nmod rm_dims;\nmod scatter_elements;\nmod scatter_nd;\nmod shape;\nmod size;\nmod slice;\nmod split;\nmod squeeze;\nmod strided_slice;\nmod tile;\n\npub use add_dims::AddDims;\npub use array_feature_extractor::ArrayFeatureExtractor;\npub use broadcast::MultiBroadcastTo;\npub use concat::{Concat, TypedConcat};\npub use constant_like::{ConstantLike, EyeLike};\npub use constant_of_shape::ConstantOfShape;\npub use crop::Crop;\npub use flatten::Flatten;\npub use gather::Gather;\npub use gather_elements::GatherElements;\npub use gather_nd::GatherNd;\npub use pad::{Pad, PadMode};\npub use permute_axes::PermuteAxes;\npub use range::Range;\npub use reshape::Reshape;\npub use rm_dims::RmDims;\npub use scatter_elements::ScatterElements;\npub use scatter_nd::{ScatterNd, ScatterReduction};\npub use shape::Shape;\npub use size::Size;\npub use slice::Slice;\npub use split::Split;\npub use squeeze::Squeeze;\npub use tile::Tile;\npub use tract_core::ops::array::StridedSlice;\n"
  },
  {
    "path": "hir/src/ops/array/pad.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::array::{Pad, PadMode};\n\nimpl InferenceRulesOp for Pad {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        for (ix, &(a, b)) in self.pads.iter().enumerate() {\n            s.equals(&inputs[0].shape[ix], outputs[0].shape[ix].bex() - a.to_dim() - b.to_dim())?;\n        }\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/array/permute_axes.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct PermuteAxes {\n    pub axes: Option<TVec<usize>>,\n}\n\nimpl PermuteAxes {\n    fn compute_shape<D: DimLike>(&self, input: &[D]) -> TractResult<TVec<D>> {\n        if let Some(ref axes) = self.axes {\n            if input.len() != axes.len() {\n                bail!(\n                    \"Op expects tensor of rank {}, input is actually of rank {}.\",\n                    axes.len(),\n                    input.len()\n                );\n            }\n            let mut new_shape = tvec![D::zero(); input.len()];\n            for (ix, &d) in axes.iter().enumerate() {\n                new_shape[ix] = input[d].clone();\n            }\n            Ok(new_shape)\n        } else {\n            let mut new_shape: TVec<D> = input.iter().cloned().collect();\n            new_shape.reverse();\n            Ok(new_shape)\n        }\n    }\n}\n\nimpl Expansion for PermuteAxes {\n    fn name(&self) -> StaticName {\n        \"PermuteAxes\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"{:?}\", self.axes)])\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.equals(&outputs[0].rank, &inputs[0].rank)?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            let output_shape = self.compute_shape(&shape)?;\n            s.equals(&outputs[0].shape, output_shape)\n        })?;\n        if let Some(axes) = &self.axes {\n            s.equals(&outputs[0].rank, axes.len() as i64)?;\n        }\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let fact = target.outlet_fact(inputs[0])?;\n        let axes = if let Some(axes) = &self.axes {\n            if fact.rank() != axes.len() {\n                bail!(\n                    \"Op expects tensor of rank {}, input is actually of rank {}.\",\n                    axes.len(),\n                    fact.rank()\n                );\n            }\n            axes.clone()\n        } else {\n            (0..fact.rank()).rev().collect()\n        };\n        let mut wire: TVec<OutletId> = inputs.into();\n        for (ix, op) in perm_to_ops(&axes).into_iter().enumerate() {\n            wire = target.wire_node(format!(\"{}.{}-{}\", prefix, op.name(), ix), op, &wire)?;\n        }\n        Ok(wire)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/range.rs",
    "content": "use tract_core::ops::cast::wire_cast;\n\nuse crate::internal::*;\n\n#[derive(Debug, Default, Clone, new, Hash, PartialEq, Eq)]\npub struct Range;\n\nimpl Expansion for Range {\n    fn name(&self) -> StaticName {\n        \"Range\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        s.given_3(\n            &inputs[0].datum_type,\n            &inputs[1].datum_type,\n            &inputs[2].datum_type,\n            move |s, dt0, dt1, dt2| {\n                let dt =\n                    DatumType::super_type_for([dt0, dt1, dt2]).context(\"No supertype found\")?;\n                if dt.is_tdim() {\n                    s.equals(&outputs[0].datum_type, i64::datum_type())\n                } else {\n                    s.equals(dt, &outputs[0].datum_type)\n                }\n            },\n        )?;\n        s.equals(&inputs[0].rank, 0)?;\n        s.equals(&inputs[1].rank, 0)?;\n        s.equals(&inputs[2].rank, 0)?;\n        s.equals(&outputs[0].rank, 1)?;\n        s.given_3(&inputs[0].value, &inputs[1].value, &inputs[2].value, move |s, v0, v1, v2| {\n            let v0 = v0.cast_to::<TDim>()?;\n            let v1 = v1.cast_to::<TDim>()?;\n            let v2 = v2.cast_to::<i64>()?;\n            let out = (v1.try_as_plain()?.to_scalar::<TDim>()?.clone()\n                - v0.try_as_plain()?.to_scalar::<TDim>()?)\n            .divceil(*v2.try_as_plain()?.to_scalar::<i64>()? as _);\n            s.equals(&outputs[0].shape[0], out)\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let dt: DatumType = DatumType::super_type_for(\n            inputs.iter().map(|o| model.outlet_fact(*o).unwrap().datum_type),\n        )\n        .context(\"No supertype for inputs\")?;\n        let inputs = wire_cast(prefix, model, inputs, dt)?;\n        let len = model.symbols.new_with_prefix(\"range\");\n        model.wire_node(prefix, tract_core::ops::array::Range::new(len.into()), &inputs)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/reshape.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Reshape {}\n\nimpl Expansion for Reshape {\n    fn name(&self) -> StaticName {\n        \"Reshape\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.given_2(&inputs[0].shape, &inputs[1].value, move |s, ishape, shape| {\n            let shape = shape.cast_to::<TDim>()?;\n            let shape = shape.try_as_plain()?.as_slice::<TDim>()?;\n            let oshape = tract_core::ops::change_axes::compute_shape_with_tf_rules(&ishape, shape)\n                .with_context(|| format!(\"Reshaping {ishape:?} to {shape:?}\"))?;\n            s.equals(&outputs[0].shape, ShapeFactoid::from(oshape))\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(ref shape) = model.outlet_fact(inputs[1])?.konst {\n            let input_shape: TVec<TDim> = model.outlet_fact(inputs[0])?.shape.to_tvec();\n            let shape = shape.cast_to::<TDim>()?;\n            let shape = shape.try_as_plain()?.as_slice::<TDim>()?;\n            let mut wire = tvec!(inputs[0]);\n            for (ix, op) in to_axis_ops_with_tf_rules(&input_shape, shape)?.into_iter().enumerate()\n            {\n                wire = model.wire_node(format!(\"{prefix}.{ix}\"), op, &wire)?;\n            }\n            return Ok(wire);\n        }\n        bail!(\"shape input is variable\")\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/rm_dims.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\nuse tract_itertools::Itertools;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct RmDims {\n    pub axes: Vec<isize>,\n}\n\nimpl RmDims {\n    fn compute_shape<D: DimLike>(&self, input: &[D]) -> TVec<D> {\n        let axes = self\n            .axes\n            .iter()\n            .map(|&a| if a < 0 { a + input.len() as isize } else { a } as usize)\n            .collect::<Vec<_>>();\n        input\n            .iter()\n            .enumerate()\n            .filter(|(ix, _d)| !axes.contains(ix))\n            .map(|(_ix, d)| d.clone())\n            .collect()\n    }\n}\n\nimpl Expansion for RmDims {\n    fn name(&self) -> StaticName {\n        \"RmDims\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.equals(&outputs[0].rank, (&inputs[0].rank).bex() - self.axes.len() as i64)?;\n        s.given(&inputs[0].rank, move |s, rank| {\n            for axis in &self.axes {\n                let axis = if *axis < 0 { axis + rank as isize } else { *axis } as usize;\n                s.equals(&inputs[0].shape[axis], 1.to_dim())?;\n            }\n            Ok(())\n        })?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            let output_shape = self.compute_shape(&shape);\n            s.equals(&outputs[0].shape, output_shape)\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let mut wire = inputs[0];\n        let rank = target.outlet_fact(inputs[0])?.rank();\n        let axes = self\n            .axes\n            .iter()\n            .map(|&a| if a < 0 { a + rank as isize } else { a } as usize)\n            .sorted()\n            .rev();\n        for axis in axes {\n            wire = target.wire_node(format!(\"{prefix}.axis-{axis}\"), AxisOp::Rm(axis), &[wire])?[0];\n        }\n        Ok(tvec!(wire))\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/scatter_elements.rs",
    "content": "use tract_core::ops::cast::wire_cast;\n\nuse crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct ScatterElements {\n    axis: i64,\n    reduction: tract_core::ops::array::ScatterReduction,\n}\n\nimpl Expansion for ScatterElements {\n    fn name(&self) -> StaticName {\n        \"ScatterElements\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n\n        s.given_2(&inputs[0].datum_type, &inputs[2].datum_type, move |s, input, updates| {\n            let super_type: DatumType = DatumType::super_type_for([input, updates])\n                .with_context(|| format!(\"No supertype found for {input:?} and {updates:?}\"))?;\n            s.equals(&outputs[0].datum_type, super_type)\n        })?;\n        s.equals(&inputs[0].rank, &inputs[1].rank)?;\n        s.equals(&inputs[1].shape, &inputs[2].shape)?;\n        s.equals(&outputs[0].shape, &inputs[0].shape)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input_rank = model.outlet_fact(inputs[0])?.rank();\n        let axis = if self.axis < 0 { self.axis + input_rank as i64 } else { self.axis } as usize;\n        let super_type = if let Some(super_type) = DatumType::super_type_for([\n            model.outlet_fact(inputs[0])?.datum_type,\n            model.outlet_fact(inputs[2])?.datum_type,\n        ]) {\n            super_type\n        } else {\n            bail!(\"Can not type op\");\n        };\n        let casted = wire_cast(prefix, model, &[inputs[0], inputs[2]], super_type)?;\n        model.wire_node(\n            prefix,\n            tract_core::ops::array::ScatterElements { axis, reduction: self.reduction },\n            &[casted[0], inputs[1], casted[1]],\n        )\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/scatter_nd.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::array::{ScatterNd, ScatterReduction};\n\nimpl InferenceRulesOp for ScatterNd {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.equals(&inputs[2].datum_type, &inputs[0].datum_type)?;\n        s.equals(&outputs[0].shape, &inputs[0].shape)?;\n\n        s.given_2(&inputs[0].rank, &inputs[1].rank, move |s, p, q| {\n            s.given(&inputs[1].shape[q as usize - 1], move |s, r| {\n                if let Ok(r) = r.to_i64() {\n                    s.equals(&inputs[2].rank, p + q - r - 1)?;\n                }\n                Ok(())\n            })\n        })?;\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/array/shape.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Shape {\n    pub dt: DatumType,\n}\n\nimpl Expansion for Shape {\n    fn name(&self) -> StaticName {\n        \"Shape\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].rank, 1)?;\n        s.equals(&outputs[0].shape[0], inputs[0].rank.bex().to_dim())?;\n        s.equals(&outputs[0].datum_type, self.dt.bex())?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            let shape = tensor1(&shape);\n            if let Ok(shape) = shape.cast_to_dt(self.dt) {\n                s.equals(&outputs[0].value, shape.into_owned().into_arc_tensor())?;\n            }\n            Ok(())\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let shape = tensor1(&model.outlet_fact(inputs[0])?.shape.to_tvec());\n        let wire = model.add_const(format!(\"{prefix}.const\"), shape)?;\n        model.wire_node(prefix, tract_core::ops::cast::cast(self.dt), &[wire])\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/size.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Size {\n    pub dt: DatumType,\n}\n\nimpl Expansion for Size {\n    fn name(&self) -> StaticName {\n        \"Size\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, self.dt)?;\n        s.equals(&outputs[0].rank, 0)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let mut size = tensor0(model.outlet_fact(inputs[0])?.shape.iter().product::<TDim>());\n        if let Ok(s) = size.cast_to_dt(self.dt) {\n            size = s.into_owned();\n        }\n        let wire = model.add_const(prefix, size)?;\n        Ok(tvec!(wire))\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/slice.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::array::Slice;\n\nimpl InferenceRulesOp for Slice {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.given(&inputs[0].rank, move |s, rank| {\n            (0..(rank as usize)).try_for_each(move |axis| {\n                if self.axis == axis {\n                    s.equals(&outputs[0].shape[axis], (self.end.clone() - &self.start).to_dim())\n                } else {\n                    s.equals(&outputs[0].shape[axis], &inputs[0].shape[axis])\n                }\n            })\n        })?;\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/array/split.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Split {\n    axis: isize,\n    outputs: usize,\n    split: Option<Vec<usize>>,\n}\n\nimpl Split {\n    fn split_dims<D: DimLike>(&self, input: &D) -> TractResult<TVec<D>> {\n        if let Some(split) = self.split.as_ref() {\n            Ok(split.iter().map(|&d| D::from(d)).collect())\n        } else {\n            let bigs = input.clone().divceil(self.outputs);\n            let last = input.clone() - (bigs.clone() * (self.outputs - 1));\n            let mut splits = tvec!(bigs ; self.outputs - 1);\n            splits.push(last);\n            Ok(splits)\n        }\n    }\n}\n\nimpl Expansion for Split {\n    fn name(&self) -> StaticName {\n        \"Split\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, self.outputs)?;\n        (0..self.outputs).try_for_each(|i| {\n            s.equals(&inputs[0].datum_type, &outputs[i].datum_type)?;\n            s.equals(&inputs[0].rank, &outputs[i].rank)\n        })?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            let axis =\n                if self.axis < 0 { self.axis + shape.len() as isize } else { self.axis } as usize;\n            let dims = self.split_dims(&shape[axis])?;\n            for i in 0..self.outputs {\n                let mut shape = shape.clone();\n                shape[axis] = dims[i].clone();\n                s.equals(&outputs[i].shape, shape)?;\n            }\n            Ok(())\n        })?;\n        Ok(())\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(self.outputs)\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input = target.outlet_fact(inputs[0])?.clone();\n        let mut outputs = tvec!();\n        let mut current = 0.to_dim();\n        let axis =\n            if self.axis < 0 { self.axis + input.rank() as isize } else { self.axis } as usize;\n        for (ix, len) in self.split_dims(&input.shape[axis])?.into_iter().enumerate() {\n            let end = current.clone() + len;\n            outputs.push(\n                target.wire_node(\n                    format!(\"{prefix}.axis{axis}_slice{ix}_{current}..{end}\"),\n                    crate::ops::array::Slice::new(axis, current, end.clone()),\n                    inputs,\n                )?[0],\n            );\n            current = end;\n        }\n        Ok(outputs)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/squeeze.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\nuse super::RmDims;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Squeeze {\n    axes: Option<Vec<isize>>,\n}\n\nimpl Squeeze {\n    pub fn output_shape<D: DimLike>(&self, input: &[D]) -> TractResult<TVec<D>> {\n        if let Some(ref axes) = self.axes {\n            let axes = axes\n                .iter()\n                .map(|&a| if a < 0 { a + input.len() as isize } else { a } as usize)\n                .collect::<Vec<_>>();\n            let mut shape: TVec<D> = input.iter().cloned().collect();\n            for &axis in axes.iter().rev() {\n                if shape.remove(axis) != D::one() {\n                    bail!(\n                        \"Attempt to squeeze an axis which dimension is not one {:?}, {:?}\",\n                        self,\n                        input\n                    );\n                }\n            }\n            Ok(shape)\n        } else {\n            Ok(input.iter().filter(|&d| d != &D::one()).cloned().collect())\n        }\n    }\n}\n\nimpl Expansion for Squeeze {\n    fn name(&self) -> StaticName {\n        \"Squeeze\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        if let Some(ref axes) = self.axes {\n            s.equals(&outputs[0].rank, (&inputs[0].rank).bex() - axes.len() as i64)?;\n        }\n        s.given(&inputs[0].shape, move |s, shape| {\n            let output_shape = self.output_shape(&shape)?;\n            s.equals(&outputs[0].shape, output_shape)\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input = inputs[0];\n        let axes = if let Some(axes) = &self.axes {\n            axes.clone()\n        } else {\n            let input_fact = target.outlet_fact(input)?;\n            input_fact\n                .shape\n                .iter()\n                .enumerate()\n                .filter(|(_ix, d)| d.is_one())\n                .map(|(ix, _d)| ix as isize)\n                .collect()\n        };\n        RmDims::new(axes).wire(prefix, target, inputs)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/strided_slice.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::array::StridedSlice;\nuse tract_itertools::Itertools;\n\nimpl InferenceRulesOp for StridedSlice {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(\n            inputs,\n            3 + self.optional_axes_input.is_some() as usize\n                + self.optional_steps_input.is_some() as usize,\n        )?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[1].rank, 1)?;\n        s.equals(&inputs[2].rank, 1)?;\n        s.equals(&inputs[1].shape[0], &inputs[2].shape[0])?;\n        s.equals(\n            &outputs[0].rank,\n            inputs[0].rank.bex() - self.shrink_axis_mask.count_ones() as i64,\n        )?;\n        if let Some(axis) = self.optional_axes_input {\n            s.equals(&inputs[1].shape, &inputs[axis].shape)?;\n        };\n        if let Some(step) = self.optional_steps_input {\n            s.equals(&inputs[1].shape, &inputs[step].shape)?;\n        };\n        if let Some(axes_input) = self.optional_axes_input {\n            s.given(&inputs[axes_input].value, move |s, axes| {\n                let axes = axes.cast_to::<i64>()?.into_owned();\n                s.given(&outputs[0].rank, move |s, orank| {\n                    let axes = axes\n                        .try_as_plain()?\n                        .as_slice::<i64>()?\n                        .iter()\n                        .map(|a| if *a >= 0 { *a } else { *a + orank } as usize)\n                        .collect_vec();\n                    let mut iaxis = 0;\n                    for oaxis in 0..orank as usize {\n                        while self.shrink_axis_mask & (1 << iaxis) != 0 {\n                            iaxis += 1;\n                        }\n                        if !axes.contains(&iaxis) {\n                            s.equals(&inputs[0].shape[iaxis], &outputs[0].shape[oaxis])?;\n                        }\n                        iaxis += 1;\n                    }\n                    Ok(())\n                })\n            })?;\n        }\n        s.given(&inputs[0].shape, move |s, input_shape| {\n            s.given_all(inputs[1..].iter().map(|i| &i.value), move |s, params| {\n                let begin = &params[0];\n                let end = &params[1];\n                let strides = if let Some(i) = self.optional_steps_input {\n                    let t = params[i - 1].cast_to::<i32>()?;\n                    t.try_as_plain()?.as_slice::<i32>()?.to_vec()\n                } else {\n                    vec![1; input_shape.len()]\n                };\n                let axes: TVec<usize> = if let Some(i) = self.optional_axes_input {\n                    let axes = params[i - 1].cast_to::<i32>()?;\n                    axes.try_as_plain()?\n                        .as_slice::<i32>()?\n                        .iter()\n                        .map(|&i| if i < 0 { input_shape.len() as i32 + i } else { i } as usize)\n                        .collect()\n                } else {\n                    (0..input_shape.len()).collect()\n                };\n                let mut output_shape = input_shape.clone();\n                let mut shrink = vec![];\n                for (ix, axis) in axes.into_iter().enumerate() {\n                    let preped =\n                        self.prepare_one_dim(ix, &input_shape[axis], begin, end, &strides)?;\n                    output_shape[axis] = preped.soft_len()?;\n                    if preped.shrink {\n                        shrink.push(axis);\n                    }\n                }\n                for shrink in shrink.iter().sorted().rev() {\n                    output_shape.remove(*shrink);\n                }\n                s.equals(&outputs[0].shape, output_shape)\n            })\n        })\n    }\n\n    to_typed!();\n    as_op!();\n}\n\n#[cfg(test)]\nmod tests {\n    #![allow(non_snake_case)]\n    use super::*;\n    use tract_core::ops::array::strided_slice::Dim;\n    use tract_ndarray::{arr1, arr2, arr3};\n\n    pub fn strided_slice(begin_mask: i64, end_mask: i64, shrink_axis_mask: i64) -> StridedSlice {\n        StridedSlice {\n            begin_mask,\n            end_mask,\n            shrink_axis_mask,\n            optional_axes_input: None,\n            optional_steps_input: Some(3),\n        }\n    }\n\n    fn eval<I, B, E, S>(op: StridedSlice, input: I, begin: B, end: E, strides: S) -> Tensor\n    where\n        I: Into<Tensor>,\n        B: Into<Tensor>,\n        E: Into<Tensor>,\n        S: Into<Tensor>,\n    {\n        op.eval(tvec![\n            input.into().into(),\n            begin.into().into(),\n            end.into().into(),\n            strides.into().into(),\n        ])\n        .unwrap()\n        .pop()\n        .unwrap()\n        .into_tensor()\n    }\n\n    // https://www.tensorflow.org/api_docs/python/tf/strided_slice\n    #[test]\n    fn eval_1() {\n        assert_eq!(\n            eval(\n                strided_slice(0, 0, 0),\n                arr3(&[[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]], [[5, 5, 5], [6, 6, 6]],]),\n                tensor1(&[1, 0, 0]),\n                tensor1(&[2, 1, 3]),\n                tensor1(&[1, 1, 1])\n            ),\n            Tensor::from(arr3(&[[[3, 3, 3]]])),\n        );\n    }\n\n    #[test]\n    fn eval_2() {\n        assert_eq!(\n            eval(\n                strided_slice(0, 0, 0),\n                arr3(&[[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]], [[5, 5, 5], [6, 6, 6]],]),\n                tensor1(&[1, 0, 0]),\n                tensor1(&[2, 2, 3]),\n                tensor1(&[1, 1, 1])\n            ),\n            Tensor::from(arr3(&[[[3, 3, 3], [4, 4, 4]]])),\n        );\n    }\n\n    #[test]\n    fn eval_3_negative_stride() {\n        assert_eq!(\n            eval(\n                strided_slice(0, 0, 0),\n                arr3(&[[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]], [[5, 5, 5], [6, 6, 6]],]),\n                tensor1(&[1, -1, 0]),\n                tensor1(&[2, -3, 3]),\n                tensor1(&[1, -1, 1])\n            ),\n            Tensor::from(arr3(&[[[4, 4, 4], [3, 3, 3]]])),\n        );\n    }\n\n    #[test]\n    fn eval_3_bis() {\n        assert_eq!(\n            eval(\n                strided_slice(0, 0, 0),\n                arr1(&[0, 1]),\n                tensor1(&[-1]),\n                tensor1(&[-3]),\n                tensor1(&[-1])\n            ),\n            Tensor::from(arr1(&[1, 0]))\n        );\n    }\n\n    #[test]\n    fn eval_4() {\n        assert_eq!(\n            eval(\n                strided_slice(0, 0, 0),\n                tensor3(&[[[1, 1, 1], [2, 2, 2]], [[3, 3, 3], [4, 4, 4]], [[5, 5, 5], [6, 6, 6]],]),\n                tensor1(&[1, 0, 0]),\n                tensor1(&[2, 2, 4]),\n                tensor1(&[1, 1, 2])\n            ),\n            tensor3(&[[[3, 3], [4, 4]]]),\n        );\n    }\n\n    #[test]\n    fn eval_5() {\n        assert_eq!(\n            eval(\n                strided_slice(0, 0, 0),\n                tensor1(&[0, 0]),\n                tensor1(&[0]),\n                tensor1(&[-1]),\n                tensor1(&[1])\n            ),\n            tensor1(&[0])\n        )\n    }\n\n    #[test]\n    fn eval_6() {\n        assert_eq!(\n            eval(\n                strided_slice(0, 0, 0),\n                tensor2(&[[1, 0, 0, 0], [3, 0, 0, 0], [0, 0, 0, 0]]),\n                tensor1(&[-3, -4]),\n                tensor1(&[-1, -1]),\n                tensor1(&[1, 2])\n            ),\n            tensor2(&[[1, 0], [3, 0]])\n        )\n    }\n\n    #[test]\n    fn eval_7() {\n        assert_eq!(\n            eval(\n                strided_slice(0, 0, 0),\n                tensor2(&[[0, 6], [0, 0]]),\n                tensor1(&[0]),\n                tensor1(&[2]),\n                tensor1(&[1])\n            ),\n            tensor2(&[[0, 6], [0, 0]])\n        )\n    }\n\n    #[test]\n    fn eval_begin_mask_1() {\n        let mut op = strided_slice(0, 0, 0);\n        op.begin_mask = 1;\n        assert_eq!(\n            eval(op, tensor1(&[0, 1]), tensor1(&[1]), tensor1(&[1]), tensor1(&[1])),\n            tensor1(&[0])\n        )\n    }\n\n    #[test]\n    fn eval_shrink_1() {\n        let mut op = strided_slice(0, 0, 0);\n        op.shrink_axis_mask = 1;\n        assert_eq!(\n            eval(op, arr2(&[[0]]), tensor1(&[0, 0]), tensor1(&[0, 0]), tensor1(&[1, 1])),\n            tensor1::<i32>(&[])\n        )\n    }\n\n    #[test]\n    fn eval_shrink_to_scalar() {\n        let mut op = strided_slice(0, 0, 0);\n        op.shrink_axis_mask = 1;\n        assert_eq!(\n            eval(op, tensor1(&[0]), tensor1(&[0]), tensor1(&[0]), tensor1(&[1])),\n            tensor0::<i32>(0)\n        )\n    }\n\n    #[test]\n    fn inference_1() {\n        let mut op = strided_slice(5, 7, 0);\n        let input = InferenceFact::default().with_datum_type(DatumType::F32);\n        let begin = InferenceFact::from(tensor1(&[0i32, 2, 0]));\n        let end = InferenceFact::from(tensor1(&[0i32, 0, 0]));\n        let strides = InferenceFact::from(tensor1(&[1i32, 1, 1]));\n        let any = InferenceFact::default();\n\n        let (input_facts, output_facts, _) =\n            op.infer_facts(tvec![&input, &begin, &end, &strides], tvec![&any], tvec!()).unwrap();\n        assert_eq!(\n            input_facts,\n            tvec![\n                InferenceFact::default()\n                    .with_datum_type(DatumType::F32)\n                    .with_shape(shapefactoid![..]),\n                begin,\n                end,\n                strides,\n            ]\n        );\n        assert_eq!(\n            output_facts,\n            tvec![\n                InferenceFact::default()\n                    .with_datum_type(DatumType::F32)\n                    .with_shape(shapefactoid![..]),\n            ]\n        );\n    }\n\n    #[test]\n    fn inference_2() {\n        let mut op = strided_slice(1, 1, 2);\n        let input = InferenceFact::default().with_datum_type(DatumType::F32);\n        let begin = InferenceFact::from(tensor1(&[0i32, 0]));\n        let end = InferenceFact::from(tensor1(&[0i32, 1]));\n        let strides = InferenceFact::from(tensor1(&[1i32, 1]));\n        let any = InferenceFact::default();\n\n        let (input_facts, output_facts, _) =\n            op.infer_facts(tvec![&input, &begin, &end, &strides], tvec![&any], tvec!()).unwrap();\n        assert_eq!(\n            input_facts,\n            tvec![\n                InferenceFact::default()\n                    .with_datum_type(DatumType::F32)\n                    .with_shape(shapefactoid![..]),\n                begin,\n                end,\n                strides,\n            ]\n        );\n        assert_eq!(\n            output_facts,\n            tvec![\n                InferenceFact::default()\n                    .with_datum_type(DatumType::F32)\n                    .with_shape(shapefactoid![..]),\n            ]\n        );\n    }\n\n    #[test]\n    fn inference_3() {\n        let table = SymbolScope::default();\n        let s = table.new_with_prefix(\"S\").to_dim();\n        let mut op = strided_slice(5, 7, 0);\n        let input = f32::fact(dims!(1, s.clone() - 2, 16)).into();\n        let begin = InferenceFact::from(tensor1(&[0i32, 2, 0]));\n        let end = InferenceFact::from(tensor1(&[0i32, 0, 0]));\n        let strides = InferenceFact::from(tensor1(&[1i32, 1, 1]));\n        let any = InferenceFact::default();\n\n        let (_, output_facts, _) =\n            op.infer_facts(tvec![&input, &begin, &end, &strides], tvec![&any], tvec!()).unwrap();\n\n        assert_eq!(output_facts, tvec![f32::fact(dims!(1, s - 4, 16)).into()]);\n    }\n\n    #[test]\n    fn prep_1() {\n        let op = strided_slice(0, 0, 0);\n        assert_eq!(\n            op.prepare_one_dim(0, &4.to_dim(), &tensor1(&[-1i64]), &tensor1(&[i64::MIN]), &[-1])\n                .unwrap(),\n            Dim { begin: 3.to_dim(), end: (-1).to_dim(), stride: -1, shrink: false }\n        );\n    }\n\n    #[test]\n    fn prep_pytorch_onnx_bug_workadound() {\n        let op = strided_slice(0, 0, 0);\n        assert_eq!(\n            op.prepare_one_dim(\n                0,\n                &4.to_dim(),\n                &tensor1(&[-1i64]),\n                &tensor1(&[i64::MIN + 1]),\n                &[-1]\n            )\n            .unwrap(),\n            Dim { begin: 3.to_dim(), end: (-1).to_dim(), stride: -1, shrink: false }\n        );\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/array/tile.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Tile;\n\nimpl Expansion for Tile {\n    fn name(&self) -> StaticName {\n        \"Tile\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        s.equals(&inputs[1].rank, 1)?;\n        s.equals(&inputs[1].shape[0], inputs[0].rank.bex().to_dim())?;\n        s.given(&inputs[1].value, move |s, mult| {\n            for (ix, m) in\n                mult.cast_to::<TDim>()?.try_as_plain()?.as_slice::<TDim>()?.iter().enumerate()\n            {\n                if let Some(m) = m.as_i64() {\n                    s.equals(m * inputs[0].shape[ix].bex(), &outputs[0].shape[ix])?;\n                } else {\n                    let m = m.clone();\n                    s.given(&inputs[0].shape[ix], move |s, input| {\n                        s.equals(input * &m, &outputs[0].shape[ix])\n                    })?;\n                }\n            }\n            Ok(())\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(ref mult) = target.outlet_fact(inputs[1])?.konst {\n            let mult: TVec<TDim> =\n                mult.cast_to::<TDim>()?.try_as_plain()?.as_slice::<TDim>()?.into();\n            target.wire_node(prefix, tract_core::ops::array::Tile::new(mult), &inputs[0..1])\n        } else {\n            bail!(\"shape input is variable\")\n        }\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/binary.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\nuse tract_core::broadcast::multi_broadcast;\nuse tract_core::ops as mir;\nuse tract_core::ops::binary::BinMiniOp;\npub use tract_core::ops::cast::wire_cast;\npub use tract_core::ops::change_axes::wire_rank_broadcast;\n\n#[derive(Debug, Clone)]\npub struct InferenceBinOp(pub Box<dyn BinMiniOp>);\n\nimpl Expansion for InferenceBinOp {\n    fn name(&self) -> StaticName {\n        self.0.name().into()\n    }\n\n    fn validation(&self) -> Validation {\n        self.0.validation()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        rules(s, inputs, outputs, move |typa, typb| self.0.result_datum_type(typa, typb))\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let operating_datum_type = self.0.operating_datum_type(\n            target.outlet_fact(inputs[0])?.datum_type,\n            target.outlet_fact(inputs[1])?.datum_type,\n        )?;\n        let wires = wire_rank_broadcast(prefix, target, inputs)?;\n        let wires = wire_cast(prefix, target, &wires, operating_datum_type)?;\n        target.wire_node(prefix, mir::binary::TypedBinOp(self.0.clone(), None), &wires)\n    }\n}\n\npub fn rules<'r, 'p: 'r, 's: 'r, DT: Fn(DatumType, DatumType) -> TractResult<DatumType> + 'p>(\n    s: &mut Solver<'r>,\n    inputs: &'p [TensorProxy],\n    outputs: &'p [TensorProxy],\n    dt: DT,\n) -> InferenceResult {\n    check_input_arity(inputs, 2)?;\n    check_output_arity(outputs, 1)?;\n\n    /*\n    s.with(&inputs[0].shape, move |s, a_shape| {\n        s.with(&inputs[1].shape, move |s, b_shape| {\n            /*\n            if let Some(c_shape) =\n                crate::infer::helpers::infer_shape_broadcasting(&[&a_shape, &b_shape])\n                    .with_context(|| {\n                        format!(\n                            \"Matching {a_shape:?} and {b_shape:?} with numpy/onnx broadcast rules\"\n                        )\n                    })?\n            {\n                s.equals(&outputs[0].shape, c_shape)?;\n            }\n            Ok(())\n        })\n        */\n    })?;\n    */\n    s.given_2(&inputs[0].shape, &inputs[1].shape, move |s, a, b| {\n        s.equals(&outputs[0].shape, multi_broadcast(&[a, b])?)\n    })?;\n    s.given_2(&inputs[0].datum_type, &inputs[1].datum_type, move |s, typa, typb| {\n        s.equals(&outputs[0].datum_type, dt(typa, typb)?)\n    })?;\n    Ok(())\n}\n\npub trait BinIntoHir {\n    fn into_hir(self) -> Box<dyn InferenceOp>;\n}\n\nimpl<B: BinMiniOp> BinIntoHir for B {\n    fn into_hir(self) -> Box<dyn InferenceOp> {\n        expand(InferenceBinOp(Box::new(self) as _))\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct Nary(pub Box<dyn mir::binary::BinMiniOp>, pub bool);\n\nimpl Nary {\n    fn normalize_t<T>(t: &mut Tensor, n: usize) -> TractResult<()>\n    where\n        T: Datum + std::ops::DivAssign<T> + Copy,\n        usize: tract_num_traits::AsPrimitive<T>,\n    {\n        use tract_num_traits::AsPrimitive;\n        let mut t_plain = t.try_as_plain_mut()?;\n        let mut t = t_plain.to_array_view_mut::<T>()?;\n        let n: T = n.as_();\n        t /= &tract_ndarray::arr0(n);\n        Ok(())\n    }\n}\n\nimpl Op for Nary {\n    fn name(&self) -> StaticName {\n        format!(\"{}Nary\", self.0.name()).into()\n    }\n\n    fn validation(&self) -> Validation {\n        self.0.validation()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for Nary {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let mut t = inputs[0].clone().into_tensor();\n        for i in inputs[1..].iter() {\n            let mut i = i.clone().into_tensor();\n            let operating_datum_type =\n                self.0.operating_datum_type(t.datum_type(), i.datum_type())?;\n            if i.datum_type() != operating_datum_type {\n                i = i.cast_to_dt(operating_datum_type)?.into_owned();\n            }\n            if t.datum_type() != operating_datum_type {\n                t = t.cast_to_dt(operating_datum_type)?.into_owned();\n            }\n            t = self.0.eval(t.into_tvalue(), i.into_tvalue(), operating_datum_type)?;\n        }\n        if self.1 {\n            dispatch_numbers!(Self::normalize_t(t.datum_type())(&mut t, inputs.len()))?;\n        }\n        Ok(tvec!(t.into_tvalue()))\n    }\n}\n\nimpl InferenceRulesOp for Nary {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1)?;\n        let n = inputs.len();\n        s.given_all(\n            (0..n).map(|i| (&inputs[i].datum_type).bex()),\n            move |s, types: Vec<DatumType>| {\n                let dt = DatumType::super_type_for(&types)\n                    .with_context(|| format!(\"No super type for {types:?}\"))?;\n                let dt = self.0.operating_datum_type(dt, dt)?;\n                let result = self.0.result_datum_type(dt, dt)?;\n                s.equals(&outputs[0].datum_type, result)\n            },\n        )?;\n        s.given_all(inputs.iter().map(|i| &i.shape), move |s, shapes: Vec<TVec<TDim>>| {\n            let out = tract_core::broadcast::multi_broadcast(&shapes)?;\n            s.equals(&outputs[0].shape, ShapeFactoid::from(out))\n        })\n    }\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        let inputs = node.inputs.iter().map(|i| mapping[i]).collect::<Vec<_>>();\n        let types = inputs\n            .iter()\n            .map(|i| Ok(target.outlet_fact(*i)?.datum_type))\n            .collect::<TractResult<Vec<_>>>()?;\n        let dt = DatumType::super_type_for(&types)\n            .with_context(|| format!(\"No super type for {types:?}\"))?;\n        let operating = self.0.operating_datum_type(dt, dt)?;\n        let inputs = wire_cast(&node.name, target, &inputs, operating)?;\n        let mut wire = inputs[0];\n        for (ix, i) in inputs[1..].iter().enumerate() {\n            let wires = wire_rank_broadcast(format!(\"{}.{}\", node.name, ix), target, &[wire, *i])?;\n            wire = target.wire_node(\n                format!(\"{}.{}\", node.name, ix),\n                mir::binary::TypedBinOp(self.0.clone(), None),\n                &wires,\n            )?[0];\n        }\n        if self.1 {\n            let n = tensor0(inputs.len() as i32)\n                .cast_to_dt(node.outputs[0].fact.datum_type.concretize().unwrap())?\n                .into_owned()\n                .broadcast_into_rank(target.outlet_fact(inputs[0])?.rank())?;\n            let n = target.add_const(format!(\"{}.n\", node.name), n.into_arc_tensor())?;\n            wire = target.wire_node(\n                format!(\"{}.norm\", node.name),\n                crate::ops::math::div(),\n                &[wire, n],\n            )?[0];\n        }\n        Ok(tvec!(wire))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "hir/src/ops/cast.rs",
    "content": "use crate::infer::*;\nuse tract_core::internal::*;\n\nuse tract_core::ops::cast::Cast;\npub use tract_core::ops::cast::cast;\n\nimpl InferenceRulesOp for Cast {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        s.equals(&outputs[0].datum_type, self.to)?;\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/cnn/conv.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\nuse crate::ops::cast::cast;\n\nuse tract_core::ops::cnn::conv::KernelFormat;\nuse tract_core::ops::cnn::{PaddingSpec, PoolSpec};\nuse tract_core::ops::nn::DataFormat;\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\npub struct Conv {\n    pub data_format: DataFormat,\n    pub kernel_fmt: KernelFormat,\n    pub dilations: Option<TVec<usize>>,\n    pub kernel_shape: Option<TVec<usize>>,\n    pub padding: PaddingSpec,\n    pub strides: Option<TVec<usize>>,\n    pub group: Option<usize>,\n\n    pub x_scale_input: Option<usize>,\n    pub x_zero_point_input: Option<usize>,\n    pub k_input: Option<usize>,\n    pub k_scale_input: Option<usize>,\n    pub k_zero_point_input: Option<usize>,\n\n    pub y_scale_input: Option<usize>,\n    pub y_zero_point_input: Option<usize>,\n\n    pub bias_input: Option<usize>,\n\n    pub override_output_datum_type: Option<DatumType>,\n}\n\nimpl Conv {\n    pub fn hwc(self) -> Conv {\n        Conv { data_format: DataFormat::HWC, ..self }\n    }\n\n    pub fn nhwc(self) -> Conv {\n        Conv { data_format: DataFormat::NHWC, ..self }\n    }\n\n    pub fn hwio(self) -> Conv {\n        Conv { kernel_fmt: KernelFormat::HWIO, ..self }\n    }\n\n    pub fn padding(self, padding: PaddingSpec) -> Conv {\n        Conv { padding, ..self }\n    }\n\n    pub fn dilations(self, dilations: TVec<usize>) -> Conv {\n        Conv { dilations: Some(dilations), ..self }\n    }\n\n    pub fn group(self, group: usize) -> Conv {\n        Conv { group: Some(group), ..self }\n    }\n\n    pub fn strides(self, strides: TVec<usize>) -> Conv {\n        Conv { strides: Some(strides), ..self }\n    }\n\n    pub fn kernel_shape(self, kernel_shape: TVec<usize>) -> Conv {\n        Conv { kernel_shape: Some(kernel_shape), ..self }\n    }\n\n    pub fn bias_input(self, input: usize) -> Conv {\n        Conv { bias_input: Some(input), ..self }\n    }\n\n    pub fn x_zero_point_input(self, input: usize) -> Conv {\n        Conv { x_zero_point_input: Some(input), ..self }\n    }\n\n    pub fn k_zero_point_input(self, input: usize) -> Conv {\n        Conv { k_zero_point_input: Some(input), ..self }\n    }\n\n    pub fn output_shape<D: DimLike>(&self, ishape: &[D], kshape: &[usize]) -> TractResult<TVec<D>> {\n        debug_assert_eq!(\n            ishape.len()\n                + (self.data_format == DataFormat::HWC || self.data_format == DataFormat::CHW)\n                    as usize,\n            kshape.len(),\n            \"Input and kernel ranks are inconsistent\"\n        );\n        let mut result: TVec<D> = ishape.into();\n        let ishape = self.data_format.shape(ishape)?;\n        let spatial_rank = ishape.hw_rank();\n        let ones = tvec![1; spatial_rank];\n        let kernel_spatial_shape = self.kernel_fmt.hw(kshape);\n        let computed = self.padding.compute(\n            ishape.hw_dims(),\n            kernel_spatial_shape,\n            self.dilations.as_ref().unwrap_or(&ones),\n            self.strides.as_ref().unwrap_or(&ones),\n        );\n        let channels_out = *self.kernel_fmt.o(kshape);\n        result[ishape.c_axis()] = channels_out.into();\n        for (ix, d) in computed.iter().enumerate() {\n            result[ishape.h_axis() + ix] = d.convoluted.clone();\n        }\n        Ok(result)\n    }\n}\n\nimpl Expansion for Conv {\n    fn name(&self) -> StaticName {\n        \"ConvHir\".into()\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Rounding\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        if inputs.len() < 2 {\n            bail!(\"Wrong number of inputs. Expected 2 or more, got {}\", inputs.len());\n        }\n        let has_n = self.data_format == DataFormat::NHWC || self.data_format == DataFormat::NCHW;\n        let k_input = &inputs[self.k_input.unwrap_or(1)];\n        if let Some(kshape) = &self.kernel_shape {\n            s.equals(&k_input.rank, kshape.len() as i64 + 2)?;\n            for (ix, dim) in kshape.iter().enumerate() {\n                s.equals(&k_input.shape[ix + self.kernel_fmt.h_axis()], TDim::from(*dim as i64))?;\n            }\n        }\n        s.equals(&inputs[0].rank, k_input.rank.bex() + (has_n as usize as i64 - 1))?;\n        s.equals(&outputs[0].rank, &inputs[0].rank)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &k_input.datum_type)?;\n        if let Some(dt) = self.override_output_datum_type {\n            s.equals(&outputs[0].datum_type, dt)?;\n        } else {\n            s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        }\n        if let Some(bias) = self.bias_input {\n            // bias datum type is ill-defined. no check\n            s.equals(&inputs[bias].rank, 1)?;\n            s.given(&k_input.rank, move |s, krank| {\n                let filter_o = match self.kernel_fmt {\n                    KernelFormat::OIHW => &k_input.shape[0],\n                    KernelFormat::HWIO => &k_input.shape[krank as usize - 1],\n                    KernelFormat::OHWI => &k_input.shape[0],\n                };\n                s.equals(&inputs[bias].shape[0], filter_o)\n            })?\n        }\n        s.given_2(&inputs[0].rank, &k_input.rank, move |s, irank, krank| {\n            let input_c =\n                if self.data_format == DataFormat::NHWC || self.data_format == DataFormat::HWC {\n                    &inputs[0].shape[irank as usize - 1]\n                } else {\n                    &inputs[0].shape[1]\n                };\n            let filter_i = match self.kernel_fmt {\n                KernelFormat::OIHW => &k_input.shape[1],\n                KernelFormat::HWIO => &k_input.shape[krank as usize - 2],\n                KernelFormat::OHWI => &k_input.shape[krank as usize - 1],\n            };\n            s.equals(input_c.bex(), self.group.unwrap_or(1) as i64 * filter_i.bex())\n        })?;\n        s.given_2(&inputs[0].shape, &k_input.shape, move |s, ishape, kshape| {\n            if let Some(kshape) =\n                kshape.iter().map(|d| d.to_usize().ok()).collect::<Option<TVec<_>>>()\n            {\n                let oshape = self.output_shape(&ishape, &kshape)?;\n                s.equals(&outputs[0].shape, oshape)?;\n            }\n            Ok(())\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let kernel_input = self.k_input.unwrap_or(1);\n        let kernel_fact = model.outlet_fact(inputs[kernel_input])?.clone();\n        let input = model.outlet_fact(inputs[0])?.clone();\n        let input_shape = self.data_format.shape(&input.shape)?;\n        let kernel_full_shape =\n            kernel_fact.shape.as_concrete().context(\"Expect concrete shape for kernel\")?;\n        let group = self.group.unwrap_or(1);\n        let input_channels = self.kernel_fmt.input_channels(kernel_full_shape, group).into_owned();\n        let output_channels =\n            self.kernel_fmt.output_channels(kernel_full_shape, group).into_owned();\n        if input_shape.c_dim() != &input_channels.to_dim() {\n            bail!(\"Input has {} channels, kernel expects {}\", input_shape.c_dim(), input_channels)\n        }\n        let bias_dt =\n            if input.datum_type.is_float() { input.datum_type } else { i32::datum_type() };\n        let mut bias = if let Some(slot) = self.bias_input {\n            model.wire_node(format!(\"{prefix}.bias\"), cast(bias_dt), &[inputs[slot]])?[0]\n        } else {\n            model.add_const(format!(\"{prefix}.bias\"), Tensor::zero_scalar_dt(bias_dt)?)?\n        };\n        while let Some(axis) = model\n            .outlet_fact(bias)?\n            .shape\n            .to_tvec()\n            .iter()\n            .enumerate()\n            .rev()\n            .position(|(_, dim)| dim.is_one())\n        {\n            bias =\n                model.wire_node(format!(\"{prefix}.bias_rm_{axis}\"), AxisOp::Rm(axis), &[bias])?[0];\n        }\n        let mut wires = vec![inputs[0], inputs[kernel_input], bias];\n        let pool_spec = PoolSpec {\n            data_format: self.data_format,\n            padding: self.padding.clone(),\n            strides: self.strides.clone(),\n            dilations: self.dilations.clone(),\n            kernel_shape: self.kernel_fmt.hw(kernel_full_shape).into(),\n            input_channels,\n            output_channels,\n        };\n\n        let quantized = self.k_zero_point_input.is_some()\n            || self.k_scale_input.is_some()\n            || self.x_zero_point_input.is_some()\n            || self.x_scale_input.is_some()\n            || self.y_zero_point_input.is_some()\n            || self.y_scale_input.is_some();\n        let output_type = self.override_output_datum_type.unwrap_or(input.datum_type);\n        if quantized {\n            let zero = model.add_const(format!(\"{prefix}.zero\"), tensor0(0i32))?;\n            let one = model.add_const(format!(\"{prefix}.one\"), tensor0(1f32))?;\n\n            macro_rules! qp {\n                ($id: ident, $def: expr, $ty: ty) => {\n                    let wire = self.$id.map(|i| inputs[i]).unwrap_or($def);\n                    let wire = model.wire_node(\n                        format!(\"{prefix}.cast_{}\", stringify!($id)),\n                        cast(<$ty>::datum_type()),\n                        &[wire],\n                    )?[0];\n                    wires.push(wire);\n                };\n            }\n\n            qp!(x_zero_point_input, zero, i32);\n            qp!(x_scale_input, one, f32);\n            qp!(k_zero_point_input, zero, i32);\n            qp!(k_scale_input, one, f32);\n            qp!(y_zero_point_input, zero, i32);\n            qp!(y_scale_input, one, f32);\n        };\n\n        let reduced = tract_core::ops::cnn::Conv::new(\n            pool_spec,\n            self.kernel_fmt,\n            group,\n            Some(output_type).filter(|_| quantized),\n        );\n        model.wire_node(prefix, reduced, &wires)\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::setup_test_logger;\n\n    #[test]\n    fn test_infer_with_known_kshape() {\n        let mut op = expand(Conv::default().strides(tvec![2, 2]).kernel_shape(tvec![3, 3]));\n        let ifact = f32::fact([1, 1, 7, 5]).into();\n        let kfact = f32::fact([1, 1, 3, 3]).into();\n        let ofact = InferenceFact::default();\n        let facts = op.infer_facts(tvec!(&ifact, &kfact), tvec!(&ofact), tvec!()).unwrap();\n        assert_eq!(facts.1, tvec!(f32::fact([1, 1, 3, 2]).into()));\n    }\n\n    #[test]\n    fn test_infer_channels() {\n        let mut op = expand(Conv::default()); // NCHW - OIHW\n        let ifact = f32::fact([1, 2, 1, 1]).into();\n        let kfact = f32::fact([3, 2, 1, 1]).into();\n        let ofact = InferenceFact::default();\n        let facts = op.infer_facts(tvec!(&ifact, &kfact), tvec!(&ofact), tvec!()).unwrap();\n        assert_eq!(facts.1, tvec!(f32::fact([1, 3, 1, 1]).into()));\n    }\n\n    #[test]\n    fn test_infer_onnx_strides_no_padding() {\n        let mut op = expand(Conv::default().strides(tvec![2, 2]));\n        let ifact = f32::fact([1, 1, 7, 5]).into();\n        let kfact = f32::fact([1, 1, 3, 3]).into();\n        let ofact = InferenceFact::default();\n        let facts = op.infer_facts(tvec!(&ifact, &kfact), tvec!(&ofact), tvec!()).unwrap();\n        assert_eq!(facts.1, tvec!(f32::fact([1, 1, 3, 2]).into()));\n    }\n\n    #[test]\n    fn test_infer_nhwc_1() {\n        let mut op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let ifact = f32::fact([1, 2, 2, 2]).into();\n        let kfact = f32::fact([2, 2, 2, 1]).into();\n        let ofact = InferenceFact::default();\n        let facts = op.infer_facts(tvec!(&ifact, &kfact), tvec!(&ofact), tvec!()).unwrap();\n        assert_eq!(facts.1, tvec!(f32::fact([1, 2, 2, 1]).into()));\n    }\n\n    #[test]\n    fn test_eval_nhwc_1() -> TractResult<()> {\n        setup_test_logger();\n        let op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let res = op.eval(tvec!(\n            Tensor::zero::<f32>(&[1, 2, 2, 2]).unwrap().into_tvalue(),\n            Tensor::zero::<f32>(&[2, 2, 2, 1]).unwrap().into_tvalue(),\n        ))?;\n        Tensor::zero::<f32>(&[1, 2, 2, 1]).unwrap().close_enough(&res[0], false)\n    }\n\n    #[test]\n    fn test_infer_nhwc_2() {\n        setup_test_logger();\n        let mut op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let ifact = f32::fact([1, 1, 2, 2]).into();\n        let kfact = f32::fact([2, 1, 2, 1]).into();\n        let ofact = InferenceFact::default();\n        let facts = op.infer_facts(tvec!(&ifact, &kfact), tvec!(&ofact), tvec!()).unwrap();\n        assert_eq!(facts.1, tvec!(f32::fact([1, 1, 2, 1]).into()));\n    }\n\n    #[test]\n    fn test_eval_nhwc_2() {\n        setup_test_logger();\n        let op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let i = tensor4(&[[[[0.0f32, 0.0], [1.0, 0.0]]]]);\n        let k = tensor4(&[[[[0.0f32], [0.0]], [[1.0], [0.0]]]]);\n        let e = tensor4(&[[[[1.0f32], [0.0]]]]);\n        let res = op.eval(tvec!(i.into(), k.into())).unwrap();\n        res[0].close_enough(&e, Approximation::Approximate).unwrap();\n    }\n\n    #[test]\n    fn test_eval_nhwc_3() {\n        setup_test_logger();\n        let op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let i = tensor4(&[[[[0.0f32, 1.0], [2.0, 3.0]], [[10.0, 11.0], [12.0, 13.0]]]]);\n        let k = tensor4(&[[[[1.0f32, 0.0], [0.0, 1.0]]]]);\n        let res = op.eval(tvec!(i.clone().into(), k.into())).unwrap();\n        res[0].close_enough(&i, Approximation::Approximate).unwrap()\n    }\n\n    #[test]\n    fn test_eval_nhwc_batch() {\n        setup_test_logger();\n        let op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let result = op\n            .eval(tvec!(\n                tensor4(&[[[[2.0f32]]], [[[0.0f32]]]]).into(),\n                tensor4(&[[[[1.0f32]]]]).into()\n            ))\n            .unwrap();\n        result[0]\n            .close_enough(&tensor4(&[[[[2.0f32]]], [[[0.0f32]]]]), Approximation::Approximate)\n            .unwrap();\n    }\n\n    #[test]\n    fn test_infer_ntc_simple() {\n        let mut op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let ifact = f32::fact([1, 2, 1]).into();\n        let kfact = f32::fact([1, 1, 1]).into();\n        let ofact = InferenceFact::default();\n        let facts = op.infer_facts(tvec!(&ifact, &kfact), tvec!(&ofact), tvec!()).unwrap();\n        assert_eq!(facts.1, tvec!(f32::fact([1, 2, 1]).into()));\n    }\n\n    #[test]\n    fn test_eval_ntc_simple() {\n        let op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let result = op\n            .eval(tvec!(tensor3(&[[[2.0f32], [0.0f32]]]).into(), tensor3(&[[[1.0f32]]]).into()))\n            .unwrap();\n        result[0]\n            .close_enough(&tensor3(&[[[2.0f32], [0.0f32]]]), Approximation::Approximate)\n            .unwrap();\n    }\n\n    #[test]\n    fn test_infer_ntc_batch() {\n        let mut op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let ifact = f32::fact([2, 1, 1]).into();\n        let kfact = f32::fact([1, 1, 1]).into();\n        let ofact = InferenceFact::default();\n        let facts = op.infer_facts(tvec!(&ifact, &kfact), tvec!(&ofact), tvec!()).unwrap();\n        assert_eq!(facts.1, tvec!(f32::fact([2, 1, 1]).into()));\n    }\n\n    #[test]\n    fn test_eval_ntc_batch() {\n        let op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let result = op\n            .eval(tvec!(tensor3(&[[[2.0f32]], [[0.0f32]]]).into(), tensor3(&[[[1.0f32]]]).into()))\n            .unwrap();\n        result[0]\n            .close_enough(&tensor3(&[[[2.0f32]], [[0.0f32]]]), Approximation::Approximate)\n            .unwrap();\n    }\n\n    #[test]\n    fn test_infer_ntc_channel() {\n        let mut op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let ifact = f32::fact([1, 1, 2]).into();\n        let kfact = f32::fact([1, 2, 1]).into();\n        let ofact = InferenceFact::default();\n        let facts = op.infer_facts(tvec!(&ifact, &kfact), tvec!(&ofact), tvec!()).unwrap();\n        assert_eq!(facts.1, tvec!(f32::fact([1, 1, 1]).into()));\n    }\n\n    #[test]\n    fn test_eval_ntc_channel() {\n        let op = expand(Conv::default().nhwc().hwio().padding(PaddingSpec::SameUpper));\n        let result = op\n            .eval(tvec!(\n                tensor3(&[[[2.0f32, 0.0f32]]]).into(),\n                tensor3(&[[[1.0f32], [0.0f32]]]).into()\n            ))\n            .unwrap();\n        result[0].close_enough(&tensor3(&[[[2.0f32]]]), Approximation::Approximate).unwrap();\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/cnn/mod.rs",
    "content": "mod conv;\nmod pools;\n\npub use conv::Conv;\npub use pools::{HirMaxPool, HirSumPool};\npub use tract_core::ops::cnn::{PaddingSpec, PoolSpec};\n"
  },
  {
    "path": "hir/src/ops/cnn/pools.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\nuse tract_core::ops::cnn::MaxPool;\nuse tract_core::ops::cnn::PoolSpec;\nuse tract_core::ops::cnn::SumPool;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct HirSumPool {\n    pub pool_spec: PoolSpec,\n    pub count_include_pad: bool,\n    pub normalize: bool,\n}\n\nimpl Expansion for HirSumPool {\n    fn name(&self) -> StaticName {\n        \"SumPool\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        rules_for_shape(&self.pool_spec, s, inputs, outputs)\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let c = self\n            .pool_spec\n            .data_format\n            .shape(&model.outlet_fact(inputs[0])?.shape)?\n            .c()\n            .to_usize()\n            .context(\"Expect constant integer depth\")?;\n        let pool_spec =\n            PoolSpec { input_channels: c, output_channels: c, ..self.pool_spec.clone() };\n        model.wire_node(\n            prefix,\n            SumPool {\n                pool_spec,\n                count_include_pad: self.count_include_pad,\n                normalize: self.normalize,\n            },\n            inputs,\n        )\n    }\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct HirMaxPool {\n    pub pool_spec: PoolSpec,\n    pub with_index_outputs: Option<DatumType>,\n}\n\nimpl Expansion for HirMaxPool {\n    fn name(&self) -> StaticName {\n        \"MaxPool\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1 + self.with_index_outputs.is_some() as usize)?;\n        s.equals(&outputs[0].rank, &inputs[0].rank)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        if let Some(idt) = self.with_index_outputs {\n            s.equals(&outputs[1].datum_type, idt)?;\n            s.equals(&outputs[1].shape, &outputs[0].shape)?;\n        }\n        rules_for_shape(&self.pool_spec, s, inputs, outputs)\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(1 + self.with_index_outputs.is_some() as usize)\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let c = self\n            .pool_spec\n            .data_format\n            .shape(&model.outlet_fact(inputs[0])?.shape)?\n            .c()\n            .to_usize()\n            .context(\"Expect constant integer depth\")?;\n        let pool_spec =\n            PoolSpec { input_channels: c, output_channels: c, ..self.pool_spec.clone() };\n        model.wire_node(\n            prefix,\n            MaxPool { pool_spec, with_index_outputs: self.with_index_outputs },\n            inputs,\n        )\n    }\n}\n\npub fn rules_for_shape<'r, 'p: 'r, 's: 'r>(\n    pool_spec: &'s PoolSpec,\n    s: &mut Solver<'r>,\n    inputs: &'p [TensorProxy],\n    outputs: &'p [TensorProxy],\n) -> InferenceResult {\n    s.equals(&outputs[0].rank, &inputs[0].rank)?;\n    s.given(&inputs[0].shape, move |s, ishape| {\n        let ishape = pool_spec.data_format.shape(ishape)?;\n        let ones = tvec![1; ishape.hw_rank()];\n        let computed = pool_spec.padding.compute(\n            ishape.hw_dims(),\n            &pool_spec.kernel_shape,\n            pool_spec.dilations.as_ref().unwrap_or(&ones),\n            pool_spec.strides.as_ref().unwrap_or(&ones),\n        );\n        for o in outputs {\n            for (ix, d) in computed.iter().enumerate() {\n                s.equals(&o.shape[ix + ishape.h_axis()], &d.convoluted)?;\n            }\n            if ishape.n_axis().is_some() {\n                s.equals(&o.shape[ishape.n_axis().unwrap()], ishape.n_dim().unwrap())?;\n            }\n            // hack for max and sum pool, convolutions know this and deal with it on their side\n            if pool_spec.input_channels == 0 && pool_spec.output_channels == 0 {\n                s.equals(&o.shape[ishape.c_axis()], ishape.c_dim())?;\n            }\n        }\n        Ok(())\n    })\n}\n"
  },
  {
    "path": "hir/src/ops/downsample.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::downsample::Downsample;\n\nimpl InferenceRulesOp for Downsample {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.given(&inputs[0].rank, move |s, r| {\n            for i in 0..(r as usize) {\n                if i == self.axis {\n                    s.given(&inputs[0].shape[i], move |s, d| {\n                        s.equals(\n                            &outputs[0].shape[i],\n                            (d - self.modulo).div_ceil(self.stride as u64),\n                        )\n                    })?\n                } else {\n                    s.equals(&inputs[0].shape[i], &outputs[0].shape[i])?\n                }\n            }\n            Ok(())\n        })\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/dummy.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::dummy::Dummy;\n\nimpl InferenceRulesOp for Dummy {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        _s: &mut Solver<'r>,\n        _inputs: &'p [TensorProxy],\n        _outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/element_wise.rs",
    "content": "use tract_core::ops::cast::wire_cast;\n\nuse crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Debug, Clone)]\npub struct ElementWiseOp(pub Box<dyn ElementWiseMiniOp>);\n\nimpl Expansion for ElementWiseOp {\n    fn name(&self) -> StaticName {\n        self.0.name().into()\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let operating_datum_type =\n            self.0.operating_datum_type(target.outlet_fact(inputs[0])?.datum_type);\n        let wires = wire_cast(prefix, target, inputs, operating_datum_type)?;\n        target.wire_node(\n            prefix,\n            tract_core::ops::element_wise::ElementWiseOp(self.0.clone(), None),\n            &wires,\n        )\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.given(&inputs[0].datum_type, move |s, dt| {\n            let dt = self.0.operating_datum_type(dt);\n            if let Some(dt) = self.0.output_type(dt) {\n                s.equals(&outputs[0].datum_type, dt)\n            } else {\n                s.equals(&outputs[0].datum_type, dt)\n            }\n        })?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n}\n\npub trait ElementWiseIntoHir {\n    fn into_hir(self) -> Box<dyn InferenceOp>;\n}\n\nimpl ElementWiseIntoHir for tract_core::ops::element_wise::ElementWiseOp {\n    fn into_hir(self) -> Box<dyn InferenceOp> {\n        expand(ElementWiseOp(self.0))\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/expandable.rs",
    "content": "use std::any::Any;\n\nuse crate::internal::*;\nuse tract_core::internal::*;\n\npub fn expand<E: Expansion>(e: E) -> Box<dyn InferenceOp> {\n    Box::new(Box::new(e) as Box<dyn Expansion>)\n}\n\npub trait Expansion:\n    tract_core::dyn_clone::DynClone\n    + std::fmt::Debug\n    + Send\n    + Sync\n    + tract_core::downcast_rs::Downcast\n    + Any\n{\n    fn name(&self) -> StaticName;\n    fn validation(&self) -> Validation {\n        Validation::Accurate\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![])\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(1)\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>>;\n\n    #[allow(unused_variables)]\n    fn wire_with_inference_model_and_node(\n        &self,\n        prefix: &str,\n        model: &InferenceModel,\n        node: &InferenceNode,\n        typed_model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        self.wire(prefix, typed_model, inputs)\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult;\n\n    fn is_stateless(&self) -> bool {\n        true\n    }\n}\n\ntract_core::dyn_clone::clone_trait_object!(Expansion);\n\nimpl PartialEq for Box<dyn Expansion> {\n    fn eq(&self, _other: &Self) -> bool {\n        false\n    }\n}\nimpl Eq for Box<dyn Expansion> {}\n\nimpl Op for Box<dyn Expansion> {\n    fn name(&self) -> StaticName {\n        self.as_ref().name()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        self.as_ref().info()\n    }\n\n    fn validation(&self) -> Validation {\n        self.as_ref().validation()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for Box<dyn Expansion> {\n    fn is_stateless(&self) -> bool {\n        self.as_ref().is_stateless()\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let mut adhoc = TypedModel::default();\n        let wires = inputs\n            .iter()\n            .enumerate()\n            .map(|(ix, i)| {\n                adhoc.add_source(\n                    format!(\"adhoc-source-{ix}\"),\n                    TypedFact::try_from(i.clone().into_arc_tensor())?,\n                )\n            })\n            .collect::<TractResult<TVec<OutletId>>>()?;\n\n        let wires = self.wire(\"adhoc\", &mut adhoc, &wires)?;\n        adhoc.select_output_outlets(&wires)?;\n        SimplePlan::new(adhoc)?.run(inputs)\n    }\n}\n\nimpl InferenceRulesOp for Box<dyn Expansion> {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        self.as_ref().rules(s, inputs, outputs)\n    }\n\n    fn to_typed(\n        &self,\n        source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        let inputs = node.inputs.iter().map(|i| mapping[i]).collect::<Vec<_>>();\n        let outputs =\n            self.wire_with_inference_model_and_node(&node.name, source, node, target, &inputs)?;\n        for (ix, o) in outputs.iter().enumerate() {\n            let expected = &node.outputs[ix].fact;\n            let got = target.outlet_fact(*o)?;\n            if expected.clone().unify_with(&InferenceFact::from(got)).is_err() {\n                bail!(\n                    \"Output mismatch after rewiring expansion for output #{}: expected {:?} got {:?}\",\n                    ix,\n                    expected,\n                    got\n                );\n            }\n        }\n        Ok(outputs)\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        self.as_ref().nboutputs()\n    }\n\n    as_op!();\n}\n\npub fn inference_wrap<O, R>(op: O, outputs: usize, rules: R) -> Box<dyn InferenceOp>\nwhere\n    O: TypedOp,\n    R: for<'r, 'p, 's> Fn(\n            &'s dyn Op,\n            &mut Solver<'r>,\n            &'p [TensorProxy],\n            &'p [TensorProxy],\n        ) -> InferenceResult\n        + Send\n        + Sync\n        + 'static,\n{\n    expand(InferenceWrapper { typed_op: Box::new(op), rules: Arc::new(rules), outputs })\n}\n\ntype RuleProducer = dyn for<'r, 'p, 's> Fn(\n        &'s dyn Op,\n        &mut Solver<'r>,\n        &'p [TensorProxy],\n        &'p [TensorProxy],\n    ) -> InferenceResult\n    + Send\n    + Sync\n    + 'static;\n\n#[derive(Clone, new)]\npub struct InferenceWrapper {\n    typed_op: Box<dyn TypedOp>,\n    rules: Arc<RuleProducer>,\n    outputs: usize,\n}\n\nimpl std::fmt::Debug for InferenceWrapper {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        std::fmt::Debug::fmt(&self.typed_op, f)\n    }\n}\n\nimpl Expansion for InferenceWrapper {\n    fn name(&self) -> StaticName {\n        self.typed_op.name()\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        model.wire_node(prefix, &self.typed_op, inputs)\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        (self.rules)(self.typed_op.as_op(), s, inputs, outputs)\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(self.outputs)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/identity.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::identity::Identity;\n\nimpl InferenceRulesOp for Identity {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/konst.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::konst::*;\n\nimpl InferenceRulesOp for Const {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 0)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].value, self.val().clone().bex())?;\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n"
  },
  {
    "path": "hir/src/ops/logic.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\nuse tract_core::broadcast::multi_broadcast;\nuse tract_core::ops::cast::wire_cast;\npub use tract_core::ops::change_axes::wire_with_rank_broadcast;\npub use tract_core::ops::logic::*;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct Iff;\n\nimpl Expansion for Iff {\n    fn name(&self) -> StaticName {\n        \"Iff\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, DatumType::Bool)?;\n        s.given_2(&inputs[1].datum_type, &inputs[2].datum_type, move |s, a, b| {\n            let dt = a\n                .common_super_type(b)\n                .with_context(|| format!(\"No super type for {a:?} and {b:?}\"))?;\n            s.equals(&outputs[0].datum_type, dt)\n        })?;\n        s.given_3(&inputs[0].shape, &inputs[1].shape, &inputs[2].shape, move |s, c, t, f| {\n            let shape = multi_broadcast(&[&c, &t, &f])?;\n            s.equals(&outputs[0].shape, shape)\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let dta = model.outlet_fact(inputs[1])?.datum_type;\n        let dtb = model.outlet_fact(inputs[2])?.datum_type;\n        let dt = dta\n            .common_super_type(dtb)\n            .with_context(|| format!(\"No super type for {dta:?} and {dtb:?}\"))?;\n        let mut casted = wire_cast(prefix, model, &inputs[1..], dt)?;\n        casted.insert(0, inputs[0]);\n        wire_with_rank_broadcast(prefix, model, tract_core::ops::logic::Iff, &casted)\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/matmul.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\nuse tract_core::ops::einsum::EinSum;\nuse tract_core::tract_data::itertools::Itertools;\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\npub struct MatMulInference {\n    pub a_trans: bool,\n    pub b_trans: bool,\n    pub c_trans: bool,\n}\n\nimpl MatMulInference {\n    pub fn with_a_trans(self, a_trans: bool) -> MatMulInference {\n        MatMulInference { a_trans, ..self }\n    }\n\n    pub fn with_b_trans(self, b_trans: bool) -> MatMulInference {\n        MatMulInference { b_trans, ..self }\n    }\n\n    pub fn with_c_trans(self, c_trans: bool) -> MatMulInference {\n        MatMulInference { c_trans, ..self }\n    }\n}\n\nimpl Expansion for MatMulInference {\n    fn name(&self) -> StaticName {\n        \"MatMulInference\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &inputs[1].datum_type)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.given_2(&inputs[0].shape, &inputs[1].shape, move |s, ashape, bshape| {\n            let (_, _, _, cshape) =\n                compute_shapes(ashape, bshape, self.a_trans, self.b_trans, self.c_trans)?;\n            s.equals(&outputs[0].shape, cshape)\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let a_rank = target.outlet_fact(inputs[0])?.rank();\n        let b_rank = target.outlet_fact(inputs[1])?.rank();\n        ensure!(a_rank > 1 || b_rank > 1);\n        let mk = if self.a_trans { \"km\" } else { \"mk\" };\n        let kn = if self.b_trans { \"nk\" } else { \"kn\" };\n        let mn = if self.c_trans { \"nm\" } else { \"mn\" };\n        let axes: AxesMapping = if a_rank == 1 {\n            let prefix: String = ('a'..).take(b_rank - 2).collect();\n            format!(\"k,{prefix}{kn}->{prefix}n\").parse()?\n        } else if b_rank == 1 {\n            let prefix: String = ('a'..).take(a_rank - 2).collect();\n            format!(\"{prefix}{mk},k->{prefix}m\").parse()?\n        } else {\n            let c_rank = b_rank.max(a_rank);\n            let a_prefix: String =\n                ('a'..).take(c_rank - 2).skip(b_rank.saturating_sub(a_rank)).collect();\n            let b_prefix: String =\n                ('a'..).take(c_rank - 2).skip(a_rank.saturating_sub(b_rank)).collect();\n            let c_prefix: String = ('a'..).take(c_rank - 2).collect();\n            format!(\"{a_prefix}{mk},{b_prefix}{kn}->{c_prefix}{mn}\").parse()?\n        };\n        let dt = target.outlet_fact(inputs[0])?.datum_type;\n        target.wire_node(prefix, EinSum { axes, operating_dt: dt, q_params: None }, inputs)\n    }\n}\n\n#[allow(clippy::type_complexity)]\npub fn compute_shapes<D: DimLike>(\n    mut ashape: TVec<D>,\n    mut bshape: TVec<D>,\n    a_trans: bool,\n    b_trans: bool,\n    c_trans: bool,\n) -> TractResult<(TVec<D>, TVec<D>, TVec<D>, TVec<D>)> {\n    let mut implicit_m = false;\n    let mut implicit_n = false;\n    if ashape.len() < 2 {\n        implicit_m = true;\n        ashape.insert(a_trans as usize, D::one());\n    }\n    if bshape.len() < 2 {\n        implicit_n = true;\n        bshape.insert(!b_trans as usize, D::one());\n    }\n    while ashape.len() < bshape.len() {\n        ashape.insert(0, D::one());\n    }\n    while bshape.len() < ashape.len() {\n        bshape.insert(0, D::one());\n    }\n    let c_bc_shape_prefix = tract_core::broadcast::multi_broadcast(&[\n        &ashape[..(ashape.len() - 2)],\n        &bshape[..(bshape.len() - 2)],\n    ])?;\n    let mut c_bc_shape: TVec<D> = c_bc_shape_prefix;\n    let (mut m, mut ka) = (ashape[ashape.len() - 2].clone(), ashape[ashape.len() - 1].clone());\n    let (mut kb, mut n) = (bshape[bshape.len() - 2].clone(), bshape[bshape.len() - 1].clone());\n    if a_trans {\n        std::mem::swap(&mut m, &mut ka);\n    }\n    if b_trans {\n        std::mem::swap(&mut kb, &mut n);\n    }\n    if !ka.compatible_with(&kb) {\n        bail!(\n            \"Inconsistent matmul: a: {} b: {}, a_trans: {} b_trans: {} c_trans: {}\",\n            ashape.iter().join(\",\"),\n            bshape.iter().join(\",\"),\n            a_trans,\n            b_trans,\n            c_trans\n        );\n    }\n    let mut c_shape_final = c_bc_shape.clone();\n    if c_trans {\n        c_bc_shape.push(n.clone());\n        c_bc_shape.push(m.clone());\n        if !implicit_n {\n            c_shape_final.push(n.clone());\n        }\n        if !implicit_m {\n            c_shape_final.push(m.clone());\n        }\n    } else {\n        c_bc_shape.push(m.clone());\n        c_bc_shape.push(n.clone());\n        if !implicit_m {\n            c_shape_final.push(m.clone());\n        }\n        if !implicit_n {\n            c_shape_final.push(n.clone());\n        }\n    }\n    Ok((ashape, bshape, c_bc_shape, c_shape_final))\n}\n"
  },
  {
    "path": "hir/src/ops/mod.rs",
    "content": "pub mod activations;\npub mod array;\npub mod binary;\npub mod cast;\npub mod cnn;\npub mod downsample;\npub mod dummy;\npub mod element_wise;\npub mod expandable;\npub mod identity;\npub mod konst;\npub mod logic;\npub use tract_core::ops::math;\npub mod matmul;\npub mod nn;\npub use tract_core::ops::quant;\n\npub mod scan;\npub mod source;\npub mod unimpl;\n"
  },
  {
    "path": "hir/src/ops/nn/global_pools.rs",
    "content": "use tract_core::ops::change_axes::wire_with_rank_broadcast;\n\nuse crate::infer::*;\nuse crate::internal::*;\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\npub struct GlobalAvgPool;\n\nimpl Expansion for GlobalAvgPool {\n    fn name(&self) -> StaticName {\n        \"GlobalAvgPool\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        solver: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        rules(solver, inputs, outputs)\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input = inputs[0];\n        let input_fact = target.outlet_fact(input)?.clone();\n        let axes = (2..input_fact.rank()).collect();\n        let wire = target.wire_node(\n            name.to_string() + \".sum\",\n            tract_core::ops::nn::Reduce::new(axes, tract_core::ops::nn::Reducer::Sum),\n            &[input],\n        )?;\n        let div = tensor0(input_fact.shape.iter().skip(2).product::<TDim>());\n        let div = target.add_const(format!(\"{name}.div\"), div)?;\n        let div = target.wire_node(\n            format!(\"{name}.casted\"),\n            tract_core::ops::cast::cast(input_fact.datum_type),\n            &[div],\n        )?;\n        wire_with_rank_broadcast(\n            format!(\"{name}.norm\"),\n            target,\n            tract_core::ops::math::div(),\n            &[wire[0], div[0]],\n        )\n    }\n}\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\npub struct GlobalLpPool(usize);\n\nimpl Expansion for GlobalLpPool {\n    fn name(&self) -> StaticName {\n        format!(\"GlobalL{}Pool\", self.0).into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        solver: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        rules(solver, inputs, outputs)\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input = inputs[0];\n        let input_fact = target.outlet_fact(input)?.clone();\n        let axes = (2..input_fact.rank()).collect();\n        let mut wire = tvec!(input);\n        if self.0 == 2 {\n            wire = target.wire_node(\n                name.to_string() + \".sqr\",\n                tract_core::ops::math::square(),\n                &wire,\n            )?;\n        } else {\n            let pow = tensor0(self.0 as f64)\n                .cast_to_dt(input_fact.datum_type)?\n                .into_owned()\n                .broadcast_into_rank(input_fact.rank())?\n                .into_arc_tensor();\n            let pow = target.add_const(name.to_string() + \".pow.cst\", pow)?;\n            wire = target.wire_node(\n                name.to_string() + \".pow\",\n                tract_core::ops::math::pow(),\n                &[wire[0], pow],\n            )?;\n        }\n        wire = target.wire_node(\n            name.to_string() + \".sum\",\n            tract_core::ops::nn::Reduce::new(axes, tract_core::ops::nn::Reducer::Sum),\n            &wire,\n        )?;\n        let div = tensor0(input_fact.shape.iter().skip(2).product::<TDim>().to_i64()? as f64)\n            .cast_to_dt(input_fact.datum_type)?\n            .into_owned()\n            .broadcast_into_rank(input_fact.rank())?;\n        let div = target.add_const(name.to_string() + \".div\", div)?;\n        wire = target.wire_node(\n            name.to_string() + \".norm\",\n            tract_core::ops::math::div(),\n            &[wire[0], div],\n        )?;\n        if self.0 == 2 {\n            wire = target.wire_node(\n                name.to_string() + \".sqrt\",\n                tract_core::ops::math::sqrt(),\n                &wire,\n            )?;\n        } else {\n            let anti_pow = tensor0((self.0 as f64).recip())\n                .cast_to_dt(input_fact.datum_type)?\n                .into_owned()\n                .broadcast_into_rank(input_fact.rank())?\n                .into_arc_tensor();\n            let anti_pow = target.add_const(name.to_string() + \".anti_pow\", anti_pow)?;\n            wire = target.wire_node(\n                name.to_string() + \".antipow\",\n                tract_core::ops::math::pow(),\n                &[wire[0], anti_pow],\n            )?;\n        }\n        Ok(wire)\n    }\n}\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\npub struct GlobalMaxPool;\n\nimpl Expansion for GlobalMaxPool {\n    fn name(&self) -> StaticName {\n        \"GlobalMaxPool\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        solver: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        rules(solver, inputs, outputs)\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input = inputs[0];\n        let input_fact = target.outlet_fact(input)?.clone();\n        let axes = (2..input_fact.rank()).collect();\n        target.wire_node(\n            name.to_string() + \".max\",\n            tract_core::ops::nn::Reduce::new(axes, tract_core::ops::nn::Reducer::Max),\n            &[input],\n        )\n    }\n}\n\nfn rules<'r, 'p: 'r, 's: 'r>(\n    s: &mut Solver<'r>,\n    inputs: &'p [TensorProxy],\n    outputs: &'p [TensorProxy],\n) -> InferenceResult {\n    check_input_arity(inputs, 1)?;\n    check_output_arity(outputs, 1)?;\n    s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n    s.equals(&outputs[0].rank, &inputs[0].rank)?;\n    s.equals(&outputs[0].shape[0], &inputs[0].shape[0])?;\n    s.equals(&outputs[0].shape[1], &inputs[0].shape[1])?;\n    s.given(&inputs[0].rank, move |s, rank| {\n        for i in 2..rank {\n            s.equals(&outputs[0].shape[i as usize], TDim::from(1))?;\n        }\n        Ok(())\n    })\n}\n"
  },
  {
    "path": "hir/src/ops/nn/layer_max.rs",
    "content": "use tract_core::ops::nn::Softmax;\n\nuse crate::infer::*;\nuse crate::internal::*;\n\n// TODO tricky to re-express in \"core\" because of the multiple hot point... do\n// we need one more reduce ?\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct LayerHardmax {\n    axis: isize,\n    coerce_to_2d: bool,\n}\n\nimpl Expansion for LayerHardmax {\n    fn name(&self) -> StaticName {\n        \"LayerHardmax\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        solver: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        rules(solver, inputs, outputs)\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        use tract_core::ops::{array, change_axes, nn};\n        let input = inputs[0];\n        let input_fact = target.outlet_fact(input)?.clone();\n        let input_dt = input_fact.datum_type;\n        let rank = input_fact.rank();\n        let axis = if self.axis < 0 { rank as isize + self.axis } else { self.axis } as usize;\n        let suffix_dim: TDim = input_fact.shape[axis..].iter().product();\n        let dim = if self.coerce_to_2d {\n            suffix_dim.to_usize()\n        } else {\n            input_fact.shape[axis].to_usize()\n        }\n        .context(\"Assumes known dimension on working axes suffix.\")?;\n        let off = tensor0(0f32).cast_to_dt(input_dt)?.into_owned().into_arc_tensor();\n        let on = tensor0(1f32).cast_to_dt(input_dt)?.into_owned().into_arc_tensor();\n        let mut wires = inputs.into();\n        if self.coerce_to_2d {\n            wires = target.wire_node(\n                format!(\"{name}.reshaped\"),\n                AxisOp::Reshape(axis, input_fact.shape[axis..].into(), tvec!(suffix_dim.clone())),\n                &[input],\n            )?;\n        }\n        wires = target.wire_node(\n            format!(\"{name}.argmax\"),\n            nn::Reduce::new(tvec!(axis), nn::Reducer::ArgMax(false)),\n            &wires,\n        )?;\n        wires =\n            target.wire_node(format!(\"{name}.rm_axis\"), change_axes::AxisOp::Rm(axis), &wires)?;\n        wires = target.wire_node(\n            format!(\"{name}.hardmax\"),\n            array::OneHot { axis, dim, off, on },\n            &wires,\n        )?;\n        if self.coerce_to_2d {\n            wires = target.wire_node(\n                format!(\"{name}.hardmax_reshaped\"),\n                AxisOp::Reshape(axis, tvec!(suffix_dim), input_fact.shape[axis..].into()),\n                &wires,\n            )?;\n        }\n        Ok(wires)\n    }\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct LayerLogSoftmax {\n    pub axis: isize,\n    pub coerce_to_2d: bool,\n}\n\nimpl Expansion for LayerLogSoftmax {\n    fn name(&self) -> StaticName {\n        \"LayerLogSoftmax\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        solver: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        rules(solver, inputs, outputs)\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let softmax = LayerSoftmax { axis: self.axis, coerce_to_2d: self.coerce_to_2d }\n            .wire(name, target, inputs)?;\n        target.wire_node(format!(\"{name}.logsoftmax\"), tract_core::ops::math::ln(), &softmax)\n    }\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct LayerSoftmax {\n    axis: isize,\n    coerce_to_2d: bool,\n}\n\nimpl Expansion for LayerSoftmax {\n    fn name(&self) -> StaticName {\n        \"LayerSoftmax\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        solver: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        rules(solver, inputs, outputs)\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input = inputs[0];\n        let rank = target.outlet_fact(input)?.rank();\n        let dt = target.outlet_fact(input)?.datum_type;\n        let axis = if self.axis < 0 { rank as isize + self.axis } else { self.axis } as usize;\n        let axes =\n            if self.coerce_to_2d { (axis..rank).collect::<TVec<usize>>() } else { tvec!(axis) };\n        let quant_output_dt = if dt.is_float() { None } else { Some(dt) };\n        target.wire_node(name, Softmax { axes, quant_output_dt, ..Softmax::default() }, inputs)\n    }\n}\n\nfn rules<'r, 'p: 'r, 's: 'r>(\n    s: &mut Solver<'r>,\n    inputs: &'p [TensorProxy],\n    outputs: &'p [TensorProxy],\n) -> InferenceResult {\n    check_output_arity(outputs, 1)?;\n    s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n    s.equals(&outputs[0].rank, &inputs[0].rank)?;\n    s.equals(&outputs[0].shape, &inputs[0].shape)?;\n    Ok(())\n}\n"
  },
  {
    "path": "hir/src/ops/nn/mod.rs",
    "content": "pub mod global_pools;\npub mod layer_max;\npub mod reduce;\npub mod softmax;\n\npub use global_pools::*;\npub use layer_max::*;\npub use reduce::{Reduce, Reducer};\npub use softmax::Softmax;\n\npub use tract_core::ops::nn::{DataFormat, hard_swish, sigmoid};\n"
  },
  {
    "path": "hir/src/ops/nn/reduce.rs",
    "content": "use crate::internal::*;\n\nuse tract_core::ops::nn::Reduce as TReduce;\nuse tract_core::ops::nn::Reducer as TReducer;\n\n#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]\npub enum Reducer {\n    ArgMax(bool), // take last\n    ArgMin(bool),\n    L1,\n    L2,\n    LogSum,\n    LogSumExp,\n    Max,\n    Mean,\n    Min,\n    Prod,\n    Sum,\n    SumSquare,\n}\n\nimpl Reducer {\n    pub fn wire(\n        &self,\n        axes: TVec<usize>,\n        name: &str,\n        target: &mut TypedModel,\n        mut wire: OutletId,\n    ) -> TractResult<OutletId> {\n        use Reducer::*;\n        use tract_core::ops::math;\n        match self {\n            ArgMax(last) => {\n                wire =\n                    target.wire_node(name, TReduce::new(axes, TReducer::ArgMax(*last)), &[wire])?[0]\n            }\n            ArgMin(last) => {\n                wire =\n                    target.wire_node(name, TReduce::new(axes, TReducer::ArgMin(*last)), &[wire])?[0]\n            }\n            Max => wire = target.wire_node(name, TReduce::new(axes, TReducer::Max), &[wire])?[0],\n            Min => wire = target.wire_node(name, TReduce::new(axes, TReducer::Min), &[wire])?[0],\n            Sum => wire = target.wire_node(name, TReduce::new(axes, TReducer::Sum), &[wire])?[0],\n            Prod => wire = target.wire_node(name, TReduce::new(axes, TReducer::Prod), &[wire])?[0],\n\n            L1 => {\n                wire = target.wire_node(format!(\"{name}.abs\"), math::abs(), &[wire])?[0];\n                wire = target.wire_node(\n                    format!(\"{name}.sum\"),\n                    TReduce::new(axes, TReducer::Sum),\n                    &[wire],\n                )?[0];\n            }\n            L2 => {\n                wire = target.wire_node(format!(\"{name}.sq\"), math::square(), &[wire])?[0];\n                wire = target.wire_node(\n                    format!(\"{name}.sum\"),\n                    TReduce::new(axes, TReducer::Sum),\n                    &[wire],\n                )?[0];\n                wire = target.wire_node(format!(\"{name}.sqrt\"), math::sqrt(), &[wire])?[0];\n            }\n            LogSum => {\n                wire = target.wire_node(\n                    format!(\"{name}.sum\"),\n                    TReduce::new(axes, TReducer::Sum),\n                    &[wire],\n                )?[0];\n                wire = target.wire_node(format!(\"{name}.ln\"), math::ln(), &[wire])?[0];\n            }\n            LogSumExp => {\n                wire = target.wire_node(format!(\"{name}.exp\"), math::exp(), &[wire])?[0];\n                wire = target.wire_node(\n                    format!(\"{name}.sum\"),\n                    TReduce::new(axes, TReducer::Sum),\n                    &[wire],\n                )?[0];\n                wire = target.wire_node(format!(\"{name}.ln\"), math::ln(), &[wire])?[0];\n            }\n            SumSquare => {\n                wire = target.wire_node(format!(\"{name}.sq\"), math::square(), &[wire])?[0];\n                wire = target.wire_node(\n                    name.to_string() + \".sum\",\n                    TReduce::new(axes, TReducer::Sum),\n                    &[wire],\n                )?[0]\n            }\n            Mean => {\n                let fact = target.outlet_fact(wire)?.clone();\n                wire = target.wire_node(\n                    name.to_string() + \".sum\",\n                    TReduce::new(axes.clone(), TReducer::Sum),\n                    &[wire],\n                )?[0];\n                let size: TDim = axes.iter().map(|ax| &fact.shape[*ax]).product();\n                let size = tensor0(size).broadcast_into_rank(fact.rank())?;\n                let size = target.add_const(name.to_string() + \".size\", size)?;\n                let size = target.wire_node(\n                    name.to_string() + \".cast\",\n                    tract_core::ops::cast::cast(fact.datum_type),\n                    &[size],\n                )?[0];\n                wire = target.wire_node(name.to_string() + \".norm\", math::div(), &[wire, size])?[0];\n            }\n        };\n        Ok(wire)\n    }\n}\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\npub struct Reduce {\n    pub axes: Option<Vec<i64>>,\n    pub keep_dims: bool,\n    pub reducer: Reducer,\n}\n\nimpl Reduce {\n    pub fn must_reduce(&self, ax: usize, rank: usize) -> bool {\n        let resolved_axes: Option<Vec<usize>> = match &self.axes {\n            None => None,\n            Some(original_axes) => {\n                let mut ans: Vec<usize> = vec![];\n                for or_ax in original_axes.iter() {\n                    ans.push(Self::resolve_axis(*or_ax, rank).unwrap());\n                }\n                Some(ans)\n            }\n        };\n\n        resolved_axes.as_ref().map(|axes| axes.contains(&ax)).unwrap_or(true)\n    }\n\n    pub fn output_shape(&self, shape: &[TDim]) -> TVec<TDim> {\n        shape\n            .iter()\n            .enumerate()\n            .filter_map(|(ix, d)| {\n                if self.must_reduce(ix, shape.len()) {\n                    if self.keep_dims { Some(1.to_dim()) } else { None }\n                } else {\n                    Some(d.clone())\n                }\n            })\n            .collect()\n    }\n\n    fn resolve_axis(axis: i64, rank: usize) -> TractResult<usize> {\n        if 0 <= axis && axis < rank as i64 {\n            Ok(axis as usize)\n        } else if -(rank as i64) <= axis && axis < 0 {\n            Ok((axis + rank as i64) as usize)\n        } else {\n            bail!(\"Illegal combination of values for rank and axis: {} and {}\", rank, axis)\n        }\n    }\n\n    fn resolve_axes(&self, input_rank: usize) -> TractResult<TVec<usize>> {\n        let mut axes: TVec<usize> = match self.axes.as_ref() {\n            None => Ok((0..input_rank).collect()),\n            Some(axis) => axis.iter().map(|&a| Self::resolve_axis(a, input_rank)).collect(),\n        }?;\n        axes.sort();\n        Ok(axes)\n    }\n}\n\nimpl Expansion for Reduce {\n    fn name(&self) -> StaticName {\n        format!(\"Reduce<{:?}>\", self.reducer).into()\n    }\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axes: {:?} keep_dims: {}\", self.axes, self.keep_dims)])\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        if let Reducer::ArgMax(_) | Reducer::ArgMin(_) = self.reducer {\n            s.equals(&outputs[0].datum_type, DatumType::I64)?;\n        } else {\n            s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        }\n        if self.keep_dims {\n            s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        } else if let Some(axes) = self.axes.as_ref() {\n            s.equals(inputs[0].rank.bex() - axes.len() as i64, &outputs[0].rank)?;\n        } else {\n            s.equals(&outputs[0].rank, 0)?;\n        }\n        s.given(&inputs[0].shape, move |s, shape| {\n            let out_shape = self.output_shape(&shape);\n            s.equals(&outputs[0].shape, out_shape)\n        })\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let mut wire = inputs[0];\n        let fact = target.outlet_fact(wire)?.clone();\n        let mut axes = self.resolve_axes(fact.rank())?;\n        axes.sort();\n        if fact.datum_type == TDim::datum_type() {\n            wire = target.wire_node(\n                format!(\"{name}.cast_from_tdim\"),\n                tract_core::ops::cast::cast(i64::datum_type()),\n                &[wire],\n            )?[0];\n        }\n        wire = self.reducer.wire(axes.clone(), name, target, wire).context(\"wiring reducer\")?;\n        if fact.datum_type == TDim::datum_type() {\n            wire = target.wire_node(\n                format!(\"{name}.cast_to_tdim\"),\n                tract_core::ops::cast::cast(TDim::datum_type()),\n                &[wire],\n            )?[0];\n        }\n        if !self.keep_dims {\n            for axis in axes.into_iter().rev() {\n                wire = target.wire_node(\n                    format!(\"{name}-dispose-dims-{axis}\"),\n                    AxisOp::Rm(axis),\n                    &[wire],\n                )?[0];\n            }\n        }\n        Ok(tvec!(wire))\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/nn/softmax.rs",
    "content": "use crate::internal::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Softmax {\n    axis: isize,\n}\n\nimpl Expansion for Softmax {\n    fn name(&self) -> StaticName {\n        \"Softmax\".into()\n    }\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis: {:?}\", self.axis)])\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let axis = if self.axis < 0 {\n            (target.outlet_fact(inputs[0])?.rank() as isize + self.axis) as usize\n        } else {\n            self.axis as usize\n        };\n\n        let input = target.outlet_fact(inputs[0])?.clone();\n        let input_dt = input.datum_type;\n        let quant_output_dt = if input_dt.is_quantized() {\n            // Quantization parameters are not specified in ONNX (v13) so we set this value as default\n            // in order to maximize the precision of the output.\n            Some(DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 0.0078125 }))\n        } else {\n            None\n        };\n\n        target.wire_node(\n            name,\n            tract_core::ops::nn::Softmax {\n                axes: tvec![axis],\n                quant_output_dt,\n                ..tract_core::ops::nn::Softmax::default()\n            },\n            inputs,\n        )\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/quant.rs",
    "content": ""
  },
  {
    "path": "hir/src/ops/scan.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::scan::Scan;\nuse tract_core::ops::scan::ScanInfo;\npub use tract_core::ops::scan::{InputMapping, OutputMapping};\n\n#[derive(Debug, Clone, new, Default)]\npub struct InferenceScan {\n    pub body: InferenceModel,\n    pub input_mapping: Vec<InputMapping>,\n    pub output_mapping: Vec<OutputMapping<TDim>>,\n    pub clean_scan_counts: bool,\n    pub iter_count_fact: GenericFactoid<TDim>,\n}\n\nimpl PartialEq for InferenceScan {\n    fn eq(&self, _other: &Self) -> bool {\n        false\n    }\n}\nimpl Eq for InferenceScan {}\n\nimpl Op for InferenceScan {\n    fn name(&self) -> StaticName {\n        \"Scan\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut lines = vec![];\n        for (ix, im) in self.input_mapping.iter().enumerate() {\n            lines.push(format!(\"Model input  #{ix}: {im:?}\"));\n        }\n        for (ix, om) in self.output_mapping.iter().enumerate() {\n            lines.push(format!(\"Model output #{ix}: {om:?}\"));\n        }\n        Ok(lines)\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for InferenceScan {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        self.to_mir_scan()?.state(session, node_id)\n    }\n}\n\nimpl InferenceScan {\n    pub(super) fn to_mir_scan(&self) -> TractResult<Box<Scan>> {\n        let typed_model = self.body.clone().into_typed()?;\n        let input_mapping = self\n            .input_mapping\n            .iter()\n            .enumerate()\n            .map(|(ix, im)| {\n                Ok(match im {\n                    InputMapping::Scan(info) => InputMapping::Scan(ScanInfo {\n                        chunk: typed_model.input_fact(ix)?.shape[info.axis].to_isize()?,\n                        ..*info\n                    }),\n                    other => other.clone(),\n                })\n            })\n            .collect::<TractResult<_>>()?;\n        let output_mapping = self\n            .output_mapping\n            .iter()\n            .enumerate()\n            .map(|(ix, im)| {\n                let scan = if let Some((slot, scan)) = im.scan {\n                    Some((\n                        slot,\n                        ScanInfo {\n                            chunk: typed_model.input_fact(ix)?.shape[scan.axis].to_isize()?,\n                            ..scan\n                        },\n                    ))\n                } else {\n                    None\n                };\n                Ok(OutputMapping {\n                    state: im.state,\n                    scan,\n                    full_dim_hint: im.full_dim_hint.clone(),\n                    last_value_slot: im.last_value_slot,\n                })\n            })\n            .collect::<TractResult<_>>()?;\n        Ok(Box::new(Scan::new(typed_model, input_mapping, output_mapping, 0)?))\n    }\n\n    fn unify_scanning_tensor_fact(\n        outer: &mut InferenceFact,\n        inner: &mut InferenceFact,\n        outer_scan_axis: usize,\n    ) -> TractResult<bool> {\n        let mut changed = outer.datum_type.unify_with_mut(&mut inner.datum_type)?;\n        let rank = outer\n            .shape\n            .rank()\n            .concretize()\n            .or_else(|| inner.shape.rank().concretize())\n            .map(|r| r as usize);\n        if let Some(rank) = rank {\n            if outer.shape.unify_with(&ShapeFactoid::closed(tvec!(GenericFactoid::Any; rank)))? {\n                changed = true;\n            }\n            if inner.shape.unify_with(&ShapeFactoid::closed(tvec!(GenericFactoid::Any; rank)))? {\n                changed = true;\n            }\n            for axis in 0..rank {\n                if axis != outer_scan_axis {\n                    let value = outer\n                        .shape\n                        .dim(axis)\n                        .unwrap()\n                        .concretize()\n                        .or_else(|| inner.shape.dim(axis).unwrap().concretize());\n                    if let Some(value) = value {\n                        if outer.shape.set_dim(axis, value.clone()) {\n                            changed = true\n                        }\n                        if inner.shape.set_dim(axis, value) {\n                            changed = true\n                        }\n                    }\n                }\n            }\n        }\n        Ok(changed)\n    }\n\n    fn unify_facts(\n        &mut self,\n        inputs: &mut [InferenceFact],\n        outputs: &mut [InferenceFact],\n    ) -> TractResult<bool> {\n        let mut changed = false;\n        let hidden_state_len = self.input_mapping.iter().filter(|m| m.is_state()).count();\n        #[allow(clippy::needless_range_loop)]\n        for state_ix in 0..hidden_state_len {\n            trace!(\"Unify hidden state #{state_ix}\");\n            let inner_model_output_ix = self\n                .output_mapping\n                .iter()\n                .enumerate()\n                .filter(|(_ix, map)| map.state)\n                .nth(state_ix)\n                .unwrap()\n                .0;\n            let mut facts = self.body.outlets_fact_mut(&[\n                self.body.input_outlets()?[state_ix],\n                self.body.output_outlets()?[inner_model_output_ix],\n            ])?;\n            facts.push(&mut inputs[state_ix]);\n            if Factoid::unify_all(\n                &mut facts.iter_mut().map(|f| &mut f.datum_type).collect::<TVec<_>>(),\n            )? {\n                changed = true;\n            }\n            if Factoid::unify_all(&mut facts.iter_mut().map(|f| &mut f.shape).collect::<TVec<_>>())?\n            {\n                changed = true;\n            }\n        }\n        for (slot, i) in self.input_mapping.iter().enumerate() {\n            match i {\n                InputMapping::State => {}\n                InputMapping::Full => {\n                    if inputs[slot].unify_with_mut(self.body.input_fact_mut(slot)?)? {\n                        changed = true;\n                    }\n                }\n                InputMapping::Scan(scan) => {\n                    let incoming = &mut inputs[slot];\n                    let inner = self.body.input_fact_mut(slot)?;\n                    if Self::unify_scanning_tensor_fact(incoming, inner, scan.axis)? {\n                        changed = true;\n                    };\n                    if self.clean_scan_counts {\n                        if incoming.shape.ensure_rank_at_least(scan.axis) {\n                            changed = true;\n                        }\n                        let value =\n                            self.iter_count_fact.unify(&incoming.shape.dim(scan.axis).unwrap())?;\n                        if self.iter_count_fact != value {\n                            changed = true;\n                            self.iter_count_fact = value.clone();\n                        }\n                        if incoming.shape.dim(scan.axis).unwrap() != value {\n                            changed = true;\n                            incoming.shape.set_dim(scan.axis, value.concretize().unwrap());\n                        }\n                    }\n                }\n            }\n        }\n        for (ix, i) in self.output_mapping.iter().enumerate() {\n            if let Some((slot, scan)) = i.scan {\n                let outgoing = &mut outputs[slot];\n                let inner = self.body.output_fact_mut(ix)?;\n                if Self::unify_scanning_tensor_fact(outgoing, inner, scan.axis)? {\n                    changed = true\n                }\n                if self.clean_scan_counts {\n                    if outgoing.shape.ensure_rank_at_least(scan.axis) {\n                        changed = true;\n                    }\n                    let value =\n                        self.iter_count_fact.unify(&outgoing.shape.dim(scan.axis).unwrap())?;\n                    if self.iter_count_fact != value {\n                        changed = true;\n                        self.iter_count_fact = value.clone();\n                    }\n                    if outgoing.shape.dim(scan.axis).unwrap() != value {\n                        changed = true;\n                        outgoing.shape.set_dim(scan.axis, value.concretize().unwrap());\n                    }\n                }\n            }\n            if let Some(slot) = i.last_value_slot {\n                if outputs[slot].unify_with(self.body.output_fact_mut(ix)?)? {\n                    changed = true;\n                }\n            }\n        }\n        Ok(changed)\n    }\n}\n\nimpl InferenceOp for InferenceScan {\n    fn infer_facts(\n        &mut self,\n        inputs: TVec<&InferenceFact>,\n        outputs: TVec<&InferenceFact>,\n        _observed: TVec<&InferenceFact>,\n    ) -> TractResult<(TVec<InferenceFact>, TVec<InferenceFact>, TVec<InferenceFact>)> {\n        let body_inputs = self.body.input_outlets()?.len();\n        let body_outputs = self.body.output_outlets()?.len();\n        let expected_op_inputs = self.input_mapping.len();\n        let expected_op_outputs = self\n            .output_mapping\n            .iter()\n            .filter_map(|om| om.last_value_slot)\n            .chain(self.output_mapping.iter().filter_map(|om| om.scan.map(|si| si.0)))\n            .max()\n            .context(\"No output slot found\")?\n            + 1;\n        if inputs.len() != expected_op_inputs {\n            bail!(\"Scan receives {} inputs, mappings expects {}\", inputs.len(), expected_op_inputs)\n        }\n        if body_inputs != self.input_mapping.len() {\n            bail!(\n                \"Scan body expect {} inputs, mappings expects {}\",\n                body_inputs,\n                self.input_mapping.len()\n            )\n        }\n        if outputs.len() != expected_op_outputs {\n            bail!(\"Scan has {} outputs, mappings expects {}\", outputs.len(), expected_op_outputs);\n        }\n        if body_outputs != self.output_mapping.len() {\n            bail!(\n                \"Scan body expect {} outputs, mappings expects {}\",\n                body_outputs,\n                self.output_mapping.len()\n            )\n        }\n        let mut inputs: TVec<InferenceFact> = inputs.into_iter().cloned().collect();\n        let mut outputs: TVec<InferenceFact> = outputs.into_iter().cloned().collect();\n        loop {\n            trace!(\"Unify inner and outer interface\");\n            let mut changed = self.unify_facts(&mut inputs, &mut outputs)?;\n            trace!(\"iters: {:?} changed: {:?}\", self.iter_count_fact, changed);\n            for (ix, input) in self.body.input_outlets()?.iter().enumerate() {\n                trace!(\"  Input inner model: {} {:?} {:?}\", ix, input, self.body.input_fact(ix));\n            }\n            for (ix, output) in self.body.output_outlets()?.iter().enumerate() {\n                trace!(\"  Output inner model: {} {:?} {:?}\", ix, output, self.body.output_fact(ix));\n            }\n            trace!(\"Inner model analyse\");\n            if self.body.analyse(false).context(\"analysing inner model\")? {\n                changed = true;\n            }\n            if !changed {\n                break;\n            }\n            trace!(\"Finished inner model analyse\");\n        }\n        Ok((inputs, outputs, tvec!()))\n    }\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        let inputs = node.inputs.iter().map(|m| mapping[m]).collect::<TVec<_>>();\n        target.wire_node(&*node.name, self.to_mir_scan()? as Box<dyn TypedOp>, &inputs)\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(self.output_mapping.iter().filter(|om| !om.invisible()).count())\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "hir/src/ops/source.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\nuse tract_core::ops::source::{SourceState, TypedSource};\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Source;\n\nimpl Op for Source {\n    fn name(&self) -> StaticName {\n        \"Source\".into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for Source {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n    fn state(&self, _session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(SourceState(node_id))))\n    }\n}\n\nimpl InferenceRulesOp for Source {\n    /// Registers the inference rules of the operator.\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        _s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 0)?;\n        check_output_arity(outputs, 1)?;\n        Ok(())\n    }\n\n    as_op!();\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        _mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        if let Ok(fact) = TypedFact::try_from(&node.outputs[0].fact) {\n            target.wire_node(&*node.name, TypedSource::new(fact), &[])\n        } else {\n            bail!(\n                \"Source node without a determined fact. Help: provide explicit input facts to your model.\"\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "hir/src/ops/unimpl.rs",
    "content": "use crate::infer::*;\nuse crate::internal::*;\n\npub use tract_core::ops::unimpl::UnimplementedOp;\n\nimpl InferenceRulesOp for UnimplementedOp {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        _: &mut Solver<'r>,\n        _: &'p [TensorProxy],\n        _: &'p [TensorProxy],\n    ) -> InferenceResult {\n        Ok(())\n    }\n\n    as_op!();\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        _node: &InferenceNode,\n        _target: &mut TypedModel,\n        _mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        bail!(\"Operator can not be made a TypedOp.\")\n    }\n}\n"
  },
  {
    "path": "libcli/Cargo.toml",
    "content": "[package]\nname = \"tract-libcli\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [\"TensorFlow\", \"NeuralNetworks\"]\ncategories = [\"science\"]\nedition = \"2024\"\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nbox_drawing.workspace = true\nclap.workspace = true\ncolorous.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\nndarray-npy.workspace = true\nnu-ansi-term.workspace = true\npy_literal.workspace = true\nrand.workspace = true\nserde.workspace = true\nserde_json.workspace = true\ntract-core.workspace = true\ntract-hir.workspace = true\ntract-onnx = { workspace = true, optional = true }\ntract-tflite.workspace = true\ntract-gpu.workspace = true\ntract-transformers = { workspace = true, optional = true }\n\n[target.'cfg(any(target_os = \"macos\", target_os = \"ios\"))'.dependencies]\ntract-metal = { workspace = true }\n\n[target.'cfg(any(target_os = \"linux\", target_os = \"windows\"))'.dependencies]\ncudarc.workspace = true\ntract-cuda.workspace = true\n\n[features]\ndefault = [\"transformers\"]\n# hir = [\"tract-hir\"]\nhir = []\nonnx = [\"tract-onnx\"]\ncomplex = [\"tract-core/complex\"]\ntransformers = [\"tract-transformers\"]\n"
  },
  {
    "path": "libcli/src/annotations.rs",
    "content": "use nu_ansi_term::Style;\nuse std::collections::HashMap;\n#[allow(unused_imports)]\nuse std::convert::TryFrom;\nuse std::time::Duration;\nuse tract_core::internal::*;\nuse tract_core::ops::scan::Scan;\nuse tract_itertools::Itertools;\nuse tract_itertools::izip;\n\nuse crate::model::Model;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct NodeQId(pub TVec<(usize, String)>, pub usize);\n\nimpl From<usize> for NodeQId {\n    fn from(id: usize) -> NodeQId {\n        NodeQId(tvec!(), id)\n    }\n}\n\nimpl NodeQId {\n    pub fn model<'a>(&self, model: &'a dyn Model) -> Option<&'a dyn Model> {\n        fn scope<'a>(path: &[(usize, String)], model: &'a dyn Model) -> Option<&'a dyn Model> {\n            if path.is_empty() {\n                Some(model)\n            } else {\n                model\n                    .nested_models(path[0].0)\n                    .into_iter()\n                    .find(|(name, _sub)| name == &path[0].1)\n                    .map(|(_, sub)| sub)\n            }\n        }\n        scope(&self.0, model)\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct NodeTags {\n    pub cost: Vec<(Cost, TDim)>,\n    pub tmp_mem_usage: Option<TDim>,\n    pub style: Option<Style>,\n    pub labels: Vec<String>,\n    pub sections: Vec<Vec<String>>,\n    pub profile: Option<Duration>,\n    pub accelerator_profile: Option<Duration>,\n    pub model_input: Option<String>,\n    pub model_output: Option<String>,\n    pub outlet_labels: Vec<Vec<String>>,\n    pub outlet_axes: Vec<Vec<String>>,\n}\n\nimpl<'a> std::ops::Add<&'a NodeTags> for &'a NodeTags {\n    type Output = NodeTags;\n    fn add(self, other: &'a NodeTags) -> NodeTags {\n        let cost = self\n            .cost\n            .iter()\n            .chain(other.cost.iter())\n            .sorted_by_key(|(a, _)| a)\n            .chunk_by(|(a, _)| a)\n            .into_iter()\n            .map(|(cost, dims)| {\n                (cost.clone(), dims.into_iter().fold(0.to_dim(), |acc, d| acc + &d.1))\n            })\n            .collect::<Vec<(Cost, TDim)>>();\n\n        let tmp_mem_usage = match (self.tmp_mem_usage.clone(), other.tmp_mem_usage.clone()) {\n            (Some(self_mem), Some(other_mem)) => Some(self_mem + other_mem),\n            (_, Some(mem)) | (Some(mem), _) => Some(mem),\n            (None, None) => None,\n        };\n\n        let profile = self.profile.unwrap_or_default() + other.profile.unwrap_or_default();\n        let profile = if profile != Duration::default() { Some(profile) } else { None };\n        let accelerator_profile = self.accelerator_profile.unwrap_or_default()\n            + other.accelerator_profile.unwrap_or_default();\n        let accelerator_profile = if accelerator_profile != Duration::default() {\n            Some(accelerator_profile)\n        } else {\n            None\n        };\n\n        let style = self.style.or(other.style);\n        let labels = self.labels.iter().chain(other.labels.iter()).cloned().collect();\n        let sections = self.sections.iter().chain(other.sections.iter()).cloned().collect();\n        let model_input = self.model_input.clone().or_else(|| other.model_input.clone());\n        let model_output = self.model_output.clone().or_else(|| other.model_output.clone());\n        let outlet_labels = izip!(&self.outlet_labels, &other.outlet_labels)\n            .map(|(s, o)| s.iter().chain(o.iter()).cloned().collect())\n            .collect();\n        let outlet_axes = izip!(&self.outlet_axes, &other.outlet_axes)\n            .map(|(s, o)| s.iter().chain(o.iter()).cloned().collect())\n            .collect();\n        NodeTags {\n            cost,\n            tmp_mem_usage,\n            profile,\n            accelerator_profile,\n            style,\n            labels,\n            sections,\n            model_input,\n            model_output,\n            outlet_labels,\n            outlet_axes,\n        }\n    }\n}\n\nimpl<'a> std::iter::Sum<&'a NodeTags> for NodeTags {\n    fn sum<I>(iter: I) -> NodeTags\n    where\n        I: std::iter::Iterator<Item = &'a NodeTags>,\n    {\n        iter.fold(EMPTY, |a, b| &a + b)\n    }\n}\n\nconst EMPTY: NodeTags = NodeTags {\n    cost: Vec::new(),\n    tmp_mem_usage: None,\n    style: None,\n    labels: Vec::new(),\n    sections: Vec::new(),\n    profile: None,\n    accelerator_profile: None,\n    model_output: None,\n    model_input: None,\n    outlet_labels: Vec::new(),\n    outlet_axes: Vec::new(),\n};\n\n#[derive(Debug, Clone, Default)]\npub struct Annotations {\n    pub tags: HashMap<NodeQId, NodeTags>,\n    pub profile_summary: Option<ProfileSummary>,\n    pub memory_summary: Option<MemorySummary>,\n}\n\nimpl Annotations {\n    pub fn node_mut(&mut self, qid: NodeQId) -> &mut NodeTags {\n        self.tags.entry(qid).or_default()\n    }\n\n    pub fn track_tmp_memory_usage<Flushable>(\n        &mut self,\n        model: &dyn Model,\n        flushable: Flushable,\n        skip_order_opt_ram: bool,\n    ) -> TractResult<()>\n    where\n        Flushable: Fn(&TypedNode) -> bool,\n    {\n        let Some(model) = model.downcast_ref::<TypedModel>() else { return Ok(()) };\n        let order = if skip_order_opt_ram {\n            tract_core::model::order::eval_order(model)?\n        } else {\n            tract_core::model::order::eval_order_opt_ram(model)?\n        };\n\n        let tmp_mem_usage = model.eval_tmp_memory_usage(&order, &flushable)?;\n\n        let peak_tmp_mem_usage = tmp_mem_usage\n            .iter()\n            .map(|(n, mem)| mem.to_usize().map(|m| (*n, m)))\n            .collect::<TractResult<TVec<_>>>()\n            .ok()\n            .and_then(|mems| {\n                mems.into_iter().map(|(n, mem)| (NodeQId(tvec![], n), mem)).max_by_key(|it| it.1)\n            });\n\n        self.memory_summary =\n            peak_tmp_mem_usage.map(|(n, mem)| MemorySummary { max: mem, max_reached_by_node: n });\n\n        for (n, mem_size) in tmp_mem_usage.into_iter() {\n            let qid = NodeQId(tvec![], n);\n            let tags = self.tags.entry(qid).or_default();\n            tags.tmp_mem_usage = Some(mem_size.simplify());\n        }\n        Ok(())\n    }\n\n    pub fn track_axes(\n        &mut self,\n        model: &dyn Model,\n        hints: &HashMap<OutletId, TVec<String>>,\n    ) -> TractResult<()> {\n        let Some(model) = model.downcast_ref::<TypedModel>() else { return Ok(()) };\n        fn sub(\n            annotations: &mut Annotations,\n            prefix: &[(usize, String)],\n            name_prefix: &str,\n            model: &TypedModel,\n            hints: &HashMap<OutletId, TVec<String>>,\n        ) -> TractResult<()> {\n            let tracking = tract_core::axes::full_axis_tracking(model)?;\n            for (ix, axis) in tracking.iter().enumerate() {\n                let name = axis\n                    .creators\n                    .iter()\n                    .find_map(|cre| hints.get(cre).and_then(|hints| hints.get(axis.outlets[cre])))\n                    .cloned()\n                    .unwrap_or_else(|| format!(\"{name_prefix}x{ix}\"));\n                for outlet in axis.outlets.keys() {\n                    let axis = axis.outlets[&outlet];\n                    let qid = NodeQId(prefix.into(), outlet.node);\n                    let tags = annotations.tags.entry(qid).or_default();\n                    while tags.outlet_axes.len() <= outlet.slot {\n                        tags.outlet_axes.push(vec![]);\n                    }\n                    while tags.outlet_axes[outlet.slot].len() <= axis {\n                        tags.outlet_axes[outlet.slot].push(Default::default());\n                    }\n                    tags.outlet_axes[outlet.slot][axis].clone_from(&name);\n                }\n            }\n            for node in &model.nodes {\n                if let Some(scan) = node.op_as::<Scan>() {\n                    let mut prefix: TVec<_> = prefix.into();\n                    prefix.push((node.id, \"loop\".to_string()));\n                    sub(\n                        annotations,\n                        &prefix,\n                        &format!(\"{name_prefix}loop_\"),\n                        &scan.body,\n                        &Default::default(),\n                    )?;\n                }\n            }\n            Ok(())\n        }\n        sub(self, &[], \"\", model, hints)\n    }\n\n    pub fn from_model(model: &dyn Model) -> TractResult<Annotations> {\n        let mut annotations = Annotations::default();\n        fn set_subio_labels(\n            model: &dyn Model,\n            prefix: &[(usize, String)],\n            annotations: &mut Annotations,\n        ) {\n            for n in 0..model.nodes_len() {\n                for output in 0..model.node_output_count(n) {\n                    if let Some(label) = model.outlet_label((n, output).into()) {\n                        let qid = NodeQId(prefix.into(), n);\n                        annotations\n                            .tags\n                            .entry(qid.clone())\n                            .or_default()\n                            .outlet_labels\n                            .resize(output + 1, vec![]);\n                        annotations.tags.entry(qid).or_default().outlet_labels[output] =\n                            vec![label.to_string()];\n                    }\n                }\n                for (label, sub /*, ins, outs*/) in model.nested_models(n) {\n                    let mut prefix: TVec<(usize, String)> = prefix.into();\n                    prefix.push((n, label.to_string()));\n                    set_subio_labels(sub, &prefix, annotations);\n                    /*\n                    ins.into_iter().enumerate().for_each(|(ix, i)| {\n                    annotations.tags.entry(qid).or_default().model_input = Some(i);\n                    });\n                    outs.into_iter().enumerate().for_each(|(ix, o)| {\n                    let qid = NodeQId(prefix.clone(), ix);\n                    annotations.tags.entry(qid).or_default().model_output = Some(o);\n                    });\n                    */\n                }\n            }\n        }\n        set_subio_labels(model, &[], &mut annotations);\n        Ok(annotations)\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct ProfileSummary {\n    pub max: Duration,\n    pub sum: Duration,\n    pub accel_sum: Duration,\n    pub entire: Duration,\n    pub iters: usize,\n}\n\n#[derive(Debug, Clone)]\npub struct MemorySummary {\n    pub max: usize,\n    pub max_reached_by_node: NodeQId,\n}\n"
  },
  {
    "path": "libcli/src/display_params.rs",
    "content": "use crate::model::Model;\nuse tract_core::prelude::*;\n\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\npub enum Io {\n    None,\n    #[default]\n    Short,\n    Long,\n}\n\n#[derive(Debug, Clone, Default, PartialEq, Eq)]\npub struct DisplayParams {\n    pub konst: bool,\n    pub invariants: bool,\n    pub quiet: bool,\n    pub natural_order: bool,\n    pub opt_ram_order: bool,\n    pub debug_op: bool,\n    pub cost: bool,\n    pub tmp_mem_usage: bool,\n    pub profile: bool,\n    pub folded: bool,\n    pub node_ids: Option<Vec<TVec<(usize, String)>>>,\n    pub op_name: Option<String>,\n    pub node_name: Option<String>,\n    pub expect_core: bool,\n    pub outlet_labels: bool,\n    pub io: Io,\n    pub json: bool,\n    pub info: bool,\n    pub left_column_width: usize,\n    pub mm: bool,\n    pub summary: bool,\n    pub audit_json: bool,\n}\n\nimpl DisplayParams {\n    pub fn filter(\n        &self,\n        model: &dyn Model,\n        scope: &[(usize, String)],\n        node_id: usize,\n    ) -> TractResult<bool> {\n        if let Some(nodes) = self.node_ids.as_ref() {\n            return Ok(nodes.iter().any(|n| {\n                n.len() == scope.len() + 1\n                    && &n[0..scope.len()] == scope\n                    && n.last().unwrap().0 == node_id\n            }));\n        }\n        if let Some(node_name) = self.node_name.as_ref() {\n            return Ok(model.node_name(node_id).starts_with(node_name));\n        }\n        if let Some(op_name) = self.op_name.as_ref() {\n            return Ok(model.node_op_name(node_id).starts_with(op_name));\n        }\n        /*\n        if let Some(successor) = self.successors {\n        return Ok(model.node_inputs(node_id).iter().any(|i| i.node == successor));\n        }\n        */\n        Ok(!model.node_const(node_id) || self.konst)\n    }\n\n    pub fn should_draw(&self) -> bool {\n        !self.natural_order\n    }\n\n    pub fn order(&self, model: &dyn Model) -> TractResult<Vec<usize>> {\n        if self.natural_order {\n            Ok((0..model.nodes_len()).collect())\n        } else if self.opt_ram_order {\n            model.eval_order_opt_ram()\n        } else {\n            model.eval_order()\n        }\n    }\n}\n"
  },
  {
    "path": "libcli/src/draw.rs",
    "content": "use crate::display_params::DisplayParams;\nuse crate::model::Model;\nuse box_drawing::heavy::*;\nuse nu_ansi_term::{Color, Style};\nuse std::collections::HashSet;\nuse std::fmt::Write;\nuse tract_core::internal::*;\n\n/// A wire that is not rendered (const node output when konst=false).\n#[derive(Clone, Debug)]\nstruct HiddenWire {\n    successors: Vec<InletId>,\n}\n\n/// A wire that occupies a visual column.\n#[derive(Clone, Debug)]\nstruct VisibleWire {\n    outlet: OutletId,\n    color: Style,\n    successors: Vec<InletId>,\n    should_change_color: bool,\n}\n\n/// White circled number for model inputs: ⓪①②...⑳\npub fn circled_input(ix: usize) -> char {\n    match ix {\n        0 => '⓪',\n        1..=20 => char::from_u32(0x2460 + (ix as u32 - 1)).unwrap(),\n        _ => '○',\n    }\n}\n\n/// Filled circled number for model outputs: ⓿❶❷...❿\npub fn circled_output(ix: usize) -> char {\n    match ix {\n        0 => '⓿',\n        1..=10 => char::from_u32(0x2776 + (ix as u32 - 1)).unwrap(),\n        _ => '●',\n    }\n}\n\n#[derive(Clone, Default)]\npub struct DrawingState {\n    hidden: Vec<HiddenWire>,\n    visible: Vec<VisibleWire>, // index = visual column\n    latest_node_color: Style,\n    visited: HashSet<usize>,\n}\n\nimpl DrawingState {\n    fn next_color(&self) -> Style {\n        let colors = &[\n            Color::Red.normal(),\n            Color::Green.normal(),\n            Color::Yellow.normal(),\n            Color::Blue.normal(),\n            Color::Purple.normal(),\n            Color::Cyan.normal(),\n            Color::White.normal(),\n            Color::Red.bold(),\n            Color::Green.bold(),\n            Color::Yellow.bold(),\n            Color::Blue.bold(),\n            Color::Purple.bold(),\n            Color::Cyan.bold(),\n            Color::White.bold(),\n        ];\n        *colors\n            .iter()\n            .min_by_key(|&c| self.visible.iter().filter(|w| w.color == *c).count())\n            .unwrap()\n    }\n\n    /// Number of visible wires that pass through (have successors to nodes other than `node`).\n    fn passthrough_count(&self, node: usize) -> usize {\n        self.visible.iter().filter(|w| w.successors.iter().any(|i| i.node != node)).count()\n    }\n\n    /// Color of the last visible wire, or the latest node color.\n    pub fn last_wire_color(&self) -> Style {\n        self.visible.last().map(|w| w.color).unwrap_or(self.latest_node_color)\n    }\n\n    /// Render a filler line: one ┃ per visible wire.\n    fn render_filler(&self) -> String {\n        let mut s = String::new();\n        for w in &self.visible {\n            let _ = write!(s, \"{}\", w.color.paint(VERTICAL));\n        }\n        s\n    }\n\n    pub fn draw_node_vprefix(\n        &mut self,\n        model: &dyn Model,\n        node: usize,\n        _opts: &DisplayParams,\n    ) -> TractResult<Vec<String>> {\n        let mut lines = vec![];\n\n        // Prune wires whose only remaining successors are all already visited.\n        self.visible.retain(|w| w.successors.iter().any(|i| !self.visited.contains(&i.node)));\n        self.hidden.retain(|w| w.successors.iter().any(|i| !self.visited.contains(&i.node)));\n\n        // Build target layout: passthroughs in current order, then visible inputs in input order.\n        let inputs = model.node_inputs(node);\n        let mut passthroughs: Vec<VisibleWire> = Vec::new();\n        let mut input_wires: Vec<Option<VisibleWire>> = vec![None; inputs.len()];\n\n        for w in &self.visible {\n            // Check if this wire feeds any input of this node\n            let mut matched_input = None;\n            for (ix, &inlet) in inputs.iter().enumerate() {\n                if w.outlet == inlet {\n                    matched_input = Some(ix);\n                    break;\n                }\n            }\n\n            if let Some(ix) = matched_input {\n                let this_inlet = InletId::new(node, ix);\n                let must_clone = w.successors.iter().any(|i| *i != this_inlet);\n                if must_clone {\n                    // Wire feeds this node AND others: clone it.\n                    // Original (with other successors) stays as passthrough.\n                    let mut pass_wire = w.clone();\n                    pass_wire.successors.retain(|i| *i != this_inlet);\n                    passthroughs.push(pass_wire);\n                    input_wires[ix] = Some(VisibleWire {\n                        outlet: w.outlet,\n                        color: w.color,\n                        successors: vec![this_inlet],\n                        should_change_color: true,\n                    });\n                } else {\n                    // Wire feeds only this node: move entirely to input position.\n                    input_wires[ix] = Some(w.clone());\n                }\n            } else {\n                passthroughs.push(w.clone());\n            }\n        }\n\n        // Target = passthroughs ++ visible input wires\n        let pt = passthroughs.len();\n        let mut target: Vec<VisibleWire> = passthroughs;\n        for w in input_wires.iter().flatten() {\n            target.push(w.clone());\n        }\n\n        // Build working state with empty slots for the input region.\n        // Cols 0..pt are passthroughs (occupied), cols pt..target.len() start empty.\n        let n_inputs_visible = input_wires.iter().filter(|w| w.is_some()).count();\n        let total_cols = pt + n_inputs_visible;\n        let mut slots: Vec<Option<VisibleWire>> = Vec::with_capacity(total_cols);\n        for w in &self.visible {\n            slots.push(Some(w.clone()));\n        }\n        while slots.len() < total_cols {\n            slots.push(None); // empty reserved slots\n        }\n\n        // Process inputs right to left. For each input:\n        // - Find the wire in `slots` (by outlet)\n        // - Compute its target column in the final layout\n        // - Render the routing line and update slots\n        for (ix, &inlet) in inputs.iter().enumerate().rev() {\n            let Some(ref input_wire) = input_wires[ix] else { continue };\n\n            let target_col = target\n                .iter()\n                .position(|w| w.outlet == inlet && w.successors.iter().any(|i| i.node == node))\n                .unwrap();\n\n            let cur_col =\n                match slots.iter().position(|s| s.as_ref().is_some_and(|w| w.outlet == inlet)) {\n                    Some(c) => c,\n                    None => continue,\n                };\n\n            let must_clone = input_wire.should_change_color; // proxy: cloned wires have this set\n\n            if cur_col == target_col && !must_clone {\n                continue;\n            }\n\n            // Render the routing line from cur_col to target_col.\n            let mut s = String::new();\n            let color = slots[cur_col].as_ref().unwrap().color;\n            let from = cur_col.min(target_col);\n            let to = cur_col.max(target_col);\n\n            // Leading verticals (cols before the leftmost endpoint)\n            for w in slots[..from].iter().flatten() {\n                let _ = write!(s, \"{}\", w.color.paint(VERTICAL));\n            }\n\n            if must_clone {\n                // Split: ┣ at cur_col, horizontals in between, ┓ at target_col\n                let _ = write!(s, \"{}\", color.paint(VERTICAL_RIGHT));\n            } else {\n                // Swap: ┗ at cur_col, horizontals in between, ┓ at target_col\n                let _ = write!(s, \"{}\", color.paint(UP_RIGHT));\n            }\n            for _ in from + 1..to {\n                let _ = write!(s, \"{}\", color.paint(HORIZONTAL));\n            }\n            let _ = write!(s, \"{}\", color.paint(DOWN_LEFT));\n\n            // Trailing verticals (cols after the rightmost endpoint)\n            for w in slots[to + 1..].iter().flatten() {\n                let _ = write!(s, \"{}\", w.color.paint(VERTICAL));\n            }\n\n            lines.push(s);\n\n            // Update slots: place the wire/clone at target_col\n            if must_clone {\n                // Original stays at cur_col, clone goes to target_col\n                slots[target_col] = Some(input_wire.clone());\n            } else {\n                // Move: remove from cur_col, place at target_col\n                slots[cur_col] = None;\n                slots[target_col] = Some(input_wire.clone());\n            }\n        }\n\n        // Set final state\n        self.visible = target;\n\n        lines.retain(|l: &String| !l.trim().is_empty());\n        Ok(lines)\n    }\n\n    pub fn draw_node_body(\n        &mut self,\n        model: &dyn Model,\n        node: usize,\n        opts: &DisplayParams,\n    ) -> TractResult<Vec<String>> {\n        let mut lines = vec![String::new()];\n        macro_rules! p { ($($args: expr),*) => { write!(lines.last_mut().unwrap(), $($args),*)?;} }\n        macro_rules! ln {\n            () => {\n                lines.push(String::new())\n            };\n        }\n\n        let inputs = model.node_inputs(node).to_vec();\n        let passthrough_count = self.passthrough_count(node);\n        let display = opts.konst || !model.node_const(node);\n\n        if display {\n            // Draw passthrough verticals\n            for w in &self.visible[..passthrough_count] {\n                p!(\"{}\", w.color.paint(VERTICAL));\n            }\n\n            let node_output_count = model.node_output_count(node);\n\n            // Determine node color\n            self.latest_node_color = if !inputs.is_empty() && passthrough_count < self.visible.len()\n            {\n                let wire0 = &self.visible[passthrough_count];\n                if !wire0.should_change_color { wire0.color } else { self.next_color() }\n            } else {\n                self.next_color()\n            };\n\n            // Draw junction\n            match (inputs.len(), node_output_count) {\n                (0, 1) => {\n                    // Source node: use circled number if it's a model input\n                    let input_idx = model.input_outlets().iter().position(|o| o.node == node);\n                    let symbol = match input_idx {\n                        Some(i) => circled_input(i).to_string(),\n                        _ => DOWN_RIGHT.to_string(),\n                    };\n                    p!(\"{}\", self.latest_node_color.paint(symbol));\n                }\n                (1, 0) => {\n                    p!(\"{}\", self.latest_node_color.paint(\"╹\"));\n                }\n                (u, d) => {\n                    p!(\"{}\", self.latest_node_color.paint(VERTICAL_RIGHT));\n                    for _ in 1..u.min(d) {\n                        p!(\"{}\", self.latest_node_color.paint(VERTICAL_HORIZONTAL));\n                    }\n                    for _ in u..d {\n                        p!(\"{}\", self.latest_node_color.paint(DOWN_HORIZONTAL));\n                    }\n                    for _ in d..u {\n                        p!(\"{}\", self.latest_node_color.paint(UP_HORIZONTAL));\n                    }\n                }\n            }\n            ln!();\n        }\n\n        while lines.last().map(|s| s.trim()) == Some(\"\") {\n            lines.pop();\n        }\n        Ok(lines)\n    }\n\n    pub fn draw_node_vfiller(&self, _model: &dyn Model, _node: usize) -> TractResult<String> {\n        Ok(self.render_filler())\n    }\n\n    pub fn draw_node_vsuffix(\n        &mut self,\n        model: &dyn Model,\n        node: usize,\n        opts: &DisplayParams,\n    ) -> TractResult<Vec<String>> {\n        // Mark node as visited now that its inputs have been consumed.\n        self.visited.insert(node);\n        let mut lines = vec![];\n        let passthrough_count = self.passthrough_count(node);\n        let node_output_count = model.node_output_count(node);\n\n        // Remove input wires (keep passthroughs)\n        self.visible.truncate(passthrough_count);\n\n        // Add output wires\n        for slot in 0..node_output_count {\n            let outlet = OutletId::new(node, slot);\n            let successors = model.outlet_successors(outlet).to_vec();\n            let color = if !opts.konst && model.node_const(node) {\n                // Const node: wire goes to hidden, not visible\n                self.hidden.push(HiddenWire { successors });\n                continue;\n            } else if slot == 0 {\n                self.latest_node_color\n            } else {\n                self.next_color()\n            };\n            self.visible.push(VisibleWire {\n                outlet,\n                color,\n                successors,\n                should_change_color: false,\n            });\n        }\n\n        // Mark model outputs with a circled number on a filler line.\n        let model_outputs = model.output_outlets();\n        let has_output_marker = self.visible.iter().any(|w| model_outputs.contains(&w.outlet));\n        if has_output_marker {\n            let mut s = String::new();\n            for w in &self.visible {\n                if model_outputs.contains(&w.outlet) {\n                    let output_idx = model_outputs.iter().position(|o| *o == w.outlet);\n                    let symbol = match output_idx {\n                        Some(i) => circled_output(i),\n                        _ => '●',\n                    };\n                    let _ = write!(s, \"{}\", w.color.paint(symbol.to_string()));\n                } else {\n                    let _ = write!(s, \"{}\", w.color.paint(VERTICAL));\n                }\n            }\n            lines.push(s);\n        }\n\n        // Remove wires with no successors\n        self.visible.retain(|w| !w.successors.is_empty());\n\n        lines.retain(|l: &String| !l.trim().is_empty());\n        Ok(lines)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::display_params::DisplayParams;\n    use crate::model::Model;\n    use tract_core::ops::identity::Identity;\n    use tract_core::ops::math;\n\n    fn strip_ansi(s: &str) -> String {\n        let mut out = String::new();\n        let mut in_escape = false;\n        for c in s.chars() {\n            if in_escape {\n                if c == 'm' {\n                    in_escape = false;\n                }\n            } else if c == '\\x1b' {\n                in_escape = true;\n            } else {\n                out.push(c);\n            }\n        }\n        out\n    }\n\n    fn draw_all(model: &dyn Model, ds: &mut DrawingState, node: usize) -> Vec<String> {\n        let opts = DisplayParams { konst: true, ..DisplayParams::default() };\n        let mut lines = vec![];\n        for l in ds.draw_node_vprefix(model, node, &opts).unwrap() {\n            lines.push(strip_ansi(&l));\n        }\n        for l in ds.draw_node_body(model, node, &opts).unwrap() {\n            lines.push(strip_ansi(&l));\n        }\n        for l in ds.draw_node_vsuffix(model, node, &opts).unwrap() {\n            lines.push(strip_ansi(&l));\n        }\n        lines.retain(|l| !l.trim().is_empty());\n        lines\n    }\n\n    /// Source → Identity (linear chain, no branching)\n    #[test]\n    fn linear_chain() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let s = model.add_source(\"s\", f32::fact([1]))?;\n        let _id = model.wire_node(\"id\", Identity, &[s])?[0];\n        model.auto_outputs()?;\n        let mut ds = DrawingState::default();\n        let lines0 = draw_all(&model, &mut ds, 0);\n        assert_eq!(lines0, vec![\"⓪\"]); // circled 0 (first model input)\n        let lines1 = draw_all(&model, &mut ds, 1);\n        assert_eq!(lines1[0], VERTICAL_RIGHT); // ┣ (1 in, 1 out)\n        assert!(lines1.len() == 2 && lines1[1] == \"⓿\"); // output marker\n        Ok(())\n    }\n\n    /// Source → Add(source, source) — fan-in from one source to two inputs\n    #[test]\n    fn fanin_from_one_source() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let s = model.add_source(\"s\", f32::fact([1]))?;\n        let _add = model.wire_node(\"add\", math::add(), &[s, s])?[0];\n        model.auto_outputs()?;\n        let mut ds = DrawingState::default();\n        let lines0 = draw_all(&model, &mut ds, 0);\n        assert_eq!(lines0, vec![\"⓪\"]); // circled 0 (first model input)\n        let lines1 = draw_all(&model, &mut ds, 1);\n        let joined = lines1.join(\"|\");\n        assert!(\n            joined.contains(UP_HORIZONTAL), // ┻ (merge)\n            \"Expected merge pattern, got: {lines1:?}\"\n        );\n        Ok(())\n    }\n\n    /// Two sources → Add → two consumers (fork)\n    #[test]\n    fn fork_after_merge() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let a = model.add_source(\"a\", f32::fact([1]))?;\n        let b = model.add_source(\"b\", f32::fact([1]))?;\n        let add = model.wire_node(\"add\", math::add(), &[a, b])?[0];\n        let _id1 = model.wire_node(\"id1\", Identity, &[add])?[0];\n        let _id2 = model.wire_node(\"id2\", Identity, &[add])?[0];\n        model.auto_outputs()?;\n        let mut ds = DrawingState::default();\n        draw_all(&model, &mut ds, 0); // source a\n        draw_all(&model, &mut ds, 1); // source b\n        let lines_add = draw_all(&model, &mut ds, 2); // add (2 inputs, 1 output)\n        let joined = lines_add.join(\"|\");\n        assert!(\n            joined.contains(UP_HORIZONTAL), // ┻ (2 inputs merge)\n            \"Expected merge in body, got: {lines_add:?}\"\n        );\n        let lines_id1 = draw_all(&model, &mut ds, 3); // id1\n        assert!(!lines_id1.is_empty(), \"id1 should render\");\n        Ok(())\n    }\n\n    /// No blank lines in prefix output (regression for leading-empty-line bug)\n    #[test]\n    fn no_blank_prefix_lines() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let a = model.add_source(\"a\", f32::fact([1]))?;\n        let b = model.add_source(\"b\", f32::fact([1]))?;\n        let add = model.wire_node(\"add\", math::add(), &[a, b])?[0];\n        let _id = model.wire_node(\"id\", Identity, &[add])?[0];\n        model.auto_outputs()?;\n        let opts = DisplayParams { konst: true, ..DisplayParams::default() };\n        let mut ds = DrawingState::default();\n        let order = model.eval_order()?;\n        for &node in &order {\n            let prefix = ds.draw_node_vprefix(&model, node, &opts).unwrap();\n            for (i, l) in prefix.iter().enumerate() {\n                let stripped = strip_ansi(l);\n                assert!(\n                    !stripped.trim().is_empty() || i == prefix.len() - 1,\n                    \"Blank line at position {i} in prefix for node {node}: {prefix:?}\"\n                );\n            }\n            ds.draw_node_body(&model, node, &opts).unwrap();\n            ds.draw_node_vsuffix(&model, node, &opts).unwrap();\n        }\n        Ok(())\n    }\n\n    /// Filler width matches the number of visible wires (post-suffix state)\n    #[test]\n    fn filler_width_matches_visible() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let a = model.add_source(\"a\", f32::fact([1]))?;\n        let b = model.add_source(\"b\", f32::fact([1]))?;\n        let add = model.wire_node(\"add\", math::add(), &[a, b])?[0];\n        let _id1 = model.wire_node(\"id1\", Identity, &[add])?[0];\n        let _id2 = model.wire_node(\"id2\", Identity, &[add])?[0];\n        model.auto_outputs()?;\n        let opts = DisplayParams { konst: true, ..DisplayParams::default() };\n        let mut ds = DrawingState::default();\n        let order = model.eval_order()?;\n        for &node in &order {\n            ds.draw_node_vprefix(&model, node, &opts).unwrap();\n            ds.draw_node_body(&model, node, &opts).unwrap();\n            ds.draw_node_vsuffix(&model, node, &opts).unwrap();\n            let filler = ds.draw_node_vfiller(&model, node).unwrap();\n            let filler_w = strip_ansi(&filler).chars().count();\n            let visible_count = ds.visible.len();\n            assert_eq!(\n                filler_w, visible_count,\n                \"Filler width {filler_w} != visible wire count {visible_count} for node {node}\"\n            );\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "libcli/src/export.rs",
    "content": "use crate::annotations::{Annotations, NodeQId};\nuse crate::model::Model;\nuse serde::Serialize;\nuse std::collections::HashMap;\nuse tract_core::internal::*;\n\n#[derive(Clone, Debug, Default, Serialize)]\npub struct GraphPerfInfo {\n    nodes: Vec<Node>,\n    profiling_info: Option<ProfilingInfo>,\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]\npub struct NodeQIdSer(pub Vec<(usize, String)>, pub usize);\n\n#[derive(Clone, Debug, Serialize)]\npub struct Node {\n    qualified_id: NodeQIdSer,\n    op_name: String,\n    node_name: String,\n\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    cost: HashMap<String, String>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    secs_per_iter: Option<f64>,\n}\n\n#[derive(Clone, Debug, Serialize)]\npub struct ProfilingInfo {\n    iterations: usize,\n    secs_per_iter: f64,\n}\n\nimpl GraphPerfInfo {\n    pub fn from(model: &dyn Model, annotations: &Annotations) -> GraphPerfInfo {\n        let nodes = annotations\n            .tags\n            .iter()\n            .map(|(id, node)| Node {\n                qualified_id: NodeQIdSer(id.0.iter().cloned().collect(), id.1),\n                cost: node.cost.iter().map(|(k, v)| (format!(\"{k:?}\"), format!(\"{v}\"))).collect(),\n                node_name: id.model(model).unwrap().node_name(id.1).to_string(),\n                op_name: id.model(model).unwrap().node_op_name(id.1).to_string(),\n                secs_per_iter: node.profile.map(|s| s.as_secs_f64()),\n            })\n            .collect();\n        let profiling_info = annotations.profile_summary.as_ref().map(|summary| ProfilingInfo {\n            secs_per_iter: summary.entire.as_secs_f64(),\n            iterations: summary.iters,\n        });\n        GraphPerfInfo { nodes, profiling_info }\n    }\n}\n\n// -- audit-json --\n\n#[derive(Serialize)]\npub struct AuditModel {\n    properties: HashMap<String, String>,\n    assertions: Vec<String>,\n    inputs: Vec<AuditModelIo>,\n    outputs: Vec<AuditModelIo>,\n    nodes: Vec<AuditNode>,\n}\n\n#[derive(Serialize)]\nstruct AuditModelIo {\n    name: String,\n    node: usize,\n    slot: usize,\n    fact: String,\n}\n\n#[derive(Serialize)]\nstruct AuditNode {\n    id: usize,\n    name: String,\n    op: String,\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    info: Vec<String>,\n    inputs: Vec<AuditOutletRef>,\n    outputs: Vec<AuditNodeOutput>,\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    cost: HashMap<String, String>,\n}\n\n#[derive(Serialize)]\nstruct AuditOutletRef {\n    node: usize,\n    slot: usize,\n}\n\n#[derive(Serialize)]\nstruct AuditNodeOutput {\n    fact: String,\n    successors: Vec<AuditInletRef>,\n}\n\n#[derive(Serialize)]\nstruct AuditInletRef {\n    node: usize,\n    slot: usize,\n}\n\npub fn audit_json(\n    model: &dyn Model,\n    annotations: &Annotations,\n    writer: impl std::io::Write,\n) -> TractResult<()> {\n    let properties: HashMap<String, String> =\n        model.properties().iter().map(|(k, v)| (k.clone(), format!(\"{v:?}\"))).collect();\n\n    let scope = model.symbols();\n    let assertions: Vec<String> = scope.all_assertions().iter().map(|a| format!(\"{a}\")).collect();\n\n    let inputs: Vec<AuditModelIo> = model\n        .input_outlets()\n        .iter()\n        .map(|o| {\n            Ok(AuditModelIo {\n                name: model.node_name(o.node).to_string(),\n                node: o.node,\n                slot: o.slot,\n                fact: model.outlet_fact_format(*o),\n            })\n        })\n        .collect::<TractResult<_>>()?;\n\n    let outputs: Vec<AuditModelIo> = model\n        .output_outlets()\n        .iter()\n        .map(|o| {\n            Ok(AuditModelIo {\n                name: model.node_name(o.node).to_string(),\n                node: o.node,\n                slot: o.slot,\n                fact: model.outlet_fact_format(*o),\n            })\n        })\n        .collect::<TractResult<_>>()?;\n\n    let nodes: Vec<AuditNode> = (0..model.nodes_len())\n        .map(|id| {\n            let op = model.node_op(id);\n            let info = op.info().unwrap_or_default();\n            let node_inputs: Vec<AuditOutletRef> = model\n                .node_inputs(id)\n                .iter()\n                .map(|o| AuditOutletRef { node: o.node, slot: o.slot })\n                .collect();\n            let node_outputs: Vec<AuditNodeOutput> = (0..model.node_output_count(id))\n                .map(|slot| {\n                    let outlet = OutletId::new(id, slot);\n                    let fact = model.outlet_fact_format(outlet);\n                    let successors: Vec<AuditInletRef> = model\n                        .outlet_successors(outlet)\n                        .iter()\n                        .map(|inlet| AuditInletRef { node: inlet.node, slot: inlet.slot })\n                        .collect();\n                    AuditNodeOutput { fact, successors }\n                })\n                .collect();\n            let cost: HashMap<String, String> = annotations\n                .tags\n                .get(&NodeQId(tvec!(), id))\n                .map(|tags| {\n                    tags.cost.iter().map(|(k, v)| (format!(\"{k:?}\"), format!(\"{v}\"))).collect()\n                })\n                .unwrap_or_default();\n            AuditNode {\n                id,\n                name: model.node_name(id).to_string(),\n                op: model.node_op_name(id).to_string(),\n                info,\n                inputs: node_inputs,\n                outputs: node_outputs,\n                cost,\n            }\n        })\n        .collect();\n\n    let audit = AuditModel { properties, assertions, inputs, outputs, nodes };\n    serde_json::to_writer_pretty(writer, &audit)?;\n    Ok(())\n}\n"
  },
  {
    "path": "libcli/src/lib.rs",
    "content": "#![allow(clippy::collapsible_if)]\n#[macro_use]\nextern crate log;\n\npub mod annotations;\npub mod display_params;\npub mod draw;\npub mod export;\npub mod model;\npub mod profile;\npub mod tensor;\npub mod terminal;\npub mod time;\n\nuse tract_core::internal::*;\n#[allow(unused_imports)]\n#[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\nuse tract_cuda::utils::ensure_cuda_runtime_dependencies;\n\npub fn capture_gpu_trace<F>(matches: &clap::ArgMatches, func: F) -> TractResult<()>\nwhere\n    F: FnOnce() -> TractResult<()>,\n{\n    if matches.contains_id(\"metal-gpu-trace\")\n        && matches.get_one::<String>(\"metal-gpu-trace\").is_some()\n    {\n        #[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\n        {\n            let gpu_trace_path =\n                std::path::Path::new(matches.get_one::<String>(\"metal-gpu-trace\").unwrap())\n                    .to_path_buf();\n            ensure!(gpu_trace_path.is_absolute(), \"Metal GPU trace file has to be absolute\");\n            ensure!(\n                !gpu_trace_path.exists(),\n                format!(\"Given Metal GPU trace file {:?} already exists.\", gpu_trace_path)\n            );\n\n            log::info!(\"Capturing Metal GPU trace at : {gpu_trace_path:?}\");\n            tract_metal::with_metal_stream(move |stream| {\n                stream.capture_trace(gpu_trace_path, move |_stream| func())\n            })\n        }\n        #[cfg(not(any(target_os = \"macos\", target_os = \"ios\")))]\n        {\n            bail!(\"`--metal-gpu-trace` present but it is only available on MacOS and iOS\")\n        }\n    } else if matches.get_flag(\"cuda-gpu-trace\") {\n        #[cfg(any(target_os = \"linux\", target_os = \"windows\"))]\n        {\n            ensure_cuda_runtime_dependencies(\n                \"`--cuda-gpu-trace` present but no CUDA installation has been found\",\n            )?;\n            let _prof = cudarc::driver::safe::Profiler::new()?;\n            func()\n        }\n        #[cfg(not(any(target_os = \"linux\", target_os = \"windows\")))]\n        {\n            bail!(\"`--cuda-gpu-trace` present but it is only available on Linux and Windows\")\n        }\n    } else {\n        func()\n    }\n}\n"
  },
  {
    "path": "libcli/src/model.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::{downcast_rs, dyn_clone};\n\n/// Common methods for all variants of model.\npub trait Model:\n    downcast_rs::Downcast + std::fmt::Debug + dyn_clone::DynClone + Send + Sync\n{\n    /// Lookup node id by name\n    fn node_id_by_name(&self, name: &str) -> TractResult<usize>;\n\n    /// Node name by id\n    fn node_name(&self, id: usize) -> &str;\n\n    /// Node op by id\n    fn node_op(&self, id: usize) -> &dyn Op;\n\n    /// Node is const\n    fn node_const(&self, id: usize) -> bool;\n\n    /// Node op by id\n    fn node_op_name(&self, id: usize) -> StaticName;\n\n    /// Node inputs by id\n    fn node_inputs(&self, id: usize) -> &[OutletId];\n\n    /// Number of outputs for a node, by id.\n    fn node_output_count(&self, id: usize) -> usize;\n\n    /// Number nodes\n    fn nodes_len(&self) -> usize;\n\n    /// Formatted node label\n    fn node_display(&self, id: usize) -> String;\n\n    /// Formatted node label\n    fn node_debug(&self, id: usize) -> String;\n\n    /// Eval order for the model\n    fn eval_order(&self) -> TractResult<Vec<usize>>;\n\n    /// Eval order for the model\n    fn eval_order_opt_ram(&self) -> TractResult<Vec<usize>>;\n\n    /// Inputs of the model\n    fn input_outlets(&self) -> &[OutletId];\n\n    fn set_input_names(&mut self, names: &[&str]) -> TractResult<()>;\n    fn select_outputs_by_name(&mut self, names: &[&str]) -> TractResult<()>;\n\n    /// Outputs of the model\n    fn output_outlets(&self) -> &[OutletId];\n\n    /// Tensorfact for an outlet\n    fn outlet_typedfact(&self, outlet: OutletId) -> TractResult<TypedFact>;\n\n    /// Short outlet formatter (id plus fact)\n    fn outlet_fact_format(&self, outlet: OutletId) -> String;\n\n    /// Labels for an outlet\n    fn outlet_label(&self, id: OutletId) -> Option<&str>;\n\n    /// List consumers of an outlet\n    fn outlet_successors(&self, outlet: OutletId) -> &[InletId];\n\n    /// Subnets of a node\n    fn nested_models(&self, id: usize) -> Vec<(String, &dyn Model)> {\n        if let Some(submodel) =\n            self.node_op(id).downcast_ref::<tract_core::ops::submodel::SubmodelOp>()\n        {\n            return vec![(\"submodel\".into(), submodel.model())];\n        }\n        if let Some(lir) = self.node_op(id).downcast_ref::<tract_core::ops::scan::OptScan>() {\n            return vec![(\"loop\".into(), lir.plan.model())];\n        }\n        if let Some(mir) = self.node_op(id).downcast_ref::<tract_core::ops::scan::Scan>() {\n            return vec![(\"loop\".into(), &mir.body)];\n        }\n        if let Some(mir) = self.node_op(id).downcast_ref::<tract_core::ops::logic::IfThenElse>() {\n            return vec![(\"then\".into(), &mir.then_body), (\"else\".into(), &mir.else_body)];\n        }\n        #[cfg(feature = \"hir\")]\n        if let Some(hir) = self.node_op(id).downcast_ref::<tract_hir::ops::scan::InferenceScan>() {\n            return vec![(\"loop\".into(), &hir.body)];\n        }\n        #[cfg(feature = \"onnx\")]\n        if let Some(hir) = self.node_op(id).downcast_ref::<tract_onnx::ops::logic::If>() {\n            return vec![(\"then\".into(), &hir.then_body), (\"else\".into(), &hir.else_body)];\n        }\n        vec![]\n    }\n\n    /// Subnets of a node\n    fn nested_models_iters(&self, id: usize, input: &[&TypedFact]) -> Option<TDim> {\n        if let Some(submodel) =\n            self.node_op(id).downcast_ref::<tract_core::ops::submodel::SubmodelOp>()\n        {\n            submodel.iteration_count(input)\n        } else if let Some(lir) = self.node_op(id).downcast_ref::<tract_core::ops::scan::OptScan>()\n        {\n            lir.iteration_count(input)\n        } else if let Some(mir) = self.node_op(id).downcast_ref::<tract_core::ops::scan::Scan>() {\n            mir.iteration_count(input)\n        } else {\n            None\n        }\n    }\n\n    fn auto_outputs(&mut self) -> TractResult<()>;\n\n    fn properties(&self) -> &HashMap<String, Arc<Tensor>>;\n\n    fn symbols(&self) -> &SymbolScope;\n\n    fn get_or_intern_symbol(&self, name: &str) -> Symbol;\n\n    fn rename_node(&mut self, id: usize, name: &str) -> TractResult<()>;\n}\n\ndowncast_rs::impl_downcast!(Model);\ndyn_clone::clone_trait_object!(Model);\n\nimpl<F, O> Model for Graph<F, O>\nwhere\n    F: Fact + Hash + Clone + 'static,\n    O: std::fmt::Debug\n        + std::fmt::Display\n        + AsRef<dyn Op>\n        + AsMut<dyn Op>\n        + Clone\n        + 'static\n        + Send\n        + Sync,\n    Graph<F, O>: Send + Sync + 'static,\n{\n    fn node_id_by_name(&self, name: &str) -> TractResult<usize> {\n        self.nodes\n            .iter()\n            .find(|n| n.name == name)\n            .map(|n| n.id)\n            .with_context(|| format!(\"No node found for name: \\\"{name}\\\"\"))\n    }\n\n    fn node_name(&self, id: usize) -> &str {\n        &self.nodes[id].name\n    }\n\n    fn node_op_name(&self, id: usize) -> StaticName {\n        self.node(id).op().name()\n    }\n\n    fn node_const(&self, id: usize) -> bool {\n        self.node_op_name(id) == \"Const\"\n    }\n\n    fn node_inputs(&self, id: usize) -> &[OutletId] {\n        &self.nodes[id].inputs\n    }\n\n    fn node_output_count(&self, id: usize) -> usize {\n        self.nodes[id].outputs.len()\n    }\n\n    fn nodes_len(&self) -> usize {\n        self.nodes.len()\n    }\n\n    fn node_display(&self, id: usize) -> String {\n        format!(\"{}\", self.nodes[id])\n    }\n\n    fn node_debug(&self, id: usize) -> String {\n        format!(\"{:?}\", self.nodes[id])\n    }\n\n    fn eval_order(&self) -> TractResult<Vec<usize>> {\n        tract_core::model::order::eval_order(self)\n    }\n\n    fn eval_order_opt_ram(&self) -> TractResult<Vec<usize>> {\n        tract_core::model::order::eval_order_opt_ram(self)\n    }\n\n    fn input_outlets(&self) -> &[OutletId] {\n        &self.inputs\n    }\n\n    fn set_input_names(&mut self, names: &[&str]) -> TractResult<()> {\n        self.set_input_names(names.iter())\n    }\n\n    fn select_outputs_by_name(&mut self, names: &[&str]) -> TractResult<()> {\n        self.select_outputs_by_name(names)\n    }\n\n    fn output_outlets(&self) -> &[OutletId] {\n        &self.outputs\n    }\n\n    fn node_op(&self, id: usize) -> &dyn Op {\n        self.nodes[id].op.as_ref()\n    }\n\n    fn outlet_typedfact(&self, outlet: OutletId) -> TractResult<TypedFact> {\n        Ok(self.outlet_fact(outlet)?.to_typed_fact()?.into_owned())\n    }\n\n    fn outlet_fact_format(&self, outlet: OutletId) -> String {\n        format!(\"{:?}\", self.outlet_fact(outlet).unwrap())\n    }\n\n    fn outlet_label(&self, id: OutletId) -> Option<&str> {\n        self.outlet_label(id)\n    }\n\n    fn outlet_successors(&self, outlet: OutletId) -> &[InletId] {\n        &self.nodes[outlet.node].outputs[outlet.slot].successors\n    }\n\n    fn auto_outputs(&mut self) -> TractResult<()> {\n        self.auto_outputs()\n    }\n\n    fn properties(&self) -> &HashMap<String, Arc<Tensor>> {\n        &self.properties\n    }\n\n    fn symbols(&self) -> &SymbolScope {\n        &self.symbols\n    }\n    fn rename_node(&mut self, id: usize, name: &str) -> TractResult<()> {\n        self.rename_node(id, name)\n    }\n\n    fn get_or_intern_symbol(&self, name: &str) -> Symbol {\n        self.symbols.sym(name)\n    }\n}\n"
  },
  {
    "path": "libcli/src/profile.rs",
    "content": "use crate::model::Model;\nuse crate::tensor::RunTensors;\nuse crate::tensor::make_inputs_for_model;\nuse crate::{annotations::*, capture_gpu_trace};\nuse std::any::TypeId;\nuse std::time::{Duration, Instant};\nuse tract_core::internal::*;\nuse tract_core::num_traits::Zero;\nuse tract_core::ops::submodel::TypedModelOpState;\n\npub fn reusable_state(runnable: &Arc<dyn Runnable>) -> bool {\n    runnable.typed_model().is_some_and(|model| model.properties().contains_key(\"pulse.delay\"))\n}\n\npub fn run_one_step(\n    runnable: &Arc<dyn Runnable>,\n    state: &mut Box<dyn State>,\n    inputs: &RunTensors,\n) -> TractResult<Duration> {\n    if !reusable_state(runnable) {\n        *state = runnable.spawn()?;\n    }\n    let start = Instant::now();\n    for source in &inputs.sources {\n        state.run(source.clone())?;\n    }\n    Ok(start.elapsed())\n}\n\npub struct BenchLimits {\n    pub warmup_loops: usize,\n    pub warmup_time: std::time::Duration,\n    pub max_loops: usize,\n    pub max_time: std::time::Duration,\n}\n\nimpl Default for BenchLimits {\n    fn default() -> Self {\n        BenchLimits {\n            warmup_loops: 0,\n            warmup_time: Duration::default(),\n            max_loops: 100_000,\n            max_time: std::time::Duration::from_secs(5),\n        }\n    }\n}\n\nimpl BenchLimits {\n    pub fn warmup(&self, runnable: &Arc<dyn Runnable>, inputs: &RunTensors) -> TractResult<()> {\n        if self.warmup_time.is_zero() && self.warmup_loops.is_zero() {\n            return Ok(());\n        }\n        let reuse = reusable_state(runnable);\n        let mut state = runnable.spawn()?;\n\n        let mut iters = 0;\n        let max_loops = if self.warmup_loops.is_zero() { usize::MAX } else { self.warmup_loops };\n        let max_time = if self.warmup_time.is_zero() { Duration::MAX } else { self.warmup_time };\n\n        let start_warmup = Instant::now();\n        info!(\"Warming up before profiling...\");\n        while iters < max_loops && start_warmup.elapsed() < max_time {\n            if !reuse {\n                state = runnable.spawn()?;\n            }\n            state.run(inputs.sources[0].clone())?;\n            iters += 1;\n        }\n        info!(\"Done warming up.\");\n\n        Ok(())\n    }\n\n    pub fn bench(\n        &self,\n        runnable: &Arc<dyn Runnable>,\n        inputs: &RunTensors,\n    ) -> TractResult<(usize, Duration)> {\n        if self.max_time.is_zero() && self.max_loops.is_zero() {\n            return Ok(Default::default());\n        }\n        let reuse = reusable_state(runnable);\n        let mut state = runnable.spawn()?;\n\n        let mut iters = 0;\n        let max_loops = if self.max_loops.is_zero() { usize::MAX } else { self.max_loops };\n        let max_time = if self.max_time.is_zero() { Duration::MAX } else { self.max_time };\n\n        let mut dur = Duration::default();\n        let start = Instant::now();\n        while iters < max_loops && start.elapsed() < max_time {\n            if !reuse {\n                state = runnable.spawn()?;\n            }\n            let start_inner = Instant::now();\n            state.run(inputs.sources[0].clone())?;\n            dur += start_inner.elapsed();\n            iters += 1;\n        }\n\n        Ok((iters, dur))\n    }\n}\n\npub fn profile(\n    runnable: &Arc<dyn Runnable>,\n    bench_limits: &BenchLimits,\n    dg: &mut Annotations,\n    inputs: &RunTensors,\n    custom_profiler: Option<HashMap<TypeId, Profiler>>,\n    folded: bool,\n) -> TractResult<()> {\n    let Some(plan) = runnable.typed_plan() else {\n        bail!(\"Can only profile TypedRunnable\");\n    };\n    info!(\"Running entire network\");\n    let mut iters = 0usize;\n    let prefix = tvec!();\n\n    bench_limits.warmup(runnable, inputs)?;\n\n    let reuse = reusable_state(runnable);\n    let mut state = plan.spawn()?;\n\n    let mut dur = Duration::default();\n    let mut time_accounted_by_inner_nodes = Duration::default();\n    while iters < bench_limits.max_loops && dur < bench_limits.max_time {\n        if !reuse {\n            state = plan.spawn()?;\n        }\n        let start = Instant::now();\n\n        for source in &inputs.sources {\n            rec_profiler(\n                &mut state,\n                dg,\n                source,\n                custom_profiler.as_ref(),\n                &prefix,\n                None,\n                &mut time_accounted_by_inner_nodes,\n                folded,\n            )?;\n        }\n        dur += start.elapsed();\n        iters += 1;\n    }\n\n    dur -= time_accounted_by_inner_nodes;\n\n    info!(\"Running {} iterations max. for each node.\", bench_limits.max_loops);\n    info!(\"Running for {} ms max. for each node.\", bench_limits.max_time.as_millis());\n\n    let denum = (iters as f32).recip();\n    let entire = dur.mul_f32(denum);\n    for d in dg.tags.values_mut() {\n        if let Some(d) = d.profile.as_mut() {\n            *d = d.mul_f32(denum);\n        }\n\n        if let Some(d) = d.accelerator_profile.as_mut() {\n            *d = d.mul_f32(denum);\n        }\n    }\n    let max = dg.tags.values().filter_map(|t| t.profile).max().unwrap();\n    let sum = dg.tags.values().filter_map(|t| t.profile).sum::<Duration>();\n    let accel_sum = dg.tags.values().filter_map(|t| t.accelerator_profile).sum::<Duration>();\n    dg.profile_summary = Some(ProfileSummary { max, sum, accel_sum, entire, iters });\n    Ok(())\n}\n\npub fn profile_gpu(\n    runnable: &Arc<dyn Runnable>,\n    bench_limits: &BenchLimits,\n    sub_matches: &clap::ArgMatches,\n    dg: &mut Annotations,\n    inputs: &RunTensors,\n    before_node: &dyn Fn(usize),\n    after_iteration: &dyn Fn(&mut Annotations, &[(usize, String)]) -> TractResult<()>,\n) -> TractResult<()> {\n    let Some(plan) = runnable.typed_plan() else {\n        bail!(\"Can only profile TypedRunnable\");\n    };\n    info!(\"Running entire network\");\n    let mut iters = 0usize;\n    let prefix = tvec!();\n\n    bench_limits.warmup(runnable, inputs)?;\n\n    let reuse = reusable_state(runnable);\n    let mut state = plan.spawn()?;\n\n    let mut dur = Duration::default();\n\n    capture_gpu_trace(sub_matches, || -> TractResult<()> {\n        while iters < bench_limits.max_loops && dur < bench_limits.max_time {\n            if !reuse {\n                state = plan.spawn()?;\n            }\n            let start = Instant::now();\n            for source in &inputs.sources {\n                rec_profiler_gpu(&mut state, dg, source, &prefix, before_node)?;\n            }\n            after_iteration(dg, &prefix)?;\n            dur += start.elapsed();\n            iters += 1;\n        }\n        Ok(())\n    })?;\n\n    info!(\"Running {} iterations max. for each node.\", bench_limits.max_loops);\n    info!(\"Running for {} ms max. for each node.\", bench_limits.max_time.as_millis());\n\n    let denum = (iters as f32).recip();\n    let entire = dur.mul_f32(denum);\n    for d in dg.tags.values_mut() {\n        if let Some(d) = d.profile.as_mut() {\n            *d = d.mul_f32(denum);\n        }\n\n        if let Some(d) = d.accelerator_profile.as_mut() {\n            *d = d.mul_f32(denum);\n        }\n    }\n    let max = dg.tags.values().filter_map(|t| t.profile).max().unwrap();\n    let sum = dg.tags.values().filter_map(|t| t.profile).sum::<Duration>();\n    let accel_sum = dg.tags.values().filter_map(|t| t.accelerator_profile).sum::<Duration>();\n    dg.profile_summary = Some(ProfileSummary { max, sum, accel_sum, entire, iters });\n    Ok(())\n}\n\npub fn rec_profiler_gpu(\n    state: &mut TypedSimpleState,\n    dg: &mut Annotations,\n    inputs: &TVec<TValue>,\n    prefix: &[(usize, String)],\n    before_node: &dyn Fn(usize),\n) -> TractResult<TVec<TValue>> {\n    let r = state.run_plan_with_eval(\n        inputs.clone(),\n        |session_state, mut node_state, node, input| {\n            before_node(node.id);\n            // Profile node\n            let start = crate::time::now();\n            let res = tract_core::plan::eval(\n                session_state,\n                node_state.as_deref_mut(),\n                node,\n                input.clone(),\n            );\n            let elapsed = start.elapsed();\n            let node_id = NodeQId(prefix.into(), node.id);\n            *dg.node_mut(node_id).profile.get_or_insert(Duration::default()) += elapsed;\n\n            res\n        },\n    )?;\n\n    Ok(r)\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn rec_profiler(\n    state: &mut TypedSimpleState,\n    dg: &mut Annotations,\n    inputs: &TVec<TValue>,\n    profilers: Option<&HashMap<TypeId, Profiler>>,\n    prefix: &[(usize, String)],\n    multiplier: Option<usize>,\n    time_accounted_by_inner_nodes: &mut Duration,\n    folded: bool,\n) -> TractResult<TVec<TValue>> {\n    let r = state.run_plan_with_eval(\n        inputs.clone(),\n        |session_state, mut node_state, node, input| {\n            // Profile node\n            let start = crate::time::now();\n            let res = tract_core::plan::eval(\n                session_state,\n                node_state.as_deref_mut(),\n                node,\n                input.clone(),\n            );\n            let elapsed = start.elapsed().mul_f32(multiplier.unwrap_or(1) as _);\n            let node_id = NodeQId(prefix.into(), node.id);\n            *dg.node_mut(node_id).profile.get_or_insert(Duration::default()) += elapsed;\n\n            if !folded {\n                let start = crate::time::now();\n                profile_submodel(\n                    node,\n                    node_state,\n                    input,\n                    dg,\n                    profilers,\n                    prefix,\n                    time_accounted_by_inner_nodes,\n                )?;\n                *time_accounted_by_inner_nodes += start.elapsed();\n            }\n\n            // Update parent nodes if any (childs timings are deducted from parents)\n            let prefix_vec = prefix.to_vec();\n            if !prefix_vec.is_empty() {\n                (1..prefix_vec.len() + 1).map(|idx| prefix_vec[..idx].to_vec()).for_each(\n                    |parent_path| {\n                        let parent_node = parent_path.last().map(|it| it.0).unwrap();\n                        let parent = dg\n                            .node_mut(NodeQId(\n                                parent_path[..parent_path.len() - 1].into(),\n                                parent_node,\n                            ))\n                            .profile\n                            .get_or_insert(Duration::default());\n                        *parent -= elapsed.min(*parent);\n                    },\n                );\n            }\n            res\n        },\n    )?;\n    Ok(r)\n}\n\nfn profile_submodel(\n    node: &TypedNode,\n    mut node_state: Option<&mut dyn OpState>,\n    input: TVec<TValue>,\n    dg: &mut Annotations,\n    profilers: Option<&HashMap<TypeId, Profiler>>,\n    prefix: &[(usize, String)],\n    time_accounted_by_inner_nodes: &mut Duration,\n) -> TractResult<()> {\n    if let Some(ref mut op_state) = node_state {\n        if let Some(profiler) = profilers.and_then(|it| it.get(&op_state.type_id())) {\n            let mut new_prefix: TVec<_> = prefix.into();\n            new_prefix.push((node.id, \"submodel\".to_string()));\n\n            let (_, _) =\n                (profiler.func)(*op_state, input, dg, &new_prefix, time_accounted_by_inner_nodes)?;\n        } else if let Some(scan_state) = op_state.downcast_mut::<tract_core::ops::scan::State>() {\n            let mut new_prefix: TVec<_> = prefix.into();\n            new_prefix.push((node.id, \"loop\".to_string()));\n\n            let scan_inputs = make_inputs_for_model(scan_state.model_state.model())?;\n            let multi = scan_state.iteration_count(&input);\n\n            rec_profiler(\n                &mut scan_state.model_state,\n                dg,\n                &scan_inputs,\n                None,\n                &new_prefix,\n                Some(multi),\n                time_accounted_by_inner_nodes,\n                false,\n            )?;\n        } else if let Some(typed_model_state) = op_state.downcast_mut::<TypedModelOpState>() {\n            let mut new_prefix: TVec<_> = prefix.into();\n            new_prefix.push((node.id, \"submodel\".to_string()));\n\n            rec_profiler(\n                typed_model_state,\n                dg,\n                &input,\n                None,\n                &new_prefix,\n                None,\n                time_accounted_by_inner_nodes,\n                false,\n            )?;\n        }\n    }\n\n    Ok(())\n}\n\ntype ProfilerFn = fn(\n    &mut dyn OpState,\n    TVec<TValue>,\n    &mut Annotations,\n    &[(usize, String)],\n    &mut Duration,\n) -> TractResult<(TractResult<TVec<TValue>>, Duration)>;\n\n#[derive(Clone)]\npub struct Profiler {\n    pub func: ProfilerFn,\n    pub name: &'static str,\n}\n\nimpl Hash for Profiler {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.name.hash(state)\n    }\n}\n\npub fn extract_costs(\n    annotations: &mut Annotations,\n    model: &dyn Model,\n    extra_symbols: &SymbolValues,\n) -> TractResult<()> {\n    fn extract_costs_rec(\n        annotations: &mut Annotations,\n        model: &dyn Model,\n        prefix: &[(usize, String)],\n        multiplier: TDim,\n        extra_symbols: &SymbolValues,\n    ) -> TractResult<()> {\n        if let Some(model) = model.downcast_ref::<TypedModel>() {\n            for node_id in 0..model.nodes().len() {\n                let inputs = model.node_input_facts(node_id)?;\n                let cost = model\n                    .node(node_id)\n                    .op\n                    .cost(&inputs)\n                    .with_context(|| format!(\"costing node {}\", model.node(node_id)))?;\n                annotations.node_mut(NodeQId(prefix.into(), node_id)).cost = cost\n                    .into_iter()\n                    .map(|(k, v)| {\n                        let cost = if k.is_compute() { v * &multiplier } else { v };\n                        (k, cost.eval(extra_symbols))\n                    })\n                    .collect();\n\n                let nested_subs = model.nested_models(node_id);\n                let nested_multis = (model as &dyn Model).nested_models_iters(node_id, &inputs);\n                for (name, sub) in nested_subs {\n                    let mut prefix: TVec<_> = prefix.into();\n                    prefix.push((node_id, name.to_string()));\n                    extract_costs_rec(\n                        annotations,\n                        sub,\n                        &prefix,\n                        nested_multis.clone().unwrap_or_else(|| 1.into()) * &multiplier,\n                        extra_symbols,\n                    )?;\n                }\n            }\n        }\n        Ok(())\n    }\n    extract_costs_rec(annotations, model, &[], 1.into(), extra_symbols)\n}\n"
  },
  {
    "path": "libcli/src/tensor.rs",
    "content": "use std::collections::HashSet;\nuse std::io::{Read, Seek};\nuse std::ops::Range;\nuse std::str::FromStr;\nuse std::sync::Mutex;\n\nuse crate::model::Model;\nuse tract_hir::internal::*;\nuse tract_num_traits::Zero;\n\n#[cfg(feature = \"transformers\")]\nuse tract_transformers::figure_out_causal_llm_b_s_p;\n\n#[derive(Debug, Default, Clone)]\npub struct TensorsValues(pub Vec<TensorValues>);\n\nimpl TensorsValues {\n    pub fn by_name(&self, name: &str) -> Option<&TensorValues> {\n        self.0.iter().find(|t| t.name.as_deref() == Some(name))\n    }\n    pub fn by_name_mut(&mut self, name: &str) -> Option<&mut TensorValues> {\n        self.0.iter_mut().find(|t| t.name.as_deref() == Some(name))\n    }\n    pub fn by_name_mut_with_default(&mut self, name: &str) -> &mut TensorValues {\n        if self.by_name_mut(name).is_none() {\n            self.add(TensorValues { name: Some(name.to_string()), ..TensorValues::default() });\n        }\n        self.by_name_mut(name).unwrap()\n    }\n\n    pub fn by_input_ix(&self, ix: usize) -> Option<&TensorValues> {\n        self.0.iter().find(|t| t.input_index == Some(ix))\n    }\n    pub fn by_input_ix_mut(&mut self, ix: usize) -> Option<&mut TensorValues> {\n        self.0.iter_mut().find(|t| t.input_index == Some(ix))\n    }\n    pub fn by_input_ix_mut_with_default(&mut self, ix: usize) -> &mut TensorValues {\n        if self.by_input_ix_mut(ix).is_none() {\n            self.add(TensorValues { input_index: Some(ix), ..TensorValues::default() });\n        }\n        self.by_input_ix_mut(ix).unwrap()\n    }\n\n    pub fn add(&mut self, other: TensorValues) {\n        let mut tensor = other.input_index.and_then(|ix| self.by_input_ix_mut(ix));\n\n        if tensor.is_none() {\n            tensor = other.name.as_deref().and_then(|ix| self.by_name_mut(ix))\n        }\n\n        if let Some(tensor) = tensor {\n            if tensor.fact.is_none() {\n                tensor.fact = other.fact;\n            }\n            if tensor.values.is_none() {\n                tensor.values = other.values;\n            }\n        } else {\n            self.0.push(other.clone());\n        };\n    }\n\n    pub fn input_by_name(&self, name: &str) -> Option<&TensorValues> {\n        self.0\n            .iter()\n            .filter(|tv| tv.output_index.is_none() && !tv.only_output)\n            .find(|t| t.name.as_deref() == Some(name))\n    }\n}\n\n#[derive(Debug, PartialEq, Clone, Default)]\npub struct TensorValues {\n    pub input_index: Option<usize>,\n    pub output_index: Option<usize>,\n    pub name: Option<String>,\n    pub fact: Option<InferenceFact>,\n    pub values: Option<Vec<TValue>>,\n    pub random_range: Option<Range<f32>>,\n    pub only_input: bool,\n    pub only_output: bool,\n}\n\nfn parse_dt(dt: &str) -> TractResult<DatumType> {\n    Ok(match dt.to_lowercase().as_ref() {\n        \"bool\" => DatumType::Bool,\n        \"f16\" => DatumType::F16,\n        \"f32\" => DatumType::F32,\n        \"f64\" => DatumType::F64,\n        \"i8\" => DatumType::I8,\n        \"i16\" => DatumType::I16,\n        \"i32\" => DatumType::I32,\n        \"i64\" => DatumType::I64,\n        \"u8\" => DatumType::U8,\n        \"u16\" => DatumType::U16,\n        \"u32\" => DatumType::U32,\n        \"u64\" => DatumType::U64,\n        \"tdim\" => DatumType::TDim,\n        _ => bail!(\n            \"Type of the input should be f16, f32, f64, i8, i16, i16, i32, u8, u16, u32, u64, TDim.\"\n        ),\n    })\n}\n\npub fn parse_spec(symbol_table: &SymbolScope, size: &str) -> TractResult<InferenceFact> {\n    if size.is_empty() {\n        return Ok(InferenceFact::default());\n    }\n    parse_coma_spec(symbol_table, size)\n}\n\npub fn parse_coma_spec(symbol_table: &SymbolScope, size: &str) -> TractResult<InferenceFact> {\n    let splits = size.split(',').collect::<Vec<_>>();\n\n    #[allow(clippy::literal_string_with_formatting_args)]\n    if splits.is_empty() {\n        bail!(\"The <size> argument should be formatted as {{size}},{{...}},{{type}}.\");\n    }\n\n    let last = splits.last().unwrap();\n    let (datum_type, shape) = if let Ok(dt) = parse_dt(last) {\n        (Some(dt), &splits[0..splits.len() - 1])\n    } else {\n        (None, &*splits)\n    };\n\n    let shape = ShapeFactoid::closed(\n        shape\n            .iter()\n            .map(|&s| {\n                Ok(if s == \"_\" {\n                    GenericFactoid::Any\n                } else {\n                    GenericFactoid::Only(parse_tdim(symbol_table, s)?)\n                })\n            })\n            .collect::<TractResult<TVec<DimFact>>>()?,\n    );\n\n    if let Some(dt) = datum_type {\n        Ok(InferenceFact::dt_shape(dt, shape))\n    } else {\n        Ok(InferenceFact::shape(shape))\n    }\n}\n\nfn parse_values<T: Datum + FromStr>(shape: &[usize], it: Vec<&str>) -> TractResult<Tensor> {\n    let values = it\n        .into_iter()\n        .map(|v| v.parse::<T>().map_err(|_| format_err!(\"Failed to parse {}\", v)))\n        .collect::<TractResult<Vec<T>>>()?;\n    Ok(tract_ndarray::Array::from_shape_vec(shape, values)?.into())\n}\n\nfn tensor_for_text_data(\n    symbol_table: &SymbolScope,\n    _filename: &str,\n    mut reader: impl Read,\n) -> TractResult<Tensor> {\n    let mut data = String::new();\n    reader.read_to_string(&mut data)?;\n\n    let mut lines = data.lines();\n    let proto = parse_spec(symbol_table, lines.next().context(\"Empty data file\")?)?;\n    let shape = proto.shape.concretize().unwrap();\n\n    let values = lines.flat_map(|l| l.split_whitespace()).collect::<Vec<&str>>();\n\n    // We know there is at most one streaming dimension, so we can deduce the\n    // missing value with a simple division.\n    let product: usize = shape.iter().map(|o| o.to_usize().unwrap_or(1)).product();\n    let missing = values.len() / product;\n\n    let shape: Vec<_> = shape.iter().map(|d| d.to_usize().unwrap_or(missing)).collect();\n    dispatch_numbers!(parse_values(proto.datum_type.concretize().unwrap())(&*shape, values))\n}\n\n/// Parses the `data` command-line argument.\npub fn for_data(\n    symbol_table: &SymbolScope,\n    filename: &str,\n    reader: impl Read + std::io::Seek,\n) -> TractResult<(Option<String>, InferenceFact)> {\n    #[allow(unused_imports)]\n    use std::convert::TryFrom;\n    if filename.ends_with(\".pb\") {\n        #[cfg(feature = \"onnx\")]\n        {\n            use tract_onnx::data_resolver::FopenDataResolver;\n            use tract_onnx::tensor::load_tensor;\n            let proto = ::tract_onnx::tensor::proto_from_reader(reader)?;\n            let tensor = load_tensor(&FopenDataResolver, &proto, None)?;\n            Ok((Some(proto.name.to_string()).filter(|s| !s.is_empty()), tensor.into()))\n        }\n        #[cfg(not(feature = \"onnx\"))]\n        {\n            panic!(\"Loading tensor from protobuf requires onnx features\");\n        }\n    } else if filename.contains(\".npz:\") {\n        let mut tokens = filename.split(':');\n        let (_filename, inner) = (tokens.next().unwrap(), tokens.next().unwrap());\n        let mut npz = ndarray_npy::NpzReader::new(reader)?;\n        Ok((None, for_npz(&mut npz, inner)?.into()))\n    } else {\n        Ok((None, tensor_for_text_data(symbol_table, filename, reader)?.into()))\n    }\n}\n\npub fn for_npz(\n    npz: &mut ndarray_npy::NpzReader<impl Read + Seek>,\n    name: &str,\n) -> TractResult<Tensor> {\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<f32>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<f64>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<i8>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<i16>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<i32>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<i64>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<u8>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<u16>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<u32>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<u64>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    if let Ok(t) = npz.by_name::<tract_ndarray::OwnedRepr<bool>, tract_ndarray::IxDyn>(name) {\n        return Ok(t.into_tensor());\n    }\n    bail!(\"Can not extract tensor from {}\", name);\n}\n\npub fn for_string(\n    symbol_table: &SymbolScope,\n    value: &str,\n) -> TractResult<(Option<String>, InferenceFact)> {\n    let (name, value) = if value.contains(':') {\n        let mut splits = value.split(':');\n        (Some(splits.next().unwrap().to_string()), splits.next().unwrap())\n    } else {\n        (None, value)\n    };\n    if value.contains('=') {\n        let mut split = value.split('=');\n        let spec = parse_spec(symbol_table, split.next().unwrap())?;\n        let value = split.next().unwrap().split(',');\n        let dt =\n            spec.datum_type.concretize().context(\"Must specify type when giving tensor value\")?;\n        let shape = spec\n            .shape\n            .as_concrete_finite()?\n            .context(\"Must specify concrete shape when giving tensor value\")?;\n        let tensor = if dt == TDim::datum_type() {\n            let mut tensor = Tensor::zero::<TDim>(&shape)?;\n            let values =\n                value.map(|v| parse_tdim(symbol_table, v)).collect::<TractResult<Vec<_>>>()?;\n            tensor\n                .try_as_plain_mut()?\n                .as_slice_mut::<TDim>()?\n                .iter_mut()\n                .zip(values)\n                .for_each(|(t, v)| *t = v);\n            tensor\n        } else {\n            dispatch_numbers!(parse_values(dt)(&*shape, value.collect()))?\n        };\n        Ok((name, tensor.into()))\n    } else {\n        Ok((name, parse_spec(symbol_table, value)?))\n    }\n}\n\nlazy_static::lazy_static! {\n    static ref MESSAGE_ONCE: Mutex<HashSet<String>> = Mutex::new(HashSet::new());\n}\n\nfn info_once(msg: String) {\n    if MESSAGE_ONCE.lock().unwrap().insert(msg.clone()) {\n        info!(\"{msg}\");\n    }\n}\n\npub struct RunParams {\n    pub tensors_values: TensorsValues,\n    pub allow_random_input: bool,\n    pub allow_float_casts: bool,\n    pub symbols: SymbolValues,\n    pub prompt_chunk_size: Option<usize>,\n}\n\npub struct RunTensors {\n    pub sources: Vec<TVec<TValue>>,\n}\n\n#[cfg(feature = \"transformers\")]\nfn chunk_fact(\n    fact: &TypedFact,\n    params: &RunParams,\n    model: &Arc<dyn Model>,\n) -> TractResult<Vec<TypedFact>> {\n    let Some(chunk_size) = params.prompt_chunk_size else {\n        return Ok(vec![fact.clone()]);\n    };\n    let Some(model) = model.downcast_ref::<TypedModel>() else {\n        return Ok(vec![fact.clone()]);\n    };\n    let (_, s, _) = figure_out_causal_llm_b_s_p(model)?;\n    let Some(s) = s else {\n        return Ok(vec![fact.clone()]);\n    };\n\n    let dims = fact.shape.dims();\n    let Some(sym_idx) = dims.iter().position(|d| *d == TDim::Sym(s.clone())) else {\n        return Ok(vec![fact.clone()]);\n    };\n\n    let resolved_sym = dims[sym_idx].eval_to_i64(&params.symbols)? as usize;\n    if resolved_sym <= chunk_size {\n        return Ok(vec![fact.clone()]);\n    }\n\n    let num_chunks = resolved_sym.div_ceil(chunk_size);\n    let mut out = Vec::with_capacity(num_chunks);\n\n    for start in (0..resolved_sym).step_by(chunk_size) {\n        let this = chunk_size.min(resolved_sym - start) as i64;\n\n        let mut new_fact = fact.clone();\n        new_fact.shape = new_fact\n            .shape\n            .iter()\n            .enumerate()\n            .map(|(i, d)| if i == sym_idx { TDim::Val(this) } else { d.eval(&params.symbols) })\n            .collect();\n\n        out.push(new_fact);\n    }\n\n    Ok(out)\n}\n\n#[cfg(feature = \"transformers\")]\nfn chunk_tensor(\n    tensor: Tensor,\n    fact: &TypedFact,\n    params: &RunParams,\n    model: &Arc<dyn Model>,\n) -> TractResult<Vec<TValue>> {\n    let Some(chunk_size) = params.prompt_chunk_size else {\n        return Ok(vec![tensor.into_tvalue()]);\n    };\n\n    let Some(model) = model.downcast_ref::<TypedModel>() else {\n        return Ok(vec![tensor.into_tvalue()]);\n    };\n    let (_, s, _) = figure_out_causal_llm_b_s_p(model)?;\n    let Some(s) = s else {\n        return Ok(vec![tensor.into_tvalue()]);\n    };\n\n    let dims = fact.shape.dims();\n    let Some(symb_axis) = dims.iter().position(|d| *d == TDim::Sym(s.clone())) else {\n        return Ok(vec![tensor.into_tvalue()]);\n    };\n\n    let resolved_sym = tensor.shape()[symb_axis];\n    if resolved_sym <= chunk_size {\n        return Ok(vec![tensor.into_tvalue()]);\n    }\n\n    let num_chunks = resolved_sym.div_ceil(chunk_size);\n    let mut out = Vec::with_capacity(num_chunks);\n\n    for start in (0..resolved_sym).step_by(chunk_size) {\n        let this = chunk_size.min(resolved_sym - start);\n        out.push(tensor.slice(symb_axis, start, start + this)?.into_tvalue());\n    }\n\n    Ok(out)\n}\n\nfn get_or_make_tensors(\n    model: &Arc<dyn Model>,\n    params: &RunParams,\n    fact: TypedFact,\n    name: &str,\n    input_idx: usize,\n    target: &mut TVec<Vec<TValue>>,\n) -> TractResult<()> {\n    if let Some(mut value) = params\n        .tensors_values\n        .by_name(name)\n        .or_else(|| params.tensors_values.by_input_ix(input_idx))\n        .and_then(|t| t.values.clone())\n    {\n        if !value[0].datum_type().is_quantized()\n            && fact.datum_type.is_quantized()\n            && value[0].datum_type() == fact.datum_type.unquantized()\n        {\n            value = value\n                .iter()\n                .map(|v| {\n                    let mut v = v.clone().into_tensor();\n                    unsafe { v.set_datum_type(fact.datum_type) };\n                    v.into()\n                })\n                .collect();\n        }\n        let mut chunked_tensors: Vec<TValue> = vec![];\n        for t in &value {\n            let tensor = if TypedFact::shape_and_dt_of(&value[0]).compatible_with(&fact) {\n                info_once(format!(\n                    \"Using fixed input for input called {} ({} turn(s))\",\n                    name,\n                    value.len()\n                ));\n                t.clone().into_tensor()\n            } else if fact.datum_type == f16::datum_type()\n                && value[0].datum_type() == f32::datum_type()\n                && params.allow_float_casts\n            {\n                debug!(\"Casting input to F16 for input called {} ({} turn(s))\", name, value.len());\n                t.cast_to::<f16>()?.into_owned()\n            } else {\n                break;\n            };\n\n            chunked_tensors.extend(chunk_tensor(tensor, &fact, params, model)?);\n        }\n        if !chunked_tensors.is_empty() {\n            target.push(chunked_tensors);\n            return Ok(());\n        }\n\n        if value.len() == 1 && model.properties().contains_key(\"pulse.delay\") {\n            let value = &value[0];\n            let input_pulse_axis = model\n                .properties()\n                .get(\"pulse.input_axes\")\n                .context(\"Expect pulse.input_axes property\")?\n                .cast_to::<i64>()?\n                .try_as_plain()?\n                .as_slice::<i64>()?[input_idx] as usize;\n            let input_pulse = fact.shape.get(input_pulse_axis).unwrap().to_usize().unwrap();\n            let input_len = value.shape()[input_pulse_axis];\n\n            // how many pulses do we need to push full result out ?\n            // guess by looking at len and delay of the first output\n            let output_pulse_axis = model\n                .properties()\n                .get(\"pulse.output_axes\")\n                .context(\"Expect pulse.output_axes property\")?\n                .cast_to::<i64>()?\n                .try_as_plain()?\n                .as_slice::<i64>()?[0] as usize;\n            let output_fact = model.outlet_typedfact(model.output_outlets()[0])?;\n            let output_pulse =\n                output_fact.shape.get(output_pulse_axis).unwrap().to_usize().unwrap();\n            let output_len = input_len * output_pulse / input_pulse;\n            let output_delay =\n                model.properties()[\"pulse.delay\"].try_as_plain()?.as_slice::<i64>()?[0] as usize;\n            let last_frame = output_len + output_delay;\n            let needed_pulses = last_frame.divceil(output_pulse);\n            let mut values = vec![];\n            for ix in 0..needed_pulses {\n                let mut t = Tensor::zero_dt(fact.datum_type, fact.shape.as_concrete().unwrap())?;\n                let start = ix * input_pulse;\n                let end = (start + input_pulse).min(input_len);\n                if end > start {\n                    t.assign_slice(0..end - start, value, start..end, input_pulse_axis)?;\n                }\n                values.push(t.into());\n            }\n            info!(\n                \"Generated {} pulses of shape {:?} for input {}.\",\n                needed_pulses, fact.shape, input_idx\n            );\n            target.push(values);\n        } else {\n            bail!(\n                \"For input {}, can not reconcile model input fact {:?} with provided input {:?}\",\n                name,\n                fact,\n                value[0]\n            );\n        };\n    } else if fact.shape.is_concrete() && fact.shape.volume() == TDim::zero() {\n        let shape = fact.shape.as_concrete().unwrap();\n        let tensor = Tensor::zero_dt(fact.datum_type, shape)?;\n        target.push(vec![tensor.into()]);\n    } else if params.allow_random_input {\n        info_once(format!(\"Using random input for input called {name:?}: {fact:?}\"));\n        let tv = params\n            .tensors_values\n            .by_name(name)\n            .or_else(|| params.tensors_values.by_input_ix(input_idx));\n\n        let mut chunked_facts = chunk_fact(&fact, params, model)?;\n\n        let mut chunked_tensors = Vec::with_capacity(chunked_facts.len());\n        for fact in &mut chunked_facts {\n            fact.shape = fact.shape.iter().map(|dim| dim.eval(&params.symbols)).collect();\n            chunked_tensors.push(tensor_for_fact(fact, None, tv)?.into());\n        }\n        target.push(chunked_tensors);\n    } else {\n        bail!(\n            \"Unmatched tensor {}. Fix the input or use \\\"--allow-random-input\\\" if this was intended\",\n            name\n        );\n    }\n    Ok(())\n}\n\npub fn get_or_make_inputs(tract: &Arc<dyn Model>, params: &RunParams) -> TractResult<RunTensors> {\n    // Resolve source inputs\n    let mut tmp_inputs = tvec![];\n    for (ix, input) in tract.input_outlets().iter().enumerate() {\n        let fact = tract.outlet_typedfact(*input)?;\n        let name = tract.node_name(input.node);\n        get_or_make_tensors(tract, params, fact, name, ix, &mut tmp_inputs)?;\n    }\n\n    let n_turns = tmp_inputs.iter().map(|t| t.len()).max().unwrap_or(0);\n    let sources = (0..n_turns)\n        .map(|i| {\n            tmp_inputs\n                .iter()\n                .map(|t| if i < t.len() { t[i].clone() } else { t[t.len() - 1].clone() })\n                .collect::<TVec<_>>()\n        })\n        .collect::<Vec<_>>();\n\n    Ok(RunTensors { sources })\n}\n\nfn make_inputs(values: &[impl std::borrow::Borrow<TypedFact>]) -> TractResult<TVec<TValue>> {\n    values.iter().map(|v| tensor_for_fact(v.borrow(), None, None).map(|t| t.into())).collect()\n}\n\npub fn make_inputs_for_model(model: &dyn Model) -> TractResult<TVec<TValue>> {\n    make_inputs(\n        &model\n            .input_outlets()\n            .iter()\n            .map(|&t| model.outlet_typedfact(t))\n            .collect::<TractResult<Vec<TypedFact>>>()?,\n    )\n}\n\n#[allow(unused_variables)]\npub fn tensor_for_fact(\n    fact: &TypedFact,\n    streaming_dim: Option<usize>,\n    tv: Option<&TensorValues>,\n) -> TractResult<Tensor> {\n    if let Some(value) = &fact.konst {\n        return Ok(value.clone().into_tensor());\n    }\n    Ok(random(\n        fact.shape\n            .as_concrete()\n            .with_context(|| format!(\"Expected concrete shape, found: {fact:?}\"))?,\n        fact.datum_type,\n        tv,\n    ))\n}\n\n/// Generates a random tensor of a given size and type.\npub fn random(sizes: &[usize], datum_type: DatumType, tv: Option<&TensorValues>) -> Tensor {\n    use rand::{RngExt, SeedableRng};\n    let mut rng = rand::rngs::StdRng::seed_from_u64(21242);\n    let mut tensor = Tensor::zero::<f32>(sizes).unwrap();\n    let mut tensor_plain = tensor.try_as_plain_mut().unwrap();\n    let slice = tensor_plain.as_slice_mut::<f32>().unwrap();\n    if let Some(range) = tv.and_then(|tv| tv.random_range.as_ref()) {\n        slice.iter_mut().for_each(|x| *x = rng.random_range(range.clone()))\n    } else {\n        slice.iter_mut().for_each(|x| *x = rng.random())\n    };\n    tensor.cast_to_dt(datum_type).unwrap().into_owned()\n}\n"
  },
  {
    "path": "libcli/src/terminal.rs",
    "content": "use std::time::Duration;\n\nuse crate::annotations::*;\nuse crate::display_params::*;\nuse crate::draw::DrawingState;\nuse crate::model::Model;\nuse nu_ansi_term::AnsiString;\nuse nu_ansi_term::Color::*;\n#[allow(unused_imports)]\nuse std::convert::TryFrom;\nuse tract_core::internal::*;\nuse tract_core::num_traits::AsPrimitive;\nuse tract_itertools::Itertools;\n\npub fn render(\n    model: &dyn Model,\n    annotations: &Annotations,\n    options: &DisplayParams,\n) -> TractResult<()> {\n    if options.quiet {\n        return Ok(());\n    }\n    render_prefixed(model, \"\", &[], annotations, options)?;\n    if !model.properties().is_empty() {\n        println!(\"{}\", White.bold().paint(\"# Properties\"));\n    }\n    for (k, v) in model.properties().iter().sorted_by_key(|(k, _)| k.to_string()) {\n        println!(\"* {}: {:?}\", White.paint(k), v)\n    }\n    let symbols = model.symbols();\n    if !symbols.all_assertions().is_empty() {\n        println!(\"{}\", White.bold().paint(\"# Assertions\"));\n        for a in symbols.all_assertions() {\n            println!(\" * {a}\");\n        }\n    }\n    for (ix, scenario) in symbols.all_scenarios().into_iter().enumerate() {\n        if ix == 0 {\n            println!(\"{}\", White.bold().paint(\"# Scenarios\"));\n        }\n        for a in scenario.1 {\n            println!(\" * {}: {}\", scenario.0, a);\n        }\n    }\n    Ok(())\n}\n\npub fn render_node(\n    model: &dyn Model,\n    node_id: usize,\n    annotations: &Annotations,\n    options: &DisplayParams,\n) -> TractResult<()> {\n    render_node_prefixed(model, \"\", &[], node_id, None, annotations, options)\n}\n\nfn render_prefixed(\n    model: &dyn Model,\n    prefix: &str,\n    scope: &[(usize, String)],\n    annotations: &Annotations,\n    options: &DisplayParams,\n) -> TractResult<()> {\n    let mut drawing_state =\n        if options.should_draw() { Some(DrawingState::default()) } else { None };\n    let node_ids = options.order(model)?;\n    for node in node_ids {\n        if options.filter(model, scope, node)? {\n            render_node_prefixed(\n                model,\n                prefix,\n                scope,\n                node,\n                drawing_state.as_mut(),\n                annotations,\n                options,\n            )?\n        } else if let Some(ref mut ds) = drawing_state {\n            let _prefix = ds.draw_node_vprefix(model, node, options)?;\n            let _body = ds.draw_node_body(model, node, options)?;\n            let _suffix = ds.draw_node_vsuffix(model, node, options)?;\n        }\n    }\n    Ok(())\n}\n\npub fn si_prefix(v: impl AsPrimitive<f64>, unit: &str) -> String {\n    radical_prefix(v, unit, 1000, \"\")\n}\n\npub fn pow2_prefix(v: impl AsPrimitive<f64>, unit: &str) -> String {\n    radical_prefix(v, unit, 1024, \"i\")\n}\n\npub fn radical_prefix(\n    v: impl AsPrimitive<f64>,\n    unit: &str,\n    radical: usize,\n    radical_prefix: &str,\n) -> String {\n    let v: f64 = v.as_();\n    let radical = radical as f64;\n    let radical3 = radical.powi(3);\n    let radical2 = radical.powi(2);\n    if v > radical3 {\n        format!(\"{:7.3} G{}{}\", v / radical3, radical_prefix, unit)\n    } else if v > 1e6 {\n        format!(\"{:7.3} M{}{}\", v / radical2, radical_prefix, unit)\n    } else if v > 1e3 {\n        format!(\"{:7.3} k{}{}\", v / radical, radical_prefix, unit)\n    } else {\n        format!(\"{v:7.3}  {unit}\")\n    }\n}\n\nfn render_node_prefixed(\n    model: &dyn Model,\n    prefix: &str,\n    scope: &[(usize, String)],\n    node_id: usize,\n    mut drawing_state: Option<&mut DrawingState>,\n    annotations: &Annotations,\n    options: &DisplayParams,\n) -> TractResult<()> {\n    let qid = NodeQId(scope.into(), node_id);\n    let tags = annotations.tags.get(&qid).cloned().unwrap_or_default();\n    let name_color = tags.style.unwrap_or_else(|| White.into());\n    let node_name = model.node_name(node_id);\n    let node_op_name = model.node_op_name(node_id);\n    let profile_column_pad = format!(\"{:>1$}\", \"\", options.profile as usize * 20);\n    let cost_column_pad = format!(\"{:>1$}\", \"\", options.cost as usize * 25);\n    let mem_padding = if annotations.memory_summary.is_some() { 15 } else { 30 };\n    let tmp_mem_usage_column_pad =\n        format!(\"{:>1$}\", \"\", options.tmp_mem_usage as usize * mem_padding);\n    let flops_column_pad = format!(\"{:>1$}\", \"\", (options.profile && options.cost) as usize * 20);\n\n    if let Some(ds) = &mut drawing_state {\n        for l in ds.draw_node_vprefix(model, node_id, options)? {\n            println!(\n                \"{cost_column_pad}{profile_column_pad}{flops_column_pad}{tmp_mem_usage_column_pad}{prefix}{l} \"\n            );\n        }\n    }\n\n    // profile column\n    let mut profile_column = tags.profile.map(|measure| {\n        let profile_summary = annotations.profile_summary.as_ref().unwrap();\n        let use_micros = profile_summary.sum < Duration::from_millis(1);\n        let ratio = measure.as_secs_f64() / profile_summary.sum.as_secs_f64();\n        let ratio_for_color = measure.as_secs_f64() / profile_summary.max.as_secs_f64();\n        let color = colorous::RED_YELLOW_GREEN.eval_continuous(1.0 - ratio_for_color);\n        let color = nu_ansi_term::Color::Rgb(color.r, color.g, color.b);\n        let label = format!(\n            \"{:7.3} {}s/i {}  \",\n            measure.as_secs_f64() * if use_micros { 1e6 } else { 1e3 },\n            if use_micros { \"µ\" } else { \"m\" },\n            color.bold().paint(format!(\"{:>4.1}%\", ratio * 100.0))\n        );\n        std::iter::once(label)\n    });\n\n    // cost column\n    let mut cost_column = if options.cost {\n        Some(\n            tags.cost\n                .iter()\n                .map(|c| {\n                    let key = format!(\"{:?}\", c.0);\n                    let value = render_tdim(&c.1);\n                    let value_visible_len = c.1.to_string().len();\n                    let padding = 24usize.saturating_sub(value_visible_len + key.len());\n                    key + &*std::iter::repeat_n(' ', padding).join(\"\") + &value.to_string() + \" \"\n                })\n                .peekable(),\n        )\n    } else {\n        None\n    };\n\n    // flops column\n    let mut flops_column = if options.profile && options.cost {\n        let timing: f64 = tags.profile.as_ref().map(|d| d.as_secs_f64()).unwrap_or(0.0);\n        let flops_column_pad = flops_column_pad.clone();\n        let it = tags.cost.iter().map(move |c| {\n            if c.0.is_compute() {\n                let flops = c.1.to_usize().unwrap_or(0) as f64 / timing;\n                let unpadded = si_prefix(flops, \"F/s\");\n                format!(\"{:>1$} \", unpadded, 19)\n            } else {\n                flops_column_pad.clone()\n            }\n        });\n        Some(it)\n    } else {\n        None\n    };\n\n    // tmp_mem_usage column\n    let mut tmp_mem_usage_column = if options.tmp_mem_usage {\n        let it = tags.tmp_mem_usage.iter().map(move |mem| {\n            let unpadded = if let Ok(mem_size) = mem.to_usize() {\n                pow2_prefix(mem_size, \"B\")\n            } else {\n                format!(\"{mem:.3} B\")\n            };\n            format!(\"{:>1$} \", unpadded, mem_padding - 1)\n        });\n        Some(it)\n    } else {\n        None\n    };\n\n    // drawing column\n    let mut drawing_lines: Box<dyn Iterator<Item = String>> =\n        if let Some(ds) = drawing_state.as_mut() {\n            let body = ds.draw_node_body(model, node_id, options)?;\n            let suffix = ds.draw_node_vsuffix(model, node_id, options)?;\n            let filler = ds.draw_node_vfiller(model, node_id)?;\n            Box::new(body.into_iter().chain(suffix).chain(std::iter::repeat(filler)))\n        } else {\n            Box::new(std::iter::repeat(cost_column_pad.clone()))\n        };\n\n    macro_rules! prefix {\n        () => {\n            let cost = cost_column\n                .as_mut()\n                .map(|it| it.next().unwrap_or_else(|| cost_column_pad.to_string()))\n                .unwrap_or(\"\".to_string());\n            let profile = profile_column\n                .as_mut()\n                .map(|it| it.next().unwrap_or_else(|| profile_column_pad.to_string()))\n                .unwrap_or(\"\".to_string());\n            let flops = flops_column\n                .as_mut()\n                .map(|it| it.next().unwrap_or_else(|| flops_column_pad.to_string()))\n                .unwrap_or(\"\".to_string());\n            let tmp_mem_usage = tmp_mem_usage_column\n                .as_mut()\n                .map(|it| it.next().unwrap_or_else(|| tmp_mem_usage_column_pad.to_string()))\n                .unwrap_or(\"\".to_string());\n            print!(\n                \"{}{}{}{}{}{} \",\n                profile,\n                cost,\n                flops,\n                tmp_mem_usage,\n                prefix,\n                drawing_lines.next().unwrap(),\n            )\n        };\n    }\n\n    let have_accel_profiling =\n        annotations.tags.iter().any(|(_, tag)| tag.accelerator_profile.is_some());\n    let is_cpu_fallback = have_accel_profiling\n        && tags.accelerator_profile.unwrap_or_default() == Duration::default()\n        && tags.profile.unwrap_or_default() > Duration::default();\n    let op_color = if node_name == \"UnimplementedOp\" {\n        Red.bold()\n    } else if is_cpu_fallback {\n        Yellow.bold()\n    } else {\n        Blue.bold()\n    };\n\n    prefix!();\n    println!(\n        \"{} {} {}\",\n        White.bold().paint(format!(\"{node_id}\")),\n        op_color.paint(node_op_name),\n        name_color.italic().paint(node_name)\n    );\n    for label in tags.labels.iter() {\n        prefix!();\n        println!(\"  * {label}\");\n    }\n    if let Io::Long = options.io {\n        for (ix, i) in model.node_inputs(node_id).iter().enumerate() {\n            let star = if ix == 0 { '*' } else { ' ' };\n            prefix!();\n            println!(\n                \"  {} input fact  #{}: {} {}\",\n                star,\n                ix,\n                White.bold().paint(format!(\"{i:?}\")),\n                model.outlet_fact_format(*i),\n            );\n        }\n        for slot in 0..model.node_output_count(node_id) {\n            let star = if slot == 0 { '*' } else { ' ' };\n            let outlet = OutletId::new(node_id, slot);\n            let mut model_io = vec![];\n            for (ix, _) in model.input_outlets().iter().enumerate().filter(|(_, o)| **o == outlet) {\n                model_io.push(Cyan.bold().paint(format!(\"MODEL INPUT #{ix}\")).to_string());\n            }\n            if let Some(t) = &tags.model_input {\n                model_io.push(t.to_string());\n            }\n            for (ix, _) in model.output_outlets().iter().enumerate().filter(|(_, o)| **o == outlet)\n            {\n                model_io.push(Yellow.bold().paint(format!(\"MODEL OUTPUT #{ix}\")).to_string());\n            }\n            if let Some(t) = &tags.model_output {\n                model_io.push(t.to_string());\n            }\n            let successors = model.outlet_successors(outlet);\n            prefix!();\n            let mut axes =\n                tags.outlet_axes.get(slot).map(|s| s.join(\",\")).unwrap_or_else(|| \"\".to_string());\n            if !axes.is_empty() {\n                axes.push(' ')\n            }\n            println!(\n                \"  {} output fact #{}: {}{} {} {} {}\",\n                star,\n                slot,\n                Green.bold().italic().paint(axes),\n                model.outlet_fact_format(outlet),\n                White.bold().paint(successors.iter().map(|s| format!(\"{s:?}\")).join(\" \")),\n                model_io.join(\", \"),\n                Blue.bold().italic().paint(\n                    tags.outlet_labels\n                        .get(slot)\n                        .map(|s| s.join(\",\"))\n                        .unwrap_or_else(|| \"\".to_string())\n                )\n            );\n            if options.outlet_labels {\n                if let Some(label) = model.outlet_label(OutletId::new(node_id, slot)) {\n                    prefix!();\n                    println!(\"            {} \", White.italic().paint(label));\n                }\n            }\n        }\n    }\n    if options.info {\n        for info in model.node_op(node_id).info()? {\n            prefix!();\n            println!(\"  * {info}\");\n        }\n    }\n    if options.invariants {\n        if let Some(typed) = model.downcast_ref::<TypedModel>() {\n            let node = typed.node(node_id);\n            let (inputs, outputs) = typed.node_facts(node.id)?;\n            let axes_mapping = node.op().as_typed().unwrap().axes_mapping(&inputs, &outputs)?;\n            prefix!();\n            println!(\"  * {axes_mapping}\");\n        }\n    }\n    if options.debug_op {\n        prefix!();\n        println!(\"  * {:?}\", model.node_op(node_id));\n    }\n    for section in tags.sections {\n        if section.is_empty() {\n            continue;\n        }\n        prefix!();\n        println!(\"  * {}\", section[0]);\n        for s in &section[1..] {\n            prefix!();\n            println!(\"    {s}\");\n        }\n    }\n\n    if !options.folded {\n        for (label, sub) in model.nested_models(node_id) {\n            let prefix = drawing_lines.next().unwrap();\n            let mut scope: TVec<_> = scope.into();\n            scope.push((node_id, label));\n            let scope_prefix = scope.iter().map(|(_, p)| p).join(\"|\");\n            render_prefixed(\n                sub,\n                &format!(\"{prefix} [{scope_prefix}] \"),\n                &scope,\n                annotations,\n                options,\n            )?\n        }\n    }\n    if let Io::Short = options.io {\n        let same = !model.node_inputs(node_id).is_empty()\n            && model.node_output_count(node_id) == 1\n            && model.outlet_fact_format(node_id.into())\n                == model.outlet_fact_format(model.node_inputs(node_id)[0]);\n        if !same || model.output_outlets().iter().any(|o| o.node == node_id) {\n            let style = drawing_state.map(|s| s.last_wire_color()).unwrap_or_else(|| White.into());\n            for ix in 0..model.node_output_count(node_id) {\n                prefix!();\n                println!(\n                    \"  {}{}{} {}\",\n                    style.paint(box_drawing::heavy::HORIZONTAL),\n                    style.paint(box_drawing::heavy::HORIZONTAL),\n                    style.paint(box_drawing::heavy::HORIZONTAL),\n                    model.outlet_fact_format((node_id, ix).into())\n                );\n            }\n        }\n    }\n\n    while cost_column.as_mut().map(|cost| cost.peek().is_some()).unwrap_or(false) {\n        prefix!();\n        println!();\n    }\n\n    Ok(())\n}\n\npub fn render_summaries(\n    model: &dyn Model,\n    annotations: &Annotations,\n    options: &DisplayParams,\n) -> TractResult<()> {\n    let total = annotations.tags.values().sum::<NodeTags>();\n\n    if options.tmp_mem_usage {\n        if let Some(summary) = &annotations.memory_summary {\n            println!(\"{}\", White.bold().paint(\"Memory summary\"));\n            println!(\" * Peak flushable memory: {}\", pow2_prefix(summary.max, \"B\"));\n        }\n    }\n    if options.cost {\n        println!(\"{}\", White.bold().paint(\"Cost summary\"));\n        for (c, i) in &total.cost {\n            println!(\" * {:?}: {}\", c, render_tdim(i));\n        }\n    }\n\n    if options.profile {\n        let summary = annotations.profile_summary.as_ref().unwrap();\n\n        let have_accel_profiling =\n            annotations.tags.iter().any(|(_, tag)| tag.accelerator_profile.is_some());\n        println!(\n            \"{}{}{}\",\n            White.bold().paint(format!(\"{:<43}\", \"Most time consuming operations\")),\n            White.bold().paint(format!(\"{:<17}\", \"CPU\")),\n            White.bold().paint(if have_accel_profiling { \"Accelerator\" } else { \"\" }),\n        );\n\n        for (op, (cpu_dur, accel_dur, n)) in annotations\n            .tags\n            .iter()\n            .map(|(k, v)| {\n                (\n                    k.model(model).unwrap().node_op_name(k.1),\n                    (v.profile.unwrap_or_default(), v.accelerator_profile.unwrap_or_default()),\n                )\n            })\n            .sorted_by_key(|a| a.0.to_string())\n            .chunk_by(|(n, _)| n.clone())\n            .into_iter()\n            .map(|(a, group)| {\n                (\n                    a,\n                    group.into_iter().fold(\n                        (Duration::default(), Duration::default(), 0),\n                        |(accu, accel_accu, n), d| (accu + d.1.0, accel_accu + d.1.1, n + 1),\n                    ),\n                )\n            })\n            .sorted_by_key(|(_, d)| if have_accel_profiling { d.1 } else { d.0 })\n            .rev()\n        {\n            let is_cpu_fallback = have_accel_profiling\n                && accel_dur == Duration::default()\n                && cpu_dur > Duration::default();\n            let op_color = if is_cpu_fallback { Yellow.bold() } else { Blue.bold() };\n            println!(\n                \" * {} {:3} nodes: {}  {}\",\n                op_color.paint(format!(\"{op:22}\")),\n                n,\n                dur_avg_ratio(cpu_dur, summary.sum),\n                if have_accel_profiling {\n                    dur_avg_ratio(accel_dur, summary.accel_sum)\n                } else {\n                    \"\".to_string()\n                }\n            );\n        }\n\n        println!(\"{}\", White.bold().paint(\"By prefix\"));\n        fn prefixes_for(s: &str) -> impl Iterator<Item = String> + '_ {\n            use tract_itertools::*;\n            let split = s.split('.').count();\n            (0..split).map(move |n| s.split('.').take(n).join(\".\"))\n        }\n        let all_prefixes = annotations\n            .tags\n            .keys()\n            .flat_map(|id| prefixes_for(id.model(model).unwrap().node_name(id.1)))\n            .filter(|s| !s.is_empty())\n            .sorted()\n            .unique()\n            .collect::<Vec<String>>();\n\n        for prefix in &all_prefixes {\n            let sum = annotations\n                .tags\n                .iter()\n                .filter(|(k, _v)| k.model(model).unwrap().node_name(k.1).starts_with(prefix))\n                .map(|(_k, v)| v)\n                .sum::<NodeTags>();\n\n            let profiler =\n                if !have_accel_profiling { sum.profile } else { sum.accelerator_profile };\n            if profiler.unwrap_or_default().as_secs_f64() / summary.entire.as_secs_f64() < 0.01 {\n                continue;\n            }\n            print!(\"{}    \", dur_avg_ratio(profiler.unwrap_or_default(), summary.sum));\n\n            for _ in prefix.chars().filter(|c| *c == '.') {\n                print!(\"   \");\n            }\n            println!(\"{prefix}\");\n        }\n\n        println!(\n            \"Not accounted by ops: {}\",\n            dur_avg_ratio(summary.entire - summary.sum.min(summary.entire), summary.entire)\n        );\n\n        if have_accel_profiling {\n            println!(\n                \"(Total CPU Op time - Total Accelerator Op time): {}\",\n                dur_avg_ratio(summary.sum - summary.accel_sum.min(summary.sum), summary.entire)\n            );\n        }\n        println!(\"Entire network performance: {}\", dur_avg(summary.entire));\n    }\n\n    Ok(())\n}\n\npub fn render_summary(model: &dyn Model, annotations: &Annotations) -> TractResult<()> {\n    if !model.properties().is_empty() {\n        println!(\"{}\", White.bold().paint(\"# Properties\"));\n        for (k, v) in model.properties().iter().sorted_by_key(|(k, _)| k.to_string()) {\n            println!(\"* {}: {:?}\", White.paint(k), v);\n        }\n    }\n    println!(\"{}\", White.bold().paint(\"# Inputs\"));\n    for (ix, input) in model.input_outlets().iter().enumerate() {\n        let name = model.node_name(input.node);\n        let fact = model.outlet_typedfact(*input)?;\n        let symbol = crate::draw::circled_input(ix);\n        println!(\"  {symbol} {name}: {fact:?}\");\n    }\n    println!(\"{}\", White.bold().paint(\"# Outputs\"));\n    for (ix, output) in model.output_outlets().iter().enumerate() {\n        let name = model.node_name(output.node);\n        let fact = model.outlet_typedfact(*output)?;\n        let symbol = crate::draw::circled_output(ix);\n        println!(\"  {symbol} {name}: {fact:?}\");\n    }\n    let mut op_counts: HashMap<StaticName, usize> = HashMap::default();\n    let mut op_costs: HashMap<StaticName, Vec<(Cost, TDim)>> = HashMap::default();\n    for id in 0..model.nodes_len() {\n        let op_name = model.node_op_name(id);\n        *op_counts.entry(op_name.clone()).or_default() += 1;\n        if let Some(tags) = annotations.tags.get(&NodeQId(tvec!(), id)) {\n            let costs = op_costs.entry(op_name).or_default();\n            for (cost_kind, value) in &tags.cost {\n                if let Some(existing) = costs.iter_mut().find(|(k, _)| k == cost_kind) {\n                    existing.1 = existing.1.clone() + value;\n                } else {\n                    costs.push((cost_kind.clone(), value.clone()));\n                }\n            }\n        }\n    }\n    let total = annotations.tags.values().sum::<NodeTags>();\n    let total_cost_str = total\n        .cost\n        .iter()\n        .filter(|(k, _)| k.is_compute())\n        .map(|(kind, val)| format!(\"{kind:?}: {}\", render_tdim(val)))\n        .join(\", \");\n    let all_costs_concrete = op_costs\n        .values()\n        .all(|costs| costs.iter().filter(|(k, _)| k.is_compute()).all(|(_, v)| v.to_i64().is_ok()));\n    let concrete_compute_cost = |op: &StaticName| -> i64 {\n        op_costs\n            .get(op)\n            .map(|costs| {\n                costs\n                    .iter()\n                    .filter(|(k, _)| k.is_compute())\n                    .filter_map(|(_, v)| v.to_i64().ok())\n                    .sum::<i64>()\n            })\n            .unwrap_or(0)\n    };\n    println!(\"{}\", White.bold().paint(\"# Operators\"));\n    for (op, count) in op_counts.iter().sorted_by(|a, b| {\n        if all_costs_concrete {\n            concrete_compute_cost(b.0)\n                .cmp(&concrete_compute_cost(a.0))\n                .then(b.1.cmp(a.1))\n                .then(a.0.cmp(b.0))\n        } else {\n            b.1.cmp(a.1).then(a.0.cmp(b.0))\n        }\n    }) {\n        let cost_str = op_costs\n            .get(op)\n            .map(|costs| {\n                costs.iter().map(|(kind, val)| format!(\"{kind:?}: {}\", render_tdim(val))).join(\", \")\n            })\n            .unwrap_or_default();\n        if cost_str.is_empty() {\n            println!(\"  {count:>5} {op}\");\n        } else {\n            println!(\"  {count:>5} {op}  [{cost_str}]\");\n        }\n    }\n    let total_nodes: usize = op_counts.values().sum();\n    if total_cost_str.is_empty() {\n        println!(\"  {total_nodes:>5} total\");\n    } else {\n        println!(\"  {total_nodes:>5} total  [{total_cost_str}]\");\n    }\n    Ok(())\n}\n\n/// Format a rusage::Duration showing avgtime in ms.\npub fn dur_avg(measure: Duration) -> String {\n    White.bold().paint(format!(\"{:.3} ms/i\", measure.as_secs_f64() * 1e3)).to_string()\n}\n\n/// Format a rusage::Duration showing avgtime in ms, with percentage to a global\n/// one.\npub fn dur_avg_ratio(measure: Duration, global: Duration) -> String {\n    format!(\n        \"{} {}\",\n        White.bold().paint(format!(\"{:7.3} ms/i\", measure.as_secs_f64() * 1e3)),\n        Yellow\n            .bold()\n            .paint(format!(\"{:>4.1}%\", measure.as_secs_f64() / global.as_secs_f64() * 100.)),\n    )\n}\n\nfn render_tdim(d: &TDim) -> AnsiString<'static> {\n    if let Ok(i) = d.to_i64() { render_big_integer(i) } else { d.to_string().into() }\n}\n\nfn render_big_integer(i: i64) -> nu_ansi_term::AnsiString<'static> {\n    let raw = i.to_string();\n    let mut blocks = raw\n        .chars()\n        .rev()\n        .chunks(3)\n        .into_iter()\n        .map(|mut c| c.join(\"\").chars().rev().join(\"\"))\n        .enumerate()\n        .map(|(ix, s)| if ix % 2 == 1 { White.bold().paint(s).to_string() } else { s })\n        .collect::<Vec<_>>();\n    blocks.reverse();\n    blocks.into_iter().join(\"\").into()\n}\n"
  },
  {
    "path": "libcli/src/time.rs",
    "content": "#![allow(dead_code)]\n\n#[cfg(not(target_arch = \"aarch64\"))]\npub use generic::*;\n\n#[cfg(target_arch = \"aarch64\")]\npub use aarch64::*;\n\nmod generic {\n    pub fn now() -> std::time::Instant {\n        std::time::Instant::now()\n    }\n}\n\n#[cfg(target_arch = \"aarch64\")]\nmod aarch64 {\n    use std::arch::asm;\n    use std::time::Duration;\n\n    pub struct Timestamp(u64);\n\n    impl Timestamp {\n        pub fn elapsed(&self) -> Duration {\n            let diff = timestamp().saturating_sub(self.0) as f64;\n            let secs = diff / frequency() as f64;\n            std::time::Duration::from_secs_f64(secs)\n        }\n    }\n\n    pub fn now() -> Timestamp {\n        Timestamp(timestamp())\n    }\n\n    #[inline]\n    fn frequency() -> u64 {\n        unsafe {\n            let frequency: u64;\n            asm!(\n                \"mrs {}, cntfrq_el0\",\n                out(reg) frequency,\n                options(nomem, nostack, preserves_flags, pure),\n            );\n            frequency\n        }\n    }\n\n    #[inline(always)]\n    fn timestamp() -> u64 {\n        unsafe {\n            let timestamp: u64;\n            asm!(\n                \"mrs {}, cntvct_el0\",\n                out(reg) timestamp,\n                // Leave off `nomem` because this should be a compiler fence.\n                options(nostack, preserves_flags),\n            );\n            timestamp\n        }\n    }\n}\n"
  },
  {
    "path": "libcli/validate_wires.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Validate wire drawing continuity in tract dump output.\n\nTop-down approach: track which wires are active at each visual column,\nupdate state as we encounter node lines, swap lines, and split lines.\nFlag any discontinuity we can't explain.\n\"\"\"\n\nimport sys\nimport re\n\ndef strip_ansi(s):\n    return re.sub(r'\\x1b\\[[0-9;]*m', '', s)\n\ndef parse_colored_chars(line):\n    \"\"\"Return list of (char, ansi_color_code) for each visible character.\"\"\"\n    result = []\n    current_color = \"\"\n    i = 0\n    while i < len(line):\n        if line[i] == '\\x1b':\n            j = line.index('m', i) + 1\n            seq = line[i:j]\n            if seq == '\\x1b[0m':\n                current_color = \"\"\n            else:\n                current_color = seq\n            i = j\n        else:\n            result.append((line[i], current_color))\n            i += 1\n    return result\n\n# Character classification\nCONNECTS_DOWN = set('┃┣┏╋┳┓')\nCONNECTS_UP   = set('┃┣╋┻┗╹')\nCONNECTS_RIGHT = set('┣┏┗━╋┳┻')\nCONNECTS_LEFT  = set('┓┛┗━╋┳┻')\nALL_WIRE = CONNECTS_DOWN | CONNECTS_UP | set('━')\n\ndef classify_line(wire_str):\n    \"\"\"Classify a wire-region string as: filler, node, swap, split, info, or unknown.\"\"\"\n    if not wire_str:\n        return 'empty'\n    chars = set(wire_str) - {' '}\n    if not chars:\n        return 'empty'\n    # Info line: has ━━━ after spaces (output shape)\n    if '━━━' in wire_str and ('┃' in wire_str or wire_str.strip().startswith('━')):\n        only_vert_and_horiz = chars <= {'┃', '━', ' '}\n        if only_vert_and_horiz:\n            return 'info'\n    # Filler: only vertical bars\n    if chars <= {'┃'}:\n        return 'filler'\n    # Swap: has ┗ and ┓ (wire moves right)\n    if '┗' in wire_str and '┓' in wire_str and '┣' not in wire_str:\n        return 'swap'\n    # Split: has ┣ and ┓ but no ┻ (wire clones right)\n    if '┣' in wire_str and '┓' in wire_str and '┻' not in wire_str:\n        return 'split'\n    # Node: has ┣ with possible ┻ (inputs merging)\n    if '┣' in wire_str and ('┻' in wire_str or '┓' in wire_str or wire_str.endswith('┣')):\n        return 'node'\n    # Source: starts with ┏\n    if '┏' in wire_str:\n        return 'node'\n    # Single ┣ at end (1 input, 1 output)\n    if wire_str.rstrip().endswith('┣'):\n        return 'node'\n    return 'unknown'\n\ndef extract_wire_region(raw_line):\n    \"\"\"Extract wire region from a line. Returns (offset, wire_string, colored_chars) or None.\"\"\"\n    stripped = strip_ansi(raw_line)\n    colored = parse_colored_chars(raw_line)\n\n    first = None\n    for i, c in enumerate(stripped):\n        if c in ALL_WIRE:\n            first = i\n            break\n    if first is None:\n        return None\n\n    # Find end of wire region (stop at non-wire, non-space char)\n    end = first\n    for i in range(first, len(stripped)):\n        if stripped[i] in ALL_WIRE:\n            end = i + 1\n        elif stripped[i] != ' ':\n            break\n\n    wire_str = stripped[first:end]\n    wire_colored = colored[first:end] if first < len(colored) else []\n    return (first, wire_str, wire_colored)\n\ndef find_node_id(stripped_line):\n    \"\"\"Try to extract node ID from a line like '... ┣┻ 123 OpName ...'\"\"\"\n    m = re.search(r'[┃┣┻┓┗┏╋┳╹]\\s+(\\d+)\\s+', stripped_line)\n    if m:\n        return int(m.group(1))\n    return None\n\ndef main():\n    raw_lines = [l.rstrip('\\n') for l in sys.stdin.readlines()]\n\n    errors = []\n    prev_wire = None\n    prev_colored = None\n    prev_lineno = 0\n    prev_kind = None\n    prev_node_id = None\n\n    for lineno_0, raw_line in enumerate(raw_lines):\n        lineno = lineno_0 + 1\n        stripped = strip_ansi(raw_line)\n        r = extract_wire_region(raw_line)\n        if r is None:\n            continue\n\n        offset, wire_str, wire_colored = r\n        kind = classify_line(wire_str)\n        node_id = find_node_id(stripped)\n\n        if prev_wire is not None:\n            prev_str = prev_wire\n            # Check top-down: each column in prev that connects down\n            # must have something valid below\n            max_col = max(len(prev_str), len(wire_str))\n            for col in range(max_col):\n                pc = prev_str[col] if col < len(prev_str) else ' '\n                cc = wire_str[col] if col < len(wire_str) else ' '\n\n                if pc in CONNECTS_DOWN and cc == ' ':\n                    # Wire going down disappears\n                    # Acceptable if: current line is a node/filler line (wire consumed or output terminated)\n                    if kind in ('node', 'filler', 'info'):\n                        continue\n                    errors.append((lineno, col, prev_lineno, prev_node_id, node_id,\n                        f\"col {col}: '{pc}' connects down into space\"))\n\n                elif pc in CONNECTS_DOWN and cc not in CONNECTS_UP and cc != '━' and cc not in CONNECTS_DOWN:\n                    # Connecting down into something that doesn't connect up\n                    # ┓ doesn't connect up — but it's OK in a swap/split context\n                    if cc == '┓' and kind in ('swap', 'split'):\n                        continue  # swap/split endpoint\n                    errors.append((lineno, col, prev_lineno, prev_node_id, node_id,\n                        f\"col {col}: '{pc}' down into '{cc}' ({kind} line)\"))\n\n                elif cc in CONNECTS_UP and pc == ' ':\n                    # Wire connecting up from nothing\n                    # Acceptable if: this is a node line (new const/hidden input)\n                    # or an info/filler line right after a source node\n                    if kind in ('node', 'info', 'filler'):\n                        continue\n                    errors.append((lineno, col, prev_lineno, prev_node_id, node_id,\n                        f\"col {col}: '{cc}' up from space ({kind} line)\"))\n\n                elif cc in CONNECTS_UP and pc not in CONNECTS_DOWN and pc != '━':\n                    # Connecting up from something that doesn't connect down\n                    if pc == '┗' and kind in ('node', 'filler'):\n                        continue  # ┗ was a swap, wire shifted\n                    if pc == '┻' and kind == 'node':\n                        continue  # stacked node inputs\n                    errors.append((lineno, col, prev_lineno, prev_node_id, node_id,\n                        f\"col {col}: '{cc}' up from '{pc}' ({kind} line, prev={prev_kind})\"))\n\n                # Color continuity: ┃ to ┃ must be same color (not at node junction)\n                if (pc == '┃' and cc == '┃'\n                        and prev_colored is not None\n                        and col < len(prev_colored) and col < len(wire_colored)):\n                    pc_color = prev_colored[col][1]\n                    cc_color = wire_colored[col][1]\n                    if pc_color and cc_color and pc_color != cc_color:\n                        errors.append((lineno, col, prev_lineno, prev_node_id, node_id,\n                            f\"col {col}: color change on ┃→┃ ({prev_kind}→{kind})\"))\n\n        prev_wire = wire_str\n        prev_colored = wire_colored\n        prev_lineno = lineno\n        prev_kind = kind\n        prev_node_id = node_id\n\n    if not errors:\n        print(\"All wire connections valid.\")\n        sys.exit(0)\n\n    # Group by line pair and show in context\n    shown = set()\n    for lineno, col, prev_ln, prev_nid, curr_nid, msg in errors:\n        key = (prev_ln, lineno)\n        if key in shown:\n            continue\n        shown.add(key)\n        errs_here = [(c, m) for l, c, pl, pn, cn, m in errors if pl == prev_ln and l == lineno]\n\n        node_info = \"\"\n        if prev_nid is not None:\n            node_info += f\" (after node {prev_nid})\"\n        if curr_nid is not None:\n            node_info += f\" (at node {curr_nid})\"\n\n        print(f\"\\n--- Lines {prev_ln}-{lineno}{node_info}: {len(errs_here)} error(s) ---\")\n        # Show context\n        prev_r = extract_wire_region(raw_lines[prev_ln - 1])\n        curr_r = extract_wire_region(raw_lines[lineno - 1])\n        if prev_r:\n            print(f\"  {prev_r[1]}  [{classify_line(prev_r[1])}]\")\n        if curr_r:\n            print(f\"  {curr_r[1]}  [{classify_line(curr_r[1])}]\")\n        if prev_r and curr_r:\n            marker = list(' ' * max(len(prev_r[1]), len(curr_r[1])))\n            for c, m in errs_here:\n                if c < len(marker):\n                    marker[c] = '^'\n            print(f\"  {''.join(marker)}\")\n        for c, m in errs_here:\n            print(f\"    {m}\")\n\n    total = len(errors)\n    print(f\"\\nTotal: {total} errors across {len(shown)} line pairs\")\n    sys.exit(1)\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "linalg/Cargo.toml",
    "content": "[package]\nname = \"tract-linalg\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [\"TensorFlow\", \"NeuralNetworks\"]\ncategories = [\"science\"]\nautobenches = false\nedition = \"2024\"\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nbyteorder.workspace = true\nderive-new.workspace = true\ndowncast-rs.workspace = true\ndyn-clone.workspace = true\ndyn-eq.workspace = true\ndyn-hash.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\nnum-traits.workspace = true\npastey.workspace = true\nrayon = { workspace = true, optional = true }\nscan_fmt.workspace = true\ntract-data.workspace = true\n\n[build-dependencies]\ncc.workspace = true\nhalf.workspace = true\nliquid.workspace = true\nliquid-core.workspace = true\nliquid-derive.workspace = true\nsmallvec.workspace = true\nunicode-normalization.workspace = true\ntime.workspace = true\nwalkdir.workspace = true\n\n[dev-dependencies]\nenv_logger.workspace = true\nlibc.workspace = true\nnu-ansi-term.workspace = true\ncore_affinity.workspace = true\n\n[target.'cfg(not(target_family = \"wasm\"))'.dev-dependencies]\ncriterion.workspace = true\nproptest.workspace = true\n\n[target.'cfg(target_family = \"wasm\")'.dev-dependencies]\n# Wasm doesn't support the `rayon` feature of criterion\ncriterion = { version = \"0.8\", default-features = false, features = [\"plotters\", \"cargo_bench_support\"] }\n# Wasm doesn't support the `fork` feature of proptest.\nproptest = { version = \"1.0.0\", default-features = false, features = [\"std\", \"bit-set\"] }\n\n[features]\n# This feature is meant to accomodate very restrictive / legacy toolchains that do\n# have support for fp16 instructions, breaking tract compilation.\n# It is not meant to be used in other situations, where run-time detection is\n# preferred.\nno_fp16 = []\napple-amx-ios = []\ndefault = [ ]\nmultithread-mm = [ \"rayon\" ]\ncomplex = [ \"tract-data/complex\" ]\nhwbench = [ \"rayon\" ]\n\n[[bench]]\nbench = false\nname = \"arm64\"\nharness = false\n\n[[bench]]\nname = \"mat_vec\"\nharness = false\n\n[[bench]]\nname = \"mm_for_wavenet_hw\"\nharness = false\n\n[[bench]]\nname = \"mm_for_inception\"\nharness = false\n\n[[bench]]\nname = \"mm_for_asr_am\"\nharness = false\n\n[[bench]]\nname = \"sigmoid\"\nharness = false\n\n[[bench]]\nname = \"softmax\"\nharness = false\n\n[[bench]]\nbench = false\nname = \"arm64simd\"\nharness = false\n\n[[bench]]\nbench = false\nname = \"arm32neon\"\nharness = false\n\n[[bench]]\nname = \"virtual_im2col\"\nharness = false\n\n[[bench]]\nbench = false\nname = \"x86_64\"\nharness = false\n\n[[bench]]\nbench = false\nname = \"intel\"\nharness = false\n\n[[bench]]\nbench = false\nname = \"leaky_relu\"\nharness = false\n"
  },
  {
    "path": "linalg/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "linalg/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "linalg/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "linalg/README.md",
    "content": "# tract-linalg\n\nlinalg stands for \"linear algebra\". This is a misnamer. This crates contains\nlow-level, architecture dependant optimisations used by tract-core.\n\n# Functions\n\n* MatMatMul: Extended matrix*matrix product:\n    * inspired by Gotoblass and BLIS micro kernel approach\n    * extended for convolution friendly addressing (fused img2col)\n    * fused output pipeline (min, max, and a few more simple, fast ops)\n    * f32*f32 -> f32 (à la sgemm)\n    * i8*i8 -> i32 accumulator -> i32 storage\n    * i8*i8 -> i32 accumulator -> i8 (with channel zeropoint and scale, and re-quantization pipeline)\n* f32 sigmoid and f32 tanh: at f32 precision, by a rationale function (no exponentiation)\n* byte-to-byte lookup table\n\n# Implementations\n\n|                   |  generic fallback  |   armv6, vfp  |     armv7 neon    |    armv8 simd     |     x64 FMA\n|-------------------|--------------------|---------------|-------------------|-------------------|-----------------\n| MatMatMul f32     |                    |      4x4      |         8x4       |       8x8         |       16x6\n| MatMatMul i8->i8  |                    |               |         8x4       |                   |        8x8\n| MatMatMul i8->i32 |                    |               |                   |                   |        8x8\n| sigmoid f32       |                    |               |         4n        |        4n         |\n| tanh f32          |                    |               |         4n        |        4n         |\n| byte lookup       |                    |               |                   |                   |\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_f32_32x1_core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs\n//\n//      q8[0]\n//      q8[1]\n//      q8[2]\n//      q8[3]\n//\n//      ....\n//\n//      q15[0]\n//      q15[1]\n//      q15[2]\n//      q15[3]\n\n    .arm\n    .text\n    .global armv7neon_mmm_f32_32x1_{{core}}_{{suffix}}\n    .type armv7neon_mmm_f32_32x1_{{core}}_{{suffix}}, %function\n\narmv7neon_mmm_f32_32x1_{{core}}_{{suffix}}:\n\n    pld     [r0]\n    push    { r4-r12 }\n    vpush   { q4-q7 }\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n\n    cmp     r3, #0\n    beq     .non_linear_loop\n\n    mov     r1, r4 // packed A ptr\n    pld     [r3]\n    pld     [r5]\n\n    pld     [r1, #128]\n    pld     [r1, #192]\n    pld     [r1, #256]\n    pld     [r1, #320]\n    pld     [r1, #384]\n    pld     [r1, #448]\n    pld     [r1, #512]\n\n.packed_packed_loop_1:\n    pld     [r5]                           // packed B ptr\n\n{% if core == \"cortexa7\" %}\n\n    vldr            d0, [r1]\n    vldr            d1, [r1, #8]\n    vldr            d2, [r1, #16]\n    vldr            d3, [r1, #24]\n    vldr            d4, [r1, #32]\n    vldr            d5, [r1, #40]\n    vldr            d6, [r1, #48]\n    vldr            d7, [r1, #56]\n    vldr            d8, [r1, #64]\n    vldr            d9, [r1, #72]\n    vldr            d10, [r1, #80]\n    vldr            d11, [r1, #88]\n    vldr            s30, [r5]\n\n    pld             [r1, #512]\n    pld             [r1, #576]\n    pld             [r5, #64]\n\n    vmla.f32        q8, q0, d15[0]\n    vmla.f32        q9, q1, d15[0]\n\n    vldr            d0, [r1, #96]\n    vldr            d1, [r1, #104]\n    vldr            d2, [r1, #112]\n    vldr            d3, [r1, #120]\n\n    vmla.f32        q10, q2, d15[0]\n    vmla.f32        q11, q3, d15[0]\n\n    vmla.f32        q12, q4, d15[0]\n    vmla.f32        q13, q5, d15[0]\n\n    vmla.f32        q14, q0, d15[0]\n    vmla.f32        q15, q1, d15[0]\n\n    add             r1, #128\n    add             r5, #4\n\n{% elsif core == \"cortexa9\" %}\n\n    vld1.64         {d0-d3}, [r1]!\n    vld1.64         {d4-d7}, [r1]!\n    pld             [r1, #512]\n    pld             [r1, #576]\n    vld1.64         {d8-d11}, [r1]!\n    vld1.f32        d15[0], [r5]!\n    pld             [r5, #64]\n\n    vmla.f32        q8, q0, d15[0]\n    vmla.f32        q9, q1, d15[0]\n    vld1.64         {d0-d3}, [r1]!\n\n    vmla.f32        q10, q2, d15[0]\n    vmla.f32        q11, q3, d15[0]\n\n    vmla.f32        q12, q4, d15[0]\n    vmla.f32        q13, q5, d15[0]\n\n    vmla.f32        q14, q0, d15[0]\n    vmla.f32        q15, q1, d15[0]\n\n{% else %}\n\n    vldmia          r1!, { q0-q3 }\n    vldmia          r5!, { s30 }\n\n    vmla.f32        q8, q0, d15[0]\n    vmla.f32        q9, q1, d15[0]\n    vldmia          r1!, { q0-q1 }\n\n    vmla.f32        q10, q2, d15[0]\n    vmla.f32        q11, q3, d15[0]\n    vldmia          r1!, { q2-q3 }\n\n    vmla.f32        q12, q0, d15[0]\n    vmla.f32        q13, q1, d15[0]\n\n    vmla.f32        q14, q2, d15[0]\n    vmla.f32        q15, q3, d15[0]\n\n{% endif %}\n\n    subs            r3, r3, #1\n    bne .packed_packed_loop_1\n\n    b   .non_linear_loop\n\n{% include \"armv7neon_mmm_f32_scalars.tmpliq\" from:8, to:15 %}\n{% include \"armv7neon_mmm_f32_per_rows.tmpliq\" mr:32, from:8, to:15 %}\n{% include \"armv7neon_mmm_f32_per_cols.tmpliq\" mr:32, from:8, to:15 %}\n\n.add_unicast:\n    {% for reg in (0..15) %}\n        vld1.f32    d{{reg}}[0], [ r3 ], r4\n        vld1.f32    d{{reg}}[1], [ r3 ], r4\n    {% endfor %}\n    {% for reg in (0..7) %}\n        vadd.f32 q{{reg|plus:8}}, q{{reg|plus:8}}, q{{reg}}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.add_row_col_products:\n    vld1.f32        d0[0], [ r4 ]\n    vldmia          r3!, { q4-q7 }\n\n    vmla.f32        q8, q4, d0[0]\n    vmla.f32        q9, q5, d0[0]\n\n    vmla.f32        q10, q6, d0[0]\n    vmla.f32        q11, q7, d0[0]\n\n    vldmia          r3!, { q4-q7 }\n\n    vmla.f32        q12, q4, d0[0]\n    vmla.f32        q13, q5, d0[0]\n\n    vmla.f32        q14, q6, d0[0]\n    vmla.f32        q15, q7, d0[0]\n\n    b .non_linear_loop\n\n.store:\n    // r3, r4 <- ptr, rsc\n    cmp     r4, #4\n    bne     .store_generic\n\n    vst1.f64    {d16-d19}, [r3]!\n    vst1.f64    {d20-d23}, [r3]!\n    vst1.f64    {d24-d27}, [r3]!\n    vst1.f64    {d28-d31}, [r3]!\n\n    b .non_linear_loop\n\n.store_generic:\n\n    {% for reg in (16..31) %}\n        vst1.f32    d{{reg}}[0], [r3], r4\n        vst1.f32    d{{reg}}[1], [r3], r4\n    {% endfor %}\n\n    b .non_linear_loop\n\n.load_tile:\n    vldmia          r3!, { q8-q15 }\n    b .non_linear_loop\n\n.return:\n    vpop        { q4-q7 }\n    pop         { r4-r12 }\n\n    bx          lr\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_f32_8x1_core.tmpl",
    "content": "// vim: ft=arm\n\n    .arm\n    .text\n    .global armv7neon_mmm_f32_8x1_{{core}}_{{suffix}}\n    .type armv7neon_mmm_f32_8x1_{{core}}_{{suffix}}, %function\n\narmv7neon_mmm_f32_8x1_{{core}}_{{suffix}}:\n\n    pld     [r0]\n    push    { r4-r12 }\n    vpush   { q4-q7 }\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n\n    cmp     r3, #0\n    beq     .non_linear_loop\n\n    mov     r1, r4 // packed A ptr\n    pld     [r3]\n    pld     [r5]\n\n    pld     [r1, #128]\n    pld     [r1, #192]\n    pld     [r1, #256]\n    pld     [r1, #320]\n    pld     [r1, #384]\n    pld     [r1, #448]\n    pld     [r1, #512]\n\n.packed_packed_loop_1:\n    pld     [r5]                           // packed B ptr\n\n    vldmia          r1!, { q0-q1 }\n    vldmia          r5!, { s30 }\n\n    vmla.f32        q8, q0, d15[0]\n    vmla.f32        q9, q1, d15[0]\n\n    subs            r3, r3, #1\n    bne .packed_packed_loop_1\n\n    b   .non_linear_loop\n\n{% include \"armv7neon_mmm_f32_scalars.tmpliq\" from:8, to:9 %}\n{% include \"armv7neon_mmm_f32_per_rows.tmpliq\" mr:8, from:8, to:9 %}\n{% include \"armv7neon_mmm_f32_per_cols.tmpliq\" mr:8, from:8, to:9 %}\n\n.add_unicast:\n    {% for reg in (0..15) %}\n        vld1.f32    d{{reg}}[0], [ r3 ], r4\n        vld1.f32    d{{reg}}[1], [ r3 ], r4\n    {% endfor %}\n    {% for reg in (0..7) %}\n        vadd.f32 q{{reg|plus:8}}, q{{reg|plus:8}}, q{{reg}}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.add_row_col_products:\n    vld1.f32        d0[0], [ r4 ]\n    vldmia          r3!, { q4-q5 }\n\n    vmla.f32        q8, q4, d0[0]\n    vmla.f32        q9, q5, d0[0]\n\n    b .non_linear_loop\n\n.store:\n    // r3, r4 <- ptr, rsc\n    cmp     r4, #4\n    bne     .store_generic\n\n    vst1.f64    {d16-d19}, [r3]!\n\n    b .non_linear_loop\n\n.store_generic:\n\n    {% for reg in (16..19) %}\n        vst1.f32    d{{reg}}[0], [r3], r4\n        vst1.f32    d{{reg}}[1], [r3], r4\n    {% endfor %}\n\n    b .non_linear_loop\n\n.load_tile:\n    vldmia          r3!, { q8-q15 }\n    b .non_linear_loop\n\n.return:\n    vpop        { q4-q7 }\n    pop         { r4-r12 }\n\n    bx          lr\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_f32_8x4_core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs\n//\n//      q8[0]    q10[0]   q12[0]    q14[0]\n//      q8[1]    q10[1]   q12[1]    q14[1]\n//      q8[2]    q10[2]   q12[2]    q14[2]\n//      q8[3]    q10[3]   q12[3]    q14[3]\n//\n//      q9[0]    q11[0]   q13[0]    q15[0]\n//      q9[1]    q11[1]   q13[1]    q15[1]\n//      q9[2]    q11[2]   q13[2]    q15[2]\n//      q9[3]    q11[3]   q13[3]    q15[3]\n\n// packed A buffering (2x8 values): alternating q0, q1 with q2, q3\n// packed B buffering (2x4 values): alternating q4 with q5\n\n    .arm\n    .text\n    .global armv7neon_mmm_f32_8x4_{{core}}_{{suffix}}\n    .type armv7neon_mmm_f32_8x4_{{core}}_{{suffix}}, %function\n\narmv7neon_mmm_f32_8x4_{{core}}_{{suffix}}:\n    pld     [r0]\n    push    { r4-r12 }\n    vpush   { q4-q7 }\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n\n    cmp     r3, #0\n    beq     .non_linear_loop\n\n    mov     r1, r4 // packed A ptr\n    pld     [r3]\n    pld     [r5]\n\n    .packed_packed:\n    pld     [r5]                           // packed B ptr\n    .packed_packed_loop_1:\n\n{% if core == \"cortexa7\" %}\n    vldr            d0, [r1]\n    vldr            d1, [r1, #8]\n    vldr            d2, [r1, #16]\n    vldr            d3, [r1, #24]\n    vldr            d4, [r5]\n    vldr            d5, [r5, #8]\n{% elsif core == \"cortexa9\" %}\n    vld1.64         {d0-d3}, [r1]!\n    vld1.64         {d4, d5}, [r5]!\n{% else %}\n    vldmia          r1!, { q0, q1}\n    vldmia          r5!, { q2 }\n{% endif %}\n\n{% if core != \"generic\" %}\n    pld             [r1, #512]\n    pld             [r5, #512]\n{% endif %}\n\n    vmla.f32        q8, q0, d4[0]\n    vmla.f32        q9, q1, d4[0]\n\n    vmla.f32        q10, q0, d4[1]\n    vmla.f32        q11, q1, d4[1]\n\n    vmla.f32        q12, q0, d5[0]\n    vmla.f32        q13, q1, d5[0]\n\n    vmla.f32        q14, q0, d5[1]\n    vmla.f32        q15, q1, d5[1]\n\n{% if core == \"cortexa7\" %}\n    add             r1, #32\n    add             r5, #16\n{% endif %}\n\n    subs r3, r3, #1\n    bne .packed_packed_loop_1\n    b   .non_linear_loop\n\n{% include \"armv7neon_mmm_f32_scalars.tmpliq\" from:8, to:15 %}\n{% include \"armv7neon_mmm_f32_per_rows.tmpliq\" mr:8, from:8, to:15 %}\n{% include \"armv7neon_mmm_f32_per_cols.tmpliq\" mr:8, from:8, to:15 %}\n\n.add_unicast:\n    // r3, r4, r5 <- ptr, rsc, csc\n    {% for col in (0..3) %}\n        mov         r2, r3\n        {% for reg in (0..3) %}\n            vld1.f32    d0[0], [ r2 ], r4\n            vld1.f32    d0[1], [ r2 ], r4\n            vadd.f32    d{{col | times: 4 | plus: reg | plus : 16}}, d0\n        {% endfor %}\n        add r3, r3, r5\n    {% endfor %}\n\n    b .non_linear_loop\n\n.add_row_col_products:\n    vldmia          r3!, { q0, q1 }\n    vldmia          r4!, { q4 }\n\n    vmla.f32        q8, q0, d8[0]\n    vmla.f32        q9, q1, d8[0]\n\n    vmla.f32        q10, q0, d8[1]\n    vmla.f32        q11, q1, d8[1]\n\n    vmla.f32        q12, q0, d9[0]\n    vmla.f32        q13, q1, d9[0]\n\n    vmla.f32        q14, q0, d9[1]\n    vmla.f32        q15, q1, d9[1]\n\n    b .non_linear_loop\n\n.store:\n    // r3,r4,r5 are c,rsc,csc\n    {% for col in (0..3) %}\n        mov         r8, r3\n        {% for reg in (0..3) %}\n            vst1.f32    d{{col | times: 4 | plus: reg | plus : 16}}[0], [ r8 ], r4\n            vst1.f32    d{{col | times: 4 | plus: reg | plus : 16}}[1], [ r8 ], r4\n        {% endfor %}\n        {% if col < 3 %}\n            add r3, r3, r5\n        {% endif %}\n    {% endfor %}\n    b .non_linear_loop\n\n.load_tile:\n    vldmia          r3!, { q8-q15 }\n    b .non_linear_loop\n\n.return:\n    vpop        { q4-q7 }\n    pop         { r4-r12 }\n\n    bx          lr\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_f32_8x6_core.tmpl",
    "content": "// vim: ft=arm\n\n    .arm\n    .text\n    .global armv7neon_mmm_f32_8x6_{{core}}_{{suffix}}\n    .type armv7neon_mmm_f32_8x6_{{core}}_{{suffix}}, %function\n\narmv7neon_mmm_f32_8x6_{{core}}_{{suffix}}:\n\n    pld     [r0]\n    push    { r4-r12 }\n    vpush   { q4-q7 }\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    cmp     r3, #0\n    beq     .non_linear_loop\n\n    mov     r1, r4 // packed A ptr\n    pld     [r3]\n    pld     [r5]\n\n    .packed_packed_loop_1:\n\n{% if core == \"cortexa7\" %}\n    vldr            d0, [r1]\n    vldr            d1, [r1, #8]\n    vldr            d2, [r1, #16]\n    vldr            d3, [r1, #24]\n    vldr            d4, [r5]\n    vldr            d5, [r5, #8]\n    vldr            d6, [r5, #16]\n{% elsif core == \"cortexa9\" %}\n    vld1.64         {d0-d3}, [r1]!\n    vld1.64         {d4, d5, d6}, [r5]!\n{% else %}\n    vldmia          r1!, {q0-q1}\n    vldmia          r5!, {d4-d6}\n{% endif %}\n\n{% if core != \"generic\" %}\n    pld             [r1, #512]\n    pld             [r5, #512]\n{% endif %}\n\n    vmla.f32        q4, q0, d4[0]\n    vmla.f32        q5, q1, d4[0]\n\n    vmla.f32        q6, q0, d4[1]\n    vmla.f32        q7, q1, d4[1]\n\n    vmla.f32        q8, q0, d5[0]\n    vmla.f32        q9, q1, d5[0]\n\n    vmla.f32        q10, q0, d5[1]\n    vmla.f32        q11, q1, d5[1]\n\n    vmla.f32        q12, q0, d6[0]\n    vmla.f32        q13, q1, d6[0]\n\n    vmla.f32        q14, q0, d6[1]\n    vmla.f32        q15, q1, d6[1]\n\n{% if core == \"cortexa7\" %}\n    add             r1, #32\n    add             r5, #24\n{% endif %}\n\n    subs r3, r3, #1\n    bne .packed_packed_loop_1\n    b   .non_linear_loop\n\n{% include \"armv7neon_mmm_f32_scalars.tmpliq\" from:4, to:15 %}\n{% include \"armv7neon_mmm_f32_per_rows.tmpliq\" mr:8, from:4, to:15 %}\n{% include \"armv7neon_mmm_f32_per_cols.tmpliq\" mr:8, from:4, to:15 %}\n\n.add_unicast:\n    //  r3, r4, r5, r6 <- ptr, rsc, csc, size\n    {% for col in (0..5) %}\n        mov         r2, r3\n        {% for reg in (0..3) %}\n            vld1.f32    d0[0], [ r2 ], r4\n            vld1.f32    d0[1], [ r2 ], r4\n            vadd.f32    d{{col | times: 4 | plus: reg | plus : 8}}, d0\n        {% endfor %}\n        add r3, r3, r5\n    {% endfor %}\n\n    b .non_linear_loop\n\n.add_row_col_products:\n    vldmia          r3!, { q0, q1 }\n    vldmia          r4!, { d4, d5, d6 }\n\n    vmla.f32        q4, q0, d4[0]\n    vmla.f32        q5, q1, d4[0]\n\n    vmla.f32        q6, q0, d4[1]\n    vmla.f32        q7, q1, d4[1]\n\n    vmla.f32        q8, q0, d5[0]\n    vmla.f32        q9, q1, d5[0]\n\n    vmla.f32        q10, q0, d5[1]\n    vmla.f32        q11, q1, d5[1]\n\n    vmla.f32        q12, q0, d6[0]\n    vmla.f32        q13, q1, d6[0]\n\n    vmla.f32        q14, q0, d6[1]\n    vmla.f32        q15, q1, d6[1]\n\n    b .non_linear_loop\n\n.store:\n    // r3, r4, r5 <- ptr, rsc, csc\n\n    cmp     r4, #4\n    bne     .store_generic\n\n    {% for col in (0..5) %}\n        mov         r8, r3\n        {% for reg in (0..3) %}\n            vst1.64     d{{col| times: 4 | plus: 8 | plus: reg}}, [ r8 ]!\n        {% endfor %}\n        {% if col < 5 %}\n            add r3, r3, r5\n        {% endif %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.store_generic:\n    {% for col in (0..5) %}\n        mov         r8, r3\n        {% for reg in (0..3) %}\n            vst1.f32    d{{col | times: 4 | plus: reg | plus : 8}}[0], [ r8 ], r4\n            vst1.f32    d{{col | times: 4 | plus: reg | plus : 8}}[1], [ r8 ], r4\n        {% endfor %}\n        {% if col < 5 %}\n            add r3, r3, r5\n        {% endif %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.load_tile:\n    vldmia          r3!, { q4-q7 }\n    vldmia          r3!, { q8-q15 }\n    b .non_linear_loop\n\n.return:\n    vpop        { q4-q7 }\n    pop         { r4-r12 }\n\n    bx          lr\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_f32_per_cols.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_min\", op:\"vmin.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_max\", op:\"vmax.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_mul\", op:\"vmul.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_add\", op:\"vadd.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_sub\", op:\"vsub.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_sub_flipped\", op:\"vsub.f32\", mr:mr, from:from, to:to, flipped: true%}\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_f32_per_rows.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_min\", op:\"vmin.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_max\", op:\"vmax.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_mul\", op:\"vmul.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_add\", op:\"vadd.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_sub\", op:\"vsub.f32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_sub_flipped\", op:\"vsub.f32\", mr:mr, from:from, to:to, flipped: true%}\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_f32_scalars.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_min\", op:\"vmin.f32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_max\", op:\"vmax.f32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_mul\", op:\"vmul.f32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_add\", op:\"vadd.f32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_sub\", op:\"vsub.f32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_sub_flipped\", op:\"vsub.f32\", from:from, to:to, flipped:true%}\n\n.leaky_relu:\n    vmov            s0, r3\n    vdup.32         q0, d0[0]\n    {% for reg in (from..to) %}\n        vmul.f32    q2, q{{reg}}, q0\n        vcgt.f32    q1, q{{reg}}, 0\n        vbsl        q1, q{{reg}}, q2\n        vmov        q{{reg}}, q1\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shl:\n.q_shr:\n.q_scale:\n    b .unsupported\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_i32_32x1.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: q8..q16\n\n    .arm\n    .text\n    .global armv7neon_mmm_i32_32x1_{{suffix}}\n    .type armv7neon_mmm_i32_32x1_{{suffix}}, %function\n\narmv7neon_mmm_i32_32x1_{{suffix}}:\n\n    pld     [r0]\n    push    { r4-r12 }\n    vpush   { q4-q7 }\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    // r3 r4 r5 r6\n    // k  a  b  packing\n    cmp     r3, #0\n    beq     .non_linear_loop\n\n    mov     r1, r4 // packed A ptr\n    pld     [r3]\n    pld     [r7]\n\n    cmp     r6, #1\n    beq     .packed_packed_i8i8\n\n    .packed_packed:\n\n    .packed_packed_loop_1:\n    vldmia         r1!, { q4-q7 }\n\n    vld1.32        { d0[0] }, [ r5 ]!\n\n    vmla.s32       q8, q4, d0[0]\n\n    vldmia         r1!, { q1-q4 }\n\n    vmla.s32       q9, q5, d0[0]\n    vmla.s32       q10, q6, d0[0]\n    vmla.s32       q11, q7, d0[0]\n\n    vmla.s32       q12, q1, d0[0]\n    vmla.s32       q13, q2, d0[0]\n\n    vmla.s32       q14, q3, d0[0]\n    vmla.s32       q15, q4, d0[0]\n\n    subs r3, r3, #1\n    bne .packed_packed_loop_1\n    b   .non_linear_loop\n\n    .packed_packed_i8i8:\n\n    .packed_packed_loop_i8i8_1:\n    vldmia          r1!, { q4-q5 }\n\n    vld1.8          { d0[0] }, [ r5 ]!\n    vmovl.s8        q0, d0\n\n    vmovl.s8        q1, d8\n    vmlal.s16       q8, d2, d0[0]\n    vmlal.s16       q9, d3, d0[0]\n\n    vmovl.s8        q1, d9\n    vmlal.s16       q10, d2, d0[0]\n    vmlal.s16       q11, d3, d0[0]\n\n    vmovl.s8        q1, d10\n    vmlal.s16       q12, d2, d0[0]\n    vmlal.s16       q13, d3, d0[0]\n\n    vmovl.s8        q1, d11\n    vmlal.s16       q14, d2, d0[0]\n    vmlal.s16       q15, d3, d0[0]\n\n    subs r3, r3, #1\n    bne .packed_packed_loop_i8i8_1\n    b   .non_linear_loop\n\n{% include \"armv7neon_mmm_i32_scalars.tmpliq\" from:8, to:15 %}\n{% include \"armv7neon_mmm_i32_per_rows.tmpliq\" mr:32, from:8, to:15 %}\n{% include \"armv7neon_mmm_i32_per_cols.tmpliq\" mr:32, from:8, to:15 %}\n\n.add_unicast:\n    // r3, r4, r5, r6 <- ptr, rsc, csc, size\n\n    cmp     r6, #4\n    beq     .non_linear_addc_i32\n\n    {% for reg in (16..31) %}\n        vld1.s8     d0[0], [ r3 ], r4\n        vld1.s8     d0[1], [ r3 ], r4\n        vmovl.s8    q0, d0\n        vmovl.s16   q0, d0\n        vadd.i32    d{{reg}}, d0\n    {% endfor %}\n\n    b .non_linear_loop\n\n.non_linear_addc_i32:\n    {% for reg in (16..31) %}\n        vld1.s32    d0[0], [ r3 ], r4\n        vld1.s32    d0[1], [ r3 ], r4\n        vadd.i32    d{{reg}}, d0\n    {% endfor %}\n    b .non_linear_loop\n\n.add_row_col_products:\n    vldm    \tr4, { s0 }\n\n    vldmia          r3!, { q4-q7 }\n\n    vmla.s32        q8, q4, d0[0]\n    vmla.s32        q9, q5, d0[0]\n\n    vmla.s32        q10, q6, d0[0]\n    vmla.s32        q11, q7, d0[0]\n\n    vldmia          r3!, { q4-q7 }\n\n    vmla.s32        q12, q4, d0[0]\n    vmla.s32        q13, q5, d0[0]\n\n    vmla.s32        q14, q6, d0[0]\n    vmla.s32        q15, q7, d0[0]\n\n    b .non_linear_loop\n\n    {% include \"armv7neon_mmm_i32_scale_q8_q15.tmpliq\" %}\n\n.store:\n    // r3, r4, r5, r6 <- ptr, rsc, csc, size\n    cmp     r6, #4\n    beq     .store_strides_i32\n\n    {% for reg in (8..15) %}\n        vmovn.s32 d{{reg | times: 2}}, q{{reg}}\n        vmovn.s16 d{{reg | times: 2}}, q{{reg}}\n    {% endfor %}\n    {% for reg in (8..15) %}\n        {%capture d%}{{reg | times: 2 }}{%endcapture%}\n        vst1.s8     d{{d}}[0], [ r3 ], r4\n        vst1.s8     d{{d}}[1], [ r3 ], r4\n        vst1.s8     d{{d}}[2], [ r3 ], r4\n        vst1.s8     d{{d}}[3], [ r3 ], r4\n    {% endfor %}\n\n    b .non_linear_loop\n\n.store_strides_i32:\n    {% for reg in (8..15) %}\n        {%capture d%}{{reg | times: 2}}{%endcapture%}\n        vst1.s32    d{{d}}[0], [ r3 ], r4\n        vst1.s32    d{{d}}[1], [ r3 ], r4\n        vst1.s32    d{{d|plus:1}}[0], [ r3 ], r4\n        vst1.s32    d{{d|plus:1}}[1], [ r3 ], r4\n    {% endfor %}\n\n    b .non_linear_loop\n\n.load_tile:\n    vldmia          r3!, { q8-q15 }\n    b .non_linear_loop\n\n.return:\n    vpop        { q4-q7 }\n    pop         { r4-r12 }\n\n    bx          lr\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_i32_8x4.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs\n// \n//      q8[0]    q10[0]   q12[0]    q14[0]\n//      q8[1]    q10[1]   q12[1]    q14[1]\n//      q8[2]    q10[2]   q12[2]    q14[2]\n//      q8[3]    q10[3]   q12[3]    q14[3]\n//\n//      q9[0]    q11[0]   q13[0]    q15[0]\n//      q9[1]    q11[1]   q13[1]    q15[1]\n//      q9[2]    q11[2]   q13[2]    q15[2]\n//      q9[3]    q11[3]   q13[3]    q15[3]\n\n    .arm\n    .text\n    .global armv7neon_mmm_i32_8x4_{{suffix}}\n    .type armv7neon_mmm_i32_8x4_{{suffix}}, %function\n\narmv7neon_mmm_i32_8x4_{{suffix}}:\n\n    pld     [r0]\n    push    { r4-r12 }\n    vpush   { q4-q7 }\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    // r3 r4 r5 r6\n    // k  a  b  packing\n    cmp     r3, #0\n    beq     .non_linear_loop\n\n    mov     r1, r4 // packed A ptr\n    pld     [r3]\n    pld     [r5]\n\n    cmp     r6, #1\n    beq     .packed_packed_i8i8\n\n    .packed_packed_loop_1:\n\n    vldmia          r1!, { q0, q1 }\n    vldmia          r5!, { q2 }\n\n    vmla.s32       q8, q0, d4[0]\n    vmla.s32       q9, q1, d4[0]\n\n    vmla.s32       q10, q0, d4[1]\n    vmla.s32       q11, q1, d4[1]\n\n    vmla.s32       q12, q0, d5[0]\n    vmla.s32       q13, q1, d5[0]\n\n    vmla.s32       q14, q0, d5[1]\n    vmla.s32       q15, q1, d5[1]\n\n    subs r3, r3, #1\n    bne .packed_packed_loop_1\n\n    b   .non_linear_loop\n\n    .packed_packed_i8i8:\n    pld     [r5]                           // packed B ptr       \n\n    cmp r3, #4\n    blt .packed_packed_loop_i8i8_1\n\n    .packed_packed_loop_i8i8_4:\n    pld             [r1, #64]\n    pld             [r5, #64]\n\n    // q2: d4 -> d4,d5 A even cols (from r1)\n    // q3: d6 -> d6,d7 A odd cols (from r1)\n    // q0: s0 -> d0 : B even lines (from r5)\n    // q1: s4 -> d2 : B odd lines (from r5)\n\n    // 0\n    vldmia          r1!, { d4 }\n    vldmia          r5!, { s0 }\n\n    vmovl.s8        q2, d4\n    vmovl.s8        q0, d0\n\n    vmlal.s16       q8, d4, d0[0]\n    vmlal.s16       q9, d5, d0[0]\n\n    vldmia          r1!, { d6 }\n\n    vmlal.s16       q10, d4, d0[1]\n    vmlal.s16       q11, d5, d0[1]\n\n    vldmia          r5!, { s4 }\n\n    vmlal.s16       q12, d4, d0[2]\n    vmlal.s16       q13, d5, d0[2]\n\n    vmlal.s16       q14, d4, d0[3]\n    vmlal.s16       q15, d5, d0[3]\n\n    // 1\n    vmovl.s8        q3, d6\n    vmovl.s8        q1, d2\n\n    vmlal.s16       q8, d6, d2[0]\n    vldmia          r1!, { d4 }\n    vmlal.s16       q9, d7, d2[0]\n    vldmia          r5!, { s0 }\n\n    vmlal.s16       q10, d6, d2[1]\n    vmlal.s16       q11, d7, d2[1]\n\n    vmlal.s16       q12, d6, d2[2]\n    vmlal.s16       q13, d7, d2[2]\n\n    vmlal.s16       q14, d6, d2[3]\n    vmlal.s16       q15, d7, d2[3]\n\n    // 2\n    vmovl.s8        q2, d4\n    vmovl.s8        q0, d0\n\n    vmlal.s16       q8, d4, d0[0]\n    vmlal.s16       q9, d5, d0[0]\n\n    vldmia          r1!, { d6 }\n\n    vmlal.s16       q10, d4, d0[1]\n    vmlal.s16       q11, d5, d0[1]\n\n    vldmia          r5!, { s4 }\n\n    vmlal.s16       q12, d4, d0[2]\n    vmlal.s16       q13, d5, d0[2]\n\n    vmlal.s16       q14, d4, d0[3]\n    vmlal.s16       q15, d5, d0[3]\n\n    // 3\n    vmovl.s8        q3, d6\n    vmovl.s8        q1, d2\n\n    vmlal.s16       q8, d6, d2[0]\n    vmlal.s16       q9, d7, d2[0]\n\n    vmlal.s16       q10, d6, d2[1]\n    vmlal.s16       q11, d7, d2[1]\n\n    vmlal.s16       q12, d6, d2[2]\n    vmlal.s16       q13, d7, d2[2]\n\n    vmlal.s16       q14, d6, d2[3]\n    vmlal.s16       q15, d7, d2[3]\n\n    sub r3, r3, #4\n    cmp r3, #4\n    bge .packed_packed_loop_i8i8_4\n\n    cmp r3, #0\n    beq .non_linear_loop\n\n    .packed_packed_loop_i8i8_1:\n\n    vldmia          r1!, { s0, s1 }\n    vmovl.s8        q0, d0\n    vldmia          r5!, { s4 }\n    vmovl.s8        q1, d2\n\n    vmlal.s16       q8, d0, d2[0]\n    vmlal.s16       q9, d1, d2[0]\n\n    vmlal.s16       q10, d0, d2[1]\n    vmlal.s16       q11, d1, d2[1]\n\n    vmlal.s16       q12, d0, d2[2]\n    vmlal.s16       q13, d1, d2[2]\n\n    vmlal.s16       q14, d0, d2[3]\n    vmlal.s16       q15, d1, d2[3]\n\n    subs r3, r3, #1\n    bne .packed_packed_loop_i8i8_1\n    b   .non_linear_loop\n\n{% include \"armv7neon_mmm_i32_scalars.tmpliq\" from:8, to:15 %}\n{% include \"armv7neon_mmm_i32_per_rows.tmpliq\" mr:8, from:8, to:15 %}\n{% include \"armv7neon_mmm_i32_per_cols.tmpliq\" mr:8, from:8, to:15 %}\n\n.add_unicast:\n    // r3, r4, r5, r6 <- ptr, rsc, csc, size\n    cmp     r6, #4\n    beq     .non_linear_addc_i32\n\n    {% for col in (0..3) %}\n        mov         r8, r3\n        {% for reg in (0..3) %}\n            vld1.s8     d0[0], [ r8 ], r4\n            vld1.s8     d0[1], [ r8 ], r4\n            vmovl.s8    q0, d0\n            vmovl.s16   q0, d0\n            vadd.i32    d{{col | times: 4 | plus: reg | plus : 16}}, d0\n        {% endfor %}\n        add r3, r3, r5\n    {% endfor %}\n\n    b .non_linear_loop\n\n.non_linear_addc_i32:\n\n    {% for col in (0..3) %}\n        mov         r8, r3\n        {% for reg in (0..3) %}\n            vld1.s32    d0[0], [ r8 ], r4\n            vld1.s32    d0[1], [ r8 ], r4\n            vadd.i32    d{{col | times: 4 | plus: reg | plus : 16}}, d0\n        {% endfor %}\n        {% if col < 3 %}\n            add r3, r3, r5\n        {% endif %}\n    {% endfor %}\n\nb .non_linear_loop\n\n.add_row_col_products:\n    vldmia          r3!, { q0, q1 }\n    vldmia          r4!, { q4 }\n\n    vmla.s32        q8, q0, d8[0]\n    vmla.s32        q9, q1, d8[0]\n\n    vmla.s32        q10, q0, d8[1]\n    vmla.s32        q11, q1, d8[1]\n\n    vmla.s32        q12, q0, d9[0]\n    vmla.s32        q13, q1, d9[0]\n\n    vmla.s32        q14, q0, d9[1]\n    vmla.s32        q15, q1, d9[1]\n\n    b .non_linear_loop\n\n    {% include \"armv7neon_mmm_i32_scale_q8_q15.tmpliq\" %}\n\n.store:\n    // r3, r4, r5, r6 <- ptr, rsc, csc, size\n    cmp     r6, #4\n    beq     .store_strides_i32\n\n    {% for reg in (8..15) %}\n        vmovn.s32 d{{reg | times: 2}}, q{{reg}}\n        vmovn.s16 d{{reg | times: 2}}, q{{reg}}\n    {% endfor %}\n    {% for col in (0..3) %}\n        mov         r8, r3\n        {% for reg in (0..1) %}\n            {%capture d%}{{col | times: 2 | plus: reg | times: 2 | plus: 16}}{%endcapture%}\n            vst1.s8     d{{d}}[0], [ r8 ], r4\n            vst1.s8     d{{d}}[1], [ r8 ], r4\n            vst1.s8     d{{d}}[2], [ r8 ], r4\n            vst1.s8     d{{d}}[3], [ r8 ], r4\n        {% endfor %}\n        {% if col < 3 %}\n            add r3, r3, r5\n        {% endif %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.store_strides_i32:\n\n    {% for col in (0..3) %}\n        mov         r8, r3\n        {% for reg in (0..3) %}\n            {% for lane in (0..1) %}\n                vst1.s32     d{{col | times: 4 | plus: reg | plus: 16}}[{{lane}}], [ r8 ], r4\n            {% endfor %}\n        {% endfor %}\n        {% if col < 3 %}\n            add r3, r3, r5\n        {% endif %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.load_tile:\n    vldmia          r3!, { q8-q15 }\n    b .non_linear_loop\n\n.return:\n    vpop        { q4-q7 }\n    pop         { r4-r12 }\n\n    bx          lr\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_i32_per_cols.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_min\", op:\"vmin.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_max\", op:\"vmax.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_mul\", op:\"vmul.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_add\", op:\"vadd.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_sub\", op:\"vsub.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_col.tmpliq\" label:\"per_col_sub_flipped\", op:\"vsub.s32\", mr:mr, from:from, to:to, flipped:true%}\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_i32_per_rows.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_min\", op:\"vmin.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_max\", op:\"vmax.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_mul\", op:\"vmul.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_add\", op:\"vadd.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_sub\", op:\"vsub.s32\", mr:mr, from:from, to:to %}\n{% include \"armv7neon_mmm_q_per_row.tmpliq\" label:\"per_row_sub_flipped\", op:\"vsub.s32\", mr:mr, from:from, to:to, flipped:true%}\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_i32_scalars.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_min\", op:\"vmin.s32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_max\", op:\"vmax.s32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_mul\", op:\"vmul.s32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_add\", op:\"vadd.s32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_sub\", op:\"vsub.s32\", from:from, to:to%}\n{% include \"armv7neon_mmm_q_scalar.tmpliq\" label:\"scalar_sub_flipped\", op:\"vsub.s32\", from:from, to:to, flipped:true%}\n\n.leaky_relu:\n    vmov            s0, r3\n    vdup.32         q0, d0[0]\n    {% for reg in (from..to) %}\n        vmul.s32    q2, q{{reg}}, q0\n        vcgt.s32    q1, q{{reg}}, 0\n        vbsl        q1, q{{reg}}, q2\n        vmov        q{{reg}}, q1\n    {% endfor %}\n    b .non_linear_loop\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_i32_scale_q8_q15.tmpliq",
    "content": "// vim: ft=arm\n\n.q_scale:\n    ldm         r0, { r4, r5, r6, r7 }      // fixme params are already loaded by disp.\n    vdup.s32    q0, r7                      // q0 <- multiplier\n\n    mov         r3, #1\n    vdup.s32    q1, r3                      // q1 <- ones\n    vmovl.s32   q1, d2\n\n    add         r5, #32\n    neg         r5, r5\n    vdup.s32    q2, r5                      // q2 <- -(shift + 32)\n    vmovl.s32   q2, d4\n\n    cmp     r6, #1\n    beq     .q_scale_rounding_zero\n    cmp     r6, #2\n    beq     .q_scale_rounding_away\n    cmp     r6, #3\n    beq     .q_scale_rounding_minus_inf\n    cmp     r6, #4\n    beq     .q_scale_rounding_plus_inf\n    cmp     r6, #5\n    beq     .q_scale_rounding_even\n    cmp     r6, #6\n    beq     .q_scale_rounding_odd\n\n    b .unsupported\n\n.q_scale_rounding_zero:\n    {% for q in (8..15) %}\n        vclt.s32    q7, q{{q}}, #0\n        vabs.s32    q{{q}}, q{{q}}\n        vqdmull.s32 q5, d{{q | times:2}}, d0[0]\n        vqdmull.s32 q6, d{{q | times:2 | plus:1}}, d0[0]\n        vsub.s64    q5, q1\n        vsub.s64    q6, q1\n        vqrshl.s64  q5, q2\n        vqrshl.s64  q6, q2\n        vmovn.s64   d{{q | times:2}}, q5\n        vmovn.s64   d{{q | times:2 | plus: 1}}, q6\n        vneg.s32    q5, q{{q}}\n        vbit.s32    q{{q}}, q5, q7\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_away:\n    {% for q in (8..15) %}\n        vclt.s32    q7, q{{q}}, #0\n        vabs.s32    q{{q}}, q{{q}}\n        vqdmull.s32 q5, d{{q | times:2}}, d0[0]\n        vqdmull.s32 q6, d{{q | times:2 | plus:1}}, d0[0]\n        vqrshl.s64  q5, q2\n        vqrshl.s64  q6, q2\n        vmovn.s64   d{{q | times:2}}, q5\n        vmovn.s64   d{{q | times:2 | plus: 1}}, q6\n        vneg.s32    q5, q{{q}}\n        vbit.s32    q{{q}}, q5, q7\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_minus_inf:\n    {% for q in (8..15) %}\n        vqdmull.s32 q5, d{{q | times:2}}, d0[0]\n        vqdmull.s32 q6, d{{q | times:2 | plus:1}}, d0[0]\n        vsub.s64    q5, q1\n        vsub.s64    q6, q1\n        vqrshl.s64  q5, q2\n        vqrshl.s64  q6, q2\n        vmovn.s64   d{{q | times:2}}, q5\n        vmovn.s64   d{{q | times:2 | plus: 1}}, q6\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_plus_inf:\n    {% for q in (8..15) %}\n        vqdmull.s32 q5, d{{q | times:2}}, d0[0]\n        vqdmull.s32 q6, d{{q | times:2 | plus:1}}, d0[0]\n        vqrshl.s64  q5, q2\n        vqrshl.s64  q6, q2\n        vmovn.s64   d{{q | times:2}}, q5\n        vmovn.s64   d{{q | times:2 | plus: 1}}, q6\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_even:\n    {% for q in (8..15) %}\n        vclt.s32    q7, q{{q}}, #0\n        vabs.s32    q{{q}}, q{{q}}\n        vqdmull.s32 q5, d{{q | times:2}}, d0[0]\n        vqdmull.s32 q6, d{{q | times:2 | plus:1}}, d0[0]\n        vqshl.s64   q3, q5, q2\n        vqshl.s64   q4, q6, q2\n        vand        q3, q3, q1\n        vand        q4, q4, q1\n        vsub.s64    q3, q3, q1\n        vsub.s64    q4, q4, q1\n        vadd.s64    q5, q3\n        vadd.s64    q6, q4\n        vqrshl.s64  q5, q2\n        vqrshl.s64  q6, q2\n        vmovn.s64   d{{q | times:2}}, q5\n        vmovn.s64   d{{q | times:2 | plus: 1}}, q6\n        vneg.s32    q5, q{{q}}\n        vbit.s32    q{{q}}, q5, q7\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_odd:\n    {% for q in (8..15) %}\n        vclt.s32    q7, q{{q}}, #0\n        vabs.s32    q{{q}}, q{{q}}\n        vqdmull.s32 q5, d{{q | times:2}}, d0[0]\n        vqdmull.s32 q6, d{{q | times:2 | plus:1}}, d0[0]\n        vqshl.s64   q3, q5, q2\n        vqshl.s64   q4, q6, q2\n        vand        q3, q3, q1\n        vand        q4, q4, q1\n        vsub.s64    q5, q3\n        vsub.s64    q6, q4\n        vqrshl.s64  q5, q2\n        vqrshl.s64  q6, q2\n        vmovn.s64   d{{q | times:2}}, q5\n        vmovn.s64   d{{q | times:2 | plus: 1}}, q6\n        vneg.s32    q5, q{{q}}\n        vbit.s32    q{{q}}, q5, q7\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_shl:\n    ldm         r0, { r4, r5 }      // fixme params are already loaded by disp.\n    vdup.s32    q2, r5              // q2 <- shift\n\n    {% for q in (8..15) %}\n        vqrshl.s32  q{{q}}, q2      // Shift\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_shr:\n    ldm         r0, { r4, r5, r6 }      // fixme params are already loaded by disp.\n\n    mov         r3, #1\n    vdup.s32    q1, r3                      // q1 <- ones\n\n    neg         r5, r5\n    vdup.s32    q2, r5                      // q2 <- shift\n\n    cmp     r6, #1\n    beq     .q_shr_rounding_zero\n    cmp     r6, #2\n    beq     .q_shr_rounding_away\n    cmp     r6, #3\n    beq     .q_shr_rounding_minus_inf\n    cmp     r6, #4\n    beq     .q_shr_rounding_plus_inf\n    cmp     r6, #5\n    beq     .q_shr_rounding_even\n    cmp     r6, #6\n    beq     .q_shr_rounding_odd\n\n    b .unsupported\n\n.q_shr_rounding_zero:\n    // return signum(x) * ((abs(x) - 1) >>r shift )\n    {% for q in (8..15) %}\n        vclt.s32    q3, q{{q}}, #0  // Store the sign of the value\n        vabs.s32    q{{q}}, q{{q}}  // Compute their abs\n        vsub.s32    q{{q}}, q1      // Substract 1 to abs(x)\n        vqrshl.s32  q{{q}}, q2      // Rounding shift (0.5 -> 1)\n        vneg.s32    q4, q{{q}}      // Compute -((abs(x) - 1) >>r shift )\n        vbit.s32    q{{q}}, q4, q3  // Restore sign of x with bit mask\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_away:\n    // return signum(x) * (abs(x) >>r shift )\n    {% for q in (8..15) %}\n        vclt.s32    q3, q{{q}}, #0  // Store the sign of the value\n        vabs.s32    q{{q}}, q{{q}}  // Compute their abs\n        vqrshl.s32  q{{q}}, q2      // Rounding shift (0.5 -> 1)\n        vneg.s32    q4, q{{q}}      // Compute -(abs(x) >>r shift )\n        vbit.s32    q{{q}}, q4, q3  // Restore sign of x with bit mask\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_minus_inf:\n    // return -(-x >>r shift)\n    {% for q in (8..15) %}\n        vneg.s32    q3, q{{q}}      // Compute -x\n        vqrshl.s32  q3, q2          // Rounding shift (0.5 -> 1)\n        vneg.s32    q{{q}}, q3      // Compute -(-x >>r shift)\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_plus_inf:\n    // return x >>r shift\n    {% for q in (8..15) %}\n        vqrshl.s32  q{{q}}, q2      // Rounding shift (0.5 -> 1)\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_even:\n    // If (x >> shift) is odd -> (x - 0) >>r shift\n    // If (x >> shift) is even -> (x - 1) >>r shift\n    {% for q in (8..15) %}\n        vqshl.s32   q3, q{{q}}, q2      // Truncate shift (0.5 -> 0)\n        vand.s32    q4, q3, q1          // Store if x is odd\n        vsub.s32    q5, q4, q1          // If (x >> shift) is odd 0 else -1\n        vadd.s32   q{{q}}, q{{q}}, q5   // If (x >> shift) is odd (x - 0) else (x - 1)\n        vqrshl.s32 q{{q}}, q2           // Rounding shift (0.5 -> 1)\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_odd:\n    // If (x >> shift) is even -> (x - 0) >>r shift\n    // If (x >> shift) is odd -> (x - 1) >>r shift\n    {% for q in (8..15) %}\n        vqshl.s32   q3, q{{q}}, q2      // Truncate shift (0.5 -> 0)\n        vand.s32    q4, q3, q1          // Store if x >> shift is odd\n        vneg.s32    q5, q4              // If x is odd -1 else 0\n        vadd.s32   q{{q}}, q{{q}}, q5   // If x is odd (x - 1) else (x - 0)\n        vqrshl.s32 q{{q}}, q2           // Rounding shift (0.5 -> 1)\n    {% endfor %}\n    b .non_linear_loop\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_q_per_col.tmpliq",
    "content": "// vim: ft=arm\n\n.{{label}}:\n\n{% capture mr_over_4 %}{{ mr | divided_by: 4}}{%endcapture%}\n{% capture mr_over_4_min_1 %}{{ mr | divided_by: 4 | minus: 1}}{%endcapture%}\n\n{%capture cols%}{{to | plus: 1| minus:from| divided_by:mr_over_4}}{%endcapture%}\n{%capture cols_min_1%}{{to | plus: 1| minus:from| divided_by:mr_over_4|minus:1}}{%endcapture%}\n\n{% if cols == \"1\" %}\n    vld1.f32        d0[0], [ r3 ]\n{% else %}\n    {%capture cols_over_2_minus_1%}{{cols | divided_by:2 | minus:1}}{%endcapture%}\n    {% for c in (0..cols_over_2_minus_1) %}\n        vldmia      r3!, { d{{c}} }\n    {% endfor %}\n{% endif %}\n\n\n{% for right in (0..cols_min_1) %}\n    vdup.f32 q3, d{{right|divided_by:2}}[{{right| modulo:2}}]\n    {% for down in (0..mr_over_4_min_1) %}\n        {%capture acc%}{{mr_over_4|times:right|plus:from|plus:down}}{%endcapture%}\n        {% if flipped %}\n            {{op}} q{{acc}}, q{{acc}}, q3\n        {% else %}\n            {{op}} q{{acc}}, q3, q{{acc}}\n        {% endif %}\n    {% endfor %}\n{% endfor %}\n\n    b .non_linear_loop\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_q_per_row.tmpliq",
    "content": "// vim: ft=arm\n\n.{{label}}:\n\n{% capture mr_over_4 %}{{ mr | divided_by: 4}}{%endcapture%}\n{% capture mr_over_4_min_1 %}{{ mr | divided_by: 4 | minus: 1}}{%endcapture%}\n\n{% for reg in (0..mr_over_4_min_1) %}\n    vldmia         r3!, { q{{reg}} }\n{% endfor %}\n\n{% if flipped %}\n    {% for acc in (from..to) %}\n        {% capture other%}{{acc | minus: from | modulo: mr_over_4}}{%endcapture%}\n        {{op}} q{{acc}}, q{{acc}}, q{{other}}\n    {% endfor %}\n{% else %}\n    {% for acc in (from..to) %}\n        {% capture other%}{{acc | minus: from | modulo: mr_over_4}}{%endcapture%}\n        {{op}} q{{acc}}, q{{other}}, q{{acc}}\n    {% endfor %}\n{% endif %}\n\nb           .non_linear_loop\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_mmm_q_scalar.tmpliq",
    "content": "// vim: ft=arm\n\n.{{label}}:\n    vmov            s0, r3\n    vdup.32         q0, d0[0]\n    {% if flipped %}\n        {% for reg in (from..to) %}\n            {{op}}    q{{reg}}, q{{reg}}, q0\n        {% endfor %}\n    {% else %}\n        {% for reg in (from..to) %}\n            {{op}}    q{{reg}}, q0, q{{reg}}\n        {% endfor %}\n    {% endif %}\n    b .non_linear_loop\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_prefetch.tmpl",
    "content": "// vim: ft=arm\n\n.arm\n.text\n.global armv7neon_prefetch_{{suffix}}\n.type armv7neon_prefetch_{{suffix}}, %function\n\narmv7neon_prefetch_{{suffix}}:\nloop:\n    pld     [r0]\n    pld     [r0, #32]\n    pld     [r0, #64]\n    pld     [r0, #96]\n    pld     [r0, #128]\n    pld     [r0, #160]\n    pld     [r0, #192]\n    pld     [r0, #224]\n    add     r0, r0, #256\n    cmp     r0, r1\n    blt     loop\n\n    bx      lr\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_sigmoid_f32_4n.tmpl",
    "content": "// vim: ft=arm\n\n    .arm\n    .text\n    .global armv7neon_sigmoid_f32_4n_{{suffix}}\n    .type armv7neon_sigmoid_f32_4n_{{suffix}}, %function\n\n/*\n    s16–s31 (d8–d15, q4–q7) must be preserved\n    s0–s15 (d0–d7, q0–q3) and d16–d31 (q8–q15) do not need to be preserved\n*/\n\narmv7neon_sigmoid_f32_4n_{{suffix}}:\n    cmp         r1, #0\n    blxeq       lr\n\n    vpush       { q4-q7 }\n\n    adr         r2, .coeffs_num\n    vldmia      r2!, { s0-s13 }\n\n// q4 -> q4,5,6\n// q5 -> q7,8,9\n// q6 -> q10,11,12\n// q7 -> q13,14,15\n\n\n    cmp         r1, #12\n    blt         .loop\n\n.loop_3:\n    vldmia      r0, { q4, q5, q6 }         // q4 <- x\n\n    vdup.32     q15, d0[0]\n    vmax.f32    q4, q15\n    vmax.f32    q5, q15\n    vmax.f32    q6, q15\n    vdup.32     q15, d0[1]\n    vmin.f32    q4, q15\n    vmin.f32    q5, q15\n    vmin.f32    q6, q15\n\n    vmul.f32    q7, q4, q4          // q7 <- x2\n    vmul.f32    q8, q5, q5\n    vmul.f32    q9, q6, q6\n\n    vdup.32     q10, d1[0]\n    vdup.32     q11, d1[0]\n    vdup.32     q12, d1[0]\n    vdup.32     q13, d1[1]\n    vdup.32     q14, d1[1]\n    vdup.32     q15, d1[1]\n    vmla.f32    q13, q7, q10\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n    vdup.32     q10, d2[0]\n    vdup.32     q11, d2[0]\n    vdup.32     q12, d2[0]\n    vmla.f32    q10, q13, q7\n    vmla.f32    q11, q14, q8\n    vmla.f32    q12, q15, q9\n    vdup.32     q13, d2[1]\n    vdup.32     q14, d2[1]\n    vdup.32     q15, d2[1]\n    vmla.f32    q13, q7, q10\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n    vdup.32     q10, d3[0]\n    vdup.32     q11, d3[0]\n    vdup.32     q12, d3[0]\n    vmla.f32    q10, q13, q7\n    vmla.f32    q11, q14, q8\n    vmla.f32    q12, q15, q9\n    vdup.32     q13, d3[1]\n    vdup.32     q14, d3[1]\n    vdup.32     q15, d3[1]\n    vmla.f32    q13, q7, q10\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n    vdup.32     q10, d4[0]\n    vdup.32     q11, d4[0]\n    vdup.32     q12, d4[0]\n    vmla.f32    q10, q13, q7\n    vmla.f32    q11, q14, q8\n    vmla.f32    q12, q15, q9\n    vmul.f32    q4, q4, q10          // q4 <- numerator\n    vmul.f32    q5, q5, q11\n    vmul.f32    q6, q6, q12\n\n    vdup.32     q10, d4[1]\n    vdup.32     q11, d4[1]\n    vdup.32     q12, d4[1]\n    vdup.32     q13, d5[0]\n    vdup.32     q14, d5[0]\n    vdup.32     q15, d5[0]\n    vmla.f32    q13, q7, q10\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n    vdup.32     q10, d5[1]\n    vdup.32     q11, d5[1]\n    vdup.32     q12, d5[1]\n    vmla.f32    q10, q13, q7\n    vmla.f32    q11, q14, q8\n    vmla.f32    q12, q15, q9\n    vdup.32     q13, d6[0]\n    vdup.32     q14, d6[0]\n    vdup.32     q15, d6[0]\n    vmla.f32    q13, q7, q10          // q13 <- denum\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n\n    vrecpe.f32  q7, q13\n    vrecpe.f32  q8, q14\n    vrecpe.f32  q9, q15\n    vrecps.f32  q10, q7, q13\n    vrecps.f32  q11, q8, q14\n    vrecps.f32  q12, q9, q15\n    vmul.f32    q7, q7, q10\n    vmul.f32    q8, q8, q11\n    vmul.f32    q9, q9, q12\n    vrecps.f32  q10, q7, q13\n    vrecps.f32  q11, q8, q14\n    vrecps.f32  q12, q9, q15\n    vmul.f32    q7, q7, q10          // q7 <- 1/q13\n    vmul.f32    q8, q8, q11\n    vmul.f32    q9, q9, q12\n\n    vdup.32     q10, d6[1]\n    vdup.32     q11, d6[1]\n    vdup.32     q12, d6[1]\n    vmla.f32    q10, q4, q7\n    vmla.f32    q11, q5, q8\n    vmla.f32    q12, q6, q9\n\n    vstmia      r0!, { q10, q11, q12 }\n\n    subs        r1, #12\n    cmp         r1, #12\n    bge         .loop_3\n\n    cmp         r1, #0;\n    beq         .return\n\n.loop:\n    vldmia      r0, { q4 }         // q4 <- x\n\n    vdup.32     q15, d0[0]\n    vmax.f32    q4, q15\n    vdup.32     q15, d0[1]\n    vmin.f32    q4, q15\n\n    vmul.f32    q7, q4, q4          // q7 <- x2\n\n    vdup.32     q10, d1[0]\n    vdup.32     q13, d1[1]\n    vmla.f32    q13, q7, q10\n    vdup.32     q10, d2[0]\n    vmla.f32    q10, q13, q7\n    vdup.32     q13, d2[1]\n    vmla.f32    q13, q7, q10\n    vdup.32     q10, d3[0]\n    vmla.f32    q10, q13, q7\n    vdup.32     q13, d3[1]\n    vmla.f32    q13, q7, q10\n    vdup.32     q10, d4[0]\n    vmla.f32    q10, q13, q7\n    vmul.f32    q4, q4, q10          // q4 <- numerator\n\n    vdup.32     q10, d4[1]\n    vdup.32     q13, d5[0]\n    vmla.f32    q13, q7, q10\n    vdup.32     q10, d5[1]\n    vmla.f32    q10, q13, q7\n    vdup.32     q13, d6[0]\n    vmla.f32    q13, q7, q10          // q13 <- denum\n\n    vrecpe.f32  q7, q13\n    vrecps.f32  q10, q7, q13\n    vmul.f32    q7, q7, q10\n    vrecps.f32  q10, q7, q13\n    vmul.f32    q7, q7, q10          // q7 <- 1/q13\n\n    vdup.32     q10, d6[1]\n    vmla.f32    q10, q4, q7\n\n    vstmia      r0!, { q10 }\n\n    subs        r1, #4;\n    bne         .loop\n\n.return:\n    vpop        { q4-q7 }\n    bx          lr\n\n.coeffs_num:\n    .float -18.6                    // low\n    .float 18.6                     // high\n    .float -4.433153405e-18         // alpha_13\n    .float 1.169974371e-14\n\n    .float -1.875289645e-11\n    .float 4.257889523e-8\n    .float 0.00004811817576\n    .float 0.008163842030\n\n    .float 0.2499999971\n    .float 3.922935744e-6           // beta_6\n    .float 0.001524872358\n    .float 0.1159886749\n\n    .float 1.0\n    .float 0.5                      //              \n    .float 0.0                      // padding\n    .float 0.0\n\n"
  },
  {
    "path": "linalg/arm32/armv7neon/armv7neon_tanh_f32_4n.tmpl",
    "content": "// vim: ft=arm\n\n    .arm\n    .text\n    .global armv7neon_tanh_f32_4n_{{suffix}}\n    .type armv7neon_tanh_f32_4n_{{suffix}}, %function\n\n/*\n    s16–s31 (d8–d15, q4–q7) must be preserved\n    s0–s15 (d0–d7, q0–q3) and d16–d31 (q8–q15) do not need to be preserved\n*/\n\narmv7neon_tanh_f32_4n_{{suffix}}:\n    cmp         r1, #0\n    blxeq       lr\n\n    vpush       { q4-q7 }\n\n    adr         r2, .coeffs_num\n    vldmia      r2!, { s0-s13 }\n\n// q4 -> q4,5,6\n// q5 -> q7,8,9\n// q6 -> q10,11,12\n// q7 -> q13,14,15\n\n    cmp         r1, #12\n    blt         .loop\n\n.loop_3:\n    vldmia      r0, { q4, q5, q6 }         // q4 <- x\n\n    vdup.32     q15, d0[0]\n    vmax.f32    q4, q15\n    vmax.f32    q5, q15\n    vmax.f32    q6, q15\n    vdup.32     q15, d0[1]\n    vmin.f32    q4, q15\n    vmin.f32    q5, q15\n    vmin.f32    q6, q15\n\n    vmul.f32    q7, q4, q4          // q7 <- x2\n    vmul.f32    q8, q5, q5\n    vmul.f32    q9, q6, q6\n\n    vdup.32     q10, d1[0]\n    vdup.32     q11, d1[0]\n    vdup.32     q12, d1[0]\n    vdup.32     q13, d1[1]\n    vdup.32     q14, d1[1]\n    vdup.32     q15, d1[1]\n    vmla.f32    q13, q7, q10\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n    vdup.32     q10, d2[0]\n    vdup.32     q11, d2[0]\n    vdup.32     q12, d2[0]\n    vmla.f32    q10, q13, q7\n    vmla.f32    q11, q14, q8\n    vmla.f32    q12, q15, q9\n    vdup.32     q13, d2[1]\n    vdup.32     q14, d2[1]\n    vdup.32     q15, d2[1]\n    vmla.f32    q13, q7, q10\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n    vdup.32     q10, d3[0]\n    vdup.32     q11, d3[0]\n    vdup.32     q12, d3[0]\n    vmla.f32    q10, q13, q7\n    vmla.f32    q11, q14, q8\n    vmla.f32    q12, q15, q9\n    vdup.32     q13, d3[1]\n    vdup.32     q14, d3[1]\n    vdup.32     q15, d3[1]\n    vmla.f32    q13, q7, q10\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n    vdup.32     q10, d4[0]\n    vdup.32     q11, d4[0]\n    vdup.32     q12, d4[0]\n    vmla.f32    q10, q13, q7\n    vmla.f32    q11, q14, q8\n    vmla.f32    q12, q15, q9\n    vmul.f32    q4, q4, q10          // q4 <- numerator\n    vmul.f32    q5, q5, q11\n    vmul.f32    q6, q6, q12\n\n    vdup.32     q10, d4[1]\n    vdup.32     q11, d4[1]\n    vdup.32     q12, d4[1]\n    vdup.32     q13, d5[0]\n    vdup.32     q14, d5[0]\n    vdup.32     q15, d5[0]\n    vmla.f32    q13, q7, q10\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n    vdup.32     q10, d5[1]\n    vdup.32     q11, d5[1]\n    vdup.32     q12, d5[1]\n    vmla.f32    q10, q13, q7\n    vmla.f32    q11, q14, q8\n    vmla.f32    q12, q15, q9\n    vdup.32     q13, d6[0]\n    vdup.32     q14, d6[0]\n    vdup.32     q15, d6[0]\n    vmla.f32    q13, q7, q10          // q13 <- denum\n    vmla.f32    q14, q8, q11\n    vmla.f32    q15, q9, q12\n\n    vrecpe.f32  q7, q13\n    vrecpe.f32  q8, q14\n    vrecpe.f32  q9, q15\n    vrecps.f32  q10, q7, q13\n    vrecps.f32  q11, q8, q14\n    vrecps.f32  q12, q9, q15\n    vmul.f32    q7, q7, q10\n    vmul.f32    q8, q8, q11\n    vmul.f32    q9, q9, q12\n    vrecps.f32  q10, q7, q13\n    vrecps.f32  q11, q8, q14\n    vrecps.f32  q12, q9, q15\n    vmul.f32    q7, q7, q10          // q7 <- 1/q13\n    vmul.f32    q8, q8, q11\n    vmul.f32    q9, q9, q12\n\n    vmul.f32    q10, q4, q7\n    vmul.f32    q11, q5, q8\n    vmul.f32    q12, q6, q9\n\n    vstmia      r0!, { q10, q11, q12 }\n\n    subs        r1, #12\n    cmp         r1, #12\n    bge         .loop_3\n\n    cmp         r1, #0;\n    beq         .return\n\n.loop:\n    vldmia      r0, { q4 }         // q4 <- x\n\n    vdup.32     q15, d0[0]\n    vmax.f32    q4, q15\n    vdup.32     q15, d0[1]\n    vmin.f32    q4, q15\n\n    vmul.f32    q7, q4, q4          // q7 <- x2\n\n    vdup.32     q10, d1[0]\n    vdup.32     q13, d1[1]\n    vmla.f32    q13, q7, q10\n    vdup.32     q10, d2[0]\n    vmla.f32    q10, q13, q7\n    vdup.32     q13, d2[1]\n    vmla.f32    q13, q7, q10\n    vdup.32     q10, d3[0]\n    vmla.f32    q10, q13, q7\n    vdup.32     q13, d3[1]\n    vmla.f32    q13, q7, q10\n    vdup.32     q10, d4[0]\n    vmla.f32    q10, q13, q7\n    vmul.f32    q4, q4, q10          // q4 <- numerator\n\n    vdup.32     q10, d4[1]\n    vdup.32     q13, d5[0]\n    vmla.f32    q13, q7, q10\n    vdup.32     q10, d5[1]\n    vmla.f32    q10, q13, q7\n    vdup.32     q13, d6[0]\n    vmla.f32    q13, q7, q10          // q13 <- denum\n\n    vrecpe.f32  q7, q13\n    vrecps.f32  q10, q7, q13\n    vmul.f32    q7, q7, q10\n    vrecps.f32  q10, q7, q13\n    vmul.f32    q7, q7, q10          // q7 <- 1/q13\n\n    vmul.f32    q10, q4, q7\n\n    vstmia      r0!, { q10 }\n\n    subs        r1, #4;\n    bne         .loop\n\n.return:\n    vpop        { q4-q7 }\n    bx          lr\n\n.coeffs_num:\n    .float -8.9                     // low\n    .float 8.9                      // high\n    .float -8.488492677e-14         // alpha_13\n    .float 5.277853000e-11\n\n    .float -2.022500419e-8\n    .float 0.00001115424833\n    .float 0.003103950131\n    .float 0.1308400453\n\n    .float 0.9999999934\n    .float 0.0002546136580          // beta_6\n    .float 0.02449515379\n    .float 0.4641733162\n\n    .float 1.0\n    .float 0                        // padding\n    .float 0                        // padding\n    .float 0                        // padding\n"
  },
  {
    "path": "linalg/arm32/armv7neon/dispatcher.tmpliq",
    "content": "// vim: ft=arm\n\n.non_linear:\n\n.non_linear_loop_entry:\n    sub     r0, #20\n\n.non_linear_loop:\n    add     r0, #20\n    ldm     r0, { r2, r3, r4, r5, r6 }\n\n    cmp     r2, #{{ jump_table | size }}\n    movgt   r2, #{{ jump_table | size }}\n    cmp     r2, #0\n    movlt   r2, #{{ jump_table | size }}\n\n    add     pc, pc, r2, LSL#2\n    nop     // pc in Rn above is start of the add instruction + 8, hence a nop is needed\n            // This is A32 asm, for T32/Thump2 use nop.w and b.w to avoid problems.\n{% for j in jump_table %}\n    b .{{j}}\n{% endfor %}\n    b .unsupported\n\n\n.unsupported:\n    mov         r0,     #1\n    b           .return\n\n.done:\n    mov         r0,     #0\n    b           .return\n\n.clear:\n{% for r in (4..15) %}\n    veor    q{{r}}, q{{r}}, q{{r}}\n{% endfor %}\n    b           .non_linear_loop\n"
  },
  {
    "path": "linalg/arm32/armvfpv2/armvfpv2_mmm_f32_4x4.tmpl",
    "content": "// vim: ft=arm\n\n    .arm\n    .text\n    .global armvfpv2_mmm_f32_4x4_{{suffix}}\n    .type armvfpv2_mmm_f32_4x4_{{suffix}}, %function\n\n// C tile:\n\n//  s16 s20 s24 s28\n//  s17 s21 s25 s29\n//  s18 s22 s26 s30\n//  s19 s23 s27 s31\n\n// packed A: (2x4) alternating between (s0-s3) and (s4-s7)\n// packed B: (2x4) alternating between (s8-s11) and (s12-15)\n\n// all vfp registers in use.\n\narmvfpv2_mmm_f32_4x4_{{suffix}}:\n\n/*\n    pld [r1]\n    pld [r1, #8]\n    pld [r2]\n    pld [r2, #8]\n*/\n\n    push        { r4-r12 }               // no lr (we're a leaf), no fp. #24 bytes\n\n    ldr         r8, [sp, #28]\n    ldr         r9, [sp, #24]\n\n//  r8=rsc, r9=csc\n\n    vmrs        r6, FPSCR\n    bic         r6, r6, #0x00370000\n    vmsr        FPSCR, r6\n\n    vpush       { s16-s31 }\n\n{% include \"dispatcher.tmpliq\" %}\n\n.clear:\n    eor         r6, r6\n    vmov        s16, r6\n    vmov.f32    s17, s16\n    vmov.f32    s18, s16\n    vmov.f32    s19, s16\n    vmov.f32    s20, s16\n    vmov.f32    s21, s16\n    vmov.f32    s22, s16\n    vmov.f32    s23, s16\n    vmov.f32    s24, s16\n    vmov.f32    s25, s16\n    vmov.f32    s26, s16\n    vmov.f32    s27, s16\n    vmov.f32    s28, s16\n    vmov.f32    s29, s16\n    vmov.f32    s30, s16\n    vmov.f32    s31, s16\n    b     .non_linear_loop\n\n.add_mat_mul:\n    // r3 <- k, r4 <- a, r5 <- b\n    cmp     r3, #0\n    beq     .non_linear_loop\n\n    mov     r1, r4 // packed A ptr\n    pld     [r3]\n    pld     [r5]\n\n    .packed_packed:\n    cmp r3, #4\n    blt .packed_packed_loop_1\n\n    .packed_packed_loop_4:\n\n    // 1\n    vldmia          r1!, { s0, s1 }\n    vldmia          r5!, { s8, s9 }\n\n    vmla.f32        s16, s0, s8\n    vldmia          r1!, { s2, s3 }\n    vmla.f32        s17, s1, s8\n    vldmia          r5!, { s10, s11 }\n    vmla.f32        s18, s2, s8\n    vmla.f32        s19, s3, s8\n\n    vmla.f32        s20, s0, s9\n    vmla.f32        s21, s1, s9\n    vmla.f32        s22, s2, s9\n    vmla.f32        s23, s3, s9\n\n    vldmia          r1!, { s4-s7 }\n    vmla.f32        s24, s0, s10\n    vmla.f32        s25, s1, s10\n    vmla.f32        s26, s2, s10\n    vmla.f32        s27, s3, s10\n\n    vldmia          r5!, { s12-s15 }\n    vmla.f32        s28, s0, s11\n    vmla.f32        s29, s1, s11\n    vmla.f32        s30, s2, s11\n    vmla.f32        s31, s3, s11\n\n    // 2\n    vmla.f32        s16, s4, s12\n    vmla.f32        s17, s5, s12\n    vmla.f32        s18, s6, s12\n    vmla.f32        s19, s7, s12\n\n    vldmia          r1!, { s0-s3 }\n\n    vmla.f32        s20, s4, s13\n    vmla.f32        s21, s5, s13\n    vmla.f32        s22, s6, s13\n    vmla.f32        s23, s7, s13\n\n    vldmia          r5!, { s8-s11 }\n\n    vmla.f32        s24, s4, s14\n    vmla.f32        s25, s5, s14\n    vmla.f32        s26, s6, s14\n    vmla.f32        s27, s7, s14\n\n    vmla.f32        s28, s4, s15\n    vmla.f32        s29, s5, s15\n    vmla.f32        s30, s6, s15\n    vmla.f32        s31, s7, s15\n\n    // 3\n    vmla.f32        s16, s0, s8\n    vmla.f32        s17, s1, s8\n    vmla.f32        s18, s2, s8\n    vmla.f32        s19, s3, s8\n\n    vldmia          r1!, { s4-s7 }\n\n    vmla.f32        s20, s0, s9\n    vmla.f32        s21, s1, s9\n    vmla.f32        s22, s2, s9\n    vmla.f32        s23, s3, s9\n\n    vldmia          r5!, { s12-s15 }\n\n    vmla.f32        s24, s0, s10\n    vmla.f32        s25, s1, s10\n    vmla.f32        s26, s2, s10\n    vmla.f32        s27, s3, s10\n\n    pld [r1]\n\n    vmla.f32        s28, s0, s11\n    vmla.f32        s29, s1, s11\n    vmla.f32        s30, s2, s11\n    vmla.f32        s31, s3, s11\n\n    pld [r6]\n\n    // 4\n    vmla.f32        s16, s4, s12\n    vmla.f32        s17, s5, s12\n    vmla.f32        s18, s6, s12\n    vmla.f32        s19, s7, s12\n\n    vmla.f32        s20, s4, s13\n    vmla.f32        s21, s5, s13\n    vmla.f32        s22, s6, s13\n    vmla.f32        s23, s7, s13\n\n    vmla.f32        s24, s4, s14\n    vmla.f32        s25, s5, s14\n    vmla.f32        s26, s6, s14\n    vmla.f32        s27, s7, s14\n\n    vmla.f32        s28, s4, s15\n    vmla.f32        s29, s5, s15\n    vmla.f32        s30, s6, s15\n    vmla.f32        s31, s7, s15\n\n    sub r3, r3, #4\n    cmp r3, #4\n    bge .packed_packed_loop_4\n\n    cmp r3, #0\n    beq .non_linear_loop\n\n    .packed_packed_loop_1:\n\n    vldmia          r1!, { s0, s1 }\n    vldmia          r5!, { s8, s9 }\n\n    vmla.f32        s16, s0, s8\n    vldmia          r1!, { s2, s3 }\n    vmla.f32        s17, s1, s8\n    vldmia          r5!, { s10, s11 }\n    vmla.f32        s18, s2, s8\n    vmla.f32        s19, s3, s8\n\n    vmla.f32        s20, s0, s9\n    vmla.f32        s21, s1, s9\n    vmla.f32        s22, s2, s9\n    vmla.f32        s23, s3, s9\n\n    vmla.f32        s24, s0, s10\n    vmla.f32        s25, s1, s10\n    vmla.f32        s26, s2, s10\n    vmla.f32        s27, s3, s10\n\n    vmla.f32        s28, s0, s11\n    vmla.f32        s29, s1, s11\n    vmla.f32        s30, s2, s11\n    vmla.f32        s31, s3, s11\n\n    subs r3, r3, #1\n    bne .packed_packed_loop_1\n\n    b .non_linear_loop\n\n.add_unicast:\n    {% for col in (0..3) %}\n        mov         r8, r3\n        {% for reg in (0..3) %}\n            vldr            s0, [ r8 ]\n            vadd.f32        s{{col|times:4|plus:reg|plus:16}}, s{{col|times:4|plus:reg|plus:16}}, s0\n            {% if reg < 3 %}\n                add         r8, r8, r4\n            {% endif %}\n        {% endfor %}\n        {% if col < 3 %}\n            add r3, r3, r5\n        {% endif %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.scalar_min:\n    vmov            s0, r3\n    {% for reg in (16..31) %}\n        vcmp.f32        s{{reg}}, s0\n        vmrs            apsr_nzcv, fpscr\n        vmovge          s{{reg}}, s0\n    {% endfor %}\n\n    b .non_linear_loop\n\n.scalar_max:\n    vmov            s0, r3\n    {% for reg in (16..31) %}\n        vcmp.f32        s{{reg}}, s0\n        vmrs            apsr_nzcv, fpscr\n        vmovle          s{{reg}}, s0\n    {% endfor %}\n\n    b .non_linear_loop\n\n.scalar_add:\n    vmov            s0, r3\n    {% for s in (16..31) %}\n        vadd.f32    s{{s}}, s{{s}}, s0\n    {% endfor %}\n\n    b .non_linear_loop\n\n.scalar_mul:\n    vmov            s0, r3\n    {% for s in (16..31) %}\n        vmul.f32    s{{s}}, s{{s}}, s0\n    {% endfor %}\n\n    b .non_linear_loop\n\n.scalar_sub:\n    vmov            s0, r3\n    {% for s in (16..31) %}\n        vsub.f32    s{{s}}, s0, s{{s}}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.scalar_sub_flipped:\n    vmov            s0, r3\n    {% for s in (16..31) %}\n        vsub.f32    s{{s}}, s{{s}}, s0\n    {% endfor %}\n\n    b .non_linear_loop\n\n.leaky_relu:\n    vmov            s0, r3\n    {% for reg in (16..31) %}\n        vmul.f32        s1, s0, s{{reg}}\n        vcmp.f32        s{{reg}}, #0\n        vmrs            apsr_nzcv, fpscr\n        vmovlt          s{{reg}}, s1\n    {% endfor %}\n    b .non_linear_loop\n\n.per_row_min:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            {%capture s%}s{{col|times:4|plus:row|plus:16}}{%endcapture%}\n            vcmp.f32        {{s}}, s{{row}}\n            vmrs            apsr_nzcv, fpscr\n            vmovge          {{s}}, s{{row}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_row_max:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            {%capture s%}s{{col|times:4|plus:row|plus:16}}{%endcapture%}\n            vcmp.f32        {{s}}, s{{row}}\n            vmrs            apsr_nzcv, fpscr\n            vmovlt          {{s}}, s{{row}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_row_add:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            vadd.f32    s{{col|times:4|plus:row|plus:16}}, s{{col|times:4|plus:row|plus:16}}, s{{row}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_row_mul:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            vmul.f32    s{{col|times:4|plus:row|plus:16}}, s{{col|times:4|plus:row|plus:16}}, s{{row}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_row_sub:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            vsub.f32    s{{col|times:4|plus:row|plus:16}}, s{{row}}, s{{col|times:4|plus:row|plus:16}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_row_sub_flipped:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            vsub.f32    s{{col|times:4|plus:row|plus:16}}, s{{col|times:4|plus:row|plus:16}}, s{{row}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_col_min:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            {%capture s%}s{{col|times:4|plus:row|plus:16}}{%endcapture%}\n            vcmp.f32        {{s}}, s{{col}}\n            vmrs            apsr_nzcv, fpscr\n            vmovge          {{s}}, s{{col}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_col_max:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            {%capture s%}s{{col|times:4|plus:row|plus:16}}{%endcapture%}\n            vcmp.f32        {{s}}, s{{col}}\n            vmrs            apsr_nzcv, fpscr\n            vmovlt          {{s}}, s{{col}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_col_add:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            vadd.f32    s{{col|times:4|plus:row|plus:16}}, s{{col|times:4|plus:row|plus:16}}, s{{col}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_col_mul:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            vmul.f32    s{{col|times:4|plus:row|plus:16}}, s{{col|times:4|plus:row|plus:16}}, s{{col}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_col_sub:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            vsub.f32    s{{col|times:4|plus:row|plus:16}}, s{{col}}, s{{col|times:4|plus:row|plus:16}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.per_col_sub_flipped:\n    vldm    r3, {s0, s1, s2, s3}\n    {% for row in (0..3) %}\n        {% for col in (0..3) %}\n            vsub.f32    s{{col|times:4|plus:row|plus:16}}, s{{col|times:4|plus:row|plus:16}}, s{{col}}\n        {% endfor %}\n    {% endfor %}\n\n    b .non_linear_loop\n\n.add_row_col_products:\n    vldmia          r3!, { s0, s1 }\n    vldmia          r4!, { s8, s9 }\n\n    vmla.f32        s16, s0, s8\n    vldmia          r3!, { s2, s3 }\n    vmla.f32        s17, s1, s8\n    vldmia          r4!, { s10, s11 }\n    vmla.f32        s18, s2, s8\n    vmla.f32        s19, s3, s8\n\n    vmla.f32        s20, s0, s9\n    vmla.f32        s21, s1, s9\n    vmla.f32        s22, s2, s9\n    vmla.f32        s23, s3, s9\n\n    vmla.f32        s24, s0, s10\n    vmla.f32        s25, s1, s10\n    vmla.f32        s26, s2, s10\n    vmla.f32        s27, s3, s10\n\n    vmla.f32        s28, s0, s11\n    vmla.f32        s29, s1, s11\n    vmla.f32        s30, s2, s11\n    vmla.f32        s31, s3, s11\n\n    b .non_linear_loop\n\n.store:\n    {% for col in (0..3) %}\n        mov         r8, r3\n        {% for reg in (0..3) %}\n            fsts        s{{col|times:4|plus:reg|plus:16}}, [ r8 ]\n            {% if reg < 3 %}\n                add         r8, r8, r4\n            {% endif %}\n        {% endfor %}\n        {% if col < 3 %}\n            add r3, r3, r5\n        {% endif %}\n    {% endfor %}\n\n    mov         r0,     #0\n    b   .return\n\n.load_tile:\n    vldmia       r3!, { s16-s31 }\n    b .non_linear_loop\n\n.q_scale:\n.q_shl:\n.q_shr:\n    b   .unsupported\n\n.return:\n    vpop        { s16-s31 }\n    pop         { r4-r12 }\n\n    bx          lr\n\n"
  },
  {
    "path": "linalg/arm32/armvfpv2/dispatcher.tmpliq",
    "content": "// vim: ft=arm\n\n.non_linear:\n\n.non_linear_loop_entry:\n    sub     r0, #20\n\n.non_linear_loop:\n    add     r0, #20\n    ldm     r0, { r2, r3, r4, r5, r6 }\n\n    cmp     r2, #{{ jump_table | size }}\n    movgt   r2, #{{ jump_table | size }}\n    cmp     r2, #0\n    movlt   r2, #{{ jump_table | size }}\n\n    add     pc, pc, r2, LSL#2\n    nop     // pc in Rn above is start of the add instruction + 8, hence a nop is needed\n            // This is A32 asm, for T32/Thump2 use nop.w and b.w to avoid problems.\n{% for j in jump_table %}\n        b .{{j}}\n{% endfor %}\n    b .unsupported\n\n.unsupported:\n    mov         r0,     #1\n    b           .return\n\n.done:\n    mov         r0,     #0\n    b           .return\n\n"
  },
  {
    "path": "linalg/arm64/apple_amx/apple_amx_mmm_f16_64x1.tmpl",
    "content": "// vim: ft=arm\n.text\n.align 4\n\n/* Z: 32x1\n z0[0] ..  z0[15] z1[0] .. z1[15]\n*/\n    \n\n.global {{G}}apple_amx_mmm_f16_64x1_{{suffix}}\n{{G}}apple_amx_mmm_f16_64x1_{{suffix}}:\n\n{{ AMX_SET }}\n\n    // set x1 to a 128 bytes aligned block for loads\n    mov x1, sp\n    lsr x1, x1, #7\n    lsl x1, x1, #7\n    sub x1, x1, 128\n\n{% include \"dispatcher.tmpliq\" %}\n\n.leaky_relu:\n.q_scale:\n.q_shl:\n.q_shr:\n    b .unsupported\n\n.add_mat_mul:\n\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x4, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    orr x4, x4, {{ 0|setting:62 }}  // load a pair of A\n\n    mov x5, {{ 0|setting:43 }}      // f16\n    orr x5, x5, {{ 0|setting:38 }}  // Broadcast Y\n\n    orr x6, x5, {{ 0|setting:20 }}  // z offset\n    orr x6, x6, {{ 0|setting:16 }}  // x offset\n\n    cmp         x3, #32\n    blt         .packed_packed_loop_1\n\n    mov x9, {{0|setting:32}}        // Y broadcast offset += 1\n\n    .packed_packed_loop_32:\n        mov x7, x5\n        mov x8, x6\n        {% amx ldy x2 %}\n        {% for k in (0..31) %}\n            {% amx ldx x4 %}\n            add x4, x4, 128\n            {% amx vecfp x7 %}\n            {% amx vecfp x8 %}\n            add x7, x7, x9\n            add x8, x8, x9\n        {% endfor %}\n        add x2, x2, #64\n        sub x3, x3, #32\n        cmp x3, #32\n    bge .packed_packed_loop_32\n\n    cmp x3, #0\n    beq .non_linear_loop\n\n    .packed_packed_loop_1:\n        ldr w7, [x2], #2\n        str w7, [x1]\n        {% amx ldx x4 %}\n        {% amx ldy x1 %}\n        {% amx vecfp x5 %}\n        {% amx vecfp x6 %}\n        add x4, x4, 128\n        subs x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b .non_linear_loop\n\n.clear:\n    // top left\n    eor x2, x2, x2\n    orr x2, x2, {{ 0|setting:27 }}\n    orr x2, x2, {{ 0|setting:28 }}\n    orr x2, x2, {{ 0|setting:29 }}  // Z = 0\n    {% amx fma32 x2 %}\n\n    // top right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    {% amx fma32 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:21 }}  // Z row = 3\n    {% amx fma32 x2 %}\n\n    // bottom left\n    eor x2, x2, {{ 0|setting:20 }}  // Z row = 2\n    {% amx fma32 x2 %}\n\n    b .non_linear_loop\n\n.per_col_sub:\n\n    // performs a unary neg on Z\n    eor x2, x2, x2                      // X[0] = Z[0]\n    // extr[hxyz] is suport confusing\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    {% amx extrx x2 %}\n    {% amx fms16 x4 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    add x4, x4, {{0|setting:20}}    // next Z row\n    {% amx extrx x2 %}              // extr[hxyz] is confusing\n    {% amx fms16 x4 %}\n\n    // continue\n\n.per_col_add:\n    ldr         x2, [x0, #8]\n\n    // broadcast value to x0\n    ld1         { v0.h }[0], [x2]\n    dup         v0.8h, v0.h[0]\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    sub         x1, x1, #64\n\n    {% amx ldx x1 %} // load into x0 by default\n\n    mov x2, {{ 0|setting:28 }}      // z += y\n    {% amx fma16 x2 %}\n\n    orr x2, x2, {{ 0|setting:20 }}  // target is now z1\n    {% amx fma16 x2 %}\n\n    b .non_linear_loop\n\n.per_col_sub_flipped:\n    ldr         x2, [x0, #8]\n\n    // broadcast value to x0\n    ld1         { v0.h }[0], [x2]\n    dup         v0.8h, v0.h[0]\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    sub x1, x1, #64\n\n    {% amx ldx x1 %} // load into x0 by default\n\n    mov x2, {{ 0|setting:28 }}      // z += y\n    {% amx fms16 x2 %}\n\n    orr x2, x2, {{ 0|setting:20 }}  // target is now z1\n    {% amx fms16 x2 %}\n\n    b .non_linear_loop\n\n.per_row_sub_flipped:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2], #64\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1], #64\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2]\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    mov x2, {{ 0|setting:63 }}      // vector mode\n    orr x2, x2, {{ 0|setting:29 }}  // z -= y\n\n    // top left\n    {% amx fms16 x2 %}\n\n    // bottom left\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fms16 x2 %}\n\n    b .non_linear_loop\n\n.per_row_sub:\n    // performs a unary neg on Z\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    {% amx extrx x2 %}\n    {% amx fms16 x4 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    add x4, x4, {{0|setting:20}}    // next Z row\n    {% amx extrx x2 %}\n    {% amx fms16 x4 %}\n\n    // continue\n\n.per_row_add:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2], #64\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1], #64\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2]\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    mov x2, {{ 0|setting:63 }}      // vector mode\n    orr x2, x2, {{ 0|setting:29 }}  // z += y\n\n    // top left\n    {% amx fma16 x2 %}\n\n    // bottom left\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fma16 x2 %}\n\n    b .non_linear_loop\n\n.per_row_min:\n    mov x2, 5\n    b .per_row_min_max\n.per_row_max:\n    mov x2, 7\n.per_row_min_max:\n    ldr         x5, [x0, #8]\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x5], #64\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1], #64\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x5]\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    sub x1, x1, #64\n\n    orr x5, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldx x5 %}\n\n    lsl x2, x2, 47                  // max(x,z) (or min)\n    orr x2, x2, {{ 0|setting:44 }}  // f32\n    {% amx vecfp x2 %}\n\n    orr x2, x2, {{ 0|setting:16 }}  // x1\n    orr x2, x2, {{ 0|setting:20 }}  // z1\n    {% amx vecfp x2 %}\n\n    b .non_linear_loop\n\n.per_col_min:\n    mov x2, 5\n    b .per_col_min_max\n.per_col_max:\n    mov x2, 7\n.per_col_min_max:\n    ldr         x4, [x0, #8]\n\n    // broadcast value to x0\n    ld1         { v0.h }[0], [x4]\n    dup         v0.8h, v0.h[0]\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    sub         x1, x1, #64\n\n    {% amx ldx x1 %}\n\n    lsl x2, x2, 47                  // max(x,z) (or min)\n    orr x2, x2, {{ 0|setting:43 }}  // f32\n\n    {% amx vecfp x2 %}\n    orr x2, x2, {{ 0|setting:20 }}  // z offset\n    {% amx vecfp x2 %}\n\n    b .non_linear_loop\n\n.per_col_mul:\n    ldr         x4, [x0, #8]\n\n    // broadcast value to y0\n    ld1         { v0.h }[0], [x4]\n    dup         v0.8h, v0.h[0]\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    st1         { v0.8h }, [x1], #16\n    sub         x1, x1, #64\n\n    {% amx ldy x1 %}\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n    {% amx extrx x2 %}\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n    {% amx fma16 x4 %}\n    orr x2, x2, {{ 0|setting:20 }}      // Z1\n    {% amx extrx x2 %}\n    orr x4, x4, {{ 0|setting:20 }}      // Z1\n    {% amx fma16 x4 %}\n\n    b .non_linear_loop\n\n.per_row_mul:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2], #64\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1], #64\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2]\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}      // pair\n    {% amx ldy x2 %}\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n    {% amx extrx x2 %}\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n    {% amx fma16 x4 %}\n    orr x2, x2, {{ 0|setting:20 }}      // Z1\n    {% amx extrx x2 %}\n    orr x4, x4, {{ 0|setting:20 }}      // Z1\n    orr x4, x4, {{ 0|setting:6 }}       // Y1\n    {% amx fma16 x4 %}\n\n    b .non_linear_loop\n\n.scalar_sub:\n    // performs a unary neg on Z, then go to scalar_add\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n    {% amx extrx x2 %}\n    {% amx fms16 x4 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    add x4, x4, {{0|setting:20}}    // next Z row\n    {% amx extrx x2 %}\n    {% amx fms16 x4 %}\n\n    // continue on purpose\n\n.scalar_add:\n    ldr         w5, [x0, #8]\n\n    fmov        h0, w5\n    dup         v0.8h, v0.h[0]\n    dup         v1.8h, v0.h[0]\n    dup         v2.8h, v0.h[0]\n    dup         v3.8h, v0.h[0]\n\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    mov x2, {{ 0|setting:28 }}          // Z+=X \n    {% amx fma16 x2 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    {% amx fma16 x2 %}\n    b .non_linear_loop\n\n.scalar_sub_flipped:\n    ldr         w5, [x0, #8]\n    fmov        s0, w5\n    dup         v0.8h, v0.h[0]\n    dup         v1.8h, v0.h[0]\n    dup         v2.8h, v0.h[0]\n    dup         v3.8h, v0.h[0]\n\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    mov x2, {{ 0|setting:28 }}          // Z-=X \n    {% amx fms16 x2 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    {% amx fms16 x2 %}\n    b .non_linear_loop\n\n.scalar_mul:\n    ldr         w5, [x0, #8]\n    fmov        h0, w5\n    dup         v0.8h, v0.h[0]\n    dup         v1.8h, v0.h[0]\n    dup         v2.8h, v0.h[0]\n    dup         v3.8h, v0.h[0]\n\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    {% amx ldy x1 %}\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n\n    {% amx extrx x2 %}\n    {% amx fma16 x4 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    add x4, x4, {{0|setting:20}}    // next Z row\n    {% amx extrx x2 %}\n    {% amx fma16 x4 %}\n\n    b .non_linear_loop\n\n.scalar_min:\n    mov x2, 5\n    b .scalar_min_max\n.scalar_max:\n    mov x2, 7\n.scalar_min_max:\n    ldr         w5, [x0, #8]\n    fmov        h0, w5\n    dup         v0.8h, v0.h[0]\n    dup         v1.8h, v0.h[0]\n    dup         v2.8h, v0.h[0]\n    dup         v3.8h, v0.h[0]\n\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    lsl x2, x2, 47\n    orr x2, x2, {{ 0|setting:43 }} // f16\n\n    {% amx vecfp x2 %}\n    add x2, x2, {{ 0|setting:20}} // next Z\n    {% amx vecfp x2 %}\n\n    b .non_linear_loop\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    {% for neon in (0..7) %}\n       {% for lane in (0..7) %}\n           ld1 { v{{neon}}.h }[{{lane}}], [x5], x6\n       {% endfor %}\n    {% endfor %}\n    mov x8, x1\n    st1 { v0.8h, v1.8h, v2.8h, v3.8h }, [x8], #64\n    st1 { v4.8h, v5.8h, v6.8h, v7.8h }, [x8], #64\n\n    orr x8, x1, {{ 0|setting:62 }}          // pair\n    {% amx ldy x8 %}\n\n    eor x2, x2, x2\n    orr x2, x2, {{ 0|setting:63 }}  // vector mode\n    orr x2, x2, {{ 0|setting:29 }}  // perform Z0+=Y0\n    {% amx fma16 x2 %}\n    orr x2, x2, {{ 0|setting:20 }}  // Z1\n    orr x2, x2, 64                  // offset Y\n    {% amx fma16 x2 %}\n    \n    b .non_linear_loop\n\n.add_row_col_products:\n    ldp         x5, x6, [x0, #8]            // a base ptr, b base ptr\n\n    ld1         { v0.h }[0], [x6]\n    st1         { v0.h }[0], [x1]\n    {% amx ldy x1 %}\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldx x2 %}\n\n    mov x2, {{ 0|setting:43 }}      // f16\n    orr x2, x2, {{ 0|setting:38 }}  // Broadcast Y\n    {% amx vecfp x2 %}\n\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    orr x2, x2, {{ 0|setting:16 }}  // X offset\n    {% amx vecfp x2 %}\n\n    b .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    ands        x8, x5, 0x7f\n    bne         .store_generic\n    cmp         x6, 4\n    bne         .store_generic\n    cmp         x7, 4\n    bne         .store_generic\n \n    orr x5, x5, {{ 0|setting:62 }}          // pair\n    {% amx stz x5 %}\n    b .non_linear_loop\n\n .store_generic:\n\n    orr x8, x1, {{ 0|setting:62 }}          // pair\n    {% amx stz x8 %}\n\n    mov x8, x1\n    ld1 { v0.8h, v1.8h, v2.8h, v3.8h }, [x8], #64\n    ld1 { v4.8h, v5.8h, v6.8h, v7.8h }, [x8], #64\n    {% for neon in (0..7) %}\n       {% for lane in (0..7) %}\n           st1 { v{{neon}}.h }[{{lane}}], [x5], x6\n       {% endfor %}\n    {% endfor %}\n    \n    b .non_linear_loop\n\n.load_tile:\n    ldr  x2, [x0, #16]                      // row major ptr\n    orr  x2, x2, {{0|setting:62}}           // load pairs\n    {% amx ldz x2 %}\n    b .non_linear_loop\n\n.return:\n{{ AMX_CLR }}\nret\n"
  },
  {
    "path": "linalg/arm64/apple_amx/apple_amx_mmm_f16_64x32.tmpl",
    "content": "// vim: ft=arm\n.text\n.align 4\n\n/* Z: 64x32 tile. each Z reg is f16x32\n    Z0\n    Z2\n    ...\n    Z62\n    \n    Z1\n    Z3\n    S63\n*/\n    \n\n.global {{G}}apple_amx_mmm_f16_64x32_{{suffix}}\n{{G}}apple_amx_mmm_f16_64x32_{{suffix}}:\n\n{{ AMX_SET }}\n\n    // set x1 to a 128 bytes aligned block for loads\n    mov x1, sp\n    lsr x1, x1, #7\n    lsl x1, x1, #7\n    sub x1, x1, 128\n\n{% include \"dispatcher.tmpliq\" %}\n\n.leaky_relu:\n.q_scale:\n.q_shl:\n.q_shr:\n    b .unsupported\n\n.add_mat_mul:\n\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x4, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    orr         x4, x4, {{0|setting:62}}    // load pairs (A)\n\n    eor         x5, x5, x5                  // top left\n\n    orr         x7, x5, {{ 0|setting:20 }}\n    orr         x7, x7, {{ 0|setting:6 }}   // bottom left\n\n    .packed_packed_loop_1:\n    {% amx ldx x2 %}\n    {% amx ldy x4 %}\n    add x2, x2, 64\n    add x4, x4, 128\n\n    {% amx fma16 x5 %}\n    {% amx fma16 x7 %}\n\n    subs x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b .non_linear_loop\n\n.clear:\n    // top left\n    eor x2, x2, x2\n    orr x2, x2, {{ 0|setting:27 }}\n    orr x2, x2, {{ 0|setting:28 }}\n    orr x2, x2, {{ 0|setting:29 }}  // Z = 0\n    {% amx fma32 x2 %}\n\n    // top right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    {% amx fma32 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:21 }}  // Z row = 3\n    {% amx fma32 x2 %}\n\n    // bottom left\n    eor x2, x2, {{ 0|setting:20 }}  // Z row = 2\n    {% amx fma32 x2 %}\n\n    mov     x3, #16\n    str     x3, [x1]\n\n    b .non_linear_loop\n\n.per_col_sub:\n\n    // performs a unary neg on Z\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    mov x6, 64\n    .per_col_sub_loop:\n        {% amx extrx x2 %}\n        {% amx fms16 x4 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n        add x4, x4, {{0|setting:20}}    // next Z row\n    subs x6, x6, 1\n    bne .per_col_sub_loop\n\n    // continue\n\n.per_col_add:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldx x1 %}\n\n    mov x2, {{ 0|setting:28 }}      // z += y\n\n    // top left\n    {% amx fma16 x2 %}\n\n    // bottom left\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 2\n    {% amx fma16 x2 %}\n\n    b .non_linear_loop\n\n.per_col_sub_flipped:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2]\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n\n    {% amx ldx x1 %}\n\n    mov x2, {{ 0|setting:28 }}      // z += y\n\n    {% amx fms16 x2 %}\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    {% amx fms16 x2 %}\n\n    b .non_linear_loop\n\n.per_row_sub_flipped:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2], #64\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1], #64\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2]\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    mov x2, {{ 0|setting:29 }}      // z += y\n\n    // top left\n    {% amx fms16 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 3\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fms16 x2 %}\n\n    b .non_linear_loop\n\n.per_row_sub:\n    // performs a unary neg on Z\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    mov x6, 64\n    .per_row_sub_loop:\n        {% amx extrx x2 %}\n        {% amx fms16 x4 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n        add x4, x4, {{0|setting:20}}    // next Z row\n    subs x6, x6, 1\n    bne .per_row_sub_loop\n\n    // continue\n\n.per_row_add:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2], #64\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1], #64\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2]\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    mov x2, {{ 0|setting:29 }}      // z += y\n\n    // top left\n    {% amx fma16 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fma16 x2 %}\n\n    b .non_linear_loop\n\n.per_row_min:\n    mov x2, 5\n    b .per_row_min_max\n.per_row_max:\n    mov x2, 7\n.per_row_min_max:\n    ldr         x5, [x0, #8]\n\n    add x6, x5, 64\n\n    lsl x2, x2, 47                  // max(x,z) (or min)\n    orr x2, x2, {{ 0|setting:43 }}  // f16\n\n    orr x8, x2, {{ 0|setting:20 }}  // bottom left\n\n    mov x4, 32\n    .loop_per_row_max:\n        // top half\n        ld1         { v0.h }[0], [x5], #2\n        dup         v0.8h, v0.h[0]\n        dup         v1.8h, v0.h[0]\n        dup         v2.8h, v0.h[0]\n        dup         v3.8h, v0.h[0]\n        st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n\n        {% amx ldx x1 %}\n        {% amx vecfp x2 %}\n\n        add x2, x2, {{ 0|setting:21 }}\n\n        // bottom half\n        ld1         { v0.h }[0], [x6], #2\n        dup         v0.8h, v0.h[0]\n        dup         v1.8h, v0.h[0]\n        dup         v2.8h, v0.h[0]\n        dup         v3.8h, v0.h[0]\n        st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n\n        {% amx ldx x1 %}\n        {% amx vecfp x8 %}\n\n        add x8, x8, {{ 0|setting:21 }}\n\n    subs x4, x4, 1\n    bne .loop_per_row_max\n\n    b .non_linear_loop\n\n.per_col_min:\n    mov x2, 5\n    b .per_col_min_max\n.per_col_max:\n    mov x2, 7\n.per_col_min_max:\n    ldr         x4, [x0, #8]\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x4]\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    {% amx ldx x1 %}\n\n    lsl x2, x2, 47                  // max(x,z) (or min)\n    orr x2, x2, {{ 0|setting:43 }}  // f16\n\n    mov x4, 64\n    .loop_per_col_max:\n        {% amx vecfp x2 %}\n        add x2, x2, {{ 0|setting:20 }}\n    subs x4, x4, 1\n    bne .loop_per_col_max\n\n    b .non_linear_loop\n\n.per_col_mul:\n    ldr         x4, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x4]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldy x1 %}\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n\n    mov x6, 64\n    .loop_per_col_mul:\n        {% amx extrx x2 %}\n        {% amx fma16 x4 %}\n        add x2, x2, {{0|setting:20}}\n        add x4, x4, {{0|setting:20}}\n    subs x6, x6, 1\n    bne .loop_per_col_mul\n\n    b .non_linear_loop\n\n.per_row_mul:\n    ldr         x14, [x0, #8]\n    add         x15, x14, 64\n\n    // extrx\n    eor x2, x2, x2                      // X[0] = Z[0] (top left)\n\n    eor x4, x4, x4\n    orr x4, x4, {{0|setting:20}}        // X[0] = Z[1] (bottom left)\n\n    // fma16\n    eor x6, x6, x6\n    orr x6, x6, {{0|setting:63}}        // vector mode\n    orr x6, x6, {{0|setting:27}}        // Z=X*Y       Z[0]=X[0]*Y[0]\n\n    orr x8, x6, {{0|setting:20}}        // Z[1]\n\n    mov x10, 32\n    .loop_per_row_mul:\n        // top\n        ld1         { v0.h }[0], [x14], #2\n        dup         v0.8h, v0.h[0]\n        dup         v1.8h, v0.h[0]\n        dup         v2.8h, v0.h[0]\n        dup         v3.8h, v0.h[0]\n        st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n\n        {% amx ldy x1 %}\n        {% amx extrx x2 %}\n        {% amx fma16 x6 %}\n\n        add x2, x2, {{ 0|setting:21 }}\n        add x6, x6, {{ 0|setting:21 }}\n\n        // bottom\n        ld1         { v0.h }[0], [x15], #2\n        dup         v0.8h, v0.h[0]\n        dup         v1.8h, v0.h[0]\n        dup         v2.8h, v0.h[0]\n        dup         v3.8h, v0.h[0]\n        st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n\n        {% amx ldy x1 %}\n        {% amx extrx x4 %}\n        {% amx fma16 x8 %}\n\n        add x4, x4, {{ 0|setting:21 }}\n        add x8, x8, {{ 0|setting:21 }}\n\n    subs x10, x10, 1\n    bne .loop_per_row_mul\n\n    b .non_linear_loop\n\n.scalar_sub:\n    // performs a unary neg on Z, then go to scalar_add\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    mov x6, 64\n    .scalar_sub_loop:\n        {% amx extrx x2 %}\n        {% amx fms16 x4 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n        add x4, x4, {{0|setting:20}}    // next Z row\n    subs x6, x6, 1\n    bne .scalar_sub_loop\n\n    // continue on purpose\n\n.scalar_add:\n    ldr         w5, [x0, #8]\n\n    fmov        h0, w5\n    dup         v0.8h, v0.h[0]\n    dup         v1.8h, v0.h[0]\n    dup         v2.8h, v0.h[0]\n    dup         v3.8h, v0.h[0]\n\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    mov x2, {{ 0|setting:28 }}          // Z+=X \n    {% amx fma16 x2 %}\n    add x2, x2, {{0|setting:20}}    // Z1\n    {% amx fma16 x2 %}\n    b .non_linear_loop\n\n.scalar_sub_flipped:\n    ldr         w5, [x0, #8]\n\n    fmov        h0, w5\n    dup         v0.8h, v0.h[0]\n    dup         v1.8h, v0.h[0]\n    dup         v2.8h, v0.h[0]\n    dup         v3.8h, v0.h[0]\n\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    {% amx ldx x1 %}    // load 32 values\n\n    mov x2, {{ 0|setting:28 }}          // Z-=X \n    {% amx fms16 x2 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    {% amx fms16 x2 %}\n    b .non_linear_loop\n\n.scalar_mul:\n    ldr         w5, [x0, #8]\n\n    fmov        h0, w5\n    dup         v0.8h, v0.h[0]\n    dup         v1.8h, v0.h[0]\n    dup         v2.8h, v0.h[0]\n    dup         v3.8h, v0.h[0]\n\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    {% amx ldy x1 %}    // load 32 values\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n\n    mov x6, 64\n    .scalar_mul_loop:\n        {% amx extrx x2 %}\n        {% amx fma16 x4 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n        add x4, x4, {{0|setting:20}}    // next Z row\n    subs x6, x6, 1\n    bne .scalar_mul_loop\n\n    b .non_linear_loop\n\n.scalar_min:\n    mov x2, 5\n    b .scalar_min_max\n.scalar_max:\n    mov x2, 7\n.scalar_min_max:\n    ldr         w5, [x0, #8]\n\n    fmov        h0, w5\n    dup         v0.8h, v0.h[0]\n    dup         v1.8h, v0.h[0]\n    dup         v2.8h, v0.h[0]\n    dup         v3.8h, v0.h[0]\n\n    st1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    lsl x2, x2, 47\n    orr x2, x2, {{ 0|setting:43 }} // f32\n\n    mov x3, 64\n    .loop_scalar_max:\n        add x2, x2, {{ 0|setting:20}} // next Z\n        {% amx vecfp x2 %}\n        subs x3, x3, 1\n        bne .loop_scalar_max\n\n    b .non_linear_loop\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    mov x3, 0                               // x3 is the row\n    .loop_load:\n        // z reg is (row % 32) * 2 + (row / 32)\n        and x9, x3, 0x1f\n        lsl x9, x9, 1\n        lsr x10, x3, 5\n        add x9, x9, x10\n\n        mov x4, x5\n        {% for neon in (0..3) %}\n            {% for lane in (0..7) %}\n                ld1 { v{{neon}}.h }[{{lane}}], [x4], x7\n            {% endfor %}\n        {% endfor %}\n\n        st1 { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n        {% amx ldy x1 %}\n\n        lsl x2, x9, 20                  // Z register to update\n        orr x2, x2, {{ 0|setting:63 }}  // vector mode\n        orr x2, x2, {{ 0|setting:29 }}  // perform Z+=Y\n        {% amx fma16 x2 %}\n\n        add x5, x5, x6\n        add x3, x3, 1\n        cmp x3, 64\n    bne .loop_load\n\n    /*\n    mov x3, 0                               // x3 is the row\n    .loop_load:\n        and x9, x3, 0xf                     // x9 = row % 16\n        lsl x9, x9, 2                       // x9 = (row % 16) * 4\n        lsr x10, x3, 4                      // x10 = row / 16 \n        lsl x10, x10, 1                     // x10 = (row / 16) * 2\n        add x9, x9, x10                     // x9 = x9 + x10\n\n        mov x4, x5\n        {% for neon in (0..3) %}\n            {% for lane in (0..3) %}\n                ld1 { v{{neon}}.s }[{{lane}}], [x4], x7\n            {% endfor %}\n        {% endfor %}\n        st1 { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n        {% for neon in (0..3) %}\n            {% for lane in (0..3) %}\n                ld1 { v{{neon}}.s }[{{lane}}], [x4], x7\n            {% endfor %}\n        {% endfor %}\n        st1 { v0.4s, v1.4s, v2.4s, v3.4s }, [x8]\n\n        mov x2, x1\n        orr x2, x2, {{ 0|setting:62 }} // load 32 values\n        {% amx ldy x2 %}\n\n        lsl x2, x9, 20                  // left Z register to update\n        orr x2, x2, {{ 0|setting:63 }}  // vector mode\n        orr x2, x2, {{ 0|setting:29 }}  // perform Z+=Y\n        {% amx fma32 x2 %}\n\n        add x2, x2, {{0|setting:20}}\n        orr x2, x2, 64                  // offset Y by 16 values\n        {% amx fma32 x2 %}\n\n        add x5, x5, x6\n    add x3, x3, 1\n    cmp x3, 32\n    bne .loop_load\n\n*/\n\n    b .non_linear_loop\n\n.add_row_col_products:\n    ldp         x5, x6, [x0, #8]            // a base ptr, b base ptr\n\n    add x8, x1, 64\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x6]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n\n    {% amx ldx x1 %}\n\n    // top\n    eor x2, x2, x2\n    {% amx fma16 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fma16 x2 %}\n\n    b .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x7, 2\n    bne         .store_generic\n    ands        x8, x5, 0x7f\n    bne         .store_generic\n    ands        x8, x6, 0x7f\n    bne         .store_generic\n\n    lsl x8, x6, 5\n    add x8, x8, x5                          // x8 = 32*rsc\n    orr x8, x8, {{ 0|setting:56 }}          // first to x8 is z1\n\n    mov x4, {{0|setting:57}}                // Zreg += 2\n    add x4, x4, x6                          // +rsc\n\n    mov x3, 32\n    .loop_store_direct:\n        {% amx stz x5 %}\n        {% amx stz x8 %}\n        add x5, x5, x4\n        add x8, x8, x4\n    subs x3, x3, 1\n    bne .loop_store_direct\n\n    b .non_linear_loop\n\n.store_generic:\n\n    mov x3, 0                               // row id\n    .loop_store:\n        // z reg is (row % 32) * 2 + (row / 32)\n        and x9, x3, 0x1f\n        lsl x9, x9, 1\n        lsr x10, x3, 5\n        add x9, x9, x10\n\n        lsl x2, x9, 56\n        orr x2, x2, x1\n        {% amx stz x2 %}                            // f16 x 32\n\n        ld1 { v0.8h, v1.8h, v2.8h, v3.8h }, [x1]\n\n        mov x4, x5\n        {% for neon in (0..3) %}\n            {% for lane in (0..7) %}\n                st1 { v{{neon}}.h }[{{lane}}], [x4], x7\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x6\n\n        add x3, x3, 1\n        cmp x3, 64\n    bne .loop_store\n    b .non_linear_loop\n\n.load_tile:\n    ldr  x2, [x0, #16]                      // row major ptr\n    orr  x3, x2, {{0|setting:56}}\n    add  x3, x3, #2048\n    \n    mov  x4, {{0|setting:57}}               // z+=2\n    add  x4, x4, #64\n\n    mov x8, 32\n    .loop_load_tile:\n        {% amx ldz x2 %}\n        {% amx ldz x3 %}\n        add x2, x2, x4\n        add x3, x3, x4\n    subs x8, x8, 1\n    bne .loop_load_tile\n\n    b .non_linear_loop\n   \n.return:\n{{ AMX_CLR }}\nret\n"
  },
  {
    "path": "linalg/arm64/apple_amx/apple_amx_mmm_f32_32x1.tmpl",
    "content": "// vim: ft=arm\n.text\n.align 4\n\n/* Z: 32x1\n z0[0] ..  z0[15] z1[0] .. z1[15]\n*/\n    \n\n.global {{G}}apple_amx_mmm_f32_32x1_{{suffix}}\n{{G}}apple_amx_mmm_f32_32x1_{{suffix}}:\n\n{{ AMX_SET }}\n\n    // set x1 to a 128 bytes aligned block for loads\n    mov x1, sp\n    lsr x1, x1, #7\n    lsl x1, x1, #7\n    sub x1, x1, 128\n\n{% include \"dispatcher.tmpliq\" %}\n\n.leaky_relu:\n.q_scale:\n.q_shl:\n.q_shr:\n    b .unsupported\n\n.add_mat_mul:\n\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x4, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    orr x4, x4, {{ 0|setting:62 }}  // load a pair of A\n\n    mov x5, {{ 0|setting:44 }}      // f32\n    orr x5, x5, {{ 0|setting:38 }}  // Broadcast Y\n\n    orr x6, x5, {{ 0|setting:20 }}  // z offset\n    orr x6, x6, {{ 0|setting:16 }}  // x offset\n\n    cmp         x3, #16\n    blt         .packed_packed_loop_1\n\n    mov x9, {{0|setting:32}}        // Y broadcast offset += 1\n\n    .packed_packed_loop_16:\n        mov x7, x5\n        mov x8, x6\n        {% amx ldy x2 %}\n        {% for k in (0..15) %}\n            {% amx ldx x4 %}\n            add x4, x4, 128\n            {% amx vecfp x7 %}\n            {% amx vecfp x8 %}\n            add x7, x7, x9\n            add x8, x8, x9\n        {% endfor %}\n        add x2, x2, #64\n        sub x3, x3, #16\n        cmp x3, #16\n    bge .packed_packed_loop_16\n\n    cmp x3, #0\n    beq .non_linear_loop\n\n    .packed_packed_loop_1:\n        ldr w7, [x2], #4\n        str w7, [x1]\n        {% amx ldx x4 %}\n        {% amx ldy x1 %}\n        {% amx vecfp x5 %}\n        {% amx vecfp x6 %}\n        add x4, x4, 128\n        subs x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b .non_linear_loop\n\n.clear:\n    // top left\n    eor x2, x2, x2\n    orr x2, x2, {{ 0|setting:27 }}\n    orr x2, x2, {{ 0|setting:28 }}\n    orr x2, x2, {{ 0|setting:29 }}  // Z = 0\n    {% amx fma32 x2 %}\n\n    // top right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    {% amx fma32 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:21 }}  // Z row = 3\n    {% amx fma32 x2 %}\n\n    // bottom left\n    eor x2, x2, {{ 0|setting:20 }}  // Z row = 2\n    {% amx fma32 x2 %}\n\n    b .non_linear_loop\n\n.per_col_sub:\n\n    // performs a unary neg on Z\n    eor x2, x2, x2                      // X[0] = Z[0]\n    // extr[hxyz] is suport confusing\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    {% amx extrx x2 %}\n    {% amx fms32 x4 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    add x4, x4, {{0|setting:20}}    // next Z row\n    {% amx extrx x2 %}              // extr[hxyz] is confusing\n    {% amx fms32 x4 %}\n\n    // continue\n\n.per_col_add:\n    ldr         x2, [x0, #8]\n\n    // broadcast value to x0\n    ld1         { v0.s }[0], [x2]\n    dup         v0.4s, v0.s[0]\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    sub         x1, x1, #64\n\n    {% amx ldx x1 %} // load into x0 by default\n\n    mov x2, {{ 0|setting:28 }}      // z += y\n    {% amx fma32 x2 %}\n\n    orr x2, x2, {{ 0|setting:20 }}  // target is now z1\n    {% amx fma32 x2 %}\n\n    b .non_linear_loop\n\n.per_col_sub_flipped:\n    ldr         x2, [x0, #8]\n\n    // broadcast value to x0\n    ld1         { v0.s }[0], [x2]\n    dup         v0.4s, v0.s[0]\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    sub x1, x1, #64\n\n    {% amx ldx x1 %} // load into x0 by default\n\n    mov x2, {{ 0|setting:28 }}      // z += y\n    {% amx fms32 x2 %}\n\n    orr x2, x2, {{ 0|setting:20 }}  // target is now z1\n    {% amx fms32 x2 %}\n\n    b .non_linear_loop\n\n.per_row_sub_flipped:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    mov x2, {{ 0|setting:63 }}      // vector mode\n    orr x2, x2, {{ 0|setting:29 }}  // z -= y\n\n    // top left\n    {% amx fms32 x2 %}\n\n    // bottom left\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fms32 x2 %}\n\n    b .non_linear_loop\n\n.per_row_sub:\n    // performs a unary neg on Z\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    {% amx extrx x2 %}\n    {% amx fms32 x4 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    add x4, x4, {{0|setting:20}}    // next Z row\n    {% amx extrx x2 %}\n    {% amx fms32 x4 %}\n\n    // continue\n\n.per_row_add:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    mov x2, {{ 0|setting:63 }}      // vector mode\n    orr x2, x2, {{ 0|setting:29 }}  // z += y\n\n    // top left\n    {% amx fma32 x2 %}\n\n    // bottom left\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fma32 x2 %}\n\n    b .non_linear_loop\n\n.per_row_min:\n    mov x2, 5\n    b .per_row_min_max\n.per_row_max:\n    mov x2, 7\n.per_row_min_max:\n    ldr         x5, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x5, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldx x5 %}\n\n    lsl x2, x2, 47                  // max(x,z) (or min)\n    orr x2, x2, {{ 0|setting:44 }}  // f32\n    {% amx vecfp x2 %}\n\n    orr x2, x2, {{ 0|setting:16 }}  // x1\n    orr x2, x2, {{ 0|setting:20 }}  // z1\n    {% amx vecfp x2 %}\n\n    b .non_linear_loop\n\n.per_col_min:\n    mov x2, 5\n    b .per_col_min_max\n.per_col_max:\n    mov x2, 7\n.per_col_min_max:\n    ldr         x4, [x0, #8]\n\n    // broadcast value to x0\n    ld1         { v0.s }[0], [x4]\n    dup         v0.4s, v0.s[0]\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    sub         x1, x1, #64\n\n    {% amx ldx x1 %}\n\n    lsl x2, x2, 47                  // max(x,z) (or min)\n    orr x2, x2, {{ 0|setting:44 }}  // f32\n\n    {% amx vecfp x2 %}\n    orr x2, x2, {{ 0|setting:20 }}  // z offset\n    {% amx vecfp x2 %}\n\n    b .non_linear_loop\n\n.per_col_mul:\n    ldr         x4, [x0, #8]\n\n    // broadcast value to y0\n    ld1         { v0.s }[0], [x4]\n    dup         v0.4s, v0.s[0]\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    st1         { v0.4s }, [x1], #16\n    sub         x1, x1, #64\n\n    {% amx ldy x1 %}\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n    {% amx extrx x2 %}\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n    {% amx fma32 x4 %}\n    orr x2, x2, {{ 0|setting:20 }}      // Z1\n    {% amx extrx x2 %}\n    orr x4, x4, {{ 0|setting:20 }}      // Z1\n    {% amx fma32 x4 %}\n\n    b .non_linear_loop\n\n.per_row_mul:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}      // pair\n    {% amx ldy x2 %}\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n    {% amx extrx x2 %}\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n    {% amx fma32 x4 %}\n    orr x2, x2, {{ 0|setting:20 }}      // Z1\n    {% amx extrx x2 %}\n    orr x4, x4, {{ 0|setting:20 }}      // Z1\n    orr x4, x4, {{ 0|setting:6 }}       // Y1\n    {% amx fma32 x4 %}\n\n    b .non_linear_loop\n\n.scalar_sub:\n    // performs a unary neg on Z, then go to scalar_add\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n    {% amx extrx x2 %}\n    {% amx fms32 x4 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    add x4, x4, {{0|setting:20}}    // next Z row\n    {% amx extrx x2 %}\n    {% amx fms32 x4 %}\n\n    // continue on purpose\n\n.scalar_add:\n    ldr         w5, [x0, #8]\n\n    fmov        s0, w5\n    dup         v0.4s, v0.s[0]\n    dup         v1.4s, v0.s[0]\n    dup         v2.4s, v0.s[0]\n    dup         v3.4s, v0.s[0]\n\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    mov x2, {{ 0|setting:28 }}          // Z+=X \n    {% amx fma32 x2 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    {% amx fma32 x2 %}\n    b .non_linear_loop\n\n.scalar_sub_flipped:\n    ldr         w5, [x0, #8]\n    fmov        s0, w5\n    dup         v0.4s, v0.s[0]\n    dup         v1.4s, v0.s[0]\n    dup         v2.4s, v0.s[0]\n    dup         v3.4s, v0.s[0]\n\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    mov x2, {{ 0|setting:28 }}          // Z-=X \n    {% amx fms32 x2 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    {% amx fms32 x2 %}\n    b .non_linear_loop\n\n.scalar_mul:\n    ldr         w5, [x0, #8]\n    fmov        s0, w5\n    dup         v0.4s, v0.s[0]\n    dup         v1.4s, v0.s[0]\n    dup         v2.4s, v0.s[0]\n    dup         v3.4s, v0.s[0]\n\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldy x1 %}    // load 16 values\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n\n    {% amx extrx x2 %}\n    {% amx fma32 x4 %}\n    add x2, x2, {{0|setting:20}}    // next Z row\n    add x4, x4, {{0|setting:20}}    // next Z row\n    {% amx extrx x2 %}\n    {% amx fma32 x4 %}\n\n    b .non_linear_loop\n\n.scalar_min:\n    mov x2, 5\n    b .scalar_min_max\n.scalar_max:\n    mov x2, 7\n.scalar_min_max:\n    ldr         w5, [x0, #8]\n    fmov        s0, w5\n    dup         v0.4s, v0.s[0]\n    dup         v1.4s, v0.s[0]\n    dup         v2.4s, v0.s[0]\n    dup         v3.4s, v0.s[0]\n\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    lsl x2, x2, 47\n    orr x2, x2, {{ 0|setting:44 }} // f32\n\n    {% amx vecfp x2 %}\n    add x2, x2, {{ 0|setting:20}} // next Z\n    {% amx vecfp x2 %}\n\n    b .non_linear_loop\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    {% for neon in (0..7) %}\n       {% for lane in (0..3) %}\n           ld1 { v{{neon}}.s }[{{lane}}], [x5], x6\n       {% endfor %}\n    {% endfor %}\n    mov x8, x1\n    st1 { v0.4s, v1.4s, v2.4s, v3.4s }, [x8], #64\n    st1 { v4.4s, v5.4s, v6.4s, v7.4s }, [x8], #64\n\n    orr x8, x1, {{ 0|setting:62 }}          // pair\n    {% amx ldy x8 %}\n\n    eor x2, x2, x2\n    orr x2, x2, {{ 0|setting:63 }}  // vector mode\n    orr x2, x2, {{ 0|setting:29 }}  // perform Z0+=Y0\n    {% amx fma32 x2 %}\n    orr x2, x2, {{ 0|setting:20 }}  // Z1\n    orr x2, x2, 64                  // offset Y by 16 values\n    {% amx fma32 x2 %}\n    \n    b .non_linear_loop\n\n.add_row_col_products:\n    ldp         x5, x6, [x0, #8]            // a base ptr, b base ptr\n\n    ld1         { v0.s }[0], [x6]\n    st1         { v0.s }[0], [x1]\n    {% amx ldy x1 %}\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldx x2 %}\n\n    mov x2, {{ 0|setting:44 }}      // f32\n    orr x2, x2, {{ 0|setting:38 }}  // Broadcast Y\n    {% amx vecfp x2 %}\n\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    orr x2, x2, {{ 0|setting:16 }}  // X offset\n    {% amx vecfp x2 %}\n\n    b .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    ands        x8, x5, 0x7f\n    bne         .store_generic\n    cmp         x6, 4\n    bne         .store_generic\n    cmp         x7, 4\n    bne         .store_generic\n \n    orr x5, x5, {{ 0|setting:62 }}          // pair\n    {% amx stz x5 %}\n    b .non_linear_loop\n\n .store_generic:\n\n    orr x8, x1, {{ 0|setting:62 }}          // pair\n    {% amx stz x8 %}\n\n    mov x8, x1\n    ld1 { v0.4s, v1.4s, v2.4s, v3.4s }, [x8], #64\n    ld1 { v4.4s, v5.4s, v6.4s, v7.4s }, [x8], #64\n    {% for neon in (0..7) %}\n       {% for lane in (0..3) %}\n           st1 { v{{neon}}.s }[{{lane}}], [x5], x6\n       {% endfor %}\n    {% endfor %}\n    \n    b .non_linear_loop\n\n.load_tile:\n    ldr  x2, [x0, #16]                      // row major ptr\n    orr  x2, x2, {{0|setting:62}}           // load pairs\n    {% amx ldz x2 %}\n    b .non_linear_loop\n\n.return:\n{{ AMX_CLR }}\nret\n"
  },
  {
    "path": "linalg/arm64/apple_amx/apple_amx_mmm_f32_32x32.tmpl",
    "content": "// vim: ft=arm\n.text\n.align 4\n\n/* Z: 32x32\n z0[0] ..  z0[15] z1[0] .. z1[15]\n z4[0] ..  z4[15] z5[0] .. z5[15]\n  ..\nz60[0] .. z60[15] z61[0] .. z61[15]\n\n z2[0] ..  z2[15] z3[0] .. z3[15]\n z5[0] ..  z5[15] z6[0] .. z6[15]\n  ..\nz62[0] .. z62[15] z63[0] .. z63[15]\n*/\n    \n\n.global {{G}}apple_amx_mmm_f32_32x32_{{suffix}}\n{{G}}apple_amx_mmm_f32_32x32_{{suffix}}:\n\n{{ AMX_SET }}\n\n    // set x1 to a 128 bytes aligned block for loads\n    mov x1, sp\n    lsr x1, x1, #7\n    lsl x1, x1, #7\n    sub x1, x1, 128\n\n{% include \"dispatcher.tmpliq\" %}\n\n.leaky_relu:\n.q_scale:\n.q_shl:\n.q_shr:\n    b .unsupported\n\n.add_mat_mul:\n\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x4, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    orr         x4, x4, {{0|setting:62}}    // load pairs (A)\n    orr         x2, x2, {{0|setting:62}}    // load pairs (B)\n\n    eor         x5, x5, x5                  // top left\n\n    orr         x6, x5, {{ 0|setting:20 }}  // Z row = 1\n    orr         x6, x6, {{ 0|setting:16 }}  // top right\n\n    orr         x7, x5, {{ 0|setting:21 }}\n    orr         x7, x7, {{ 0|setting:6 }}   // bottom left\n\n    orr         x8, x7, x6                  // bottom right\n\n    .packed_packed_loop_1:\n    {% amx ldx x2 %}\n    {% amx ldy x4 %}\n    add x2, x2, 128\n    add x4, x4, 128\n\n    {% amx fma32 x5 %}\n    {% amx fma32 x6 %}\n    {% amx fma32 x7 %}\n    {% amx fma32 x8 %}\n\n    subs x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b .non_linear_loop\n\n.clear:\n    // top left\n    eor x2, x2, x2\n    orr x2, x2, {{ 0|setting:27 }}\n    orr x2, x2, {{ 0|setting:28 }}\n    orr x2, x2, {{ 0|setting:29 }}  // Z = 0\n    {% amx fma32 x2 %}\n\n    // top right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    {% amx fma32 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:21 }}  // Z row = 3\n    {% amx fma32 x2 %}\n\n    // bottom left\n    eor x2, x2, {{ 0|setting:20 }}  // Z row = 2\n    {% amx fma32 x2 %}\n\n    b .non_linear_loop\n\n.per_col_sub:\n\n    // performs a unary neg on Z\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    mov x6, 64\n    .per_col_sub_loop:\n        {% amx extrx x2 %}\n        {% amx fms32 x4 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n        add x4, x4, {{0|setting:20}}    // next Z row\n    subs x6, x6, 1\n    bne .per_col_sub_loop\n\n    // continue\n\n.per_col_add:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x1, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldx x1 %}\n\n    mov x2, {{ 0|setting:28 }}      // z += y\n\n    // top left\n    {% amx fma32 x2 %}\n\n    // bottom left\n    orr x2, x2, {{ 0|setting:21 }}  // Z row = 2\n    {% amx fma32 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:16 }}  // X offset\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 3\n    {% amx fma32 x2 %}\n\n    // top right\n    eor x2, x2, {{ 0|setting:21 }}  // Z row = 1\n    {% amx fma32 x2 %}\n\n    b .non_linear_loop\n\n.per_col_sub_flipped:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x1, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldx x1 %}\n\n    mov x2, {{ 0|setting:28 }}      // z += y\n\n    // top left\n    {% amx fms32 x2 %}\n\n    // bottom left\n    orr x2, x2, {{ 0|setting:21 }}  // Z row = 2\n    {% amx fms32 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:16 }}  // X offset\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 3\n    {% amx fms32 x2 %}\n\n    // top right\n    eor x2, x2, {{ 0|setting:21 }}  // Z row = 1\n    {% amx fms32 x2 %}\n\n\n    b .non_linear_loop\n\n.per_row_sub_flipped:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    mov x2, {{ 0|setting:29 }}      // z += y\n\n    // top left\n    {% amx fms32 x2 %}\n\n    // top right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    {% amx fms32 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:21 }}  // Z row = 3\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fms32 x2 %}\n\n    // bottom left\n    eor x2, x2, {{ 0|setting:20 }}  // Z row = 2\n    {% amx fms32 x2 %}\n\n    b .non_linear_loop\n\n.per_row_sub:\n    // performs a unary neg on Z\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    mov x6, 64\n    .per_row_sub_loop:\n        {% amx extrx x2 %}\n        {% amx fms32 x4 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n        add x4, x4, {{0|setting:20}}    // next Z row\n    subs x6, x6, 1\n    bne .per_row_sub_loop\n\n    // continue\n\n.per_row_add:\n    ldr         x2, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    mov x2, {{ 0|setting:29 }}      // z += y\n\n    // top left\n    {% amx fma32 x2 %}\n\n    // top right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    {% amx fma32 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:21 }}  // Z row = 3\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fma32 x2 %}\n\n    // bottom left\n    eor x2, x2, {{ 0|setting:20 }}  // Z row = 2\n    {% amx fma32 x2 %}\n\n    b .non_linear_loop\n\n.per_row_min:\n    mov x2, 5\n    b .per_row_min_max\n.per_row_max:\n    mov x2, 7\n.per_row_min_max:\n    ldr         x5, [x0, #8]\n\n    add x6, x5, 64\n\n    lsl x2, x2, 47                  // max(x,z) (or min)\n    orr x2, x2, {{ 0|setting:44 }}  // f32\n    orr x3, x2, {{ 0|setting:20 }}  // right half: z offset\n\n    orr x8, x2, {{ 0|setting:21 }}  // bottom left\n    orr x9, x3, {{ 0|setting:21 }}  // bottom right\n\n    mov x4, 16\n    .loop_per_row_max:\n        // top half\n        ld1         { v0.s }[0], [x5], #4\n        dup         v0.4s, v0.s[0]\n        dup         v1.4s, v0.s[0]\n        dup         v2.4s, v0.s[0]\n        dup         v3.4s, v0.s[0]\n        st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n\n        {% amx ldx x1 %}\n        {% amx vecfp x2 %}\n        {% amx vecfp x3 %}\n\n        add x2, x2, {{ 0|setting:22 }}\n        add x3, x3, {{ 0|setting:22 }}\n\n        // bottom half\n        ld1         { v0.s }[0], [x6], #4\n        dup         v0.4s, v0.s[0]\n        dup         v1.4s, v0.s[0]\n        dup         v2.4s, v0.s[0]\n        dup         v3.4s, v0.s[0]\n        st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n\n        {% amx ldx x1 %}\n        {% amx vecfp x8 %}\n        {% amx vecfp x9 %}\n\n        add x8, x8, {{ 0|setting:22 }}\n        add x9, x9, {{ 0|setting:22 }}\n\n    subs x4, x4, 1\n    bne .loop_per_row_max\n\n    b .non_linear_loop\n\n.per_col_min:\n    mov x2, 5\n    b .per_col_min_max\n.per_col_max:\n    mov x2, 7\n.per_col_min_max:\n    ldr         x4, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x4], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x4]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x3, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldx x3 %}\n\n    lsl x2, x2, 47                  // max(x,z) (or min)\n    orr x2, x2, {{ 0|setting:44 }}  // f32\n\n    orr x3, x2, {{ 0|setting:16 }}  // right half: x offset\n    orr x3, x3, {{ 0|setting:20 }}  // right half: z offset\n\n    mov x4, 32\n    .loop_per_col_max:\n        {% amx vecfp x2 %}\n        {% amx vecfp x3 %}\n        add x2, x2, {{ 0|setting:21 }}\n        add x3, x3, {{ 0|setting:21 }}\n    subs x4, x4, 1\n    bne .loop_per_col_max\n\n    b .non_linear_loop\n\n.per_col_mul:\n    ldr         x4, [x0, #8]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x4], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x4]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}      // load a pair\n    {% amx ldy x2 %}\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    eor x3, x3, x3\n    orr x3, x3, {{0|setting:20 }}       // Z[1]\n    orr x3, x3, {{0|setting:16 }}       // X[1]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n\n    mov x5, {{ 0|setting:63 }}          // vector mode\n    orr x5, x5, {{ 0|setting:27 }}      // Z=X*Y\n    orr x5, x5, {{ 0|setting:20 }}      // Z right\n    orr x5, x5, {{ 0|setting:16 }}      // X[1] (right)\n    orr x5, x5, {{ 0|setting:6 }}       // Y[1] (right)\n\n    mov x6, 32\n    .loop_per_col_mul:\n        {% amx extrx x2 %}\n        {% amx extrx x3 %}\n        {% amx fma32 x4 %}\n        {% amx fma32 x5 %}\n        add x2, x2, {{0|setting:21}}\n        add x3, x3, {{0|setting:21}}\n        add x4, x4, {{0|setting:21}}\n        add x5, x5, {{0|setting:21}}\n    subs x6, x6, 1\n    bne .loop_per_col_mul\n\n    b .non_linear_loop\n\n.per_row_mul:\n    ldr         x14, [x0, #8]\n    add         x15, x14, 64\n\n    // extrx\n    eor x2, x2, x2                      // X[0] = Z[0] (top left)\n\n    eor x3, x3, x3\n    orr x3, x3, {{0|setting:20 }}       // Z[1]\n    orr x3, x3, {{0|setting:16 }}       // X[1] = Z[1] (top right)\n\n    eor x4, x4, x4\n    orr x4, x4, {{0|setting:21}}        // X[0] = Z[2] (bottom left)\n\n    orr x5, x4, {{0|setting:20}}\n    orr x5, x5, {{0|setting:16}}        // X[1] = Z[3] (bottom right)\n\n    // fma32\n    eor x6, x6, x6\n    orr x6, x6, {{0|setting:63}}        // vector mode\n    orr x6, x6, {{0|setting:27}}        // Z=X*Y       Z[0]=X[0]*Y[0]\n\n    orr x7, x6, {{0|setting:20}}        // Z[1]\n    orr x7, x7, {{0|setting:16}}        // X[1]        Z[1] = X[1]*Y[0]\n\n    orr x8, x6, {{0|setting:21}}        // Z[2]\n    orr x8, x8, {{0|setting:21}}        // Z[2]\n\n    orr x9, x8, {{0|setting:20}}        // Z[3]\n    orr x9, x9, {{0|setting:16}}        // X[1]\n\n    mov x10, 16\n    .loop_per_row_mul:\n        // top\n        ld1         { v0.s }[0], [x14], #4\n        dup         v0.4s, v0.s[0]\n        dup         v1.4s, v0.s[0]\n        dup         v2.4s, v0.s[0]\n        dup         v3.4s, v0.s[0]\n        st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n\n        {% amx ldy x1 %}\n        {% amx extrx x2 %}\n        {% amx extrx x3 %}\n        {% amx fma32 x6 %}\n        {% amx fma32 x7 %}\n\n        add x2, x2, {{ 0|setting:22 }}\n        add x3, x3, {{ 0|setting:22 }}\n        add x6, x6, {{ 0|setting:22 }}\n        add x7, x7, {{ 0|setting:22 }}\n\n        // bottom\n        ld1         { v0.s }[0], [x15], #4\n        dup         v0.4s, v0.s[0]\n        dup         v1.4s, v0.s[0]\n        dup         v2.4s, v0.s[0]\n        dup         v3.4s, v0.s[0]\n        st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n\n        {% amx ldy x1 %}\n        {% amx extrx x4 %}\n        {% amx extrx x5 %}\n        {% amx fma32 x8 %}\n        {% amx fma32 x9 %}\n\n        add x4, x4, {{ 0|setting:22 }}\n        add x5, x5, {{ 0|setting:22 }}\n        add x8, x8, {{ 0|setting:22 }}\n        add x9, x9, {{ 0|setting:22 }}\n\n    subs x10, x10, 1\n    bne .loop_per_row_mul\n\n    b .non_linear_loop\n\n.scalar_sub:\n    // performs a unary neg on Z, then go to scalar_add\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:28 }}\n    orr x4, x4, {{ 0|setting:27 }}      // Z=-X\n\n    mov x6, 64\n    .scalar_sub_loop:\n        {% amx extrx x2 %}\n        {% amx fms32 x4 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n        add x4, x4, {{0|setting:20}}    // next Z row\n    subs x6, x6, 1\n    bne .scalar_sub_loop\n\n    // continue on purpose\n\n.scalar_add:\n    ldr         w5, [x0, #8]\n\n    fmov        s0, w5\n    dup         v0.4s, v0.s[0]\n    dup         v1.4s, v0.s[0]\n    dup         v2.4s, v0.s[0]\n    dup         v3.4s, v0.s[0]\n\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    mov x2, {{ 0|setting:28 }}          // Z+=X \n    {% for chunk in (0..3) %}\n        {% amx fma32 x2 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n    {% endfor %}\n    b .non_linear_loop\n\n.scalar_sub_flipped:\n    ldr         w5, [x0, #8]\n    fmov        s0, w5\n    dup         v0.4s, v0.s[0]\n    dup         v1.4s, v0.s[0]\n    dup         v2.4s, v0.s[0]\n    dup         v3.4s, v0.s[0]\n\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    mov x2, {{ 0|setting:28 }}          // Z-=X \n    {% for chunk in (0..3) %}\n        {% amx fms32 x2 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n    {% endfor %}\n    b .non_linear_loop\n\n.scalar_mul:\n    ldr         w5, [x0, #8]\n    fmov        s0, w5\n    dup         v0.4s, v0.s[0]\n    dup         v1.4s, v0.s[0]\n    dup         v2.4s, v0.s[0]\n    dup         v3.4s, v0.s[0]\n\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldy x1 %}    // load 16 values\n\n    eor x2, x2, x2                      // X[0] = Z[0]\n\n    mov x4, {{ 0|setting:63 }}          // vector mode\n    orr x4, x4, {{ 0|setting:27 }}      // Z=X*Y\n\n    mov x6, 64\n    .scalar_mul_loop:\n        {% amx extrx x2 %}\n        {% amx fma32 x4 %}\n        add x2, x2, {{0|setting:20}}    // next Z row\n        add x4, x4, {{0|setting:20}}    // next Z row\n    subs x6, x6, 1\n    bne .scalar_mul_loop\n\n    b .non_linear_loop\n\n.scalar_min:\n    mov x2, 5\n    b .scalar_min_max\n.scalar_max:\n    mov x2, 7\n.scalar_min_max:\n    ldr         w5, [x0, #8]\n    fmov        s0, w5\n    dup         v0.4s, v0.s[0]\n    dup         v1.4s, v0.s[0]\n    dup         v2.4s, v0.s[0]\n    dup         v3.4s, v0.s[0]\n\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    {% amx ldx x1 %}    // load 16 values\n\n    lsl x2, x2, 47\n    orr x2, x2, {{ 0|setting:44 }} // f32\n\n    mov x3, 64\n    .loop_scalar_max:\n        add x2, x2, {{ 0|setting:20}} // next Z\n        {% amx vecfp x2 %}\n        subs x3, x3, 1\n        bne .loop_scalar_max\n\n    b .non_linear_loop\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    add x8, x1, 64\n\n    mov x3, 0                               // x3 is the row\n    .loop_load:\n        and x9, x3, 0xf                     // x9 = row % 16\n        lsl x9, x9, 2                       // x9 = (row % 16) * 4\n        lsr x10, x3, 4                      // x10 = row / 16 \n        lsl x10, x10, 1                     // x10 = (row / 16) * 2\n        add x9, x9, x10                     // x9 = x9 + x10\n\n        mov x4, x5\n        {% for neon in (0..3) %}\n            {% for lane in (0..3) %}\n                ld1 { v{{neon}}.s }[{{lane}}], [x4], x7\n            {% endfor %}\n        {% endfor %}\n        st1 { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n        {% for neon in (0..3) %}\n            {% for lane in (0..3) %}\n                ld1 { v{{neon}}.s }[{{lane}}], [x4], x7\n            {% endfor %}\n        {% endfor %}\n        st1 { v0.4s, v1.4s, v2.4s, v3.4s }, [x8]\n\n        mov x2, x1\n        orr x2, x2, {{ 0|setting:62 }} // load 32 values\n        {% amx ldy x2 %}\n\n        lsl x2, x9, 20                  // left Z register to update\n        orr x2, x2, {{ 0|setting:63 }}  // vector mode\n        orr x2, x2, {{ 0|setting:29 }}  // perform Z+=Y\n        {% amx fma32 x2 %}\n\n        add x2, x2, {{0|setting:20}}\n        orr x2, x2, 64                  // offset Y by 16 values\n        {% amx fma32 x2 %}\n\n        add x5, x5, x6\n    add x3, x3, 1\n    cmp x3, 32\n    bne .loop_load\n\n    b .non_linear_loop\n\n.add_row_col_products:\n    ldp         x5, x6, [x0, #8]            // a base ptr, b base ptr\n\n    add x8, x1, 64\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x5]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldy x2 %}\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x6], #64\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1], #64\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x6]\n    st1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n    sub x1, x1, #64\n\n    orr x2, x1, {{ 0|setting:62 }}  // load a pair\n    {% amx ldx x2 %}\n\n    // top left\n    eor x2, x2, x2\n    {% amx fma32 x2 %}\n\n    // top right\n    orr x2, x2, {{ 0|setting:20 }}  // Z row = 1\n    orr x2, x2, {{ 0|setting:16 }}  // X offset\n    {% amx fma32 x2 %}\n\n    // bottom right\n    orr x2, x2, {{ 0|setting:21 }}  // Z row = 3\n    orr x2, x2, {{ 0|setting:6 }}   // Y offset\n    {% amx fma32 x2 %}\n\n    // bottom left\n    eor x2, x2, {{ 0|setting:20 }}  // Z row = 2\n    eor x2, x2, {{ 0|setting:16 }}  // X offset <-\n    {% amx fma32 x2 %}\n\n    b .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x7, 4\n    bne         .store_generic\n    ands        x8, x5, 0x7f\n    bne         .store_generic\n    ands        x8, x6, 0x7f\n    bne         .store_generic\n\n    orr x5, x5, {{ 0|setting:62 }}          // pair\n    lsl x8, x6, 4\n    add x8, x8, x5                          // x8 = 16*rsc\n    orr x8, x8, {{ 0|setting:57 }}          // first to x8 is z2\n\n    mov x4, {{0|setting:58}}                // Zreg += 4\n    add x4, x4, x6                          // +rsc\n\n    mov x3, 16\n    .loop_store_direct:\n        {% amx stz x5 %}\n        {% amx stz x8 %}\n        add x5, x5, x4\n        add x8, x8, x4\n    subs x3, x3, 1\n    bne .loop_store_direct\n\n    b .non_linear_loop\n\n.store_generic:\n    \n    add x8, x1, 64\n\n    mov x3, 0                               // row id\n    .loop_store:\n        and x9, x3, 0xf                     // x9 = row % 16\n        lsl x9, x9, 2                       // x9 = (row % 16) * 4\n        lsr x10, x3, 4                      // x10 = row / 16 \n        lsl x10, x10, 1                     // x10 = (row / 16) * 2\n        add x9, x9, x10                     // x9 = x9 + x10\n\n        lsl x2, x9, 56\n        orr x2, x2, {{ 0|setting:62 }}\n        orr x2, x2, x1\n        {% amx stz x2 %}\n        ld1 { v0.4s, v1.4s, v2.4s, v3.4s }, [x1]\n\n        mov x4, x5\n        {% for neon in (0..3) %}\n            {% for lane in (0..3) %}\n                st1 { v{{neon}}.s }[{{lane}}], [x4], x7\n            {% endfor %}\n        {% endfor %}\n        ld1 { v0.4s, v1.4s, v2.4s, v3.4s }, [x8]\n        {% for neon in (0..3) %}\n            {% for lane in (0..3) %}\n                st1 { v{{neon}}.s }[{{lane}}], [x4], x7\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x6\n\n    add x3, x3, 1\n    cmp x3, 32\n    bne .loop_store\n\n    b .non_linear_loop\n\n.load_tile:\n    ldr  x2, [x0, #16]                      // row major ptr\n    orr  x2, x2, {{0|setting:62}}           // load pairs\n    mov  x3, x2\n    orr  x3, x3, {{0|setting:57}}\n    add  x3, x3, #2048\n    \n    mov  x4, {{0|setting:58}}               // z+=4\n    add  x4, x4, #128\n\n    mov x8, 16\n    .loop_load_tile:\n        {% amx ldz x2 %}\n        {% amx ldz x3 %}\n        add x2, x2, x4\n        add x3, x3, x4\n    subs x8, x8, 1\n    bne .loop_load_tile\n\n    b .non_linear_loop\n\n.return:\n{{ AMX_CLR }}\nret\n"
  },
  {
    "path": "linalg/arm64/apple_amx/dispatcher.tmpliq",
    "content": "// vim: ft=arm\n\n.non_linear:\n    sub         x0, x0, 40\n\n.non_linear_loop:\n    add         x0, x0, 40\n    ldr         x2, [x0]\n\n    mov         x4, #{{ jump_table | size }}\n\n    cmp         x2, #{{ jump_table | size }}\n    csel        x2, x2, x4, lt\n    cmp         x2, #0\n    csel        x2, x4, x2, lt\n\n    adr         x3, .jmp_table\n    add         x3, x3, x2, LSL#2\n    br          x3\n\n.jmp_table:\n{% for j in jump_table %}\n    b   .{{j}}\n{% endfor %}\n    b   .unsupported\n\n    add x0, x2, #4000\n    b .return\n\n.unsupported:\n    mov         x0, #1\n    b           .return\n\n.done:\n    mov         x0, 0\n    b           .return\n\n"
  },
  {
    "path": "linalg/arm64/apple_amx/instructions.rs",
    "content": "use liquid::model::KString;\nuse liquid::partials::PartialCompiler;\nuse liquid::{ParserBuilder, ValueView};\nuse liquid_core::{\n    Display_filter, Expression, Filter, FilterParameters, FilterReflection, FromFilterParameters,\n    ParseFilter, ParseTag, Renderable, Runtime, TagReflection, Value,\n};\n\npub fn register<C: PartialCompiler>(parser: ParserBuilder<C>) -> ParserBuilder<C> {\n    parser.tag(AmxTag).filter(LeftShift).filter(Setting).filter(Unsigned)\n}\n\npub fn globals() -> Vec<(KString, Value)> {\n    vec![\n        (\"AMX_SET\".to_string().into(), Value::scalar(amx_nop_op_imm5(17, 0))),\n        (\"AMX_CLR\".to_string().into(), Value::scalar(amx_nop_op_imm5(17, 1))),\n    ]\n}\n\nfn amx_nop_op_imm5(op: usize, imm5: usize) -> String {\n    format!(\"nop\\nnop\\nnop\\n.word 0x{:x}\\n\", (0x201000 + (op << 5) + imm5))\n}\n\nfn amx_nop_op_gpr(op: usize, gpr: usize) -> String {\n    format!(\".word 0x{:x}\", (0x201000 + (op << 5) + gpr))\n}\n\n#[derive(Copy, Clone)]\nstruct AmxTag;\n\nimpl ParseTag for AmxTag {\n    fn reflection(&self) -> &dyn liquid_core::TagReflection {\n        self\n    }\n\n    fn parse(\n        &self,\n        mut arguments: liquid_core::TagTokenIter,\n        _options: &liquid_core::Language,\n    ) -> liquid_core::Result<Box<dyn liquid_core::Renderable>> {\n        let op = arguments.expect_next(\"expects op and gpr\")?.as_str().to_string();\n        let gpr = arguments\n            .expect_next(\"expects op and gpr\")?\n            .as_str()\n            .trim_start_matches('x')\n            .parse::<usize>()\n            .unwrap();\n        let op_id = [\n            \"ldx\", \"ldy\", \"stx\", \"sty\", \"ldz\", \"stz\", \"ldzi\", \"stzi\", \"extrx\", \"extry\", \"fma64\",\n            \"fms64\", \"fma32\", \"fms32\", \"mac16\", \"fma16\", \"fms16\", \"setclr\", \"vecint\", \"vecfp\",\n            \"matint\", \"matfp\", \"genlut\",\n        ]\n        .iter()\n        .position(|x| x == &op)\n        .unwrap();\n        Ok(Box::new(RenderedAmxTag(format!(\n            \"{} \\t\\t\\t\\t// AMX {op} x{gpr}\\n\",\n            amx_nop_op_gpr(op_id, gpr)\n        ))))\n    }\n}\n\nimpl TagReflection for AmxTag {\n    fn tag(&self) -> &str {\n        \"amx\"\n    }\n\n    fn description(&self) -> &str {\n        \"translate to an Apple AMX instruction\"\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct RenderedAmxTag(String);\n\nimpl Renderable for RenderedAmxTag {\n    fn render_to(\n        &self,\n        writer: &mut dyn std::io::Write,\n        _runtime: &dyn liquid_core::Runtime,\n    ) -> liquid_core::Result<()> {\n        writer.write_all(self.0.as_bytes()).unwrap();\n        Ok(())\n    }\n}\n\n#[derive(Debug, FilterParameters)]\nstruct ShiftArgs {\n    #[parameter(description = \"The number to shift the input by.\")]\n    operand: Expression,\n}\n\n#[derive(Clone, ParseFilter, FilterReflection)]\n#[filter(\n    name = \"lsl\",\n    description = \"Shift left a number by the given operand.\",\n    parameters(ShiftArgs),\n    parsed(LeftShiftFilter)\n)]\nstruct LeftShift;\n\n#[derive(Debug, FromFilterParameters, Display_filter)]\n#[name = \"lsl\"]\nstruct LeftShiftFilter {\n    #[parameters]\n    args: ShiftArgs,\n}\n\nimpl Filter for LeftShiftFilter {\n    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> liquid_core::Result<Value> {\n        let args = self.args.evaluate(runtime)?;\n\n        let operand = args\n            .operand\n            .as_scalar()\n            .ok_or_else(|| invalid_argument(\"operand\", \"Number expected\"))?;\n\n        let result = input\n            .as_scalar()\n            .unwrap()\n            .to_integer()\n            .and_then(|i| operand.to_integer().map(|o| Value::scalar(i << o)))\n            .ok_or_else(|| invalid_argument(\"operand\", \"Integer expected\"))?;\n\n        Ok(result)\n    }\n}\n\n#[derive(Clone, ParseFilter, FilterReflection)]\n#[filter(\n    name = \"setting\",\n    description = \"Set the bit deigned by the operand.\",\n    parameters(ShiftArgs),\n    parsed(SettingFilter)\n)]\nstruct Setting;\n\n#[derive(Debug, FromFilterParameters, Display_filter)]\n#[name = \"setting\"]\nstruct SettingFilter {\n    #[parameters]\n    args: ShiftArgs,\n}\n\nimpl Filter for SettingFilter {\n    fn evaluate(&self, input: &dyn ValueView, runtime: &dyn Runtime) -> liquid_core::Result<Value> {\n        let args = self.args.evaluate(runtime)?;\n\n        let operand = args\n            .operand\n            .as_scalar()\n            .ok_or_else(|| invalid_argument(\"operand\", \"Number expected\"))?;\n\n        let result = input\n            .as_scalar()\n            .unwrap()\n            .to_integer()\n            .and_then(|i| operand.to_integer().map(|o| Value::scalar(i | (1 << o))))\n            .ok_or_else(|| invalid_argument(\"operand\", \"Integer expected\"))?;\n\n        Ok(result)\n    }\n}\n\nfn invalid_argument<S>(argument: S, cause: S) -> liquid::Error\nwhere\n    S: Into<liquid_core::model::KString>,\n{\n    liquid_core::Error::with_msg(\"Invalid argument\")\n        .context(\"argument\", argument)\n        .context(\"cause\", cause)\n}\n\n#[derive(Clone, ParseFilter, FilterReflection)]\n#[filter(name = \"u\", description = \"unsigned number\", parsed(UnsignedFilter))]\npub struct Unsigned;\n\n#[derive(Debug, Default, Display_filter)]\n#[name = \"float16\"]\nstruct UnsignedFilter;\n\nimpl Filter for UnsignedFilter {\n    fn evaluate(\n        &self,\n        input: &dyn ValueView,\n        _runtime: &dyn Runtime,\n    ) -> liquid_core::Result<Value> {\n        let input = input.as_scalar().unwrap().to_integer().unwrap() as u64;\n        Ok(input.to_string().to_value())\n    }\n}\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_leaky_relu_f16_8n.tmpl",
    "content": "// vim: ft=arm\n\n// no preservation either for v0-v7 and v16-v31\n\n.text\n.align 4\n\n{% if needs_pragma == true %}\n.cpu generic+fp+simd+fp16\n{% endif %}\n.global {{G}}arm64fp16_leaky_relu_f16_8n_{{suffix}}\n{{G}}arm64fp16_leaky_relu_f16_8n_{{suffix}}:\n\n    cmp         x1, #0\n    beq         .return\n\n    mov         v31.h[0], w2\n    dup         v31.8h, v31.h[0]\n    mov         x2, x0\n    \n    cmp         x1, #64\n    blt         .loop\n\n    ld1         { v16.8h, v17.8h, v18.8h, v19.8h }, [x2], #64\n.loop4:\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [x2], #64\n\n    fmul        v20.8h, v16.8h, v31.8h\n    fmul        v21.8h, v17.8h, v31.8h\n    fmul        v22.8h, v18.8h, v31.8h\n    fmul        v23.8h, v19.8h, v31.8h\n\n    fcmge       v24.8h, v16.8h, #0.0\n    fcmge       v25.8h, v17.8h, #0.0\n    fcmge       v26.8h, v18.8h, #0.0\n    fcmge       v27.8h, v19.8h, #0.0\n\n    bsl         v24.16b, v16.16b, v20.16b\n    bsl         v25.16b, v17.16b, v21.16b\n    bsl         v26.16b, v18.16b, v22.16b\n    bsl         v27.16b, v19.16b, v23.16b\n\n    st1         { v24.8h, v25.8h, v26.8h, v27.8h }, [x0], #64\n\n    and         v16.16b, v0.16b, v0.16b\n    and         v17.16b, v1.16b, v1.16b\n    and         v18.16b, v2.16b, v2.16b\n    and         v19.16b, v3.16b, v3.16b\n\n    subs        x1, x1, #32\n    cmp         x1, #64\n    bge         .loop4\n\n    cmp         x1, #0\n    beq         .return\n\n.loop:\n    ld1         { v16.8h }, [x0]\n\n    fmul        v17.8h, v16.8h, v31.8h\n    fcmge       v18.8h, v16.8h, #0.0\n    bsl         v18.16b, v16.16b, v17.16b\n    \n    st1         { v18.8h }, [x0], #16\n\n    subs        x1, x1, #8\n    bne         .loop\n\n.return:\n    ret\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_8h_per_col.tmpliq",
    "content": "// vim: ft=arm\n\n.{{label}}:\n    ldr         x2, [x0, #8]\n\n{% capture mr_over_8 %}{{ mr | divided_by: 8}}{%endcapture%}\n{% capture cols%}{{to | plus: 1| minus:from| divided_by:mr_over_8}}{%endcapture%}\n\n{% capture loads %}{{cols | divided_by: 8}}{% endcapture %}\n\n{%if cols == \"1\" %}\n        ld1         {v0.h}[0], [ x2 ]\n{% elsif cols == \"3\" %}\n        ld1         {v0.s}[0], [ x2 ], #4\n        ld1         {v0.h}[2], [ x2 ]\n{% elsif cols == \"4\" %}\n        ldr         d0, [ x2 ]\n{% elsif cols == \"6\" %}\n        ld1         {v0.d}[0], [ x2 ], #8\n        ld1         {v0.s}[2], [ x2 ]\n{% else %}\n    {% for reg in (1..loads) %}\n        ldr         q{{reg |minus:1}}, [ x2 ], #16\n    {% endfor %}\n{% endif %}\n\n// mr:{{mr}} {{ loads }} {{cols}}\n\n{% for col in (1..cols) %}\n    dup v3.8h, v{{col| minus: 1|divided_by:8}}.h[{{col| minus: 1|modulo:8}}]\n    {% for row in (1..mr_over_8) %}\n        {% capture acc %}{{ col|minus:1|times:mr_over_8|plus:row|minus:1|plus:from }}{% endcapture %}\n        {% if flipped %}\n            {{op}} v{{acc}}.8h, v{{acc}}.8h, v3.8h\n        {% else %}\n            {{op}} v{{acc}}.8h, v3.8h, v{{acc}}.8h\n        {% endif %}\n    {% endfor %}\n{% endfor %}\n\nb           .non_linear_loop\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_8h_per_row.tmpliq",
    "content": "// vim: ft=arm\n\n.{{label}}:\n    ldr         x2, [x0, #8]\n\n{% capture mr_over_8 %}{{ mr | divided_by: 8 }}{%endcapture%}\n{% capture mr_over_8_min_1 %}{{ mr | divided_by: 8 | minus: 1 }}{%endcapture%}\n\n{% for reg in (0..mr_over_8_min_1) %}\n    ldr         q{{reg}}, [ x2 ], #16\n{% endfor %}\n\n{% if flipped %}\n    {% for acc in (from..to) %}\n        {% capture other%}{{acc | minus: from | modulo: mr_over_8}}{%endcapture%}\n        {{op}} v{{acc}}.8h, v{{acc}}.8h, v{{other}}.8h\n    {% endfor %}\n{% else %}\n    {% for acc in (from..to) %}\n        {% capture other%}{{acc | minus: from | modulo: mr_over_8}}{%endcapture%}\n        {{op}} v{{acc}}.8h, v{{other}}.8h, v{{acc}}.8h\n    {% endfor %}\n{% endif %}\n\nb           .non_linear_loop\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_8h_scalar.tmpliq",
    "content": "// vim: ft=arm\n\n.{{label}}:\n    add         x2, x0, #8\n    ld1         {v0.h}[0], [ x2 ]\n    dup         v0.8h, v0.h[0]\n    {% if flipped %}\n        {% for reg in (from..to) %}\n            {{op}}       v{{reg}}.8h, v{{reg}}.8h, v0.8h\n        {% endfor %}\n    {% else %}\n        {% for reg in (from..to) %}\n            {{op}}       v{{reg}}.8h, v0.8h, v{{reg}}.8h\n        {% endfor %}\n    {% endif %}\n\n    b           .non_linear_loop\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_128x1/loop1/cortex_a53.tmpli",
    "content": "    fmla        v16.4s, v0.4s, v8.s[0]\n    ldr         x5, [x1, #128]\n    fmla        v17.4s, v1.4s, v8.s[0]\n    ldr         x6, [x1, #136]\n    fmla        v18.4s, v2.4s, v8.s[0]\n    ldr         x7, [x1, #144]\n    fmla        v19.4s, v3.4s, v8.s[0]\n    ldr         x9, [x1, #152]\n    ld1         {{ v0.4s, v1.4s, v2.4s, v3.4s }}, [ x1 ], #64\n\n    fmla        v20.4s, v4.4s, v8.s[0]\n    ldr         x10, [x1, #96]\n    fmla        v21.4s, v5.4s, v8.s[0]\n    ldr         x11, [x1, #104]\n    fmla        v22.4s, v6.4s, v8.s[0]\n    ldr         x12, [x1, #112]\n    fmla        v23.4s, v7.4s, v8.s[0]\n    ldr         x13, [x1, #120]\n\n    ld1         {{ v4.4s, v5.4s, v6.4s, v7.4s }}, [ x1 ]\n\n    fmla        v24.4s, v0.4s, v8.s[0]\n    ldr         x14, [x1, #128]\n    fmla        v25.4s, v1.4s, v8.s[0]\n    ldr         x15, [x1, #136]\n    fmla        v26.4s, v2.4s, v8.s[0]\n    ldr         x20, [x1, #144]\n    fmla        v27.4s, v3.4s, v8.s[0]\n    ldr         x21, [x1, #152]\n    fmla        v28.4s, v4.4s, v8.s[0]\n    ldr         x22, [x1, #160]\n    fmla        v29.4s, v5.4s, v8.s[0]\n    ldr         x23, [x1, #168]\n    fmla        v30.4s, v6.4s, v8.s[0]\n    ldr         x24, [x1, #176]\n    fmla        v31.4s, v7.4s, v8.s[0]\n    ldr         x25, [x1, #184]\n\n    ld1         {{ v8.s }}[0], [ x2 ], #4\n\n    prfm        pldl1keep, [x1, #1024]\n    prfm        pldl1keep, [x1, #1088]\n    prfm        pldl1keep, [x1, #1152]\n    prfm        pldl1keep, [x1, #1216]\n    prfm        pldl1keep, [x2, #256]\n\n    ins         v0.d[0], x5\n    ins         v1.d[0], x7\n    ins         v2.d[0], x10\n    ins         v3.d[0], x12\n    ins         v4.d[0], x14\n    ins         v5.d[0], x20\n    ins         v6.d[0], x22\n    ins         v7.d[0], x24\n\n    ins         v0.d[1], x6\n    ins         v1.d[1], x9\n    ins         v2.d[1], x11\n    ins         v3.d[1], x13\n    ins         v4.d[1], x15\n    ins         v5.d[1], x21\n    ins         v6.d[1], x23\n    ins         v7.d[1], x25\n\n    add         x1, x1, #192\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_128x1/loop1/naive.tmpli",
    "content": "    ld1         {{ v9.8h, v10.8h, v11.8h, v12.8h }}, [x1], #64\n    ld1         {{ v13.8h, v14.8h, v15.8h }}, [x1], #48\n\n    fmla        v16.8h, v0.8h, v8.h[0]\n    fmla        v17.8h, v1.8h, v8.h[0]\n    fmla        v18.8h, v2.8h, v8.h[0]\n    fmla        v19.8h, v3.8h, v8.h[0]\n    fmla        v20.8h, v4.8h, v8.h[0]\n    fmla        v21.8h, v5.8h, v8.h[0]\n    fmla        v22.8h, v6.8h, v8.h[0]\n    fmla        v23.8h, v7.8h, v8.h[0]\n    fmla        v24.8h, v9.8h, v8.h[0]\n    ld1         {{ v9.8h }}, [ x1 ], #16\n    ld1         {{ v0.8h, v1.8h, v2.8h, v3.8h }}, [x1], #64\n    ld1         {{ v4.8h, v5.8h, v6.8h, v7.8h }}, [x1], #64\n    fmla        v25.8h, v10.8h, v8.h[0]\n    fmla        v26.8h, v11.8h, v8.h[0]\n    fmla        v27.8h, v12.8h, v8.h[0]\n    fmla        v28.8h, v13.8h, v8.h[0]\n    fmla        v29.8h, v14.8h, v8.h[0]\n    fmla        v30.8h, v15.8h, v8.h[0]\n\n    fmla        v31.8h, v9.8h, v8.h[0]\n\n    ld1         {{ v8.h }}[0], [ x2 ], #2\n\n    prfm        pldl1keep, [x1, #1024]\n    prfm        pldl1keep, [x1, #1088]\n    prfm        pldl1keep, [x1, #1152]\n    prfm        pldl1keep, [x1, #1216]\n    prfm        pldl1keep, [x2, #256]\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_128x1/loop2/cortex_a55.tmpli",
    "content": "    ld1         {{ v9.4s, v10.4s, v11.4s }}, [x1], #48\n\n    fmla        v16.8h, v0.8h, v8.h[0]\n    ldr         w8, [x2], #4\n    fmla        v17.8h, v1.8h, v8.h[0]\n    ldr         d12, [x1], #8\n    fmla        v18.8h, v2.8h, v8.h[0]\n    ldr         x12, [x1], #8\n    fmla        v19.8h, v3.8h, v8.h[0]\n    ldr         d13, [x1], #8\n    fmla        v20.8h, v4.8h, v8.h[0]\n    ldr         x13, [x1], #8\n    fmla        v21.8h, v5.8h, v8.h[0]\n    ldr         d14, [x1], #8\n    fmla        v22.8h, v6.8h, v8.h[0]\n    ldr         x14, [x1], #8\n    fmla        v23.8h, v7.8h, v8.h[0]\n    ldr         d15, [x1], #8\n    fmla        v24.8h, v9.8h, v8.h[0]\n    ldr         x15, [x1], #8\n\n    ld1         {{ v0.8h, v1.8h, v2.8h, v3.8h }}, [x1], #64\n    ins         v8.s[1], w8\n    ld1         {{ v4.8h, v5.8h, v6.8h, v7.8h }}, [x1], #64\n\n    fmla        v25.8h, v10.8h, v8.h[0]\n    ins         v12.d[1], x12\n    fmla        v26.8h, v11.8h, v8.h[0]\n    ins         v13.d[1], x13\n    fmla        v27.8h, v12.8h, v8.h[0]\n    ins         v14.d[1], x14\n    fmla        v28.8h, v13.8h, v8.h[0]\n    ins         v15.d[1], x15\n\n    ld1         {{ v9.8h, v10.8h, v11.8h, v12.8h }}, [x1], #64\n\n    fmla        v29.8h, v14.8h, v8.h[0]\n    ldr         d13, [x1], #8\n    fmla        v30.8h, v15.8h, v8.h[0]\n    ldr         x13, [x1], #8\n    fmla        v31.8h, v0.8h, v8.h[0]\n    ldr         d14, [x1], #8\n\n    fmla        v16.8h, v1.8h, v8.h[2]\n    ldr         x14, [x1], #8\n    fmla        v17.8h, v2.8h, v8.h[2]\n    ldr         d15, [x1], #8\n    fmla        v18.8h, v3.8h, v8.h[2]\n    ldr         x15, [x1], #8\n    fmla        v19.8h, v4.8h, v8.h[2]\n\n    ld1         {{ v0.8h }}, [x1], #16\n\n    fmla        v20.8h, v5.8h, v8.h[2]\n    ldr         d1, [x1], #8\n    fmla        v21.8h, v6.8h, v8.h[2]\n    ldr         x10, [x1], #8\n\n    fmla        v22.8h, v7.8h, v8.h[2]\n\n    fmla        v23.8h, v9.8h, v8.h[2]\n    ins         v13.d[1], x13\n    fmla        v24.8h, v10.8h, v8.h[2]\n    ins         v14.d[1], x14\n    fmla        v25.8h, v11.8h, v8.h[2]\n    ins         v15.d[1], x15\n\n    fmla        v26.8h, v12.8h, v8.h[2]\n    prfm        pldl1keep, [x1, #1024]\n    fmla        v27.8h, v13.8h, v8.h[2]\n    ins         v1.d[1], x10\n    fmla        v28.8h, v14.8h, v8.h[2]\n    prfm        pldl1keep, [x1, #1088]\n    fmla        v29.8h, v15.8h, v8.h[2]\n    prfm        pldl1keep, [x1, #1152]\n    fmla        v30.8h, v0.8h, v8.h[2]\n    prfm        pldl1keep, [x1, #1216]\n    fmla        v31.8h, v1.8h, v8.h[2]\n    prfm        pldl1keep, [x2, #256]\n\n    ld1         {{ v0.4s, v1.4s, v2.4s, v3.4s }}, [x1], #64\n    ins         v8.h[0], v8.h[3]\n    ld1         {{ v4.4s, v5.4s, v6.4s, v7.4s }}, [x1], #64\n\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_128x1_core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: v16 to v31, no need to preserve\n\n// no preservation either for v0-v7...\n// v8..v15 are callee-preserved\n// packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n{% if needs_pragma == true %}\n.cpu generic+fp+simd+fp16\n{% endif %}\n.global {{G}}arm64fp16_mmm_f16_128x1_{{core}}_{{suffix}}\n{{G}}arm64fp16_mmm_f16_128x1_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n    sub         x3, x3, #1\n\n\n    ld1         { v8.h }[0], [ x2 ], #2\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x1 ], #64\n    ld1         { v4.4s, v5.4s, v6.4s, v7.4s }, [ x1 ], #64\n\n    cmp         x3, #0\n    beq         .packed_packed_loop_1_last\n\n    cmp         x3, #4\n    blt        .packed_packed_loop_1\n\n{% capture packed_packed_loop1 %}\n    {% include \"arm64fp16_mmm_f16_128x1/loop1/naive.tmpli\" %}\n{% endcapture %}\n\n{% capture packed_packed_loop2 %}\n    {% include \"arm64fp16_mmm_f16_128x1/loop2/cortex_a55.tmpli\" %}\n{% endcapture %}\n\n.p2align 4\n.packed_packed_loop_4:\n    {{ packed_packed_loop2 }}\n    {{ packed_packed_loop2 }}\n\n    sub         x3, x3, #4\n    cmp         x3, #4\n    bge         .packed_packed_loop_4\n\n    cmp         x3, #0\n    beq         .packed_packed_loop_1_last\n\n.p2align 4\n.packed_packed_loop_1:\n    {{ packed_packed_loop1 }}\n\n    subs        x3, x3, #1\n    bne         .packed_packed_loop_1\n\n// last loop can't read beyond actual input as it's likely not packed and padded\n.packed_packed_loop_1_last:\n    ld1         { v9.8h, v10.8h, v11.8h, v12.8h }, [x1], #64\n    ld1         { v13.8h, v14.8h, v15.8h }, [x1], #48\n\n    fmla        v16.8h, v0.8h, v8.h[0]\n    fmla        v17.8h, v1.8h, v8.h[0]\n    ld1         { v0.8h }, [ x1 ]\n    fmla        v18.8h, v2.8h, v8.h[0]\n    fmla        v19.8h, v3.8h, v8.h[0]\n    fmla        v20.8h, v4.8h, v8.h[0]\n    fmla        v21.8h, v5.8h, v8.h[0]\n    fmla        v22.8h, v6.8h, v8.h[0]\n    fmla        v23.8h, v7.8h, v8.h[0]\n\n    fmla        v24.8h, v9.8h, v8.h[0]\n    fmla        v25.8h, v10.8h, v8.h[0]\n    fmla        v26.8h, v11.8h, v8.h[0]\n    fmla        v27.8h, v12.8h, v8.h[0]\n    fmla        v28.8h, v13.8h, v8.h[0]\n    fmla        v29.8h, v14.8h, v8.h[0]\n    fmla        v30.8h, v15.8h, v8.h[0]\n    fmla        v31.8h, v0.8h, v8.h[0]\n\n    b           .non_linear_loop\n\n{% include \"arm64fp16_mmm_f16_scalars.tmpliq\" from:16, to:31%}\n{% include \"arm64fp16_mmm_f16_per_rows.tmpliq\" mr:128, from:16, to:31%}\n{% include \"arm64fp16_mmm_f16_per_cols.tmpliq\" mr:128, from:16, to:31%}\n{% include \"arm64fp16_mmm_load_tile.tmpliq\" from:16, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]           // c base ptr, rsc\n    cmp         x6, #2\n    beq         .do_per_row_add\n\n    {% for reg in (16..31) %}\n        {% for lane in (0..7) %}\n            ld1 {v0.h}[{{lane}}], [ x5 ], x6\n        {% endfor %}\n        fadd v{{reg}}.8h, v{{reg}}.8h, v0.8h\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.do_per_row_add:\n    ld1     {v0.8h-v3.8h}, [x5], #64\n    ld1     {v4.8h-v7.8h}, [x5], #64\n    ld1     {v8.8h-v11.8h}, [x5], #64\n    ld1     {v12.8h-v15.8h}, [x5], #64\n\n    {% for r in (0..15) %}\n        fadd v{{r| plus: 16}}.8h, v{{r | plus: 16}}.8h, v{{r}}.8h\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x3, [x0, #16]\n    ldr     x2, [x0, #8]\n\n    ld1         {v8.h}[0], [ x3 ]\n\n    {% for r in (0..7) %}\n        ldr     q{{r}}, [x2], #16\n    {% endfor %}\n\n    fmla        v16.8h, v0.8h, v8.h[0]\n    ldr         q0, [x2], #16\n    fmla        v17.8h, v1.8h, v8.h[0] \n    ldr         q1, [x2], #16\n    fmla        v18.8h, v2.8h, v8.h[0] \n    ldr         q2, [x2], #16\n    fmla        v19.8h, v3.8h, v8.h[0] \n    ldr         q3, [x2], #16\n    fmla        v20.8h, v4.8h, v8.h[0] \n    ldr         q4, [x2], #16\n    fmla        v21.8h, v5.8h, v8.h[0] \n    ldr         q5, [x2], #16\n    fmla        v22.8h, v6.8h, v8.h[0] \n    ldr         q6, [x2], #16\n    fmla        v23.8h, v7.8h, v8.h[0] \n    ldr         q7, [x2], #16\n\n    fmla        v24.8h, v0.8h, v8.h[0]\n    fmla        v25.8h, v1.8h, v8.h[0] \n    fmla        v26.8h, v2.8h, v8.h[0] \n    fmla        v27.8h, v3.8h, v8.h[0] \n    fmla        v28.8h, v4.8h, v8.h[0] \n    fmla        v29.8h, v5.8h, v8.h[0] \n    fmla        v30.8h, v6.8h, v8.h[0] \n    fmla        v31.8h, v7.8h, v8.h[0] \n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]                // c base ptr, rsc$\n\n    cmp         x6, #2\n    beq         .store_strides_contig\n\n    {% for reg in (16..31) %}\n        {% for lane in (0..7) %}\n            st1 { v{{reg}}.h }[{{lane}}], [ x5 ], x6\n        {% endfor %}\n    {% endfor %}\n    b           .non_linear_loop\n\n.store_strides_contig:\n\n    {% for reg in (16..31) %}\n        st1 { v{{reg}}.8h }, [ x5 ], #16\n    {% endfor %}\n    b           .non_linear_loop\n\n.return:\n\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_16x8/loop1/naive.tmpli",
    "content": "\nfmla        v16.8h, v0.8h, v4.h[0]\nfmla        v17.8h, v1.8h, v4.h[0]\nfmla        v18.8h, v0.8h, v4.h[1]\nfmla        v19.8h, v1.8h, v4.h[1]\nfmla        v20.8h, v0.8h, v4.h[2]\nfmla        v21.8h, v1.8h, v4.h[2]\nfmla        v22.8h, v0.8h, v4.h[3]\nfmla        v23.8h, v1.8h, v4.h[3]\n\nfmla        v24.8h, v0.8h, v4.h[4]\nfmla        v25.8h, v1.8h, v4.h[4]\nfmla        v26.8h, v0.8h, v4.h[5]\nfmla        v27.8h, v1.8h, v4.h[5]\nfmla        v28.8h, v0.8h, v4.h[6]\nfmla        v29.8h, v1.8h, v4.h[6]\nfmla        v30.8h, v0.8h, v4.h[7]\nfmla        v31.8h, v1.8h, v4.h[7]\n\nld1         {{ v0.8h, v1.8h }}, [x1], #32\nld1         {{ v4.8h }}, [x2], #16\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_16x8/loop2/cortex_a55.tmpli",
    "content": "fmla        v16.8h, v0.8h, v4.h[0]\nldr         d2, [x1], #8\nfmla        v17.8h, v1.8h, v4.h[0]\nldr         d6, [x2], #8\nfmla        v18.8h, v0.8h, v4.h[1]\nldr         x5, [x1], #8\nfmla        v19.8h, v1.8h, v4.h[1]\nldr         x7, [x2], #8\nfmla        v20.8h, v0.8h, v4.h[2]\nldr         d3, [x1], #8\nfmla        v21.8h, v1.8h, v4.h[2]\nfmla        v22.8h, v0.8h, v4.h[3]\nldr         x6, [x1], #8\nfmla        v23.8h, v1.8h, v4.h[3]\n\nfmla        v24.8h, v0.8h, v4.h[4]\nfmla        v25.8h, v1.8h, v4.h[4]\nfmla        v26.8h, v0.8h, v4.h[5]\nfmla        v27.8h, v1.8h, v4.h[5]\nfmla        v28.8h, v0.8h, v4.h[6]\nins         v2.d[1], x5\nfmla        v29.8h, v1.8h, v4.h[6]\nins         v6.d[1], x7\nfmla        v30.8h, v0.8h, v4.h[7]\nins         v3.d[1], x6\nfmla        v31.8h, v1.8h, v4.h[7]\n\nfmla        v16.8h, v2.8h, v6.h[0]\nldr         d0, [x1], #8\nfmla        v17.8h, v3.8h, v6.h[0]\nldr         d4, [x2], #8\nfmla        v18.8h, v2.8h, v6.h[1]\nldr         x5, [x1], #8\nfmla        v19.8h, v3.8h, v6.h[1]\nldr         x7, [x2], #8\nfmla        v20.8h, v2.8h, v6.h[2]\nldr         d1, [x1], #8\nfmla        v21.8h, v3.8h, v6.h[2]\nfmla        v22.8h, v2.8h, v6.h[3]\nldr         x6, [x1], #8\nfmla        v23.8h, v3.8h, v6.h[3]\n\nfmla        v24.8h, v2.8h, v6.h[4]\nfmla        v25.8h, v3.8h, v6.h[4]\nfmla        v26.8h, v2.8h, v6.h[5]\nfmla        v27.8h, v3.8h, v6.h[5]\nfmla        v28.8h, v2.8h, v6.h[6]\nins         v0.d[1], x5\nfmla        v29.8h, v3.8h, v6.h[6]\nins         v4.d[1], x7\nfmla        v30.8h, v2.8h, v6.h[7]\nins         v1.d[1], x6\nfmla        v31.8h, v3.8h, v6.h[7]\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_16x8_core.tmpl",
    "content": "// vim: ft=arm\n\n// x20..x27 are used, callee-preserved\n\n// C tile regs: v16 to v31, (scratch)\n// \n//      v16[0] v18[0] v20[0] v22[0] v24[0] v26[0] v28[0] v30[0]\n//      v16[1] v18[1] \n//      v16[2] v18[2] \n//      v16[3] v18[3]\n//                     \n//      v17[0] v19[0] v21[0] v23[0] v25[0] v27[0] v29[0] v31[0]\n//      v17[1] v19[1] \n//      v17[2] v19[2] \n//      v17[3] v19[3] \n\n// v8 is used, d8 (lower half) must preserved\n// v0-v7 (scratch registers)\n//  packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n//  packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n{% if needs_pragma == true %}\n.cpu generic+fp+simd+fp16\n{% endif %}\n.global {{G}}arm64fp16_mmm_f16_16x8_{{core}}_{{suffix}}\n{{G}}arm64fp16_mmm_f16_16x8_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n\n    str         q8, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n.packed_packed:\n    ld1         { v0.4s, v1.4s }, [ x1 ], #32\n    ld1         { v4.4s }, [ x2 ], #16\n\n{% capture packed_packed_loop1 %}\n    {% include \"arm64fp16_mmm_f16_16x8/loop1/naive.tmpli\" %}\n{% endcapture %}\n\n{% capture packed_packed_loop2 %}\n    {% if core == \"a55\" %}\n        {% include \"arm64fp16_mmm_f16_16x8/loop2/cortex_a55.tmpli\" %}\n    {% else %}\n        {{ packed_packed_loop1 }}\n        {{ packed_packed_loop1 }}\n    {% endif %}\n{% endcapture %}\n\n    cmp         x3, #4\n    blt         .packed_packed_loop_1\n\n.p2align 4\n.packed_packed_loop_4:\n    {{ packed_packed_loop2 }}\n    {{ packed_packed_loop2 }}\n\n    sub x3, x3, #4\n    cmp x3, #4\n    bge .packed_packed_loop_4\n\n\n    cmp x3, #0\n    beq .non_linear_loop\n\n.p2align 4\n.packed_packed_loop_1:\n    {{ packed_packed_loop1 }}\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b .non_linear_loop\n\n{% include \"arm64fp16_mmm_f16_scalars.tmpliq\" from:16, to:31%}\n{% include \"arm64fp16_mmm_f16_per_rows.tmpliq\" mr:16, from:16, to:31 %}\n{% include \"arm64fp16_mmm_f16_per_cols.tmpliq\" mr:16, from:16, to:31 %}\n{% include \"arm64fp16_mmm_load_tile.tmpliq\" from:16, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    {% for col in (8..15) %}\n        mov x4, x5\n        {% for reg in (0..1) %}\n            {% for lane in (0..7) %}\n                ld1 {v0.h}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            fadd v{{col | times:2 | plus: reg}}.8h, v{{col | times:2 | plus: reg}}.8h, v0.8h\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x2, [x0, #8]\n    ldr     x3, [x0, #16]\n\n    ld1         { v0.4s, v1.4s }, [ x2 ], #32\n    ld1         { v4.4s }, [ x3 ], #16\n\n    fmla        v16.8h, v0.8h, v4.h[0]\n    fmla        v17.8h, v1.8h, v4.h[0]\n    fmla        v18.8h, v0.8h, v4.h[1]\n    fmla        v19.8h, v1.8h, v4.h[1]\n    fmla        v20.8h, v0.8h, v4.h[2]\n    fmla        v21.8h, v1.8h, v4.h[2]\n    fmla        v22.8h, v0.8h, v4.h[3]\n    fmla        v23.8h, v1.8h, v4.h[3]\n\n    fmla        v24.8h, v0.8h, v4.h[4]\n    fmla        v25.8h, v1.8h, v4.h[4]\n    fmla        v26.8h, v0.8h, v4.h[5]\n    fmla        v27.8h, v1.8h, v4.h[5]\n    fmla        v28.8h, v0.8h, v4.h[6]\n    fmla        v29.8h, v1.8h, v4.h[6]\n    fmla        v30.8h, v0.8h, v4.h[7]\n    fmla        v31.8h, v1.8h, v4.h[7]\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x6, #2\n    bne         .store_strides_generic\n\n    {% for col in (8..15) %}\n        str q{{col | times:2 }}, [ x5 ]\n        str q{{col | times:2 | plus: 1}}, [ x5, #16 ]\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store_strides_generic:\n\n    {% for col in (8..15) %}\n        mov x4, x5\n        {% for reg in (0..1) %}\n            {% for lane in (0..7) %}\n                st1 { v{{col | times:2 | plus: reg}}.h }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n    ldr         q8, [sp], #16\n\n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_32x4/loop1/naive.tmpli",
    "content": "\nfmla        v16.8h, v0.8h, v4.h[0]\nfmla        v17.8h, v1.8h, v4.h[0]\nfmla        v18.8h, v2.8h, v4.h[0]\nfmla        v19.8h, v3.8h, v4.h[0]\nfmla        v20.8h, v0.8h, v4.h[1]\nfmla        v21.8h, v1.8h, v4.h[1]\nfmla        v22.8h, v2.8h, v4.h[1]\nfmla        v23.8h, v3.8h, v4.h[1]\n\nfmla        v24.8h, v0.8h, v4.h[2]\nfmla        v25.8h, v1.8h, v4.h[2]\nfmla        v26.8h, v2.8h, v4.h[2]\nfmla        v27.8h, v3.8h, v4.h[2]\nfmla        v28.8h, v0.8h, v4.h[3]\nfmla        v29.8h, v1.8h, v4.h[3]\nfmla        v30.8h, v2.8h, v4.h[3]\nfmla        v31.8h, v3.8h, v4.h[3]\n\nld1         {{ v0.8h, v1.8h, v2.8h, v3.8h }}, [ x1 ], #64\nldr         d4, [x2], #8\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_32x4/loop2/cortex_a55.tmpli",
    "content": "// mul a: v0, v1, v2, v3 b: v4\n// load a: v5(d5/x5), v6(d6,x6), v7(d7,x7), v8(d8, x8)\n// load b: v9 as d9\n\nfmla        v16.8h, v0.8h, v4.h[0]\nldr         d5, [x1], #8\nfmla        v17.8h, v1.8h, v4.h[0]\nldr         d9, [x2], #8\nfmla        v18.8h, v2.8h, v4.h[0]\nldr         x5, [x1], #8\nfmla        v19.8h, v3.8h, v4.h[0]\nfmla        v20.8h, v0.8h, v4.h[1]\nldr         d6, [x1], #8\nfmla        v21.8h, v1.8h, v4.h[1]\nldr         x6, [x1], #8\nfmla        v22.8h, v2.8h, v4.h[1]\nldr         d7, [x1], #8\nfmla        v23.8h, v3.8h, v4.h[1]\nldr         x7, [x1], #8\n\nfmla        v24.8h, v0.8h, v4.h[2]\nldr         d8, [x1], #8\nfmla        v25.8h, v1.8h, v4.h[2]\nldr         x8, [x1], #8\nfmla        v26.8h, v2.8h, v4.h[2]\nins         v5.d[1], x5\nfmla        v27.8h, v3.8h, v4.h[2]\nins         v6.d[1], x6\nfmla        v28.8h, v0.8h, v4.h[3]\nins         v7.d[1], x7\nfmla        v29.8h, v1.8h, v4.h[3]\nins         v8.d[1], x8\nfmla        v30.8h, v2.8h, v4.h[3]\nins         v9.d[1], x9\nfmla        v31.8h, v3.8h, v4.h[3]\n\n// mul a: v5, v6, v7, v8 b: v9\n// load a: v0(d0/x5), v1(d1,x6), v2(d2,x7), v3(d3, x8)\n// load b: v4 as d4\n\nfmla        v16.8h, v5.8h, v9.h[0]\nldr         d0, [x1], #8\nfmla        v17.8h, v6.8h, v9.h[0]\nldr         d4, [x2], #8\nfmla        v18.8h, v7.8h, v9.h[0]\nldr         x5, [x1], #8\nfmla        v19.8h, v8.8h, v9.h[0]\nfmla        v20.8h, v5.8h, v9.h[1]\nldr         d1, [x1], #8\nfmla        v21.8h, v6.8h, v9.h[1]\nldr         x6, [x1], #8\nfmla        v22.8h, v7.8h, v9.h[1]\nldr         d2, [x1], #8\nfmla        v23.8h, v8.8h, v9.h[1]\nldr         x7, [x1], #8\n\nfmla        v24.8h, v5.8h, v9.h[2]\nldr         d3, [x1], #8\nfmla        v25.8h, v6.8h, v9.h[2]\nldr         x8, [x1], #8\nfmla        v26.8h, v7.8h, v9.h[2]\nins         v0.d[1], x5\nfmla        v27.8h, v8.8h, v9.h[2]\nins         v1.d[1], x6\nfmla        v28.8h, v5.8h, v9.h[3]\nins         v2.d[1], x7\nfmla        v29.8h, v6.8h, v9.h[3]\nins         v3.d[1], x8\nfmla        v30.8h, v7.8h, v9.h[3]\nins         v4.d[1], x9\nfmla        v31.8h, v8.8h, v9.h[3]\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_32x4_core.tmpl",
    "content": "// vim: ft=arm\n\n// x20..x27 are used, callee-preserved\n\n// C tile regs: v16 to v31, (scratch)\n\n// v8 is used, d8 (lower half) must preserved\n// v0-v7 (scratch registers)\n//  packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n//  packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n{% if needs_pragma == true %}\n.cpu generic+fp+simd+fp16\n{% endif %}\n.global {{G}}arm64fp16_mmm_f16_32x4_{{core}}_{{suffix}}\n{{G}}arm64fp16_mmm_f16_32x4_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x1 ], #64\n    ldr         d4, [x2], #8\n\n{% capture packed_packed_loop1 %}\n    {% include \"arm64fp16_mmm_f16_32x4/loop1/naive.tmpli\" %}\n{% endcapture %}\n\n{% capture packed_packed_loop2 %}\n    {% if core == \"a55\" %}\n        {% include \"arm64fp16_mmm_f16_32x4/loop2/cortex_a55.tmpli\" %}\n    {% else %}\n        {{ packed_packed_loop1 }}\n        {{ packed_packed_loop1 }}\n    {% endif %}\n{% endcapture %}\n\n    cmp         x3, #4\n    blt         .packed_packed_loop_1\n\n.p2align 4\n.packed_packed_loop_4:\n    {{ packed_packed_loop2 }}\n    {{ packed_packed_loop2 }}\n\n    sub x3, x3, #4\n    cmp x3, #4\n    bge .packed_packed_loop_4\n\n    cmp x3, #0\n    beq .non_linear_loop\n\n.p2align 4\n.packed_packed_loop_1:\n    {{ packed_packed_loop1 }}\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b   .non_linear_loop\n\n{% include \"arm64fp16_mmm_f16_scalars.tmpliq\" from:16, to:31%}\n{% include \"arm64fp16_mmm_f16_per_rows.tmpliq\" mr:32, from:16, to:31 %}\n{% include \"arm64fp16_mmm_f16_per_cols.tmpliq\" mr:32, from:16, to:31 %}\n{% include \"arm64fp16_mmm_load_tile.tmpliq\" from:16, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    {% for col in (0..3) %}\n        mov x4, x5\n        {% for reg in (0..3) %}\n            {% for lane in (0..7) %}\n                ld1 {v0.h}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            fadd v{{col | times:4 | plus: 16| plus: reg}}.8h, v{{col | times:4 | plus: 16 | plus: reg}}.8h, v0.8h\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x2, [x0, #8]\n    ldr     x3, [x0, #16]\n\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [ x2 ]\n    ldr         d4, [x3]\n\n    fmla        v16.8h, v0.8h, v4.h[0]\n    fmla        v17.8h, v1.8h, v4.h[0]\n    fmla        v18.8h, v2.8h, v4.h[0]\n    fmla        v19.8h, v3.8h, v4.h[0]\n    fmla        v20.8h, v0.8h, v4.h[1]\n    fmla        v21.8h, v1.8h, v4.h[1]\n    fmla        v22.8h, v2.8h, v4.h[1]\n    fmla        v23.8h, v3.8h, v4.h[1]\n\n    fmla        v24.8h, v0.8h, v4.h[2]\n    fmla        v25.8h, v1.8h, v4.h[2]\n    fmla        v26.8h, v2.8h, v4.h[2]\n    fmla        v27.8h, v3.8h, v4.h[2]\n    fmla        v28.8h, v0.8h, v4.h[3]\n    fmla        v29.8h, v1.8h, v4.h[3]\n    fmla        v30.8h, v2.8h, v4.h[3]\n    fmla        v31.8h, v3.8h, v4.h[3]\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x6, #2\n    bne           .store_strides_generic\n\n    {% for col in (0..3) %}\n        str q{{col | times:4 | plus:16 | plus: 0}}, [ x5 ]\n        str q{{col | times:4 | plus:16 | plus: 1}}, [ x5, #16 ]\n        str q{{col | times:4 | plus:16 | plus: 2}}, [ x5, #32 ]\n        str q{{col | times:4 | plus:16 | plus: 3}}, [ x5, #48 ]\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store_strides_generic:\n\n    {% for col in (0..3) %}\n        mov x4, x5\n        {% for reg in (0..3) %}\n            {% for lane in (0..7) %}\n                st1 { v{{col | times:4 | plus: 16 | plus: reg}}.h }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n    ldp         d8, d9, [sp], #16\n\n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_32x6.core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: v16 to v31, no need to preserve\n\n// no preservation either for v0-v7...\n// v8..v15 are callee-preserved\n// packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n{% if needs_pragma == true %}\n.cpu generic+fp+simd+fp16\n{% endif %}\n.global {{G}}arm64fp16_mmm_f16_32x6_{{core}}_{{suffix}}\n{{G}}arm64fp16_mmm_f16_32x6_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldp         x2, x4, [x0, #24]   // b, packing\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n.p2align 4\n.packed_packed_loop_1:\n    ld1         { v7.8h }, [ x2 ]\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [ x1 ], #64\n    add         x2, x2, 12\n\n{% for row in (0..3) %}\n    {% for col in (0..5) %}\n        fmla        v{{ col|times:4|plus:8|plus:row}}.8h, v{{row}}.8h, v7.h[{{col}}]\n    {% endfor %}\n    /*\n    {% for col in (0..1) %}\n        fmla        v{{ col|plus:4|times:4|plus:8|plus:row}}.8h, v{{row}}.8h, v6.h[{{col}}]\n    {% endfor %}\n    */\n{% endfor %}\n\n    subs        x3, x3, #1\n    bne         .packed_packed_loop_1\n\n    b           .non_linear_loop\n\n{% include \"arm64fp16_mmm_f16_scalars.tmpliq\" from:8, to:31%}\n{% include \"arm64fp16_mmm_f16_per_rows.tmpliq\" mr:32, from:8, to:31%}\n{% include \"arm64fp16_mmm_f16_per_cols.tmpliq\" mr:32, from:8, to:31%}\n{% include \"arm64fp16_mmm_load_tile.tmpliq\" from:8, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    {% for col in (0..5) %}\n        mov x4, x5\n        {% for reg in (0..3) %}\n            {% for lane in (0..7) %}\n                ld1 {v0.h}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            fadd v{{col | times:4 | plus: 8| plus: reg}}.8h, v{{col | times:4 | plus: 8 | plus: reg}}.8h, v0.8h\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.do_per_row_add:\n    ld1     {v0.8h-v3.8h}, [x5], #64\n    ld1     {v4.8h-v7.8h}, [x5], #64\n\n    {% for r in (0..7) %}\n        fadd v{{r| plus: 24}}.8h, v{{r | plus: 24}}.8h, v{{r}}.8h\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldp         x2, x3, [x0, #8]\n\n    ld1         { v7.d }[0], [ x3 ], #8\n    ld1         { v7.s }[2], [ x3 ], #4\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [ x2 ], #64\n\n{% for row in (0..3) %}\n    {% for col in (0..5) %}\n        fmla        v{{ col|times:4|plus:8|plus:row}}.8h, v{{row}}.8h, v7.h[{{col}}]\n    {% endfor %}\n{% endfor %}\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]                // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]               // csc, item_size\n\n    cmp         x6, #2\n    beq         .store_strides_contig\n\n    {% for col in (0..5) %}\n        mov x4, x5\n        {% for reg in (0..3) %}\n            {% for lane in (0..7) %}\n                st1 { v{{col | times:4 | plus: 8 | plus: reg}}.h }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n    b           .non_linear_loop\n\n.store_strides_contig:\n\n    {% for col in (0..5) %}\n        mov x4, x5\n        {% for r in (0..3) %}\n            st1 { v{{col | times:4 | plus: 8 | plus: r}}.8h }, [ x4 ], 16\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_64x1.core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: v16 to v31, no need to preserve\n\n// no preservation either for v0-v7...\n// v8..v15 are callee-preserved\n// packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n{% if needs_pragma == true %}\n.cpu generic+fp+simd+fp16\n{% endif %}\n.global {{G}}arm64fp16_mmm_f16_64x1_{{core}}_{{suffix}}\n{{G}}arm64fp16_mmm_f16_64x1_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldp         x2, x4, [x0, #24]   // b, packing\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    cmp         x4, #1\n    beq         .q4f16se\n    \n    cmp         x4, #2\n    beq         .q4f16\n\n    \n\n.p2align 4\n.packed_packed_loop_1:\n    ld1         { v8.h }[0], [ x2 ], #2\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [ x1 ], #64\n    ld1         { v4.8h, v5.8h, v6.8h, v7.8h }, [ x1 ], #64\n\n    fmla        v24.8h, v0.8h, v8.h[0]\n    fmla        v25.8h, v1.8h, v8.h[0]\n    fmla        v26.8h, v2.8h, v8.h[0]\n    fmla        v27.8h, v3.8h, v8.h[0]\n    fmla        v28.8h, v4.8h, v8.h[0]\n    fmla        v29.8h, v5.8h, v8.h[0]\n    fmla        v30.8h, v6.8h, v8.h[0]\n    fmla        v31.8h, v7.8h, v8.h[0]\n    subs        x3, x3, #1\n    bne         .packed_packed_loop_1\n\n    b           .non_linear_loop\n\n.p2align 8\n.q40f16_const:\n    .byte 0xc8, 0xc7, 0xc6, 0xc5, 0xc4, 0xc2, 0xc0, 0xbc\n    .byte 0x00, 0x3c, 0x40, 0x42, 0x44, 0x45, 0x46, 0x47\n\n.q4f16se:\n    adr      x4, .q40f16_const\n    movi     v15.16b, 15\n    ld1      {v13.16b}, [ x4 ]\n    eor      v12.16b, v12.16b, v12.16b\n\n.q4f16se_outerloop:\n{% for i in (0..7) %}\n    eor      v{{i|plus:16}}.16b, v{{i|plus:16}}.16b, v{{i|plus:16}}.16b\n{% endfor %}\n    mov         x4, #32\n\n.p2align 4\n.q4f16se_innerloop:\n        ld1      { v9.16b-v10.16b }, [x1], #32\n        ld1      { v8.h }[0], [ x2 ], #2\n\n        and      v0.16b, v9.16b, v15.16b\n        ushr     v2.16b, v9.16b, 4\n\n        and      v4.16b, v10.16b, v15.16b\n        ushr     v6.16b, v10.16b, 4\n\n        tbl      v0.16b, { v13.16b }, v0.16b\n        tbl      v2.16b, { v13.16b }, v2.16b\n        tbl      v4.16b, { v13.16b }, v4.16b\n        tbl      v6.16b, { v13.16b }, v6.16b\n\n        zip2     v1.16b, v12.16b, v0.16b\n        zip2     v3.16b, v12.16b, v2.16b\n        zip2     v5.16b, v12.16b, v4.16b\n        zip2     v7.16b, v12.16b, v6.16b\n\n        zip1     v0.16b, v12.16b, v0.16b\n        zip1     v2.16b, v12.16b, v2.16b\n        zip1     v4.16b, v12.16b, v4.16b\n        zip1     v6.16b, v12.16b, v6.16b\n\n{% for i in (0..7) %}\n        fmla        v{{ i|plus: 16 }}.8h, v{{i}}.8h, v8.h[0]\n{% endfor %}\n\n    subs        x4, x4, #1\n    bne         .q4f16se_innerloop\n\n    // scales\n    ld1         { v0.8h-v3.8h }, [ x1 ], #64\n    ld1         { v4.8h-v7.8h }, [ x1 ], #64\n\n{% for i in (0..7) %}\n       fmla     v{{i|plus:24}}.8h, v{{i}}.8h, v{{i|plus:16}}.8h\n{% endfor %}\n\n    subs        x3, x3, #32\n    bne         .q4f16se_outerloop\n\n    b           .non_linear_loop\n    \n.q4f16:\n    adr      x4, .q40f16_const\n    movi     v15.16b, 15\n    ld1      {v13.16b}, [ x4 ]\n    eor      v12.16b, v12.16b, v12.16b\n\n.q4f16_outerloop:\n    // scales\n    ld1         { v16.8h-v19.8h }, [ x1 ], #64\n    ld1         { v20.8h-v23.8h }, [ x1 ], #64\n    mov         x4, #32\n\n.p2align 4\n.q4f16_innerloop:\n        ld1      { v9.16b-v10.16b }, [x1], #32\n        ld1      { v8.h }[0], [ x2 ], #2\n\n        and      v0.16b, v9.16b, v15.16b\n        ushr     v2.16b, v9.16b, 4\n\n        and      v4.16b, v10.16b, v15.16b\n        ushr     v6.16b, v10.16b, 4\n\n        tbl      v0.16b, { v13.16b }, v0.16b\n        tbl      v2.16b, { v13.16b }, v2.16b\n        tbl      v4.16b, { v13.16b }, v4.16b\n        tbl      v6.16b, { v13.16b }, v6.16b\n\n        zip2     v1.16b, v12.16b, v0.16b\n        zip2     v3.16b, v12.16b, v2.16b\n        zip2     v5.16b, v12.16b, v4.16b\n        zip2     v7.16b, v12.16b, v6.16b\n\n        zip1     v0.16b, v12.16b, v0.16b\n        zip1     v2.16b, v12.16b, v2.16b\n        zip1     v4.16b, v12.16b, v4.16b\n        zip1     v6.16b, v12.16b, v6.16b\n\n{% for i in (0..7) %}\n       fmul     v{{i}}.8h, v{{i}}.8h, v{{i|plus:16}}.8h\n{% endfor %}\n\n{% for i in (0..7) %}\n        fmla        v{{ i|plus: 24 }}.8h, v{{i}}.8h, v8.h[0]\n{% endfor %}\n\n    subs        x4, x4, #1\n    bne         .q4f16_innerloop\n\n    subs        x3, x3, #32\n    bne         .q4f16_outerloop\n\n    b           .non_linear_loop\n\n{% include \"arm64fp16_mmm_f16_scalars.tmpliq\" from:24, to:31%}\n{% include \"arm64fp16_mmm_f16_per_rows.tmpliq\" mr:64, from:24, to:31%}\n{% include \"arm64fp16_mmm_f16_per_cols.tmpliq\" mr:64, from:24, to:31%}\n{% include \"arm64fp16_mmm_load_tile.tmpliq\" from:24, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]           // c base ptr, rsc\n    cmp         x6, #2\n    beq         .do_per_row_add\n\n    {% for reg in (24..31) %}\n        {% for lane in (0..7) %}\n            ld1 {v0.h}[{{lane}}], [ x5 ], x6\n        {% endfor %}\n        fadd v{{reg}}.8h, v{{reg}}.8h, v0.8h\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.do_per_row_add:\n    ld1     {v0.8h-v3.8h}, [x5], #64\n    ld1     {v4.8h-v7.8h}, [x5], #64\n\n    {% for r in (0..7) %}\n        fadd v{{r| plus: 24}}.8h, v{{r | plus: 24}}.8h, v{{r}}.8h\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x3, [x0, #16]\n    ldr     x2, [x0, #8]\n\n    ld1         {v8.h}[0], [ x3 ]\n\n    {% for r in (0..7) %}\n        ldr     q{{r}}, [x2], #16\n    {% endfor %}\n\n    fmla        v24.8h, v0.8h, v8.h[0]\n    fmla        v25.8h, v1.8h, v8.h[0] \n    fmla        v26.8h, v2.8h, v8.h[0] \n    fmla        v27.8h, v3.8h, v8.h[0] \n    fmla        v28.8h, v4.8h, v8.h[0] \n    fmla        v29.8h, v5.8h, v8.h[0] \n    fmla        v30.8h, v6.8h, v8.h[0] \n    fmla        v31.8h, v7.8h, v8.h[0] \n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]                // c base ptr, rsc$\n\n    cmp         x6, #2\n    beq         .store_strides_contig\n\n    {% for reg in (24..31) %}\n        {% for lane in (0..7) %}\n            st1 { v{{reg}}.h }[{{lane}}], [ x5 ], x6\n        {% endfor %}\n    {% endfor %}\n    b           .non_linear_loop\n\n.store_strides_contig:\n\n    {% for reg in (24..31) %}\n        st1 { v{{reg}}.8h }, [ x5 ], #16\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_64x3.core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: v16 to v31, no need to preserve\n\n// no preservation either for v0-v7...\n// v8..v15 are callee-preserved\n// packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n{% if needs_pragma == true %}\n.cpu generic+fp+simd+fp16\n{% endif %}\n.global {{G}}arm64fp16_mmm_f16_64x3_{{core}}_{{suffix}}\n{{G}}arm64fp16_mmm_f16_64x3_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldp         x2, x4, [x0, #24]   // b, packing\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n.p2align 4\n.packed_packed_loop_1:\n    ld1         { v7.4s }, [ x2 ]\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [ x1 ], #64\n    ld1         { v4.8h, v5.8h, v6.8h }, [ x1 ], #48\n    add         x2, x2, #6\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:8}}.8h, v0.8h, v7.h[{{ col }}]\n{% endfor %}\n\n    ld1         { v0.8h }, [ x1 ], #16\n\n{% for row in (1..6) %}\n    {% for col in (0..2) %}\n        fmla        v{{ col|times:8|plus:8|plus:row}}.8h, v{{row}}.8h, v7.h[{{col}}]\n    {% endfor %}\n{% endfor %}\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:15}}.8h, v0.8h, v7.h[{{ col }}]\n{% endfor %}\n\n    subs        x3, x3, #1\n    bne         .packed_packed_loop_1\n\n    b           .non_linear_loop\n\n{% include \"arm64fp16_mmm_f16_scalars.tmpliq\" from:8, to:31%}\n{% include \"arm64fp16_mmm_f16_per_rows.tmpliq\" mr:64, from:8, to:31%}\n{% include \"arm64fp16_mmm_f16_per_cols.tmpliq\" mr:64, from:8, to:31%}\n{% include \"arm64fp16_mmm_load_tile.tmpliq\" from:8, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    {% for col in (0..2) %}\n        mov x4, x5\n        {% for reg in (0..7) %}\n            {% for lane in (0..7) %}\n                ld1 {v0.h}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            fadd v{{col | times:8 | plus: 8| plus: reg}}.8h, v{{col | times:8 | plus: 8 | plus: reg}}.8h, v0.8h\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.do_per_row_add:\n    ld1     {v0.8h-v3.8h}, [x5], #64\n    ld1     {v4.8h-v7.8h}, [x5], #64\n\n    {% for r in (0..7) %}\n        fadd v{{r| plus: 24}}.8h, v{{r | plus: 24}}.8h, v{{r}}.8h\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldp         x2, x3, [x0, #8]\n\n    ld1         { v7.s }[0], [ x3 ], #4\n    ld1         { v7.h }[2], [ x3 ], #2\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [ x2 ], #64\n    ld1         { v4.8h, v5.8h, v6.8h }, [ x2 ], #48\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:8}}.8h, v0.8h, v7.h[{{ col }}]\n{% endfor %}\n\n    ld1         { v0.8h }, [ x2 ], #16\n\n{% for row in (1..6) %}\n    {% for col in (0..2) %}\n        fmla        v{{ col|times:8|plus:8|plus:row}}.8h, v{{row}}.8h, v7.h[{{col}}]\n    {% endfor %}\n{% endfor %}\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:15}}.8h, v0.8h, v7.h[{{ col }}]\n{% endfor %}\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]                // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]               // csc, item_size\n\n    cmp         x6, #2\n    beq         .store_strides_contig\n\n    {% for col in (0..2) %}\n        mov x4, x5\n        {% for reg in (0..7) %}\n            {% for lane in (0..7) %}\n                st1 { v{{col | times:8 | plus: 8 | plus: reg}}.h }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n    b           .non_linear_loop\n\n.store_strides_contig:\n\n    {% for col in (0..2) %}\n        mov x4, x5\n        {% for r in (0..7) %}\n            st1 { v{{col | times:8 | plus: 8 | plus: r}}.8h }, [ x4 ], 16\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_per_cols.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"arm64fp16_mmm_8h_per_col.tmpliq\" label:\"per_col_min\", op:\"fmin\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_col.tmpliq\" label:\"per_col_max\", op:\"fmax\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_col.tmpliq\" label:\"per_col_mul\", op:\"fmul\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_col.tmpliq\" label:\"per_col_add\", op:\"fadd\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_col.tmpliq\" label:\"per_col_sub\", op:\"fsub\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_col.tmpliq\" label:\"per_col_sub_flipped\", op:\"fsub\", mr:mr, from:from, to:to, flipped: true%}\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_per_rows.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"arm64fp16_mmm_8h_per_row.tmpliq\" label:\"per_row_min\", op:\"fmin\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_row.tmpliq\" label:\"per_row_max\", op:\"fmax\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_row.tmpliq\" label:\"per_row_mul\", op:\"fmul\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_row.tmpliq\" label:\"per_row_add\", op:\"fadd\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_row.tmpliq\" label:\"per_row_sub\", op:\"fsub\", mr:mr, from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_per_row.tmpliq\" label:\"per_row_sub_flipped\", op:\"fsub\", mr:mr, from:from, to:to, flipped: true%}\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_f16_scalars.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"arm64fp16_mmm_8h_scalar.tmpliq\" label:\"scalar_min\", op:\"fmin\", from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_scalar.tmpliq\" label:\"scalar_max\", op:\"fmax\", from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_scalar.tmpliq\" label:\"scalar_mul\", op:\"fmul\", from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_scalar.tmpliq\" label:\"scalar_add\", op:\"fadd\", from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_scalar.tmpliq\" label:\"scalar_sub\", op:\"fsub\", from:from, to:to %}\n{% include \"arm64fp16_mmm_8h_scalar.tmpliq\" label:\"scalar_sub_flipped\", op:\"fsub\", from:from, to:to, flipped:true %}\n\n.clear:\n{% for r in (from..to) %}\n    eor         v{{r}}.8b, v{{r}}.8b, v{{r}}.8b\n{% endfor %}\n    b .non_linear_loop\n\n.leaky_relu:\n    add         x2, x0, #8\n    ld1         {v4.s}[0], [ x2 ]\n    dup         v4.8h, v4.h[0]\n\n    // bsl cond/dst, then, else\n    // fcmge dst, src, #0.0\n    {% for r in (from..to) %}\n        fmul  v0.8h, v{{r}}.8h, v4.8h\n        fcmge v1.8h, v{{r}}.8h, #0.0\n        bsl   v1.16b, v{{r}}.16b, v0.16b\n        and   v{{r}}.16b, v1.16b, v1.16b\n    {% endfor %}\n\n    b .non_linear_loop\n\n\n.q_scale:\n.q_shl:\n.q_shr:\n    b .unsupported\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_mmm_load_tile.tmpliq",
    "content": "// vim: ft=arm\n\n.load_tile:\n    ldr         x2, [ x0, #8 ]\n    {% for reg in (from..to) %}\n        ld1         { v{{reg}}.4s }, [ x2 ], #16\n    {% endfor %}\n\n    b           .non_linear_loop\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_sigmoid_f16_8n.tmpl",
    "content": "// vim: ft=arm\n\n// no preservation either for v0-v7 and v16-v31\n\n.text\n.align 4\n\n{% if needs_pragma == true %}\n.cpu generic+fp+simd+fp16\n{% endif %}\n.global {{G}}arm64fp16_sigmoid_f16_8n_{{suffix}}\n{{G}}arm64fp16_sigmoid_f16_8n_{{suffix}}:\n\n    cmp         x1, #0\n    beq         .return\n\n    adr         x2, .coeffs_num\n    ld1         { v0.8h }, [x2]\n    dup         v5.8h, v0.h[0]              // v5 <- low, broadcasted\n    dup         v6.8h, v0.h[1]              // v6 <- high, broadcasted\n    dup         v7.8h, v0.h[7]              // v7 <- half, broadcasted\n\n    cmp         x1, #32\n    blt         .loop\n\n.loop4:\n    ld1         { v16.8h, v17.8h, v18.8h, v19.8h }, [x0]\n\n    fmax        v16.8h, v16.8h, v5.8h\n    fmax        v17.8h, v17.8h, v5.8h\n    fmax        v18.8h, v18.8h, v5.8h\n    fmax        v19.8h, v19.8h, v5.8h\n\n    fmin        v16.8h, v16.8h, v6.8h\n    fmin        v17.8h, v17.8h, v6.8h\n    fmin        v18.8h, v18.8h, v6.8h\n    fmin        v19.8h, v19.8h, v6.8h       // v16 <- x\n\n    fmul        v20.8h, v16.8h, v16.8h\n    fmul        v21.8h, v17.8h, v17.8h\n    fmul        v22.8h, v18.8h, v18.8h\n    fmul        v23.8h, v19.8h, v19.8h      // v20 <- x2\n\n    dup         v28.8h, v0.h[3]\n    fmla        v28.8h, v20.8h, v0.h[2]\n    dup         v29.8h, v0.h[3]\n    fmla        v29.8h, v21.8h, v0.h[2]\n    dup         v30.8h, v0.h[3]\n    fmla        v30.8h, v22.8h, v0.h[2]\n    dup         v31.8h, v0.h[3]\n    fmla        v31.8h, v23.8h, v0.h[2]\n\n    dup         v24.8h, v0.h[4]\n    fmla        v24.8h, v20.8h, v28.8h\n    dup         v25.8h, v0.h[4]\n    fmla        v25.8h, v21.8h, v29.8h\n    dup         v26.8h, v0.h[4]\n    fmla        v26.8h, v22.8h, v30.8h\n    dup         v27.8h, v0.h[4]\n    fmla        v27.8h, v23.8h, v31.8h\n\n    fmul        v16.8h, v16.8h, v24.8h\n    fmul        v17.8h, v17.8h, v25.8h\n    fmul        v18.8h, v18.8h, v26.8h\n    fmul        v19.8h, v19.8h, v27.8h      // v16 <- numerator\n\n    dup         v24.8h, v0.h[6]\n    dup         v25.8h, v0.h[6]\n    dup         v26.8h, v0.h[6]\n    dup         v27.8h, v0.h[6]\n    fmla        v24.8h, v20.8h, v0.h[5]\n    fmla        v25.8h, v21.8h, v0.h[5]\n    fmla        v26.8h, v22.8h, v0.h[5]\n    fmla        v27.8h, v23.8h, v0.h[5]      // v24 <- denum\n\n    fdiv        v16.8h, v16.8h, v24.8h\n    fdiv        v17.8h, v17.8h, v25.8h\n    fdiv        v18.8h, v18.8h, v26.8h\n    fdiv        v19.8h, v19.8h, v27.8h\n\n    fadd        v16.8h, v16.8h, v7.8h\n    fadd        v17.8h, v17.8h, v7.8h\n    fadd        v18.8h, v18.8h, v7.8h\n    fadd        v19.8h, v19.8h, v7.8h\n\n    st1         { v16.8h, v17.8h, v18.8h, v19.8h }, [x0], #64\n\n    subs        x1, x1, #32\n    cmp         x1, #32\n    bge         .loop4\n\n    cmp         x1, #0\n    beq         .return\n\n.loop:\n    ld1         { v16.8h }, [x0]\n\n    fmax        v16.8h, v16.8h, v5.8h\n    fmin        v16.8h, v16.8h, v6.8h       // v16 <- x\n    fmul        v20.8h, v16.8h, v16.8h      // v20 <- x2\n\n    dup         v28.8h, v0.h[3]\n    fmla        v28.8h, v20.8h, v0.h[2]\n    dup         v24.8h, v0.h[4]\n    fmla        v24.8h, v20.8h, v28.8h\n    fmul        v16.8h, v16.8h, v24.8h      // v16 <- numerator\n\n    dup         v24.8h, v0.h[6]\n    fmla        v24.8h, v20.8h, v0.h[5]      // v24 <- denum\n\n    fdiv        v16.8h, v16.8h, v24.8h\n    fadd        v16.8h, v16.8h, v7.8h\n    \n    st1         { v16.8h }, [x0], #16\n\n    subs        x1, x1, #8\n    bne         .loop\n\n.return:\n    ret\n\n.coeffs_num:\n    {{ -6.92 | float16 }}\n    {{ 6.92 | float16 }}\n    {{ -0.0000124702 | float16 }}\n    {{ 0.00400222 | float16 }}\n\n    {{ 0.249895 | float16 }}\n    {{ 0.098734 | float16 }}\n    {{ 1.0 | float16 }}\n    {{ 0.5 | float16 }}\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/arm64fp16_tanh_f16_8n.tmpl",
    "content": "// vim: ft=arm\n\n// no preservation either for v0-v7 and v16-v31\n\n.text\n.align 4\n\n{% if needs_pragma == true %}\n.cpu generic+fp+simd+fp16\n{% endif %}\n.global {{G}}arm64fp16_tanh_f16_8n_{{suffix}}\n{{G}}arm64fp16_tanh_f16_8n_{{suffix}}:\n\n    cmp         x1, #0\n    beq         .return\n\n    adr         x2, .coeffs_num\n    ld1         { v0.8h }, [x2]\n    dup         v5.8h, v0.h[0]              // v5 <- low, broadcasted\n    dup         v6.8h, v0.h[1]              // v6 <- high, broadcasted\n\n    cmp         x1, #32\n    blt         .loop\n\n.loop4:\n    ld1         { v16.8h, v17.8h, v18.8h, v19.8h }, [x0]\n\n    fmax        v16.8h, v16.8h, v5.8h\n    fmax        v17.8h, v17.8h, v5.8h\n    fmax        v18.8h, v18.8h, v5.8h\n    fmax        v19.8h, v19.8h, v5.8h\n\n    fmin        v16.8h, v16.8h, v6.8h\n    fmin        v17.8h, v17.8h, v6.8h\n    fmin        v18.8h, v18.8h, v6.8h\n    fmin        v19.8h, v19.8h, v6.8h       // v16 <- x\n\n    fmul        v20.8h, v16.8h, v16.8h\n    fmul        v21.8h, v17.8h, v17.8h\n    fmul        v22.8h, v18.8h, v18.8h\n    fmul        v23.8h, v19.8h, v19.8h      // v20 <- x2\n\n    dup         v24.8h, v0.h[3]\n    fmla        v24.8h, v20.8h, v0.h[2]\n    dup         v25.8h, v0.h[3]\n    fmla        v25.8h, v21.8h, v0.h[2]\n    dup         v26.8h, v0.h[3]\n    fmla        v26.8h, v22.8h, v0.h[2]\n    dup         v27.8h, v0.h[3]\n    fmla        v27.8h, v23.8h, v0.h[2]\n\n    fmul        v16.8h, v16.8h, v24.8h\n    fmul        v17.8h, v17.8h, v25.8h\n    fmul        v18.8h, v18.8h, v26.8h\n    fmul        v19.8h, v19.8h, v27.8h      // v16 <- numerator\n\n    dup         v28.8h, v0.h[5]\n    fmla        v28.8h, v20.8h, v0.h[4]\n    dup         v29.8h, v0.h[5]\n    fmla        v29.8h, v21.8h, v0.h[4]\n    dup         v30.8h, v0.h[5]\n    fmla        v30.8h, v22.8h, v0.h[4]\n    dup         v31.8h, v0.h[5]\n    fmla        v31.8h, v23.8h, v0.h[4]\n\n    dup         v24.8h, v0.h[6]\n    fmla        v24.8h, v20.8h, v28.8h\n    dup         v25.8h, v0.h[6]\n    fmla        v25.8h, v21.8h, v29.8h\n    dup         v26.8h, v0.h[6]\n    fmla        v26.8h, v22.8h, v30.8h\n    dup         v27.8h, v0.h[6]\n    fmla        v27.8h, v23.8h, v31.8h      // v24 <- denum\n\n    fdiv        v16.8h, v16.8h, v24.8h\n    fdiv        v17.8h, v17.8h, v25.8h\n    fdiv        v18.8h, v18.8h, v26.8h\n    fdiv        v19.8h, v19.8h, v27.8h\n\n    st1         { v16.8h, v17.8h, v18.8h, v19.8h }, [x0], #64\n\n    subs        x1, x1, #32\n    cmp         x1, #32\n    bge         .loop4\n\n    cmp         x1, #0\n    beq         .return\n\n.loop:\n    ld1         { v16.8h }, [x0]\n\n    fmax        v16.8h, v16.8h, v5.8h\n    fmin        v16.8h, v16.8h, v6.8h       // v16 <- x\n    fmul        v20.8h, v16.8h, v16.8h      // v20 <- x2\n\n    dup         v24.8h, v0.h[3]\n    fmla        v24.8h, v20.8h, v0.h[2]\n    fmul        v16.8h, v16.8h, v24.8h      // v16 <- numerator\n\n    dup         v28.8h, v0.h[5]\n    fmla        v28.8h, v20.8h, v0.h[4]\n    dup         v24.8h, v0.h[6]\n    fmla        v24.8h, v20.8h, v28.8h      // v24 <- denum\n\n    fdiv        v16.8h, v16.8h, v24.8h\n    \n    st1         { v16.8h }, [x0], #16\n\n    subs        x1, x1, #8\n    bne         .loop\n\n.return:\n    ret\n\n.coeffs_num:\n    {{ -3.84 | float16 }}\n    {{ 3.84 | float16 }}\n    {{ 0.082654955 | float16 }}              // alpha\n    {{ 0.99963124 | float16 }}\n\n    {{ 0.0065383179 | float16 }}             // beta\n    {{ 0.41401828 | float16 }}   \n    {{ 1.0 | float16 }}\n    {{ 0 | float16 }}                        // padding\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/dispatcher.tmpliq",
    "content": "// vim: ft=arm\n\n.non_linear:\n    sub         x0, x0, 40\n\n.non_linear_loop:\n    add         x0, x0, 40\n    ldr         x2, [x0]\n\n    mov         x4, #{{ jump_table | size }}\n\n    cmp         x2, #{{ jump_table | size }}\n    csel        x2, x2, x4, lt\n    cmp         x2, #0\n    csel        x2, x4, x2, lt\n\n    adr         x3, .jmp_table\n    add         x3, x3, x2, LSL#2\n    br          x3\n\n.jmp_table:\n{% for j in jump_table %}\n    b   .{{j}}\n{% endfor %}\n    b   .unsupported\n\n    add x0, x2, #4000\n    b .return\n\n.unsupported:\n    mov         x0, #1\n    b           .return\n\n.done:\n    mov         x0, 0\n    b           .return\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/dummy_fmla_no_pragma.S",
    "content": "// vim: ft=arm\n\n// serves as a canary build file to figure out which flag combination will accept half precision fmla \n\n.text\n.align 4\n\n// .cpu generic+fp+simd+fp16\n.global foo\nfoo:\n    fmla        v16.8h, v0.8h, v8.h[0]\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64fp16/dummy_fmla_pragma.S",
    "content": "// vim: ft=arm\n\n// serves as a canary build file to figure out which flag combination will accept half precision fmla \n\n.text\n.align 4\n\n.cpu generic+fp+simd+fp16\n.global foo\nfoo:\n    fmla        v16.8h, v0.8h, v8.h[0]\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_4s_per_col.tmpliq",
    "content": "// vim: ft=arm\n\n.{{label}}:\n    ldr         x2, [x0, #8]\n\n{% capture mr_over_4 %}{{ mr | divided_by: 4}}{%endcapture%}\n{% capture cols%}{{to | plus: 1| minus:from| divided_by:mr_over_4}}{%endcapture%}\n\n{% capture loads %}{{cols | divided_by:4}}{% endcapture %}\n\n{%if cols == \"1\" %}\n        ld1         {v0.s}[0], [ x2 ]\n{% elsif cols == \"3\" %}\n        ld1         {v0.d}[0], [ x2 ], #8\n        ld1         {v0.s}[2], [ x2 ]\n{% else %}\n    {% for reg in (1..loads) %}\n        ldr         q{{reg |minus:1}}, [ x2 ], #16\n    {% endfor %}\n{% endif %}\n\n// {{mr}} {{cols}}\n\n{% for col in (1..cols) %}\n    dup v3.4s, v{{col| minus: 1|divided_by:4}}.s[{{col| minus: 1|modulo:4}}]\n    {% for row in (1..mr_over_4) %}\n        {% capture acc %}{{ col|minus:1|times:mr_over_4|plus:row|minus:1|plus:from }}{% endcapture %}\n        {% if flipped %}\n            {{op}} v{{acc}}.4s, v{{acc}}.4s, v3.4s\n        {% else %}\n            {{op}} v{{acc}}.4s, v3.4s, v{{acc}}.4s\n        {% endif %}\n    {% endfor %}\n{% endfor %}\n\nb           .non_linear_loop\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_4s_per_row.tmpliq",
    "content": "// vim: ft=arm\n\n.{{label}}:\n    ldr         x2, [x0, #8]\n\n{% capture mr_over_4 %}{{ mr | divided_by: 4}}{%endcapture%}\n{% capture mr_over_4_min_1 %}{{ mr | divided_by: 4 | minus: 1}}{%endcapture%}\n\n{% for reg in (0..mr_over_4_min_1) %}\n    ldr         q{{reg}}, [ x2 ], #16\n{% endfor %}\n\n{% if flipped %}\n    {% for acc in (from..to) %}\n        {% capture other%}{{acc | minus: from | modulo: mr_over_4}}{%endcapture%}\n        {{op}} v{{acc}}.4s, v{{acc}}.4s, v{{other}}.4s\n    {% endfor %}\n{% else %}\n    {% for acc in (from..to) %}\n        {% capture other%}{{acc | minus: from | modulo: mr_over_4}}{%endcapture%}\n        {{op}} v{{acc}}.4s, v{{other}}.4s, v{{acc}}.4s\n    {% endfor %}\n{% endif %}\n\nb           .non_linear_loop\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_4s_scalar.tmpliq",
    "content": "// vim: ft=arm\n\n.{{label}}:\n    add         x2, x0, #8\n    ld1         {v0.s}[0], [ x2 ]\n    dup         v0.4s, v0.s[0]\n    {% if flipped %}\n        {% for reg in (from..to) %}\n            {{op}}       v{{reg}}.4s, v{{reg}}.4s, v0.4s\n        {% endfor %}\n    {% else %}\n        {% for reg in (from..to) %}\n            {{op}}       v{{reg}}.4s, v0.4s, v{{reg}}.4s\n        {% endfor %}\n    {% endif %}\n\n    b           .non_linear_loop\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_12x8/packed_packed_loop1/ldr_w_no_preload.tmpli",
    "content": "fmla        v8.4s, v0.4s, v4.s[0]\nldr         w4, [x1], #4\nfmla        v9.4s, v1.4s, v4.s[0]\nldr         w20, [x2], #4\nfmla        v10.4s, v2.4s, v4.s[0]\nldr         w5, [x1], #4\n\nfmla        v11.4s, v0.4s, v4.s[1]\nldr         w21, [x2], #4\nfmla        v12.4s, v1.4s, v4.s[1]\nldr         w6, [x1], #4\nfmla        v13.4s, v2.4s, v4.s[1]\nldr         w22, [x2], #4\n\nfmla        v14.4s, v0.4s, v4.s[2]\nldr         w7, [x1], #4\nfmla        v15.4s, v1.4s, v4.s[2]\nldr         w23, [x2], #4\nfmla        v16.4s, v2.4s, v4.s[2]\nldr         w8, [x1], #4\nfmla        v17.4s, v0.4s, v4.s[3]\nldr         w24, [x2], #4\nfmla        v18.4s, v1.4s, v4.s[3]\nldr         w9, [x1], #4\nfmla        v19.4s, v2.4s, v4.s[3]\nldr         w25, [x2], #4\n\nfmla        v20.4s, v0.4s, v5.s[0]\nldr         w10, [x1], #4\nfmla        v21.4s, v1.4s, v5.s[0]\nldr         w26, [x2], #4\nfmla        v22.4s, v2.4s, v5.s[0]\nldr         w11, [x1], #4\nfmla        v23.4s, v0.4s, v5.s[1]\nldr         w27, [x2], #4\nfmla        v24.4s, v1.4s, v5.s[1]\nldr         w12, [x1], #4\nfmla        v25.4s, v2.4s, v5.s[1]\n\nfmla        v26.4s, v0.4s, v5.s[2]\nldr         w13, [x1], #4\nfmla        v27.4s, v1.4s, v5.s[2]\nfmla        v28.4s, v2.4s, v5.s[2]\nldr         w14, [x1], #4\nfmla        v29.4s, v0.4s, v5.s[3]\nfmla        v30.4s, v1.4s, v5.s[3]\nldr         w15, [x1], #4\nfmla        v31.4s, v2.4s, v5.s[3]\n\nins         v0.s[0], w4\nins         v1.s[0], w8\nins         v2.s[0], w12\nins         v4.s[0], w20\nins         v5.s[0], w24\nins         v0.s[1], w5\nins         v1.s[1], w9\nins         v2.s[1], w13\nins         v4.s[1], w21\nins         v5.s[1], w25\nins         v0.s[2], w6\nins         v1.s[2], w10\nins         v2.s[2], w14\nins         v4.s[2], w22\nins         v5.s[2], w26\nins         v0.s[3], w7\nins         v1.s[3], w11\nins         v2.s[3], w15\nins         v4.s[3], w23\nins         v5.s[3], w27\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_12x8/packed_packed_loop1/ldr_w_preload.tmpli",
    "content": "fmla        v8.4s, v0.4s, v4.s[0]\n    ldr         w4, [x1]\nfmla        v9.4s, v1.4s, v4.s[0]\n        ldr         w20, [x2], #4\nfmla        v10.4s, v2.4s, v4.s[0]\n    ldr         w5, [x1, #4]\n\nfmla        v11.4s, v0.4s, v4.s[1]\n        ldr         w21, [x2], #4\nfmla        v12.4s, v1.4s, v4.s[1]\n    ldr         w6, [x1, #8]\nfmla        v13.4s, v2.4s, v4.s[1]\n        ldr         w22, [x2], #4\n\nfmla        v14.4s, v0.4s, v4.s[2]\n    ldr         w7, [x1, #12]\nfmla        v15.4s, v1.4s, v4.s[2]\n        ldr         w23, [x2], #4\nfmla        v16.4s, v2.4s, v4.s[2]\n    ldr         w8, [x1, #16]\nfmla        v17.4s, v0.4s, v4.s[3]\n        ldr         w24, [x2], #4\nfmla        v18.4s, v1.4s, v4.s[3]\n    ldr         w9, [x1, #20]\nfmla        v19.4s, v2.4s, v4.s[3]\n        ldr         w25, [x2], #4\n\nfmla        v20.4s, v0.4s, v5.s[0]\n    ldr         w10, [x1, #24]\nfmla        v21.4s, v1.4s, v5.s[0]\n        ldr         w26, [x2], #4\nfmla        v22.4s, v2.4s, v5.s[0]\n    ldr         w11, [x1, #28]\nfmla        v23.4s, v0.4s, v5.s[1]\n        ldr         w27, [x2], #4\nfmla        v24.4s, v1.4s, v5.s[1]\n    ldr         w12, [x1, #32]\nfmla        v25.4s, v2.4s, v5.s[1]\n    ldr         w13, [x1, #36]\n\nfmla        v26.4s, v0.4s, v5.s[2]\n    ldr         w14, [x1, #40]\nfmla        v27.4s, v1.4s, v5.s[2]\n    ldr         w15, [x1, #44]\nfmla        v28.4s, v2.4s, v5.s[2]\n    prfm        pldl1keep, [x1, #512]\nfmla        v29.4s, v0.4s, v5.s[3]\n    add         x1, x1, #48\nfmla        v30.4s, v1.4s, v5.s[3]\n    prfm        pldl1keep, [x2, #384]\nfmla        v31.4s, v2.4s, v5.s[3]\n\n    ins         v0.s[0], w4\n\n    ins         v1.s[0], w8\n    ins         v2.s[0], w12\n\n        ins         v4.s[0], w20\n        ins         v5.s[0], w24\n\n    ins         v0.s[1], w5\n    ins         v1.s[1], w9\n\n    ins         v2.s[1], w13\n        ins         v4.s[1], w21\n\n        ins         v5.s[1], w25\n    ins         v0.s[2], w6\n\n    ins         v1.s[2], w10\n    ins         v2.s[2], w14\n\n        ins         v4.s[2], w22\n        ins         v5.s[2], w26\n\n    ins         v0.s[3], w7\n    ins         v1.s[3], w11\n\n    ins         v2.s[3], w15\n        ins         v4.s[3], w23\n        ins         v5.s[3], w27\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_12x8/packed_packed_loop1/ldr_x_preload.tmpli",
    "content": "fmla        v8.4s, v0.4s, v4.s[0]\n    ldr         x4, [x1]\nfmla        v9.4s, v1.4s, v4.s[0]\n        ldr         x20, [x2]\nfmla        v10.4s, v2.4s, v4.s[0]\n    ldr         x5, [x1, #8]\n\nfmla        v11.4s, v0.4s, v4.s[1]\n        ldr         x21, [x2, #8]\nfmla        v12.4s, v1.4s, v4.s[1]\n    ldr         x6, [x1, #16]\nfmla        v13.4s, v2.4s, v4.s[1]\n        ldr         x22, [x2, #16]\n\nfmla        v14.4s, v0.4s, v4.s[2]\n    ldr         x7, [x1, #24]\nfmla        v15.4s, v1.4s, v4.s[2]\n        ldr         x23, [x2, #24]\nfmla        v16.4s, v2.4s, v4.s[2]\n    ldr         x8, [x1, #32]\nfmla        v17.4s, v0.4s, v4.s[3]\nfmla        v18.4s, v1.4s, v4.s[3]\n    ldr         x9, [x1, #40]\nfmla        v19.4s, v2.4s, v4.s[3]\n\nfmla        v20.4s, v0.4s, v5.s[0]\nfmla        v21.4s, v1.4s, v5.s[0]\nfmla        v22.4s, v2.4s, v5.s[0]\nfmla        v23.4s, v0.4s, v5.s[1]\nfmla        v24.4s, v1.4s, v5.s[1]\nfmla        v25.4s, v2.4s, v5.s[1]\n\nfmla        v26.4s, v0.4s, v5.s[2]\nfmla        v27.4s, v1.4s, v5.s[2]\nfmla        v28.4s, v2.4s, v5.s[2]\n    prfm        pldl1keep, [x1, #512]\nfmla        v29.4s, v0.4s, v5.s[3]\n    add         x1, x1, #48\nfmla        v30.4s, v1.4s, v5.s[3]\n    prfm        pldl1keep, [x2, #384]\nfmla        v31.4s, v2.4s, v5.s[3]\n    add         x2, x2, #32\n\n\n    ins         v0.d[0], x4\n    ins         v2.d[0], x8\n\n        ins         v4.d[0], x20\n        ins         v5.d[0], x22\n\n    ins         v0.d[1], x5\n    ins         v2.d[1], x9\n\n        ins         v4.d[1], x21\n    ins         v1.d[0], x6\n\n    ins         v1.d[1], x7\n\n        ins         v5.d[1], x23\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_12x8/packed_packed_loop1/naive.tmpli",
    "content": "fmla        v8.4s, v0.4s, v4.s[0]\nfmla        v9.4s, v1.4s, v4.s[0]\nfmla        v10.4s, v2.4s, v4.s[0]\n\nfmla        v11.4s, v0.4s, v4.s[1]\nfmla        v12.4s, v1.4s, v4.s[1]\nfmla        v13.4s, v2.4s, v4.s[1]\n\nfmla        v14.4s, v0.4s, v4.s[2]\nfmla        v15.4s, v1.4s, v4.s[2]\nfmla        v16.4s, v2.4s, v4.s[2]\n\nfmla        v17.4s, v0.4s, v4.s[3]\nfmla        v18.4s, v1.4s, v4.s[3]\nfmla        v19.4s, v2.4s, v4.s[3]\n\nfmla        v20.4s, v0.4s, v5.s[0]\nfmla        v21.4s, v1.4s, v5.s[0]\nfmla        v22.4s, v2.4s, v5.s[0]\n\nfmla        v23.4s, v0.4s, v5.s[1]\nfmla        v24.4s, v1.4s, v5.s[1]\nfmla        v25.4s, v2.4s, v5.s[1]\n\nfmla        v26.4s, v0.4s, v5.s[2]\nfmla        v27.4s, v1.4s, v5.s[2]\nfmla        v28.4s, v2.4s, v5.s[2]\n\nfmla        v29.4s, v0.4s, v5.s[3]\nfmla        v30.4s, v1.4s, v5.s[3]\nfmla        v31.4s, v2.4s, v5.s[3]\n\nld1         {{ v0.4s, v1.4s, v2.4s }}, [x1], #48\nld1         {{ v4.4s, v5.4s }}, [x2], #32\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_12x8/packed_packed_loop2/cortex_a55.tmpli",
    "content": "// mul a: v0, v1, v2, b: v4, v5\n// load a: d3/x23, d6/x26, d7/x27\n// load b: x4, x5, x6, x7\n\nfmla        v8.4s,  v0.4s, v4.s[0] \nldr         d3, [x1], #8\nfmla        v9.4s,  v1.4s, v4.s[0]\nldr         x4, [x2], #8\nfmla        v10.4s, v2.4s, v4.s[0]\nldr         x23, [x1], #8\nfmla        v11.4s, v0.4s, v4.s[1]\nldr         x5, [x2], #8\nfmla        v12.4s, v1.4s, v4.s[1]\nldr         d6, [x1], #8\nfmla        v13.4s, v2.4s, v4.s[1]\nldr         x6, [x2], #8\nfmla        v14.4s, v0.4s, v4.s[2]\nldr         x26, [x1], #8\nfmla        v15.4s, v1.4s, v4.s[2]\nldr         x7, [x2], #8\nfmla        v16.4s, v2.4s, v4.s[2]\nldr         d7, [x1], #8\nfmla        v17.4s, v0.4s, v4.s[3]\nldr         x27, [x1], #8\nfmla        v18.4s, v1.4s, v4.s[3]\n\nfmla        v19.4s, v2.4s, v4.s[3]\nfmla        v20.4s, v0.4s, v5.s[0]\n\n// ins b: v4 <- x4/x5\n// ins a: d3/x23, d6/x26, d7/x27\n\nins         v4.d[0], x4\nfmla        v21.4s, v1.4s, v5.s[0]\nins         v4.d[1], x5\nfmla        v22.4s, v2.4s, v5.s[0]\nfmla        v23.4s, v0.4s, v5.s[1]\n\nfmla        v24.4s, v1.4s, v5.s[1]\nfmla        v25.4s, v2.4s, v5.s[1]\nfmla        v26.4s, v0.4s, v5.s[2]\nfmla        v27.4s, v1.4s, v5.s[2]\nfmla        v28.4s, v2.4s, v5.s[2]\nfmla        v29.4s, v0.4s, v5.s[3]\nins         v3.d[1], x23\nfmla        v30.4s, v1.4s, v5.s[3]\nins         v6.d[1], x26\nfmla        v31.4s, v2.4s, v5.s[3]\nins         v7.d[1], x27\n\n// mul a: v3, v6, v7, b: v4, v5\n// ins b, v5 <- x6, x7\n// load a: d0/x20, d1/x21, d2/x22\n// load b: x4, x5\n\nfmla        v8.4s,  v3.4s, v4.s[0] \nins         v5.d[0], x6\nfmla        v9.4s,  v6.4s, v4.s[0]\nins         v5.d[1], x7\nfmla        v10.4s, v7.4s, v4.s[0]\nldr         d0, [x1], #8\nfmla        v11.4s, v3.4s, v4.s[1]\nldr         x4, [x2], #8\nfmla        v12.4s, v6.4s, v4.s[1]\nldr         x20, [x1], #8\nfmla        v13.4s, v7.4s, v4.s[1]\nldr         x5, [x2], #8\nfmla        v14.4s, v3.4s, v4.s[2]\nldr         d1, [x1], #8\nfmla        v15.4s, v6.4s, v4.s[2]\nldr         x6, [x2], #8\nfmla        v16.4s, v7.4s, v4.s[2]\nldr         x21, [x1], #8\nfmla        v17.4s, v3.4s, v4.s[3]\nldr         x7, [x2], #8\n\n// load b: x6, x7\nfmla        v18.4s, v6.4s, v4.s[3]\nldr         d2, [x1], #8\nfmla        v19.4s, v7.4s, v4.s[3]\nldr         x22, [x1], #8\nfmla        v20.4s, v3.4s, v5.s[0]\nfmla        v21.4s, v6.4s, v5.s[0]\nfmla        v22.4s, v7.4s, v5.s[0]\nfmla        v23.4s, v3.4s, v5.s[1]\nfmla        v24.4s, v6.4s, v5.s[1]\nfmla        v25.4s, v7.4s, v5.s[1]\n\n// ins a: d0/x20, d1/x21, d2/x22\nfmla        v26.4s, v3.4s, v5.s[2]\nins         v0.d[1], x20\nfmla        v27.4s, v6.4s, v5.s[2]\nins         v1.d[1], x21\nfmla        v28.4s, v7.4s, v5.s[2]\nins         v2.d[1], x22\n\n// ins b: v4 <- x4, x5\nfmla        v29.4s, v3.4s, v5.s[3]\nins         v4.d[0], x4\nfmla        v30.4s, v6.4s, v5.s[3]\nins         v4.d[1], x5\nfmla        v31.4s, v7.4s, v5.s[3]\n\n// ins b: v5 <- x6, x7\nins         v5.d[0], x6\nins         v5.d[1], x7\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_12x8_core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: \n// - x19-x29 to preserve (but x19, x28, x29 not used) \n// - d8..d15 to preserve\n// - v16 to v31, no need to preserve\n//\n// v8  v11 v14 v17 v20 v23 v26 v29\n// v9  v12 v15 v18 v21 v24 v27 v30\n// v10 v13 v16 v19 v22 v25 v28 v31\n\n// no preservation for v0-v7:\n// packed A buffering (2x8 values): rotating over v0..v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_mmm_f32_12x8_{{core}}_{{suffix}}\n{{G}}arm64simd_mmm_f32_12x8_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    ld1         { v0.4s, v1.4s, v2.4s }, [ x1 ], #48\n    ld1         { v4.4s, v5.4s }, [ x2 ], #32\n\n{% capture packed_packed_loop1 %}\n    {% if core == \"a53\" %}\n        {% include \"arm64simd_mmm_f32_12x8/packed_packed_loop1/ldr_x_preload.tmpli\" %}\n    {% else %}\n        {% include \"arm64simd_mmm_f32_12x8/packed_packed_loop1/naive.tmpli\" %}\n    {% endif %}\n{% endcapture %}\n\n{% capture packed_packed_loop2 %}\n    {% if core == \"a55\" %}\n        {% include \"arm64simd_mmm_f32_12x8/packed_packed_loop2/cortex_a55.tmpli\" %}\n    {% else %}\n        {{ packed_packed_loop1 }}\n        {{ packed_packed_loop1 }}\n    {% endif %}\n{% endcapture %}\n\n    cmp         x3, #4\n    blt         .packed_packed_loop_1\n\n.p2align 4\n.packed_packed_loop_4:\n    {{ packed_packed_loop2 }}\n    {{ packed_packed_loop2 }}\n\n    sub x3, x3, #4\n    cmp x3, #4\n    bge .packed_packed_loop_4\n\n    cmp x3, #0\n    beq .non_linear_loop\n\n.p2align 4\n.packed_packed_loop_1:\n    {{ packed_packed_loop1 }}\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b .non_linear_loop\n\n{% include \"arm64simd_mmm_f32_scalars.tmpliq\" from:8, to:31%}\n{% include \"arm64simd_mmm_f32_per_rows.tmpliq\" mr:12, from:8, to:31 %}\n{% include \"arm64simd_mmm_f32_per_cols.tmpliq\" mr:12, from:8, to:31 %}\n{% include \"arm64simd_mmm_load_tile.tmpliq\" from:8, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8 ]           // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    {% for col in (0..7) %}\n        mov x4, x5\n        {% for reg in (0..2) %}\n            {% for lane in (0..3) %}\n                ld1 {v0.s}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            fadd v{{col | times:3 | plus: 8| plus: reg}}.4s, v{{col | times:3 | plus: 8 | plus: reg}}.4s, v0.4s\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x2, [x0, #8]\n    ldr     x3, [x0, #16]\n\n    ld1         { v0.4s, v1.4s, v2.4s }, [ x2 ]\n    ld1         { v4.4s, v5.4s }, [ x3 ]\n\n    {% for col in (0..7) %}\n        {% for reg in (0..2) %}\n            fmla v{{col | times:3 | plus: 8 | plus: reg}}.4s, v{{reg}}.4s, v{{col| divided_by:4 | plus: 4}}.s[{{col| modulo: 4}}]\n        {% endfor %}\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x6, #4\n    bne         .store_strides_generic\n\n    {% for col in (0..7) %}\n        str q{{col | times:3 | plus: 8 }}, [ x5 ]\n        str q{{col | times:3 | plus: 9}}, [ x5, #16 ]\n        str q{{col | times:3 | plus: 10}}, [ x5, #32 ]\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store_strides_generic:\n    {% for col in (0..7) %}\n        mov x4, x5\n        {% for reg in (0..2) %}\n            {% for lane in (0..3) %}\n                st1 { v{{col | times:3 | plus: 8 | plus: reg}}.s }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_16x4/packed_packed_loop1/cortex_a53.tmpli",
    "content": "fmla        v16.4s, v0.4s, v4.s[0]\nldr         x5, [x1]\nfmla        v17.4s, v1.4s, v4.s[0]\nldr         x6, [x1, #8]\nfmla        v18.4s, v2.4s, v4.s[0]\nldr         x7, [x1, #16]\nfmla        v19.4s, v3.4s, v4.s[0]\nldr         x8, [x1, #24]\nfmla        v20.4s, v0.4s, v4.s[1]\nldr         x9, [x1, #32]\nfmla        v21.4s, v1.4s, v4.s[1]\nldr         x10, [x1, #40]\nfmla        v22.4s, v2.4s, v4.s[1]\nldr         x11, [x1, #48]\nfmla        v23.4s, v3.4s, v4.s[1]\nldr         x12, [x1, #56]\n\nfmla        v24.4s, v0.4s, v4.s[2]\nldr         x24, [x2]\nfmla        v25.4s, v1.4s, v4.s[2]\nldr         x25, [x2, #8]\nfmla        v26.4s, v2.4s, v4.s[2]\nadd         x1, x1, #64\nfmla        v27.4s, v3.4s, v4.s[2]\nadd         x2, x2, #16\nfmla        v28.4s, v0.4s, v4.s[3]\nprfm        pldl1keep, [x1, #256]\nfmla        v29.4s, v1.4s, v4.s[3]\nprfm        pldl1keep, [x2, #256]\nfmla        v30.4s, v2.4s, v4.s[3]\nprfm        pldl1keep, [x1, #256]\nfmla        v31.4s, v3.4s, v4.s[3]\n\nins         v0.d[0], x5\nins         v2.d[0], x9\nins         v1.d[0], x7\nins         v3.d[0], x11\nins         v4.d[0], x24\n\nins         v0.d[1], x6\nins         v2.d[1], x10\nins         v1.d[1], x8\nins         v3.d[1], x12\nins         v4.d[1], x25\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_16x4/packed_packed_loop1/naive.tmpli",
    "content": "\nfmla        v16.4s, v0.4s, v4.s[0]\nfmla        v17.4s, v1.4s, v4.s[0]\nfmla        v18.4s, v2.4s, v4.s[0]\nfmla        v19.4s, v3.4s, v4.s[0]\nfmla        v20.4s, v0.4s, v4.s[1]\nfmla        v21.4s, v1.4s, v4.s[1]\nfmla        v22.4s, v2.4s, v4.s[1]\nfmla        v23.4s, v3.4s, v4.s[1]\n\nfmla        v24.4s, v0.4s, v4.s[2]\nfmla        v25.4s, v1.4s, v4.s[2]\nfmla        v26.4s, v2.4s, v4.s[2]\nfmla        v27.4s, v3.4s, v4.s[2]\nfmla        v28.4s, v0.4s, v4.s[3]\nfmla        v29.4s, v1.4s, v4.s[3]\nfmla        v30.4s, v2.4s, v4.s[3]\nfmla        v31.4s, v3.4s, v4.s[3]\n\nld1         {{ v0.4s, v1.4s, v2.4s, v3.4s }}, [ x1 ], #64\nld1         {{ v4.4s }}, [ x2 ], #16\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_16x4/packed_packed_loop2/cortex_a55.tmpli",
    "content": "// mul a: v0, v1, v2, v3 b: v4\n// load a: v5(d5/x5), v6(d6,x6), v7(d7,x7), v8(d8, x8)\n// load b: v9(d9/x9)\n\nfmla        v16.4s, v0.4s, v4.s[0]\nldr         d5, [x1], #8\nfmla        v17.4s, v1.4s, v4.s[0]\nldr         d9, [x2], #8\nfmla        v18.4s, v2.4s, v4.s[0]\nldr         x5, [x1], #8\nfmla        v19.4s, v3.4s, v4.s[0]\nldr         x9, [x2], #8\nfmla        v20.4s, v0.4s, v4.s[1]\nldr         d6, [x1], #8\nfmla        v21.4s, v1.4s, v4.s[1]\nldr         x6, [x1], #8\nfmla        v22.4s, v2.4s, v4.s[1]\nldr         d7, [x1], #8\nfmla        v23.4s, v3.4s, v4.s[1]\nldr         x7, [x1], #8\n\nfmla        v24.4s, v0.4s, v4.s[2]\nldr         d8, [x1], #8\nfmla        v25.4s, v1.4s, v4.s[2]\nldr         x8, [x1], #8\nfmla        v26.4s, v2.4s, v4.s[2]\nins         v5.d[1], x5\nfmla        v27.4s, v3.4s, v4.s[2]\nins         v6.d[1], x6\nfmla        v28.4s, v0.4s, v4.s[3]\nins         v7.d[1], x7\nfmla        v29.4s, v1.4s, v4.s[3]\nins         v8.d[1], x8\nfmla        v30.4s, v2.4s, v4.s[3]\nins         v9.d[1], x9\nfmla        v31.4s, v3.4s, v4.s[3]\n\n// mul a: v5, v6, v7, v8 b: v9\n// load a: v0(d0/x5), v1(d1,x6), v2(d2,x7), v3(d3, x8)\n// load b: v4(d4/x9)\n\nfmla        v16.4s, v5.4s, v9.s[0]\nldr         d0, [x1], #8\nfmla        v17.4s, v6.4s, v9.s[0]\nldr         d4, [x2], #8\nfmla        v18.4s, v7.4s, v9.s[0]\nldr         x5, [x1], #8\nfmla        v19.4s, v8.4s, v9.s[0]\nldr         x9, [x2], #8\nfmla        v20.4s, v5.4s, v9.s[1]\nldr         d1, [x1], #8\nfmla        v21.4s, v6.4s, v9.s[1]\nldr         x6, [x1], #8\nfmla        v22.4s, v7.4s, v9.s[1]\nldr         d2, [x1], #8\nfmla        v23.4s, v8.4s, v9.s[1]\nldr         x7, [x1], #8\n\nfmla        v24.4s, v5.4s, v9.s[2]\nldr         d3, [x1], #8\nfmla        v25.4s, v6.4s, v9.s[2]\nldr         x8, [x1], #8\nfmla        v26.4s, v7.4s, v9.s[2]\nins         v0.d[1], x5\nfmla        v27.4s, v8.4s, v9.s[2]\nins         v1.d[1], x6\nfmla        v28.4s, v5.4s, v9.s[3]\nins         v2.d[1], x7\nfmla        v29.4s, v6.4s, v9.s[3]\nins         v3.d[1], x8\nfmla        v30.4s, v7.4s, v9.s[3]\nins         v4.d[1], x9\nfmla        v31.4s, v8.4s, v9.s[3]\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_16x4_core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: v16 to v31, (scratch)\n// - x19-x29 to preserve (but x19, x28, x29 not used) \n// - d8..d15 to preserve\n// - v16 to v31, no need to preserve\n\n// v8 is used, d8 (lower half) must preserved\n// v0-v7 (scratch registers)\n//  packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n//  packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_mmm_f32_16x4_{{core}}_{{suffix}}\n{{G}}arm64simd_mmm_f32_16x4_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x1 ], #64\n    ld1         { v4.4s }, [ x2 ], #16\n\n{% capture packed_packed_loop1 %}\n    {% if core == \"a53\" %}\n        {% include \"arm64simd_mmm_f32_16x4/packed_packed_loop1/cortex_a53.tmpli\" %}\n    {% else %}\n        {% include \"arm64simd_mmm_f32_16x4/packed_packed_loop1/naive.tmpli\" %}\n    {% endif %}\n{% endcapture %}\n\n{% capture packed_packed_loop2 %}\n    {% if core == \"a55\" %}\n        {% include \"arm64simd_mmm_f32_16x4/packed_packed_loop2/cortex_a55.tmpli\" %}\n    {% else %}\n        {{ packed_packed_loop1 }}\n        {{ packed_packed_loop1 }}\n    {% endif %}\n{% endcapture %}\n\n    cmp         x3, #4\n    blt         .packed_packed_loop_1\n\n.p2align 4\n.packed_packed_loop_4:\n    {{ packed_packed_loop2 }}\n    {{ packed_packed_loop2 }}\n\n    sub x3, x3, #4\n    cmp x3, #4\n    bge .packed_packed_loop_4\n\n    cmp x3, #0\n    beq .non_linear_loop\n\n.p2align 4\n.packed_packed_loop_1:\n    {{ packed_packed_loop1 }}\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b   .non_linear_loop\n\n{% include \"arm64simd_mmm_f32_scalars.tmpliq\" from:16, to:31%}\n{% include \"arm64simd_mmm_f32_per_rows.tmpliq\" mr:16, from:16, to:31 %}\n{% include \"arm64simd_mmm_f32_per_cols.tmpliq\" mr:16, from:16, to:31 %}\n{% include \"arm64simd_mmm_load_tile.tmpliq\" from:16, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    {% for col in (0..3) %}\n        mov x4, x5\n        {% for reg in (0..3) %}\n            {% for lane in (0..3) %}\n                ld1 {v0.s}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            fadd v{{col | times:4 | plus: 16| plus: reg}}.4s, v{{col | times:4 | plus: 16 | plus: reg}}.4s, v0.4s\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x2, [x0, #8]\n    ldr     x3, [x0, #16]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x2 ]\n    ld1         { v4.4s }, [ x3 ]\n\n    fmla        v16.4s, v0.4s, v4.s[0]\n    fmla        v17.4s, v1.4s, v4.s[0]\n    fmla        v18.4s, v2.4s, v4.s[0]\n    fmla        v19.4s, v3.4s, v4.s[0]\n    fmla        v20.4s, v0.4s, v4.s[1]\n    fmla        v21.4s, v1.4s, v4.s[1]\n    fmla        v22.4s, v2.4s, v4.s[1]\n    fmla        v23.4s, v3.4s, v4.s[1]\n\n    fmla        v24.4s, v0.4s, v4.s[2]\n    fmla        v25.4s, v1.4s, v4.s[2]\n    fmla        v26.4s, v2.4s, v4.s[2]\n    fmla        v27.4s, v3.4s, v4.s[2]\n    fmla        v28.4s, v0.4s, v4.s[3]\n    fmla        v29.4s, v1.4s, v4.s[3]\n    fmla        v30.4s, v2.4s, v4.s[3]\n    fmla        v31.4s, v3.4s, v4.s[3]\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x6, #4\n    bne           .store_strides_generic\n\n    {% for col in (0..3) %}\n        str q{{col | times:4 | plus:16 | plus: 0}}, [ x5 ]\n        str q{{col | times:4 | plus:16 | plus: 1}}, [ x5, #16 ]\n        str q{{col | times:4 | plus:16 | plus: 2}}, [ x5, #32 ]\n        str q{{col | times:4 | plus:16 | plus: 3}}, [ x5, #48 ]\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store_strides_generic:\n\n    {% for col in (0..3) %}\n        mov x4, x5\n        {% for reg in (0..3) %}\n            {% for lane in (0..3) %}\n                st1 { v{{col | times:4 | plus: 16 | plus: reg}}.s }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_24x4/loop2/cortex_a55.tmpli",
    "content": "// mul a: v0, v1, v2, v3, v4, v5 b: v7\n// load a: v5(d5/x5), v6(d6,x6), v7(d7,x7), v8(d8, x8)\n// load b: v9(d9/x9)\n\nfmla        v16.4s, v0.4s, v4.s[0]\nldr         d5, [x1], #8\nfmla        v17.4s, v1.4s, v4.s[0]\nldr         d9, [x2], #8\nfmla        v18.4s, v2.4s, v4.s[0]\nldr         x5, [x1], #8\nfmla        v19.4s, v3.4s, v4.s[0]\nldr         x9, [x2], #8\nfmla        v20.4s, v0.4s, v4.s[1]\nldr         d6, [x1], #8\nfmla        v21.4s, v1.4s, v4.s[1]\nldr         x6, [x1], #8\nfmla        v22.4s, v2.4s, v4.s[1]\nldr         d7, [x1], #8\nfmla        v23.4s, v3.4s, v4.s[1]\nldr         x7, [x1], #8\n\nfmla        v24.4s, v0.4s, v4.s[2]\nldr         d8, [x1], #8\nfmla        v25.4s, v1.4s, v4.s[2]\nldr         x8, [x1], #8\nfmla        v26.4s, v2.4s, v4.s[2]\nins         v5.d[1], x5\nfmla        v27.4s, v3.4s, v4.s[2]\nins         v6.d[1], x6\nfmla        v28.4s, v0.4s, v4.s[3]\nins         v7.d[1], x7\nfmla        v29.4s, v1.4s, v4.s[3]\nins         v8.d[1], x8\nfmla        v30.4s, v2.4s, v4.s[3]\nins         v9.d[1], x9\nfmla        v31.4s, v3.4s, v4.s[3]\n\n// mul a: v5, v6, v7, v8 b: v9\n// load a: v0(d0/x5), v1(d1,x6), v2(d2,x7), v3(d3, x8)\n// load b: v4(d4/x9)\n\nfmla        v16.4s, v5.4s, v9.s[0]\nldr         d0, [x1], #8\nfmla        v17.4s, v6.4s, v9.s[0]\nldr         d4, [x2], #8\nfmla        v18.4s, v7.4s, v9.s[0]\nldr         x5, [x1], #8\nfmla        v19.4s, v8.4s, v9.s[0]\nldr         x9, [x2], #8\nfmla        v20.4s, v5.4s, v9.s[1]\nldr         d1, [x1], #8\nfmla        v21.4s, v6.4s, v9.s[1]\nldr         x6, [x1], #8\nfmla        v22.4s, v7.4s, v9.s[1]\nldr         d2, [x1], #8\nfmla        v23.4s, v8.4s, v9.s[1]\nldr         x7, [x1], #8\n\nfmla        v24.4s, v5.4s, v9.s[2]\nldr         d3, [x1], #8\nfmla        v25.4s, v6.4s, v9.s[2]\nldr         x8, [x1], #8\nfmla        v26.4s, v7.4s, v9.s[2]\nins         v0.d[1], x5\nfmla        v27.4s, v8.4s, v9.s[2]\nins         v1.d[1], x6\nfmla        v28.4s, v5.4s, v9.s[3]\nins         v2.d[1], x7\nfmla        v29.4s, v6.4s, v9.s[3]\nins         v3.d[1], x8\nfmla        v30.4s, v7.4s, v9.s[3]\nins         v4.d[1], x9\nfmla        v31.4s, v8.4s, v9.s[3]\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_24x4/packed_packed_loop1/cortex_a53.tmpli",
    "content": "\nfmla        v8.4s, v0.4s, v7.s[0]\n    ldr         x4, [x1]\nfmla        v9.4s, v1.4s, v7.s[0]\n    ldr         x5, [x1, #8]\nfmla        v10.4s, v2.4s, v7.s[0]\n    ldr         x6, [x1, #16]\nfmla        v11.4s, v3.4s, v7.s[0]\n    ldr         x7, [x1, #24]\nfmla        v12.4s, v4.4s, v7.s[0]\n    ldr         x8, [x1, #32]\nfmla        v13.4s, v5.4s, v7.s[0]\n    ldr         x9, [x1, #40]\n\nfmla        v14.4s, v0.4s, v7.s[1]\n    ldr         x10, [x1, #48]\nfmla        v15.4s, v1.4s, v7.s[1]\n    ldr         x11, [x1, #56]\nfmla        v16.4s, v2.4s, v7.s[1]\n    ldr         x12, [x1, #64]\nfmla        v17.4s, v3.4s, v7.s[1]\n    ldr         x13, [x1, #72]\nfmla        v18.4s, v4.4s, v7.s[1]\n    ldr         x14, [x1, #80]\nfmla        v19.4s, v5.4s, v7.s[1]\n    ldr         x15, [x1, #88]\n\nfmla        v20.4s, v0.4s, v7.s[2]\n        ldr         x20, [x2]\nfmla        v21.4s, v1.4s, v7.s[2]\n        ldr         x21, [x2, #8]\nfmla        v22.4s, v2.4s, v7.s[2]\n    add         x1, x1, #96\nfmla        v23.4s, v3.4s, v7.s[2]\n        add         x2, x2, #16\nfmla        v24.4s, v4.4s, v7.s[2]\n    prfm        pldl1keep, [x1, #256]\nfmla        v25.4s, v5.4s, v7.s[2]\n        prfm        pldl1keep, [x2, #256]\n\nfmla        v26.4s, v0.4s, v7.s[3]\n    prfm        pldl1keep, [x1, #320]\nfmla        v27.4s, v1.4s, v7.s[3]\nfmla        v28.4s, v2.4s, v7.s[3]\nfmla        v29.4s, v3.4s, v7.s[3]\nfmla        v30.4s, v4.4s, v7.s[3]\nfmla        v31.4s, v5.4s, v7.s[3]\n\nins         v0.d[0], x4\nins         v1.d[0], x6\nins         v2.d[0], x8\nins         v3.d[0], x10\nins         v4.d[0], x12\nins         v5.d[0], x14\nins         v7.d[0], x20\n\nins         v0.d[1], x5\nins         v1.d[1], x7\nins         v2.d[1], x9\nins         v3.d[1], x11\nins         v4.d[1], x13\nins         v5.d[1], x15\nins         v7.d[1], x21\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_24x4/packed_packed_loop1/cortex_a55.tmpli",
    "content": "fmla        v8.4s, v0.4s, v7.s[0]\nfmla        v14.4s, v0.4s, v7.s[1]\n        prfm        pldl1keep, [x2, #256]\nfmla        v20.4s, v0.4s, v7.s[2]\nfmla        v26.4s, v0.4s, v7.s[3]\n    ldr         d0, [x1], #8\nfmla        v9.4s, v1.4s, v7.s[0]\n    ldr         x5, [x1], #8\nfmla        v15.4s, v1.4s, v7.s[1]\n        ldr         x20, [x2], #8\nfmla        v21.4s, v1.4s, v7.s[2]\n        ldr         x21, [x2], #8\nfmla        v27.4s, v1.4s, v7.s[3]\n    ldr         d1, [x1], #8\nfmla        v10.4s, v2.4s, v7.s[0]\n    ldr         x7, [x1], #8\nfmla        v16.4s, v2.4s, v7.s[1]\n    prfm        pldl1keep, [x1, #256]\nfmla        v22.4s, v2.4s, v7.s[2]\n    prfm        pldl1keep, [x1, #320]\nfmla        v28.4s, v2.4s, v7.s[3]\n    ldr         d2, [x1], #8\nfmla        v11.4s, v3.4s, v7.s[0]\n    ldr         x9, [x1], #8\nfmla        v17.4s, v3.4s, v7.s[1]\n    ins         v0.d[1], x5\nfmla        v23.4s, v3.4s, v7.s[2]\n    ins         v1.d[1], x7\nfmla        v29.4s, v3.4s, v7.s[3]\n    ldr         d3, [x1], #8\nfmla        v12.4s, v4.4s, v7.s[0]\n    ldr         x11, [x1], #8\nfmla        v18.4s, v4.4s, v7.s[1]\n    ins         v2.d[1], x9\nfmla        v24.4s, v4.4s, v7.s[2]\nfmla        v30.4s, v4.4s, v7.s[3]\n    ldr         d4, [x1], #8\nfmla        v13.4s, v5.4s, v7.s[0]\n    ldr         x13, [x1], #8\nfmla        v19.4s, v5.4s, v7.s[1]\n    ldr         x14, [x1], #8\nfmla        v25.4s, v5.4s, v7.s[2]\n    ldr         x15, [x1], #8\nfmla        v31.4s, v5.4s, v7.s[3]\n\nins         v7.d[0], x20\nins         v7.d[1], x21\n\nins         v5.d[0], x14\nins         v5.d[1], x15\n\nins         v3.d[1], x11\nins         v4.d[1], x13\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_24x4/packed_packed_loop1/naive.tmpli",
    "content": "fmla        v8.4s, v0.4s, v7.s[0]\nfmla        v9.4s, v1.4s, v7.s[0]\nfmla        v10.4s, v2.4s, v7.s[0]\nfmla        v11.4s, v3.4s, v7.s[0]\nfmla        v12.4s, v4.4s, v7.s[0]\nfmla        v13.4s, v5.4s, v7.s[0]\n\nfmla        v14.4s, v0.4s, v7.s[1]\nfmla        v15.4s, v1.4s, v7.s[1]\nfmla        v16.4s, v2.4s, v7.s[1]\nfmla        v17.4s, v3.4s, v7.s[1]\nfmla        v18.4s, v4.4s, v7.s[1]\nfmla        v19.4s, v5.4s, v7.s[1]\n\nfmla        v20.4s, v0.4s, v7.s[2]\nfmla        v21.4s, v1.4s, v7.s[2]\nfmla        v22.4s, v2.4s, v7.s[2]\nfmla        v23.4s, v3.4s, v7.s[2]\nfmla        v24.4s, v4.4s, v7.s[2]\nfmla        v25.4s, v5.4s, v7.s[2]\n\nfmla        v26.4s, v0.4s, v7.s[3]\nfmla        v27.4s, v1.4s, v7.s[3]\nfmla        v28.4s, v2.4s, v7.s[3]\nfmla        v29.4s, v3.4s, v7.s[3]\nfmla        v30.4s, v4.4s, v7.s[3]\nfmla        v31.4s, v5.4s, v7.s[3]\n\nld1         {{ v0.4s, v1.4s, v2.4s, v3.4s }}, [ x1 ], #64\nld1         {{ v4.4s, v5.4s }}, [ x1 ], #32\nld1         {{ v7.4s }}, [ x2 ], #16\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_24x4_core.tmpl",
    "content": "// vim: ft=arm\n\n// x20..x27 are used, callee-preserved\n\n// C tile regs: v8 to v31, (scratch)\n// - x19-x29 to preserve (but x19, x28, x29 not used) \n// - d8..d15 to preserve\n// - v16 to v31, no need to preserve\n\n// v8 is used, d8 (lower half) must preserved\n// v0-v7 (scratch registers)\n//  packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n//  packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_mmm_f32_24x4_{{core}}_{{suffix}}\n{{G}}arm64simd_mmm_f32_24x4_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x1 ], #64\n    ld1         { v4.4s, v5.4s }, [ x1 ], #32\n    ld1         { v7.4s }, [ x2 ], #16\n\n{% capture packed_packed_loop1 %}\n    {% if core == \"a53\" %}\n        {% include \"arm64simd_mmm_f32_24x4/packed_packed_loop1/cortex_a53.tmpli\" %}\n    {% elsif core == \"a55\" %}\n        {% include \"arm64simd_mmm_f32_24x4/packed_packed_loop1/cortex_a55.tmpli\" %}\n    {% else %}\n        {% include \"arm64simd_mmm_f32_24x4/packed_packed_loop1/naive.tmpli\" %}\n    {% endif %}\n{% endcapture %}\n\n    cmp         x3, #4\n    blt         .packed_packed_loop_1\n\n.p2align 4\n.packed_packed_loop_4:\n    {{ packed_packed_loop1 }}\n    {{ packed_packed_loop1 }}\n    {{ packed_packed_loop1 }}\n    {{ packed_packed_loop1 }}\n\n    sub x3, x3, #4\n    cmp x3, #4\n    bge .packed_packed_loop_4\n\n    cmp x3, #0\n    beq .non_linear_loop\n\n.p2align 4\n.packed_packed_loop_1:\n    {{ packed_packed_loop1 }}\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b   .non_linear_loop\n\n{% include \"arm64simd_mmm_f32_scalars.tmpliq\" from:8, to:31 %}\n{% include \"arm64simd_mmm_f32_per_rows.tmpliq\" mr:24, from:8, to:31 %}\n{% include \"arm64simd_mmm_f32_per_cols.tmpliq\" mr:24, from:8, to:31 %}\n{% include \"arm64simd_mmm_load_tile.tmpliq\" from:8, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    {% for col in (0..3) %}\n        mov x4, x5\n        {% for reg in (0..5) %}\n            {% for lane in (0..3) %}\n                ld1 {v0.s}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            fadd v{{col | times:6 | plus: 8 | plus: reg}}.4s, v{{col | times:6 | plus: 8 | plus: reg}}.4s, v0.4s\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x2, [x0, #8]\n    ldr     x3, [x0, #16]\n\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x2 ], #64\n    ld1         { v7.4s }, [ x3 ]\n    ld1         { v4.4s, v5.4s }, [ x2 ]\n\n    fmla        v8.4s, v0.4s, v7.s[0]\n    fmla        v9.4s, v1.4s, v7.s[0]\n    fmla        v10.4s, v2.4s, v7.s[0]\n    fmla        v11.4s, v3.4s, v7.s[0]\n    fmla        v12.4s, v4.4s, v7.s[0]\n    fmla        v13.4s, v5.4s, v7.s[0]\n\n    fmla        v14.4s, v0.4s, v7.s[1]\n    fmla        v15.4s, v1.4s, v7.s[1]\n    fmla        v16.4s, v2.4s, v7.s[1]\n    fmla        v17.4s, v3.4s, v7.s[1]\n    fmla        v18.4s, v4.4s, v7.s[1]\n    fmla        v19.4s, v5.4s, v7.s[1]\n\n    fmla        v20.4s, v0.4s, v7.s[2]\n    fmla        v21.4s, v1.4s, v7.s[2]\n    fmla        v22.4s, v2.4s, v7.s[2]\n    fmla        v23.4s, v3.4s, v7.s[2]\n    fmla        v24.4s, v4.4s, v7.s[2]\n    fmla        v25.4s, v5.4s, v7.s[2]\n\n    fmla        v26.4s, v0.4s, v7.s[3]\n    fmla        v27.4s, v1.4s, v7.s[3]\n    fmla        v28.4s, v2.4s, v7.s[3]\n    fmla        v29.4s, v3.4s, v7.s[3]\n    fmla        v30.4s, v4.4s, v7.s[3]\n    fmla        v31.4s, v5.4s, v7.s[3]\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x6, #4\n    bne           .store_strides_generic\n\n    {% for col in (0..3) %}\n        str q{{col | times:6 | plus:8 | plus: 0}}, [ x5 ]\n        str q{{col | times:6 | plus:8 | plus: 1}}, [ x5, #16 ]\n        str q{{col | times:6 | plus:8 | plus: 2}}, [ x5, #32 ]\n        str q{{col | times:6 | plus:8 | plus: 3}}, [ x5, #48 ]\n        str q{{col | times:6 | plus:8 | plus: 4}}, [ x5, #64 ]\n        str q{{col | times:6 | plus:8 | plus: 5}}, [ x5, #80 ]\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store_strides_generic:\n\n    {% for col in (0..3) %}\n        mov x4, x5\n        {% for reg in (0..5) %}\n            {% for lane in (0..3) %}\n                st1 { v{{col | times:6 | plus:8 | plus: reg}}.s }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_32x1_core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs:\n// - x19-x29 to preserve (but x19, x28, x29 not used) \n// - d8..d15 to preserve\n// - v16 to v31, no need to preserve\n// \n//      v16[0] v18[0] v20[0] v22[0] v24[0] v26[0] v28[0] v30[0]\n//      v16[1] v18[1] \n//      v16[2] v18[2] \n//      v16[3] v18[3]\n//                     \n//      v17[0] v19[0] v21[0] v23[0] v25[0] v27[0] v29[0] v31[0]\n//      v17[1] v19[1] \n//      v17[2] v19[2] \n//      v17[3] v19[3] \n\n// packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_mmm_f32_32x1_{{core}}_{{suffix}}\n{{G}}arm64simd_mmm_f32_32x1_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldp         x2, x4, [x0, #24]   // b, packing\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    cmp         x4, #1\n    beq         .q4f16se\n    cmp         x4, #2\n    beq         .q4f32se\n    cmp         x4, #3\n    beq         .f16f16\n    cmp         x4, #4\n    beq         .f32f16\n    cmp         x4, #5\n    beq         .f16f32\n\n    sub         x3, x3, #1\n\n.p2align 4\n.packed_packed_loop_1:\n    ld1         { v8.s }[0], [ x2 ], #4\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x1 ], #64\n    ld1         { v4.4s, v5.4s, v6.4s, v7.4s }, [ x1 ], #64\n\n    fmla        v24.4s, v0.4s, v8.s[0]\n    fmla        v25.4s, v1.4s, v8.s[0]\n    fmla        v26.4s, v2.4s, v8.s[0]\n    fmla        v27.4s, v3.4s, v8.s[0]\n    fmla        v28.4s, v4.4s, v8.s[0]\n    fmla        v29.4s, v5.4s, v8.s[0]\n    fmla        v30.4s, v6.4s, v8.s[0]\n    fmla        v31.4s, v7.4s, v8.s[0]\n\n    subs        x3, x3, #1\n    bge         .packed_packed_loop_1\n\n    b           .non_linear_loop\n\n.p2align 8\n.q40f16_const:\n    .byte 0xc8, 0xc7, 0xc6, 0xc5, 0xc4, 0xc2, 0xc0, 0xbc\n    .byte 0x00, 0x3c, 0x40, 0x42, 0x44, 0x45, 0x46, 0x47\n\n.q4f16se:\n    adr      x4, .q40f16_const\n    movi     v15.16b, 15\n    ld1      {v13.16b}, [ x4 ]\n    eor      v12.16b, v12.16b, v12.16b\n\n.q4f16se_outerloop:\n{% for i in (0..7) %}\n    eor      v{{i|plus:16}}.16b, v{{i|plus:16}}.16b, v{{i|plus:16}}.16b\n{% endfor %}\n    mov         x4, #32\n\n.p2align 4\n.q4f16se_innerloop:\n        ld1      { v10.16b }, [ x1 ], #16\n        ld1      { v11.h }[0], [ x2 ], #2\n\n        and      v9.16b, v10.16b, v15.16b\n        ushr     v10.16b, v10.16b, 4\n\n        tbl      v9.16b, { v13.16b }, v9.16b\n        tbl      v10.16b, { v13.16b }, v10.16b\n\n        zip1     v0.16b, v12.16b, v9.16b\n        zip2     v2.16b, v12.16b, v9.16b\n        zip1     v4.16b, v12.16b, v10.16b\n        zip2     v6.16b, v12.16b, v10.16b\n\n        fcvtl    v11.4s, v11.4h\n\n        fcvtl2   v1.4s, v0.8h\n        fcvtl2   v3.4s, v2.8h\n        fcvtl2   v5.4s, v4.8h\n        fcvtl2   v7.4s, v6.8h\n        fcvtl    v0.4s, v0.4h\n        fcvtl    v2.4s, v2.4h\n        fcvtl    v4.4s, v4.4h\n        fcvtl    v6.4s, v6.4h\n\n{% for i in (0..7) %}\n        fmla        v{{ i|plus: 16 }}.4s, v{{i}}.4s, v11.s[0]\n{% endfor %}\n\n    subs        x4, x4, #1\n    bne         .q4f16se_innerloop\n\n    // scales\n    ld1         { v0.8h-v3.8h }, [ x1 ], #64\n\n    fcvtl       v4.4s, v0.4h\n    fcvtl2      v5.4s, v0.8h\n    fcvtl       v6.4s, v1.4h\n    fcvtl2      v7.4s, v1.8h\n    fcvtl       v8.4s, v2.4h\n    fcvtl2      v9.4s, v2.8h\n    fcvtl       v10.4s, v3.4h\n    fcvtl2      v11.4s, v3.8h\n\n{% for i in (0..7) %}\n       fmla     v{{i|plus:24}}.4s, v{{i|plus:4}}.4s, v{{i|plus:16}}.4s\n{% endfor %}\n\n    subs        x3, x3, #32\n    bne         .q4f16se_outerloop\n\n    b           .non_linear_loop\n\n.q4f32se:\n    adr      x4, .q40f16_const\n    movi     v15.16b, 15\n    ld1      {v13.16b}, [ x4 ]\n    eor      v12.16b, v12.16b, v12.16b\n\n.q4f32se_outerloop:\n{% for i in (0..7) %}\n    eor      v{{i|plus:16}}.16b, v{{i|plus:16}}.16b, v{{i|plus:16}}.16b\n{% endfor %}\n    mov         x4, #32\n\n.p2align 4\n.q4f32se_innerloop:\n        ld1      { v10.16b }, [ x1 ], #16\n        ld1      { v11.s }[0], [ x2 ], #4\n\n        and      v9.16b, v10.16b, v15.16b\n        ushr     v10.16b, v10.16b, 4\n\n        tbl      v9.16b, { v13.16b }, v9.16b\n        tbl      v10.16b, { v13.16b }, v10.16b\n\n        zip1     v0.16b, v12.16b, v9.16b\n        zip2     v2.16b, v12.16b, v9.16b\n        zip1     v4.16b, v12.16b, v10.16b\n        zip2     v6.16b, v12.16b, v10.16b\n\n        fcvtl2   v1.4s, v0.8h\n        fcvtl2   v3.4s, v2.8h\n        fcvtl2   v5.4s, v4.8h\n        fcvtl2   v7.4s, v6.8h\n        fcvtl    v0.4s, v0.4h\n        fcvtl    v2.4s, v2.4h\n        fcvtl    v4.4s, v4.4h\n        fcvtl    v6.4s, v6.4h\n\n{% for i in (0..7) %}\n        fmla        v{{ i|plus: 16 }}.4s, v{{i}}.4s, v11.s[0]\n{% endfor %}\n\n    subs        x4, x4, #1\n    bne         .q4f32se_innerloop\n\n    // scales\n    ld1         { v0.8h-v3.8h }, [ x1 ], #64\n\n    fcvtl       v4.4s, v0.4h\n    fcvtl2      v5.4s, v0.8h\n    fcvtl       v6.4s, v1.4h\n    fcvtl2      v7.4s, v1.8h\n    fcvtl       v8.4s, v2.4h\n    fcvtl2      v9.4s, v2.8h\n    fcvtl       v10.4s, v3.4h\n    fcvtl2      v11.4s, v3.8h\n\n{% for i in (0..7) %}\n       fmla     v{{i|plus:24}}.4s, v{{i|plus:4}}.4s, v{{i|plus:16}}.4s\n{% endfor %}\n\n    subs        x3, x3, #32\n    bne         .q4f32se_outerloop\n\n    b           .non_linear_loop\n\n.p2align 4\n.f16f16:\n    sub         x3, x3, #1\n.f16f16_loop:\n    ld1         { v9.h }[0], [ x2 ], #2\n    ld1         { v10.8h-v13.8h }, [ x1 ], #64\n\n    fcvtl       v8.4s, v9.4h\n    {% for reg in (0..3) %}\n        fcvtl       v{{reg|times:2}}.4s, v{{reg|plus:10}}.4h\n        fcvtl2      v{{reg|times:2|plus:1}}.4s, v{{reg|plus:10}}.8h\n    {% endfor %}\n\n    fmla        v24.4s, v0.4s, v8.s[0]\n    fmla        v25.4s, v1.4s, v8.s[0]\n    fmla        v26.4s, v2.4s, v8.s[0]\n    fmla        v27.4s, v3.4s, v8.s[0]\n    fmla        v28.4s, v4.4s, v8.s[0]\n    fmla        v29.4s, v5.4s, v8.s[0]\n    fmla        v30.4s, v6.4s, v8.s[0]\n    fmla        v31.4s, v7.4s, v8.s[0]\n\n    subs        x3, x3, #1\n    bge         .f16f16_loop\n\n    b           .non_linear_loop\n\n.p2align 4\n.f32f16:\n    sub         x3, x3, #1\n.f32f16_loop:\n    ld1         { v9.h }[0], [ x2 ], #2\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x1 ], #64\n    ld1         { v4.4s, v5.4s, v6.4s, v7.4s }, [ x1 ], #64\n\n    fcvtl       v8.4s, v9.4h\n\n    fmla        v24.4s, v0.4s, v8.s[0]\n    fmla        v25.4s, v1.4s, v8.s[0]\n    fmla        v26.4s, v2.4s, v8.s[0]\n    fmla        v27.4s, v3.4s, v8.s[0]\n    fmla        v28.4s, v4.4s, v8.s[0]\n    fmla        v29.4s, v5.4s, v8.s[0]\n    fmla        v30.4s, v6.4s, v8.s[0]\n    fmla        v31.4s, v7.4s, v8.s[0]\n\n    subs        x3, x3, #1\n    bge         .f32f16_loop\n\n    b           .non_linear_loop\n\n.p2align 4\n.f16f32:\n    sub         x3, x3, #1\n.f16f32_loop:\n    ld1         { v8.s }[0], [ x2 ], #4\n    ld1         { v10.8h-v13.8h }, [ x1 ], #64\n\n    {% for reg in (0..3) %}\n        fcvtl       v{{reg|times:2}}.4s, v{{reg|plus:10}}.4h\n        fcvtl2      v{{reg|times:2|plus:1}}.4s, v{{reg|plus:10}}.8h\n    {% endfor %}\n\n    fmla        v24.4s, v0.4s, v8.s[0]\n    fmla        v25.4s, v1.4s, v8.s[0]\n    fmla        v26.4s, v2.4s, v8.s[0]\n    fmla        v27.4s, v3.4s, v8.s[0]\n    fmla        v28.4s, v4.4s, v8.s[0]\n    fmla        v29.4s, v5.4s, v8.s[0]\n    fmla        v30.4s, v6.4s, v8.s[0]\n    fmla        v31.4s, v7.4s, v8.s[0]\n\n    subs        x3, x3, #1\n    bge         .f16f32_loop\n\n    b           .non_linear_loop\n\n{% include \"arm64simd_mmm_f32_scalars.tmpliq\" from:24, to:31%}\n{% include \"arm64simd_mmm_f32_per_rows.tmpliq\" mr:32, from:24, to:31%}\n{% include \"arm64simd_mmm_f32_per_cols.tmpliq\" mr:32, from:24, to:31%}\n{% include \"arm64simd_mmm_load_tile.tmpliq\" from:24, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]           // c base ptr, rsc\n    cmp         x6, #4\n    beq         .do_per_row_add\n\n    {% for reg in (24..31) %}\n        {% for lane in (0..3) %}\n            ld1 {v0.s}[{{lane}}], [ x5 ], x6\n        {% endfor %}\n        fadd v{{reg}}.4s, v{{reg}}.4s, v0.4s\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.do_per_row_add:\n    ld1     {v0.4s-v3.4s}, [x5], #64\n    ld1     {v4.4s-v7.4s}, [x5], #64\n\n    {% for r in (0..7) %}\n        fadd v{{r| plus: 24}}.4s, v{{r | plus: 24}}.4s, v{{r}}.4s\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x3, [x0, #16]\n    ldr     x2, [x0, #8]\n\n    ld1         {v8.s}[0], [ x3 ]\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x2 ], #64\n    ld1         { v4.4s, v5.4s, v6.4s, v7.4s }, [ x2 ], #64\n\n    fmla        v24.4s, v0.4s, v8.s[0]\n    fmla        v25.4s, v1.4s, v8.s[0]\n    fmla        v26.4s, v2.4s, v8.s[0]\n    fmla        v27.4s, v3.4s, v8.s[0]\n    fmla        v28.4s, v4.4s, v8.s[0]\n    fmla        v29.4s, v5.4s, v8.s[0]\n    fmla        v30.4s, v6.4s, v8.s[0]\n    fmla        v31.4s, v7.4s, v8.s[0]\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]                // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]               // csc, item_size\n\n    cmp         x8, #2\n    beq         .store_f16\n\n    cmp         x6, #4\n    beq         .store_strides_contig\n\n    {% for reg in (24..31) %}\n        {% for lane in (0..3) %}\n            st1 { v{{reg}}.s }[{{lane}}], [ x5 ], x6\n        {% endfor %}\n    {% endfor %}\n    b           .non_linear_loop\n\n.store_strides_contig:\n\n    {% for reg in (24..31) %}\n        st1 { v{{reg}}.4s }, [ x5 ], #16\n    {% endfor %}\n    b           .non_linear_loop\n\n.store_f16:\n    {% for reg in (0..3) %}\n        fcvtn  v{{reg}}.4h, v{{reg|times:2|plus:24}}.4s\n        fcvtn2 v{{reg}}.8h, v{{reg|times:2|plus:25}}.4s\n    {% endfor %}\n\n    cmp         x6, #2\n    beq         .store_strides_contig_f16\n\n    {% for reg in (0..3) %}\n        {% for lane in (0..7) %}\n            st1 { v{{reg}}.h }[{{lane}}], [ x5 ], x6\n        {% endfor %}\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store_strides_contig_f16:\n\n    {% for reg in (0..3) %}\n        st1 { v{{reg}}.8h }, [ x5 ], #16\n    {% endfor %}\n    b           .non_linear_loop\n\n.return:\n\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_32x3_core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: v16 to v31, no need to preserve\n\n// no preservation either for v0-v7...\n// v8..v15 are callee-preserved\n// packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n.global {{G}}arm64simd_mmm_f32_32x3_{{core}}_{{suffix}}\n{{G}}arm64simd_mmm_f32_32x3_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldp         x2, x4, [x0, #24]   // b, packing\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    cmp         x4, #1\n    beq         .f32f16\n    cmp         x4, #2\n    beq         .f16f32\n    cmp         x4, #3\n    beq         .f16f16\n\n.p2align 4\n.packed_packed_loop_1:\n    ld1         { v7.4s }, [ x2 ]\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x1 ], #64\n    ld1         { v4.4s, v5.4s, v6.4s }, [ x1 ], #48\n    add         x2, x2, #12\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:8}}.4s, v0.4s, v7.s[{{ col }}]\n{% endfor %}\n\n    ld1         { v0.4s }, [ x1 ], #16\n\n{% for row in (1..6) %}\n    {% for col in (0..2) %}\n        fmla        v{{ col|times:8|plus:8|plus:row}}.4s, v{{row}}.4s, v7.s[{{col}}]\n    {% endfor %}\n{% endfor %}\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:15}}.4s, v0.4s, v7.s[{{ col }}]\n{% endfor %}\n\n    subs        x3, x3, #1\n    bne         .packed_packed_loop_1\n\n    b           .non_linear_loop\n\n.p2align 4\n.f32f16:\n    ld1         { v7.4h }, [ x2 ]\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x1 ], #64\n    ld1         { v4.4s, v5.4s, v6.4s }, [ x1 ], #48\n    fcvtl       v7.4s, v7.4h\n    add         x2, x2, #6\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:8}}.4s, v0.4s, v7.s[{{ col }}]\n{% endfor %}\n\n    ld1         { v0.4s }, [ x1 ], #16\n\n{% for row in (1..6) %}\n    {% for col in (0..2) %}\n        fmla        v{{ col|times:8|plus:8|plus:row}}.4s, v{{row}}.4s, v7.s[{{col}}]\n    {% endfor %}\n{% endfor %}\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:15}}.4s, v0.4s, v7.s[{{ col }}]\n{% endfor %}\n\n    subs        x3, x3, #1\n    bne         .f32f16\n\n    b           .non_linear_loop\n\n.p2align 4\n.f16f32:\n    ld1         { v7.4s }, [ x2 ]\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [ x1 ], #64\n    add         x2, x2, #12\n\n    fcvtl       v4.4s, v0.4h\n    fcvtl2      v5.4s, v0.8h\n    fcvtl       v6.4s, v1.4h\n    fcvtl2      v0.4s, v1.8h\n\n    {% for col in (0..2) %}\n        fmla        v{{ col|times:8|plus:8}}.4s, v4.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:9}}.4s, v5.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:10}}.4s, v6.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:11}}.4s, v0.4s, v7.s[{{col}}]\n    {% endfor %}\n\n    fcvtl       v4.4s, v2.4h\n    fcvtl2      v5.4s, v2.8h\n    fcvtl       v6.4s, v3.4h\n    fcvtl2      v1.4s, v3.8h\n    \n    {% for col in (0..2) %}\n        fmla        v{{ col|times:8|plus:12}}.4s, v4.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:13}}.4s, v5.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:14}}.4s, v6.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:15}}.4s, v1.4s, v7.s[{{col}}]\n    {% endfor %}\n\n    subs        x3, x3, #1\n    bne         .f16f32\n\n    b           .non_linear_loop\n\n.p2align 4\n.f16f16:\n    ld1         { v7.4h }, [ x2 ]\n    ld1         { v0.8h, v1.8h, v2.8h, v3.8h }, [ x1 ], #64\n    add         x2, x2, #6\n\n    fcvtl       v7.4s, v7.4h\n\n    fcvtl       v4.4s, v0.4h\n    fcvtl2      v5.4s, v0.8h\n    fcvtl       v6.4s, v1.4h\n    fcvtl2      v0.4s, v1.8h\n\n    {% for col in (0..2) %}\n        fmla        v{{ col|times:8|plus:8}}.4s, v4.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:9}}.4s, v5.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:10}}.4s, v6.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:11}}.4s, v0.4s, v7.s[{{col}}]\n    {% endfor %}\n\n    fcvtl       v4.4s, v2.4h\n    fcvtl2      v5.4s, v2.8h\n    fcvtl       v6.4s, v3.4h\n    fcvtl2      v1.4s, v3.8h\n    \n    {% for col in (0..2) %}\n        fmla        v{{ col|times:8|plus:12}}.4s, v4.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:13}}.4s, v5.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:14}}.4s, v6.4s, v7.s[{{col}}]\n        fmla        v{{ col|times:8|plus:15}}.4s, v1.4s, v7.s[{{col}}]\n    {% endfor %}\n\n    subs        x3, x3, #1\n    bne         .f16f16\n\n    b           .non_linear_loop\n\n\n{% include \"arm64simd_mmm_f32_scalars.tmpliq\" from:8, to:31%}\n{% include \"arm64simd_mmm_f32_per_rows.tmpliq\" mr:32, from:8, to:31%}\n{% include \"arm64simd_mmm_f32_per_cols.tmpliq\" mr:32, from:8, to:31%}\n{% include \"arm64simd_mmm_load_tile.tmpliq\" from:8, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    {% for col in (0..2) %}\n        mov x4, x5\n        {% for reg in (0..7) %}\n            {% for lane in (0..3) %}\n                ld1 {v0.s}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            fadd v{{col | times:8 | plus: 8| plus: reg}}.4s, v{{col | times:8 | plus: 8 | plus: reg}}.4s, v0.4s\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldp         x2, x3, [x0, #8]\n\n    ld1         { v7.d }[0], [ x3 ], #8\n    ld1         { v7.s }[2], [ x3 ], #4\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x2 ], #64\n    ld1         { v4.4s, v5.4s, v6.4s }, [ x2 ], #48\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:8}}.4s, v0.4s, v7.s[{{ col }}]\n{% endfor %}\n\n    ld1         { v0.4s }, [ x2 ], #16\n\n{% for row in (1..6) %}\n    {% for col in (0..2) %}\n        fmla        v{{ col|times:8|plus:8|plus:row}}.4s, v{{row}}.4s, v7.s[{{col}}]\n    {% endfor %}\n{% endfor %}\n\n{% for col in (0..2) %}\n    fmla        v{{ col|times:8|plus:15}}.4s, v0.4s, v7.s[{{ col }}]\n{% endfor %}\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]                // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]               // csc, item_size\n\n    cmp         x8, #2\n    beq         .store_f16\n\n    cmp         x6, #4\n    beq         .store_strides_contig\n\n\n    {% for col in (0..2) %}\n        mov x4, x5\n        {% for reg in (0..7) %}\n            {% for lane in (0..3) %}\n                st1 { v{{col | times:8 | plus: 8 | plus: reg}}.s }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n    b           .non_linear_loop\n\n.store_strides_contig:\n\n    {% for col in (0..2) %}\n        mov x4, x5\n        {% for r in (0..7) %}\n            st1 { v{{col | times:8 | plus: 8 | plus: r}}.4s }, [ x4 ], 16\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store_f16:\n\n    cmp         x6, #2\n    beq         .store_strides_contig_f16\n\n    {% for col in (0..2) %}\n        {% for reg in (0..3) %}\n            fcvtn  v{{reg}}.4h, v{{col|times:4|plus:reg|times:2|plus:8}}.4s\n            fcvtn2 v{{reg}}.8h, v{{col|times:4|plus:reg|times:2|plus:9}}.4s\n        {% endfor %}\n\n        mov x4, x5\n        {% for reg in (0..3) %}\n            {% for lane in (0..7) %}\n                st1 { v{{reg}}.h }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n\n    {% endfor %}\n\n\n    b           .non_linear_loop\n\n.store_strides_contig_f16:\n\n    {% for col in (0..2) %}\n        {% for reg in (0..3) %}\n            fcvtn  v{{reg}}.4h, v{{col|times:4|plus:reg|times:2|plus:8}}.4s\n            fcvtn2 v{{reg}}.8h, v{{col|times:4|plus:reg|times:2|plus:9}}.4s\n        {% endfor %}\n\n        mov x4, x5\n        {% for reg in (0..3) %}\n            st1 { v{{reg}}.4s }, [ x4 ], #16\n        {% endfor %}\n        add x5, x5, x7\n\n    {% endfor %}\n    b           .non_linear_loop\n\n\n.return:\n\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_64x1/loop1/cortex_a53.tmpli",
    "content": "    fmla        v16.4s, v0.4s, v8.s[0]\n    ldr         x5, [x1, #128]\n    fmla        v17.4s, v1.4s, v8.s[0]\n    ldr         x6, [x1, #136]\n    fmla        v18.4s, v2.4s, v8.s[0]\n    ldr         x7, [x1, #144]\n    fmla        v19.4s, v3.4s, v8.s[0]\n    ldr         x9, [x1, #152]\n    ld1         {{ v0.4s, v1.4s, v2.4s, v3.4s }}, [ x1 ], #64\n\n    fmla        v20.4s, v4.4s, v8.s[0]\n    ldr         x10, [x1, #96]\n    fmla        v21.4s, v5.4s, v8.s[0]\n    ldr         x11, [x1, #104]\n    fmla        v22.4s, v6.4s, v8.s[0]\n    ldr         x12, [x1, #112]\n    fmla        v23.4s, v7.4s, v8.s[0]\n    ldr         x13, [x1, #120]\n\n    ld1         {{ v4.4s, v5.4s, v6.4s, v7.4s }}, [ x1 ]\n\n    fmla        v24.4s, v0.4s, v8.s[0]\n    ldr         x14, [x1, #128]\n    fmla        v25.4s, v1.4s, v8.s[0]\n    ldr         x15, [x1, #136]\n    fmla        v26.4s, v2.4s, v8.s[0]\n    ldr         x20, [x1, #144]\n    fmla        v27.4s, v3.4s, v8.s[0]\n    ldr         x21, [x1, #152]\n    fmla        v28.4s, v4.4s, v8.s[0]\n    ldr         x22, [x1, #160]\n    fmla        v29.4s, v5.4s, v8.s[0]\n    ldr         x23, [x1, #168]\n    fmla        v30.4s, v6.4s, v8.s[0]\n    ldr         x24, [x1, #176]\n    fmla        v31.4s, v7.4s, v8.s[0]\n    ldr         x25, [x1, #184]\n\n    ld1         {{ v8.s }}[0], [ x2 ], #4\n\n    prfm        pldl1keep, [x1, #1024]\n    prfm        pldl1keep, [x1, #1088]\n    prfm        pldl1keep, [x1, #1152]\n    prfm        pldl1keep, [x1, #1216]\n    prfm        pldl1keep, [x2, #256]\n\n    ins         v0.d[0], x5\n    ins         v1.d[0], x7\n    ins         v2.d[0], x10\n    ins         v3.d[0], x12\n    ins         v4.d[0], x14\n    ins         v5.d[0], x20\n    ins         v6.d[0], x22\n    ins         v7.d[0], x24\n\n    ins         v0.d[1], x6\n    ins         v1.d[1], x9\n    ins         v2.d[1], x11\n    ins         v3.d[1], x13\n    ins         v4.d[1], x15\n    ins         v5.d[1], x21\n    ins         v6.d[1], x23\n    ins         v7.d[1], x25\n\n    add         x1, x1, #192\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_64x1/loop1/naive.tmpli",
    "content": "    ld1         {{ v9.4s, v10.4s, v11.4s, v12.4s }}, [x1], #64\n    ld1         {{ v13.4s, v14.4s, v15.4s }}, [x1], #48\n\n    fmla        v16.4s, v0.4s, v8.s[0]\n    fmla        v17.4s, v1.4s, v8.s[0]\n    fmla        v18.4s, v2.4s, v8.s[0]\n    fmla        v19.4s, v3.4s, v8.s[0]\n    fmla        v20.4s, v4.4s, v8.s[0]\n    fmla        v21.4s, v5.4s, v8.s[0]\n    fmla        v22.4s, v6.4s, v8.s[0]\n    fmla        v23.4s, v7.4s, v8.s[0]\n    fmla        v24.4s, v9.4s, v8.s[0]\n    ld1         {{ v9.4s }}, [ x1 ], #16\n    ld1         {{ v0.4s, v1.4s, v2.4s, v3.4s }}, [x1], #64\n    ld1         {{ v4.4s, v5.4s, v6.4s, v7.4s }}, [x1], #64\n    fmla        v25.4s, v10.4s, v8.s[0]\n    fmla        v26.4s, v11.4s, v8.s[0]\n    fmla        v27.4s, v12.4s, v8.s[0]\n    fmla        v28.4s, v13.4s, v8.s[0]\n    fmla        v29.4s, v14.4s, v8.s[0]\n    fmla        v30.4s, v15.4s, v8.s[0]\n\n    fmla        v31.4s, v9.4s, v8.s[0]\n\n    ld1         {{ v8.s }}[0], [ x2 ], #4\n\n    prfm        pldl1keep, [x1, #1024]\n    prfm        pldl1keep, [x1, #1088]\n    prfm        pldl1keep, [x1, #1152]\n    prfm        pldl1keep, [x1, #1216]\n    prfm        pldl1keep, [x2, #256]\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_64x1/loop2/cortex_a55.tmpli",
    "content": "    ld1         {{ v9.4s, v10.4s, v11.4s }}, [x1], #48\n\n    fmla        v16.4s, v0.4s, v8.s[0]\n    ldr         x8, [x2], #8\n    fmla        v17.4s, v1.4s, v8.s[0]\n    ldr         d12, [x1], #8\n    fmla        v18.4s, v2.4s, v8.s[0]\n    ldr         x12, [x1], #8\n    fmla        v19.4s, v3.4s, v8.s[0]\n    ldr         d13, [x1], #8\n    fmla        v20.4s, v4.4s, v8.s[0]\n    ldr         x13, [x1], #8\n    fmla        v21.4s, v5.4s, v8.s[0]\n    ldr         d14, [x1], #8\n    fmla        v22.4s, v6.4s, v8.s[0]\n    ldr         x14, [x1], #8\n    fmla        v23.4s, v7.4s, v8.s[0]\n    ldr         d15, [x1], #8\n    fmla        v24.4s, v9.4s, v8.s[0]\n    ldr         x15, [x1], #8\n\n    ld1         {{ v0.4s, v1.4s, v2.4s, v3.4s }}, [x1], #64\n    ins         v8.d[1], x8\n    ld1         {{ v4.4s, v5.4s, v6.4s, v7.4s }}, [x1], #64\n\n    fmla        v25.4s, v10.4s, v8.s[0]\n    ins         v12.d[1], x12\n    fmla        v26.4s, v11.4s, v8.s[0]\n    ins         v13.d[1], x13\n    fmla        v27.4s, v12.4s, v8.s[0]\n    ins         v14.d[1], x14\n    fmla        v28.4s, v13.4s, v8.s[0]\n    ins         v15.d[1], x15\n\n    ld1         {{ v9.4s, v10.4s, v11.4s, v12.4s }}, [x1], #64\n\n    fmla        v29.4s, v14.4s, v8.s[0]\n    ldr         d13, [x1], #8\n    fmla        v30.4s, v15.4s, v8.s[0]\n    ldr         x13, [x1], #8\n    fmla        v31.4s, v0.4s, v8.s[0]\n    ldr         d14, [x1], #8\n\n    fmla        v16.4s, v1.4s, v8.s[2]\n    ldr         x14, [x1], #8\n    fmla        v17.4s, v2.4s, v8.s[2]\n    ldr         d15, [x1], #8\n    fmla        v18.4s, v3.4s, v8.s[2]\n    ldr         x15, [x1], #8\n    fmla        v19.4s, v4.4s, v8.s[2]\n\n    ld1         {{ v0.4s }}, [x1], #16\n\n    fmla        v20.4s, v5.4s, v8.s[2]\n    ldr         d1, [x1], #8\n    fmla        v21.4s, v6.4s, v8.s[2]\n    ldr         x10, [x1], #8\n\n    fmla        v22.4s, v7.4s, v8.s[2]\n\n    fmla        v23.4s, v9.4s, v8.s[2]\n    ins         v13.d[1], x13\n    fmla        v24.4s, v10.4s, v8.s[2]\n    ins         v14.d[1], x14\n    fmla        v25.4s, v11.4s, v8.s[2]\n    ins         v15.d[1], x15\n\n    fmla        v26.4s, v12.4s, v8.s[2]\n    prfm        pldl1keep, [x1, #1024]\n    fmla        v27.4s, v13.4s, v8.s[2]\n    ins         v1.d[1], x10\n    fmla        v28.4s, v14.4s, v8.s[2]\n    prfm        pldl1keep, [x1, #1088]\n    fmla        v29.4s, v15.4s, v8.s[2]\n    prfm        pldl1keep, [x1, #1152]\n    fmla        v30.4s, v0.4s, v8.s[2]\n    prfm        pldl1keep, [x1, #1216]\n    fmla        v31.4s, v1.4s, v8.s[2]\n    prfm        pldl1keep, [x2, #256]\n\n    ld1         {{ v0.4s, v1.4s, v2.4s, v3.4s }}, [x1], #64\n    ins         v8.s[0], v8.s[3]\n    ld1         {{ v4.4s, v5.4s, v6.4s, v7.4s }}, [x1], #64\n\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_64x1/loop2/naive.tmpli",
    "content": "// load a: v9, v10, v11, v12, v13, v14, v15\n// load a: v0, v1, v2, v3, v4, v4, v6, v7\n\n    ld1         {{ v9.4s, v10.4s, v11.4s, v12.4s }}, [x1], #64\n    ld1         {{ v13.4s, v14.4s, v15.4s }}, [x1], #48\n\n    fmla        v16.4s, v0.4s, v8.s[0]\n    fmla        v17.4s, v1.4s, v8.s[0]\n    fmla        v18.4s, v2.4s, v8.s[0]\n    fmla        v19.4s, v3.4s, v8.s[0]\n\n    ld1         {{ v0.4s, v1.4s }}, [x1], #32\n\n    fmla        v20.4s, v4.4s, v8.s[0]\n    fmla        v21.4s, v5.4s, v8.s[0]\n\n    ld1         {{  v2.4s, v3.4s, v4.4s, v5.4s }}, [x1], #64\n    fmla        v22.4s, v6.4s, v8.s[0]\n    fmla        v23.4s, v7.4s, v8.s[0]\n\n    ld1         {{  v6.4s, v7.4s }}, [x1], #32\n\n    fmla        v24.4s, v9.4s, v8.s[0]\n    fmla        v25.4s, v10.4s, v8.s[0]\n    fmla        v26.4s, v11.4s, v8.s[0]\n    fmla        v27.4s, v12.4s, v8.s[0]\n    fmla        v28.4s, v13.4s, v8.s[0]\n    fmla        v29.4s, v14.4s, v8.s[0]\n    fmla        v30.4s, v15.4s, v8.s[0]\n\n    ld1         {{ v9.4s, v10.4s, v11.4s, v12.4s }}, [x1], #64\n    ld1         {{ v13.4s, v14.4s, v15.4s }}, [x1], #48\n\n    fmla        v31.4s, v0.4s, v8.s[0]\n    ld1         {{ v8.s }}[0], [ x2 ], #4\n\n    fmla        v16.4s, v1.4s, v8.s[0]\n    ld1         {{ v0.4s, v1.4s }}, [x1], #32\n    fmla        v17.4s, v2.4s, v8.s[0]\n    fmla        v18.4s, v3.4s, v8.s[0]\n    fmla        v19.4s, v4.4s, v8.s[0]\n\n    fmla        v20.4s, v5.4s, v8.s[0]\n    fmla        v21.4s, v6.4s, v8.s[0]\n    fmla        v22.4s, v7.4s, v8.s[0]\n    fmla        v23.4s, v9.4s, v8.s[0]\n\n    fmla        v24.4s, v10.4s, v8.s[0]\n    fmla        v25.4s, v11.4s, v8.s[0]\n    fmla        v26.4s, v12.4s, v8.s[0]\n    fmla        v27.4s, v13.4s, v8.s[0]\n    fmla        v28.4s, v14.4s, v8.s[0]\n    fmla        v29.4s, v15.4s, v8.s[0]\n    fmla        v30.4s, v0.4s, v8.s[0]\n    fmla        v31.4s, v1.4s, v8.s[0]\n    ld1         {{ v8.s }}[0], [ x2 ], #4\n\n    ld1         {{ v0.4s, v1.4s, v2.4s, v3.4s }}, [x1], #64\n    ld1         {{ v4.4s, v5.4s, v6.4s, v7.4s }}, [x1], #64\n\n    prfm        pldl1keep, [x1, #1024]\n    prfm        pldl1keep, [x1, #1088]\n    prfm        pldl1keep, [x1, #1152]\n    prfm        pldl1keep, [x1, #1216]\n    prfm        pldl1keep, [x2, #256]\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_64x1_core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs:\n// - x19-x29 to preserve (but x19, x28, x29 not used) \n// - d8..d15 to preserve\n// - v16 to v31, no need to preserve\n// \n//      v16[0] v18[0] v20[0] v22[0] v24[0] v26[0] v28[0] v30[0]\n//      v16[1] v18[1] \n//      v16[2] v18[2] \n//      v16[3] v18[3]\n//                     \n//      v17[0] v19[0] v21[0] v23[0] v25[0] v27[0] v29[0] v31[0]\n//      v17[1] v19[1] \n//      v17[2] v19[2] \n//      v17[3] v19[3] \n\n// packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_mmm_f32_64x1_{{core}}_{{suffix}}\n{{G}}arm64simd_mmm_f32_64x1_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n    sub         x3, x3, #1\n\n\n    ld1         { v8.s }[0], [ x2 ], #4\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [ x1 ], #64\n    ld1         { v4.4s, v5.4s, v6.4s, v7.4s }, [ x1 ], #64\n\n    cmp         x3, #0\n    beq         .packed_packed_loop_1_last\n\n    cmp         x3, #4\n    blt        .packed_packed_loop_1\n\n{% capture packed_packed_loop1 %}\n    {% if core == \"a53\" %}\n        {% include \"arm64simd_mmm_f32_64x1/loop1/cortex_a53.tmpli\" %}\n    {% else %}\n        {% include \"arm64simd_mmm_f32_64x1/loop1/naive.tmpli\" %}\n    {% endif %}\n{% endcapture %}\n\n{% capture packed_packed_loop2 %}\n    {% if core == \"a53\" %}\n        {{ packed_packed_loop1 }}\n        {{ packed_packed_loop1 }}\n    {% elsif core == \"a55\" %}\n        {% include \"arm64simd_mmm_f32_64x1/loop2/cortex_a55.tmpli\" %}\n    {% else %}\n        {% include \"arm64simd_mmm_f32_64x1/loop2/naive.tmpli\" %}\n    {% endif %}\n{% endcapture %}\n\n.p2align 4\n.packed_packed_loop_4:\n    {{ packed_packed_loop2 }}\n    {{ packed_packed_loop2 }}\n\n    sub         x3, x3, #4\n    cmp         x3, #4\n    bge         .packed_packed_loop_4\n\n    cmp         x3, #0\n    beq         .packed_packed_loop_1_last\n\n.p2align 4\n.packed_packed_loop_1:\n    {{ packed_packed_loop1 }}\n\n    subs        x3, x3, #1\n    bne         .packed_packed_loop_1\n\n// last loop can't read beyond actual input as it's likely not packed and padded\n.packed_packed_loop_1_last:\n    ld1         { v9.4s, v10.4s, v11.4s, v12.4s }, [x1], #64\n    ld1         { v13.4s, v14.4s, v15.4s }, [x1], #48\n\n    fmla        v16.4s, v0.4s, v8.s[0]\n    fmla        v17.4s, v1.4s, v8.s[0]\n    ld1         { v0.4s }, [ x1 ]\n    fmla        v18.4s, v2.4s, v8.s[0]\n    fmla        v19.4s, v3.4s, v8.s[0]\n    fmla        v20.4s, v4.4s, v8.s[0]\n    fmla        v21.4s, v5.4s, v8.s[0]\n    fmla        v22.4s, v6.4s, v8.s[0]\n    fmla        v23.4s, v7.4s, v8.s[0]\n\n    fmla        v24.4s, v9.4s, v8.s[0]\n    fmla        v25.4s, v10.4s, v8.s[0]\n    fmla        v26.4s, v11.4s, v8.s[0]\n    fmla        v27.4s, v12.4s, v8.s[0]\n    fmla        v28.4s, v13.4s, v8.s[0]\n    fmla        v29.4s, v14.4s, v8.s[0]\n    fmla        v30.4s, v15.4s, v8.s[0]\n    fmla        v31.4s, v0.4s, v8.s[0]\n\n    b           .non_linear_loop\n\n{% include \"arm64simd_mmm_f32_scalars.tmpliq\" from:16, to:31%}\n{% include \"arm64simd_mmm_f32_per_rows.tmpliq\" mr:64, from:16, to:31%}\n{% include \"arm64simd_mmm_f32_per_cols.tmpliq\" mr:64, from:16, to:31%}\n{% include \"arm64simd_mmm_load_tile.tmpliq\" from:16, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]           // c base ptr, rsc\n    cmp         x6, #4\n    beq         .do_per_row_add\n\n    {% for reg in (16..31) %}\n        {% for lane in (0..3) %}\n            ld1 {v0.s}[{{lane}}], [ x5 ], x6\n        {% endfor %}\n        fadd v{{reg}}.4s, v{{reg}}.4s, v0.4s\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.do_per_row_add:\n    ld1     {v0.4s-v3.4s}, [x5], #64\n    ld1     {v4.4s-v7.4s}, [x5], #64\n    ld1     {v8.4s-v11.4s}, [x5], #64\n    ld1     {v12.4s-v15.4s}, [x5], #64\n\n    {% for r in (0..15) %}\n        fadd v{{r| plus: 16}}.4s, v{{r | plus: 16}}.4s, v{{r}}.4s\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x3, [x0, #16]\n    ldr     x2, [x0, #8]\n\n    ld1         {v8.s}[0], [ x3 ]\n\n    {% for r in (0..7) %}\n        ldr     q{{r}}, [x2], #16\n    {% endfor %}\n\n    fmla        v16.4s, v0.4s, v8.s[0]\n    ldr         q0, [x2], #16\n    fmla        v17.4s, v1.4s, v8.s[0] \n    ldr         q1, [x2], #16\n    fmla        v18.4s, v2.4s, v8.s[0] \n    ldr         q2, [x2], #16\n    fmla        v19.4s, v3.4s, v8.s[0] \n    ldr         q3, [x2], #16\n    fmla        v20.4s, v4.4s, v8.s[0] \n    ldr         q4, [x2], #16\n    fmla        v21.4s, v5.4s, v8.s[0] \n    ldr         q5, [x2], #16\n    fmla        v22.4s, v6.4s, v8.s[0] \n    ldr         q6, [x2], #16\n    fmla        v23.4s, v7.4s, v8.s[0] \n    ldr         q7, [x2], #16\n\n    fmla        v24.4s, v0.4s, v8.s[0]\n    fmla        v25.4s, v1.4s, v8.s[0] \n    fmla        v26.4s, v2.4s, v8.s[0] \n    fmla        v27.4s, v3.4s, v8.s[0] \n    fmla        v28.4s, v4.4s, v8.s[0] \n    fmla        v29.4s, v5.4s, v8.s[0] \n    fmla        v30.4s, v6.4s, v8.s[0] \n    fmla        v31.4s, v7.4s, v8.s[0] \n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]                // c base ptr, rsc$\n\n    cmp         x6, #4\n    beq         .store_strides_contig\n\n    {% for reg in (16..31) %}\n        {% for lane in (0..3) %}\n            st1 { v{{reg}}.s }[{{lane}}], [ x5 ], x6\n        {% endfor %}\n    {% endfor %}\n    b           .non_linear_loop\n\n.store_strides_contig:\n\n    {% for reg in (16..31) %}\n        st1 { v{{reg}}.4s }, [ x5 ], #16\n    {% endfor %}\n    b           .non_linear_loop\n\n.return:\n\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_8x8/packed_packed_loop1/broken_chains.tmpli",
    "content": "ld1         {{ v2.4s, v3.4s }}, [x1], #32\nld1         {{ v6.4s, v7.4s }}, [x2], #32\n\nfmla        v16.4s, v0.4s, v4.s[0]\nfmla        v17.4s, v1.4s, v4.s[0]\nfmla        v18.4s, v0.4s, v4.s[1]\nfmla        v19.4s, v1.4s, v4.s[1]\nfmla        v20.4s, v0.4s, v4.s[2]\nfmla        v21.4s, v1.4s, v4.s[2]\nfmla        v22.4s, v0.4s, v4.s[3]\nfmla        v23.4s, v1.4s, v4.s[3]\n\nfmla        v24.4s, v0.4s, v5.s[0]\nfmla        v25.4s, v1.4s, v5.s[0]\nfmla        v26.4s, v0.4s, v5.s[1]\nfmla        v27.4s, v1.4s, v5.s[1]\nfmla        v28.4s, v0.4s, v5.s[2]\nfmla        v29.4s, v1.4s, v5.s[2]\nfmla        v30.4s, v0.4s, v5.s[3]\nfmla        v31.4s, v1.4s, v5.s[3]\n\nand         v0.16b, v2.16b, v2.16b\nand         v1.16b, v3.16b, v3.16b\nand         v4.16b, v6.16b, v6.16b\nand         v5.16b, v7.16b, v7.16b\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_8x8/packed_packed_loop1/ldr_w_no_preload.tmpli",
    "content": "\nfmla        v16.4s, v0.4s, v4.s[0]\nldr         w5, [x1], #4\nfmla        v17.4s, v1.4s, v4.s[0]\nldr         w20, [x2], #4\nfmla        v18.4s, v0.4s, v4.s[1]\nldr         w6, [x1], #4\nfmla        v20.4s, v1.4s, v4.s[1]\nldr         w21, [x2], #4\nfmla        v20.4s, v0.4s, v4.s[2]\nldr         w7, [x1], #4\nfmla        v21.4s, v1.4s, v4.s[2]\nldr         w22, [x2], #4\nfmla        v22.4s, v0.4s, v4.s[3]\nldr         w8, [x1], #4\nfmla        v23.4s, v1.4s, v4.s[3]\nldr         w23, [x2], #4\n\nfmla        v24.4s, v0.4s, v5.s[0]\nldr         w9, [x1], #4\nfmla        v25.4s, v1.4s, v5.s[0]\nldr         w24, [x2], #4\nfmla        v26.4s, v0.4s, v5.s[1]\nldr         w10, [x1], #4\nfmla        v27.4s, v1.4s, v5.s[1]\nldr         w25, [x2], #4\nfmla        v28.4s, v0.4s, v5.s[2]\nldr         w11, [x1], #4\nfmla        v29.4s, v1.4s, v5.s[2]\nldr         w26, [x2], #4\nfmla        v30.4s, v0.4s, v5.s[3]\nldr         w12, [x1], #4\nfmla        v31.4s, v1.4s, v5.s[3]\nldr         w27, [x2], #4\n\nins         v0.s[0], w5\nins         v4.s[0], w20\nins         v1.s[0], w9\nins         v5.s[0], w24\nins         v0.s[2], w7\nins         v4.s[2], w22\nins         v1.s[2], w11\nins         v5.s[2], w26\nins         v0.s[1], w6\nins         v4.s[1], w21\nins         v1.s[1], w10\nins         v5.s[1], w25\nins         v0.s[3], w8\nins         v4.s[3], w23\nins         v1.s[3], w12\nins         v5.s[3], w27\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_8x8/packed_packed_loop1/ldr_w_preload.tmpli",
    "content": "fmla        v16.4s, v0.4s, v4.s[0]\nldr         w5, [x1], #4\nfmla        v17.4s, v1.4s, v4.s[0]\nldr         w20, [x2], #4\nfmla        v18.4s, v0.4s, v4.s[1]\nldr         w6, [x1], #4\nfmla        v19.4s, v1.4s, v4.s[1]\nldr         w21, [x2], #4\nfmla        v20.4s, v0.4s, v4.s[2]\nldr         w7, [x1], #4\nfmla        v21.4s, v1.4s, v4.s[2]\nldr         w22, [x2], #4\nfmla        v22.4s, v0.4s, v4.s[3]\nldr         w8, [x1], #4\nfmla        v23.4s, v1.4s, v4.s[3]\nldr         w23, [x2], #4\n\nfmla        v24.4s, v0.4s, v5.s[0]\nldr         w9, [x1], #4\nfmla        v25.4s, v1.4s, v5.s[0]\nldr         w24, [x2], #4\nfmla        v26.4s, v0.4s, v5.s[1]\nldr         w10, [x1], #4\nfmla        v27.4s, v1.4s, v5.s[1]\nldr         w25, [x2], #4\nfmla        v28.4s, v0.4s, v5.s[2]\nldr         w11, [x1], #4\nfmla        v29.4s, v1.4s, v5.s[2]\nldr         w26, [x2], #4\nfmla        v30.4s, v0.4s, v5.s[3]\nldr         w12, [x1], #4\nfmla        v31.4s, v1.4s, v5.s[3]\nldr         w27, [x2], #4\n\nprfm        pldl1keep, [x1, #256]\nprfm        pldl1keep, [x2, #256]\n\nins         v0.s[0], w5\nins         v4.s[0], w20\nins         v1.s[0], w9\nins         v5.s[0], w24\nins         v0.s[2], w7\nins         v4.s[2], w22\nins         v1.s[2], w11\nins         v5.s[2], w26\nins         v0.s[1], w6\nins         v4.s[1], w21\nins         v1.s[1], w10\nins         v5.s[1], w25\nins         v0.s[3], w8\nins         v4.s[3], w23\nins         v1.s[3], w12\nins         v5.s[3], w27\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_8x8/packed_packed_loop1/ldr_x_no_preload.tmpli",
    "content": "\nfmla        v16.4s, v0.4s, v4.s[0]\nldr         x5, [x1], #8\nfmla        v17.4s, v1.4s, v4.s[0]\nldr         x9, [x2], #8\nfmla        v18.4s, v0.4s, v4.s[1]\nldr         x6, [x1], #8\nfmla        v19.4s, v1.4s, v4.s[1]\nldr         x10, [x2], #8\nfmla        v20.4s, v0.4s, v4.s[2]\nldr         x7, [x1], #8\nfmla        v21.4s, v1.4s, v4.s[2]\nldr         x11, [x2], #8\nfmla        v22.4s, v0.4s, v4.s[3]\nldr         x8, [x1], #8\nfmla        v23.4s, v1.4s, v4.s[3]\nldr         x12, [x2], #8\n\nfmla        v24.4s, v0.4s, v5.s[0]\nfmla        v25.4s, v1.4s, v5.s[0]\nfmla        v26.4s, v0.4s, v5.s[1]\nfmla        v27.4s, v1.4s, v5.s[1]\nfmla        v28.4s, v0.4s, v5.s[2]\nfmla        v29.4s, v1.4s, v5.s[2]\nfmla        v30.4s, v0.4s, v5.s[3]\nfmla        v31.4s, v1.4s, v5.s[3]\n\nins         v2.d[0], x5\nins         v6.d[0], x9\nins         v3.d[0], x7\nins         v7.d[0], x11\nins         v2.d[1], x6\nins         v6.d[1], x10\nins         v3.d[1], x8\nins         v7.d[1], x12\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_8x8/packed_packed_loop1/ldr_x_preload.tmpli",
    "content": "\nfmla        v16.4s, v0.4s, v4.s[0]\nldr         x5, [x1], #8\nfmla        v17.4s, v1.4s, v4.s[0]\nldr         x9, [x2], #8\nfmla        v18.4s, v0.4s, v4.s[1]\nldr         x6, [x1], #8\nfmla        v19.4s, v1.4s, v4.s[1]\nldr         x10, [x2], #8\nfmla        v20.4s, v0.4s, v4.s[2]\nldr         x7, [x1], #8\nfmla        v21.4s, v1.4s, v4.s[2]\nldr         x11, [x2], #8\nfmla        v22.4s, v0.4s, v4.s[3]\nldr         x8, [x1], #8\nfmla        v23.4s, v1.4s, v4.s[3]\nldr         x12, [x2], #8\n\nfmla        v24.4s, v0.4s, v5.s[0]\nprfm        pldl1keep, [x1, #256]\nfmla        v25.4s, v1.4s, v5.s[0]\nprfm        pldl1keep, [x1, #320]\nfmla        v26.4s, v0.4s, v5.s[1]\nprfm        pldl1keep, [x1, #384]\nfmla        v27.4s, v1.4s, v5.s[1]\nprfm        pldl1keep, [x1, #448]\nfmla        v28.4s, v0.4s, v5.s[2]\nprfm        pldl1keep, [x2, #256]\nfmla        v29.4s, v1.4s, v5.s[2]\nprfm        pldl1keep, [x2, #320]\nfmla        v30.4s, v0.4s, v5.s[3]\nprfm        pldl1keep, [x2, #384]\nfmla        v31.4s, v1.4s, v5.s[3]\nprfm        pldl1keep, [x2, #448]\n\nins         v0.d[0], x5\nins         v4.d[0], x9\nins         v1.d[0], x7\nins         v5.d[0], x11\nins         v0.d[1], x6\nins         v4.d[1], x10\nins         v1.d[1], x8\nins         v5.d[1], x12\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_8x8/packed_packed_loop1/naive.tmpli",
    "content": "\nfmla        v16.4s, v0.4s, v4.s[0]\nfmla        v17.4s, v1.4s, v4.s[0]\nfmla        v18.4s, v0.4s, v4.s[1]\nfmla        v19.4s, v1.4s, v4.s[1]\nfmla        v20.4s, v0.4s, v4.s[2]\nfmla        v21.4s, v1.4s, v4.s[2]\nfmla        v22.4s, v0.4s, v4.s[3]\nfmla        v23.4s, v1.4s, v4.s[3]\n\nfmla        v24.4s, v0.4s, v5.s[0]\nfmla        v25.4s, v1.4s, v5.s[0]\nfmla        v26.4s, v0.4s, v5.s[1]\nfmla        v27.4s, v1.4s, v5.s[1]\nfmla        v28.4s, v0.4s, v5.s[2]\nfmla        v29.4s, v1.4s, v5.s[2]\nfmla        v30.4s, v0.4s, v5.s[3]\nfmla        v31.4s, v1.4s, v5.s[3]\n\nld1         {{ v0.4s, v1.4s }}, [x1], #32\nld1         {{ v4.4s, v5.4s }}, [x2], #32\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_8x8/packed_packed_loop2/broken_chains.tmpli",
    "content": "ld1         {{ v2.4s, v3.4s }}, [x1], #32\nld1         {{ v6.4s, v7.4s }}, [x2], #32\n\nfmla        v16.4s, v0.4s, v4.s[0]\nfmla        v17.4s, v1.4s, v4.s[0]\nfmla        v18.4s, v0.4s, v4.s[1]\nfmla        v19.4s, v1.4s, v4.s[1]\nfmla        v20.4s, v0.4s, v4.s[2]\nfmla        v21.4s, v1.4s, v4.s[2]\nfmla        v22.4s, v0.4s, v4.s[3]\nfmla        v23.4s, v1.4s, v4.s[3]\n\nfmla        v24.4s, v0.4s, v5.s[0]\nfmla        v25.4s, v1.4s, v5.s[0]\nfmla        v26.4s, v0.4s, v5.s[1]\nfmla        v27.4s, v1.4s, v5.s[1]\nfmla        v28.4s, v0.4s, v5.s[2]\nfmla        v29.4s, v1.4s, v5.s[2]\nfmla        v30.4s, v0.4s, v5.s[3]\nfmla        v31.4s, v1.4s, v5.s[3]\n\nld1         {{ v0.4s, v1.4s }}, [x1], #32\nld1         {{ v4.4s, v5.4s }}, [x2], #32\n\nfmla        v16.4s, v2.4s, v6.s[0]\nfmla        v17.4s, v3.4s, v6.s[0]\nfmla        v18.4s, v2.4s, v6.s[1]\nfmla        v19.4s, v3.4s, v6.s[1]\nfmla        v20.4s, v2.4s, v6.s[2]\nfmla        v21.4s, v3.4s, v6.s[2]\nfmla        v22.4s, v2.4s, v6.s[3]\nfmla        v23.4s, v3.4s, v6.s[3]\n\nfmla        v24.4s, v2.4s, v7.s[0]\nfmla        v25.4s, v3.4s, v7.s[0]\nfmla        v26.4s, v2.4s, v7.s[1]\nfmla        v27.4s, v3.4s, v7.s[1]\nfmla        v28.4s, v2.4s, v7.s[2]\nfmla        v29.4s, v3.4s, v7.s[2]\nfmla        v30.4s, v2.4s, v7.s[3]\nfmla        v31.4s, v3.4s, v7.s[3]\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_8x8/packed_packed_loop2/cortex_a55.tmpli",
    "content": "fmla        v16.4s, v0.4s, v4.s[0]\nldr         d2, [x1], #8\nfmla        v17.4s, v1.4s, v4.s[0]\nldr         d6, [x2], #8\nfmla        v18.4s, v0.4s, v4.s[1]\nldr         x5, [x1], #8\nfmla        v19.4s, v1.4s, v4.s[1]\nldr         x7, [x2], #8\nfmla        v20.4s, v0.4s, v4.s[2]\nldr         d3, [x1], #8\nfmla        v21.4s, v1.4s, v4.s[2]\nldr         d7, [x2], #8\nfmla        v22.4s, v0.4s, v4.s[3]\nldr         x6, [x1], #8\nfmla        v23.4s, v1.4s, v4.s[3]\nldr         x8, [x2], #8\n\nfmla        v24.4s, v0.4s, v5.s[0]\nfmla        v25.4s, v1.4s, v5.s[0]\nfmla        v26.4s, v0.4s, v5.s[1]\nfmla        v27.4s, v1.4s, v5.s[1]\nfmla        v28.4s, v0.4s, v5.s[2]\nins         v2.d[1], x5\nfmla        v29.4s, v1.4s, v5.s[2]\nins         v6.d[1], x7\nfmla        v30.4s, v0.4s, v5.s[3]\nins         v3.d[1], x6\nfmla        v31.4s, v1.4s, v5.s[3]\nins         v7.d[1], x8\n\nfmla        v16.4s, v2.4s, v6.s[0]\nldr         d0, [x1], #8\nfmla        v17.4s, v3.4s, v6.s[0]\nldr         d4, [x2], #8\nfmla        v18.4s, v2.4s, v6.s[1]\nldr         x5, [x1], #8\nfmla        v19.4s, v3.4s, v6.s[1]\nldr         x7, [x2], #8\nfmla        v20.4s, v2.4s, v6.s[2]\nldr         d1, [x1], #8\nfmla        v21.4s, v3.4s, v6.s[2]\nldr         d5, [x2], #8\nfmla        v22.4s, v2.4s, v6.s[3]\nldr         x6, [x1], #8\nfmla        v23.4s, v3.4s, v6.s[3]\nldr         x8, [x2], #8\n\nfmla        v24.4s, v2.4s, v7.s[0]\nfmla        v25.4s, v3.4s, v7.s[0]\nfmla        v26.4s, v2.4s, v7.s[1]\nfmla        v27.4s, v3.4s, v7.s[1]\nfmla        v28.4s, v2.4s, v7.s[2]\nins         v0.d[1], x5\nfmla        v29.4s, v3.4s, v7.s[2]\nins         v4.d[1], x7\nfmla        v30.4s, v2.4s, v7.s[3]\nins         v1.d[1], x6\nfmla        v31.4s, v3.4s, v7.s[3]\nins         v5.d[1], x8\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_8x8_core.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: v16 to v31, (scratch)\n// - x19-x29 to preserve (but x19, x28, x29 not used) \n// - d8..d15 to preserve\n// - v16 to v31, no need to preserve\n// \n//      v16[0] v18[0] v20[0] v22[0] v24[0] v26[0] v28[0] v30[0]\n//      v16[1] v18[1] \n//      v16[2] v18[2] \n//      v16[3] v18[3]\n//                     \n//      v17[0] v19[0] v21[0] v23[0] v25[0] v27[0] v29[0] v31[0]\n//      v17[1] v19[1] \n//      v17[2] v19[2] \n//      v17[3] v19[3] \n\n// v0-v7 (scratch registers)\n//  packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n//  packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_mmm_f32_8x8_{{core}}_{{suffix}}\n{{G}}arm64simd_mmm_f32_8x8_{{core}}_{{suffix}}:\n\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n    \n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldr         x2, [x0, #24]       // b\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n.packed_packed:\n    ld1         { v0.4s, v1.4s }, [ x1 ], #32\n    ld1         { v4.4s, v5.4s }, [ x2 ], #32\n\n{% capture packed_packed_loop1 %}\n    {% if core == \"a53\" %}\n        {% include \"arm64simd_mmm_f32_8x8/packed_packed_loop1/ldr_x_preload.tmpli\" %}\n    {% else %}\n        {% include \"arm64simd_mmm_f32_8x8/packed_packed_loop1/naive.tmpli\" %}\n    {% endif %}\n{% endcapture %}\n\n{% capture packed_packed_loop2 %}\n    {% if core == \"a55\" %}\n        {% include \"arm64simd_mmm_f32_8x8/packed_packed_loop2/cortex_a55.tmpli\" %}\n    {% else %}\n        {{ packed_packed_loop1 }}\n        {{ packed_packed_loop1 }}\n    {% endif %}\n{% endcapture %}\n\n    cmp         x3, #4\n    blt         .packed_packed_loop_1\n\n.p2align 4\n.packed_packed_loop_4:\n    {{ packed_packed_loop2 }}\n    {{ packed_packed_loop2 }}\n\n    sub x3, x3, #4\n    cmp x3, #4\n    bge .packed_packed_loop_4\n\n\n    cmp x3, #0\n    beq .non_linear_loop\n\n.p2align 4\n.packed_packed_loop_1:\n    {{ packed_packed_loop1 }}\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b .non_linear_loop\n\n{% include \"arm64simd_mmm_f32_scalars.tmpliq\" from:16, to:31%}\n{% include \"arm64simd_mmm_f32_per_rows.tmpliq\" mr:8, from:16, to:31 %}\n{% include \"arm64simd_mmm_f32_per_cols.tmpliq\" mr:8, from:16, to:31 %}\n{% include \"arm64simd_mmm_load_tile.tmpliq\" from:16, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    {% for col in (8..15) %}\n        mov x4, x5\n        {% for reg in (0..1) %}\n            {% for lane in (0..3) %}\n                ld1 {v0.s}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            fadd v{{col | times:2 | plus: reg}}.4s, v{{col | times:2 | plus: reg}}.4s, v0.4s\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x2, [x0, #8]\n    ldr     x3, [x0, #16]\n\n    ld1         { v0.4s, v1.4s }, [ x2 ], #32\n    ld1         { v4.4s, v5.4s }, [ x3 ], #32\n\n    fmla        v16.4s, v0.4s, v4.s[0]\n    fmla        v17.4s, v1.4s, v4.s[0]\n    fmla        v18.4s, v0.4s, v4.s[1]\n    fmla        v19.4s, v1.4s, v4.s[1]\n    fmla        v20.4s, v0.4s, v4.s[2]\n    fmla        v21.4s, v1.4s, v4.s[2]\n    fmla        v22.4s, v0.4s, v4.s[3]\n    fmla        v23.4s, v1.4s, v4.s[3]\n\n    fmla        v24.4s, v0.4s, v5.s[0]\n    fmla        v25.4s, v1.4s, v5.s[0]\n    fmla        v26.4s, v0.4s, v5.s[1]\n    fmla        v27.4s, v1.4s, v5.s[1]\n    fmla        v28.4s, v0.4s, v5.s[2]\n    fmla        v29.4s, v1.4s, v5.s[2]\n    fmla        v30.4s, v0.4s, v5.s[3]\n    fmla        v31.4s, v1.4s, v5.s[3]\n\n    b           .non_linear_loop\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x6, #4\n    bne         .store_strides_generic\n\n    {% for col in (8..15) %}\n        str q{{col | times:2 }}, [ x5 ]\n        str q{{col | times:2 | plus: 1}}, [ x5, #16 ]\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store_strides_generic:\n\n    {% for col in (8..15) %}\n        mov x4, x5\n        {% for reg in (0..1) %}\n            {% for lane in (0..3) %}\n                st1 { v{{col | times:2 | plus: reg}}.s }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_per_cols.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_min\", op:\"fmin\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_max\", op:\"fmax\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_mul\", op:\"fmul\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_add\", op:\"fadd\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_sub\", op:\"fsub\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_sub_flipped\", op:\"fsub\", mr:mr, from:from, to:to, flipped: true%}\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_per_rows.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_min\", op:\"fmin\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_max\", op:\"fmax\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_mul\", op:\"fmul\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_add\", op:\"fadd\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_sub\", op:\"fsub\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_sub_flipped\", op:\"fsub\", mr:mr, from:from, to:to, flipped: true%}\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_f32_scalars.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_min\", op:\"fmin\", from:from, to:to %}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_max\", op:\"fmax\", from:from, to:to %}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_mul\", op:\"fmul\", from:from, to:to %}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_add\", op:\"fadd\", from:from, to:to %}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_sub\", op:\"fsub\", from:from, to:to %}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_sub_flipped\", op:\"fsub\", from:from, to:to, flipped:true %}\n\n\n.clear:\n{% for r in (from..to) %}\n    eor         v{{r}}.8b, v{{r}}.8b, v{{r}}.8b\n{% endfor %}\n    b .non_linear_loop\n\n.leaky_relu:\n    add         x2, x0, #8\n    ld1         {v4.s}[0], [ x2 ]\n    dup         v4.4s, v4.s[0]\n\n    // bsl cond/dst, then, else\n    // fcmge dst, src, #0.0\n    {% for r in (from..to) %}\n        fmul  v0.4s, v{{r}}.4s, v4.4s\n        fcmge v1.4s, v{{r}}.4s, #0.0\n        bsl   v1.16b, v{{r}}.16b, v0.16b\n        and   v{{r}}.16b, v1.16b, v1.16b\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale:\n.q_shl:\n.q_shr:\n    b .unsupported\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_i32_64x1.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs: \n// - x19-x29 to preserve (but x19, x28, x29 not used) \n// - d8..d15 to preserve\n// - v16 to v31, no need to preserve\n\n// no preservation either for v0-v7...\n// packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_mmm_i32_64x1_{{suffix}}\n{{G}}arm64simd_mmm_i32_64x1_{{suffix}}:\n\n/*\n    prfm        pldl1keep, [x1]\n    prfm        pldl1keep, [x2]\n*/\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldp         x2, x4, [x0, #24]   // b, packing\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    cmp         x4, #1\n    beq         .packed_packed_loop_1_i8i8\n\n.packed_packed_loop_1:\n    ld1         {v9.s}[0], [ x2 ], 4\n\n    ld1\t        { v0.4s-v3.4s }, [ x1 ], #64\n    ld1\t        { v4.4s-v7.4s }, [ x1 ], #64\n    {% for reg in (0..3) %}\n        mla      v{{reg | times: 2 | plus: 16 }}.4s, v{{reg | times:2}}.4s, v9.s[0]\n        mla      v{{reg | times: 2 | plus: 17 }}.4s, v{{reg | times:2 | plus:1}}.4s, v9.s[0]\n    {% endfor %}\n\n    ld1\t        { v0.4s-v3.4s }, [ x1 ], #64\n    ld1\t        { v4.4s-v7.4s }, [ x1 ], #64\n    {% for reg in (0..3) %}\n        mla      v{{reg | times: 2 | plus: 24 }}.4s, v{{reg | times:2}}.4s, v9.s[0]\n        mla      v{{reg | times: 2 | plus: 25 }}.4s, v{{reg | times:2 | plus:1}}.4s, v9.s[0]\n    {% endfor %}\n\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1\n    \n    b .non_linear_loop\n\n.packed_packed_loop_1_i8i8:\n    ld1         {v9.b}[0], [ x2 ], 1\n    sshll       v9.8h, v9.8b, 0\n\n    ld1\t        { v0.8b-v3.8b }, [ x1 ], #32\n    ld1\t        { v4.8b-v7.8b }, [ x1 ], #32\n\n    {% for reg in (0..7) %}\n        sshll       v10.8h, v{{reg}}.8b, 0\n        smlal       v{{reg | times: 2 | plus: 16 }}.4s, v10.4h, v9.h[0]\n        smlal2      v{{reg | times: 2 | plus: 17 }}.4s, v10.8h, v9.h[0]\n    {% endfor %}\n\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1_i8i8\n\n    b .non_linear_loop\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    cmp         x8, #4\n    beq         non_linear_addc_i32\n\n    {% for reg in (16..31) %}\n        {% for lane in (0..3) %}\n            ld1 {v0.b}[{{lane}}], [ x5 ], x6\n        {% endfor %}\n        sshll v0.8h, v0.8b, 0\n        sshll v0.4s, v0.4h, 0\n        add v{{reg}}.4s, v{{reg}}.4s, v0.4s\n    {% endfor %}\n\n    b           .non_linear_loop\n\nnon_linear_addc_i32:\n    {% for reg in (16..31) %}\n        {% for lane in (0..3) %}\n            ld1 {v0.s}[{{lane}}], [ x5 ], x6\n        {% endfor %}\n        add v{{reg}}.4s, v{{reg}}.4s, v0.4s\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x2, [x0, #8]\n    ldr     x3, [x0, #16]\n\n    ld1         { v15.s }[0], [ x3 ]\n    xtn         v15.4h, v15.4s\n\n    ld1         { v0.4s-v3.4s }, [ x2 ], #64\n    ld1         { v4.4s-v7.4s }, [ x2 ], #64\n\n    {% for reg in (0..7) %}\n        xtn         v{{reg}}.4h, v{{reg}}.4s\n        smlal        v{{reg|plus: 16}}.4s, v{{reg}}.4h, v15.h[0]\n    {% endfor %}\n\n    ld1         { v0.4s-v3.4s }, [ x2 ], #64\n    ld1         { v4.4s-v7.4s }, [ x2 ], #64\n\n    {% for reg in (0..7) %}\n        xtn         v{{reg}}.4h, v{{reg}}.4s\n        smlal        v{{reg|plus: 24}}.4s, v{{reg}}.4h, v15.h[0]\n    {% endfor %}\n\n    b           .non_linear_loop\n\n{% include \"arm64simd_mmm_i32_scalars.tmpliq\" from:16, to:31 %}\n{% include \"arm64simd_mmm_i32_per_rows.tmpliq\" mr:64, from:16, to:31 %}\n{% include \"arm64simd_mmm_i32_per_cols.tmpliq\" mr:64, from:16, to:31 %}\n{% include \"arm64simd_mmm_i32_scale_q16_q31.tmpliq\" %}\n{% include \"arm64simd_mmm_load_tile.tmpliq\" from:16, to:31 %}\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x8, #4\n    beq         .store_strides_i32\n\n    {% for reg in (16..31) %}\n        {% for lane in (0..3) %}\n            st1 { v{{reg}}.b }[{{lane | times: 4}}], [ x5 ], x6\n        {% endfor %}\n    {% endfor %}\n\n    b   .non_linear_loop\n\n.store_strides_i32:\n    {% for reg in (16..31) %}\n        {% for lane in (0..3) %}\n            st1 { v{{reg}}.s }[{{lane}}], [ x5 ], x6\n        {% endfor %}\n    {% endfor %}\n\n    b   .non_linear_loop\n\n.return:\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n    \n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_i32_8x8.tmpl",
    "content": "// vim: ft=arm\n\n// C tile regs:\n// - x19-x29 to preserve (but x19, x28, x29 not used) \n// - d8..d15 to preserve\n// - v16 to v31, no need to preserve\n// \n//      v16[0] v18[0] v20[0] v22[0] v24[0] v26[0] v28[0] v30[0]\n//      v16[1] v18[1] \n//      v16[2] v18[2] \n//      v16[3] v18[3]\n//                     \n//      v17[0] v19[0] v21[0] v23[0] v25[0] v27[0] v29[0] v31[0]\n//      v17[1] v19[1] \n//      v17[2] v19[2] \n//      v17[3] v19[3] \n\n// no preservation either for v0-v7...\n// packed A buffering (2x8 values): alternating v0, v1 with v2, v3\n// packed B buffering (2x8 values): alternating v4, v5 with v6, v7\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_mmm_i32_8x8_{{suffix}}\n{{G}}arm64simd_mmm_i32_8x8_{{suffix}}:\n\n/*\n    prfm        pldl1keep, [x1]\n    prfm        pldl1keep, [x2]\n*/\n    stp         x20, x21, [sp, #-16]!\n    stp         x22, x23, [sp, #-16]!\n    stp         x24, x25, [sp, #-16]!\n    stp         x26, x27, [sp, #-16]!\n\n    stp         d8, d9, [sp, #-16]!\n    stp         d10, d11, [sp, #-16]!\n    stp         d12, d13, [sp, #-16]!\n    stp         d14, d15, [sp, #-16]!\n\n{% include \"dispatcher.tmpliq\" %}\n\n.add_mat_mul:\n    ldp         x2, x4, [x0, #24]   // b, packing\n    ldp         x3, x1, [x0, #8]    // k, a\n\n    cmp         x3, #0\n    beq         .non_linear_loop\n\n    cmp         x4, #1\n    beq         .packed_packed_loop_1_i8i8\n\n.packed_packed_loop_1:\n\n    ld1\t        { v0.4s, v1.4s }, [ x1 ], #32\n    ld1\t        { v4.4s, v5.4s }, [ x2 ], #32\n\n    mla         v16.4s, v0.4s, v4.s[0]\n    mla         v17.4s, v1.4s, v4.s[0]\n    mla         v18.4s, v0.4s, v4.s[1]\n    mla         v19.4s, v1.4s, v4.s[1]\n\n    mla         v20.4s, v0.4s, v4.s[2]\n    mla         v21.4s, v1.4s, v4.s[2]\n    mla         v22.4s, v0.4s, v4.s[3]\n    mla         v23.4s, v1.4s, v4.s[3]\n\n    mla         v24.4s, v0.4s, v5.s[0]\n    mla         v25.4s, v1.4s, v5.s[0]\n    mla         v26.4s, v0.4s, v5.s[1]\n    mla         v27.4s, v1.4s, v5.s[1]\n\n    mla         v28.4s, v0.4s, v5.s[2]\n    mla         v29.4s, v1.4s, v5.s[2]\n    mla         v30.4s, v0.4s, v5.s[3]\n    mla         v31.4s, v1.4s, v5.s[3]\n\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1\n\n    b .non_linear_loop\n\n.packed_packed_loop_1_i8i8:\n\n    ld1\t        { v0.8b }, [ x1 ], #8\n    sshll       v0.8h, v0.8b, 0\n    ld1         { v4.8b }, [ x2 ], #8\n    sshll        v4.8h, v4.8b, 0\n\n    smlal        v16.4s, v0.4h, v4.h[0]\n    smlal2       v17.4s, v0.8h, v4.h[0]\n    smlal        v18.4s, v0.4h, v4.h[1]\n    smlal2       v19.4s, v0.8h, v4.h[1]\n    smlal        v20.4s, v0.4h, v4.h[2]\n    smlal2       v21.4s, v0.8h, v4.h[2]\n    smlal        v22.4s, v0.4h, v4.h[3]\n    smlal2       v23.4s, v0.8h, v4.h[3]\n\n    smlal        v24.4s, v0.4h, v4.h[4]\n    smlal2       v25.4s, v0.8h, v4.h[4]\n    smlal        v26.4s, v0.4h, v4.h[5]\n    smlal2       v27.4s, v0.8h, v4.h[5]\n    smlal        v28.4s, v0.4h, v4.h[6]\n    smlal2       v29.4s, v0.8h, v4.h[6]\n    smlal        v30.4s, v0.4h, v4.h[7]\n    smlal2       v31.4s, v0.8h, v4.h[7]\n\n    subs        x3, x3, #1\n    bne .packed_packed_loop_1_i8i8\n\n    b .non_linear_loop\n\n{% include \"arm64simd_mmm_i32_scalars.tmpliq\" from:16, to:31%}\n{% include \"arm64simd_mmm_i32_per_rows.tmpliq\" mr:8, from:16, to:31%}\n{% include \"arm64simd_mmm_i32_per_cols.tmpliq\" mr:8, from:16, to:31%}\n{% include \"arm64simd_mmm_load_tile.tmpliq\" from:16, to:31 %}\n\n.add_unicast:\n    ldp         x5, x6, [x0, #8]\n    ldp         x7, x8, [x0, #24]\n\n    cmp         x8, #4\n    beq         non_linear_addc_i32\n\n    {% for col in (8..15) %}\n        mov x4, x5\n        {% for reg in (0..1) %}\n            {% for lane in (0..3) %}\n                ld1 {v0.b}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            sshll v0.8h, v0.8b, 0\n            sshll v0.4s, v0.4h, 0\n            add v{{col | times:2 | plus: reg}}.4s, v{{col | times:2 | plus: reg}}.4s, v0.4s\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\nnon_linear_addc_i32:\n    {% for col in (8..15) %}\n        mov x4, x5\n        {% for reg in (0..1) %}\n            {% for lane in (0..3) %}\n                ld1 {v0.s}[{{lane}}], [ x4 ], x6\n            {% endfor %}\n            add v{{col | times:2 | plus: reg}}.4s, v{{col | times:2 | plus: reg}}.4s, v0.4s\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.add_row_col_products:\n    ldr     x2, [x0, #8]\n    ldr     x3, [x0, #16]\n\n    ld1         { v0.4s, v1.4s }, [ x2 ]\n    ld1         { v4.4s, v5.4s }, [ x3 ]\n\n    xtn         v0.4h, v0.4s\n    xtn         v1.4h, v1.4s\n    xtn         v4.4h, v4.4s\n    xtn         v5.4h, v5.4s\n\n    smlal        v16.4s, v0.4h, v4.h[0]\n    smlal        v17.4s, v1.4h, v4.h[0]\n    smlal        v18.4s, v0.4h, v4.h[1]\n    smlal        v19.4s, v1.4h, v4.h[1]\n    smlal        v20.4s, v0.4h, v4.h[2]\n    smlal        v21.4s, v1.4h, v4.h[2]\n    smlal        v22.4s, v0.4h, v4.h[3]\n    smlal        v23.4s, v1.4h, v4.h[3]\n\n    smlal        v24.4s, v0.4h, v5.h[0]\n    smlal        v25.4s, v1.4h, v5.h[0]\n    smlal        v26.4s, v0.4h, v5.h[1]\n    smlal        v27.4s, v1.4h, v5.h[1]\n    smlal        v28.4s, v0.4h, v5.h[2]\n    smlal        v29.4s, v1.4h, v5.h[2]\n    smlal        v30.4s, v0.4h, v5.h[3]\n    smlal        v31.4s, v1.4h, v5.h[3]\n\n    b           .non_linear_loop\n\n    {% include \"arm64simd_mmm_i32_scale_q16_q31.tmpliq\" %}\n\n.store:\n    ldp         x5, x6, [x0, #8]            // c base ptr, rsc\n    ldp         x7, x8, [x0, #24]           // csc, item_size\n\n    cmp         x8, #4\n    beq         .store_strides_i32\n\n    {% for col in (8..15) %}\n        mov x4, x5\n        {% for reg in (0..1) %}\n            {% for lane in (0..3) %}\n                st1 { v{{col | times:2 | plus: reg}}.b }[{{lane|times:4}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.store_strides_i32:\n    {% for col in (8..15) %}\n        mov x4, x5\n        {% for reg in (0..1) %}\n            {% for lane in (0..3) %}\n                st1 { v{{col | times:2 | plus: reg}}.s }[{{lane}}], [ x4 ], x6\n            {% endfor %}\n        {% endfor %}\n        add x5, x5, x7\n    {% endfor %}\n\n    b           .non_linear_loop\n\n.return:\n    ldp         d14, d15, [sp], #16\n    ldp         d12, d13, [sp], #16\n    ldp         d10, d11, [sp], #16\n    ldp         d8, d9, [sp], #16\n\n    ldp         x26, x27, [sp], #16\n    ldp         x24, x25, [sp], #16\n    ldp         x22, x23, [sp], #16\n    ldp         x20, x21, [sp], #16\n\n    ret\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_i32_per_cols.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_min\", op:\"smin\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_max\", op:\"smax\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_mul\", op:\"mul\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_add\", op:\"add\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_sub\", op:\"sub\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_col.tmpliq\" label:\"per_col_sub_flipped\", op:\"sub\", mr:mr, from:from, to:to, flipped: true %}\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_i32_per_rows.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_min\", op:\"smin\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_max\", op:\"smax\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_mul\", op:\"mul\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_add\", op:\"add\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_sub\", op:\"sub\", mr:mr, from:from, to:to %}\n{% include \"arm64simd_mmm_4s_per_row.tmpliq\" label:\"per_row_sub_flipped\", op:\"sub\", mr:mr, from:from, to:to, flipped: true %}\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_i32_scalars.tmpliq",
    "content": "// vim: ft=arm\n\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_min\", op:\"smin\", from:from, to:to%}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_max\", op:\"smax\", from:from, to:to%}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_mul\", op:\"mul\", from:from, to:to%}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_add\", op:\"add\", from:from, to:to%}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_sub\", op:\"sub\", from:from, to:to%}\n{% include \"arm64simd_mmm_4s_scalar.tmpliq\" label:\"scalar_sub_flipped\", op:\"sub\", from:from, to:to, flipped:true%}\n\n.clear:\n{% for r in (from..to) %}\n    eor         v{{r}}.8b, v{{r}}.8b, v{{r}}.8b\n{% endfor %}\n    b .non_linear_loop\n\n.leaky_relu:\n    add         x2, x0, #8\n    ld1         {v4.s}[0], [ x2 ]\n    dup         v4.4s, v4.s[0]\n\n    // bsl cond/dst, then, else\n    // fcmge dst, src, #0.0\n    {% for r in (from..to) %}\n        mul   v0.4s, v{{r}}.4s, v4.4s\n        cmge  v1.4s, v{{r}}.4s, #0\n        bsl   v1.16b, v{{r}}.16b, v0.16b\n        and   v{{r}}.16b, v1.16b, v1.16b\n    {% endfor %}\n\n    b .non_linear_loop\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_i32_scale_q16_q31.tmpliq",
    "content": "\n// vim: ft=arm\n\n.q_scale:\n    ldp     x5, x6, [x0, #8]            // x5: shift, x6: policy\n    add     x2, x0, #24\n    ld1r    { v2.4s }, [x2]             // v2.4s <- multiplier\n\n    mov     w3, #1\n    ins     v4.d[0], x3\n    dup     v4.2d, v4.d[0]              // v4.2d <- 1\n\n    add     x5, x5, #32                 // add 32 to shift\n    neg     x5, x5                      // broadcast shift\n    ins     v1.d[0], x5\n    dup     v1.2d, v1.d[0]              // v1.2s <- -(shift + 32)\n\n    cmp     x6, 1\n    beq     .q_scale_rounding_zero\n    cmp     x6, 2\n    beq     .q_scale_rounding_away\n    cmp     x6, 3\n    beq     .q_scale_rounding_minus_inf\n    cmp     x6, 4\n    beq     .q_scale_rounding_plus_inf\n    cmp     x6, 5\n    beq     .q_scale_rounding_even\n    cmp     x6, 6\n    beq     .q_scale_rounding_odd\n\n    b .unsupported\n\n.q_scale_rounding_zero:\n        // rust: signum * ((abs + nudge2) >> shift\n        // asm: signum * (2*abs - 1) >>r (shift + 1)\n\n    {% for q in (16..31) %}\n        cmlt        v0.4s, v{{q}}.4s, #0\n        abs         v{{q}}.4s, v{{q}}.4s\n        sqdmull     v8.2d, v{{q}}.2s, v2.2s\n        sqdmull2    v9.2d, v{{q}}.4s, v2.4s     //mul without shift and store results in v8 and v9\n\n        sub         v8.2d, v8.2d, v4.2d\n        sqrshl      v8.2d, v8.2d, v1.2d\n\n        sub         v9.2d, v9.2d, v4.2d\n        sqrshl      v9.2d, v9.2d, v1.2d\n\n        uzp1         v{{q}}.4s, v8.4s, v9.4s    //combine back\n\n        neg         v3.4s, v{{q}}.4s\n        bit         v{{q}}.16b, v3.16b, v0.16b\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_away: // signum * (abs >> (shift-1) + 1 >> 1)\n\n    {% for q in (16..31) %}\n        cmlt        v0.4s, v{{q}}.4s, #0\n        abs         v{{q}}.4s, v{{q}}.4s\n        sqdmull     v8.2d, v{{q}}.2s, v2.2s\n        sqdmull2    v9.2d, v{{q}}.4s, v2.4s     //mul without shift and store results in v8 and v9\n\n        sqrshl      v8.2d, v8.2d, v1.2d\n        sqrshl      v9.2d, v9.2d, v1.2d\n\n        uzp1         v{{q}}.4s, v8.4s, v9.4s    //combine back\n\n        neg         v3.4s, v{{q}}.4s\n        bit         v{{q}}.16b, v3.16b, v0.16b\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_minus_inf: // val >> shift\n\n    {% for q in (16..31) %}\n        sqdmull     v8.2d, v{{q}}.2s, v2.2s\n        sqdmull2    v9.2d, v{{q}}.4s, v2.4s     //mul without shift and store results in v8 and v9\n\n        sub         v8.2d, v8.2d, v4.2d\n        sqrshl      v8.2d, v8.2d, v1.2d\n\n        sub         v9.2d, v9.2d, v4.2d\n        sqrshl      v9.2d, v9.2d, v1.2d\n\n        uzp1         v{{q}}.4s, v8.4s, v9.4s    //combine back\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_plus_inf: // (val >> shift-1)+1 >>1\n\n    {% for q in (16..31) %}\n        sqdmull     v8.2d, v{{q}}.2s, v2.2s\n        sqdmull2    v9.2d, v{{q}}.4s, v2.4s     //mul without shift and store results in v8 and v9\n\n        sqrshl      v8.2d, v8.2d, v1.2d\n        sqrshl      v9.2d, v9.2d, v1.2d\n\n        uzp1         v{{q}}.4s, v8.4s, v9.4s    //combine back\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_even: // signum * ((abs >> shift-1) + (abs & 0x1) - 1 >> 1)\n\n    {% for q in (16..31) %}\n        cmlt        v0.4s, v{{q}}.4s, #0\n        abs         v{{q}}.4s, v{{q}}.4s\n        sqdmull     v8.2d, v{{q}}.2s, v2.2s\n        sqdmull2    v9.2d, v{{q}}.4s, v2.4s     //mul without shift and store results in v8 and v9\n\n        sqshl       v3.2d, v8.2d, v1.2d         // abs >> shift - 1\n        and         v3.16b, v3.16b, v4.16b      // abs & 0x1\n        sub         v3.2d, v3.2d, v4.2d         //nudge : -1 if we want to round down, 0 if up\n\n        add         v8.2d, v8.2d, v3.2d\n        sqrshl      v8.2d, v8.2d, v1.2d\n\n        sqshl       v3.2d, v9.2d, v1.2d\n        and         v3.16b, v3.16b, v4.16b\n        sub         v3.2d, v3.2d, v4.2d         //nudge : -1 if we want to round down, 0 if up\n\n        add         v9.2d, v9.2d, v3.2d\n        sqrshl      v9.2d, v9.2d, v1.2d\n\n        uzp1         v{{q}}.4s, v8.4s, v9.4s    //combine back\n\n        neg         v3.4s, v{{q}}.4s\n        bit         v{{q}}.16b, v3.16b, v0.16b\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_scale_rounding_odd: // signum * ((abs >> shift-1) - (abs & 0x1) >> 1)\n\n    {% for q in (16..31) %}\n        cmlt        v0.4s, v{{q}}.4s, #0\n        abs         v{{q}}.4s, v{{q}}.4s\n        sqdmull     v8.2d, v{{q}}.2s, v2.2s\n        sqdmull2    v9.2d, v{{q}}.4s, v2.4s     //mul without shift and store results in v8 and v9\n\n        sqshl       v3.2d, v8.2d, v1.2d\n        and         v3.16b, v3.16b, v4.16b      //nudge : -1 if we want to round down, 0 if up\n\n        sub         v8.2d, v8.2d, v3.2d\n        sqrshl      v8.2d, v8.2d, v1.2d\n\n        sqshl       v3.2d, v9.2d, v1.2d\n        and         v3.16b, v3.16b, v4.16b      //nudge : -1 if we want to round down, 0 if up\n\n        sub         v9.2d, v9.2d, v3.2d\n        sqrshl      v9.2d, v9.2d, v1.2d\n\n        uzp1        v{{q}}.4s, v8.4s, v9.4s    //combine back\n\n        neg         v3.4s, v{{q}}.4s\n        bit         v{{q}}.16b, v3.16b, v0.16b\n    {% endfor %}\n\n    b .non_linear_loop\n\n.q_shl:\n    ldr     x5, [x0, #8]                // x5: shift\n    ins     v1.s[0], w5\n    dup     v1.4s, v1.s[0]              // v1.4s <- shift\n\n    {% for q in (16..31) %}\n        sqrshl      v{{q}}.4s, v{{q}}.4s, v1.4s\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr:\n    ldp     x5, x6, [x0, #8]            // x5: shift, x6: policy\n\n    mov     w3, #1\n    ins     v4.s[0], w3\n    dup     v4.4s, v4.s[0]              // v4.4d <- 1\n\n    neg     w5, w5                      // broadcast shift\n    ins     v1.s[0], w5\n    dup     v1.4s, v1.s[0]              // v1.4s <- -shift\n\n    cmp     x6, 1\n    beq     .q_shr_rounding_zero\n    cmp     x6, 2\n    beq     .q_shr_rounding_away\n    cmp     x6, 3\n    beq     .q_shr_rounding_minus_inf\n    cmp     x6, 4\n    beq     .q_shr_rounding_plus_inf\n    cmp     x6, 5\n    beq     .q_shr_rounding_even\n    cmp     x6, 6\n    beq     .q_shr_rounding_odd\n\n    b .unsupported\n\n.q_shr_rounding_zero:\n    // asm: signum * (abs >>r shift)\n    {% for q in (16..31) %}\n        cmlt        v0.4s, v{{q}}.4s, #0\n        abs         v{{q}}.4s, v{{q}}.4s\n\n        sub         v{{q}}.4s, v{{q}}.4s, v4.4s\n        sqrshl      v{{q}}.4s, v{{q}}.4s, v1.4s\n\n        neg         v3.4s, v{{q}}.4s\n        bit         v{{q}}.16b, v3.16b, v0.16b\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_away:\n    {% for q in (16..31) %}\n        cmlt        v0.4s, v{{q}}.4s, #0\n        abs         v{{q}}.4s, v{{q}}.4s\n\n        sqrshl      v{{q}}.4s, v{{q}}.4s, v1.4s\n\n        neg         v3.4s, v{{q}}.4s\n        bit         v{{q}}.16b, v3.16b, v0.16b\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_minus_inf:\n    {% for q in (16..31) %}\n        sqneg       v{{q}}.4s, v{{q}}.4s\n        sqrshl      v{{q}}.4s, v{{q}}.4s, v1.4s\n        sqneg       v{{q}}.4s, v{{q}}.4s\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_plus_inf:\n    {% for q in (16..31) %}\n        sqrshl      v{{q}}.4s, v{{q}}.4s, v1.4s\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_even:\n    // sqrshl is round(+inf), sqshl trauncates\n    // we look at parity of result by truncation: if it's odd, we have nothing more to do, we go towards +inf\n    // if it's even, we need to nudge towards 0 by adding -1\n    // => nudge = (x >>l shift) & 0x1 - 1 (>>l is sqshl)\n    // => result is (x + nudge) >>r shift (with sqrshl)\n    {% for q in (16..31) %}\n        sqshl       v3.4s, v{{q}}.4s, v1.4s // trunc\n        and         v3.16b, v3.16b, v4.16b\n        sub         v3.4s, v3.4s, v4.4s\n        add         v{{q}}.4s, v{{q}}.4s, v3.4s\n\n        sqrshl      v{{q}}.4s, v{{q}}.4s, v1.4s\n    {% endfor %}\n    b .non_linear_loop\n\n.q_shr_rounding_odd:\n    // here: nudge is -((x >>l shift) & 0x1)\n    {% for q in (16..31) %}\n        sqshl       v3.4s, v{{q}}.4s, v1.4s // trunc\n        and         v3.16b, v3.16b, v4.16b\n        neg         v3.4s, v3.4s\n        add         v{{q}}.4s, v{{q}}.4s, v3.4s\n\n        sqrshl      v{{q}}.4s, v{{q}}.4s, v1.4s\n    {% endfor %}\n    b .non_linear_loop\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_mmm_load_tile.tmpliq",
    "content": "// vim: ft=arm\n\n.load_tile:\n    ldr         x2, [ x0, #8 ]\n    {% for reg in (from..to) %}\n        ld1         { v{{reg}}.4s }, [ x2 ], #16\n    {% endfor %}\n\n    b           .non_linear_loop\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_sigmoid_f32_4n.tmpl",
    "content": "// vim: ft=arm\n\n// no preservation either for v0-v7 and v16-v31\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_sigmoid_f32_4n_{{suffix}}\n{{G}}arm64simd_sigmoid_f32_4n_{{suffix}}:\n\n    cmp         x1, #0\n    beq         .return\n\n    adr         x2, .coeffs_num\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    dup         v5.4s, v0.s[0]              // v5 <- low, broadcasted\n    dup         v6.4s, v0.s[1]              // v6 <- high, broadcasted\n    dup         v7.4s, v3.s[1]              // v7 <- 0.5, broadcasted\n\n    cmp         x1, #16\n    blt         .loop\n\n.loop4:\n    ld1         { v16.4s, v17.4s, v18.4s, v19.4s }, [x0]\n\n    fmax        v16.4s, v16.4s, v5.4s\n    fmax        v17.4s, v17.4s, v5.4s\n    fmax        v18.4s, v18.4s, v5.4s\n    fmax        v19.4s, v19.4s, v5.4s\n\n    fmin        v16.4s, v16.4s, v6.4s\n    fmin        v17.4s, v17.4s, v6.4s\n    fmin        v18.4s, v18.4s, v6.4s\n    fmin        v19.4s, v19.4s, v6.4s       // v16 <- x\n\n    fmul        v20.4s, v16.4s, v16.4s\n    fmul        v21.4s, v17.4s, v17.4s\n    fmul        v22.4s, v18.4s, v18.4s\n    fmul        v23.4s, v19.4s, v19.4s      // v20 <- x2\n\n    dup         v24.4s, v0.s[3]\n    fmla        v24.4s, v20.4s, v0.s[2]\n    dup         v25.4s, v0.s[3]\n    fmla        v25.4s, v21.4s, v0.s[2]\n    dup         v26.4s, v0.s[3]\n    fmla        v26.4s, v22.4s, v0.s[2]\n    dup         v27.4s, v0.s[3]\n    fmla        v27.4s, v23.4s, v0.s[2]\n\n    dup         v28.4s, v1.s[0]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v29.4s, v1.s[0]\n    fmla        v29.4s, v21.4s, v25.4s\n    dup         v30.4s, v1.s[0]\n    fmla        v30.4s, v22.4s, v26.4s\n    dup         v31.4s, v1.s[0]\n    fmla        v31.4s, v23.4s, v27.4s\n\n    dup         v24.4s, v1.s[1]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v25.4s, v1.s[1]\n    fmla        v25.4s, v21.4s, v29.4s\n    dup         v26.4s, v1.s[1]\n    fmla        v26.4s, v22.4s, v30.4s\n    dup         v27.4s, v1.s[1]\n    fmla        v27.4s, v23.4s, v31.4s\n\n    dup         v28.4s, v1.s[2]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v29.4s, v1.s[2]\n    fmla        v29.4s, v21.4s, v25.4s\n    dup         v30.4s, v1.s[2]\n    fmla        v30.4s, v22.4s, v26.4s\n    dup         v31.4s, v1.s[2]\n    fmla        v31.4s, v23.4s, v27.4s\n\n    dup         v24.4s, v1.s[3]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v25.4s, v1.s[3]\n    fmla        v25.4s, v21.4s, v29.4s\n    dup         v26.4s, v1.s[3]\n    fmla        v26.4s, v22.4s, v30.4s\n    dup         v27.4s, v1.s[3]\n    fmla        v27.4s, v23.4s, v31.4s\n\n    dup         v28.4s, v2.s[0]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v29.4s, v2.s[0]\n    fmla        v29.4s, v21.4s, v25.4s\n    dup         v30.4s, v2.s[0]\n    fmla        v30.4s, v22.4s, v26.4s\n    dup         v31.4s, v2.s[0]\n    fmla        v31.4s, v23.4s, v27.4s\n\n    fmul        v16.4s, v16.4s, v28.4s\n    fmul        v17.4s, v17.4s, v29.4s\n    fmul        v18.4s, v18.4s, v30.4s\n    fmul        v19.4s, v19.4s, v31.4s      // v16 <- numerator\n\n    dup         v24.4s, v2.s[2]\n    fmla        v24.4s, v20.4s, v2.s[1]\n    dup         v25.4s, v2.s[2]\n    fmla        v25.4s, v21.4s, v2.s[1]\n    dup         v26.4s, v2.s[2]\n    fmla        v26.4s, v22.4s, v2.s[1]\n    dup         v27.4s, v2.s[2]\n    fmla        v27.4s, v23.4s, v2.s[1]\n\n    dup         v28.4s, v2.s[3]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v29.4s, v2.s[3]\n    fmla        v29.4s, v21.4s, v25.4s\n    dup         v30.4s, v2.s[3]\n    fmla        v30.4s, v22.4s, v26.4s\n    dup         v31.4s, v2.s[3]\n    fmla        v31.4s, v23.4s, v27.4s\n\n    dup         v24.4s, v3.s[0]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v25.4s, v3.s[0]\n    fmla        v25.4s, v21.4s, v29.4s\n    dup         v26.4s, v3.s[0]\n    fmla        v26.4s, v22.4s, v30.4s\n    dup         v27.4s, v3.s[0]\n    fmla        v27.4s, v23.4s, v31.4s  // v24 denum\n\n    fdiv        v16.4s, v16.4s, v24.4s\n    fdiv        v17.4s, v17.4s, v25.4s\n    fdiv        v18.4s, v18.4s, v26.4s\n    fdiv        v19.4s, v19.4s, v27.4s\n\n    fadd        v16.4s, v16.4s, v7.4s\n    fadd        v17.4s, v17.4s, v7.4s\n    fadd        v18.4s, v18.4s, v7.4s\n    fadd        v19.4s, v19.4s, v7.4s\n\n    st1         { v16.4s, v17.4s, v18.4s, v19.4s }, [x0], #64\n\n    subs        x1, x1, #16\n    cmp         x1, #16\n    bge         .loop4\n\n    cmp         x1, #0\n    beq         .return\n\n.loop:\n    ld1         { v16.4s }, [x0]\n\n    fmax        v16.4s, v16.4s, v5.4s\n    fmin        v16.4s, v16.4s, v6.4s       // v16 <- x\n    fmul        v20.4s, v16.4s, v16.4s      // v20 <- x2\n\n    dup         v24.4s, v0.s[3]\n    fmla        v24.4s, v20.4s, v0.s[2]\n    dup         v28.4s, v1.s[0]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v24.4s, v1.s[1]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v28.4s, v1.s[2]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v24.4s, v1.s[3]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v28.4s, v2.s[0]\n    fmla        v28.4s, v20.4s, v24.4s\n    fmul        v16.4s, v16.4s, v28.4s      // v16 <- numerator\n\n    dup         v24.4s, v2.s[2]\n    fmla        v24.4s, v20.4s, v2.s[1]\n    dup         v28.4s, v2.s[3]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v24.4s, v3.s[0]\n    fmla        v24.4s, v20.4s, v28.4s      // v24 <- denum\n\n    fdiv        v16.4s, v16.4s, v24.4s\n    fadd        v16.4s, v16.4s, v7.4s\n\n    st1         { v16.4s }, [x0], #16\n\n    subs        x1, x1, #4\n    bne         .loop\n\n.return:\n    ret\n\n.coeffs_num:\n    .float -18.6                    // low\n    .float 18.6                     // high\n    .float -4.433153405e-18         // alpha_13\n    .float 1.169974371e-14\n\n    .float -1.875289645e-11\n    .float 4.257889523e-8\n    .float 0.00004811817576\n    .float 0.008163842030\n\n    .float 0.2499999971\n    .float 3.922935744e-6           // beta_6\n    .float 0.001524872358\n    .float 0.1159886749\n\n    .float 1.0\n    .float 0.5                      //              \n    .float 0.0                      // padding\n    .float 0.0\n\n"
  },
  {
    "path": "linalg/arm64/arm64simd/arm64simd_tanh_f32_4n.tmpl",
    "content": "// vim: ft=arm\n\n// no preservation either for v0-v7 and v16-v31\n\n.text\n.align 4\n\n.cpu generic+fp+simd\n.global {{G}}arm64simd_tanh_f32_4n_{{suffix}}\n{{G}}arm64simd_tanh_f32_4n_{{suffix}}:\n\n    cmp         x1, #0\n    beq         .return\n\n    adr         x2, .coeffs_num\n    ld1         { v0.4s, v1.4s, v2.4s, v3.4s }, [x2]\n    dup         v5.4s, v0.s[0]              // v5 <- low, broadcasted\n    dup         v6.4s, v0.s[1]              // v6 <- high, broadcasted\n\n    cmp         x1, #16\n    blt         .loop\n\n.loop4:\n    ld1         { v16.4s, v17.4s, v18.4s, v19.4s }, [x0]\n\n    fmax        v16.4s, v16.4s, v5.4s\n    fmax        v17.4s, v17.4s, v5.4s\n    fmax        v18.4s, v18.4s, v5.4s\n    fmax        v19.4s, v19.4s, v5.4s\n\n    fmin        v16.4s, v16.4s, v6.4s\n    fmin        v17.4s, v17.4s, v6.4s\n    fmin        v18.4s, v18.4s, v6.4s\n    fmin        v19.4s, v19.4s, v6.4s       // v16 <- x\n\n    fmul        v20.4s, v16.4s, v16.4s\n    fmul        v21.4s, v17.4s, v17.4s\n    fmul        v22.4s, v18.4s, v18.4s\n    fmul        v23.4s, v19.4s, v19.4s      // v20 <- x2\n\n    dup         v24.4s, v0.s[3]\n    fmla        v24.4s, v20.4s, v0.s[2]\n    dup         v25.4s, v0.s[3]\n    fmla        v25.4s, v21.4s, v0.s[2]\n    dup         v26.4s, v0.s[3]\n    fmla        v26.4s, v22.4s, v0.s[2]\n    dup         v27.4s, v0.s[3]\n    fmla        v27.4s, v23.4s, v0.s[2]\n\n    dup         v28.4s, v1.s[0]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v29.4s, v1.s[0]\n    fmla        v29.4s, v21.4s, v25.4s\n    dup         v30.4s, v1.s[0]\n    fmla        v30.4s, v22.4s, v26.4s\n    dup         v31.4s, v1.s[0]\n    fmla        v31.4s, v23.4s, v27.4s\n\n    dup         v24.4s, v1.s[1]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v25.4s, v1.s[1]\n    fmla        v25.4s, v21.4s, v29.4s\n    dup         v26.4s, v1.s[1]\n    fmla        v26.4s, v22.4s, v30.4s\n    dup         v27.4s, v1.s[1]\n    fmla        v27.4s, v23.4s, v31.4s\n\n    dup         v28.4s, v1.s[2]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v29.4s, v1.s[2]\n    fmla        v29.4s, v21.4s, v25.4s\n    dup         v30.4s, v1.s[2]\n    fmla        v30.4s, v22.4s, v26.4s\n    dup         v31.4s, v1.s[2]\n    fmla        v31.4s, v23.4s, v27.4s\n\n    dup         v24.4s, v1.s[3]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v25.4s, v1.s[3]\n    fmla        v25.4s, v21.4s, v29.4s\n    dup         v26.4s, v1.s[3]\n    fmla        v26.4s, v22.4s, v30.4s\n    dup         v27.4s, v1.s[3]\n    fmla        v27.4s, v23.4s, v31.4s\n\n    dup         v28.4s, v2.s[0]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v29.4s, v2.s[0]\n    fmla        v29.4s, v21.4s, v25.4s\n    dup         v30.4s, v2.s[0]\n    fmla        v30.4s, v22.4s, v26.4s\n    dup         v31.4s, v2.s[0]\n    fmla        v31.4s, v23.4s, v27.4s\n\n    fmul        v16.4s, v16.4s, v28.4s\n    fmul        v17.4s, v17.4s, v29.4s\n    fmul        v18.4s, v18.4s, v30.4s\n    fmul        v19.4s, v19.4s, v31.4s      // v16 <- numerator\n\n    dup         v24.4s, v2.s[2]\n    fmla        v24.4s, v20.4s, v2.s[1]\n    dup         v25.4s, v2.s[2]\n    fmla        v25.4s, v21.4s, v2.s[1]\n    dup         v26.4s, v2.s[2]\n    fmla        v26.4s, v22.4s, v2.s[1]\n    dup         v27.4s, v2.s[2]\n    fmla        v27.4s, v23.4s, v2.s[1]\n\n    dup         v28.4s, v2.s[3]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v29.4s, v2.s[3]\n    fmla        v29.4s, v21.4s, v25.4s\n    dup         v30.4s, v2.s[3]\n    fmla        v30.4s, v22.4s, v26.4s\n    dup         v31.4s, v2.s[3]\n    fmla        v31.4s, v23.4s, v27.4s\n\n    dup         v24.4s, v3.s[0]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v25.4s, v3.s[0]\n    fmla        v25.4s, v21.4s, v29.4s\n    dup         v26.4s, v3.s[0]\n    fmla        v26.4s, v22.4s, v30.4s\n    dup         v27.4s, v3.s[0]\n    fmla        v27.4s, v23.4s, v31.4s  // v24 denum\n\n    fdiv        v16.4s, v16.4s, v24.4s\n    fdiv        v17.4s, v17.4s, v25.4s\n    fdiv        v18.4s, v18.4s, v26.4s\n    fdiv        v19.4s, v19.4s, v27.4s\n\n    st1         { v16.4s, v17.4s, v18.4s, v19.4s }, [x0], #64\n\n    subs        x1, x1, #16\n    cmp         x1, #16\n    bge         .loop4\n\n    cmp         x1, #0\n    beq         .return\n\n.loop:\n    ld1         { v16.4s }, [x0]\n\n    fmax        v16.4s, v16.4s, v5.4s\n    fmin        v16.4s, v16.4s, v6.4s       // v16 <- x\n    fmul        v20.4s, v16.4s, v16.4s      // v20 <- x2\n\n    dup         v24.4s, v0.s[3]\n    fmla        v24.4s, v20.4s, v0.s[2]\n    dup         v28.4s, v1.s[0]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v24.4s, v1.s[1]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v28.4s, v1.s[2]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v24.4s, v1.s[3]\n    fmla        v24.4s, v20.4s, v28.4s\n    dup         v28.4s, v2.s[0]\n    fmla        v28.4s, v20.4s, v24.4s\n    fmul        v16.4s, v16.4s, v28.4s      // v16 <- numerator\n\n    dup         v24.4s, v2.s[2]\n    fmla        v24.4s, v20.4s, v2.s[1]\n    dup         v28.4s, v2.s[3]\n    fmla        v28.4s, v20.4s, v24.4s\n    dup         v24.4s, v3.s[0]\n    fmla        v24.4s, v20.4s, v28.4s      // v24 <- denum\n\n    fdiv        v16.4s, v16.4s, v24.4s\n\n    st1         { v16.4s }, [x0], #16\n\n    subs        x1, x1, #4\n    bne         .loop\n\n.return:\n    ret\n\n.coeffs_num:\n    .float -8.9                     // low\n    .float 8.9                      // high\n    .float -8.488492677e-14         // alpha_13\n    .float 5.277853000e-11\n\n    .float -2.022500419e-8\n    .float 0.00001115424833\n    .float 0.003103950131\n    .float 0.1308400453\n\n    .float 0.9999999934\n    .float 0.0002546136580          // beta_6\n    .float 0.02449515379\n    .float 0.4641733162\n\n    .float 1.0\n    .float 0                        // padding\n    .float 0                        // padding\n    .float 0                        // padding\n"
  },
  {
    "path": "linalg/arm64/arm64simd/dispatcher.tmpliq",
    "content": "// vim: ft=arm\n\n.non_linear:\n    sub         x0, x0, 40\n\n.non_linear_loop:\n    add         x0, x0, 40\n    ldr         x2, [x0]\n\n    mov         x4, #{{ jump_table | size }}\n\n    cmp         x2, #{{ jump_table | size }}\n    csel        x2, x2, x4, lt\n    cmp         x2, #0\n    csel        x2, x4, x2, lt\n\n    adr         x3, .jmp_table\n    add         x3, x3, x2, LSL#2\n    br          x3\n\n.jmp_table:\n{% for j in jump_table %}\n    b   .{{j}}\n{% endfor %}\n    b   .unsupported\n\n    add x0, x2, #4000\n    b .return\n\n.unsupported:\n    mov         x0, #1\n    b           .return\n\n.done:\n    mov         x0, 0\n    b           .return\n\n"
  },
  {
    "path": "linalg/benches/arm32neon.rs",
    "content": "#![feature(asm)]\n#![allow(dead_code, non_upper_case_globals, unused_macros, non_snake_case, unused_assignments)]\n\nuse std::time::Instant;\n\nmacro_rules! r2 { ($($stat:stmt)*) => { $( $stat )* $( $stat )* } }\nmacro_rules! r4 { ($($stat:stmt)*) => { r2!(r2!($($stat)*)) }}\nmacro_rules! r8 { ($($stat:stmt)*) => { r4!(r2!($($stat)*)) }}\nmacro_rules! r16 { ($($stat:stmt)*) => { r4!(r4!($($stat)*)) }}\nmacro_rules! r32 { ($($stat:stmt)*) => { r8!(r4!($($stat)*)) }}\nmacro_rules! r64 { ($($stat:stmt)*) => { r8!(r8!($($stat)*)) }}\nmacro_rules! r128 { ($($stat:stmt)*) => { r8!(r16!($($stat)*)) }}\nmacro_rules! r1024 { ($($stat:stmt)*) => { r8!(r128!($($stat)*)) }}\nmacro_rules! r4096 { ($($stat:stmt)*) => { r4!(r1024!($($stat)*)) }}\n\nconst _F32: [f32; 1024] = [12.; 1024];\nconst F32: *const f32 = _F32.as_ptr();\n\n/*\nfn ruin_cache() {\nlet _a = (0..1000000).collect::<Vec<i32>>();\n}\n*/\n\nmacro_rules! b {\n    ($f: block, $inner_loop: expr, $measures: expr) => {{\n        let mut values = Vec::with_capacity($measures);\n        for _ in 0..$measures {\n            //       ruin_cache();\n            let start = Instant::now();\n            for _ in 0..$inner_loop {\n                unsafe { $f };\n            }\n            values.push(start.elapsed());\n        }\n        values.sort();\n        values[$measures / 2].as_nanos() as f64 / 1e9 / $inner_loop as f64\n    }};\n}\n\nfn main() {\n    let cycle = b!(\n        {\n            r1024!(asm!(\"orr r0, r0, r0\", out(\"r0\") _));\n        },\n        1000,\n        1000\n    ) / 1024.;\n    let indep_fmla = b!(\n        {\n            r8!(asm!(\"\n                vmla.f32 q0, q0, q0\n                vmla.f32 q1, q1, q1\n                vmla.f32 q2, q2, q2\n                vmla.f32 q3, q3, q3\n                vmla.f32 q4, q4, q4\n                vmla.f32 q5, q5, q5\n                vmla.f32 q6, q6, q6\n                vmla.f32 q7, q7, q7\n                 \", out(\"q0\") _, out(\"q1\") _, out(\"q2\") _, out(\"q3\") _, out(\"q4\") _, out(\"q5\") _, out(\"q6\") _, out(\"q7\") _));\n        },\n        1000,\n        1000\n    ) / 64.;\n    eprintln!(\"rcp tp: indep fmla: {}\", indep_fmla / cycle);\n    let dep_accu_fmla = b!(\n        {\n            r16!(asm!(\"\n                vmla.f32 q15, q0, q0\n                vmla.f32 q15, q1, q1\n                vmla.f32 q15, q2, q2\n                vmla.f32 q15, q3, q3\n                vmla.f32 q15, q4, q4\n                vmla.f32 q15, q5, q5\n                vmla.f32 q15, q6, q6\n                vmla.f32 q15, q7, q7\n                vmla.f32 q15, q8, q8\n                vmla.f32 q15, q9, q9\n                vmla.f32 q15, q10, q10\n                vmla.f32 q15, q11, q11\n                vmla.f32 q15, q12, q12\n                vmla.f32 q15, q13, q13\n                vmla.f32 q15, q14, q14\n                 \", out(\"q0\") _, out(\"q1\") _, out(\"q2\") _, out(\"q3\") _, out(\"q4\") _, out(\"q5\") _, out(\"q6\") _, out(\"q7\") _,\n                 out(\"q8\") _, out(\"q9\") _, out(\"q10\") _, out(\"q11\") _, out(\"q12\") _, out(\"q13\") _, out(\"q14\") _, out(\"q15\") _));\n        },\n        1000,\n        1000\n    ) / 16.\n        / 15.;\n    eprintln!(\"rcp tp: accu-dep fmla: {}\", dep_accu_fmla / cycle);\n    let load_s_using_vld1_64 = b!(\n        {\n            let mut p = F32;\n            r16!(asm!(\"\n                vld1.64         {{d0-d3}}, [{0}]!\n                vld1.64         {{d4-d7}}, [{0}]!\n                vld1.64         {{d8-d11}}, [{0}]!\n                vld1.64         {{d12-d15}}, [{0}]!\n                vld1.64         {{d16-d19}}, [{0}]!\n                vld1.64         {{d20-d23}}, [{0}]!\n                vld1.64         {{d24-d27}}, [{0}]!\n                vld1.64         {{d28-d31}}, [{0}]!\n                 \", \n                 inout(reg) p,\n                 out(\"q0\") _, out(\"q1\") _, out(\"q2\") _, out(\"q3\") _, out(\"q4\") _, out(\"q5\") _, out(\"q6\") _, out(\"q7\") _,\n                 out(\"q8\") _, out(\"q9\") _, out(\"q10\") _, out(\"q11\") _, out(\"q12\") _, out(\"q13\") _, out(\"q14\") _, out(\"q15\") _));\n        },\n        1000,\n        1000\n    ) / 16.\n        / 64.; // each line load 8 s\n    eprintln!(\"rcp tp: load s using vld1_64 ia {}\", load_s_using_vld1_64 / cycle);\n    let load_s_using_vldm_q = b!(\n        {\n            let mut p = F32;\n            r16!(asm!(\"\n                vldm            {0}!, {{q0-q3}}\n                vldm            {0}!, {{q4-q7}}\n                vldm            {0}!, {{q8-q11}}\n                vldm            {0}!, {{q12-q15}}\n                 \", \n                 inout(reg) p,\n                 out(\"q0\") _, out(\"q1\") _, out(\"q2\") _, out(\"q3\") _, out(\"q4\") _, out(\"q5\") _, out(\"q6\") _, out(\"q7\") _,\n                 out(\"q8\") _, out(\"q9\") _, out(\"q10\") _, out(\"q11\") _, out(\"q12\") _, out(\"q13\") _, out(\"q14\") _, out(\"q15\") _));\n        },\n        1000,\n        1000\n    ) / 16.\n        / 64.;\n    eprintln!(\"rcp tp: load s using vldmia q: {}\", load_s_using_vldm_q / cycle);\n    let load = b!(\n        {\n            let mut p = F32;\n            r16!(asm!(\"\n                vldr.64  d0, [{0}]\n                vldr.64  d1, [{0}, #8]\n                vldr.64  d2, [{0}, #16]\n                vldr.64  d3, [{0}, #24]\n                vldr.64  d4, [{0}, #32]\n                vldr.64  d5, [{0}, #40]\n                vldr.64  d6, [{0}, #48]\n                vldr.64  d7, [{0}, #56]\n                vldr.64  d8, [{0}, #64]\n                vldr.64  d9, [{0}, #72]\n                vldr.64  d10, [{0}, #80]\n                vldr.64  d11, [{0}, #88]\n                vldr.64  d12, [{0}, #96]\n                vldr.64  d13, [{0}, #104]\n                vldr.64  d14, [{0}, #112]\n                vldr.64  d15, [{0}, #120]\n                vldr.64  d16, [{0}, #128]\n                vldr.64  d17, [{0}, #136]\n                vldr.64  d18, [{0}, #144]\n                vldr.64  d19, [{0}, #152]\n                vldr.64  d20, [{0}, #160]\n                vldr.64  d21, [{0}, #168]\n                vldr.64  d22, [{0}, #176]\n                vldr.64  d23, [{0}, #184]\n                vldr.64  d24, [{0}, #192]\n                vldr.64  d25, [{0}, #200]\n                vldr.64  d26, [{0}, #208]\n                vldr.64  d27, [{0}, #216]\n                vldr.64  d28, [{0}, #224]\n                vldr.64  d29, [{0}, #232]\n                vldr.64  d30, [{0}, #240]\n                vldr.64  d31, [{0}, #248]\n                add {0}, #256\n                 \", \n                 inout(reg) p,\n                 out(\"q0\") _, out(\"q1\") _, out(\"q2\") _, out(\"q3\") _, out(\"q4\") _, out(\"q5\") _, out(\"q6\") _, out(\"q7\") _,\n                 out(\"q8\") _, out(\"q9\") _, out(\"q10\") _, out(\"q11\") _, out(\"q12\") _, out(\"q13\") _, out(\"q14\") _, out(\"q15\") _));\n        },\n        1000,\n        1000\n    ) / 16.\n        / 64.;\n    eprintln!(\"rcp tp: load s using vldr d + imm: {}\", load / cycle);\n}\n"
  },
  {
    "path": "linalg/benches/arm64.rs",
    "content": "use std::time::Instant;\n\nuse tract_data::prelude::*;\nuse tract_linalg::LADatum;\nuse tract_linalg::frame::mmm::FusedSpec;\nuse tract_linalg::frame::mmm::MatMatMulKer;\n\nfn ruin_cache() {\n    let _a = (0..1000000).collect::<Vec<i32>>();\n}\n\nfn bench_to_nanos<T: LADatum + Copy + num_traits::Zero, K: MatMatMulKer<T>>(\n    k: usize,\n    loops: usize,\n) -> f64 {\n    let item_size = T::datum_type().size_of();\n    let a = Tensor::zero_aligned::<T>(\n        &[(k + K::end_padding_packed_a()) * K::mr()],\n        K::alignment_bytes_packed_a(),\n    )\n    .unwrap();\n    let b = Tensor::zero_aligned::<T>(\n        &[(k + K::end_padding_packed_b()) * K::nr()],\n        K::alignment_bytes_packed_b(),\n    )\n    .unwrap();\n    let mut c = Tensor::zero::<T>(&[K::mr() * K::nr()]).unwrap();\n    let ref a = InputStoreKer::Packed { ptr: unsafe { a.as_ptr_unchecked::<u8>() as _ } };\n    let ref b = InputStoreKer::Packed { ptr: unsafe { b.as_ptr_unchecked::<u8>() as _ } };\n    let ref c = OutputStoreKer {\n        ptr: unsafe { c.as_ptr_mut_unchecked::<u8>() as _ },\n        item_size,\n        col_byte_stride: (item_size * K::mr()) as isize,\n        row_byte_stride: item_size as isize,\n    };\n    let ref linear = LinearSpec::Mul { k };\n    let op = MatMatMulKerSpec { a, b, c, linear, non_linear: std::ptr::null() };\n    let mut values = Vec::with_capacity(loops);\n    for _ in 0..loops {\n        ruin_cache();\n        let start = Instant::now();\n        K::kernel(&op);\n        values.push(start.elapsed());\n    }\n    values.sort();\n    values[loops / 2].as_nanos() as f64\n}\n\nfn model<T: Datum + Copy + num_traits::Zero, K: MatMatMulKer<T>>() -> (f64, f64) {\n    let x = 1000;\n    let zp = bench_to_nanos::<T, K>(0, 10000);\n    let y = bench_to_nanos::<T, K>(x, 1000);\n    let slope = (y - zp) / x as f64;\n    (slope, zp)\n}\n\nfn as_match_line<T: Datum + Copy + num_traits::Zero, K: MatMatMulKer<T>>() {\n    let coeffs = model::<T, K>();\n    println!(\n        \"({:?}, {}, {}) => {} * k + {},\",\n        K::name(),\n        K::mr(),\n        K::nr(),\n        (coeffs.0 * 1000.).round(),\n        (coeffs.1 * 1000.).round()\n    );\n}\n\nfn main() {\n    use tract_linalg::arm64::*;\n    as_match_line::<f32, MatMatMulF32x16x4>();\n    as_match_line::<f32, MatMatMulF32x12x8>();\n    as_match_line::<f32, MatMatMulF32x8x8>();\n    as_match_line::<f32, MatMatMulF32x16x4A53>();\n    as_match_line::<f32, MatMatMulF32x12x8A53>();\n    as_match_line::<f32, MatMatMulF32x8x8A53>();\n}\n"
  },
  {
    "path": "linalg/benches/arm64simd.rs",
    "content": "#![allow(dead_code, non_upper_case_globals, unused_macros, non_snake_case, unused_assignments)]\n\nuse std::arch::asm;\n\n// mod nano;\n\n#[repr(C, align(8))]\nstruct Floats([f32; 4096]);\nconst _F32: Floats = Floats([12.; 4096]);\nconst F32: *const f32 = (&_F32) as *const Floats as *const f32;\n\nlazy_static::lazy_static! {\n    static ref TICK: f64 = unsafe { b8192!(asm!(\"orr x20, x20, x20\", out(\"x20\") _)) };\n}\n\npub unsafe fn armv8(filter: Option<&str>) {\n    macro_rules! s32 {\n        ($label: literal, $n: expr, $stmt:block) => {\n            if $label.contains(filter.unwrap_or(\"\")) {\n                println!(\"{:40} {:.2}\", $label, b32!($stmt) / $n as f64 / *TICK);\n            }\n        };\n    }\n\n    macro_rules! s128 {\n        ($label: literal, $n: expr, $stmt:block) => {\n            if $label.contains(filter.unwrap_or(\"\")) {\n                println!(\"{:40} {:.2}\", $label, b128!($stmt) / $n as f64 / *TICK);\n            }\n        };\n    }\n\n    macro_rules! s1024 {\n        ($label: literal, $n: expr, $stmt:block) => {\n            if $label.contains(filter.unwrap_or(\"\")) {\n                println!(\"{:40} {:.2}\", $label, b1024!($stmt) / $n as f64 / *TICK);\n            }\n        };\n    }\n\n    macro_rules! s8192 {\n        ($label: literal, $n: expr, $stmt:block) => {\n            if $label.contains(filter.unwrap_or(\"\")) {\n                println!(\"{:40} {:.2}\", $label, b8192!($stmt) / $n as f64 / *TICK);\n            }\n        };\n    }\n\n    s128!(\"nop\", 1, { asm!(\"nop\") });\n    s128!(\"vands\", 4, {\n        asm!(\"  and v0.16b, v1.16b, v1.16b\n                and v2.16b, v3.16b, v3.16b\n                and v4.16b, v5.16b, v5.16b\n                and v6.16b, v7.16b, v7.16b \",\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        )\n    });\n    s128!(\"fmax\", 4, {\n        asm!(\"  fmax v0.4s, v1.4s, v1.4s\n                fmax v2.4s, v3.4s, v3.4s\n                fmax v4.4s, v5.4s, v5.4s\n                fmax v6.4s, v7.4s, v7.4s \",\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        )\n    });\n    s128!(\"fmax_with_dep\", 1, { asm!(\"fmax v0.4s, v0.4s, v0.4s\", out(\"v0\") _) });\n    s128!(\"fmla\", 16, {\n        asm!(\" fmla v0.4s, v0.4s, v0.4s\n               fmla v1.4s, v1.4s, v1.4s\n               fmla v2.4s, v2.4s, v2.4s\n               fmla v3.4s, v3.4s, v3.4s\n               fmla v4.4s, v4.4s, v4.4s\n               fmla v5.4s, v5.4s, v5.4s\n               fmla v6.4s, v6.4s, v6.4s\n               fmla v7.4s, v7.4s, v7.4s\n               fmla v8.4s, v8.4s, v8.4s\n               fmla v9.4s, v9.4s, v9.4s\n               fmla v10.4s,v10.4s,v10.4s\n               fmla v11.4s,v11.4s,v11.4s\n               fmla v12.4s,v12.4s,v12.4s\n               fmla v13.4s,v13.4s,v13.4s\n               fmla v14.4s,v14.4s,v14.4s\n               fmla v15.4s,v15.4s,v15.4s \",\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        )\n    });\n\n    s128!(\"fmla_with_dep\", 1, { asm!(\"fmla v0.4s, v0.4s, v0.4s\", out(\"v0\") _) });\n    s32!(\"w_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"ldr w20, [{0}]\n                   ldr w21, [{0}]\n                   ldr w22, [{0}]\n                   ldr w23, [{0}]\n                   ldr w24, [{0}]\n                   ldr w25, [{0}]\n                   ldr w26, [{0}]\n                   ldr w27, [{0}]\",\n        inout(reg) p,\n        out(\"x20\") _, out(\"x21\") _, out(\"x22\") _, out(\"x23\") _,\n        out(\"x24\") _, out(\"x25\") _, out(\"x26\") _, out(\"x27\") _,\n        ));\n    });\n    s32!(\"x_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n           ldr x20, [{0}]\n           ldr x21, [{0}]\n           ldr x22, [{0}]\n           ldr x23, [{0}]\n           ldr x24, [{0}]\n           ldr x25, [{0}]\n           ldr x26, [{0}]\n           ldr x27, [{0}]\n           \",\n        inout(reg) p,\n        out(\"x20\") _, out(\"x21\") _, out(\"x22\") _, out(\"x23\") _,\n        out(\"x24\") _, out(\"x25\") _, out(\"x26\") _, out(\"x27\") _,\n        ));\n    });\n    s32!(\"d_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n       ldr d20, [{0}]\n       ldr d21, [{0}]\n       ldr d22, [{0}]\n       ldr d23, [{0}]\n       ldr d24, [{0}]\n       ldr d25, [{0}]\n       ldr d26, [{0}]\n       ldr d27, [{0}]\n       \",\n        inout(reg) p,\n        out(\"v20\") _, out(\"v21\") _, out(\"v22\") _, out(\"v23\") _,\n        out(\"v24\") _, out(\"v25\") _, out(\"v26\") _, out(\"v27\") _,\n        ));\n    });\n    s32!(\"s_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n       ld1 {{v20.s}}[0], [{0}]\n       ld1 {{v21.s}}[0], [{0}]\n       ld1 {{v22.s}}[0], [{0}]\n       ld1 {{v23.s}}[0], [{0}]\n       ld1 {{v24.s}}[0], [{0}]\n       ld1 {{v25.s}}[0], [{0}]\n       ld1 {{v26.s}}[0], [{0}]\n       ld1 {{v27.s}}[0], [{0}]\n       \",\n        inout(reg) p,\n        out(\"v20\") _, out(\"v21\") _, out(\"v22\") _, out(\"v23\") _,\n        out(\"v24\") _, out(\"v25\") _, out(\"v26\") _, out(\"v27\") _,\n        ));\n    });\n    s32!(\"d_load_as_v\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n       ld1 {{v20.d}}[0], [{0}]\n       ld1 {{v21.d}}[0], [{0}]\n       ld1 {{v22.d}}[0], [{0}]\n       ld1 {{v23.d}}[0], [{0}]\n       ld1 {{v24.d}}[0], [{0}]\n       ld1 {{v25.d}}[0], [{0}]\n       ld1 {{v26.d}}[0], [{0}]\n       ld1 {{v27.d}}[0], [{0}]\n       \",\n        inout(reg) p,\n        out(\"v20\") _, out(\"v21\") _, out(\"v22\") _, out(\"v23\") _,\n        out(\"v24\") _, out(\"v25\") _, out(\"v26\") _, out(\"v27\") _,\n        ));\n    });\n    s32!(\"v_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n       ld1 {{v20.4s}}, [{0}]\n       ld1 {{v21.4s}}, [{0}]\n       ld1 {{v22.4s}}, [{0}]\n       ld1 {{v23.4s}}, [{0}]\n       ld1 {{v24.4s}}, [{0}]\n       ld1 {{v25.4s}}, [{0}]\n       ld1 {{v26.4s}}, [{0}]\n       ld1 {{v27.4s}}, [{0}]\n       \",\n        inout(reg) p,\n        out(\"v20\") _, out(\"v21\") _, out(\"v22\") _, out(\"v23\") _,\n        out(\"v24\") _, out(\"v25\") _, out(\"v26\") _, out(\"v27\") _,\n        ));\n    });\n    s32!(\"v2_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     ld1 {{v0.4s, v1.4s}}, [{0}]\n                     ld1 {{v2.4s, v3.4s}}, [{0}]\n                     ld1 {{v4.4s, v5.4s}}, [{0}]\n                     ld1 {{v6.4s, v7.4s}}, [{0}]\n                     ld1 {{v8.4s, v9.4s}}, [{0}]\n                     ld1 {{v10.4s, v11.4s}}, [{0}]\n                     ld1 {{v12.4s, v13.4s}}, [{0}]\n                     ld1 {{v14.4s, v15.4s}}, [{0}]\n       \",\n        inout(reg) p,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"v3_load\", 32, {\n        let mut p = F32;\n        r8!(asm!(\"\n           ld1 {{v0.4s, v1.4s, v2.4s}}, [{0}]\n           ld1 {{v3.4s, v4.4s, v5.4s}}, [{0}]\n           ld1 {{v6.4s, v7.4s, v8.4s}}, [{0}]\n           ld1 {{v9.4s, v10.4s, v11.4s}}, [{0}]\n       \",\n        inout(reg) p,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        ));\n    });\n    s32!(\"v4_load\", 32, {\n        let mut p = F32;\n        r8!(asm!(\"\n           ld1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{0}]\n           ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{0}]\n           ld1 {{v8.4s, v9.4s, v10.4s, v11.4s}}, [{0}]\n           ld1 {{v12.4s, v13.4s, v14.4s, v15.4s}}, [{0}]\n       \",\n        inout(reg) p,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"ins_32b\", 64, {\n        r8!(asm!(\"\n           ins v8.s[0], w20\n           ins v9.s[0], w20\n           ins v10.s[0], w20\n           ins v11.s[0], w20\n           ins v12.s[0], w20\n           ins v13.s[0], w20\n           ins v14.s[0], w20\n           ins v15.s[0], w20\n       \",\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"ins_32b_same_lane\", 128, {\n        r8!(asm!(\"\n           ins         v0.s[0], w20\n           ins         v1.s[0], w20\n           ins         v4.s[0], w20\n           ins         v5.s[0], w20\n           ins         v0.s[1], w20\n           ins         v1.s[1], w20\n           ins         v4.s[1], w20\n           ins         v5.s[1], w20\n           ins         v0.s[2], w20\n           ins         v1.s[2], w20\n           ins         v4.s[2], w20\n           ins         v5.s[2], w20\n           ins         v0.s[3], w20\n           ins         v1.s[3], w20\n           ins         v4.s[3], w20\n           ins         v5.s[3], w20\n       \",\n        out(\"v0\") _, out(\"v1\") _, out(\"v4\") _, out(\"v5\") _,\n        ));\n    });\n    s32!(\"ins_64b\", 64, {\n        r8!(asm!(\"\n           ins v8.d[0], x20\n           ins v9.d[0], x20\n           ins v10.d[0], x20\n           ins v11.d[0], x20\n           ins v12.d[0], x20\n           ins v13.d[0], x20\n           ins v14.d[0], x20\n           ins v15.d[0], x20\n       \",\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"ins_64b_same_v\", 64, {\n        r8!(asm!(\"\n                     ins v8.d[0], x20\n                     ins v8.d[1], x20\n                     ins v8.d[0], x20\n                     ins v8.d[1], x20\n                     ins v8.d[0], x20\n                     ins v8.d[1], x20\n                     ins v8.d[0], x20\n                     ins v8.d[1], x20\n                     \",\n        out(\"v8\") _,\n        ));\n    });\n    s32!(\"ins_64b_from_v\", 64, {\n        r8!(asm!(\"\n                     ins v8.d[0], v9.d[0]\n                     ins v8.d[1], v9.d[0]\n                     ins v8.d[0], v9.d[1]\n                     ins v8.d[1], v9.d[1]\n                     ins v8.d[0], v9.d[0]\n                     ins v8.d[1], v9.d[0]\n                     ins v8.d[0], v9.d[1]\n                     ins v8.d[1], v9.d[1]\n                     \",\n        out(\"v8\") _,\n        ));\n    });\n    s32!(\"fmla_with_prfm\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n           prfm pldl1keep, [{0}, #256]\n           fmla v0.4s, v0.4s, v0.4s\n           prfm pldl1keep, [{0}, #320]\n           fmla v1.4s, v1.4s, v1.4s\n           prfm pldl1keep, [{0}, #384]\n           fmla v2.4s, v2.4s, v2.4s\n           prfm pldl1keep, [{0}, #448]\n           fmla v3.4s, v3.4s, v3.4s\n           prfm pldl1keep, [{0}, #512]\n           fmla v4.4s, v4.4s, v4.4s\n           prfm pldl1keep, [{0}, #576]\n           fmla v5.4s, v5.4s, v5.4s\n           prfm pldl1keep, [{0}, #640]\n           fmla v6.4s, v6.4s, v6.4s\n           prfm pldl1keep, [{0}, #704]\n           fmla v7.4s, v7.4s, v7.4s\n           prfm pldl1keep, [{0}, #768]\n           \",\n        inout(reg) p,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        ));\n    });\n    s32!(\"fmla_with_w_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n           ldr w20, [{0}]\n           fmla v0.4s, v0.4s, v0.4s\n           ldr w21, [{0}]\n           fmla v1.4s, v1.4s, v1.4s\n           ldr w22, [{0}]\n           fmla v2.4s, v2.4s, v2.4s\n           ldr w23, [{0}]\n           fmla v3.4s, v3.4s, v3.4s\n           ldr w24, [{0}]\n           fmla v4.4s, v4.4s, v4.4s\n           ldr w25, [{0}]\n           fmla v5.4s, v5.4s, v5.4s\n           ldr w26, [{0}]\n           fmla v6.4s, v6.4s, v6.4s\n           ldr w27, [{0}]\n           fmla v7.4s, v7.4s, v7.4s\n           \",\n        inout(reg) p,\n        out(\"x20\") _, out(\"x21\") _, out(\"x22\") _, out(\"x23\") _,\n        out(\"x24\") _, out(\"x25\") _, out(\"x26\") _, out(\"x27\") _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        ));\n    });\n    s32!(\"fmla_with_w_load_inc\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     ldr w20, [{0}], #4\n                     fmla v0.4s, v0.4s, v0.4s\n                     ldr w21, [{0}], #4\n                     fmla v1.4s, v1.4s, v1.4s\n                     ldr w22, [{0}], #4\n                     fmla v2.4s, v2.4s, v2.4s\n                     ldr w23, [{0}], #4\n                     fmla v3.4s, v3.4s, v3.4s\n                     ldr w24, [{0}], #4\n                     fmla v4.4s, v4.4s, v4.4s\n                     ldr w25, [{0}], #4\n                     fmla v5.4s, v5.4s, v5.4s\n                     ldr w26, [{0}], #4\n                     fmla v6.4s, v6.4s, v6.4s\n                     ldr w27, [{0}], #4\n                     fmla v7.4s, v7.4s, v7.4s\n                     \",\n        inout(reg) p,\n        out(\"x20\") _, out(\"x21\") _, out(\"x22\") _, out(\"x23\") _,\n        out(\"x24\") _, out(\"x25\") _, out(\"x26\") _, out(\"x27\") _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        ));\n    });\n    s32!(\"fmla_with_w_load_inc_alt\", 64, {\n        let mut p = F32;\n        let mut q = F32;\n        r8!(asm!(\"\n                     ldr w20, [{0}], #4\n                     fmla v0.4s, v0.4s, v0.4s\n                     ldr w21, [{1}], #4\n                     fmla v1.4s, v1.4s, v1.4s\n                     ldr w22, [{0}], #4\n                     fmla v2.4s, v2.4s, v2.4s\n                     ldr w23, [{1}], #4\n                     fmla v3.4s, v3.4s, v3.4s\n                     ldr w24, [{0}], #4\n                     fmla v4.4s, v4.4s, v4.4s\n                     ldr w25, [{1}], #4\n                     fmla v5.4s, v5.4s, v5.4s\n                     ldr w26, [{0}], #4\n                     fmla v6.4s, v6.4s, v6.4s\n                     ldr w27, [{1}], #4\n                     fmla v7.4s, v7.4s, v7.4s\n                     \",\n        inout(reg) p, inout(reg) q,\n        out(\"x20\") _, out(\"x21\") _, out(\"x22\") _, out(\"x23\") _,\n        out(\"x24\") _, out(\"x25\") _, out(\"x26\") _, out(\"x27\") _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        ));\n    });\n    s32!(\"fmla_with_w_load_offset\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     ldr w20, [{0}]\n                     fmla v0.4s, v0.4s, v0.4s\n                     ldr w21, [{0}, #4]\n                     fmla v1.4s, v1.4s, v1.4s\n                     ldr w22, [{0}, #8]\n                     fmla v2.4s, v2.4s, v2.4s\n                     ldr w23, [{0}, #12]\n                     fmla v3.4s, v3.4s, v3.4s\n                     ldr w24, [{0}, #16]\n                     fmla v4.4s, v4.4s, v4.4s\n                     ldr w25, [{0}, #20]\n                     fmla v5.4s, v5.4s, v5.4s\n                     ldr w26, [{0}, #24]\n                     fmla v6.4s, v6.4s, v6.4s\n                     ldr w27, [{0}, #28]\n                     fmla v7.4s, v7.4s, v7.4s\n                     \",\n        inout(reg) p,\n        out(\"x20\") _, out(\"x21\") _, out(\"x22\") _, out(\"x23\") _,\n        out(\"x24\") _, out(\"x25\") _, out(\"x26\") _, out(\"x27\") _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        ));\n    });\n    s32!(\"fmla_with_x_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     fmla v0.4s, v0.4s, v0.4s\n                     ldr x20, [{0}]\n                     fmla v1.4s, v1.4s, v1.4s\n                     ldr x21, [{0}]\n                     fmla v2.4s, v2.4s, v2.4s\n                     ldr x22, [{0}]\n                     fmla v3.4s, v3.4s, v3.4s\n                     ldr x23, [{0}]\n                     fmla v4.4s, v4.4s, v4.4s\n                     ldr x24, [{0}]\n                     fmla v5.4s, v5.4s, v5.4s\n                     ldr x25, [{0}]\n                     fmla v6.4s, v6.4s, v6.4s\n                     ldr x26, [{0}]\n                     fmla v7.4s, v7.4s, v7.4s\n                     ldr x27, [{0}]\n                     \",\n        inout(reg) p,\n        out(\"x20\") _, out(\"x21\") _, out(\"x22\") _, out(\"x23\") _,\n        out(\"x24\") _, out(\"x25\") _, out(\"x26\") _, out(\"x27\") _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        ));\n    });\n    s32!(\"fmla_with_s_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     ldr s16, [{0}]\n                     fmla v0.4s, v0.4s, v0.4s\n                     ldr s17, [{0}]\n                     fmla v1.4s, v1.4s, v1.4s\n                     ldr s18, [{0}]\n                     fmla v2.4s, v2.4s, v2.4s\n                     ldr s19, [{0}]\n                     fmla v3.4s, v3.4s, v3.4s\n                     ldr s20, [{0}]\n                     fmla v4.4s, v4.4s, v4.4s\n                     ldr s21, [{0}]\n                     fmla v5.4s, v5.4s, v5.4s\n                     ldr s22, [{0}]\n                     fmla v6.4s, v6.4s, v6.4s\n                     ldr s23, [{0}]\n                     fmla v7.4s, v7.4s, v7.4s\n                     \",\n        inout(reg) p,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"fmla_with_d_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     ldr d16, [{0}]\n                     fmla v0.4s, v0.4s, v0.4s\n                     ldr d17, [{0}]\n                     fmla v1.4s, v1.4s, v1.4s\n                     ldr d18, [{0}]\n                     fmla v2.4s, v2.4s, v2.4s\n                     ldr d19, [{0}]\n                     fmla v3.4s, v3.4s, v3.4s\n                     ldr d20, [{0}]\n                     fmla v4.4s, v4.4s, v4.4s\n                     ldr d21, [{0}]\n                     fmla v5.4s, v5.4s, v5.4s\n                     ldr d22, [{0}]\n                     fmla v6.4s, v6.4s, v6.4s\n                     ldr d23, [{0}]\n                     fmla v7.4s, v7.4s, v7.4s\n                     \",\n        inout(reg) p,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        out(\"v16\") _, out(\"v17\") _, out(\"v18\") _, out(\"v19\") _,\n        out(\"v20\") _, out(\"v21\") _, out(\"v22\") _, out(\"v23\") _,\n        ));\n    });\n    s32!(\"fmla_with_d_load_as_v\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     fmla v0.4s, v0.4s, v0.4s\n                     ld1 {{ v9.d }}[0], [{0}]\n                     fmla v1.4s, v1.4s, v1.4s\n                     ld1 {{ v10.d }}[0], [{0}]\n                     fmla v2.4s, v2.4s, v2.4s\n                     ld1 {{ v11.d }}[0], [{0}]\n                     fmla v3.4s, v3.4s, v3.4s\n                     ld1 {{ v12.d }}[0], [{0}]\n                     fmla v4.4s, v4.4s, v4.4s\n                     ld1 {{ v13.d }}[0], [{0}]\n                     fmla v5.4s, v5.4s, v5.4s\n                     ld1 {{ v14.d }}[0], [{0}]\n                     fmla v6.4s, v6.4s, v6.4s\n                     ld1 {{ v15.d }}[0], [{0}]\n                     fmla v7.4s, v7.4s, v7.4s\n                     ld1 {{ v16.d }}[0], [{0}]\n                     \",\n        inout(reg) p,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"fmla_with_v_load\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     fmla v0.4s, v0.4s, v0.4s\n                     ld1 {{ v9.4s }}, [{0}]\n                     fmla v1.4s, v1.4s, v1.4s\n                     ld1 {{ v10.4s }}, [{0}]\n                     fmla v2.4s, v2.4s, v2.4s\n                     ld1 {{ v11.4s }}, [{0}]\n                     fmla v3.4s, v3.4s, v3.4s\n                     ld1 {{ v12.4s }}, [{0}]\n                     fmla v4.4s, v4.4s, v4.4s\n                     ld1 {{ v13.4s }}, [{0}]\n                     fmla v5.4s, v5.4s, v5.4s\n                     ld1 {{ v14.4s }}, [{0}]\n                     fmla v6.4s, v6.4s, v6.4s\n                     ld1 {{ v15.4s }}, [{0}]\n                     fmla v7.4s, v7.4s, v7.4s\n                     ld1 {{ v16.4s }}, [{0}]\n                     \",\n        inout(reg) p,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"fmla_with_ins_32b\", 64, {\n        r8!(asm!(\"\n                     fmla v0.4s, v0.4s, v0.4s\n                     ins v8.s[0], w20\n                     fmla v1.4s, v1.4s, v1.4s\n                     ins v9.s[0], w20\n                     fmla v2.4s, v2.4s, v2.4s\n                     ins v10.s[0], w20\n                     fmla v3.4s, v3.4s, v3.4s\n                     ins v11.s[0], w20\n                     fmla v4.4s, v4.4s, v4.4s\n                     ins v12.s[0], w20\n                     fmla v5.4s, v5.4s, v5.4s\n                     ins v13.s[0], w20\n                     fmla v6.4s, v6.4s, v6.4s\n                     ins v14.s[0], w20\n                     fmla v7.4s, v7.4s, v7.4s\n                     ins v15.s[0], w20\n                     \",\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        out(\"x20\") _,\n        ));\n    });\n    s32!(\"fmla_with_ins_64b\", 64, {\n        r8!(asm!(\"\n                     fmla v0.4s, v0.4s, v0.4s\n                     ins v8.d[0], x20\n                     fmla v1.4s, v1.4s, v1.4s\n                     ins v9.d[0], x20\n                     fmla v2.4s, v2.4s, v2.4s\n                     ins v10.d[0], x20\n                     fmla v3.4s, v3.4s, v3.4s\n                     ins v11.d[0], x20\n                     fmla v4.4s, v4.4s, v4.4s\n                     ins v12.d[0], x20\n                     fmla v5.4s, v5.4s, v5.4s\n                     ins v13.d[0], x20\n                     fmla v6.4s, v6.4s, v6.4s\n                     ins v14.d[0], x20\n                     fmla v7.4s, v7.4s, v7.4s\n                     ins v15.d[0], x20\n                     \",\n        out(\"x20\") _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"fmla_with_ins_64b_cross_parity\", 64, {\n        r8!(asm!(\"\n                     fmla v0.4s, v0.4s, v0.4s\n                     ins v9.d[0], x20\n                     fmla v1.4s, v1.4s, v1.4s\n                     ins v10.d[0], x20\n                     fmla v2.4s, v2.4s, v2.4s\n                     ins v11.d[0], x20\n                     fmla v3.4s, v6.4s, v3.4s\n                     ins v12.d[0], x20\n                     fmla v4.4s, v4.4s, v4.4s\n                     ins v13.d[0], x20\n                     fmla v5.4s, v5.4s, v5.4s\n                     ins v14.d[0], x20\n                     fmla v6.4s, v6.4s, v6.4s\n                     ins v15.d[0], x20\n                     fmla v7.4s, v7.4s, v7.4s\n                     ins v8.d[0], x20\n                     \",\n        out(\"x20\") _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"ins_32b_with_load_s\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     ldr s0, [{0}]\n                     ins v8.d[0], x20\n                     ldr s1, [{0}]\n                     ins v9.d[0], x20\n                     ldr s2, [{0}]\n                     ins v10.d[0], x20\n                     ldr s3, [{0}]\n                     ins v11.d[0], x20\n                     ldr s4, [{0}]\n                     ins v12.d[0], x20\n                     ldr s5, [{0}]\n                     ins v13.d[0], x20\n                     ldr s6, [{0}]\n                     ins v14.d[0], x20\n                     ldr s7, [{0}]\n                     ins v15.d[0], x20\n                     \",\n        inout(reg) p,\n        out(\"x20\") _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n    s32!(\"ins_32b_with_load_s_cross_parity\", 64, {\n        let mut p = F32;\n        r8!(asm!(\"\n                     ldr s0, [{0}]\n                     ins v9.d[0], x20\n                     ldr s1, [{0}]\n                     ins v10.d[0], x20\n                     ldr s2, [{0}]\n                     ins v11.d[0], x20\n                     ldr s3, [{0}]\n                     ins v12.d[0], x20\n                     ldr s4, [{0}]\n                     ins v13.d[0], x20\n                     ldr s5, [{0}]\n                     ins v14.d[0], x20\n                     ldr s6, [{0}]\n                     ins v15.d[0], x20\n                     ldr s7, [{0}]\n                     ins v8.d[0], x20\n                     \",\n        inout(reg) p,\n        out(\"x20\") _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        ));\n    });\n}\n\nfn has_asimdhp() -> bool {\n    std::fs::read_to_string(\"/proc/cpuinfo\").unwrap().contains(\"asimdhp\")\n}\n\n#[target_feature(enable = \"fp16\")]\npub unsafe fn asimdhp(filter: Option<&str>) {\n    macro_rules! s32 {\n        ($label: literal, $n: expr, $stmt:block) => {\n            if $label.contains(filter.unwrap_or(\"\")) {\n                println!(\"{:40} {:.2}\", $label, b32!($stmt) / $n as f64 / *TICK);\n            }\n        };\n    }\n\n    s32!(\"fmlahp\", 16, {\n        asm!(\" fmla v0.8h, v0.8h, v0.8h\n               fmla v1.8h, v1.8h, v1.8h\n               fmla v2.8h, v2.8h, v2.8h\n               fmla v3.8h, v3.8h, v3.8h\n               fmla v4.8h, v4.8h, v4.8h\n               fmla v5.8h, v5.8h, v5.8h\n               fmla v6.8h, v6.8h, v6.8h\n               fmla v7.8h, v7.8h, v7.8h\n               fmla v8.8h, v8.8h, v8.8h\n               fmla v9.8h, v9.8h, v9.8h\n               fmla v10.8h,v10.8h,v10.8h\n               fmla v11.8h,v11.8h,v11.8h\n               fmla v12.8h,v12.8h,v12.8h\n               fmla v13.8h,v13.8h,v13.8h\n               fmla v14.8h,v14.8h,v14.8h\n               fmla v15.8h,v15.8h,v15.8h \",\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        )\n    });\n\n    s32!(\"fcvt\", 16, {\n        asm!(\" fcvtn v0.4h,  v0.4s\n               fcvtn v1.4h,  v1.4s \n               fcvtn v2.4h,  v2.4s \n               fcvtn v3.4h,  v3.4s \n               fcvtn v4.4h,  v4.4s \n               fcvtn v5.4h,  v5.4s \n               fcvtn v6.4h,  v6.4s \n               fcvtn v7.4h,  v7.4s \n               fcvtn v8.4h,  v8.4s \n               fcvtn v9.4h,  v9.4s \n               fcvtn v10.4h, v10.4s\n               fcvtn v11.4h, v11.4s\n               fcvtn v12.4h, v12.4s\n               fcvtn v13.4h, v13.4s\n               fcvtn v14.4h, v14.4s\n               fcvtn v15.4h, v15.4s\",\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        )\n    });\n\n    s32!(\"fcvt2\", 16, {\n        asm!(\" fcvtn2 v0.8h,  v0.4s\n               fcvtn2 v1.8h,  v1.4s \n               fcvtn2 v2.8h,  v2.4s \n               fcvtn2 v3.8h,  v3.4s \n               fcvtn2 v4.8h,  v4.4s \n               fcvtn2 v5.8h,  v5.4s \n               fcvtn2 v6.8h,  v6.4s \n               fcvtn2 v7.8h,  v7.4s \n               fcvtn2 v8.8h,  v8.4s \n               fcvtn2 v9.8h,  v9.4s \n               fcvtn2 v10.8h, v10.4s\n               fcvtn2 v11.8h, v11.4s\n               fcvtn2 v12.8h, v12.4s\n               fcvtn2 v13.8h, v13.4s\n               fcvtn2 v14.8h, v14.4s\n               fcvtn2 v15.8h, v15.4s\",\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        )\n    });\n\n    s32!(\"fmlahp_with_dep\", 1, { asm!(\"fmla v0.8h, v0.8h, v0.8h\", out(\"v0\") _) });\n    s32!(\"fcvtn_with_dep\", 1, { asm!(\"fcvtn v0.4h, v0.4s\", out(\"v0\") _) });\n    s32!(\"fcvtn2_with_dep\", 1, { asm!(\"fcvtn2 v0.8h, v0.4s\", out(\"v0\") _) });\n}\n\nmacro_rules! ksimd {\n    ($filter: expr, $vector_size: expr, $geo: literal, $n: expr, $path: literal) => {\n        kloop!($filter, $vector_size, $geo, $n, \"arm64simd\", $path)\n    };\n}\n\nmacro_rules! kfp16 {\n    ($filter: expr, $vector_size: expr, $geo: literal, $n: expr, $path: literal) => {\n        kloop!($filter, $vector_size, $geo, $n, \"arm64fp16\", $path)\n    };\n}\n\nmacro_rules! kloop {\n    ($filter: expr, $vector_size: expr, $geo: literal, $n: expr, $dir: literal, $path: literal) => {\n        let label = $path.split(\"/\").last().unwrap().split_once(\".\").unwrap().0;\n        let full_label = format!(\"{:8} {:40}\", $geo, label);\n        if full_label.contains($filter.unwrap_or(\"\")) {\n            let time = b2!({\n                let mut p = F32;\n                let mut q = F32;\n                r4!(asm!(include_str!(concat!(\"../arm64/\", $dir, \"/\", $path)),\n                inout(\"x1\") p, inout(\"x2\") q, out(\"x3\") _,\n                out(\"x4\") _, out(\"x5\") _, out(\"x6\") _, out(\"x7\") _,\n                out(\"x8\") _, out(\"x9\") _, out(\"x10\") _, out(\"x11\") _,\n                out(\"x12\") _, out(\"x13\") _, out(\"x14\") _, out(\"x15\") _,\n                out(\"x20\") _, out(\"x21\") _, out(\"x22\") _, out(\"x23\") _,\n                out(\"x24\") _, out(\"x25\") _, out(\"x26\") _, out(\"x27\") _,\n                out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n                out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n                out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n                out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n                out(\"v16\") _, out(\"v17\") _, out(\"v18\") _, out(\"v19\") _,\n                out(\"v20\") _, out(\"v21\") _, out(\"v22\") _, out(\"v23\") _,\n                out(\"v24\") _, out(\"v25\") _, out(\"v26\") _, out(\"v27\") _,\n                out(\"v28\") _, out(\"v29\") _, out(\"v30\") _, out(\"v31\") _,\n                ));\n            }) / 4.;\n            println!(\"{} {:3.0}% ({:0.2}/{} cy)\", full_label, $n as f64 / $vector_size as f64 / time * 100. * *TICK, time / *TICK, $n as f64 / $vector_size as f64);\n        }\n    }\n}\n\nunsafe fn f32_8x8(f: Option<&str>) {\n    ksimd!(f, 4, \"8x8x1xf32\", 64, \"arm64simd_mmm_f32_8x8/packed_packed_loop1/naive.tmpli\");\n    ksimd!(f, 4, \"8x8x1xf32\", 64, \"arm64simd_mmm_f32_8x8/packed_packed_loop1/broken_chains.tmpli\");\n    ksimd!(\n        f,\n        4,\n        \"8x8x1xf32\",\n        64,\n        \"arm64simd_mmm_f32_8x8/packed_packed_loop1/ldr_x_no_preload.tmpli\"\n    );\n    ksimd!(f, 4, \"8x8x1xf32\", 64, \"arm64simd_mmm_f32_8x8/packed_packed_loop1/ldr_x_preload.tmpli\");\n    ksimd!(\n        f,\n        4,\n        \"8x8x1xf32\",\n        64,\n        \"arm64simd_mmm_f32_8x8/packed_packed_loop1/ldr_w_no_preload.tmpli\"\n    );\n    ksimd!(f, 4, \"8x8x1xf32\", 64, \"arm64simd_mmm_f32_8x8/packed_packed_loop1/ldr_w_preload.tmpli\");\n    ksimd!(f, 4, \"8x8x2xf32\", 128, \"arm64simd_mmm_f32_8x8/packed_packed_loop2/broken_chains.tmpli\");\n    ksimd!(f, 4, \"8x8x2xf32\", 128, \"arm64simd_mmm_f32_8x8/packed_packed_loop2/cortex_a55.tmpli\");\n}\n\nunsafe fn f32_12x8(f: Option<&str>) {\n    ksimd!(f, 4, \"12x8x1xf32\", 96, \"arm64simd_mmm_f32_12x8/packed_packed_loop1/naive.tmpli\");\n    ksimd!(\n        f,\n        4,\n        \"12x8x1xf32\",\n        96,\n        \"arm64simd_mmm_f32_12x8/packed_packed_loop1/ldr_w_no_preload.tmpli\"\n    );\n    ksimd!(\n        f,\n        4,\n        \"12x8x1xf32\",\n        96,\n        \"arm64simd_mmm_f32_12x8/packed_packed_loop1/ldr_w_preload.tmpli\"\n    );\n    ksimd!(\n        f,\n        4,\n        \"12x8x1xf32\",\n        96,\n        \"arm64simd_mmm_f32_12x8/packed_packed_loop1/ldr_x_preload.tmpli\"\n    );\n    ksimd!(f, 4, \"12x8x2xf32\", 192, \"arm64simd_mmm_f32_12x8/packed_packed_loop2/cortex_a55.tmpli\");\n}\n\nunsafe fn f32_16x4(f: Option<&str>) {\n    ksimd!(f, 4, \"16x4x1xf32\", 64, \"arm64simd_mmm_f32_16x4/packed_packed_loop1/naive.tmpli\");\n    ksimd!(f, 4, \"16x4x1xf32\", 64, \"arm64simd_mmm_f32_16x4/packed_packed_loop1/cortex_a53.tmpli\");\n    ksimd!(f, 4, \"16x4x2xf32\", 128, \"arm64simd_mmm_f32_16x4/packed_packed_loop2/cortex_a55.tmpli\");\n}\n\nunsafe fn f32_24x4(f: Option<&str>) {\n    ksimd!(f, 4, \"24x4x1xf32\", 96, \"arm64simd_mmm_f32_24x4/packed_packed_loop1/naive.tmpli\");\n    ksimd!(f, 4, \"24x4x1xf32\", 96, \"arm64simd_mmm_f32_24x4/packed_packed_loop1/cortex_a53.tmpli\");\n    ksimd!(f, 4, \"24x4x1xf32\", 96, \"arm64simd_mmm_f32_24x4/packed_packed_loop1/cortex_a55.tmpli\");\n}\n\nunsafe fn f32_64x1(f: Option<&str>) {\n    ksimd!(f, 4, \"64x1x1xf32\", 64, \"arm64simd_mmm_f32_64x1/loop1/naive.tmpli\");\n    ksimd!(f, 4, \"64x1x1xf32\", 64, \"arm64simd_mmm_f32_64x1/loop1/cortex_a53.tmpli\");\n    ksimd!(f, 4, \"64x1x2xf32\", 128, \"arm64simd_mmm_f32_64x1/loop2/naive.tmpli\");\n    ksimd!(f, 4, \"64x1x2xf32\", 128, \"arm64simd_mmm_f32_64x1/loop2/cortex_a55.tmpli\");\n}\n\n// RUSTFLAGS=\"-C target-feature=+fp16\" cargo +nightly dinghy -d khadas-paris bench --bench arm64simd\n#[target_feature(enable = \"fp16\")]\nunsafe fn f16_16x8(f: Option<&str>) {\n    kfp16!(f, 8, \"16x8x1xf16\", 128, \"arm64fp16_mmm_f16_16x8/loop1/naive.tmpli\");\n    kfp16!(f, 8, \"16x8x2xf16\", 256, \"arm64fp16_mmm_f16_16x8/loop2/cortex_a55.tmpli\");\n    kfp16!(f, 8, \"32x4x1xf16\", 128, \"arm64fp16_mmm_f16_32x4/loop1/naive.tmpli\");\n    kfp16!(f, 8, \"32x4x2xf16\", 256, \"arm64fp16_mmm_f16_32x4/loop2/cortex_a55.tmpli\");\n}\n\nfn main() {\n    println!(\"freq {:.2}GHz\\n\", 1e-9 / *TICK);\n\n    let filter = std::env::args().skip(1).filter(|a| a != \"--bench\").next();\n    unsafe {\n        armv8(filter.as_deref());\n        if has_asimdhp() {\n            asimdhp(filter.as_deref());\n        }\n        f32_8x8(filter.as_deref());\n        f32_12x8(filter.as_deref());\n        f32_16x4(filter.as_deref());\n        f32_24x4(filter.as_deref());\n        f32_64x1(filter.as_deref());\n        f16_16x8(filter.as_deref());\n    }\n}\n"
  },
  {
    "path": "linalg/benches/intel.rs",
    "content": "#![allow(dead_code)]\nuse std::time::Instant;\n\nuse tract_data::prelude::*;\nuse tract_linalg::frame::mmm::*;\n\nfn ruin_cache() {\n    // return;\n    let _a = (0..1000000).collect::<Vec<i32>>();\n}\n\npub fn reference<T, K>(mr: usize, k: usize, nr: usize) -> Vec<f32>\nwhere\n    T: Datum + Copy + num_traits::Zero + tract_linalg::LADatum,\n    K: MatMatMulKer<T>,\n{\n    let mut vi = vec![0.0; k * nr];\n\n    for m in 0..mr {\n        for n in 0..nr {\n            for _ in 0..k {\n                let a: f32 = 1.0;\n                let b = 1.0;\n                let offset = { n + m * nr };\n                vi[offset] += a * b;\n            }\n        }\n    }\n    vi\n}\n\nfn bench_to_nanos<\n    T: Datum + Copy + num_traits::Zero + tract_linalg::LADatum,\n    K: MatMatMulKer<T>,\n>(\n    loops: usize,\n    m: usize,\n    n: usize,\n    k: usize,\n) -> f64 {\n    let kernel = K::mmm();\n\n    let mut a = Tensor::zero_aligned::<T>(\n        &[(k + K::end_padding_packed_a()) * m],\n        K::alignment_bytes_packed_a(),\n    )\n    .unwrap();\n\n    let mut a_plain = a.try_as_plain_mut().unwrap();\n    let mut v = a_plain.to_array_view_mut::<f32>().unwrap();\n    v += 1.0;\n    drop(v);\n    drop(a_plain);\n    let mut b = Tensor::zero_aligned::<T>(\n        &[(k + K::end_padding_packed_b()) * n],\n        K::alignment_bytes_packed_b(),\n    )\n    .unwrap();\n\n    let mut b_plain = b.try_as_plain_mut().unwrap();\n    let mut v = b_plain.to_array_view_mut::<f32>().unwrap();\n    v += 1.0;\n    drop(v);\n    drop(b_plain);\n    let mut c = Tensor::zero::<T>(&[n, m]).unwrap();\n\n    let ops = unsafe {\n        [\n            FusedSpec::AddMatMul {\n                k,\n                a: kernel.a_packed(4, k).wrap(&a.view()),\n                b: kernel.b_packed(4, k).wrap(&b.view()),\n            },\n            // FusedSpec::AddUnicast(kernel.c_view(1, 0).wrap(&c.view_mut())),\n            FusedSpec::Store(kernel.c_view(1, 0).wrap(&c.view_mut())),\n        ]\n    };\n\n    let mut values = Vec::with_capacity(loops);\n\n    for _ in 0..loops {\n        ruin_cache();\n        let start = Instant::now();\n        unsafe { kernel.run(m, n, &ops).unwrap() };\n        values.push(start.elapsed());\n    }\n\n    eprintln!(\"{:?} -> {:?}\", values.first().unwrap(), values.last().unwrap());\n\n    values.sort();\n    values[loops / 2].as_nanos() as f64\n}\n\nfn model<T: Datum + Copy + num_traits::Zero + tract_linalg::LADatum, K: MatMatMulKer<T>>()\n-> (f64, f64) {\n    let x = 1000;\n    let zp = bench_to_nanos::<T, K>(1000, K::mr() * 4, K::nr() * 4, 0);\n    let y = bench_to_nanos::<T, K>(1000, K::mr() * 4, K::nr() * 4, x);\n    let slope = (y - zp) / x as f64;\n    (slope, zp)\n}\n\nfn as_match_line<T: Datum + Copy + num_traits::Zero + tract_linalg::LADatum, K: MatMatMulKer<T>>() {\n    let coeffs = model::<T, K>();\n    println!(\"({:?}, {}, {}) => {} * k + {}\", K::name(), K::mr(), K::nr(), (coeffs.0), (coeffs.1),);\n}\n\nfn main() {\n    let core_id = core_affinity::get_core_ids().unwrap()[0];\n    core_affinity::set_for_current(core_id);\n    // as_match_line::<f32, fma_mmm_f32_64x1>();\n    // as_match_line::<f32, avx512_mmm_f32_128x1>();\n    // as_match_line::<f32, avx512_mmm_f32_16x1>();\n    // as_match_line::<f32, fma_mmm_f32_40x2>();\n    // as_match_line::<f32, fma_mmm_f32_32x3>();\n    // as_match_line::<f32, fma_mmm_f32_24x4>();\n    // as_match_line::<f32, fma_mmm_f32_16x5>();\n    // as_match_line::<f32, fma_mmm_f32_16x6>();\n    // as_match_line::<f32, fma_mmm_f32_8x8>();\n\n    // mmv_perf_m();\n    mmm_perf_batch_size();\n}\n\n// for mmv\nfn mmv_perf_m() {\n    use tract_linalg::x86_64_fma::mmm::*;\n    let core_id = core_affinity::get_core_ids().unwrap()[0];\n    core_affinity::set_for_current(core_id);\n    fn bench<T: Datum + Copy + num_traits::Zero + tract_linalg::LADatum, K: MatMatMulKer<T>>(\n        m: usize,\n    ) {\n        let val = bench_to_nanos::<T, K>(1000, m, 1, 100) / (m * 100) as f64;\n        print!(\"{val}\\t\");\n    }\n\n    print!(\"N\\t\");\n    print!(\"fma_mmm_f32_64x1\\t\");\n    print!(\"avx512_mmm_f32_128x1\\t\");\n    print!(\"avx512_mmm_f32_16x1\\t\");\n    println!();\n    for n in 1..=128 {\n        eprintln!(\"{n}\");\n        print!(\"{n}\\t\");\n        bench::<f32, fma_mmm_f32_64x1>(n);\n        bench::<f32, avx512_mmm_f32_128x1>(n);\n        bench::<f32, avx512_mmm_f32_16x1>(n);\n        println!();\n    }\n}\n\n// output a csv file with the perf of the kernels wrt batch size\nfn mmm_perf_batch_size() {\n    use tract_linalg::x86_64_fma::mmm::*;\n    let core_id = core_affinity::get_core_ids().unwrap()[0];\n    core_affinity::set_for_current(core_id);\n    fn bench<T: Datum + Copy + num_traits::Zero + tract_linalg::LADatum, K: MatMatMulKer<T>>(\n        n: usize,\n    ) {\n        let val =\n            bench_to_nanos::<T, K>(1000, K::mr() * 4, n, 100) / (K::mr() * 4 * 100 * n) as f64;\n        print!(\"{val}\\t\");\n    }\n\n    print!(\"N\\t\");\n    print!(\"fma_mmm_f32_8x8\\t\");\n    print!(\"fma_mmm_f32_16x6\\t\");\n    print!(\"fma_mmm_f32_16x5\\t\");\n    print!(\"fma_mmm_f32_24x4\\t\");\n    print!(\"fma_mmm_f32_32x3\\t\");\n    print!(\"fma_mmm_f32_40x2\\t\");\n    print!(\"fma_mmm_f32_64x1\\t\");\n    print!(\"avx512_mmm_f32_128x1\\t\");\n    print!(\"avx512_mmm_f32_16x1\\t\");\n    print!(\"avx512_mmm_f32_16x12\\t\");\n    print!(\"avx512_mmm_f32_16x8\\t\");\n    print!(\"avx512_mmm_f32_32x6\\t\");\n    print!(\"avx512_mmm_f32_32x5\\t\");\n    print!(\"avx512_mmm_f32_48x4\\t\");\n    print!(\"avx512_mmm_f32_64x3\\t\");\n    print!(\"avx512_mmm_f32_80x2\\t\");\n    println!();\n    for n in 1..=128 {\n        eprintln!(\"{n}\");\n        print!(\"{n}\\t\");\n        bench::<f32, fma_mmm_f32_8x8>(n);\n        bench::<f32, fma_mmm_f32_16x6>(n);\n        bench::<f32, fma_mmm_f32_16x5>(n);\n        bench::<f32, fma_mmm_f32_24x4>(n);\n        bench::<f32, fma_mmm_f32_32x3>(n);\n        bench::<f32, fma_mmm_f32_40x2>(n);\n        bench::<f32, fma_mmm_f32_64x1>(n);\n        bench::<f32, avx512_mmm_f32_128x1>(n);\n        bench::<f32, avx512_mmm_f32_16x1>(n);\n        bench::<f32, avx512_mmm_f32_16x12>(n);\n        bench::<f32, avx512_mmm_f32_16x8>(n);\n        bench::<f32, avx512_mmm_f32_32x6>(n);\n        bench::<f32, avx512_mmm_f32_32x5>(n);\n        bench::<f32, avx512_mmm_f32_48x4>(n);\n        bench::<f32, avx512_mmm_f32_64x3>(n);\n        bench::<f32, avx512_mmm_f32_80x2>(n);\n        println!();\n    }\n}\n"
  },
  {
    "path": "linalg/benches/leaky_relu.rs",
    "content": "use criterion::*;\nuse tract_data::prelude::*;\n\nuse tract_linalg::element_wise::ElementWiseKer;\n\nfn leaky_relu_f16(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"leaky_relu_f16\");\n    group.throughput(Throughput::Elements(1024));\n    let mut input = unsafe { Tensor::uninitialized_aligned::<f16>(&[1024], 16).unwrap() };\n    let input = input.as_slice_mut::<f16>().unwrap();\n    let alpha = f16::from_f32(0.1);\n    group.bench_function(\"rust\", |b| b.iter(|| rust_fp16(input, alpha)));\n    group.bench_function(\"rust_with_f16\", |b| b.iter(|| unsafe { rust_with_fp16(input, alpha) }));\n    group.bench_function(\"linalg\", |b| b.iter(|| linalg16(input, alpha)));\n    group.bench_function(\"linalg-asm\", |b| {\n        b.iter(|| tract_linalg::arm64::arm64fp16_leaky_relu_f16_16n::run(input, alpha))\n    });\n}\n\n#[inline(never)]\nfn rust_fp16(input: &mut [f16], alpha: f16) {\n    for x in input {\n        *x = if *x > f16::ZERO { *x } else { *x * alpha }\n    }\n}\n\n#[target_feature(enable = \"fp16\")]\n#[inline(never)]\nunsafe fn rust_with_fp16(input: &mut [f16], alpha: f16) {\n    for x in input {\n        *x = if *x > f16::ZERO { *x } else { *x * alpha }\n    }\n}\n\n#[inline(never)]\nfn linalg16(input: &mut [f16], alpha: f16) {\n    (tract_linalg::ops().leaky_relu_f16)().run_with_params(input, alpha).unwrap();\n}\n\nfn leaky_relu_f32(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"leaky_relu_f32\");\n    group.throughput(Throughput::Elements(1024));\n    let mut input = unsafe { Tensor::uninitialized_aligned::<f32>(&[1024], 16).unwrap() };\n    let input = input.as_slice_mut::<f32>().unwrap();\n    let alpha = 0.1f32;\n    group.bench_function(\"rust\", |b| b.iter(|| rust_fp32(input, alpha)));\n    group.bench_function(\"linalg\", |b| b.iter(|| linalg32(input, alpha)));\n    group.bench_function(\"linalg-asm\", |b| {\n        b.iter(|| tract_linalg::arm64::arm64simd_leaky_relu_f32_8n::run(input, alpha))\n    });\n}\n\n#[inline(never)]\nfn rust_fp32(input: &mut [f32], alpha: f32) {\n    for x in input {\n        *x = if *x > 0.0 { *x } else { *x * alpha }\n    }\n}\n\n#[inline(never)]\nfn linalg32(input: &mut [f32], alpha: f32) {\n    (tract_linalg::ops().leaky_relu_f32)().run_with_params(input, alpha).unwrap();\n}\n\ncriterion_group!(benches, leaky_relu_f32, leaky_relu_f16);\ncriterion_main!(benches);\n"
  },
  {
    "path": "linalg/benches/mat_vec.rs",
    "content": "use criterion::*;\nuse tract_data::internal::*;\nuse tract_linalg::mmm::{AsInputValue, FusedSpec};\n\nuse DatumType::F32;\n\nfn mat_vec_mul(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"mat_vec_mul\");\n    unsafe {\n        {\n            let (m, k) = &(768usize, 256usize);\n            group.throughput(Throughput::Elements((m * k) as u64));\n            group.bench_with_input(\n                BenchmarkId::from_parameter(format!(\"{m}x{k}\")),\n                &(m, k),\n                |be, &(&m, &k)| {\n                    let mmm = tract_linalg::ops().mmm(F32, Some(m), Some(k), Some(1)).unwrap();\n                    let packing = &mmm.packings()[0];\n                    let a = Tensor::zero::<f32>(&[m, k]).unwrap();\n                    let pa = packing.0.prepare_one(&a, 1, 0).unwrap();\n                    let b = Tensor::zero::<f32>(&[k, 1]).unwrap();\n                    let pb = packing.1.prepare_one(&b, 0, 1).unwrap();\n                    let mut c = Tensor::zero::<f32>(&[m]).unwrap();\n                    be.iter(move || {\n                        mmm.run(\n                            m,\n                            1,\n                            &[\n                                FusedSpec::AddMatMul {\n                                    a: AsInputValue::Borrowed(&*pa),\n                                    b: AsInputValue::Borrowed(&*pb),\n                                    packing: 0,\n                                },\n                                FusedSpec::Store(mmm.c_view(Some(0), Some(0)).wrap(&c.view_mut())),\n                            ],\n                        )\n                    });\n                },\n            );\n        }\n    }\n    group.finish();\n}\n\ncriterion_group!(benches, mat_vec_mul);\ncriterion_main!(benches);\n"
  },
  {
    "path": "linalg/benches/mm_for_asr_am.rs",
    "content": "use criterion::*;\n\nmod utils;\nuse utils::*;\n\nfn all(c: &mut Criterion) {\n    // packed_packed: co, ci, n\n    //    direct_conv(c, \"asr_2M\", 24, 5, 40, 200, 1); // lda\n    packed_packed(c, \"asr_2M\", 256, 200, 24); // tdnn1\n    //    direct_conv(c, \"asr_2M\", 24, 3, 256, 256, 1); // tdnn2\n    //    direct_conv(c, \"asr_2M\", 24, 3, 256, 256, 3); // tdnn3\n    packed_packed(c, \"asr_2M\", 256, 256, 8); // fastlstm1 and 2 (input) x 8 (4 prod x 2 layers)\n    packed_packed(c, \"asr_2M\", 256, 128, 1); // fastlstm1 and 2 (hidden) x 64 (4 prod x 2 layers x 8 loops)\n    packed_packed(c, \"asr_2M\", 256, 256, 1); // fastlstm1 and 2 (rp) x 16 (2 layers x 8 loops)\n    //    direct_conv(c, \"asr_2M\", 8, 3, 256, 256, 1); // tdnn4, tdd5 (x2)\n    packed_packed(c, \"asr_2M\", 1690, 256, 8); // output\n\n    // 8M\n    packed_packed(c, \"asr_8M\", 512, 200, 24); // tdnn1\n    packed_packed(c, \"asr_8M\", 512, 512, 24); // tdnn2\n    packed_packed(c, \"asr_8M\", 512, 256, 1); // fastlstm1 and 2 (four parts, rec mat*vec)\n    packed_vec(c, \"asr_8M\", 512, 256, 1); // fastlstm1 and 2 (four parts, rec mat*vec)\n\n    // pseudo 15M\n    packed_packed(c, \"asr_pseudo15M\", 768, 200, 24); // tdnn1\n    packed_packed(c, \"asr_pseudo15M\", 768, 2304, 24); // tdnn2\n    packed_packed(c, \"asr_pseudo15M\", 768, 2304, 8); // tdnn3,4,5\n    packed_packed(c, \"asr_pseudo15M\", 768, 768, 8); // fastlstm1 and 2 (four parts, rec mat*mat)\n    packed_packed(c, \"asr_pseudo15M\", 768, 384, 1); // fastlstm1 and 2 (four parts, rec mat*vec)\n    packed_vec(c, \"asr_pseudo15M\", 768, 384, 1); // fastlstm1 and 2 (four parts, rec mat*vec)\n\n    // 15M\n    packed_vec(c, \"asr_15M\", 768, 256, 1); // fastlstm1 and 2 (four parts, rec mat*vec)\n}\n\ncriterion_group!(benches, all);\ncriterion_main!(benches);\n"
  },
  {
    "path": "linalg/benches/mm_for_inception.rs",
    "content": "extern crate criterion;\nuse criterion::*;\nuse tract_data::internal::*;\nuse tract_linalg::mmm::{AsInputValue, FusedSpec};\n\nuse DatumType::F32;\n\nfn mat_mul_smmm(be: &mut criterion::Bencher, &(m, k, n): &(usize, usize, usize)) {\n    unsafe {\n        let mmm = tract_linalg::ops().mmm(F32, Some(m), Some(k), Some(n)).unwrap();\n        let a = Tensor::zero::<f32>(&[m, k]).unwrap();\n        let b = Tensor::zero::<f32>(&[k, n]).unwrap();\n        let packing = &mmm.packings()[0];\n        let pa = packing.0.prepare_one(&a, 1, 0).unwrap();\n        let pb = packing.1.prepare_one(&b, 0, 1).unwrap();\n\n        let mut c = Tensor::zero::<f32>(&[m, n]).unwrap();\n        be.iter(move || {\n            mmm.run(\n                m,\n                n,\n                &[\n                    FusedSpec::AddMatMul {\n                        a: AsInputValue::Borrowed(&*pa),\n                        b: AsInputValue::Borrowed(&*pb),\n                        packing: 0,\n                    },\n                    FusedSpec::Store(mmm.c_view(Some(0), Some(1)).wrap(&c.view_mut())),\n                ],\n            )\n        });\n    }\n}\n\nfn mat_mul_prepacked(c: &mut Criterion, m: usize, k: usize, n: usize) {\n    let mut group = c.benchmark_group(\"mat_mul_prepacked\");\n    group.bench_function(\"smmm\", |be| mat_mul_smmm(be, &(m, k, n)));\n}\n\nfn s64x288x21609(c: &mut Criterion) {\n    mat_mul_prepacked(c, 64, 288, 21609)\n}\n\ncriterion::criterion_group!(benches, s64x288x21609);\ncriterion::criterion_main!(benches);\n"
  },
  {
    "path": "linalg/benches/mm_for_wavenet_hw.rs",
    "content": "use criterion::*;\n\nmod utils;\nuse utils::*;\n\nfn s16x60x8(c: &mut Criterion) {\n    packed_packed(c, \"wavenet\", 32, 32, 8); // postproc\n    packed_packed(c, \"wavenet\", 16, 60, 8);\n}\n\ncriterion_group!(benches, s16x60x8);\ncriterion_main!(benches);\n"
  },
  {
    "path": "linalg/benches/sigmoid.rs",
    "content": "#[macro_use]\nextern crate criterion;\nextern crate tract_linalg;\nuse criterion::Criterion;\n\nfn ssigmoid(c: &mut Criterion, n: usize) {\n    c.bench_function(&format!(\"ssigmoid_{n}\"), move |be| {\n        let mut s = (0..n).map(|i| i as f32 / 10.0).collect::<Vec<f32>>();\n        let op = &(tract_linalg::ops().sigmoid_f32)();\n        be.iter(|| op.run(&mut s));\n    });\n}\n\nfn bs(c: &mut Criterion) {\n    ssigmoid(c, 4);\n    ssigmoid(c, 8);\n    ssigmoid(c, 128);\n    ssigmoid(c, 1024);\n}\n\ncriterion_group!(benches, bs);\ncriterion_main!(benches);\n"
  },
  {
    "path": "linalg/benches/softmax.rs",
    "content": "use criterion::*;\nuse tract_data::prelude::*;\nuse tract_linalg::element_wise::ElementWiseKer;\nuse tract_linalg::generic::reduce::softmax_l2::SSoftMaxL2;\nuse tract_linalg::reduce::{MapReduceKer, ReduceKer};\n\n#[inline(never)]\nfn loop1_f32_naive(slice: &mut [f32]) -> f32 {\n    let mut max = f32::MIN;\n    for x in &*slice {\n        if *x > max {\n            max = *x;\n        }\n    }\n    max\n}\n\n#[inline(never)]\nfn loop2_f32(slice: &mut [f32], max: f32) -> f32 {\n    let mut sum = 0.;\n    for x in slice.iter_mut() {\n        *x = (*x - max).exp();\n        sum += *x;\n    }\n    sum\n}\n\n#[inline(never)]\nfn loop3_f32(slice: &mut [f32], sum: f32) {\n    let recip = sum.recip();\n    for x in slice {\n        *x *= recip;\n    }\n}\n\n#[inline(never)]\nfn rust_f32(slice: &mut [f32]) {\n    let max = loop1_f32_naive(slice);\n    let sum = loop2_f32(slice, max);\n    loop3_f32(slice, sum);\n}\n\nfn softmax_f32(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"softmax_f32\");\n    group.throughput(Throughput::Elements(1500));\n    let mut input = unsafe { Tensor::uninitialized_aligned::<f32>(&[1500], 16).unwrap() };\n    let mut plain = input.try_as_plain_mut().unwrap();\n    let input = plain.as_slice_mut::<f32>().unwrap();\n    group.bench_function(\"rust\", |b| b.iter(|| rust_f32(input)));\n    group.bench_function(\"loop1/naive\", |b| b.iter(|| loop1_f32_naive(input)));\n    group.bench_function(\"loop1/generic\", |b| {\n        b.iter(|| tract_linalg::generic::reduce::max::SMax4::red().run(input))\n    });\n    #[cfg(target_arch = \"x86_64\")]\n    group.bench_function(\"loop1/iasm\", |b| {\n        b.iter(|| {\n            tract_linalg::x86_64_fma::max::x86_64_fma_max_f32_32n::red().run(input).unwrap();\n        })\n    });\n    #[cfg(target_arch = \"aarch64\")]\n    group.bench_function(\"loop1/intr\", |b| {\n        b.iter(|| {\n            tract_linalg::arm64::arm64simd_max_f32_16n::red().run(input).unwrap();\n        })\n    });\n    group.bench_function(\"loop2/naive\", |b| b.iter(|| loop2_f32(input, 1.0)));\n    group.bench_function(\"loop2/generic\", |b| {\n        b.iter(|| SSoftMaxL2::red().run_with_params(input, 10.))\n    });\n    #[cfg(target_arch = \"x86_64\")]\n    group.bench_function(\"loop2/iasm\", |b| {\n        b.iter(|| {\n            tract_linalg::x86_64_fma::softmax::x86_64_fma_softmax2_fastcompact_f32_32n::red()\n                .run_with_params(input, 10.)\n                .unwrap()\n        });\n    });\n    #[cfg(target_arch = \"aarch64\")]\n    group.bench_function(\"loop2/iasm\", |b| {\n        b.iter(|| {\n            tract_linalg::arm64::arm64simd_softmax2_fastcompact_f32_16n::red()\n                .run_with_params(input, 0.21)\n                .unwrap()\n        });\n    });\n    group.bench_function(\"loop3/naive\", |b| b.iter(|| loop3_f32(input, 0.21)));\n    group.bench_function(\"loop3/generic\", |b| {\n        b.iter(|| {\n            tract_linalg::generic::by_scalar::SMulByScalar4::ew().run_with_params(input, 0.21)\n        })\n    });\n    #[cfg(target_arch = \"x86_64\")]\n    group.bench_function(\"loop3/iasm\", |b| {\n        b.iter(|| {\n            tract_linalg::x86_64_fma::by_scalar::x86_64_avx_f32_mul_by_scalar_32n::ew()\n                .run_with_params(input, 0.21)\n                .unwrap()\n        });\n    });\n    #[cfg(target_arch = \"aarch64\")]\n    group.bench_function(\"loop3/iasm\", |b| {\n        b.iter(|| {\n            tract_linalg::arm64::arm64simd_mul_by_scalar_f32_16n::ew()\n                .run_with_params(input, 0.21)\n                .unwrap()\n        });\n    });\n}\n\ncriterion_group!(benches, softmax_f32);\ncriterion_main!(benches);\n"
  },
  {
    "path": "linalg/benches/utils.rs",
    "content": "#![allow(dead_code)]\nuse criterion::*;\nuse tract_data::internal::*;\nuse tract_linalg::mmm::{FusedSpec, MMMInputValue, MatMatMul};\n\nuse DatumType::*;\nuse tract_linalg::mmm::AsInputValue;\n\npub fn packed_packed(c: &mut Criterion, name: &str, m: usize, k: usize, n: usize) {\n    let mut group = c.benchmark_group(format!(\"{name}/packed_packed\"));\n    group.throughput(Throughput::Elements((m * k * n) as u64));\n    let id = format!(\"{m}x{k}x{n}\");\n    group.bench_with_input(BenchmarkId::new(\"f32/cold\", &id), &(F32, m, k, n, true), mat_mat);\n    group.bench_with_input(BenchmarkId::new(\"f32/hot\", &id), &(F32, m, k, n, false), mat_mat);\n    group.bench_with_input(BenchmarkId::new(\"i8/cold\", &id), &(I8, m, k, n, true), mat_mat);\n    group.bench_with_input(BenchmarkId::new(\"i8/hot\", &id), &(I8, m, k, n, false), mat_mat);\n}\n\npub fn packed_vec(c: &mut Criterion, name: &str, m: usize, k: usize, n: usize) {\n    assert_eq!(n, 1);\n    let mut group = c.benchmark_group(format!(\"{name}/packed_vec\"));\n    group.throughput(Throughput::Elements((m * k * n) as u64));\n    let id = format!(\"{m}x{k}x{n}\");\n    group.bench_with_input(BenchmarkId::new(\"f32/cold\", &id), &(F32, m, k, n, true), mat_mat);\n    group.bench_with_input(BenchmarkId::new(\"f32/hot\", &id), &(F32, m, k, n, false), mat_mat);\n    group.bench_with_input(BenchmarkId::new(\"i8/cold\", &id), &(I8, m, k, n, true), mat_mat);\n    group.bench_with_input(BenchmarkId::new(\"i8/hot\", &id), &(I8, m, k, n, false), mat_mat);\n}\n\npub fn ruin_cache() {\n    let _a = (0..1000000).collect::<Vec<i32>>();\n}\n\n#[allow(clippy::too_many_arguments)]\nunsafe fn run(\n    m: usize,\n    _k: usize,\n    n: usize,\n    be: &mut Bencher,\n    mmm: &dyn MatMatMul,\n    a: &dyn MMMInputValue,\n    b: &dyn MMMInputValue,\n    cold: bool,\n) {\n    let mut scratch = unsafe { mmm.allocate_scratch_space() };\n    be.iter_custom(move |iters| {\n        let mut dur = std::time::Duration::default();\n        for _ in 0..iters {\n            if cold {\n                ruin_cache();\n            }\n            let instant = std::time::Instant::now();\n            unsafe {\n                mmm.run_with_scratch_space(\n                    m,\n                    n,\n                    scratch.as_mut(),\n                    &[FusedSpec::AddMatMul {\n                        a: AsInputValue::Borrowed(a),\n                        b: AsInputValue::Borrowed(b),\n                        packing: 0,\n                    }],\n                )\n                .unwrap()\n            };\n            let time = instant.elapsed();\n            dur += time;\n        }\n        dur\n    });\n}\n\nfn mat_mat(be: &mut Bencher, params: &(DatumType, usize, usize, usize, bool)) {\n    let (dt, m, k, n, _) = *params;\n    let mm = tract_linalg::ops().mmm(dt, Some(m), Some(k), Some(n)).unwrap();\n    mat_mat_with_mm(be, &*mm, params)\n}\n\npub fn mat_mat_with_mm(\n    be: &mut Bencher,\n    mmm: &dyn MatMatMul,\n    &(dt, m, k, n, cold): &(DatumType, usize, usize, usize, bool),\n) {\n    let a = Tensor::zero_dt(dt, &[m, k]).unwrap();\n    let b = Tensor::zero_dt(dt, &[k, n]).unwrap();\n    let packing = &mmm.packings()[0];\n    let pa = packing.0.prepare_one(&a, 1, 0).unwrap();\n    let pb = packing.1.prepare_one(&b, 0, 1).unwrap();\n    unsafe {\n        run(m, k, n, be, mmm, &*pa, &*pb, cold);\n    }\n}\n"
  },
  {
    "path": "linalg/benches/virtual_im2col.rs",
    "content": "use criterion::measurement::WallTime;\nuse criterion::*;\nuse tract_data::internal::*;\n\n#[allow(dead_code)]\n#[path = \"../tests/virtual_im2col.rs\"]\nmod virtual_im2col;\nuse virtual_im2col::ConvProblem;\n\nfn conv(\n    c: &mut BenchmarkGroup<WallTime>,\n    ci: usize,\n    h: usize,\n    w: usize,\n    co: usize,\n    kh: usize,\n    kw: usize,\n) {\n    // CHW HWIO\n    let input = Tensor::zero::<f32>(&[ci, h, w]).unwrap();\n    let filters = Tensor::zero::<f32>(&[kh, kw, ci, co]).unwrap();\n    let mut cv = ConvProblem { input, filters, lazy_im2col: false };\n    c.bench_function(\"eager\", |b| {\n        b.iter(|| {\n            cv.tract().unwrap();\n        })\n    });\n    cv.lazy_im2col = true;\n    c.bench_function(\"lazy\", |b| {\n        b.iter(|| {\n            cv.tract().unwrap();\n        })\n    });\n}\n\nfn ex1(c: &mut Criterion) {\n    let mut c = c.benchmark_group(\"ex1\");\n    conv(&mut c, 32, 256, 256, 32, 3, 3);\n}\n\nfn big(c: &mut Criterion) {\n    let mut c = c.benchmark_group(\"big\");\n    conv(&mut c, 1, 1024, 1024, 99, 3, 3);\n}\n\ncriterion_group!(benches, ex1, big);\ncriterion_main!(benches);\n"
  },
  {
    "path": "linalg/benches/x86_64.rs",
    "content": "#![allow(dead_code, non_upper_case_globals, unused_macros, non_snake_case, unused_assignments)]\n\nuse std::arch::asm;\n\n// mod nano;\n\n#[repr(C, align(64))]\nstruct Floats([f32; 256 * 1024 * 64]);\nconst _F32: Floats = Floats([12.; 256 * 1024 * 64]);\nconst F32: *const f32 = (&_F32) as *const Floats as *const f32;\n\nlazy_static::lazy_static! {\n    static ref TICK: f64 = unsafe { b8192!(asm!(\"or rax, rax\", out(\"rax\") _)) };\n}\n\nmacro_rules! kloop {\n    ($filter: expr, $geo: literal, $n: expr, $path: literal, $ww: expr, $u: expr, $arch: expr) => {\n        let label = $path.split(\"/\").last().unwrap().split_once(\".\").unwrap().0;\n        let full_label = format!(\"{:8} {:40}\", $geo, label);\n\t\tlet repeats = 32;\n\t\tlet ks = 256;\n        if full_label.contains($filter.unwrap_or(\"\")) {\n            let time = b1!({\n\n\t\t\t\tlet mut p = F32;\n\t\t\t\tlet mut q = F32;\n\t\t\t\tlet mut k = ks;\n\t\t\t\tlet mut r = repeats;\n\t\t\t\tasm!(\n\t\t\t\t\tconcat!(r#\"\n2:\n      mov rax, r9\n      mov rcx, r10\n      mov r8, r12\n3:\n    \"#, include_str!(\t\t\tconcat!(\"../x86_64/\", $arch, \"/\", $path)), \"\\n sub r8, \", $u, r#\"\njnz 3b\n\nsub r11, 1\njnz 2b\n\"#),\n\t\t\t\t\tinout(\"r9\") p, inout(\"r10\") q, inout(\"r12\") k, inout(\"r11\") r, out(\"rax\") _, out(\"rcx\") _,\n\t\t\t\t\tout(\"r8\") _,\n\t\t\t\t\tout(\"zmm0\") _, out(\"zmm1\") _, out(\"zmm2\") _, out(\"zmm3\") _,\n\t\t\t\t\tout(\"zmm4\") _, out(\"zmm5\") _, out(\"zmm6\") _, out(\"zmm7\") _,\n\t\t\t\t\tout(\"zmm8\") _, out(\"zmm9\") _, out(\"zmm10\") _, out(\"zmm11\") _,\n\t\t\t\t\tout(\"zmm12\") _, out(\"zmm13\") _, out(\"zmm14\") _, out(\"zmm15\") _,\n\t\t\t\t\tout(\"zmm20\") _, out(\"zmm21\") _, out(\"zmm22\") _, out(\"zmm23\") _,\n\t\t\t\t\tout(\"zmm24\") _, out(\"zmm25\") _, out(\"zmm26\") _, out(\"zmm27\") _,\n                    out(\"zmm28\") _,  out(\"zmm29\") _,  out(\"zmm30\") _,  out(\"zmm31\") _,\n\t\t\t\t);\n            });\n\n\t\t\t// We have k=1024 * 64 but some tests step twice per iteration\n\t\t\tlet iterations = (ks * repeats / $u);\n\t\t\t// Those that step twice process twice as many elements per iteration\n\t\t\tlet elems_per_iteration = $n * $u;\n\n\t\t\tlet time_per_iteration = time / iterations  as f64;\n\n\t\t\tlet total_floats = elems_per_iteration * iterations;\n\t\t\tlet flops = total_floats as f64 / time;\n\n\t\t\tlet total_time_ms = time * 1e6;\n\t\t\tlet fmas_per_iteration = ($n as f64 / $ww as f64) * $u as f64;\n\t\t\tlet ticks_per_iteration = time_per_iteration / *TICK;\n            println!(\"{} {:3.5} {:3.0}% ({:>5.2 }/{:3 } cy) {:.2} GFLOP/s\", full_label, total_time_ms, fmas_per_iteration / ticks_per_iteration * 100., ticks_per_iteration, fmas_per_iteration, flops / 1e9 );\n        }\n    };\n\n\t($filter: expr, $geo: literal, $n: expr, $path: literal, $ww: expr) => {\n\t\tkloop!($filter, $geo, $n, $path, $ww, 1, \"fma\")\n\t};\n\t($filter: expr, $geo: literal, $n: expr, $path: literal, $ww: expr, $u: expr) => {\n\t\tkloop!($filter, $geo, $n, $path, $ww, $u, \"fma\")\n\t};\n}\n\nunsafe fn packed_packed_1x12(f: Option<&str>) {\n    println!(\"-- 1x12 kernels\");\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(\n            f,\n            \"1x12x1\",\n            (16 * 1 * 12),\n            \"1x12/packed_packed_loop1/avx-512.tmpli\",\n            16,\n            1,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_1x8(f: Option<&str>) {\n    println!(\"-- 1x8 kernels\");\n    kloop!(f, \"1x8x1\", (8 * 8), \"8x8/packed_packed_loop1/avx.tmpli\", 8);\n    kloop!(f, \"1x8x2\", (8 * 8), \"8x8/packed_packed_loop1/avx-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"1x8x1\", (16 * 1 * 8), \"8x8/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n    }\n    println!();\n}\n\nunsafe fn packed_packed_2x6(f: Option<&str>) {\n    println!(\"-- 2x6 kernels\");\n    kloop!(f, \"2x6x1\", (16 * 6), \"2x6/packed_packed_loop1/original.tmpli\", 8);\n    kloop!(f, \"2x6x2\", (16 * 6), \"2x6/packed_packed_loop1/original-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"2x6x1\", (16 * 2 * 6), \"2x6/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(\n            f,\n            \"2x6x2\",\n            (16 * 2 * 6),\n            \"2x6/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_2x5(f: Option<&str>) {\n    println!(\"-- 2x5 kernels\");\n    kloop!(f, \"2x5x1\", (16 * 5), \"2x5/packed_packed_loop1/avx.tmpli\", 8);\n    kloop!(f, \"2x5x2\", (16 * 5), \"2x5/packed_packed_loop1/avx-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"2x5x1\", (32 * 5), \"2x5/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(\n            f,\n            \"2x5x2\",\n            (32 * 5),\n            \"2x5/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_3x4(f: Option<&str>) {\n    println!(\"-- 3x4 kernels\");\n    kloop!(f, \"3x4x1\", (24 * 4), \"3x4/packed_packed_loop1/avx.tmpli\", 8);\n    kloop!(f, \"3x4x2\", (24 * 4), \"3x4/packed_packed_loop1/avx-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"3x4x1\", (16 * 3 * 4), \"3x4/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(\n            f,\n            \"3x4x2\",\n            (16 * 3 * 4),\n            \"3x4/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_4x3(f: Option<&str>) {\n    println!(\"-- 4x3 kernels\");\n    kloop!(f, \"4x3x1\", (32 * 3), \"4x3/packed_packed_loop1/avx.tmpli\", 8);\n    kloop!(f, \"4x3x2\", (32 * 3), \"4x3/packed_packed_loop1/avx-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"4x3x1\", (16 * 4 * 3), \"4x3/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(\n            f,\n            \"4x3x2\",\n            (16 * 4 * 3),\n            \"4x3/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_5x2(f: Option<&str>) {\n    println!(\"-- 5x2 kernels\");\n    kloop!(f, \"5x2x1\", (40 * 2), \"5x2/packed_packed_loop1/avx.tmpli\", 8);\n    kloop!(f, \"5x2x1\", (40 * 2), \"5x2/packed_packed_loop1/avx-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"5x2x1\", (16 * 5 * 2), \"5x2/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(\n            f,\n            \"5x2x2\",\n            (16 * 5 * 2),\n            \"5x2/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_6x2(f: Option<&str>) {\n    println!(\"-- 6x2 kernels\");\n    kloop!(f, \"6x2x1\", (48 * 2), \"6x2/packed_packed_loop1/avx.tmpli\", 8);\n    kloop!(f, \"6x2x2\", (48 * 2), \"6x2/packed_packed_loop1/avx-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"6x2x1\", (16 * 6 * 2), \"6x2/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(\n            f,\n            \"6x2x2\",\n            (16 * 6 * 2),\n            \"6x2/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_8x2(f: Option<&str>) {\n    println!(\"-- 8x2 kernels\");\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"8x2x1\", (16 * 8 * 2), \"8x2/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n    }\n    println!();\n}\n\nunsafe fn packed_packed_8x1(f: Option<&str>) {\n    println!(\"-- 8x1 kernels\");\n    kloop!(f, \"8x1x1\", (64 * 1), \"8x1/packed_packed_loop1/avx.tmpli\", 8);\n    kloop!(f, \"8x1x2\", (64 * 1), \"8x1/packed_packed_loop1/avx-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"8x1x1\", (16 * 8 * 1), \"8x1/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(\n            f,\n            \"8x1x2\",\n            (16 * 8 * 1),\n            \"8x1/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_6x1(f: Option<&str>) {\n    println!(\"-- 6x1 kernels\");\n    kloop!(f, \"6x1x1\", (48 * 1), \"6x1/packed_packed_loop1/avx.tmpli\", 8);\n    kloop!(f, \"6x1x2\", (48 * 1), \"6x1/packed_packed_loop1/avx-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"6x1x1\", (16 * 6 * 1), \"6x1/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(\n            f,\n            \"6x1x2\",\n            (16 * 6 * 1),\n            \"6x1/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_7x1(f: Option<&str>) {\n    println!(\"-- 7x1 kernels\");\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"7x1x1\", (16 * 7 * 1), \"7x1/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(\n            f,\n            \"7x1x2\",\n            (16 * 7 * 1),\n            \"7x1/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_1x1(f: Option<&str>) {\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(f, \"1x1x1\", (16 * 1 * 1), \"1x1/packed_packed_loop1/avx-512.tmpli\", 16, 1, \"avx512\");\n        kloop!(f, \"1x1x2\", (16 * 1 * 1), \"1x1/packed_packed_loop1/unroll.tmpli\", 16, 2, \"avx512\");\n        kloop!(f, \"1x1x4\", (16 * 1 * 1), \"1x1/packed_packed_loop1/unroll-4.tmpli\", 16, 4, \"avx512\");\n        kloop!(f, \"1x1x8\", (16 * 1 * 1), \"1x1/packed_packed_loop1/unroll-8.tmpli\", 16, 8, \"avx512\");\n        kloop!(\n            f,\n            \"1x1x16\",\n            (16 * 1 * 1),\n            \"1x1/packed_packed_loop1/unroll-16.tmpli\",\n            16,\n            16,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nunsafe fn packed_packed_10x1(f: Option<&str>) {\n    println!(\"-- 10x1 kernels\");\n    kloop!(f, \"10x1x1\", (80 * 1), \"10x1/packed_packed_loop1/avx.tmpli\", 8);\n    kloop!(f, \"10x1x2\", (80 * 1), \"10x1/packed_packed_loop1/avx-unroll.tmpli\", 8, 2);\n    if std::is_x86_feature_detected!(\"avx512f\") {\n        kloop!(\n            f,\n            \"10x1x1\",\n            (16 * 10 * 1),\n            \"10x1/packed_packed_loop1/avx-512.tmpli\",\n            16,\n            1,\n            \"avx512\"\n        );\n        kloop!(\n            f,\n            \"10x1x2\",\n            (16 * 10 * 1),\n            \"10x1/packed_packed_loop1/avx-512-unroll.tmpli\",\n            16,\n            2,\n            \"avx512\"\n        );\n    }\n    println!();\n}\n\nfn main() {\n    let filter = std::env::args().skip(1).find(|a| a != \"--bench\");\n    unsafe {\n        packed_packed_1x1(filter.as_deref());\n        packed_packed_1x12(filter.as_deref());\n        packed_packed_1x8(filter.as_deref());\n        packed_packed_2x6(filter.as_deref());\n        packed_packed_2x5(filter.as_deref());\n        packed_packed_3x4(filter.as_deref());\n        packed_packed_4x3(filter.as_deref());\n        packed_packed_5x2(filter.as_deref());\n        packed_packed_6x2(filter.as_deref());\n        packed_packed_8x2(filter.as_deref());\n        packed_packed_6x1(filter.as_deref());\n        packed_packed_7x1(filter.as_deref());\n        packed_packed_8x1(filter.as_deref());\n        packed_packed_10x1(filter.as_deref());\n    }\n}\n"
  },
  {
    "path": "linalg/build.rs",
    "content": "#![allow(clippy::box_default)]\n\nuse liquid_core::Runtime;\nuse liquid_core::{Display_filter, Filter, FilterReflection, ParseFilter};\nuse liquid_core::{Value, ValueView};\n\nuse std::{env, ffi, fs, path};\n\n#[path = \"arm64/apple_amx/instructions.rs\"]\nmod apple_amx_instructions;\n\nfn var(k: &str) -> String {\n    env::var(k).unwrap()\n}\n\nfn use_masm() -> bool {\n    env::var(\"CARGO_CFG_TARGET_ENV\") == Ok(\"msvc\".to_string()) && var(\"HOST\").contains(\"-windows-\")\n}\n\nfn include_amx() -> bool {\n    let arch = var(\"CARGO_CFG_TARGET_ARCH\");\n    let os = var(\"CARGO_CFG_TARGET_OS\");\n    os == \"macos\"\n        || (env::var(\"CARGO_FEATURE_APPLE_AMX_IOS\").is_ok() && os == \"ios\" && arch == \"aarch64\")\n}\n\nfn jump_table() -> Vec<String> {\n    println!(\"cargo:rerun-if-changed=src/frame/mmm/fuse.rs\");\n    std::fs::read_to_string(\"src/frame/mmm/fuse.rs\")\n        .unwrap()\n        .lines()\n        .filter(|l| l.contains(\"// jump_to:\"))\n        .map(|l| l.split(\"jump_to:\").nth(1).unwrap().to_owned())\n        .collect()\n}\n\n#[derive(Clone, Debug)]\nstruct ConfigForHalf {\n    extra_flags: Vec<String>,\n    needs_pragma: bool,\n}\n\nimpl ConfigForHalf {\n    fn new(extra_flags: Vec<String>, needs_pragma: bool) -> ConfigForHalf {\n        ConfigForHalf { extra_flags, needs_pragma }\n    }\n\n    fn all() -> Vec<ConfigForHalf> {\n        let mut configs = vec![];\n        for extra_flags in\n            [vec![], vec![\"-march=armv8.2-a\".to_string()], vec![\"-mcpu=cortex-a55\".to_string()]]\n        {\n            for needs_pragma in [false, true] {\n                configs.push(ConfigForHalf::new(extra_flags.clone(), needs_pragma))\n            }\n        }\n        configs\n    }\n\n    fn cc(&self) -> cc::Build {\n        let mut cc = cc::Build::new();\n        for flag in &self.extra_flags {\n            cc.flag(flag);\n        }\n        cc\n    }\n\n    fn works(&self) -> bool {\n        let filename = if self.needs_pragma {\n            \"arm64/arm64fp16/dummy_fmla_pragma.S\"\n        } else {\n            \"arm64/arm64fp16/dummy_fmla_no_pragma.S\"\n        };\n        self.cc().file(filename).try_compile(\"dummy\").is_ok()\n    }\n\n    pub fn probe() -> Option<ConfigForHalf> {\n        Self::all().iter().find(|c| c.works()).cloned()\n    }\n}\n\nfn main() {\n    let target = var(\"TARGET\");\n    let arch = var(\"CARGO_CFG_TARGET_ARCH\");\n    let os = var(\"CARGO_CFG_TARGET_OS\");\n    let out_dir = path::PathBuf::from(var(\"OUT_DIR\"));\n\n    let suffix = env!(\"CARGO_PKG_VERSION\").replace(['-', '.'], \"_\");\n    make_extern_kernel_decl_macro(&out_dir, &suffix);\n\n    match arch.as_ref() {\n        \"x86_64\" => {\n            let mut files = preprocess_files(\"x86_64/fma\", &[], &suffix, false);\n            files.extend(preprocess_files(\"x86_64/avx512\", &[], &suffix, false));\n\n            if os == \"windows\" {\n                if use_masm() {\n                    let mut lib_exe = cc::windows_registry::find(&target, \"lib.exe\")\n                        .expect(\"Could not find lib.exe\");\n                    lib_exe\n                        .arg(format!(\"/out:{}\", out_dir.join(\"x86_64_fma.lib\").to_str().unwrap()));\n                    for f in files {\n                        let mut obj = f.clone();\n                        obj.set_extension(\"o\");\n                        let mut ml_exe = cc::windows_registry::find(&target, \"ml64.exe\")\n                            .expect(\"Could not find ml64.exe\");\n                        if !ml_exe\n                            .arg(\"/Fo\")\n                            .arg(&obj)\n                            .arg(\"/c\")\n                            .arg(&f)\n                            .status()\n                            .unwrap()\n                            .success()\n                        {\n                            for (i, l) in std::fs::read_to_string(&f).unwrap().lines().enumerate() {\n                                println!(\"{i:8} {l}\");\n                            }\n                            panic!();\n                        }\n                        lib_exe.arg(obj);\n                    }\n                    assert!(lib_exe.status().unwrap().success());\n                    println!(\"cargo:rustc-link-search=native={}\", out_dir.to_str().unwrap());\n                    println!(\"cargo:rustc-link-lib=static=x86_64_fma\");\n                } else {\n                    cc::Build::new()\n                        .files(files)\n                        .flag(\"-mfma\")\n                        .flag(\"-mf16c\")\n                        .compile(\"x86_64_fma\");\n\n                    // clang at least (dunno about gcc) outputs .asm files in the\n                    // root directory that we need to clean up so we don't pollute\n                    // the build output/working directory\n                    let _ = fs::remove_file(\"fma_mmm_f32_16x6.asm\");\n                    let _ = fs::remove_file(\"fma_mmm_i32_8x8.asm\");\n                    let _ = fs::remove_file(\"fma_sigmoid_f32.asm\");\n                    let _ = fs::remove_file(\"fma_tanh_f32.asm\");\n                }\n            } else {\n                cc::Build::new().files(files).flag(\"-mfma\").compile(\"x86_64_fma\");\n            }\n        }\n        \"arm\" | \"armv7\" => {\n            let files = preprocess_files(\"arm32/armvfpv2\", &[], &suffix, false);\n            cc::Build::new().files(files).flag(\"-marm\").flag(\"-mfpu=vfp\").compile(\"armvfpv2\");\n            let files = preprocess_files(\n                \"arm32/armv7neon\",\n                &[(\"core\", vec![\"cortexa7\", \"cortexa9\", \"generic\"])],\n                &suffix,\n                false,\n            );\n            cc::Build::new().files(files).flag(\"-marm\").flag(\"-mfpu=neon\").compile(\"armv7neon\");\n        }\n        \"aarch64\" => {\n            let files = preprocess_files(\n                \"arm64/arm64simd\",\n                &[(\"core\", vec![\"a53\", \"a55\", \"gen\"])],\n                &suffix,\n                false,\n            );\n            cc::Build::new().files(files).compile(\"arm64simd\");\n            if include_amx() {\n                let files = preprocess_files(\"arm64/apple_amx\", &[], &suffix, false);\n                cc::Build::new().files(files).compile(\"appleamx\");\n            }\n            if std::env::var(\"CARGO_FEATURE_NO_FP16\").is_err() {\n                let config =\n                    ConfigForHalf::probe().expect(\"No configuration found for fp16 support\");\n                let files = preprocess_files(\n                    \"arm64/arm64fp16\",\n                    &[(\"core\", vec![\"a55\", \"gen\"])],\n                    &suffix,\n                    config.needs_pragma,\n                );\n                config.cc().files(files).compile(\"arm64fp16\")\n            }\n        }\n        _ => {}\n    }\n}\n\ntype Variant = (&'static str, Vec<&'static str>);\n\nfn preprocess_files(\n    input: impl AsRef<path::Path>,\n    variants: &[Variant],\n    suffix: &str,\n    needs_pragma: bool,\n) -> Vec<path::PathBuf> {\n    let out_dir = path::PathBuf::from(var(\"OUT_DIR\"));\n    let mut files = vec![];\n    let dir_entries = {\n        let mut dir_entries: Vec<fs::DirEntry> =\n            input.as_ref().read_dir().unwrap().map(|f| f.unwrap()).collect();\n        dir_entries.sort_by_key(|a| a.path());\n        dir_entries\n    };\n    for f in dir_entries {\n        if f.path().extension() == Some(ffi::OsStr::new(\"tmpl\")) {\n            let tmpl_file = f.path().file_name().unwrap().to_str().unwrap().to_owned();\n            let concerned_variants: Vec<&Variant> =\n                variants.iter().filter(|v| tmpl_file.contains(v.0)).collect();\n            let expanded_variants = concerned_variants.iter().map(|pair| pair.1.len()).product();\n            for v in 0..expanded_variants {\n                let mut tmpl_file = tmpl_file.clone();\n                let mut id = v;\n                let mut globals = vec![];\n                for variable in variants {\n                    let key = variable.0;\n                    let value = variable.1[id % variable.1.len()];\n                    globals.push((key, value));\n                    tmpl_file = tmpl_file.replace(key, value);\n                    id /= variable.1.len();\n                }\n                let mut file = out_dir.join(tmpl_file);\n                file.set_extension(\"S\");\n                preprocess_file(f.path(), &file, &globals, suffix, needs_pragma);\n                files.push(file);\n            }\n        }\n    }\n    files\n}\n\nfn strip_comments(s: String, msvc: bool) -> String {\n    if msvc {\n        s.lines().map(|line| line.replace(\"//\", \";\")).collect::<Vec<String>>().join(\"\\n\")\n    } else {\n        s\n    }\n}\n\nfn preprocess_file(\n    template: impl AsRef<path::Path>,\n    output: impl AsRef<path::Path>,\n    variants: &[(&'static str, &'static str)],\n    suffix: &str,\n    needs_pragma: bool,\n) {\n    println!(\"cargo:rerun-if-changed={}\", template.as_ref().to_string_lossy());\n    let family = var(\"CARGO_CFG_TARGET_FAMILY\");\n    let os = var(\"CARGO_CFG_TARGET_OS\");\n\n    // We also check to see if we're on a windows host, if we aren't, we won't be\n    // able to use the Microsoft assemblers,\n    let msvc = use_masm();\n    println!(\"cargo:rerun-if-changed={}\", template.as_ref().to_string_lossy());\n    let mut input = fs::read_to_string(&template).unwrap();\n    input = strip_comments(input, msvc);\n    let l = if os == \"macos\" {\n        \"L\"\n    } else if family == \"windows\" {\n        \"\"\n    } else {\n        \".L\"\n    }\n    .to_owned();\n    let long = if msvc { \"dd\" } else { \".long\" };\n    let g = if os == \"macos\" || os == \"ios\" || os == \"watchos\" || os == \"tvos\" { \"_\" } else { \"\" };\n    // note: use .align with bytes instead of p2align since they both use direct bytes.\n    let align = if msvc { \"align\" } else { \".align\" };\n    let mut globals = liquid::object!({\n        \"msvc\": msvc,\n        \"needs_pragma\": needs_pragma,\n        \"family\": family,\n        \"os\": os,\n        \"L\": l,\n        \"G\": g,\n        \"suffix\": suffix,\n        \"long\": long,\n        \"jump_table\": jump_table(),\n        \"align\": align,\n        \"offset\": if msvc { \"offset\" } else { \"rip + \"},\n    });\n    for (k, v) in variants {\n        globals.insert(k.to_string().into(), liquid::model::Value::scalar(*v));\n    }\n    let partials = load_partials(template.as_ref().parent().unwrap(), msvc);\n    let mut parser = liquid::ParserBuilder::with_stdlib()\n        .partials(liquid::partials::LazyCompiler::new(partials))\n        .filter(F16);\n    if include_amx() {\n        parser = apple_amx_instructions::register(parser);\n        globals.extend(apple_amx_instructions::globals());\n    }\n    if let Err(e) = parser\n        .build()\n        .and_then(|p| p.parse(&input))\n        .and_then(|r| r.render_to(&mut fs::File::create(&output).unwrap(), &globals))\n    {\n        eprintln!(\"Processing {}\", template.as_ref().to_string_lossy());\n        eprintln!(\"{e}\");\n        panic!()\n    }\n}\n\nfn load_partials(p: &path::Path, msvc: bool) -> liquid::partials::InMemorySource {\n    let mut mem = liquid::partials::InMemorySource::new();\n    for f in walkdir::WalkDir::new(p) {\n        let f = f.unwrap();\n        if f.path().is_dir() {\n            continue;\n        }\n\n        let ext = f.path().extension().map(|s| s.to_string_lossy()).unwrap_or(\"\".into());\n        let text = std::fs::read_to_string(f.path()).unwrap_or_else(|_| panic!(\"file {f:?}\"));\n        let text = match ext.as_ref() {\n            \"tmpli\" => Some(text.replace(\"{{\", \"{\").replace(\"}}\", \"}\")),\n            \"tmpliq\" => Some(text),\n            _ => None,\n        };\n        if let Some(text) = text {\n            let text = strip_comments(text, msvc);\n            let key =\n                f.path().strip_prefix(p).unwrap().to_str().unwrap().to_owned().replace('\\\\', \"/\");\n            println!(\"cargo:rerun-if-changed={}\", f.path().to_string_lossy().replace('\\\\', \"/\"));\n\n            mem.add(key, text);\n        }\n    }\n    mem\n}\n\nfn make_extern_kernel_decl_macro(out_dir: &path::Path, suffix: &str) {\n    let macro_decl = r#\"\n    macro_rules! extern_kernel {\n        (fn $name: ident($($par_name:ident : $par_type: ty ),*) -> $rv: ty) => {\n            paste! {\n                unsafe extern \"C\" { pub fn [<$name _ _suffix>]($(par_name: $par_type),*) -> $rv; }\n                pub use [<$name _ _suffix>] as $name;\n            }\n        }\n    }\"#\n    .replace(\"_suffix\", suffix);\n    std::fs::write(out_dir.join(\"extern_kernel_macro.rs\"), macro_decl).unwrap();\n}\n\n#[derive(Clone, ParseFilter, FilterReflection)]\n#[filter(\n    name = \"float16\",\n    description = \"Write a float16 constant with the .float16 directive in gcc, or as short in clang\",\n    parsed(F16Filter)\n)]\npub struct F16;\n\n#[derive(Debug, Default, Display_filter)]\n#[name = \"float16\"]\nstruct F16Filter;\n\nimpl Filter for F16Filter {\n    fn evaluate(\n        &self,\n        input: &dyn ValueView,\n        _runtime: &dyn Runtime,\n    ) -> liquid_core::Result<Value> {\n        let input: f32 = input.as_scalar().unwrap().to_float().unwrap() as f32;\n        let value = half::f16::from_f32(input);\n        let bits = value.to_bits();\n        Ok(format!(\".short {bits}\").to_value())\n    }\n}\n"
  },
  {
    "path": "linalg/cost_model/Cargo.toml",
    "content": "[package]\nname = \"cost_model\"\nversion = \"0.20.7-pre\"\nedition = \"2024\"\n\n[workspace]\nmembers = []\n\n[dependencies]\nlazy_static = \"1.4.0\"\nclap = \"3.0.7\"\nscan_fmt = \"0.2.6\"\nrand = \"0.8.4\"\ncolorous = \"1.0.6\"\nnu-ansi-term = \"0.50\"\npbr = \"1\"\nreadings-probe = \"0.1.4\"\ntract-linalg = { path = \"..\"}\ntract-data = { path = \"../../data\" }\n"
  },
  {
    "path": "linalg/cost_model/src/main.rs",
    "content": "use pbr::ProgressBar;\nuse tract_data::internal::*;\nuse tract_linalg::mmm::*;\n\nuse rand::prelude::*;\nuse std::io::Write;\nuse std::ops::Range;\nuse std::str::FromStr;\nuse std::time::{Duration, Instant};\nuse tract_itertools::Itertools;\n\npub fn ruin_cache() {\n    let _a = (0..1_000_000).collect::<Vec<i32>>();\n}\n\nfn order_f<F: tract_num_traits::Float>(&a: &F, &b: &F) -> std::cmp::Ordering {\n    if a < b {\n        std::cmp::Ordering::Less\n    } else {\n        std::cmp::Ordering::Greater\n    }\n}\n\npub struct Bencher {\n    bench_time_target: Duration,\n    chunk_time_target: Duration,\n    chunks_min_count: usize,\n    chunks_max_count: usize,\n    probe: Option<readings_probe::Probe>,\n}\n\nimpl Bencher {\n    fn black_box<T>(dummy: T) -> T {\n        unsafe {\n            let ret = std::ptr::read_volatile(&dummy);\n            std::mem::forget(dummy);\n            ret\n        }\n    }\n\n    pub fn run_bench<T, I, P: FnMut() -> Vec<I>, F: FnMut(&mut I) -> T>(\n        &self,\n        mut prep: P,\n        mut f: F,\n    ) -> f64 {\n        let mut inputs = prep();\n        let islen = inputs.len();\n        Self::black_box(f(&mut inputs[0]));\n        let start = Instant::now();\n        Self::black_box(f(&mut inputs[1.min(islen - 1)]));\n        let once = start.elapsed();\n        //   dbg!(once);\n        let evaled = if once < Duration::from_millis(1) {\n            let start = Instant::now();\n            for i in 0..1000 {\n                Self::black_box(f(&mut inputs[i % islen]));\n            }\n            start.elapsed().as_secs_f64() / 1000.\n        } else {\n            once.as_secs_f64()\n        };\n        //    let warmup = (0.2 / evaled) as usize;\n        //    let iters = 5.0 / evaled as f64;\n        // chunk just need to be big enough be measurable\n        //    dbg!(evaled);\n        let chunk = ((self.chunk_time_target.as_secs_f64() / evaled) as usize).max(1);\n        // chunks is the number of measure. make it 1000 at least, 10000 at most\n        let chunks = ((self.bench_time_target.as_secs_f64() / (evaled * chunk as f64)) as usize)\n            .max(self.chunks_min_count)\n            .min(self.chunks_max_count);\n        // let chunks = 10;\n        //dbg!(chunk, chunks);\n        let mut measures = vec![0.0; chunks];\n        /*\n        for _ in 0..warmup {\n        black_box(f());\n        }\n        */\n        let mut input = 0;\n        for i in 0..chunks {\n            let start = Instant::now();\n            for _ in 0..chunk {\n                Self::black_box(f(&mut inputs[input]));\n                input += 1;\n                if input == inputs.len() {\n                    input = 0\n                }\n            }\n            let time = start.elapsed().as_secs_f64();\n            measures[i] = time / chunk as f64;\n        }\n        measures.sort_by(order_f);\n        let q1 = measures[chunks / 4];\n        /*\n           let q3 = measures[chunks - chunks / 4];\n           let iq = q3 - q1;\n        //    measures.retain(|&x| x >= q1 && x <= q3);\n        let epsilon = iq * 2. / (q3 + q1);\n        eprintln!(\"evaled: {} chunk:{} chunks: {} epsilon: {:.3e}\", evaled, chunk, chunks, epsilon);\n        */\n        /*\n        let mut hist = vec![0; 101];\n        for m in &measures {\n        let bucket = (m - measures[0]) / (measures[measures.len() - 1] - measures[0]);\n        hist[(100. * bucket) as usize] += 1;\n        }\n        eprintln!(\"{hist:?}\");\n        eprintln!(\"q1: {}\", measures[measures.len() / 4]);\n        */\n        /*\n        eprintln!(\"avg: {}\", );\n        measures[chunks / 4] //[..chunks / 2].iter().copied().sum::<f64>() / (chunks / 2) as f64\n        */\n        q1\n    }\n}\n\nfn measure_add_mat_mul(bencher: &Bencher, mm: &dyn MatMatMul, m: usize, k: usize, n: usize) -> f64 {\n    let dt = mm.internal_type();\n    if let Some(probe) = &bencher.probe {\n        probe.log_event(&format!(\"start_{},{},{}\", m, k, n)).unwrap();\n    }\n    let a = Tensor::zero_dt(dt, &[m, k]).unwrap();\n    let b = Tensor::zero_dt(dt, &[k, n]).unwrap();\n    unsafe {\n        let time = bencher.run_bench(\n            || {\n                let pb_size = 4 * (m * k + m * n + k * n);\n                let inputs = (10_000_000 / pb_size).max(1);\n                (0..inputs)\n                    .map(|_| {\n                        let (packed_a, packed_b) = &mm.packings()[0];\n                        let pa = packed_a.prepare_one(&a, 1, 0).unwrap();\n                        let pb = packed_b.prepare_one(&b, 0, 1).unwrap();\n                        let c = Tensor::zero_dt(dt, &[m, n]).unwrap();\n                        let pc = mm.c_view(Some(0), Some(1)).wrap(&c.view());\n                        let scratch = mm.allocate_scratch_space();\n                        (scratch, c, pa, pb, pc)\n                    })\n                    .collect()\n            },\n            #[allow(unused_mut)] // not sure why the warning pops\n            |(scratch, _c, pa, pb, pc)| {\n                mm.run_with_scratch_space(\n                    m,\n                    n,\n                    scratch.as_mut(),\n                    &[\n                        FusedSpec::AddMatMul {\n                            a: AsInputValue::Borrowed(&**pa),\n                            b: AsInputValue::Borrowed(&**pb),\n                            packing: 0,\n                        },\n                        FusedSpec::Store(*pc),\n                    ],\n                )\n                .unwrap();\n            },\n        );\n        time\n    }\n}\n\n#[derive(Clone, Debug)]\nenum SamplingStrategy {\n    Random(Range<usize>),\n    Fixed(Vec<usize>),\n}\n\nimpl SamplingStrategy {\n    fn sample(&self) -> Vec<usize> {\n        use SamplingStrategy::*;\n        let mut rng = thread_rng();\n        match self {\n            Random(range) => vec![rng.gen_range(range.clone())],\n            Fixed(v) => v.clone(),\n        }\n    }\n}\n\nimpl FromStr for SamplingStrategy {\n    type Err = TractError;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        if s.contains(\"-\") {\n            let (min, max) = s.split_once(\"-\").unwrap();\n            Ok(SamplingStrategy::Random(\n                min.parse::<usize>().unwrap()..max.parse::<usize>().unwrap() + 1,\n            ))\n        } else {\n            Ok(SamplingStrategy::Fixed(s.split(\",\").map(|s| s.parse::<usize>().unwrap()).collect()))\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct Sample {\n    kernel: String,\n    mr: usize,\n    nr: usize,\n    m: usize,\n    k: usize,\n    n: usize,\n}\n\n#[derive(Clone, Debug)]\nstruct Dataset(Vec<(Sample, f64)>);\n\nimpl Dataset {\n    pub fn smart_sample(mmm: &[&dyn MatMatMul]) -> Vec<Sample> {\n        let mut inputs = vec![];\n        for mm in mmm {\n            let ms = [1, 2, 4, 32, 128]\n                .iter()\n                .map(|m| m * mm.mr())\n                .flat_map(|m| [m - 1, m, m + 1])\n                .collect_vec();\n            let ns = [1, 2, 4, 32, 128]\n                .iter()\n                .map(|m| m * mm.nr())\n                .flat_map(|m| [m - 1, m, m + 1])\n                .collect_vec();\n            let ks = [32, 128, 1024];\n            for m in ms {\n                for &n in &ns {\n                    for k in ks {\n                        inputs.push(Sample {\n                            kernel: mm.name().to_string(),\n                            mr: mm.mr(),\n                            nr: mm.nr(),\n                            m,\n                            k,\n                            n,\n                        });\n                    }\n                }\n            }\n        }\n        inputs\n    }\n\n    pub fn allkernels_random_sample(\n        mmm: &[&dyn MatMatMul],\n        size: usize,\n        m: SamplingStrategy,\n        k: SamplingStrategy,\n        n: SamplingStrategy,\n        max_mkn: usize,\n    ) -> Vec<Sample> {\n        let mut inputs = vec![];\n        for _ in 0..size {\n            let ms = m.sample();\n            let ks = k.sample();\n            let ns = n.sample();\n            for m in ms {\n                for &k in &ks {\n                    for &n in &ns {\n                        for mm in mmm {\n                            if max_mkn < m * k * n {\n                                continue;\n                            }\n                            inputs.push(Sample {\n                                kernel: mm.name().to_string(),\n                                mr: mm.mr(),\n                                nr: mm.nr(),\n                                m,\n                                k,\n                                n,\n                            });\n                        }\n                    }\n                }\n            }\n        }\n        inputs\n    }\n\n    pub fn make_dataset(\n        bencher: &Bencher,\n        mut inputs: Vec<Sample>,\n        mmm: &[&dyn MatMatMul],\n    ) -> Dataset {\n        //        let ruin_cache_time = bencher.run_bench(|| ruin_cache());\n        let mut rng = thread_rng();\n        inputs.shuffle(&mut rng);\n        let mut progress_bar = ProgressBar::new(inputs.len() as _);\n        let mut samples = vec![];\n        for s in inputs {\n            let mm = mmm.iter().find(|mm| mm.name() == s.kernel).unwrap();\n            let y = measure_add_mat_mul(&bencher, *mm, s.m, s.k, s.n);\n            samples.push((s.clone(), y));\n            progress_bar.inc();\n        }\n        progress_bar.finish();\n        Dataset(samples)\n    }\n\n    pub fn save(&self, filename: &str) {\n        let mut f = std::fs::File::create(filename).unwrap();\n        for (s, y) in &self.0 {\n            writeln!(&mut f, \"{} {} {} {} {} {} {}\", s.kernel, s.mr, s.nr, s.m, s.k, s.n, y)\n                .unwrap();\n        }\n    }\n}\n\nfn display_comparison(\n    m: usize,\n    k: usize,\n    n: usize,\n    alts: &[(impl AsRef<str>, f64)],\n    choice: Option<&str>,\n) {\n    alts.iter().sorted_by(|a, b| order_f(&a.1, &b.1)).enumerate().for_each(|(ix, (s, t))| {\n        let s = s.as_ref();\n        let line = format!(\n            \"{:30} truth: {:9.03} us / {:9.03} GFLops\",\n            s,\n            t * 1e6,\n            (m * k * n) as f64 / t / 1e9,\n        );\n        if Some(s) == choice {\n            if ix == 0 {\n                println!(\"{}\", nu_ansi_term::Color::Green.bold().paint(line));\n            } else {\n                println!(\"{}\", nu_ansi_term::Color::Red.bold().paint(line));\n            }\n        } else {\n            println!(\"{}\", line);\n        }\n    });\n}\n\nfn main() {\n    use clap::*;\n\n    let probe = if let Ok(file) = std::fs::File::create(\"readings.out\") {\n        let mut probe = readings_probe::Probe::new(file).unwrap();\n        probe.spawn_heartbeat(std::time::Duration::from_millis(1000)).unwrap();\n        Some(probe)\n    } else {\n        None\n    };\n\n    let parser = App::new(\"tract-linalg-cost-model\")\n        .arg(\n            Arg::new(\"bench-time-target\")\n                .long(\"bench-time-target\")\n                .default_value(\"0.1\")\n                .help(\"Target time for chunk sizing\"),\n        )\n        .arg(\n            Arg::new(\"chunk-time-target\")\n                .long(\"chunk-time-target\")\n                .default_value(\"0.01\")\n                .help(\"Target time for chunk sizing\"),\n        )\n        .arg(\n            Arg::new(\"chunks-min-count\")\n                .long(\"chunks-min-count\")\n                .default_value(\"100\")\n                .help(\"Minimum number of chunks\"),\n        )\n        .arg(\n            Arg::new(\"chunks-max-count\")\n                .long(\"chunks-max-count\")\n                .default_value(\"10000\")\n                .help(\"Minimum number of chunks\"),\n        )\n        .subcommand(App::new(\"list-models\"))\n        .subcommand(\n            App::new(\"time\")\n                .arg(Arg::new(\"mm\").long(\"mm\").help(\"Filter kernels\").takes_value(true))\n                .arg(Arg::new(\"m\"))\n                .arg(Arg::new(\"k\"))\n                .arg(Arg::new(\"n\")),\n        )\n        .subcommand(\n            App::new(\"ds\")\n                .arg(Arg::new(\"mm\").long(\"mm\").help(\"Filter kernels\").takes_value(true))\n                .arg(\n                    Arg::new(\"m\")\n                        .short('m')\n                        .help(\"m values: 1-512 or 1,16,32\")\n                        .takes_value(true)\n                        .default_value(\"1-512\"),\n                )\n                .arg(\n                    Arg::new(\"k\")\n                        .short('k')\n                        .help(\"k values: 1-512 or 1,16,32\")\n                        .takes_value(true)\n                        .default_value(\"1-512\"),\n                )\n                .arg(\n                    Arg::new(\"n\")\n                        .short('n')\n                        .help(\"m values: 1-512 or 1,16,32\")\n                        .takes_value(true)\n                        .default_value(\"1-512\"),\n                )\n                .arg(\n                    Arg::new(\"mkn\")\n                        .long(\"mkn\")\n                        .help(\"Max m*k*n value\")\n                        .takes_value(true)\n                        .default_value(\"9999999999\"),\n                )\n                .arg(\n                    Arg::new(\"size\")\n                        .short('s')\n                        .long(\"size\")\n                        .help(\"Sample size (total)\")\n                        .takes_value(true)\n                        .default_value(\"128\"),\n                )\n                .arg(\n                    Arg::new(\"strat\")\n                        .long(\"strat\")\n                        .help(\"Strategy for sampling\")\n                        .takes_value(true)\n                        .possible_values([\"smart\", \"random\"])\n                        .default_value(\"random\"),\n                )\n                .arg(Arg::new(\"name\").required(true)),\n        );\n\n    let matches = parser.get_matches();\n\n    let bencher = Bencher {\n        bench_time_target: Duration::from_secs_f64(\n            matches.value_of_t(\"bench-time-target\").unwrap(),\n        ),\n        chunk_time_target: Duration::from_secs_f64(\n            matches.value_of_t(\"chunk-time-target\").unwrap(),\n        ),\n        chunks_min_count: matches.value_of_t(\"chunks-min-count\").unwrap(),\n        chunks_max_count: matches.value_of_t(\"chunks-max-count\").unwrap(),\n        probe,\n    };\n\n    let impls = tract_linalg::ops().mmm_impls().iter().collect_vec();\n    let mmms: Vec<&dyn MatMatMul> = impls.iter().map(|p| &***p).collect_vec();\n    match matches.subcommand() {\n        Some((\"list-models\", _sub)) => {\n            for mmm in mmms {\n                println!(\"{}\", mmm.name());\n            }\n        }\n        Some((\"ds\", sub)) => {\n            let mut mmms = mmms.clone();\n            if let Some(mm) = sub.value_of(\"mm\") {\n                mmms.retain(|m| m.name().contains(mm));\n            }\n            let inputs = match sub.value_of(\"strat\").unwrap() {\n                \"smart\" => Dataset::smart_sample(&*mmms),\n                \"random\" => Dataset::allkernels_random_sample(\n                    &*mmms,\n                    sub.value_of_t(\"size\").unwrap(),\n                    sub.value_of_t(\"m\").unwrap(),\n                    sub.value_of_t(\"k\").unwrap(),\n                    sub.value_of_t(\"n\").unwrap(),\n                    sub.value_of_t(\"mkn\").unwrap(),\n                ),\n                _ => unreachable!(),\n            };\n            Dataset::make_dataset(&bencher, inputs, &mmms).save(sub.value_of(\"name\").unwrap());\n        }\n        Some((\"time\", sub)) => {\n            let mut mmms = impls.clone();\n            if let Some(mm) = sub.value_of(\"mm\") {\n                mmms.retain(|m| m.name().contains(mm));\n            }\n            let m: usize = sub.value_of(\"m\").unwrap().parse().unwrap();\n            let k: usize = sub.value_of(\"k\").unwrap().parse().unwrap();\n            let n: usize = sub.value_of(\"n\").unwrap().parse().unwrap();\n            let mut alts = vec![];\n            for mm in &mmms {\n                let y = measure_add_mat_mul(&bencher, &***mm, m, k, n);\n                alts.push((mm.name(), y));\n            }\n            display_comparison(m, k, n, &*alts, None);\n        }\n        _ => panic!(),\n    };\n}\n"
  },
  {
    "path": "linalg/cost_model/train/README.md",
    "content": "# Matrix-matrix multiplication kernel prediction\n\nScript to train a feed-forward neural network with one hidden layer to\npredict which kernel to use for matrix-matrix multiplication, from a dataset\nof measurements made with tract.\n\nTo install the dependencies in a virtual environment:\n\n```sh\nvirtualenv venv\npip install -r requirements.txt\n```\n\nTo train `N=15` neural networks on the dataset (e.g. `a53-dataset`) and save the\nbest one to `neural_net_a53.rs`, run:\n\n```sh\npython train.py -N 15 --platform=a53 a53-dataset neural_net_a53.rs\n```\n\nThis will save the neural network as an instance of ../../src/frame/mmm/cost_model.rs.\n\nThe neural network computes:\n\n```\n  softmax( b2 + w2 * tanh(b1 + w1 * (x - feat_norm_mean) / feat_norm_stddev ) )\n```\n\nRust CostModel implementation is for kernel selection. It is only interested in the ArgMax, so it skips the SoftMax.\n"
  },
  {
    "path": "linalg/cost_model/train/requirements.txt",
    "content": "numpy==1.22.0\ntorch==2.7.0\n"
  },
  {
    "path": "linalg/cost_model/train/runme.sh",
    "content": "#!/bin/sh\n\ndevice_name=$1\ndataset_name=$2\nplatform=$3\n\n[ -e venv ] || virtualenv venv\n. venv/bin/activate\n\npip install -r requirements.txt\n\nset -ex\nmkdir -p tmp\n(\ncd tmp\naws s3 cp s3://tract-ci-builds/products/$device_name/$dataset_name.tgz .\ntar zxf $dataset_name.tgz\ndata=`ls -1 $dataset_name.$device_name`\npython ../train.py -N 15 --platform=$platform $dataset_name.$device_name/$data $platform.rs\n)\nmv tmp/$platform.rs .\n"
  },
  {
    "path": "linalg/cost_model/train/train.py",
    "content": "import random\nfrom math import ceil\nimport numpy as np\nimport torch\nfrom torch import nn\nfrom collections import Counter\n\n\nclass Dataset:\n    def __init__(self, data, kernels, platform=\"cortex_a53\"):\n        self.data = data\n        self.kernels = kernels\n        self.platform = platform\n\n    @classmethod\n    def from_tract_outputs(cls, input_file, platform=\"cortex_a53\"):\n        kernels = set()\n        mkn = set()\n        params = []\n        for line in open(input_file):\n            x = line.strip().split()\n            if len(x) < 7:\n                continue\n            m, k, n = list(map(float, x[3:6]))\n            dur = float(x[-1])\n            kernels.add((x[0], int(x[1]), int(x[2])))\n            mkn.add((m, k, n))\n            params.append((x[0], m, k, n, dur))\n        kernels = sorted(list(kernels))\n        kernel_names = list(map(lambda ker: ker[0], kernels))\n        mkn_kernels = {mkn_value: [0 for k in kernels] for mkn_value in mkn}\n        for krn, m, k, n, dur in params:\n            i = kernel_names.index(krn)\n            mkn_kernels[(m, k, n)][i] = dur\n        sorted_mkn = sorted(list([(m * k * n, (m, k, n)) for m, k, n in mkn]))\n\n        data = []\n        for pdt, (m, k, n) in sorted_mkn:\n            if pdt == 0:\n                continue\n            data.append((np.array([m, k, n]), np.array(mkn_kernels[(m, k, n)])))\n        return cls(data, kernels)\n\n    def shuffle(self):\n        random.shuffle(self.data)\n\n    def split(self, validation=0.1, shuffle=True):\n        if shuffle:\n            self.shuffle()\n        N = len(self.data)\n        S = ceil(validation * N)\n        trainset = Dataset(self.data[S:], self.kernels)\n        valset = Dataset(self.data[:S], self.kernels)\n        return trainset, valset\n\n    def filter_mkn(self, max_val=None, min_val=None):\n        def keep(x):\n            mkn = np.prod(x)\n            if max_val is not None and mkn > max_val:\n                return False\n            if min_val is not None and mkn < min_val:\n                return False\n            return True\n\n        data = [(x, y) for x, y in self.data if keep(x)]\n        return Dataset(data, self.kernels)\n\n    def __len__(self):\n        return len(self.data)\n\n    def get_mr_nr_values(self):\n        _, mrs, nrs = zip(*self.kernels)\n        return sorted(list(set(mrs))), sorted(list(set(nrs)))\n\n    def get_classif_features_for(self, x):\n        m, k, n = x\n\n        mrs, nrs = self.get_mr_nr_values()\n        fts = [\n            np.log(m),\n            np.log(k),\n            np.log(n),\n            np.log(m * k * n),\n        ]\n        for mr in mrs:\n            fts.append(m % mr)\n            fts.append(float(m % mr != 0))\n        for nr in nrs:\n            fts.append(n % nr)\n            fts.append(float(n % nr != 0))\n\n        return np.array(fts)\n\n    def get_classif_features(self, soft_targets=True, temp=5e-2):\n        feats = []\n        targets = []\n        for x, y in self.data:\n            x_features = self.get_classif_features_for(x)\n            if soft_targets:\n                tgt = 1 / y\n                tgt = tgt - tgt.max()\n                tgt *= temp\n                tgt = np.exp(tgt)\n                tgt = tgt / tgt.sum()\n                targets.append(tgt)\n            else:\n                targets.append(y.argmin())\n            feats.append(x_features)\n        return np.array(feats), np.array(targets)\n\n    def get_rel_diffs(self, preds):\n        diffs = []\n        for (x, y), z in zip(self.data, preds):\n            t = y.argmin()\n            if z == t:\n                continue\n            diffs.append((y[z] - y.min()) / y.min())\n        return np.array(diffs)\n\n    def big_product_behaviour(self):\n        sorted_data = sorted((np.prod(x), np.argmin(y)) for x, y in self.data)\n        biggest_exp = sorted_data[:-int(len(self) / 100)]\n        _, choices = zip(*biggest_exp)\n        kernel_ix = Counter(choices).most_common(1)[0][0]\n        kernel = self.kernels[kernel_ix][0]\n        threshold = sorted_data[-1][0]\n        return (threshold, kernel)\n\nclass MLP(nn.Module):\n    def __init__(\n        self,\n        kernels,\n        num_features,\n        num_hiddens,\n        normalize=True,\n        num_updates=3000,\n        batch_size=128,\n        weight_decay=0.0001,\n        soft_preds=False,\n    ):\n        super().__init__()\n        self.kernels = kernels\n        num_kernels = len(kernels)\n        self.linear_1 = nn.Linear(num_features, num_hiddens)\n        self.act = nn.Tanh()\n        self.linear_2 = nn.Linear(num_hiddens, num_kernels)\n        self.softmax = nn.LogSoftmax(dim=1)\n        self.mean = None\n        self.std = None\n        self._normalize = normalize\n        self.num_updates = num_updates\n        self.batch_size = batch_size\n        self.soft_preds = soft_preds\n        self.weight_decay = weight_decay\n\n    def forward(self, x):\n        y1 = self.linear_1.forward(x)\n        y = self.act.forward(y1)\n\n        y = self.linear_2.forward(y)\n        return self.softmax.forward(y)\n\n    def normalize(self, X):\n        if self._normalize:\n            return (X - self.mean) / self.std\n        return X\n\n    def predict_proba(self, x):\n        x = self.normalize(x)\n        tx = torch.from_numpy(x).float()\n        y = self.forward(tx)\n        return np.exp(y.detach().numpy())\n\n    def predict(self, x):\n        y = self.predict_proba(x)\n        return y.argmax(axis=1)\n\n    def fit(self, X, y):\n        if self._normalize:\n            self.mean = X.mean(axis=0, keepdims=True)\n            self.std = X.std(axis=0, keepdims=True)\n            self.std[self.std < 1e-4] = 1e-4\n            X = self.normalize(X)\n\n        updates = 0\n        optimizer = torch.optim.AdamW(\n            self.parameters(), lr=1e-3, weight_decay=self.weight_decay\n        )\n        loss = (\n            torch.nn.KLDivLoss(reduction=\"batchmean\")\n            if self.soft_preds\n            else torch.nn.NLLLoss()\n        )\n        indices = list(range(X.shape[0]))\n        num_batches = len(indices) // self.batch_size\n\n        prev_loss = None\n        num_iter_no_impr = 0\n\n        while updates < self.num_updates:\n            random.shuffle(indices)\n            total_loss = 0\n            batches_seen = 0\n            for bnum in range(num_batches):\n                bb = self.batch_size * bnum\n                be = bb + self.batch_size\n                Xb = X[indices[bb:be]]\n                yb = y[indices[bb:be]]\n\n                tx = torch.from_numpy(Xb).float()\n                if self.soft_preds:\n                    ty = torch.from_numpy(yb).float()\n                else:\n                    ty = torch.from_numpy(yb).long()\n\n                optimizer.zero_grad()\n                z = self.forward(tx)\n                loss_val = loss(z, ty)\n                loss_val.backward()\n                optimizer.step()\n\n                sloss = loss_val.detach().numpy()\n                total_loss += sloss\n\n                updates += 1\n                batches_seen += 1\n                if updates > self.num_updates:\n                    break\n\n            total_loss /= batches_seen\n            if prev_loss is not None:\n                impr = (prev_loss - total_loss) / prev_loss\n                if impr < 1e-4:\n                    num_iter_no_impr += 1\n                else:\n                    num_iter_no_impr = 0\n            prev_loss = total_loss\n            if num_iter_no_impr > 4:\n                break\n\ndef save_as_rust(mlp, dataset, output):\n    with open(output, 'w') as f:\n        mrs, nrs = dataset.get_mr_nr_values()\n        params = {}\n        for name, tensor in mlp.named_parameters():\n            params[name] = tensor.detach().numpy()\n        big_product_mkn_threshold, big_product_kernel_choice = dataset.big_product_behaviour()\n        f.write(f\"\"\"use crate::frame::mmm::CostModel;\n        pub fn model() -> CostModel<'static> {{\n            CostModel {{\n                big_product_mkn_threshold: {big_product_mkn_threshold},\n                big_product_kernel_choice: \"{big_product_kernel_choice}\",\n                kernels: &{str(list(map(lambda k: k[0], mlp.kernels))).replace(\"'\", '\"')},\n                mrs: &{mrs},\n                nrs: &{nrs},\n                feat_norm_mean: &{mlp.mean.flatten().tolist()},\n                feat_norm_stddev: &{mlp.std.flatten().tolist()},\n                w1: &{params[\"linear_1.weight\"].flatten().tolist()},\n                b1: &{params[\"linear_1.bias\"].flatten().tolist()},\n                w2: &{params[\"linear_2.weight\"].flatten().tolist()},\n                b2: &{params[\"linear_2.bias\"].flatten().tolist()},\n            }}\n        }}\n\"\"\")\n\ndef train_one_mlp(\n    dataset, hidden_layer_size, validation=0.2, num_updates=3000,\n):\n    train_ds, dev_ds = dataset.split(validation=validation)\n    Xtrain, ytrain = train_ds.get_classif_features()\n    clf = MLP(\n        train_ds.kernels,\n        Xtrain.shape[1],\n        hidden_layer_size,\n        soft_preds=True,\n        num_updates=num_updates,\n    )\n    clf.fit(Xtrain, ytrain)\n\n    Xtest, ytest = dev_ds.get_classif_features()\n    ztest = clf.predict(Xtest)\n    ptest = clf.predict_proba(Xtest)\n\n    gtclass = ytest.argmax(axis=1)\n    accuracy = 100 * np.sum(gtclass == ztest) / len(ytest)\n    crank = [\n        len(dev_ds.kernels) - list(stdpreds).index(gty)\n        for stdpreds, gty in zip(ptest.argsort(axis=1), gtclass)\n    ]\n    rdiffs = list(dev_ds.get_rel_diffs(ztest))\n    return accuracy, rdiffs, crank, clf\n\n\nPASS_TESTS = {\n    \"cortex_a7\": [\n        ([16, 60, 8], \"armv7neon_mmm_f32_8x4_cortexa7\"),\n        ([16, 64, 8], \"armv7neon_mmm_f32_8x4_cortexa7\"),\n#         ([2, 32, 8], \"generic_f32_4x4\"),\n        ([64, 48, 8], \"armv7neon_mmm_f32_8x4_cortexa7\"),\n        ([256, 768, 6], \"armv7neon_mmm_f32_8x6_cortexa7\"),\n        ([512, 1536, 18], \"armv7neon_mmm_f32_8x6_cortexa7\"),\n        ([512, 1536, 24], \"armv7neon_mmm_f32_8x6_cortexa7\"),\n    ],\n    \"cortex_a9\": [\n        ([16, 60, 8], \"armv7neon_mmm_f32_8x4_cortexa9\"),\n        ([16, 64, 8], \"armv7neon_mmm_f32_8x4_cortexa9\"),\n#         ([2, 32, 8], \"generic_f32_4x4\"),\n        ([64, 48, 8], \"armv7neon_mmm_f32_8x4_cortexa9\"),\n        ([256, 768, 6], \"armv7neon_mmm_f32_8x6_cortexa9\"),\n    ],\n    \"cortex_a53\": [\n        ([16, 60, 8], \"arm64simd_mmm_f32_8x8_a53\"),\n        ([16, 64, 8], \"arm64simd_mmm_f32_8x8_a53\"),\n        ([2, 32, 8], \"arm64simd_mmm_f32_8x8_a53\"),\n        ([64, 48, 8], \"arm64simd_mmm_f32_8x8_a53\"),\n        ([256, 768, 4], \"arm64simd_mmm_f32_24x4_a53\"),\n    ],\n    \"cortex_a55\": [\n        ([16, 60, 8], \"arm64simd_mmm_f32_8x8_a53\"),\n        ([16, 64, 8], \"arm64simd_mmm_f32_8x8_a53\"),\n        ([64, 48, 8], \"arm64simd_mmm_f32_8x8_a53\"),\n        ([256, 768, 4], \"arm64simd_mmm_f32_16x4_a55\"),\n        ([512, 1536, 24], \"arm64simd_mmm_f32_12x8_a55\"),\n        ([60, 40, 1337], \"arm64simd_mmm_f32_12x8_a55\"),\n        ([72, 2400,728], \"arm64simd_mmm_f32_12x8_a55\"),\n    ],\n}\n\n\ndef main():\n    from argparse import ArgumentParser\n\n    parser = ArgumentParser(\n        description=\"Train a neural network to predict which mmm kernel\"\n    )\n    parser.add_argument(\n        \"-H\",\n        \"--hidden-size\",\n        type=int,\n        default=40,\n        help=\"Size of the hidden layer of the neural network [default: 40].\",\n    )\n    parser.add_argument(\n        \"-N\", \"--num-trainings\", type=int, default=1, help=\"Number of trainings.\",\n    )\n    parser.add_argument(\"--platform\", default=\"cortex_a53\", choices=PASS_TESTS.keys(), help=\"Platform\")\n    parser.add_argument(\"dataset\")\n    parser.add_argument(\"output_rs\")\n    args = parser.parse_args()\n    print(\"Loading dataset...\")\n    dataset = Dataset.from_tract_outputs(args.dataset, platform=args.platform)\n    print(f\"Loaded {len(dataset)} samples\")\n    passed = False\n    tests = PASS_TESTS[args.platform]\n    best = None\n    best_acc = 0.0\n    trained = 0\n    while not passed:\n        print(f\"[{trained + 1}] Training MLP with {args.hidden_size} units...\")\n        accuracy, _, _, model = train_one_mlp(dataset, args.hidden_size)\n        trained += 1\n        num_passed = 0\n        for mkn, ker in tests:\n            x = dataset.get_classif_features_for(mkn)\n            y = model.predict([x])[0]\n            if dataset.kernels[int(y)][0] == ker:\n                num_passed += 1\n            else:\n                print(f\"for {mkn} predicted: {dataset.kernels[int(y)]}, expected: {ker}\")\n        passed = num_passed == len(tests)\n        if passed and accuracy > best_acc:\n            best_acc = accuracy\n            best = model\n        color = 92 if passed else 91\n        print(\n            f\"\\tAccuracy: {accuracy:.1f}% ... \\033[{color}mPASSED {num_passed} / {len(tests)}\\033[0m\"\n        )\n        passed = passed and trained >= args.num_trainings\n    print(f\"Saving model to {args.output_rs}\")\n    save_as_rust(best, dataset, args.output_rs)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "linalg/matmul-bench/Cargo.toml",
    "content": "[package]\nname = \"matmul-bench\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nedition = \"2024\"\n\n[workspace]\nmembers = []\n\n[dependencies]\ncblas = { version = \"0.3\", optional = true }\naccelerate-src = { version = \"0.3\", optional = true }\nblis-src = { version = \"0.2\", features = [\"static\"], optional = true }\nmatrixmultiply = \"*\"\ntract-data.workspace = true\ntract-linalg.workspace = true\n\n\n[features]\ndefault = []\nblas = [\"cblas\"]\nblis = [\"blis-src\", \"blas\"]\naccelerate = [\"accelerate-src\", \"blas\"]\n\n[build-dependencies]\ncc = \"1.0\"\n\n[dev-dependencies]\ncriterion = \"*\"\n\n[[bench]]\nname = \"matmul\"\nharness = false\n"
  },
  {
    "path": "linalg/matmul-bench/benches/matmul.rs",
    "content": "#![allow(non_snake_case)]\n#[cfg(feature = \"accelerate\")]\nextern crate accelerate_src;\n#[cfg(feature = \"blis\")]\nextern crate blis_src;\n#[cfg(feature = \"blis\")]\nextern crate cblas;\n\nuse criterion::measurement::WallTime;\nuse criterion::*;\nuse tract_data::internal::*;\n\nmacro_rules! b {\n    ($id:ident) => {\n        pub fn $id(crit: &mut BenchmarkGroup<WallTime>, m: usize, k: usize, n: usize) {\n            let a = vec![0f32; m * k];\n            let b = vec![0f32; k * n];\n            let mut c = vec![0f32; m * n];\n            crit.bench_function(stringify!($id), |be| {\n                be.iter(|| matmul_bench::$id(m, k, n, &a, &b, &mut c))\n            });\n        }\n    };\n}\n\nb!(naive);\nb!(ctile_1x1);\nb!(tile_2x2);\nb!(ctile_2x2);\nb!(tile_4x4);\nb!(ctile_4x4);\nb!(cpacked_tile_4x4);\nb!(tile_8x8);\nb!(ctile_8x8);\nb!(cpacked_tile_8x8);\nb!(matrixmultiply);\nb!(cblas);\nb!(tract);\n\npub fn tract_blaslike(\n    crit: &mut BenchmarkGroup<WallTime>,\n    m: usize,\n    k: usize,\n    n: usize,\n    dt: DatumType,\n) {\n    use tract_linalg::frame::mmm::FusedSpec;\n    let a = Tensor::zero_dt(dt, &[m, k]).unwrap();\n    let b = Tensor::zero_dt(dt, &[k, n]).unwrap();\n    let mut c = Tensor::zero_dt(dt, &[m, n]).unwrap();\n\n    unsafe {\n        let mmm = tract_linalg::ops().mmm(dt, dt, dt, Some(m), Some(k), Some(n)).unwrap();\n\n        let c_storage = mmm.c_view(0, 1);\n\n        let mut scratch = mmm.allocate_scratch_space();\n\n        crit.bench_function(&format!(\"tract_blaslike_{:?}\", dt), |be| {\n            let packed_a = mmm.a_pack().pack_tensor(&a, 1, 0).unwrap();\n            let packed_b = mmm.b_pack().pack_tensor(&b, 0, 1).unwrap();\n\n            be.iter(|| {\n                mmm.run_with_scratch_space(\n                    m,\n                    n,\n                    &mut *scratch,\n                    &[\n                        FusedSpec::AddMatMul {\n                            a: packed_a.as_ref(),\n                            b: packed_b.as_ref(),\n                        },\n                        FusedSpec::Store(c_storage.wrap(&mut c.view_mut())),\n                    ],\n                )\n                .unwrap()\n            });\n        });\n    }\n}\n\nfn matmul(c: &mut Criterion, m: usize, k: usize, n: usize) {\n    let mut c = c.benchmark_group(format!(\"{}x{}x{}\", m, k, n));\n    c.throughput(Throughput::Elements((m * k * n) as _));\n    naive(&mut c, m, k, n);\n    ctile_1x1(&mut c, m, k, n);\n    tile_2x2(&mut c, m, k, n);\n    ctile_2x2(&mut c, m, k, n);\n    tile_4x4(&mut c, m, k, n);\n    ctile_4x4(&mut c, m, k, n);\n    cpacked_tile_4x4(&mut c, m, k, n);\n    tile_8x8(&mut c, m, k, n);\n    ctile_8x8(&mut c, m, k, n);\n    cpacked_tile_8x8(&mut c, m, k, n);\n    matrixmultiply(&mut c, m, k, n);\n    cblas(&mut c, m, k, n);\n    tract(&mut c, m, k, n);\n    tract_blaslike(&mut c, m, k, n, f32::datum_type());\n    tract_blaslike(&mut c, m, k, n, f16::datum_type());\n    c.finish();\n}\n\nfn big(c: &mut Criterion) {\n    matmul(c, 512, 512, 512);\n}\n\nfn wavenet(c: &mut Criterion) {\n    matmul(c, 32, 32, 8);\n    matmul(c, 16, 60, 8);\n}\n\nfn asr_15M(c: &mut Criterion) {\n    matmul(c, 768, 200, 24);\n    matmul(c, 768, 2304, 24);\n    matmul(c, 768, 2304, 8);\n    matmul(c, 768, 384, 1);\n}\n\nfn inception(c: &mut Criterion) {\n    matmul(c, 64, 288, 21609);\n}\n\nfn whisper_base(c: &mut Criterion) {\n    matmul(c, 512, 512, 1500);\n}\n\ncriterion_group!(benches, big, wavenet, asr_15M, inception, whisper_base);\ncriterion_main!(benches);\n"
  },
  {
    "path": "linalg/matmul-bench/build.rs",
    "content": "fn main() {\n    let mut cc = cc::Build::new();\n    cc\n        .file(\"c/tile_1x1.c\")\n        .file(\"c/tile_2x2.c\")\n        .file(\"c/tile_4x4.c\")\n        .file(\"c/packed_tile_4x4.c\")\n        .file(\"c/tile_8x8.c\")\n        .file(\"c/packed_tile_8x8.c\");\n    if std::env::var(\"TARGET\").unwrap().starts_with(\"aarch64\") {\n        cc.flag(\"-mtune=cortex-a53\");\n    } else {\n        cc.flag(\"-mtune=haswell\");\n    }\n    cc.flag(\"-funsafe-math-optimizations\").compile(\"libmatmulbench\");\n}\n"
  },
  {
    "path": "linalg/matmul-bench/c/packed_tile_4x4.c",
    "content": "\n#include <stddef.h>\n\nvoid c_packed_tile_4x4(size_t m, size_t k, size_t n, float *a, float *b, float *c) {\n    for(size_t row = 0 ; row < m / 4 ; row++) {\n        for(size_t col = 0 ; col < n / 4 ; col++) {\n            float  sum00 = 0.0;\n            float  sum01 = 0.0;\n            float  sum02 = 0.0;\n            float  sum03 = 0.0;\n            float  sum10 = 0.0;\n            float  sum11 = 0.0;\n            float  sum12 = 0.0;\n            float  sum13 = 0.0;\n            float  sum20 = 0.0;\n            float  sum21 = 0.0;\n            float  sum22 = 0.0;\n            float  sum23 = 0.0;\n            float  sum30 = 0.0;\n            float  sum31 = 0.0;\n            float  sum32 = 0.0;\n            float  sum33 = 0.0;\n            float *pa = a + row * k * 4;\n            float *pb = b + col * k * 4;\n            for(size_t i = 0; i < k ; i++) {\n                float a0 = pa[0];\n                float a1 = pa[1];\n                float a2 = pa[2];\n                float a3 = pa[3];\n                float b0 = pb[0];\n                float b1 = pb[1];\n                float b2 = pb[2];\n                float b3 = pb[3];\n                pa += 4;\n                pb += 4;\n                sum00 += a0 * b0;\n                sum01 += a0 * b1;\n                sum02 += a0 * b2;\n                sum03 += a0 * b3;\n                sum10 += a1 * b0;\n                sum11 += a1 * b1;\n                sum12 += a1 * b2;\n                sum13 += a1 * b3;\n                sum20 += a2 * b0;\n                sum21 += a2 * b1;\n                sum22 += a2 * b2;\n                sum23 += a2 * b3;\n                sum30 += a3 * b0;\n                sum31 += a3 * b1;\n                sum32 += a3 * b2;\n                sum33 += a3 * b3;\n            }\n            c[(row * 4 + 0) * n + col * 4] = sum00;\n            c[(row * 4 + 0) * n + col * 4 + 1] = sum01;\n            c[(row * 4 + 0) * n + col * 4 + 2] = sum02;\n            c[(row * 4 + 0) * n + col * 4 + 3] = sum03;\n            c[(row * 4 + 1) * n + col * 4] = sum10;\n            c[(row * 4 + 1) * n + col * 4 + 1] = sum11;\n            c[(row * 4 + 1) * n + col * 4 + 2] = sum12;\n            c[(row * 4 + 1) * n + col * 4 + 3] = sum13;\n            c[(row * 4 + 2) * n + col * 4] = sum20;\n            c[(row * 4 + 2) * n + col * 4 + 1] = sum21;\n            c[(row * 4 + 2) * n + col * 4 + 2] = sum22;\n            c[(row * 4 + 2) * n + col * 4 + 3] = sum23;\n            c[(row * 4 + 3) * n + col * 4] = sum30;\n            c[(row * 4 + 3) * n + col * 4 + 1] = sum31;\n            c[(row * 4 + 3) * n + col * 4 + 2] = sum32;\n            c[(row * 4 + 3) * n + col * 4 + 3] = sum33;\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/matmul-bench/c/packed_tile_8x8.c",
    "content": "\n\n#include <stddef.h>\n\nvoid c_packed_tile_8x8(size_t m, size_t k, size_t n, float *a, float *b, float *c) {\n    for(size_t row = 0 ; row < m / 8 ; row++) {\n        for(size_t col = 0 ; col < n / 8 ; col++) {\n            float sum00 = 0.0;\n            float sum01 = 0.0;\n            float sum02 = 0.0;\n            float sum03 = 0.0;\n            float sum04 = 0.0;\n            float sum05 = 0.0;\n            float sum06 = 0.0;\n            float sum07 = 0.0;\n            float sum10 = 0.0;\n            float sum11 = 0.0;\n            float sum12 = 0.0;\n            float sum13 = 0.0;\n            float sum14 = 0.0;\n            float sum15 = 0.0;\n            float sum16 = 0.0;\n            float sum17 = 0.0;\n            float sum20 = 0.0;\n            float sum21 = 0.0;\n            float sum22 = 0.0;\n            float sum23 = 0.0;\n            float sum24 = 0.0;\n            float sum25 = 0.0;\n            float sum26 = 0.0;\n            float sum27 = 0.0;\n            float sum30 = 0.0;\n            float sum31 = 0.0;\n            float sum32 = 0.0;\n            float sum33 = 0.0;\n            float sum34 = 0.0;\n            float sum35 = 0.0;\n            float sum36 = 0.0;\n            float sum37 = 0.0;\n            float sum40 = 0.0;\n            float sum41 = 0.0;\n            float sum42 = 0.0;\n            float sum43 = 0.0;\n            float sum44 = 0.0;\n            float sum45 = 0.0;\n            float sum46 = 0.0;\n            float sum47 = 0.0;\n            float sum50 = 0.0;\n            float sum51 = 0.0;\n            float sum52 = 0.0;\n            float sum53 = 0.0;\n            float sum54 = 0.0;\n            float sum55 = 0.0;\n            float sum56 = 0.0;\n            float sum57 = 0.0;\n            float sum60 = 0.0;\n            float sum61 = 0.0;\n            float sum62 = 0.0;\n            float sum63 = 0.0;\n            float sum64 = 0.0;\n            float sum65 = 0.0;\n            float sum66 = 0.0;\n            float sum67 = 0.0;\n            float sum70 = 0.0;\n            float sum71 = 0.0;\n            float sum72 = 0.0;\n            float sum73 = 0.0;\n            float sum74 = 0.0;\n            float sum75 = 0.0;\n            float sum76 = 0.0;\n            float sum77 = 0.0;\n            float *pa = a + row * k * 8;\n            float *pb = b + col * k * 8;\n            for(size_t i = 0 ; i < k ; i++) {\n                float a0 = a[0];\n                float a1 = a[1];\n                float a2 = a[2];\n                float a3 = a[3];\n                float a4 = a[4];\n                float a5 = a[5];\n                float a6 = a[6];\n                float a7 = a[7];\n                float b0 = b[0];\n                float b1 = b[1];\n                float b2 = b[2];\n                float b3 = b[3];\n                float b4 = b[4];\n                float b5 = b[5];\n                float b6 = b[6];\n                float b7 = b[7];\n                pa += 8;\n                pb += 8;\n                sum00 += a0 * b0;\n                sum01 += a0 * b1;\n                sum02 += a0 * b2;\n                sum03 += a0 * b3;\n                sum04 += a0 * b4;\n                sum05 += a0 * b5;\n                sum06 += a0 * b6;\n                sum07 += a0 * b7;\n                sum10 += a1 * b0;\n                sum11 += a1 * b1;\n                sum12 += a1 * b2;\n                sum13 += a1 * b3;\n                sum14 += a1 * b4;\n                sum15 += a1 * b5;\n                sum16 += a1 * b6;\n                sum17 += a1 * b7;\n                sum20 += a2 * b0;\n                sum21 += a2 * b1;\n                sum22 += a2 * b2;\n                sum23 += a2 * b3;\n                sum24 += a2 * b4;\n                sum25 += a2 * b5;\n                sum26 += a2 * b6;\n                sum27 += a2 * b7;\n                sum30 += a3 * b0;\n                sum31 += a3 * b1;\n                sum32 += a3 * b2;\n                sum33 += a3 * b3;\n                sum34 += a3 * b4;\n                sum35 += a3 * b5;\n                sum36 += a3 * b6;\n                sum37 += a3 * b7;\n                sum40 += a4 * b0;\n                sum41 += a4 * b1;\n                sum42 += a4 * b2;\n                sum43 += a4 * b3;\n                sum44 += a4 * b4;\n                sum45 += a4 * b5;\n                sum46 += a4 * b6;\n                sum47 += a4 * b7;\n                sum50 += a5 * b0;\n                sum51 += a5 * b1;\n                sum52 += a5 * b2;\n                sum53 += a5 * b3;\n                sum54 += a5 * b4;\n                sum55 += a5 * b5;\n                sum56 += a5 * b6;\n                sum57 += a5 * b7;\n                sum60 += a6 * b0;\n                sum61 += a6 * b1;\n                sum62 += a6 * b2;\n                sum63 += a6 * b3;\n                sum64 += a6 * b4;\n                sum65 += a6 * b5;\n                sum66 += a6 * b6;\n                sum67 += a6 * b7;\n                sum70 += a7 * b0;\n                sum71 += a7 * b1;\n                sum72 += a7 * b2;\n                sum73 += a7 * b3;\n                sum74 += a7 * b4;\n                sum75 += a7 * b5;\n                sum76 += a7 * b6;\n                sum77 += a7 * b7;\n            }\n            c[(row * 8 + 0) * n + col * 8] = sum00;\n            c[(row * 8 + 0) * n + col * 8 + 1] = sum01;\n            c[(row * 8 + 0) * n + col * 8 + 2] = sum02;\n            c[(row * 8 + 0) * n + col * 8 + 3] = sum03;\n            c[(row * 8 + 0) * n + col * 8 + 4] = sum04;\n            c[(row * 8 + 0) * n + col * 8 + 5] = sum05;\n            c[(row * 8 + 0) * n + col * 8 + 6] = sum06;\n            c[(row * 8 + 0) * n + col * 8 + 7] = sum07;\n            c[(row * 8 + 1) * n + col * 8] = sum10;\n            c[(row * 8 + 1) * n + col * 8 + 1] = sum11;\n            c[(row * 8 + 1) * n + col * 8 + 2] = sum12;\n            c[(row * 8 + 1) * n + col * 8 + 3] = sum13;\n            c[(row * 8 + 1) * n + col * 8 + 4] = sum14;\n            c[(row * 8 + 1) * n + col * 8 + 5] = sum15;\n            c[(row * 8 + 1) * n + col * 8 + 6] = sum16;\n            c[(row * 8 + 1) * n + col * 8 + 7] = sum17;\n            c[(row * 8 + 2) * n + col * 8] = sum20;\n            c[(row * 8 + 2) * n + col * 8 + 1] = sum21;\n            c[(row * 8 + 2) * n + col * 8 + 2] = sum22;\n            c[(row * 8 + 2) * n + col * 8 + 3] = sum23;\n            c[(row * 8 + 2) * n + col * 8 + 4] = sum24;\n            c[(row * 8 + 2) * n + col * 8 + 5] = sum25;\n            c[(row * 8 + 2) * n + col * 8 + 6] = sum26;\n            c[(row * 8 + 2) * n + col * 8 + 7] = sum27;\n            c[(row * 8 + 3) * n + col * 8] = sum30;\n            c[(row * 8 + 3) * n + col * 8 + 1] = sum31;\n            c[(row * 8 + 3) * n + col * 8 + 2] = sum32;\n            c[(row * 8 + 3) * n + col * 8 + 3] = sum33;\n            c[(row * 8 + 3) * n + col * 8 + 4] = sum34;\n            c[(row * 8 + 3) * n + col * 8 + 5] = sum35;\n            c[(row * 8 + 3) * n + col * 8 + 6] = sum36;\n            c[(row * 8 + 3) * n + col * 8 + 7] = sum37;\n            c[(row * 8 + 4) * n + col * 8] = sum40;\n            c[(row * 8 + 4) * n + col * 8 + 1] = sum41;\n            c[(row * 8 + 4) * n + col * 8 + 2] = sum42;\n            c[(row * 8 + 4) * n + col * 8 + 3] = sum43;\n            c[(row * 8 + 4) * n + col * 8 + 4] = sum44;\n            c[(row * 8 + 4) * n + col * 8 + 5] = sum45;\n            c[(row * 8 + 4) * n + col * 8 + 6] = sum46;\n            c[(row * 8 + 4) * n + col * 8 + 7] = sum47;\n            c[(row * 8 + 5) * n + col * 8] = sum50;\n            c[(row * 8 + 5) * n + col * 8 + 1] = sum51;\n            c[(row * 8 + 5) * n + col * 8 + 2] = sum52;\n            c[(row * 8 + 5) * n + col * 8 + 3] = sum53;\n            c[(row * 8 + 5) * n + col * 8 + 4] = sum54;\n            c[(row * 8 + 5) * n + col * 8 + 5] = sum55;\n            c[(row * 8 + 5) * n + col * 8 + 6] = sum56;\n            c[(row * 8 + 5) * n + col * 8 + 7] = sum57;\n            c[(row * 8 + 6) * n + col * 8] = sum60;\n            c[(row * 8 + 6) * n + col * 8 + 1] = sum61;\n            c[(row * 8 + 6) * n + col * 8 + 2] = sum62;\n            c[(row * 8 + 6) * n + col * 8 + 3] = sum63;\n            c[(row * 8 + 6) * n + col * 8 + 4] = sum64;\n            c[(row * 8 + 6) * n + col * 8 + 5] = sum65;\n            c[(row * 8 + 6) * n + col * 8 + 6] = sum66;\n            c[(row * 8 + 6) * n + col * 8 + 7] = sum67;\n            c[(row * 8 + 7) * n + col * 8] = sum70;\n            c[(row * 8 + 7) * n + col * 8 + 1] = sum71;\n            c[(row * 8 + 7) * n + col * 8 + 2] = sum72;\n            c[(row * 8 + 7) * n + col * 8 + 3] = sum73;\n            c[(row * 8 + 7) * n + col * 8 + 4] = sum74;\n            c[(row * 8 + 7) * n + col * 8 + 5] = sum75;\n            c[(row * 8 + 7) * n + col * 8 + 6] = sum76;\n            c[(row * 8 + 7) * n + col * 8 + 7] = sum77;\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/matmul-bench/c/tile_1x1.c",
    "content": "\n#include <stddef.h>\n\nvoid c_tile_1x1(size_t m, size_t k, size_t n, float *a, float *b, float *c) {\n    for(size_t row = 0 ; row < m ; row++) {\n        for(size_t col = 0 ; col < n ; col++) {\n            float  sum00 = 0.0;\n            for(size_t i = 0; i < k ; i++) {\n                float a0 = a[row * k + i];\n                float b0 = b[i * n + col];\n                sum00 += a0 * b0;\n            }\n            c[row * n + col] = sum00;\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/matmul-bench/c/tile_2x2.c",
    "content": "\n#include <stddef.h>\n\nvoid c_tile_2x2(size_t m, size_t k, size_t n, float *a, float *b, float *c) {\n    for(size_t row = 0 ; row < m / 2 ; row++) {\n        for(size_t col = 0 ; col < n / 2 ; col++) {\n            float  sum00 = 0.0;\n            float  sum01 = 0.0;\n            float  sum10 = 0.0;\n            float  sum11 = 0.0;\n            for(size_t i = 0; i < k ; i++) {\n                float a0 = a[2 * row * k + i];\n                float a1 = a[(2 * row + 1) * k + i];\n                float b0 = b[i * n + 2 * col];\n                float b1 = b[i * n + 2 * col + 1];\n                sum00 += a0 * b0;\n                sum01 += a0 * b1;\n                sum10 += a1 * b0;\n                sum11 += a1 * b1;\n            }\n            c[(2 * row + 0) * n + 2 * col] = sum00;\n            c[(2 * row + 0) * n + 2 * col + 1] = sum01;\n            c[(2 * row + 1) * n + 2 * col] = sum10;\n            c[(2 * row + 1) * n + 2 * col + 1] = sum11;\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/matmul-bench/c/tile_4x4.c",
    "content": "\n#include <stddef.h>\n\nvoid c_tile_4x4(size_t m, size_t k, size_t n, float *a, float *b, float *c) {\n    for(size_t row = 0 ; row < m / 4 ; row++) {\n        for(size_t col = 0 ; col < n / 4 ; col++) {\n            float  sum00 = 0.0;\n            float  sum01 = 0.0;\n            float  sum02 = 0.0;\n            float  sum03 = 0.0;\n            float  sum10 = 0.0;\n            float  sum11 = 0.0;\n            float  sum12 = 0.0;\n            float  sum13 = 0.0;\n            float  sum20 = 0.0;\n            float  sum21 = 0.0;\n            float  sum22 = 0.0;\n            float  sum23 = 0.0;\n            float  sum30 = 0.0;\n            float  sum31 = 0.0;\n            float  sum32 = 0.0;\n            float  sum33 = 0.0;\n            for(size_t i = 0; i < k ; i++) {\n                float a0 = a[4 * row * k + i];\n                float a1 = a[(4 * row + 1) * k + i];\n                float a2 = a[(4 * row + 2) * k + i];\n                float a3 = a[(4 * row + 3) * k + i];\n                float b0 = b[i * n + 4 * col];\n                float b1 = b[i * n + 4 * col + 1];\n                float b2 = b[i * n + 4 * col + 2];\n                float b3 = b[i * n + 4 * col + 3];\n                sum00 += a0 * b0;\n                sum01 += a0 * b1;\n                sum02 += a0 * b2;\n                sum03 += a0 * b3;\n                sum10 += a1 * b0;\n                sum11 += a1 * b1;\n                sum12 += a1 * b2;\n                sum13 += a1 * b3;\n                sum20 += a2 * b0;\n                sum21 += a2 * b1;\n                sum22 += a2 * b2;\n                sum23 += a2 * b3;\n                sum30 += a3 * b0;\n                sum31 += a3 * b1;\n                sum32 += a3 * b2;\n                sum33 += a3 * b3;\n            }\n            c[(4 * row + 0) * n + 4 * col] = sum00;\n            c[(4 * row + 0) * n + 4 * col + 1] = sum01;\n            c[(4 * row + 0) * n + 4 * col + 2] = sum02;\n            c[(4 * row + 0) * n + 4 * col + 3] = sum03;\n            c[(4 * row + 1) * n + 4 * col] = sum10;\n            c[(4 * row + 1) * n + 4 * col + 1] = sum11;\n            c[(4 * row + 1) * n + 4 * col + 2] = sum12;\n            c[(4 * row + 1) * n + 4 * col + 3] = sum13;\n            c[(4 * row + 2) * n + 4 * col] = sum20;\n            c[(4 * row + 2) * n + 4 * col + 1] = sum21;\n            c[(4 * row + 2) * n + 4 * col + 2] = sum22;\n            c[(4 * row + 2) * n + 4 * col + 3] = sum23;\n            c[(4 * row + 3) * n + 4 * col] = sum30;\n            c[(4 * row + 3) * n + 4 * col + 1] = sum31;\n            c[(4 * row + 3) * n + 4 * col + 2] = sum32;\n            c[(4 * row + 3) * n + 4 * col + 3] = sum33;\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/matmul-bench/c/tile_8x8.c",
    "content": "\n\n#include <stddef.h>\n\nvoid c_tile_8x8(size_t m, size_t k, size_t n, float *a, float *b, float *c) {\n    for(size_t row = 0 ; row < m / 8 ; row++) {\n        for(size_t col = 0 ; col < n / 8 ; col++) {\n            float sum00 = 0.0;\n            float sum01 = 0.0;\n            float sum02 = 0.0;\n            float sum03 = 0.0;\n            float sum04 = 0.0;\n            float sum05 = 0.0;\n            float sum06 = 0.0;\n            float sum07 = 0.0;\n            float sum10 = 0.0;\n            float sum11 = 0.0;\n            float sum12 = 0.0;\n            float sum13 = 0.0;\n            float sum14 = 0.0;\n            float sum15 = 0.0;\n            float sum16 = 0.0;\n            float sum17 = 0.0;\n            float sum20 = 0.0;\n            float sum21 = 0.0;\n            float sum22 = 0.0;\n            float sum23 = 0.0;\n            float sum24 = 0.0;\n            float sum25 = 0.0;\n            float sum26 = 0.0;\n            float sum27 = 0.0;\n            float sum30 = 0.0;\n            float sum31 = 0.0;\n            float sum32 = 0.0;\n            float sum33 = 0.0;\n            float sum34 = 0.0;\n            float sum35 = 0.0;\n            float sum36 = 0.0;\n            float sum37 = 0.0;\n            float sum40 = 0.0;\n            float sum41 = 0.0;\n            float sum42 = 0.0;\n            float sum43 = 0.0;\n            float sum44 = 0.0;\n            float sum45 = 0.0;\n            float sum46 = 0.0;\n            float sum47 = 0.0;\n            float sum50 = 0.0;\n            float sum51 = 0.0;\n            float sum52 = 0.0;\n            float sum53 = 0.0;\n            float sum54 = 0.0;\n            float sum55 = 0.0;\n            float sum56 = 0.0;\n            float sum57 = 0.0;\n            float sum60 = 0.0;\n            float sum61 = 0.0;\n            float sum62 = 0.0;\n            float sum63 = 0.0;\n            float sum64 = 0.0;\n            float sum65 = 0.0;\n            float sum66 = 0.0;\n            float sum67 = 0.0;\n            float sum70 = 0.0;\n            float sum71 = 0.0;\n            float sum72 = 0.0;\n            float sum73 = 0.0;\n            float sum74 = 0.0;\n            float sum75 = 0.0;\n            float sum76 = 0.0;\n            float sum77 = 0.0;\n            for(size_t i = 0 ; i < k ; i++) {\n                float a0 = a[8 * row * k + i];\n                float a1 = a[(8 * row + 1) * k + i];\n                float a2 = a[(8 * row + 2) * k + i];\n                float a3 = a[(8 * row + 3) * k + i];\n                float a4 = a[(8 * row + 4) * k + i];\n                float a5 = a[(8 * row + 5) * k + i];\n                float a6 = a[(8 * row + 6) * k + i];\n                float a7 = a[(8 * row + 7) * k + i];\n                float b0 = b[i * n + 8 * col];\n                float b1 = b[i * n + 8 * col + 1];\n                float b2 = b[i * n + 8 * col + 2];\n                float b3 = b[i * n + 8 * col + 3];\n                float b4 = b[i * n + 8 * col + 4];\n                float b5 = b[i * n + 8 * col + 5];\n                float b6 = b[i * n + 8 * col + 6];\n                float b7 = b[i * n + 8 * col + 7];\n                sum00 += a0 * b0;\n                sum01 += a0 * b1;\n                sum02 += a0 * b2;\n                sum03 += a0 * b3;\n                sum04 += a0 * b4;\n                sum05 += a0 * b5;\n                sum06 += a0 * b6;\n                sum07 += a0 * b7;\n                sum10 += a1 * b0;\n                sum11 += a1 * b1;\n                sum12 += a1 * b2;\n                sum13 += a1 * b3;\n                sum14 += a1 * b4;\n                sum15 += a1 * b5;\n                sum16 += a1 * b6;\n                sum17 += a1 * b7;\n                sum20 += a2 * b0;\n                sum21 += a2 * b1;\n                sum22 += a2 * b2;\n                sum23 += a2 * b3;\n                sum24 += a2 * b4;\n                sum25 += a2 * b5;\n                sum26 += a2 * b6;\n                sum27 += a2 * b7;\n                sum30 += a3 * b0;\n                sum31 += a3 * b1;\n                sum32 += a3 * b2;\n                sum33 += a3 * b3;\n                sum34 += a3 * b4;\n                sum35 += a3 * b5;\n                sum36 += a3 * b6;\n                sum37 += a3 * b7;\n                sum40 += a4 * b0;\n                sum41 += a4 * b1;\n                sum42 += a4 * b2;\n                sum43 += a4 * b3;\n                sum44 += a4 * b4;\n                sum45 += a4 * b5;\n                sum46 += a4 * b6;\n                sum47 += a4 * b7;\n                sum50 += a5 * b0;\n                sum51 += a5 * b1;\n                sum52 += a5 * b2;\n                sum53 += a5 * b3;\n                sum54 += a5 * b4;\n                sum55 += a5 * b5;\n                sum56 += a5 * b6;\n                sum57 += a5 * b7;\n                sum60 += a6 * b0;\n                sum61 += a6 * b1;\n                sum62 += a6 * b2;\n                sum63 += a6 * b3;\n                sum64 += a6 * b4;\n                sum65 += a6 * b5;\n                sum66 += a6 * b6;\n                sum67 += a6 * b7;\n                sum70 += a7 * b0;\n                sum71 += a7 * b1;\n                sum72 += a7 * b2;\n                sum73 += a7 * b3;\n                sum74 += a7 * b4;\n                sum75 += a7 * b5;\n                sum76 += a7 * b6;\n                sum77 += a7 * b7;\n            }\n            c[(8 * row + 0) * n + 8 * col] = sum00;\n            c[(8 * row + 0) * n + 8 * col + 1] = sum01;\n            c[(8 * row + 0) * n + 8 * col + 2] = sum02;\n            c[(8 * row + 0) * n + 8 * col + 3] = sum03;\n            c[(8 * row + 0) * n + 8 * col + 4] = sum04;\n            c[(8 * row + 0) * n + 8 * col + 5] = sum05;\n            c[(8 * row + 0) * n + 8 * col + 6] = sum06;\n            c[(8 * row + 0) * n + 8 * col + 7] = sum07;\n            c[(8 * row + 1) * n + 8 * col] = sum10;\n            c[(8 * row + 1) * n + 8 * col + 1] = sum11;\n            c[(8 * row + 1) * n + 8 * col + 2] = sum12;\n            c[(8 * row + 1) * n + 8 * col + 3] = sum13;\n            c[(8 * row + 1) * n + 8 * col + 4] = sum14;\n            c[(8 * row + 1) * n + 8 * col + 5] = sum15;\n            c[(8 * row + 1) * n + 8 * col + 6] = sum16;\n            c[(8 * row + 1) * n + 8 * col + 7] = sum17;\n            c[(8 * row + 2) * n + 8 * col] = sum20;\n            c[(8 * row + 2) * n + 8 * col + 1] = sum21;\n            c[(8 * row + 2) * n + 8 * col + 2] = sum22;\n            c[(8 * row + 2) * n + 8 * col + 3] = sum23;\n            c[(8 * row + 2) * n + 8 * col + 4] = sum24;\n            c[(8 * row + 2) * n + 8 * col + 5] = sum25;\n            c[(8 * row + 2) * n + 8 * col + 6] = sum26;\n            c[(8 * row + 2) * n + 8 * col + 7] = sum27;\n            c[(8 * row + 3) * n + 8 * col] = sum30;\n            c[(8 * row + 3) * n + 8 * col + 1] = sum31;\n            c[(8 * row + 3) * n + 8 * col + 2] = sum32;\n            c[(8 * row + 3) * n + 8 * col + 3] = sum33;\n            c[(8 * row + 3) * n + 8 * col + 4] = sum34;\n            c[(8 * row + 3) * n + 8 * col + 5] = sum35;\n            c[(8 * row + 3) * n + 8 * col + 6] = sum36;\n            c[(8 * row + 3) * n + 8 * col + 7] = sum37;\n            c[(8 * row + 4) * n + 8 * col] = sum40;\n            c[(8 * row + 4) * n + 8 * col + 1] = sum41;\n            c[(8 * row + 4) * n + 8 * col + 2] = sum42;\n            c[(8 * row + 4) * n + 8 * col + 3] = sum43;\n            c[(8 * row + 4) * n + 8 * col + 4] = sum44;\n            c[(8 * row + 4) * n + 8 * col + 5] = sum45;\n            c[(8 * row + 4) * n + 8 * col + 6] = sum46;\n            c[(8 * row + 4) * n + 8 * col + 7] = sum47;\n            c[(8 * row + 5) * n + 8 * col] = sum50;\n            c[(8 * row + 5) * n + 8 * col + 1] = sum51;\n            c[(8 * row + 5) * n + 8 * col + 2] = sum52;\n            c[(8 * row + 5) * n + 8 * col + 3] = sum53;\n            c[(8 * row + 5) * n + 8 * col + 4] = sum54;\n            c[(8 * row + 5) * n + 8 * col + 5] = sum55;\n            c[(8 * row + 5) * n + 8 * col + 6] = sum56;\n            c[(8 * row + 5) * n + 8 * col + 7] = sum57;\n            c[(8 * row + 6) * n + 8 * col] = sum60;\n            c[(8 * row + 6) * n + 8 * col + 1] = sum61;\n            c[(8 * row + 6) * n + 8 * col + 2] = sum62;\n            c[(8 * row + 6) * n + 8 * col + 3] = sum63;\n            c[(8 * row + 6) * n + 8 * col + 4] = sum64;\n            c[(8 * row + 6) * n + 8 * col + 5] = sum65;\n            c[(8 * row + 6) * n + 8 * col + 6] = sum66;\n            c[(8 * row + 6) * n + 8 * col + 7] = sum67;\n            c[(8 * row + 7) * n + 8 * col] = sum70;\n            c[(8 * row + 7) * n + 8 * col + 1] = sum71;\n            c[(8 * row + 7) * n + 8 * col + 2] = sum72;\n            c[(8 * row + 7) * n + 8 * col + 3] = sum73;\n            c[(8 * row + 7) * n + 8 * col + 4] = sum74;\n            c[(8 * row + 7) * n + 8 * col + 5] = sum75;\n            c[(8 * row + 7) * n + 8 * col + 6] = sum76;\n            c[(8 * row + 7) * n + 8 * col + 7] = sum77;\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/matmul-bench/src/lib.rs",
    "content": "#![allow(non_snake_case)]\n#[cfg(feature = \"accelerate\")]\nextern crate accelerate_src;\n#[cfg(feature = \"blis\")]\nextern crate blis_src;\n#[cfg(feature = \"blis\")]\nextern crate cblas;\n\npub fn naive(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    for row in 0..m {\n        for col in 0..n {\n            let mut sum = 0.0;\n            for i in 0..k {\n                sum += a[row * k + i] * b[i * n + col];\n            }\n            c[row * n + col] = sum;\n        }\n    }\n}\n\npub fn tile_2x2(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    for row in 0..m / 2 {\n        for col in 0..n / 2 {\n            let mut sum00 = 0.0;\n            let mut sum01 = 0.0;\n            let mut sum10 = 0.0;\n            let mut sum11 = 0.0;\n            for i in 0..k {\n                let a0 = a[2 * row * k + i];\n                let a1 = a[(2 * row + 1) * k + i];\n                let b0 = b[i * n + 2 * col];\n                let b1 = b[i * n + 2 * col + 1];\n                sum00 += a0 * b0;\n                sum01 += a0 * b1;\n                sum10 += a1 * b0;\n                sum11 += a1 * b1;\n            }\n            c[2 * row * n + 2 * col] = sum00;\n            c[2 * row * n + 2 * col + 1] = sum01;\n            c[(2 * row + 1) * n + 2 * col] = sum10;\n            c[(2 * row + 1) * n + 2 * col + 1] = sum11;\n        }\n    }\n}\n\npub fn tile_4x4(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    for row in 0..m / 4 {\n        for col in 0..n / 4 {\n            let mut sum00 = 0.0;\n            let mut sum01 = 0.0;\n            let mut sum02 = 0.0;\n            let mut sum03 = 0.0;\n            let mut sum10 = 0.0;\n            let mut sum11 = 0.0;\n            let mut sum12 = 0.0;\n            let mut sum13 = 0.0;\n            let mut sum20 = 0.0;\n            let mut sum21 = 0.0;\n            let mut sum22 = 0.0;\n            let mut sum23 = 0.0;\n            let mut sum30 = 0.0;\n            let mut sum31 = 0.0;\n            let mut sum32 = 0.0;\n            let mut sum33 = 0.0;\n            for i in 0..k {\n                let a0 = a[4 * row * k + i];\n                let a1 = a[(4 * row + 1) * k + i];\n                let a2 = a[(4 * row + 2) * k + i];\n                let a3 = a[(4 * row + 3) * k + i];\n                let b0 = b[i * n + 4 * col];\n                let b1 = b[i * n + 4 * col + 1];\n                let b2 = b[i * n + 4 * col + 2];\n                let b3 = b[i * n + 4 * col + 3];\n                sum00 += a0 * b0;\n                sum01 += a0 * b1;\n                sum02 += a0 * b2;\n                sum03 += a0 * b3;\n                sum10 += a1 * b0;\n                sum11 += a1 * b1;\n                sum12 += a1 * b2;\n                sum13 += a1 * b3;\n                sum20 += a2 * b0;\n                sum21 += a2 * b1;\n                sum22 += a2 * b2;\n                sum23 += a2 * b3;\n                sum30 += a3 * b0;\n                sum31 += a3 * b1;\n                sum32 += a3 * b2;\n                sum33 += a3 * b3;\n            }\n            c[(4 * row + 0) * n + 4 * col] = sum00;\n            c[(4 * row + 0) * n + 4 * col + 1] = sum01;\n            c[(4 * row + 0) * n + 4 * col + 2] = sum02;\n            c[(4 * row + 0) * n + 4 * col + 3] = sum03;\n            c[(4 * row + 1) * n + 4 * col] = sum10;\n            c[(4 * row + 1) * n + 4 * col + 1] = sum11;\n            c[(4 * row + 1) * n + 4 * col + 2] = sum12;\n            c[(4 * row + 1) * n + 4 * col + 3] = sum13;\n            c[(4 * row + 2) * n + 4 * col] = sum20;\n            c[(4 * row + 2) * n + 4 * col + 1] = sum21;\n            c[(4 * row + 2) * n + 4 * col + 2] = sum22;\n            c[(4 * row + 2) * n + 4 * col + 3] = sum23;\n            c[(4 * row + 3) * n + 4 * col] = sum30;\n            c[(4 * row + 3) * n + 4 * col + 1] = sum31;\n            c[(4 * row + 3) * n + 4 * col + 2] = sum32;\n            c[(4 * row + 3) * n + 4 * col + 3] = sum33;\n        }\n    }\n}\n\npub fn tile_8x8(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    for row in 0..m / 8 {\n        for col in 0..n / 8 {\n            let mut sum00 = 0.0;\n            let mut sum01 = 0.0;\n            let mut sum02 = 0.0;\n            let mut sum03 = 0.0;\n            let mut sum04 = 0.0;\n            let mut sum05 = 0.0;\n            let mut sum06 = 0.0;\n            let mut sum07 = 0.0;\n            let mut sum10 = 0.0;\n            let mut sum11 = 0.0;\n            let mut sum12 = 0.0;\n            let mut sum13 = 0.0;\n            let mut sum14 = 0.0;\n            let mut sum15 = 0.0;\n            let mut sum16 = 0.0;\n            let mut sum17 = 0.0;\n            let mut sum20 = 0.0;\n            let mut sum21 = 0.0;\n            let mut sum22 = 0.0;\n            let mut sum23 = 0.0;\n            let mut sum24 = 0.0;\n            let mut sum25 = 0.0;\n            let mut sum26 = 0.0;\n            let mut sum27 = 0.0;\n            let mut sum30 = 0.0;\n            let mut sum31 = 0.0;\n            let mut sum32 = 0.0;\n            let mut sum33 = 0.0;\n            let mut sum34 = 0.0;\n            let mut sum35 = 0.0;\n            let mut sum36 = 0.0;\n            let mut sum37 = 0.0;\n            let mut sum40 = 0.0;\n            let mut sum41 = 0.0;\n            let mut sum42 = 0.0;\n            let mut sum43 = 0.0;\n            let mut sum44 = 0.0;\n            let mut sum45 = 0.0;\n            let mut sum46 = 0.0;\n            let mut sum47 = 0.0;\n            let mut sum50 = 0.0;\n            let mut sum51 = 0.0;\n            let mut sum52 = 0.0;\n            let mut sum53 = 0.0;\n            let mut sum54 = 0.0;\n            let mut sum55 = 0.0;\n            let mut sum56 = 0.0;\n            let mut sum57 = 0.0;\n            let mut sum60 = 0.0;\n            let mut sum61 = 0.0;\n            let mut sum62 = 0.0;\n            let mut sum63 = 0.0;\n            let mut sum64 = 0.0;\n            let mut sum65 = 0.0;\n            let mut sum66 = 0.0;\n            let mut sum67 = 0.0;\n            let mut sum70 = 0.0;\n            let mut sum71 = 0.0;\n            let mut sum72 = 0.0;\n            let mut sum73 = 0.0;\n            let mut sum74 = 0.0;\n            let mut sum75 = 0.0;\n            let mut sum76 = 0.0;\n            let mut sum77 = 0.0;\n            for i in 0..k {\n                let a0 = a[8 * row * k + i];\n                let a1 = a[(8 * row + 1) * k + i];\n                let a2 = a[(8 * row + 2) * k + i];\n                let a3 = a[(8 * row + 3) * k + i];\n                let a4 = a[(8 * row + 4) * k + i];\n                let a5 = a[(8 * row + 5) * k + i];\n                let a6 = a[(8 * row + 6) * k + i];\n                let a7 = a[(8 * row + 7) * k + i];\n                let b0 = b[i * n + 8 * col];\n                let b1 = b[i * n + 8 * col + 1];\n                let b2 = b[i * n + 8 * col + 2];\n                let b3 = b[i * n + 8 * col + 3];\n                let b4 = b[i * n + 8 * col + 4];\n                let b5 = b[i * n + 8 * col + 5];\n                let b6 = b[i * n + 8 * col + 6];\n                let b7 = b[i * n + 8 * col + 7];\n                sum00 += a0 * b0;\n                sum01 += a0 * b1;\n                sum02 += a0 * b2;\n                sum03 += a0 * b3;\n                sum04 += a0 * b4;\n                sum05 += a0 * b5;\n                sum06 += a0 * b6;\n                sum07 += a0 * b7;\n                sum10 += a1 * b0;\n                sum11 += a1 * b1;\n                sum12 += a1 * b2;\n                sum13 += a1 * b3;\n                sum14 += a1 * b4;\n                sum15 += a1 * b5;\n                sum16 += a1 * b6;\n                sum17 += a1 * b7;\n                sum20 += a2 * b0;\n                sum21 += a2 * b1;\n                sum22 += a2 * b2;\n                sum23 += a2 * b3;\n                sum24 += a2 * b4;\n                sum25 += a2 * b5;\n                sum26 += a2 * b6;\n                sum27 += a2 * b7;\n                sum30 += a3 * b0;\n                sum31 += a3 * b1;\n                sum32 += a3 * b2;\n                sum33 += a3 * b3;\n                sum34 += a3 * b4;\n                sum35 += a3 * b5;\n                sum36 += a3 * b6;\n                sum37 += a3 * b7;\n                sum40 += a4 * b0;\n                sum41 += a4 * b1;\n                sum42 += a4 * b2;\n                sum43 += a4 * b3;\n                sum44 += a4 * b4;\n                sum45 += a4 * b5;\n                sum46 += a4 * b6;\n                sum47 += a4 * b7;\n                sum50 += a5 * b0;\n                sum51 += a5 * b1;\n                sum52 += a5 * b2;\n                sum53 += a5 * b3;\n                sum54 += a5 * b4;\n                sum55 += a5 * b5;\n                sum56 += a5 * b6;\n                sum57 += a5 * b7;\n                sum60 += a6 * b0;\n                sum61 += a6 * b1;\n                sum62 += a6 * b2;\n                sum63 += a6 * b3;\n                sum64 += a6 * b4;\n                sum65 += a6 * b5;\n                sum66 += a6 * b6;\n                sum67 += a6 * b7;\n                sum70 += a7 * b0;\n                sum71 += a7 * b1;\n                sum72 += a7 * b2;\n                sum73 += a7 * b3;\n                sum74 += a7 * b4;\n                sum75 += a7 * b5;\n                sum76 += a7 * b6;\n                sum77 += a7 * b7;\n            }\n            c[(8 * row + 0) * n + 8 * col] = sum00;\n            c[(8 * row + 0) * n + 8 * col + 1] = sum01;\n            c[(8 * row + 0) * n + 8 * col + 2] = sum02;\n            c[(8 * row + 0) * n + 8 * col + 3] = sum03;\n            c[(8 * row + 0) * n + 8 * col + 4] = sum04;\n            c[(8 * row + 0) * n + 8 * col + 5] = sum05;\n            c[(8 * row + 0) * n + 8 * col + 6] = sum06;\n            c[(8 * row + 0) * n + 8 * col + 7] = sum07;\n            c[(8 * row + 1) * n + 8 * col] = sum10;\n            c[(8 * row + 1) * n + 8 * col + 1] = sum11;\n            c[(8 * row + 1) * n + 8 * col + 2] = sum12;\n            c[(8 * row + 1) * n + 8 * col + 3] = sum13;\n            c[(8 * row + 1) * n + 8 * col + 4] = sum14;\n            c[(8 * row + 1) * n + 8 * col + 5] = sum15;\n            c[(8 * row + 1) * n + 8 * col + 6] = sum16;\n            c[(8 * row + 1) * n + 8 * col + 7] = sum17;\n            c[(8 * row + 2) * n + 8 * col] = sum20;\n            c[(8 * row + 2) * n + 8 * col + 1] = sum21;\n            c[(8 * row + 2) * n + 8 * col + 2] = sum22;\n            c[(8 * row + 2) * n + 8 * col + 3] = sum23;\n            c[(8 * row + 2) * n + 8 * col + 4] = sum24;\n            c[(8 * row + 2) * n + 8 * col + 5] = sum25;\n            c[(8 * row + 2) * n + 8 * col + 6] = sum26;\n            c[(8 * row + 2) * n + 8 * col + 7] = sum27;\n            c[(8 * row + 3) * n + 8 * col] = sum30;\n            c[(8 * row + 3) * n + 8 * col + 1] = sum31;\n            c[(8 * row + 3) * n + 8 * col + 2] = sum32;\n            c[(8 * row + 3) * n + 8 * col + 3] = sum33;\n            c[(8 * row + 3) * n + 8 * col + 4] = sum34;\n            c[(8 * row + 3) * n + 8 * col + 5] = sum35;\n            c[(8 * row + 3) * n + 8 * col + 6] = sum36;\n            c[(8 * row + 3) * n + 8 * col + 7] = sum37;\n            c[(8 * row + 4) * n + 8 * col] = sum40;\n            c[(8 * row + 4) * n + 8 * col + 1] = sum41;\n            c[(8 * row + 4) * n + 8 * col + 2] = sum42;\n            c[(8 * row + 4) * n + 8 * col + 3] = sum43;\n            c[(8 * row + 4) * n + 8 * col + 4] = sum44;\n            c[(8 * row + 4) * n + 8 * col + 5] = sum45;\n            c[(8 * row + 4) * n + 8 * col + 6] = sum46;\n            c[(8 * row + 4) * n + 8 * col + 7] = sum47;\n            c[(8 * row + 5) * n + 8 * col] = sum50;\n            c[(8 * row + 5) * n + 8 * col + 1] = sum51;\n            c[(8 * row + 5) * n + 8 * col + 2] = sum52;\n            c[(8 * row + 5) * n + 8 * col + 3] = sum53;\n            c[(8 * row + 5) * n + 8 * col + 4] = sum54;\n            c[(8 * row + 5) * n + 8 * col + 5] = sum55;\n            c[(8 * row + 5) * n + 8 * col + 6] = sum56;\n            c[(8 * row + 5) * n + 8 * col + 7] = sum57;\n            c[(8 * row + 6) * n + 8 * col] = sum60;\n            c[(8 * row + 6) * n + 8 * col + 1] = sum61;\n            c[(8 * row + 6) * n + 8 * col + 2] = sum62;\n            c[(8 * row + 6) * n + 8 * col + 3] = sum63;\n            c[(8 * row + 6) * n + 8 * col + 4] = sum64;\n            c[(8 * row + 6) * n + 8 * col + 5] = sum65;\n            c[(8 * row + 6) * n + 8 * col + 6] = sum66;\n            c[(8 * row + 6) * n + 8 * col + 7] = sum67;\n            c[(8 * row + 7) * n + 8 * col] = sum70;\n            c[(8 * row + 7) * n + 8 * col + 1] = sum71;\n            c[(8 * row + 7) * n + 8 * col + 2] = sum72;\n            c[(8 * row + 7) * n + 8 * col + 3] = sum73;\n            c[(8 * row + 7) * n + 8 * col + 4] = sum74;\n            c[(8 * row + 7) * n + 8 * col + 5] = sum75;\n            c[(8 * row + 7) * n + 8 * col + 6] = sum76;\n            c[(8 * row + 7) * n + 8 * col + 7] = sum77;\n        }\n    }\n}\n\nextern \"C\" {\n    fn c_tile_1x1(m: usize, k: usize, n: usize, a: *const f32, b: *const f32, c: *mut f32);\n    fn c_tile_2x2(m: usize, k: usize, n: usize, a: *const f32, b: *const f32, c: *mut f32);\n    fn c_tile_4x4(m: usize, k: usize, n: usize, a: *const f32, b: *const f32, c: *mut f32);\n    fn c_packed_tile_4x4(m: usize, k: usize, n: usize, a: *const f32, b: *const f32, c: *mut f32);\n    fn c_tile_8x8(m: usize, k: usize, n: usize, a: *const f32, b: *const f32, c: *mut f32);\n    fn c_packed_tile_8x8(m: usize, k: usize, n: usize, a: *const f32, b: *const f32, c: *mut f32);\n}\n\npub fn ctile_1x1(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    unsafe { c_tile_1x1(m, k, n, a.as_ptr(), b.as_ptr(), c.as_mut_ptr()) }\n}\n\npub fn ctile_2x2(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    unsafe { c_tile_2x2(m, k, n, a.as_ptr(), b.as_ptr(), c.as_mut_ptr()) }\n}\n\npub fn ctile_4x4(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    unsafe { c_tile_4x4(m, k, n, a.as_ptr(), b.as_ptr(), c.as_mut_ptr()) }\n}\n\npub fn cpacked_tile_4x4(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    unsafe { c_packed_tile_4x4(m, k, n, a.as_ptr(), b.as_ptr(), c.as_mut_ptr()) }\n}\n\npub fn ctile_8x8(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    unsafe { c_tile_8x8(m, k, n, a.as_ptr(), b.as_ptr(), c.as_mut_ptr()) }\n}\n\npub fn cpacked_tile_8x8(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    unsafe { c_packed_tile_8x8(m, k, n, a.as_ptr(), b.as_ptr(), c.as_mut_ptr()) }\n}\n\npub fn matrixmultiply(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    unsafe {\n        matrixmultiply::sgemm(\n            m,\n            k,\n            n,\n            1.0,\n            a.as_ptr(),\n            k as _,\n            1,\n            b.as_ptr(),\n            n as _,\n            1,\n            0.0,\n            c.as_mut_ptr(),\n            n as _,\n            1,\n        )\n    }\n}\n\n#[allow(unused_variables, unused_mut)]\npub fn cblas(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    #[cfg(feature = \"blas\")]\n    unsafe {\n        cblas::sgemm(\n            cblas::Layout::RowMajor,\n            cblas::Transpose::None,\n            cblas::Transpose::None,\n            m as _,\n            n as _,\n            k as _,\n            1.0,\n            &a,\n            k as _,\n            &b,\n            n as _,\n            0.0,\n            c,\n            n as _,\n        )\n    }\n}\n\npub fn tract(m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {\n    use tract_data::internal::*;\n    use tract_linalg::frame::mmm::FusedSpec;\n    unsafe {\n        let mmm = tract_linalg::ops()\n            .mmm(DatumType::F32, DatumType::F32, DatumType::F32, Some(m), Some(k), Some(n))\n            .unwrap();\n\n        let c_storage = mmm.c_view(0, 1);\n\n        let a = Tensor::from_shape(&[m, k], a).unwrap();\n        let b = Tensor::from_shape(&[k, n], b).unwrap();\n        let mut tc = Tensor::uninitialized_dt(f32::datum_type(), &[m, n]).unwrap();\n\n        let packed_a = mmm.a_pack().pack_tensor(&a, 1, 0).unwrap();\n        let packed_b = mmm.b_pack().pack_tensor(&b, 0, 1).unwrap();\n\n        let mut scratch = mmm.allocate_scratch_space();\n\n        mmm.run_with_scratch_space(\n            m,\n            n,\n            &mut *scratch,\n            &[\n                FusedSpec::AddMatMul {\n                    a: packed_a.as_ref(),\n                    b: packed_b.as_ref(),\n                },\n                FusedSpec::Store(c_storage.wrap(&mut tc.view_mut())),\n            ],\n        )\n        .unwrap();\n        c.copy_from_slice(tc.as_slice_unchecked())\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm32/armv7neon.rs",
    "content": "use crate::Ops;\nuse crate::frame::mmm::ImplementationQuality::ManuallyOptimized;\nuse crate::pack::PackedFormat;\n\nconst NEON: fn() -> bool = || crate::arm32::has_neon();\n\nMMMExternKernel!(armv7neon_mmm_f32_8x4_cortexa7 <f32>( 8, 4)@(16, 4) where(NEON) quality(ManuallyOptimized));\nMMMExternKernel!(armv7neon_mmm_f32_8x4_cortexa9 <f32>( 8, 4)@(16, 4) where(NEON) quality(ManuallyOptimized));\nMMMExternKernel!(armv7neon_mmm_f32_8x4_generic  <f32>( 8, 4)@(16, 4) where(NEON) quality(ManuallyOptimized));\nMMMExternKernel!(armv7neon_mmm_f32_8x6_cortexa7 <f32>( 8, 6)@(16, 4) where(NEON) quality(ManuallyOptimized));\nMMMExternKernel!(armv7neon_mmm_f32_8x6_cortexa9 <f32>( 8, 6)@(16, 4) where(NEON) quality(ManuallyOptimized));\nMMMExternKernel!(armv7neon_mmm_f32_8x6_generic  <f32>( 8, 6)@(16, 4) where(NEON) quality(ManuallyOptimized));\nMMMExternKernel!(armv7neon_mmm_f32_8x1_generic  <f32>( 8, 1)@(16, 4) where(NEON) quality(ManuallyOptimized));\nMMMExternKernel!(armv7neon_mmm_f32_32x1_cortexa7<f32>(32, 1)@(16, 4) where(NEON) quality(ManuallyOptimized));\nMMMExternKernel!(armv7neon_mmm_f32_32x1_cortexa9<f32>(32, 1)@(16, 4) where(NEON) quality(ManuallyOptimized));\nMMMExternKernel!(armv7neon_mmm_f32_32x1_generic <f32>(32, 1)@(16, 4) where(NEON) quality(ManuallyOptimized));\n\nMMMExternKernel!(armv7neon_mmm_i32_8x4<i32>(8, 4)@(32, 4) where(NEON)\n  packing[1] = i8i8 => |k| k.with_packing(PackedFormat::new(DatumType::I8, 8, 32), PackedFormat::new(DatumType::I8, 4, 32));\n  quality(ManuallyOptimized)\n  store(i8)\n);\n\nMMMExternKernel!(armv7neon_mmm_i32_32x1<i32>(32, 1)@(32, 4) where(NEON)\n  packing[1] = i8i8 => |k| k.with_packing(PackedFormat::new(DatumType::I8, 32, 32), PackedFormat::new(DatumType::I8, 1, 4));\n  quality(ManuallyOptimized)\n  store(i8)\n);\n\npub fn plug(ops: &mut Ops) {\n    ops.mmm_impls.extend_from_slice(&[\n        armv7neon_mmm_f32_8x4_cortexa7.mmm(),\n        armv7neon_mmm_f32_8x4_cortexa9.mmm(),\n        armv7neon_mmm_f32_8x4_generic.mmm(),\n        armv7neon_mmm_f32_8x6_cortexa7.mmm(),\n        armv7neon_mmm_f32_8x6_cortexa9.mmm(),\n        armv7neon_mmm_f32_8x6_generic.mmm(),\n        armv7neon_mmm_f32_8x1_generic.mmm(),\n        armv7neon_mmm_f32_32x1_cortexa7.mmm(),\n        armv7neon_mmm_f32_32x1_cortexa9.mmm(),\n        armv7neon_mmm_f32_32x1_generic.mmm(),\n    ]);\n}\n\nsigmoid_impl!(f32, armv7neon_sigmoid_f32_4n, 4, 4, crate::arm32::has_neon());\ntanh_impl!(f32, armv7neon_tanh_f32_4n, 4, 4, crate::arm32::has_neon());\n"
  },
  {
    "path": "linalg/src/arm32/armvfpv2.rs",
    "content": "use crate::Ops;\nuse crate::frame::mmm::ImplementationQuality::ManuallyOptimized;\nuse crate::frame::mmm::*;\n\nMMMExternKernel!(armvfpv2_mmm_f32_4x4<f32>(4, 4)@(4, 4) quality(ManuallyOptimized));\n\npub fn plug(ops: &mut Ops) {\n    log::info!(\"armvfpv2 activated for smmm\");\n    ops.mmm_f32 = Box::new(|_, _, _| armvfpv2_mmm_f32_4x4.mmm());\n    ops.mmm_impls.push(armvfpv2_mmm_f32_4x4.mmm());\n}\n"
  },
  {
    "path": "linalg/src/arm32/cortex_a7.rs",
    "content": "use crate::frame::mmm::CostModel;\npub fn model() -> CostModel<'static> {\n    CostModel {\n        big_product_mkn_threshold: 4193728.0,\n        big_product_kernel_choice: \"armv7neon_mmm_f32_8x6_cortexa7\",\n        kernels: &[\n            \"armv7neon_mmm_f32_8x4_cortexa7\",\n            \"armv7neon_mmm_f32_8x4_cortexa9\",\n            \"armv7neon_mmm_f32_8x4_generic\",\n            \"armv7neon_mmm_f32_8x6_cortexa7\",\n            \"armv7neon_mmm_f32_8x6_cortexa9\",\n            \"armv7neon_mmm_f32_8x6_generic\",\n            \"generic_f32_4x4\",\n        ],\n        mrs: &[4, 8],\n        nrs: &[4, 6],\n        feat_norm_mean: &[\n            4.589878771602424,\n            4.5739692460187005,\n            4.598167981532298,\n            13.762015999153403,\n            1.5038983903420524,\n            0.749874245472837,\n            3.465165995975855,\n            0.8777665995975855,\n            1.5022635814889336,\n            0.7570422535211268,\n            2.482142857142857,\n            0.8333752515090543,\n        ],\n        feat_norm_stddev: &[\n            1.2587312982588519,\n            1.2603116830524392,\n            1.2581181647300588,\n            1.3169322340874257,\n            1.1192637768418767,\n            0.43308528195884044,\n            2.2762097127791114,\n            0.32755518043295856,\n            1.1069539235554247,\n            0.42886977033219037,\n            1.7067987601825914,\n            0.37264049924995035,\n        ],\n        w1: &[\n            0.06765510141849518,\n            0.024555781856179237,\n            -0.8821254968643188,\n            -0.004870870150625706,\n            -0.10525479167699814,\n            0.1827959418296814,\n            0.1633400171995163,\n            -0.2377464473247528,\n            -0.17880690097808838,\n            0.19097138941287994,\n            0.04676022008061409,\n            -0.11329511553049088,\n            0.4089120030403137,\n            -0.3100685477256775,\n            -0.1652061492204666,\n            -0.19124962389469147,\n            -0.03810987249016762,\n            -0.00785011239349842,\n            0.09714752435684204,\n            -0.11142419278621674,\n            0.19261880218982697,\n            -0.2893339991569519,\n            -0.19540216028690338,\n            0.39759594202041626,\n            -0.00619965186342597,\n            -0.8473111391067505,\n            0.343344122171402,\n            -0.12575943768024445,\n            0.029266485944390297,\n            -0.02900734543800354,\n            -0.019343264400959015,\n            0.08306540548801422,\n            -0.1927606761455536,\n            0.23312175273895264,\n            0.2576882541179657,\n            -0.35881471633911133,\n            -0.27300119400024414,\n            -0.2995607852935791,\n            -0.7934547662734985,\n            -0.9349930286407471,\n            -0.011614155024290085,\n            -0.12521372735500336,\n            0.011371670290827751,\n            0.05779163911938667,\n            0.17875070869922638,\n            -0.23169392347335815,\n            -0.09749509394168854,\n            0.07436174154281616,\n            0.24035069346427917,\n            -0.1262669861316681,\n            0.3874961733818054,\n            -0.11149000376462936,\n            0.03639678284525871,\n            0.17740628123283386,\n            0.03768332302570343,\n            -0.20480288565158844,\n            -0.1955408751964569,\n            0.44144806265830994,\n            0.3628064692020416,\n            -0.2537013292312622,\n            0.019405143335461617,\n            0.06186319515109062,\n            0.5196826457977295,\n            0.3010406494140625,\n            0.04013144597411156,\n            0.03517461195588112,\n            -0.037290964275598526,\n            0.009919736534357071,\n            -0.3135205805301666,\n            0.4654330909252167,\n            0.46720823645591736,\n            0.29665476083755493,\n            0.09099660068750381,\n            -0.7376689314842224,\n            -0.07840575277805328,\n            -0.5192644000053406,\n            0.019796665757894516,\n            -0.021734869107604027,\n            0.13953897356987,\n            -0.04154204577207565,\n            0.10942933708429337,\n            -0.13621817529201508,\n            -0.04218055680394173,\n            0.09188657253980637,\n            -0.16021296381950378,\n            -0.19393481314182281,\n            0.3737955689430237,\n            0.08288388699293137,\n            -0.08280416578054428,\n            -0.13087297976016998,\n            -0.09470323473215103,\n            0.2779513895511627,\n            0.03663017228245735,\n            0.36601993441581726,\n            0.8102841377258301,\n            0.6883901953697205,\n            -0.33066609501838684,\n            -0.34960171580314636,\n            0.923985481262207,\n            0.5853908061981201,\n            0.07039576023817062,\n            -0.11843020468950272,\n            -0.06797836720943451,\n            0.0974433571100235,\n            -0.4707315266132355,\n            0.37827417254447937,\n            0.15521520376205444,\n            -0.7403592467308044,\n            -0.25005313754081726,\n            0.596679151058197,\n            -0.7277861833572388,\n            -0.6915309429168701,\n            -0.0050544412806630135,\n            -0.12311484664678574,\n            0.04149714484810829,\n            0.05289606750011444,\n            0.2448417991399765,\n            -0.47261708974838257,\n            -0.3535511791706085,\n            0.4614925682544708,\n            0.9230178594589233,\n            -0.5351396799087524,\n            0.8224894404411316,\n            0.37244901061058044,\n            -0.08826857805252075,\n            -0.0452042818069458,\n            0.0035054143518209457,\n            0.09203510731458664,\n            0.08918709307909012,\n            -0.0694250762462616,\n            -0.053435735404491425,\n            0.1012222170829773,\n            0.3401939570903778,\n            -0.38458573818206787,\n            0.3040490746498108,\n            0.7614821791648865,\n            -0.17064380645751953,\n            0.22403603792190552,\n            0.08646601438522339,\n            -0.08289062976837158,\n            -0.20126193761825562,\n            0.2795524299144745,\n            0.13253425061702728,\n            -0.07332615554332733,\n            0.2151418924331665,\n            0.16798575222492218,\n            0.003749655559659004,\n            0.2437056005001068,\n            -0.09098415076732635,\n            0.18923071026802063,\n            0.07854695618152618,\n            -0.25417080521583557,\n            0.15693743526935577,\n            -0.30657434463500977,\n            -0.19041943550109863,\n            0.26519766449928284,\n            0.24278832972049713,\n            -0.18357035517692566,\n            -0.015992645174264908,\n            0.43973660469055176,\n            0.02785446122288704,\n            0.3032245934009552,\n            -0.021606506779789925,\n            -0.2682349383831024,\n            -0.10395143181085587,\n            0.050348248332738876,\n            0.12892353534698486,\n            -0.10498340427875519,\n            -0.027477847412228584,\n            0.09730125963687897,\n            -0.16150422394275665,\n            -0.21831916272640228,\n            0.10376061499118805,\n            -0.25544440746307373,\n            0.031593386083841324,\n            0.11986788362264633,\n            0.22690074145793915,\n            -0.3509098291397095,\n            -0.1881190538406372,\n            -0.04210145026445389,\n            0.6883101463317871,\n            -0.07829979062080383,\n            0.4657376706600189,\n            0.9263871908187866,\n            0.08322961628437042,\n            0.04429711028933525,\n            -0.08905605971813202,\n            -0.06788893789052963,\n            -0.056182388216257095,\n            -0.04881853610277176,\n            -0.04854113608598709,\n            0.15449045598506927,\n            0.32911357283592224,\n            -0.5772383809089661,\n            -0.00027374469209462404,\n            -0.2995521128177643,\n            -0.027322502806782722,\n            0.5023694038391113,\n            0.045783523470163345,\n            -0.4035968780517578,\n            0.053967904299497604,\n            0.00014662329340353608,\n            0.021607715636491776,\n            -0.028252260759472847,\n            -0.05918470770120621,\n            -0.1273883581161499,\n            0.0679078996181488,\n            0.25051605701446533,\n            -0.0745333656668663,\n            0.18680104613304138,\n            -0.12048312276601791,\n            0.013110226020216942,\n            -0.07659415900707245,\n            0.2906968295574188,\n            0.3136366307735443,\n            -0.47699007391929626,\n            0.02583535574376583,\n            -0.15701107680797577,\n            0.045304182916879654,\n            0.23456838726997375,\n            -0.06186807528138161,\n            0.3926846981048584,\n            -0.13252438604831696,\n            -0.16362214088439941,\n            0.013557562604546547,\n            -0.09991434961557388,\n            0.09150815010070801,\n            -0.006477471441030502,\n            0.2915862202644348,\n            0.5867642164230347,\n            -0.37984445691108704,\n            0.033169880509376526,\n            0.024414243176579475,\n            -0.0384003147482872,\n            -0.06395144015550613,\n            0.07380940765142441,\n            -0.025898484513163567,\n            0.03951931372284889,\n            -0.2343142330646515,\n            0.27318838238716125,\n            0.1105947494506836,\n            0.290696382522583,\n            -0.17851489782333374,\n            -0.17699271440505981,\n            -0.210996612906456,\n            -0.10575137287378311,\n            0.15886521339416504,\n            0.10631759464740753,\n            0.22946283221244812,\n            -0.3170112073421478,\n            -0.49773311614990234,\n            -0.10753292590379715,\n            -0.1114523783326149,\n            -0.10953730344772339,\n            0.4754663109779358,\n            0.20793643593788147,\n            0.021392812952399254,\n            -0.0691467821598053,\n            0.03368104621767998,\n            -0.017844771966338158,\n            0.1657843142747879,\n            -0.5556477904319763,\n            -1.108074426651001,\n            -0.822117805480957,\n            -0.06053074076771736,\n            -0.4072379469871521,\n            0.09109722077846527,\n            -0.5544739961624146,\n            -0.13978064060211182,\n            -0.36262163519859314,\n            0.20034632086753845,\n            0.050625383853912354,\n            0.1497042030096054,\n            -0.18745489418506622,\n            0.0894727036356926,\n            0.00417149206623435,\n            0.2228451371192932,\n            0.00852279644459486,\n            -0.028313757851719856,\n            0.04104698821902275,\n            -0.0874263271689415,\n            0.19788521528244019,\n            -0.019343160092830658,\n            -0.03962515667080879,\n            0.2092486023902893,\n            -0.44425246119499207,\n            -0.48542261123657227,\n            -0.04222029820084572,\n            0.7616084218025208,\n            0.512810468673706,\n            -0.17871123552322388,\n            0.5459727644920349,\n            -0.13069608807563782,\n            0.09155352413654327,\n            0.11548610031604767,\n            -0.15368784964084625,\n            0.038799818605184555,\n            -0.049028217792510986,\n            -0.03215758875012398,\n            -0.050522346049547195,\n            0.1663637012243271,\n            -0.15482299029827118,\n            -0.9425870180130005,\n            -0.7017998695373535,\n            0.04315050691366196,\n            -0.019968662410974503,\n            0.03749818727374077,\n            -0.07611791789531708,\n            0.32011789083480835,\n            -0.6925904750823975,\n            -0.49334919452667236,\n            0.23214411735534668,\n            1.1447347402572632,\n            -0.6757001876831055,\n            0.7940422296524048,\n            0.40169182419776917,\n            -0.018513813614845276,\n            0.048821814358234406,\n            -0.016693273559212685,\n            0.008068449795246124,\n            0.04566117003560066,\n            -0.09829569607973099,\n            -0.026971371844410896,\n            0.05381541699171066,\n            -0.3659301698207855,\n            0.3473235070705414,\n            0.14521746337413788,\n            0.11228122562170029,\n            -0.041056130081415176,\n            -0.11228874325752258,\n            0.006667478010058403,\n            0.15931302309036255,\n            -0.30010080337524414,\n            0.3464723229408264,\n            0.4476386308670044,\n            -0.3498152494430542,\n            0.2616507112979889,\n            -0.19995814561843872,\n            0.10946320742368698,\n            0.4034257233142853,\n            -0.08651446551084518,\n            0.018647747114300728,\n            0.11572548002004623,\n            -0.100877545773983,\n            -0.16341210901737213,\n            0.2377898246049881,\n            0.3417612910270691,\n            -0.49084869027137756,\n            -0.02805873565375805,\n            -0.09811390936374664,\n            0.17161016166210175,\n            0.3627470135688782,\n            -0.08954513072967529,\n            0.06629404425621033,\n            0.012786897830665112,\n            0.01578289456665516,\n            -0.32630467414855957,\n            0.4854920506477356,\n            0.12709765136241913,\n            -0.4909423291683197,\n            -0.3745254874229431,\n            -0.6513142585754395,\n            -0.040075208991765976,\n            -0.569782018661499,\n            -0.009953420609235764,\n            0.04735071584582329,\n            0.0230120699852705,\n            -0.07381311058998108,\n            -0.06293600797653198,\n            0.20196016132831573,\n            0.26551517844200134,\n            -0.42071688175201416,\n            0.28809165954589844,\n            0.19747501611709595,\n            -0.5686206221580505,\n            -0.5285986661911011,\n            0.02009684592485428,\n            0.11322621256113052,\n            -0.1082596555352211,\n            -0.0856761634349823,\n            -0.04493662342429161,\n            -0.6179490089416504,\n            -0.1442672610282898,\n            0.028762176632881165,\n            0.12426868081092834,\n            -0.5771384835243225,\n            0.1608373522758484,\n            0.004147801548242569,\n            -0.047590240836143494,\n            0.10347189754247665,\n            0.11780986934900284,\n            -0.08490656316280365,\n            -0.0746934711933136,\n            0.15699702501296997,\n            0.1298881322145462,\n            -0.14411042630672455,\n            -0.08601037412881851,\n            0.2997709810733795,\n            -0.05418943241238594,\n            -0.1772651970386505,\n            0.04576871916651726,\n            -0.13510753214359283,\n            -0.057203926146030426,\n            0.18647770583629608,\n            0.0055348677560687065,\n            -0.12238732725381851,\n            -0.11199415475130081,\n            0.43077343702316284,\n            0.1349855363368988,\n            0.21327465772628784,\n            0.05924845486879349,\n            0.12549948692321777,\n            -0.060076650232076645,\n            0.23921678960323334,\n            0.02152605727314949,\n            -0.1352948695421219,\n            0.09325127303600311,\n            -0.14411674439907074,\n            0.010495728813111782,\n            0.11577513813972473,\n            -0.07580242305994034,\n            0.42641204595565796,\n            -0.5557231903076172,\n            -0.12044595927000046,\n            0.024152765050530434,\n            -0.14175696671009064,\n            0.024960221722722054,\n            0.10017693042755127,\n            -0.07402117550373077,\n            0.09156208485364914,\n            0.455565482378006,\n            0.424320250749588,\n            -0.07668061554431915,\n            0.10318724811077118,\n            -0.32521969079971313,\n            -0.2653461694717407,\n            -0.03919212520122528,\n            0.12909358739852905,\n            -0.17091549932956696,\n            0.07353391498327255,\n            0.11510979384183884,\n            -0.23758216202259064,\n            -0.3059186339378357,\n            -0.046047650277614594,\n            0.17527209222316742,\n            0.19020265340805054,\n            -0.20766229927539825,\n            -0.23476286232471466,\n            -0.14011070132255554,\n            0.1085173636674881,\n            -0.020777594298124313,\n            0.014691418968141079,\n            0.21648286283016205,\n            -0.21576255559921265,\n            0.28203028440475464,\n            0.6320008635520935,\n            -0.23609709739685059,\n            0.16072526574134827,\n            0.30149686336517334,\n            -0.05675647035241127,\n            -0.018186205998063087,\n            -0.1844293773174286,\n            0.13510139286518097,\n            0.05780869722366333,\n            0.07202577590942383,\n            0.07459436357021332,\n            0.18700383603572845,\n            -0.09449177235364914,\n            0.057188909500837326,\n            0.21453143656253815,\n            -0.30002379417419434,\n            -0.12217795103788376,\n            0.03723505884408951,\n            -0.18360234797000885,\n            -0.029992947354912758,\n            0.10999765247106552,\n            0.09575961530208588,\n            -0.36028456687927246,\n            -0.4311397075653076,\n            0.5812231302261353,\n        ],\n        b1: &[\n            0.3801889419555664,\n            -0.5001883506774902,\n            0.19484910368919373,\n            0.6488791704177856,\n            0.38620173931121826,\n            0.8780303597450256,\n            -0.1126403734087944,\n            0.021730314940214157,\n            -0.7806469202041626,\n            -0.04312174394726753,\n            0.3102167546749115,\n            0.9241658449172974,\n            0.8900863528251648,\n            -0.2938256561756134,\n            -0.5012822151184082,\n            -0.00329477502964437,\n            0.5169500708580017,\n            0.4563848376274109,\n            -0.4903448224067688,\n            0.27919942140579224,\n            -0.4288303554058075,\n            -0.1836952418088913,\n            -0.09118890762329102,\n            0.5528226494789124,\n            -0.19896377623081207,\n            0.33588215708732605,\n            0.07895006239414215,\n            0.07812929153442383,\n            0.6203332543373108,\n            0.8427650332450867,\n            -0.684628427028656,\n            0.5408275723457336,\n            -0.5548633933067322,\n            -0.49557214975357056,\n            0.7953769564628601,\n            -0.4109633266925812,\n            -0.6270897388458252,\n            -0.43285393714904785,\n            -0.7562689781188965,\n            -0.7167727947235107,\n        ],\n        w2: &[\n            0.15592391788959503,\n            0.25119924545288086,\n            -0.499594122171402,\n            -0.5441639423370361,\n            -0.11186911165714264,\n            -0.6334478855133057,\n            0.28880706429481506,\n            -0.592946469783783,\n            0.7188563942909241,\n            -0.49322614073753357,\n            -0.1398385912179947,\n            -0.1868145614862442,\n            0.9288992881774902,\n            -0.07525540888309479,\n            0.2288437783718109,\n            0.09932874143123627,\n            0.2782813012599945,\n            -0.12644614279270172,\n            -0.14151062071323395,\n            0.38845404982566833,\n            0.2691279947757721,\n            -0.9148958921432495,\n            0.19230225682258606,\n            0.6098687052726746,\n            -0.24782557785511017,\n            -0.6989489197731018,\n            -0.30721813440322876,\n            -0.4890380799770355,\n            -0.43724432587623596,\n            -0.38428765535354614,\n            -0.6491377353668213,\n            -0.28134995698928833,\n            -0.36228886246681213,\n            -0.05963568389415741,\n            0.5086851119995117,\n            0.4664144814014435,\n            0.3797634541988373,\n            0.5596290826797485,\n            -0.1977449357509613,\n            0.6540879607200623,\n            -0.24533972144126892,\n            0.6865915656089783,\n            -0.18364377319812775,\n            0.0013501447392627597,\n            -0.4037604331970215,\n            -0.287411093711853,\n            -0.43570032715797424,\n            -0.4085054099559784,\n            0.7341827750205994,\n            -0.29973891377449036,\n            -0.18240050971508026,\n            -0.23446109890937805,\n            0.7225431799888611,\n            0.008502814918756485,\n            0.04582007974386215,\n            0.03352205455303192,\n            0.12457727640867233,\n            -0.2019437849521637,\n            -0.1299249827861786,\n            -0.09946829080581665,\n            0.40665051341056824,\n            -0.6841736435890198,\n            -0.523845911026001,\n            0.21656402945518494,\n            0.6046024560928345,\n            -0.6393186450004578,\n            -0.3965637981891632,\n            -0.7872777581214905,\n            -0.13687947392463684,\n            -0.19312888383865356,\n            -0.5453231930732727,\n            -0.21912647783756256,\n            0.011589044705033302,\n            0.2665385603904724,\n            0.3249806761741638,\n            0.293254017829895,\n            0.1047254130244255,\n            0.4246895909309387,\n            -0.0033608688972890377,\n            0.4066942632198334,\n            0.06138676777482033,\n            0.382074236869812,\n            0.0787188857793808,\n            -0.28631800413131714,\n            -0.3500039279460907,\n            -0.1490340679883957,\n            -0.14991725981235504,\n            -0.180477574467659,\n            0.15140952169895172,\n            -0.35168370604515076,\n            0.38904908299446106,\n            -0.11262823641300201,\n            -0.18404939770698547,\n            0.5045862197875977,\n            0.23344825208187103,\n            0.6740546226501465,\n            -0.054060351103544235,\n            -0.47260594367980957,\n            0.287933886051178,\n            0.28975099325180054,\n            0.2366262525320053,\n            -0.1751112937927246,\n            -0.15358465909957886,\n            -0.062381260097026825,\n            0.45881521701812744,\n            -0.12647950649261475,\n            0.45258036255836487,\n            -0.21084383130073547,\n            -0.15994171798229218,\n            -0.4229416847229004,\n            -0.18642400205135345,\n            -0.2506699860095978,\n            0.20604389905929565,\n            0.16662882268428802,\n            -0.23073841631412506,\n            0.045810505747795105,\n            0.33520498871803284,\n            0.37685254216194153,\n            0.11563336104154587,\n            0.22259201109409332,\n            -0.010484708473086357,\n            -0.45855188369750977,\n            0.24794596433639526,\n            0.33667632937431335,\n            0.20378778874874115,\n            0.4198003113269806,\n            0.23384596407413483,\n            0.23601709306240082,\n            -0.509751558303833,\n            0.5694931149482727,\n            -0.08933047205209732,\n            0.037133198231458664,\n            0.20635388791561127,\n            -0.2857131361961365,\n            -0.4278101921081543,\n            -0.26602792739868164,\n            0.1998632550239563,\n            0.4324374794960022,\n            -0.13389578461647034,\n            0.11837134510278702,\n            -0.17028754949569702,\n            0.37928706407546997,\n            0.10062910616397858,\n            -0.04736608266830444,\n            -0.04692180082201958,\n            0.6633663773536682,\n            -0.3517492711544037,\n            0.2055688351392746,\n            0.44142597913742065,\n            0.42460545897483826,\n            0.4567111134529114,\n            0.3061029016971588,\n            -0.16390416026115417,\n            -0.3541538417339325,\n            0.2544074058532715,\n            -0.18162837624549866,\n            -0.21904821693897247,\n            -0.2520917057991028,\n            -0.07266020774841309,\n            -0.23432950675487518,\n            -0.1989256739616394,\n            0.09460597485303879,\n            -0.24563294649124146,\n            0.9719013571739197,\n            0.2578149139881134,\n            0.26680076122283936,\n            -0.39480605721473694,\n            0.22382304072380066,\n            -0.4284250736236572,\n            0.4294125437736511,\n            -0.04923247918486595,\n            0.5011574625968933,\n            0.1887599676847458,\n            -0.02984841726720333,\n            -0.16428305208683014,\n            -0.33957910537719727,\n            -0.16184143722057343,\n            0.37313663959503174,\n            -0.11775537580251694,\n            -0.34507161378860474,\n            -0.24848994612693787,\n            0.3492432236671448,\n            -0.2122095823287964,\n            -0.022055158391594887,\n            0.07298140972852707,\n            0.36230477690696716,\n            -0.2514148950576782,\n            0.11675992608070374,\n            0.4010731875896454,\n            0.31790846586227417,\n            0.0585796944797039,\n            0.30878275632858276,\n            0.5536429286003113,\n            -0.061644136905670166,\n            -0.06381722539663315,\n            -0.1873038411140442,\n            -0.24746698141098022,\n            -0.3139619529247284,\n            -0.19278131425380707,\n            -0.48264867067337036,\n            0.5122742056846619,\n            0.09536745399236679,\n            0.17870695888996124,\n            0.18145892024040222,\n            0.2471739798784256,\n            -0.16399677097797394,\n            -0.18874068558216095,\n            0.21305255591869354,\n            -0.6930050253868103,\n            -0.4031701982021332,\n            0.5250658392906189,\n            0.4295860230922699,\n            -0.464653879404068,\n            -0.026941847056150436,\n            -0.08213993161916733,\n            0.34638163447380066,\n            -0.15401627123355865,\n            0.021148433908820152,\n            0.19726167619228363,\n            -0.25100240111351013,\n            3.085673233726993e-05,\n            0.16563303768634796,\n            -0.008333534933626652,\n            -0.02890022285282612,\n            -0.284770667552948,\n            0.3429299592971802,\n            0.6073935627937317,\n            -0.10915102809667587,\n            0.3420248329639435,\n            0.07347360253334045,\n            0.18400518596172333,\n            0.2084905058145523,\n            0.3218590021133423,\n            0.16883575916290283,\n            -0.6880696415901184,\n            -0.37455135583877563,\n            0.04792584478855133,\n            -0.04572531208395958,\n            -0.17001567780971527,\n            -0.12369263172149658,\n            -0.3716808259487152,\n            -0.04167286679148674,\n            0.04307235777378082,\n            -0.1655367612838745,\n            -0.47902533411979675,\n            -0.21886907517910004,\n            0.4065888226032257,\n            0.30626556277275085,\n            0.25965678691864014,\n            0.07168732583522797,\n            -0.17138782143592834,\n            -0.6293558478355408,\n            -0.6350710988044739,\n            0.25923609733581543,\n            0.5668261647224426,\n            -0.030662082135677338,\n            -0.7059182524681091,\n            -0.25901535153388977,\n            0.25449642539024353,\n            -0.3232290744781494,\n            0.42758384346961975,\n            0.7120643258094788,\n            0.023215001448988914,\n            -0.40807682275772095,\n            0.1332295536994934,\n            -0.33705568313598633,\n            0.1038941740989685,\n            0.39904412627220154,\n            -0.567590057849884,\n            -0.26575762033462524,\n            0.7635160088539124,\n            -0.38967835903167725,\n            -0.08988548815250397,\n            0.4150312840938568,\n            -0.540441632270813,\n            0.33467426896095276,\n            -0.03507159277796745,\n            0.00720902718603611,\n            0.6702240109443665,\n            0.2707512676715851,\n        ],\n        b2: &[\n            0.3580038547515869,\n            0.06861710548400879,\n            -0.04651366174221039,\n            0.24638813734054565,\n            0.1557426154613495,\n            -0.40271297097206116,\n            -0.405432790517807,\n        ],\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm32/cortex_a7.txt",
    "content": "armv7neon_mmm_f32_8x4_cortexa7 16 128 8 0.000019373978862224142\narmv7neon_mmm_f32_8x6_generic 24 4 18 0.000005589467629481233\narmv7neon_mmm_f32_8x6_cortexa7 24 32 5 0.0000068541067994687505\narmv7neon_mmm_f32_8x6_cortexa7 24 128 12 0.000038981217414944424\ngeneric_f32_4x4 4 32 3 0.000001790047741390679\narmv7neon_mmm_f32_8x4_generic 17 32 12 0.000013250293625322834\narmv7neon_mmm_f32_8x4_cortexa7 9 4 9 0.00000392359813080775\narmv7neon_mmm_f32_8x6_generic 16 128 18 0.00003780372998360008\narmv7neon_mmm_f32_8x6_generic 7 32 7 0.000004508038841829227\narmv7neon_mmm_f32_8x4_cortexa9 25 128 4 0.00001979557034383245\narmv7neon_mmm_f32_8x4_cortexa7 24 128 5 0.00002916219359816615\ngeneric_f32_4x4 8 4 7 0.000002792935337190097\narmv7neon_mmm_f32_8x6_generic 25 32 5 0.000008513973930688824\narmv7neon_mmm_f32_8x4_generic 8 128 4 0.000004984706018353683\narmv7neon_mmm_f32_8x6_cortexa7 23 4 6 0.0000024972411854271217\narmv7neon_mmm_f32_8x6_cortexa9 17 32 18 0.000018034380706056615\ngeneric_f32_4x4 9 32 5 0.000007787097193216308\narmv7neon_mmm_f32_8x4_cortexa7 7 32 11 0.000005445588694072235\narmv7neon_mmm_f32_8x4_cortexa9 7 4 3 0.0000011467255960994079\narmv7neon_mmm_f32_8x6_cortexa7 7 128 6 0.0000071177868474168225\narmv7neon_mmm_f32_8x6_generic 15 128 5 0.000013291532044598022\narmv7neon_mmm_f32_8x6_cortexa7 25 4 18 0.000007704060547781454\narmv7neon_mmm_f32_8x4_generic 15 128 11 0.000028366457109510148\narmv7neon_mmm_f32_8x6_cortexa7 7 4 12 0.000002031241642569242\ngeneric_f32_4x4 12 32 9 0.000011111678190760999\narmv7neon_mmm_f32_8x6_cortexa9 24 32 17 0.000018402463967012353\narmv7neon_mmm_f32_8x4_cortexa7 8 4 12 0.0000019146048075747953\narmv7neon_mmm_f32_8x4_cortexa7 23 4 8 0.00000383750508652125\narmv7neon_mmm_f32_8x4_cortexa7 25 4 7 0.0000052043882186278224\narmv7neon_mmm_f32_8x4_generic 16 32 12 0.000008739868831484192\ngeneric_f32_4x4 12 4 8 0.00000347003352464419\narmv7neon_mmm_f32_8x6_cortexa7 25 32 19 0.00003152139986100901\narmv7neon_mmm_f32_8x6_cortexa9 25 32 18 0.000023769189598361993\narmv7neon_mmm_f32_8x4_cortexa7 15 128 5 0.00001982738009364039\narmv7neon_mmm_f32_8x6_generic 9 128 11 0.000025754948633914126\narmv7neon_mmm_f32_8x4_cortexa7 15 4 4 0.0000016751657126978973\narmv7neon_mmm_f32_8x4_cortexa9 24 32 8 0.000009220363478862854\ngeneric_f32_4x4 3 32 13 0.000005627827228507873\narmv7neon_mmm_f32_8x6_cortexa9 23 128 13 0.00006012344410992782\narmv7neon_mmm_f32_8x6_cortexa7 24 128 6 0.00001973568156590405\narmv7neon_mmm_f32_8x4_generic 17 128 8 0.000027722972081041346\narmv7neon_mmm_f32_8x4_generic 9 128 4 0.000009605985891246275\narmv7neon_mmm_f32_8x4_cortexa7 15 32 3 0.000003754036592781278\narmv7neon_mmm_f32_8x4_cortexa7 25 128 8 0.00003835708390551408\narmv7neon_mmm_f32_8x4_cortexa9 9 32 4 0.0000035373293731924376\ngeneric_f32_4x4 7 4 11 0.000004044788183106201\narmv7neon_mmm_f32_8x6_cortexa9 7 32 12 0.000004744792047894666\narmv7neon_mmm_f32_8x4_cortexa9 23 4 8 0.0000038749254387558\ngeneric_f32_4x4 9 32 8 0.0000075635927841880176\narmv7neon_mmm_f32_8x4_cortexa7 9 4 12 0.000003727778947810718\narmv7neon_mmm_f32_8x6_cortexa7 23 32 5 0.0000067956400405037536\ngeneric_f32_4x4 8 4 5 0.000002723326536848814\narmv7neon_mmm_f32_8x6_cortexa9 15 32 5 0.000004690163698982028\narmv7neon_mmm_f32_8x4_cortexa9 9 32 3 0.0000037317693401172363\narmv7neon_mmm_f32_8x6_cortexa7 25 128 13 0.00007831443093305969\narmv7neon_mmm_f32_8x4_cortexa7 24 32 12 0.00001331331475399187\narmv7neon_mmm_f32_8x6_cortexa7 24 128 19 0.00007802209851667727\narmv7neon_mmm_f32_8x4_generic 16 128 11 0.00002788579513287149\narmv7neon_mmm_f32_8x4_cortexa7 17 128 4 0.000014706021218529391\narmv7neon_mmm_f32_8x6_generic 8 4 13 0.0000023378593097707485\narmv7neon_mmm_f32_8x6_generic 7 4 7 0.0000019186954240278283\narmv7neon_mmm_f32_8x4_cortexa9 16 4 8 0.000002428666090877155\narmv7neon_mmm_f32_8x6_generic 24 128 13 0.00005734829975884668\ngeneric_f32_4x4 8 128 11 0.000021025833076703056\narmv7neon_mmm_f32_8x6_cortexa9 17 4 19 0.00000803059286526004\narmv7neon_mmm_f32_8x6_cortexa7 25 4 7 0.00000571019568400475\narmv7neon_mmm_f32_8x4_cortexa9 7 128 3 0.000005476007735837036\ngeneric_f32_4x4 9 32 9 0.000011314156862861827\narmv7neon_mmm_f32_8x4_cortexa9 8 32 3 0.000002175330632998311\narmv7neon_mmm_f32_8x4_cortexa7 9 128 13 0.00003871137553728846\narmv7neon_mmm_f32_8x6_cortexa7 16 32 19 0.00001594300556874623\narmv7neon_mmm_f32_8x4_cortexa7 17 4 13 0.000007095518187604073\narmv7neon_mmm_f32_8x4_generic 17 4 12 0.00000511222434537087\narmv7neon_mmm_f32_8x4_generic 23 32 3 0.000005240662819589997\narmv7neon_mmm_f32_8x6_generic 7 4 19 0.000003459698540347226\narmv7neon_mmm_f32_8x6_cortexa9 7 128 19 0.000027297846707037145\narmv7neon_mmm_f32_8x4_cortexa7 23 4 9 0.000005799914306902319\narmv7neon_mmm_f32_8x4_generic 23 4 4 0.0000021622122978664557\narmv7neon_mmm_f32_8x4_cortexa9 17 128 12 0.00004402054920694197\narmv7neon_mmm_f32_8x6_cortexa9 15 4 18 0.000004616359494672357\narmv7neon_mmm_f32_8x4_cortexa9 8 4 4 0.0000010041412858292361\narmv7neon_mmm_f32_8x4_cortexa9 8 32 13 0.000006496433067761439\ngeneric_f32_4x4 11 128 3 0.000011040772633263298\narmv7neon_mmm_f32_8x6_generic 24 4 11 0.000004523362892147976\ngeneric_f32_4x4 9 4 4 0.0000021034125372056796\ngeneric_f32_4x4 8 4 9 0.000003722094027081023\ngeneric_f32_4x4 4 4 8 0.0000014952745701485973\narmv7neon_mmm_f32_8x4_generic 16 128 3 0.000009908089584838082\narmv7neon_mmm_f32_8x4_generic 25 32 7 0.000012421086220512836\narmv7neon_mmm_f32_8x6_cortexa7 8 4 5 0.0000013403828133224992\narmv7neon_mmm_f32_8x4_cortexa7 23 32 4 0.00000503241852364444\narmv7neon_mmm_f32_8x6_cortexa9 7 32 6 0.000002618507739501653\narmv7neon_mmm_f32_8x4_cortexa7 7 4 13 0.0000031793808774197963\narmv7neon_mmm_f32_8x4_generic 16 4 7 0.00000282397574390546\narmv7neon_mmm_f32_8x4_cortexa9 17 128 4 0.00001500697741030371\narmv7neon_mmm_f32_8x6_cortexa7 24 4 6 0.000002233395192000468\narmv7neon_mmm_f32_8x6_cortexa9 15 128 6 0.0000138021942440989\narmv7neon_mmm_f32_8x4_cortexa9 15 128 11 0.000030164013207782058\ngeneric_f32_4x4 7 4 5 0.0000028577946255424533\narmv7neon_mmm_f32_8x4_cortexa9 16 128 5 0.000020020455876347578\narmv7neon_mmm_f32_8x6_cortexa9 16 128 6 0.000013512716817189273\narmv7neon_mmm_f32_8x4_cortexa7 16 4 11 0.0000037996360400635168\narmv7neon_mmm_f32_8x4_generic 7 32 4 0.0000021000959358733572\narmv7neon_mmm_f32_8x4_cortexa9 9 4 11 0.000004001256873073548\narmv7neon_mmm_f32_8x6_generic 24 128 7 0.00003837839936999645\narmv7neon_mmm_f32_8x4_cortexa9 15 128 4 0.00001034155567067011\narmv7neon_mmm_f32_8x4_generic 7 128 9 0.000014585897754194483\narmv7neon_mmm_f32_8x4_cortexa9 7 128 11 0.000015558039194528556\narmv7neon_mmm_f32_8x4_generic 25 32 11 0.000018056248294844393\narmv7neon_mmm_f32_8x6_cortexa7 9 32 6 0.0000044289694676171784\narmv7neon_mmm_f32_8x4_cortexa9 9 32 7 0.000006775203907440964\narmv7neon_mmm_f32_8x4_cortexa7 17 32 4 0.000004906536483038752\narmv7neon_mmm_f32_8x6_generic 25 128 13 0.00007697771420855431\narmv7neon_mmm_f32_8x6_generic 15 32 13 0.000012236156128465641\narmv7neon_mmm_f32_8x4_generic 9 32 11 0.000009363916532723868\narmv7neon_mmm_f32_8x6_cortexa9 16 32 7 0.000008475723903784643\narmv7neon_mmm_f32_8x4_cortexa9 24 4 8 0.000003400276973989579\narmv7neon_mmm_f32_8x4_generic 8 32 9 0.000004816590922050574\narmv7neon_mmm_f32_8x6_cortexa9 7 32 18 0.0000068452889719178265\narmv7neon_mmm_f32_8x4_cortexa9 9 32 5 0.000006726253331965845\ngeneric_f32_4x4 11 32 13 0.000015033889948226767\narmv7neon_mmm_f32_8x4_generic 17 4 11 0.000005556840437336695\ngeneric_f32_4x4 13 32 7 0.000010313627379766583\ngeneric_f32_4x4 9 4 3 0.0000023801388628702307\narmv7neon_mmm_f32_8x4_generic 8 128 3 0.00000520201390212873\narmv7neon_mmm_f32_8x4_generic 8 32 12 0.000004612565409735615\narmv7neon_mmm_f32_8x4_cortexa9 16 128 4 0.000010106142800191435\narmv7neon_mmm_f32_8x4_cortexa7 8 4 5 0.0000016424950539860917\narmv7neon_mmm_f32_8x4_generic 8 32 8 0.000003246701797046072\narmv7neon_mmm_f32_8x6_cortexa7 17 128 7 0.00003957490867985703\narmv7neon_mmm_f32_8x4_cortexa7 7 32 12 0.000005449789255433999\narmv7neon_mmm_f32_8x4_cortexa7 15 32 5 0.000006774977020301673\narmv7neon_mmm_f32_8x6_generic 7 4 13 0.0000027111208216897315\narmv7neon_mmm_f32_8x4_cortexa7 16 32 5 0.000006561637517767873\ngeneric_f32_4x4 3 128 9 0.000011017761032513612\narmv7neon_mmm_f32_8x4_cortexa7 23 128 5 0.00002948816149020999\narmv7neon_mmm_f32_8x4_cortexa7 17 32 11 0.00001417097516045665\narmv7neon_mmm_f32_8x6_cortexa7 16 4 12 0.000002812978829286788\narmv7neon_mmm_f32_8x6_cortexa9 23 32 5 0.0000068076361854689625\narmv7neon_mmm_f32_8x4_cortexa7 24 4 3 0.0000025622772317463136\narmv7neon_mmm_f32_8x4_cortexa9 16 32 13 0.000012465423813216206\ngeneric_f32_4x4 4 32 11 0.000004083209759238612\narmv7neon_mmm_f32_8x4_generic 7 32 5 0.000003626955080135103\narmv7neon_mmm_f32_8x6_cortexa7 23 128 6 0.000020078583004924116\narmv7neon_mmm_f32_8x6_cortexa9 25 128 19 0.00010584384897256878\narmv7neon_mmm_f32_8x6_generic 17 4 12 0.000004107213127611975\narmv7neon_mmm_f32_8x6_cortexa7 16 4 17 0.000004432708536915881\narmv7neon_mmm_f32_8x6_cortexa7 16 32 18 0.00001183035782221589\narmv7neon_mmm_f32_8x4_cortexa9 25 4 3 0.000003166221225598654\narmv7neon_mmm_f32_8x4_cortexa9 24 128 9 0.00004426766902863704\narmv7neon_mmm_f32_8x6_generic 25 4 18 0.000007554578118985422\narmv7neon_mmm_f32_8x6_cortexa7 15 32 5 0.000004708546886703234\narmv7neon_mmm_f32_8x6_cortexa9 9 128 6 0.000013657373073808522\narmv7neon_mmm_f32_8x6_cortexa7 25 32 7 0.00001623345709716141\narmv7neon_mmm_f32_8x4_cortexa7 9 32 5 0.000006618675517469529\narmv7neon_mmm_f32_8x4_cortexa7 7 32 13 0.000007030082349548267\narmv7neon_mmm_f32_8x4_cortexa9 17 4 7 0.0000040753817592878874\narmv7neon_mmm_f32_8x6_generic 15 4 5 0.0000019991656588201214\ngeneric_f32_4x4 12 128 5 0.00002108090993489815\narmv7neon_mmm_f32_8x6_cortexa9 25 128 17 0.00007984886358906369\ngeneric_f32_4x4 8 128 5 0.000014230862544269676\narmv7neon_mmm_f32_8x6_generic 24 128 18 0.000056743692014886694\narmv7neon_mmm_f32_8x4_cortexa9 8 32 8 0.0000034133387591211906\narmv7neon_mmm_f32_8x4_generic 9 128 12 0.000027800478110813017\ngeneric_f32_4x4 7 32 7 0.000005509675967097076\narmv7neon_mmm_f32_8x6_cortexa9 16 32 13 0.000012310372306220252\narmv7neon_mmm_f32_8x4_generic 15 128 13 0.00003762533200230424\ngeneric_f32_4x4 4 128 5 0.000007369710897637148\narmv7neon_mmm_f32_8x6_generic 16 128 6 0.000012947918354537283\narmv7neon_mmm_f32_8x4_cortexa7 8 32 3 0.000002138296476186818\narmv7neon_mmm_f32_8x4_generic 9 128 11 0.000028058363419186727\narmv7neon_mmm_f32_8x4_cortexa7 7 128 13 0.00002008308819140427\narmv7neon_mmm_f32_8x6_cortexa9 15 128 12 0.000027068411466415602\narmv7neon_mmm_f32_8x6_generic 8 128 12 0.000012940488826817833\narmv7neon_mmm_f32_8x4_generic 24 32 7 0.00000940385598649166\narmv7neon_mmm_f32_8x6_cortexa7 15 128 19 0.00005293187157460067\narmv7neon_mmm_f32_8x4_cortexa9 25 128 3 0.000020446918539621497\narmv7neon_mmm_f32_8x4_cortexa7 17 4 9 0.000005534537134563046\narmv7neon_mmm_f32_8x6_cortexa9 15 128 19 0.00005366369135659099\narmv7neon_mmm_f32_8x4_generic 25 4 4 0.000002522928321866339\narmv7neon_mmm_f32_8x6_generic 16 4 19 0.000005280876227150452\narmv7neon_mmm_f32_8x6_generic 23 128 12 0.000038470533759474454\narmv7neon_mmm_f32_8x4_generic 16 32 3 0.00000367665371967417\narmv7neon_mmm_f32_8x6_cortexa7 7 32 12 0.000004702345831209106\narmv7neon_mmm_f32_8x6_generic 16 32 11 0.000008300619599261807\narmv7neon_mmm_f32_8x4_cortexa9 23 4 5 0.000004136206632272795\narmv7neon_mmm_f32_8x4_cortexa9 23 32 7 0.000010130368574328868\narmv7neon_mmm_f32_8x4_generic 7 128 12 0.000014642209408823472\narmv7neon_mmm_f32_8x4_generic 16 4 3 0.0000018668229452058654\narmv7neon_mmm_f32_8x4_cortexa9 24 128 8 0.00002933769331717579\narmv7neon_mmm_f32_8x4_cortexa9 15 32 5 0.0000068812833008684075\narmv7neon_mmm_f32_8x6_generic 16 4 17 0.000004317183704154257\narmv7neon_mmm_f32_8x6_cortexa9 17 32 7 0.000012443561351360363\ngeneric_f32_4x4 13 128 7 0.000028155411441347644\narmv7neon_mmm_f32_8x6_cortexa9 25 4 12 0.000005357355009390057\narmv7neon_mmm_f32_8x6_cortexa7 9 128 18 0.00003939231793264078\ngeneric_f32_4x4 9 128 13 0.00004157559429631316\narmv7neon_mmm_f32_8x6_cortexa9 23 32 11 0.000012894467716591857\narmv7neon_mmm_f32_8x6_generic 17 32 19 0.000023157961942761285\narmv7neon_mmm_f32_8x6_generic 23 128 13 0.00005762004261565962\narmv7neon_mmm_f32_8x6_generic 25 32 12 0.000015419463508057784\narmv7neon_mmm_f32_8x6_generic 23 32 19 0.000023607314480173466\ngeneric_f32_4x4 11 4 9 0.0000056154966368798884\narmv7neon_mmm_f32_8x4_generic 23 4 3 0.000002512455070432762\narmv7neon_mmm_f32_8x6_generic 17 128 12 0.000038205761728052566\narmv7neon_mmm_f32_8x6_cortexa9 25 4 6 0.0000029441583370270357\narmv7neon_mmm_f32_8x6_cortexa7 8 128 5 0.00000718614713533821\narmv7neon_mmm_f32_8x6_cortexa9 9 32 11 0.000008580295552398777\narmv7neon_mmm_f32_8x4_generic 17 4 8 0.0000036027501506567706\narmv7neon_mmm_f32_8x4_cortexa9 24 128 12 0.000043681280972474085\narmv7neon_mmm_f32_8x6_cortexa7 23 128 19 0.00007892740320659124\narmv7neon_mmm_f32_8x4_cortexa7 7 4 7 0.000001867957586629068\narmv7neon_mmm_f32_8x4_cortexa7 17 128 3 0.000015105393976853567\narmv7neon_mmm_f32_8x6_cortexa9 16 32 5 0.000004737870196682113\narmv7neon_mmm_f32_8x6_cortexa7 17 32 11 0.000012513663992372357\narmv7neon_mmm_f32_8x4_cortexa9 8 4 13 0.000002615891904894993\ngeneric_f32_4x4 9 128 8 0.00002092560518618388\narmv7neon_mmm_f32_8x6_cortexa9 24 4 13 0.000006118924275544352\narmv7neon_mmm_f32_8x4_cortexa9 23 128 12 0.00004443207751479167\narmv7neon_mmm_f32_8x4_cortexa9 9 32 13 0.000012776770370453443\narmv7neon_mmm_f32_8x4_cortexa7 7 128 4 0.000005416694010439176\ngeneric_f32_4x4 3 32 3 0.0000017607189056083412\ngeneric_f32_4x4 8 32 13 0.00000985693332598433\narmv7neon_mmm_f32_8x4_generic 15 128 9 0.00002827090625785394\narmv7neon_mmm_f32_8x6_generic 17 4 5 0.0000026659420735671337\narmv7neon_mmm_f32_8x6_cortexa9 7 4 19 0.000003496314614706576\ngeneric_f32_4x4 9 32 12 0.00001103802239472411\narmv7neon_mmm_f32_8x6_cortexa7 24 32 19 0.00002361513173387037\narmv7neon_mmm_f32_8x6_cortexa9 24 32 13 0.00001816124851845242\narmv7neon_mmm_f32_8x6_cortexa7 24 4 19 0.000007820954390784165\narmv7neon_mmm_f32_8x4_cortexa9 16 4 9 0.0000037661558813876353\narmv7neon_mmm_f32_8x6_generic 25 4 13 0.000007941364297025098\narmv7neon_mmm_f32_8x6_cortexa7 23 128 7 0.0000397790896264826\narmv7neon_mmm_f32_8x4_cortexa7 23 32 12 0.000014064248486107495\narmv7neon_mmm_f32_8x6_cortexa9 25 32 17 0.000024451766821636896\narmv7neon_mmm_f32_8x4_generic 16 32 13 0.000011863952520137507\narmv7neon_mmm_f32_8x6_cortexa9 15 4 13 0.000004662604065748643\narmv7neon_mmm_f32_8x4_cortexa7 16 128 13 0.00003851383301865242\narmv7neon_mmm_f32_8x4_cortexa9 17 4 3 0.0000024871767077518544\narmv7neon_mmm_f32_8x6_generic 9 128 18 0.000038275747204244\narmv7neon_mmm_f32_8x4_cortexa7 23 4 7 0.000004236340851810833\narmv7neon_mmm_f32_8x4_generic 24 32 12 0.000012855667391841598\narmv7neon_mmm_f32_8x6_cortexa9 15 32 17 0.000012917112497463966\narmv7neon_mmm_f32_8x4_generic 23 128 11 0.000042177094013740186\narmv7neon_mmm_f32_8x4_cortexa7 16 128 12 0.000028624335558632763\narmv7neon_mmm_f32_8x6_cortexa7 23 4 17 0.000006797176857939227\narmv7neon_mmm_f32_8x4_generic 25 4 3 0.0000031059316600251953\narmv7neon_mmm_f32_8x6_cortexa7 23 4 19 0.000008441504854510506\narmv7neon_mmm_f32_8x6_cortexa7 23 128 17 0.00005957716872417335\narmv7neon_mmm_f32_8x4_generic 24 32 3 0.000005255472766158138\narmv7neon_mmm_f32_8x6_cortexa7 7 32 6 0.0000025969460967210575\ngeneric_f32_4x4 12 128 9 0.00003118081783563989\narmv7neon_mmm_f32_8x6_cortexa7 24 32 7 0.000012316039376583595\narmv7neon_mmm_f32_8x4_cortexa9 9 128 11 0.00002984202658897139\narmv7neon_mmm_f32_8x6_generic 24 4 12 0.00000390465698063092\narmv7neon_mmm_f32_8x6_generic 24 128 5 0.000019825223712031248\ngeneric_f32_4x4 11 128 12 0.00003129955987835105\narmv7neon_mmm_f32_8x4_generic 15 32 8 0.000006474371340367591\narmv7neon_mmm_f32_8x6_cortexa7 25 128 17 0.00007864370121172857\narmv7neon_mmm_f32_8x4_cortexa7 7 4 9 0.000002506847196975368\narmv7neon_mmm_f32_8x6_cortexa9 25 4 11 0.000005933707367961421\narmv7neon_mmm_f32_8x4_cortexa9 16 4 5 0.000002787654264318038\narmv7neon_mmm_f32_8x6_generic 17 32 13 0.00001761805014832438\narmv7neon_mmm_f32_8x4_cortexa9 7 32 4 0.0000021905945506812955\narmv7neon_mmm_f32_8x6_cortexa9 17 128 11 0.00004027130474721357\ngeneric_f32_4x4 7 32 9 0.000007912119634032119\narmv7neon_mmm_f32_8x4_generic 23 4 13 0.0000073828945636745854\ngeneric_f32_4x4 12 128 11 0.00003129061011102201\narmv7neon_mmm_f32_8x6_cortexa7 9 4 5 0.0000019755012039429016\narmv7neon_mmm_f32_8x6_cortexa9 8 4 13 0.000002405709930798169\narmv7neon_mmm_f32_8x6_cortexa9 24 4 7 0.0000044131675061571695\narmv7neon_mmm_f32_8x4_cortexa7 25 128 3 0.00002004605546076412\narmv7neon_mmm_f32_8x6_cortexa7 25 32 11 0.000016504451247846665\ngeneric_f32_4x4 11 4 3 0.0000024035209703323816\ngeneric_f32_4x4 5 128 9 0.00002116685987655445\narmv7neon_mmm_f32_8x4_generic 9 32 9 0.000009310383786805071\narmv7neon_mmm_f32_8x6_cortexa7 15 32 12 0.00000858295982977344\ngeneric_f32_4x4 9 4 9 0.0000055122591860013075\narmv7neon_mmm_f32_8x4_cortexa7 24 128 4 0.000014582241975346975\narmv7neon_mmm_f32_8x6_cortexa7 7 32 11 0.0000047102983594351624\narmv7neon_mmm_f32_8x6_cortexa7 17 32 5 0.000006703902999940797\narmv7neon_mmm_f32_8x4_cortexa9 9 32 12 0.000009569569775041666\ngeneric_f32_4x4 11 32 4 0.000004102122527868448\narmv7neon_mmm_f32_8x4_cortexa7 23 128 11 0.00004393294052475175\narmv7neon_mmm_f32_8x6_cortexa7 17 128 18 0.00005867315609945908\narmv7neon_mmm_f32_8x6_generic 15 4 19 0.000005888722731281036\narmv7neon_mmm_f32_8x4_cortexa9 25 128 12 0.000058416478283637004\narmv7neon_mmm_f32_8x6_cortexa7 15 128 18 0.00003981402002143062\narmv7neon_mmm_f32_8x6_generic 17 32 11 0.000012156545175144957\narmv7neon_mmm_f32_8x6_cortexa9 17 128 5 0.000020505815042923473\narmv7neon_mmm_f32_8x4_generic 23 4 8 0.0000037989035755978144\narmv7neon_mmm_f32_8x4_cortexa9 23 128 4 0.000015133679307570648\ngeneric_f32_4x4 12 32 3 0.000004382513702192395\ngeneric_f32_4x4 12 4 5 0.000003824564176783666\ngeneric_f32_4x4 5 32 7 0.000005434572832921092\ngeneric_f32_4x4 3 4 8 0.0000017566767353451639\narmv7neon_mmm_f32_8x6_cortexa7 7 32 13 0.0000067229229930212575\narmv7neon_mmm_f32_8x4_generic 8 4 13 0.0000025649388423337978\ngeneric_f32_4x4 11 32 12 0.000011236073634740423\narmv7neon_mmm_f32_8x6_generic 25 4 17 0.000008196880964751204\narmv7neon_mmm_f32_8x6_cortexa9 7 32 11 0.000004753650782271882\narmv7neon_mmm_f32_8x4_cortexa9 7 128 4 0.000005529569938400554\ngeneric_f32_4x4 13 4 13 0.000009131495505295274\narmv7neon_mmm_f32_8x6_cortexa9 9 4 7 0.00000312421222650566\narmv7neon_mmm_f32_8x6_generic 17 32 7 0.000011970222410677647\narmv7neon_mmm_f32_8x4_cortexa7 7 4 3 0.0000011523161892903704\narmv7neon_mmm_f32_8x6_cortexa7 23 128 18 0.00005908293678660039\narmv7neon_mmm_f32_8x4_cortexa9 23 4 13 0.000007508665249371487\narmv7neon_mmm_f32_8x4_cortexa9 7 32 8 0.000003875838102020609\narmv7neon_mmm_f32_8x6_cortexa7 24 32 11 0.00001256332295885272\narmv7neon_mmm_f32_8x6_cortexa9 23 128 6 0.000020319056425196668\narmv7neon_mmm_f32_8x4_generic 23 32 4 0.00000488125169723422\narmv7neon_mmm_f32_8x6_generic 24 32 18 0.00001705594140661088\narmv7neon_mmm_f32_8x4_generic 7 4 3 0.000001143118878212571\narmv7neon_mmm_f32_8x4_generic 24 4 3 0.000002536821660220204\narmv7neon_mmm_f32_8x6_cortexa9 7 4 17 0.000002840883149941229\narmv7neon_mmm_f32_8x6_cortexa7 17 32 13 0.000018113836070828896\narmv7neon_mmm_f32_8x6_cortexa7 24 4 5 0.000002893802636827323\narmv7neon_mmm_f32_8x4_cortexa9 7 128 8 0.00001055308814900546\narmv7neon_mmm_f32_8x4_cortexa9 15 4 4 0.0000017017415253459248\narmv7neon_mmm_f32_8x6_generic 9 128 17 0.00003847119775045202\narmv7neon_mmm_f32_8x4_cortexa9 17 128 7 0.00003001254835210308\ngeneric_f32_4x4 7 128 3 0.0000075145364744355526\narmv7neon_mmm_f32_8x6_cortexa9 24 128 13 0.00005963153865876477\narmv7neon_mmm_f32_8x6_generic 16 4 11 0.0000031955847464919697\narmv7neon_mmm_f32_8x6_generic 24 128 17 0.000057273707239946334\narmv7neon_mmm_f32_8x6_cortexa9 16 128 17 0.00004016422558583101\narmv7neon_mmm_f32_8x4_generic 24 4 7 0.000003971324732226714\narmv7neon_mmm_f32_8x6_cortexa9 25 32 11 0.0000166679830493271\narmv7neon_mmm_f32_8x4_generic 7 128 3 0.000005169798410902207\narmv7neon_mmm_f32_8x6_cortexa7 15 4 11 0.000003482445418673263\narmv7neon_mmm_f32_8x6_cortexa7 8 128 6 0.0000069109869984566186\narmv7neon_mmm_f32_8x4_cortexa9 24 32 13 0.000018445950950001155\narmv7neon_mmm_f32_8x6_generic 8 4 11 0.0000018570966900607452\narmv7neon_mmm_f32_8x6_cortexa9 24 4 6 0.000002228168146536169\narmv7neon_mmm_f32_8x6_generic 8 128 7 0.000013146479087947972\narmv7neon_mmm_f32_8x6_cortexa9 25 32 12 0.000016067383961246167\narmv7neon_mmm_f32_8x4_cortexa9 16 32 5 0.000006662778599544951\narmv7neon_mmm_f32_8x6_generic 9 4 11 0.0000031451298378122295\narmv7neon_mmm_f32_8x4_generic 16 32 7 0.000006438918321306648\narmv7neon_mmm_f32_8x6_cortexa9 7 32 7 0.000004657116192635584\narmv7neon_mmm_f32_8x6_generic 17 32 12 0.00001176030032709091\narmv7neon_mmm_f32_8x4_generic 15 4 8 0.0000028163110410039656\narmv7neon_mmm_f32_8x4_cortexa9 7 4 9 0.0000025260788973778824\ngeneric_f32_4x4 8 4 11 0.000003795516523798154\narmv7neon_mmm_f32_8x6_generic 15 32 6 0.0000044168833936544535\narmv7neon_mmm_f32_8x4_cortexa7 8 128 9 0.000014761720958613642\narmv7neon_mmm_f32_8x6_cortexa7 9 32 7 0.000008410754850495816\narmv7neon_mmm_f32_8x4_cortexa7 16 4 13 0.000004684953060253782\narmv7neon_mmm_f32_8x4_generic 16 32 5 0.000006354175934259964\narmv7neon_mmm_f32_8x4_generic 24 4 4 0.0000019148396982311507\narmv7neon_mmm_f32_8x4_generic 17 128 4 0.000014107689106847269\narmv7neon_mmm_f32_8x6_cortexa9 24 32 11 0.000012681731796133347\ngeneric_f32_4x4 3 4 5 0.0000017351550450721446\narmv7neon_mmm_f32_8x4_cortexa7 16 4 9 0.0000037512996244278985\narmv7neon_mmm_f32_8x6_cortexa9 23 32 13 0.000018600736039744996\ngeneric_f32_4x4 7 32 13 0.000010363567629711427\ngeneric_f32_4x4 7 128 13 0.000028209372228861907\narmv7neon_mmm_f32_8x6_generic 24 32 13 0.000017489604886914003\narmv7neon_mmm_f32_8x4_cortexa7 24 32 8 0.00000906737294441132\narmv7neon_mmm_f32_8x6_generic 15 32 5 0.000004544284306242495\narmv7neon_mmm_f32_8x6_cortexa9 16 128 19 0.00005301309753082383\narmv7neon_mmm_f32_8x6_cortexa7 15 128 11 0.000026895954754680612\narmv7neon_mmm_f32_8x4_cortexa9 15 32 12 0.000009927381424295026\narmv7neon_mmm_f32_8x4_cortexa9 16 128 12 0.00002922630339731252\narmv7neon_mmm_f32_8x4_cortexa9 24 4 11 0.000005481062579336857\narmv7neon_mmm_f32_8x6_generic 7 128 18 0.000019821467429857938\narmv7neon_mmm_f32_8x6_cortexa9 7 32 5 0.000002575926161215081\narmv7neon_mmm_f32_8x6_cortexa7 15 32 7 0.000008595071585263579\narmv7neon_mmm_f32_8x4_cortexa9 17 32 4 0.000005001583148868544\narmv7neon_mmm_f32_8x4_cortexa7 25 32 4 0.000006334740769256929\narmv7neon_mmm_f32_8x4_cortexa7 25 128 7 0.00003905527355458101\ngeneric_f32_4x4 4 4 5 0.0000016159570864189527\narmv7neon_mmm_f32_8x4_cortexa9 17 128 11 0.000044522329607090446\narmv7neon_mmm_f32_8x6_cortexa9 8 128 18 0.0000200309695734825\narmv7neon_mmm_f32_8x6_generic 24 32 19 0.000022973011330361666\ngeneric_f32_4x4 3 4 13 0.000002984204583865987\narmv7neon_mmm_f32_8x6_cortexa9 15 4 5 0.000002015871636210361\ngeneric_f32_4x4 7 4 9 0.000003999946420645267\narmv7neon_mmm_f32_8x4_generic 7 4 9 0.0000024834088306212943\narmv7neon_mmm_f32_8x4_cortexa7 25 128 9 0.00005782975703195741\narmv7neon_mmm_f32_8x4_generic 25 32 8 0.000011779346411467398\narmv7neon_mmm_f32_8x6_cortexa9 25 32 7 0.000016399753838712434\narmv7neon_mmm_f32_8x6_generic 24 4 6 0.000002204607070591687\narmv7neon_mmm_f32_8x6_cortexa7 9 32 17 0.000012402833540807695\narmv7neon_mmm_f32_8x4_cortexa7 25 128 13 0.00007672799293438395\narmv7neon_mmm_f32_8x6_generic 24 128 12 0.0000379228420372398\narmv7neon_mmm_f32_8x4_cortexa7 16 128 3 0.00001030544963732427\narmv7neon_mmm_f32_8x4_generic 15 128 3 0.000009885955298583161\narmv7neon_mmm_f32_8x4_generic 15 4 12 0.0000039769457516235065\narmv7neon_mmm_f32_8x6_generic 23 4 11 0.000004727342544050073\narmv7neon_mmm_f32_8x6_cortexa7 15 4 12 0.0000032858094110604857\narmv7neon_mmm_f32_8x4_cortexa7 16 4 3 0.0000018824484779381146\narmv7neon_mmm_f32_8x6_cortexa9 23 4 7 0.000004580846920018228\ngeneric_f32_4x4 11 4 8 0.00000379091798312253\narmv7neon_mmm_f32_8x6_cortexa9 23 32 18 0.00001843667314281391\narmv7neon_mmm_f32_8x6_generic 8 32 18 0.0000060026095209057905\narmv7neon_mmm_f32_8x4_cortexa7 8 32 7 0.00000356603711659208\narmv7neon_mmm_f32_8x6_generic 15 128 18 0.00003865113024109526\narmv7neon_mmm_f32_8x6_generic 7 32 5 0.0000025012501670870884\ngeneric_f32_4x4 12 128 3 0.00001108291043110956\narmv7neon_mmm_f32_8x6_cortexa9 9 128 17 0.00004019255842003589\narmv7neon_mmm_f32_8x4_cortexa9 15 128 3 0.000010489952376847188\narmv7neon_mmm_f32_8x4_cortexa9 7 4 5 0.0000018283099982388463\narmv7neon_mmm_f32_8x6_cortexa7 23 32 13 0.000018410568030938504\narmv7neon_mmm_f32_8x6_generic 15 4 17 0.000004759242853957918\narmv7neon_mmm_f32_8x6_cortexa7 17 128 5 0.00002027503703993735\narmv7neon_mmm_f32_8x4_generic 7 4 12 0.000002526741586689988\narmv7neon_mmm_f32_8x6_cortexa9 17 4 13 0.000006256702206265106\narmv7neon_mmm_f32_8x6_cortexa7 16 128 13 0.000039449772577428465\ngeneric_f32_4x4 5 32 4 0.0000028896370502300425\narmv7neon_mmm_f32_8x6_cortexa9 23 4 6 0.000002496256395769302\narmv7neon_mmm_f32_8x4_generic 17 4 9 0.000005469161034223508\narmv7neon_mmm_f32_8x4_generic 17 4 13 0.0000070099392970628585\narmv7neon_mmm_f32_8x6_generic 24 4 17 0.000006212264617752923\narmv7neon_mmm_f32_8x6_cortexa7 23 32 18 0.000018244504523002484\narmv7neon_mmm_f32_8x4_cortexa7 17 32 3 0.000005314636591625876\narmv7neon_mmm_f32_8x6_generic 23 4 13 0.00000640173388890707\narmv7neon_mmm_f32_8x6_cortexa7 25 4 19 0.00001046601238496823\narmv7neon_mmm_f32_8x4_cortexa7 9 128 5 0.000019669882407282677\ngeneric_f32_4x4 5 4 3 0.0000017330186385522962\narmv7neon_mmm_f32_8x6_generic 9 4 13 0.000004297017223755962\narmv7neon_mmm_f32_8x4_cortexa9 9 128 7 0.0000201239354954616\narmv7neon_mmm_f32_8x4_cortexa7 8 4 7 0.0000016739241733780589\narmv7neon_mmm_f32_8x6_cortexa7 7 4 6 0.000001271018362845475\ngeneric_f32_4x4 13 32 9 0.000014839920846066823\narmv7neon_mmm_f32_8x4_generic 25 4 9 0.000007058701573468786\narmv7neon_mmm_f32_8x4_cortexa7 7 32 5 0.000003729557835063842\narmv7neon_mmm_f32_8x6_cortexa9 7 128 5 0.000007173464418699434\narmv7neon_mmm_f32_8x6_cortexa7 24 128 13 0.00005881823134405547\narmv7neon_mmm_f32_8x6_cortexa9 17 32 12 0.000012231167345130436\narmv7neon_mmm_f32_8x4_cortexa7 8 4 4 0.0000009861752945826601\narmv7neon_mmm_f32_8x4_cortexa9 23 128 3 0.00001548775471015623\narmv7neon_mmm_f32_8x4_generic 8 128 9 0.000014166892351007583\narmv7neon_mmm_f32_8x4_cortexa9 25 32 3 0.000007055926590582058\narmv7neon_mmm_f32_8x6_cortexa7 8 32 6 0.0000023905642959740767\narmv7neon_mmm_f32_8x4_generic 24 4 12 0.000004705581936932163\ngeneric_f32_4x4 13 32 13 0.000019466458499229495\narmv7neon_mmm_f32_8x6_cortexa7 8 128 12 0.000013354945269855691\ngeneric_f32_4x4 4 32 8 0.000002784564616216757\ngeneric_f32_4x4 11 4 13 0.000007235980721358389\narmv7neon_mmm_f32_8x4_cortexa9 15 4 9 0.000004211132231298468\narmv7neon_mmm_f32_8x4_generic 23 4 7 0.000004197975758631663\narmv7neon_mmm_f32_8x4_generic 24 4 13 0.000006669346538195287\ngeneric_f32_4x4 9 4 11 0.000005605492408222339\narmv7neon_mmm_f32_8x4_cortexa9 7 4 7 0.0000018866714926916992\narmv7neon_mmm_f32_8x6_generic 7 4 12 0.00000203404671528183\narmv7neon_mmm_f32_8x6_cortexa9 16 32 12 0.000008156581758904169\narmv7neon_mmm_f32_8x6_generic 9 4 19 0.000005498641044027106\ngeneric_f32_4x4 7 4 8 0.000002762174951002869\narmv7neon_mmm_f32_8x4_cortexa9 16 4 3 0.0000019059695727093323\narmv7neon_mmm_f32_8x4_generic 25 4 12 0.00000651099979096713\narmv7neon_mmm_f32_8x6_cortexa7 24 128 5 0.000020414031398736494\narmv7neon_mmm_f32_8x6_cortexa7 7 128 18 0.000020361101695155362\ngeneric_f32_4x4 11 128 5 0.00002125700072345171\narmv7neon_mmm_f32_8x6_cortexa7 17 32 18 0.000017837908064150246\narmv7neon_mmm_f32_8x6_cortexa7 25 32 13 0.00002390699968855355\narmv7neon_mmm_f32_8x4_generic 23 4 5 0.0000040641271507242515\ngeneric_f32_4x4 8 4 3 0.0000017865876036353628\narmv7neon_mmm_f32_8x6_cortexa9 16 4 17 0.00000442887972122732\narmv7neon_mmm_f32_8x4_cortexa9 25 32 8 0.000012384930380190053\ngeneric_f32_4x4 5 4 5 0.000002798692518373374\narmv7neon_mmm_f32_8x6_cortexa9 8 128 6 0.0000070012080134920035\narmv7neon_mmm_f32_8x6_cortexa7 17 32 6 0.000006327458516647519\narmv7neon_mmm_f32_8x4_cortexa7 24 32 7 0.0000097071140215999\narmv7neon_mmm_f32_8x6_generic 23 32 12 0.000012023256652212629\narmv7neon_mmm_f32_8x6_generic 9 32 11 0.000008252674922417165\narmv7neon_mmm_f32_8x6_generic 9 32 6 0.000004271117675286997\narmv7neon_mmm_f32_8x4_cortexa7 7 4 4 0.0000011809046933853917\narmv7neon_mmm_f32_8x6_cortexa7 15 32 19 0.00001661331304395181\narmv7neon_mmm_f32_8x4_generic 25 128 5 0.00003728262011412803\narmv7neon_mmm_f32_8x6_cortexa9 17 128 19 0.00007942199229108003\ngeneric_f32_4x4 11 128 9 0.000031521930244918895\narmv7neon_mmm_f32_8x6_cortexa7 25 4 6 0.000002951526384640025\narmv7neon_mmm_f32_8x6_generic 25 128 18 0.00007559930226918245\narmv7neon_mmm_f32_8x6_generic 24 32 7 0.000011956301096424158\narmv7neon_mmm_f32_8x4_cortexa7 24 4 4 0.000001942280219822285\narmv7neon_mmm_f32_8x4_cortexa7 16 4 5 0.000002773541103005484\ngeneric_f32_4x4 13 4 7 0.000005120137461912069\narmv7neon_mmm_f32_8x4_generic 9 4 9 0.000003880248068324046\narmv7neon_mmm_f32_8x4_cortexa9 8 4 5 0.0000016579504301247745\narmv7neon_mmm_f32_8x6_cortexa7 7 32 18 0.000006779881862785529\narmv7neon_mmm_f32_8x4_generic 24 32 9 0.000013438884904462624\narmv7neon_mmm_f32_8x4_generic 17 32 11 0.000013721051068287207\narmv7neon_mmm_f32_8x4_cortexa7 23 32 3 0.000005392987682793705\narmv7neon_mmm_f32_8x6_cortexa9 17 4 18 0.000005996181221285945\narmv7neon_mmm_f32_8x4_generic 23 128 12 0.000041706025206470595\narmv7neon_mmm_f32_8x4_generic 25 128 7 0.00003743774855697319\ngeneric_f32_4x4 3 4 12 0.000002379029629256848\narmv7neon_mmm_f32_8x6_cortexa9 24 128 18 0.00005914765901143539\narmv7neon_mmm_f32_8x4_cortexa7 23 32 5 0.00000981049054917298\narmv7neon_mmm_f32_8x6_cortexa7 16 4 19 0.000005412079670267658\narmv7neon_mmm_f32_8x4_cortexa7 8 32 4 0.0000019274117652778804\narmv7neon_mmm_f32_8x6_generic 17 128 6 0.000019306808921524692\narmv7neon_mmm_f32_8x6_cortexa7 7 128 12 0.000013761467144605992\narmv7neon_mmm_f32_8x6_cortexa7 16 32 7 0.000008404964853562951\narmv7neon_mmm_f32_8x6_cortexa9 9 32 7 0.000008475033766481017\narmv7neon_mmm_f32_8x6_cortexa9 15 128 5 0.000013894752583522413\narmv7neon_mmm_f32_8x6_generic 16 128 17 0.000038424998460484644\narmv7neon_mmm_f32_8x4_cortexa7 8 32 9 0.00000497069436204451\narmv7neon_mmm_f32_8x6_generic 9 32 5 0.000004444687551155496\narmv7neon_mmm_f32_8x4_cortexa7 24 32 5 0.00000958372113096483\narmv7neon_mmm_f32_8x6_cortexa9 24 4 12 0.000003962114122866607\narmv7neon_mmm_f32_8x4_generic 15 4 11 0.000004231967658632821\narmv7neon_mmm_f32_8x6_cortexa7 24 32 13 0.000017973816622821935\narmv7neon_mmm_f32_8x4_cortexa9 23 32 13 0.000019157368846217136\narmv7neon_mmm_f32_8x4_generic 16 32 9 0.00000912682021206548\narmv7neon_mmm_f32_8x6_generic 16 32 5 0.000004588157612379825\narmv7neon_mmm_f32_8x6_generic 16 4 13 0.0000041677198842138695\narmv7neon_mmm_f32_8x6_generic 15 128 17 0.00003886891043603058\narmv7neon_mmm_f32_8x6_cortexa9 7 128 11 0.000013961383587786538\narmv7neon_mmm_f32_8x4_generic 7 128 5 0.000009860600106626588\narmv7neon_mmm_f32_8x4_cortexa9 16 4 12 0.0000033867538649589902\narmv7neon_mmm_f32_8x6_cortexa9 17 32 19 0.000024087066037173788\narmv7neon_mmm_f32_8x6_generic 9 128 5 0.000013189504068813325\narmv7neon_mmm_f32_8x6_cortexa9 8 32 18 0.000006222642744442069\narmv7neon_mmm_f32_8x6_cortexa7 24 32 12 0.000011846425063097506\ngeneric_f32_4x4 5 4 13 0.000004993325421206121\narmv7neon_mmm_f32_8x6_generic 8 128 11 0.000013157428320463883\ngeneric_f32_4x4 7 32 4 0.0000029484328830194284\narmv7neon_mmm_f32_8x4_cortexa9 7 32 9 0.000005475212517453128\narmv7neon_mmm_f32_8x4_cortexa9 9 128 13 0.00003949164898061412\narmv7neon_mmm_f32_8x4_cortexa9 23 4 11 0.000005947124934955378\narmv7neon_mmm_f32_8x6_generic 23 32 11 0.00001239533186039336\narmv7neon_mmm_f32_8x6_generic 7 32 17 0.000006661670995420206\narmv7neon_mmm_f32_8x4_cortexa7 7 4 8 0.0000018776318580783529\narmv7neon_mmm_f32_8x6_generic 8 32 5 0.0000025552685069830137\narmv7neon_mmm_f32_8x4_cortexa9 9 32 9 0.000009774693013502346\ngeneric_f32_4x4 13 128 5 0.000027995490696525238\narmv7neon_mmm_f32_8x4_cortexa9 8 4 11 0.0000021722237770380927\narmv7neon_mmm_f32_8x4_generic 23 128 9 0.0000419391925884448\narmv7neon_mmm_f32_8x4_cortexa9 25 32 7 0.000013031732285626503\narmv7neon_mmm_f32_8x4_cortexa7 25 32 12 0.000017966835523846677\narmv7neon_mmm_f32_8x4_cortexa7 16 32 11 0.000009494335431932482\narmv7neon_mmm_f32_8x6_cortexa7 17 128 19 0.0000783675036460944\narmv7neon_mmm_f32_8x6_cortexa7 17 4 7 0.0000044328101817797105\ngeneric_f32_4x4 3 32 8 0.000003088179719444515\ngeneric_f32_4x4 12 4 11 0.000005408469300584859\narmv7neon_mmm_f32_8x4_cortexa7 23 32 13 0.000018862031131309997\narmv7neon_mmm_f32_8x4_generic 23 32 11 0.000014024445889402156\ngeneric_f32_4x4 4 128 11 0.00001075990813733738\narmv7neon_mmm_f32_8x6_generic 24 4 7 0.000004301779173383875\narmv7neon_mmm_f32_8x6_cortexa9 25 128 12 0.000052850505948183045\narmv7neon_mmm_f32_8x4_generic 15 32 4 0.0000034946744383909553\narmv7neon_mmm_f32_8x4_generic 17 4 3 0.0000024363476636608272\narmv7neon_mmm_f32_8x4_cortexa7 16 128 4 0.000009891301569266948\narmv7neon_mmm_f32_8x6_cortexa9 25 4 13 0.000008105640345047328\narmv7neon_mmm_f32_8x4_cortexa9 16 4 4 0.000001487225488304516\ngeneric_f32_4x4 5 32 9 0.000007777979342543209\narmv7neon_mmm_f32_8x6_cortexa7 8 4 19 0.0000029885727357209406\ngeneric_f32_4x4 8 32 9 0.000007585061806416855\narmv7neon_mmm_f32_8x4_cortexa7 17 128 13 0.000057643034032365925\narmv7neon_mmm_f32_8x4_generic 7 128 4 0.000005216038661083481\narmv7neon_mmm_f32_8x4_cortexa7 16 32 12 0.000009049743188132303\narmv7neon_mmm_f32_8x4_cortexa9 17 4 8 0.000003682177568678712\narmv7neon_mmm_f32_8x4_cortexa9 17 32 11 0.000014400917572892256\narmv7neon_mmm_f32_8x4_cortexa9 15 4 12 0.0000040529055101759995\narmv7neon_mmm_f32_8x4_cortexa7 15 4 11 0.000004271052070252677\narmv7neon_mmm_f32_8x6_cortexa7 16 128 5 0.000013791269931666714\narmv7neon_mmm_f32_8x6_generic 8 32 7 0.000004325189484240455\ngeneric_f32_4x4 3 128 7 0.000007522472627920054\narmv7neon_mmm_f32_8x6_cortexa9 9 32 6 0.000004463650285453612\ngeneric_f32_4x4 11 32 7 0.000007948963253145964\narmv7neon_mmm_f32_8x4_cortexa9 24 128 4 0.000014890215129529216\narmv7neon_mmm_f32_8x6_cortexa9 16 4 19 0.000005414025318591343\narmv7neon_mmm_f32_8x6_cortexa7 17 4 6 0.00000238128254631235\narmv7neon_mmm_f32_8x4_cortexa9 24 4 5 0.000003916194410369112\narmv7neon_mmm_f32_8x4_cortexa9 8 128 9 0.000015067666888020707\narmv7neon_mmm_f32_8x6_cortexa7 23 32 11 0.000012775953874196187\ngeneric_f32_4x4 8 32 3 0.0000030910360857362123\narmv7neon_mmm_f32_8x4_cortexa7 7 32 3 0.0000021042104056045423\narmv7neon_mmm_f32_8x6_cortexa9 25 32 5 0.000008813546121201035\narmv7neon_mmm_f32_8x6_cortexa7 25 4 17 0.000008369215065652083\ngeneric_f32_4x4 3 32 9 0.000004340286298208673\narmv7neon_mmm_f32_8x4_generic 17 4 5 0.000003911088956924729\ngeneric_f32_4x4 9 4 13 0.000007067880206932553\narmv7neon_mmm_f32_8x4_cortexa7 8 128 13 0.00001944164572907436\narmv7neon_mmm_f32_8x4_cortexa7 25 4 8 0.000004598061889383109\narmv7neon_mmm_f32_8x4_generic 15 32 5 0.000006570904917754489\narmv7neon_mmm_f32_8x4_cortexa9 7 4 4 0.000001199774667010064\narmv7neon_mmm_f32_8x4_cortexa9 16 32 9 0.000009580069903775671\narmv7neon_mmm_f32_8x6_generic 17 4 19 0.000007879961865942272\narmv7neon_mmm_f32_8x4_cortexa7 23 128 13 0.00005805408197282095\narmv7neon_mmm_f32_8x6_generic 25 32 13 0.000023213828889712974\narmv7neon_mmm_f32_8x6_cortexa9 23 4 17 0.000006797095061554012\narmv7neon_mmm_f32_8x6_cortexa9 24 32 6 0.000006232031189380453\narmv7neon_mmm_f32_8x4_cortexa7 15 128 11 0.00002955574134898906\narmv7neon_mmm_f32_8x6_cortexa9 24 4 11 0.000004626561901203258\narmv7neon_mmm_f32_8x6_cortexa9 8 4 7 0.0000018344586562526734\narmv7neon_mmm_f32_8x6_generic 15 4 18 0.000004518763751153566\narmv7neon_mmm_f32_8x6_cortexa9 16 128 11 0.000027030003785723685\narmv7neon_mmm_f32_8x4_cortexa7 9 4 4 0.0000015888326511808313\narmv7neon_mmm_f32_8x4_cortexa7 7 4 12 0.0000025479152659232096\narmv7neon_mmm_f32_8x4_cortexa7 23 128 7 0.000029563915293732792\narmv7neon_mmm_f32_8x4_cortexa9 25 4 13 0.000009216194666264275\ngeneric_f32_4x4 4 4 7 0.0000016464048874437295\narmv7neon_mmm_f32_8x4_cortexa7 7 128 11 0.00001525304959159917\narmv7neon_mmm_f32_8x4_cortexa9 9 4 9 0.000003956900236836743\ngeneric_f32_4x4 4 128 8 0.000007233357504684728\narmv7neon_mmm_f32_8x4_cortexa9 16 128 9 0.000029612198853914824\narmv7neon_mmm_f32_8x4_generic 15 128 12 0.000028154522908512787\narmv7neon_mmm_f32_8x6_cortexa9 8 128 5 0.000007227187608771384\narmv7neon_mmm_f32_8x4_cortexa9 25 128 7 0.000039766026339765396\ngeneric_f32_4x4 9 128 5 0.00002114949804637606\narmv7neon_mmm_f32_8x4_generic 17 32 4 0.000004758199768605913\narmv7neon_mmm_f32_8x6_cortexa9 7 4 13 0.0000027386176506007003\ngeneric_f32_4x4 7 32 8 0.000005375358081458721\narmv7neon_mmm_f32_8x6_cortexa9 8 4 18 0.000002220900812833037\narmv7neon_mmm_f32_8x4_cortexa9 16 128 11 0.000029669482734966696\narmv7neon_mmm_f32_8x6_cortexa7 8 32 18 0.000006161276860676508\narmv7neon_mmm_f32_8x4_cortexa7 8 32 8 0.000003353396080834466\narmv7neon_mmm_f32_8x6_generic 16 128 13 0.000038174416411990654\narmv7neon_mmm_f32_8x6_generic 15 32 19 0.00001612698422472605\narmv7neon_mmm_f32_8x4_cortexa9 9 128 3 0.000010402587362113396\narmv7neon_mmm_f32_8x6_cortexa9 9 4 11 0.0000032196424297717835\narmv7neon_mmm_f32_8x4_cortexa9 8 128 11 0.000015086238127499093\narmv7neon_mmm_f32_8x6_generic 16 128 19 0.00005061640609152132\narmv7neon_mmm_f32_8x4_generic 16 128 9 0.00002782092751958622\ngeneric_f32_4x4 4 32 4 0.000001643238458184986\narmv7neon_mmm_f32_8x6_cortexa7 17 4 11 0.0000046002558984855774\ngeneric_f32_4x4 5 4 9 0.0000039003390490700075\ngeneric_f32_4x4 9 128 7 0.000021241983402647673\ngeneric_f32_4x4 7 4 13 0.000005155057897108874\narmv7neon_mmm_f32_8x6_generic 17 4 17 0.000006303527455540143\narmv7neon_mmm_f32_8x4_generic 16 4 13 0.000004620655887935485\narmv7neon_mmm_f32_8x4_cortexa7 8 128 5 0.000010060854723538398\narmv7neon_mmm_f32_8x4_cortexa9 16 32 11 0.000009653502461801779\narmv7neon_mmm_f32_8x6_generic 23 128 5 0.00001973212006224306\narmv7neon_mmm_f32_8x4_generic 8 4 7 0.000001657225281211141\narmv7neon_mmm_f32_8x6_generic 16 4 12 0.0000027740993216529004\narmv7neon_mmm_f32_8x6_cortexa9 15 128 18 0.00004029185998372536\narmv7neon_mmm_f32_8x4_cortexa9 25 32 4 0.000006456592320044242\narmv7neon_mmm_f32_8x6_cortexa7 25 128 12 0.000052118173447989496\narmv7neon_mmm_f32_8x4_cortexa7 25 32 9 0.000018537854346104505\narmv7neon_mmm_f32_8x6_cortexa7 16 4 7 0.000003147284440858119\narmv7neon_mmm_f32_8x4_generic 17 128 5 0.000028048493049013062\narmv7neon_mmm_f32_8x6_cortexa7 23 4 18 0.000006335447404352516\narmv7neon_mmm_f32_8x6_cortexa9 24 4 19 0.000007813810057423332\narmv7neon_mmm_f32_8x4_cortexa7 17 128 8 0.000028915520714406696\narmv7neon_mmm_f32_8x4_cortexa7 17 128 7 0.000029363768223094683\ngeneric_f32_4x4 3 32 11 0.0000043748460947431475\narmv7neon_mmm_f32_8x4_cortexa9 9 128 5 0.0000200735894165016\narmv7neon_mmm_f32_8x4_cortexa7 17 4 3 0.000002458111098809617\ngeneric_f32_4x4 5 128 11 0.0000211987993849004\ngeneric_f32_4x4 12 32 7 0.00000782487986470221\narmv7neon_mmm_f32_8x4_generic 7 128 11 0.000014659497491826627\narmv7neon_mmm_f32_8x6_cortexa7 17 128 11 0.00003969667780371537\narmv7neon_mmm_f32_8x6_generic 7 4 6 0.0000012612776856940052\narmv7neon_mmm_f32_8x6_cortexa9 7 4 6 0.0000012711169589040185\narmv7neon_mmm_f32_8x6_cortexa7 8 4 13 0.0000024259808837385263\narmv7neon_mmm_f32_8x4_cortexa7 24 128 9 0.000043285670969312205\narmv7neon_mmm_f32_8x6_generic 15 128 19 0.00005126058311511034\ngeneric_f32_4x4 13 128 11 0.00004170340313011054\narmv7neon_mmm_f32_8x6_cortexa9 8 128 12 0.000013510136481590433\narmv7neon_mmm_f32_8x6_generic 23 32 17 0.00001815155326925384\narmv7neon_mmm_f32_8x4_generic 24 32 8 0.000008758373656325976\narmv7neon_mmm_f32_8x4_generic 15 32 13 0.000012566353080909321\narmv7neon_mmm_f32_8x4_generic 15 4 9 0.000004138286673960469\narmv7neon_mmm_f32_8x4_cortexa7 16 32 4 0.000003365053985432853\narmv7neon_mmm_f32_8x4_cortexa7 9 128 3 0.0000101978289798725\ngeneric_f32_4x4 11 32 8 0.000007681371193157391\ngeneric_f32_4x4 7 128 9 0.00002127278439974851\narmv7neon_mmm_f32_8x4_cortexa7 24 4 13 0.000006759425188306811\narmv7neon_mmm_f32_8x4_cortexa7 24 32 9 0.000013896132257150766\narmv7neon_mmm_f32_8x4_cortexa9 25 32 9 0.000018829874971593756\narmv7neon_mmm_f32_8x4_cortexa7 9 32 4 0.000003474275688786252\narmv7neon_mmm_f32_8x6_cortexa7 8 32 7 0.0000044833206937891445\ngeneric_f32_4x4 3 32 7 0.0000030714723992174605\narmv7neon_mmm_f32_8x6_cortexa9 17 4 11 0.000004585922035285582\narmv7neon_mmm_f32_8x4_cortexa9 24 128 5 0.000029781338716311416\narmv7neon_mmm_f32_8x6_generic 15 32 18 0.000012214298327316169\narmv7neon_mmm_f32_8x6_generic 25 32 6 0.000007957229153899586\narmv7neon_mmm_f32_8x6_cortexa9 9 32 18 0.000012307833924250695\narmv7neon_mmm_f32_8x6_generic 8 32 13 0.000006159219915111089\narmv7neon_mmm_f32_8x4_cortexa7 8 128 3 0.000005401956819775254\narmv7neon_mmm_f32_8x4_generic 15 32 9 0.000009577293347035024\narmv7neon_mmm_f32_8x4_cortexa9 25 128 13 0.00007830393224178603\ngeneric_f32_4x4 4 4 13 0.0000026249296920626683\narmv7neon_mmm_f32_8x6_cortexa9 8 32 6 0.000002404882860716393\narmv7neon_mmm_f32_8x6_cortexa9 9 32 5 0.0000045921033389599396\narmv7neon_mmm_f32_8x6_generic 17 128 11 0.000038492804993535863\narmv7neon_mmm_f32_8x4_generic 17 32 9 0.000013624172849176604\narmv7neon_mmm_f32_8x4_cortexa9 24 4 9 0.000005393334776799241\narmv7neon_mmm_f32_8x4_cortexa9 23 32 8 0.000009723809855547939\narmv7neon_mmm_f32_8x4_cortexa9 25 4 4 0.0000025900920077984026\narmv7neon_mmm_f32_8x6_generic 23 4 5 0.0000027608446978317408\narmv7neon_mmm_f32_8x4_generic 8 4 12 0.0000019060496583230645\narmv7neon_mmm_f32_8x4_cortexa7 15 32 8 0.000006678781571694655\ngeneric_f32_4x4 4 4 9 0.0000021081574199754717\narmv7neon_mmm_f32_8x4_cortexa7 17 128 9 0.00004346458158147509\narmv7neon_mmm_f32_8x4_generic 25 4 8 0.000004544403472760322\narmv7neon_mmm_f32_8x4_generic 24 128 4 0.000013981279265161887\narmv7neon_mmm_f32_8x6_cortexa7 25 4 12 0.000005353769509302435\narmv7neon_mmm_f32_8x4_cortexa9 15 4 8 0.000002871100263594495\narmv7neon_mmm_f32_8x6_cortexa7 9 4 12 0.0000030500030063725167\narmv7neon_mmm_f32_8x4_cortexa9 15 128 7 0.000020348668200781455\ngeneric_f32_4x4 4 4 3 0.0000011380263710773134\narmv7neon_mmm_f32_8x4_generic 17 32 7 0.000009461004385359903\narmv7neon_mmm_f32_8x6_cortexa7 16 4 11 0.000003292952568894936\narmv7neon_mmm_f32_8x4_generic 9 128 7 0.0000189264627670604\narmv7neon_mmm_f32_8x4_cortexa7 9 32 12 0.000009404582356065284\narmv7neon_mmm_f32_8x6_cortexa7 24 4 11 0.000004645745440552646\narmv7neon_mmm_f32_8x4_generic 7 4 8 0.0000018628381186813514\ngeneric_f32_4x4 13 32 12 0.000014451033360231452\narmv7neon_mmm_f32_8x6_generic 7 128 11 0.00001335338354211472\narmv7neon_mmm_f32_8x4_cortexa9 24 32 5 0.000009736881086291142\narmv7neon_mmm_f32_8x4_generic 9 32 8 0.000006244209351860461\narmv7neon_mmm_f32_8x4_cortexa7 23 32 7 0.000009963814618687657\narmv7neon_mmm_f32_8x6_cortexa7 24 128 7 0.000039514548121680776\narmv7neon_mmm_f32_8x4_cortexa7 17 32 5 0.000009649055437118146\narmv7neon_mmm_f32_8x6_generic 7 128 7 0.000013256390141337729\narmv7neon_mmm_f32_8x6_cortexa7 9 128 17 0.00003956707654525446\narmv7neon_mmm_f32_8x4_generic 9 128 8 0.000018708339822835962\ngeneric_f32_4x4 9 4 7 0.00000399084452417743\narmv7neon_mmm_f32_8x4_generic 15 128 8 0.000018936209183566976\narmv7neon_mmm_f32_8x6_cortexa7 9 32 19 0.00001616084997042782\narmv7neon_mmm_f32_8x6_generic 16 128 5 0.00001333369244237612\narmv7neon_mmm_f32_8x4_cortexa7 9 32 3 0.0000036715654135840626\narmv7neon_mmm_f32_8x4_cortexa9 23 32 5 0.000009965038150728467\narmv7neon_mmm_f32_8x6_cortexa7 9 128 7 0.000026494406670274787\narmv7neon_mmm_f32_8x4_cortexa9 24 128 13 0.00005851957886711075\narmv7neon_mmm_f32_8x6_cortexa7 9 4 18 0.000004273474311217914\ngeneric_f32_4x4 5 32 12 0.000007635355402928665\narmv7neon_mmm_f32_8x4_cortexa9 7 128 7 0.000010528249080204253\narmv7neon_mmm_f32_8x6_cortexa9 24 32 19 0.00002386884326098149\narmv7neon_mmm_f32_8x6_generic 8 4 7 0.0000017826372979170567\narmv7neon_mmm_f32_8x4_generic 7 4 5 0.000001794625708774814\narmv7neon_mmm_f32_8x6_cortexa7 9 4 6 0.000001801617764943881\narmv7neon_mmm_f32_8x4_generic 25 32 13 0.00002352372957802219\narmv7neon_mmm_f32_8x4_generic 24 128 13 0.00005496909042425587\narmv7neon_mmm_f32_8x6_cortexa9 23 32 17 0.000018875847839557392\ngeneric_f32_4x4 12 32 4 0.000003926824640983302\narmv7neon_mmm_f32_8x6_cortexa9 7 128 18 0.000020631769324589798\narmv7neon_mmm_f32_8x6_cortexa9 8 128 19 0.000026726033464302463\narmv7neon_mmm_f32_8x6_cortexa7 7 4 5 0.0000012346138819052426\narmv7neon_mmm_f32_8x4_generic 17 128 7 0.00002816390176070489\narmv7neon_mmm_f32_8x6_generic 9 32 19 0.000015684300494904117\narmv7neon_mmm_f32_8x4_cortexa7 9 128 4 0.000010001875449763685\narmv7neon_mmm_f32_8x4_generic 23 32 13 0.000018251850356188044\ngeneric_f32_4x4 13 4 3 0.000003021647059953821\narmv7neon_mmm_f32_8x6_cortexa9 25 128 7 0.000053220906812986594\narmv7neon_mmm_f32_8x4_cortexa9 16 128 8 0.00001967608213206112\narmv7neon_mmm_f32_8x6_generic 24 32 5 0.000006620165371496042\narmv7neon_mmm_f32_8x4_cortexa7 9 4 7 0.0000028674611086712135\narmv7neon_mmm_f32_8x4_cortexa9 17 4 11 0.000005653151614630126\ngeneric_f32_4x4 13 4 4 0.000002594385628155042\narmv7neon_mmm_f32_8x6_generic 25 128 5 0.000026094659734105824\narmv7neon_mmm_f32_8x4_generic 17 32 3 0.000005161144602046728\narmv7neon_mmm_f32_8x4_cortexa9 24 32 11 0.000014220571969428708\ngeneric_f32_4x4 4 128 3 0.000004015357652724343\narmv7neon_mmm_f32_8x4_cortexa9 8 128 5 0.00001027176965651363\narmv7neon_mmm_f32_8x6_cortexa7 15 4 17 0.000004874430261781061\narmv7neon_mmm_f32_8x4_cortexa7 15 4 12 0.0000040169005856724945\narmv7neon_mmm_f32_8x4_cortexa7 8 32 12 0.00000477050855780904\narmv7neon_mmm_f32_8x6_cortexa7 9 4 7 0.0000031516329281183407\narmv7neon_mmm_f32_8x4_generic 7 32 11 0.000005308351521432454\narmv7neon_mmm_f32_8x4_cortexa7 25 32 5 0.000012670365676368953\ngeneric_f32_4x4 5 128 5 0.000014294465011666321\narmv7neon_mmm_f32_8x6_cortexa7 7 32 17 0.000006818696484359277\narmv7neon_mmm_f32_8x4_cortexa9 24 32 7 0.00000986925847989351\ngeneric_f32_4x4 4 128 13 0.000014106152963483687\narmv7neon_mmm_f32_8x4_cortexa9 16 128 3 0.000010512285712675924\ngeneric_f32_4x4 8 128 4 0.000007240602279573043\narmv7neon_mmm_f32_8x4_cortexa9 7 128 9 0.00001548686209510202\narmv7neon_mmm_f32_8x4_cortexa9 7 32 3 0.000002138015694680355\narmv7neon_mmm_f32_8x4_generic 24 32 13 0.000017536918553280824\narmv7neon_mmm_f32_8x6_cortexa9 23 4 12 0.0000044455569065126315\narmv7neon_mmm_f32_8x4_cortexa7 8 4 13 0.0000026000896469710663\narmv7neon_mmm_f32_8x4_cortexa7 25 32 8 0.000012179917551949525\ngeneric_f32_4x4 5 32 5 0.000005390504735926875\ngeneric_f32_4x4 11 128 13 0.00004175553018581371\narmv7neon_mmm_f32_8x4_cortexa7 7 32 4 0.0000021521595449855865\narmv7neon_mmm_f32_8x4_cortexa9 8 4 9 0.000002148291671877107\narmv7neon_mmm_f32_8x6_cortexa7 24 4 18 0.000005654885858199984\narmv7neon_mmm_f32_8x4_generic 25 128 9 0.00005533756635099798\narmv7neon_mmm_f32_8x4_cortexa9 9 128 9 0.00002978120797293901\narmv7neon_mmm_f32_8x6_cortexa7 8 32 12 0.000004281164608278993\narmv7neon_mmm_f32_8x4_generic 9 4 13 0.000004935869563311462\narmv7neon_mmm_f32_8x4_cortexa7 23 128 3 0.000015184873420309865\ngeneric_f32_4x4 12 4 12 0.000004912969841843077\narmv7neon_mmm_f32_8x6_generic 16 4 7 0.0000030460066806302122\narmv7neon_mmm_f32_8x4_cortexa7 9 4 13 0.000004992355989646887\narmv7neon_mmm_f32_8x6_cortexa9 15 4 12 0.0000032832021366886516\narmv7neon_mmm_f32_8x6_cortexa9 17 4 7 0.000004416231510276644\narmv7neon_mmm_f32_8x4_generic 9 128 5 0.000018874961280340985\narmv7neon_mmm_f32_8x4_cortexa7 25 4 9 0.000007146809416343016\narmv7neon_mmm_f32_8x6_cortexa9 23 4 5 0.000002791403862004092\ngeneric_f32_4x4 3 128 11 0.000011052312539972672\narmv7neon_mmm_f32_8x4_cortexa7 16 32 13 0.000012274313788252517\ngeneric_f32_4x4 5 4 8 0.000002691374011509211\narmv7neon_mmm_f32_8x6_generic 8 4 12 0.0000016391318699699268\ngeneric_f32_4x4 8 4 12 0.0000034590196765442797\narmv7neon_mmm_f32_8x6_generic 17 128 18 0.00005685255235859907\narmv7neon_mmm_f32_8x6_cortexa9 17 128 6 0.00002017521356859541\narmv7neon_mmm_f32_8x6_generic 25 128 6 0.00002553797390337986\narmv7neon_mmm_f32_8x6_cortexa9 25 128 5 0.000027210481604244293\narmv7neon_mmm_f32_8x4_cortexa7 15 128 7 0.000019945561157727263\narmv7neon_mmm_f32_8x4_generic 7 4 11 0.000002550958711536068\ngeneric_f32_4x4 13 4 8 0.000004699429428545618\narmv7neon_mmm_f32_8x4_cortexa9 9 4 5 0.0000028517696924614755\narmv7neon_mmm_f32_8x4_cortexa9 8 4 3 0.0000012050980961045702\narmv7neon_mmm_f32_8x6_cortexa9 24 128 17 0.000059842842681725297\narmv7neon_mmm_f32_8x6_cortexa9 25 128 6 0.000026691322740044253\ngeneric_f32_4x4 9 32 3 0.000004333031052520466\narmv7neon_mmm_f32_8x6_cortexa7 9 32 11 0.000008502744568312225\narmv7neon_mmm_f32_8x6_cortexa9 16 32 11 0.000008643328393303817\narmv7neon_mmm_f32_8x6_cortexa9 16 4 11 0.00000327884229270897\narmv7neon_mmm_f32_8x4_generic 8 128 5 0.000009661930895932827\narmv7neon_mmm_f32_8x4_cortexa9 15 4 5 0.0000029989007242676373\narmv7neon_mmm_f32_8x4_cortexa7 23 32 11 0.000014477721812785248\narmv7neon_mmm_f32_8x6_cortexa7 9 4 13 0.000004411946245984795\narmv7neon_mmm_f32_8x6_generic 7 128 12 0.000013345094425498999\narmv7neon_mmm_f32_8x4_cortexa9 24 128 11 0.00004426398857587651\narmv7neon_mmm_f32_8x6_generic 25 128 7 0.00005086677641643256\narmv7neon_mmm_f32_8x6_generic 16 32 17 0.00001197546406643872\ngeneric_f32_4x4 4 32 13 0.000005196854104063007\narmv7neon_mmm_f32_8x4_cortexa7 16 4 7 0.000002852179426317928\narmv7neon_mmm_f32_8x6_cortexa7 23 4 7 0.000004598943528457281\narmv7neon_mmm_f32_8x6_generic 17 32 6 0.000006120980761955415\narmv7neon_mmm_f32_8x6_cortexa7 17 32 17 0.00001829474178461942\ngeneric_f32_4x4 7 4 12 0.000003918843035175344\narmv7neon_mmm_f32_8x6_generic 9 4 7 0.0000030530125579888694\narmv7neon_mmm_f32_8x6_cortexa9 23 4 13 0.000006537462226861253\ngeneric_f32_4x4 12 32 11 0.000011229611140019307\narmv7neon_mmm_f32_8x4_cortexa7 7 32 9 0.000005392896316953727\narmv7neon_mmm_f32_8x4_cortexa9 15 4 7 0.00000310030600994376\narmv7neon_mmm_f32_8x4_cortexa9 23 32 3 0.000005486033134258231\narmv7neon_mmm_f32_8x6_cortexa7 25 4 11 0.000005954018792233075\narmv7neon_mmm_f32_8x4_generic 23 128 8 0.000027951206051686114\narmv7neon_mmm_f32_8x6_generic 23 4 12 0.000004342351795004267\narmv7neon_mmm_f32_8x4_cortexa9 8 32 5 0.0000035943022440195\narmv7neon_mmm_f32_8x6_cortexa9 7 4 18 0.0000028075381670446466\narmv7neon_mmm_f32_8x4_generic 8 32 13 0.0000061830339053399435\narmv7neon_mmm_f32_8x6_generic 15 32 7 0.000008321528944284549\narmv7neon_mmm_f32_8x6_cortexa7 16 128 6 0.00001332317233161335\narmv7neon_mmm_f32_8x4_generic 9 4 12 0.000003689645307663438\narmv7neon_mmm_f32_8x4_cortexa9 16 32 7 0.000006751619359417928\ngeneric_f32_4x4 7 4 4 0.0000016418721244643219\narmv7neon_mmm_f32_8x4_generic 7 128 13 0.000019287387814897854\narmv7neon_mmm_f32_8x6_cortexa9 23 4 11 0.000004831555332090134\narmv7neon_mmm_f32_8x4_cortexa9 8 128 8 0.00001009639921913338\narmv7neon_mmm_f32_8x6_generic 23 32 5 0.000006582337877689147\narmv7neon_mmm_f32_8x4_generic 25 128 3 0.00001920626106575073\narmv7neon_mmm_f32_8x4_generic 7 32 8 0.000003711210711678028\narmv7neon_mmm_f32_8x6_cortexa7 16 128 19 0.00005218314273770285\narmv7neon_mmm_f32_8x6_cortexa9 7 128 12 0.000013936034942170719\narmv7neon_mmm_f32_8x6_generic 17 32 5 0.000006487710283772798\narmv7neon_mmm_f32_8x4_generic 8 128 7 0.000009698770362126403\narmv7neon_mmm_f32_8x4_generic 15 4 7 0.0000030411777158900867\narmv7neon_mmm_f32_8x6_generic 25 128 19 0.00010155326542007438\narmv7neon_mmm_f32_8x6_cortexa7 9 128 5 0.000013650595324888383\narmv7neon_mmm_f32_8x4_cortexa9 9 32 11 0.00000982080901853953\ngeneric_f32_4x4 9 32 11 0.00001141647780993558\ngeneric_f32_4x4 8 32 11 0.000007662846153151327\narmv7neon_mmm_f32_8x6_generic 8 128 17 0.000019396378758926843\narmv7neon_mmm_f32_8x6_cortexa9 8 4 17 0.00000248907607573393\ngeneric_f32_4x4 13 128 12 0.000041173556961397094\narmv7neon_mmm_f32_8x4_generic 25 32 3 0.0000067405543706379265\narmv7neon_mmm_f32_8x6_cortexa9 16 4 13 0.000004284450398155638\narmv7neon_mmm_f32_8x6_generic 7 32 13 0.000006564286527582003\ngeneric_f32_4x4 9 4 12 0.000005239644799087451\ngeneric_f32_4x4 3 128 12 0.00001105053431182824\narmv7neon_mmm_f32_8x4_cortexa9 16 32 12 0.000009204841041049037\narmv7neon_mmm_f32_8x4_cortexa9 8 4 7 0.0000016950881959591487\narmv7neon_mmm_f32_8x4_cortexa9 8 4 8 0.00000148340332010083\narmv7neon_mmm_f32_8x4_cortexa9 24 32 4 0.000004874669124633379\narmv7neon_mmm_f32_8x4_cortexa9 16 128 7 0.00002009832072993214\narmv7neon_mmm_f32_8x4_generic 16 4 5 0.000002739180418018979\narmv7neon_mmm_f32_8x4_cortexa9 8 4 12 0.000001953874992361998\narmv7neon_mmm_f32_8x4_generic 15 4 4 0.0000016638026379811712\narmv7neon_mmm_f32_8x4_cortexa9 25 128 8 0.000039087913140379676\narmv7neon_mmm_f32_8x6_cortexa9 17 4 12 0.000004212564422068874\narmv7neon_mmm_f32_8x6_cortexa9 8 32 5 0.00000263061064210809\narmv7neon_mmm_f32_8x4_generic 8 4 5 0.000001623245576042727\narmv7neon_mmm_f32_8x6_cortexa7 15 128 6 0.000013618693064061564\narmv7neon_mmm_f32_8x6_cortexa7 7 4 18 0.000002804843066747126\ngeneric_f32_4x4 3 4 11 0.0000023913102926543824\narmv7neon_mmm_f32_8x6_generic 16 4 6 0.000001638807649221633\narmv7neon_mmm_f32_8x6_generic 8 128 6 0.0000067051548997103826\narmv7neon_mmm_f32_8x6_cortexa9 16 4 6 0.0000016568160988445508\narmv7neon_mmm_f32_8x6_cortexa9 16 128 7 0.000026851544464649677\narmv7neon_mmm_f32_8x4_cortexa9 8 128 12 0.000014872272678272856\narmv7neon_mmm_f32_8x6_generic 24 128 19 0.00007580588220559739\ngeneric_f32_4x4 3 128 5 0.0000074971724012246544\ngeneric_f32_4x4 3 128 8 0.000007539035525450043\narmv7neon_mmm_f32_8x6_cortexa9 24 128 7 0.00004003350952160301\narmv7neon_mmm_f32_8x4_cortexa7 23 32 9 0.000014356067971382847\narmv7neon_mmm_f32_8x6_cortexa9 15 32 18 0.000012709917183710011\ngeneric_f32_4x4 9 128 3 0.000011011983185480526\narmv7neon_mmm_f32_8x4_generic 15 128 7 0.000019154850070723174\ngeneric_f32_4x4 7 32 12 0.000007829205768353918\ngeneric_f32_4x4 12 4 3 0.000002428328614613921\narmv7neon_mmm_f32_8x4_cortexa7 8 128 4 0.000005191807529366239\narmv7neon_mmm_f32_8x4_cortexa9 17 128 3 0.00001540571905602067\narmv7neon_mmm_f32_8x4_cortexa9 17 128 9 0.000044358855138628255\narmv7neon_mmm_f32_8x4_generic 8 32 4 0.0000018712794095957982\narmv7neon_mmm_f32_8x6_cortexa9 9 128 7 0.000026855539948208353\narmv7neon_mmm_f32_8x6_cortexa7 7 128 13 0.000020286298173428065\narmv7neon_mmm_f32_8x4_cortexa7 7 32 7 0.0000037916453247576294\narmv7neon_mmm_f32_8x4_cortexa7 16 128 11 0.000029071525948432538\narmv7neon_mmm_f32_8x4_generic 8 128 8 0.000009479495707619602\narmv7neon_mmm_f32_8x4_cortexa7 9 32 13 0.000012575930597830614\narmv7neon_mmm_f32_8x6_cortexa7 7 4 7 0.0000019563323222872665\narmv7neon_mmm_f32_8x4_cortexa7 15 128 3 0.000010282742767020747\narmv7neon_mmm_f32_8x4_generic 17 128 9 0.00004166868490626211\narmv7neon_mmm_f32_8x6_cortexa9 15 32 11 0.000008848994549844927\narmv7neon_mmm_f32_8x4_cortexa7 16 128 7 0.000019694781274526964\narmv7neon_mmm_f32_8x6_cortexa9 7 128 17 0.00002067047797610865\narmv7neon_mmm_f32_8x4_cortexa7 16 4 8 0.000002421339765366324\narmv7neon_mmm_f32_8x6_cortexa9 25 4 19 0.000010458910160208884\narmv7neon_mmm_f32_8x4_cortexa7 17 4 12 0.000005174031688170145\narmv7neon_mmm_f32_8x4_cortexa7 8 128 7 0.000010096523777664552\narmv7neon_mmm_f32_8x4_cortexa7 9 32 11 0.000009664173576551376\narmv7neon_mmm_f32_8x4_cortexa9 15 32 13 0.000013172771066560177\narmv7neon_mmm_f32_8x6_cortexa7 15 32 6 0.0000045760506363113526\narmv7neon_mmm_f32_8x4_generic 16 128 7 0.000018904285841254675\narmv7neon_mmm_f32_8x6_cortexa9 24 32 7 0.000012425961839460377\narmv7neon_mmm_f32_8x4_cortexa7 8 128 8 0.00000988276098193371\narmv7neon_mmm_f32_8x4_cortexa7 25 32 13 0.000024332756642022888\narmv7neon_mmm_f32_8x6_cortexa9 8 4 12 0.000001657247635058824\narmv7neon_mmm_f32_8x4_cortexa7 8 4 9 0.000002131892294337394\ngeneric_f32_4x4 8 4 8 0.000002499295427359815\narmv7neon_mmm_f32_8x6_cortexa9 16 128 5 0.000013927438070206447\ngeneric_f32_4x4 3 4 7 0.0000017546173861422715\narmv7neon_mmm_f32_8x4_cortexa7 23 128 12 0.000043445426131070976\narmv7neon_mmm_f32_8x4_generic 8 128 12 0.00001395887153959496\narmv7neon_mmm_f32_8x4_cortexa9 7 4 11 0.0000025905119707065483\ngeneric_f32_4x4 12 128 4 0.00001060459652081298\narmv7neon_mmm_f32_8x6_generic 17 128 13 0.00005710733590612565\narmv7neon_mmm_f32_8x4_cortexa7 7 4 11 0.000002572252868831556\narmv7neon_mmm_f32_8x6_generic 15 128 11 0.000026019339889436572\ngeneric_f32_4x4 9 4 5 0.000003906378066863749\narmv7neon_mmm_f32_8x6_cortexa9 15 4 17 0.00000485956993190203\narmv7neon_mmm_f32_8x6_generic 9 32 17 0.000012038137584352969\narmv7neon_mmm_f32_8x4_cortexa9 8 32 11 0.000005081279026778461\narmv7neon_mmm_f32_8x4_cortexa7 25 128 11 0.000057856501014562726\narmv7neon_mmm_f32_8x6_cortexa9 25 32 19 0.00003186559880573022\narmv7neon_mmm_f32_8x6_cortexa9 8 32 12 0.000004319717163614961\narmv7neon_mmm_f32_8x6_cortexa9 8 32 19 0.000008329624592686573\narmv7neon_mmm_f32_8x6_cortexa9 9 32 13 0.000012409078932821099\narmv7neon_mmm_f32_8x6_cortexa9 17 128 12 0.00003982138220281054\narmv7neon_mmm_f32_8x4_cortexa9 8 32 4 0.0000019683045582115826\narmv7neon_mmm_f32_8x6_cortexa9 17 128 13 0.000059714513355137016\narmv7neon_mmm_f32_8x6_cortexa9 25 32 13 0.00002415767938328582\narmv7neon_mmm_f32_8x4_cortexa7 7 128 8 0.000010344849965132014\narmv7neon_mmm_f32_8x6_cortexa9 8 128 11 0.000013785904590501993\narmv7neon_mmm_f32_8x4_cortexa7 24 32 13 0.000018144082756384124\narmv7neon_mmm_f32_8x6_generic 15 4 13 0.000004567420998191548\narmv7neon_mmm_f32_8x4_generic 9 32 13 0.000012173282994755266\ngeneric_f32_4x4 4 4 4 0.0000009923563076021443\narmv7neon_mmm_f32_8x4_cortexa9 24 128 7 0.000029905631027460932\narmv7neon_mmm_f32_8x4_cortexa7 9 128 8 0.0000195010462183809\narmv7neon_mmm_f32_8x6_cortexa9 9 4 5 0.000001915573042137183\ngeneric_f32_4x4 7 128 12 0.000021189012747949428\narmv7neon_mmm_f32_8x4_generic 23 32 9 0.000013896926880805095\narmv7neon_mmm_f32_8x4_generic 8 128 13 0.000018647991490366943\narmv7neon_mmm_f32_8x4_cortexa7 15 32 11 0.00000998072291720506\ngeneric_f32_4x4 3 128 4 0.0000040235161328678346\narmv7neon_mmm_f32_8x4_generic 9 128 3 0.000009799742651068634\narmv7neon_mmm_f32_8x6_generic 25 4 11 0.000005815747914439624\narmv7neon_mmm_f32_8x4_cortexa9 9 4 12 0.0000037593832702220446\narmv7neon_mmm_f32_8x4_generic 8 32 7 0.0000034630101008340834\narmv7neon_mmm_f32_8x4_cortexa9 24 4 7 0.000004049582371373125\narmv7neon_mmm_f32_8x6_generic 9 32 12 0.000008052850558834548\narmv7neon_mmm_f32_8x4_cortexa7 25 4 3 0.000003139298464286286\narmv7neon_mmm_f32_8x4_generic 15 4 3 0.0000018347526374757481\narmv7neon_mmm_f32_8x6_cortexa9 25 4 18 0.000007705086964181997\ngeneric_f32_4x4 5 128 13 0.00002796513369706675\narmv7neon_mmm_f32_8x4_cortexa9 23 4 12 0.0000055210994766811515\ngeneric_f32_4x4 7 128 4 0.000007400928983118127\ngeneric_f32_4x4 12 128 8 0.000020684245069264504\narmv7neon_mmm_f32_8x4_generic 9 4 7 0.0000028402738019775784\ngeneric_f32_4x4 13 4 9 0.000007094831203377165\narmv7neon_mmm_f32_8x4_cortexa9 17 4 4 0.000002109614550782161\ngeneric_f32_4x4 9 32 7 0.00000788121798038347\narmv7neon_mmm_f32_8x4_cortexa9 17 32 9 0.000014308883462633102\narmv7neon_mmm_f32_8x4_generic 16 128 12 0.00002742841574783841\narmv7neon_mmm_f32_8x6_generic 9 32 13 0.000011930679710766873\narmv7neon_mmm_f32_8x4_cortexa7 24 128 7 0.000029306857678894273\narmv7neon_mmm_f32_8x4_cortexa9 15 4 11 0.0000043086270265983256\ngeneric_f32_4x4 11 4 11 0.000005690020384668188\ngeneric_f32_4x4 11 4 5 0.000003971103754939711\narmv7neon_mmm_f32_8x4_generic 23 128 7 0.000028363425957340315\narmv7neon_mmm_f32_8x4_cortexa7 25 32 7 0.000012820527346969179\narmv7neon_mmm_f32_8x6_generic 17 4 13 0.0000061204912028192775\narmv7neon_mmm_f32_8x4_cortexa7 23 4 13 0.000007468189054284558\ngeneric_f32_4x4 4 32 7 0.0000029448449923973884\narmv7neon_mmm_f32_8x4_cortexa9 25 32 12 0.000018265077104449187\narmv7neon_mmm_f32_8x6_cortexa9 17 32 5 0.000006709520484710961\narmv7neon_mmm_f32_8x4_cortexa9 23 128 7 0.00003016688393762355\narmv7neon_mmm_f32_8x4_cortexa9 16 128 13 0.000039189527740613905\narmv7neon_mmm_f32_8x4_cortexa7 16 128 5 0.000019619696312768993\narmv7neon_mmm_f32_8x4_generic 17 128 13 0.00005528191915660002\narmv7neon_mmm_f32_8x6_generic 17 128 19 0.00007601141239533076\narmv7neon_mmm_f32_8x4_generic 8 32 5 0.0000034293993184402013\ngeneric_f32_4x4 9 32 4 0.000004039130783975973\narmv7neon_mmm_f32_8x4_cortexa9 23 32 9 0.000014579239774429512\narmv7neon_mmm_f32_8x6_cortexa7 8 128 11 0.000013612858256767989\narmv7neon_mmm_f32_8x6_cortexa9 15 32 12 0.000008658441674303882\narmv7neon_mmm_f32_8x4_generic 9 128 13 0.00003711806630993475\narmv7neon_mmm_f32_8x6_generic 17 4 11 0.000004486220214719747\narmv7neon_mmm_f32_8x6_generic 24 32 17 0.00001769011948694583\narmv7neon_mmm_f32_8x6_generic 9 4 18 0.00000416889219962654\narmv7neon_mmm_f32_8x6_cortexa7 23 32 19 0.000024297125708617196\narmv7neon_mmm_f32_8x6_cortexa9 9 32 19 0.000016308328501596863\ngeneric_f32_4x4 5 32 13 0.000010150287138446791\ngeneric_f32_4x4 11 128 8 0.000021041592663498075\narmv7neon_mmm_f32_8x4_generic 17 32 8 0.000009030738946480375\narmv7neon_mmm_f32_8x4_generic 25 4 5 0.0000050194271140758065\narmv7neon_mmm_f32_8x4_generic 8 128 11 0.000014188042949903446\ngeneric_f32_4x4 13 32 11 0.000014981513303829195\narmv7neon_mmm_f32_8x4_cortexa9 8 128 4 0.000005303802744401548\narmv7neon_mmm_f32_8x6_cortexa7 17 4 5 0.0000027556390651666856\narmv7neon_mmm_f32_8x6_generic 25 128 12 0.000050521454291978235\narmv7neon_mmm_f32_8x6_cortexa9 16 4 7 0.0000031344346513242154\narmv7neon_mmm_f32_8x6_cortexa7 16 32 5 0.000004750660751815082\narmv7neon_mmm_f32_8x4_cortexa9 17 128 8 0.000029514324154126036\narmv7neon_mmm_f32_8x4_cortexa7 8 4 8 0.000001465122696586523\narmv7neon_mmm_f32_8x6_cortexa9 8 128 17 0.00002029296708644369\narmv7neon_mmm_f32_8x4_generic 25 32 12 0.00001736483445253461\ngeneric_f32_4x4 3 32 5 0.0000030443235090952762\ngeneric_f32_4x4 7 128 11 0.000021331736800406107\narmv7neon_mmm_f32_8x4_generic 24 128 3 0.000014605163771206865\narmv7neon_mmm_f32_8x4_cortexa9 15 32 8 0.000006785141126690698\narmv7neon_mmm_f32_8x6_cortexa7 23 4 11 0.000004845198031614994\narmv7neon_mmm_f32_8x6_cortexa7 8 4 18 0.0000022246057256413265\narmv7neon_mmm_f32_8x4_generic 23 32 12 0.000013609481500575639\narmv7neon_mmm_f32_8x6_generic 7 128 17 0.00001990485288299381\narmv7neon_mmm_f32_8x6_cortexa7 25 128 19 0.00010434796032564001\narmv7neon_mmm_f32_8x6_generic 15 4 7 0.0000032121848403447733\narmv7neon_mmm_f32_8x4_generic 25 32 9 0.00001793284879597201\narmv7neon_mmm_f32_8x4_cortexa7 25 4 5 0.0000050795046947410985\narmv7neon_mmm_f32_8x4_cortexa7 17 32 12 0.000013704665363244266\ngeneric_f32_4x4 5 32 3 0.0000030334097589492826\narmv7neon_mmm_f32_8x4_cortexa9 17 128 5 0.000030021544003020483\narmv7neon_mmm_f32_8x4_cortexa7 24 4 9 0.000005360352425382144\narmv7neon_mmm_f32_8x6_cortexa7 9 32 18 0.000012173297962511017\narmv7neon_mmm_f32_8x4_cortexa7 15 4 13 0.000005358787865343933\narmv7neon_mmm_f32_8x6_cortexa9 17 32 11 0.000012635537581820717\narmv7neon_mmm_f32_8x4_generic 9 32 12 0.000009105324476823614\narmv7neon_mmm_f32_8x4_cortexa7 17 128 11 0.00004382978043849\narmv7neon_mmm_f32_8x6_generic 17 4 6 0.0000023082534746308964\narmv7neon_mmm_f32_8x6_cortexa7 8 128 17 0.000020059146620211786\narmv7neon_mmm_f32_8x4_cortexa7 15 4 7 0.000003068689058039639\narmv7neon_mmm_f32_8x4_cortexa9 15 4 13 0.000005392948403824124\narmv7neon_mmm_f32_8x4_cortexa7 15 4 8 0.0000028443546860500008\narmv7neon_mmm_f32_8x4_cortexa9 24 4 3 0.000002583780394094933\ngeneric_f32_4x4 13 4 11 0.0000072216668884748755\narmv7neon_mmm_f32_8x6_cortexa7 25 4 13 0.00000811613473861232\narmv7neon_mmm_f32_8x6_generic 7 32 18 0.000006623134760255213\narmv7neon_mmm_f32_8x4_cortexa7 16 4 4 0.0000014656631347323804\narmv7neon_mmm_f32_8x6_cortexa9 24 128 12 0.00003980745750816077\narmv7neon_mmm_f32_8x6_cortexa9 17 32 6 0.00000638262644761529\ngeneric_f32_4x4 8 128 12 0.000020674001160178604\narmv7neon_mmm_f32_8x6_cortexa9 9 128 5 0.000013783853077105059\narmv7neon_mmm_f32_8x4_cortexa7 23 4 11 0.00000590971284549428\narmv7neon_mmm_f32_8x6_generic 17 128 7 0.00003951740163826464\narmv7neon_mmm_f32_8x6_cortexa7 23 128 12 0.00003972987011474437\ngeneric_f32_4x4 11 32 9 0.000011454015846057979\narmv7neon_mmm_f32_8x4_generic 7 128 8 0.000009944618306958537\narmv7neon_mmm_f32_8x4_cortexa7 9 128 12 0.00002904338895016975\ngeneric_f32_4x4 7 32 3 0.000003062772103932835\narmv7neon_mmm_f32_8x6_generic 7 32 19 0.000008594590275392258\narmv7neon_mmm_f32_8x4_cortexa9 24 4 4 0.0000019652194228578485\narmv7neon_mmm_f32_8x6_generic 25 32 17 0.00002354000930758944\narmv7neon_mmm_f32_8x4_cortexa7 24 4 12 0.000004776517171804484\ngeneric_f32_4x4 9 128 12 0.00003113614108946529\narmv7neon_mmm_f32_8x4_generic 25 4 13 0.000009058320096239628\narmv7neon_mmm_f32_8x4_cortexa9 15 32 7 0.000007013841485260632\narmv7neon_mmm_f32_8x6_cortexa7 8 32 13 0.000006373539570632082\narmv7neon_mmm_f32_8x6_cortexa9 7 128 7 0.000013849670236184067\narmv7neon_mmm_f32_8x6_cortexa7 16 4 5 0.0000021140389285040373\narmv7neon_mmm_f32_8x6_cortexa7 15 4 19 0.000006018452488388481\narmv7neon_mmm_f32_8x4_cortexa9 25 4 11 0.000007297177417716392\narmv7neon_mmm_f32_8x4_cortexa7 7 128 3 0.0000053704412970208095\narmv7neon_mmm_f32_8x6_cortexa7 25 32 6 0.000008216173586447742\narmv7neon_mmm_f32_8x6_cortexa9 23 32 6 0.000006530580171598388\narmv7neon_mmm_f32_8x6_cortexa9 8 4 11 0.0000019099336489036645\narmv7neon_mmm_f32_8x4_cortexa7 8 4 3 0.0000011915557137944658\narmv7neon_mmm_f32_8x4_cortexa9 7 32 13 0.000007135759350139172\narmv7neon_mmm_f32_8x4_cortexa9 15 128 8 0.000020152163526097688\narmv7neon_mmm_f32_8x6_generic 16 128 12 0.000025531348127989936\narmv7neon_mmm_f32_8x4_cortexa9 9 32 8 0.00000655179781932232\narmv7neon_mmm_f32_8x4_generic 25 4 7 0.000005150952172982353\narmv7neon_mmm_f32_8x6_generic 23 128 7 0.00003975509670222853\ngeneric_f32_4x4 7 128 7 0.000014417564018686427\narmv7neon_mmm_f32_8x6_generic 7 32 6 0.0000025435007870682203\ngeneric_f32_4x4 12 32 13 0.000014516218594466092\narmv7neon_mmm_f32_8x6_generic 24 4 13 0.000005989131584497686\narmv7neon_mmm_f32_8x6_cortexa7 24 4 13 0.000006130318576111218\narmv7neon_mmm_f32_8x4_cortexa7 24 4 8 0.0000033735678137031244\narmv7neon_mmm_f32_8x6_cortexa9 15 32 6 0.000004614375157776428\narmv7neon_mmm_f32_8x4_cortexa7 17 128 5 0.000029428735157155848\ngeneric_f32_4x4 11 32 3 0.000004361903012775443\narmv7neon_mmm_f32_8x6_cortexa9 24 128 11 0.00004051475238084589\narmv7neon_mmm_f32_8x6_cortexa7 8 4 11 0.0000019289152314594922\narmv7neon_mmm_f32_8x4_generic 7 32 12 0.00000529445196979877\narmv7neon_mmm_f32_8x4_cortexa9 25 4 9 0.0000071851687447521495\narmv7neon_mmm_f32_8x4_cortexa9 7 128 13 0.0000205036178463644\narmv7neon_mmm_f32_8x4_cortexa7 25 128 4 0.00001951695951825782\ngeneric_f32_4x4 11 32 11 0.0000115439545889861\narmv7neon_mmm_f32_8x6_cortexa7 23 32 7 0.000012514048765991971\ngeneric_f32_4x4 13 32 8 0.000009849544291047146\narmv7neon_mmm_f32_8x4_cortexa9 24 4 13 0.0000068036126797526975\narmv7neon_mmm_f32_8x6_cortexa9 23 128 7 0.00004044892329500272\narmv7neon_mmm_f32_8x4_cortexa9 24 32 9 0.000014128824207386303\narmv7neon_mmm_f32_8x6_cortexa9 15 4 19 0.000006004743734908054\narmv7neon_mmm_f32_8x6_generic 16 128 7 0.000025803175203093092\narmv7neon_mmm_f32_8x4_generic 24 4 9 0.000005289440520085159\narmv7neon_mmm_f32_8x4_generic 7 128 7 0.000009924053412179527\narmv7neon_mmm_f32_8x6_cortexa7 15 4 5 0.0000020760458197512403\narmv7neon_mmm_f32_8x4_cortexa9 23 32 12 0.000014302742013508701\narmv7neon_mmm_f32_8x6_cortexa9 23 128 18 0.00006018418795895432\narmv7neon_mmm_f32_8x4_cortexa7 7 128 12 0.000015239535080520215\narmv7neon_mmm_f32_8x6_generic 15 4 6 0.0000018544842241934815\narmv7neon_mmm_f32_8x6_cortexa7 25 32 18 0.000023497641993495917\ngeneric_f32_4x4 9 4 8 0.0000037065692747303202\narmv7neon_mmm_f32_8x6_generic 9 4 17 0.0000044060023407811705\narmv7neon_mmm_f32_8x4_generic 17 4 7 0.000004004011518049962\narmv7neon_mmm_f32_8x4_generic 16 4 9 0.0000037009767742197034\narmv7neon_mmm_f32_8x4_generic 16 4 11 0.0000037573621028760724\narmv7neon_mmm_f32_8x6_cortexa9 23 4 18 0.000006335024379033378\ngeneric_f32_4x4 7 32 11 0.00000797109252276388\ngeneric_f32_4x4 8 32 8 0.00000506136423984456\narmv7neon_mmm_f32_8x6_cortexa9 16 32 18 0.000011962958224342696\narmv7neon_mmm_f32_8x4_generic 9 32 3 0.0000035695396503977124\narmv7neon_mmm_f32_8x4_cortexa7 15 128 9 0.000029527917733416504\narmv7neon_mmm_f32_8x4_cortexa7 16 32 3 0.0000037787003081727403\narmv7neon_mmm_f32_8x4_cortexa9 17 4 5 0.000003983936158752065\ngeneric_f32_4x4 5 4 7 0.0000028427432000736736\narmv7neon_mmm_f32_8x6_generic 24 128 6 0.000019285772023622517\narmv7neon_mmm_f32_8x6_cortexa9 24 128 6 0.000020079389293074595\narmv7neon_mmm_f32_8x4_cortexa7 24 128 11 0.000043650740679119\narmv7neon_mmm_f32_8x6_cortexa7 15 4 6 0.0000019214162046321274\narmv7neon_mmm_f32_8x6_generic 24 128 11 0.000039928070800596723\narmv7neon_mmm_f32_8x6_cortexa7 17 128 13 0.00005915137603637775\narmv7neon_mmm_f32_8x6_cortexa7 9 128 12 0.00002647047584440748\ngeneric_f32_4x4 8 32 7 0.000005396919284647475\narmv7neon_mmm_f32_8x4_cortexa7 16 32 7 0.000006638663857963081\narmv7neon_mmm_f32_8x4_cortexa7 9 128 9 0.000029246794882198228\narmv7neon_mmm_f32_8x4_generic 15 32 12 0.000009467451463683956\narmv7neon_mmm_f32_8x6_generic 23 128 6 0.00001954577461053118\narmv7neon_mmm_f32_8x4_cortexa9 7 32 7 0.000003851842872992441\narmv7neon_mmm_f32_8x6_cortexa9 16 32 6 0.000004321654529645647\ngeneric_f32_4x4 13 32 4 0.00000517924119222049\narmv7neon_mmm_f32_8x4_generic 8 4 9 0.000002104797317503441\narmv7neon_mmm_f32_8x4_cortexa7 24 128 3 0.000015233751299574878\narmv7neon_mmm_f32_8x6_cortexa9 16 128 18 0.000039798911987149584\narmv7neon_mmm_f32_8x6_cortexa7 23 128 13 0.00005946225660703637\ngeneric_f32_4x4 7 32 5 0.000005462565009156774\narmv7neon_mmm_f32_8x6_generic 8 128 19 0.000025872976344928823\ngeneric_f32_4x4 5 4 12 0.0000037781682890084256\narmv7neon_mmm_f32_8x4_cortexa9 7 4 13 0.000003202055925058074\narmv7neon_mmm_f32_8x6_cortexa9 8 32 13 0.000006422608725593242\narmv7neon_mmm_f32_8x6_generic 9 128 7 0.000025813127546183672\narmv7neon_mmm_f32_8x4_generic 23 128 3 0.000014644120134860135\narmv7neon_mmm_f32_8x4_generic 8 4 3 0.0000011807711987459403\narmv7neon_mmm_f32_8x6_cortexa7 16 128 18 0.00003920283874886212\narmv7neon_mmm_f32_8x4_cortexa9 15 32 3 0.0000038264927174656245\narmv7neon_mmm_f32_8x6_cortexa7 25 32 12 0.000015882492018463845\narmv7neon_mmm_f32_8x6_generic 15 128 13 0.00003967007811496026\narmv7neon_mmm_f32_8x6_generic 25 4 12 0.00000523795399145237\narmv7neon_mmm_f32_8x4_cortexa9 16 4 7 0.0000028765969548151297\narmv7neon_mmm_f32_8x4_cortexa7 7 4 5 0.0000018096325702613645\narmv7neon_mmm_f32_8x6_generic 25 4 19 0.00001025642311454773\narmv7neon_mmm_f32_8x6_generic 9 128 6 0.000013045121912181557\narmv7neon_mmm_f32_8x6_cortexa7 15 32 11 0.000008770690105208661\narmv7neon_mmm_f32_8x6_generic 23 128 17 0.00005984628206960245\narmv7neon_mmm_f32_8x4_cortexa9 8 32 9 0.000005054839401749921\ngeneric_f32_4x4 9 32 13 0.000014812319931001716\narmv7neon_mmm_f32_8x4_cortexa9 8 32 7 0.0000036300075704923473\ngeneric_f32_4x4 12 128 12 0.00003080051055479753\narmv7neon_mmm_f32_8x4_generic 9 4 4 0.0000015775072922596973\ngeneric_f32_4x4 11 128 11 0.00003163786674887252\ngeneric_f32_4x4 11 128 7 0.000021315035021180117\narmv7neon_mmm_f32_8x4_generic 24 128 11 0.00004302931660486898\narmv7neon_mmm_f32_8x6_cortexa7 7 128 7 0.000013660184612749007\narmv7neon_mmm_f32_8x6_generic 15 4 12 0.0000032030364372184837\narmv7neon_mmm_f32_8x6_cortexa7 15 128 17 0.00004014988958130008\narmv7neon_mmm_f32_8x4_cortexa7 17 32 7 0.000009763008233938806\narmv7neon_mmm_f32_8x4_generic 23 4 11 0.000005850624187247901\narmv7neon_mmm_f32_8x6_cortexa9 25 4 7 0.000005691323284198507\narmv7neon_mmm_f32_8x6_generic 9 4 5 0.0000018995170310061328\ngeneric_f32_4x4 11 32 5 0.000007873582158405535\narmv7neon_mmm_f32_8x6_generic 17 4 7 0.0000043144070272553465\narmv7neon_mmm_f32_8x6_cortexa9 7 32 13 0.000006785328240517207\narmv7neon_mmm_f32_8x4_generic 25 128 12 0.00005645345647499795\narmv7neon_mmm_f32_8x4_generic 9 4 8 0.0000026364183937099707\narmv7neon_mmm_f32_8x4_generic 9 4 3 0.000001754783422972911\narmv7neon_mmm_f32_8x6_cortexa9 24 4 5 0.000002822627399800846\narmv7neon_mmm_f32_8x4_cortexa9 23 4 7 0.000004277460484355386\narmv7neon_mmm_f32_8x4_cortexa9 16 4 13 0.000004703052607837251\narmv7neon_mmm_f32_8x4_cortexa7 9 32 8 0.0000064472411584695615\ngeneric_f32_4x4 5 128 3 0.000007484402044593335\narmv7neon_mmm_f32_8x6_cortexa7 24 4 12 0.000003960884362652896\narmv7neon_mmm_f32_8x4_cortexa7 7 32 8 0.0000038146920144727004\narmv7neon_mmm_f32_8x4_cortexa9 24 4 12 0.000004810650577018378\narmv7neon_mmm_f32_8x4_cortexa9 9 4 13 0.000005031022350762627\narmv7neon_mmm_f32_8x6_cortexa9 9 128 12 0.000026841942459090627\ngeneric_f32_4x4 3 4 3 0.0000011084107564139803\narmv7neon_mmm_f32_8x4_generic 23 128 13 0.000057838678203389286\narmv7neon_mmm_f32_8x6_cortexa7 23 128 5 0.000020389937552256748\narmv7neon_mmm_f32_8x4_cortexa7 15 32 13 0.000012977109930123348\narmv7neon_mmm_f32_8x4_generic 16 32 11 0.00000919341672636989\narmv7neon_mmm_f32_8x6_cortexa9 17 128 17 0.000060215116898669664\narmv7neon_mmm_f32_8x4_generic 24 4 8 0.000003324206058446326\narmv7neon_mmm_f32_8x4_cortexa7 17 4 5 0.000003957357480955988\narmv7neon_mmm_f32_8x6_cortexa9 16 32 19 0.000016111976497239984\ngeneric_f32_4x4 13 4 12 0.000006710960240784156\narmv7neon_mmm_f32_8x6_cortexa9 23 128 5 0.000020639189093056077\narmv7neon_mmm_f32_8x6_generic 24 32 11 0.000012182073684360654\narmv7neon_mmm_f32_8x6_cortexa9 9 128 13 0.00004021781819635084\narmv7neon_mmm_f32_8x4_cortexa9 17 4 12 0.000005225946660532542\narmv7neon_mmm_f32_8x6_cortexa9 15 4 7 0.0000032840412749160575\narmv7neon_mmm_f32_8x4_cortexa9 17 128 13 0.000059220265497108164\narmv7neon_mmm_f32_8x6_cortexa9 7 4 11 0.0000020564874758047424\ngeneric_f32_4x4 8 128 8 0.000013969303780467269\narmv7neon_mmm_f32_8x4_cortexa7 25 4 12 0.000006595762756379242\narmv7neon_mmm_f32_8x6_generic 16 32 19 0.000015466876363304513\narmv7neon_mmm_f32_8x4_generic 24 4 11 0.0000053731939607204945\ngeneric_f32_4x4 8 128 13 0.00002774022280530327\ngeneric_f32_4x4 7 128 8 0.00001428610505820359\narmv7neon_mmm_f32_8x4_cortexa9 15 128 12 0.00002999051242337208\narmv7neon_mmm_f32_8x4_cortexa9 17 32 5 0.00000980239512919155\narmv7neon_mmm_f32_8x4_cortexa9 9 128 8 0.00001992992679699177\narmv7neon_mmm_f32_8x4_generic 25 32 5 0.00001226452231341447\ngeneric_f32_4x4 11 128 4 0.0000107871041093641\ngeneric_f32_4x4 13 32 5 0.000010185534520279402\narmv7neon_mmm_f32_8x6_cortexa7 9 32 13 0.000012306258740029597\ngeneric_f32_4x4 3 4 9 0.0000023591282065930476\narmv7neon_mmm_f32_8x4_cortexa7 15 128 12 0.000029408783696922103\narmv7neon_mmm_f32_8x6_cortexa9 25 4 5 0.000003460717936029046\narmv7neon_mmm_f32_8x6_cortexa9 15 32 7 0.000008659647810573247\narmv7neon_mmm_f32_8x6_generic 15 32 12 0.000008324450366911187\narmv7neon_mmm_f32_8x4_generic 7 4 4 0.0000011733671526145581\narmv7neon_mmm_f32_8x6_generic 8 32 17 0.000006245151171661497\ngeneric_f32_4x4 4 128 12 0.000010589994729591713\ngeneric_f32_4x4 13 32 3 0.000005623254183753802\narmv7neon_mmm_f32_8x4_cortexa9 17 32 8 0.000009489997139764281\narmv7neon_mmm_f32_8x6_cortexa7 15 128 12 0.0000267366663985427\narmv7neon_mmm_f32_8x6_generic 8 32 12 0.000004170620267459084\ngeneric_f32_4x4 12 128 13 0.00004142579938644744\narmv7neon_mmm_f32_8x6_cortexa7 7 4 17 0.0000028372537187841064\narmv7neon_mmm_f32_8x6_cortexa7 17 4 18 0.000005987713490844775\narmv7neon_mmm_f32_8x6_cortexa9 7 4 5 0.0000012358111649104604\narmv7neon_mmm_f32_8x4_generic 7 4 7 0.0000018542946008174326\narmv7neon_mmm_f32_8x4_generic 7 4 13 0.0000031498896105686864\narmv7neon_mmm_f32_8x6_cortexa9 15 4 11 0.0000034645213247919744\narmv7neon_mmm_f32_8x6_cortexa7 23 4 5 0.0000028436717084834024\narmv7neon_mmm_f32_8x4_generic 17 128 3 0.00001456481059501176\narmv7neon_mmm_f32_8x4_cortexa7 17 4 7 0.00000404574535805843\narmv7neon_mmm_f32_8x6_cortexa9 23 4 19 0.000008441880755451333\narmv7neon_mmm_f32_8x6_generic 16 4 5 0.000002037583190427157\narmv7neon_mmm_f32_8x4_cortexa7 17 4 11 0.000005618041346469936\ngeneric_f32_4x4 8 128 3 0.000007542706448510692\narmv7neon_mmm_f32_8x6_cortexa7 16 4 6 0.0000016599211327997808\narmv7neon_mmm_f32_8x4_generic 24 32 11 0.000013531694734026834\narmv7neon_mmm_f32_8x4_cortexa7 24 128 13 0.00005774273822898698\ngeneric_f32_4x4 12 32 8 0.000007328921605870887\narmv7neon_mmm_f32_8x4_generic 17 32 5 0.000009344539695515143\narmv7neon_mmm_f32_8x6_cortexa7 25 4 5 0.0000035354392629416224\narmv7neon_mmm_f32_8x4_generic 9 128 9 0.000028070243170981636\narmv7neon_mmm_f32_8x4_generic 7 32 7 0.0000036902878668462197\ngeneric_f32_4x4 4 32 5 0.000002913735845387932\narmv7neon_mmm_f32_8x4_cortexa7 23 128 9 0.00004401834026329482\narmv7neon_mmm_f32_8x6_cortexa7 15 32 17 0.000012793909984998035\narmv7neon_mmm_f32_8x4_cortexa7 24 32 11 0.000013985445748841752\narmv7neon_mmm_f32_8x4_generic 17 128 12 0.0000426589070381794\narmv7neon_mmm_f32_8x6_generic 9 128 19 0.000053237918402024005\narmv7neon_mmm_f32_8x6_cortexa7 17 4 17 0.000006443148021217296\narmv7neon_mmm_f32_8x4_cortexa7 8 128 12 0.000014559920878726009\narmv7neon_mmm_f32_8x6_cortexa9 8 4 19 0.000002972990505690349\narmv7neon_mmm_f32_8x6_cortexa7 25 128 5 0.000027009599886340098\narmv7neon_mmm_f32_8x6_cortexa7 7 32 5 0.0000025526809022378453\ngeneric_f32_4x4 13 128 13 0.00005568030788678991\narmv7neon_mmm_f32_8x4_cortexa7 15 32 12 0.000009774179275886764\narmv7neon_mmm_f32_8x4_generic 16 128 8 0.000018479196791574824\narmv7neon_mmm_f32_8x6_generic 25 128 17 0.00007786728690688477\narmv7neon_mmm_f32_8x6_generic 7 32 11 0.000004604319680609591\narmv7neon_mmm_f32_8x4_cortexa9 15 128 5 0.000020252737794258734\narmv7neon_mmm_f32_8x4_cortexa9 25 4 12 0.000006639922481547946\narmv7neon_mmm_f32_8x4_cortexa7 23 128 8 0.000029341008489406463\narmv7neon_mmm_f32_8x4_cortexa7 8 32 5 0.000003533774210652598\ngeneric_f32_4x4 8 128 7 0.000014303959630243704\narmv7neon_mmm_f32_8x4_cortexa9 25 32 5 0.000012867678837939383\narmv7neon_mmm_f32_8x6_generic 8 128 13 0.000019410057867203144\narmv7neon_mmm_f32_8x4_generic 15 4 5 0.0000029437961456768246\ngeneric_f32_4x4 12 4 9 0.000005303987106806706\narmv7neon_mmm_f32_8x6_generic 24 4 19 0.000007657205135344466\narmv7neon_mmm_f32_8x4_generic 9 4 11 0.000003929237868686891\ngeneric_f32_4x4 13 128 9 0.00004175491457338789\narmv7neon_mmm_f32_8x6_cortexa7 9 4 11 0.0000032396587513198363\narmv7neon_mmm_f32_8x6_cortexa7 9 32 5 0.000004609642726421324\narmv7neon_mmm_f32_8x6_cortexa7 8 128 13 0.000019980200207777093\ngeneric_f32_4x4 8 4 13 0.000004697441883300323\narmv7neon_mmm_f32_8x4_generic 24 128 5 0.000028404231306910303\narmv7neon_mmm_f32_8x4_cortexa7 15 128 8 0.00001974335359333113\narmv7neon_mmm_f32_8x4_generic 8 4 4 0.000000975840577396329\narmv7neon_mmm_f32_8x6_cortexa9 17 4 6 0.000002372991977511254\narmv7neon_mmm_f32_8x6_cortexa7 24 4 17 0.0000063580907262403335\narmv7neon_mmm_f32_8x6_cortexa9 24 4 17 0.00000634338841510274\narmv7neon_mmm_f32_8x6_cortexa7 8 128 19 0.000026457168307267402\narmv7neon_mmm_f32_8x4_cortexa7 15 32 4 0.0000035973058136569266\narmv7neon_mmm_f32_8x6_cortexa7 23 128 11 0.000040137380065831114\narmv7neon_mmm_f32_8x6_generic 8 32 11 0.0000044080486884835774\narmv7neon_mmm_f32_8x6_cortexa7 16 32 13 0.000012184909260827915\narmv7neon_mmm_f32_8x4_generic 24 128 12 0.00004230513222395041\narmv7neon_mmm_f32_8x6_generic 16 32 6 0.000004173665390794717\narmv7neon_mmm_f32_8x4_generic 15 128 5 0.000019049336126167508\ngeneric_f32_4x4 5 32 11 0.000007837409156830309\narmv7neon_mmm_f32_8x6_cortexa7 15 128 5 0.00001375002565612756\narmv7neon_mmm_f32_8x6_generic 17 128 17 0.00005946391401813929\ngeneric_f32_4x4 8 128 9 0.00002095153754448952\ngeneric_f32_4x4 12 4 7 0.000003927725122844973\narmv7neon_mmm_f32_8x6_cortexa9 25 32 6 0.000008303393078126922\narmv7neon_mmm_f32_8x4_generic 17 128 11 0.000043129532556527814\narmv7neon_mmm_f32_8x6_generic 23 128 19 0.00007849962174893098\narmv7neon_mmm_f32_8x4_cortexa9 25 32 11 0.00001896209821400977\ngeneric_f32_4x4 13 128 4 0.000014088650845963764\narmv7neon_mmm_f32_8x6_generic 17 32 17 0.000017768659574740396\narmv7neon_mmm_f32_8x6_generic 25 32 11 0.000016016335806526446\ngeneric_f32_4x4 4 128 9 0.000010729958874422145\narmv7neon_mmm_f32_8x6_generic 9 128 12 0.00002573025108782965\narmv7neon_mmm_f32_8x6_cortexa7 9 128 19 0.000052645037711891825\narmv7neon_mmm_f32_8x4_cortexa7 15 32 9 0.000009881132385003031\narmv7neon_mmm_f32_8x6_generic 15 4 11 0.0000033903277250974355\narmv7neon_mmm_f32_8x4_cortexa9 23 4 9 0.000005836030607874567\narmv7neon_mmm_f32_8x6_cortexa9 15 32 13 0.00001271636871027449\narmv7neon_mmm_f32_8x4_cortexa9 7 128 12 0.00001554481747780415\narmv7neon_mmm_f32_8x6_cortexa7 9 128 11 0.00002665664619685107\narmv7neon_mmm_f32_8x6_generic 23 4 19 0.000008276510705477435\narmv7neon_mmm_f32_8x6_cortexa7 9 128 13 0.00003963680038872068\narmv7neon_mmm_f32_8x6_generic 9 4 12 0.0000029664494321281043\narmv7neon_mmm_f32_8x4_cortexa7 9 4 8 0.0000026618049788927614\narmv7neon_mmm_f32_8x6_generic 25 4 5 0.0000034227395786859638\narmv7neon_mmm_f32_8x6_cortexa7 7 4 11 0.00000205419298901934\narmv7neon_mmm_f32_8x4_generic 15 128 4 0.000009727827829578247\narmv7neon_mmm_f32_8x4_generic 23 32 5 0.000009507106099903071\ngeneric_f32_4x4 13 128 3 0.000014531958964466404\ngeneric_f32_4x4 8 4 4 0.0000014932684997984591\narmv7neon_mmm_f32_8x6_cortexa7 9 32 12 0.0000083110843565406\narmv7neon_mmm_f32_8x4_cortexa9 9 128 4 0.000010214368441429339\narmv7neon_mmm_f32_8x6_cortexa7 24 128 11 0.000039934073240007294\narmv7neon_mmm_f32_8x4_cortexa9 25 128 9 0.0000592519545970783\narmv7neon_mmm_f32_8x4_cortexa7 17 32 13 0.000018457944384762052\narmv7neon_mmm_f32_8x6_generic 15 32 17 0.000012429374164019118\narmv7neon_mmm_f32_8x6_generic 7 4 5 0.0000012252588749082722\ngeneric_f32_4x4 4 4 11 0.000002142420120818127\narmv7neon_mmm_f32_8x4_cortexa7 17 32 8 0.000009331391202514006\narmv7neon_mmm_f32_8x6_cortexa9 9 32 12 0.000008401621869943723\narmv7neon_mmm_f32_8x6_cortexa7 16 32 17 0.00001235175359963665\narmv7neon_mmm_f32_8x4_cortexa9 16 4 11 0.000003828421619878369\narmv7neon_mmm_f32_8x4_cortexa9 9 4 8 0.000002691222544669364\narmv7neon_mmm_f32_8x4_cortexa9 15 32 4 0.0000036579305511926493\narmv7neon_mmm_f32_8x6_cortexa7 8 32 11 0.0000045660588709311665\ngeneric_f32_4x4 5 4 11 0.000003955847289217088\ngeneric_f32_4x4 7 128 5 0.00001437315927626327\ngeneric_f32_4x4 7 4 7 0.0000028946154117180987\ngeneric_f32_4x4 8 32 12 0.000007313658366544497\narmv7neon_mmm_f32_8x4_cortexa7 16 4 12 0.000003356919407698039\narmv7neon_mmm_f32_8x4_cortexa9 17 32 12 0.000013931859029317578\narmv7neon_mmm_f32_8x6_generic 7 128 13 0.000019807746403591995\narmv7neon_mmm_f32_8x6_generic 16 32 12 0.000007859148665693045\narmv7neon_mmm_f32_8x6_cortexa9 7 32 17 0.000006883792927542178\narmv7neon_mmm_f32_8x4_generic 24 32 4 0.000004632821669832301\narmv7neon_mmm_f32_8x6_cortexa7 15 32 18 0.000012578243798367562\narmv7neon_mmm_f32_8x6_cortexa9 17 128 7 0.000040256201289956\narmv7neon_mmm_f32_8x6_cortexa9 25 128 13 0.00007968673747995729\narmv7neon_mmm_f32_8x6_cortexa7 16 32 12 0.000008069060391993281\narmv7neon_mmm_f32_8x6_cortexa9 8 128 13 0.000020267415746870704\narmv7neon_mmm_f32_8x4_cortexa7 23 4 12 0.0000054788039005168164\narmv7neon_mmm_f32_8x4_cortexa7 7 128 9 0.000015183203798286496\narmv7neon_mmm_f32_8x4_cortexa9 25 128 11 0.00005936651957856526\narmv7neon_mmm_f32_8x6_cortexa9 7 128 13 0.00002061155210279855\narmv7neon_mmm_f32_8x6_cortexa9 16 128 12 0.000026606165960514827\narmv7neon_mmm_f32_8x4_cortexa7 24 4 7 0.000004016126966910536\narmv7neon_mmm_f32_8x4_cortexa9 16 32 4 0.000003429114676947513\narmv7neon_mmm_f32_8x4_cortexa7 9 128 7 0.000019732799833911844\narmv7neon_mmm_f32_8x6_generic 17 128 5 0.00001976574761517156\narmv7neon_mmm_f32_8x6_cortexa7 16 128 12 0.00002622611042000424\narmv7neon_mmm_f32_8x4_generic 23 128 4 0.000014300391832012492\ngeneric_f32_4x4 4 32 9 0.0000040473563381120025\narmv7neon_mmm_f32_8x6_cortexa9 16 4 12 0.00000281228428913529\narmv7neon_mmm_f32_8x6_generic 15 128 7 0.000025996819067612833\narmv7neon_mmm_f32_8x4_generic 25 128 13 0.00007519120583347806\narmv7neon_mmm_f32_8x6_cortexa7 25 128 18 0.00007816972627698026\narmv7neon_mmm_f32_8x6_generic 23 4 7 0.000004478475265472157\narmv7neon_mmm_f32_8x6_cortexa7 23 32 17 0.000018694963726284716\narmv7neon_mmm_f32_8x4_cortexa9 24 128 3 0.000015528002576413032\narmv7neon_mmm_f32_8x4_generic 15 4 13 0.000005299524671067579\narmv7neon_mmm_f32_8x6_cortexa9 8 32 7 0.000004505823387651013\narmv7neon_mmm_f32_8x4_cortexa9 9 4 7 0.0000028943361192655857\narmv7neon_mmm_f32_8x4_generic 9 32 4 0.0000033743539224745525\narmv7neon_mmm_f32_8x4_cortexa9 23 128 5 0.000030175843280997272\narmv7neon_mmm_f32_8x6_generic 8 4 6 0.000001069506404381519\narmv7neon_mmm_f32_8x6_cortexa9 9 32 17 0.000012530842578999487\narmv7neon_mmm_f32_8x6_generic 25 4 7 0.0000055691779651847645\narmv7neon_mmm_f32_8x6_cortexa7 24 32 18 0.000017489152288872757\narmv7neon_mmm_f32_8x4_generic 8 32 3 0.0000020846296384167996\narmv7neon_mmm_f32_8x4_generic 24 128 9 0.000042880640962818326\narmv7neon_mmm_f32_8x6_cortexa7 16 32 6 0.000004280562843189148\ngeneric_f32_4x4 11 4 7 0.000004035828927778453\narmv7neon_mmm_f32_8x6_cortexa7 17 128 6 0.000019945123788870955\narmv7neon_mmm_f32_8x6_cortexa7 23 4 13 0.000006544590047318178\ngeneric_f32_4x4 3 128 13 0.00001453817180213847\narmv7neon_mmm_f32_8x6_cortexa9 7 4 7 0.000001955919692684627\narmv7neon_mmm_f32_8x4_generic 8 4 8 0.0000014466616080596564\narmv7neon_mmm_f32_8x4_cortexa9 7 4 12 0.00000257120273391917\narmv7neon_mmm_f32_8x6_cortexa7 23 32 12 0.000012365351347550488\narmv7neon_mmm_f32_8x4_cortexa7 24 128 8 0.000028841373491782905\ngeneric_f32_4x4 12 4 4 0.0000019842828386500398\narmv7neon_mmm_f32_8x4_cortexa7 25 32 11 0.000018655746399310692\narmv7neon_mmm_f32_8x4_cortexa7 15 4 5 0.0000029733858994116004\narmv7neon_mmm_f32_8x4_cortexa9 8 128 3 0.000005515258544137823\narmv7neon_mmm_f32_8x6_cortexa7 24 4 7 0.000004423529851941163\ngeneric_f32_4x4 8 32 4 0.0000027872687254075905\narmv7neon_mmm_f32_8x6_generic 17 32 18 0.000017325844518327246\narmv7neon_mmm_f32_8x4_cortexa7 25 4 13 0.000009174920440845347\narmv7neon_mmm_f32_8x4_generic 25 128 8 0.00003833303029711307\narmv7neon_mmm_f32_8x4_generic 16 4 4 0.0000014454956826662534\narmv7neon_mmm_f32_8x4_cortexa7 17 4 4 0.0000020761952577578897\narmv7neon_mmm_f32_8x6_cortexa7 8 32 5 0.0000026618366158480003\ngeneric_f32_4x4 3 4 4 0.0000011319274511202646\narmv7neon_mmm_f32_8x6_cortexa7 8 32 17 0.000006455239935042666\narmv7neon_mmm_f32_8x4_cortexa7 15 4 3 0.0000018508727527174426\narmv7neon_mmm_f32_8x4_cortexa9 7 128 5 0.000010464038311697658\narmv7neon_mmm_f32_8x4_cortexa7 9 128 11 0.000029303633762621288\narmv7neon_mmm_f32_8x6_generic 15 128 6 0.000013191467223518409\narmv7neon_mmm_f32_8x6_generic 8 32 19 0.00000799220605890635\narmv7neon_mmm_f32_8x4_cortexa7 25 32 3 0.000006944685928632884\ngeneric_f32_4x4 5 128 7 0.000014344402542266391\narmv7neon_mmm_f32_8x6_generic 25 32 18 0.00002286640455940777\narmv7neon_mmm_f32_8x6_generic 23 4 17 0.000006658878255694999\narmv7neon_mmm_f32_8x4_cortexa9 15 32 11 0.000010153701137788554\narmv7neon_mmm_f32_8x4_cortexa7 8 32 13 0.000006386976234107792\ngeneric_f32_4x4 9 128 11 0.0000315155364444221\narmv7neon_mmm_f32_8x4_generic 16 128 5 0.00001883838484826069\narmv7neon_mmm_f32_8x6_generic 8 4 18 0.0000021936894870634253\narmv7neon_mmm_f32_8x6_cortexa7 24 32 17 0.00001822762304619792\narmv7neon_mmm_f32_8x6_generic 24 32 6 0.000006012607283304525\narmv7neon_mmm_f32_8x4_cortexa9 7 4 8 0.0000018992159881966028\ngeneric_f32_4x4 5 32 8 0.000005268936385502091\narmv7neon_mmm_f32_8x6_generic 23 32 13 0.000017886686307111186\narmv7neon_mmm_f32_8x6_generic 23 128 11 0.00004012008266709576\narmv7neon_mmm_f32_8x4_generic 7 32 3 0.000002052664755929161\narmv7neon_mmm_f32_8x4_generic 7 32 13 0.0000068232658761941584\narmv7neon_mmm_f32_8x4_cortexa9 15 4 3 0.0000018728759441468222\narmv7neon_mmm_f32_8x4_cortexa9 17 32 7 0.000009921472462970692\narmv7neon_mmm_f32_8x4_cortexa7 23 4 5 0.000004108135771777712\narmv7neon_mmm_f32_8x6_generic 17 4 18 0.0000058654454576938545\narmv7neon_mmm_f32_8x6_generic 8 32 6 0.0000023314322550231567\narmv7neon_mmm_f32_8x6_cortexa9 24 32 12 0.000011982465286422162\narmv7neon_mmm_f32_8x6_cortexa9 9 4 12 0.000003049708218088651\narmv7neon_mmm_f32_8x6_cortexa7 9 4 19 0.000005629342662833259\ngeneric_f32_4x4 3 32 12 0.00000437213523735793\narmv7neon_mmm_f32_8x6_generic 23 128 18 0.00005941769593137103\narmv7neon_mmm_f32_8x6_cortexa7 15 4 18 0.000004622352036845354\narmv7neon_mmm_f32_8x6_cortexa9 24 128 19 0.00007950053817997623\narmv7neon_mmm_f32_8x4_generic 16 128 4 0.000009494054447373921\narmv7neon_mmm_f32_8x6_cortexa7 7 128 17 0.000020414604977074606\narmv7neon_mmm_f32_8x4_cortexa9 7 32 12 0.000005535747117413851\narmv7neon_mmm_f32_8x6_cortexa9 17 4 5 0.000002695145363194299\ngeneric_f32_4x4 4 128 4 0.000003870015760471616\narmv7neon_mmm_f32_8x4_generic 15 32 11 0.000009681542653487903\narmv7neon_mmm_f32_8x6_cortexa7 7 4 13 0.000002737908308165428\narmv7neon_mmm_f32_8x4_cortexa7 9 32 7 0.000006661491356810654\narmv7neon_mmm_f32_8x4_generic 16 4 8 0.0000023864199459892154\narmv7neon_mmm_f32_8x6_generic 24 32 12 0.000011535291893158489\narmv7neon_mmm_f32_8x6_cortexa9 15 128 11 0.00002729544131868298\narmv7neon_mmm_f32_8x4_cortexa7 15 128 13 0.00003933216865420138\ngeneric_f32_4x4 11 4 4 0.0000021466514853413024\narmv7neon_mmm_f32_8x4_cortexa7 15 32 7 0.000006892235042247499\narmv7neon_mmm_f32_8x4_generic 24 128 7 0.000028572043647170913\narmv7neon_mmm_f32_8x4_cortexa9 9 128 12 0.000029617239836207004\narmv7neon_mmm_f32_8x6_generic 23 32 6 0.000006267673062941748\narmv7neon_mmm_f32_8x6_cortexa9 23 128 17 0.00006059797167110977\narmv7neon_mmm_f32_8x6_cortexa9 9 128 18 0.00004014798753858387\narmv7neon_mmm_f32_8x6_generic 25 32 19 0.00003066922065226773\narmv7neon_mmm_f32_8x6_generic 9 32 7 0.00000813819010478461\narmv7neon_mmm_f32_8x6_generic 8 128 18 0.000019253370491633585\narmv7neon_mmm_f32_8x6_generic 8 4 5 0.0000012747243711687536\narmv7neon_mmm_f32_8x4_cortexa7 25 4 4 0.000002551159854962235\narmv7neon_mmm_f32_8x4_cortexa9 8 128 13 0.000019869699675831398\narmv7neon_mmm_f32_8x6_cortexa9 9 128 19 0.00005342392770358122\narmv7neon_mmm_f32_8x6_cortexa9 7 4 12 0.000002051630860797947\narmv7neon_mmm_f32_8x6_cortexa9 16 128 13 0.00004015173417099066\narmv7neon_mmm_f32_8x6_cortexa9 23 128 12 0.0000403164031404094\narmv7neon_mmm_f32_8x4_generic 23 128 5 0.000028627967286799668\narmv7neon_mmm_f32_8x4_cortexa9 25 4 8 0.000004634829587692317\narmv7neon_mmm_f32_8x4_cortexa7 15 128 4 0.000010126569600218532\narmv7neon_mmm_f32_8x4_cortexa9 23 32 4 0.000005123975664670012\narmv7neon_mmm_f32_8x6_generic 8 4 17 0.0000024242275965441413\narmv7neon_mmm_f32_8x6_cortexa7 25 128 7 0.000052727920949761586\narmv7neon_mmm_f32_8x4_cortexa7 16 32 9 0.000009437089513621594\narmv7neon_mmm_f32_8x4_generic 23 4 9 0.000005734226973840947\narmv7neon_mmm_f32_8x4_cortexa7 17 128 12 0.0000433599443382883\narmv7neon_mmm_f32_8x4_cortexa7 24 32 3 0.000005409349524302138\narmv7neon_mmm_f32_8x6_cortexa7 8 4 7 0.0000018575873013342354\narmv7neon_mmm_f32_8x6_generic 7 128 19 0.000026452799909726964\narmv7neon_mmm_f32_8x4_cortexa9 24 32 12 0.000013540981249723916\narmv7neon_mmm_f32_8x4_cortexa9 23 128 9 0.000044894692860566556\narmv7neon_mmm_f32_8x6_cortexa9 9 4 17 0.000004505536489134122\narmv7neon_mmm_f32_8x6_generic 15 32 11 0.000008507986633036132\narmv7neon_mmm_f32_8x6_cortexa7 7 32 19 0.000008805049700237493\narmv7neon_mmm_f32_8x4_generic 17 4 4 0.0000020583169447652696\narmv7neon_mmm_f32_8x4_generic 7 32 9 0.000005237513207003328\narmv7neon_mmm_f32_8x6_cortexa9 8 4 5 0.0000012863007612366299\narmv7neon_mmm_f32_8x6_cortexa7 16 128 17 0.00003972939922069338\narmv7neon_mmm_f32_8x6_cortexa9 16 32 17 0.000012476036240479964\ngeneric_f32_4x4 5 128 4 0.000007343673936530293\narmv7neon_mmm_f32_8x6_cortexa7 17 4 13 0.000006264666040125929\narmv7neon_mmm_f32_8x6_cortexa9 25 128 18 0.00007930397959821057\narmv7neon_mmm_f32_8x6_cortexa9 16 4 5 0.0000020584584410478216\narmv7neon_mmm_f32_8x4_cortexa9 7 32 5 0.00000378972657151533\narmv7neon_mmm_f32_8x6_cortexa9 16 4 18 0.000003945842563203367\narmv7neon_mmm_f32_8x4_generic 23 32 7 0.000009665874611953724\narmv7neon_mmm_f32_8x6_cortexa7 25 128 6 0.000026459671946913376\ngeneric_f32_4x4 7 4 3 0.0000017591858474192314\narmv7neon_mmm_f32_8x6_cortexa7 16 4 18 0.000003943668999181098\ngeneric_f32_4x4 4 128 7 0.0000074032510257738404\narmv7neon_mmm_f32_8x4_generic 16 128 13 0.0000374668929079076\narmv7neon_mmm_f32_8x4_cortexa9 9 4 3 0.0000017875665004976476\narmv7neon_mmm_f32_8x4_cortexa9 15 128 9 0.00003010940924012135\narmv7neon_mmm_f32_8x6_cortexa9 17 32 13 0.000018295003723366057\narmv7neon_mmm_f32_8x6_cortexa9 9 4 13 0.000004395459186352696\narmv7neon_mmm_f32_8x6_cortexa7 8 4 12 0.0000016616372440035075\ngeneric_f32_4x4 5 128 12 0.000021001699183635672\narmv7neon_mmm_f32_8x6_generic 25 4 6 0.000002871872138224116\narmv7neon_mmm_f32_8x6_generic 7 128 5 0.000006875620566369796\ngeneric_f32_4x4 5 4 4 0.000001600585638499612\narmv7neon_mmm_f32_8x4_cortexa9 23 4 4 0.0000022070594896923405\narmv7neon_mmm_f32_8x6_cortexa9 8 32 17 0.0000065161951430999815\narmv7neon_mmm_f32_8x6_cortexa7 17 4 19 0.000008057471663557758\narmv7neon_mmm_f32_8x6_cortexa9 8 128 7 0.000013701107804035602\narmv7neon_mmm_f32_8x4_cortexa9 23 128 11 0.00004502007320686303\narmv7neon_mmm_f32_8x4_cortexa7 8 32 11 0.000004991462488279611\narmv7neon_mmm_f32_8x6_cortexa9 24 32 18 0.00001769207861513086\narmv7neon_mmm_f32_8x4_generic 25 128 4 0.000019508351532460557\narmv7neon_mmm_f32_8x6_cortexa7 23 4 12 0.0000044400455773711846\narmv7neon_mmm_f32_8x6_cortexa9 24 32 5 0.000006843511403564935\narmv7neon_mmm_f32_8x6_cortexa9 25 128 11 0.000053747765759359946\narmv7neon_mmm_f32_8x4_cortexa7 25 4 11 0.000007253341541711691\narmv7neon_mmm_f32_8x6_cortexa9 23 32 19 0.000024549141146277552\narmv7neon_mmm_f32_8x6_cortexa7 16 128 11 0.000026732044849478285\narmv7neon_mmm_f32_8x6_cortexa9 23 128 19 0.0000801357471530611\ngeneric_f32_4x4 3 32 4 0.0000017966908912230104\narmv7neon_mmm_f32_8x6_cortexa7 24 128 17 0.00005929496892410179\narmv7neon_mmm_f32_8x6_cortexa9 23 32 12 0.000012490651961414628\narmv7neon_mmm_f32_8x4_cortexa7 16 128 9 0.000029084669529414875\narmv7neon_mmm_f32_8x6_cortexa7 7 128 19 0.000026966388966864694\narmv7neon_mmm_f32_8x4_cortexa7 23 32 8 0.000009566852853696024\narmv7neon_mmm_f32_8x4_generic 17 32 13 0.00001785315018140605\narmv7neon_mmm_f32_8x4_cortexa7 23 4 4 0.00000218193596958567\narmv7neon_mmm_f32_8x4_generic 9 32 7 0.000006462010776076977\narmv7neon_mmm_f32_8x4_generic 23 32 8 0.000009259693411977238\narmv7neon_mmm_f32_8x6_generic 15 128 12 0.000025997314252108204\ngeneric_f32_4x4 12 32 5 0.000007719446523382617\narmv7neon_mmm_f32_8x6_cortexa9 15 128 17 0.000040755037497853366\narmv7neon_mmm_f32_8x4_generic 24 32 5 0.00000927870929356689\narmv7neon_mmm_f32_8x4_cortexa9 23 32 11 0.00001470624762457902\narmv7neon_mmm_f32_8x4_cortexa7 7 128 7 0.000010321518649139075\narmv7neon_mmm_f32_8x4_cortexa9 17 32 3 0.000005401396003956081\narmv7neon_mmm_f32_8x4_cortexa7 25 128 5 0.00003910615015651058\narmv7neon_mmm_f32_8x6_cortexa9 17 128 18 0.00005978117998553773\narmv7neon_mmm_f32_8x6_generic 7 4 11 0.000002036996116605385\narmv7neon_mmm_f32_8x6_cortexa9 15 4 6 0.0000019083928751646085\narmv7neon_mmm_f32_8x6_cortexa9 9 4 19 0.000005613783088587898\narmv7neon_mmm_f32_8x4_cortexa7 24 32 4 0.0000047895509427620865\narmv7neon_mmm_f32_8x4_cortexa9 8 32 12 0.000004854203771648976\narmv7neon_mmm_f32_8x4_generic 25 4 11 0.000007170056956827459\narmv7neon_mmm_f32_8x6_cortexa7 24 32 6 0.000006172583934492533\narmv7neon_mmm_f32_8x6_generic 23 32 18 0.000017730914760808268\ngeneric_f32_4x4 9 128 4 0.00001072609031817021\narmv7neon_mmm_f32_8x6_generic 16 32 7 0.000008131169126293927\narmv7neon_mmm_f32_8x6_cortexa9 8 32 11 0.000004596930720411251\ngeneric_f32_4x4 3 128 3 0.000003987497974732173\narmv7neon_mmm_f32_8x6_generic 16 32 18 0.000011518507884431023\ngeneric_f32_4x4 12 4 13 0.0000067677797185944386\narmv7neon_mmm_f32_8x6_cortexa7 17 4 12 0.00000420578831765641\narmv7neon_mmm_f32_8x6_cortexa7 25 128 11 0.00005298746347142357\narmv7neon_mmm_f32_8x6_cortexa9 17 4 17 0.0000064380265240168815\narmv7neon_mmm_f32_8x4_cortexa9 16 32 3 0.000003850508528044626\narmv7neon_mmm_f32_8x4_cortexa7 15 4 9 0.0000041824910345443945\narmv7neon_mmm_f32_8x6_cortexa7 16 4 13 0.000004283476073931167\narmv7neon_mmm_f32_8x6_cortexa9 15 32 19 0.00001676006214460718\narmv7neon_mmm_f32_8x6_cortexa9 9 4 6 0.0000017882273242368877\ngeneric_f32_4x4 11 4 12 0.000005385894826689573\narmv7neon_mmm_f32_8x6_generic 25 128 11 0.00005279747695062305\narmv7neon_mmm_f32_8x4_generic 25 128 11 0.000057163651606893397\narmv7neon_mmm_f32_8x6_generic 16 32 13 0.000011809288098621582\narmv7neon_mmm_f32_8x6_cortexa7 16 128 7 0.00002656376898311115\narmv7neon_mmm_f32_8x6_generic 8 4 19 0.0000028980842866481956\narmv7neon_mmm_f32_8x6_cortexa9 25 4 17 0.000008360160748075129\narmv7neon_mmm_f32_8x6_cortexa7 25 32 17 0.00002416762541107772\narmv7neon_mmm_f32_8x4_cortexa7 9 4 11 0.000003971891623914232\narmv7neon_mmm_f32_8x6_cortexa7 8 128 18 0.000019766077162036943\narmv7neon_mmm_f32_8x4_generic 15 32 3 0.0000036533992903578352\narmv7neon_mmm_f32_8x6_cortexa7 25 32 5 0.0000087944366611451\narmv7neon_mmm_f32_8x6_cortexa7 7 128 5 0.000007076691588612229\narmv7neon_mmm_f32_8x6_generic 23 4 6 0.000002427909563551317\narmv7neon_mmm_f32_8x4_cortexa9 17 4 9 0.000005573474441160942\narmv7neon_mmm_f32_8x4_cortexa9 25 4 5 0.000005112325491179266\ngeneric_f32_4x4 8 32 5 0.000005323391922801219\narmv7neon_mmm_f32_8x6_cortexa7 24 128 18 0.00005856236723783224\narmv7neon_mmm_f32_8x6_generic 23 4 18 0.000006215918837505929\narmv7neon_mmm_f32_8x4_generic 16 32 4 0.000003260594187485516\narmv7neon_mmm_f32_8x4_generic 8 4 11 0.000002124764231070715\narmv7neon_mmm_f32_8x6_cortexa9 15 128 7 0.000027104995655962794\narmv7neon_mmm_f32_8x4_cortexa7 23 4 3 0.000002539994674766333\narmv7neon_mmm_f32_8x4_cortexa9 15 128 13 0.000040120613837028105\narmv7neon_mmm_f32_8x4_generic 9 32 5 0.000006416826225047117\narmv7neon_mmm_f32_8x6_generic 9 128 13 0.000039231938860404315\narmv7neon_mmm_f32_8x4_cortexa7 17 32 9 0.000014080751383917914\narmv7neon_mmm_f32_8x4_generic 24 4 5 0.0000038475341959329615\narmv7neon_mmm_f32_8x6_cortexa7 17 32 19 0.00002385320479583871\narmv7neon_mmm_f32_8x4_cortexa9 25 4 7 0.000005243554539274239\narmv7neon_mmm_f32_8x4_cortexa9 8 128 7 0.000010306643098752432\narmv7neon_mmm_f32_8x4_cortexa7 9 32 9 0.000009612025649062918\narmv7neon_mmm_f32_8x6_generic 7 128 6 0.000006918233017670394\narmv7neon_mmm_f32_8x6_cortexa7 23 32 6 0.000006480642874727307\narmv7neon_mmm_f32_8x4_cortexa7 17 4 8 0.0000036467312614380765\narmv7neon_mmm_f32_8x6_cortexa9 9 4 18 0.000004263423188010347\narmv7neon_mmm_f32_8x4_cortexa9 23 4 3 0.000002565453674896801\narmv7neon_mmm_f32_8x6_cortexa7 17 128 17 0.00005934218053895787\narmv7neon_mmm_f32_8x6_cortexa9 9 128 11 0.00002703084778716899\narmv7neon_mmm_f32_8x6_cortexa7 8 32 19 0.000008254990878887053\narmv7neon_mmm_f32_8x4_cortexa7 24 4 11 0.000005437311620791031\narmv7neon_mmm_f32_8x6_cortexa7 17 32 12 0.000012103957972190128\narmv7neon_mmm_f32_8x6_cortexa7 16 32 11 0.000008575915963542344\narmv7neon_mmm_f32_8x6_cortexa7 15 128 13 0.00003995376024317136\narmv7neon_mmm_f32_8x4_cortexa7 25 128 12 0.00005747997493377876\narmv7neon_mmm_f32_8x6_cortexa7 9 4 17 0.000004513992669521213\narmv7neon_mmm_f32_8x4_cortexa9 7 32 11 0.000005542086596158504\narmv7neon_mmm_f32_8x4_generic 16 32 8 0.00000600791906169857\narmv7neon_mmm_f32_8x6_cortexa9 8 4 6 0.000001079992540189233\narmv7neon_mmm_f32_8x6_cortexa7 15 4 7 0.0000033103105460849674\narmv7neon_mmm_f32_8x6_generic 7 4 18 0.0000027798204465348195\narmv7neon_mmm_f32_8x4_cortexa9 9 4 4 0.0000016086729123039774\narmv7neon_mmm_f32_8x4_cortexa9 17 4 13 0.000007139907020236834\narmv7neon_mmm_f32_8x6_generic 7 4 17 0.000002811022144491351\narmv7neon_mmm_f32_8x4_generic 16 4 12 0.00000330778427406012\narmv7neon_mmm_f32_8x6_cortexa7 17 32 7 0.000012331946306204285\narmv7neon_mmm_f32_8x4_cortexa7 23 128 4 0.000014866985895709814\narmv7neon_mmm_f32_8x6_generic 8 128 5 0.000006930294425648014\narmv7neon_mmm_f32_8x6_cortexa7 7 128 11 0.000013755100434106755\narmv7neon_mmm_f32_8x4_cortexa7 9 4 3 0.0000017694089316929863\narmv7neon_mmm_f32_8x4_cortexa9 25 128 5 0.00003989811331140034\narmv7neon_mmm_f32_8x6_cortexa9 24 4 18 0.000005659337689416026\narmv7neon_mmm_f32_8x6_cortexa7 7 32 7 0.000004615694133689407\narmv7neon_mmm_f32_8x4_generic 23 4 12 0.000005414200630328292\narmv7neon_mmm_f32_8x6_cortexa7 15 128 7 0.00002674534329947021\narmv7neon_mmm_f32_8x4_cortexa9 23 128 13 0.0000596298628444228\narmv7neon_mmm_f32_8x6_cortexa9 7 32 19 0.000008889380829716555\narmv7neon_mmm_f32_8x6_cortexa7 8 4 17 0.0000025025489216272165\narmv7neon_mmm_f32_8x6_cortexa9 24 128 5 0.000020677151329306246\narmv7neon_mmm_f32_8x6_cortexa9 17 32 17 0.000018491383989214777\narmv7neon_mmm_f32_8x4_cortexa7 8 4 11 0.000002146399138479012\narmv7neon_mmm_f32_8x6_cortexa7 7 4 19 0.0000034947036576981197\narmv7neon_mmm_f32_8x6_cortexa7 15 32 13 0.00001261074648998989\narmv7neon_mmm_f32_8x6_cortexa7 17 128 12 0.000039467248529014934\narmv7neon_mmm_f32_8x6_cortexa9 15 128 13 0.0000405486222699268\narmv7neon_mmm_f32_8x6_cortexa7 8 4 6 0.0000010838257676938484\narmv7neon_mmm_f32_8x4_cortexa7 8 128 11 0.00001478367268084039\ngeneric_f32_4x4 13 4 5 0.000005005970941882336\narmv7neon_mmm_f32_8x4_generic 9 4 5 0.0000027981911123646093\narmv7neon_mmm_f32_8x4_cortexa9 24 32 3 0.000005499754513818659\narmv7neon_mmm_f32_8x4_cortexa9 16 32 8 0.00000632372871965183\narmv7neon_mmm_f32_8x6_generic 25 32 7 0.000015750403152655726\narmv7neon_mmm_f32_8x6_cortexa7 9 128 6 0.000013469652928515112\ngeneric_f32_4x4 5 128 8 0.000014178354640152658\narmv7neon_mmm_f32_8x4_cortexa9 25 32 13 0.00002471922478461092\narmv7neon_mmm_f32_8x4_generic 15 32 7 0.000006693321105057111\narmv7neon_mmm_f32_8x4_cortexa9 23 128 8 0.000029940232861406133\narmv7neon_mmm_f32_8x4_cortexa7 7 128 5 0.000010257823038892243\ngeneric_f32_4x4 4 32 12 0.000003911119050209719\narmv7neon_mmm_f32_8x6_cortexa7 8 128 7 0.000013528068455942153\narmv7neon_mmm_f32_8x4_cortexa7 24 4 5 0.000003893712023496801\narmv7neon_mmm_f32_8x4_cortexa7 9 4 5 0.00000282867688987085\narmv7neon_mmm_f32_8x6_generic 9 4 6 0.0000017337687872243595\narmv7neon_mmm_f32_8x4_generic 8 32 11 0.000004839072297522764\narmv7neon_mmm_f32_8x4_cortexa7 16 32 8 0.000006215610136029107\narmv7neon_mmm_f32_8x4_cortexa7 24 128 12 0.00004296408794693615\narmv7neon_mmm_f32_8x6_generic 23 32 7 0.000012139369977001298\narmv7neon_mmm_f32_8x6_cortexa9 23 32 7 0.00001262657347999726\narmv7neon_mmm_f32_8x6_generic 9 32 18 0.000011811540149507637\ngeneric_f32_4x4 4 4 12 0.0000019950687694365225\narmv7neon_mmm_f32_8x4_cortexa9 17 32 13 0.000018760347719013833\narmv7neon_mmm_f32_8x4_generic 24 128 8 0.000027893007985374452\narmv7neon_mmm_f32_8x6_generic 7 32 12 0.000004596558109391685\ngeneric_f32_4x4 12 32 12 0.0000107075710637508\narmv7neon_mmm_f32_8x6_generic 16 4 18 0.000003888078280974674\ngeneric_f32_4x4 12 128 7 0.000021189568041458556\narmv7neon_mmm_f32_8x6_generic 16 128 11 0.000025972024072475202\narmv7neon_mmm_f32_8x6_cortexa7 15 4 13 0.000004683720683139814\narmv7neon_mmm_f32_8x6_cortexa9 7 128 6 0.000007213870080063144\narmv7neon_mmm_f32_8x6_cortexa9 23 128 11 0.00004071170407195971\narmv7neon_mmm_f32_8x6_generic 24 4 5 0.000002793903924978298\narmv7neon_mmm_f32_8x4_cortexa9 15 32 9 0.000010036115634563751\ngeneric_f32_4x4 9 128 9 0.000031422044792444864\narmv7neon_mmm_f32_8x4_generic 25 32 4 0.000006133898665196312\ngeneric_f32_4x4 13 128 8 0.00002772292203595808\n"
  },
  {
    "path": "linalg/src/arm32/cortex_a9.rs",
    "content": "use crate::frame::mmm::CostModel;\npub fn model() -> CostModel<'static> {\n    CostModel {\n        big_product_mkn_threshold: 4194036.0,\n        big_product_kernel_choice: \"armv7neon_mmm_f32_8x6_cortexa9\",\n        kernels: &[\n            \"armv7neon_mmm_f32_8x4_cortexa7\",\n            \"armv7neon_mmm_f32_8x4_cortexa9\",\n            \"armv7neon_mmm_f32_8x4_generic\",\n            \"armv7neon_mmm_f32_8x6_cortexa7\",\n            \"armv7neon_mmm_f32_8x6_cortexa9\",\n            \"armv7neon_mmm_f32_8x6_generic\",\n            \"generic_f32_4x4\",\n        ],\n        mrs: &[4, 8],\n        nrs: &[4, 6],\n        feat_norm_mean: &[\n            4.582296677813486,\n            4.595402322442016,\n            4.571260231028445,\n            13.748959231283994,\n            1.5179177668804225,\n            0.7575757575757576,\n            3.5337608449641644,\n            0.8831887338111405,\n            1.5048409405255878,\n            0.7526719476926946,\n            2.489123601156796,\n            0.8326417704011065,\n        ],\n        feat_norm_stddev: &[\n            1.2635817489024164,\n            1.2723436827339079,\n            1.2620157548883217,\n            1.3497763942449361,\n            1.1141159992246472,\n            0.42854956435545316,\n            2.2880460409304937,\n            0.32119525880720723,\n            1.1154901716833412,\n            0.43145902105435263,\n            1.7051378780434328,\n            0.37329539587896904,\n        ],\n        w1: &[\n            0.5391961336135864,\n            -0.32089367508888245,\n            0.203999862074852,\n            -0.10011337697505951,\n            0.09040801972150803,\n            -0.14198464155197144,\n            0.031854499131441116,\n            0.12334256619215012,\n            0.15339604020118713,\n            -0.20091375708580017,\n            -0.014548280276358128,\n            0.12154694646596909,\n            0.31225234270095825,\n            0.10782113671302795,\n            0.44618168473243713,\n            0.8267014026641846,\n            -0.1204405128955841,\n            -0.08261110633611679,\n            -0.052502430975437164,\n            0.3066086769104004,\n            0.1493932157754898,\n            -0.14119412004947662,\n            -0.1985343098640442,\n            0.19361039996147156,\n            -0.4636686146259308,\n            0.08120443671941757,\n            0.03210291638970375,\n            0.17303235828876495,\n            0.16502155363559723,\n            -0.19771894812583923,\n            -0.11060577630996704,\n            0.08698348701000214,\n            -0.07793445140123367,\n            0.32749465107917786,\n            0.3663202226161957,\n            -0.4629170894622803,\n            -0.1586134433746338,\n            0.4272242486476898,\n            -0.12016090005636215,\n            -0.17830348014831543,\n            -0.05493386462330818,\n            -0.036517318338155746,\n            0.01293050218373537,\n            0.016577009111642838,\n            0.10738552361726761,\n            -0.3662779629230499,\n            -0.2917434275150299,\n            0.5752639770507812,\n            0.11406347155570984,\n            0.8622727394104004,\n            0.07158719748258591,\n            0.29530274868011475,\n            -0.11287810653448105,\n            0.12262264639139175,\n            0.02478562481701374,\n            0.17749948799610138,\n            -0.036227867007255554,\n            0.10140471905469894,\n            -0.011896232143044472,\n            -0.021761735901236534,\n            0.06046223267912865,\n            0.5727048516273499,\n            -0.007826486602425575,\n            0.3863913118839264,\n            -0.04224887117743492,\n            0.056023009121418,\n            -0.02467598207294941,\n            0.0385640449821949,\n            0.0219524335116148,\n            -0.03437826409935951,\n            -0.2060588151216507,\n            0.2895224988460541,\n            0.10751669108867645,\n            0.00845037866383791,\n            -0.1836385875940323,\n            -0.24757762253284454,\n            -0.09606243669986725,\n            0.03918633610010147,\n            0.07913251221179962,\n            0.06499160826206207,\n            -0.08156774938106537,\n            0.08835449814796448,\n            0.13896305859088898,\n            -0.16936920583248138,\n            0.010146846994757652,\n            -0.42553824186325073,\n            0.39916151762008667,\n            -0.004584060981869698,\n            -0.10256388038396835,\n            0.041573416441679,\n            0.05155385658144951,\n            0.015019520185887814,\n            0.09554271399974823,\n            -0.20487457513809204,\n            -0.4146610200405121,\n            -0.773110032081604,\n            0.3662724494934082,\n            -0.23762361705303192,\n            0.6974321603775024,\n            0.8990052938461304,\n            0.02772649936378002,\n            0.042197681963443756,\n            -0.0022736566606909037,\n            -0.028843341395258904,\n            -0.4559306204319,\n            0.6326258778572083,\n            0.4568879008293152,\n            -0.4892531633377075,\n            -0.032289132475852966,\n            0.04378330707550049,\n            -0.4118069112300873,\n            0.2493579089641571,\n            -0.021955665200948715,\n            -0.01538186427205801,\n            -0.21400974690914154,\n            -0.09971866756677628,\n            0.02185226045548916,\n            -0.18125569820404053,\n            -0.13828244805335999,\n            -0.20846466720104218,\n            -0.10373540222644806,\n            0.4842098653316498,\n            -0.06586655229330063,\n            0.03369470313191414,\n            0.013142148964107037,\n            0.017437899485230446,\n            0.15891534090042114,\n            0.5269678831100464,\n            0.02546108327805996,\n            -0.004250233061611652,\n            -5.8676625485531986e-05,\n            0.06777831166982651,\n            -0.14051207900047302,\n            0.6876491904258728,\n            -0.3455996811389923,\n            0.0378129817545414,\n            0.15291574597358704,\n            -0.03829087316989899,\n            -0.05761529877781868,\n            -0.05344394966959953,\n            0.1421334147453308,\n            -0.3614322543144226,\n            -0.21606910228729248,\n            0.1558765172958374,\n            0.14480257034301758,\n            -0.1799984872341156,\n            0.4238421618938446,\n            -0.08961529284715652,\n            -0.04010967165231705,\n            0.14250615239143372,\n            -0.0038367861416190863,\n            -0.044531334191560745,\n            -0.08958051353693008,\n            -0.1577986180782318,\n            -0.5795103907585144,\n            -1.1048516035079956,\n            0.16444185376167297,\n            -0.09989812225103378,\n            -0.26304998993873596,\n            0.040687527507543564,\n            0.065303735435009,\n            -0.06267901510000229,\n            0.08742637187242508,\n            0.02480895072221756,\n            0.23719966411590576,\n            -0.09509539604187012,\n            0.39278310537338257,\n            0.18978112936019897,\n            0.11301649361848831,\n            -0.16268616914749146,\n            -0.14119602739810944,\n            -0.04518252611160278,\n            0.10456270724534988,\n            0.008367948234081268,\n            0.004280170891433954,\n            0.01894286274909973,\n            -0.1547478288412094,\n            0.197267547249794,\n            0.20271208882331848,\n            -0.28377917408943176,\n            -0.26751258969306946,\n            0.15954937040805817,\n            0.33988064527511597,\n            0.16848208010196686,\n            0.11668887734413147,\n            -0.057433612644672394,\n            -0.049777109175920486,\n            0.00744214653968811,\n            -0.012330793775618076,\n            -0.08413149416446686,\n            -0.2053118497133255,\n            0.09235486388206482,\n            -0.1354941576719284,\n            0.41610953211784363,\n            0.8428494334220886,\n            0.880882740020752,\n            0.024029193446040154,\n            -0.08453702926635742,\n            0.00771496444940567,\n            -0.013013732619583607,\n            -0.23804998397827148,\n            0.4110376536846161,\n            0.23720477521419525,\n            -0.13951541483402252,\n            -0.1747516244649887,\n            -0.34215790033340454,\n            0.014357345178723335,\n            0.34224632382392883,\n            0.03783192113041878,\n            0.01125166192650795,\n            -0.08253959566354752,\n            0.015717405825853348,\n            -0.22759634256362915,\n            0.3980898857116699,\n            0.2427154779434204,\n            -0.3319437801837921,\n            0.11146843433380127,\n            -0.9666317105293274,\n            -0.12227121740579605,\n            -0.1948898285627365,\n            -0.030186548829078674,\n            0.0011711223050951958,\n            -0.040062546730041504,\n            -0.16316139698028564,\n            -0.14714862406253815,\n            0.13224393129348755,\n            -0.0019320327555760741,\n            -0.09674090147018433,\n            0.3630145490169525,\n            -0.019513679668307304,\n            -0.07729464769363403,\n            -0.34592965245246887,\n            0.15215164422988892,\n            0.046678490936756134,\n            0.06675180792808533,\n            -0.08943335711956024,\n            0.006386714521795511,\n            0.10086977481842041,\n            -0.07409387081861496,\n            -0.19604018330574036,\n            -0.042700666934251785,\n            0.12124726921319962,\n            0.5694677233695984,\n            0.25033196806907654,\n            0.01862989366054535,\n            0.0053687929175794125,\n            -0.0017405126709491014,\n            -0.01638556271791458,\n            -0.32222822308540344,\n            0.5348804593086243,\n            0.5546748042106628,\n            1.2770946025848389,\n            0.11648745834827423,\n            -0.058405984193086624,\n            -0.2997635006904602,\n            -0.2040756195783615,\n            0.15525077283382416,\n            -0.12436354905366898,\n            -0.089121975004673,\n            0.06441225856542587,\n            0.2444663643836975,\n            -0.3495825529098511,\n            -0.05243751034140587,\n            0.08752834796905518,\n            0.08800745010375977,\n            -0.09807545691728592,\n            -0.3823537230491638,\n            -0.13047000765800476,\n            0.029333092272281647,\n            0.11618250608444214,\n            -0.0638590008020401,\n            -0.09598273783922195,\n            -0.07390140742063522,\n            0.09151650220155716,\n            -0.1700282245874405,\n            0.23608872294425964,\n            0.24879834055900574,\n            -0.15922772884368896,\n            -0.33795130252838135,\n            -0.053850702941417694,\n            0.1014639139175415,\n            -0.05480973795056343,\n            -0.06753639131784439,\n            0.04606246575713158,\n            -0.07082260400056839,\n            0.07848796248435974,\n            0.05011916160583496,\n            -0.05570689216256142,\n            -0.14584510028362274,\n            -0.8908579349517822,\n            -0.5959509611129761,\n            -0.8982105255126953,\n            0.0788002535700798,\n            -0.03575791418552399,\n            0.052424680441617966,\n            -0.08019822835922241,\n            0.10848221182823181,\n            0.0957408994436264,\n            0.1457311511039734,\n            -0.1956494003534317,\n            -0.21669772267341614,\n            0.9854136109352112,\n            -0.23215851187705994,\n            0.16359730064868927,\n            0.02025810070335865,\n            -0.08975380659103394,\n            -0.013868067413568497,\n            -0.22188447415828705,\n            0.020666224882006645,\n            -0.22304703295230865,\n            0.06407633423805237,\n            0.19804184138774872,\n            -0.05285267159342766,\n            -0.5510660409927368,\n            -0.8522927761077881,\n            -0.6061599850654602,\n            0.08484024554491043,\n            -0.08973539620637894,\n            0.013228937052190304,\n            -0.07834818214178085,\n            0.02858446165919304,\n            -0.3826225996017456,\n            0.059726644307374954,\n            0.1139102503657341,\n            -0.19311848282814026,\n            0.05770142376422882,\n            0.22584261000156403,\n            0.34312352538108826,\n            -0.15085645020008087,\n            0.34372228384017944,\n            0.08070214092731476,\n            0.5744000673294067,\n            -0.08693907409906387,\n            -0.003695777617394924,\n            -0.1334235966205597,\n            0.06418291479349136,\n            0.02848576195538044,\n            -0.34958112239837646,\n            -0.3419312834739685,\n            -0.09599799662828445,\n            0.015022341161966324,\n            0.03255023807287216,\n            0.09713662415742874,\n            -0.1730588674545288,\n            0.1904430240392685,\n            -0.32815566658973694,\n            -0.16749203205108643,\n            0.35736411809921265,\n            -0.503787100315094,\n            0.5057004690170288,\n            -0.47198373079299927,\n            0.11386436969041824,\n            -0.0722493901848793,\n            0.03358639404177666,\n            0.005928087048232555,\n            -0.05637047439813614,\n            0.06552420556545258,\n            -0.07283362001180649,\n            -0.09314802289009094,\n            0.13586974143981934,\n            -0.5054865479469299,\n            -0.18127793073654175,\n            0.08853171765804291,\n            -0.13333705067634583,\n            -0.2623322308063507,\n            0.17757390439510345,\n            0.04408252611756325,\n            -0.0277855321764946,\n            -0.05175777152180672,\n            0.40444689989089966,\n            -0.03518976643681526,\n            -0.36402902007102966,\n            -0.019589770585298538,\n            -0.05277400091290474,\n            -0.27273234724998474,\n            -0.07373850792646408,\n            -0.058221735060214996,\n            0.14292845129966736,\n            -0.005004828795790672,\n            -0.05554938316345215,\n            0.20361287891864777,\n            -0.30462127923965454,\n            -0.1140812486410141,\n            0.16081976890563965,\n            -0.07133162021636963,\n            -0.20463652908802032,\n            0.34733739495277405,\n            0.17099761962890625,\n            0.025868643075227737,\n            -0.02960631065070629,\n            -0.02717636525630951,\n            0.02027258090674877,\n            -0.13165302574634552,\n            0.36201152205467224,\n            0.5002728700637817,\n            0.39691421389579773,\n            -0.04605599492788315,\n            0.28801581263542175,\n            -1.0140656232833862,\n            -0.5481916666030884,\n            0.0896061584353447,\n            -0.049390073865652084,\n            0.08813252300024033,\n            -0.1784677952528,\n            0.34480658173561096,\n            -0.36402803659439087,\n            0.16948284208774567,\n            0.45740315318107605,\n            -0.23747704923152924,\n            0.580975353717804,\n            -0.24338461458683014,\n            -0.11410018056631088,\n            0.06431885808706284,\n            -0.0317281149327755,\n            -0.024683356285095215,\n            -0.10083278268575668,\n            0.024547407403588295,\n            -0.16270779073238373,\n            -0.07757837325334549,\n            0.19732129573822021,\n            0.03790999948978424,\n            -0.18804220855236053,\n            0.8675169348716736,\n            0.5377629399299622,\n            -0.0036910742055624723,\n            -0.0016441351035609841,\n            -0.030448857694864273,\n            0.07757671177387238,\n            -0.1475408971309662,\n            0.613543689250946,\n            0.30266445875167847,\n            0.12106148898601532,\n            0.05485830456018448,\n            -0.04748840630054474,\n            -0.23233623802661896,\n            -0.1949906051158905,\n            0.05692804977297783,\n            0.07474583387374878,\n            -0.11879625171422958,\n            0.07200933247804642,\n            -0.012743310071527958,\n            -0.02546215057373047,\n            -0.3765566349029541,\n            0.28637346625328064,\n            -0.18051809072494507,\n            0.5034835934638977,\n            -0.34970414638519287,\n            -0.2386687994003296,\n            -0.03804561868309975,\n            -0.03649319335818291,\n            -0.10303670912981033,\n            0.1299818456172943,\n            0.24685724079608917,\n            -0.34168556332588196,\n            -0.086674265563488,\n            0.32085898518562317,\n            0.48488491773605347,\n            -0.522548258304596,\n            0.309568852186203,\n            0.167385995388031,\n            0.11308691650629044,\n            0.14733079075813293,\n            -0.22416195273399353,\n            0.14763982594013214,\n            -0.07242503017187119,\n            0.07601745426654816,\n            -0.10375087708234787,\n            -0.03409396857023239,\n            -0.35759225487709045,\n            0.18936687707901,\n            0.28248289227485657,\n            0.26482364535331726,\n            0.061123836785554886,\n            -0.021603189408779144,\n            -0.13469825685024261,\n            0.07248867303133011,\n            -0.03464066982269287,\n            0.06557167321443558,\n            0.16093865036964417,\n            -0.1718607246875763,\n        ],\n        b1: &[\n            -0.3893989324569702,\n            -0.2791002690792084,\n            0.07853052020072937,\n            -0.4629746377468109,\n            -0.7148261070251465,\n            0.8680436015129089,\n            -0.46459102630615234,\n            0.0404132716357708,\n            -0.44012945890426636,\n            0.08434166759252548,\n            0.32190972566604614,\n            -0.20194832980632782,\n            -0.3781348764896393,\n            -0.23968002200126648,\n            -0.581799328327179,\n            0.6500483155250549,\n            -0.6192854046821594,\n            0.5922245383262634,\n            0.44006091356277466,\n            0.2982949912548065,\n            0.6136102676391602,\n            -0.597486138343811,\n            -0.3697699308395386,\n            -0.45241132378578186,\n            0.60771644115448,\n            -0.3373708128929138,\n            0.5697194337844849,\n            0.4784911870956421,\n            -0.49601855874061584,\n            0.5023709535598755,\n            0.21592296659946442,\n            -0.45412343740463257,\n            0.5104787945747375,\n            0.558862566947937,\n            0.4729066491127014,\n            -0.5520593523979187,\n            -0.5120576620101929,\n            -0.7157037258148193,\n            0.12596718966960907,\n            0.4773174524307251,\n        ],\n        w2: &[\n            0.1379607617855072,\n            0.09308824688196182,\n            -0.2596932649612427,\n            0.4461972713470459,\n            0.3480601906776428,\n            0.036684323102235794,\n            0.4057384729385376,\n            -0.3081648051738739,\n            0.4561280608177185,\n            0.2749394178390503,\n            -0.1400817334651947,\n            0.3145979046821594,\n            -0.16919250786304474,\n            0.7247185707092285,\n            0.3479674756526947,\n            -0.7546817064285278,\n            0.38135531544685364,\n            -0.3939172029495239,\n            -0.038021210581064224,\n            0.026914050802588463,\n            -0.5281358361244202,\n            0.39009571075439453,\n            0.4090450406074524,\n            0.5053343772888184,\n            -0.23938016593456268,\n            0.488080233335495,\n            -0.38536468148231506,\n            -0.23763014376163483,\n            0.2661689519882202,\n            -0.14746293425559998,\n            -0.7541974186897278,\n            0.27726081013679504,\n            -0.4072169065475464,\n            -0.8030230402946472,\n            -0.386343389749527,\n            0.6674754619598389,\n            0.06677238643169403,\n            0.5055669546127319,\n            -0.44330647587776184,\n            -0.3423362970352173,\n            -0.10948927700519562,\n            0.11290912330150604,\n            -0.2759379744529724,\n            0.5522158741950989,\n            -0.5766478776931763,\n            0.7288797497749329,\n            -0.4967955946922302,\n            -0.5466133952140808,\n            0.7254890203475952,\n            0.1274457424879074,\n            0.3098924458026886,\n            0.2524661719799042,\n            -0.7162019610404968,\n            0.19503603875637054,\n            -0.5212412476539612,\n            0.0968603864312172,\n            0.4835629463195801,\n            -0.5865079164505005,\n            0.27647316455841064,\n            0.1975109577178955,\n            -0.845225989818573,\n            0.4172143042087555,\n            -0.014424118213355541,\n            -0.24702520668506622,\n            -0.16123531758785248,\n            -0.047759659588336945,\n            -0.09985388815402985,\n            0.10430619865655899,\n            0.53556889295578,\n            0.2595883011817932,\n            0.11729882657527924,\n            0.36996161937713623,\n            -0.41997936367988586,\n            -0.3332042694091797,\n            0.2527308464050293,\n            0.6039140820503235,\n            0.35183605551719666,\n            0.42042237520217896,\n            -0.2265913337469101,\n            -0.06852111965417862,\n            0.3749903440475464,\n            0.3698897361755371,\n            -0.43096107244491577,\n            0.1275794953107834,\n            0.27926334738731384,\n            -0.3282606303691864,\n            0.290679931640625,\n            -0.14467079937458038,\n            0.3357028663158417,\n            -0.0683436468243599,\n            -0.35492125153541565,\n            -0.14275093376636505,\n            -0.1504347324371338,\n            0.1782987266778946,\n            0.07464402168989182,\n            -0.2788643538951874,\n            0.5896115303039551,\n            -0.314520001411438,\n            -0.3235827684402466,\n            -0.2899278700351715,\n            -0.21264874935150146,\n            0.41862159967422485,\n            0.3237628936767578,\n            0.2948566973209381,\n            -0.6101413369178772,\n            -0.025511808693408966,\n            -0.4238346517086029,\n            -0.28283095359802246,\n            0.32077667117118835,\n            -0.34138476848602295,\n            -0.5257527232170105,\n            0.24129967391490936,\n            -0.38175472617149353,\n            -0.20559589564800262,\n            -0.11267697811126709,\n            0.32475054264068604,\n            0.29545050859451294,\n            0.0010625360300764441,\n            0.4097916781902313,\n            -0.3120468556880951,\n            0.3134985566139221,\n            0.33620578050613403,\n            -0.27408266067504883,\n            -0.0118736382573843,\n            0.21356475353240967,\n            -0.6716119647026062,\n            0.14166241884231567,\n            0.020748334005475044,\n            0.27158322930336,\n            -0.27066248655319214,\n            -0.5078546404838562,\n            0.39642488956451416,\n            0.4044502079486847,\n            0.1363500952720642,\n            0.38089585304260254,\n            -0.18438327312469482,\n            -0.08652642369270325,\n            0.05718545988202095,\n            -0.5758764743804932,\n            0.0948563665151596,\n            0.298057496547699,\n            -0.07299521565437317,\n            -0.24248233437538147,\n            0.29135069251060486,\n            -0.44556060433387756,\n            0.6689074039459229,\n            -0.12930674850940704,\n            -0.12669484317302704,\n            0.1074564978480339,\n            -0.20472179353237152,\n            0.14787982404232025,\n            -0.13180267810821533,\n            0.3045596182346344,\n            -0.3345180153846741,\n            -0.3405822217464447,\n            0.22327540814876556,\n            0.02809770777821541,\n            0.17404714226722717,\n            0.22873322665691376,\n            -0.3915692865848541,\n            -0.39005470275878906,\n            -0.4675980806350708,\n            0.44798821210861206,\n            -0.31790846586227417,\n            -0.21734853088855743,\n            0.2172199934720993,\n            -0.3485357165336609,\n            0.1241735890507698,\n            -0.6933310031890869,\n            -0.09649480134248734,\n            0.24731965363025665,\n            -0.20421941578388214,\n            0.13033808767795563,\n            -0.4282769560813904,\n            -0.22173112630844116,\n            0.08912057429552078,\n            -0.3927532434463501,\n            0.3523387908935547,\n            0.36073970794677734,\n            -0.036902282387018204,\n            0.5880261063575745,\n            -0.29945725202560425,\n            -0.40845751762390137,\n            -0.3265145421028137,\n            0.370391309261322,\n            -0.3553546965122223,\n            0.5133077502250671,\n            0.1800842434167862,\n            -0.34683868288993835,\n            0.28811708092689514,\n            0.3033837080001831,\n            -0.4140017628669739,\n            0.4362258017063141,\n            0.3689269423484802,\n            0.3121638596057892,\n            -0.3287503123283386,\n            -0.15226924419403076,\n            -0.17191028594970703,\n            -0.10683685541152954,\n            0.34219542145729065,\n            0.34955963492393494,\n            0.22892920672893524,\n            -0.20123478770256042,\n            -0.3934169411659241,\n            0.25449705123901367,\n            -0.541163444519043,\n            0.21640898287296295,\n            0.19343338906764984,\n            -0.14020974934101105,\n            0.010480044409632683,\n            -0.24229897558689117,\n            -0.4682120084762573,\n            0.02336042746901512,\n            0.039344485849142075,\n            0.42446646094322205,\n            -0.3173693120479584,\n            0.23609045147895813,\n            0.20335273444652557,\n            -0.19347436726093292,\n            -0.05698636546730995,\n            0.17990583181381226,\n            0.30915674567222595,\n            0.3115670382976532,\n            0.4147215485572815,\n            -0.38558056950569153,\n            -0.12379863113164902,\n            0.025996098294854164,\n            -0.3010733425617218,\n            0.03275908902287483,\n            -0.6039671897888184,\n            0.06267470866441727,\n            -0.012677585706114769,\n            0.3484704792499542,\n            0.24301587045192719,\n            -0.40881243348121643,\n            -0.16732162237167358,\n            0.190901979804039,\n            -0.5619192719459534,\n            0.30009278655052185,\n            -0.43359509110450745,\n            0.26643550395965576,\n            0.5083268880844116,\n            0.3491555452346802,\n            0.4731655716896057,\n            0.6301924586296082,\n            -0.8111121654510498,\n            0.6473397016525269,\n            -0.001451796037144959,\n            0.3649038076400757,\n            -0.6002859473228455,\n            -0.41925248503685,\n            0.05584913119673729,\n            0.7823511362075806,\n            0.421135276556015,\n            0.5779385566711426,\n            -0.49475061893463135,\n            0.5293950438499451,\n            -0.45432502031326294,\n            -0.680946946144104,\n            -0.3506624102592468,\n            -0.21028658747673035,\n            0.4775547385215759,\n            0.25049126148223877,\n            0.2707470655441284,\n            -0.3469635546207428,\n            0.5959001779556274,\n            -0.5623777508735657,\n            -0.6334168910980225,\n            0.4096938669681549,\n            -0.3921370208263397,\n            -0.27649807929992676,\n            0.4424516260623932,\n            -0.28308066725730896,\n            -0.22009265422821045,\n            -0.386872798204422,\n            0.5130718350410461,\n            0.5702601075172424,\n            0.7469420433044434,\n            -0.09606175124645233,\n            -0.4271978437900543,\n        ],\n        b2: &[\n            -0.07522959262132645,\n            0.3644154667854309,\n            -0.25166040658950806,\n            -0.12973527610301971,\n            0.25026997923851013,\n            -0.2794199585914612,\n            -0.17614373564720154,\n        ],\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm32/cortex_a9.txt",
    "content": "armv7neon_mmm_f32_8x6_generic 17 128 19 0.00006235573582381347\narmv7neon_mmm_f32_8x4_cortexa7 23 32 3 0.000006021597781788675\narmv7neon_mmm_f32_8x6_cortexa7 17 128 7 0.000041163109831630036\narmv7neon_mmm_f32_8x6_generic 9 4 5 0.0000020753617625129768\ngeneric_f32_4x4 13 4 3 0.000003220368712907131\narmv7neon_mmm_f32_8x4_cortexa7 9 128 3 0.000011219671010907719\narmv7neon_mmm_f32_8x6_generic 24 4 12 0.00000416032880372066\narmv7neon_mmm_f32_8x6_cortexa9 15 128 12 0.00002134037524275856\narmv7neon_mmm_f32_8x4_cortexa7 16 32 7 0.000007487730700761545\narmv7neon_mmm_f32_8x4_generic 25 128 4 0.000015723210051937644\narmv7neon_mmm_f32_8x6_cortexa9 15 128 13 0.000031729639665247244\narmv7neon_mmm_f32_8x4_cortexa7 17 128 5 0.00003233807616782481\ngeneric_f32_4x4 5 4 9 0.000004114340189363069\narmv7neon_mmm_f32_8x4_cortexa9 7 4 12 0.0000026805076801341797\narmv7neon_mmm_f32_8x6_cortexa7 8 4 7 0.0000020191503624854738\ngeneric_f32_4x4 5 32 4 0.0000030555103653558445\narmv7neon_mmm_f32_8x4_cortexa7 8 128 12 0.000016128094616247412\ngeneric_f32_4x4 13 128 8 0.000029057855790622486\narmv7neon_mmm_f32_8x6_cortexa7 23 128 5 0.00002125083853467516\ngeneric_f32_4x4 5 128 4 0.000007724128084704853\ngeneric_f32_4x4 9 128 5 0.000022305019807747277\narmv7neon_mmm_f32_8x6_generic 23 32 11 0.000011268391904458938\narmv7neon_mmm_f32_8x4_generic 7 4 12 0.0000026796237449338948\narmv7neon_mmm_f32_8x4_cortexa9 8 32 11 0.000004490236277399054\narmv7neon_mmm_f32_8x6_cortexa7 23 4 7 0.000005431543985839428\narmv7neon_mmm_f32_8x4_cortexa9 7 32 11 0.000004893302315959357\narmv7neon_mmm_f32_8x6_generic 17 4 6 0.0000024671248311817606\narmv7neon_mmm_f32_8x6_cortexa7 7 4 7 0.0000023786534435590155\narmv7neon_mmm_f32_8x6_cortexa9 23 32 7 0.000011142179078429717\narmv7neon_mmm_f32_8x6_cortexa7 23 128 18 0.00006187753967619513\narmv7neon_mmm_f32_8x4_cortexa9 17 4 13 0.000007583246734362711\narmv7neon_mmm_f32_8x4_cortexa7 25 32 8 0.000013742616132230942\narmv7neon_mmm_f32_8x4_cortexa9 15 128 13 0.00003174823725370825\narmv7neon_mmm_f32_8x6_cortexa7 8 32 17 0.0000068525292311713665\narmv7neon_mmm_f32_8x4_generic 23 4 12 0.000005946433902061308\narmv7neon_mmm_f32_8x4_generic 7 32 12 0.000004913908866329809\narmv7neon_mmm_f32_8x4_generic 9 4 5 0.0000029635862825605913\narmv7neon_mmm_f32_8x6_generic 24 128 7 0.00003097026633557495\narmv7neon_mmm_f32_8x4_generic 24 4 13 0.000007343587112941961\narmv7neon_mmm_f32_8x4_generic 23 4 8 0.000004129384710709428\ngeneric_f32_4x4 11 128 8 0.00002206478635932785\narmv7neon_mmm_f32_8x4_generic 8 4 9 0.000002203244153911471\narmv7neon_mmm_f32_8x4_cortexa9 17 32 3 0.000004810722960669039\narmv7neon_mmm_f32_8x4_cortexa7 7 128 12 0.000016707563395180685\narmv7neon_mmm_f32_8x6_cortexa7 7 4 12 0.000002638802872509435\narmv7neon_mmm_f32_8x4_cortexa7 17 128 9 0.0000480527815279415\narmv7neon_mmm_f32_8x4_cortexa7 7 128 7 0.000011282871511736955\narmv7neon_mmm_f32_8x4_cortexa9 16 4 5 0.000002910382588603331\narmv7neon_mmm_f32_8x4_generic 17 128 13 0.00004671426916376633\narmv7neon_mmm_f32_8x4_cortexa7 9 128 8 0.00002150638840328972\narmv7neon_mmm_f32_8x4_cortexa9 25 32 12 0.000015994893141833952\narmv7neon_mmm_f32_8x4_cortexa7 23 32 11 0.000016244020387064702\narmv7neon_mmm_f32_8x6_cortexa9 7 4 11 0.000002387141196478666\narmv7neon_mmm_f32_8x6_cortexa7 7 4 5 0.0000013875129728218855\narmv7neon_mmm_f32_8x4_cortexa7 15 32 4 0.0000039730943727200555\narmv7neon_mmm_f32_8x4_cortexa7 24 32 3 0.000006034875853921332\narmv7neon_mmm_f32_8x4_cortexa9 8 4 5 0.0000016776748569042105\narmv7neon_mmm_f32_8x6_cortexa7 16 4 18 0.0000045013629937264245\narmv7neon_mmm_f32_8x4_cortexa9 8 128 8 0.000008024678845849025\narmv7neon_mmm_f32_8x6_cortexa9 25 4 18 0.000008301730396887936\narmv7neon_mmm_f32_8x4_generic 7 32 11 0.000004892898505691065\narmv7neon_mmm_f32_8x4_cortexa9 16 128 4 0.000008030995808345325\narmv7neon_mmm_f32_8x4_cortexa7 16 32 3 0.000004194758783711011\narmv7neon_mmm_f32_8x4_generic 25 32 3 0.000006291923472603681\ngeneric_f32_4x4 3 32 13 0.000005952451348897524\narmv7neon_mmm_f32_8x6_cortexa9 23 32 19 0.000021623159632105592\narmv7neon_mmm_f32_8x4_generic 9 4 7 0.0000030054177268153855\narmv7neon_mmm_f32_8x4_cortexa9 7 128 3 0.0000044229477614846184\narmv7neon_mmm_f32_8x4_generic 25 32 12 0.0000159741241286288\narmv7neon_mmm_f32_8x6_generic 8 32 5 0.000002351046891403544\narmv7neon_mmm_f32_8x6_generic 8 128 17 0.00001572220287732098\narmv7neon_mmm_f32_8x6_cortexa9 25 4 13 0.00000878947090986632\narmv7neon_mmm_f32_8x6_cortexa7 9 128 18 0.00004088792544048485\narmv7neon_mmm_f32_8x4_generic 9 128 8 0.000015874731663155136\narmv7neon_mmm_f32_8x4_generic 16 128 5 0.000015914646023810615\narmv7neon_mmm_f32_8x6_cortexa7 17 32 18 0.000019117240551030633\narmv7neon_mmm_f32_8x4_cortexa9 7 4 11 0.0000026740706182831685\narmv7neon_mmm_f32_8x4_cortexa7 9 128 13 0.00004271080548146737\narmv7neon_mmm_f32_8x6_generic 16 128 11 0.000020976534312868846\narmv7neon_mmm_f32_8x4_cortexa7 16 128 7 0.00002177826510527881\narmv7neon_mmm_f32_8x4_generic 15 32 7 0.000006163372672989967\narmv7neon_mmm_f32_8x4_generic 9 128 11 0.000023697623425846144\narmv7neon_mmm_f32_8x6_cortexa7 16 4 6 0.0000018480490329829836\narmv7neon_mmm_f32_8x4_cortexa9 25 4 7 0.00000554548266542983\narmv7neon_mmm_f32_8x6_generic 15 128 13 0.00003163198682165344\narmv7neon_mmm_f32_8x6_cortexa7 25 32 18 0.000025184955719578352\narmv7neon_mmm_f32_8x6_generic 8 4 6 0.000001113907974305815\narmv7neon_mmm_f32_8x6_cortexa7 16 32 5 0.0000050492542920704264\narmv7neon_mmm_f32_8x6_generic 8 32 19 0.000007097287986832066\narmv7neon_mmm_f32_8x6_cortexa9 25 32 5 0.000007806390304931795\narmv7neon_mmm_f32_8x6_cortexa9 15 128 11 0.000021399956173572563\narmv7neon_mmm_f32_8x6_generic 16 32 6 0.000003721529767067075\narmv7neon_mmm_f32_8x4_cortexa7 17 32 5 0.000010843745704621824\narmv7neon_mmm_f32_8x4_generic 7 128 7 0.00000843066632668338\narmv7neon_mmm_f32_8x4_generic 23 32 13 0.000016850629087203773\narmv7neon_mmm_f32_8x4_cortexa9 16 32 9 0.000008424770761452617\narmv7neon_mmm_f32_8x4_cortexa9 15 32 13 0.000011652714145510248\narmv7neon_mmm_f32_8x4_generic 23 128 5 0.000023910719482642124\narmv7neon_mmm_f32_8x4_cortexa9 23 128 3 0.000012498528953898262\narmv7neon_mmm_f32_8x4_generic 16 32 8 0.000005593957529271591\narmv7neon_mmm_f32_8x6_cortexa7 7 128 13 0.000021419187824431687\narmv7neon_mmm_f32_8x6_cortexa9 7 128 6 0.000005900049478814555\narmv7neon_mmm_f32_8x4_cortexa7 24 32 11 0.000015824034451655578\narmv7neon_mmm_f32_8x6_cortexa7 16 4 17 0.000004987657461648502\narmv7neon_mmm_f32_8x4_cortexa7 23 32 8 0.000010735439695395534\narmv7neon_mmm_f32_8x4_cortexa9 25 4 5 0.000005392929349101573\narmv7neon_mmm_f32_8x4_generic 25 4 5 0.000005418182338760064\narmv7neon_mmm_f32_8x4_generic 7 128 9 0.000012382199177609924\narmv7neon_mmm_f32_8x4_cortexa9 9 32 9 0.000008579188782470308\narmv7neon_mmm_f32_8x4_generic 8 128 12 0.000011801915167566902\narmv7neon_mmm_f32_8x4_cortexa9 23 4 8 0.0000041152642927900605\narmv7neon_mmm_f32_8x4_generic 7 4 11 0.0000026764041239252324\ngeneric_f32_4x4 13 128 12 0.00004319526080896362\ngeneric_f32_4x4 5 32 5 0.000005725142687375275\ngeneric_f32_4x4 5 128 12 0.00002200938035136394\narmv7neon_mmm_f32_8x4_generic 23 4 5 0.000004369620528795486\narmv7neon_mmm_f32_8x6_generic 9 4 11 0.000003424705713246475\narmv7neon_mmm_f32_8x6_generic 17 32 12 0.000010395428642618952\ngeneric_f32_4x4 13 32 11 0.000015867743214986118\narmv7neon_mmm_f32_8x6_cortexa9 9 32 17 0.000010850143335646025\narmv7neon_mmm_f32_8x6_cortexa9 23 4 6 0.0000028251957000612876\narmv7neon_mmm_f32_8x6_cortexa9 9 128 7 0.000020914788448206477\narmv7neon_mmm_f32_8x4_cortexa7 15 128 11 0.000032581738066809436\narmv7neon_mmm_f32_8x4_cortexa9 25 128 3 0.000016370587474295946\narmv7neon_mmm_f32_8x4_cortexa7 23 4 4 0.0000024826703257808083\narmv7neon_mmm_f32_8x4_generic 9 128 4 0.000008108848864529598\narmv7neon_mmm_f32_8x6_cortexa9 25 128 5 0.00002136490649839919\narmv7neon_mmm_f32_8x6_generic 23 128 19 0.00006253554336626349\ngeneric_f32_4x4 12 4 12 0.000005236763691820686\narmv7neon_mmm_f32_8x6_cortexa9 8 4 7 0.0000019309843288986318\narmv7neon_mmm_f32_8x4_cortexa7 15 4 5 0.000003357403897484634\narmv7neon_mmm_f32_8x6_cortexa9 24 4 6 0.0000024072433754401144\narmv7neon_mmm_f32_8x6_cortexa7 25 128 7 0.000054619400677372974\narmv7neon_mmm_f32_8x6_generic 7 4 19 0.000004265433096040261\ngeneric_f32_4x4 7 4 12 0.0000041235655802886916\narmv7neon_mmm_f32_8x6_generic 17 128 17 0.000046385914441781564\narmv7neon_mmm_f32_8x4_generic 8 32 7 0.0000032122117166780275\narmv7neon_mmm_f32_8x4_cortexa9 16 4 7 0.0000030113926906216977\ngeneric_f32_4x4 13 128 13 0.00005791151370731608\narmv7neon_mmm_f32_8x4_cortexa7 9 32 9 0.000010771893810221605\narmv7neon_mmm_f32_8x4_cortexa7 23 4 7 0.000004820473947807521\narmv7neon_mmm_f32_8x4_cortexa9 24 4 13 0.00000731015216867438\ngeneric_f32_4x4 3 4 5 0.0000018607872137198219\narmv7neon_mmm_f32_8x4_generic 15 32 9 0.000008854455084657494\narmv7neon_mmm_f32_8x6_cortexa9 15 32 6 0.000004174355801441308\narmv7neon_mmm_f32_8x6_cortexa9 16 4 5 0.000002250377451226225\narmv7neon_mmm_f32_8x6_cortexa9 16 32 19 0.000013757586520904355\narmv7neon_mmm_f32_8x6_cortexa9 24 128 11 0.000031276431090202235\narmv7neon_mmm_f32_8x4_generic 25 128 7 0.00003164406261833124\narmv7neon_mmm_f32_8x4_generic 24 4 4 0.0000020584132727266\ngeneric_f32_4x4 7 128 11 0.000022350186275600858\narmv7neon_mmm_f32_8x4_cortexa9 23 4 4 0.0000022753692017814563\narmv7neon_mmm_f32_8x6_cortexa9 17 32 19 0.00002069369907930334\ngeneric_f32_4x4 4 128 13 0.00001478326569151377\narmv7neon_mmm_f32_8x6_cortexa9 23 32 18 0.00001626278262596922\narmv7neon_mmm_f32_8x6_generic 7 128 18 0.000016667585395966858\narmv7neon_mmm_f32_8x4_cortexa9 8 128 3 0.000004452778028984887\narmv7neon_mmm_f32_8x4_cortexa7 16 128 13 0.000042499574729319346\ngeneric_f32_4x4 3 32 11 0.000004620575096918176\ngeneric_f32_4x4 4 4 8 0.0000015603089093623964\narmv7neon_mmm_f32_8x6_cortexa9 15 32 7 0.00000772763531212548\narmv7neon_mmm_f32_8x4_cortexa7 9 4 11 0.000004505536773333099\narmv7neon_mmm_f32_8x6_cortexa9 15 32 18 0.000011416109977905379\narmv7neon_mmm_f32_8x4_cortexa7 24 128 12 0.00004730343110835064\narmv7neon_mmm_f32_8x4_cortexa9 8 4 7 0.000001726865000473268\narmv7neon_mmm_f32_8x4_cortexa9 17 4 12 0.00000554999550214869\narmv7neon_mmm_f32_8x4_cortexa9 24 32 3 0.000004919761516416024\narmv7neon_mmm_f32_8x4_cortexa7 7 4 7 0.0000020327105105148146\ngeneric_f32_4x4 8 32 11 0.000008129741741591272\narmv7neon_mmm_f32_8x6_cortexa9 17 128 6 0.000015685032106978185\narmv7neon_mmm_f32_8x6_cortexa7 24 4 13 0.0000069628322548194484\ngeneric_f32_4x4 11 32 7 0.000008418658676221946\narmv7neon_mmm_f32_8x4_cortexa9 23 32 13 0.000016859244394935546\narmv7neon_mmm_f32_8x6_cortexa7 15 4 19 0.000007361880271251662\narmv7neon_mmm_f32_8x6_cortexa7 25 128 19 0.00010862057908423823\ngeneric_f32_4x4 7 128 5 0.00001508121452285347\narmv7neon_mmm_f32_8x4_generic 8 4 12 0.0000020475145659989298\narmv7neon_mmm_f32_8x6_generic 23 32 6 0.000005742234510888845\narmv7neon_mmm_f32_8x6_cortexa9 8 32 7 0.000003926599359988243\narmv7neon_mmm_f32_8x4_cortexa7 16 32 5 0.00000738840656931522\ngeneric_f32_4x4 3 128 5 0.000007875053042737703\narmv7neon_mmm_f32_8x6_cortexa9 9 128 5 0.000010860557674890965\narmv7neon_mmm_f32_8x6_generic 15 32 11 0.000007797932920988772\ngeneric_f32_4x4 8 4 3 0.0000019148150797610994\narmv7neon_mmm_f32_8x4_generic 23 32 9 0.000012836974653040903\narmv7neon_mmm_f32_8x4_cortexa7 25 4 3 0.0000035757992765103294\narmv7neon_mmm_f32_8x4_generic 7 32 8 0.0000034181328210189654\narmv7neon_mmm_f32_8x6_cortexa9 25 32 7 0.000014158102081231813\narmv7neon_mmm_f32_8x6_generic 9 4 18 0.000004486229240869606\narmv7neon_mmm_f32_8x4_cortexa7 8 4 4 0.0000010559381010362017\narmv7neon_mmm_f32_8x4_cortexa9 25 32 3 0.000006292048691193805\narmv7neon_mmm_f32_8x4_cortexa9 24 4 11 0.000005847758703659802\narmv7neon_mmm_f32_8x4_cortexa9 7 128 12 0.00001242120677392976\narmv7neon_mmm_f32_8x4_cortexa7 16 128 9 0.00003210848238315819\narmv7neon_mmm_f32_8x4_cortexa9 24 128 7 0.00002382518082369683\narmv7neon_mmm_f32_8x4_generic 24 32 3 0.0000049198649021802165\narmv7neon_mmm_f32_8x4_cortexa9 25 4 11 0.000007781278370753281\narmv7neon_mmm_f32_8x6_cortexa7 17 32 12 0.000012940679584474003\narmv7neon_mmm_f32_8x6_cortexa7 16 32 17 0.000013190821660739104\narmv7neon_mmm_f32_8x6_cortexa7 25 4 19 0.000012097338566670792\narmv7neon_mmm_f32_8x6_generic 16 32 18 0.000010138504593611297\narmv7neon_mmm_f32_8x4_generic 9 128 5 0.000015947315101595927\narmv7neon_mmm_f32_8x6_cortexa9 23 4 5 0.0000031081830446744932\narmv7neon_mmm_f32_8x6_cortexa9 24 128 7 0.00003103549849311001\narmv7neon_mmm_f32_8x4_cortexa7 25 4 13 0.000010646545231619916\ngeneric_f32_4x4 9 128 12 0.00003260110763311798\narmv7neon_mmm_f32_8x6_cortexa9 23 4 17 0.000007631985122319121\narmv7neon_mmm_f32_8x4_cortexa9 8 128 13 0.00001574791956845722\narmv7neon_mmm_f32_8x6_cortexa9 23 4 18 0.000007303182081264746\narmv7neon_mmm_f32_8x4_cortexa7 24 128 13 0.00006349168486923089\narmv7neon_mmm_f32_8x4_generic 23 32 11 0.000012994166844502801\ngeneric_f32_4x4 7 32 4 0.0000031041482961797043\narmv7neon_mmm_f32_8x4_cortexa9 9 32 7 0.000005959447070414145\narmv7neon_mmm_f32_8x4_cortexa7 23 128 11 0.00004845207607490223\narmv7neon_mmm_f32_8x6_cortexa7 17 4 11 0.0000052419075338197185\ngeneric_f32_4x4 8 4 13 0.00000500809427570026\narmv7neon_mmm_f32_8x4_generic 24 32 7 0.000008731713037367055\narmv7neon_mmm_f32_8x6_cortexa9 25 128 13 0.00006164900543131858\narmv7neon_mmm_f32_8x6_cortexa9 17 4 7 0.000004817790997463295\narmv7neon_mmm_f32_8x6_cortexa9 8 32 19 0.000007141501791335855\narmv7neon_mmm_f32_8x4_cortexa9 16 4 4 0.000001530544742707957\narmv7neon_mmm_f32_8x6_generic 25 4 17 0.000008895187762533163\narmv7neon_mmm_f32_8x4_cortexa7 23 4 3 0.0000028668169166829235\narmv7neon_mmm_f32_8x6_cortexa7 24 128 11 0.00004128096933839809\ngeneric_f32_4x4 7 4 13 0.000005447538386200191\narmv7neon_mmm_f32_8x6_cortexa9 9 128 17 0.0000311696244066944\narmv7neon_mmm_f32_8x6_cortexa7 25 4 17 0.000009561336620584211\narmv7neon_mmm_f32_8x4_generic 16 128 8 0.000015609467528565242\narmv7neon_mmm_f32_8x6_cortexa9 7 4 6 0.0000015274572782560943\narmv7neon_mmm_f32_8x6_cortexa9 7 4 19 0.000004327266570693739\ngeneric_f32_4x4 4 128 3 0.0000042186208925339794\narmv7neon_mmm_f32_8x6_cortexa9 16 32 6 0.000003783089782648409\narmv7neon_mmm_f32_8x4_generic 24 4 12 0.000005240725524660556\narmv7neon_mmm_f32_8x6_generic 24 4 5 0.000003075012528242585\narmv7neon_mmm_f32_8x4_cortexa7 16 128 3 0.000011341419957358612\ngeneric_f32_4x4 13 32 7 0.000010885101829856154\ngeneric_f32_4x4 8 128 13 0.00002903953507476824\narmv7neon_mmm_f32_8x6_cortexa9 7 32 12 0.000004528441801230068\narmv7neon_mmm_f32_8x4_cortexa9 8 128 9 0.000011968397967338213\ngeneric_f32_4x4 9 4 11 0.000005879906070039223\narmv7neon_mmm_f32_8x4_generic 9 128 9 0.000023684049431562338\narmv7neon_mmm_f32_8x6_cortexa7 23 128 19 0.00008252903643379173\narmv7neon_mmm_f32_8x6_generic 23 128 7 0.000031381294348434004\narmv7neon_mmm_f32_8x4_generic 15 4 13 0.000005669760296114337\narmv7neon_mmm_f32_8x4_generic 15 4 11 0.000004507200935249084\narmv7neon_mmm_f32_8x4_generic 15 4 3 0.0000019251390208258458\ngeneric_f32_4x4 9 32 7 0.00000832450452449881\ngeneric_f32_4x4 9 4 9 0.000005828318767488796\narmv7neon_mmm_f32_8x4_cortexa7 16 4 3 0.000002093301960254744\narmv7neon_mmm_f32_8x4_generic 15 128 12 0.000023812063445324994\narmv7neon_mmm_f32_8x6_cortexa7 7 4 19 0.000004468409864573403\ngeneric_f32_4x4 8 4 7 0.000002955604897568221\narmv7neon_mmm_f32_8x6_cortexa9 9 32 12 0.000007238338427088594\narmv7neon_mmm_f32_8x6_generic 16 32 12 0.00000694068903036478\narmv7neon_mmm_f32_8x6_cortexa7 25 128 6 0.000027308370544312844\narmv7neon_mmm_f32_8x6_cortexa9 24 4 17 0.000006795821587814609\narmv7neon_mmm_f32_8x6_cortexa7 15 128 12 0.00002803261400606977\ngeneric_f32_4x4 9 32 8 0.000008044423595514706\narmv7neon_mmm_f32_8x4_generic 23 128 9 0.00003548379543389711\narmv7neon_mmm_f32_8x4_cortexa7 17 32 3 0.000005926615429744039\narmv7neon_mmm_f32_8x4_generic 8 128 7 0.000008235490634332163\narmv7neon_mmm_f32_8x4_cortexa7 9 4 12 0.000004255018272123806\narmv7neon_mmm_f32_8x4_cortexa9 16 32 11 0.000008524184894246237\narmv7neon_mmm_f32_8x4_cortexa9 24 32 4 0.000004264845803875226\ngeneric_f32_4x4 3 4 11 0.000002555428269031667\narmv7neon_mmm_f32_8x4_cortexa7 24 4 4 0.0000022261643758837446\narmv7neon_mmm_f32_8x4_cortexa7 15 128 3 0.000011306220407671872\narmv7neon_mmm_f32_8x6_generic 8 4 18 0.000002337607386621077\narmv7neon_mmm_f32_8x4_cortexa9 25 4 4 0.0000026788269418985845\ngeneric_f32_4x4 11 4 7 0.000004308040885452502\narmv7neon_mmm_f32_8x4_cortexa9 15 128 4 0.000008223517905022214\ngeneric_f32_4x4 5 32 11 0.000008288284199113315\narmv7neon_mmm_f32_8x6_cortexa9 9 4 12 0.0000032467302759009237\narmv7neon_mmm_f32_8x4_generic 24 128 7 0.00002382735987416326\narmv7neon_mmm_f32_8x6_generic 24 32 13 0.000015487434262812744\narmv7neon_mmm_f32_8x6_generic 17 128 13 0.00004629753684559049\narmv7neon_mmm_f32_8x6_cortexa7 24 128 13 0.00006117633525152988\narmv7neon_mmm_f32_8x6_generic 24 4 19 0.000008326315174295047\narmv7neon_mmm_f32_8x6_cortexa7 17 32 7 0.000013248073815175261\narmv7neon_mmm_f32_8x4_cortexa9 8 4 9 0.0000022033600613983073\ngeneric_f32_4x4 13 4 12 0.000007132280989099253\narmv7neon_mmm_f32_8x4_cortexa7 16 128 4 0.000010902014380146801\narmv7neon_mmm_f32_8x6_cortexa7 9 4 6 0.0000019491119088955803\ngeneric_f32_4x4 3 4 7 0.0000019042920757312053\narmv7neon_mmm_f32_8x4_generic 7 32 5 0.0000033764800411714497\narmv7neon_mmm_f32_8x6_cortexa7 23 4 19 0.000010215694576435441\narmv7neon_mmm_f32_8x4_generic 17 4 3 0.0000025969094160852644\ngeneric_f32_4x4 5 4 11 0.0000041349188443565096\narmv7neon_mmm_f32_8x6_cortexa7 9 32 17 0.000013295032359470915\narmv7neon_mmm_f32_8x4_cortexa7 15 128 13 0.00004313466302520057\narmv7neon_mmm_f32_8x4_cortexa9 15 4 8 0.0000030055981545968782\narmv7neon_mmm_f32_8x6_generic 16 4 12 0.0000029570565051151263\narmv7neon_mmm_f32_8x6_cortexa9 8 4 11 0.000002014524477563071\narmv7neon_mmm_f32_8x4_cortexa9 8 4 3 0.0000012065261711370315\narmv7neon_mmm_f32_8x4_cortexa7 7 32 7 0.000004136959156912223\narmv7neon_mmm_f32_8x4_generic 17 4 9 0.000005894535633035915\ngeneric_f32_4x4 9 4 13 0.00000752174902729491\narmv7neon_mmm_f32_8x6_cortexa9 7 32 5 0.000002364950726746234\ngeneric_f32_4x4 13 32 4 0.000005521387429490463\narmv7neon_mmm_f32_8x4_generic 8 128 3 0.000004474358471860054\narmv7neon_mmm_f32_8x4_cortexa7 23 4 12 0.0000063640165444956975\narmv7neon_mmm_f32_8x4_generic 7 32 4 0.0000019290673256470733\narmv7neon_mmm_f32_8x4_generic 17 32 12 0.000012180617018748219\narmv7neon_mmm_f32_8x6_generic 17 4 11 0.000004899442085076861\narmv7neon_mmm_f32_8x4_cortexa7 24 128 7 0.000032404931744711066\narmv7neon_mmm_f32_8x4_cortexa9 16 32 12 0.000008099332243893017\narmv7neon_mmm_f32_8x6_generic 9 128 18 0.00003081307981321012\narmv7neon_mmm_f32_8x6_generic 15 128 11 0.00002132277281707897\narmv7neon_mmm_f32_8x6_cortexa7 7 128 6 0.000007540283032400861\narmv7neon_mmm_f32_8x6_generic 25 32 12 0.000013597212519317418\narmv7neon_mmm_f32_8x6_generic 23 4 17 0.000007516814516170969\narmv7neon_mmm_f32_8x6_cortexa7 24 32 11 0.000013420883457543118\ngeneric_f32_4x4 9 32 11 0.000012087233281631852\narmv7neon_mmm_f32_8x6_cortexa7 24 4 17 0.000007204721001644005\narmv7neon_mmm_f32_8x6_cortexa7 25 32 7 0.000017455571757923716\narmv7neon_mmm_f32_8x4_cortexa9 23 32 11 0.000012992255106920675\narmv7neon_mmm_f32_8x4_cortexa9 9 4 9 0.000004090278120441601\narmv7neon_mmm_f32_8x4_cortexa7 23 32 13 0.000021193261455653347\narmv7neon_mmm_f32_8x6_cortexa7 24 32 19 0.00002532279550192562\narmv7neon_mmm_f32_8x4_generic 25 128 11 0.00004692401023814237\narmv7neon_mmm_f32_8x4_cortexa9 17 32 7 0.000008780640229470475\narmv7neon_mmm_f32_8x4_cortexa7 9 128 9 0.00003233195046796576\narmv7neon_mmm_f32_8x4_cortexa7 23 128 5 0.000032464808470770534\narmv7neon_mmm_f32_8x4_cortexa9 9 4 5 0.00000293936322263601\narmv7neon_mmm_f32_8x6_cortexa7 25 32 6 0.000008732005752739124\narmv7neon_mmm_f32_8x6_cortexa9 8 4 5 0.0000013801093771363888\narmv7neon_mmm_f32_8x6_cortexa7 16 128 6 0.000013837384210811002\narmv7neon_mmm_f32_8x4_generic 23 32 8 0.000008530859801735756\narmv7neon_mmm_f32_8x4_cortexa7 17 4 8 0.000004162931637169246\narmv7neon_mmm_f32_8x6_generic 7 32 13 0.000006254936643865642\narmv7neon_mmm_f32_8x6_cortexa7 9 32 7 0.00000901443584736606\narmv7neon_mmm_f32_8x4_cortexa7 15 4 7 0.0000034347738366310684\ngeneric_f32_4x4 9 32 13 0.000015744382160735945\narmv7neon_mmm_f32_8x6_generic 9 32 18 0.000010478111403829218\narmv7neon_mmm_f32_8x6_cortexa9 23 128 11 0.00003166377905937062\narmv7neon_mmm_f32_8x4_generic 15 128 7 0.000016210803535229283\narmv7neon_mmm_f32_8x4_cortexa7 23 4 9 0.000006649300631510142\narmv7neon_mmm_f32_8x4_generic 17 32 3 0.000004810545788546653\narmv7neon_mmm_f32_8x6_generic 24 32 12 0.000010144077161084113\narmv7neon_mmm_f32_8x4_cortexa9 15 32 7 0.000006152877670399033\narmv7neon_mmm_f32_8x4_cortexa9 23 4 12 0.000005900735151833346\narmv7neon_mmm_f32_8x4_cortexa7 16 32 4 0.0000037548262311678015\narmv7neon_mmm_f32_8x6_cortexa7 23 4 18 0.000007713970848848978\narmv7neon_mmm_f32_8x6_cortexa7 16 32 6 0.000004565838055826264\narmv7neon_mmm_f32_8x4_cortexa9 25 32 5 0.000011311149331127226\narmv7neon_mmm_f32_8x6_cortexa9 16 128 18 0.00003053013458613619\narmv7neon_mmm_f32_8x6_cortexa7 15 32 17 0.000013972664044561828\narmv7neon_mmm_f32_8x4_generic 16 32 4 0.000003010794297325924\narmv7neon_mmm_f32_8x6_generic 7 128 7 0.000011021195155003848\ngeneric_f32_4x4 4 32 13 0.000005510533372512351\narmv7neon_mmm_f32_8x4_cortexa9 16 32 7 0.000005969619454408248\narmv7neon_mmm_f32_8x4_cortexa9 24 32 12 0.000011900133300984794\narmv7neon_mmm_f32_8x4_cortexa7 8 128 3 0.000005902896580930324\narmv7neon_mmm_f32_8x4_generic 16 32 3 0.000003439802295093395\narmv7neon_mmm_f32_8x4_cortexa7 17 32 4 0.000005503311716430611\narmv7neon_mmm_f32_8x6_generic 9 32 12 0.000007171024246490819\narmv7neon_mmm_f32_8x6_cortexa7 23 32 5 0.000007315917802939159\narmv7neon_mmm_f32_8x6_cortexa7 23 128 12 0.0000413645417403771\narmv7neon_mmm_f32_8x4_generic 16 32 7 0.000005981518355563633\ngeneric_f32_4x4 7 4 7 0.000003097228954014147\narmv7neon_mmm_f32_8x6_cortexa7 24 4 18 0.000006499393673193748\narmv7neon_mmm_f32_8x6_cortexa9 24 4 7 0.000004723972074276078\narmv7neon_mmm_f32_8x4_cortexa7 16 32 11 0.00001075379201180745\ngeneric_f32_4x4 5 32 8 0.000005591832396338314\narmv7neon_mmm_f32_8x6_cortexa7 7 4 17 0.000003519015871119777\narmv7neon_mmm_f32_8x6_generic 25 4 13 0.000008668362849414556\narmv7neon_mmm_f32_8x4_cortexa7 15 128 4 0.000011117502498464982\narmv7neon_mmm_f32_8x6_cortexa7 15 4 17 0.000005776336577667951\narmv7neon_mmm_f32_8x4_cortexa9 8 32 13 0.000005695220879131919\narmv7neon_mmm_f32_8x4_cortexa7 24 4 12 0.000005659072390762546\narmv7neon_mmm_f32_8x4_generic 23 128 4 0.00001202844464043909\narmv7neon_mmm_f32_8x6_generic 17 128 7 0.00003102141310178231\narmv7neon_mmm_f32_8x4_generic 16 128 11 0.00002360551094138009\narmv7neon_mmm_f32_8x4_cortexa7 25 128 9 0.00006388979998675838\narmv7neon_mmm_f32_8x4_generic 9 32 9 0.00000858128920884699\narmv7neon_mmm_f32_8x6_cortexa7 9 4 12 0.0000034111339123867222\narmv7neon_mmm_f32_8x6_cortexa9 17 128 11 0.00003127371093460866\narmv7neon_mmm_f32_8x4_cortexa9 15 128 11 0.00002401650595136234\narmv7neon_mmm_f32_8x6_cortexa9 23 128 12 0.00003137052216487605\narmv7neon_mmm_f32_8x6_cortexa9 16 128 17 0.00003103196066568324\ngeneric_f32_4x4 7 32 3 0.000003268544296254094\narmv7neon_mmm_f32_8x4_generic 9 4 8 0.0000027781499181131053\narmv7neon_mmm_f32_8x4_cortexa7 16 32 9 0.000010640273784126178\narmv7neon_mmm_f32_8x4_cortexa9 25 4 9 0.00000761410675406794\ngeneric_f32_4x4 3 4 12 0.00000255471395132772\ngeneric_f32_4x4 12 32 13 0.00001543431169651167\narmv7neon_mmm_f32_8x6_cortexa9 15 4 7 0.0000037646981265010737\narmv7neon_mmm_f32_8x4_cortexa7 7 4 9 0.0000027630250787792764\narmv7neon_mmm_f32_8x6_cortexa9 15 4 13 0.000005407861539689391\narmv7neon_mmm_f32_8x4_cortexa7 24 4 5 0.000004483592435521624\narmv7neon_mmm_f32_8x6_cortexa9 16 128 5 0.000010995701297253243\narmv7neon_mmm_f32_8x4_cortexa9 7 4 13 0.000003344708907386396\ngeneric_f32_4x4 3 4 4 0.0000012029323677452384\narmv7neon_mmm_f32_8x6_cortexa7 8 4 5 0.0000014134340225429588\narmv7neon_mmm_f32_8x4_cortexa7 16 4 5 0.000003166142335812665\narmv7neon_mmm_f32_8x6_cortexa9 15 4 17 0.000005534126802188086\narmv7neon_mmm_f32_8x4_generic 17 128 4 0.000011910311848497855\narmv7neon_mmm_f32_8x4_cortexa9 17 128 9 0.00003520115855849476\narmv7neon_mmm_f32_8x6_cortexa7 24 128 17 0.00006131644295051564\narmv7neon_mmm_f32_8x4_generic 8 32 12 0.000004263261337563466\narmv7neon_mmm_f32_8x4_generic 15 128 13 0.000031724034290535855\ngeneric_f32_4x4 13 128 4 0.000014842273639272524\narmv7neon_mmm_f32_8x6_generic 16 128 17 0.00003115527449889593\narmv7neon_mmm_f32_8x4_cortexa7 25 128 13 0.00008540107215655228\narmv7neon_mmm_f32_8x4_cortexa9 17 4 8 0.000003867693626667699\narmv7neon_mmm_f32_8x6_generic 9 4 6 0.0000018447023821962963\narmv7neon_mmm_f32_8x4_generic 15 4 5 0.0000031342530980756114\narmv7neon_mmm_f32_8x4_cortexa9 25 32 11 0.000016711914928453156\narmv7neon_mmm_f32_8x4_cortexa9 9 4 7 0.0000029971874356879734\narmv7neon_mmm_f32_8x4_cortexa9 17 32 8 0.000008299083390781581\narmv7neon_mmm_f32_8x6_generic 9 128 11 0.000020922916297729766\narmv7neon_mmm_f32_8x6_cortexa9 25 4 11 0.000006455717226984232\ngeneric_f32_4x4 9 128 13 0.00004380056172414816\narmv7neon_mmm_f32_8x4_generic 9 4 3 0.0000018459881520629983\ngeneric_f32_4x4 13 32 13 0.000020718460422326147\ngeneric_f32_4x4 11 128 11 0.00003310679264480642\narmv7neon_mmm_f32_8x4_cortexa7 17 128 12 0.00004762075330394028\narmv7neon_mmm_f32_8x6_cortexa9 25 4 6 0.0000031406906098450826\narmv7neon_mmm_f32_8x4_cortexa7 15 4 3 0.0000020595941028725178\ngeneric_f32_4x4 4 32 5 0.000003094039509493947\narmv7neon_mmm_f32_8x4_cortexa7 17 4 13 0.000008167970707514618\narmv7neon_mmm_f32_8x6_cortexa9 16 128 7 0.000020852896757305592\narmv7neon_mmm_f32_8x4_generic 24 32 12 0.000011875110028324704\narmv7neon_mmm_f32_8x6_cortexa7 23 32 6 0.0000070036066823672265\narmv7neon_mmm_f32_8x4_generic 24 32 9 0.000012379847822938195\narmv7neon_mmm_f32_8x6_generic 16 32 7 0.000007288146983219525\narmv7neon_mmm_f32_8x4_generic 8 32 4 0.0000017213389522499293\narmv7neon_mmm_f32_8x6_cortexa7 25 32 12 0.00001698633380929768\narmv7neon_mmm_f32_8x4_generic 25 32 8 0.000010843062239811664\narmv7neon_mmm_f32_8x6_generic 16 128 5 0.000010970239776322642\narmv7neon_mmm_f32_8x6_cortexa9 23 32 12 0.00001105222841817834\narmv7neon_mmm_f32_8x4_cortexa7 7 4 12 0.0000028259675178908435\narmv7neon_mmm_f32_8x4_generic 17 32 13 0.000016456626258678596\narmv7neon_mmm_f32_8x6_generic 23 32 19 0.000021489093420466062\narmv7neon_mmm_f32_8x6_cortexa9 9 32 7 0.000007388528782309879\narmv7neon_mmm_f32_8x6_generic 16 128 13 0.0000308218952023933\narmv7neon_mmm_f32_8x4_cortexa9 24 128 4 0.000011799248370378306\narmv7neon_mmm_f32_8x6_cortexa7 15 4 5 0.000002311967551514126\narmv7neon_mmm_f32_8x6_cortexa9 15 32 19 0.000015002244448339703\narmv7neon_mmm_f32_8x4_cortexa7 25 32 4 0.000007113231524121585\narmv7neon_mmm_f32_8x4_generic 9 4 9 0.0000040996991473354\ngeneric_f32_4x4 11 4 11 0.00000604219727392619\narmv7neon_mmm_f32_8x4_generic 23 128 12 0.0000351784079527385\narmv7neon_mmm_f32_8x4_generic 8 32 8 0.0000029996111096440974\ngeneric_f32_4x4 3 128 11 0.000011575521961547323\narmv7neon_mmm_f32_8x6_generic 17 4 19 0.000008636925572322338\narmv7neon_mmm_f32_8x6_cortexa7 17 32 17 0.000019651252774271738\narmv7neon_mmm_f32_8x4_cortexa7 25 32 5 0.000014279031192163009\narmv7neon_mmm_f32_8x6_cortexa9 7 128 5 0.000005734808712355398\narmv7neon_mmm_f32_8x4_cortexa9 15 4 5 0.0000031170038028798296\narmv7neon_mmm_f32_8x4_cortexa9 24 128 13 0.000046424963618177915\narmv7neon_mmm_f32_8x6_generic 16 4 5 0.0000022248730615242665\ngeneric_f32_4x4 8 128 9 0.0000220091711298822\narmv7neon_mmm_f32_8x6_cortexa9 24 32 17 0.000015790292495872178\narmv7neon_mmm_f32_8x6_cortexa7 25 4 12 0.000006064814099731578\narmv7neon_mmm_f32_8x4_generic 9 32 3 0.0000033141535571016215\narmv7neon_mmm_f32_8x4_cortexa7 24 32 7 0.000010955777874109968\narmv7neon_mmm_f32_8x6_generic 7 32 19 0.000008240412026502634\narmv7neon_mmm_f32_8x6_generic 15 128 12 0.000021273134898855332\narmv7neon_mmm_f32_8x4_generic 9 128 12 0.000023427967692188372\ngeneric_f32_4x4 9 128 4 0.000011233611698379809\narmv7neon_mmm_f32_8x4_generic 23 32 3 0.000004898603928972602\narmv7neon_mmm_f32_8x4_cortexa9 15 4 7 0.000003196236363013327\ngeneric_f32_4x4 13 128 9 0.000043631376927673965\narmv7neon_mmm_f32_8x6_cortexa7 8 32 13 0.000006769099621081802\narmv7neon_mmm_f32_8x6_cortexa9 16 32 11 0.000007489162129911177\narmv7neon_mmm_f32_8x6_cortexa7 15 4 11 0.000004047240611447739\narmv7neon_mmm_f32_8x6_cortexa9 8 128 12 0.000010521407486118232\narmv7neon_mmm_f32_8x6_generic 24 128 12 0.000030475350289631447\narmv7neon_mmm_f32_8x6_cortexa9 8 4 18 0.0000023963651215663894\narmv7neon_mmm_f32_8x4_generic 7 4 13 0.0000033498033347340617\narmv7neon_mmm_f32_8x6_cortexa7 24 128 5 0.000021225542006705262\narmv7neon_mmm_f32_8x6_generic 8 128 7 0.000010658035873946595\narmv7neon_mmm_f32_8x6_cortexa9 16 128 13 0.00003086535920353521\narmv7neon_mmm_f32_8x6_cortexa7 9 4 13 0.000005015620872516637\narmv7neon_mmm_f32_8x6_cortexa9 8 4 13 0.0000025412882105309308\narmv7neon_mmm_f32_8x4_cortexa9 24 32 11 0.000012564889907233448\narmv7neon_mmm_f32_8x4_generic 7 128 13 0.000016356824884577277\narmv7neon_mmm_f32_8x4_cortexa7 15 32 11 0.00001113088620100476\narmv7neon_mmm_f32_8x6_generic 23 32 18 0.000016159850846463288\narmv7neon_mmm_f32_8x6_generic 15 32 19 0.000014889331973563625\narmv7neon_mmm_f32_8x4_generic 7 128 11 0.000012426167611771446\ngeneric_f32_4x4 4 4 4 0.000001047996290990507\narmv7neon_mmm_f32_8x4_generic 23 4 9 0.000006177048756338135\narmv7neon_mmm_f32_8x6_cortexa9 16 4 11 0.0000034974057138003183\narmv7neon_mmm_f32_8x6_cortexa9 7 128 12 0.00001128966410991496\narmv7neon_mmm_f32_8x4_cortexa9 9 4 8 0.0000027723235031605195\narmv7neon_mmm_f32_8x4_generic 7 128 12 0.000012422711675746062\narmv7neon_mmm_f32_8x4_cortexa7 25 128 12 0.0000632498211882511\narmv7neon_mmm_f32_8x4_generic 7 128 5 0.000008380542348646574\narmv7neon_mmm_f32_8x4_cortexa9 17 4 3 0.000002593764492859534\ngeneric_f32_4x4 9 4 3 0.0000025396068006223777\ngeneric_f32_4x4 9 4 12 0.0000055571336153173335\narmv7neon_mmm_f32_8x4_cortexa7 16 128 8 0.000021301840263398342\narmv7neon_mmm_f32_8x4_generic 9 32 12 0.000008360119586696751\narmv7neon_mmm_f32_8x6_cortexa9 23 32 13 0.00001642300983789001\narmv7neon_mmm_f32_8x4_cortexa7 9 32 13 0.000014115360584179656\narmv7neon_mmm_f32_8x4_generic 7 4 4 0.0000011988761531825325\ngeneric_f32_4x4 4 32 12 0.000004173516705669169\narmv7neon_mmm_f32_8x6_cortexa7 8 4 17 0.000002748952290655516\narmv7neon_mmm_f32_8x6_cortexa9 15 128 17 0.000031856750398890617\narmv7neon_mmm_f32_8x4_cortexa9 17 128 4 0.00001190476277599742\narmv7neon_mmm_f32_8x6_cortexa7 23 4 17 0.000008057629471932516\narmv7neon_mmm_f32_8x4_cortexa7 24 128 9 0.00004787648599530554\narmv7neon_mmm_f32_8x4_cortexa7 23 4 5 0.000004689549145067684\narmv7neon_mmm_f32_8x6_cortexa9 17 4 18 0.000006445994468840472\narmv7neon_mmm_f32_8x6_generic 9 128 17 0.00003107927056434744\narmv7neon_mmm_f32_8x4_generic 24 128 8 0.000023288445002113136\narmv7neon_mmm_f32_8x6_cortexa9 16 32 17 0.000010725045258389392\narmv7neon_mmm_f32_8x4_cortexa7 17 128 11 0.00004814359255643903\narmv7neon_mmm_f32_8x4_generic 7 4 3 0.0000011794687664451176\ngeneric_f32_4x4 7 32 5 0.000005808524626140803\narmv7neon_mmm_f32_8x6_generic 24 32 11 0.000010897102444398395\narmv7neon_mmm_f32_8x4_generic 7 4 7 0.0000019250466424784192\narmv7neon_mmm_f32_8x4_cortexa9 7 32 13 0.000006310599463529341\ngeneric_f32_4x4 4 32 8 0.000002964574479014875\narmv7neon_mmm_f32_8x6_cortexa7 17 4 12 0.000004769238750897463\narmv7neon_mmm_f32_8x6_generic 8 32 17 0.000005580365730578937\narmv7neon_mmm_f32_8x4_cortexa9 7 4 5 0.0000018767262951495324\narmv7neon_mmm_f32_8x4_cortexa9 9 128 3 0.000008376722642527261\ngeneric_f32_4x4 8 32 8 0.000005427007518436356\ngeneric_f32_4x4 3 32 7 0.000003269319162898471\narmv7neon_mmm_f32_8x6_generic 23 128 11 0.000031615917676258355\ngeneric_f32_4x4 11 32 3 0.000004643343538119153\narmv7neon_mmm_f32_8x6_generic 17 4 13 0.000006685914546845586\narmv7neon_mmm_f32_8x4_cortexa9 7 128 13 0.000016356503104634956\narmv7neon_mmm_f32_8x6_generic 8 4 11 0.000001987955413940483\narmv7neon_mmm_f32_8x6_cortexa9 23 32 6 0.0000058061790469852035\narmv7neon_mmm_f32_8x6_cortexa9 17 128 12 0.00003080777419909108\ngeneric_f32_4x4 3 4 13 0.00000320269810797615\narmv7neon_mmm_f32_8x6_cortexa9 17 32 12 0.000010479324211722284\narmv7neon_mmm_f32_8x4_cortexa9 7 4 4 0.000001192052740790223\narmv7neon_mmm_f32_8x4_cortexa9 24 128 12 0.00003457434886740767\narmv7neon_mmm_f32_8x6_cortexa9 23 32 11 0.000011348364973457056\narmv7neon_mmm_f32_8x4_generic 16 4 12 0.000003656453112482303\narmv7neon_mmm_f32_8x6_cortexa9 16 128 6 0.000010542301986141948\narmv7neon_mmm_f32_8x4_generic 24 128 4 0.000011845830939195392\narmv7neon_mmm_f32_8x6_cortexa7 9 32 18 0.000013032529505137248\narmv7neon_mmm_f32_8x4_generic 24 128 12 0.00003455867526442596\narmv7neon_mmm_f32_8x4_generic 23 128 7 0.00002405539721722316\narmv7neon_mmm_f32_8x4_cortexa9 23 32 4 0.000004484967279906347\narmv7neon_mmm_f32_8x4_cortexa9 17 32 4 0.000004369095064954239\narmv7neon_mmm_f32_8x6_cortexa7 8 4 12 0.000001838051428755667\narmv7neon_mmm_f32_8x4_generic 15 4 8 0.000003030902158049641\narmv7neon_mmm_f32_8x4_cortexa7 8 128 9 0.000016361621231447548\narmv7neon_mmm_f32_8x6_generic 15 4 5 0.000002193007903880869\narmv7neon_mmm_f32_8x6_cortexa9 24 32 18 0.000015047680763476656\narmv7neon_mmm_f32_8x6_generic 25 128 6 0.00002065407414270236\narmv7neon_mmm_f32_8x4_generic 25 32 9 0.00001655596981137993\narmv7neon_mmm_f32_8x4_cortexa7 7 4 8 0.000002046321793305707\narmv7neon_mmm_f32_8x4_generic 24 32 5 0.000008563586932451436\narmv7neon_mmm_f32_8x6_cortexa7 25 32 19 0.00003389329143278319\ngeneric_f32_4x4 3 4 3 0.0000011908985597580253\ngeneric_f32_4x4 11 128 13 0.000043852326643092945\narmv7neon_mmm_f32_8x6_cortexa9 7 32 6 0.0000025216564013293366\ngeneric_f32_4x4 8 128 11 0.00002204318798460829\ngeneric_f32_4x4 9 32 5 0.00000828023901975494\narmv7neon_mmm_f32_8x6_generic 25 4 11 0.000006352168944400001\narmv7neon_mmm_f32_8x4_cortexa9 24 32 5 0.000008590022043400085\narmv7neon_mmm_f32_8x4_generic 8 32 3 0.0000019449019781287236\narmv7neon_mmm_f32_8x4_cortexa7 24 128 8 0.0000317811111212887\narmv7neon_mmm_f32_8x4_cortexa9 7 128 9 0.000012372639572013476\narmv7neon_mmm_f32_8x6_generic 15 128 19 0.00004250719000509339\narmv7neon_mmm_f32_8x6_cortexa7 17 4 7 0.000005084587454728339\narmv7neon_mmm_f32_8x6_generic 15 32 5 0.000004169790668084077\ngeneric_f32_4x4 9 128 3 0.000011555287825087273\narmv7neon_mmm_f32_8x4_cortexa7 25 128 7 0.00004317760438423037\ngeneric_f32_4x4 8 32 12 0.00000779706808746927\narmv7neon_mmm_f32_8x6_generic 8 32 12 0.000003719914023610946\narmv7neon_mmm_f32_8x6_cortexa7 15 128 17 0.000041839968426337905\narmv7neon_mmm_f32_8x6_cortexa7 8 128 13 0.000020713409071600263\narmv7neon_mmm_f32_8x6_cortexa9 24 4 5 0.000003104317520805454\narmv7neon_mmm_f32_8x6_cortexa9 9 4 13 0.000004778161797091213\narmv7neon_mmm_f32_8x4_cortexa9 25 128 12 0.00004644468928643539\ngeneric_f32_4x4 11 4 9 0.000005973042856611991\narmv7neon_mmm_f32_8x4_cortexa7 23 128 8 0.00003217939318511314\narmv7neon_mmm_f32_8x6_generic 24 4 11 0.00000490822983265492\narmv7neon_mmm_f32_8x4_generic 17 4 12 0.000005542606566473033\narmv7neon_mmm_f32_8x6_cortexa9 16 32 5 0.000004236072973244724\narmv7neon_mmm_f32_8x6_cortexa7 24 32 12 0.000012668891408140166\narmv7neon_mmm_f32_8x6_cortexa7 17 128 19 0.00008180995267293593\narmv7neon_mmm_f32_8x4_cortexa7 8 4 9 0.0000023929041278364927\narmv7neon_mmm_f32_8x4_generic 17 4 11 0.000005996779011118073\narmv7neon_mmm_f32_8x4_generic 23 32 5 0.000008783310902330428\narmv7neon_mmm_f32_8x4_cortexa9 24 4 5 0.000004126054215412369\narmv7neon_mmm_f32_8x4_cortexa7 15 4 4 0.0000018664924896343623\ngeneric_f32_4x4 12 32 12 0.000011419939741453591\narmv7neon_mmm_f32_8x6_cortexa9 9 128 19 0.00004140314991254094\narmv7neon_mmm_f32_8x6_cortexa7 24 4 19 0.000008976504261442944\narmv7neon_mmm_f32_8x6_cortexa7 23 32 12 0.000013517680029411935\narmv7neon_mmm_f32_8x6_generic 23 128 17 0.00004767212397281204\narmv7neon_mmm_f32_8x6_cortexa7 9 128 12 0.000027472854173113385\ngeneric_f32_4x4 13 4 4 0.0000027493566687604084\narmv7neon_mmm_f32_8x6_cortexa7 7 32 7 0.000005097398974457722\narmv7neon_mmm_f32_8x4_generic 25 4 3 0.0000033394241283747185\narmv7neon_mmm_f32_8x4_cortexa7 25 4 5 0.000005839267769079541\ngeneric_f32_4x4 8 4 9 0.0000039467461932377605\narmv7neon_mmm_f32_8x6_cortexa7 8 32 6 0.0000025355396885136015\narmv7neon_mmm_f32_8x4_cortexa7 7 4 13 0.0000035454436358925364\ngeneric_f32_4x4 12 128 5 0.000022119589677203316\narmv7neon_mmm_f32_8x4_cortexa9 17 32 5 0.000008619398425928128\ngeneric_f32_4x4 5 32 12 0.00000809048145029521\narmv7neon_mmm_f32_8x6_generic 7 32 12 0.000004497135112528872\narmv7neon_mmm_f32_8x6_cortexa9 17 128 18 0.00004612047264624732\narmv7neon_mmm_f32_8x4_cortexa9 24 128 9 0.00003503924267370847\narmv7neon_mmm_f32_8x6_cortexa9 25 128 18 0.00006126219444229572\narmv7neon_mmm_f32_8x4_cortexa9 7 32 7 0.0000034009841492082025\narmv7neon_mmm_f32_8x6_cortexa7 8 32 18 0.0000065795660386672954\narmv7neon_mmm_f32_8x4_cortexa7 9 4 7 0.0000032333021816508883\ngeneric_f32_4x4 11 4 5 0.000004260253310204167\narmv7neon_mmm_f32_8x4_cortexa7 15 32 12 0.00001091466040761069\narmv7neon_mmm_f32_8x6_generic 25 128 17 0.00006242813577501301\narmv7neon_mmm_f32_8x6_cortexa7 23 128 17 0.00006230757214971725\narmv7neon_mmm_f32_8x4_cortexa7 9 4 3 0.000001972675027149826\narmv7neon_mmm_f32_8x6_generic 9 32 19 0.000013969471313273173\narmv7neon_mmm_f32_8x4_cortexa7 9 4 4 0.0000017499654922061095\ngeneric_f32_4x4 8 4 8 0.0000026159902565290668\narmv7neon_mmm_f32_8x6_generic 23 128 12 0.000031313730389866355\narmv7neon_mmm_f32_8x4_cortexa7 15 128 7 0.000021942752305858642\narmv7neon_mmm_f32_8x6_cortexa7 9 32 6 0.000004675388926372666\narmv7neon_mmm_f32_8x6_cortexa7 8 32 5 0.000002772093015202498\narmv7neon_mmm_f32_8x6_cortexa7 9 32 19 0.000017344416985284313\narmv7neon_mmm_f32_8x4_cortexa9 8 128 7 0.000008229649908268804\ngeneric_f32_4x4 3 128 12 0.000011565133509989783\ngeneric_f32_4x4 9 32 12 0.000011785850787897203\narmv7neon_mmm_f32_8x6_cortexa9 17 128 13 0.00004651964090971228\narmv7neon_mmm_f32_8x6_cortexa9 25 32 6 0.0000071127343061349165\narmv7neon_mmm_f32_8x6_cortexa7 25 128 13 0.00008178262598187339\narmv7neon_mmm_f32_8x4_cortexa9 25 32 13 0.000021697932623977668\narmv7neon_mmm_f32_8x4_cortexa9 16 4 11 0.0000040619610195960516\narmv7neon_mmm_f32_8x4_generic 23 128 3 0.000012468771697124281\ngeneric_f32_4x4 11 32 13 0.000015931672245334774\ngeneric_f32_4x4 8 32 9 0.000008097203894273956\narmv7neon_mmm_f32_8x4_cortexa9 17 4 5 0.000004171816997731455\narmv7neon_mmm_f32_8x6_generic 15 32 17 0.000011434650801767795\narmv7neon_mmm_f32_8x4_cortexa7 25 128 3 0.000022089826731240803\narmv7neon_mmm_f32_8x4_cortexa9 17 32 9 0.000012562108538363274\ngeneric_f32_4x4 7 128 4 0.000007740927731735862\narmv7neon_mmm_f32_8x6_cortexa7 16 128 17 0.00004105266320070572\narmv7neon_mmm_f32_8x6_generic 17 128 5 0.000016103121774230763\narmv7neon_mmm_f32_8x6_cortexa9 25 128 19 0.0000820798385767341\narmv7neon_mmm_f32_8x4_generic 15 32 12 0.000008721032730753763\narmv7neon_mmm_f32_8x4_generic 25 4 9 0.000007623970700084407\narmv7neon_mmm_f32_8x4_generic 17 32 11 0.000012672883096785458\narmv7neon_mmm_f32_8x6_generic 16 32 19 0.000013691087754459915\narmv7neon_mmm_f32_8x6_generic 9 4 12 0.0000031605677124646883\ngeneric_f32_4x4 5 4 5 0.0000029759889706969683\narmv7neon_mmm_f32_8x4_generic 16 32 9 0.00000841650213766426\ngeneric_f32_4x4 8 32 13 0.000010487684258287977\ngeneric_f32_4x4 4 128 5 0.000007733485289678424\narmv7neon_mmm_f32_8x6_cortexa7 8 4 19 0.0000033370479940342483\narmv7neon_mmm_f32_8x6_cortexa7 16 4 13 0.000004820409587660123\narmv7neon_mmm_f32_8x6_cortexa7 7 128 5 0.000007382676538540774\narmv7neon_mmm_f32_8x6_cortexa9 9 4 7 0.0000034194906166596\narmv7neon_mmm_f32_8x4_generic 24 128 3 0.000012489994751068132\narmv7neon_mmm_f32_8x6_generic 17 4 5 0.0000029369592565703416\narmv7neon_mmm_f32_8x6_cortexa9 7 32 7 0.000004299364469367014\ngeneric_f32_4x4 8 128 8 0.000014675074170610452\narmv7neon_mmm_f32_8x6_generic 24 32 6 0.000005321602550624656\ngeneric_f32_4x4 8 128 3 0.000007920826245860217\narmv7neon_mmm_f32_8x6_cortexa7 9 128 17 0.000041149683356221505\narmv7neon_mmm_f32_8x6_generic 15 128 18 0.0000317697933254728\narmv7neon_mmm_f32_8x4_generic 15 32 11 0.000008972113808644295\narmv7neon_mmm_f32_8x6_generic 17 128 11 0.00003128672479118261\narmv7neon_mmm_f32_8x6_generic 16 32 5 0.000004210171223198828\narmv7neon_mmm_f32_8x4_cortexa9 23 32 7 0.000008919434958029703\narmv7neon_mmm_f32_8x6_cortexa7 23 4 11 0.000005630918362501218\ngeneric_f32_4x4 13 4 13 0.000009734041681688549\narmv7neon_mmm_f32_8x4_cortexa7 23 4 8 0.000004419109488183114\narmv7neon_mmm_f32_8x6_generic 17 128 18 0.00004641763657018708\narmv7neon_mmm_f32_8x6_cortexa7 23 32 7 0.000013604910503058097\narmv7neon_mmm_f32_8x4_cortexa9 16 4 8 0.0000025811126658507737\narmv7neon_mmm_f32_8x6_generic 16 4 17 0.000004664791456959476\narmv7neon_mmm_f32_8x6_cortexa9 15 128 19 0.00004234538403110192\narmv7neon_mmm_f32_8x4_cortexa7 7 128 8 0.000011300292613931975\ngeneric_f32_4x4 4 32 7 0.000003116445195774716\narmv7neon_mmm_f32_8x4_generic 17 128 3 0.000012383648487655847\narmv7neon_mmm_f32_8x6_cortexa9 24 32 12 0.00001022987009394415\narmv7neon_mmm_f32_8x4_cortexa9 17 32 11 0.000012684638765009217\narmv7neon_mmm_f32_8x6_cortexa9 9 128 18 0.000030893903863542745\narmv7neon_mmm_f32_8x6_cortexa7 9 4 18 0.000004852117278755586\narmv7neon_mmm_f32_8x6_cortexa9 8 128 13 0.00001571685239499597\narmv7neon_mmm_f32_8x6_cortexa9 23 4 7 0.000005166723329090456\narmv7neon_mmm_f32_8x6_cortexa9 7 128 18 0.000016699359662800567\narmv7neon_mmm_f32_8x4_generic 17 32 4 0.0000043747418052874395\narmv7neon_mmm_f32_8x4_cortexa9 25 128 13 0.00006245169757364539\narmv7neon_mmm_f32_8x4_generic 24 128 11 0.00003520805509267024\narmv7neon_mmm_f32_8x4_generic 7 4 8 0.000001940897025671589\narmv7neon_mmm_f32_8x4_cortexa9 24 32 8 0.000008105445830543554\ngeneric_f32_4x4 3 32 5 0.000003239638778592903\narmv7neon_mmm_f32_8x6_generic 16 128 6 0.000010481759011880018\narmv7neon_mmm_f32_8x6_cortexa9 23 4 11 0.000005380241199288215\narmv7neon_mmm_f32_8x6_cortexa7 24 4 11 0.000005233250260928121\narmv7neon_mmm_f32_8x6_cortexa9 23 4 19 0.000009671598448323651\narmv7neon_mmm_f32_8x6_cortexa7 23 4 6 0.0000029172933842802905\narmv7neon_mmm_f32_8x4_cortexa9 23 32 8 0.000008528926728389173\narmv7neon_mmm_f32_8x4_cortexa9 24 128 8 0.000023207480788900347\ngeneric_f32_4x4 7 32 11 0.00000844108827118094\ngeneric_f32_4x4 12 4 5 0.000004095794522926841\narmv7neon_mmm_f32_8x4_generic 25 4 11 0.000007781652426587433\narmv7neon_mmm_f32_8x4_generic 16 32 12 0.000008090083939378293\narmv7neon_mmm_f32_8x4_generic 9 128 7 0.000016030853255705617\narmv7neon_mmm_f32_8x6_generic 15 4 19 0.000006928708092302355\narmv7neon_mmm_f32_8x6_cortexa7 24 128 12 0.00004054881877870741\ngeneric_f32_4x4 8 4 4 0.0000015677362682744682\narmv7neon_mmm_f32_8x6_generic 7 128 19 0.000021822554568277356\narmv7neon_mmm_f32_8x6_cortexa9 24 4 12 0.00000423613681186459\narmv7neon_mmm_f32_8x4_cortexa7 16 4 4 0.0000016584422934049926\narmv7neon_mmm_f32_8x6_cortexa7 15 4 12 0.0000039842977137122266\narmv7neon_mmm_f32_8x4_cortexa7 7 128 4 0.000005876303683712701\narmv7neon_mmm_f32_8x6_cortexa9 7 4 5 0.000001360389294622995\narmv7neon_mmm_f32_8x4_cortexa7 24 4 9 0.000006182082152017003\narmv7neon_mmm_f32_8x6_generic 7 128 17 0.000016508734849042377\narmv7neon_mmm_f32_8x4_generic 17 128 9 0.000035220470083517575\ngeneric_f32_4x4 11 32 4 0.000004333160180811584\narmv7neon_mmm_f32_8x6_cortexa7 7 32 12 0.000005349730883357719\narmv7neon_mmm_f32_8x4_generic 8 32 5 0.0000031582408088385206\narmv7neon_mmm_f32_8x6_cortexa9 8 32 6 0.000002149345359409538\ngeneric_f32_4x4 5 32 13 0.000010767359583748404\narmv7neon_mmm_f32_8x4_generic 17 32 5 0.000008612220700720838\ngeneric_f32_4x4 5 4 4 0.0000016841544626715604\narmv7neon_mmm_f32_8x4_cortexa9 9 32 8 0.000005740276944412148\narmv7neon_mmm_f32_8x6_cortexa9 23 128 7 0.00003146196425036688\narmv7neon_mmm_f32_8x4_cortexa9 25 128 9 0.00004693016970364154\narmv7neon_mmm_f32_8x4_generic 24 4 8 0.0000036269745603468488\narmv7neon_mmm_f32_8x6_cortexa9 24 32 5 0.000006081990180966961\narmv7neon_mmm_f32_8x6_generic 7 4 6 0.000001511590523783866\ngeneric_f32_4x4 13 128 7 0.00002944642529063838\narmv7neon_mmm_f32_8x4_cortexa9 8 4 11 0.000002254110071814015\narmv7neon_mmm_f32_8x6_cortexa9 24 128 19 0.00006153515122537746\narmv7neon_mmm_f32_8x4_cortexa9 16 128 13 0.00003108122366900544\ngeneric_f32_4x4 11 128 9 0.000033046521573243154\narmv7neon_mmm_f32_8x4_generic 7 4 9 0.0000026137777410885136\narmv7neon_mmm_f32_8x6_cortexa7 7 4 13 0.0000034217472919863715\narmv7neon_mmm_f32_8x6_generic 16 4 11 0.000003453597254301285\narmv7neon_mmm_f32_8x4_cortexa7 17 32 11 0.000015933978694713537\narmv7neon_mmm_f32_8x6_cortexa7 23 4 12 0.0000053181832633687915\narmv7neon_mmm_f32_8x6_generic 17 32 6 0.000005447406276972202\narmv7neon_mmm_f32_8x4_cortexa7 7 32 4 0.000002302756052490624\narmv7neon_mmm_f32_8x4_cortexa9 15 4 13 0.000005662932902369066\narmv7neon_mmm_f32_8x4_cortexa7 17 32 8 0.000010470053145450968\narmv7neon_mmm_f32_8x4_generic 17 4 8 0.000003846416592921501\narmv7neon_mmm_f32_8x4_cortexa7 25 32 7 0.000014416885420743293\narmv7neon_mmm_f32_8x6_cortexa9 7 4 7 0.000002316979424271203\narmv7neon_mmm_f32_8x6_cortexa9 16 4 17 0.000004718807435203576\ngeneric_f32_4x4 11 32 12 0.000011882704283190954\narmv7neon_mmm_f32_8x4_cortexa7 23 32 9 0.000016122379106643026\narmv7neon_mmm_f32_8x6_cortexa7 9 32 11 0.000009102642142770847\narmv7neon_mmm_f32_8x6_generic 25 4 7 0.000006116994655414601\narmv7neon_mmm_f32_8x4_generic 23 4 3 0.000002685832196401119\narmv7neon_mmm_f32_8x6_cortexa9 25 32 11 0.000014409038834226244\narmv7neon_mmm_f32_8x6_generic 23 32 12 0.000010966793413222081\narmv7neon_mmm_f32_8x4_cortexa7 15 4 8 0.0000032350546103453447\narmv7neon_mmm_f32_8x6_cortexa7 15 128 7 0.000027943938585492828\ngeneric_f32_4x4 7 4 4 0.0000017389973856077342\narmv7neon_mmm_f32_8x6_generic 8 4 12 0.0000017181145762324274\narmv7neon_mmm_f32_8x4_cortexa9 15 4 11 0.000004502163536054048\narmv7neon_mmm_f32_8x6_generic 15 128 6 0.000010890629782885533\narmv7neon_mmm_f32_8x4_cortexa7 23 128 12 0.00004796960186341638\narmv7neon_mmm_f32_8x6_generic 8 32 7 0.000003896823245256928\narmv7neon_mmm_f32_8x6_cortexa9 15 128 7 0.000021306419489905895\narmv7neon_mmm_f32_8x6_cortexa7 7 4 11 0.0000024493622993257104\narmv7neon_mmm_f32_8x6_cortexa7 7 32 19 0.000009918011828071643\narmv7neon_mmm_f32_8x4_cortexa9 24 4 7 0.000004277922454765341\narmv7neon_mmm_f32_8x4_cortexa7 9 4 5 0.0000031825583861368363\narmv7neon_mmm_f32_8x6_cortexa9 8 32 11 0.000004007622996506884\narmv7neon_mmm_f32_8x6_generic 15 4 11 0.0000038091887644203395\narmv7neon_mmm_f32_8x6_cortexa7 23 128 11 0.000041674027523638683\narmv7neon_mmm_f32_8x6_cortexa7 17 4 18 0.0000068571632794369916\narmv7neon_mmm_f32_8x6_cortexa9 9 128 12 0.000020793132831426426\narmv7neon_mmm_f32_8x6_generic 25 32 19 0.000027146627303420713\narmv7neon_mmm_f32_8x6_cortexa7 9 128 19 0.00005467645413703322\narmv7neon_mmm_f32_8x6_cortexa7 8 32 11 0.0000048195055146364755\narmv7neon_mmm_f32_8x4_cortexa7 8 128 5 0.000011083761554457595\ngeneric_f32_4x4 7 128 12 0.000022151037088381854\ngeneric_f32_4x4 9 128 11 0.00003295464355640065\narmv7neon_mmm_f32_8x6_cortexa9 25 4 17 0.000009038405830025483\ngeneric_f32_4x4 8 32 3 0.0000032862084964825317\narmv7neon_mmm_f32_8x4_cortexa9 23 128 12 0.00003536277613858418\narmv7neon_mmm_f32_8x6_cortexa9 16 128 11 0.000021144715462112054\narmv7neon_mmm_f32_8x6_generic 17 32 5 0.000005941189535355633\narmv7neon_mmm_f32_8x6_generic 15 32 13 0.000011297818131567483\narmv7neon_mmm_f32_8x6_generic 16 32 11 0.000007448947403712638\ngeneric_f32_4x4 5 4 7 0.0000029999149449467176\narmv7neon_mmm_f32_8x4_generic 8 32 13 0.000005690794573889263\narmv7neon_mmm_f32_8x4_cortexa9 23 128 4 0.000012046555463702128\narmv7neon_mmm_f32_8x4_cortexa7 25 128 5 0.00004305932269978789\narmv7neon_mmm_f32_8x6_cortexa7 24 32 7 0.000013181202441221999\narmv7neon_mmm_f32_8x4_cortexa9 23 4 3 0.0000026820954481134975\ngeneric_f32_4x4 12 4 8 0.0000036441773649361156\narmv7neon_mmm_f32_8x6_cortexa9 24 128 17 0.000046509213530800746\narmv7neon_mmm_f32_8x6_cortexa9 8 4 6 0.0000011575433314376175\ngeneric_f32_4x4 3 32 4 0.0000018882099872634265\narmv7neon_mmm_f32_8x4_cortexa7 9 32 3 0.000004070589988484743\narmv7neon_mmm_f32_8x4_cortexa7 17 32 9 0.000015851096904703647\ngeneric_f32_4x4 13 32 12 0.00001535208709935954\narmv7neon_mmm_f32_8x6_cortexa9 25 4 19 0.000011387388616753858\narmv7neon_mmm_f32_8x6_cortexa9 23 128 5 0.00001624764366012842\narmv7neon_mmm_f32_8x4_cortexa9 9 128 12 0.00002346244933254337\narmv7neon_mmm_f32_8x4_cortexa9 25 4 12 0.000007150024568853224\narmv7neon_mmm_f32_8x6_cortexa9 15 32 12 0.000007825900269054534\narmv7neon_mmm_f32_8x4_cortexa7 25 4 12 0.000007707673254391108\ngeneric_f32_4x4 11 32 5 0.000008363778534607299\narmv7neon_mmm_f32_8x4_cortexa7 17 128 8 0.00003194246552397465\narmv7neon_mmm_f32_8x6_generic 8 32 6 0.0000021066884422501677\narmv7neon_mmm_f32_8x6_cortexa7 16 4 11 0.0000036716657035155366\narmv7neon_mmm_f32_8x4_generic 17 128 7 0.00002384609528431003\narmv7neon_mmm_f32_8x4_cortexa7 9 128 7 0.000021759641210777887\narmv7neon_mmm_f32_8x6_cortexa7 17 32 11 0.000013412538846571892\ngeneric_f32_4x4 7 32 8 0.000005690920861795519\narmv7neon_mmm_f32_8x6_generic 25 128 19 0.00008258096370056387\narmv7neon_mmm_f32_8x6_cortexa7 15 4 7 0.000003922803920269034\narmv7neon_mmm_f32_8x4_cortexa9 16 4 12 0.0000036509568763084446\ngeneric_f32_4x4 11 128 4 0.000011287445429820831\narmv7neon_mmm_f32_8x6_cortexa7 15 128 5 0.000014299033646853434\narmv7neon_mmm_f32_8x6_cortexa9 24 128 12 0.00003053734816197179\narmv7neon_mmm_f32_8x6_cortexa9 17 32 6 0.000005510675534687666\narmv7neon_mmm_f32_8x6_cortexa9 9 32 19 0.000014080838768872731\ngeneric_f32_4x4 13 128 5 0.000029375850896877852\narmv7neon_mmm_f32_8x6_cortexa7 25 32 5 0.000009454087084930695\narmv7neon_mmm_f32_8x4_generic 8 4 8 0.0000015055905623190838\narmv7neon_mmm_f32_8x4_cortexa7 16 4 13 0.00000545158088053187\narmv7neon_mmm_f32_8x6_generic 8 128 6 0.0000054873008191504395\ngeneric_f32_4x4 5 128 7 0.000015018624971335435\narmv7neon_mmm_f32_8x6_generic 15 128 7 0.00002123110926361701\narmv7neon_mmm_f32_8x4_cortexa7 7 32 3 0.000002283721924125051\narmv7neon_mmm_f32_8x6_generic 9 128 19 0.00004153518147749415\narmv7neon_mmm_f32_8x4_generic 9 32 11 0.000008626343403146056\narmv7neon_mmm_f32_8x6_cortexa9 9 4 6 0.0000018884740207207647\narmv7neon_mmm_f32_8x6_cortexa7 9 128 6 0.000013948466701679792\ngeneric_f32_4x4 11 4 12 0.000005708922503580876\narmv7neon_mmm_f32_8x6_cortexa7 24 128 6 0.000020527418679269756\narmv7neon_mmm_f32_8x4_cortexa7 8 4 11 0.0000024406051533290663\narmv7neon_mmm_f32_8x4_cortexa9 23 128 9 0.000035493958882244616\narmv7neon_mmm_f32_8x6_cortexa9 17 4 11 0.00000498803165144454\narmv7neon_mmm_f32_8x6_generic 8 32 18 0.000005320856703844532\narmv7neon_mmm_f32_8x4_cortexa9 9 128 4 0.00000810350837903274\ngeneric_f32_4x4 9 4 8 0.000003892676050216947\narmv7neon_mmm_f32_8x6_cortexa9 24 128 5 0.000016255452753216072\narmv7neon_mmm_f32_8x6_generic 7 4 5 0.0000013389729556607902\narmv7neon_mmm_f32_8x6_generic 17 32 19 0.000020571678410473347\narmv7neon_mmm_f32_8x6_cortexa9 7 32 13 0.0000063039500398145465\narmv7neon_mmm_f32_8x4_cortexa9 17 128 7 0.000023826784148315835\narmv7neon_mmm_f32_8x4_generic 24 4 7 0.000004274685140650307\narmv7neon_mmm_f32_8x6_cortexa9 17 32 17 0.000015955807283560996\narmv7neon_mmm_f32_8x6_cortexa7 17 4 13 0.000007179865477053954\narmv7neon_mmm_f32_8x4_cortexa9 7 32 12 0.00000488792089076881\ngeneric_f32_4x4 4 128 11 0.000011283150201350866\narmv7neon_mmm_f32_8x6_cortexa7 7 128 17 0.00002152414507851334\narmv7neon_mmm_f32_8x4_generic 25 32 13 0.0000216921434601138\narmv7neon_mmm_f32_8x6_generic 15 32 12 0.000007763276203034865\ngeneric_f32_4x4 7 128 9 0.00002231642099570999\narmv7neon_mmm_f32_8x4_generic 23 4 7 0.0000044972515990527706\narmv7neon_mmm_f32_8x4_generic 16 128 9 0.00002352868039758383\narmv7neon_mmm_f32_8x4_generic 24 32 8 0.000008105856924464553\narmv7neon_mmm_f32_8x4_cortexa7 25 4 9 0.000008239024137829502\narmv7neon_mmm_f32_8x4_generic 8 4 7 0.000001726186960943308\narmv7neon_mmm_f32_8x6_cortexa7 24 4 12 0.000004476686880431161\narmv7neon_mmm_f32_8x4_generic 8 4 5 0.0000016761254405249265\narmv7neon_mmm_f32_8x6_cortexa9 17 32 18 0.000015401920673930987\narmv7neon_mmm_f32_8x6_cortexa7 8 4 18 0.000002497401986754502\narmv7neon_mmm_f32_8x6_cortexa7 23 32 18 0.000019976606065621178\narmv7neon_mmm_f32_8x4_cortexa9 16 128 5 0.000015920451629989893\narmv7neon_mmm_f32_8x6_cortexa7 16 128 19 0.00005438554195188942\narmv7neon_mmm_f32_8x4_generic 25 32 11 0.00001669591843012648\narmv7neon_mmm_f32_8x6_cortexa9 7 32 18 0.000006518795701369289\ngeneric_f32_4x4 3 128 7 0.000007891377185866899\narmv7neon_mmm_f32_8x6_cortexa9 9 128 11 0.000021029476636249923\narmv7neon_mmm_f32_8x4_generic 9 32 8 0.00000573930624839856\narmv7neon_mmm_f32_8x4_cortexa9 23 32 9 0.000012851357918328979\narmv7neon_mmm_f32_8x4_cortexa7 25 128 11 0.00006422947640130408\narmv7neon_mmm_f32_8x4_cortexa7 9 128 5 0.000021690553836350107\narmv7neon_mmm_f32_8x4_cortexa7 8 128 4 0.000005680428664405247\narmv7neon_mmm_f32_8x6_cortexa9 17 32 11 0.000010949048608533127\narmv7neon_mmm_f32_8x4_cortexa9 9 32 13 0.00001119516860361631\narmv7neon_mmm_f32_8x4_cortexa9 8 128 11 0.000012017424644185006\narmv7neon_mmm_f32_8x4_cortexa9 17 4 9 0.000005874110633384251\narmv7neon_mmm_f32_8x6_cortexa7 9 4 7 0.0000035812085152963736\narmv7neon_mmm_f32_8x4_cortexa7 8 4 12 0.0000022112784369711703\narmv7neon_mmm_f32_8x6_generic 23 32 13 0.000016306177258992233\narmv7neon_mmm_f32_8x4_cortexa7 9 32 11 0.000010819099493176479\narmv7neon_mmm_f32_8x4_generic 25 128 12 0.00004665173717365099\narmv7neon_mmm_f32_8x4_cortexa9 25 4 8 0.000004903249015406008\narmv7neon_mmm_f32_8x4_generic 15 128 8 0.000016011765752869696\ngeneric_f32_4x4 4 4 5 0.00000173237663586998\ngeneric_f32_4x4 7 32 13 0.000010951743398686358\narmv7neon_mmm_f32_8x6_cortexa9 7 32 11 0.000004373180249911381\narmv7neon_mmm_f32_8x6_generic 9 4 7 0.000003340351034825373\narmv7neon_mmm_f32_8x6_cortexa7 8 128 6 0.000007164475272224828\ngeneric_f32_4x4 8 4 11 0.000003982256860993786\narmv7neon_mmm_f32_8x6_generic 15 128 5 0.000010930509860719574\narmv7neon_mmm_f32_8x4_cortexa9 25 128 11 0.00004712675673590545\narmv7neon_mmm_f32_8x4_cortexa7 7 4 11 0.000002822400310815202\narmv7neon_mmm_f32_8x6_cortexa7 17 32 13 0.000019470812344800868\narmv7neon_mmm_f32_8x4_cortexa7 23 32 4 0.000005619794374416781\ngeneric_f32_4x4 11 32 8 0.000008140965167056766\narmv7neon_mmm_f32_8x4_cortexa7 17 4 5 0.000004516405012537028\narmv7neon_mmm_f32_8x4_cortexa7 17 128 4 0.00001625158956213561\narmv7neon_mmm_f32_8x6_generic 16 128 18 0.000030484389052945016\narmv7neon_mmm_f32_8x6_cortexa7 17 128 11 0.00004127386103261296\narmv7neon_mmm_f32_8x6_cortexa7 24 32 6 0.000006586776362035912\narmv7neon_mmm_f32_8x4_generic 9 32 5 0.000005900796369564617\narmv7neon_mmm_f32_8x6_cortexa7 7 128 11 0.000014441097326114378\narmv7neon_mmm_f32_8x6_cortexa9 24 4 19 0.000008421665590898334\narmv7neon_mmm_f32_8x4_cortexa7 15 32 9 0.000011049029459002566\narmv7neon_mmm_f32_8x6_cortexa9 17 32 7 0.000010792251610714281\narmv7neon_mmm_f32_8x4_generic 24 32 11 0.000012578955321334167\narmv7neon_mmm_f32_8x6_generic 8 4 17 0.0000025848174603713075\narmv7neon_mmm_f32_8x4_cortexa9 8 128 12 0.000011812308047768464\ngeneric_f32_4x4 12 128 8 0.00002174331661540877\narmv7neon_mmm_f32_8x4_cortexa7 16 128 12 0.0000317241936699183\narmv7neon_mmm_f32_8x6_cortexa9 9 4 5 0.000002126610313888205\narmv7neon_mmm_f32_8x6_cortexa9 8 32 12 0.0000037644801015301184\narmv7neon_mmm_f32_8x4_cortexa9 9 32 4 0.000003081152195958602\ngeneric_f32_4x4 7 32 9 0.000008408374041494756\ngeneric_f32_4x4 4 128 4 0.0000040497405415986515\narmv7neon_mmm_f32_8x4_cortexa9 15 128 7 0.000016202393035594662\narmv7neon_mmm_f32_8x6_cortexa9 25 128 6 0.000020788151245438416\narmv7neon_mmm_f32_8x4_cortexa7 8 128 13 0.000021488179929071295\narmv7neon_mmm_f32_8x4_cortexa7 25 32 9 0.00002091079321932589\narmv7neon_mmm_f32_8x4_cortexa9 8 128 5 0.000008180420598290364\narmv7neon_mmm_f32_8x4_generic 7 128 3 0.000004423621357206741\narmv7neon_mmm_f32_8x4_cortexa9 15 4 9 0.000004394233374561228\narmv7neon_mmm_f32_8x6_generic 7 4 12 0.0000025127271377138368\narmv7neon_mmm_f32_8x4_generic 23 128 8 0.00002364193719899604\narmv7neon_mmm_f32_8x4_cortexa7 7 32 11 0.000005969501269507202\narmv7neon_mmm_f32_8x6_cortexa9 16 4 7 0.0000033362358276857998\narmv7neon_mmm_f32_8x4_generic 25 4 4 0.000002679652210124942\narmv7neon_mmm_f32_8x6_cortexa7 24 128 19 0.00008148701915820504\narmv7neon_mmm_f32_8x4_cortexa7 25 32 12 0.000020301609592643522\narmv7neon_mmm_f32_8x6_generic 25 4 12 0.000005597867792560783\narmv7neon_mmm_f32_8x4_cortexa9 16 4 13 0.0000050370172264265826\ngeneric_f32_4x4 11 4 8 0.000003987191713644613\narmv7neon_mmm_f32_8x4_cortexa7 7 32 9 0.00000592156011559423\narmv7neon_mmm_f32_8x6_cortexa7 25 4 13 0.000009317851927845776\narmv7neon_mmm_f32_8x6_generic 7 32 6 0.0000025009886657895365\narmv7neon_mmm_f32_8x4_cortexa9 9 32 5 0.0000059031902576708605\narmv7neon_mmm_f32_8x4_cortexa7 25 4 7 0.000005997625417148922\narmv7neon_mmm_f32_8x4_generic 15 128 9 0.000023965770004599912\narmv7neon_mmm_f32_8x4_generic 9 128 3 0.000008336141118552254\narmv7neon_mmm_f32_8x6_cortexa9 15 4 5 0.0000022485015750854945\narmv7neon_mmm_f32_8x4_cortexa7 25 32 13 0.000027508557090202434\narmv7neon_mmm_f32_8x4_cortexa9 7 32 4 0.0000019276370605991633\narmv7neon_mmm_f32_8x6_cortexa7 7 32 13 0.000007512654299558133\ngeneric_f32_4x4 12 128 12 0.000032295866967880904\narmv7neon_mmm_f32_8x6_generic 8 128 13 0.00001569076394434193\narmv7neon_mmm_f32_8x4_cortexa9 17 128 8 0.000023412675748873913\narmv7neon_mmm_f32_8x4_cortexa9 16 4 3 0.000001964056838467611\narmv7neon_mmm_f32_8x4_generic 16 4 4 0.0000015347326629006993\narmv7neon_mmm_f32_8x4_cortexa9 24 4 4 0.0000020572993016505205\narmv7neon_mmm_f32_8x4_cortexa9 8 32 3 0.0000019507567628415824\narmv7neon_mmm_f32_8x4_cortexa7 8 32 7 0.000004003993118643105\narmv7neon_mmm_f32_8x4_generic 17 4 13 0.000007623324255157955\narmv7neon_mmm_f32_8x6_cortexa9 9 4 11 0.0000035090633204775533\narmv7neon_mmm_f32_8x6_generic 9 4 19 0.00000601244922696809\narmv7neon_mmm_f32_8x6_generic 9 128 7 0.000020875839952630357\ngeneric_f32_4x4 4 32 11 0.000004326527977488747\narmv7neon_mmm_f32_8x6_cortexa9 25 32 18 0.00002022815888923194\narmv7neon_mmm_f32_8x6_cortexa9 15 4 18 0.000005434062942764287\narmv7neon_mmm_f32_8x4_cortexa9 24 4 3 0.000002704298016252254\narmv7neon_mmm_f32_8x4_generic 24 4 9 0.000005701271915969865\narmv7neon_mmm_f32_8x6_cortexa9 7 128 19 0.000021850407441008384\narmv7neon_mmm_f32_8x4_cortexa9 15 4 3 0.0000019251432800014627\narmv7neon_mmm_f32_8x6_cortexa7 15 4 18 0.000005711033859652051\narmv7neon_mmm_f32_8x6_cortexa7 9 4 17 0.000005102785803942399\narmv7neon_mmm_f32_8x4_generic 8 128 4 0.000004232482987549164\narmv7neon_mmm_f32_8x4_cortexa9 25 128 7 0.00003172200454891407\narmv7neon_mmm_f32_8x4_cortexa9 9 32 12 0.000008361571818242635\narmv7neon_mmm_f32_8x6_cortexa7 24 32 17 0.000019514235657015186\narmv7neon_mmm_f32_8x6_cortexa7 23 128 6 0.000020944404830412158\narmv7neon_mmm_f32_8x6_cortexa9 17 128 5 0.000016134142374025674\narmv7neon_mmm_f32_8x4_cortexa7 8 4 7 0.000001873057019803265\narmv7neon_mmm_f32_8x6_cortexa7 25 128 5 0.00002802974521426182\narmv7neon_mmm_f32_8x6_cortexa9 17 4 17 0.000006958050831218578\narmv7neon_mmm_f32_8x4_cortexa9 15 32 5 0.00000608182468505122\narmv7neon_mmm_f32_8x4_generic 7 128 4 0.0000044404498411270055\ngeneric_f32_4x4 13 4 5 0.000005350599856702572\narmv7neon_mmm_f32_8x4_generic 16 4 9 0.0000039608523692233575\narmv7neon_mmm_f32_8x6_cortexa9 16 4 12 0.000003013935297522993\narmv7neon_mmm_f32_8x6_generic 15 4 13 0.000005319052004340107\narmv7neon_mmm_f32_8x4_cortexa9 8 4 13 0.0000027408020700732427\narmv7neon_mmm_f32_8x6_generic 8 128 18 0.000015505866042830093\narmv7neon_mmm_f32_8x6_cortexa7 15 32 18 0.000013888471960481304\narmv7neon_mmm_f32_8x4_cortexa7 15 128 8 0.00002174159034943732\narmv7neon_mmm_f32_8x4_generic 9 32 4 0.0000030856158641387716\narmv7neon_mmm_f32_8x4_generic 24 4 5 0.000004129514406774627\ngeneric_f32_4x4 4 4 7 0.0000017497295508923947\narmv7neon_mmm_f32_8x6_generic 23 32 17 0.00001653018733416135\ngeneric_f32_4x4 7 128 13 0.000029515913541067617\narmv7neon_mmm_f32_8x6_cortexa9 24 32 19 0.000020360891894208073\narmv7neon_mmm_f32_8x6_cortexa9 9 32 18 0.000010557768641496435\narmv7neon_mmm_f32_8x4_generic 8 4 13 0.000002736501222546797\narmv7neon_mmm_f32_8x4_generic 16 32 11 0.000008543993772071838\narmv7neon_mmm_f32_8x4_cortexa7 7 32 12 0.0000059782618274442185\narmv7neon_mmm_f32_8x6_cortexa7 8 128 11 0.000014095624176931188\narmv7neon_mmm_f32_8x4_cortexa7 7 32 13 0.00000775363354362587\narmv7neon_mmm_f32_8x4_cortexa7 24 128 5 0.000032299734402349505\narmv7neon_mmm_f32_8x6_cortexa9 25 32 12 0.000013697867410484751\narmv7neon_mmm_f32_8x4_cortexa7 24 128 4 0.000016113162651910352\narmv7neon_mmm_f32_8x6_cortexa9 9 32 5 0.0000041006560791439575\ngeneric_f32_4x4 7 4 11 0.000004296735480110931\narmv7neon_mmm_f32_8x6_generic 8 32 11 0.000003977003783384346\narmv7neon_mmm_f32_8x4_generic 24 32 13 0.000016227668114322495\narmv7neon_mmm_f32_8x6_generic 7 32 5 0.0000023419854212729095\ngeneric_f32_4x4 3 128 3 0.00000421323412258659\ngeneric_f32_4x4 8 4 12 0.0000036950676170218278\narmv7neon_mmm_f32_8x4_cortexa7 15 4 12 0.000004609879642718733\narmv7neon_mmm_f32_8x6_generic 8 128 11 0.000010745832002669769\narmv7neon_mmm_f32_8x6_cortexa7 16 32 18 0.000012726640475291294\narmv7neon_mmm_f32_8x6_generic 24 4 13 0.000006477448734654056\narmv7neon_mmm_f32_8x4_cortexa9 23 128 5 0.000023893090361578605\narmv7neon_mmm_f32_8x6_cortexa9 7 128 7 0.000011059489910827611\narmv7neon_mmm_f32_8x4_cortexa7 24 4 13 0.000007916232793484605\narmv7neon_mmm_f32_8x6_generic 24 32 5 0.000006050244864860637\narmv7neon_mmm_f32_8x6_generic 23 4 5 0.000003050740283377829\narmv7neon_mmm_f32_8x4_generic 17 4 7 0.000004317179186717401\ngeneric_f32_4x4 13 128 3 0.000015316541870867625\narmv7neon_mmm_f32_8x6_generic 25 128 11 0.00004196449161824797\narmv7neon_mmm_f32_8x6_generic 24 32 7 0.000010654786791684403\narmv7neon_mmm_f32_8x6_generic 9 128 13 0.0000310078542141026\narmv7neon_mmm_f32_8x4_cortexa7 17 128 13 0.00006395441906545403\narmv7neon_mmm_f32_8x6_cortexa7 25 4 11 0.000006802751222800881\narmv7neon_mmm_f32_8x6_cortexa7 16 32 12 0.000008630037205832748\narmv7neon_mmm_f32_8x6_cortexa9 8 128 6 0.000005530202507023102\narmv7neon_mmm_f32_8x4_cortexa7 24 4 3 0.000002895093469687887\narmv7neon_mmm_f32_8x6_cortexa7 23 32 13 0.00002011431966689971\narmv7neon_mmm_f32_8x6_cortexa7 8 128 17 0.000020781906415830542\narmv7neon_mmm_f32_8x6_cortexa7 8 128 7 0.00001401297102177983\ngeneric_f32_4x4 12 128 4 0.000011113783764067\narmv7neon_mmm_f32_8x4_cortexa9 23 128 8 0.000023633711148043694\narmv7neon_mmm_f32_8x6_cortexa7 8 32 7 0.00000473866242690848\narmv7neon_mmm_f32_8x6_cortexa7 23 128 13 0.00006200535049902772\narmv7neon_mmm_f32_8x6_generic 25 128 12 0.000040933940992834904\narmv7neon_mmm_f32_8x6_generic 8 4 7 0.0000019018312390917206\narmv7neon_mmm_f32_8x6_cortexa9 9 128 13 0.0000311113672962118\narmv7neon_mmm_f32_8x6_cortexa7 17 32 6 0.000006712613519894369\narmv7neon_mmm_f32_8x6_generic 17 32 17 0.00001584305104507533\narmv7neon_mmm_f32_8x6_cortexa9 25 4 5 0.000003846834546374256\narmv7neon_mmm_f32_8x4_generic 23 128 11 0.00003563405640596688\narmv7neon_mmm_f32_8x6_cortexa7 7 32 18 0.000007762095957340233\narmv7neon_mmm_f32_8x4_cortexa7 8 4 3 0.000001278724116798178\narmv7neon_mmm_f32_8x4_generic 8 32 9 0.000004428552791795867\narmv7neon_mmm_f32_8x4_cortexa7 9 32 8 0.000007212913220352459\narmv7neon_mmm_f32_8x6_cortexa7 16 32 11 0.000009132197785046325\narmv7neon_mmm_f32_8x6_generic 15 4 6 0.0000021293993921351435\narmv7neon_mmm_f32_8x6_cortexa9 8 32 17 0.000005618722861518267\ngeneric_f32_4x4 12 32 8 0.000007824605805291677\ngeneric_f32_4x4 3 4 9 0.0000025426825999668207\narmv7neon_mmm_f32_8x6_cortexa9 15 128 6 0.000010938928121944877\narmv7neon_mmm_f32_8x6_generic 8 128 12 0.000010479271785006985\narmv7neon_mmm_f32_8x6_cortexa7 25 4 18 0.000008843120394707296\ngeneric_f32_4x4 5 128 8 0.000014867005692635484\narmv7neon_mmm_f32_8x4_generic 25 4 12 0.000007134980758480078\narmv7neon_mmm_f32_8x6_generic 23 4 6 0.0000027550692223444384\ngeneric_f32_4x4 8 128 4 0.000007594935908642548\narmv7neon_mmm_f32_8x4_generic 24 4 3 0.0000027070026756528944\narmv7neon_mmm_f32_8x6_generic 15 4 7 0.0000036879615500804916\narmv7neon_mmm_f32_8x6_cortexa7 8 4 13 0.0000026670243954904784\narmv7neon_mmm_f32_8x6_generic 17 4 7 0.000004739940833422853\narmv7neon_mmm_f32_8x6_generic 24 128 11 0.00003139196614178293\narmv7neon_mmm_f32_8x6_cortexa9 24 128 13 0.0000461884138564535\ngeneric_f32_4x4 9 128 9 0.00003291260190607621\narmv7neon_mmm_f32_8x4_cortexa9 7 128 11 0.000012420788787006248\narmv7neon_mmm_f32_8x6_generic 17 128 12 0.00003088346853188957\narmv7neon_mmm_f32_8x4_cortexa7 8 4 13 0.000002968959195086507\narmv7neon_mmm_f32_8x6_cortexa9 24 128 6 0.000015548482246921477\narmv7neon_mmm_f32_8x4_cortexa7 17 32 7 0.000010943141781236415\narmv7neon_mmm_f32_8x6_cortexa7 16 4 12 0.000003160151719580885\ngeneric_f32_4x4 5 128 5 0.000014996303737029157\narmv7neon_mmm_f32_8x6_generic 25 4 6 0.0000030664133228659144\narmv7neon_mmm_f32_8x4_cortexa9 9 4 11 0.0000041810312425253365\narmv7neon_mmm_f32_8x6_generic 15 4 18 0.000005353071509503547\narmv7neon_mmm_f32_8x6_cortexa7 8 32 12 0.000004562116220290897\narmv7neon_mmm_f32_8x4_cortexa9 8 32 7 0.0000032056568363054847\narmv7neon_mmm_f32_8x4_generic 16 4 11 0.0000040609047361039316\narmv7neon_mmm_f32_8x6_cortexa9 17 4 19 0.00000876633957316096\narmv7neon_mmm_f32_8x6_cortexa7 15 128 18 0.00004180200146350024\ngeneric_f32_4x4 5 128 9 0.000022192516532888443\narmv7neon_mmm_f32_8x6_cortexa9 24 32 11 0.000010954278251461477\narmv7neon_mmm_f32_8x6_cortexa9 8 32 5 0.000002372663230804753\narmv7neon_mmm_f32_8x6_generic 8 4 5 0.0000013605175071348517\narmv7neon_mmm_f32_8x6_cortexa7 24 4 6 0.0000025054020094278158\narmv7neon_mmm_f32_8x6_generic 24 128 5 0.000016222838915570737\ngeneric_f32_4x4 12 32 4 0.000004159176762509275\ngeneric_f32_4x4 13 4 11 0.000007582485226342242\ngeneric_f32_4x4 9 4 5 0.000004196638831417376\narmv7neon_mmm_f32_8x6_cortexa7 25 32 17 0.000026071317528366354\narmv7neon_mmm_f32_8x4_cortexa9 16 128 8 0.000015686411664235285\ngeneric_f32_4x4 13 128 11 0.00004380249651194818\narmv7neon_mmm_f32_8x4_cortexa7 24 32 13 0.000020563104321953744\narmv7neon_mmm_f32_8x4_cortexa9 23 4 7 0.0000044861575535086865\ngeneric_f32_4x4 12 128 3 0.000011613772328414718\narmv7neon_mmm_f32_8x6_cortexa7 16 4 19 0.000006163473976132481\ngeneric_f32_4x4 7 4 8 0.0000029313315346635237\narmv7neon_mmm_f32_8x4_cortexa9 23 32 12 0.000012545342985454958\narmv7neon_mmm_f32_8x6_cortexa9 7 128 11 0.000011136453503507945\narmv7neon_mmm_f32_8x6_cortexa7 9 128 13 0.000041110907972239005\narmv7neon_mmm_f32_8x4_cortexa9 7 32 8 0.000003418961974304266\narmv7neon_mmm_f32_8x4_cortexa7 24 32 12 0.000015105629538772432\ngeneric_f32_4x4 7 128 3 0.000007903637217361153\narmv7neon_mmm_f32_8x6_cortexa7 9 4 11 0.000003661168986658601\ngeneric_f32_4x4 4 128 9 0.00001126621217728165\narmv7neon_mmm_f32_8x4_cortexa7 23 128 3 0.000016760586455833876\narmv7neon_mmm_f32_8x6_cortexa9 16 32 12 0.000007056930252112048\narmv7neon_mmm_f32_8x6_cortexa9 8 4 19 0.000003165350777332514\narmv7neon_mmm_f32_8x6_generic 8 32 13 0.000005498586776368978\ngeneric_f32_4x4 3 128 13 0.000015227075547579779\ngeneric_f32_4x4 12 4 13 0.000007217490541690208\narmv7neon_mmm_f32_8x6_cortexa7 16 128 18 0.000040584526180649477\narmv7neon_mmm_f32_8x6_cortexa9 25 4 7 0.000006216705461889991\narmv7neon_mmm_f32_8x4_generic 24 128 9 0.000035021164515684145\ngeneric_f32_4x4 3 32 12 0.000004609887398147942\narmv7neon_mmm_f32_8x4_generic 25 128 9 0.00004684932933132534\narmv7neon_mmm_f32_8x6_generic 7 4 17 0.0000033628632970040393\ngeneric_f32_4x4 4 128 12 0.000011148897561634414\narmv7neon_mmm_f32_8x4_generic 8 128 8 0.000008028324048468155\narmv7neon_mmm_f32_8x4_generic 8 4 11 0.0000022515619534309057\narmv7neon_mmm_f32_8x4_cortexa7 9 32 5 0.00000739044702644143\narmv7neon_mmm_f32_8x4_generic 8 128 5 0.000008179707225297848\narmv7neon_mmm_f32_8x4_generic 9 4 4 0.0000016079691037580586\narmv7neon_mmm_f32_8x4_generic 16 128 4 0.00000803169255760427\narmv7neon_mmm_f32_8x4_generic 25 128 3 0.000016355748669331108\narmv7neon_mmm_f32_8x6_cortexa9 24 4 18 0.0000061179448610506165\narmv7neon_mmm_f32_8x6_cortexa9 16 4 19 0.000005803928147585998\narmv7neon_mmm_f32_8x6_cortexa7 17 128 6 0.00002064753338392539\narmv7neon_mmm_f32_8x4_cortexa9 9 4 12 0.000003936841431536775\narmv7neon_mmm_f32_8x4_cortexa7 17 32 13 0.000020815595163068456\narmv7neon_mmm_f32_8x6_cortexa7 23 32 11 0.000013810001606181058\narmv7neon_mmm_f32_8x4_cortexa7 15 32 13 0.00001449569792912252\narmv7neon_mmm_f32_8x4_cortexa7 7 4 3 0.0000012380899483592991\narmv7neon_mmm_f32_8x6_cortexa9 23 128 17 0.00004728149304262184\narmv7neon_mmm_f32_8x4_cortexa7 15 32 3 0.000004158381257275162\narmv7neon_mmm_f32_8x4_cortexa7 9 4 9 0.000004430434184731522\narmv7neon_mmm_f32_8x6_generic 23 128 13 0.00004715382202102794\narmv7neon_mmm_f32_8x6_cortexa9 9 4 19 0.000006116079821760871\narmv7neon_mmm_f32_8x6_cortexa9 7 4 18 0.0000035442600221807646\narmv7neon_mmm_f32_8x4_generic 15 128 5 0.000016261604433183137\narmv7neon_mmm_f32_8x4_cortexa9 8 128 4 0.000004251382345566181\narmv7neon_mmm_f32_8x6_cortexa7 25 128 11 0.000055294824394928404\narmv7neon_mmm_f32_8x6_cortexa9 23 128 13 0.00004716175024845285\ngeneric_f32_4x4 12 128 11 0.00003283409583810123\narmv7neon_mmm_f32_8x6_cortexa7 15 128 6 0.000014244472752339967\narmv7neon_mmm_f32_8x4_generic 17 32 8 0.000008307634701683482\narmv7neon_mmm_f32_8x4_generic 16 4 13 0.000005034888545956025\narmv7neon_mmm_f32_8x6_cortexa7 9 128 11 0.000027682771225447373\narmv7neon_mmm_f32_8x4_generic 25 128 8 0.000030970064243633\narmv7neon_mmm_f32_8x6_cortexa9 15 32 5 0.0000042126470351483745\narmv7neon_mmm_f32_8x6_cortexa9 8 128 7 0.000010688502492077545\narmv7neon_mmm_f32_8x6_cortexa7 8 32 19 0.000008785622923040567\ngeneric_f32_4x4 8 32 7 0.000005699367356537332\narmv7neon_mmm_f32_8x4_cortexa9 23 4 13 0.000007982321009915726\narmv7neon_mmm_f32_8x6_cortexa9 7 32 19 0.000008296837727225683\narmv7neon_mmm_f32_8x4_cortexa9 17 128 12 0.00003482723156416195\narmv7neon_mmm_f32_8x4_cortexa7 16 4 12 0.000003962627391393735\narmv7neon_mmm_f32_8x4_generic 24 32 4 0.000004275975584058462\narmv7neon_mmm_f32_8x4_generic 15 4 4 0.00000172768002087017\ngeneric_f32_4x4 12 128 13 0.000043289852359027704\narmv7neon_mmm_f32_8x6_cortexa7 24 32 18 0.00001876149316286456\narmv7neon_mmm_f32_8x4_cortexa9 23 128 13 0.000047081790611742305\narmv7neon_mmm_f32_8x4_generic 25 4 8 0.000004894765654717258\ngeneric_f32_4x4 13 32 9 0.000015809325344243507\narmv7neon_mmm_f32_8x6_cortexa9 15 32 11 0.000007869115330631703\narmv7neon_mmm_f32_8x6_cortexa7 16 32 19 0.000017064208112188813\narmv7neon_mmm_f32_8x4_cortexa7 9 32 12 0.000010571719001405112\narmv7neon_mmm_f32_8x6_cortexa9 7 4 12 0.000002545629385927187\narmv7neon_mmm_f32_8x4_cortexa9 24 128 5 0.00002368492009732677\narmv7neon_mmm_f32_8x6_cortexa7 25 4 5 0.000004026698373090156\ngeneric_f32_4x4 9 32 9 0.000012044684258478282\narmv7neon_mmm_f32_8x6_cortexa9 25 128 12 0.000040909803644855135\ngeneric_f32_4x4 8 128 12 0.00002170370665519434\narmv7neon_mmm_f32_8x6_generic 23 4 13 0.000007322110702891982\narmv7neon_mmm_f32_8x4_generic 8 128 9 0.000011964404434625917\narmv7neon_mmm_f32_8x6_generic 24 4 6 0.00000234486203657902\narmv7neon_mmm_f32_8x4_generic 16 128 12 0.000023289775015844785\narmv7neon_mmm_f32_8x4_generic 25 32 4 0.00000566239966628999\narmv7neon_mmm_f32_8x4_cortexa7 8 128 7 0.000011133117389377919\narmv7neon_mmm_f32_8x4_cortexa7 23 128 7 0.000032614653457132354\ngeneric_f32_4x4 11 128 3 0.000011596634661777904\narmv7neon_mmm_f32_8x6_generic 9 32 7 0.000007301874181587425\narmv7neon_mmm_f32_8x6_cortexa7 25 4 6 0.0000032842534990270153\narmv7neon_mmm_f32_8x4_cortexa7 25 4 11 0.000008398472143479583\narmv7neon_mmm_f32_8x4_cortexa9 17 128 3 0.000012367498051511246\narmv7neon_mmm_f32_8x4_cortexa9 15 4 4 0.0000017226474127640693\narmv7neon_mmm_f32_8x6_cortexa9 8 4 12 0.0000017663174571893155\narmv7neon_mmm_f32_8x6_generic 24 32 18 0.000014939528824089691\narmv7neon_mmm_f32_8x4_cortexa9 15 128 3 0.000008424928636646119\narmv7neon_mmm_f32_8x4_cortexa9 25 32 8 0.000010844406621465396\narmv7neon_mmm_f32_8x6_generic 24 128 6 0.00001549274717546449\narmv7neon_mmm_f32_8x4_cortexa9 7 4 8 0.0000019411905486089406\ngeneric_f32_4x4 5 4 12 0.000003970968097394295\narmv7neon_mmm_f32_8x4_cortexa7 17 128 3 0.000016675513856755485\narmv7neon_mmm_f32_8x6_generic 15 128 17 0.0000319988391989146\narmv7neon_mmm_f32_8x4_generic 8 4 3 0.0000012046114173177262\narmv7neon_mmm_f32_8x4_generic 9 4 13 0.000005271381310779438\narmv7neon_mmm_f32_8x6_generic 25 4 5 0.000003787680148419445\narmv7neon_mmm_f32_8x6_cortexa9 16 4 13 0.00000455965655028581\narmv7neon_mmm_f32_8x6_generic 16 128 12 0.000020507038874403576\narmv7neon_mmm_f32_8x4_cortexa9 7 4 3 0.0000011789464739113475\narmv7neon_mmm_f32_8x4_cortexa7 15 4 9 0.000004718086287004601\ngeneric_f32_4x4 3 32 8 0.0000032590697277049294\narmv7neon_mmm_f32_8x4_cortexa7 25 32 11 0.000021029318586240916\narmv7neon_mmm_f32_8x4_cortexa9 7 128 7 0.000008425064090676705\narmv7neon_mmm_f32_8x4_cortexa9 23 128 7 0.000024027014834706714\narmv7neon_mmm_f32_8x4_cortexa9 9 128 13 0.000031337531340777375\ngeneric_f32_4x4 12 32 3 0.0000046491920750409604\narmv7neon_mmm_f32_8x4_cortexa7 9 128 12 0.00003204704023977606\narmv7neon_mmm_f32_8x6_cortexa9 15 128 5 0.000010976146066847136\narmv7neon_mmm_f32_8x6_generic 9 128 5 0.000010821808595127155\narmv7neon_mmm_f32_8x6_cortexa9 16 32 18 0.000010217778748183444\narmv7neon_mmm_f32_8x6_cortexa9 8 32 13 0.000005537991894862071\narmv7neon_mmm_f32_8x6_cortexa7 15 128 11 0.000028075729994494098\narmv7neon_mmm_f32_8x6_cortexa7 24 4 7 0.000004992618899850428\narmv7neon_mmm_f32_8x6_generic 15 32 6 0.000004128781340215273\narmv7neon_mmm_f32_8x6_generic 15 32 18 0.000011342396294923752\ngeneric_f32_4x4 3 128 9 0.000011614205849736826\narmv7neon_mmm_f32_8x4_generic 7 32 9 0.000004862452767728157\narmv7neon_mmm_f32_8x4_cortexa9 17 4 7 0.000004308345799806778\narmv7neon_mmm_f32_8x6_cortexa9 16 32 13 0.000010598291750058129\narmv7neon_mmm_f32_8x4_generic 17 32 7 0.000008775628241420315\narmv7neon_mmm_f32_8x4_cortexa9 8 4 8 0.00000151354666080862\narmv7neon_mmm_f32_8x6_cortexa7 9 4 19 0.0000064774482704190535\narmv7neon_mmm_f32_8x4_cortexa9 9 4 4 0.000001610951102254284\narmv7neon_mmm_f32_8x4_cortexa7 7 128 3 0.00000588268473785512\narmv7neon_mmm_f32_8x4_cortexa9 9 128 7 0.00001614593088465898\narmv7neon_mmm_f32_8x6_cortexa9 9 128 6 0.000010694366815766805\narmv7neon_mmm_f32_8x6_cortexa9 17 4 5 0.000003000192050393217\narmv7neon_mmm_f32_8x4_cortexa9 8 32 4 0.0000017239720455885444\narmv7neon_mmm_f32_8x6_cortexa7 25 128 18 0.0000814675708223554\ngeneric_f32_4x4 12 128 7 0.000022191318720751985\ngeneric_f32_4x4 5 128 13 0.000029341360540777852\ngeneric_f32_4x4 7 4 5 0.000003063610185404798\narmv7neon_mmm_f32_8x4_cortexa7 23 32 12 0.000015790133040211727\narmv7neon_mmm_f32_8x6_cortexa7 17 128 13 0.00006139081303315256\narmv7neon_mmm_f32_8x4_cortexa9 8 32 12 0.000004268120941798172\narmv7neon_mmm_f32_8x6_generic 16 32 17 0.000010656142728555816\ngeneric_f32_4x4 13 4 8 0.000004925203028119758\narmv7neon_mmm_f32_8x6_generic 16 4 6 0.0000017414671485444925\narmv7neon_mmm_f32_8x6_generic 23 4 7 0.000005090461887505994\narmv7neon_mmm_f32_8x4_cortexa9 7 128 4 0.000004439163092495918\narmv7neon_mmm_f32_8x4_cortexa9 15 32 4 0.000003199516110418427\ngeneric_f32_4x4 13 4 9 0.000007521941079552681\narmv7neon_mmm_f32_8x4_generic 9 128 13 0.00003132672472959027\narmv7neon_mmm_f32_8x6_cortexa7 9 128 5 0.000014186965083572584\narmv7neon_mmm_f32_8x4_cortexa9 16 32 13 0.000010950995485777078\narmv7neon_mmm_f32_8x4_cortexa7 24 4 11 0.000006343455120468695\narmv7neon_mmm_f32_8x4_generic 23 32 12 0.000012518582750578088\narmv7neon_mmm_f32_8x4_cortexa9 23 4 5 0.000004357470789736493\narmv7neon_mmm_f32_8x6_generic 9 128 12 0.000020738827544116163\narmv7neon_mmm_f32_8x4_generic 7 128 8 0.000008441257822863349\narmv7neon_mmm_f32_8x6_generic 25 4 18 0.000008206815180054958\narmv7neon_mmm_f32_8x6_cortexa7 16 128 7 0.00002754330318348781\narmv7neon_mmm_f32_8x4_cortexa9 15 128 12 0.000023818850811398474\narmv7neon_mmm_f32_8x6_cortexa7 7 32 5 0.0000027477786822716826\ngeneric_f32_4x4 4 32 9 0.000004310962413140207\narmv7neon_mmm_f32_8x6_cortexa9 9 32 11 0.000007472405716714093\ngeneric_f32_4x4 9 4 7 0.0000042110601811121985\narmv7neon_mmm_f32_8x4_cortexa9 25 128 5 0.00003145000193606305\narmv7neon_mmm_f32_8x6_generic 16 128 7 0.000020961637268522957\narmv7neon_mmm_f32_8x6_cortexa7 8 128 18 0.00002061441681055432\narmv7neon_mmm_f32_8x6_cortexa7 15 32 11 0.000009542090957976056\narmv7neon_mmm_f32_8x4_cortexa9 8 32 8 0.0000030204030341535007\narmv7neon_mmm_f32_8x6_cortexa9 16 4 18 0.000004289256443600587\narmv7neon_mmm_f32_8x4_cortexa7 15 32 8 0.000007451057144596197\narmv7neon_mmm_f32_8x6_cortexa7 25 128 12 0.00005431857023175915\narmv7neon_mmm_f32_8x6_generic 7 128 11 0.000011099973931488493\narmv7neon_mmm_f32_8x4_cortexa9 16 128 11 0.000023648803168223167\narmv7neon_mmm_f32_8x6_cortexa9 17 4 12 0.0000044808063412694585\narmv7neon_mmm_f32_8x6_generic 25 32 7 0.000014053446208239492\narmv7neon_mmm_f32_8x6_generic 16 128 19 0.0000411706712809725\narmv7neon_mmm_f32_8x4_generic 16 32 13 0.000010937227005963747\narmv7neon_mmm_f32_8x4_cortexa7 8 32 3 0.000002329652194595725\narmv7neon_mmm_f32_8x4_cortexa9 23 4 11 0.000006308879061820964\narmv7neon_mmm_f32_8x6_cortexa7 16 4 7 0.0000035065261101064537\narmv7neon_mmm_f32_8x6_generic 9 128 6 0.000010599309996894829\narmv7neon_mmm_f32_8x4_cortexa7 24 128 11 0.000048035927729516165\narmv7neon_mmm_f32_8x6_generic 7 32 11 0.000004333152377602563\narmv7neon_mmm_f32_8x4_cortexa7 15 4 11 0.000004824968358151188\narmv7neon_mmm_f32_8x6_cortexa7 7 32 6 0.0000029036965940094368\ngeneric_f32_4x4 12 4 3 0.0000026010633583656195\narmv7neon_mmm_f32_8x6_cortexa9 8 32 18 0.000005373878070970815\narmv7neon_mmm_f32_8x4_generic 16 128 7 0.000016057054409709098\narmv7neon_mmm_f32_8x4_generic 17 128 5 0.000023713194238003484\narmv7neon_mmm_f32_8x6_generic 24 128 19 0.0000639824281045487\ngeneric_f32_4x4 5 4 13 0.000005260715034760787\narmv7neon_mmm_f32_8x6_cortexa9 16 128 19 0.000040932625772708764\narmv7neon_mmm_f32_8x4_generic 16 32 5 0.00000587187854127478\narmv7neon_mmm_f32_8x4_generic 15 32 4 0.0000032031359648686487\narmv7neon_mmm_f32_8x4_cortexa9 24 32 13 0.00001619115724351509\narmv7neon_mmm_f32_8x4_generic 15 32 8 0.000005976862649343733\narmv7neon_mmm_f32_8x6_cortexa9 16 32 7 0.000007329062657266122\narmv7neon_mmm_f32_8x4_cortexa9 25 32 7 0.00001147457512424339\narmv7neon_mmm_f32_8x4_cortexa9 24 128 3 0.000012480179872163079\narmv7neon_mmm_f32_8x6_generic 16 4 13 0.000004503173649426117\narmv7neon_mmm_f32_8x6_generic 16 4 19 0.000005734445256719681\narmv7neon_mmm_f32_8x4_cortexa7 24 4 8 0.000003952435244053743\narmv7neon_mmm_f32_8x6_cortexa9 9 32 6 0.000003904765036328731\narmv7neon_mmm_f32_8x6_cortexa7 23 4 13 0.000007856276222270553\narmv7neon_mmm_f32_8x6_cortexa9 15 4 6 0.000002182843988681052\narmv7neon_mmm_f32_8x6_generic 23 4 18 0.000007195841362691159\narmv7neon_mmm_f32_8x4_cortexa7 16 32 12 0.000010255406371017558\narmv7neon_mmm_f32_8x4_cortexa9 25 128 8 0.0000309762014941751\narmv7neon_mmm_f32_8x6_cortexa7 25 128 17 0.00008204444005539351\narmv7neon_mmm_f32_8x6_cortexa7 17 4 6 0.0000026268622339640763\narmv7neon_mmm_f32_8x4_cortexa9 15 32 9 0.000008859604622252251\ngeneric_f32_4x4 5 32 9 0.000008264557275071169\ngeneric_f32_4x4 13 32 5 0.000010823964577863127\ngeneric_f32_4x4 5 4 3 0.000001851289980714991\narmv7neon_mmm_f32_8x4_generic 15 128 4 0.000008225264190077282\narmv7neon_mmm_f32_8x6_generic 17 32 18 0.000015295586544658026\narmv7neon_mmm_f32_8x6_cortexa9 9 4 18 0.000004572833723975051\narmv7neon_mmm_f32_8x4_cortexa7 15 4 13 0.000006075307141015665\narmv7neon_mmm_f32_8x6_cortexa7 17 32 19 0.000025630924279728742\ngeneric_f32_4x4 7 32 7 0.0000058385014382941885\narmv7neon_mmm_f32_8x4_cortexa9 16 128 7 0.000016054999854639244\narmv7neon_mmm_f32_8x4_cortexa7 25 4 4 0.0000029326008363454604\narmv7neon_mmm_f32_8x6_cortexa7 16 128 12 0.000027204121533574832\narmv7neon_mmm_f32_8x4_generic 15 128 3 0.000008426038232256742\narmv7neon_mmm_f32_8x4_cortexa9 7 32 5 0.0000033516027030490246\narmv7neon_mmm_f32_8x4_cortexa7 16 4 7 0.0000032624014554442194\narmv7neon_mmm_f32_8x4_cortexa9 16 128 3 0.000008462143402947945\narmv7neon_mmm_f32_8x4_generic 25 4 7 0.000005557466852852917\narmv7neon_mmm_f32_8x4_generic 23 32 4 0.000004495607981128392\narmv7neon_mmm_f32_8x4_cortexa9 15 4 12 0.000004291414852711907\ngeneric_f32_4x4 4 4 13 0.0000027727007811134473\narmv7neon_mmm_f32_8x4_generic 16 4 8 0.0000025796442872451462\narmv7neon_mmm_f32_8x4_cortexa9 23 4 9 0.000006179316744537218\narmv7neon_mmm_f32_8x4_cortexa7 25 32 3 0.000007764249905705397\narmv7neon_mmm_f32_8x6_generic 17 32 7 0.000010687826852062675\ngeneric_f32_4x4 13 4 7 0.000005399817435719611\ngeneric_f32_4x4 12 4 4 0.0000021094685699886824\narmv7neon_mmm_f32_8x6_cortexa9 7 4 17 0.0000034025163658270913\ngeneric_f32_4x4 4 4 9 0.0000022359497586694876\narmv7neon_mmm_f32_8x6_cortexa9 17 128 17 0.00004660020476545699\narmv7neon_mmm_f32_8x4_generic 15 4 12 0.00000428385702551851\narmv7neon_mmm_f32_8x4_generic 9 4 11 0.000004185007110545824\narmv7neon_mmm_f32_8x6_cortexa9 24 128 18 0.000045657972165917084\narmv7neon_mmm_f32_8x6_cortexa9 15 4 19 0.000007027336593004323\narmv7neon_mmm_f32_8x4_cortexa9 9 128 9 0.00002367365046977455\narmv7neon_mmm_f32_8x4_cortexa9 16 4 9 0.000003960113146754688\narmv7neon_mmm_f32_8x4_cortexa7 8 32 9 0.000005561279017749759\narmv7neon_mmm_f32_8x4_cortexa9 23 32 3 0.000004897369194486712\narmv7neon_mmm_f32_8x4_cortexa9 16 32 3 0.0000034377989421061634\narmv7neon_mmm_f32_8x6_generic 25 32 5 0.00000775592482259445\narmv7neon_mmm_f32_8x4_cortexa9 9 128 11 0.000023733495515623523\narmv7neon_mmm_f32_8x4_generic 23 32 7 0.000008935361824557136\narmv7neon_mmm_f32_8x4_cortexa7 7 128 9 0.00001664390737173506\ngeneric_f32_4x4 5 4 8 0.0000028477215638418112\narmv7neon_mmm_f32_8x6_generic 23 4 19 0.000009557906532901389\narmv7neon_mmm_f32_8x6_cortexa9 16 128 12 0.000020636825667982075\narmv7neon_mmm_f32_8x6_generic 9 32 11 0.000007421682594966975\narmv7neon_mmm_f32_8x4_generic 23 128 13 0.000047353976390946026\narmv7neon_mmm_f32_8x4_cortexa7 17 4 7 0.000004622886247456753\narmv7neon_mmm_f32_8x6_cortexa9 8 128 5 0.000005754129281131009\narmv7neon_mmm_f32_8x4_cortexa7 9 128 11 0.000032319021391833177\narmv7neon_mmm_f32_8x4_cortexa9 24 4 9 0.000005698490674755626\narmv7neon_mmm_f32_8x6_generic 17 4 18 0.000006326121915439696\narmv7neon_mmm_f32_8x4_cortexa9 16 128 12 0.000023196293109823396\narmv7neon_mmm_f32_8x4_cortexa9 25 32 9 0.000016534317998128363\narmv7neon_mmm_f32_8x6_cortexa7 24 4 5 0.0000032362274636792855\narmv7neon_mmm_f32_8x6_cortexa7 9 32 13 0.000013201464669659568\narmv7neon_mmm_f32_8x4_generic 25 128 5 0.000031432252054608495\narmv7neon_mmm_f32_8x4_cortexa7 16 32 8 0.000007004694197132091\narmv7neon_mmm_f32_8x4_cortexa7 25 128 8 0.00004234315278499361\narmv7neon_mmm_f32_8x6_cortexa9 25 128 11 0.00004162498921103509\narmv7neon_mmm_f32_8x4_cortexa9 17 4 11 0.000005990145971795821\narmv7neon_mmm_f32_8x6_generic 7 128 5 0.000005711586442858516\ngeneric_f32_4x4 5 32 7 0.000005745084475312582\narmv7neon_mmm_f32_8x4_cortexa7 17 4 3 0.0000027778998365925647\narmv7neon_mmm_f32_8x6_generic 7 32 17 0.000006323975084622719\narmv7neon_mmm_f32_8x4_cortexa7 24 32 4 0.0000053627426905310905\narmv7neon_mmm_f32_8x6_cortexa9 7 128 13 0.000016465054999968026\narmv7neon_mmm_f32_8x6_cortexa9 25 32 17 0.000021029276181809713\narmv7neon_mmm_f32_8x6_generic 7 4 18 0.0000035040816774469106\narmv7neon_mmm_f32_8x6_cortexa7 17 128 12 0.00004084848200658872\narmv7neon_mmm_f32_8x6_cortexa7 16 128 11 0.000027707012596319906\narmv7neon_mmm_f32_8x6_cortexa7 8 128 12 0.0000138330433065325\narmv7neon_mmm_f32_8x4_cortexa9 7 32 9 0.000004837549683885907\narmv7neon_mmm_f32_8x4_generic 7 32 7 0.000003409099607922408\narmv7neon_mmm_f32_8x6_cortexa7 16 4 5 0.0000023317089411955496\narmv7neon_mmm_f32_8x6_cortexa7 7 32 17 0.000007581121899800583\narmv7neon_mmm_f32_8x4_cortexa7 16 4 9 0.0000042985642379541856\narmv7neon_mmm_f32_8x6_generic 16 4 7 0.0000032960593218992506\narmv7neon_mmm_f32_8x4_generic 15 32 5 0.0000060704297110345465\narmv7neon_mmm_f32_8x6_cortexa9 24 32 6 0.00000538028834244819\narmv7neon_mmm_f32_8x6_generic 25 32 18 0.0000200993477092098\narmv7neon_mmm_f32_8x6_generic 23 128 6 0.00001591506566691279\narmv7neon_mmm_f32_8x6_generic 16 32 13 0.00001049572423866497\narmv7neon_mmm_f32_8x4_cortexa7 15 32 5 0.000007565304970371118\ngeneric_f32_4x4 12 32 7 0.000008258205556820225\narmv7neon_mmm_f32_8x4_cortexa7 17 4 9 0.000006349279290785205\ngeneric_f32_4x4 8 128 5 0.000014938883570524155\narmv7neon_mmm_f32_8x4_generic 15 32 3 0.0000034015953691065083\narmv7neon_mmm_f32_8x6_cortexa7 7 4 18 0.000003692100644999123\ngeneric_f32_4x4 8 128 7 0.000015006901203342376\ngeneric_f32_4x4 4 4 3 0.0000012181206340328286\ngeneric_f32_4x4 12 32 5 0.000008206014399831754\narmv7neon_mmm_f32_8x6_cortexa9 8 128 19 0.000020717968248585576\ngeneric_f32_4x4 5 32 3 0.000003225094305304002\narmv7neon_mmm_f32_8x6_generic 24 4 18 0.000006021495896771489\narmv7neon_mmm_f32_8x6_cortexa7 16 128 13 0.00004093551587387655\narmv7neon_mmm_f32_8x4_cortexa9 15 128 8 0.00001604448990415154\narmv7neon_mmm_f32_8x6_generic 9 32 13 0.000010651318368732651\narmv7neon_mmm_f32_8x4_cortexa7 8 32 8 0.000003772107419321899\ngeneric_f32_4x4 9 32 3 0.000004621846936980697\narmv7neon_mmm_f32_8x4_cortexa9 16 32 5 0.000005906329928132943\narmv7neon_mmm_f32_8x6_cortexa9 16 4 6 0.000001808587040184933\narmv7neon_mmm_f32_8x6_cortexa9 9 32 13 0.000010778660548919894\narmv7neon_mmm_f32_8x4_cortexa9 16 32 4 0.0000030105150384631114\narmv7neon_mmm_f32_8x6_generic 17 4 17 0.000006838981837746129\narmv7neon_mmm_f32_8x6_cortexa7 7 128 19 0.000028499453971524397\narmv7neon_mmm_f32_8x6_cortexa7 23 32 17 0.000020319754403004648\narmv7neon_mmm_f32_8x6_generic 9 32 17 0.000010765407372129566\narmv7neon_mmm_f32_8x4_generic 23 4 13 0.000008003285656461567\narmv7neon_mmm_f32_8x4_generic 17 4 4 0.000002161502194915875\narmv7neon_mmm_f32_8x4_generic 23 4 11 0.000006323737070277622\narmv7neon_mmm_f32_8x4_generic 9 4 12 0.00000394067683542442\narmv7neon_mmm_f32_8x4_cortexa7 23 128 4 0.00001636523555605817\narmv7neon_mmm_f32_8x4_cortexa9 17 32 12 0.000012187305627907507\narmv7neon_mmm_f32_8x4_generic 25 32 5 0.000011305023296577953\narmv7neon_mmm_f32_8x4_cortexa7 23 128 13 0.0000641420493251813\narmv7neon_mmm_f32_8x6_cortexa9 17 4 13 0.0000067882601578195375\narmv7neon_mmm_f32_8x6_cortexa9 24 32 13 0.00001554482246687919\narmv7neon_mmm_f32_8x4_cortexa7 15 128 12 0.00003239866815806022\narmv7neon_mmm_f32_8x6_cortexa9 23 4 13 0.000007410621365009766\narmv7neon_mmm_f32_8x4_cortexa9 17 128 5 0.000023718649742852314\narmv7neon_mmm_f32_8x6_cortexa9 15 32 13 0.000011397983577281118\narmv7neon_mmm_f32_8x6_cortexa7 15 32 5 0.0000050262586648579474\narmv7neon_mmm_f32_8x4_cortexa9 16 128 9 0.000023527233248412315\narmv7neon_mmm_f32_8x6_cortexa7 17 4 17 0.000007344312997274551\narmv7neon_mmm_f32_8x6_cortexa7 15 128 13 0.00004174083709909147\narmv7neon_mmm_f32_8x4_cortexa9 24 128 11 0.00003518379984445077\narmv7neon_mmm_f32_8x6_cortexa7 15 4 6 0.0000022388984932072717\narmv7neon_mmm_f32_8x6_generic 17 32 11 0.000010862418538636322\narmv7neon_mmm_f32_8x6_cortexa9 17 128 19 0.00006205059984963943\narmv7neon_mmm_f32_8x6_cortexa7 24 128 7 0.00004110240285622754\narmv7neon_mmm_f32_8x6_generic 25 32 11 0.00001430805053910281\narmv7neon_mmm_f32_8x4_cortexa9 23 128 11 0.00003562503787212338\narmv7neon_mmm_f32_8x4_generic 8 128 13 0.00001577265144197705\narmv7neon_mmm_f32_8x4_cortexa9 15 32 3 0.0000034017274319352206\ngeneric_f32_4x4 13 32 3 0.000005959878460430609\narmv7neon_mmm_f32_8x4_cortexa9 8 32 9 0.000004432308412032044\narmv7neon_mmm_f32_8x4_cortexa7 8 4 5 0.0000018214760004994945\narmv7neon_mmm_f32_8x4_generic 16 4 5 0.0000029127359283609326\narmv7neon_mmm_f32_8x4_cortexa7 8 32 11 0.000005611833179795977\narmv7neon_mmm_f32_8x4_generic 17 128 12 0.00003481391410825688\narmv7neon_mmm_f32_8x6_cortexa7 17 4 19 0.00000928821589263059\narmv7neon_mmm_f32_8x6_generic 8 4 19 0.00000313513885546113\ngeneric_f32_4x4 11 32 11 0.000012240954381721343\narmv7neon_mmm_f32_8x4_cortexa9 9 128 8 0.000015834005248731945\narmv7neon_mmm_f32_8x6_generic 25 128 7 0.00004137887977916487\narmv7neon_mmm_f32_8x6_cortexa7 25 32 13 0.000025709475387022286\narmv7neon_mmm_f32_8x6_cortexa7 24 32 5 0.000007312970559712554\narmv7neon_mmm_f32_8x4_cortexa9 7 32 3 0.0000019121426586981612\narmv7neon_mmm_f32_8x4_cortexa9 8 4 12 0.0000020507155861457753\narmv7neon_mmm_f32_8x4_cortexa9 25 128 4 0.000015694825557140796\narmv7neon_mmm_f32_8x6_cortexa9 9 4 17 0.0000048654285121987666\narmv7neon_mmm_f32_8x4_generic 15 32 13 0.000011594181812430167\ngeneric_f32_4x4 8 4 5 0.0000029234671714958714\narmv7neon_mmm_f32_8x6_generic 25 128 18 0.00006398210071667638\narmv7neon_mmm_f32_8x4_generic 25 32 7 0.000011481869355677313\narmv7neon_mmm_f32_8x6_cortexa7 15 32 13 0.000013835762311238379\narmv7neon_mmm_f32_8x6_generic 7 32 7 0.000004270239691930375\narmv7neon_mmm_f32_8x4_cortexa9 24 4 12 0.0000052513903304867065\narmv7neon_mmm_f32_8x4_cortexa7 7 128 13 0.000022090489564676277\narmv7neon_mmm_f32_8x4_cortexa7 9 32 7 0.0000074510499136902\narmv7neon_mmm_f32_8x4_generic 15 128 11 0.00002405185440078953\narmv7neon_mmm_f32_8x6_cortexa7 24 128 18 0.00006059904552298362\narmv7neon_mmm_f32_8x4_cortexa9 24 4 8 0.0000036249029546823233\narmv7neon_mmm_f32_8x4_generic 17 4 5 0.000004186211638893127\narmv7neon_mmm_f32_8x4_cortexa7 15 128 5 0.00002190540588603946\narmv7neon_mmm_f32_8x6_cortexa7 9 32 5 0.000004913938240718097\narmv7neon_mmm_f32_8x4_cortexa7 25 128 4 0.000021428570118948462\narmv7neon_mmm_f32_8x6_generic 9 4 17 0.000004759335274456197\narmv7neon_mmm_f32_8x4_cortexa9 23 32 5 0.000008792076478618836\narmv7neon_mmm_f32_8x4_cortexa9 17 128 13 0.00004665611088212803\ngeneric_f32_4x4 11 128 12 0.00003275274568553372\narmv7neon_mmm_f32_8x6_cortexa7 7 128 12 0.000014620372931092478\narmv7neon_mmm_f32_8x6_generic 24 128 13 0.000046299674326571746\ngeneric_f32_4x4 12 4 9 0.000005632703760211813\narmv7neon_mmm_f32_8x6_cortexa7 15 32 6 0.000004965054605236343\narmv7neon_mmm_f32_8x6_cortexa9 7 128 17 0.000016538122105202102\narmv7neon_mmm_f32_8x6_cortexa9 17 128 7 0.00003112401925909139\narmv7neon_mmm_f32_8x4_cortexa7 7 32 5 0.0000040832664009078\narmv7neon_mmm_f32_8x4_cortexa9 25 4 3 0.000003352094433744027\narmv7neon_mmm_f32_8x4_cortexa7 17 4 4 0.0000023752236037367475\narmv7neon_mmm_f32_8x4_cortexa9 8 4 4 0.000000986252516977325\narmv7neon_mmm_f32_8x6_cortexa7 9 4 5 0.0000022147026524486433\narmv7neon_mmm_f32_8x6_cortexa9 24 4 13 0.000006569367159583456\narmv7neon_mmm_f32_8x6_cortexa9 25 32 13 0.000020818742897252734\narmv7neon_mmm_f32_8x4_cortexa9 15 32 12 0.000008751598473767715\narmv7neon_mmm_f32_8x4_cortexa7 7 4 4 0.000001249360695162341\narmv7neon_mmm_f32_8x6_generic 25 4 19 0.000011234098334898354\ngeneric_f32_4x4 7 4 9 0.000004262590318570047\ngeneric_f32_4x4 12 128 9 0.000032724926249473206\narmv7neon_mmm_f32_8x4_cortexa7 23 4 11 0.0000067800221845095914\narmv7neon_mmm_f32_8x4_cortexa7 23 4 13 0.00000857826635505948\narmv7neon_mmm_f32_8x4_generic 7 32 3 0.0000019119582227554076\narmv7neon_mmm_f32_8x6_cortexa9 25 4 12 0.000005714183543712814\ngeneric_f32_4x4 7 128 8 0.000014963178444176004\narmv7neon_mmm_f32_8x6_generic 23 4 11 0.000005289174016444985\narmv7neon_mmm_f32_8x4_generic 15 4 7 0.000003212917021733202\narmv7neon_mmm_f32_8x4_cortexa9 17 4 4 0.000002157304549786207\narmv7neon_mmm_f32_8x4_cortexa7 25 4 8 0.000005296217018377082\narmv7neon_mmm_f32_8x4_cortexa7 16 4 8 0.000002789622045679376\narmv7neon_mmm_f32_8x4_cortexa9 9 32 11 0.00000862398264754348\narmv7neon_mmm_f32_8x6_generic 16 4 18 0.000004182651246093671\narmv7neon_mmm_f32_8x4_cortexa9 7 4 7 0.0000019258225946178918\narmv7neon_mmm_f32_8x4_generic 7 32 13 0.0000063109264345399555\narmv7neon_mmm_f32_8x6_generic 15 32 7 0.000007649176449323901\narmv7neon_mmm_f32_8x4_cortexa7 9 4 13 0.00000566559429146675\ngeneric_f32_4x4 11 4 13 0.00000768804413911549\narmv7neon_mmm_f32_8x6_cortexa7 15 128 19 0.000055484906874277455\narmv7neon_mmm_f32_8x6_generic 8 128 19 0.000020785294814215668\narmv7neon_mmm_f32_8x4_cortexa7 23 32 7 0.000011137926376204002\narmv7neon_mmm_f32_8x4_cortexa9 25 32 4 0.000005634291322373054\narmv7neon_mmm_f32_8x4_cortexa7 16 4 11 0.0000043943657333441155\ngeneric_f32_4x4 3 128 4 0.000004206283247834624\narmv7neon_mmm_f32_8x6_cortexa9 23 32 17 0.000016635289447796483\narmv7neon_mmm_f32_8x6_cortexa7 23 4 5 0.0000032303127776050868\ngeneric_f32_4x4 7 128 7 0.000015111291354960824\narmv7neon_mmm_f32_8x6_cortexa7 8 128 19 0.000027387915918929853\narmv7neon_mmm_f32_8x4_cortexa7 24 32 9 0.000015681272955767534\narmv7neon_mmm_f32_8x6_cortexa7 15 32 7 0.000009363971820493224\narmv7neon_mmm_f32_8x6_generic 15 4 17 0.000005464908042174986\ngeneric_f32_4x4 11 4 4 0.000002266320385947867\narmv7neon_mmm_f32_8x4_cortexa7 7 4 5 0.0000019827556786453945\narmv7neon_mmm_f32_8x4_generic 7 4 5 0.0000018753720795687383\narmv7neon_mmm_f32_8x6_cortexa7 23 32 19 0.000026555363914009545\ngeneric_f32_4x4 12 32 11 0.000011904804943326893\ngeneric_f32_4x4 9 32 4 0.000004278605538448279\narmv7neon_mmm_f32_8x4_cortexa9 9 4 3 0.0000018374246302226002\narmv7neon_mmm_f32_8x4_generic 17 128 11 0.00003530676669688071\narmv7neon_mmm_f32_8x4_cortexa7 23 32 5 0.000011014184598781204\narmv7neon_mmm_f32_8x4_cortexa7 8 128 8 0.000010889817517963246\narmv7neon_mmm_f32_8x4_generic 24 128 5 0.000023658482563487358\narmv7neon_mmm_f32_8x4_generic 23 4 4 0.0000022865778486536074\narmv7neon_mmm_f32_8x6_cortexa7 16 32 7 0.000008963466531834949\narmv7neon_mmm_f32_8x4_cortexa7 16 128 11 0.00003220406926334493\narmv7neon_mmm_f32_8x4_cortexa9 7 4 9 0.000002612401333065891\narmv7neon_mmm_f32_8x6_generic 24 128 18 0.00004570910076801179\narmv7neon_mmm_f32_8x6_cortexa7 15 32 19 0.00001825949129411215\narmv7neon_mmm_f32_8x4_cortexa7 7 128 11 0.00001669033881154567\narmv7neon_mmm_f32_8x6_cortexa7 17 128 17 0.00006152323359147728\narmv7neon_mmm_f32_8x6_generic 23 4 12 0.0000049613667404571205\narmv7neon_mmm_f32_8x6_cortexa9 17 4 6 0.0000025304413562385254\narmv7neon_mmm_f32_8x6_generic 25 32 13 0.000020634077057855525\ngeneric_f32_4x4 3 128 8 0.000007895048382688004\narmv7neon_mmm_f32_8x6_generic 7 128 6 0.000005880420229157795\narmv7neon_mmm_f32_8x6_cortexa7 7 4 6 0.0000015435048498918165\narmv7neon_mmm_f32_8x6_cortexa9 15 4 12 0.0000038035548284711065\narmv7neon_mmm_f32_8x4_generic 16 128 13 0.00003105986968794038\narmv7neon_mmm_f32_8x6_generic 17 128 6 0.00001561934075445538\narmv7neon_mmm_f32_8x6_generic 23 32 5 0.000006038048456367719\ngeneric_f32_4x4 3 4 8 0.0000018887559388867697\narmv7neon_mmm_f32_8x6_cortexa7 17 128 5 0.000021122166031945488\narmv7neon_mmm_f32_8x4_cortexa7 17 4 12 0.000005984255865199454\narmv7neon_mmm_f32_8x6_cortexa9 8 4 17 0.00000262350755833462\narmv7neon_mmm_f32_8x6_generic 24 32 17 0.000015705211312591065\narmv7neon_mmm_f32_8x6_generic 23 32 7 0.00001103425525544337\narmv7neon_mmm_f32_8x6_generic 23 128 18 0.00004698363257869806\narmv7neon_mmm_f32_8x4_cortexa7 9 128 4 0.000010997242917041898\ngeneric_f32_4x4 12 32 9 0.000011855594903877275\narmv7neon_mmm_f32_8x4_cortexa7 8 32 4 0.000002107136592547487\narmv7neon_mmm_f32_8x4_cortexa7 9 32 4 0.0000038494164534325605\ngeneric_f32_4x4 8 32 4 0.0000029596097342538777\narmv7neon_mmm_f32_8x6_cortexa9 7 4 13 0.0000033183395006378194\narmv7neon_mmm_f32_8x4_cortexa9 7 128 5 0.00000837292374457436\narmv7neon_mmm_f32_8x6_cortexa7 9 32 12 0.000008879244649291164\narmv7neon_mmm_f32_8x6_cortexa9 23 32 5 0.00000608726564780247\ngeneric_f32_4x4 4 4 11 0.000002253269465041155\narmv7neon_mmm_f32_8x6_generic 15 4 12 0.000003737614965115773\narmv7neon_mmm_f32_8x4_cortexa7 8 32 13 0.000007179427212160366\narmv7neon_mmm_f32_8x6_generic 7 4 7 0.00000227244051714339\narmv7neon_mmm_f32_8x6_generic 7 128 13 0.000016430484681121403\narmv7neon_mmm_f32_8x4_cortexa9 25 4 13 0.000009867842979190694\narmv7neon_mmm_f32_8x4_cortexa9 24 32 7 0.000008714828341166468\narmv7neon_mmm_f32_8x6_generic 7 128 12 0.00001125899429533851\narmv7neon_mmm_f32_8x4_cortexa7 24 128 3 0.000016779227724494087\narmv7neon_mmm_f32_8x6_cortexa7 25 32 11 0.000017700250695390733\narmv7neon_mmm_f32_8x6_cortexa9 8 128 17 0.00001578057628845984\narmv7neon_mmm_f32_8x6_cortexa9 15 128 18 0.00003176376886582956\narmv7neon_mmm_f32_8x6_generic 7 32 18 0.000006481310483268657\narmv7neon_mmm_f32_8x6_generic 24 32 19 0.000020260301856480532\narmv7neon_mmm_f32_8x6_cortexa7 25 4 7 0.000006556992855989281\narmv7neon_mmm_f32_8x4_cortexa7 9 4 8 0.0000030004666340701197\narmv7neon_mmm_f32_8x6_generic 8 4 13 0.0000025029052985754286\narmv7neon_mmm_f32_8x4_cortexa7 16 32 13 0.00001386814401860724\narmv7neon_mmm_f32_8x4_generic 16 128 3 0.000008461514063719399\ngeneric_f32_4x4 8 32 5 0.000005666217550078829\narmv7neon_mmm_f32_8x4_generic 17 32 9 0.000012567178399696468\narmv7neon_mmm_f32_8x4_cortexa7 15 128 9 0.00003252514906200416\narmv7neon_mmm_f32_8x6_generic 25 128 13 0.00006413295578186804\narmv7neon_mmm_f32_8x6_generic 7 4 11 0.0000023405037923286433\ngeneric_f32_4x4 7 32 12 0.000008239110132919408\narmv7neon_mmm_f32_8x6_cortexa9 23 128 6 0.00001597150399238562\narmv7neon_mmm_f32_8x4_generic 25 4 13 0.000009863674729272788\narmv7neon_mmm_f32_8x4_cortexa7 16 128 5 0.00002172651909764639\narmv7neon_mmm_f32_8x6_cortexa7 15 32 12 0.000009468415752921276\narmv7neon_mmm_f32_8x6_cortexa9 24 4 11 0.000004968600626897013\narmv7neon_mmm_f32_8x4_generic 24 128 13 0.00004635289274725685\narmv7neon_mmm_f32_8x6_generic 23 128 5 0.000016196412852382603\narmv7neon_mmm_f32_8x6_cortexa7 16 32 13 0.000013024830178376121\narmv7neon_mmm_f32_8x4_cortexa9 9 32 3 0.0000033151175658832473\narmv7neon_mmm_f32_8x6_generic 7 4 13 0.000003265379138781528\narmv7neon_mmm_f32_8x4_cortexa9 17 32 13 0.000016459192383496957\narmv7neon_mmm_f32_8x6_cortexa7 24 32 13 0.000019272536127977423\ngeneric_f32_4x4 4 32 3 0.0000019007760051192194\ngeneric_f32_4x4 9 128 8 0.000021954328754919918\narmv7neon_mmm_f32_8x4_cortexa7 8 32 12 0.000005371382475600821\ngeneric_f32_4x4 7 4 3 0.000001903414095718297\narmv7neon_mmm_f32_8x6_generic 24 4 7 0.000004666010098240403\narmv7neon_mmm_f32_8x4_cortexa9 16 32 8 0.000005563656358158919\ngeneric_f32_4x4 3 32 9 0.000004602770059818119\narmv7neon_mmm_f32_8x4_generic 9 32 13 0.000011194068458304182\narmv7neon_mmm_f32_8x4_cortexa9 15 32 11 0.000008943973907120027\ngeneric_f32_4x4 11 128 7 0.000022328923764966492\narmv7neon_mmm_f32_8x6_generic 8 128 5 0.000005731544283554465\narmv7neon_mmm_f32_8x6_cortexa9 8 128 11 0.00001076946555010939\narmv7neon_mmm_f32_8x4_cortexa7 7 128 5 0.000011229094508072489\narmv7neon_mmm_f32_8x4_cortexa9 15 128 9 0.000023961524252030835\ngeneric_f32_4x4 11 128 5 0.000022274598932900346\ngeneric_f32_4x4 5 128 3 0.000007857636386132928\ngeneric_f32_4x4 4 128 7 0.00000774651849385228\narmv7neon_mmm_f32_8x4_cortexa9 15 128 5 0.000016175167728379695\narmv7neon_mmm_f32_8x6_cortexa7 17 4 5 0.0000031159889361188713\ngeneric_f32_4x4 12 4 11 0.000005685376438998431\narmv7neon_mmm_f32_8x4_cortexa7 8 32 5 0.000003934735618425319\ngeneric_f32_4x4 4 32 4 0.0000017328412461691005\narmv7neon_mmm_f32_8x4_cortexa7 8 128 11 0.000016332637899779713\narmv7neon_mmm_f32_8x6_cortexa9 15 32 17 0.000011527836971780865\narmv7neon_mmm_f32_8x4_generic 15 4 9 0.000004398255246018968\narmv7neon_mmm_f32_8x4_cortexa9 15 32 8 0.000005965121319547414\narmv7neon_mmm_f32_8x6_cortexa7 8 4 11 0.0000020902150325449643\narmv7neon_mmm_f32_8x6_cortexa7 15 4 13 0.000005648201530441972\narmv7neon_mmm_f32_8x6_cortexa9 23 128 18 0.00004690308298866514\narmv7neon_mmm_f32_8x6_cortexa7 17 128 18 0.00006100420785992372\ngeneric_f32_4x4 3 32 3 0.000001876030465879534\ngeneric_f32_4x4 4 128 8 0.000007586370052411088\narmv7neon_mmm_f32_8x6_cortexa9 8 128 18 0.000015532022218663232\narmv7neon_mmm_f32_8x4_cortexa7 17 4 11 0.000006454744732596089\narmv7neon_mmm_f32_8x6_cortexa9 15 4 11 0.0000038999665208551116\narmv7neon_mmm_f32_8x6_cortexa9 25 128 17 0.00006218409021295646\narmv7neon_mmm_f32_8x6_cortexa7 9 128 7 0.000027592791728379908\narmv7neon_mmm_f32_8x4_cortexa7 17 128 7 0.0000324174579838398\narmv7neon_mmm_f32_8x6_generic 17 32 13 0.00001566115346007812\ngeneric_f32_4x4 9 4 4 0.0000022019063671444208\narmv7neon_mmm_f32_8x4_cortexa9 17 128 11 0.0000353036171900936\narmv7neon_mmm_f32_8x4_cortexa7 17 32 12 0.0000154170992202297\ngeneric_f32_4x4 4 4 12 0.0000020915219079462592\narmv7neon_mmm_f32_8x4_generic 16 4 3 0.000001964189345127758\ngeneric_f32_4x4 11 4 3 0.0000025875707471480883\narmv7neon_mmm_f32_8x4_cortexa7 24 32 8 0.000010240674992864977\narmv7neon_mmm_f32_8x4_cortexa7 24 4 7 0.00000464383472294094\narmv7neon_mmm_f32_8x6_generic 24 128 17 0.00004653762565754179\narmv7neon_mmm_f32_8x4_cortexa9 9 128 5 0.000015984472800286198\narmv7neon_mmm_f32_8x6_cortexa9 17 32 13 0.000015793322899399953\narmv7neon_mmm_f32_8x4_cortexa7 23 128 9 0.00004832080937818461\narmv7neon_mmm_f32_8x6_generic 17 4 12 0.0000043886752215018795\narmv7neon_mmm_f32_8x6_generic 24 4 17 0.000006718970310010203\narmv7neon_mmm_f32_8x6_cortexa9 23 4 12 0.000005053037256901505\narmv7neon_mmm_f32_8x4_generic 25 128 13 0.00006314133455535656\ngeneric_f32_4x4 9 128 7 0.000022234980219629062\narmv7neon_mmm_f32_8x6_cortexa7 7 128 7 0.000014376478927807395\narmv7neon_mmm_f32_8x6_cortexa7 16 128 5 0.0000143212403106337\narmv7neon_mmm_f32_8x6_generic 25 32 6 0.000007045905360966285\narmv7neon_mmm_f32_8x6_generic 25 32 17 0.000020892213993308648\narmv7neon_mmm_f32_8x6_cortexa9 24 32 7 0.000010711106258433335\narmv7neon_mmm_f32_8x4_cortexa7 15 32 7 0.000007644594488957809\narmv7neon_mmm_f32_8x4_generic 9 32 7 0.0000059612924557323865\narmv7neon_mmm_f32_8x4_cortexa7 24 32 5 0.000010813567327083286\ngeneric_f32_4x4 11 32 9 0.000012181640310257489\narmv7neon_mmm_f32_8x4_generic 8 32 11 0.0000044856528446547376\narmv7neon_mmm_f32_8x6_cortexa7 8 128 5 0.00000740698393210008\narmv7neon_mmm_f32_8x6_generic 9 4 13 0.00000467415217244372\narmv7neon_mmm_f32_8x4_cortexa9 8 32 5 0.0000031557846756887957\narmv7neon_mmm_f32_8x6_generic 9 32 6 0.000003833293115622011\ngeneric_f32_4x4 5 128 11 0.000022197811800866368\narmv7neon_mmm_f32_8x4_cortexa7 8 4 8 0.000001630228451378558\narmv7neon_mmm_f32_8x6_cortexa7 7 128 18 0.00002168993830362326\narmv7neon_mmm_f32_8x4_generic 16 4 7 0.000003012988093329698\narmv7neon_mmm_f32_8x6_generic 9 32 5 0.0000040586320817732015\narmv7neon_mmm_f32_8x4_generic 8 128 11 0.000012020324272970633\narmv7neon_mmm_f32_8x4_cortexa9 9 4 13 0.00000525401614258308\narmv7neon_mmm_f32_8x6_cortexa9 17 32 5 0.000005959198344084862\narmv7neon_mmm_f32_8x6_cortexa9 25 128 7 0.00004136188393626292\narmv7neon_mmm_f32_8x6_cortexa7 8 4 6 0.0000011677036715350285\narmv7neon_mmm_f32_8x6_cortexa7 7 32 11 0.000005167860829086469\narmv7neon_mmm_f32_8x4_generic 24 4 11 0.000005847812768700571\narmv7neon_mmm_f32_8x4_generic 17 128 8 0.0000233987853798507\narmv7neon_mmm_f32_8x4_cortexa9 7 128 8 0.000008435297227106425\narmv7neon_mmm_f32_8x4_generic 8 4 4 0.000000981928574441578\ngeneric_f32_4x4 13 32 8 0.000010469226231367714\ngeneric_f32_4x4 12 4 7 0.000004168142600643075\narmv7neon_mmm_f32_8x6_generic 25 128 5 0.000021400005561991285\narmv7neon_mmm_f32_8x6_cortexa9 23 128 19 0.00006309986803011645\narmv7neon_mmm_f32_8x6_cortexa7 17 32 5 0.000007188792393610533\narmv7neon_mmm_f32_8x6_cortexa9 7 32 17 0.00000637568429598606\narmv7neon_mmm_f32_8x6_cortexa9 25 32 19 0.000027309010551769435\narmv7neon_mmm_f32_8x4_cortexa7 7 32 8 0.000004145572586004413\narmv7neon_mmm_f32_8x4_cortexa9 24 32 9 0.000012468417684719209\narmv7neon_mmm_f32_8x6_cortexa7 23 128 7 0.00004157442562492112\n"
  },
  {
    "path": "linalg/src/arm32.rs",
    "content": "use std::{env, fs};\npub mod armv7neon;\nmod armvfpv2;\nmod cortex_a7;\nmod cortex_a9;\nuse armv7neon::*;\n\nuse crate::frame::element_wise::ElementWiseKer;\n\nuse crate::Ops;\n\nfn has_neon_cpuinfo() -> std::io::Result<bool> {\n    let cpu_info = fs::read_to_string(\"/proc/cpuinfo\")?;\n    let neon = cpu_info.split(\"\\n\").any(|line| {\n        line.starts_with(\"Features\") && (line.contains(\"neon\") || line.contains(\"asimd\"))\n    });\n    Ok(neon)\n}\n\nfn cpu_part() -> Option<usize> {\n    fs::read_to_string(\"/proc/cpuinfo\").ok().and_then(|cpuinfo| {\n        cpuinfo\n            .lines()\n            .find(|line| line.starts_with(\"CPU part\"))\n            .and_then(|s| s.trim().split_whitespace().last())\n            .and_then(|s| s.strip_prefix(\"0x\"))\n            .and_then(|s| usize::from_str_radix(s, 16).ok())\n    })\n}\n\nfn has_neon() -> bool {\n    if let Ok(v) = env::var(\"TRACT_CPU_ARM32_NEON\") {\n        return v == \"true\" || v == \"1\";\n    }\n    has_neon_cpuinfo().unwrap_or(false)\n}\n\npub fn plug(ops: &mut Ops) {\n    if has_neon() {\n        log::info!(\"armv7neon activated (smmm, ssigmoid), stanh)\");\n        armv7neon::plug(ops);\n\n        let cpu = cpu_part().unwrap_or(0);\n\n        fn prefer_8x4(_m: Option<usize>, _k: Option<usize>, n: Option<usize>) -> bool {\n            n.map(|n| n % 4 == 0 && n % 6 != 0 && n <= 12).unwrap_or(false)\n        }\n\n        let cost_managed_impls = vec![\n            armv7neon_mmm_f32_8x4_cortexa7.mmm(),\n            armv7neon_mmm_f32_8x6_cortexa7.mmm(),\n            armv7neon_mmm_f32_8x4_cortexa9.mmm(),\n            armv7neon_mmm_f32_8x6_cortexa9.mmm(),\n            armv7neon_mmm_f32_8x4_generic.mmm(),\n            armv7neon_mmm_f32_8x6_generic.mmm(),\n            crate::generic::mmm::generic_f32_4x4.mmm(),\n        ];\n        ops.mmv_f32 = match cpu {\n            0xc07 => Box::new(|_, _| armv7neon::armv7neon_mmm_f32_32x1_cortexa7.mmm()),\n            0xc09 => Box::new(|_, _| armv7neon::armv7neon_mmm_f32_32x1_cortexa9.mmm()),\n            _ => Box::new(|_, _| armv7neon::armv7neon_mmm_f32_32x1_generic.mmm()),\n        };\n\n        ops.mmm_f32 = match cpu {\n            0xc07 => {\n                let model = cortex_a7::model();\n                Box::new(move |m, k, n| model.pick(&cost_managed_impls, m, k, n))\n            }\n            0xc09 => {\n                let model = cortex_a9::model();\n                Box::new(move |m, k, n| model.pick(&cost_managed_impls, m, k, n))\n            }\n            _ => Box::new(|m, k, n| {\n                if prefer_8x4(m, k, n) {\n                    armv7neon::armv7neon_mmm_f32_8x4_generic.mmm()\n                } else {\n                    armv7neon::armv7neon_mmm_f32_8x6_generic.mmm()\n                }\n            }),\n        };\n        ops.qmmm_i32 = Box::new(|_, _, _| armv7neon::armv7neon_mmm_i32_8x4.mmm());\n        ops.qmmv_i32 = Box::new(|_, _| armv7neon::armv7neon_mmm_i32_32x1.mmm());\n        ops.sigmoid_f32 = Box::new(|| armv7neon_sigmoid_f32_4n::ew());\n        ops.tanh_f32 = Box::new(|| armv7neon_tanh_f32_4n::ew());\n    } else {\n        armvfpv2::plug(ops);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn may_have_neon() {\n        println!(\"Has neon ? {:?}\", has_neon());\n        if let Ok(neon) = env::var(\"TRACT_CPU_EXPECT_ARM32_NEON\") {\n            assert_eq!(neon == \"true\", has_neon());\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm64/apple_amx.rs",
    "content": "use crate::Ops;\nuse crate::frame::mmm::ImplementationQuality::ManuallyOptimized;\nuse crate::mmm::*;\nuse tract_data::prelude::*;\n\nuse super::has_amx;\n\nconst AMX: fn() -> bool = crate::arm64::has_amx;\nconst CAN_FUSE: fn(&FusedSpec) -> bool = |f| !matches!(f, &FusedSpec::LeakyRelu(_));\n\nMMMExternKernel!(apple_amx_mmm_f32_32x32<f32>(32, 32)@(128, 128) where(AMX) can_fuse(CAN_FUSE) quality(ManuallyOptimized));\nMMMExternKernel!(apple_amx_mmm_f32_32x1<f32>(32, 1)@(128, 128) where(AMX) can_fuse(CAN_FUSE) quality(ManuallyOptimized));\nMMMExternKernel!(apple_amx_mmm_f16_64x32<f16>(64, 32)@(128, 128) where(AMX) can_fuse(CAN_FUSE) quality(ManuallyOptimized));\nMMMExternKernel!(apple_amx_mmm_f16_64x1<f16>(64, 1)@(128, 128) where(AMX) can_fuse(CAN_FUSE) quality(ManuallyOptimized));\n\npub fn plug(ops: &mut Ops) {\n    if has_amx() {\n        log::info!(\"AMX optimisation activated\");\n        ops.mmm_f16 = Box::new(|_, _, _| apple_amx_mmm_f16_64x32.mmm());\n        ops.mmm_f32 = Box::new(|_, _, _| apple_amx_mmm_f32_32x32.mmm());\n        ops.mmv_f16 = Box::new(|_, _| apple_amx_mmm_f16_64x1.mmm());\n        ops.mmv_f32 = Box::new(|_, _| apple_amx_mmm_f32_32x1.mmm());\n        ops.mmm_impls.extend_from_slice(&[\n            apple_amx_mmm_f32_32x32.mmm(),\n            apple_amx_mmm_f32_32x1.mmm(),\n            apple_amx_mmm_f16_64x32.mmm(),\n            apple_amx_mmm_f16_64x1.mmm(),\n        ]);\n    } else {\n        log::info!(\"No AMX optimisation\");\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64fp16/by_scalar.rs",
    "content": "use crate::f16;\n\nby_scalar_impl_wrap!(\n    f16,\n    arm64fp16_mul_by_scalar_f16_32n,\n    32,\n    4,\n    f16,\n    fn run(buf: &mut [f16], s: f16) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(buf: &mut [f16], s: f16) {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                std::arch::asm!(\"\n            dup v0.8h, v0.h[0]\n            2:\n                ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}]\n                fmul v4.8h, v4.8h, v0.8h\n                fmul v5.8h, v5.8h, v0.8h\n                fmul v6.8h, v6.8h, v0.8h\n                fmul v7.8h, v7.8h, v0.8h\n                st1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}], 64\n                subs {len}, {len}, 32\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s.to_bits(),\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n            }\n        }\n        unsafe { run(buf, s) }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    arm64fp16_add_by_scalar_f16_32n,\n    32,\n    4,\n    f16,\n    fn run(buf: &mut [f16], s: f16) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(buf: &mut [f16], s: f16) {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                std::arch::asm!(\"\n            dup v0.8h, v0.h[0]\n            2:\n                ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}]\n                fadd v4.8h, v4.8h, v0.8h\n                fadd v5.8h, v5.8h, v0.8h\n                fadd v6.8h, v6.8h, v0.8h\n                fadd v7.8h, v7.8h, v0.8h\n                st1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}], 64\n                subs {len}, {len}, 32\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s.to_bits(),\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n            }\n        }\n        unsafe { run(buf, s) }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    arm64fp16_sub_by_scalar_f16_32n,\n    32,\n    4,\n    f16,\n    fn run(buf: &mut [f16], s: f16) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(buf: &mut [f16], s: f16) {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                std::arch::asm!(\"\n            dup v0.8h, v0.h[0]\n            2:\n                ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}]\n                fsub v4.8h, v4.8h, v0.8h\n                fsub v5.8h, v5.8h, v0.8h\n                fsub v6.8h, v6.8h, v0.8h\n                fsub v7.8h, v7.8h, v0.8h\n                st1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}], 64\n                subs {len}, {len}, 32\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s.to_bits(),\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n            }\n        }\n        unsafe { run(buf, s) }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    arm64fp16_subf_by_scalar_f16_32n,\n    32,\n    4,\n    f16,\n    fn run(buf: &mut [f16], s: f16) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(buf: &mut [f16], s: f16) {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                std::arch::asm!(\"\n            dup v0.8h, v0.h[0]\n            2:\n                ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}]\n                fsub v4.8h, v0.8h, v4.8h\n                fsub v5.8h, v0.8h, v5.8h\n                fsub v6.8h, v0.8h, v6.8h\n                fsub v7.8h, v0.8h, v7.8h\n                st1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}], 64\n                subs {len}, {len}, 32\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s.to_bits(),\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n            }\n        }\n        unsafe { run(buf, s) }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    arm64fp16_min_by_scalar_f16_32n,\n    32,\n    4,\n    f16,\n    fn run(buf: &mut [f16], s: f16) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(buf: &mut [f16], s: f16) {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                std::arch::asm!(\"\n            dup v0.8h, v0.h[0]\n            2:\n                ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}]\n                fmin v4.8h, v4.8h, v0.8h\n                fmin v5.8h, v5.8h, v0.8h\n                fmin v6.8h, v6.8h, v0.8h\n                fmin v7.8h, v7.8h, v0.8h\n                st1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}], 64\n                subs {len}, {len}, 32\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s.to_bits(),\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n            }\n        }\n        unsafe { run(buf, s) }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    arm64fp16_max_by_scalar_f16_32n,\n    32,\n    4,\n    f16,\n    fn run(buf: &mut [f16], s: f16) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(buf: &mut [f16], s: f16) {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                std::arch::asm!(\"\n            dup v0.8h, v0.h[0]\n            2:\n                ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}]\n                fmax v4.8h, v4.8h, v0.8h\n                fmax v5.8h, v5.8h, v0.8h\n                fmax v6.8h, v6.8h, v0.8h\n                fmax v7.8h, v7.8h, v0.8h\n                st1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}], 64\n                subs {len}, {len}, 32\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s.to_bits(),\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n            }\n        }\n        unsafe { run(buf, s) }\n    }\n);\n\n#[cfg(test)]\nmod test_arm64fp16_mul_by_scalar_f16_32n {\n    use super::*;\n    by_scalar_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_mul_by_scalar_f16_32n,\n        |a, b| a * b\n    );\n    by_scalar_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_add_by_scalar_f16_32n,\n        |a, b| a + b\n    );\n    by_scalar_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_sub_by_scalar_f16_32n,\n        |a, b| a - b\n    );\n    by_scalar_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_subf_by_scalar_f16_32n,\n        |a, b| b - a\n    );\n    by_scalar_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_min_by_scalar_f16_32n,\n        |a, b| a.min(b)\n    );\n    by_scalar_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_max_by_scalar_f16_32n,\n        |a, b| a.max(b)\n    );\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64fp16/leaky_relu.rs",
    "content": "use tract_data::internal::f16;\n\new_impl_wrap!(\n    f16,\n    arm64fp16_leaky_relu_f16_16n,\n    16,\n    8,\n    f16,\n    #[inline(never)]\n    fn run(buf: &mut [f16], alpha: f16) {\n        assert!(buf.len() % 8 == 0);\n        assert!(buf.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(buf: &mut [f16], alpha: f16) {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                std::arch::asm!(\"\n                    dup v0.8h, {alpha:v}.h[0]\n                    dup v1.8h, {one:v}.h[0]\n                    2:\n                        ldp q3, q4, [{ptr}]\n\n                        fcmgt v5.8h, v3.8h, #0.0\n                        fcmgt v6.8h, v4.8h, #0.0\n                        bsl   v5.16b, v1.16b, v0.16b\n                        bsl   v6.16b, v1.16b, v0.16b\n                        fmul  v3.8h, v3.8h, v5.8h\n                        fmul  v4.8h, v4.8h, v6.8h\n\n                        stp q3, q4, [{ptr}], #32\n                        subs {len}, {len}, 16\n                        bne 2b\n                \",\n                one = in(vreg) f16::from_f32(1.0f32).to_bits(),\n                alpha = in(vreg) alpha.to_bits(),\n                len = inout(reg) len => _,\n                ptr = inout(reg) ptr => _,\n                out(\"v0\") _,\n                out(\"v1\") _,\n                out(\"q3\") _,\n                out(\"q4\") _,\n                out(\"q5\") _,\n                out(\"q6\") _,\n                );\n            }\n        }\n        unsafe { run(buf, alpha) }\n    }\n);\n\n#[cfg(test)]\npub mod test_arm64simd_leaky_relu_f16_16n {\n    use super::*;\n    leaky_relu_frame_tests!(crate::arm64::has_fp16(), f16, arm64fp16_leaky_relu_f16_16n);\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64fp16/max.rs",
    "content": "use tract_data::half::f16;\n\nreduce_impl_wrap!(\n    f16,\n    arm64fp16_max_f16_32n,\n    32,\n    8,\n    (),\n    f16::MIN,\n    #[inline(never)]\n    fn run(buf: &[f16], _: ()) -> f16 {\n        assert!(buf.len() % 32 == 0);\n        assert!(buf.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(buf: &[f16]) -> f16 {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                let mut out: u16;\n                std::arch::asm!(\"\n                ins v0.h[0], {min:w}\n                dup v0.8h, v0.h[0]\n                dup v1.8h, v0.h[0]\n                dup v2.8h, v0.h[0]\n                dup v3.8h, v0.h[0]\n\n                2:\n                    ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}], 64\n                    fmax v0.8h, v0.8h, v4.8h\n                    fmax v1.8h, v1.8h, v5.8h\n                    fmax v2.8h, v2.8h, v6.8h\n                    fmax v3.8h, v3.8h, v7.8h\n\n                    subs {len}, {len}, 32\n                    bne 2b\n\n                fmax v0.8h, v0.8h, v1.8h\n                fmax v2.8h, v2.8h, v3.8h\n                fmax v0.8h, v0.8h, v2.8h\n                fmaxv h0, v0.8h\n                \",\n                // using v0 as inout triggers https://github.com/rust-lang/rust/issues/120374\n                min = in(reg) f16::MIN.to_bits(),\n                ptr = inout(reg) ptr => _,\n                len = inout(reg) len => _,\n                out(\"v0\") out, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n                out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n                f16::from_bits(out)\n            }\n        }\n        unsafe { run(buf) }\n    },\n    #[inline(never)]\n    fn reduce_two(a: f16, b: f16) -> f16 {\n        a.max(b)\n    }\n);\n\n#[cfg(test)]\nmod test_arm64fp16_max_f16_32n {\n    use super::*;\n    crate::max_frame_tests!(crate::arm64::has_fp16(), f16, arm64fp16_max_f16_32n);\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64fp16/panel_extract.rs",
    "content": "use super::FP16;\nuse crate::Ops;\nuse crate::block_quant::{PackedBlockQuantFormat, Q4_0};\nuse crate::pack::Packing;\nuse tract_data::internal::*;\n\npub fn plug(ops: &mut Ops) {\n    ops.panel_extractors.push(packed_64_q40_to_f16.clone());\n}\n\npanel_extractor!(kernel_packed_64_q40_to_f16 as packed_64_q40_to_f16(\n    Box::new(PackedBlockQuantFormat::new(&Q4_0, 64, 16, true)),\n    f16::packing(64).align(16)\n) where(FP16));\n\n#[target_feature(enable = \"fp16\")]\nunsafe fn kernel_packed_64_q40_to_f16(input: *const u8, output: *mut u8, k: usize) {\n    unsafe {\n        if k == 0 {\n            return;\n        }\n        let lookup_table: [u8; 16] = [\n            0xc8, 0xc7, 0xc6, 0xc5, 0xc4, 0xc2, 0xc0, 0xbc, 0x00, 0x3c, 0x40, 0x42, 0x44, 0x45,\n            0x46, 0x47,\n        ];\n        std::arch::asm!(\"\n    ld1      {{v13.16b}}, [{lookup_table}]\n    movi     v15.16b, 15\n    eor      v12.16b, v12.16b, v12.16b\n\n    2:\n        add     {scales}, {i}, 1024  // scales at end: 32 (cols) * 64 (rows) / 2 (half byte)\n        ld1     {{v16.16b-v19.16b}}, [{scales}], #64\n        ld1     {{v20.16b-v23.16b}}, [{scales}]\n\n        mov     {k2}, 32\n    3:\n        ld1     {{ v9.16b-v10.16b }}, [{i}], #32\n\n        and     v0.16b, v9.16b, v15.16b\n        ushr    v2.16b, v9.16b, 4\n\n        and     v4.16b, v10.16b, v15.16b\n        ushr    v6.16b, v10.16b, 4\n\n        tbl     v0.16b, {{ v13.16b }}, v0.16b\n        tbl     v2.16b, {{ v13.16b }}, v2.16b\n        tbl     v4.16b, {{ v13.16b }}, v4.16b\n        tbl     v6.16b, {{ v13.16b }}, v6.16b\n\n        zip2    v1.16b, v12.16b, v0.16b\n        zip2    v3.16b, v12.16b, v2.16b\n        zip2    v5.16b, v12.16b, v4.16b\n        zip2    v7.16b, v12.16b, v6.16b\n\n        zip1    v0.16b, v12.16b, v0.16b\n        zip1    v2.16b, v12.16b, v2.16b\n        zip1    v4.16b, v12.16b, v4.16b\n        zip1    v6.16b, v12.16b, v6.16b\n\n        fmul    v0.8h, v0.8h, v16.8h\n        fmul    v1.8h, v1.8h, v17.8h\n        fmul    v2.8h, v2.8h, v18.8h\n        fmul    v3.8h, v3.8h, v19.8h\n        fmul    v4.8h, v4.8h, v20.8h\n        fmul    v5.8h, v5.8h, v21.8h\n        fmul    v6.8h, v6.8h, v22.8h\n        fmul    v7.8h, v7.8h, v23.8h\n\n        st1     {{v0.16b-v3.16b}}, [{o}], #64\n        st1     {{v4.16b-v7.16b}}, [{o}], #64\n\n        subs    {k2}, {k2}, #1\n        bne     3b\n\n        add     {i}, {i}, 128 // skip scales\n        subs    {k}, {k}, 32\n        bne     2b\n            \",\n        lookup_table = in(reg) &lookup_table,\n        k = inout(reg) k => _,\n        k2 = out(reg) _,\n        scales = out(reg) _,\n        i = inout(reg) input => _,\n        o = inout(reg) output => _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        out(\"v16\") _, out(\"v17\") _, out(\"v18\") _, out(\"v19\") _,\n        out(\"v20\") _, out(\"v21\") _, out(\"v22\") _, out(\"v23\") _,\n        );\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64fp16/sum.rs",
    "content": "use crate::num_traits::Zero;\nuse tract_data::half::f16;\n\nreduce_impl_wrap!(\n    f16,\n    arm64fp16_sum_f16_32n,\n    32,\n    8,\n    (),\n    f16::zero(),\n    #[inline(never)]\n    fn run(buf: &[f16], _: ()) -> f16 {\n        assert!(buf.len() % 32 == 0);\n        assert!(buf.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(buf: &[f16]) -> f16 {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                let mut out: u16;\n                std::arch::asm!(\"\n                movi v0.8h, #0\n                movi v1.8h, #0\n                movi v2.8h, #0\n                movi v3.8h, #0\n                2:\n                    ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{ptr}], 64\n                    fadd v0.8h, v0.8h, v4.8h\n                    fadd v1.8h, v1.8h, v5.8h\n                    fadd v2.8h, v2.8h, v6.8h\n                    fadd v3.8h, v3.8h, v7.8h\n\n                    subs {len}, {len}, 32\n                    bne 2b\n\n                fadd v0.8h, v0.8h, v1.8h\n                fadd v2.8h, v2.8h, v3.8h\n                fadd v0.8h, v0.8h, v2.8h\n                faddp v0.8h, v0.8h, v0.8h\n                faddp v0.8h, v0.8h, v0.8h\n                faddp v0.8h, v0.8h, v0.8h\n                \",\n                ptr = inout(reg) ptr => _,\n                len = inout(reg) len => _,\n                out(\"s0\") out, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n                out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n                f16::from_bits(out)\n            }\n        }\n        unsafe { run(buf) }\n    },\n    #[inline(never)]\n    fn reduce_two(a: f16, b: f16) -> f16 {\n        a + b\n    }\n);\n\n#[cfg(test)]\nmod test_arm64fp16_sum_f16_32n {\n    use super::*;\n    crate::sum_frame_tests!(crate::arm64::has_fp16(), f16, arm64fp16_sum_f16_32n);\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64fp16/unicast.rs",
    "content": "use tract_data::half::f16;\n\nunicast_impl_wrap!(\n    f16,\n    arm64fp16_unicast_mul_f16_32n,\n    32,\n    8,\n    #[inline(never)]\n    fn run(a: &mut [f16], b: &[f16]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 32 == 0);\n        assert!(a.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(a: &mut [f16], b: &[f16]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}]\n                    ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{b_ptr}], 64\n                    fmul v0.8h, v0.8h, v4.8h\n                    fmul v1.8h, v1.8h, v5.8h\n                    fmul v2.8h, v2.8h, v6.8h\n                    fmul v3.8h, v3.8h, v7.8h\n                    st1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}], 64\n                    subs {len}, {len}, 32\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    arm64fp16_unicast_add_f16_32n,\n    32,\n    8,\n    #[inline(never)]\n    fn run(a: &mut [f16], b: &[f16]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 32 == 0);\n        assert!(a.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(a: &mut [f16], b: &[f16]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}]\n                    ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{b_ptr}], 64\n                    fadd v0.8h, v0.8h, v4.8h\n                    fadd v1.8h, v1.8h, v5.8h\n                    fadd v2.8h, v2.8h, v6.8h\n                    fadd v3.8h, v3.8h, v7.8h\n                    st1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}], 64\n                    subs {len}, {len}, 32\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    arm64fp16_unicast_sub_f16_32n,\n    32,\n    8,\n    #[inline(never)]\n    fn run(a: &mut [f16], b: &[f16]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 32 == 0);\n        assert!(a.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(a: &mut [f16], b: &[f16]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}]\n                    ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{b_ptr}], 64\n                    fsub v0.8h, v0.8h, v4.8h\n                    fsub v1.8h, v1.8h, v5.8h\n                    fsub v2.8h, v2.8h, v6.8h\n                    fsub v3.8h, v3.8h, v7.8h\n                    st1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}], 64\n                    subs {len}, {len}, 32\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    arm64fp16_unicast_subf_f16_32n,\n    32,\n    8,\n    #[inline(never)]\n    fn run(a: &mut [f16], b: &[f16]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 32 == 0);\n        assert!(a.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(a: &mut [f16], b: &[f16]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}]\n                    ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{b_ptr}], 64\n                    fsub v0.8h, v4.8h, v0.8h\n                    fsub v1.8h, v5.8h, v1.8h\n                    fsub v2.8h, v6.8h, v2.8h\n                    fsub v3.8h, v7.8h, v3.8h\n                    st1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}], 64\n                    subs {len}, {len}, 32\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    arm64fp16_unicast_min_f16_32n,\n    32,\n    8,\n    #[inline(never)]\n    fn run(a: &mut [f16], b: &[f16]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 32 == 0);\n        assert!(a.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(a: &mut [f16], b: &[f16]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}]\n                    ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{b_ptr}], 64\n                    fmin v0.8h, v0.8h, v4.8h\n                    fmin v1.8h, v1.8h, v5.8h\n                    fmin v2.8h, v2.8h, v6.8h\n                    fmin v3.8h, v3.8h, v7.8h\n                    st1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}], 64\n                    subs {len}, {len}, 32\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    arm64fp16_unicast_max_f16_32n,\n    32,\n    8,\n    #[inline(never)]\n    fn run(a: &mut [f16], b: &[f16]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 32 == 0);\n        assert!(a.len() > 0);\n        #[target_feature(enable = \"fp16\")]\n        unsafe fn run(a: &mut [f16], b: &[f16]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}]\n                    ld1 {{v4.8h, v5.8h, v6.8h, v7.8h}}, [{b_ptr}], 64\n                    fmax v0.8h, v0.8h, v4.8h\n                    fmax v1.8h, v1.8h, v5.8h\n                    fmax v2.8h, v2.8h, v6.8h\n                    fmax v3.8h, v3.8h, v7.8h\n                    st1 {{v0.8h, v1.8h, v2.8h, v3.8h}}, [{a_ptr}], 64\n                    subs {len}, {len}, 32\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\n#[cfg(test)]\nmod test_arm64fp16_unicast_mul_f16_32n {\n    use super::*;\n    use proptest::strategy::Strategy;\n    crate::unicast_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_unicast_mul_f16_32n,\n        |a, b| a * b\n    );\n    crate::unicast_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_unicast_add_f16_32n,\n        |a, b| a + b\n    );\n    crate::unicast_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_unicast_sub_f16_32n,\n        |a, b| a - b\n    );\n    crate::unicast_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_unicast_subf_f16_32n,\n        |a, b| b - a\n    );\n    crate::unicast_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_unicast_min_f16_32n,\n        |a, b| a.min(b)\n    );\n    crate::unicast_frame_tests!(\n        crate::arm64::has_fp16(),\n        f16,\n        arm64fp16_unicast_max_f16_32n,\n        |a, b| a.max(b)\n    );\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64fp16.rs",
    "content": "use tract_data::half::f16;\n\nmod by_scalar;\nmod leaky_relu;\nmod max;\npub mod panel_extract;\nmod sum;\nmod unicast;\npub use by_scalar::*;\npub use leaky_relu::*;\npub use max::*;\npub use sum::*;\npub use unicast::*;\n\nuse crate::Ops;\nuse crate::block_quant::PackedBlockQuantFormat;\nuse crate::block_quant::Q4_0;\nuse crate::frame::mmm::ImplementationQuality::ManuallyOptimized;\n\nconst FP16: fn() -> bool = crate::arm64::has_fp16;\n\nMMMExternKernel!(arm64fp16_mmm_f16_16x8_gen<f16>(16, 8)@(16, 16) where(FP16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64fp16_mmm_f16_16x8_a55<f16>(16, 8)@(16, 16) where(FP16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64fp16_mmm_f16_32x4_gen<f16>(32, 4)@(16, 16) where(FP16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64fp16_mmm_f16_32x4_a55<f16>(32, 4)@(16, 16) where(FP16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64fp16_mmm_f16_128x1_gen<f16>(128,1)@(16, 16) where(FP16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64fp16_mmm_f16_128x1_a55<f16>(128,1)@(16, 16) where(FP16) quality(ManuallyOptimized));\n\nMMMExternKernel!(arm64fp16_mmm_f16_64x3_gen<f16>(64, 3)@(16, 16) where(FP16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64fp16_mmm_f16_32x6_gen<f16>(32, 6)@(16, 16) where(FP16) quality(ManuallyOptimized));\n\nMMMExternKernel! { arm64fp16_mmm_f16_64x1_gen<f16>(64, 1)@(16, 16) where(FP16)\n    packing[1] = q40f16z16se => |k| k.with_packing_a(PackedBlockQuantFormat::new(&Q4_0, 64, 16, true));\n    packing[2] = q40f16z16 => |k| k.with_packing_a(PackedBlockQuantFormat::new(&Q4_0, 64, 16, false));\n    quality(ManuallyOptimized)\n}\n\npub fn plug(ops: &mut Ops) {\n    panel_extract::plug(ops);\n    ops.mmm_impls.extend_from_slice(&[\n        arm64fp16_mmm_f16_16x8_a55.mmm(),\n        arm64fp16_mmm_f16_16x8_gen.mmm(),\n        arm64fp16_mmm_f16_32x4_a55.mmm(),\n        arm64fp16_mmm_f16_32x4_gen.mmm(),\n        arm64fp16_mmm_f16_128x1_a55.mmm(),\n        arm64fp16_mmm_f16_128x1_gen.mmm(),\n        arm64fp16_mmm_f16_64x3_gen.mmm(),\n        arm64fp16_mmm_f16_32x6_gen.mmm(),\n        arm64fp16_mmm_f16_64x1_gen.mmm(),\n    ]);\n}\n\ntanh_impl!(f16, arm64fp16_tanh_f16_8n, 8, 8, crate::arm64::has_fp16());\nsigmoid_impl!(f16, arm64fp16_sigmoid_f16_8n, 8, 8, crate::arm64::has_fp16());\n\n#[cfg(test)]\nmod test {\n\n    #[test]\n    fn kits() {\n        let mut ops = crate::generic();\n        super::plug(&mut ops);\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64simd/by_scalar.rs",
    "content": "by_scalar_impl_wrap!(\n    f32,\n    arm64simd_mul_by_scalar_f32_16n,\n    16,\n    4,\n    f32,\n    fn run(buf: &mut [f32], s: f32) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        unsafe {\n            let len = buf.len();\n            let ptr = buf.as_ptr();\n            std::arch::asm!(\"\n            dup v0.4s, v0.s[0]\n            2:\n                ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}]\n                fmul v4.4s, v4.4s, v0.4s\n                fmul v5.4s, v5.4s, v0.4s\n                fmul v6.4s, v6.4s, v0.4s\n                fmul v7.4s, v7.4s, v0.4s\n                st1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}], 64\n                subs {len}, {len}, 16\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s,\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n        }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    arm64simd_add_by_scalar_f32_16n,\n    16,\n    4,\n    f32,\n    fn run(buf: &mut [f32], s: f32) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        unsafe {\n            let len = buf.len();\n            let ptr = buf.as_ptr();\n            std::arch::asm!(\"\n            dup v0.4s, v0.s[0]\n            2:\n                ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}]\n                fadd v4.4s, v4.4s, v0.4s\n                fadd v5.4s, v5.4s, v0.4s\n                fadd v6.4s, v6.4s, v0.4s\n                fadd v7.4s, v7.4s, v0.4s\n                st1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}], 64\n                subs {len}, {len}, 16\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s,\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n        }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    arm64simd_sub_by_scalar_f32_16n,\n    16,\n    4,\n    f32,\n    fn run(buf: &mut [f32], s: f32) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        unsafe {\n            let len = buf.len();\n            let ptr = buf.as_ptr();\n            std::arch::asm!(\"\n            dup v0.4s, v0.s[0]\n            2:\n                ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}]\n                fsub v4.4s, v4.4s, v0.4s\n                fsub v5.4s, v5.4s, v0.4s\n                fsub v6.4s, v6.4s, v0.4s\n                fsub v7.4s, v7.4s, v0.4s\n                st1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}], 64\n                subs {len}, {len}, 16\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s,\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n        }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    arm64simd_subf_by_scalar_f32_16n,\n    16,\n    4,\n    f32,\n    fn run(buf: &mut [f32], s: f32) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        unsafe {\n            let len = buf.len();\n            let ptr = buf.as_ptr();\n            std::arch::asm!(\"\n            dup v0.4s, v0.s[0]\n            2:\n                ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}]\n                fsub v4.4s, v0.4s, v4.4s\n                fsub v5.4s, v0.4s, v5.4s\n                fsub v6.4s, v0.4s, v6.4s\n                fsub v7.4s, v0.4s, v7.4s\n                st1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}], 64\n                subs {len}, {len}, 16\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s,\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n        }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    arm64simd_min_by_scalar_f32_16n,\n    16,\n    4,\n    f32,\n    fn run(buf: &mut [f32], s: f32) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        unsafe {\n            let len = buf.len();\n            let ptr = buf.as_ptr();\n            std::arch::asm!(\"\n            dup v0.4s, v0.s[0]\n            2:\n                ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}]\n                fmin v4.4s, v4.4s, v0.4s\n                fmin v5.4s, v5.4s, v0.4s\n                fmin v6.4s, v6.4s, v0.4s\n                fmin v7.4s, v7.4s, v0.4s\n                st1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}], 64\n                subs {len}, {len}, 16\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s,\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n        }\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    arm64simd_max_by_scalar_f32_16n,\n    16,\n    4,\n    f32,\n    fn run(buf: &mut [f32], s: f32) {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        unsafe {\n            let len = buf.len();\n            let ptr = buf.as_ptr();\n            std::arch::asm!(\"\n            dup v0.4s, v0.s[0]\n            2:\n                ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}]\n                fmax v4.4s, v4.4s, v0.4s\n                fmax v5.4s, v5.4s, v0.4s\n                fmax v6.4s, v6.4s, v0.4s\n                fmax v7.4s, v7.4s, v0.4s\n                st1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}], 64\n                subs {len}, {len}, 16\n                bne 2b\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            in(\"v0\") s,\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n        }\n    }\n);\n\n#[cfg(test)]\nmod test_arm64simd_mul_by_scalar_f32_16n {\n    use super::*;\n    by_scalar_frame_tests!(true, f32, arm64simd_mul_by_scalar_f32_16n, |a, b| a * b);\n    by_scalar_frame_tests!(true, f32, arm64simd_add_by_scalar_f32_16n, |a, b| a + b);\n    by_scalar_frame_tests!(true, f32, arm64simd_sub_by_scalar_f32_16n, |a, b| a - b);\n    by_scalar_frame_tests!(true, f32, arm64simd_subf_by_scalar_f32_16n, |a, b| b - a);\n    by_scalar_frame_tests!(true, f32, arm64simd_min_by_scalar_f32_16n, |a, b| a.min(b));\n    by_scalar_frame_tests!(true, f32, arm64simd_max_by_scalar_f32_16n, |a, b| a.max(b));\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64simd/leaky_relu.rs",
    "content": "ew_impl_wrap!(\n    f32,\n    arm64simd_leaky_relu_f32_8n,\n    8,\n    4,\n    f32,\n    #[inline(never)]\n    fn run(buf: &mut [f32], alpha: f32) {\n        assert!(buf.len() % 8 == 0);\n        assert!(buf.len() > 0);\n        unsafe {\n            let len = buf.len();\n            let ptr = buf.as_ptr();\n            std::arch::asm!(\"\n                dup v0.4s, {alpha:v}.s[0]\n                dup v1.4s, {one:v}.s[0]\n                2:\n                    ldp q3, q4, [{ptr}]\n\n                    fcmgt v5.4s, v3.4s, #0.0\n                    fcmgt v6.4s, v4.4s, #0.0\n                    bsl   v5.16b, v1.16b, v0.16b\n                    bsl   v6.16b, v1.16b, v0.16b\n                    fmul  v3.4s, v3.4s, v5.4s\n                    fmul  v4.4s, v4.4s, v6.4s\n\n                    stp q3, q4, [{ptr}], #32\n                    subs {len}, {len}, 8\n                    bne 2b\n            \",\n            one = in(vreg) 1.0f32,\n            alpha = in(vreg) alpha,\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            out(\"v0\") _,\n            out(\"v1\") _,\n            out(\"q3\") _,\n            out(\"q4\") _,\n            out(\"q5\") _,\n            out(\"q6\") _,\n            );\n        }\n    }\n);\n\n#[cfg(test)]\npub mod test_arm64simd_leaky_relu_f32_8n {\n    use super::*;\n    leaky_relu_frame_tests!(true, f32, arm64simd_leaky_relu_f32_8n);\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64simd/max.rs",
    "content": "use std::arch::aarch64::{float32x4_t, vdupq_n_f32, vgetq_lane_f32};\n\nreduce_impl_wrap!(\n    f32,\n    arm64simd_max_f32_16n,\n    16,\n    4,\n    (),\n    f32::MIN,\n    #[inline(never)]\n    fn run(buf: &[f32], _: ()) -> f32 {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        unsafe {\n            let len = buf.len();\n            let ptr = buf.as_ptr();\n            let mut out: float32x4_t = vdupq_n_f32(f32::MIN);\n            std::arch::asm!(\"\n            and v1.16b, v0.16b, v0.16b\n            and v2.16b, v0.16b, v0.16b\n            and v3.16b, v0.16b, v0.16b\n            2:\n                ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}], 64\n                fmax v0.4s, v0.4s, v4.4s\n                fmax v1.4s, v1.4s, v5.4s\n                fmax v2.4s, v2.4s, v6.4s\n                fmax v3.4s, v3.4s, v7.4s\n                subs {len}, {len}, 16\n                bne 2b\n            fmax v0.4s, v0.4s, v1.4s\n            fmax v2.4s, v2.4s, v3.4s\n            fmax v0.4s, v0.4s, v2.4s\n            fmaxv s0, v0.4s\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            inout(\"v0\") out, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n            out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n            vgetq_lane_f32(out, 0)\n        }\n    },\n    #[inline(never)]\n    fn reduce_two(a: f32, b: f32) -> f32 {\n        a.max(b)\n    }\n);\n\n#[cfg(test)]\nmod test_arm64simd_max_f32_16n {\n    use super::*;\n    crate::max_frame_tests!(true, f32, arm64simd_max_f32_16n);\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64simd/panel_extract.rs",
    "content": "use crate::Ops;\nuse crate::pack::Packing;\n\npub fn plug(ops: &mut Ops) {\n    ops.panel_extractors.push(packed_32_q40_to_f32.clone());\n}\n\npanel_extractor!(kernel_packed_32_q40_to_f32 as packed_32_q40_to_f32(\n    Box::new(super::q40p32z16se()),\n    f32::packing(32).align(16)\n));\n\nunsafe fn kernel_packed_32_q40_to_f32(input: *const u8, output: *mut u8, k: usize) {\n    unsafe {\n        if k == 0 {\n            return;\n        }\n        let lookup_table: [u8; 16] = [\n            0xc8, 0xc7, 0xc6, 0xc5, 0xc4, 0xc2, 0xc0, 0xbc, 0x00, 0x3c, 0x40, 0x42, 0x44, 0x45,\n            0x46, 0x47,\n        ];\n        std::arch::asm!(\"\n    ld1      {{v13.16b}}, [{lookup_table}]\n    movi     v15.16b, 15\n    eor      v12.16b, v12.16b, v12.16b\n\n    2:\n        add     {scales}, {i}, 512  // scales at end: 32 (cols) * 32 (rows) / 2 (half byte)\n        ld1     {{v0.8h-v3.8h}}, [{scales}]\n\n        fcvtl   v16.4s, v0.4h\n        fcvtl2  v17.4s, v0.8h\n        fcvtl   v18.4s, v1.4h\n        fcvtl2  v19.4s, v1.8h\n        fcvtl   v20.4s, v2.4h\n        fcvtl2  v21.4s, v2.8h\n        fcvtl   v22.4s, v3.4h\n        fcvtl2  v23.4s, v3.8h\n\n        mov     {k2}, 32\n    3:\n        ld1     {{ v9.16b }}, [{i}], #16\n\n        and     v0.16b, v9.16b, v15.16b\n        ushr    v4.16b, v9.16b, 4\n\n        tbl     v0.16b, {{ v13.16b }}, v0.16b\n        tbl     v4.16b, {{ v13.16b }}, v4.16b\n\n        zip2    v2.16b, v12.16b, v0.16b\n        zip2    v6.16b, v12.16b, v4.16b\n\n        zip1    v0.16b, v12.16b, v0.16b\n        zip1    v4.16b, v12.16b, v4.16b\n\n        fcvtl2  v1.4s, v0.8h\n        fcvtl   v0.4s, v0.4h\n        fcvtl2  v3.4s, v2.8h\n        fcvtl   v2.4s, v2.4h\n        fcvtl2  v5.4s, v4.8h\n        fcvtl   v4.4s, v4.4h\n        fcvtl2  v7.4s, v6.8h\n        fcvtl   v6.4s, v6.4h\n\n        fmul    v0.4s, v0.4s, v16.4s\n        fmul    v1.4s, v1.4s, v17.4s\n        fmul    v2.4s, v2.4s, v18.4s\n        fmul    v3.4s, v3.4s, v19.4s\n        fmul    v4.4s, v4.4s, v20.4s\n        fmul    v5.4s, v5.4s, v21.4s\n        fmul    v6.4s, v6.4s, v22.4s\n        fmul    v7.4s, v7.4s, v23.4s\n\n        st1     {{v0.16b-v3.16b}}, [{o}], #64\n        st1     {{v4.16b-v7.16b}}, [{o}], #64\n\n        subs    {k2}, {k2}, #1\n        bne     3b\n\n        add     {i}, {i}, 64 // skip scales\n        subs    {k}, {k}, 32\n        bne     2b\n            \",\n        lookup_table = in(reg) &lookup_table,\n        k = inout(reg) k => _,\n        k2 = out(reg) _,\n        scales = out(reg) _,\n        i = inout(reg) input => _,\n        o = inout(reg) output => _,\n        out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n        out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,\n        out(\"v8\") _, out(\"v9\") _, out(\"v10\") _, out(\"v11\") _,\n        out(\"v12\") _, out(\"v13\") _, out(\"v14\") _, out(\"v15\") _,\n        out(\"v16\") _, out(\"v17\") _, out(\"v18\") _, out(\"v19\") _,\n        out(\"v20\") _, out(\"v21\") _, out(\"v22\") _, out(\"v23\") _,\n        );\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64simd/softmax.rs",
    "content": "map_reduce_impl_wrap!(\n    f32,\n    arm64simd_softmax2_fastcompact_f32_16n,\n    16,\n    4,\n    f32,\n    f32::MIN,\n    0f32,\n    #[inline(never)]\n    fn run(buf: &mut [f32], max: f32) -> f32 {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        let len = buf.len();\n        let ptr = buf.as_ptr();\n        let mut acc;\n        const MLN2: f32 = 0.6931471805f32;\n        const A: f32 = 8388608.0f32;\n        const B: f32 = 1065353216.0f32;\n        const C: f32 = 60801.0f32;\n        const SLOPE: f32 = A / MLN2;\n        const OFFSET: f32 = B - C;\n        unsafe {\n            std::arch::asm!(\"\n            // v0-v3 sum acc\n            eor v0.16b, v0.16b, v0.16b\n            eor v1.16b, v1.16b, v1.16b\n            eor v2.16b, v2.16b, v2.16b\n            eor v3.16b, v3.16b, v3.16b\n\n            dup v4.4s, v4.s[0] // max\n            dup v5.4s, v5.s[0] // slope\n            dup v6.4s, v6.s[0] // offset\n            eor v7.16b, v7.16b, v7.16b // zero for max\n            2:\n                ld1 {{v8.4s, v9.4s, v10.4s, v11.4s}}, [{ptr}]\n\n                fsub v8.4s, v8.4s, v4.4s\n                fsub v9.4s, v9.4s, v4.4s\n                fsub v10.4s, v10.4s, v4.4s\n                fsub v11.4s, v11.4s, v4.4s\n\n                fmul v8.4s, v8.4s, v5.4s\n                fmul v9.4s, v9.4s, v5.4s\n                fmul v10.4s, v10.4s, v5.4s\n                fmul v11.4s, v11.4s, v5.4s\n\n                fadd v8.4s, v8.4s, v6.4s\n                fadd v9.4s, v9.4s, v6.4s\n                fadd v10.4s, v10.4s, v6.4s\n                fadd v11.4s, v11.4s, v6.4s\n\n                fmax v8.4s, v8.4s, v7.4s\n                fmax v9.4s, v9.4s, v7.4s\n                fmax v10.4s, v10.4s, v7.4s\n                fmax v11.4s, v11.4s, v7.4s\n\n                fcvtnu v8.4s, v8.4s\n                fcvtnu v9.4s, v9.4s\n                fcvtnu v10.4s, v10.4s\n                fcvtnu v11.4s, v11.4s\n\n                fadd v0.4s, v0.4s, v8.4s\n                fadd v1.4s, v1.4s, v9.4s\n                fadd v2.4s, v2.4s, v10.4s\n                fadd v3.4s, v3.4s, v11.4s\n\n                st1 {{v8.4s, v9.4s, v10.4s, v11.4s}}, [{ptr}], 64\n                subs {len}, {len}, 16\n                bne 2b\n\n            fadd v0.4s, v0.4s, v1.4s\n            fadd v2.4s, v2.4s, v3.4s\n            fadd v0.4s, v0.4s, v2.4s\n\n            ext v1.16b, v0.16b, v0.16b, 4\n            ext v2.16b, v0.16b, v0.16b, 8\n            ext v3.16b, v0.16b, v0.16b, 12\n            fadd v0.4s, v0.4s, v1.4s\n            fadd v2.4s, v2.4s, v3.4s\n            fadd v0.4s, v0.4s, v2.4s\n            \",\n            len = inout(reg) len => _,\n            ptr = inout(reg) ptr => _,\n            out(\"v0\") acc,\n            out(\"v1\") _,\n            out(\"v2\") _,\n            out(\"v3\") _,\n            inout(\"v4\") max => _,\n            inout(\"v5\") SLOPE => _,\n            inout(\"v6\") OFFSET => _,\n            out(\"v7\") _,\n            out(\"v8\") _,\n            out(\"v9\") _,\n            out(\"v10\") _,\n            out(\"v11\") _,\n            );\n        }\n        acc\n    },\n    #[inline(never)]\n    fn reduce_two(a: f32, b: f32) -> f32 {\n        a + b\n    }\n);\n\n#[cfg(test)]\nmod test_arm64simd_softmax2_fastcompact_f32_16n {\n    use super::*;\n    crate::softmax_l2_frame_tests!(true, f32, arm64simd_softmax2_fastcompact_f32_16n);\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64simd/sum.rs",
    "content": "use crate::num_traits::Zero;\n\nreduce_impl_wrap!(\n    f32,\n    arm64simd_sum_f32_16n,\n    16,\n    4,\n    (),\n    f32::zero(),\n    #[inline(never)]\n    fn run(buf: &[f32], _: ()) -> f32 {\n        assert!(buf.len() % 16 == 0);\n        assert!(buf.len() > 0);\n        unsafe fn run(buf: &[f32]) -> f32 {\n            unsafe {\n                let len = buf.len();\n                let ptr = buf.as_ptr();\n                let mut out: u32;\n                std::arch::asm!(\"\n                movi v0.4s, #0\n                movi v1.4s, #0\n                movi v2.4s, #0\n                movi v3.4s, #0\n                2:\n                    ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{ptr}], 64\n                    fadd v0.4s, v0.4s, v4.4s\n                    fadd v1.4s, v1.4s, v5.4s\n                    fadd v2.4s, v2.4s, v6.4s\n                    fadd v3.4s, v3.4s, v7.4s\n\n                    subs {len}, {len}, 16\n                    bne 2b\n\n                fadd v0.4s, v0.4s, v1.4s\n                fadd v2.4s, v2.4s, v3.4s\n                fadd v0.4s, v0.4s, v2.4s\n                faddp v0.4s, v0.4s, v0.4s\n                faddp v0.4s, v0.4s, v0.4s\n                \",\n                ptr = inout(reg) ptr => _,\n                len = inout(reg) len => _,\n                out(\"s0\") out, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,\n                out(\"v4\") _, out(\"v5\") _, out(\"v6\") _, out(\"v7\") _,);\n                f32::from_bits(out)\n            }\n        }\n        unsafe { run(buf) }\n    },\n    #[inline(never)]\n    fn reduce_two(a: f32, b: f32) -> f32 {\n        a + b\n    }\n);\n\n#[cfg(test)]\nmod test_arm64simd_sum_f32_16n {\n    use super::*;\n    crate::sum_frame_tests!(true, f32, arm64simd_sum_f32_16n);\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64simd/unicast.rs",
    "content": "unicast_impl_wrap!(\n    f32,\n    arm64simd_unicast_mul_f32_16n,\n    16,\n    4,\n    #[inline(never)]\n    fn run(a: &mut [f32], b: &[f32]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 16 == 0);\n        assert!(a.len() > 0);\n        unsafe fn run(a: &mut [f32], b: &[f32]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}]\n                    ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{b_ptr}], 64\n                    fmul v0.4s, v0.4s, v4.4s\n                    fmul v1.4s, v1.4s, v5.4s\n                    fmul v2.4s, v2.4s, v6.4s\n                    fmul v3.4s, v3.4s, v7.4s\n                    st1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}], 64\n                    subs {len}, {len}, 16\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    arm64simd_unicast_add_f32_16n,\n    16,\n    4,\n    #[inline(never)]\n    fn run(a: &mut [f32], b: &[f32]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 16 == 0);\n        assert!(a.len() > 0);\n        unsafe fn run(a: &mut [f32], b: &[f32]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}]\n                    ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{b_ptr}], 64\n                    fadd v0.4s, v0.4s, v4.4s\n                    fadd v1.4s, v1.4s, v5.4s\n                    fadd v2.4s, v2.4s, v6.4s\n                    fadd v3.4s, v3.4s, v7.4s\n                    st1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}], 64\n                    subs {len}, {len}, 16\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    arm64simd_unicast_sub_f32_16n,\n    16,\n    4,\n    #[inline(never)]\n    fn run(a: &mut [f32], b: &[f32]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 16 == 0);\n        assert!(a.len() > 0);\n        unsafe fn run(a: &mut [f32], b: &[f32]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}]\n                    ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{b_ptr}], 64\n                    fsub v0.4s, v0.4s, v4.4s\n                    fsub v1.4s, v1.4s, v5.4s\n                    fsub v2.4s, v2.4s, v6.4s\n                    fsub v3.4s, v3.4s, v7.4s\n                    st1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}], 64\n                    subs {len}, {len}, 16\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    arm64simd_unicast_subf_f32_16n,\n    16,\n    4,\n    #[inline(never)]\n    fn run(a: &mut [f32], b: &[f32]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 16 == 0);\n        assert!(a.len() > 0);\n        unsafe fn run(a: &mut [f32], b: &[f32]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}]\n                    ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{b_ptr}], 64\n                    fsub v0.4s, v4.4s, v0.4s\n                    fsub v1.4s, v5.4s, v1.4s\n                    fsub v2.4s, v6.4s, v2.4s\n                    fsub v3.4s, v7.4s, v3.4s\n                    st1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}], 64\n                    subs {len}, {len}, 16\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    arm64simd_unicast_max_f32_16n,\n    16,\n    4,\n    #[inline(never)]\n    fn run(a: &mut [f32], b: &[f32]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 16 == 0);\n        assert!(a.len() > 0);\n        unsafe fn run(a: &mut [f32], b: &[f32]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}]\n                    ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{b_ptr}], 64\n                    fmax v0.4s, v0.4s, v4.4s\n                    fmax v1.4s, v1.4s, v5.4s\n                    fmax v2.4s, v2.4s, v6.4s\n                    fmax v3.4s, v3.4s, v7.4s\n                    st1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}], 64\n                    subs {len}, {len}, 16\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    arm64simd_unicast_min_f32_16n,\n    16,\n    4,\n    #[inline(never)]\n    fn run(a: &mut [f32], b: &[f32]) {\n        assert!(a.len() == b.len());\n        assert!(a.len() % 16 == 0);\n        assert!(a.len() > 0);\n        unsafe fn run(a: &mut [f32], b: &[f32]) {\n            unsafe {\n                let len = a.len();\n                let a_ptr = a.as_ptr();\n                let b_ptr = b.as_ptr();\n                std::arch::asm!(\"\n                2:\n                    ld1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}]\n                    ld1 {{v4.4s, v5.4s, v6.4s, v7.4s}}, [{b_ptr}], 64\n                    fmin v0.4s, v0.4s, v4.4s\n                    fmin v1.4s, v1.4s, v5.4s\n                    fmin v2.4s, v2.4s, v6.4s\n                    fmin v3.4s, v3.4s, v7.4s\n                    st1 {{v0.4s, v1.4s, v2.4s, v3.4s}}, [{a_ptr}], 64\n                    subs {len}, {len}, 16\n                    bne 2b\n            \",\n            len = inout(reg) len => _,\n            a_ptr = inout(reg) a_ptr => _,\n            b_ptr = inout(reg) b_ptr => _,\n            out(\"v0\") _, out(\"v1\") _, out(\"v2\") _, out(\"v3\") _,);\n            }\n        }\n        unsafe { run(a, b) }\n    }\n);\n\n#[cfg(test)]\nmod test_arm64simd_unicast_mul_f32_16n {\n    use super::*;\n    use proptest::strategy::Strategy;\n    crate::unicast_frame_tests!(true, f32, arm64simd_unicast_mul_f32_16n, |a, b| a * b);\n    crate::unicast_frame_tests!(true, f32, arm64simd_unicast_add_f32_16n, |a, b| a + b);\n    crate::unicast_frame_tests!(true, f32, arm64simd_unicast_sub_f32_16n, |a, b| a - b);\n    crate::unicast_frame_tests!(true, f32, arm64simd_unicast_subf_f32_16n, |a, b| b - a);\n    crate::unicast_frame_tests!(true, f32, arm64simd_unicast_min_f32_16n, |a, b| a.min(b));\n    crate::unicast_frame_tests!(true, f32, arm64simd_unicast_max_f32_16n, |a, b| a.max(b));\n}\n"
  },
  {
    "path": "linalg/src/arm64/arm64simd.rs",
    "content": "mod by_scalar;\nmod leaky_relu;\nmod max;\nmod panel_extract;\nmod softmax;\nmod sum;\nmod unicast;\n\npub use by_scalar::*;\npub use leaky_relu::arm64simd_leaky_relu_f32_8n;\npub use max::arm64simd_max_f32_16n;\npub use softmax::arm64simd_softmax2_fastcompact_f32_16n;\npub use sum::arm64simd_sum_f32_16n;\npub use unicast::*;\n\nuse crate::Ops;\nuse crate::block_quant::{PackedBlockQuantFormat, Q4_0};\nuse crate::frame::mmm::ImplementationQuality::ManuallyOptimized;\nuse crate::pack::PackedFormat;\n\nuse super::Kind;\n\nfn a55() -> isize {\n    if *super::KIND == Kind::CortexA55 { 1 } else { -1 }\n}\n\nfn a53() -> isize {\n    if *super::KIND == Kind::CortexA53 { 1 } else { -1 }\n}\n\nMMMExternKernel!(arm64simd_mmm_f32_8x8_a55 <f32>(8,  8)@(16, 16) quality(ManuallyOptimized) boost(a55));\nMMMExternKernel!(arm64simd_mmm_f32_12x8_a55<f32>(12, 8)@(16, 16) quality(ManuallyOptimized) boost(a55));\nMMMExternKernel!(arm64simd_mmm_f32_16x4_a55<f32>(16, 4)@(16, 16) quality(ManuallyOptimized) boost(a55));\nMMMExternKernel!(arm64simd_mmm_f32_24x4_a55<f32>(24, 4)@(16, 16) quality(ManuallyOptimized) boost(a55));\nMMMExternKernel!(arm64simd_mmm_f32_64x1_a55<f32>(64, 1)@(16, 16) quality(ManuallyOptimized) boost(a55));\n\nMMMExternKernel!(arm64simd_mmm_f32_16x4_a53<f32>(16, 4)@(16, 16) quality(ManuallyOptimized) boost(a53));\nMMMExternKernel!(arm64simd_mmm_f32_24x4_a53<f32>(24, 4)@(16, 16) quality(ManuallyOptimized) boost(a53));\nMMMExternKernel!(arm64simd_mmm_f32_8x8_a53 <f32>(8,  8)@(16, 16) quality(ManuallyOptimized) boost(a53));\nMMMExternKernel!(arm64simd_mmm_f32_12x8_a53<f32>(12, 8)@(16, 16) quality(ManuallyOptimized) boost(a53));\nMMMExternKernel!(arm64simd_mmm_f32_64x1_a53<f32>(64, 1)@(16, 16) quality(ManuallyOptimized) boost(a53));\n\nMMMExternKernel!(arm64simd_mmm_f32_16x4_gen<f32>(16, 4)@(16, 16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64simd_mmm_f32_24x4_gen<f32>(24, 4)@(16, 16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64simd_mmm_f32_8x8_gen <f32>(8,  8)@(16, 16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64simd_mmm_f32_12x8_gen<f32>(12, 8)@(16, 16) quality(ManuallyOptimized));\nMMMExternKernel!(arm64simd_mmm_f32_64x1_gen<f32>(64, 1)@(16, 16) quality(ManuallyOptimized));\n\nfn q40p32z16se() -> PackedBlockQuantFormat {\n    PackedBlockQuantFormat::new(&Q4_0, 32, 16, true)\n}\n\nMMMExternKernel!(arm64simd_mmm_f32_32x1_gen<f32>(32, 1)@(16, 16)\n    packing[1] = q40f16 => |k| k.with_packing(q40p32z16se(), f16::packing(1));\n    packing[2] = q40f32 => |k| k.with_packing(q40p32z16se(), f32::packing(1));\n    packing[3] = f16f16 => |k| k.with_packing(f16::packing(32), f16::packing(1));\n    packing[4] = f32f16 => |k| k.with_packing(f32::packing(32), f16::packing(1));\n    packing[5] = f16f32 => |k| k.with_packing(f16::packing(32), f32::packing(1));\n    quality(ManuallyOptimized)\n    store(f16)\n);\n\nMMMExternKernel!(arm64simd_mmm_f32_32x3_gen<f32>(32, 3)@(16, 16)\n    packing[1] = f32f16 => |k| k.with_packing(f32::packing(32), f16::packing(3));\n    packing[2] = f16f32 => |k| k.with_packing(f16::packing(32), f32::packing(3));\n    packing[3] = f16f16 => |k| k.with_packing(f16::packing(32), f16::packing(3));\n    quality(ManuallyOptimized)\n    store(f16)\n);\n\nMMMExternKernel!(arm64simd_mmm_i32_8x8<i32>(8, 8)@(16, 16)\n   packing[1] = i8i8 => |k| k.with_packing(PackedFormat::new(DatumType::I8, 8, 16), PackedFormat::new(DatumType::I8, 8, 16));\n   quality(ManuallyOptimized)\n   store(i8)\n);\n\nMMMExternKernel!(arm64simd_mmm_i32_64x1<i32>(64, 1)@(16, 1)\n   packing[1] = i8i8 => |k| k.with_packing(PackedFormat::new(DatumType::I8, 64,16), PackedFormat::new(DatumType::I8, 1, 1));\n   quality(ManuallyOptimized)\n   store(i8)\n);\n\npub fn plug(ops: &mut Ops) {\n    ops.mmm_impls.extend([\n        arm64simd_mmm_f32_12x8_gen.mmm(),\n        arm64simd_mmm_f32_12x8_a53.mmm(),\n        arm64simd_mmm_f32_12x8_a55.mmm(),\n        arm64simd_mmm_f32_8x8_gen.mmm(),\n        arm64simd_mmm_f32_8x8_a53.mmm(),\n        arm64simd_mmm_f32_8x8_a55.mmm(),\n        arm64simd_mmm_f32_16x4_gen.mmm(),\n        arm64simd_mmm_f32_16x4_a53.mmm(),\n        arm64simd_mmm_f32_16x4_a55.mmm(),\n        arm64simd_mmm_f32_24x4_gen.mmm(),\n        arm64simd_mmm_f32_24x4_a53.mmm(),\n        arm64simd_mmm_f32_24x4_a55.mmm(),\n        arm64simd_mmm_f32_32x1_gen.mmm(),\n        arm64simd_mmm_f32_32x3_gen.mmm(),\n        arm64simd_mmm_f32_64x1_gen.mmm(),\n        arm64simd_mmm_f32_64x1_a53.mmm(),\n        arm64simd_mmm_f32_64x1_a55.mmm(),\n        arm64simd_mmm_i32_8x8.mmm(),\n        arm64simd_mmm_i32_64x1.mmm(),\n    ]);\n    panel_extract::plug(ops);\n}\n\ntanh_impl!(f32, arm64simd_tanh_f32_4n, 4, 4, true);\nsigmoid_impl!(f32, arm64simd_sigmoid_f32_4n, 4, 4, true);\n"
  },
  {
    "path": "linalg/src/arm64/cortex_a53.rs",
    "content": "use crate::frame::mmm::CostModel;\npub fn model() -> CostModel<'static> {\n    CostModel {\n        big_product_mkn_threshold: 4193280.0,\n        big_product_kernel_choice: \"arm64simd_mmm_f32_12x8_a53\",\n        kernels: &[\n            \"arm64simd_mmm_f32_12x8_a53\",\n            \"arm64simd_mmm_f32_12x8_gen\",\n            \"arm64simd_mmm_f32_16x4_a53\",\n            \"arm64simd_mmm_f32_16x4_gen\",\n            \"arm64simd_mmm_f32_24x4_a53\",\n            \"arm64simd_mmm_f32_24x4_gen\",\n            \"arm64simd_mmm_f32_8x8_a53\",\n            \"arm64simd_mmm_f32_8x8_gen\",\n            \"generic_f32_4x4\",\n        ],\n        mrs: &[4, 8, 12, 16, 24],\n        nrs: &[4, 8],\n        feat_norm_mean: &[\n            4.592185479105843,\n            4.595318666792368,\n            4.579484503710355,\n            13.76698864960861,\n            1.5094315895372235,\n            0.7603118712273642,\n            3.47170523138833,\n            0.8752515090543259,\n            5.487801810865191,\n            0.9224094567404426,\n            7.414361167002012,\n            0.9387575452716298,\n            11.415367203219317,\n            0.959758551307847,\n            1.5074195171026157,\n            0.750125754527163,\n            3.47170523138833,\n            0.875125754527163,\n        ],\n        feat_norm_stddev: &[\n            1.2629893666668983,\n            1.2446322895476982,\n            1.258916587498509,\n            1.3105293102858375,\n            1.1063478713873012,\n            0.4268931127321023,\n            2.3025561444671223,\n            0.330433510637837,\n            3.431728816936762,\n            0.2675261685447694,\n            4.624258056138275,\n            0.23977451171303063,\n            6.954988241153163,\n            0.19652499713600946,\n            1.1207056563030822,\n            0.4329400731304941,\n            2.292868878895526,\n            0.3305762669799629,\n        ],\n        w1: &[\n            -0.6321063041687012,\n            0.24184978008270264,\n            -0.4356610178947449,\n            -0.1422707587480545,\n            0.10410869866609573,\n            0.09415467828512192,\n            0.1568029671907425,\n            -0.25644537806510925,\n            -0.37143954634666443,\n            0.15696385502815247,\n            0.050514884293079376,\n            -0.07972156256437302,\n            -0.253411203622818,\n            0.27587205171585083,\n            0.02698700875043869,\n            -0.07245094329118729,\n            -0.013899300247430801,\n            0.022088056430220604,\n            0.2630922496318817,\n            -0.06870237737894058,\n            0.40947580337524414,\n            0.22110328078269958,\n            0.03808840364217758,\n            -0.008957616984844208,\n            -0.11127127707004547,\n            0.07818343490362167,\n            0.025474127382040024,\n            -0.09513817727565765,\n            0.10613243281841278,\n            0.029441041871905327,\n            0.0819312185049057,\n            -0.03519295156002045,\n            -0.3130439519882202,\n            0.4705337882041931,\n            0.4476615786552429,\n            -0.616556704044342,\n            0.2223544716835022,\n            -0.23584842681884766,\n            -0.3312308192253113,\n            0.18874213099479675,\n            -0.033394988626241684,\n            0.09006354957818985,\n            0.014722823165357113,\n            0.0877116397023201,\n            0.07635975629091263,\n            0.04284617677330971,\n            -0.029695890843868256,\n            -0.05645013228058815,\n            -0.096514992415905,\n            0.16431200504302979,\n            0.11922749876976013,\n            -0.08329842984676361,\n            -0.15593503415584564,\n            0.33497852087020874,\n            0.5143201947212219,\n            -0.4143742322921753,\n            -0.07121813297271729,\n            0.032980211079120636,\n            -0.014759342186152935,\n            -0.10575086623430252,\n            -0.08755142986774445,\n            0.053559254854917526,\n            0.2959750294685364,\n            -0.210640087723732,\n            -0.09462635219097137,\n            0.14600691199302673,\n            0.22388464212417603,\n            -0.185477152466774,\n            -0.100673608481884,\n            -0.10946766287088394,\n            0.03957876190543175,\n            -0.10485030710697174,\n            0.01792730763554573,\n            0.15610192716121674,\n            -0.14726269245147705,\n            0.30900657176971436,\n            0.21081387996673584,\n            -0.06592089682817459,\n            0.03168980032205582,\n            0.20096036791801453,\n            0.021350117400288582,\n            -0.04456694424152374,\n            0.35106319189071655,\n            0.04561518132686615,\n            -0.14208926260471344,\n            0.06227286159992218,\n            -0.20092618465423584,\n            0.08163813501596451,\n            0.23094142973423004,\n            -0.0332462415099144,\n            0.26035502552986145,\n            0.4639679193496704,\n            0.11891252547502518,\n            0.4722647964954376,\n            -0.025709064677357674,\n            0.1651654839515686,\n            -0.009242026135325432,\n            0.02252785675227642,\n            0.13325856626033783,\n            -0.32073062658309937,\n            -0.05948975682258606,\n            -0.07114000618457794,\n            -0.04468341916799545,\n            -0.002579547930508852,\n            0.2056179940700531,\n            -0.14614446461200714,\n            -0.11110267788171768,\n            0.09043771028518677,\n            0.135812908411026,\n            -0.3300320506095886,\n            0.290109783411026,\n            0.23399846255779266,\n            -0.04882314056158066,\n            -7.729629578534514e-05,\n            0.04754950851202011,\n            0.003435821272432804,\n            0.1115187332034111,\n            -0.08208155632019043,\n            0.018088344484567642,\n            -0.01600349321961403,\n            -0.025757616385817528,\n            0.060233402997255325,\n            -0.08445348590612411,\n            0.375010222196579,\n            0.7828134298324585,\n            -0.836024820804596,\n            0.041282471269369125,\n            -0.07747451961040497,\n            0.31279265880584717,\n            -0.05552798509597778,\n            -0.03274049609899521,\n            -0.1147448793053627,\n            -0.1660863310098648,\n            0.390122652053833,\n            0.29283249378204346,\n            -0.0705522671341896,\n            -0.2927100956439972,\n            0.038575850427150726,\n            -0.15336857736110687,\n            -0.028894517570734024,\n            -0.06372164189815521,\n            0.2578844130039215,\n            0.060502175241708755,\n            -0.14235782623291016,\n            0.6358739137649536,\n            -0.2645033001899719,\n            0.01847453974187374,\n            0.3809853792190552,\n            0.0059107388369739056,\n            -0.07365082949399948,\n            -0.17490413784980774,\n            0.26099810004234314,\n            0.38216090202331543,\n            -0.44192376732826233,\n            -0.1497800052165985,\n            0.11983825266361237,\n            0.05704215168952942,\n            -0.09331715852022171,\n            -0.027353238314390182,\n            0.07132093608379364,\n            0.013686291873455048,\n            -0.14973664283752441,\n            -0.6386663317680359,\n            -0.42794787883758545,\n            0.43632233142852783,\n            -0.022474655881524086,\n            0.011099671013653278,\n            0.08784982562065125,\n            0.046248968690633774,\n            0.011553826741874218,\n            0.0328642763197422,\n            0.08678832650184631,\n            0.3153251111507416,\n            -0.15444470942020416,\n            -0.5339609980583191,\n            0.10007581859827042,\n            -0.02821769379079342,\n            -0.3091129660606384,\n            -0.6009559631347656,\n            -0.555920422077179,\n            0.9594710469245911,\n            -0.5884919166564941,\n            -0.08316593617200851,\n            0.07074970006942749,\n            0.026868166401982307,\n            0.03690064698457718,\n            -0.2468167096376419,\n            0.20655325055122375,\n            0.2654767632484436,\n            -0.11032287031412125,\n            0.09603621065616608,\n            0.12746618688106537,\n            0.11097392439842224,\n            -0.046335164457559586,\n            0.2753968834877014,\n            -0.4040895402431488,\n            -0.20803606510162354,\n            0.29299837350845337,\n            -0.21050886809825897,\n            -0.02308674342930317,\n            0.32019543647766113,\n            -0.010012545622885227,\n            -0.07219666987657547,\n            0.03816547617316246,\n            -0.03670865297317505,\n            -0.023583250120282173,\n            -0.2030763179063797,\n            0.4087490737438202,\n            0.19682352244853973,\n            -0.061049312353134155,\n            -0.34018784761428833,\n            0.4121433198451996,\n            -0.10742263495922089,\n            -0.2883375287055969,\n            0.15564028918743134,\n            -0.014489974826574326,\n            -0.40427249670028687,\n            0.04029366746544838,\n            -0.46333804726600647,\n            -0.5811125636100769,\n            0.1686166524887085,\n            -0.08247993886470795,\n            0.02783152647316456,\n            -0.07444962859153748,\n            -0.11033248156309128,\n            0.17976728081703186,\n            -0.05866902321577072,\n            -0.037863120436668396,\n            0.016240332275629044,\n            0.08362828195095062,\n            0.04285397008061409,\n            -0.2676204442977905,\n            -0.18113869428634644,\n            0.10164932906627655,\n            0.5798585414886475,\n            -0.2936221659183502,\n            -0.16815273463726044,\n            0.3153108060359955,\n            0.1320323497056961,\n            0.29474350810050964,\n            -0.31565147638320923,\n            0.032277628779411316,\n            0.5137525796890259,\n            0.13915763795375824,\n            -0.08313784748315811,\n            0.0871160700917244,\n            0.07447603344917297,\n            -0.4863177537918091,\n            0.022499559447169304,\n            0.07244526594877243,\n            -0.1484450399875641,\n            -0.08256664127111435,\n            0.09993510693311691,\n            0.33980417251586914,\n            -0.5465939044952393,\n            -0.18684262037277222,\n            0.050183601677417755,\n            0.015223318710923195,\n            -0.32613685727119446,\n            0.2532300353050232,\n            0.21044038236141205,\n            -0.24877160787582397,\n            0.17659279704093933,\n            -0.14793306589126587,\n            0.054353710263967514,\n            -0.07312241941690445,\n            0.04128497466444969,\n            -0.0071349963545799255,\n            -0.17010675370693207,\n            0.3045605719089508,\n            -0.391606867313385,\n            0.19206605851650238,\n            0.10403380542993546,\n            -0.3808597922325134,\n            -0.016270365566015244,\n            -0.09313700348138809,\n            0.11184006929397583,\n            0.01242944784462452,\n            -0.03349926695227623,\n            -0.1107369139790535,\n            0.2315940409898758,\n            0.03170541673898697,\n            -0.48357459902763367,\n            0.21056240797042847,\n            -0.25072887539863586,\n            0.3221265375614166,\n            0.5108669400215149,\n            -0.6159838438034058,\n            -0.5540208220481873,\n            0.38405123353004456,\n            0.1323588639497757,\n            -0.11752784997224808,\n            0.07821227610111237,\n            0.0494898185133934,\n            0.28607267141342163,\n            -0.45723024010658264,\n            -0.5914809703826904,\n            -0.15741930902004242,\n            -0.09551641345024109,\n            -0.769051730632782,\n            -0.2119017094373703,\n            -0.8505933284759521,\n            0.025818098336458206,\n            0.11196669936180115,\n            0.013385393656790257,\n            -0.02640729956328869,\n            -0.061663247644901276,\n            -0.012524818070232868,\n            -0.8237857222557068,\n            -0.40553018450737,\n            -0.06807617098093033,\n            -0.07508324831724167,\n            -0.011943532153964043,\n            0.07591933757066727,\n            0.18625806272029877,\n            -0.14417743682861328,\n            0.0031204342376440763,\n            -0.031199704855680466,\n            -0.037418268620967865,\n            -0.062444642186164856,\n            0.0434197299182415,\n            -0.12462416291236877,\n            -0.256317675113678,\n            -0.0023087849840521812,\n            0.20042477548122406,\n            0.17625926434993744,\n            -0.21970611810684204,\n            0.1626158505678177,\n            -0.09550918638706207,\n            -0.10577445477247238,\n            -0.17239737510681152,\n            0.28190216422080994,\n            0.003485368099063635,\n            -0.24596424400806427,\n            0.5330491662025452,\n            -0.6179713010787964,\n            -0.19186368584632874,\n            0.04049135372042656,\n            0.005797799210995436,\n            0.10468537360429764,\n            -0.03522713482379913,\n            0.2554764151573181,\n            -0.6601210832595825,\n            0.3554987609386444,\n            -0.1528356373310089,\n            -0.2578294575214386,\n            -0.01912580616772175,\n            0.14837700128555298,\n            0.28032413125038147,\n            0.6525465250015259,\n            -0.16390740871429443,\n            -0.12456659972667694,\n            -0.04434182122349739,\n            0.44120529294013977,\n            -0.06832294911146164,\n            0.4077378511428833,\n            -0.07938709110021591,\n            0.23457404971122742,\n            -0.05966708064079285,\n            0.09640492498874664,\n            0.7555295825004578,\n            -0.3110663592815399,\n            0.035311225801706314,\n            0.25391876697540283,\n            0.09088675677776337,\n            0.03320888802409172,\n            -0.1745719611644745,\n            0.2270633578300476,\n            0.2851920425891876,\n            -0.07204318791627884,\n            -0.05483328923583031,\n            0.189837247133255,\n            -0.15304607152938843,\n            -0.08311894536018372,\n            -0.06649994850158691,\n            -0.0776129737496376,\n            0.11864881962537766,\n            -0.06670717149972916,\n            -0.00406235596165061,\n            -0.6984686255455017,\n            0.28291743993759155,\n            -0.04160117730498314,\n            -0.09169034659862518,\n            0.14924104511737823,\n            0.46138641238212585,\n            -0.29699283838272095,\n            -0.6411864757537842,\n            0.26037612557411194,\n            0.21487018465995789,\n            -0.20806393027305603,\n            -0.4174681007862091,\n            0.1901395320892334,\n            0.049021925777196884,\n            0.2822348475456238,\n            -0.03862098604440689,\n            0.029824024066329002,\n            0.2657202184200287,\n            -0.43108099699020386,\n            0.37041717767715454,\n            -0.025845345109701157,\n            -0.09200481325387955,\n            -0.017871620133519173,\n            0.281535267829895,\n            -0.20838744938373566,\n            -0.400356650352478,\n            0.4133286476135254,\n            -0.08745774626731873,\n            0.02171195112168789,\n            0.4766440987586975,\n            -0.24629971385002136,\n            0.2504408657550812,\n            -0.5850875973701477,\n            -0.49699774384498596,\n            0.7086884379386902,\n            -0.479250967502594,\n            0.6140879392623901,\n            0.0023341099731624126,\n            -0.06628652662038803,\n            -0.0873338133096695,\n            -0.2862805724143982,\n            0.28077220916748047,\n            0.030578527599573135,\n            -0.281633198261261,\n            -0.7042887806892395,\n            -0.03409203886985779,\n            0.3272986114025116,\n            0.3397904634475708,\n            -0.7069221138954163,\n            0.09408266842365265,\n            -0.05243761092424393,\n            -0.20503726601600647,\n            0.15679042041301727,\n            0.4723545014858246,\n            -0.39158886671066284,\n            0.17581138014793396,\n            0.10779093205928802,\n            -0.013951681554317474,\n            0.052481986582279205,\n            -0.36543500423431396,\n            0.29497984051704407,\n            0.4044850766658783,\n            -0.3766767382621765,\n            -0.07298431545495987,\n            0.9660398364067078,\n            0.27753373980522156,\n            -0.11616200953722,\n            0.05277060344815254,\n            -0.05379771068692207,\n            0.026094499975442886,\n            -0.011136082001030445,\n            -0.13593854010105133,\n            0.033518679440021515,\n            0.6947338581085205,\n            0.6335914134979248,\n            -0.06526267528533936,\n            0.019844267517328262,\n            0.10042254626750946,\n            -0.16847042739391327,\n            -0.15717101097106934,\n            -0.7462965250015259,\n            -0.0653005987405777,\n            0.057602036744356155,\n            0.010834889486432076,\n            -0.46870648860931396,\n            -0.1872870922088623,\n            0.3152116537094116,\n            0.0731910765171051,\n            -0.13902369141578674,\n            0.10666802525520325,\n            0.3094567656517029,\n            -0.926356315612793,\n            -0.38388797640800476,\n            -0.02191060781478882,\n            -0.005548040382564068,\n            -0.20935170352458954,\n            0.24779647588729858,\n            0.12304577976465225,\n            -0.2883053123950958,\n            0.019766222685575485,\n            -0.029659172520041466,\n            0.06051887571811676,\n            -0.01741836965084076,\n            0.04409812018275261,\n            0.011840295046567917,\n            -0.14320705831050873,\n            0.31673386693000793,\n            -0.069312185049057,\n            -0.00935965683311224,\n            0.019028477370738983,\n            -0.1078404039144516,\n            -0.12472966313362122,\n            0.10027194768190384,\n            0.31244829297065735,\n            -0.10855710506439209,\n            -0.3165830969810486,\n            0.4076120853424072,\n            0.05742274224758148,\n            0.17263729870319366,\n            0.3141464293003082,\n            -0.13655878603458405,\n            0.07613589614629745,\n            -0.10808823257684708,\n            -0.19837258756160736,\n            0.16735948622226715,\n            0.055960867553949356,\n            0.005388774909079075,\n            -0.30227115750312805,\n            -0.009724846109747887,\n            -0.11610261350870132,\n            0.05133519321680069,\n            -0.029441826045513153,\n            0.06810834258794785,\n            -0.13311177492141724,\n            0.2196519374847412,\n            0.19138571619987488,\n            -0.2621391713619232,\n            0.11996466666460037,\n            -0.05961257219314575,\n            0.1763487011194229,\n            -0.10918399691581726,\n            -0.14629563689231873,\n            0.5217060446739197,\n            -0.0012722538085654378,\n            0.08564157783985138,\n            -0.6640400290489197,\n            -0.41702714562416077,\n            0.045037489384412766,\n            -0.059789709746837616,\n            -0.05092751979827881,\n            0.10446680337190628,\n            -0.05335049331188202,\n            0.0846114456653595,\n            0.04981796815991402,\n            -0.14310699701309204,\n            0.01863306201994419,\n            -0.0474325567483902,\n            0.23124581575393677,\n            -0.6166588068008423,\n            -0.7533295154571533,\n            -1.1133880615234375,\n            -0.1241607666015625,\n            -0.5540894865989685,\n            0.2806711494922638,\n            -0.4259497821331024,\n            -0.07380827516317368,\n            0.009988346137106419,\n            0.3110937178134918,\n            0.0072226757183671,\n            0.2422133982181549,\n            -0.351376473903656,\n            -0.5103139877319336,\n            0.5470908284187317,\n            -0.14952707290649414,\n            -0.005531645845621824,\n            -0.24725599586963654,\n            0.1639375537633896,\n            0.07172811776399612,\n            -0.1566568911075592,\n            0.32833099365234375,\n            0.06875353306531906,\n            -0.17773276567459106,\n            -0.09706790000200272,\n            -0.019849322736263275,\n            0.1257631778717041,\n            0.02103520557284355,\n            0.12721672654151917,\n            0.012451020069420338,\n            0.039879027754068375,\n            0.17779605090618134,\n            -0.09887054562568665,\n            -0.08146625012159348,\n            0.05893132835626602,\n            0.18479469418525696,\n            -0.2479601502418518,\n            -0.26928654313087463,\n            0.3720027506351471,\n            -0.45930227637290955,\n            0.3673400282859802,\n            0.016545426100492477,\n            0.13507097959518433,\n            -0.006458526011556387,\n            0.036685895174741745,\n            0.309455007314682,\n            -0.23917894065380096,\n            -0.11758854985237122,\n            0.2146540731191635,\n            -0.11578961461782455,\n            0.006646907888352871,\n            -0.04229713976383209,\n            0.09812270104885101,\n            0.06730903685092926,\n            0.28935620188713074,\n            -0.02212020941078663,\n            0.007341589778661728,\n            -0.1257125288248062,\n            -0.4639318287372589,\n            0.41743314266204834,\n            0.40524497628211975,\n            -0.20389464497566223,\n            0.1286880075931549,\n            0.05365758389234543,\n            -0.14487741887569427,\n            0.1511518359184265,\n            0.11219878494739532,\n            0.13080842792987823,\n            -0.175934836268425,\n            -0.08939457684755325,\n            0.16476190090179443,\n            -0.061722587794065475,\n            0.15382836759090424,\n            0.15293729305267334,\n            -0.23814627528190613,\n            -0.778872013092041,\n            0.2813372313976288,\n            0.20388194918632507,\n            -0.34535032510757446,\n            -0.014981378801167011,\n            0.1560390293598175,\n            0.534339189529419,\n            0.7075706124305725,\n            -0.20866382122039795,\n            0.050050001591444016,\n            -0.030285198241472244,\n            0.430580735206604,\n            0.06858251988887787,\n            0.32321590185165405,\n            0.006104054860770702,\n            0.11919829249382019,\n            -0.09377042204141617,\n            -0.028785547241568565,\n            0.489607572555542,\n            -0.321664422750473,\n            0.020770607516169548,\n            0.5259214639663696,\n            -0.0682888925075531,\n            0.10569659620523453,\n            -0.18257132172584534,\n            0.2565872073173523,\n            0.2177353799343109,\n            0.029641704633831978,\n            0.0678875744342804,\n            0.1679811030626297,\n            -0.04851052165031433,\n            -0.1633165180683136,\n            -0.007416700944304466,\n            -0.06638842821121216,\n            0.06177712231874466,\n            -0.0709109827876091,\n            -0.11213518679141998,\n            -0.20582593977451324,\n            0.7092531323432922,\n            0.43438467383384705,\n            -0.0060964771546423435,\n            -0.12442151457071304,\n            -0.008676152676343918,\n            0.21390584111213684,\n            -0.014475004747509956,\n            -0.7601429224014282,\n            0.15622451901435852,\n            -0.3261253833770752,\n            0.005610095337033272,\n            -0.5111817121505737,\n            -0.003055301494896412,\n            0.32741662859916687,\n            -0.022710084915161133,\n            -0.24255472421646118,\n            -0.6487520933151245,\n            0.08797790110111237,\n            0.2754897177219391,\n            -0.2213398665189743,\n            -0.17206217348575592,\n            0.1177680641412735,\n            0.16599608957767487,\n            -0.19922694563865662,\n            -0.07098120450973511,\n            -0.1628963202238083,\n            0.03356413170695305,\n            -0.24303652346134186,\n            -0.2067747414112091,\n            0.1192406490445137,\n            -0.020932691171765327,\n            0.07735628634691238,\n            0.24762177467346191,\n            -0.3007707893848419,\n            -0.43011191487312317,\n            -0.07597793638706207,\n            0.2528873085975647,\n            -0.3795652985572815,\n            0.14651291072368622,\n            0.07552091032266617,\n            0.026706784963607788,\n            -0.11118876934051514,\n            0.0460294634103775,\n            0.4268769323825836,\n            0.32645294070243835,\n            -0.09493713080883026,\n            0.18892213702201843,\n            0.17980137467384338,\n            0.06521839648485184,\n            0.03702569752931595,\n            0.05443478748202324,\n            -0.030978504568338394,\n            -0.11806164681911469,\n            -0.20229215919971466,\n            0.6260767579078674,\n            0.6068219542503357,\n            -0.060956377536058426,\n            0.05200914293527603,\n            0.04499080404639244,\n            -0.09300816804170609,\n            0.0501115508377552,\n            0.9676806926727295,\n            -0.12394528090953827,\n            0.17313909530639648,\n            -0.0274575874209404,\n            1.0245190858840942,\n            -0.24425312876701355,\n            0.3827340602874756,\n            0.270155131816864,\n            -0.7169324159622192,\n        ],\n        b1: &[\n            -0.518636167049408,\n            0.7074531316757202,\n            -0.4965735971927643,\n            0.6063699126243591,\n            -0.3258720934391022,\n            0.4608336389064789,\n            0.8324258327484131,\n            -0.6118353605270386,\n            0.8226121664047241,\n            0.3534131944179535,\n            -0.43312883377075195,\n            -0.05448569357395172,\n            -0.5826212167739868,\n            0.8478071689605713,\n            0.23062080144882202,\n            -0.30911386013031006,\n            -0.5776869058609009,\n            0.5107449293136597,\n            0.18762148916721344,\n            0.2889731228351593,\n            -0.5579098463058472,\n            0.7818499207496643,\n            0.7910265922546387,\n            -0.4228874444961548,\n            0.6197248697280884,\n            -0.4563252627849579,\n            0.27223169803619385,\n            -0.2859383523464203,\n            -0.4862801730632782,\n            -0.7853735089302063,\n            -0.1534343808889389,\n            -0.5592636466026306,\n            -0.6364999413490295,\n            -0.5210756063461304,\n            0.3506944477558136,\n            -0.5348182916641235,\n            -0.5098673105239868,\n            0.45690369606018066,\n            -0.3907462954521179,\n            0.8493368029594421,\n        ],\n        w2: &[\n            -0.525189995765686,\n            0.44041961431503296,\n            -0.4107511341571808,\n            0.3741440176963806,\n            -0.02630656771361828,\n            0.27733951807022095,\n            0.3907228410243988,\n            -0.05409616604447365,\n            0.3991526663303375,\n            0.24264170229434967,\n            -0.657869279384613,\n            -0.3758363425731659,\n            -0.5133534669876099,\n            0.3480457663536072,\n            0.5088834166526794,\n            0.0942729115486145,\n            -0.4167974889278412,\n            0.4895906448364258,\n            0.17553496360778809,\n            0.3702719211578369,\n            -0.5372111201286316,\n            -0.1560969352722168,\n            -0.30670106410980225,\n            -0.48799967765808105,\n            0.4005548357963562,\n            -0.3075137138366699,\n            0.656658947467804,\n            -0.4914362132549286,\n            -0.36532747745513916,\n            -0.5505443811416626,\n            0.1328023225069046,\n            -0.3564044237136841,\n            -0.467242956161499,\n            -0.3465808629989624,\n            0.4501214027404785,\n            -0.4742763936519623,\n            -0.35285890102386475,\n            0.46182748675346375,\n            -0.28942185640335083,\n            0.2825036346912384,\n            -0.1725425124168396,\n            -0.17012473940849304,\n            0.5306965708732605,\n            -0.34125325083732605,\n            0.21301832795143127,\n            -0.49370092153549194,\n            -0.06135714799165726,\n            0.5665233135223389,\n            -0.01510544028133154,\n            -0.0015591675182804465,\n            0.4308379292488098,\n            0.09525317698717117,\n            0.06129995733499527,\n            -0.06124228611588478,\n            -0.28377535939216614,\n            -0.038286369293928146,\n            0.19221894443035126,\n            -0.45041826367378235,\n            -0.4307488799095154,\n            -0.30516454577445984,\n            0.3670405447483063,\n            -0.1779327690601349,\n            -0.36808863282203674,\n            0.344722718000412,\n            -0.2691067159175873,\n            0.5803861021995544,\n            -0.42112261056900024,\n            0.1169033870100975,\n            0.35742461681365967,\n            0.16161565482616425,\n            0.44920068979263306,\n            0.2572435438632965,\n            0.263318806886673,\n            0.7236857414245605,\n            -0.2759736180305481,\n            0.37376394867897034,\n            0.37350600957870483,\n            -0.4067005515098572,\n            0.18588955700397491,\n            -0.4281120300292969,\n            0.4204690456390381,\n            -0.448592871427536,\n            0.11808016151189804,\n            -0.4660882353782654,\n            0.33337321877479553,\n            -0.11569353938102722,\n            -0.589764416217804,\n            -0.17854063212871552,\n            -0.44001755118370056,\n            0.7101057767868042,\n            0.057653751224279404,\n            0.3937684893608093,\n            0.257487416267395,\n            -0.38924211263656616,\n            0.08511713892221451,\n            0.10950952023267746,\n            0.0917661041021347,\n            -0.25429144501686096,\n            0.6342174410820007,\n            -0.15891794860363007,\n            -0.021509289741516113,\n            0.535305380821228,\n            0.28721731901168823,\n            -0.32432296872138977,\n            -0.26846611499786377,\n            0.07051636278629303,\n            -0.12710770964622498,\n            0.14568471908569336,\n            0.6293584704399109,\n            0.4198862612247467,\n            -0.8883509039878845,\n            0.5271400809288025,\n            0.17345309257507324,\n            0.1771862506866455,\n            -0.214192733168602,\n            0.17817191779613495,\n            0.44757506251335144,\n            0.04112042486667633,\n            0.6819244027137756,\n            -0.7277362942695618,\n            0.19224950671195984,\n            -0.2905896008014679,\n            0.5791959762573242,\n            -0.4898945093154907,\n            0.47323065996170044,\n            -0.40173205733299255,\n            -0.36294564604759216,\n            0.6861273050308228,\n            -0.2955973744392395,\n            -0.19740070402622223,\n            0.4044080674648285,\n            -0.11244003474712372,\n            0.58234703540802,\n            -0.31175708770751953,\n            -0.3454722762107849,\n            0.12274620682001114,\n            0.29693669080734253,\n            -0.41234102845191956,\n            -0.1583351045846939,\n            -0.2763107419013977,\n            0.34174609184265137,\n            -0.7301539182662964,\n            -0.4137580394744873,\n            0.5135444402694702,\n            -0.19664454460144043,\n            0.3913029730319977,\n            -0.47720086574554443,\n            0.2519521415233612,\n            0.3860025703907013,\n            0.4073657691478729,\n            0.06604084372520447,\n            0.32879960536956787,\n            0.4341438114643097,\n            0.4072171449661255,\n            -0.3755425810813904,\n            0.29250237345695496,\n            0.4723772704601288,\n            -0.39177075028419495,\n            0.3535446524620056,\n            -0.5977760553359985,\n            -0.11535356938838959,\n            -0.8606860637664795,\n            0.3202466070652008,\n            0.534551203250885,\n            -0.10786011070013046,\n            0.5766461491584778,\n            -1.0034655332565308,\n            -0.08353354036808014,\n            0.20165663957595825,\n            -0.8530645370483398,\n            0.2801732122898102,\n            -0.2713226079940796,\n            0.460101842880249,\n            0.5550602078437805,\n            0.11862986534833908,\n            -0.8431587219238281,\n            -0.41269758343696594,\n            -0.36862486600875854,\n            0.08385410159826279,\n            0.1634000688791275,\n            -0.22930988669395447,\n            -0.39085301756858826,\n            0.8845512270927429,\n            0.2522968053817749,\n            0.3779301643371582,\n            0.3454946279525757,\n            -0.14984408020973206,\n            0.2937467098236084,\n            0.3651972711086273,\n            1.1317671537399292,\n            -0.4535387456417084,\n            0.07272656261920929,\n            -0.29987066984176636,\n            -0.03405649587512016,\n            0.1012202724814415,\n            -0.12492970377206802,\n            -0.048626113682985306,\n            -0.3150321841239929,\n            -0.4124220013618469,\n            -0.7775830030441284,\n            0.25562793016433716,\n            -0.4026365876197815,\n            0.27681317925453186,\n            -0.3169574439525604,\n            0.414761483669281,\n            -0.37095436453819275,\n            -0.2815983295440674,\n            0.6821384429931641,\n            -0.23631460964679718,\n            -0.391885370016098,\n            0.32081300020217896,\n            0.029309673234820366,\n            0.3151959478855133,\n            -0.23872429132461548,\n            -0.2680605947971344,\n            0.2245175689458847,\n            0.28024742007255554,\n            -0.5187304615974426,\n            -0.17155316472053528,\n            -0.18662460148334503,\n            0.44196388125419617,\n            -0.7731465697288513,\n            -0.39956656098365784,\n            0.4926709830760956,\n            -0.2705640196800232,\n            0.5851831436157227,\n            -0.28655296564102173,\n            0.21914565563201904,\n            0.42291808128356934,\n            0.3754308521747589,\n            0.12476411461830139,\n            0.4564429223537445,\n            0.41455739736557007,\n            0.24721866846084595,\n            -0.39062193036079407,\n            0.47335484623908997,\n            0.4390261769294739,\n            -0.2776612639427185,\n            0.36352279782295227,\n            -0.4658246338367462,\n            0.5458199977874756,\n            0.2368425875902176,\n            -0.28375834226608276,\n            -0.21349868178367615,\n            -0.12575705349445343,\n            -0.314109742641449,\n            0.2133757472038269,\n            -0.4604170322418213,\n            -0.5457999110221863,\n            0.347943514585495,\n            0.3864844739437103,\n            0.2128392457962036,\n            0.06274894624948502,\n            -0.5941122174263,\n            -0.4954967200756073,\n            0.3897503614425659,\n            0.6681548953056335,\n            0.011607992462813854,\n            -0.5754616260528564,\n            -0.4551040530204773,\n            0.14332124590873718,\n            0.5475043058395386,\n            0.35485684871673584,\n            0.516143798828125,\n            -0.43508225679397583,\n            -0.2927212119102478,\n            -0.38220953941345215,\n            0.22585861384868622,\n            -0.49666696786880493,\n            -0.47814127802848816,\n            0.6455125212669373,\n            -0.4184291362762451,\n            0.5714888572692871,\n            -0.06349734216928482,\n            -0.337534636259079,\n            0.08359762281179428,\n            -0.6663680672645569,\n            -0.05490731820464134,\n            0.27789443731307983,\n            0.44944822788238525,\n            -0.12919825315475464,\n            -0.24064187705516815,\n            0.3863179683685303,\n            -0.21315856277942657,\n            -0.010893935337662697,\n            -0.49465489387512207,\n            -0.1953386515378952,\n            0.4405977129936218,\n            -0.362499862909317,\n            -0.15224213898181915,\n            0.503758430480957,\n            0.13674911856651306,\n            0.24574719369411469,\n            -0.2888658046722412,\n            -0.5966756939888,\n            0.24279867112636566,\n            0.43060633540153503,\n            -0.2950061857700348,\n            -0.3071616590023041,\n            -0.31878525018692017,\n            0.5719135999679565,\n            -0.46542906761169434,\n            -0.33102989196777344,\n            0.2584391236305237,\n            -0.3341030776500702,\n            0.35185420513153076,\n            -0.5347702503204346,\n            0.2021929919719696,\n            0.3747906982898712,\n            0.3017856478691101,\n            0.4192887842655182,\n            0.2290816456079483,\n            0.26369208097457886,\n            0.30613088607788086,\n            -0.2766033113002777,\n            0.48649486899375916,\n            0.28767234086990356,\n            -0.31826111674308777,\n            0.47518086433410645,\n            -0.2643313407897949,\n            0.38674306869506836,\n            -0.20252466201782227,\n            0.2426745593547821,\n            -0.2963939607143402,\n            0.35027387738227844,\n            -0.40756842494010925,\n            -0.17158618569374084,\n            0.6504075527191162,\n            -0.23639068007469177,\n            -0.5520732998847961,\n            0.34597641229629517,\n            0.12782879173755646,\n            0.46479496359825134,\n            -0.4128115773200989,\n            -0.4125882685184479,\n            0.20131008327007294,\n            0.4997844099998474,\n            -0.21766024827957153,\n            -0.2570849657058716,\n            -0.1471637338399887,\n            0.5070111155509949,\n            -0.6722937226295471,\n            -0.5443961024284363,\n            0.5341878533363342,\n            -0.29976886510849,\n            0.6135430932044983,\n            -0.3595261573791504,\n            0.49033448100090027,\n            0.3653552234172821,\n            0.2656362056732178,\n            0.10900922119617462,\n            0.4813465476036072,\n            0.41922783851623535,\n            0.2692069411277771,\n            -0.4056242108345032,\n            0.33006641268730164,\n            0.27100467681884766,\n            -0.5306692123413086,\n            0.2701503336429596,\n            -0.6044796705245972,\n        ],\n        b2: &[\n            0.044342152774333954,\n            -0.28361865878105164,\n            -0.0350283607840538,\n            -0.129508376121521,\n            -0.006770995445549488,\n            -0.24053514003753662,\n            0.3617520332336426,\n            -0.3381704092025757,\n            -0.24953331053256989,\n        ],\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm64/cortex_a55.rs",
    "content": "use crate::frame::mmm::CostModel;\npub fn model() -> CostModel<'static> {\n    CostModel {\n        big_product_mkn_threshold: 263214080.0,\n        big_product_kernel_choice: \"arm64simd_mmm_f32_12x8_a55\",\n        kernels: &[\n            \"arm64simd_mmm_f32_12x8_a53\",\n            \"arm64simd_mmm_f32_12x8_a55\",\n            \"arm64simd_mmm_f32_12x8_gen\",\n            \"arm64simd_mmm_f32_16x4_a53\",\n            \"arm64simd_mmm_f32_16x4_a55\",\n            \"arm64simd_mmm_f32_16x4_gen\",\n            \"arm64simd_mmm_f32_24x4_a53\",\n            \"arm64simd_mmm_f32_24x4_a55\",\n            \"arm64simd_mmm_f32_24x4_gen\",\n            \"arm64simd_mmm_f32_8x8_a53\",\n            \"arm64simd_mmm_f32_8x8_a55\",\n            \"arm64simd_mmm_f32_8x8_gen\",\n            \"generic_f32_4x4\",\n        ],\n        mrs: &[4, 8, 12, 16, 24],\n        nrs: &[4, 8],\n        feat_norm_mean: &[\n            5.27886946965165,\n            6.250454700699139,\n            5.241114620514529,\n            16.770438790865423,\n            1.540625,\n            0.770625,\n            3.518125,\n            0.8775,\n            5.560625,\n            0.923125,\n            7.453125,\n            0.943125,\n            11.613125,\n            0.9575,\n            1.509375,\n            0.771875,\n            3.581875,\n            0.898125,\n        ],\n        feat_norm_stddev: &[\n            0.9509890252368828,\n            0.6930410342704738,\n            1.0261938261805659,\n            1.600617293156687,\n            1.0981118382819681,\n            0.42043086158725473,\n            2.338198341538852,\n            0.3278623949159141,\n            3.494112850120205,\n            0.26639300736881577,\n            4.56457037785321,\n            0.2316036147710134,\n            7.043415558830455,\n            0.20172691937369452,\n            1.0925484471523377,\n            0.4196236222795381,\n            2.273113830052299,\n            0.30248385804039707,\n        ],\n        w1: &[\n            -0.13682155311107635,\n            -0.1783919334411621,\n            0.26539096236228943,\n            0.19552235305309296,\n            -0.10618806630373001,\n            0.13501706719398499,\n            0.21776071190834045,\n            0.08390733599662781,\n            -0.2215081751346588,\n            0.18829140067100525,\n            -0.20535176992416382,\n            -0.0463368222117424,\n            0.05815611779689789,\n            -0.13855215907096863,\n            -0.024539709091186523,\n            -0.4855460524559021,\n            -0.414151668548584,\n            -0.7574286460876465,\n            0.8987273573875427,\n            0.5316352844238281,\n            0.8244147896766663,\n            0.8388808369636536,\n            -0.02545193023979664,\n            0.04357631504535675,\n            -0.007071307860314846,\n            0.18223997950553894,\n            -0.04292978346347809,\n            0.004330582916736603,\n            -0.013073648326098919,\n            -0.04028080403804779,\n            -0.09901119023561478,\n            0.062175191938877106,\n            -0.006247916258871555,\n            0.009531030431389809,\n            0.09731218218803406,\n            0.004297865089029074,\n            0.6260067224502563,\n            -0.10042139887809753,\n            0.807989239692688,\n            0.6866835951805115,\n            -0.018399836495518684,\n            -0.07194910198450089,\n            -0.18889868259429932,\n            -0.07729395478963852,\n            -0.03907148540019989,\n            0.017019111663103104,\n            0.06159460172057152,\n            -0.02395886555314064,\n            0.23730705678462982,\n            -0.15546496212482452,\n            -0.04492897167801857,\n            -0.003982013091444969,\n            -0.09160511195659637,\n            0.03185845538973808,\n            -0.27577653527259827,\n            0.5699247121810913,\n            -0.6027079820632935,\n            -0.4136800467967987,\n            -0.04364140331745148,\n            -0.11226192861795425,\n            0.16899903118610382,\n            -0.11524038016796112,\n            0.12308179587125778,\n            0.027925828471779823,\n            -0.06269104778766632,\n            0.11644940823316574,\n            -0.369202196598053,\n            0.3338239789009094,\n            -0.06509242206811905,\n            0.2303273230791092,\n            0.018171854317188263,\n            -0.08709719777107239,\n            0.18228614330291748,\n            -0.071574367582798,\n            -0.012407014146447182,\n            -0.284942090511322,\n            -0.1326635330915451,\n            -0.08634418249130249,\n            0.11018547415733337,\n            -0.09423547983169556,\n            0.13697068393230438,\n            -0.03515861555933952,\n            0.014629656448960304,\n            -0.21159854531288147,\n            0.15693463385105133,\n            -0.021487032994627953,\n            0.032396819442510605,\n            0.028369005769491196,\n            0.08724819868803024,\n            -0.13204769790172577,\n            0.4691336452960968,\n            0.1237262561917305,\n            -0.06020978465676308,\n            0.24037614464759827,\n            0.05237792432308197,\n            -0.10641840100288391,\n            0.1820996105670929,\n            0.6079273819923401,\n            -0.4903985857963562,\n            0.40744978189468384,\n            0.43370547890663147,\n            0.5092437863349915,\n            0.0810965895652771,\n            0.4670366048812866,\n            -0.11692337691783905,\n            -0.013550599105656147,\n            -0.364605575799942,\n            0.34470170736312866,\n            -0.01755279302597046,\n            0.30621764063835144,\n            0.35784396529197693,\n            0.42736300826072693,\n            0.022546129301190376,\n            0.08497388660907745,\n            0.07601173967123032,\n            0.0696730837225914,\n            -0.21918217837810516,\n            0.6236687898635864,\n            -0.0793512761592865,\n            -0.0668395534157753,\n            0.010559543035924435,\n            0.46621084213256836,\n            -0.2632196843624115,\n            -0.03991322219371796,\n            0.1392994076013565,\n            0.003188274335116148,\n            -0.2655166983604431,\n            -0.22143644094467163,\n            -0.19157607853412628,\n            -0.39395904541015625,\n            -0.021266555413603783,\n            0.08848410844802856,\n            0.08152330666780472,\n            0.013220606371760368,\n            -0.18424198031425476,\n            0.05234640836715698,\n            0.05919161066412926,\n            -0.16255362331867218,\n            -0.04549096152186394,\n            0.044437166303396225,\n            0.21704396605491638,\n            -0.5149197578430176,\n            -0.6047705411911011,\n            -0.8048356175422668,\n            -0.36901935935020447,\n            -0.19035962224006653,\n            1.3252514600753784,\n            0.19824109971523285,\n            0.07630860805511475,\n            -0.011165079660713673,\n            0.011559495702385902,\n            0.10554458945989609,\n            -0.12820255756378174,\n            0.29352235794067383,\n            -0.06662449240684509,\n            -0.15792854130268097,\n            0.0345480814576149,\n            -0.04881494492292404,\n            -0.06912268698215485,\n            -0.00013739474525209516,\n            -0.1597173660993576,\n            0.34323570132255554,\n            0.34446775913238525,\n            0.3795395493507385,\n            0.017453864216804504,\n            0.1102253794670105,\n            0.04026523232460022,\n            0.1107630804181099,\n            0.10295291990041733,\n            0.5326219201087952,\n            0.1749747395515442,\n            -0.2661803066730499,\n            0.11752097308635712,\n            0.08010037988424301,\n            -0.5501991510391235,\n            -0.059987347573041916,\n            -0.04125819355249405,\n            0.16356444358825684,\n            0.020170046016573906,\n            -0.08306766301393509,\n            0.17777052521705627,\n            0.4687126874923706,\n            0.7723219394683838,\n            0.7309747934341431,\n            -0.019829215481877327,\n            0.10945341736078262,\n            0.06796073168516159,\n            -0.12042505294084549,\n            -0.26762208342552185,\n            0.10846878588199615,\n            0.013867417350411415,\n            0.01077105849981308,\n            -0.10193657130002975,\n            -0.1757654845714569,\n            -0.245382159948349,\n            0.20442898571491241,\n            0.10115985572338104,\n            0.2514199912548065,\n            -0.3793720304965973,\n            -0.6926521062850952,\n            -0.6686761975288391,\n            -0.607191264629364,\n            0.16187654435634613,\n            -0.0073340232484042645,\n            -0.09948350489139557,\n            -0.21431320905685425,\n            -0.12334707379341125,\n            -0.15290899574756622,\n            -0.026063116267323494,\n            0.26553207635879517,\n            0.18921764194965363,\n            -0.1665697544813156,\n            -0.00264778733253479,\n            0.20274107158184052,\n            0.3660823404788971,\n            -0.35731416940689087,\n            0.50246661901474,\n            0.2781502604484558,\n            0.1629776805639267,\n            -0.03493829071521759,\n            -0.16012291610240936,\n            -0.08139592409133911,\n            -0.1440155804157257,\n            0.32721832394599915,\n            0.1312151998281479,\n            0.17874418199062347,\n            0.06143738701939583,\n            -0.05158458650112152,\n            0.4802771806716919,\n            -0.6857288479804993,\n            0.08245638012886047,\n            -0.09577414393424988,\n            -0.12872998416423798,\n            0.16155612468719482,\n            0.24089869856834412,\n            0.44030725955963135,\n            -0.30994167923927307,\n            0.12139064073562622,\n            0.029418930411338806,\n            -0.051672156900167465,\n            -0.10080718994140625,\n            -0.007311842869967222,\n            -0.15189751982688904,\n            -0.1559375822544098,\n            0.2731820344924927,\n            -0.03627878054976463,\n            0.10538394004106522,\n            0.15048423409461975,\n            0.12981411814689636,\n            0.0002639668236952275,\n            0.05666665732860565,\n            0.08173252642154694,\n            -0.16131722927093506,\n            -0.043261025100946426,\n            -0.14845971763134003,\n            -0.29335740208625793,\n            0.039398159831762314,\n            -0.02791670151054859,\n            0.22897064685821533,\n            -0.12178067117929459,\n            -0.4062419831752777,\n            0.3934949040412903,\n            -0.05093907564878464,\n            -0.06126153841614723,\n            -0.07318481802940369,\n            -0.08793392032384872,\n            -0.01818496361374855,\n            -0.24753189086914062,\n            -0.30580347776412964,\n            0.44876909255981445,\n            0.5379880666732788,\n            0.11587893962860107,\n            0.2174995243549347,\n            -0.035063862800598145,\n            -0.0010147193679586053,\n            -0.12281838059425354,\n            -0.21301835775375366,\n            0.3645245432853699,\n            0.39920729398727417,\n            -0.45564430952072144,\n            0.03503882512450218,\n            0.6949061155319214,\n            -0.5742982625961304,\n            0.38680514693260193,\n            -0.018345845863223076,\n            0.04529440030455589,\n            -0.04468340799212456,\n            -0.020917288959026337,\n            0.2523670792579651,\n            -0.4574699103832245,\n            0.17178472876548767,\n            -0.12147565186023712,\n            0.043810319155454636,\n            -0.17998050153255463,\n            -0.09663069248199463,\n            -0.03498067706823349,\n            0.06111514940857887,\n            -0.11410824209451675,\n            0.18208050727844238,\n            -0.09109053015708923,\n            0.08489643037319183,\n            0.15014725923538208,\n            0.18506401777267456,\n            -0.060843177139759064,\n            -0.11932594329118729,\n            0.11290943622589111,\n            -0.23226700723171234,\n            -0.2114422470331192,\n            -0.36001038551330566,\n            -0.29864072799682617,\n            -0.05599717050790787,\n            -0.21294310688972473,\n            -0.1301364004611969,\n            -0.4993196725845337,\n            0.097460076212883,\n            0.030209479853510857,\n            0.35134217143058777,\n            -0.9156147837638855,\n            0.0173207875341177,\n            -0.9142565131187439,\n            0.13512593507766724,\n            -0.1926516443490982,\n            -0.2812888026237488,\n            0.04805266484618187,\n            0.5790673494338989,\n            -0.28300249576568604,\n            -0.10372477024793625,\n            0.2964925169944763,\n            0.16425621509552002,\n            -0.25588271021842957,\n            0.37744808197021484,\n            -0.07827199995517731,\n            -0.7785226702690125,\n            -0.4873232841491699,\n            -0.0240982286632061,\n            -0.31732890009880066,\n            -0.7271391749382019,\n            -0.40648236870765686,\n            -0.08706668019294739,\n            -0.0876365602016449,\n            -0.08107846975326538,\n            0.049622420221567154,\n            0.5049374103546143,\n            -0.09109669923782349,\n            -0.2958216369152069,\n            0.23400314152240753,\n            0.0727144181728363,\n            -0.06163109838962555,\n            -0.3235352635383606,\n            -0.08323507010936737,\n            0.06926267594099045,\n            0.12505480647087097,\n            0.06806384027004242,\n            -0.1783592253923416,\n            -0.09036792814731598,\n            0.007250780239701271,\n            0.07478834688663483,\n            0.37752634286880493,\n            0.10522382706403732,\n            -0.3126020133495331,\n            -0.339804470539093,\n            -0.2922729253768921,\n            -0.04612985998392105,\n            0.06431944668292999,\n            0.08483731746673584,\n            0.12883307039737701,\n            -0.015924949198961258,\n            0.10468991845846176,\n            -0.3394957184791565,\n            0.23376204073429108,\n            -0.22720825672149658,\n            0.005506275221705437,\n            -0.22926953434944153,\n            -0.10148110240697861,\n            0.06526672840118408,\n            -0.2586720287799835,\n            -0.32853958010673523,\n            0.3440588712692261,\n            -0.11197478324174881,\n            -0.24647162854671478,\n            0.32472386956214905,\n            0.18955329060554504,\n            0.22783295810222626,\n            0.27004650235176086,\n            0.06792190670967102,\n            -0.25404539704322815,\n            -0.0421239472925663,\n            0.19141103327274323,\n            -0.1919824779033661,\n            0.024490466341376305,\n            -0.45774775743484497,\n            0.15080632269382477,\n            -0.21607035398483276,\n            -0.15506379306316376,\n            -0.4421549439430237,\n            -0.3747740089893341,\n            -0.40712970495224,\n            -0.01002188865095377,\n            -0.18514835834503174,\n            -0.052659012377262115,\n            -0.009491002187132835,\n            -0.04560127854347229,\n            0.5816720724105835,\n            -0.8684999942779541,\n            -0.6074734330177307,\n            -0.6023196578025818,\n            0.09026342630386353,\n            -0.8521136045455933,\n            -0.677777886390686,\n            -0.7927519083023071,\n            0.05012498050928116,\n            0.006620208732783794,\n            0.09600439667701721,\n            0.006934305187314749,\n            -0.41822823882102966,\n            0.5416979193687439,\n            1.3451576232910156,\n            0.6131516098976135,\n            -0.1447380781173706,\n            0.09429032355546951,\n            0.06888633966445923,\n            0.09988542646169662,\n            -0.09572823345661163,\n            0.09141702950000763,\n            0.05828794091939926,\n            -0.20784544944763184,\n            -0.14200495183467865,\n            0.014049896970391273,\n            -0.081334687769413,\n            0.15918458998203278,\n            0.001768372836522758,\n            0.009856577031314373,\n            0.5256384611129761,\n            0.49961280822753906,\n            0.5969673991203308,\n            0.37020817399024963,\n            -0.07463415712118149,\n            -0.0038648881018161774,\n            0.014317997731268406,\n            0.07256675511598587,\n            0.27220791578292847,\n            -0.14287996292114258,\n            -0.18170645833015442,\n            -0.021593274548649788,\n            -0.15909305214881897,\n            0.3259168863296509,\n            -0.11064229905605316,\n            0.12034989148378372,\n            0.36166661977767944,\n            -0.21680544316768646,\n            -0.14505243301391602,\n            -0.24518895149230957,\n            -0.054052721709012985,\n            0.11477477848529816,\n            0.10946492105722427,\n            -0.004644579254090786,\n            -0.11873581260442734,\n            0.00934956781566143,\n            0.026955196633934975,\n            -0.0947655513882637,\n            -0.0432097427546978,\n            0.2264525443315506,\n            0.4585563540458679,\n            -0.2117093950510025,\n            0.06864829361438751,\n            0.01817937195301056,\n            -0.09130346775054932,\n            -0.031736359000205994,\n            -0.6623827219009399,\n            0.07924489676952362,\n            0.30316102504730225,\n            0.06474705785512924,\n            0.12052184343338013,\n            -0.06878554821014404,\n            0.048135798424482346,\n            0.14442582428455353,\n            -0.1945008486509323,\n            0.16308918595314026,\n            0.13180820643901825,\n            -0.3005691170692444,\n            -0.08318639546632767,\n            -0.0371159091591835,\n            -0.036223117262125015,\n            0.27411049604415894,\n            -0.008904200047254562,\n            -0.21584218740463257,\n            -0.22458405792713165,\n            -0.2840893864631653,\n            0.9380438327789307,\n            -0.026274412870407104,\n            -0.03674294427037239,\n            -0.039288733154535294,\n            0.20259428024291992,\n            -0.2627299726009369,\n            -0.03588804602622986,\n            -0.09061996638774872,\n            0.0026293552946299314,\n            -1.1599351167678833,\n            -0.0888570249080658,\n            0.3020864427089691,\n            0.10419020056724548,\n            -0.2301473766565323,\n            -0.2372182309627533,\n            0.255910724401474,\n            -0.9108321666717529,\n            -0.17266617715358734,\n            -0.21715109050273895,\n            -0.4768790900707245,\n            0.02349638193845749,\n            0.06996935606002808,\n            0.2306048572063446,\n            -0.2647320032119751,\n            -0.5029106140136719,\n            0.18124276399612427,\n            0.05404527485370636,\n            -0.556660532951355,\n            -0.20282964408397675,\n            0.1787903904914856,\n            -0.13809867203235626,\n            0.012665750458836555,\n            -0.007909105159342289,\n            -0.11666542291641235,\n            0.192016139626503,\n            0.20280246436595917,\n            0.04091315343976021,\n            0.21129484474658966,\n            0.06015581637620926,\n            -0.1396055370569229,\n            0.11048803478479385,\n            -0.22130873799324036,\n            0.10175041109323502,\n            0.15478093922138214,\n            -0.06699641793966293,\n            0.16655825078487396,\n            -0.5767931938171387,\n            0.23376262187957764,\n            -0.06561370939016342,\n            0.08572515100240707,\n            0.22690269351005554,\n            -0.10714394599199295,\n            0.2328615039587021,\n            0.06609856337308884,\n            0.15064586699008942,\n            0.1398843675851822,\n            9.159173350781202e-05,\n            -0.006412057671695948,\n            0.1231503039598465,\n            0.2868848741054535,\n            -0.37850138545036316,\n            -0.4390513002872467,\n            -0.10716433078050613,\n            -0.16492293775081635,\n            -0.17774488031864166,\n            -0.006263014394789934,\n            -0.15535981953144073,\n            -0.15121980011463165,\n            -0.022719506174325943,\n            -0.3260766863822937,\n            0.1365034133195877,\n            0.7772430777549744,\n            0.8306354880332947,\n            0.8039601445198059,\n            0.16534824669361115,\n            -0.03939266875386238,\n            -0.15611104667186737,\n            0.21217003464698792,\n            -0.022034769877791405,\n            -0.025939559563994408,\n            0.1058378517627716,\n            -0.08505864441394806,\n            0.08503950387239456,\n            -0.0037705348804593086,\n            -0.0026697057764977217,\n            0.3492349088191986,\n            0.15157155692577362,\n            -0.3159380555152893,\n            -0.10824967920780182,\n            -0.04872310906648636,\n            0.19715555012226105,\n            -0.2658633291721344,\n            -0.06968845427036285,\n            0.009916169568896294,\n            0.18593478202819824,\n            -0.038871243596076965,\n            -0.3416462540626526,\n            0.1855567842721939,\n            0.21629339456558228,\n            -0.10832708328962326,\n            -0.04190235957503319,\n            0.2388715296983719,\n            -0.11624565720558167,\n            -0.10361404716968536,\n            0.0536813959479332,\n            0.12528158724308014,\n            -0.262010782957077,\n            0.05081893876194954,\n            0.29551735520362854,\n            0.05958620831370354,\n            -0.01989975944161415,\n            -0.19261345267295837,\n            0.01736867055296898,\n            -0.07923264801502228,\n            -0.4404444694519043,\n            0.3125889301300049,\n            0.10095971822738647,\n            0.17173698544502258,\n            0.23782190680503845,\n            -0.07170403748750687,\n            0.013639729470014572,\n            0.19007621705532074,\n            0.1901141107082367,\n            -0.052342064678668976,\n            -0.9643150568008423,\n            -0.12307217717170715,\n            -0.21010802686214447,\n            -0.5640560984611511,\n            0.010125457309186459,\n            0.1314179003238678,\n            0.10721258819103241,\n            -0.24371789395809174,\n            -0.5925355553627014,\n            0.49424877762794495,\n            -0.03528435528278351,\n            -0.21386614441871643,\n            1.4134130477905273,\n            -0.2751445770263672,\n            0.007012579124420881,\n            -0.023824317380785942,\n            0.004113825503736734,\n            -0.06332013010978699,\n            0.286077082157135,\n            0.04896686226129532,\n            0.31404414772987366,\n            0.15028351545333862,\n            0.003490754636004567,\n            0.0802399218082428,\n            -0.230818971991539,\n            -0.022719932720065117,\n            0.26083019375801086,\n            -0.2885863184928894,\n            0.07537354528903961,\n            0.12282905727624893,\n            -0.38638314604759216,\n            0.1752759963274002,\n            -0.07370781153440475,\n            0.13994526863098145,\n            0.13313405215740204,\n            0.2851952016353607,\n            0.905279278755188,\n            0.34521353244781494,\n            -0.36453402042388916,\n            0.46360254287719727,\n            -0.002040385501459241,\n            -0.003476516343653202,\n            -0.19058215618133545,\n            0.27096763253211975,\n            0.08722586184740067,\n            0.03202880546450615,\n            -0.06164764240384102,\n            0.011678489856421947,\n            0.21189850568771362,\n            -0.40100231766700745,\n            0.022941868752241135,\n            0.0394427627325058,\n            0.0675845518708229,\n            -0.22503064572811127,\n            0.14730903506278992,\n            0.24842065572738647,\n            -0.34360530972480774,\n            0.21811245381832123,\n            -0.05238509923219681,\n            -0.008763357996940613,\n            -0.1336073875427246,\n            0.15671105682849884,\n            0.4475333094596863,\n            -0.5187726616859436,\n            0.005388418212532997,\n            -0.07889139652252197,\n            0.10729073733091354,\n            0.22381159663200378,\n            0.07434546202421188,\n            -0.0843898206949234,\n            0.13574494421482086,\n            0.01853088103234768,\n            -0.41072791814804077,\n            0.40448933839797974,\n            -0.8231801986694336,\n            -0.4780847728252411,\n            -0.11237931996583939,\n            0.012673617340624332,\n            -0.04672158136963844,\n            -0.23933981359004974,\n            0.01667654886841774,\n            -0.14681674540042877,\n            0.077765092253685,\n            0.15309257805347443,\n            0.03254099190235138,\n            -0.015896232798695564,\n            -0.029608771204948425,\n            -0.288953959941864,\n            -0.32651081681251526,\n            0.06307528167963028,\n            -0.09873636066913605,\n            0.08938323706388474,\n            0.27018269896507263,\n            0.018129458650946617,\n            -0.050469521433115005,\n            -0.17951229214668274,\n            0.02319747768342495,\n            0.06737810373306274,\n            0.2690926194190979,\n            -0.10778623819351196,\n            -0.04740763455629349,\n            0.30407941341400146,\n            -0.08746829628944397,\n            -0.2184152454137802,\n            0.14826175570487976,\n            0.18092381954193115,\n            0.07989493757486343,\n            -0.1297195851802826,\n        ],\n        b1: &[\n            -0.5191351175308228,\n            0.6662623882293701,\n            0.610133707523346,\n            -1.1585999727249146,\n            0.6903770565986633,\n            0.4241520166397095,\n            0.754120945930481,\n            -0.7599878907203674,\n            -0.3445088267326355,\n            0.9317805767059326,\n            -0.2041703462600708,\n            0.17219330370426178,\n            1.1566059589385986,\n            -0.41121166944503784,\n            -0.6977726817131042,\n            0.7911778092384338,\n            0.6611397862434387,\n            -0.6938921213150024,\n            -0.03742314130067825,\n            -0.16022440791130066,\n            0.11257349699735641,\n            0.07743008434772491,\n            -0.6286312937736511,\n            0.544836699962616,\n            -0.15634237229824066,\n            -0.5572881698608398,\n            0.9681645035743713,\n            -0.7440500855445862,\n            0.10288882255554199,\n            0.9043763875961304,\n            0.14654643833637238,\n            -0.024421239271759987,\n            -0.4609592854976654,\n            0.917902410030365,\n            0.2704138457775116,\n            0.6341348886489868,\n            0.034945350140333176,\n            0.5565919876098633,\n            0.1746397614479065,\n            -0.6341800093650818,\n        ],\n        w2: &[\n            0.07229708135128021,\n            0.2507615387439728,\n            0.16330942511558533,\n            0.5204483866691589,\n            0.24313874542713165,\n            -0.5474504232406616,\n            -0.28332123160362244,\n            -0.2225571572780609,\n            -0.1043124571442604,\n            0.06595291197299957,\n            0.21239061653614044,\n            -0.14725270867347717,\n            -0.8134568333625793,\n            0.07381946593523026,\n            -0.24956485629081726,\n            0.4919748604297638,\n            0.2962062954902649,\n            0.3260444402694702,\n            0.07504145801067352,\n            -0.053836897015571594,\n            0.2531750500202179,\n            -0.04855559393763542,\n            -0.5578967332839966,\n            -0.5225025415420532,\n            0.055111128836870193,\n            -0.21510563790798187,\n            0.5871708989143372,\n            -0.19132649898529053,\n            0.007392226252704859,\n            -0.298953115940094,\n            0.16707110404968262,\n            -0.04706822335720062,\n            0.07302752882242203,\n            -0.08172990381717682,\n            0.23955324292182922,\n            -0.15824700891971588,\n            -0.3977665305137634,\n            0.5267415642738342,\n            -0.11258449405431747,\n            -0.3343915045261383,\n            0.23245088756084442,\n            -0.7491211891174316,\n            -0.6333310604095459,\n            0.0232061930000782,\n            -0.2315434217453003,\n            -0.3745144307613373,\n            -0.03209906071424484,\n            -0.4041699469089508,\n            0.041345734149217606,\n            0.19181972742080688,\n            -0.2760458290576935,\n            -0.07779327034950256,\n            0.24569696187973022,\n            -0.18802686035633087,\n            -0.6544056534767151,\n            0.556419849395752,\n            0.11468080431222916,\n            -0.32528090476989746,\n            0.38538315892219543,\n            0.33702555298805237,\n            -0.442532479763031,\n            0.00750756124034524,\n            -0.45737770199775696,\n            -0.06860284507274628,\n            -0.4411284625530243,\n            -0.23914210498332977,\n            0.06834587454795837,\n            0.14571186900138855,\n            0.6887655258178711,\n            0.5702284574508667,\n            0.3135473430156708,\n            -0.3360161781311035,\n            -0.5353860259056091,\n            0.06292688101530075,\n            0.735708475112915,\n            0.7143703103065491,\n            -0.3693147897720337,\n            0.525284469127655,\n            0.39448651671409607,\n            -0.09941494464874268,\n            0.09564384818077087,\n            0.5881519913673401,\n            0.05619557946920395,\n            0.4508857727050781,\n            -0.2834583520889282,\n            -0.16902177035808563,\n            0.24799591302871704,\n            -0.182522252202034,\n            0.0468696765601635,\n            0.14808374643325806,\n            -0.013205822557210922,\n            -0.12705814838409424,\n            0.0614711195230484,\n            0.14103399217128754,\n            -0.2599405348300934,\n            0.028414186090230942,\n            -0.2865449786186218,\n            -0.08163938671350479,\n            0.13120926916599274,\n            0.17990124225616455,\n            -0.16350798308849335,\n            -0.09809352457523346,\n            -0.013590727932751179,\n            -0.17736633121967316,\n            0.05107983574271202,\n            0.3411618173122406,\n            -0.2772451341152191,\n            0.32397109270095825,\n            0.046551186591386795,\n            0.13246433436870575,\n            0.05053735896945,\n            0.24057962000370026,\n            -0.04693610221147537,\n            -0.1650579869747162,\n            0.1331019252538681,\n            0.09457181394100189,\n            -0.16547952592372894,\n            -0.09469929337501526,\n            0.30049434304237366,\n            0.12664170563220978,\n            -0.013082812540233135,\n            0.390655517578125,\n            0.6400918364524841,\n            -0.0010483618825674057,\n            -0.03533017635345459,\n            0.16345657408237457,\n            0.05697643384337425,\n            0.1748565286397934,\n            0.0036667422391474247,\n            -0.05557025969028473,\n            0.016822226345539093,\n            -0.12541711330413818,\n            -0.4695605933666229,\n            0.008447905071079731,\n            0.16371716558933258,\n            -0.1481284201145172,\n            -0.10916673392057419,\n            0.1754710078239441,\n            -0.05557332932949066,\n            0.17406205832958221,\n            0.03734235838055611,\n            -0.0014076621737331152,\n            0.16409075260162354,\n            -0.0339696928858757,\n            0.11525241285562515,\n            0.11995170265436172,\n            -0.39020177721977234,\n            0.01936984248459339,\n            -0.14390763640403748,\n            -0.18344464898109436,\n            -0.08675119280815125,\n            0.19569827616214752,\n            0.48439380526542664,\n            -0.232485830783844,\n            -0.004231136757880449,\n            0.15202505886554718,\n            0.01103641465306282,\n            -0.1192987710237503,\n            -0.17487019300460815,\n            0.27336806058883667,\n            -0.5894135236740112,\n            -0.03331466019153595,\n            0.21942859888076782,\n            0.30420297384262085,\n            0.2666693329811096,\n            0.4481956958770752,\n            -0.020630693063139915,\n            0.8494743704795837,\n            0.5691520571708679,\n            0.5711295008659363,\n            0.00404204148799181,\n            0.5070351958274841,\n            0.09074786305427551,\n            0.15874768793582916,\n            0.7676622271537781,\n            0.6556511521339417,\n            0.1220490038394928,\n            0.7263025641441345,\n            -0.07173441350460052,\n            0.14413252472877502,\n            0.49090006947517395,\n            -0.3324028253555298,\n            0.45898303389549255,\n            0.5931536555290222,\n            0.19021296501159668,\n            -0.7473744750022888,\n            -0.834629476070404,\n            -0.1385311633348465,\n            -0.05174582824110985,\n            0.018871335312724113,\n            -0.42817312479019165,\n            0.20682017505168915,\n            0.016382897272706032,\n            -0.6684255599975586,\n            0.3525462746620178,\n            -0.42306870222091675,\n            -0.0817568302154541,\n            0.3572525084018707,\n            -0.23954586684703827,\n            -0.4869120717048645,\n            0.016070470213890076,\n            0.5639761686325073,\n            0.17797298729419708,\n            0.2919785678386688,\n            -0.3837592601776123,\n            0.13362792134284973,\n            0.09925093501806259,\n            0.12642522156238556,\n            0.09690988808870316,\n            -0.08732952922582626,\n            0.24605968594551086,\n            -0.3894798457622528,\n            -0.174991175532341,\n            0.2573908269405365,\n            0.22514064610004425,\n            -0.24535547196865082,\n            -0.2993263006210327,\n            0.24350187182426453,\n            0.03375721350312233,\n            0.16244018077850342,\n            -0.16753582656383514,\n            -0.08621060848236084,\n            0.1272309273481369,\n            0.007472787983715534,\n            0.20557984709739685,\n            0.1578531116247177,\n            -0.5838948488235474,\n            0.08410368114709854,\n            -0.2831973135471344,\n            -0.28126293420791626,\n            -0.08023717254400253,\n            0.5180243849754333,\n            0.2208152413368225,\n            -0.3613019585609436,\n            -0.06204051896929741,\n            -0.13526616990566254,\n            0.09384715557098389,\n            -0.27185022830963135,\n            -0.05938927084207535,\n            0.284194678068161,\n            0.04228530079126358,\n            0.5006632208824158,\n            0.6578063368797302,\n            -0.07014274597167969,\n            -0.3233219087123871,\n            -0.01618030108511448,\n            0.2888641357421875,\n            -0.08185673505067825,\n            -0.17689819633960724,\n            -0.2994365096092224,\n            0.016244128346443176,\n            0.02359011210501194,\n            0.1367129534482956,\n            -0.01653127372264862,\n            -0.09157261997461319,\n            -0.3516620397567749,\n            -0.09030301123857498,\n            -0.07817772775888443,\n            0.17603041231632233,\n            -0.01393663790076971,\n            -0.029468189924955368,\n            -0.0814921036362648,\n            -0.12077502906322479,\n            -0.10759524255990982,\n            -0.0750858411192894,\n            0.2511105239391327,\n            -0.20753242075443268,\n            -0.05136517807841301,\n            -0.024205535650253296,\n            -0.3384825587272644,\n            0.020664114505052567,\n            0.11200296878814697,\n            0.08333364874124527,\n            -0.24177855253219604,\n            -0.07010341435670853,\n            0.020779477432370186,\n            -0.20839253067970276,\n            -0.0016562794335186481,\n            0.023504814133048058,\n            0.3570723235607147,\n            -0.30022287368774414,\n            -0.3554439842700958,\n            -0.027536675333976746,\n            -1.1282703876495361,\n            -0.08706718683242798,\n            0.0742080882191658,\n            0.18080361187458038,\n            -0.02274167723953724,\n            -0.704075813293457,\n            -0.9722687602043152,\n            0.1188407614827156,\n            -0.029379399493336678,\n            0.8019110560417175,\n            -0.34810709953308105,\n            0.04902748018503189,\n            -0.7494327425956726,\n            0.5064789056777954,\n            -0.11681736260652542,\n            0.2257058471441269,\n            -0.4354608356952667,\n            0.3252757489681244,\n            -0.1591869592666626,\n            -0.5933760404586792,\n            -0.5259361863136292,\n            0.22252318263053894,\n            0.30712220072746277,\n            0.29186123609542847,\n            -0.7899709343910217,\n            0.3455640971660614,\n            -0.8577526807785034,\n            0.19282177090644836,\n            0.29095181822776794,\n            -0.3287593424320221,\n            0.0454283282160759,\n            -0.5983009338378906,\n            -0.08342050760984421,\n            -0.8976981043815613,\n            0.10165920853614807,\n            0.13396088778972626,\n            0.2290259599685669,\n            0.02499830722808838,\n            0.7539560794830322,\n            0.1477266401052475,\n            0.3097168207168579,\n            -0.3993585705757141,\n            0.0817292109131813,\n            0.038499560207128525,\n            0.048502497375011444,\n            0.10572300106287003,\n            -0.17650842666625977,\n            0.30300378799438477,\n            -0.3586488962173462,\n            -0.09699319303035736,\n            0.28980425000190735,\n            0.1152607873082161,\n            -0.30993735790252686,\n            -0.3226162791252136,\n            0.2082981914281845,\n            0.08206543326377869,\n            0.09643732011318207,\n            -0.09098457545042038,\n            -0.09191355854272842,\n            0.04240717366337776,\n            -0.08706614375114441,\n            0.3119218051433563,\n            0.24132680892944336,\n            -0.5137639045715332,\n            0.03463784605264664,\n            -0.29585450887680054,\n            -0.3583862781524658,\n            -0.09919128566980362,\n            0.5263358950614929,\n            0.19875890016555786,\n            -0.4007430374622345,\n            -0.044145308434963226,\n            -0.24342355132102966,\n            0.16471655666828156,\n            -0.25901785492897034,\n            0.012997856363654137,\n            0.3298455476760864,\n            -0.23130790889263153,\n            0.4484388828277588,\n            0.35633817315101624,\n            0.26454973220825195,\n            0.15214529633522034,\n            -0.12443697452545166,\n            -0.405061811208725,\n            0.17236965894699097,\n            -0.36522531509399414,\n            -0.074102483689785,\n            0.09564346820116043,\n            -0.26696014404296875,\n            -0.7053405046463013,\n            -0.4750596880912781,\n            0.2850874066352844,\n            -0.42413032054901123,\n            0.3273111581802368,\n            0.013779409229755402,\n            -0.7248923182487488,\n            -0.49210208654403687,\n            0.5041399002075195,\n            -0.14308881759643555,\n            0.629442036151886,\n            -0.8470776677131653,\n            0.36798736453056335,\n            -0.17092065513134003,\n            0.5437707304954529,\n            -0.26034078001976013,\n            -0.4502609074115753,\n            0.2898317873477936,\n            -0.3266198933124542,\n            0.1681036651134491,\n            0.6064534783363342,\n            0.48974573612213135,\n            -0.3461318910121918,\n            -0.36192092299461365,\n            0.3675844371318817,\n            -0.731248676776886,\n            -0.21227769553661346,\n            -0.4246974289417267,\n            0.17397946119308472,\n            -0.3643985986709595,\n            0.205714613199234,\n            0.629838228225708,\n            0.10543780773878098,\n            0.010421440936625004,\n            0.6487590670585632,\n            -0.685522198677063,\n            0.010746597312390804,\n            0.371294766664505,\n            -0.68584144115448,\n            0.69797283411026,\n            -0.39890381693840027,\n            0.2957388460636139,\n            0.10036955028772354,\n            -0.31620606780052185,\n            -0.5876231789588928,\n            -0.5783882737159729,\n            -0.4745366871356964,\n            0.20689401030540466,\n            -0.2748165428638458,\n            0.34110450744628906,\n            0.817054033279419,\n            0.8686729073524475,\n            -0.6139298677444458,\n            -0.19506172835826874,\n            -0.03448706120252609,\n            0.635860025882721,\n            -0.38243091106414795,\n            0.8843176960945129,\n            0.08922040462493896,\n            -0.8030375242233276,\n            0.01003911904990673,\n            0.49227485060691833,\n            0.02043282799422741,\n            -0.1812848448753357,\n            0.8425045609474182,\n            -0.18937410414218903,\n            0.2360723465681076,\n            -0.0486280657351017,\n            0.1306903064250946,\n            0.44811540842056274,\n            -0.09772484004497528,\n            0.3676001727581024,\n            -0.10864408314228058,\n            0.10239739716053009,\n            0.26535993814468384,\n            -0.19465096294879913,\n            -0.05268852412700653,\n            0.013907784596085548,\n            0.11859709769487381,\n            -0.008244873955845833,\n            -0.12678827345371246,\n            0.16795198619365692,\n            0.09826375544071198,\n            -0.13783332705497742,\n            -0.32474759221076965,\n            -0.018496913835406303,\n            -0.12179988622665405,\n            0.22411927580833435,\n            -0.10514824092388153,\n            0.038778163492679596,\n            0.33486974239349365,\n            0.31644245982170105,\n            0.05365574359893799,\n            0.24912847578525543,\n            -0.31889432668685913,\n            0.24240325391292572,\n            -0.19231560826301575,\n            0.18558776378631592,\n            -0.022984078153967857,\n            0.11608095467090607,\n            0.15418484807014465,\n            -0.14139854907989502,\n            0.01758008636534214,\n            -0.12027571350336075,\n            0.2522386610507965,\n            -0.2922046184539795,\n            0.049236513674259186,\n            0.19894357025623322,\n            0.39957553148269653,\n            0.3346879780292511,\n            0.3187335133552551,\n            0.4501717686653137,\n            -0.8946970701217651,\n            0.18189306557178497,\n            -0.08766483515501022,\n            0.2782788574695587,\n            0.3587392270565033,\n            -0.33824455738067627,\n            0.6033147573471069,\n            -0.6243746876716614,\n            -0.6177958250045776,\n            0.6629742383956909,\n            0.4856598377227783,\n            -0.3099081814289093,\n            -0.678487241268158,\n            0.47894829511642456,\n            -0.03139176964759827,\n            0.16848357021808624,\n            -0.5739434957504272,\n            -0.16708984971046448,\n            0.11146949231624603,\n            0.090438611805439,\n            0.4812713861465454,\n            0.5129365921020508,\n            -0.7324693202972412,\n            0.26365718245506287,\n            -0.4824923276901245,\n            -0.5487518310546875,\n            -0.20128659904003143,\n            0.5759150385856628,\n            0.3504473567008972,\n            -0.36605504155158997,\n            -0.4257725477218628,\n            -0.25298258662223816,\n            0.512897789478302,\n            -0.4181336462497711,\n            -0.516604483127594,\n            0.37244912981987,\n        ],\n        b2: &[\n            0.14859354496002197,\n            -0.018167857080698013,\n            -0.3407953083515167,\n            -0.14991576969623566,\n            0.4018653333187103,\n            -0.2384500652551651,\n            -0.4047893285751343,\n            0.15702210366725922,\n            -0.3152092695236206,\n            0.29297566413879395,\n            0.26403820514678955,\n            -0.2573520541191101,\n            -0.11290331929922104,\n        ],\n    }\n}\n"
  },
  {
    "path": "linalg/src/arm64/cortex_a72.rs",
    "content": "use crate::frame::mmm::cost_model::CostModel;\npub fn models() -> Vec<(&'static str, CostModel<'static>)> {\n    vec![]\n}\n"
  },
  {
    "path": "linalg/src/arm64/cortex_a73.rs",
    "content": "use crate::frame::mmm::cost_model::CostModel;\npub fn models() -> Vec<(&'static str, CostModel<'static>)> {\n    vec![]\n}\n"
  },
  {
    "path": "linalg/src/arm64.rs",
    "content": "#![allow(clippy::excessive_precision)]\n#[cfg(any(target_os = \"macos\", all(target_os = \"ios\", feature = \"apple-amx-ios\")))]\nmod apple_amx;\nmod arm64simd;\npub mod cortex_a53;\nmod cortex_a55;\n//mod cortex_a72;\n//mod cortex_a73;\npub use arm64simd::*;\n\n#[cfg(not(feature = \"no_fp16\"))]\npub mod arm64fp16;\n#[cfg(not(feature = \"no_fp16\"))]\npub use arm64fp16::*;\n\nuse crate::f16;\nuse crate::{BinOp, DatumType, LinalgRegistry, Ops};\n\nuse crate::frame::by_scalar::ByScalarKer;\nuse crate::frame::element_wise::ElementWiseKer;\nuse crate::frame::reduce::{MapReduceKer, ReduceKer};\nuse crate::frame::unicast::UnicastKer;\n\n// https://en.wikipedia.org/wiki/Comparison_of_ARMv8-A_cores\nconst PART_A53: &str = \"0xd03\";\nconst PART_A55: &str = \"0xd05\";\n#[allow(dead_code)]\nconst PART_A72: &str = \"0xd08\";\n#[allow(dead_code)]\nconst PART_A73: &str = \"0xd09\";\n#[allow(dead_code)]\nconst PART_A75: &str = \"0xd0a\";\n#[allow(dead_code)]\nconst PART_NEOVERSE_N1: &str = \"0xd0c\";\n#[allow(dead_code)]\nconst PART_NEOVERSE_N2: &str = \"0xd49\";\n#[allow(dead_code)]\nconst PART_NEOVERSE_N3: &str = \"0xd8e\";\n#[allow(dead_code)]\nconst PART_NEOVERSE_V1: &str = \"0xd40\";\n#[allow(dead_code)]\nconst PART_NEOVERSE_V2: &str = \"0xd4f\";\n#[allow(dead_code)]\nconst PART_NEOVERSE_V3: &str = \"0xd83\";\n\nfn max_cpuid() -> std::io::Result<String> {\n    let cpu_info = std::fs::read_to_string(\"/proc/cpuinfo\")?;\n    let max = cpu_info\n        .lines()\n        .filter(|line| line.starts_with(\"CPU part\"))\n        .map(|line| line.split_whitespace().last().unwrap_or(\"\"))\n        .max();\n    Ok(max.unwrap_or(\"\").to_string())\n}\n\nlazy_static::lazy_static! {\n    static ref KIND: Kind = Kind::choose();\n\n    static ref CPU_FEATURES: Vec<String> = {\n        #[cfg(test)] crate::setup_test_logger();\n        let Ok(cpu_info) = std::fs::read_to_string(\"/proc/cpuinfo\") else {\n            log::warn!(\"Could not read /proc/cpuinfo. CPU Features detection may be impaired.\");\n            return vec!();\n        };\n        if let Some(line) = cpu_info\n            .lines()\n                .find(|line| line.starts_with(\"Features\")) {\n                    line.split_once(':').unwrap().1.split_whitespace().map(|s| s.to_string()).collect()\n                } else {\n                    log::warn!(\"Could not find \\\"Features  :\\\" lines in /proc/cpuinfo. CPU Features detection may be impaired.\");\n                    vec!()\n        }\n    };\n\n    static ref HAS_FP16: bool = {\n        CPU_FEATURES.iter().any(|s| &**s == \"asimdhp\")\n    };\n}\n\n#[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\nfn apple_string_from_c_bytes(buf: &[u8]) -> String {\n    use std::ffi::CStr;\n\n    CStr::from_bytes_until_nul(buf)\n        .ok()\n        .map(|s| s.to_string_lossy().into_owned())\n        .unwrap_or_default()\n}\n\n#[cfg(any(target_os = \"macos\", target_os = \"ios\"))]\nfn apple_get_syscall(key: &str) -> String {\n    use std::ffi::{CString, c_char, c_int, c_void};\n    use std::ptr::null_mut;\n\n    unsafe extern \"C\" {\n        fn sysctlbyname(\n            name: *const c_char,\n            oldp: *mut c_void,\n            oldlenp: *mut usize,\n            newp: *mut c_void,\n            newlen: usize,\n        ) -> c_int;\n    }\n\n    let Ok(name) = CString::new(key) else {\n        return String::new();\n    };\n\n    unsafe {\n        let mut len_needed: usize = 0;\n        if sysctlbyname(name.as_ptr(), null_mut(), &mut len_needed, null_mut(), 0) != 0 {\n            return String::new();\n        }\n\n        let mut buf = vec![0u8; len_needed.saturating_add(1)];\n        let mut len: usize = buf.len();\n        if sysctlbyname(name.as_ptr(), buf.as_mut_ptr() as _, &mut len, null_mut(), 0) != 0 {\n            return String::new();\n        }\n\n        buf.truncate(len.min(buf.len()));\n        if buf.last().copied() != Some(0) {\n            buf.push(0);\n        }\n\n        apple_string_from_c_bytes(&buf)\n    }\n}\n\n#[cfg(all(test, any(target_os = \"macos\", target_os = \"ios\")))]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn apple_string_from_c_bytes_returns_empty_without_nul() {\n        assert_eq!(apple_string_from_c_bytes(b\"hello\"), \"\");\n    }\n\n    #[test]\n    fn apple_string_from_c_bytes_stops_at_first_nul() {\n        assert_eq!(apple_string_from_c_bytes(b\"hello\\0world\\0\"), \"hello\");\n    }\n\n    #[test]\n    fn apple_get_syscall_does_not_panic() {\n        let _ = apple_get_syscall(\"machdep.cpu.brand_string\");\n    }\n}\n\n#[cfg(target_os = \"macos\")]\npub fn has_amx() -> bool {\n    !apple_get_syscall(\"machdep.cpu.brand_string\").contains(\"(Virtual)\")\n}\n\n#[cfg(target_os = \"ios\")]\nlazy_static::lazy_static! {\n    static ref IPHONE_MODEL_MAJOR:Option<usize> = {\n        let version = apple_get_syscall(\"hw.machine\");\n        let Some((major, _)) = version.trim_start_matches(\"iPhone\").split_once(\",\") else { return None };\n        major.parse::<usize>().ok()\n    };\n}\n\n#[cfg(all(target_os = \"ios\", feature = \"apple-amx-ios\"))]\nfn has_amx() -> bool {\n    // iPhone12,1 is the one branded \"iPhone 11\", with Apple A13 bionic, first CPU featuring amx\n    IPHONE_MODEL_MAJOR.map(|it| it >= 12).unwrap_or(false)\n}\n\n#[inline]\n#[cfg(target_os = \"ios\")]\npub fn has_fp16() -> bool {\n    // iPhone10,1 is the one branded \"iPhone 8\", with Apple A11 bionic, first CPU featuring fp16\n    IPHONE_MODEL_MAJOR.map(|it| it >= 10).unwrap_or(false)\n}\n\n#[inline]\n#[cfg(not(target_os = \"ios\"))]\npub fn has_fp16() -> bool {\n    cfg!(target_os = \"macos\")\n        || cfg!(feature_cpu = \"fp16\")\n        || *KIND == Kind::CortexA55\n        || *KIND == Kind::CortexA75\n        || *HAS_FP16\n}\n\n#[target_feature(enable = \"fp16\")]\n#[inline]\npub unsafe fn add_f16(a: f16, b: f16) -> f16 {\n    unsafe {\n        let result: u16;\n        std::arch::asm!(\n        \"fadd {0:h}, {1:h}, {2:h}\",\n        lateout(vreg) result,\n        in(vreg) a.to_bits(),\n        in(vreg) b.to_bits(),\n        options(pure, nomem, nostack, preserves_flags));\n        f16::from_bits(result)\n    }\n}\n\n#[target_feature(enable = \"fp16\")]\n#[inline]\npub unsafe fn mul_f16(a: f16, b: f16) -> f16 {\n    unsafe {\n        let result: u16;\n        std::arch::asm!(\n        \"fmul {0:h}, {1:h}, {2:h}\",\n        lateout(vreg) result,\n        in(vreg) a.to_bits(),\n        in(vreg) b.to_bits(),\n        options(pure, nomem, nostack, preserves_flags));\n        f16::from_bits(result)\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, Copy, Clone)]\npub enum Kind {\n    Generic,\n    AppleM,\n    Neoverse,\n    CortexA53,\n    CortexA55,\n    CortexA72,\n    CortexA73,\n    CortexA75,\n}\n\nimpl Kind {\n    pub fn choose() -> Kind {\n        #[cfg(test)]\n        crate::setup_test_logger();\n        let kind = if let Ok(kind) = std::env::var(\"TRACT_CPU_AARCH64_KIND\") {\n            log::info!(\"CPU kind forced with TRACT_CPU_AARCH64_KIND: {}\", kind);\n            let kind = kind.to_lowercase();\n            if kind.contains(\"a53\") {\n                Kind::CortexA53\n            } else if kind.contains(\"a55\") {\n                Kind::CortexA55\n            } else if kind.contains(\"a72\") {\n                Kind::CortexA72\n            } else if kind.contains(\"a73\") {\n                Kind::CortexA73\n            } else if kind.contains(\"a75\") {\n                Kind::CortexA75\n            } else if kind.contains(\"neoverse\") {\n                Kind::Neoverse\n            } else if kind.contains(\"applem\") {\n                Kind::AppleM\n            } else {\n                Kind::Generic\n            }\n        } else if cfg!(target_os = \"macos\") {\n            Kind::AppleM\n        } else {\n            let part = if let Ok(part) = std::env::var(\"TRACT_CPU_AARCH64_OVERRIDE_CPU_PART\") {\n                log::info!(\"CPU part forced with TRACT_CPU_AARCH64_OVERRIDE_CPU_PART: {}\", part);\n                part\n            } else if cfg!(target_os = \"linux\") {\n                let part = max_cpuid().unwrap_or_else(|_| \"0x00\".to_string());\n                log::info!(\"CPU part auto detected: {}\", part);\n                part\n            } else {\n                log::info!(\"Unknown CPU part\");\n                \"0x00\".to_string()\n            };\n            match &*part {\n                PART_A53 => Kind::CortexA53,\n                PART_A55 => Kind::CortexA55,\n                PART_A72 => Kind::CortexA72,\n                PART_A73 => Kind::CortexA73,\n                PART_A75 => Kind::CortexA75,\n                PART_NEOVERSE_N1 | PART_NEOVERSE_N2 | PART_NEOVERSE_N3 | PART_NEOVERSE_V1\n                | PART_NEOVERSE_V2 | PART_NEOVERSE_V3 => Kind::Neoverse,\n                _ => Kind::Generic,\n            }\n        };\n        log::info!(\"CPU optimisation: {:?}\", kind);\n        kind\n    }\n}\n\npub(crate) fn register_all_unicast(registry: &mut LinalgRegistry) {\n    registry\n        .insert((BinOp::Mul, DatumType::F32), Box::new(|| arm64simd_unicast_mul_f32_16n::bin()));\n    registry\n        .insert((BinOp::Mul, DatumType::F16), Box::new(|| arm64fp16_unicast_mul_f16_32n::bin()));\n    registry\n        .insert((BinOp::Add, DatumType::F32), Box::new(|| arm64simd_unicast_add_f32_16n::bin()));\n    registry\n        .insert((BinOp::Add, DatumType::F16), Box::new(|| arm64fp16_unicast_add_f16_32n::bin()));\n    registry\n        .insert((BinOp::Sub, DatumType::F32), Box::new(|| arm64simd_unicast_sub_f32_16n::bin()));\n    registry\n        .insert((BinOp::Sub, DatumType::F16), Box::new(|| arm64fp16_unicast_sub_f16_32n::bin()));\n    registry\n        .insert((BinOp::SubF, DatumType::F32), Box::new(|| arm64simd_unicast_subf_f32_16n::bin()));\n    registry\n        .insert((BinOp::SubF, DatumType::F16), Box::new(|| arm64fp16_unicast_subf_f16_32n::bin()));\n    registry\n        .insert((BinOp::Min, DatumType::F32), Box::new(|| arm64simd_unicast_min_f32_16n::bin()));\n    registry\n        .insert((BinOp::Min, DatumType::F16), Box::new(|| arm64fp16_unicast_min_f16_32n::bin()));\n    registry\n        .insert((BinOp::Max, DatumType::F32), Box::new(|| arm64simd_unicast_max_f32_16n::bin()));\n    registry\n        .insert((BinOp::Max, DatumType::F16), Box::new(|| arm64fp16_unicast_max_f16_32n::bin()));\n}\n\npub(crate) fn register_all_by_scalar(registry: &mut LinalgRegistry) {\n    registry\n        .insert((BinOp::Mul, DatumType::F32), Box::new(|| arm64simd_mul_by_scalar_f32_16n::bin()));\n    registry\n        .insert((BinOp::Mul, DatumType::F16), Box::new(|| arm64fp16_mul_by_scalar_f16_32n::bin()));\n    registry\n        .insert((BinOp::Add, DatumType::F32), Box::new(|| arm64simd_add_by_scalar_f32_16n::bin()));\n    registry\n        .insert((BinOp::Add, DatumType::F16), Box::new(|| arm64fp16_add_by_scalar_f16_32n::bin()));\n    registry\n        .insert((BinOp::Sub, DatumType::F32), Box::new(|| arm64simd_sub_by_scalar_f32_16n::bin()));\n    registry\n        .insert((BinOp::Sub, DatumType::F16), Box::new(|| arm64fp16_sub_by_scalar_f16_32n::bin()));\n    registry.insert(\n        (BinOp::SubF, DatumType::F32),\n        Box::new(|| arm64simd_subf_by_scalar_f32_16n::bin()),\n    );\n    registry.insert(\n        (BinOp::SubF, DatumType::F16),\n        Box::new(|| arm64fp16_subf_by_scalar_f16_32n::bin()),\n    );\n    registry\n        .insert((BinOp::Min, DatumType::F32), Box::new(|| arm64simd_min_by_scalar_f32_16n::bin()));\n    registry\n        .insert((BinOp::Min, DatumType::F16), Box::new(|| arm64fp16_min_by_scalar_f16_32n::bin()));\n    registry\n        .insert((BinOp::Max, DatumType::F32), Box::new(|| arm64simd_max_by_scalar_f32_16n::bin()));\n    registry\n        .insert((BinOp::Max, DatumType::F16), Box::new(|| arm64fp16_max_by_scalar_f16_32n::bin()));\n}\n\npub fn plug(ops: &mut Ops) {\n    arm64simd::plug(ops);\n\n    #[cfg(not(feature = \"no_fp16\"))]\n    if has_fp16() {\n        arm64fp16::plug(ops);\n    }\n\n    ops.qmmm_i32 = Box::new(|_, _, _| arm64simd_mmm_i32_8x8.mmm());\n    ops.qmmv_i32 = Box::new(|_, _| arm64simd_mmm_i32_64x1.mmm());\n    ops.mmv_f32 = match *KIND {\n        Kind::CortexA53 => Box::new(|_, _| arm64simd_mmm_f32_64x1_a53.mmm()),\n        Kind::CortexA55 => Box::new(|_, _| arm64simd_mmm_f32_64x1_a55.mmm()),\n        _ => Box::new(|_, _| arm64simd_mmm_f32_64x1_gen.mmm()),\n    };\n    let model = match *KIND {\n        Kind::CortexA53 => Some(cortex_a53::model()),\n        Kind::CortexA55 => Some(cortex_a55::model()),\n        _ => None,\n    };\n    let impls = ops.mmm_impls.clone();\n    ops.mmm_f32 = if let Some(model) = model {\n        Box::new(move |m, k, n| model.pick(&impls, m, k, n))\n    } else {\n        Box::new(move |_, _, n| {\n            if n.unwrap_or(8) < 8 {\n                arm64simd_mmm_f32_16x4_gen.mmm()\n            } else {\n                arm64simd_mmm_f32_8x8_gen.mmm()\n            }\n        })\n    };\n    #[cfg(feature = \"no_fp16\")]\n    if has_fp16() {\n        log::warn!(\n            \"This is a build with fp16 disabled, while your platform CPU seems to support it.\"\n        );\n    }\n    #[cfg(not(feature = \"no_fp16\"))]\n    if has_fp16() {\n        if *KIND == Kind::CortexA55 {\n            log::info!(\"Cortex-A55 mmm_f16 and mmv_f16 activated\");\n            ops.mmm_f16 = Box::new(|_, _, n| {\n                use tract_data::internal::DimLike;\n                if n.unwrap_or(1024).divceil(4) * 4 < n.unwrap_or(1024).divceil(8) * 8 {\n                    arm64fp16_mmm_f16_32x4_a55.mmm()\n                } else {\n                    arm64fp16_mmm_f16_16x8_a55.mmm()\n                }\n            });\n            ops.mmv_f16 = Box::new(|_, _| arm64fp16_mmm_f16_128x1_a55.mmm());\n        } else {\n            log::info!(\"ARMv8.2 mmm_f16 and mmv_f16 activated\");\n            ops.mmm_f16 = Box::new(|_, _, n| {\n                use tract_data::internal::DimLike;\n                if n.unwrap_or(1024).divceil(4) * 4 < n.unwrap_or(1024).divceil(8) * 8 {\n                    arm64fp16_mmm_f16_32x4_gen.mmm()\n                } else {\n                    arm64fp16_mmm_f16_16x8_gen.mmm()\n                }\n            });\n            ops.mmv_f16 = Box::new(|_, _| arm64fp16_mmm_f16_128x1_gen.mmm());\n        }\n    }\n    ops.leaky_relu_f32 = Box::new(|| arm64simd_leaky_relu_f32_8n::ew());\n    ops.sigmoid_f32 = Box::new(|| arm64simd_sigmoid_f32_4n::ew());\n    ops.tanh_f32 = Box::new(|| arm64simd_tanh_f32_4n::ew());\n    ops.max_f32 = Box::new(|| arm64simd_max_f32_16n::red());\n    ops.sum_f32 = Box::new(|| arm64simd_sum_f32_16n::red());\n    ops.mul_by_scalar_f32 = Box::new(|| arm64simd_mul_by_scalar_f32_16n::ew());\n    ops.softmax2_fastcompact_f32 = Box::new(|| arm64simd_softmax2_fastcompact_f32_16n::red());\n    #[cfg(not(feature = \"no_fp16\"))]\n    if has_fp16() {\n        log::info!(\"ARMv8.2 tanh_f16 and sigmoid_f16 activated\");\n        ops.leaky_relu_f16 = Box::new(|| arm64fp16_leaky_relu_f16_16n::ew());\n        ops.tanh_f16 = Box::new(|| arm64fp16_tanh_f16_8n::ew());\n        ops.sigmoid_f16 = Box::new(|| arm64fp16_sigmoid_f16_8n::ew());\n        ops.max_f16 = Box::new(|| arm64fp16_max_f16_32n::red());\n        ops.sum_f16 = Box::new(|| arm64fp16_sum_f16_32n::red());\n        ops.mul_by_scalar_f16 = Box::new(|| arm64fp16_mul_by_scalar_f16_32n::ew());\n    } else {\n        log::info!(\"No native fp16 support\");\n    }\n    #[cfg(any(target_os = \"macos\", all(target_os = \"ios\", feature = \"apple-amx-ios\")))]\n    {\n        apple_amx::plug(ops);\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/block_quant/helpers.rs",
    "content": "use byteorder::{LE, ReadBytesExt, WriteBytesExt};\nuse std::io::{Cursor, Read, Write};\nuse tract_data::internal::*;\n\npub struct NibbleReader<R> {\n    second_half: Option<i8>,\n    reader: R,\n}\n\nimpl<'s> NibbleReader<Cursor<&'s [u8]>> {\n    pub fn for_slice(slice: &'s [u8]) -> Self {\n        NibbleReader::new(Cursor::new(slice))\n    }\n}\n\nimpl<R: Read> NibbleReader<R> {\n    pub fn new(reader: R) -> NibbleReader<R> {\n        NibbleReader { reader, second_half: None }\n    }\n\n    pub fn read_f16(&mut self) -> f16 {\n        assert!(self.second_half.is_none());\n        f16::from_bits(self.reader.read_u16::<LE>().unwrap())\n    }\n\n    pub fn read_i4(&mut self) -> i8 {\n        if let Some(second) = self.second_half.take() {\n            second\n        } else {\n            let byte = self.reader.read_u8().unwrap();\n            self.second_half = Some((byte >> 4) as i8);\n            (byte & 0x0F) as i8\n        }\n    }\n\n    pub fn read_i8(&mut self) -> i8 {\n        self.reader.read_i8().unwrap()\n    }\n}\n\npub struct NibbleWriter<W> {\n    first_half: Option<i8>,\n    writer: W,\n}\n\nimpl<'s> NibbleWriter<Cursor<&'s mut [u8]>> {\n    pub fn for_slice(slice: &'s mut [u8]) -> Self {\n        NibbleWriter::new(Cursor::new(slice))\n    }\n}\n\nimpl<W: Write> NibbleWriter<W> {\n    pub fn new(writer: W) -> NibbleWriter<W> {\n        NibbleWriter { writer, first_half: None }\n    }\n\n    pub fn write_f16(&mut self, f: f16) {\n        assert!(self.first_half.is_none());\n        self.writer.write_u16::<LE>(f.to_bits()).unwrap()\n    }\n\n    pub fn write_i4(&mut self, q: i8) {\n        if let Some(first) = self.first_half.take() {\n            self.writer.write_u8(first as u8 | ((q as u8) << 4)).unwrap()\n        } else {\n            self.first_half = Some(q);\n        }\n    }\n\n    pub fn write_i8(&mut self, q: i8) {\n        self.writer.write_i8(q).unwrap()\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/block_quant/mod.rs",
    "content": "use downcast_rs::{Downcast, impl_downcast};\nuse dyn_clone::{DynClone, clone_box};\nuse dyn_eq::DynEq;\nuse dyn_hash::DynHash;\nuse num_traits::Zero;\nuse tract_data::internal::*;\nuse tract_data::itertools::Itertools;\n\nuse std::alloc::Layout;\nuse std::borrow::Cow;\nuse std::fmt::{Debug, Display};\nuse std::hash::Hash;\nuse std::sync::Arc;\n\nmod helpers;\nmod q4_0;\nmod q8_1;\nmod storage;\nmod value;\n\npub use helpers::{NibbleReader, NibbleWriter};\npub use q4_0::Q4_0;\npub use q8_1::Q8_1;\npub use storage::{BlockQuantStorage, block_quant_slice};\npub use value::{BlockQuantFact, PackedBlockQuantFact};\n\nuse crate::mmm::{EagerPackedInput, MMMInputFormat};\nuse crate::pack::PackedFormat;\n\nuse crate::WeightType;\n\nuse super::mmm::MMMInputValue;\n\npub trait BlockQuant:\n    Debug + Display + Send + Sync + DynClone + DynHash + dyn_eq::DynEq + Downcast\n{\n    fn block_len(&self) -> usize;\n\n    fn block_bytes(&self) -> usize;\n\n    fn dequant_block_f32(&self, quant: &[u8], block: &mut [f32]);\n    fn dequant_block_f16(&self, quant: &[u8], block: &mut [f16]);\n    fn quant_block_f16(&self, block: &[f16], quant: &mut [u8]);\n    fn quant_block_f32(&self, block: &[f32], quant: &mut [u8]);\n\n    fn quant_f16(&self, input: &[f16]) -> TractResult<Blob> {\n        unsafe {\n            let blocks = input.len() / self.block_len();\n            let mut quant = Blob::for_layout(\n                Layout::from_size_align(blocks * self.block_bytes(), 128).unwrap(),\n            );\n            for b in 0..blocks {\n                let block = &input[b * self.block_len()..][..self.block_len()];\n                let qblock = &mut quant[b * self.block_bytes()..][..self.block_bytes()];\n                self.quant_block_f16(block, qblock);\n            }\n            Ok(quant)\n        }\n    }\n\n    fn quant_f32(&self, input: &[f32]) -> TractResult<Blob> {\n        unsafe {\n            let blocks = input.len() / self.block_len();\n            let mut quant = Blob::for_layout(\n                Layout::from_size_align(blocks * self.block_bytes(), 128).unwrap(),\n            );\n            for b in 0..blocks {\n                let block = &input[b * self.block_len()..][..self.block_len()];\n                let qblock = &mut quant[b * self.block_bytes()..][..self.block_bytes()];\n                self.quant_block_f32(block, qblock);\n            }\n            Ok(quant)\n        }\n    }\n\n    fn dequant_f32(&self, input: &[u8]) -> TractResult<Tensor> {\n        unsafe {\n            let blocks = input.len() / self.block_bytes();\n            let mut tensor = Tensor::uninitialized::<f32>(&[blocks * self.block_len()])?;\n            let mut tensor_plain = tensor.try_as_plain_mut()?;\n            let slice = tensor_plain.as_slice_mut::<f32>()?;\n            for b in 0..blocks {\n                let block = &mut slice[b * self.block_len()..][..self.block_len()];\n                let qblock = &input[b * self.block_bytes()..][..self.block_bytes()];\n                self.dequant_block_f32(qblock, block);\n            }\n            Ok(tensor)\n        }\n    }\n\n    fn dequant_f16(&self, input: &[u8]) -> TractResult<Tensor> {\n        unsafe {\n            let blocks = input.len() / self.block_bytes();\n            let mut tensor = Tensor::uninitialized::<f16>(&[blocks * self.block_len()])?;\n            let mut tensor_plain = tensor.try_as_plain_mut()?;\n            let slice = tensor_plain.as_slice_mut::<f16>()?;\n            for b in 0..blocks {\n                let block = &mut slice[b * self.block_len()..][..self.block_len()];\n                let qblock = &input[b * self.block_bytes()..][..self.block_bytes()];\n                self.dequant_block_f16(qblock, block);\n            }\n            Ok(tensor)\n        }\n    }\n\n    fn extract_at_offset_f16(&self, input: &[u8], offset: usize) -> f16 {\n        let len = self.block_len();\n        let block_id = offset / len;\n        let mut block = vec![f16::zero(); self.block_len()];\n        self.dequant_block_f16(\n            &input[block_id * self.block_bytes()..][..self.block_bytes()],\n            &mut block,\n        );\n        block[offset % len]\n    }\n\n    fn extract_at_offset_f32(&self, input: &[u8], offset: usize) -> f32 {\n        let len = self.block_len();\n        let block_id = offset / len;\n        let mut block = vec![f32::zero(); self.block_len()];\n        self.dequant_block_f32(\n            &input[block_id * self.block_bytes()..][..self.block_bytes()],\n            &mut block,\n        );\n        block[offset % len]\n    }\n\n    fn simulate_precision_loss(\n        &self,\n        mut tensor: Tensor,\n        block_axis: usize,\n    ) -> TractResult<Tensor> {\n        ensure!(block_axis == tensor.rank() - 1);\n        ensure!(tensor.shape()[block_axis] % self.block_len() == 0);\n        let mut scratch = vec![0u8; self.block_bytes()];\n        if tensor.datum_type() == f32::datum_type() {\n            let mut tensor_plain = tensor.try_as_plain_mut()?;\n            for block in tensor_plain.as_slice_mut::<f32>()?.chunks_mut(self.block_len()) {\n                self.quant_block_f32(block, &mut scratch);\n                self.dequant_block_f32(&scratch, block);\n            }\n            drop(tensor_plain);\n            Ok(tensor)\n        } else if tensor.datum_type() == f16::datum_type() {\n            let mut tensor_plain = tensor.try_as_plain_mut()?;\n            for block in tensor_plain.as_slice_mut::<f16>()?.chunks_mut(self.block_len()) {\n                self.quant_block_f16(block, &mut scratch);\n                self.dequant_block_f16(&scratch, block);\n            }\n            drop(tensor_plain);\n            Ok(tensor)\n        } else {\n            todo!()\n        }\n    }\n\n    fn pack(\n        &self,\n        input: &[u8],\n        k: usize,\n        r: usize,\n        zip: usize,\n        scales_at_end: bool,\n    ) -> TractResult<EagerPackedInput>;\n\n    unsafe fn extract_packed_panel(\n        &self,\n        value: &EagerPackedInput,\n        target: &PackedFormat,\n        panel: usize,\n        scratch: *mut u8,\n    ) -> TractResult<()>;\n\n    fn extract_at_mn_f16(\n        &self,\n        value: &EagerPackedInput,\n        mn: usize,\n        target: &mut [f16],\n    ) -> TractResult<()>;\n\n    fn extract_at_mn_f32(\n        &self,\n        value: &EagerPackedInput,\n        mn: usize,\n        target: &mut [f32],\n    ) -> TractResult<()>;\n}\n\ndyn_clone::clone_trait_object!(BlockQuant);\ndyn_hash::hash_trait_object!(BlockQuant);\ndyn_eq::eq_trait_object!(BlockQuant);\nimpl_downcast!(BlockQuant);\n\n#[allow(clippy::derived_hash_with_manual_eq)]\n#[derive(Clone, Hash)]\npub struct PackedBlockQuantFormat {\n    pub bq: Box<dyn BlockQuant>,\n    pub r: usize,\n    pub zip: usize,\n    pub scales_at_end: bool,\n}\n\nimpl PartialEq for PackedBlockQuantFormat {\n    fn eq(&self, other: &Self) -> bool {\n        *self.bq == *other.bq\n            && self.r == other.r\n            && self.zip == other.zip\n            && self.scales_at_end == other.scales_at_end\n    }\n}\n\nimpl Eq for PackedBlockQuantFormat {}\n\nimpl Display for PackedBlockQuantFormat {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Packed{}[{}]\", &*self.bq, self.r)?;\n        if self.zip != 0 {\n            write!(f, \"Z{}\", self.zip)?;\n        }\n        if self.scales_at_end {\n            write!(f, \"Se\")?;\n        }\n        Ok(())\n    }\n}\n\nimpl Debug for PackedBlockQuantFormat {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        <Self as Display>::fmt(self, f)\n    }\n}\n\nimpl PackedBlockQuantFormat {\n    pub fn new(bq: &dyn BlockQuant, r: usize, zip: usize, scales_at_end: bool) -> Self {\n        PackedBlockQuantFormat { bq: clone_box(bq), r, zip, scales_at_end }\n    }\n\n    pub fn simulate_precision_loss(\n        &self,\n        tensor: Tensor,\n        block_axis: usize,\n    ) -> TractResult<Tensor> {\n        self.bq.simulate_precision_loss(tensor, block_axis)\n    }\n\n    pub fn pack(&self, input: &[u8], k: usize) -> TractResult<EagerPackedInput> {\n        self.bq.pack(input, k, self.r, self.zip, self.scales_at_end)\n    }\n}\n\nimpl MMMInputFormat for PackedBlockQuantFormat {\n    fn prepare_tensor(&self, t: &Tensor, _k_axis: usize, _mn_axis: usize) -> TractResult<Tensor> {\n        let bqs = t.try_storage_as::<BlockQuantStorage>()?;\n        let num_groups: usize =\n            if t.rank() > 2 { t.shape()[..t.rank() - 2].iter().product() } else { 1 };\n        let m_per_group = t.shape()[t.rank().saturating_sub(2)];\n        let k = *t.shape().last().unwrap();\n        let values = (0..num_groups)\n            .map(|g| {\n                let slice = block_quant_slice(bqs.value(), &*self.bq, m_per_group, k, g);\n                let packed = self.pack(slice, k)?;\n                Ok(Box::new(packed) as Box<dyn MMMInputValue>)\n            })\n            .collect::<TractResult<Vec<_>>>()?;\n        let leading_shape = &t.shape()[..t.rank().saturating_sub(2)];\n        Ok(crate::mmm::PackedMatrixStorage::new_batched(leading_shape, values)\n            .into_tensor(t.datum_type()))\n    }\n\n    fn prepare_one(\n        &self,\n        t: &Tensor,\n        k_axis: usize,\n        mn_axis: usize,\n    ) -> TractResult<Box<dyn MMMInputValue>> {\n        // this code path is essentially there for test scenarios\n        let t = if t.is_plain() && t.datum_type().is_number() {\n            let k = t.shape()[k_axis];\n            let m = t.shape()[mn_axis];\n            assert!(k % self.bq.block_len() == 0);\n            let t: Cow<Tensor> = if k_axis == 1 && mn_axis == 0 {\n                Cow::Borrowed(t)\n            } else {\n                Cow::Owned(t.clone().move_axis(1, 0)?)\n            };\n            let quant = if t.datum_type() == f32::datum_type() {\n                self.bq.quant_f32(t.try_as_plain()?.as_slice()?)?\n            } else if t.datum_type() == f16::datum_type() {\n                self.bq.quant_f16(t.try_as_plain()?.as_slice()?)?\n            } else {\n                todo!()\n            };\n            Cow::Owned(\n                BlockQuantStorage::new(self.bq.clone(), m, k, Arc::new(quant))?\n                    .into_tensor_with_shape(t.datum_type(), &[1, m, k]),\n            )\n        } else {\n            Cow::Borrowed(t)\n        };\n        ensure!(mn_axis == 0);\n        ensure!(k_axis == 1);\n        let bqs = t.try_storage_as::<BlockQuantStorage>()?;\n        let k = *t.shape().last().unwrap();\n        let packed = self.pack(bqs.value(), k)?;\n        Ok(Box::new(packed))\n    }\n\n    fn precursor(&self) -> WeightType {\n        WeightType::BlockQuant(self.bq.clone())\n    }\n\n    fn k_alignment(&self) -> usize {\n        self.bq.block_len()\n    }\n\n    fn r(&self) -> usize {\n        self.r\n    }\n\n    fn mem_size(&self, k: TDim, mn: TDim) -> TDim {\n        k * mn * self.bq.block_bytes() / self.bq.block_len()\n    }\n\n    fn extract_at_mn_f16(\n        &self,\n        data: &EagerPackedInput,\n        mn: usize,\n        slice: &mut [f16],\n    ) -> TractResult<()> {\n        self.bq.extract_at_mn_f16(data, mn, slice)\n    }\n\n    fn extract_at_mn_f32(\n        &self,\n        data: &EagerPackedInput,\n        mn: usize,\n        slice: &mut [f32],\n    ) -> TractResult<()> {\n        self.bq.extract_at_mn_f32(data, mn, slice)\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/block_quant/q4_0.rs",
    "content": "use crate::mmm::PackedExoticFact;\n\nuse super::*;\nuse num_traits::{AsPrimitive, Float, Zero};\nuse std::alloc::Layout;\n\n#[derive(Copy, Clone, Hash, PartialEq, Eq)]\npub struct BaseQ4_0<const QK: usize = 32>;\n\npub const Q4_0: BaseQ4_0 = BaseQ4_0::<32>;\n\nimpl<const QK: usize> Debug for BaseQ4_0<QK> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        if QK == 32 { write!(f, \"Q4_0\") } else { write!(f, \"BaseQ4_0<{QK}>\") }\n    }\n}\n\nimpl<const QK: usize> BaseQ4_0<QK> {\n    fn quant_block<T>(&self, block: &[T], quant: &mut [u8])\n    where\n        f32: AsPrimitive<i8> + From<T>,\n        T: Debug + Float,\n    {\n        assert!(quant.len() == self.block_bytes());\n        assert!(block.len() == self.block_len());\n        let mut writer = NibbleWriter::for_slice(quant);\n        let mut amax = T::zero();\n        let mut max = T::zero();\n        for v in block {\n            if amax < v.abs() {\n                amax = v.abs();\n                max = *v;\n            }\n        }\n        let scale = f32::from(max) / -8f32;\n        let r_scale = if scale.is_zero() { 0f32 } else { scale.recip() };\n        writer.write_f16(f16::from_f32(scale));\n\n        for idx in 0..block.len() {\n            // Quant block in GGML nibble order\n            let ggml_idx = (block.len() / 2) * (idx % 2) + (idx / 2);\n            let i: i8 = (f32::from(block[ggml_idx]) * r_scale + 8.5f32).as_();\n            writer.write_i4(i.min(15));\n        }\n    }\n\n    fn dequant_block<T: Float + 'static>(&self, quant: &[u8], block: &mut [T])\n    where\n        f16: AsPrimitive<T>,\n        i8: AsPrimitive<T>,\n    {\n        assert!(quant.len() == self.block_bytes());\n        assert!(block.len() == self.block_len());\n        let mut nibbles = NibbleReader::for_slice(quant);\n        let d: T = nibbles.read_f16().as_();\n        for idx in 0..block.len() {\n            let ggml_idx = (block.len() / 2) * (idx % 2) + (idx / 2);\n            block[ggml_idx] = (nibbles.read_i4() - 8).as_() * d;\n        }\n    }\n\n    unsafe fn extract_panel_t<T: Float + Debug + 'static>(\n        &self,\n        value: &EagerPackedInput,\n        target: &PackedFormat,\n        panel: usize,\n        scratch: *mut u8,\n    ) -> TractResult<()>\n    where\n        f16: AsPrimitive<T>,\n        i8: AsPrimitive<T>,\n    {\n        let pbqf: &PackedBlockQuantFormat =\n            value.fact.format.downcast_ref().with_context(|| {\n                format!(\"Expecing PackedBlockQuantFormat, found {:?}\", value.fact.format)\n            })?;\n        ensure!(pbqf.r == target.r);\n        ensure!(value.fact.k % self.block_len() == 0);\n        ensure!(*pbqf.bq == *(self as &dyn BlockQuant));\n        let scratch =\n            unsafe { std::slice::from_raw_parts_mut(scratch as *mut T, value.fact.k * target.r) };\n        let blocks_for_k = value.fact.k / self.block_len();\n        let row_bytes = blocks_for_k * self.block_bytes();\n        let input = &value.packed[panel * target.r * row_bytes..];\n        let mut scales = vec![T::zero(); target.r];\n        let mut scratch = scratch.iter_mut();\n        let zipped_order = zipped_order(pbqf.r, pbqf.zip);\n        let mut weights = vec![0i8; pbqf.r];\n        let panel_block_bytes = target.r * self.block_bytes();\n        let (scale_offset, weights_offset) = if pbqf.scales_at_end {\n            (panel_block_bytes - target.r * f16::datum_type().size_of(), 0)\n        } else {\n            (0, target.r * f16::datum_type().size_of())\n        };\n        for block in 0..blocks_for_k {\n            let block = &input[block * panel_block_bytes..][..panel_block_bytes];\n            let mut s_reader = NibbleReader::for_slice(&block[scale_offset..]);\n            let mut w_reader = NibbleReader::for_slice(&block[weights_offset..]);\n            for s in &mut scales {\n                *s = s_reader.read_f16().as_();\n            }\n            for _ in 0..self.block_len() {\n                for &o in &zipped_order {\n                    weights[o] = w_reader.read_i4();\n                }\n                for (w, s) in weights.iter().zip(scales.iter()) {\n                    *scratch.next().unwrap() = *s * (*w - 8).as_();\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn extract_at_mn_t<T: Float + Debug + 'static>(\n        &self,\n        value: &EagerPackedInput,\n        mn: usize,\n        target: &mut [T],\n    ) -> TractResult<()>\n    where\n        f16: AsPrimitive<T>,\n        i8: AsPrimitive<T>,\n    {\n        let pbqf: &PackedBlockQuantFormat =\n            value.fact.format.downcast_ref().with_context(|| {\n                format!(\"Expecing PackedBlockQuantFormat, found {:?}\", value.fact.format)\n            })?;\n        ensure!(value.fact.k % self.block_len() == 0);\n        ensure!(*pbqf.bq == *(self as &dyn BlockQuant));\n        ensure!(value.fact.mn.to_usize().ok().map(|it| mn < it).unwrap_or(true));\n        ensure!(value.fact.k == target.len());\n        let blocks_for_k = value.fact.k / self.block_len();\n        let row_bytes = blocks_for_k * self.block_bytes();\n        let panel = mn / pbqf.r;\n        let value = &value.packed[panel * pbqf.r * row_bytes..];\n        let mut target = target.iter_mut();\n        let zipped_order =\n            zipped_order(pbqf.r, pbqf.zip).iter().position(|x| *x == mn % pbqf.r).unwrap();\n\n        let panel_block_bytes = pbqf.r * self.block_bytes();\n        let (scale_offset, weights_offset) = if pbqf.scales_at_end {\n            (panel_block_bytes - pbqf.r * f16::datum_type().size_of(), 0)\n        } else {\n            (0, pbqf.r * f16::datum_type().size_of())\n        };\n        unsafe {\n            for block in 0..blocks_for_k {\n                let block = value.as_ptr().add(block * panel_block_bytes);\n                let scale = *((block.add(scale_offset) as *const f16).add(mn % pbqf.r));\n                let scale: T = scale.as_();\n                for i in 0..self.block_len() {\n                    let byte = *block.add(weights_offset + i * pbqf.r / 2 + zipped_order / 2);\n                    let nib = if zipped_order % 2 == 0 { byte & 0x0F } else { byte >> 4 };\n                    *target.next().unwrap() = scale * ((nib as i8) - 8).as_();\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\nfn zipped_order(r: usize, zip: usize) -> Vec<usize> {\n    if zip == 0 {\n        (0..r).collect_vec()\n    } else {\n        (0..r)\n            .map(|i| {\n                let vec_pair_ix = i / (2 * zip);\n                let lane = (i % (2 * zip)) / 2;\n                let side = i % 2;\n                vec_pair_ix * 2 * zip + side * zip + lane\n            })\n            .collect_vec()\n    }\n}\n\nimpl<const QK: usize> BlockQuant for BaseQ4_0<QK> {\n    fn block_len(&self) -> usize {\n        QK\n    }\n\n    fn block_bytes(&self) -> usize {\n        2 + self.block_len() / 2\n    }\n\n    fn quant_block_f32(&self, block: &[f32], quant: &mut [u8]) {\n        self.quant_block(block, quant)\n    }\n\n    fn quant_block_f16(&self, block: &[f16], quant: &mut [u8]) {\n        self.quant_block(block, quant)\n    }\n\n    fn dequant_block_f32(&self, quant: &[u8], block: &mut [f32]) {\n        self.dequant_block(quant, block)\n    }\n\n    fn dequant_block_f16(&self, quant: &[u8], block: &mut [f16]) {\n        self.dequant_block(quant, block)\n    }\n\n    // s0_0 n0_0 n0_1 n0_2 n0_3 ... n0_30n0_31 s0_32 n0_32n0_33 ...\n    // s1_0 n1_0 n1_1 n1_2 n1_3 ... n1_30n1_31 s1_32 n1_32n1_33 ...\n    //\n    //  becomes (with r=4)\n    //\n    //  s0_0  s1_0  s2_0  s3_0  n0_0 n1_0 n2_0 n3_0  n0_1 n1_1 n2_1 n3_1 ... n0_33 n1_33 n2_33 n3_33\n    //  s0_32 s1_32 s2_32 s3_32 n0_0 n1_0 n2_0 n3_0  n0_1 n1_1 n2_1 n3_1 ... n0_33 n1_33 n2_33 n3_33\n    //  ...\n    fn pack(\n        &self,\n        input: &[u8],\n        k: usize,\n        r: usize,\n        zip: usize,\n        scales_at_end: bool,\n    ) -> TractResult<EagerPackedInput> {\n        ensure!(input.len() % self.block_bytes() == 0);\n        ensure!(k % self.block_len() == 0);\n        // ensure!(input.len() == k * r / self.block_len() * self.block_bytes());\n        ensure!(zip < r);\n        let m = if input.len() == 0 {\n            0\n        } else {\n            input.len() / self.block_bytes() * self.block_len() / k\n        };\n        let panels = m.divceil(r);\n        let blocks_for_k = k / self.block_len();\n        let row_bytes = blocks_for_k * self.block_bytes();\n        let panel_bytes = row_bytes * r;\n        let mut blob =\n            unsafe { Blob::for_layout(Layout::from_size_align(panel_bytes * panels, 128)?) };\n        let mut writer = NibbleWriter::for_slice(&mut blob);\n        let order = zipped_order(r, zip);\n        let mut scales = vec![f16::zero(); r];\n        for p in 0..panels {\n            let input = &input[(r * p) * row_bytes..];\n            let mut readers = (0..r)\n                .map(|r| {\n                    // manage partial panel\n                    let offset = if r * row_bytes < input.len() { r * row_bytes } else { 0 };\n                    NibbleReader::for_slice(&input[offset..])\n                })\n                .collect_vec();\n            let mut temp_nibbles = vec![vec![0i8; self.block_len()]; r];\n            for _ in 0..blocks_for_k {\n                for (row, reader) in readers.iter_mut().enumerate() {\n                    scales[row] = reader.read_f16();\n                    temp_nibbles[row] =\n                        (0..self.block_len()).map(|_| reader.read_i4()).collect_vec();\n                }\n                if !scales_at_end {\n                    scales.iter().for_each(|s| writer.write_f16(*s))\n                }\n                for pos in 0..self.block_len() {\n                    for &row in &order {\n                        let ggml_idx = pos / (self.block_len() / 2) + (2 * pos) % self.block_len();\n                        let nib = temp_nibbles[row][ggml_idx];\n                        writer.write_i4(nib);\n                    }\n                }\n                if scales_at_end {\n                    scales.iter().for_each(|s| writer.write_f16(*s))\n                }\n            }\n        }\n        Ok(EagerPackedInput {\n            fact: PackedExoticFact {\n                format: Box::new(PackedBlockQuantFormat {\n                    bq: Box::new(*self),\n                    r,\n                    zip,\n                    scales_at_end,\n                }),\n                mn: m.to_dim(),\n                k,\n            },\n            packed: blob.into(),\n            panel_bytes,\n            mn: m,\n        })\n    }\n\n    unsafe fn extract_packed_panel(\n        &self,\n        value: &EagerPackedInput,\n        target: &PackedFormat,\n        panel: usize,\n        scratch: *mut u8,\n    ) -> TractResult<()> {\n        unsafe {\n            dispatch_floatlike!(Self::extract_panel_t(target.dt)(\n                self, value, target, panel, scratch\n            ))\n        }\n    }\n\n    fn extract_at_mn_f16(\n        &self,\n        value: &EagerPackedInput,\n        mn: usize,\n        target: &mut [f16],\n    ) -> TractResult<()> {\n        self.extract_at_mn_t(value, mn, target)\n    }\n\n    fn extract_at_mn_f32(\n        &self,\n        value: &EagerPackedInput,\n        mn: usize,\n        target: &mut [f32],\n    ) -> TractResult<()> {\n        self.extract_at_mn_t(value, mn, target)\n    }\n}\n\nimpl<const QK: usize> Display for BaseQ4_0<QK> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Q4_0\")\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use num_traits::Zero;\n    use tract_data::internal::tract_ndarray::Array2;\n\n    use crate::pack::PackedFormat;\n\n    use super::*;\n\n    fn test_loop_f32(b: impl BlockQuant, data: &[f32]) {\n        let mut input = data.to_vec();\n        while input.len() % b.block_len() != 0 {\n            input.push(0f32);\n        }\n        let quant = b.quant_f32(&input).unwrap();\n        let result = b.dequant_f32(&quant).unwrap();\n        let view = &result.try_as_plain().unwrap().as_slice::<f32>().unwrap()[..data.len()];\n        assert_eq!(data, view);\n    }\n\n    fn test_loop_f16(b: impl BlockQuant, data: &[f32]) {\n        let mut input = data.iter().map(|f| f16::from_f32(*f)).collect_vec();\n        while input.len() % b.block_len() != 0 {\n            input.push(f16::zero());\n        }\n        let quant = b.quant_f16(&input).unwrap();\n        let result = b.dequant_f16(&quant).unwrap();\n        let view = &result.try_as_plain().unwrap().as_slice::<f16>().unwrap();\n        assert_eq!(&input, view);\n    }\n\n    #[test]\n    fn loop_q4f32_pos() {\n        test_loop_f32(Q4_0, &[1.0, 2.0, 3.0, 4.0]);\n    }\n\n    #[test]\n    fn loop_q4f16_pos() {\n        test_loop_f16(Q4_0, &[1.0, 2.0, 3.0, 4.0]);\n    }\n\n    #[test]\n    fn loop_q4f32_neg() {\n        test_loop_f32(Q4_0, &[-1.0, -2.0, -3.0, -4.0]);\n    }\n\n    #[test]\n    fn loop_q4f16_neg() {\n        test_loop_f16(Q4_0, &[-1.0, -2.0, -3.0, -4.0]);\n    }\n\n    #[test]\n    fn loop_q4_big_pos() {\n        test_loop_f32(Q4_0, &[1234.0]);\n        test_loop_f16(Q4_0, &[1234.0]);\n    }\n\n    #[test]\n    fn loop_q4_big_neg() {\n        test_loop_f32(Q4_0, &[-1234.0]);\n        test_loop_f16(Q4_0, &[-1234.0]);\n    }\n\n    fn test_extract_f32(b: impl BlockQuant, data: &[f32]) {\n        let mut input = data.to_vec();\n        while input.len() % b.block_len() != 0 {\n            input.push(0f32);\n        }\n        let quant = b.quant_f32(&input).unwrap();\n        for (ix, v) in data.iter().enumerate() {\n            assert_eq!(b.extract_at_offset_f32(&quant, ix).round(), *v);\n        }\n    }\n\n    #[test]\n    fn extract_q40f32_pos() {\n        let data = (1..).map(|i| ((i % 14) - 6) as f32).take(5 * Q4_0.block_len()).collect_vec();\n        test_extract_f32(Q4_0, &data);\n    }\n\n    fn test_pack_then_extract_panel(\n        q: impl BlockQuant,\n        k: usize,\n        m: usize,\n        r: usize,\n        zip: usize,\n        scales_at_end: bool,\n    ) -> TractResult<()> {\n        let weights_orig =\n            Array2::from_shape_fn((m, k), |(m, k)| ((m * 31 + k * 17) % 20) as f32 - 10.)\n                .into_tensor();\n        let weights_f32 = q\n            .dequant_f32(&q.quant_f32(weights_orig.try_as_plain()?.as_slice::<f32>()?)?)?\n            .into_shape(&[m, k])?;\n        let packer = PackedFormat::new(f32::datum_type(), r, 128);\n        let packed_f32 = packer.pack_tensor(&weights_f32, 1, 0)?;\n\n        let q4 = q.quant_f32(weights_f32.try_as_plain()?.as_slice::<f32>()?)?;\n        let packed_q4 = q.pack(&q4, k, r, zip, scales_at_end)?;\n\n        for panel in 0..packed_f32.panels_count() {\n            unsafe {\n                let panel_f32 = packed_f32.panel_bytes(panel, None)?;\n                let panel_f32 = std::slice::from_raw_parts(panel_f32 as *const f32, k * r);\n                let mut panel_q4 = Tensor::zero::<f32>(&[k * r])?;\n                q.extract_packed_panel(\n                    &packed_q4,\n                    &packer,\n                    panel,\n                    panel_q4.as_bytes_mut().as_mut_ptr(),\n                )?;\n                assert_eq!(panel_q4.try_as_plain()?.as_slice::<f32>()?, panel_f32);\n            }\n        }\n        Ok(())\n    }\n\n    #[test]\n    fn pack_then_extract_panel() -> TractResult<()> {\n        test_pack_then_extract_panel(BaseQ4_0::<2>, 4, 4, 2, 0, false)\n    }\n\n    #[test]\n    fn pack_then_extract_panel_with_zip() -> TractResult<()> {\n        test_pack_then_extract_panel(BaseQ4_0::<2>, 2, 8, 8, 4, false)\n    }\n\n    #[test]\n    fn pack_then_extract_panel_with_scales_at_end() -> TractResult<()> {\n        test_pack_then_extract_panel(BaseQ4_0::<2>, 2, 4, 4, 0, true)\n    }\n\n    fn test_pack_then_extract_row(\n        q: impl BlockQuant,\n        k: usize,\n        m: usize,\n        r: usize,\n        zip: usize,\n        scales_at_end: bool,\n    ) -> TractResult<()> {\n        let weights_orig =\n            Array2::from_shape_fn((m, k), |(m, k)| ((m * 31 + k * 17) % 20) as f32 - 10.)\n                .into_tensor();\n        let weights_f32 = q\n            .dequant_f32(&q.quant_f32(weights_orig.try_as_plain()?.as_slice::<f32>()?)?)?\n            .into_shape(&[m, k])?;\n        let packer = PackedFormat::new(f32::datum_type(), r, 128);\n        let packed_f32 = packer.pack_tensor(&weights_f32, 1, 0)?;\n\n        let q4 = q.quant_f32(weights_f32.try_as_plain()?.as_slice::<f32>()?)?;\n        let packed_q4 = q.pack(&q4, k, r, zip, scales_at_end)?;\n\n        for row in 0..packed_f32.mn() {\n            unsafe {\n                let panel_f32 = packed_f32.panel_bytes(row / r, None)?;\n                let panel_f32 = std::slice::from_raw_parts(panel_f32 as *const f32, k * r);\n                let row_f32 = (0..k).map(|ix| panel_f32[row % r + r * ix]).collect_vec();\n\n                let mut q4 = vec![0f32; k];\n                q.extract_at_mn_f32(&packed_q4, row, &mut q4)?;\n                assert_eq!(q4, row_f32);\n            }\n        }\n        Ok(())\n    }\n\n    #[test]\n    fn pack_then_extract_row() -> TractResult<()> {\n        test_pack_then_extract_row(BaseQ4_0::<2>, 4, 4, 2, 0, false)\n    }\n\n    #[test]\n    fn pack_then_extract_row_with_zip() -> TractResult<()> {\n        test_pack_then_extract_row(BaseQ4_0::<2>, 2, 8, 8, 4, false)\n    }\n\n    #[test]\n    fn pack_then_extract_row_with_scales_at_end() -> TractResult<()> {\n        test_pack_then_extract_row(BaseQ4_0::<2>, 2, 4, 4, 0, true)\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/block_quant/q8_1.rs",
    "content": "use crate::mmm::PackedExoticFact;\n\nuse super::*;\nuse num_traits::{AsPrimitive, Float, Zero};\nuse std::alloc::Layout;\nuse std::ops::AddAssign;\n\n#[derive(Copy, Clone, Hash, PartialEq, Eq)]\npub struct BaseQ8_1<const QK: usize = 32>;\n\npub const Q8_1: BaseQ8_1 = BaseQ8_1::<32>;\n\nimpl<const QK: usize> Debug for BaseQ8_1<QK> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        if QK == 32 { write!(f, \"Q8_1\") } else { write!(f, \"BaseQ8_1<{QK}>\") }\n    }\n}\n\nimpl<const QK: usize> BaseQ8_1<QK> {\n    fn quant_block<T>(&self, block: &[T], quant: &mut [u8])\n    where\n        f32: AsPrimitive<i8> + From<T>,\n        T: Debug + Float + AsPrimitive<f16> + AddAssign + 'static,\n    {\n        assert!(quant.len() == self.block_bytes());\n        assert!(block.len() == self.block_len());\n        let mut writer = NibbleWriter::for_slice(quant);\n        let mut amax = T::zero();\n        let mut max = T::zero();\n        let mut sum = T::zero();\n        for v in block {\n            if amax < v.abs() {\n                amax = v.abs();\n                max = *v;\n            }\n            sum += *v;\n        }\n\n        let scale = f32::from(max) / 127f32;\n        let r_scale = if scale.is_zero() { 0f32 } else { scale.recip() };\n        writer.write_f16(f16::from_f32(scale));\n        writer.write_f16(sum.as_());\n\n        for val_f in block {\n            let i: i8 = (f32::from(*val_f) * r_scale).round().as_();\n            writer.write_i8(i);\n        }\n    }\n\n    fn dequant_block<T: Float + 'static>(&self, quant: &[u8], block: &mut [T])\n    where\n        f16: AsPrimitive<T>,\n        i8: AsPrimitive<T>,\n    {\n        assert!(quant.len() == self.block_bytes());\n        assert!(block.len() == self.block_len());\n        let mut quants = NibbleReader::for_slice(quant);\n        let d: T = quants.read_f16().as_();\n        let _sum: T = quants.read_f16().as_();\n        for val_f in block {\n            *val_f = (quants.read_i8()).as_() * d;\n        }\n    }\n\n    unsafe fn extract_panel_t<T: Float + Debug + 'static>(\n        &self,\n        value: &EagerPackedInput,\n        target: &PackedFormat,\n        panel: usize,\n        scratch: *mut u8,\n    ) -> TractResult<()>\n    where\n        f16: AsPrimitive<T>,\n        i8: AsPrimitive<T>,\n    {\n        let pbqf: &PackedBlockQuantFormat =\n            value.fact.format.downcast_ref().with_context(|| {\n                format!(\"Expecing PackedBlockQuantFormat, found {:?}\", value.fact.format)\n            })?;\n        ensure!(pbqf.r == target.r);\n        ensure!(value.fact.k % self.block_len() == 0);\n        ensure!(*pbqf.bq == *(self as &dyn BlockQuant));\n        let scratch =\n            unsafe { std::slice::from_raw_parts_mut(scratch as *mut T, value.fact.k * target.r) };\n        let blocks_for_k = value.fact.k / self.block_len();\n        let row_bytes = blocks_for_k * self.block_bytes();\n        let input = &value.packed[panel * target.r * row_bytes..];\n        let mut scales = vec![T::zero(); target.r];\n        let mut scratch = scratch.iter_mut();\n        let mut weights = vec![0i8; pbqf.r];\n        let panel_block_bytes = target.r * self.block_bytes();\n        let (params_offset, weights_offset) = if pbqf.scales_at_end {\n            (panel_block_bytes - target.r * 2 * f16::datum_type().size_of(), 0)\n        } else {\n            (0, target.r * 2 * f16::datum_type().size_of())\n        };\n        for block in 0..blocks_for_k {\n            let block = &input[block * panel_block_bytes..][..panel_block_bytes];\n            let mut s_reader = NibbleReader::for_slice(&block[params_offset..]);\n            let mut w_reader = NibbleReader::for_slice(&block[weights_offset..]);\n            // Layout: [scale_0, sum_0, scale_1, sum_1, .., weights]\n            for s in &mut scales {\n                *s = s_reader.read_f16().as_();\n                // Unused sums\n                s_reader.read_f16();\n            }\n\n            for _ in 0..self.block_len() {\n                for w in &mut weights {\n                    *w = w_reader.read_i8();\n                }\n                for (w, s) in weights.iter().zip(scales.iter()) {\n                    *scratch.next().unwrap() = *s * (*w).as_();\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn extract_at_mn_t<T: Float + Debug + 'static>(\n        &self,\n        value: &EagerPackedInput,\n        mn: usize,\n        target: &mut [T],\n    ) -> TractResult<()>\n    where\n        f16: AsPrimitive<T>,\n        i8: AsPrimitive<T>,\n    {\n        let pbqf: &PackedBlockQuantFormat =\n            value.fact.format.downcast_ref().with_context(|| {\n                format!(\"Expecing PackedBlockQuantFormat, found {:?}\", value.fact.format)\n            })?;\n        ensure!(value.fact.k % self.block_len() == 0);\n        ensure!(*pbqf.bq == *(self as &dyn BlockQuant));\n        ensure!(value.fact.mn.to_usize().ok().map(|it| mn < it).unwrap_or(true));\n        ensure!(value.fact.k == target.len());\n        let blocks_for_k = value.fact.k / self.block_len();\n        let row_bytes = blocks_for_k * self.block_bytes();\n        let panel = mn / pbqf.r;\n        let value = &value.packed[panel * pbqf.r * row_bytes..];\n        let mut target = target.iter_mut();\n        let panel_block_bytes = pbqf.r * self.block_bytes();\n        let (scale_offset, weights_offset) = if pbqf.scales_at_end {\n            (panel_block_bytes - pbqf.r * 2 * f16::datum_type().size_of(), 0)\n        } else {\n            (0, pbqf.r * 2 * f16::datum_type().size_of())\n        };\n        unsafe {\n            for block in 0..blocks_for_k {\n                let block = value.as_ptr().add(block * panel_block_bytes);\n                let scale = *((block.add(scale_offset) as *const f16).add(2 * (mn % pbqf.r)));\n                let scale: T = scale.as_();\n                for i in 0..self.block_len() {\n                    let byte = *block.add(weights_offset + i * pbqf.r + mn % pbqf.r);\n                    *target.next().unwrap() = scale * (byte as i8).as_();\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl<const QK: usize> BlockQuant for BaseQ8_1<QK> {\n    fn block_len(&self) -> usize {\n        QK\n    }\n\n    fn block_bytes(&self) -> usize {\n        4 + self.block_len()\n    }\n\n    fn quant_block_f32(&self, block: &[f32], quant: &mut [u8]) {\n        self.quant_block(block, quant)\n    }\n\n    fn quant_block_f16(&self, block: &[f16], quant: &mut [u8]) {\n        self.quant_block(block, quant)\n    }\n\n    fn dequant_block_f32(&self, quant: &[u8], block: &mut [f32]) {\n        self.dequant_block(quant, block)\n    }\n\n    fn dequant_block_f16(&self, quant: &[u8], block: &mut [f16]) {\n        self.dequant_block(quant, block)\n    }\n\n    // s0_0 sum_0_0 n0_0 n0_1 n0_2 n0_3 ... n0_30n0_31 s0_32 sum0_32 n0_32n0_33 ...\n    // s1_0 sum_1_0 n1_0 n1_1 n1_2 n1_3 ... n1_30n1_31 s1_32 sum_1_32 n1_32n1_33 ...\n    //\n    //  becomes (with r=4)\n    //\n    //  s0_0   sum0_0  s1_0  sum 1_0  s2_0  sum2_0  s3_0  sum3_0   n0_0 n1_0 n2_0 n3_0  n0_1 n1_1 n2_1 n3_1 ... n0_33 n1_33 n2_33 n3_33\n    //  s0_32  sum0_32 s1_32 sum 1_32 s2_32 sum2_32 s3_32 sum3_32  n0_0 n1_0 n2_0 n3_0  n0_1 n1_1 n2_1 n3_1 ... n0_33 n1_33 n2_33 n3_33\n    //  ...\n    fn pack(\n        &self,\n        input: &[u8],\n        k: usize,\n        r: usize,\n        zip: usize,\n        scales_at_end: bool,\n    ) -> TractResult<EagerPackedInput> {\n        ensure!(input.len() % self.block_bytes() == 0);\n        ensure!(k % self.block_len() == 0);\n        ensure!(zip == 0, \"No zipping required for Q8_1\");\n        let m = if input.len() == 0 {\n            0\n        } else {\n            input.len() / self.block_bytes() * self.block_len() / k\n        };\n        let panels = m.divceil(r);\n        let blocks_for_k = k / self.block_len();\n        let row_bytes = blocks_for_k * self.block_bytes();\n        let panel_bytes = row_bytes * r;\n        let mut blob =\n            unsafe { Blob::for_layout(Layout::from_size_align(panel_bytes * panels, 128)?) };\n        let mut writer = NibbleWriter::for_slice(&mut blob);\n        let mut scales = vec![f16::zero(); r];\n        let mut sums = vec![f16::zero(); r];\n        for p in 0..panels {\n            let input = &input[(r * p) * row_bytes..];\n            let mut readers = (0..r)\n                .map(|r| {\n                    // manage partial panel\n                    let offset = if r * row_bytes < input.len() { r * row_bytes } else { 0 };\n                    NibbleReader::for_slice(&input[offset..])\n                })\n                .collect_vec();\n            let mut temp_quants = vec![vec![0i8; self.block_len()]; r];\n            for _ in 0..blocks_for_k {\n                for (row, reader) in readers.iter_mut().enumerate() {\n                    scales[row] = reader.read_f16();\n                    sums[row] = reader.read_f16();\n                    temp_quants[row] =\n                        (0..self.block_len()).map(|_| reader.read_i8()).collect_vec();\n                }\n                if !scales_at_end {\n                    scales.iter().zip(&sums).for_each(|(scale, sum)| {\n                        writer.write_f16(*scale);\n                        writer.write_f16(*sum);\n                    });\n                }\n                for pos in 0..self.block_len() {\n                    for row in &temp_quants {\n                        let q = row[pos];\n                        writer.write_i8(q);\n                    }\n                }\n                if scales_at_end {\n                    scales.iter().zip(&sums).for_each(|(scale, sum)| {\n                        writer.write_f16(*scale);\n                        writer.write_f16(*sum);\n                    });\n                }\n            }\n        }\n        Ok(EagerPackedInput {\n            fact: PackedExoticFact {\n                format: Box::new(PackedBlockQuantFormat {\n                    bq: Box::new(*self),\n                    r,\n                    zip,\n                    scales_at_end,\n                }),\n                mn: m.to_dim(),\n                k,\n            },\n            packed: blob.into(),\n            panel_bytes,\n            mn: m,\n        })\n    }\n\n    unsafe fn extract_packed_panel(\n        &self,\n        value: &EagerPackedInput,\n        target: &PackedFormat,\n        panel: usize,\n        scratch: *mut u8,\n    ) -> TractResult<()> {\n        unsafe {\n            dispatch_floatlike!(Self::extract_panel_t(target.dt)(\n                self, value, target, panel, scratch\n            ))\n        }\n    }\n\n    fn extract_at_mn_f16(\n        &self,\n        value: &EagerPackedInput,\n        mn: usize,\n        target: &mut [f16],\n    ) -> TractResult<()> {\n        self.extract_at_mn_t(value, mn, target)\n    }\n\n    fn extract_at_mn_f32(\n        &self,\n        value: &EagerPackedInput,\n        mn: usize,\n        target: &mut [f32],\n    ) -> TractResult<()> {\n        self.extract_at_mn_t(value, mn, target)\n    }\n}\n\nimpl<const QK: usize> Display for BaseQ8_1<QK> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Q8_1\")\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use num_traits::Zero;\n    use tract_data::internal::tract_ndarray::Array2;\n\n    use crate::pack::PackedFormat;\n\n    use super::*;\n\n    fn test_loop_f32(b: impl BlockQuant, data: &[f32]) -> TractResult<()> {\n        let mut input = data.to_vec();\n        while input.len() % b.block_len() != 0 {\n            input.push(0f32);\n        }\n        let ref_tensor = unsafe { Tensor::from_slice_align(&input, vector_size())? };\n\n        let quant = b.quant_f32(&input).unwrap();\n        let result = b.dequant_f32(&quant).unwrap();\n        result.close_enough(&ref_tensor, Approximation::VeryApproximate)\n    }\n\n    fn test_loop_f16(b: impl BlockQuant, data: &[f32]) -> TractResult<()> {\n        let mut input = data.iter().map(|f| f16::from_f32(*f)).collect_vec();\n        while input.len() % b.block_len() != 0 {\n            input.push(f16::zero());\n        }\n        let ref_tensor = unsafe { Tensor::from_slice_align(&input, vector_size())? };\n\n        let quant = b.quant_f16(&input).unwrap();\n        let result = b.dequant_f16(&quant).unwrap();\n        result.close_enough(&ref_tensor, Approximation::VeryApproximate)\n    }\n\n    #[test]\n    fn loop_q81f32_pos() -> TractResult<()> {\n        test_loop_f32(Q8_1, &[1.0, 2.0, 3.0, 4.0])?;\n        Ok(())\n    }\n\n    #[test]\n    fn loop_q81f16_pos() -> TractResult<()> {\n        test_loop_f16(Q8_1, &[1.0, 2.0, 3.0, 4.0])?;\n        Ok(())\n    }\n\n    #[test]\n    fn loop_q81f32_neg() -> TractResult<()> {\n        test_loop_f32(Q8_1, &[-1.0, -2.0, -3.0, -4.0])?;\n        Ok(())\n    }\n\n    #[test]\n    fn loop_q81f16_neg() -> TractResult<()> {\n        test_loop_f16(Q8_1, &[-1.0, -2.0, -3.0, -4.0])?;\n        Ok(())\n    }\n\n    #[test]\n    fn loop_q81_big_pos() -> TractResult<()> {\n        test_loop_f32(Q8_1, &[1234.0])?;\n        test_loop_f16(Q8_1, &[1234.0])?;\n        Ok(())\n    }\n\n    #[test]\n    fn loop_q81_big_neg() -> TractResult<()> {\n        test_loop_f32(Q8_1, &[-1234.0])?;\n        test_loop_f16(Q8_1, &[-1234.0])?;\n        Ok(())\n    }\n\n    fn test_extract_f32(b: impl BlockQuant, data: &[f32]) {\n        let mut input = data.to_vec();\n        while input.len() % b.block_len() != 0 {\n            input.push(0f32);\n        }\n        let quant = b.quant_f32(&input).unwrap();\n        for (ix, v) in data.iter().enumerate() {\n            assert_eq!(b.extract_at_offset_f32(&quant, ix).round(), *v);\n        }\n    }\n\n    #[test]\n    fn extract_q81f32_pos() {\n        let data = (1..).map(|i| ((i % 14) - 6) as f32).take(5 * Q8_1.block_len()).collect_vec();\n        test_extract_f32(Q8_1, &data);\n    }\n\n    fn test_pack_then_extract_panel(\n        q: impl BlockQuant,\n        k: usize,\n        m: usize,\n        r: usize,\n        scales_at_end: bool,\n    ) -> TractResult<()> {\n        let weights_orig =\n            Array2::from_shape_fn((m, k), |(m, k)| ((m * 31 + k * 17) % 20) as f32 - 10.)\n                .into_tensor();\n        let weights_f32 = q\n            .dequant_f32(&q.quant_f32(weights_orig.try_as_plain()?.as_slice::<f32>()?)?)?\n            .into_shape(&[m, k])?;\n        let packer = PackedFormat::new(f32::datum_type(), r, 128);\n        let packed_f32 = packer.pack_tensor(&weights_f32, 1, 0)?;\n\n        let q81 = q.quant_f32(weights_f32.try_as_plain()?.as_slice::<f32>()?)?;\n        let packed_q81 = q.pack(&q81, k, r, 0, scales_at_end)?;\n\n        for panel in 0..packed_f32.panels_count() {\n            unsafe {\n                let panel_f32 = packed_f32.panel_bytes(panel, None)?;\n                let panel_f32 = std::slice::from_raw_parts(panel_f32 as *const f32, k * r);\n                let mut panel_q81 = Tensor::zero::<f32>(&[k * r])?;\n                q.extract_packed_panel(\n                    &packed_q81,\n                    &packer,\n                    panel,\n                    panel_q81.as_bytes_mut().as_mut_ptr(),\n                )?;\n                assert_eq!(panel_q81.try_as_plain()?.as_slice::<f32>()?, panel_f32);\n            }\n        }\n        Ok(())\n    }\n\n    #[test]\n    fn pack_then_extract_panel() -> TractResult<()> {\n        test_pack_then_extract_panel(BaseQ8_1::<2>, 4, 4, 2, false)\n    }\n\n    #[test]\n    fn pack_then_extract_panel_with_scales_at_end() -> TractResult<()> {\n        test_pack_then_extract_panel(BaseQ8_1::<2>, 2, 4, 4, true)\n    }\n\n    fn test_pack_then_extract_row(\n        q: impl BlockQuant,\n        k: usize,\n        m: usize,\n        r: usize,\n        scales_at_end: bool,\n    ) -> TractResult<()> {\n        let weights_orig =\n            Array2::from_shape_fn((m, k), |(m, k)| ((m * 31 + k * 17) % 20) as f32 - 10.)\n                .into_tensor();\n        let weights_f32 = q\n            .dequant_f32(&q.quant_f32(weights_orig.try_as_plain()?.as_slice::<f32>()?)?)?\n            .into_shape(&[m, k])?;\n        let packer = PackedFormat::new(f32::datum_type(), r, 128);\n        let packed_f32 = packer.pack_tensor(&weights_f32, 1, 0)?;\n\n        let q81 = q.quant_f32(weights_f32.try_as_plain()?.as_slice::<f32>()?)?;\n        let packed_q81 = q.pack(&q81, k, r, 0, scales_at_end)?;\n\n        for row in 0..packed_f32.mn() {\n            unsafe {\n                let panel_f32 = packed_f32.panel_bytes(row / r, None)?;\n                let panel_f32 = std::slice::from_raw_parts(panel_f32 as *const f32, k * r);\n                let row_f32 = (0..k).map(|ix| panel_f32[row % r + r * ix]).collect_vec();\n\n                let mut q81 = vec![0f32; k];\n                q.extract_at_mn_f32(&packed_q81, row, &mut q81)?;\n                assert_eq!(q81, row_f32);\n            }\n        }\n        Ok(())\n    }\n\n    #[test]\n    fn pack_then_extract_row() -> TractResult<()> {\n        test_pack_then_extract_row(BaseQ8_1::<2>, 4, 4, 2, false)\n    }\n\n    #[test]\n    fn pack_then_extract_row_with_scales_at_end() -> TractResult<()> {\n        test_pack_then_extract_row(BaseQ8_1::<2>, 2, 4, 4, true)\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/block_quant/storage.rs",
    "content": "use std::fmt;\nuse std::sync::Arc;\n\nuse tract_data::internal::*;\n\nuse super::BlockQuant;\nuse super::BlockQuantFact;\n\n/// Concrete tensor storage for block-quantized weights.\n///\n/// Stores a single contiguous `Arc<Blob>` of quantized data along with the\n/// block-quant format. Shape lives on the tensor, not here.\n#[derive(Clone, PartialEq, Eq)]\npub struct BlockQuantStorage {\n    format: Box<dyn BlockQuant>,\n    data: Arc<Blob>,\n}\n\nimpl BlockQuantStorage {\n    fn expected_bytes(format: &dyn BlockQuant, m: usize, k: usize) -> usize {\n        m * k / format.block_len() * format.block_bytes()\n    }\n\n    pub fn new(\n        format: Box<dyn BlockQuant>,\n        m: usize,\n        k: usize,\n        data: Arc<Blob>,\n    ) -> TractResult<Self> {\n        let expected = Self::expected_bytes(&*format, m, k);\n        ensure!(\n            data.len() == expected,\n            \"BlockQuantStorage::new: blob length {} does not match expected {} (m={}, k={}, format={})\",\n            data.len(),\n            expected,\n            m,\n            k,\n            format,\n        );\n        Ok(Self { format, data })\n    }\n\n    pub fn format(&self) -> &dyn BlockQuant {\n        &*self.format\n    }\n\n    /// Returns the single contiguous blob.\n    pub fn value(&self) -> &Arc<Blob> {\n        &self.data\n    }\n\n    /// Converts this storage into a `Tensor` with the given shape.\n    ///\n    /// `dt` is the logical element type (e.g. f32, f16) — the type these\n    /// weights represent when dequantized.\n    pub fn into_tensor_with_shape(self, dt: DatumType, shape: &[usize]) -> Tensor {\n        Tensor::from_storage(dt, shape, self)\n    }\n}\n\n/// Returns a byte slice for a single group within contiguous block-quant data.\npub fn block_quant_slice<'a>(\n    data: &'a [u8],\n    format: &dyn BlockQuant,\n    m_per_group: usize,\n    k: usize,\n    g: usize,\n) -> &'a [u8] {\n    let row_bytes = k / format.block_len() * format.block_bytes();\n    let group_bytes = m_per_group * row_bytes;\n    let start = g * group_bytes;\n    &data[start..start + group_bytes]\n}\n\nimpl fmt::Debug for BlockQuantStorage {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"BlockQuantStorage({}, bytes={})\", self.format, self.data.len())\n    }\n}\n\nimpl fmt::Display for BlockQuantStorage {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"BlockQuantStorage({}, bytes={})\", self.format, self.data.len())\n    }\n}\n\nimpl TensorStorage for BlockQuantStorage {\n    fn byte_len(&self) -> usize {\n        self.data.len()\n    }\n\n    fn is_empty(&self) -> bool {\n        self.data.is_empty()\n    }\n\n    fn deep_clone(&self) -> Box<dyn TensorStorage> {\n        Box::new(self.clone())\n    }\n\n    fn as_plain(&self) -> Option<&PlainStorage> {\n        None\n    }\n\n    fn as_plain_mut(&mut self) -> Option<&mut PlainStorage> {\n        None\n    }\n\n    fn into_plain(self: Box<Self>) -> Option<PlainStorage> {\n        None\n    }\n\n    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {\n        state.write_u8(1);\n        self.format.dyn_hash(state);\n        state.write(self.data.as_bytes());\n    }\n\n    fn exotic_fact(&self, shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {\n        Ok(Some(Box::new(BlockQuantFact::new(dyn_clone::clone_box(&*self.format), shape.into()))))\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/block_quant/value.rs",
    "content": "use super::{BlockQuant, PackedBlockQuantFormat};\nuse tract_data::TVec;\nuse tract_data::internal::*;\n\n#[allow(clippy::derived_hash_with_manual_eq)]\n#[derive(Clone, Hash)]\npub struct BlockQuantFact {\n    pub format: Box<dyn BlockQuant>,\n    shape: TVec<usize>,\n}\nimpl BlockQuantFact {\n    pub fn new(format: Box<dyn BlockQuant>, shape: TVec<usize>) -> Self {\n        Self { format, shape }\n    }\n\n    /// Product of all leading dims except the last two (M, K).\n    /// For rank <= 2, returns 1.\n    pub fn num_groups(&self) -> usize {\n        if self.shape.len() <= 2 { 1 } else { self.shape[..self.shape.len() - 2].iter().product() }\n    }\n\n    /// Product of all dims except the last (K). This is the flat M\n    /// dimension (groups * m_per_group).\n    pub fn m(&self) -> usize {\n        self.shape[..self.shape.len() - 1].iter().product()\n    }\n\n    /// Last dimension.\n    pub fn k(&self) -> usize {\n        *self.shape.last().unwrap()\n    }\n\n    pub fn shape(&self) -> &[usize] {\n        &self.shape\n    }\n}\n\nimpl std::fmt::Debug for BlockQuantFact {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}({:?})\", self.format, self.shape)\n    }\n}\n\nimpl ExoticFact for BlockQuantFact {\n    fn buffer_sizes(&self) -> TVec<TDim> {\n        let total = self.m() * self.k() / self.format.block_len() * self.format.block_bytes();\n        tvec!(total.to_dim())\n    }\n}\n\nimpl PartialEq for BlockQuantFact {\n    fn eq(&self, other: &Self) -> bool {\n        *self.format == *other.format && self.shape == other.shape\n    }\n}\nimpl Eq for BlockQuantFact {}\n\n#[derive(Clone, Hash, PartialEq)]\npub struct PackedBlockQuantFact {\n    pub format: PackedBlockQuantFormat,\n    pub shape: TVec<usize>,\n}\nimpl Eq for PackedBlockQuantFact {}\n\nimpl std::fmt::Debug for PackedBlockQuantFact {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}({:?})\", self.format, self.shape)\n    }\n}\n\nimpl ExoticFact for PackedBlockQuantFact {\n    fn buffer_sizes(&self) -> TVec<TDim> {\n        tvec!(\n            (self.shape.iter().product::<usize>() / self.format.bq.block_len()\n                * self.format.bq.block_bytes())\n            .to_dim()\n        )\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/by_scalar.rs",
    "content": "use std::fmt::Debug;\nuse std::marker::PhantomData;\n\nuse crate::element_wise::{ElementWise, ElementWiseKer};\nuse crate::element_wise_helper::map_slice_with_alignment;\nuse crate::{LADatum, LinalgFn};\nuse tract_data::internal::*;\n\n/// Generic implementation struct that unify all by scalar kernels.\n/// A by scalar operation is an ElementWise operation with a scalar paramerer.\n#[derive(Debug, Clone, new)]\npub struct ByScalarImpl<K, T>\nwhere\n    T: LADatum,\n    K: ByScalarKer<T> + Clone,\n{\n    phantom: PhantomData<(K, T)>,\n}\n\nimpl<K, T> ElementWise<T, T> for ByScalarImpl<K, T>\nwhere\n    T: LADatum,\n    K: ByScalarKer<T> + Clone,\n{\n    fn name(&self) -> &'static str {\n        K::name()\n    }\n    fn run_with_params(&self, vec: &mut [T], params: T) -> TractResult<()> {\n        map_slice_with_alignment(vec, |data| K::run(data, params), K::nr(), K::alignment_bytes())\n    }\n}\n\npub trait ByScalarKer<T>: ElementWiseKer<T, T>\nwhere\n    T: LADatum,\n{\n    fn bin() -> Box<LinalgFn> {\n        Box::new(|a: &mut TensorView, b: &TensorView| {\n            let a_slice = a.as_slice_mut()?;\n            let b = b.as_slice()?[0];\n            (Self::ew()).run_with_params(a_slice, b)\n        })\n    }\n}\n\nmacro_rules! by_scalar_impl_wrap {\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $params: ty, $run: item) => {\n        paste! {\n            ew_impl_wrap!($ti, $func, $nr, $alignment_items, $ti, $run);\n\n            impl crate::frame::by_scalar::ByScalarKer<$ti> for $func {}\n        }\n    };\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod test {\n    use crate::LADatum;\n    use crate::frame::element_wise::ElementWiseKer;\n    use num_traits::{AsPrimitive, Float};\n    use proptest::test_runner::TestCaseResult;\n\n    #[macro_export]\n    macro_rules! by_scalar_frame_tests {\n        ($cond:expr, $t: ty, $ker:ty, $func:expr) => {\n            pastey::paste! {\n                proptest::proptest! {\n                    #[test]\n                    fn [<prop_ $ker:snake>](xs in proptest::collection::vec(-25f32..25.0, 0..100), scalar in -25f32..25f32) {\n                        if $cond {\n                            $crate::frame::by_scalar::test::test_by_scalar::<$ker, $t>(&*xs, scalar, $func).unwrap()\n                        }\n                    }\n                }\n            }\n        };\n    }\n\n    pub fn test_by_scalar<K: ElementWiseKer<T, T>, T: LADatum + Float>(\n        values: &[f32],\n        scalar: f32,\n        func: impl Fn(T, T) -> T,\n    ) -> TestCaseResult\n    where\n        f32: AsPrimitive<T>,\n    {\n        crate::setup_test_logger();\n        let values: Vec<T> = values.iter().copied().map(|x| x.as_()).collect();\n        crate::frame::element_wise::test::test_element_wise_params::<K, T, _, T>(\n            &values,\n            |a| (func)(a, scalar.as_()),\n            scalar.as_(),\n        )\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/element_wise.rs",
    "content": "use std::fmt::Debug;\nuse std::marker::PhantomData;\n\nuse tract_data::TractResult;\n\nuse crate::LADatum;\n\nuse super::element_wise_helper::map_slice_with_alignment;\n\nmacro_rules! ew_impl_wrap {\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $params: ty, $run: item) => {\n        paste! {\n            #[derive(Copy, Clone, Debug)]\n            #[allow(non_camel_case_types)]\n            pub struct $func;\n\n            impl crate::frame::element_wise::ElementWiseKer<$ti, $params> for $func {\n                #[inline(always)]\n                fn name() -> &'static str {\n                    stringify!($func)\n                }\n                #[inline(always)]\n                fn nr() -> usize {\n                    $nr\n                }\n                #[inline(always)]\n                fn alignment_items() -> usize {\n                    $alignment_items\n                }\n                $run\n            }\n        }\n    };\n}\n\nmacro_rules! ew_impl {\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr) => {\n        paste! {\n            mod [<sys_ $func>] {\n                #[allow(unused_imports)]\n                use tract_data::prelude::f16;\n                extern_kernel!(fn $func(ptr: *mut $ti, count: usize) -> ());\n            }\n            ew_impl_wrap!($ti, $func, $nr, $alignment_items, (),\n                #[inline(never)]\n                fn run(buf: &mut [$ti], _params: ()) {\n                    unsafe { [<sys_ $func>]::$func(buf.as_mut_ptr(), buf.len()) }\n                }\n            );\n        }\n    };\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $params: ty) => {\n        paste! {\n            mod [<sys_ $func>] {\n                #[allow(unused_imports)]\n                use tract_data::prelude::f16;\n                extern_kernel!(fn $func(ptr: *mut $ti, count: usize, params: $params) -> ());\n            }\n            ew_impl_wrap!($ti, $func, $nr, $alignment_items, $params,\n                #[inline(never)]\n                fn run(buf: &mut [$ti], params: $params) {\n                    unsafe { [<sys_ $func>]::$func(buf.as_mut_ptr(), buf.len(), params) }\n                }\n            );\n        }\n    };\n}\n\npub trait ElementWise<T, Params = ()>: Send + Sync + Debug + dyn_clone::DynClone\nwhere\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    T: Copy + Debug + PartialEq + Send + Sync,\n{\n    fn name(&self) -> &'static str;\n    fn run(&self, vec: &mut [T]) -> TractResult<()> {\n        self.run_with_params(vec, Params::default())\n    }\n    fn run_with_params(&self, vec: &mut [T], params: Params) -> TractResult<()>;\n}\n\ndyn_clone::clone_trait_object!(<T, Params> ElementWise<T, Params> where T: Copy, Params: Copy);\n\n#[derive(Debug, Clone, new)]\npub struct ElementWiseImpl<K, T, Params = ()>\nwhere\n    T: LADatum,\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    K: ElementWiseKer<T, Params> + Clone,\n{\n    phantom: PhantomData<(K, T, Params)>,\n}\n\nimpl<K, T, Params> ElementWise<T, Params> for ElementWiseImpl<K, T, Params>\nwhere\n    T: LADatum,\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    K: ElementWiseKer<T, Params> + Clone,\n{\n    fn name(&self) -> &'static str {\n        K::name()\n    }\n    fn run_with_params(&self, vec: &mut [T], params: Params) -> TractResult<()> {\n        map_slice_with_alignment(vec, |data| K::run(data, params), K::nr(), K::alignment_bytes())\n    }\n}\n\npub trait ElementWiseKer<T, Params = ()>:\n    Send + Sync + Debug + dyn_clone::DynClone + Clone + 'static\nwhere\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    T: LADatum,\n{\n    fn name() -> &'static str;\n    fn alignment_bytes() -> usize {\n        Self::alignment_items() * T::datum_type().size_of()\n    }\n    fn alignment_items() -> usize;\n    fn nr() -> usize;\n    fn run(vec: &mut [T], params: Params);\n    fn ew() -> Box<dyn ElementWise<T, Params>> {\n        Box::new(ElementWiseImpl::<Self, T, Params>::new())\n    }\n}\n\n#[cfg(test)]\npub mod test {\n    use crate::{LADatum, frame::element_wise::*};\n    use proptest::test_runner::{TestCaseError, TestCaseResult};\n    use tract_data::internal::*;\n\n    pub fn test_element_wise<K: ElementWiseKer<T, ()>, T: LADatum, F: Fn(T) -> T>(\n        values: &[T],\n        reference: F,\n    ) -> TestCaseResult {\n        test_element_wise_params::<K, T, F, ()>(values, reference, ())\n    }\n\n    pub fn test_element_wise_params<\n        K: ElementWiseKer<T, Params>,\n        T: LADatum,\n        F: Fn(T) -> T,\n        Params,\n    >(\n        values: &[T],\n        reference: F,\n        params: Params,\n    ) -> TestCaseResult\n    where\n        Params: Copy + Send + Sync + Debug + 'static + Default,\n    {\n        crate::setup_test_logger();\n        let op = ElementWiseImpl::<K, T, Params>::new();\n        let mut values = values.to_vec();\n        while values.len() < K::nr() {\n            values.push(T::zero());\n        }\n        let expected = values.iter().copied().map(reference).collect::<Vec<_>>();\n        let mut found = values;\n        op.run_with_params(&mut found, params).unwrap();\n        tensor1(&found)\n            .close_enough(&tensor1(&expected), true)\n            .map_err(|e| TestCaseError::fail(e.root_cause().to_string()))?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/element_wise_helper.rs",
    "content": "use crate::LADatum;\nuse std::alloc::*;\nuse tract_data::TractResult;\n\npub(crate) fn map_slice_with_alignment<T>(\n    vec: &mut [T],\n    f: impl Fn(&mut [T]),\n    nr: usize,\n    alignment_bytes: usize,\n) -> TractResult<()>\nwhere\n    T: LADatum,\n{\n    if vec.is_empty() {\n        return Ok(());\n    }\n    unsafe {\n        TMP.with(|buffer| {\n            let mut buffer = buffer.borrow_mut();\n            buffer.ensure(nr * T::datum_type().size_of(), alignment_bytes);\n            let tmp = std::slice::from_raw_parts_mut(buffer.buffer as *mut T, nr);\n            let mut compute_via_temp_buffer = |slice: &mut [T]| {\n                tmp[..slice.len()].copy_from_slice(slice);\n                f(tmp);\n                slice.copy_from_slice(&tmp[..slice.len()])\n            };\n            let prefix_len = vec.as_ptr().align_offset(alignment_bytes).min(vec.len());\n            if prefix_len > 0 {\n                compute_via_temp_buffer(&mut vec[..prefix_len]);\n            }\n            let aligned_len = (vec.len() - prefix_len) / nr * nr;\n            if aligned_len > 0 {\n                f(&mut vec[prefix_len..][..aligned_len]);\n            }\n            if prefix_len + aligned_len < vec.len() {\n                compute_via_temp_buffer(&mut vec[prefix_len + aligned_len..]);\n            }\n        })\n    }\n    Ok(())\n}\n\npub(crate) fn reduce_slice_with_alignment<T>(\n    vec: &[T],\n    f: impl Fn(&[T]) -> T,\n    nr: usize,\n    alignment_bytes: usize,\n    neutral: T,\n    reduce: impl Fn(T, T) -> T,\n) -> TractResult<T>\nwhere\n    T: LADatum,\n{\n    if vec.is_empty() {\n        return Ok(neutral);\n    }\n    let mut red = neutral;\n    unsafe {\n        TMP.with(|buffer| {\n            let mut buffer = buffer.borrow_mut();\n            buffer.ensure(nr * T::datum_type().size_of(), alignment_bytes);\n            let tmp = std::slice::from_raw_parts_mut(buffer.buffer as *mut T, nr);\n            let mut compute_via_temp_buffer = |slice: &[T], red: &mut T| {\n                tmp[..slice.len()].copy_from_slice(slice);\n                tmp[slice.len()..].fill(neutral);\n                *red = reduce(*red, f(tmp));\n            };\n            let prefix_len = vec.as_ptr().align_offset(alignment_bytes).min(vec.len());\n            if prefix_len > 0 {\n                compute_via_temp_buffer(&vec[..prefix_len], &mut red);\n            }\n            let aligned_len = (vec.len() - prefix_len) / nr * nr;\n            if aligned_len > 0 {\n                let t = f(&vec[prefix_len..][..aligned_len]);\n                red = reduce(red, t);\n            }\n            if prefix_len + aligned_len < vec.len() {\n                compute_via_temp_buffer(&vec[prefix_len + aligned_len..], &mut red);\n            }\n        })\n    }\n    Ok(red)\n}\n\npub(crate) fn map_reduce_slice_with_alignment<T>(\n    vec: &mut [T],\n    f: impl Fn(&mut [T]) -> T,\n    nr: usize,\n    alignment_bytes: usize,\n    map_neutral: T,\n    neutral: T,\n    reduce: impl Fn(T, T) -> T,\n) -> TractResult<T>\nwhere\n    T: LADatum,\n{\n    if vec.is_empty() {\n        return Ok(neutral);\n    }\n    let mut red = neutral;\n    unsafe {\n        TMP.with(|buffer| {\n            let mut buffer = buffer.borrow_mut();\n            buffer.ensure(nr * T::datum_type().size_of(), alignment_bytes);\n            let tmp = std::slice::from_raw_parts_mut(buffer.buffer as *mut T, nr);\n            let mut compute_via_temp_buffer = |slice: &mut [T], red: &mut T| {\n                tmp[..slice.len()].copy_from_slice(slice);\n                tmp[slice.len()..].fill(map_neutral);\n                *red = reduce(*red, f(tmp));\n                slice.copy_from_slice(&tmp[..slice.len()]);\n            };\n            let prefix_len = vec.as_ptr().align_offset(alignment_bytes).min(vec.len());\n            if prefix_len > 0 {\n                compute_via_temp_buffer(&mut vec[..prefix_len], &mut red);\n            }\n            let aligned_len = (vec.len() - prefix_len) / nr * nr;\n            if aligned_len > 0 {\n                let t = f(&mut vec[prefix_len..][..aligned_len]);\n                red = reduce(red, t);\n            }\n            if prefix_len + aligned_len < vec.len() {\n                compute_via_temp_buffer(&mut vec[prefix_len + aligned_len..], &mut red);\n            }\n        })\n    }\n    Ok(red)\n}\n\nstd::thread_local! {\n    static TMP: std::cell::RefCell<TempBuffer> = std::cell::RefCell::new(TempBuffer::default());\n}\n\npub struct TempBuffer {\n    pub layout: Layout,\n    pub buffer: *mut u8,\n}\n\nimpl Default for TempBuffer {\n    fn default() -> Self {\n        TempBuffer { layout: Layout::new::<()>(), buffer: std::ptr::null_mut() }\n    }\n}\n\nimpl TempBuffer {\n    pub fn ensure(&mut self, size: usize, alignment: usize) {\n        unsafe {\n            if size > self.layout.size() || alignment > self.layout.align() {\n                let size = size.max(self.layout.size());\n                let alignment = alignment.max(self.layout.align());\n                if !self.buffer.is_null() {\n                    std::alloc::dealloc(self.buffer, self.layout);\n                }\n                self.layout = Layout::from_size_align_unchecked(size, alignment);\n                self.buffer = std::alloc::alloc(self.layout);\n                assert!(!self.buffer.is_null());\n            }\n        }\n    }\n}\n\nimpl Drop for TempBuffer {\n    fn drop(&mut self) {\n        unsafe {\n            if !self.buffer.is_null() {\n                std::alloc::dealloc(self.buffer, self.layout);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/leaky_relu.rs",
    "content": "#[allow(unused_macros)]\nmacro_rules! leaky_relu_impl {\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $cond: expr) => {\n        ew_impl!($ti, $func, $nr, $alignment_items, $ti);\n        #[cfg(test)]\n        paste! {\n            mod [<test_ $func>] {\n                use super::*;\n                leaky_relu_frame_tests!($cond, $ti, $func);\n            }\n        }\n    };\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod test {\n    use crate::{LADatum, frame::element_wise::*};\n    use num_traits::{AsPrimitive, Float};\n    use proptest::test_runner::TestCaseResult;\n\n    #[macro_export]\n    macro_rules! leaky_relu_frame_tests {\n        ($cond:expr, $t: ty, $ker:ty) => {\n            proptest::proptest! {\n                #[test]\n                fn prop(xs in proptest::collection::vec(-25f32..25.0, 0..100), alpha in 0f32..1f32) {\n                    if $cond {\n                        $crate::frame::leaky_relu::test::test_leaky_relu::<$ker, $t>(&*xs, alpha).unwrap()\n                    }\n                }\n            }\n            #[test]\n            fn trivial() {\n                if $cond {\n                    $crate::frame::leaky_relu::test::test_leaky_relu::<$ker, $t>(&[-10f32], 0.0496).unwrap();\n                }\n            }\n        };\n    }\n\n    pub fn test_leaky_relu<K: ElementWiseKer<T, T>, T: LADatum + Float>(\n        values: &[f32],\n        alpha: f32,\n    ) -> TestCaseResult\n    where\n        f32: AsPrimitive<T>,\n    {\n        let data = tract_data::prelude::tensor1(values);\n        let data = data.cast_to::<T>().unwrap();\n        let data = data.try_as_plain().unwrap().as_slice::<T>().unwrap();\n        let alpha: T = tract_data::prelude::tensor0(alpha).cast_to_scalar::<T>().unwrap();\n        crate::frame::element_wise::test::test_element_wise_params::<K, T, _, T>(\n            data,\n            |x: T| {\n                if x > T::zero() { x } else { alpha * x }\n            },\n            alpha,\n        )\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/lut.rs",
    "content": "use std::fmt;\nuse std::hash::Hash;\nuse std::marker::PhantomData;\nuse tract_data::internal::*;\n\npub trait Lut: fmt::Debug + dyn_clone::DynClone + Send + Sync {\n    fn table(&self) -> &[u8];\n    fn run(&self, buf: &mut [u8]);\n}\n\ndyn_clone::clone_trait_object!(Lut);\n\nimpl PartialEq for dyn Lut {\n    fn eq(&self, other: &Self) -> bool {\n        self.table() == other.table()\n    }\n}\nimpl Eq for dyn Lut {}\n\n#[derive(Debug, Clone, Hash)]\npub struct LutImpl<K: LutKer> {\n    table: Tensor,\n    _boo: PhantomData<K>,\n}\n\nimpl<K: LutKer> LutImpl<K> {\n    pub fn new(table: &[u8]) -> LutImpl<K> {\n        unsafe {\n            LutImpl {\n                table: Tensor::from_raw_aligned::<u8>(\n                    &[table.len()],\n                    table,\n                    K::table_alignment_bytes(),\n                )\n                .unwrap(),\n                _boo: PhantomData,\n            }\n        }\n    }\n}\n\nimpl<K: LutKer> Lut for LutImpl<K> {\n    fn table(&self) -> &[u8] {\n        self.table.try_as_plain().unwrap().as_slice().unwrap()\n    }\n\n    fn run(&self, buf: &mut [u8]) {\n        unsafe {\n            let table: *const u8 = self.table.as_ptr_unchecked();\n            let align = K::input_alignment_bytes();\n            let aligned_start = (buf.as_ptr() as usize).next_multiple_of(align);\n            let prefix = (aligned_start - buf.as_ptr() as usize).min(buf.len());\n            for i in 0..(prefix as isize) {\n                let ptr = buf.as_mut_ptr().offset(i);\n                *ptr = *table.offset(*ptr as isize);\n            }\n            let remaining = buf.len() - prefix;\n            if remaining == 0 {\n                return;\n            }\n            let n = K::n();\n            let aligned_len = remaining / n * n;\n            if aligned_len > 0 {\n                K::run(buf.as_mut_ptr().add(prefix), aligned_len, table);\n            }\n            let remaining = buf.len() - aligned_len - prefix;\n            for i in 0..remaining {\n                let ptr = buf.as_mut_ptr().add(i + prefix + aligned_len);\n                *ptr = *table.offset(*ptr as isize);\n            }\n        }\n    }\n}\n\npub trait LutKer: Clone + fmt::Debug + Send + Sync + Hash {\n    fn name() -> &'static str;\n    fn n() -> usize;\n    fn input_alignment_bytes() -> usize;\n    fn table_alignment_bytes() -> usize;\n    unsafe fn run(buf: *mut u8, len: usize, table: *const u8);\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod test {\n    use super::*;\n    use proptest::prelude::*;\n\n    #[derive(Debug)]\n    pub struct LutProblem {\n        pub table: Vec<u8>,\n        pub data: Vec<u8>,\n    }\n\n    impl Arbitrary for LutProblem {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_p: ()) -> Self::Strategy {\n            proptest::collection::vec(any::<u8>(), 1..256)\n                .prop_flat_map(|table| {\n                    let data = proptest::collection::vec(0..table.len() as u8, 0..100);\n                    (Just(table), data)\n                })\n                .prop_map(|(table, data)| LutProblem { table, data })\n                .boxed()\n        }\n    }\n\n    impl LutProblem {\n        pub fn reference(&self) -> Vec<u8> {\n            self.data.iter().map(|x| self.table[*x as usize]).collect()\n        }\n\n        pub fn test<K: LutKer>(&self) -> Vec<u8> {\n            let lut = LutImpl::<K>::new(&self.table);\n            let mut data = self.data.clone();\n            lut.run(&mut data);\n            data\n        }\n    }\n\n    #[macro_export]\n    macro_rules! lut_frame_tests {\n        ($cond:expr, $ker:ty) => {\n            mod lut {\n                use proptest::prelude::*;\n                #[allow(unused_imports)]\n                use $crate::frame::lut::test::*;\n\n                proptest::proptest! {\n                    #[test]\n                    fn lut_prop(pb in any::<LutProblem>()) {\n                        if $cond {\n                            prop_assert_eq!(pb.test::<$ker>(), pb.reference())\n                        }\n                    }\n                }\n\n                #[test]\n                fn test_empty() {\n                    let pb = LutProblem { table: vec![0], data: vec![] };\n                    assert_eq!(pb.test::<$ker>(), pb.reference())\n                }\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/cost_model.rs",
    "content": "use tract_data::internal::*;\nuse tract_data::itertools::{Itertools, izip};\n\nuse super::MatMatMul;\n\nfn order_f<F: tract_num_traits::Float>(&a: &F, &b: &F) -> std::cmp::Ordering {\n    if a < b { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater }\n}\n\n#[derive(Debug)]\npub struct CostModel<'a> {\n    pub big_product_mkn_threshold: f32,\n    pub big_product_kernel_choice: &'a str,\n    pub kernels: &'a [&'a str],\n    pub mrs: &'a [u32],\n    pub nrs: &'a [u32],\n    pub feat_norm_mean: &'a [f32],\n    pub feat_norm_stddev: &'a [f32],\n    pub w1: &'a [f32],\n    pub b1: &'a [f32],\n    pub w2: &'a [f32],\n    pub b2: &'a [f32],\n}\n\nimpl CostModel<'_> {\n    pub fn features(&self, m: usize, k: usize, n: usize) -> Vec<f32> {\n        let mut feat = vec![\n            (m as f32).ln(),\n            (k as f32).ln(),\n            (n as f32).ln(),\n            (n as f32 * m as f32 * k as f32).ln(),\n        ];\n        for &mr in self.mrs {\n            let mr = mr as usize;\n            feat.push((m % mr) as f32);\n            feat.push((m % mr != 0) as usize as f32);\n        }\n        for &nr in self.nrs {\n            let nr = nr as usize;\n            feat.push((n % nr) as f32);\n            feat.push((n % nr != 0) as usize as f32);\n        }\n        feat\n    }\n\n    fn normalize(&self, feat: &mut [f32]) {\n        izip!(feat, self.feat_norm_mean, self.feat_norm_stddev)\n            .for_each(|(x, m, s)| *x = (*x - m) / s)\n    }\n\n    fn dnn(x: &[f32], w: &[f32], b: &[f32]) -> Vec<f32> {\n        let x = tract_ndarray::Array1::from_vec(x.to_vec());\n        let w = tract_ndarray::Array2::from_shape_vec([b.len(), x.len()], w.to_vec()).unwrap();\n        let b = tract_ndarray::Array1::from_vec(b.to_vec());\n        (w.dot(&x) + b).to_vec()\n    }\n\n    pub fn predict(&self, m: usize, k: usize, n: usize) -> &str {\n        let mut x = self.features(m, k, n);\n        self.normalize(&mut x);\n        let mut hidden = Self::dnn(&x, self.w1, self.b1);\n        (crate::generic().tanh_f32)().run(&mut hidden).unwrap();\n        let output = Self::dnn(&hidden, self.w2, self.b2);\n        let ix = output.iter().copied().position_max_by(order_f).unwrap();\n        self.kernels[ix]\n    }\n\n    pub fn pick(\n        &self,\n        impls: &[Box<dyn MatMatMul>],\n        m: Option<usize>,\n        k: Option<usize>,\n        n: Option<usize>,\n    ) -> Box<dyn MatMatMul> {\n        if let (Some(m), Some(k), Some(n)) = (m, k, n) {\n            let choice = self.predict(m, k, n);\n            impls.iter().find(|k| k.name() == choice).unwrap().clone()\n        } else {\n            impls.iter().find(|k| k.name() == self.big_product_kernel_choice).unwrap().clone()\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/fuse.rs",
    "content": "use std::fmt::Debug;\nuse std::ops::Deref;\n\nuse crate::BinOp;\nuse crate::pack::PackedFormat;\n\nuse super::{MMMInputValue, OutputStore, OutputStoreKer};\nuse tract_data::internal::*;\n\n#[repr(usize)]\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]\npub enum RoundingPolicy {\n    Native,\n    Zero,\n    Away,\n    MinusInf,\n    PlusInf,\n    Even,\n    Odd,\n}\n\n#[derive(Clone, Debug)]\npub enum AsInputValue<'t> {\n    Owned(Box<dyn MMMInputValue>),\n    Borrowed(&'t dyn MMMInputValue),\n}\n\nimpl Deref for AsInputValue<'_> {\n    type Target = dyn MMMInputValue;\n    fn deref(&self) -> &Self::Target {\n        match self {\n            AsInputValue::Owned(b) => &**b,\n            AsInputValue::Borrowed(r) => *r,\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub enum FusedSpec<'t> {\n    BinScalar(&'t Tensor, BinOp),\n    BinPerRow(TensorView<'t>, BinOp),\n    BinPerCol(TensorView<'t>, BinOp),\n    AddRowColProducts(&'t Tensor, &'t Tensor),\n    AddUnicast(OutputStore),\n    LeakyRelu(&'t Tensor),\n    QScale(isize, RoundingPolicy, i32),\n    RoundingShiftRight(usize, RoundingPolicy),\n    ShiftLeft(usize),\n    Store(OutputStore),\n    AddMatMul { a: AsInputValue<'t>, b: AsInputValue<'t>, packing: usize },\n}\n\nimpl FusedSpec<'_> {\n    pub fn prefer_col_outer(&self) -> Option<bool> {\n        if let FusedSpec::AddMatMul { a, b, .. } = self {\n            let a_is_eager = a.format().is::<PackedFormat>();\n            let b_is_eager = b.format().is::<PackedFormat>();\n            if a_is_eager == b_is_eager { None } else { Some(a_is_eager) }\n        } else {\n            None\n        }\n    }\n}\n\n// Careful here, the jump_to comments are used by the build script.\n#[repr(C, usize)]\n#[derive(PartialEq, Eq, Copy, Clone, Debug)]\n#[rustfmt::skip]\npub enum FusedKerSpec<TI: Copy> {\n    Done,                                       // jump_to:done\n    Clear,                                      // jump_to:clear\n                                                //\n    LoadTile(*const TI, *const TI),             // jump_to:load_tile\n\n    ScalarMin(TI),                              // jump_to:scalar_min\n    ScalarMax(TI),                              // jump_to:scalar_max\n    ScalarAdd(TI),                              // jump_to:scalar_add\n    ScalarMul(TI),                              // jump_to:scalar_mul\n    ScalarSub(TI),                              // jump_to:scalar_sub\n    ScalarSubF(TI),                             // jump_to:scalar_sub_flipped\n\n    LeakyRelu(TI),                              // jump_to:leaky_relu\n\n    PerRowMin(*const TI),                       // jump_to:per_row_min\n    PerRowMax(*const TI),                       // jump_to:per_row_max\n    PerRowAdd(*const TI),                       // jump_to:per_row_add\n    PerRowMul(*const TI),                       // jump_to:per_row_mul\n    PerRowSub(*const TI),                       // jump_to:per_row_sub\n    PerRowSubF(*const TI),                      // jump_to:per_row_sub_flipped\n\n    PerColMin(*const TI),                       // jump_to:per_col_min\n    PerColMax(*const TI),                       // jump_to:per_col_max\n    PerColAdd(*const TI),                       // jump_to:per_col_add\n    PerColMul(*const TI),                       // jump_to:per_col_mul\n    PerColSub(*const TI),                       // jump_to:per_col_sub\n    PerColSubF(*const TI),                      // jump_to:per_col_sub_flipped\n\n    QScale(isize, RoundingPolicy, i32),         // jump_to:q_scale\n    RoundingShiftRight(usize, RoundingPolicy),  // jump_to:q_shr\n    ShiftLeft(usize),                           // jump_to:q_shl\n    AddUnicast(OutputStoreKer),                 // jump_to:add_unicast\n    AddRowColProducts(*const TI, *const TI),    // jump_to:add_row_col_products\n    Store(OutputStoreKer),                      // jump_to:store\n\n    // jump_to:add_mat_mul\n    AddMatMul { k: usize, pa: *const u8, pb: *const u8, packing: usize },\n}\n\nunsafe impl<TI: Copy> Send for FusedKerSpec<TI> {}\nunsafe impl<TI: Copy> Sync for FusedKerSpec<TI> {}\n\n#[cfg(test)]\n#[test]\nfn check_non_linear_enum_size() {\n    assert_eq!(std::mem::size_of::<RoundingPolicy>(), std::mem::size_of::<usize>());\n    assert_eq!(\n        std::mem::size_of::<FusedKerSpec<f32>>(),\n        std::mem::size_of::<usize>() + std::mem::size_of::<OutputStoreKer>()\n    );\n    assert_eq!(std::mem::size_of::<FusedKerSpec<f32>>(), 5 * std::mem::size_of::<usize>());\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/input_store.rs",
    "content": "use downcast_rs::{Downcast, impl_downcast};\nuse dyn_clone::DynClone;\nuse dyn_eq::DynEq;\nuse dyn_hash::DynHash;\nuse std::alloc::Layout;\nuse std::fmt::{Debug, Display};\nuse std::hash::Hash;\nuse std::sync::Arc;\nuse tract_data::internal::*;\n\nuse crate::WeightType;\n\npub trait MMMInputFormat:\n    Downcast + Debug + DynHash + dyn_eq::DynEq + DynClone + Send + Sync + Display\n{\n    fn prepare_tensor(&self, t: &Tensor, k_axis: usize, mn_axis: usize) -> TractResult<Tensor>;\n    fn prepare_one(\n        &self,\n        t: &Tensor,\n        k_axis: usize,\n        mn_axis: usize,\n    ) -> TractResult<Box<dyn MMMInputValue>>;\n    fn precursor(&self) -> WeightType;\n    fn r(&self) -> usize;\n    fn k_alignment(&self) -> usize;\n    fn merge_with<'o, 'a: 'o, 'b: 'o>(\n        &'a self,\n        other: &'b dyn MMMInputFormat,\n    ) -> Option<&'o dyn MMMInputFormat> {\n        if self.dyn_eq(other) { Some(other) } else { None }\n    }\n    fn mem_size(&self, k: TDim, mn: TDim) -> TDim;\n    fn extract_at_mn_f16(\n        &self,\n        data: &EagerPackedInput,\n        mn: usize,\n        slice: &mut [f16],\n    ) -> TractResult<()>;\n    fn extract_at_mn_f32(\n        &self,\n        data: &EagerPackedInput,\n        mn: usize,\n        slice: &mut [f32],\n    ) -> TractResult<()>;\n}\n\ndyn_clone::clone_trait_object!(MMMInputFormat);\nimpl_downcast!(MMMInputFormat);\ndyn_hash::hash_trait_object!(MMMInputFormat);\ndyn_eq::eq_trait_object!(MMMInputFormat);\n\npub trait MMMInputValue:\n    DynClone + Debug + DynHash + dyn_eq::DynEq + Send + Sync + Display + Downcast\n{\n    fn format(&self) -> &dyn MMMInputFormat;\n    fn scratch_panel_buffer_layout(&self) -> Option<Layout>;\n    fn panel_bytes(&self, i: usize, buffer: Option<*mut u8>) -> TractResult<*const u8>;\n    fn panels_count(&self) -> usize {\n        self.mn().divceil(self.format().r())\n    }\n    fn mn(&self) -> usize;\n    fn k(&self) -> usize;\n    fn exotic_fact(&self) -> &dyn ExoticFact;\n\n    fn extract_at_mn_f16(&self, mn: usize, slice: &mut [f16]) -> TractResult<()>;\n    fn extract_at_mn_f32(&self, mn: usize, slice: &mut [f32]) -> TractResult<()>;\n}\ndyn_clone::clone_trait_object!(MMMInputValue);\nimpl_downcast!(MMMInputValue);\ndyn_hash::hash_trait_object!(MMMInputValue);\ndyn_eq::eq_trait_object!(MMMInputValue);\n\n#[allow(clippy::derived_hash_with_manual_eq)]\n#[derive(Clone, Hash, Debug)]\npub struct PackedExoticFact {\n    pub format: Box<dyn MMMInputFormat>,\n    pub mn: TDim,\n    pub k: usize,\n}\n\nimpl Display for PackedExoticFact {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Eager {} tensor (mn={} k={})\", self.format, self.mn, self.k)\n    }\n}\n\nimpl ExoticFact for PackedExoticFact {\n    fn buffer_sizes(&self) -> TVec<TDim> {\n        tvec!(self.format.mem_size(self.k.to_dim(), self.mn.clone()))\n    }\n}\n\nimpl PartialEq for PackedExoticFact {\n    fn eq(&self, other: &Self) -> bool {\n        self.format == other.format && self.mn == other.mn && self.k == other.k\n    }\n}\nimpl Eq for PackedExoticFact {}\n\n#[derive(Clone, Hash, PartialEq, Eq)]\npub struct EagerPackedInput {\n    pub fact: PackedExoticFact,\n    pub packed: Arc<Blob>,\n    pub panel_bytes: usize,\n    pub mn: usize,\n}\n\nimpl MMMInputValue for EagerPackedInput {\n    fn scratch_panel_buffer_layout(&self) -> Option<Layout> {\n        None\n    }\n    fn panel_bytes(&self, i: usize, _buffer: Option<*mut u8>) -> TractResult<*const u8> {\n        unsafe { Ok(self.packed.as_ptr().add(i * self.panel_bytes)) }\n    }\n    fn k(&self) -> usize {\n        self.fact.k\n    }\n    fn mn(&self) -> usize {\n        self.mn\n    }\n    fn format(&self) -> &dyn MMMInputFormat {\n        &*self.fact.format\n    }\n    fn exotic_fact(&self) -> &dyn ExoticFact {\n        &self.fact\n    }\n    fn extract_at_mn_f16(&self, mn: usize, slice: &mut [f16]) -> TractResult<()> {\n        ensure!(slice.len() == self.k());\n        ensure!(mn < self.mn());\n        self.fact.format.extract_at_mn_f16(self, mn, slice)\n    }\n    fn extract_at_mn_f32(&self, mn: usize, slice: &mut [f32]) -> TractResult<()> {\n        ensure!(slice.len() == self.k());\n        ensure!(mn < self.mn());\n        self.fact.format.extract_at_mn_f32(self, mn, slice)\n    }\n}\n\nimpl Display for EagerPackedInput {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        (&self.fact as &dyn Display).fmt(f)\n    }\n}\n\nimpl Debug for EagerPackedInput {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        <Self as Display>::fmt(self, f)\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/kernel.rs",
    "content": "use crate::frame::pack::PackedFormat;\n\nuse super::*;\nuse std::borrow::Cow;\nuse std::fmt::Debug;\n\nuse crate::LADatum;\n\npub trait MatMatMulKer: Clone + Debug + Send + Sync + 'static {\n    type Acc: LADatum;\n    fn name(&self) -> &str;\n    fn kernel(&self, op: &[FusedKerSpec<Self::Acc>]) -> isize;\n    fn mr(&self) -> usize;\n    fn nr(&self) -> usize;\n\n    fn quality(&self) -> ImplementationQuality;\n    fn dynamic_boost(&self) -> isize;\n\n    #[allow(clippy::type_complexity)]\n    fn packings(&self) -> &[(Box<dyn MMMInputFormat>, Box<dyn MMMInputFormat>)];\n    fn stores(&self) -> Cow<'_, [DatumType]>;\n\n    #[allow(unused_variables)]\n    fn can_fuse(&self, spec: &FusedSpec) -> bool {\n        true\n    }\n\n    #[allow(unused_variables)]\n    fn is_supported_here(&self) -> bool {\n        true\n    }\n}\n\ntype Kernel<Acc> = unsafe fn(&[FusedKerSpec<Acc>]) -> isize;\n\n#[derive(Clone)]\npub struct DynKernel<const MR: usize, const NR: usize, Acc: LADatum> {\n    pub name: String,\n    pub kernel: Kernel<Acc>,\n    pub quality: ImplementationQuality,\n    pub packings: Vec<(Box<dyn MMMInputFormat>, Box<dyn MMMInputFormat>)>,\n    pub stores: Vec<DatumType>,\n    pub supported_predicate: fn() -> bool,\n    pub boost: fn() -> isize,\n    pub can_fuse: fn(&FusedSpec) -> bool,\n}\n\nimpl<const MR: usize, const NR: usize, Acc: LADatum> DynKernel<MR, NR, Acc> {\n    pub fn new(\n        name: &str,\n        kernel: Kernel<Acc>,\n        packing_a: PackedFormat,\n        packing_b: PackedFormat,\n        quality: ImplementationQuality,\n    ) -> Self {\n        let kernel = DynKernel {\n            name: name.to_string(),\n            kernel,\n            quality,\n            packings: vec![],\n            stores: vec![Acc::datum_type()],\n            supported_predicate: || true,\n            boost: || 0,\n            can_fuse: |_| true,\n        };\n        kernel.with_packing(packing_a, packing_b)\n    }\n\n    pub fn with_platform_condition(mut self, f: fn() -> bool) -> Self {\n        self.supported_predicate = f;\n        self\n    }\n\n    pub fn with_boost(mut self, f: fn() -> isize) -> Self {\n        self.boost = f;\n        self\n    }\n\n    pub fn with_packing(mut self, a: impl MMMInputFormat, b: impl MMMInputFormat) -> Self {\n        self.packings.push((Box::new(a), Box::new(b)));\n        self\n    }\n\n    pub fn with_packing_a(self, a: impl MMMInputFormat) -> Self {\n        let b = self.regular_pack_b();\n        self.with_packing(a, b)\n    }\n\n    pub fn regular_pack_a(&self) -> PackedFormat {\n        *self.packings[0].0.clone().downcast::<PackedFormat>().unwrap()\n    }\n\n    pub fn regular_pack_b(&self) -> PackedFormat {\n        *self.packings[0].1.clone().downcast::<PackedFormat>().unwrap()\n    }\n\n    pub fn with_can_fuse(self, can_fuse: fn(&FusedSpec) -> bool) -> Self {\n        Self { can_fuse, ..self }\n    }\n\n    pub fn with_store<D: LADatum>(mut self) -> Self {\n        self.stores.push(D::datum_type());\n        self\n    }\n\n    pub fn mmm(&self) -> Box<dyn MatMatMul> {\n        Box::new(self.clone())\n    }\n}\n\nimpl<const MR: usize, const NR: usize, Acc: LADatum> Debug for DynKernel<MR, NR, Acc> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.name)\n    }\n}\n\nimpl<const MR: usize, const NR: usize, Acc: LADatum> MatMatMulKer for DynKernel<MR, NR, Acc> {\n    type Acc = Acc;\n    fn name(&self) -> &str {\n        &self.name\n    }\n\n    fn mr(&self) -> usize {\n        MR\n    }\n\n    fn nr(&self) -> usize {\n        NR\n    }\n\n    fn quality(&self) -> ImplementationQuality {\n        self.quality\n    }\n\n    fn is_supported_here(&self) -> bool {\n        (self.supported_predicate)()\n    }\n\n    fn can_fuse(&self, spec: &FusedSpec) -> bool {\n        (self.can_fuse)(spec)\n    }\n\n    fn kernel(&self, op: &[FusedKerSpec<Self::Acc>]) -> isize {\n        unsafe { (self.kernel)(op) }\n    }\n\n    #[allow(clippy::type_complexity)]\n    fn packings(&self) -> &[(Box<dyn MMMInputFormat>, Box<dyn MMMInputFormat>)] {\n        &self.packings\n    }\n\n    fn stores(&self) -> Cow<'_, [DatumType]> {\n        Cow::Borrowed(&self.stores)\n    }\n\n    fn dynamic_boost(&self) -> isize {\n        (self.boost)()\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/macros.rs",
    "content": "macro_rules! MMMExternKernel {\n    (\n            $func:ident<$ti:ident>($mr: expr, $nr: expr)\n            $(@($align_a:expr, $align_b:expr))?\n            $(where($where:expr))?\n            $(can_fuse($can_fuse:expr))?\n            $(packing[$pnum:literal] = $pid:ident => $packing:expr;)*\n            $(quality($quality:expr))?\n            $(boost($boost:expr))?\n            $(store($($store:ty),*))?\n     ) => {\n        paste! {\n            mod [<sys_ $func>] {\n                #[allow(unused_imports)]\n                use super::*;\n                #[allow(unused_imports)]\n                use crate::frame::mmm::*;\n                extern_kernel!(fn $func(op: *const FusedKerSpec<$ti>) -> isize);\n\n                #[inline]\n                pub unsafe fn rusty(op: &[FusedKerSpec<$ti>]) -> isize {\n                    unsafe { $func(op.as_ptr()) }\n                }\n            }\n\n            MMMKernel!([<sys_$func>]::rusty as $func<$ti>($mr, $nr)\n                $(@($align_a, $align_b))?\n                $(where($where))?\n                $(can_fuse($can_fuse))?\n                $(packing[$pnum] = $pid => $packing;)*\n                $(quality($quality))?\n                $(boost($boost))?\n                $(store($($store),*))?\n            );\n        }\n    };\n}\nmacro_rules! MMMRustKernel {\n    (       $func: path =>\n            $id:ident<$ti:ident>($mr: expr, $nr: expr)\n            $(@($align_a:expr, $align_b:expr))?\n            $(where($where:expr))?\n            $(can_fuse($can_fuse:expr))?\n            $(packing[$pnum:literal] = $pid:ident => $packing:expr;)*\n            $(quality($quality:expr))?\n            $(store($($store:ty),*))?\n     ) => {\n        paste! {\n            mod [<sys_ $id>] {\n                #[allow(unused_imports)]\n                use crate::frame::mmm::*;\n                use super::*;\n                #[inline]\n                pub unsafe fn rusty(op: &[FusedKerSpec<$ti>]) -> isize {\n                    unsafe { $func(op.as_ptr()) }\n                }\n            }\n            MMMKernel!([<sys_$id>]::rusty as $id<$ti>($mr, $nr)\n                $(@($align_a, $align_b))?\n                generic(true)\n                $(where($where))?\n                $(can_fuse($can_fuse))?\n                $(packing[$pnum] = $pid => $packing;)*\n                $(quality($quality))?\n                $(store($($store),*))?\n            );\n        }\n    }\n}\n\nmacro_rules! MMMKernel {\n    (\n            $func: path as\n            $id:ident<$ti:ident>($mr: expr, $nr: expr)\n            $(@($align_a:expr, $align_b:expr))?\n            $(generic($generic:expr))?\n            $(where($where:expr))?\n            $(can_fuse($can_fuse:expr))?\n            $(packing[$pnum:literal] = $pid:ident => $packing:expr;)*\n            $(quality($quality:expr))?\n            $(boost($boost:expr))?\n            $(store($($store:ty),*))?\n     ) => {\n        paste! {\n            lazy_static::lazy_static! {\n                pub static ref $id: $crate::mmm::DynKernel<$mr, $nr, $ti> = {\n                    use $crate::mmm::DynKernel;\n                    #[allow(unused_imports)]\n                    use tract_data::prelude::*;\n                    use $crate::pack::Packing;\n                    #[allow(unused_mut)]\n                    let (mut packing_a, mut packing_b) = ($ti::packing($mr), $ti::packing($nr));\n                    $(\n                        packing_a = packing_a.align($align_a);\n                        packing_b = packing_b.align($align_b);\n                    )?\n                    #[allow(unused_mut)]\n                    let mut k = DynKernel::<$mr, $nr, $ti>::new(stringify!($id), $func, packing_a, packing_b, $crate::frame::mmm::ImplementationQuality::Dreadful);\n                    $(k = k.with_platform_condition($where);)?\n                    $(\n                        assert!(k.packings.len() == $pnum);\n                        let f: fn(DynKernel<$mr, $nr, $ti>) -> DynKernel<$mr, $nr, $ti> = $packing;\n                        k = f(k);\n                    )*\n                    $($(\n                        k.stores.push(<$store>::datum_type());\n                    )*)?\n                    $(k.can_fuse = $can_fuse;)?\n                    $(k.quality = $quality;)?\n                    $(k = k.with_boost($boost);)?\n                    k\n                };\n            }\n\n            #[cfg(test)]\n            mod [<test_$id>] {\n                use super::$id;\n                test_mmm_kernel!($ti, &*super::$id);\n                $(mmm_packed_packed_tests!(&*super::$id, $pid : $pnum);)*\n                $($(mmm_store_test!(&*super::$id, $store);)*)?\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/mod.rs",
    "content": "#[macro_use]\nmod macros;\n\npub mod cost_model;\n#[macro_use]\npub(crate) mod fuse;\npub(crate) mod input_store;\npub(crate) mod kernel;\n#[macro_use]\npub(crate) mod panel_extract;\nmod scratch;\nmod storage;\n\n#[cfg(test)]\n#[macro_use]\npub mod tests;\n\nuse crate::multithread::Executor;\n#[cfg(feature = \"multithread-mm\")]\nuse rayon::prelude::*;\nuse std::borrow::Cow;\nuse std::cmp::Ordering;\nuse std::fmt::Debug;\nuse tract_data::internal::*;\n\npub use cost_model::*;\npub use fuse::*;\npub use input_store::*;\npub use kernel::*;\npub use panel_extract::*;\npub use scratch::*;\npub use storage::*;\n\npub fn no_prefetch(_ptr: *const u8, _len: usize) {}\n\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]\npub enum ImplementationQuality {\n    /// Individual operations are emulated by individual conversion (f16->f32->f16)\n    Dreadful,\n    /// Rust scalar operation (with whatever optimisation the compiler manages)\n    Generic,\n    /// Implicit vectorization (e.g. Rust code, some unrolled loops, explicit template instantiations for small constant)\n    RustOptimized,\n    /// Explicit vectorization (e.g. intrinsics vector code)\n    TargetOptimized,\n    /// Hand optimized (assembly)\n    ManuallyOptimized,\n}\n\nimpl ImplementationQuality {\n    pub fn best_to_worst() -> &'static [ImplementationQuality] {\n        use ImplementationQuality::*;\n        &[ManuallyOptimized, TargetOptimized, RustOptimized, Generic, Dreadful]\n    }\n\n    pub fn cost(&self) -> usize {\n        ImplementationQuality::best_to_worst().iter().position(|x| x == self).unwrap()\n    }\n}\n\nimpl PartialOrd for ImplementationQuality {\n    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\n        Some(usize::from(*self).cmp(&usize::from(*other)))\n    }\n}\n\nimpl From<ImplementationQuality> for usize {\n    fn from(value: ImplementationQuality) -> Self {\n        value.cost()\n    }\n}\n\npub trait MatMatMul: Debug + dyn_clone::DynClone + Send + Sync + std::any::Any {\n    fn name(&self) -> &str;\n    fn mr(&self) -> usize;\n    fn nr(&self) -> usize;\n\n    fn quality(&self) -> ImplementationQuality;\n    fn dynamic_boost(&self) -> isize;\n\n    #[allow(clippy::type_complexity)]\n    fn packings(&self) -> &[(Box<dyn MMMInputFormat>, Box<dyn MMMInputFormat>)];\n\n    fn internal_type(&self) -> DatumType;\n\n    unsafe fn c_view(&self, m_axis: Option<usize>, n_axis: Option<usize>) -> OutputStoreSpec;\n    unsafe fn c_from_data_and_strides(\n        &self,\n        item_size: usize,\n        row_stride: isize,\n        col_stride: isize,\n    ) -> OutputStoreSpec;\n\n    fn can_fuse(&self, spec: &FusedSpec) -> bool;\n\n    fn stores(&self) -> Cow<'_, [DatumType]>;\n\n    unsafe fn run(&self, m: usize, n: usize, non_linear: &[FusedSpec]) -> TractResult<()> {\n        unsafe {\n            let mut scratch = self.allocate_scratch_space();\n            self.run_with_scratch_space(m, n, &mut *scratch, non_linear)\n        }\n    }\n\n    unsafe fn allocate_scratch_space(&self) -> Box<dyn ScratchSpace>;\n    unsafe fn can_use_scratch_space(&self, scratch: &dyn ScratchSpace) -> bool;\n    unsafe fn run_with_scratch_space(\n        &self,\n        m: usize,\n        n: usize,\n        scratch: &mut dyn ScratchSpace,\n        non_linear: &[FusedSpec],\n    ) -> TractResult<()>;\n}\n\ndyn_clone::clone_trait_object!(MatMatMul);\n\nimpl PartialEq for Box<dyn MatMatMul> {\n    fn eq(&self, other: &Box<dyn MatMatMul>) -> bool {\n        self.name() == other.name()\n    }\n}\nimpl Eq for Box<dyn MatMatMul> {}\n\nimpl std::hash::Hash for Box<dyn MatMatMul> {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.name().hash(state)\n    }\n}\n\nimpl<K: MatMatMulKer> MatMatMul for K {\n    fn name(&self) -> &str {\n        self.name()\n    }\n    fn mr(&self) -> usize {\n        self.mr()\n    }\n    fn nr(&self) -> usize {\n        self.nr()\n    }\n\n    fn quality(&self) -> ImplementationQuality {\n        MatMatMulKer::quality(self)\n    }\n\n    fn dynamic_boost(&self) -> isize {\n        MatMatMulKer::dynamic_boost(self)\n    }\n\n    fn packings(&self) -> &[(Box<dyn MMMInputFormat>, Box<dyn MMMInputFormat>)] {\n        self.packings()\n    }\n\n    fn internal_type(&self) -> DatumType {\n        K::Acc::datum_type()\n    }\n\n    fn can_fuse(&self, spec: &FusedSpec) -> bool {\n        self.can_fuse(spec)\n    }\n\n    unsafe fn c_view(&self, m_axis: Option<usize>, n_axis: Option<usize>) -> OutputStoreSpec {\n        OutputStoreSpec::View { m_axis, n_axis, mr: self.mr(), nr: self.nr() }\n    }\n\n    unsafe fn c_from_data_and_strides(\n        &self,\n        item_size: usize,\n        row_stride: isize,\n        col_stride: isize,\n    ) -> OutputStoreSpec {\n        OutputStoreSpec::Strides {\n            row_byte_stride: row_stride * item_size as isize,\n            col_byte_stride: col_stride * item_size as isize,\n            mr: self.mr(),\n            nr: self.nr(),\n        }\n    }\n\n    fn stores(&self) -> Cow<'_, [DatumType]> {\n        self.stores()\n    }\n\n    unsafe fn allocate_scratch_space(&self) -> Box<dyn ScratchSpace> {\n        Box::<ScratchSpaceImpl<K::Acc>>::default()\n    }\n\n    unsafe fn can_use_scratch_space(&self, scratch: &dyn ScratchSpace) -> bool {\n        scratch.downcast_ref::<ScratchSpaceImpl<K::Acc>>().is_some()\n    }\n\n    unsafe fn run_with_scratch_space(\n        &self,\n        m: usize,\n        n: usize,\n        scratch: &mut dyn ScratchSpace,\n        non_linear: &[FusedSpec],\n    ) -> TractResult<()> {\n        unsafe {\n            let scratch = scratch\n                .downcast_mut::<ScratchSpaceImpl<K::Acc>>()\n                .context(\"Wrong scratch space type\")?;\n            scratch.prepare(self, m, n, non_linear)?;\n            if n == 1 && self.nr() == 1 {\n                run_with_scratch_space_vec(self, m, scratch, non_linear)\n            } else {\n                let (mut prefer_col, mut prefer_row) = (0, 0);\n                for uop in non_linear.iter() {\n                    if let Some(col) = uop.prefer_col_outer() {\n                        prefer_col = col as usize;\n                        prefer_row = (!col) as usize;\n                    }\n                }\n                if prefer_col > prefer_row {\n                    run_with_scratch_space_col_outer(self, m, n, scratch, non_linear)\n                } else {\n                    run_with_scratch_space_row_outer(self, m, n, scratch, non_linear)\n                }\n            }\n        }\n    }\n}\n\nunsafe fn run_with_scratch_space_vec<K: MatMatMulKer>(\n    ker: &K,\n    m: usize,\n    scratch: &mut ScratchSpaceImpl<K::Acc>,\n    non_linear: &[FusedSpec],\n) -> TractResult<()> {\n    unsafe {\n        match crate::multithread::current_tract_executor() {\n            Executor::SingleThread => {\n                for ia in 0..m.divceil(ker.mr()) {\n                    scratch.run(ker, non_linear, ia, 0)?;\n                }\n                Ok(())\n            }\n            #[cfg(feature = \"multithread-mm\")]\n            Executor::MultiThread(pool) => pool.install(|| {\n                (0..m.div_ceil(ker.mr()))\n                    .into_par_iter()\n                    .try_for_each(|ia| scratch.run(ker, non_linear, ia, 0))\n            }),\n        }\n    }\n}\n\nunsafe fn run_with_scratch_space_col_outer<K: MatMatMulKer>(\n    ker: &K,\n    m: usize,\n    n: usize,\n    scratch: &mut ScratchSpaceImpl<K::Acc>,\n    non_linear: &[FusedSpec],\n) -> TractResult<()> {\n    unsafe {\n        match crate::multithread::current_tract_executor() {\n            Executor::SingleThread => {\n                for ib in 0..n.divceil(ker.nr()) {\n                    for ia in 0..m.divceil(ker.mr()) {\n                        scratch.run(ker, non_linear, ia, ib)?;\n                    }\n                }\n                Ok(())\n            }\n            #[cfg(feature = \"multithread-mm\")]\n            Executor::MultiThread(pool) => pool.install(|| {\n                (0..n.div_ceil(ker.nr())).into_par_iter().try_for_each(|ib| {\n                    for ia in 0..m.divceil(ker.mr()) {\n                        scratch.run(ker, non_linear, ia, ib)?;\n                    }\n                    Ok(())\n                })\n            }),\n        }\n    }\n}\n\nunsafe fn run_with_scratch_space_row_outer<K: MatMatMulKer>(\n    ker: &K,\n    m: usize,\n    n: usize,\n    scratch: &mut ScratchSpaceImpl<K::Acc>,\n    non_linear: &[FusedSpec],\n) -> TractResult<()> {\n    unsafe {\n        match crate::multithread::current_tract_executor() {\n            Executor::SingleThread => {\n                for ia in 0..m.divceil(ker.mr()) {\n                    for ib in 0..n.divceil(ker.nr()) {\n                        scratch.run(ker, non_linear, ia, ib)?;\n                    }\n                }\n                Ok(())\n            }\n            #[cfg(feature = \"multithread-mm\")]\n            Executor::MultiThread(pool) => pool.install(|| {\n                pool.install(|| {\n                    (0..m.div_ceil(ker.mr())).into_par_iter().try_for_each(|ia| {\n                        for ib in 0..n.divceil(ker.nr()) {\n                            scratch.run(ker, non_linear, ia, ib)?;\n                        }\n                        Ok(())\n                    })\n                })\n            }),\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/panel_extract.rs",
    "content": "use std::fmt::{Debug, Display};\nuse tract_data::internal::*;\n\nuse super::{EagerPackedInput, MMMInputFormat, MMMInputValue};\nuse crate::pack::PackedFormat;\n\ntype Kernel = unsafe fn(input: *const u8, output: *mut u8, k: usize);\n\n#[allow(clippy::derived_hash_with_manual_eq)]\n#[derive(Hash, Clone)]\npub struct PanelExtractor {\n    pub name: String,\n    pub from: Box<dyn MMMInputFormat>,\n    pub to: PackedFormat,\n    pub kernel: Kernel,\n    pub supported_predicate: fn() -> bool,\n}\n\nimpl Debug for PanelExtractor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{} ({:?} -> {:?})\", self.name, self.from, self.to)\n    }\n}\n\nimpl Display for PanelExtractor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.name)\n    }\n}\n\nimpl PartialEq for PanelExtractor {\n    fn eq(&self, other: &Self) -> bool {\n        self.name == other.name && *self.from == *other.from && self.to == other.to\n    }\n}\nimpl Eq for PanelExtractor {}\n\nimpl PanelExtractor {\n    #[allow(unused_variables)]\n    pub fn is_supported_here(&self) -> bool {\n        (self.supported_predicate)()\n    }\n}\n\n#[derive(Clone, Hash, PartialEq, Eq)]\npub struct PanelExtractInput {\n    pub format: PanelExtractor,\n    pub data: EagerPackedInput,\n}\n\nimpl MMMInputValue for PanelExtractInput {\n    fn scratch_panel_buffer_layout(&self) -> Option<std::alloc::Layout> {\n        Some(self.format.to.single_panel_layout(self.data.k(), self.format.to.dt.size_of()))\n    }\n    fn panel_bytes(&self, i: usize, buffer: Option<*mut u8>) -> TractResult<*const u8> {\n        let scratch = buffer.unwrap();\n        unsafe {\n            let source = self.data.packed.as_ptr().add(self.data.panel_bytes * i);\n            (self.format.kernel)(source, scratch, self.data.k());\n        }\n        Ok(scratch)\n    }\n    fn mn(&self) -> usize {\n        self.data.mn()\n    }\n    fn k(&self) -> usize {\n        self.data.k()\n    }\n    fn format(&self) -> &dyn MMMInputFormat {\n        &self.format.to\n    }\n    fn exotic_fact(&self) -> &dyn ExoticFact {\n        self.data.exotic_fact()\n    }\n    fn extract_at_mn_f16(&self, mn: usize, slice: &mut [f16]) -> TractResult<()> {\n        self.data.extract_at_mn_f16(mn, slice)\n    }\n    fn extract_at_mn_f32(&self, mn: usize, slice: &mut [f32]) -> TractResult<()> {\n        self.data.extract_at_mn_f32(mn, slice)\n    }\n}\n\nimpl Display for PanelExtractInput {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"PanelExtract({})\", self.data)\n    }\n}\n\nimpl Debug for PanelExtractInput {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"PanelExtract({})\", self.data)\n    }\n}\n\n#[macro_export]\nmacro_rules! panel_extractor {\n    ( $func:path as $id:ident($from:expr, $to: expr)\n            $(where($where:expr))?\n     ) => {\n        paste! {\n            lazy_static::lazy_static! {\n                pub static ref $id: $crate::mmm::PanelExtractor = {\n                    use $crate::mmm::MMMInputFormat;\n                    let (from, to) = ($from, $to);\n                    assert!(from.r() == to.r());\n                    #[allow(unused_mut)]\n                    let mut it = $crate::mmm::PanelExtractor {\n                        name: stringify!($id).to_string(),\n                        from,\n                        to,\n                        kernel: $func,\n                        supported_predicate: || true\n                    };\n                    $(\n                        it.supported_predicate = $where;\n                    )?\n                    it\n                };\n            }\n\n            #[cfg(test)]\n            mod [<test_$id>] {\n                use super::$id;\n                #[test]\n                fn repack_0block_1panel() {\n                    $crate::frame::mmm::panel_extract::test::test_packing(&$id, 0, 1).unwrap();\n                }\n\n                #[test]\n                fn repack_1block_0panel() {\n                    $crate::frame::mmm::panel_extract::test::test_packing(&$id, 1, 0).unwrap();\n                }\n\n                #[test]\n                fn repack_1block_1panel() {\n                    $crate::frame::mmm::panel_extract::test::test_packing(&$id, 1, 1).unwrap();\n                }\n\n                #[test]\n                fn repack_2block_1panel() {\n                    $crate::frame::mmm::panel_extract::test::test_packing(&$id, 2, 1).unwrap();\n                }\n\n                #[test]\n                fn repack_1block_2panel() {\n                    $crate::frame::mmm::panel_extract::test::test_packing(&$id, 1, 2).unwrap();\n                }\n\n                #[test]\n                fn repack_2block_2panel() {\n                    $crate::frame::mmm::panel_extract::test::test_packing(&$id, 2, 2).unwrap();\n                }\n            }\n        }\n    };\n}\n\n#[cfg(test)]\npub mod test {\n    use crate::frame::block_quant::PackedBlockQuantFormat;\n    use crate::mmm::PackedMatrixStorage;\n    use tract_data::internal::*;\n    use tract_ndarray::Array2;\n\n    use super::*;\n\n    pub fn test_packing(\n        extractor: &PanelExtractor,\n        blocks: usize,\n        panels: usize,\n    ) -> TractResult<()> {\n        if !extractor.is_supported_here() {\n            return Ok(());\n        }\n        assert!(extractor.from.r() == extractor.to.r());\n        assert!(extractor.to.dt == f32::datum_type() || extractor.to.dt == f16::datum_type());\n        if let Some(from) = extractor.from.downcast_ref::<PackedBlockQuantFormat>() {\n            test_packing_bq(extractor, from, blocks, panels)\n        } else if let Some(from) = extractor.from.downcast_ref() {\n            test_packing_plain(extractor, from, blocks, panels)\n        } else {\n            todo!()\n        }\n    }\n\n    pub fn test_packing_plain(\n        extractor: &PanelExtractor,\n        from: &PackedFormat,\n        blocks: usize,\n        panels: usize,\n    ) -> TractResult<()> {\n        let m = from.r * panels;\n        let k = 8 * blocks; // 8 is arbitrary\n        let to = &extractor.to;\n        let weights_orig =\n            Array2::from_shape_fn((m, k), |(m, k)| ((m * 31 + k * 17) % 20) as f32 - 10.)\n                .into_tensor()\n                .cast_to_dt(from.dt)?\n                .into_owned();\n        let packed_orig = from.prepare_tensor(&weights_orig, 1, 0)?;\n        let packed_orig_storage = packed_orig.try_storage_as::<PackedMatrixStorage>()?;\n        let packed_orig = packed_orig_storage.value().downcast_ref::<EagerPackedInput>().unwrap();\n\n        for panel in 0..panels {\n            let orig_panel = &packed_orig.packed[packed_orig.panel_bytes * panel..]\n                [..k * from.r * from.dt.size_of()];\n            let mut reference_panel = Tensor::zero_dt(from.dt, &[k, from.r])?;\n            reference_panel.as_bytes_mut().copy_from_slice(orig_panel);\n            reference_panel = reference_panel.cast_to_dt(to.dt)?.into_owned();\n\n            let mut tested_panel = Tensor::zero_dt(to.dt, &[k, from.r])?;\n            unsafe {\n                (extractor.kernel)(\n                    orig_panel.as_ptr(),\n                    tested_panel.as_bytes_mut().as_mut_ptr(),\n                    k,\n                );\n            }\n            compare_panels(&tested_panel, &reference_panel, from.r, k);\n        }\n        Ok(())\n    }\n\n    pub fn test_packing_bq(\n        extractor: &PanelExtractor,\n        from: &PackedBlockQuantFormat,\n        blocks: usize,\n        panels: usize,\n    ) -> TractResult<()> {\n        let m = from.r * panels;\n        let k = from.bq.block_len() * blocks;\n        let to = &extractor.to;\n        let weights_orig =\n            Array2::from_shape_fn((m, k), |(m, k)| ((m * 31 + k * 17) % 20) as f32 - 10.)\n                .into_tensor()\n                .cast_to_dt(to.dt)?\n                .into_owned();\n        let weights = if to.dt == f32::datum_type() {\n            from.bq\n                .dequant_f32(&from.bq.quant_f32(weights_orig.try_as_plain()?.as_slice::<f32>()?)?)?\n                .into_shape(&[m, k])?\n        } else {\n            from.bq\n                .dequant_f16(&from.bq.quant_f16(weights_orig.try_as_plain()?.as_slice::<f16>()?)?)?\n                .into_shape(&[m, k])?\n        };\n        let block_quant = if to.dt == f32::datum_type() {\n            from.bq.quant_f32(weights.try_as_plain()?.as_slice::<f32>()?)?\n        } else {\n            from.bq.quant_f16(weights.try_as_plain()?.as_slice::<f16>()?)?\n        };\n        let packed_block_quant =\n            from.bq.pack(&block_quant, k, from.r, from.zip, from.scales_at_end)?;\n\n        let mut reference_panel = Tensor::zero_dt(to.dt, &[k, from.r])?;\n        let mut tested_panel = Tensor::zero_dt(to.dt, &[k, from.r])?;\n\n        for panel in 0..packed_block_quant.panels_count() {\n            unsafe {\n                from.bq.extract_packed_panel(\n                    &packed_block_quant,\n                    to,\n                    panel,\n                    reference_panel.as_bytes_mut().as_mut_ptr(),\n                )?;\n\n                let source =\n                    packed_block_quant.packed.as_ptr().add(packed_block_quant.panel_bytes * panel);\n                (extractor.kernel)(source, tested_panel.as_bytes_mut().as_mut_ptr(), k);\n            }\n            compare_panels(&tested_panel, &reference_panel, from.r, k);\n        }\n        Ok(())\n    }\n\n    fn compare_panels(tested_panel: &Tensor, reference_panel: &Tensor, r: usize, k: usize) {\n        if tested_panel != reference_panel {\n            if reference_panel.datum_type() == f32::datum_type() {\n                crate::frame::mmm::tests::display_error(\n                    tested_panel.try_as_plain().unwrap().as_slice::<f32>().unwrap(),\n                    reference_panel.try_as_plain().unwrap().as_slice::<f32>().unwrap(),\n                    r,\n                    k,\n                );\n            } else {\n                crate::frame::mmm::tests::display_error(\n                    tested_panel.try_as_plain().unwrap().as_slice::<f16>().unwrap(),\n                    reference_panel.try_as_plain().unwrap().as_slice::<f16>().unwrap(),\n                    r,\n                    k,\n                );\n            }\n        }\n        assert_eq!(tested_panel, reference_panel);\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/scratch.rs",
    "content": "use super::{FusedKerSpec, FusedSpec, MatMatMulKer, OutputStoreKer};\nuse crate::{BinOp, LADatum};\nuse downcast_rs::{Downcast, impl_downcast};\nuse std::cell::RefCell;\nuse std::fmt::Debug;\nuse std::sync::atomic::AtomicUsize;\nuse tract_data::internal::num_integer::Integer;\nuse tract_data::internal::*;\n\nstatic GENERATION: AtomicUsize = AtomicUsize::new(1);\n\nthread_local! {\n    static TLS: RefCell<TLSScratch> = Default::default();\n}\n\n#[derive(Default, Debug)]\nstruct TLSScratch {\n    generation: usize,\n    blob: Blob,\n    ker_specs_16: Vec<FusedKerSpec<f16>>,\n    ker_specs_32: Vec<FusedKerSpec<f32>>,\n    ker_specs_64: Vec<FusedKerSpec<f64>>,\n}\n\nimpl TLSScratch {\n    #[allow(unknown_lints, clippy::missing_transmute_annotations)]\n    fn ker_specs<TI: LADatum>(&mut self) -> &mut Vec<FusedKerSpec<TI>> {\n        unsafe {\n            if TI::datum_type() == f32::datum_type() || TI::datum_type() == i32::datum_type() {\n                std::mem::transmute(&mut self.ker_specs_32)\n            } else if TI::datum_type() == f16::datum_type() {\n                std::mem::transmute(&mut self.ker_specs_16)\n            } else if TI::datum_type() == f64::datum_type() {\n                std::mem::transmute(&mut self.ker_specs_64)\n            } else {\n                todo!();\n            }\n        }\n    }\n\n    fn sync<TI: LADatum>(&mut self, scratch: &ScratchSpaceImpl<TI>) {\n        if self.generation == scratch.generation {\n            return;\n        }\n        let ker_specs = self.ker_specs::<TI>();\n        ker_specs.clear();\n        ker_specs.extend_from_slice(&scratch.ker_specs);\n\n        unsafe {\n            self.blob.ensure_size_and_align(scratch.blob_size, scratch.blob_align);\n\n            for LocDependant { loc, ker_spec, .. } in &scratch.loc_dependant {\n                #[allow(clippy::single_match)]\n                if matches!(scratch.ker_specs[*ker_spec], FusedKerSpec::AddMatMul { .. }) {\n                    let scratch = &mut *(self.blob.as_ptr().add(*loc) as *mut AddMatMulTemp);\n                    scratch.panel_a_id = usize::MAX;\n                    scratch.panel_b_id = usize::MAX;\n                };\n            }\n        }\n        self.generation = scratch.generation;\n    }\n}\n\npub trait ScratchSpace: Downcast + Send {}\nimpl_downcast!(ScratchSpace);\n\n#[derive(Debug, Default)]\npub struct ScratchSpaceImpl<TI: LADatum> {\n    generation: usize,\n    blob_size: usize,\n    blob_align: usize,\n    ker_specs: Vec<FusedKerSpec<TI>>,\n    loc_dependant: TVec<LocDependant>,\n    valid_down_tiles: usize,\n    remnant_down: usize,\n    valid_right_tiles: usize,\n    remnant_right: usize,\n}\n\n#[derive(Debug, new)]\nstruct LocDependant {\n    spec: usize,\n    ker_spec: usize,\n    // offset for the location dependant structure\n    loc: usize,\n    // offset of its associated dynamic-size buffers\n    buffer_a: Option<usize>,\n    buffer_b: Option<usize>,\n}\n\nimpl<TI: LADatum> ScratchSpace for ScratchSpaceImpl<TI> {}\nunsafe impl<TI: LADatum> Send for ScratchSpaceImpl<TI> {}\n\n#[derive(Debug)]\nstruct AddMatMulTemp {\n    ptr_a: *const u8,\n    panel_a_id: usize,\n    ptr_b: *const u8,\n    panel_b_id: usize,\n}\n\nimpl<TI: LADatum> ScratchSpaceImpl<TI> {\n    pub unsafe fn prepare(\n        &mut self,\n        ker: &impl MatMatMulKer<Acc = TI>,\n        m: usize,\n        n: usize,\n        specs: &[FusedSpec],\n    ) -> TractResult<()> {\n        use FusedKerSpec as FKS;\n        use FusedSpec as FS;\n        self.ker_specs.clear();\n        self.loc_dependant.clear();\n        self.ker_specs.reserve(specs.len() + 2);\n        self.ker_specs.push(FusedKerSpec::Clear);\n        self.valid_down_tiles = m / ker.mr();\n        self.remnant_down = m % ker.mr();\n        self.valid_right_tiles = n / ker.nr();\n        self.remnant_right = n % ker.nr();\n        let mut offset = 0;\n        let mut align = std::mem::size_of::<*const ()>();\n        fn ld(spec: usize, uspec: usize, loc: usize) -> LocDependant {\n            LocDependant { spec, ker_spec: uspec, loc, buffer_a: None, buffer_b: None }\n        }\n        for (ix, spec) in specs.iter().enumerate() {\n            offset = offset.next_multiple_of(&align);\n            let ker_spec = match spec {\n                FS::BinScalar(t, op) => match op {\n                    BinOp::Min => FKS::ScalarMin(*t.try_as_plain()?.to_scalar()?),\n                    BinOp::Max => FKS::ScalarMax(*t.try_as_plain()?.to_scalar()?),\n                    BinOp::Mul => FKS::ScalarMul(*t.try_as_plain()?.to_scalar()?),\n                    BinOp::Add => FKS::ScalarAdd(*t.try_as_plain()?.to_scalar()?),\n                    BinOp::Sub => FKS::ScalarSub(*t.try_as_plain()?.to_scalar()?),\n                    BinOp::SubF => FKS::ScalarSubF(*t.try_as_plain()?.to_scalar()?),\n                },\n                FS::ShiftLeft(s) => FKS::ShiftLeft(*s),\n                FS::RoundingShiftRight(s, rp) => FKS::RoundingShiftRight(*s, *rp),\n                FS::QScale(s, rp, m) => FKS::QScale(*s, *rp, *m),\n                FS::BinPerRow(_, _) => {\n                    self.loc_dependant.push(ld(ix, self.ker_specs.len(), offset));\n                    offset += TI::datum_type().size_of() * ker.mr();\n                    FusedKerSpec::Done\n                }\n                FS::BinPerCol(_, _) => {\n                    self.loc_dependant.push(ld(ix, self.ker_specs.len(), offset));\n                    offset += TI::datum_type().size_of() * ker.nr();\n                    FusedKerSpec::Done\n                }\n                FS::AddRowColProducts(_, _) => {\n                    self.loc_dependant.push(ld(ix, self.ker_specs.len(), offset));\n                    offset += TI::datum_type().size_of() * (ker.mr() + ker.nr());\n                    FusedKerSpec::Done\n                }\n                FS::AddUnicast(_) => {\n                    self.loc_dependant.push(ld(ix, self.ker_specs.len(), offset));\n                    offset += TI::datum_type().size_of() * ker.mr() * ker.nr();\n                    FusedKerSpec::Done\n                }\n                FS::Store(store) => {\n                    self.loc_dependant.push(ld(ix, self.ker_specs.len(), offset));\n                    offset += store.item_size * ker.mr() * ker.nr();\n                    FusedKerSpec::Done\n                }\n                FS::LeakyRelu(t) => FKS::LeakyRelu(*t.try_as_plain()?.to_scalar()?),\n                FS::AddMatMul { a, b, packing } => {\n                    let mut ld = ld(ix, self.ker_specs.len(), offset);\n                    offset += std::mem::size_of::<AddMatMulTemp>();\n                    if let Some(tmp) = a.scratch_panel_buffer_layout() {\n                        align = tmp.align().lcm(&align);\n                        offset = Integer::next_multiple_of(&offset, &tmp.align());\n                        ld.buffer_a = Some(offset);\n                        offset += tmp.size();\n                    }\n                    if let Some(tmp) = b.scratch_panel_buffer_layout() {\n                        align = tmp.align().lcm(&align);\n                        offset = Integer::next_multiple_of(&offset, &tmp.align());\n                        ld.buffer_b = Some(offset);\n                        offset += tmp.size();\n                    }\n                    self.loc_dependant.push(ld);\n                    FusedKerSpec::AddMatMul {\n                        k: 0,\n                        pa: std::ptr::null(),\n                        pb: std::ptr::null(),\n                        packing: *packing,\n                    }\n                }\n            };\n            self.ker_specs.push(ker_spec);\n        }\n        self.ker_specs.push(FKS::Done);\n        self.blob_size = offset;\n        self.blob_align = align;\n\n        self.generation = GENERATION.fetch_add(1, std::sync::atomic::Ordering::Relaxed);\n        Ok(())\n    }\n\n    pub unsafe fn run(\n        &self,\n        ker: &impl MatMatMulKer<Acc = TI>,\n        specs: &[FusedSpec],\n        down: usize,\n        right: usize,\n    ) -> TractResult<()> {\n        unsafe {\n            TLS.with_borrow_mut(|tls| {\n                tls.sync(self);\n                if down < self.valid_down_tiles && right < self.valid_right_tiles {\n                    self.for_valid_tile(ker, specs, tls, down, right)?;\n                    let err = ker.kernel(tls.ker_specs());\n                    debug_assert_eq!(err, 0, \"Kernel return error {err}\");\n                } else {\n                    let remnant_down =\n                        if down < self.valid_down_tiles { ker.mr() } else { self.remnant_down };\n                    let remnant_right =\n                        if right < self.valid_right_tiles { ker.nr() } else { self.remnant_right };\n                    self.for_border_tile(\n                        ker,\n                        specs,\n                        tls,\n                        down,\n                        right,\n                        remnant_down,\n                        remnant_right,\n                    )?;\n                    let err = ker.kernel(tls.ker_specs());\n                    debug_assert_eq!(err, 0, \"Kernel return error {err}\");\n                    self.postprocess_tile(specs, tls, down, right, remnant_down, remnant_right)?;\n                }\n                Ok(())\n            })\n        }\n    }\n\n    #[inline(always)]\n    unsafe fn for_valid_tile(\n        &self,\n        ker: &impl MatMatMulKer<Acc = TI>,\n        specs: &[FusedSpec],\n        tls: &mut TLSScratch,\n        down: usize,\n        right: usize,\n    ) -> TractResult<()> {\n        unsafe {\n            use FusedKerSpec as FKS;\n            use FusedSpec as FS;\n            let ScratchSpaceImpl { ker_specs, loc_dependant, .. } = self;\n            debug_assert!(specs.len() + 2 == ker_specs.len());\n            for LocDependant { spec, ker_spec, loc, buffer_a, buffer_b } in loc_dependant {\n                let spec = specs.get_unchecked(*spec);\n                let it = match spec {\n                    FS::BinPerRow(v, op) => {\n                        let v = v.as_ptr_unchecked::<TI>().add(down * ker.mr());\n                        match op {\n                            BinOp::Min => FKS::PerRowMin(v),\n                            BinOp::Max => FKS::PerRowMax(v),\n                            BinOp::Add => FKS::PerRowAdd(v),\n                            BinOp::Mul => FKS::PerRowMul(v),\n                            BinOp::Sub => FKS::PerRowSub(v),\n                            BinOp::SubF => FKS::PerRowSubF(v),\n                        }\n                    }\n                    FS::BinPerCol(v, op) => {\n                        let v = v.as_ptr_unchecked::<TI>().add(right * ker.nr());\n                        match op {\n                            BinOp::Min => FKS::PerColMin(v),\n                            BinOp::Max => FKS::PerColMax(v),\n                            BinOp::Add => FKS::PerColAdd(v),\n                            BinOp::Mul => FKS::PerColMul(v),\n                            BinOp::Sub => FKS::PerColSub(v),\n                            BinOp::SubF => FKS::PerColSubF(v),\n                        }\n                    }\n                    FS::AddRowColProducts(rows, cols) => {\n                        let row_ptr = rows.as_ptr_unchecked::<TI>().add(down * ker.mr());\n                        let col_ptr = cols.as_ptr_unchecked::<TI>().add(right * ker.nr());\n                        FKS::AddRowColProducts(row_ptr, col_ptr)\n                    }\n                    FS::AddUnicast(store) => FKS::AddUnicast(store.tile_c(down, right)),\n                    FS::Store(c_store) => FKS::Store(c_store.tile_c(down, right)),\n                    FS::AddMatMul { a, b, packing } => {\n                        let scratch = (tls.blob.as_mut_ptr().add(*loc) as *mut AddMatMulTemp)\n                            .as_mut()\n                            .unwrap();\n                        if scratch.panel_a_id != down {\n                            scratch.ptr_a = a.panel_bytes(\n                                down,\n                                buffer_a.map(|o| tls.blob.as_mut_ptr().add(o)),\n                            )?;\n                            scratch.panel_a_id = down;\n                        }\n                        if scratch.panel_b_id != right {\n                            scratch.ptr_b = b.panel_bytes(\n                                right,\n                                buffer_b.map(|o| tls.blob.as_mut_ptr().add(o)),\n                            )?;\n                            scratch.panel_b_id = right;\n                        }\n                        FKS::AddMatMul {\n                            k: b.k(),\n                            pa: scratch.ptr_a,\n                            pb: scratch.ptr_b,\n                            packing: *packing,\n                        }\n                    }\n                    _ => std::hint::unreachable_unchecked(),\n                };\n                *tls.ker_specs().get_unchecked_mut(*ker_spec) = it;\n            }\n            Ok(())\n        }\n    }\n\n    #[inline(never)]\n    #[allow(clippy::too_many_arguments)]\n    unsafe fn for_border_tile(\n        &self,\n        ker: &impl MatMatMulKer<Acc = TI>,\n        specs: &[FusedSpec],\n        tls: &mut TLSScratch,\n        down: usize,\n        right: usize,\n        m_remnant: usize,\n        n_remnant: usize,\n    ) -> TractResult<()> {\n        unsafe {\n            use FusedKerSpec as FKS;\n            use FusedSpec as FS;\n            for LocDependant { spec, ker_spec: uspec, loc, buffer_a, buffer_b } in\n                &self.loc_dependant\n            {\n                let loc = tls.blob.as_mut_ptr().add(*loc);\n                let spec = specs.get_unchecked(*spec);\n                let it = match spec {\n                    FS::BinPerRow(v, op) => {\n                        let buf = std::slice::from_raw_parts_mut(loc as *mut TI, ker.mr());\n                        let ptr = if m_remnant < ker.mr() {\n                            if m_remnant > 0 {\n                                buf.get_unchecked_mut(..m_remnant).copy_from_slice(\n                                    v.as_slice_unchecked()\n                                        .get_unchecked(down * ker.mr()..)\n                                        .get_unchecked(..m_remnant),\n                                );\n                            }\n                            if cfg!(debug_assertions) {\n                                buf.get_unchecked_mut(m_remnant..)\n                                    .iter_mut()\n                                    .for_each(|x| *x = TI::zero());\n                            }\n                            buf.as_ptr()\n                        } else {\n                            v.as_ptr_unchecked::<TI>().add(down * ker.mr())\n                        };\n                        match op {\n                            BinOp::Min => FKS::PerRowMin(ptr),\n                            BinOp::Max => FKS::PerRowMax(ptr),\n                            BinOp::Add => FKS::PerRowAdd(ptr),\n                            BinOp::Mul => FKS::PerRowMul(ptr),\n                            BinOp::Sub => FKS::PerRowSub(ptr),\n                            BinOp::SubF => FKS::PerRowSubF(ptr),\n                        }\n                    }\n                    FS::BinPerCol(v, op) => {\n                        let buf = std::slice::from_raw_parts_mut(loc as *mut TI, ker.nr());\n                        let ptr = if n_remnant < ker.nr() {\n                            if n_remnant > 0 {\n                                buf.get_unchecked_mut(..n_remnant).copy_from_slice(\n                                    v.as_slice_unchecked()\n                                        .get_unchecked(right * ker.nr()..)\n                                        .get_unchecked(..n_remnant),\n                                );\n                            }\n                            if cfg!(debug_assertions) {\n                                buf.get_unchecked_mut(n_remnant..)\n                                    .iter_mut()\n                                    .for_each(|x| *x = TI::zero());\n                            }\n                            buf.as_ptr()\n                        } else {\n                            v.as_ptr_unchecked::<TI>().add(right * ker.nr())\n                        };\n                        match op {\n                            BinOp::Min => FKS::PerColMin(ptr),\n                            BinOp::Max => FKS::PerColMax(ptr),\n                            BinOp::Add => FKS::PerColAdd(ptr),\n                            BinOp::Mul => FKS::PerColMul(ptr),\n                            BinOp::Sub => FKS::PerColSub(ptr),\n                            BinOp::SubF => FKS::PerColSubF(ptr),\n                        }\n                    }\n                    FS::AddRowColProducts(rows, cols) => {\n                        let r = std::slice::from_raw_parts_mut(loc as *mut TI, ker.mr());\n                        let row_ptr = if m_remnant < ker.mr() {\n                            r.get_unchecked_mut(..m_remnant).copy_from_slice(\n                                rows.as_slice_unchecked()\n                                    .get_unchecked(down * ker.mr()..)\n                                    .get_unchecked(..m_remnant),\n                            );\n                            if cfg!(debug_assertions) {\n                                r.get_unchecked_mut(m_remnant..)\n                                    .iter_mut()\n                                    .for_each(|x| *x = TI::zero());\n                            }\n                            r.as_ptr()\n                        } else {\n                            rows.as_ptr_unchecked::<TI>().add(down * ker.mr())\n                        };\n                        let c = std::slice::from_raw_parts_mut(\n                            (loc as *mut TI).add(ker.mr()),\n                            ker.nr(),\n                        );\n                        let col_ptr = if n_remnant < ker.nr() {\n                            c.get_unchecked_mut(..n_remnant).copy_from_slice(\n                                cols.as_slice_unchecked()\n                                    .get_unchecked(right * ker.nr()..)\n                                    .get_unchecked(..n_remnant),\n                            );\n                            if cfg!(debug_assertions) {\n                                r.get_unchecked_mut(n_remnant..)\n                                    .iter_mut()\n                                    .for_each(|x| *x = TI::zero());\n                            }\n                            c.as_ptr()\n                        } else {\n                            cols.as_ptr_unchecked::<TI>().add(right * ker.nr())\n                        };\n                        FKS::AddRowColProducts(row_ptr, col_ptr)\n                    }\n                    FS::AddUnicast(store) => {\n                        let row_byte_stride = store.row_byte_stride;\n                        let col_byte_stride = store.col_byte_stride;\n                        let tile_offset = row_byte_stride * down as isize * ker.mr() as isize\n                            + col_byte_stride * right as isize * ker.nr() as isize;\n                        let tile_ptr = store.ptr.offset(tile_offset);\n                        let tmp_d_tile =\n                            std::slice::from_raw_parts_mut(loc as *mut TI, ker.mr() * ker.nr());\n                        if cfg!(debug_assertions) {\n                            tmp_d_tile.iter_mut().for_each(|t| *t = TI::zero());\n                        }\n                        for r in 0..m_remnant as isize {\n                            for c in 0..n_remnant as isize {\n                                let inner_offset = c * col_byte_stride + r * row_byte_stride;\n                                if inner_offset + tile_offset\n                                    < (store.item_size * store.item_count) as isize\n                                {\n                                    *tmp_d_tile\n                                        .get_unchecked_mut(r as usize + c as usize * ker.mr()) =\n                                        *(tile_ptr.offset(inner_offset) as *const TI);\n                                }\n                            }\n                        }\n                        FKS::AddUnicast(OutputStoreKer {\n                            ptr: tmp_d_tile.as_ptr() as _,\n                            row_byte_stride: std::mem::size_of::<TI>() as isize,\n                            col_byte_stride: (std::mem::size_of::<TI>() * ker.mr()) as isize,\n                            item_size: std::mem::size_of::<TI>(),\n                        })\n                    }\n                    FS::Store(c_store) => {\n                        let tmpc = OutputStoreKer {\n                            ptr: loc as _,\n                            item_size: c_store.item_size,\n                            row_byte_stride: c_store.item_size as isize,\n                            col_byte_stride: (c_store.item_size * ker.mr()) as isize,\n                        };\n                        FKS::Store(tmpc)\n                    }\n                    FS::AddMatMul { a, b, packing } => {\n                        let scratch = (loc as *mut AddMatMulTemp).as_mut().unwrap();\n                        if scratch.panel_a_id != down {\n                            scratch.ptr_a = a.panel_bytes(\n                                down,\n                                buffer_a.map(|o| tls.blob.as_mut_ptr().add(o)),\n                            )?;\n                            scratch.panel_a_id = down;\n                        }\n                        if scratch.panel_b_id != right {\n                            scratch.ptr_b = b.panel_bytes(\n                                right,\n                                buffer_b.map(|o| tls.blob.as_mut_ptr().add(o)),\n                            )?;\n                            scratch.panel_b_id = right;\n                        }\n                        FKS::AddMatMul {\n                            k: b.k(),\n                            pa: scratch.ptr_a,\n                            pb: scratch.ptr_b,\n                            packing: *packing,\n                        }\n                    }\n                    _ => std::hint::unreachable_unchecked(),\n                };\n                *tls.ker_specs().get_unchecked_mut(*uspec) = it;\n            }\n            Ok(())\n        }\n    }\n\n    #[inline]\n    pub fn uspecs(&self) -> &[FusedKerSpec<TI>] {\n        &self.ker_specs\n    }\n\n    unsafe fn postprocess_tile(\n        &self,\n        specs: &[FusedSpec],\n        tls: &mut TLSScratch,\n        down: usize,\n        right: usize,\n        m_remnant: usize,\n        n_remnant: usize,\n    ) -> TractResult<()>\n    where\n        TI: LADatum,\n    {\n        unsafe {\n            for LocDependant { spec, ker_spec: uspec, .. } in self.loc_dependant.iter() {\n                let spec = specs.get_unchecked(*spec);\n                let ker_spec = tls.ker_specs::<TI>().get_unchecked(*uspec);\n                if let (FusedSpec::Store(c_store), FusedKerSpec::Store(tmp)) = (spec, ker_spec) {\n                    c_store.set_from_tile(down, right, m_remnant, n_remnant, tmp)\n                }\n            }\n            Ok(())\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/storage.rs",
    "content": "use std::fmt;\nuse std::fmt::Debug;\nuse tract_data::internal::*;\n\nuse super::MMMInputValue;\n\n/// Non-plain tensor storage for packed matrices.\n///\n/// Holds one or more `Box<dyn MMMInputValue>` values with an optional batch\n/// shape, replacing the previous `Tensor` + double-downcast pattern.\n#[derive(Clone, PartialEq, Eq)]\npub struct PackedMatrixStorage {\n    values: Vec<Box<dyn MMMInputValue>>,\n    batch_shape: TVec<usize>,\n    batch_strides: TVec<isize>,\n}\n\nimpl PackedMatrixStorage {\n    /// Scalar storage (one value, empty shape).\n    pub fn new(value: Box<dyn MMMInputValue>) -> Self {\n        PackedMatrixStorage { values: vec![value], batch_shape: tvec![], batch_strides: tvec![] }\n    }\n\n    /// Batched storage (shape like `[batch, group]`).\n    pub fn new_batched(shape: &[usize], values: Vec<Box<dyn MMMInputValue>>) -> Self {\n        let expected: usize = shape.iter().product();\n        assert_eq!(values.len(), expected, \"values length must match shape product\");\n        let strides = Self::compute_strides(shape);\n        PackedMatrixStorage { values, batch_shape: shape.into(), batch_strides: strides }\n    }\n\n    fn compute_strides(shape: &[usize]) -> TVec<isize> {\n        let mut strides: TVec<isize> = tvec![0; shape.len()];\n        if !shape.is_empty() {\n            strides[shape.len() - 1] = 1;\n            for i in (0..shape.len() - 1).rev() {\n                strides[i] = strides[i + 1] * shape[i + 1] as isize;\n            }\n        }\n        strides\n    }\n\n    /// Scalar access (asserts single value).\n    #[inline]\n    pub fn value(&self) -> &dyn MMMInputValue {\n        debug_assert_eq!(self.values.len(), 1);\n        &*self.values[0]\n    }\n\n    /// Batched access by coordinates.\n    pub fn value_at(&self, coords: &[usize]) -> &dyn MMMInputValue {\n        let idx = self.flat_index(coords);\n        &*self.values[idx]\n    }\n\n    /// Batched access by flat (pre-computed) index.\n    #[inline]\n    pub fn value_at_flat(&self, idx: usize) -> &dyn MMMInputValue {\n        &*self.values[idx]\n    }\n\n    pub fn values(&self) -> &[Box<dyn MMMInputValue>] {\n        &self.values\n    }\n\n    pub fn batch_shape(&self) -> &[usize] {\n        &self.batch_shape\n    }\n\n    pub fn batch_strides(&self) -> &[isize] {\n        &self.batch_strides\n    }\n\n    /// Convert to a Tensor with the given logical datum type.\n    pub fn into_tensor(self, dt: DatumType) -> Tensor {\n        let shape: TVec<usize> = self.batch_shape.clone();\n        Tensor::from_storage(dt, &shape, self)\n    }\n\n    fn flat_index(&self, coords: &[usize]) -> usize {\n        coords.iter().zip(self.batch_strides.iter()).map(|(c, s)| *c as isize * s).sum::<isize>()\n            as usize\n    }\n}\n\nimpl fmt::Debug for PackedMatrixStorage {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"PackedMatrixStorage({} values, shape={:?})\", self.values.len(), self.batch_shape)\n    }\n}\n\nimpl fmt::Display for PackedMatrixStorage {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"PackedMatrixStorage({} values, shape={:?})\", self.values.len(), self.batch_shape)\n    }\n}\n\nimpl TensorStorage for PackedMatrixStorage {\n    fn byte_len(&self) -> usize {\n        // Approximate: sum of individual value sizes isn't precise but gives a ballpark\n        self.values.len() * std::mem::size_of::<Box<dyn MMMInputValue>>()\n    }\n\n    fn is_empty(&self) -> bool {\n        self.values.is_empty()\n    }\n\n    fn deep_clone(&self) -> Box<dyn TensorStorage> {\n        Box::new(self.clone())\n    }\n\n    fn as_plain(&self) -> Option<&PlainStorage> {\n        None\n    }\n\n    fn as_plain_mut(&mut self) -> Option<&mut PlainStorage> {\n        None\n    }\n\n    fn into_plain(self: Box<Self>) -> Option<PlainStorage> {\n        None\n    }\n\n    fn dyn_hash(&self, state: &mut dyn std::hash::Hasher) {\n        for v in &self.values {\n            v.dyn_hash(state);\n        }\n    }\n\n    fn exotic_fact(&self, _shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {\n        if self.values.len() == 1 {\n            Ok(Some(dyn_clone::clone_box(self.values[0].exotic_fact())))\n        } else {\n            let facts: TVec<Box<dyn ExoticFact>> =\n                self.values.iter().map(|v| dyn_clone::clone_box(v.exotic_fact())).collect();\n            Ok(Some(Box::new(facts)))\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]\npub enum OutputStoreSpec {\n    View { m_axis: Option<usize>, n_axis: Option<usize>, mr: usize, nr: usize },\n    Strides { row_byte_stride: isize, col_byte_stride: isize, mr: usize, nr: usize },\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct OutputStore {\n    pub(crate) ptr: *mut u8,\n    pub(crate) row_byte_stride: isize,\n    pub(crate) col_byte_stride: isize,\n    pub(crate) panel_row_byte_stride: isize,\n    pub(crate) panel_col_byte_stride: isize,\n    pub(crate) item_size: usize,\n    pub(crate) item_count: usize,\n    pub(crate) mr: usize,\n}\n\nunsafe impl Send for OutputStore {}\nunsafe impl Sync for OutputStore {}\n\nimpl OutputStoreSpec {\n    #[inline]\n    pub unsafe fn wrap(&self, tensor: &TensorView) -> OutputStore {\n        let (mr, nr, row_byte_stride, col_byte_stride) = unsafe { self.compute_strides(tensor) };\n        OutputStore {\n            ptr: unsafe { tensor.as_ptr_unchecked::<u8>() } as _,\n            row_byte_stride,\n            col_byte_stride,\n            panel_row_byte_stride: row_byte_stride * mr as isize,\n            panel_col_byte_stride: col_byte_stride * nr as isize,\n            item_size: tensor.datum_type().size_of(),\n            mr,\n            item_count: tensor.len(),\n        }\n    }\n\n    #[inline]\n    unsafe fn compute_strides(&self, tensor: &TensorView) -> (usize, usize, isize, isize) {\n        let size_of = tensor.datum_type().size_of() as isize;\n        match self {\n            OutputStoreSpec::View { m_axis, n_axis, mr, nr, .. } => {\n                let tensor_strides = tensor.strides();\n                let row_item_stride =\n                    m_axis.map(|ax| *unsafe { tensor_strides.get_unchecked(ax) }).unwrap_or(0);\n                let col_item_stride =\n                    n_axis.map(|ax| *unsafe { tensor_strides.get_unchecked(ax) }).unwrap_or(0);\n                let row_byte_stride = row_item_stride * size_of;\n                let col_byte_stride = col_item_stride * size_of;\n                (*mr, *nr, row_byte_stride, col_byte_stride)\n            }\n            OutputStoreSpec::Strides { row_byte_stride, col_byte_stride, mr, nr, .. } => {\n                (*mr, *nr, *row_byte_stride, *col_byte_stride)\n            }\n        }\n    }\n}\n\nimpl OutputStore {\n    #[inline]\n    pub(super) unsafe fn tile_c(&self, down: usize, right: usize) -> OutputStoreKer {\n        unsafe {\n            let (down, right) = (down as isize, right as isize);\n            OutputStoreKer {\n                ptr: self\n                    .ptr\n                    .offset(self.panel_row_byte_stride * down + self.panel_col_byte_stride * right)\n                    as *mut _,\n                row_byte_stride: self.row_byte_stride,\n                col_byte_stride: self.col_byte_stride,\n                item_size: self.item_size,\n            }\n        }\n    }\n\n    #[inline]\n    pub fn item_size(&self) -> usize {\n        self.item_size\n    }\n\n    #[inline]\n    pub(super) unsafe fn set_from_tile(\n        &self,\n        down: usize,\n        right: usize,\n        height: usize,\n        width: usize,\n        tile: &OutputStoreKer,\n    ) {\n        unsafe {\n            if self.item_size() == 1 {\n                self.set_from_tile_t::<i8>(down, right, height, width, tile)\n            } else if self.item_size() == 2 {\n                self.set_from_tile_t::<i16>(down, right, height, width, tile)\n            } else if self.item_size() == 4 {\n                self.set_from_tile_t::<i32>(down, right, height, width, tile)\n            } else {\n                self.set_from_tile_t::<i64>(down, right, height, width, tile)\n            }\n        }\n    }\n\n    #[inline]\n    unsafe fn set_from_tile_t<T: Datum + Copy>(\n        &self,\n        down: usize,\n        right: usize,\n        height: usize,\n        width: usize,\n        tile: &OutputStoreKer,\n    ) {\n        unsafe {\n            let tile = tile.ptr as *mut T;\n            let dst = self.ptr.add(\n                self.panel_row_byte_stride as usize * down\n                    + self.panel_col_byte_stride as usize * right,\n            );\n            for y in 0..height as isize {\n                for x in 0..width as isize {\n                    let value = tile.offset(y + x * self.mr as isize);\n                    let dst = dst.offset(y * self.row_byte_stride + x * self.col_byte_stride);\n                    *(dst as *mut T) = *value;\n                }\n            }\n        }\n    }\n}\n\n#[repr(C)]\n#[derive(PartialEq, Eq, Copy, Clone, Debug)]\npub struct OutputStoreKer {\n    pub ptr: *mut u8,\n    pub row_byte_stride: isize,\n    pub col_byte_stride: isize,\n    pub item_size: usize,\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/tests/frame.rs",
    "content": "use crate::frame::mmm::*;\nuse crate::{BinOp, LADatum};\nuse num_traits::AsPrimitive;\nuse std::ops::Neg;\nuse tests::display_error;\nuse tract_data::internal::*;\n\n#[macro_export]\nmacro_rules! mmm_frame_tests {\n    ($ker:expr, $ta:ty, $tb:ty, $tc:ty, $ti:ty) => {\n        mod frame {\n            use tract_data::internal::*;\n            #[allow(unused_imports)]\n            use $crate::frame::mmm::tests::frame::*;\n\n            #[test]\n            fn row_mul_2_1_3() -> TractResult<()> {\n                unsafe { row_mul::<_, $ta, $tb, $tc, $ti>($ker, 2, 3)? }\n                Ok(())\n            }\n\n            #[test]\n            fn row_add_2_1_3() -> TractResult<()> {\n                unsafe { row_add::<_, $ta, $tb, $tc, $ti>($ker, 2, 3)? }\n                Ok(())\n            }\n\n            #[test]\n            fn col_mul_2_1_3() -> TractResult<()> {\n                unsafe { col_mul::<_, $ta, $tb, $tc, $ti>($ker, 2, 3)? }\n                Ok(())\n            }\n\n            #[test]\n            fn col_add_2_1_3() -> TractResult<()> {\n                unsafe { col_add::<_, $ta, $tb, $tc, $ti>($ker, 2, 3)? }\n                Ok(())\n            }\n\n            #[test]\n            fn max_2_1_3() -> TractResult<()> {\n                unsafe { max::<_, $ta, $tb, $tc, $ti>($ker, 2, 3)? }\n                Ok(())\n            }\n\n            #[test]\n            fn min_2_1_3() -> TractResult<()> {\n                unsafe { min::<_, $ta, $tb, $tc, $ti>($ker, 2, 3)? }\n                Ok(())\n            }\n\n            #[test]\n            fn add_d_2_1_3() -> TractResult<()> {\n                unsafe { add_d::<_, $ta, $tb, $tc, $ti>($ker, 2, 3)? }\n                Ok(())\n            }\n\n            #[test]\n            fn add_d_big() -> TractResult<()> {\n                unsafe { add_d::<_, $ta, $tb, $tc, $ti>($ker, 197, 1)? }\n                Ok(())\n            }\n        }\n    };\n}\n\npub unsafe fn fused_ops<\n    K: MatMatMulKer<Acc = TI> + 'static,\n    TA,\n    TB,\n    TC,\n    TI,\n    F: Fn(usize, usize) -> TC,\n>(\n    ker: &K,\n    m: usize,\n    n: usize,\n    spec: &[FusedSpec],\n    expect: F,\n) -> TractResult<()>\nwhere\n    TA: LADatum + AsPrimitive<TI> + 'static,\n    TB: LADatum + AsPrimitive<TI> + 'static,\n    TC: LADatum + AsPrimitive<TI> + 'static,\n    TI: LADatum + AsPrimitive<TC> + 'static,\n    i32: AsPrimitive<TI>,\n    usize: AsPrimitive<TI>,\n{\n    if !ker.is_supported_here() {\n        return Ok(());\n    };\n    crate::setup_test_logger();\n\n    let mut found = Tensor::zero::<TC>(&[m, n])?;\n    let c_store = unsafe {\n        ker.c_from_data_and_strides(TC::datum_type().size_of(), n as isize, 1)\n            .wrap(&found.view_mut())\n    };\n    let mut spec: TVec<FusedSpec> = spec.into();\n    spec.push(FusedSpec::Store(c_store));\n\n    unsafe { ker.run(m, n, &spec) }?;\n    let expected =\n        tract_ndarray::prelude::Array2::from_shape_fn((m, n), |(r, c)| expect(r, c)).into_tensor();\n    let err = found.close_enough(&expected, true);\n    if err.is_err() {\n        display_error(\n            found.try_as_plain()?.as_slice::<TC>()?,\n            expected.try_as_plain()?.as_slice::<TC>()?,\n            m,\n            n,\n        );\n    }\n    err\n}\n\npub unsafe fn row_add<K: MatMatMulKer<Acc = TI> + 'static, TA, TB, TC, TI>(\n    ker: &K,\n    m: usize,\n    n: usize,\n) -> TractResult<()>\nwhere\n    TA: LADatum + AsPrimitive<TI> + 'static,\n    TB: LADatum + AsPrimitive<TI> + 'static,\n    TC: LADatum + AsPrimitive<TI> + 'static,\n    TI: LADatum + AsPrimitive<TC> + 'static + Neg<Output = TI>,\n    i32: AsPrimitive<TI>,\n    usize: AsPrimitive<TI>,\n{\n    let bias = (0..m).map(|i| i.as_()).collect::<Vec<TI>>();\n    unsafe {\n        fused_ops::<K, TA, TB, TC, TI, _>(\n            ker,\n            m,\n            n,\n            &[FusedSpec::BinPerRow(tensor1(&bias).view(), BinOp::Add)],\n            |r, _| bias[r].as_(),\n        )\n    }\n}\n\npub unsafe fn row_mul<K: MatMatMulKer<Acc = TI> + 'static, TA, TB, TC, TI>(\n    ker: &K,\n    m: usize,\n    n: usize,\n) -> TractResult<()>\nwhere\n    TA: LADatum + AsPrimitive<TI> + 'static,\n    TB: LADatum + AsPrimitive<TI> + 'static,\n    TC: LADatum + AsPrimitive<TI> + 'static,\n    TI: LADatum + AsPrimitive<TC> + 'static + Neg<Output = TI>,\n    i32: AsPrimitive<TI>,\n    usize: AsPrimitive<TI>,\n{\n    let bias = (0..m).map(|i| i.as_()).collect::<Vec<TI>>();\n    unsafe {\n        fused_ops::<K, TA, TB, TC, TI, _>(\n            ker,\n            m,\n            n,\n            &[\n                FusedSpec::BinScalar(&tensor0(1i32.as_()), BinOp::Add),\n                FusedSpec::BinPerRow(tensor1(&bias).view(), BinOp::Mul),\n            ],\n            |r, _| bias[r].as_(),\n        )\n    }\n}\n\npub unsafe fn col_add<K: MatMatMulKer<Acc = TI> + 'static, TA, TB, TC, TI>(\n    ker: &K,\n    m: usize,\n    n: usize,\n) -> TractResult<()>\nwhere\n    TA: LADatum + AsPrimitive<TI> + 'static,\n    TB: LADatum + AsPrimitive<TI> + 'static,\n    TC: LADatum + AsPrimitive<TI> + 'static,\n    TI: LADatum + AsPrimitive<TC> + 'static + Neg<Output = TI>,\n    i32: AsPrimitive<TI>,\n    usize: AsPrimitive<TI>,\n{\n    let bias = (0..n).map(|i| i.as_()).collect::<Vec<TI>>();\n    unsafe {\n        fused_ops::<K, TA, TB, TC, TI, _>(\n            ker,\n            m,\n            n,\n            &[FusedSpec::BinPerCol(tensor1(&bias).view(), BinOp::Add)],\n            |_, c| bias[c].as_(),\n        )\n    }\n}\n\npub unsafe fn col_mul<K: MatMatMulKer<Acc = TI> + 'static, TA, TB, TC, TI>(\n    ker: &K,\n    m: usize,\n    n: usize,\n) -> TractResult<()>\nwhere\n    TA: LADatum + AsPrimitive<TI> + 'static,\n    TB: LADatum + AsPrimitive<TI> + 'static,\n    TC: LADatum + AsPrimitive<TI> + 'static,\n    TI: LADatum + AsPrimitive<TC> + 'static + Neg<Output = TI>,\n    i32: AsPrimitive<TI>,\n    usize: AsPrimitive<TI>,\n{\n    let bias = (0..n).map(|i| i.as_()).collect::<Vec<TI>>();\n    unsafe {\n        fused_ops::<K, TA, TB, TC, TI, _>(\n            ker,\n            m,\n            n,\n            &[\n                FusedSpec::BinScalar(&tensor0(1i32.as_()), BinOp::Add),\n                FusedSpec::BinPerCol(tensor1(&bias).view(), BinOp::Mul),\n            ],\n            |_, c| bias[c].as_(),\n        )\n    }\n}\n\npub unsafe fn add_d<K: MatMatMulKer<Acc = TI> + 'static, TA, TB, TC, TI>(\n    ker: &K,\n    m: usize,\n    n: usize,\n) -> TractResult<()>\nwhere\n    TA: LADatum + AsPrimitive<TI> + 'static,\n    TB: LADatum + AsPrimitive<TI> + 'static,\n    TC: LADatum + AsPrimitive<TI> + 'static,\n    TI: LADatum + AsPrimitive<TC> + 'static + Neg<Output = TI>,\n    i32: AsPrimitive<TI>,\n    usize: AsPrimitive<TI>,\n{\n    let d = (0..m * n).map(|i| i.as_()).collect::<Vec<TI>>();\n    let d = tensor1(&d).into_shape(&[m, n])?;\n    let store_spec =\n        OutputStoreSpec::View { m_axis: Some(0), n_axis: Some(1), mr: ker.mr(), nr: ker.nr() };\n    let view_d = d.to_plain_array_view::<TI>()?.into_dimensionality()?;\n    unsafe {\n        fused_ops::<K, TA, TB, TC, TI, _>(\n            ker,\n            m,\n            n,\n            &[FusedSpec::AddUnicast(store_spec.wrap(&d.view()))],\n            |r, c| view_d[(r, c)].as_(),\n        )\n    }\n}\n\npub unsafe fn max<K: MatMatMulKer<Acc = TI>, TA, TB, TC, TI>(\n    ker: &K,\n    m: usize,\n    n: usize,\n) -> TractResult<()>\nwhere\n    TA: LADatum + AsPrimitive<TI> + 'static,\n    TB: LADatum + AsPrimitive<TI> + 'static,\n    TC: LADatum + AsPrimitive<TI> + 'static,\n    TI: LADatum + AsPrimitive<TC> + 'static + Neg<Output = TI>,\n    i32: AsPrimitive<TI>,\n    usize: AsPrimitive<TI>,\n{\n    let five: TI = 5.as_();\n    unsafe {\n        fused_ops::<K, TA, TB, TC, TI, _>(\n            ker,\n            m,\n            n,\n            &[FusedSpec::BinScalar(&tensor0(five), BinOp::Max)],\n            |_, _| five.as_(),\n        )\n    }\n}\n\npub unsafe fn min<K: MatMatMulKer<Acc = TI>, TA, TB, TC, TI>(\n    ker: &K,\n    m: usize,\n    n: usize,\n) -> TractResult<()>\nwhere\n    TA: LADatum + AsPrimitive<TI> + 'static,\n    TB: LADatum + AsPrimitive<TI> + 'static,\n    TC: LADatum + AsPrimitive<TI> + 'static,\n    TI: LADatum + AsPrimitive<TC> + 'static + Neg<Output = TI>,\n    i32: AsPrimitive<TI>,\n    usize: AsPrimitive<TI>,\n{\n    let five: TI = 5.as_();\n    unsafe {\n        fused_ops::<K, TA, TB, TC, TI, _>(\n            ker,\n            m,\n            n,\n            &[FusedSpec::BinScalar(&tensor0(five), BinOp::Min)],\n            |_, _| TC::zero(),\n        )\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/tests/fuse.rs",
    "content": "use crate::frame::mmm::fuse::FusedKerSpec;\nuse crate::frame::mmm::storage::*;\nuse crate::frame::mmm::tests::display_error;\nuse crate::frame::mmm::tests::store::mmm_stride_storage;\nuse crate::frame::mmm::*;\nuse num_traits::{AsPrimitive, Bounded};\nuse proptest::prelude::*;\nuse tract_data::internal::*;\n\n#[macro_export]\nmacro_rules! mmm_kernel_fuse_tests {\n    ($ker:expr, $tc:ty, $ti: ty) => {\n        mod fuse {\n            use num_traits::Zero;\n            #[allow(unused_imports)]\n            use tract_data::prelude::f16;\n            use tract_data::prelude::tensor0;\n            use $crate::frame::mmm::MatMatMulKer;\n            use $crate::frame::mmm::tests::fuse as test;\n            #[allow(unused_imports)]\n            use $crate::frame::mmm::tests::fuse::*;\n\n            #[test]\n            fn return_zeros() {\n                test::return_zeros::<_, $tc, $ti>($ker)\n            }\n\n            #[test]\n            fn store_non_contiguous() {\n                test::store_non_contiguous::<_, $tc, $ti>($ker)\n            }\n            proptest::proptest! {\n                #[test]\n                fn return_c_prop(c in tile::<_, $ti>($ker)) {\n                    test::return_c::<_, $ti>($ker, &c)\n                }\n            }\n\n            fn fmin<T: PartialOrd>(a: T, b: T) -> T {\n                if a < b { a } else { b }\n            }\n\n            fn fmax<T: PartialOrd>(a: T, b: T) -> T {\n                if a > b { a } else { b }\n            }\n\n            macro_rules! bin {\n                ($FKS:ident, $geo:expr, $f:expr, $extra_cond:expr) => {\n                    paste! {\n                        #[test]\n                        fn [<$FKS:snake>]() {\n                            if ($ker).is_supported_here() && $extra_cond {\n                                test::$geo::<_, $ti>($ker, $crate::mmm::FusedKerSpec::$FKS, $f);\n                            }\n                        }\n                    }\n                };\n            }\n\n            bin!(PerColMin, per_col, fmin, true);\n            bin!(PerColMax, per_col, fmax, true);\n            bin!(PerColAdd, per_col, |a, b| a + b, true);\n            bin!(PerColMul, per_col, |a, b| a * b, true);\n            bin!(PerColSub, per_col, |a, b| a - b, true);\n            bin!(PerColSubF, per_col, |a, b| b - a, true);\n\n            bin!(PerRowMin, per_row, fmin, true);\n            bin!(PerRowMax, per_row, fmax, true);\n            bin!(PerRowAdd, per_row, |a, b| a + b, true);\n            bin!(PerRowMul, per_row, |a, b| a * b, true);\n            bin!(PerRowSub, per_row, |a, b| a - b, true);\n            bin!(PerRowSubF, per_row, |a, b| b - a, true);\n\n            bin!(ScalarMin, scalar, fmin, true);\n            bin!(ScalarMax, scalar, fmax, true);\n            bin!(ScalarAdd, scalar, |a, b| a + b, true);\n            bin!(ScalarMul, scalar, |a, b| a * b, true);\n            bin!(ScalarSub, scalar, |a, b| a - b, true);\n            bin!(ScalarSubF, scalar, |a, b| b - a, true);\n\n            bin!(\n                LeakyRelu,\n                scalar,\n                |a, b| if b > <$ti>::zero() { b } else { a * b },\n                ($ker).can_fuse(&$crate::mmm::FusedSpec::LeakyRelu(&tensor0(<$ti>::from(1_u8))))\n            );\n\n            #[test]\n            fn return_c_add_row_col_product() {\n                test::return_c_add_row_col_product::<_, $ti>($ker)\n            }\n\n            #[test]\n            fn return_c_plus_d() {\n                test::return_c_plus_d::<_, $ti, $ti>($ker)\n            }\n\n            #[test]\n            fn return_c_clear() {\n                test::return_c_clear::<_, $ti>($ker)\n            }\n        }\n    };\n}\n\nuse crate::LADatum;\npub fn return_zeros<K, TC, TI>(ker: &K)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TC: LADatum,\n    TI: LADatum + Bounded + PartialEq,\n{\n    if !ker.is_supported_here() {\n        return;\n    }\n    let v = vec![TC::max_value(); ker.mr() * ker.nr()];\n    let c = mmm_stride_storage(&v, ker.nr());\n    let non_linear = tvec![FusedKerSpec::Clear, FusedKerSpec::Store(c), FusedKerSpec::Done];\n    let err = ker.kernel(&non_linear);\n    assert_eq!(err, 0);\n    let expected = vec![TC::zero(); v.len()];\n    display_error(&v, &expected, ker.mr(), ker.nr());\n    assert_eq!(v, expected);\n}\n\npub fn store_non_contiguous<K, TC, TI>(ker: &K)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TC: LADatum,\n    TI: LADatum + Bounded + PartialEq,\n{\n    if !ker.is_supported_here() {\n        return;\n    }\n    let v = vec![TC::max_value(); ker.mr() * 5 * ker.nr() * 3];\n    let c = OutputStoreKer {\n        ptr: v.as_ptr() as _,\n        row_byte_stride: (std::mem::size_of::<TC>() * 3 * ker.nr() * 5) as isize,\n        col_byte_stride: std::mem::size_of::<TC>() as isize * 3,\n        item_size: std::mem::size_of::<TC>(),\n    };\n    let non_linear = tvec![FusedKerSpec::Clear, FusedKerSpec::Store(c), FusedKerSpec::Done];\n    let err = ker.kernel(&non_linear);\n    assert_eq!(err, 0);\n    let mut expected = vec![TC::max_value(); v.len()];\n    for c in 0..ker.nr() {\n        for r in 0..ker.mr() {\n            expected[c * 3 + r * 3 * 5 * ker.nr()] = TC::zero();\n        }\n    }\n    assert_eq!(v, expected);\n}\n\npub fn fused_ops<K, TI, E>(ker: &K, c: &[TI], ops: &[FusedKerSpec<TI>], expect: E)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TI: LADatum,\n    E: Fn(usize, usize, TI) -> TI,\n{\n    if !ker.is_supported_here() {\n        return;\n    }\n    assert!(c.len() == ker.mr() * ker.nr());\n    let v = c.to_vec();\n    let c = mmm_stride_storage(&v, ker.nr());\n    let mut ops = ops.to_vec();\n    ops.insert(0, FusedKerSpec::AddUnicast(c));\n    ops.insert(0, FusedKerSpec::Clear);\n    ops.push(FusedKerSpec::Store(c));\n    ops.push(FusedKerSpec::Done);\n    let expected =\n        (0..v.len()).map(|ix| expect(ix / ker.nr(), ix % ker.nr(), v[ix])).collect::<Vec<TI>>();\n    let err = ker.kernel(&ops);\n    assert_eq!(err, 0);\n    display_error(&v, &expected, ker.mr(), ker.nr());\n    assert_eq!(v, expected);\n}\n\npub fn return_c<K, TI>(ker: &K, v: &[TI])\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TI: LADatum,\n    usize: AsPrimitive<TI>,\n{\n    fused_ops::<K, TI, _>(ker, v, &[], |_, _, c| c + 1.as_() - 1.as_())\n}\n\npub fn return_c_plus_d<K, TI, TD>(ker: &K)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TI: LADatum,\n    TD: LADatum + AsPrimitive<TI>,\n    usize: AsPrimitive<TI> + AsPrimitive<TD>,\n{\n    let len = ker.mr() * ker.nr();\n    let v: Vec<TI> = (0..len).map(|f| f.as_()).collect();\n    let d: Vec<TD> = (0..len).map(|f| ((3 * f) % 7).as_()).collect();\n    fused_ops::<K, TI, _>(\n        ker,\n        &v,\n        &[FusedKerSpec::AddUnicast(mmm_stride_storage(&d, ker.nr()))],\n        |row, col, c| c + d[row * ker.nr() + col].as_(),\n    );\n}\n\npub fn per_col<K, TI>(ker: &K, op: impl Fn(*const TI) -> FusedKerSpec<TI>, f: impl Fn(TI, TI) -> TI)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TI: LADatum,\n    usize: AsPrimitive<TI>,\n{\n    let len = ker.mr() * ker.nr();\n    let v: Vec<TI> = (0..len).map(|f| f.as_()).collect();\n    let bias: Vec<TI> = (0..ker.nr()).map(|f| (f + 1).as_()).collect();\n    fused_ops::<K, TI, _>(ker, &v, &[op(bias.as_ptr())], |_, col, c| f(bias[col], c))\n}\n\npub fn per_row<K, TI>(ker: &K, op: impl Fn(*const TI) -> FusedKerSpec<TI>, f: impl Fn(TI, TI) -> TI)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TI: LADatum,\n    usize: AsPrimitive<TI>,\n{\n    let len = ker.mr() * ker.nr();\n    let v: Vec<TI> = (0..len).map(|f| f.as_()).collect();\n    let bias: Vec<TI> = (0..ker.mr()).map(|f| (f + 1).as_()).collect();\n    fused_ops::<K, TI, _>(ker, &v, &[op(bias.as_ptr())], |row, _, c| f(bias[row], c))\n}\n\npub fn scalar<K, TI>(ker: &K, op: impl Fn(TI) -> FusedKerSpec<TI>, f: impl Fn(TI, TI) -> TI)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TI: LADatum,\n    isize: AsPrimitive<TI>,\n{\n    let len = ker.mr() * ker.nr();\n    let v: Vec<TI> = (0..len as isize).map(|f| (f - len as isize / 2).as_()).collect();\n    let five: TI = 5.as_();\n    fused_ops::<K, TI, _>(ker, &v, &[op(five)], |_, _, c| f(five, c))\n}\n\npub fn return_c_add_row_col_product<K, TI>(ker: &K)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TI: LADatum,\n    usize: AsPrimitive<TI>,\n{\n    let len = ker.mr() * ker.nr();\n    let v: Vec<TI> = (0..len).map(|f| (f + 1).as_()).collect();\n    let rows: Vec<TI> = (0..ker.mr()).map(|f| (f + 3).as_()).collect();\n    let cols: Vec<TI> = (0..ker.nr()).map(|f| (f + 2).as_()).collect();\n    fused_ops::<K, TI, _>(\n        ker,\n        &v,\n        &[FusedKerSpec::AddRowColProducts(rows.as_ptr(), cols.as_ptr())],\n        |row, col, c| c + cols[col] * rows[row],\n    )\n}\n\npub fn return_c_clear<K, TI>(ker: &K)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TI: LADatum,\n    usize: AsPrimitive<TI>,\n{\n    let len = ker.mr() * ker.nr();\n    let v: Vec<TI> = (0..len).map(|f| f.as_()).collect();\n    fused_ops::<K, TI, _>(ker, &v, &[FusedKerSpec::Clear], |_, _, _| 0.as_())\n}\n\npub fn tile<K, TI>(ker: &K) -> BoxedStrategy<Vec<TI>>\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TI: LADatum,\n    i8: AsPrimitive<TI>,\n{\n    let len = ker.mr() * ker.nr();\n    proptest::collection::vec(any::<i8>().prop_map(|c| c.as_()), len..=len).boxed()\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/tests/mod.rs",
    "content": "use crate::LADatum;\n\n#[macro_use]\npub mod fuse;\n#[macro_use]\npub mod frame;\n#[macro_use]\npub mod packed_packed;\n#[macro_use]\npub mod q_scale;\n#[macro_use]\npub mod store;\n\n#[cfg(test)]\nmacro_rules! test_mmm_kernel {\n    (f16, $ker:expr) => {\n        test_mmm_kernel_f16!($ker);\n    };\n    (f32, $ker:expr) => {\n        test_mmm_kernel_f32!($ker);\n    };\n    (f64, $ker:expr) => {\n        test_mmm_kernel_f64!($ker);\n    };\n    (i32, $ker:expr) => {\n        test_mmm_kernel_i32!($ker);\n    };\n}\n\n#[macro_export]\nmacro_rules! test_mmm_kernel_f16 {\n    ($ker: expr) => {\n        mmm_packed_packed_tests!(&*$ker, f16f16:0);\n        mmm_frame_tests!(&*$ker, f16, f16, f16, f16);\n        mmm_kernel_fuse_tests!(&*$ker, f16, f16);\n        mmm_store_test!(&*$ker, f16);\n    };\n}\n\n#[macro_export]\nmacro_rules! test_mmm_kernel_f32 {\n    ($ker: expr) => {\n        mmm_packed_packed_tests!(&*$ker, f32f32:0);\n        mmm_frame_tests!(&*$ker, f32, f32, f32, f32);\n        mmm_kernel_fuse_tests!(&*$ker, f32, f32);\n        mmm_store_test!(&*$ker, f32);\n    };\n}\n\n#[macro_export]\nmacro_rules! test_mmm_kernel_f64 {\n    ($ker:expr) => {\n        mmm_packed_packed_tests!(&*$ker, f64f64:0);\n        mmm_frame_tests!(&*$ker, f64, f64, f64, f64);\n        mmm_kernel_fuse_tests!(&*$ker, f64, f64);\n        mmm_store_test!(&*$ker, f64);\n    };\n}\n\n#[macro_export]\nmacro_rules! test_mmm_kernel_i32 {\n    ($ker: expr) => {\n        mmm_packed_packed_tests!(&*$ker, i32i32:0);\n        mmm_kernel_fuse_tests!(&*$ker, i32, i32);\n        mmm_frame_tests!(&*$ker, i32, i32, i32, i32);\n        mmm_q_scale_tests!(&*$ker);\n        mmm_store_test!(&*$ker, i32);\n    };\n}\n\npub fn display_error<TC: LADatum>(v: &[TC], expected: &[TC], m: usize, n: usize) {\n    if v != expected {\n        for ixm in 0..m {\n            print!(\"|\");\n            for ixn in 0..n {\n                use nu_ansi_term::Color::*;\n                let f = v[ixm * n + ixn];\n                let e = expected[ixm * n + ixn];\n                let color = if f != e { Red.bold() } else { Green.into() };\n                print!(\"{}|\", color.paint(format!(\"{f:5}\")));\n            }\n            print!(\"  #  \");\n            for ixn in 0..n {\n                print!(\"{:5} \", expected[ixm * n + ixn]);\n            }\n            println!();\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/tests/packed_packed.rs",
    "content": "use crate::block_quant::PackedBlockQuantFormat;\nuse crate::mmm::tests::display_error;\nuse crate::mmm::{AsInputValue, FusedKerSpec, FusedSpec, MatMatMul, MatMatMulKer, OutputStoreKer};\nuse crate::pack::PackedFormat;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse std::fmt::Debug;\nuse tract_data::internal::*;\n\n#[macro_export]\nmacro_rules! mmm_packed_packed_tests {\n    ($ker:expr, $packing_id:ident : $packing: expr) => {\n        mod $packing_id {\n            use super::*;\n            #[allow(unused_imports)]\n            use proptest::prelude::*;\n            #[allow(unused_imports)]\n            use tract_data::prelude::f16;\n            use tract_data::prelude::*;\n            use tract_itertools::Itertools;\n            use $crate::frame::mmm::kernel::MatMatMulKer;\n            #[allow(unused_imports)]\n            use $crate::frame::mmm::tests::packed_packed::*;\n\n            mod fuse {\n                use super::*;\n\n                proptest::proptest! {\n                    #[test]\n                    fn prop(pb in arbitrary_problem(false, $ker, $packing)) {\n                        pb.check().unwrap()\n                    }\n                }\n\n                fn t(a: impl Into<Vec<f32>>, b: impl Into<Vec<f32>>) -> TractResult<()> {\n                    PackedPackedProblem::kernel($ker, $packing, a, b).check()\n                }\n\n                #[test]\n                fn packed_packed_1() -> TractResult<()> {\n                    t(vec![1f32; $ker.mr()], vec![1f32; $ker.nr()])\n                }\n\n                #[test]\n                fn packed_packed_2() -> TractResult<()> {\n                    t(vec![1f32; $ker.mr() * 2], vec![1f32; $ker.nr() * 2])\n                }\n\n                #[test]\n                fn packed_packed_13() -> TractResult<()> {\n                    t(vec![1f32; $ker.mr() * 13], vec![1f32; $ker.nr() * 13])\n                }\n\n                #[test]\n                fn packed_packed_a_scale() -> TractResult<()> {\n                    t((1..=$ker.mr() as i64).map(|x| x as f32).collect_vec(), vec![1f32; $ker.nr()])\n                }\n\n                #[test]\n                fn packed_packed_a_scale_times_2() -> TractResult<()> {\n                    t(\n                        (1..=2 * $ker.mr() as i64).map(|x| x as f32).collect_vec(),\n                        vec![1f32; $ker.nr() * 2],\n                    )\n                }\n\n                #[test]\n                fn packed_packed_empty() -> TractResult<()> {\n                    t(vec![0f32; 0], vec![0f32; 0])\n                }\n\n                #[test]\n                fn packed_packed_bug_1() -> TractResult<()> {\n                    t(vec![0f32; $ker.mr()], vec![0f32; $ker.nr()])\n                }\n\n                #[test]\n                fn packed_packed_bug_2() -> TractResult<()> {\n                    let mut a = vec![0f32; $ker.mr()];\n                    a[0] = 1.;\n                    let mut b = vec![0f32; $ker.nr()];\n                    b[0] = 1.;\n                    t(a, b)\n                }\n\n                #[test]\n                fn packed_packed_bug_3() -> TractResult<()> {\n                    if $ker.mr() >= 4 {\n                        let mut a = vec![0f32; 2 * $ker.mr()];\n                        let mut b = vec![0f32; 2 * $ker.nr()];\n                        a[2] = -0.7548828f32;\n                        a[3] = 0.23547363f32;\n                        b[2 * $ker.nr() - 1] = 0.93603516;\n                        t(a, b)?;\n                    }\n                    Ok(())\n                }\n\n                #[test]\n                fn packed_packed_bug_4() -> TractResult<()> {\n                    if $ker.mr() > 16 {\n                        let mut a = vec![0f32; $ker.mr()];\n                        let mut b = vec![0f32; $ker.nr()];\n                        a[16] = 1.;\n                        b[0] = 1.;\n                        t(a, b)?;\n                    }\n                    Ok(())\n                }\n            }\n\n            mod frame {\n                use super::*;\n\n                proptest::proptest! {\n                    #[test]\n                    fn prop(pb in arbitrary_problem(true, $ker, $packing)) {\n                        pb.check().unwrap()\n                    }\n                }\n\n                fn t(\n                    m: usize,\n                    n: usize,\n                    a: impl Into<Vec<f32>>,\n                    b: impl Into<Vec<f32>>,\n                ) -> TractResult<()> {\n                    PackedPackedProblem::frame($ker, $packing, m, n, a, b).check()\n                }\n\n                fn ti(\n                    m: usize,\n                    n: usize,\n                    a: impl Into<Vec<i32>>,\n                    b: impl Into<Vec<i32>>,\n                ) -> TractResult<()> {\n                    let a = a.into().into_iter().map(|i| i as f32).collect_vec();\n                    let b = b.into().into_iter().map(|i| i as f32).collect_vec();\n                    t(m, n, a, b)\n                }\n\n                #[test]\n                fn trivial_1x2() -> TractResult<()> {\n                    ti(1, 2, [0], [0, 0])\n                }\n\n                #[test]\n                fn packed_packed_empty() -> TractResult<()> {\n                    t($ker.mr(), $ker.nr(), [], [])\n                }\n\n                #[test]\n                fn packed_packed_empty_2() -> TractResult<()> {\n                    t(2 * $ker.mr(), 2 * $ker.nr(), [], [])\n                }\n\n                #[test]\n                fn mat_mul_1() -> TractResult<()> {\n                    ti(3, 2, [-3, 3, 5, -5, 6, 0, -6, -5, 0, 0, 9, 7], [-8, 5, 5, -3, 5, 7, -8, -1])\n                }\n\n                #[test]\n                fn mat_mul_2() -> TractResult<()> {\n                    ti(1, 3, [122, 82], [0, 0, 37, 0, 0, 57])\n                }\n            }\n        }\n    };\n}\n\n#[derive(Debug, new)]\npub struct PackedPackedProblem<K>\nwhere\n    K: MatMatMulKer,\n{\n    pub frame_test: Option<(usize, usize)>,\n    pub ker: K,\n    pub packing: usize,\n    pub a: Vec<f32>,\n    pub b: Vec<f32>,\n}\n\npub fn arbitrary_problem<K: MatMatMulKer>(\n    frame_test: bool,\n    ker: &K,\n    packing: usize,\n) -> BoxedStrategy<PackedPackedProblem<K>> {\n    let (mr, nr) = (ker.mr(), ker.nr());\n    let item_range = if ker.internal_type().is_integer() { (-5f32)..5f32 } else { (-1f32)..1f32 };\n    let (m_range, n_range) =\n        if frame_test { (1usize..3 * mr, 1usize..3 * nr) } else { (mr..mr + 1, nr..nr + 1) };\n    let ker = ker.clone();\n    (m_range, 0usize..40, n_range)\n        .prop_flat_map(move |(m, k, n)| {\n            (\n                vec(item_range.clone(), k * m..=k * m),\n                vec(item_range.clone(), k * n..=k * n),\n                Just((m, n)),\n            )\n        })\n        .prop_map(move |(mut a, mut b, mn)| {\n            a.reverse();\n            b.reverse();\n            PackedPackedProblem {\n                frame_test: Some(mn).filter(|_| frame_test),\n                ker: ker.clone(),\n                packing,\n                a,\n                b,\n            }\n        })\n        .boxed()\n}\n\nimpl<K: MatMatMulKer> PackedPackedProblem<K> {\n    pub fn kernel(\n        ker: &K,\n        packing: usize,\n        a: impl Into<Vec<f32>>,\n        b: impl Into<Vec<f32>>,\n    ) -> PackedPackedProblem<K> {\n        PackedPackedProblem {\n            frame_test: None,\n            ker: ker.clone(),\n            packing,\n            a: a.into(),\n            b: b.into(),\n        }\n    }\n\n    pub fn frame(\n        ker: &K,\n        packing: usize,\n        m: usize,\n        n: usize,\n        a: impl Into<Vec<f32>>,\n        b: impl Into<Vec<f32>>,\n    ) -> PackedPackedProblem<K> {\n        PackedPackedProblem {\n            frame_test: Some((m, n)),\n            ker: ker.clone(),\n            packing,\n            a: a.into(),\n            b: b.into(),\n        }\n    }\n\n    pub fn mkn(&self) -> (usize, usize, usize) {\n        let (m, n) = self.frame_test.unwrap_or((self.ker.mr(), self.ker.nr()));\n        assert!(m != 0 && n != 0);\n        let k = self.a.len() / m;\n        assert_eq!(self.b.len() / n, k);\n        (m, k, n)\n    }\n\n    pub fn padded_inputs(&self) -> TractResult<(Tensor, Tensor)> {\n        let (pack_a, pack_b) = &self.ker.packings()[self.packing];\n        assert!(pack_b.k_alignment() == 1);\n        let (m, k, n) = self.mkn();\n        let k_aligned = k.next_multiple_of(pack_a.k_alignment());\n\n        let mut a = Tensor::zero::<f32>(&[m, k_aligned])?;\n        for row in 0..m {\n            for col in 0..k {\n                a.try_as_plain_mut()?.to_array_view_mut()?[[row, col]] = self.a[col + k * row];\n            }\n        }\n        if let Some(pf) = pack_a.downcast_ref::<PackedFormat>() {\n            a = a.cast_to_dt(pf.dt)?.into_owned();\n        }\n        let mut b = Tensor::zero::<f32>(&[k_aligned, n])?;\n        for row in 0..k {\n            for col in 0..n {\n                b.try_as_plain_mut()?.to_array_view_mut()?[[row, col]] = self.b[col + n * row];\n            }\n        }\n        if let Some(pf) = pack_b.downcast_ref::<PackedFormat>() {\n            b = b.cast_to_dt(pf.dt)?.into_owned();\n        }\n\n        Ok((a, b))\n    }\n\n    pub fn reference(&self) -> TractResult<Tensor> {\n        let (m, k, n) = self.mkn();\n        let pack_a = &self.ker.packings()[self.packing].0;\n        let (mut a, b) = self.padded_inputs()?;\n        let k_aligned = k.next_multiple_of(pack_a.k_alignment());\n        if let Some(pbqf) = pack_a.downcast_ref::<PackedBlockQuantFormat>() {\n            a = pbqf.simulate_precision_loss(a, 1)?;\n        };\n        let mut c = Tensor::zero::<K::Acc>(&[m, n])?;\n\n        let a = a.cast_to::<K::Acc>()?;\n        let a = a.try_as_plain()?.as_slice::<K::Acc>()?;\n        let b = b.cast_to::<K::Acc>()?;\n        let b = b.try_as_plain()?.as_slice::<K::Acc>()?;\n        let mut c_plain = c.try_as_plain_mut()?;\n        let mut view = c_plain.to_array_view_mut::<K::Acc>()?.into_dimensionality()?;\n        for ix_m in 0..m {\n            for ix_n in 0..n {\n                for ix_k in 0..k {\n                    let a = a[ix_k + k_aligned * ix_m];\n                    let b = b[ix_n + n * ix_k];\n                    view[(ix_m, ix_n)] += a * b;\n                }\n            }\n        }\n        Ok(c)\n    }\n\n    pub fn run(&self) -> TractResult<Tensor> {\n        let (m, k, n) = self.mkn();\n        let (pack_a, pack_b) = &self.ker.packings()[self.packing];\n        assert!(pack_b.k_alignment() == 1);\n        let k_aligned = k.next_multiple_of(pack_a.k_alignment());\n\n        let (a, b) = self.padded_inputs()?;\n        let pa = pack_a.prepare_one(&a, 1, 0)?;\n        let pb = pack_b.prepare_one(&b, 0, 1)?;\n\n        let mut v = unsafe { Tensor::uninitialized_dt(self.ker.internal_type(), &[m, n])? };\n        let item_size = self.ker.internal_type().size_of();\n\n        if self.frame_test.is_some() {\n            unsafe {\n                let c = self.ker.c_view(Some(0), Some(1)).wrap(&v.view_mut());\n                let ops = tvec!(\n                    FusedSpec::AddMatMul {\n                        a: AsInputValue::Borrowed(&*pa),\n                        b: AsInputValue::Borrowed(&*pb),\n                        packing: self.packing\n                    },\n                    FusedSpec::Store(c)\n                );\n                self.ker.run(m, n, &ops)?;\n            }\n        } else {\n            let c = OutputStoreKer {\n                ptr: v.as_bytes_mut().as_mut_ptr(),\n                row_byte_stride: (item_size * self.ker.nr()) as isize,\n                col_byte_stride: item_size as isize,\n                item_size,\n            };\n\n            let non_linear_ops = tvec!(\n                FusedKerSpec::Clear,\n                FusedKerSpec::AddMatMul {\n                    k: k_aligned,\n                    pa: pa.panel_bytes(0, None)?,\n                    pb: pb.panel_bytes(0, None)?,\n                    packing: self.packing\n                },\n                FusedKerSpec::Store(c),\n                FusedKerSpec::Done\n            );\n            let err = self.ker.kernel(&non_linear_ops);\n            assert_eq!(err, 0);\n        }\n        Ok(v)\n    }\n\n    pub fn check(&self) -> TractResult<()> {\n        if !self.ker.is_supported_here() {\n            return Ok(());\n        }\n        let expected = self.reference()?;\n        let found = self.run()?;\n        let app = if K::Acc::datum_type() == f16::datum_type() {\n            Approximation::SuperApproximate\n        } else {\n            Approximation::Approximate\n        };\n        let result = found.close_enough(&expected, app);\n        if result.is_err() {\n            let exp = expected.try_as_plain()?.as_slice::<K::Acc>()?;\n            let found = found.try_as_plain()?.as_slice::<K::Acc>()?;\n            let (m, _, n) = self.mkn();\n            display_error(found, exp, m, n);\n        }\n        result\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/tests/q_scale.rs",
    "content": "use crate::Scaler;\nuse crate::frame::mmm::MatMatMulKer;\nuse crate::frame::mmm::fuse::RoundingPolicy;\nuse crate::generic::rounding::ScaleShiftAndRound;\nuse crate::mmm::{FusedKerSpec, FusedSpec};\nuse proptest::prelude::*;\n\nuse super::fuse::fused_ops;\n\n#[derive(Debug, new)]\npub struct QScaleProblem<K>\nwhere\n    K: MatMatMulKer<Acc = i32>,\n{\n    pub ker: K,\n    pub c: Vec<i32>,\n    pub scaler: Scaler,\n    pub boo: std::marker::PhantomData<K>,\n}\n\npub fn arbitrary_qscale_problem<K: MatMatMulKer<Acc = i32>>(\n    ker: &K,\n) -> BoxedStrategy<QScaleProblem<K>> {\n    use RoundingPolicy::*;\n    let ker = ker.clone();\n    let len = ker.mr() * ker.nr();\n    (\n        proptest::collection::vec(-20i32..20, len..=len),\n        -5i32..5,\n        prop_oneof!(Just(1f32), 0f32..1f32),\n        proptest::prop_oneof![\n            Just(Zero),\n            Just(Away),\n            Just(PlusInf),\n            Just(MinusInf),\n            Just(Odd),\n            Just(Even)\n        ],\n    )\n        .prop_map(move |(c, scale_pot, scale_mult, policy)| QScaleProblem {\n            ker: ker.clone(),\n            c,\n            scaler: Scaler::new(scale_mult * 2f32.powi(scale_pot), policy),\n            boo: std::marker::PhantomData,\n        })\n        .boxed()\n}\n\nimpl<K> QScaleProblem<K>\nwhere\n    K: MatMatMulKer<Acc = i32>,\n{\n    pub fn run(&self) {\n        if !self.ker.is_supported_here() {\n            return;\n        }\n        if let FusedSpec::QScale(shift, policy, mult) = self.scaler.as_fused_spec() {\n            fused_ops::<K, i32, _>(\n                &self.ker,\n                &self.c,\n                &[FusedKerSpec::QScale(shift, policy, mult)],\n                |_, _, c| c.q_scale(self.scaler),\n            )\n        } else if let FusedSpec::RoundingShiftRight(shift, policy) = self.scaler.as_fused_spec() {\n            fused_ops::<K, i32, _>(\n                &self.ker,\n                &self.c,\n                &[FusedKerSpec::RoundingShiftRight(shift, policy)],\n                |_, _, c| c.q_shr(shift, policy),\n            )\n        } else if let FusedSpec::ShiftLeft(shift) = self.scaler.as_fused_spec() {\n            fused_ops::<K, i32, _>(\n                &self.ker,\n                &self.c,\n                &[FusedKerSpec::ShiftLeft(shift)],\n                |_, _, c| c.q_shl(shift),\n            )\n        } else {\n            unreachable!()\n        }\n    }\n}\n\npub fn return_c_scale_bigpot<K>(ker: &K)\nwhere\n    K: MatMatMulKer<Acc = i32>,\n{\n    let ker = ker.clone();\n    let len = ker.mr() * ker.nr();\n    let v: Vec<i32> = (-(len as i32) / 2..).take(len).collect();\n    fused_ops::<K, i32, _>(&ker, &v, &[FusedKerSpec::ShiftLeft(1)], |_, _, c| c.q_shl(1))\n}\n\n#[macro_export]\nmacro_rules! mmm_q_scale_tests {\n    ($ker:expr) => {\n        use $crate::frame::mmm::fuse::RoundingPolicy;\n        use $crate::frame::mmm::tests::q_scale::arbitrary_qscale_problem;\n        use $crate::frame::mmm::tests::q_scale::QScaleProblem;\n        use $crate::frame::mmm::MatMatMulKer;\n        use $crate::generic::Scaler;\n        // FIXME: Scaler should be arbitrary\n        macro_rules! test_q_scale {\n            ($policy: ident) => {\n                paste! {\n                    #[test]\n                    fn [<return_q_scale_halfpos_ $policy:lower>]() {\n                        let ker = $ker;\n                        let len = (ker.mr() * ker.nr()) as i64;\n                        let v = (0..len).map(|i| (i - len / 2) as i32).collect();\n                        QScaleProblem::new(ker.clone(), v, Scaler::new(0.5f32, RoundingPolicy::$policy)).run()\n                    }\n\n                    #[test]\n                    fn [<return_q_scale_halfneg_ $policy:lower>]() {\n                        let ker = $ker;\n                        let len = (ker.mr() * ker.nr()) as i64;\n                        let v = (0..len).map(|i| (i - len / 2) as i32).collect();\n                        QScaleProblem::new(ker.clone(), v, Scaler::new(-0.5f32, RoundingPolicy::$policy)).run()\n                    }\n\n                    #[test]\n                    fn [<return_q_scale_pot_ $policy:lower>]() {\n                        let ker = $ker;\n                        let len = (ker.mr() * ker.nr()) as i64;\n                        let v = (0..len).map(|i| (i - len / 2) as i32).collect();\n                        QScaleProblem::new(ker.clone(), v, Scaler::new(0.25f32, RoundingPolicy::$policy)).run()\n                    }\n\n                    #[test]\n                    fn [<return_q_scale_nonpot_ $policy:lower>]() {\n                        let ker = $ker;\n                        let len = (ker.mr() * ker.nr()) as i64;\n                        let v = (0..len).map(|i| (i - len / 2) as i32).collect();\n                        QScaleProblem::new(ker.clone(), v, Scaler::new(1f32 / 5., RoundingPolicy::$policy)).run()\n                    }\n\n                    #[test]\n                    fn [<return_q_scale_bigpot_ $policy:lower>]() {\n                        let ker = $ker;\n                        let len = (ker.mr() * ker.nr()) as i64;\n                        let v = (0..len).map(|i| (i - len / 2) as i32).collect();\n                        QScaleProblem::new(ker.clone(), v, Scaler::new(4f32, RoundingPolicy::$policy)).run()\n                    }\n\n                    #[test]\n                    fn [<return_q_scale_bignonpot_ $policy:lower>]() {\n                        let ker = $ker;\n                        let len = (ker.mr() * ker.nr()) as i64;\n                        let v = (0..len).map(|i| (i - len / 2) as i32).collect();\n                        QScaleProblem::new(ker.clone(), v, Scaler::new(14., RoundingPolicy::$policy)).run()\n                    }\n                }\n            }\n        }\n\n        test_q_scale!(Zero);\n        test_q_scale!(Away);\n        test_q_scale!(MinusInf);\n        test_q_scale!(PlusInf);\n        test_q_scale!(Even);\n        test_q_scale!(Odd);\n\n        proptest::proptest! {\n            #[test]\n            fn return_q_scale_prop(pb in arbitrary_qscale_problem($ker)) {\n                pb.run()\n            }\n        }\n\n        #[test]\n        fn return_c_scale_bigpot() {\n            $crate::frame::mmm::tests::q_scale::return_c_scale_bigpot::<_>($ker)\n        }\n    };\n}\n"
  },
  {
    "path": "linalg/src/frame/mmm/tests/store.rs",
    "content": "use crate::LADatum;\nuse crate::frame::mmm::fuse::FusedKerSpec;\nuse crate::frame::mmm::storage::*;\nuse crate::frame::mmm::tests::display_error;\nuse crate::frame::mmm::*;\nuse num_traits::Bounded;\nuse tract_data::internal::*;\nuse tract_itertools::Itertools;\nuse tract_ndarray::Axis;\n\n#[macro_export]\nmacro_rules! mmm_store_test {\n    ($ker:expr, $tc:ident) => {\n        paste! {\n            mod [<store_$tc>] {\n                #[allow(unused_imports)]\n                use tract_data::prelude::f16;\n                use $crate::frame::mmm::tests::store::StoreLayout;\n\n                #[test] fn store_zeros() {\n                    $crate::frame::mmm::tests::store::store_zeros::<_,$tc,_>($ker);\n                }\n\n                #[test] fn store_col_major() {\n                    $crate::frame::mmm::tests::store::store_pattern::<_,$tc,_>($ker, StoreLayout::ColMajor);\n                }\n\n                #[test] fn store_row_major() {\n                    $crate::frame::mmm::tests::store::store_pattern::<_,$tc,_>($ker, StoreLayout::RowMajor);\n                }\n\n                #[test] fn store_arbitrary() {\n                    $crate::frame::mmm::tests::store::store_pattern::<_,$tc,_>($ker, StoreLayout::Arbitrary);\n                }\n            }\n        }\n    };\n}\n\npub fn mmm_stride_storage<T: Copy>(v: &[T], rsc: usize) -> OutputStoreKer {\n    OutputStoreKer {\n        ptr: v.as_ptr() as _,\n        row_byte_stride: (std::mem::size_of::<T>() * rsc) as isize,\n        col_byte_stride: std::mem::size_of::<T>() as isize,\n        item_size: std::mem::size_of::<T>(),\n    }\n}\n\npub fn store_zeros<K, TC, TI>(ker: &K)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TC: LADatum,\n    TI: LADatum + Bounded + PartialEq,\n{\n    if !ker.is_supported_here() {\n        return;\n    }\n    let v = vec![TC::max_value(); ker.mr() * ker.nr()];\n    let c = mmm_stride_storage(&v, ker.nr());\n    let non_linear = tvec![FusedKerSpec::Clear, FusedKerSpec::Store(c), FusedKerSpec::Done];\n    let err = ker.kernel(&non_linear);\n    assert_eq!(err, 0);\n    let expected = vec![TC::zero(); v.len()];\n    display_error(&v, &expected, ker.mr(), ker.nr());\n    assert_eq!(v, expected);\n}\n\npub enum StoreLayout {\n    ColMajor,\n    RowMajor,\n    Arbitrary,\n}\n\npub fn store_pattern<K, TC, TI>(ker: &K, layout: StoreLayout)\nwhere\n    K: MatMatMulKer<Acc = TI>,\n    TC: LADatum,\n    TI: LADatum + Bounded + PartialEq,\n{\n    if !ker.is_supported_here() {\n        return;\n    }\n    let (mr, nr) = (ker.mr(), ker.nr());\n    let pattern = tensor1(&(0..).take(mr * nr).collect_vec())\n        .cast_to::<TI>()\n        .unwrap()\n        .into_owned()\n        .into_shape(&[mr, nr])\n        .unwrap();\n    let pattern_aligned = Blob::from_bytes_alignment(pattern.as_bytes(), 128).unwrap();\n    let pattern_col_major = pattern.clone().permute_axes(&[1, 0]).unwrap();\n    let pattern_col_major_aligned =\n        Blob::from_bytes_alignment(pattern_col_major.as_bytes(), 128).unwrap();\n    let size_of_tc = std::mem::size_of::<TC>();\n    let (row_stride, col_stride, result_size) = match layout {\n        StoreLayout::RowMajor => (nr, 1, mr * nr),\n        StoreLayout::ColMajor => (1, mr, mr * nr),\n        // like row major, but storing every other third column\n        StoreLayout::Arbitrary => (nr * 3, 3, mr * nr * 3),\n    };\n    let mut result = tensor0(TC::max_value()).broadcast_to_shape(&[result_size]).unwrap();\n    let non_linear = tvec![\n        FusedKerSpec::LoadTile(\n            pattern_col_major_aligned.as_ptr() as *const TI,\n            pattern_aligned.as_ptr() as *const TI,\n        ),\n        FusedKerSpec::Store(OutputStoreKer {\n            ptr: result.as_bytes_mut().as_mut_ptr(),\n            row_byte_stride: (size_of_tc * row_stride) as isize,\n            col_byte_stride: (size_of_tc * col_stride) as isize,\n            item_size: size_of_tc,\n        }),\n        FusedKerSpec::Done\n    ];\n    let err = ker.kernel(&non_linear);\n    assert_eq!(err, 0);\n    let expected = pattern.cast_to::<TC>().unwrap().into_owned();\n    let result = match layout {\n        StoreLayout::RowMajor => result,\n        StoreLayout::ColMajor => {\n            result.into_shape(&[ker.nr(), ker.mr()]).unwrap().permute_axes(&[1, 0]).unwrap()\n        }\n        StoreLayout::Arbitrary => result\n            .into_plain_array::<TC>()\n            .unwrap()\n            .into_shape_with_order((mr, nr, 3))\n            .unwrap()\n            .index_axis_move(Axis(2), 0)\n            .into_tensor(),\n    };\n    let expected = expected.try_as_plain().unwrap().as_slice::<TC>().unwrap();\n    let result = result.try_as_plain().unwrap().as_slice::<TC>().unwrap();\n    display_error(result, expected, ker.mr(), ker.nr());\n    assert_eq!(result, expected);\n}\n"
  },
  {
    "path": "linalg/src/frame/mod.rs",
    "content": "#[macro_use]\npub mod block_quant;\n#[macro_use]\npub mod element_wise;\npub mod element_wise_helper;\n#[macro_use]\npub mod unicast;\n#[macro_use]\npub mod by_scalar;\n#[macro_use]\npub mod leaky_relu;\n#[macro_use]\npub mod lut;\n#[macro_use]\npub mod mmm;\n#[macro_use]\npub mod pack;\n#[macro_use]\npub mod reduce;\n#[macro_use]\npub mod sigmoid;\n#[macro_use]\npub mod tanh;\n#[macro_use]\npub mod weights;\n"
  },
  {
    "path": "linalg/src/frame/pack.rs",
    "content": "use std::alloc::Layout;\nuse std::fmt::{Debug, Display};\nuse std::marker::PhantomData;\nuse std::ops::Range;\nuse tract_data::internal::*;\n\nuse crate::mmm::{\n    EagerPackedInput, MMMInputFormat, MMMInputValue, PackedExoticFact, PackedMatrixStorage,\n};\n\nuse crate::WeightType;\n\n#[derive(Clone, Eq, PartialEq, Hash)]\npub struct PackedFormat {\n    pub dt: DatumType,\n    pub r: usize,\n    pub alignment_bytes: usize,\n    pub end_padding_record: usize,\n}\n\nimpl MMMInputFormat for PackedFormat {\n    fn prepare_tensor(&self, t: &Tensor, k_axis: usize, mn_axis: usize) -> TractResult<Tensor> {\n        let packed = PackedFormat::pack_tensor(self, t, k_axis, mn_axis)?;\n        Ok(PackedMatrixStorage::new(packed).into_tensor(t.datum_type()))\n    }\n\n    fn prepare_one(\n        &self,\n        t: &Tensor,\n        k_axis: usize,\n        mn_axis: usize,\n    ) -> TractResult<Box<dyn MMMInputValue>> {\n        PackedFormat::pack_tensor(self, t, k_axis, mn_axis)\n    }\n\n    fn precursor(&self) -> WeightType {\n        WeightType::Plain(self.dt)\n    }\n\n    fn r(&self) -> usize {\n        self.r\n    }\n\n    fn k_alignment(&self) -> usize {\n        1\n    }\n\n    #[allow(clippy::collapsible_if)]\n    fn merge_with<'o, 'a: 'o, 'b: 'o>(\n        &'a self,\n        other: &'b dyn MMMInputFormat,\n    ) -> Option<&'o dyn MMMInputFormat> {\n        if let Some(other) = other.downcast_ref::<PackedFormat>() {\n            if self.r == other.r && self.dt == other.dt {\n                if self.alignment_bytes % other.alignment_bytes == 0\n                    && self.end_padding_record >= other.end_padding_record\n                {\n                    return Some(self);\n                }\n                if other.alignment_bytes % self.alignment_bytes == 0\n                    && other.end_padding_record >= self.end_padding_record\n                {\n                    return Some(other);\n                }\n            }\n        }\n        None\n    }\n\n    fn mem_size(&self, k: TDim, mn: TDim) -> TDim {\n        self.len(k, mn) * self.dt.size_of()\n    }\n\n    fn extract_at_mn_f16(\n        &self,\n        data: &EagerPackedInput,\n        mn: usize,\n        slice: &mut [f16],\n    ) -> TractResult<()> {\n        ensure!(data.format().dyn_eq(self));\n        ensure!(self.len(data.k(), data.mn()) * self.dt.size_of() == data.packed.len());\n        unsafe {\n            let ptr = data.packed.as_ptr().add(\n                (self.single_panel_len(data.k()) * (mn / self.r) + mn % self.r) * self.dt.size_of(),\n            );\n            for (i, slot) in slice.iter_mut().enumerate() {\n                let ptr = ptr.add(i * self.dt.size_of() * self.r);\n                *slot = if self.dt == f16::datum_type() {\n                    *(ptr as *const f16)\n                } else if self.dt == f32::datum_type() {\n                    f16::from_f32(*(ptr as *const f32))\n                } else {\n                    bail!(\"Unexpected DT {:?}\", self.dt)\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn extract_at_mn_f32(\n        &self,\n        data: &EagerPackedInput,\n        mn: usize,\n        slice: &mut [f32],\n    ) -> TractResult<()> {\n        ensure!(data.format().dyn_eq(self));\n        ensure!(self.len(data.k(), data.mn()) * self.dt.size_of() == data.packed.len());\n        unsafe {\n            let ptr = data.packed.as_ptr().add(\n                (self.single_panel_len(data.k()) * (mn / self.r) + mn % self.r) * self.dt.size_of(),\n            );\n            for (i, slot) in slice.iter_mut().enumerate() {\n                let ptr = ptr.add(i * self.dt.size_of() * self.r);\n                *slot = if self.dt == f16::datum_type() {\n                    (*(ptr as *const f16)).to_f32()\n                } else if self.dt == f32::datum_type() {\n                    *(ptr as *const f32)\n                } else {\n                    bail!(\"Unexpected DT {:?}\", self.dt)\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl Display for PackedFormat {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Packed{:?}[{}]\", self.dt, self.r)\n    }\n}\n\nimpl Debug for PackedFormat {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"Packed{:?}[{}]@{}+{}\",\n            self.dt, self.r, self.alignment_bytes, self.end_padding_record\n        )\n    }\n}\n\nimpl PackedFormat {\n    pub const fn new(dt: DatumType, nr: usize, alignment_bytes: usize) -> PackedFormat {\n        PackedFormat { dt, r: nr, alignment_bytes, end_padding_record: 1 }\n    }\n\n    pub const fn with_end_padding_record(self, end_padding_record: usize) -> Self {\n        PackedFormat { end_padding_record, ..self }\n    }\n\n    #[inline]\n    pub fn align(self, alignment: usize) -> Self {\n        Self { alignment_bytes: alignment, ..self }\n    }\n\n    #[inline]\n    pub fn alignment(&self) -> usize {\n        self.alignment_bytes\n    }\n\n    #[inline]\n    pub fn panel_width(&self) -> usize {\n        self.r\n    }\n\n    #[inline]\n    pub fn len<D: DimLike>(&self, k: D, n: D) -> D {\n        n.divceil(self.r) * self.single_panel_len(k)\n    }\n\n    #[inline]\n    pub fn single_panel_len<D: DimLike>(&self, k: D) -> D {\n        ((k + self.end_padding_record) * self.r).divceil(self.alignment()) * self.alignment()\n    }\n\n    #[inline]\n    pub fn single_panel_layout(&self, k: usize, item_size: usize) -> Layout {\n        Layout::from_size_align(self.single_panel_len(k) * item_size, self.alignment()).unwrap()\n    }\n\n    pub fn pack_tensor(\n        &self,\n        t: &Tensor,\n        k_axis: usize,\n        mn_axis: usize,\n    ) -> TractResult<Box<dyn MMMInputValue>> {\n        ensure!(t.datum_type().is_copy());\n        ensure!(\n            t.datum_type().unquantized() == self.dt.unquantized(),\n            \"Attempting to pack for {self} tensor {t:?}\"\n        );\n        let k = t.shape()[k_axis];\n        let mn = t.shape()[mn_axis];\n        let packed_len = self.len(k, mn);\n        let panel_len = self.single_panel_len(k);\n        let panel_bytes = panel_len * t.datum_type().size_of();\n        let strides = t.strides();\n        unsafe {\n            let mut packed = Blob::new_for_size_and_align(\n                t.datum_type().size_of() * packed_len,\n                self.alignment_bytes,\n            );\n            if cfg!(debug_assertions) {\n                packed.as_bytes_mut().fill(0u8);\n            }\n            dispatch_copy!(Self::pack_t(t.datum_type())(\n                self,\n                packed.as_mut_ptr() as _,\n                t.as_ptr_unchecked(),\n                mn,\n                strides[k_axis],\n                strides[mn_axis],\n                0..k,\n                0..mn\n            ));\n            Ok(Box::new(EagerPackedInput {\n                fact: PackedExoticFact { format: Box::new(self.clone()), mn: mn.to_dim(), k },\n                packed: packed.into(),\n                panel_bytes,\n                mn,\n            }))\n        }\n    }\n\n    pub fn pack_tensor_view(\n        &self,\n        t: &TensorView,\n        k_axis: usize,\n        mn_axis: usize,\n    ) -> TractResult<Box<dyn MMMInputValue>> {\n        ensure!(\n            t.datum_type().unquantized() == self.dt.unquantized(),\n            \"Attempting to pack for {self} tensor view {t:?}\"\n        );\n        let k = t.shape()[k_axis];\n        let mn = t.shape()[mn_axis];\n        let packed_len = self.len(k, mn);\n        let panel_len = self.single_panel_len(k);\n        let panel_bytes = panel_len * t.datum_type().size_of();\n        let strides = t.strides();\n        unsafe {\n            let mut packed = Blob::new_for_size_and_align(\n                t.datum_type().size_of() * packed_len,\n                self.alignment_bytes,\n            );\n            if cfg!(debug_assertions) {\n                packed.as_bytes_mut().fill(0u8);\n            }\n            dispatch_copy!(Self::pack_t(t.datum_type())(\n                self,\n                packed.as_mut_ptr() as _,\n                t.as_ptr_unchecked(),\n                mn,\n                strides[k_axis],\n                strides[mn_axis],\n                0..k,\n                0..mn\n            ));\n            Ok(Box::new(EagerPackedInput {\n                fact: PackedExoticFact { format: Box::new(self.clone()), mn: mn.to_dim(), k },\n                packed: packed.into(),\n                panel_bytes,\n                mn,\n            }))\n        }\n    }\n\n    pub unsafe fn pack<'a, 'b>(\n        &self,\n        pb: impl std::borrow::BorrowMut<TensorView<'a>>,\n        b: impl std::borrow::Borrow<TensorView<'b>>,\n        k_axis: usize,\n        mn_axis: usize,\n    ) {\n        let k = b.borrow().shape()[k_axis];\n        let mn = b.borrow().shape()[mn_axis];\n        unsafe { self.pack_segment(pb, b, k_axis, mn_axis, 0..k, 0..mn) };\n    }\n\n\n    #[allow(clippy::too_many_arguments)]\n    #[rustfmt::skip]\n    pub unsafe fn pack_t<T: Datum + Copy>(\n        &self,\n        pb: *mut T,\n        b: *const T,\n        mn: usize,\n        k_stride: isize,\n        mn_stride: isize,\n        k_range: Range<usize>,\n        mn_range: Range<usize>,\n        ) { unsafe {\n        if k_range.len() == 0 || mn_range.len() == 0 {\n            return\n        }\n        if self.r == 1 && k_stride == 1 && mn == 1 {\n            pb.copy_from_nonoverlapping(b.add(k_range.start), k_range.len())\n        } else if mn_stride == 1 {\n            let size_of = T::datum_type().size_of();\n            let rbytes = self.r * size_of;\n            let mn_valid_end = mn_range.end.min(mn);\n            let mn_range_bytes = mn_range.start * size_of..mn_valid_end * size_of;\n            let k_stride_bytes = k_stride * size_of as isize;\n            let bb = b as *const u8;\n            let pbb = pb as *mut u8;\n            let panel_len = self.single_panel_len(k_range.len()) * size_of;\n            match rbytes {\n                16 => pack_mn_major::<[u8; 16]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),\n                24 => pack_mn_major::<[u8; 24]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),\n                32 => pack_mn_major::<[u8; 32]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),\n                48 => pack_mn_major::<[u8; 48]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),\n                64 => pack_mn_major::<[u8; 64]>(bb, pbb, panel_len, k_stride_bytes, mn_range_bytes, k_range),\n                _ => {\n                    let mut packer = self.write_with_k_outer(pb, k_range.len(), mn_range.len());\n                    for k in k_range {\n                        for x in mn_range.start..mn_valid_end {\n                            packer.write(*b.offset(x as isize + k_stride * k as isize))\n                        }\n                        for _x in mn_valid_end..mn_range.end {\n                            packer.write(T::default())\n                        }\n                    }\n                }\n            }\n        } else if k_stride == 1 {\n            let mut packer = self.write_with_k_inner(pb, k_range.len(), mn);\n            let mn_valid_end = mn_range.end.min(mn);\n            for x in mn_range.start..mn_valid_end {\n                for k in k_range.clone() {\n                    packer.write(*b.offset(x as isize * mn_stride + k as isize))\n                }\n            }\n            // just ignore invalid mn_range\n        } else {\n            let mut packer = self.write_with_k_outer(pb, k_range.len(), mn);\n            let mn_valid_end = mn_range.end.min(mn);\n            for k in k_range {\n                for x in mn_range.start..mn_valid_end {\n                    packer.write(*b.offset(x as isize * mn_stride + k_stride * k as isize))\n                }\n                for _x in mn_valid_end..mn_range.end {\n                    packer.write(T::default())\n                }\n            }\n        }\n    }}\n\n    #[inline]\n    pub unsafe fn pack_segment<'a, 'b>(\n        &self,\n        mut pb: impl std::borrow::BorrowMut<TensorView<'a>>,\n        b: impl std::borrow::Borrow<TensorView<'b>>,\n        k_axis: usize,\n        mn_axis: usize,\n        k_range: Range<usize>,\n        mn_range: Range<usize>,\n    ) {\n        debug_assert!(pb.borrow().len() >= self.len(k_range.len(), mn_range.len()));\n        let pb = pb.borrow_mut();\n        let b = b.borrow();\n        let dt = pb.datum_type();\n        unsafe {\n            dispatch_copy!(Self::pack_t(dt)(\n                self,\n                pb.as_ptr_mut_unchecked(),\n                b.as_ptr_unchecked(),\n                b.shape()[mn_axis],\n                b.strides()[k_axis],\n                b.strides()[mn_axis],\n                k_range,\n                mn_range\n            ));\n        }\n    }\n\n    pub fn write_with_k_outer<'p, T: Copy + Debug>(\n        &self,\n        pb: *mut T,\n        k: usize,\n        mn: usize,\n    ) -> KOutWriter<'p, T> {\n        KOutWriter::new(pb, self.r, self.single_panel_len(k), mn, k)\n    }\n\n    pub fn write_single_panel_with_k_outer<'p, T: Copy + Debug>(\n        &self,\n        pb: *mut T,\n    ) -> KOutSinglePanelWriter<'p, T> {\n        KOutSinglePanelWriter::new(pb)\n    }\n\n    pub fn write_with_k_inner<'p, T: Copy + Debug>(\n        &self,\n        pb: *mut T,\n        k: usize,\n        mn: usize,\n    ) -> KInWriter<'p, T> {\n        let panel_len = self.single_panel_len(k);\n        KInWriter::new(pb, panel_len, self.r, mn, k)\n    }\n}\n\npub trait PackingWriter<T: Copy> {\n    fn write(&mut self, t: T);\n}\n\n#[derive(Debug)]\npub struct KOutSinglePanelWriter<'p, T>\nwhere\n    T: Copy + std::fmt::Debug,\n{\n    ptr: *mut T,\n    _phantom: PhantomData<&'p T>,\n}\n\nimpl<'p, T> KOutSinglePanelWriter<'p, T>\nwhere\n    T: Copy + std::fmt::Debug,\n{\n    pub fn new(ptr: *mut T) -> KOutSinglePanelWriter<'p, T> {\n        KOutSinglePanelWriter { ptr, _phantom: PhantomData }\n    }\n}\n\nimpl<T> PackingWriter<T> for KOutSinglePanelWriter<'_, T>\nwhere\n    T: Copy + std::fmt::Debug,\n{\n    #[inline(always)]\n    fn write(&mut self, t: T) {\n        unsafe {\n            *self.ptr = t;\n            self.ptr = self.ptr.offset(1);\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct KOutWriter<'p, T>\nwhere\n    T: Copy + std::fmt::Debug,\n{\n    ptr: *mut T,\n    panels: usize,\n    panel_width: usize,\n    last_panel_width: usize,\n    remain: usize,\n    current_panel: usize,\n    next_panel: isize,\n    next_lane: isize,\n    _phantom: PhantomData<&'p T>,\n}\n\nimpl<'p, T> KOutWriter<'p, T>\nwhere\n    T: Copy + std::fmt::Debug,\n{\n    pub fn new(\n        ptr: *mut T,\n        panel_width: usize,\n        panel_len: usize,\n        mn: usize,\n        _k: usize,\n    ) -> KOutWriter<'p, T> {\n        let panels = mn.divceil(panel_width);\n        let last_panel_width = mn - (panels - 1) * panel_width;\n        KOutWriter {\n            ptr,\n            panels,\n            panel_width,\n            last_panel_width,\n            remain: if panels > 1 { panel_width } else { last_panel_width },\n            current_panel: 0,\n            next_panel: (panel_len - panel_width) as isize,\n            next_lane: (panel_width - last_panel_width) as isize\n                - (panel_len * (panels - 1)) as isize,\n            _phantom: PhantomData,\n        }\n    }\n}\n\nimpl<T> PackingWriter<T> for KOutWriter<'_, T>\nwhere\n    T: Copy + std::fmt::Debug,\n{\n    #[inline(always)]\n    fn write(&mut self, t: T) {\n        unsafe {\n            *self.ptr = t;\n            self.remain -= 1;\n            self.ptr = self.ptr.offset(1);\n            if self.remain == 0 {\n                self.current_panel += 1;\n                if self.current_panel == self.panels {\n                    self.ptr = self.ptr.offset(self.next_lane);\n                    self.current_panel = 0;\n                } else {\n                    self.ptr = self.ptr.offset(self.next_panel);\n                }\n                if self.current_panel == self.panels - 1 {\n                    self.remain = self.last_panel_width;\n                } else {\n                    self.remain = self.panel_width;\n                }\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct KInWriter<'p, T>\nwhere\n    T: Copy + Debug,\n{\n    ptr: *mut T,\n    k: usize,\n    panels: usize,\n    panel_width: usize,\n    last_panel_width: usize,\n    remain_on_k: usize,\n    remain_on_mn: usize,\n    current_panel: usize,\n    next_mn_offset: isize,\n    next_panel_offset: isize,\n    _phantom: PhantomData<&'p T>,\n}\n\nimpl<'p, T> KInWriter<'p, T>\nwhere\n    T: Copy + Debug,\n{\n    pub fn new(\n        ptr: *mut T,\n        panel_len: usize,\n        panel_width: usize,\n        mn: usize,\n        k: usize,\n    ) -> KInWriter<'p, T> {\n        let panels = mn.divceil(panel_width);\n        let last_panel_width = mn - (panels - 1) * panel_width;\n        KInWriter {\n            ptr,\n            k,\n            panels,\n            panel_width,\n            last_panel_width,\n            remain_on_k: k,\n            remain_on_mn: if panels == 1 { last_panel_width } else { panel_width },\n            current_panel: 0,\n            next_mn_offset: 1 - (k * panel_width) as isize,\n            next_panel_offset: panel_len as isize - (k * panel_width + panel_width - 1) as isize,\n            //                 ^ next panel     ^    ^ rewind left ^   ^ rewind up   ^\n            _phantom: PhantomData,\n        }\n    }\n}\n\nimpl<T> PackingWriter<T> for KInWriter<'_, T>\nwhere\n    T: Copy + std::fmt::Debug,\n{\n    #[inline(always)]\n    fn write(&mut self, t: T) {\n        unsafe {\n            *self.ptr = t;\n            self.remain_on_k -= 1;\n            self.ptr = self.ptr.add(self.panel_width);\n            if self.remain_on_k == 0 {\n                self.remain_on_k = self.k;\n                self.remain_on_mn -= 1;\n                if self.remain_on_mn > 0 {\n                    self.ptr = self.ptr.offset(self.next_mn_offset);\n                } else {\n                    self.ptr = self.ptr.offset(self.next_panel_offset);\n                    self.current_panel += 1;\n                    if self.current_panel == self.panels - 1 {\n                        self.remain_on_mn = self.last_panel_width;\n                    } else {\n                        self.remain_on_mn = self.panel_width;\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[inline(never)]\nunsafe fn pack_mn_major<Chunk: Copy>(\n    b: *const u8,\n    packed: *mut u8,\n    panel_len: usize,\n    k_stride_bytes: isize,\n    mn_range_bytes: Range<usize>,\n    k_range: Range<usize>,\n) {\n    unsafe {\n        let mnr = std::mem::size_of::<Chunk>();\n        let full_panes = mn_range_bytes.len() / mnr;\n        let partial_pane = mn_range_bytes.len() % mnr;\n        for k in 0..k_range.len() {\n            let mut p_row = packed.add(k * mnr);\n            let mut b_row = b.offset(\n                (k_range.start + k) as isize * k_stride_bytes + mn_range_bytes.start as isize,\n            );\n            for _ in 0..full_panes {\n                p_row.copy_from_nonoverlapping(b_row, mnr);\n                p_row = p_row.add(panel_len);\n                b_row = b_row.add(mnr);\n            }\n            if partial_pane > 0 {\n                p_row.copy_from_nonoverlapping(b_row, partial_pane);\n            }\n        }\n    }\n}\n\npub trait Packing {\n    fn packing(r: usize) -> PackedFormat;\n}\n\nimpl<D: Datum> Packing for D {\n    fn packing(r: usize) -> PackedFormat {\n        PackedFormat::new(Self::datum_type(), r, vector_size())\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use std::ops::Range;\n\n    use proptest::prelude::*;\n    use tract_data::internal::num_integer::Integer;\n    use tract_data::internal::tract_ndarray::Zip;\n    use tract_data::internal::*;\n    use tract_ndarray::prelude::*;\n\n    #[derive(Debug)]\n    struct PackProblem {\n        k: usize,\n        mn: usize,\n        is_a: bool,\n        r: usize,\n        k_range: Range<usize>,\n        mn_range: Range<usize>,\n        align_panel: usize,\n    }\n\n    impl PackProblem {\n        fn input(&self) -> Array2<u32> {\n            let shape = if self.is_a { (self.mn, self.k) } else { (self.k, self.mn) };\n            let data = (0..(self.k * self.mn) as u32).collect();\n            Array2::from_shape_vec(shape, data).unwrap()\n        }\n\n        fn packer(&self) -> Array2<u32> {\n            let panels = self.mn_range.len().divceil(self.r);\n            let packer = super::PackedFormat::new(u32::datum_type(), self.r, self.align_panel)\n                .with_end_padding_record(0);\n            let input = self.input().into_tensor();\n            let panel_len = packer.single_panel_len(self.k_range.len());\n            let mut output =\n                Tensor::zero::<u32>(&[packer.len(self.k_range.len(), self.mn_range.len())])\n                    .unwrap();\n            unsafe {\n                packer.pack_segment(\n                    output.view_mut(),\n                    input.view(),\n                    self.is_a as usize,\n                    !self.is_a as usize,\n                    self.k_range.clone(),\n                    self.mn_range.clone(),\n                )\n            };\n            output\n                .into_plain_array::<u32>()\n                .unwrap()\n                .into_shape_with_order((panels, panel_len))\n                .unwrap()\n        }\n\n        fn reference(&self) -> Array2<u32> {\n            let input = self.input();\n            let panels = self.mn_range.len().divceil(self.r);\n            let len = Integer::next_multiple_of(&(self.k_range.len() * self.r), &self.align_panel);\n            Array2::from_shape_fn([panels, len], |(panel, z)| {\n                let k = z / self.r;\n                let x = z % self.r;\n                let mn = panel * self.r + x + self.mn_range.start;\n                let k = k + self.k_range.start;\n                let coords = if self.is_a { (mn, k) } else { (k, mn) };\n                *input.get(coords).unwrap_or(&0)\n            })\n        }\n\n        fn valid(&self) -> Array2<bool> {\n            let panels = self.mn_range.len().divceil(self.r);\n            let len = Integer::next_multiple_of(&(self.k_range.len() * self.r), &self.align_panel);\n            Array2::from_shape_fn([panels, len], |(panel, z)| {\n                let k = z / self.r;\n                let x = z % self.r;\n                let k = k + self.k_range.start;\n                let mn = panel * self.r + x + self.mn_range.start;\n                k < self.k_range.end.min(self.k) && mn < self.mn_range.end.min(self.mn)\n            })\n        }\n\n        fn check(&self) {\n            let mut packer = self.packer();\n            let mut reference = self.reference();\n            let valid = self.valid();\n            Zip::from(&mut packer).and(&valid).for_each(|p, v| *p = if *v { *p } else { -1 as _ });\n            Zip::from(&mut reference)\n                .and(&valid)\n                .for_each(|p, v| *p = if *v { *p } else { -1 as _ });\n            assert_eq!(packer, reference);\n        }\n    }\n\n    impl Arbitrary for PackProblem {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<PackProblem>;\n        fn arbitrary_with(_args: ()) -> Self::Strategy {\n            (any::<bool>(), 1usize..9, 1usize..20, 1usize..20)\n                .prop_flat_map(|(is_a, r, k, mn)| {\n                    (\n                        Just((is_a, r, k, mn)),\n                        sub_range_strat(0..k),\n                        sub_range_strat(0..mn),\n                        1usize..5,\n                    )\n                })\n                .prop_map(|((is_a, r, k, mn), k_range, mn_range, align_panel)| PackProblem {\n                    k,\n                    mn,\n                    is_a,\n                    r,\n                    k_range,\n                    mn_range,\n                    align_panel,\n                })\n                .boxed()\n        }\n    }\n\n    fn sub_range_strat(range: Range<usize>) -> BoxedStrategy<Range<usize>> {\n        (0..range.len())\n            .prop_flat_map(|cropped| (Just(cropped), 0..=cropped))\n            .prop_map(move |(cropped, left)| range.start + left..range.end - (cropped - left))\n            .boxed()\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn prop(pb in any::<PackProblem>()) {\n            pb.check();\n        }\n\n        #[test]\n        fn subrange_prop(_range in sub_range_strat(0..20)) {\n        }\n\n    }\n\n    #[test]\n    fn simple_b_1() {\n        PackProblem {\n            k: 2,\n            mn: 1,\n            is_a: false,\n            r: 1,\n            k_range: 0..2,\n            mn_range: 0..1,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn simple_b_2() {\n        PackProblem {\n            k: 2,\n            mn: 2,\n            is_a: false,\n            r: 1,\n            k_range: 0..2,\n            mn_range: 0..2,\n            align_panel: 1,\n        }\n        .check()\n    }\n\n    #[test]\n    fn simple_b_3() {\n        PackProblem {\n            k: 2,\n            mn: 1,\n            is_a: false,\n            r: 4,\n            k_range: 0..2,\n            mn_range: 0..1,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn simple_b_4() {\n        PackProblem {\n            k: 1,\n            mn: 3,\n            is_a: false,\n            r: 2,\n            k_range: 0..1,\n            mn_range: 0..3,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn simple_a_1() {\n        PackProblem {\n            k: 2,\n            mn: 2,\n            is_a: true,\n            r: 1,\n            k_range: 0..2,\n            mn_range: 0..2,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn simple_a_2() {\n        PackProblem {\n            k: 2,\n            mn: 3,\n            is_a: true,\n            r: 2,\n            k_range: 0..2,\n            mn_range: 0..3,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn range_k_0() {\n        PackProblem {\n            k: 2,\n            mn: 1,\n            is_a: false,\n            r: 1,\n            k_range: 1..2,\n            mn_range: 0..1,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn range_k_1() {\n        PackProblem {\n            k: 2,\n            mn: 2,\n            is_a: false,\n            r: 1,\n            k_range: 0..2,\n            mn_range: 0..1,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn range_k_2() {\n        PackProblem {\n            k: 2,\n            mn: 1,\n            is_a: false,\n            r: 6,\n            k_range: 1..2,\n            mn_range: 0..1,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn range_mn_0() {\n        PackProblem {\n            k: 1,\n            mn: 2,\n            is_a: false,\n            r: 2,\n            k_range: 0..1,\n            mn_range: 0..1,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn range_b_4() {\n        PackProblem {\n            k: 1,\n            mn: 2,\n            is_a: false,\n            r: 6,\n            k_range: 0..1,\n            mn_range: 1..2,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn range_b_5() {\n        PackProblem {\n            k: 1,\n            mn: 7,\n            is_a: false,\n            r: 6,\n            k_range: 0..1,\n            mn_range: 1..7,\n            align_panel: 1,\n        }\n        .check();\n    }\n\n    #[test]\n    fn align_a_1() {\n        PackProblem {\n            k: 2,\n            mn: 2,\n            is_a: true,\n            r: 1,\n            k_range: 0..1,\n            mn_range: 0..2,\n            align_panel: 2,\n        }\n        .check();\n    }\n\n    #[test]\n    fn align_b_1() {\n        PackProblem {\n            k: 1,\n            mn: 1,\n            is_a: false,\n            r: 1,\n            k_range: 0..1,\n            mn_range: 0..1,\n            align_panel: 2,\n        }\n        .check();\n    }\n\n    #[test]\n    fn align_b_2() {\n        PackProblem {\n            k: 3,\n            mn: 1,\n            is_a: false,\n            r: 1,\n            k_range: 0..3,\n            mn_range: 0..1,\n            align_panel: 2,\n        }\n        .check();\n    }\n\n    #[test]\n    fn align_b_3() {\n        PackProblem {\n            k: 1,\n            mn: 1,\n            is_a: false,\n            r: 3,\n            k_range: 0..1,\n            mn_range: 0..1,\n            align_panel: 2,\n        }\n        .check();\n    }\n\n    #[test]\n    fn align_b_4() {\n        PackProblem {\n            k: 2,\n            mn: 1,\n            is_a: false,\n            r: 1,\n            k_range: 0..1,\n            mn_range: 0..1,\n            align_panel: 2,\n        }\n        .check();\n    }\n\n    #[test]\n    fn align_b_5() {\n        PackProblem {\n            k: 1,\n            mn: 5,\n            is_a: false,\n            r: 4,\n            k_range: 0..1,\n            mn_range: 0..5,\n            align_panel: 3,\n        }\n        .check();\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/reduce/max.rs",
    "content": "#[cfg(test)]\n#[macro_use]\npub mod test {\n    use crate::LADatum;\n    use crate::frame::reduce::ReduceKer;\n    use num_traits::{AsPrimitive, Float};\n    use proptest::test_runner::TestCaseResult;\n\n    #[macro_export]\n    macro_rules! max_frame_tests {\n        ($cond:expr, $t: ty, $ker:ty) => {\n            proptest::proptest! {\n                #[test]\n                fn prop(xs in proptest::collection::vec(-25f32..25.0, 0..100)) {\n                    if $cond {\n                        $crate::frame::reduce::max::test::test_max::<$ker, $t>(&*xs).unwrap()\n                    }\n                }\n            }\n\n            #[test]\n            fn empty() {\n                if $cond {\n                    $crate::frame::reduce::max::test::test_max::<$ker, $t>(&[]).unwrap()\n                }\n            }\n        };\n    }\n\n    pub fn test_max<K: ReduceKer<T>, T: LADatum + Float>(values: &[f32]) -> TestCaseResult\n    where\n        f32: AsPrimitive<T>,\n    {\n        crate::setup_test_logger();\n        let values: Vec<T> = values.iter().copied().map(|x| x.as_()).collect();\n        crate::frame::reduce::test::test_reduce::<K, _>(\n            &values,\n            <T as Float>::min_value(),\n            |a, b| a.max(b),\n        )\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/reduce/mod.rs",
    "content": "pub mod max;\npub mod softmax;\npub mod sum;\n\nuse std::fmt::Debug;\nuse std::marker::PhantomData;\n\nuse tract_data::TractResult;\n\nuse crate::LADatum;\n\nuse super::element_wise_helper::{map_reduce_slice_with_alignment, reduce_slice_with_alignment};\n\nmacro_rules! reduce_impl_wrap {\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $params: ty, $neutral: expr, $run: item, $reduce_two: item) => {\n        paste! {\n            #[derive(Copy, Clone, Debug)]\n            #[allow(non_camel_case_types)]\n            pub struct $func;\n\n            impl crate::frame::reduce::ReduceKer<$ti, $params> for $func {\n                #[inline(always)]\n                fn name() -> &'static str {\n                    stringify!($func)\n                }\n                #[inline(always)]\n                fn nr() -> usize {\n                    $nr\n                }\n                #[inline(always)]\n                fn alignment_items() -> usize {\n                    $alignment_items\n                }\n                #[inline(always)]\n                fn alignment_bytes() -> usize {\n                    $alignment_items * std::mem::size_of::<$ti>()\n                }\n                #[inline(always)]\n                fn neutral() -> $ti {\n                    $neutral\n                }\n                $run\n                $reduce_two\n            }\n        }\n    };\n}\n\npub trait Reduce<T, Params = ()>: Send + Sync + Debug + dyn_clone::DynClone\nwhere\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    T: Copy + Debug + PartialEq + Send + Sync,\n{\n    fn name(&self) -> &'static str;\n    fn run(&self, vec: &[T]) -> TractResult<T> {\n        self.run_with_params(vec, Params::default())\n    }\n    fn run_with_params(&self, vec: &[T], params: Params) -> TractResult<T>;\n}\n\ndyn_clone::clone_trait_object!(<T, Params> Reduce<T, Params> where T: Copy, Params: Copy);\n\n#[derive(Debug, Clone, new)]\npub struct ReduceImpl<K, T, Params = ()>\nwhere\n    T: LADatum,\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    K: ReduceKer<T, Params> + Clone,\n{\n    phantom: PhantomData<(K, T, Params)>,\n}\n\nimpl<K, T, Params> Reduce<T, Params> for ReduceImpl<K, T, Params>\nwhere\n    T: LADatum,\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    K: ReduceKer<T, Params> + Clone,\n{\n    fn name(&self) -> &'static str {\n        K::name()\n    }\n\n    fn run_with_params(&self, vec: &[T], params: Params) -> TractResult<T> {\n        reduce_slice_with_alignment(\n            vec,\n            |data| K::run(data, params),\n            K::nr(),\n            K::alignment_bytes(),\n            K::neutral(),\n            K::reduce_two,\n        )\n    }\n}\n\npub trait ReduceKer<T, Params = ()>:\n    Send + Sync + Debug + dyn_clone::DynClone + Clone + 'static\nwhere\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    T: LADatum,\n{\n    fn name() -> &'static str;\n    fn alignment_bytes() -> usize {\n        Self::alignment_items() * T::datum_type().size_of()\n    }\n    fn alignment_items() -> usize;\n    fn nr() -> usize;\n    fn neutral() -> T;\n    fn reduce_two(a: T, b: T) -> T;\n    fn run(vec: &[T], params: Params) -> T;\n    fn red() -> Box<dyn Reduce<T, Params>> {\n        Box::new(ReduceImpl::<Self, T, Params>::new())\n    }\n}\n\n#[allow(unused_macros)]\nmacro_rules! map_reduce_impl_wrap {\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $params: ty, $map_neutral: expr, $reduce_neutral: expr, $run: item, $reduce_two: item) => {\n        paste! {\n            #[derive(Copy, Clone, Debug)]\n            #[allow(non_camel_case_types)]\n            pub struct $func;\n\n            impl crate::frame::reduce::MapReduceKer<$ti, $params> for $func {\n                #[inline(always)]\n                fn name() -> &'static str {\n                    stringify!($func)\n                }\n                #[inline(always)]\n                fn nr() -> usize {\n                    $nr\n                }\n                #[inline(always)]\n                fn alignment_items() -> usize {\n                    $alignment_items\n                }\n                #[inline(always)]\n                fn alignment_bytes() -> usize {\n                    $alignment_items * std::mem::size_of::<$ti>()\n                }\n                #[inline(always)]\n                fn map_neutral() -> $ti {\n                    $map_neutral\n                }\n                #[inline(always)]\n                fn reduce_neutral() -> $ti {\n                    $reduce_neutral\n                }\n                $run\n                $reduce_two\n            }\n        }\n    };\n}\n\npub trait MapReduce<T, Params = ()>: Send + Sync + Debug + dyn_clone::DynClone\nwhere\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    T: Copy + Debug + PartialEq + Send + Sync,\n{\n    fn name(&self) -> &'static str;\n    fn run(&self, vec: &mut [T]) -> TractResult<T> {\n        self.run_with_params(vec, Params::default())\n    }\n    fn run_with_params(&self, vec: &mut [T], params: Params) -> TractResult<T>;\n}\n\ndyn_clone::clone_trait_object!(<T, Params> MapReduce<T, Params> where T: Copy, Params: Copy);\n\n#[derive(Debug, Clone, new)]\npub struct MapReduceImpl<K, T, Params = ()>\nwhere\n    T: LADatum,\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    K: MapReduceKer<T, Params> + Clone,\n{\n    phantom: PhantomData<(K, T, Params)>,\n}\n\nimpl<K, T, Params> MapReduce<T, Params> for MapReduceImpl<K, T, Params>\nwhere\n    T: LADatum,\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    K: MapReduceKer<T, Params> + Clone,\n{\n    fn name(&self) -> &'static str {\n        K::name()\n    }\n    fn run_with_params(&self, vec: &mut [T], params: Params) -> TractResult<T> {\n        map_reduce_slice_with_alignment(\n            vec,\n            |data| K::run(data, params),\n            K::nr(),\n            K::alignment_bytes(),\n            K::map_neutral(),\n            K::reduce_neutral(),\n            K::reduce_two,\n        )\n    }\n}\n\npub trait MapReduceKer<T, Params = ()>:\n    Send + Sync + Debug + dyn_clone::DynClone + Clone + 'static\nwhere\n    Params: Copy + Send + Sync + Debug + 'static + Default,\n    T: LADatum,\n{\n    fn name() -> &'static str;\n    fn alignment_bytes() -> usize {\n        Self::alignment_items() * T::datum_type().size_of()\n    }\n    fn alignment_items() -> usize;\n    fn nr() -> usize;\n    fn map_neutral() -> T;\n    fn reduce_neutral() -> T;\n    fn reduce_two(a: T, b: T) -> T;\n    fn run(vec: &mut [T], params: Params) -> T;\n    fn red() -> Box<dyn MapReduce<T, Params>> {\n        Box::new(MapReduceImpl::<Self, T, Params>::new())\n    }\n}\n\n#[cfg(test)]\npub mod test {\n    use super::*;\n    use proptest::test_runner::{TestCaseError, TestCaseResult};\n    use tract_data::internal::*;\n    use tract_data::itertools::Itertools;\n\n    pub fn test_reduce<K: ReduceKer<T, ()>, T: LADatum>(\n        values: &[T],\n        neutral: T,\n        reference_reduce: impl Fn(T, T) -> T,\n    ) -> TestCaseResult {\n        test_reduce_params::<K, T, ()>(values, neutral, reference_reduce, ())\n    }\n\n    pub fn test_reduce_params<K: ReduceKer<T, Params>, T: LADatum, Params>(\n        values: &[T],\n        neutral: T,\n        reference_reducer: impl Fn(T, T) -> T,\n        params: Params,\n    ) -> TestCaseResult\n    where\n        Params: Copy + Send + Sync + Debug + 'static + Default,\n    {\n        crate::setup_test_logger();\n        let op = K::red();\n        let expected = values.iter().fold(neutral, |acc, i| reference_reducer(acc, *i));\n        let found = values;\n        let red = op.run_with_params(found, params).unwrap();\n        tensor0(red)\n            .close_enough(&tensor0(expected), true)\n            .map_err(|e| TestCaseError::fail(e.root_cause().to_string()))?;\n        Ok(())\n    }\n\n    pub fn test_map_reduce<K: MapReduceKer<T, ()>, T: LADatum>(\n        values: &[T],\n        map_neutral: T,\n        neutral: T,\n        reference_map: impl Fn(T) -> T,\n        reference_reduce: impl Fn(T, T) -> T,\n    ) -> TestCaseResult {\n        test_map_reduce_params::<K, T, ()>(\n            values,\n            map_neutral,\n            neutral,\n            reference_map,\n            reference_reduce,\n            (),\n        )\n    }\n\n    pub fn test_map_reduce_params<K: MapReduceKer<T, Params>, T: LADatum, Params>(\n        values: &[T],\n        _neutral: T,\n        map_neutral: T,\n        reference_map: impl Fn(T) -> T,\n        reference_reducer: impl Fn(T, T) -> T,\n        params: Params,\n    ) -> TestCaseResult\n    where\n        Params: Copy + Send + Sync + Debug + 'static + Default,\n    {\n        crate::setup_test_logger();\n        let op = K::red();\n        let mut found = values.to_vec();\n        let expected_values = values.iter().copied().map(reference_map).collect_vec();\n        let expected_reduced =\n            expected_values.iter().fold(map_neutral, |acc, i| reference_reducer(acc, *i));\n        let red = op.run_with_params(&mut found, params).unwrap();\n        tensor1(&found)\n            .close_enough(&tensor1(&expected_values), Approximation::SuperApproximate)\n            .map_err(|e| TestCaseError::fail(e.root_cause().to_string()))?;\n        tensor0(red)\n            .close_enough(&tensor0(expected_reduced), Approximation::SuperApproximate)\n            .map_err(|e| TestCaseError::fail(e.root_cause().to_string()))?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/reduce/softmax.rs",
    "content": "#[cfg(test)]\n#[macro_use]\npub mod test {\n    use crate::LADatum;\n    use crate::frame::reduce::MapReduceKer;\n    use num_traits::{AsPrimitive, Float};\n    use proptest::test_runner::TestCaseResult;\n\n    #[macro_export]\n    macro_rules! softmax_l2_frame_tests {\n        ($cond:expr, $t: ty, $ker:ty) => {\n            proptest::proptest! {\n                #[test]\n                fn prop(xs in proptest::collection::vec(-25f32..25.0, 1..100)) {\n                    if $cond {\n                        $crate::frame::reduce::softmax::test::test_softmax_l2::<$ker, $t>(&*xs).unwrap()\n                    }\n                }\n            }\n\n            #[test]\n            fn single() {\n                if $cond {\n                    $crate::frame::reduce::softmax::test::test_softmax_l2::<$ker, $t>(&[0.0]).unwrap()\n                }\n            }\n\n            #[test]\n            fn two_zeros() {\n                if $cond {\n                    $crate::frame::reduce::softmax::test::test_softmax_l2::<$ker, $t>(&[0.0, 0.0]).unwrap()\n                }\n            }\n\n            #[test]\n            fn two_0() {\n                if $cond {\n                    $crate::frame::reduce::softmax::test::test_softmax_l2::<$ker, $t>(&[\n                        16.62555, 21.950674,\n                    ])\n                    .unwrap()\n                }\n            }\n\n            #[test]\n            fn two_1() {\n                if $cond {\n                    $crate::frame::reduce::softmax::test::test_softmax_l2::<$ker, $t>(&[0.0f32, 0.38132212])\n                        .unwrap()\n                }\n            }\n\n            #[test]\n            fn two_missing_max() {\n                if $cond {\n                    $crate::frame::reduce::softmax::test::test_softmax_l2::<$ker, $t>(&[\n                        -46.15512, 42.875168,\n                    ])\n                    .unwrap()\n                }\n            }\n        };\n    }\n\n    pub fn test_softmax_l2<K: MapReduceKer<T, T>, T>(values: &[f32]) -> TestCaseResult\n    where\n        T: LADatum + Float + AsPrimitive<f32>,\n        f32: AsPrimitive<T>,\n    {\n        use crate::generic::reduce::softmax_l2::fast_compact_exp_f32;\n        crate::setup_test_logger();\n        let max = values.iter().max_by(|a, b| a.total_cmp(b)).unwrap();\n        let values: Vec<T> = values.iter().copied().map(|x| x.as_()).collect();\n        crate::frame::reduce::test::test_map_reduce_params::<K, T, T>(\n            &values,\n            <T as Float>::min_value(),\n            T::zero(),\n            //            |x| (x - max.as_()).exp(),\n            |x| fast_compact_exp_f32(x.as_() - max).as_(),\n            |a, b| a + b,\n            max.as_(),\n        )\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/reduce/sum.rs",
    "content": "#[cfg(test)]\n#[macro_use]\npub mod test {\n    use crate::LADatum;\n    use crate::frame::reduce::ReduceKer;\n    use num_traits::{AsPrimitive, Float, Zero};\n    use proptest::test_runner::TestCaseResult;\n\n    #[macro_export]\n    macro_rules! sum_frame_tests {\n        ($cond:expr, $t: ty, $ker:ty) => {\n            proptest::proptest! {\n                #[test]\n                fn prop(xs in proptest::collection::vec(-25_isize..25, 0..100)) {\n                    if $cond {\n                        let xs_float = xs.into_iter().map(|it| it as f32).collect::<Vec<_>>();\n                        $crate::frame::reduce::sum::test::test_sum::<$ker, $t>(&*xs_float).unwrap()\n                    }\n                }\n            }\n\n            #[test]\n            fn empty() {\n                if $cond {\n                    $crate::frame::reduce::sum::test::test_sum::<$ker, $t>(&[]).unwrap()\n                }\n            }\n\n            #[test]\n            fn simple() {\n                if $cond {\n                    $crate::frame::reduce::sum::test::test_sum::<$ker, $t>(&[1.0, 2.0]).unwrap()\n                }\n            }\n            #[test]\n            fn multiple_tile() {\n                if $cond {\n                    $crate::frame::reduce::sum::test::test_sum::<$ker, $t>(&[1.0; 35]).unwrap()\n                }\n            }\n        };\n    }\n\n    pub fn test_sum<K, T>(values: &[f32]) -> TestCaseResult\n    where\n        K: ReduceKer<T>,\n        f32: AsPrimitive<T>,\n        T: LADatum + Float + Zero + AsPrimitive<f32>,\n    {\n        crate::setup_test_logger();\n        let values: Vec<T> = values.iter().copied().map(|x| x.as_()).collect();\n        crate::frame::reduce::test::test_reduce::<K, _>(&values, <T as Zero>::zero(), |a, b| a + b)\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/sigmoid.rs",
    "content": "macro_rules! sigmoid_impl {\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $cond: expr) => {\n        ew_impl!($ti, $func, $nr, $alignment_items);\n        #[cfg(test)]\n        paste! {\n            mod [<test_ $func>] {\n                use super::*;\n                sigmoid_frame_tests!($cond, $ti, $func);\n            }\n        }\n    };\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod test {\n    use crate::{LADatum, frame::element_wise::*};\n    use num_traits::{AsPrimitive, Float};\n    use proptest::test_runner::TestCaseResult;\n\n    #[macro_export]\n    macro_rules! sigmoid_frame_tests {\n        ($cond:expr, $t: ty, $ker:ty) => {\n            proptest::proptest! {\n                #[test]\n                fn sigmoid(xs in proptest::collection::vec(-25f32..25.0, 0..100)) {\n                    if $cond {\n                        $crate::frame::sigmoid::test::test_sigmoid::<$ker, $t>(&*xs).unwrap()\n                    }\n                }\n            }\n\n            #[test]\n            fn sigmoid_4_magic() {\n                if $cond {\n                    $crate::frame::sigmoid::test::test_sigmoid::<$ker, $t>(&[\n                        0f32, -20.0, 20.0, 0.0,\n                    ])\n                    .unwrap()\n                }\n            }\n\n            #[test]\n            fn sigmoid_4zeros() {\n                if $cond {\n                    $crate::frame::sigmoid::test::test_sigmoid::<$ker, $t>(&[0.0; 4]).unwrap();\n                }\n            }\n\n            #[test]\n            fn sigmoid_20_ones() {\n                if $cond {\n                    $crate::frame::sigmoid::test::test_sigmoid::<$ker, $t>(&[1.0; 20]).unwrap();\n                }\n            }\n\n            #[test]\n            fn sigmoid_18_zeros() {\n                if $cond {\n                    $crate::frame::sigmoid::test::test_sigmoid::<$ker, $t>(&[0.0; 18]).unwrap();\n                }\n            }\n\n            #[test]\n            fn sigmoid_asymptots() {\n                use tract_data::internal::*;\n                use $crate::frame::element_wise::*;\n                if $cond {\n                    let mut input: Vec<$t> = [-100f32, 100f32]\n                        .iter()\n                        .map(|x| <f32 as num_traits::AsPrimitive<$t>>::as_(*x))\n                        .collect();\n                    let expected: Vec<$t> = [-0f32, 1f32]\n                        .iter()\n                        .map(|x| <f32 as num_traits::AsPrimitive<$t>>::as_(*x))\n                        .collect();\n                    <$ker>::ew().run(&mut input).unwrap();\n                    tensor1(&input)\n                        .close_enough(&tensor1(&expected), Approximation::Close)\n                        .unwrap();\n                }\n            }\n        };\n    }\n\n    pub fn test_sigmoid<K: ElementWiseKer<T>, T: LADatum + Float>(values: &[f32]) -> TestCaseResult\n    where\n        f32: AsPrimitive<T>,\n    {\n        crate::setup_test_logger();\n        let values: Vec<T> = values.iter().copied().map(|x| x.as_()).collect();\n        crate::frame::element_wise::test::test_element_wise::<K, _, _>(&values, |x| {\n            (1f32).as_() / (1f32.as_() + (-x).exp())\n        })\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/tanh.rs",
    "content": "macro_rules! tanh_impl {\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $cond: expr) => {\n        ew_impl!($ti, $func, $nr, $alignment_items);\n        #[cfg(test)]\n        paste! {\n            mod [<test_ $func>] {\n                use super::*;\n                tanh_frame_tests!($cond, $ti, $func);\n            }\n        }\n    };\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod test {\n    use crate::LADatum;\n    use crate::frame::element_wise::*;\n    use num_traits::AsPrimitive;\n    use num_traits::float::Float;\n    use proptest::test_runner::TestCaseResult;\n\n    #[macro_export]\n    macro_rules! tanh_frame_tests {\n        ($cond:expr, $t:ty, $ker:ty) => {\n            proptest::proptest! {\n                #[test]\n                fn tanh(xs in proptest::collection::vec(-25f32..25.0, 0..100)) {\n                    if $cond {\n                        $crate::frame::tanh::test::test_tanh::<$ker, $t>(&*xs).unwrap()\n                    }\n                }\n            }\n\n            #[test]\n            fn tanh_4_magic() {\n                if $cond {\n                    $crate::frame::tanh::test::test_tanh::<$ker, $t>(&[0f32, -20.0, 20.0, 0.0])\n                        .unwrap()\n                }\n            }\n\n            #[test]\n            fn tanh_4zeros() {\n                if $cond {\n                    $crate::frame::tanh::test::test_tanh::<$ker, $t>(&[0.0; 4]).unwrap();\n                }\n            }\n\n            #[test]\n            fn tanh_20_ones() {\n                if $cond {\n                    $crate::frame::tanh::test::test_tanh::<$ker, $t>(&[1.0; 20]).unwrap();\n                }\n            }\n\n            #[test]\n            fn tanh_18_zeros() {\n                if $cond {\n                    $crate::frame::tanh::test::test_tanh::<$ker, $t>(&[0.0; 18]).unwrap();\n                }\n            }\n\n            #[test]\n            fn tanh_foo() {\n                if $cond {\n                    $crate::frame::tanh::test::test_tanh::<$ker, $t>(&[0.67503357]).unwrap();\n                }\n            }\n\n            #[test]\n            fn tanh_asymptots() {\n                use tract_data::internal::*;\n                use $crate::frame::element_wise::*;\n                if $cond {\n                    let mut input: Vec<$t> = [-100f32, 100f32]\n                        .iter()\n                        .map(|x| <f32 as num_traits::AsPrimitive<$t>>::as_(*x))\n                        .collect();\n                    let expected: Vec<$t> = [-1f32, 1f32]\n                        .iter()\n                        .map(|x| <f32 as num_traits::AsPrimitive<$t>>::as_(*x))\n                        .collect();\n                    <$ker>::ew().run(&mut input).unwrap();\n                    tensor1(&input)\n                        .close_enough(&tensor1(&expected), Approximation::Close)\n                        .unwrap();\n                }\n            }\n        };\n    }\n\n    pub fn test_tanh<K: ElementWiseKer<T>, T: LADatum + Float>(values: &[f32]) -> TestCaseResult\n    where\n        f32: AsPrimitive<T>,\n    {\n        crate::setup_test_logger();\n        let values: Vec<T> = values.iter().copied().map(|x| x.as_()).collect();\n        crate::frame::element_wise::test::test_element_wise::<K, _, _>(&values, |x| x.tanh())\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/unicast.rs",
    "content": "use std::fmt::Debug;\nuse std::marker::PhantomData;\n\nuse tract_data::TractResult;\nuse tract_data::internal::TensorView;\n\nuse crate::frame::element_wise_helper::TempBuffer;\nuse crate::{LADatum, LinalgFn};\n\nmacro_rules! unicast_impl_wrap {\n    ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $run: item) => {\n        paste! {\n            #[derive(Copy, Clone, Debug)]\n            #[allow(non_camel_case_types)]\n            pub struct $func;\n\n            impl crate::frame::unicast::UnicastKer<$ti> for $func {\n                #[inline(always)]\n                fn name() -> &'static str {\n                    stringify!($func)\n                }\n                #[inline(always)]\n                fn nr() -> usize {\n                    $nr\n                }\n                #[inline(always)]\n                fn alignment_items() -> usize {\n                    $alignment_items\n                }\n                $run\n            }\n        }\n    };\n}\n\npub trait Unicast<T>: Send + Sync + Debug + dyn_clone::DynClone\nwhere\n    T: Copy + Debug + PartialEq + Send + Sync,\n{\n    fn name(&self) -> &'static str;\n    fn run(&self, a: &mut [T], b: &[T]) -> TractResult<()>;\n}\n\ndyn_clone::clone_trait_object!(<T> Unicast<T> where T: Copy);\n\n#[derive(Debug, Clone, new)]\npub struct UnicastImpl<K, T>\nwhere\n    T: LADatum,\n    K: UnicastKer<T> + Clone,\n{\n    phantom: PhantomData<(K, T)>,\n}\n\nimpl<K, T> UnicastImpl<K, T>\nwhere\n    T: LADatum,\n    K: UnicastKer<T> + Clone,\n{\n}\nimpl<K, T> Unicast<T> for UnicastImpl<K, T>\nwhere\n    T: LADatum,\n    K: UnicastKer<T> + Clone,\n{\n    fn name(&self) -> &'static str {\n        K::name()\n    }\n    fn run(&self, a: &mut [T], b: &[T]) -> TractResult<()> {\n        unicast_with_alignment(a, b, |a, b| K::run(a, b), K::nr(), K::alignment_bytes())\n    }\n}\n\npub trait UnicastKer<T>: Send + Sync + Debug + dyn_clone::DynClone + Clone + 'static\nwhere\n    T: LADatum,\n{\n    fn name() -> &'static str;\n    fn alignment_bytes() -> usize {\n        Self::alignment_items() * T::datum_type().size_of()\n    }\n    fn alignment_items() -> usize;\n    fn nr() -> usize;\n    fn run(a: &mut [T], b: &[T]);\n    fn bin() -> Box<LinalgFn> {\n        Box::new(|a: &mut TensorView, b: &TensorView| {\n            let a_slice = a.as_slice_mut()?;\n            let b_slice = b.as_slice()?;\n            UnicastImpl::<Self, T>::new().run(a_slice, b_slice)\n        })\n    }\n}\n\nstd::thread_local! {\n    static TMP: std::cell::RefCell<(TempBuffer, TempBuffer)> = std::cell::RefCell::new((TempBuffer::default(), TempBuffer::default()));\n}\n\npub(crate) fn unicast_with_alignment<T>(\n    a: &mut [T],\n    b: &[T],\n    f: impl Fn(&mut [T], &[T]),\n    nr: usize,\n    alignment_bytes: usize,\n) -> TractResult<()>\nwhere\n    T: LADatum,\n{\n    if a.is_empty() {\n        return Ok(());\n    }\n    unsafe {\n        TMP.with(|buffers| {\n            let mut buffers = buffers.borrow_mut();\n            buffers.0.ensure(nr * T::datum_type().size_of(), alignment_bytes);\n            buffers.1.ensure(nr * T::datum_type().size_of(), alignment_bytes);\n            let tmp_a = std::slice::from_raw_parts_mut(buffers.0.buffer as *mut T, nr);\n            let tmp_b = std::slice::from_raw_parts_mut(buffers.1.buffer as *mut T, nr);\n            let mut compute_via_temp_buffer = |a: &mut [T], b: &[T]| {\n                tmp_a[..a.len()].copy_from_slice(a);\n                tmp_b[..b.len()].copy_from_slice(b);\n                f(tmp_a, tmp_b);\n                a.copy_from_slice(&tmp_a[..a.len()])\n            };\n\n            let mut num_element_processed = 0;\n            let a_prefix_len = a.as_ptr().align_offset(alignment_bytes).min(a.len());\n            let b_prefix_len = b.as_ptr().align_offset(alignment_bytes).min(b.len());\n            assert!(\n                a_prefix_len == b_prefix_len,\n                \"Both inputs should be of the same alignement, got {a_prefix_len:?}, {b_prefix_len:?}\"\n            );\n            let mut applied_prefix_len = 0;\n            if a_prefix_len > 0 {\n                // Incomplete tile needs to be created to process unaligned data.\n                let sub_a = &mut a[..a_prefix_len];\n                let sub_b = &b[..a_prefix_len];\n                compute_via_temp_buffer(sub_a, sub_b);\n                num_element_processed += a_prefix_len;\n                applied_prefix_len = a_prefix_len;\n            }\n\n            let num_complete_tiles = (a.len() - applied_prefix_len) / nr;\n            if num_complete_tiles > 0 {\n                // Process all tiles that are complete.\n                let sub_a = &mut a[applied_prefix_len..][..(num_complete_tiles * nr)];\n                let sub_b = &b[applied_prefix_len..][..(num_complete_tiles * nr)];\n                f(sub_a, sub_b);\n                num_element_processed += num_complete_tiles * nr;\n            }\n\n            if num_element_processed < a.len() {\n                // Incomplete tile needs to be created to process remaining elements.\n                compute_via_temp_buffer(\n                    &mut a[num_element_processed..],\n                    &b[num_element_processed..],\n                );\n            }\n        })\n    }\n    Ok(())\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod test {\n    use super::*;\n    use crate::LADatum;\n    use proptest::test_runner::{TestCaseError, TestCaseResult};\n    use tract_data::internal::*;\n    use tract_num_traits::{AsPrimitive, Float};\n\n    pub fn test_unicast<K: UnicastKer<T>, T: LADatum>(\n        a: &mut [T],\n        b: &[T],\n        reference: impl Fn(T, T) -> T,\n    ) -> TestCaseResult {\n        crate::setup_test_logger();\n        let op = UnicastImpl::<K, T>::new();\n        let expected = a.iter().zip(b.iter()).map(|(a, b)| (reference)(*a, *b)).collect::<Vec<_>>();\n        op.run(a, b).unwrap();\n        tensor1(a)\n            .close_enough(&tensor1(&expected), true)\n            .map_err(|e| TestCaseError::fail(e.root_cause().to_string()))?;\n        Ok(())\n    }\n\n    pub fn test_unicast_t<K: UnicastKer<T>, T: LADatum + Float>(\n        a: &[f32],\n        b: &[f32],\n        func: impl Fn(T, T) -> T,\n    ) -> TestCaseResult\n    where\n        f32: AsPrimitive<T>,\n    {\n        crate::setup_test_logger();\n        let vec_a: Vec<T> = a.iter().copied().map(|x| x.as_()).collect();\n        // We allocate a tensor to ensure allocation is done with alignement\n        let mut a = unsafe { Tensor::from_slice_align(vec_a.as_slice(), vector_size()).unwrap() };\n        let vec_b: Vec<T> = b.iter().copied().map(|x| x.as_()).collect();\n        // We allocate a tensor to ensure allocation is done with alignement\n        let b = unsafe { Tensor::from_slice_align(vec_b.as_slice(), vector_size()).unwrap() };\n        crate::frame::unicast::test::test_unicast::<K, _>(\n            a.try_as_plain_mut().unwrap().as_slice_mut::<T>().unwrap(),\n            b.try_as_plain().unwrap().as_slice::<T>().unwrap(),\n            func,\n        )\n    }\n\n    #[macro_export]\n    macro_rules! unicast_frame_tests {\n        ($cond:expr, $t: ty, $ker:ty, $func:expr) => {\n            pastey::paste! {\n                proptest::proptest! {\n                    #[test]\n                    fn [<prop_ $ker:snake>](\n                        (a, b) in (0..100_usize).prop_flat_map(|len| (vec![-25f32..25.0; len], vec![-25f32..25.0; len]))\n                    ) {\n                        if $cond {\n                            $crate::frame::unicast::test::test_unicast_t::<$ker, $t>(&*a, &*b, $func).unwrap()\n                        }\n                    }\n                }\n\n                #[test]\n                fn [<empty_ $ker:snake>]() {\n                    if $cond {\n                        $crate::frame::unicast::test::test_unicast_t::<$ker, $t>(&[], &[], $func).unwrap()\n                    }\n                }\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "linalg/src/frame/weights.rs",
    "content": "use std::fmt::Debug;\nuse tract_data::prelude::DatumType;\n\nuse crate::block_quant::{BlockQuant, PackedBlockQuantFormat};\n\nuse crate::mmm::MMMInputFormat;\nuse crate::pack::PackedFormat;\n\n#[derive(Clone)]\npub enum WeightType {\n    Plain(DatumType),\n    BlockQuant(Box<dyn BlockQuant>),\n}\n\nimpl From<DatumType> for WeightType {\n    fn from(value: DatumType) -> Self {\n        match value {\n            DatumType::F16 => WeightType::Plain(DatumType::F16),\n            DatumType::F32 => WeightType::Plain(DatumType::F32),\n            DatumType::F64 => WeightType::Plain(DatumType::F64),\n            DatumType::I32 => WeightType::Plain(DatumType::I32),\n            DatumType::I8 | DatumType::QI8(_) => WeightType::Plain(DatumType::I8),\n            DatumType::U8 | DatumType::QU8(_) => WeightType::Plain(DatumType::U8),\n            _ => panic!(\"Can't build a WeightType from {value:?}\"),\n        }\n    }\n}\n\nimpl From<Box<dyn MMMInputFormat>> for WeightType {\n    fn from(value: Box<dyn MMMInputFormat>) -> Self {\n        (&*value).into()\n    }\n}\n\nimpl From<&dyn MMMInputFormat> for WeightType {\n    fn from(value: &dyn MMMInputFormat) -> Self {\n        if let Some(pf) = value.downcast_ref::<PackedFormat>() {\n            WeightType::Plain(pf.dt)\n        } else if let Some(pbqf) = value.downcast_ref::<PackedBlockQuantFormat>() {\n            WeightType::BlockQuant(dyn_clone::clone_box(&*pbqf.bq))\n        } else {\n            todo!()\n        }\n    }\n}\n\nimpl PartialEq for WeightType {\n    fn eq(&self, other: &Self) -> bool {\n        use WeightType::*;\n        match (self, other) {\n            (Plain(a), Plain(b)) => a == b,\n            (BlockQuant(a), BlockQuant(b)) => a == b,\n            _ => false,\n        }\n    }\n}\n\nimpl<BQ: BlockQuant> From<BQ> for WeightType {\n    fn from(value: BQ) -> Self {\n        WeightType::BlockQuant(dyn_clone::clone_box(&value))\n    }\n}\n\nimpl Debug for WeightType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Plain(p) => write!(f, \"{p:?}\"),\n            Self::BlockQuant(bq) => write!(f, \"{bq:?}\"),\n        }\n    }\n}\n\nimpl WeightType {\n    pub fn as_dt(&self) -> Option<DatumType> {\n        match self {\n            WeightType::Plain(dt) => Some(*dt),\n            _ => None,\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/src/generic/by_scalar.rs",
    "content": "use tract_data::internal::f16;\n\nby_scalar_impl_wrap!(\n    f32,\n    SMulByScalar4,\n    4,\n    4,\n    f32,\n    fn run(x: &mut [f32], s: f32) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px *= s)\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    SAddByScalar4,\n    4,\n    4,\n    f32,\n    fn run(x: &mut [f32], s: f32) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px += s)\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    SSubByScalar4,\n    4,\n    4,\n    f32,\n    fn run(x: &mut [f32], s: f32) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px -= s)\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    SSubFByScalar4,\n    4,\n    4,\n    f32,\n    fn run(x: &mut [f32], s: f32) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = s - *px)\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    SMinByScalar4,\n    4,\n    4,\n    f32,\n    fn run(x: &mut [f32], s: f32) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = px.min(s))\n    }\n);\n\nby_scalar_impl_wrap!(\n    f32,\n    SMaxByScalar4,\n    4,\n    4,\n    f32,\n    fn run(x: &mut [f32], s: f32) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = px.max(s))\n    }\n);\n\n#[cfg(test)]\n#[macro_use]\npub mod mul_by_scalar_f32 {\n    use super::*;\n    by_scalar_frame_tests!(true, f32, SMulByScalar4, |a, b| a * b);\n    by_scalar_frame_tests!(true, f32, SAddByScalar4, |a, b| a + b);\n    by_scalar_frame_tests!(true, f32, SSubByScalar4, |a, b| a - b);\n    by_scalar_frame_tests!(true, f32, SSubFByScalar4, |a, b| b - a);\n    by_scalar_frame_tests!(true, f32, SMinByScalar4, |a, b| a.min(b));\n    by_scalar_frame_tests!(true, f32, SMaxByScalar4, |a, b| a.max(b));\n}\n\nby_scalar_impl_wrap!(\n    f16,\n    HMulByScalar8,\n    8,\n    8,\n    f16,\n    fn run(x: &mut [f16], s: f16) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px *= s)\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    HAddByScalar8,\n    8,\n    8,\n    f16,\n    fn run(x: &mut [f16], s: f16) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px += s)\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    HSubByScalar8,\n    8,\n    8,\n    f16,\n    fn run(x: &mut [f16], s: f16) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px -= s)\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    HSubFByScalar8,\n    8,\n    8,\n    f16,\n    fn run(x: &mut [f16], s: f16) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = s - *px)\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    HMinByScalar8,\n    8,\n    8,\n    f16,\n    fn run(x: &mut [f16], s: f16) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = px.min(s))\n    }\n);\n\nby_scalar_impl_wrap!(\n    f16,\n    HMaxByScalar8,\n    8,\n    8,\n    f16,\n    fn run(x: &mut [f16], s: f16) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = px.max(s))\n    }\n);\n\n#[cfg(test)]\n#[macro_use]\npub mod mul_by_scalar_f16 {\n    use super::*;\n    by_scalar_frame_tests!(true, f16, HMulByScalar8, |a, b| a * b);\n    by_scalar_frame_tests!(true, f16, HAddByScalar8, |a, b| a + b);\n    by_scalar_frame_tests!(true, f16, HSubByScalar8, |a, b| a - b);\n    by_scalar_frame_tests!(true, f16, HSubFByScalar8, |a, b| b - a);\n    by_scalar_frame_tests!(true, f16, HMinByScalar8, |a, b| a.min(b));\n    by_scalar_frame_tests!(true, f16, HMaxByScalar8, |a, b| a.max(b));\n}\n"
  },
  {
    "path": "linalg/src/generic/erf.rs",
    "content": "use crate::element_wise::ElementWiseKer;\n\n#[allow(non_upper_case_globals)]\n#[allow(clippy::excessive_precision)]\nfn serf(x: &mut f32) {\n    const a1: f32 = 0.0705230784;\n    const a2: f32 = 0.0422820123;\n    const a3: f32 = 0.0092705272;\n    const a4: f32 = 0.0001520143;\n    const a5: f32 = 0.0002765672;\n    const a6: f32 = 0.0000430638;\n\n    let signum = x.signum();\n    let abs = x.abs();\n    let y = a6 * abs;\n    let y = (a5 + y) * abs;\n    let y = (a4 + y) * abs;\n    let y = (a3 + y) * abs;\n    let y = (a2 + y) * abs;\n    let y = (a1 + y) * abs;\n    let y = 1.0 - (y + 1.0).powi(16).recip();\n\n    *x = y.copysign(signum)\n}\n\n#[derive(Clone, Debug)]\npub struct SErf4;\n\nimpl ElementWiseKer<f32> for SErf4 {\n    fn name() -> &'static str {\n        \"generic\"\n    }\n\n    fn alignment_items() -> usize {\n        16\n    }\n\n    fn alignment_bytes() -> usize {\n        16\n    }\n\n    fn nr() -> usize {\n        4\n    }\n\n    fn run(x: &mut [f32], _: ()) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(serf)\n    }\n}\n"
  },
  {
    "path": "linalg/src/generic/leaky_relu.rs",
    "content": "#![allow(clippy::excessive_precision)]\nuse crate::frame::element_wise::ElementWiseKer;\nuse tract_data::internal::*;\nuse tract_num_traits::Zero;\n\n#[derive(Clone, Debug)]\npub struct SLeakyRelu4;\n\nimpl ElementWiseKer<f32, f32> for SLeakyRelu4 {\n    fn name() -> &'static str {\n        \"generic\"\n    }\n\n    fn alignment_bytes() -> usize {\n        16\n    }\n\n    fn alignment_items() -> usize {\n        4\n    }\n\n    fn nr() -> usize {\n        4\n    }\n\n    fn run(x: &mut [f32], alpha: f32) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = if *px < 0. { *px * alpha } else { *px });\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct HLeakyRelu8;\n\nimpl ElementWiseKer<f16, f16> for HLeakyRelu8 {\n    fn name() -> &'static str {\n        \"generic\"\n    }\n\n    fn alignment_bytes() -> usize {\n        16\n    }\n\n    fn alignment_items() -> usize {\n        4\n    }\n\n    fn nr() -> usize {\n        8\n    }\n\n    fn run(x: &mut [f16], alpha: f16) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = if *px < f16::zero() { *px * alpha } else { *px })\n    }\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod s {\n    leaky_relu_frame_tests!(true, f32, crate::generic::leaky_relu::SLeakyRelu4);\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod h {\n    leaky_relu_frame_tests!(\n        true,\n        tract_data::internal::f16,\n        crate::generic::leaky_relu::HLeakyRelu8\n    );\n}\n"
  },
  {
    "path": "linalg/src/generic/lut.rs",
    "content": "use crate::frame::lut::LutKer;\n\n#[derive(Clone, Debug, Hash)]\npub struct GenericLut8;\n\nimpl LutKer for GenericLut8 {\n    fn name() -> &'static str {\n        \"generic\"\n    }\n\n    fn input_alignment_bytes() -> usize {\n        1\n    }\n\n    fn table_alignment_bytes() -> usize {\n        1\n    }\n\n    fn n() -> usize {\n        8\n    }\n\n    unsafe fn run(buf: *mut u8, len: usize, table: *const u8) {\n        unsafe {\n            debug_assert!(len % Self::n() == 0);\n            debug_assert!(buf as usize % Self::input_alignment_bytes() == 0);\n            debug_assert!(table as usize % Self::table_alignment_bytes() == 0);\n            for i in 0..((len / 8) as isize) {\n                let ptr = buf.offset(8 * i);\n                *ptr.offset(0) = *table.offset(*ptr.offset(0) as isize);\n                *ptr.offset(1) = *table.offset(*ptr.offset(1) as isize);\n                *ptr.offset(2) = *table.offset(*ptr.offset(2) as isize);\n                *ptr.offset(3) = *table.offset(*ptr.offset(3) as isize);\n                *ptr.offset(4) = *table.offset(*ptr.offset(4) as isize);\n                *ptr.offset(5) = *table.offset(*ptr.offset(5) as isize);\n                *ptr.offset(6) = *table.offset(*ptr.offset(6) as isize);\n                *ptr.offset(7) = *table.offset(*ptr.offset(7) as isize);\n            }\n        }\n    }\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod test {\n    lut_frame_tests!(true, crate::generic::GenericLut8);\n}\n"
  },
  {
    "path": "linalg/src/generic/mmm.rs",
    "content": "#![allow(clippy::needless_range_loop)]\nuse num_traits::AsPrimitive;\n\nuse tract_data::prelude::f16;\nuse tract_data::prelude::*;\n\nuse super::*;\nuse crate::frame::block_quant::{BlockQuant, NibbleReader, PackedBlockQuantFormat, Q4_0};\nuse crate::frame::mmm::*;\nuse crate::{LADatum, Ops, has_fp16};\n\nmacro_rules! scalar {\n    ($ab: expr, $m: expr, $f: expr) => {\n        for i in 0..$ab.len() {\n            for j in 0..$ab[0].len() {\n                $ab[i][j] = $f($m, $ab[i][j])\n            }\n        }\n    };\n}\n\nmacro_rules! per_row {\n    ($ab: expr, $m: expr, $f: expr) => {\n        for i in 0..$ab.len() {\n            for j in 0..$ab[0].len() {\n                $ab[i][j] = $f(*$m.add(i), $ab[i][j])\n            }\n        }\n    };\n}\n\nmacro_rules! per_col {\n    ($ab: expr, $m: expr, $f: expr) => {\n        for i in 0..$ab.len() {\n            for j in 0..$ab[0].len() {\n                $ab[i][j] = $f(*$m.add(j), $ab[i][j])\n            }\n        }\n    };\n}\n\nunsafe fn add_mat_mul<const MR: usize, const NR: usize, TI, TA, TB>(\n    pa: *const u8,\n    pb: *const u8,\n    k: usize,\n    ab: &mut [[TI; NR]; MR],\n) where\n    TA: LADatum + AsPrimitive<TI>,\n    TB: LADatum + AsPrimitive<TI>,\n    TI: LADatum,\n{\n    unsafe {\n        let a = pa as *const TA;\n        let b = pb as *const TB;\n        for ik in 0..k {\n            let a = std::slice::from_raw_parts(a.add(MR * ik), MR);\n            let b = std::slice::from_raw_parts(b.add(NR * ik), NR);\n            for i in 0..MR {\n                for j in 0..NR {\n                    ab[i][j] += a[i].as_() * b[j].as_();\n                }\n            }\n        }\n    }\n}\n\nunsafe fn add_mat_mul_pq40<const MR: usize, const NR: usize, TB, TI>(\n    pa: *const u8,\n    pb: *const u8,\n    k: usize,\n    ab: &mut [[TI; NR]; MR],\n) where\n    TI: LADatum,\n    f16: AsPrimitive<TI>,\n    TB: AsPrimitive<TI>,\n    i8: AsPrimitive<TI>,\n{\n    unsafe {\n        assert!(k % Q4_0.block_len() == 0);\n        let len = (k * MR) / Q4_0.block_len() * Q4_0.block_bytes();\n        let mut pa = NibbleReader::for_slice(std::slice::from_raw_parts(pa, len));\n        let b = pb as *const TB;\n        for bk in 0..k / 32 {\n            let mut scales: [TI; MR] = [TI::zero(); MR];\n            scales.iter_mut().for_each(|x| *x = pa.read_f16().as_());\n            for ik in 0..32 {\n                let mut a: [TI; MR] = [TI::zero(); MR];\n                a.iter_mut().zip(&scales).for_each(|(x, s)| *x = *s * (pa.read_i4() - 8).as_());\n                let b = std::slice::from_raw_parts(b.add(NR * (ik + 32 * bk)), NR);\n                for i in 0..MR {\n                    for j in 0..NR {\n                        ab[i][j] += a[i] * b[j].as_();\n                    }\n                }\n            }\n        }\n    }\n}\n\nunsafe fn add_mat_mul_pq40_scales_at_end<const MR: usize, const NR: usize, TB, TI>(\n    pa: *const u8,\n    pb: *const u8,\n    k: usize,\n    ab: &mut [[TI; NR]; MR],\n) where\n    TI: LADatum,\n    f16: AsPrimitive<TI>,\n    TB: AsPrimitive<TI>,\n    i8: AsPrimitive<TI>,\n{\n    unsafe {\n        assert!(k % Q4_0.block_len() == 0);\n        let len = (k * MR) / Q4_0.block_len() * Q4_0.block_bytes();\n        let mut pa = NibbleReader::for_slice(std::slice::from_raw_parts(pa, len));\n        let b = pb as *const TB;\n        for bk in 0..k / 32 {\n            let mut temp = [[TI::zero(); NR]; MR];\n            for ik in 0..32 {\n                let mut a: [TI; MR] = [TI::zero(); MR];\n                a.iter_mut().for_each(|x| *x = (pa.read_i4() - 8).as_());\n                let b = std::slice::from_raw_parts(b.add(NR * (ik + 32 * bk)), NR);\n                for i in 0..MR {\n                    for j in 0..NR {\n                        temp[i][j] += a[i] * b[j].as_();\n                    }\n                }\n            }\n            for i in 0..MR {\n                let scale = pa.read_f16().as_();\n                for j in 0..NR {\n                    ab[i][j] += temp[i][j] * scale;\n                }\n            }\n        }\n    }\n}\n\nunsafe fn add_unicast<const MR: usize, const NR: usize, TI, TO>(\n    ab: &mut [[TI; NR]; MR],\n    other: &OutputStoreKer,\n) where\n    TI: LADatum,\n    TO: LADatum + AsPrimitive<TI>,\n{\n    unsafe {\n        for i in 0usize..MR {\n            for j in 0usize..NR {\n                let value: *const TO = other\n                    .ptr\n                    .offset(other.row_byte_stride * i as isize + other.col_byte_stride * j as isize)\n                    as _;\n                ab[i].as_mut()[j] += (*value).as_();\n            }\n        }\n    }\n}\n\nunsafe fn store_t<const MR: usize, const NR: usize, TC, TI>(\n    tile: &OutputStoreKer,\n    ab: &[[TI; NR]; MR],\n) where\n    TC: Copy,\n{\n    unsafe {\n        for i in 0usize..MR {\n            for j in 0usize..NR {\n                let loc: *mut TC = tile\n                    .ptr\n                    .offset(tile.row_byte_stride * i as isize + tile.col_byte_stride * j as isize)\n                    as _;\n                let val: *const TC = (&ab[i].as_ref()[j]) as *const TI as _;\n                *loc = *val\n            }\n        }\n    }\n}\n\nunsafe fn store_float_t<const MR: usize, const NR: usize, TC, TI>(\n    tile: &OutputStoreKer,\n    ab: &[[TI; NR]; MR],\n) where\n    TC: Copy + 'static,\n    TI: Copy + 'static + AsPrimitive<TC>,\n{\n    unsafe {\n        for i in 0usize..MR {\n            for j in 0usize..NR {\n                let loc: *mut TC = tile\n                    .ptr\n                    .offset(tile.row_byte_stride * i as isize + tile.col_byte_stride * j as isize)\n                    as _;\n                let val = ab[i].as_ref()[j].as_();\n                *loc = val\n            }\n        }\n    }\n}\n\n#[inline(never)]\nunsafe fn kernel<TI, const MR: usize, const NR: usize>(mut pnl: *const FusedKerSpec<TI>) -> isize\nwhere\n    TI: LADatum + ScaleShiftAndRound + AsPrimitive<TI>,\n    TI: AsPrimitive<f16> + AsPrimitive<f32> + AsPrimitive<f64>,\n    usize: AsPrimitive<TI>,\n    f16: AsPrimitive<TI>,\n    f32: AsPrimitive<TI>,\n    f64: AsPrimitive<TI>,\n    i8: AsPrimitive<TI>,\n    i32: AsPrimitive<TI>,\n{\n    unsafe {\n        let mut ab = [[TI::zero(); NR]; MR];\n        loop {\n            if pnl.is_null() {\n                break;\n            }\n            match *pnl {\n                FusedKerSpec::Done => break,\n                FusedKerSpec::Clear => ab = std::mem::zeroed(),\n                FusedKerSpec::LoadTile(col_major, _row_major) => {\n                    for row in 0..MR {\n                        for col in 0..NR {\n                            ab[row][col] = *col_major.add(col * MR + row);\n                        }\n                    }\n                }\n                FusedKerSpec::ScalarAdd(a) => scalar!(ab, a, |a, b| a + b),\n                FusedKerSpec::ScalarMul(a) => scalar!(ab, a, |a, b| a * b),\n                FusedKerSpec::ScalarMin(m) => scalar!(ab, m, |a, b| if a < b { a } else { b }),\n                FusedKerSpec::ScalarMax(m) => scalar!(ab, m, |a, b| if a > b { a } else { b }),\n                FusedKerSpec::ScalarSub(m) => scalar!(ab, m, |a, b| a - b),\n                FusedKerSpec::ScalarSubF(m) => scalar!(ab, m, |a, b| b - a),\n                FusedKerSpec::LeakyRelu(m) => {\n                    scalar!(ab, m, |a, b| if b > TI::zero() { b } else { a * b })\n                }\n                FusedKerSpec::PerRowMin(m) => per_row!(ab, m, |a, b| if a < b { a } else { b }),\n                FusedKerSpec::PerRowMax(m) => per_row!(ab, m, |a, b| if a > b { a } else { b }),\n                FusedKerSpec::PerRowAdd(m) => per_row!(ab, m, |a, b| a + b),\n                FusedKerSpec::PerRowMul(m) => per_row!(ab, m, |a, b| a * b),\n                FusedKerSpec::PerRowSub(m) => per_row!(ab, m, |a, b| a - b),\n                FusedKerSpec::PerRowSubF(m) => per_row!(ab, m, |a, b| b - a),\n                FusedKerSpec::PerColMin(m) => per_col!(ab, m, |a, b| if a < b { a } else { b }),\n                FusedKerSpec::PerColMax(m) => per_col!(ab, m, |a, b| if a > b { a } else { b }),\n                FusedKerSpec::PerColAdd(m) => per_col!(ab, m, |a, b| a + b),\n                FusedKerSpec::PerColMul(m) => per_col!(ab, m, |a, b| a * b),\n                FusedKerSpec::PerColSub(m) => per_col!(ab, m, |a, b| a - b),\n                FusedKerSpec::PerColSubF(m) => per_col!(ab, m, |a, b| b - a),\n                FusedKerSpec::AddRowColProducts(rows, cols) => {\n                    for i in 0..MR {\n                        for j in 0..NR {\n                            ab[i][j] += *rows.add(i) * *cols.add(j);\n                        }\n                    }\n                }\n                FusedKerSpec::AddUnicast(other) => {\n                    if TI::datum_type().is_float() && other.item_size == 2 {\n                        add_unicast::<MR, NR, TI, f16>(&mut ab, &other)\n                    } else if TI::datum_type().is_float() && other.item_size == 4 {\n                        add_unicast::<MR, NR, TI, f32>(&mut ab, &other)\n                    } else if TI::datum_type().is_float() && other.item_size == 8 {\n                        add_unicast::<MR, NR, TI, f64>(&mut ab, &other)\n                    } else if TI::datum_type() == i32::datum_type() && other.item_size == 1 {\n                        add_unicast::<MR, NR, TI, i8>(&mut ab, &other)\n                    } else if TI::datum_type() == i32::datum_type() && other.item_size == 4 {\n                        add_unicast::<MR, NR, TI, i32>(&mut ab, &other)\n                    } else {\n                        unimplemented!(\"Missing AddUnicast type\");\n                    }\n                }\n                FusedKerSpec::ShiftLeft(shift) => {\n                    for i in 0..MR {\n                        for j in 0..NR {\n                            ab[i][j] = ab[i][j].q_shl(shift);\n                        }\n                    }\n                }\n                FusedKerSpec::RoundingShiftRight(shift, rp) => {\n                    for i in 0..MR {\n                        for j in 0..NR {\n                            ab[i][j] = ab[i][j].q_shr(shift, rp);\n                        }\n                    }\n                }\n                FusedKerSpec::QScale(shift, rp, mult) => {\n                    for i in 0..MR {\n                        for j in 0..NR {\n                            ab[i][j] = ab[i][j].q_scale(Scaler::from_fuse_params(shift, rp, mult));\n                        }\n                    }\n                }\n                FusedKerSpec::AddMatMul { k, pa, pb, packing } => {\n                    use std::mem::transmute;\n                    if TI::datum_type().is_float() {\n                        match packing {\n                            0 => add_mat_mul::<MR, NR, TI, TI, TI>(pa, pb, k, &mut ab),\n                            1 => add_mat_mul::<MR, NR, TI, f16, f16>(pa, pb, k, &mut ab),\n                            2 => add_mat_mul::<MR, NR, TI, f32, f32>(pa, pb, k, &mut ab),\n                            3 => add_mat_mul::<MR, NR, TI, f16, f32>(pa, pb, k, &mut ab),\n                            4 => add_mat_mul::<MR, NR, TI, f32, f16>(pa, pb, k, &mut ab),\n                            5 => add_mat_mul_pq40::<MR, NR, f16, TI>(pa, pb, k, &mut ab),\n                            6 => add_mat_mul_pq40_scales_at_end::<MR, NR, f16, TI>(\n                                pa, pb, k, &mut ab,\n                            ),\n                            7 => add_mat_mul_pq40::<MR, NR, f32, TI>(pa, pb, k, &mut ab),\n                            _ => unreachable!(),\n                        }\n                    } else if TI::datum_type() == i32::datum_type() {\n                        // transmute to allow using explicitly i3 in add_mat_mul generic params\n                        let ab = transmute::<&mut [[TI; NR]; MR], &mut [[i32; NR]; MR]>(&mut ab);\n                        if packing == 0 {\n                            add_mat_mul::<MR, NR, i32, i32, i32>(pa, pb, k, ab)\n                        } else if packing == 1 {\n                            add_mat_mul::<MR, NR, i32, i8, i8>(pa, pb, k, ab)\n                        } else {\n                            return 1;\n                        }\n                    } else {\n                        return 1;\n                    }\n                }\n                FusedKerSpec::Store(tile) => {\n                    if TI::datum_type().is_float() {\n                        match tile.item_size {\n                            2 => store_float_t::<MR, NR, f16, _>(&tile, &ab),\n                            4 => store_float_t::<MR, NR, f32, _>(&tile, &ab),\n                            8 => store_float_t::<MR, NR, f64, _>(&tile, &ab),\n                            _ => unimplemented!(),\n                        }\n                    } else {\n                        match tile.item_size {\n                            1 => store_t::<MR, NR, u8, _>(&tile, &ab),\n                            2 => store_t::<MR, NR, u16, _>(&tile, &ab),\n                            4 => store_t::<MR, NR, u32, _>(&tile, &ab),\n                            8 => store_t::<MR, NR, u64, _>(&tile, &ab),\n                            _ => unimplemented!(),\n                        }\n                    }\n                }\n            };\n            pnl = pnl.add(1);\n        }\n    }\n    0\n}\n\nfn pq40_r4() -> PackedBlockQuantFormat {\n    PackedBlockQuantFormat::new(&Q4_0, 4, 0, false)\n}\n\nfn pq40_r4_se() -> PackedBlockQuantFormat {\n    PackedBlockQuantFormat::new(&Q4_0, 4, 0, true)\n}\n\n// f16 kernels\nMMMRustKernel!(kernel::<f16, 4, 4> => generic_f16_4x4<f16>(4,4)\n    packing[1] = f16f16bis => |k| k.with_packing(f16::packing(4), f16::packing(4));\n    packing[2] = f32f32 => |k| k.with_packing(f32::packing(4), f32::packing(4));\n    packing[3] = f16f32 => |k| k.with_packing(f16::packing(4), f32::packing(4));\n    packing[4] = f32f16 => |k| k.with_packing(f32::packing(4), f16::packing(4));\n    packing[5] = q40f16 => |k| k.with_packing(pq40_r4(), f16::packing(4));\n    packing[6] = q40f16se => |k| k.with_packing(pq40_r4_se(), f16::packing(4));\n    packing[7] = q40f32 => |k| k.with_packing(pq40_r4(), f32::packing(4));\n    quality(if has_fp16() { ImplementationQuality::Generic } else { ImplementationQuality::Dreadful })\n    store(f32, f64)\n);\n\nMMMRustKernel! {kernel::<f16, 4, 1> => generic_f16_4x1<f16>(4,1)\n    packing[1] = f16f16bis => |k| k.with_packing(f16::packing(4), f16::packing(1));\n    packing[2] = f32f32 => |k| k.with_packing(f32::packing(4), f32::packing(1));\n    packing[3] = f16f32 => |k| k.with_packing(f16::packing(4), f32::packing(1));\n    packing[4] = f32f16 => |k| k.with_packing(f32::packing(4), f16::packing(1));\n    packing[5] = q40f16 => |k| k.with_packing(pq40_r4(), f16::packing(1));\n    packing[6] = q40f16se => |k| k.with_packing(pq40_r4_se(), f16::packing(1));\n    packing[7] = q40f32 => |k| k.with_packing(pq40_r4(), f32::packing(1));\n    quality(if has_fp16() { ImplementationQuality::Generic } else { ImplementationQuality::Dreadful })\n    store(f32, f64)\n}\n\n// f32 kernels\nMMMRustKernel!(kernel::<f32, 4, 4> => generic_f32_4x4<f32>(4,4)\n    packing[1] = f16f16 => |k| k.with_packing(f16::packing(4), f16::packing(4));\n    packing[2] = f32f32bis => |k| k.with_packing(f32::packing(4), f32::packing(4));\n    packing[3] = f16f32 => |k| k.with_packing(f16::packing(4), f32::packing(4));\n    packing[4] = f32f16 => |k| k.with_packing(f32::packing(4), f16::packing(4));\n    packing[5] = q40f16 => |k| k.with_packing(pq40_r4(), f16::packing(4));\n    packing[6] = q40f16se => |k| k.with_packing(pq40_r4_se(), f16::packing(4));\n    packing[7] = q40f32 => |k| k.with_packing(pq40_r4(), f32::packing(4));\n    quality(ImplementationQuality::Generic)\n    store(f16, f64)\n);\nMMMRustKernel! {kernel::<f32, 4, 1> => generic_f32_4x1<f32>(4,1)\n    packing[1] = f16f16 => |k| k.with_packing(f16::packing(4), f16::packing(1));\n    packing[2] = f32f32bis => |k| k.with_packing(f32::packing(4), f32::packing(1));\n    packing[3] = f16f32 => |k| k.with_packing(f16::packing(4), f32::packing(1));\n    packing[4] = f32f16 => |k| k.with_packing(f32::packing(4), f16::packing(1));\n    packing[5] = q40f16 => |k| k.with_packing(pq40_r4(), f16::packing(1));\n    packing[6] = q40f16se => |k| k.with_packing(pq40_r4_se(), f16::packing(1));\n    packing[7] = q40f32 => |k| k.with_packing(pq40_r4(), f32::packing(1));\n    quality(ImplementationQuality::Generic)\n    store(f16, f64)\n}\n\n// f64 kernels\nMMMRustKernel!(kernel::<f64, 4, 4> => generic_f64_4x4<f64>(4,4)\n    quality(ImplementationQuality::Generic)\n    store(f16, f32));\nMMMRustKernel!(kernel::<f64, 4, 1> => generic_f64_4x1<f64>(4,1)\n    quality(ImplementationQuality::Generic)\n    store(f16, f32));\n\n// I32 kernels\nMMMRustKernel! {kernel::<i32, 4, 4> => generic_i32_4x4<i32>(4,4)\n    packing[1] = i8i8 => |k| k.with_packing(i8::packing(4), i8::packing(4));\n    quality(ImplementationQuality::Generic)\n    store(i8)\n}\n\nMMMRustKernel! {kernel::<i32, 4, 1> => generic_i32_4x1<i32>(4,1)\n    packing[1] = i8i8 => |k| k.with_packing(i8::packing(4), i8::packing(1));\n    quality(ImplementationQuality::Generic)\n    store(i8)\n}\n\n// extra tests kernels\n#[cfg(test)]\nMMMRustKernel!(kernel::<f32, 3, 2> => generic_f32_3x2<f32>(3,2) store(f16, f64));\n\n#[cfg(test)]\nMMMRustKernel! {kernel::<i32, 3, 2> => generic_i32_3x2<i32>(3,2)\n    packing[1] = i8i8 => |k| k.with_packing(i8::packing(3), i8::packing(2));\n    store(i8)\n}\n\npub fn plug(ops: &mut Ops) {\n    ops.mmm_impls.push(generic_f16_4x4.mmm());\n    ops.mmm_impls.push(generic_f16_4x1.mmm());\n    ops.mmm_impls.push(generic_f32_4x4.mmm());\n    ops.mmm_impls.push(generic_f32_4x1.mmm());\n    ops.mmm_impls.push(generic_f64_4x4.mmm());\n    ops.mmm_impls.push(generic_f64_4x1.mmm());\n    ops.mmm_impls.push(generic_i32_4x4.mmm());\n    ops.mmm_impls.push(generic_i32_4x1.mmm());\n}\n\n#[cfg(test)]\nmod test {\n\n    #[test]\n    fn kits() {\n        let mut ops = crate::generic();\n        super::plug(&mut ops);\n    }\n}\n"
  },
  {
    "path": "linalg/src/generic/reduce.rs",
    "content": "// Reduce<max> generic implementation\npub mod max {\n    pub use tract_data::internal::f16;\n\n    reduce_impl_wrap!(\n        f32,\n        SMax4,\n        4,\n        4,\n        (),\n        f32::MIN,\n        fn run(x: &[f32], _: ()) -> f32 {\n            debug_assert!(x.len() % Self::nr() == 0);\n            debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n            *x.iter().max_by(|a, b| a.total_cmp(b)).unwrap()\n        },\n        fn reduce_two(a: f32, b: f32) -> f32 {\n            a.max(b)\n        }\n    );\n\n    reduce_impl_wrap!(\n        f16,\n        HMax8,\n        8,\n        8,\n        (),\n        f16::MIN,\n        fn run(x: &[f16], _: ()) -> f16 {\n            debug_assert!(x.len() % Self::nr() == 0);\n            debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n            *x.iter().max_by(|a, b| a.total_cmp(b)).unwrap()\n        },\n        fn reduce_two(a: f16, b: f16) -> f16 {\n            a.max(b)\n        }\n    );\n\n    #[cfg(test)]\n    #[macro_use]\n    pub mod s {\n        crate::max_frame_tests!(true, f32, crate::generic::reduce::max::SMax4);\n    }\n\n    #[cfg(test)]\n    #[macro_use]\n    pub mod h {\n        use super::*;\n        crate::max_frame_tests!(true, f16, crate::generic::reduce::max::HMax8);\n    }\n}\n\n// Reduce<sum> generic implementation\npub mod sum {\n    use crate::num_traits::Zero;\n    pub use tract_data::internal::f16;\n\n    reduce_impl_wrap!(\n        f32,\n        SSum4,\n        4,\n        4,\n        (),\n        0.0,\n        fn run(x: &[f32], _: ()) -> f32 {\n            debug_assert!(x.len() % Self::nr() == 0);\n            debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n            x.iter().sum::<f32>()\n        },\n        fn reduce_two(a: f32, b: f32) -> f32 {\n            a + b\n        }\n    );\n\n    reduce_impl_wrap!(\n        f16,\n        HSum8,\n        8,\n        8,\n        (),\n        f16::zero(),\n        fn run(x: &[f16], _: ()) -> f16 {\n            debug_assert!(x.len() % Self::nr() == 0);\n            debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n            x.iter().sum::<f16>()\n        },\n        fn reduce_two(a: f16, b: f16) -> f16 {\n            a + b\n        }\n    );\n\n    #[cfg(test)]\n    #[macro_use]\n    pub mod s {\n        crate::sum_frame_tests!(true, f32, crate::generic::reduce::sum::SSum4);\n    }\n\n    #[cfg(test)]\n    #[macro_use]\n    pub mod h {\n        use super::*;\n        crate::sum_frame_tests!(true, f16, crate::generic::reduce::sum::HSum8);\n    }\n}\n\n// Softmax generic implementation\npub mod softmax_l2 {\n    use crate::num_traits::Zero;\n    use tract_data::internal::f16;\n\n    map_reduce_impl_wrap!(\n        f32,\n        SSoftMaxL2,\n        4,\n        4,\n        f32,\n        f32::MIN,\n        0.0,\n        fn run(x: &mut [f32], max: f32) -> f32 {\n            debug_assert!(x.len() % Self::nr() == 0);\n            debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n            let mut sum = 0.;\n            for v in x.iter_mut() {\n                let y = *v - max;\n                let y = fast_compact_exp_f32(y);\n                *v = y;\n                sum += y;\n            }\n            sum\n        },\n        fn reduce_two(a: f32, b: f32) -> f32 {\n            a + b\n        }\n    );\n\n    map_reduce_impl_wrap!(\n        f16,\n        HSoftMaxL2,\n        8,\n        8,\n        f16,\n        f16::MIN,\n        f16::zero(),\n        fn run(x: &mut [f16], max: f16) -> f16 {\n            debug_assert!(x.len() % Self::nr() == 0);\n            debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n            let mut sum = f16::zero();\n            for v in x.iter_mut() {\n                let y = *v - max;\n                let y = f16::from_f32(fast_compact_exp_f32(y.to_f32()));\n                *v = y;\n                sum += y;\n            }\n            sum\n        },\n        fn reduce_two(a: f16, b: f16) -> f16 {\n            a + b\n        }\n    );\n\n    // ported from https://github.com/gnuradio/volk/blob/master/kernels/volk/volk_32f_expfast_32f.h\n    // probably inspired from https://nic.schraudolph.org/pubs/Schraudolph99.pdf\n    // not that the cast to u32 deals with negative right, while implem in volk code are wrong in some\n    // corner cases (need a max(0,x) before the u32 conversion)\n    pub fn fast_compact_exp_f32(v: f32) -> f32 {\n        const MLN2: f32 = 0.6931471805f32;\n        const A: f32 = 8388608.0f32;\n        const B: f32 = 1065353216.0f32;\n        const C: f32 = 60801.0f32;\n        const SLOPE: f32 = A / MLN2;\n        const OFFSET: f32 = B - C;\n        f32::from_bits(((SLOPE * v) + OFFSET) as u32)\n    }\n\n    #[cfg(test)]\n    #[macro_use]\n    pub mod s {\n        crate::softmax_l2_frame_tests!(true, f32, super::SSoftMaxL2);\n    }\n\n    #[cfg(test)]\n    #[macro_use]\n    pub mod h {\n        use super::*;\n        crate::softmax_l2_frame_tests!(true, f16, HSoftMaxL2);\n    }\n}\n"
  },
  {
    "path": "linalg/src/generic/rounding.rs",
    "content": "use crate::frame::mmm::*;\nuse std::hash::{Hash, Hasher};\nuse std::ops::Mul;\nuse tract_data::prelude::f16;\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Scaler {\n    pub scale: f32,\n    pub mult: Option<i32>,\n    pub shift: isize,\n    pub policy: RoundingPolicy,\n}\n\nimpl Eq for Scaler {}\n\n#[allow(clippy::derived_hash_with_manual_eq)]\nimpl Hash for Scaler {\n    fn hash<H>(&self, state: &mut H)\n    where\n        H: Hasher,\n    {\n        Hash::hash(&self.scale.to_bits(), state)\n    }\n}\n\nimpl Scaler {\n    pub fn new(scale: f32, policy: RoundingPolicy) -> Self {\n        let (mult, shift) = Self::convert_scale_to_mult_shift(scale);\n        Self { scale, mult, shift, policy }\n    }\n\n    pub fn as_fused_spec(&self) -> FusedSpec<'_> {\n        if let Some(multiplier) = self.mult {\n            FusedSpec::QScale(self.shift, self.policy, multiplier)\n        } else if self.shift > 0 {\n            FusedSpec::RoundingShiftRight(self.shift as usize, self.policy)\n        } else {\n            FusedSpec::ShiftLeft((-self.shift) as usize)\n        }\n    }\n\n    // FIXME: Only to avoid fused op breaking\n    pub fn from_fuse_params(shift: isize, policy: RoundingPolicy, mult: i32) -> Self {\n        let scale = mult as f32 * 2f32.powi(-(31 + shift as i32));\n        Self { scale, mult: Some(mult), shift, policy }\n    }\n\n    #[inline]\n    // This function convert a scale (actually a fraction of two integers Q/D)\n    // into an integer multiplier and a shift (the multiplier being 1/2D in Q0_31).\n    fn convert_scale_to_mult_shift(scale: f32) -> (Option<i32>, isize) {\n        // Zero is a special case to handle\n        if scale == 0.0 {\n            return (None, 0);\n        }\n\n        // Convert f32 to bits representation with the following pattern\n        // Bit |  31  |  30-23   |   22-0    |\n        //     | Sign | Exponent |  Fraction |\n        let scale_bits = scale.to_bits();\n\n        // Get actual value of the exponent\n        let current_exponent = (scale_bits >> 23) & 0xff;\n\n        // Extract fractional part of the float with:\n        // - 0x007fffff that represents the mask of the 23 lower bits (fractional part)\n        // (partial because it doesn't include the hidden bit (24) of the float representation)\n        let partial_frac = scale_bits & 0x007fffff;\n\n        if partial_frac == 0 {\n            let shift = 127 - current_exponent as isize;\n            (None, shift)\n        } else {\n            // We add 0x800000 that represents the hidden bit set to one.\n            // Here the frac is encoded as a Q8_23.\n            let frac = partial_frac | 0x800000;\n\n            // We rescale the result to be in Q0_31\n            // We should have shifted the result by 8 but the frac value is in [1.0, 2.0)\n            // so we cannot do that (we would need one bit for the integer).\n            // Instead we devide the frac by two to be in [0.5, 1.0) in Q0_31\n            // which lead to a shift of (8-1 = 7).\n            let half_frac = (frac << 7) as i32;\n\n            // Compute the actual value of the shift\n            // Here, we remove one as half_frac needs to be multiplied by 2.\n            let shift = 127 - current_exponent as isize - 1;\n            (Some(half_frac), shift)\n        }\n    }\n}\n\nimpl Mul<f16> for Scaler {\n    type Output = f16;\n\n    #[inline]\n    fn mul(self, rhs: f16) -> Self::Output {\n        f16::from_f32(self.scale) * rhs\n    }\n}\n\nimpl Mul<f32> for Scaler {\n    type Output = f32;\n\n    #[inline]\n    fn mul(self, rhs: f32) -> Self::Output {\n        self.scale * rhs\n    }\n}\n\nimpl Mul<f64> for Scaler {\n    type Output = f64;\n\n    #[inline]\n    fn mul(self, rhs: f64) -> Self::Output {\n        self.scale as f64 * rhs\n    }\n}\n\nimpl Mul<Scaler> for f16 {\n    type Output = f16;\n\n    #[inline]\n    fn mul(self, rhs: Scaler) -> Self::Output {\n        rhs * self\n    }\n}\n\nimpl Mul<Scaler> for f32 {\n    type Output = f32;\n\n    #[inline]\n    fn mul(self, rhs: Scaler) -> Self::Output {\n        rhs * self\n    }\n}\n\nimpl Mul<Scaler> for f64 {\n    type Output = f64;\n\n    #[inline]\n    fn mul(self, rhs: Scaler) -> Self::Output {\n        rhs * self\n    }\n}\n\nimpl Mul<i32> for Scaler {\n    type Output = i32;\n\n    #[inline]\n    fn mul(self, rhs: i32) -> Self::Output {\n        let (val, shift) = if let Some(multiplier) = self.mult {\n            (multiplier as i64 * rhs as i64, self.shift + 31)\n        } else {\n            (rhs as i64, self.shift)\n        };\n\n        // Round according to rounding policy\n        use RoundingPolicy::*;\n        if shift > 0 {\n            let half: i64 = 1 << (shift - 1);\n            let nudge: i64 = match self.policy {\n                Zero => -1,\n                MinusInf => -((val >= 0) as i64),\n                PlusInf => -((val <= 0) as i64),\n                Away => 0,\n                Even => ((val.abs() >> shift) & 0x1) - 1,\n                Odd => -((val.abs() >> shift) & 0x1),\n                _ => panic!(),\n            };\n\n            (val.signum() * ((val.abs() + half + nudge) >> shift)) as i32\n        } else {\n            (val << -shift) as i32\n        }\n    }\n}\n\nimpl Mul<Scaler> for i32 {\n    type Output = i32;\n\n    #[inline]\n    fn mul(self, rhs: Scaler) -> Self::Output {\n        rhs * self\n    }\n}\n\npub trait ScaleShiftAndRound {\n    fn q_scale(self, scaler: Scaler) -> Self;\n    fn q_shl(self, shift: usize) -> Self;\n    fn q_shr(self, shift: usize, rp: RoundingPolicy) -> Self;\n}\n\nimpl ScaleShiftAndRound for f64 {\n    fn q_scale(self, scaler: Scaler) -> Self {\n        self * scaler\n    }\n    fn q_shl(self, shift: usize) -> Self {\n        self * 2f64.powi(shift as i32)\n    }\n    fn q_shr(self, shift: usize, _rp: RoundingPolicy) -> Self {\n        self * 2f64.powi(-(shift as i32))\n    }\n}\n\nimpl ScaleShiftAndRound for f32 {\n    fn q_scale(self, scaler: Scaler) -> Self {\n        self * scaler\n    }\n    fn q_shl(self, shift: usize) -> Self {\n        self * 2f32.powi(shift as i32)\n    }\n    fn q_shr(self, shift: usize, _rp: RoundingPolicy) -> Self {\n        self * 2f32.powi(-(shift as i32))\n    }\n}\n\nimpl ScaleShiftAndRound for f16 {\n    fn q_scale(self, scaler: Scaler) -> Self {\n        self * scaler\n    }\n    fn q_shl(self, shift: usize) -> Self {\n        self * f16::from_f32(2f32.powi(shift as i32))\n    }\n    fn q_shr(self, shift: usize, _rp: RoundingPolicy) -> Self {\n        self * f16::from_f32(2f32.powi(-(shift as i32)))\n    }\n}\n\nimpl ScaleShiftAndRound for i32 {\n    fn q_scale(self, scaler: Scaler) -> Self {\n        self * scaler\n    }\n    fn q_shr(self, shift: usize, rp: RoundingPolicy) -> Self {\n        use RoundingPolicy::*;\n        let half: i32 = 1 << (shift - 1);\n        let nudge: i32 = match rp {\n            Zero => -1,\n            MinusInf => -((self >= 0) as i32),\n            PlusInf => -((self <= 0) as i32),\n            Away => 0,\n            Even => ((self.abs() >> shift) & 0x1) - 1,\n            Odd => -((self.abs() >> shift) & 0x1),\n            _ => panic!(),\n        };\n        self.signum() * ((self.abs() + half + nudge) >> shift)\n    }\n    fn q_shl(self, shift: usize) -> Self {\n        self << shift\n    }\n}\n\n// 6 / 4 -> 1.5 -> arrondi: 2.  rien a faire\n// 2 / 4 -> 0.5 -> arrondi: 1. veut 0 -> nudge = -1\n\n#[cfg(test)]\nmod test {\n    use super::RoundingPolicy::*;\n    use super::*;\n\n    #[test]\n    fn test_scale_rounding_f32() {\n        assert_eq!(0f32.q_scale(Scaler::new(0.5, Zero)), 0.0);\n        assert_eq!(1f32.q_scale(Scaler::new(0.5, Zero)), 0.5);\n        assert_eq!(2f32.q_scale(Scaler::new(0.5, Zero)), 1.0);\n        assert_eq!(3f32.q_scale(Scaler::new(0.5, Zero)), 1.5);\n        assert_eq!((-1f32).q_scale(Scaler::new(0.5, Zero)), -0.5);\n        assert_eq!((-2f32).q_scale(Scaler::new(0.5, Zero)), -1.0);\n        assert_eq!((-3f32).q_scale(Scaler::new(0.5, Zero)), -1.5);\n    }\n\n    #[test]\n    fn test_shift_rounding_zero() {\n        assert_eq!(0i32.q_shr(1, Zero), 0);\n        assert_eq!(1i32.q_shr(1, Zero), 0);\n        assert_eq!(2i32.q_shr(1, Zero), 1);\n        assert_eq!(3i32.q_shr(1, Zero), 1);\n        assert_eq!(0i32.q_shr(2, Zero), 0);\n        assert_eq!(1i32.q_shr(2, Zero), 0);\n        assert_eq!(2i32.q_shr(2, Zero), 0);\n        assert_eq!(3i32.q_shr(2, Zero), 1);\n        assert_eq!(4i32.q_shr(2, Zero), 1);\n        assert_eq!(5i32.q_shr(2, Zero), 1);\n        assert_eq!(6i32.q_shr(2, Zero), 1);\n        assert_eq!((-1i32).q_shr(2, Zero), 0);\n        assert_eq!((-2i32).q_shr(2, Zero), 0);\n        assert_eq!((-3i32).q_shr(2, Zero), -1);\n        assert_eq!((-4i32).q_shr(2, Zero), -1);\n        assert_eq!((-5i32).q_shr(2, Zero), -1);\n        assert_eq!((-6i32).q_shr(2, Zero), -1);\n    }\n\n    #[test]\n    fn test_scale_rounding_zero() {\n        assert_eq!(0i32.q_scale(Scaler::new(0.5, Zero)), 0);\n        assert_eq!(1i32.q_scale(Scaler::new(0.5, Zero)), 0);\n        assert_eq!(2i32.q_scale(Scaler::new(0.5, Zero)), 1);\n        assert_eq!(3i32.q_scale(Scaler::new(0.5, Zero)), 1);\n        assert_eq!((-1i32).q_scale(Scaler::new(0.5, Zero)), 0);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.5, Zero)), -1);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.5, Zero)), -1);\n        assert_eq!(2i32.q_scale(Scaler::new(0.25, Zero)), 0);\n        assert_eq!(3i32.q_scale(Scaler::new(0.25, Zero)), 1);\n        assert_eq!(4i32.q_scale(Scaler::new(0.25, Zero)), 1);\n        assert_eq!(5i32.q_scale(Scaler::new(0.25, Zero)), 1);\n        assert_eq!(6i32.q_scale(Scaler::new(0.25, Zero)), 1);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.25, Zero)), 0);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.25, Zero)), -1);\n        assert_eq!((-4i32).q_scale(Scaler::new(0.25, Zero)), -1);\n        assert_eq!((-5i32).q_scale(Scaler::new(0.25, Zero)), -1);\n        assert_eq!((-6i32).q_scale(Scaler::new(0.25, Zero)), -1);\n    }\n\n    #[test]\n    fn test_shift_rounding_away() {\n        assert_eq!(0i32.q_shr(1, Away), 0);\n        assert_eq!(1i32.q_shr(1, Away), 1);\n        assert_eq!(2i32.q_shr(1, Away), 1);\n        assert_eq!(3i32.q_shr(1, Away), 2);\n        assert_eq!(0i32.q_shr(2, Away), 0);\n        assert_eq!(1i32.q_shr(2, Away), 0);\n        assert_eq!(2i32.q_shr(2, Away), 1);\n        assert_eq!(3i32.q_shr(2, Away), 1);\n        assert_eq!(4i32.q_shr(2, Away), 1);\n        assert_eq!(5i32.q_shr(2, Away), 1);\n        assert_eq!(6i32.q_shr(2, Away), 2);\n        assert_eq!((-1i32).q_shr(2, Away), 0);\n        assert_eq!((-2i32).q_shr(2, Away), -1);\n        assert_eq!((-3i32).q_shr(2, Away), -1);\n        assert_eq!((-4i32).q_shr(2, Away), -1);\n        assert_eq!((-5i32).q_shr(2, Away), -1);\n        assert_eq!((-6i32).q_shr(2, Away), -2);\n    }\n\n    #[test]\n    fn test_scale_rounding_away() {\n        assert_eq!(0i32.q_scale(Scaler::new(0.5, Away)), 0);\n        assert_eq!(1i32.q_scale(Scaler::new(0.5, Away)), 1);\n        assert_eq!(2i32.q_scale(Scaler::new(0.5, Away)), 1);\n        assert_eq!(3i32.q_scale(Scaler::new(0.5, Away)), 2);\n        assert_eq!((-1i32).q_scale(Scaler::new(0.5, Away)), -1);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.5, Away)), -1);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.5, Away)), -2);\n        assert_eq!(2i32.q_scale(Scaler::new(0.25, Away)), 1);\n        assert_eq!(3i32.q_scale(Scaler::new(0.25, Away)), 1);\n        assert_eq!(4i32.q_scale(Scaler::new(0.25, Away)), 1);\n        assert_eq!(5i32.q_scale(Scaler::new(0.25, Away)), 1);\n        assert_eq!(6i32.q_scale(Scaler::new(0.25, Away)), 2);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.25, Away)), -1);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.25, Away)), -1);\n        assert_eq!((-4i32).q_scale(Scaler::new(0.25, Away)), -1);\n        assert_eq!((-5i32).q_scale(Scaler::new(0.25, Away)), -1);\n        assert_eq!((-6i32).q_scale(Scaler::new(0.25, Away)), -2);\n    }\n\n    #[test]\n    fn test_shift_rounding_plus_inf() {\n        assert_eq!(0i32.q_shr(1, PlusInf), 0);\n        assert_eq!(1i32.q_shr(1, PlusInf), 1);\n        assert_eq!(2i32.q_shr(1, PlusInf), 1);\n        assert_eq!(3i32.q_shr(1, PlusInf), 2);\n        assert_eq!(0i32.q_shr(2, PlusInf), 0);\n        assert_eq!(1i32.q_shr(2, PlusInf), 0);\n        assert_eq!(2i32.q_shr(2, PlusInf), 1);\n        assert_eq!(3i32.q_shr(2, PlusInf), 1);\n        assert_eq!(4i32.q_shr(2, PlusInf), 1);\n        assert_eq!(5i32.q_shr(2, PlusInf), 1);\n        assert_eq!(6i32.q_shr(2, PlusInf), 2);\n        assert_eq!((-1i32).q_shr(2, PlusInf), 0);\n        assert_eq!((-2i32).q_shr(2, PlusInf), 0);\n        assert_eq!((-3i32).q_shr(2, PlusInf), -1);\n        assert_eq!((-4i32).q_shr(2, PlusInf), -1);\n        assert_eq!((-5i32).q_shr(2, PlusInf), -1);\n        assert_eq!((-6i32).q_shr(2, PlusInf), -1);\n    }\n\n    #[test]\n    fn test_scale_rounding_plus_inf() {\n        assert_eq!(0i32.q_scale(Scaler::new(0.5, PlusInf)), 0);\n        assert_eq!(1i32.q_scale(Scaler::new(0.5, PlusInf)), 1);\n        assert_eq!(2i32.q_scale(Scaler::new(0.5, PlusInf)), 1);\n        assert_eq!(3i32.q_scale(Scaler::new(0.5, PlusInf)), 2);\n        assert_eq!((-1i32).q_scale(Scaler::new(0.5, PlusInf)), 0);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.5, PlusInf)), -1);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.5, PlusInf)), -1);\n        assert_eq!(2i32.q_scale(Scaler::new(0.25, PlusInf)), 1);\n        assert_eq!(3i32.q_scale(Scaler::new(0.25, PlusInf)), 1);\n        assert_eq!(4i32.q_scale(Scaler::new(0.25, PlusInf)), 1);\n        assert_eq!(5i32.q_scale(Scaler::new(0.25, PlusInf)), 1);\n        assert_eq!(6i32.q_scale(Scaler::new(0.25, PlusInf)), 2);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.25, PlusInf)), 0);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.25, PlusInf)), -1);\n        assert_eq!((-4i32).q_scale(Scaler::new(0.25, PlusInf)), -1);\n        assert_eq!((-5i32).q_scale(Scaler::new(0.25, PlusInf)), -1);\n        assert_eq!((-6i32).q_scale(Scaler::new(0.25, PlusInf)), -1);\n    }\n\n    #[test]\n    fn test_shift_rounding_minus_inf() {\n        assert_eq!(0i32.q_shr(1, MinusInf), 0);\n        assert_eq!(1i32.q_shr(1, MinusInf), 0);\n        assert_eq!(2i32.q_shr(1, MinusInf), 1);\n        assert_eq!(3i32.q_shr(1, MinusInf), 1);\n        assert_eq!(0i32.q_shr(2, MinusInf), 0);\n        assert_eq!(1i32.q_shr(2, MinusInf), 0);\n        assert_eq!(2i32.q_shr(2, MinusInf), 0);\n        assert_eq!(3i32.q_shr(2, MinusInf), 1);\n        assert_eq!(4i32.q_shr(2, MinusInf), 1);\n        assert_eq!(5i32.q_shr(2, MinusInf), 1);\n        assert_eq!(6i32.q_shr(2, MinusInf), 1);\n        assert_eq!((-1i32).q_shr(2, MinusInf), 0);\n        assert_eq!((-2i32).q_shr(2, MinusInf), -1);\n        assert_eq!((-3i32).q_shr(2, MinusInf), -1);\n        assert_eq!((-4i32).q_shr(2, MinusInf), -1);\n        assert_eq!((-5i32).q_shr(2, MinusInf), -1);\n        assert_eq!((-6i32).q_shr(2, MinusInf), -2);\n    }\n\n    #[test]\n    fn test_scale_rounding_minus_inf() {\n        assert_eq!(0i32.q_scale(Scaler::new(0.5, MinusInf)), 0);\n        assert_eq!(1i32.q_scale(Scaler::new(0.5, MinusInf)), 0);\n        assert_eq!(2i32.q_scale(Scaler::new(0.5, MinusInf)), 1);\n        assert_eq!(3i32.q_scale(Scaler::new(0.5, MinusInf)), 1);\n        assert_eq!((-1i32).q_scale(Scaler::new(0.5, MinusInf)), -1);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.5, MinusInf)), -1);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.5, MinusInf)), -2);\n        assert_eq!(2i32.q_scale(Scaler::new(0.25, MinusInf)), 0);\n        assert_eq!(3i32.q_scale(Scaler::new(0.25, MinusInf)), 1);\n        assert_eq!(4i32.q_scale(Scaler::new(0.25, MinusInf)), 1);\n        assert_eq!(5i32.q_scale(Scaler::new(0.25, MinusInf)), 1);\n        assert_eq!(6i32.q_scale(Scaler::new(0.25, MinusInf)), 1);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.25, MinusInf)), -1);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.25, MinusInf)), -1);\n        assert_eq!((-4i32).q_scale(Scaler::new(0.25, MinusInf)), -1);\n        assert_eq!((-5i32).q_scale(Scaler::new(0.25, MinusInf)), -1);\n        assert_eq!((-6i32).q_scale(Scaler::new(0.25, MinusInf)), -2);\n        //assert_eq!((-9i32).q_scale(ONE_OVER_TWO_IN_Q0_30, 5, MinusInf), 0);\n    }\n\n    #[test]\n    fn test_shift_rounding_even() {\n        assert_eq!(0i32.q_shr(1, Even), 0);\n        assert_eq!(1i32.q_shr(1, Even), 0);\n        assert_eq!(2i32.q_shr(1, Even), 1);\n        assert_eq!(3i32.q_shr(1, Even), 2);\n        assert_eq!(0i32.q_shr(2, Even), 0);\n        assert_eq!(1i32.q_shr(2, Even), 0);\n        assert_eq!(2i32.q_shr(2, Even), 0);\n        assert_eq!(3i32.q_shr(2, Even), 1);\n        assert_eq!(4i32.q_shr(2, Even), 1);\n        assert_eq!(5i32.q_shr(2, Even), 1);\n        assert_eq!(6i32.q_shr(2, Even), 2);\n        assert_eq!((-1i32).q_shr(2, Even), 0);\n        assert_eq!((-2i32).q_shr(2, Even), 0);\n        assert_eq!((-3i32).q_shr(2, Even), -1);\n        assert_eq!((-4i32).q_shr(2, Even), -1);\n        assert_eq!((-5i32).q_shr(2, Even), -1);\n        assert_eq!((-6i32).q_shr(2, Even), -2);\n    }\n\n    #[test]\n    fn test_scale_rounding_even() {\n        assert_eq!(0i32.q_scale(Scaler::new(0.5, Even)), 0);\n        assert_eq!(1i32.q_scale(Scaler::new(0.5, Even)), 0);\n        assert_eq!(2i32.q_scale(Scaler::new(0.5, Even)), 1);\n        assert_eq!(3i32.q_scale(Scaler::new(0.5, Even)), 2);\n        assert_eq!((-1i32).q_scale(Scaler::new(0.5, Even)), 0);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.5, Even)), -1);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.5, Even)), -2);\n        assert_eq!(2i32.q_scale(Scaler::new(0.25, Even)), 0);\n        assert_eq!(3i32.q_scale(Scaler::new(0.25, Even)), 1);\n        assert_eq!(4i32.q_scale(Scaler::new(0.25, Even)), 1);\n        assert_eq!(5i32.q_scale(Scaler::new(0.25, Even)), 1);\n        assert_eq!(6i32.q_scale(Scaler::new(0.25, Even)), 2);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.25, Even)), 0);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.25, Even)), -1);\n        assert_eq!((-4i32).q_scale(Scaler::new(0.25, Even)), -1);\n        assert_eq!((-5i32).q_scale(Scaler::new(0.25, Even)), -1);\n        assert_eq!((-6i32).q_scale(Scaler::new(0.25, Even)), -2);\n    }\n\n    #[test]\n    fn test_shift_rounding_odd() {\n        assert_eq!(0i32.q_shr(1, Odd), 0);\n        assert_eq!(1i32.q_shr(1, Odd), 1);\n        assert_eq!(2i32.q_shr(1, Odd), 1);\n        assert_eq!(3i32.q_shr(1, Odd), 1);\n        assert_eq!(0i32.q_shr(2, Odd), 0);\n        assert_eq!(1i32.q_shr(2, Odd), 0);\n        assert_eq!(2i32.q_shr(2, Odd), 1);\n        assert_eq!(3i32.q_shr(2, Odd), 1);\n        assert_eq!(4i32.q_shr(2, Odd), 1);\n        assert_eq!(5i32.q_shr(2, Odd), 1);\n        assert_eq!(6i32.q_shr(2, Odd), 1);\n        assert_eq!((-1i32).q_shr(2, Odd), 0);\n        assert_eq!((-2i32).q_shr(2, Odd), -1);\n        assert_eq!((-3i32).q_shr(2, Odd), -1);\n        assert_eq!((-4i32).q_shr(2, Odd), -1);\n        assert_eq!((-5i32).q_shr(2, Odd), -1);\n        assert_eq!((-6i32).q_shr(2, Odd), -1);\n    }\n\n    #[test]\n    fn test_scale_rounding_odd() {\n        assert_eq!(0i32.q_scale(Scaler::new(0.5, Odd)), 0);\n        assert_eq!(1i32.q_scale(Scaler::new(0.5, Odd)), 1);\n        assert_eq!(2i32.q_scale(Scaler::new(0.5, Odd)), 1);\n        assert_eq!(3i32.q_scale(Scaler::new(0.5, Odd)), 1);\n        assert_eq!((-1i32).q_scale(Scaler::new(0.5, Odd)), -1);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.5, Odd)), -1);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.5, Odd)), -1);\n        assert_eq!(2i32.q_scale(Scaler::new(0.25, Odd)), 1);\n        assert_eq!(3i32.q_scale(Scaler::new(0.25, Odd)), 1);\n        assert_eq!(4i32.q_scale(Scaler::new(0.25, Odd)), 1);\n        assert_eq!(5i32.q_scale(Scaler::new(0.25, Odd)), 1);\n        assert_eq!(6i32.q_scale(Scaler::new(0.25, Odd)), 1);\n        assert_eq!((-2i32).q_scale(Scaler::new(0.25, Odd)), -1);\n        assert_eq!((-3i32).q_scale(Scaler::new(0.25, Odd)), -1);\n        assert_eq!((-4i32).q_scale(Scaler::new(0.25, Odd)), -1);\n        assert_eq!((-5i32).q_scale(Scaler::new(0.25, Odd)), -1);\n        assert_eq!((-6i32).q_scale(Scaler::new(0.25, Odd)), -1);\n    }\n}\n"
  },
  {
    "path": "linalg/src/generic/sigmoid.rs",
    "content": "#![allow(clippy::excessive_precision)]\nuse crate::frame::element_wise::ElementWiseKer;\nuse tract_data::internal::*;\n\npub fn ssigmoid(x: f32) -> f32 {\n    const LOW: f32 = -18.6;\n    const HIGH: f32 = -LOW;\n\n    const ALPHA_13: f32 = -4.433153405e-18;\n    const ALPHA_11: f32 = 1.169974371e-14;\n    const ALPHA_9: f32 = -1.875289645e-11;\n    const ALPHA_7: f32 = 4.257889523e-8;\n    const ALPHA_5: f32 = 0.00004811817576;\n    const ALPHA_3: f32 = 0.008163842030;\n    const ALPHA_1: f32 = 0.2499999971;\n    const BETA_6: f32 = 3.922935744e-6;\n    const BETA_4: f32 = 0.001524872358;\n    const BETA_2: f32 = 0.1159886749;\n    const BETA_0: f32 = 1.0;\n\n    let x = x.clamp(LOW, HIGH);\n\n    let x2 = x * x;\n\n    let p = ALPHA_13;\n    let p = x2 * p + ALPHA_11;\n    let p = x2 * p + ALPHA_9;\n    let p = x2 * p + ALPHA_7;\n    let p = x2 * p + ALPHA_5;\n    let p = x2 * p + ALPHA_3;\n    let p = x2 * p + ALPHA_1;\n    let p = p * x;\n\n    let q = BETA_6;\n    let q = x2 * q + BETA_4;\n    let q = x2 * q + BETA_2;\n    let q = x2 * q + BETA_0;\n\n    p / q + 0.5\n}\n\npub fn hsigmoid(x: f16) -> f16 {\n    /*\n     * (x (0.249895 + x^2 (0.00400222 - 0.0000124702 x^2)))\n     * /\n     * (1. + 0.098734 x^2)\n     */\n\n    const LOW: f16 = f16::from_f32_const(-6.92);\n    const HIGH: f16 = f16::from_f32_const(6.92);\n\n    const ALPHA_5: f16 = f16::from_f32_const(-0.0000124702);\n    const ALPHA_3: f16 = f16::from_f32_const(0.00400222);\n    const ALPHA_1: f16 = f16::from_f32_const(0.249895);\n\n    const BETA_2: f16 = f16::from_f32_const(0.098734);\n    const BETA_0: f16 = f16::from_f32_const(1.0);\n\n    let x = x.clamp(LOW, HIGH);\n\n    let x2 = x * x;\n\n    let p = ALPHA_5;\n    let p = x2 * p + ALPHA_3;\n    let p = x2 * p + ALPHA_1;\n    let p = p * x;\n\n    let q = BETA_2;\n    let q = x2 * q + BETA_0;\n\n    p / q + f16::from_f32_const(0.5)\n}\n\n#[derive(Clone, Debug)]\npub struct SSigmoid4;\n\nimpl ElementWiseKer<f32> for SSigmoid4 {\n    fn name() -> &'static str {\n        \"generic\"\n    }\n\n    fn alignment_bytes() -> usize {\n        16\n    }\n\n    fn alignment_items() -> usize {\n        4\n    }\n\n    fn nr() -> usize {\n        4\n    }\n\n    fn run(x: &mut [f32], _: ()) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = ssigmoid(*px))\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct HSigmoid8;\n\nimpl ElementWiseKer<f16> for HSigmoid8 {\n    fn name() -> &'static str {\n        \"generic\"\n    }\n\n    fn alignment_bytes() -> usize {\n        16\n    }\n\n    fn alignment_items() -> usize {\n        4\n    }\n\n    fn nr() -> usize {\n        8\n    }\n\n    fn run(x: &mut [f16], _: ()) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = hsigmoid(*px))\n    }\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod s {\n    sigmoid_frame_tests!(true, f32, crate::generic::sigmoid::SSigmoid4);\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod h {\n    sigmoid_frame_tests!(true, tract_data::internal::f16, crate::generic::sigmoid::HSigmoid8);\n}\n"
  },
  {
    "path": "linalg/src/generic/tanh.rs",
    "content": "#![allow(clippy::excessive_precision)]\nuse crate::frame::element_wise::ElementWiseKer;\nuse tract_data::internal::*;\n\npub fn stanh(x: f32) -> f32 {\n    const LOW: f32 = -8.9;\n    const HIGH: f32 = 8.9;\n\n    const ALPHA_13: f32 = -8.488492677e-14;\n    const ALPHA_11: f32 = 5.277853000e-11;\n    const ALPHA_9: f32 = -2.022500419e-8;\n    const ALPHA_7: f32 = 0.00001115424833;\n    const ALPHA_5: f32 = 0.003103950131;\n    const ALPHA_3: f32 = 0.1308400453;\n    const ALPHA_1: f32 = 0.9999999934;\n\n    const BETA_6: f32 = 0.0002546136580;\n    const BETA_4: f32 = 0.02449515379;\n    const BETA_2: f32 = 0.4641733162;\n    const BETA_0: f32 = 1.0;\n\n    let x = x.clamp(LOW, HIGH);\n\n    let x2 = x * x;\n\n    let p = ALPHA_13;\n    let p = x2 * p + ALPHA_11;\n    let p = x2 * p + ALPHA_9;\n    let p = x2 * p + ALPHA_7;\n    let p = x2 * p + ALPHA_5;\n    let p = x2 * p + ALPHA_3;\n    let p = x2 * p + ALPHA_1;\n    let p = p * x;\n\n    let q = BETA_6;\n    let q = x2 * q + BETA_4;\n    let q = x2 * q + BETA_2;\n    let q = x2 * q + BETA_0;\n\n    p / q\n}\n\npub fn htanh(x: f16) -> f16 {\n    const LOW: f16 = f16::from_f32_const(-3.84);\n    const HIGH: f16 = f16::from_f32_const(3.84);\n\n    const ALPHA_3: f16 = f16::from_f32_const(0.082654955);\n    const ALPHA_1: f16 = f16::from_f32_const(0.99963124);\n\n    const BETA_4: f16 = f16::from_f32_const(0.0065383179);\n    const BETA_2: f16 = f16::from_f32_const(0.41401828);\n    const BETA_0: f16 = f16::from_f32_const(1.0);\n\n    let x = x.clamp(LOW, HIGH);\n\n    let x2 = x * x;\n\n    let p = ALPHA_3;\n    let p = x2 * p + ALPHA_1;\n    let p = p * x;\n\n    let q = BETA_4;\n    let q = x2 * q + BETA_2;\n    let q = x2 * q + BETA_0;\n\n    p / q\n}\n\n#[derive(Clone, Debug)]\npub struct STanh4;\n\nimpl ElementWiseKer<f32> for STanh4 {\n    fn name() -> &'static str {\n        \"generic\"\n    }\n\n    fn alignment_items() -> usize {\n        16\n    }\n\n    fn alignment_bytes() -> usize {\n        16\n    }\n\n    fn nr() -> usize {\n        4\n    }\n\n    fn run(x: &mut [f32], _: ()) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = stanh(*px))\n    }\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod s {\n    tanh_frame_tests!(true, f32, crate::generic::tanh::STanh4);\n}\n\n#[derive(Clone, Debug)]\npub struct HTanh8;\n\nimpl ElementWiseKer<f16> for HTanh8 {\n    fn name() -> &'static str {\n        \"generic\"\n    }\n\n    fn alignment_items() -> usize {\n        16\n    }\n\n    fn alignment_bytes() -> usize {\n        16\n    }\n\n    fn nr() -> usize {\n        8\n    }\n\n    fn run(x: &mut [f16], _: ()) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        x.iter_mut().for_each(|px| *px = htanh(*px))\n    }\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod h {\n    tanh_frame_tests!(true, tract_data::internal::f16, crate::generic::tanh::HTanh8);\n}\n"
  },
  {
    "path": "linalg/src/generic/unicast.rs",
    "content": "pub use tract_data::internal::f16;\nunicast_impl_wrap!(\n    f32,\n    SUnicastMul4,\n    4,\n    4,\n    fn run(a: &mut [f32], b: &[f32]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a *= b)\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    HUnicastMul8,\n    8,\n    8,\n    fn run(a: &mut [f16], b: &[f16]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a *= b)\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    SUnicastAdd4,\n    4,\n    4,\n    fn run(a: &mut [f32], b: &[f32]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a += b)\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    HUnicastAdd8,\n    8,\n    8,\n    fn run(a: &mut [f16], b: &[f16]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a += b)\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    SUnicastSub4,\n    4,\n    4,\n    fn run(a: &mut [f32], b: &[f32]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a -= b)\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    HUnicastSub8,\n    8,\n    8,\n    fn run(a: &mut [f16], b: &[f16]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a -= b)\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    SUnicastSubF4,\n    4,\n    4,\n    fn run(a: &mut [f32], b: &[f32]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a = *b - *a)\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    HUnicastSubF8,\n    8,\n    8,\n    fn run(a: &mut [f16], b: &[f16]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a = *b - *a)\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    SUnicastMin4,\n    4,\n    4,\n    fn run(a: &mut [f32], b: &[f32]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a = a.min(*b))\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    HUnicastMin8,\n    8,\n    8,\n    fn run(a: &mut [f16], b: &[f16]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a = a.min(*b))\n    }\n);\n\nunicast_impl_wrap!(\n    f32,\n    SUnicastMax4,\n    4,\n    4,\n    fn run(a: &mut [f32], b: &[f32]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a = a.max(*b))\n    }\n);\n\nunicast_impl_wrap!(\n    f16,\n    HUnicastMax8,\n    8,\n    8,\n    fn run(a: &mut [f16], b: &[f16]) {\n        debug_assert!(a.len() == b.len());\n        debug_assert!(a.len() % Self::nr() == 0);\n        debug_assert!(a.as_ptr() as usize % Self::alignment_bytes() == 0);\n        debug_assert!(b.as_ptr() as usize % Self::alignment_bytes() == 0);\n        a.iter_mut().zip(b.iter()).for_each(|(a, b)| *a = a.max(*b))\n    }\n);\n\n#[cfg(test)]\n#[macro_use]\npub mod s {\n    use super::*;\n    use proptest::strategy::Strategy;\n    crate::unicast_frame_tests!(true, f32, SUnicastMul4, |a, b| a * b);\n    crate::unicast_frame_tests!(true, f32, SUnicastAdd4, |a, b| a + b);\n    crate::unicast_frame_tests!(true, f32, SUnicastSub4, |a, b| a - b);\n    crate::unicast_frame_tests!(true, f32, SUnicastSubF4, |a, b| b - a);\n    crate::unicast_frame_tests!(true, f32, SUnicastMin4, |a, b| a.min(b));\n    crate::unicast_frame_tests!(true, f32, SUnicastMax4, |a, b| a.max(b));\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod h {\n    use super::*;\n    use proptest::strategy::Strategy;\n    crate::unicast_frame_tests!(true, f16, HUnicastMul8, |a, b| a * b);\n    crate::unicast_frame_tests!(true, f16, HUnicastAdd8, |a, b| a + b);\n    crate::unicast_frame_tests!(true, f16, HUnicastSub8, |a, b| a - b);\n    crate::unicast_frame_tests!(true, f16, HUnicastSubF8, |a, b| b - a);\n    crate::unicast_frame_tests!(true, f16, HUnicastMin8, |a, b| a.min(b));\n    crate::unicast_frame_tests!(true, f16, HUnicastMax8, |a, b| a.max(b));\n}\n"
  },
  {
    "path": "linalg/src/generic.rs",
    "content": "pub mod by_scalar;\npub mod erf;\npub mod leaky_relu;\npub mod lut;\npub mod mmm;\npub mod reduce;\npub mod rounding;\npub mod sigmoid;\npub mod tanh;\npub mod unicast;\n\nuse tract_data::prelude::DatumType;\n\nuse crate::by_scalar::ByScalarKer;\nuse crate::unicast::UnicastKer;\nuse crate::{BinOp, LinalgRegistry};\n\npub use self::by_scalar::{HMulByScalar8, SMulByScalar4};\npub use self::erf::SErf4;\npub use self::leaky_relu::{HLeakyRelu8, SLeakyRelu4};\npub use self::lut::GenericLut8;\npub use self::reduce::softmax_l2::SSoftMaxL2;\npub use self::rounding::{ScaleShiftAndRound, Scaler};\npub use self::sigmoid::{HSigmoid8, SSigmoid4};\npub use self::tanh::{HTanh8, STanh4};\n\npub(crate) fn register_all_unicast(registry: &mut LinalgRegistry) {\n    registry.insert((BinOp::Mul, DatumType::F32), Box::new(|| unicast::SUnicastMul4::bin()));\n    registry.insert((BinOp::Mul, DatumType::F16), Box::new(|| unicast::HUnicastMul8::bin()));\n    registry.insert((BinOp::Add, DatumType::F32), Box::new(|| unicast::SUnicastAdd4::bin()));\n    registry.insert((BinOp::Add, DatumType::F16), Box::new(|| unicast::HUnicastAdd8::bin()));\n    registry.insert((BinOp::Sub, DatumType::F32), Box::new(|| unicast::SUnicastSub4::bin()));\n    registry.insert((BinOp::Sub, DatumType::F16), Box::new(|| unicast::HUnicastSub8::bin()));\n    registry.insert((BinOp::SubF, DatumType::F32), Box::new(|| unicast::SUnicastSubF4::bin()));\n    registry.insert((BinOp::SubF, DatumType::F16), Box::new(|| unicast::HUnicastSubF8::bin()));\n    registry.insert((BinOp::Min, DatumType::F32), Box::new(|| unicast::SUnicastMin4::bin()));\n    registry.insert((BinOp::Min, DatumType::F16), Box::new(|| unicast::HUnicastMin8::bin()));\n    registry.insert((BinOp::Max, DatumType::F32), Box::new(|| unicast::SUnicastMax4::bin()));\n    registry.insert((BinOp::Max, DatumType::F16), Box::new(|| unicast::HUnicastMax8::bin()));\n}\n\npub(crate) fn register_all_by_scalar(registry: &mut LinalgRegistry) {\n    registry.insert((BinOp::Mul, DatumType::F32), Box::new(|| by_scalar::SMulByScalar4::bin()));\n    registry.insert((BinOp::Mul, DatumType::F16), Box::new(|| by_scalar::HMulByScalar8::bin()));\n    registry.insert((BinOp::Add, DatumType::F32), Box::new(|| by_scalar::SAddByScalar4::bin()));\n    registry.insert((BinOp::Add, DatumType::F16), Box::new(|| by_scalar::HAddByScalar8::bin()));\n    registry.insert((BinOp::Sub, DatumType::F32), Box::new(|| by_scalar::SSubByScalar4::bin()));\n    registry.insert((BinOp::Sub, DatumType::F16), Box::new(|| by_scalar::HSubByScalar8::bin()));\n    registry.insert((BinOp::SubF, DatumType::F32), Box::new(|| by_scalar::SSubFByScalar4::bin()));\n    registry.insert((BinOp::SubF, DatumType::F16), Box::new(|| by_scalar::HSubFByScalar8::bin()));\n    registry.insert((BinOp::Min, DatumType::F32), Box::new(|| by_scalar::SMinByScalar4::bin()));\n    registry.insert((BinOp::Min, DatumType::F16), Box::new(|| by_scalar::HMinByScalar8::bin()));\n    registry.insert((BinOp::Max, DatumType::F32), Box::new(|| by_scalar::SMaxByScalar4::bin()));\n    registry.insert((BinOp::Max, DatumType::F16), Box::new(|| by_scalar::HMaxByScalar8::bin()));\n}\n"
  },
  {
    "path": "linalg/src/hwbench/bandwidth.rs",
    "content": "use tract_data::itertools::Itertools;\nuse tract_data::prelude::Blob;\n\nuse super::runner;\n\n#[cfg(target_arch = \"x86_64\")]\nstatic mut HAS_AVX512: bool = false;\n\n#[cfg(target_arch = \"x86_64\")]\n#[inline(never)]\nfn load_a_slice(slice: &[u8], loops: usize) {\n    unsafe {\n        if HAS_AVX512 {\n            for _ in 0..loops {\n                let mut ptr = slice.as_ptr();\n                let end = ptr.add(slice.len());\n                while ptr < end {\n                    std::arch::asm!(\"\n                vmovaps zmm0, [rsi]\n                vmovaps zmm1, [rsi + 64]\n                vmovaps zmm2, [rsi + 128]\n                vmovaps zmm3, [rsi + 192]\n                vmovaps zmm4, [rsi + 256]\n                vmovaps zmm5, [rsi + 320]\n                vmovaps zmm6, [rsi + 384]\n                vmovaps zmm7, [rsi + 448]\n                    \", inout(\"rsi\") ptr,\n                    out(\"zmm0\") _,\n                    out(\"zmm1\") _,\n                    );\n                    ptr = ptr.add(512);\n                }\n            }\n        } else {\n            let mut ptr = slice.as_ptr();\n            let end = ptr.add(slice.len());\n            for _ in 0..loops {\n                while ptr < end {\n                    std::arch::asm!(\"\n                vmovaps ymm0, [rsi]\n                vmovaps ymm1, [rsi + 32]\n                vmovaps ymm2, [rsi + 64]\n                vmovaps ymm3, [rsi + 96]\n                    \", inout(\"rsi\") ptr,\n                    out(\"ymm0\") _,\n                    out(\"ymm1\") _,\n                    out(\"ymm2\") _,\n                    out(\"ymm3\") _,\n                    );\n                    ptr = ptr.add(128);\n                }\n            }\n        }\n    }\n}\n\n#[cfg(target_arch = \"aarch64\")]\n#[inline]\nfn load_a_slice(slice: &[u8], loops: usize) {\n    unsafe {\n        for _ in 0..loops {\n            let mut ptr = slice.as_ptr();\n            let end = ptr.add(slice.len());\n            while ptr < end {\n                std::arch::asm!(\"\n                    ld1 {{v0.16b-v3.16b}}, [x0], #64\n                    ld1 {{v4.16b-v7.16b}}, [x0], #64\n                        \", inout(\"x0\") ptr,\n                out(\"v0\") _,\n                out(\"v1\") _,\n                out(\"v2\") _,\n                out(\"v3\") _,\n                out(\"v4\") _,\n                out(\"v5\") _,\n                out(\"v6\") _,\n                out(\"v7\") _,\n                );\n            }\n        }\n    }\n}\n\n#[cfg(target_arch = \"arm\")]\n#[inline(never)]\nfn load_a_slice(slice: &[u8], loops: usize) {\n    unsafe {\n        for _ in 0..loops {\n            let mut ptr = slice.as_ptr();\n            let end = ptr.add(slice.len());\n            while ptr < end {\n                std::arch::asm!(\"\n                vldmia r1!, {{q0-q3}}\n                vldmia r1!, {{q4-q7}}\n                    \", inout(\"r1\") ptr,\n                out(\"d0\") _, out(\"d1\") _, out(\"d2\") _, out(\"d3\") _,\n                out(\"d4\") _, out(\"d5\") _, out(\"d6\") _, out(\"d7\") _,\n                out(\"d8\") _, out(\"d9\") _, out(\"d10\") _, out(\"d11\") _,\n                out(\"d12\") _, out(\"d13\") _, out(\"d14\") _, out(\"d15\") _,\n                );\n            }\n        }\n    }\n}\n\nfn bandwidth_seq(slice_len: usize, threads: usize) -> f64 {\n    #[cfg(target_arch = \"x86_64\")]\n    unsafe {\n        HAS_AVX512 = std::is_x86_feature_detected!(\"avx512f\");\n    }\n    std::thread::scope(|s| {\n        let gards = (0..threads)\n            .map(|_| {\n                s.spawn(|| {\n                    let buffer = unsafe { Blob::new_for_size_and_align(slice_len, 1024) };\n                    runner::run_bench(|loops| load_a_slice(&buffer, loops))\n                })\n            })\n            .collect_vec();\n        let time = gards.into_iter().map(|t| t.join().unwrap()).sum::<f64>() / threads as f64;\n        (slice_len * threads) as f64 / time\n    })\n}\n\npub fn what_is_big() -> usize {\n    1024 * 1024 * if cfg!(target_arch = \"arm\") { 64 } else { 256 }\n}\n\npub fn l1_bandwidth_seq(threads: usize) -> f64 {\n    // [1024, 2048, 4096, 8192, 16384, 32768, 65536]\n    [1024]\n        .into_iter()\n        .map(|slice_len| bandwidth_seq(slice_len, threads))\n        .max_by_key(|x| *x as i64)\n        .unwrap()\n}\n\npub fn main_memory_bandwith_seq(threads: usize) -> f64 {\n    bandwidth_seq(what_is_big(), threads)\n}\n\n#[ignore]\n#[test]\nfn b() {\n    let max = what_is_big();\n    for threads in [1, 2, 3, 4] {\n        println!(\"Threads: {}\", threads);\n        for size in (0..)\n            .flat_map(|po2| (0..2).map(move |f| (1024 + 512 * f) * (1 << po2)))\n            .take_while(|&s| s < max)\n        {\n            let bw = bandwidth_seq(size, threads);\n            println!(\n                \"threads: {threads} slice: {} KiB bandwidth: {} GiB/s\",\n                size as f64 / 1024.,\n                (bw / (1024. * 1024. * 1024.)) as usize\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/src/hwbench/mod.rs",
    "content": "pub mod runner;\n\n#[cfg(feature = \"hwbench\")]\npub mod bandwidth;\n"
  },
  {
    "path": "linalg/src/hwbench/runner.rs",
    "content": "#![allow(unused_macros)]\n\nuse std::time::Duration;\nuse std::time::Instant;\n\n#[macro_export]\nmacro_rules! r1 { ($($stat:stmt)*) => { $( $stat )* } }\n#[macro_export]\nmacro_rules! r2 { ($($stat:stmt)*) => { $( $stat )* $( $stat )* } }\n#[macro_export]\nmacro_rules! r4 { ($($stat:stmt)*) => { r2!(r2!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r8 { ($($stat:stmt)*) => { r2!(r4!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r16 { ($($stat:stmt)*) => { r2!(r8!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r32 { ($($stat:stmt)*) => { r2!(r16!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r64 { ($($stat:stmt)*) => { r2!(r32!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r128 { ($($stat:stmt)*) => { r2!(r64!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r256 { ($($stat:stmt)*) => { r2!(r128!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r512 { ($($stat:stmt)*) => { r2!(r256!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r1024 { ($($stat:stmt)*) => { r2!(r512!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r2048 { ($($stat:stmt)*) => { r2!(r1024!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r4096 { ($($stat:stmt)*) => { r2!(r2048!($($stat)*)) }}\n#[macro_export]\nmacro_rules! r8192 { ($($stat:stmt)*) => { r2!(r4096!($($stat)*)) }}\n\n#[macro_export]\nmacro_rules! b1 { ($($stat:stmt)*) => { nano::run_bench(|| { r1!($($stat)*); }) / 1.0 } }\n#[macro_export]\nmacro_rules! b2 { ($($stat:stmt)*) => { nano::run_bench(|| { r2!($($stat)*); }) / 2.0 } }\n#[macro_export]\nmacro_rules! b4 { ($($stat:stmt)*) => { nano::run_bench(|| { r4!($($stat)*); }) / 4.0 } }\n#[macro_export]\nmacro_rules! b8 { ($($stat:stmt)*) => { nano::run_bench(|| { r8!($($stat)*); }) / 8.0 } }\n#[macro_export]\nmacro_rules! b16 { ($($stat:stmt)*) => { nano::run_bench(|| { r16!($($stat)*); }) / 16.0 } }\n#[macro_export]\nmacro_rules! b32 { ($($stat:stmt)*) => { nano::run_bench(|| { r32!($($stat)*); }) / 32.0 } }\n#[macro_export]\nmacro_rules! b64 { ($($stat:stmt)*) => { nano::run_bench(|| { r64!($($stat)*); }) / 64.0 } }\n#[macro_export]\nmacro_rules! b128 { ($($stat:stmt)*) => { nano::run_bench(|| { r128!($($stat)*); }) / 128.0 } }\n#[macro_export]\nmacro_rules! b256 { ($($stat:stmt)*) => { nano::run_bench(|| { r256!($($stat)*); }) / 256.0 } }\n#[macro_export]\nmacro_rules! b512 { ($($stat:stmt)*) => { nano::run_bench(|| { r512!($($stat)*); }) / 512.0 } }\n#[macro_export]\nmacro_rules! b1024 { ($($stat:stmt)*) => { nano::run_bench(|| { r1024!($($stat)*); }) / 1024.0 } }\n#[macro_export]\nmacro_rules! b2048 { ($($stat:stmt)*) => { nano::run_bench(|| { r2048!($($stat)*); }) / 2048.0 } }\n#[macro_export]\nmacro_rules! b4096 { ($($stat:stmt)*) => { nano::run_bench(|| { r4096!($($stat)*); }) / 4096.0 } }\n#[macro_export]\nmacro_rules! b8192 { ($($stat:stmt)*) => { nano::run_bench(|| { r8192!($($stat)*); }) / 8192.0 } }\n\n#[inline]\nfn black_box<T>(dummy: T) -> T {\n    unsafe {\n        let ret = std::ptr::read_volatile(&dummy);\n        std::mem::forget(dummy);\n        ret\n    }\n}\n\npub fn run_bench<T, F: FnMut(usize) -> T + Copy>(f: F) -> f64 {\n    let start = Instant::now();\n    let mut f = black_box(f);\n    black_box(f(1));\n    let once = start.elapsed();\n    let evaled = if once < Duration::from_millis(1) {\n        let start = Instant::now();\n        black_box(f)(1000);\n        start.elapsed().as_secs_f64() / 1000.\n    } else {\n        once.as_secs_f64()\n    };\n    // raw evaluation is over a second. stop right there\n    if evaled > 1.0 {\n        return evaled;\n    }\n\n    // we want each individual sample to run for no less than\n    let minimum_sampling_time_s = 0.01;\n    let minimum_samples = 25;\n    let desired_bench_time = 1.0;\n\n    let inner_loops = (minimum_sampling_time_s / evaled).max(1.0) as usize;\n\n    let samples =\n        ((desired_bench_time / (inner_loops as f64 * evaled)) as usize).max(minimum_samples);\n    let warmup = (1.0 / evaled) as usize;\n\n    // println!(\n    //     \"evaled: {:?} samples:{samples} inner_loops:{inner_loops} time:{}\",\n    //     Duration::from_secs_f64(evaled),\n    //     (samples * inner_loops) as f64 * evaled\n    // );\n    let mut measures = vec![0.0; samples];\n\n    black_box(f(warmup));\n    for m in &mut measures {\n        let start = Instant::now();\n        black_box(black_box(f))(inner_loops);\n        let time = start.elapsed().as_secs_f64();\n        *m = time / inner_loops as f64\n    }\n    measures\n        .sort_by(|a, b| if a < b { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater });\n    let q1 = measures[samples / 4];\n    let q3 = measures[samples - samples / 4];\n    let iq = q3 - q1;\n    measures.retain(|&x| x >= q1 - 3. * iq && x <= q3 + 3. * iq);\n    measures.iter().copied().sum::<f64>() / measures.len() as f64\n}\n"
  },
  {
    "path": "linalg/src/lib.rs",
    "content": "#![allow(clippy::missing_safety_doc)]\n#![allow(clippy::redundant_closure_call)]\n#![allow(clippy::len_zero)]\n#![allow(clippy::excessive_precision)]\n#![allow(clippy::approx_constant)]\n#![allow(clippy::manual_is_multiple_of)]\n#![allow(unexpected_cfgs)]\n#![allow(unused_macros)]\n#[macro_use]\nextern crate derive_new;\nextern crate lazy_static;\nextern crate log;\nextern crate num_traits;\n#[macro_use]\nextern crate pastey;\n#[cfg(test)]\nextern crate proptest;\n\ninclude!(concat!(env!(\"OUT_DIR\"), \"/extern_kernel_macro.rs\"));\n\n#[macro_use]\nmod frame;\npub mod generic;\npub mod multithread;\npub use frame::weights::WeightType;\npub use generic::{ScaleShiftAndRound, Scaler};\nuse lazy_static::lazy_static;\nuse mmm::{MMMInputFormat, MatMatMul, PanelExtractor};\nuse tract_data::internal::TensorView;\n#[cfg(target_arch = \"x86_64\")]\npub mod x86_64_fma;\n\npub mod hwbench;\n\n#[cfg(target_arch = \"aarch64\")]\npub mod arm64;\n\n#[cfg(target_arch = \"aarch64\")]\npub use arm64::has_fp16;\nuse tract_itertools::Itertools;\n\n#[cfg(not(target_arch = \"aarch64\"))]\npub fn has_fp16() -> bool {\n    false\n}\n\n#[cfg(any(target_arch = \"arm\", target_arch = \"armv7\", target_arch = \"arm\"))]\npub mod arm32;\n\n#[cfg(all(target_family = \"wasm\", target_feature = \"simd128\"))]\npub mod wasm;\n\npub use self::frame::*;\n\nuse tract_data::prelude::*;\n\npub type MMMImpl = Box<\n    dyn Fn(Option<usize>, Option<usize>, Option<usize>) -> Box<dyn mmm::MatMatMul> + Send + Sync,\n>;\n\ntype MMVImpl = Box<dyn Fn(Option<usize>, Option<usize>) -> Box<dyn mmm::MatMatMul> + Send + Sync>;\n\n#[allow(clippy::type_complexity)]\npub struct Ops {\n    mmm_impls: Vec<Box<dyn mmm::MatMatMul>>,\n    panel_extractors: Vec<mmm::PanelExtractor>,\n\n    mmm_f64: MMMImpl,\n    mmv_f64: MMVImpl,\n\n    mmm_f32: MMMImpl,\n    mmv_f32: MMVImpl,\n\n    mmm_f16: MMMImpl,\n    mmv_f16: MMVImpl,\n\n    qmmm_i32: MMMImpl,\n    qmmv_i32: MMVImpl,\n\n    pub leaky_relu_f16: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16, f16>> + Send + Sync>,\n    pub leaky_relu_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32, f32>> + Send + Sync>,\n    pub mul_by_scalar_f32:\n        Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32, f32>> + Send + Sync>,\n    pub mul_by_scalar_f16:\n        Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16, f16>> + Send + Sync>,\n\n    pub sigmoid_f16: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16>> + Send + Sync>,\n    pub sigmoid_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32>> + Send + Sync>,\n    pub tanh_f16: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f16>> + Send + Sync>,\n    pub tanh_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32>> + Send + Sync>,\n    pub erf_f32: Box<dyn Fn() -> Box<dyn element_wise::ElementWise<f32>> + Send + Sync>,\n    pub lut_u8: Box<dyn Fn(&[u8]) -> Box<dyn lut::Lut> + Send + Sync>,\n\n    pub max_f16: Box<dyn Fn() -> Box<dyn reduce::Reduce<f16>> + Send + Sync>,\n    pub max_f32: Box<dyn Fn() -> Box<dyn reduce::Reduce<f32>> + Send + Sync>,\n\n    pub sum_f16: Box<dyn Fn() -> Box<dyn reduce::Reduce<f16>> + Send + Sync>,\n    pub sum_f32: Box<dyn Fn() -> Box<dyn reduce::Reduce<f32>> + Send + Sync>,\n\n    pub softmax2_fastcompact_f16:\n        Box<dyn Fn() -> Box<dyn reduce::MapReduce<f16, f16>> + Send + Sync>,\n    pub softmax2_fastcompact_f32:\n        Box<dyn Fn() -> Box<dyn reduce::MapReduce<f32, f32>> + Send + Sync>,\n}\n\nimpl Ops {\n    pub fn mmm_impls(&self) -> &[Box<dyn mmm::MatMatMul>] {\n        &self.mmm_impls\n    }\n\n    pub fn all_possible_packing(\n        &self,\n        weight_type: impl Into<WeightType>,\n    ) -> impl Iterator<Item = &dyn MMMInputFormat> {\n        let weight_type = weight_type.into();\n        self.mmm_impls\n            .iter()\n            .flat_map(|m| m.packings())\n            .map(|p| &*p.0)\n            .flat_map(move |p| {\n                let mut packs: Vec<&dyn MMMInputFormat> = vec![];\n                if p.precursor() == weight_type {\n                    packs.push(p)\n                };\n                for pe in &self.panel_extractors {\n                    if pe.from.precursor() == weight_type && pe.to.dyn_eq(p) {\n                        packs.push(&*pe.from);\n                    }\n                }\n                packs.into_iter()\n            })\n            .sorted_by_key(|p| p.to_string())\n            .dedup()\n    }\n\n    pub fn filter_impls<'o>(\n        &'o self,\n        weight: &'o dyn MMMInputFormat,\n        acc: &[DatumType],\n        act: DatumType,\n        store: DatumType,\n    ) -> impl Iterator<\n        Item = (\n            &'o dyn MatMatMul,\n            usize,\n            &'o dyn MMMInputFormat,\n            Option<&'o PanelExtractor>,\n            &'o dyn MMMInputFormat,\n        ),\n    > {\n        let acc = acc.to_vec();\n        self.mmm_impls\n            .iter()\n            .filter(move |mmm| acc.contains(&mmm.internal_type()) && mmm.stores().contains(&store))\n            .flat_map(|mmm| {\n                mmm.packings()\n                    .iter()\n                    .enumerate()\n                    .map(|(pack_ix, (a, b))| (&**mmm, pack_ix, &**a, &**b))\n            })\n            .filter_map(|(mmm, ix, a, b)| {\n                if a.dyn_eq(weight) {\n                    Some((mmm, ix, a, None, b))\n                } else {\n                    self.panel_extractors\n                        .iter()\n                        .find(|pe| pe.from.dyn_eq(weight) && pe.to.dyn_eq(a))\n                        .map(|pe| (mmm, ix, a, Some(pe), b))\n                }\n            })\n            .filter(move |(_mmm, _ix, _a, _pe, b)| {\n                b.precursor().as_dt().is_some_and(|dt| dt == act)\n            })\n    }\n\n    pub fn panel_extractors(&self) -> &[mmm::panel_extract::PanelExtractor] {\n        &self.panel_extractors\n    }\n\n    pub fn mmm(\n        &self,\n        accumulator: DatumType,\n        m: Option<usize>,\n        k: Option<usize>,\n        n: Option<usize>,\n    ) -> Option<Box<dyn mmm::MatMatMul>> {\n        use DatumType::*;\n        match accumulator {\n            F64 => Some(if n == Some(1) { (self.mmv_f64)(m, k) } else { (self.mmm_f64)(m, k, n) }),\n            F32 => Some(if n == Some(1) { (self.mmv_f32)(m, k) } else { (self.mmm_f32)(m, k, n) }),\n            F16 => Some(if n == Some(1) { (self.mmv_f16)(m, k) } else { (self.mmm_f16)(m, k, n) }),\n            I32 => {\n                Some(if n == Some(1) { (self.qmmv_i32)(m, k) } else { (self.qmmm_i32)(m, k, n) })\n            }\n            _ => None,\n        }\n    }\n}\n\npub fn generic() -> Ops {\n    use crate::generic::mmm::*;\n    use element_wise::ElementWiseKer;\n    use reduce::{MapReduceKer, ReduceKer};\n    let mut ops = Ops {\n        mmm_impls: vec![],\n        panel_extractors: vec![],\n        mmm_f64: Box::new(|_, _, _| generic_f64_4x4.mmm()),\n        mmv_f64: Box::new(|_, _| generic_f64_4x1.mmm()),\n        mmm_f32: Box::new(|_, _, _| generic_f32_4x4.mmm()),\n        mmv_f32: Box::new(|_, _| generic_f32_4x1.mmm()),\n        mmm_f16: Box::new(|_, _, _| generic_f16_4x4.mmm()),\n        mmv_f16: Box::new(|_, _| generic_f16_4x1.mmm()),\n        qmmm_i32: Box::new(|_, _, _| generic_i32_4x4.mmm()),\n        qmmv_i32: Box::new(|_, _| generic_i32_4x4.mmm()),\n        leaky_relu_f16: Box::new(|| generic::HLeakyRelu8::ew()),\n        leaky_relu_f32: Box::new(|| generic::SLeakyRelu4::ew()),\n        mul_by_scalar_f16: Box::new(|| generic::HMulByScalar8::ew()),\n        mul_by_scalar_f32: Box::new(|| generic::SMulByScalar4::ew()),\n        sigmoid_f16: Box::new(|| generic::HSigmoid8::ew()),\n        sigmoid_f32: Box::new(|| generic::SSigmoid4::ew()),\n        tanh_f16: Box::new(|| generic::HTanh8::ew()),\n        tanh_f32: Box::new(|| generic::STanh4::ew()),\n        erf_f32: Box::new(|| generic::SErf4::ew()),\n        lut_u8: Box::new(|table: &[u8]| Box::new(lut::LutImpl::<generic::GenericLut8>::new(table))),\n        max_f16: Box::new(|| generic::reduce::max::HMax8::red()),\n        max_f32: Box::new(|| generic::reduce::max::SMax4::red()),\n        sum_f16: Box::new(|| generic::reduce::sum::HSum8::red()),\n        sum_f32: Box::new(|| generic::reduce::sum::SSum4::red()),\n        /*\n        activation_f32: Box::new(|microcode| generic::SActivation::new(microcode))\n        */\n        softmax2_fastcompact_f16: Box::new(|| generic::reduce::softmax_l2::HSoftMaxL2::red()),\n        softmax2_fastcompact_f32: Box::new(|| generic::reduce::softmax_l2::SSoftMaxL2::red()),\n    };\n    crate::generic::mmm::plug(&mut ops);\n    ops\n}\n\n#[allow(unreachable_code, unused_mut, unexpected_cfgs)]\npub fn best() -> Ops {\n    let mut ops = generic();\n    #[cfg(target_arch = \"x86_64\")]\n    x86_64_fma::plug(&mut ops);\n    #[cfg(any(target_arch = \"arm\", target_arch = \"armv7\"))]\n    arm32::plug(&mut ops);\n    #[cfg(target_arch = \"aarch64\")]\n    arm64::plug(&mut ops);\n    #[cfg(all(target_family = \"wasm\", target_feature = \"simd128\"))]\n    wasm::plug(&mut ops);\n\n    ops\n}\n\nlazy_static::lazy_static! {\n    static ref OPS: Ops = {\n        best()\n    };\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]\npub enum BinOp {\n    Min,\n    Max,\n    Add,\n    Mul,\n    Sub,\n    SubF,\n}\n\nimpl BinOp {\n    pub fn flip(&self) -> BinOp {\n        use BinOp::*;\n        match self {\n            Sub => SubF,\n            SubF => Sub,\n            sym => *sym,\n        }\n    }\n}\n\nfn register_all_unicast(registry: &mut LinalgRegistry) {\n    generic::register_all_unicast(registry);\n    #[cfg(target_arch = \"aarch64\")]\n    arm64::register_all_unicast(registry);\n}\n\nfn register_all_by_scalar(registry: &mut LinalgRegistry) {\n    generic::register_all_by_scalar(registry);\n    #[cfg(target_arch = \"aarch64\")]\n    arm64::register_all_by_scalar(registry);\n}\n\npub type LinalgFn = dyn Fn(&mut TensorView, &TensorView) -> TractResult<()> + Send + Sync;\ntype LinalgRegistry = HashMap<(BinOp, DatumType), Box<dyn Fn() -> Box<LinalgFn> + Send + Sync>>;\nlazy_static! {\n    static ref BIN_UNICAST_OPS: Mutex<LinalgRegistry> = {\n        let mut registry = HashMap::default();\n        register_all_unicast(&mut registry);\n        Mutex::new(registry)\n    };\n    static ref BIN_BY_SCALAR_OPS: Mutex<LinalgRegistry> = {\n        let mut registry = HashMap::default();\n        register_all_by_scalar(&mut registry);\n        Mutex::new(registry)\n    };\n}\n\npub fn bin_by_scalar(dt: DatumType, bin: BinOp) -> Option<Box<LinalgFn>> {\n    let map = BIN_BY_SCALAR_OPS.lock().unwrap();\n    if (dt == DatumType::F16) && !has_fp16() {\n        return None;\n    }\n    map.get(&(bin, dt)).map(|it| (it)())\n}\n\npub fn bin_unicast(dt: DatumType, bin: BinOp) -> Option<Box<LinalgFn>> {\n    let map = BIN_UNICAST_OPS.lock().unwrap();\n    if (dt == DatumType::F16) && !has_fp16() {\n        return None;\n    }\n    map.get(&(bin, dt)).map(|it| (it)())\n}\n\npub fn ops() -> &'static Ops {\n    &OPS\n}\n\nuse dyn_eq::DynEq;\nuse num_traits::*;\nuse std::collections::HashMap;\nuse std::fmt::Debug;\nuse std::ops::*;\nuse std::sync::Mutex;\n\npub trait LADatum:\n    Sized\n    + std::fmt::Display\n    + Debug\n    + Copy\n    + Clone\n    + Zero\n    + One\n    + 'static\n    + Add<Output = Self>\n    + Sub<Output = Self>\n    + Mul\n    + AddAssign\n    + PartialOrd\n    + Bounded\n    + tract_data::prelude::Datum\n{\n    #[cfg(test)]\n    fn strat() -> proptest::prelude::BoxedStrategy<Self>;\n}\n\n#[cfg(test)]\nuse proptest::prelude::*;\n\nimpl LADatum for f16 {\n    #[cfg(test)]\n    fn strat() -> BoxedStrategy<Self> {\n        f32::strat().prop_map(|f| f.as_()).boxed()\n    }\n}\n\nimpl LADatum for f32 {\n    #[cfg(test)]\n    fn strat() -> BoxedStrategy<Self> {\n        (-1000isize..1000).prop_map(|i| i as f32 / 1000.0).boxed()\n    }\n}\n\nimpl LADatum for f64 {\n    #[cfg(test)]\n    fn strat() -> BoxedStrategy<Self> {\n        (-1000isize..1000).prop_map(|i| i as f64 / 1000.0).boxed()\n    }\n}\n\nimpl LADatum for u8 {\n    #[cfg(test)]\n    fn strat() -> BoxedStrategy<Self> {\n        any::<u8>().boxed()\n    }\n}\n\nimpl LADatum for i8 {\n    #[cfg(test)]\n    fn strat() -> BoxedStrategy<Self> {\n        any::<i8>().boxed()\n    }\n}\n\nimpl LADatum for i32 {\n    #[cfg(test)]\n    fn strat() -> BoxedStrategy<Self> {\n        any::<i32>().boxed()\n    }\n}\n\n#[cfg(test)]\n#[allow(dead_code)]\nfn setup_test_logger() {\n    let _ = env_logger::Builder::from_env(\"TRACT_LOG\").try_init();\n}\n"
  },
  {
    "path": "linalg/src/multithread.rs",
    "content": "use std::cell::RefCell;\n#[allow(unused_imports)]\nuse std::sync::{Arc, Mutex};\n\n#[cfg(feature = \"multithread-mm\")]\nuse rayon::{ThreadPool, ThreadPoolBuilder};\n\n#[derive(Debug, Clone, Default)]\npub enum Executor {\n    #[default]\n    SingleThread,\n    #[cfg(feature = \"multithread-mm\")]\n    MultiThread(Arc<ThreadPool>),\n}\n\nimpl Executor {\n    #[cfg(feature = \"multithread-mm\")]\n    pub fn multithread(n: usize) -> Executor {\n        Executor::multithread_with_name(n, \"tract-default\")\n    }\n\n    #[cfg(feature = \"multithread-mm\")]\n    pub fn multithread_with_name(n: usize, name: &str) -> Executor {\n        let name = name.to_string();\n        let pool = ThreadPoolBuilder::new()\n            .thread_name(move |n| format!(\"{name}-{n}\"))\n            .num_threads(n)\n            .build()\n            .unwrap();\n        Executor::MultiThread(Arc::new(pool))\n    }\n}\n\nstatic DEFAULT_EXECUTOR: Mutex<Executor> = Mutex::new(Executor::SingleThread);\n\nthread_local! {\n    static TLS_EXECUTOR_OVERRIDE: RefCell<Option<Executor>> = Default::default();\n}\n\npub fn current_tract_executor() -> Executor {\n    if let Some(over_ride) = TLS_EXECUTOR_OVERRIDE.with_borrow(|tls| tls.clone()) {\n        over_ride\n    } else {\n        DEFAULT_EXECUTOR.lock().unwrap().clone()\n    }\n}\n\npub fn set_default_executor(executor: Executor) {\n    *DEFAULT_EXECUTOR.lock().unwrap() = executor;\n}\n\npub fn multithread_tract_scope<R, F: FnOnce() -> R>(pool: Executor, f: F) -> R {\n    let previous = TLS_EXECUTOR_OVERRIDE.replace(Some(pool));\n    let result = f();\n    TLS_EXECUTOR_OVERRIDE.set(previous);\n    result\n}\n"
  },
  {
    "path": "linalg/src/wasm.rs",
    "content": "/// Wasm SIMD implementation of `MatMatMulKer<f32>`\n///\n/// To run test, you need to install `wasmtime`\n/// and export the following environment variables:\n/// ```\n/// > export RUSTFLAGS='-C target-feature=+simd128'\n/// > export CARGO_TARGET_WASM32_WASI_RUNNER=wasmtime\n/// > cargo test --target=wasm32-wasi\n/// ```\nuse crate::mmm::FusedKerSpec;\nuse crate::mmm::ImplementationQuality;\nuse crate::{Ops, Scaler};\n\npub fn plug(ops: &mut Ops) {\n    ops.mmm_impls.push(wasm_f32_4x4.mmm());\n    ops.mmm_f32 = Box::new(|_m, _k, _n| wasm_f32_4x4.mmm());\n}\n\nunsafe fn kernel_f32_4x4(mut pnl: *const FusedKerSpec<f32>) -> isize {\n    use std::arch::wasm32::*;\n\n    unsafe {\n        // Each of these variables stores a row of the matrix,\n        // consisting of four packed `f32` numbers.\n        let mut ab0 = f32x4_splat(0.0);\n        let mut ab1 = f32x4_splat(0.0);\n        let mut ab2 = f32x4_splat(0.0);\n        let mut ab3 = f32x4_splat(0.0);\n\n        while !pnl.is_null() {\n            match *pnl {\n                FusedKerSpec::Done => break,\n                FusedKerSpec::Clear => {\n                    let a = f32x4_splat(0.0);\n                    ab0 = a;\n                    ab1 = a;\n                    ab2 = a;\n                    ab3 = a;\n                }\n                FusedKerSpec::LoadTile(_cols, rows) => {\n                    let rows = rows as *const v128;\n                    ab0 = *rows;\n                    ab1 = *rows.add(1);\n                    ab2 = *rows.add(2);\n                    ab3 = *rows.add(3);\n                }\n                FusedKerSpec::ScalarMin(a) => {\n                    let a = f32x4_splat(a);\n                    ab0 = f32x4_min(a, ab0);\n                    ab1 = f32x4_min(a, ab1);\n                    ab2 = f32x4_min(a, ab2);\n                    ab3 = f32x4_min(a, ab3);\n                }\n                FusedKerSpec::ScalarMax(a) => {\n                    let a = f32x4_splat(a);\n                    ab0 = f32x4_max(a, ab0);\n                    ab1 = f32x4_max(a, ab1);\n                    ab2 = f32x4_max(a, ab2);\n                    ab3 = f32x4_max(a, ab3);\n                }\n                FusedKerSpec::ScalarAdd(a) => {\n                    let a = f32x4_splat(a);\n                    ab0 = f32x4_add(a, ab0);\n                    ab1 = f32x4_add(a, ab1);\n                    ab2 = f32x4_add(a, ab2);\n                    ab3 = f32x4_add(a, ab3);\n                }\n                FusedKerSpec::ScalarMul(a) => {\n                    let a = f32x4_splat(a);\n                    ab0 = f32x4_mul(a, ab0);\n                    ab1 = f32x4_mul(a, ab1);\n                    ab2 = f32x4_mul(a, ab2);\n                    ab3 = f32x4_mul(a, ab3);\n                }\n                FusedKerSpec::ScalarSub(a) => {\n                    let a = f32x4_splat(a);\n                    ab0 = f32x4_sub(a, ab0);\n                    ab1 = f32x4_sub(a, ab1);\n                    ab2 = f32x4_sub(a, ab2);\n                    ab3 = f32x4_sub(a, ab3);\n                }\n                FusedKerSpec::ScalarSubF(a) => {\n                    let a = f32x4_splat(a);\n                    ab0 = f32x4_sub(ab0, a);\n                    ab1 = f32x4_sub(ab1, a);\n                    ab2 = f32x4_sub(ab2, a);\n                    ab3 = f32x4_sub(ab3, a);\n                }\n                FusedKerSpec::LeakyRelu(a) => {\n                    let a = f32x4_splat(a);\n                    let zero = f32x4_splat(0.0);\n\n                    let mask0 = f32x4_gt(ab0, zero);\n                    ab0 = v128_bitselect(ab0, f32x4_mul(a, ab0), mask0);\n\n                    let mask1 = f32x4_gt(ab1, zero);\n                    ab1 = v128_bitselect(ab1, f32x4_mul(a, ab1), mask1);\n\n                    let mask2 = f32x4_gt(ab2, zero);\n                    ab2 = v128_bitselect(ab2, f32x4_mul(a, ab2), mask2);\n\n                    let mask3 = f32x4_gt(ab3, zero);\n                    ab3 = v128_bitselect(ab3, f32x4_mul(a, ab3), mask3);\n                }\n                FusedKerSpec::PerRowMin(row) => {\n                    let row = std::slice::from_raw_parts(row, 4);\n                    ab0 = f32x4_min(f32x4_splat(row[0]), ab0);\n                    ab1 = f32x4_min(f32x4_splat(row[1]), ab1);\n                    ab2 = f32x4_min(f32x4_splat(row[2]), ab2);\n                    ab3 = f32x4_min(f32x4_splat(row[3]), ab3);\n                }\n                FusedKerSpec::PerRowMax(row) => {\n                    let row = std::slice::from_raw_parts(row, 4);\n                    ab0 = f32x4_max(f32x4_splat(row[0]), ab0);\n                    ab1 = f32x4_max(f32x4_splat(row[1]), ab1);\n                    ab2 = f32x4_max(f32x4_splat(row[2]), ab2);\n                    ab3 = f32x4_max(f32x4_splat(row[3]), ab3);\n                }\n                FusedKerSpec::PerRowAdd(row) => {\n                    let row = std::slice::from_raw_parts(row, 4);\n                    ab0 = f32x4_add(f32x4_splat(row[0]), ab0);\n                    ab1 = f32x4_add(f32x4_splat(row[1]), ab1);\n                    ab2 = f32x4_add(f32x4_splat(row[2]), ab2);\n                    ab3 = f32x4_add(f32x4_splat(row[3]), ab3);\n                }\n                FusedKerSpec::PerRowMul(row) => {\n                    let row = std::slice::from_raw_parts(row, 4);\n                    ab0 = f32x4_mul(f32x4_splat(row[0]), ab0);\n                    ab1 = f32x4_mul(f32x4_splat(row[1]), ab1);\n                    ab2 = f32x4_mul(f32x4_splat(row[2]), ab2);\n                    ab3 = f32x4_mul(f32x4_splat(row[3]), ab3);\n                }\n                FusedKerSpec::PerRowSub(row) => {\n                    let row = std::slice::from_raw_parts(row, 4);\n                    ab0 = f32x4_sub(f32x4_splat(row[0]), ab0);\n                    ab1 = f32x4_sub(f32x4_splat(row[1]), ab1);\n                    ab2 = f32x4_sub(f32x4_splat(row[2]), ab2);\n                    ab3 = f32x4_sub(f32x4_splat(row[3]), ab3);\n                }\n                FusedKerSpec::PerRowSubF(row) => {\n                    let row = std::slice::from_raw_parts(row, 4);\n                    ab0 = f32x4_sub(ab0, f32x4_splat(row[0]));\n                    ab1 = f32x4_sub(ab1, f32x4_splat(row[1]));\n                    ab2 = f32x4_sub(ab2, f32x4_splat(row[2]));\n                    ab3 = f32x4_sub(ab3, f32x4_splat(row[3]));\n                }\n                FusedKerSpec::PerColMin(cols) => {\n                    let cols = v128_load(cols as *const v128);\n                    ab0 = f32x4_min(cols, ab0);\n                    ab1 = f32x4_min(cols, ab1);\n                    ab2 = f32x4_min(cols, ab2);\n                    ab3 = f32x4_min(cols, ab3);\n                }\n                FusedKerSpec::PerColMax(cols) => {\n                    let cols = v128_load(cols as *const v128);\n                    ab0 = f32x4_max(cols, ab0);\n                    ab1 = f32x4_max(cols, ab1);\n                    ab2 = f32x4_max(cols, ab2);\n                    ab3 = f32x4_max(cols, ab3);\n                }\n                FusedKerSpec::PerColAdd(cols) => {\n                    let cols = v128_load(cols as *const v128);\n                    ab0 = f32x4_add(cols, ab0);\n                    ab1 = f32x4_add(cols, ab1);\n                    ab2 = f32x4_add(cols, ab2);\n                    ab3 = f32x4_add(cols, ab3);\n                }\n                FusedKerSpec::PerColMul(cols) => {\n                    let cols = v128_load(cols as *const v128);\n                    ab0 = f32x4_mul(cols, ab0);\n                    ab1 = f32x4_mul(cols, ab1);\n                    ab2 = f32x4_mul(cols, ab2);\n                    ab3 = f32x4_mul(cols, ab3);\n                }\n                FusedKerSpec::PerColSub(cols) => {\n                    let cols = v128_load(cols as *const v128);\n                    ab0 = f32x4_sub(cols, ab0);\n                    ab1 = f32x4_sub(cols, ab1);\n                    ab2 = f32x4_sub(cols, ab2);\n                    ab3 = f32x4_sub(cols, ab3);\n                }\n                FusedKerSpec::PerColSubF(cols) => {\n                    let cols = v128_load(cols as *const v128);\n                    ab0 = f32x4_sub(ab0, cols);\n                    ab1 = f32x4_sub(ab1, cols);\n                    ab2 = f32x4_sub(ab2, cols);\n                    ab3 = f32x4_sub(ab3, cols);\n                }\n                FusedKerSpec::QScale(shift, rp, mult) => {\n                    let scaler = Scaler::from_fuse_params(shift, rp, mult);\n                    let scale = f32x4_splat(scaler.scale);\n                    ab0 = f32x4_mul(scale, ab0);\n                    ab1 = f32x4_mul(scale, ab1);\n                    ab2 = f32x4_mul(scale, ab2);\n                    ab3 = f32x4_mul(scale, ab3);\n                }\n                FusedKerSpec::RoundingShiftRight(shift, _rp) => {\n                    let shift = f32x4_splat(2f32.powi(-(shift as i32)));\n                    ab0 = f32x4_mul(shift, ab0);\n                    ab1 = f32x4_mul(shift, ab1);\n                    ab2 = f32x4_mul(shift, ab2);\n                    ab3 = f32x4_mul(shift, ab3);\n                }\n                FusedKerSpec::ShiftLeft(shift) => {\n                    let shift = f32x4_splat(2f32.powi(shift as i32));\n                    ab0 = f32x4_mul(shift, ab0);\n                    ab1 = f32x4_mul(shift, ab1);\n                    ab2 = f32x4_mul(shift, ab2);\n                    ab3 = f32x4_mul(shift, ab3);\n                }\n                FusedKerSpec::AddUnicast(tile) => {\n                    let mut ptr: *const u8 = tile.ptr;\n\n                    let m0 = *(ptr as *const f32);\n                    let m1 = *(ptr.offset(tile.col_byte_stride) as *const f32);\n                    let m2 = *(ptr.offset(tile.col_byte_stride * 2) as *const f32);\n                    let m3 = *(ptr.offset(tile.col_byte_stride * 3) as *const f32);\n                    ab0 = f32x4_add(ab0, f32x4(m0, m1, m2, m3));\n                    ptr = ptr.add(tile.row_byte_stride as usize);\n\n                    let m0 = *(ptr as *const f32);\n                    let m1 = *(ptr.offset(tile.col_byte_stride) as *const f32);\n                    let m2 = *(ptr.offset(tile.col_byte_stride * 2) as *const f32);\n                    let m3 = *(ptr.offset(tile.col_byte_stride * 3) as *const f32);\n                    ab1 = f32x4_add(ab1, f32x4(m0, m1, m2, m3));\n                    ptr = ptr.add(tile.row_byte_stride as usize);\n\n                    let m0 = *(ptr as *const f32);\n                    let m1 = *(ptr.offset(tile.col_byte_stride) as *const f32);\n                    let m2 = *(ptr.offset(tile.col_byte_stride * 2) as *const f32);\n                    let m3 = *(ptr.offset(tile.col_byte_stride * 3) as *const f32);\n                    ab2 = f32x4_add(ab2, f32x4(m0, m1, m2, m3));\n                    ptr = ptr.add(tile.row_byte_stride as usize);\n\n                    let m0 = *(ptr as *const f32);\n                    let m1 = *(ptr.offset(tile.col_byte_stride) as *const f32);\n                    let m2 = *(ptr.offset(tile.col_byte_stride * 2) as *const f32);\n                    let m3 = *(ptr.offset(tile.col_byte_stride * 3) as *const f32);\n                    ab3 = f32x4_add(ab3, f32x4(m0, m1, m2, m3));\n                }\n                FusedKerSpec::AddRowColProducts(rows, cols) => {\n                    let cols = v128_load(cols as *const v128);\n                    ab0 = f32x4_add(ab0, f32x4_mul(f32x4_splat(*rows.add(0)), cols));\n                    ab1 = f32x4_add(ab1, f32x4_mul(f32x4_splat(*rows.add(1)), cols));\n                    ab2 = f32x4_add(ab2, f32x4_mul(f32x4_splat(*rows.add(2)), cols));\n                    ab3 = f32x4_add(ab3, f32x4_mul(f32x4_splat(*rows.add(3)), cols));\n                }\n                FusedKerSpec::Store(tile) => {\n                    let mut ptr: *mut u8 = tile.ptr;\n\n                    *(ptr as *mut f32) = f32x4_extract_lane::<0>(ab0);\n                    *(ptr.offset(tile.col_byte_stride) as *mut f32) = f32x4_extract_lane::<1>(ab0);\n                    *(ptr.offset(tile.col_byte_stride * 2) as *mut f32) =\n                        f32x4_extract_lane::<2>(ab0);\n                    *(ptr.offset(tile.col_byte_stride * 3) as *mut f32) =\n                        f32x4_extract_lane::<3>(ab0);\n                    ptr = ptr.add(tile.row_byte_stride as usize);\n\n                    *(ptr as *mut f32) = f32x4_extract_lane::<0>(ab1);\n                    *(ptr.offset(tile.col_byte_stride) as *mut f32) = f32x4_extract_lane::<1>(ab1);\n                    *(ptr.offset(tile.col_byte_stride * 2) as *mut f32) =\n                        f32x4_extract_lane::<2>(ab1);\n                    *(ptr.offset(tile.col_byte_stride * 3) as *mut f32) =\n                        f32x4_extract_lane::<3>(ab1);\n                    ptr = ptr.add(tile.row_byte_stride as usize);\n\n                    *(ptr as *mut f32) = f32x4_extract_lane::<0>(ab2);\n                    *(ptr.offset(tile.col_byte_stride) as *mut f32) = f32x4_extract_lane::<1>(ab2);\n                    *(ptr.offset(tile.col_byte_stride * 2) as *mut f32) =\n                        f32x4_extract_lane::<2>(ab2);\n                    *(ptr.offset(tile.col_byte_stride * 3) as *mut f32) =\n                        f32x4_extract_lane::<3>(ab2);\n                    ptr = ptr.add(tile.row_byte_stride as usize);\n\n                    *(ptr as *mut f32) = f32x4_extract_lane::<0>(ab3);\n                    *(ptr.offset(tile.col_byte_stride) as *mut f32) = f32x4_extract_lane::<1>(ab3);\n                    *(ptr.offset(tile.col_byte_stride * 2) as *mut f32) =\n                        f32x4_extract_lane::<2>(ab3);\n                    *(ptr.offset(tile.col_byte_stride * 3) as *mut f32) =\n                        f32x4_extract_lane::<3>(ab3);\n                }\n                FusedKerSpec::AddMatMul { k, pa, pb, packing: _ } => {\n                    let a = pa as *const f32;\n                    let b = pb as *const v128;\n                    for i in 0..k {\n                        let a = std::slice::from_raw_parts(a.offset(4 * i as isize), 4);\n                        let b = v128_load(b.offset(i as isize));\n                        ab0 = f32x4_add(ab0, f32x4_mul(f32x4_splat(a[0]), b));\n                        ab1 = f32x4_add(ab1, f32x4_mul(f32x4_splat(a[1]), b));\n                        ab2 = f32x4_add(ab2, f32x4_mul(f32x4_splat(a[2]), b));\n                        ab3 = f32x4_add(ab3, f32x4_mul(f32x4_splat(a[3]), b));\n                    }\n                }\n            }\n            pnl = pnl.add(1);\n        }\n    }\n    0\n}\n\nMMMRustKernel!(kernel_f32_4x4 => wasm_f32_4x4<f32>(4,4)@(4,4) quality(ImplementationQuality::TargetOptimized));\n"
  },
  {
    "path": "linalg/src/x86_64_fma/by_scalar.rs",
    "content": "ew_impl_wrap!(\n    f32,\n    x86_64_avx_f32_mul_by_scalar_32n,\n    32,\n    8,\n    f32,\n    fn run(x: &mut [f32], s: f32) {\n        debug_assert!(x.len() % Self::nr() == 0);\n        debug_assert!(x.as_ptr() as usize % Self::alignment_bytes() == 0);\n        unsafe { x86_64_avx_f32_mul_by_scalar_32n_run(x, s) }\n    }\n);\n\n#[target_feature(enable = \"avx\")]\nunsafe fn x86_64_avx_f32_mul_by_scalar_32n_run(buf: &mut [f32], scalar: f32) {\n    unsafe {\n        let len = buf.len();\n        let ptr = buf.as_ptr();\n        std::arch::asm!(\"\n            vbroadcastss ymm0, xmm0\n            2:\n                vmovaps ymm4, [{ptr}]\n                vmovaps ymm5, [{ptr} + 32]\n                vmovaps ymm6, [{ptr} + 64]\n                vmovaps ymm7, [{ptr} + 96]\n                vmulps ymm4, ymm4, ymm0\n                vmulps ymm5, ymm5, ymm0\n                vmulps ymm6, ymm6, ymm0\n                vmulps ymm7, ymm7, ymm0\n                vmovaps [{ptr}], ymm4\n                vmovaps [{ptr} + 32], ymm5\n                vmovaps [{ptr} + 64], ymm6\n                vmovaps [{ptr} + 96], ymm7\n                add {ptr}, 128\n                sub {len}, 32\n                jnz 2b\n            \",\n        len = inout(reg) len => _,\n        ptr = inout(reg) ptr => _,\n        in(\"xmm0\") scalar,\n        out(\"ymm4\") _, out(\"ymm5\") _, out(\"ymm6\") _, out(\"ymm7\") _\n        );\n    }\n}\n\n#[cfg(test)]\n#[macro_use]\npub mod test_x86_64_avx_f32_mul_by_scalar_32n {\n    use super::*;\n    by_scalar_frame_tests!(\n        is_x86_feature_detected!(\"avx2\"),\n        f32,\n        x86_64_avx_f32_mul_by_scalar_32n,\n        |a, b| a * b\n    );\n}\n"
  },
  {
    "path": "linalg/src/x86_64_fma/intel.rs",
    "content": "use crate::frame::mmm::cost_model::CostModel;\n#[allow(dead_code)]\npub fn models() -> Vec<(&'static str, CostModel<'static>)> {\n    vec![]\n}\n"
  },
  {
    "path": "linalg/src/x86_64_fma/max.rs",
    "content": "reduce_impl_wrap!(\n    f32,\n    x86_64_fma_max_f32_32n,\n    32,\n    8,\n    (),\n    f32::MIN,\n    #[inline(never)]\n    fn run(buf: &[f32], _: ()) -> f32 {\n        assert!(buf.len() % 32 == 0);\n        assert!(buf.len() > 0);\n        unsafe { x86_64_fma_max_f32_32n_run(buf) }\n    },\n    #[inline(never)]\n    fn reduce_two(a: f32, b: f32) -> f32 {\n        a.max(b)\n    }\n);\n\n#[target_feature(enable = \"avx\")]\nunsafe fn x86_64_fma_max_f32_32n_run(buf: &[f32]) -> f32 {\n    unsafe {\n        let len = buf.len();\n        let ptr = buf.as_ptr();\n        let mut acc = f32::MIN;\n        std::arch::asm!(\"\n            vbroadcastss ymm0, xmm0\n            vmovaps ymm1, ymm0\n            vmovaps ymm2, ymm0\n            vmovaps ymm3, ymm0\n            2:\n                vmovaps ymm4, [{ptr}]\n                vmovaps ymm5, [{ptr} + 32]\n                vmovaps ymm6, [{ptr} + 64]\n                vmovaps ymm7, [{ptr} + 96]\n                vmaxps ymm0, ymm0, ymm4\n                vmaxps ymm1, ymm1, ymm5\n                vmaxps ymm2, ymm2, ymm6\n                vmaxps ymm3, ymm3, ymm7\n                add {ptr}, 128\n                sub {len}, 32\n                jnz 2b\n            vmaxps ymm0, ymm0, ymm1\n            vmaxps ymm2, ymm2, ymm3\n            vmaxps ymm0, ymm0, ymm2\n            vperm2f128 ymm1, ymm0, ymm0, 1      // copy second half (4xf32) of ymm0 to ymm1\n            vmaxps xmm0, xmm0, xmm1             // xmm0 contains 4 values to max\n            vpermilps xmm1, xmm0, 2 + (3 << 2)  // second 2x32 bit half moved to top\n            vmaxps xmm0, xmm0, xmm1             // xmm0 containes 2 values\n            vpermilps xmm1, xmm0, 1             // second f32 to top\n            vmaxps xmm0, xmm0, xmm1\n            \",\n        len = inout(reg) len => _,\n        ptr = inout(reg) ptr => _,\n        inout(\"ymm0\") acc,\n        out(\"ymm1\") _, out(\"ymm2\") _, out(\"ymm3\") _,\n        out(\"ymm4\") _, out(\"ymm5\") _, out(\"ymm6\") _, out(\"ymm7\") _\n        );\n        acc\n    }\n}\n\n#[cfg(test)]\nmod test_x86_64_fma_max_f32_32n {\n    use super::*;\n    crate::max_frame_tests!(is_x86_feature_detected!(\"avx2\"), f32, x86_64_fma_max_f32_32n);\n}\n"
  },
  {
    "path": "linalg/src/x86_64_fma/mmm.rs",
    "content": "use crate::Ops;\nuse crate::block_quant::*;\nuse crate::mmm::ImplementationQuality::ManuallyOptimized;\nuse crate::pack::PackedFormat;\n\nuse super::*;\n\nMMMExternKernel!(fma_mmm_f32_8x8 <f32>(8, 8)@(256,4) where(FMA) quality(ManuallyOptimized));\nMMMExternKernel!(fma_mmm_f32_16x6<f32>(16,6)@(256,4) where(FMA) quality(ManuallyOptimized));\nMMMExternKernel!(fma_mmm_f32_16x5<f32>(16,5)@(256,4) where(FMA) quality(ManuallyOptimized));\nMMMExternKernel!(fma_mmm_f32_24x4<f32>(24,4)@(256,4) where(FMA) quality(ManuallyOptimized));\nMMMExternKernel!(fma_mmm_f32_40x2<f32>(40,2)@(256,4) where(FMA) quality(ManuallyOptimized));\nMMMExternKernel!(fma_mmm_f32_64x1<f32>(64,1)@(256,4) where(FMA) quality(ManuallyOptimized));\n\npub fn pq40_r32() -> PackedBlockQuantFormat {\n    PackedBlockQuantFormat::new(&Q4_0, 32, 16, false)\n}\nMMMExternKernel! {fma_mmm_f32_32x1<f32>(32,1)@(256,4) where(FMA)\n    packing[1] = q40f32 => |k| k.with_packing_a(pq40_r32());\n    packing[2] = q40f16 => |k| k.with_packing(pq40_r32(), f16::packing(1));\n    packing[3] = f16f16 => |k| k.with_packing(f16::packing(32), f16::packing(1));\n    packing[4] = f16f32 => |k| k.with_packing(f16::packing(32), f32::packing(1));\n    packing[5] = f32f16 => |k| k.with_packing(f32::packing(32), f16::packing(1));\n    quality(ManuallyOptimized)\n    store(f16)\n}\nMMMExternKernel!(fma_mmm_f32_32x3<f32>(32,3)@(256,4) where(FMA)\n packing[1] = f32f16 => |k| k.with_packing(f32::packing(32).align(256), f16::packing(3));\n packing[2] = f16f32 => |k| k.with_packing(f16::packing(32).align(256), f32::packing(3));\n packing[3] = f16f16 => |k| k.with_packing(f16::packing(32).align(256), f16::packing(3));\n quality(ManuallyOptimized)\n store(f16)\n);\n\nMMMExternKernel!(avx512_mmm_f32_128x1<f32>(128, 1)@(512,4) where (AVX512F) quality(ManuallyOptimized));\nMMMExternKernel!(avx512_mmm_f32_16x1 <f32>( 16, 1)@(512,4) where (AVX512F) quality(ManuallyOptimized));\nMMMExternKernel!(avx512_mmm_f32_16x12<f32>( 16,12)@(512,4) where (AVX512F) quality(ManuallyOptimized));\nMMMExternKernel!(avx512_mmm_f32_16x8 <f32>( 16, 8)@(512,4) where (AVX512F) quality(ManuallyOptimized));\nMMMExternKernel!(avx512_mmm_f32_32x6 <f32>( 32, 6)@(512,4) where (AVX512F) quality(ManuallyOptimized));\nMMMExternKernel!(avx512_mmm_f32_32x5 <f32>( 32, 5)@(512,4) where (AVX512F) quality(ManuallyOptimized));\nMMMExternKernel!(avx512_mmm_f32_48x4 <f32>( 48, 4)@(512,4) where (AVX512F) quality(ManuallyOptimized));\nMMMExternKernel!(avx512_mmm_f32_64x3 <f32>( 64, 3)@(512,4) where (AVX512F) quality(ManuallyOptimized));\nMMMExternKernel!(avx512_mmm_f32_80x2 <f32>( 80, 2)@(512,4) where (AVX512F) quality(ManuallyOptimized));\n\nMMMExternKernel! { avx2_mmm_i32_8x8<i32>(8,8)@(256,4) where(AVX2)\n    packing[1] = i8i8 => |k| k.with_packing(PackedFormat::new(DatumType::I8, 8, 256), PackedFormat::new(DatumType::I8, 8, 4));\n    quality(ManuallyOptimized)\n    store(i8)\n}\n\npub fn plug(ops: &mut Ops) {\n    if is_x86_feature_detected!(\"avx2\") {\n        plug_avx2(ops);\n        if is_x86_feature_detected!(\"fma\") {\n            plug_fma(ops);\n            if is_x86_feature_detected!(\"avx512f\") {\n                plug_avx512f(ops);\n            }\n        }\n    }\n}\n\npub fn plug_avx2(ops: &mut Ops) {\n    ops.mmm_impls.push(mmm::avx2_mmm_i32_8x8.mmm());\n    ops.qmmm_i32 = Box::new(|_, _, _| mmm::avx2_mmm_i32_8x8.mmm());\n    log::info!(\"qmmm_i32: x86_64/avx2 activated\");\n}\n\npub fn plug_fma(ops: &mut Ops) {\n    ops.mmm_impls.extend([\n        fma_mmm_f32_8x8.mmm(),\n        fma_mmm_f32_16x5.mmm(),\n        fma_mmm_f32_16x6.mmm(),\n        fma_mmm_f32_24x4.mmm(),\n        fma_mmm_f32_32x3.mmm(),\n        fma_mmm_f32_40x2.mmm(),\n        fma_mmm_f32_64x1.mmm(),\n    ]);\n\n    ops.mmv_f32 = Box::new(|_, _| fma_mmm_f32_64x1.mmm());\n\n    ops.mmm_f32 = Box::new(|_, _, n| {\n        if n.is_none() {\n            return fma_mmm_f32_16x6.mmm();\n        }\n\n        let n = n.unwrap();\n\n        match n {\n            1 => unreachable!(\"should've been mmv\"),\n            2 => return fma_mmm_f32_40x2.mmm(),\n            3 => return fma_mmm_f32_32x3.mmm(),\n            4 => return fma_mmm_f32_24x4.mmm(),\n            5 => return fma_mmm_f32_16x5.mmm(),\n            6 => return fma_mmm_f32_16x6.mmm(),\n            8 => return fma_mmm_f32_8x8.mmm(),\n            _ => {}\n        };\n\n        let scaling_baseline = 60.0;\n        let kernel_normalized_perf = [\n            44.0 / scaling_baseline, // 8x8\n            54.0 / scaling_baseline, // 2x6\n            54.0 / scaling_baseline, // 2x5\n            54.0 / scaling_baseline, // 3x4\n            54.0 / scaling_baseline, // 4x3\n            54.0 / scaling_baseline, // 5x2\n        ];\n\n        fn compute_efficiency(n: usize, kernel_width: usize, scale: f32) -> f32 {\n            let kernel_width = kernel_width as f32;\n            let n = n as f32;\n            let batch_count = (n / kernel_width).ceil();\n            let actual_count = batch_count * kernel_width;\n            let multi_batch_penalty = 1.0 - batch_count / 100.0;\n            n / actual_count * scale * multi_batch_penalty\n        }\n\n        let efficiencies = [\n            compute_efficiency(n, 8, kernel_normalized_perf[0]),\n            compute_efficiency(n, 6, kernel_normalized_perf[1]),\n            compute_efficiency(n, 5, kernel_normalized_perf[2]),\n            compute_efficiency(n, 4, kernel_normalized_perf[3]),\n            compute_efficiency(n, 3, kernel_normalized_perf[4]),\n            compute_efficiency(n, 2, kernel_normalized_perf[5]),\n        ];\n\n        let best_idx = efficiencies\n            .iter()\n            .copied()\n            .enumerate()\n            .fold((0, 0.0), |max, val| if val.1 > max.1 { val } else { max });\n\n        match best_idx.0 {\n            0 => fma_mmm_f32_8x8.mmm(),\n            1 => fma_mmm_f32_16x6.mmm(),\n            2 => fma_mmm_f32_16x5.mmm(),\n            3 => fma_mmm_f32_24x4.mmm(),\n            4 => fma_mmm_f32_32x3.mmm(),\n            5 => fma_mmm_f32_40x2.mmm(),\n            _ => unreachable!(\"not a valid index\"),\n        }\n    });\n    log::info!(\"mmm_f32, mmv_f32: x86_64/fma activated\");\n\n    if is_x86_feature_detected!(\"f16c\") {\n        ops.mmm_impls.push(mmm::fma_mmm_f32_32x1.mmm()); // q40f32 requires f16c\n        log::info!(\"found f16c, added fake-f16 and q40-able kernels\");\n    }\n}\n\npub fn plug_avx512f(ops: &mut Ops) {\n    ops.mmm_impls.push(avx512_mmm_f32_128x1.mmm());\n    ops.mmm_impls.push(avx512_mmm_f32_80x2.mmm());\n    ops.mmm_impls.push(avx512_mmm_f32_48x4.mmm());\n    ops.mmm_impls.push(avx512_mmm_f32_64x3.mmm());\n    ops.mmm_impls.push(avx512_mmm_f32_16x12.mmm());\n    ops.mmv_f32 = Box::new(|m, _k| match m {\n        Some(m) if m < 31 => avx512_mmm_f32_16x1.mmm(),\n        _ => avx512_mmm_f32_128x1.mmm(),\n    });\n\n    ops.mmm_f32 = Box::new(|m, _, n| match (m, n) {\n        (_, Some(1)) => unreachable!(\"should've been mmv\"),\n        (_, Some(2)) => avx512_mmm_f32_80x2.mmm(),\n        (Some(m), _) if m <= 16 => mmm::avx512_mmm_f32_16x12.mmm(),\n        (_, Some(n)) if n % 4 == 0 && n % 3 != 0 && n < 32 => avx512_mmm_f32_48x4.mmm(),\n        (_, Some(n)) if n < 32 => avx512_mmm_f32_64x3.mmm(),\n        _ => avx512_mmm_f32_16x12.mmm(),\n    });\n    log::info!(\"mmm_f32, mmv_f32: x86_64/avx512f activated\");\n}\n"
  },
  {
    "path": "linalg/src/x86_64_fma/panel_extract.rs",
    "content": "use super::*;\nuse crate::Ops;\nuse crate::pack::{PackedFormat, Packing};\nuse tract_data::internal::*;\n\npub fn plug(ops: &mut Ops) {\n    ops.panel_extractors.extend([packed_32_q40_to_f32.clone(), packed_32_f16_to_f32.clone()]);\n}\n\npanel_extractor!(kernel_packed_32_q40_to_f32 as packed_32_q40_to_f32(\n    Box::new(super::mmm::pq40_r32()),\n    f32::packing(32).align(32)\n) where(AVX2));\n\npanel_extractor!(kernel_packed_32_f16_to_f32 as packed_32_f16_to_f32(\n    Box::new(PackedFormat::new(f16::datum_type(), 32, 32)),\n    f32::packing(32).align(32)\n) where(AVX2));\n\n#[target_feature(enable = \"avx2\")]\nunsafe fn kernel_packed_32_q40_to_f32(input: *const u8, output: *mut u8, k: usize) {\n    unsafe {\n        if k == 0 {\n            return;\n        }\n        debug_assert!(k % 32 == 0);\n        debug_assert!(output as usize % 32 == 0);\n        std::arch::asm!(\"\n    vbroadcastss    ymm14, dword ptr [{mask}]\n    vbroadcastss    ymm13, dword ptr [{eight}]\n\n    2:\n        vmovaps         xmm4, [{i}]\n        vmovaps         xmm5, [{i} + 16]\n        vmovaps         xmm6, [{i} + 32]\n        vmovaps         xmm7, [{i} + 48]\n        vcvtph2ps       ymm4, xmm4\n        vcvtph2ps       ymm5, xmm5\n        vcvtph2ps       ymm6, xmm6\n        vcvtph2ps       ymm7, xmm7\n        add             {i}, 64\n\n        mov {k2}, 32\n    3:\n        vmovaps         xmm8, [{i}]            // 32 nibbles\n        vpand           xmm10, xmm8, xmm14     // 16 bytes\n        vpmovzxbd       ymm9, xmm10            // 8 u32\n\n        vpermilpd       xmm10, xmm10, 1        // swap 64bit halves\n        vpmovzxbd       ymm10, xmm10           // 8 u32\n\n        vpsrlw          xmm8, xmm8, 4\n        vpand           xmm12, xmm8, xmm14      // 16 bytes\n        vpmovzxbd       ymm11, xmm12            // 8 u32\n        vpermilpd       xmm12, xmm12, 1         // swap 64bit halves\n        vpmovzxbd       ymm12, xmm12            // 8 u32\n\n        vpsubd          ymm9, ymm9, ymm13\n        vpsubd          ymm10, ymm10, ymm13\n        vpsubd          ymm11, ymm11, ymm13\n        vpsubd          ymm12, ymm12, ymm13\n\n        vcvtdq2ps       ymm9, ymm9\n        vcvtdq2ps       ymm10, ymm10\n        vcvtdq2ps       ymm11, ymm11\n        vcvtdq2ps       ymm12, ymm12\n\n        vmulps          ymm9, ymm9, ymm4\n        vmulps          ymm10, ymm10, ymm5\n        vmulps          ymm11, ymm11, ymm6\n        vmulps          ymm12, ymm12, ymm7\n\n        vmovaps         [{o}], ymm9\n        vmovaps         [{o}+32], ymm10\n        vmovaps         [{o}+64], ymm11\n        vmovaps         [{o}+96], ymm12\n\n        add             {i}, 16\n        add             {o}, 128\n        sub             {k2}, 1\n        jnz             3b\n\n        sub {k}, 32\n        jnz 2b;\n            \",\n        mask = in(reg) &0x0F0F0F0F,\n        eight = in(reg) &0x08,\n        k = inout(reg) k => _,\n        k2 = out(reg) _,\n        i = inout(reg) input => _,\n        o = inout(reg) output => _,\n        out(\"ymm0\") _, out(\"ymm1\") _, out(\"ymm2\") _, out(\"ymm3\") _,\n        out(\"ymm4\") _, out(\"ymm5\") _, out(\"ymm6\") _, out(\"ymm7\") _,\n        out(\"ymm8\") _, out(\"ymm9\") _, out(\"ymm10\") _, out(\"ymm11\") _,\n        out(\"ymm12\") _, out(\"ymm13\") _, out(\"ymm14\") _, out(\"ymm15\") _\n        );\n    }\n}\n\n#[target_feature(enable = \"avx2\")]\nunsafe fn kernel_packed_32_f16_to_f32(input: *const u8, output: *mut u8, k: usize) {\n    unsafe {\n        if k == 0 {\n            return;\n        }\n        debug_assert!(output as usize % 32 == 0);\n        std::arch::asm!(\"\n    2:\n        vmovaps         xmm4, [{i}]\n        vmovaps         xmm5, [{i} + 16]\n        vmovaps         xmm6, [{i} + 32]\n        vmovaps         xmm7, [{i} + 48]\n\n        vcvtph2ps       ymm4, xmm4\n        vcvtph2ps       ymm5, xmm5\n        vcvtph2ps       ymm6, xmm6\n        vcvtph2ps       ymm7, xmm7\n\n        vmovaps         [{o}], ymm4\n        vmovaps         [{o}+32], ymm5\n        vmovaps         [{o}+64], ymm6\n        vmovaps         [{o}+96], ymm7\n\n        add             {i}, 64\n        add             {o}, 128\n\n        sub {k}, 1\n        jnz 2b;\n            \",\n        k = inout(reg) k => _,\n        i = inout(reg) input => _,\n        o = inout(reg) output => _,\n        out(\"ymm4\") _, out(\"ymm5\") _, out(\"ymm6\") _, out(\"ymm7\") _,\n        );\n    }\n}\n"
  },
  {
    "path": "linalg/src/x86_64_fma/softmax.rs",
    "content": "map_reduce_impl_wrap!(\n    f32,\n    x86_64_fma_softmax2_fastcompact_f32_32n,\n    32,\n    8,\n    f32,\n    f32::MIN,\n    0f32,\n    #[inline(never)]\n    fn run(buf: &mut [f32], max: f32) -> f32 {\n        assert!(buf.len() % 32 == 0);\n        assert!(buf.len() > 0);\n        unsafe { x86_64_fma_softmax2_fastcompact_f32_32n_run(buf, max) }\n    },\n    #[inline(never)]\n    fn reduce_two(a: f32, b: f32) -> f32 {\n        a + b\n    }\n);\n\n#[target_feature(enable = \"avx,fma\")]\nunsafe fn x86_64_fma_softmax2_fastcompact_f32_32n_run(buf: &mut [f32], max: f32) -> f32 {\n    unsafe {\n        let len = buf.len();\n        let ptr = buf.as_ptr();\n        let mut acc = 0f32;\n        const MLN2: f32 = 0.6931471805f32;\n        const A: f32 = 8388608.0f32;\n        const B: f32 = 1065353216.0f32;\n        const C: f32 = 60801.0f32;\n        const SLOPE: f32 = A / MLN2;\n        const OFFSET: f32 = B - C;\n        std::arch::asm!(\"\n            vbroadcastss ymm0, xmm0\n            vmovaps ymm1, ymm0\n            vmovaps ymm2, ymm0\n            vmovaps ymm3, ymm0\n\n            vpxor   ymm12, ymm12, ymm12\n            vbroadcastss ymm13, xmm13\n            vbroadcastss ymm14, xmm14\n            vbroadcastss ymm15, xmm15\n            2:\n                vmovaps ymm4, [{ptr}]\n                vmovaps ymm5, [{ptr} + 32]\n                vmovaps ymm6, [{ptr} + 64]\n                vmovaps ymm7, [{ptr} + 96]\n\n                vsubps ymm4, ymm4, ymm13\n                vsubps ymm5, ymm5, ymm13\n                vsubps ymm6, ymm6, ymm13\n                vsubps ymm7, ymm7, ymm13\n\n                vmovaps ymm8, ymm15\n                vmovaps ymm9, ymm15\n                vmovaps ymm10, ymm15\n                vmovaps ymm11, ymm15\n\n                vfmadd231ps ymm8, ymm4, ymm14\n                vfmadd231ps ymm9, ymm5, ymm14\n                vfmadd231ps ymm10, ymm6, ymm14\n                vfmadd231ps ymm11, ymm7, ymm14\n\n                vmaxps ymm8, ymm8, ymm12\n                vmaxps ymm9, ymm9, ymm12\n                vmaxps ymm10, ymm10, ymm12\n                vmaxps ymm11, ymm11, ymm12\n\n                vcvttps2dq ymm8, ymm8\n                vcvttps2dq ymm9, ymm9\n                vcvttps2dq ymm10, ymm10\n                vcvttps2dq ymm11, ymm11\n\n                vmovaps [{ptr}]     , ymm8\n                vmovaps [{ptr} + 32], ymm9\n                vmovaps [{ptr} + 64], ymm10\n                vmovaps [{ptr} + 96], ymm11\n\n                vaddps ymm0, ymm0, ymm8\n                vaddps ymm1, ymm1, ymm9\n                vaddps ymm2, ymm2, ymm10\n                vaddps ymm3, ymm3, ymm11\n\n                add {ptr}, 128\n                sub {len}, 32\n                jnz 2b\n\n            vaddps ymm0, ymm0, ymm1\n            vaddps ymm2, ymm2, ymm3\n            vaddps ymm0, ymm0, ymm2\n            vperm2f128 ymm1, ymm0, ymm0, 1\n            vaddps xmm0, xmm0, xmm1\n            vpermilps xmm1, xmm0, 2 + (3 << 2)\n            vaddps xmm0, xmm0, xmm1\n            vpermilps xmm1, xmm0, 1\n            vaddps xmm0, xmm0, xmm1\n            \",\n        len = inout(reg) len => _,\n        ptr = inout(reg) ptr => _,\n        inout(\"ymm0\") acc,\n        out(\"ymm1\") _, out(\"ymm2\") _, out(\"ymm3\") _,\n        out(\"ymm4\") _, out(\"ymm5\") _, out(\"ymm6\") _, out(\"ymm7\") _,\n        out(\"ymm8\") _, out(\"ymm9\") _, out(\"ymm10\") _, out(\"ymm11\") _,\n        out(\"ymm12\") _,\n        inout(\"ymm13\") max => _,\n        inout(\"ymm14\") SLOPE => _,\n        inout(\"ymm15\") OFFSET => _,\n        );\n        acc\n    }\n}\n\n#[cfg(test)]\nmod test_x86_64_fma_softmax2_fastcompact_f32_32n {\n    use super::*;\n    crate::softmax_l2_frame_tests!(\n        is_x86_feature_detected!(\"fma\"),\n        f32,\n        x86_64_fma_softmax2_fastcompact_f32_32n\n    );\n}\n"
  },
  {
    "path": "linalg/src/x86_64_fma.rs",
    "content": "use crate::Ops;\nuse crate::frame::element_wise::ElementWiseKer;\nuse crate::frame::reduce::{MapReduceKer, ReduceKer};\nuse crate::x86_64_fma::softmax::x86_64_fma_softmax2_fastcompact_f32_32n;\n\npub mod mmm;\n\npub mod by_scalar;\nmod intel;\npub mod max;\npub mod panel_extract;\npub mod softmax;\n\nconst AVX2: fn() -> bool = || is_x86_feature_detected!(\"avx2\");\nconst FMA: fn() -> bool = || is_x86_feature_detected!(\"fma\");\nconst AVX512F: fn() -> bool = || is_x86_feature_detected!(\"avx512f\");\n\ntanh_impl!(f32, fma_tanh_f32, 8, 8, is_x86_feature_detected!(\"fma\"));\nsigmoid_impl!(f32, fma_sigmoid_f32, 8, 8, is_x86_feature_detected!(\"fma\"));\n\nfn plug_avx2(_ops: &mut Ops) {}\n\nfn plug_fma(ops: &mut Ops) {\n    panel_extract::plug(ops);\n\n    ops.sigmoid_f32 = Box::new(|| fma_sigmoid_f32::ew());\n    ops.tanh_f32 = Box::new(|| fma_tanh_f32::ew());\n\n    ops.mul_by_scalar_f32 = Box::new(|| by_scalar::x86_64_avx_f32_mul_by_scalar_32n::ew());\n    ops.max_f32 = Box::new(|| max::x86_64_fma_max_f32_32n::red());\n    ops.softmax2_fastcompact_f32 = Box::new(|| x86_64_fma_softmax2_fastcompact_f32_32n::red());\n\n    log::info!(\"sigmoid_f32, tanh_f32: x86_64/fma activated\");\n}\n\nfn plug_avx512f(_ops: &mut Ops) {}\n\npub fn plug(ops: &mut Ops) {\n    mmm::plug(ops);\n    if is_x86_feature_detected!(\"avx2\") {\n        plug_avx2(ops);\n        if is_x86_feature_detected!(\"fma\") {\n            plug_fma(ops);\n            if is_x86_feature_detected!(\"avx512f\") {\n                plug_avx512f(ops);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "linalg/tests/virtual_im2col.rs",
    "content": "use std::alloc::Layout;\nuse std::fmt::Display;\n\nuse DatumType::F32;\nuse proptest::arbitrary::Arbitrary;\nuse proptest::prelude::*;\nuse proptest::strategy::{BoxedStrategy, Strategy};\nuse tract_data::internal::*;\nuse tract_linalg::WeightType;\nuse tract_linalg::mmm::FusedSpec;\nuse tract_linalg::mmm::{AsInputValue, EagerPackedInput, MMMInputFormat, MMMInputValue};\nuse tract_linalg::pack::{PackedFormat, PackingWriter};\n\nproptest::proptest! {\n    #[test]\n    fn prop(pb in any::<ConvProblem>()) {\n        pb.check()\n    }\n}\n\n#[test]\nfn test1() {\n    ConvProblem {\n        lazy_im2col: false,\n        input: tensor3(&[[[1f32]]]),\n        filters: tensor4(&[[[[-1f32]]]]),\n    }\n    .check()\n}\n\n#[test]\nfn test_axes_0() {\n    // CHW HWIO CHW\n    // 121 1112 221\n    ConvProblem {\n        lazy_im2col: false,\n        input: tensor3(&[[[0f32], [-1.0]]]),\n        filters: tensor4(&[[[[0f32, -1f32]]]]),\n    }\n    .check()\n}\n\n#[test]\nfn test_axes_1() {\n    ConvProblem {\n        lazy_im2col: false,\n        input: tensor3(&[[[0f32, 1.]]]),\n        filters: tensor4(&[[[[1f32]]]]),\n    }\n    .check()\n}\n\n#[test]\nfn test_lazy_0() {\n    ConvProblem { lazy_im2col: true, input: tensor3(&[[[1f32]]]), filters: tensor4(&[[[[1f32]]]]) }\n        .check()\n}\n\n#[test]\nfn test_lazy_1() {\n    ConvProblem {\n        lazy_im2col: true,\n        input: tensor3(&[[[0f32], [0.], [0.]]]),\n        filters: tensor4(&[[[[0f32]]]]),\n    }\n    .check()\n}\n\n#[test]\nfn test_lazy_2() {\n    ConvProblem {\n        lazy_im2col: true,\n        input: tensor3(&[[[0f32, 0.], [0., 1.]]]),\n        filters: tensor4(&[[[[0f32]], [[1.]]]]),\n    }\n    .check()\n}\n\n#[test]\nfn test_lazy_3() {\n    // CHW HWIO CHW\n    // 212 1221 111\n    // im2col: k=4, n=1, k <- kh, kw, c\n    // 0 X X X X kh=0, kw=0, c=0\n    // 1 X X X X kh=0, kw=0, c=1\n    // 0 X X X X kh=0, kw=1, c=0\n    // 0 X X X X kh=0, kw=1, c=1\n    ConvProblem {\n        lazy_im2col: true,\n        input: tensor3(&[[[0f32, 0.]], [[1., 0.]]]),\n        filters: tensor4(&[[[[0f32], [0.]], [[1.], [0.]]]]),\n    }\n    .check()\n}\n\n#[test]\nfn test_eager_asan_0() {\n    ConvProblem {\n        lazy_im2col: false,\n        input: tensor(vec![3, 3, 5]),\n        filters: tensor(vec![3, 3, 3, 1]),\n    }\n    .check()\n}\n\n// 2D valid, no group, no dil, no stride, HWIO, CHW\n#[derive(Clone, Debug)]\npub struct ConvProblem {\n    pub lazy_im2col: bool,\n    pub input: Tensor,\n    pub filters: Tensor,\n}\n\nfn mknhw(filters: &[usize], input: &[usize]) -> (usize, usize, usize, usize, usize) {\n    let m = filters[3];\n    let k = filters[0..3].iter().product::<usize>();\n    let h = input[1] - filters[0] + 1;\n    let w = input[2] - filters[1] + 1;\n    let n = h * w;\n    (m, k, n, h, w)\n}\n\nimpl ConvProblem {\n    fn reference(&self) -> Tensor {\n        let (m, _, _, h, w) = mknhw(self.filters.shape(), self.input.shape());\n        let output_shape = [m, h, w];\n        let mut output = Tensor::zero::<f32>(&output_shape).unwrap();\n        let mut output_plain = output.try_as_plain_mut().unwrap();\n        let mut output_view = output_plain.to_array_view_mut::<f32>().unwrap();\n        let input_view = self.input.to_plain_array_view::<f32>().unwrap();\n        let filters_view = self.filters.to_plain_array_view::<f32>().unwrap();\n        for geo_out in tract_ndarray::indices(&output_shape[1..]) {\n            for ker_geo in tract_ndarray::indices(&self.filters.shape()[0..2]) {\n                for ci in 0..self.filters.shape()[2] {\n                    for co in 0..self.filters.shape()[3] {\n                        let output_coord = [co, geo_out[0], geo_out[1]];\n                        let input_coord = [ci, geo_out[0] + ker_geo[0], geo_out[1] + ker_geo[1]];\n                        let ker_coord = [ker_geo[0], ker_geo[1], ci, co];\n                        output_view[output_coord] +=\n                            filters_view[ker_coord] * input_view[input_coord];\n                    }\n                }\n            }\n        }\n        output\n    }\n\n    pub fn tract(&self) -> TractResult<Tensor> {\n        let (m, k, n, h, w) = mknhw(self.filters.shape(), self.input.shape());\n        let output_shape = [m, h, w];\n        let internal_output_shape = [m, h * w];\n        let mmm = tract_linalg::ops().mmm(F32, Some(m), Some(k), Some(n)).unwrap();\n        let output = Tensor::zero::<f32>(&internal_output_shape)?;\n        let reshaped_filters = self.filters.clone().into_shape(&[k, m])?;\n        let (a_pack, b_pack) = &mmm.packings()[0];\n        let a = a_pack.prepare_one(&reshaped_filters, 0, 1)?;\n        unsafe {\n            let im2col: Box<dyn MMMInputValue> = if self.lazy_im2col {\n                LazyIm2colSpec {\n                    full_kernel_shape: self.filters.shape().into(),\n                    packer: b_pack.downcast_ref::<PackedFormat>().unwrap().clone(),\n                }\n                .wrap(&self.input.view())\n            } else {\n                EagerIm2colSpec {\n                    full_kernel_shape: self.filters.shape().into(),\n                    packer: b_pack.downcast_ref::<PackedFormat>().unwrap().clone(),\n                }\n                .wrap(&self.input.view())\n            };\n            let c_store = mmm.c_view(Some(0), Some(1)).wrap(&output.view());\n            mmm.run(\n                m,\n                n,\n                &[\n                    FusedSpec::AddMatMul {\n                        a: AsInputValue::Owned(a),\n                        b: AsInputValue::Owned(im2col),\n                        packing: 0,\n                    },\n                    FusedSpec::Store(c_store),\n                ],\n            )\n            .unwrap()\n        }\n        output.into_shape(&output_shape)\n    }\n\n    fn check(&self) {\n        let expected = self.reference();\n        let found = self.tract().unwrap();\n        if found.close_enough(&expected, true).is_err() {\n            println!(\"found: \");\n            println!(\"{:?}\", found.to_plain_array_view::<f32>().unwrap());\n            println!(\"expected: \");\n            println!(\"{:?}\", expected.to_plain_array_view::<f32>().unwrap());\n        }\n        found.close_enough(&expected, true).unwrap()\n    }\n}\n\nimpl Arbitrary for ConvProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {\n        (any::<bool>(), 1..4usize, 1..4usize, 1..4usize, 1..4usize, 0..3usize, 0..3usize)\n            .prop_map(|(eager_im2col, h, w, i, o, extra_h, extra_w)| {\n                let filters = tensor(vec![h, w, i, o]);\n                let input = tensor(vec![i, h + extra_h, w + extra_w]);\n                ConvProblem { lazy_im2col: eager_im2col, filters, input }\n            })\n            .boxed()\n    }\n}\n\nfn tensor(shape: Vec<usize>) -> Tensor {\n    let mut tensor = Tensor::zero::<f32>(&shape).unwrap();\n    tensor\n        .try_as_plain_mut()\n        .unwrap()\n        .as_slice_mut::<f32>()\n        .unwrap()\n        .iter_mut()\n        .enumerate()\n        .for_each(|(ix, x)| *x = ix as f32);\n    tensor\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\nstruct EagerIm2colSpec {\n    packer: PackedFormat,\n    full_kernel_shape: TVec<usize>,\n}\n\nimpl EagerIm2colSpec {\n    fn wrap(&self, input: &TensorView) -> Box<dyn MMMInputValue> {\n        let (_, k, n, h, w) = mknhw(&self.full_kernel_shape, input.shape());\n        // let input = input.to_array_view::<f32>().unwrap();\n        let ci = input.shape()[0];\n        let kh = self.full_kernel_shape[0];\n        let kw = self.full_kernel_shape[1];\n        let im2col = tract_ndarray::Array5::<f32>::from_shape_fn(\n            [kh, kw, ci, h, w],\n            |(kh, kw, ci, h, w)| *input.at([ci, h + kh, w + kw]).unwrap(),\n        )\n        .into_shape_with_order([k, n])\n        .unwrap();\n        Box::new(EagerIm2col { im2col: im2col.into_tensor(), packer: self.packer.clone(), k })\n    }\n}\n\nimpl Display for EagerIm2colSpec {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"EagerIm2colSpec\")\n    }\n}\n\nimpl MMMInputFormat for EagerIm2colSpec {\n    fn prepare_tensor(&self, _t: &Tensor, _k_axis: usize, _mn_axis: usize) -> TractResult<Tensor> {\n        todo!();\n    }\n\n    fn precursor(&self) -> WeightType {\n        WeightType::Plain(f32::datum_type())\n    }\n\n    fn k_alignment(&self) -> usize {\n        1\n    }\n\n    fn r(&self) -> usize {\n        self.packer.r()\n    }\n\n    fn mem_size(&self, _k: TDim, _mn: TDim) -> TDim {\n        unimplemented!()\n    }\n\n    fn extract_at_mn_f16(\n        &self,\n        _data: &EagerPackedInput,\n        _mn: usize,\n        _slice: &mut [f16],\n    ) -> TractResult<()> {\n        todo!();\n    }\n\n    fn extract_at_mn_f32(\n        &self,\n        _data: &EagerPackedInput,\n        _mn: usize,\n        _slice: &mut [f32],\n    ) -> TractResult<()> {\n        todo!();\n    }\n\n    fn prepare_one(\n        &self,\n        _t: &Tensor,\n        _k_axis: usize,\n        _mn_axis: usize,\n    ) -> TractResult<Box<dyn MMMInputValue>> {\n        todo!()\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\nstruct EagerIm2col {\n    packer: PackedFormat,\n    im2col: Tensor,\n    k: usize,\n}\n\nimpl Display for EagerIm2col {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"eager\")\n    }\n}\n\nimpl MMMInputValue for EagerIm2col {\n    fn scratch_panel_buffer_layout(&self) -> Option<std::alloc::Layout> {\n        Some(\n            Layout::from_size_align(\n                self.packer.single_panel_len(self.k) * f32::datum_type().size_of(),\n                self.packer.alignment(),\n            )\n            .unwrap(),\n        )\n    }\n\n    fn panel_bytes(&self, i: usize, buffer: Option<*mut u8>) -> TractResult<*const u8> {\n        let buffer = buffer.unwrap();\n        let mn = self.im2col.shape()[1];\n        unsafe {\n            self.packer.pack_t::<f32>(\n                buffer as _,\n                self.im2col.as_ptr().unwrap(),\n                mn,\n                mn as isize,\n                1,\n                0..self.k,\n                (i * self.packer.r)..((i + 1) * self.packer.r),\n            );\n        }\n        Ok(buffer)\n    }\n\n    fn k(&self) -> usize {\n        self.k\n    }\n\n    fn mn(&self) -> usize {\n        self.im2col.shape()[1]\n    }\n\n    fn format(&self) -> &dyn tract_linalg::mmm::MMMInputFormat {\n        &self.packer\n    }\n\n    fn exotic_fact(&self) -> &dyn ExoticFact {\n        unimplemented!()\n    }\n\n    fn extract_at_mn_f16(&self, _mn: usize, _slice: &mut [f16]) -> TractResult<()> {\n        unimplemented!()\n    }\n\n    fn extract_at_mn_f32(&self, _mn: usize, _slice: &mut [f32]) -> TractResult<()> {\n        unimplemented!()\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\nstruct LazyIm2colSpec {\n    packer: PackedFormat,\n    full_kernel_shape: TVec<usize>,\n}\n\nimpl LazyIm2colSpec {\n    fn wrap(&self, input: &TensorView) -> Box<dyn MMMInputValue> {\n        let (_, _, _, h, w) = mknhw(&self.full_kernel_shape, input.shape());\n        let kh = self.full_kernel_shape[0];\n        let kw = self.full_kernel_shape[1];\n        let ci = self.full_kernel_shape[2];\n        let input_strides = input.strides();\n        let k_offsets = (0..kh as isize)\n            .flat_map(|kh| {\n                (0..kw as isize).flat_map(move |kw| {\n                    (0..ci as isize).map(move |ci| {\n                        ci * input_strides[0] + kh * input_strides[1] + kw * input_strides[2]\n                    })\n                })\n            })\n            .collect();\n        let n_offsets = (0..h as isize)\n            .flat_map(|h| (0..w as isize).map(move |w| h * input_strides[1] + w * input_strides[2]))\n            .collect();\n        unsafe {\n            Box::new(LazyIm2col {\n                spec: self.clone(),\n                image: input.as_ptr_unchecked(),\n                k_offsets,\n                n_offsets,\n                packer: self.packer.clone(),\n            })\n        }\n    }\n}\n\nimpl Display for LazyIm2colSpec {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"LazyIm2colSpec\")\n    }\n}\n\nimpl MMMInputFormat for LazyIm2colSpec {\n    fn prepare_tensor(&self, _t: &Tensor, _k_axis: usize, _mn_axis: usize) -> TractResult<Tensor> {\n        todo!();\n    }\n    fn prepare_one(\n        &self,\n        _t: &Tensor,\n        _k_axis: usize,\n        _mn_axis: usize,\n    ) -> TractResult<Box<dyn MMMInputValue>> {\n        todo!();\n    }\n\n    fn precursor(&self) -> WeightType {\n        WeightType::Plain(f32::datum_type())\n    }\n\n    fn k_alignment(&self) -> usize {\n        1\n    }\n\n    fn r(&self) -> usize {\n        self.packer.r()\n    }\n\n    fn mem_size(&self, _k: TDim, _mn: TDim) -> TDim {\n        unimplemented!()\n    }\n\n    fn extract_at_mn_f16(\n        &self,\n        _data: &EagerPackedInput,\n        _mn: usize,\n        _slice: &mut [f16],\n    ) -> TractResult<()> {\n        todo!();\n    }\n\n    fn extract_at_mn_f32(\n        &self,\n        _data: &EagerPackedInput,\n        _mn: usize,\n        _slice: &mut [f32],\n    ) -> TractResult<()> {\n        todo!();\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\nstruct LazyIm2col {\n    spec: LazyIm2colSpec,\n    packer: PackedFormat,\n    image: *const f32,\n    n_offsets: Vec<isize>,\n    k_offsets: Vec<isize>,\n}\nunsafe impl Send for LazyIm2col {}\nunsafe impl Sync for LazyIm2col {}\n\nimpl Display for LazyIm2col {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"lazy\")\n    }\n}\n\nimpl MMMInputValue for LazyIm2col {\n    fn scratch_panel_buffer_layout(&self) -> Option<std::alloc::Layout> {\n        Some(\n            Layout::from_size_align(\n                self.packer.single_panel_len(self.k_offsets.len() * f32::datum_type().size_of()),\n                self.packer.alignment(),\n            )\n            .unwrap(),\n        )\n    }\n\n    fn panel_bytes(&self, i: usize, buffer: Option<*mut u8>) -> TractResult<*const u8> {\n        let buffer = buffer.unwrap() as *mut f32;\n        let mn_end = ((i + 1) * self.packer.r).min(self.n_offsets.len());\n        let n_range = (i * self.packer.r)..mn_end;\n        let k = self.k_offsets.len();\n        unsafe {\n            let mut writer = self.packer.write_with_k_outer(buffer, k, n_range.len());\n            for k in 0..k {\n                for n in n_range.clone() {\n                    writer.write(\n                        *self.image.offset(\n                            self.n_offsets.get_unchecked(n) + self.k_offsets.get_unchecked(k),\n                        ),\n                    )\n                }\n            }\n        }\n        Ok(buffer as _)\n    }\n\n    fn k(&self) -> usize {\n        self.k_offsets.len()\n    }\n\n    fn mn(&self) -> usize {\n        self.n_offsets.len()\n    }\n\n    fn format(&self) -> &dyn MMMInputFormat {\n        &self.spec\n    }\n\n    fn exotic_fact(&self) -> &dyn ExoticFact {\n        unimplemented!()\n    }\n\n    fn extract_at_mn_f16(&self, _mn: usize, _slice: &mut [f16]) -> TractResult<()> {\n        unimplemented!()\n    }\n\n    fn extract_at_mn_f32(&self, _mn: usize, _slice: &mut [f32]) -> TractResult<()> {\n        unimplemented!()\n    }\n}\n"
  },
  {
    "path": "linalg/x86_64/avx512/10x1/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Tile size: 10x1\n\t// Accumulators: 0-9\n\t// Col regs: 10-19\n\t// Row regs: 20, 21\n\n\tvbroadcastss    zmm20,  dword ptr [rcx]\n\n\tvmovaps         zmm10, [rax + 0]\n\tvmovaps         zmm11, [rax + 64]\n    vmovaps         zmm12, [rax + 128]\n\tvmovaps         zmm13, [rax + 192]\n    vmovaps         zmm14, [rax + 256]\n\n    vfmadd231ps     zmm0, zmm10, zmm20\n    vfmadd231ps     zmm1, zmm11, zmm20\n    vfmadd231ps     zmm2, zmm12, zmm20\n    vfmadd231ps     zmm3, zmm13, zmm20\n    vfmadd231ps     zmm4, zmm14, zmm20\n\n\tvmovaps         zmm15, [rax + 320]\n    vmovaps         zmm16, [rax + 384]\n\tvmovaps         zmm17, [rax + 448]\n\tvmovaps         zmm18, [rax + 512]\n\tvmovaps         zmm19, [rax + 576]\n\n    vfmadd231ps     zmm5, zmm10, zmm20\n    vfmadd231ps     zmm6, zmm11, zmm20\n    vfmadd231ps     zmm7, zmm12, zmm20\n    vfmadd231ps     zmm8, zmm13, zmm20\n    vfmadd231ps     zmm9, zmm14, zmm20\n\n\tvbroadcastss    zmm21,  dword ptr [rcx + 4]\n\n\tvmovaps         zmm10, [rax + 640]\n\tvmovaps         zmm11, [rax + 704]\n    vmovaps         zmm12, [rax + 768]\n\tvmovaps         zmm13, [rax + 832]\n    vmovaps         zmm14, [rax + 896]\n\n\tvfmadd231ps     zmm0, zmm10, zmm21\n    vfmadd231ps     zmm1, zmm11, zmm21\n    vfmadd231ps     zmm2, zmm12, zmm21\n    vfmadd231ps     zmm3, zmm13, zmm21\n    vfmadd231ps     zmm4, zmm14, zmm21\n\n\tvmovaps         zmm15, [rax + 960]\n    vmovaps         zmm16, [rax + 1024]\n\tvmovaps         zmm17, [rax + 1088]\n\tvmovaps         zmm18, [rax + 1152]\n\tvmovaps         zmm19, [rax + 1216]\n\n    vfmadd231ps     zmm5, zmm10, zmm21\n    vfmadd231ps     zmm6, zmm11, zmm21\n    vfmadd231ps     zmm7, zmm12, zmm21\n    vfmadd231ps     zmm8, zmm13, zmm21\n    vfmadd231ps     zmm9, zmm14, zmm21\n\n    add rcx, 8\n\tadd rax, 1280\n"
  },
  {
    "path": "linalg/x86_64/avx512/10x1/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 10x1\n\t// Accumulators: 0-9\n\t// Col regs: 10-19\n\t// Row regs: 20\n\n\tvbroadcastss    zmm20,  dword ptr [rcx]\n\n\tvmovaps         zmm10, [rax + 0]\n\tvmovaps         zmm11, [rax + 64]\n    vmovaps         zmm12, [rax + 128]\n\tvmovaps         zmm13, [rax + 192]\n    vmovaps         zmm14, [rax + 256]\n\n    vfmadd231ps     zmm0, zmm10, zmm20\n    vfmadd231ps     zmm1, zmm11, zmm20\n    vfmadd231ps     zmm2, zmm12, zmm20\n    vfmadd231ps     zmm3, zmm13, zmm20\n    vfmadd231ps     zmm4, zmm14, zmm20\n\n\tvmovaps         zmm15, [rax + 320]\n    vmovaps         zmm16, [rax + 384]\n\tvmovaps         zmm17, [rax + 448]\n\tvmovaps         zmm18, [rax + 512]\n\tvmovaps         zmm19, [rax + 576]\n\n    vfmadd231ps     zmm5, zmm10, zmm20\n    vfmadd231ps     zmm6, zmm11, zmm20\n    vfmadd231ps     zmm7, zmm12, zmm20\n    vfmadd231ps     zmm8, zmm13, zmm20\n    vfmadd231ps     zmm9, zmm14, zmm20\n\n    add rcx, 4\n\tadd rax, 320\n"
  },
  {
    "path": "linalg/x86_64/avx512/1x1/packed_packed_loop1/avx-512.tmpli",
    "content": "\tvbroadcastss    zmm15, dword ptr [rcx]\n\n    vmovups         zmm8, [rax]\n    vfmadd231ps     zmm0, zmm15, zmm8\n\n    add rcx, 4\n\tadd rax, 64\n"
  },
  {
    "path": "linalg/x86_64/avx512/1x1/packed_packed_loop1/unroll-16.tmpli",
    "content": "\tvmovups    zmm31, [rcx]\n\t// vbroadcastss    zmm17, [rcx + 4 * 0]\n\t// vbroadcastss    zmm18, [rcx + 4 * 1]\n\t// vbroadcastss    zmm19, [rcx + 4 * 2]\n\t// vbroadcastss    zmm20, [rcx + 4 * 3]\n\t// vbroadcastss    zmm21, [rcx + 4 * 4]\n\t// vbroadcastss    zmm22, [rcx + 4 * 5]\n\t// vbroadcastss    zmm23, [rcx + 4 * 6]\n\t// vbroadcastss    zmm24, [rcx + 4 * 7]\n\t// vbroadcastss    zmm25, [rcx + 4 * 8]\n\t// vbroadcastss    zmm26, [rcx + 4 * 9]\n\t// vbroadcastss    zmm27, [rcx + 4 * 10]\n\t// vbroadcastss    zmm28, [rcx + 4 * 11]\n\t// vbroadcastss    zmm29, [rcx + 4 * 12]\n\t// vbroadcastss    zmm30, [rcx + 4 * 13]\n\t// vbroadcastss    zmm31, [rcx + 4 * 14]\n\n\tvbroadcastss zmm16, xmm31\n\tvalignd zmm17, zmm31, zmm31, 1\n\tvbroadcastss zmm17, xmm17\n\tvalignd zmm18, zmm31, zmm31, 2\n\tvbroadcastss zmm18, xmm18\n\tvalignd zmm19, zmm31, zmm31, 3\n\tvbroadcastss zmm19, xmm19\n\tvalignd zmm20, zmm31, zmm31, 4\n\tvbroadcastss zmm20, xmm20\n\tvalignd zmm21, zmm31, zmm31, 5\n\tvbroadcastss zmm21, xmm21\n\tvalignd zmm22, zmm31, zmm31, 6\n\tvbroadcastss zmm22, xmm22\n\tvalignd zmm23, zmm31, zmm31, 7\n\tvbroadcastss zmm23, xmm23\n\tvalignd zmm24, zmm31, zmm31, 8\n\tvbroadcastss zmm24, xmm24\n\tvalignd zmm25, zmm31, zmm31, 9\n\tvbroadcastss zmm25, xmm25\n\tvalignd zmm26, zmm31, zmm31, 10\n\tvbroadcastss zmm26, xmm26\n\tvalignd zmm27, zmm31, zmm31, 11\n\tvbroadcastss zmm27, xmm27\n\tvalignd zmm28, zmm31, zmm31, 12\n\tvbroadcastss zmm28, xmm28\n\tvalignd zmm29, zmm31, zmm31, 13\n\tvbroadcastss zmm29, xmm29\n\tvalignd zmm30, zmm31, zmm31, 14\n\tvbroadcastss zmm30, xmm30\n\tvalignd zmm31, zmm31, zmm31, 15\n\tvbroadcastss zmm31, xmm31\n\n\tvfmadd231ps     zmm0, zmm16, [rax + 0]\n    vfmadd231ps     zmm1, zmm17, [rax + 64]\n    vfmadd231ps     zmm2, zmm18, [rax + 128]\n    vfmadd231ps     zmm3, zmm19, [rax + 192]\n\tvfmadd231ps     zmm4, zmm20, [rax + 256]\n    vfmadd231ps     zmm5, zmm21, [rax + 320]\n    vfmadd231ps     zmm6, zmm22, [rax + 384]\n    vfmadd231ps     zmm7, zmm23, [rax + 448]\n\tvfmadd231ps     zmm8, zmm24, [rax + 512]\n    vfmadd231ps     zmm9, zmm25, [rax + 576]\n    vfmadd231ps     zmm10, zmm26, [rax + 640]\n    vfmadd231ps     zmm11, zmm27, [rax + 704]\n\tvfmadd231ps     zmm12, zmm28, [rax + 768]\n    vfmadd231ps     zmm13, zmm29, [rax + 832]\n    vfmadd231ps     zmm14, zmm30, [rax + 896]\n    vfmadd231ps     zmm15, zmm31, [rax + 960]\n\n    add rcx, 64\n\tadd rax, 1024\n"
  },
  {
    "path": "linalg/x86_64/avx512/1x1/packed_packed_loop1/unroll-4.tmpli",
    "content": "\t// slow\n\tvbroadcastss xmm16, dword ptr [rcx]\n\tvbroadcastss xmm17, dword ptr [rcx + 4]\n\tvbroadcastss xmm18, dword ptr [rcx + 8]\n\tvbroadcastss xmm19, dword ptr [rcx + 12]\n\n\t// fast\n\tvmovups\t   \t\txmm31, [rcx]\n\tvbroadcastss \tzmm16, xmm31\n\tvalignd \t\txmm17, xmm31, xmm31, 1\n\tvbroadcastss \tzmm17, xmm17\n\tvalignd \t\txmm18, xmm31, xmm31, 2\n\tvbroadcastss \tzmm18, xmm18\n\tvalignd \t\txmm19, xmm31, xmm31, 3\n\tvbroadcastss \tzmm19, xmm19\n\n\t// commmon\n\tvfmadd231ps\t\tzmm0, zmm16, [rax + 0]\n\tvfmadd231ps\t\tzmm1, zmm17, [rax + 64]\n\tvfmadd231ps\t\tzmm2, zmm18, [rax + 128]\n\tvfmadd231ps\t\tzmm3, zmm19, [rax + 192]\n\n\tadd rcx, 16\n\tadd rax, 256\n"
  },
  {
    "path": "linalg/x86_64/avx512/1x1/packed_packed_loop1/unroll-8.tmpli",
    "content": "\tvmovups    ymm31, [rcx]\n\n\tvbroadcastss zmm16, xmm31\n\tvalignd ymm17, ymm31, ymm31, 1\n\tvbroadcastss zmm17, xmm17\n\tvalignd ymm18, ymm31, ymm31, 2\n\tvbroadcastss zmm18, xmm18\n\tvalignd ymm19, ymm31, ymm31, 3\n\tvbroadcastss zmm19, xmm19\n\tvalignd ymm20, ymm31, ymm31, 4\n\tvbroadcastss zmm20, xmm20\n\tvalignd ymm21, ymm31, ymm31, 5\n\tvbroadcastss zmm21, xmm21\n\tvalignd ymm22, ymm31, ymm31, 6\n\tvbroadcastss zmm22, xmm22\n\tvalignd ymm23, ymm31, ymm31, 7\n\tvbroadcastss zmm23, xmm23\n\n\tvfmadd231ps     zmm0, zmm16, [rax + 0]\n    vfmadd231ps     zmm1, zmm17, [rax + 64]\n    vfmadd231ps     zmm2, zmm18, [rax + 128]\n    vfmadd231ps     zmm3, zmm19, [rax + 192]\n\tvfmadd231ps     zmm4, zmm20, [rax + 256]\n    vfmadd231ps     zmm5, zmm21, [rax + 320]\n    vfmadd231ps     zmm6, zmm22, [rax + 384]\n    vfmadd231ps     zmm7, zmm23, [rax + 448]\n\n    add rcx, 32\n\tadd rax, 512\n"
  },
  {
    "path": "linalg/x86_64/avx512/1x1/packed_packed_loop1/unroll.tmpli",
    "content": "\tvbroadcastss    zmm15,  dword ptr [rcx]\n\n    vmovaps     zmm8, [rax + 0]\n    vfmadd231ps     zmm0, zmm15, zmm8\n\n\tvbroadcastss    zmm16,  dword ptr [rcx + 4]\n    vmovaps     zmm9, [rax + 64]\n    vfmadd231ps     zmm1, zmm16, zmm9\n\n    add rcx, 8\n\tadd rax, 128\n"
  },
  {
    "path": "linalg/x86_64/avx512/1x12/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 1x12\n\t// Accumulators: 0-11\n\t// Col regs: zmm14\n\t// Row regs: zmm15\n\n    vmovaps         zmm15,  [rax]\n\n    vbroadcastss    zmm14, dword ptr [rcx + 0 * 4]\n    vfmadd231ps     zmm0, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 1 * 4]\n    vfmadd231ps     zmm1, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 2 * 4]\n    vfmadd231ps     zmm2, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 3 * 4]\n    vfmadd231ps     zmm3, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 4 * 4]\n    vfmadd231ps     zmm4, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 5 * 4]\n    vfmadd231ps     zmm5, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 6 * 4]\n    vfmadd231ps     zmm6, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 7 * 4]\n    vfmadd231ps     zmm7, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 8 * 4]\n    vfmadd231ps     zmm8, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 9 * 4]\n    vfmadd231ps     zmm9, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 10 * 4]\n    vfmadd231ps     zmm10, zmm15, zmm14\n\n    vbroadcastss    zmm14, dword ptr [rcx + 11 * 4]\n    vfmadd231ps     zmm11, zmm15, zmm14\n\n\tadd rcx, 48\n\tadd rax, 64"
  },
  {
    "path": "linalg/x86_64/avx512/2x5/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Accumulators: 0-9\n\t// Columns: 15-16\n\t// Rows: 10-14\n    vbroadcastss    zmm10,  dword ptr [rcx]\n    vbroadcastss    zmm11,  dword ptr [rcx + 4]\n    vbroadcastss    zmm12,  dword ptr [rcx + 8]\n    vbroadcastss    zmm13,  dword ptr [rcx + 12]\n\tvbroadcastss    zmm14,  dword ptr [rcx + 16]\n\n    vmovaps         zmm15,  [rax]\n    vmovaps         zmm16,  [rax + 64]\n\n    vfmadd231ps     zmm0,   zmm15, zmm10\n    vfmadd231ps     zmm1,   zmm16, zmm10\n\n    vfmadd231ps     zmm2,   zmm15, zmm11\n    vfmadd231ps     zmm3,   zmm16, zmm11\n\n    vfmadd231ps     zmm4,   zmm15, zmm12\n    vfmadd231ps     zmm5,   zmm16, zmm12\n\n    vfmadd231ps     zmm6,   zmm15, zmm13\n    vfmadd231ps     zmm7,   zmm16, zmm13\n\n    vfmadd231ps     zmm8,   zmm15, zmm14\n    vfmadd231ps     zmm9,   zmm16, zmm14\n\n    vbroadcastss    zmm10,  dword ptr [rcx + 20]\n    vbroadcastss    zmm11,  dword ptr [rcx + 24]\n    vbroadcastss    zmm12,  dword ptr [rcx + 28]\n    vbroadcastss    zmm13,  dword ptr [rcx + 32]\n    vbroadcastss    zmm14,  dword ptr [rcx + 36]\n\n    vmovaps         zmm15,  [rax + 128]\n    vmovaps         zmm16,  [rax + 192]\n\n    vfmadd231ps     zmm0,   zmm15, zmm10\n    vfmadd231ps     zmm1,   zmm16, zmm10\n\n    vfmadd231ps     zmm2,   zmm15, zmm11\n    vfmadd231ps     zmm3,   zmm16, zmm11\n\n    vfmadd231ps     zmm4,   zmm15, zmm12\n    vfmadd231ps     zmm5,   zmm16, zmm12\n\n    vfmadd231ps     zmm6,   zmm15, zmm13\n    vfmadd231ps     zmm7,   zmm16, zmm13\n\n    vfmadd231ps     zmm8,   zmm15, zmm14\n    vfmadd231ps     zmm9,   zmm16, zmm14\n\n\tadd rcx, 40\n\tadd rax, 256\n"
  },
  {
    "path": "linalg/x86_64/avx512/2x5/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Accumulators: 0-9\n\t// Columns: 15\n\t// Rows: 10-14\n\n    vbroadcastss    zmm10,  dword ptr [rcx]\n    vbroadcastss    zmm11,  dword ptr [rcx + 4]\n    vbroadcastss    zmm12,  dword ptr [rcx + 8]\n    vbroadcastss    zmm13,  dword ptr [rcx + 12]\n\tvbroadcastss    zmm14,  dword ptr [rcx + 16]\n\n    vmovaps         zmm15,  [rax]\n    vmovaps         zmm16,  [rax + 64]\n\n    vfmadd231ps     zmm0,   zmm15, zmm10\n    vfmadd231ps     zmm1,   zmm16, zmm10\n\n    vfmadd231ps     zmm2,   zmm15, zmm11\n    vfmadd231ps     zmm3,   zmm16, zmm11\n\n    vfmadd231ps     zmm4,   zmm15, zmm12\n    vfmadd231ps     zmm5,   zmm16, zmm12\n\n    vfmadd231ps     zmm6,   zmm15, zmm13\n    vfmadd231ps     zmm7,   zmm16, zmm13\n\n    vfmadd231ps     zmm8,   zmm15, zmm14\n    vfmadd231ps     zmm9,   zmm16, zmm14\n\n\tadd rcx, 20\n\tadd rax, 128\n"
  },
  {
    "path": "linalg/x86_64/avx512/2x6/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Tile size: 2x6\n\t// Accumulators: 0-11\n\t// Col regs: zmm14-15\n\t// Row regs: zmm12-13\n\n\tvbroadcastss\tzmm14,\tdword ptr [rcx]\n\tvmovaps\t\t\tzmm12,\t[rax]\n\tvmovaps\t\t\tzmm13,\t[rax + 64]\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 4]\n\n\tvfmadd231ps\t\tzmm0,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm1,\tzmm13, zmm14\n\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 8]\n\n\tvfmadd231ps\t\tzmm2,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm3,\tzmm13, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 12]\n\n\tvfmadd231ps\t\tzmm4,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm5,\tzmm13, zmm14\n\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 16]\n\n\tvfmadd231ps\t\tzmm6,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm7,\tzmm13, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 20]\n\n\tvfmadd231ps\t\tzmm8,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm9,\tzmm13, zmm14\n\n\tvbroadcastss\tzmm14,\tdword ptr [rcx+24]\n\n\tvfmadd231ps\t\tzmm10,\t zmm12, zmm15\n\tvfmadd231ps\t\tzmm11,\t zmm13, zmm15\n\n\t// Iteration two\n\tvmovaps\t\t\tzmm12,\t[rax + 128]\n\tvmovaps\t\t\tzmm13,\t[rax + 192]\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 24 + 4]\n\n\tvfmadd231ps\t\tzmm0,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm1,\tzmm13, zmm14\n\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 24 + 8]\n\n\tvfmadd231ps\t\tzmm2,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm3,\tzmm13, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 24 + 12]\n\n\tvfmadd231ps\t\tzmm4,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm5,\tzmm13, zmm14\n\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 24 + 16]\n\n\tvfmadd231ps\t\tzmm6,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm7,\tzmm13, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 24 + 20]\n\n\tvfmadd231ps\t\tzmm8,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm9,\tzmm13, zmm14\n\n\tvfmadd231ps\t\tzmm10,\t zmm12, zmm15\n\tvfmadd231ps\t\tzmm11,\t zmm13, zmm15\n\n\tadd rax, 256\n\tadd rcx, 48\n"
  },
  {
    "path": "linalg/x86_64/avx512/2x6/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 2x6\n\t// Accumulators: 0-11\n\t// Col regs: zmm14-15\n\t// Row regs: zmm12-13\n\n\t// Load ordered by earliest use for first 2x2 block\n\tvbroadcastss\tzmm14,\tdword ptr [rcx]\n\tvmovaps\t\t\tzmm12,\t[rax]\n\tvmovaps\t\t\tzmm13,\t[rax + 64]\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 4]\n\n\tvfmadd231ps\t\tzmm0,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm1,\tzmm13, zmm14\n\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 8]\n\n\tvfmadd231ps\t\tzmm2,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm3,\tzmm13, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 12]\n\n\tvfmadd231ps\t\tzmm4,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm5,\tzmm13, zmm14\n\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 16]\n\n\tvfmadd231ps\t\tzmm6,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm7,\tzmm13, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 20]\n\n\tvfmadd231ps\t\tzmm8,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm9,\tzmm13, zmm14\n\n\tvfmadd231ps\t\tzmm10,\t zmm12, zmm15\n\tvfmadd231ps\t\tzmm11,\t zmm13, zmm15\n\n\tadd rax, 128\n\tadd rcx, 24\n"
  },
  {
    "path": "linalg/x86_64/avx512/3x4/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Tile size: 3x4\n\t// Accumulators: 0-11\n\t// Col regs: zmm12-14\n\t// Row regs: zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax]\n\tvmovaps\t\t\tzmm13,\t[rax+64]\n\tvmovaps\t\t\tzmm14,\t[rax+128]\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 0]\n\n\tvfmadd231ps\t\tzmm0,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm1,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm2,\tzmm14, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 4]\n\n\tvfmadd231ps\t\tzmm3,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm4,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm5,\tzmm14, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 8]\n\n\tvfmadd231ps\t\tzmm6,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm7,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm8,\tzmm14, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 12]\n\n\tvfmadd231ps\t\tzmm9,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm10,\t zmm13, zmm15\n\tvfmadd231ps\t\tzmm11,\t zmm14, zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax + 192]\n\tvmovaps\t\t\tzmm13,\t[rax + 256]\n\tvmovaps\t\t\tzmm14,\t[rax + 320]\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 16]\n\n\tvfmadd231ps\t\tzmm0,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm1,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm2,\tzmm14, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 20]\n\n\tvfmadd231ps\t\tzmm3,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm4,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm5,\tzmm14, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 24]\n\n\tvfmadd231ps\t\tzmm6,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm7,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm8,\tzmm14, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 28]\n\n\tvfmadd231ps\t\tzmm9,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm10,\t zmm13, zmm15\n\tvfmadd231ps\t\tzmm11,\t zmm14, zmm15\n\n\tadd rax, 384\n\tadd rcx, 32\n"
  },
  {
    "path": "linalg/x86_64/avx512/3x4/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 3x4\n\t// Accumulators: 0-11\n\t// Col regs: zmm12-14\n\t// Row regs: zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax]\n\tvmovaps\t\t\tzmm13,\t[rax+64]\n\tvmovaps\t\t\tzmm14,\t[rax+128]\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 0]\n\n\tvfmadd231ps\t\tzmm0,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm1,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm2,\tzmm14, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 4]\n\n\tvfmadd231ps\t\tzmm3,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm4,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm5,\tzmm14, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 8]\n\n\tvfmadd231ps\t\tzmm6,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm7,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm8,\tzmm14, zmm15\n\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 12]\n\n\tvfmadd231ps\t\tzmm9,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm10,\t zmm13, zmm15\n\tvfmadd231ps\t\tzmm11,\t zmm14, zmm15\n\n\tadd rax, 192\n\tadd rcx, 16\n"
  },
  {
    "path": "linalg/x86_64/avx512/4x3/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Tile size: 4x3\n\t// Accumulators: 0-11\n\t// Col regs: zmm12\n\t// Row regs: zmm13-15\n\n\t// Load col of A\n\tvmovaps\t\t\tzmm12,\t[rax]\n\n\t// Fill 3 cols of B\n\tvbroadcastss\tzmm13,\tdword ptr [rcx + 0]\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 4]\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 8]\n\n\t// N.B. Stepping cols in inner loop\n\tvfmadd231ps\t\tzmm0,\tzmm12, zmm13\n\tvfmadd231ps\t\tzmm4,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm8,\tzmm12, zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax+64]\n\n\tvfmadd231ps\t\tzmm1,\tzmm12, zmm13\n\tvfmadd231ps\t\tzmm5,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm9,\tzmm12, zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax+128]\n\n\tvfmadd231ps\t\tzmm2,\tzmm12, zmm13\n\tvfmadd231ps\t\tzmm6,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm10,\t zmm12, zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax+192]\n\n\tvfmadd231ps\t\tzmm3,\tzmm12, zmm13\n\tvfmadd231ps\t\tzmm7,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm11,\tzmm12, zmm15\n\n\t// Load col of A, switching col!\n\tvmovaps\t\t\tzmm13,\t[rax + 256]\n\n\t// Fill 3 cols of B\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 12]\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 16]\n\tvbroadcastss\tzmm12,\tdword ptr [rcx + 20]\n\n\t// N.B. Stepping cols in inner loop\n\tvfmadd231ps\t\tzmm0,\tzmm13, zmm14\n\tvfmadd231ps\t\tzmm4,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm8,\tzmm13, zmm12\n\n\tvmovaps\t\t\tzmm13,\t[rax + 320]\n\n\tvfmadd231ps\t\tzmm1,\tzmm13, zmm14\n\tvfmadd231ps\t\tzmm5,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm9,\tzmm13, zmm12\n\n\tvmovaps\t\t\tzmm13,\t[rax + 384]\n\n\tvfmadd231ps\t\tzmm2,\tzmm13, zmm14\n\tvfmadd231ps\t\tzmm6,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm10,\t zmm13, zmm12\n\n\tvmovaps\t\t\tzmm13,\t[rax + 448]\n\n\tvfmadd231ps\t\tzmm3,\tzmm13, zmm14\n\tvfmadd231ps\t\tzmm7,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm11,\tzmm13, zmm12\n\n    add             rcx,    24\n    add             rax,    512\n"
  },
  {
    "path": "linalg/x86_64/avx512/4x3/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 4x3\n\t// Accumulators: 0-11\n\t// Col regs: zmm12\n\t// Row regs: zmm13-15\n\n\t// Load col of A\n\tvmovaps\t\t\tzmm12,\t[rax]\n\n\t// Fill 3 cols of B\n\tvbroadcastss\tzmm13,\tdword ptr [rcx + 0]\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 4]\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 8]\n\n\t// N.B. Stepping cols in inner loop\n\tvfmadd231ps\t\tzmm0,\tzmm12, zmm13\n\tvfmadd231ps\t\tzmm4,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm8,\tzmm12, zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax+64]\n\n\tvfmadd231ps\t\tzmm1,\tzmm12, zmm13\n\tvfmadd231ps\t\tzmm5,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm9,\tzmm12, zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax+128]\n\n\tvfmadd231ps\t\tzmm2,\tzmm12, zmm13\n\tvfmadd231ps\t\tzmm6,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm10,\t zmm12, zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax+192]\n\n\tvfmadd231ps\t\tzmm3,\tzmm12, zmm13\n\tvfmadd231ps\t\tzmm7,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm11,\tzmm12, zmm15\n\n    add             rcx,    12\n    add             rax,    256\n"
  },
  {
    "path": "linalg/x86_64/avx512/5x2/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Tile size: 5x2\n\t// Accumulators: 0-9\n\t// Col regs: zmm10-13\n\t// Row regs: zmm14-15\n\n\tvmovaps\t\t\tzmm10,\t[rax]\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 0]\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 4]\n\tvmovaps\t\t\tzmm11,\t[rax + 64]\n\n\t// NB stepping column-wise\n\tvfmadd231ps\t\tzmm0,\tzmm10, zmm14\n\tvfmadd231ps\t\tzmm5,\tzmm10, zmm15\n\n\tvmovaps\t\t\tzmm12,\t[rax + 128]\n\n\tvfmadd231ps\t\tzmm1,\tzmm11, zmm14\n\tvfmadd231ps\t\tzmm6,\tzmm11, zmm15\n\n\tvmovaps\t\t\tzmm13,\t[rax + 192]\n\n\tvfmadd231ps\t\tzmm2,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm7,\tzmm12, zmm15\n\n\tvmovaps\t\t\tzmm10,\t[rax + 256]\n\n\tvfmadd231ps\t\tzmm3,\tzmm13, zmm14\n\tvfmadd231ps\t\tzmm8,\tzmm13, zmm15\n\n\tvmovaps\t\t\tzmm11,\t[rax + 320]\n\n\tvfmadd231ps\t\tzmm4,\tzmm10, zmm14\n\tvfmadd231ps\t\tzmm9,\tzmm10, zmm15\n\n\tvbroadcastss\tzmm14,\tdword ptr [rcx + 8]\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 12]\n\n\tvmovaps\t\t\tzmm12,\t[rax + 384]\n\n\t// NB stepping column-wise\n\tvfmadd231ps\t\tzmm0,\tzmm11, zmm14\n\tvfmadd231ps\t\tzmm5,\tzmm11, zmm15\n\n\tvmovaps\t\t\tzmm13,\t[rax + 448]\n\n\tvfmadd231ps\t\tzmm1,\tzmm12, zmm14\n\tvfmadd231ps\t\tzmm6,\tzmm12, zmm15\n\n\tvmovaps\t\t\tzmm10,\t[rax + 512]\n\n\tvfmadd231ps\t\tzmm2,\tzmm13, zmm14\n\tvfmadd231ps\t\tzmm7,\tzmm13, zmm15\n\n\tvmovaps\t\t\tzmm11,\t[rax + 576]\n\n\tvfmadd231ps\t\tzmm3,\tzmm10, zmm14\n\tvfmadd231ps\t\tzmm8,\tzmm10, zmm15\n\n\tvfmadd231ps\t\tzmm4,\tzmm11, zmm14\n\tvfmadd231ps\t\tzmm9,\tzmm11, zmm15\n\n\tadd rax, 640\n\tadd rcx, 16\n"
  },
  {
    "path": "linalg/x86_64/avx512/5x2/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 5x2\n\t// Accumulators: 0-9\n\t// Col regs: zmm10-14\n\t// Row regs: zmm15-16\n\n\tvmovaps\t\t\tzmm10,\t[rax]\n\tvbroadcastss\tzmm15,\tdword ptr [rcx + 0]\n\tvbroadcastss\tzmm16,\tdword ptr [rcx + 4]\n\tvmovaps\t\t\tzmm11,\t[rax + 64]\n\n\t// NB stepping column-wise\n\tvfmadd231ps\t\tzmm0,\tzmm10, zmm15\n\tvfmadd231ps\t\tzmm5,\tzmm10, zmm16\n\n\tvmovaps\t\t\tzmm12,\t[rax + 128]\n\n\tvfmadd231ps\t\tzmm1,\tzmm11, zmm15\n\tvfmadd231ps\t\tzmm6,\tzmm11, zmm16\n\n\tvmovaps\t\t\tzmm13,\t[rax + 192]\n\n\tvfmadd231ps\t\tzmm2,\tzmm12, zmm15\n\tvfmadd231ps\t\tzmm7,\tzmm12, zmm16\n\n\tvmovaps\t\t\tzmm14,\t[rax + 256]\n\n\tvfmadd231ps\t\tzmm3,\tzmm13, zmm15\n\tvfmadd231ps\t\tzmm8,\tzmm13, zmm16\n\n\tvfmadd231ps\t\tzmm4,\tzmm14, zmm15\n\tvfmadd231ps\t\tzmm9,\tzmm14, zmm16\n\n\tadd rax, 320\n\tadd rcx, 8\n"
  },
  {
    "path": "linalg/x86_64/avx512/6x1/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Tile size: 6x1\n\t// Accumulators: 0-5\n\t// Col regs: 6-11\n\t// Row regs: 15\n\n\n    vbroadcastss    zmm15,  dword ptr [rcx]\n    vfmadd231ps     zmm0, zmm15, [rax]\n    vfmadd231ps     zmm1, zmm15, [rax + 64]\n    vfmadd231ps     zmm2, zmm15, [rax + 128]\n    vfmadd231ps     zmm3, zmm15, [rax + 192]\n    vfmadd231ps     zmm4, zmm15, [rax + 256]\n    vfmadd231ps     zmm5, zmm15, [rax + 320]\n\n    vbroadcastss    zmm14,  dword ptr [rcx + 4]\n\n    vfmadd231ps     zmm0, zmm14, [rax + 384]\n    vfmadd231ps     zmm1, zmm14, [rax + 448]\n    vfmadd231ps     zmm2, zmm14, [rax + 512]\n    vfmadd231ps     zmm3, zmm14, [rax + 576]\n    vfmadd231ps     zmm4, zmm14, [rax + 640]\n    vfmadd231ps     zmm5, zmm14, [rax + 704]\n\n\tadd rax, 768\n    add rcx, 8\n"
  },
  {
    "path": "linalg/x86_64/avx512/6x1/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 6x1\n\t// Accumulators: 0-5\n\t// Col regs: 6-11\n\t// Row regs: 15\n\n    vbroadcastss    zmm15,  dword ptr [rcx]\n\n\tvmovups     zmm10, [rax]\n\tvmulps     zmm10, zmm10, zmm15\n\tvaddps     zmm0, zmm0, zmm10\n    vmovups     zmm11, [rax + 64]\n\tvmulps     zmm11, zmm11, zmm15\n\tvaddps     zmm1, zmm1, zmm11\n    vmovups     zmm12, [rax + 128]\n\tvmulps     zmm12, zmm12, zmm15\n\tvaddps     zmm2, zmm2, zmm12\n    vmovups     zmm13, [rax + 192]\n\tvmulps     zmm13, zmm13, zmm15\n\tvaddps     zmm3, zmm3, zmm13\n    vmovups     zmm14, [rax + 256]\n\tvmulps     zmm14, zmm14, zmm15\n\tvaddps     zmm4, zmm4, zmm14\n    vmovups     zmm15, [rax + 320]\n\tvmulps     zmm15, zmm15, zmm15\n\tvaddps     zmm5, zmm5, zmm15\n\n\n    add rcx, 4\n\tadd rax, 384\n"
  },
  {
    "path": "linalg/x86_64/avx512/6x2/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "    // Tile size: 6x2\n\t// Accumulators: 0-9\n\t// Col regs: zmm10-13\n\t// Row regs: zmm14-15\n\n\tvmovaps         zmm12,  [rax]\n\tvbroadcastss    zmm14,  dword ptr [rcx + 0]\n    vbroadcastss    zmm15,  dword ptr [rcx + 4]\n\tvmovaps         zmm13,  [rax + 64]\n\n    vfmadd231ps     zmm0,   zmm12, zmm14\n    vfmadd231ps     zmm6,   zmm12, zmm15\n\n\tvmovaps         zmm12,  [rax + 128]\n\n    vfmadd231ps     zmm1,   zmm13, zmm14\n    vfmadd231ps     zmm7,   zmm13, zmm15\n\n\tvmovaps         zmm13,  [rax + 192]\n\n    vfmadd231ps     zmm2,   zmm12, zmm14\n    vfmadd231ps     zmm8,   zmm12, zmm15\n\n\tvmovaps         zmm12,  [rax + 256]\n\n\tvfmadd231ps     zmm3,   zmm13, zmm14\n    vfmadd231ps     zmm9,   zmm13, zmm15\n\n\tvmovaps         zmm13,  [rax + 320]\n\n\tvfmadd231ps     zmm4,   zmm12, zmm14\n    vfmadd231ps     zmm10,  zmm12, zmm15\n\n\tvmovaps         zmm12,  [rax + 384]\n\tvbroadcastss    zmm14,  dword ptr [rcx + 8]\n\n\tvfmadd231ps     zmm5,   zmm13, zmm14\n    vfmadd231ps     zmm11, \tzmm13, zmm15\n\n    vbroadcastss    zmm15,  dword ptr [rcx + 12]\n\tvmovaps         zmm13,  [rax + 448]\n\n    vfmadd231ps     zmm0,   zmm12, zmm14\n    vfmadd231ps     zmm6,   zmm12, zmm15\n\n\tvmovaps         zmm12,  [rax + 512]\n\n    vfmadd231ps     zmm1,   zmm13, zmm14\n    vfmadd231ps     zmm7,   zmm13, zmm15\n\n\tvmovaps         zmm13,  [rax + 576]\n\n    vfmadd231ps     zmm2,   zmm12, zmm14\n    vfmadd231ps     zmm8,   zmm12, zmm15\n\n\tvmovaps         zmm12,  [rax + 640]\n\n\tvfmadd231ps     zmm3,   zmm13, zmm14\n    vfmadd231ps     zmm9,   zmm13, zmm15\n\n\tvmovaps         zmm13,  [rax + 704]\n\n\tvfmadd231ps     zmm4,   zmm12, zmm14\n    vfmadd231ps     zmm10,  zmm12, zmm15\n\n\tvfmadd231ps     zmm5,   zmm13, zmm14\n    vfmadd231ps     zmm11, \tzmm13, zmm15\n\n\tadd rax, 768\n\tadd rcx, 16\n"
  },
  {
    "path": "linalg/x86_64/avx512/6x2/packed_packed_loop1/avx-512.tmpli",
    "content": "    // Tile size: 6x2\n\t// Accumulators: 0-11\n\t// Col regs: 12-13\n\t// Row regs: 14-15\n\n\tvmovaps         zmm12,  [rax]\n\tvbroadcastss    zmm14,  dword ptr [rcx + 0]\n    vbroadcastss    zmm15,  dword ptr [rcx + 4]\n\tvmovaps         zmm13,  [rax + 64]\n\n    vfmadd231ps     zmm0,   zmm12, zmm14\n    vfmadd231ps     zmm6,   zmm12, zmm15\n\n\tvmovaps         zmm12,  [rax + 128]\n\n    vfmadd231ps     zmm1,   zmm13, zmm14\n    vfmadd231ps     zmm7,   zmm13, zmm15\n\n\tvmovaps         zmm13,  [rax + 192]\n\n    vfmadd231ps     zmm2,   zmm12, zmm14\n    vfmadd231ps     zmm8,   zmm12, zmm15\n\n\tvmovaps         zmm12,  [rax + 256]\n\n\tvfmadd231ps     zmm3,   zmm13, zmm14\n    vfmadd231ps     zmm9,   zmm13, zmm15\n\n\tvmovaps         zmm13,  [rax + 320]\n\n\tvfmadd231ps     zmm4,   zmm12, zmm14\n    vfmadd231ps     zmm10,  zmm12, zmm15\n\n\tvfmadd231ps     zmm5,   zmm13, zmm14\n    vfmadd231ps     zmm11, \tzmm13, zmm15\n\n\tadd rcx, 8\n\tadd rax, 384\n"
  },
  {
    "path": "linalg/x86_64/avx512/7x1/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Tile size: 6x1\n\t// Accumulators: 0-5\n\t// Col regs: 6-11\n\t// Row regs: 15\n\n\tvbroadcastss    zmm15,  dword ptr [rcx]\n\n    vmovaps         zmm7,  [rax + 0]\n\tvmovaps         zmm8,  [rax + 64]\n\tvmovaps         zmm9,  [rax + 128]\n\tvmovaps         zmm10, [rax + 192]\n    vmovaps         zmm11, [rax + 256]\n\tvmovaps         zmm12, [rax + 320]\n\tvmovaps         zmm13, [rax + 384]\n\n    vfmadd231ps     zmm0, zmm7, zmm15\n    vfmadd231ps     zmm1, zmm8, zmm15\n    vfmadd231ps     zmm2, zmm9, zmm15\n    vfmadd231ps     zmm3, zmm10, zmm15\n    vfmadd231ps     zmm4, zmm11, zmm15\n    vfmadd231ps     zmm5, zmm12, zmm15\n\tvfmadd231ps     zmm6, zmm13, zmm15\n\n\tvbroadcastss    zmm16,  dword ptr [rcx + 4]\n\n    vmovaps         zmm7,  [rax + 448 + 0]\n\tvmovaps         zmm8,  [rax + 448 + 64]\n\tvmovaps         zmm9,  [rax + 448 + 128]\n\tvmovaps         zmm10, [rax + 448 + 192]\n    vmovaps         zmm11, [rax + 448 + 256]\n\tvmovaps         zmm12, [rax + 448 + 320]\n\tvmovaps         zmm13, [rax + 448 + 384]\n\n    vfmadd231ps     zmm0, zmm7, zmm15\n    vfmadd231ps     zmm1, zmm8, zmm15\n    vfmadd231ps     zmm2, zmm9, zmm15\n    vfmadd231ps     zmm3, zmm10, zmm15\n    vfmadd231ps     zmm4, zmm11, zmm15\n    vfmadd231ps     zmm5, zmm12, zmm15\n\tvfmadd231ps     zmm6, zmm13, zmm15\n"
  },
  {
    "path": "linalg/x86_64/avx512/7x1/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 7x1\n\t// Accumulators: 0-6\n\t// Col regs: 6-13\n\t// Row regs: 15\n    vbroadcastss    zmm15,  dword ptr [rcx]\n\n    vmovaps         zmm7,  [rax + 0]\n\tvmovaps         zmm8,  [rax + 64]\n\tvmovaps         zmm9,  [rax + 128]\n\tvmovaps         zmm10, [rax + 192]\n    vmovaps         zmm11, [rax + 256]\n\tvmovaps         zmm12, [rax + 320]\n\tvmovaps         zmm13, [rax + 384]\n\n    vfmadd231ps     zmm0, zmm7, zmm15\n    vfmadd231ps     zmm1, zmm8, zmm15\n    vfmadd231ps     zmm2, zmm9, zmm15\n    vfmadd231ps     zmm3, zmm10, zmm15\n    vfmadd231ps     zmm4, zmm11, zmm15\n    vfmadd231ps     zmm5, zmm12, zmm15\n\tvfmadd231ps     zmm6, zmm13, zmm15\n"
  },
  {
    "path": "linalg/x86_64/avx512/8x1/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Tile size: 8x1\n\t// Accumulators: 0-7\n\t// Col regs: 8-14\n\t// Row regs: 15\n\n\tvbroadcastss    zmm17,  dword ptr [rcx]\n\n\n    vfmadd231ps     zmm0, zmm17, [rax + 0]\n    vfmadd231ps     zmm1, zmm17, [rax + 64]\n    vfmadd231ps     zmm2, zmm17, [rax + 128]\n    vfmadd231ps     zmm3, zmm17, [rax + 192]\n    vfmadd231ps     zmm4, zmm17, [rax + 256]\n    vfmadd231ps     zmm5, zmm17, [rax + 320]\n    vfmadd231ps     zmm6, zmm17, [rax + 384]\n    vfmadd231ps     zmm7, zmm17, [rax + 448]\n\n\tvbroadcastss    zmm16,  dword ptr [rcx + 4]\n\n\tvfmadd231ps     zmm0, zmm16, [rax + 0 + 512]\n    vfmadd231ps     zmm1, zmm16, [rax + 64 + 512]\n    vfmadd231ps     zmm2, zmm16, [rax + 128 + 512]\n    vfmadd231ps     zmm3, zmm16, [rax + 192 + 512]\n    vfmadd231ps     zmm4, zmm16, [rax + 256 + 512]\n    vfmadd231ps     zmm5, zmm16, [rax + 320 + 512]\n    vfmadd231ps     zmm6, zmm16, [rax + 384 + 512]\n    vfmadd231ps     zmm7, zmm16, [rax + 448 + 512]\n\n    add rcx, 8\n\tadd rax, 1024\n"
  },
  {
    "path": "linalg/x86_64/avx512/8x1/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 8x1\n\t// Accumulators: 0-7\n\t// Col regs: 8-14\n\t// Row regs: 15\n\n\tvbroadcastss    zmm15,  dword ptr [rcx]\n\n    vmovaps     zmm8, [rax + 0]\n    vfmadd231ps     zmm0, zmm15, zmm8\n    vmovaps     zmm9, [rax + 64]\n    vfmadd231ps     zmm1, zmm15, zmm9\n    vmovaps     zmm10, [rax + 128]\n    vfmadd231ps     zmm2, zmm15, zmm10\n    vmovaps     zmm11, [rax + 192]\n\tvfmadd231ps     zmm3, zmm15, zmm11\n    vmovaps     zmm12, [rax + 256]\n\tvfmadd231ps     zmm4, zmm15, zmm12\n    vmovaps     zmm13, [rax + 320]\n\tvfmadd231ps     zmm5, zmm15, zmm13\n    vmovaps     zmm14, [rax + 384]\n\tvfmadd231ps     zmm6, zmm15, zmm14\n    vmovaps     zmm8, [rax + 448]\n\tvfmadd231ps     zmm7, zmm15, zmm8\n    add rcx, 4\n\tadd rax, 512\n"
  },
  {
    "path": "linalg/x86_64/avx512/8x2/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 8x2\n\t// Accumulators: 0-15\n\t// Col regs: 16-23\n\t// Row regs: 24-25\n\n    vmovaps         zmm16,  [rax + 0]\n\tvbroadcastss\tzmm24,\tdword ptr [rcx + 0]\n\tvbroadcastss\tzmm25,\tdword ptr [rcx + 4]\n\n    vfmadd231ps     zmm0, zmm16, zmm24\n    vfmadd231ps     zmm8, zmm16, zmm25\n\n    vmovaps         zmm17,  [rax + 64]\n    vfmadd231ps     zmm1, zmm17, zmm24\n    vfmadd231ps     zmm9, zmm17, zmm25\n\n    vmovaps         zmm18,  [rax + 128]\n    vfmadd231ps     zmm2, zmm18, zmm24\n    vfmadd231ps     zmm10, zmm18, zmm25\n\n    vmovaps         zmm19,  [rax + 192]\n    vfmadd231ps     zmm3, zmm19, zmm24\n    vfmadd231ps     zmm11, zmm19, zmm25\n\n    vmovaps         zmm20,  [rax + 256]\n    vfmadd231ps     zmm4, zmm20, zmm24\n    vfmadd231ps     zmm12, zmm20, zmm25\n\n    vmovaps         zmm21,  [rax + 320]\n    vfmadd231ps     zmm5, zmm21, zmm24\n    vfmadd231ps     zmm13, zmm21, zmm25\n\n    vmovaps         zmm22,  [rax + 384]\n    vfmadd231ps     zmm6, zmm22, zmm24\n    vfmadd231ps     zmm14, zmm22, zmm25\n\n    vmovaps         zmm23,  [rax + 448]\n    vfmadd231ps     zmm7, zmm23, zmm24\n    vfmadd231ps     zmm15, zmm23, zmm25\n\n\tadd rax, 512\n\tadd rcx, 8\n"
  },
  {
    "path": "linalg/x86_64/avx512/8x8/packed_packed_loop1/avx-512-unroll.tmpli",
    "content": "\t// Tile size: 1x8\n\t// Accumulators: 0-7\n\t// Col regs: 8-14\n\t// Row regs: 15\n\n\n    vmovaps         zmm15,  [rax]\n\n    vbroadcastss    zmm8, dword ptr [rcx + 0 * 4]\n    vfmadd231ps     zmm0, zmm15, zmm8\n\n    vbroadcastss    zmm9, dword ptr [rcx + 1 * 4]\n    vfmadd231ps     zmm1, zmm15, zmm9\n\n    vbroadcastss    zmm10, dword ptr [rcx + 2 * 4]\n    vfmadd231ps     zmm2, zmm15, zmm10\n\n    vbroadcastss    zmm11, dword ptr [rcx + 3 * 4]\n    vfmadd231ps     zmm3, zmm15, zmm11\n\n    vbroadcastss    zmm12, dword ptr [rcx + 4 * 4]\n    vfmadd231ps     zmm4, zmm15, zmm12\n\n    vbroadcastss    zmm13, dword ptr [rcx + 5 * 4]\n    vfmadd231ps     zmm5, zmm15, zmm13\n\n    vbroadcastss    zmm10, dword ptr [rcx + 6 * 4]\n    vfmadd231ps     zmm6, zmm15, zmm10\n\n    vbroadcastss    zmm11, dword ptr [rcx + 7 * 4]\n    vfmadd231ps     zmm7, zmm15, zmm11\n\n\n    vmovaps         zmm15,  [rax+64]\n\n    vbroadcastss    zmm8, dword ptr [rcx + 8 * 4]\n    vfmadd231ps     zmm0, zmm15, zmm8\n\n    vbroadcastss    zmm9, dword ptr [rcx + 9 * 4]\n    vfmadd231ps     zmm1, zmm15, zmm9\n\n    vbroadcastss    zmm10, dword ptr [rcx + 10 * 4]\n    vfmadd231ps     zmm2, zmm15, zmm10\n\n    vbroadcastss    zmm11, dword ptr [rcx + 11 * 4]\n    vfmadd231ps     zmm3, zmm15, zmm11\n\n    vbroadcastss    zmm12, dword ptr [rcx + 12 * 4]\n    vfmadd231ps     zmm4, zmm15, zmm12\n\n    vbroadcastss    zmm13, dword ptr [rcx + 13 * 4]\n    vfmadd231ps     zmm5, zmm15, zmm13\n\n    vbroadcastss    zmm10, dword ptr [rcx + 14 * 4]\n    vfmadd231ps     zmm6, zmm15, zmm10\n\n    vbroadcastss    zmm11, dword ptr [rcx + 15 * 4]\n    vfmadd231ps     zmm7, zmm15, zmm11\n\n\tadd rcx, 64\n\tadd rax, 128"
  },
  {
    "path": "linalg/x86_64/avx512/8x8/packed_packed_loop1/avx-512.tmpli",
    "content": "\t// Tile size: 1x8\n\t// Accumulators: 0-7\n\t// Col regs: 8-14\n\t// Row regs: 15\n\n    vmovaps         zmm15,  [rax]\n\n    vbroadcastss    zmm8, dword ptr [rcx + 0 * 4]\n    vfmadd231ps     zmm0, zmm15, zmm8\n\n    vbroadcastss    zmm9, dword ptr [rcx + 1 * 4]\n    vfmadd231ps     zmm1, zmm15, zmm9\n\n    vbroadcastss    zmm10, dword ptr [rcx + 2 * 4]\n    vfmadd231ps     zmm2, zmm15, zmm10\n\n    vbroadcastss    zmm11, dword ptr [rcx + 3 * 4]\n    vfmadd231ps     zmm3, zmm15, zmm11\n\n    vbroadcastss    zmm12, dword ptr [rcx + 4 * 4]\n    vfmadd231ps     zmm4, zmm15, zmm12\n\n    vbroadcastss    zmm13, dword ptr [rcx + 5 * 4]\n    vfmadd231ps     zmm5, zmm15, zmm13\n\n    vbroadcastss    zmm10, dword ptr [rcx + 6 * 4]\n    vfmadd231ps     zmm6, zmm15, zmm10\n\n    vbroadcastss    zmm11, dword ptr [rcx + 7 * 4]\n    vfmadd231ps     zmm7, zmm15, zmm11\n\n\tadd rcx, 32\n\tadd rax, 64\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_f32_128x1.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 128 x 1\n\n    zmm0\n    zmm1\n    ...\n    zmm7\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of ZMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" size:\"128x1\", suffix:suffix, G:G, arch:\"avx512\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{align}} 16\n{{L}}main_loop_packed_packed:\n\t{% include \"8x1/packed_packed_loop1/avx-512.tmpli\" %}\n\n    sub             rbx, 1\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{% include \"f32_scalars.tmpliq\" from:0, to:7 %}\n{% include \"f32_per_rows.tmpliq\" mr:128, from:0, to:7 %}\n{% include \"f32_per_cols.tmpliq\" mr:128, from:0, to:7 %}\n{% include \"avx512_mmm_load_tile.tmpliq\" from:0, to:7 %}\n\n{{L}}add_unicast:\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n\n    {% for row in (0..7) %}\n        vaddps zmm{{row}}, zmm{{row}}, [ r10 + {{row|times:64}} ]\n    {% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vbroadcastss    zmm14, dword ptr [rbx]\n\n{% for i in (0..7) %}\n    vmovups         zmm12,  [rax + {{i|times:64}}]\n    vfmadd231ps     zmm{{i}}, zmm12, zmm14\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n\n    cmp     rsi, 4\n    jne      {{L}}store_noncontiguous\n\n\ttest r8, 63\n\tjnz {{L}}store_unaligned\n\n\t{% for row in (0..7) %}\n        vmovaps [r8 + {{row|times:64}}], zmm{{row}}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_unaligned:\n\t{% for row in (0..7) %}\n        vmovups [r8 + {{row|times:64}}], zmm{{row}}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_noncontiguous:\n    {% for r in (0..7) %}\n        {% for quarter in (0..3) %}\n            vextractf32x4 xmm8, zmm{{r}}, {{quarter}}\n            {% for row in (0..3) %}\n                vextractps  dword ptr [r8], xmm8, {{row}}\n                add         r8, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" size:\"128x1\", suffix:suffix, G:G, L:L, arch:\"avx512\" %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_f32_16x1.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 16 x 1\n\n    zmm0\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of ZMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n\n{% include \"preamble.tmpliq\" size:\"16x1\", suffix:suffix, G:G, arch:\"avx512\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n\tcmp rbx, 8\n\tjl {{L}}main_loop_packed_packed_tail\n\n{{align}} 16\n{{L}}main_loop_packed_packed:\n\t{% include \"1x1/packed_packed_loop1/unroll-4.tmpli\" %}\n\n    sub             rbx, 4\n\tcmp rbx,        4\n\tjge              {{L}}main_loop_packed_packed\n\n\t{% for r in (1..3) %}\n\t   vaddps zmm0, zmm0, zmm{{r}}\n\t{% endfor %}\n\n    test    rbx, rbx\n    jz      {{L}}non_linear_loop\n\n{{align}} 16\n{{L}}main_loop_packed_packed_tail:\n\t{% include \"1x1/packed_packed_loop1/avx-512.tmpli\" %}\n\n\tsub             rbx, 1\n    jnz\t\t\t\t{{L}}main_loop_packed_packed_tail\n\n    jmp      {{L}}non_linear_loop\n\n{% include \"f32_scalars.tmpliq\" from:0, to:0 %}\n{% include \"f32_per_rows.tmpliq\" mr:16, from:0, to:0 %}\n{% include \"f32_per_cols.tmpliq\" mr:16, from:0, to:0 %}\n{% include \"avx512_mmm_load_tile.tmpliq\" from:0, to:0 %}\n\n{{L}}add_unicast:\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n\n\tcmp rsi, 4\n\tjne {{L}}add_unicast_generic\n\n\tvaddps zmm0, zmm0, [r10]\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_unicast_generic:\n    mov r8, [0]\n//     mov     eax,    0\n// {% for i in (0..3) %}\n//     pinsrd  xmm14, eax, {{i}}\n//     add     eax,    esi\n// {% endfor %}\n// {% for i in (0..3) %}\n//     pinsrd  xmm15, eax, {{i}}\n//     add     eax,    esi\n// {% endfor %}\n//\n//     vperm2f128      zmm14,  zmm14, zmm15,         32 // zmm14 <- xmm14::xmm15\n//\n// {% for i in (0..7) %}\n//     vpcmpeqd        zmm15,  zmm15, zmm15\n//     vgatherdps      zmm12,  [ r10 + zmm14 ], zmm15\n//\n//     vaddps          zmm{{i}},   zmm{{i}},   zmm12\n//     lea             r10, [ r10 + rsi * 8 ]\n// {% endfor %}\n//\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vbroadcastss    zmm14, dword ptr [rbx]\n\n{% for i in (0..0) %}\n    vmovups         zmm12,  [rax + {{i|times:64}}]\n    vfmadd231ps     zmm{{i}}, zmm12, zmm14\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n\n    cmp     rsi, 4\n    jne      {{L}}store_noncontiguous\n\n\ttest r8, 63\n\tjnz {{L}}store_unaligned\n\n    vmovaps [r8], zmm0\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_unaligned:\n\tvmovups [r8], zmm0\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_noncontiguous:\n    {% for quarter in (0..3) %}\n        vextractf32x4 xmm8, zmm0, {{quarter}}\n        {% for row in (0..3) %}\n            vextractps  dword ptr [r8], xmm8, {{row}}\n            add         r8, rsi\n        {% endfor %}\n    {% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" size:\"16x1\", suffix:suffix, G:G, L:L, arch:\"avx512\" %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_f32_16x12.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 16 x 12\n\n    zmm0 zmm1 ... zmm11\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of ZMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n\n{% include \"preamble.tmpliq\" size:\"16x12\", suffix:suffix, G:G, arch:\"avx512\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{align}} 16\n{{L}}main_loop_packed_packed_tail:\n\t{% include \"1x12/packed_packed_loop1/avx-512.tmpli\" %}\n\n\tsub             rbx, 1\n    jnz\t\t\t\t{{L}}main_loop_packed_packed_tail\n\n    jmp      {{L}}non_linear_loop\n\n{% include \"f32_scalars.tmpliq\" from:0, to:11 %}\n{% include \"f32_per_rows.tmpliq\" mr:16, from:0, to:11 %}\n{% include \"f32_per_cols.tmpliq\" mr:16, from:0, to:11 %}\n{% include \"avx512_mmm_load_tile.tmpliq\" from:0, to:11 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm12, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm13, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n    vperm2f128      ymm13,  ymm12, ymm13,         32 // ymm12 <- xmm12::xmm13\n    vinsertf32x8    zmm14, zmm14, ymm13, 1\n\n{% for i in (0..11) %}\n    kxnorw k1,k1,k1\n    vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n    add     r10, rbx\n    vaddps          zmm{{i}},   zmm{{i}},   zmm12\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         zmm12, zmmword ptr [rax]\n\n{% for i in (0..11) %}\n    vbroadcastss    zmm14, dword ptr [rbx + {{i|times:4}} ]\n    vfmadd231ps     zmm{{i}},   zmm12, zmm14\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    // tops of cols\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n\n    {% for quarter in (0..3) %}\n        {% for r in (0..3) %}\n            vextractf32x4 xmm{{r | plus: 12}}, zmm{{r}}, {{quarter}}\n        {% endfor %}\n        {% for row in (0..3) %}\n            {% for i in (0..3) %}\n                vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 12}}, {{row}}\n                add         r{{i | plus: 8}}, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    mov     r8,     [rdi + 8]           // c ptr\n\n    // tops of cols\n    lea     r8,     [ r8 + 4 * rbx ]\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n\n    {% for quarter in (0..3) %}\n        {% for r in (0..3) %}\n            vextractf32x4 xmm{{r | plus: 12}}, zmm{{r | plus: 4}}, {{quarter}}\n        {% endfor %}\n        {% for row in (0..3) %}\n            {% for i in (0..3) %}\n                vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 12}}, {{row}}\n                add         r{{i | plus: 8}}, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n        mov     r8,     [rdi + 8]           // c ptr\n\n    // tops of cols\n    lea     r8,     [ r8 + 8 * rbx ]\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n\n    {% for quarter in (0..3) %}\n        {% for r in (0..3) %}\n            vextractf32x4 xmm{{r | plus: 12}}, zmm{{r | plus: 8}}, {{quarter}}\n        {% endfor %}\n        {% for row in (0..3) %}\n            {% for i in (0..3) %}\n                vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 12}}, {{row}}\n                add         r{{i | plus: 8}}, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" size:\"16x12\", suffix:suffix, G:G, L:L, arch:\"avx512\" %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_f32_16x8.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 16 x 8\n\n    zmm0 zmm1 ... zmm8\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of ZMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n\n{% include \"preamble.tmpliq\" size:\"16x8\", suffix:suffix, G:G, arch:\"avx512\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n\tcmp rbx, 2\n\tjl {{L}}main_loop_packed_packed_tail\n\n{{align}} 16\n{{L}}main_loop_packed_packed:\n\t{% include \"8x8/packed_packed_loop1/avx-512-unroll.tmpli\" %}\n\n    sub             rbx, 2\n\tcmp rbx,        2\n\tjge              {{L}}main_loop_packed_packed\n\n    test    rbx, rbx\n    jz      {{L}}non_linear_loop\n\n{{align}} 16\n{{L}}main_loop_packed_packed_tail:\n\t{% include \"8x8/packed_packed_loop1/avx-512.tmpli\" %}\n\n\tsub             rbx, 1\n    jnz\t\t\t\t{{L}}main_loop_packed_packed_tail\n\n    jmp      {{L}}non_linear_loop\n\n{% include \"f32_scalars.tmpliq\" from:0, to:7 %}\n{% include \"f32_per_rows.tmpliq\" mr:16, from:0, to:7 %}\n{% include \"f32_per_cols.tmpliq\" mr:16, from:0, to:7 %}\n{% include \"avx512_mmm_load_tile.tmpliq\" from:0, to:7 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm12, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm13, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n    vperm2f128      ymm13,  ymm12, ymm13,         32 // ymm12 <- xmm12::xmm13\n    vinsertf32x8    zmm14, zmm14, ymm13, 1\n\n{% for i in (0..7) %}\n    kxnorw k1,k1,k1\n    vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n    add     r10, rbx\n    vaddps          zmm{{i}},   zmm{{i}},   zmm12\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         zmm12, zmmword ptr [rax]\n\n{% for i in (0..7) %}\n    vbroadcastss    zmm14, dword ptr [rbx + {{i|times:4}} ]\n    vfmadd231ps     zmm{{i}},   zmm12, zmm14\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    // tops of cols\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r12,    [ r8 + 4 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n    lea     r13,    [ r12 + rbx ]\n    lea     r14,    [ r12 + 2 * rbx ]\n    lea     r15,    [ r13 + 2 * rbx ]\n    \n    {% for quarter in (0..3) %}\n        {% for r in (0..7) %}\n            vextractf32x4 xmm{{r | plus: 8}}, zmm{{r}}, {{quarter}}\n        {% endfor %}\n        {% for row in (0..3) %}\n            {% for i in (0..7) %}\n                vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 8}}, {{row}}\n                add         r{{i | plus: 8}}, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" size:\"16x8\", suffix:suffix, G:G, L:L, arch:\"avx512\" %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_f32_32x5.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 32 x 5:\n\n    zmm0 zmm2 zmm4 zmm6 zmm8\n    zmm1 zmm3 zmm5 zmm7 zmm9\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" size:\"32x5\", suffix:suffix, G:G, arch:\"avx512\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n\t{% include \"2x5/packed_packed_loop1/avx-512.tmpli\" %}\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{% include \"f32_scalars.tmpliq\" from:0, to:9 %}\n{% include \"f32_per_rows.tmpliq\" mr:32, from:0, to:9 %}\n{% include \"f32_per_cols.tmpliq\" mr:32, from:0, to:9 %}\n{% include \"avx512_mmm_load_tile.tmpliq\" from:0, to:9 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm12, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm13, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n    vperm2f128      ymm13,  ymm12, ymm13,         32 // ymm12 <- xmm12::xmm13\n    vinsertf32x8    zmm14, zmm14, ymm13, 1\n\n{% for i in (0..4) %}\n    kxnorw k1,k1,k1\n    vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n    add     r10, rbx\n    vaddps          zmm{{i | times: 2}},   zmm{{i | times: 2}},   zmm12\n{% endfor %}\n\n    imul    esi,    16\n    vpbroadcastd    zmm15, esi\n\n    mov     r10,    [rdi + 8]\n    vpaddd          zmm14, zmm14, zmm15\n\n{% for i in (0..4) %}\n    kxnorw k1,k1,k1\n    vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n    add     r10, rbx\n    vaddps          zmm{{i | times: 2 | plus: 1}},   zmm{{i | times: 2 | plus: 1}},   zmm12\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         zmm12, zmmword ptr [rax]\n    vmovups         zmm13, zmmword ptr [rax+64]\n\n{% for i in (0..4) %}\n    vbroadcastss    zmm14, dword ptr [rbx + {{i|times:4}} ]\n    vfmadd231ps     zmm{{i | times: 2}}, zmm12, zmm14\n    vfmadd231ps     zmm{{i | times: 2 | plus: 1}}, zmm13, zmm14\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    // tops of cols\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n    lea     r12,    [ r10 + 2 * rbx ]\n\n    {% for word in (0..1) %}\n        {% for quarter in (0..3) %}\n            {% for r in (0..4) %}\n                vextractf32x4 xmm{{r | plus: 11}}, zmm{{r | times: 2 | plus: word}}, {{quarter}}\n            {% endfor %}\n            {% for row in (0..3) %}\n                {% for i in (0..4) %}\n                    vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 11}}, {{row}}\n                    add         r{{i | plus: 8}}, rsi\n                {% endfor %}\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}    \n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" size:\"32x5\", suffix:suffix, G:G, L:L, arch:\"avx512\" %}\n\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_f32_32x6.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 32 x 6:\n\n    zmm0 zmm2 zmm4 zmm6 zmm8 zmm10\n    zmm1 zmm3 zmm5 zmm7 zmm9 zmm11\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" size:\"32x6\", suffix:suffix, G:G, arch:\"avx512\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n\t{% include \"2x6/packed_packed_loop1/avx-512.tmpli\" %}\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{% include \"f32_scalars.tmpliq\" from:0, to:11 %}\n{% include \"f32_per_rows.tmpliq\" mr:32, from:0, to:11 %}\n{% include \"f32_per_cols.tmpliq\" mr:32, from:0, to:11 %}\n{% include \"avx512_mmm_load_tile.tmpliq\" from:0, to:11 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm12, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm13, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n    vperm2f128      ymm13,  ymm12, ymm13,         32 // ymm12 <- xmm12::xmm13\n    vinsertf32x8    zmm14, zmm14, ymm13, 1\n\n{% for i in (0..5) %}\n    kxnorw k1,k1,k1\n    vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n    add     r10, rbx\n    vaddps          zmm{{i | times: 2}},   zmm{{i | times: 2}},   zmm12\n{% endfor %}\n\n    mov     r10,    [rdi + 8]\n    imul    esi,    16\n    vpbroadcastd    zmm15, esi\n    vpaddd          zmm14, zmm14, zmm15\n\n{% for i in (0..5) %}\n    kxnorw k1,k1,k1\n    vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n    add     r10, rbx\n    vaddps          zmm{{i | times: 2 | plus: 1}},   zmm{{i | times: 2 | plus: 1}},   zmm12\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         zmm12, zmmword ptr [rax]\n    vmovups         zmm13, zmmword ptr [rax+64]\n\n{% for i in (0..5) %}\n    vbroadcastss    zmm14, dword ptr [rbx + {{i|times:4}} ]\n    vfmadd231ps     zmm{{i | times: 2}}, zmm12, zmm14\n    vfmadd231ps     zmm{{i | times: 2 | plus: 1}}, zmm13, zmm14\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    // tops of cols\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n\n    {% for word in (0..1) %}\n        {% for quarter in (0..3) %}\n            {% for r in (0..2) %}\n                vextractf32x4 xmm{{r | plus: 12}}, zmm{{r | times: 2 | plus: word}}, {{quarter}}\n            {% endfor %}\n            {% for row in (0..3) %}\n                {% for i in (0..2) %}\n                    vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 12}}, {{row}}\n                    add         r{{i | plus: 8}}, rsi\n                {% endfor %}\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}    \n\n    // tops of cols\n    mov     r8, r11\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n\n    {% for word in (0..1) %}\n        {% for quarter in (0..3) %}\n            {% for r in (0..2) %}\n                vextractf32x4 xmm{{r | plus: 12}}, zmm{{r | plus: 3 | times: 2 | plus: word}}, {{quarter}}\n            {% endfor %}\n            {% for row in (0..3) %}\n                {% for i in (0..2) %}\n                    vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 12}}, {{row}}\n                    add         r{{i | plus: 8}}, rsi\n                {% endfor %}\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" size:\"32x6\", suffix:suffix, G:G, L:L, arch:\"avx512\" %}\n\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_f32_48x4.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 48 x 4:\n\n    zmm0 zmm3 zmm6 zmm9\n    zmm1 zmm4 zmm7 zmm10\n    zmm2 zmm5 zmm8 zmm11\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" size:\"48x4\", suffix:suffix, G:G, arch:\"avx512\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n\t{% include \"3x4/packed_packed_loop1/avx-512.tmpli\" %}\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{% include \"f32_scalars.tmpliq\" from:0, to:11 %}\n{% include \"f32_per_rows.tmpliq\" mr:48, from:0, to:11 %}\n{% include \"f32_per_cols.tmpliq\" mr:48, from:0, to:11 %}\n{% include \"avx512_mmm_load_tile.tmpliq\" from:0, to:11 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm12, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm13, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n    vperm2f128      ymm13,  ymm12, ymm13,         32 // ymm12 <- xmm12::xmm13\n    vinsertf32x8    zmm14, zmm14, ymm13, 1\n\n{% for i in (0..3) %}\n    kxnorw k1,k1,k1\n    vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n    add     r10, rbx\n    vaddps          zmm{{i | times: 3}},   zmm{{i | times: 3}},   zmm12\n{% endfor %}\n\n    imul    esi,    16\n    vpbroadcastd    zmm15, esi\n\n{% for j in (1..2) %}\n    mov     r10,    [rdi + 8]\n    vpaddd          zmm14, zmm14, zmm15\n\n    {% for i in (0..3) %}\n        kxnorw k1,k1,k1\n        vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n        add     r10, rbx\n        vaddps          zmm{{i | times: 3 | plus: j}},   zmm{{i | times: 3 | plus: j}},   zmm12\n    {% endfor %}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         zmm12, zmmword ptr [rax]\n    vmovups         zmm13, zmmword ptr [rax+64]\n    vmovups         zmm15, zmmword ptr [rax+128]\n\n{% for i in (0..3) %}\n    vbroadcastss    zmm14, dword ptr [rbx + {{i|times:4}} ]\n    vfmadd231ps     zmm{{i | times: 3}}, zmm12, zmm14\n    vfmadd231ps     zmm{{i | times: 3 | plus: 1}}, zmm13, zmm14\n    vfmadd231ps     zmm{{i | times: 3 | plus: 2}}, zmm15, zmm14\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    // tops of cols\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n\n    {% for word in (0..2) %}\n        {% for quarter in (0..3) %}\n            {% for r in (0..3) %}\n                vextractf32x4 xmm{{r | plus: 12}}, zmm{{r | times: 3 | plus: word}}, {{quarter}}\n            {% endfor %}\n            {% for row in (0..3) %}\n                {% for i in (0..3) %}\n                    vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 12}}, {{row}}\n                    add         r{{i | plus: 8}}, rsi\n                {% endfor %}\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}    \n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" size:\"48x4\", suffix:suffix, G:G, L:L, arch:\"avx512\" %}\n\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_f32_64x3.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 64 x 3:\n\n    zmm0 zmm4 zmm8\n    zmm1 zmm5 zmm9\n    zmm2 zmm6 zmm10\n    zmm3 zmm7 zmm11\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" size:\"64x3\", suffix:suffix, G:G, arch:\"avx512\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n\t{% include \"4x3/packed_packed_loop1/avx-512.tmpli\" %}\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{% include \"f32_scalars.tmpliq\" from:0, to:11 %}\n{% include \"f32_per_rows.tmpliq\" mr:64, from:0, to:11 %}\n{% include \"f32_per_cols.tmpliq\" mr:64, from:0, to:11 %}\n{% include \"avx512_mmm_load_tile.tmpliq\" from:0, to:11 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm12, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm13, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n    vperm2f128      ymm13,  ymm12, ymm13,         32 // ymm12 <- xmm12::xmm13\n    vinsertf32x8    zmm14, zmm14, ymm13, 1\n\n{% for i in (0..2) %}\n    kxnorw k1,k1,k1\n    vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n    add     r10, rbx\n    vaddps          zmm{{i | times: 4}},   zmm{{i | times: 4}},   zmm12\n{% endfor %}\n\n    imul    esi,    16\n    vpbroadcastd    zmm15, esi\n\n{% for j in (1..3) %}\n    mov     r10,    [rdi + 8]\n    vpaddd          zmm14, zmm14, zmm15\n\n    {% for i in (0..2) %}\n        kxnorw k1,k1,k1\n        vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n        add     r10, rbx\n        vaddps          zmm{{i | times: 4 | plus: j}},   zmm{{i | times: 4 | plus: j}},   zmm12\n    {% endfor %}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vbroadcastss    zmm13, dword ptr [rbx]\n    vbroadcastss    zmm14, dword ptr [rbx+4]\n    vbroadcastss    zmm15, dword ptr [rbx+8]\n\n{% for i in (0..3) %}\n    vmovups         zmm12, zmmword ptr [rax+{{i | times:64}}]\n    vfmadd231ps     zmm{{i}}, zmm12, zmm13\n    vfmadd231ps     zmm{{i | plus: 4}}, zmm12, zmm14\n    vfmadd231ps     zmm{{i | plus: 8}}, zmm12, zmm15\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    // tops of cols\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n\n    {% for word in (0..3) %}\n        {% for quarter in (0..3) %}\n            {% for r in (0..2) %}\n                vextractf32x4 xmm{{r | plus: 12}}, zmm{{r | times: 4 | plus: word}}, {{quarter}}\n            {% endfor %}\n            {% for row in (0..3) %}\n                {% for i in (0..2) %}\n                    vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 12}}, {{row}}\n                    add         r{{i | plus: 8}}, rsi\n                {% endfor %}\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}    \n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" size:\"64x3\", suffix:suffix, G:G, L:L, arch:\"avx512\" %}\n\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_f32_80x2.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 80 x 2:\n\n    zmm0 zmm5\n    zmm1 zmm6\n    zmm2 zmm7\n    zmm3 zmm8\n    zmm4 zmm9\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" size:\"80x2\", suffix:suffix, G:G, arch:\"avx512\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n\t{% include \"5x2/packed_packed_loop1/avx-512.tmpli\" %}\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{% include \"f32_scalars.tmpliq\" from:0, to:9 %}\n{% include \"f32_per_rows.tmpliq\" mr:80, from:0, to:9 %}\n{% include \"f32_per_cols.tmpliq\" mr:80, from:0, to:9 %}\n{% include \"avx512_mmm_load_tile.tmpliq\" from:0, to:9 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm12, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm13, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n    vperm2f128      ymm13,  ymm12, ymm13,         32 // ymm12 <- xmm12::xmm13\n    vinsertf32x8    zmm14, zmm14, ymm13, 1\n\n{% for i in (0..1) %}\n    kxnorw k1,k1,k1\n    vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n    add     r10, rbx\n    vaddps          zmm{{i | times: 5}},   zmm{{i | times: 5}},   zmm12\n{% endfor %}\n\n    imul    esi,    16\n    vpbroadcastd    zmm15, esi\n\n{% for j in (1..4) %}\n    mov     r10,    [rdi + 8]\n    vpaddd          zmm14, zmm14, zmm15\n\n    {% for i in (0..1) %}\n        kxnorw k1,k1,k1\n        vgatherdps      zmm12{k1},  [ r10 + zmm14 ]\n        add     r10, rbx\n        vaddps          zmm{{i | times: 5 | plus: j}},   zmm{{i | times: 5 | plus: j}},   zmm12\n    {% endfor %}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vbroadcastss    zmm14, dword ptr [rbx]\n    vbroadcastss    zmm15, dword ptr [rbx+4]\n\n{% for i in (0..4) %}\n    vmovups         zmm12, zmmword ptr [rax+{{i | times:64}}]\n    vfmadd231ps     zmm{{i}}, zmm12, zmm14\n    vfmadd231ps     zmm{{i | plus: 5}}, zmm12, zmm15\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    // tops of cols\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n\n    {% for word in (0..4) %}\n        {% for quarter in (0..3) %}\n            {% for r in (0..1) %}\n                vextractf32x4 xmm{{r | plus: 12}}, zmm{{r | times: 5 | plus: word}}, {{quarter}}\n            {% endfor %}\n            {% for row in (0..3) %}\n                {% for i in (0..1) %}\n                    vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | plus: 12}}, {{row}}\n                    add         r{{i | plus: 8}}, rsi\n                {% endfor %}\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}    \n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" size:\"80x2\", suffix:suffix, G:G, L:L, arch:\"avx512\" %}\n\n"
  },
  {
    "path": "linalg/x86_64/avx512/avx512_mmm_load_tile.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}load_tile:\n    mov          r8, [rdi + 8]\n    {% for reg in (from..to) %}\n        vmovups         zmm{{reg}}, zmmword ptr [r8 + {{ reg|minus:from|times:64 }}]\n    {% endfor %}\n\n    jmp    {{L}}non_linear_loop\n"
  },
  {
    "path": "linalg/x86_64/avx512/dispatcher.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}non_linear:\n\n{{L}}non_linear_loop_enter:\n    sub     rdi,    40\n{{L}}non_linear_loop:\n    add     rdi,    40\n    mov     rax,    [rdi]\n\n    mov     r8, {{ jump_table | size }}\n    cmp     rax, 0\n    cmovl   rax, r8\n    cmp     rax, {{ jump_table | size }}\n    cmovg   rax, r8\n\n{% if msvc %}\n    lea     r8, [ offset {{L}}jmp_table ]\n{% else %}\n    lea     r8, [ rip + {{L}}jmp_table ]\n{% endif %}\n    movsxd  r9, dword ptr [ r8 + rax * 4 ]\n    lea     r8, [ r8 + r9 ]\n    jmp     r8\n\n{{L}}jmp_table:\n{% for j in jump_table %}\n    {{long}}      {{L}}{{j}}-{{L}}jmp_table\n{% endfor %}\n    {{long}}      {{L}}unsupported-{{L}}jmp_table\n\n{{L}}unsupported:\n    mov     rax,    1\n    jmp     {{L}}return\n\n\n{{L}}done:\n    mov     rax, 0\n    jmp     {{L}}return\n\n"
  },
  {
    "path": "linalg/x86_64/avx512/f32_per_cols.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_min\", op:\"vminps\", mr:mr, from:from, to:to %}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_max\", op:\"vmaxps\", mr:mr, from:from, to:to %}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_add\", op:\"vaddps\", mr:mr, from:from, to:to %}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_mul\", op:\"vmulps\", mr:mr, from:from, to:to %}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_sub\", op:\"vsubps\", from:from, to:to %}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_sub_flipped\", op:\"vsubps\", from:from, to:to, flipped: true %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/f32_per_rows.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_min\", op:\"vminps\", mr:mr, from:from, to:to %}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_max\", op:\"vmaxps\", mr:mr, from:from, to:to %}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_add\", op:\"vaddps\", mr:mr, from:from, to:to %}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_mul\", op:\"vmulps\", mr:mr, from:from, to:to %}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_sub\", op:\"vsubps\", from:from, to:to %}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_sub_flipped\", op:\"vsubps\", from:from, to:to, flipped: true %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/f32_scalars.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_min\", op:\"vminps\", from:from, to:to %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_max\", op:\"vmaxps\", from:from, to:to %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_add\", op:\"vaddps\", from:from, to:to %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_mul\", op:\"vmulps\", from:from, to:to %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_sub\", op:\"vsubps\", from:from, to:to %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_sub_flipped\", op:\"vsubps\", from:from, to:to, flipped: true %}\n\n{{L}}leaky_relu:\n    // can only use zmm12 to zmm15\n    // ymm15 <- alpha\n    vbroadcastss    zmm15, dword ptr [rdi + 8]\n    // ymm14 <- all zero\n    vpxorq          zmm14, zmm14, zmm14\n\n    {% for reg in (from..to) %}\n        vcmpps      k1, zmm{{reg}}, zmm14, 1 // 1 means LT\n        // ymm12 <- alpha * x if < 0\n        vmulps      zmm{{reg}} {k1}, zmm{{reg}}, zmm15\n    {% endfor %}\n    // select muled of orginal\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}q_scale:\n{{L}}q_shl:\n{{L}}q_shr:\n    jmp {{L}}unsupported\n"
  },
  {
    "path": "linalg/x86_64/avx512/i32_per_cols.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_min\", op:\"vpminsd\", mr:mr, from:from, to:to%}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_max\", op:\"vpmaxsd\", mr:mr, from:from, to:to%}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_add\", op:\"vpaddd\", mr:mr, from:from, to:to%}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_mul\", op:\"vpmulld\", mr:mr, from:from, to:to%}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_sub\", op:\"vpsubd\", from:from, to:to%}\n{% include \"zmm_per_col.tmpliq\" label:\"per_col_sub_flipped\", op:\"vpsubd\", from:from, to:to, flipped: true%}\n"
  },
  {
    "path": "linalg/x86_64/avx512/i32_per_rows.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_min\", op:\"vpminsd\", mr:mr, from:from, to:to%}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_max\", op:\"vpmaxsd\", mr:mr, from:from, to:to%}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_add\", op:\"vpaddd\", mr:mr, from:from, to:to%}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_mul\", op:\"vpmulld\", mr:mr, from:from, to:to%}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_sub\", op:\"vpsubd\", from:from, to:to%}\n{% include \"zmm_per_row.tmpliq\" label:\"per_row_sub_flipped\", op:\"vpsubd\", from:from, to:to, flipped: true%}\n"
  },
  {
    "path": "linalg/x86_64/avx512/i32_scalars.tmpliq",
    "content": "// vim: set syntax=asm :\n{% unless arch %}\n   {% assign arch = \"ymm\" %}\n{% endunless %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_min\", op:\"vpminsd\", from:from, to:to, arch:arch %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_max\", op:\"vpmaxsd\", from:from, to:to, arch:arch %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_mul\", op:\"vpmulld\", from:from, to:to, arch:arch %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_add\", op:\"vpaddd\", from:from, to:to, arch:arch %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_sub\", op:\"vpsubd\", from:from, to:to, arch:arch %}\n{% include \"zmm_scalar.tmpliq\" label:\"scalar_sub_flipped\", op:\"vpsubd\", from:from, to:to, flipped: true, arch:arch %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/postamble.tmpliq",
    "content": "{{L}}return:\n    ldmxcsr     [rsp + 4]\n    add         rsp, 8\n\n    pop r15\n    pop r14\n    pop r13\n    pop r12\n    pop rbx\n\n{% if family == \"windows\" %}\n    pop rsi\n    pop rdi\n\n    vmovaps xmm15, [rsp+16*9]\n    vmovaps xmm14, [rsp+16*8]\n    vmovaps xmm13, [rsp+16*7]\n    vmovaps xmm12, [rsp+16*6]\n    vmovaps xmm11, [rsp+16*5]\n    vmovaps xmm10, [rsp+16*4]\n    vmovaps xmm9, [rsp+16*3]\n    vmovaps xmm8, [rsp+16*2]\n    vmovaps xmm7, [rsp+16*1]\n    vmovaps xmm6, [rsp]\n{% endif %}\n\n    mov rsp, rbp\n    pop rbp\n    ret\n\n{% if msvc %}\n{{arch}}_mmm_f32_{{size}}_{{suffix}} endp\n_text ends\nend\n\n{% else %}\n.cfi_endproc\n{% endif %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/preamble.tmpliq",
    "content": "{% if msvc %}\n\n_text segment\n{{arch}}_mmm_f32_{{size}}_{{suffix}} proc\n\n{% else %}\n\n.intel_syntax noprefix\n.text\n.p2align 5\n.globl {{G}}{{arch}}_mmm_f32_{{size}}_{{suffix}}\n{{G}}{{arch}}_mmm_f32_{{size}}_{{suffix}}:\n.cfi_startproc\n\n{% endif %}\n\n    push        rbp\n    mov         rbp, rsp\n\n{% if family == \"windows\" %}\n// https://www.agner.org/optimize/calling_conventions.pdf xmm6-15 are not scratch\n// https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers\n    and rsp,-16\n    lea rsp,[rsp-160]\n    vmovaps [rsp], xmm6\n    vmovaps [rsp+16*1],xmm7\n    vmovaps [rsp+16*2],xmm8\n    vmovaps [rsp+16*3],xmm9\n    vmovaps [rsp+16*4],xmm10\n    vmovaps [rsp+16*5],xmm11\n    vmovaps [rsp+16*6],xmm12\n    vmovaps [rsp+16*7],xmm13\n    vmovaps [rsp+16*8],xmm14\n    vmovaps [rsp+16*9],xmm15\n\n    push        rdi\n    push        rsi\n\n    mov         rdi, rcx\n\n{% endif %}\n\n    push        rbx\n    push        r12\n    push        r13\n    push        r14\n    push        r15\n\n    sub         rsp, 8\n\n{% if family == \"unix\" %}\n.cfi_def_cfa_offset 64\n{% endif %}\n    stmxcsr     [rsp + 4]\n{% if msvc %}\n    mov         rax, 1FC0h\n{% else %}\n    mov         rax, 0x1FC0\n{% endif %}\n    mov         [rsp], eax\n    ldmxcsr     [rsp]\n\n{% include \"dispatcher.tmpliq\" %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/sigmoid_f32.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n\n// TODO[TSolberg] : Not validated.\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of ZMM0-15 and ZMM0-15\n    return: rax (+rdx)\n\n{% endcomment %}\n\n{% if msvc %}\n\n_text segment\navx512_sigmoid_f32_{{suffix}} proc\n\n{% else %}\n\n.intel_syntax noprefix\n.text\n.p2align 5\n.globl {{G}}avx512_sigmoid_f32_{{suffix}}\n{{G}}avx512_sigmoid_f32_{{suffix}}:\n.cfi_startproc\n{% endif %}\n\n    push        rbp\n    mov         rbp, rsp\n\n\n{% if family == \"windows\" %}\n// https://www.agner.org/optimize/calling_conventions.pdf xmm6-15 are not scratch\n// https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers\n    and rsp,-16\n    lea rsp,[rsp-160]\n    vmovaps [rsp], xmm6\n    vmovaps [rsp+16*1],xmm7\n    vmovaps [rsp+16*2],xmm8\n    vmovaps [rsp+16*3],xmm9\n    vmovaps [rsp+16*4],xmm10\n    vmovaps [rsp+16*5],xmm11\n    vmovaps [rsp+16*6],xmm12\n    vmovaps [rsp+16*7],xmm13\n    vmovaps [rsp+16*8],xmm14\n    vmovaps [rsp+16*9],xmm15\n\n    // move around arguments to mimick SysV rdi,rsi passing\n    push        rdi\n    push        rsi\n    mov         rdi, rcx\n    mov         rsi, rdx\n\n{% endif %}\n\n    push        rbx\n    push        r12\n    push        r13\n    push        r14\n    push        r15\n\n    sub         rsp, 8\n\n{% if family == \"unix\" %}\n// FIXME\n// .cfi_def_cfa_offset 64\n{% endif %}\n\n    stmxcsr     [rsp + 4]\n{% if msvc %}\n    mov         rax, 1FC0h\n{% else %}\n    mov         rax, 0x1FC0\n{% endif %}\n    mov         [rsp], eax\n    ldmxcsr     [rsp]\n// ----------------------------------------------------------------------\n\n{%capture offset%}{% if msvc %} offset {%else%} rip + {%endif%} {%endcapture%}\n\n    cmp     rsi, 0\n    je      {{L}}done\n\n    cmp     rsi, 32\n    jl      {{L}}loop_1\n\n{{L}}loop_4:\n\n    vmovaps         zmm4, [rdi]\n    vmovaps         zmm5, [rdi + 64]\n    vmovaps         zmm6, [rdi + 128]\n    vmovaps         zmm7, [rdi + 192]\n\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_low]\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_high]\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_9]\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_7]\n\n    vmaxps          zmm4, zmm4, zmm0\n    vmaxps          zmm5, zmm5, zmm0\n    vmaxps          zmm6, zmm6, zmm0\n    vmaxps          zmm7, zmm7, zmm0\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_5]\n\n    vminps          zmm4, zmm4, zmm1\n    vminps          zmm5, zmm5, zmm1\n    vminps          zmm6, zmm6, zmm1\n    vminps          zmm7, zmm7, zmm1        // zmm4..7 <- x\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_alpha_3]\n\n    vmulps          zmm8, zmm4, zmm4\n    vmulps          zmm9, zmm5, zmm5\n    vmulps          zmm10, zmm6, zmm6\n    vmulps          zmm11, zmm7, zmm7        // zmm8..11 <- x^2\n\n    vmovaps         zmm12, zmm2\n    vmovaps         zmm13, zmm2\n    vmovaps         zmm14, zmm2\n    vmovaps         zmm15, zmm2\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_1]\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vfmadd132ps     zmm13, zmm3, zmm9\n    vfmadd132ps     zmm14, zmm3, zmm10\n    vfmadd132ps     zmm15, zmm3, zmm11\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_10]\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vfmadd132ps     zmm13, zmm0, zmm9\n    vfmadd132ps     zmm14, zmm0, zmm10\n    vfmadd132ps     zmm15, zmm0, zmm11\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_8]\n    vfmadd132ps     zmm12, zmm1, zmm8\n    vfmadd132ps     zmm13, zmm1, zmm9\n    vfmadd132ps     zmm14, zmm1, zmm10\n    vfmadd132ps     zmm15, zmm1, zmm11\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_beta_6]\n    vfmadd132ps     zmm12, zmm2, zmm8\n    vfmadd132ps     zmm13, zmm2, zmm9\n    vfmadd132ps     zmm14, zmm2, zmm10\n    vfmadd132ps     zmm15, zmm2, zmm11\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_beta_4]\n    vmulps          zmm4, zmm4, zmm12\n    vmulps          zmm5, zmm5, zmm13\n    vmulps          zmm6, zmm6, zmm14\n    vmulps          zmm7, zmm7, zmm15   // zmm4..7 <- num\n\n    vmovaps         zmm12, zmm3\n    vmovaps         zmm13, zmm3\n    vmovaps         zmm14, zmm3\n    vmovaps         zmm15, zmm3\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_2]\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vfmadd132ps     zmm13, zmm0, zmm9\n    vfmadd132ps     zmm14, zmm0, zmm10\n    vfmadd132ps     zmm15, zmm0, zmm11\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_0]\n    vfmadd132ps     zmm12, zmm1, zmm8\n    vfmadd132ps     zmm13, zmm1, zmm9\n    vfmadd132ps     zmm14, zmm1, zmm10\n    vfmadd132ps     zmm15, zmm1, zmm11\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_half]\n    vfmadd132ps     zmm12, zmm2, zmm8\n    vfmadd132ps     zmm13, zmm2, zmm9\n    vfmadd132ps     zmm14, zmm2, zmm10\n    vfmadd132ps     zmm15, zmm2, zmm11\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vfmadd132ps     zmm13, zmm3, zmm9\n    vfmadd132ps     zmm14, zmm3, zmm10\n    vfmadd132ps     zmm15, zmm3, zmm11\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vfmadd132ps     zmm13, zmm0, zmm9\n    vfmadd132ps     zmm14, zmm0, zmm10\n    vfmadd132ps     zmm15, zmm0, zmm11  // zmm12..14 <- denum\n\n    vdivps          zmm4, zmm4, zmm12\n    vdivps          zmm5, zmm5, zmm13\n    vdivps          zmm6, zmm6, zmm14\n    vdivps          zmm7, zmm7, zmm15\n    vaddps          zmm4, zmm4, zmm1\n    vaddps          zmm5, zmm5, zmm1\n    vaddps          zmm6, zmm6, zmm1\n    vaddps          zmm7, zmm7, zmm1\n\n    vmovaps [rdi], zmm4\n    vmovaps [rdi + 64], zmm5\n    vmovaps [rdi + 128], zmm6\n    vmovaps [rdi + 192], zmm7\n\n    add     rdi, 256\n    sub     rsi, 32\n    cmp     rsi, 32\n    jg      {{L}}loop_4\n\n    cmp     rsi, 0\n    je      {{L}}done\n\n{{L}}loop_1:\n    vmovaps         zmm4, [rdi]\n\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_low]\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_high]\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_9]\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_7]\n\n    vmaxps          zmm4, zmm4, zmm0\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_5]\n\n    vminps          zmm4, zmm4, zmm1        // zmm4 <- x\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_alpha_3]\n\n    vmulps          zmm8, zmm4, zmm4        // zmm8 <- x^2\n\n    vmovaps         zmm12, zmm2\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_1]\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_10]\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_8]\n    vfmadd132ps     zmm12, zmm1, zmm8\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_beta_6]\n    vfmadd132ps     zmm12, zmm2, zmm8\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_beta_4]\n    vmulps          zmm4, zmm4, zmm12\n\n    vmovaps         zmm12, zmm3\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_2]\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_0]\n    vfmadd132ps     zmm12, zmm1, zmm8\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_half]\n    vfmadd132ps     zmm12, zmm2, zmm8\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vfmadd132ps     zmm12, zmm0, zmm8\n\n    vdivps          zmm4, zmm4, zmm12\n    vaddps          zmm4, zmm4, zmm1\n\n    vmovaps [rdi], zmm4\n    add     rdi, 32\n    sub     rsi, 8\n    jnz     {{L}}loop_1\n\n{{L}}done:\n\n// ----------------------------------------------------------------------\n\n    ldmxcsr     [rsp + 4]\n\n    add         rsp, 8\n\n    pop r15\n    pop r14\n    pop r13\n    pop r12\n    pop rbx\n\n{% if family == \"windows\" %}\n    pop rsi\n    pop rdi\n\n    vmovaps xmm15, [rsp+16*9]\n    vmovaps xmm14, [rsp+16*8]\n    vmovaps xmm13, [rsp+16*7]\n    vmovaps xmm12, [rsp+16*6]\n    vmovaps xmm11, [rsp+16*5]\n    vmovaps xmm10, [rsp+16*4]\n    vmovaps xmm9, [rsp+16*3]\n    vmovaps xmm8, [rsp+16*2]\n    vmovaps xmm7, [rsp+16*1]\n    vmovaps xmm6, [rsp]\n{% endif %}\n\n    mov rsp, rbp\n    pop rbp\n    ret\n\n{%capture float%}{% if msvc %} real4 {%else%} .float {%endif%}{%endcapture%}\n\n{{L}}coeffs_num_low:\n    {{float}} -18.0                    // low\n{{L}}coeffs_num_high:\n    {{float}} 18.0                     // high\n\n{{L}}coeffs_num_alpha_9:\n    {{float}} 4.37031012579801e-11     // alpha_9\n{{L}}coeffs_num_alpha_7:\n    {{float}} 1.15627324459942e-07     // alpha_7\n{{L}}coeffs_num_alpha_5:\n    {{float}} 6.08574864600143e-05     // alpha_5\n{{L}}coeffs_num_alpha_3:\n    {{float}} 8.51377133304701e-03     // alpha_3\n{{L}}coeffs_num_alpha_1:\n    {{float}} 2.48287947061529e-01     // alpha_1\n\n{{L}}coeffs_num_beta_10:\n    {{float}} 6.10247389755681e-13\n{{L}}coeffs_num_beta_8:\n    {{float}} 5.76102136993427e-09\n{{L}}coeffs_num_beta_6:\n    {{float}} 6.29106785017040e-06     // beta_6\n{{L}}coeffs_num_beta_4:\n    {{float}} 1.70198817374094e-03     // beta_4\n{{L}}coeffs_num_beta_2:\n    {{float}} 1.16817656904453e-01     // beta_2\n{{L}}coeffs_num_beta_0:\n    {{float}} 9.93151921023180e-01     // beta_0\n\n{{L}}coeffs_num_half:\n    {{float}} 0.5\n\n{% if msvc %}\navx512_sigmoid_f32_{{suffix}} endp\n_text ends\nend\n{% else %}\n.cfi_endproc\n{% endif %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/tanh_f32.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n// TODO[TSolberg] : Not validated.\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of ZMM0-15 and ZMM0-15\n    return: rax (+rdx)\n\n{% endcomment %}\n\n{% if msvc %}\n\n_text segment\navx512_tanh_f32_{{suffix}} proc\n\n{% else %}\n\n.intel_syntax noprefix\n.text\n.p2align 5\n.globl {{G}}avx512_tanh_f32_{{suffix}}\n{{G}}avx512_tanh_f32_{{suffix}}:\n.cfi_startproc\n{% endif %}\n\n    push        rbp\n    mov         rbp, rsp\n\n\n{% if family == \"windows\" %}\n// https://www.agner.org/optimize/calling_conventions.pdf xmm6-15 are not scratch\n// https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers\n    and rsp,-16\n    lea rsp,[rsp-160]\n    vmovaps [rsp], xmm6\n    vmovaps [rsp+16*1],xmm7\n    vmovaps [rsp+16*2],xmm8\n    vmovaps [rsp+16*3],xmm9\n    vmovaps [rsp+16*4],xmm10\n    vmovaps [rsp+16*5],xmm11\n    vmovaps [rsp+16*6],xmm12\n    vmovaps [rsp+16*7],xmm13\n    vmovaps [rsp+16*8],xmm14\n    vmovaps [rsp+16*9],xmm15\n\n    // move around arguments to mimick SysV rdi,rsi passing\n    push        rdi\n    push        rsi\n    mov         rdi, rcx\n    mov         rsi, rdx\n\n{% endif %}\n\n    push        rbx\n    push        r12\n    push        r13\n    push        r14\n    push        r15\n\n    sub         rsp, 8\n\n{% if family == \"unix\" %}\n// FIXME\n// .cfi_def_cfa_offset 64\n{% endif %}\n\n    stmxcsr     [rsp + 4]\n{% if msvc %}\n    mov         rax, 1FC0h\n{% else %}\n    mov         rax, 0x1FC0\n{% endif %}\n    mov         [rsp], eax\n    ldmxcsr     [rsp]\n// ----------------------------------------------------------------------\n\n{%capture offset%}{% if msvc %} offset {%else%} rip + {%endif%} {%endcapture%}\n\n    cmp     rsi, 0\n    je      {{L}}done\n\n    cmp     rsi, 32\n    jl      {{L}}loop_1\n\n{{L}}loop_4:\n\n    vmovaps         zmm4, [rdi]\n    vmovaps         zmm5, [rdi + 64]\n    vmovaps         zmm6, [rdi + 128]\n    vmovaps         zmm7, [rdi + 192]\n\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_low]\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_high]\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_13]\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_11]\n\n    vmaxps          zmm4, zmm4, zmm0\n    vmaxps          zmm5, zmm5, zmm0\n    vmaxps          zmm6, zmm6, zmm0\n    vmaxps          zmm7, zmm7, zmm0\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_9]\n\n    vminps          zmm4, zmm4, zmm1\n    vminps          zmm5, zmm5, zmm1\n    vminps          zmm6, zmm6, zmm1\n    vminps          zmm7, zmm7, zmm1        // zmm4..7 <- x\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_alpha_7]\n\n    vmulps          zmm8, zmm4, zmm4\n    vmulps          zmm9, zmm5, zmm5\n    vmulps          zmm10, zmm6, zmm6\n    vmulps          zmm11, zmm7, zmm7        // zmm8..11 <- x^2\n\n    vmovaps         zmm12, zmm2\n    vmovaps         zmm13, zmm2\n    vmovaps         zmm14, zmm2\n    vmovaps         zmm15, zmm2\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_5]\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vfmadd132ps     zmm13, zmm3, zmm9\n    vfmadd132ps     zmm14, zmm3, zmm10\n    vfmadd132ps     zmm15, zmm3, zmm11\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_3]\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vfmadd132ps     zmm13, zmm0, zmm9\n    vfmadd132ps     zmm14, zmm0, zmm10\n    vfmadd132ps     zmm15, zmm0, zmm11\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_1]\n    vfmadd132ps     zmm12, zmm1, zmm8\n    vfmadd132ps     zmm13, zmm1, zmm9\n    vfmadd132ps     zmm14, zmm1, zmm10\n    vfmadd132ps     zmm15, zmm1, zmm11\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_beta_6]\n    vfmadd132ps     zmm12, zmm2, zmm8\n    vfmadd132ps     zmm13, zmm2, zmm9\n    vfmadd132ps     zmm14, zmm2, zmm10\n    vfmadd132ps     zmm15, zmm2, zmm11\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_beta_4]\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vfmadd132ps     zmm13, zmm3, zmm9\n    vfmadd132ps     zmm14, zmm3, zmm10\n    vfmadd132ps     zmm15, zmm3, zmm11\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_2]\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vfmadd132ps     zmm13, zmm0, zmm9\n    vfmadd132ps     zmm14, zmm0, zmm10\n    vfmadd132ps     zmm15, zmm0, zmm11\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_0]\n    vmulps          zmm4, zmm4, zmm12\n    vmulps          zmm5, zmm5, zmm13\n    vmulps          zmm6, zmm6, zmm14\n    vmulps          zmm7, zmm7, zmm15   // zmm4..7 <- num\n\n    vmovaps         zmm12, zmm1\n    vmovaps         zmm13, zmm1\n    vmovaps         zmm14, zmm1\n    vmovaps         zmm15, zmm1\n    vfmadd132ps     zmm12, zmm2, zmm8\n    vfmadd132ps     zmm13, zmm2, zmm9\n    vfmadd132ps     zmm14, zmm2, zmm10\n    vfmadd132ps     zmm15, zmm2, zmm11\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vfmadd132ps     zmm13, zmm3, zmm9\n    vfmadd132ps     zmm14, zmm3, zmm10\n    vfmadd132ps     zmm15, zmm3, zmm11\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vfmadd132ps     zmm13, zmm0, zmm9\n    vfmadd132ps     zmm14, zmm0, zmm10\n    vfmadd132ps     zmm15, zmm0, zmm11  // zmm12..14 <- denum\n\n    vdivps          zmm4, zmm4, zmm12\n    vdivps          zmm5, zmm5, zmm13\n    vdivps          zmm6, zmm6, zmm14\n    vdivps          zmm7, zmm7, zmm15\n\n    vmovaps [rdi], zmm4\n    vmovaps [rdi + 64], zmm5\n    vmovaps [rdi + 128], zmm6\n    vmovaps [rdi + 192], zmm7\n\n    add     rdi, 256\n    sub     rsi, 32\n    cmp     rsi, 32\n    jg      {{L}}loop_4\n\n    cmp     rsi, 0\n    je      {{L}}done\n\n{{L}}loop_1:\n    vmovaps         zmm4, [rdi]\n\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_low]\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_high]\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_13]\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_11]\n\n    vmaxps          zmm4, zmm4, zmm0\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_9]\n\n    vminps          zmm4, zmm4, zmm1        // zmm4 <- x\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_alpha_7]\n\n    vmulps          zmm8, zmm4, zmm4        // zmm8 <- x^2\n\n    vmovaps         zmm12, zmm2\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_5]\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_3]\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_1]\n    vfmadd132ps     zmm12, zmm1, zmm8\n    vbroadcastss    zmm1, dword ptr [{{offset}} {{L}}coeffs_num_beta_6]\n    vfmadd132ps     zmm12, zmm2, zmm8\n    vbroadcastss    zmm2, dword ptr [{{offset}} {{L}}coeffs_num_beta_4]\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vbroadcastss    zmm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_2]\n    vfmadd132ps     zmm12, zmm0, zmm8\n    vbroadcastss    zmm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_0]\n    vmulps          zmm4, zmm4, zmm12\n\n    vmovaps         zmm12, zmm1\n    vfmadd132ps     zmm12, zmm2, zmm8\n    vfmadd132ps     zmm12, zmm3, zmm8\n    vfmadd132ps     zmm12, zmm0, zmm8\n\n    vdivps          zmm4, zmm4, zmm12\n\n    vmovaps [rdi], zmm4\n    add     rdi, 32\n    sub     rsi, 8\n    jnz     {{L}}loop_1\n\n{{L}}done:\n\n// ----------------------------------------------------------------------\n\n    ldmxcsr     [rsp + 4]\n\n    add         rsp, 8\n\n    pop r15\n    pop r14\n    pop r13\n    pop r12\n    pop rbx\n\n{% if family == \"windows\" %}\n    pop rsi\n    pop rdi\n\n    vmovaps xmm15, [rsp+16*9]\n    vmovaps xmm14, [rsp+16*8]\n    vmovaps xmm13, [rsp+16*7]\n    vmovaps xmm12, [rsp+16*6]\n    vmovaps xmm11, [rsp+16*5]\n    vmovaps xmm10, [rsp+16*4]\n    vmovaps xmm9, [rsp+16*3]\n    vmovaps xmm8, [rsp+16*2]\n    vmovaps xmm7, [rsp+16*1]\n    vmovaps xmm6, [rsp]\n{% endif %}\n\n    mov rsp, rbp\n    pop rbp\n    ret\n\n{%capture float%}{% if msvc %} real4 {%else%} .float {%endif%}{%endcapture%}\n\n{{L}}coeffs_num_low:\n    {{float}} -9.0                     // low\n{{L}}coeffs_num_high:\n    {{float}} 9.0                      // high\n\n{{L}}coeffs_num_alpha_13:\n    {{float}} -2.76076847742355e-16    // alpha_13\n{{L}}coeffs_num_alpha_11:\n    {{float}} 2.00018790482477e-13     // alpha_11\n{{L}}coeffs_num_alpha_9:\n    {{float}} -8.60467152213735e-11    // alpha_9\n{{L}}coeffs_num_alpha_7:\n    {{float}} 5.12229709037114e-08     // alpha_7\n{{L}}coeffs_num_alpha_5:\n    {{float}} 1.48572235717979e-05     // alpha_5\n{{L}}coeffs_num_alpha_3:\n    {{float}} 6.37261928875436e-04     // alpha_3\n{{L}}coeffs_num_alpha_1:\n    {{float}} 4.89352455891786e-03     // alpha_1\n\n{{L}}coeffs_num_beta_6:\n    {{float}} 1.19825839466702e-06     // beta_6\n{{L}}coeffs_num_beta_4:\n    {{float}} 1.18534705686654e-04     // beta_4\n{{L}}coeffs_num_beta_2:\n    {{float}} 2.26843463243900e-03     // beta_2\n{{L}}coeffs_num_beta_0:\n    {{float}} 4.89352518554385e-03     // beta_0\n\n{% if msvc %}\navx512_tanh_f32_{{suffix}} endp\n_text ends\nend\n{% else %}\n.cfi_endproc\n{% endif %}\n"
  },
  {
    "path": "linalg/x86_64/avx512/zmm_per_col.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}{{label}}:\n    mov             rax, [ rdi + 8 ]\n\n{% capture mr_over_16 %}{{ mr | divided_by: 16}}{%endcapture%}\n{% capture mr_over_16_min_1 %}{{ mr | divided_by: 16 | minus: 1}}{%endcapture%}\n\n{%capture tmp%}{{to | plus: 1 }}{%endcapture%}\n\n{%capture cols%}{{to | plus: 1| minus:from| divided_by:mr_over_16}}{%endcapture%}\n{%capture cols_min_1%}{{to | plus: 1| minus:from| divided_by:mr_over_16|minus:1}}{%endcapture%}\n// {{to|minus:from|plus:1}} cols:{{cols}}\n\n{% for right in (0..cols_min_1) %}\n    vbroadcastss    zmm{{tmp}}, dword ptr [ rax ]\n    add             rax, 4\n\n    {% for down in (0..mr_over_16_min_1) %}\n        {%capture acc%}{{mr_over_16|times:right|plus:from|plus:down}}{%endcapture%}\n        {% if flipped %}\n            {{op}} zmm{{acc}}, zmm{{acc}}, zmm{{tmp}}\n        {% else %}\n            {{op}} zmm{{acc}}, zmm{{tmp}}, zmm{{acc}}\n        {% endif %}\n    {% endfor %}\n{% endfor %}\n\n    jmp {{L}}non_linear_loop\n"
  },
  {
    "path": "linalg/x86_64/avx512/zmm_per_row.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}{{label}}:\n    mov             rax, [ rdi + 8 ]\n\n{% capture mr_over_16 %}{{ mr | divided_by: 16}}{%endcapture%}\n{% capture mr_over_16_min_1 %}{{ mr | divided_by: 16 | minus: 1}}{%endcapture%}\n\n{% for ix in (0..mr_over_16_min_1) %}\n    vmovups         zmm{{to | plus: 1 | plus: ix}},  [rax + {{ix | times: 64}}]\n{% endfor %}\n\n{% if flipped %}\n    {% for acc in (from..to) %}\n        {{op}} zmm{{acc}}, zmm{{acc}}, zmm{{ acc | modulo: mr_over_16 | plus: to | plus: 1 }}\n    {% endfor %}\n{% else %}\n    {% for acc in (from..to) %}\n        {{op}} zmm{{acc}}, zmm{{ acc | modulo: mr_over_16 | plus: to | plus: 1 }}, zmm{{acc}}\n    {% endfor %}\n{% endif %}\n\n    jmp {{L}}non_linear_loop\n"
  },
  {
    "path": "linalg/x86_64/avx512/zmm_scalar.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}{{label}}:\n    vbroadcastss    zmm12, dword ptr [rdi + 8]\n    {% if flipped %}\n        {% for reg in (from..to) %}\n            {{op}}          zmm{{reg}}, zmm{{reg}}, zmm12\n        {% endfor %}\n    {% else %}\n        {% for reg in (from..to) %}\n            {{op}}          zmm{{reg}}, zmm12, zmm{{reg}}\n        {% endfor %}\n    {% endif %}\n\n    jmp    {{L}}non_linear_loop\n"
  },
  {
    "path": "linalg/x86_64/fma/10x1/packed_packed_loop1/avx-unroll.tmpli",
    "content": "\t// Accumulators: 0-7\n\t// Columns: 14-15\n\t// Rows: 8-13\n\n\tvbroadcastss    ymm15,  dword ptr [rcx]\n\n\tvmovaps         ymm10, [rax + 0]\n\tvmovaps         ymm11, [rax + 32]\n    vmovaps         ymm12, [rax + 64]\n\tvmovaps         ymm13, [rax + 96]\n    vmovaps         ymm14, [rax + 128]\n\n    vfmadd231ps     ymm0, ymm10, ymm15\n    vfmadd231ps     ymm1, ymm11, ymm15\n    vfmadd231ps     ymm2, ymm12, ymm15\n    vfmadd231ps     ymm3, ymm13, ymm15\n    vfmadd231ps     ymm4, ymm14, ymm15\n\n\tvmovaps         ymm10, [rax + 160]\n    vmovaps         ymm11, [rax + 192]\n\tvmovaps         ymm12, [rax + 224]\n\tvmovaps         ymm13, [rax + 256]\n\tvmovaps         ymm14, [rax + 288]\n\n    vfmadd231ps     ymm5, ymm10, ymm15\n    vfmadd231ps     ymm6, ymm11, ymm15\n    vfmadd231ps     ymm7, ymm12, ymm15\n    vfmadd231ps     ymm8, ymm13, ymm15\n    vfmadd231ps     ymm9, ymm14, ymm15\n\n\tvbroadcastss    ymm15,  dword ptr [rcx + 4]\n\n\tvmovaps         ymm10, [rax + 320]\n\tvmovaps         ymm11, [rax + 352]\n    vmovaps         ymm12, [rax + 384]\n\tvmovaps         ymm13, [rax + 416]\n    vmovaps         ymm14, [rax + 448]\n\n\tvfmadd231ps     ymm0, ymm10, ymm15\n    vfmadd231ps     ymm1, ymm11, ymm15\n    vfmadd231ps     ymm2, ymm12, ymm15\n    vfmadd231ps     ymm3, ymm13, ymm15\n    vfmadd231ps     ymm4, ymm14, ymm15\n\n\tvmovaps         ymm10, [rax + 480]\n    vmovaps         ymm11, [rax + 512]\n\tvmovaps         ymm12, [rax + 544]\n\tvmovaps         ymm13, [rax + 576]\n\tvmovaps         ymm14, [rax + 608]\n\n    vfmadd231ps     ymm5, ymm10, ymm15\n    vfmadd231ps     ymm6, ymm11, ymm15\n    vfmadd231ps     ymm7, ymm12, ymm15\n    vfmadd231ps     ymm8, ymm13, ymm15\n    vfmadd231ps     ymm9, ymm14, ymm15\n\n    add rcx, 8\n\tadd rax, 640\n"
  },
  {
    "path": "linalg/x86_64/fma/10x1/packed_packed_loop1/avx.tmpli",
    "content": "\t// Tile size: 10x1\n\t// Accumulators: 0-9\n\t// Col regs: 10-14\n\t// Row regs: 15\n\n\tvbroadcastss    ymm15,  dword ptr [rcx]\n\n\tvmovaps         ymm10, [rax + 0]\n\tvmovaps         ymm11, [rax + 32]\n    vmovaps         ymm12, [rax + 64]\n\tvmovaps         ymm13, [rax + 96]\n    vmovaps         ymm14, [rax + 128]\n\n    vfmadd231ps     ymm0, ymm10, ymm15\n    vfmadd231ps     ymm1, ymm11, ymm15\n    vfmadd231ps     ymm2, ymm12, ymm15\n    vfmadd231ps     ymm3, ymm13, ymm15\n    vfmadd231ps     ymm4, ymm14, ymm15\n\n\tvmovaps         ymm10, [rax + 160]\n    vmovaps         ymm11, [rax + 192]\n\tvmovaps         ymm12, [rax + 224]\n\tvmovaps         ymm13, [rax + 256]\n\tvmovaps         ymm14, [rax + 288]\n\n    vfmadd231ps     ymm5, ymm10, ymm15\n    vfmadd231ps     ymm6, ymm11, ymm15\n    vfmadd231ps     ymm7, ymm12, ymm15\n    vfmadd231ps     ymm8, ymm13, ymm15\n    vfmadd231ps     ymm9, ymm14, ymm15\n\n    add rcx, 4\n\tadd rax, 320\n"
  },
  {
    "path": "linalg/x86_64/fma/2x5/packed_packed_loop1/avx-unroll.tmpli",
    "content": "\t// Accumulators: 0-9\n\t// Columns: 14-15\n\t// Rows: 10-13\n    vbroadcastss    ymm10,  dword ptr [rcx]\n    vbroadcastss    ymm11,  dword ptr [rcx + 4]\n    vbroadcastss    ymm12,  dword ptr [rcx + 8]\n    vbroadcastss    ymm13,  dword ptr [rcx + 12]\n\n    vmovaps         ymm14,  [rax]\n    vmovaps         ymm15,  [rax + 32]\n\n    vfmadd231ps     ymm0,   ymm14, ymm10\n    vfmadd231ps     ymm1,   ymm15, ymm10\n\n    vfmadd231ps     ymm2,   ymm14, ymm11\n    vfmadd231ps     ymm3,   ymm15, ymm11\n\n    vbroadcastss    ymm11,  dword ptr [rcx + 16]\n\n    vfmadd231ps     ymm4,   ymm14, ymm12\n    vfmadd231ps     ymm5,   ymm15, ymm12\n\n    vfmadd231ps     ymm6,   ymm14, ymm13\n    vfmadd231ps     ymm7,   ymm15, ymm13\n\n    vfmadd231ps     ymm8,   ymm14, ymm11\n    vfmadd231ps     ymm9,   ymm15, ymm11\n\n    vbroadcastss    ymm10,  dword ptr [rcx + 20]\n    vbroadcastss    ymm11,  dword ptr [rcx + 24]\n    vbroadcastss    ymm12,  dword ptr [rcx + 28]\n    vbroadcastss    ymm13,  dword ptr [rcx + 32]\n\n    vmovaps         ymm14,  [rax + 64]\n    vmovaps         ymm15,  [rax + 96]\n\n    vfmadd231ps     ymm0,   ymm14, ymm10\n    vfmadd231ps     ymm1,   ymm15, ymm10\n\n    vfmadd231ps     ymm2,   ymm14, ymm11\n    vfmadd231ps     ymm3,   ymm15, ymm11\n\n    vbroadcastss    ymm11,  dword ptr [rcx + 36]\n\n    vfmadd231ps     ymm4,   ymm14, ymm12\n    vfmadd231ps     ymm5,   ymm15, ymm12\n\n    vfmadd231ps     ymm6,   ymm14, ymm13\n    vfmadd231ps     ymm7,   ymm15, ymm13\n\n    vfmadd231ps     ymm8,   ymm14, ymm11\n    vfmadd231ps     ymm9,   ymm15, ymm11\n"
  },
  {
    "path": "linalg/x86_64/fma/2x5/packed_packed_loop1/avx.tmpli",
    "content": "\t// Accumulators: 0-9\n\t// Columns: 14-15\n\t// Rows: 10-13\n    vbroadcastss    ymm10,  dword ptr [rcx]\n    vbroadcastss    ymm11,  dword ptr [rcx + 4]\n    vbroadcastss    ymm12,  dword ptr [rcx + 8]\n    vbroadcastss    ymm13,  dword ptr [rcx + 12]\n\n    vmovaps         ymm14,  [rax]\n    vmovaps         ymm15,  [rax + 32]\n\n    vfmadd231ps     ymm0,   ymm14, ymm10\n    vfmadd231ps     ymm1,   ymm15, ymm10\n\n    vfmadd231ps     ymm2,   ymm14, ymm11\n    vfmadd231ps     ymm3,   ymm15, ymm11\n\n\t// Use register 11 as it's \"middle\" use, leading to a decent\n\t// trade-off between required use next iteration and when it has\n\t// to be used this iteration.\n    vbroadcastss    ymm11,  dword ptr [rcx + 16]\n\n    vfmadd231ps     ymm4,   ymm14, ymm12\n    vfmadd231ps     ymm5,   ymm15, ymm12\n\n    vfmadd231ps     ymm6,   ymm14, ymm13\n    vfmadd231ps     ymm7,   ymm15, ymm13\n\n    vfmadd231ps     ymm8,   ymm14, ymm11\n    vfmadd231ps     ymm9,   ymm15, ymm11\n"
  },
  {
    "path": "linalg/x86_64/fma/2x6/packed_packed_loop1/original-unroll.tmpli",
    "content": "\t// Tile size: 2x6\n\t// Accumulators: 0-11\n\t// Col regs: ymm14-15\n\t// Row regs: ymm12-13\n\n\tvbroadcastss\tymm14,\tdword ptr [rcx]\n\tvmovaps\t\t\tymm12,\t[rax]\n\tvmovaps\t\t\tymm13,\t[rax + 32]\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 4]\n\n\tvfmadd231ps\t\tymm0,\tymm12, ymm14\n\tvfmadd231ps\t\tymm1,\tymm13, ymm14\n\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 8]\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm15\n\tvfmadd231ps\t\tymm3,\tymm13, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 12]\n\n\tvfmadd231ps\t\tymm4,\tymm12, ymm14\n\tvfmadd231ps\t\tymm5,\tymm13, ymm14\n\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 16]\n\n\tvfmadd231ps\t\tymm6,\tymm12, ymm15\n\tvfmadd231ps\t\tymm7,\tymm13, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 20]\n\n\tvfmadd231ps\t\tymm8,\tymm12, ymm14\n\tvfmadd231ps\t\tymm9,\tymm13, ymm14\n\n\tvbroadcastss\tymm14,\tdword ptr [rcx+24]\n\n\tvfmadd231ps\t\tymm10,\t ymm12, ymm15\n\tvfmadd231ps\t\tymm11,\t ymm13, ymm15\n\n\t// Iteration two\n\tvmovaps\t\t\tymm12,\t[rax + 64]\n\tvmovaps\t\t\tymm13,\t[rax + 96]\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 24 + 4]\n\n\tvfmadd231ps\t\tymm0,\tymm12, ymm14\n\tvfmadd231ps\t\tymm1,\tymm13, ymm14\n\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 24 + 8]\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm15\n\tvfmadd231ps\t\tymm3,\tymm13, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 24 + 12]\n\n\tvfmadd231ps\t\tymm4,\tymm12, ymm14\n\tvfmadd231ps\t\tymm5,\tymm13, ymm14\n\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 24 + 16]\n\n\tvfmadd231ps\t\tymm6,\tymm12, ymm15\n\tvfmadd231ps\t\tymm7,\tymm13, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 24 + 20]\n\n\tvfmadd231ps\t\tymm8,\tymm12, ymm14\n\tvfmadd231ps\t\tymm9,\tymm13, ymm14\n\n\tvfmadd231ps\t\tymm10,\t ymm12, ymm15\n\tvfmadd231ps\t\tymm11,\t ymm13, ymm15\n\n\tadd rax, 128\n\tadd rcx, 48\n"
  },
  {
    "path": "linalg/x86_64/fma/2x6/packed_packed_loop1/original.tmpli",
    "content": "\t// Tile size: 2x6\n\t// Accumulators: 0-11\n\t// Col regs: ymm14-15\n\t// Row regs: ymm12-13\n\n\t// Load ordered by earliest use for first 2x2 block\n\tvbroadcastss\tymm14,\tdword ptr [rcx]\n\tvmovaps\t\t\tymm12,\t[rax]\n\tvmovaps\t\t\tymm13,\t[rax + 32]\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 4]\n\n\tvfmadd231ps\t\tymm0,\tymm12, ymm14\n\tvfmadd231ps\t\tymm1,\tymm13, ymm14\n\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 8]\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm15\n\tvfmadd231ps\t\tymm3,\tymm13, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 12]\n\n\tvfmadd231ps\t\tymm4,\tymm12, ymm14\n\tvfmadd231ps\t\tymm5,\tymm13, ymm14\n\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 16]\n\n\tvfmadd231ps\t\tymm6,\tymm12, ymm15\n\tvfmadd231ps\t\tymm7,\tymm13, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 20]\n\n\tvfmadd231ps\t\tymm8,\tymm12, ymm14\n\tvfmadd231ps\t\tymm9,\tymm13, ymm14\n\n\tvfmadd231ps\t\tymm10,\t ymm12, ymm15\n\tvfmadd231ps\t\tymm11,\t ymm13, ymm15\n\n\tadd rax, 64\n\tadd rcx, 24\n"
  },
  {
    "path": "linalg/x86_64/fma/3x4/packed_packed_loop1/avx-unroll.tmpli",
    "content": "\t// Tile size: 3x4\n\t// Accumulators: 0-11\n\t// Col regs: ymm12-14\n\t// Row regs: ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax]\n\tvmovaps\t\t\tymm13,\t[rax+32]\n\tvmovaps\t\t\tymm14,\t[rax+64]\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 0]\n\n\tvfmadd231ps\t\tymm0,\tymm12, ymm15\n\tvfmadd231ps\t\tymm1,\tymm13, ymm15\n\tvfmadd231ps\t\tymm2,\tymm14, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 4]\n\n\tvfmadd231ps\t\tymm3,\tymm12, ymm15\n\tvfmadd231ps\t\tymm4,\tymm13, ymm15\n\tvfmadd231ps\t\tymm5,\tymm14, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 8]\n\n\tvfmadd231ps\t\tymm6,\tymm12, ymm15\n\tvfmadd231ps\t\tymm7,\tymm13, ymm15\n\tvfmadd231ps\t\tymm8,\tymm14, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 12]\n\n\tvfmadd231ps\t\tymm9,\tymm12, ymm15\n\tvfmadd231ps\t\tymm10,\t ymm13, ymm15\n\tvfmadd231ps\t\tymm11,\t ymm14, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax + 96]\n\tvmovaps\t\t\tymm13,\t[rax + 128]\n\tvmovaps\t\t\tymm14,\t[rax + 160]\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 16]\n\n\tvfmadd231ps\t\tymm0,\tymm12, ymm15\n\tvfmadd231ps\t\tymm1,\tymm13, ymm15\n\tvfmadd231ps\t\tymm2,\tymm14, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 20]\n\n\tvfmadd231ps\t\tymm3,\tymm12, ymm15\n\tvfmadd231ps\t\tymm4,\tymm13, ymm15\n\tvfmadd231ps\t\tymm5,\tymm14, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 24]\n\n\tvfmadd231ps\t\tymm6,\tymm12, ymm15\n\tvfmadd231ps\t\tymm7,\tymm13, ymm15\n\tvfmadd231ps\t\tymm8,\tymm14, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 28]\n\n\tvfmadd231ps\t\tymm9,\tymm12, ymm15\n\tvfmadd231ps\t\tymm10,\t ymm13, ymm15\n\tvfmadd231ps\t\tymm11,\t ymm14, ymm15\n"
  },
  {
    "path": "linalg/x86_64/fma/3x4/packed_packed_loop1/avx.tmpli",
    "content": "\t// Tile size: 3x4\n\t// Accumulators: 0-11\n\t// Col regs: ymm12-14\n\t// Row regs: ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax]\n\tvmovaps\t\t\tymm13,\t[rax+32]\n\tvmovaps\t\t\tymm14,\t[rax+64]\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 0]\n\n\tvfmadd231ps\t\tymm0,\tymm12, ymm15\n\tvfmadd231ps\t\tymm1,\tymm13, ymm15\n\tvfmadd231ps\t\tymm2,\tymm14, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 4]\n\n\tvfmadd231ps\t\tymm3,\tymm12, ymm15\n\tvfmadd231ps\t\tymm4,\tymm13, ymm15\n\tvfmadd231ps\t\tymm5,\tymm14, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 8]\n\n\tvfmadd231ps\t\tymm6,\tymm12, ymm15\n\tvfmadd231ps\t\tymm7,\tymm13, ymm15\n\tvfmadd231ps\t\tymm8,\tymm14, ymm15\n\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 12]\n\n\tvfmadd231ps\t\tymm9,\tymm12, ymm15\n\tvfmadd231ps\t\tymm10,\t ymm13, ymm15\n\tvfmadd231ps\t\tymm11,\t ymm14, ymm15\n"
  },
  {
    "path": "linalg/x86_64/fma/4x3/packed_packed_loop1/avx-unroll.tmpli",
    "content": "\t// Tile size: 4x3\n\t// Accumulators: 0-11\n\t// Col regs: ymm12\n\t// Row regs: ymm13-15\n\n\t// Load col of A\n\tvmovaps\t\t\tymm12,\t[rax]\n\n\t// Fill 3 cols of B\n\tvbroadcastss\tymm13,\tdword ptr [rcx + 0]\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 4]\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 8]\n\n\t// N.B. Stepping cols in inner loop\n\tvfmadd231ps\t\tymm0,\tymm12, ymm13\n\tvfmadd231ps\t\tymm4,\tymm12, ymm14\n\tvfmadd231ps\t\tymm8,\tymm12, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax+32]\n\n\tvfmadd231ps\t\tymm1,\tymm12, ymm13\n\tvfmadd231ps\t\tymm5,\tymm12, ymm14\n\tvfmadd231ps\t\tymm9,\tymm12, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax+64]\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm13\n\tvfmadd231ps\t\tymm6,\tymm12, ymm14\n\tvfmadd231ps\t\tymm10,\t ymm12, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax+96]\n\n\tvfmadd231ps\t\tymm3,\tymm12, ymm13\n\tvfmadd231ps\t\tymm7,\tymm12, ymm14\n\tvfmadd231ps\t\tymm11,\tymm12, ymm15\n\n\t// Load col of A, switching col!\n\tvmovaps\t\t\tymm13,\t[rax + 128]\n\n\t// Fill 3 cols of B\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 12]\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 16]\n\tvbroadcastss\tymm12,\tdword ptr [rcx + 20]\n\n\t// N.B. Stepping cols in inner loop\n\tvfmadd231ps\t\tymm0,\tymm13, ymm14\n\tvfmadd231ps\t\tymm4,\tymm13, ymm15\n\tvfmadd231ps\t\tymm8,\tymm13, ymm12\n\n\tvmovaps\t\t\tymm13,\t[rax + 160]\n\n\tvfmadd231ps\t\tymm1,\tymm13, ymm14\n\tvfmadd231ps\t\tymm5,\tymm13, ymm15\n\tvfmadd231ps\t\tymm9,\tymm13, ymm12\n\n\tvmovaps\t\t\tymm13,\t[rax + 192]\n\n\tvfmadd231ps\t\tymm2,\tymm13, ymm14\n\tvfmadd231ps\t\tymm6,\tymm13, ymm15\n\tvfmadd231ps\t\tymm10,\t ymm13, ymm12\n\n\tvmovaps\t\t\tymm13,\t[rax + 224]\n\n\tvfmadd231ps\t\tymm3,\tymm13, ymm14\n\tvfmadd231ps\t\tymm7,\tymm13, ymm15\n\tvfmadd231ps\t\tymm11,\tymm13, ymm12\n\n    add             rcx,    24\n    add             rax,    256\n"
  },
  {
    "path": "linalg/x86_64/fma/4x3/packed_packed_loop1/avx.tmpli",
    "content": "\t// Tile size: 4x3\n\t// Accumulators: 0-11\n\t// Col regs: ymm12\n\t// Row regs: ymm13-15\n\n\t// Load col of A\n\tvmovaps\t\t\tymm12,\t[rax]\n\n\t// Fill 3 cols of B\n\tvbroadcastss\tymm13,\tdword ptr [rcx + 0]\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 4]\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 8]\n\n\t// N.B. Stepping cols in inner loop\n\tvfmadd231ps\t\tymm0,\tymm12, ymm13\n\tvfmadd231ps\t\tymm4,\tymm12, ymm14\n\tvfmadd231ps\t\tymm8,\tymm12, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax+32]\n\n\tvfmadd231ps\t\tymm1,\tymm12, ymm13\n\tvfmadd231ps\t\tymm5,\tymm12, ymm14\n\tvfmadd231ps\t\tymm9,\tymm12, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax+64]\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm13\n\tvfmadd231ps\t\tymm6,\tymm12, ymm14\n\tvfmadd231ps\t\tymm10,\t ymm12, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax+96]\n\n\tvfmadd231ps\t\tymm3,\tymm12, ymm13\n\tvfmadd231ps\t\tymm7,\tymm12, ymm14\n\tvfmadd231ps\t\tymm11,\tymm12, ymm15\n\n    add             rcx,    12\n    add             rax,    128\n"
  },
  {
    "path": "linalg/x86_64/fma/5x2/packed_packed_loop1/avx-unroll.tmpli",
    "content": "\t// Tile size: 5x2\n\t// Accumulators: 0-9\n\t// Col regs: ymm10-13\n\t// Row regs: ymm14-15\n\n\tvmovaps\t\t\tymm10,\t[rax]\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 0]\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 4]\n\tvmovaps\t\t\tymm11,\t[rax + 32]\n\n\t// NB stepping column-wise\n\tvfmadd231ps\t\tymm0,\tymm10, ymm14\n\tvfmadd231ps\t\tymm5,\tymm10, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax + 64]\n\n\tvfmadd231ps\t\tymm1,\tymm11, ymm14\n\tvfmadd231ps\t\tymm6,\tymm11, ymm15\n\n\tvmovaps\t\t\tymm13,\t[rax + 96]\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm14\n\tvfmadd231ps\t\tymm7,\tymm12, ymm15\n\n\tvmovaps\t\t\tymm10,\t[rax + 128]\n\n\tvfmadd231ps\t\tymm3,\tymm13, ymm14\n\tvfmadd231ps\t\tymm8,\tymm13, ymm15\n\n\tvmovaps\t\t\tymm11,\t[rax + 160]\n\n\tvfmadd231ps\t\tymm4,\tymm10, ymm14\n\tvfmadd231ps\t\tymm9,\tymm10, ymm15\n\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 8]\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 12]\n\n\tvmovaps\t\t\tymm12,\t[rax + 192]\n\n\t// NB stepping column-wise\n\tvfmadd231ps\t\tymm0,\tymm11, ymm14\n\tvfmadd231ps\t\tymm5,\tymm11, ymm15\n\n\tvmovaps\t\t\tymm13,\t[rax + 224]\n\n\tvfmadd231ps\t\tymm1,\tymm12, ymm14\n\tvfmadd231ps\t\tymm6,\tymm12, ymm15\n\n\tvmovaps\t\t\tymm10,\t[rax + 256]\n\n\tvfmadd231ps\t\tymm2,\tymm13, ymm14\n\tvfmadd231ps\t\tymm7,\tymm13, ymm15\n\n\tvmovaps\t\t\tymm11,\t[rax + 288]\n\n\tvfmadd231ps\t\tymm3,\tymm10, ymm14\n\tvfmadd231ps\t\tymm8,\tymm10, ymm15\n\n\tvfmadd231ps\t\tymm4,\tymm11, ymm14\n\tvfmadd231ps\t\tymm9,\tymm11, ymm15\n\n\tadd rax, 320\n\tadd rcx, 16\n"
  },
  {
    "path": "linalg/x86_64/fma/5x2/packed_packed_loop1/avx.tmpli",
    "content": "\t// Tile size: 5x2\n\t// Accumulators: 0-9\n\t// Col regs: ymm10-13\n\t// Row regs: ymm14-15\n\n\tvmovaps\t\t\tymm10,\t[rax]\n\tvbroadcastss\tymm14,\tdword ptr [rcx + 0]\n\tvbroadcastss\tymm15,\tdword ptr [rcx + 4]\n\tvmovaps\t\t\tymm11,\t[rax + 32]\n\n\t// NB stepping column-wise\n\tvfmadd231ps\t\tymm0,\tymm10, ymm14\n\tvfmadd231ps\t\tymm5,\tymm10, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax + 64]\n\n\tvfmadd231ps\t\tymm1,\tymm11, ymm14\n\tvfmadd231ps\t\tymm6,\tymm11, ymm15\n\n\tvmovaps\t\t\tymm13,\t[rax + 96]\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm14\n\tvfmadd231ps\t\tymm7,\tymm12, ymm15\n\n\tvmovaps\t\t\tymm11,\t[rax + 128]\n\n\tvfmadd231ps\t\tymm3,\tymm13, ymm14\n\tvfmadd231ps\t\tymm8,\tymm13, ymm15\n\n\tvfmadd231ps\t\tymm4,\tymm11, ymm14\n\tvfmadd231ps\t\tymm9,\tymm11, ymm15\n\n\tadd rax, 160\n\tadd rcx, 8\n"
  },
  {
    "path": "linalg/x86_64/fma/6x1/packed_packed_loop1/avx-unroll.tmpli",
    "content": "\t// Tile size: 6x1\n\t// Accumulators: 0-5\n\t// Col regs: 6-11\n\t// Row regs: 15\n\n\n    vbroadcastss    ymm15,  dword ptr [rcx]\n    vfmadd231ps     ymm0, ymm15, [rax]\n    vfmadd231ps     ymm1, ymm15, [rax + 32]\n    vfmadd231ps     ymm2, ymm15, [rax + 64]\n    vfmadd231ps     ymm3, ymm15, [rax + 96]\n    vfmadd231ps     ymm4, ymm15, [rax + 128]\n    vfmadd231ps     ymm5, ymm15, [rax + 160]\n\n    vbroadcastss    ymm14,  dword ptr [rcx + 4]\n\n    vfmadd231ps     ymm0, ymm14, [rax + 192]\n    vfmadd231ps     ymm1, ymm14, [rax + 224]\n    vfmadd231ps     ymm2, ymm14, [rax + 256]\n    vfmadd231ps     ymm3, ymm14, [rax + 288]\n    vfmadd231ps     ymm4, ymm14, [rax + 320]\n    vfmadd231ps     ymm5, ymm14, [rax + 352]\n\n\tadd rax, 384\n    add rcx, 8\n"
  },
  {
    "path": "linalg/x86_64/fma/6x1/packed_packed_loop1/avx.tmpli",
    "content": "\t// Tile size: 6x1\n\t// Accumulators: 0-5\n\t// Col regs: 6-11\n\t// Row regs: 15\n\n    vbroadcastss    ymm15,  dword ptr [rcx]\n\n\tvmovups     ymm10, [rax]\n\tvmulps     ymm10, ymm10, ymm15\n\tvaddps     ymm0, ymm0, ymm10\n    vmovups     ymm11, [rax + 32]\n\tvmulps     ymm11, ymm11, ymm15\n\tvaddps     ymm1, ymm1, ymm11\n    vmovups     ymm12, [rax + 64]\n\tvmulps     ymm12, ymm12, ymm15\n\tvaddps     ymm2, ymm2, ymm12\n    vmovups     ymm13, [rax + 96]\n\tvmulps     ymm13, ymm13, ymm15\n\tvaddps     ymm3, ymm3, ymm13\n    vmovups     ymm14, [rax + 128]\n\tvmulps     ymm14, ymm14, ymm15\n\tvaddps     ymm4, ymm4, ymm14\n    vmovups     ymm15, [rax + 160]\n\tvmulps     ymm15, ymm15, ymm15\n\tvaddps     ymm5, ymm5, ymm15\n\n\n    add rcx, 4\n\tadd rax, 192\n"
  },
  {
    "path": "linalg/x86_64/fma/6x2/packed_packed_loop1/avx-unroll.tmpli",
    "content": "    // Tile size: 6x2\n\t// Accumulators: 0-9\n\t// Col regs: ymm10-13\n\t// Row regs: ymm14-15\n\n\tvmovaps         ymm12,  [rax]\n\tvbroadcastss    ymm14,  dword ptr [rcx + 0]\n    vbroadcastss    ymm15,  dword ptr [rcx + 4]\n\tvmovaps         ymm13,  [rax + 32]\n\n    vfmadd231ps     ymm0,   ymm12, ymm14\n    vfmadd231ps     ymm6,   ymm12, ymm15\n\n\tvmovaps         ymm12,  [rax + 64]\n\n    vfmadd231ps     ymm1,   ymm13, ymm14\n    vfmadd231ps     ymm7,   ymm13, ymm15\n\n\tvmovaps         ymm13,  [rax + 96]\n\n    vfmadd231ps     ymm2,   ymm12, ymm14\n    vfmadd231ps     ymm8,   ymm12, ymm15\n\n\tvmovaps         ymm12,  [rax + 128]\n\n\tvfmadd231ps     ymm3,   ymm13, ymm14\n    vfmadd231ps     ymm9,   ymm13, ymm15\n\n\tvmovaps         ymm13,  [rax + 160]\n\n\tvfmadd231ps     ymm4,   ymm12, ymm14\n    vfmadd231ps     ymm10,  ymm12, ymm15\n\n\tvmovaps         ymm12,  [rax + 192]\n\tvbroadcastss    ymm14,  dword ptr [rcx + 8]\n\n\tvfmadd231ps     ymm5,   ymm13, ymm14\n    vfmadd231ps     ymm11, \tymm13, ymm15\n\n    vbroadcastss    ymm15,  dword ptr [rcx + 12]\n\tvmovaps         ymm13,  [rax + 224]\n\n    vfmadd231ps     ymm0,   ymm12, ymm14\n    vfmadd231ps     ymm6,   ymm12, ymm15\n\n\tvmovaps         ymm12,  [rax + 256]\n\n    vfmadd231ps     ymm1,   ymm13, ymm14\n    vfmadd231ps     ymm7,   ymm13, ymm15\n\n\tvmovaps         ymm13,  [rax + 288]\n\n    vfmadd231ps     ymm2,   ymm12, ymm14\n    vfmadd231ps     ymm8,   ymm12, ymm15\n\n\tvmovaps         ymm12,  [rax + 320]\n\n\tvfmadd231ps     ymm3,   ymm13, ymm14\n    vfmadd231ps     ymm9,   ymm13, ymm15\n\n\tvmovaps         ymm13,  [rax + 352]\n\n\tvfmadd231ps     ymm4,   ymm12, ymm14\n    vfmadd231ps     ymm10,  ymm12, ymm15\n\n\tvfmadd231ps     ymm5,   ymm13, ymm14\n    vfmadd231ps     ymm11, \tymm13, ymm15\n\n\tadd rax, 384\n\tadd rcx, 16\n"
  },
  {
    "path": "linalg/x86_64/fma/6x2/packed_packed_loop1/avx.tmpli",
    "content": "    // Tile size: 6x2\n\t// Accumulators: 0-11\n\t// Col regs: 12-13\n\t// Row regs: 14-15\n\n\tvmovaps         ymm12,  [rax]\n\tvbroadcastss    ymm14,  dword ptr [rcx + 0]\n    vbroadcastss    ymm15,  dword ptr [rcx + 4]\n\tvmovaps         ymm13,  [rax + 32]\n\n    vfmadd231ps     ymm0,   ymm12, ymm14\n    vfmadd231ps     ymm6,   ymm12, ymm15\n\n\tvmovaps         ymm12,  [rax + 64]\n\n    vfmadd231ps     ymm1,   ymm13, ymm14\n    vfmadd231ps     ymm7,   ymm13, ymm15\n\n\tvmovaps         ymm13,  [rax + 96]\n\n    vfmadd231ps     ymm2,   ymm12, ymm14\n    vfmadd231ps     ymm8,   ymm12, ymm15\n\n\tvmovaps         ymm12,  [rax + 128]\n\n\tvfmadd231ps     ymm3,   ymm13, ymm14\n    vfmadd231ps     ymm9,   ymm13, ymm15\n\n\tvmovaps         ymm13,  [rax + 160]\n\n\tvfmadd231ps     ymm4,   ymm12, ymm14\n    vfmadd231ps     ymm10,  ymm12, ymm15\n\n\tvfmadd231ps     ymm5,   ymm13, ymm14\n    vfmadd231ps     ymm11, \tymm13, ymm15\n\n\tadd rcx, 8\n\tadd rax, 192\n"
  },
  {
    "path": "linalg/x86_64/fma/7x1/packed_packed_loop1/avx-unroll.tmpli",
    "content": "\t// Tile size: 6x1\n\t// Accumulators: 0-5\n\t// Col regs: 6-11\n\t// Row regs: 15\n    vbroadcastss    ymm15,  dword ptr [rcx]\n\n    vmovaps         ymm6, [rax + 0]\n\tvmovaps         ymm7, [rax + 32]\n\tvmovaps         ymm8, [rax + 64]\n\tvmovaps         ymm9, [rax + 96]\n\n    vfmadd231ps     ymm0, ymm6, ymm15\n    vmovaps         ymm10, [rax + 128]\n\n\tvfmadd231ps     ymm1, ymm7, ymm15\n\tvmovaps         ymm11, [rax + 160]\n    vfmadd231ps     ymm2, ymm8, ymm15\n\tvbroadcastss    ymm14,  dword ptr [rcx+4]\n    vfmadd231ps     ymm3, ymm9, ymm15\n    vmovaps         ymm12, [rax + 192]\n    vfmadd231ps     ymm4, ymm10, ymm15\n\tvmovaps         ymm13, [rax + 224]\n    vfmadd231ps     ymm5, ymm11, ymm15\n\n\tvmovaps         ymm6, [rax + 256]\n    vfmadd231ps     ymm0, ymm12, ymm14\n\tvmovaps         ymm7, [rax + 288]\n    vfmadd231ps     ymm1, ymm13, ymm14\n\n    vmovaps         ymm8, [rax + 128]\n    vfmadd231ps     ymm2, ymm6, ymm14\n\n\tvmovaps         ymm9, [rax + 160]\n    vfmadd231ps     ymm3, ymm7, ymm14\n\n    vfmadd231ps     ymm4, ymm8, ymm14\n    vfmadd231ps     ymm5, ymm9, ymm14\n"
  },
  {
    "path": "linalg/x86_64/fma/7x1/packed_packed_loop1/avx.tmpli",
    "content": "\t// Tile size: 6x1\n\t// Accumulators: 0-5\n\t// Col regs: 6-11\n\t// Row regs: 15\n    vbroadcastss    ymm15,  dword ptr [rcx]\n\n    vmovaps         ymm6, [rax + 0]\n\tvmovaps         ymm7, [rax + 32]\n\tvmovaps         ymm8, [rax + 64]\n\tvmovaps         ymm9, [rax + 96]\n\n    vfmadd231ps     ymm0, ymm6, ymm15\n    vfmadd231ps     ymm1, ymm7, ymm15\n\n    vmovaps         ymm10, [rax + 128]\n    vfmadd231ps     ymm2, ymm8, ymm15\n\n\tvmovaps         ymm11, [rax + 160]\n    vfmadd231ps     ymm3, ymm9, ymm15\n\n    vfmadd231ps     ymm4, ymm10, ymm15\n    vfmadd231ps     ymm5, ymm11, ymm15\n"
  },
  {
    "path": "linalg/x86_64/fma/8x1/packed_packed_loop1/avx-unroll.tmpli",
    "content": "\t// Accumulators: 0-7\n\t// Columns: 14-15\n\t// Rows: 8-13\n\n\n    vbroadcastss    ymm15,  dword ptr [rcx]\n    vbroadcastss    ymm14,  dword ptr [rcx + 4]\n\n    vmovaps     ymm8, [rax]\n    vmovaps     ymm9, [rax + 32]\n    vmovaps     ymm10, [rax + 64]\n    vmovaps     ymm11, [rax + 96]\n    vmovaps     ymm12, [rax + 128]\n    vmovaps     ymm13, [rax + 160]\n\n    vfmadd231ps     ymm0, ymm15, ymm8\n    vfmadd231ps     ymm1, ymm15, ymm9\n    vfmadd231ps     ymm2, ymm15, ymm10\n    vfmadd231ps     ymm3, ymm15, ymm11\n    vfmadd231ps     ymm4, ymm15, ymm12\n    vfmadd231ps     ymm5, ymm15, ymm13\n\n\tvmovaps     ymm8, [rax + 192]\n    vmovaps     ymm9, [rax + 224]\n    vmovaps     ymm10, [rax + 256]\n    vmovaps     ymm11, [rax + 288]\n    vmovaps     ymm12, [rax + 320]\n    vmovaps     ymm13, [rax + 352]\n\n    vfmadd231ps     ymm6, ymm15, ymm8\n    vfmadd231ps     ymm7, ymm15, ymm9\n    vfmadd231ps     ymm0, ymm14, ymm10\n    vfmadd231ps     ymm1, ymm14, ymm11\n    vfmadd231ps     ymm2, ymm14, ymm12\n    vfmadd231ps     ymm3, ymm14, ymm13\n\n    vmovaps     ymm8, [rax + 384]\n    vmovaps     ymm9, [rax + 416]\n    vmovaps     ymm10, [rax + 448]\n    vmovaps     ymm11, [rax + 480]\n\n    vfmadd231ps     ymm4, ymm14, ymm8\n    vfmadd231ps     ymm5, ymm14, ymm9\n    vfmadd231ps     ymm6, ymm14, ymm10\n    vfmadd231ps     ymm7, ymm14, ymm11\n\n    add rcx, 8\n\tadd rax, 512\n"
  },
  {
    "path": "linalg/x86_64/fma/8x1/packed_packed_loop1/avx.tmpli",
    "content": "\t// Tile size: 8x1\n\t// Accumulators: 0-7\n\t// Col regs: 8-14\n\t// Row regs: 15\n\n\tvbroadcastss    ymm15,  dword ptr [rcx]\n\n    vmovaps         ymm8, [rax + 0]\n\tvmovaps         ymm9, [rax + 32]\n\tvmovaps         ymm10, [rax + 64]\n\tvmovaps         ymm11, [rax + 96]\n\n    vfmadd231ps     ymm0, ymm8, ymm15\n    vfmadd231ps     ymm1, ymm9, ymm15\n\n    vmovaps         ymm12, [rax + 128]\n\tvmovaps         ymm13, [rax + 160]\n\n    vfmadd231ps     ymm2, ymm10, ymm15\n    vfmadd231ps     ymm3, ymm11, ymm15\n\n    vmovaps         ymm14, [rax + 192]\n\tvmovaps         ymm11, [rax + 224]\n\n    vfmadd231ps     ymm4, ymm12, ymm15\n    vfmadd231ps     ymm5, ymm13, ymm15\n\n\n    vfmadd231ps     ymm6, ymm14, ymm15\n    vfmadd231ps     ymm7, ymm11, ymm15\n\n    add rcx, 4\n\tadd rax, 256\n"
  },
  {
    "path": "linalg/x86_64/fma/8x8/packed_packed_loop1/avx-unroll.tmpli",
    "content": "\t// Tile size: 1x8\n\t// Accumulators: 0-7\n\t// Col regs: 8-14\n\t// Row regs: 15\n\n\n    vmovaps         ymm15,  [rax]\n\n    vbroadcastss    ymm8, dword ptr [rcx + 0 * 4]\n    vfmadd231ps     ymm0, ymm15, ymm8\n\n    vbroadcastss    ymm9, dword ptr [rcx + 1 * 4]\n    vfmadd231ps     ymm1, ymm15, ymm9\n\n    vbroadcastss    ymm10, dword ptr [rcx + 2 * 4]\n    vfmadd231ps     ymm2, ymm15, ymm10\n\n    vbroadcastss    ymm11, dword ptr [rcx + 3 * 4]\n    vfmadd231ps     ymm3, ymm15, ymm11\n\n    vbroadcastss    ymm12, dword ptr [rcx + 4 * 4]\n    vfmadd231ps     ymm4, ymm15, ymm12\n\n    vbroadcastss    ymm13, dword ptr [rcx + 5 * 4]\n    vfmadd231ps     ymm5, ymm15, ymm13\n\n    vbroadcastss    ymm10, dword ptr [rcx + 6 * 4]\n    vfmadd231ps     ymm6, ymm15, ymm10\n\n    vbroadcastss    ymm11, dword ptr [rcx + 7 * 4]\n    vfmadd231ps     ymm7, ymm15, ymm11\n\n\n    vmovaps         ymm15,  [rax]\n\n    vbroadcastss    ymm8, dword ptr [rcx + 0 * 4]\n    vfmadd231ps     ymm0, ymm15, ymm8\n\n    vbroadcastss    ymm9, dword ptr [rcx + 1 * 4]\n    vfmadd231ps     ymm1, ymm15, ymm9\n\n    vbroadcastss    ymm10, dword ptr [rcx + 2 * 4]\n    vfmadd231ps     ymm2, ymm15, ymm10\n\n    vbroadcastss    ymm11, dword ptr [rcx + 3 * 4]\n    vfmadd231ps     ymm3, ymm15, ymm11\n\n    vbroadcastss    ymm12, dword ptr [rcx + 4 * 4]\n    vfmadd231ps     ymm4, ymm15, ymm12\n\n    vbroadcastss    ymm13, dword ptr [rcx + 5 * 4]\n    vfmadd231ps     ymm5, ymm15, ymm13\n\n    vbroadcastss    ymm10, dword ptr [rcx + 6 * 4]\n    vfmadd231ps     ymm6, ymm15, ymm10\n\n    vbroadcastss    ymm11, dword ptr [rcx + 7 * 4]\n    vfmadd231ps     ymm7, ymm15, ymm11\n"
  },
  {
    "path": "linalg/x86_64/fma/8x8/packed_packed_loop1/avx.tmpli",
    "content": "\t// Tile size: 1x8\n\t// Accumulators: 0-7\n\t// Col regs: 8-14\n\t// Row regs: 15\n\n    vmovaps         ymm15,  [rax]\n\n    vbroadcastss    ymm8, dword ptr [rcx + 0 * 4]\n    vfmadd231ps     ymm0, ymm15, ymm8\n\n    vbroadcastss    ymm9, dword ptr [rcx + 1 * 4]\n    vfmadd231ps     ymm1, ymm15, ymm9\n\n    vbroadcastss    ymm10, dword ptr [rcx + 2 * 4]\n    vfmadd231ps     ymm2, ymm15, ymm10\n\n    vbroadcastss    ymm11, dword ptr [rcx + 3 * 4]\n    vfmadd231ps     ymm3, ymm15, ymm11\n\n    vbroadcastss    ymm12, dword ptr [rcx + 4 * 4]\n    vfmadd231ps     ymm4, ymm15, ymm12\n\n    vbroadcastss    ymm13, dword ptr [rcx + 5 * 4]\n    vfmadd231ps     ymm5, ymm15, ymm13\n\n    vbroadcastss    ymm10, dword ptr [rcx + 6 * 4]\n    vfmadd231ps     ymm6, ymm15, ymm10\n\n    vbroadcastss    ymm11, dword ptr [rcx + 7 * 4]\n    vfmadd231ps     ymm7, ymm15, ymm11\n"
  },
  {
    "path": "linalg/x86_64/fma/avx2_mmm_i32_8x8.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 8x8:\n\n    ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% if msvc %}\n\n_text segment\navx2_mmm_i32_8x8_{{suffix}} proc\n\n{% else %}\n\n.intel_syntax noprefix\n.text\n.p2align 5\n.globl {{G}}avx2_mmm_i32_8x8_{{suffix}}\n{{G}}avx2_mmm_i32_8x8_{{suffix}}:\n.cfi_startproc\n\n{% endif %}\n\n    push        rbp\n    mov         rbp, rsp\n\n{% if family == \"windows\" %}\n// https://www.agner.org/optimize/calling_conventions.pdf xmm6-15 are not scratch\n// https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers\n    and rsp,-16\n    lea rsp,[rsp-160]\n    vmovaps [rsp], xmm6\n    vmovaps [rsp+16*1],xmm7\n    vmovaps [rsp+16*2],xmm8\n    vmovaps [rsp+16*3],xmm9\n    vmovaps [rsp+16*4],xmm10\n    vmovaps [rsp+16*5],xmm11\n    vmovaps [rsp+16*6],xmm12\n    vmovaps [rsp+16*7],xmm13\n    vmovaps [rsp+16*8],xmm14\n    vmovaps [rsp+16*9],xmm15\n\n    push        rdi\n    push        rsi\n\n    mov         rdi, rcx\n\n{% endif %}\n\n    push        rbx\n    push        r12\n    push        r13\n    push        r14\n    push        r15\n\n    sub         rsp, 8\n\n{% if family == \"unix\" %}\n.cfi_def_cfa_offset 64\n{% endif %}\n\n    stmxcsr     [rsp + 4]\n{% if msvc %}\n    mov         rax, 1FC0h\n{% else %}\n    mov         rax, 0x1FC0\n{% endif %}\n    mov         [rsp], eax\n    ldmxcsr     [rsp]\n\n{% include \"dispatcher.tmpliq\" %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     r12,    [rdi + 32]   // packing\n    mov     rbx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rcx,    [rdi + 8]    // k\n    test    rcx,    rcx\n    jz      {{L}}non_linear_loop\n\n    cmp     r12, 1\n    je      {{L}}main_loop_packed_packed_i8i8\n\n{{L}}main_loop_packed_packed:\n    vmovaps         ymm12,  [rax]\n\n    {% for i in (0..7) %}\n        vbroadcastss    ymm14, dword ptr [rbx + {{i}} * 4]\n        vpmulld         ymm13, ymm12, ymm14\n        vpaddd          ymm{{i}}, ymm{{i}}, ymm13\n    {% endfor %}\n\n    add             rax,    32\n    add             rbx,    32\n    dec             rcx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed_i8i8:\n    movq            xmm8, qword ptr [rax]          // read 8 bytes\n    vpmovsxbw       ymm8, xmm8                     // promote byte to i32x8\n\n    vpbroadcastb    ymm9, byte ptr [rbx]           // broadcast 1 byte from B\n    vpbroadcastb    ymm10, byte ptr [rbx + 1]      // broadcast 1 byte from B\n    vpbroadcastb    ymm11, byte ptr [rbx + 2]      // broadcast 1 byte from B\n    vpbroadcastb    ymm12, byte ptr [rbx + 3]      // broadcast 1 byte from B\n    vpmovsxbw       ymm9, xmm9                     // promote byte to i32x8\n    vpmovsxbw       ymm10, xmm10                   // promote byte to i32x8\n    vpmovsxbw       ymm11, xmm11                   // promote byte to i32x8\n    vpmovsxbw       ymm12, xmm12                   // promote byte to i32x8\n\n    vpmullw         ymm9, ymm9, ymm8\n    vpmullw         ymm10, ymm10, ymm8\n    vpmullw         ymm11, ymm11, ymm8\n    vpmullw         ymm12, ymm12, ymm8\n    vpmovsxwd       ymm9, xmm9                     // promote byte to i32x8\n    vpmovsxwd       ymm10, xmm10                   // promote byte to i32x8\n    vpmovsxwd       ymm11, xmm11                   // promote byte to i32x8\n    vpmovsxwd       ymm12, xmm12                   // promote byte to i32x8\n    vpaddd          ymm0, ymm0, ymm9\n    vpaddd          ymm1, ymm1, ymm10\n    vpaddd          ymm2, ymm2, ymm11\n    vpaddd          ymm3, ymm3, ymm12\n\n    vpbroadcastb    ymm9, byte ptr [rbx + 4]\n    vpbroadcastb    ymm10, byte ptr [rbx + 5]\n    vpbroadcastb    ymm11, byte ptr [rbx + 6]\n    vpbroadcastb    ymm12, byte ptr [rbx + 7]\n    vpmovsxbw       ymm9, xmm9\n    vpmovsxbw       ymm10, xmm10\n    vpmovsxbw       ymm11, xmm11\n    vpmovsxbw       ymm12, xmm12\n\n    vpmullw         ymm9, ymm9, ymm8\n    vpmullw         ymm10, ymm10, ymm8\n    vpmullw         ymm11, ymm11, ymm8\n    vpmullw         ymm12, ymm12, ymm8\n    vpmovsxwd       ymm9, xmm9                     // promote byte to i32x8\n    vpmovsxwd       ymm10, xmm10                   // promote byte to i32x8\n    vpmovsxwd       ymm11, xmm11                   // promote byte to i32x8\n    vpmovsxwd       ymm12, xmm12                   // promote byte to i32x8\n    vpaddd          ymm4, ymm4, ymm9\n    vpaddd          ymm5, ymm5, ymm10\n    vpaddd          ymm6, ymm6, ymm11\n    vpaddd          ymm7, ymm7, ymm12\n\n    add             rbx,    8\n    add             rax,    8\n    dec             rcx\n    jnz             {{L}}main_loop_packed_packed_i8i8\n\n    jmp             {{L}}non_linear_loop\n\n{% include \"fma_mmm_i32_scalars.tmpliq\" from:0, to:7 %}\n{% include \"fma_mmm_i32_per_rows.tmpliq\" mr:8,from:0, to:7 %}\n{% include \"fma_mmm_i32_per_cols.tmpliq\" mr:8,from:0, to:7 %}\n{% include \"fma_mmm_load_tile.tmpliq\" from:0, to:7 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n    mov     r8,     [rdi + 32]          // item size\n\n    cmp     r8,    4\n    je      {{L}}non_linear_addc_i32\n\n{% comment %}\n// This is not great as vgatherdps reads 32-bits values and goes beyond our buffer. Probably harmless though.\n// Commented and replaced with the \"mov al\" loop beyond to pacify valgrind.\n// ymm14 and ymm15 are the same as in the non_linear_addc_i32 case (compute them before the test right above here.\n// {% for i in (0..7) %}\n//     vpcmpeqd        ymm15, ymm15, ymm15\n//     vgatherdps      ymm12, [ r10 + ymm14 ], ymm15   // 0xxx 1xxx 2xxx 3xxx 4xxx 5xxx 6xxx 7xxx\n//\n//     // we need to go through vpmovsxbd, shuffling naively erases signs\n//     vpshufb         ymm12, ymm12, ymm10             // 0123 0123 0123 0123 4567 4567 4567 4567\n//\n//     vpermd          ymm12, ymm11, ymm12             // 0123 4567\n//     vpmovsxbd       ymm12, xmm12                    // sign extend\n//\n//     vpaddd          ymm{{i}},   ymm{{i}},   ymm12\n//     add             r10, rbx\n// {% endfor %}\n{% endcomment %}\n\n    {% for col in (0..7) %}\n        mov r8, r10\n        {% for half in (0..1) %}\n            {% for lane in (0..3) %}\n                mov al, [ r8 ]\n                add r8, rsi\n                movsx eax, al\n                pinsrd xmm10, eax, {{lane}}\n            {% endfor %}\n            vperm2f128  ymm10,   ymm10,   ymm10,  1\n        {% endfor %}\n        vpaddd ymm{{col}}, ymm{{col}}, ymm10\n        add r10, rbx\n    {% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}non_linear_addc_i32:\n\n    mov     eax,    0\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n    vpermq          ymm14, ymm14, 78 // 0b01001110\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n    vpermq          ymm14, ymm14, 78 // 0b01001110\n\n\n{% if msvc %}\n    vpbroadcastd    ymm10, dword ptr [ offset byte_shuffle ]\n    vmovups         ymm11, dword ptr [ offset i128_shuffle ]\n{% else %}\n    vpbroadcastd    ymm10, [ rip + {{L}}byte_shuffle ]\n    vmovups         ymm11, [ rip + {{L}}i128_shuffle ]\n{% endif %}\n\n{% for i in (0..7) %}\n    vpcmpeqd        ymm15, ymm15, ymm15\n    vgatherdps      ymm12, [ r10 + ymm14 ], ymm15\n    vpaddd          ymm{{i}},   ymm{{i}},   ymm12\n    add             r10, rbx\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{% if msvc %}\n.data\nbyte_shuffle dd              201851904 // 0x0c080400\ni128_shuffle dd              0, 4\n.code\n{% else %}\n{{L}}byte_shuffle: .int            201851904 // 0x0c080400\n{{L}}i128_shuffle: .int            0, 4\n{% endif %}\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         ymm12,  [rax]\n\n{% for i in (0..7) %}\n    vbroadcastss    ymm14, dword ptr [rbx + {{i|times:4}} ]\n    vpmulld         ymm15, ymm12, ymm14\n    vpaddd          ymm{{i}}, ymm{{i}}, ymm15\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}q_scale:\n    mov             r8, [ rdi + 16 ]        // policy\n    vbroadcastss    ymm8, dword ptr [rdi + 24] // multi\n\n    mov             rax, 1\n    movq            xmm9, rax\n    vpbroadcastq    ymm9, xmm9              // ymm9 <- 1\n\n    mov             rax, [ rdi + 8 ]        // xmm10 <- shift + 31\n    add             rax, 31\n    movq            xmm10, rax\n    vpbroadcastq    ymm10, xmm10\n\n    mov             rax, 1\n    movq            xmm11, rax\n    vpsubq          ymm12, ymm10, ymm9      // shift+31 - 1\n    vpsllq          ymm11, ymm9, xmm12      // ymm11 <- 1 << (shift + 31 - 1)\n\n    cmp     r8, 1\n    je      {{L}}q_scale_rounding_zero\n    cmp     r8, 2\n    je      {{L}}q_scale_rounding_away\n    cmp     r8, 3\n    je      {{L}}q_scale_rounding_minus_inf\n    cmp     r8, 4\n    je      {{L}}q_scale_rounding_plus_inf\n    cmp     r8, 5\n    je      {{L}}q_scale_rounding_even\n    cmp     r8, 6\n    je      {{L}}q_scale_rounding_odd\n\n    jmp    {{L}}unsupported\n\n{{L}}q_scale_rounding_zero:           // signum * ( (abs + nudge) >> shift )\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    vpsrldq     ymm15, ymm14, 4             // ymm15 <- a1, a2, a3, a4, a5, a6, a7, 0\n    vpmuldq     ymm14, ymm14, ymm8          // ymm14  <- a0*c, a2*c, a4*c, a6*c\n    vpmuldq     ymm15, ymm15, ymm8          // ymm15 <- a1*c, a3*c, a5*c, a7*c\n\n    vpaddq      ymm14, ymm14, ymm11\n    vpaddq      ymm15, ymm15, ymm11\n\n    vpsubq      ymm14, ymm14, ymm9\n    vpsubq      ymm15, ymm15, ymm9\n\n    vpsrlq      ymm14, ymm14, xmm10\n    vpsrlq      ymm15, ymm15, xmm10\n\n    vpslldq     ymm15, ymm15, 4\n    vpblendd    ymm14, ymm15, ymm14, 85     // 0x55\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}q_scale_rounding_away:           // signum * ( (abs + nudge) >> shift )\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    vpsrldq     ymm15, ymm14, 4             // ymm15 <- a1, a2, a3, a4, a5, a6, a7, 0\n    vpmuldq     ymm14, ymm14, ymm8          // ymm14  <- a0*c, a2*c, a4*c, a6*c\n    vpmuldq     ymm15, ymm15, ymm8          // ymm15 <- a1*c, a3*c, a5*c, a7*c\n\n    vpaddq      ymm14, ymm14, ymm11\n    vpaddq      ymm15, ymm15, ymm11\n\n    vpsrlq      ymm14, ymm14, xmm10\n    vpsrlq      ymm15, ymm15, xmm10\n\n    vpslldq     ymm15, ymm15, 4\n    vpblendd    ymm14, ymm15, ymm14, 85     // 0x55\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}q_scale_rounding_minus_inf:           // signum * ( (abs << 32 + 1<<30+shift) >> shift )\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    // sign extract for nudging in the right direction\n    vpxor       ymm13, ymm13, ymm13\n    vpcmpgtd    ymm13, ymm{{i}}, ymm13      // ymm13 <- s0, s1, ..s8 (signums, as all ones or all zeros)\n    vpsrld      ymm13, ymm13, 31            // then just 0 or 1\n\n    vpsrldq     ymm15, ymm14, 4             // ymm15 <- a1, a2, a3, a4, a5, a6, a7, 0\n    vpmuldq     ymm14, ymm14, ymm8          // ymm14  <- a0*c, a2*c, a4*c, a6*c\n    vpmuldq     ymm15, ymm15, ymm8          // ymm15 <- a1*c, a3*c, a5*c, a7*c\n\n    vpaddq      ymm14, ymm14, ymm11\n    vpaddq      ymm15, ymm15, ymm11\n\n    // reinterpret ymm13=s0i32..s7 as i64 and blend with zero to pick the even ones as i64\n    vpxor       ymm12, ymm12, ymm12\n    vpblendd    ymm12, ymm12, ymm13, 85     // 0x55\n    vpsubq      ymm14, ymm14, ymm12\n\n    vpsrldq     ymm13, ymm13, 4             // ymm13 <- s1, s2, .., s7, 0\n    vpxor       ymm12, ymm12, ymm12\n    vpblendd    ymm12, ymm12, ymm13, 85     // 0x55\n    vpsubq      ymm15, ymm15, ymm12\n\n    vpsrlq      ymm14, ymm14, xmm10\n    vpsrlq      ymm15, ymm15, xmm10\n\n    vpslldq     ymm15, ymm15, 4\n    vpblendd    ymm14, ymm15, ymm14, 85     // 0x55\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}q_scale_rounding_plus_inf:           // signum * ( (abs << 32 + 1<<30+shift) >> shift )\n\n    vpbroadcastd ymm9, xmm9\n\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    vpxor       ymm13, ymm13, ymm13\n\n    // sign extract for nudging in the right direction\n    vpcmpgtd    ymm13, ymm{{i}}, ymm13      // ymm13 <- s0, s1, ..s8 (signums, as all ones or all zeros)\n    vpaddd      ymm13, ymm13, ymm9          // if val >= 0 { 0i32 } else { 1i32 }\n\n    vpsrldq     ymm15, ymm14, 4             // ymm15 <- a1, a2, a3, a4, a5, a6, a7, 0\n    vpmuldq     ymm14, ymm14, ymm8          // ymm14  <- a0*c, a2*c, a4*c, a6*c\n    vpmuldq     ymm15, ymm15, ymm8          // ymm15 <- a1*c, a3*c, a5*c, a7*c\n\n    vpaddq      ymm14, ymm14, ymm11\n    vpaddq      ymm15, ymm15, ymm11\n\n    // reinterpret ymm13=s0i32..s7 as i64 and blend with zero to pick the even ones as i64\n    vpxor       ymm12, ymm12, ymm12\n    vpblendd    ymm12, ymm12, ymm13, 85     // 0x55\n    vpsubq      ymm14, ymm14, ymm12\n\n    vpsrldq     ymm13, ymm13, 4             // ymm13 <- s1, s2, .., s7, 0\n    vpxor       ymm12, ymm12, ymm12\n    vpblendd    ymm12, ymm12, ymm13, 85     // 0x55\n    vpsubq      ymm15, ymm15, ymm12\n\n    vpsrlq      ymm14, ymm14, xmm10\n    vpsrlq      ymm15, ymm15, xmm10\n\n    vpslldq     ymm15, ymm15, 4\n    vpblendd    ymm14, ymm15, ymm14, 85     // 0x55\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}q_scale_rounding_even:           // signum * ( (abs + nudge) >> shift )\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    vpsrldq     ymm15, ymm14, 4             // ymm15 <- a1, a2, a3, a4, a5, a6, a7, 0\n    vpmuldq     ymm14, ymm14, ymm8          // ymm14  <- a0*c, a2*c, a4*c, a6*c\n    vpmuldq     ymm15, ymm15, ymm8          // ymm15 <- a1*c, a3*c, a5*c, a7*c\n\n    vpsrlq      ymm12, ymm14, xmm10\n    vpand       ymm12, ymm12, ymm9\n    vpaddq      ymm14, ymm14, ymm12\n    vpsubq      ymm14, ymm14, ymm9\n\n    vpsrlq      ymm12, ymm15, xmm10\n    vpand       ymm12, ymm12, ymm9\n    vpaddq      ymm15, ymm15, ymm12\n    vpsubq      ymm15, ymm15, ymm9\n\n    vpaddq      ymm14, ymm14, ymm11\n    vpaddq      ymm15, ymm15, ymm11\n\n    vpsrlq      ymm14, ymm14, xmm10\n    vpsrlq      ymm15, ymm15, xmm10\n\n    vpslldq     ymm15, ymm15, 4\n    vpblendd    ymm14, ymm15, ymm14, 85     // 0x55\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}q_scale_rounding_odd:           // signum * ( (abs + nudge) >> shift )\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    vpsrldq     ymm15, ymm14, 4             // ymm15 <- a1, a2, a3, a4, a5, a6, a7, 0\n    vpmuldq     ymm14, ymm14, ymm8          // ymm14  <- a0*c, a2*c, a4*c, a6*c\n    vpmuldq     ymm15, ymm15, ymm8          // ymm15 <- a1*c, a3*c, a5*c, a7*c\n\n    vpsrlq      ymm12, ymm14, xmm10\n    vpand       ymm12, ymm12, ymm9\n    vpsubq      ymm14, ymm14, ymm12\n\n    vpsrlq      ymm12, ymm15, xmm10\n    vpand       ymm12, ymm12, ymm9\n    vpsubq      ymm15, ymm15, ymm12\n\n    vpaddq      ymm14, ymm14, ymm11\n    vpaddq      ymm15, ymm15, ymm11\n\n    vpsrlq      ymm14, ymm14, xmm10\n    vpsrlq      ymm15, ymm15, xmm10\n\n    vpslldq     ymm15, ymm15, 4\n    vpblendd    ymm14, ymm15, ymm14, 85     // 0x55\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}q_shl:\n    mov             eax, [ rdi + 8 ]        // xmm10 <- -shift (8 times)\n    movd            xmm10, eax\n    vpbroadcastd    ymm10, xmm10\n\n{% for i in (0..7) %}\n    vpsllvd     ymm{{i}}, ymm{{i}}, ymm10\n{% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{{L}}q_shr:\n    mov             r8, [ rdi + 16 ]        // policy\n\n    mov             eax, 1\n    movd            xmm9, eax\n    vpbroadcastd    ymm9, xmm9              // ymm9 <- 1u32 (8 times)\n\n    mov             eax, [ rdi + 8 ]        // xmm10 <- shift (8 times)\n    movd            xmm10, eax\n    vpbroadcastd    ymm10, xmm10\n\n    mov             ebx, 1\n    mov             cl, al\n    sub             cl, 1                  // rcx <- shift -1\n    sal             ebx, cl                // rbx <- (1 << (shift - 1))\n    movd            xmm11, ebx\n    vpbroadcastd    ymm11, xmm11            // ymm11 <- \"half\"\n\n    vpxor           ymm12, ymm12, ymm12     // ymm12 <- zeroes\n\n    cmp     r8, 1\n    je      {{L}}q_shr_rounding_zero\n    cmp     r8, 2\n    je      {{L}}q_shr_rounding_away\n    cmp     r8, 3\n    je      {{L}}q_shr_rounding_minus_inf\n    cmp     r8, 4\n    je      {{L}}q_shr_rounding_plus_inf\n    cmp     r8, 5\n    je      {{L}}q_shr_rounding_even\n    cmp     r8, 6\n    je      {{L}}q_shr_rounding_odd\n\n    jmp    {{L}}unsupported\n\n{{L}}q_shr_rounding_zero:\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    vpsubd      ymm14, ymm14, ymm9\n    vpaddd      ymm14, ymm14, ymm11\n    vpsravd     ymm14, ymm14, ymm10\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{{L}}q_shr_rounding_away:\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    vpaddd      ymm14, ymm14, ymm11\n    vpsravd     ymm14, ymm14, ymm10\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{{L}}q_shr_rounding_minus_inf:\n{% for i in (0..7) %}\n    vpsubd  ymm{{i}}, ymm{{i}}, ymm9\n    vpaddd  ymm{{i}}, ymm{{i}}, ymm11\n    vpsravd ymm{{i}}, ymm{{i}}, ymm10\n{% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{{L}}q_shr_rounding_plus_inf:\n{% for i in (0..7) %}\n    vpaddd  ymm{{i}}, ymm{{i}}, ymm11\n    vpsravd ymm{{i}}, ymm{{i}}, ymm10\n{% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{{L}}q_shr_rounding_even:\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    vpsravd ymm13, ymm14, ymm10\n    vpand   ymm13, ymm13, ymm9\n    vpsubd  ymm13, ymm13, ymm9          // nudge = ((abs >>l shift) & 0x01) - 1\n    vpaddd  ymm14, ymm14, ymm13         // add nudge\n    vpaddd  ymm14, ymm14, ymm11         // add half\n    vpsravd ymm14, ymm14, ymm10\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{{L}}q_shr_rounding_odd:\n{% for i in (0..7) %}\n    vpabsd      ymm14, ymm{{i}}\n    vpsravd ymm13, ymm14, ymm10\n    vpand   ymm13, ymm13, ymm9\n    vpsubd  ymm13, ymm12, ymm13          // nudge = - ((abs >>l shift) & 0x01)\n    vpaddd  ymm14, ymm14, ymm13         // add nudge\n    vpaddd  ymm14, ymm14, ymm11         // add half\n    vpsravd ymm14, ymm14, ymm10\n    vpsignd     ymm{{i}}, ymm14, ymm{{i}}\n{% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rdx,    [rdi + 24]          // col stride\n    mov     rcx,    [rdi + 32]          // item size\n\n    cmp     rcx,    4\n    je      {{L}}store_strides_i32\n\n    {% for col in (0..7) %}\n        mov r10, r8\n        {% for row in (0..3) %}\n            extractps   ebx, xmm{{col}}, {{row}}\n            mov         byte ptr [r10], bl\n            add         r10, rsi\n        {% endfor %}\n        vperm2f128  ymm{{col}},   ymm{{col}},   ymm{{col}},  1\n        {% for row in (0..3) %}\n            extractps   ebx, xmm{{col}}, {{row}}\n            mov         byte ptr [r10], bl\n            add         r10, rsi\n        {% endfor %}\n        add r8, rdx\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_strides_i32:\n    {% for col in (0..7) %}\n        mov r10,    r8\n        {% for row in (0..3) %}\n            extractps   ebx, xmm{{col}}, {{row}}\n            mov         dword ptr [r10], ebx\n            add         r10, rsi\n        {% endfor %}\n        vperm2f128  ymm{{col}},   ymm{{col}},   ymm{{col}},  1\n        {% for row in (0..3) %}\n            extractps   ebx, xmm{{col}}, {{row}}\n            mov         dword ptr [r10], ebx\n            add         r10, rsi\n        {% endfor %}\n        add r8, rdx\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}return:\n    ldmxcsr     [rsp + 4]\n    add         rsp, 8\n\n    pop r15\n    pop r14\n    pop r13\n    pop r12\n    pop rbx\n\n{% if family == \"windows\" %}\n    pop rsi\n    pop rdi\n\n    vmovaps xmm15, [rsp+16*9]\n    vmovaps xmm14, [rsp+16*8]\n    vmovaps xmm13, [rsp+16*7]\n    vmovaps xmm12, [rsp+16*6]\n    vmovaps xmm11, [rsp+16*5]\n    vmovaps xmm10, [rsp+16*4]\n    vmovaps xmm9, [rsp+16*3]\n    vmovaps xmm8, [rsp+16*2]\n    vmovaps xmm7, [rsp+16*1]\n    vmovaps xmm6, [rsp]\n{% endif %}\n\n    mov rsp, rbp\n    pop rbp\n    ret\n\n\n{{L}}one_32bit:\n{% if msvc %}\n    dd      1\n{% else %}\n    .int    1\n{% endif %}\n\n{% if msvc %}\navx2_mmm_i32_8x8_{{suffix}} endp\n_text ends\nend\n{% else %}\n.cfi_endproc\n{% endif %}\n"
  },
  {
    "path": "linalg/x86_64/fma/dispatcher.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}non_linear:\n\n{{L}}non_linear_loop_enter:\n    sub     rdi,    40\n{{L}}non_linear_loop:\n    add     rdi,    40\n    mov     rax,    [rdi]\n\n    mov     r8, {{ jump_table | size }}\n    cmp     rax, 0\n    cmovl   rax, r8\n    cmp     rax, {{ jump_table | size }}\n    cmovg   rax, r8\n\n{% if msvc %}\n    lea     r8, [ offset {{L}}jmp_table ]\n{% else %}\n    lea     r8, [ rip + {{L}}jmp_table ]\n{% endif %}\n    movsxd  r9, dword ptr [ r8 + rax * 4 ]\n    lea     r8, [ r8 + r9 ]\n    jmp     r8\n\n{{L}}jmp_table:\n{% for j in jump_table %}\n    {{long}}      {{L}}{{j}}-{{L}}jmp_table\n{% endfor %}\n    {{long}}      {{L}}unsupported-{{L}}jmp_table\n\n{{L}}unsupported:\n    mov     rax,    1\n    jmp     {{L}}return\n\n\n{{L}}done:\n    mov     rax, 0\n    jmp     {{L}}return\n\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_16x5.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n/* mmm 16 x 5:\n\n    ymm0 ymm2 ymm4 ymm6 ymm8\n    ymm1 ymm3 ymm5 ymm7 ymm9\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" type:\"f32\", size:\"16x5\", suffix:suffix, G:G %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n    {% include \"2x5/packed_packed_loop1/avx.tmpli\" %}\n\n    add             rcx,    20\n    add             rax,    64\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n// NON LINEAR / ADDC\n\n{% include \"fma_mmm_f32_scalars.tmpliq\" from:0, to:9, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_rows.tmpliq\" mr:16, from:0, to:9, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_cols.tmpliq\" mr:16, from:0, to:9, type:\"f32\" %}\n{% include \"fma_mmm_load_tile.tmpliq\" from:0, to:9 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n\n    lea             r8, [ r10 + rsi * 8 ]\n\n{% for i in (0..4) %}\n    vpcmpeqd        ymm15,  ymm15, ymm15\n    vgatherdps      ymm12,  [ r10 + ymm14 ],      ymm15\n    vpcmpeqd        ymm15,  ymm15, ymm15\n    vgatherdps      ymm13,  [ r8  + ymm14 ],      ymm15\n    add             r10, rbx\n    add             r8, rbx\n    vaddps          ymm{{i | times:2 }},   ymm{{i | times:2}},   ymm12\n    vaddps          ymm{{i | times:2 | plus: 1}}, ymm{{i | times:2 | plus:1 }},   ymm13\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         ymm12,  [rax]\n    vmovups         ymm13,  [rax + 32]\n\n{% for i in (0..4) %}\n    vbroadcastss    ymm14, dword ptr [rbx + {{i|times:4}} ]\n    vfmadd231ps     ymm{{i|times:2}},   ymm12, ymm14\n    vfmadd231ps     ymm{{i|times:2|plus:1}}, ymm13, ymm14\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r12,    [ r8 + 4 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n    cmp     rbx,    64\n    jne     {{L}}store_strides_generic\n\n    {% for row in (0..1) %}\n        {% for col in (0..4) %}\n            vmovups ymmword ptr [r{{col|plus:8}}], ymm{{col|times:2|plus:row}}\n            add r{{col|plus:8}}, 32\n       {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_strides_generic:\n    // tops of cols\n\n    {% for quarter in (0..3) %}\n        {% if quarter != 0 %}\n            // move next four rows at top (xmm0,2,..10)\n            vperm2f128  ymm0,   ymm0,   ymm1,  {{quarter}}\n            vperm2f128  ymm2,   ymm2,   ymm3,  {{quarter}}\n            vperm2f128  ymm4,   ymm4,   ymm5,  {{quarter}}\n            vperm2f128  ymm6,   ymm6,   ymm7,  {{quarter}}\n            vperm2f128  ymm8,   ymm8,   ymm9,  {{quarter}}\n        {% endif %}\n        {% for row in (0..3) %}\n            {% for i in (0..4) %}\n                vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | times:2}}, {{row}}\n                add         r{{i | plus: 8}}, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" type:\"f32\", size:\"16x5\", suffix:suffix, G:G, L:L %}\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_16x6.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 16 x 6:\n\n    ymm0 ymm2 ymm4 ymm6 ymm8 ymm10\n    ymm1 ymm3 ymm5 ymm7 ymm9 ymm11\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" type:\"f32\", size:\"16x6\", suffix:suffix, G:G %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n\t{% include \"2x6/packed_packed_loop1/original.tmpli\" %}\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n// NON LINEAR / ADDC\n\n{% include \"fma_mmm_f32_scalars.tmpliq\" from:0, to:11, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_rows.tmpliq\" mr:16, from:0, to:11, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_cols.tmpliq\" mr:16, from:0, to:11, type:\"f32\" %}\n{% include \"fma_mmm_load_tile.tmpliq\" from:0, to:11 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n\n    lea             r8, [ r10 + rsi * 8 ]\n\n{% for i in (0..5) %}\n    vpcmpeqd        ymm15,  ymm15, ymm15\n    vgatherdps      ymm12,  [ r10 + ymm14 ],      ymm15\n    vpcmpeqd        ymm15,  ymm15, ymm15\n    vgatherdps      ymm13,  [ r8  + ymm14 ],      ymm15\n    add     \t\tr10, rbx\n    add     \t\tr8, rbx\n    vaddps          ymm{{i | times:2 }},   ymm{{i | times:2}},   ymm12\n    vaddps          ymm{{i | times:2 | plus: 1}}, ymm{{i | times:2 | plus:1 }},   ymm13\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         ymm12,  [rax]\n    vmovups         ymm13,  [rax + 32]\n\n{% for i in (0..5) %}\n    vbroadcastss    ymm14, dword ptr [rbx + {{i|times:4}} ]\n    vfmadd231ps     ymm{{i|times:2}},   ymm12, ymm14\n    vfmadd231ps     ymm{{i|times:2|plus:1}}, ymm13, ymm14\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    // tops of cols\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r12,    [ r8 + 4 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n    lea     r13,    [ r12 + rbx ]\n\n    {% for quarter in (0..3) %}\n        {% if quarter != 0 %}\n            // move next four rows at top (xmm0,2,..10)\n            vperm2f128  ymm0,   ymm0,   ymm1,  {{quarter}}\n            vperm2f128  ymm2,   ymm2,   ymm3,  {{quarter}}\n            vperm2f128  ymm4,   ymm4,   ymm5,  {{quarter}}\n            vperm2f128  ymm6,   ymm6,   ymm7,  {{quarter}}\n            vperm2f128  ymm8,   ymm8,   ymm9,  {{quarter}}\n            vperm2f128  ymm10,  ymm10,  ymm11, {{quarter}}\n        {% endif %}\n        {% for row in (0..3) %}\n            {% for i in (0..5) %}\n                vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i | times:2}}, {{row}}\n                add         r{{i | plus: 8}}, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" type:\"f32\", size:\"16x6\", suffix:suffix, G:G, L:L %}\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_24x4.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n/* mmm 24 x 4:\n\n    ymm0 ymm3 ymm6 ymm10\n    ymm1 ymm4 ymm7 ymm11\n    ymm2 ymm5 ymm8 ymm12\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" type:\"f32\", size:\"24x4\", suffix:suffix, G:G %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n    {% include \"3x4/packed_packed_loop1/avx.tmpli\" %}\n\n    add             rcx,    16\n    add             rax,    96\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n// NON LINEAR / ADDC\n\n{% include \"fma_mmm_f32_scalars.tmpliq\" from:0, to:11, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_rows.tmpliq\" mr:24, from:0, to:11, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_cols.tmpliq\" mr:24, from:0, to:11, type:\"f32\" %}\n{% include \"fma_mmm_load_tile.tmpliq\" from:0, to:11 %}\n\n{{L}}add_unicast:\n\n    mov     r8,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    cmp rsi, 4\n    jne {{L}}unicast_generic\n\n    lea             r9,  [ r8 + rbx ]\n    lea             r10, [ r9 + rbx]\n    lea             r11, [ r10 + rbx ]\n    lea             r12, [ r11 + rbx ]\n\n{% for col in (0..3) %}\n    {% for row in (0..2) %}\n        vmovups ymm12,  [ r{{col|plus:8}} ]\n        add r{{col|plus:8}}, 32\n        vaddps ymm{{col|times:3|plus:row}}, ymm{{col|times:3|plus:row}}, ymm12\n    {% endfor %}\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}unicast_generic:\n    mov     eax,    0\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n//  mov r12, [0]\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n\n    lea             r9, [ r8 + rsi * 8 ]\n    lea             r10, [ r9 + rsi * 8 ]\n\n{% for col in (0..3) %}\n   {% for row in (0..2) %}\n      vpcmpeqd        ymm15,  ymm15, ymm15\n      vgatherdps      ymm12,  [ r{{row|plus:8}} + ymm14 ], ymm15\n      add r{{row|plus:8}}, rbx\n      vaddps ymm{{col|times:3|plus:row}}, ymm{{col|times:3|plus:row}}, ymm12\n   {% endfor %}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         ymm12,  [rax]\n    vmovups         ymm13,  [rax + 32]\n    vmovups         ymm15,  [rax + 64]\n{% for i in (0..3) %}\n    vbroadcastss    ymm14, dword ptr [rbx + {{i|times:4}} ]\n    vfmadd231ps     ymm{{i|times:3}},   ymm12, ymm14\n    vfmadd231ps     ymm{{i|times:3|plus:1}}, ymm13, ymm14\n    vfmadd231ps     ymm{{i|times:3|plus:2}}, ymm15, ymm14\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    lea     r9,     [ r8  +     rbx ]\n    lea     r10,    [ r8  + 2 * rbx ]\n    lea     r11,    [ r10 +     rbx ]\n\n    cmp         rsi, 4\n    jne         {{L}}store_strides_generic\n\n    {% for col in (0..3) %}\n       {% for row in (0..2) %}\n            vmovups ymmword ptr [r{{col|plus:8}}], ymm{{col|times:3|plus:row}}\n            add r{{col|plus:8}}, 32\n       {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_strides_generic:\n    {% for col in (0..3) %}\n       {% for row in (0..2) %}\n           {% for i in (0..3) %}\n                vextractps  dword ptr [r{{col | plus: 8}}], xmm{{col | times:3 | plus:row}}, {{i}}\n                add         r{{col | plus: 8}}, rsi\n           {% endfor %}\n           vperm2f128  ymm{{col | times:3 | plus:row}}, ymm{{col | times:3 | plus:row}}, ymm{{col | times:3 | plus:row}}, 1\n           {% for i in (0..3) %}\n                vextractps  dword ptr [r{{col | plus: 8}}], xmm{{col | times:3|plus:row}}, {{i}}\n                add         r{{col | plus: 8}}, rsi\n           {% endfor %}\n       {% endfor %}\n    {% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" type:\"f32\", size:\"24x4\", suffix:suffix, G:G, L:L %}\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_32x1.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 64 x 1\n\n    ymm0\n    ymm1\n    ymm2\n    ymm3\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" type:\"f32\", size:\"32x1\", suffix:suffix, G:G %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    mov     r8,    [rdi + 32]   // packing\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n    cmp     r8, 1\n    jz      {{L}}q40f32\n\n    cmp     r8, 2\n    jz      {{L}}q40f16\n\n    cmp     r8, 3\n    jz      {{L}}f16f16\n\n    cmp     r8, 4\n    jz      {{L}}f16f32\n\n    cmp     r8, 5\n    jz      {{L}}f32f16\n\n{{align}} 16\n{{L}}main_loop_packed_packed:\n    vbroadcastss    ymm15,  dword ptr [rcx]\n\n    vmovaps     ymm8, [rax]\n    vmovaps     ymm9, [rax + 32]\n    vmovaps     ymm10, [rax + 64]\n    vmovaps     ymm11, [rax + 96]\n\n    vfmadd231ps     ymm0, ymm15, ymm8\n    vfmadd231ps     ymm1, ymm15, ymm9\n    vfmadd231ps     ymm2, ymm15, ymm10\n    vfmadd231ps     ymm3, ymm15, ymm11\n\n    add             rcx, 4\n\tadd             rax, 128\n    sub             rbx, 1\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{% if msvc %}\n{{L}}q40f32_mask:\n    {{long}} 0F0F0F0Fh\n{{L}}q40f32_eight:\n    {{long}} 08h\n{% else %}\n{{L}}q40f32_mask:\n    {{long}} 0x0F0F0F0F\n{{L}}q40f32_eight:\n    {{long}} 8\n{% endif %}\n\n{{L}}q40f32:\n    // ymm0-3: acc\n    // ymm4-7: scales\n    // ymm13: 8\n    // ymm14: mask\n    // ymm15: b value\n    vbroadcastss    ymm14, dword ptr [{{offset}} {{L}}q40f32_mask]\n    vbroadcastss    ymm13, dword ptr [{{offset}} {{L}}q40f32_eight]\n\n{{L}}q40f32_outerloop:\n    // scales\n    vmovaps         xmm4, [rax]\n    vmovaps         xmm5, [rax + 16]\n    vmovaps         xmm6, [rax + 32]\n    vmovaps         xmm7, [rax + 48]\n    vcvtph2ps       ymm4, xmm4\n    vcvtph2ps       ymm5, xmm5\n    vcvtph2ps       ymm6, xmm6\n    vcvtph2ps       ymm7, xmm7\n    add             rax, 64\n\n    mov             rdx, 32\n\n{{L}}q40f32_innerloop:\n    vbroadcastss    ymm15, dword ptr [rcx]\n    vmovaps         xmm8, [rax]            // 32 nibbles\n\n    vpand           xmm10, xmm8, xmm14      // 16 bytes\n\n    vpmovzxbd       ymm9, xmm10            // 8 u32\n\n    vpermilpd       xmm10, xmm10, 1        // swap 64bit halves\n    vpmovzxbd       ymm10, xmm10            // 8 u32\n\n    vpsrlw          xmm8, xmm8, 4\n    vpand           xmm12, xmm8, xmm14      // 16 bytes\n    vpmovzxbd       ymm11, xmm12            // 8 u32\n    vpermilpd       xmm12, xmm12, 1        // swap 64bit halves\n    vpmovzxbd       ymm12, xmm12            // 8 u32\n\n    vpsubd          ymm9, ymm9, ymm13\n    vpsubd          ymm10, ymm10, ymm13\n    vpsubd          ymm11, ymm11, ymm13\n    vpsubd          ymm12, ymm12, ymm13\n\n    vcvtdq2ps       ymm9, ymm9\n    vcvtdq2ps       ymm10, ymm10\n    vcvtdq2ps       ymm11, ymm11\n    vcvtdq2ps       ymm12, ymm12\n\n    vmulps          ymm9, ymm9, ymm4\n    vmulps          ymm10, ymm10, ymm5\n    vmulps          ymm11, ymm11, ymm6\n    vmulps          ymm12, ymm12, ymm7\n\n    vfmadd231ps     ymm0, ymm15, ymm9\n    vfmadd231ps     ymm1, ymm15, ymm10\n    vfmadd231ps     ymm2, ymm15, ymm11\n    vfmadd231ps     ymm3, ymm15, ymm12\n\n    add             rax, 16\n    add             rcx, 4\n    sub             rdx, 1\n    jnz             {{L}}q40f32_innerloop\n\n    sub             rbx, 32\n    jnz             {{L}}q40f32_outerloop\n\n    jmp             {{L}}non_linear_loop\n\n{{L}}q40f16:\n    // ymm0-3: acc\n    // ymm4-7: scales\n    // ymm13: 8\n    // ymm14: mask\n    // ymm15: b value\n    vbroadcastss    ymm14, dword ptr [{{offset}} {{L}}q40f32_mask]\n    vbroadcastss    ymm13, dword ptr [{{offset}} {{L}}q40f32_eight]\n\n{{L}}q40f16_outerloop:\n    // scales\n    vmovaps         xmm4, [rax]\n    vmovaps         xmm5, [rax + 16]\n    vmovaps         xmm6, [rax + 32]\n    vmovaps         xmm7, [rax + 48]\n    vcvtph2ps       ymm4, xmm4\n    vcvtph2ps       ymm5, xmm5\n    vcvtph2ps       ymm6, xmm6\n    vcvtph2ps       ymm7, xmm7\n    add             rax, 64\n\n    mov             rdx, 32\n\n{{L}}q40f16_innerloop:\n    vpbroadcastw    ymm15, word ptr [rcx]\n    vcvtph2ps       ymm15, xmm15\n\n    vmovaps         xmm8, [rax]            // 32 nibbles\n\n    vpand           xmm10, xmm8, xmm14      // 16 bytes\n\n    vpmovzxbd       ymm9, xmm10            // 8 u32\n\n    vpermilpd       xmm10, xmm10, 1        // swap 64bit halves\n    vpmovzxbd       ymm10, xmm10            // 8 u32\n\n    vpsrlw          xmm8, xmm8, 4\n    vpand           xmm12, xmm8, xmm14      // 16 bytes\n    vpmovzxbd       ymm11, xmm12            // 8 u32\n    vpermilpd       xmm12, xmm12, 1        // swap 64bit halves\n    vpmovzxbd       ymm12, xmm12            // 8 u32\n\n    vpsubd          ymm9, ymm9, ymm13\n    vpsubd          ymm10, ymm10, ymm13\n    vpsubd          ymm11, ymm11, ymm13\n    vpsubd          ymm12, ymm12, ymm13\n\n    vcvtdq2ps       ymm9, ymm9\n    vcvtdq2ps       ymm10, ymm10\n    vcvtdq2ps       ymm11, ymm11\n    vcvtdq2ps       ymm12, ymm12\n\n    vmulps          ymm9, ymm9, ymm4\n    vmulps          ymm10, ymm10, ymm5\n    vmulps          ymm11, ymm11, ymm6\n    vmulps          ymm12, ymm12, ymm7\n\n    vfmadd231ps     ymm0, ymm15, ymm9\n    vfmadd231ps     ymm1, ymm15, ymm10\n    vfmadd231ps     ymm2, ymm15, ymm11\n    vfmadd231ps     ymm3, ymm15, ymm12\n\n    add             rax, 16\n    add             rcx, 2\n    sub             rdx, 1\n    jnz             {{L}}q40f16_innerloop\n\n    sub             rbx, 32\n    jnz             {{L}}q40f16_outerloop\n\n    jmp             {{L}}non_linear_loop\n\n{{L}}f16f16:\n{{align}} 16\n    vpbroadcastw    ymm15, word ptr [rcx]\n\n    vmovaps     xmm4, [rax]\n    vmovaps     xmm5, [rax + 16]\n    vmovaps     xmm6, [rax + 32]\n    vmovaps     xmm7, [rax + 48]\n\n    vcvtph2ps       ymm15, xmm15\n    vcvtph2ps       ymm4, xmm4\n    vcvtph2ps       ymm5, xmm5\n    vcvtph2ps       ymm6, xmm6\n    vcvtph2ps       ymm7, xmm7\n\n    vfmadd231ps     ymm0, ymm15, ymm4\n    vfmadd231ps     ymm1, ymm15, ymm5\n    vfmadd231ps     ymm2, ymm15, ymm6\n    vfmadd231ps     ymm3, ymm15, ymm7\n\n    add             rcx, 2\n\tadd             rax, 64\n    sub             rbx, 1\n    jnz             {{L}}f16f16\n\n    jmp             {{L}}non_linear_loop\n\n{{L}}f32f16:\n{{align}} 16\n    vpbroadcastw    ymm15, word ptr [rcx]\n\n    vmovaps     ymm4, [rax]\n    vmovaps     ymm5, [rax + 32]\n    vmovaps     ymm6, [rax + 64]\n    vmovaps     ymm7, [rax + 96]\n\n    vcvtph2ps       ymm15, xmm15\n\n    vfmadd231ps     ymm0, ymm15, ymm4\n    vfmadd231ps     ymm1, ymm15, ymm5\n    vfmadd231ps     ymm2, ymm15, ymm6\n    vfmadd231ps     ymm3, ymm15, ymm7\n\n    add             rcx, 2\n\tadd             rax, 128\n    sub             rbx, 1\n    jnz             {{L}}f32f16\n\n    jmp             {{L}}non_linear_loop\n\n{{L}}f16f32:\n{{align}} 16\n    vbroadcastss    ymm15,  dword ptr [rcx]\n\n    vmovaps     xmm4, [rax]\n    vmovaps     xmm5, [rax + 16]\n    vmovaps     xmm6, [rax + 32]\n    vmovaps     xmm7, [rax + 48]\n\n    vcvtph2ps       ymm4, xmm4\n    vcvtph2ps       ymm5, xmm5\n    vcvtph2ps       ymm6, xmm6\n    vcvtph2ps       ymm7, xmm7\n\n    vfmadd231ps     ymm0, ymm15, ymm4\n    vfmadd231ps     ymm1, ymm15, ymm5\n    vfmadd231ps     ymm2, ymm15, ymm6\n    vfmadd231ps     ymm3, ymm15, ymm7\n\n    add             rcx, 4\n\tadd             rax, 64\n    sub             rbx, 1\n    jnz             {{L}}f16f32\n\n    jmp             {{L}}non_linear_loop\n\n\n{% include \"fma_mmm_f32_scalars.tmpliq\" from:0, to:3, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_rows.tmpliq\" mr:32, from:0, to:3, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_cols.tmpliq\" mr:32, from:0, to:3, type:\"f32\" %}\n{% include \"fma_mmm_load_tile.tmpliq\" from:0, to:3 %}\n\n{{L}}add_unicast:\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n\n\tcmp rsi, 4\n\tjne {{L}}add_unicast_generic\n\n    {% for row in (0..3) %}\n        vaddps ymm{{row}}, ymm{{row}}, [ r10 + {{row|times:32}} ]\n    {% endfor %}\n    jmp    {{L}}non_linear_loop\n\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_unicast_generic:\n    mov     eax,    0\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n\n{% for i in (0..3) %}\n    vpcmpeqd        ymm15,  ymm15, ymm15\n    vgatherdps      ymm12,  [ r10 + ymm14 ], ymm15\n\n    vaddps          ymm{{i}},   ymm{{i}},   ymm12\n    lea             r10, [ r10 + rsi * 8 ]\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vbroadcastss    ymm14, dword ptr [rbx]\n\n{% for i in (0..3) %}\n    vmovups         ymm12,  [rax + {{i|times:32}}]\n    vfmadd231ps     ymm{{i}}, ymm12, ymm14\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     r11,    [rdi + 32]          // item size\n\n    cmp     r11, 2\n    je      {{L}}store_f16\n\n\tcmp rsi, 4\n\tjne {{L}}store_generic\n\n\t{% for row in (0..3) %}\n        vmovups [r8 + {{row|times:32}}], ymm{{row}}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_generic:\n\n    {% for vec in (0..3) %}\n        {% for half in (0..1) %}\n            {% if half == 0 %}\n                movaps xmm9, xmm{{vec}}\n            {% else %}\n                vperm2f128 ymm9, ymm{{vec}}, ymm{{vec}}, 1\n            {% endif %}\n            {% for row in (0..3) %}\n                vextractps  dword ptr [r8], xmm9, {{row}}\n                add         r8, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}store_f16:\n\n    vcvtps2ph   xmm0, ymm0, 0\n    vcvtps2ph   xmm1, ymm1, 0\n    vcvtps2ph   xmm2, ymm2, 0\n    vcvtps2ph   xmm3, ymm3, 0\n\n    cmp         rsi, 2\n\tjne {{L}}store_generic_f16\n\n\t{% for row in (0..3) %}\n        vmovups [r8 + {{row|times:16}}], xmm{{row}}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n    \n{{L}}store_generic_f16:\n\n    {% for vec in (0..3) %}\n        {% for row in (0..7) %}\n            pextrw      word ptr [r8], xmm{{vec}}, {{row}}\n            add         r8, rsi\n        {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" type:\"f32\", size:\"32x1\", suffix:suffix, G:G, L:L %}\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_32x3.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n/* mmm 16 x 5:\n\n    ymm0 ymm4 ymm8\n    ymm1 ymm5 ymm9\n    ymm2 ymm6 ymm10\n    ymm3 ymm7 ymm11\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" type:\"f32\", size:\"32x3\", suffix:suffix, G:G %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rbx,    [rdi + 8]    // k\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     r8,    [rdi + 32]   // packing\n\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n    cmp     r8, 1\n    jz      {{L}}main_loop_packed_packed_f32_f16\n\n    cmp     r8, 2\n    jz      {{L}}main_loop_packed_packed_f16_f32\n\n    cmp     r8, 3\n    jz      {{L}}main_loop_packed_packed_f16_f16\n\n{{L}}main_loop_packed_packed:\n    {% include \"4x3/packed_packed_loop1/avx.tmpli\" %}\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed_f32_f16:\n\t// Load col of A\n\tvmovaps\t\t\tymm12,\t[rax]\n\n\t// Fill 3 cols of B\n    vpbroadcastw    xmm13,  word ptr [rcx + 0]\n    vpbroadcastw    xmm14,  word ptr [rcx + 2]\n    vpbroadcastw    xmm15,  word ptr [rcx + 4]\n\n    vcvtph2ps       ymm13, xmm13\n    vcvtph2ps       ymm14, xmm14\n    vcvtph2ps       ymm15, xmm15\n\n\t// N.B. Stepping cols in inner loop\n\tvfmadd231ps\t\tymm0,\tymm12, ymm13\n\tvfmadd231ps\t\tymm4,\tymm12, ymm14\n\tvfmadd231ps\t\tymm8,\tymm12, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax+32]\n\n\tvfmadd231ps\t\tymm1,\tymm12, ymm13\n\tvfmadd231ps\t\tymm5,\tymm12, ymm14\n\tvfmadd231ps\t\tymm9,\tymm12, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax+64]\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm13\n\tvfmadd231ps\t\tymm6,\tymm12, ymm14\n\tvfmadd231ps\t\tymm10,\t ymm12, ymm15\n\n\tvmovaps\t\t\tymm12,\t[rax+96]\n\n\tvfmadd231ps\t\tymm3,\tymm12, ymm13\n\tvfmadd231ps\t\tymm7,\tymm12, ymm14\n\tvfmadd231ps\t\tymm11,\tymm12, ymm15\n\n    add             rcx,    6\n    add             rax,    128\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed_f32_f16\n\n    jmp             {{L}}non_linear_loop\n    \n{{L}}main_loop_packed_packed_f16_f32:\n\t// Load col of A\n\tvmovaps\t\t\txmm12,\t[rax]\n\n\t// Fill 3 cols of B\n    vbroadcastss    ymm13, dword ptr [rcx + 0]\n    vbroadcastss    ymm14, dword ptr [rcx + 4]\n    vbroadcastss    ymm15, dword ptr [rcx + 8]\n\n    vcvtph2ps       ymm12, xmm12\n\n\tvfmadd231ps\t\tymm0,\tymm12, ymm13\n\tvfmadd231ps\t\tymm4,\tymm12, ymm14\n\tvfmadd231ps\t\tymm8,\tymm12, ymm15\n\n\tvmovaps\t\t\txmm12,\t[rax+16]\n    vcvtph2ps       ymm12, xmm12\n\n\tvfmadd231ps\t\tymm1,\tymm12, ymm13\n\tvfmadd231ps\t\tymm5,\tymm12, ymm14\n\tvfmadd231ps\t\tymm9,\tymm12, ymm15\n\n\tvmovaps\t\t\txmm12,\t[rax+32]\n    vcvtph2ps       ymm12, xmm12\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm13\n\tvfmadd231ps\t\tymm6,\tymm12, ymm14\n\tvfmadd231ps\t\tymm10,\t ymm12, ymm15\n\n\tvmovaps\t\t\txmm12,\t[rax+48]\n    vcvtph2ps       ymm12, xmm12\n\n\tvfmadd231ps\t\tymm3,\tymm12, ymm13\n\tvfmadd231ps\t\tymm7,\tymm12, ymm14\n\tvfmadd231ps\t\tymm11,\tymm12, ymm15\n\n    add             rcx,    12\n    add             rax,    64\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed_f16_f32\n\n    jmp             {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed_f16_f16:\n\t// Load col of A\n\tvmovaps\t\t\txmm12,\t[rax]\n\n\t// Fill 3 cols of B\n    vpbroadcastw    xmm13,  word ptr [rcx + 0]\n    vpbroadcastw    xmm14,  word ptr [rcx + 2]\n    vpbroadcastw    xmm15,  word ptr [rcx + 4]\n\n    vcvtph2ps       ymm12, xmm12\n    vcvtph2ps       ymm13, xmm13\n    vcvtph2ps       ymm14, xmm14\n    vcvtph2ps       ymm15, xmm15\n\n\tvfmadd231ps\t\tymm0,\tymm12, ymm13\n\tvfmadd231ps\t\tymm4,\tymm12, ymm14\n\tvfmadd231ps\t\tymm8,\tymm12, ymm15\n\n\tvmovaps\t\t\txmm12,\t[rax+16]\n    vcvtph2ps       ymm12, xmm12\n\n\tvfmadd231ps\t\tymm1,\tymm12, ymm13\n\tvfmadd231ps\t\tymm5,\tymm12, ymm14\n\tvfmadd231ps\t\tymm9,\tymm12, ymm15\n\n\tvmovaps\t\t\txmm12,\t[rax+32]\n    vcvtph2ps       ymm12, xmm12\n\n\tvfmadd231ps\t\tymm2,\tymm12, ymm13\n\tvfmadd231ps\t\tymm6,\tymm12, ymm14\n\tvfmadd231ps\t\tymm10,\t ymm12, ymm15\n\n\tvmovaps\t\t\txmm12,\t[rax+48]\n    vcvtph2ps       ymm12, xmm12\n\n\tvfmadd231ps\t\tymm3,\tymm12, ymm13\n\tvfmadd231ps\t\tymm7,\tymm12, ymm14\n\tvfmadd231ps\t\tymm11,\tymm12, ymm15\n\n    add             rcx,    6\n    add             rax,    64\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed_f16_f16\n\n    jmp             {{L}}non_linear_loop\n\n// NON LINEAR / ADDC\n\n{% include \"fma_mmm_f32_scalars.tmpliq\" from:0, to:11, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_rows.tmpliq\" mr:32, from:0, to:11, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_cols.tmpliq\" mr:32, from:0, to:11, type:\"f32\" %}\n{% include \"fma_mmm_load_tile.tmpliq\" from:0, to:11 %}\n\n{{L}}add_unicast:\n    mov     r8,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    cmp     rsi, 4\n    jne     {{L}}unicast_generic\n\n    lea             r9,  [ r8 + rbx ]\n    lea             r10, [ r9 + rbx]\n    lea             r11, [ r10 + rbx ]\n\n{% for col in (0..2) %}\n    {% for row in (0..3) %}\n        vmovups     ymm12, [ r{{col|plus:8}} ]\n        add         r{{col|plus:8}}, 32\n        vaddps      ymm{{col|times:4|plus:row}}, ymm{{col|times:4|plus:row}}, ymm12\n    {% endfor %}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}unicast_generic:\n    mov     eax,    0\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n//  mov r12, [0]\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n\n    lea             r9,  [ r8 + rsi * 8 ]\n    lea             r10, [ r9 + rsi * 8 ]\n    lea             r11, [ r10 + rsi * 8 ]\n\n{% for col in (0..2) %}\n   {% for row in (0..3) %}\n      vpcmpeqd      ymm15,  ymm15, ymm15\n      vgatherdps    ymm12,  [ r{{row|plus:8}} + ymm14 ], ymm15\n      add           r{{row|plus:8}}, rbx\n      vaddps        ymm{{col|times:4|plus:row}}, ymm{{col|times:4|plus:row}}, ymm12\n   {% endfor %}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vbroadcastss    ymm13, dword ptr [rbx]\n    vbroadcastss    ymm14, dword ptr [rbx + 4]\n    vbroadcastss    ymm15, dword ptr [rbx + 8]\n{% for i in (0..3) %}\n    vmovups         ymm12,  [rax + {{i|times:32}}]\n    vfmadd231ps     ymm{{0|plus:i}}, ymm12, ymm13\n    vfmadd231ps     ymm{{4|plus:i}}, ymm12, ymm14\n    vfmadd231ps     ymm{{8|plus:i}}, ymm12, ymm15\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n    mov     r11,    [rdi + 32]          // item size\n\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n\n    cmp     r11, 2\n    je      {{L}}store_f16\n\n    cmp         rsi, 4\n    jne         {{L}}store_strides_generic\n\n    {% for col in (0..2) %}\n        {% for row in (0..3) %}\n            vmovups ymmword ptr [r{{col|plus:8}}], ymm{{col|times:4|plus:row}}\n            add     r{{col|plus:8}}, 32\n       {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_strides_generic:\n\n    {% for col in (0..2) %}\n       {% for row in (0..3) %}\n           {% for i in (0..3) %}\n                vextractps  dword ptr [r{{col | plus: 8}}], xmm{{col | times:4 | plus:row}}, {{i}}\n                add         r{{col | plus: 8}}, rsi\n           {% endfor %}\n           vperm2f128  ymm{{col | times:4 | plus:row}}, ymm{{col | times:4 | plus:row}}, ymm{{col | times:4 | plus:row}}, 1\n           {% for i in (0..3) %}\n                vextractps  dword ptr [r{{col | plus: 8}}], xmm{{col | times:4|plus:row}}, {{i}}\n                add         r{{col | plus: 8}}, rsi\n           {% endfor %}\n       {% endfor %}\n    {% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_f16:\n\n    {% for reg in (0..11) %}\n        vcvtps2ph   xmm{{reg}}, ymm{{reg}}, 0\n    {% endfor %}\n\n    cmp         rsi, 2\n\tjne {{L}}store_generic_f16\n\n    {% for col in (0..2) %}\n        {% for row in (0..3) %}\n            vmovups [r{{col|plus:8}} + {{row|times:16}}], xmm{{col|times:4|plus:row}}\n        {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n    \n{{L}}store_generic_f16:\n    {% for col in (0..2) %}\n        {% for vec in (0..3) %}\n            {% for row in (0..7) %}\n                pextrw  word ptr [r{{col|plus:8}}], xmm{{col|times:4|plus:vec}}, {{row}}\n                add         r{{col|plus:8}}, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" type:\"f32\", size:\"32x3\", suffix:suffix, G:G, L:L %}\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_40x2.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n/* mmm 40 x 5:\n\n    ymm0 ymm5\n    ymm1 ymm6\n    ymm2 ymm7\n    ymm3 ymm8\n    ymm4 ymm9\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" type:\"f32\", size:\"40x2\", suffix:suffix, G:G %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n    {% include \"5x2/packed_packed_loop1/avx.tmpli\" %}\n\n    dec             rbx\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n// NON LINEAR / ADDC\n\n{% include \"fma_mmm_f32_scalars.tmpliq\" from:0, to:9, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_rows.tmpliq\" mr:40, from:0, to:9, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_cols.tmpliq\" mr:40, from:0, to:9, type:\"f32\" %}\n{% include \"fma_mmm_load_tile.tmpliq\" from:0, to:9 %}\n\n{{L}}add_unicast:\n    mov     r8,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    cmp rsi, 4\n    jne {{L}}unicast_generic\n\n    lea             r9,  [ r8 + rbx ]\n    lea             r10, [ r9 + rbx]\n    lea             r11, [ r10 + rbx ]\n    lea             r12, [ r11 + rbx ]\n\n\n{% for col in (0..1) %}\n    {% for row in (0..4) %}\n        vmovups ymm12,  [ r{{col|plus:8}} ]\n        add\t\tr{{col|plus:8}}, 32\n        vaddps \tymm{{col|times:5|plus:row}}, ymm{{col|times:5|plus:row}}, ymm12\n    {% endfor %}\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}unicast_generic:\n    mov     eax,    0\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n\n    lea             r9,  [ r8 + rsi * 8]\n    lea             r10, [ r9 + rsi * 8]\n    lea             r11, [ r10 + rsi * 8]\n    lea             r12, [ r11 + rsi * 8]\n\n{% for col in (0..1) %}\n   {% for row in (0..4) %}\n      vpcmpeqd        ymm15, ymm15, ymm15\n      vgatherdps      ymm12, [ r{{row|plus:8}} + ymm14 ], ymm15\n      add \t\t\t  r{{row|plus:8}}, \trbx\n      vaddps \t\t  ymm{{col|times:5|plus:row}}, ymm{{col|times:5|plus:row}}, ymm12\n   {% endfor %}\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vbroadcastss    ymm10, dword ptr [rbx]\n    vbroadcastss    ymm11, dword ptr [rbx + 4]\n{% for i in (0..4) %}\n    vmovups         ymm12,  [rax + {{i|times:32}}]\n    vfmadd231ps     ymm{{0|plus:i}}, ymm12, ymm10\n    vfmadd231ps     ymm{{5|plus:i}}, ymm12, ymm11\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    lea     r9,     [ r8  +     rbx ]\n    lea     r10,    [ r8  + 2 * rbx ]\n    lea     r11,    [ r10 +     rbx ]\n    lea     r12,    [ r10 + 2 * rbx ]\n\n    cmp         rsi, 4\n    jne         {{L}}store_strides_generic\n\n    {% for col in (0..1) %}\n       {% for row in (0..4) %}\n            vmovups ymmword ptr [r{{col|plus:8}}], ymm{{col|times:5|plus:row}}\n            add \tr{{col|plus:8}}, 32\n       {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_strides_generic:\n    {% for col in (0..1) %}\n       {% for row in (0..4) %}\n           {% for i in (0..3) %}\n                vextractps  dword ptr [r{{col | plus: 8}}], xmm{{col | times:5 | plus:row}}, {{i}}\n                add         r{{col | plus: 8}}, rsi\n           {% endfor %}\n           vperm2f128  ymm{{col | times:5 | plus:row}}, ymm{{col | times:5 | plus:row}}, ymm{{col | times:5 | plus:row}}, 1\n           {% for i in (0..3) %}\n                vextractps  dword ptr [r{{col | plus: 8}}], xmm{{col | times:5|plus:row}}, {{i}}\n                add         r{{col | plus: 8}}, rsi\n           {% endfor %}\n       {% endfor %}\n    {% endfor %}\n    jmp     {{L}}non_linear_loop\n\n{% include \"postamble.tmpliq\" type:\"f32\", size:\"40x2\", suffix:suffix, G:G, L:L %}\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_64x1.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 64 x 1\n\n    ymm0\n    ymm1\n    ...\n    ymm8\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" type:\"f32\", size:\"64x1\", suffix:suffix, G:G %}\n\n{{L}}clear:\n    vzeroall\n    jmp     {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rcx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rbx,    [rdi + 8]    // k\n    test    rbx,    rbx\n    jz      {{L}}non_linear_loop\n\n\ttest rbx, 1\n\tjz {{L}}main_loop_packed_packed\n\t{% include \"8x1/packed_packed_loop1/avx.tmpli\" %}\n\n    dec             rbx\n    jz              {{L}}non_linear_loop\n\n{{align}} 16\n{{L}}main_loop_packed_packed:\n\t{% include \"8x1/packed_packed_loop1/avx-unroll.tmpli\" %}\n\n    sub             rbx, 2\n    jnz             {{L}}main_loop_packed_packed\n\n    jmp             {{L}}non_linear_loop\n\n{% include \"fma_mmm_f32_scalars.tmpliq\" from:0, to:7, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_rows.tmpliq\" mr:64, from:0, to:7, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_cols.tmpliq\" mr:64, from:0, to:7, type:\"f32\" %}\n{% include \"fma_mmm_load_tile.tmpliq\" from:0, to:7 %}\n\n{{L}}add_unicast:\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n\n\tcmp rsi, 4\n\tjne {{L}}add_unicast_generic\n\n    {% for row in (0..7) %}\n        vaddps ymm{{row}}, ymm{{row}}, [ r10 + {{row|times:32}} ]\n    {% endfor %}\n    jmp    {{L}}non_linear_loop\n\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_unicast_generic:\n    mov     eax,    0\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n\n{% for i in (0..7) %}\n    vpcmpeqd        ymm15,  ymm15, ymm15\n    vgatherdps      ymm12,  [ r10 + ymm14 ], ymm15\n\n    vaddps          ymm{{i}},   ymm{{i}},   ymm12\n    lea             r10, [ r10 + rsi * 8 ]\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vbroadcastss    ymm14, dword ptr [rbx]\n\n{% for i in (0..7) %}\n    vmovups         ymm12,  [rax + {{i|times:32}}]\n    vfmadd231ps     ymm{{i}}, ymm12, ymm14\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n\n\tcmp rsi, 4\n\tjne {{L}}store_generic\n\n\t{% for row in (0..7) %}\n        vmovups [r8 + {{row|times:32}}], ymm{{row}}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n{{L}}store_generic:\n\n    {% for vec in (0..7) %}\n        {% for half in (0..1) %}\n            {% if half == 0 %}\n                movaps xmm9, xmm{{vec}}\n            {% else %}\n                vperm2f128 ymm9, ymm{{vec}}, ymm{{vec}}, 1\n            {% endif %}\n            {% for row in (0..3) %}\n                vextractps  dword ptr [r8], xmm9, {{row}}\n                add         r8, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n\n{% include \"postamble.tmpliq\" type:\"f32\", size:\"64x1\", suffix:suffix, G:G, L:L %}\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_8x8.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\n/* mmm 16 x 6:\n\n    ymm0 ymm2 ymm4 ymm6 ymm8 ymm10\n    ymm1 ymm3 ymm5 ymm7 ymm9 ymm11\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n*/\n{% endcomment %}\n\n{% include \"preamble.tmpliq\" type:\"f32\", size:\"8x8\", suffix:suffix, G:G %}\n\n{{L}}clear:\n    vzeroall\n    jmp             {{L}}non_linear_loop\n\n{{L}}add_mat_mul:\n    mov     rbx,    [rdi + 24]   // B\n    mov     rax,    [rdi + 16]   // A\n\n    mov     rcx,    [rdi + 8]    // k\n    test    rcx,    rcx\n    jz      {{L}}non_linear_loop\n\n{{L}}main_loop_packed_packed:\n    vmovaps         ymm12,  [rax]\n\n    {% for i in (0..7) %}\n        vbroadcastss    ymm14, dword ptr [rbx + {{i}} * 4]\n        vfmadd231ps     ymm{{i}}, ymm12, ymm14\n    {% endfor %}\n\n    add             rax,    32\n    add             rbx,    32\n    dec             rcx\n    jnz             {{L}}main_loop_packed_packed\n    jmp             {{L}}non_linear_loop\n\n// NON LINEAR / ADDC\n\n{% include \"fma_mmm_f32_scalars.tmpliq\" from:0, to:7, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_rows.tmpliq\" mr:8, from:0, to:7, type:\"f32\" %}\n{% include \"fma_mmm_f32_per_cols.tmpliq\" mr:8, from:0, to:7, type:\"f32\" %}\n{% include \"fma_mmm_load_tile.tmpliq\" from:0, to:7 %}\n\n{{L}}add_unicast:\n\n    mov     r10,    [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    mov     eax,    0\n{% for i in (0..3) %}\n    pinsrd  xmm14, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n{% for i in (0..3) %}\n    pinsrd  xmm15, eax, {{i}}\n    add     eax,    esi\n{% endfor %}\n\n    vperm2f128      ymm14,  ymm14, ymm15,         32 // ymm14 <- xmm14::xmm15\n\n{% for i in (0..7) %}\n    vpcmpeqd        ymm15,  ymm15, ymm15\n    vgatherdps      ymm12,  [ r10 + ymm14 ],      ymm15\n    add     r10, rbx\n    vaddps          ymm{{i}},   ymm{{i}},   ymm12\n{% endfor %}\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}add_row_col_products:\n    mov             rax, [ rdi + 8 ]\n    mov             rbx, [ rdi + 16 ]\n\n    vmovups         ymm12,  [rax]\n\n{% for i in (0..7) %}\n    vbroadcastss    ymm14, dword ptr [rbx + {{i|times:4}} ]\n    vfmadd231ps     ymm{{i}},   ymm12, ymm14\n{% endfor %}\n    jmp    {{L}}non_linear_loop\n\n{{L}}store:\n    mov     r8,     [rdi + 8]           // c ptr\n    mov     rsi,    [rdi + 16]          // row stride\n    mov     rbx,    [rdi + 24]          // col stride\n\n    // tops of cols\n    lea     r9,     [ r8 + rbx ]\n    lea     r10,    [ r8 + 2 * rbx ]\n    lea     r12,    [ r8 + 4 * rbx ]\n    lea     r11,    [ r10 + rbx ]\n    lea     r13,    [ r12 + rbx ]\n    lea     r14,    [ r12 + 2 * rbx ]\n    lea     r15,    [ r13 + 2 * rbx ]\n\n    {% for quarter in (0..1) %}\n        {% if quarter != 0 %}\n            // move next four rows at top (xmm0,2,..10)\n            {% for r in (0..7) %}\n                vperm2f128  ymm{{r}},   ymm{{r}},   ymm{{r}},  {{quarter}}\n            {% endfor %}\n        {% endif %}\n        {% for row in (0..3) %}\n            {% for i in (0..7) %}\n                vextractps  dword ptr [r{{i | plus: 8}}], xmm{{i}}, {{row}}\n                add         r{{i | plus: 8}}, rsi\n            {% endfor %}\n        {% endfor %}\n    {% endfor %}\n\n    jmp     {{L}}non_linear_loop\n\n\n{% include \"postamble.tmpliq\" type:\"f32\", size:\"8x8\", suffix:suffix, G:G, L:L %}\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_per_cols.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_min\", op:\"vminps\", mr:mr, from:from, to:to, type:type%}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_max\", op:\"vmaxps\", mr:mr, from:from, to:to, type:type%}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_add\", op:\"vaddps\", mr:mr, from:from, to:to, type:type%}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_mul\", op:\"vmulps\", mr:mr, from:from, to:to, type:type%}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_sub\", op:\"vsubps\", from:from, to:to, type:type %}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_sub_flipped\", op:\"vsubps\", from:from, to:to, flipped: true, type:type%}\n\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_per_rows.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_min\", op:\"vminps\", mr:mr, from:from, to:to, type: type%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_max\", op:\"vmaxps\", mr:mr, from:from, to:to, type: type%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_add\", op:\"vaddps\", mr:mr, from:from, to:to, type: type%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_mul\", op:\"vmulps\", mr:mr, from:from, to:to, type: type%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_sub\", op:\"vsubps\", from:from, to:to, type: type%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_sub_flipped\", op:\"vsubps\", from:from, to:to, flipped: true, type: type%}\n\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_f32_scalars.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_min\", op:\"vminps\", from:from, to:to, type:type%}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_max\", op:\"vmaxps\", from:from, to:to, type:type%}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_add\", op:\"vaddps\", from:from, to:to, type:type%}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_mul\", op:\"vmulps\", from:from, to:to, type:type%}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_sub\", op:\"vsubps\", from:from, to:to, type:type%}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_sub_flipped\", op:\"vsubps\", from:from, to:to, flipped: true, type:type%}\n\n{{L}}leaky_relu:\n    // can only use ymm12 to ymm15\n    // ymm15 <- alpha\n    {% if type == \"f32\" %}\n        vbroadcastss    ymm15, dword ptr [rdi + 8]\n    {% else %}\n        pinsrw          xmm15, word ptr [rdi + 8], 0\n        vcvtph2ps       ymm15, xmm15\n        vbroadcastss    ymm15, xmm15\n    {% endif %}\n\n    // ymm14 <- all zero\n    vpxor           ymm14, ymm14, ymm14\n\n    {% for reg in (from..to) %}\n        // ymm12 <- alpha * x\n        vmulps      ymm12, ymm{{reg}}, ymm15\n        vcmpps     ymm13, ymm14, ymm{{reg}}, 1 // 1 means LT\n        vblendvps   ymm{{reg}}, ymm12, ymm{{reg}}, ymm13\n    {% endfor %}\n    // select muled of orginal\n\n    jmp    {{L}}non_linear_loop\n\n{{L}}q_scale:\n{{L}}q_shl:\n{{L}}q_shr:\n    jmp {{L}}unsupported\n\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_i32_per_cols.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_min\", op:\"vpminsd\", mr:mr, from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_max\", op:\"vpmaxsd\", mr:mr, from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_add\", op:\"vpaddd\", mr:mr, from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_mul\", op:\"vpmulld\", mr:mr, from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_sub\", op:\"vpsubd\", from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_col.tmpliq\" label:\"per_col_sub_flipped\", op:\"vpsubd\", from:from, to:to, flipped: true, type:\"i32\"%}\n\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_i32_per_rows.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_min\", op:\"vpminsd\", mr:mr, from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_max\", op:\"vpmaxsd\", mr:mr, from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_add\", op:\"vpaddd\", mr:mr, from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_mul\", op:\"vpmulld\", mr:mr, from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_sub\", op:\"vpsubd\", from:from, to:to, type:\"i32\"%}\n{% include \"fma_mmm_ymm_per_row.tmpliq\" label:\"per_row_sub_flipped\", op:\"vpsubd\", from:from, to:to, flipped: true, type:\"i32\"%}\n\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_i32_scalars.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_min\", op:\"vpminsd\", from:from, to:to, type:\"i32\" %}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_max\", op:\"vpmaxsd\", from:from, to:to, type:\"i32\" %}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_mul\", op:\"vpmulld\", from:from, to:to, type:\"i32\" %}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_add\", op:\"vpaddd\", from:from, to:to, type:\"i32\" %}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_sub\", op:\"vpsubd\", from:from, to:to, type:\"i32\" %}\n{% include \"fma_mmm_ymm_scalar.tmpliq\" label:\"scalar_sub_flipped\", op:\"vpsubd\", from:from, to:to, flipped: true, type:\"i32\" %}\n\n{{L}}leaky_relu:\n    // can only use ymm12 to ymm15\n    // ymm15 <- alpha\n    vbroadcastss    ymm15, dword ptr [rdi + 8]\n    // ymm14 <- all zero\n    vpxor          ymm14, ymm14, ymm14\n\n    {% for reg in (from..to) %}\n        vpmulld     ymm12, ymm{{reg}}, ymm15\n        vpcmpgtd    ymm13, ymm14, ymm{{reg}}\n        vblendvps   ymm{{reg}}, ymm{{reg}}, ymm12, ymm13\n    {% endfor %}\n\n    jmp    {{L}}non_linear_loop\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_load_tile.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}load_tile:\n    mov          r8, [rdi + 8]\n    {% for reg in (from..to) %}\n        vmovups         ymm{{reg}}, ymmword ptr [r8 + {{ reg|minus:from|times:32 }}]\n    {% endfor %}\n\n    jmp    {{L}}non_linear_loop\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_ymm_per_col.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}{{label}}:\n    mov             rax, [ rdi + 8 ]\n\n{% capture mr_over_8 %}{{ mr | divided_by: 8}}{%endcapture%}\n{% capture mr_over_8_min_1 %}{{ mr | divided_by: 8 | minus: 1}}{%endcapture%}\n\n{%capture tmp%}{{to | plus: 1 }}{%endcapture%}\n\n{%capture cols%}{{to | plus: 1| minus:from| divided_by:mr_over_8}}{%endcapture%}\n{%capture cols_min_1%}{{to | plus: 1| minus:from| divided_by:mr_over_8|minus:1}}{%endcapture%}\n\n\n{% for right in (0..cols_min_1) %}\n    {% if type == \"f16\" %} \n        pinsrw          xmm{{tmp}}, word ptr [ rax ], 0\n        add             rax, 2\n        vcvtph2ps      ymm{{tmp}}, xmm{{tmp}}\n        vbroadcastss    ymm{{tmp}}, xmm{{tmp}}\n    {% else %}\n        vbroadcastss    ymm{{tmp}}, dword ptr [ rax ]\n        add             rax, 4\n    {% endif %}\n    {% for down in (0..mr_over_8_min_1) %}\n        {%capture acc%}{{mr_over_8|times:right|plus:from|plus:down}}{%endcapture%}\n        {% if flipped %}\n            {{op}} ymm{{acc}}, ymm{{acc}}, ymm{{tmp}}\n        {% else %}\n            {{op}} ymm{{acc}}, ymm{{tmp}}, ymm{{acc}}\n        {% endif %}\n    {% endfor %}\n{% endfor %}\n\n    jmp {{L}}non_linear_loop\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_ymm_per_row.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}{{label}}:\n    mov             rax, [ rdi + 8 ]\n\n{% capture mr_over_8 %}{{ mr | divided_by: 8}}{%endcapture%}\n{% capture mr_over_8_min_1 %}{{ mr | divided_by: 8 | minus: 1}}{%endcapture%}\n\n{% if type == \"f16\" %}\n    {% for ix in (0..mr_over_8_min_1) %}\n        vmovups         xmm{{to | plus: 1 | plus: ix}},  [rax + {{ix | times: 16}}]\n    {% endfor %}\n    {% for ix in (0..mr_over_8_min_1) %}\n        vcvtph2ps       ymm{{to | plus: 1 | plus: ix}}, xmm{{to | plus: 1 | plus: ix}}\n    {% endfor %}\n{% else %}\n    {% for ix in (0..mr_over_8_min_1) %}\n        vmovups         ymm{{to | plus: 1 | plus: ix}},  [rax + {{ix | times: 32}}]\n    {% endfor %}\n{% endif %}\n\n{% if flipped %}\n    {% for acc in (from..to) %}\n        {{op}} ymm{{acc}}, ymm{{acc}}, ymm{{ acc | modulo: mr_over_8 | plus: to | plus: 1 }}\n    {% endfor %}\n{% else %}\n    {% for acc in (from..to) %}\n        {{op}} ymm{{acc}}, ymm{{ acc | modulo: mr_over_8 | plus: to | plus: 1 }}, ymm{{acc}}\n    {% endfor %}\n{% endif %}\n\n    jmp {{L}}non_linear_loop\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_mmm_ymm_scalar.tmpliq",
    "content": "// vim: set syntax=asm :\n\n{{L}}{{label}}:\n    {% if type == \"f16\" %}\n        pinsrw          xmm12, word ptr [rdi + 8], 0\n        vcvtph2ps       ymm12, xmm12\n        vbroadcastss    ymm12, xmm12\n    {% else %}\n        vbroadcastss    ymm12, dword ptr [rdi + 8]\n    {% endif %}\n    \n    {% if flipped %}\n        {% for reg in (from..to) %}\n            {{op}}          ymm{{reg}}, ymm{{reg}}, ymm12\n        {% endfor %}\n    {% else %}\n        {% for reg in (from..to) %}\n            {{op}}          ymm{{reg}}, ymm12, ymm{{reg}}\n        {% endfor %}\n    {% endif %}\n\n    jmp    {{L}}non_linear_loop\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_sigmoid_f32.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n\n{% endcomment %}\n\n{% if msvc %}\n\n_text segment\nfma_sigmoid_f32_{{suffix}} proc\n\n{% else %}\n\n.intel_syntax noprefix\n.text\n.p2align 5\n.globl {{G}}fma_sigmoid_f32_{{suffix}}\n{{G}}fma_sigmoid_f32_{{suffix}}:\n.cfi_startproc\n{% endif %}\n\n    push        rbp\n    mov         rbp, rsp\n\n\n{% if family == \"windows\" %}\n// https://www.agner.org/optimize/calling_conventions.pdf xmm6-15 are not scratch\n// https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers\n    and rsp,-16\n    lea rsp,[rsp-160]\n    vmovaps [rsp], xmm6\n    vmovaps [rsp+16*1],xmm7\n    vmovaps [rsp+16*2],xmm8\n    vmovaps [rsp+16*3],xmm9\n    vmovaps [rsp+16*4],xmm10\n    vmovaps [rsp+16*5],xmm11\n    vmovaps [rsp+16*6],xmm12\n    vmovaps [rsp+16*7],xmm13\n    vmovaps [rsp+16*8],xmm14\n    vmovaps [rsp+16*9],xmm15\n\n    // move around arguments to mimick SysV rdi,rsi passing\n    push        rdi\n    push        rsi\n    mov         rdi, rcx\n    mov         rsi, rdx\n\n{% endif %}\n\n    push        rbx\n    push        r12\n    push        r13\n    push        r14\n    push        r15\n\n    sub         rsp, 8\n\n{% if family == \"unix\" %}\n// FIXME\n// .cfi_def_cfa_offset 64 \n{% endif %}\n\n    stmxcsr     [rsp + 4]\n{% if msvc %}\n    mov         rax, 1FC0h\n{% else %}\n    mov         rax, 0x1FC0\n{% endif %}\n    mov         [rsp], eax\n    ldmxcsr     [rsp]\n// ----------------------------------------------------------------------\n\n    cmp     rsi, 0\n    je      {{L}}done\n\n    cmp     rsi, 32\n    jl      {{L}}loop_1\n\n{{L}}loop_4:\n\n    vmovaps         ymm4, [rdi]\n    vmovaps         ymm5, [rdi + 32]\n    vmovaps         ymm6, [rdi + 64]\n    vmovaps         ymm7, [rdi + 96]\n\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_low]\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_high]\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_13]\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_11]\n\n    vmaxps          ymm4, ymm4, ymm0\n    vmaxps          ymm5, ymm5, ymm0\n    vmaxps          ymm6, ymm6, ymm0\n    vmaxps          ymm7, ymm7, ymm0\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_9]\n\n    vminps          ymm4, ymm4, ymm1\n    vminps          ymm5, ymm5, ymm1\n    vminps          ymm6, ymm6, ymm1\n    vminps          ymm7, ymm7, ymm1        // ymm4..7 <- x\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_alpha_7]\n\n    vmulps          ymm8, ymm4, ymm4\n    vmulps          ymm9, ymm5, ymm5\n    vmulps          ymm10, ymm6, ymm6\n    vmulps          ymm11, ymm7, ymm7        // ymm8..11 <- x^2\n\n    vmovaps         ymm12, ymm2\n    vmovaps         ymm13, ymm2\n    vmovaps         ymm14, ymm2\n    vmovaps         ymm15, ymm2\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_5]\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vfmadd132ps     ymm13, ymm3, ymm9\n    vfmadd132ps     ymm14, ymm3, ymm10\n    vfmadd132ps     ymm15, ymm3, ymm11\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_3]\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vfmadd132ps     ymm13, ymm0, ymm9\n    vfmadd132ps     ymm14, ymm0, ymm10\n    vfmadd132ps     ymm15, ymm0, ymm11\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_1]\n    vfmadd132ps     ymm12, ymm1, ymm8\n    vfmadd132ps     ymm13, ymm1, ymm9\n    vfmadd132ps     ymm14, ymm1, ymm10\n    vfmadd132ps     ymm15, ymm1, ymm11\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_beta_6]\n    vfmadd132ps     ymm12, ymm2, ymm8\n    vfmadd132ps     ymm13, ymm2, ymm9\n    vfmadd132ps     ymm14, ymm2, ymm10\n    vfmadd132ps     ymm15, ymm2, ymm11\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_beta_4]\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vfmadd132ps     ymm13, ymm3, ymm9\n    vfmadd132ps     ymm14, ymm3, ymm10\n    vfmadd132ps     ymm15, ymm3, ymm11\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_2]\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vfmadd132ps     ymm13, ymm0, ymm9\n    vfmadd132ps     ymm14, ymm0, ymm10\n    vfmadd132ps     ymm15, ymm0, ymm11\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_0]\n    vmulps          ymm4, ymm4, ymm12\n    vmulps          ymm5, ymm5, ymm13\n    vmulps          ymm6, ymm6, ymm14\n    vmulps          ymm7, ymm7, ymm15   // ymm4..7 <- num\n\n    vmovaps         ymm12, ymm1\n    vmovaps         ymm13, ymm1\n    vmovaps         ymm14, ymm1\n    vmovaps         ymm15, ymm1\n\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_half]\n    vfmadd132ps     ymm12, ymm2, ymm8\n    vfmadd132ps     ymm13, ymm2, ymm9\n    vfmadd132ps     ymm14, ymm2, ymm10\n    vfmadd132ps     ymm15, ymm2, ymm11\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vfmadd132ps     ymm13, ymm3, ymm9\n    vfmadd132ps     ymm14, ymm3, ymm10\n    vfmadd132ps     ymm15, ymm3, ymm11\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vfmadd132ps     ymm13, ymm0, ymm9\n    vfmadd132ps     ymm14, ymm0, ymm10\n    vfmadd132ps     ymm15, ymm0, ymm11  // ymm12..14 <- denum\n\n    vdivps          ymm4, ymm4, ymm12\n    vdivps          ymm5, ymm5, ymm13\n    vdivps          ymm6, ymm6, ymm14\n    vdivps          ymm7, ymm7, ymm15\n    vaddps          ymm4, ymm4, ymm1\n    vaddps          ymm5, ymm5, ymm1\n    vaddps          ymm6, ymm6, ymm1\n    vaddps          ymm7, ymm7, ymm1\n\n    vmovaps [rdi], ymm4\n    vmovaps [rdi + 32], ymm5\n    vmovaps [rdi + 64], ymm6\n    vmovaps [rdi + 96], ymm7\n\n    add     rdi, 128\n    sub     rsi, 32\n    cmp     rsi, 32\n    jg      {{L}}loop_4\n\n    cmp     rsi, 0\n    je      {{L}}done\n\n{{L}}loop_1:\n    vmovaps         ymm4, [rdi]\n\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_low]\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_high]\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_13]\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_11]\n\n    vmaxps          ymm4, ymm4, ymm0\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_9]\n\n    vminps          ymm4, ymm4, ymm1        // ymm4 <- x\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_alpha_7]\n\n    vmulps          ymm8, ymm4, ymm4        // ymm8 <- x^2\n\n    vmovaps         ymm12, ymm2\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_5]\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_3]\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_1]\n    vfmadd132ps     ymm12, ymm1, ymm8\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_beta_6]\n    vfmadd132ps     ymm12, ymm2, ymm8\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_beta_4]\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_2]\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_0]\n    vmulps          ymm4, ymm4, ymm12\n\n    vmovaps         ymm12, ymm1\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_half]\n    vfmadd132ps     ymm12, ymm2, ymm8\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vfmadd132ps     ymm12, ymm0, ymm8\n\n    vdivps          ymm4, ymm4, ymm12\n    vaddps          ymm4, ymm4, ymm1\n\n    vmovaps [rdi], ymm4\n    add     rdi, 32\n    sub     rsi, 8\n    jnz     {{L}}loop_1\n{{L}}done:\n\n// ----------------------------------------------------------------------\n\n    ldmxcsr     [rsp + 4]\n\n    add         rsp, 8\n\n    pop r15\n    pop r14\n    pop r13\n    pop r12\n    pop rbx\n\n{% if family == \"windows\" %}\n    pop rsi\n    pop rdi\n\n    vmovaps xmm15, [rsp+16*9]\n    vmovaps xmm14, [rsp+16*8]\n    vmovaps xmm13, [rsp+16*7]\n    vmovaps xmm12, [rsp+16*6]\n    vmovaps xmm11, [rsp+16*5]\n    vmovaps xmm10, [rsp+16*4]\n    vmovaps xmm9, [rsp+16*3]\n    vmovaps xmm8, [rsp+16*2]\n    vmovaps xmm7, [rsp+16*1]\n    vmovaps xmm6, [rsp]\n{% endif %}\n\n    mov rsp, rbp\n    pop rbp\n    ret\n\n{%capture float%}{% if msvc %} real4 {%else%} .float {%endif%}{%endcapture%}\n\n{{L}}coeffs_num_low:\n    {{float}} -18.6                   // low\n{{L}}coeffs_num_high:\n    {{float}} 18.6                     // high         \n\n{{L}}coeffs_num_alpha_13:\n    {{float}} -4.433153405e-18\n{{L}}coeffs_num_alpha_11:\n    {{float}} 1.169974371e-14\n{{L}}coeffs_num_alpha_9:\n    {{float}} -1.875289645e-11\n{{L}}coeffs_num_alpha_7:\n    {{float}} 4.257889523e-8\n{{L}}coeffs_num_alpha_5:\n    {{float}} 0.00004811817576\n{{L}}coeffs_num_alpha_3:\n    {{float}} 0.008163842030\n{{L}}coeffs_num_alpha_1:\n    {{float}} 0.2499999971\n\n{{L}}coeffs_num_beta_6:\n    {{float}} 3.922935744e-6\n{{L}}coeffs_num_beta_4:\n    {{float}} 0.001524872358\n{{L}}coeffs_num_beta_2:\n    {{float}} 0.1159886749\n{{L}}coeffs_num_beta_0:\n    {{float}} 1.0;\n\n{{L}}coeffs_num_half:\n    {{float}} 0.5\n\n{% if msvc %}\nfma_sigmoid_f32_{{suffix}} endp\n_text ends\nend\n{% else %}\n.cfi_endproc\n{% endif %}\n"
  },
  {
    "path": "linalg/x86_64/fma/fma_tanh_f32.tmpl",
    "content": "{% comment %}\n// vim: set syntax=asm :\n\nSystem V ABI:\n    args: rdi, rsi, rdx, rcx, r8, r9\n    preserve: rbx, rsp, rbp, r12, r13, r14, r15\n    scratch: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11\n    return: rax (+rdx)\n\nWindows ABI:\n    args: RCX, RDX, R8, R9\n    preserve: RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, and XMM6-15\n    scratch: RAX, RCX, RDX, R8, R9, R10, R11, XMM0-5, and the upper portions of YMM0-15 and ZMM0-15\n    return: rax (+rdx)\n\n{% endcomment %}\n\n{% if msvc %}\n\n_text segment\nfma_tanh_f32_{{suffix}} proc\n\n{% else %}\n\n.intel_syntax noprefix\n.text\n.p2align 5\n.globl {{G}}fma_tanh_f32_{{suffix}}\n{{G}}fma_tanh_f32_{{suffix}}:\n.cfi_startproc\n{% endif %}\n\n    push        rbp\n    mov         rbp, rsp\n\n\n{% if family == \"windows\" %}\n// https://www.agner.org/optimize/calling_conventions.pdf xmm6-15 are not scratch\n// https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers\n    and rsp,-16\n    lea rsp,[rsp-160]\n    vmovaps [rsp], xmm6\n    vmovaps [rsp+16*1],xmm7\n    vmovaps [rsp+16*2],xmm8\n    vmovaps [rsp+16*3],xmm9\n    vmovaps [rsp+16*4],xmm10\n    vmovaps [rsp+16*5],xmm11\n    vmovaps [rsp+16*6],xmm12\n    vmovaps [rsp+16*7],xmm13\n    vmovaps [rsp+16*8],xmm14\n    vmovaps [rsp+16*9],xmm15\n\n    // move around arguments to mimick SysV rdi,rsi passing\n    push        rdi\n    push        rsi\n    mov         rdi, rcx\n    mov         rsi, rdx\n\n{% endif %}\n\n    push        rbx\n    push        r12\n    push        r13\n    push        r14\n    push        r15\n\n    sub         rsp, 8\n\n{% if family == \"unix\" %}\n// FIXME\n// .cfi_def_cfa_offset 64 \n{% endif %}\n\n    stmxcsr     [rsp + 4]\n{% if msvc %}\n    mov         rax, 1FC0h\n{% else %}\n    mov         rax, 0x1FC0\n{% endif %}\n    mov         [rsp], eax\n    ldmxcsr     [rsp]\n// ----------------------------------------------------------------------\n\n{%capture offset%}{% if msvc %} offset {%else%} rip + {%endif%} {%endcapture%}\n\n    cmp     rsi, 0\n    je      {{L}}done\n\n    cmp     rsi, 32\n    jl      {{L}}loop_1\n\n{{L}}loop_4:\n\n    vmovaps         ymm4, [rdi]\n    vmovaps         ymm5, [rdi + 32]\n    vmovaps         ymm6, [rdi + 64]\n    vmovaps         ymm7, [rdi + 96]\n\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_low]\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_high]\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_13]\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_11]\n\n    vmaxps          ymm4, ymm4, ymm0\n    vmaxps          ymm5, ymm5, ymm0\n    vmaxps          ymm6, ymm6, ymm0\n    vmaxps          ymm7, ymm7, ymm0\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_9]\n\n    vminps          ymm4, ymm4, ymm1\n    vminps          ymm5, ymm5, ymm1\n    vminps          ymm6, ymm6, ymm1\n    vminps          ymm7, ymm7, ymm1        // ymm4..7 <- x\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_alpha_7]\n\n    vmulps          ymm8, ymm4, ymm4\n    vmulps          ymm9, ymm5, ymm5\n    vmulps          ymm10, ymm6, ymm6\n    vmulps          ymm11, ymm7, ymm7        // ymm8..11 <- x^2\n\n    vmovaps         ymm12, ymm2\n    vmovaps         ymm13, ymm2\n    vmovaps         ymm14, ymm2\n    vmovaps         ymm15, ymm2\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_5]\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vfmadd132ps     ymm13, ymm3, ymm9\n    vfmadd132ps     ymm14, ymm3, ymm10\n    vfmadd132ps     ymm15, ymm3, ymm11\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_3]\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vfmadd132ps     ymm13, ymm0, ymm9\n    vfmadd132ps     ymm14, ymm0, ymm10\n    vfmadd132ps     ymm15, ymm0, ymm11\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_1]\n    vfmadd132ps     ymm12, ymm1, ymm8\n    vfmadd132ps     ymm13, ymm1, ymm9\n    vfmadd132ps     ymm14, ymm1, ymm10\n    vfmadd132ps     ymm15, ymm1, ymm11\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_beta_6]\n    vfmadd132ps     ymm12, ymm2, ymm8\n    vfmadd132ps     ymm13, ymm2, ymm9\n    vfmadd132ps     ymm14, ymm2, ymm10\n    vfmadd132ps     ymm15, ymm2, ymm11\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_beta_4]\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vfmadd132ps     ymm13, ymm3, ymm9\n    vfmadd132ps     ymm14, ymm3, ymm10\n    vfmadd132ps     ymm15, ymm3, ymm11\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_2]\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vfmadd132ps     ymm13, ymm0, ymm9\n    vfmadd132ps     ymm14, ymm0, ymm10\n    vfmadd132ps     ymm15, ymm0, ymm11\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_0]\n    vmulps          ymm4, ymm4, ymm12\n    vmulps          ymm5, ymm5, ymm13\n    vmulps          ymm6, ymm6, ymm14\n    vmulps          ymm7, ymm7, ymm15   // ymm4..7 <- num\n\n    vmovaps         ymm12, ymm1\n    vmovaps         ymm13, ymm1\n    vmovaps         ymm14, ymm1\n    vmovaps         ymm15, ymm1\n    vfmadd132ps     ymm12, ymm2, ymm8\n    vfmadd132ps     ymm13, ymm2, ymm9\n    vfmadd132ps     ymm14, ymm2, ymm10\n    vfmadd132ps     ymm15, ymm2, ymm11\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vfmadd132ps     ymm13, ymm3, ymm9\n    vfmadd132ps     ymm14, ymm3, ymm10\n    vfmadd132ps     ymm15, ymm3, ymm11\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vfmadd132ps     ymm13, ymm0, ymm9\n    vfmadd132ps     ymm14, ymm0, ymm10\n    vfmadd132ps     ymm15, ymm0, ymm11  // ymm12..14 <- denum\n\n    vdivps          ymm4, ymm4, ymm12\n    vdivps          ymm5, ymm5, ymm13\n    vdivps          ymm6, ymm6, ymm14\n    vdivps          ymm7, ymm7, ymm15\n\n    vmovaps [rdi], ymm4\n    vmovaps [rdi + 32], ymm5\n    vmovaps [rdi + 64], ymm6\n    vmovaps [rdi + 96], ymm7\n\n    add     rdi, 128\n    sub     rsi, 32\n    cmp     rsi, 32\n    jg      {{L}}loop_4\n\n    cmp     rsi, 0\n    je      {{L}}done\n\n{{L}}loop_1:\n    vmovaps         ymm4, [rdi]\n\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_low]\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_high]\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_13]\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_11]\n\n    vmaxps          ymm4, ymm4, ymm0\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_9]\n\n    vminps          ymm4, ymm4, ymm1        // ymm4 <- x\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_alpha_7]\n\n    vmulps          ymm8, ymm4, ymm4        // ymm8 <- x^2\n\n    vmovaps         ymm12, ymm2\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_alpha_5]\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_alpha_3]\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_alpha_1]\n    vfmadd132ps     ymm12, ymm1, ymm8\n    vbroadcastss    ymm1, dword ptr [{{offset}} {{L}}coeffs_num_beta_6]\n    vfmadd132ps     ymm12, ymm2, ymm8\n    vbroadcastss    ymm2, dword ptr [{{offset}} {{L}}coeffs_num_beta_4]\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vbroadcastss    ymm3, dword ptr [{{offset}} {{L}}coeffs_num_beta_2]\n    vfmadd132ps     ymm12, ymm0, ymm8\n    vbroadcastss    ymm0, dword ptr [{{offset}} {{L}}coeffs_num_beta_0]\n    vmulps          ymm4, ymm4, ymm12\n\n    vmovaps         ymm12, ymm1\n    vfmadd132ps     ymm12, ymm2, ymm8\n    vfmadd132ps     ymm12, ymm3, ymm8\n    vfmadd132ps     ymm12, ymm0, ymm8\n\n    vdivps          ymm4, ymm4, ymm12\n\n    vmovaps [rdi], ymm4\n    add     rdi, 32\n    sub     rsi, 8\n    jnz     {{L}}loop_1\n\n{{L}}done:\n\n// ----------------------------------------------------------------------\n\n    ldmxcsr     [rsp + 4]\n\n    add         rsp, 8\n\n    pop r15\n    pop r14\n    pop r13\n    pop r12\n    pop rbx\n\n{% if family == \"windows\" %}\n    pop rsi\n    pop rdi\n\n    vmovaps xmm15, [rsp+16*9]\n    vmovaps xmm14, [rsp+16*8]\n    vmovaps xmm13, [rsp+16*7]\n    vmovaps xmm12, [rsp+16*6]\n    vmovaps xmm11, [rsp+16*5]\n    vmovaps xmm10, [rsp+16*4]\n    vmovaps xmm9, [rsp+16*3]\n    vmovaps xmm8, [rsp+16*2]\n    vmovaps xmm7, [rsp+16*1]\n    vmovaps xmm6, [rsp]\n{% endif %}\n\n    mov rsp, rbp\n    pop rbp\n    ret\n\n{%capture float%}{% if msvc %} real4 {%else%} .float {%endif%}{%endcapture%}\n\n{{L}}coeffs_num_low:\n    {{float}} -8.9\n{{L}}coeffs_num_high:\n    {{float}} 8.9\n\n{{L}}coeffs_num_alpha_13:\n    {{float}} -8.488492677e-14\n{{L}}coeffs_num_alpha_11:\n    {{float}} 5.277853000e-11\n{{L}}coeffs_num_alpha_9:\n    {{float}} -2.022500419e-8\n{{L}}coeffs_num_alpha_7:\n    {{float}} 0.00001115424833\n{{L}}coeffs_num_alpha_5:\n    {{float}} 0.003103950131\n{{L}}coeffs_num_alpha_3:\n    {{float}} 0.1308400453\n{{L}}coeffs_num_alpha_1:\n    {{float}} 0.9999999934\n\n{{L}}coeffs_num_beta_6:\n    {{float}} 0.0002546136580\n{{L}}coeffs_num_beta_4:\n    {{float}} 0.02449515379\n{{L}}coeffs_num_beta_2:\n    {{float}} 0.4641733162\n{{L}}coeffs_num_beta_0:\n    {{float}} 1.0\n\n\n\n{% if msvc %}\nfma_tanh_f32_{{suffix}} endp\n_text ends\nend\n{% else %}\n.cfi_endproc\n{% endif %}\n"
  },
  {
    "path": "linalg/x86_64/fma/postamble.tmpliq",
    "content": "{{L}}return:\n    ldmxcsr     [rsp + 4]\n    add         rsp, 8\n\n    pop r15\n    pop r14\n    pop r13\n    pop r12\n    pop rbx\n\n{% if family == \"windows\" %}\n    pop rsi\n    pop rdi\n\n    vmovaps xmm15, [rsp+16*9]\n    vmovaps xmm14, [rsp+16*8]\n    vmovaps xmm13, [rsp+16*7]\n    vmovaps xmm12, [rsp+16*6]\n    vmovaps xmm11, [rsp+16*5]\n    vmovaps xmm10, [rsp+16*4]\n    vmovaps xmm9, [rsp+16*3]\n    vmovaps xmm8, [rsp+16*2]\n    vmovaps xmm7, [rsp+16*1]\n    vmovaps xmm6, [rsp]\n{% endif %}\n\n    mov rsp, rbp\n    pop rbp\n    ret\n\n{% if msvc %}\nfma_mmm_{{type}}_{{size}}_{{suffix}} endp\n_text ends\nend\n\n{% else %}\n.cfi_endproc\n{% endif %}\n"
  },
  {
    "path": "linalg/x86_64/fma/preamble.tmpliq",
    "content": "\n{% if msvc %}\n\n_text segment\nfma_mmm_{{type}}_{{size}}_{{suffix}} proc\n\n{% else %}\n\n.intel_syntax noprefix\n.text\n.p2align 5\n.globl {{G}}fma_mmm_{{type}}_{{size}}_{{suffix}}\n{{G}}fma_mmm_{{type}}_{{size}}_{{suffix}}:\n.cfi_startproc\n\n{% endif %}\n\n    push        rbp\n    mov         rbp, rsp\n\n{% if family == \"windows\" %}\n// https://www.agner.org/optimize/calling_conventions.pdf xmm6-15 are not scratch\n// https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers\n    and rsp,-16\n    lea rsp,[rsp-160]\n    vmovaps [rsp], xmm6\n    vmovaps [rsp+16*1],xmm7\n    vmovaps [rsp+16*2],xmm8\n    vmovaps [rsp+16*3],xmm9\n    vmovaps [rsp+16*4],xmm10\n    vmovaps [rsp+16*5],xmm11\n    vmovaps [rsp+16*6],xmm12\n    vmovaps [rsp+16*7],xmm13\n    vmovaps [rsp+16*8],xmm14\n    vmovaps [rsp+16*9],xmm15\n\n    push        rdi\n    push        rsi\n\n    mov         rdi, rcx\n\n{% endif %}\n\n    push        rbx\n    push        r12\n    push        r13\n    push        r14\n    push        r15\n\n    sub         rsp, 8\n\n{% if family == \"unix\" %}\n.cfi_def_cfa_offset 64\n{% endif %}\n    stmxcsr     [rsp + 4]\n{% if msvc %}\n    mov         rax, 1FC0h\n{% else %}\n    mov         rax, 0x1FC0\n{% endif %}\n    mov         [rsp], eax\n    ldmxcsr     [rsp]\n\n{% include \"dispatcher.tmpliq\" %}\n"
  },
  {
    "path": "metal/Cargo.toml",
    "content": "[package]\nname = \"tract-metal\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\n\t\"Hubert de La Jonquière <hubert.delajonquiere@sonos.com>\",\n\t\"Mathieu Poumeyrol <kali@zoy.org>\",\n]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\", \"Metal\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nanyhow.workspace = true\nderive-new.workspace = true\ndowncast-rs.workspace = true\ninventory.workspace = true\nlog.workspace = true\nmetal.workspace = true\nobjc = { version = \"0.2.7\" }\nnum-traits.workspace = true\ntract-core.workspace = true\ntract-pulse-opl.workspace = true\ntract-transformers.workspace = true\ntract-gpu.workspace = true\n\n[features]\ndefault = [ ]\n\n[dev-dependencies]\ncriterion.workspace = true\nproptest.workspace = true\nrand.workspace = true\nggml = { git = \"https://github.com/rustformers/llm.git\", rev=\"9376078\", features = [\"metal\"] }\n\n[[bench]]\nname = \"metal_gemm\"\nharness = false\n\n"
  },
  {
    "path": "metal/README.md",
    "content": "# tract-metal\n\n## Updating Metal Flash Attention library\n\n```\ngit clone https://github.com/philipturner/metal-flash-attention.git\ncd metal-flash-attention\n\n# for iOS\nswift build.swift --platform iOS --xcode-path /Applications/Xcode.app\ncp build/lib/libMetalFlashAttention.metallib path/to/tract/metal/src/kernels/libMetalFlashAttention-ios.metallib\n\n# for MacOS\nswift build.swift --platform macOS --xcode-path /Applications/Xcode.app\ncp build/lib/libMetalFlashAttention.metallib path/to/tract/metal/src/kernels/libMetalFlashAttention-macos.metallib\n```"
  },
  {
    "path": "metal/benches/metal_gemm.rs",
    "content": "use crate::matmul::{BasicMatMul, GemmImpl, GemmKernel, MfaGemm, MlxGemm};\nuse criterion::measurement::WallTime;\nuse criterion::*;\nuse ggml::Context;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::IntoDevice;\nuse tract_linalg::mmm::AsInputValue;\nuse tract_metal::MetalStream;\nuse tract_metal::kernels::matmul::GgmlGemm;\nuse tract_metal::kernels::{LibraryName, matmul};\n\npub fn ggml_matmul(\n    crit: &mut BenchmarkGroup<WallTime>,\n    m: usize,\n    k: usize,\n    n: usize,\n    dt: DatumType,\n) {\n    let ggml_dt = match dt {\n        DatumType::F32 => ggml::Type::F32,\n        DatumType::F16 => ggml::Type::F16,\n        _ => unimplemented!(),\n    };\n\n    let ctxt = Context::new_with_allocate(500_000_000);\n\n    let mut t = ctxt.new_tensor_3d(ggml_dt, 1, 2, 3);\n    t.zero_data();\n\n    let mut a = ctxt.new_tensor_2d(ggml_dt, k, m);\n    a.zero_data();\n    let mut b = ctxt.new_tensor_2d(ggml_dt, k, n); // intern transposition\n    b.zero_data();\n\n    crit.bench_function(&format!(\"ggml_{:?}\", dt), |be| {\n        be.iter(|| {\n            let ctxt = Context::new_with_allocate(500_000_000);\n            let mut a = ctxt.new_tensor_2d(ggml_dt, k, m);\n            a.zero_data();\n            let mut b = ctxt.new_tensor_2d(ggml_dt, k, n); // intern transposition\n            b.zero_data();\n            let c = ctxt.op_mul_mat(&a, &b);\n            let mut graph = ctxt.create_compute_graph();\n            graph.build_forward_expand(&c);\n\n            let mut execution_plan = ggml::GraphExecutionPlan::new(&mut graph, 1);\n            execution_plan.execute(&ctxt);\n        });\n    });\n}\n\npub fn tract_with_packing(\n    crit: &mut BenchmarkGroup<WallTime>,\n    batch: usize,\n    m: usize,\n    k: usize,\n    n: usize,\n    dt: DatumType,\n) {\n    use tract_linalg::mmm::FusedSpec;\n    let a = Tensor::zero_dt(dt, &[batch, m, k]).unwrap();\n    let b = Tensor::zero_dt(dt, &[batch, k, n]).unwrap();\n    let mut c = Tensor::zero_dt(dt, &[m, n]).unwrap();\n\n    // mk,kn -> mn\n    unsafe {\n        let mmm = tract_linalg::ops().mmm(dt, Some(m), Some(k), Some(n)).unwrap();\n\n        let c_storage = mmm.c_view(Some(0), Some(1));\n\n        let mut scratch = mmm.allocate_scratch_space();\n\n        let (packer_a, packer_b) = &mmm.packings()[0];\n\n        crit.bench_function(&format!(\"tract_with_packing_{:?}\", dt), |be| {\n            let packed_a = packer_a.prepare_one(&a, 1, 0).unwrap();\n            let packed_b = packer_b.prepare_one(&b, 0, 1).unwrap();\n\n            be.iter(|| {\n                mmm.run_with_scratch_space(\n                    m,\n                    n,\n                    &mut *scratch,\n                    &[\n                        FusedSpec::AddMatMul {\n                            packing: 0,\n                            a: AsInputValue::Borrowed(&(*packed_a)),\n                            b: AsInputValue::Borrowed(&(*packed_b)),\n                        },\n                        FusedSpec::Store(c_storage.wrap(&mut c.view_mut())),\n                    ],\n                )\n                .unwrap()\n            });\n        });\n    }\n}\n\npub fn metal_gemm<K: GemmKernel>(\n    crit: &mut BenchmarkGroup<WallTime>,\n    batch: usize,\n    m: usize,\n    k: usize,\n    n: usize,\n    dt: DatumType,\n    is_ggml: bool,\n) {\n    let stream = MetalStream::new();\n    stream.load_library(LibraryName::MfaLib).unwrap();\n\n    let a = Tensor::zero_dt(dt, &[batch, m, k]).unwrap();\n    let b = if is_ggml {\n        Tensor::zero_dt(dt, &[batch, n, k]).unwrap()\n    } else {\n        Tensor::zero_dt(dt, &[batch, k, n]).unwrap()\n    };\n\n    let metal_a = a.into_device().unwrap();\n    let metal_b = b.into_device().unwrap();\n    // Warmup\n    let _ = GemmImpl::<MfaGemm>::default().eval(&stream, &metal_a, &metal_b).unwrap();\n\n    crit.bench_function(&format!(\"tract_metal_gemm_{}_{:?}\", K::name(), dt), |be| {\n        be.iter(|| {\n            let _ = GemmImpl::<K>::new(false, is_ggml).eval(&stream, &metal_a, &metal_b).unwrap();\n        });\n    });\n}\n\nfn matmul(c: &mut Criterion, b: usize, m: usize, k: usize, n: usize) {\n    let mut c = c.benchmark_group(format!(\"{}x{}x{}x{}\", b, m, k, n));\n    c.throughput(Throughput::Elements((m * k * n) as _));\n    // ggml_matmul(&mut c, m, k, n, f32::datum_type());\n\n    for dt in [f32::datum_type(), f16::datum_type()] {\n        metal_gemm::<BasicMatMul>(&mut c, b, m, k, n, dt, false);\n        metal_gemm::<MlxGemm>(&mut c, b, m, k, n, dt, false);\n        metal_gemm::<MfaGemm>(&mut c, b, m, k, n, dt, false);\n        metal_gemm::<GgmlGemm>(&mut c, b, m, k, n, dt, true);\n    }\n    // ggml_matmul(&mut c, m, k, n, f16::datum_type());\n    // tract_with_packing(&mut c, b, m, k, n, f32::datum_type());\n    //tract_with_packing(&mut c, b, m, k, n, f16::datum_type());\n    c.finish();\n}\n\n#[allow(unused)]\nfn tinyllama(c: &mut Criterion) {\n    let shapes = vec![\n        (32, 1, 25, 32),\n        (1, 32003, 2048, 1),\n        (1, 1, 2048, 32003),\n        // (32003, 2048, 6),\n        // (1, 32, 32),\n        // (1, 4, 4),\n        // (1, 4096, 4096),\n        // (1, 2048, 2048),\n        // (1, 1024, 1024),\n        // (1, 128, 128),\n        // (1, 64, 3),\n        // (1, 64, 1),\n        // (1, 5632, 2048),\n        // (1, 3, 64),\n        // (1, 64, 13),\n        // (1, 12, 64),\n        // (1, 2048, 5632),\n        // (1, 2048, 32003),\n        // (1, 2048, 2048),\n        // (1, 2048, 256),\n    ];\n    for (b, m, k, n) in shapes {\n        matmul(c, b, m, k, n);\n    }\n}\n\n#[allow(unused)]\nfn big(c: &mut Criterion) {\n    matmul(c, 1, 2048, 2048, 1);\n    matmul(c, 1, 1, 2048, 2048);\n    matmul(c, 1, 2048, 2048, 2048);\n    matmul(c, 1, 4096, 4096, 4096);\n}\n\n#[allow(unused)]\nfn wavenet(c: &mut Criterion) {\n    matmul(c, 1, 32, 32, 8);\n    matmul(c, 1, 16, 60, 8);\n}\n\n#[allow(unused)]\nfn asr_15_m(c: &mut Criterion) {\n    matmul(c, 1, 768, 200, 24);\n    matmul(c, 1, 768, 2304, 24);\n    matmul(c, 1, 768, 2304, 8);\n    matmul(c, 1, 768, 384, 1);\n}\n\n#[allow(unused)]\nfn inception(c: &mut Criterion) {\n    matmul(c, 1, 64, 288, 21609);\n}\n\n#[allow(unused)]\nfn whisper_base(c: &mut Criterion) {\n    matmul(c, 1, 512, 512, 1500);\n}\n\ncriterion_group!(benches, tinyllama); //big, wavenet, asr_15_m, inception, whisper_base);\ncriterion_main!(benches);\n"
  },
  {
    "path": "metal/src/command_buffer.rs",
    "content": "use metal::{CommandBuffer, ComputeCommandEncoder, ComputeCommandEncoderRef};\nuse std::ops::{Deref, DerefMut};\n\n#[derive(Debug, Clone)]\npub struct TCommandBuffer {\n    inner: CommandBuffer,\n    encoder: ComputeCommandEncoder,\n}\n\nimpl TCommandBuffer {\n    pub fn new(command_buffer: CommandBuffer) -> Self {\n        let encoder = command_buffer.new_compute_command_encoder().to_owned();\n\n        TCommandBuffer { inner: command_buffer, encoder }\n    }\n\n    pub fn encoder(&self) -> &ComputeCommandEncoder {\n        &self.encoder\n    }\n\n    pub fn encode<EncodeCallback>(&self, encode_cb: EncodeCallback)\n    where\n        EncodeCallback: Fn(&ComputeCommandEncoderRef),\n    {\n        encode_cb(&self.encoder);\n    }\n}\n\nimpl Deref for TCommandBuffer {\n    type Target = CommandBuffer;\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\nimpl DerefMut for TCommandBuffer {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.inner\n    }\n}\n"
  },
  {
    "path": "metal/src/context.rs",
    "content": "use crate::command_buffer::TCommandBuffer;\nuse crate::func_constants::ConstantValues;\nuse crate::kernels::{LibraryContent, LibraryName};\nuse crate::tensor::{MValue, MetalTensor};\n\nuse metal::NSUInteger;\nuse tract_core::tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage};\nuse tract_gpu::device::{DeviceBuffer, DeviceContext};\nuse tract_gpu::tensor::{DeviceTensor, OwnedDeviceTensor};\nuse tract_gpu::utils::as_q40_tensor;\n\nuse std::alloc::Layout;\nuse std::cell::RefCell;\nuse std::ffi::c_void;\nuse std::ops::{Deref, DerefMut};\nuse std::path::Path;\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::{Arc, OnceLock, RwLock};\n\nuse anyhow::{Context, anyhow};\nuse metal::{\n    Buffer, CommandQueue, CompileOptions, ComputePipelineState, Device, Function,\n    FunctionConstantValues, Library, MTLResourceOptions,\n};\nuse std::collections::HashMap;\nuse tract_core::internal::*;\n\nthread_local! {\n    static METAL_STREAM: RefCell<Option<MetalStream>> = const { RefCell::new(None) };\n}\n\npub fn with_metal_stream<R>(f: impl FnOnce(&MetalStream) -> TractResult<R>) -> TractResult<R> {\n    metal_context(); // ensures context is initialized\n    METAL_STREAM.with(|cell| {\n        let needs_init = cell.borrow().is_none();\n        if needs_init {\n            let stream = MetalStream::new();\n            *cell.borrow_mut() = Some(stream);\n        }\n        let borrow = cell.borrow();\n        f(borrow.as_ref().unwrap())\n    })\n}\n\npub fn metal_context() -> MetalContext {\n    static INSTANCE: OnceLock<MetalContext> = OnceLock::new();\n    INSTANCE\n        .get_or_init(|| {\n            let ctxt = MetalContext::new().expect(\"Could not create Metal context\");\n            tract_gpu::device::set_context(Box::new(ctxt.clone()))\n                .expect(\"Could not set Metal context\");\n            ctxt\n        })\n        .clone()\n}\n\n#[derive(Debug, Clone)]\npub struct MetalContext {\n    device: Device,\n    cache_libraries: Arc<RwLock<HashMap<LibraryName, Library>>>,\n    #[allow(clippy::type_complexity)]\n    cache_pipelines:\n        Arc<RwLock<HashMap<(LibraryName, String, Option<ConstantValues>), ComputePipelineState>>>,\n}\n\nimpl MetalContext {\n    pub fn new() -> TractResult<Self> {\n        let device = Device::system_default()\n            .with_context(|| \"Could not find system default Metal device\")?;\n\n        let ctxt = Self {\n            device,\n            cache_libraries: Arc::new(RwLock::new(HashMap::new())),\n            cache_pipelines: Arc::new(RwLock::new(HashMap::new())),\n        };\n        ctxt.preload_pipelines()?;\n        Ok(ctxt)\n    }\n\n    pub fn preload_pipelines(&self) -> TractResult<()> {\n        for ew_func in crate::kernels::element_wise::all_functions() {\n            let _ = self.load_pipeline(LibraryName::ElementWiseOps, &ew_func);\n        }\n        for bin_func in crate::kernels::bin_ops::all_functions() {\n            let _ = self.load_pipeline(LibraryName::BinOps, &bin_func);\n        }\n        for func in crate::kernels::array::all_functions() {\n            let _ = self.load_pipeline(LibraryName::ArrayOps, &func);\n        }\n        for func in crate::kernels::nn::all_functions() {\n            let _ = self.load_pipeline(LibraryName::NNOps, &func);\n        }\n        Ok(())\n    }\n\n    pub fn load_library(&self, name: LibraryName) -> TractResult<Library> {\n        {\n            let cache_libraries = self.cache_libraries.read().map_err(|e| anyhow!(\"{:?}\", e))?;\n            if let Some(library) = cache_libraries.get(&name) {\n                return Ok(library.clone());\n            }\n        }\n        let mut cache_libraries = self.cache_libraries.write().map_err(|e| anyhow!(\"{:?}\", e))?;\n        let library = match name.content() {\n            LibraryContent::Data(lib_data) => self\n                .device\n                .new_library_with_data(lib_data)\n                .map_err(|e| anyhow!(\"{}\", e))\n                .with_context(|| {\n                    format!(\"Error while loading Metal library from data: {:?}\", name)\n                })?,\n            LibraryContent::Source(lib_source) => self\n                .device\n                .new_library_with_source(lib_source, &CompileOptions::new())\n                .map_err(|e| anyhow!(\"{}\", e))\n                .with_context(|| {\n                    format!(\"Error while loading Metal library from source: {:?}\", name)\n                })?,\n        };\n        cache_libraries.insert(name, library.clone());\n        Ok(library)\n    }\n\n    pub fn load_function(\n        &self,\n        library_name: LibraryName,\n        func_name: &str,\n        constants: Option<FunctionConstantValues>,\n    ) -> TractResult<Function> {\n        let func = self\n            .load_library(library_name)?\n            .get_function(func_name, constants)\n            .map_err(|e| anyhow!(\"{}\", e))\n            .with_context(|| {\n                format!(\n                    \"Error while loading function {func_name} from library: {:?} with constants\",\n                    library_name\n                )\n            })?;\n        Ok(func)\n    }\n\n    pub(crate) fn load_pipeline_with_constants(\n        &self,\n        library_name: LibraryName,\n        func_name: &str,\n        constants: Option<ConstantValues>,\n    ) -> TractResult<ComputePipelineState> {\n        let key = (library_name, func_name.to_string(), constants);\n        {\n            let cache_pipelines = self.cache_pipelines.read().map_err(|e| anyhow!(\"{:?}\", e))?;\n            if let Some(pipeline) = cache_pipelines.get(&key) {\n                return Ok(pipeline.clone());\n            }\n        }\n        let mut cache_pipelines = self.cache_pipelines.write().map_err(|e| anyhow!(\"{:?}\", e))?;\n\n        let (library_name, func_name, constants) = key;\n        let func = self.load_function(\n            library_name,\n            &func_name,\n            constants.as_ref().map(|c| c.function_constant_values()),\n        )?;\n        let pipeline = self.device\n            .new_compute_pipeline_state_with_function(&func)\n            .map_err(|e| anyhow!(\"{}\", e))\n            .with_context(|| format!(\"Error while creating compute pipeline for function {func_name} from source: {:?}\", library_name))?;\n        cache_pipelines.insert((library_name, func_name.to_string(), constants), pipeline.clone());\n        Ok(pipeline)\n    }\n\n    pub fn load_pipeline(\n        &self,\n        library_name: LibraryName,\n        func_name: &str,\n    ) -> TractResult<ComputePipelineState> {\n        self.load_pipeline_with_constants(library_name, func_name, None)\n    }\n}\n\nimpl DeviceContext for MetalContext {\n    fn synchronize(&self) -> TractResult<()> {\n        with_metal_stream(|stream| stream.wait_until_completed())\n    }\n\n    fn tensor_to_device(&self, tensor: TValue) -> TractResult<Box<dyn OwnedDeviceTensor>> {\n        let view = tensor.view();\n        ensure!(\n            DeviceTensor::is_supported_dt(view.datum_type()),\n            \"Tensor of {:?} is not copied. No device buffer can be allocated for it.\",\n            view.datum_type(),\n        );\n        let bqs = as_q40_tensor(view.tensor);\n\n        let (data_bytes, bqf) = if let Some(bqs) = bqs {\n            (\n                bqs.value().as_bytes(),\n                Some(Box::new(BlockQuantFact::new(\n                    tract_core::dyn_clone::clone_box(bqs.format()),\n                    tensor.view().tensor.shape().into(),\n                )) as Box<dyn ExoticFact>),\n            )\n        } else {\n            (view.tensor.as_bytes(), None)\n        };\n\n        // Handle empty data\n        static ZERO: [u8; 1] = [0];\n        let data = if data_bytes.is_empty() { &ZERO } else { data_bytes };\n\n        let size = core::mem::size_of_val(data) as NSUInteger;\n        let device_buffer = MetalBuffer {\n            inner: self.device.new_buffer_with_bytes_no_copy(\n                data.as_ptr() as *const core::ffi::c_void,\n                size,\n                MTLResourceOptions::StorageModeShared,\n                None,\n            ),\n        };\n\n        Ok(Box::new(MetalTensor {\n            inner: MValue::Natural(tensor.into_arc_tensor()),\n            device_buffer,\n            exotic_fact: bqf,\n        }))\n    }\n\n    fn uninitialized_device_tensor(\n        &self,\n        shape: &[usize],\n        dt: DatumType,\n    ) -> TractResult<Box<dyn OwnedDeviceTensor>> {\n        let tensor = unsafe {\n            Tensor::uninitialized_dt(dt, shape).with_context(|| {\n                format!(\"Error while allocating a {dt:?} tensor of shape {shape:?}\")\n            })?\n        };\n        self.tensor_to_device(tensor.into())\n    }\n\n    fn uninitialized_device_exotic_tensor(\n        &self,\n        exotic_fact: Box<dyn ExoticFact>,\n    ) -> TractResult<Box<dyn OwnedDeviceTensor>> {\n        if let Some(bqf) = exotic_fact.downcast_ref::<BlockQuantFact>() {\n            let blocks = bqf.shape().iter().product::<usize>() / bqf.format.block_len();\n            let blob = unsafe {\n                Blob::for_layout(\n                    Layout::from_size_align(blocks * bqf.format.block_bytes(), vector_size())\n                        .unwrap(),\n                )\n            };\n            let tensor =\n                BlockQuantStorage::new(bqf.format.clone(), bqf.m(), bqf.k(), Arc::new(blob))?\n                    .into_tensor_with_shape(f32::datum_type(), bqf.shape());\n            self.tensor_to_device(tensor.into())\n        } else {\n            bail!(\"Only BlockQuant Tensor allocation supported for now\")\n        }\n    }\n\n    fn copy_nd(\n        &self,\n        input: &DeviceTensor,\n        input_offset: usize,\n        input_strides: &[isize],\n        output: &DeviceTensor,\n        output_offset: usize,\n        output_shape: &[usize],\n        output_strides: &[isize],\n    ) -> TractResult<()> {\n        crate::kernels::array::metal_copy_nd_dispatch(\n            input,\n            input_offset,\n            input_strides,\n            output,\n            output_offset,\n            output_shape,\n            output_strides,\n        )\n    }\n}\n\n#[derive(Debug)]\npub struct MetalStream {\n    context: MetalContext,\n    command_queue: CommandQueue,\n    command_buffer: RefCell<Option<TCommandBuffer>>,\n    command_buffer_id: AtomicUsize,\n    retained_tensors: RefCell<Vec<DeviceTensor>>,\n}\n\nimpl Default for MetalStream {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl MetalStream {\n    pub fn new() -> Self {\n        let context = metal_context();\n        let command_queue = context.device.new_command_queue();\n        Self {\n            context,\n            command_queue,\n            command_buffer: RefCell::new(None),\n            command_buffer_id: AtomicUsize::new(0),\n            retained_tensors: RefCell::new(vec![]),\n        }\n    }\n\n    pub fn load_library(&self, name: LibraryName) -> TractResult<Library> {\n        self.context.load_library(name)\n    }\n\n    pub fn load_pipeline(\n        &self,\n        library_name: LibraryName,\n        func_name: &str,\n    ) -> TractResult<ComputePipelineState> {\n        self.context.load_pipeline(library_name, func_name)\n    }\n\n    pub(crate) fn load_pipeline_with_constants(\n        &self,\n        library_name: LibraryName,\n        func_name: &str,\n        constants: Option<ConstantValues>,\n    ) -> TractResult<ComputePipelineState> {\n        self.context.load_pipeline_with_constants(library_name, func_name, constants)\n    }\n\n    pub fn retain_tensor(&self, tensor: &DeviceTensor) {\n        self.retained_tensors.borrow_mut().push(tensor.clone());\n    }\n\n    pub fn command_buffer(&self) -> TCommandBuffer {\n        self.command_buffer\n            .borrow_mut()\n            .get_or_insert_with(|| {\n                TCommandBuffer::new(self.command_queue.new_command_buffer().to_owned())\n            })\n            .to_owned()\n    }\n\n    pub fn wait_until_completed(&self) -> TractResult<()> {\n        let Some(command_buffer) = self.command_buffer.borrow().to_owned() else { return Ok(()) };\n\n        command_buffer.encoder().end_encoding();\n\n        match command_buffer.status() {\n            metal::MTLCommandBufferStatus::Committed\n            | metal::MTLCommandBufferStatus::Scheduled\n            | metal::MTLCommandBufferStatus::Completed => {\n                anyhow::bail!(\"Current Metal command buffer is already committed.\")\n            }\n            _ => {}\n        }\n        let command_buffer_id = self.command_buffer_id.load(Ordering::Relaxed);\n        command_buffer.commit();\n        log::trace!(\"Command buffer {:?} commit\", command_buffer_id);\n        command_buffer.wait_until_completed();\n        log::trace!(\"Command buffer {:?} has completed (Blocking call)\", command_buffer_id);\n\n        // Clear local retained values used by the command buffer\n        self.retained_tensors.borrow_mut().clear();\n\n        *self.command_buffer.borrow_mut() = None;\n        Ok(())\n    }\n\n    pub fn capture_trace<P, F>(&self, path: P, compute: F) -> TractResult<()>\n    where\n        P: AsRef<Path>,\n        F: FnOnce(&Self) -> TractResult<()>,\n    {\n        self.wait_until_completed()?;\n\n        anyhow::ensure!(path.as_ref().is_absolute());\n\n        let capture = metal::CaptureManager::shared();\n        let descriptor = metal::CaptureDescriptor::new();\n        descriptor.set_destination(metal::MTLCaptureDestination::GpuTraceDocument);\n        descriptor.set_capture_device(&self.context.device);\n        descriptor.set_output_url(path);\n\n        capture.start_capture(&descriptor).map_err(|e| anyhow!(\"Error Metal Capture: {:?}\", e))?;\n\n        (compute)(self)?;\n\n        self.wait_until_completed()?;\n        capture.stop_capture();\n        Ok(())\n    }\n}\n\nimpl Drop for MetalStream {\n    fn drop(&mut self) {\n        let Some(command_buffer) = self.command_buffer.borrow_mut().to_owned() else { return };\n\n        match command_buffer.status() {\n            metal::MTLCommandBufferStatus::Committed\n            | metal::MTLCommandBufferStatus::Scheduled\n            | metal::MTLCommandBufferStatus::Completed => {\n                panic!(\"Current Metal command buffer is already committed.\")\n            }\n            _ => {}\n        }\n\n        command_buffer.encoder().end_encoding();\n        command_buffer.commit();\n        command_buffer.wait_until_completed();\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct MetalBuffer {\n    pub inner: Buffer,\n}\n\nimpl PartialEq for MetalBuffer {\n    fn eq(&self, other: &Self) -> bool {\n        self.inner.length() == other.inner.length() && self.inner.length() == other.inner.length()\n    }\n}\nimpl Eq for MetalBuffer {}\n\nimpl Deref for MetalBuffer {\n    type Target = Buffer;\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\nimpl DerefMut for MetalBuffer {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.inner\n    }\n}\nimpl DeviceBuffer for MetalBuffer {\n    fn ptr(&self) -> *const c_void {\n        self.inner.gpu_address() as *const c_void\n    }\n}\n"
  },
  {
    "path": "metal/src/encoder.rs",
    "content": "use metal::{ComputeCommandEncoderRef, MTLResourceUsage};\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\nuse crate::utils::get_metal_buffer;\n\npub trait EncoderExt {\n    fn set_metal_tensor(&self, idx: u64, t: &DeviceTensor, usage: MTLResourceUsage);\n    fn set_metal_tensor_with_offset(\n        &self,\n        idx: u64,\n        t: &DeviceTensor,\n        offset: u64,\n        usage: MTLResourceUsage,\n    );\n    fn set_tensor(&self, idx: u64, t: &Tensor);\n    fn set_slice<T: Copy>(&self, idx: u64, data: &[T]);\n}\n\nimpl EncoderExt for &ComputeCommandEncoderRef {\n    fn set_metal_tensor(&self, idx: u64, t: &DeviceTensor, usage: MTLResourceUsage) {\n        let buffer = get_metal_buffer(t);\n        self.set_buffer(idx, Some(buffer), t.buffer_offset());\n        self.use_resource(buffer, usage);\n    }\n\n    fn set_metal_tensor_with_offset(\n        &self,\n        idx: u64,\n        t: &DeviceTensor,\n        offset: u64,\n        usage: MTLResourceUsage,\n    ) {\n        let buffer = get_metal_buffer(t);\n        self.set_buffer(idx, Some(buffer), t.buffer_offset::<u64>() + offset);\n        self.use_resource(buffer, usage);\n    }\n\n    fn set_tensor(&self, idx: u64, t: &Tensor) {\n        self.set_bytes(idx, (t.datum_type().size_of() * t.len()) as _, unsafe {\n            t.as_ptr_unchecked::<u8>()\n        } as *const _);\n    }\n\n    fn set_slice<T: Copy>(&self, idx: u64, data: &[T]) {\n        self.set_bytes(idx, std::mem::size_of_val(data) as _, data.as_ptr() as *const _)\n    }\n}\n"
  },
  {
    "path": "metal/src/func_constants.rs",
    "content": "use metal::{FunctionConstantValues, MTLDataType};\nuse std::ffi::c_void;\n\n/// From candle-metal-kernels\n#[derive(Debug, PartialEq)]\npub enum Value {\n    USize(usize),\n    Bool(bool),\n    F32(f32),\n    U16(u16),\n}\n\nimpl std::hash::Hash for Value {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        match self {\n            Value::F32(v) => v.to_bits().hash(state),\n            Value::USize(v) => v.hash(state),\n            Value::U16(v) => v.hash(state),\n            Value::Bool(v) => v.hash(state),\n        }\n    }\n}\n\nimpl Value {\n    fn data_type(&self) -> MTLDataType {\n        match self {\n            Value::USize(_) => MTLDataType::UInt,\n            Value::F32(_) => MTLDataType::Float,\n            Value::U16(_) => MTLDataType::UShort,\n            Value::Bool(_) => MTLDataType::Bool,\n        }\n    }\n}\n\n// Not true, good enough for our purposes.\nimpl Eq for Value {}\n\n/// From candle-metal-kernels\n#[derive(Debug, Eq, PartialEq, Hash)]\npub(crate) struct ConstantValues(Vec<(usize, Value)>);\n\nimpl ConstantValues {\n    pub fn new(values: Vec<(usize, Value)>) -> Self {\n        Self(values)\n    }\n\n    pub fn function_constant_values(&self) -> FunctionConstantValues {\n        let f = FunctionConstantValues::new();\n        for (index, value) in &self.0 {\n            let ty = value.data_type();\n            match value {\n                Value::USize(v) => {\n                    f.set_constant_value_at_index(\n                        v as *const usize as *const c_void,\n                        ty,\n                        *index as u64,\n                    );\n                }\n                Value::F32(v) => {\n                    f.set_constant_value_at_index(\n                        v as *const f32 as *const c_void,\n                        ty,\n                        *index as u64,\n                    );\n                }\n                Value::U16(v) => {\n                    f.set_constant_value_at_index(\n                        v as *const u16 as *const c_void,\n                        ty,\n                        *index as u64,\n                    );\n                }\n                Value::Bool(v) => {\n                    f.set_constant_value_at_index(\n                        v as *const bool as *const c_void,\n                        ty,\n                        *index as u64,\n                    );\n                }\n            }\n        }\n        f\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/array/array_ops.metal",
    "content": "#include <metal_integer>\n#include <metal_math>\n#include <metal_simdgroup_matrix> // Available from Metal version 2.3 released with OS X 11.0+\n#include <metal_stdlib>\n\nusing namespace metal;\n\nnamespace utils {\nMETAL_FUNC uint indices_to_idx_2(uint2 indices,\n                                 constant const size_t strides[2]) {\n    return indices.x * strides[1] + indices.y * strides[0];\n}\n\n// Returns offset for iterating over most inner axis\nMETAL_FUNC uint indices_to_outer_idx(uint3 indices,\n                                     constant const size_t *shape,\n                                     constant const size_t *strides,\n                                     size_t rank) {\n    if (rank == 1) {\n        return 0;\n    } else if (rank == 2) {\n        return indices.x * strides[0];\n    } else {\n        auto idx =\n            indices.x * strides[rank - 2] + indices.y * strides[rank - 3];\n\n        for (int32_t i = rank - 4; i >= 0; i--) {\n            idx += (indices.z % shape[i]) * strides[i];\n            indices.z /= shape[i];\n        }\n        return idx;\n    }\n}\n} // namespace utils\n\n#define INSTANTIATE_COPY(tname, type)                                          \\\n    template [[host_name(                                                      \\\n        \"array_ops::copy_nd1_\" #tname)]] [[kernel]] copy_nd1_t copy_nd1<type>; \\\n    template [[host_name(                                                      \\\n        \"array_ops::copy_nd2_\" #tname)]] [[kernel]] copy_nd2_t copy_nd2<type>; \\\n    template [[host_name(                                                      \\\n        \"array_ops::copy_nd3_\" #tname)]] [[kernel]] copy_nd3_t copy_nd3<type>; \\\n    template [[host_name(                                                      \\\n        \"array_ops::copy_nd4_\" #tname)]] [[kernel]] copy_nd4_t copy_nd4<type>; \\\n    template [[host_name(                                                      \\\n        \"array_ops::copy_nd5_\" #tname)]] [[kernel]] copy_nd5_t copy_nd5<type>; \\\n    template [[host_name(                                                      \\\n        \"array_ops::copy_nd6_\" #tname)]] [[kernel]] copy_nd6_t copy_nd6<type>; \\\n    template [[host_name(                                                      \\\n        \"array_ops::copy_unicast_\" #tname)]] [[kernel]] copy_unicast_t         \\\n        copy_unicast<type>;\n\n#define INSTANTIATE_CAST_OP(tname, itype, otype)                               \\\n    template [[host_name(                                                      \\\n        \"array_ops::cast_\" #tname)]] [[kernel]] cast_t cast<itype, otype>;\n\ntemplate <typename In, typename Out>\n[[kernel]] void cast(device const void *input_b [[buffer(0)]],\n                     device void *output_b [[buffer(1)]],\n                     uint tpig [[thread_position_in_grid]]) {\n    device const In *input = (device const In *)input_b;\n    device Out *output = (device Out *)output_b;\n    output[tpig] = static_cast<Out>(input[tpig]);\n}\n\ntypedef decltype(cast<float, float>) cast_t;\n\ntemplate <typename T>\n[[kernel]] void copy_unicast(device const void *input_b [[buffer(0)]],\n                             device void *output_b [[buffer(1)]],\n                             uint tpig [[thread_position_in_grid]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n    output[tpig] = input[tpig];\n}\n\ntypedef decltype(copy_unicast<float>) copy_unicast_t;\n\ntemplate <typename T>\n[[kernel]] void copy_nd1(device const void *input_b [[buffer(0)]],\n                         constant const size_t *input_strides [[buffer(1)]],\n                         device void *output_b [[buffer(2)]],\n                         constant const size_t *out_shape [[buffer(3)]],\n                         constant const size_t *out_strides [[buffer(4)]],\n                         uint3 tgpig [[threadgroup_position_in_grid]],\n                         ushort3 tpitg [[thread_position_in_threadgroup]],\n                         ushort3 ntg [[threads_per_threadgroup]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n    for (size_t i = tpitg.x; i < out_shape[0]; i += ntg.x) {\n        output[i] = input[i * input_strides[0]];\n    }\n}\n\ntypedef decltype(copy_nd1<float>) copy_nd1_t;\n\ntemplate <typename T>\n[[kernel]] void copy_nd2(device const void *input_b [[buffer(0)]],\n                         constant const size_t *input_strides [[buffer(1)]],\n                         device void *output_b [[buffer(2)]],\n                         constant const size_t *out_shape [[buffer(3)]],\n                         constant const size_t *out_strides [[buffer(4)]],\n                         uint3 tgpig [[threadgroup_position_in_grid]],\n                         ushort3 tpitg [[thread_position_in_threadgroup]],\n                         ushort3 ntg [[threads_per_threadgroup]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n\n    auto idx = utils::indices_to_outer_idx(tgpig, out_shape, input_strides, 2);\n    auto out_idx =\n        utils::indices_to_outer_idx(tgpig, out_shape, out_strides, 2);\n    for (size_t i = tpitg.x; i < out_shape[1]; i += ntg.x) {\n        output[out_idx + i] = input[idx + i * input_strides[1]];\n    }\n}\n\ntypedef decltype(copy_nd2<float>) copy_nd2_t;\n\ntemplate <typename T>\n[[kernel]] void copy_nd3(device const void *input_b [[buffer(0)]],\n                         constant const size_t *input_strides [[buffer(1)]],\n                         device void *output_b [[buffer(2)]],\n                         constant const size_t *out_shape [[buffer(3)]],\n                         constant const size_t *out_strides [[buffer(4)]],\n                         uint3 tgpig [[threadgroup_position_in_grid]],\n                         ushort3 tpitg [[thread_position_in_threadgroup]],\n                         ushort3 ntg [[threads_per_threadgroup]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n\n    auto idx = utils::indices_to_outer_idx(tgpig, out_shape, input_strides, 3);\n    auto out_idx =\n        utils::indices_to_outer_idx(tgpig, out_shape, out_strides, 3);\n    for (size_t i = tpitg.x; i < out_shape[2]; i += ntg.x) {\n        output[out_idx + i] = input[idx + i * input_strides[2]];\n    }\n}\n\ntypedef decltype(copy_nd3<float>) copy_nd3_t;\n\ntemplate <typename T>\n[[kernel]] void copy_nd4(device const void *input_b [[buffer(0)]],\n                         constant const size_t *input_strides [[buffer(1)]],\n                         device void *output_b [[buffer(2)]],\n                         constant const size_t *out_shape [[buffer(3)]],\n                         constant const size_t *out_strides [[buffer(4)]],\n                         uint3 tgpig [[threadgroup_position_in_grid]],\n                         ushort3 tpitg [[thread_position_in_threadgroup]],\n                         ushort3 ntg [[threads_per_threadgroup]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n\n    auto idx = utils::indices_to_outer_idx(tgpig, out_shape, input_strides, 4);\n    auto out_idx =\n        utils::indices_to_outer_idx(tgpig, out_shape, out_strides, 4);\n    for (size_t i = tpitg.x; i < out_shape[3]; i += ntg.x) {\n        output[out_idx + i] = input[idx + i * input_strides[3]];\n    }\n}\n\ntypedef decltype(copy_nd4<float>) copy_nd4_t;\n\ntemplate <typename T>\n[[kernel]] void copy_nd5(device const void *input_b [[buffer(0)]],\n                         constant const size_t *input_strides [[buffer(1)]],\n                         device void *output_b [[buffer(2)]],\n                         constant const size_t *out_shape [[buffer(3)]],\n                         constant const size_t *out_strides [[buffer(4)]],\n                         uint3 tgpig [[threadgroup_position_in_grid]],\n                         ushort3 tpitg [[thread_position_in_threadgroup]],\n                         ushort3 ntg [[threads_per_threadgroup]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n\n    auto idx = utils::indices_to_outer_idx(tgpig, out_shape, input_strides, 5);\n    auto out_idx =\n        utils::indices_to_outer_idx(tgpig, out_shape, out_strides, 5);\n    for (size_t i = tpitg.x; i < out_shape[4]; i += ntg.x) {\n        output[out_idx + i] = input[idx + i * input_strides[4]];\n    }\n}\n\ntypedef decltype(copy_nd5<float>) copy_nd5_t;\n\ntemplate <typename T>\n[[kernel]] void copy_nd6(device const void *input_b [[buffer(0)]],\n                         constant const size_t *input_strides [[buffer(1)]],\n                         device void *output_b [[buffer(2)]],\n                         constant const size_t *out_shape [[buffer(3)]],\n                         constant const size_t *out_strides [[buffer(4)]],\n                         uint3 tgpig [[threadgroup_position_in_grid]],\n                         ushort3 tpitg [[thread_position_in_threadgroup]],\n                         ushort3 ntg [[threads_per_threadgroup]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n\n    auto idx = utils::indices_to_outer_idx(tgpig, out_shape, input_strides, 6);\n    auto out_idx =\n        utils::indices_to_outer_idx(tgpig, out_shape, out_strides, 6);\n    for (size_t i = tpitg.x; i < out_shape[5]; i += ntg.x) {\n        output[out_idx + i] = input[idx + i * input_strides[5]];\n    }\n}\n\ntypedef decltype(copy_nd6<float>) copy_nd6_t;\n\n// Rotate half of the input buffer\n//\n// Y = Concat(Neg(Slice(X, X.shape[-1]/2.., -1)), Slice(X, ..X.shape[-1]/2, -1))\n//\ntemplate <typename T>\n[[kernel]] void rotate_half_nd2(device const void *input_b [[buffer(0)]],\n                                device void *output_b [[buffer(1)]],\n                                constant const size_t *shape [[buffer(2)]],\n                                constant const size_t *strides [[buffer(3)]],\n                                uint2 tpig [[thread_position_in_grid]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n\n    uint2 rotated_tpig = tpig;\n    rotated_tpig.x += shape[1] / 2;\n\n    // output[tpig] = -1 * input[rotated_tpig]\n    // output[rotated_tpig] = input[tpig]\n\n    auto rotated_idx = utils::indices_to_idx_2(rotated_tpig, strides);\n    auto out_idx = utils::indices_to_idx_2(tpig, strides);\n\n    output[out_idx] = -input[rotated_idx];\n\n    auto idx = utils::indices_to_idx_2(tpig, strides);\n    auto rotated_out_idx = utils::indices_to_idx_2(rotated_tpig, strides);\n\n    output[rotated_out_idx] = input[idx];\n}\n\ntypedef decltype(rotate_half_nd2<float>) rotate_half_nd2_t;\n\n#define INSTANTIATE_ROTATE_HALF_OP(tname, type)                                \\\n    template [[host_name(                                                      \\\n        \"array_ops::rotate_half_nd2_\" #tname)]] [[kernel]] rotate_half_nd2_t   \\\n        rotate_half_nd2<type>;\n\n// Copy kernels: only u8/u16/u32/u64 (copy is type-size based)\nINSTANTIATE_COPY(u8, uint8_t)\nINSTANTIATE_COPY(u16, uint16_t)\nINSTANTIATE_COPY(u32, uint32_t)\nINSTANTIATE_COPY(u64, uint64_t)\n\n// Cast kernels: all types\n#define INSTANTIATE_CAST_FROM(tname, type)                                     \\\n    INSTANTIATE_CAST_OP(tname##_bool, type, bool)                              \\\n    INSTANTIATE_CAST_OP(tname##_f32, type, float)                              \\\n    INSTANTIATE_CAST_OP(tname##_f16, type, half)                               \\\n    INSTANTIATE_CAST_OP(tname##_u8, type, uint8_t)                             \\\n    INSTANTIATE_CAST_OP(tname##_u16, type, uint16_t)                           \\\n    INSTANTIATE_CAST_OP(tname##_u32, type, uint32_t)                           \\\n    INSTANTIATE_CAST_OP(tname##_u64, type, uint64_t)                           \\\n    INSTANTIATE_CAST_OP(tname##_i8, type, int8_t)                              \\\n    INSTANTIATE_CAST_OP(tname##_i16, type, int16_t)                            \\\n    INSTANTIATE_CAST_OP(tname##_i32, type, int32_t)                            \\\n    INSTANTIATE_CAST_OP(tname##_i64, type, int64_t)\n\nINSTANTIATE_CAST_FROM(bool, bool)\nINSTANTIATE_CAST_FROM(f32, float)\nINSTANTIATE_CAST_FROM(f16, half)\nINSTANTIATE_CAST_FROM(i8, int8_t)\nINSTANTIATE_CAST_FROM(i16, int16_t)\nINSTANTIATE_CAST_FROM(i32, int32_t)\nINSTANTIATE_CAST_FROM(i64, int64_t)\nINSTANTIATE_CAST_FROM(u8, uint8_t)\nINSTANTIATE_CAST_FROM(u16, uint16_t)\nINSTANTIATE_CAST_FROM(u32, uint32_t)\nINSTANTIATE_CAST_FROM(u64, uint64_t)\n\n// Rotate half: only float types\nINSTANTIATE_ROTATE_HALF_OP(f32, float)\nINSTANTIATE_ROTATE_HALF_OP(f16, half)\n"
  },
  {
    "path": "metal/src/kernels/array/cast.rs",
    "content": "use crate::encoder::EncoderExt;\n\nuse crate::{LibraryName, MetalStream};\nuse derive_new::new;\nuse metal::{MTLSize, NSUInteger};\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, new, PartialEq, Eq, Hash)]\npub struct Cast;\n\nimpl fmt::Display for Cast {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{:?}\", self)\n    }\n}\n\nimpl Cast {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(\n            dt,\n            DatumType::F32\n                | DatumType::F16\n                | DatumType::U8\n                | DatumType::U16\n                | DatumType::U32\n                | DatumType::U64\n                | DatumType::I8\n                | DatumType::I16\n                | DatumType::I32\n                | DatumType::I64\n                | DatumType::Bool\n        )\n    }\n\n    pub fn kernel_name(&self, from_dt: DatumType, to_dt: DatumType) -> TractResult<String> {\n        ensure!(\n            Self::is_supported_dt(from_dt),\n            \"Unsupported from_dt {:?} for metal castop\",\n            from_dt\n        );\n        ensure!(Self::is_supported_dt(to_dt), \"Unsupported to_dt {:?} for metal castop\", to_dt);\n        let from_tname = DeviceTensor::tname(from_dt)?;\n        let to_tname = DeviceTensor::tname(to_dt)?;\n        Ok(format!(\"array_ops::cast_{from_tname}_{to_tname}\"))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        to_dt: DatumType,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(to_dt, input.shape())? };\n        self.dispatch_eval(stream, input, &output)?;\n        stream.wait_until_completed()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n        ensure!(\n            input.shape() == output.shape(),\n            \"Cast I/O don't have the same shape in: {:?}, out: {:?}\",\n            input.shape(),\n            output.shape()\n        );\n\n        let kernel_name = self.kernel_name(input.datum_type(), output.datum_type())?;\n\n        let pipeline = stream.load_pipeline(LibraryName::ArrayOps, &kernel_name)?;\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, output, metal::MTLResourceUsage::Write);\n\n            let grid_size = MTLSize { width: output.len() as NSUInteger, height: 1, depth: 1 };\n            let group_size = MTLSize { width: 1, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n}\n\npub fn metal_cast_dispatch(input: &DeviceTensor, output: &DeviceTensor) -> TractResult<()> {\n    crate::with_metal_stream(|stream| Cast.dispatch_eval(stream, input, output))\n}\n\ncrate::register_metal_op!(tract_core::ops::cast::Cast, |_source, _node, op| {\n    Ok(crate::transform::metal_cast_new(op.to).map(|c| Box::new(c) as _))\n});\n"
  },
  {
    "path": "metal/src/kernels/array/copy.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::{LibraryName, MetalStream};\nuse derive_new::new;\nuse metal::{MTLSize, NSUInteger};\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, new, PartialEq, Eq, Hash)]\npub struct Memcpy;\n\nimpl fmt::Display for Memcpy {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{:?}\", self)\n    }\n}\n\npub fn metal_memcpy_dispatch(\n    input: &DeviceTensor,\n    input_offset: usize,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| Memcpy.dispatch_eval(stream, input, input_offset, output))\n}\n\nimpl Memcpy {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(\n            dt,\n            DatumType::F32\n                | DatumType::F16\n                | DatumType::U8\n                | DatumType::U16\n                | DatumType::U32\n                | DatumType::U64\n                | DatumType::I8\n                | DatumType::I16\n                | DatumType::I32\n                | DatumType::I64\n                | DatumType::Bool\n        )\n    }\n\n    pub fn kernel_name(&self, dt: DatumType) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for metal copyop\", dt);\n        let tname = tract_gpu::utils::BroadcastKind::copy_tname(dt);\n        Ok(format!(\"array_ops::copy_unicast_{tname}\"))\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        input_offset: usize,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(input_offset % input.datum_type().size_of() == 0);\n        ensure!(output.len() <= input.len() - (input_offset / input.datum_type().size_of()));\n\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n\n        let kernel_name = self.kernel_name(input.datum_type())?;\n\n        let pipeline = stream.load_pipeline(LibraryName::ArrayOps, &kernel_name)?;\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor_with_offset(\n                0,\n                input,\n                input_offset as _,\n                metal::MTLResourceUsage::Read,\n            );\n            encoder.set_metal_tensor(1, output, metal::MTLResourceUsage::Write);\n\n            let grid_size = MTLSize { width: output.len() as NSUInteger, height: 1, depth: 1 };\n            let group_size = MTLSize { width: 1, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n\n    pub fn eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        input_offset: usize,\n        output_shape: &[usize],\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), output_shape)? };\n        self.dispatch_eval(stream, input, input_offset, &output)?;\n        stream.wait_until_completed()?;\n        Ok(output)\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/array/dispatch.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::kernels::utils::build_metal_grid_and_groups_for_el_wise_op;\nuse crate::{LibraryName, MetalStream};\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\nuse tract_gpu::utils::BroadcastKind;\n\n/// Single dispatch function for all copy_nd kernel launches.\n/// Used by GpuMultiBroadcastTo, GpuSlice, GpuConcat, and GpuAxisOp.\npub fn metal_copy_nd_dispatch(\n    input: &DeviceTensor,\n    input_offset: usize,\n    input_strides: &[isize],\n    output: &DeviceTensor,\n    output_offset: usize,\n    output_shape: &[usize],\n    output_strides: &[isize],\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| {\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n\n        let kernel_name = BroadcastKind::from_rank(output_shape.len())?\n            .copy_kernel_name(input.datum_type(), \"array_ops::\")?;\n\n        let pipeline = stream.load_pipeline(LibraryName::ArrayOps, &kernel_name)?;\n        let command_buffer = stream.command_buffer();\n\n        // Convert isize strides to usize for Metal buffers\n        let input_strides_usize: TVec<usize> = input_strides.iter().map(|&s| s as usize).collect();\n        let output_strides_usize: TVec<usize> =\n            output_strides.iter().map(|&s| s as usize).collect();\n\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor_with_offset(\n                0,\n                input,\n                input_offset as _,\n                metal::MTLResourceUsage::Read,\n            );\n            encoder.set_slice(1, &input_strides_usize);\n            encoder.set_metal_tensor_with_offset(\n                2,\n                output,\n                output_offset as _,\n                metal::MTLResourceUsage::Write,\n            );\n            encoder.set_slice(3, output_shape);\n            encoder.set_slice(4, &output_strides_usize);\n\n            let (grid_size, group_size) = build_metal_grid_and_groups_for_el_wise_op(\n                output_shape,\n                pipeline.max_total_threads_per_threadgroup() as _,\n            );\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    })\n}\n"
  },
  {
    "path": "metal/src/kernels/array/mod.rs",
    "content": "mod cast;\nmod copy;\nmod dispatch;\nmod rotate_half;\n\npub use cast::Cast;\npub use cast::metal_cast_dispatch;\npub use copy::Memcpy;\npub use dispatch::metal_copy_nd_dispatch;\npub use rotate_half::RotateHalf;\npub use rotate_half::metal_rotate_half_dispatch;\n\npub fn all_functions() -> Vec<String> {\n    use std::collections::HashSet;\n    use tract_gpu::utils::BroadcastKind;\n    let mut functions = HashSet::<String>::new();\n\n    functions.extend(BroadcastKind::all_copy_kernel_names(\"array_ops::\"));\n\n    functions.extend(\n        [\"u8\", \"u16\", \"u32\", \"u64\"]\n            .into_iter()\n            .map(|tname| format!(\"array_ops::copy_unicast_{tname}\")),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt1| {\n                tract_gpu::tensor::DeviceTensor::SUPPORTED_DT.into_iter().map(move |dt2| (dt1, dt2))\n            })\n            .flat_map(|(dt1, dt2)| Cast.kernel_name(dt1, dt2).into_iter()),\n    );\n\n    functions.into_iter().collect()\n}\n"
  },
  {
    "path": "metal/src/kernels/array/rotate_half.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::kernels::utils;\nuse crate::{LibraryName, MetalStream};\nuse anyhow::ensure;\nuse metal::MTLSize;\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct RotateHalf;\n\nimpl fmt::Display for RotateHalf {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{:?}\", self)\n    }\n}\n\nimpl RotateHalf {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(\n            dt,\n            DatumType::F32\n                | DatumType::F16\n                | DatumType::I8\n                | DatumType::I16\n                | DatumType::I32\n                | DatumType::I64\n        )\n    }\n\n    pub fn kernel_name(&self, dt: DatumType) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for metal rotate halfop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        Ok(format!(\"array_ops::rotate_half_nd2_{tname}\"))\n    }\n\n    pub fn eval(&self, stream: &MetalStream, input: &DeviceTensor) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, &output)?;\n        stream.wait_until_completed()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n\n        let shape_nd2 = utils::reshape_to_rank_2(input.shape(), input.rank() - 1);\n        ensure!(\n            shape_nd2[1] % 2 == 0,\n            \"Rotate half required most inner dimension to be a multiple of 2: {:?}\",\n            input.shape()\n        );\n        let strides_nd2 = Tensor::natural_strides(&shape_nd2);\n\n        let kernel_name = self.kernel_name(input.datum_type())?;\n\n        let pipeline = stream.load_pipeline(LibraryName::ArrayOps, &kernel_name)?;\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, output, metal::MTLResourceUsage::Write);\n            encoder.set_slice(2, &shape_nd2);\n            encoder.set_slice(3, &strides_nd2);\n\n            let grid_size =\n                MTLSize { width: (shape_nd2[1] / 2) as _, height: shape_nd2[0] as _, depth: 1 };\n            let group_size = utils::build_metal_size_with_ones();\n\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n}\n\npub fn metal_rotate_half_dispatch(input: &DeviceTensor, output: &DeviceTensor) -> TractResult<()> {\n    crate::with_metal_stream(|stream| RotateHalf.dispatch_eval(stream, input, output))\n}\n\ncrate::register_metal_op!(tract_transformers::ops::apply_rope::RotateHalf, |source, node, _op| {\n    rule_if!(RotateHalf::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::rotate_half::GpuRotateHalf::new(\n        \"Metal\",\n        metal_rotate_half_dispatch,\n    ))))\n});\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::with_borrowed_metal_stream;\n\n    use super::*;\n    use num_traits::AsPrimitive;\n    use tract_core::internal::Tensor;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_transformers::ops::apply_rope;\n\n    fn run_test_case<F>(shape: &[usize]) -> TractResult<()>\n    where\n        F: Copy + 'static + Datum,\n        usize: AsPrimitive<F>,\n    {\n        with_borrowed_metal_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a =\n                Tensor::from_shape(shape, &(0..len).map(|f| -> F { f.as_() }).collect::<Vec<_>>())?;\n\n            let metal_a = a.clone().into_device()?;\n\n            let cpu_output =\n                apply_rope::RotateHalf.eval(tvec![a.clone().into()])?[0].clone().into_tensor();\n            let metal_output = RotateHalf.eval(stream, &metal_a)?;\n\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), Approximation::Exact)\n                .with_context(|| {\n                format!(\n                    \"Input: {:?} Cpu: {:?}, Metal: {:?}\",\n                    a.dump(true),\n                    cpu_output.dump(true),\n                    metal_output.to_host().and_then(|it| it.dump(true))\n                )\n            })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_rotate_half() -> TractResult<()> {\n        run_test_case::<f32>(&[2, 2])?;\n        run_test_case::<f32>(&[512, 512])?;\n        run_test_case::<f32>(&[10, 8, 8])?;\n        run_test_case::<f32>(&[10, 512, 1024])?;\n        run_test_case::<f32>(&[10, 512, 1024])?;\n        run_test_case::<f16>(&[10, 256, 4])?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/bin_ops.metal",
    "content": "#include <metal_stdlib>\n#include <metal_integer>\n#include <metal_math>\n#include <metal_simdgroup_matrix>  // Available from Metal version 2.3 released with OS X 11.0+\n\nusing namespace metal;\n\nnamespace utils {\n    \n    METAL_FUNC uint indices_to_idx_1(uint index, constant const size_t strides[1]) {\n        return index * strides[0];\n    }\n    \n    METAL_FUNC uint indices_to_idx_2(uint2 indices, constant const size_t strides[2]) {\n        return indices.x * strides[1] + indices.y * strides[0];\n    }\n    \n    METAL_FUNC uint indices_to_idx_3(uint3 indices, constant const size_t strides[3]) {\n        return indices.x * strides[2] + indices.y * strides[1] + indices.z * strides[0];\n    }\n    \n    METAL_FUNC uint indices_to_idx_4(uint3 indices,\n                                     constant const size_t shape[4], \n                                     constant const size_t strides[4]) {\n        auto idx = indices.x * strides[3] + indices.y * strides[2];\n        idx += (indices.z % shape[1]) * strides[1];\n        indices.z /= shape[1];\n        idx += indices.z * strides[0];\n        return idx;\n    }\n    \n    METAL_FUNC uint indices_to_idx_5(uint3 indices,\n                                     constant const size_t shape[5], \n                                     constant const size_t strides[5]) {\n        auto idx = indices.x * strides[4] + indices.y * strides[3];\n        idx += (indices.z % shape[2]) * strides[2];\n        indices.z /= shape[2];\n        idx += (indices.z % shape[1]) * strides[1];\n        indices.z /= shape[1];\n        idx += indices.z * strides[0];\n        return idx;\n    }\n}\n\n/*\n * Based on code from:\n * https://github.com/ml-explore/mlx/blob/main/mlx/backend/metal/kernels/binary_ops.h\n */\n\nstruct Add {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x + y;\n    }\n};\n\nstruct Div {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x / y;\n    }\n};\n\nstruct Sub {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x - y;\n    }\n};\n\nstruct Mul {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x * y;\n    }\n};\n\nstruct Equals {\n    template <typename T>\n    bool operator()(T x, T y) {\n        return x == y;\n    }\n};\n\nstruct NotEquals {\n    template <typename T>\n    bool operator()(T x, T y) {\n        return x != y;\n    }\n};\n\nstruct Greater {\n    template <typename T>\n    bool operator()(T x, T y) {\n        return x > y;\n    }\n};\n\nstruct GreaterEqual {\n    template <typename T>\n    bool operator()(T x, T y) {\n        return x >= y;\n    }\n};\n\nstruct Less {\n    template <typename T>\n    bool operator()(T x, T y) {\n        return x < y;\n    }\n};\n\nstruct LessEqual {\n    template <typename T>\n    bool operator()(T x, T y) {\n        return x <= y;\n    }\n};\n\nstruct And {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x && y;\n    };\n};\n\nstruct Or {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x || y;\n    };\n};\n\nstruct Min {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x < y ? x : y;\n    }\n};\n\nstruct Max {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x > y ? x : y;\n    }\n};\n\nstruct BitAnd {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x & y;\n    }\n};\n\nstruct BitOr {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x | y;\n    }\n};\n\nstruct BitXor {\n    template <typename T>\n    T operator()(T x, T y) {\n        return x ^ y;\n    }\n};\n\nstruct Pow {\n    template <typename T>\n    metal::enable_if_t<!metal::is_integral_v<T>, T>\n    operator()(T base, T exp) {\n        return metal::pow(base, exp);\n    }\n    \n    template <typename T>\n    metal::enable_if_t<metal::is_integral_v<T>, T>\n    operator()(T base, T exp) {\n        T res = 1;\n        while (exp) {\n            if (exp & 1) {\n                res *= base;\n            }\n            exp >>= 1;\n            base *= base;\n        }\n        return res;\n    }\n};\n\n#define INSTANTIATE_1ROW_BIN_OP()                             \\\ntemplate [[host_name(\"bin_ops::add_1row_f32\")]] [[kernel]]     \\\nbin_op_1row_t bin_op_1row<float4, Add>;                         \\\ntemplate [[host_name(\"bin_ops::sub_1row_f32\")]] [[kernel]]     \\\nbin_op_1row_t bin_op_1row<float4, Sub>;                         \\\ntemplate [[host_name(\"bin_ops::div_1row_f32\")]] [[kernel]]     \\\nbin_op_1row_t bin_op_1row<float4, Div>;                         \\\ntemplate [[host_name(\"bin_ops::mul_1row_f32\")]] [[kernel]]     \\\nbin_op_1row_t bin_op_1row<float4, Mul>;                         \\\ntemplate [[host_name(\"bin_ops::add_1row_f16\")]] [[kernel]]     \\\nbin_op_1row_t bin_op_1row<half4, Add>;                         \\\ntemplate [[host_name(\"bin_ops::sub_1row_f16\")]] [[kernel]]     \\\nbin_op_1row_t bin_op_1row<half4, Sub>;                         \\\ntemplate [[host_name(\"bin_ops::dib_1row_f16\")]] [[kernel]]     \\\nbin_op_1row_t bin_op_1row<half4, Div>;                         \\\ntemplate [[host_name(\"bin_ops::mul_1row_f16\")]] [[kernel]]     \\\nbin_op_1row_t bin_op_1row<half4, Mul>;                         \\\n\n#define INSTANTIATE_BIN_OP(name, op, itname, itype, otype)                    \\\ntemplate [[host_name(\"bin_ops::\" #name \"_\" #itname)]] [[kernel]]      \\\nbin_op_t bin_op<itype, otype, op>;                            \\\n\n#define INSTANTIATE_FLOAT(name, op)                     \\\nINSTANTIATE_BIN_OP(name, op, f32, float, float)         \\\nINSTANTIATE_BIN_OP(name, op, f16, half, half)          \n\n#define INSTANTIATE_FLOAT_BOOL(name, op)                \\\nINSTANTIATE_BIN_OP(name, op, f32, float, bool)          \\\nINSTANTIATE_BIN_OP(name, op, f16, half, bool)          \n\n#define INSTANTIATE_INTEGER(name, op)                    \\\nINSTANTIATE_BIN_OP(name, op, u8,  uint8_t, uint8_t)      \\\nINSTANTIATE_BIN_OP(name, op, u16, uint16_t, uint16_t)    \\\nINSTANTIATE_BIN_OP(name, op, u32, uint32_t, uint32_t)    \\\nINSTANTIATE_BIN_OP(name, op, u64, uint64_t, uint64_t)    \\\nINSTANTIATE_BIN_OP(name, op, i8,  int8_t, int8_t)        \\\nINSTANTIATE_BIN_OP(name, op, i16, int16_t, int16_t)      \\\nINSTANTIATE_BIN_OP(name, op, i32, int32_t, int32_t)      \\\nINSTANTIATE_BIN_OP(name, op, i64, int64_t, int64_t)       \n\n#define INSTANTIATE_INTEGER_BOOL(name, op)               \\\nINSTANTIATE_BIN_OP(name, op, u8,  uint8_t, bool)         \\\nINSTANTIATE_BIN_OP(name, op, u16, uint16_t, bool)        \\\nINSTANTIATE_BIN_OP(name, op, u32, uint32_t, bool)        \\\nINSTANTIATE_BIN_OP(name, op, u64, uint64_t, bool)        \\\nINSTANTIATE_BIN_OP(name, op, i8,  int8_t, bool)          \\\nINSTANTIATE_BIN_OP(name, op, i16, int16_t, bool)         \\\nINSTANTIATE_BIN_OP(name, op, i32, int32_t, bool)         \\\nINSTANTIATE_BIN_OP(name, op, i64, int64_t, bool)        \n\n#define INSTANTIATE_ALL_TYPES(name, op)                  \\\nINSTANTIATE_FLOAT(name, op)                              \\\nINSTANTIATE_INTEGER(name, op)  \n\n#define INSTANTIATE_ALL_TYPES_BOOL(name, op)             \\\nINSTANTIATE_FLOAT_BOOL(name, op)                         \\\nINSTANTIATE_INTEGER_BOOL(name, op)                \n\ntemplate<typename In, typename Out, typename Op>\n[[kernel]] void bin_op(device const void *lhs_b [[buffer(0)]],\n                    constant const size_t * lhs_shape [[buffer(1)]],\n                    constant const size_t * lhs_strides [[buffer(2)]],\n                    device const void *rhs_b [[buffer(3)]],\n                    constant const size_t * rhs_shape [[buffer(4)]],\n                    constant const size_t * rhs_strides [[buffer(5)]],\n                    device void *output_b [[buffer(6)]],\n                    constant const size_t * out_shape [[buffer(7)]],\n                    constant const size_t * out_strides [[buffer(8)]],\n                    uint3   tgpig[[threadgroup_position_in_grid]],\n                    ushort3 tpitg[[thread_position_in_threadgroup]],\n                    ushort3   ntg[[threads_per_threadgroup]]) {\n        device const In * lhs = (device const In *)lhs_b;\n        device const In * rhs = (device const In *)rhs_b;\n        device  Out * output = (device Out *)output_b;\n\n        auto lhs_idx = tgpig.z * lhs_strides[0] + tgpig.y * lhs_strides[1] + tgpig.x * lhs_strides[2];\n        auto rhs_idx = tgpig.z * rhs_strides[0] + tgpig.y * rhs_strides[1] + tgpig.x * rhs_strides[2];\n        auto out_idx = tgpig.z * out_strides[0] + tgpig.y * out_strides[1] + tgpig.x * out_strides[2];\n\n        for (size_t i = tpitg.x; i < out_shape[3]; i += ntg.x) {\n            output[out_idx + i] = Op()(lhs[lhs_idx + i * lhs_strides[3]], rhs[rhs_idx + i * rhs_strides[3]]);\n        }\n}\n\ntypedef decltype(bin_op<float, float, Mul>) bin_op_t;\n\n\ntemplate<typename T4, typename Op>\n[[kernel]] void bin_op_1row(device const void *lhs_b [[buffer(0)]],\n                           device const void *rhs_b [[buffer(1)]],\n                           device void *output_b [[buffer(2)]],\n                           device const size_t & n [[buffer(3)]],\n                           uint tpig[[thread_position_in_grid]]) {\n    device const T4 * lhs = (device const T4 *)lhs_b;\n    device const T4 * rhs = (device const T4 *)rhs_b;\n    device  T4 * output = (device  T4 *)output_b;\n\n    const uint nb = n/4;\n    output[tpig] = Op()(lhs[tpig], rhs[tpig % nb]);\n}\n\ntypedef decltype(bin_op_1row<float4, Mul>) bin_op_1row_t;\n\nINSTANTIATE_ALL_TYPES(mul, Mul)\nINSTANTIATE_ALL_TYPES(div, Div)\nINSTANTIATE_ALL_TYPES(add, Add)\nINSTANTIATE_ALL_TYPES(sub, Sub)\nINSTANTIATE_ALL_TYPES(pow, Pow)\nINSTANTIATE_ALL_TYPES_BOOL(lt, Less)\nINSTANTIATE_ALL_TYPES_BOOL(gt, Greater)\nINSTANTIATE_ALL_TYPES_BOOL(lte, LessEqual)\nINSTANTIATE_ALL_TYPES_BOOL(gte, GreaterEqual)\nINSTANTIATE_ALL_TYPES_BOOL(eq, Equals)\nINSTANTIATE_ALL_TYPES_BOOL(ne, NotEquals)\nINSTANTIATE_ALL_TYPES(min, Min)\nINSTANTIATE_ALL_TYPES(max, Max)\nINSTANTIATE_INTEGER(bitand, BitAnd)\nINSTANTIATE_INTEGER(bitor, BitOr)\nINSTANTIATE_INTEGER(bitxor, BitXor)\nINSTANTIATE_BIN_OP(and, And, bool, bool, bool)\nINSTANTIATE_BIN_OP(or, Or, bool, bool, bool)\n\nINSTANTIATE_1ROW_BIN_OP()\n\n// --- Iff (select) kernel ---\n\ntemplate <typename T>\n[[kernel]] void iff_generic(\n    device const bool *cond [[buffer(0)]],\n    device const T *then_values [[buffer(1)]],\n    device const T *else_values [[buffer(2)]],\n    device T *out [[buffer(3)]],\n    constant const size_t *out_shape [[buffer(4)]],\n    constant const size_t *cond_strides [[buffer(5)]],\n    constant const size_t *then_strides [[buffer(6)]],\n    constant const size_t *else_strides [[buffer(7)]],\n    constant const size_t *out_strides [[buffer(8)]],\n    uint tpig [[thread_position_in_grid]])\n{\n    size_t total = out_shape[0] * out_shape[1] * out_shape[2] * out_shape[3] * out_shape[4];\n    if (tpig >= total) return;\n\n    size_t tmp = tpig;\n    size_t i4 = tmp % out_shape[4]; tmp /= out_shape[4];\n    size_t i3 = tmp % out_shape[3]; tmp /= out_shape[3];\n    size_t i2 = tmp % out_shape[2]; tmp /= out_shape[2];\n    size_t i1 = tmp % out_shape[1]; tmp /= out_shape[1];\n    size_t i0 = tmp;\n\n    size_t icond = i0 * cond_strides[0] + i1 * cond_strides[1] + i2 * cond_strides[2]\n                 + i3 * cond_strides[3] + i4 * cond_strides[4];\n    bool pick = cond[icond];\n\n    size_t offset = i0 * (pick ? then_strides[0] : else_strides[0])\n                  + i1 * (pick ? then_strides[1] : else_strides[1])\n                  + i2 * (pick ? then_strides[2] : else_strides[2])\n                  + i3 * (pick ? then_strides[3] : else_strides[3])\n                  + i4 * (pick ? then_strides[4] : else_strides[4]);\n\n    size_t io = i0 * out_strides[0] + i1 * out_strides[1] + i2 * out_strides[2]\n              + i3 * out_strides[3] + i4 * out_strides[4];\n\n    out[io] = (pick ? then_values : else_values)[offset];\n}\n\n#define INSTANTIATE_IFF(tname, type) \\\n    template [[host_name(\"bin_ops::iff_generic_\" #tname)]] [[kernel]] \\\n    void iff_generic<type>( \\\n        device const bool*, device const type*, device const type*, device type*, \\\n        constant const size_t*, constant const size_t*, constant const size_t*, \\\n        constant const size_t*, constant const size_t*, uint);\n\nINSTANTIATE_IFF(u8, uint8_t)\nINSTANTIATE_IFF(u16, uint16_t)\nINSTANTIATE_IFF(u32, uint32_t)\nINSTANTIATE_IFF(u64, uint64_t)\n"
  },
  {
    "path": "metal/src/kernels/bin_ops.rs",
    "content": "use super::BroadcastKind;\nuse super::utils::build_metal_grid_and_groups_for_el_wise_op;\nuse crate::encoder::EncoderExt;\nuse crate::kernels::utils::compute_broadcast_strides;\nuse crate::{LibraryName, MetalStream};\nuse anyhow::ensure;\nuse metal::{MTLSize, NSUInteger};\nuse std::ffi::c_void;\nuse tract_core::internal::tract_smallvec::SmallVec;\nuse tract_core::internal::*;\nuse tract_core::ops::binary::BinMiniOp;\nuse tract_gpu::tensor::DeviceTensor;\n\nconst ALL_OP_NAMES: &[&str] = &[\n    \"mul\", \"add\", \"div\", \"sub\", \"pow\", \"min\", \"max\", \"gt\", \"gte\", \"eq\", \"ne\", \"lt\", \"lte\", \"and\",\n    \"or\", \"bitor\", \"bitand\", \"bitxor\",\n];\n\npub fn all_functions() -> Vec<String> {\n    ALL_OP_NAMES\n        .iter()\n        .flat_map(|kname| {\n            DeviceTensor::SUPPORTED_DT.into_iter().flat_map(move |dt| {\n                let tname = DeviceTensor::tname(dt).ok()?;\n                Some([true, false].into_iter().map(move |row| {\n                    if row {\n                        format!(\"bin_ops::{kname}_1row_{tname}\")\n                    } else {\n                        format!(\"bin_ops::{kname}_{tname}\")\n                    }\n                }))\n            })\n        })\n        .flatten()\n        .chain(\n            [\"u8\", \"u16\", \"u32\", \"u64\"]\n                .into_iter()\n                .map(|tname| format!(\"bin_ops::iff_generic_{tname}\")),\n        )\n        .collect()\n}\n\npub fn is_supported(mini_op: &dyn BinMiniOp, dt: DatumType) -> bool {\n    ALL_OP_NAMES.contains(&mini_op.name().to_lowercase().as_str())\n        && (dt.is_number() || dt.is::<bool>())\n}\n\nfn kernel_name(op_name: &str, dt: DatumType, use_row_kernel: bool) -> TractResult<String> {\n    let tname = DeviceTensor::tname(dt)?;\n    if use_row_kernel {\n        Ok(format!(\"bin_ops::{op_name}_1row_{tname}\"))\n    } else {\n        Ok(format!(\"bin_ops::{op_name}_{tname}\"))\n    }\n}\n\nfn can_use_row_kernel(mini_op: &dyn BinMiniOp, lhs: &DeviceTensor, rhs: &DeviceTensor) -> bool {\n    let compatible_op = matches!(mini_op.name(), \"Mul\" | \"Add\" | \"Div\" | \"Sub\");\n    let compatible_type = matches!(lhs.datum_type(), DatumType::F16 | DatumType::F32);\n    let rank = lhs.rank();\n\n    compatible_op\n        && compatible_type\n        && (rank > 0)\n        && ((rhs.len() == rhs.shape()[rank - 1])\n            || ((lhs.len() == lhs.shape()[rank - 1]) && matches!(mini_op.name(), \"Mul\" | \"Add\")))\n        && (lhs.shape()[rank - 1] % 4 == 0)\n        && (rhs.shape()[rank - 1] % 4 == 0)\n}\n\nfn reshape_to_rank_4_with_broadcast(\n    lhs: &DeviceTensor,\n    rhs: &DeviceTensor,\n    out: &DeviceTensor,\n) -> TractResult<(TVec<usize>, TVec<usize>, TVec<usize>)> {\n    let rank = lhs.rank();\n\n    if rank <= 4 {\n        let mut pad = |shape: &[usize]| {\n            let mut result = [1; 4];\n            result[4 - shape.len()..].copy_from_slice(shape);\n            result.into()\n        };\n        return Ok((pad(lhs.shape()), pad(rhs.shape()), pad(out.shape())));\n    }\n\n    if lhs.shape() == rhs.shape() {\n        let mut shape = vec![lhs.shape()[..rank - 3].iter().product::<usize>()];\n        shape.extend(&lhs.shape()[rank - 3..]);\n\n        Ok((shape.clone().into(), shape.clone().into(), shape.into()))\n    } else {\n        let broadcast_axes: Vec<usize> = (0..lhs.rank())\n            .filter(|ix| lhs.shape()[*ix] != rhs.shape()[*ix] || lhs.shape()[*ix] == 1)\n            .collect();\n\n        let mut segments = vec![];\n        let mut current_segment = vec![0];\n        let mut current_is_broadcast = broadcast_axes.contains(&0);\n\n        for i in 1..rank {\n            let is_broadcast = broadcast_axes.contains(&i);\n            if is_broadcast == current_is_broadcast {\n                current_segment.push(i);\n            } else {\n                segments.push((current_is_broadcast, current_segment));\n                current_segment = vec![i];\n                current_is_broadcast = is_broadcast;\n            }\n        }\n        segments.push((current_is_broadcast, current_segment));\n\n        let mut reshaped_groups: Vec<Vec<usize>> = vec![vec![], vec![], vec![], vec![]];\n        let mut group_idx = 0;\n        for (_, segment) in segments {\n            reshaped_groups[group_idx].extend(segment);\n            group_idx += 1;\n            ensure!(group_idx < 4, \"Cannot reshape to rank 4\");\n        }\n\n        fn compute_shape(shape: &[usize], groups: &[Vec<usize>]) -> TVec<usize> {\n            let mut result = [1; 4];\n            for (i, group) in groups.iter().enumerate() {\n                result[i] = group.iter().map(|&dim| shape[dim]).product();\n            }\n            result.into()\n        }\n\n        Ok((\n            compute_shape(lhs.shape(), &reshaped_groups),\n            compute_shape(rhs.shape(), &reshaped_groups),\n            compute_shape(out.shape(), &reshaped_groups),\n        ))\n    }\n}\n\nfn natural_strides(shape: &[usize]) -> SmallVec<[isize; 4]> {\n    let mut strides = SmallVec::from_elem(1, shape.len());\n    for i in (0..shape.len()).rev().skip(1) {\n        strides[i] = strides[i + 1] * shape[i + 1] as isize;\n    }\n    strides\n}\n\npub fn dispatch_eval(\n    stream: &MetalStream,\n    mini_op: &dyn BinMiniOp,\n    lhs: &DeviceTensor,\n    rhs: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    stream.retain_tensor(lhs);\n    stream.retain_tensor(rhs);\n    stream.retain_tensor(output);\n\n    ensure!(lhs.rank() == rhs.rank());\n    let rank = lhs.rank();\n\n    let use_row = can_use_row_kernel(mini_op, lhs, rhs);\n    let op_name = mini_op.name().to_lowercase();\n    let kname = kernel_name(&op_name, lhs.datum_type(), use_row)?;\n\n    if use_row {\n        let pipeline = stream.load_pipeline(LibraryName::BinOps, &kname)?;\n\n        let (a, b) = if rhs.len() == rhs.shape()[rank - 1] { (lhs, rhs) } else { (rhs, lhs) };\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, a, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, b, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(2, output, metal::MTLResourceUsage::Write);\n            encoder.set_bytes(\n                3,\n                std::mem::size_of::<usize>() as u64,\n                &b.len() as *const usize as *const c_void,\n            );\n\n            let grid_size =\n                MTLSize { width: (output.len() / 4) as NSUInteger, height: 1, depth: 1 };\n            let group_size = MTLSize { width: 1, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n    } else {\n        let (lhs_shape, rhs_shape, out_shape) = reshape_to_rank_4_with_broadcast(lhs, rhs, output)?;\n\n        let lhs_strides =\n            compute_broadcast_strides::<usize>(&lhs_shape, &*natural_strides(&lhs_shape))?;\n        let rhs_strides =\n            compute_broadcast_strides::<usize>(&rhs_shape, &natural_strides(&rhs_shape))?;\n        let out_strides =\n            compute_broadcast_strides::<usize>(&out_shape, &natural_strides(&out_shape))?;\n\n        let pipeline = stream.load_pipeline(LibraryName::BinOps, &kname)?;\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, lhs, metal::MTLResourceUsage::Read);\n            encoder.set_slice(1, &lhs_shape);\n            encoder.set_slice(2, &lhs_strides);\n            encoder.set_metal_tensor(3, rhs, metal::MTLResourceUsage::Read);\n            encoder.set_slice(4, &rhs_shape);\n            encoder.set_slice(5, &rhs_strides);\n            encoder.set_metal_tensor(6, output, metal::MTLResourceUsage::Write);\n            encoder.set_slice(7, &out_shape);\n            encoder.set_slice(8, &out_strides);\n\n            let (grid_size, group_size) = build_metal_grid_and_groups_for_el_wise_op(\n                &out_shape,\n                pipeline.max_total_threads_per_threadgroup() as _,\n            );\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n    }\n    Ok(())\n}\n\npub fn metal_bin_op_dispatch(\n    mini_op: &dyn BinMiniOp,\n    lhs: &DeviceTensor,\n    rhs: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| dispatch_eval(stream, mini_op, lhs, rhs, output))\n}\n\npub fn metal_bin_op(mini_op: Box<dyn BinMiniOp>) -> tract_gpu::ops::binary::GpuBinOp {\n    tract_gpu::ops::binary::GpuBinOp::new(mini_op, \"Metal\", metal_bin_op_dispatch)\n}\n\ncrate::register_metal_op!(tract_core::ops::binary::TypedBinOp, |source, node, op| {\n    rule_if!(is_supported(&*op.0, source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(metal_bin_op(op.0.clone()))))\n});\n\ncrate::register_metal_op!(tract_core::ops::logic::Iff, |_source, _node, _op| {\n    Ok(Some(Box::new(tract_gpu::ops::iff::GpuIff::new(\"Metal\", metal_iff_dispatch))))\n});\n\npub fn metal_iff_dispatch(\n    cond: &DeviceTensor,\n    then_value: &DeviceTensor,\n    else_value: &DeviceTensor,\n    cond_strides: &[isize],\n    then_strides: &[isize],\n    else_strides: &[isize],\n    output: &DeviceTensor,\n    output_shape: &[usize],\n    output_strides: &[isize],\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| {\n        stream.retain_tensor(cond);\n        stream.retain_tensor(then_value);\n        stream.retain_tensor(else_value);\n        stream.retain_tensor(output);\n\n        let tname = tract_gpu::utils::BroadcastKind::copy_tname(output.datum_type());\n        let kernel_name = format!(\"bin_ops::iff_generic_{tname}\");\n        let total_elems: usize = output_shape.iter().product();\n\n        let pipeline = stream.load_pipeline(LibraryName::BinOps, &kernel_name)?;\n        let command_buffer = stream.command_buffer();\n\n        let cond_strides_usize: TVec<usize> = cond_strides.iter().map(|&s| s as usize).collect();\n        let then_strides_usize: TVec<usize> = then_strides.iter().map(|&s| s as usize).collect();\n        let else_strides_usize: TVec<usize> = else_strides.iter().map(|&s| s as usize).collect();\n        let out_strides_usize: TVec<usize> = output_strides.iter().map(|&s| s as usize).collect();\n\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, cond, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, then_value, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(2, else_value, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(3, output, metal::MTLResourceUsage::Write);\n            encoder.set_slice(4, output_shape);\n            encoder.set_slice(5, &cond_strides_usize);\n            encoder.set_slice(6, &then_strides_usize);\n            encoder.set_slice(7, &else_strides_usize);\n            encoder.set_slice(8, &out_strides_usize);\n\n            let grid_size = MTLSize { width: total_elems as NSUInteger, height: 1, depth: 1 };\n            let group_size = MTLSize { width: 1, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::with_borrowed_metal_stream;\n\n    use super::*;\n    use tract_gpu::tensor::IntoDevice;\n\n    fn reference<FI: Datum, FO: Datum>(\n        a: &Tensor,\n        b: &Tensor,\n        cab: impl Fn(&mut FO, &FI, &FI),\n    ) -> TractResult<Tensor> {\n        let out_shape = tract_core::broadcast::multi_broadcast(&[a.shape(), b.shape()])?;\n        let mut out = unsafe { Tensor::uninitialized_dt(FO::datum_type(), &out_shape)? };\n        let a_view = a.to_plain_array_view::<FI>()?;\n        let b_view = b.to_plain_array_view::<FI>()?;\n        let mut plain_out = out.try_as_plain_mut()?;\n        let mut c = plain_out.to_array_view_mut::<FO>()?;\n        tract_core::ndarray::Zip::from(&mut c)\n            .and_broadcast(a_view)\n            .and_broadcast(b_view)\n            .for_each(cab);\n        Ok(out)\n    }\n\n    fn run_test_case_logic(\n        mini_op: &dyn BinMiniOp,\n        a_shape: &[usize],\n        b_shape: &[usize],\n        cab: impl Fn(&mut bool, &bool, &bool),\n    ) -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let a_len = a_shape.iter().product::<usize>();\n            let b_len = b_shape.iter().product::<usize>();\n\n            let a =\n                Tensor::from_shape(a_shape, &(0..a_len).map(|f| f % 2 == 0).collect::<Vec<_>>())?\n                    .into_device()?;\n            let b =\n                Tensor::from_shape(b_shape, &(0..b_len).map(|f| f % 4 == 0).collect::<Vec<_>>())?\n                    .into_device()?;\n\n            let out_dt = mini_op.result_datum_type(a.datum_type(), b.datum_type())?;\n            let out_shape = tract_core::broadcast::multi_broadcast(&[a.shape(), b.shape()])?;\n            let output = unsafe { DeviceTensor::uninitialized_dt(out_dt, &out_shape)? };\n            dispatch_eval(stream, mini_op, &a, &b, &output)?;\n            stream.wait_until_completed()?;\n\n            let ref_output = reference::<bool, bool>(\n                &a.to_host()?.into_tensor(),\n                &b.to_host()?.into_tensor(),\n                cab,\n            )?;\n\n            assert_eq!(output.to_host()?.into_tensor(), ref_output);\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_logic() -> TractResult<()> {\n        run_test_case_logic(&tract_core::ops::logic::And, &[2, 4], &[2, 4], |c, a, b| {\n            *c = *a && *b\n        })?;\n        run_test_case_logic(&tract_core::ops::logic::Or, &[2, 4], &[2, 4], |c, a, b| {\n            *c = *a || *b\n        })?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/conv.metal",
    "content": "#include <metal_stdlib>\nusing namespace metal;\n\n// Direct convolution kernel — one thread per output spatial position.\n// Grid: (ceil(spatial_out / threads_per_group), output_channels, batch_size)\n//\n// Buffer layout:\n//   0: input         [T]\n//   1: in_shape      [N, C, spatial...]  (2 + georank ints)\n//   2: in_strides    [N, C, spatial...]  (2 + georank ints)\n//   3: weights       [T]\n//   4: ker_params    [groups, co_per_group, ci_per_group, ker_spatial...]  (3 + georank ints)\n//   5: ker_strides   [g_stride, o_stride, i_stride, spatial...]  (3 + georank ints)\n//   6: bias          [T] (may be empty)\n//   7: bias_stride   scalar int32 (-1 = no bias)\n//   8: pad           [spatial...]  (georank ints)\n//   9: strides       [spatial...]  (georank ints)\n//  10: dilations     [spatial...]  (georank ints)\n//  11: output        [T]\n//  12: out_shape     [N, C, spatial...]  (2 + georank ints)\n//  13: out_strides   [N, C, spatial...]  (2 + georank ints)\n\ntemplate <typename T, int GEORANK>\nvoid conv_generic_impl(\n    device const T *input,\n    constant int32_t *in_shape,\n    constant int32_t *in_strides,\n    device const T *weights,\n    constant int32_t *ker_params,\n    constant int32_t *ker_strides,\n    device const T *bias,\n    int32_t bias_stride,\n    constant int32_t *p,\n    constant int32_t *str,\n    constant int32_t *dil,\n    device T *output,\n    constant int32_t *out_shape,\n    constant int32_t *out_strides,\n    uint3 gid)\n{\n    int n  = gid.z;\n    int co = gid.y;\n    int xyz = gid.x;\n\n    int co_per_group = ker_params[1];\n    int ci_per_group = ker_params[2];\n    int group        = co / co_per_group;\n\n    // Decompose linear index into per-axis output coords (last axis fastest)\n    int ox[GEORANK];\n    {\n        int rem = xyz;\n        for (int d = GEORANK - 1; d >= 0; d--) {\n            int dim = out_shape[2 + d];\n            ox[d] = rem % dim;\n            rem /= dim;\n        }\n    }\n\n    // Bounds check\n    for (int d = 0; d < GEORANK; d++) {\n        if (ox[d] >= out_shape[2 + d]) return;\n    }\n    if (n >= out_shape[0] || co >= out_shape[1]) return;\n\n    device const T *pfi = input + n * in_strides[0]\n                          + ci_per_group * group * in_strides[1];\n    device const T *pfk = weights + co * ker_strides[1];\n\n    float sum = (bias_stride >= 0) ? float(bias[co * bias_stride]) : 0.0f;\n\n    for (int ci = 0; ci < ci_per_group; ci++) {\n        // Recursive-style nested loop over spatial kernel dims.\n        // Unrolled at compile time thanks to constexpr GEORANK.\n        if (GEORANK == 1) {\n            for (int k0 = 0; k0 < ker_params[3]; k0++) {\n                int x0 = ox[0] * str[0] + k0 * dil[0] - p[0];\n                if (x0 < 0 || x0 >= in_shape[2]) continue;\n                sum += float(pfi[ci * in_strides[1] + x0 * in_strides[2]])\n                     * float(pfk[ci * ker_strides[2] + k0 * ker_strides[3]]);\n            }\n        } else if (GEORANK == 2) {\n            for (int k0 = 0; k0 < ker_params[3]; k0++) {\n                int x0 = ox[0] * str[0] + k0 * dil[0] - p[0];\n                if (x0 < 0 || x0 >= in_shape[2]) continue;\n                for (int k1 = 0; k1 < ker_params[4]; k1++) {\n                    int x1 = ox[1] * str[1] + k1 * dil[1] - p[1];\n                    if (x1 < 0 || x1 >= in_shape[3]) continue;\n                    sum += float(pfi[ci * in_strides[1] + x0 * in_strides[2] + x1 * in_strides[3]])\n                         * float(pfk[ci * ker_strides[2] + k0 * ker_strides[3] + k1 * ker_strides[4]]);\n                }\n            }\n        } else if (GEORANK == 3) {\n            for (int k0 = 0; k0 < ker_params[3]; k0++) {\n                int x0 = ox[0] * str[0] + k0 * dil[0] - p[0];\n                if (x0 < 0 || x0 >= in_shape[2]) continue;\n                for (int k1 = 0; k1 < ker_params[4]; k1++) {\n                    int x1 = ox[1] * str[1] + k1 * dil[1] - p[1];\n                    if (x1 < 0 || x1 >= in_shape[3]) continue;\n                    for (int k2 = 0; k2 < ker_params[5]; k2++) {\n                        int x2 = ox[2] * str[2] + k2 * dil[2] - p[2];\n                        if (x2 < 0 || x2 >= in_shape[4]) continue;\n                        sum += float(pfi[ci * in_strides[1] + x0 * in_strides[2]\n                                        + x1 * in_strides[3] + x2 * in_strides[4]])\n                             * float(pfk[ci * ker_strides[2] + k0 * ker_strides[3]\n                                        + k1 * ker_strides[4] + k2 * ker_strides[5]]);\n                    }\n                }\n            }\n        } else if (GEORANK == 4) {\n            for (int k0 = 0; k0 < ker_params[3]; k0++) {\n                int x0 = ox[0] * str[0] + k0 * dil[0] - p[0];\n                if (x0 < 0 || x0 >= in_shape[2]) continue;\n                for (int k1 = 0; k1 < ker_params[4]; k1++) {\n                    int x1 = ox[1] * str[1] + k1 * dil[1] - p[1];\n                    if (x1 < 0 || x1 >= in_shape[3]) continue;\n                    for (int k2 = 0; k2 < ker_params[5]; k2++) {\n                        int x2 = ox[2] * str[2] + k2 * dil[2] - p[2];\n                        if (x2 < 0 || x2 >= in_shape[4]) continue;\n                        for (int k3 = 0; k3 < ker_params[6]; k3++) {\n                            int x3 = ox[3] * str[3] + k3 * dil[3] - p[3];\n                            if (x3 < 0 || x3 >= in_shape[5]) continue;\n                            sum += float(pfi[ci * in_strides[1] + x0 * in_strides[2]\n                                            + x1 * in_strides[3] + x2 * in_strides[4]\n                                            + x3 * in_strides[5]])\n                                 * float(pfk[ci * ker_strides[2] + k0 * ker_strides[3]\n                                            + k1 * ker_strides[4] + k2 * ker_strides[5]\n                                            + k3 * ker_strides[6]]);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    int out_offset = n * out_strides[0] + co * out_strides[1];\n    for (int d = 0; d < GEORANK; d++) {\n        out_offset += ox[d] * out_strides[2 + d];\n    }\n    output[out_offset] = T(sum);\n}\n\n// --- Kernel entry points: 8 variants (f32/f16 × georank 1-4) ---\n\n#define CONV_ENTRY(GEORANK, SUFFIX, T)                                              \\\nkernel void conv##GEORANK##d_##SUFFIX##_generic(                                    \\\n    device const T *input          [[buffer(0)]],                                   \\\n    constant int32_t *in_shape     [[buffer(1)]],                                   \\\n    constant int32_t *in_strides   [[buffer(2)]],                                   \\\n    device const T *weights        [[buffer(3)]],                                   \\\n    constant int32_t *ker_params   [[buffer(4)]],                                   \\\n    constant int32_t *ker_strides  [[buffer(5)]],                                   \\\n    device const T *bias           [[buffer(6)]],                                   \\\n    constant int32_t &bias_stride  [[buffer(7)]],                                   \\\n    constant int32_t *p            [[buffer(8)]],                                   \\\n    constant int32_t *str          [[buffer(9)]],                                   \\\n    constant int32_t *dil          [[buffer(10)]],                                  \\\n    device T *output               [[buffer(11)]],                                  \\\n    constant int32_t *out_shape    [[buffer(12)]],                                  \\\n    constant int32_t *out_strides  [[buffer(13)]],                                  \\\n    uint3 gid                      [[thread_position_in_grid]])                     \\\n{                                                                                   \\\n    conv_generic_impl<T, GEORANK>(input, in_shape, in_strides, weights, ker_params, \\\n        ker_strides, bias, bias_stride, p, str, dil, output, out_shape,             \\\n        out_strides, gid);                                                          \\\n}\n\nCONV_ENTRY(1, f32, float)\nCONV_ENTRY(2, f32, float)\nCONV_ENTRY(3, f32, float)\nCONV_ENTRY(4, f32, float)\n\nCONV_ENTRY(1, f16, half)\nCONV_ENTRY(2, f16, half)\nCONV_ENTRY(3, f16, half)\nCONV_ENTRY(4, f16, half)\n"
  },
  {
    "path": "metal/src/kernels/conv.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::{LibraryName, MetalStream};\nuse metal::MTLSize;\nuse tract_core::internal::*;\nuse tract_core::ops::cnn::Conv;\nuse tract_gpu::tensor::DeviceTensor;\n\npub fn kernel_name(hw_rank: usize, dt: DatumType) -> TractResult<String> {\n    let dt_name = if dt == DatumType::F16 { \"f16\" } else { \"f32\" };\n    Ok(format!(\"conv{hw_rank}d_{dt_name}_generic\"))\n}\n\npub fn metal_conv_dispatch(\n    stream: &MetalStream,\n    op: &Conv,\n    input: &DeviceTensor,\n    weights: &DeviceTensor,\n    bias: Option<&DeviceTensor>,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    stream.retain_tensor(input);\n    stream.retain_tensor(weights);\n    if let Some(b) = bias {\n        stream.retain_tensor(b);\n    }\n    stream.retain_tensor(output);\n\n    let input_shape = op.pool_spec.data_format.shape(input.shape())?;\n    let hw_rank = input_shape.hw_rank();\n    let func_name = kernel_name(hw_rank, input.datum_type())?;\n    let pipeline = stream.load_pipeline(LibraryName::ConvOps, &func_name)?;\n\n    let co_per_group = op.pool_spec.output_channels / op.group;\n    let ci_per_group = op.pool_spec.input_channels / op.group;\n\n    // in_shape: [N, C, spatial...]\n    let in_n = *input_shape.n().unwrap_or(&1);\n    let in_c = *input_shape.c();\n    let mut in_shape_buf: TVec<i32> = tvec![in_n as i32, in_c as i32];\n    in_shape_buf.extend(input_shape.hw_dims().iter().map(|&d| d as i32));\n\n    let mut in_strides_buf: TVec<i32> =\n        tvec![*input_shape.n_stride().unwrap_or(&0) as i32, *input_shape.c_stride() as i32];\n    in_strides_buf.extend(input_shape.hw_strides().iter().map(|&s| s as i32));\n\n    // ker_params: [groups, co_per_group, ci_per_group, ker_spatial...]\n    let mut ker_params: TVec<i32> =\n        tvec![op.group as i32, co_per_group as i32, ci_per_group as i32];\n    ker_params.extend(weights.shape()[2..].iter().map(|&d| d as i32));\n\n    // ker_strides: [g_stride, o_stride, i_stride, spatial...]\n    let group_stride = weights.strides()[0] as usize * co_per_group;\n    let mut ker_strides: TVec<i32> = tvec![group_stride as i32];\n    ker_strides.extend(weights.strides().iter().map(|&s| s as i32));\n\n    // padding\n    let padding = op.pool_spec.computed_padding(input_shape.hw_dims());\n    let pad_buf: TVec<i32> = padding.iter().map(|p| p.pad_before as i32).collect();\n\n    let strides = op.pool_spec.strides();\n    let strides_buf: TVec<i32> = strides.iter().map(|&s| s as i32).collect();\n\n    let dilations = op.pool_spec.dilations();\n    let dilations_buf: TVec<i32> = dilations.iter().map(|&d| d as i32).collect();\n\n    let output_shape = op.pool_spec.data_format.shape(output.shape())?;\n    let out_n = *output_shape.n().unwrap_or(&1);\n    let out_c = *output_shape.c();\n    let mut out_shape_buf: TVec<i32> = tvec![out_n as i32, out_c as i32];\n    out_shape_buf.extend(output_shape.hw_dims().iter().map(|&d| d as i32));\n\n    let mut out_strides_buf: TVec<i32> =\n        tvec![*output_shape.n_stride().unwrap_or(&0) as i32, *output_shape.c_stride() as i32];\n    out_strides_buf.extend(output_shape.hw_strides().iter().map(|&s| s as i32));\n\n    // bias_stride: -1 means no bias, 0 means scalar broadcast, 1 means per-channel\n    let bias_stride: i32 = if let Some(b) = bias { if b.rank() == 0 { 0 } else { 1 } } else { -1 };\n\n    let spatial_out: usize = output_shape.hw_dims().iter().product();\n    let threads_per_group = 32usize;\n\n    let command_buffer = stream.command_buffer();\n    command_buffer.encode(|encoder| {\n        encoder.set_compute_pipeline_state(&pipeline);\n        encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n        encoder.set_slice(1, &in_shape_buf);\n        encoder.set_slice(2, &in_strides_buf);\n        encoder.set_metal_tensor(3, weights, metal::MTLResourceUsage::Read);\n        encoder.set_slice(4, &ker_params);\n        encoder.set_slice(5, &ker_strides);\n        if let Some(b) = bias {\n            encoder.set_metal_tensor(6, b, metal::MTLResourceUsage::Read);\n        } else {\n            // Empty buffer — kernel checks bias_stride < 0\n            encoder.set_bytes(6, 0, std::ptr::null());\n        }\n        encoder.set_slice(7, &[bias_stride]);\n        encoder.set_slice(8, &pad_buf);\n        encoder.set_slice(9, &strides_buf);\n        encoder.set_slice(10, &dilations_buf);\n        encoder.set_metal_tensor(11, output, metal::MTLResourceUsage::Write);\n        encoder.set_slice(12, &out_shape_buf);\n        encoder.set_slice(13, &out_strides_buf);\n\n        let grid_size = MTLSize {\n            width: spatial_out.div_ceil(threads_per_group) as _,\n            height: out_c as _,\n            depth: out_n as _,\n        };\n        let group_size = MTLSize { width: threads_per_group as _, height: 1, depth: 1 };\n        encoder.dispatch_thread_groups(grid_size, group_size);\n    });\n    Ok(())\n}\n"
  },
  {
    "path": "metal/src/kernels/element_wise.metal",
    "content": "#include <metal_stdlib>\n#include <metal_integer>\n#include <metal_math>\n#include <metal_simdgroup_matrix>  // Available from Metal version 2.3 released with OS X 11.0+\n\nusing namespace metal;\n\nMETAL_FUNC float erf_f32(float x ) {\n    const float a1 = 0.0705230784;\n    const float a2 = 0.0422820123;\n    const float a3 = 0.0092705272;\n    const float a4 = 0.0001520143;\n    const float a5 = 0.0002765672;\n    const float a6 = 0.0000430638;\n    \n    float abs = metal::abs(x);\n    float y = a6 * abs;\n    y = (a5 + y) * abs;\n    y = (a4 + y) * abs;\n    y = (a3 + y) * abs;\n    y = (a2 + y) * abs;\n    y = (a1 + y) * abs;\n    y = 1.0 - (1.0 / metal::powr(y + 1.0, 16));\n    y = metal::copysign(y, x);\n    return y;\n}\n\n/*\n * Based on code from:\n * https://github.com/ml-explore/mlx/blob/main/mlx/backend/metal/kernels/unary_ops.h\n */\n\nstruct Abs {\n    template <typename T>\n    metal::enable_if_t<metal::is_integral_v<T> & !metal::is_signed_v<T>, T>\n    operator()(T x) {\n        return x;\n    }\n    \n    template <typename T>\n    metal::enable_if_t<metal::is_integral_v<T> & metal::is_signed_v<T>, T>\n    operator()(T x) {\n        return metal::abs(x);\n    };\n    \n    template <typename T>\n    metal::enable_if_t<!metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return metal::abs(x);\n    };\n};\n\nstruct Ceil {\n    template <typename T>\n    metal::enable_if_t<!metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return metal::ceil(x);\n    }\n    \n    template <typename T>\n    metal::enable_if_t<metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return x;\n    }\n};\n\nstruct Floor {\n    template <typename T>\n    metal::enable_if_t<!metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return metal::floor(x);\n    }\n    \n    template <typename T>\n    metal::enable_if_t<metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return x;\n    }\n};\n\nstruct Round {\n    template <typename T>\n    metal::enable_if_t<!metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return metal::round(x);\n    }\n    \n    template <typename T>\n    metal::enable_if_t<metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return x;\n    }\n};\n\nstruct RoundHalfToEven {\n    template <typename T>\n    metal::enable_if_t<!metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return metal::rint(x);\n    }\n    \n    template <typename T>\n    metal::enable_if_t<metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return x;\n    }\n};\n\nstruct Recip {\n    template <typename T>\n    T operator()(T x) {\n        return 1 / x;\n    }\n};\n\nstruct Erf {\n    template <typename T>\n    T operator()(T x) {\n        return static_cast<T>(erf_f32(static_cast<float>(x)));\n    };\n};\n\nstruct Exp {\n    template <typename T>\n    T operator()(T x) {\n        return metal::precise::exp(x);\n    };\n};\n\nstruct Ln {\n    template <typename T>\n    T operator()(T x) {\n        return metal::precise::log(x);\n    };\n};\n\nstruct Sigmoid {\n    template <typename T>\n    T operator()(T x) {\n        auto y = 1 / (1 + metal::exp(-metal::abs(x)));\n        return (x < 0) ? 1 - y : y;\n    }\n};\n\n// Cosine of x\nstruct Cos {\n    template <typename T>\n    T operator()(T x) {\n        return metal::cos(x);\n    }\n};\n\n// Hyperbolic cosine of x\nstruct Cosh {\n    template <typename T>\n    T operator()(T x) {\n        return metal::cosh(x);\n    }\n};\n\n// Arc cosine of x\nstruct Acos {\n    template <typename T>\n    T operator()(T x) {\n        return metal::acos(x);\n    }\n};\n\n// Inverse hyperbolic cosine of x\nstruct Acosh {\n    template <typename T>\n    T operator()(T x) {\n        return metal::acosh(x);\n    }\n};\n\n// Sine of x\nstruct Sin {\n    template <typename T>\n    T operator()(T x) {\n        return metal::sin(x);\n    }\n};\n\n// Hyperbolic sine of x\nstruct Sinh {\n    template <typename T>\n    T operator()(T x) {\n        return metal::sinh(x);\n    }\n};\n\n// Arc sine of x\nstruct Asin {\n    template <typename T>\n    T operator()(T x) {\n        return metal::asin(x);\n    }\n};\n\n// Inverse hyperbolic sine of x\nstruct Asinh {\n    template <typename T>\n    T operator()(T x) {\n        return metal::asinh(x);\n    }\n};\n\n// Tangent of x\nstruct Tan {\n    template <typename T>\n    T operator()(T x) {\n        return metal::tan(x);\n    }\n};\n\n// Arc tangent of x\nstruct Atan {\n    template <typename T>\n    T operator()(T x) {\n        return metal::precise::atan(x);\n    }\n};\n\n// Inverse hyperbolic tangent of x\nstruct Atanh {\n    template <typename T>\n    T operator()(T x) {\n        return metal::precise::atanh(x);\n    }\n};\n\n// Hyperbolic tangent of x\nstruct Tanh {\n    template <typename T>\n    T operator()(T x) {\n        // Use precise to avoid NaN for large value with fast implementation \n        return metal::precise::tanh(x);\n    }\n};\n\nstruct Square {\n    template <typename T>\n    T operator()(T x) {\n        return metal::pow(x, static_cast<T>(2.0));\n    }\n};\n\nstruct Sqrt {\n    template <typename T>\n    T operator()(T x) {\n        return metal::precise::sqrt(x);\n    };\n};\n\nstruct Rsqrt {\n    template <typename T>\n    T operator()(T x) {\n        return metal::precise::rsqrt(x);\n    };\n};\n\nstruct Neg {\n    template <typename T>\n    T operator()(T x) {\n        return -x;\n    };\n};\n\nstruct Sign {\n    template <typename T>\n    metal::enable_if_t<!metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return (x > T(0)) ? T(1) : ((x < T(0)) ? T(-1) : T(0));\n    }\n\n    template <typename T>\n    metal::enable_if_t<metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return (x > T(0)) - (x < T(0));\n    }\n};\n\nstruct HardSwish {\n    template <typename T>\n    T operator()(T x) {\n        return x * metal::max(T(0), metal::min(T(1), x / T(6) + T(0.5)));\n    }\n};\n\nstruct Silu {\n    template <typename T>\n    T operator()(T x) {\n        return x / (T(1) + metal::exp(-x));\n    }\n};\n\nstruct BitNot {\n    template <typename T>\n    metal::enable_if_t<metal::is_integral_v<T>, T>\n    operator()(T x) {\n        return ~x;\n    }\n\n    bool operator()(bool x) {\n        return !x;\n    }\n};\n\ntemplate<typename T, typename Op>\n[[kernel]] void eval_out_of_place(device const T *input[  [buffer(0)]],\n                                  device T *output [[buffer(1)]],\n                                  uint tpig[[thread_position_in_grid]]) {\n    output[tpig] = Op()(input[tpig]);\n}\n\ntemplate<typename T, typename Op>\n[[kernel]] void eval_in_place(device T *inout[  [buffer(0)]],\n                              uint tpig[[thread_position_in_grid]]) {\n    inout[tpig] = Op()(inout[tpig]);\n}\n\n#define INSTANTIATE_ELEMENT_WISE_OP(name, op, tname, type)            \\\ntemplate [[host_name(\"element_wise_ops::\" #name \"_out_of_place_\" #tname)]] [[kernel]] void eval_out_of_place<type, op>(                                   \\\ndevice const type *input [[buffer(0)]],                    \\\ndevice type *output [[buffer(1)]],                        \\\nuint tpig[[thread_position_in_grid]]                       \\\n);                                                             \\\ntemplate [[host_name(\"element_wise_ops::\" #name \"_in_place_\" #tname)]] [[kernel]] void eval_in_place<type, op>(                                  \\\ndevice type *inout [[buffer(0)]],                          \\\nuint tpig[[thread_position_in_grid]]                       \\\n);\n\n\n#define INSTANTIATE_FLOAT(name, op)                      \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, f32,  float)       \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, f16, half)         \\\n\n#define INSTANTIATE_INTEGER_SIGNED(name, op)             \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, i8,  int8_t)       \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, i16, int16_t)      \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, i32, int32_t)      \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, i64, int64_t)\n\n#define INSTANTIATE_INTEGER_UNSIGNED(name, op)                    \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, u8,  uint8_t)      \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, u16, uint16_t)     \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, u32, uint32_t)     \\\nINSTANTIATE_ELEMENT_WISE_OP(name, op, u64, uint64_t)     \\\n\n#define INSTANTIATE_INTEGER(name, op)                    \\\nINSTANTIATE_INTEGER_SIGNED(name, op)                     \\\nINSTANTIATE_INTEGER_UNSIGNED(name, op)                   \\\n\n#define INSTANTIATE_ALL_TYPES(name, op)                  \\\nINSTANTIATE_FLOAT(name, op)                              \\\nINSTANTIATE_INTEGER(name, op)\n\nINSTANTIATE_ALL_TYPES(abs, Abs)\nINSTANTIATE_FLOAT(exp, Exp)\nINSTANTIATE_FLOAT(ln, Ln)\nINSTANTIATE_FLOAT(sqrt, Sqrt)\nINSTANTIATE_FLOAT(rsqrt, Rsqrt)\nINSTANTIATE_FLOAT(sigmoid, Sigmoid)\nINSTANTIATE_FLOAT(square, Square)\nINSTANTIATE_FLOAT(recip, Recip)\nINSTANTIATE_ALL_TYPES(ceil, Ceil)\nINSTANTIATE_ALL_TYPES(floor, Floor)\nINSTANTIATE_ALL_TYPES(round, Round)\nINSTANTIATE_ALL_TYPES(roundhalftoeven, RoundHalfToEven)\nINSTANTIATE_FLOAT(cos, Cos)\nINSTANTIATE_FLOAT(acos, Acos)\nINSTANTIATE_FLOAT(acosh, Acosh)\nINSTANTIATE_FLOAT(cosh, Cosh)\nINSTANTIATE_FLOAT(sin, Sin)\nINSTANTIATE_FLOAT(asin, Asin)\nINSTANTIATE_FLOAT(asinh, Asinh)\nINSTANTIATE_FLOAT(sinh, Sinh)\nINSTANTIATE_FLOAT(tan, Tan)\nINSTANTIATE_FLOAT(atan, Atan)\nINSTANTIATE_FLOAT(atanh, Atanh)\nINSTANTIATE_FLOAT(tanh, Tanh)\nINSTANTIATE_FLOAT(erf, Erf)\nINSTANTIATE_FLOAT(neg, Neg)\nINSTANTIATE_INTEGER_SIGNED(neg, Neg)\nINSTANTIATE_FLOAT(sign, Sign)\nINSTANTIATE_INTEGER_SIGNED(sign, Sign)\nINSTANTIATE_FLOAT(hardswish, HardSwish)\nINSTANTIATE_FLOAT(silu, Silu)\nINSTANTIATE_INTEGER(bitnot, BitNot)\nINSTANTIATE_ELEMENT_WISE_OP(bitnot, BitNot, bool, bool)\n"
  },
  {
    "path": "metal/src/kernels/element_wise.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::{LibraryName, MetalStream};\nuse anyhow::ensure;\nuse metal::{MTLSize, NSUInteger};\nuse tract_core::internal::*;\nuse tract_core::ops::element_wise::ElementWiseMiniOp;\nuse tract_gpu::tensor::DeviceTensor;\n\nconst ALL_OP_NAMES: &[&str] = &[\n    \"abs\",\n    \"exp\",\n    \"ln\",\n    \"sigmoid\",\n    \"square\",\n    \"sqrt\",\n    \"rsqrt\",\n    \"recip\",\n    \"ceil\",\n    \"floor\",\n    \"round\",\n    \"roundhalftoeven\",\n    \"cos\",\n    \"acos\",\n    \"acosh\",\n    \"cosh\",\n    \"sin\",\n    \"asin\",\n    \"asinh\",\n    \"sinh\",\n    \"tan\",\n    \"atan\",\n    \"atanh\",\n    \"tanh\",\n    \"erf\",\n    \"neg\",\n    \"sign\",\n    \"hardswish\",\n    \"silu\",\n    \"bitnot\",\n];\n\npub fn all_functions() -> Vec<String> {\n    ALL_OP_NAMES\n        .iter()\n        .flat_map(|kname| {\n            DeviceTensor::SUPPORTED_DT.into_iter().flat_map(move |dt| {\n                let tname = DeviceTensor::tname(dt).ok()?;\n                Some(format!(\"element_wise_ops::{kname}_out_of_place_{tname}\"))\n            })\n        })\n        .collect()\n}\n\npub fn is_supported(mini_op: &dyn ElementWiseMiniOp, dt: DatumType) -> bool {\n    let name = mini_op.name().to_lowercase();\n    ALL_OP_NAMES.contains(&name.as_str())\n        && if name == \"bitnot\" {\n            dt.is_integer() || dt.is::<bool>()\n        } else {\n            matches!(dt, DatumType::F32 | DatumType::F16)\n        }\n}\n\npub fn dispatch_eval(\n    stream: &MetalStream,\n    mini_op: &dyn ElementWiseMiniOp,\n    input: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    stream.retain_tensor(input);\n    stream.retain_tensor(output);\n\n    ensure!(output.shape() == input.shape() && output.datum_type() == input.datum_type());\n\n    let op_name = mini_op.name().to_lowercase();\n    let tname = DeviceTensor::tname(input.datum_type())?;\n    let kernel_name = format!(\"element_wise_ops::{op_name}_out_of_place_{tname}\");\n\n    let pipeline = stream.load_pipeline(LibraryName::ElementWiseOps, &kernel_name)?;\n    let command_buffer = stream.command_buffer();\n    command_buffer.encode(|encoder| {\n        encoder.set_compute_pipeline_state(&pipeline);\n        encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n        encoder.set_metal_tensor(1, output, metal::MTLResourceUsage::Write);\n\n        let grid_size = MTLSize { width: output.len() as NSUInteger, height: 1, depth: 1 };\n        let group_size = MTLSize { width: 1, height: 1, depth: 1 };\n        encoder.dispatch_thread_groups(grid_size, group_size);\n    });\n    Ok(())\n}\n\npub fn metal_element_wise_dispatch(\n    mini_op: &dyn ElementWiseMiniOp,\n    input: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| dispatch_eval(stream, mini_op, input, output))\n}\n\npub fn metal_element_wise_op(\n    mini_op: Box<dyn ElementWiseMiniOp>,\n) -> tract_gpu::ops::element_wise::GpuElementWise {\n    tract_gpu::ops::element_wise::GpuElementWise::new(mini_op, \"Metal\", metal_element_wise_dispatch)\n}\n\n// Generic element-wise fallback — checked after LeakyRelu, GeluApproximate.\ncrate::register_metal_op!(tract_core::ops::element_wise::ElementWiseOp, |source, node, op| {\n    rule_if!(is_supported(&*op.0, source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(metal_element_wise_op(op.0.clone()))))\n});\n"
  },
  {
    "path": "metal/src/kernels/matmul/basic/basic_mat_mul.metal",
    "content": "#include <metal_stdlib>\n#include <metal_simdgroup_matrix>  // Available from Metal version 2.3 released with OS X 11.0+\n\nusing namespace metal;\n\n#define NUM_SIMDGROUP 32\n\n#define INSTANTIATE_BASIC_MATMUL(tname, type)                    \\\ntemplate [[host_name(\"matmul::basic_matvec_\" #tname)]]           \\\n[[kernel]] void basic_matvec<type>(                              \\\n    device const type *lhs [[buffer(0)]],                        \\\n    device const type *rhs [[buffer(1)]],                        \\\n    device type *output [[buffer(2)]],                           \\\n    constant   int32_t & m,                                      \\\n    constant   int32_t & k,                                      \\\n    uint3 tgpig[[threadgroup_position_in_grid]],                 \\\n    uint  tiisg[[thread_index_in_simdgroup]],                    \\\n    uint  sgitg[[simdgroup_index_in_threadgroup]]                \\\n);                                                               \\\ntemplate [[host_name(\"matmul::basic_matmul_\" #tname)]]           \\\n[[kernel]] void basic_matmul<type>(                              \\\n    device const type *lhs [[buffer(0)]],                        \\\n    device const type *rhs [[buffer(1)]],                        \\\n    device type *output [[buffer(2)]],                           \\\n    constant   int32_t & m,                                      \\\n    constant   int32_t & k,                                      \\\n    constant   int32_t & n,                                      \\\n    constant   int32_t & transpose_lhs,                          \\\n    constant   int32_t & transpose_rhs,                          \\\n    uint3 tgpig[[threadgroup_position_in_grid]],                 \\\n    uint  tiisg[[thread_index_in_simdgroup]],                    \\\n    uint  sgitg[[simdgroup_index_in_threadgroup]]                \\\n);                                                           \n\n\ntemplate<typename T>  \n[[kernel]]  void basic_matvec(device const T *lhs [[buffer(0)]],\n                              device const T *rhs [[buffer(1)]],\n                              device T *output [[buffer(2)]],\n                              constant   int32_t & m,\n                              constant   int32_t & k,\n                              uint3 tgpig[[threadgroup_position_in_grid]],\n                              uint  tiisg[[thread_index_in_simdgroup]],\n                              uint  sgitg[[simdgroup_index_in_threadgroup]]\n                              ) {\n    \n    const int32_t m_group_size = 4;\n    const int32_t _batch_idx = tgpig.x;\n    const int32_t m_group_start = tgpig.y*m_group_size;\n    \n    for (int m_group_idx = 0; m_group_idx < m_group_size; ++m_group_idx) {\n        int m_idx = m_group_start + m_group_idx;\n        if (m_idx >= m) {\n            break;\n        }\n        device const T * lhs_m = (device const T *) (lhs + m_idx * k);\n        T sumf = 0;\n        // Accumulate per simd\n\n        for (int i = tiisg; i < k; i += NUM_SIMDGROUP) {\n            sumf +=  rhs[i] * lhs_m[i];\n        }\n        T all_sum = simd_sum(sumf);\n        if (tiisg == 0) {\n            output[m_idx] = all_sum;\n        }\n    }\n}\n\n\ntemplate<typename T>  \n[[kernel]]  void basic_matmul(device const T  *lhs [[buffer(0)]],\n                              device const T *rhs [[buffer(1)]],\n                              device T *output [[buffer(2)]],\n                              constant   int32_t & m,\n                              constant   int32_t & k,\n                              constant   int32_t & n,\n                              constant   int32_t & transpose_lhs,\n                              constant   int32_t & transpose_rhs, \n                              uint3 tgpig[[threadgroup_position_in_grid]],\n                              uint  tiisg[[thread_index_in_simdgroup]],\n                              uint  sgitg[[simdgroup_index_in_threadgroup]]\n                              ) {\n    \n    const int32_t group_size = 4;\n    const int32_t n_group_start = tgpig.x * group_size;\n    const int32_t m_group_start = tgpig.y * group_size;\n    \n    // [m_idx, n_idx] = m_idx * n + n_idx\n    \n    for (int m_group_idx = 0; m_group_idx < group_size; ++m_group_idx) {\n        int m_idx = m_group_start + m_group_idx;\n        if (m_idx >= m) {\n            break;\n        }\n        for (int n_group_idx = 0; n_group_idx < group_size; ++n_group_idx) {\n            int n_idx = n_group_start + n_group_idx;\n            \n            if (n_idx >= n) {\n                break;\n            }\n            \n            T sumf = 0;\n            // Accumulate per simd\n            if(transpose_lhs == 0 && transpose_rhs == 0) {\n                for (int i = tiisg; i < k; i += NUM_SIMDGROUP) {\n                    // lhs[m_idx, i] = m_idx * k + i\n                    // rhs[i, n_idx] = i * n + n_idx\n                    sumf += rhs[i * n + n_idx] * lhs[m_idx * k + i];\n                }\n            } else if(transpose_lhs == 0 && transpose_rhs != 0) {\n                for (int i = tiisg; i < k; i += NUM_SIMDGROUP) {\n                    // lhs[m_idx, i] = m_idx * k + i\n                    // rhs[n_idx, i] = n_idx * k + i\n                    sumf += rhs[n_idx * k + i] * lhs[m_idx * k + i];\n                }\n            } else if(transpose_lhs != 0 && transpose_rhs != 0) {\n                for (int i = tiisg; i < k; i += NUM_SIMDGROUP) {\n                    // lhs[i, m_idx] = i * m + m_idx\n                    // rhs[n_idx, i] = n_idx * k + i\n                    sumf += rhs[n_idx * k + i] * lhs[i * m + m_idx];\n                }\n            } else if(transpose_lhs != 0 && transpose_rhs == 0) {\n                for (int i = tiisg; i < k; i += NUM_SIMDGROUP) {\n                    // lhs[i, m_idx] = i * m + m_idx\n                    // rhs[i, n_idx] = i * n + n_idx\n                    sumf += rhs[i * n + n_idx] * lhs[i * m + m_idx];\n                }\n            }\n            \n            T all_sum = simd_sum(sumf);\n            if (tiisg == 0) {\n                output[m_idx * n + n_idx] = all_sum;\n            }\n        }\n    }\n}\n\nINSTANTIATE_BASIC_MATMUL(f32, float)\nINSTANTIATE_BASIC_MATMUL(f16, half)\n"
  },
  {
    "path": "metal/src/kernels/matmul/basic/mod.rs",
    "content": "use crate::kernels::matmul::{GemmDispatchParams, GemmKernel};\nuse crate::{LibraryName, MetalStream};\nuse anyhow::bail;\nuse derive_new::new;\nuse metal::{Buffer, MTLSize, NSUInteger};\nuse std::fmt;\nuse tract_core::internal::*;\n\n#[derive(Debug, Clone, new, Default, PartialEq, Eq, Hash)]\npub struct BasicMatMul;\n\nimpl GemmKernel for BasicMatMul {\n    fn name() -> &'static str {\n        \"basic\"\n    }\n\n    fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        params: GemmDispatchParams,\n        a_buffer: &Buffer,\n        b_buffer: &Buffer,\n        c_buffer: &Buffer,\n    ) -> TractResult<()> {\n        let GemmDispatchParams {\n            dts,\n            a_batch,\n            m,\n            k,\n            n,\n            transpose_a,\n            a_offset,\n            transpose_b,\n            b_offset,\n            c_offset,\n            ..\n        } = params;\n\n        ensure!(\n            Self::tname(dts[0]).is_ok(),\n            \"Unsupported datum type for Metal BasicMatmul {:?}\",\n            dts[0]\n        );\n        ensure!(\n            dts[0] == dts[1] && dts[0] == dts[2],\n            \"Metal BasicMatmul only supports homogenous datum types. I: {:?}, {:?}. O: {:?}\",\n            dts[0],\n            dts[1],\n            dts[2]\n        );\n\n        let dt = dts[0];\n        for b_idx in 0..a_batch {\n            let a_offset = a_offset + b_idx * m * k * dt.size_of();\n            let b_offset = b_offset + b_idx * n * k * dt.size_of();\n            let c_offset = c_offset + b_idx * m * n * dt.size_of();\n            if n == 1 && !transpose_a && !transpose_b {\n                Self::metal_mat_vec(\n                    stream, dt, m, k, a_buffer, a_offset, b_buffer, b_offset, c_buffer, c_offset,\n                )?;\n            } else {\n                Self::metal_mat_mul(\n                    stream,\n                    dt,\n                    m,\n                    k,\n                    n,\n                    a_buffer,\n                    a_offset,\n                    transpose_a,\n                    b_buffer,\n                    b_offset,\n                    transpose_b,\n                    c_buffer,\n                    c_offset,\n                )?;\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl fmt::Display for BasicMatMul {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"BasicMatMul\")\n    }\n}\n\nimpl BasicMatMul {\n    pub fn tname(dt: DatumType) -> TractResult<&'static str> {\n        let tname = match dt {\n            DatumType::F32 => \"f32\",\n            DatumType::F16 => \"f16\",\n            _ => bail!(\"Unsupported dt {:?} for metal basic matmul\", dt),\n        };\n        Ok(tname)\n    }\n\n    pub fn kernel_name(dt: DatumType, mat_vec: bool) -> TractResult<String> {\n        let tname = Self::tname(dt)?;\n        if mat_vec {\n            Ok(format!(\"matmul::basic_matvec_{tname}\"))\n        } else {\n            Ok(format!(\"matmul::basic_matmul_{tname}\"))\n        }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn metal_mat_vec(\n        stream: &MetalStream,\n        dt: DatumType,\n        m: usize,\n        k: usize,\n        lhs_buffer: &Buffer,\n        lhs_offset: usize,\n        rhs_buffer: &Buffer,\n        rhs_offset: usize,\n        output: &Buffer,\n        output_offset: usize,\n    ) -> TractResult<()> {\n        let pipeline =\n            stream.load_pipeline(LibraryName::BasicMatMul, &Self::kernel_name(dt, true)?)?;\n\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_buffer(0, Some(lhs_buffer), lhs_offset as _);\n            encoder.set_buffer(1, Some(rhs_buffer), rhs_offset as _);\n            encoder.set_buffer(2, Some(output), output_offset as _);\n            encoder.set_bytes(3, 4, &(m as i32) as *const i32 as *const _);\n            encoder.set_bytes(4, 4, &(k as i32) as *const i32 as *const _);\n\n            // m x k * k * 1\n            let grid_size =\n                MTLSize { width: 1, height: m.div_ceil(4) as NSUInteger, depth: 1 as NSUInteger };\n            let group_size = MTLSize { width: 32, height: 1, depth: 1 };\n            encoder.use_resource(lhs_buffer, metal::MTLResourceUsage::Read);\n            encoder.use_resource(rhs_buffer, metal::MTLResourceUsage::Read);\n            encoder.use_resource(output, metal::MTLResourceUsage::Write);\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn metal_mat_mul(\n        stream: &MetalStream,\n        dt: DatumType,\n        m: usize,\n        k: usize,\n        n: usize,\n        lhs_buffer: &Buffer,\n        lhs_offset: usize,\n        lhs_transpose: bool,\n        rhs_buffer: &Buffer,\n        rhs_offset: usize,\n        rhs_transpose: bool,\n        output: &Buffer,\n        output_offset: usize,\n    ) -> TractResult<()> {\n        let pipeline =\n            stream.load_pipeline(LibraryName::BasicMatMul, &Self::kernel_name(dt, false)?)?;\n\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_buffer(0, Some(lhs_buffer), lhs_offset as _);\n            encoder.set_buffer(1, Some(rhs_buffer), rhs_offset as _);\n            encoder.set_buffer(2, Some(output), output_offset as _);\n            encoder.set_bytes(3, 4, &(m as i32) as *const i32 as *const _);\n            encoder.set_bytes(4, 4, &(k as i32) as *const i32 as *const _);\n            encoder.set_bytes(5, 4, &(n as i32) as *const i32 as *const _);\n            encoder.set_bytes(6, 4, &(lhs_transpose as i32) as *const i32 as *const _);\n            encoder.set_bytes(7, 4, &(rhs_transpose as i32) as *const i32 as *const _);\n\n            let grid_size = MTLSize {\n                width: n.div_ceil(4) as NSUInteger,\n                height: m.div_ceil(4) as NSUInteger,\n                depth: 1 as NSUInteger,\n            };\n            let group_size: MTLSize = MTLSize { width: 32, height: 1, depth: 1 };\n            encoder.use_resource(lhs_buffer, metal::MTLResourceUsage::Read);\n            encoder.use_resource(rhs_buffer, metal::MTLResourceUsage::Read);\n            encoder.use_resource(output, metal::MTLResourceUsage::Write);\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::kernels::matmul::tests::run_mmm_test_case;\n\n    #[test]\n    fn test_mat_vec() -> TractResult<()> {\n        run_mmm_test_case::<BasicMatMul>(\n            (1, 4, 4, 1),\n            false,\n            false,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (1, 1, 4, 4),\n            false,\n            false,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (1, 1, 15, 7),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_mat_mul() -> TractResult<()> {\n        run_mmm_test_case::<BasicMatMul>(\n            (1, 3, 5, 4),\n            false,\n            false,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (1, 2, 5, 10),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (1, 4, 4, 4),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (1, 4, 4, 200),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (1, 25, 1280, 32000),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n\n        run_mmm_test_case::<BasicMatMul>(\n            (10, 3, 5, 4),\n            false,\n            false,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (10, 2, 5, 10),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (10, 4, 4, 4),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (10, 4, 4, 200),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        run_mmm_test_case::<BasicMatMul>(\n            (10, 25, 1280, 32000),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/matmul/ggml_gemm/README.md",
    "content": "### Kernel list\n\nOutputs are always F32. Accumulation is F32. Weights are in first position.\n- kernel_mul_mv: (f16_f16, f32_f32, f16_f32)\n- kernel_mul_mv_1row (f16_f32)\n- kernel_mul_mv_l4 (f16_f32)\n\n- kernel_mul_mm (f32_f32, f16_f32)\n\n### Tensor layout\n\nChannel is a batch-like dimension. It is NOT as in the matmul operational convention (==m or k)\n\n- A: [a_batch, a_channel, m, k]\n- B: [b_batch, b_channel, n, k]\n- Out: [b_batch, b_channel, n, m]\n\n**We actually swap A and B when calling the kernel to have the untransposed output!**\n\n### Kernel prototype\n\nMatvec Kernel params. Matmul params are a subset of this struct\n```\nggml_metal_kargs_mul_mv args = {\n        /*.ne00 =*/ ne00, // Inner axis len of input A: k\n        /*.ne01 =*/ ne01, // m\n        /*.ne02 =*/ ne02, // a_channel\n        /*.nb00 =*/ nb00, // Inner stride of input A\n        /*.nb01 =*/ nb01, // k\n        /*.nb02 =*/ nb02,\n        /*.nb03 =*/ nb03,\n        /*.ne10 =*/ ne10, // Inner axis len of input B: k\n        /*.ne11 =*/ ne11, // n\n        /*.ne12 =*/ ne12, //b_channel\n        /*.nb10 =*/ nb10, // Inner stride of input B\n        /*.nb11 =*/ nb11,\n        /*.nb12 =*/ nb12,\n        /*.nb13 =*/ nb13,\n        /*.ne0  =*/ ne0, // Inner axis len of Output: m\n        /*.ne1  =*/ ne1, // n\n        /*.r2   =*/ r2, // channel_broadcast_ratio\n        /*.r3   =*/ r3, // batch_broadcast_ratio\n    };\n```\n\nr2 and r3 are currently always 1!\nThese are for multiplying multiple Bs with a single A.\nKernel should support it if frame tries to call them with values != 1.\n\n```\ntemplate<typename T0, typename T04, typename T1, typename T14>\nkernel void kernel_mul_mv(\n        constant ggml_metal_kargs_mul_mv & args,\n        device const char * src0,\n        device const char * src1,\n        device       char * dst,\n        uint3  tgpig[[threadgroup_position_in_grid]],\n        ushort tiisg[[thread_index_in_simdgroup]])\n```\n\n```\ntemplate<typename T, typename T4x4, typename simdgroup_T8x8, typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread T4x4 &)>\nkernel void kernel_mul_mm(\n        constant ggml_metal_kargs_mul_mm & args,\n        device const char * src0,\n        device const char * src1,\n        device       char * dst,\n        threadgroup  char * shmem [[threadgroup(0)]],\n        uint3  tgpig[[threadgroup_position_in_grid]],\n        ushort tiitg[[thread_index_in_threadgroup]],\n        ushort sgitg[[simdgroup_index_in_threadgroup]])\n```\n"
  },
  {
    "path": "metal/src/kernels/matmul/ggml_gemm/ggml_mm_mv.metal",
    "content": "#include <metal_simdgroup>\n#include <metal_simdgroup_matrix>\n#include <metal_stdlib>\n\nusing namespace metal;\n\n#define N_SIMDWIDTH 32 // assuming SIMD group size is 32\n\n#define QK4_0 32\ntypedef struct {\n    half d;           // delta\n    uint8_t qs[QK4_0 / 2]; // nibbles / quants\n} block_q4_0;\n\ntypedef struct {\n    int32_t  ne00;\n    int32_t  ne02;\n    uint64_t nb01;\n    uint64_t nb02;\n    uint64_t nb03;\n    int32_t  ne12;\n    uint64_t nb10;\n    uint64_t nb11;\n    uint64_t nb12;\n    uint64_t nb13;\n    int32_t  ne0;\n    int32_t  ne1;\n    int16_t  r2;\n    int16_t  r3;\n} ggml_metal_kargs_mul_mm;\n\ntypedef struct {\n    int32_t  ne00;\n    int32_t  ne01;\n    int32_t  ne02;\n    uint64_t nb00;\n    uint64_t nb01;\n    uint64_t nb02;\n    uint64_t nb03;\n    int32_t  ne10;\n    int32_t  ne11;\n    int32_t  ne12;\n    uint64_t nb10;\n    uint64_t nb11;\n    uint64_t nb12;\n    uint64_t nb13;\n    int32_t  ne0;\n    int32_t  ne1;\n    int16_t  r2;\n    int16_t  r3;\n} ggml_metal_kargs_mul_mv;\n\n#define N_MV_T_T 4\n\ntemplate<typename T0, typename T04, typename T1, typename T14, typename args_t>\nvoid kernel_mul_mv_impl(\n        args_t args,\n        device const char * src0,\n        device const char * src1,\n        device       char * dst,\n        uint3  tgpig,\n        ushort tiisg) {\n    const int r0 = tgpig.x;\n    const int rb = tgpig.y*N_MV_T_T;\n    const int im = tgpig.z;\n\n    const uint i12 = im%args.ne12;\n    const uint i13 = im/args.ne12;\n\n    const uint64_t offset0 = r0*args.nb01 + (i12/args.r2)*args.nb02 + (i13/args.r3)*args.nb03;\n\n    device const T0 * x = (device const T0 *) (src0 + offset0);\n\n    device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1;\n\n    if (args.ne00 < 128) {\n        for (int row = 0; row < N_MV_T_T; ++row) {\n            int r1 = rb + row;\n            if (r1 >= args.ne11) {\n                break;\n            }\n\n            const uint64_t offset1 = r1*args.nb11 + (i12   )*args.nb12 + (i13   )*args.nb13;\n\n            device const T1 * y = (device const T1 *) (src1 + offset1);\n\n            float sumf = 0;\n            for (int i = tiisg; i < args.ne00; i += 32) {\n                sumf += (T0) x[i] * (T1) y[i];\n            }\n\n            float all_sum = simd_sum(sumf);\n            if (tiisg == 0) {\n                dst_f32[(uint64_t)r1*args.ne0 + r0] = all_sum;\n            }\n        }\n    } else {\n        device const T04 * x4 = (device const T04 *) x;\n        for (int row = 0; row < N_MV_T_T; ++row) {\n            int r1 = rb + row;\n            if (r1 >= args.ne11) {\n                break;\n            }\n\n            const uint64_t offset1 = r1*args.nb11 + (i12   )*args.nb12 + (i13   )*args.nb13;\n\n            device const T1  * y  = (device const T1  *) (src1 + offset1);\n            device const T14 * y4 = (device const T14 *) y;\n\n            float sumf = 0;\n            for (int i = tiisg; i < args.ne00/4; i += 32) {\n                sumf += dot((float4) x4[i], (float4) y4[i]);\n            }\n\n            float all_sum = simd_sum(sumf);\n            if (tiisg == 0) {\n                for (int i = 4*(args.ne00/4); i < args.ne00; ++i) all_sum += (float) (x[i] * y[i]);\n                dst_f32[(uint64_t)r1*args.ne0 + r0] = all_sum;\n            }\n        }\n    }\n}\n\ntemplate<typename T0, typename T04, typename T1, typename T14>\nkernel void kernel_mul_mv(\n        constant ggml_metal_kargs_mul_mv & args,\n        device const char * src0,\n        device const char * src1,\n        device       char * dst,\n        uint3  tgpig[[threadgroup_position_in_grid]],\n        ushort tiisg[[thread_index_in_simdgroup]]) {\n    kernel_mul_mv_impl<T0, T04, T1, T14, constant ggml_metal_kargs_mul_mv &>(\n        args,\n        src0,\n        src1,\n        dst,\n        tgpig,\n        tiisg);\n}\n\ntypedef decltype(kernel_mul_mv<half, half4, half, half4>) mul_mv_t;\n\ntemplate [[host_name(\"kernel_mul_mv_f32_f32\")]]   kernel mul_mv_t kernel_mul_mv<float,  float4,  float,  float4>;\ntemplate [[host_name(\"kernel_mul_mv_f16_f32\")]]   kernel mul_mv_t kernel_mul_mv<half,   half4,   float,  float4>;\ntemplate [[host_name(\"kernel_mul_mv_f16_f16\")]]   kernel mul_mv_t kernel_mul_mv<half,   half4,   half,   half4>;\n\ntemplate<typename T, typename T4>\nkernel void kernel_mul_mv_1row(\n        constant ggml_metal_kargs_mul_mv & args,\n        device const char * src0,\n        device const char * src1,\n        device       char * dst,\n        uint3  tgpig[[threadgroup_position_in_grid]],\n        ushort tiisg[[thread_index_in_simdgroup]]) {\n\n    const int r0 = tgpig.x;\n    const int r1 = tgpig.y;\n    const int im = tgpig.z;\n\n    const uint i12 = im%args.ne12;\n    const uint i13 = im/args.ne12;\n\n    const uint64_t offset0 = r0*args.nb01 + (i12/args.r2)*args.nb02 + (i13/args.r3)*args.nb03;\n    const uint64_t offset1 = r1*args.nb11 + (i12        )*args.nb12 + (i13        )*args.nb13;\n\n    device const T     * x = (device const T     *) (src0 + offset0);\n    device const float * y = (device const float *) (src1 + offset1);\n\n    device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0;\n\n    float sumf = 0;\n    if (args.ne00 < 128) {\n        for (int i = tiisg; i < args.ne00; i += 32) {\n            sumf += (float) x[i] * (float) y[i];\n        }\n        float all_sum = simd_sum(sumf);\n        if (tiisg == 0) {\n            dst_f32[r0] = all_sum;\n        }\n    } else {\n        device const T4     * x4 = (device const T4     *) x;\n        device const float4 * y4 = (device const float4 *) y;\n\n        for (int i = tiisg; i < args.ne00/4; i += 32) {\n            sumf += dot((float4) x4[i], y4[i]);\n        }\n\n        float all_sum = simd_sum(sumf);\n\n        if (tiisg == 0) {\n            for (int i = 4*(args.ne00/4); i < args.ne00; ++i) all_sum += (float) (x[i] * y[i]);\n            dst_f32[r0] = all_sum;\n        }\n    }\n}\n\ntypedef decltype(kernel_mul_mv_1row<half, half4>) mul_mv_1row_t;\n\ntemplate [[host_name(\"kernel_mul_mv_f16_f32_1row\")]]  kernel mul_mv_1row_t kernel_mul_mv_1row<half,   half4>;\n\n// Assumes row size (ne00) is a multiple of 4\ntemplate<typename T, typename T4>\nkernel void kernel_mul_mv_l4(\n        constant ggml_metal_kargs_mul_mv & args,\n        device const char * src0,\n        device const char * src1,\n        device       char * dst,\n        uint3  tgpig[[threadgroup_position_in_grid]],\n        ushort tiisg[[thread_index_in_simdgroup]]) {\n\n    const int nrows = args.ne11;\n    const int r0 = tgpig.x;\n    const int im = tgpig.z;\n\n    const uint i12 = im%args.ne12;\n    const uint i13 = im/args.ne12;\n\n    const uint64_t offset0 = r0*args.nb01 + (i12/args.r2)*args.nb02 + (i13/args.r3)*args.nb03;\n\n    device const T4 * x4 = (device const T4 *) (src0 + offset0);\n\n    device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1;\n\n    for (int r1 = 0; r1 < nrows; ++r1) {\n        const uint64_t offset1 = r1*args.nb11 + (i12   )*args.nb12 + (i13   )*args.nb13;\n\n        device const float4 * y4 = (device const float4 *) (src1 + offset1);\n\n        float sumf = 0;\n        for (int i = tiisg; i < args.ne00/4; i += 32) {\n            sumf += dot((float4) x4[i], y4[i]);\n        }\n\n        float all_sum = simd_sum(sumf);\n        if (tiisg == 0) {\n            dst_f32[(uint64_t)r1*args.ne0 + r0] = all_sum;\n        }\n    }\n}\n\ntypedef decltype(kernel_mul_mv_l4<half, half4>) mul_mv_l4_t;\n\ntemplate [[host_name(\"kernel_mul_mv_f16_f32_l4\")]]  kernel mul_mv_l4_t kernel_mul_mv_l4<half, half4>;\n\n// function for calculate inner product between half a q4_0 block and 16 floats (yl), sumy is SUM(yl[i])\n// il indicates where the q4 quants begin (0 or QK4_0/4)\n// we assume that the yl's have been multiplied with the appropriate scale factor\n// that corresponds to the missing bit shifts (1, 1/16, 1/256, 1/4096)\ninline float block_q_n_dot_y(device const block_q4_0 * qb_curr, float sumy, thread float * yl, int il) {\n    float d = qb_curr->d;\n\n    float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f };\n\n    device const uint16_t * qs = ((device const uint16_t *) qb_curr + 1 + il/2);\n\n    for (int i = 0; i < 8; i += 2) {\n        acc[0] += yl[i + 0] * (qs[i / 2] & 0x000F);\n        acc[1] += yl[i + 1] * (qs[i / 2] & 0x0F00);\n        acc[2] += yl[i + 8] * (qs[i / 2] & 0x00F0);\n        acc[3] += yl[i + 9] * (qs[i / 2] & 0xF000);\n    }\n\n    return d * (sumy * -8.f + acc[0] + acc[1] + acc[2] + acc[3]);\n}\n\n// putting them in the kernel cause a significant performance penalty\n#define N_DST 4        // each SIMD group works on 4 rows\n#define N_SIMDGROUP 2  // number of SIMD groups in a thread group\n//Note: This is a template, but strictly speaking it only applies to\n//      quantizations where the block size is 32. It also does not\n//      guard against the number of rows not being divisible by\n//      N_DST, so this is another explicit assumption of the implementation.\ntemplate<typename block_q_type, int nr, int nsg, int nw, typename args_t>\nvoid mul_vec_q_n_f32_impl(\n        args_t args,\n        device const char * src0,\n        device const char * src1,\n        device       char * dst,\n        threadgroup  char * shmem,\n        uint3  tgpig,\n        ushort tiisg,\n        ushort sgitg) {\n    const int nb = args.ne00/QK4_0;\n\n    const int r0 = tgpig.x;\n    const int r1 = tgpig.y;\n    const int im = tgpig.z;\n\n    const int first_row = (r0 * nsg + sgitg) * nr;\n\n    const uint i12 = im%args.ne12;\n    const uint i13 = im/args.ne12;\n\n  //const uint64_t offset0 = first_row*args.nb01 + (i12/args.r2)*args.nb02 + (i13/args.r3)*args.nb03;\n    const uint64_t offset1 =        r1*args.nb11 + (i12        )*args.nb12 + (i13        )*args.nb13;\n\n  //device const block_q_type * x = (device const block_q_type *) (src0 + offset0);\n    device const float        * y = (device const float        *) (src1 + offset1);\n\n    // pointers to src0 rows\n    device const block_q_type * ax[nr];\n    for (int row = 0; row < nr; ++row) {\n        const uint64_t offset0 = (first_row + row)*args.nb01 + (i12/args.r2)*args.nb02 + (i13/args.r3)*args.nb03;\n\n        ax[row] = (device const block_q_type *) ((device char *) src0 + offset0);\n    }\n\n    float yl[16]; // src1 vector cache\n    float sumf[nr] = {0.f};\n\n    const short ix = (tiisg/2);\n    const short il = (tiisg%2)*8;\n\n    device const float * yb = y + ix*QK4_0 + il;\n\n    // each thread in a SIMD group deals with half a block.\n    for (int ib = ix; ib < nb; ib += nw/2) {\n        float sumy[2] = { 0.f, 0.f };\n\n#pragma unroll\n        for (int i = 0; i < 8; i += 2) {\n            sumy[0]  += yb[i +  0] + yb[i +  1];\n            yl[i + 0] = yb[i +  0];\n            yl[i + 1] = yb[i +  1]/256.f;\n\n            sumy[1]  += yb[i + 16] + yb[i + 17];\n            yl[i + 8] = yb[i + 16]/16.f;\n            yl[i + 9] = yb[i + 17]/4096.f;\n        }\n\n#pragma unroll\n        for (int row = 0; row < nr; row++) {\n            sumf[row] += block_q_n_dot_y(ax[row] + ib, sumy[0] + sumy[1], yl, il);\n        }\n\n        yb += QK4_0 * 16;\n    }\n\n    device float * dst_f32 = (device float *) dst + im*args.ne0*args.ne1 + r1*args.ne0;\n\n    for (int row = 0; row < nr; ++row) {\n        const float tot = simd_sum(sumf[row]);\n\n        if (tiisg == 0 && first_row + row < args.ne01) {\n            dst_f32[first_row + row] = tot;\n        }\n    }\n}\n\nkernel void kernel_mul_mv_q4_0_f32(\n        constant ggml_metal_kargs_mul_mv & args,\n        device const char * src0,\n        device const char * src1,\n        device       char * dst,\n        uint3  tgpig[[threadgroup_position_in_grid]],\n        ushort tiisg[[thread_index_in_simdgroup]],\n        ushort sgitg[[simdgroup_index_in_threadgroup]]) {\n    mul_vec_q_n_f32_impl<block_q4_0, N_DST, N_SIMDGROUP, N_SIMDWIDTH, constant ggml_metal_kargs_mul_mv &>(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg);\n}\n\n#define BLOCK_SIZE_M 64 // 8 simdgroup matrices from matrix A\n#define BLOCK_SIZE_N 32 // 4 simdgroup matrices from matrix B\n#define BLOCK_SIZE_K 32\n#define THREAD_MAT_M 4 // each thread take 4 simdgroup matrices from matrix A\n#define THREAD_MAT_N 2 // each thread take 2 simdgroup matrices from matrix B\n#define THREAD_PER_BLOCK 128\n#define THREAD_PER_ROW 2 // 2 thread for each row in matrix A to load numbers\n#define THREAD_PER_COL 4 // 4 thread for each row in matrix B to load numbers\n#define SG_MAT_SIZE 64 // simdgroup matrix is of shape 8x8\n#define SG_MAT_ROW 8\n\n// each block_q contains 16*nl weights\ntemplate<typename T, typename T4x4, typename simdgroup_T8x8, typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread T4x4 &)>\nkernel void kernel_mul_mm(\n        constant ggml_metal_kargs_mul_mm & args,\n        device const char * src0,\n        device const char * src1,\n        device       char * dst,\n        threadgroup  char * shmem [[threadgroup(0)]],\n        uint3  tgpig[[threadgroup_position_in_grid]],\n        ushort tiitg[[thread_index_in_threadgroup]],\n        ushort sgitg[[simdgroup_index_in_threadgroup]]) {\n\n    threadgroup T     * sa = (threadgroup T     *)(shmem);\n    threadgroup float * sb = (threadgroup float *)(shmem + 4096);\n\n    const int r0 = tgpig.y;\n    const int r1 = tgpig.x;\n    const int im = tgpig.z;\n\n    // if this block is of 64x32 shape or smaller\n    const short n_rows = (args.ne0 - r0*BLOCK_SIZE_M < BLOCK_SIZE_M) ? (args.ne0 - r0*BLOCK_SIZE_M) : BLOCK_SIZE_M;\n    const short n_cols = (args.ne1 - r1*BLOCK_SIZE_N < BLOCK_SIZE_N) ? (args.ne1 - r1*BLOCK_SIZE_N) : BLOCK_SIZE_N;\n\n    // a thread shouldn't load data outside of the matrix\n    const short thread_row = ((short)tiitg/THREAD_PER_ROW) < n_rows ? ((short)tiitg/THREAD_PER_ROW) : n_rows - 1;\n    const short thread_col = ((short)tiitg/THREAD_PER_COL) < n_cols ? ((short)tiitg/THREAD_PER_COL) : n_cols - 1;\n\n    simdgroup_T8x8     ma[4];\n    simdgroup_float8x8 mb[2];\n    simdgroup_float8x8 mc[8];\n\n    for (short i = 0; i < 8; i++){\n        mc[i] = make_filled_simdgroup_matrix<float, 8>(0.f);\n    }\n\n    short il = (tiitg % THREAD_PER_ROW);\n\n    const int i12 = im%args.ne12;\n    const int i13 = im/args.ne12;\n\n    const uint64_t offset0 = (i12/args.r2)*args.nb02 + (i13/args.r3)*args.nb03;\n    const short    offset1 = il/nl;\n\n    device const block_q * x = (device const block_q *)(src0\n        + args.nb01*(r0*BLOCK_SIZE_M + thread_row) + offset0) + offset1;\n\n    device const float   * y = (device const float   *)(src1\n        + args.nb13*i13\n        + args.nb12*i12\n        + args.nb11*(r1*BLOCK_SIZE_N + thread_col)\n        + args.nb10*(BLOCK_SIZE_K / THREAD_PER_COL * (tiitg % THREAD_PER_COL)));\n\n    for (int loop_k = 0; loop_k < args.ne00; loop_k += BLOCK_SIZE_K) {\n        // load data and store to threadgroup memory\n        T4x4 temp_a;\n        dequantize_func(x, il, temp_a);\n\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n\n        #pragma unroll(16)\n        for (short i = 0; i < 16; i++) {\n            *(sa + SG_MAT_SIZE * ((tiitg/THREAD_PER_ROW/8) \\\n            +                     (tiitg%THREAD_PER_ROW)*16 + (i/8)*8) \\\n            +                     (tiitg/THREAD_PER_ROW)%8  + (i&7)*8) = temp_a[i/4][i%4];\n        }\n\n        *(threadgroup float2x4 *)(sb + 32*8*(tiitg%THREAD_PER_COL) + 8*(tiitg/THREAD_PER_COL)) = *((device float2x4 *) y);\n\n        il = (il + 2 < nl) ? il + 2 : il % 2;\n        x  = (il < 2) ? x + (2 + nl - 1)/nl : x;\n        y += BLOCK_SIZE_K;\n\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n\n        // load matrices from threadgroup memory and conduct outer products\n        threadgroup const T     * lsma = (sa + THREAD_MAT_M*SG_MAT_SIZE*(sgitg%2));\n        threadgroup const float * lsmb = (sb + THREAD_MAT_N*SG_MAT_SIZE*(sgitg/2));\n\n        #pragma unroll(4)\n        for (short ik = 0; ik < BLOCK_SIZE_K/8; ik++) {\n            #pragma unroll(4)\n            for (short i = 0; i < 4; i++) {\n                simdgroup_load(ma[i], lsma + SG_MAT_SIZE * i);\n            }\n\n            simdgroup_barrier(mem_flags::mem_none);\n\n            #pragma unroll(2)\n            for (short i = 0; i < 2; i++) {\n                simdgroup_load(mb[i], lsmb + SG_MAT_SIZE * i);\n            }\n\n            #pragma unroll(8)\n            for (short i = 0; i < 8; i++){\n                simdgroup_multiply_accumulate(mc[i], mb[i/4], ma[i%4], mc[i]);\n            }\n\n            lsma += (BLOCK_SIZE_M/SG_MAT_ROW)*SG_MAT_SIZE;\n            lsmb += (BLOCK_SIZE_N/SG_MAT_ROW)*SG_MAT_SIZE;\n        }\n    }\n\n    if ((r0 + 1) * BLOCK_SIZE_M <= args.ne0 && (r1 + 1) * BLOCK_SIZE_N <= args.ne1) {\n        device float * C = (device float *) dst +\n            (BLOCK_SIZE_M * r0 + 32*(sgitg &  1)) + \\\n            (BLOCK_SIZE_N * r1 + 16*(sgitg >> 1)) * args.ne0 + im*args.ne1*args.ne0;\n\n        for (short i = 0; i < 8; i++) {\n            simdgroup_store(mc[i], C + 8 * (i%4) + 8 * args.ne0 * (i/4), args.ne0);\n        }\n    } else {\n        // block is smaller than 64x32, we should avoid writing data outside of the matrix\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n        threadgroup float * temp_str = ((threadgroup float *) shmem) \\\n                                     + 32*(sgitg&1) + (16*(sgitg >> 1))*BLOCK_SIZE_M;\n        for (short i = 0; i < 8; i++) {\n            simdgroup_store(mc[i], temp_str + 8*(i%4) + 8*BLOCK_SIZE_M*(i/4), BLOCK_SIZE_M);\n        }\n\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n\n        if (sgitg == 0) {\n            for (int j = tiitg; j < n_cols; j += BLOCK_SIZE_N) {\n                device float  * D  = (device float  *) dst + (r0*BLOCK_SIZE_M) + (r1*BLOCK_SIZE_N + j)*args.ne0 + im*args.ne1*args.ne0;\n                device float4 * D4 = (device float4 *) D;\n\n                threadgroup float  * C  = temp_str + (j*BLOCK_SIZE_M);\n                threadgroup float4 * C4 = (threadgroup float4 *) C;\n\n                int i = 0;\n                for (; i < n_rows/4; i++) {\n                    *(D4 + i) = *(C4 + i);\n                }\n\n                i *= 4;\n                for (; i < n_rows; i++) {\n                    *(D + i) = *(C + i);\n                }\n            }\n        }\n    }\n}\n\ntemplate <typename type4x4>\nvoid dequantize_f16(device const half4x4 * src, short il, thread type4x4 & reg) {\n    reg = (type4x4)(*src);\n}\n\n// NOTE: this is not dequantizing - we are simply fitting the template\ntemplate <typename type4x4>\nvoid dequantize_f32(device const float4x4 * src, short il, thread type4x4 & reg) {\n    reg = (type4x4)(*src);\n}\n\ntemplate <typename type4x4>\nvoid dequantize_q4_0(device const block_q4_0 * xb, short il, thread type4x4 & reg) {\n    device const uint16_t * qs = ((device const uint16_t *)xb + 1);\n    const float d1 = il ? (xb->d / 16.h) : xb->d;\n    const float d2 = d1 / 256.f;\n    const float md = -8.h * xb->d;\n    const ushort mask0 = il ? 0x00F0 : 0x000F;\n    const ushort mask1 = mask0 << 8;\n\n    float4x4 reg_f;\n\n    for (int i = 0; i < 8; i++) {\n        reg_f[i/2][2*(i%2) + 0] = d1 * (qs[i] & mask0) + md;\n        reg_f[i/2][2*(i%2) + 1] = d2 * (qs[i] & mask1) + md;\n    }\n\n    reg = (type4x4) reg_f;\n}\n\ntypedef decltype(kernel_mul_mm<half, half4x4, simdgroup_half8x8, float4x4, 1, dequantize_f32>) mat_mm_t;\n\ntemplate [[host_name(\"kernel_mul_mm_f32_f32\")]]     kernel mat_mm_t kernel_mul_mm<half,   half4x4,   simdgroup_half8x8,   float4x4,      1,     dequantize_f32>;\ntemplate [[host_name(\"kernel_mul_mm_f16_f32\")]]     kernel mat_mm_t kernel_mul_mm<half,   half4x4,   simdgroup_half8x8,   half4x4,       1,     dequantize_f16>;\ntemplate [[host_name(\"kernel_mul_mm_q4_0_f32\")]]    kernel mat_mm_t kernel_mul_mm<half,   half4x4,   simdgroup_half8x8,   block_q4_0,    2,     dequantize_q4_0>;"
  },
  {
    "path": "metal/src/kernels/matmul/ggml_gemm/mod.rs",
    "content": "use crate::kernels::matmul::{GemmDispatchParams, GemmKernel};\nuse crate::{LibraryName, MetalStream};\nuse DatumType::{F16, F32};\nuse anyhow::ensure;\nuse metal::{Buffer, MTLSize, NSUInteger};\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_core::tract_linalg::block_quant::{BlockQuant, Q4_0};\nuse tract_gpu::tensor::DeviceTensor;\nuse tract_gpu::utils::as_quant_fact;\n\n#[derive(Debug)]\n#[repr(C)]\nstruct GgmlGemmParams {\n    ne00: i32,\n    ne02: i32,\n    nb01: u64,\n    nb02: u64,\n    nb03: u64,\n    ne12: i32,\n    nb10: u64,\n    nb11: u64,\n    nb12: u64,\n    nb13: u64,\n    ne0: i32,\n    ne1: i32,\n    r2: i16,\n    r3: i16,\n}\n\nimpl From<GemmDispatchParams> for GgmlGemmParams {\n    fn from(params: GemmDispatchParams) -> Self {\n        assert!(params.a_strides.len() == 3 && params.b_strides.len() == 3);\n        let a_el_size = params.dts[0].size_of();\n\n        let b_el_size = if params.q40_b { Q4_0.block_bytes() } else { params.dts[1].size_of() };\n        let mut b_strides = params.b_strides;\n        if params.q40_b {\n            b_strides[0] /= Q4_0.block_len() as isize;\n            b_strides[1] /= Q4_0.block_len() as isize;\n        };\n\n        // Kernel produced transposed output so we swap the inputs\n        GgmlGemmParams {\n            ne00: params.k as i32,\n            ne02: params.b_batch as i32,\n            nb01: (b_strides[1] as usize * b_el_size) as u64,\n            nb02: (b_strides[0] as usize * b_el_size) as u64,\n            nb03: (b_strides[0] as usize * params.b_batch * b_el_size) as u64,\n            ne12: params.a_batch as i32,\n            nb10: (params.a_strides[2] as usize * a_el_size) as u64,\n            nb11: (params.a_strides[1] as usize * a_el_size) as u64,\n            nb12: (params.a_strides[0] as usize * a_el_size) as u64,\n            nb13: (params.a_strides[0] as usize * params.a_batch * a_el_size) as u64,\n            ne0: params.n as i32,\n            ne1: params.m as i32,\n            r2: (params.a_batch / params.b_batch) as i16,\n            r3: 1,\n        }\n    }\n}\n\n#[derive(Debug)]\n#[repr(C)]\nstruct GgmlGemvParams {\n    ne00: i32,\n    ne01: i32,\n    ne02: i32,\n    nb00: u64,\n    nb01: u64,\n    nb02: u64,\n    nb03: u64,\n    ne10: i32,\n    ne11: i32,\n    ne12: i32,\n    nb10: u64,\n    nb11: u64,\n    nb12: u64,\n    nb13: u64,\n    ne0: i32,\n    ne1: i32,\n    r2: i16,\n    r3: i16,\n}\n\nimpl From<GemmDispatchParams> for GgmlGemvParams {\n    fn from(params: GemmDispatchParams) -> Self {\n        assert!(params.a_strides.len() == 3 && params.b_strides.len() == 3);\n        let a_el_size = params.dts[0].size_of();\n\n        let b_el_size = if params.q40_b { Q4_0.block_bytes() } else { params.dts[1].size_of() };\n        let mut b_strides = params.b_strides;\n        if params.q40_b {\n            b_strides[0] /= Q4_0.block_len() as isize;\n            b_strides[1] /= Q4_0.block_len() as isize;\n        };\n\n        // Kernel produced transposed output so we swap the inputs\n        GgmlGemvParams {\n            ne00: params.k as i32,\n            ne01: params.n as i32,\n            ne02: params.b_batch as i32,\n            nb00: (b_strides[2] as usize * b_el_size) as u64,\n            nb01: (b_strides[1] as usize * b_el_size) as u64,\n            nb02: (b_strides[0] as usize * b_el_size) as u64,\n            nb03: (b_strides[0] as usize * params.b_batch * b_el_size) as u64,\n            ne10: params.k as i32,\n            ne11: params.m as i32,\n            ne12: params.a_batch as i32,\n            nb10: (params.a_strides[2] as usize * a_el_size) as u64,\n            nb11: (params.a_strides[1] as usize * a_el_size) as u64,\n            nb12: (params.a_strides[0] as usize * a_el_size) as u64,\n            nb13: (params.a_strides[0] as usize * params.a_batch * a_el_size) as u64,\n            ne0: params.n as i32,\n            ne1: params.m as i32,\n            r2: (params.a_batch / params.b_batch) as i16,\n            r3: 1,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]\npub struct GgmlGemm;\n\nimpl fmt::Display for GgmlGemm {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"GgmlGemm\")\n    }\n}\n\nimpl GemmKernel for GgmlGemm {\n    fn name() -> &'static str {\n        \"ggml\"\n    }\n\n    fn supports_broadcast() -> bool {\n        true\n    }\n\n    fn is_supported_dts(&self, facts: &[TypedFact]) -> bool {\n        assert!(facts.len() == 2, \"Ggml: Expected 2 inputs for Matmul\");\n\n        let regular_types_support = facts.iter().all(|f| f.is_plain())\n            && matches!(\n                (facts[0].datum_type, facts[1].datum_type),\n                (F32, F32) | (F16, F16) | (F32, F16)\n            );\n\n        regular_types_support\n            || (as_quant_fact(&facts[1], &Q4_0).is_some()\n                && facts[0].is_plain()\n                && matches!(facts[0].datum_type, F16 | F32))\n    }\n\n    fn output_dt(&self, _a_dt: DatumType, _b_dt: DatumType) -> TractResult<DatumType> {\n        Ok(F32)\n    }\n\n    fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        params: GemmDispatchParams,\n        a_buffer: &Buffer,\n        b_buffer: &Buffer,\n        c_buffer: &Buffer,\n    ) -> TractResult<()> {\n        let GemmDispatchParams {\n            dts,\n            a_batch,\n            m,\n            k,\n            transpose_a,\n            a_offset,\n            transpose_b,\n            b_offset,\n            c_offset,\n            q40_b,\n            ..\n        } = params;\n\n        ensure!(!transpose_a && transpose_b);\n\n        if (dts[0] == F32) && (k % 32 == 0) && (k >= 64) && ((m > 4) || (q40_b && a_batch > 1)) {\n            dispatch_metal_ggml_gemm(\n                stream, params, a_offset, a_buffer, b_offset, b_buffer, c_buffer, c_offset,\n            )?;\n        } else {\n            dispatch_metal_ggml_gemv(\n                stream, params, a_offset, a_buffer, b_offset, b_buffer, c_buffer, c_offset,\n            )?;\n        }\n\n        Ok(())\n    }\n}\n\nfn mv_kernel_name_and_dispatch_params(\n    params: &GemmDispatchParams,\n) -> TractResult<(String, (u64, u64, u64))> {\n    if params.q40_b {\n        ensure!(params.dts[0] == F32);\n        Ok((\"kernel_mul_mv_q4_0_f32\".to_string(), (8, 8, 1)))\n    } else if params.dts[1] == F32 {\n        ensure!(params.dts[0] == F32);\n        Ok((\"kernel_mul_mv_f32_f32\".to_string(), (32, 1, 4)))\n    } else if params.dts[1] == F16 {\n        if params.dts[0] == F32 {\n            if (params.m * params.a_batch) < 4 {\n                Ok((\"kernel_mul_mv_f16_f32_1row\".to_string(), (32, 1, 1)))\n            } else if (params.k >= 128) && (params.k % 4 == 0) && (params.n >= 8) {\n                Ok((\"kernel_mul_mv_f16_f32_l4\".to_string(), (32, 1, params.m as u64)))\n            } else {\n                Ok((\"kernel_mul_mv_f16_f32\".to_string(), (32, 1, 4)))\n            }\n        } else {\n            // Never used in practice since we upcast input[0] to f32\n            ensure!(params.dts[0] == F16);\n            Ok((\"kernel_mul_mv_f16_f16\".to_string(), (32, 1, 4)))\n        }\n    } else {\n        bail!(\"Unsupported dtype combination for GGML gemv: dts={:?}\", params.dts);\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\nfn dispatch_metal_ggml_gemv(\n    stream: &MetalStream,\n    params: GemmDispatchParams,\n    a_offset: usize,\n    a_buffer: &Buffer,\n    b_offset: usize,\n    b_buffer: &Buffer,\n    output: &Buffer,\n    output_offset: usize,\n) -> TractResult<()> {\n    let (name, (nth0, nth1, nrows)) = mv_kernel_name_and_dispatch_params(&params)?;\n    //dbg!(&name);\n    let pipeline = stream.load_pipeline(LibraryName::Ggml, &name)?;\n\n    let ggml_params: GgmlGemvParams = params.clone().into();\n    let command_buffer = stream.command_buffer();\n    command_buffer.encode(|encoder| {\n        encoder.set_compute_pipeline_state(&pipeline);\n        encoder.set_bytes(\n            0,\n            std::mem::size_of::<GgmlGemvParams>() as u64,\n            &ggml_params as *const _ as *const _,\n        );\n\n        // Kernel produced transposed output so we swap the inputs\n        encoder.set_buffer(1, Some(b_buffer), b_offset as NSUInteger);\n        encoder.set_buffer(2, Some(a_buffer), a_offset as NSUInteger);\n        encoder.set_buffer(3, Some(output), output_offset as NSUInteger);\n\n        let grid_size = if !params.q40_b {\n            MTLSize {\n                width: params.n as u64,\n                height: (params.m as u64).div_ceil(nrows),\n                depth: /* batch_size_out */ params.a_batch as u64,\n            }\n        } else {\n            MTLSize {\n                width: (params.n as u64).div_ceil(8),\n                height: params.m as u64,\n                depth: /* batch_size_out */ params.a_batch as u64,\n            }\n        };\n        let group_size = MTLSize { width: nth0, height: nth1, depth: 1 };\n\n        encoder.dispatch_thread_groups(grid_size, group_size);\n    });\n\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\nfn dispatch_metal_ggml_gemm(\n    stream: &MetalStream,\n    params: GemmDispatchParams,\n    a_offset: usize,\n    a_buffer: &Buffer,\n    b_offset: usize,\n    b_buffer: &Buffer,\n    output: &Buffer,\n    output_offset: usize,\n) -> TractResult<()> {\n    let GemmDispatchParams { dts, q40_b, .. } = params;\n\n    ensure!((matches!(dts[1], F32 | F16) || q40_b) && dts[0] == F32);\n\n    let i1_tname = if !q40_b { DeviceTensor::tname(dts[1])? } else { \"q4_0\" };\n    let i2_tname = DeviceTensor::tname(dts[0])?;\n\n    let name = format!(\"kernel_mul_mm_{i1_tname}_{i2_tname}\");\n    //dbg!(&name);\n    let pipeline = stream.load_pipeline(LibraryName::Ggml, &name)?;\n\n    let ggml_params: GgmlGemmParams = params.clone().into();\n    let command_buffer = stream.command_buffer();\n    command_buffer.encode(|encoder| {\n        encoder.set_compute_pipeline_state(&pipeline);\n        encoder.set_bytes(\n            0,\n            std::mem::size_of::<GgmlGemmParams>() as u64,\n            &ggml_params as *const _ as *const _,\n        );\n\n        // Kernel produced transposed output so we swap the inputs\n        encoder.set_buffer(1, Some(b_buffer), b_offset as NSUInteger);\n        encoder.set_buffer(2, Some(a_buffer), a_offset as NSUInteger);\n        encoder.set_buffer(3, Some(output), output_offset as NSUInteger);\n\n        let grid_size = MTLSize {\n            width: (params.m as u64).div_ceil(32),\n            height: (params.n as u64).div_ceil(64),\n            depth: /* batch_size_out */ params.a_batch as u64,\n        };\n        let group_size = MTLSize { width: 128, height: 1, depth: 1 };\n\n        encoder.set_threadgroup_memory_length(0, 8192);\n        encoder.dispatch_thread_groups(grid_size, group_size);\n    });\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::with_borrowed_metal_stream;\n\n    use std::any::TypeId;\n\n    use num_traits::Float;\n    use tract_core::ops::array::MultiBroadcastTo;\n    use tract_core::ops::cast::Cast;\n    use tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\n    use tract_linalg::block_quant::{BlockQuant, BlockQuantStorage, Q4_0};\n\n    use super::*;\n    use crate::kernels::matmul::GemmImpl;\n    use crate::kernels::matmul::tests::run_mmm_test_case;\n    use tract_gpu::tensor::IntoDevice;\n\n    #[test]\n    fn test_ggml_compilation() -> TractResult<()> {\n        crate::utils::with_borrowed_metal_stream(|stream| stream.load_library(LibraryName::Ggml))?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_mat_mul() -> TractResult<()> {\n        run_mmm_test_case::<GgmlGemm>((1, 5, 64, 2), false, true, F32, F32)?;\n        run_mmm_test_case::<GgmlGemm>((2, 8, 64, 2), false, true, F32, F32)?;\n        run_mmm_test_case::<GgmlGemm>((1, 5, 64, 2), false, true, F32, F16)?;\n        run_mmm_test_case::<GgmlGemm>((3, 8, 64, 200), false, true, F32, F16)?;\n        run_mmm_test_case::<GgmlGemm>((10, 25, 512, 320), false, true, F32, F16)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_mat_vec() -> TractResult<()> {\n        // f32_f32\n        run_mmm_test_case::<GgmlGemm>((1, 8, 32, 3), false, true, F32, F32)?;\n        run_mmm_test_case::<GgmlGemm>((1, 4, 61, 2), false, true, F32, F32)?;\n        run_mmm_test_case::<GgmlGemm>((2, 4, 128, 8), false, true, F32, F32)?;\n\n        // f16_f32_1row\n        run_mmm_test_case::<GgmlGemm>((1, 1, 32, 2), false, true, F32, F16)?;\n        run_mmm_test_case::<GgmlGemm>((1, 3, 62, 2), false, true, F32, F16)?;\n        run_mmm_test_case::<GgmlGemm>((1, 3, 2, 9), false, true, F32, F16)?;\n\n        // f16_f32_L4\n        run_mmm_test_case::<GgmlGemm>((2, 2, 128, 8), false, true, F32, F16)?;\n        run_mmm_test_case::<GgmlGemm>((4, 4, 156, 30), false, true, F32, F16)?;\n\n        // f16_f32\n        run_mmm_test_case::<GgmlGemm>((1, 4, 32, 2), false, true, F32, F16)?;\n        run_mmm_test_case::<GgmlGemm>((1, 4, 61, 2), false, true, F32, F16)?;\n        run_mmm_test_case::<GgmlGemm>((4, 4, 128, 7), false, true, F32, F16)?;\n\n        // f16_f16\n        run_mmm_test_case::<GgmlGemm>((1, 1, 2, 1), false, true, F16, F16)?;\n        run_mmm_test_case::<GgmlGemm>((1, 4, 61, 2), false, true, F16, F16)?;\n        run_mmm_test_case::<GgmlGemm>((2, 16, 128, 9), false, true, F16, F16)?;\n        Ok(())\n    }\n\n    fn reference(a: Tensor, b: Tensor) -> TractResult<Tensor> {\n        let batch = b.shape()[0];\n        let batch_ratio = a.shape()[0] / batch;\n        let matmul = PrefixMatMul {\n            transpose_a: false,\n            transpose_b: true,\n            transpose_c: false,\n            quantize_output: None,\n            operating_dt: Some(DatumType::F32),\n        };\n\n        let mut model = TypedModel::default();\n\n        let lhs = model.add_source(\"lhs\", TypedFact::shape_and_dt_of(&a))?;\n        let mut rhs = model.add_source(\"rhs\", TypedFact::shape_and_dt_of(&b))?;\n\n        if b.datum_type() == DatumType::F16 {\n            rhs = model.wire_node(\"cast\", Cast { to: DatumType::F32 }, &[rhs])?[0];\n        }\n        if batch_ratio > 1 {\n            let add_axis_out = model.wire_node(\"add_axis\", AxisOp::Add(1), &[rhs])?[0];\n            let mut broadcast_shape = b.shape().to_vec();\n\n            broadcast_shape.insert(1, batch_ratio);\n            let broadcast_out = model.wire_node(\n                \"broadcast\",\n                MultiBroadcastTo { shape: ShapeFact::from_dims(broadcast_shape) },\n                &[add_axis_out],\n            )?[0];\n            rhs = model.wire_node(\n                \"reshape\",\n                AxisOp::Reshape(\n                    0,\n                    tvec![batch.into(), batch_ratio.into()],\n                    tvec![(batch * batch_ratio).into()],\n                ),\n                &[broadcast_out],\n            )?[0]\n        }\n        let output = model.wire_node(\"matmul\", matmul, &[lhs, rhs])?;\n\n        model.select_output_outlets(&output)?;\n        model = model.into_decluttered()?;\n        let mut output =\n            DefaultRuntime.prepare(model)?.run(tvec!(a.into_tvalue(), b.into_tvalue()))?;\n        Ok(output.remove(0).into_tensor())\n    }\n\n    fn run_ggml_mat_mul_test<F: Datum + Float>(\n        batch: usize,\n        broadcast_ratio: usize,\n        m: usize,\n        k: usize,\n        n: usize,\n        q40: bool,\n    ) -> TractResult<()>\n    where\n        f32: From<F>,\n    {\n        with_borrowed_metal_stream(|stream| {\n            let a_shape = [batch * broadcast_ratio, m, k];\n            let b_shape = [batch, n, k];\n\n            let a_data = (0..batch * broadcast_ratio * k * m)\n                .map(|f| f as f32 / (batch * broadcast_ratio * m * k) as f32)\n                .collect::<Vec<_>>();\n\n            let a = Tensor::from_shape(&a_shape, &a_data)?;\n\n            let b_data = (0..batch * n * k)\n                .map(|f| F::from(f).unwrap() / F::from(batch * n * k).unwrap())\n                .collect::<Vec<_>>();\n\n            let (ref_b, metal_b) = if q40 {\n                ensure!(TypeId::of::<F>() == TypeId::of::<f32>());\n                let b_data: Vec<f32> = b_data.into_iter().map(|x| x.into()).collect();\n                let b_tensor =\n                    Q4_0.simulate_precision_loss(Tensor::from_shape(&b_shape, &b_data)?, 2)?;\n\n                ensure!(k % 32 == 0);\n                let b_q4_0_tensor = BlockQuantStorage::new(\n                    Box::new(Q4_0),\n                    batch * n,\n                    k,\n                    Arc::new(Q4_0.quant_f32(&b_data)?),\n                )?\n                .into_tensor_with_shape(f32::datum_type(), &[batch, n, k]);\n                (b_tensor, b_q4_0_tensor)\n            } else {\n                let b_tensor = Tensor::from_shape(&b_shape, &b_data)?;\n                (b_tensor.clone(), b_tensor)\n            };\n\n            let metal_output = GemmImpl::<GgmlGemm>::new(false, true).eval(\n                stream,\n                &a.clone().into_device()?,\n                &metal_b.clone().into_device()?,\n            )?;\n            let output = reference(a, ref_b)?;\n            metal_output.to_host()?.close_enough(&output, Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_broadcast() -> TractResult<()> {\n        run_ggml_mat_mul_test::<f32>(2, 2, 1, 8, 4, false)?;\n        run_ggml_mat_mul_test::<f32>(6, 3, 26, 22, 1, false)?;\n        run_ggml_mat_mul_test::<f16>(1, 2, 1, 64, 10, false)?;\n        run_ggml_mat_mul_test::<f16>(2, 2, 1, 128, 8, false)?;\n        run_ggml_mat_mul_test::<f16>(4, 4, 6, 64, 10, false)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_q4() -> TractResult<()> {\n        run_ggml_mat_mul_test::<f32>(32, 1, 1, 32, 32, true)?;\n        run_ggml_mat_mul_test::<f32>(1, 1, 320, 2048, 1, true)?;\n        run_ggml_mat_mul_test::<f32>(4, 1, 1, 2048, 320, true)?;\n        run_ggml_mat_mul_test::<f32>(1, 1, 1, 32, 32, true)?;\n        run_ggml_mat_mul_test::<f32>(1, 1, 1, 64, 4, true)?;\n        run_ggml_mat_mul_test::<f32>(3, 1, 1, 4096, 512, true)?;\n        run_ggml_mat_mul_test::<f32>(1, 1, 1, 32, 32, true)?;\n        run_ggml_mat_mul_test::<f32>(1, 1, 1, 64, 4, true)?;\n        run_ggml_mat_mul_test::<f32>(3, 1, 1, 2048, 128, true)?;\n        run_ggml_mat_mul_test::<f32>(1, 3, 1, 32, 32, true)?;\n        run_ggml_mat_mul_test::<f32>(4, 2, 1, 64, 4, true)?;\n        run_ggml_mat_mul_test::<f32>(3, 2, 1, 512, 256, true)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/matmul/mfa/mod.rs",
    "content": "use crate::kernels::matmul::{GemmDispatchParams, GemmKernel};\nuse crate::{ConstantValues, LibraryName, MetalStream, Value};\nuse anyhow::ensure;\nuse metal::{Buffer, MTLSize, NSUInteger};\nuse std::ffi::c_void;\nuse std::fmt;\nuse tract_core::internal::*;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]\npub struct MfaGemm;\n\nimpl fmt::Display for MfaGemm {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"MfaGemm\")\n    }\n}\n\nimpl GemmKernel for MfaGemm {\n    fn name() -> &'static str {\n        \"mfa\"\n    }\n\n    fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        params: GemmDispatchParams,\n        a_buffer: &Buffer,\n        b_buffer: &Buffer,\n        c_buffer: &Buffer,\n    ) -> TractResult<()> {\n        let GemmDispatchParams {\n            dts,\n            a_batch,\n            m,\n            k,\n            n,\n            transpose_a,\n            a_offset,\n            transpose_b,\n            b_offset,\n            c_offset,\n            a_strides,\n            b_strides,\n            ..\n        } = params;\n\n        ensure!(\n            matches!(dts[0], DatumType::F32 | DatumType::F16),\n            \"Unsupported datum type for Mfa {:?}\",\n            dts[0]\n        );\n        ensure!(\n            dts[0] == dts[1] && dts[0] == dts[2],\n            \"Mfa only supports homogeneous datum types. I: {:?}, {:?}. O: {:?}\",\n            dts[0],\n            dts[1],\n            dts[2]\n        );\n\n        dispatch_metal_mfa_gemm(\n            stream,\n            dts[0],\n            (a_batch, m, n, k),\n            unsafe { std::mem::transmute::<&[isize], &[usize]>(a_strides.as_slice()) },\n            a_offset,\n            a_buffer,\n            transpose_a,\n            unsafe { std::mem::transmute::<&[isize], &[usize]>(b_strides.as_slice()) },\n            b_offset,\n            b_buffer,\n            transpose_b,\n            c_buffer,\n            c_offset,\n        )?;\n\n        Ok(())\n    }\n}\n\n// From https://github.com/huggingface/candle/blob/main/candle-metal-kernels/src/lib.rs\n#[allow(clippy::too_many_arguments)]\npub fn dispatch_metal_mfa_gemm(\n    stream: &MetalStream,\n    dt: DatumType,\n    (b, m, n, k): (usize, usize, usize, usize),\n    lhs_stride: &[usize],\n    lhs_offset: usize,\n    lhs_buffer: &Buffer,\n    lhs_transpose: bool,\n    rhs_stride: &[usize],\n    rhs_offset: usize,\n    rhs_buffer: &Buffer,\n    rhs_transpose: bool,\n    output: &Buffer,\n    output_offset: usize,\n) -> TractResult<()> {\n    assert!(rhs_stride.len() >= 2);\n    assert!(lhs_stride.len() >= 2);\n    let rhs_m1 = rhs_stride[rhs_stride.len() - 1];\n    let rhs_m2 = rhs_stride[rhs_stride.len() - 2];\n    let lhs_m1 = lhs_stride[lhs_stride.len() - 1];\n    let lhs_m2 = lhs_stride[lhs_stride.len() - 2];\n    let a_trans = lhs_transpose;\n    let b_trans = rhs_transpose;\n\n    if a_trans {\n        // (k, m)\n        ensure!(\n            lhs_m1 == 1 && lhs_m2 == m,\n            \"Invalid left matmul argument [{lhs_m2}, {lhs_m1}] != [{m}, 1], strides: {:?} {:?} dims: (m: {m}, n: {n}, k: {k})\",\n            lhs_stride,\n            rhs_stride\n        );\n    } else {\n        // (m, k)\n        ensure!(\n            lhs_m1 == 1 && lhs_m2 == k,\n            \"Invalid left matmul argument [{lhs_m2}, {lhs_m1}] != [{k}, 1], strides: {:?} {:?} dims: (m: {m}, n: {n}, k: {k})\",\n            lhs_stride,\n            rhs_stride\n        );\n    }\n\n    if b_trans {\n        // (n, k)\n        ensure!(\n            rhs_m1 == 1 && rhs_m2 == k,\n            \"Invalid right matmul argument [{rhs_m2}, {rhs_m1}] != [{k}, 1], strides: {:?} {:?} dims: (m: {m}, n: {n}, k: {k})\",\n            lhs_stride,\n            rhs_stride\n        );\n    } else {\n        // (k, n)\n        ensure!(\n            rhs_m1 == 1 && rhs_m2 == n,\n            \"Invalid right matmul argument [{rhs_m2}, {rhs_m1}] != [{n}, 1] {:?} {:?} dims: (m: {m}, n: {n}, k: {k})\",\n            lhs_stride,\n            rhs_stride\n        );\n    }\n\n    let d_trans = false;\n    let alpha = 1.0f32;\n    let beta = 0.0f32;\n    let batched = b > 1;\n    let fused_activation = false;\n    let fused_bias = false;\n    let (m_simd, n_simd, k_simd, m_splits, n_splits) = if m == 1 {\n        let m_simd = 8;\n        let n_simd = 8;\n        let k_simd = 64;\n        let m_splits = 1;\n        let n_splits = 1;\n        (m_simd, n_simd, k_simd, m_splits, n_splits)\n    } else {\n        let m_simd = 40;\n        let n_simd = 40;\n        let k_simd = 32;\n        let m_splits = 1;\n        let n_splits = 1;\n        (m_simd, n_simd, k_simd, m_splits, n_splits)\n    };\n    let constants = Some(ConstantValues::new(vec![\n        (0, Value::USize(m)),\n        (1, Value::USize(n)),\n        (2, Value::USize(k)),\n        (10, Value::Bool(a_trans)),\n        (11, Value::Bool(b_trans)),\n        (13, Value::Bool(d_trans)),\n        (20, Value::F32(alpha)),\n        (21, Value::F32(beta)),\n        (100, Value::Bool(batched)),\n        (101, Value::Bool(fused_activation)),\n        // Garbage\n        (102, Value::Bool(false)),\n        (103, Value::Bool(false)),\n        (113, Value::Bool(false)),\n        (50_000, Value::Bool(false)),\n        // End garbage\n        (200, Value::U16(m_simd)),\n        (201, Value::U16(n_simd)),\n        (202, Value::U16(k_simd)),\n        (210, Value::U16(m_splits)),\n        (211, Value::U16(n_splits)),\n        (50_001, Value::Bool(fused_bias)),\n    ]));\n\n    let name = match dt {\n        DatumType::F32 => \"sgemm\",\n        DatumType::F16 => \"hgemm\",\n        _ => bail!(\"MFA GEMM only support F32 or F16 tensors\"),\n    };\n\n    let pipeline = stream.load_pipeline_with_constants(LibraryName::MfaLib, name, constants)?;\n    let m_group = m_simd * m_splits;\n    let n_group = n_simd * n_splits;\n\n    let a_block_length = m_group * k_simd;\n    let b_block_length = k_simd * n_group;\n\n    let mut block_elements = a_block_length + b_block_length;\n    if (m % 8 != 0) && (n % 8 != 0) {\n        let c_block_length = m_group * n_group;\n        block_elements = std::cmp::max(c_block_length, block_elements)\n    }\n    if fused_bias {\n        if d_trans {\n            block_elements = std::cmp::max(block_elements, m_group);\n        } else {\n            block_elements = std::cmp::max(block_elements, n_group);\n        }\n    }\n\n    let block_bytes = block_elements * dt.size_of() as u16;\n\n    let command_buffer = stream.command_buffer();\n    command_buffer.encode(|encoder| {\n        encoder.set_compute_pipeline_state(&pipeline);\n        encoder.set_threadgroup_memory_length(0, block_bytes.into());\n        encoder.set_buffer(0, Some(lhs_buffer), lhs_offset as NSUInteger);\n        encoder.set_buffer(1, Some(rhs_buffer), rhs_offset as NSUInteger);\n        encoder.set_buffer(2, Some(output), output_offset as NSUInteger);\n        // TODO Tensor D\n\n        let grid_z = b;\n        if batched {\n            let byte_stride_a: usize = lhs_stride[lhs_stride.len() - 3] * dt.size_of();\n            let byte_stride_b: usize = rhs_stride[rhs_stride.len() - 3] * dt.size_of();\n            let byte_stride_c = m * n * dt.size_of();\n            // TODO byte_stride_d\n            let byte_stride_d = 0;\n\n            let buffer: Vec<u64> = vec![\n                byte_stride_a as _,\n                byte_stride_b as _,\n                byte_stride_c as _,\n                byte_stride_d as _,\n            ];\n            encoder.set_bytes(\n                10,\n                (buffer.len() * core::mem::size_of::<u64>()) as NSUInteger,\n                buffer.as_ptr() as *const NSUInteger as *const c_void,\n            );\n        }\n\n        let grid_size = MTLSize {\n            width: n.div_ceil(n_group.into()) as NSUInteger,\n            height: m.div_ceil(m_group.into()) as NSUInteger,\n            depth: grid_z as NSUInteger,\n        };\n        let group_size =\n            MTLSize { width: 32 * (m_splits as u64) * (n_splits as u64), height: 1, depth: 1 };\n        encoder.use_resource(lhs_buffer, metal::MTLResourceUsage::Read);\n        encoder.use_resource(rhs_buffer, metal::MTLResourceUsage::Read);\n        encoder.use_resource(output, metal::MTLResourceUsage::Write);\n        encoder.dispatch_thread_groups(grid_size, group_size);\n    });\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::with_borrowed_metal_stream;\n\n    use super::*;\n    use crate::kernels::matmul::GemmImpl;\n    use tract_gpu::tensor::{DeviceTensor, IntoDevice};\n\n    #[test]\n    fn test_mfa_gemm() -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let (b, m, n, k) = (1, 2, 4, 3);\n            let a = Tensor::from_shape(\n                &[b, m, k],\n                &(0..b * m * k).map(|f| f as f32).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n            let b = Tensor::from_shape(\n                &[b, k, n],\n                &(0..b * n * k).map(|f| f as f32).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let c = GemmImpl::<MfaGemm>::default().eval(stream, &a, &b)?;\n\n            let expected_c =\n                Tensor::from_shape(&[1, 2, 4], &[20.0, 23.0, 26.0, 29.0, 56.0, 68.0, 80.0, 92.0])?;\n\n            let c = c.to_host()?;\n            assert!(c.close_enough(&expected_c, Approximation::Close).is_ok());\n\n            let (b, m, n, k) = (2, 2, 4, 3);\n            let a = DeviceTensor::from_shape(\n                &[b, m, k],\n                &(0..b * m * k).map(|f| f as f32).collect::<Vec<_>>(),\n            )?;\n            let b = DeviceTensor::from_shape(\n                &[b, k, n],\n                &(0..b * n * k).map(|f| f as f32).collect::<Vec<_>>(),\n            )?;\n\n            let c = GemmImpl::<MfaGemm>::default().eval(stream, &a, &b)?;\n\n            let expected_c = Tensor::from_shape(\n                &[2, 2, 4],\n                &[\n                    20.0, 23.0, 26.0, 29.0, 56.0, 68.0, 80.0, 92.0, 344.0, 365.0, 386.0, 407.0,\n                    488.0, 518.0, 548.0, 578.0,\n                ],\n            )?;\n\n            assert!(c.to_host()?.close_enough(&expected_c, Approximation::Close).is_ok());\n            Ok(())\n        })\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/matmul/mlx_gemm/mlx_gemm.metal",
    "content": "// MLX Kernel extracted from:\n// https://github.com/ml-explore/mlx/blob/main/mlx/backend/metal/kernels/steel/gemm\n// Copyright © 2024 Apple Inc.\n\n#include <metal_simdgroup>\n#include <metal_simdgroup_matrix>\n#include <metal_stdlib>\n\n#define STEEL_CONST static constant constexpr const\n#define STEEL_PRAGMA_UNROLL _Pragma(\"clang loop unroll(full)\")\n\nusing namespace metal;\n\n// https://github.com/ml-explore/mlx/blob/02efb310cac667bc547d1b96f21596c221f84fe7/mlx/backend/metal/kernels/steel/gemm/params.h#L1\n///////////////////////////////////////////////////////////////////////////////\n// GEMM param classes\n///////////////////////////////////////////////////////////////////////////////\n\nstruct GEMMParams {\n  const int M;\n  const int N;\n  const int K;\n\n  const int lda;\n  const int ldb;\n  const int ldd;\n\n  const int tiles_n;\n  const int tiles_m;\n\n  const size_t batch_stride_a;\n  const size_t batch_stride_b;\n  const size_t batch_stride_d;\n\n  const int swizzle_log;\n  const int gemm_k_iterations_aligned;\n\n  const int batch_ndim;\n};\n\nstruct GEMMDebug {\n  int TM_stride;\n  int TN_stride;\n  int WM;\n  int WN;\n  int TM;\n  int TN;\n  int num_threads_in_simd;\n  int num_simd_group;\n};\n\nstruct GEMMSpiltKParams {\n  const int M;\n  const int N;\n  const int K;\n\n  const int lda;\n  const int ldb;\n  const int ldc;\n\n  const int tiles_n;\n  const int tiles_m;\n\n  const int split_k_partitions;\n  const int split_k_partition_stride;\n  const int split_k_partition_size;\n\n  const int gemm_k_iterations_aligned;\n};\n\nstruct GEMMAddMMParams {\n  const int ldc;\n  const int fdc;\n\n  const size_t batch_stride_c;\n\n  const float alpha;\n  const float beta;\n};\n\n// https://github.com/ml-explore/mlx/blob/02efb310cac667bc547d1b96f21596c221f84fe7/mlx/backend/metal/kernels/steel/gemm/loader.h#L1\n///////////////////////////////////////////////////////////////////////////////\n// Loading helper\n///////////////////////////////////////////////////////////////////////////////\n\ntemplate <\n    typename T,\n    short BROWS,\n    short BCOLS,\n    short dst_ld,\n    short reduction_dim,\n    short tgp_size,\n    short alignment = 1,\n    short n_reads = (BCOLS * BROWS) / (tgp_size),\n    short TCOLS = BCOLS / n_reads,\n    short TROWS = tgp_size / TCOLS>\nstruct BlockLoader {\n  STEEL_CONST short n_rows = (BROWS + TROWS - 1) / TROWS;\n  STEEL_CONST short vec_size = n_reads;\n\n  // Leading dimension for src\n  const int src_ld;\n  const int tile_stride;\n\n  // Thread location indices\n  const short thread_idx;\n  const short bi;\n  const short bj;\n\n  // threadgroup and device memory\n  threadgroup T* dst;\n  const device T* src;\n\n  struct alignas(alignment * sizeof(T)) ReadVector {\n    uint8_t v[sizeof(T) * vec_size];\n  };\n\n  /* Constructor */\n  METAL_FUNC BlockLoader(\n      const device T* src_,\n      const int src_ld_,\n      threadgroup T* dst_,\n      ushort simd_group_id [[simdgroup_index_in_threadgroup]],\n      ushort simd_lane_id [[thread_index_in_simdgroup]])\n      : src_ld(src_ld_),\n        tile_stride(reduction_dim ? BCOLS : BROWS * src_ld),\n        thread_idx(simd_group_id * 32 + simd_lane_id),\n        bi(thread_idx / TCOLS),\n        bj(vec_size * (thread_idx % TCOLS)),\n        dst(dst_ + bi * dst_ld + bj),\n        src(src_ + bi * src_ld + bj) {}\n\n  /* Apply operation to threadgroup without bound checking */\n  template <typename UnaryOp>\n  METAL_FUNC void apply_inplace_op(thread const UnaryOp& op) const {\n    STEEL_PRAGMA_UNROLL\n    for (short i = 0; i < BROWS; i += TROWS) {\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < vec_size; j++) {\n        dst[i * dst_ld + j] = op.apply(dst[i * dst_ld + j]);\n      }\n    }\n  }\n\n  /* Load from device memory into threadgroup memory - without bound checking */\n  METAL_FUNC void load_unsafe() const {\n    STEEL_PRAGMA_UNROLL\n    for (short i = 0; i < BROWS; i += TROWS) {\n      *((threadgroup ReadVector*)(&dst[i * dst_ld])) =\n          *((const device ReadVector*)(&src[i * src_ld]));\n    }\n  }\n\n  /* Load from device memory into threadgroup memory - with bound checking */\n  METAL_FUNC void load_safe(short2 src_tile_dim) const {\n    src_tile_dim = src_tile_dim - short2(bj, bi);\n\n    // Skip loading if thread has no valid reads\n    if (src_tile_dim.x <= 0 || src_tile_dim.y <= 0) {\n      STEEL_PRAGMA_UNROLL\n      for (short i = 0; i < BROWS; i += TROWS) {\n        STEEL_PRAGMA_UNROLL\n        for (short j = 0; j < vec_size; j++) {\n          dst[i * dst_ld + j] = T(0);\n        }\n      }\n      return;\n    }\n\n    // Use fast thread memory for bound checks\n    bool tmp_idx[vec_size];\n    T tmp_val[vec_size];\n\n    STEEL_PRAGMA_UNROLL\n    for (short i = 0; i < BROWS; i += TROWS) {\n      // Make sure tmp_idx only contains valid indices\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < vec_size; j++) {\n        tmp_idx[j] = (i < src_tile_dim.y) && (j < src_tile_dim.x);\n      }\n\n      // Read valid indices into tmp_val\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < vec_size; j++) {\n        tmp_val[j] = src[(tmp_idx[j] ? i * src_ld + j : 0)];\n      }\n\n      // Zero out uneeded values\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < vec_size; j++) {\n        tmp_val[j] = tmp_idx[j] ? tmp_val[j] : T(0);\n      }\n\n      // Copy values to threadgroup memory\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < vec_size; j++) {\n        dst[i * dst_ld + j] = tmp_val[j];\n      }\n    }\n  }\n\n  /* Iteration helper */\n  METAL_FUNC void next() {\n    src += tile_stride;\n  }\n};\n\n// https://github.com/ml-explore/mlx/blob/02efb310cac667bc547d1b96f21596c221f84fe7/mlx/backend/metal/kernels/steel/gemm/transforms.h#L1\n///////////////////////////////////////////////////////////////////////////////\n// Transforms and Epilogues\n///////////////////////////////////////////////////////////////////////////////\n\ntemplate <typename OutT, typename InT>\nstruct TransformNone {\n  static METAL_FUNC OutT apply(InT x) {\n    return static_cast<OutT>(x);\n  }\n\n  static METAL_FUNC OutT apply(InT x, OutT) {\n    return static_cast<OutT>(x);\n  }\n};\n\ntemplate <typename OutT, typename InT>\nstruct TransformAdd {\n  TransformAdd(const float, const float) {}\n\n  static METAL_FUNC OutT apply(InT x) {\n    return static_cast<OutT>(x);\n  }\n\n  static METAL_FUNC OutT apply(InT x, OutT c) {\n    return static_cast<OutT>(x) + c;\n  }\n};\n\ntemplate <typename OutT, typename InT>\nstruct TransformAxpby {\n  const float alpha;\n  const float beta;\n\n  TransformAxpby(const float alpha_, const float beta_)\n      : alpha(alpha_), beta(beta_) {}\n\n  static METAL_FUNC OutT apply(InT x) {\n    return static_cast<OutT>(x);\n  }\n\n  METAL_FUNC OutT apply(InT x, OutT c) const {\n    return static_cast<OutT>(x * alpha + (beta * c));\n  }\n};\n\ntemplate <typename T>\nstruct AccumHelper {\n  typedef float accum_type;\n};\n\nstruct BlockSwizzle {\n  static METAL_FUNC int2\n  swizzle(uint3 tid [[threadgroup_position_in_grid]], const int swizzle_log) {\n    const int tid_x = (tid.x) >> swizzle_log;\n    const int tid_y =\n        ((tid.y) << swizzle_log) + ((tid.x) & ((1 << swizzle_log) - 1));\n    return int2(tid_x, tid_y);\n  }\n};\n\n// https://github.com/ml-explore/mlx/blob/02efb310cac667bc547d1b96f21596c221f84fe7/mlx/backend/metal/kernels/steel/gemm/mma.h#L1\n///////////////////////////////////////////////////////////////////////////////\n// MMA helper\n///////////////////////////////////////////////////////////////////////////////\n\ntemplate <\n    typename T,\n    typename U,\n    int BM,\n    int BN,\n    int BK,\n    int WM,\n    int WN,\n    bool transpose_a,\n    bool transpose_b,\n    short lda_tgp,\n    short ldb_tgp,\n    typename AccumType = float,\n    typename Epilogue = TransformNone<U, AccumType>>\nstruct BlockMMA {\n  // Warp tile simdgroup matrix strides along M\n  STEEL_CONST short TM_stride = 8 * WM;\n  // Warp tile simdgroup matrix strides along M\n  STEEL_CONST short TN_stride = 8 * WN;\n\n  // Warp tile size along M\n  STEEL_CONST short TM = BM / TM_stride;\n  // Warp tile size along N\n  STEEL_CONST short TN = BN / TN_stride;\n\n  // Strides of A, B along reduction axis\n  STEEL_CONST short simd_stride_a = {\n      transpose_a ? TM_stride : TM_stride * lda_tgp};\n  STEEL_CONST short simd_stride_b = {\n      transpose_b ? TN_stride * ldb_tgp : TN_stride};\n\n  // Jump between elements\n  STEEL_CONST short jump_a = {transpose_a ? lda_tgp : 1};\n  STEEL_CONST short jump_b = {transpose_b ? ldb_tgp : 1};\n\n  STEEL_CONST short tile_stride_a = {transpose_a ? 8 * lda_tgp : 8};\n  STEEL_CONST short tile_stride_b = {transpose_b ? 8 : 8 * ldb_tgp};\n\n  // Simdgroup matrices\n  simdgroup_matrix<AccumType, 8, 8> Asimd[TM];\n  simdgroup_matrix<AccumType, 8, 8> Bsimd[TN];\n  simdgroup_matrix<AccumType, 8, 8> results[TM * TN] = {\n      simdgroup_matrix<AccumType, 8, 8>(0)};\n\n  // Offsets within threadgroup\n  const short tm;\n  const short tn;\n\n  short sm;\n  short sn;\n\n  short As_offset;\n  short Bs_offset;\n\n  /* Constructor */\n  METAL_FUNC BlockMMA(\n      ushort simd_group_id [[simdgroup_index_in_threadgroup]],\n      ushort simd_lane_id [[thread_index_in_simdgroup]])\n      : tm(8 * (simd_group_id / WN)), tn(8 * (simd_group_id % WN)) {\n    // Determine thread position in simdgroup matrix\n    short qid = simd_lane_id / 4;\n    sm = (qid & 4) + (simd_lane_id / 2) % 4;\n    sn = (qid & 2) * 2 + (simd_lane_id % 2) * 2;\n\n    // Determine thread and simdgroup offset\n    As_offset =\n        transpose_a ? ((sn)*lda_tgp + (tm + sm)) : ((sn) + (tm + sm) * lda_tgp);\n    Bs_offset =\n        transpose_b ? ((tn + sn) * ldb_tgp + (sm)) : ((sm)*ldb_tgp + (tn + sn));\n  }\n\n  /* (BM, BK) X (BK, BN) multiply accumulate function */\n  METAL_FUNC void mma(const threadgroup T* As, const threadgroup T* Bs) {\n    // Adjust for simdgroup and thread location\n    As += As_offset;\n    Bs += Bs_offset;\n\n    // Iterate over BK in blocks of 8\n    STEEL_PRAGMA_UNROLL\n    for (short kk = 0; kk < BK; kk += 8) {\n      simdgroup_barrier(mem_flags::mem_none);\n\n      // Load elements from threadgroup A as simdgroup matrices\n      STEEL_PRAGMA_UNROLL\n      for (short i = 0; i < TM; i++) {\n        Asimd[i].thread_elements()[0] =\n            static_cast<AccumType>(As[i * simd_stride_a + 0]);\n        Asimd[i].thread_elements()[1] =\n            static_cast<AccumType>(As[i * simd_stride_a + jump_a]);\n      }\n\n      simdgroup_barrier(mem_flags::mem_none);\n\n      // Load elements from threadgroup B as simdgroup matrices\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < TN; j++) {\n        Bsimd[j].thread_elements()[0] =\n            static_cast<AccumType>(Bs[j * simd_stride_b + 0]);\n        Bsimd[j].thread_elements()[1] =\n            static_cast<AccumType>(Bs[j * simd_stride_b + jump_b]);\n      }\n\n      simdgroup_barrier(mem_flags::mem_none);\n\n      // Multiply and accumulate into result simdgroup matrices\n      STEEL_PRAGMA_UNROLL\n      for (short i = 0; i < TM; i++) {\n        STEEL_PRAGMA_UNROLL\n        for (short j = 0; j < TN; j++) {\n          short j_serp = (i % 2) ? (TN - 1 - j) : j;\n\n          simdgroup_multiply_accumulate(\n              results[i * TN + j_serp],\n              Asimd[i],\n              Bsimd[j_serp],\n              results[i * TN + j_serp]);\n        }\n      }\n\n      // Progress to next simdgroup tile\n      As += tile_stride_a;\n      Bs += tile_stride_b;\n    }\n  }\n\n  /* Store results from simdgroup_matrix results into device memory */\n  METAL_FUNC void store_result(device U* D, const int ldd) const {\n    // Adjust for simdgroup and thread location\n    D += (sm + tm) * ldd + tn + sn;\n\n    // Loop over all simdgroup tiles\n    STEEL_PRAGMA_UNROLL\n    for (short i = 0; i < TM; i++) {\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < TN; j++) {\n        // Get accumulated result and associated offset in C\n        thread const auto& accum = results[i * TN + j].thread_elements();\n        int offset = (i * TM_stride) * ldd + (j * TN_stride);\n\n        // Apply epilogue\n        U outs[2] = {Epilogue::apply(accum[0]), Epilogue::apply(accum[1])};\n\n        // Write out D\n        D[offset] = outs[0];\n        D[offset + 1] = outs[1];\n      }\n    }\n  }\n\n  METAL_FUNC void\n  store_result_safe(device U* D, const int ldd, short2 dst_tile_dims) const {\n    // Adjust for simdgroup and thread location\n    D += (sm + tm) * ldd + (tn + sn);\n    dst_tile_dims -= short2(tn + sn, sm + tm);\n\n    if (dst_tile_dims.x <= 0 || dst_tile_dims.y <= 0)\n      return;\n\n    STEEL_PRAGMA_UNROLL\n    for (int i = 0; i < TM; i++) {\n      if (i * TM_stride < dst_tile_dims.y) {\n        STEEL_PRAGMA_UNROLL\n        for (int j = 0; j < TN; j++) {\n          // Get accumulated result and associated offset in C\n          thread const auto& accum = results[i * TN + j].thread_elements();\n          int offset = (i * TM_stride) * ldd + (j * TN_stride);\n\n          // Apply epilogue and output C\n          if (j * TN_stride < dst_tile_dims.x) {\n            D[offset] = Epilogue::apply(accum[0]);\n          }\n\n          if (j * TN_stride + 1 < dst_tile_dims.x) {\n            D[offset + 1] = Epilogue::apply(accum[1]);\n          }\n        }\n      }\n    }\n  }\n\n  /* Apply epilogue */\n  template <typename UnaryEpilogue>\n  METAL_FUNC void apply_epilogue(thread const UnaryEpilogue& epilogue_op) {\n    // Loop over all simdgroup tiles\n    STEEL_PRAGMA_UNROLL\n    for (short i = 0; i < TM; i++) {\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < TN; j++) {\n        // Get accumulated result and associated offset in C\n        thread auto& accum = results[i * TN + j].thread_elements();\n\n        // Apply epilogue\n        accum[0] = epilogue_op.apply(accum[0]);\n        accum[1] = epilogue_op.apply(accum[1]);\n      }\n    }\n  }\n\n  /* Apply epilogue */\n  template <typename BinaryEpilogue>\n  METAL_FUNC void apply_epilogue(\n      const device U* C,\n      const int ldc,\n      const int fdc,\n      thread const BinaryEpilogue& epilogue_op) {\n    // Adjust for simdgroup and thread location\n    C += (sm + tm) * ldc + (tn + sn) * fdc;\n\n    // Loop over all simdgroup tiles\n    STEEL_PRAGMA_UNROLL\n    for (short i = 0; i < TM; i++) {\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < TN; j++) {\n        // Get accumulated result and associated offset in C\n        thread auto& accum = results[i * TN + j].thread_elements();\n        int offset_c = (i * TM_stride) * ldc + (j * TN_stride) * fdc;\n\n        // Apply epilogue\n        accum[0] = epilogue_op.apply(accum[0], C[offset_c]);\n        accum[1] = epilogue_op.apply(accum[1], C[offset_c + fdc]);\n      }\n    }\n  }\n\n  /* Apply epilogue */\n  template <typename BinaryEpilogue>\n  METAL_FUNC void apply_epilogue_safe(\n      const device U* C,\n      const int ldc,\n      const int fdc,\n      short2 dst_tile_dims,\n      thread const BinaryEpilogue& epilogue_op) {\n    // Adjust for simdgroup and thread location\n    C += (sm + tm) * ldc + (tn + sn) * fdc;\n    dst_tile_dims -= short2(tn + sn, sm + tm);\n\n    if (dst_tile_dims.x <= 0 || dst_tile_dims.y <= 0)\n      return;\n\n    // Loop over all simdgroup tiles\n    STEEL_PRAGMA_UNROLL\n    for (short i = 0; i < TM; i++) {\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < TN; j++) {\n        // Get accumulated result and associated offset in C\n        thread auto& accum = results[i * TN + j].thread_elements();\n        int offset_c = (i * TM_stride) * ldc + (j * TN_stride) * fdc;\n\n        // Read C\n        U c_elems[2] = {0};\n\n        if ((j * TN_stride + 1) < dst_tile_dims.x) {\n          c_elems[0] = C[offset_c];\n          c_elems[1] = C[offset_c + fdc];\n        } else if ((j * TN_stride) < dst_tile_dims.x) {\n          c_elems[0] = C[offset_c];\n        }\n\n        // Apply epilogue\n        accum[0] = epilogue_op.apply(accum[0], c_elems[0]);\n        accum[1] = epilogue_op.apply(accum[1], c_elems[1]);\n      }\n    }\n  }\n\n  /* Store results from simdgroup_matrix results into device memory */\n  METAL_FUNC void store_result(\n      device U* D,\n      const int ldd,\n      const device U* C,\n      const int ldc,\n      const int fdc,\n      thread const Epilogue& epilogue_op) const {\n    // Adjust for simdgroup and thread location\n    C += (sm + tm) * ldc + (tn + sn) * fdc;\n    D += (sm + tm) * ldd + tn + sn;\n\n    // Loop over all simdgroup tiles\n    STEEL_PRAGMA_UNROLL\n    for (short i = 0; i < TM; i++) {\n      STEEL_PRAGMA_UNROLL\n      for (short j = 0; j < TN; j++) {\n        // Get accumulated result and associated offset in C\n        thread const auto& accum = results[i * TN + j].thread_elements();\n        int offset_c = (i * TM_stride) * ldc + (j * TN_stride) * fdc;\n        int offset_d = (i * TM_stride) * ldd + (j * TN_stride);\n\n        // Apply epilogue\n        U outs[2] = {\n            epilogue_op.apply(accum[0], C[offset_c]),\n            epilogue_op.apply(accum[1], C[offset_c + fdc])};\n\n        // Write out D\n        D[offset_d] = outs[0];\n        D[offset_d + 1] = outs[1];\n      }\n    }\n  }\n\n  METAL_FUNC void store_result_safe(\n      device U* D,\n      const int ldd,\n      const device U* C,\n      const int ldc,\n      const int fdc,\n      short2 dst_tile_dims,\n      thread const Epilogue& epilogue_op) const {\n    // Adjust for simdgroup and thread location\n    C += (sm + tm) * ldc + (tn + sn) * fdc;\n    D += (sm + tm) * ldd + tn + sn;\n    dst_tile_dims -= short2(tn + sn, sm + tm);\n\n    if (dst_tile_dims.x <= 0 || dst_tile_dims.y <= 0)\n      return;\n\n    STEEL_PRAGMA_UNROLL\n    for (int i = 0; i < TM; i++) {\n      if (i * TM_stride < dst_tile_dims.y) {\n        STEEL_PRAGMA_UNROLL\n        for (int j = 0; j < TN; j++) {\n          // Get accumulated result and associated offset in C\n          thread const auto& accum = results[i * TN + j].thread_elements();\n          int offset_c = (i * TM_stride) * ldc + (j * TN_stride) * fdc;\n          int offset_d = (i * TM_stride) * ldd + (j * TN_stride);\n\n          // Apply epilogue and output C\n          if (j * TN_stride < dst_tile_dims.x) {\n            D[offset_d] = epilogue_op.apply(accum[0], C[offset_c]);\n          }\n\n          if (j * TN_stride + 1 < dst_tile_dims.x) {\n            D[offset_d + 1] = epilogue_op.apply(accum[1], C[offset_c + fdc]);\n          }\n        }\n      }\n    }\n  }\n};\n\n// https://github.com/ml-explore/mlx/blob/02efb310cac667bc547d1b96f21596c221f84fe7/mlx/backend/metal/kernels/steel/gemm/gemm.h#L1\n///////////////////////////////////////////////////////////////////////////////\n// GEMM kernel class\n///////////////////////////////////////////////////////////////////////////////\n\ntemplate <bool M_aligned, bool N_aligned, bool K_aligned>\nstruct LoopAlignment {};\n\ntemplate <\n    typename T,\n    typename U,\n    int BM,\n    int BN,\n    int BK,\n    int WM,\n    int WN,\n    bool transpose_a,\n    bool transpose_b,\n    bool MN_aligned,\n    bool K_aligned,\n    typename AccumType = typename AccumHelper<T>::accum_type,\n    typename Epilogue = TransformNone<U, AccumType>>\nstruct GEMMKernel {\n  STEEL_CONST short tgp_padding_a = 16 / sizeof(T);\n  STEEL_CONST short tgp_padding_b = 16 / sizeof(T);\n  STEEL_CONST short tgp_mem_size_a =\n      transpose_a ? BK * (BM + tgp_padding_a) : BM * (BK + tgp_padding_a);\n  STEEL_CONST short tgp_mem_size_b =\n      transpose_b ? BN * (BK + tgp_padding_b) : BK * (BN + tgp_padding_b);\n  STEEL_CONST short tgp_mem_size = tgp_mem_size_a + tgp_mem_size_b;\n\n  STEEL_CONST short tgp_size = WM * WN * 32;\n\n  using loader_a_t = BlockLoader<\n      T,\n      transpose_a ? BK : BM,\n      transpose_a ? BM : BK,\n      transpose_a ? BM + tgp_padding_a : BK + tgp_padding_a,\n      !transpose_a,\n      tgp_size>;\n  using loader_b_t = BlockLoader<\n      T,\n      transpose_b ? BN : BK,\n      transpose_b ? BK : BN,\n      transpose_b ? BK + tgp_padding_b : BN + tgp_padding_b,\n      transpose_b,\n      tgp_size>;\n  using mma_t = BlockMMA<\n      T,\n      U,\n      BM,\n      BN,\n      BK,\n      WM,\n      WN,\n      transpose_a,\n      transpose_b,\n      transpose_a ? BM + tgp_padding_a : BK + tgp_padding_a,\n      transpose_b ? BK + tgp_padding_b : BN + tgp_padding_b,\n      AccumType,\n      Epilogue>;\n\n  /* Main kernel function */\n  template <bool M_aligned, bool N_aligned, bool K_aligned_>\n  static METAL_FUNC void gemm_loop(\n      threadgroup T* As [[threadgroup(0)]],\n      threadgroup T* Bs [[threadgroup(1)]],\n      const int gemm_k_iterations,\n      thread loader_a_t& loader_a,\n      thread loader_b_t& loader_b,\n      thread mma_t& mma_op,\n      thread const short& tgp_bm,\n      thread const short& tgp_bn,\n      thread const short& lbk,\n      LoopAlignment<M_aligned, N_aligned, K_aligned_> l = {}) {\n    // Appease the compiler\n    (void)l;\n\n    short2 tile_dims_A = transpose_a ? short2(tgp_bm, BK) : short2(BK, tgp_bm);\n\n    short2 tile_dims_B = transpose_b ? short2(BK, tgp_bn) : short2(tgp_bn, BK);\n\n    for (int k = 0; k < gemm_k_iterations; k++) {\n      threadgroup_barrier(mem_flags::mem_threadgroup);\n      // Load elements into threadgroup\n      if (M_aligned) {\n        loader_a.load_unsafe();\n      } else {\n        loader_a.load_safe(tile_dims_A);\n      }\n\n      if (N_aligned) {\n        loader_b.load_unsafe();\n      } else {\n        loader_b.load_safe(tile_dims_B);\n      }\n\n      threadgroup_barrier(mem_flags::mem_threadgroup);\n\n      // Multiply and accumulate threadgroup elements\n      mma_op.mma(As, Bs);\n\n      // Prepare for next iteration\n      loader_a.next();\n      loader_b.next();\n    }\n\n    if (!K_aligned_) {\n      threadgroup_barrier(mem_flags::mem_threadgroup);\n\n      short2 tile_dims_A_last =\n          transpose_a ? short2(tgp_bm, lbk) : short2(lbk, tgp_bm);\n      short2 tile_dims_B_last =\n          transpose_b ? short2(lbk, tgp_bn) : short2(tgp_bn, lbk);\n\n      loader_a.load_safe(tile_dims_A_last);\n      loader_b.load_safe(tile_dims_B_last);\n\n      threadgroup_barrier(mem_flags::mem_threadgroup);\n\n      mma_op.mma(As, Bs);\n    }\n  }\n\n  /* Main kernel function */\n  static METAL_FUNC void run(\n      const device T* A [[buffer(0)]],\n      const device T* B [[buffer(1)]],\n      device U* D [[buffer(2)]],\n      const constant GEMMParams* params [[buffer(3)]],\n      threadgroup T* As [[threadgroup(0)]],\n      threadgroup T* Bs [[threadgroup(1)]],\n      uint simd_lane_id [[thread_index_in_simdgroup]],\n      uint simd_group_id [[simdgroup_index_in_threadgroup]],\n      uint3 tid [[threadgroup_position_in_grid]],\n      uint3 lid [[thread_position_in_threadgroup]]) {\n    // Pacifying compiler\n    (void)lid;\n\n    const int tid_y = ((tid.y) << params->swizzle_log) +\n        ((tid.x) & ((1 << params->swizzle_log) - 1));\n    const int tid_x = (tid.x) >> params->swizzle_log;\n\n    if (params->tiles_n <= tid_x || params->tiles_m <= tid_y) {\n      return;\n    }\n\n    threadgroup_barrier(mem_flags::mem_none);\n\n    // Find block in A, B, C\n    const int c_row = tid_y * BM;\n    const int c_col = tid_x * BN;\n    const size_t c_row_long = size_t(c_row);\n    const size_t c_col_long = size_t(c_col);\n\n    A += transpose_a ? c_row_long : c_row_long * params->lda;\n    B += transpose_b ? c_col_long * params->ldb : c_col_long;\n    D += c_row_long * params->ldd + c_col_long;\n\n    // Prepare threadgroup loading operations\n    thread loader_a_t loader_a(A, params->lda, As, simd_group_id, simd_lane_id);\n    thread loader_b_t loader_b(B, params->ldb, Bs, simd_group_id, simd_lane_id);\n\n    // Prepare threadgroup mma operation\n    thread mma_t mma_op(simd_group_id, simd_lane_id);\n\n    int gemm_k_iterations = params->gemm_k_iterations_aligned;\n\n    ///////////////////////////////////////////////////////////////////////////////\n    // MNK aligned loop\n    if (MN_aligned) {\n      for (int k = 0; k < gemm_k_iterations; k++) {\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n        // Load elements into threadgroup\n        loader_a.load_unsafe();\n        loader_b.load_unsafe();\n\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n\n        // Multiply and accumulate threadgroup elements\n        mma_op.mma(As, Bs);\n\n        // Prepare for next iteration\n        loader_a.next();\n        loader_b.next();\n      }\n\n      threadgroup_barrier(mem_flags::mem_none);\n\n      // Loop tail\n      if (!K_aligned) {\n        int lbk = params->K - params->gemm_k_iterations_aligned * BK;\n        short2 tile_dims_A = transpose_a ? short2(BM, lbk) : short2(lbk, BM);\n        short2 tile_dims_B = transpose_b ? short2(lbk, BN) : short2(BN, lbk);\n\n        loader_a.load_safe(tile_dims_A);\n        loader_b.load_safe(tile_dims_B);\n\n        threadgroup_barrier(mem_flags::mem_threadgroup);\n\n        mma_op.mma(As, Bs);\n      }\n\n      // Store results to device memory\n      mma_op.store_result(D, params->ldd);\n      return;\n\n    }\n    ///////////////////////////////////////////////////////////////////////////////\n    // MN unaligned loop\n    else { // Loop over K - unaligned case\n      short tgp_bm = min(BM, params->M - c_row);\n      short tgp_bn = min(BN, params->N - c_col);\n      short leftover_bk = params->K - params->gemm_k_iterations_aligned * BK;\n\n      if (tgp_bm == BM && tgp_bn == BN) {\n        gemm_loop<true, true, K_aligned>(\n            As,\n            Bs,\n            gemm_k_iterations,\n            loader_a,\n            loader_b,\n            mma_op,\n            tgp_bm,\n            tgp_bn,\n            leftover_bk);\n\n        mma_op.store_result(D, params->ldd);\n        return;\n\n      } else if (tgp_bn == BN) {\n        gemm_loop<false, true, K_aligned>(\n            As,\n            Bs,\n            gemm_k_iterations,\n            loader_a,\n            loader_b,\n            mma_op,\n            tgp_bm,\n            tgp_bn,\n            leftover_bk);\n\n        mma_op.store_result_safe(D, params->ldd, short2(tgp_bn, tgp_bm));\n        return;\n\n      } else if (tgp_bm == BM) {\n        gemm_loop<true, false, K_aligned>(\n            As,\n            Bs,\n            gemm_k_iterations,\n            loader_a,\n            loader_b,\n            mma_op,\n            tgp_bm,\n            tgp_bn,\n            leftover_bk);\n\n        mma_op.store_result_safe(D, params->ldd, short2(tgp_bn, tgp_bm));\n        return;\n\n      } else {\n        gemm_loop<false, false, K_aligned>(\n            As,\n            Bs,\n            gemm_k_iterations,\n            loader_a,\n            loader_b,\n            mma_op,\n            tgp_bm,\n            tgp_bn,\n            leftover_bk);\n\n        mma_op.store_result_safe(D, params->ldd, short2(tgp_bn, tgp_bm));\n        return;\n      }\n    }\n  }\n};\n\n// utils.h\n///////////////////////////////////////////////////////////////////////////////\n// Single Array with generic dims\n\ntemplate <typename stride_t>\nMETAL_FUNC stride_t elem_to_loc(\n    uint elem,\n    device const int* shape,\n    device const stride_t* strides,\n    int ndim) {\n  stride_t loc = 0;\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    loc += (elem % shape[i]) * strides[i];\n    elem /= shape[i];\n  }\n  return loc;\n}\n\ntemplate <typename stride_t>\nMETAL_FUNC stride_t elem_to_loc(\n    uint elem,\n    constant const int* shape,\n    constant const stride_t* strides,\n    int ndim) {\n  stride_t loc = 0;\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    loc += (elem % shape[i]) * strides[i];\n    elem /= shape[i];\n  }\n  return loc;\n}\n\ntemplate <typename stride_t>\nMETAL_FUNC stride_t elem_to_loc(\n    stride_t elem,\n    device const int* shape,\n    device const stride_t* strides,\n    int ndim) {\n  stride_t loc = 0;\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    loc += (elem % shape[i]) * strides[i];\n    elem /= shape[i];\n  }\n  return loc;\n}\n\ntemplate <typename stride_t>\nMETAL_FUNC stride_t elem_to_loc(\n    stride_t elem,\n    constant const int* shape,\n    constant const stride_t* strides,\n    int ndim) {\n  stride_t loc = 0;\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    loc += (elem % shape[i]) * strides[i];\n    elem /= shape[i];\n  }\n  return loc;\n}\n\n// Non templated version to handle arbitrary dims\ntemplate <typename stride_t>\nMETAL_FUNC stride_t elem_to_loc(\n    uint3 elem,\n    constant const int* shape,\n    constant const stride_t* strides,\n    int ndim) {\n  stride_t loc = elem.x * strides[ndim - 1] + elem.y * strides[ndim - 2];\n  for (int d = ndim - 3; d >= 0; --d) {\n    loc += (elem.z % shape[d]) * strides[d];\n    elem.z /= shape[d];\n  }\n  return loc;\n}\n\n\nMETAL_FUNC ulong2 elem_to_loc_broadcast(\n    uint elem,\n    constant const int* shape,\n    constant const size_t* a_strides,\n    constant const size_t* b_strides,\n    int ndim) {\n  ulong loc_a{0};\n  ulong loc_b{0};\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    int pos_in_dim = (elem % shape[i]);\n    elem /= shape[i];\n    loc_a += pos_in_dim * a_strides[i];\n    loc_b += pos_in_dim * b_strides[i];\n  }\n  return ulong2(loc_a, loc_b);\n}\n\nMETAL_FUNC ulong3 elem_to_loc_broadcast(\n    uint elem,\n    constant const int* shape,\n    constant const size_t* a_strides,\n    constant const size_t* b_strides,\n    constant const size_t* c_strides,\n    int ndim) {\n  ulong loc_a{0};\n  ulong loc_b{0};\n  ulong loc_c{0};\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    int pos_in_dim = (elem % shape[i]);\n    elem /= shape[i];\n    loc_a += pos_in_dim * a_strides[i];\n    loc_b += pos_in_dim * b_strides[i];\n    loc_c += pos_in_dim * c_strides[i];\n  }\n  return ulong3(loc_a, loc_b, loc_c);\n}\n\n\n// https://github.com/ml-explore/mlx/blob/02efb310cac667bc547d1b96f21596c221f84fe7/mlx/backend/metal/kernels/steel/gemm/kernels/steel_gemm_fused.h#L1\n///////////////////////////////////////////////////////////////////////////////\n// GEMM kernels\n///////////////////////////////////////////////////////////////////////////////\n\nconstant bool has_batch [[function_constant(10)]];\n\nconstant bool use_out_source [[function_constant(100)]];\nconstant bool do_axpby [[function_constant(110)]];\n\nconstant bool align_M [[function_constant(200)]];\nconstant bool align_N [[function_constant(201)]];\nconstant bool align_K [[function_constant(202)]];\n\nconstant bool do_gather [[function_constant(300)]];\n\nconstant bool debug_mode [[function_constant(400)]];\n\nconstant bool gather_bias = do_gather && use_out_source;\n\n// clang-format off\ntemplate <\n    typename T,\n    int BM,\n    int BN,\n    int BK,\n    int WM,\n    int WN,\n    bool transpose_a,\n    bool transpose_b,\n    typename AccumType = float>\n[[kernel, max_total_threads_per_threadgroup(WM* WN * 32)]] void gemm(\n    const device T* A [[buffer(0)]],\n    const device T* B [[buffer(1)]],\n    const device T* C [[buffer(2), function_constant(use_out_source)]],\n    device T* D [[buffer(3)]],\n    const constant GEMMParams* params [[buffer(4)]],\n    const constant GEMMAddMMParams* addmm_params [[buffer(5), function_constant(use_out_source)]],\n    const constant int* batch_shape [[buffer(6)]],\n    const constant size_t* batch_strides [[buffer(7)]],\n    const constant uint32_t* lhs_indices [[buffer(10), function_constant(do_gather)]],\n    const constant uint32_t* rhs_indices [[buffer(11), function_constant(do_gather)]],\n    const constant uint32_t* C_indices [[buffer(12), function_constant(gather_bias)]],\n    const constant int* operand_shape [[buffer(13), function_constant(do_gather)]],\n    const constant size_t* operand_strides [[buffer(14), function_constant(do_gather)]],\n    const constant packed_int3& operand_batch_ndim [[buffer(15), function_constant(do_gather)]],\n    device GEMMDebug * debug [[buffer(16), function_constant(debug_mode)]],\n    uint3 tpig[[thread_position_in_grid]],\n    uint num_simd_group [[simdgroups_per_threadgroup]],\n    uint num_threads_in_simd [[threads_per_simdgroup]],\n    uint simd_lane_id [[thread_index_in_simdgroup]],\n    uint simd_group_id [[simdgroup_index_in_threadgroup]],\n    uint3 tid [[threadgroup_position_in_grid]],\n    uint3 lid [[thread_position_in_threadgroup]]) { // clang-format on\n  // Pacifying compiler\n  (void)lid;\n\n  using gemm_kernel = GEMMKernel<\n      T,\n      T,\n      BM,\n      BN,\n      BK,\n      WM,\n      WN,\n      transpose_a,\n      transpose_b,\n      true,\n      true,\n      AccumType>;\n\n  using loader_a_t = typename gemm_kernel::loader_a_t;\n  using loader_b_t = typename gemm_kernel::loader_b_t;\n  using mma_t = typename gemm_kernel::mma_t;\n\n  // Find block\n  const int tid_y = ((tid.y) << params->swizzle_log) +\n      ((tid.x) & ((1 << params->swizzle_log) - 1));\n  const int tid_x = (tid.x) >> params->swizzle_log;\n\n  // Exit early if out of bounds\n  if (params->tiles_n <= tid_x || params->tiles_m <= tid_y) {\n    return;\n  }\n\n  // Adjust for batch\n\n  // Handle gather\n  if (do_gather) {\n    // Read indices\n    uint32_t indx_A, indx_B, indx_C;\n\n    if (has_batch) {\n      const constant size_t* indx_A_bstrides = batch_strides;\n      const constant size_t* indx_B_bstrides =\n          batch_strides + params->batch_ndim;\n\n      ulong2 indx_offsets = elem_to_loc_broadcast(\n          tid.z,\n          batch_shape,\n          indx_A_bstrides,\n          indx_B_bstrides,\n          params->batch_ndim);\n      indx_A = lhs_indices[indx_offsets.x];\n      indx_B = rhs_indices[indx_offsets.y];\n\n      if (use_out_source) {\n        const constant size_t* indx_C_bstrides =\n            indx_B_bstrides + params->batch_ndim;\n        auto indx_offset_C = elem_to_loc(\n            tid.z, batch_shape, indx_C_bstrides, params->batch_ndim);\n        indx_C = C_indices[indx_offset_C];\n      }\n    } else {\n      indx_A = lhs_indices[params->batch_stride_a * tid.z];\n      indx_B = rhs_indices[params->batch_stride_b * tid.z];\n\n      if (use_out_source) {\n        indx_C = C_indices[addmm_params->batch_stride_c * tid.z];\n      }\n    }\n\n    // Translate indices to offsets\n    int batch_ndim_A = operand_batch_ndim.x;\n    const constant int* batch_shape_A = operand_shape;\n    const constant size_t* batch_strides_A = operand_strides;\n    A += elem_to_loc(indx_A, batch_shape_A, batch_strides_A, batch_ndim_A);\n\n    int batch_ndim_B = operand_batch_ndim.y;\n    const constant int* batch_shape_B = batch_shape_A + batch_ndim_A;\n    const constant size_t* batch_strides_B = batch_strides_A + batch_ndim_A;\n    B += elem_to_loc(indx_B, batch_shape_B, batch_strides_B, batch_ndim_B);\n\n    if (use_out_source) {\n      int batch_ndim_C = operand_batch_ndim.z;\n      const constant int* batch_shape_C = batch_shape_B + batch_ndim_B;\n      const constant size_t* batch_strides_C = batch_strides_B + batch_ndim_B;\n      C += elem_to_loc(indx_C, batch_shape_C, batch_strides_C, batch_ndim_C);\n    }\n\n  }\n\n  // Handle regular batch\n  else {\n    if (has_batch) {\n      const constant size_t* A_bstrides = batch_strides;\n      const constant size_t* B_bstrides = batch_strides + params->batch_ndim;\n\n      ulong2 batch_offsets = elem_to_loc_broadcast(\n          tid.z, batch_shape, A_bstrides, B_bstrides, params->batch_ndim);\n\n      A += batch_offsets.x;\n      B += batch_offsets.y;\n\n      if (use_out_source) {\n        const constant size_t* C_bstrides = B_bstrides + params->batch_ndim;\n        C += elem_to_loc(tid.z, batch_shape, C_bstrides, params->batch_ndim);\n      }\n    } else {\n      A += params->batch_stride_a * tid.z;\n      B += params->batch_stride_b * tid.z;\n\n      if (use_out_source) {\n        C += addmm_params->batch_stride_c * tid.z;\n      }\n    }\n  }\n\n  D += params->batch_stride_d * tid.z;\n\n  // Prepare threadgroup memory\n  threadgroup T As[gemm_kernel::tgp_mem_size_a];\n  threadgroup T Bs[gemm_kernel::tgp_mem_size_b];\n\n  threadgroup_barrier(mem_flags::mem_none);\n\n  // Find block in A, B, C\n  const int c_row = tid_y * BM;\n  const int c_col = tid_x * BN;\n  const size_t c_row_long = size_t(c_row);\n  const size_t c_col_long = size_t(c_col);\n\n  A += transpose_a ? c_row_long : c_row_long * params->lda;\n  B += transpose_b ? c_col_long * params->ldb : c_col_long;\n  D += c_row_long * params->ldd + c_col_long;\n\n  if (use_out_source) {\n    C += c_row_long * addmm_params->ldc + c_col_long * addmm_params->fdc;\n  }\n\n  // Prepare threadgroup mma operation\n  thread mma_t mma_op(simd_group_id, simd_lane_id);\n\n  // Collect debug information\n  if(debug_mode && tpig.x == 0 && tpig.y == 0 && tpig.z == 0) {\n     debug -> WM = WM;\n     debug -> WN = WN;\n     debug -> TM_stride = mma_op.TM_stride;\n     debug -> TN_stride = mma_op.TN_stride;\n     debug -> TM = mma_op.TM;\n     debug -> TN = mma_op.TN;\n     debug -> num_threads_in_simd = num_threads_in_simd;\n     debug -> num_simd_group = num_simd_group;\n  }\n\n  // Prepare threadgroup loading operations\n  thread loader_a_t loader_a(A, params->lda, As, simd_group_id, simd_lane_id);\n  thread loader_b_t loader_b(B, params->ldb, Bs, simd_group_id, simd_lane_id);\n\n  // Prepare threadgroup bounds\n  const short tgp_bm = align_M ? BM : short(min(BM, params->M - c_row));\n  const short tgp_bn = align_N ? BN : short(min(BN, params->N - c_col));\n\n  // Prepare iterations\n  int gemm_k_iterations = params->gemm_k_iterations_aligned;\n\n  // Do unaligned K iterations first\n  if (!align_K) {\n    const int k_last = params->gemm_k_iterations_aligned * BK;\n    const int k_remain = params->K - k_last;\n    const size_t k_jump_a =\n        transpose_a ? params->lda * size_t(k_last) : size_t(k_last);\n    const size_t k_jump_b =\n        transpose_b ? size_t(k_last) : params->ldb * size_t(k_last);\n\n    // Move loader source ahead to end\n    loader_a.src += k_jump_a;\n    loader_b.src += k_jump_b;\n\n    // Load tile\n    const short2 tile_dims_A =\n        transpose_a ? short2(tgp_bm, k_remain) : short2(k_remain, tgp_bm);\n    const short2 tile_dims_B =\n        transpose_b ? short2(k_remain, tgp_bn) : short2(tgp_bn, k_remain);\n\n    loader_a.load_safe(tile_dims_A);\n    loader_b.load_safe(tile_dims_B);\n\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n\n    // Do matmul\n    mma_op.mma(As, Bs);\n\n    // Reset source back to start\n    loader_a.src -= k_jump_a;\n    loader_b.src -= k_jump_b;\n  }\n\n  const TransformAdd<AccumType, AccumType> epilogue_op_add(\n      addmm_params->alpha, addmm_params->beta);\n  const TransformAxpby<AccumType, AccumType> epilogue_op_axpby(\n      addmm_params->alpha, addmm_params->beta);\n\n  ///////////////////////////////////////////////////////////////////////////////\n  // MNK aligned loop\n  if (align_M && align_N) {\n    // Do gemm\n    for (int k = 0; k < gemm_k_iterations; k++) {\n      threadgroup_barrier(mem_flags::mem_threadgroup);\n      // Load elements into threadgroup\n      loader_a.load_unsafe();\n      loader_b.load_unsafe();\n\n      threadgroup_barrier(mem_flags::mem_threadgroup);\n\n      // Multiply and accumulate threadgroup elements\n      mma_op.mma(As, Bs);\n\n      // Prepare for next iteration\n      loader_a.next();\n      loader_b.next();\n    }\n\n    threadgroup_barrier(mem_flags::mem_none);\n\n    // Do epilogue\n    if (use_out_source) {\n      if (do_axpby) {\n        mma_op.apply_epilogue(\n            C, addmm_params->ldc, addmm_params->fdc, epilogue_op_axpby);\n      } else {\n        mma_op.apply_epilogue(\n            C, addmm_params->ldc, addmm_params->fdc, epilogue_op_add);\n      }\n    }\n\n    // Store results to device memory\n    return mma_op.store_result(D, params->ldd);\n\n  }\n  ///////////////////////////////////////////////////////////////////////////////\n  // MN unaligned loop\n  else { // Loop over K - unaligned case\n    const int leftover_bk = 0;\n\n    if ((align_M || tgp_bm == BM) && (align_N || tgp_bn == BN)) {\n      // Do gemm\n      gemm_kernel::gemm_loop(\n          As,\n          Bs,\n          gemm_k_iterations,\n          loader_a,\n          loader_b,\n          mma_op,\n          tgp_bm,\n          tgp_bn,\n          leftover_bk,\n          LoopAlignment<true, true, true>{});\n\n      // Do epilogue\n      if (use_out_source) {\n        if (do_axpby) {\n          mma_op.apply_epilogue(\n              C, addmm_params->ldc, addmm_params->fdc, epilogue_op_axpby);\n        } else {\n          mma_op.apply_epilogue(\n              C, addmm_params->ldc, addmm_params->fdc, epilogue_op_add);\n        }\n      }\n\n      // Store results to device memory\n      return mma_op.store_result(D, params->ldd);\n\n    } else if (align_N || tgp_bn == BN) {\n      gemm_kernel::gemm_loop(\n          As,\n          Bs,\n          gemm_k_iterations,\n          loader_a,\n          loader_b,\n          mma_op,\n          tgp_bm,\n          tgp_bn,\n          leftover_bk,\n          LoopAlignment<false, true, true>{});\n\n      // Do epilogue\n      if (use_out_source) {\n        if (do_axpby) {\n          mma_op.apply_epilogue_safe(\n              C,\n              addmm_params->ldc,\n              addmm_params->fdc,\n              short2(tgp_bn, tgp_bm),\n              epilogue_op_axpby);\n        } else {\n          mma_op.apply_epilogue_safe(\n              C,\n              addmm_params->ldc,\n              addmm_params->fdc,\n              short2(tgp_bn, tgp_bm),\n              epilogue_op_add);\n        }\n      }\n\n      // Store results to device memory\n      return mma_op.store_result_safe(D, params->ldd, short2(tgp_bn, tgp_bm));\n\n    } else if (align_M || tgp_bm == BM) {\n      gemm_kernel::gemm_loop(\n          As,\n          Bs,\n          gemm_k_iterations,\n          loader_a,\n          loader_b,\n          mma_op,\n          tgp_bm,\n          tgp_bn,\n          leftover_bk,\n          LoopAlignment<true, false, true>{});\n\n      // Do epilogue\n      if (use_out_source) {\n        if (do_axpby) {\n          mma_op.apply_epilogue_safe(\n              C,\n              addmm_params->ldc,\n              addmm_params->fdc,\n              short2(tgp_bn, tgp_bm),\n              epilogue_op_axpby);\n        } else {\n          mma_op.apply_epilogue_safe(\n              C,\n              addmm_params->ldc,\n              addmm_params->fdc,\n              short2(tgp_bn, tgp_bm),\n              epilogue_op_add);\n        }\n      }\n\n      // Store results to device memory\n      return mma_op.store_result_safe(D, params->ldd, short2(tgp_bn, tgp_bm));\n\n    } else {\n      gemm_kernel::gemm_loop(\n          As,\n          Bs,\n          gemm_k_iterations,\n          loader_a,\n          loader_b,\n          mma_op,\n          tgp_bm,\n          tgp_bn,\n          leftover_bk,\n          LoopAlignment<false, false, true>{});\n\n      // Do epilogue\n      if (use_out_source) {\n        if (do_axpby) {\n          mma_op.apply_epilogue_safe(\n              C,\n              addmm_params->ldc,\n              addmm_params->fdc,\n              short2(tgp_bn, tgp_bm),\n              epilogue_op_axpby);\n        } else {\n          mma_op.apply_epilogue_safe(\n              C,\n              addmm_params->ldc,\n              addmm_params->fdc,\n              short2(tgp_bn, tgp_bm),\n              epilogue_op_add);\n        }\n      }\n\n      // Store results to device memory\n      return mma_op.store_result_safe(D, params->ldd, short2(tgp_bn, tgp_bm));\n    }\n  }\n}\n\n#define instantiate_gemm(tname, trans_a, trans_b, iname, itype, oname, otype, acctype, bm, bn, bk, wm, wn) \\\n  template [[host_name(\"gemm_\" #tname \"_\"  #iname \"_\" #oname \"_\" #bm \"_\" #bn \"_\" #bk \"_\" #wm \"_\" #wn)]] \\\n  [[kernel]] void gemm<itype, bm, bn, bk, wm, wn, trans_a, trans_b, acctype>( \\\n      const device itype *A [[buffer(0)]], \\\n      const device itype *B [[buffer(1)]], \\\n      const device itype *C [[buffer(2), function_constant(use_out_source)]], \\\n      device itype *D [[buffer(3)]], \\\n      const constant GEMMParams* params [[buffer(4)]], \\\n      const constant GEMMAddMMParams* addmm_params [[buffer(5), function_constant(use_out_source)]], \\\n      const constant int* batch_shape [[buffer(6)]], \\\n      const constant size_t* batch_strides [[buffer(7)]], \\\n      const constant uint32_t* lhs_indices [[buffer(10), function_constant(do_gather)]], \\\n      const constant uint32_t* rhs_indices [[buffer(11), function_constant(do_gather)]], \\\n      const constant uint32_t* C_indices [[buffer(12), function_constant(gather_bias)]], \\\n      const constant int* operand_shape [[buffer(13), function_constant(do_gather)]], \\\n      const constant size_t* operand_strides [[buffer(14), function_constant(do_gather)]], \\\n      const constant packed_int3& operand_batch_ndim [[buffer(15), function_constant(do_gather)]], \\\n      device GEMMDebug * debug [[buffer(16), function_constant(debug_mode)]], \\\n      uint3 tpig[[thread_position_in_grid]], \\\n      uint num_simd_group [[simdgroups_per_threadgroup]], \\\n      uint num_threads_in_simd [[threads_per_simdgroup]], \\\n      uint simd_lane_id [[thread_index_in_simdgroup]], \\\n      uint simd_group_id [[simdgroup_index_in_threadgroup]], \\\n      uint3 tid [[threadgroup_position_in_grid]], \\\n      uint3 lid [[thread_position_in_threadgroup]]);\n\n#define instantiate_gemm_transpose_helper(iname, itype, oname, otype, acctype, bm, bn, bk, wm, wn) \\\n    instantiate_gemm(nn, false, false, iname, itype, oname, otype, acctype, bm, bn, bk, wm, wn) \\\n    instantiate_gemm(nt, false, true , iname, itype, oname, otype, acctype, bm, bn, bk, wm, wn) \\\n    instantiate_gemm(tn, true , false, iname, itype, oname, otype, acctype, bm, bn, bk, wm, wn) \\\n    instantiate_gemm(tt, true , true , iname, itype, oname, otype, acctype, bm, bn, bk, wm, wn)\n\ninstantiate_gemm_transpose_helper(f32, float, f32, float, float, 32, 32, 16, 2, 2)\ninstantiate_gemm_transpose_helper(f16, half, f16, half, half, 32, 32, 16, 2, 2)"
  },
  {
    "path": "metal/src/kernels/matmul/mlx_gemm/mlx_gemv.metal",
    "content": "// MLX Kernel extracted from:\n// https://github.com/ml-explore/mlx/blob/main/mlx/backend/metal/kernels/gemv.metal\n// Copyright © 2023-2024 Apple Inc.\n\n#include <metal_simdgroup>\n#include <metal_stdlib>\n\n// #include \"mlx/backend/metal/kernels/defines.h\"\n// #include \"mlx/backend/metal/kernels/utils.h\"\n// #include \"mlx/backend/metal/kernels/steel/utils.h\"\n\n//////////////////////////////////////////////////////////////////////////////\n/// \"mlx/backend/metal/kernels/utils.h\"\n///////////////////////////////////////////////////////////////////////////////\n\n///////////////////////////////////////////////////////////////////////////////\n// Single Array with generic dims\n\ntemplate <typename stride_t>\nMETAL_FUNC stride_t elem_to_loc(\n    uint elem,\n    constant const int* shape,\n    constant const stride_t* strides,\n    int ndim) {\n  stride_t loc = 0;\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    loc += (elem % shape[i]) * strides[i];\n    elem /= shape[i];\n  }\n  return loc;\n}\n\ntemplate <typename stride_t>\nMETAL_FUNC stride_t elem_to_loc(\n    stride_t elem,\n    constant const int* shape,\n    constant const stride_t* strides,\n    int ndim) {\n  stride_t loc = 0;\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    loc += (elem % shape[i]) * strides[i];\n    elem /= shape[i];\n  }\n  return loc;\n}\n\n// Non templated version to handle arbitrary dims\ntemplate <typename stride_t>\nMETAL_FUNC stride_t elem_to_loc(\n    uint3 elem,\n    constant const int* shape,\n    constant const stride_t* strides,\n    int ndim) {\n  stride_t loc = elem.x * strides[ndim - 1] + elem.y * strides[ndim - 2];\n  for (int d = ndim - 3; d >= 0; --d) {\n    loc += (elem.z % shape[d]) * strides[d];\n    elem.z /= shape[d];\n  }\n  return loc;\n}\n\n///////////////////////////////////////////////////////////////////////////////\n// Indexing utils\n///////////////////////////////////////////////////////////////////////////////\n\n#define MLX_MTL_PRAGMA_UNROLL _Pragma(\"clang loop unroll(full)\")\n\n///////////////////////////////////////////////////////////////////////////////\n// SIMD shuffle ops\n///////////////////////////////////////////////////////////////////////////////\n\ninline uint64_t simd_shuffle_down(uint64_t data, uint16_t delta) {\n  return as_type<uint64_t>(\n      metal::simd_shuffle_down(as_type<uint2>(data), delta));\n}\n\ninline int64_t simd_shuffle_down(int64_t data, uint16_t delta) {\n  return as_type<int64_t>(\n      metal::simd_shuffle_down(as_type<uint2>(data), delta));\n}\n\n\n//////////////////////////////////////////////////////////////////////////////\n/// \"mlx/backend/metal/kernels/steel/utils.h\"\n///////////////////////////////////////////////////////////////////////////////\n\nMETAL_FUNC ulong2 elem_to_loc_broadcast(\n    uint elem,\n    constant const int* shape,\n    constant const size_t* a_strides,\n    constant const size_t* b_strides,\n    int ndim) {\n  ulong loc_a{0};\n  ulong loc_b{0};\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    int pos_in_dim = (elem % shape[i]);\n    elem /= shape[i];\n    loc_a += pos_in_dim * a_strides[i];\n    loc_b += pos_in_dim * b_strides[i];\n  }\n  return ulong2(loc_a, loc_b);\n}\n\nMETAL_FUNC ulong3 elem_to_loc_broadcast(\n    uint elem,\n    constant const int* shape,\n    constant const size_t* a_strides,\n    constant const size_t* b_strides,\n    constant const size_t* c_strides,\n    int ndim) {\n  ulong loc_a{0};\n  ulong loc_b{0};\n  ulong loc_c{0};\n  for (int i = ndim - 1; i >= 0 && elem > 0; --i) {\n    int pos_in_dim = (elem % shape[i]);\n    elem /= shape[i];\n    loc_a += pos_in_dim * a_strides[i];\n    loc_b += pos_in_dim * b_strides[i];\n    loc_c += pos_in_dim * c_strides[i];\n  }\n  return ulong3(loc_a, loc_b, loc_c);\n}\n\n\nusing namespace metal;\n\n///////////////////////////////////////////////////////////////////////////////\n/// Matrix vector multiplication\n///////////////////////////////////////////////////////////////////////////////\n\n#define MLX_MTL_CONST static constant constexpr const\n\ntemplate <\n    typename T,\n    const int BM, /* Threadgroup rows (in simdgroups) */\n    const int BN, /* Threadgroup cols (in simdgroups) */\n    const int SM, /* Simdgroup rows (in threads) */\n    const int SN, /* Simdgroup cols (in threads) */\n    const int TM, /* Thread rows (in elements) */\n    const int TN, /* Thread cols (in elements) */\n    const bool kDoAxpby> /* Do out = alpha * out + beta * bias */\nstruct GEMVKernel {\n  MLX_MTL_CONST int threadsM = BM * SM;\n  MLX_MTL_CONST int threadsN = BN * SN;\n\n  MLX_MTL_CONST int blockM = threadsM * TM;\n  MLX_MTL_CONST int blockN = threadsN * TN;\n\n  static_assert(SM * SN == 32, \"simdgroup can only have 32 threads\");\n\n  static_assert(\n      SN == 8 || SN == 16 || SN == 32,\n      \"gemv block must have a width of 8, 16, or 32\");\n\n  // - The matrix of size (M = out_vec_size, K = in_vec_size) is divided up\n  //   into blocks of (blockM, blockN) divided among threadgroups\n  // - Every thread works on a block of (TM, TN)\n  // - We assume each threadgroup has (threadsN, threadsM, 1) threads\n  //\n  // 1. A thread loads TN elements each from mat along TM rows\n  //    and the corresponding scalar from the vector\n  // 2. The thread then multiplies and adds to accumulate its local result for\n  //    the block\n  // 3. At the end, each thread has accumulated results over all blocks across\n  //    the rows. These are then summed up across the threadgroup\n  // 4. Each threadgroup writes its accumulated blockM outputs\n  //\n  // Edge case handling:\n  // - The threadgroup with the largest tid has blocks that exceed the matrix\n  //   * The blocks that start outside the matrix are never read (thread results\n  //     remain zero)\n  //   * The last thread that partially overlaps with the matrix is shifted\n  //     inwards such that the thread block fits exactly in the matrix\n\n  MLX_MTL_CONST short tgp_mem_size = BN > 1 ? BN*(blockM + TM) : 0;\n  MLX_MTL_CONST bool needs_tgp_reduction = BN > 1;\n\n  static METAL_FUNC void\n  load_unsafe(const device T* src, thread T dst[TN], const int src_offset = 0) {\n    MLX_MTL_PRAGMA_UNROLL\n    for (int tn = 0; tn < TN; tn++) {\n      dst[tn] = src[src_offset + tn];\n    }\n  }\n\n  static METAL_FUNC void load_safe(\n      const device T* src,\n      thread T dst[TN],\n      const int src_offset = 0,\n      const int src_size = TN) {\n    if (src_offset + TN <= src_size) {\n      MLX_MTL_PRAGMA_UNROLL\n      for (int tn = 0; tn < TN; tn++) {\n        dst[tn] = src[src_offset + tn];\n      }\n    } else { // Edgecase\n      MLX_MTL_PRAGMA_UNROLL\n      for (int tn = 0; tn < TN; tn++) {\n        dst[tn] = src_offset + tn < src_size ? src[src_offset + tn] : 0;\n      }\n    }\n  }\n\n  static METAL_FUNC void run(\n      const device T* mat [[buffer(0)]],\n      const device T* in_vec [[buffer(1)]],\n      const device T* bias [[buffer(2)]],\n      device T* out_vec [[buffer(3)]],\n      const constant int& in_vec_size [[buffer(4)]],\n      const constant int& out_vec_size [[buffer(5)]],\n      const constant int& matrix_ld [[buffer(6)]],\n      const constant float& alpha [[buffer(7)]],\n      const constant float& beta [[buffer(8)]],\n      const constant int& bias_stride [[buffer(14)]],\n      threadgroup T* tgp_memory [[threadgroup(0)]],\n      uint3 tid [[threadgroup_position_in_grid]],\n      uint3 lid [[thread_position_in_threadgroup]],\n      uint simd_gid [[simdgroup_index_in_threadgroup]],\n      uint simd_lid [[thread_index_in_simdgroup]]) {\n    // Appease compiler\n    (void)lid;\n\n    // Thread local accumulation results\n    thread T result[TM] = {0};\n    thread T inter[TN];\n    thread T v_coeff[TN];\n\n    const int thrM = SN != 32 ? simd_lid / SN : 0;\n    const int thrN = SN != 32 ? simd_lid % SN : int(simd_lid);\n\n    const int sgN = BN != 1 ? (simd_gid % BN) : 0;\n\n    const int simdM = BN != 1 ? SM * (simd_gid / BN) : int(SM * simd_gid);\n    const int simdN = BN != 1 ? SN * (simd_gid % BN) : 0;\n\n    int bm = (simdM + thrM) * TM;\n    int bn = (simdN + thrN) * TN;\n\n    // Block position\n    int out_row = tid.x * blockM + bm;\n\n    // Exit simdgroup if rows out of bound\n    if (out_row >= out_vec_size)\n      return;\n\n    // Adjust tail simdgroup to ensure in bound reads\n    out_row = out_row + TM <= out_vec_size ? out_row : out_vec_size - TM;\n\n    // Advance matrix\n    mat += out_row * matrix_ld;\n\n    constexpr const uniform<int> loop_stride = make_uniform(blockN);\n    const uniform<int> in_size = make_uniform(in_vec_size);\n    const uniform<int> n_iter = in_size / loop_stride;\n    const uniform<int> last_iter = loop_stride * n_iter;\n    const uniform<int> leftover = in_size - last_iter;\n\n    // Loop over in_vec in blocks of blockN\n    for (int i = 0; i < n_iter; ++i) {\n      load_unsafe(in_vec, v_coeff, bn);\n\n      // Per thread work loop\n      int mat_offset = 0;\n      MLX_MTL_PRAGMA_UNROLL\n      for (int tm = 0; tm < TM; tm++) {\n        // Load for the row\n        load_unsafe(mat, inter, mat_offset + bn);\n\n        // Accumulate results\n        MLX_MTL_PRAGMA_UNROLL\n        for (int tn = 0; tn < TN; tn++) {\n          result[tm] += inter[tn] * v_coeff[tn];\n        }\n\n        mat_offset += matrix_ld;\n      }\n\n      bn += blockN;\n    }\n\n    if (leftover > 0) {\n      load_safe(in_vec, v_coeff, bn, in_size);\n\n      // Per thread work loop\n      MLX_MTL_PRAGMA_UNROLL\n      for (int tm = 0; tm < TM; tm++) {\n        // Load for the row\n        load_safe(&mat[tm * matrix_ld], inter, bn, in_size);\n\n        // Accumulate results\n        MLX_MTL_PRAGMA_UNROLL\n        for (int tn = 0; tn < TN; tn++) {\n          result[tm] += inter[tn] * v_coeff[tn];\n        }\n      }\n    }\n\n    // Simdgroup accumulations\n    MLX_MTL_PRAGMA_UNROLL\n    for (int tm = 0; tm < TM; tm++) {\n      MLX_MTL_PRAGMA_UNROLL\n      for (ushort sn = (SN / 2); sn >= 1; sn >>= 1) {\n        result[tm] += simd_shuffle_down(result[tm], sn);\n      }\n    }\n\n    // Threadgroup accumulation results\n    if (needs_tgp_reduction) {\n      threadgroup T* tgp_results = tgp_memory + sgN * (blockM + TM) + bm;\n      if (thrN == 0) {\n        MLX_MTL_PRAGMA_UNROLL\n        for (int tm = 0; tm < TM; tm++) {\n          tgp_results[tm] = result[tm];\n        }\n\n        threadgroup_barrier(mem_flags::mem_none);\n\n        if (sgN == 0) {\n          MLX_MTL_PRAGMA_UNROLL\n          for (int sgn = 1; sgn < BN; sgn++) {\n            MLX_MTL_PRAGMA_UNROLL\n            for (int tm = 0; tm < TM; tm++) {\n              result[tm] += tgp_results[sgn * (blockM + TM) + tm];\n            }\n          }\n        }\n      }\n    }\n\n    // Write outputs\n    if (simdN == 0 && thrN == 0) {\n      MLX_MTL_PRAGMA_UNROLL\n      for (int tm = 0; tm < TM; tm++) {\n        if (kDoAxpby) {\n          out_vec[out_row + tm] = static_cast<T>(alpha) * result[tm] +\n              static_cast<T>(beta) * bias[(out_row + tm) * bias_stride];\n        } else {\n          out_vec[out_row + tm] = result[tm];\n        }\n      }\n    }\n  }\n};\n\n///////////////////////////////////////////////////////////////////////////////\n/// Vector matrix multiplication\n///////////////////////////////////////////////////////////////////////////////\n\ntemplate <\n    typename T,\n    const int BM, /* Threadgroup rows (in simdgroups) */\n    const int BN, /* Threadgroup cols (in simdgroups) */\n    const int SM, /* Simdgroup rows (in threads) */\n    const int SN, /* Simdgroup cols (in threads) */\n    const int TM, /* Thread rows (in elements) */\n    const int TN, /* Thread cols (in elements) */\n    const bool kDoAxpby> /* Do out = alpha * out + beta * bias */\nstruct GEMVTKernel {\n  MLX_MTL_CONST int threadsM = BM * SM;\n  MLX_MTL_CONST int threadsN = BN * SN;\n\n  MLX_MTL_CONST int blockM = threadsM * TM;\n  MLX_MTL_CONST int blockN = threadsN * TN;\n\n  static_assert(SM * SN == 32, \"simdgroup can only have 32 threads\");\n\n  // - The matrix of size (M = in_vec_size, N = out_vec_size) is divided up\n  //   into blocks of (blockM, blockN) divided among threadgroups\n  // - Every thread works on a block of (TM, TN)\n  // - We assume each threadgroup has (threadsN, threadsM, 1) threads\n  //\n  // 1. A thread loads TN elements each from mat along TM contiguous rows\n  //    and the corresponding scalar from the vector\n  // 2. The thread then accumulates its local result for the block\n  // 3. At the end, each thread has accumulated results over all blocks across\n  //    the rows. These are then summed up across the threadgroup\n  // 4. Each threadgroup writes its accumulated BN * TN outputs\n  //\n  // Edge case handling:\n  // - The threadgroup with the largest tid has blocks that exceed the matrix\n  //   * The blocks that start outside the matrix are never read (thread results\n  //     remain zero)\n  //   * The last thread that partially overlaps with the matrix is shifted\n  //     inwards such that the thread block fits exactly in the matrix\n\n  MLX_MTL_CONST short tgp_mem_size = BM > 1 ? BM*(blockN + TN) : 0;\n  MLX_MTL_CONST bool needs_tgp_reduction = BM > 1;\n\n  static METAL_FUNC void run(\n      const device T* mat [[buffer(0)]],\n      const device T* in_vec [[buffer(1)]],\n      const device T* bias [[buffer(2)]],\n      device T* out_vec [[buffer(3)]],\n      const constant int& in_vec_size [[buffer(4)]],\n      const constant int& out_vec_size [[buffer(5)]],\n      const constant int& marix_ld [[buffer(6)]],\n      const constant float& alpha [[buffer(7)]],\n      const constant float& beta [[buffer(8)]],\n      const constant int& bias_stride [[buffer(14)]],\n      threadgroup T* tgp_memory [[threadgroup(0)]],\n      uint3 tid [[threadgroup_position_in_grid]],\n      uint3 lid [[thread_position_in_threadgroup]],\n      uint simd_gid [[simdgroup_index_in_threadgroup]],\n      uint simd_lid [[thread_index_in_simdgroup]]) {\n    // Appease compiler\n    (void)lid;\n\n    // Thread local accumulation results\n    T result[TN] = {0};\n    T inter[TN];\n    T v_coeff[TM];\n\n    const int thrM = SN != 32 ? simd_lid / SN : 0;\n    const int thrN = SN != 32 ? simd_lid % SN : int(simd_lid);\n\n    const int sgM = BN != 1 ? (simd_gid / BN) : int(simd_gid);\n    const int sgN = BN != 1 ? (simd_gid % BN) : 0;\n\n    const int simdM = SM * sgM;\n    const int simdN = SN * sgN;\n\n    int cm = (simdM + thrM);\n    int cn = (simdN + thrN);\n\n    int bm = cm * TM;\n    int bn = cn * TN;\n\n    int out_col = tid.x * blockN + bn;\n\n    constexpr const uniform<int> loop_stride = make_uniform(blockM);\n    const uniform<int> in_size = make_uniform(in_vec_size);\n    const uniform<int> n_iter = in_size / loop_stride;\n    const uniform<int> last_iter = loop_stride * n_iter;\n    const uniform<int> leftover = in_size - last_iter;\n\n    // Edgecase handling\n    if (out_col < out_vec_size) {\n      out_col = out_col + TN < out_vec_size ? out_col : out_vec_size - TN;\n\n      // Per thread accumulation main loop\n      for (int i = 0; i < n_iter; ++i) {\n        // Adding a threadgroup_barrier improves performance slightly\n        // This is possibly it may help exploit cache better\n        threadgroup_barrier(mem_flags::mem_none);\n\n        MLX_MTL_PRAGMA_UNROLL\n        for (int tm = 0; tm < TM; tm++) {\n          v_coeff[tm] = in_vec[bm + tm];\n        }\n\n        MLX_MTL_PRAGMA_UNROLL\n        for (int tm = 0; tm < TM; tm++) {\n          for (int tn = 0; tn < TN; tn++) {\n            inter[tn] = mat[(bm + tm) * marix_ld + out_col + tn];\n          }\n          for (int tn = 0; tn < TN; tn++) {\n            result[tn] += v_coeff[tm] * inter[tn];\n          }\n        }\n\n        bm += blockM;\n      }\n\n      if (leftover > 0) {\n        for (int tm = 0; tm < TM && bm + tm < in_vec_size; tm++) {\n          v_coeff[tm] = in_vec[bm + tm];\n\n          MLX_MTL_PRAGMA_UNROLL\n          for (int tn = 0; tn < TN; tn++) {\n            inter[tn] = mat[(bm + tm) * marix_ld + out_col + tn];\n          }\n\n          MLX_MTL_PRAGMA_UNROLL\n          for (int tn = 0; tn < TN; tn++) {\n            result[tn] += v_coeff[tm] * inter[tn];\n          }\n        }\n      }\n    }\n\n    // Simdgroup accumulations\n    MLX_MTL_PRAGMA_UNROLL\n    for (int tn = 0; tn < TN; tn++) {\n      MLX_MTL_PRAGMA_UNROLL\n      for (ushort sm = (SM / 2); sm >= 1; sm >>= 1) {\n        result[tn] += simd_shuffle_down(result[tn], SN * sm);\n      }\n    }\n\n    // Threadgroup accumulation results\n    if (needs_tgp_reduction) {\n      threadgroup T* tgp_results = tgp_memory + sgM * (blockN + TN) + bn;\n      if (thrM == 0) {\n        MLX_MTL_PRAGMA_UNROLL\n        for (int tn = 0; tn < TN; tn++) {\n          tgp_results[tn] = result[tn];\n        }\n\n        threadgroup_barrier(mem_flags::mem_none);\n\n        if (sgM == 0) {\n          MLX_MTL_PRAGMA_UNROLL\n          for (int sgm = 1; sgm < BM; sgm++) {\n            MLX_MTL_PRAGMA_UNROLL\n            for (int tn = 0; tn < TN; tn++) {\n              result[tn] += tgp_results[sgm * (blockN + TN) + tn];\n            }\n          }\n        }\n      }\n    }\n\n    // Threadgroup accumulation and writing out results\n    if (cm == 0 && out_col < out_vec_size) {\n      MLX_MTL_PRAGMA_UNROLL\n      for (int j = 0; j < TN; j++) {\n        if (kDoAxpby) {\n          out_vec[out_col + j] = static_cast<T>(alpha) * result[j] +\n              static_cast<T>(beta) * bias[(out_col + j) * bias_stride];\n        } else {\n          out_vec[out_col + j] = result[j];\n        }\n      }\n    }\n  }\n};\n\n///////////////////////////////////////////////////////////////////////////////\n/// Matrix vector multiplication\n///////////////////////////////////////////////////////////////////////////////\n\ntemplate <\n    typename T,\n    const int BM, /* Threadgroup rows (in simdgroups) */\n    const int BN, /* Threadgroup cols (in simdgroups) */\n    const int SM, /* Simdgroup rows (in threads) */\n    const int SN, /* Simdgroup cols (in threads) */\n    const int TM, /* Thread rows (in elements) */\n    const int TN, /* Thread cols (in elements) */\n    const bool kDoNCBatch, /* Batch ndim > 1 */\n    const bool kDoAxpby> /* Do out = alpha * out + beta * bias */\n[[kernel, max_total_threads_per_threadgroup(BM* BN * 32)]] void gemv(\n    const device T* mat [[buffer(0)]],\n    const device T* in_vec [[buffer(1)]],\n    const device T* bias [[buffer(2)]],\n    device T* out_vec [[buffer(3)]],\n    const constant int& in_vec_size [[buffer(4)]],\n    const constant int& out_vec_size [[buffer(5)]],\n    const constant int& marix_ld [[buffer(6)]],\n    const constant float& alpha [[buffer(7)]],\n    const constant float& beta [[buffer(8)]],\n    const constant int& batch_ndim [[buffer(9)]],\n    const constant int* batch_shape [[buffer(10)]],\n    const constant size_t* vector_batch_stride [[buffer(11)]],\n    const constant size_t* matrix_batch_stride [[buffer(12)]],\n    const constant size_t* bias_batch_stride [[buffer(13)]],\n    const constant int& bias_stride [[buffer(14)]],\n    uint3 tid [[threadgroup_position_in_grid]],\n    uint3 lid [[thread_position_in_threadgroup]],\n    uint simd_gid [[simdgroup_index_in_threadgroup]],\n    uint simd_lid [[thread_index_in_simdgroup]]) {\n  using gemv_kernel = GEMVKernel<T, BM, BN, SM, SN, TM, TN, kDoAxpby>;\n  threadgroup T tgp_memory\n      [gemv_kernel::tgp_mem_size == 0 ? 1 : gemv_kernel::tgp_mem_size];\n\n  // Update batch offsets\n  if (kDoNCBatch) {\n    in_vec += elem_to_loc(tid.z, batch_shape, vector_batch_stride, batch_ndim);\n    mat += elem_to_loc(tid.z, batch_shape, matrix_batch_stride, batch_ndim);\n\n    if (kDoAxpby) {\n      bias += elem_to_loc(tid.z, batch_shape, bias_batch_stride, batch_ndim);\n    }\n\n  } else {\n    in_vec += tid.z * vector_batch_stride[0];\n    mat += tid.z * matrix_batch_stride[0];\n\n    if (kDoAxpby) {\n      bias += tid.z * bias_batch_stride[0];\n    }\n  }\n\n  out_vec += tid.z * out_vec_size;\n\n  gemv_kernel::run(\n      mat,\n      in_vec,\n      bias,\n      out_vec,\n      in_vec_size,\n      out_vec_size,\n      marix_ld,\n      alpha,\n      beta,\n      bias_stride,\n      gemv_kernel::tgp_mem_size == 0 ? nullptr : tgp_memory,\n      tid,\n      lid,\n      simd_gid,\n      simd_lid);\n}\n\n#define instantiate_gemv_helper(                                             \\\n    name, itype, bm, bn, sm, sn, tm, tn, nc, axpby)                          \\\n  template [[host_name(\"gemv_\" #name \"_bm\" #bm \"_bn\" #bn \"_sm\" #sm \"_sn\" #sn \\\n                       \"_tm\" #tm \"_tn\" #tn \"_nc\" #nc                         \\\n                       \"_axpby\" #axpby)]] [[kernel]] void                    \\\n  gemv<itype, bm, bn, sm, sn, tm, tn, nc, axpby>(                            \\\n      const device itype* mat [[buffer(0)]],                                 \\\n      const device itype* in_vec [[buffer(1)]],                              \\\n      const device itype* bias [[buffer(2)]],                                \\\n      device itype* out_vec [[buffer(3)]],                                   \\\n      const constant int& in_vec_size [[buffer(4)]],                         \\\n      const constant int& out_vec_size [[buffer(5)]],                        \\\n      const constant int& marix_ld [[buffer(6)]],                            \\\n      const constant float& alpha [[buffer(7)]],                             \\\n      const constant float& beta [[buffer(8)]],                              \\\n      const constant int& batch_ndim [[buffer(9)]],                          \\\n      const constant int* batch_shape [[buffer(10)]],                        \\\n      const constant size_t* vector_batch_stride [[buffer(11)]],             \\\n      const constant size_t* matrix_batch_stride [[buffer(12)]],             \\\n      const constant size_t* bias_batch_stride [[buffer(13)]],               \\\n      const constant int& bias_stride [[buffer(14)]],                        \\\n      uint3 tid [[threadgroup_position_in_grid]],                            \\\n      uint3 lid [[thread_position_in_threadgroup]],                          \\\n      uint simd_gid [[simdgroup_index_in_threadgroup]],                      \\\n      uint simd_lid [[thread_index_in_simdgroup]]);\n\n// clang-format off\n#define instantiate_gemv(name, itype, bm, bn, tm, tn)              \\\n  instantiate_gemv_helper(name, itype, bm, 1, 1, bn, tm, tn, 0, 0) \\\n  instantiate_gemv_helper(name, itype, bm, 1, 1, bn, tm, tn, 0, 1) \\\n  instantiate_gemv_helper(name, itype, bm, 1, 1, bn, tm, tn, 1, 0) \\\n  instantiate_gemv_helper(name, itype, bm, 1, 1, bn, tm, tn, 1, 1) // clang-format on\n\n// clang-format off\n#define instantiate_gemv_blocks(name, itype) \\\n  instantiate_gemv(name, itype, 4, 32, 1, 4) \\\n  instantiate_gemv(name, itype, 4, 32, 4, 4) \\\n  instantiate_gemv(name, itype, 8, 32, 4, 4) // clang-format on\n\ninstantiate_gemv_blocks(f32, float);\ninstantiate_gemv_blocks(f16, half);\n\ntemplate <\n    typename T,\n    const int BM, /* Threadgroup rows (in simdgroups) */\n    const int BN, /* Threadgroup cols (in simdgroups) */\n    const int SM, /* Simdgroup rows (in threads) */\n    const int SN, /* Simdgroup cols (in threads) */\n    const int TM, /* Thread rows (in elements) */\n    const int TN> /* Thread cols (in elements) */\n[[kernel, max_total_threads_per_threadgroup(BM* BN * 32)]] void gemv_gather(\n    const device T* mat [[buffer(0)]],\n    const device T* in_vec [[buffer(1)]],\n    const device T* bias [[buffer(2)]],\n    device T* out_vec [[buffer(3)]],\n    const constant int& in_vec_size [[buffer(4)]],\n    const constant int& out_vec_size [[buffer(5)]],\n    const constant int& marix_ld [[buffer(6)]],\n    const constant float& alpha [[buffer(7)]],\n    const constant float& beta [[buffer(8)]],\n    const constant int& batch_ndim [[buffer(9)]],\n    const constant int* batch_shape [[buffer(10)]],\n    const constant size_t* index_batch_strides [[buffer(11)]],\n    const constant int& vector_batch_ndim [[buffer(12)]],\n    const constant int* vector_batch_shape [[buffer(13)]],\n    const constant size_t* vector_batch_stride [[buffer(14)]],\n    const constant int& matrix_batch_ndim [[buffer(15)]],\n    const constant int* matrix_batch_shape [[buffer(16)]],\n    const constant size_t* matrix_batch_stride [[buffer(17)]],\n    const constant uint32_t* vec_indices [[buffer(18)]],\n    const constant uint32_t* mat_indices [[buffer(19)]],\n    uint3 tid [[threadgroup_position_in_grid]],\n    uint3 lid [[thread_position_in_threadgroup]],\n    uint simd_gid [[simdgroup_index_in_threadgroup]],\n    uint simd_lid [[thread_index_in_simdgroup]]) {\n  using gemv_kernel = GEMVKernel<T, BM, BN, SM, SN, TM, TN, false>;\n  threadgroup T tgp_memory\n      [gemv_kernel::tgp_mem_size == 0 ? 1 : gemv_kernel::tgp_mem_size];\n\n  uint32_t indx_vec;\n  uint32_t indx_mat;\n\n  // Update batch offsets\n  if (batch_ndim > 1) {\n    const constant size_t* veci_bstrides = index_batch_strides;\n    const constant size_t* mati_bstrides = index_batch_strides + batch_ndim;\n\n    ulong2 batch_offsets = elem_to_loc_broadcast(\n        tid.z, batch_shape, veci_bstrides, mati_bstrides, batch_ndim);\n\n    indx_vec = vec_indices[batch_offsets.x];\n    indx_mat = mat_indices[batch_offsets.y];\n\n  } else {\n    indx_vec = vec_indices[index_batch_strides[0] * tid.z];\n    indx_mat = mat_indices[index_batch_strides[batch_ndim] * tid.z];\n  }\n\n  if (vector_batch_ndim > 1) {\n    in_vec += elem_to_loc(\n        indx_vec, vector_batch_shape, vector_batch_stride, vector_batch_ndim);\n  } else {\n    in_vec += indx_vec * vector_batch_stride[0];\n  }\n\n  if (matrix_batch_ndim > 1) {\n    mat += elem_to_loc(\n        indx_mat, matrix_batch_shape, matrix_batch_stride, matrix_batch_ndim);\n  } else {\n    mat += indx_mat * matrix_batch_stride[0];\n  }\n\n  out_vec += tid.z * out_vec_size;\n\n  gemv_kernel::run(\n      mat,\n      in_vec,\n      bias,\n      out_vec,\n      in_vec_size,\n      out_vec_size,\n      marix_ld,\n      alpha,\n      beta,\n      batch_ndim, // Not used\n      gemv_kernel::tgp_mem_size == 0 ? nullptr : tgp_memory,\n      tid,\n      lid,\n      simd_gid,\n      simd_lid);\n}\n\n#define instantiate_gemv_bs_helper(nm, itype, bm, bn, sm, sn, tm, tn)   \\\n  template [[host_name(\"gemv_gather_\" #nm \"_bm\" #bm \"_bn\" #bn \"_sm\" #sm \\\n                       \"_sn\" #sn \"_tm\" #tm \"_tn\" #tn)]] [[kernel]] void \\\n  gemv_gather<itype, bm, bn, sm, sn, tm, tn>(                           \\\n      const device itype* mat [[buffer(0)]],                            \\\n      const device itype* in_vec [[buffer(1)]],                         \\\n      const device itype* bias [[buffer(2)]],                           \\\n      device itype* out_vec [[buffer(3)]],                              \\\n      const constant int& in_vec_size [[buffer(4)]],                    \\\n      const constant int& out_vec_size [[buffer(5)]],                   \\\n      const constant int& marix_ld [[buffer(6)]],                       \\\n      const constant float& alpha [[buffer(7)]],                        \\\n      const constant float& beta [[buffer(8)]],                         \\\n      const constant int& batch_ndim [[buffer(9)]],                     \\\n      const constant int* batch_shape [[buffer(10)]],                   \\\n      const constant size_t* index_batch_strides [[buffer(11)]],        \\\n      const constant int& vector_batch_ndim [[buffer(12)]],             \\\n      const constant int* vector_batch_shape [[buffer(13)]],            \\\n      const constant size_t* vector_batch_stride [[buffer(14)]],        \\\n      const constant int& matrix_batch_ndim [[buffer(15)]],             \\\n      const constant int* matrix_batch_shape [[buffer(16)]],            \\\n      const constant size_t* matrix_batch_stride [[buffer(17)]],        \\\n      const constant uint32_t* vec_indices [[buffer(18)]],              \\\n      const constant uint32_t* mat_indices [[buffer(19)]],              \\\n      uint3 tid [[threadgroup_position_in_grid]],                       \\\n      uint3 lid [[thread_position_in_threadgroup]],                     \\\n      uint simd_gid [[simdgroup_index_in_threadgroup]],                 \\\n      uint simd_lid [[thread_index_in_simdgroup]]);\n\n// clang-format off\n#define instantiate_gemv_bs_blocks(name, itype)        \\\n  instantiate_gemv_bs_helper(name, itype, 4, 1, 1, 32, 1, 4) \\\n  instantiate_gemv_bs_helper(name, itype, 4, 1, 1, 32, 4, 4) \\\n  instantiate_gemv_bs_helper(name, itype, 8, 1, 1, 32, 4, 4) // clang-format on\n\ninstantiate_gemv_bs_blocks(f32, float);\ninstantiate_gemv_bs_blocks(f16, half);\n\n///////////////////////////////////////////////////////////////////////////////\n/// Vector matrix multiplication\n///////////////////////////////////////////////////////////////////////////////\n\ntemplate <\n    typename T,\n    const int BM, /* Threadgroup rows (in simdgroups) */\n    const int BN, /* Threadgroup cols (in simdgroups) */\n    const int SM, /* Simdgroup rows (in threads) */\n    const int SN, /* Simdgroup cols (in threads) */\n    const int TM, /* Thread rows (in elements) */\n    const int TN, /* Thread cols (in elements) */\n    const bool kDoNCBatch, /* Batch ndim > 1 */\n    const bool kDoAxpby> /* Do out = alpha * out + beta * bias */\n[[kernel, max_total_threads_per_threadgroup(BM* BN * 32)]] void gemv_t(\n    const device T* mat [[buffer(0)]],\n    const device T* in_vec [[buffer(1)]],\n    const device T* bias [[buffer(2)]],\n    device T* out_vec [[buffer(3)]],\n    const constant int& in_vec_size [[buffer(4)]],\n    const constant int& out_vec_size [[buffer(5)]],\n    const constant int& marix_ld [[buffer(6)]],\n    const constant float& alpha [[buffer(7)]],\n    const constant float& beta [[buffer(8)]],\n    const constant int& batch_ndim [[buffer(9)]],\n    const constant int* batch_shape [[buffer(10)]],\n    const constant size_t* vector_batch_stride [[buffer(11)]],\n    const constant size_t* matrix_batch_stride [[buffer(12)]],\n    const constant size_t* bias_batch_stride [[buffer(13)]],\n    const constant int& bias_stride [[buffer(14)]],\n    uint3 tid [[threadgroup_position_in_grid]],\n    uint3 lid [[thread_position_in_threadgroup]],\n    uint simd_gid [[simdgroup_index_in_threadgroup]],\n    uint simd_lid [[thread_index_in_simdgroup]]) {\n  using gemv_kernel = GEMVTKernel<T, BM, BN, SM, SN, TM, TN, kDoAxpby>;\n  threadgroup T tgp_memory\n      [gemv_kernel::tgp_mem_size == 0 ? 1 : gemv_kernel::tgp_mem_size];\n\n  // Update batch offsets\n  if (kDoNCBatch) {\n    in_vec += elem_to_loc(tid.z, batch_shape, vector_batch_stride, batch_ndim);\n    mat += elem_to_loc(tid.z, batch_shape, matrix_batch_stride, batch_ndim);\n\n    if (kDoAxpby) {\n      bias += elem_to_loc(tid.z, batch_shape, bias_batch_stride, batch_ndim);\n    }\n\n  } else {\n    in_vec += tid.z * vector_batch_stride[0];\n    mat += tid.z * matrix_batch_stride[0];\n\n    if (kDoAxpby) {\n      bias += tid.z * bias_batch_stride[0];\n    }\n  }\n\n  out_vec += tid.z * out_vec_size;\n\n  gemv_kernel::run(\n      mat,\n      in_vec,\n      bias,\n      out_vec,\n      in_vec_size,\n      out_vec_size,\n      marix_ld,\n      alpha,\n      beta,\n      bias_stride,\n      gemv_kernel::tgp_mem_size == 0 ? nullptr : tgp_memory,\n      tid,\n      lid,\n      simd_gid,\n      simd_lid);\n}\n\n#define instantiate_gemv_t_helper(                                             \\\n    name, itype, bm, bn, sm, sn, tm, tn, nc, axpby)                            \\\n  template [[host_name(\"gemv_t_\" #name \"_bm\" #bm \"_bn\" #bn \"_sm\" #sm \"_sn\" #sn \\\n                       \"_tm\" #tm \"_tn\" #tn \"_nc\" #nc                           \\\n                       \"_axpby\" #axpby)]] [[kernel]] void                      \\\n  gemv_t<itype, bm, bn, sm, sn, tm, tn, nc, axpby>(                            \\\n      const device itype* mat [[buffer(0)]],                                   \\\n      const device itype* in_vec [[buffer(1)]],                                \\\n      const device itype* bias [[buffer(2)]],                                  \\\n      device itype* out_vec [[buffer(3)]],                                     \\\n      const constant int& in_vec_size [[buffer(4)]],                           \\\n      const constant int& out_vec_size [[buffer(5)]],                          \\\n      const constant int& marix_ld [[buffer(6)]],                              \\\n      const constant float& alpha [[buffer(7)]],                               \\\n      const constant float& beta [[buffer(8)]],                                \\\n      const constant int& batch_ndim [[buffer(9)]],                            \\\n      const constant int* batch_shape [[buffer(10)]],                          \\\n      const constant size_t* vector_batch_stride [[buffer(11)]],               \\\n      const constant size_t* matrix_batch_stride [[buffer(12)]],               \\\n      const constant size_t* bias_batch_stride [[buffer(13)]],                 \\\n      const constant int& bias_stride [[buffer(14)]],                          \\\n      uint3 tid [[threadgroup_position_in_grid]],                              \\\n      uint3 lid [[thread_position_in_threadgroup]],                            \\\n      uint simd_gid [[simdgroup_index_in_threadgroup]],                        \\\n      uint simd_lid [[thread_index_in_simdgroup]]);\n\n// clang-format off\n#define instantiate_gemv_t(name, itype, bm, bn, sm, sn, tm, tn)        \\\n  instantiate_gemv_t_helper(name, itype, bm, bn, sm, sn, tm, tn, 0, 0) \\\n  instantiate_gemv_t_helper(name, itype, bm, bn, sm, sn, tm, tn, 0, 1) \\\n  instantiate_gemv_t_helper(name, itype, bm, bn, sm, sn, tm, tn, 1, 0) \\\n  instantiate_gemv_t_helper(name, itype, bm, bn, sm, sn, tm, tn, 1, 1) // clang-format on\n\n// clang-format off\n#define instantiate_gemv_t_blocks(name, itype) \\\n  instantiate_gemv_t(name, itype, 1, 2,  8, 4, 4, 1) \\\n  instantiate_gemv_t(name, itype, 1, 2,  8, 4, 4, 4) \\\n  instantiate_gemv_t(name, itype, 1, 4,  8, 4, 4, 4) \\\n  instantiate_gemv_t(name, itype, 1, 16, 8, 4, 4, 4) \\\n  instantiate_gemv_t(name, itype, 1, 16, 4, 8, 4, 4) // clang-format on\n\n// clang-format off\ninstantiate_gemv_t_blocks(f32, float);\ninstantiate_gemv_t_blocks(f16, half);\n\ntemplate <\n    typename T,\n    const int BM, /* Threadgroup rows (in simdgroups) */\n    const int BN, /* Threadgroup cols (in simdgroups) */\n    const int SM, /* Simdgroup rows (in threads) */\n    const int SN, /* Simdgroup cols (in threads) */\n    const int TM, /* Thread rows (in elements) */\n    const int TN> /* Thread cols (in elements) */\n[[kernel, max_total_threads_per_threadgroup(BM* BN * 32)]] void gemv_t_gather(\n    const device T* mat [[buffer(0)]],\n    const device T* in_vec [[buffer(1)]],\n    const device T* bias [[buffer(2)]],\n    device T* out_vec [[buffer(3)]],\n    const constant int& in_vec_size [[buffer(4)]],\n    const constant int& out_vec_size [[buffer(5)]],\n    const constant int& marix_ld [[buffer(6)]],\n    const constant float& alpha [[buffer(7)]],\n    const constant float& beta [[buffer(8)]],\n    const constant int& batch_ndim [[buffer(9)]],\n    const constant int* batch_shape [[buffer(10)]],\n    const constant size_t* index_batch_strides [[buffer(11)]],\n    const constant int& vector_batch_ndim [[buffer(12)]],\n    const constant int* vector_batch_shape [[buffer(13)]],\n    const constant size_t* vector_batch_stride [[buffer(14)]],\n    const constant int& matrix_batch_ndim [[buffer(15)]],\n    const constant int* matrix_batch_shape [[buffer(16)]],\n    const constant size_t* matrix_batch_stride [[buffer(17)]],\n    const constant uint32_t* vec_indices [[buffer(18)]],\n    const constant uint32_t* mat_indices [[buffer(19)]],\n    uint3 tid [[threadgroup_position_in_grid]],\n    uint3 lid [[thread_position_in_threadgroup]],\n    uint simd_gid [[simdgroup_index_in_threadgroup]],\n    uint simd_lid [[thread_index_in_simdgroup]]) {\n  using gemv_kernel = GEMVTKernel<T, BM, BN, SM, SN, TM, TN, false>;\n  threadgroup T tgp_memory\n      [gemv_kernel::tgp_mem_size == 0 ? 1 : gemv_kernel::tgp_mem_size];\n\n  uint32_t indx_vec;\n  uint32_t indx_mat;\n\n  // Update batch offsets\n  if (batch_ndim > 1) {\n    const constant size_t* veci_bstrides = index_batch_strides;\n    const constant size_t* mati_bstrides = index_batch_strides + batch_ndim;\n\n    ulong2 batch_offsets = elem_to_loc_broadcast(\n        tid.z, batch_shape, veci_bstrides, mati_bstrides, batch_ndim);\n\n    indx_vec = vec_indices[batch_offsets.x];\n    indx_mat = mat_indices[batch_offsets.y];\n\n  } else {\n    indx_vec = vec_indices[index_batch_strides[0] * tid.z];\n    indx_mat = mat_indices[index_batch_strides[batch_ndim] * tid.z];\n  }\n\n  if (vector_batch_ndim > 1) {\n    in_vec += elem_to_loc(\n        indx_vec, vector_batch_shape, vector_batch_stride, vector_batch_ndim);\n  } else {\n    in_vec += indx_vec * vector_batch_stride[0];\n  }\n\n  if (matrix_batch_ndim > 1) {\n    mat += elem_to_loc(\n        indx_mat, matrix_batch_shape, matrix_batch_stride, matrix_batch_ndim);\n  } else {\n    mat += indx_mat * matrix_batch_stride[0];\n  }\n\n  out_vec += tid.z * out_vec_size;\n\n  gemv_kernel::run(\n      mat,\n      in_vec,\n      bias,\n      out_vec,\n      in_vec_size,\n      out_vec_size,\n      marix_ld,\n      alpha,\n      beta,\n      batch_ndim, // Not used,\n      gemv_kernel::tgp_mem_size == 0 ? nullptr : tgp_memory,\n      tid,\n      lid,\n      simd_gid,\n      simd_lid);\n}\n\n#define instantiate_gemv_t_bs_helper(nm, itype, bm, bn, sm, sn, tm, tn)   \\\n  template [[host_name(\"gemv_t_gather_\" #nm \"_bm\" #bm \"_bn\" #bn \"_sm\" #sm \\\n                       \"_sn\" #sn \"_tm\" #tm \"_tn\" #tn)]] [[kernel]] void   \\\n  gemv_t_gather<itype, bm, bn, sm, sn, tm, tn>(                           \\\n      const device itype* mat [[buffer(0)]],                              \\\n      const device itype* in_vec [[buffer(1)]],                           \\\n      const device itype* bias [[buffer(2)]],                             \\\n      device itype* out_vec [[buffer(3)]],                                \\\n      const constant int& in_vec_size [[buffer(4)]],                      \\\n      const constant int& out_vec_size [[buffer(5)]],                     \\\n      const constant int& marix_ld [[buffer(6)]],                         \\\n      const constant float& alpha [[buffer(7)]],                          \\\n      const constant float& beta [[buffer(8)]],                           \\\n      const constant int& batch_ndim [[buffer(9)]],                       \\\n      const constant int* batch_shape [[buffer(10)]],                     \\\n      const constant size_t* index_batch_strides [[buffer(11)]],          \\\n      const constant int& vector_batch_ndim [[buffer(12)]],               \\\n      const constant int* vector_batch_shape [[buffer(13)]],              \\\n      const constant size_t* vector_batch_stride [[buffer(14)]],          \\\n      const constant int& matrix_batch_ndim [[buffer(15)]],               \\\n      const constant int* matrix_batch_shape [[buffer(16)]],              \\\n      const constant size_t* matrix_batch_stride [[buffer(17)]],          \\\n      const constant uint32_t* vec_indices [[buffer(18)]],                \\\n      const constant uint32_t* mat_indices [[buffer(19)]],                \\\n      uint3 tid [[threadgroup_position_in_grid]],                         \\\n      uint3 lid [[thread_position_in_threadgroup]],                       \\\n      uint simd_gid [[simdgroup_index_in_threadgroup]],                   \\\n      uint simd_lid [[thread_index_in_simdgroup]]);\n\n// clang-format off\n#define instantiate_gemv_t_bs_blocks(name, itype)              \\\n  instantiate_gemv_t_bs_helper(name, itype, 1,  2, 8, 4, 4, 1) \\\n  instantiate_gemv_t_bs_helper(name, itype, 1,  2, 8, 4, 4, 4) \\\n  instantiate_gemv_t_bs_helper(name, itype, 1,  4, 8, 4, 4, 4) \\\n  instantiate_gemv_t_bs_helper(name, itype, 1, 16, 8, 4, 4, 4) \\\n  instantiate_gemv_t_bs_helper(name, itype, 1, 16, 4, 8, 4, 4) // clang-format on\n\n// clang-format off\ninstantiate_gemv_t_bs_blocks(f32, float);\ninstantiate_gemv_t_bs_blocks(f16, half);\n"
  },
  {
    "path": "metal/src/kernels/matmul/mlx_gemm/mod.rs",
    "content": "use crate::kernels::matmul::{GemmDispatchParams, GemmKernel};\nuse crate::{ConstantValues, LibraryName, MetalStream, Value};\nuse anyhow::ensure;\nuse metal::{Buffer, MTLSize, NSUInteger};\nuse std::ffi::c_void;\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug)]\n#[repr(C)]\nstruct MlxGemmParams {\n    m: i32,\n    n: i32,\n    k: i32,\n    lda: i32,\n    ldb: i32,\n    ldd: i32,\n    tiles_n: i32,\n    tiles_m: i32,\n    batch_stride_a: isize,\n    batch_stride_b: isize,\n    batch_stride_d: isize,\n    swizzle_log: i32,\n    gemm_k_iterations_aligned: i32,\n    batch_ndim: i32,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]\npub struct MlxGemm;\n\nimpl fmt::Display for MlxGemm {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"MlxGemm\")\n    }\n}\n\nimpl GemmKernel for MlxGemm {\n    fn name() -> &'static str {\n        \"mlx\"\n    }\n\n    fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        params: GemmDispatchParams,\n        a_buffer: &Buffer,\n        b_buffer: &Buffer,\n        c_buffer: &Buffer,\n    ) -> TractResult<()> {\n        let GemmDispatchParams {\n            dts,\n            a_batch,\n            m,\n            k,\n            n,\n            transpose_a,\n            a_offset,\n            transpose_b,\n            b_offset,\n            c_offset,\n            a_strides,\n            b_strides,\n            ..\n        } = params;\n\n        ensure!(\n            matches!(dts[0], DatumType::F32 | DatumType::F16),\n            \"Unsupported datum type for MlxGemm {:?}\",\n            dts[0]\n        );\n        ensure!(\n            dts[0] == dts[1] && dts[0] == dts[2],\n            \"MlxGemm only supports homogeneous datum types. I: {:?}, {:?}. O: {:?}\",\n            dts[0],\n            dts[1],\n            dts[2]\n        );\n\n        if m == 1 || n == 1 {\n            dispatch_metal_mlx_gemv(\n                stream,\n                dts[0],\n                (a_batch, m, n, k),\n                unsafe { std::mem::transmute::<&[isize], &[usize]>(a_strides.as_slice()) },\n                a_offset,\n                a_buffer,\n                transpose_a,\n                unsafe { std::mem::transmute::<&[isize], &[usize]>(b_strides.as_slice()) },\n                b_offset,\n                b_buffer,\n                transpose_b,\n                c_buffer,\n                c_offset,\n            )?;\n        } else {\n            dispatch_metal_mlx_gemm(\n                stream,\n                dts[0],\n                (a_batch, m, n, k),\n                unsafe { std::mem::transmute::<&[isize], &[usize]>(a_strides.as_slice()) },\n                a_offset,\n                a_buffer,\n                transpose_a,\n                unsafe { std::mem::transmute::<&[isize], &[usize]>(b_strides.as_slice()) },\n                b_offset,\n                b_buffer,\n                transpose_b,\n                c_buffer,\n                c_offset,\n                false,\n            )?;\n        }\n\n        Ok(())\n    }\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn dispatch_metal_mlx_gemv(\n    stream: &MetalStream,\n    dt: DatumType,\n    (b, m, n, k): (usize, usize, usize, usize),\n    a_strides: &[usize],\n    a_offset: usize,\n    a_buffer: &Buffer,\n    a_trans: bool,\n    b_strides: &[usize],\n    b_offset: usize,\n    b_buffer: &Buffer,\n    b_trans: bool,\n    output: &Buffer,\n    output_offset: usize,\n) -> TractResult<()> {\n    ensure!(m == 1 || n == 1);\n    ensure!(a_strides.len() >= 2 && b_strides.len() >= 2);\n    ensure!(a_strides.len() >= 2);\n\n    let lda = if a_trans { m } else { k };\n    let ldb = if b_trans { k } else { n };\n\n    // Determine dispatch kernel\n    let (mut tm, mut tn) = (4, 4);\n    #[allow(unused_assignments)]\n    let (mut sm, mut sn) = (1, 32);\n    let (mut bm, mut bn) = (1, 1);\n\n    // Map (m, k, n) to Matrix * Vector\n\n    let is_b_matrix = n != 1;\n    let mv_m = if is_b_matrix { n } else { m };\n    let mv_k = k;\n    let mv_ld = if is_b_matrix { ldb } else { lda };\n    let mv_trans = if is_b_matrix { !b_trans } else { a_trans };\n    let mat_batch_stride = if is_b_matrix { b_strides[0] } else { a_strides[0] };\n    let vec_batch_stride = if is_b_matrix { a_strides[0] } else { b_strides[0] };\n\n    let n_out_per_tgp = if mv_trans {\n        (sm, sn) = if mv_k >= 8192 && mv_m >= 2048 { (4, 8) } else { (8, 4) };\n        bn = if mv_m >= 2048 {\n            16\n        } else if mv_m >= 512 {\n            4\n        } else {\n            2\n        };\n        // Specialized kernel for very small outputs\n        tn = if mv_m < tn { 1 } else { tn };\n\n        bn * sn * tn\n    } else {\n        bm = if mv_m >= 4096 { 8 } else { 4 };\n        sn = 32;\n        // Specialized kernel for very small outputs\n        tm = if mv_m < tm { 1 } else { tm };\n        bm * sm * tm\n    };\n\n    let n_tgp = mv_m.div_ceil(n_out_per_tgp);\n\n    let group_size = MTLSize { width: 32, height: bn as _, depth: bm as _ };\n    let grid_size = MTLSize {\n        width: n_tgp as _,\n        height: 1,\n        depth: /* batch_size_out */ b as u64,\n    };\n\n    let t_mat = if mv_trans { \"t_\" } else { \"\" };\n\n    let tname = DeviceTensor::tname(dt)?;\n    let name = format!(\"gemv_{t_mat}{tname}_bm{bm}_bn{bn}_sm{sm}_sn{sn}_tm{tm}_tn{tn}_nc0_axpby0\");\n    let pipeline = stream.load_pipeline(LibraryName::MlxGemv, &name)?;\n\n    let command_buffer = stream.command_buffer();\n    command_buffer.encode(|encoder| {\n        encoder.set_compute_pipeline_state(&pipeline);\n        if is_b_matrix {\n            encoder.set_buffer(0, Some(b_buffer), b_offset as _);\n            encoder.set_buffer(1, Some(a_buffer), a_offset as _);\n        } else {\n            encoder.set_buffer(0, Some(a_buffer), a_offset as _);\n            encoder.set_buffer(1, Some(b_buffer), b_offset as _);\n        }\n        encoder.set_buffer(3, Some(output), output_offset as _);\n\n        encoder.set_bytes(\n            4,\n            std::mem::size_of::<i32>() as u64,\n            &(mv_k as i32) as *const i32 as *const c_void,\n        );\n\n        encoder.set_bytes(\n            5,\n            std::mem::size_of::<i32>() as u64,\n            &(mv_m as i32) as *const i32 as *const c_void,\n        );\n\n        encoder.set_bytes(\n            6,\n            std::mem::size_of::<i32>() as u64,\n            &(mv_ld as i32) as *const i32 as *const c_void,\n        );\n\n        encoder.set_bytes(\n            9, // batch_ndim\n            std::mem::size_of::<i32>() as u64,\n            &1_i32 as *const i32 as *const c_void,\n        );\n        encoder.set_bytes(\n            10, // batch_shape\n            std::mem::size_of::<i32>() as u64,\n            &(b as i32) as *const i32 as *const c_void,\n        );\n        encoder.set_bytes(\n            11, // batch_strides_vec\n            std::mem::size_of::<usize>() as u64,\n            &vec_batch_stride as *const usize as *const c_void,\n        );\n        encoder.set_bytes(\n            12, // batch_strides_mat\n            std::mem::size_of::<usize>() as u64,\n            &mat_batch_stride as *const usize as *const c_void,\n        );\n\n        encoder.use_resource(a_buffer, metal::MTLResourceUsage::Read);\n        encoder.use_resource(b_buffer, metal::MTLResourceUsage::Read);\n        encoder.use_resource(output, metal::MTLResourceUsage::Write);\n        encoder.dispatch_thread_groups(grid_size, group_size);\n    });\n    Ok(())\n}\n\n// From https://github.com/huggingface/candle/blob/main/candle-metal-kernels/src/lib.rs\n#[allow(clippy::too_many_arguments)]\npub fn dispatch_metal_mlx_gemm(\n    stream: &MetalStream,\n    dt: DatumType,\n    (b, m, n, k): (usize, usize, usize, usize),\n    lhs_stride: &[usize],\n    lhs_offset: usize,\n    lhs_buffer: &Buffer,\n    lhs_transpose: bool,\n    rhs_stride: &[usize],\n    rhs_offset: usize,\n    rhs_buffer: &Buffer,\n    rhs_transpose: bool,\n    output: &Buffer,\n    output_offset: usize,\n    debug: bool,\n) -> TractResult<()> {\n    ensure!(rhs_stride.len() >= 2);\n    ensure!(lhs_stride.len() >= 2);\n\n    let rhs_m1 = rhs_stride[rhs_stride.len() - 1];\n    let rhs_m2 = rhs_stride[rhs_stride.len() - 2];\n    let lhs_m1 = lhs_stride[lhs_stride.len() - 1];\n    let lhs_m2 = lhs_stride[lhs_stride.len() - 2];\n    let a_trans = lhs_transpose;\n    let b_trans = rhs_transpose;\n\n    if a_trans {\n        // (k, m)\n        ensure!(\n            lhs_m1 == 1 && lhs_m2 == m,\n            \"Invalid left matmul argument [{lhs_m2}, {lhs_m1}] != [{m}, 1], strides: {:?} {:?} dims: (m: {m}, n: {n}, k: {k})\",\n            lhs_stride,\n            rhs_stride\n        );\n    } else {\n        // (m, k)\n        ensure!(\n            lhs_m1 == 1 && lhs_m2 == k,\n            \"Invalid left matmul argument [{lhs_m2}, {lhs_m1}] != [{k}, 1], strides: {:?} {:?} dims: (m: {m}, n: {n}, k: {k})\",\n            lhs_stride,\n            rhs_stride\n        );\n    }\n\n    if b_trans {\n        // (n, k)\n        ensure!(\n            rhs_m1 == 1 && rhs_m2 == k,\n            \"Invalid right matmul argument [{rhs_m2}, {rhs_m1}] != [{k}, 1], strides: {:?} {:?} dims: (m: {m}, n: {n}, k: {k})\",\n            lhs_stride,\n            rhs_stride\n        );\n    } else {\n        // (k, n)\n        ensure!(\n            rhs_m1 == 1 && rhs_m2 == n,\n            \"Invalid right matmul argument [{rhs_m2}, {rhs_m1}] != [{n}, 1] {:?} {:?} dims: (m: {m}, n: {n}, k: {k})\",\n            lhs_stride,\n            rhs_stride\n        );\n    }\n\n    let (bm, bn, bk, wn, wm) = (32, 32, 16, 2, 2);\n    // https://github.com/ml-explore/mlx/blob/02efb310cac667bc547d1b96f21596c221f84fe7/mlx/backend/metal/matmul.cpp#L422\n    let constants = Some(ConstantValues::new(vec![\n        (10, Value::Bool(/* has_batch */ b > 1)),\n        (100, Value::Bool(/* use_out_source */ false)),\n        (110, Value::Bool(/* do_axpby */ false)),\n        (200, Value::Bool(/* align_m */ m % bm == 0)),\n        (201, Value::Bool(/* align_n */ n % bn == 0)),\n        (202, Value::Bool(/* align_k */ k % bk == 0)),\n        (300, Value::Bool(/* do_gather */ false)),\n        (400, Value::Bool(debug)),\n    ]));\n\n    let swizzle_log = 0;\n    let tile = 1 << swizzle_log;\n    let tn = n.div_ceil(bn);\n    let tm = m.div_ceil(bm);\n    let tn = tn * tile;\n    let tm = tm.div_ceil(tile);\n\n    let batch_stride_a =\n        if lhs_stride.len() > 2 { lhs_stride[lhs_stride.len() - 3] } else { m * k };\n    let batch_stride_b =\n        if rhs_stride.len() > 2 { rhs_stride[rhs_stride.len() - 3] } else { n * k };\n\n    let gemm_params = MlxGemmParams {\n        m: m as i32,\n        n: n as i32,\n        k: k as i32,\n        lda: if a_trans { m } else { k } as i32,\n        ldb: if b_trans { k } else { n } as i32,\n        ldd: n as i32,\n        tiles_n: tn as i32,\n        tiles_m: tm as i32,\n        swizzle_log,\n        batch_stride_a: batch_stride_a as isize,\n        batch_stride_b: batch_stride_b as isize,\n        batch_stride_d: (m * n) as isize,\n        batch_ndim: 1i32,\n        gemm_k_iterations_aligned: (k / bk) as i32,\n    };\n\n    let batch_strides = [gemm_params.batch_stride_a, gemm_params.batch_stride_b];\n\n    let name = kernel_name_gemm(dt, a_trans, b_trans)?;\n\n    let pipeline = stream.load_pipeline_with_constants(LibraryName::MlxGemm, &name, constants)?;\n\n    let command_buffer = stream.command_buffer();\n    command_buffer.encode(|encoder| {\n        encoder.set_compute_pipeline_state(&pipeline);\n        encoder.set_buffer(0, Some(lhs_buffer), lhs_offset as NSUInteger);\n        encoder.set_buffer(1, Some(rhs_buffer), rhs_offset as NSUInteger);\n        encoder.set_buffer(3, Some(output), output_offset as NSUInteger);\n        encoder.set_bytes(\n            4,\n            std::mem::size_of::<MlxGemmParams>() as u64,\n            &gemm_params as *const MlxGemmParams as *const c_void,\n        );\n        encoder.set_bytes(\n            6, // batch_shape\n            std::mem::size_of::<i32>() as u64,\n            &(b as i32) as *const i32 as *const c_void,\n        );\n        encoder.set_bytes(\n            7,\n            (std::mem::size_of::<isize>() * batch_strides.len()) as u64,\n            batch_strides.as_ptr() as *const c_void,\n        );\n\n        let grid_size = MTLSize {\n            width: tn as u64,\n            height: tm as u64,\n            depth: /* batch_size_out */ b as u64,\n        };\n        let group_size = MTLSize { width: 32, height: wn, depth: wm };\n        encoder.use_resource(lhs_buffer, metal::MTLResourceUsage::Read);\n        encoder.use_resource(rhs_buffer, metal::MTLResourceUsage::Read);\n        encoder.use_resource(output, metal::MTLResourceUsage::Write);\n        encoder.dispatch_thread_groups(grid_size, group_size);\n    });\n    if debug {\n        stream.wait_until_completed()?;\n        //log::debug!(\"{:#?}\", gemm_debug);\n    }\n\n    Ok(())\n}\n\npub fn kernel_name_gemm(\n    dt: DatumType,\n    transpose_a: bool,\n    transpose_b: bool,\n) -> TractResult<String> {\n    let t_a = if transpose_a { \"t\" } else { \"n\" };\n    let t_b = if transpose_b { \"t\" } else { \"n\" };\n\n    let tname = DeviceTensor::tname(dt)?;\n    Ok(format!(\"gemm_{t_a}{t_b}_{tname}_{tname}_32_32_16_2_2\"))\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::with_borrowed_metal_stream;\n\n    use super::*;\n    use crate::kernels::matmul::GemmImpl;\n    use crate::kernels::matmul::tests::run_mmm_test_case;\n    use tract_gpu::tensor::{DeviceTensor, IntoDevice};\n\n    #[test]\n    fn test_mlx_gemv_compilation() -> TractResult<()> {\n        crate::utils::with_borrowed_metal_stream(|stream| {\n            stream.load_library(LibraryName::MlxGemv)\n        })?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_mlx_gemm() -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let (b, m, n, k) = (10, 32, 32, 16);\n            let a = Tensor::from_shape(\n                &[b, m, k],\n                &(0..b * m * k).map(|_f| 1.0 as f32).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n            let b = Tensor::from_shape(\n                &[b, k, n],\n                &(0..b * n * k).map(|_f| 1.0 as f32).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let c = GemmImpl::<MlxGemm>::default().eval(stream, &a, &b)?;\n\n            let expected_c = Tensor::from_shape(&[10, 32, 32], &vec![16.0; 10 * 32 * 32])?;\n\n            let c = c.to_host()?;\n            c.close_enough(&expected_c, Approximation::Approximate)?;\n            assert!(c.close_enough(&expected_c, Approximation::Approximate).is_ok());\n\n            let (b, m, n, k) = (2, 2, 4, 3);\n            let a = DeviceTensor::from_shape(\n                &[b, m, k],\n                &(0..b * m * k).map(|f| f as f32).collect::<Vec<_>>(),\n            )?;\n            let b = DeviceTensor::from_shape(\n                &[b, k, n],\n                &(0..b * n * k).map(|f| f as f32).collect::<Vec<_>>(),\n            )?;\n\n            let c = GemmImpl::<MlxGemm>::default().eval(stream, &a, &b)?;\n\n            let expected_c = Tensor::from_shape(\n                &[2, 2, 4],\n                &[\n                    20.0, 23.0, 26.0, 29.0, 56.0, 68.0, 80.0, 92.0, 344.0, 365.0, 386.0, 407.0,\n                    488.0, 518.0, 548.0, 578.0,\n                ],\n            )?;\n\n            assert!(c.to_host()?.close_enough(&expected_c, Approximation::Approximate).is_ok());\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_mat_vec() -> TractResult<()> {\n        run_mmm_test_case::<MlxGemm>((1, 4, 4, 1), false, false, DatumType::F32, DatumType::F32)?;\n        run_mmm_test_case::<MlxGemm>((10, 1, 4, 4), false, false, DatumType::F32, DatumType::F32)?;\n        run_mmm_test_case::<MlxGemm>((5, 1, 15, 7), false, true, DatumType::F32, DatumType::F32)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_mat_mul() -> TractResult<()> {\n        run_mmm_test_case::<MlxGemm>((1, 3, 5, 4), false, false, DatumType::F32, DatumType::F32)?;\n        run_mmm_test_case::<MlxGemm>((1, 2, 5, 10), false, true, DatumType::F32, DatumType::F32)?;\n        run_mmm_test_case::<MlxGemm>((1, 4, 4, 4), false, true, DatumType::F32, DatumType::F32)?;\n        run_mmm_test_case::<MlxGemm>((1, 4, 4, 200), false, true, DatumType::F32, DatumType::F32)?;\n        run_mmm_test_case::<MlxGemm>(\n            (1, 25, 1280, 32000),\n            false,\n            true,\n            DatumType::F32,\n            DatumType::F32,\n        )?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/matmul/mod.rs",
    "content": "mod basic;\nmod ggml_gemm;\nmod mfa;\nmod mlx_gemm;\n\npub use basic::BasicMatMul;\npub use ggml_gemm::GgmlGemm;\npub use mfa::MfaGemm;\npub use mlx_gemm::MlxGemm;\nuse tract_core::tract_linalg::block_quant::{BlockQuant, Q4_0};\n\nuse crate::MetalStream;\nuse crate::utils::get_metal_buffer;\nuse metal::Buffer;\nuse num_traits::One;\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\nuse tract_gpu::utils::{as_q40_tensor, get_quant_fact};\n\n#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)]\npub enum MetalGemmImplKind {\n    Mlx,\n    Mfa,\n    #[default]\n    Ggml,\n}\n\n#[derive(Debug, PartialEq, Eq, Hash, Clone)]\npub struct GemmDispatchParams {\n    pub dts: [DatumType; 3],\n    pub a_batch: usize,\n    pub b_batch: usize,\n    pub m: usize,\n    pub k: usize,\n    pub n: usize,\n    pub transpose_a: bool,\n    pub a_offset: usize,\n    pub transpose_b: bool,\n    pub b_offset: usize,\n    pub q40_b: bool,\n    pub c_offset: usize,\n    pub a_strides: TVec<isize>,\n    pub b_strides: TVec<isize>,\n}\n\nimpl GemmDispatchParams {\n    #[allow(clippy::too_many_arguments)]\n    pub fn compute_dispatches_params<M: GemmKernel>(\n        dts: [DatumType; 3],\n        a_offset: usize,\n        a_shape: &[usize],\n        transpose_a: bool,\n        b_offset: usize,\n        b_shape: &[usize],\n        transpose_b: bool,\n        q40_b: bool,\n        c_offset: usize,\n        c_shape: &[usize],\n    ) -> TractResult<Vec<GemmDispatchParams>> {\n        let rank = c_shape.len();\n        let squeezed_a_shape = squeeze_batch_axes(a_shape)?;\n        let squeezed_b_shape = squeeze_batch_axes(b_shape)?;\n        let squeezed_c_shape = squeeze_batch_axes(c_shape)?;\n\n        let a_batch = squeezed_a_shape[0];\n        let b_batch = squeezed_b_shape[0];\n\n        ensure!(squeezed_c_shape[0] == a_batch || squeezed_c_shape[0] == b_batch);\n\n        let m = c_shape[rank - 2];\n        let n = c_shape[rank - 1];\n        let k = a_shape[a_shape.len() - 2 + !transpose_a as usize];\n\n        ensure!((a_batch % b_batch == 0) || (a_batch == 1));\n        let a_strides = if transpose_a {\n            natural_strides(&[a_batch, k, m])\n        } else {\n            natural_strides(&[a_batch, m, k])\n        };\n        let b_strides = if transpose_b {\n            natural_strides(&[b_batch, n, k])\n        } else {\n            natural_strides(&[b_batch, k, n])\n        };\n\n        let b_batch_stride = if !q40_b {\n            n * k * dts[1].size_of()\n        } else {\n            ensure!(k % Q4_0.block_len() == 0);\n            n * (k / Q4_0.block_len()) * Q4_0.block_bytes()\n        };\n\n        match (a_batch, b_batch) {\n            // bmk, 1kn -> bmn\n            // bmk, 1nk -> bmn\n            (a_batch, 1) if a_batch != 1 && !transpose_a => Ok(vec![GemmDispatchParams {\n                dts,\n                a_batch: 1,\n                b_batch: 1,\n                m: m * a_batch,\n                n,\n                k,\n                transpose_a,\n                a_offset,\n                transpose_b,\n                b_offset,\n                q40_b,\n                c_offset,\n                a_strides,\n                b_strides,\n            }]),\n            // bkm, 1kn -> bmn\n            // bkm, 1nk -> bmn\n            // As many dispatches as batch dimension.\n            (a_batch, 1) if a_batch != 1 => Ok((0..a_batch)\n                .map(|a_batch_idx| GemmDispatchParams {\n                    dts,\n                    a_batch: 1,\n                    b_batch: 1,\n                    m,\n                    n,\n                    k,\n                    transpose_a,\n                    a_offset: a_offset + a_batch_idx * m * k * dts[0].size_of(),\n                    transpose_b,\n                    b_offset,\n                    q40_b,\n                    c_offset: c_offset + a_batch_idx * m * n * dts[2].size_of(),\n                    a_strides: a_strides.clone(),\n                    b_strides: b_strides.clone(),\n                })\n                .collect()),\n            // 1mk, bkn -> bmn\n            // 1km, bkn -> bmn\n            // 1mk, bnk -> bmn\n            // 1km, bnk -> bmn\n            // As many dispatch as batch dimension.\n            (1, b_batch) if b_batch != 1 => Ok((0..b_batch)\n                .map(|b_batch_idx| GemmDispatchParams {\n                    dts,\n                    a_batch: 1,\n                    b_batch: 1,\n                    m,\n                    n,\n                    k,\n                    transpose_a,\n                    a_offset,\n                    transpose_b,\n                    b_offset: b_offset + b_batch_idx * b_batch_stride,\n                    q40_b,\n                    c_offset: c_offset + b_batch_idx * m * n * dts[2].size_of(),\n                    a_strides: a_strides.clone(),\n                    b_strides: b_strides.clone(),\n                })\n                .collect()),\n            (a_batch, b_batch) => {\n                if M::supports_broadcast() {\n                    Ok(vec![GemmDispatchParams {\n                        dts,\n                        a_batch,\n                        b_batch,\n                        m,\n                        n,\n                        k,\n                        transpose_a,\n                        a_offset,\n                        transpose_b,\n                        b_offset,\n                        q40_b,\n                        c_offset,\n                        a_strides,\n                        b_strides,\n                    }])\n                } else {\n                    ensure!(a_batch == b_batch);\n                    // bmk, bkn -> bmn\n                    // bkm, bkn -> bmn\n                    // bmk, bnk -> bmn\n                    // bkm, bnk -> bmn\n                    Ok(vec![GemmDispatchParams {\n                        dts,\n                        a_batch,\n                        b_batch,\n                        m,\n                        n,\n                        k,\n                        transpose_a,\n                        a_offset,\n                        transpose_b,\n                        b_offset,\n                        q40_b,\n                        c_offset,\n                        a_strides,\n                        b_strides,\n                    }])\n                }\n            }\n        }\n    }\n}\n\npub trait GemmKernel:\n    fmt::Display + fmt::Debug + Clone + Default + Send + Sync + PartialEq + Eq + Hash\n{\n    fn name() -> &'static str;\n\n    fn supports_broadcast() -> bool {\n        false\n    }\n\n    fn is_supported_dts(&self, facts: &[TypedFact]) -> bool {\n        assert!(facts.len() == 2, \"Expected 2 inputs for matmul\");\n        facts.iter().all(|f| f.is_plain())\n            && matches!(facts[0].datum_type, DatumType::F32 | DatumType::F16)\n            && facts[0].datum_type == facts[1].datum_type\n    }\n\n    fn output_dt(&self, a_dt: DatumType, b_dt: DatumType) -> TractResult<DatumType> {\n        ensure!([DatumType::F16, DatumType::F32].contains(&a_dt));\n        ensure!(a_dt == b_dt);\n        Ok(a_dt)\n    }\n\n    fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        params: GemmDispatchParams,\n        a_buffer: &Buffer,\n        b_buffer: &Buffer,\n        c_buffer: &Buffer,\n    ) -> TractResult<()>;\n}\n\n#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]\npub struct GemmImpl<M: GemmKernel> {\n    pub transpose_a: bool,\n    pub transpose_b: bool,\n    pub matmul: M,\n}\n\nimpl<M: GemmKernel> fmt::Display for GemmImpl<M> {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{}\", self.matmul)\n    }\n}\n\nimpl<M: GemmKernel> GemmImpl<M> {\n    pub fn new(transpose_a: bool, transpose_b: bool) -> Self {\n        Self { transpose_a, transpose_b, matmul: M::default() }\n    }\n\n    pub fn output_shape<D: DimLike + One>(&self, a: &[D], b: &[D]) -> TVec<D> {\n        let rank = a.len();\n        let mut output: TVec<D> = (0..rank - 2)\n            .map(|ix| if a[ix].is_one() { b[ix].clone() } else { a[ix].clone() })\n            .collect();\n        output.push(a[rank - 2 + self.transpose_a as usize].clone());\n        output.push(b[rank - 2 + !self.transpose_b as usize].clone());\n        output\n    }\n\n    pub fn output_facts(\n        &self,\n        shape: &[TDim],\n        a_dt: DatumType,\n        b_dt: DatumType,\n    ) -> TractResult<TVec<TypedFact>> {\n        let out_dt = self.matmul.output_dt(a_dt, b_dt)?;\n        ensure!([DatumType::F16, DatumType::F32].contains(&out_dt));\n        Ok(tvec!(out_dt.fact(shape)))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &MetalStream,\n        a: &DeviceTensor,\n        b: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        // For q40 weights the tensor shape already carries the full logical\n        // dimensions [batch, n, k].  No need to chain with the fact shape.\n        let b_shape = b.shape().to_vec();\n\n        let c_dt = self.matmul.output_dt(a.datum_type(), b.datum_type())?;\n        let c_shape = self.output_shape(a.shape(), &b_shape);\n        let c = unsafe { DeviceTensor::uninitialized_dt(c_dt, &c_shape)? };\n\n        self.dispatch_eval(stream, a, b, &c)?;\n        stream.wait_until_completed()?;\n        Ok(c)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        a: &DeviceTensor,\n        b: &DeviceTensor,\n        c: &DeviceTensor,\n    ) -> TractResult<()> {\n        stream.retain_tensor(a);\n        stream.retain_tensor(b);\n        stream.retain_tensor(c);\n\n        let q40_b = get_quant_fact(b, &Q4_0);\n        // For q40 weights the tensor shape already carries the full logical\n        // dimensions [batch, n, k].  No need to chain with the fact shape.\n        let b_shape = b.shape().to_vec();\n\n        ensure!(c.shape() == self.output_shape(a.shape(), &b_shape).as_slice());\n\n        if c.shape().iter().product::<usize>() == 0 {\n            return Ok(());\n        }\n\n        let dispatches = GemmDispatchParams::compute_dispatches_params::<M>(\n            [a.datum_type(), b.datum_type(), c.datum_type()],\n            a.buffer_offset(),\n            a.shape(),\n            self.transpose_a,\n            b.buffer_offset(),\n            &b_shape,\n            self.transpose_b,\n            q40_b.is_some(),\n            c.buffer_offset(),\n            c.shape(),\n        )?;\n\n        let a_buff = get_metal_buffer(a);\n        let b_buff = get_metal_buffer(b);\n        let c_buff = get_metal_buffer(c);\n\n        for d in dispatches {\n            self.matmul\n                .dispatch_eval(\n                    stream,\n                    d.clone(),\n                    a_buff,\n                    b_buff,\n                    c_buff,\n                )\n                .with_context(|| {\n                    format!(\n                    \"Error while performing MatMul with {:?} (a: {:?}), (b: {:?}) = (c: {:?}) for dispatch: {:?}\",\n                    self.matmul,\n                    a.shape(),\n                    b.shape(),\n                    c.shape(),\n                    d,\n                )\n            })?;\n        }\n\n        Ok(())\n    }\n}\n\n// Squeeze batch axes and return a shape with a rank of 3.\nfn squeeze_batch_axes(s: &[usize]) -> TractResult<TVec<usize>> {\n    ensure!(s.len() >= 2);\n    let rank = s.len();\n    if s.len() == 2 {\n        return Ok(tvec![1, s[rank - 2], s[rank - 1]]);\n    }\n    let rank = s.len();\n    Ok(tvec![s[..rank - 2].iter().product(), s[rank - 2], s[rank - 1],])\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::with_borrowed_metal_stream;\n\n    use super::*;\n    use crate::kernels::matmul::GemmImpl;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\n    use tract_core::tract_data::itertools::Itertools;\n    use tract_core::tract_linalg::block_quant::{BlockQuant, BlockQuantStorage, Q4_0};\n    use tract_gpu::tensor::IntoDevice;\n\n    pub(crate) fn run_mmm_test_case<K: GemmKernel>(\n        (batch, m, k, n): (usize, usize, usize, usize),\n        transpose_a: bool,\n        transpose_b: bool,\n        a_dt: DatumType,\n        b_dt: DatumType,\n    ) -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let a_shape = if !transpose_a { [batch, m, k] } else { [batch, k, m] };\n            let b_shape = if !transpose_b { [batch, k, n] } else { [batch, n, k] };\n            let mut a = if a_dt == DatumType::F16 {\n                Tensor::from_shape(\n                    &a_shape,\n                    &(0..batch * m * k)\n                        .map(|f| f16::from_f32(f as f32 / (batch * m * k) as f32))\n                        .collect::<Vec<_>>(),\n                )?\n            } else {\n                Tensor::from_shape(\n                    &a_shape,\n                    &(0..batch * m * k)\n                        .map(|f| f as f32 / (batch * m * k) as f32)\n                        .collect::<Vec<_>>(),\n                )?\n            };\n\n            let mut b = if b_dt == DatumType::F16 {\n                Tensor::from_shape(\n                    &b_shape,\n                    &(0..batch * k * n)\n                        .map(|f| f16::from_f32(f as f32 / (batch * n * k) as f32))\n                        .collect::<Vec<_>>(),\n                )?\n            } else {\n                Tensor::from_shape(\n                    &b_shape,\n                    &(0..batch * k * n)\n                        .map(|f| f as f32 / (batch * m * k) as f32)\n                        .collect::<Vec<_>>(),\n                )?\n            };\n\n            let metal_output = GemmImpl::<K>::new(transpose_a, transpose_b).eval(\n                stream,\n                &a.clone().into_device()?,\n                &b.clone().into_device()?,\n            )?;\n\n            let matmul = PrefixMatMul {\n                transpose_a,\n                transpose_b,\n                transpose_c: false,\n                quantize_output: None,\n                operating_dt: Some(DatumType::F32),\n            };\n\n            // Compare to full precision\n            if a_dt == DatumType::F16 && !(b_dt == DatumType::F16) {\n                a = a.clone().cast_to_dt(DatumType::F32).unwrap().into_owned();\n            }\n            if b_dt == DatumType::F16 && !(a_dt == DatumType::F16) {\n                b = b.clone().cast_to_dt(DatumType::F32).unwrap().into_owned();\n            }\n\n            let output = args_1!(matmul.eval(tvec![a.into_tvalue(), b.into_tvalue()])?);\n            metal_output.to_host()?.close_enough(&output, Approximation::SuperApproximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_gemm_dispatches_params() -> TractResult<()> {\n        let dt = DatumType::F32;\n        let (m, k, n) = (2, 3, 4);\n        assert_eq!(\n            GemmDispatchParams::compute_dispatches_params::<MlxGemm>(\n                [dt; 3],\n                0,\n                &[1, m, k],\n                false,\n                0,\n                &[1, k, n],\n                false,\n                false,\n                0,\n                &[1, m, n],\n            )?,\n            vec![GemmDispatchParams {\n                dts: [dt; 3],\n                a_batch: 1,\n                b_batch: 1,\n                m,\n                n,\n                k,\n                transpose_a: false,\n                a_offset: 0,\n                transpose_b: false,\n                b_offset: 0,\n                q40_b: false,\n                c_offset: 0,\n                a_strides: natural_strides(&[1, m, k]),\n                b_strides: natural_strides(&[1, k, n])\n            }]\n        );\n\n        assert_eq!(\n            GemmDispatchParams::compute_dispatches_params::<MlxGemm>(\n                [dt; 3],\n                0,\n                &[10, m, k],\n                false,\n                0,\n                &[10, k, n],\n                false,\n                false,\n                0,\n                &[10, m, n],\n            )?,\n            vec![GemmDispatchParams {\n                dts: [dt; 3],\n                a_batch: 10,\n                b_batch: 10,\n                m,\n                n,\n                k,\n                transpose_a: false,\n                a_offset: 0,\n                transpose_b: false,\n                b_offset: 0,\n                q40_b: false,\n                c_offset: 0,\n                a_strides: natural_strides(&[10, m, k]),\n                b_strides: natural_strides(&[10, k, n])\n            }]\n        );\n\n        assert_eq!(\n            GemmDispatchParams::compute_dispatches_params::<MlxGemm>(\n                [dt; 3],\n                0,\n                &[1, m, k],\n                false,\n                0,\n                &[2, k, n],\n                false,\n                false,\n                10,\n                &[2, m, n],\n            )?,\n            vec![\n                GemmDispatchParams {\n                    dts: [dt; 3],\n                    a_batch: 1,\n                    b_batch: 1,\n                    m,\n                    n,\n                    k,\n                    transpose_a: false,\n                    a_offset: 0,\n                    transpose_b: false,\n                    b_offset: 0,\n                    q40_b: false,\n                    c_offset: 10,\n                    a_strides: natural_strides(&[1, m, k]),\n                    b_strides: natural_strides(&[2, k, n])\n                },\n                GemmDispatchParams {\n                    dts: [dt; 3],\n                    a_batch: 1,\n                    b_batch: 1,\n                    m,\n                    n,\n                    k,\n                    transpose_a: false,\n                    a_offset: 0,\n                    transpose_b: false,\n                    b_offset: 1 * n * k * dt.size_of(),\n                    q40_b: false,\n                    c_offset: 10 + m * n * dt.size_of(),\n                    a_strides: natural_strides(&[1, m, k]),\n                    b_strides: natural_strides(&[2, k, n])\n                }\n            ]\n        );\n\n        assert_eq!(\n            GemmDispatchParams::compute_dispatches_params::<MlxGemm>(\n                [dt; 3],\n                0,\n                &[2, k, m],\n                true,\n                0,\n                &[2, k, n],\n                false,\n                false,\n                100,\n                &[2, m, n],\n            )?,\n            vec![GemmDispatchParams {\n                dts: [dt; 3],\n                a_batch: 2,\n                b_batch: 2,\n                m,\n                n,\n                k,\n                transpose_a: true,\n                a_offset: 0,\n                transpose_b: false,\n                b_offset: 0,\n                q40_b: false,\n                c_offset: 100,\n                a_strides: natural_strides(&[2, k, m]),\n                b_strides: natural_strides(&[2, k, n])\n            }]\n        );\n\n        assert_eq!(\n            GemmDispatchParams::compute_dispatches_params::<MlxGemm>(\n                [dt; 3],\n                0,\n                &[2, k, m],\n                true,\n                0,\n                &[1, k, n],\n                false,\n                false,\n                100,\n                &[2, m, n],\n            )?,\n            vec![\n                GemmDispatchParams {\n                    dts: [dt; 3],\n                    a_batch: 1,\n                    b_batch: 1,\n                    m,\n                    n,\n                    k,\n                    transpose_a: true,\n                    a_offset: 0,\n                    transpose_b: false,\n                    b_offset: 0,\n                    q40_b: false,\n                    c_offset: 100,\n                    a_strides: natural_strides(&[2, k, m]),\n                    b_strides: natural_strides(&[1, k, n])\n                },\n                GemmDispatchParams {\n                    dts: [dt; 3],\n                    a_batch: 1,\n                    b_batch: 1,\n                    m,\n                    n,\n                    k,\n                    transpose_a: true,\n                    a_offset: 1 * m * k * dt.size_of(),\n                    transpose_b: false,\n                    b_offset: 0,\n                    q40_b: false,\n                    c_offset: 100 + 1 * m * n * dt.size_of(),\n                    a_strides: natural_strides(&[2, k, m]),\n                    b_strides: natural_strides(&[1, k, n])\n                }\n            ]\n        );\n\n        assert_eq!(\n            GemmDispatchParams::compute_dispatches_params::<MlxGemm>(\n                [dt; 3],\n                0,\n                &[10, m, k],\n                false,\n                10,\n                &[1, k, n],\n                false,\n                false,\n                0,\n                &[10, m, n],\n            )?,\n            vec![GemmDispatchParams {\n                dts: [dt; 3],\n                a_batch: 1,\n                b_batch: 1,\n                m: 10 * m,\n                n,\n                k,\n                transpose_a: false,\n                a_offset: 0,\n                transpose_b: false,\n                b_offset: 10,\n                q40_b: false,\n                c_offset: 0,\n                a_strides: natural_strides(&[10, m, k]),\n                b_strides: natural_strides(&[1, k, n])\n            }]\n        );\n\n        assert_eq!(\n            GemmDispatchParams::compute_dispatches_params::<GgmlGemm>(\n                [dt; 3],\n                0,\n                &[4, m, k],\n                false,\n                0,\n                &[2, k, n],\n                false,\n                false,\n                0,\n                &[4, m, n],\n            )?,\n            vec![GemmDispatchParams {\n                dts: [dt; 3],\n                a_batch: 4,\n                b_batch: 2,\n                m,\n                n,\n                k,\n                transpose_a: false,\n                a_offset: 0,\n                transpose_b: false,\n                b_offset: 0,\n                q40_b: false,\n                c_offset: 0,\n                a_strides: natural_strides(&[4, m, k]),\n                b_strides: natural_strides(&[2, k, n])\n            },]\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_squeeze_batch_axes() -> TractResult<()> {\n        assert_eq!(squeeze_batch_axes(&[1, 2, 3, 4])?, tvec![2, 3, 4]);\n        assert_eq!(squeeze_batch_axes(&[3, 2, 3, 4])?, tvec![6, 3, 4]);\n        assert_eq!(squeeze_batch_axes(&[3, 1, 2, 3, 4])?, tvec![6, 3, 4]);\n        assert!(squeeze_batch_axes(&[1]).is_err());\n        assert_eq!(squeeze_batch_axes(&[1, 1, 3, 4])?, tvec![1, 3, 4]);\n        Ok(())\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn mmm_mfa_prop_f32(pb in any::<MmmProblem<MfaGemm, f32>>()) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::Close).is_ok())\n        }\n\n        #[test]\n        fn mmm_mfa_prop_f16(pb in any::<MmmProblem<MfaGemm, f16>>()) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::Approximate).is_ok())\n        }\n\n        #[test]\n        fn mmm_mlx_prop_f32(pb in any::<MmmProblem<MlxGemm, f32>>()) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::Approximate).is_ok())\n        }\n\n        #[test]\n        fn mmm_mlx_prop_f16(pb in any::<MmmProblem<MlxGemm, f16>>()) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::VeryApproximate).is_ok())\n        }\n\n        #[test]\n        fn mmm_ggml_prop_f32(pb in <MmmProblem<GgmlGemm, f32>>::arbitrary_with(\n            MmmProblemParams {\n                force_k_as_inner_axis: true,\n                q4_0_weights: false,\n            }\n        )) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::Approximate).is_ok())\n        }\n\n        #[test]\n        fn mmm_ggml_prop_f16(pb in <MmmProblem<GgmlGemm, f16>>::arbitrary_with(\n            MmmProblemParams {\n                force_k_as_inner_axis: true,\n                q4_0_weights: false,\n            }\n        )) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::VeryApproximate).is_ok())\n        }\n\n        #[test]\n        fn mmm_ggml_prop_q4(pb in <MmmProblem<GgmlGemm, f32>>::arbitrary_with(\n            MmmProblemParams {\n                force_k_as_inner_axis: true,\n                q4_0_weights: true,\n            }\n        )) {\n            let output = pb.run().unwrap();\n            prop_assert!(output.close_enough(&pb.reference().unwrap(), Approximation::Approximate).is_ok())\n        }\n    }\n\n    #[derive(Default, Debug, Clone)]\n    pub struct MmmProblemParams {\n        pub force_k_as_inner_axis: bool,\n        pub q4_0_weights: bool,\n    }\n\n    #[derive(Debug)]\n    pub struct MmmProblem<K: GemmKernel, F: Datum + Float>\n    where\n        F: Datum + Float,\n        f32: AsPrimitive<F>,\n    {\n        pub b: usize,\n        pub m: usize,\n        pub k: usize,\n        pub n: usize,\n        pub lhs: Vec<F>,\n        pub transpose_lhs: bool,\n        pub rhs: Vec<F>,\n        pub transpose_rhs: bool,\n        pub q4_0: bool,\n        pub _phantom: std::marker::PhantomData<K>,\n    }\n\n    impl<K, F> Arbitrary for MmmProblem<K, F>\n    where\n        K: GemmKernel,\n        F: Datum + Float,\n        f32: AsPrimitive<F>,\n    {\n        type Parameters = MmmProblemParams;\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(params: MmmProblemParams) -> Self::Strategy {\n            (1usize..4, 1usize..128, 1usize..256, 1usize..128)\n                .prop_flat_map(move |(b, m, mut k, n)| {\n                    if params.q4_0_weights {\n                        k = k.div_ceil(32) * 32\n                    };\n\n                    let lhs_len = b * m * k;\n                    let rhs_len = b * n * k;\n                    let datum = (0f32..1f32).prop_map(|x| x.as_());\n                    (\n                        Just(b),\n                        Just(m),\n                        Just(k),\n                        Just(n),\n                        vec(datum.clone(), lhs_len..=lhs_len),\n                        proptest::bool::ANY,\n                        vec(datum, rhs_len..=rhs_len),\n                        proptest::bool::ANY,\n                    )\n                })\n                .prop_map(move |(b, m, k, n, lhs, mut transpose_lhs, rhs, mut transpose_rhs)| {\n                    if params.force_k_as_inner_axis {\n                        (transpose_lhs, transpose_rhs) = (false, true);\n                    }\n                    Self {\n                        b,\n                        m,\n                        k,\n                        n,\n                        lhs,\n                        transpose_lhs,\n                        rhs,\n                        transpose_rhs,\n                        q4_0: params.q4_0_weights,\n                        _phantom: std::marker::PhantomData,\n                    }\n                })\n                .boxed()\n        }\n    }\n\n    impl<K, F> MmmProblem<K, F>\n    where\n        K: GemmKernel,\n        F: Datum + Float + std::ops::AddAssign,\n        f32: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let matmul = PrefixMatMul {\n                transpose_a: self.transpose_lhs,\n                transpose_b: self.transpose_rhs,\n                transpose_c: false,\n                quantize_output: None,\n                operating_dt: Some(F::datum_type()),\n            };\n\n            let lhs_tensor = if self.transpose_lhs {\n                Tensor::from_shape(&[self.b, self.k, self.m], &self.lhs)?\n            } else {\n                Tensor::from_shape(&[self.b, self.m, self.k], &self.lhs)?\n            };\n            let mut rhs_tensor = if self.transpose_rhs {\n                Tensor::from_shape(&[self.b, self.n, self.k], &self.rhs)?\n            } else {\n                Tensor::from_shape(&[self.b, self.k, self.n], &self.rhs)?\n            };\n\n            if self.q4_0 {\n                rhs_tensor = Q4_0.simulate_precision_loss(rhs_tensor, 2)?\n            };\n            let output = matmul.eval(tvec![lhs_tensor.into_tvalue(), rhs_tensor.into_tvalue()])?;\n\n            Ok(output[0].clone().into_tensor())\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            with_borrowed_metal_stream(|stream| {\n                let lhs = if self.transpose_lhs {\n                    Tensor::from_shape(&[self.b, self.k, self.m], &self.lhs)?.into_device()?\n                } else {\n                    Tensor::from_shape(&[self.b, self.m, self.k], &self.lhs)?.into_device()?\n                };\n                let rhs = if self.transpose_rhs {\n                    if !self.q4_0 {\n                        Tensor::from_shape(&[self.b, self.n, self.k], &self.rhs)?\n                    } else {\n                        let b_quant = Q4_0.quant_f32(\n                            &self\n                                .rhs\n                                .clone()\n                                .into_iter()\n                                .map(|x| x.to_f32().unwrap())\n                                .collect_vec(),\n                        )?;\n\n                        BlockQuantStorage::new(\n                            Box::new(Q4_0),\n                            self.b * self.n,\n                            self.k,\n                            Arc::new(b_quant),\n                        )?\n                        .into_tensor_with_shape(f32::datum_type(), &[self.b, self.n, self.k])\n                    }\n                } else {\n                    Tensor::from_shape(&[self.b, self.k, self.n], &self.rhs)?\n                }\n                .into_device()?;\n\n                let matmul = GemmImpl::<K>::new(self.transpose_lhs, self.transpose_rhs);\n\n                let c = matmul.eval(stream, &lhs, &rhs)?;\n                Ok(c.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/mod.rs",
    "content": "#![allow(unused)]\n\npub mod array;\npub mod bin_ops;\npub mod conv;\npub mod element_wise;\npub mod matmul;\npub mod nn;\nmod utils;\n\nuse tract_core::internal::*;\n\n#[cfg(target_os = \"ios\")]\nconst METAL_FLASH_ATTENTION_LIB: &[u8] =\n    include_bytes!(\"matmul/mfa/libMetalFlashAttention-ios.metallib\");\n\n#[cfg(target_os = \"macos\")]\nconst METAL_FLASH_ATTENTION_LIB: &[u8] =\n    include_bytes!(\"matmul/mfa/libMetalFlashAttention-macos.metallib\");\n\n#[cfg(not(any(target_os = \"ios\", target_os = \"macos\")))]\nconst METAL_FLASH_ATTENTION_LIB: &[u8] = &[];\n\nconst MLX_GEMM: &str = include_str!(\"matmul/mlx_gemm/mlx_gemm.metal\");\nconst MLX_GEMV: &str = include_str!(\"matmul/mlx_gemm/mlx_gemv.metal\");\nconst GGML: &str = include_str!(\"matmul/ggml_gemm/ggml_mm_mv.metal\");\nconst BASIC_MAT_MUL: &str = include_str!(\"matmul/basic/basic_mat_mul.metal\");\nconst ARRAY_OPS: &str = include_str!(\"array/array_ops.metal\");\nconst BIN_OPS: &str = include_str!(\"bin_ops.metal\");\nconst NN_OPS: &str = include_str!(\"nn/nn_ops.metal\");\nconst CONV_OPS: &str = include_str!(\"conv.metal\");\nconst ELEMENT_WISE_OPS: &str = include_str!(\"element_wise.metal\");\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub enum LibraryContent<'a> {\n    Data(&'a [u8]),\n    Source(&'a str),\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum LibraryName {\n    MlxGemm,\n    MlxGemv,\n    MfaLib,\n    BasicMatMul,\n    BinOps,\n    ArrayOps,\n    ConvOps,\n    NNOps,\n    ElementWiseOps,\n    Ggml,\n}\n\nimpl LibraryName {\n    pub fn content(&self) -> LibraryContent<'static> {\n        match self {\n            Self::MfaLib => LibraryContent::Data(METAL_FLASH_ATTENTION_LIB),\n            Self::BasicMatMul => LibraryContent::Source(BASIC_MAT_MUL),\n            Self::ArrayOps => LibraryContent::Source(ARRAY_OPS),\n            Self::BinOps => LibraryContent::Source(BIN_OPS),\n            Self::ConvOps => LibraryContent::Source(CONV_OPS),\n            Self::NNOps => LibraryContent::Source(NN_OPS),\n            Self::ElementWiseOps => LibraryContent::Source(ELEMENT_WISE_OPS),\n            Self::MlxGemm => LibraryContent::Source(MLX_GEMM),\n            Self::MlxGemv => LibraryContent::Source(MLX_GEMV),\n            Self::Ggml => LibraryContent::Source(GGML),\n        }\n    }\n}\n\npub use tract_gpu::utils::BroadcastKind;\n"
  },
  {
    "path": "metal/src/kernels/nn/apply_rope.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::kernels::utils::compute_broadcast_strides;\nuse crate::kernels::{BroadcastKind, utils};\nuse crate::{LibraryName, MetalStream};\nuse anyhow::ensure;\nuse std::fmt;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct ApplyRope;\n\nimpl fmt::Display for ApplyRope {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{:?}\", self)\n    }\n}\n\nimpl ApplyRope {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn is_supported_broadcast(broadcast_kind: BroadcastKind) -> bool {\n        matches!(broadcast_kind, BroadcastKind::Nd2 | BroadcastKind::Nd3 | BroadcastKind::Nd4)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType, broadcast_kind: BroadcastKind) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for metal apply rope\", dt);\n        ensure!(\n            Self::is_supported_broadcast(broadcast_kind),\n            \"Unsupported broadcast kind {:?} for metal apply rope\",\n            broadcast_kind\n        );\n        let tname = DeviceTensor::tname(dt)?;\n        let broadcast_name = broadcast_kind.name();\n        Ok(format!(\"nn_ops::apply_rope_{broadcast_name}_{tname}\"))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        cos: &DeviceTensor,\n        sin: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, cos, sin, &output)?;\n        stream.wait_until_completed()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        cos: &DeviceTensor,\n        sin: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        ensure!(input.datum_type() == cos.datum_type());\n        ensure!(input.datum_type() == sin.datum_type());\n\n        ensure!(cos.shape() == sin.shape());\n\n        stream.retain_tensor(input);\n        stream.retain_tensor(cos);\n        stream.retain_tensor(sin);\n        stream.retain_tensor(output);\n\n        ensure!(input.rank() >= 2 && input.rank() <= 4);\n        ensure!(cos.rank() <= input.rank());\n\n        let padded_shape = [&tvec![1; input.rank() - cos.rank()], cos.shape()].concat();\n        let (padded_cos, padded_sin) =\n            (cos.reshaped(padded_shape.clone().into())?, sin.reshaped(padded_shape.into())?);\n\n        ensure!(\n            input.shape()[input.rank() - 1] % 2 == 0,\n            \"Rotate half required most inner dimension to be a multiple of 2: {:?}\",\n            input.shape()\n        );\n\n        let cos_sin_strides =\n            compute_broadcast_strides::<usize>(padded_cos.shape(), padded_sin.strides())?;\n\n        let broadcast_kind = BroadcastKind::from_rank(input.rank())\n            .with_context(|| format!(\"Unsupported rank for ApplyRope op: {:?}\", input.shape(),))?;\n\n        let kernel_name = self.kernel_name(input.datum_type(), broadcast_kind)?;\n\n        let pipeline = stream.load_pipeline(LibraryName::NNOps, &kernel_name)?;\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, &padded_cos, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(2, &padded_sin, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(3, output, metal::MTLResourceUsage::Write);\n            encoder.set_slice(4, input.shape());\n            encoder.set_slice(5, input.strides());\n            encoder.set_slice(6, &cos_sin_strides);\n            encoder.set_slice(7, output.strides());\n\n            let mut grid_size = utils::build_metal_size_for_shape(input.shape());\n            grid_size.width /= 2;\n\n            let group_size = metal::MTLSize { width: 32 as _, height: 32 as _, depth: 1 as _ };\n            encoder.dispatch_threads(grid_size, group_size);\n        });\n        Ok(())\n    }\n}\n\npub fn metal_apply_rope_dispatch(\n    input: &DeviceTensor,\n    cos: &DeviceTensor,\n    sin: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| ApplyRope.dispatch_eval(stream, input, cos, sin, output))\n}\n\ncrate::register_metal_op!(tract_transformers::ops::apply_rope::ApplyRope, |source, node, _op| {\n    rule_if!(ApplyRope::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::apply_rope::GpuApplyRope::new(\n        \"Metal\",\n        metal_apply_rope_dispatch,\n    ))))\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::utils::with_borrowed_metal_stream;\n    use tract_core::internal::Tensor;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_transformers::ops::apply_rope;\n\n    fn run_test_case(shape: &[usize]) -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len).map(|f| f as f32 / 1000.0).collect::<Vec<_>>(),\n            )?;\n\n            let cos =\n                Tensor::from_shape(shape, &(0..len).map(|f| (f as f32).cos()).collect::<Vec<_>>())?;\n\n            let sin =\n                Tensor::from_shape(shape, &(0..len).map(|f| (f as f32).sin()).collect::<Vec<_>>())?;\n\n            let metal_a = a.clone().into_device()?;\n            let metal_sin = sin.clone().into_device()?;\n            let metal_cos = cos.clone().into_device()?;\n\n            let cpu_output = apply_rope::ApplyRope.eval(tvec![\n                a.clone().into(),\n                cos.clone().into(),\n                sin.clone().into(),\n            ])?[0]\n                .clone()\n                .into_tensor();\n            let metal_output = ApplyRope.eval(stream, &metal_a, &metal_cos, &metal_sin)?;\n\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), Approximation::Approximate)\n                .with_context(|| {\n                    format!(\n                        \"Input: {:?} Cpu: {:?}, Metal: {:?}\",\n                        a.dump(true),\n                        cpu_output.dump(true),\n                        metal_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_apply_rope() -> TractResult<()> {\n        run_test_case(&[2, 1, 2, 2])?;\n        run_test_case(&[2, 4, 4])?;\n        run_test_case(&[2, 1, 512, 10])?;\n        run_test_case(&[8, 8])?;\n        run_test_case(&[1, 10, 512, 24])?;\n        run_test_case(&[3, 10, 512, 24])?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/nn/gelu_approximate.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::{LibraryName, MetalStream};\nuse metal::MTLSize;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]\npub struct GeluApproximate {\n    pub fast_impl: bool,\n}\n\nimpl GeluApproximate {\n    pub fn fast() -> Self {\n        Self { fast_impl: true }\n    }\n\n    pub fn accurate() -> Self {\n        Self { fast_impl: false }\n    }\n\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for metal geluop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        if self.fast_impl {\n            Ok(format!(\"nn_ops::gelu_approx_fast_{tname}\"))\n        } else {\n            Ok(format!(\"nn_ops::gelu_approx_{tname}\"))\n        }\n    }\n\n    pub fn eval(&self, stream: &MetalStream, input: &DeviceTensor) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, &output)?;\n        stream.wait_until_completed()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n\n        ensure!(output.shape() == input.shape());\n        ensure!(output.datum_type() == input.datum_type());\n\n        let kernel_name = self.kernel_name(input.datum_type())?;\n\n        let pipeline = stream.load_pipeline(LibraryName::NNOps, &kernel_name)?;\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, output, metal::MTLResourceUsage::Write);\n            let grid_size = MTLSize { width: output.len() as _, height: 1, depth: 1 };\n            let group_size = MTLSize { width: 1, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n}\n\npub fn metal_gelu_approximate_dispatch(\n    fast_impl: bool,\n    input: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| {\n        GeluApproximate { fast_impl }.dispatch_eval(stream, input, output)\n    })\n}\n\n// GeluApproximate is an ElementWiseMiniOp, so we register under ElementWiseOp's TypeId.\ncrate::register_metal_op!(tract_core::ops::element_wise::ElementWiseOp, |source, node, op| {\n    rule_if_some!(\n        ew = op.0.downcast_ref::<tract_transformers::ops::gelu_approximate::GeluApproximate>()\n    );\n    rule_if!(GeluApproximate::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::gelu_approximate::GpuGeluApproximate::new(\n        ew.fast_impl,\n        \"Metal\",\n        metal_gelu_approximate_dispatch,\n    ))))\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::utils::with_borrowed_metal_stream;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n    use tract_gpu::tensor::IntoDevice;\n    use tract_transformers::ops::gelu_approximate;\n\n    fn test_case<F>(\n        gelu_approx: GeluApproximate,\n        shape: &[usize],\n        offset: f32,\n        scale: f32,\n        approximate: Approximation,\n    ) -> TractResult<()>\n    where\n        F: Float + Datum,\n        usize: AsPrimitive<f32>,\n        f32: AsPrimitive<F>,\n    {\n        with_borrowed_metal_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v * scale + offset).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu_output = gelu_approximate::gelu_approximate(false)\n                .eval(tvec![a.to_host()?.into_tvalue()])?[0]\n                .clone()\n                .into_tensor();\n            let metal_output = gelu_approx.eval(stream, &a)?;\n\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), approximate)\n                .with_context(|| {\n                    format!(\n                        \"Input: {:?}, scale: {:?} Cpu: {:?}, Metal: {:?}\",\n                        a.to_host().and_then(|it| it.dump(true)),\n                        scale,\n                        cpu_output.dump(true),\n                        metal_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_gelu_approx() -> TractResult<()> {\n        test_case::<f32>(\n            GeluApproximate::accurate(),\n            &[4, 4],\n            -0.0,\n            1.0 / 100.0,\n            Approximation::Approximate,\n        )?;\n        test_case::<f32>(\n            GeluApproximate::accurate(),\n            &[4, 4],\n            -6.0,\n            1.0 / 1000.0,\n            Approximation::Approximate,\n        )?;\n        test_case::<f16>(\n            GeluApproximate::accurate(),\n            &[4, 4],\n            -6.0,\n            1.0 / 1000.0,\n            Approximation::SuperApproximate,\n        )?;\n        Ok(())\n    }\n    #[test]\n    fn test_gelu_approx_fast() -> TractResult<()> {\n        test_case::<f32>(\n            GeluApproximate::fast(),\n            &[4, 4],\n            -0.0,\n            1.0 / 100.0,\n            Approximation::SuperApproximate,\n        )?;\n        test_case::<f32>(\n            GeluApproximate::fast(),\n            &[4, 4],\n            -6.0,\n            1.0 / 1000.0,\n            Approximation::SuperApproximate,\n        )?;\n        test_case::<f16>(\n            GeluApproximate::fast(),\n            &[4, 4],\n            -6.0,\n            1.0 / 1000.0,\n            Approximation::SuperApproximate,\n        )?;\n        Ok(())\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn gelu_approx_prop_f32(pb in any::<GeluProblem<f32>>()) {\n            fn run(pb: GeluProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn gelu_approx_prop_f16(pb in any::<GeluProblem<f16>>()) {\n            fn run(pb: GeluProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct GeluProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub shape: Vec<usize>,\n        pub input: Vec<F>,\n    }\n\n    impl<F> Arbitrary for GeluProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            (0usize..3, 0usize..3)\n                .prop_flat_map(|(left, right)| {\n                    let shape_len = usize::min(left + right + 1, 4);\n                    let shape = 1usize..10;\n                    vec(shape, shape_len..=shape_len)\n                })\n                .prop_map(|shape| {\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { shape, input }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> GeluProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n\n            let cpu_output = gelu_approximate::gelu_approximate(false)\n                .eval(tvec![a.into_tvalue()])?[0]\n                .clone()\n                .into_tensor();\n\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            with_borrowed_metal_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let metal_output = GeluApproximate::accurate().eval(stream, &a)?;\n                Ok(metal_output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/nn/leaky_relu.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::{LibraryName, MetalStream};\nuse metal::{MTLSize, NSUInteger};\nuse std::ffi::c_void;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct LeakyRelu;\n\nimpl LeakyRelu {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for metal leaky_relu\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        Ok(format!(\"nn_ops::leaky_relu_{tname}\"))\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        alpha: f32,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n\n        ensure!(output.shape() == input.shape());\n        ensure!(output.datum_type() == input.datum_type());\n\n        let pipeline =\n            stream.load_pipeline(LibraryName::NNOps, &self.kernel_name(input.datum_type())?)?;\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, output, metal::MTLResourceUsage::Write);\n            encoder.set_bytes(\n                2,\n                std::mem::size_of::<f32>() as u64,\n                &alpha as *const f32 as *const c_void,\n            );\n\n            let grid_size = MTLSize { width: output.len() as NSUInteger, height: 1, depth: 1 };\n            let group_size = MTLSize { width: 1, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n}\n\npub fn metal_leaky_relu_dispatch(\n    alpha: f32,\n    input: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| LeakyRelu.dispatch_eval(stream, input, alpha, output))\n}\n\n// LeakyRelu is an ElementWiseMiniOp, so we register under ElementWiseOp's TypeId.\ncrate::register_metal_op!(tract_core::ops::element_wise::ElementWiseOp, |_source, _node, op| {\n    rule_if_some!(leaky = op.0.downcast_ref::<tract_core::ops::nn::LeakyRelu>());\n    Ok(Some(Box::new(tract_gpu::ops::leaky_relu::GpuLeakyRelu::new(\n        leaky.alpha,\n        \"Metal\",\n        metal_leaky_relu_dispatch,\n    ))))\n});\n"
  },
  {
    "path": "metal/src/kernels/nn/mod.rs",
    "content": "pub mod apply_rope;\npub mod gelu_approximate;\npub mod leaky_relu;\npub mod reduce;\npub mod rms_norm;\npub mod scaled_masked_softmax;\npub mod silu;\npub mod softmax;\n\npub use apply_rope::{ApplyRope, metal_apply_rope_dispatch};\npub use gelu_approximate::GeluApproximate;\npub use gelu_approximate::metal_gelu_approximate_dispatch;\npub use leaky_relu::LeakyRelu;\npub use leaky_relu::metal_leaky_relu_dispatch;\npub use reduce::{Reducer, metal_reduce_launch};\npub use rms_norm::RmsNorm;\npub use rms_norm::metal_rms_norm_dispatch;\npub use scaled_masked_softmax::{ScaledMaskedSoftmax, metal_scaled_masked_softmax_dispatch};\npub use silu::Silu;\npub use softmax::Softmax;\npub use softmax::metal_softmax_dispatch;\n\nuse crate::kernels::BroadcastKind;\n\npub fn all_functions() -> Vec<String> {\n    use std::collections::HashSet;\n    let mut functions = HashSet::<String>::new();\n\n    functions.extend(\n        Reducer::ALL\n            .into_iter()\n            .flat_map(|op| {\n                tract_gpu::tensor::DeviceTensor::SUPPORTED_DT.into_iter().map(move |dt| (op, dt))\n            })\n            .flat_map(|(op, dt)| reduce::kernel_name(&op, dt).into_iter()),\n    );\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| Softmax.kernel_name(dt).into_iter()),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| ScaledMaskedSoftmax.kernel_name(dt).into_iter()),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| [true, false].into_iter().map(move |is_l4| (dt, is_l4)))\n            .flat_map(|(dt, is_l4)| RmsNorm.kernel_name(dt, is_l4).into_iter()),\n    );\n\n    functions.extend(\n        BroadcastKind::ALL\n            .into_iter()\n            .flat_map(|brdcast| {\n                tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n                    .into_iter()\n                    .map(move |dt| (dt, brdcast))\n            })\n            .flat_map(|(dt, brdcast)| ApplyRope.kernel_name(dt, brdcast).into_iter()),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| [true, false].into_iter().map(move |fast_impl| (dt, fast_impl)))\n            .flat_map(|(dt, fast_impl)| GeluApproximate { fast_impl }.kernel_name(dt).into_iter()),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| [true, false].into_iter().map(move |is_l4| (dt, is_l4)))\n            .flat_map(|(dt, is_l4)| Silu.kernel_name(dt, is_l4).into_iter()),\n    );\n\n    functions.extend(\n        tract_gpu::tensor::DeviceTensor::SUPPORTED_DT\n            .into_iter()\n            .flat_map(|dt| LeakyRelu.kernel_name(dt).into_iter()),\n    );\n    functions.into_iter().collect()\n}\n"
  },
  {
    "path": "metal/src/kernels/nn/nn_ops.metal",
    "content": "#include <metal_math>\n#include <metal_stdlib>\n\nusing namespace metal;\n\n#define NUM_SIMDGROUP 32\n\nMETAL_FUNC uint indices_to_idx_2(uint2 indices,\n                                 constant const size_t strides[2]) {\n    return indices.x * strides[1] + indices.y * strides[0];\n}\n\nMETAL_FUNC uint indices_to_idx_3(uint3 indices,\n                                 constant const size_t strides[3]) {\n    return indices.x * strides[2] + indices.y * strides[1] +\n           indices.z * strides[0];\n}\n\nMETAL_FUNC uint indices_to_idx_4(uint3 indices, constant const size_t shape[4],\n                                 constant const size_t strides[4]) {\n    auto idx = indices.x * strides[3] + indices.y * strides[2];\n    idx += (indices.z % shape[1]) * strides[1];\n    indices.z /= shape[1];\n    idx += indices.z * strides[0];\n    return idx;\n}\n\ntemplate <typename U> struct MeanOfSquares {\n    float simd_reduce(float val, size_t reduce_dim) {\n        return simd_sum(val) / static_cast<float>(reduce_dim);\n    }\n\n    static constexpr constant float init = 0.0;\n\n    // Operator\n    float operator()(float acc, U a) {\n        float a_f = static_cast<float>(a);\n        return acc + a_f * a_f;\n    }\n};\n\ntemplate <typename U> struct Sum {\n    U simd_reduce(U val, size_t reduce_dim) { return simd_sum(val); }\n\n    static constexpr constant U init = U(0);\n\n    // Operator\n    U operator()(U acc, U a) { return acc + a; }\n};\n\ntemplate <typename U> struct Min {\n    template <typename T> T simd_reduce(T val, size_t reduce_dim) {\n        return simd_min(val);\n    }\n\n    static constexpr constant U init = metal::numeric_limits<U>::infinity();\n\n    // Operator\n    U operator()(U a, U b) { return a < b ? a : b; }\n};\n\ntemplate <typename U> struct Max {\n    template <typename T> T simd_reduce(T val, size_t reduce_dim) {\n        return simd_max(val);\n    }\n\n    static constexpr constant U init = -metal::numeric_limits<U>::infinity();\n\n    // Operator\n    U operator()(U a, U b) { return a > b ? a : b; }\n};\n\ntemplate <typename U> struct Prod {\n    U simd_reduce(U val, size_t reduce_dim) { return simd_product(val); }\n\n    static constexpr constant U init = U(1);\n\n    // Operator\n    U operator()(U acc, U a) { return acc * a; }\n};\n\ntemplate <typename U> struct All {\n    U simd_reduce(U val, size_t reduce_dim) { return simd_all(val); }\n\n    static constexpr constant U init = U(1);\n\n    // Operator\n    U operator()(U acc, U a) { return acc && a; }\n};\n\ntemplate <typename U> struct Any {\n    U simd_reduce(U val, size_t reduce_dim) { return simd_any(val); }\n\n    static constexpr constant U init = U(0);\n\n    // Operator\n    U operator()(U acc, U a) { return acc || a; }\n};\n\ntemplate <typename F, typename Op>\n[[kernel]] void reduce_nd3(device const void *input_b, device void *output_b,\n                           constant const size_t input_shape[3],\n                           constant const size_t input_strides[3],\n                           constant const size_t output_strides[3],\n                           uint3 tgpig [[threadgroup_position_in_grid]],\n                           uint tiisg [[thread_index_in_simdgroup]],\n                           uint tpsg [[threads_per_simdgroup]]) {\n\n    device const F *input = (device const F *)input_b;\n    device F *output = (device F *)output_b;\n\n    Op op = Op();\n\n    size_t reduce_dim = input_shape[1];\n\n    size_t out_idx = tgpig.x * output_strides[2] + tgpig.y * output_strides[1] +\n                     tgpig.z * output_strides[0];\n\n    size_t base_in_idx =\n        tgpig.x * input_strides[2] + tgpig.z * input_strides[0];\n\n    auto partial_acc = Op::init;\n    for (size_t i = tiisg; i < reduce_dim; i += tpsg) {\n        F el = input[base_in_idx + i * input_strides[1]];\n        partial_acc = op(partial_acc, el);\n    }\n    auto acc = op.simd_reduce(partial_acc, reduce_dim);\n\n    if (tiisg == 0) {\n        output[out_idx] = acc;\n    }\n}\n\ntypedef decltype(reduce_nd3<float, Prod<float>>) reduce_nd3_t;\n\n#define INSTANTIATE_REDUCE(name, op, tname, type)                              \\\n    template [[host_name(                                                      \\\n        \"nn_ops::reduce_\" #name                                                \\\n        \"_nd3_\" #tname)]] [[kernel]] reduce_nd3_t reduce_nd3<type, op<type>>;\n\nINSTANTIATE_REDUCE(mean_of_squares, MeanOfSquares, f32, float)\nINSTANTIATE_REDUCE(mean_of_squares, MeanOfSquares, f16, half)\nINSTANTIATE_REDUCE(sum, Sum, f32, float)\nINSTANTIATE_REDUCE(sum, Sum, f16, half)\nINSTANTIATE_REDUCE(min, Min, f32, float)\nINSTANTIATE_REDUCE(min, Min, f16, half)\nINSTANTIATE_REDUCE(max, Max, f32, float)\nINSTANTIATE_REDUCE(max, Max, f16, half)\nINSTANTIATE_REDUCE(prod, Prod, f32, float)\nINSTANTIATE_REDUCE(prod, Prod, f16, half)\nINSTANTIATE_REDUCE(all, All, bool, char)\nINSTANTIATE_REDUCE(any, Any, bool, char)\n\ntemplate <typename F>\n[[kernel]] void rms_norm_nd3(device const void *input_b, constant void *eps_b,\n                             device void *output_b,\n                             constant const size_t shape[3],\n                             constant const size_t strides[3],\n                             threadgroup float *shmem_f32 [[threadgroup(0)]],\n                             uint tgpig [[threadgroup_position_in_grid]],\n                             ushort tpitg [[thread_position_in_threadgroup]],\n                             ushort sgitg [[simdgroup_index_in_threadgroup]],\n                             ushort tiisg [[thread_index_in_simdgroup]],\n                             ushort ntg [[threads_per_threadgroup]]) {\n    if (sgitg == 0) {\n        shmem_f32[tiisg] = 0.0f;\n    }\n    device const F *input = (device const F *)input_b;\n    float eps = ((constant float *)eps_b)[0];\n    device F *output = (device F *)output_b;\n\n    size_t dim = shape[1];\n\n    size_t base_idx =\n        (tgpig % shape[2]) * strides[2] + (tgpig / shape[2]) * strides[0];\n\n    float partial_acc = 0.0;\n    for (size_t i = tpitg; i < dim; i += ntg) {\n        float el = static_cast<float>(input[base_idx + i * strides[1]]);\n        partial_acc += el * el;\n    }\n\n    partial_acc = simd_sum(partial_acc);\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n\n    if (tiisg == 0) {\n        shmem_f32[sgitg] = partial_acc;\n    }\n\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n\n    partial_acc = shmem_f32[tiisg];\n    partial_acc = simd_sum(partial_acc);\n\n    float mean_of_squares = partial_acc / dim;\n\n    float norm = metal::rsqrt(mean_of_squares + eps);\n\n    for (size_t i = tpitg; i < dim; i += ntg) {\n        auto idx = base_idx + i * strides[1];\n        output[idx] = input[idx] * norm;\n    }\n}\n\ntemplate <typename F, typename F4>\n[[kernel]] void rms_norm_nd2_l4(device const char *input_b,\n                                constant char *eps_b, device char *output_b,\n                                constant const size_t &n,\n                                constant const size_t &n_div_4,\n                                constant const size_t &outer_stride,\n                                threadgroup float *shmem_f32 [[threadgroup(0)]],\n                                uint tgpig [[threadgroup_position_in_grid]],\n                                ushort tpitg [[thread_position_in_threadgroup]],\n                                ushort sgitg [[simdgroup_index_in_threadgroup]],\n                                ushort tiisg [[thread_index_in_simdgroup]],\n                                ushort ntg [[threads_per_threadgroup]]) {\n    if (sgitg == 0) {\n        shmem_f32[tiisg] = 0.0f;\n    }\n\n    device const F4 *x = (device const F4 *)(input_b + tgpig * outer_stride);\n    float eps = ((constant float *)eps_b)[0];\n    float sumf = 0.0f;\n\n    // parallel sum\n    for (size_t i = tpitg; i < n_div_4; i += ntg) {\n        float4 el = static_cast<float4>(x[i]);\n        sumf += dot(el, el);\n    }\n    sumf = simd_sum(sumf);\n\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n\n    if (tiisg == 0) {\n        shmem_f32[sgitg] = sumf;\n    }\n\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n\n    sumf = shmem_f32[tiisg];\n    sumf = simd_sum(sumf);\n\n    const float mean = sumf / n;\n    const float scale = 1.0f / sqrt(mean + eps);\n\n    device F4 *y = (device F4 *)output_b + tgpig * n_div_4;\n    for (size_t i = tpitg; i < n_div_4; i += ntg) {\n        y[i] = x[i] * scale;\n    }\n}\n\ntypedef decltype(rms_norm_nd3<float>) rms_norm_nd3_t;\ntypedef decltype(rms_norm_nd2_l4<float, float4>) rms_norm_nd2_l4_t;\n\ntemplate [[host_name(\n    \"nn_ops::rms_norm_nd3_f32\")]] [[kernel]] rms_norm_nd3_t rms_norm_nd3<float>;\ntemplate [[host_name(\n    \"nn_ops::rms_norm_nd3_f16\")]] [[kernel]] rms_norm_nd3_t rms_norm_nd3<half>;\ntemplate\n    [[host_name(\"nn_ops::rms_norm_nd2_l4_f32\")]] [[kernel]] rms_norm_nd2_l4_t\n        rms_norm_nd2_l4<float, float4>;\ntemplate\n    [[host_name(\"nn_ops::rms_norm_nd2_l4_f16\")]] [[kernel]] rms_norm_nd2_l4_t\n        rms_norm_nd2_l4<half, half4>;\n\nstruct Sigmoid {\n    template <typename T> T operator()(T x) {\n        auto y = 1 / (1 + metal::exp(-metal::abs(x)));\n        return (x < 0) ? 1 - y : y;\n    }\n};\n\ntemplate <typename T>\n[[kernel]] void silu(device const void *input_b [[buffer(0)]],\n                     device void *output_b [[buffer(1)]],\n                     uint tpig [[thread_position_in_grid]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n\n    output[tpig] = Sigmoid()(static_cast<float>(input[tpig])) * input[tpig];\n}\n\ntypedef decltype(silu<float>) silu_t;\n\ntemplate <typename T4>\n[[kernel]] void silu_4(device const void *input_b, device void *output_b,\n                       uint tpig [[thread_position_in_grid]]) {\n    device const T4 *input = (device const T4 *)input_b;\n    device T4 *output = (device T4 *)output_b;\n    auto x = input[tpig];\n    output[tpig] = x / (1.0f + exp(-x));\n}\n\ntypedef decltype(silu_4<float4>) silu_4_t;\n\ntemplate [[host_name(\"nn_ops::silu_f32\")]] [[kernel]] silu_t silu<float>;\ntemplate [[host_name(\"nn_ops::silu_f16\")]] [[kernel]] silu_t silu<half>;\n\ntemplate [[host_name(\"nn_ops::silu_4_f32\")]] [[kernel]] silu_4_t silu_4<float4>;\ntemplate [[host_name(\"nn_ops::silu_4_f16\")]] [[kernel]] silu_4_t silu_4<half4>;\n\ntemplate <typename T>\n[[kernel]] void leaky_relu(device const void *input_b [[buffer(0)]],\n                           device void *output_b [[buffer(1)]],\n                           constant float &alpha [[buffer(2)]],\n                           uint tpig [[thread_position_in_grid]]) {\n    device const T *input = (device const T *)input_b;\n    device T *output = (device T *)output_b;\n    T x = input[tpig];\n    output[tpig] = x >= T(0) ? x : T(alpha) * x;\n}\n\ntypedef decltype(leaky_relu<float>) leaky_relu_t;\n\ntemplate [[host_name(\"nn_ops::leaky_relu_f32\")]] [[kernel]] leaky_relu_t leaky_relu<float>;\ntemplate [[host_name(\"nn_ops::leaky_relu_f16\")]] [[kernel]] leaky_relu_t leaky_relu<half>;\n\ntemplate <typename F>\n[[kernel]] void softmax_nd3(device const void *input_b, device void *output_b,\n                            constant const size_t shape[3],\n                            constant const size_t strides[3],\n                            uint3 tgpig [[threadgroup_position_in_grid]],\n                            uint tiisg [[thread_index_in_simdgroup]],\n                            uint tpsg [[threads_per_simdgroup]]) {\n\n    device const F *input = (device const F *)input_b;\n    device F *output = (device F *)output_b;\n\n    size_t dim = shape[1];\n\n    size_t base_idx = tgpig.x * strides[2] + tgpig.z * strides[0];\n\n    // Get max value on softmax dim\n    float partial_max = -INFINITY;\n    for (size_t i = tiisg; i < dim; i += tpsg) {\n        auto idx = base_idx + i * strides[1];\n        float el = static_cast<float>(input[idx]);\n        partial_max = max(partial_max, el);\n    }\n\n    float axis_max = simd_max(partial_max);\n\n    // Compute Sum(exp(x - max))\n    float partial_norm = 0;\n    for (size_t i = tiisg; i < dim; i += tpsg) {\n        auto idx = base_idx + i * strides[1];\n        float el = static_cast<float>(input[idx]);\n        float exp_el = fast::exp(el - axis_max);\n        partial_norm += exp_el;\n        output[idx] = static_cast<F>(exp_el);\n    }\n\n    float axis_norm = simd_sum(partial_norm);\n    float inv_axis_norm = 1.0 / axis_norm;\n\n    for (size_t i = tiisg; i < dim; i += tpsg) {\n        auto idx = base_idx + i * strides[1];\n        float exp_el = static_cast<float>(output[idx]);\n        output[idx] = static_cast<F>(exp_el * inv_axis_norm);\n    }\n}\n\ntypedef decltype(softmax_nd3<float>) softmax_nd3_t;\n\ntemplate [[host_name(\n    \"nn_ops::softmax_nd3_f32\")]] [[kernel]] softmax_nd3_t softmax_nd3<float>;\ntemplate [[host_name(\n    \"nn_ops::softmax_nd3_f16\")]] [[kernel]] softmax_nd3_t softmax_nd3<half>;\n\ntemplate <typename F>\n[[kernel]] void scaled_masked_softmax_nd5(\n    device const void *input_b, device const void *mask_b,\n    constant float *scale_b, device void *output_b,\n    constant const size_t shape[5], constant const size_t strides[5],\n    constant const size_t mask_strides[5], constant const size_t out_strides[5],\n\n    uint3 tgpig [[threadgroup_position_in_grid]],\n    uint tiisg [[thread_index_in_simdgroup]],\n    uint tpsg [[threads_per_simdgroup]],\n    uint3 tptg [[thread_position_in_threadgroup]],\n    uint3 tptgN [[threads_per_threadgroup]],\n\n    threadgroup float *tgmem [[threadgroup(0)]]) {\n\n    const uint tid = tptg.x;\n    const uint tg_sz = tptgN.x;\n    const uint sg_id = tid / tpsg;\n    const uint lane = tiisg;\n\n    // Grid is (rows, g, b * kh) == (shape[3], shape[2], shape[0] * shape[1])\n    const size_t row = (size_t)tgpig.x;\n    const size_t h = (size_t)tgpig.y;\n    const size_t z = (size_t)tgpig.z;\n    const size_t z0 = z / shape[1];\n    const size_t z1 = z % shape[1];\n\n    device const F *x = (device const F *)input_b;\n    device const F *mask = (device const F *)mask_b;\n    device F *out = (device F *)output_b;\n\n    const float scale = *scale_b;\n\n    x += row * strides[3] + h * strides[2] + z1 * strides[1] + z0 * strides[0];\n    out += row * out_strides[3] + h * out_strides[2] + z1 * out_strides[1] +\n           z0 * out_strides[0];\n\n    const bool has_mask = (mask_b != nullptr);\n    if (has_mask) {\n        mask += row * mask_strides[3] + h * mask_strides[2] +\n                z1 * mask_strides[1] + z0 * mask_strides[0];\n    }\n\n    // Threadgroup scratch layout:\n    // tgmem[0..31]                -> buf_iw (one float per simdgroup, up to 32\n    // simdgroups) tgmem[32..32+cols-1]        -> vals   (float[cols]) If you\n    // allocate nextPow2(cols) for vals, that's fine too.\n    threadgroup float *buf_iw = tgmem;\n    threadgroup float *vals = tgmem + 32;\n\n    const uint simd_size = tpsg; // usually 32 on Apple GPUs\n    const uint num_sg = (tg_sz + simd_size - 1u) / simd_size;\n\n    const size_t cols = shape[4];\n\n    // 1) Load (x*scale + mask) and compute max in float\n    float max_val = -INFINITY;\n\n    for (size_t col = (size_t)tid; col < cols; col += (size_t)tg_sz) {\n        const float xv = (float)x[col * strides[4]] * scale;\n        const float mv = has_mask ? (float)mask[col * mask_strides[4]] : 0.0f;\n        const float v = xv + mv;\n\n        vals[col] = v;\n        max_val = metal::max(max_val, v);\n    }\n\n    // reduce max across simdgroup\n    float sg_max = simd_max(max_val);\n\n    // write per-simdgroup max\n    if (lane == 0) {\n        buf_iw[sg_id] = sg_max;\n    }\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n\n    // reduce across simdgroups using first simdgroup\n    if (sg_id == 0) {\n        float x0 = (lane < num_sg) ? buf_iw[lane] : -INFINITY;\n        float block_max = simd_max(x0);\n        if (lane == 0)\n            buf_iw[0] = block_max;\n    }\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    max_val = buf_iw[0];\n\n    // 2) exp(vals - max) and sum\n    float sum = 0.0f;\n    for (size_t col = (size_t)tid; col < cols; col += (size_t)tg_sz) {\n        float e = exp(vals[col] - max_val);\n        vals[col] = e;\n        sum += e;\n    }\n\n    float sg_sum = simd_sum(sum);\n\n    if (lane == 0) {\n        buf_iw[sg_id] = sg_sum;\n    }\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n\n    if (sg_id == 0) {\n        float x0 = (lane < num_sg) ? buf_iw[lane] : 0.0f;\n        float block_sum = simd_sum(x0);\n        if (lane == 0)\n            buf_iw[0] = block_sum;\n    }\n    threadgroup_barrier(mem_flags::mem_threadgroup);\n    sum = buf_iw[0];\n\n    const float inv_sum = 1.0f / sum;\n\n    // 3) write output\n    for (size_t col = (size_t)tid; col < cols; col += (size_t)tg_sz) {\n        float y = vals[col] * inv_sum;\n        out[col * out_strides[4]] = (F)y;\n    }\n}\n\ntypedef decltype(scaled_masked_softmax_nd5<float>) scaled_masked_softmax_nd5_t;\n\ntemplate [[host_name(\"nn_ops::scaled_masked_softmax_nd5_\"\n                     \"f32\")]] [[kernel]] scaled_masked_softmax_nd5_t\n    scaled_masked_softmax_nd5<float>;\ntemplate [[host_name(\"nn_ops::scaled_masked_softmax_nd5_\"\n                     \"f16\")]] [[kernel]] scaled_masked_softmax_nd5_t\n    scaled_masked_softmax_nd5<half>;\n\nconstant float GELU_COEF_A = 0.044715f;\nconstant float SQRT_2_OVER_PI = 0.79788456080286535587989211986876f;\n\ntemplate <typename F>\n[[kernel]] void gelu_approx(device const void *input_b, device void *output_b,\n                            uint tpig [[thread_position_in_grid]]) {\n\n    device const F *input = (device const F *)input_b;\n    device F *output = (device F *)output_b;\n\n    float x = static_cast<float>(input[tpig]);\n    float output_f32 =\n        0.5 * x *\n        (1.0 +\n         precise::tanh(SQRT_2_OVER_PI * (x + GELU_COEF_A * metal::powr(x, 3))));\n    output[tpig] = static_cast<F>(output_f32);\n}\n\ntypedef decltype(gelu_approx<float>) gelu_approx_t;\n\ntemplate [[host_name(\n    \"nn_ops::gelu_approx_f32\")]] [[kernel]] gelu_approx_t gelu_approx<float>;\ntemplate [[host_name(\n    \"nn_ops::gelu_approx_f16\")]] [[kernel]] gelu_approx_t gelu_approx<half>;\n\ntemplate <typename F>\n[[kernel]] void gelu_approx_fast(device const void *input_b,\n                                 device void *output_b,\n                                 uint tpig [[thread_position_in_grid]]) {\n\n    device const F *input = (device const F *)input_b;\n    device F *output = (device F *)output_b;\n\n    float x = static_cast<float>(input[tpig]);\n    float output_f32 =\n        0.5 * x *\n        (1.0 +\n         precise::tanh(SQRT_2_OVER_PI * (x + GELU_COEF_A * metal::powr(x, 2))));\n    output[tpig] = static_cast<F>(output_f32);\n}\n\ntypedef decltype(gelu_approx_fast<float>) gelu_approx_fast_t;\n\ntemplate\n    [[host_name(\"nn_ops::gelu_approx_fast_f32\")]] [[kernel]] gelu_approx_fast_t\n        gelu_approx_fast<float>;\ntemplate\n    [[host_name(\"nn_ops::gelu_approx_fast_f16\")]] [[kernel]] gelu_approx_fast_t\n        gelu_approx_fast<half>;\n\ntemplate <typename T>\n[[kernel]] void apply_rope_nd2(device const void *input_b [[buffer(0)]],\n                               device const void *cos_b [[buffer(1)]],\n                               device const void *sin_b [[buffer(2)]],\n                               device void *output_b [[buffer(3)]],\n                               constant const size_t *shape [[buffer(4)]],\n                               constant const size_t *strides [[buffer(5)]],\n                               constant const size_t *cos_sin_strides\n                               [[buffer(6)]],\n                               constant const size_t *out_strides [[buffer(7)]],\n                               uint2 tpig [[thread_position_in_grid]]) {\n    device const T *input = (device const T *)input_b;\n    device const T *cos = (device const T *)cos_b;\n    device const T *sin = (device const T *)sin_b;\n\n    device T *output = (device T *)output_b;\n\n    uint2 rotated_tpig = tpig;\n    rotated_tpig.x += shape[1] / 2;\n\n    auto idx = indices_to_idx_2(tpig, strides);\n    auto rot_idx = indices_to_idx_2(rotated_tpig, strides);\n    auto out_idx = indices_to_idx_2(tpig, out_strides);\n    auto out_rot_idx = indices_to_idx_2(rotated_tpig, out_strides);\n\n    auto cos_sin_idx = indices_to_idx_2(tpig, cos_sin_strides);\n    auto rot_cos_sin_idx = indices_to_idx_2(rotated_tpig, cos_sin_strides);\n\n    output[out_idx] =\n        input[idx] * cos[cos_sin_idx] - input[rot_idx] * sin[cos_sin_idx];\n    output[out_rot_idx] = input[rot_idx] * cos[rot_cos_sin_idx] +\n                          input[idx] * sin[rot_cos_sin_idx];\n}\n\ntemplate <typename T>\n[[kernel]] void apply_rope_nd3(device const void *input_b [[buffer(0)]],\n                               device const void *cos_b [[buffer(1)]],\n                               device const void *sin_b [[buffer(2)]],\n                               device void *output_b [[buffer(3)]],\n                               constant const size_t *shape [[buffer(4)]],\n                               constant const size_t *strides [[buffer(5)]],\n                               constant const size_t *cos_sin_strides\n                               [[buffer(6)]],\n                               constant const size_t *out_strides [[buffer(7)]],\n                               uint3 tpig [[thread_position_in_grid]]) {\n    device const T *input = (device const T *)input_b;\n    device const T *cos = (device const T *)cos_b;\n    device const T *sin = (device const T *)sin_b;\n\n    device T *output = (device T *)output_b;\n\n    uint3 rotated_tpig = tpig;\n    rotated_tpig.x += shape[2] / 2;\n\n    auto idx = indices_to_idx_3(tpig, strides);\n    auto rot_idx = indices_to_idx_3(rotated_tpig, strides);\n    auto out_idx = indices_to_idx_3(tpig, out_strides);\n    auto out_rot_idx = indices_to_idx_3(rotated_tpig, out_strides);\n\n    auto cos_sin_idx = indices_to_idx_3(tpig, cos_sin_strides);\n    auto rot_cos_sin_idx = indices_to_idx_3(rotated_tpig, cos_sin_strides);\n\n    output[out_idx] =\n        input[idx] * cos[cos_sin_idx] - input[rot_idx] * sin[cos_sin_idx];\n    output[out_rot_idx] = input[rot_idx] * cos[rot_cos_sin_idx] +\n                          input[idx] * sin[rot_cos_sin_idx];\n}\n\ntemplate <typename T>\n[[kernel]] void apply_rope_nd4(device const void *input_b [[buffer(0)]],\n                               device const void *cos_b [[buffer(1)]],\n                               device const void *sin_b [[buffer(2)]],\n                               device void *output_b [[buffer(3)]],\n                               constant const size_t *shape [[buffer(4)]],\n                               constant const size_t *strides [[buffer(5)]],\n                               constant const size_t *cos_sin_strides\n                               [[buffer(6)]],\n                               constant const size_t *out_strides [[buffer(7)]],\n                               uint3 tpig [[thread_position_in_grid]]) {\n    device const T *input = (device const T *)input_b;\n    device const T *cos = (device const T *)cos_b;\n    device const T *sin = (device const T *)sin_b;\n\n    device T *output = (device T *)output_b;\n\n    uint3 rotated_tpig = tpig;\n    rotated_tpig.x += shape[3] / 2;\n\n    auto idx = indices_to_idx_4(tpig, shape, strides);\n    auto rot_idx = indices_to_idx_4(rotated_tpig, shape, strides);\n    auto out_idx = indices_to_idx_4(tpig, shape, out_strides);\n    auto out_rot_idx = indices_to_idx_4(rotated_tpig, shape, out_strides);\n\n    auto cos_sin_idx = indices_to_idx_4(tpig, shape, cos_sin_strides);\n    auto rot_cos_sin_idx =\n        indices_to_idx_4(rotated_tpig, shape, cos_sin_strides);\n\n    output[out_idx] =\n        input[idx] * cos[cos_sin_idx] - input[rot_idx] * sin[cos_sin_idx];\n    output[out_rot_idx] = input[rot_idx] * cos[rot_cos_sin_idx] +\n                          input[idx] * sin[rot_cos_sin_idx];\n}\n\ntypedef decltype(apply_rope_nd2<float>) apply_rope_nd2_t;\ntypedef decltype(apply_rope_nd3<float>) apply_rope_nd3_t;\ntypedef decltype(apply_rope_nd4<float>) apply_rope_nd4_t;\n\ntemplate [[host_name(\"nn_ops::apply_rope_nd2_f32\")]] [[kernel]] apply_rope_nd2_t\n    apply_rope_nd2<float>;\ntemplate [[host_name(\"nn_ops::apply_rope_nd3_f32\")]] [[kernel]] apply_rope_nd3_t\n    apply_rope_nd3<float>;\ntemplate [[host_name(\"nn_ops::apply_rope_nd4_f32\")]] [[kernel]] apply_rope_nd4_t\n    apply_rope_nd4<float>;\n\ntemplate [[host_name(\"nn_ops::apply_rope_nd2_f16\")]] [[kernel]] apply_rope_nd2_t\n    apply_rope_nd2<half>;\ntemplate [[host_name(\"nn_ops::apply_rope_nd3_f16\")]] [[kernel]] apply_rope_nd3_t\n    apply_rope_nd3<half>;\ntemplate [[host_name(\"nn_ops::apply_rope_nd4_f16\")]] [[kernel]] apply_rope_nd4_t\n    apply_rope_nd4<half>;\n"
  },
  {
    "path": "metal/src/kernels/nn/reduce.rs",
    "content": "use crate::LibraryName;\nuse crate::encoder::EncoderExt;\nuse crate::kernels::utils;\nuse metal::MTLSize;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\npub use tract_gpu::ops::reduce::Reducer;\n\npub fn kernel_name(reducer: &Reducer, dt: DatumType) -> TractResult<String> {\n    ensure!(reducer.is_supported_dt(dt), \"Unsupported dt {dt:?} for metal reduceop {reducer:?}\",);\n    let tname = DeviceTensor::tname(dt)?;\n    Ok(format!(\"nn_ops::reduce_{}_nd3_{tname}\", reducer))\n}\n\npub fn metal_reduce_launch(\n    reducer: &Reducer,\n    input: &DeviceTensor,\n    axis: usize,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| {\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n\n        ensure!(output.datum_type() == input.datum_type());\n        ensure!(output.shape()[axis] == 1);\n\n        let input_shape_nd3 = utils::reshape_to_rank_3(input.shape(), axis);\n        let input_strides_nd3 = Tensor::natural_strides(&input_shape_nd3);\n        let output_shape_nd3 = utils::reshape_to_rank_3(output.shape(), axis);\n        let output_strides_nd3 = Tensor::natural_strides(&output_shape_nd3);\n\n        let pipeline =\n            stream.load_pipeline(LibraryName::NNOps, &kernel_name(reducer, input.datum_type())?)?;\n\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, output, metal::MTLResourceUsage::Write);\n            encoder.set_slice(2, &input_shape_nd3);\n            encoder.set_slice(3, &input_strides_nd3);\n            encoder.set_slice(4, &output_strides_nd3);\n\n            let grid_size = utils::build_metal_size_for_shape(&output_shape_nd3);\n            let group_size =\n                MTLSize { width: usize::min(32, input_shape_nd3[1]) as _, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    })\n}\n\ncrate::register_metal_op!(tract_core::ops::nn::Reduce, |source, node, op| {\n    let dt = source.node_input_facts(node.id)?[0].datum_type;\n    if let Ok(gpu_op) =\n        tract_gpu::ops::reduce::GpuReduce::from_tract_core(op, \"Metal\", metal_reduce_launch)\n    {\n        rule_if!(gpu_op.reducer.is_supported_dt(dt));\n        return Ok(Some(Box::new(gpu_op)));\n    }\n    Ok(None)\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::utils::with_borrowed_metal_stream;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n    use tract_core::ops::nn::Reducer as TractReducer;\n    use tract_core::tract_data::itertools::Itertools;\n    use tract_gpu::tensor::IntoDevice;\n\n    fn test_case<F>(\n        reducer: Reducer,\n        tract_reducer: TractReducer,\n        shape: &[usize],\n        axis: usize,\n        scale: f32,\n    ) -> TractResult<()>\n    where\n        F: Float + Datum,\n        usize: AsPrimitive<f32>,\n        f32: AsPrimitive<F>,\n    {\n        with_borrowed_metal_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v * scale).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu_output = tract_reducer.reduce(&[axis], &a.to_host()?.into_tensor())?;\n            let mut o_shape = a.shape().to_vec();\n            o_shape[axis] = 1;\n            let output = unsafe { DeviceTensor::uninitialized_dt(a.datum_type(), &o_shape)? };\n            metal_reduce_launch(&reducer, &a, axis, &output)?;\n            stream.wait_until_completed()?;\n            let metal_output = output;\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), Approximation::Approximate)\n                .with_context(|| {\n                    format!(\n                        \"A: {:?}, scale: {:?} Cpu: {:?}, Metal: {:?}\",\n                        a.to_host().and_then(|it| it.dump(true)),\n                        scale,\n                        cpu_output.dump(true),\n                        metal_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_reduce_mean_of_squares() -> TractResult<()> {\n        test_case::<f32>(Reducer::MeanOfSquares, TractReducer::MeanOfSquares, &[4, 4], 1, 1.0)?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[4, 4],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[1, 10],\n            0,\n            1.0 / 100.0,\n        )?;\n        test_case::<f32>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[1, 10],\n            0,\n            1.0 / 100.0,\n        )?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 1],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f32>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 1],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 2, 82, 38],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f16>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 2, 82, 38],\n            2,\n            1.0 / 100.0,\n        )?;\n        test_case::<f32>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 2, 82, 38],\n            1,\n            1.0 / 100.0,\n        )?;\n        test_case::<f32>(\n            Reducer::MeanOfSquares,\n            TractReducer::MeanOfSquares,\n            &[2, 2, 82, 38],\n            2,\n            1.0 / 100.0,\n        )?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_reduce_sum() -> TractResult<()> {\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[4, 4], 1, 1.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[4, 4], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Sum, TractReducer::Sum, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Sum, TractReducer::Sum, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_reduce_prod() -> TractResult<()> {\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[4, 4], 1, 1.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[4, 4], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Prod, TractReducer::Prod, &[2, 2, 82, 38], 2, 1.0 / 100000.0)?;\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Prod, TractReducer::Prod, &[2, 2, 82, 38], 2, 1.0 / 1000.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_reduce_max() -> TractResult<()> {\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[4, 4], 1, 1.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[4, 4], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[1, 10], 0, -1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[2, 1], 1, -1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[2, 2, 82, 38], 1, -1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Max, TractReducer::Max, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[2, 2, 82, 38], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Max, TractReducer::Max, &[2, 2, 82, 38], 2, -1.0 / 100.0)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_reduce_min() -> TractResult<()> {\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[4, 4], 1, 1.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[4, 4], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[1, 10], 0, -1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[1, 10], 0, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[2, 1], 1, 1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[2, 2, 82, 38], 1, -1.0 / 100.0)?;\n        test_case::<f16>(Reducer::Min, TractReducer::Min, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[2, 2, 82, 38], 1, -1.0 / 100.0)?;\n        test_case::<f32>(Reducer::Min, TractReducer::Min, &[2, 2, 82, 38], 2, 1.0 / 100.0)?;\n        Ok(())\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn reduce_prop_f32(pb in any::<ReduceProblem<f32>>()) {\n            fn run(pb: ReduceProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn reduce_prop_f16(pb in any::<ReduceProblem<f16>>()) {\n            fn run(pb: ReduceProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct ReduceProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        pub op: Reducer,\n        pub shape: Vec<usize>,\n        pub axis: usize,\n        pub input: Vec<F>,\n    }\n\n    impl<F> Arbitrary for ReduceProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            let reducers = Reducer::ALL.into_iter().filter(|r| !r.is_logic()).collect_vec();\n            (0..reducers.len(), 0usize..3, 0usize..3)\n                .prop_flat_map(move |(op_idx, left, right)| {\n                    let axis = left;\n                    let shape_len = usize::min(left + right + 1, 4);\n                    let shape = 1usize..10;\n                    let op = reducers[op_idx];\n                    (Just(op), vec(shape, shape_len..=shape_len), Just(axis))\n                })\n                .prop_map(|(op, shape, axis)| {\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { op, shape, axis, input }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> ReduceProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n            let cpu_output = match self.op {\n                Reducer::Sum => TractReducer::Sum.reduce(&[self.axis], &a)?,\n                Reducer::Prod => TractReducer::Prod.reduce(&[self.axis], &a)?,\n                Reducer::MeanOfSquares => TractReducer::MeanOfSquares.reduce(&[self.axis], &a)?,\n                Reducer::Min => TractReducer::Min.reduce(&[self.axis], &a)?,\n                Reducer::Max => TractReducer::Max.reduce(&[self.axis], &a)?,\n                _ => unreachable!(),\n            };\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            with_borrowed_metal_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let mut o_shape = a.shape().to_vec();\n                o_shape[self.axis] = 1;\n                let output = unsafe { DeviceTensor::uninitialized_dt(a.datum_type(), &o_shape)? };\n                metal_reduce_launch(&self.op, &a, self.axis, &output)?;\n                stream.wait_until_completed()?;\n                Ok(output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/nn/rms_norm.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::kernels::utils;\nuse crate::{LibraryName, MetalStream};\nuse metal::MTLSize;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct RmsNorm;\n\nimpl RmsNorm {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType, is_l4: bool) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for metal rmsop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        if !is_l4 {\n            Ok(format!(\"nn_ops::rms_norm_nd3_{tname}\"))\n        } else {\n            Ok(format!(\"nn_ops::rms_norm_nd2_l4_{tname}\"))\n        }\n    }\n\n    pub fn eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        axis: usize,\n        eps: &Tensor,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, axis, eps, &output)?;\n        stream.wait_until_completed()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        axis: usize,\n        eps: &Tensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n\n        ensure!(output.shape() == input.shape());\n        ensure!(output.datum_type() == input.datum_type());\n\n        if (axis == (input.rank() - 1)) && (input.shape()[axis] % 4 == 0) {\n            let shape = input.shape();\n            let shape_nd2 = tvec![shape[..axis].iter().product::<usize>(), shape[axis]];\n\n            let pipeline = stream\n                .load_pipeline(LibraryName::NNOps, &self.kernel_name(input.datum_type(), true)?)?;\n\n            let iter_dim = shape_nd2[1];\n            let iter_dim_div_4 = iter_dim / 4;\n            let outer_stride = iter_dim * input.datum_type().size_of();\n\n            let mut nthreads = 32;\n            let limit = iter_dim_div_4.min(pipeline.max_total_threads_per_threadgroup() as usize);\n\n            while (nthreads * 2) < limit {\n                nthreads *= 2;\n            }\n            nthreads = nthreads.min(iter_dim_div_4);\n            let command_buffer = stream.command_buffer();\n            command_buffer.encode(|encoder| {\n                encoder.set_compute_pipeline_state(&pipeline);\n                encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n                encoder.set_tensor(1, eps);\n                encoder.set_metal_tensor(2, output, metal::MTLResourceUsage::Write);\n                encoder.set_bytes(\n                    3,\n                    std::mem::size_of::<usize>() as u64,\n                    &iter_dim as *const usize as *const _,\n                );\n                encoder.set_bytes(\n                    4,\n                    std::mem::size_of::<usize>() as u64,\n                    &iter_dim_div_4 as *const usize as *const _,\n                );\n                encoder.set_bytes(\n                    5,\n                    std::mem::size_of::<usize>() as u64,\n                    &outer_stride as *const usize as *const _,\n                );\n                encoder.set_threadgroup_memory_length(0, 32 * std::mem::size_of::<f32>() as u64);\n                let grid_size = MTLSize { width: shape_nd2[0] as _, height: 1, depth: 1 };\n                let group_size = MTLSize { width: nthreads as _, height: 1, depth: 1 };\n\n                encoder.dispatch_thread_groups(grid_size, group_size);\n            });\n        } else {\n            let shape_nd3 = utils::reshape_to_rank_3(input.shape(), axis);\n            let strides_nd3 = Tensor::natural_strides(&shape_nd3);\n\n            let pipeline = stream\n                .load_pipeline(LibraryName::NNOps, &self.kernel_name(input.datum_type(), false)?)?;\n\n            let iter_dim = shape_nd3[1];\n\n            let mut nthreads = 32;\n            let limit = iter_dim.min(pipeline.max_total_threads_per_threadgroup() as usize);\n            while (nthreads * 2) < limit {\n                nthreads *= 2;\n            }\n\n            let command_buffer = stream.command_buffer();\n            command_buffer.encode(|encoder| {\n                encoder.set_compute_pipeline_state(&pipeline);\n                encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n                encoder.set_tensor(1, eps);\n                encoder.set_metal_tensor(2, output, metal::MTLResourceUsage::Write);\n                encoder.set_slice(3, &shape_nd3);\n                encoder.set_slice(4, &strides_nd3);\n                encoder.set_threadgroup_memory_length(0, 32 * std::mem::size_of::<f32>() as u64);\n                let grid_size =\n                    MTLSize { width: (shape_nd3[2] * shape_nd3[0]) as _, height: 1, depth: 1 };\n                let group_size = MTLSize { width: nthreads as _, height: 1, depth: 1 };\n\n                encoder.dispatch_thread_groups(grid_size, group_size);\n            });\n        }\n        Ok(())\n    }\n}\n\npub fn metal_rms_norm_dispatch(\n    input: &DeviceTensor,\n    axis: usize,\n    eps: &Tensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| RmsNorm.dispatch_eval(stream, input, axis, eps, output))\n}\n\ncrate::register_metal_op!(tract_transformers::ops::rms_norm::RmsNorm, |source, node, op| {\n    rule_if!(RmsNorm::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::rms_norm::GpuRmsNorm::new(\n        op.axis,\n        op.eps.clone(),\n        \"Metal\",\n        metal_rms_norm_dispatch,\n    ))))\n});\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::with_borrowed_metal_stream;\n    use tract_gpu::tensor::IntoDevice;\n\n    use super::*;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n    use tract_transformers::ops::rms_norm;\n\n    fn test_case<F>(shape: &[usize], axis: usize, offset: f32, scale: f32) -> TractResult<()>\n    where\n        F: Float + Datum,\n        usize: AsPrimitive<f32>,\n        f32: AsPrimitive<F>,\n    {\n        with_borrowed_metal_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v * scale + offset).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let eps = Arc::new(tensor0(0.0001f32));\n            let cpu_rms = rms_norm::RmsNorm { axis, eps: Arc::clone(&eps) };\n\n            let cpu_output =\n                cpu_rms.eval(tvec![a.to_host()?.into_tvalue()])?[0].clone().into_tensor();\n            let metal_output = RmsNorm.eval(stream, &a, axis, &eps)?;\n\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), Approximation::Approximate)\n                .with_context(|| {\n                    format!(\n                        \"Input: {:?}, scale: {:?} Cpu: {:?}, Metal: {:?}\",\n                        a.to_host().and_then(|it| it.dump(true)),\n                        scale,\n                        cpu_output.dump(true),\n                        metal_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_rms() -> TractResult<()> {\n        test_case::<f32>(&[4, 4], 1, -8.0, 1.0 / 100.0)?;\n        test_case::<f16>(&[4, 4], 1, -8.0, 1.0 / 100.0)?;\n        Ok(())\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn rms_prop_f32(pb in any::<RmsNormProblem<f32>>()) {\n            fn run(pb: RmsNormProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn rms_prop_f16(pb in any::<RmsNormProblem<f16>>()) {\n            fn run(pb: RmsNormProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct RmsNormProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub shape: Vec<usize>,\n        pub axis: usize,\n        pub input: Vec<F>,\n        pub eps: Arc<Tensor>,\n    }\n\n    impl<F> Arbitrary for RmsNormProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            (0usize..3, 0usize..3)\n                .prop_flat_map(|(left, right)| {\n                    let axis = left;\n                    let shape_len = usize::min(left + right, 4);\n                    let iter_ax_dim = 1usize..1024;\n                    let other_dim = 1usize..10;\n                    (iter_ax_dim, vec(other_dim, shape_len..=shape_len), Just(axis))\n                })\n                .prop_map(|(iter_dim, mut shape, axis)| {\n                    shape.insert(axis, iter_dim);\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { shape, axis, input, eps: Arc::new(tensor0(0.0001f32)) }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> RmsNormProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n\n            let cpu_rms = rms_norm::RmsNorm { axis: self.axis, eps: Arc::clone(&self.eps) };\n\n            let cpu_output = cpu_rms.eval(tvec![a.into_tvalue()])?[0].clone().into_tensor();\n\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            with_borrowed_metal_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let metal_output = RmsNorm.eval(stream, &a, self.axis, &self.eps)?;\n                Ok(metal_output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/nn/scaled_masked_softmax.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::kernels::utils::compute_broadcast_strides;\nuse crate::{LibraryName, MetalStream};\nuse metal::MTLSize;\nuse num_traits::AsPrimitive;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct ScaledMaskedSoftmax;\n\nimpl ScaledMaskedSoftmax {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType) -> TractResult<String> {\n        ensure!(\n            Self::is_supported_dt(dt),\n            \"Unsupported dt {:?} for metal scaled masked softmaxop\",\n            dt\n        );\n        let tname = DeviceTensor::tname(dt)?;\n        Ok(format!(\"nn_ops::scaled_masked_softmax_nd5_{tname}\"))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        scale: &Tensor,\n        mask: &DeviceTensor,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, scale, mask, &output)?;\n        stream.wait_until_completed()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        scale: &Tensor,\n        mask: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        stream.retain_tensor(input);\n        stream.retain_tensor(mask);\n        stream.retain_tensor(output);\n\n        ensure!(output.shape() == input.shape());\n        ensure!(input.rank() >= 2);\n        ensure!(input.rank() <= 5);\n        ensure!(mask.rank() == input.rank());\n        ensure!(output.datum_type() == input.datum_type());\n        ensure!(mask.datum_type() == input.datum_type());\n        let scale = scale.cast_to::<f32>()?;\n\n        let shape = pad(input.shape(), 1);\n        let strides = pad(input.strides(), 0);\n        let mask_strides =\n            pad(&compute_broadcast_strides::<usize>(mask.shape(), mask.strides())?, 0);\n        let out_strides = pad(output.strides(), 0);\n\n        let inner_len = shape[4] as usize;\n        let mut nth = 32usize;\n        while nth < inner_len && nth < 256 {\n            nth *= 2;\n        }\n\n        let tg_floats = 32 + inner_len;\n        let tg_bytes = tg_floats * f32::datum_type().size_of();\n\n        let pipeline =\n            stream.load_pipeline(LibraryName::NNOps, &self.kernel_name(input.datum_type())?)?;\n\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, mask, metal::MTLResourceUsage::Read);\n            encoder.set_tensor(2, &scale);\n            encoder.set_metal_tensor(3, output, metal::MTLResourceUsage::Write);\n            encoder.set_slice(4, &shape);\n            encoder.set_slice(5, &strides);\n            encoder.set_slice(6, &mask_strides);\n            encoder.set_slice(7, &out_strides);\n            encoder.set_threadgroup_memory_length(0, tg_bytes as _);\n            let grid_size = MTLSize {\n                width: shape[3] as _,\n                height: shape[2] as _,\n                depth: (shape[0] * shape[1]) as _,\n            };\n            let group_size = MTLSize { width: nth as _, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n}\n\nfn pad(vals: &[impl AsPrimitive<isize>], neutral: isize) -> [isize; 5] {\n    let mut it = [neutral; 5];\n    for (ix, val) in vals.iter().enumerate() {\n        it[ix + 5 - vals.len()] = val.as_();\n    }\n    it\n}\n\npub fn metal_scaled_masked_softmax_dispatch(\n    input: &DeviceTensor,\n    scale: &Tensor,\n    mask: &DeviceTensor,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| {\n        ScaledMaskedSoftmax.dispatch_eval(stream, input, scale, mask, output)\n    })\n}\n\ncrate::register_metal_op!(\n    tract_transformers::ops::scaled_masked_softmax::ScaledMaskedSoftmax,\n    |source, node, op| {\n        rule_if!(!op.post_softmax_mask);\n        rule_if!(ScaledMaskedSoftmax::is_supported_dt(\n            source.node_input_facts(node.id)?[0].datum_type\n        ));\n        Ok(Some(Box::new(tract_gpu::ops::scaled_masked_softmax::GpuScaledMaskedSoftmax::new(\n            op.scale.clone(),\n            \"Metal\",\n            metal_scaled_masked_softmax_dispatch,\n        ))))\n    }\n);\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::with_borrowed_metal_stream;\n    use tract_gpu::tensor::IntoDevice;\n\n    use super::*;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use proptest::strategy::Strategy;\n    use tract_core::internal::Tensor;\n    use tract_transformers::ops::scaled_masked_softmax;\n\n    #[test]\n    fn test_scaled_masked_softmax_f32() -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let m = 4;\n            let n = 4;\n            let scale: Arc<_> = tensor0(0.125f32).into();\n            let mask = Tensor::from_shape(&[1, 1, m, n], &vec![-1000f32; m * n])?.into_device()?;\n\n            let a = Tensor::from_shape(\n                &[1, 1, m, n],\n                &(0..m * n).map(|f| f as f32).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu = scaled_masked_softmax::ScaledMaskedSoftmax {\n                scale: scale.clone(),\n                post_softmax_mask: false,\n            };\n\n            let cpu_output = cpu\n                .eval(tvec![a.to_host()?.into_tvalue(), mask.to_host()?.into_tvalue()])?[0]\n                .clone()\n                .into_tensor();\n            let metal_output = ScaledMaskedSoftmax.eval(stream, &a, &scale, &mask)?;\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_scaled_masked_softmax_f32_2() -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let m = 4;\n            let n = 1024;\n            let scale: Arc<_> = tensor0(0.125f32).into();\n            let mask = Tensor::from_shape(&[1, 1, m, n], &vec![-1000f32; m * n])?.into_device()?;\n\n            let a = Tensor::from_shape(\n                &[1, 1, m, n],\n                &(0..m * n).map(|f| f as f32).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu = scaled_masked_softmax::ScaledMaskedSoftmax {\n                scale: scale.clone(),\n                post_softmax_mask: false,\n            };\n\n            let cpu_output = cpu\n                .eval(tvec![a.to_host()?.into_tvalue(), mask.to_host()?.into_tvalue()])?[0]\n                .clone()\n                .into_tensor();\n            let metal_output = ScaledMaskedSoftmax.eval(stream, &a, &scale, &mask)?;\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn scaled_masked_softmax_prop_f32(pb in any::<ScaledMaskedSoftmaxProblem<f32>>()) {\n            fn run(pb: ScaledMaskedSoftmaxProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn scaled_masked_softmax_prop_f16(pb in any::<ScaledMaskedSoftmaxProblem<f16>>()) {\n            fn run(pb: ScaledMaskedSoftmaxProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct ScaledMaskedSoftmaxProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        pub shape: Vec<usize>,\n        pub mask_shape: Vec<usize>,\n        pub input: Vec<F>,\n        pub mask: Vec<F>,\n    }\n\n    impl<F> Arbitrary for ScaledMaskedSoftmaxProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            vec(1usize..10, 4..=4)\n                .prop_map(|shape| {\n                    let mut mask_shape = shape.clone();\n                    mask_shape[0] = 1;\n                    mask_shape[1] = 1;\n\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n\n                    let mask = (0..mask_shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { shape, input, mask_shape, mask }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> ScaledMaskedSoftmaxProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n            let mask = Tensor::from_shape(self.mask_shape.as_slice(), &self.mask)?;\n            let scale: Arc<_> = tensor0::<F>(0.125f32.as_()).into();\n\n            let cpu_output =\n                scaled_masked_softmax::ScaledMaskedSoftmax { scale, post_softmax_mask: false }\n                    .eval(tvec![a.into_tvalue(), mask.into_tvalue()])?[0]\n                    .clone()\n                    .into_tensor();\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            with_borrowed_metal_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let mask =\n                    Tensor::from_shape(self.mask_shape.as_slice(), &self.mask)?.into_device()?;\n                let scale: Arc<_> = tensor0::<F>(0.125f32.as_()).into();\n                let metal_output = ScaledMaskedSoftmax.eval(stream, &a, &scale, &mask)?;\n                Ok(metal_output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/nn/silu.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::{LibraryName, MetalStream};\nuse metal::MTLSize;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct Silu;\n\nimpl Silu {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType, use_silu_4: bool) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for metal siluop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        if use_silu_4 {\n            Ok(format!(\"nn_ops::silu_4_{tname}\"))\n        } else {\n            Ok(format!(\"nn_ops::silu_{tname}\"))\n        }\n    }\n\n    pub fn eval(&self, stream: &MetalStream, input: &DeviceTensor) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, &output)?;\n        stream.wait_until_completed()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n\n        ensure!(output.shape() == input.shape());\n        ensure!(output.datum_type() == input.datum_type());\n\n        let n_el = output.len();\n\n        let use_silu_4 = (n_el % 4 == 0) && (n_el as f32 > 2f32.powi(12));\n        let kernel_name = self.kernel_name(input.datum_type(), use_silu_4)?;\n\n        let n_threads = if use_silu_4 { n_el / 4 } else { n_el };\n        let pipeline = stream.load_pipeline(LibraryName::NNOps, &kernel_name)?;\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, output, metal::MTLResourceUsage::Write);\n\n            let grid_size = MTLSize { width: n_threads as _, height: 1, depth: 1 };\n            let group_size = MTLSize { width: 1, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::utils::with_borrowed_metal_stream;\n    use tract_gpu::tensor::IntoDevice;\n\n    use super::*;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n\n    fn test_case<F>(\n        shape: &[usize],\n        offset: f32,\n        scale: f32,\n        approximate: Approximation,\n    ) -> TractResult<()>\n    where\n        F: Float + Datum,\n        usize: AsPrimitive<f32>,\n        f32: AsPrimitive<F>,\n    {\n        with_borrowed_metal_stream(|stream| {\n            let len = shape.iter().product::<usize>();\n\n            let a = Tensor::from_shape(\n                shape,\n                &(0..len)\n                    .map(|f| -> F {\n                        let v: f32 = f.as_();\n                        (v * scale + offset).as_()\n                    })\n                    .collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu_output = tract_core::ops::nn::silu::silu()\n                .eval(tvec![a.to_host()?.into_tvalue()])?[0]\n                .clone()\n                .into_tensor();\n            let metal_output = Silu.eval(stream, &a)?;\n\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), approximate)\n                .with_context(|| {\n                    format!(\n                        \"Input: {:?}, scale: {:?} Cpu: {:?}, Metal: {:?}\",\n                        a.to_host().and_then(|it| it.dump(true)),\n                        scale,\n                        cpu_output.dump(true),\n                        metal_output.to_host().and_then(|it| it.dump(true))\n                    )\n                })?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_silu() -> TractResult<()> {\n        test_case::<f32>(&[4, 4], -0.0, 1.0 / 100.0, Approximation::Approximate)?;\n        test_case::<f16>(&[4, 4], -6.0, 1.0 / 1000.0, Approximation::SuperApproximate)?;\n        Ok(())\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn silu_prop_f32(pb in any::<SiluProblem<f32>>()) {\n            fn run(pb: SiluProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn silu_prop_f16(pb in any::<SiluProblem<f16>>()) {\n            fn run(pb: SiluProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct SiluProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub shape: Vec<usize>,\n        pub input: Vec<F>,\n    }\n\n    impl<F> Arbitrary for SiluProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            (0usize..3, 0usize..3)\n                .prop_flat_map(|(left, right)| {\n                    let shape_len = usize::min(left + right + 1, 4);\n                    let shape = 1usize..10;\n                    vec(shape, shape_len..=shape_len)\n                })\n                .prop_map(|shape| {\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { shape, input }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> SiluProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n        f32: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n            let silu = tract_core::ops::nn::silu::silu();\n            let cpu_output = silu.eval(tvec![a.into_tvalue()])?[0].clone().into_tensor();\n\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            with_borrowed_metal_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let metal_output = Silu.eval(stream, &a)?;\n                Ok(metal_output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/nn/softmax.rs",
    "content": "use crate::encoder::EncoderExt;\nuse crate::kernels::utils;\nuse crate::{LibraryName, MetalStream};\nuse metal::MTLSize;\nuse tract_core::internal::*;\nuse tract_gpu::tensor::DeviceTensor;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct Softmax;\n\nimpl Softmax {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n\n    pub fn kernel_name(&self, dt: DatumType) -> TractResult<String> {\n        ensure!(Self::is_supported_dt(dt), \"Unsupported dt {:?} for metal softmaxop\", dt);\n        let tname = DeviceTensor::tname(dt)?;\n        Ok(format!(\"nn_ops::softmax_nd3_{tname}\"))\n    }\n\n    pub fn eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        axis: usize,\n    ) -> TractResult<DeviceTensor> {\n        let output = unsafe { DeviceTensor::uninitialized_dt(input.datum_type(), input.shape())? };\n        self.dispatch_eval(stream, input, axis, &output)?;\n        stream.wait_until_completed()?;\n        Ok(output)\n    }\n\n    pub fn dispatch_eval(\n        &self,\n        stream: &MetalStream,\n        input: &DeviceTensor,\n        axis: usize,\n        output: &DeviceTensor,\n    ) -> TractResult<()> {\n        stream.retain_tensor(input);\n        stream.retain_tensor(output);\n\n        ensure!(output.shape() == input.shape());\n        ensure!(output.datum_type() == input.datum_type());\n\n        let shape_nd3 = utils::reshape_to_rank_3(input.shape(), axis);\n        let strides_nd3 = Tensor::natural_strides(&shape_nd3);\n\n        let pipeline =\n            stream.load_pipeline(LibraryName::NNOps, &self.kernel_name(input.datum_type())?)?;\n\n        let command_buffer = stream.command_buffer();\n        command_buffer.encode(|encoder| {\n            encoder.set_compute_pipeline_state(&pipeline);\n            encoder.set_metal_tensor(0, input, metal::MTLResourceUsage::Read);\n            encoder.set_metal_tensor(1, output, metal::MTLResourceUsage::Write);\n            encoder.set_slice(2, &shape_nd3);\n            encoder.set_slice(3, &strides_nd3);\n\n            let grid_size =\n                MTLSize { width: shape_nd3[2] as _, height: 1, depth: shape_nd3[0] as _ };\n            let group_size =\n                MTLSize { width: usize::min(32, shape_nd3[1]) as _, height: 1, depth: 1 };\n            encoder.dispatch_thread_groups(grid_size, group_size);\n        });\n        Ok(())\n    }\n}\n\npub fn metal_softmax_dispatch(\n    input: &DeviceTensor,\n    axis: usize,\n    output: &DeviceTensor,\n) -> TractResult<()> {\n    crate::with_metal_stream(|stream| Softmax.dispatch_eval(stream, input, axis, output))\n}\n\ncrate::register_metal_op!(tract_core::ops::nn::Softmax, |source, node, op| {\n    rule_if!(Softmax::is_supported_dt(source.node_input_facts(node.id)?[0].datum_type));\n    Ok(Some(Box::new(tract_gpu::ops::softmax::GpuSoftmax::from_tract_core(\n        op,\n        \"Metal\",\n        metal_softmax_dispatch,\n    )?)))\n});\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::utils::with_borrowed_metal_stream;\n    use derive_new::new;\n    use num_traits::AsPrimitive;\n    use num_traits::Float;\n    use proptest::collection::vec;\n    use proptest::prelude::*;\n    use tract_core::internal::Tensor;\n    use tract_core::ops::nn::Softmax as TractSoftmax;\n    use tract_core::ops::nn::{SoftmaxExp, SoftmaxKind};\n    use tract_gpu::tensor::IntoDevice;\n\n    #[test]\n    fn test_softmax_f32() -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let m = 4;\n            let k = 4;\n            let axis = 1;\n\n            let a = Tensor::from_shape(&[m, k], &(0..m * k).map(|f| f as f32).collect::<Vec<_>>())?\n                .into_device()?;\n\n            let cpu_softmax = TractSoftmax {\n                axes: tvec![axis],\n                quant_output_dt: None,\n                kind: SoftmaxKind::Softmax(SoftmaxExp::Libc),\n            };\n\n            let cpu_output =\n                cpu_softmax.eval(tvec![a.to_host()?.into_tvalue()])?[0].clone().into_tensor();\n            let metal_output = Softmax.eval(stream, &a, axis)?;\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_softmax_f32_2() -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let shape = [8, 4, 3];\n            let num_elements = shape.iter().product();\n            let axis = 0;\n\n            let a = Tensor::from_shape(\n                &shape,\n                &(0..num_elements).map(|f| f as f32 / 1000.0).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu_softmax = TractSoftmax {\n                axes: tvec![axis],\n                quant_output_dt: None,\n                kind: SoftmaxKind::Softmax(SoftmaxExp::Libc),\n            };\n\n            let cpu_output =\n                cpu_softmax.eval(tvec![a.to_host()?.into_tvalue()])?[0].clone().into_tensor();\n            let metal_output = Softmax.eval(stream, &a, axis)?;\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_softmax_f16() -> TractResult<()> {\n        with_borrowed_metal_stream(|stream| {\n            let m = 4;\n            let k = 4;\n            let axis = 1;\n\n            let a = Tensor::from_shape(\n                &[m, k],\n                &(0..m * k).map(|f| -> f16 { f.as_() }).collect::<Vec<_>>(),\n            )?\n            .into_device()?;\n\n            let cpu_softmax = TractSoftmax {\n                axes: tvec![axis],\n                quant_output_dt: None,\n                kind: SoftmaxKind::Softmax(SoftmaxExp::Libc),\n            };\n\n            let cpu_output =\n                cpu_softmax.eval(tvec![a.to_host()?.into_tvalue()])?[0].clone().into_tensor();\n            let metal_output = Softmax.eval(stream, &a, axis)?;\n            cpu_output\n                .close_enough(&metal_output.to_host()?.into_tensor(), Approximation::Approximate)?;\n            Ok(())\n        })\n    }\n\n    proptest::proptest! {\n        #[test]\n        fn softmax_prop_f32(pb in any::<SoftmaxProblem<f32>>()) {\n            fn run(pb: SoftmaxProblem<f32>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n\n        #[test]\n        fn softmax_prop_f16(pb in any::<SoftmaxProblem<f16>>()) {\n            fn run(pb: SoftmaxProblem<f16>) -> TractResult<()> {\n                let out = pb.run()?;\n                let reference = pb.reference()?;\n\n                out.close_enough(&reference, Approximation::Approximate)\n                   .with_context(|| format!(\"Cpu: {:?}, Metal: {:?}\", reference.dump(true), out.dump(true)))\n            }\n\n            run(pb).map_err(|e| TestCaseError::Fail(format!(\"{:?}\", e).into()))?;\n        }\n    }\n\n    #[derive(Debug, new)]\n    pub struct SoftmaxProblem<F: Datum + Float>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        pub shape: Vec<usize>,\n        pub axis: usize,\n        pub input: Vec<F>,\n    }\n\n    impl<F> Arbitrary for SoftmaxProblem<F>\n    where\n        F: Datum + Float,\n        usize: AsPrimitive<F>,\n    {\n        type Parameters = ();\n        type Strategy = BoxedStrategy<Self>;\n\n        fn arbitrary_with(_: ()) -> Self::Strategy {\n            (0usize..3, 0usize..3)\n                .prop_flat_map(|(left, right)| {\n                    let axis = left;\n                    let shape_len = usize::min(left + right + 1, 4);\n                    let shape = 1usize..10;\n                    (vec(shape, shape_len..=shape_len), Just(axis))\n                })\n                .prop_map(|(shape, axis)| {\n                    let input = (0..shape.iter().product::<usize>())\n                        .map(|f| f.as_() / 1000.as_())\n                        .collect::<Vec<_>>();\n                    Self { shape, axis, input }\n                })\n                .boxed()\n        }\n    }\n\n    impl<F> SoftmaxProblem<F>\n    where\n        F: Datum + Float + std::ops::AddAssign,\n        usize: AsPrimitive<F>,\n    {\n        pub fn reference(&self) -> TractResult<Tensor> {\n            let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?;\n\n            let cpu_softmax = TractSoftmax {\n                axes: tvec![self.axis],\n                quant_output_dt: None,\n                kind: SoftmaxKind::Softmax(SoftmaxExp::Libc),\n            };\n            let cpu_output = cpu_softmax.eval(tvec![a.into_tvalue()])?[0].clone().into_tensor();\n            Ok(cpu_output)\n        }\n\n        pub fn run(&self) -> TractResult<Tensor> {\n            with_borrowed_metal_stream(|stream| {\n                let a = Tensor::from_shape(self.shape.as_slice(), &self.input)?.into_device()?;\n                let metal_output = Softmax.eval(stream, &a, self.axis)?;\n                Ok(metal_output.to_host()?.into_tensor())\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "metal/src/kernels/utils.rs",
    "content": "use metal::{ComputePipelineState, MTLSize};\nuse tract_core::internal::TractResult;\n\npub fn build_metal_size_for_shape(shape: &[usize]) -> MTLSize {\n    match shape.len() {\n        0 => panic!(\"Unexpected empty shape while build grid size\"),\n        1 => MTLSize { width: shape[0] as _, height: 1, depth: 1 },\n        2 => MTLSize { width: shape[1] as _, height: shape[0] as _, depth: 1 },\n        3.. => MTLSize {\n            width: shape[shape.len() - 1] as _,\n            height: shape[shape.len() - 2] as _,\n            depth: (shape[..shape.len() - 2].iter().product::<usize>()) as _,\n        },\n    }\n}\n\npub fn build_metal_grid_and_groups_for_el_wise_op(\n    shape: &[usize],\n    max_thread: usize,\n) -> (MTLSize, MTLSize) {\n    let grid_size = match shape.len() {\n        0 => panic!(\"Unexpected empty shape while build grid size\"),\n        1 => MTLSize { width: 1, height: 1, depth: 1 },\n        2 => MTLSize { width: shape[0] as _, height: 1, depth: 1 },\n        3 => MTLSize { width: shape[1] as _, height: shape[0] as _, depth: 1 },\n        4.. => MTLSize {\n            width: shape[shape.len() - 2] as _,\n            height: shape[shape.len() - 3] as _,\n            depth: (shape[..shape.len() - 3].iter().product::<usize>()) as _,\n        },\n    };\n\n    (grid_size, MTLSize { width: shape[shape.len() - 1].min(max_thread) as _, height: 1, depth: 1 })\n}\n\npub fn build_metal_size_with_ones() -> MTLSize {\n    MTLSize { width: 1, height: 1, depth: 1 }\n}\n\npub use tract_gpu::utils::{compute_broadcast_strides, reshape_to_rank_2, reshape_to_rank_3};\n"
  },
  {
    "path": "metal/src/lib.rs",
    "content": "mod command_buffer;\nmod context;\nmod encoder;\nmod func_constants;\npub mod kernels;\npub mod ops;\nmod rewrite_rules;\nmod tensor;\nmod tests;\nmod transform;\nmod utils;\n\nuse tract_core::internal::*;\nuse tract_core::transform::ModelTransform;\n\nuse crate::func_constants::{ConstantValues, Value};\nuse crate::kernels::LibraryName;\npub use crate::kernels::matmul::MetalGemmImplKind;\n\npub use crate::context::{MetalContext, MetalStream, with_metal_stream};\npub use crate::transform::MetalTransform;\n\n#[derive(Debug)]\nstruct MetalRuntime;\n\nimpl Runtime for MetalRuntime {\n    fn name(&self) -> StaticName {\n        \"metal\".into()\n    }\n\n    fn prepare_with_options(\n        &self,\n        mut model: TypedModel,\n        options: &RunOptions,\n    ) -> TractResult<Box<dyn Runnable>> {\n        MetalTransform::default().transform(&mut model)?;\n        model = model.into_optimized()?;\n\n        let options = RunOptions { skip_order_opt_ram: true, ..options.clone() };\n        let mut runnable = TypedSimplePlan::build(model, &options)?;\n        if let Some(hints) = options.memory_sizing_hints {\n            let session_handler =\n                tract_gpu::session_handler::DeviceSessionHandler::from_plan(&runnable, &hints)\n                    .context(\"While sizing memory arena. Missing hint ?\")?;\n            runnable = runnable.with_session_handler(session_handler);\n        }\n\n        Ok(Box::new(Arc::new(runnable)))\n    }\n\n    fn check(&self) -> TractResult<()> {\n        Ok(())\n    }\n}\n\nregister_runtime!(MetalRuntime = MetalRuntime);\n"
  },
  {
    "path": "metal/src/ops/conv.rs",
    "content": "use crate::kernels::conv::metal_conv_dispatch;\nuse tract_core::internal::*;\nuse tract_core::ops::cnn::Conv;\nuse tract_gpu::ops::change_axes::GpuAxisOp;\nuse tract_gpu::tensor::DeviceTensorExt;\n\npub fn wire_metal_conv(\n    source: &TypedModel,\n    node: &TypedNode,\n    target: &mut TypedModel,\n    inputs: &[OutletId],\n    op: &Conv,\n) -> TractResult<TVec<OutletId>> {\n    let facts = source.node_input_facts(node.id)?;\n    let data_shape = op.pool_spec.data_format.shape(&facts[0].shape)?;\n    let prefix = &node.name;\n    let bias = &facts[2];\n    let need_bias = !(bias.konst.is_some() && bias.konst.as_ref().unwrap().is_all_zero()?);\n    let conv_name = format!(\"{prefix}.conv\");\n    let mut conv_wire = target.wire_node(\n        if need_bias { &conv_name } else { &node.name },\n        MetalConv { op: op.clone() },\n        &inputs[0..2],\n    )?[0];\n    if need_bias {\n        let mut needed_shape = tvec![1.to_dim(); node.outputs[0].fact.rank()];\n        needed_shape[data_shape.c_axis()] = op.pool_spec.output_channels.to_dim();\n        let reshaped = target.wire_node(\n            format!(\"{prefix}.bias_reshaped\"),\n            GpuAxisOp::new(AxisOp::Reshape(0, bias.shape.to_tvec(), needed_shape)),\n            &[inputs[2]],\n        )?[0];\n        conv_wire = target.wire_node(\n            prefix,\n            crate::kernels::bin_ops::metal_bin_op(Box::new(tract_core::ops::math::Add)),\n            &[conv_wire, reshaped],\n        )?[0];\n    }\n    Ok(tvec!(conv_wire))\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct MetalConv {\n    pub op: Conv,\n}\n\nimpl Op for MetalConv {\n    fn name(&self) -> StaticName {\n        \"MetalConv\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        self.op.info()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for MetalConv {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let inputs =\n            inputs.iter().map(|it| it.to_device_tensor()).collect::<TractResult<TVec<_>>>()?;\n        let output_shape = self.op.pool_spec.output_shape(inputs[0].shape())?;\n        let output = tract_gpu::session_handler::make_tensor_for_node(\n            session,\n            node_id,\n            inputs[0].datum_type(),\n            &output_shape.shape,\n        )?;\n\n        if output.len() > 0 {\n            crate::with_metal_stream(|stream| {\n                metal_conv_dispatch(\n                    stream,\n                    &self.op,\n                    inputs[0],\n                    inputs[1],\n                    inputs.get(2).cloned(),\n                    &output,\n                )\n            })?;\n        }\n        Ok(tvec!(output.into_tensor().into_tvalue()))\n    }\n}\n\nimpl TypedOp for MetalConv {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        tract_gpu::utils::facts_to_device_facts(inputs, |facts| {\n            let zero = facts[0].datum_type.scalar_fact();\n            let mut facts: TVec<&TypedFact> = facts.into();\n            if facts.len() == 2 {\n                facts.push(&zero);\n            }\n            self.op.output_facts(&facts)\n        })\n        .with_context(|| \"Error while computing facts for MetalConv\")\n    }\n}\n"
  },
  {
    "path": "metal/src/ops/fused_axis_op.rs",
    "content": "use derive_new::new;\nuse tract_core::internal::tract_smallvec::ToSmallVec;\nuse tract_core::internal::*;\nuse tract_core::ops::OpStateFreeze;\nuse tract_gpu::ops::change_axes::GpuAxisOp;\nuse tract_gpu::tensor::{DeviceTensor, DeviceTensorExt};\n\n#[derive(Clone, Debug, new, PartialEq, Eq)]\npub struct MetalFusedAxisOp {\n    /// List of axis ops to apply for each op inputs\n    /// Length of the list is equal to number of inputs\n    pub grouped_axis_ops: TVec<TVec<GpuAxisOp>>,\n    pub op: Box<dyn TypedOp>,\n}\n\n#[derive(Debug, Clone, new)]\npub struct MetalFusedAxisOpState {\n    pub op_state: Box<dyn OpState>,\n}\n\nfn compute_reshaped_inputs(\n    inputs: TVec<TValue>,\n    grouped_axis_ops: &TVec<TVec<GpuAxisOp>>,\n    session: &TurnState,\n) -> TractResult<TVec<TValue>> {\n    // Apply Axis Ops per input\n\n    inputs\n        .into_iter()\n        .zip(grouped_axis_ops.iter())\n        .map(|(input, axis_ops)| {\n            if axis_ops.is_empty() {\n                return Ok(input);\n            };\n            let m_input = input.to_device_tensor()?;\n            let reshaped_input = axis_ops.iter().try_fold(\n                m_input.clone(),\n                |t, axis_op| -> TractResult<DeviceTensor> {\n                    let new_shape = match &axis_op.inner {\n                        AxisOp::Reshape(skip, from, to) => {\n                            let from =\n                                from.iter().map(|d| d.eval(&session.resolved_symbols)).collect();\n                            let to = to.iter().map(|d| d.eval(&session.resolved_symbols)).collect();\n                            let mut shape: TVec<usize> = t.shape().into();\n                            AxisOp::Reshape(*skip, from, to)\n                                .change_shape_array(&mut shape, false)?;\n                            shape\n                        }\n                        AxisOp::Add(_) | AxisOp::Rm(_) | AxisOp::Move(..) => {\n                            let mut shape: TVec<usize> = t.shape().into();\n                            axis_op.inner.change_shape_array(&mut shape, false)?;\n                            shape\n                        }\n                    };\n                    if let AxisOp::Move(from, to) = axis_op.inner {\n                        let mut out_strides: TVec<isize> = t.strides().to_smallvec();\n                        let removed_stride = out_strides.remove(from);\n                        out_strides.insert(to, removed_stride);\n                        let tmp_t = t.reshaped(new_shape)?;\n                        tmp_t.restrided(out_strides)\n                    } else {\n                        t.reshaped(new_shape)\n                    }\n                },\n            )?;\n\n            Ok(reshaped_input.into_tensor().into())\n        })\n        .collect::<TractResult<TVec<_>>>()\n}\n\nimpl OpState for MetalFusedAxisOpState {\n    fn init_tensor_fact(&self) -> Option<(String, TypedFact)> {\n        self.op_state.init_tensor_fact()\n    }\n\n    fn load_from(\n        &mut self,\n        session: &mut TurnState,\n        states: &mut dyn Iterator<Item = tract_core::value::TValue>,\n    ) -> TractResult<()> {\n        self.op_state.load_from(session, states)\n    }\n\n    fn save_to(&self, states: &mut Vec<TValue>) -> TractResult<()> {\n        self.op_state.save_to(states)\n    }\n\n    fn resolve_symbols(&mut self, session: &mut TurnState) -> TractResult<()> {\n        self.op_state.resolve_symbols(session)\n    }\n\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let fused_axis_op = op.downcast_ref::<MetalFusedAxisOp>().unwrap();\n        let inputs = compute_reshaped_inputs(inputs, &fused_axis_op.grouped_axis_ops, session)?;\n        // Runner inner op\n        self.op_state.eval(session, fused_axis_op.op.as_op(), inputs)\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct FrozenMetalFusedAxisOpState {\n    pub op_state: Box<dyn FrozenOpState>,\n}\n\nimpl OpStateFreeze for MetalFusedAxisOpState {\n    fn freeze(&self) -> Box<dyn FrozenOpState + 'static> {\n        Box::new(FrozenMetalFusedAxisOpState { op_state: self.op_state.freeze() })\n    }\n}\n\nimpl FrozenOpState for FrozenMetalFusedAxisOpState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(MetalFusedAxisOpState { op_state: self.op_state.unfreeze() })\n    }\n}\n\nimpl Op for MetalFusedAxisOp {\n    fn name(&self) -> StaticName {\n        self.op.name()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut info = self.op.info()?;\n        for (idx, axis_ops) in self.grouped_axis_ops.iter().enumerate() {\n            if !axis_ops.is_empty() {\n                info.push(format!(\n                    \"Fused axis Op on Input #{idx}: {}\",\n                    axis_ops\n                        .iter()\n                        .map(|axis_op| Ok(format!(\n                            \"{} - {}\",\n                            axis_op.name(),\n                            axis_op.info()?.join(\" | \")\n                        )))\n                        .collect::<TractResult<TVec<_>>>()?\n                        .join(\" | \")\n                ));\n            }\n        }\n        Ok(info)\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for MetalFusedAxisOp {\n    fn is_stateless(&self) -> bool {\n        self.op.is_stateless()\n    }\n\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        if let Some(state) = self.op.state(session, node_id)? {\n            Ok(Some(Box::new(MetalFusedAxisOpState { op_state: state })))\n        } else {\n            Ok(None)\n        }\n    }\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let inputs = compute_reshaped_inputs(inputs, &self.grouped_axis_ops, session)?;\n        // Runner inner op\n        self.op.eval_with_session(node_id, session, inputs)\n    }\n}\n\nimpl TypedOp for MetalFusedAxisOp {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(\n            inputs.len() == self.grouped_axis_ops.len(),\n            \"Number of inputs and fused axis ops are not aligned\"\n        );\n        // Apply AxisOp\n        let inputs = inputs\n            .iter()\n            .zip(self.grouped_axis_ops.iter())\n            .map(|(i, axis_ops)| {\n                axis_ops.iter().try_fold((*i).clone(), |reshaped_i, axis_op| {\n                    Ok(axis_op.output_facts(&[&reshaped_i])?[0].clone())\n                })\n            })\n            .collect::<TractResult<TVec<_>>>()?;\n\n        let inputs_ref = inputs.iter().collect::<TVec<_>>();\n        // Apply Op\n        self.op.output_facts(&inputs_ref)\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "metal/src/ops/gemm.rs",
    "content": "use crate::kernels::matmul::{GemmImpl, GemmKernel};\n\nuse anyhow::{bail, ensure};\nuse tract_core::internal::*;\nuse tract_core::tract_linalg::block_quant::Q4_0;\nuse tract_gpu::tensor::DeviceTensorExt;\nuse tract_gpu::utils::as_quant_fact;\n\n#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]\npub struct MetalGemm<K: GemmKernel> {\n    pub kernel: GemmImpl<K>,\n}\n\nimpl<K: GemmKernel> MetalGemm<K> {\n    pub fn new(transpose_a: bool, transpose_b: bool) -> Self {\n        Self { kernel: GemmImpl::<K>::new(transpose_a, transpose_b) }\n    }\n}\n\nimpl<K: GemmKernel + 'static> Op for MetalGemm<K> {\n    fn name(&self) -> StaticName {\n        format!(\"Metal{}\", self.kernel).into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![\n            format!(\"transpose_a: {} transpose_b: {}\", self.transpose_a(), self.transpose_b(),),\n        ])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl<K: GemmKernel> MetalGemm<K> {\n    fn transpose_a(&self) -> bool {\n        self.kernel.transpose_a\n    }\n\n    fn transpose_b(&self) -> bool {\n        self.kernel.transpose_b\n    }\n\n    fn resolve_output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let [a, b] = inputs else {\n            bail!(\"Expects 2 inputs\");\n        };\n\n        if a.is_plain() && b.is_plain() && a.datum_type.is_number() && b.datum_type.is_number() {\n            ensure!(a.rank() == b.rank());\n            ensure!(a.rank() >= 2);\n            ensure!(\n                a.shape[a.rank() - 2 + !self.transpose_a() as usize]\n                    == b.shape[b.rank() - 2 + self.transpose_b() as usize]\n            );\n            let out_shape = self.kernel.output_shape(&a.shape, &b.shape);\n            Ok(self.kernel.output_facts(&out_shape, a.datum_type, b.datum_type)?)\n        } else if as_quant_fact(inputs[0], &Q4_0).is_some()\n            || as_quant_fact(inputs[1], &Q4_0).is_some()\n        {\n            // Exotic tensors now carry their full logical shape directly on the\n            // fact, so no need to chain with BlockQuantFact.shape().\n            let out_shape = self.kernel.output_shape(&a.shape, &b.shape);\n            Ok(self.kernel.output_facts(&out_shape, a.datum_type, b.datum_type)?)\n        } else {\n            todo!()\n        }\n    }\n}\nimpl<K: GemmKernel + 'static> EvalOp for MetalGemm<K> {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval_with_session(\n        &self,\n        node_id: usize,\n        session: &TurnState,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let (a_raw, b_raw) = args_2!(inputs);\n        let a = a_raw\n            .to_device_tensor()\n            .with_context(|| format!(\"A tensor is not a metal tensor: {:?}\", a_raw))?;\n        let b = b_raw\n            .to_device_tensor()\n            .with_context(|| format!(\"B tensor is not a metal tensor {:?}\", b_raw))?;\n\n        // For q40 weights the tensor shape already carries the full logical\n        // dimensions [batch, n, k].  No need to chain with the fact shape.\n        let b_shape = b.shape().to_vec();\n\n        let c_dt = self.kernel.matmul.output_dt(a.datum_type(), b.datum_type())?;\n        let c_shape = self.kernel.output_shape(a.shape(), &b_shape);\n        let c = tract_gpu::session_handler::make_tensor_for_node(session, node_id, c_dt, &c_shape)?;\n\n        crate::with_metal_stream(|stream| self.kernel.dispatch_eval(stream, a, b, &c))?;\n\n        Ok(tvec![c.into_tensor().into_tvalue()])\n    }\n}\n\nimpl<K: GemmKernel + 'static> TypedOp for MetalGemm<K> {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        tract_gpu::utils::facts_to_device_facts(inputs, |input_facts| {\n            self.resolve_output_facts(input_facts)\n        })\n        .with_context(|| format!(\"Error while computing output facts for {}\", self.name()))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        tract_gpu::utils::get_device_facts(inputs, |input_facts| {\n            let fma = self.resolve_output_facts(input_facts)?[0].shape.iter().product::<TDim>()\n                * input_facts[0].shape.last().unwrap();\n            if input_facts[0].datum_type == f16::datum_type() {\n                Ok(tvec!((Cost::FMA(f16::datum_type()), fma)))\n            } else {\n                Ok(tvec!((Cost::FMA(f32::datum_type()), fma)))\n            }\n        })\n        .with_context(|| format!(\"Error while computing cost for {:?}\", self.name()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "metal/src/ops/mod.rs",
    "content": "pub mod conv;\npub mod fused_axis_op;\npub mod gemm;\n\npub use fused_axis_op::MetalFusedAxisOp;\npub use gemm::MetalGemm;\n"
  },
  {
    "path": "metal/src/rewrite_rules/add_matmul_broadcast.rs",
    "content": "use crate::{MetalGemmImplKind, MetalTransform};\nuse tract_core::internal::*;\nuse tract_core::ops::array::MultiBroadcastTo;\nuse tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\nuse tract_gpu::rule_ensure;\n\npub fn add_broadcast_pre_matmul(\n    ctx: &MetalTransform,\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &PrefixMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    let in_facts = model.node_input_facts(node.id)?;\n    // GGML supports broadcast\n    rule_ensure!(in_facts[0].rank() > 2);\n    rule_ensure!(\n        !(ctx.gemm_impl == Some(MetalGemmImplKind::Ggml)\n            || (ctx.gemm_impl.is_none() && in_facts[0].datum_type == DatumType::F32))\n    );\n\n    // Detect broadcast\n    let a_shape = &in_facts[0].shape;\n    let b_shape = &in_facts[1].shape;\n    let a_rank = a_shape.rank();\n\n    let a_batch = &a_shape[..a_rank - 2];\n    let b_batch = &b_shape[..a_rank - 2];\n\n    // Remove from batch_dim array all symbolic dimensions also present in the other batch_dim array\n    // Symbolic Dimensions will be considered as 1 in gcd() so this allows identifying a\n    // symbolic broadcast factor.\n    let a_batch_dims: Vec<_> = a_batch\n        .iter()\n        .filter(|tdim| !matches!(tdim, TDim::Sym(_)) || b_batch.contains(tdim))\n        .cloned()\n        .collect();\n\n    let b_batch_dims: Vec<_> = b_batch\n        .iter()\n        .filter(|tdim| !matches!(tdim, TDim::Sym(_)) || a_batch.contains(tdim))\n        .cloned()\n        .collect();\n\n    let symb_in_a = a_batch_dims != a_batch;\n    let symb_in_b = b_batch_dims != b_batch;\n\n    let a_batch_size = a_batch_dims.iter().product::<TDim>().gcd();\n    let b_batch_size = b_batch_dims.iter().product::<TDim>().gcd();\n\n    let (activ_slot, weight_slot) = if (a_batch_size % b_batch_size == 0)\n        && ((a_batch_size != b_batch_size) || symb_in_a)\n    {\n        (0, 1)\n    } else if (b_batch_size % a_batch_size == 0) && ((a_batch_size != b_batch_size) || symb_in_b) {\n        (1, 0)\n    } else {\n        return Ok(None);\n    };\n\n    let mut patch = TypedModelPatch::default();\n    let activ = patch.tap_model(model, node.inputs[activ_slot])?;\n    let weights = patch.tap_model(model, node.inputs[weight_slot])?;\n    let brd_shape = ShapeFact::from_dims(\n        [\n            in_facts[activ_slot].shape.dims()[..a_rank - 2].to_vec(),\n            in_facts[weight_slot].shape.dims()[a_rank - 2..].to_vec(),\n        ]\n        .concat(),\n    );\n    let brd = MultiBroadcastTo { shape: brd_shape };\n\n    let brd_out = patch.wire_node(format!(\"{node_name}.broadcast\"), brd, &[weights])?[0];\n\n    let inputs = if activ_slot == 1 { [brd_out, activ] } else { [activ, brd_out] };\n    let mm_out = patch.wire_node(node_name, *op, &inputs)?[0];\n\n    patch.shunt_outside(model, node.id.into(), mm_out)?;\n\n    Ok(Some(patch))\n}\n"
  },
  {
    "path": "metal/src/rewrite_rules/fuse_axis_op.rs",
    "content": "use crate::ops::MetalFusedAxisOp;\nuse tract_core::internal::*;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_gpu::fact::DeviceTypedFactExt;\nuse tract_gpu::ops::change_axes::GpuAxisOp;\nuse tract_gpu::rule_ensure;\n\nfn is_supported_axis_op(op: &GpuAxisOp) -> bool {\n    matches!(op.inner, AxisOp::Add(_) | AxisOp::Rm(_) | AxisOp::Reshape(..))\n}\n\nfn can_fuse_move(model: &TypedModel, axis_node: &TypedNode) -> bool {\n    model.single_succ(axis_node.id).unwrap().is_some_and(|node| {\n        node.op_is::<tract_gpu::ops::concat::GpuConcat>()\n            || node.op_is::<tract_gpu::ops::apply_rope::GpuApplyRope>()\n            || node.op_is::<tract_gpu::ops::scaled_masked_softmax::GpuScaledMaskedSoftmax>()\n            || node.op_is::<tract_gpu::ops::slice::GpuSlice>()\n            || node.op_is::<tract_gpu::ops::broadcast::GpuMultiBroadcastTo>()\n            || node.op_is::<tract_gpu::ops::dyn_kv_cache::GpuDynKVCache>()\n    })\n}\n\npub fn collect_chain_of_axis_ops<'a>(\n    model: &'a TypedModel,\n    mut cursor: &'a TypedNode,\n) -> TractResult<Option<(TVec<GpuAxisOp>, &'a TypedNode)>> {\n    let mut acc_axis_ops = tvec![];\n    let mut head_of_chain = cursor;\n\n    while let Some(axis_op) = cursor.op_as::<GpuAxisOp>().filter(|o| {\n        is_supported_axis_op(o)\n            || (matches!(o.inner, AxisOp::Move(..)) && can_fuse_move(model, cursor))\n    }) {\n        acc_axis_ops.push(axis_op.clone());\n        head_of_chain = cursor;\n\n        if let Some(prev) = model.single_prec(cursor.id)? {\n            cursor = prev;\n        } else {\n            break;\n        }\n    }\n\n    Ok(if acc_axis_ops.is_empty() {\n        None\n    } else {\n        Some((acc_axis_ops.into_iter().rev().collect(), head_of_chain))\n    })\n}\n\nfn split_succs(\n    model: &TypedModel,\n    axis_node: &TypedNode,\n    axis_node_name: &str,\n    axis_op: &GpuAxisOp,\n) -> TractResult<Option<TypedModelPatch>> {\n    let succs = model.all_succ(axis_node.id)?.context(\"Expected node with successors\")?;\n\n    let mut patch = TypedModelPatch::default();\n    let input = patch.tap_model(model, axis_node.inputs[0])?;\n\n    for (i, succ) in succs.iter().enumerate() {\n        let axis_out =\n            patch.wire_node(format!(\"{axis_node_name}.{i}\"), axis_op.clone(), &[input])?[0];\n\n        let mut op_ins = patch.taps(model, &succ.inputs)?;\n\n        let (idx, _) = succ\n            .inputs\n            .iter()\n            .enumerate()\n            .find(|(_, inlet)| inlet.node == axis_node.id)\n            .context(\"Axis node not found in its successor inputs\")?;\n\n        op_ins[idx] = axis_out;\n\n        let op_outs = patch.wire_node(succ.name.clone(), succ.op.clone(), &op_ins)?;\n        for out in op_outs {\n            patch.shunt_outside(model, succ.id.into(), out)?;\n        }\n    }\n\n    Ok(Some(patch))\n}\n\npub fn fuse_axis_op(\n    _ctx: &(),\n    model: &TypedModel,\n    axis_node: &TypedNode,\n    axis_node_name: &str,\n    axis_op: &GpuAxisOp,\n) -> TractResult<Option<TypedModelPatch>> {\n    // Only support certain axis ops (or a Move, which is handled specially below)\n    rule_ensure!(is_supported_axis_op(axis_op) || matches!(axis_op.inner, AxisOp::Move(..)));\n\n    let Some(node) = model.single_succ(axis_node.id)? else {\n        return split_succs(model, axis_node, axis_node_name, axis_op);\n    };\n\n    // Disallow fusing when the successor is already an axis/fused op or a sync,\n    // *unless* it's a Move AxisOp (we allow that via the early-quit branch).\n    let is_axis_like = node.op_is::<GpuAxisOp>() || node.op_is::<MetalFusedAxisOp>();\n    let is_allowed_move =\n        node.op_as::<GpuAxisOp>().is_some_and(|op| matches!(op.inner, AxisOp::Move(..)));\n\n    rule_ensure!(!is_axis_like || is_allowed_move);\n\n    let node_name = &node.name;\n\n    let Some(in_nodes) = model.all_prec(node.id)? else {\n        return Ok(None);\n    };\n\n    let mut grouped_axis_ops: TVec<TVec<GpuAxisOp>> = tvec![];\n    let mut tap_inputs = tvec![];\n    let mut patch = TypedModelPatch::default();\n\n    for (in_idx, in_node) in in_nodes.into_iter().enumerate() {\n        match collect_chain_of_axis_ops(model, in_node)? {\n            Some((acc_axis_ops, head_of_chain)) => {\n                grouped_axis_ops.push(acc_axis_ops);\n                tap_inputs.push(patch.tap_model(model, head_of_chain.inputs[0])?);\n            }\n            None => {\n                grouped_axis_ops.push(tvec![]);\n                tap_inputs.push(patch.tap_model(model, node.inputs[in_idx])?);\n            }\n        }\n    }\n\n    // If the successor is a Move, we may fuse it now or defer.\n    if let Some(op) = node.op_as::<GpuAxisOp>() {\n        if matches!(op.inner, AxisOp::Move(..)) {\n            let should_defer_move = !grouped_axis_ops[0].is_empty() && !can_fuse_move(model, node);\n            if should_defer_move {\n                let out = patch.wire_node(\n                    format!(\"{node_name}.fused_axis_op\"),\n                    MetalFusedAxisOp { grouped_axis_ops, op: Box::new(op.clone()) },\n                    &tap_inputs,\n                )?;\n                patch.shunt_outside(model, node.id.into(), out[0])?;\n                return Ok(Some(patch));\n            } else {\n                // Nothing to do right now; we’ll fuse on a later pass.\n                return Ok(None);\n            }\n        }\n    }\n\n    // General case: fuse using the successor's op.\n    let out = patch.wire_node(\n        format!(\"{node_name}.fused_axis_op\"),\n        MetalFusedAxisOp { grouped_axis_ops, op: node.op.clone() },\n        &tap_inputs,\n    )?;\n    patch.shunt_outside(model, node.id.into(), out[0])?;\n    Ok(Some(patch))\n}\n\npub fn fuse_move_axis(\n    _ctx: &(),\n    model: &TypedModel,\n    axis_node: &TypedNode,\n    axis_node_name: &str,\n    axis_op: &GpuAxisOp,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_ensure!(matches!(axis_op.inner, AxisOp::Move(..)));\n\n    let in_fact = model.node_input_facts(axis_node.id)?[0];\n    let in_shape =\n        in_fact.as_device_fact().map(|mf| mf.shape.clone()).unwrap_or(in_fact.shape.clone());\n\n    let out_fact = model.node_output_facts(axis_node.id)?[0];\n    let out_shape =\n        out_fact.as_device_fact().map(|mf| mf.shape.clone()).unwrap_or(out_fact.shape.clone());\n\n    // Checks if MoveAxis has no impact on shape + layout\n    if in_shape == out_shape {\n        if let (Some(in_strides), AxisOp::Move(from, to)) =\n            (in_shape.as_concrete().map(Tensor::natural_strides), axis_op.inner.clone())\n        {\n            let mut out_strides = in_strides.clone();\n            let remove_stride = out_strides.remove(from);\n            out_strides.insert(to, remove_stride);\n            if in_strides == out_strides {\n                return TypedModelPatch::shunt_one_op(model, axis_node);\n            }\n        }\n    }\n\n    // Reshape are always fusable. Change Move by Reshape if possible\n    let simpl_op = GpuAxisOp::simplify_axis_op(axis_op.inner.clone(), in_shape.dims());\n    if simpl_op != *axis_op {\n        return Ok(Some(TypedModelPatch::replace_single_op(\n            model,\n            axis_node,\n            &[axis_node.inputs[0]],\n            simpl_op,\n        )?));\n    }\n\n    // Fuse consecutive MoveAxis if possible\n    let Some(cursor) = model.single_succ(axis_node.id)? else { return Ok(None) };\n    if let (AxisOp::Move(from_1, to_1), AxisOp::Move(from_2, to_2)) = (\n        axis_op.inner.clone(),\n        cursor.op_as::<GpuAxisOp>().map(|ax_op| ax_op.inner.clone()).unwrap_or(AxisOp::Add(0)),\n    ) {\n        let max_rank = [from_1, from_2, to_1, to_2].iter().max().unwrap() + 1;\n        let mut perm: TVec<usize> = (0..max_rank).collect_vec().into();\n\n        AxisOp::Move(from_1, to_1).change_shape_array(&mut perm, false)?;\n        AxisOp::Move(from_2, to_2).change_shape_array(&mut perm, false)?;\n        let new_axis_ops = perm_to_ops(&perm);\n        if new_axis_ops.len() == 1 {\n            let mut patch = TypedModelPatch::default();\n            let inputs = patch.taps(model, &axis_node.inputs)?;\n            let out = patch.wire_node(\n                format!(\"{axis_node_name}.fused_move_axis\"),\n                GpuAxisOp::new(new_axis_ops[0].clone()),\n                &inputs,\n            )?;\n            patch.shunt_outside(model, cursor.id.into(), out[0])?;\n            return Ok(Some(patch));\n        }\n    }\n\n    // Add(x) -> Move(x, y)\n    let Some(cursor) = model.single_prec(axis_node.id)? else { return Ok(None) };\n    if let (AxisOp::Move(from_1, to_1), AxisOp::Add(ax)) = (\n        axis_op.inner.clone(),\n        cursor.op_as::<GpuAxisOp>().map(|ax_op| ax_op.inner.clone()).unwrap_or(AxisOp::Rm(0)),\n    ) {\n        if ax == from_1 {\n            let mut patch = TypedModelPatch::default();\n            let inputs = patch.taps(model, &cursor.inputs)?;\n            let out =\n                patch.wire_node(cursor.name.clone(), GpuAxisOp::new(AxisOp::Add(to_1)), &inputs)?;\n            patch.shunt_outside(model, axis_node.id.into(), out[0])?;\n            return Ok(Some(patch));\n        }\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "metal/src/rewrite_rules/mod.rs",
    "content": "mod add_matmul_broadcast;\nmod fuse_axis_op;\nmod untranspose_matmul_output;\n\npub use add_matmul_broadcast::add_broadcast_pre_matmul;\npub use fuse_axis_op::{fuse_axis_op, fuse_move_axis};\npub use untranspose_matmul_output::untranspose_matmul_output;\n"
  },
  {
    "path": "metal/src/rewrite_rules/untranspose_matmul_output.rs",
    "content": "use crate::MetalTransform;\nuse tract_core::internal::*;\nuse tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\nuse tract_gpu::rule_ensure;\n\n/// Rewrite BasicMatMul { .. transpose_c: true } to BasicMatMul { .. transpose_c: false}\npub fn untranspose_matmul_output(\n    _ctx: &MetalTransform,\n    model: &TypedModel,\n    node: &TypedNode,\n    _node_name: &str,\n    op: &PrefixMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_ensure!(op.transpose_c);\n\n    let new_matmul = PrefixMatMul {\n        transpose_a: !op.transpose_b,\n        transpose_b: !op.transpose_a,\n        transpose_c: false,\n        ..*op\n    };\n\n    TypedModelPatch::replace_single_op(model, node, &[node.inputs[1], node.inputs[0]], new_matmul)\n        .map(Some)\n}\n"
  },
  {
    "path": "metal/src/tensor.rs",
    "content": "use std::fmt::Display;\nuse tract_core::internal::*;\nuse tract_gpu::device::DeviceBuffer;\nuse tract_gpu::tensor::{DeviceTensor, OwnedDeviceTensor};\nuse tract_gpu::utils::check_strides_validity;\n\nuse crate::context::MetalBuffer;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub enum MValue {\n    Natural(Arc<Tensor>),\n    Reshaped { t: Arc<Tensor>, shape: TVec<usize>, strides: TVec<isize> },\n}\n\nimpl MValue {\n    /// Get the datum type of the tensor.\n    #[inline]\n    pub fn datum_type(&self) -> DatumType {\n        match self {\n            Self::Natural(t) => t.datum_type(),\n            Self::Reshaped { t, .. } => t.datum_type(),\n        }\n    }\n\n    #[inline]\n    pub fn shape(&self) -> &[usize] {\n        match self {\n            MValue::Natural(t) => t.shape(),\n            MValue::Reshaped { shape, .. } => shape,\n        }\n    }\n\n    /// Get the number of values.\n    #[inline]\n    #[allow(clippy::len_without_is_empty)]\n    pub fn len(&self) -> usize {\n        self.shape().iter().product()\n    }\n\n    /// Reshaped tensor with given shape.\n    pub fn reshaped(&self, shape: impl Into<TVec<usize>>) -> TractResult<Self> {\n        let shape = shape.into();\n        if self.len() != shape.iter().product::<usize>() {\n            bail!(\"Invalid reshape {:?} to {:?}\", self.shape(), shape);\n        }\n        if shape.as_slice() != self.shape() {\n            match &self {\n                MValue::Natural(t) | MValue::Reshaped { t, .. } => Ok(Self::Reshaped {\n                    t: Arc::clone(t),\n                    strides: Tensor::natural_strides(&shape),\n                    shape,\n                }),\n            }\n        } else {\n            Ok(self.clone())\n        }\n    }\n\n    pub fn restrided(&self, strides: impl Into<TVec<isize>>) -> TractResult<Self> {\n        let strides = strides.into();\n        check_strides_validity(self.shape().into(), strides.clone())?;\n\n        match &self {\n            MValue::Natural(t) => {\n                Ok(Self::Reshaped { t: Arc::clone(t), strides, shape: self.shape().into() })\n            }\n            MValue::Reshaped { t, strides: old_strides, .. } => {\n                if &strides != old_strides {\n                    Ok(Self::Reshaped { t: Arc::clone(t), strides, shape: self.shape().into() })\n                } else {\n                    Ok(self.clone())\n                }\n            }\n        }\n    }\n\n    pub fn as_arc_tensor(&self) -> Option<&Arc<Tensor>> {\n        match self {\n            MValue::Natural(t) => Some(t),\n            MValue::Reshaped { .. } => None,\n        }\n    }\n}\n\nimpl IntoTensor for MValue {\n    fn into_tensor(self) -> Tensor {\n        match self {\n            Self::Natural(t) => Arc::try_unwrap(t).unwrap_or_else(|t| (*t).clone()),\n            Self::Reshaped { t, shape, strides: _ } => {\n                let mut t = Arc::try_unwrap(t).unwrap_or_else(|t| (*t).clone());\n                t.set_shape(&shape).expect(\"Could not apply shape to reshaped GPU tensor\");\n                t\n            }\n        }\n    }\n}\n\nimpl From<Tensor> for MValue {\n    fn from(v: Tensor) -> Self {\n        Self::Natural(Arc::new(v))\n    }\n}\n\nimpl From<Arc<Tensor>> for MValue {\n    fn from(v: Arc<Tensor>) -> Self {\n        Self::Natural(v)\n    }\n}\n\n/// This struct represents a owned tensor that can be accessed from the\n/// GPU and the CPU.\n#[derive(Clone, PartialEq, Eq)]\npub struct MetalTensor {\n    pub inner: MValue,\n    pub device_buffer: MetalBuffer,\n    pub exotic_fact: Option<Box<dyn ExoticFact>>,\n}\n\nimpl std::fmt::Debug for MetalTensor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"MetalTensor: {:?}\", self.inner)\n    }\n}\n\nimpl Hash for MetalTensor {\n    #[inline]\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.inner.hash(state)\n    }\n}\n\nimpl OwnedDeviceTensor for MetalTensor {\n    fn datum_type(&self) -> DatumType {\n        self.inner.datum_type()\n    }\n\n    #[inline]\n    fn shape(&self) -> &[usize] {\n        self.inner.shape()\n    }\n\n    /// Get the number of values in the tensor.\n    #[inline]\n    #[allow(clippy::len_without_is_empty)]\n    fn len(&self) -> usize {\n        self.shape().iter().product()\n    }\n\n    /// Get the strides of the tensor.\n    #[inline]\n    fn strides(&self) -> &[isize] {\n        match &self.inner {\n            MValue::Natural(t) => t.strides(),\n            MValue::Reshaped { strides, .. } => strides,\n        }\n    }\n\n    /// Get underlying inner device buffer.\n    #[inline]\n    fn device_buffer(&self) -> &dyn DeviceBuffer {\n        &self.device_buffer\n    }\n\n    /// Reshaped tensor with given shape.\n    #[inline]\n    fn reshaped(&self, shape: TVec<usize>) -> TractResult<DeviceTensor> {\n        Ok(DeviceTensor::Owned(Box::new(Self {\n            inner: self.inner.reshaped(shape)?,\n            device_buffer: self.device_buffer.clone(),\n            exotic_fact: self.exotic_fact.clone(),\n        })))\n    }\n\n    /// Change tensor stride.\n    #[inline]\n    fn restrided(&self, strides: TVec<isize>) -> TractResult<DeviceTensor> {\n        Ok(DeviceTensor::Owned(Box::new(Self {\n            inner: self.inner.restrided(strides)?,\n            device_buffer: self.device_buffer.clone(),\n            exotic_fact: self.exotic_fact.clone(),\n        })))\n    }\n\n    fn to_host(&self) -> TractResult<Arc<Tensor>> {\n        Ok(self\n            .inner\n            .as_arc_tensor()\n            .cloned()\n            .unwrap_or_else(|| self.inner.clone().into_tensor().into_arc_tensor()))\n    }\n\n    fn exotic_fact(&self) -> Option<&dyn ExoticFact> {\n        self.exotic_fact.as_deref()\n    }\n\n    fn get_bytes_slice(&self, offset: usize, len: usize) -> Vec<u8> {\n        self.inner.as_arc_tensor().unwrap().as_bytes()[offset..offset + len].to_vec()\n    }\n}\n\nimpl Display for MetalTensor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match &self.inner {\n            MValue::Natural(t) => {\n                let content = t.dump(false).unwrap_or_else(|e| format!(\"Error : {e:?}\"));\n                write!(f, \"GPU {{ {content} }}\")\n            }\n            MValue::Reshaped { t, shape, strides: _ } => {\n                let content = t.dump(false).unwrap_or_else(|e| format!(\"Error : {e:?}\"));\n                write!(f, \"GPU reshaped: {:?} - {{ {content} }}\", shape)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "metal/src/tests.rs",
    "content": "#[cfg(test)]\nmod tests {\n    use crate::MetalTransform;\n    use crate::utils::with_borrowed_metal_stream;\n    use tract_core::internal::*;\n    use tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\n    use tract_core::ops::math::{add, mul};\n    use tract_core::ops::nn::{Softmax, SoftmaxExp, SoftmaxKind};\n    use tract_core::transform::ModelTransform;\n    use tract_gpu::memory::DeviceMemSchema;\n    use tract_gpu::tensor::IntoDevice;\n\n    #[test]\n    fn test_alloc_zero() -> TractResult<()> {\n        with_borrowed_metal_stream(|_| Tensor::from_shape::<f32>(&[0], &[])?.into_device())?;\n        Ok(())\n    }\n\n    fn wire_sdpa_layer(\n        model: &mut TypedModel,\n        name: impl ToString,\n        q: OutletId,\n        k: OutletId,\n        v: OutletId,\n    ) -> TractResult<TVec<OutletId>> {\n        let name = name.to_string();\n\n        // Reshape Q\n        let q_shape = model.outlet_fact(q)?.shape.to_tvec();\n        let embed_dim: TDim = q_shape[1].clone();\n        let head_dim: TDim = q_shape[3].clone();\n        let batch: TDim = q_shape[0].clone();\n        let seq_len: TDim = q_shape[2].clone();\n        ensure!(batch.to_i64()? == 1, \"Input 'q' shape is {:?} (expect batch = 1)\", q_shape);\n        ensure!(q_shape.len() == 4, \"Input 'q' shape is {:?} (expect 4D)\", q_shape);\n        let q_reshaped = model.wire_node(\n            format!(\"q_reshape_{}\", name),\n            AxisOp::Reshape(\n                0,\n                q_shape.clone(),\n                tvec![embed_dim.clone(), batch.clone(), seq_len.clone(), head_dim.clone(),],\n            ),\n            &[q],\n        )?[0];\n\n        // Reshape K\n        let k_shape = model.outlet_fact(k)?.shape.to_tvec();\n        ensure!(k_shape.len() == 4, \"Input 'k' shape is {:?} (expect 4D)\", k_shape);\n        let seq_plus_prompt_len: TDim = k_shape[2].clone();\n\n        let k_reshaped = model.wire_node(\n            format!(\"k_reshape_{}\", name),\n            AxisOp::Reshape(\n                0,\n                k_shape.clone(),\n                tvec![\n                    embed_dim.clone(),\n                    batch.clone(),\n                    seq_plus_prompt_len.clone(),\n                    head_dim.clone(),\n                ],\n            ),\n            &[k],\n        )?[0];\n\n        // Compute Q * K^T\n        let qk = model.wire_node(\n            format!(\"qk_{}\", name),\n            PrefixMatMul {\n                transpose_a: false,\n                transpose_b: true,\n                transpose_c: false,\n                quantize_output: None,\n                operating_dt: Some(DatumType::F32),\n            },\n            &[q_reshaped, k_reshaped],\n        )?[0];\n\n        let qk_squeezed = model.wire_node(\n            format!(\"qk_squeezed_{}\", name),\n            AxisOp::Reshape(\n                0,\n                tvec![\n                    embed_dim.clone(),\n                    batch.clone(),\n                    seq_len.clone(),\n                    seq_plus_prompt_len.clone(),\n                ],\n                tvec![embed_dim.clone(), seq_len.clone(), seq_plus_prompt_len.clone(),],\n            ),\n            &[qk],\n        )?[0];\n\n        // Scale factor for attention\n        let scale = model.add_const(\n            format!(\"scale_{}\", name),\n            tensor3(&[[[1.0f32 / (head_dim.to_i64()? as f32).sqrt()]]]),\n        )?;\n        let qk_scaled =\n            model.wire_node(format!(\"qk_scaled_{}\", name), mul(), &[qk_squeezed, scale])?[0];\n\n        // Mask QK\n        let mask = model.add_const(\"mask\", tensor3(&[[[1.0f32]]]))?;\n        let qk_scaled_masked =\n            model.wire_node(format!(\"qk_scaled_masked_{}\", name), add(), &[qk_scaled, mask])?[0];\n\n        // Apply softmax\n        let attention = model.wire_node(\n            format!(\"attention_weights_{}\", name),\n            Softmax::new(tvec![2], None, SoftmaxKind::Softmax(SoftmaxExp::Libc)),\n            &[qk_scaled_masked],\n        )?[0];\n\n        // Reshape V\n        let v_reshaped = model.wire_node(\n            format!(\"v_reshape_{}\", name),\n            AxisOp::Reshape(\n                0,\n                k_shape,\n                tvec![embed_dim.clone(), seq_plus_prompt_len.clone(), head_dim.clone(),],\n            ),\n            &[v],\n        )?[0];\n\n        // Multiply with V\n        let output = model.wire_node(\n            format!(\"attention_output_{}\", name),\n            PrefixMatMul {\n                transpose_a: false,\n                transpose_b: false,\n                transpose_c: false,\n                quantize_output: None,\n                operating_dt: Some(DatumType::F32),\n            },\n            &[attention, v_reshaped],\n        )?[0];\n\n        // Reshape output\n        let output_reshaped = model.wire_node(\n            format!(\"output_reshape_{}\", name),\n            AxisOp::Reshape(\n                0,\n                tvec![embed_dim.clone(), seq_len.clone(), head_dim.clone(),],\n                q_shape,\n            ),\n            &[output],\n        )?;\n        Ok(output_reshaped)\n    }\n\n    #[test]\n    fn test_build_schema_from_model() -> TractResult<()> {\n        // Given\n        const EMBED_DIM: i64 = 32;\n        const HEAD_DIM: i64 = 64;\n        const SEQUENCE_LENGTH: i64 = 1;\n        const PAST_SEQUENCE_LENGTH: i64 = 8;\n        const EXPECTED_PEAK_SIZE: i64 = 9344;\n        const EXPECTED_USAGE: f32 = 0.89;\n\n        // Build a model with Scaled Dot-Product Attention (SDPA) layers\n        let mut model = TypedModel::default();\n\n        // Input shapes for Q, K, V\n        let s = TDim::Sym(model.sym(\"S\"));\n        let p = TDim::Sym(model.sym(\"P\"));\n        let q_fact = f32::fact(tvec![1.into(), EMBED_DIM.into(), s.clone(), HEAD_DIM.into()]);\n        let k_fact = f32::fact(tvec![1.into(), EMBED_DIM.into(), s + p, HEAD_DIM.into()]);\n        let v_fact = k_fact.clone();\n\n        // Create inputs for Q, K, V\n        let q = model.add_source(\"q\", q_fact)?;\n        let k = model.add_source(\"k\", k_fact)?;\n        let v = model.add_source(\"v\", v_fact)?;\n\n        let outputs = wire_sdpa_layer(&mut model, \"0\", q, k, v)?;\n        let outputs = wire_sdpa_layer(&mut model, \"1\", outputs[0], k, v)?;\n\n        model.select_output_outlets(&outputs)?;\n\n        // Transform model for Metal execution\n        let model = MetalTransform::default().transform_into(model)?;\n\n        // Get execution order\n        let order = model.eval_order()?;\n\n        // Hint symbol values\n        let mut symbol_values = SymbolValues::default();\n        symbol_values.set(&model.symbols.get(\"S\").context(\"Missing symbol S\")?, SEQUENCE_LENGTH);\n        symbol_values\n            .set(&model.symbols.get(\"P\").context(\"Missing symbol P\")?, PAST_SEQUENCE_LENGTH);\n\n        // Build memory schema\n        let schema = DeviceMemSchema::build(&model, &order, &symbol_values)?;\n\n        // Verify number of nodes\n        assert!(schema.model_num_nodes > 1, \"Schema should contain at least 2 nodes\");\n\n        // Verify number of partitions\n        assert!(schema.by_partition.len() > 1, \"Schema should contain at least 2 partitions\");\n\n        // Verify steps\n        assert_eq!(schema.by_steps.len(), order.len());\n        for step in 0..schema.by_steps.len() {\n            for partition in schema.by_partition.iter() {\n                let partition_size = partition.eval_size_to_i64(&symbol_values)?;\n\n                // No empty partition\n                assert!(!partition.nodes.is_empty());\n\n                if let Some(this) = partition.find_node_alive_at_step(step) {\n                    // Node memory requirement should be <= the partition size\n                    let node_size = this.mem_size.eval_to_i64(&symbol_values)?;\n                    assert!(node_size <= partition_size);\n                    assert!(node_size > 0);\n\n                    // All nodes should have a valid lifetime\n                    assert!(this.lifetime.start < this.lifetime.end);\n\n                    // No other node in the partition should be alive at this step\n                    for other in partition.nodes.iter().filter(|it| it.outlet_id != this.outlet_id)\n                    {\n                        assert!(\n                            !other.lifetime.is_alive_at_step(step)\n                                && other.lifetime.is_disjoint(&this.lifetime),\n                            \"Lifetime conflict @ step {}\\n{:?}\\n{:?}\",\n                            step,\n                            this,\n                            other\n                        );\n                    }\n\n                    // This node should not be alive in another partition at the same step\n                    for p in schema.by_partition.iter().filter(|it| it != &partition) {\n                        if let Some(other) = p.find_node_alive_at_step(step) {\n                            assert!(other.outlet_id != this.outlet_id);\n                        }\n                    }\n                }\n            }\n        }\n\n        // Verify schema usage\n        let usage = schema.eval_usage(&symbol_values)?;\n        assert!(usage >= EXPECTED_USAGE, \"Usage {}, expected >= {}\", usage, EXPECTED_USAGE);\n\n        // Verify peak memory size\n        let peak_memory_size = schema.eval_peak_memory_size(&symbol_values)?;\n        assert_eq!(peak_memory_size, EXPECTED_PEAK_SIZE, \"Peak memory size mismatch\");\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "metal/src/transform.rs",
    "content": "use std::any::TypeId;\nuse std::collections::HashMap;\nuse std::fmt::Debug;\nuse std::str::FromStr;\nuse std::sync::OnceLock;\n\nuse crate::context::metal_context;\nuse crate::kernels::matmul::{GemmKernel, GgmlGemm, MetalGemmImplKind, MfaGemm, MlxGemm};\nuse crate::{kernels, ops};\nuse tract_core::dyn_clone::clone_box;\nuse tract_core::internal::translator::Translate;\nuse tract_core::internal::*;\nuse tract_core::ops::cnn::conv::rewrite_kernel_conv_in_oihw;\nuse tract_core::ops::cnn::{Conv, rewrite_conv_with_n_axis};\nuse tract_core::ops::einsum::prefix_matmul::{PrefixMatMul, rewrite_einsum_to_prefix_matmul};\nuse tract_core::ops::konst::Const;\nuse tract_core::tract_linalg::block_quant::Q4_0;\nuse tract_core::transform::ModelTransform;\nuse tract_gpu::fact::{DeviceFact, DeviceTypedFactExt};\nuse tract_gpu::rewrite_rules::rewire_sdpa::rewire_sdpa;\nuse tract_gpu::rewrite_rules::rewire_syncs::rewire_syncs;\nuse tract_gpu::rewrite_rules::rms_norm::remove_rms_norm_cast;\nuse tract_gpu::sync::{DeviceSyncKind, sync_inputs_if_required, sync_model_outputs_if_required};\nuse tract_gpu::tensor::{DeviceTensor, IntoDevice};\nuse tract_gpu::utils::as_quant_fact;\n\nuse crate::rewrite_rules;\n\n/// A registered translator that can convert a core op into a Metal GPU op.\n/// Each kernel module submits one (or more) of these via [`register_metal_op!`].\npub struct MetalOpTranslator {\n    pub type_id: TypeId,\n    pub try_make: fn(&TypedModel, &TypedNode) -> TractResult<Option<Box<dyn TypedOp>>>,\n}\n\ninventory::collect!(MetalOpTranslator);\n\n/// Register a translator for a core op type. The closure receives `(source, node, op)`\n/// where `op` is already downcast to `$op_type`. Return `Ok(Some(gpu_op))` to translate,\n/// `Ok(None)` to skip.\n#[macro_export]\nmacro_rules! register_metal_op {\n    ($op_type:ty, |$source:ident, $node:ident, $op:ident| $body:expr) => {\n        inventory::submit! {\n            $crate::transform::MetalOpTranslator {\n                type_id: std::any::TypeId::of::<$op_type>(),\n                try_make: |$source, $node| {\n                    let Some($op) = $node.op_as::<$op_type>() else {\n                        return Ok(None);\n                    };\n                    $body\n                },\n            }\n        }\n    };\n}\n\nimpl MetalGemmImplKind {\n    pub fn variants() -> Vec<MetalGemmImplKind> {\n        vec![Self::Mlx, Self::Mfa, Self::Ggml]\n    }\n\n    pub fn variants_str() -> Vec<&'static str> {\n        Self::variants().into_iter().map(|it| it.to_str()).collect()\n    }\n\n    pub fn to_str(&self) -> &'static str {\n        match self {\n            Self::Mlx => \"mlx\",\n            Self::Mfa => \"mfa\",\n            Self::Ggml => \"ggml\",\n        }\n    }\n}\n\n#[derive(Debug, Default)]\npub struct MetalTransform {\n    pub gemm_impl: Option<MetalGemmImplKind>,\n}\n\nimpl ModelTransform for MetalTransform {\n    fn name(&self) -> StaticName {\n        \"metal-transform\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        self.transform_up_to_phase(model, usize::MAX)\n    }\n}\n\nimpl FromStr for MetalTransform {\n    type Err = TractError;\n    fn from_str(str: &str) -> TractResult<Self> {\n        let gemm_impl = match str {\n            \"mlx\" => Some(MetalGemmImplKind::Mlx),\n            \"ggml\" => Some(MetalGemmImplKind::Ggml),\n            \"mfa\" => Some(MetalGemmImplKind::Mfa),\n            \"\" => None,\n            _ => bail!(\"Unknown backend\"),\n        };\n        Ok(MetalTransform { gemm_impl })\n    }\n}\n\nimpl MetalTransform {\n    pub fn transform_up_to_phase(\n        &self,\n        model: &mut TypedModel,\n        stop_at_phase: usize,\n    ) -> TractResult<()> {\n        // Init Metal Context if not done previously\n        metal_context();\n\n        rewire_sdpa(model)?;\n        rewrite_einsum_to_prefix_matmul(model, false)?;\n        if stop_at_phase == 0 {\n            return Ok(());\n        }\n\n        Rewriter::<MetalTransform>::default()\n            .with_rule_for(\"untranspose-matmul-output\", rewrite_rules::untranspose_matmul_output)\n            .with_rule_for(\"add-broadcast-pre-matmul\", rewrite_rules::add_broadcast_pre_matmul)\n            .rewrite(self, model)?;\n\n        Rewriter::default()\n            .with_rule_for(\"rewrite_kernel_conv_in_oihw\", rewrite_kernel_conv_in_oihw)\n            .with_rule_for(\"rewrite_conv_with_n_axis\", rewrite_conv_with_n_axis)\n            .with_rule_for(\"remove_rms_norm_cast\", remove_rms_norm_cast)\n            .rewrite(&(), model)?;\n\n        if stop_at_phase == 1 {\n            return Ok(());\n        }\n\n        *model = self.translate_model(model)?;\n\n        if stop_at_phase == 2 {\n            return Ok(());\n        }\n\n        Rewriter::default()\n            .with_rule_for(\"fuse_move_axis\", rewrite_rules::fuse_move_axis)\n            .rewrite(&(), model)?;\n        Rewriter::default()\n            .with_rule_for(\"fuse_axis_op\", rewrite_rules::fuse_axis_op)\n            .rewrite(&(), model)?;\n\n        rewire_syncs(model)?;\n        Ok(())\n    }\n}\n\n/// Looks up the node's op TypeId in the inventory of registered `MetalOpTranslator`s.\n/// Returns `Some(gpu_op)` if a translator matches and succeeds, `None` otherwise.\nfn try_make_metal_op(\n    source: &TypedModel,\n    node: &TypedNode,\n) -> TractResult<Option<Box<dyn TypedOp>>> {\n    type TranslateFn = fn(&TypedModel, &TypedNode) -> TractResult<Option<Box<dyn TypedOp>>>;\n    static MAP: OnceLock<HashMap<TypeId, Vec<TranslateFn>>> = OnceLock::new();\n    let map = MAP.get_or_init(|| {\n        let mut m: HashMap<TypeId, Vec<TranslateFn>> = HashMap::new();\n        for t in inventory::iter::<MetalOpTranslator> {\n            m.entry(t.type_id).or_default().push(t.try_make);\n        }\n        m\n    });\n\n    let input_facts = source.node_input_facts(node.id)?;\n    if !input_facts.iter().all(|f| DeviceTensor::is_supported_dt(f.datum_type)) {\n        return Ok(None);\n    }\n\n    // Copy-based ops are fully generic (no backend-specific dispatch needed).\n    if let Some(op) = tract_gpu::ops::copy_based::try_make_copy_based_op(source, node)? {\n        return Ok(Some(op));\n    }\n\n    if let Some(fns) = map.get(&(*node.op).type_id()) {\n        for f in fns {\n            if let Some(op) = f(source, node)? {\n                return Ok(Some(op));\n            }\n        }\n    }\n    Ok(None)\n}\n\nimpl Translate<TypedFact, Box<dyn TypedOp>, TypedFact, Box<dyn TypedOp>> for MetalTransform {\n    fn translate_node(\n        &self,\n        source: &TypedModel,\n        node: &TypedNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        // Special multi-node ops handled first\n        let input_facts = source.node_input_facts(node.id)?;\n        if let Some(op) = node.op_as::<PrefixMatMul>() {\n            let facts: Vec<TypedFact> = input_facts.iter().map(|f| (*f).clone()).collect();\n            if !op.transpose_c && op.quantize_output.is_none() && check_matmul_in_dts(&facts) {\n                let mut device_inputs =\n                    sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToDevice)?;\n                let outlet_ids = convert_matmul_to_metal(\n                    source,\n                    node,\n                    target,\n                    &mut device_inputs,\n                    op,\n                    self.gemm_impl,\n                )?;\n                return sync_model_outputs_if_required(source, node, target, outlet_ids);\n            }\n        }\n        if let Some(conv) = node.op_as::<Conv>() {\n            if input_facts.iter().all(|f| DeviceTensor::is_supported_dt(f.datum_type))\n                && matches!(input_facts[0].datum_type, DatumType::F16 | DatumType::F32)\n            {\n                let device_inputs =\n                    sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToDevice)?;\n                let outlet_ids =\n                    ops::conv::wire_metal_conv(source, node, target, &device_inputs, conv)?;\n                return sync_model_outputs_if_required(source, node, target, outlet_ids);\n            }\n        }\n        // Const: inline conversion, not a GPU op\n        if let Some(op) = node.op_as::<Const>() {\n            if DeviceTensor::is_supported_dt(op.val().datum_type()) {\n                let device_inputs =\n                    sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToDevice)?;\n                let outlet_ids =\n                    target.wire_node(node.name.clone(), convert_const(op)?, &device_inputs)?;\n                return sync_model_outputs_if_required(source, node, target, outlet_ids);\n            }\n        }\n\n        // Single-op translation\n        if let Some(gpu_op) = try_make_metal_op(source, node)? {\n            let device_inputs =\n                sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToDevice)?;\n            let outlet_ids = target.wire_node(node.name.clone(), gpu_op, &device_inputs)?;\n            sync_model_outputs_if_required(source, node, target, outlet_ids)\n        } else {\n            let cpu_inputs =\n                sync_inputs_if_required(target, node, mapping, DeviceSyncKind::ToHost)?;\n            target.wire_node(&node.name, node.op.clone(), &cpu_inputs)\n        }\n    }\n}\n\npub(crate) fn metal_cast_new(to: DatumType) -> Option<tract_gpu::ops::cast::GpuCast> {\n    tract_gpu::ops::cast::GpuCast::new(\n        to,\n        \"Metal\",\n        kernels::array::metal_cast_dispatch,\n        kernels::array::Cast::is_supported_dt,\n    )\n}\n\nfn check_matmul_in_dts(in_facts: &[TypedFact]) -> bool {\n    MlxGemm.is_supported_dts(in_facts)\n        || MfaGemm.is_supported_dts(in_facts)\n        || GgmlGemm.is_supported_dts(in_facts)\n        || GgmlGemm.is_supported_dts(&[in_facts[1].clone(), in_facts[0].clone()])\n}\n\nfn is_input_broadcast(facts: TVec<&TypedFact>) -> bool {\n    // Assume weights are in second postion\n    let b_batch_dims: Vec<TDim> = if as_quant_fact(facts[1], &Q4_0).is_some() {\n        facts[1].shape.dims().to_vec()\n    } else {\n        let rank = facts[1].rank();\n        facts[1].shape.dims()[..rank - 2].to_vec()\n    };\n\n    let a_rank = facts[0].rank();\n    let mut a_batch_dims = facts[0].shape[..(a_rank - 2)].to_vec();\n\n    a_batch_dims.retain(|tdim| !matches!(tdim, TDim::Sym(_)) || b_batch_dims.contains(tdim));\n    let symb_in_a = a_batch_dims != facts[0].shape[..(a_rank - 2)].to_vec();\n\n    let a_batch_size = a_batch_dims.iter().product::<TDim>().gcd();\n    let b_batch_size = b_batch_dims.iter().product::<TDim>().gcd();\n\n    (a_batch_size % b_batch_size == 0) && ((a_batch_size != b_batch_size) || symb_in_a)\n}\n\npub fn resolve_gemm_impl(\n    gemm_impl: Option<MetalGemmImplKind>,\n    input_facts: TVec<&TypedFact>,\n) -> TractResult<MetalGemmImplKind> {\n    if let Some(gemm) = gemm_impl {\n        Ok(gemm)\n    } else if as_quant_fact(input_facts[0], &Q4_0).is_some()\n        || as_quant_fact(input_facts[1], &Q4_0).is_some()\n        || input_facts[0].datum_type != input_facts[1].datum_type\n        || is_input_broadcast(input_facts)\n    {\n        Ok(MetalGemmImplKind::Ggml)\n    } else {\n        Ok(MetalGemmImplKind::Mlx)\n    }\n}\n\nfn convert_matmul_to_metal(\n    model: &TypedModel,\n    node: &TypedNode,\n    target: &mut TypedModel,\n    inputs: &mut [OutletId],\n    op: &PrefixMatMul,\n    gemm_impl: Option<MetalGemmImplKind>,\n) -> TractResult<TVec<OutletId>> {\n    let mut input_facts = model.node_input_facts(node.id)?;\n\n    let resolved_gemm_impl = resolve_gemm_impl(gemm_impl, input_facts.clone())?;\n    if matches!(resolved_gemm_impl, MetalGemmImplKind::Mlx | MetalGemmImplKind::Mfa)\n        && (input_facts[0].datum_type != input_facts[1].datum_type)\n    {\n        ensure!(\n            input_facts[0].datum_type == DatumType::F16\n                || input_facts[1].datum_type == DatumType::F16\n        );\n        let inp_to_cast = if input_facts[0].datum_type == DatumType::F16 {\n            &mut inputs[0]\n        } else {\n            &mut inputs[1]\n        };\n        *inp_to_cast = target.wire_node(\n            node.name.clone() + \".cast_input\",\n            metal_cast_new(DatumType::F32).unwrap(),\n            &[*inp_to_cast],\n        )?[0];\n    }\n\n    let mut matmul_output = match resolved_gemm_impl {\n        MetalGemmImplKind::Mlx => {\n            let op = ops::MetalGemm::<MlxGemm>::new(op.transpose_a, op.transpose_b);\n            target.wire_node(node.name.clone(), op, inputs)?\n        }\n        MetalGemmImplKind::Mfa => {\n            let op = ops::MetalGemm::<MfaGemm>::new(op.transpose_a, op.transpose_b);\n            target.wire_node(node.name.clone(), op, inputs)?\n        }\n        MetalGemmImplKind::Ggml => {\n            let mut swap_inputs = false;\n            if !GgmlGemm.is_supported_dts(&[input_facts[0].clone(), input_facts[1].clone()])\n                && GgmlGemm.is_supported_dts(&[input_facts[1].clone(), input_facts[0].clone()])\n            {\n                input_facts.swap(0, 1);\n                inputs.swap(0, 1);\n                swap_inputs = true;\n            }\n\n            let a_pos = swap_inputs as usize;\n            let b_pos = 1 - swap_inputs as usize;\n            if op.transpose_a {\n                ensure!(\n                    as_quant_fact(input_facts[a_pos], &Q4_0).is_none(),\n                    \"Cannot transpose Q40 tensor\"\n                );\n\n                let rank = input_facts[a_pos].rank();\n                let perm_a_op =\n                    tract_gpu::ops::change_axes::GpuAxisOp::new(AxisOp::Move(rank - 2, rank - 1));\n                let perm_a_name = node.name.clone() + \".perm_a\";\n                inputs[a_pos] = target.wire_node(perm_a_name, perm_a_op, &[inputs[a_pos]])?[0];\n            }\n\n            if input_facts[0].datum_type == DatumType::F16 {\n                let in_cast_op = metal_cast_new(DatumType::F32).unwrap();\n                inputs[0] =\n                    target.wire_node(node.name.clone() + \".in_cast\", in_cast_op, &[inputs[0]])?[0];\n            }\n\n            if !op.transpose_b {\n                ensure!(\n                    as_quant_fact(input_facts[b_pos], &Q4_0).is_none(),\n                    \"Cannot transpose Q40 tensor\"\n                );\n\n                let rank = input_facts[b_pos].rank();\n                let perm_b_op =\n                    tract_gpu::ops::change_axes::GpuAxisOp::new(AxisOp::Move(rank - 2, rank - 1));\n                let perm_b_name = node.name.clone() + \".perm_b\";\n                inputs[b_pos] = target.wire_node(perm_b_name, perm_b_op, &[inputs[b_pos]])?[0];\n            }\n            let op = ops::MetalGemm::<GgmlGemm>::new(false, true);\n            let mut matmul_output = target.wire_node(node.name.clone(), op, inputs)?;\n\n            if swap_inputs {\n                let out_fact = target.outlet_fact(matmul_output[0])?;\n                let rank = &out_fact\n                    .exotic_fact\n                    .clone()\n                    .map(|fact| fact.clarify_dt_shape().unwrap().1.len())\n                    .unwrap();\n\n                let perm_out_op =\n                    tract_gpu::ops::change_axes::GpuAxisOp::new(AxisOp::Move(rank - 2, rank - 1));\n                matmul_output = target.wire_node(\n                    node.name.clone() + \".perm_out\",\n                    perm_out_op,\n                    &matmul_output,\n                )?;\n            }\n            matmul_output\n        }\n    };\n\n    let out_fact = target.outlet_fact(matmul_output[0])?;\n    let out_dt = out_fact.as_device_fact().map(|f| f.datum_type).unwrap_or(out_fact.datum_type);\n\n    let expected_dt = model.node_output_facts(node.id)?[0].datum_type;\n\n    if out_dt != expected_dt {\n        ensure!(\n            kernels::array::Cast::is_supported_dt(out_dt),\n            \"Matmul output type cannot be casted to expected type\"\n        );\n        let cast_op = metal_cast_new(model.node_output_facts(node.id)?[0].datum_type).unwrap();\n        matmul_output =\n            target.wire_node(node.name.clone() + \".out_cast\", cast_op, &matmul_output)?\n    }\n    Ok(matmul_output)\n}\n\nfn convert_const(op: &Const) -> TractResult<Const> {\n    let typed_fact: TypedFact = Arc::clone(op.val()).try_into()?;\n    let metal_fact = if let Some(of) = op.exotic_fact() {\n        DeviceFact::from_host(typed_fact.with_exotic_fact(clone_box(of)))?\n    } else {\n        DeviceFact::from_host(typed_fact)?\n    };\n\n    let metal_const = op.val().clone().into_device()?.into_tensor().into_arc_tensor();\n    Const::new_with_exotic_fact(metal_const, Box::new(metal_fact))\n}\n"
  },
  {
    "path": "metal/src/utils.rs",
    "content": "#![allow(clippy::missing_safety_doc)]\n\nuse crate::context::MetalBuffer;\nuse metal::Buffer;\nuse tract_gpu::tensor::DeviceTensor;\n\npub fn get_metal_buffer(tensor: &DeviceTensor) -> &Buffer {\n    if let Some(metal_buffer) = tensor.device_buffer().downcast_ref::<MetalBuffer>() {\n        &metal_buffer.inner\n    } else {\n        panic!(\"Non-Metal Buffer accessed during Metal execution\")\n    }\n}\n#[cfg(test)]\npub use tests::with_borrowed_metal_stream;\n\n#[cfg(test)]\nmod tests {\n    use std::ffi::c_void;\n\n    use crate::MetalStream;\n    use objc::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush};\n    use tract_core::internal::*;\n\n    // Copied code from objc crate to avoid closures\n    struct AutoReleaseHelper {\n        context: *mut c_void,\n    }\n\n    impl AutoReleaseHelper {\n        unsafe fn new() -> Self {\n            AutoReleaseHelper { context: unsafe { objc_autoreleasePoolPush() } }\n        }\n    }\n\n    impl Drop for AutoReleaseHelper {\n        fn drop(&mut self) {\n            unsafe { objc_autoreleasePoolPop(self.context) }\n        }\n    }\n\n    pub fn with_borrowed_metal_stream<T, F: FnOnce(&MetalStream) -> TractResult<T>>(\n        f: F,\n    ) -> TractResult<T> {\n        let _context = unsafe { AutoReleaseHelper::new() };\n        crate::with_metal_stream(f)\n    }\n}\n"
  },
  {
    "path": "nnef/Cargo.toml",
    "content": "[package]\nname = \"tract-nnef\"\nversion = \"0.23.0-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"NeuralNetworks\", \"NNEF\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nbyteorder.workspace = true\nliquid.workspace = true\nliquid-core.workspace = true\nlog.workspace = true\nnom.workspace = true\nnom-language.workspace = true\nsafetensors.workspace = true\nserde_json.workspace = true\ntar.workspace = true\nflate2 = { workspace = true, optional = true }\nsimd-adler32.workspace = true\nwalkdir.workspace = true\nerased-serde.workspace = true\nserde.workspace = true\ntract-core.workspace = true\n\n[dev-dependencies]\ntemp-dir = \"0.2.0\"\nserde_json.workspace = true\n\n[features]\ndefault = [\"flate2\"]\ncomplex = [ \"tract-core/complex\" ]\n"
  },
  {
    "path": "nnef/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "nnef/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "nnef/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "nnef/cli/Cargo.toml",
    "content": "[package]\nname = \"tract-nnef-cli\"\nversion = \"0.21.8-pre\"\nauthors = [\n\t\"Mathieu Poumeyrol <kali@zoy.org>\",\n\t\"Hubert de La Jonquière <hubert.delajonquiere@sonos.com>\"\n]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"NeuralNetworks\", \"NNEF\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[dependencies]\nanyhow.workspace = true\nenv_logger.workspace = true\nlog.workspace = true\nclap.workspace = true\ntract-nnef.workspace = true\ntract-pulse.workspace = true\ntract-onnx-opl.workspace = true\n"
  },
  {
    "path": "nnef/cli/src/main.rs",
    "content": "use anyhow::{Context, Result, anyhow};\nuse clap::Parser;\nuse std::path::PathBuf;\nuse tract_nnef::internal::DocDumper;\n\nfn main() {\n    // Collecting user arguments\n    let cli_args = CliArgs::parse();\n\n    // Setting up log level\n    let level = match cli_args.verbosity {\n        0 => \"info\",\n        1 => \"debug\",\n        _ => \"trace\",\n    };\n    unsafe { std::env::set_var(\"RUST_LOG\", level) };\n    env_logger::Builder::from_env(env_logger::Env::default()).init();\n\n    if let Err(e) = cli_args.run() {\n        log::error!(\"{e:?}\");\n        std::process::exit(1)\n    }\n}\n\n/// Struct used to define NNEF documentation CLI arguments.\n#[derive(Debug, Parser)]\n#[command(\n    name = \"tract NNEF doc command line\",\n    about = \"Command line to generate NNEF documentaion\"\n)]\npub struct CliArgs {\n    #[arg(short = 'v', action = clap::ArgAction::Count)]\n    pub verbosity: u8,\n    /// Path to write to the directory where to write the NNEF documentations\n    #[arg(long = \"doc-dir\")]\n    pub docs_path: PathBuf,\n}\n\nimpl CliArgs {\n    pub fn run(&self) -> Result<()> {\n        let registries = vec![\n            (\"tract-core.nnef\", tract_nnef::ops::tract_core()),\n            (\"tract-resource.nnef\", tract_nnef::ops::tract_resource()),\n            (\"tract-pulse.nnef\", tract_pulse::tract_nnef_registry()),\n            (\"tract-onnx.nnef\", tract_onnx_opl::onnx_opl_registry()),\n        ];\n\n        for (filename, registry) in registries {\n            let path = self.docs_path.join(filename);\n            DocDumper::registry_to_path(self.docs_path.join(filename), &registry).with_context(\n                || {\n                    anyhow!(\n                        \"Error while dumping NNEF documentation for {:?} registry at path {:?}\",\n                        registry.id,\n                        path\n                    )\n                },\n            )?;\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "nnef/nnef-resources/Cargo.toml",
    "content": "[package]\nname = \"tract-nnef-resources\"\nversion = \"0.23.0-pre\"\nauthors = [\n\t\"Mathieu Poumeyrol <kali@zoy.org>\",\n\t\"Hubert de La Jonquière <hubert.delajonquiere@sonos.com>\"\n]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"NeuralNetworks\", \"NNEF\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nserde_json.workspace = true\nserde.workspace = true\nanyhow.workspace = true\nliquid-core.workspace = true\nnom.workspace = true\nnom-language.workspace = true\ntract-nnef.workspace = true\n\n\n"
  },
  {
    "path": "nnef/nnef-resources/src/json_loader.rs",
    "content": "use anyhow::Result;\nuse nom::branch::alt;\nuse nom::character::complete::{char, digit1};\nuse nom::combinator::{all_consuming, opt};\nuse nom::combinator::{map, map_res};\nuse nom::error::ErrorKind;\nuse nom::multi::separated_list1;\nuse nom::sequence::delimited;\nuse nom::{AsChar, Parser};\nuse nom::{IResult, Input};\nuse nom_language::error::VerboseError;\nuse std::path::Path;\nuse tract_nnef::internal::*;\n\ntype R<'i, O> = IResult<&'i str, O, VerboseError<&'i str>>;\n\n/// Loader for JSON resources inside a NNEF archive\n#[derive(Debug, Clone, PartialEq)]\npub struct JsonLoader;\n\nimpl ResourceLoader for JsonLoader {\n    fn name(&self) -> StaticName {\n        \"JsonLoader\".into()\n    }\n\n    fn try_load(\n        &self,\n        path: &Path,\n        reader: &mut dyn std::io::Read,\n        _framework: &tract_nnef::framework::Nnef,\n    ) -> TractResult<Option<(String, Arc<dyn Resource>)>> {\n        if path.extension().map(|e| e == \"json\").unwrap_or(false) {\n            let value = serde_json::from_reader(reader)\n                .with_context(|| anyhow!(\"Error while parsing JSON\"))?;\n            Ok(Some((\n                tract_nnef::resource::resource_path_to_id(path)?,\n                Arc::new(JsonResource(value)),\n            )))\n        } else {\n            Ok(None)\n        }\n    }\n}\n\n/// JSON resource than can be queried while loading a NNEF graph.\n#[derive(Debug, Clone, PartialEq)]\npub struct JsonResource(pub serde_json::Value);\n\nimpl Resource for JsonResource {\n    fn get(&self, key: &str) -> TractResult<Value> {\n        let value = JsonPath::parse(key)\n            .with_context(|| anyhow!(\"Error while parsing JSON path: {:?}\", key))?\n            .search(&self.0)\n            .with_context(|| anyhow!(\"Error while acessing JSON using given path: {:?}\", key))?;\n\n        json_to_tract(value)\n            .with_context(|| anyhow!(\"Error while converting JSON value to NNEF value\"))\n    }\n\n    fn to_liquid_value(&self) -> Option<liquid_core::model::Value> {\n        Some(json_to_liquid(&self.0))\n    }\n}\n\nfn json_to_liquid(json: &serde_json::Value) -> liquid_core::model::Value {\n    use liquid_core::model::Value as L;\n    use serde_json::Value as J;\n    match json {\n        J::Number(n) => {\n            if let Some(n) = n.as_i64() {\n                L::Scalar(n.into())\n            } else {\n                L::Scalar(n.as_f64().unwrap().into())\n            }\n        }\n        J::Null => L::Nil,\n        J::Bool(b) => L::Scalar((*b).into()),\n        J::String(s) => L::Scalar(s.clone().into()),\n        J::Array(values) => L::Array(values.iter().map(json_to_liquid).collect()),\n        J::Object(map) => {\n            L::Object(map.iter().map(|(k, v)| (k.into(), json_to_liquid(v))).collect())\n        }\n    }\n}\n\npub fn json_to_tract(value: &serde_json::Value) -> TractResult<Value> {\n    match value {\n        serde_json::Value::Bool(b) => Ok(Value::Bool(*b)),\n        serde_json::Value::Number(v) => {\n            if let Some(v) = v.as_i64() {\n                Ok(Value::Dim(TDim::Val(v)))\n            } else {\n                let v = v.as_f64().ok_or_else(|| {\n                    anyhow!(\"Json number {} could not be cast to floating value\", v)\n                })?;\n                Ok(Value::Scalar(v as f32))\n            }\n        }\n        serde_json::Value::String(s) => Ok(Value::String(s.clone())),\n        serde_json::Value::Null => bail!(\"JSON null value cannot be converted to NNEF value\"),\n        serde_json::Value::Object(_) => bail!(\"JSON object cannot be converted to NNEF value\"),\n        serde_json::Value::Array(values) => {\n            let t_values = values.iter().map(json_to_tract).collect::<Result<Vec<Value>>>()?;\n            Ok(Value::Array(t_values))\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum JsonComponent {\n    Root,\n    Field(String),\n    Index(usize),\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct JsonPath {\n    pub components: Vec<JsonComponent>,\n}\n\nimpl JsonPath {\n    pub fn new(components: Vec<JsonComponent>) -> Self {\n        Self { components }\n    }\n\n    pub fn parse(s: &str) -> Result<Self> {\n        let (_, components) = all_consuming(parse_components)\n            .parse(s)\n            .map_err(|e| anyhow!(\"Error while parsing JSON path: {:?}\", e))?;\n\n        ensure!(\n            components.first() == Some(&JsonComponent::Root),\n            \"Json path must start with the root symbol '$'. None found in {}\",\n            s\n        );\n\n        Ok(Self::new(components))\n    }\n\n    pub fn search<'a>(&self, json: &'a serde_json::Value) -> Result<&'a serde_json::Value> {\n        let mut components_iter = self.components.iter();\n        ensure!(\n            components_iter.next() == Some(&JsonComponent::Root),\n            \"JSON path must start with root key '$'\"\n        );\n\n        let value = components_iter\n            .try_fold(json, |json, component| match component {\n                JsonComponent::Index(idx) => Ok(&json[idx]),\n                JsonComponent::Field(field) => Ok(&json[field]),\n                JsonComponent::Root => bail!(\"Unexpected '$'(root) symbol in json path\"),\n            })\n            .with_context(|| anyhow!(\"Error while accessing JSON with path: {:?}\", self))?;\n        Ok(value)\n    }\n}\n\npub fn json_key(input: &str) -> R<'_, &str> {\n    input.split_at_position1_complete(\n        |item| {\n            let c = item.as_char();\n            !(c.is_alphanum() || ['-', '_', '+', '='].contains(&c))\n        },\n        ErrorKind::Fail,\n    )\n}\n\nfn parse_components(i: &str) -> R<'_, Vec<JsonComponent>> {\n    map(\n        separated_list1(\n            char('.'),\n            map(\n                (\n                    alt((\n                        map(char('$'), |_| JsonComponent::Root),\n                        map(json_key, |f: &str| JsonComponent::Field(f.to_string())),\n                    )),\n                    opt(map_res(delimited(char('['), digit1, char(']')), |s: &str| {\n                        s.parse().map(JsonComponent::Index)\n                    })),\n                ),\n                |(c, idx)| vec![Some(c), idx].into_iter().flatten().collect::<Vec<_>>(),\n            ),\n        ),\n        |components| components.into_iter().flatten().collect::<Vec<_>>(),\n    )\n    .parse(i)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use anyhow::Result;\n    use serde_json::json;\n\n    #[test]\n    fn test_json_key() -> Result<()> {\n        let example: serde_json::Value = json!({\n            \"name\": \"John Doe\",\n            \"age\": 43usize,\n            \"phones\": [\n                \"+44 1234567\",\n                \"+44 2345678\"\n            ],\n            \"others-info\": {\n                \"address\": [\"Sonos\"],\n                \"indexes\": [1, 2, 3, 4, 5, 6],\n                \"weights\": [[1, 2], [3, 4], [5, 6]]\n            }\n        });\n        let resource = JsonResource(example);\n        assert_eq!(resource.get(\"$.name\")?, Value::String(\"John Doe\".into()));\n        assert_eq!(resource.get(\"$.age\")?, Value::Dim(TDim::Val(43)));\n        assert_eq!(resource.get(\"$.phones[0]\")?, Value::String(\"+44 1234567\".into()));\n        assert_eq!(resource.get(\"$.others-info.address[0]\")?, Value::String(\"Sonos\".into()));\n        assert_eq!(\n            resource.get(\"$.others-info.indexes\")?,\n            Value::Array(vec![\n                Value::Dim(TDim::Val(1)),\n                Value::Dim(TDim::Val(2)),\n                Value::Dim(TDim::Val(3)),\n                Value::Dim(TDim::Val(4)),\n                Value::Dim(TDim::Val(5)),\n                Value::Dim(TDim::Val(6))\n            ])\n        );\n        assert_eq!(\n            resource.get(\"$.others-info.weights\")?,\n            Value::Array(vec![\n                Value::Array(vec![Value::Dim(TDim::Val(1)), Value::Dim(TDim::Val(2)),]),\n                Value::Array(vec![Value::Dim(TDim::Val(3)), Value::Dim(TDim::Val(4)),]),\n                Value::Array(vec![Value::Dim(TDim::Val(5)), Value::Dim(TDim::Val(6))]),\n            ])\n        );\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "nnef/nnef-resources/src/lib.rs",
    "content": "pub mod json_loader;\n\npub mod internal {\n    pub use crate::json_loader::{JsonLoader, JsonResource};\n}\n"
  },
  {
    "path": "nnef/nnef-resources/tests/nnef_with_json/graph.nnef",
    "content": "version 1.0;\n\nextension tract_registry tract_resource;\n\ngraph afe_graph(src) -> (output)\n{\n    w = tract_resource_get(label = \"src_config\", key = \"$.width\");\n    h = tract_resource_get(label = \"src_config\", key = \"$.height\");\n    src = external<scalar>(shape = [w, h]);\n    output = src;\n}"
  },
  {
    "path": "nnef/nnef-resources/tests/nnef_with_json/src_config.json",
    "content": "{\n    \"width\": 2,\n    \"height\": 10\n}"
  },
  {
    "path": "nnef/nnef-resources/tests/test_json_resource.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef_resources::internal::JsonLoader;\n\n#[test]\nfn load_model_with_json_resource() -> TractResult<()> {\n    let model = tract_nnef::nnef()\n        .with_tract_core()\n        .with_tract_resource()\n        .with_resource_loader(JsonLoader)\n        .model_for_path(\"tests/nnef_with_json\")?;\n\n    assert_eq!(model.input_fact(0)?.shape.as_concrete().unwrap(), &vec![2, 10]);\n    assert_eq!(model.output_fact(0)?.shape.as_concrete().unwrap(), &vec![2, 10]);\n    Ok(())\n}\n"
  },
  {
    "path": "nnef/src/ast/dump.rs",
    "content": "use std::io::Write;\n\nuse crate::ast::*;\nuse tract_core::internal::*;\nuse tract_itertools::Itertools;\n\nmacro_rules! comma_loop {\n    ($self:ident, $rec: ident, $items: expr) => {\n        for (ix, l) in $items.iter().enumerate() {\n            if ix > 0 {\n                write!($self.w, \", \")?;\n            }\n            $self.$rec(l)?;\n        }\n    };\n}\n\npub struct Dumper<'a> {\n    nnef: &'a Nnef,\n    w: &'a mut dyn std::io::Write,\n    with_doc: bool,\n}\n\nimpl<'a> Dumper<'a> {\n    pub fn new(nnef: &'a Nnef, w: &'a mut dyn std::io::Write) -> Dumper<'a> {\n        Dumper { nnef, w, with_doc: false }\n    }\n\n    pub fn with_doc(mut self) -> Self {\n        self.with_doc = true;\n        self\n    }\n\n    pub fn document(&mut self, document: &Document) -> TractResult<()> {\n        writeln!(self.w, \"version {};\\n\", document.version)?;\n        for ext in document.extension.iter().sorted() {\n            write!(self.w, \"extension \")?;\n            self.identifier(&ext.0)?;\n            writeln!(self.w, \" {};\", ext.1)?;\n        }\n        if document.extension.len() > 0 {\n            writeln!(self.w)?;\n        }\n        self.fragments(&document.fragments)?;\n        self.graph_def(&document.graph_def)?;\n        Ok(())\n    }\n\n    pub fn fragments(&mut self, defs: &[FragmentDef]) -> TractResult<()> {\n        for fragment_def in defs.iter().sorted_by_key(|frag| &frag.decl.id) {\n            self.fragment_def(fragment_def)?\n        }\n        Ok(())\n    }\n\n    pub fn fragment_def(&mut self, def: &FragmentDef) -> TractResult<()> {\n        self.fragment_decl(&def.decl)?;\n        if let Some(body) = &def.body {\n            writeln!(self.w, \"\\n{{\")?;\n            for assignment in body {\n                self.assignment(assignment)?;\n            }\n            writeln!(self.w, \"}}\\n\")?;\n        } else {\n            writeln!(self.w, \";\")?;\n        };\n        Ok(())\n    }\n\n    pub(crate) fn fragment_decl(&mut self, decl: &FragmentDecl) -> TractResult<()> {\n        write!(self.w, \"fragment \")?;\n        self.identifier(&decl.id)?;\n        if let Some(generic_decl) = &decl.generic_decl {\n            if let Some(name) = generic_decl {\n                write!(self.w, \"<?=\")?;\n                self.type_name(name)?;\n                write!(self.w, \">\")?;\n            } else {\n                write!(self.w, \"<?>\")?;\n            }\n        }\n        self.parameter_list(&decl.parameters)?;\n        write!(self.w, \" -> (\")?;\n        for (ix, res) in decl.results.iter().enumerate() {\n            if ix > 0 {\n                write!(self.w, \", \")?;\n            }\n            self.identifier(&res.id)?;\n            write!(self.w, \": \")?;\n            self.type_spec(&res.spec)?;\n        }\n        write!(self.w, \")\")?;\n        Ok(())\n    }\n\n    fn parameter_list(&mut self, parameters: &[Parameter]) -> TractResult<()> {\n        write!(self.w, \"(\")?;\n        let num_parameters = parameters.len();\n        for (ix, param) in parameters.iter().enumerate() {\n            if self.with_doc\n                && let Some(doc) = &param.doc\n            {\n                write!(self.w, \"\\n    # {doc}\")?;\n            }\n            write!(self.w, \"\\n    \")?;\n            self.identifier(&param.id)?;\n            write!(self.w, \": \")?;\n            self.type_spec(&param.spec)?;\n            if let Some(lit) = &param.lit {\n                write!(self.w, \" = \")?;\n                self.literal(lit)?;\n            }\n            if ix < num_parameters - 1 {\n                write!(self.w, \",\")?;\n            }\n        }\n        write!(self.w, \"\\n)\")?;\n        Ok(())\n    }\n\n    fn type_name(&mut self, name: &TypeName) -> TractResult<()> {\n        let s = match name {\n            TypeName::Integer => \"integer\",\n            TypeName::Scalar => \"scalar\",\n            TypeName::Logical => \"logical\",\n            TypeName::String => \"string\",\n            #[cfg(feature = \"complex\")]\n            TypeName::Complex => \"complex\",\n            TypeName::Any => \"?\",\n        };\n        write!(self.w, \"{s}\")?;\n        Ok(())\n    }\n\n    fn type_spec(&mut self, spec: &TypeSpec) -> TractResult<()> {\n        match spec {\n            TypeSpec::Array(t) => {\n                self.type_spec(t)?;\n                write!(self.w, \"[]\")?;\n            }\n            TypeSpec::Single(s) => self.type_name(s)?,\n            TypeSpec::Tensor(t) => {\n                write!(self.w, \"tensor<\")?;\n                self.type_name(t)?;\n                write!(self.w, \">\")?;\n            }\n            TypeSpec::Tuple(types) => {\n                write!(self.w, \"(\")?;\n                comma_loop!(self, type_spec, types);\n                write!(self.w, \")\")?;\n            }\n        }\n        Ok(())\n    }\n\n    fn literal(&mut self, lit: &Literal) -> TractResult<()> {\n        match lit {\n            Literal::Array(lits) => {\n                write!(self.w, \"[\")?;\n                comma_loop!(self, literal, lits);\n                write!(self.w, \"]\")?;\n            }\n            Literal::Logical(b) => write!(self.w, \"{}\", if *b { \"true\" } else { \"false\" })?,\n            Literal::Numeric(num) => write!(self.w, \"{num}\")?,\n            Literal::String(s) => write!(self.w, \"{s:?}\")?,\n            Literal::Tuple(lits) => {\n                write!(self.w, \"(\")?;\n                comma_loop!(self, literal, lits);\n                write!(self.w, \")\")?;\n            }\n        }\n        Ok(())\n    }\n\n    fn graph_def(&mut self, def: &GraphDef) -> TractResult<()> {\n        write!(self.w, \"graph \")?;\n        self.identifier(&def.id)?;\n        write!(self.w, \"(\")?;\n        for (ix, id) in def.parameters.iter().enumerate() {\n            if ix > 0 {\n                write!(self.w, \", \")?;\n            }\n            self.identifier(id)?;\n        }\n        write!(self.w, \") -> (\")?;\n        for (ix, id) in def.results.iter().enumerate() {\n            if ix > 0 {\n                write!(self.w, \", \")?;\n            }\n            self.identifier(id)?;\n        }\n        writeln!(self.w, \") {{\")?;\n        for assignment in &def.body {\n            self.assignment(assignment)?;\n        }\n        writeln!(self.w, \"}}\")?;\n        Ok(())\n    }\n\n    fn assignment(&mut self, assignment: &Assignment) -> TractResult<()> {\n        write!(self.w, \"  \")?;\n        self.lvalue(&assignment.left)?;\n        write!(self.w, \" = \")?;\n        self.rvalue(&assignment.right)?;\n        writeln!(self.w, \";\")?;\n        Ok(())\n    }\n\n    fn lvalue(&mut self, left: &LValue) -> TractResult<()> {\n        match left {\n            LValue::Identifier(s) => self.identifier(s)?,\n            LValue::Tuple(s) => {\n                write!(self.w, \"( \")?;\n                comma_loop!(self, lvalue, s);\n                write!(self.w, \" )\")?;\n            }\n            LValue::Array(s) => {\n                write!(self.w, \"[ \")?;\n                comma_loop!(self, lvalue, s);\n                write!(self.w, \" ]\")?;\n            }\n        }\n        Ok(())\n    }\n\n    pub fn rvalue(&mut self, rv: &RValue) -> TractResult<()> {\n        match rv {\n            RValue::Array(vals) => {\n                write!(self.w, \"[\")?;\n                comma_loop!(self, rvalue, vals);\n                write!(self.w, \"]\")?;\n            }\n            RValue::Binary(left, op, right) => {\n                write!(self.w, \"(\")?;\n                self.rvalue(left)?;\n                write!(self.w, \" {op} \")?;\n                self.rvalue(right)?;\n                write!(self.w, \")\")?;\n            }\n            RValue::Comprehension(comp) => self.comprehension(comp)?,\n            RValue::Identifier(id) => self.identifier(id)?,\n            RValue::IfThenElse(ifte) => {\n                self.rvalue(&ifte.then)?;\n                write!(self.w, \" if \")?;\n                self.rvalue(&ifte.cond)?;\n                write!(self.w, \" else \")?;\n                self.rvalue(&ifte.otherwise)?;\n            }\n            RValue::Invocation(inv) => self.invocation(inv)?,\n            RValue::Literal(lit) => self.literal(lit)?,\n            RValue::Subscript(left, s) => {\n                self.rvalue(left)?;\n                write!(self.w, \"[\")?;\n                match s.as_ref() {\n                    Subscript::Single(s) => self.rvalue(s)?,\n                    Subscript::Range(a, b) => {\n                        if let Some(it) = a {\n                            self.rvalue(it)?;\n                        }\n                        write!(self.w, \":\")?;\n                        if let Some(it) = b {\n                            self.rvalue(it)?;\n                        }\n                    }\n                }\n                write!(self.w, \"]\")?;\n            }\n            RValue::Tuple(vals) => {\n                write!(self.w, \"(\")?;\n                comma_loop!(self, rvalue, vals);\n                write!(self.w, \")\")?;\n            }\n            RValue::Unary(op, rv) => {\n                write!(self.w, \"{op}\")?;\n                self.rvalue(rv)?;\n            }\n        }\n        Ok(())\n    }\n\n    fn invocation(&mut self, inv: &Invocation) -> TractResult<()> {\n        self.identifier(&inv.id)?;\n        if let Some(tn) = &inv.generic_type_name {\n            write!(self.w, \"<\")?;\n            self.type_name(tn)?;\n            write!(self.w, \">\")?;\n        }\n        write!(self.w, \"(\")?;\n        for (ix, arg) in inv.arguments.iter().enumerate() {\n            if ix > 0 {\n                write!(self.w, \", \")?;\n            }\n            if let Some(n) = &arg.id {\n                self.identifier(n)?;\n                write!(self.w, \" = \")?;\n            }\n            self.rvalue(&arg.rvalue)?;\n        }\n        write!(self.w, \")\")?;\n        Ok(())\n    }\n\n    fn comprehension(&mut self, comp: &Comprehension) -> TractResult<()> {\n        write!(self.w, \"[ for\")?;\n        for iter in &comp.loop_iters {\n            self.identifier(&iter.0)?;\n            write!(self.w, \" in \")?;\n            self.rvalue(&iter.1)?;\n        }\n        if let Some(filter) = &comp.filter {\n            write!(self.w, \" if \")?;\n            self.rvalue(filter)?;\n        }\n        write!(self.w, \" yield \")?;\n        self.rvalue(&comp.yields)?;\n        write!(self.w, \"]\")?;\n        Ok(())\n    }\n\n    fn identifier(&mut self, id: &Identifier) -> TractResult<()> {\n        write_identifier(&mut self.w, id, self.nnef.allow_extended_identifier_syntax, false)\n    }\n}\n\npub fn write_identifier(\n    w: &mut dyn Write,\n    id: &Identifier,\n    allow_extended_identifier_syntax: bool,\n    force_double_quotes: bool,\n) -> TractResult<()> {\n    if id.0.len() == 0 {\n        return Ok(());\n    }\n    let first = id.0.chars().next().unwrap();\n    let force_double_quotes = if force_double_quotes { \"\\\"\" } else { \"\" };\n    if (first.is_alphabetic() || first == '_')\n        && id.0.chars().all(|c| c.is_alphanumeric() || c == '_')\n    {\n        write!(w, \"{force_double_quotes}{}{force_double_quotes}\", id.0)?;\n    } else if allow_extended_identifier_syntax {\n        write!(w, \"i\\\"{}\\\"\", id.0.replace('\\\\', \"\\\\\\\\\").replace('\\\"', \"\\\\\\\"\"))?;\n    } else {\n        write!(w, \"{force_double_quotes}\")?;\n        if !(first.is_alphabetic() || first == '_') {\n            write!(w, \"_\")?;\n        }\n        for c in id.0.chars() {\n            if c.is_alphanumeric() {\n                write!(w, \"{c}\")?;\n            } else {\n                write!(w, \"_\")?;\n            }\n        }\n        write!(w, \"{force_double_quotes}\")?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "nnef/src/ast/dump_doc.rs",
    "content": "use crate::ast::dump::Dumper;\nuse crate::ast::*;\nuse std::path::Path;\nuse tract_core::internal::*;\n\npub struct DocDumper<'a> {\n    w: &'a mut dyn std::io::Write,\n}\n\nimpl DocDumper<'_> {\n    pub fn new(w: &mut dyn std::io::Write) -> DocDumper<'_> {\n        DocDumper { w }\n    }\n\n    pub fn registry(&mut self, registry: &Registry) -> TractResult<()> {\n        // Write registry docstrings.\n        for d in registry.docstrings.iter().flatten() {\n            writeln!(self.w, \"# {d}\")?;\n        }\n        writeln!(self.w)?;\n        // Generate and write unit element wise op.\n        for unit_el_wise_op in registry.unit_element_wise_ops.iter() {\n            // we are assuming function names will not exhibit crazy node name weirdness, so we can\n            // dispense with escaping\n            writeln!(\n                self.w,\n                \"fragment {}( x: tensor<scalar> ) -> (y: tensor<scalar>);\",\n                &unit_el_wise_op.0.0\n            )?;\n        }\n        writeln!(self.w)?;\n\n        // Generate and write element wise op.\n        for el_wise_op in registry.element_wise_ops.iter() {\n            let fragment_decl = FragmentDecl {\n                id: el_wise_op.0.clone(),\n                generic_decl: None,\n                parameters: el_wise_op.3.clone(),\n                results: vec![Result_ { id: \"output\".into(), spec: TypeName::Any.tensor() }],\n            };\n            Dumper::new(&Nnef::default(), self.w).with_doc().fragment_decl(&fragment_decl)?;\n        }\n        // Generate and write Primitive declarations.\n        for primitive in registry.primitives.values().sorted_by_key(|v| &v.decl.id) {\n            primitive.docstrings.iter().flatten().try_for_each(|d| writeln!(self.w, \"# {d}\"))?;\n\n            Dumper::new(&Nnef::default(), self.w).with_doc().fragment_decl(&primitive.decl)?;\n            writeln!(self.w, \";\\n\")?;\n        }\n\n        // Generate and write fragment declarations\n        Dumper::new(&Nnef::default(), self.w)\n            .with_doc()\n            .fragments(registry.fragments.values().cloned().collect::<Vec<_>>().as_slice())?;\n\n        Ok(())\n    }\n\n    pub fn registry_to_path(path: impl AsRef<Path>, registry: &Registry) -> TractResult<()> {\n        let mut file = std::fs::File::create(path.as_ref())\n            .with_context(|| anyhow!(\"Error while creating file at path: {:?}\", path.as_ref()))?;\n        DocDumper::new(&mut file).registry(registry)\n    }\n\n    pub fn to_directory(path: impl AsRef<Path>, nnef: &Nnef) -> TractResult<()> {\n        for registry in nnef.registries.iter() {\n            let registry_file = path.as_ref().join(format!(\"{}.nnef\", registry.id.0));\n            let mut file = std::fs::File::create(&registry_file).with_context(|| {\n                anyhow!(\"Error while creating file at path: {:?}\", registry_file)\n            })?;\n            DocDumper::new(&mut file).registry(registry)?;\n        }\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use temp_dir::TempDir;\n\n    #[test]\n    fn doc_example() -> TractResult<()> {\n        let d = TempDir::new()?;\n        let nnef = crate::nnef().with_tract_core().with_tract_resource();\n        DocDumper::to_directory(d.path(), &nnef)?;\n        Ok(())\n    }\n\n    #[test]\n    fn doc_registry() -> TractResult<()> {\n        let mut registry = Registry::new(\"test_doc\")\n            .with_doc(\"test_doc registry gather all the needed primitives\")\n            .with_doc(\"to test the documentation dumper\");\n        registry.register_primitive(\n            \"tract_primitive\",\n            &[TypeName::Integer.tensor().named(\"input\")],\n            &[(\"output\", TypeName::Scalar.tensor())],\n            |_, _| panic!(\"No deserialization needed\"),\n        );\n        let mut docbytes = vec![];\n        let mut dumper = DocDumper::new(&mut docbytes);\n        dumper.registry(&registry)?;\n        let docstring = String::from_utf8(docbytes)?;\n        assert_eq!(\n            docstring,\n            r#\"# test_doc registry gather all the needed primitives\n# to test the documentation dumper\n\n\nfragment tract_primitive(\n    input: tensor<integer>\n) -> (output: tensor<scalar>);\n\n\"#\n        );\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "nnef/src/ast/parse.rs",
    "content": "use nom_language::error::{VerboseError, convert_error};\nuse tract_core::internal::*;\n\nuse nom::branch::alt;\nuse nom::combinator::map;\nuse nom::{Finish, IResult, Parser};\nuse nom::{bytes::complete::*, character::complete::*, combinator::*, multi::*, sequence::*};\n\nuse crate::ast::*;\n\ntype R<'i, O> = IResult<&'i str, O, VerboseError<&'i str>>;\n\npub(super) fn translate_error(e: nom::Err<VerboseError<&str>>) -> TractError {\n    format_err!(\"{}\", e)\n}\n\n#[inline(never)]\npub fn unwrap_parse<'s, P, O>(input: &'s str, parser: P) -> TractResult<O>\nwhere\n    P: Parser<&'s str, Output = O, Error = VerboseError<&'s str>>,\n{\n    all_consuming(parser)\n        .parse(input)\n        .finish()\n        .map(|(_, p)| p)\n        .map_err(|e| anyhow!(convert_error(input, e)))\n}\n\npub fn parse_document(doc: &str) -> TractResult<Document> {\n    unwrap_parse(doc, document)\n}\n\n#[inline(never)]\npub fn parse_fragments(doc: &str) -> TractResult<Vec<FragmentDef>> {\n    unwrap_parse(doc, fragments)\n}\n\n#[inline(never)]\npub fn parse_fragment_decl(doc: &str) -> TractResult<FragmentDecl> {\n    unwrap_parse(doc, fragment_decl)\n}\n\n#[inline(never)]\npub fn parse_parameters(doc: &str) -> TractResult<Vec<Parameter>> {\n    unwrap_parse(doc, parameter_list)\n}\n\n#[inline(never)]\npub fn parse_assignments(doc: &str) -> TractResult<Vec<Assignment>> {\n    unwrap_parse(doc, many1(assignment))\n}\n\n// <document> ::= <version> <extension>* <fragmentdefinition>* <graph-definition>\nfn document(i: &str) -> R<'_, Document> {\n    map(\n        (version, many0(extension), fragments, graph_def),\n        |(version, extension, fragments, graph_def)| Document {\n            version,\n            extension,\n            fragments,\n            graph_def,\n        },\n    )\n    .parse(i)\n}\n\nfn fragments(i: &str) -> R<'_, Vec<FragmentDef>> {\n    many0(fragment_def).parse(i)\n}\n\n// <version> ::= \"version\" <numeric-literal> \";\"\n\nfn version(i: &str) -> R<'_, NumericLiteral> {\n    preceded(stag(\"version\"), cut(terminated(numeric_literal, stag(\";\")))).parse(i)\n}\n\n// NNEF spec: <extension> ::= \"extension\" <identifier>+ \";\"\n// tract accepts: <extension> ::= \"extension\" <identifier> <anything-but-;>\";\"\nfn extension(i: &str) -> R<'_, (Identifier, String)> {\n    delimited(\n        stag(\"extension\"),\n        pair(spaced(identifier), map(take_until(\";\"), |s: &str| s.to_string())),\n        stag(\";\"),\n    )\n    .parse(i)\n}\n\n// FRAGMENT\n\n// <fragment-definition> ::= <fragment-declaration> (<body> | \";\")\nfn fragment_def(i: &str) -> R<'_, FragmentDef> {\n    spaced(map(\n        pair(fragment_decl, alt((map(body, Some), map(stag(\";\"), |_| None)))),\n        |(decl, body)| FragmentDef { decl, body },\n    ))\n    .parse(i)\n}\n\n// <fragment-declaration> ::= \"fragment\" <identifier> [<generic-declaration>] \"(\" <parameter-list> \")\" \"->\" \"(\" <result-list> \")\"\nfn fragment_decl(i: &str) -> R<'_, FragmentDecl> {\n    preceded(stag(\"fragment\"), cut(commited_fragment_decl)).parse(i)\n}\n\nfn commited_fragment_decl(i: &str) -> R<'_, FragmentDecl> {\n    let (i, id) = identifier(i)?;\n    let (i, generic_decl) = opt(generic_decl).parse(i)?;\n    let (i, _) = stag(\"(\").parse(i)?;\n    let (i, parameters) = cut(parameter_list).parse(i)?;\n    let (i, _) = stag(\")\").parse(i)?;\n    let (i, _) = stag(\"->\").parse(i)?;\n    let (i, _) = stag(\"(\").parse(i)?;\n    let (i, results) = cut(result_list).parse(i)?;\n    let (i, _) = stag(\")\").parse(i)?;\n    Ok((i, FragmentDecl { id, parameters, results, generic_decl }))\n}\n\n// <generic-declaration> ::= \"<\" \"?\" [\"=\" <type-name>] \">\"\nfn generic_decl(i: &str) -> R<'_, Option<TypeName>> {\n    let (i, _) = stag(\"<\").parse(i)?;\n    let (i, _) = stag(\"?\").parse(i)?;\n    let (i, name) = opt(preceded(stag(\"=\"), type_name)).parse(i)?;\n    let (i, _) = stag(\">\").parse(i)?;\n    Ok((i, name))\n}\n\n// <parameter-list> ::= <parameter> (\",\" <parameter>)*\nfn parameter_list(i: &str) -> R<'_, Vec<Parameter>> {\n    separated_list0(stag(\",\"), parameter).parse(i)\n}\n\n// <result-list> ::= <result> (\",\" <result>)*\nfn result_list(i: &str) -> R<'_, Vec<Result_>> {\n    separated_list0(stag(\",\"), result).parse(i)\n}\n\n// <parameter> ::= <identifier> \":\" <type-spec> [\"=\" <literal-expr>]\nfn parameter(i: &str) -> R<'_, Parameter> {\n    map(\n        pair(\n            separated_pair(identifier, stag(\":\"), cut(type_spec)),\n            opt(preceded(stag(\"=\"), literal_expr)),\n        ),\n        |((id, spec), lit)| Parameter { id, spec, lit, doc: None },\n    )\n    .parse(i)\n}\n\n// <result> ::= <identifier> \":\" <type-spec>\nfn result(i: &str) -> R<'_, Result_> {\n    map(separated_pair(identifier, stag(\":\"), cut(type_spec)), |(id, spec)| Result_ { id, spec })\n        .parse(i)\n}\n\nfn literal_expr(i: &str) -> R<'_, Literal> {\n    spaced(alt((\n        literal,\n        map(delimited(stag(\"[\"), separated_list0(stag(\",\"), literal), stag(\"]\")), Literal::Array),\n        map(delimited(stag(\"(\"), separated_list0(stag(\",\"), literal), stag(\")\")), Literal::Tuple),\n    )))\n    .parse(i)\n}\n\n// <type-spec> ::= <type-name> | <tensor-type-spec> | <array-type-spec> | <tuple-type-spec>\nfn type_spec(i: &str) -> R<'_, TypeSpec> {\n    fn non_array_type(i: &str) -> R<'_, TypeSpec> {\n        alt((tuple_type_spec, map(type_name, TypeSpec::Single), tensor_type_spec)).parse(i)\n    }\n    alt((\n        (map(terminated(non_array_type, pair(stag(\"[\"), stag(\"]\"))), |t| {\n            TypeSpec::Array(Box::new(t))\n        })),\n        non_array_type,\n    ))\n    .parse(i)\n}\n\n// <type-name> ::= \"integer\" | \"scalar\" | \"logical\" | \"string\" | \"?\"\nfn type_name(i: &str) -> R<'_, TypeName> {\n    spaced(alt((\n        map(tag(\"integer\"), |_| TypeName::Integer),\n        map(tag(\"scalar\"), |_| TypeName::Scalar),\n        map(tag(\"logical\"), |_| TypeName::Logical),\n        map(tag(\"string\"), |_| TypeName::String),\n        #[cfg(feature = \"complex\")]\n        map(tag(\"complex\"), |_| TypeName::Complex),\n        map(tag(\"?\"), |_| TypeName::Any),\n    )))\n    .parse(i)\n}\n\n// <tensor-type-spec> ::= \"tensor\" \"<\" [<type-name>] \">\"\nfn tensor_type_spec(i: &str) -> R<'_, TypeSpec> {\n    map(delimited(pair(stag(\"tensor\"), stag(\"<\")), type_name, stag(\">\")), TypeSpec::Tensor).parse(i)\n}\n\n// <tuple-type-spec> ::= \"(\" <type-spec> (\",\" <type-spec>)+ \")\"\nfn tuple_type_spec(i: &str) -> R<'_, TypeSpec> {\n    map(delimited(stag(\"(\"), separated_list0(stag(\",\"), type_spec), stag(\")\")), TypeSpec::Tuple)\n        .parse(i)\n}\n\n// GRAPH\n\n// <graph-definition> ::= <graph-declaration> <body>\n// <graph-declaration> ::= \"graph\" <identifier> \"(\" <identifier-list> \")\" \"->\" \"(\" <identifier-list> \")\"\n// <identifier-list> ::= <identifier> (\",\" <identifier>)*\nfn graph_def(i: &str) -> R<'_, GraphDef> {\n    let (i, _) = stag(\"graph\").parse(i)?;\n    let (i, id) = identifier(i)?;\n    let (i, _) = stag(\"(\").parse(i)?;\n    let (i, parameters) = separated_list0(stag(\",\"), identifier).parse(i)?;\n    let (i, _) = stag(\")\").parse(i)?;\n    let (i, _) = stag(\"->\").parse(i)?;\n    let (i, _) = stag(\"(\").parse(i)?;\n    let (i, results) = separated_list0(stag(\",\"), identifier).parse(i)?;\n    let (i, _) = stag(\")\").parse(i)?;\n    let (i, body) = spaced(body).parse(i)?;\n    Ok((i, GraphDef { id, parameters, results, body }))\n}\n\n// BODY\n\n// <body> ::= \"{\" <assignment>+ \"}\"\nfn body(i: &str) -> R<'_, Vec<Assignment>> {\n    delimited(stag(\"{\"), many0(assignment), stag(\"}\")).parse(i)\n}\n\n// <assignment> ::= <lvalue-expr> \"=\" <rvalue-expr> \";\"\nfn assignment(i: &str) -> R<'_, Assignment> {\n    spaced(terminated(\n        map(separated_pair(lvalue, stag(\"=\"), rvalue), |(left, right)| Assignment { left, right }),\n        stag(\";\"),\n    ))\n    .parse(i)\n}\n\n// <lvalue-expr> ::= <identifier> | <array-lvalue-expr> | <tuple-lvalue-expr>\n// <array-lvalue-expr> ::= \"[\" [<lvalue-expr> (\",\" <lvalue-expr>)* ] \"]\"\n// <tuple-lvalue-expr> ::= \"(\" <lvalue-expr> (\",\" <lvalue-expr>)+ \")\" | <lvalue-expr> (\",\" <lvalue-expr>)+\nfn lvalue(i: &str) -> R<'_, LValue> {\n    fn inner_lvalue(i: &str) -> R<'_, LValue> {\n        alt((\n            map(\n                delimited(stag(\"[\"), separated_list0(stag(\",\"), inner_lvalue), stag(\"]\")),\n                LValue::Array,\n            ),\n            map(\n                delimited(stag(\"(\"), separated_list0(stag(\",\"), inner_lvalue), stag(\")\")),\n                LValue::Tuple,\n            ),\n            map(spaced(identifier), LValue::Identifier),\n        ))\n        .parse(i)\n    }\n\n    map(separated_list0(stag(\",\"), inner_lvalue), |mut iv| {\n        if iv.len() == 1 { iv.remove(0) } else { LValue::Tuple(iv) }\n    })\n    .parse(i)\n}\n\n// <invocation> ::= <identifier> [\"<\" <type-name> \">\"] \"(\" <argument-list> \")\"\nfn invocation(i: &str) -> R<'_, Invocation> {\n    let (i, id) = spaced(identifier).parse(i)?;\n    let (i, generic_type_name) = opt(delimited(stag(\"<\"), type_name, stag(\">\"))).parse(i)?;\n    let (i, _) = stag(\"(\").parse(i)?;\n    let (i, arguments) = argument_list.parse(i)?;\n    let (i, _) = stag(\")\").parse(i)?;\n    Ok((i, Invocation { id, generic_type_name, arguments }))\n}\n\n// <argument-list> ::= <argument> (\",\" <argument>)*\nfn argument_list(i: &str) -> R<'_, Vec<Argument>> {\n    separated_list0(stag(\",\"), argument).parse(i)\n}\n\n// <argument> ::= <rvalue-expr> | <identifier> \"=\" <rvalue-expr>\nfn argument(i: &str) -> R<'_, Argument> {\n    spaced(map(pair(opt(terminated(identifier, stag(\"=\"))), rvalue), |(id, rvalue)| Argument {\n        id,\n        rvalue,\n    }))\n    .parse(i)\n}\n\n//<rvalue-expr> ::= <identifier> | <literal> | <binary-expr> | <unary-expr> | <paren-expr>\n//                  | <array-rvalue-expr> | <tuple-rvalue-expr> | <subscript-expr> | <if-else-expr>\n//                  | <comprehension-expr> | <builtin-expr> | <invocation>\nfn rvalue(i: &str) -> R<'_, RValue> {\n    fn atom(i: &str) -> R<'_, RValue> {\n        spaced(alt((\n            map(invocation, RValue::Invocation),\n            map(literal, RValue::Literal),\n            map(identifier, RValue::Identifier),\n            map(pair(spaced(recognize(one_of(\"+-!\"))), rvalue), |(op, rv)| {\n                RValue::Unary(op.into(), Box::new(rv))\n            }),\n            map(delimited(tag(\"(\"), separated_list0(stag(\",\"), rvalue), tag(\")\")), |mut rvs| {\n                if rvs.len() == 1 { rvs.remove(0) } else { RValue::Tuple(rvs) }\n            }),\n            map(comprehension_expr, |c| RValue::Comprehension(Box::new(c))),\n            map(delimited(tag(\"[\"), separated_list0(stag(\",\"), rvalue), tag(\"]\")), |rvs| {\n                RValue::Array(rvs)\n            }),\n        )))\n        .parse(i)\n    }\n    macro_rules! bin {\n        ($name:ident, $operand: ident, $operator: expr) => {\n            fn $name(i: &str) -> R<'_, RValue> {\n                let (i, init) = $operand(i)?;\n                fold_many0(\n                    pair($operator, $operand),\n                    move || init.clone(),\n                    |left, (op, right)| {\n                        RValue::Binary(Box::new(left), op.to_string(), Box::new(right))\n                    },\n                )\n                .parse(i)\n            }\n        };\n    }\n\n    // <subscript-expr> ::= <rvalue-expr> \"[\" (<rvalue-expr> | [<rvalue-expr>] \":\" [<rvalue-expr>]) \"]\"\n    fn sub(i: &str) -> R<'_, RValue> {\n        alt((\n            map(\n                pair(\n                    atom,\n                    delimited(\n                        stag(\"[\"),\n                        alt((\n                            map(separated_pair(opt(rvalue), stag(\":\"), opt(rvalue)), |(a, b)| {\n                                Subscript::Range(a, b)\n                            }),\n                            map(rvalue, Subscript::Single),\n                        )),\n                        stag(\"]\"),\n                    ),\n                ),\n                |(rv, range)| RValue::Subscript(Box::new(rv), Box::new(range)),\n            ),\n            atom,\n        ))\n        .parse(i)\n    }\n\n    bin!(exp, sub, tag(\"^\"));\n    bin!(mul, exp, one_of(\"*/\"));\n    bin!(add, mul, one_of(\"+-\"));\n    bin!(comp, add, alt((tag(\"==\"), tag(\"!=\"), tag(\"<\"), tag(\">\"), tag(\"<=\"), tag(\">=\"))));\n    bin!(boolean, comp, alt((tag(\"||\"), tag(\"&&\"))));\n    bin!(in_for, boolean, tag(\"in\"));\n\n    // <if-else-expr> ::= <rvalue-expr> \"if\" <rvalue-expr> \"else\" <rvalue-expr>\n    fn ite(i: &str) -> R<'_, RValue> {\n        let (i, leftmost) = in_for(i)?;\n        let (i, _) = space_and_comments(i)?;\n        if i.starts_with(\"if\") {\n            let (i, _) = stag(\"if\").parse(i)?;\n            let (i, cond) = in_for(i)?;\n            let (i, _) = stag(\"else\").parse(i)?;\n            let (i, otherwise) = in_for(i)?;\n            Ok((i, RValue::IfThenElse(Box::new(IfThenElse { cond, then: leftmost, otherwise }))))\n        } else {\n            Ok((i, leftmost))\n        }\n    }\n\n    ite(i)\n}\n\n// <comprehension-expr> ::= \"[\" \"for\" <loop-iter-list> [\"if\" <rvalue-expr>] \"yield\" <rvalue-expr> \"]\"\nfn comprehension_expr(i: &str) -> R<'_, Comprehension> {\n    delimited(\n        pair(stag(\"[\"), stag(\"for\")),\n        map(separated_pair(loop_iters, stag(\"yield\"), rvalue), |(loop_iters, yields)| {\n            Comprehension { loop_iters, filter: None, yields }\n        }),\n        stag(\"]\"),\n    )\n    .parse(i)\n}\n\n// <loop-iter> ::= <identifier> \"in\" <rvalue-expr>\n// <loop-iter-list> ::= <loop-iter> (\",\" <loop-iter>)*\nfn loop_iters(i: &str) -> R<'_, Vec<(Identifier, RValue)>> {\n    separated_list0(stag(\",\"), separated_pair(identifier, stag(\"in\"), rvalue)).parse(i)\n}\n\n// TERMINALS\n\n// identifier: identifiers must consist of the following ASCII characters: _, [a-z], [A-Z], [0-9].\n// The identifier must not start with a digit.\npub(super) fn identifier(i: &str) -> R<'_, Identifier> {\n    alt((escaped_identifier, direct_identifier)).parse(i)\n}\n\npub(super) fn direct_identifier(i: &str) -> R<'_, Identifier> {\n    map(\n        recognize(pair(alt((alpha1, tag(\"_\"))), many0(alt((alphanumeric1, tag(\"_\")))))),\n        Identifier::from,\n    )\n    .parse(i)\n}\n\npub(super) fn escaped_identifier(i: &str) -> R<'_, Identifier> {\n    map(preceded(tag(\"i\"), string_literal), Identifier).parse(i)\n}\n\n// <literal> ::= <numeric-literal> | <string-literal> | <logical-literal>\nfn literal(i: &str) -> R<'_, Literal> {\n    spaced(alt((\n        map(numeric_literal, Literal::Numeric),\n        map(string_literal, Literal::String),\n        map(logical_literal, Literal::Logical),\n    )))\n    .parse(i)\n}\n\npub(super) fn numeric_literal(i: &str) -> R<'_, String> {\n    fn exp_part(i: &str) -> R<'_, &str> {\n        recognize((one_of(\"eE\"), opt(one_of(\"+-\")), digit1)).parse(i)\n    }\n    fn frac_part(i: &str) -> R<'_, &str> {\n        recognize((tag(\".\"), digit0)).parse(i)\n    }\n    spaced(map(\n        recognize((opt(tag(\"-\")), alt((digit1, tag(\"inf\"))), opt(frac_part), opt(exp_part))),\n        |s: &str| s.to_owned(),\n    ))\n    .parse(i)\n}\n\nfn string_literal(i: &str) -> R<'_, String> {\n    fn inner(i: &str) -> R<'_, String> {\n        map(\n            many0(alt((\n                preceded(tag(\"\\\\\"), nom::character::complete::anychar),\n                nom::character::complete::none_of(\"\\\\\\\"'\"),\n            ))),\n            |v: Vec<char>| v.into_iter().collect(),\n        )\n        .parse(i)\n    }\n    map(alt((delimited(tag(\"'\"), inner, tag(\"'\")), delimited(tag(\"\\\"\"), inner, tag(\"\\\"\")))), |s| s)\n        .parse(i)\n}\n\npub(super) fn logical_literal(i: &str) -> R<'_, bool> {\n    spaced(alt((map(tag(\"true\"), |_| true), map(tag(\"false\"), |_| false)))).parse(i)\n}\n\n// SPACES\n\nfn space_and_comments(i: &str) -> R<'_, ()> {\n    map(\n        many0(alt((recognize(one_of(\" \\t\\n\\r\")), recognize((tag(\"#\"), many0(none_of(\"\\r\\n\"))))))),\n        |_| (),\n    )\n    .parse(i)\n}\n\nfn spaced<'s, O, F>(it: F) -> impl Parser<&'s str, Output = O, Error = VerboseError<&'s str>>\nwhere\n    F: Parser<&'s str, Output = O, Error = VerboseError<&'s str>>,\n{\n    delimited(space_and_comments, it, space_and_comments)\n}\n\npub(super) fn stag<'s>(\n    t: &'static str,\n) -> impl Parser<&'s str, Output = &'s str, Error = VerboseError<&'s str>> {\n    spaced(tag(t))\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use TypeName::*;\n    use TypeSpec::*;\n\n    fn p<'s, P, O, E>(parser: P, i: &'s str) -> O\n    where\n        O: std::fmt::Debug,\n        P: Fn(&'s str) -> IResult<&'s str, O, E>,\n        E: nom::error::ParseError<&'s str> + std::fmt::Debug,\n    {\n        let res = all_consuming(parser).parse(i).unwrap();\n        res.1\n    }\n\n    fn param(s: impl Into<std::string::String>, t: TypeSpec) -> Parameter {\n        Parameter { id: Identifier(s.into()), spec: t, lit: None, doc: None }\n    }\n\n    fn result(s: impl Into<std::string::String>, t: TypeSpec) -> Result_ {\n        Result_ { id: Identifier(s.into()), spec: t }\n    }\n\n    #[test]\n    fn test_type_spec() {\n        assert_eq!(p(type_spec, \"scalar\"), Single(Scalar));\n        assert_eq!(p(type_spec, \"scalar[]\"), Array(Box::new(Single(Scalar))));\n        assert_eq!(p(type_spec, \"tensor<scalar>[]\"), Array(Box::new(Tensor(TypeName::Scalar))));\n        assert_eq!(\n            p(type_spec, \"(scalar,scalar[],tensor<scalar>)\"),\n            Tuple(vec!(Single(Scalar), Array(Box::new(Single(Scalar))), Tensor(Scalar)))\n        );\n        assert_eq!(p(type_spec, \"tensor<?>[]\"), Array(Box::new(Tensor(TypeName::Any))));\n        assert_eq!(p(type_spec, \"scalar[ ]\"), Array(Box::new(Single(Scalar))));\n        assert_eq!(\n            p(type_spec, \" ( scalar , scalar [ ] , tensor < scalar > ) \"),\n            Tuple(vec!(Single(Scalar), Array(Box::new(Single(Scalar))), Tensor(Scalar)))\n        );\n        #[cfg(feature = \"complex\")]\n        assert_eq!(p(type_spec, \"tensor<complex>[]\"), Array(Box::new(Tensor(TypeName::Complex))));\n    }\n\n    #[test]\n    fn test_fragment_decl_fizz() {\n        let parsed = p(\n            fragment_decl,\n            \"fragment fizz<? = scalar>( shape: integer[] ) -> ( output: tensor<?> )\",\n        );\n        assert_eq!(\n            parsed,\n            FragmentDecl {\n                id: \"fizz\".into(),\n                generic_decl: Some(Some(Scalar)),\n                parameters: vec!(param(\"shape\", Array(Box::new(Single(Integer)))),),\n                results: vec!(result(\"output\", Tensor(Any))),\n            }\n        );\n    }\n\n    #[test]\n    fn test_fragment_decl_logarithmic_quantize() {\n        let parsed = p(\n            fragment_decl,\n            \"fragment logarithmic_quantize(x: tensor<scalar>, max: tensor<scalar>, bits: integer ) -> ( y: tensor<scalar> )\",\n        );\n        assert_eq!(\n            parsed,\n            FragmentDecl {\n                id: \"logarithmic_quantize\".into(),\n                generic_decl: None,\n                parameters: vec!(\n                    param(\"x\", Tensor(Scalar)),\n                    param(\"max\", Tensor(Scalar)),\n                    param(\"bits\", Single(Integer))\n                ),\n                results: vec!(result(\"y\", Tensor(Scalar))),\n            }\n        );\n    }\n\n    #[test]\n    fn test_fragment_decl_external() {\n        p(\n            fragment_decl,\n            \"fragment external<? = scalar>( shape: integer[] ) -> ( output: tensor<?> )\",\n        );\n    }\n\n    #[test]\n    fn test_fragment_reshape() {\n        p(\n            fragments,\n            \"fragment reshape<?>( input: tensor<?>, shape: integer[], axis_start: integer = 0, axis_count: integer = -1 ) -> ( output: tensor<?> );\",\n        );\n    }\n\n    #[test]\n    fn test_fragment_conv() {\n        p(\n            fragments,\n            r#\"\n            fragment conv(\n                input: tensor<scalar>,\n                filter: tensor<scalar>,\n                bias: tensor<scalar> = 0.0,\n                border: string = 'constant',\n                padding: (integer,integer)[] = [],\n                stride: integer[] = [],\n                dilation: integer[] = [],\n                groups: integer = 1 )\n            -> ( output: tensor<scalar> );\n            \"#,\n        );\n    }\n\n    #[test]\n    fn test_fragment_local_response_normalization() {\n        p(\n            fragments,\n            r#\"\n            fragment local_response_normalization(\n                input: tensor<scalar>,\n                size: integer[],\n                alpha: scalar = 1.0,\n                beta: scalar = 0.5,\n                bias: scalar = 1.0 )\n            -> ( output: tensor<scalar> )\n            {\n                sigma = bias + alpha * box(sqr(input), size = size, normalize = true);\n                output = input / (sigma ^ beta);\n            }\n            \"#,\n        );\n    }\n\n    #[test]\n    fn test_batch_normalization() {\n        p(\n            fragments,\n            r#\"\n            fragment batch_normalization( input: tensor<scalar>, mean: tensor<scalar>, variance: tensor<scalar>, offset: tensor<scalar>, scale: tensor<scalar>, epsilon: scalar )\n            -> ( output: tensor<scalar> )\n            {\n                output = offset + scale * (input - mean) / sqrt(variance + epsilon);\n            }\n            \"#,\n        );\n    }\n\n    #[test]\n    fn test_avg_roi_align() {\n        p(\n            fragments,\n            r#\"\n                fragment avg_roi_align(\n                    input: tensor<scalar>,\n                    rois: tensor<scalar>,\n                    batch_index: tensor<integer>,\n                    output_size: integer[],\n                    sampling_rate: integer[],\n                    resize_method: string = 'symmetric' )\n                -> ( output: tensor<scalar> )\n                {\n                    size = [for i in range_of(output_size) yield output_size[i] * sampling_rate[i]];\n                    resized = roi_resample(input, rois, batch_index, output_size = size,\n                                         method = resize_method);\n                    output = avg_pool(resized, size = sampling_rate, stride = sampling_rate);\n                }\n            \"#,\n        );\n    }\n\n    #[test]\n    fn test_min_max_linear_quantize() {\n        p(\n            fragments,\n            r#\"\n                fragment min_max_linear_quantize(\n                    x: tensor<scalar>,\n                    min: tensor<scalar>,\n                    max: tensor<scalar>,\n                    bits: integer,\n                    signed: logical,\n                    symmetric: logical )\n                -> ( y: tensor<scalar> )\n                {\n                    r = scalar(2 ^ bits - 1 - integer(signed && symmetric));\n                    z = clamp(x, min, max);\n                    p = scalar(2 ^ (bits - 1) - integer(symmetric) if signed else 0);\n                    q = round((z - min) / (max - min) * r) - p;\n                    y = (q + p) / r * (max - min) + min;\n}\n            \"#,\n        );\n    }\n\n    #[test]\n    fn test_numeric() {\n        p(numeric_literal, \"12.0\");\n    }\n\n    #[test]\n    fn test_string() {\n        assert_eq!(p(string_literal, r#\"\"\"\"#), \"\");\n        assert_eq!(p(string_literal, r#\"\"foo\"\"#), \"foo\");\n        assert_eq!(p(string_literal, r#\"''\"#), \"\");\n        assert_eq!(p(string_literal, r#\"'foo'\"#), \"foo\");\n\n        assert_eq!(p(string_literal, r\"'f\\oo'\"), \"foo\");\n        assert_eq!(p(string_literal, r\"'f\\'oo'\"), \"f'oo\");\n        assert_eq!(p(string_literal, r#\"'f\\\"oo'\"#), \"f\\\"oo\");\n    }\n\n    #[test]\n    fn test_identifier() {\n        p(identifier, \"foo\");\n        assert!(identifier(\"1\").is_err());\n        assert!(identifier(\"1foo\").is_err());\n    }\n\n    #[test]\n    fn test_spacing() {\n        p(space_and_comments, \"\");\n        p(space_and_comments, \"\\n\");\n        p(space_and_comments, \"#comment\\n\");\n        p(space_and_comments, \"#boum\");\n    }\n\n    #[test]\n    fn test_spaced() {\n        assert!(spaced(identifier).parse(\"foo\").is_ok());\n        assert!(spaced(identifier).parse(\" foo \").is_ok());\n        assert!(many1(spaced(identifier)).parse(\" foo bar \").is_ok());\n        assert_eq!(\n            many1(spaced(identifier)).parse(\" foo bar\\n\").unwrap().1,\n            &[Identifier(\"foo\".to_string()), Identifier(\"bar\".to_string())]\n        );\n        assert_eq!(\n            many1(spaced(identifier)).parse(\" foo # bar\\n\").unwrap().1,\n            &[Identifier(\"foo\".to_string())]\n        );\n        assert_eq!(\n            many1(spaced(identifier)).parse(\" foo # bar\\nbaz\").unwrap().1,\n            &[Identifier(\"foo\".to_string()), Identifier(\"baz\".to_string())]\n        );\n    }\n\n    #[test]\n    fn test_document() {\n        assert!(document(\"version 1.0; graph foo() -> () {}\").is_ok());\n    }\n\n    #[test]\n    fn test_version() {\n        p(version, \"version 1.0;\");\n    }\n\n    #[test]\n    fn test_body() {\n        p(body, \"{}\");\n        p(body, \"{foo=bar;}\");\n    }\n\n    #[test]\n    fn test_lvalue() {\n        p(lvalue, \"foo\");\n        p(lvalue, \"foo,bar\");\n        p(lvalue, \"foo , bar\");\n        p(lvalue, \"(foo,bar)\");\n    }\n\n    #[test]\n    fn test_graph_def() {\n        p(graph_def, \"graph foo() -> () {}\");\n    }\n\n    #[test]\n    fn test_assignment() {\n        p(assignment, \"input = external(12);\");\n        p(assignment, \"input = external(shape = [1, 3, 224, 224]);\");\n        p(assignment, \"sigma = bias + alpha * box(sqr(input), size = size, normalize = true);\");\n        p(assignment, \"output = offset + scale * (input - mean) / sqrt(variance + epsilon);\");\n        p(\n            assignment,\n            \"size = [for i in range_of(output_size) yield output_size[i] * sampling_rate[i]];\",\n        );\n        p(assignment, \"r = scalar(2 ^ bits - 1 - integer(signed && symmetric));\");\n        p(\n            assignment,\n            \"output, index = max_pool_with_index(input, size = size, border = border, padding = padding, stride = stride, dilation = dilation);\",\n        );\n    }\n\n    #[test]\n    fn test_invocation() {\n        p(invocation, \"external(12)\");\n        p(invocation, \"sqrt(var + eps)\");\n    }\n\n    #[test]\n    fn test_arguments() {\n        p(argument, \"2\");\n        p(argument, \"12\");\n        p(argument, \"shape = [1, 3, 224, 224]\");\n    }\n\n    #[test]\n    fn test_rvalue() {\n        p(rvalue, \"12\");\n        p(rvalue, \"(0, 0)\");\n        p(rvalue, \"x ^ 2.0\");\n        p(rvalue, \"1+2\");\n        p(rvalue, \"1+sqrt(var)\");\n        p(rvalue, \"1+sqrt(var+eps)\");\n        p(rvalue, \"1 + sqrt(var + eps)\");\n        p(rvalue, \"[for i in range_of(output_size) yield output_size[i] * sampling_rate[i]]\");\n        p(rvalue, \"scalar(2 ^ (bits - 1) - integer(symmetric) if signed else 0)\");\n    }\n\n    #[test]\n    fn test_comprehenion() {\n        p(comprehension_expr, \"[for i in range_of(output_size) yield output_size * sampling_rate]\");\n    }\n\n    #[test]\n    fn test_freeze() {\n        p(\n            document,\n            r#\"\nversion 1.0;\n\ngraph y( x, s, bias ) -> ( y ) {\n  x = external<scalar>(shape = [1, 2, 1, 3]);\n  s = external<scalar>(shape = [2]);\n  bias = external<scalar>(shape = [2]);\n  y = add(\n        mul(\n            mul(\n                sub(\n                    x,\n                    mul(\n                        0.33333334,\n                        sum_reduce(\n                            x,\n                            axes = [0, 2, 3]\n                        )\n                    )\n                ),\n                rsqrt(\n                    add(\n                        0.00001,\n                        mul(\n                            0.33333334,\n                            sum_reduce(\n                                square(\n                                    sub(\n                                        x,\n                                        mul(\n                                            0.33333334,\n                                            sum_reduce(\n                                                x,\n                                                axes = [0, 2, 3]\n                                            )\n                                        )\n                                    )\n                                ),\n                                axes = [0, 2, 3]\n                            )\n                        )\n                    )\n                )\n            ),\n            unsqueeze(\n                unsqueeze(\n                    unsqueeze(\n                        s,\n                        axes = [0]\n                    ),\n                axes = [2]\n                ),\n            axes = [2]\n            )\n        ),\n        unsqueeze(\n            unsqueeze(\n                unsqueeze(\n                    bias,\n                    axes = [0]\n                ),\n                axes = [2]\n            ),\n            axes = [2]\n        )\n    );\n}\n\n\"#,\n        );\n    }\n\n    #[test]\n    fn test_fragments() {\n        p(\n            fragments,\n            r#\"\n            fragment add( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<scalar> );\n            fragment sub( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<scalar> );\n            \"#,\n        );\n    }\n}\n"
  },
  {
    "path": "nnef/src/ast/quant.rs",
    "content": "use std::str::FromStr;\n\nuse nom::Parser;\nuse nom::branch::permutation;\nuse nom::character::complete::digit1;\nuse nom::combinator::{map_res, recognize};\nuse nom::sequence::{delimited, pair};\nuse nom_language::error::VerboseError;\nuse tract_core::internal::*;\n\nuse nom::branch::alt;\nuse nom::{IResult, combinator::all_consuming};\nuse nom::{bytes::complete::*, multi::*};\nuse nom::{combinator::opt, number::complete::float};\n\nuse crate::ast::*;\n\nuse super::dump::write_identifier;\nuse super::parse::{direct_identifier, escaped_identifier, logical_literal, stag, translate_error};\n\ntype R<'i, O> = IResult<&'i str, O, VerboseError<&'i str>>;\n\n#[inline(never)]\npub fn parse_quantization(doc: &str) -> TractResult<Vec<(Identifier, QuantFormat)>> {\n    all_consuming(many0(quantization)).parse(doc).map(|pair| pair.1).map_err(translate_error)\n}\n\n// <quantization> ::= \"<identifier>\": <qparam>\nfn quantization(i: &str) -> R<'_, (Identifier, QuantFormat)> {\n    let (i, _) = stag(\"\").parse(i)?;\n    let (i, id) =\n        alt((delimited(tag(\"\\\"\"), direct_identifier, tag(\"\\\"\")), escaped_identifier)).parse(i)?;\n    let (i, _) = stag(\":\").parse(i)?;\n    let (i, qp) = qparam(i)?;\n    let (i, _) = stag(\";\").parse(i)?;\n    Ok((i, (id, qp)))\n}\n\nfn integer_numeric<T: FromStr>(i: &str) -> R<'_, T> {\n    map_res(recognize(pair(opt(tag(\"-\")), digit1)), |s: &str| s.parse::<T>()).parse(i)\n}\n\n// <qparam> ::= \"<identifier>\": <qparam>\nfn qparam(i: &str) -> R<'_, QuantFormat> {\n    let (i, id) =\n        nom::branch::alt((stag(\"linear_quantize\"), stag(\"zero_point_linear_quantize\"))).parse(i)?;\n    let (i, _) = stag(\"(\").parse(i)?;\n    let (i, params, bits, signed) = match id {\n        \"linear_quantize\" => {\n            let (i, (bits, max, min)) =\n                permutation((arg(\"bits\", integer_numeric), arg(\"max\", float), arg(\"min\", float)))\n                    .parse(i)?;\n\n            (i, QParams::MinMax { min, max }, bits, true)\n        }\n        \"zero_point_linear_quantize\" => {\n            let (i, (zero_point, scale, bits, signed, _)) = permutation((\n                arg(\"zero_point\", integer_numeric),\n                arg(\"scale\", float),\n                arg(\"bits\", integer_numeric),\n                arg(\"signed\", logical_literal),\n                opt(arg(\"symmetric\", logical_literal)),\n            ))\n            .parse(i)?;\n            (i, QParams::ZpScale { zero_point, scale }, bits, signed)\n        }\n        _ => unreachable!(),\n    };\n\n    let (i, _) = stag(\")\").parse(i)?;\n    Ok((i, QuantFormat::Linear { params, bits, signed }))\n}\n// <arg>(<id>, <f>) ::= <id> \"=\" <f> \",\"\nfn arg<'s, T, F>(name: &'static str, f: F) -> impl Fn(&'s str) -> R<'s, T>\nwhere\n    F: Fn(&'s str) -> R<'s, T>,\n{\n    move |i: &str| {\n        let (i, _) = stag(name).parse(i)?;\n        let (i, _) = stag(\"=\").parse(i)?;\n        let (i, num) = f(i)?;\n        let (i, _) = opt(stag(\",\")).parse(i)?;\n        Ok((i, num))\n    }\n}\n\npub(crate) fn write_quant_format(\n    w: &mut impl std::io::Write,\n    name: &Identifier,\n    format: QuantFormat,\n    allow_extended_identifier_syntax: bool,\n) -> TractResult<()> {\n    write_identifier(w, name, allow_extended_identifier_syntax, true)?;\n    match format {\n        QuantFormat::Linear { params: QParams::ZpScale { zero_point, scale }, bits, signed } => {\n            writeln!(\n                w,\n                \": zero_point_linear_quantize(zero_point = {zero_point}, scale = {scale:.9}, bits = {bits}, signed = {signed}, symmetric = {});\",\n                zero_point == 0\n            )?\n        }\n        QuantFormat::Linear { params: QParams::MinMax { min, max }, bits, signed: _ } => {\n            writeln!(w, \": linear_quantize(max = {max:.9}, min = {min:.9}, bits = {bits});\")?\n        }\n    }\n    Ok(())\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use nom::combinator::all_consuming;\n\n    fn p<'s, P, O>(parser: P, i: &'s str) -> O\n    where\n        O: std::fmt::Debug,\n        P: Parser<&'s str, Output = O, Error = VerboseError<&'s str>>,\n    {\n        let res = all_consuming(parser).parse(i).unwrap();\n        res.1\n    }\n\n    #[test]\n    fn test_arg() {\n        assert_eq!(p(arg(\"arg\", float), \"arg = 2.35,\"), 2.35);\n\n        assert_eq!(p(arg(\"test\", tag(\"a\")), \"test = a\"), \"a\");\n    }\n\n    #[test]\n    fn test_qparam() {\n        assert_eq!(\n            p(qparam, \"linear_quantize(min = 0.5, max = 123.8, bits = 8)\"),\n            QuantFormat::Linear {\n                params: QParams::MinMax { min: 0.5, max: 123.8 },\n                bits: 8,\n                signed: true\n            }\n        );\n    }\n\n    #[test]\n    fn test_quantization() {\n        assert_eq!(\n            p(quantization, r#\"\"tensor_name\": linear_quantize(min = 0.5, max = 123.8, bits = 8);\"#),\n            (\n                \"tensor_name\".into(),\n                QuantFormat::Linear {\n                    params: QParams::MinMax { min: 0.5, max: 123.8 },\n                    bits: 8,\n                    signed: true\n                }\n            )\n        );\n    }\n\n    #[test]\n    fn test_quant_file() {\n        assert_eq!(\n            p(\n                many0(quantization),\n                r#\"\n                   \"tensor_name1\": linear_quantize(min = 0.5, max = 123.8, bits = 8);\n                   \"tensor_name2\": linear_quantize(max = 0.52, min = 123.82, bits = 82);\n                   \"tensor_name3\": zero_point_linear_quantize(zero_point = 52, scale = 123.83, bits = 83, signed = true, symmetric = false);\"#\n            ),\n            vec![\n                (\n                    Identifier(\"tensor_name1\".to_string()),\n                    QuantFormat::Linear {\n                        params: QParams::MinMax { min: 0.5, max: 123.8 },\n                        bits: 8,\n                        signed: true\n                    }\n                ),\n                (\n                    Identifier(\"tensor_name2\".to_string()),\n                    QuantFormat::Linear {\n                        params: QParams::MinMax { max: 0.52, min: 123.82 },\n                        bits: 82,\n                        signed: true\n                    }\n                ),\n                (\n                    Identifier(\"tensor_name3\".to_string()),\n                    QuantFormat::Linear {\n                        params: QParams::ZpScale { zero_point: 52, scale: 123.83 },\n                        bits: 83,\n                        signed: true\n                    }\n                )\n            ]\n        );\n    }\n\n    #[test]\n    fn test_quant_file_1() {\n        assert_eq!(\n            p(\n                many0(quantization),\n                r#\"\n                   i\"tensor.name1\": linear_quantize(min = 0.5, max = 123.8, bits = 8);\n                   i\"tensor/name2\": linear_quantize(max = 0.52, min = 123.82, bits = 82);\n                   \"tensor_name3\": zero_point_linear_quantize(zero_point = 52, scale = 123.83, bits = 83, signed = true, symmetric = false);\"#\n            ),\n            vec![\n                (\n                    Identifier(\"tensor.name1\".to_string()),\n                    QuantFormat::Linear {\n                        params: QParams::MinMax { min: 0.5, max: 123.8 },\n                        bits: 8,\n                        signed: true\n                    }\n                ),\n                (\n                    Identifier(\"tensor/name2\".to_string()),\n                    QuantFormat::Linear {\n                        params: QParams::MinMax { max: 0.52, min: 123.82 },\n                        bits: 82,\n                        signed: true\n                    }\n                ),\n                (\n                    Identifier(\"tensor_name3\".to_string()),\n                    QuantFormat::Linear {\n                        params: QParams::ZpScale { zero_point: 52, scale: 123.83 },\n                        bits: 83,\n                        signed: true\n                    }\n                )\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "nnef/src/ast.rs",
    "content": "use crate::internal::*;\nuse tract_itertools::Itertools;\n\npub mod dump;\npub mod dump_doc;\npub mod parse;\npub mod quant;\n\n#[derive(Clone, Debug)]\npub struct ProtoModel {\n    pub doc: Document,\n    pub tensors: HashMap<Identifier, Arc<Tensor>>,\n    pub quantization: Option<HashMap<Identifier, QuantFormat>>,\n    pub resources: HashMap<String, Arc<dyn Resource>>,\n}\n\nimpl ProtoModel {\n    pub fn validate(&self) -> TractResult<()> {\n        self.doc.validate()\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum QuantFormat {\n    Linear { params: QParams, bits: i8, signed: bool },\n}\n\nimpl QuantFormat {\n    pub fn from_dt(datum_type: DatumType) -> Option<QuantFormat> {\n        if let Some(params) = datum_type.qparams() {\n            let quant_format = QuantFormat::Linear {\n                params,\n                bits: 8 * datum_type.size_of() as i8,\n                signed: datum_type.is_signed(),\n            };\n            Some(quant_format)\n        } else {\n            None\n        }\n    }\n\n    pub fn datum_type(&self) -> DatumType {\n        match self {\n            QuantFormat::Linear { params, bits, signed } => match (bits, signed) {\n                (8, true) => DatumType::QI8(*params),\n                (8, false) => DatumType::QU8(*params),\n                (32, true) => DatumType::QI32(*params),\n                (32, false) => DatumType::U32,\n                _ => todo!(),\n            },\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Document {\n    pub version: NumericLiteral,\n    pub extension: Vec<(Identifier, String)>,\n    pub fragments: Vec<FragmentDef>,\n    pub graph_def: GraphDef,\n}\n\nimpl Document {\n    pub fn validate(&self) -> TractResult<()> {\n        for frag in &self.fragments {\n            frag.validate()?;\n        }\n        Ok(())\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum TypeSpec {\n    Single(TypeName),\n    Tensor(TypeName),\n    Array(Box<TypeSpec>),\n    Tuple(Vec<TypeSpec>),\n}\n\nimpl TypeSpec {\n    pub fn array(self) -> TypeSpec {\n        TypeSpec::Array(Box::new(self))\n    }\n    pub fn named(self, s: impl AsRef<str>) -> Parameter {\n        Parameter { id: s.as_ref().into(), spec: self, lit: None, doc: None }\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum TypeName {\n    Integer,\n    Scalar,\n    #[cfg(feature = \"complex\")]\n    Complex,\n    Logical,\n    String,\n    Any,\n}\n\nimpl TypeName {\n    pub fn tensor(self) -> TypeSpec {\n        TypeSpec::Tensor(self)\n    }\n    pub fn spec(self) -> TypeSpec {\n        TypeSpec::Single(self)\n    }\n    pub fn array(self) -> TypeSpec {\n        self.spec().array()\n    }\n    pub fn named(self, s: impl AsRef<str>) -> Parameter {\n        self.spec().named(s)\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct GraphDef {\n    pub id: Identifier,\n    pub parameters: Vec<Identifier>,\n    pub results: Vec<Identifier>,\n    pub body: Vec<Assignment>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct FragmentDef {\n    pub decl: FragmentDecl,\n    pub body: Option<Vec<Assignment>>,\n}\n\nimpl FragmentDef {\n    pub fn validate(&self) -> TractResult<()> {\n        self.decl.validate().with_context(|| format!(\"Invalid fragment {:?}\", self.decl.id))\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct FragmentDecl {\n    pub id: Identifier,\n    pub generic_decl: Option<Option<TypeName>>,\n    pub parameters: Vec<Parameter>,\n    pub results: Vec<Result_>,\n}\n\nimpl FragmentDecl {\n    pub fn validate(&self) -> TractResult<()> {\n        if let Some(dup) = self\n            .parameters\n            .iter()\n            .map(|p| &p.id)\n            .sorted()\n            .chunk_by(|x| x.to_owned())\n            .into_iter()\n            .find_map(|(key, values)| if values.count() > 1 { Some(key) } else { None })\n        {\n            bail!(\"Duplicate parameter name found {:?}\", dup);\n        }\n        if let Some(dup) = self\n            .results\n            .iter()\n            .map(|p| &p.id)\n            .sorted()\n            .chunk_by(|x| x.to_owned())\n            .into_iter()\n            .find_map(|(key, values)| if values.count() > 1 { Some(key) } else { None })\n        {\n            bail!(\"Duplicate result name found {:?}\", dup);\n        }\n        if let Some(dup) = self\n            .parameters\n            .iter()\n            .map(|p| &p.id)\n            .chain(self.results.iter().map(|p| &p.id))\n            .sorted()\n            .chunk_by(|x| x.to_owned())\n            .into_iter()\n            .find_map(|(key, values)| if values.count() > 1 { Some(key) } else { None })\n        {\n            bail!(\"Same name used as parameter and result {:?}\", dup);\n        }\n        Ok(())\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Parameter {\n    pub id: Identifier,\n    pub spec: TypeSpec,\n    pub lit: Option<Literal>,\n    pub doc: Option<String>,\n}\n\nimpl Parameter {\n    pub fn default(self, lit: impl Into<Literal>) -> Parameter {\n        Parameter { lit: Some(lit.into()), ..self }\n    }\n\n    pub fn doc(mut self, s: impl Into<String>) -> Parameter {\n        self.doc = Some(s.into());\n        self\n    }\n}\n\npub fn param(s: impl AsRef<str>, spec: TypeSpec) -> Parameter {\n    Parameter { id: s.as_ref().into(), spec, lit: None, doc: None }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Result_ {\n    pub id: Identifier,\n    pub spec: TypeSpec,\n}\n\nimpl<S: Into<String>> From<(S, TypeSpec)> for Result_ {\n    fn from(v: (S, TypeSpec)) -> Result_ {\n        Result_ { id: Identifier(v.0.into()), spec: v.1 }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Default, Ord, PartialOrd, Hash)]\npub struct Identifier(pub String);\n\nimpl From<&str> for Identifier {\n    fn from(value: &str) -> Self {\n        Identifier(value.to_string())\n    }\n}\n\nimpl AsRef<str> for Identifier {\n    fn as_ref(&self) -> &str {\n        &self.0\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Assignment {\n    pub left: LValue,\n    pub right: RValue,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum LValue {\n    Identifier(Identifier),\n    Array(Vec<LValue>),\n    Tuple(Vec<LValue>),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Invocation {\n    pub id: Identifier,\n    pub generic_type_name: Option<TypeName>,\n    pub arguments: Vec<Argument>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Argument {\n    pub id: Option<Identifier>,\n    pub rvalue: RValue,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum RValue {\n    Identifier(Identifier),\n    Literal(Literal),\n    Binary(Box<RValue>, String, Box<RValue>),\n    Unary(String, Box<RValue>),\n    Tuple(Vec<RValue>),\n    Array(Vec<RValue>),\n    Subscript(Box<RValue>, Box<Subscript>),\n    Comprehension(Box<Comprehension>),\n    IfThenElse(Box<IfThenElse>),\n    Invocation(Invocation),\n}\n\nimpl RValue {\n    pub fn boxed(self) -> Box<RValue> {\n        Box::new(self)\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Comprehension {\n    pub loop_iters: Vec<(Identifier, RValue)>,\n    pub filter: Option<RValue>,\n    pub yields: RValue,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Subscript {\n    Single(RValue),\n    Range(Option<RValue>, Option<RValue>),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct IfThenElse {\n    pub cond: RValue,\n    pub then: RValue,\n    pub otherwise: RValue,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Literal {\n    Numeric(NumericLiteral),\n    String(StringLiteral),\n    Logical(LogicalLiteral),\n    Array(Vec<Literal>),\n    Tuple(Vec<Literal>),\n}\n\nimpl From<bool> for Literal {\n    fn from(b: bool) -> Literal {\n        Literal::Logical(b)\n    }\n}\n\nimpl From<i64> for Literal {\n    fn from(i: i64) -> Literal {\n        Literal::Numeric(i.to_string())\n    }\n}\n\nimpl From<f32> for Literal {\n    fn from(f: f32) -> Literal {\n        Literal::Numeric(format!(\"{f:?}\"))\n    }\n}\n\nimpl<'a> From<&'a str> for Literal {\n    fn from(s: &'a str) -> Literal {\n        Literal::String(s.to_string())\n    }\n}\n\npub type NumericLiteral = String;\npub type StringLiteral = String;\npub type LogicalLiteral = bool;\n"
  },
  {
    "path": "nnef/src/deser.rs",
    "content": "use std::ops::ControlFlow;\n\nuse tract_core::num_traits::Zero;\nuse tract_core::tract_data::itertools::Itertools;\n\nuse crate::ast::*;\nuse crate::internal::*;\n\npub struct ModelBuilder<'a> {\n    pub framework: &'a Nnef,\n    pub registries: Vec<Identifier>,\n    pub model: TypedModel,\n    pub naming_scopes: Vec<Identifier>,\n    pub scopes: Vec<HashMap<Identifier, Value>>,\n    pub proto_model: &'a ProtoModel,\n    pub symbols: Vec<Symbol>,\n    allow_new_symbol: bool,\n    #[allow(clippy::type_complexity)]\n    pub wire_resolver:\n        Option<Box<dyn FnMut(&str, &mut TypedModel) -> TractResult<Option<OutletId>> + 'a>>,\n}\n\nimpl<'mb> ModelBuilder<'mb> {\n    pub fn new(\n        framework: &'mb Nnef,\n        proto_model: &'mb ProtoModel,\n        template: TypedModel,\n    ) -> ModelBuilder<'mb> {\n        ModelBuilder {\n            registries: vec![\"tract_nnef\".into()],\n            framework,\n            model: template,\n            naming_scopes: vec![],\n            scopes: vec![],\n            proto_model,\n            symbols: vec![],\n            allow_new_symbol: false,\n            wire_resolver: None,\n        }\n    }\n\n    pub fn allowing_new_symbols<R>(&mut self, closure: impl Fn(&mut Self) -> R) -> R {\n        self.allow_new_symbol = true;\n        let r = closure(self);\n        self.allow_new_symbol = false;\n        r\n    }\n\n    fn translate(&mut self) -> TractResult<()> {\n        let mut scenario_specs = vec![];\n        'ext: for ext in &self.proto_model.doc.extension {\n            match &*ext.0.0 {\n                \"tract_registry\" => {\n                    let registry = Identifier(ext.1.trim().to_owned());\n                    if self.framework.registries.iter().any(|reg| reg.id == registry) {\n                        self.registries.push(registry.clone())\n                    } else if let Some(reg) =\n                        self.framework.registries.iter().find(|reg| reg.aliases.contains(&registry))\n                    {\n                        self.registries.push(reg.id.clone())\n                    } else {\n                        bail!(\"Registry not found {:?}\", registry)\n                    }\n                }\n                \"tract_symbol\" => {\n                    let symbol = self.model.symbols.new_with_prefix(ext.1.trim());\n                    self.symbols.push(symbol);\n                }\n                \"tract_assert\" => {\n                    if let Some(pair) = ext.1.split_once(':') {\n                        scenario_specs.push(pair);\n                    } else {\n                        self.model.symbols.add_assertion(&ext.1)?;\n                    }\n                }\n                \"KHR_enable_fragment_definitions\" | \"KHR_enable_operator_expressions\" => (),\n                _ => {\n                    for reg in &self.framework.registries {\n                        for reg_ext in &reg.extensions {\n                            match reg_ext(self, &ext.0, &ext.1)? {\n                                ControlFlow::Continue(_) => (),\n                                ControlFlow::Break(_) => continue 'ext,\n                            }\n                        }\n                    }\n                    warn!(\"Ignore unknown extension {:?}\", ext.0);\n                }\n            };\n        }\n        for (scen, rule) in scenario_specs {\n            self.model.symbols.add_scenario_assertion(scen, rule)?;\n        }\n        self.scopes.push(HashMap::new());\n        self.wire_body(&self.proto_model.doc.graph_def.body).context(\"Wiring root graph body\")?;\n        let vars = self.scopes.pop().unwrap();\n\n        let outputs = self\n            .proto_model\n            .doc\n            .graph_def\n            .results\n            .iter()\n            .map(|s| {\n                vars.get(s)\n                    .with_context(|| format!(\"Could not find variable for output named {s:?}\"))\n            })\n            .collect::<TractResult<TVec<&Value>>>()?;\n\n        let outputs = outputs\n            .into_iter()\n            .map(|s| s.to::<OutletId>(self))\n            .collect::<TractResult<TVec<OutletId>>>()?;\n        self.model.select_output_outlets(&outputs)?;\n\n        self.parse_properties().context(\"Parsing properties\")?;\n\n        for (ix, name) in self.proto_model.doc.graph_def.results.iter().enumerate() {\n            self.model.set_outlet_label(outputs[ix], name.0.to_string())?;\n        }\n\n        Ok(())\n    }\n\n    #[allow(clippy::result_large_err)]\n    pub fn into_typed_model(mut self) -> Result<TypedModel, (TypedModel, TractError)> {\n        match self.translate().context(\"In ModelBuilder::translate\") {\n            Ok(()) => Ok(self.model),\n            Err(e) => Err((self.model, e)),\n        }\n    }\n\n    fn parse_properties(&mut self) -> TractResult<()> {\n        if let Some(properties) = self\n            .proto_model\n            .doc\n            .fragments\n            .iter()\n            .find(|f| &f.decl.id.0 == \"tract_core_properties\")\n            .and_then(|f| f.body.as_ref())\n            .and_then(|body| body.first())\n        {\n            let properties: TVec<(String, Arc<Tensor>)> =\n                properties.right.resolve(self, &[])?.to(self)?;\n            self.model.properties = properties.into_iter().collect();\n        }\n        Ok(())\n    }\n\n    pub fn wire_body(&mut self, body: &[Assignment]) -> TractResult<()> {\n        // todo: can i relax the outlet id constraint ?\n        for assignment in body {\n            let identifiers = assignment.left.to_identifiers()?;\n            trace!(\"Wiring identifiers {identifiers:?}\");\n            let datum_types = identifiers\n                .iter()\n                .map(|s| {\n                    self.proto_model\n                        .quantization\n                        .as_ref()\n                        .and_then(|qm| qm.get(*s).map(|q| q.datum_type()))\n                })\n                .collect::<Vec<_>>();\n            self.naming_scopes.push(identifiers[0].clone());\n            let mut values = if identifiers.len() == 1 {\n                let value: OutletId = assignment\n                    .right\n                    .resolve(self, &datum_types)\n                    .and_then(|v| v.to(self))\n                    .with_context(|| {\n                        format!(\n                            \"Plugging in assignement for {:?}\",\n                            identifiers.iter().map(|i| &i.0).join(\", \")\n                        )\n                    })?;\n                tvec!(value)\n            } else {\n                let values: TVec<OutletId> = assignment\n                    .right\n                    .resolve(self, &datum_types)\n                    .and_then(|v| v.to(self))\n                    .with_context(|| {\n                        format!(\n                            \"Plugging in assignement for {:?}\",\n                            identifiers.iter().map(|i| &i.0).join(\", \")\n                        )\n                    })?;\n                if values.len() != identifiers.len() {\n                    bail!(\n                        \"Assignement for {} received {} value(s).\",\n                        identifiers.iter().map(|i| &i.0).join(\",\"),\n                        values.len()\n                    )\n                }\n                values\n            };\n            for (qparam, value) in datum_types.into_iter().zip(values.iter_mut()) {\n                if let Some(qparam) = qparam\n                    && qparam != self.model.outlet_fact(*value)?.datum_type\n                {\n                    self.model.node_mut(value.node).name =\n                        format!(\"{}_raw\", self.naming_scopes.iter().map(|i| &i.0).join(\"_\"));\n                    if self.model.outlet_fact(*value)?.datum_type == TDim::datum_type() {\n                        *value = self.model.wire_node(\n                            format!(\n                                \"{}_cast_to_f32\",\n                                self.naming_scopes.iter().map(|i| &i.0).join(\"_\")\n                            ),\n                            tract_core::ops::cast::cast(f32::datum_type()),\n                            &[*value],\n                        )?[0];\n                    }\n                    *value = self.model.wire_node(\n                        format!(\"{}_cast_to_q\", self.naming_scopes.iter().map(|i| &i.0).join(\"_\")),\n                        tract_core::ops::cast::cast(qparam),\n                        &[*value],\n                    )?[0];\n                }\n            }\n            for (id, outlet) in identifiers.iter().zip(values.iter()) {\n                self.scopes.last_mut().unwrap().insert((*id).clone(), Value::Wire(*outlet));\n            }\n            self.naming_scopes.pop();\n            for (value, identifier) in values.iter().zip(identifiers) {\n                if self.model.node_mut(value.node).name.is_empty() {\n                    self.naming_scopes.push(identifier.clone());\n                    self.model.node_mut(value.node).name = self.generate_node_name();\n                    self.naming_scopes.pop();\n                }\n            }\n        }\n        Ok(())\n    }\n\n    pub fn wire_invocation(\n        &mut self,\n        invocation: &Invocation,\n        dt: &[Option<DatumType>],\n    ) -> TractResult<Value> {\n        for frag in &self.proto_model.doc.fragments {\n            if frag.decl.id == invocation.id && frag.body.is_some() {\n                let resolved = ResolvedInvocation {\n                    invocation,\n                    dt_from_quant_file: dt,\n                    default_params: &frag.decl.parameters,\n                };\n                return self.wire_fragment_invocation(\n                    &resolved,\n                    &frag.decl,\n                    frag.body.as_deref().unwrap(),\n                );\n            }\n        }\n\n        // We start with the registry that has been added last\n        for registry in self.framework.registries.iter().rev() {\n            if self.registries.contains(&registry.id)\n                && let Some(outputs) = registry\n                    .deserialize(self, invocation, dt)\n                    .with_context(|| format!(\"Interrogating registry {:?}\", registry.id))?\n            {\n                return Ok(outputs);\n            }\n        }\n        bail!(\"No definition for operator {:?}\", invocation.id);\n    }\n\n    pub fn wire_fragment_invocation(\n        &mut self,\n        invocation: &ResolvedInvocation,\n        decl: &FragmentDecl,\n        body: &[Assignment],\n    ) -> TractResult<Value> {\n        let mut inner_scope = HashMap::new();\n        for par in invocation.default_params.iter() {\n            inner_scope.insert(par.id.clone(), invocation.named_arg_as::<Value>(self, &par.id.0)?);\n        }\n        self.scopes.push(inner_scope);\n        self.with_extra_naming_scope(invocation.invocation.id.clone(), |b| b.wire_body(body))?;\n        let inner_scope = self.scopes.pop().unwrap();\n        Ok(Value::Tuple(\n            decl.results.iter().map(|res| inner_scope.get(&res.id).unwrap()).cloned().collect(),\n        ))\n    }\n\n    fn with_extra_naming_scope<F: FnOnce(&mut Self) -> R, R>(\n        &mut self,\n        name: Identifier,\n        f: F,\n    ) -> R {\n        self.naming_scopes.push(name);\n        let r = f(self);\n        self.naming_scopes.pop();\n        r\n    }\n\n    pub fn generate_node_name(&self) -> String {\n        let name = self.naming_scopes.iter().map(|n| &n.0).join(\"_\");\n        if self.model.nodes().iter().any(|n| n.name == name) {\n            for i in 0.. {\n                let candidate = format!(\"{name}_{i}\");\n                if !self.model.nodes().iter().any(|n| n.name.starts_with(&candidate)) {\n                    return candidate;\n                }\n            }\n        }\n        name\n    }\n\n    pub fn wire_as_outlets(\n        &mut self,\n        op: impl Into<Box<dyn TypedOp>>,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let op = op.into();\n        let name = self.generate_node_name();\n        self.model.wire_node(name, op, inputs).with_context(|| format!(\"inputs are {inputs:?}\"))\n    }\n\n    pub fn add_const(&mut self, v: impl IntoArcTensor) -> TractResult<OutletId> {\n        self.model.add_const(self.generate_node_name(), v)\n    }\n\n    pub fn wire(\n        &mut self,\n        op: impl Into<Box<dyn TypedOp>>,\n        inputs: &[OutletId],\n    ) -> TractResult<Value> {\n        self.wire_as_outlets(op, inputs).map(Value::from)\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct ResolvedInvocation<'a> {\n    pub invocation: &'a Invocation,\n    pub dt_from_quant_file: &'a [Option<DatumType>],\n    pub default_params: &'a [Parameter],\n}\n\nimpl ResolvedInvocation<'_> {\n    pub fn named_arg_as<T>(&self, builder: &mut ModelBuilder, name: &str) -> TractResult<T>\n    where\n        T: CoerceFrom<Value>,\n    {\n        let rv = self.named_arg(name)?;\n        builder.with_extra_naming_scope(Identifier(name.into()), |builder| {\n            let v = rv\n                .resolve(builder, &[])\n                .with_context(|| format!(\"Resolving argument `{name}' ({rv:?})\"))?;\n            v.to::<T>(builder).with_context(|| format!(\"Converting argument `{name}' from {v:?}\"))\n        })\n    }\n\n    pub fn optional_named_arg_as<T>(\n        &self,\n        builder: &mut ModelBuilder,\n        name: &str,\n    ) -> TractResult<Option<T>>\n    where\n        T: CoerceFrom<Value>,\n    {\n        let Some(rv) = self.get_named_arg(name) else { return Ok(None) };\n        let v = rv\n            .resolve(builder, &[])\n            .with_context(|| format!(\"Resolving argument `{name}' ({rv:?})\"))?;\n        match v {\n            Value::Bool(b) => {\n                if !b {\n                    Ok(None)\n                } else {\n                    bail!(\n                        \"Bool(true) not expected for optional values, you might want to access a boolean direclty.\"\n                    )\n                }\n            }\n            _ => v\n                .to::<T>(builder)\n                .map(Option::Some)\n                .with_context(|| format!(\"Converting argument `{name}' from {v:?}\")),\n        }\n    }\n\n    pub fn named_arg(&self, name: &str) -> TractResult<Cow<'_, RValue>> {\n        self.get_named_arg(name).ok_or_else(|| format_err!(\"expected argument {}\", name))\n    }\n\n    pub fn get_named_arg(&self, name: &str) -> Option<Cow<'_, RValue>> {\n        // first look explicit name in invocation arguments\n        if let Some(arg) = self\n            .invocation\n            .arguments\n            .iter()\n            .find(|arg| arg.id.as_ref().map(|i| &*i.0) == Some(name))\n        {\n            return Some(Cow::Borrowed(&arg.rvalue));\n        }\n        // then use fragment prototype:\n        if let Some((ix, param)) =\n            self.default_params.iter().enumerate().find(|(_ix, param)| &*param.id.0 == name)\n        {\n            // check that all previous (and our) arguments are positional (todo:\n            // valid args when building augmented_invocation)\n            if self.invocation.arguments.len() > ix\n                && self.invocation.arguments.iter().take(ix + 1).all(|arg| arg.id.is_none())\n            {\n                return Some(Cow::Borrowed(&self.invocation.arguments[ix].rvalue));\n            }\n            if let Some(rv) = &param.lit {\n                return Some(Cow::Owned(RValue::Literal(rv.clone())));\n            }\n        }\n        None\n    }\n\n    pub fn get_named_arg_as<T>(\n        &self,\n        builder: &mut ModelBuilder,\n        name: &str,\n    ) -> TractResult<Option<T>>\n    where\n        T: CoerceFrom<Value>,\n    {\n        let Some(rv) = self.get_named_arg(name) else { return Ok(None) };\n        let v = rv\n            .resolve(builder, &[])\n            .with_context(|| format!(\"Resolving argument `{name}' ({rv:?})\"))?;\n        v.to::<T>(builder)\n            .with_context(|| format!(\"Converting argument `{name}' from {v:?}\"))\n            .map(Some)\n    }\n}\n\nimpl ModelBuilder<'_> {}\n\nimpl LValue {\n    fn to_identifier(&self) -> TractResult<&Identifier> {\n        match self {\n            LValue::Identifier(id) => Ok(id),\n            _ => bail!(\"Expected an identifier, found a tuple: {:?}\", self),\n        }\n    }\n\n    #[allow(dead_code)]\n    fn to_identifiers(&self) -> TractResult<TVec<&Identifier>> {\n        match self {\n            LValue::Identifier(_) => Ok(tvec!(self.to_identifier()?)),\n            LValue::Tuple(ids) => ids.iter().map(|id| id.to_identifier()).collect(),\n            LValue::Array(ids) => ids.iter().map(|id| id.to_identifier()).collect(),\n        }\n    }\n}\n\nimpl Invocation {}\n\nimpl RValue {\n    pub fn resolve(\n        &self,\n        builder: &mut ModelBuilder,\n        dt: &[Option<DatumType>],\n    ) -> TractResult<Value> {\n        match self {\n            RValue::Identifier(id) => {\n                if let Some(mut outlet) = builder.scopes.last().unwrap().get(id).cloned() {\n                    if let Value::Wire(outlet_id) = outlet {\n                        let out_dt = builder.model.node(outlet_id.node).outputs[outlet_id.slot]\n                            .fact\n                            .datum_type;\n                        if let Some(Some(dt)) = dt.first() {\n                            if out_dt.unquantized() != dt.unquantized() {\n                                return Err(format_err!(\n                                    \"Mismatched types expected {:?}, got {:?}\",\n                                    dt,\n                                    out_dt\n                                ));\n                            }\n                            if out_dt != *dt {\n                                outlet =\n                                    builder.wire(tract_core::ops::cast::cast(*dt), &[outlet_id])?;\n                            }\n                        }\n                    }\n                    Ok(outlet)\n                } else if let Some(outlet) = {\n                    if let Some(mut resolver) = builder.wire_resolver.take() {\n                        let result = resolver(&id.0, &mut builder.model);\n                        builder.wire_resolver = Some(resolver);\n                        result?\n                    } else {\n                        None\n                    }\n                } {\n                    let value = Value::Wire(outlet);\n                    builder.scopes.last_mut().unwrap().insert(id.clone(), value.clone());\n                    Ok(value)\n                } else if let Some(sym) = builder.model.symbols.get(&id.0) {\n                    Ok(Value::Dim(sym.into()))\n                } else if builder.allow_new_symbol {\n                    warn!(\n                        \"Introducing symbol {id:?} without forward declaration (\\\"extension tract_symbol ...\\\"). May be deprecated soon.\"\n                    );\n                    let sym = builder.model.symbols.sym(&id.0);\n                    Ok(Value::Dim(sym.into()))\n                } else {\n                    bail!(\n                        \"Can not resolve {:?}. Not a known identifier, and symbol introduction is forbidden out of \\\"external\\\" shape field\",\n                        id\n                    );\n                }\n            }\n            RValue::Invocation(inv) => builder\n                .wire_invocation(inv, dt)\n                .with_context(|| format!(\"Resolving invocation {:?}\", inv.id)),\n            RValue::Binary(left, op, right) => {\n                let op = match &**op {\n                    \"+\" => \"add\",\n                    \"-\" => \"sub\",\n                    \"*\" => \"mul\",\n                    \"/\" => \"div\",\n                    \"^\" => \"pow\",\n                    \">\" => \"gt\",\n                    \"<\" => \"lt\",\n                    \"==\" => \"eq\",\n                    \"!=\" => \"ne\",\n                    \">=\" => \"ge\",\n                    \"<=\" => \"le\",\n                    op => bail!(\"Unknown binary operator: {}\", op),\n                };\n                let inv = Invocation {\n                    id: op.into(),\n                    generic_type_name: None,\n                    arguments: vec![\n                        Argument { id: None, rvalue: left.as_ref().clone() },\n                        Argument { id: None, rvalue: right.as_ref().clone() },\n                    ],\n                };\n                builder\n                    .wire_invocation(&inv, dt)\n                    .with_context(|| format!(\"Resolving invocation {:?}\", &inv.id))\n            }\n            RValue::Array(array) => Ok(Value::Array(\n                array\n                    .iter()\n                    .zip(std::iter::repeat(&dt.first().copied().flatten()))\n                    .map(|(i, dt)| i.resolve(builder, &[*dt]))\n                    .collect::<TractResult<_>>()?,\n            )),\n            RValue::Tuple(array) => {\n                let dt_iter: Box<dyn Iterator<Item = &Option<DatumType>>> =\n                    if dt.len() == 0 || dt.len() == 1 && dt[0].is_none() {\n                        Box::new(std::iter::repeat(&None))\n                    } else if dt.len() == array.len() {\n                        Box::new(dt.iter())\n                    } else {\n                        bail!(\"Wrong number of types for a tuple, got {:?} for {:?}\", dt, array)\n                    };\n                Ok(Value::Tuple(\n                    array\n                        .iter()\n                        .zip(dt_iter)\n                        .map(|(i, dt)| {\n                            if dt.is_none() {\n                                i.resolve(builder, &[])\n                            } else {\n                                i.resolve(builder, &[*dt])\n                            }\n                        })\n                        .collect::<TractResult<_>>()?,\n                ))\n            }\n            RValue::Literal(Literal::Numeric(f)) => {\n                if f.contains('.') || f.contains('e') || f == \"inf\" || f == \"-inf\" {\n                    f.parse::<f32>()\n                        .map(Value::Scalar)\n                        .with_context(|| format!(\"Can not parse {f} as f32\"))\n                } else if let Ok(i) = f.parse::<i64>() {\n                    Ok(Value::Dim(i.into()))\n                } else if let Some(s) = builder.model.symbols.get(f) {\n                    Ok(Value::Dim(s.into()))\n                } else {\n                    bail!(\"Can not parse {}\", f)\n                }\n            }\n            RValue::Literal(Literal::String(s)) => Ok(Value::String(s.clone())),\n            RValue::Literal(Literal::Logical(s)) => Ok(Value::Bool(*s)),\n            RValue::Literal(Literal::Array(array)) => Ok(Value::Array(\n                array\n                    .iter()\n                    .zip(std::iter::repeat(&dt.first().copied().flatten()))\n                    .map(|(i, dt)| RValue::Literal(i.clone()).resolve(builder, &[*dt]))\n                    .collect::<TractResult<_>>()?,\n            )),\n            RValue::Subscript(target, subscript) => {\n                let target_value = target.resolve(builder, dt)?;\n                match subscript.as_ref() {\n                    Subscript::Single(idx_rvalue) => {\n                        let idx: usize = idx_rvalue.resolve(builder, &[])?.to(builder)?;\n                        match target_value {\n                            Value::Array(vec) | Value::Tuple(vec) => vec\n                                .into_iter()\n                                .nth(idx)\n                                .with_context(|| format!(\"Index {idx} out of bounds\")),\n                            Value::Wire(outlet) => {\n                                let fact = builder.model.outlet_fact(outlet)?;\n                                if let Some(konst) = &fact.konst {\n                                    let plain = konst.try_as_plain()?;\n                                    if konst.datum_type() == TDim::datum_type() {\n                                        let dim = plain.as_slice::<TDim>()?[idx].clone();\n                                        Ok(Value::Dim(dim))\n                                    } else {\n                                        let element = konst\n                                            .slice(0, idx, idx + 1)?\n                                            .into_shape(&[])?\n                                            .into_arc_tensor();\n                                        let wire = builder\n                                            .model\n                                            .add_const(builder.generate_node_name(), element)?;\n                                        Ok(Value::Wire(wire))\n                                    }\n                                } else {\n                                    bail!(\"Subscript on non-const wire not yet supported\")\n                                }\n                            }\n                            _ => bail!(\"Subscript not supported on {target_value:?}\"),\n                        }\n                    }\n                    Subscript::Range(..) => bail!(\"Subscript ranges not yet supported\"),\n                }\n            }\n            _ => bail!(\"Unsupported RValue variant: {self:?}\"),\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq)]\npub enum Value {\n    Tensor(Arc<Tensor>),\n    Wire(OutletId),\n    Array(Vec<Value>),\n    Tuple(Vec<Value>),\n    String(String),\n    Bool(bool),\n    Scalar(f32),\n    Dim(TDim),\n}\n\nimpl Value {\n    pub fn to<T>(&self, builder: &mut ModelBuilder) -> TractResult<T>\n    where\n        T: CoerceFrom<Value>,\n    {\n        T::coerce(builder, self)\n    }\n}\n\nimpl From<TVec<OutletId>> for Value {\n    fn from(outled_ids: TVec<OutletId>) -> Self {\n        Self::Tuple(outled_ids.into_iter().map(Self::Wire).collect())\n    }\n}\n\npub trait CoerceFrom<F> {\n    fn coerce(builder: &mut ModelBuilder, from: &F) -> TractResult<Self>\n    where\n        Self: Sized;\n}\n\nimpl CoerceFrom<Value> for Value {\n    fn coerce(_builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        Ok(from.clone())\n    }\n}\n\nimpl CoerceFrom<Value> for Arc<Tensor> {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Dim(t) => Ok(rctensor0(t.to_i32()?)),\n            Value::Tensor(t) => Ok(t.clone()),\n            Value::Tuple(t) if t.len() == 1 => t[0].to(builder),\n            Value::Scalar(f) => Ok(rctensor0(*f)),\n            Value::String(f) => Ok(rctensor0(f.clone())),\n            Value::Bool(b) => Ok(rctensor0(*b)),\n            Value::Wire(o) => builder\n                .model\n                .outlet_fact(*o)?\n                .konst\n                .clone()\n                .ok_or_else(|| format_err!(\"Not a const\")),\n            Value::Array(items) => {\n                let mut tensors = vec![];\n                for item in items {\n                    let tensor = Arc::<Tensor>::coerce(builder, item)?;\n                    let mut tensor = tensor.into_tensor();\n                    tensor.insert_axis(0)?;\n                    tensors.push(tensor);\n                }\n                let tensor = Tensor::stack_tensors(0, &tensors)?;\n                Ok(tensor.into_arc_tensor())\n            }\n            _ => bail!(\"Can not build a tensor from {:?}\", from),\n        }\n    }\n}\n\nimpl CoerceFrom<Value> for (Arc<Tensor>, DatumType) {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Tensor(t) => Ok((t.clone(), t.datum_type())),\n            Value::Scalar(f) => Ok((rctensor0(*f), DatumType::F32)),\n            Value::String(f) => Ok((rctensor0(f.clone()), DatumType::String)),\n            Value::Bool(b) => Ok((rctensor0(*b), DatumType::Bool)),\n            Value::Wire(o) => {\n                let outlet_fact = builder.model.outlet_fact(*o)?;\n                Ok((\n                    outlet_fact.konst.clone().ok_or_else(|| format_err!(\"Not a const\"))?,\n                    outlet_fact.datum_type,\n                ))\n            }\n            _ => bail!(\"Can not build a tensor from {:?}\", from),\n        }\n    }\n}\n\nimpl CoerceFrom<Value> for OutletId {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Tensor(t) => builder.add_const(t.clone()),\n            Value::Scalar(f) => builder.add_const(rctensor0(*f)),\n            Value::Dim(i) => builder.add_const(rctensor0(i.clone())),\n            Value::Wire(outlet) => Ok(*outlet),\n            Value::Tuple(tuple) if tuple.len() == 1 => OutletId::coerce(builder, &tuple[0]),\n            Value::Array(inputs) => {\n                if let Ok(c) = from.to::<Arc<Tensor>>(builder) {\n                    return builder.add_const(c);\n                }\n                let mut outlets = tvec!();\n                for i in inputs {\n                    let outlet = OutletId::coerce(builder, i)?;\n                    outlets.push(builder.wire_as_outlets(AxisOp::Add(0), &[outlet])?[0]);\n                }\n                if inputs.len() > 0 {\n                    builder\n                        .wire_as_outlets(tract_core::ops::array::TypedConcat::new(0), &outlets)\n                        .map(|o| o[0])\n                } else {\n                    builder.add_const(tensor1::<f32>(&[]))\n                }\n            }\n            Value::String(s) => builder.add_const(rctensor0(s.clone())),\n            Value::Bool(b) => builder.add_const(rctensor0(*b)),\n            _ => bail!(\"Can not build an outletid from {:?}\", from),\n        }\n    }\n}\n\nimpl CoerceFrom<Value> for u64 {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Dim(d) => Ok(d.to_i64()? as u64),\n            Value::Tensor(t) => Ok(t.cast_to_scalar::<u64>()?),\n            Value::Wire(_) => Ok(from.to::<Arc<Tensor>>(builder)?.cast_to_scalar::<u64>()?),\n            _ => bail!(\"Can not build a u64 from {:?}\", from),\n        }\n    }\n}\n\nimpl CoerceFrom<Value> for i64 {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Dim(d) => d.to_i64(),\n            Value::Tensor(t) => Ok(*t.try_as_plain()?.to_scalar::<i64>()?),\n            Value::Wire(_) => Ok(from.to::<Arc<Tensor>>(builder)?.cast_to_scalar::<i64>()?),\n            _ => bail!(\"Can not build a i64 from {:?}\", from),\n        }\n    }\n}\n\nimpl CoerceFrom<Value> for TDim {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Dim(d) => Ok(d.clone()),\n            Value::Tensor(t) => Ok(t.try_as_plain()?.to_scalar::<TDim>()?.clone()),\n            Value::Wire(_) => Ok(from\n                .to::<Arc<Tensor>>(builder)?\n                .cast_to::<TDim>()?\n                .try_as_plain()?\n                .to_scalar::<TDim>()?\n                .clone()),\n            _ => bail!(\"Can not build a TDim from {:?}\", from),\n        }\n    }\n}\n\nimpl CoerceFrom<Value> for String {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::String(s) => Ok(s.to_string()),\n            Value::Tensor(t) => Ok(t.try_as_plain()?.to_scalar::<String>()?.clone()),\n            Value::Wire(_) => Ok(from\n                .to::<Arc<Tensor>>(builder)?\n                .cast_to::<String>()?\n                .try_as_plain()?\n                .to_scalar::<String>()?\n                .clone()),\n            _ => bail!(\"Can not build a String from {:?}\", from),\n        }\n    }\n}\n\nimpl CoerceFrom<Value> for bool {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Bool(b) => Ok(*b),\n            Value::Tensor(t) => Ok(*t.try_as_plain()?.to_scalar::<bool>()?),\n            Value::Wire(_) => Ok(*from\n                .to::<Arc<Tensor>>(builder)?\n                .cast_to::<bool>()?\n                .try_as_plain()?\n                .to_scalar::<bool>()?),\n            Value::Dim(n) => Ok(!n.is_zero()),\n            _ => bail!(\"Can not build a boolean from {:?}\", from),\n        }\n    }\n}\n\nimpl CoerceFrom<Value> for usize {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        Ok(i64::coerce(builder, from)? as usize)\n    }\n}\n\nimpl CoerceFrom<Value> for isize {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        Ok(i64::coerce(builder, from)? as isize)\n    }\n}\n\nimpl CoerceFrom<Value> for f32 {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Scalar(f) => Ok(*f),\n            Value::Dim(d) => Ok(d.to_i64()? as f32),\n            Value::Tensor(t) => Ok(*t.try_as_plain()?.to_scalar::<f32>()?),\n            Value::Wire(_) => Ok(*from\n                .to::<Arc<Tensor>>(builder)?\n                .cast_to::<f32>()?\n                .try_as_plain()?\n                .to_scalar::<f32>()?),\n            _ => bail!(\"Can not build a f32 from {:?}\", from),\n        }\n    }\n}\n\nimpl<D: CoerceFrom<Value>> CoerceFrom<Value> for TVec<D> {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Array(vec) => vec.iter().map(|item| D::coerce(builder, item)).collect(),\n            Value::Tuple(vec) => vec.iter().map(|item| D::coerce(builder, item)).collect(),\n            any => Ok(tvec!(D::coerce(builder, any)?)),\n        }\n    }\n}\n\nimpl CoerceFrom<Value> for ShapeFact {\n    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n        match from {\n            Value::Array(vec) => vec.iter().map(|item| TDim::coerce(builder, item)).collect(),\n            Value::Tuple(vec) => vec.iter().map(|item| TDim::coerce(builder, item)).collect(),\n            _ => {\n                let t = from.to::<Arc<Tensor>>(builder)?;\n                Ok(t.cast_to::<TDim>()?.try_as_plain()?.as_slice::<TDim>()?.into())\n            }\n        }\n    }\n}\n\nmacro_rules! tuple {\n    ($($d: ident),*) => {\n        impl<$($d),*> CoerceFrom<Value> for ($($d),*)\n            where\n                $($d: CoerceFrom<Value>),*\n                {\n                    fn coerce(builder: &mut ModelBuilder, from: &Value) -> TractResult<Self> {\n                        match from {\n                            Value::Tuple(vec) => {\n                                let mut vec = vec.iter();\n                                Ok((\n                                        $($d::coerce(builder, vec.next().context(\"Too small a tuple\")?)?),*\n                                   ))\n                            }\n                            _ => bail!(\"Can not build a tuple from {:?}\", from),\n                        }\n                    }\n                }\n    }\n}\n\ntuple!(D1, D2);\ntuple!(D1, D2, D3);\ntuple!(D1, D2, D3, D4);\n"
  },
  {
    "path": "nnef/src/framework.rs",
    "content": "use tar::Builder;\nuse tract_core::tract_data::itertools::Itertools;\n\nuse crate::ast::quant::write_quant_format;\nuse crate::ast::{Identifier, ProtoModel, QuantFormat};\nuse crate::resource::{GraphNnef, SafeTensorsLoader};\nuse crate::{internal::*, nnef};\nuse std::io::Read;\n#[cfg(target_family = \"unix\")]\nuse std::os::unix::prelude::OsStrExt;\nuse std::path::Path;\nuse std::str::FromStr;\n\npub fn stdlib() -> Vec<FragmentDef> {\n    crate::ast::parse::parse_fragments(include_str!(\"../stdlib.nnef\")).unwrap()\n}\n\npub struct Nnef {\n    pub stdlib: Vec<FragmentDef>,\n    pub registries: Vec<Registry>,\n    pub resource_loaders: Vec<Box<dyn ResourceLoader + 'static>>,\n    pub allow_extended_identifier_syntax: bool,\n    pub extern_all_constants: bool,\n}\n\nimpl Default for Nnef {\n    fn default() -> Nnef {\n        Nnef {\n            stdlib: stdlib(),\n            registries: vec![crate::ops::tract_nnef()],\n            resource_loaders: vec![\n                GraphNnefLoader.into_boxed(),\n                DatLoader.into_boxed(),\n                GraphQuantLoader.into_boxed(),\n                TypedModelLoader::new(false).into_boxed(),\n                SafeTensorsLoader.into_boxed(),\n            ],\n            allow_extended_identifier_syntax: false,\n            extern_all_constants: false,\n        }\n    }\n}\n\nimpl Nnef {\n    pub fn with_registry(mut self, registry: Registry) -> Nnef {\n        self.registries.push(registry);\n        self\n    }\n\n    pub fn with_resource_loader(mut self, loader: impl ResourceLoader + 'static) -> Nnef {\n        self.resource_loaders.push(Box::new(loader));\n        self\n    }\n\n    pub fn enable_tract_core(&mut self) {\n        self.registries.push(crate::ops::tract_core());\n    }\n\n    pub fn with_tract_core(mut self) -> Self {\n        self.registries.push(crate::ops::tract_core());\n        self\n    }\n\n    pub fn enable_tract_resource(&mut self) {\n        self.registries.push(crate::ops::tract_resource());\n    }\n\n    pub fn with_tract_resource(mut self) -> Self {\n        self.registries.push(crate::ops::tract_resource());\n        self\n    }\n\n    pub fn allow_extended_identifier_syntax(&mut self, allow_extended_identifier_syntax: bool) {\n        self.allow_extended_identifier_syntax = allow_extended_identifier_syntax;\n    }\n\n    pub fn extern_all_constants(&mut self, value: bool) {\n        self.extern_all_constants = value;\n    }\n\n    #[allow(clippy::result_large_err)]\n    pub fn translate(\n        &self,\n        proto_model: &ProtoModel,\n        template: TypedModel,\n    ) -> Result<TypedModel, (TypedModel, TractError)> {\n        debug!(\"Build TypedModel from NNEF proto model (translate)\");\n        ModelBuilder::new(self, proto_model, template).into_typed_model()\n    }\n\n    pub fn write(&self, model: &TypedModel, w: impl std::io::Write) -> TractResult<()> {\n        self.write_to_tar(model, w)?;\n        Ok(())\n    }\n\n    pub fn write_to_tar<W: std::io::Write>(&self, model: &TypedModel, w: W) -> TractResult<W> {\n        let mut ar = tar::Builder::new(w);\n        let timestamp =\n            std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap();\n        self._write_to_tar(model, &mut ar, false, timestamp)?;\n        ar.into_inner().context(\"Finalizing tar\")\n    }\n\n    pub fn write_to_tar_with_config<W: std::io::Write>(\n        &self,\n        model: &TypedModel,\n        w: W,\n        compress_nested_models: bool,\n        deterministic: bool,\n    ) -> TractResult<W> {\n        let mut ar = tar::Builder::new(w);\n        let timestamp = if deterministic {\n            // 1 Jan 1980, MS-DOS epoch. Some tools have issues with 0 timestamps.\n            std::time::Duration::from_secs(315532800)\n        } else {\n            std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap()\n        };\n\n        self._write_to_tar(model, &mut ar, compress_nested_models, timestamp)?;\n        ar.into_inner().context(\"Finalizing tar\")\n    }\n\n    fn _write_to_tar<W: std::io::Write>(\n        &self,\n        model: &TypedModel,\n        ar: &mut Builder<W>,\n        compress_nested_models: bool,\n        timestamp: std::time::Duration,\n    ) -> TractResult<()> {\n        let proto_model =\n            crate::ser::to_proto_model(self, model).context(\"Translating model to proto_model\")?;\n\n        let mut graph_data = vec![];\n        crate::ast::dump::Dumper::new(self, &mut graph_data)\n            .document(&proto_model.doc)\n            .context(\"Serializing graph.nnef\")?;\n\n        let mut header = tar::Header::new_gnu();\n        header.set_path(\"graph.nnef\").context(\"Setting graph.nnef path\")?;\n        header.set_size(graph_data.len() as u64);\n        header.set_mode(0o644);\n        header.set_mtime(timestamp.as_secs());\n        header.set_cksum();\n        ar.append(&header, &mut &*graph_data).context(\"Appending graph.nnef\")?;\n\n        if let Some(mut quantization) = proto_model.quantization {\n            let mut quant_data = vec![];\n\n            let mut keys = quantization.keys().cloned().collect::<Vec<_>>();\n            keys.sort();\n            for name in keys {\n                let format = quantization.remove(&name).unwrap();\n                write_quant_format(\n                    &mut quant_data,\n                    &name,\n                    format,\n                    self.allow_extended_identifier_syntax,\n                )\n                .context(\"Serializing graph.quant\")?;\n            }\n\n            header.set_path(\"graph.quant\").context(\"Setting graph.quant path\")?;\n            header.set_size(quant_data.len() as u64);\n            header.set_mode(0o644);\n            header.set_mtime(timestamp.as_secs());\n            header.set_cksum();\n            ar.append(&header, &mut &*quant_data).context(\"Appending graph.quant\")?;\n        }\n\n        let mut labels = proto_model.tensors.keys().collect::<Vec<_>>();\n        labels.sort();\n        for label in labels {\n            let t = proto_model.tensors.get(label).unwrap();\n            let mut label = label.0.to_string() + \".dat\";\n            if label.starts_with('/') {\n                label.insert(0, '.');\n            }\n            let filename = std::path::Path::new(&label);\n            let mut data = vec![];\n            crate::tensors::write_tensor(&mut data, t)\n                .with_context(|| format!(\"Serializing tensor {filename:?}: {t:?}\"))?;\n\n            let mut header = tar::Header::new_gnu();\n            header.set_size(data.len() as u64);\n            header.set_mode(0o644);\n            header.set_mtime(timestamp.as_secs());\n            header.set_cksum();\n\n            ar.append_data(&mut header, filename, &mut &*data)\n                .with_context(|| format!(\"Appending tensor {filename:?}\"))?;\n        }\n\n        let mut labels = proto_model.resources.keys().collect::<Vec<_>>();\n        labels.sort();\n        for label in labels {\n            let resource = proto_model.resources.get(label).unwrap();\n            if let Some(typed_model_resource) = resource.downcast_ref::<TypedModelResource>() {\n                let mut submodel_data = vec![];\n                let mut filename = std::path::PathBuf::from_str(label)?;\n                let typed_model = &typed_model_resource.0;\n\n                if compress_nested_models {\n                    filename.set_extension(\"nnef.tgz\");\n                    let encoder = flate2::write::GzEncoder::new(\n                        &mut submodel_data,\n                        flate2::Compression::default(),\n                    );\n                    self.write(typed_model, encoder)?;\n                } else {\n                    filename.set_extension(\"nnef.tar\");\n                    self.write(typed_model, &mut submodel_data)?;\n                }\n\n                let mut header = tar::Header::new_gnu();\n                header.set_size(submodel_data.len() as u64);\n                header.set_mode(0o644);\n                header.set_mtime(timestamp.as_secs());\n                header.set_cksum();\n\n                ar.append_data(&mut header, filename, &mut &*submodel_data)\n                    .with_context(|| format!(\"Appending submodel {label:?}\"))?;\n            }\n        }\n        Ok(())\n    }\n\n    pub fn write_to_dir(\n        &self,\n        model: &TypedModel,\n        path: impl AsRef<std::path::Path>,\n    ) -> TractResult<()> {\n        let path = path.as_ref();\n        if path.exists() {\n            bail!(\"{:?} already exists. Won't overwrite.\", path);\n        }\n        let proto_model = crate::ser::to_proto_model(self, model)?;\n        std::fs::create_dir_all(path)?;\n        let mut graph_nnef = std::fs::File::create(path.join(\"graph.nnef\"))?;\n        crate::ast::dump::Dumper::new(self, &mut graph_nnef).document(&proto_model.doc)?;\n\n        if let Some(quantization) = proto_model.quantization {\n            let mut graph_quant = std::fs::File::create(path.join(\"graph.quant\"))?;\n            for (name, format) in quantization.into_iter().sorted_by_key(|(x, _)| x.clone()) {\n                write_quant_format(\n                    &mut graph_quant,\n                    &name,\n                    format,\n                    self.allow_extended_identifier_syntax,\n                )?;\n            }\n        }\n\n        for (label, t) in &proto_model.tensors {\n            let label = label.0.to_string() + \".dat\";\n            let label = label.trim_start_matches('/');\n            let parent = path.join(label).parent().unwrap().to_owned();\n            std::fs::create_dir_all(&parent).with_context(|| format!(\"Creating dir {parent:?}\"))?;\n            let filename = path.join(label).to_owned();\n            let mut file = std::fs::File::create(&filename)\n                .with_context(|| format!(\"Creating file {filename:?}\"))?;\n\n            crate::tensors::write_tensor(&mut file, t)?;\n        }\n        Ok(())\n    }\n}\n\nimpl tract_core::prelude::Framework<ProtoModel, TypedModel> for Nnef {\n    fn model_for_path(&self, p: impl AsRef<Path>) -> TractResult<TypedModel> {\n        let proto = self.proto_model_for_path(p)?;\n        self.model_for_proto_model(&proto)\n    }\n\n    fn proto_model_for_path(&self, path: impl AsRef<Path>) -> TractResult<ProtoModel> {\n        let path = path.as_ref();\n        if path.is_file() {\n            let mut f = std::fs::File::open(path)?;\n            return self.proto_model_for_read(&mut f);\n        }\n\n        let mut resources: HashMap<String, Arc<dyn Resource>> = Default::default();\n\n        // `walkdir::new` will first yield the given path at depth 0, but we don't want to load this\n        // entry here: only its descendants at depth >= 1.\n        for entry in walkdir::WalkDir::new(path).min_depth(1) {\n            let entry =\n                entry.map_err(|e| format_err!(\"Can not walk directory {:?}: {:?}\", path, e))?;\n            // We don't want to load sub-directories themselves either.\n            if entry.path().is_dir() {\n                continue;\n            }\n            let subpath = entry\n                .path()\n                .components()\n                .skip(path.components().count())\n                .collect::<std::path::PathBuf>();\n            let mut stream = std::fs::File::open(entry.path())?;\n            read_stream(&subpath, &mut stream, &mut resources, self)?;\n        }\n        proto_model_from_resources(resources)\n    }\n\n    fn proto_model_for_read(&self, reader: &mut dyn std::io::Read) -> TractResult<ProtoModel> {\n        let mut resources: HashMap<String, Arc<dyn Resource>> = Default::default();\n\n        let mut buffer = vec![0u8; 2];\n        reader.read_exact(&mut buffer)?;\n        let header = std::io::Cursor::new(buffer.clone());\n        let stream = header.chain(reader);\n        let mut tar = if buffer == [0x1f, 0x8b] {\n            #[cfg(feature = \"flate2\")]\n            {\n                let f = flate2::read::GzDecoder::new(stream);\n                tar::Archive::new(Box::new(f) as Box<dyn Read>)\n            }\n            #[cfg(not(feature = \"flate2\"))]\n            bail!(\"Cannot read gzip file without flate2 enabled.\");\n        } else {\n            tar::Archive::new(Box::new(stream) as Box<dyn Read>)\n        };\n        for entry in tar.entries()? {\n            let mut entry = entry?;\n            let mut path = entry.path()?.to_path_buf();\n            if path.starts_with(\"./\") {\n                path = path.strip_prefix(\"./\")?.to_path_buf();\n            }\n            read_stream(&path, &mut entry, &mut resources, self)?;\n        }\n        proto_model_from_resources(resources)\n    }\n\n    fn model_for_proto_model_with_model_template(\n        &self,\n        proto: &ProtoModel,\n        template: TypedModel,\n    ) -> TractResult<TypedModel> {\n        self.translate(proto, template).map_err(|e| e.1)\n    }\n}\n\nfn proto_model_from_resources(\n    resources: HashMap<String, Arc<dyn Resource>>,\n) -> TractResult<ProtoModel> {\n    // Iter resources IDs to detect submodels. Submodels are IDs with\n    // - two path compoents (ex: XXX/file)\n    // - a graph.nnef file as filename\n    let sub_models = resources\n        .keys()\n        .clone()\n        .filter_map(|id| {\n            let id_components = id.split('/').collect::<Vec<_>>();\n            if (id_components.last() == Some(&crate::resource::GRAPH_NNEF_FILENAME))\n                & (id_components.len() == 2)\n            {\n                id_components.first().map(|it| it.to_string())\n            } else {\n                None\n            }\n        })\n        .collect::<Vec<_>>();\n\n    // If there are submodels, we use the associated resources to create a TypedModel resource and add\n    // it as a new resource.\n    let mut new_resources = if sub_models.len() > 0 {\n        sub_models.into_iter().try_fold(resources, |r, it| -> TractResult<HashMap<_, _>> {\n            let (submodel_resources, mut resources): (HashMap<String, Arc<dyn Resource>>, _) =\n                r.into_iter().partition(|(k, _v)| k.starts_with(&it));\n            let submodel_resources = submodel_resources\n                .into_iter()\n                .map(|(k, v)| (k.split('/').next_back().unwrap().to_string(), v))\n                .collect::<HashMap<String, Arc<dyn Resource>>>();\n            let typed_model = nnef()\n                .model_for_proto_model(&proto_model_from_resources(submodel_resources).unwrap())?;\n            resources.insert(it, Arc::new(TypedModelResource(typed_model)));\n            Ok(resources)\n        })?\n    } else {\n        resources\n    };\n\n    // NNEF document extraction\n    let doc = new_resources\n        .remove(crate::resource::GRAPH_NNEF_FILENAME)\n        .with_context(|| {\n            anyhow!(\"Resource {} was not found in the model\", crate::resource::GRAPH_NNEF_FILENAME)\n        })?\n        .downcast_arc::<GraphNnef>()\n        .map_err(|_| anyhow!(\"Error while downcasting NNEF document resource\"))?;\n\n    let doc = Arc::try_unwrap(doc)\n            .map_err(|_| anyhow!(\"Error while extracting NNEF Document from shared reference. Only one reference to the document is expected\"))?;\n\n    // Collect all resources that can be downcastable to Arc<Tensor>.\n    let mut tensors: HashMap<_, _> = new_resources\n        .iter()\n        .filter_map(|(key, resource)| {\n            Arc::clone(resource)\n                .downcast_arc::<Tensor>()\n                .ok()\n                .map(|r| (Identifier::from(&**key), r))\n        })\n        .collect();\n\n    for r in new_resources.values() {\n        if let Some(safe_tensors) = r.downcast_ref::<Vec<(String, Arc<Tensor>)>>() {\n            for (name, t) in safe_tensors.iter() {\n                tensors.insert(Identifier::from(&**name), Arc::clone(t));\n            }\n        }\n    }\n    new_resources.retain(|_, r| !r.is::<Tensor>() && !r.is::<Vec<(String, Arc<Tensor>)>>());\n\n    // Quantization format resources extraction if present.\n    let quantization = if let Some(q_r) =\n        new_resources.remove(crate::resource::GRAPH_QUANT_FILENAME)\n    {\n        let Ok(q_r) = q_r.downcast_arc::<HashMap<String, QuantFormat>>() else {\n            bail!(\"Error while downcasting quantization format resource\")\n        };\n        let Ok(q_r) = Arc::try_unwrap(q_r) else {\n            bail!(\n                \"Error while extracting quantization format resource from shared reference. Only one reference to it is expected\"\n            )\n        };\n        Some(q_r.into_iter().map(|(k, v)| (Identifier(k), v)).collect())\n    } else {\n        None\n    };\n\n    let doc = crate::liquid::process_file(&doc.0, &new_resources)?;\n    let doc = crate::ast::parse::parse_document(&doc)?;\n\n    let proto = ProtoModel { doc, tensors, quantization, resources: new_resources };\n    proto.validate()?;\n    Ok(proto)\n}\n\nfn read_stream<R: std::io::Read>(\n    path: &Path,\n    reader: &mut R,\n    resources: &mut HashMap<String, Arc<dyn Resource>>,\n    framework: &Nnef,\n) -> TractResult<()> {\n    // ignore path with any component starting with \".\" (because OSX's tar is weird)\n    #[cfg(target_family = \"unix\")]\n    if path.components().any(|name| name.as_os_str().as_bytes().first() == Some(&b'.')) {\n        return Ok(());\n    }\n    let mut last_loader_name;\n    for loader in framework.resource_loaders.iter() {\n        last_loader_name = Some(loader.name());\n        let loaded = loader.try_load(path, reader, framework).with_context(|| {\n            anyhow!(\"Error while loading resource by {:?} at path {:?}\", loader.name(), path)\n        })?;\n        if let Some((id, resource)) = loaded {\n            ensure!(\n                !resources.contains_key(&id),\n                \"Loader {:?} succeeded to load {:?} which has been already loaded by {:?}\",\n                loader.name(),\n                id,\n                last_loader_name\n            );\n            resources.insert(id, resource);\n            break;\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "nnef/src/lib.rs",
    "content": "#![allow(clippy::len_zero)]\n#[macro_use]\nextern crate log;\n\npub mod ast;\npub mod deser;\npub mod framework;\nmod liquid;\npub mod ops;\npub mod registry;\npub mod resource;\npub mod ser;\npub mod tensors;\nmod transform;\n\npub use ast::ProtoModel;\n\npub use tract_core;\npub use tract_core::prelude::tract_ndarray;\npub use tract_core::prelude::tract_num_traits;\n\npub mod prelude {\n    pub use tract_core;\n    pub use tract_core::prelude::*;\n}\n\npub mod internal {\n    pub use crate::ast::dump_doc::DocDumper;\n    pub use crate::ast::parse::{parse_assignments, parse_parameters};\n    pub use crate::ast::{\n        Assignment, FragmentDecl, FragmentDef, Identifier, Parameter, RValue, TypeName, param,\n    };\n    pub use crate::deser::{ModelBuilder, ResolvedInvocation, Value};\n    pub use crate::framework::Nnef;\n    pub use crate::prelude::*;\n    pub use crate::registry::*;\n    pub use crate::resource::{\n        DatLoader, GraphNnefLoader, GraphQuantLoader, Resource, ResourceLoader, TypedModelLoader,\n        TypedModelResource,\n    };\n    pub use crate::ser::{IntoAst, invocation, logical, numeric, string};\n    pub use std::any::TypeId;\n    pub use tract_core::internal::*;\n}\n\npub fn nnef() -> framework::Nnef {\n    framework::Nnef::default()\n}\n"
  },
  {
    "path": "nnef/src/liquid.rs",
    "content": "use crate::internal::*;\n\npub(crate) fn process_file(\n    input: &str,\n    resources: &HashMap<String, Arc<dyn Resource>>,\n) -> TractResult<String> {\n    let parser = liquid::ParserBuilder::with_stdlib().build()?;\n    let tmpl = parser.parse(input)?;\n    let mut globals = liquid::object!({});\n    for (k, v) in resources {\n        if let Some(value) = v.to_liquid_value() {\n            globals.insert(k.into(), value);\n        }\n    }\n    Ok(tmpl.render(&globals)?)\n}\n"
  },
  {
    "path": "nnef/src/ops/core/broadcast.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_broadcast);\n    registry.register_primitive(\n        \"tract_core_broadcast\",\n        &[TypeName::Scalar.tensor().named(\"input\"), TypeName::Integer.array().named(\"shape\")],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_broadcast,\n    );\n}\n\nfn de_broadcast(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let shape: TVec<TDim> =\n        builder.allowing_new_symbols(|builder| invocation.named_arg_as(builder, \"shape\"))?;\n    builder.wire(ops::array::MultiBroadcastTo { shape: shape.into() }, &[wire])\n}\n\nfn ser_broadcast(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::array::MultiBroadcastTo,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\"tract_core_broadcast\", &[wire], &[(\"shape\", tdims(&op.shape))])))\n}\n"
  },
  {
    "path": "nnef/src/ops/core/cast.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::cast::Cast;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(cast_dump);\n    registry.register_primitive(\n        \"tract_core_cast\",\n        &cast_parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        cast_load,\n    );\n}\n\nfn cast_parameters() -> Vec<Parameter> {\n    vec![TypeName::Scalar.tensor().named(\"input\"), TypeName::String.named(\"to\")]\n}\n\nfn cast_dump(ast: &mut IntoAst, node: &TypedNode, op: &Cast) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\"tract_core_cast\", &[input], &[(\"to\", datum_type(op.to))])))\n}\n\nfn cast_load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let invocation_dt = invocation.dt_from_quant_file.first().copied().flatten();\n    let to = if let Ok(s) = invocation.named_arg_as::<String>(builder, \"to\") {\n        let dt: DatumType = s.parse()?;\n        if let Some(invocation_dt) = invocation_dt {\n            if invocation_dt.unquantized() != dt.unquantized() {\n                bail!(\"Mismatched cast: graph.quant {:?}, got graph.nnef {:?}\", invocation_dt, dt)\n            } else {\n                invocation_dt\n            }\n        } else {\n            dt\n        }\n    } else {\n        invocation_dt.context(\"No datum type for cast\")?\n    };\n    builder.wire(Cast { to }, &[input])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/complex.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::math::{ComplexToInnerDim, InnerDimToComplex};\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_ctid);\n    registry.register_dumper(ser_idtc);\n    registry.register_primitive(\n        \"tract_core_complex_to_inner_dim\",\n        &[TypeName::Complex.tensor().named(\"input\")],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_ctid,\n    );\n    registry.register_primitive(\n        \"tract_core_inner_dim_to_complex\",\n        &[TypeName::Scalar.tensor().named(\"input\")],\n        &[(\"output\", TypeName::Complex.tensor())],\n        de_idtc,\n    );\n}\n\nfn ser_ctid(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    _: &ComplexToInnerDim,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\"tract_core_complex_to_inner_dim\", &[wire], &[])))\n}\n\nfn de_ctid(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    builder.wire(ComplexToInnerDim, &[wire])\n}\n\nfn ser_idtc(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    _: &InnerDimToComplex,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\"tract_core_inner_dim_to_complex\", &[wire], &[])))\n}\n\nfn de_idtc(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    builder.wire(InnerDimToComplex, &[wire])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/downsample.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::Downsample;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_downsample);\n    registry.register_primitive(\n        \"tract_core_downsample\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Integer.named(\"stride\"),\n            TypeName::Integer.named(\"modulo\").default(0),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_downsample,\n    );\n}\n\nfn ser_downsample(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &Downsample,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\n        \"tract_core_downsample\",\n        &[wire],\n        &[\n            (\"axis\", numeric(op.axis)),\n            (\"stride\", numeric(op.stride)),\n            (\"modulo\", numeric(op.modulo)),\n        ],\n    )))\n}\n\nfn de_downsample(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let axis = invocation.named_arg_as(builder, \"axis\")?;\n    let stride = invocation.named_arg_as::<i64>(builder, \"stride\")? as isize;\n    let modulo = invocation.named_arg_as(builder, \"modulo\")?;\n    builder.wire(Downsample { axis, stride, modulo }, &[wire])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/dyn_slice.rs",
    "content": "use tract_core::ops::array::DynSlice;\n\nuse crate::internal::*;\nuse crate::ser::tdim;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser);\n    registry.register_primitive(\n        \"tract_core_dyn_slice\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.named(\"start\"),\n            TypeName::Integer.named(\"end\"), //.default(0),\n            TypeName::Integer.named(\"len\"),\n            TypeName::Integer.named(\"axis\"),\n            //            TypeName::Integer.named(\"to_the_end\").default(0),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        deser,\n    );\n}\n\npub fn ser(ast: &mut IntoAst, node: &TypedNode, op: &DynSlice) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let start = ast.mapping[&node.inputs[1]].clone();\n    let end = ast.mapping[&node.inputs[2]].clone();\n    Ok(Some(invocation(\n        \"tract_core_dyn_slice\",\n        &[input, start, end],\n        &[(\"axis\", numeric(op.axis)), (\"len\", tdim(&op.len))],\n    )))\n}\n\npub fn deser(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let start = invocation.named_arg_as(builder, \"start\")?;\n    let end = invocation.named_arg_as(builder, \"end\")?;\n    let axis = invocation.named_arg_as(builder, \"axis\")?;\n    let len = invocation.named_arg_as(builder, \"len\")?;\n    builder.wire(DynSlice { axis, len }, &[wire, start, end])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/einsum.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::einsum::EinSum;\nuse tract_core::tract_data::itertools::Itertools;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser);\n    registry.register_primitive(\n        \"tract_core_einsum\",\n        &parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_einsum,\n    );\n    registry.register_primitive(\n        \"tract_core_einsum_q\",\n        &parameters_q(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_einsum_q,\n    );\n}\n\npub fn parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().array().named(\"inputs\"),\n        TypeName::String.named(\"expr\"),\n        TypeName::String.named(\"acc\"),\n        TypeName::String.named(\"output\").default(\"\"),\n    ]\n}\n\npub fn parameters_q() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().array().named(\"inputs\"),\n        TypeName::String.named(\"expr\"),\n        TypeName::String.named(\"acc\"),\n        TypeName::String.named(\"output\").default(\"\"),\n        TypeName::Scalar.tensor().named(\"bias\").default(0),\n        TypeName::Integer.tensor().named(\"a0\"),\n        TypeName::Scalar.tensor().named(\"a_scale\"),\n        TypeName::Integer.tensor().named(\"b0\"),\n        TypeName::Scalar.tensor().named(\"b_scale\"),\n        TypeName::Integer.tensor().named(\"c0\"),\n        TypeName::Scalar.tensor().named(\"c_scale\"),\n    ]\n}\n\npub fn ser(ast: &mut IntoAst, node: &TypedNode, op: &EinSum) -> TractResult<Option<Arc<RValue>>> {\n    if op.q_params.is_some() { ser_einsum_q(ast, node) } else { ser_einsum(ast, node) }\n}\n\npub fn ser_einsum(ast: &mut IntoAst, node: &TypedNode) -> TractResult<Option<Arc<RValue>>> {\n    let einsum = node.op_as::<EinSum>().unwrap();\n    let inputs: Vec<_> = node.inputs.iter().map(|i| (*ast.mapping[i]).clone()).collect();\n    Ok(Some(invocation(\n        \"tract_core_einsum\",\n        &[Arc::new(RValue::Array(inputs))],\n        &[\n            (\"expr\", string(einsum.axes.to_string())),\n            (\"acc\", datum_type(einsum.operating_dt)),\n            (\"output\", einsum.q_params.map(datum_type).unwrap_or_else(|| string(\"\"))),\n        ],\n    )))\n}\n\npub fn ser_einsum_q(ast: &mut IntoAst, node: &TypedNode) -> TractResult<Option<Arc<RValue>>> {\n    let einsum = node.op_as::<EinSum>().unwrap();\n    let inputs = node.inputs.iter().map(|i| (*ast.mapping[i]).clone()).collect_vec();\n    Ok(Some(invocation(\n        \"tract_core_einsum_q\",\n        &[Arc::new(RValue::Array(vec![inputs[0].clone(), inputs[1].clone()]))],\n        &[\n            (\"expr\", string(einsum.axes.to_string())),\n            (\"acc\", datum_type(einsum.operating_dt)),\n            (\"output\", einsum.q_params.map(datum_type).unwrap_or_else(|| string(\"\"))),\n            (\"bias\", inputs[2].clone()),\n            (\"a0\", inputs[3].clone()),\n            (\"a_scale\", inputs[4].clone()),\n            (\"b0\", inputs[5].clone()),\n            (\"b_scale\", inputs[6].clone()),\n            (\"c0\", inputs[7].clone()),\n            (\"c_scale\", inputs[8].clone()),\n        ],\n    )))\n}\n\npub fn de_einsum(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let expr = invocation.named_arg_as::<String>(builder, \"expr\")?.parse::<AxesMapping>()?;\n    let inputs: TVec<OutletId> = invocation.named_arg_as(builder, \"inputs\")?;\n    let operating_dt = invocation.named_arg_as::<String>(builder, \"acc\")?;\n    let operating_dt = operating_dt.parse()?;\n    let einsum = EinSum::new(expr, operating_dt);\n    builder.wire(einsum, &inputs)\n}\n\npub fn de_einsum_q(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let expr = invocation.named_arg_as::<String>(builder, \"expr\")?.parse::<AxesMapping>()?;\n    let mut inputs: TVec<OutletId> = invocation.named_arg_as(builder, \"inputs\")?;\n    for qp in parameters_q().iter().skip(4) {\n        inputs.push(invocation.named_arg_as(builder, &qp.id.0)?);\n    }\n    let operating_dt = invocation.named_arg_as::<String>(builder, \"acc\")?;\n    let operating_dt = operating_dt.parse()?;\n    let output_dt = if let Some(odt) =\n        invocation.get_named_arg_as::<String>(builder, \"output\")?.filter(|odt| odt.len() > 0)\n    {\n        odt.parse()?\n    } else {\n        bail!(\"Expected an output type for tract_core_einsum_q\")\n    };\n    let einsum = EinSum::newq(expr, operating_dt, output_dt);\n    builder.wire(einsum, &inputs)\n}\n"
  },
  {
    "path": "nnef/src/ops/core/fft.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::fft::{Fft, Stft};\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_fft);\n    registry.register_primitive(\n        \"tract_core_fft\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Logical.named(\"inverse\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_fft,\n    );\n    registry.register_dumper(ser_stft);\n    registry.register_primitive(\n        \"tract_core_stft\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Integer.named(\"frame\"),\n            TypeName::Integer.named(\"stride\"),\n            TypeName::Scalar.tensor().named(\"window\").default(false),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_stft,\n    );\n}\n\nfn ser_fft(ast: &mut IntoAst, node: &TypedNode, op: &Fft) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\n        \"tract_core_fft\",\n        &[input],\n        &[(\"axis\", numeric(op.axis)), (\"inverse\", logical(op.inverse))],\n    )))\n}\n\nfn de_fft(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let axis: usize = invocation.named_arg_as(builder, \"axis\")?;\n    let inverse: bool = invocation.named_arg_as(builder, \"inverse\")?;\n    let op = Fft { axis, inverse };\n    builder.wire(op, &[input])\n}\n\nfn ser_stft(ast: &mut IntoAst, node: &TypedNode, op: &Stft) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let mut named: TVec<(_, RValue)> = tvec![\n        (\"axis\", numeric(op.axis)),\n        (\"frame\", numeric(op.frame)),\n        (\"stride\", numeric(op.stride)),\n    ];\n    if let Some(w) = &op.window {\n        let w = ast.konst(format!(\"{}_window\", node.name), w)?;\n        named.push((\"window\", (*w).clone()));\n    }\n    Ok(Some(invocation(\"tract_core_stft\", &[input], &named)))\n}\n\nfn de_stft(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let axis: usize = invocation.named_arg_as(builder, \"axis\")?;\n    let frame: usize = invocation.named_arg_as(builder, \"frame\")?;\n    let stride: usize = invocation.named_arg_as(builder, \"stride\")?;\n    let window = invocation.optional_named_arg_as::<Arc<Tensor>>(builder, \"window\")?;\n    let op = Stft { axis, frame, stride, window };\n    builder.wire(op, &[input])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/gather.rs",
    "content": "use tract_core::ops::array::Gather;\n\nuse crate::internal::*;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_gather);\n    registry.register_primitive(\n        \"tract_core_gather\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Scalar.tensor().named(\"indices\"),\n            TypeName::Integer.named(\"axis\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_gather,\n    );\n\n    macro_rules! gather_op_nnef {\n        ($GatherOp:ty, $name:ident, $field_name:ident) => {\n            mod $name {\n                use crate::internal::*;\n\n                pub fn ser_gather(\n                    ast: &mut IntoAst,\n                    node: &TypedNode,\n                    op: &$GatherOp,\n                ) -> TractResult<Option<Arc<RValue>>> {\n                    let wire = ast.mapping[&node.inputs[0]].clone();\n                    let indices = ast.mapping[&node.inputs[1]].clone();\n                    Ok(Some(invocation(\n                        concat!(\"tract_core_\", stringify!($name)),\n                        &[wire, indices],\n                        &[(stringify!($field_name), numeric(op.$field_name))],\n                    )))\n                }\n\n                pub fn de_gather(\n                    builder: &mut ModelBuilder,\n                    invocation: &ResolvedInvocation,\n                ) -> TractResult<Value> {\n                    let wire = invocation.named_arg_as(builder, \"input\")?;\n                    let indices = invocation.named_arg_as(builder, \"indices\")?;\n                    let cast_indices = builder.wire_as_outlets(\n                        tract_core::ops::cast::cast(i64::datum_type()),\n                        &[indices],\n                    )?[0];\n                    let value = invocation.named_arg_as(builder, stringify!($field_name))?;\n                    builder.wire((<$GatherOp>::new(value)), &[wire, cast_indices])\n                }\n            }\n            registry.register_dumper($name::ser_gather);\n            registry.register_primitive(\n                concat!(\"tract_core_\", stringify!($name)),\n                &[\n                    TypeName::Scalar.tensor().named(\"input\"),\n                    TypeName::Scalar.tensor().named(\"indices\"),\n                    TypeName::Integer.named(stringify!($field_name)),\n                ],\n                &[(\"output\", TypeName::Scalar.tensor())],\n                $name::de_gather,\n            );\n        };\n    }\n    gather_op_nnef!(tract_core::ops::array::GatherElements, gather_elements, axis);\n    gather_op_nnef!(tract_core::ops::array::GatherNd, gather_nd, batch_dims);\n}\n\npub fn ser_gather(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &Gather,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let indices = ast.mapping[&node.inputs[1]].clone();\n    let mut named_args = tvec!((\"axis\", numeric(op.axis)));\n    if let Some(dt) = op.output_type {\n        named_args.push((\"datum_type\", string(format!(\"{dt:?}\"))));\n    }\n    Ok(Some(invocation(\"tract_core_gather\", &[wire, indices], &named_args)))\n}\n\npub fn de_gather(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let indices = invocation.named_arg_as(builder, \"indices\")?;\n    let output_type: Option<DatumType> = invocation\n        .optional_named_arg_as::<String>(builder, \"datum_type\")?\n        .map(|s| s.parse())\n        .transpose()?;\n    let cast_indices =\n        builder.wire_as_outlets(tract_core::ops::cast::cast(i64::datum_type()), &[indices])?[0];\n    let axis = invocation.named_arg_as(builder, \"axis\")?;\n    builder.wire(Gather { axis, output_type }, &[wire, cast_indices])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/gelu_approximate.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse std::any::TypeId;\nuse tract_core::ops::element_wise::ElementWiseOp;\nuse tract_core::ops::nn::gelu_approximate::GeluApproximate;\n\nfn parameters() -> Vec<Parameter> {\n    vec![TypeName::Scalar.tensor().named(\"input\"), TypeName::Logical.named(\"fast_impl\")]\n}\n\nfn dump(ast: &mut IntoAst, node: &TypedNode) -> TractResult<Option<Arc<RValue>>> {\n    let op = node.op_as::<ElementWiseOp>().unwrap().0.downcast_ref::<GeluApproximate>().unwrap();\n    let input = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\n        \"tract_core_gelu_approx\",\n        &[input],\n        &[(\"fast_impl\", logical(op.fast_impl))],\n    )))\n}\n\nfn load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let fast_impl = invocation.named_arg_as(builder, \"fast_impl\")?;\n    builder.wire(ElementWiseOp(Box::new(GeluApproximate { fast_impl }), None), &[input])\n}\n\npub fn register(registry: &mut Registry) {\n    registry.register_element_wise(\n        \"tract_core_gelu_approx\",\n        TypeId::of::<GeluApproximate>(),\n        Box::new(dump),\n        parameters(),\n        load,\n    );\n    // Backward compatibility alias\n    registry.register_element_wise(\n        \"tract_transformers_gelu_approx\",\n        TypeId::of::<GeluApproximate>(),\n        Box::new(dump),\n        parameters(),\n        load,\n    );\n}\n"
  },
  {
    "path": "nnef/src/ops/core/is_inf.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::element_wise::ElementWiseOp;\nuse tract_core::ops::math::IsInf;\n\npub fn parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"input\"),\n        TypeName::Logical.named(\"detect_positive\").default(true),\n        TypeName::Logical.named(\"detect_negative\").default(true),\n    ]\n}\n\npub fn dump(ast: &mut IntoAst, node: &TypedNode) -> TractResult<Option<Arc<RValue>>> {\n    let op = node.op_as::<ElementWiseOp>().unwrap().0.downcast_ref::<IsInf>().unwrap();\n    let input = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\n        \"tract_core_is_inf\",\n        &[input],\n        &[\n            (\"detect_negative\", logical(op.detect_negative)),\n            (\"detect_positive\", logical(op.detect_positive)),\n        ],\n    )))\n}\n\npub fn load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let detect_positive = invocation.named_arg_as(builder, \"detect_positive\")?;\n    let detect_negative = invocation.named_arg_as(builder, \"detect_negative\")?;\n    let op = IsInf { detect_negative, detect_positive };\n    builder.wire(ElementWiseOp(Box::new(op), None), &[input])\n}\n\npub fn register(registry: &mut Registry) {\n    registry.register_element_wise(\n        \"tract_core_is_inf\",\n        TypeId::of::<IsInf>(),\n        Box::new(dump),\n        parameters(),\n        load,\n    );\n}\n"
  },
  {
    "path": "nnef/src/ops/core/matmul.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::einsum::EinSum;\n\nuse super::qmatmul::from_legacy_axes_spec;\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_core_matmul\",\n        &matmul_parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        matmul_load,\n    );\n}\n\nfn matmul_parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"A\"),\n        TypeName::Scalar.tensor().named(\"B\"),\n        TypeName::Integer.array().named(\"axes\"),\n    ]\n}\n\nfn matmul_load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let a: OutletId = invocation.named_arg_as(builder, \"A\")?;\n    let b: OutletId = invocation.named_arg_as(builder, \"B\")?;\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n    let fact = builder.model.outlet_fact(a)?;\n    let axes = from_legacy_axes_spec(&axes, fact.rank())?;\n    builder.wire(EinSum::new(axes, fact.datum_type), &[a, b])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/one_hot.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::array::OneHot;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(one_hot_dump);\n    registry.register_primitive(\n        \"tract_core_one_hot\",\n        &one_hot_parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        one_hot_load,\n    );\n}\n\npub fn one_hot_parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"input\"),\n        TypeName::Integer.named(\"axis\"),\n        TypeName::Integer.named(\"dim\"),\n        TypeName::Scalar.named(\"value_off\").default(0.0),\n        TypeName::Scalar.named(\"value_on\").default(1.0),\n    ]\n}\n\npub fn one_hot_dump(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &OneHot,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\n        \"tract_core_one_hot\",\n        &[input],\n        &[\n            (\"axis\", numeric(op.axis)),\n            (\"dim\", numeric(op.dim)),\n            (\"value_off\", numeric(op.off.cast_to_scalar::<f32>()?)),\n            (\"value_on\", numeric(op.on.cast_to_scalar::<f32>()?)),\n        ],\n    )))\n}\n\npub fn one_hot_load(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let axis = invocation.named_arg_as(builder, \"axis\")?;\n    let dim = invocation.named_arg_as(builder, \"dim\")?;\n    let off = invocation.named_arg_as(builder, \"value_off\")?;\n    let on = invocation.named_arg_as(builder, \"value_on\")?;\n    let op = OneHot { axis, dim, on, off };\n    builder.wire(op, &[input])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/qconv.rs",
    "content": "use crate::deser::Value;\nuse crate::internal::*;\nuse crate::ops::nnef::deser::read_conv_parameters;\nuse crate::ops::nnef::ser::make_conv_named_args;\nuse crate::ser::*;\nuse tract_core::ops::cnn::Conv;\nuse tract_core::ops::cnn::KernelFormat;\n\nuse super::qmatmul::qparams_as_outlets;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(qconv_unary_dump);\n    registry.register_primitive(\n        \"tract_core_qconv\",\n        &qconv_parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        qconv_load,\n    );\n}\n\nfn qconv_parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"input\"),\n        TypeName::Scalar.tensor().named(\"filter\"),\n        TypeName::Scalar.tensor().named(\"bias\").default(0),\n        TypeName::Integer.spec().named(\"group\"),\n        TypeName::Integer.array().named(\"dilation\"),\n        TypeName::Integer.array().named(\"stride\"),\n        TypeName::Integer.array().array().named(\"padding\"),\n        TypeName::String.spec().named(\"border\"),\n        TypeName::Integer.spec().named(\"a0\"),\n        TypeName::Scalar.spec().named(\"a_scale\"),\n        TypeName::Integer.spec().named(\"b0\"),\n        TypeName::Scalar.spec().named(\"b_scale\"),\n        TypeName::Integer.spec().named(\"c0\"),\n        TypeName::Scalar.spec().named(\"c_scale\"),\n    ]\n}\n\nfn qconv_unary_dump(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &Conv,\n) -> TractResult<Option<Arc<RValue>>> {\n    if op.q_params.is_none() || node.outputs[0].fact.datum_type.is_quantized() {\n        return Ok(None);\n    }\n    let mut named_args = make_conv_named_args(node, &op.pool_spec, op.group, false, None)?;\n\n    for (ix, name) in [\"b0\", \"b_scale\", \"a0\", \"a_scale\", \"c0\", \"c_scale\"].iter().enumerate() {\n        named_args.push((name, (*ast.mapping[&node.inputs[3 + ix]]).clone()));\n    }\n\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    ensure!(op.kernel_fmt == KernelFormat::OIHW);\n    let weights = ast.mapping[&node.inputs[1]].clone();\n    let bias = ast.mapping[&node.inputs[2]].clone();\n    let inputs = tvec![wire, weights, bias];\n\n    Ok(Some(invocation(\"tract_core_qconv\", &inputs, &named_args)))\n}\n\nfn qconv_load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let mut inputs: TVec<OutletId> = tvec!(invocation.named_arg_as(builder, \"input\")?);\n    inputs.push(invocation.named_arg_as(builder, \"filter\")?);\n    inputs.push(invocation.named_arg_as(builder, \"bias\")?);\n\n    let input_fact = builder.model.outlet_fact(inputs[0])?.clone();\n    let kernel_fact = builder.model.outlet_fact(inputs[1])?.clone();\n\n    if input_fact.rank() != kernel_fact.rank() {\n        bail!(\n            \"Convolution input expected as NCHW, filter as OIHW. Got {:?} and {:?}.\",\n            input_fact,\n            kernel_fact\n        );\n    }\n\n    let (group, pool_spec) = read_conv_parameters(\n        builder,\n        invocation,\n        kernel_fact.shape.as_concrete().context(\"Expect fixed size kernel\")?,\n        &input_fact,\n    )?;\n\n    let mut qparams = qparams_as_outlets(builder, invocation).context(\"Loading qparams\")?;\n    qparams.swap(0, 2);\n    qparams.swap(1, 3);\n    inputs.extend(qparams.iter().cloned());\n\n    let Some(c0) = &builder.model.outlet_fact(qparams[4])?.konst else {\n        bail!(\"For quantized convolution, output quantization must be static\");\n    };\n    let Some(c_scale) = &builder.model.outlet_fact(qparams[5])?.konst else {\n        bail!(\"For quantized convolution, output quantization must be static\");\n    };\n    let output_dt = input_fact.datum_type.with_qparams(QParams::ZpScale {\n        zero_point: c0.cast_to_scalar()?,\n        scale: c_scale.cast_to_scalar()?,\n    });\n\n    let op: Box<dyn TypedOp> =\n        Box::new(Conv::new(pool_spec, KernelFormat::OIHW, group, Some(output_dt)));\n\n    builder.wire(op, &inputs)\n}\n"
  },
  {
    "path": "nnef/src/ops/core/qmatmul.rs",
    "content": "use std::str::FromStr;\n\nuse crate::internal::*;\nuse tract_core::ops::einsum::EinSum;\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_core_qmatmul\",\n        &qmatmul_parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        qmatmul_load,\n    );\n}\n\nfn qmatmul_parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"A\"),\n        TypeName::Scalar.tensor().named(\"B\"),\n        TypeName::Scalar.tensor().named(\"bias\").default(0),\n        TypeName::Integer.array().named(\"axes\"),\n        TypeName::Integer.spec().named(\"a0\"),\n        TypeName::Scalar.spec().named(\"a_scale\"),\n        TypeName::Integer.spec().named(\"b0\"),\n        TypeName::Scalar.spec().named(\"b_scale\"),\n        TypeName::Integer.spec().named(\"c0\"),\n        TypeName::Scalar.spec().named(\"c_scale\"),\n        TypeName::String.spec().named(\"output_type\"),\n    ]\n}\n\npub fn qparams_as_outlets(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<TVec<OutletId>> {\n    let a0: OutletId = invocation\n        .named_arg_as::<OutletId>(builder, \"a0\")\n        .or_else(|_| builder.add_const(rctensor0(0i32)))?;\n    let a_scale: OutletId = invocation\n        .named_arg_as::<OutletId>(builder, \"a_scale\")\n        .or_else(|_| builder.add_const(rctensor0(1f32)))?;\n    let b0: OutletId = invocation\n        .named_arg_as::<OutletId>(builder, \"b0\")\n        .or_else(|_| builder.add_const(rctensor0(0i32)))?;\n    let b_scale: OutletId = invocation\n        .named_arg_as::<OutletId>(builder, \"b_scale\")\n        .or_else(|_| builder.add_const(rctensor0(1f32)))?;\n    let c0: OutletId = invocation\n        .named_arg_as::<OutletId>(builder, \"c0\")\n        .or_else(|_| builder.add_const(rctensor0(0i32)))?;\n    let c_scale: OutletId = invocation\n        .named_arg_as::<OutletId>(builder, \"c_scale\")\n        .or_else(|_| builder.add_const(rctensor0(1f32)))?;\n    let a0 = builder.wire_as_outlets(tract_core::ops::cast::cast(i32::datum_type()), &[a0])?[0];\n    let b0 = builder.wire_as_outlets(tract_core::ops::cast::cast(i32::datum_type()), &[b0])?[0];\n    let c0 = builder.wire_as_outlets(tract_core::ops::cast::cast(i32::datum_type()), &[c0])?[0];\n    Ok(tvec!(a0, a_scale, b0, b_scale, c0, c_scale))\n}\n\nfn qmatmul_load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let a: OutletId = invocation.named_arg_as(builder, \"A\")?;\n    let b: OutletId = invocation.named_arg_as(builder, \"B\")?;\n    let bias: OutletId = invocation.named_arg_as(builder, \"bias\")?;\n    let qparams = qparams_as_outlets(builder, invocation)?;\n    let inputs: Vec<OutletId> = [a, b, bias].into_iter().chain(qparams).collect();\n    let c_dt = if let Some(c) = invocation.dt_from_quant_file.first().cloned().flatten() {\n        c\n    } else {\n        DatumType::from_str(&invocation.named_arg_as::<String>(builder, \"output_type\")?)?\n    };\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n    let axes = from_legacy_axes_spec(&axes, builder.model.outlet_fact(a)?.rank())?;\n    builder.wire(EinSum { axes, operating_dt: i32::datum_type(), q_params: Some(c_dt) }, &inputs)\n}\n\npub fn from_legacy_axes_spec(spec: &[usize], rank: usize) -> TractResult<AxesMapping> {\n    let [a_m, a_k, b_k, b_n, c_m, c_n] = spec else { bail!(\"Invalid axes specification\") };\n    AxesMapping::disconnected_for_ranks(&[rank, rank], &[rank])?\n        .renaming((InOut::In(0), *a_m), 'm')?\n        .linking('m', (InOut::Out(0), *c_m))?\n        .renaming((InOut::In(0), *a_k), 'k')?\n        .linking('k', (InOut::In(1), *b_k))?\n        .renaming((InOut::In(1), *b_n), 'n')?\n        .linking('n', (InOut::In(0), *c_n))\n}\n"
  },
  {
    "path": "nnef/src/ops/core/range.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::array::Range;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(range_dump);\n    registry.register_primitive(\n        \"tract_core_range\",\n        &range_parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        range_load,\n    );\n}\n\nfn range_parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Integer.named(\"start\"),\n        TypeName::Integer.named(\"end\"),\n        TypeName::Integer.named(\"step\"),\n    ]\n}\n\nfn range_dump(ast: &mut IntoAst, node: &TypedNode, _: &Range) -> TractResult<Option<Arc<RValue>>> {\n    let start = ast.mapping[&node.inputs[0]].clone();\n    let end = ast.mapping[&node.inputs[1]].clone();\n    let step = ast.mapping[&node.inputs[2]].clone();\n\n    Ok(Some(invocation(\"tract_core_range\", &[start, end, step], &[])))\n}\n\nfn range_load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let start: OutletId = invocation.named_arg_as(builder, \"start\")?;\n    let end: OutletId = invocation.named_arg_as(builder, \"end\")?;\n    let step: OutletId = invocation.named_arg_as(builder, \"step\")?;\n\n    let len = builder.model.symbols.new_with_prefix(\"range\");\n    builder.wire(Range::new(len.into()), &[start, end, step])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/reduce.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::nn::{Reduce, Reducer};\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_reduce);\n    for red in &[\n        \"tract_core_argmax_reduce_last\",\n        \"tract_core_argmin_reduce_last\",\n        \"tract_core_product_reduce\",\n    ] {\n        registry.register_primitive(\n            red,\n            &[TypeName::Scalar.tensor().named(\"input\"), TypeName::Integer.array().named(\"axes\")],\n            &[(\"output\", TypeName::Scalar.tensor())],\n            de_reduce,\n        );\n    }\n}\n\nfn ser_reduce(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &Reduce,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let oper = match op.reducer {\n        Reducer::ArgMax(last) if last => \"tract_core_argmax_reduce_last\",\n        Reducer::ArgMin(last) if last => \"tract_core_argmin_reduce_last\",\n        Reducer::Prod => \"tract_core_product_reduce\",\n        _ => return Ok(None),\n    };\n    Ok(Some(invocation(oper, &[wire], &[(\"axes\", ints(&op.axes))])))\n}\n\nfn de_reduce(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let reducer = match &*invocation.invocation.id.0 {\n        \"tract_core_argmin_reduce_last\" => Reducer::ArgMin(true),\n        \"tract_core_argmax_reduce_last\" => Reducer::ArgMax(true),\n        \"tract_core_product_reduce\" => Reducer::Prod,\n        _ => panic!(),\n    };\n    let axes = invocation.named_arg_as(builder, \"axes\")?;\n    let reduce = Reduce { axes, reducer };\n    builder.wire(reduce, &[wire])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/rms_norm.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::nn::RmsNorm;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_rms_norm);\n    registry.register_primitive(\n        \"tract_core_rms_norm\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Scalar.named(\"eps\").default(1e-6f32),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_rms_norm,\n    );\n    // Backward compatibility alias\n    registry.register_primitive(\n        \"tract_transformers_rms_norm\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Scalar.named(\"eps\").default(1e-6f32),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_rms_norm,\n    );\n}\n\nfn de_rms_norm(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let axis: usize = invocation.named_arg_as(builder, \"axis\")?;\n    let eps = invocation.named_arg_as(builder, \"eps\")?;\n    builder.wire(RmsNorm { axis, eps }, &[input])\n}\n\nfn ser_rms_norm(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &RmsNorm,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\n        \"tract_core_rms_norm\",\n        &[input],\n        &[(\"axis\", numeric(op.axis)), (\"eps\", numeric(op.eps.cast_to_scalar::<f32>()?))],\n    )))\n}\n"
  },
  {
    "path": "nnef/src/ops/core/scan.rs",
    "content": "use crate::ast;\nuse crate::ast::Identifier;\nuse crate::deser::Value;\nuse crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::scan::*;\nuse tract_itertools::Itertools;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_scan);\n    registry.register_primitive(\n        \"tract_core_scan\",\n        &[\n            TypeName::String.named(\"body\"),\n            ast::TypeSpec::Tuple(vec![\n                TypeName::String.spec(),   // body param name\n                TypeName::Scalar.tensor(), // input\n                TypeName::Integer.spec(),  // axis\n                TypeName::Integer.spec(),  // step\n            ])\n            .array()\n            .named(\"scan\"),\n            ast::TypeSpec::Tuple(vec![\n                TypeName::String.spec(),   // body param name\n                TypeName::Scalar.tensor(), // input\n            ])\n            .array()\n            .named(\"full\"),\n            ast::TypeSpec::Tuple(vec![\n                TypeName::String.spec(),   // body param name\n                TypeName::Scalar.tensor(), // initializer\n                TypeName::String.spec(),   // body result name\n            ])\n            .array()\n            .named(\"state\"),\n            ast::TypeSpec::Tuple(vec![\n                TypeName::String.spec(),  // body param name\n                TypeName::String.spec(),  // \"full\" or \"last\"\n                TypeName::Integer.spec(), // axis (ignored for last)\n                TypeName::Integer.spec(), // step (ignored for last)\n            ])\n            .array()\n            .named(\"output\"),\n            TypeName::Integer.spec().named(\"skip\").default(0), // needed for pulse\n            TypeName::Integer.spec().named(\"reset_every_turn\").default(0), // needed for pulse\n        ],\n        &[(\"outputs\", TypeName::Scalar.tensor().array())],\n        de_scan,\n    );\n}\n\nfn ser_scan(ast: &mut IntoAst, node: &TypedNode, op: &Scan) -> TractResult<Option<Arc<RValue>>> {\n    let (mut body, body_tensors) = crate::ser::to_fragment_def(ast, &op.body)?;\n    body.decl.id = Identifier(format!(\"scan_body_{}\", ast.fragments.len()));\n    let mut scan = vec![];\n    let mut state = vec![];\n    let mut full = vec![];\n    let mut outputs = vec![];\n    for (slot, input) in op.input_mapping.iter().enumerate() {\n        let name = string(&body.decl.parameters[slot].id.0);\n        match input {\n            InputMapping::Scan(info) => {\n                scan.push(tuple_4(\n                    name,\n                    ast.mapping[&node.inputs[slot]].as_ref().clone(),\n                    numeric(info.axis),\n                    numeric(info.chunk),\n                ));\n            }\n            InputMapping::State => {\n                let initializer = (*ast.mapping[&node.inputs[slot]]).clone();\n                let output: usize = op\n                    .output_mapping\n                    .iter()\n                    .enumerate()\n                    .filter(|(_ix, o)| o.state)\n                    .nth(state.len())\n                    .unwrap()\n                    .0;\n                state.push(tuple_3(\n                    name,\n                    initializer,\n                    string(body.decl.results[output].id.clone()),\n                ));\n            }\n            InputMapping::Full => {\n                full.push(tuple_2(name, ast.mapping[&node.inputs[slot]].as_ref().clone()))\n            }\n        }\n    }\n    for tensor in body_tensors.iter().sorted_by_key(|t| &t.label) {\n        let t = ast.konst_variable(&tensor.label, &tensor.value)?;\n        full.push(tuple_2(string(&tensor.parameter_id), t.as_ref().clone()));\n    }\n    for slot in 0..node.outputs.len() {\n        if let Some((r_ix, om)) = op\n            .output_mapping\n            .iter()\n            .enumerate()\n            .find(|(_ix, om)| om.scan.map(|s| s.0) == Some(slot))\n        {\n            outputs.push(tuple_4(\n                string(body.decl.results[r_ix].id.clone()),\n                string(\"full\"),\n                numeric(om.scan.unwrap().1.axis),\n                numeric(om.scan.unwrap().1.chunk),\n            ));\n        } else if let Some((r_ix, _om)) =\n            op.output_mapping.iter().enumerate().find(|(_ix, om)| om.last_value_slot == Some(slot))\n        {\n            outputs.push(tuple_4(\n                string(body.decl.results[r_ix].id.clone()),\n                string(\"last\"),\n                numeric(0),\n                numeric(1),\n            ));\n        } else {\n            bail!(\"output {} is unbound\", slot);\n        };\n    }\n    let invoke = invocation(\n        \"tract_core_scan\",\n        &[],\n        &[\n            (\"body\", string(&body.decl.id)),\n            (\"scan\", array(scan)),\n            (\"full\", array(full)),\n            (\"state\", array(state)),\n            (\"output\", array(outputs)),\n            (\"skip\", numeric(op.skip)),\n            (\"reset_every_turn\", numeric(op.reset_every_turn)),\n        ],\n    );\n    ast.fragments.insert(body.decl.id.clone(), body);\n    Ok(Some(invoke))\n}\n\nfn de_scan(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let fragment_name: String = invocation.named_arg_as(builder, \"body\")?;\n    let fragment = builder\n        .proto_model\n        .doc\n        .fragments\n        .iter()\n        .find(|n| n.decl.id.0 == fragment_name)\n        .ok_or_else(|| format_err!(\"Cound not find fragment `{}'\", fragment_name))?;\n    let template = TypedModel { symbols: builder.model.symbols.clone(), ..TypedModel::default() };\n    let mut body = ModelBuilder::new(builder.framework, builder.proto_model, template);\n    body.scopes.push(HashMap::new());\n    body.naming_scopes.clone_from(&builder.naming_scopes);\n    body.registries.clone_from(&builder.registries);\n    let mut outer_inputs: TVec<OutletId> = tvec!();\n    let mut input_mapping = vec![];\n    let scan: TVec<(String, OutletId, usize, isize)> = invocation.named_arg_as(builder, \"scan\")?;\n    let full: TVec<(String, OutletId)> = invocation.named_arg_as(builder, \"full\")?;\n    let state: TVec<(String, OutletId, String)> = invocation.named_arg_as(builder, \"state\")?;\n    for par in &fragment.decl.parameters {\n        let (outer_input_wire, inner_fact) = if let Some((_, wire, axis, chunk)) =\n            scan.iter().find(|s| s.0 == par.id.0 || escape(&s.0) == par.id.0)\n        {\n            input_mapping.push(InputMapping::Scan(ScanInfo { axis: *axis, chunk: *chunk }));\n            let mut fact = builder.model.outlet_fact(*wire)?.clone();\n            fact.shape.set(*axis, chunk.abs().to_dim());\n            (*wire, fact)\n        } else if let Some((_, wire)) =\n            full.iter().find(|s| s.0 == par.id.0 || escape(&s.0) == par.id.0)\n        {\n            input_mapping.push(InputMapping::Full);\n            let fact = builder.model.outlet_fact(*wire)?.clone();\n            (*wire, fact)\n        } else if let Some((_, wire, _out)) =\n            state.iter().find(|s| s.0 == par.id.0 || escape(&s.0) == par.id.0)\n        {\n            let fact = builder.model.outlet_fact(*wire)?.clone();\n            input_mapping.push(InputMapping::State);\n            (*wire, fact.datum_type.fact(fact.shape))\n        } else {\n            bail!(\"Unbound body input parameter {}\", par.id.0);\n        };\n        outer_inputs.push(outer_input_wire);\n        body.scopes.last_mut().unwrap().insert(\n            par.id.clone(),\n            Value::Wire(body.model.add_source(par.id.0.to_string(), inner_fact)?),\n        );\n    }\n    body.wire_body(fragment.body.as_deref().unwrap()).context(\"wiring scan body\")?;\n    let body_outputs = fragment\n        .decl\n        .results\n        .iter()\n        .map(|r| {\n            body.scopes.last().unwrap().get(&r.id).with_context(|| {\n                format!(\"Could not find variable for scan output named `{}'\", r.id.0)\n            })\n        })\n        .collect::<TractResult<Vec<&Value>>>()\n        .context(\"Finding output in body\")?;\n\n    let body_outputs: Vec<OutletId> = body_outputs\n        .iter()\n        .map(|v| v.to::<OutletId>(builder))\n        .collect::<TractResult<Vec<OutletId>>>()\n        .context(\"Coercing outputs to wires\")?;\n    body.model.select_output_outlets(&body_outputs)?;\n    // preferred form for output is 4 arguments, but early models had 3 arguments output,\n    // breaking support for bidirectional\n    // this awkward dance somewhat maintains compatibility\n    let outputs: TVec<(String, String, usize, isize)> = if let Ok(tuples_4) =\n        invocation.named_arg_as(builder, \"output\")\n    {\n        tuples_4\n    } else {\n        let outputs: TVec<(String, String, usize)> = invocation.named_arg_as(builder, \"output\")?;\n        outputs.into_iter().map(|(a, b, c)| (a, b, c, 1)).collect()\n    };\n    for output in &outputs {\n        if output.1 != \"full\" && output.1 != \"last\" {\n            bail!(\n                \"output named `{}' must specify type \\\"full\\\" or \\\"last\\\", found `{}'\",\n                output.0,\n                output.1\n            )\n        }\n    }\n    let mut output_mapping = vec![];\n    for output_name in fragment.decl.results.iter().map(|o| &*o.id.0) {\n        output_mapping.push(OutputMapping {\n            full_dim_hint: None,\n            scan: outputs\n                .iter()\n                .enumerate()\n                .find(|(_, om)| {\n                    (om.0 == output_name || escape(&om.0) == output_name) && om.1 == \"full\"\n                })\n                .map(|(ix, om)| (ix, ScanInfo { axis: om.2, chunk: om.3 })),\n            last_value_slot: outputs\n                .iter()\n                .enumerate()\n                .find(|(_, om)| {\n                    (om.0 == output_name || escape(&om.0) == output_name) && om.1 == \"last\"\n                })\n                .map(|(ix, _om)| ix),\n            state: state\n                .iter()\n                .any(|state| state.2 == output_name || escape(&state.2) == output_name),\n        });\n    }\n    let skip: usize = invocation.named_arg_as(builder, \"skip\")?;\n    let mut op = Scan::new(body.model, input_mapping, output_mapping, skip)?;\n    op.reset_every_turn = invocation.named_arg_as(builder, \"reset_every_turn\")?;\n    builder.wire(op, &outer_inputs)\n}\n\nfn escape(it: &str) -> String {\n    let mut escaped = String::new();\n    let first = it.chars().next().unwrap();\n    if !(first.is_alphabetic() || first == '_') {\n        escaped.push('_');\n    }\n    escaped.extend(it.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }));\n    escaped\n}\n"
  },
  {
    "path": "nnef/src/ops/core/scatter.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::array::ScatterElements;\nuse tract_core::ops::array::ScatterNd;\nuse tract_core::ops::array::ScatterReduction;\n\npub fn register(registry: &mut Registry) {\n    use crate::internal::*;\n\n    registry.register_dumper(ser_scatter_elements);\n    registry.register_primitive(\n        \"tract_core_scatter_elements\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Scalar.tensor().named(\"indices\"),\n            TypeName::Scalar.tensor().named(\"updates\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::String.named(\"reduction\").default(\"none\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_scatter_elements,\n    );\n\n    registry.register_dumper(ser_scatter_nd);\n    registry.register_primitive(\n        \"tract_core_scatter_nd\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Scalar.tensor().named(\"indices\"),\n            TypeName::Scalar.tensor().named(\"updates\"),\n            TypeName::String.named(\"reduction\").default(\"none\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_scatter_nd,\n    );\n}\n\nfn ser_scatter_nd(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ScatterNd,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let indices = ast.mapping[&node.inputs[1]].clone();\n    let updates = ast.mapping[&node.inputs[2]].clone();\n    Ok(Some(invocation(\n        \"tract_core_scatter_nd\",\n        &[wire, indices, updates],\n        &[(\"reduction\", string(op.reduction.as_str()))],\n    )))\n}\n\nfn de_scatter_nd(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let indices = invocation.named_arg_as(builder, \"indices\")?;\n    let updates = invocation.named_arg_as(builder, \"updates\")?;\n    let reduction: String = invocation.named_arg_as(builder, \"reduction\")?;\n    builder.wire(ScatterNd::new(ScatterReduction::parse(&reduction)?), &[wire, indices, updates])\n}\n\nfn ser_scatter_elements(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ScatterElements,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let indices = ast.mapping[&node.inputs[1]].clone();\n    let updates = ast.mapping[&node.inputs[2]].clone();\n    Ok(Some(invocation(\n        \"tract_core_scatter_elements\",\n        &[wire, indices, updates],\n        &[(\"axis\", numeric(op.axis)), (\"reduction\", string(op.reduction.as_str()))],\n    )))\n}\n\nfn de_scatter_elements(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let indices = invocation.named_arg_as(builder, \"indices\")?;\n    let updates = invocation.named_arg_as(builder, \"updates\")?;\n    let axis = invocation.named_arg_as(builder, \"axis\")?;\n    let reduction: String = invocation.named_arg_as(builder, \"reduction\")?;\n    builder.wire(\n        ScatterElements::new(axis, ScatterReduction::parse(&reduction)?),\n        &[wire, indices, updates],\n    )\n}\n"
  },
  {
    "path": "nnef/src/ops/core/shape_of.rs",
    "content": "use crate::internal::*;\n\npub fn register(registry: &mut Registry) {\n    // No serialization is done since: operation follow ONNX design:\n    // At deserialization we wire it to a constant used by tract.\n    // This make the operation serialization/deserialization non-symmetric\n    registry.register_primitive(\n        \"tract_core_shape_of\",\n        &[TypeName::Scalar.tensor().named(\"input\")],\n        &[(\"output\", TypeName::Integer.tensor())],\n        de_shape_of,\n    );\n}\n\nfn de_shape_of(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let shape = tensor1(&builder.model.outlet_fact(input)?.shape.to_tvec());\n    let wire = builder.add_const(shape)?;\n    Ok(Value::Wire(wire))\n}\n"
  },
  {
    "path": "nnef/src/ops/core/silu.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::nn::silu::Silu;\n\npub fn register(registry: &mut Registry) {\n    registry.register_unit_element_wise(\"tract_core_silu\", &Silu {});\n    // Backward compatibility alias\n    registry.register_unit_element_wise(\"tract_transformers_silu\", &Silu {});\n}\n"
  },
  {
    "path": "nnef/src/ops/core/softmax.rs",
    "content": "use tract_core::ops::nn::{Softmax, SoftmaxExp, SoftmaxKind};\n\nuse crate::{internal::*, ser::ints};\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_softmax);\n    registry.register_primitive(\n        \"tract_core_softmax\",\n        &[\n            TypeName::Scalar.tensor().named(\"x\"),\n            TypeName::Integer.tensor().named(\"axes\"),\n            TypeName::String.named(\"exp\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        deser_softmax,\n    );\n    registry.register_primitive(\n        \"tract_core_log_softmax\",\n        &[TypeName::Scalar.tensor().named(\"x\"), TypeName::Integer.tensor().named(\"axes\")],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        deser_log_softmax,\n    );\n}\n\npub fn deser_softmax(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let x = invocation.named_arg_as(builder, \"x\")?;\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n\n    let input_fact = builder.model.outlet_fact(x)?.clone();\n    let quant_output_dt = if input_fact.datum_type.is_float() {\n        None\n    } else {\n        invocation.dt_from_quant_file.first().cloned().flatten()\n    };\n\n    let exp: Option<String> = invocation.get_named_arg_as(builder, \"exp\")?;\n    let exp = match exp.as_deref() {\n        Some(\"fast_compact\") => SoftmaxExp::FastCompact,\n        _ => SoftmaxExp::Libc,\n    };\n\n    builder.wire(Softmax { axes, quant_output_dt, kind: SoftmaxKind::Softmax(exp) }, &[x])\n}\n\npub fn deser_log_softmax(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let x = invocation.named_arg_as(builder, \"x\")?;\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n\n    let input_fact = builder.model.outlet_fact(x)?.clone();\n    let quant_output_dt = if input_fact.datum_type.is_float() {\n        None\n    } else {\n        invocation.dt_from_quant_file.first().cloned().flatten()\n    };\n\n    builder.wire(Softmax { axes, quant_output_dt, kind: SoftmaxKind::LogSoftmax }, &[x])\n}\n\nfn ser_softmax(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &Softmax,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let mut args = vec![(\"axes\", ints(&op.axes))];\n    let op_name = match op.kind {\n        SoftmaxKind::Softmax(exp) => {\n            if exp == SoftmaxExp::FastCompact {\n                args.push((\"exp\", string(\"fast_compact\")))\n            } else {\n                return Ok(None);\n            };\n            \"tract_core_softmax\"\n        }\n        SoftmaxKind::LogSoftmax => \"tract_core_log_softmax\",\n    };\n    Ok(Some(invocation(op_name, &[wire], &args)))\n}\n"
  },
  {
    "path": "nnef/src/ops/core/source.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::source::TypedSource;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(external_dump);\n    registry.register_primitive(\n        \"tract_core_external\",\n        &external_parameters(),\n        &[(\"output\", TypeName::Any.tensor())],\n        external_load,\n    );\n}\n\nfn external_dump(\n    _ast: &mut IntoAst,\n    _node: &TypedNode,\n    op: &TypedSource,\n) -> TractResult<Option<Arc<RValue>>> {\n    let shape = tdims(&op.fact.shape);\n    Ok(Some(invocation(\n        \"tract_core_external\",\n        &[],\n        &[\n            (\"shape\", shape),\n            (\"datum_type\", string(format!(\"{:?}\", op.fact.datum_type.unquantized()))),\n        ],\n    )))\n}\n\nfn external_parameters() -> Vec<Parameter> {\n    vec![TypeName::String.named(\"datum_type\"), TypeName::Integer.array().named(\"shape\")]\n}\n\nfn external_load(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let shape: TVec<TDim> =\n        builder.allowing_new_symbols(|builder| invocation.named_arg_as(builder, \"shape\"))?;\n    let mut dt: DatumType = invocation.named_arg_as::<String>(builder, \"datum_type\")?.parse()?;\n    if let Some(Some(qdt)) = invocation.dt_from_quant_file.first() {\n        dt = *qdt;\n    }\n    Ok(Value::Wire(builder.model.add_source(\"\", dt.fact(&*shape))?))\n}\n"
  },
  {
    "path": "nnef/src/ops/core/submodel.rs",
    "content": "use tract_core::ops::submodel::SubmodelOp;\n\nuse crate::internal::*;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_submodel);\n    registry.register_primitive(\n        \"tract_core_submodel\",\n        &[TypeName::Scalar.tensor().array().named(\"input\"), TypeName::String.named(\"label\")],\n        &[(\"outputs\", TypeName::Any.tensor().array())],\n        de_submodel,\n    );\n}\n\nfn de_submodel(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wires: TVec<OutletId> = invocation.named_arg_as(builder, \"input\")?;\n    let label: String = invocation.named_arg_as(builder, \"label\")?;\n    let model: TypedModel = builder\n        .proto_model\n        .resources\n        .get(label.as_str())\n        .with_context(|| anyhow!(\"{} not found in model builder loaded resources\", label.as_str()))?\n        .clone()\n        .downcast_arc::<TypedModelResource>()\n        .map_err(|_| anyhow!(\"Error while downcasting typed model resource\"))\n        .map(|r| r.0.clone())\n        .with_context(|| anyhow!(\"Error while loading typed model resource\"))?;\n\n    let op: Box<dyn TypedOp> = Box::new(SubmodelOp::new(Box::new(model), &label)?);\n\n    builder.model.wire_node(label, op, &wires).map(Value::from)\n}\n\nfn ser_submodel(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &SubmodelOp,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = tvec![ast.mapping[&node.inputs[0]].clone()];\n    let invoke = invocation(\"tract_core_submodel\", &input, &[(\"label\", string(op.label()))]);\n    ast.resources.insert(op.label().to_string(), Arc::new(TypedModelResource(op.model().clone())));\n    Ok(Some(invoke))\n}\n"
  },
  {
    "path": "nnef/src/ops/core/topk.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::array::Topk;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_topk);\n    registry.register_primitive(\n        \"tract_core_topk\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.tensor().named(\"k\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Logical.named(\"largest\"),\n        ],\n        &[(\"values\", TypeName::Scalar.tensor()), (\"indices\", TypeName::Integer.tensor())],\n        de_topk,\n    );\n}\n\nfn ser_topk(ast: &mut IntoAst, node: &TypedNode, op: &Topk) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let k = ast.mapping[&node.inputs[1]].clone();\n    Ok(Some(invocation(\n        \"tract_core_topk\",\n        &[input, k],\n        &[(\"largest\", logical(op.largest)), (\"axis\", numeric(op.axis))],\n    )))\n}\n\nfn de_topk(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let k = invocation.named_arg_as(builder, \"k\")?;\n    let axis = invocation.named_arg_as(builder, \"axis\")?;\n    let largest = invocation.named_arg_as(builder, \"largest\")?;\n    let fallback_k = builder.model.symbols.new_with_prefix(\"k\").into();\n    builder.wire(Topk { largest, fallback_k, axis }, &[input, k])\n}\n"
  },
  {
    "path": "nnef/src/ops/core/trilu.rs",
    "content": "use crate::internal::*;\nuse crate::ser::*;\nuse tract_core::ops::array::Trilu;\nuse tract_core::ops::cast::cast;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_trilu);\n    registry.register_primitive(\n        \"tract_core_trilu\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.tensor().named(\"k\"),\n            TypeName::Logical.named(\"upper\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_trilu,\n    );\n}\n\nfn ser_trilu(ast: &mut IntoAst, node: &TypedNode, op: &Trilu) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let k = ast.mapping[&node.inputs[1]].clone();\n    Ok(Some(invocation(\"tract_core_trilu\", &[input, k], &[(\"upper\", logical(op.upper))])))\n}\n\nfn de_trilu(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let k = invocation.named_arg_as(builder, \"k\")?;\n    let upper = invocation.named_arg_as(builder, \"upper\")?;\n    let k_casted = builder.wire_as_outlets(cast(DatumType::I64), &[k])?[0];\n    builder.wire(Trilu { upper }, &[input, k_casted])\n}\n"
  },
  {
    "path": "nnef/src/ops/core.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops;\n\nmod broadcast;\nmod cast;\n#[cfg(feature = \"complex\")]\nmod complex;\nmod downsample;\nmod dyn_slice;\nmod einsum;\nmod fft;\nmod gather;\nmod gelu_approximate;\nmod is_inf;\nmod matmul;\nmod one_hot;\nmod qconv;\nmod qmatmul;\nmod range;\nmod reduce;\nmod rms_norm;\nmod scan;\nmod scatter;\nmod shape_of;\nmod silu;\nmod softmax;\nmod source;\nmod submodel;\nmod topk;\nmod trilu;\n\npub fn register(registry: &mut Registry) {\n    registry.register_unit_element_wise(\"tract_core_round_even\", &ops::math::RoundHalfToEven {});\n    registry.register_unit_element_wise(\"tract_core_erf\", &ops::math::Erf {});\n    registry.register_unit_element_wise(\"tract_core_hard_swish\", &ops::nn::HardSwish {});\n\n    registry.register_binary(\"tract_core_xor\", &ops::logic::Xor {});\n    registry.register_binary(\"tract_core_bitand\", &ops::logic::BitAnd {});\n    registry.register_binary(\"tract_core_bitor\", &ops::logic::BitOr {});\n    registry.register_binary(\"tract_core_bitxor\", &ops::logic::BitXor {});\n    registry.register_unit_element_wise(\"tract_core_bitnot\", &ops::logic::BitNot {});\n\n    registry.register_unit_element_wise(\"tract_core_is_nan\", &ops::math::IsNan {});\n\n    registry.register_binary(\"tract_shl\", &ops::math::ShiftLeft);\n    registry.register_binary(\"tract_shr\", &ops::math::ShiftRight);\n    broadcast::register(registry);\n    cast::register(registry);\n    #[cfg(feature = \"complex\")]\n    complex::register(registry);\n    downsample::register(registry);\n    dyn_slice::register(registry);\n    einsum::register(registry);\n    fft::register(registry);\n    gather::register(registry);\n    gelu_approximate::register(registry);\n    matmul::register(registry);\n    one_hot::register(registry);\n    qconv::register(registry);\n    qmatmul::register(registry);\n    reduce::register(registry);\n    rms_norm::register(registry);\n    scan::register(registry);\n    scatter::register(registry);\n    shape_of::register(registry);\n    silu::register(registry);\n    softmax::register(registry);\n    source::register(registry);\n    submodel::register(registry);\n    range::register(registry);\n    topk::register(registry);\n    trilu::register(registry);\n    is_inf::register(registry);\n}\n"
  },
  {
    "path": "nnef/src/ops/mod.rs",
    "content": "use crate::internal::*;\n\npub(super) mod core;\npub mod nnef;\npub(super) mod resource;\n\npub use nnef::tract_nnef;\n\npub fn tract_core() -> Registry {\n    let mut reg = Registry::new(\"tract_core\")\n        .with_doc(\"Extension `tract_core` exposes NNEF fragments for using\")\n        .with_doc(\"operator defined by tract-core crate.\")\n        .with_doc(\"\")\n        .with_doc(\"Add `extension tract_core` to `graph.nnef`\");\n    core::register(&mut reg);\n    reg\n}\n\npub fn tract_resource() -> Registry {\n    let mut reg = Registry::new(\"tract_resource\")\n        .with_doc(\"Extension `tract_resource` exposes NNEF fragments for accessing\")\n        .with_doc(\"resources files in NNEF folder or archive.\")\n        .with_doc(\"\")\n        .with_doc(\"Add `extension tract_resource` to `graph.nnef`\");\n    resource::register(&mut reg);\n    reg\n}\n"
  },
  {
    "path": "nnef/src/ops/nnef/deser.rs",
    "content": "use crate::ast::*;\nuse crate::deser::Value;\nuse crate::ops::tract_core;\nuse crate::tract_ndarray::Array;\n\nuse ops::cnn::deconv::Deconv;\nuse ops::cnn::{Conv, KernelFormat};\nuse tract_core::internal::*;\nuse tract_core::ops::array::{PadMode, TypedConcat};\nuse tract_core::ops::cast::cast;\nuse tract_core::ops::cnn::PaddingSpec;\nuse tract_core::ops::cnn::PoolSpec;\nuse tract_core::ops::cnn::deconv::adjustments;\nuse tract_core::ops::einsum::block_quant_aware_input_shape;\nuse tract_core::ops::konst::Const;\nuse tract_core::ops::math::min;\nuse tract_core::ops::nn::{DataFormat, Softmax, SoftmaxKind};\nuse tract_core::tract_linalg::block_quant::{BlockQuantFact, BlockQuantStorage};\nuse tract_itertools::Itertools;\n\nuse tract_core::ops;\n\nuse crate::deser::{ModelBuilder, ResolvedInvocation};\n\nfn convert_to_shape_input(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n    name: &str,\n) -> TractResult<Value> {\n    if let Ok(tensor) = invocation.named_arg_as::<Arc<Tensor>>(builder, name) {\n        return Ok(Value::Tensor(tensor.cast_to::<TDim>()?.into_owned().into_arc_tensor()));\n    }\n    if let Ok(bits) = invocation.named_arg_as::<TVec<OutletId>>(builder, name) {\n        let concat_input = bits\n            .into_iter()\n            .map(|mut bit| {\n                let fact = builder.model.outlet_fact(bit)?.to_owned();\n                if fact.rank() != 1 {\n                    bit = builder.wire_as_outlets(\n                        AxisOp::Reshape(0, fact.shape.to_tvec(), tvec![fact.shape.volume()]),\n                        &[bit],\n                    )?[0];\n                }\n                if !fact.datum_type.is_tdim() {\n                    bit = builder.wire_as_outlets(cast(TDim::datum_type()), &[bit])?[0];\n                }\n                Ok(bit)\n            })\n            .collect::<TractResult<TVec<OutletId>>>()?;\n        return builder.wire(TypedConcat::new(0), &concat_input);\n    }\n    bail!(\"Argument '{}' only support tensor or list of integers\", name);\n}\n\n// fragment external<? = scalar>( shape: integer[] ) -> ( output: tensor<?> );\npub fn external(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let type_name = invocation.invocation.generic_type_name.unwrap_or(TypeName::Scalar);\n    let dt = if let Some(Some(dt)) = invocation.dt_from_quant_file.first() {\n        *dt\n    } else if type_name == TypeName::Scalar {\n        f32::datum_type()\n    } else if type_name == TypeName::Logical {\n        bool::datum_type()\n    } else if type_name == TypeName::Integer {\n        i64::datum_type()\n    } else {\n        todo!()\n    };\n    let shape: TVec<TDim> =\n        builder.allowing_new_symbols(|builder| invocation.named_arg_as(builder, \"shape\"))?;\n    Ok(Value::Wire(builder.model.add_source(\"\", dt.fact(&shape))?))\n}\n\n// fragment variable<? = scalar>( shape: integer[], label: string ) -> ( output: tensor<?> );\npub fn variable(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let shape: TVec<usize> = invocation.named_arg_as(builder, \"shape\")?;\n    let label = Identifier(invocation.named_arg_as(builder, \"label\")?);\n    let tensors = &builder.proto_model.tensors;\n    let mut tensor = Arc::clone(\n        tensors\n            .get(&label)\n            .or_else(|| tensors.get(&Identifier(label.0.trim_start_matches('/').to_owned())))\n            .ok_or_else(|| format_err!(\"No data for tensor {:?}\", label))?,\n    );\n    if let Some(Some(dt)) = invocation.dt_from_quant_file.first() {\n        if dt.size_of() != tensor.datum_type().size_of() {\n            bail!(\n                \"Mismatched tensor type for tensor {}: expected {:?}, got {:?}\",\n                label.0,\n                *dt,\n                tensor.datum_type()\n            );\n        }\n        if *dt != tensor.datum_type() {\n            trace!(\n                \"Casting tensor {} from {:?} to {:?} when deserializing\",\n                label.0,\n                tensor.datum_type(),\n                *dt\n            );\n            //FIXME: avoid cast by late-loading tensors ?\n            tensor = tensor.cast_to_dt(*dt)?.into_owned().into_arc_tensor()\n        }\n    }\n    if let Some(bqs) = tensor.storage_as::<BlockQuantStorage>() {\n        // Use the NNEF variable shape directly — the graph's own unsqueeze\n        // ops will add any group dims as needed.\n        let tensor =\n            bqs.clone().into_tensor_with_shape(tensor.datum_type(), &shape).into_arc_tensor();\n        let fact: Box<dyn ExoticFact> = Box::new(BlockQuantFact::new(\n            tract_core::dyn_clone::clone_box(bqs.format()),\n            shape.clone(),\n        ));\n        builder.wire(Const::new_with_exotic_fact(tensor, fact)?, &[])\n    } else {\n        ensure!(\n            tensor.shape() == &*shape,\n            \"Wrong shape for tensor: {:?}, tensor file says {:?}, graph files says {:?}\",\n            label,\n            tensor.shape(),\n            shape\n        );\n        builder.wire(Const::new(tensor)?, &[])\n    }\n}\n\n// fragment reshape<?>( input: tensor<?>, shape: integer[], axis_start: integer = 0, axis_count: integer = -1 )\n//      -> ( output: tensor<?> );\npub fn reshape(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let input_shape = builder.model.outlet_fact(input)?.shape.to_tvec();\n    let start: usize = invocation.named_arg_as(builder, \"axis_start\")?;\n    let count: i64 = invocation.named_arg_as(builder, \"axis_count\")?;\n    let count = if count == -1 { input_shape.len() - start } else { count as usize };\n    let replacement =\n        convert_to_shape_input(builder, invocation, \"shape\")?.to::<Arc<Tensor>>(builder)?;\n    let mut replacement: TVec<TDim> = replacement.try_as_plain()?.as_slice::<TDim>()?.into();\n    for i in 0..replacement.len() {\n        if replacement[i] == 0.to_dim() {\n            replacement[i] = input_shape[i + start].clone();\n        }\n    }\n    if let Some(pos) = replacement.iter().position(|d| *d == (-1).to_dim()) {\n        let product: TDim = replacement.iter().filter(|d| **d != (-1).to_dim()).product();\n        let product_input: TDim = input_shape[start..][..count].iter().product();\n        replacement[pos] = product_input.maybe_div(&product)?.0;\n    }\n\n    let op = AxisOp::Reshape(start, input_shape[start..][..count].into(), replacement);\n    builder.wire(op, &[input])\n}\n\n// fragment transpose<?>( input: tensor<?>, axes: integer[] ) -> ( output: tensor<?> );\npub fn transpose(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n    let wire = tvec!(invocation.named_arg_as(builder, \"input\")?);\n    ops::change_axes::perm_to_ops(&axes)\n        .into_iter()\n        .try_fold(wire, |wire, mov| builder.wire_as_outlets(mov, &wire))\n        .map(Value::from)\n}\n\n// fragment concat<?>( values: tensor<?>[], axis: integer ) -> ( value: tensor<?> );\npub fn concat(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let axis: usize = invocation.named_arg_as(builder, \"axis\")?;\n    let mut values: TVec<OutletId> = invocation.named_arg_as(builder, \"values\")?;\n    let dt = if let Some(dt) = invocation.dt_from_quant_file.first().and_then(|it| *it) {\n        dt\n    } else {\n        builder.model.outlet_fact(values[0])?.datum_type\n    };\n    for value in &mut values {\n        if builder.model.outlet_fact(*value)?.datum_type != dt {\n            *value = builder.wire_as_outlets(ops::cast::cast(dt), &[*value])?[0];\n        }\n    }\n\n    builder.wire(ops::array::TypedConcat::new(axis), &values)\n}\n\n// fragment slice<?>( input: tensor<?>, axes: integer[], begin: integer[], end: integer[] ) -> ( output: tensor<?> );\npub fn slice(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let mut wire = tvec!(invocation.named_arg_as(builder, \"input\")?);\n    let input_fact = builder.model.outlet_fact(wire[0])?.clone();\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n    let (begins, ends): (OutletId, OutletId) =\n        builder.allowing_new_symbols(|builder| -> TractResult<_> {\n            Ok((\n                invocation.named_arg_as(builder, \"begin\")?,\n                invocation.named_arg_as(builder, \"end\")?,\n            ))\n        })?;\n    ensure!(builder.model.outlet_fact(begins)?.rank() == 1);\n    ensure!(builder.model.outlet_fact(ends)?.rank() == 1);\n    let strides: TVec<isize> =\n        invocation.named_arg_as(builder, \"stride\").unwrap_or_else(|_| tvec!(1; axes.len()));\n    for (ix, axis) in axes.into_iter().enumerate() {\n        let axis_len = builder\n            .wire_as_outlets(Const::new(rctensor0(input_fact.shape[axis].clone()))?, &[])?[0];\n        let b = builder.wire_as_outlets(\n            tract_core::ops::array::Slice { axis: 0, start: ix.into(), end: ix.to_dim() + 1 },\n            &[begins],\n        )?;\n        let mut b = builder.wire_as_outlets(tract_core::ops::change_axes::AxisOp::Rm(0), &b)?[0];\n        b = builder.wire_as_outlets(cast(TDim::datum_type()), &[b])?[0];\n        b = builder.wire_as_outlets(min(), &[b, axis_len])?[0];\n        if let Some(k) = &builder.model.outlet_fact(b)?.konst\n            && let Ok(i) = k.cast_to_scalar::<i64>()\n            && i < 0\n        {\n            b = builder\n                .wire_as_outlets(Const::new(rctensor0(input_fact.shape[axis].clone() + i))?, &[])?\n                [0];\n        }\n        let e = builder.wire_as_outlets(\n            tract_core::ops::array::Slice { axis: 0, start: ix.into(), end: ix.to_dim() + 1 },\n            &[ends],\n        )?;\n        let mut e = builder.wire_as_outlets(tract_core::ops::change_axes::AxisOp::Rm(0), &e)?[0];\n        e = builder.wire_as_outlets(cast(TDim::datum_type()), &[e])?[0];\n        e = builder.wire_as_outlets(min(), &[e, axis_len])?[0];\n        // use \"<=\", no \"<\" end[axis] = 0 means \"up to the end\"\n        // CAUTION: this notation is 1/ deprecated 2/ invalid with non trivial slicing\n        if let Some(k) = &builder.model.outlet_fact(e)?.konst\n            && let Ok(i) = k.cast_to_scalar::<i64>()\n            && i <= 0\n        {\n            e = builder\n                .wire_as_outlets(Const::new(rctensor0(input_fact.shape[axis].clone() + i))?, &[])?\n                [0];\n        }\n        let len = if let (Some(ev), Some(bv)) =\n            (&builder.model.outlet_fact(e)?.konst, &builder.model.outlet_fact(b)?.konst)\n        {\n            ev.cast_to::<TDim>()?.try_as_plain()?.to_scalar::<TDim>()?.clone()\n                - bv.cast_to::<TDim>()?.try_as_plain()?.to_scalar::<TDim>()?\n        } else {\n            let s = builder.model.symbols.new_with_prefix(\"slice\");\n            builder.model.symbols.add_assertion(format!(\"{s} >= 0\")).ok();\n            s.into()\n        };\n        wire = builder\n            .wire_as_outlets(tract_core::ops::array::DynSlice { axis, len }, &[wire[0], b, e])?;\n        if strides[ix] != 1 {\n            wire = builder.wire_as_outlets(\n                tract_core::ops::downsample::Downsample::new(axis, strides[ix], 0),\n                &wire,\n            )?;\n        }\n    }\n    Ok(wire.into())\n}\n\n// fragment squeeze<?>( input: tensor<?>, axes: integer[] ) -> ( output: tensor<?> );\npub fn squeeze(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n    let wire = tvec!(invocation.named_arg_as(builder, \"input\")?);\n    axes.iter()\n        .sorted()\n        .rev()\n        .try_fold(wire, |wire, &axis| {\n            builder.wire_as_outlets(ops::change_axes::AxisOp::Rm(axis), &wire)\n        })\n        .map(Value::from)\n}\n\n// fragment unsqueeze<?>( input: tensor<?>, axes: integer[] ) -> ( output: tensor<?> );\npub fn unsqueeze(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n    let wire = tvec!(invocation.named_arg_as(builder, \"input\")?);\n    axes.iter()\n        .sorted()\n        .try_fold(wire, |wire, &axis| {\n            builder.wire_as_outlets(ops::change_axes::AxisOp::Add(axis), &wire)\n        })\n        .map(Value::from)\n}\n\n// fragment tile<?>( input: tensor<?>, repeats: integer[] ) -> ( output: tensor<?> );\npub fn tile(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let multipliers =\n        convert_to_shape_input(builder, invocation, \"repeats\")?.to::<OutletId>(builder)?;\n    let rank = builder.model.outlet_fact(wire)?.rank();\n    ensure!(builder.model.outlet_fact(multipliers)?.rank() == 1);\n    ensure!(builder.model.outlet_fact(multipliers)?.shape[0] == rank.to_dim());\n    builder.wire(ops::array::DynTile::new(&builder.model.symbols, rank), &[wire, multipliers])\n}\n\npub fn pad_mode(border: &str, value: Tensor) -> TractResult<tract_core::ops::array::PadMode> {\n    Ok(match border {\n        \"constant\" => PadMode::Constant(value.into_arc_tensor()),\n        \"replicated\" => PadMode::Edge,\n        \"reflect\" => PadMode::Reflect,\n        _ => bail!(\"unsupported padding mode {}\", border),\n    })\n}\n\n// fragment pad( input: tensor<scalar>, padding: (integer, integer)[], border: string = 'constant', value: scalar = 0.0 ) -> ( output: tensor<scalar> );\npub fn pad(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    use tract_core::ops::array::Pad;\n    let wire = tvec!(invocation.named_arg_as(builder, \"input\")?);\n    let padding: TVec<TVec<usize>> = invocation.named_arg_as(builder, \"padding\")?;\n    let padding: Vec<(usize, usize)> = padding.iter().map(|a| (a[0], a[1])).collect();\n    let value: Tensor = tensor0(invocation.named_arg_as::<f32>(builder, \"value\")?);\n    let border: String = invocation.named_arg_as(builder, \"border\")?;\n    let mode = pad_mode(&border, value)?;\n    builder.wire(Pad { pads: padding, mode }, &wire)\n}\n\n/*\nfragment conv( input: tensor<scalar>, filter: tensor<scalar>,\nbias: tensor<scalar> = 0.0, border: string = 'constant',\npadding: (integer,integer)[] = [], stride: integer[] = [],\ndilation: integer[] = [], groups: integer = 1 )\n-> ( output: tensor<scalar> );\n*/\n\n/*  fragment deconv(\ninput: tensor<scalar>,\nfilter: tensor<scalar>,\nbias: tensor<scalar> = 0.0,\nborder: string = 'constant',\npadding: (integer,integer)[] = [],\nstride: integer[] = [],\ndilation: integer[] = [],\noutput_shape: integer[] = [],\ngroups: integer = 1 )\n-> ( output: tensor<scalar> );\n*/\n\npub fn conv(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    conv_like(builder, invocation, ConvLikeVariant::Conv)\n}\n\npub fn deconv(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    conv_like(builder, invocation, ConvLikeVariant::Deconv)\n}\n\n/// Debox equivalent to deconv with a fixed all-one kernel, no bias, and an extra two unsqueeze at input and\n/// output. This implementation is not optimal, since it add useless matmul and reshape memory.\npub fn debox(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    conv_like(builder, invocation, ConvLikeVariant::Debox)\n}\n\npub fn read_conv_parameters(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n    kernel_shape: &[usize],\n    input_fact: &TypedFact,\n) -> TractResult<(usize, PoolSpec)> {\n    let mut group = invocation.named_arg_as(builder, \"groups\").unwrap_or(0);\n    if group == 0 {\n        group = kernel_shape[0]\n    }\n    if input_fact.shape[1] != kernel_shape[1].to_dim() * group {\n        bail!(\n            \"Convolution input and kernel channels (second axis in both) must match. Got {:?} and {:?}.\",\n            input_fact,\n            kernel_shape\n        );\n    }\n    let dilation: TVec<usize> = invocation.named_arg_as(builder, \"dilation\")?;\n    if dilation.len() != 0 && dilation.len() != input_fact.rank() - 2 {\n        bail!(\n            \"Convolution dilation only apply to spatial dimensions, so it should be of rank {}. Got {:?}\",\n            input_fact.rank() - 2,\n            dilation\n        )\n    }\n    let stride: TVec<usize> = invocation.named_arg_as(builder, \"stride\")?;\n    if stride.len() != 0 && stride.len() != input_fact.rank() - 2 {\n        bail!(\n            \"Convolution stride only apply to spatial dimensions, so it should be of rank {}. Got {:?}\",\n            input_fact.rank() - 2,\n            stride\n        )\n    }\n    let padding: TVec<TVec<usize>> = invocation.named_arg_as(builder, \"padding\")?;\n    let padding = if padding.len() == 0 {\n        PaddingSpec::SameUpper\n    } else {\n        let mut before = tvec!();\n        let mut after = tvec!();\n        for p in padding {\n            before.push(p[0]);\n            after.push(p[1]);\n        }\n        PaddingSpec::Explicit(before, after)\n    };\n    let pool_spec = PoolSpec::new(\n        DataFormat::NCHW,\n        kernel_shape[2..].into(),\n        padding,\n        if dilation.len() > 0 { Some(dilation) } else { None },\n        if stride.len() > 0 { Some(stride) } else { None },\n        kernel_shape[1] * group,\n        kernel_shape[0],\n    );\n\n    let border: String = invocation.named_arg_as(builder, \"border\")?;\n    assert_eq!(border, \"constant\");\n\n    Ok((group, pool_spec))\n}\n\n#[derive(PartialEq)]\npub enum ConvLikeVariant {\n    Conv,\n    Deconv,\n    Debox,\n}\n\npub fn conv_like(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n    variant: ConvLikeVariant,\n) -> TractResult<Value> {\n    let mut input: OutletId = invocation.named_arg_as(builder, \"input\")?;\n    if variant == ConvLikeVariant::Debox {\n        let i0 = builder.wire_as_outlets(ops::change_axes::AxisOp::Add(0), &[input])?[0];\n        let i1 = builder.wire_as_outlets(ops::change_axes::AxisOp::Add(0), &[i0])?[0];\n        input = i1;\n    }\n\n    let kernel: OutletId = if variant == ConvLikeVariant::Debox {\n        let mut size: TVec<usize> = invocation.named_arg_as(builder, \"size\")?;\n        size.insert(0, 1);\n        size.insert(0, 1);\n        let filter_ndarray = Array::<f32, _>::ones(size.to_vec());\n        let input_dt = builder.model.outlet_fact(input)?.datum_type;\n        let filter = filter_ndarray.into_arc_tensor().cast_to_dt(input_dt)?.into_owned();\n        builder.model.add_const(format!(\"{}.filter\", invocation.invocation.id.0), filter)?\n    } else {\n        invocation.named_arg_as(builder, \"filter\")?\n    };\n    let mut bias: OutletId = if variant == ConvLikeVariant::Debox {\n        builder.model.add_const(\n            format!(\"{}.bias\", invocation.invocation.id.0),\n            tensor0(0.0f32).into_arc_tensor(),\n        )?\n    } else {\n        invocation.named_arg_as(builder, \"bias\")?\n    };\n    let input_fact = builder.model.outlet_fact(input)?.clone();\n    let kernel_fact = builder.model.outlet_fact(kernel)?.clone();\n\n    let name = builder.generate_node_name();\n    while let Some((axis, _)) = builder\n        .model\n        .outlet_fact(bias)?\n        .shape\n        .to_tvec()\n        .iter()\n        .enumerate()\n        .rev()\n        .find(|(_, dim)| dim.is_one())\n    {\n        bias =\n            builder.model.wire_node(format!(\"{name}.bias_rm_{axis}\"), AxisOp::Rm(axis), &[bias])?\n                [0];\n    }\n\n    let bias_dt =\n        if input_fact.datum_type.is_float() { input_fact.datum_type } else { i32::datum_type() };\n    bias = builder.model.wire_node(format!(\"{name}.cast_bias\"), cast(bias_dt), &[bias])?[0];\n\n    let mut inputs = tvec!(input, kernel, bias);\n    let kernel_shape: ShapeFact = block_quant_aware_input_shape(&kernel_fact)?.iter().collect();\n    let (group, pool_spec) = read_conv_parameters(\n        builder,\n        invocation,\n        kernel_shape.as_concrete().context(\"Except fixed kernel shape\")?,\n        &input_fact,\n    )?;\n\n    let output_dt: Option<DatumType> = if input_fact.datum_type.is_float() {\n        None\n    } else if let Some(dt) = invocation.dt_from_quant_file.first().cloned().flatten() {\n        Some(dt)\n    } else {\n        Some(DatumType::I32)\n    };\n\n    let op: Box<dyn TypedOp> =\n        if ConvLikeVariant::Deconv == variant || ConvLikeVariant::Debox == variant {\n            let output_shape = invocation.named_arg_as::<TVec<usize>>(builder, \"output_shape\")?;\n            let output_shape = Some(output_shape).filter(|os| os.len() == pool_spec.rank());\n            let adjustments = if let Some(output_shape) = output_shape {\n                let input_shape = &input_fact\n                    .shape\n                    .as_concrete()\n                    .context(\"symbolic dimension not supported in deconv\")?[2..];\n                adjustments(&pool_spec, input_shape, &output_shape)?\n            } else {\n                tvec!(0; pool_spec.rank())\n            };\n            Box::new(Deconv::new(pool_spec, KernelFormat::OIHW, adjustments, group))\n        } else {\n            if let Some(odt) = &output_dt {\n                for dt in &[&input_fact.datum_type, &kernel_fact.datum_type, odt] {\n                    let qp = dt.qparams().unwrap_or_default();\n                    inputs.push(builder.add_const(tensor0(qp.zp_scale().0))?);\n                    inputs.push(builder.add_const(tensor0(qp.zp_scale().1))?);\n                }\n            }\n            Box::new(Conv::new(pool_spec, KernelFormat::OIHW, group, output_dt))\n        };\n    if variant == ConvLikeVariant::Debox {\n        let outlets = builder.wire_as_outlets(op, &inputs)?;\n        let outlets_unsqueeze =\n            builder.wire_as_outlets(ops::change_axes::AxisOp::Rm(0), outlets.as_slice())?;\n        return builder.wire(ops::change_axes::AxisOp::Rm(0), outlets_unsqueeze.as_slice());\n    }\n    builder.wire(op, &inputs)\n}\n\nfn pool_spec_for_pools(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n    kernel_shape: &[usize],\n    channels: usize,\n) -> TractResult<ops::cnn::PoolSpec> {\n    let kernel_shape = DataFormat::NCHW.shape(kernel_shape)?;\n    let spatial_shape = kernel_shape.hw_dims();\n    let dilation: TVec<usize> = invocation.named_arg_as(builder, \"dilation\")?;\n    if dilation.len() > 0\n        && (dilation.len() != kernel_shape.rank() || dilation[0] != 1 || dilation[1] != 1)\n    {\n        bail!(\n            \"dilation should be like [1, 1, ... ] because first two first dimensions are N and C, so they should be of dilation 1. Got dilation {dilation:?}.\"\n        );\n    }\n    let spatial_dilation = if dilation.iter().all(|it| *it == 1) || dilation.len() == 0 {\n        None\n    } else {\n        Some(DataFormat::NCHW.shape(&dilation)?.hw_dims().into())\n    };\n    let stride: TVec<usize> = invocation.named_arg_as(builder, \"stride\")?;\n    if stride.len() > 0 && (stride.len() != kernel_shape.rank() || stride[0] != 1 || stride[1] != 1)\n    {\n        bail!(\n            \"stride should be like [1, 1, ... ] because first two first dimensions are N and C, so they should be of stride 1. Got stride {stride:?}.\"\n        );\n    }\n    let spatial_stride = if stride.len() == 0 || stride.iter().all(|it| *it == 1) {\n        None\n    } else {\n        Some(DataFormat::NCHW.shape(&stride)?.hw_dims().into())\n    };\n    let padding: TVec<TVec<usize>> = invocation.named_arg_as(builder, \"padding\")?;\n    if padding.len() > 0 && (padding.len() != padding.len()) {\n        bail!(\n            \"padding should have the same rank as the input. Got padding {padding:?} but kernel_shape is {kernel_shape:?}.\"\n        );\n    }\n    let padding = if padding.len() == 0 {\n        PaddingSpec::SameUpper\n    } else {\n        let mut before = tvec!();\n        let mut after = tvec!();\n        for p in padding {\n            before.push(p[0]);\n            after.push(p[1]);\n        }\n        let spatial_pool_bef = DataFormat::NCHW.shape(&before)?.hw_dims().into();\n        let spatial_pool_aft = DataFormat::NCHW.shape(&after)?.hw_dims().into();\n        PaddingSpec::ExplicitOnnxPool(spatial_pool_bef, spatial_pool_aft, false)\n    };\n    Ok(PoolSpec::new(\n        DataFormat::NCHW,\n        spatial_shape.into(),\n        padding,\n        spatial_dilation,\n        spatial_stride,\n        channels,\n        channels,\n    ))\n}\n\n/*\n * fragment max_pool_with_index( input: tensor<scalar>, size: integer[], border: string = 'constant',\n *  padding: (integer,integer)[] = [], stride: integer[] = [], dilation: integer[] = [] )\n *   -> ( output: tensor<scalar>, index: tensor<integer> )\n */\n\npub fn max_pool_with_index(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let size: TVec<usize> = invocation.named_arg_as(builder, \"size\")?;\n    let input_fact = builder.model.outlet_fact(input)?;\n    if input_fact.rank() != size.len() {\n        bail!(\n            \"Max pool input expected as NCHW, and \\\"size\\\" paramater must be [ 1, 1, x, y ]. Got {:?}, and {:?}\",\n            input_fact,\n            size\n        );\n    }\n    let channels = DataFormat::NCHW\n        .shape(&input_fact.shape)?\n        .c()\n        .to_usize()\n        .context(\"Expect constant channel depth\")?;\n    let border: String = invocation.named_arg_as(builder, \"border\")?;\n    assert!(&*border == \"ignore\" || &*border == \"constant\");\n    //FIXME : constant is not actually supported, but it should be the same in most cases\n    let pool_spec = pool_spec_for_pools(builder, invocation, &size, channels)?;\n    let op = ops::cnn::MaxPool { pool_spec, with_index_outputs: Some(i64::datum_type()) };\n    builder.wire(op, &[input])\n}\n\n/*\n * fragment box( input: tensor<scalar>, size: integer[], border: string = 'constant', padding: (integer,integer)[] = [],\n *   stride: integer[] = [], dilation: integer[] = [], normalize: logical = false )\n * -> ( output: tensor<scalar> );\n */\n\npub fn sum_pool(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let size: TVec<usize> = invocation.named_arg_as(builder, \"size\")?;\n    let input_fact = builder.model.outlet_fact(input)?;\n    if input_fact.rank() != size.len() {\n        bail!(\n            \"Sum pool input expected as NCHW, and \\\"size\\\" paramater must be [ 1, 1, x, y ]. Got {:?}, and {:?}\",\n            input_fact,\n            size\n        );\n    }\n    let channels = DataFormat::NCHW\n        .shape(&input_fact.shape)?\n        .c()\n        .to_usize()\n        .context(\"Expect constant channel depth\")?;\n    let border: String = invocation.named_arg_as(builder, \"border\")?;\n    assert!(&*border == \"ignore\" || &*border == \"constant\");\n    let pool_spec = pool_spec_for_pools(builder, invocation, &size, channels)?;\n    let op = ops::cnn::SumPool {\n        pool_spec,\n        count_include_pad: false,\n        normalize: invocation.named_arg_as(builder, \"normalize\")?,\n    };\n    builder.wire(op, &[input])\n}\n\n/*\n *   fragment sum_reduce( input: tensor<scalar>, axes: integer[], normalize: logical = false ) -> ( output: tensor<scalar> );\n *   fragment max_reduce( input: tensor<scalar>, axes: integer[] ) -> ( output: tensor<scalar> );\n *   and also min, argmax, armmin, any, all\n */\npub fn reduce(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n    let reducer_name = invocation.invocation.id.0.split('_').next().unwrap();\n    let reducer = match reducer_name {\n        \"all\" => ops::nn::Reducer::All,\n        \"any\" => ops::nn::Reducer::Any,\n        \"sum\" => ops::nn::Reducer::Sum,\n        \"min\" => ops::nn::Reducer::Min,\n        \"max\" => ops::nn::Reducer::Max,\n        \"argmin\" => ops::nn::Reducer::ArgMin(false),\n        \"argmax\" => ops::nn::Reducer::ArgMax(false),\n        _ => bail!(\"unsupported reducer: {}\", invocation.invocation.id.0),\n    };\n    let wire = builder.wire_as_outlets(ops::nn::Reduce::new(axes.clone(), reducer), &[input])?;\n    if reducer_name != \"sum\" || !invocation.named_arg_as(builder, \"normalize\")? {\n        return Ok(wire.into());\n    }\n\n    let fact = builder.model.outlet_fact(wire[0])?.clone();\n    let input_shape = &builder.model.outlet_fact(input)?.shape;\n    let cardinality: TDim = axes.iter().map(|ax| &input_shape[*ax]).product();\n    let cardinality = builder.wire_as_outlets(\n        ops::konst::Const::new(\n            tensor0(cardinality).broadcast_into_rank(fact.rank())?.into_arc_tensor(),\n        )?,\n        &[],\n    )?;\n    let cardinality =\n        builder.wire_as_outlets(ops::cast::Cast::new(fact.datum_type), &cardinality)?;\n    builder.wire(ops::math::div(), &[wire[0], cardinality[0]])\n}\n\n/*\n * fragment matmul( A: tensor<scalar>, B: tensor<scalar>, transposeA: logical = false, transposeB: logical = false ) -> ( C: tensor<scalar> );\n */\npub fn matmul(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let a: OutletId = invocation.named_arg_as(builder, \"A\")?;\n    let b: OutletId = invocation.named_arg_as(builder, \"B\")?;\n    let a_trans: bool = invocation.named_arg_as(builder, \"transposeA\")?;\n    let b_trans: bool = invocation.named_arg_as(builder, \"transposeB\")?;\n    let a_dt = builder.model.outlet_fact(a)?.datum_type;\n    let b_dt = builder.model.outlet_fact(b)?.datum_type;\n    let name = &*invocation.invocation.id.0;\n    let a_rank = builder.model.outlet_fact(a)?.rank();\n    let b_rank = builder.model.outlet_fact(b)?.rank();\n    if builder.model.outlet_fact(a)?.is_exotic() {\n        // Block-quant tensor may have a leading group dim ([1, M, K]) while\n        // the other operand is 2D. Unsqueeze the non-exotic operand to match.\n        let mut b = b;\n        if a_rank > b_rank {\n            for _ in 0..(a_rank - b_rank) {\n                b = builder.wire_as_outlets(ops::change_axes::AxisOp::Add(0), &[b])?[0];\n            }\n        }\n        let axes = AxesMapping::for_numpy_matmul(a_rank, false, b_trans, false)?;\n        return builder\n            .wire(ops::einsum::EinSum { axes, operating_dt: b_dt, q_params: None }, &[a, b]);\n    }\n    if builder.model.outlet_fact(b)?.is_exotic() {\n        let mut a = a;\n        if b_rank > a_rank {\n            for _ in 0..(b_rank - a_rank) {\n                a = builder.wire_as_outlets(ops::change_axes::AxisOp::Add(0), &[a])?[0];\n            }\n        }\n        let axes = AxesMapping::for_numpy_matmul(b_rank, false, !a_trans, true)?;\n        return builder\n            .wire(ops::einsum::EinSum { axes, operating_dt: a_dt, q_params: None }, &[b, a]);\n    }\n    ensure!(a_rank == b_rank);\n    let c_rank = a_rank.max(b_rank);\n    let mut axes = AxesMapping::for_numpy_matmul(c_rank, a_trans, b_trans, false)?;\n    if a_dt.is_quantized() || b_dt.is_quantized() {\n        for input in 0..7 {\n            axes = axes.with_extra_input(2 + input)?;\n        }\n        let accum_dt = DatumType::QI32(QParams::ZpScale {\n            scale: a_dt.zp_scale().1 * b_dt.zp_scale().1,\n            zero_point: 0,\n        });\n        let c_dt = invocation.dt_from_quant_file.first().cloned().flatten().unwrap_or(accum_dt);\n\n        let a_qp = a_dt.qparams().unwrap_or_default().zp_scale();\n        let b_qp = b_dt.qparams().unwrap_or_default().zp_scale();\n        let c_qp = c_dt.qparams().unwrap_or_default().zp_scale();\n        let bias =\n            builder.model.add_const(format!(\"{name}.bias\"), Tensor::zero_scalar_dt(accum_dt)?)?;\n        let a0 = builder.model.add_const(format!(\"{name}.a0\"), rctensor0(a_qp.0))?;\n        let a_scale = builder.model.add_const(format!(\"{name}.a_scale\"), rctensor0(a_qp.1))?;\n        let b0 = builder.model.add_const(format!(\"{name}.b0\"), rctensor0(b_qp.0))?;\n        let b_scale = builder.model.add_const(format!(\"{name}.b_scale\"), rctensor0(b_qp.1))?;\n        let c0 = builder.model.add_const(format!(\"{name}.c0\"), rctensor0(c_qp.0))?;\n        let c_scale = builder.model.add_const(format!(\"{name}.c_scale\"), rctensor0(c_qp.1))?;\n\n        builder.wire(\n            ops::einsum::EinSum { axes, operating_dt: i32::datum_type(), q_params: Some(c_dt) },\n            &[a, b, bias, a0, a_scale, b0, b_scale, c0, c_scale],\n        )\n    } else {\n        builder.wire(ops::einsum::EinSum { axes, operating_dt: a_dt, q_params: None }, &[a, b])\n    }\n}\n\n/*\nfragment lt( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> )\nfragment gt( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> )\nfragment le( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> )\nfragment ge( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> )\nfragment eq( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> )\nfragment ne( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> )\n*/\n// comp() removed — comparison ops now handled by register_binary\n\n/*\n* fragment select<?>(\ncondition: tensor<logical>,     # the condition for selecting the result\ntrue_value: tensor<?>,          # the result when the condition is true\nfalse_value: tensor<?> )        # the result when the condition is false\n-> ( output: tensor<?> )\n*/\n\npub fn select(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let cond = invocation.named_arg_as(builder, \"condition\")?;\n    let true_value = invocation.named_arg_as(builder, \"true_value\")?;\n    let false_value = invocation.named_arg_as(builder, \"false_value\")?;\n    let inputs = crate::registry::multi_rank_broadcast(builder, &[cond, true_value, false_value])?;\n\n    builder.wire(ops::logic::Iff {}, &inputs)\n}\n\n/*\n * fragment leaky_relu( x: tensor<scalar>, alpha: scalar )-> ( y: tensor<scalar> )\n */\n\npub fn leaky_relu(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let x = invocation.named_arg_as(builder, \"x\")?;\n    let alpha = invocation.named_arg_as(builder, \"alpha\")?;\n    builder.wire(ops::nn::leaky_relu(alpha), &[x])\n}\n\n/*\n * fragment stack<?>( values: tensor<?>[], axis: integer ) -> ( value: tensor<?> )\n *\n * Same as concat but on dedicated axis\n */\n\npub fn stack(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let axis: usize = invocation.named_arg_as(builder, \"axis\")?;\n    let mut values: TVec<OutletId> = invocation.named_arg_as(builder, \"values\")?;\n    if let Some(Some(dt)) = invocation.dt_from_quant_file.first() {\n        for value in &mut values {\n            if builder.model.node(value.node).outputs[value.slot].fact.datum_type != *dt {\n                *value = builder.wire_as_outlets(ops::cast::cast(*dt), &[*value])?[0];\n            }\n        }\n    }\n\n    for value in &mut values {\n        // add unsqueeze\n        *value = builder.wire_as_outlets(ops::change_axes::AxisOp::Add(axis), &[*value])?[0];\n    }\n\n    builder.wire(ops::array::TypedConcat::new(axis), &values)\n}\n\n/*\n * fragment unstack<?>( value: tensor<?>, axis: integer ) -> ( values: tensor<?>[] )\n *\n * Inverse of stack operator\n */\npub fn unstack(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = tvec!(invocation.named_arg_as(builder, \"value\")?);\n    let axis: usize = invocation.named_arg_as(builder, \"axis\")?;\n\n    let input_fact = builder.model.outlet_fact(wire[0])?.clone();\n\n    (0..input_fact.shape[axis].clone().to_i32()?)\n        .map(|start_int| {\n            let start = start_int.to_dim();\n            let end = (start_int + 1).to_dim();\n            let sliced_wire = builder\n                .wire_as_outlets(tract_core::ops::array::Slice { axis, start, end }, &wire)?;\n            let squeezed_wire =\n                builder.wire_as_outlets(ops::change_axes::AxisOp::Rm(axis), &sliced_wire)?;\n            Ok(squeezed_wire[0])\n        })\n        .collect::<TractResult<TVec<_>>>()\n        .map(Value::from)\n}\n\n/*\n * fragment softmax( x: tensor<scalar>, axes: integer[] = [1] ) -> ( y: tensor<scalar> )\n * {\n *    m = max_reduce(x, axes = axes);\n *    e = exp(x - m);\n *    y = e / sum_reduce(e, axes = axes);\n * }\n */\n\npub fn softmax(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let x = invocation.named_arg_as(builder, \"x\")?;\n    let axes: TVec<usize> = invocation.named_arg_as(builder, \"axes\")?;\n\n    let input_fact = builder.model.outlet_fact(x)?.clone();\n    let quant_output_dt = if input_fact.datum_type.is_float() {\n        None\n    } else {\n        invocation.dt_from_quant_file.first().cloned().flatten()\n    };\n\n    builder.wire(Softmax { axes, quant_output_dt, kind: SoftmaxKind::default() }, &[x])\n}\n"
  },
  {
    "path": "nnef/src/ops/nnef/mod.rs",
    "content": "use tract_core::ops;\n\nuse crate::internal::*;\nuse crate::ops::tract_core;\n\npub mod deser;\npub mod ser;\n\npub fn tract_nnef() -> Registry {\n    let mut registry = Registry::new(\"tract_nnef\");\n    let mut stdlib = crate::framework::stdlib();\n\n    let mut primitive = |registry: &mut Registry, id: &str, func: ToTract| {\n        let pos = stdlib.iter().position(|f| f.decl.id.0 == id).unwrap();\n        let decl = stdlib.remove(pos).decl;\n        registry.register_primitive(id, &decl.parameters, &decl.results, func);\n    };\n\n    registry.register_dumper(pin_const);\n\n    primitive(&mut registry, \"external\", deser::external);\n    registry.register_dumper(ser::source);\n    primitive(&mut registry, \"variable\", deser::variable);\n    registry.register_dumper(ser::konst);\n\n    primitive(&mut registry, \"reshape\", deser::reshape);\n    primitive(&mut registry, \"transpose\", deser::transpose);\n\n    primitive(&mut registry, \"concat\", deser::concat);\n    registry.register_dumper(ser::concat);\n    primitive(&mut registry, \"slice\", deser::slice);\n    registry.register_dumper(ser::slice);\n\n    primitive(&mut registry, \"squeeze\", deser::squeeze);\n    primitive(&mut registry, \"unsqueeze\", deser::unsqueeze);\n    registry.register_dumper(ser::axis_op);\n\n    primitive(&mut registry, \"tile\", deser::tile);\n    registry.register_dumper(ser::tile);\n    registry.register_dumper(ser::dyn_tile);\n\n    primitive(&mut registry, \"pad\", deser::pad);\n    registry.register_dumper(ser::pad);\n\n    primitive(&mut registry, \"stack\", deser::stack);\n    primitive(&mut registry, \"unstack\", deser::unstack);\n\n    registry.register_binary(\"add\", &ops::math::Add {});\n    registry.register_binary(\"sub\", &ops::math::Sub {});\n    registry.register_binary(\"mul\", &ops::math::Mul {});\n    registry.register_binary(\"div\", &ops::math::Div {});\n    registry.register_binary(\"pow\", &ops::math::Pow {});\n\n    registry.register_unit_element_wise(\"exp\", &ops::math::Exp {});\n    registry.register_unit_element_wise(\"log\", &ops::math::Ln {});\n    registry.register_unit_element_wise(\"sin\", &ops::math::Sin {});\n    registry.register_unit_element_wise(\"cos\", &ops::math::Cos {});\n    registry.register_unit_element_wise(\"abs\", &ops::math::Abs {});\n    registry.register_unit_element_wise(\"neg\", &ops::math::Neg {});\n    registry.register_unit_element_wise(\"sign\", &ops::math::Sign {});\n    registry.register_unit_element_wise(\"recip\", &ops::math::Recip {});\n\n    registry.register_unit_element_wise(\"tan\", &ops::math::Tan {});\n    registry.register_unit_element_wise(\"acos\", &ops::math::Acos {});\n    registry.register_unit_element_wise(\"asin\", &ops::math::Asin {});\n    registry.register_unit_element_wise(\"atan\", &ops::math::Atan {});\n    registry.register_unit_element_wise(\"cosh\", &ops::math::Cosh {});\n    registry.register_unit_element_wise(\"sinh\", &ops::math::Sinh {});\n    registry.register_unit_element_wise(\"acosh\", &ops::math::Acosh {});\n    registry.register_unit_element_wise(\"asinh\", &ops::math::Asinh {});\n    registry.register_unit_element_wise(\"atanh\", &ops::math::Atanh {});\n\n    registry.register_unit_element_wise(\"floor\", &ops::math::Floor {});\n    registry.register_unit_element_wise(\"ceil\", &ops::math::Ceil {});\n    registry.register_unit_element_wise(\"round\", &ops::math::Round {});\n\n    registry.register_unit_element_wise(\"square\", &ops::math::Square {});\n    registry.register_unit_element_wise(\"sqrt\", &ops::math::Sqrt {});\n    registry.register_unit_element_wise(\"rsqrt\", &ops::math::Rsqrt {});\n\n    registry.register_unit_element_wise(\"tanh\", &ops::math::Tanh {});\n    registry.register_unit_element_wise(\"sigmoid\", &ops::nn::Sigmoid {});\n\n    registry.register_unit_element_wise(\"not\", &ops::logic::Not {});\n\n    registry.register_unit_element_wise(\"neg\", &ops::math::Neg {});\n\n    registry.register_element_wise(\n        \"leaky_relu\",\n        TypeId::of::<ops::nn::LeakyRelu>(),\n        Box::new(ser::leaky_relu),\n        vec![TypeName::Scalar.tensor().named(\"x\"), TypeName::Scalar.named(\"alpha\")],\n        deser::leaky_relu,\n    );\n\n    registry.register_binary(\"eq\", &ops::logic::CompEq);\n    registry.register_binary(\"ne\", &ops::logic::CompNE);\n    registry.register_binary(\"lt\", &ops::logic::CompLT);\n    registry.register_binary(\"gt\", &ops::logic::CompGT);\n    registry.register_binary(\"le\", &ops::logic::CompLTE);\n    registry.register_binary(\"ge\", &ops::logic::CompGTE);\n\n    registry.register_binary(\"and\", &ops::logic::And {});\n    registry.register_binary(\"or\", &ops::logic::Or {});\n\n    registry.register_binary(\"select\", &ops::logic::Or {});\n    registry.register_dumper(ser::select);\n    primitive(&mut registry, \"select\", deser::select);\n\n    registry.register_binary(\"min\", &ops::math::Min {});\n    registry.register_binary(\"max\", &ops::math::Max {});\n\n    primitive(&mut registry, \"matmul\", deser::matmul);\n    //    registry.register_dumper(ser::matmul);\n\n    primitive(&mut registry, \"conv\", deser::conv);\n    registry.register_dumper(ser::conv);\n    primitive(&mut registry, \"deconv\", deser::deconv);\n    registry.register_dumper(ser::deconv);\n\n    primitive(&mut registry, \"sum_reduce\", deser::reduce);\n    primitive(&mut registry, \"max_reduce\", deser::reduce);\n    primitive(&mut registry, \"min_reduce\", deser::reduce);\n    primitive(&mut registry, \"all_reduce\", deser::reduce);\n    primitive(&mut registry, \"any_reduce\", deser::reduce);\n    primitive(&mut registry, \"argmax_reduce\", deser::reduce);\n    primitive(&mut registry, \"argmin_reduce\", deser::reduce);\n    registry.register_dumper(ser::reduce);\n\n    primitive(&mut registry, \"softmax\", deser::softmax);\n    registry.register_dumper(ser::softmax);\n\n    primitive(&mut registry, \"max_pool_with_index\", deser::max_pool_with_index);\n    registry.register_dumper(ser::max_pool);\n    primitive(&mut registry, \"box\", deser::sum_pool);\n    primitive(&mut registry, \"debox\", deser::debox);\n    registry.register_dumper(ser::sum_pool);\n\n    registry.register_dumper(ser::basic_matmul);\n\n    for frag in stdlib {\n        if frag.body.is_some() {\n            registry.register_fragment(frag);\n        }\n    }\n    registry\n}\n\npub fn pin_const(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    _op: &ops::identity::PinConst,\n) -> TractResult<Option<Arc<RValue>>> {\n    Ok(Some(ast.mapping[&node.inputs[0]].clone()))\n}\n"
  },
  {
    "path": "nnef/src/ops/nnef/ser.rs",
    "content": "use crate::ast::Identifier;\nuse crate::ast::QuantFormat;\nuse crate::internal::*;\nuse crate::ser::*;\nuse tract_core::num_traits::Zero;\nuse tract_core::ops;\nuse tract_core::ops::cast::cast;\nuse tract_core::ops::cnn::Conv;\nuse tract_core::ops::cnn::PoolSpec;\nuse tract_core::ops::einsum::block_quant_aware_input_shape;\nuse tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\nuse tract_core::ops::konst::Const;\nuse tract_core::ops::nn::DataFormat;\nuse tract_core::ops::nn::SoftmaxKind;\nuse tract_core::tract_data::itertools::Itertools;\n\npub fn source(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::source::TypedSource,\n) -> TractResult<Option<Arc<RValue>>> {\n    if let Some(shape) = op.fact.shape.as_concrete() {\n        if op.fact.datum_type == DatumType::F32 {\n            return Ok(Some(invocation(\"external\", &[], &[(\"shape\", ints(shape))])));\n        } else if op.fact.datum_type.is_quantized() {\n            if let Some(qp) = QuantFormat::from_dt(node.outputs[0].fact.datum_type) {\n                ast.quantization.insert(Identifier(node.name.to_string()), qp);\n            }\n            return Ok(Some(invocation(\"external\", &[], &[(\"shape\", ints(shape))])));\n        }\n    };\n    Ok(None)\n}\n\npub fn basic_matmul(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &PrefixMatMul,\n) -> TractResult<Option<Arc<RValue>>> {\n    let inputs = node.inputs.iter().map(|i| (*ast.mapping[i]).clone()).collect_vec();\n    if op.transpose_c {\n        Ok(Some(invocation(\n            \"matmul\",\n            &[Arc::new(inputs[1].clone()), Arc::new(inputs[0].clone())],\n            &[(\"transposeA\", logical(!op.transpose_b)), (\"transposeB\", logical(!op.transpose_a))],\n        )))\n    } else {\n        Ok(Some(invocation(\n            \"matmul\",\n            &[Arc::new(inputs[0].clone()), Arc::new(inputs[1].clone())],\n            &[(\"transposeA\", logical(op.transpose_a)), (\"transposeB\", logical(op.transpose_b))],\n        )))\n    }\n}\n\npub fn konst(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::konst::Const,\n) -> TractResult<Option<Arc<RValue>>> {\n    Ok(Some(ast.konst(&node.name, op.val())?))\n}\n\n// comp() removed — comparison ops now serialized via register_binary\n\npub fn concat(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::array::TypedConcat,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wires = node\n        .inputs\n        .iter()\n        .map(|i| Ok(ast.mapping[i].as_ref().clone()))\n        .collect::<TractResult<TVec<RValue>>>()?;\n    Ok(Some(invocation(\"concat\", &[array(&wires).into()], &[(\"axis\", numeric(op.axis))])))\n}\n\npub fn slice(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::array::Slice,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    // end = 0 means \"to the end\" in early nnef specs.\n    // the case begin = 0, end = 0: tract says \"empty tensor\", but nnef says \"noop\"\n    // so serialize as begin = 0, end = -dim\n    let end = if op.end.is_zero() && op.start == op.end {\n        -ast.model.node_input_facts(node.id)?[0].shape[op.axis].clone()\n    } else {\n        op.end.clone()\n    };\n    Ok(Some(invocation(\n        \"slice\",\n        &[wire],\n        &[\n            (\"axes\", ints(&[op.axis])),\n            (\"begin\", tdims(std::slice::from_ref(&op.start))),\n            (\"end\", tdims(&[end])),\n        ],\n    )))\n}\n\npub fn tile(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::array::Tile,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\"tile\", &[wire], &[(\"repeats\", tdims(&op.multipliers))])))\n}\n\npub fn dyn_tile(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    _: &ops::array::DynTile,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let multiplier = ast.mapping[&node.inputs[1]].clone();\n    Ok(Some(invocation(\"tile\", &[wire], &[(\"repeats\", (*multiplier).clone())])))\n}\n\npub fn pad_mode(mode: &ops::array::PadMode, dt: DatumType) -> TractResult<(&str, Option<RValue>)> {\n    use ops::array::PadMode;\n    Ok(match &mode {\n        PadMode::Constant(c) => (\n            \"constant\",\n            Some(if dt.is_float() {\n                numeric(c.cast_to_scalar::<f32>()?)\n            } else {\n                numeric(c.cast_to_scalar::<i64>()?)\n            }),\n        ),\n        PadMode::Reflect => (\"reflect\", None),\n        PadMode::Edge => (\"replicated\", None),\n    })\n}\n\npub fn pad(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::array::Pad,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let dt = ast.model.outlet_fact(node.inputs[0])?.datum_type;\n    let padding = array(op.pads.iter().map(|pair| ints(&[pair.0, pair.1])).collect::<TVec<_>>());\n    let mut params = tvec!((\"padding\", padding));\n    let (border, value) = pad_mode(&op.mode, dt)?;\n    params.push((\"border\", string(border)));\n    if let Some(value) = value {\n        params.push((\"value\", value));\n    }\n    Ok(Some(invocation(\"pad\", &[wire], &params)))\n}\n\nfn data_into_ncwh(data_format: DataFormat, geo_rank: usize, mut wire: Arc<RValue>) -> Arc<RValue> {\n    use tract_core::ops::nn::DataFormat::*;\n    if !data_format.has_n() {\n        wire = invocation(\"unsqueeze\", &[wire], &[(\"axes\", ints(&[0]))]);\n    }\n    if data_format == NHWC || data_format == HWC {\n        let mut perm: TVec<usize> = (0..geo_rank + 2).collect();\n        perm[1..].rotate_right(1);\n        wire = invocation(\"transpose\", &[wire], &[(\"axes\", ints(&perm))])\n    }\n    wire\n}\n\nfn data_from_ncwh(data_format: DataFormat, geo_rank: usize, mut wire: Arc<RValue>) -> Arc<RValue> {\n    use tract_core::ops::nn::DataFormat::*;\n    if data_format == NHWC || data_format == HWC {\n        let mut perm: TVec<usize> = (0..geo_rank + 2).collect();\n        perm[1..].rotate_left(1);\n        wire = invocation(\"transpose\", &[wire], &[(\"axes\", ints(&perm))])\n    }\n    if !data_format.has_n() {\n        wire = invocation(\"squeeze\", &[wire], &[(\"axes\", ints(&[0]))]);\n    }\n    wire\n}\n\npub fn make_conv_named_args<'a>(\n    node: &'a TypedNode,\n    pool_spec: &'a PoolSpec,\n    group: usize,\n    deconv: bool,\n    adjustments: Option<&[usize]>,\n) -> TractResult<TVec<(&'a str, RValue)>> {\n    use tract_core::ops::cnn::PaddingSpec;\n    let output_shape = pool_spec.data_format.shape(node.outputs[0].fact.shape.to_tvec())?;\n    let padding = match &pool_spec.padding {\n        PaddingSpec::ExplicitOnnxPool(bef, after, _) | PaddingSpec::Explicit(bef, after) => array(\n            bef.iter()\n                .zip(after.iter())\n                .map(|(a, b)| tuple_2(numeric(a), numeric(b)))\n                .collect::<Vec<_>>(),\n        ),\n        PaddingSpec::SameUpper => array(&[]),\n        PaddingSpec::SameLower => bail!(\"Unsupported padding scheme\"),\n        PaddingSpec::Valid => array(\n            (0..pool_spec.rank()).map(|_| tuple_2(numeric(0), numeric(0))).collect::<Vec<_>>(),\n        ),\n    };\n    let mut named_args = tvec![\n        (\"dilation\", ints(&pool_spec.dilations())),\n        (\"stride\", ints(&pool_spec.strides())),\n        (\"border\", string(\"constant\")),\n        (\"groups\", numeric(group)),\n        (\"padding\", padding),\n    ];\n    if deconv && adjustments.unwrap().iter().any(|a| *a != 0) {\n        let output_shape = output_shape\n            .hw_dims()\n            .iter()\n            .map(|d| d.to_usize())\n            .collect::<TractResult<TVec<_>>>()?;\n        named_args.push((\"output_shape\", ints(&output_shape)));\n    };\n    Ok(named_args)\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn conv_like(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    pool_spec: &PoolSpec,\n    group: usize,\n    deconv: bool,\n    adjustments: Option<&[usize]>,\n) -> TractResult<Option<Arc<RValue>>> {\n    let mut wire = ast.mapping[&node.inputs[0]].clone();\n    let kernel = ast.mapping[&node.inputs[1]].clone();\n    let bias = ast.mapping[&node.inputs[2]].clone();\n    let data_format = pool_spec.data_format;\n    ensure!(data_format.has_n());\n    if data_format.c_is_last() {\n        let mut perm: TVec<usize> = (0..pool_spec.rank() + 1).collect();\n        perm.insert(1, pool_spec.rank() + 1);\n        wire = invocation(\"transpose\", &[wire], &[(\"axes\", ints(&perm))]);\n    }\n    wire = ast.force_variable(format!(\"{}_input\", node.name), &wire);\n\n    let inputs = tvec![wire, kernel, bias];\n    let named_args = make_conv_named_args(node, pool_spec, group, deconv, adjustments)?;\n\n    let name = if deconv { \"deconv\" } else { \"conv\" };\n    wire = invocation(name, &inputs, &named_args);\n    // need to force quantization storage as output code may miss it\n    let var_name = Identifier(format!(\"{}_{}\", node.name, name));\n    if let Some(qp) = QuantFormat::from_dt(node.outputs[0].fact.datum_type) {\n        ast.quantization.insert(var_name.clone(), qp);\n    }\n    wire = ast.force_variable(var_name, &wire);\n\n    if data_format.c_is_last() {\n        let mut perm: TVec<usize> = (0..pool_spec.rank() + 2).collect();\n        perm.remove(1);\n        perm.push(1);\n        wire = invocation(\"transpose\", &[wire], &[(\"axes\", ints(&perm))]);\n    }\n    Ok(Some(wire))\n}\n\npub fn conv(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::cnn::conv::Conv,\n) -> TractResult<Option<Arc<RValue>>> {\n    conv_like(ast, node, &op.pool_spec, op.group, false, None)\n}\n\npub fn deconv(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::cnn::deconv::Deconv,\n) -> TractResult<Option<Arc<RValue>>> {\n    conv_like(ast, node, &op.pool_spec, op.group, true, Some(&op.adjustments))\n}\n\nfn cnn_pool_fragment(\n    ast: &mut IntoAst,\n    data_format: DataFormat,\n    geo_rank: usize,\n    op_name: &str,\n) -> Identifier {\n    if data_format == DataFormat::NCHW {\n        return op_name.into();\n    }\n    let fragment_name =\n        Identifier(format!(\"tract_{op_name}_{data_format:?}_{geo_rank}D\").to_lowercase());\n    if ast.fragments.contains_key(&fragment_name) {\n        return fragment_name;\n    }\n\n    let mut body = vec![];\n    let mut fragment =\n        ast.framework.stdlib.iter().find(|f| f.decl.id.0 == op_name).unwrap().clone();\n    fragment.decl.id = fragment_name.clone();\n\n    let mut wire = ident(\"input\").into();\n    wire = data_into_ncwh(data_format, geo_rank, wire);\n\n    body.push(assignment(\"nchw\", wire));\n    wire = invocation(\n        op_name,\n        &[ident(\"nchw\").into()],\n        &fragment\n            .decl\n            .parameters\n            .iter()\n            .skip(1)\n            .map(|f| (&*f.id.0, ident(&f.id)))\n            .collect::<Vec<_>>(),\n    );\n    body.push(assignment(op_name, wire));\n\n    wire = data_from_ncwh(data_format, geo_rank, ident(op_name).into());\n\n    body.push(assignment(\"output\", wire));\n    fragment.body = Some(body);\n    ast.fragments.insert(fragment_name.clone(), fragment);\n    fragment_name\n}\n\nfn cnn_pool(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op_name: &str,\n    pool_spec: &tract_core::ops::cnn::PoolSpec,\n    normalize_arg: Option<(&'static str, RValue)>,\n) -> TractResult<Option<Arc<RValue>>> {\n    use tract_core::ops::cnn::PaddingSpec;\n    let mut wire = ast.mapping[&node.inputs[0]].clone();\n    wire = ast.force_variable(format!(\"{}_input\", node.name), &wire);\n    let conv_fragment = cnn_pool_fragment(ast, pool_spec.data_format, pool_spec.rank(), op_name);\n    let padding = match &pool_spec.padding {\n        PaddingSpec::ExplicitOnnxPool(bef, after, _) | PaddingSpec::Explicit(bef, after) => Some(\n            bef.iter()\n                .zip(after.iter())\n                .map(|(a, b)| tuple_2(numeric(a), numeric(b)))\n                .collect::<Vec<_>>(),\n        ),\n        PaddingSpec::SameUpper => None,\n        PaddingSpec::SameLower => bail!(\"Unsupported padding scheme\"),\n        PaddingSpec::Valid => {\n            Some((0..pool_spec.rank()).map(|_| tuple_2(numeric(0), numeric(0))).collect::<Vec<_>>())\n        }\n    };\n    let mut size = tvec!(1, 1);\n    size.extend(pool_spec.kernel_shape.iter().cloned());\n    let mut strides = tvec!(1, 1);\n    strides.extend(pool_spec.strides().iter().cloned());\n    let mut dilations = tvec!(1, 1);\n    dilations.extend(pool_spec.dilations().iter().cloned());\n    let padding = if let Some(pad) = padding {\n        let mut full_padding =\n            vec![tuple_2(numeric(0), numeric(0)), tuple_2(numeric(0), numeric(0))];\n        full_padding.extend(pad.iter().cloned());\n        array(full_padding)\n    } else {\n        array(&[])\n    };\n    let mut params = tvec!(\n        (\"size\", ints(&size)),\n        (\"dilation\", ints(&dilations)),\n        (\"stride\", ints(&strides)),\n        (\"border\", string(\"ignore\")),\n        (\"padding\", padding),\n    );\n    if let Some(normalize_arg) = normalize_arg {\n        params.push(normalize_arg);\n    };\n    wire = invocation(conv_fragment, &[wire], &params);\n    wire = ast.force_variable(&node.name, &wire);\n    Ok(Some(wire))\n}\n\npub fn max_pool(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::cnn::MaxPool,\n) -> TractResult<Option<Arc<RValue>>> {\n    cnn_pool(ast, node, \"max_pool\", &op.pool_spec, None)\n}\n\npub fn sum_pool(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::cnn::SumPool,\n) -> TractResult<Option<Arc<RValue>>> {\n    cnn_pool(ast, node, \"box\", &op.pool_spec, Some((\"normalize\", logical(op.normalize))))\n}\n\npub fn ser_axis_op(op: &ops::change_axes::AxisOp, wire: Arc<RValue>, rank: usize) -> Arc<RValue> {\n    match op {\n        AxisOp::Rm(axis) => invocation(\"squeeze\", &[wire], &[(\"axes\", ints(&[*axis]))]),\n        AxisOp::Add(axis) => invocation(\"unsqueeze\", &[wire], &[(\"axes\", ints(&[*axis]))]),\n        AxisOp::Move(from, to) => {\n            let mut perm: TVec<usize> = (0..rank).collect();\n            if from < to {\n                perm[*from..(to + 1)].rotate_left(1);\n            } else {\n                perm[*to..(from + 1)].rotate_right(1);\n            }\n            invocation(\"transpose\", &[wire], &[(\"axes\", ints(&perm))])\n        }\n        AxisOp::Reshape(start, from, to) => invocation(\n            \"reshape\",\n            &[wire],\n            &[\n                (\"shape\", tdims(to)),\n                (\"axis_start\", numeric(start)),\n                (\"axis_count\", numeric(from.len())),\n            ],\n        ),\n    }\n}\n\npub fn axis_op(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::change_axes::AxisOp,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let rank = node.outputs[0].fact.rank();\n    Ok(Some(ser_axis_op(op, wire, rank)))\n}\n\npub fn reduce(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::nn::Reduce,\n) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let oper = match op.reducer {\n        ops::nn::Reducer::ArgMax(last) if !last => \"argmax_reduce\",\n        ops::nn::Reducer::ArgMin(last) if !last => \"argmin_reduce\",\n        ops::nn::Reducer::Sum => \"sum_reduce\",\n        ops::nn::Reducer::Max => \"max_reduce\",\n        ops::nn::Reducer::Min => \"min_reduce\",\n        _ => return Ok(None),\n    };\n    Ok(Some(invocation(oper, &[wire], &[(\"axes\", ints(&op.axes))])))\n}\n\npub fn select(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    _op: &ops::logic::Iff,\n) -> TractResult<Option<Arc<RValue>>> {\n    Ok(Some(invocation(\n        \"select\",\n        &node.inputs.iter().map(|o| ast.mapping[o].clone()).collect::<TVec<_>>(),\n        &[],\n    )))\n}\n\npub fn leaky_relu(ast: &mut IntoAst, node: &TypedNode) -> TractResult<Option<Arc<RValue>>> {\n    let op = node.op_as::<ops::element_wise::ElementWiseOp>().context(\"Wrong op\")?;\n    let op = op.0.downcast_ref::<ops::nn::LeakyRelu>().context(\"Wrong op\")?;\n    Ok(Some(invocation(\n        \"leaky_relu\",\n        &node.inputs.iter().map(|o| ast.mapping[o].clone()).collect::<TVec<_>>(),\n        &[(\"alpha\", RValue::Literal(op.alpha.into()))],\n    )))\n}\n\npub fn softmax(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ops::nn::Softmax,\n) -> TractResult<Option<Arc<RValue>>> {\n    if op.kind != SoftmaxKind::default() {\n        return Ok(None);\n    }\n    let litteral_axes: Vec<_> = op.axes.iter().map(|&it| (it as i64).into()).collect();\n    Ok(Some(invocation(\n        \"softmax\",\n        &[ast.mapping[&node.inputs[0]].clone()],\n        &[(\"axes\", RValue::Literal(crate::ast::Literal::Array(litteral_axes)))],\n    )))\n}\n\npub fn rewrite_block_quant_const_to_scalar(\n    _ctx: &(),\n    _model: &TypedModel,\n    _node: &TypedNode,\n    _prefix: &str,\n    _op: &Const,\n) -> TractResult<Option<TypedModelPatch>> {\n    // Block-quant tensors now carry their logical shape [G, M, K] directly,\n    // so no scalar-to-shaped rewriting is needed.\n    Ok(None)\n}\n\npub fn rewrite_matmul_to_same_rank(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    prefix: &str,\n    op: &PrefixMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    let a_rank = block_quant_aware_input_shape(model.outlet_fact(node.inputs[0])?)?.len();\n    let b_rank = block_quant_aware_input_shape(model.outlet_fact(node.inputs[1])?)?.len();\n    if a_rank == b_rank {\n        return Ok(None);\n    }\n    let mut patch = TypedModelPatch::default();\n    let mut inputs = patch.taps(model, &node.inputs)?;\n    for i in a_rank..a_rank.max(b_rank) {\n        inputs[0] =\n            patch.wire_node(format!(\"{prefix}.extra_a_axis.{i}\"), AxisOp::Add(0), &[inputs[0]])?[0];\n    }\n    for i in b_rank..a_rank.max(b_rank) {\n        inputs[1] =\n            patch.wire_node(format!(\"{prefix}.extra_b_axis.{i}\"), AxisOp::Add(0), &[inputs[1]])?[1];\n    }\n    let result = patch.wire_node(prefix, *op, &inputs)?[0];\n    patch.shunt_outside(model, node.id.into(), result)?;\n    Ok(Some(patch))\n}\n\npub fn rewrite_consistent_quantized_conv(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    op: &Conv,\n) -> TractResult<Option<TypedModelPatch>> {\n    let facts = model.node_input_facts(node.id)?;\n    if facts.len() > 3 {\n        ensure!(facts[3..9].iter().all(|fact| fact.konst.is_some()));\n        for ix in [0, 1] {\n            let fact = model.outlet_fact(node.inputs[ix])?;\n            if !fact.datum_type.is_quantized() {\n                let mut patch = TypedModelPatch::default();\n                let mut wire = patch.taps(model, &node.inputs)?;\n                let dt = fact.datum_type.quantize(QParams::ZpScale {\n                    zero_point: facts[3 + 2 * ix]\n                        .konst\n                        .as_ref()\n                        .unwrap()\n                        .cast_to_scalar::<i32>()?,\n                    scale: facts[4 + 2 * ix].konst.as_ref().unwrap().cast_to_scalar::<f32>()?,\n                });\n                wire[ix] =\n                    patch.wire_node(format!(\"{name}.cast_to_q_{ix}\"), cast(dt), &[wire[ix]])?[0];\n                let output = patch.wire_node(name, op.clone(), &wire)?[0];\n                patch.shunt_outside(model, node.id.into(), output)?;\n                return Ok(Some(patch));\n            }\n        }\n    }\n    Ok(None)\n}\n"
  },
  {
    "path": "nnef/src/ops/resource.rs",
    "content": "use crate::internal::*;\n\npub fn register(registry: &mut Registry) {\n    registry\n        .register_primitive(\n            \"tract_resource_get\",\n            &[\n                TypeName::String.named(\"label\").doc(\"Resource label to access\"),\n                TypeName::String.named(\"key\").doc(\"Key path in resource\"),\n            ],\n            &[(\"output\", TypeName::Any.tensor())],\n            resource_get,\n        )\n        .with_doc(\"Access embedded resource by key\");\n}\n\nfn resource_get(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let label: String = invocation.named_arg_as(builder, \"label\")?;\n    let key: String = invocation.named_arg_as(builder, \"key\")?;\n    let resource = builder\n        .proto_model\n        .resources\n        .get(&label)\n        .with_context(|| anyhow!(\"No resource found for label {:?} in the model\", label))?;\n    resource.get(&key)\n}\n"
  },
  {
    "path": "nnef/src/registry.rs",
    "content": "use std::ops::ControlFlow;\n\nuse crate::ast::Identifier;\nuse crate::internal::*;\n\nuse crate::ast;\nuse crate::deser::Value;\n\nuse tract_core::dyn_clone::clone_box;\nuse tract_core::ops::binary::*;\n\npub type ToTract = fn(&mut ModelBuilder, &ResolvedInvocation) -> TractResult<Value>;\npub type FromTract =\n    Box<dyn Fn(&mut IntoAst, &TypedNode) -> TractResult<Option<Arc<RValue>>> + Send + Sync>;\npub type FromTractWithOp<O> =\n    fn(&mut IntoAst, node: &TypedNode, op: &O) -> TractResult<Option<Arc<RValue>>>;\npub type BinOp = (Identifier, Box<dyn BinMiniOp>);\npub type Extension = Box<\n    dyn Fn(&mut crate::deser::ModelBuilder, &Identifier, &str) -> TractResult<ControlFlow<(), ()>>\n        + Send\n        + Sync,\n>;\n\n#[derive(Clone)]\npub struct PrimitiveDecl {\n    pub decl: FragmentDecl,\n    pub docstrings: Option<Vec<String>>,\n    pub to_tract: ToTract,\n}\n\nimpl PrimitiveDecl {\n    pub fn validate(&self) -> TractResult<()> {\n        self.decl.validate().with_context(|| format!(\"Invalid primitive `{}'\", self.decl.id.0))\n    }\n\n    pub fn with_doc(&mut self, docstring: impl Into<String>) -> &mut Self {\n        self.docstrings.get_or_insert_with(Vec::new).push(docstring.into());\n        self\n    }\n}\n\npub struct Registry {\n    pub id: Identifier,\n    pub docstrings: Option<Vec<String>>,\n    pub aliases: Vec<Identifier>,\n    pub fragments: HashMap<Identifier, FragmentDef>,\n    pub primitives: HashMap<Identifier, PrimitiveDecl>,\n    pub unit_element_wise_ops: Vec<(Identifier, Box<dyn ElementWiseMiniOp>)>,\n    pub element_wise_ops: Vec<(Identifier, TypeId, FromTract, Vec<ast::Parameter>, ToTract)>,\n    pub binary_ops: Vec<BinOp>,\n    pub from_tract: HashMap<TypeId, FromTract>,\n    pub extensions: Vec<Extension>,\n}\n\nimpl Registry {\n    pub fn new(id: impl AsRef<str>) -> Registry {\n        Registry {\n            id: id.as_ref().into(),\n            docstrings: None,\n            aliases: Default::default(),\n            primitives: Default::default(),\n            fragments: Default::default(),\n            from_tract: Default::default(),\n            unit_element_wise_ops: Default::default(),\n            element_wise_ops: Default::default(),\n            binary_ops: Default::default(),\n            extensions: Default::default(),\n        }\n    }\n\n    pub fn with_doc(mut self, docstring: impl Into<String>) -> Registry {\n        self.docstrings.get_or_insert_with(Vec::new).push(docstring.into());\n        self\n    }\n\n    pub fn register_dumper<O: TypedOp>(&mut self, dumper: FromTractWithOp<O>) {\n        self.from_tract.insert(\n            std::any::TypeId::of::<O>(),\n            Box::new(move |ast: &mut IntoAst, node: &TypedNode| {\n                let op = node.op_as::<O>().unwrap();\n                dumper(ast, node, op)\n            }),\n        );\n    }\n\n    pub fn register_primitive(\n        &mut self,\n        id: impl AsRef<str>,\n        params: &[ast::Parameter],\n        results: &[impl Into<ast::Result_> + Clone],\n        func: ToTract,\n    ) -> &mut PrimitiveDecl {\n        let id: Identifier = id.as_ref().into();\n        let decl = FragmentDecl {\n            id: id.clone(),\n            generic_decl: None,\n            parameters: params.to_vec(),\n            results: results.iter().cloned().map(|it| it.into()).collect(),\n        };\n        let primitive_decl = PrimitiveDecl { decl, docstrings: None, to_tract: func };\n        self.primitives.insert(id.clone(), primitive_decl);\n        self.primitives.get_mut(&id).expect(\"Unexpected empty entry in primitives hashmap\")\n    }\n\n    pub fn register_fragment(&mut self, def: FragmentDef) {\n        self.fragments.insert(def.decl.id.clone(), def);\n    }\n\n    pub fn register_unit_element_wise(&mut self, id: impl AsRef<str>, ew: &dyn ElementWiseMiniOp) {\n        assert!(std::mem::size_of_val(ew) == 0);\n        self.unit_element_wise_ops.push((id.as_ref().into(), clone_box(ew)));\n    }\n\n    pub fn register_element_wise(\n        &mut self,\n        id: impl AsRef<str>,\n        type_id: TypeId,\n        dumper: FromTract,\n        parameters: Vec<ast::Parameter>,\n        loader: ToTract,\n    ) {\n        self.element_wise_ops.push((id.as_ref().into(), type_id, dumper, parameters, loader));\n    }\n\n    pub fn register_binary(&mut self, id: impl AsRef<str>, op: &dyn BinMiniOp) {\n        self.binary_ops.push((id.as_ref().into(), clone_box(op)));\n    }\n\n    pub fn serialize(\n        &self,\n        ast: &mut IntoAst,\n        node: &TypedNode,\n    ) -> TractResult<Option<Arc<RValue>>> {\n        use tract_core::ops;\n        if node.op_is::<ops::identity::Identity>() {\n            return Ok(Some(ast.mapping[&node.inputs[0]].clone()));\n        } else if let Some(op) = node.op().downcast_ref::<ops::element_wise::ElementWiseOp>() {\n            if std::mem::size_of_val(op.0.as_ref()) == 0 {\n                if let Some(op) = self\n                    .unit_element_wise_ops\n                    .iter()\n                    .find(|ew| ew.1.as_ref().type_id() == op.0.type_id())\n                {\n                    let a = ast.mapping[&node.inputs[0]].clone();\n                    return Ok(Some(invocation(&op.0, &[a], &[])));\n                }\n            } else if let Some(op) = self.element_wise_ops.iter().find(|ew| ew.1 == op.0.type_id())\n                && let Some(result) = (op.2)(ast, node)?\n            {\n                return Ok(Some(result));\n            }\n        } else if let Some(op) = node.op().downcast_ref::<ops::binary::TypedBinOp>() {\n            if let Some(op) =\n                self.binary_ops.iter().find(|ew| ew.1.as_ref().type_id() == op.0.type_id())\n            {\n                let a = ast.mapping[&node.inputs[0]].clone();\n                let b = ast.mapping[&node.inputs[1]].clone();\n                return Ok(Some(invocation(&op.0, &[a, b], &[])));\n            }\n        } else if let Some(op) = self.from_tract.get(&node.op().type_id())\n            && let Some(result) = op(ast, node)?\n        {\n            return Ok(Some(result));\n        }\n        Ok(None)\n    }\n\n    pub fn deserialize(\n        &self,\n        builder: &mut ModelBuilder,\n        invocation: &ast::Invocation,\n        dt: &[Option<DatumType>],\n    ) -> TractResult<Option<Value>> {\n        if let Some(op) = self.primitives.get(&invocation.id) {\n            let resolved = ResolvedInvocation {\n                invocation,\n                default_params: &op.decl.parameters,\n                dt_from_quant_file: dt,\n            };\n            let out_value = (op.to_tract)(builder, &resolved)\n                .with_context(|| format!(\"Deserializing op `{}'\", invocation.id.0))?;\n            return Ok(Some(out_value));\n        }\n        let c_dt: Option<DatumType> = dt.first().cloned().and_then(|dt| dt);\n        if let Some(ew) = self.unit_element_wise_ops.iter().find(|ew| ew.0 == invocation.id) {\n            let input =\n                invocation.arguments[0].rvalue.resolve(builder, &[])?.to::<OutletId>(builder)?;\n            let outlet = builder.wire_as_outlets(\n                tract_core::ops::element_wise::ElementWiseOp(ew.1.clone(), c_dt),\n                &[input],\n            )?;\n            if let Some(assumed_out_dt) = c_dt {\n                let out_dt = builder.model.outlet_fact(outlet[0])?.datum_type;\n                if out_dt != assumed_out_dt {\n                    return Ok(Some(\n                        builder.wire(tract_core::ops::cast::cast(assumed_out_dt), &outlet)?,\n                    ));\n                }\n            }\n            return Ok(Some(Value::Wire(outlet[0])));\n        }\n        if let Some(ew) = self.element_wise_ops.iter().find(|ew| ew.0 == invocation.id) {\n            let resolved =\n                ResolvedInvocation { invocation, default_params: &ew.3, dt_from_quant_file: dt };\n            return Ok(Some(\n                (ew.4)(builder, &resolved)\n                    .with_context(|| format!(\"Deserializing op `{}'\", invocation.id.0))?,\n            ));\n        }\n        if let Some(bin) = self.binary_ops.iter().find(|bin| bin.0 == invocation.id) {\n            let mut a =\n                invocation.arguments[0].rvalue.resolve(builder, &[])?.to::<OutletId>(builder)?;\n            let mut b =\n                invocation.arguments[1].rvalue.resolve(builder, &[])?.to::<OutletId>(builder)?;\n            let a_fact = builder.model.outlet_fact(a)?;\n            let b_fact = builder.model.outlet_fact(b)?;\n            let a_dt = a_fact.datum_type;\n            let b_dt = b_fact.datum_type;\n\n            // mitigation of nnef \"scalar\" type mismatch with tract-core more\n            // strict types\n            let operating_dt = if a_dt == b_dt\n                && bin.1.operating_datum_type(a_dt, b_dt).map(|it| it == a_dt).unwrap_or(false)\n            {\n                a_dt\n            } else if a_dt == String::datum_type() || b_dt == String::datum_type() {\n                String::datum_type()\n            } else if a_dt == TDim::datum_type() || b_dt == TDim::datum_type() {\n                bin.1.operating_datum_type(a_dt, b_dt)?\n            // assume scalar are inline and we should not trust their DT\n            } else if a_fact.konst.is_some() && a_fact.shape.volume().is_one() {\n                b_dt\n            } else if b_fact.konst.is_some() && b_fact.shape.volume().is_one() {\n                a_dt\n            } else if builder.model.node(a.node).op_is::<tract_core::ops::konst::Const>() {\n                b_dt\n            } else if builder.model.node(b.node).op_is::<tract_core::ops::konst::Const>() {\n                a_dt\n            } else {\n                bin.1.operating_datum_type(a_dt, b_dt)?\n            };\n\n            if !a_dt.is_quantized() || !b_dt.is_quantized() {\n                a = builder.wire_as_outlets(tract_core::ops::cast::cast(operating_dt), &[a])?[0];\n                b = builder.wire_as_outlets(tract_core::ops::cast::cast(operating_dt), &[b])?[0];\n            }\n            let inputs = multi_rank_broadcast(builder, &[a, b])?;\n\n            let c_dt: Option<DatumType> = dt.first().cloned().and_then(|dt| dt);\n            let required_operating_dt = a_dt.is_quantized() || b_dt.is_quantized();\n            let mut wire = builder.wire_as_outlets(\n                tract_core::ops::binary::TypedBinOp(\n                    bin.1.clone(),\n                    c_dt.filter(|_| required_operating_dt),\n                ),\n                &inputs,\n            )?[0];\n            if let Some(c_dt) = c_dt {\n                wire = builder.wire_as_outlets(tract_core::ops::cast::cast(c_dt), &[wire])?[0];\n            }\n            return Ok(Some(Value::Wire(wire)));\n        }\n        if let Some(frag) = self.fragments.get(&invocation.id) {\n            let resolved = ResolvedInvocation {\n                invocation,\n                default_params: &frag.decl.parameters,\n                dt_from_quant_file: dt,\n            };\n            return Ok(Some(builder.wire_fragment_invocation(\n                &resolved,\n                &frag.decl,\n                frag.body.as_deref().unwrap(),\n            )?));\n        }\n        Ok(None)\n    }\n}\n\npub fn multi_rank_broadcast(\n    builder: &mut ModelBuilder,\n    inputs: &[OutletId],\n) -> TractResult<TVec<OutletId>> {\n    let ranks = inputs\n        .iter()\n        .map(|&i| Ok(builder.model.outlet_fact(i)?.rank()))\n        .collect::<TractResult<Vec<usize>>>()?;\n    let max_rank = ranks.iter().copied().max().unwrap();\n    (inputs.iter())\n        .zip(ranks.iter())\n        .map(|(&i, &r)| {\n            (r..max_rank).try_fold(i, |w, n| Ok(builder.wire_as_outlets(AxisOp::Add(n), &[w])?[0]))\n        })\n        .collect()\n}\n"
  },
  {
    "path": "nnef/src/resource.rs",
    "content": "use std::path::Path;\n\nuse crate::ast::QuantFormat;\nuse crate::internal::*;\nuse safetensors::SafeTensors;\nuse tract_core::downcast_rs::{DowncastSync, impl_downcast};\nuse tract_core::tract_data::itertools::Itertools;\n\npub const GRAPH_NNEF_FILENAME: &str = \"graph.nnef\";\npub const GRAPH_QUANT_FILENAME: &str = \"graph.quant\";\n\npub fn resource_path_to_id(path: impl AsRef<Path>) -> TractResult<String> {\n    let mut path = path.as_ref().to_path_buf();\n    path.set_extension(\"\");\n    path.to_str()\n        .ok_or_else(|| format_err!(\"Badly encoded filename for path: {:?}\", path))\n        .map(|s| s.to_string())\n}\n\npub trait Resource: DowncastSync + std::fmt::Debug + Send + Sync {\n    /// Get value for a given key.\n    fn get(&self, _key: &str) -> TractResult<Value> {\n        bail!(\"No key access supported by this resource\");\n    }\n\n    fn to_liquid_value(&self) -> Option<liquid::model::Value> {\n        None\n    }\n}\n\nimpl_downcast!(sync Resource);\n\npub trait ResourceLoader: Send + Sync {\n    /// Name of the resource loader.\n    fn name(&self) -> StaticName;\n    /// Try to load a resource give a path and its corresponding reader.\n    /// None is returned if the path is not accepted by this loader.\n    fn try_load(\n        &self,\n        path: &Path,\n        reader: &mut dyn std::io::Read,\n        framework: &Nnef,\n    ) -> TractResult<Option<(String, Arc<dyn Resource>)>>;\n\n    fn into_boxed(self) -> Box<dyn ResourceLoader>\n    where\n        Self: Sized + 'static,\n    {\n        Box::new(self)\n    }\n}\n\n#[derive(Debug)]\npub struct GraphNnef(pub String);\nimpl Resource for GraphNnef {}\n\n#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]\npub struct GraphNnefLoader;\n\nimpl ResourceLoader for GraphNnefLoader {\n    fn name(&self) -> StaticName {\n        \"GraphNnefLoader\".into()\n    }\n\n    fn try_load(\n        &self,\n        path: &Path,\n        reader: &mut dyn std::io::Read,\n        _framework: &Nnef,\n    ) -> TractResult<Option<(String, Arc<dyn Resource>)>> {\n        if path.ends_with(GRAPH_NNEF_FILENAME) {\n            let mut text = String::new();\n            reader.read_to_string(&mut text)?;\n            Ok(Some((path.to_string_lossy().to_string(), Arc::new(GraphNnef(text)))))\n        } else {\n            Ok(None)\n        }\n    }\n}\n\nimpl Resource for Tensor {}\n\n#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]\npub struct DatLoader;\n\nimpl ResourceLoader for DatLoader {\n    fn name(&self) -> StaticName {\n        \"DatLoader\".into()\n    }\n\n    fn try_load(\n        &self,\n        path: &Path,\n        reader: &mut dyn std::io::Read,\n        _framework: &Nnef,\n    ) -> TractResult<Option<(String, Arc<dyn Resource>)>> {\n        if path.extension().map(|e| e == \"dat\").unwrap_or(false) {\n            let tensor = crate::tensors::read_tensor(reader)\n                .with_context(|| format!(\"Error while reading tensor {path:?}\"))?;\n            Ok(Some((resource_path_to_id(path)?, Arc::new(tensor))))\n        } else {\n            Ok(None)\n        }\n    }\n}\n\nimpl Resource for HashMap<String, QuantFormat> {}\n\n#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]\npub struct GraphQuantLoader;\n\nimpl ResourceLoader for GraphQuantLoader {\n    fn name(&self) -> StaticName {\n        \"GraphQuantLoader\".into()\n    }\n\n    fn try_load(\n        &self,\n        path: &Path,\n        reader: &mut dyn std::io::Read,\n        _framework: &Nnef,\n    ) -> TractResult<Option<(String, Arc<dyn Resource>)>> {\n        if path.ends_with(GRAPH_QUANT_FILENAME) {\n            let mut text = String::new();\n            reader.read_to_string(&mut text)?;\n            let quant = crate::ast::quant::parse_quantization(&text)?;\n            let quant: HashMap<String, QuantFormat> =\n                quant.into_iter().map(|(k, v)| (k.0, v)).collect();\n            Ok(Some((path.to_str().unwrap().to_string(), Arc::new(quant))))\n        } else {\n            Ok(None)\n        }\n    }\n}\n\npub struct TypedModelLoader {\n    pub optimized_model: bool,\n}\n\nimpl TypedModelLoader {\n    pub fn new(optimized_model: bool) -> Self {\n        Self { optimized_model }\n    }\n}\n\nimpl ResourceLoader for TypedModelLoader {\n    fn name(&self) -> StaticName {\n        \"TypedModelLoader\".into()\n    }\n\n    fn try_load(\n        &self,\n        path: &Path,\n        reader: &mut dyn std::io::Read,\n        framework: &Nnef,\n    ) -> TractResult<Option<(String, Arc<dyn Resource>)>> {\n        const NNEF_TGZ: &str = \".nnef.tgz\";\n        const NNEF_TAR: &str = \".nnef.tar\";\n        let path_str = path.to_str().unwrap_or(\"\");\n        if path_str.ends_with(NNEF_TGZ) || path_str.ends_with(NNEF_TAR) {\n            let model = if self.optimized_model {\n                framework.model_for_read(reader)?.into_optimized()?\n            } else {\n                framework.model_for_read(reader)?\n            };\n\n            let label = if path_str.ends_with(NNEF_TGZ) {\n                path.to_str()\n                    .ok_or_else(|| anyhow!(\"invalid model resource path\"))?\n                    .trim_end_matches(NNEF_TGZ)\n            } else {\n                path.to_str()\n                    .ok_or_else(|| anyhow!(\"invalid model resource path\"))?\n                    .trim_end_matches(NNEF_TAR)\n            };\n            Ok(Some((resource_path_to_id(label)?, Arc::new(TypedModelResource(model)))))\n        } else {\n            Ok(None)\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct TypedModelResource(pub TypedModel);\n\nimpl Resource for TypedModelResource {}\n\npub struct SafeTensorsLoader;\n\nimpl ResourceLoader for SafeTensorsLoader {\n    fn name(&self) -> StaticName {\n        \"SafeTensorsLoader\".into()\n    }\n\n    fn try_load(\n        &self,\n        path: &Path,\n        reader: &mut dyn std::io::Read,\n        _framework: &Nnef,\n    ) -> TractResult<Option<(String, Arc<dyn Resource>)>> {\n        if path.extension().is_some_and(|e| e == \"safetensors\") {\n            let mut buffer = vec![];\n            reader.read_to_end(&mut buffer)?;\n            let tensors: Vec<(String, Arc<Tensor>)> = SafeTensors::deserialize(&buffer)?\n                .tensors()\n                .into_iter()\n                .map(|(name, t)| {\n                    let dt = match t.dtype() {\n                        safetensors::Dtype::F32 => DatumType::F32,\n                        safetensors::Dtype::F16 => DatumType::F16,\n                        _ => panic!(),\n                    };\n                    let tensor = unsafe { Tensor::from_raw_dt(dt, t.shape(), t.data()).unwrap() };\n                    (name, tensor.into_arc_tensor())\n                })\n                .collect_vec();\n            return Ok(Some((path.to_string_lossy().to_string(), Arc::new(tensors))));\n        }\n        Ok(None)\n    }\n}\n\nimpl Resource for Vec<(String, Arc<Tensor>)> {}\n"
  },
  {
    "path": "nnef/src/ser.rs",
    "content": "use crate::ast::*;\nuse crate::internal::*;\nuse tract_core::ndarray::ArrayViewD;\nuse tract_core::ndarray::Axis;\nuse tract_core::ops::cnn::conv::{rewrite_kernel_conv_in_oihw, rewrite_kernel_deconv_in_oihw};\nuse tract_itertools::Itertools;\n\npub fn rewrite_model(model: &mut TypedModel) -> TractResult<()> {\n    model.prop_consts()?;\n    tract_core::ops::einsum::prefix_matmul::rewrite_einsum_to_prefix_matmul(model, true)?;\n    Rewriter::default()\n        .with_rule_for(\n            \"rewrite_block_quant_const_to_scalar\",\n            crate::ops::nnef::ser::rewrite_block_quant_const_to_scalar,\n        )\n        .with_rule_for(\n            \"rewrite_matmul_to_same_rank\",\n            crate::ops::nnef::ser::rewrite_matmul_to_same_rank,\n        )\n        .with_rule_for(\"rewrite_conv_with_n_axis\", tract_core::ops::cnn::rewrite_conv_with_n_axis)\n        .with_rule_for(\n            \"rewrite_deconv_with_n_axis\",\n            tract_core::ops::cnn::rewrite_deconv_with_n_axis,\n        )\n        .with_rule_for(\"rewrite_kernel_conv_in_oihw\", rewrite_kernel_conv_in_oihw)\n        .with_rule_for(\"rewrite_kernel_deconv_in_oihw\", rewrite_kernel_deconv_in_oihw)\n        .with_rule_for(\n            \"rewrite_consistent_quantized_conv\",\n            crate::ops::nnef::ser::rewrite_consistent_quantized_conv,\n        )\n        .with_rule_for(\"expand_mean_of_square\", tract_core::ops::nn::expand_mean_of_squares)\n        .rewrite(&(), model)\n}\n\npub fn to_proto_model(framework: &Nnef, model: &TypedModel) -> TractResult<ProtoModel> {\n    let mut fixed_model = model.clone();\n    rewrite_model(&mut fixed_model)?;\n    let mut into_ast = IntoAst::new(framework, &fixed_model);\n    into_ast.translate().context(\"Translating model to AST\")?;\n    into_ast.into_proto_model().context(\"Translating AST to proto model\")\n}\n\npub fn to_fragment_def(\n    parent: &IntoAst,\n    model: &TypedModel,\n) -> TractResult<(FragmentDef, Vec<RequiredTensorParameter>)> {\n    let mut into_ast = IntoAst::new(parent.framework, model);\n    into_ast.parent = Some(parent);\n    into_ast.translate()?;\n    into_ast.into_fragment()\n}\n\npub struct IntoAst<'a> {\n    pub framework: &'a Nnef,\n    pub parent: Option<&'a IntoAst<'a>>,\n    pub registries: Vec<Identifier>,\n    pub model: &'a TypedModel,\n    pub parameters: Vec<Identifier>,\n    pub results: Vec<Identifier>,\n    pub mapping: HashMap<OutletId, Arc<RValue>>,\n    pub tensors: HashMap<Identifier, Arc<Tensor>>,\n    pub quantization: HashMap<Identifier, QuantFormat>,\n    pub resources: HashMap<String, Arc<dyn Resource>>,\n    pub fragments: HashMap<Identifier, FragmentDef>,\n    pub body: Vec<Assignment>,\n}\n\npub struct RequiredTensorParameter {\n    pub parameter_id: Identifier,\n    pub label: Identifier,\n    pub value: Arc<Tensor>,\n}\n\nimpl<'a> IntoAst<'a> {\n    pub fn new(framework: &'a Nnef, model: &'a TypedModel) -> IntoAst<'a> {\n        IntoAst {\n            framework,\n            registries: Default::default(),\n            model,\n            parameters: Default::default(),\n            results: Default::default(),\n            mapping: Default::default(),\n            tensors: Default::default(),\n            quantization: Default::default(),\n            resources: Default::default(),\n            fragments: Default::default(),\n            body: Default::default(),\n            parent: None,\n        }\n    }\n\n    fn ensure_registry(&mut self, id: &Identifier) -> TractResult<()> {\n        if !self.framework.registries.iter().any(|r| &r.id == id) {\n            bail!(\"Registry {} required, consider allowing it on the NNEF framework.\", id.0);\n        }\n        if !self.registries.iter().any(|r| r == id) {\n            self.registries.push(id.clone());\n        }\n        Ok(())\n    }\n\n    fn translate(&mut self) -> TractResult<()> {\n        for input in self.model.input_outlets()? {\n            let left = self.scoped_id(&self.model.node(input.node).name);\n            self.parameters.push(left.clone());\n            self.node(self.model.node(input.node))?;\n            self.mapping.insert(*input, RValue::Identifier(left).into());\n        }\n        for node in self.model.eval_order()? {\n            if self.model.input_outlets()?.iter().any(|io| io.node == node) {\n                continue;\n            }\n            self.node(self.model.node(node))\n                .with_context(|| format!(\"translating node {}\", self.model.node(node)))?;\n        }\n        let outlets: Vec<OutletId> = self.model.output_outlets()?.to_vec();\n        for (ix, o) in outlets.into_iter().enumerate() {\n            let rv = if let Some(label) = self.model.outlet_label(o) {\n                self.force_variable_and_name(label, &self.mapping[&o].clone())\n            } else {\n                self.force_variable(format!(\"output_{ix}\"), &self.mapping[&o].clone())\n            };\n            if let RValue::Identifier(name) = rv.as_ref() {\n                self.results.push(name.clone());\n            } else {\n                unreachable!()\n            };\n        }\n        Ok(())\n    }\n\n    pub fn into_fragment(self) -> TractResult<(FragmentDef, Vec<RequiredTensorParameter>)> {\n        let mut tensor_params = vec![];\n        for (name, t) in &self.tensors {\n            tensor_params.push(RequiredTensorParameter {\n                parameter_id: self.scoped_id(name),\n                label: name.clone(),\n                value: t.clone(),\n            })\n        }\n        let IntoAst { body, mut parameters, results, .. } = self;\n        parameters.extend(tensor_params.iter().map(|rtp| rtp.parameter_id.clone()).sorted());\n        let body = body\n            .into_iter()\n            .filter(|assign| match &assign.left {\n                LValue::Identifier(id) => !parameters.contains(id),\n                _ => true,\n            })\n            .collect();\n        Ok((\n            FragmentDef {\n                decl: FragmentDecl {\n                    id: Identifier(\"network\".into()),\n                    generic_decl: None,\n                    parameters: parameters\n                        .into_iter()\n                        .map(|s| TypeName::Scalar.tensor().named(s))\n                        .collect(),\n                    results: results\n                        .into_iter()\n                        .map(|s| Result_ { id: s, spec: TypeName::Scalar.tensor() })\n                        .collect(),\n                },\n                body: Some(body),\n            },\n            tensor_params,\n        ))\n    }\n\n    pub fn into_proto_model(mut self) -> TractResult<ProtoModel> {\n        let mut properties = self\n            .model\n            .properties\n            .iter()\n            .sorted_by_key(|(k, _v)| k.to_owned())\n            .map(|(k, v)| Ok(tuple_2(string(k), self.konst(k, v)?.as_ref().clone())))\n            .collect::<TractResult<Vec<_>>>()?;\n        let version = env!(\"CARGO_PKG_VERSION\");\n        properties.push(tuple_2(\n            string(\"tract_nnef_ser_version\"),\n            self.konst(\"tract_nnef_ser_version\", &rctensor0(version.to_string()))?.as_ref().clone(),\n        ));\n        properties.push(tuple_2(\n            string(\"tract_nnef_format_version\"),\n            self.konst(\"tract_nnef_format_version\", &rctensor0(\"beta1\".to_string()))?\n                .as_ref()\n                .clone(),\n        ));\n        let properties: Assignment = assignment(\"properties\", Arc::new(array(properties)));\n        let IntoAst { mut fragments, body, tensors, parameters, results, .. } = self;\n        let mut extension = vec![];\n        self.registries.sort();\n        for reg in self.registries {\n            if reg.0 != \"tract_nnef\" {\n                extension.push((\"tract_registry\".into(), reg.0));\n            }\n        }\n        for sym in self.model.symbols.all_symbols() {\n            extension.push((\"tract_symbol\".into(), sym.to_string()));\n        }\n        let locked = self.model.symbols.0.lock();\n        for assert in locked.borrow().all_assertions() {\n            extension.push((\"tract_assert\".into(), assert.to_string()));\n        }\n        for scenario in locked.borrow().scenarios() {\n            for assert in locked.borrow().scenario(scenario) {\n                extension.push((\"tract_assert\".into(), format!(\"{scenario}: {assert}\")));\n            }\n        }\n        let properties = FragmentDef {\n            decl: FragmentDecl {\n                id: Identifier(\"tract_core_properties\".to_string()),\n                generic_decl: None,\n                parameters: vec![],\n                results: vec![Result_ {\n                    id: Identifier(\"properties\".to_string()),\n                    spec: TypeSpec::Tuple(vec![TypeName::String.spec(), TypeName::Scalar.tensor()])\n                        .array(),\n                }],\n            },\n            body: Some(vec![properties]),\n        };\n        fragments.insert(properties.decl.id.clone(), properties);\n        let doc = Document {\n            version: \"1.0\".into(),\n            extension,\n            fragments: fragments.into_values().collect(),\n            graph_def: GraphDef { id: Identifier(\"network\".into()), parameters, results, body },\n        };\n        let quantization = if self.quantization.len() > 0 { Some(self.quantization) } else { None };\n        Ok(ProtoModel { doc, tensors, quantization, resources: self.resources })\n    }\n\n    fn node(&mut self, node: &TypedNode) -> TractResult<TVec<Arc<RValue>>> {\n        let mut required_registries = Vec::new();\n        for reg in &self.framework.registries {\n            if let Some(outputs) = reg.serialize(self, node).context(\"Serializing op\")? {\n                if self.ensure_registry(&reg.id).is_err() {\n                    required_registries.push(&reg.id);\n                    continue;\n                };\n                let scoped = self.scoped_id(&node.name);\n                let names: Vec<_> = (0..node.outputs.len())\n                    .map(|ix| {\n                        if ix > 0 {\n                            Identifier(format!(\"{}_{}\", scoped.0, ix))\n                        } else {\n                            scoped.clone()\n                        }\n                    })\n                    .collect();\n                if node.outputs.len() > 1 {\n                    self.body.push(Assignment {\n                        left: LValue::Tuple(\n                            names.iter().map(|n| LValue::Identifier(n.clone())).collect(),\n                        ),\n                        right: outputs.as_ref().clone(),\n                    });\n                } else {\n                    self.assignment(names[0].clone(), outputs);\n                };\n\n                for (outlet, name) in node.outputs.iter().zip(names.iter()) {\n                    if let Some(qf) = QuantFormat::from_dt(outlet.fact.datum_type) {\n                        self.quantization.insert(name.clone(), qf);\n                    }\n                }\n\n                let mut outputs = tvec!();\n                for (ix, o) in names.into_iter().enumerate() {\n                    let rv = Arc::new(ident(o));\n                    self.mapping.insert((node.id, ix).into(), rv.clone());\n                    outputs.push(rv);\n                }\n\n                return Ok(outputs);\n            }\n        }\n        if required_registries.is_empty() {\n            bail!(\"No serializer found for node {}\", node);\n        } else if required_registries.len() == 1 {\n            bail!(\n                \"Registry {} required, consider allowing it on the NNEF framework.\",\n                required_registries[0].0\n            );\n        } else {\n            bail!(\n                \"One of the following registries is required: {:?}, consider allowing one on the NNEF framework.\",\n                required_registries\n            );\n        }\n    }\n\n    pub fn scoped_id(&self, name: impl AsRef<str>) -> Identifier {\n        let name = name.as_ref().to_string();\n        Identifier(name)\n    }\n\n    pub fn force_variable(&mut self, name: impl AsRef<str>, exp: &Arc<RValue>) -> Arc<RValue> {\n        if let RValue::Identifier(_) = exp.as_ref() {\n            exp.clone()\n        } else {\n            let name = self.scoped_id(name);\n            self.assignment(name.clone(), exp.clone());\n            ident(name).into()\n        }\n    }\n\n    pub fn force_variable_and_name(\n        &mut self,\n        name: impl Into<String>,\n        exp: &Arc<RValue>,\n    ) -> Arc<RValue> {\n        let name = name.into();\n        if let RValue::Identifier(id) = exp.as_ref()\n            && name == id.0\n        {\n            return exp.clone();\n        }\n        let name = self.scoped_id(name);\n        self.assignment(name.clone(), exp.clone());\n        ident(name).into()\n    }\n\n    pub fn konst(\n        &mut self,\n        name: impl AsRef<str>,\n        tensor: &Arc<Tensor>,\n    ) -> TractResult<Arc<RValue>> {\n        self.do_konst(name, tensor, false)\n    }\n\n    pub fn konst_variable(\n        &mut self,\n        name: impl AsRef<str>,\n        tensor: &Arc<Tensor>,\n    ) -> TractResult<Arc<RValue>> {\n        self.do_konst(name, tensor, true)\n    }\n\n    fn dump_rec_tensor<T: Datum>(\n        t: &ArrayViewD<T>,\n        el: impl for<'t> Fn(&'t T) -> RValue + Copy,\n    ) -> RValue {\n        if t.ndim() == 0 {\n            el(&t.as_slice().unwrap()[0])\n        } else {\n            let values: TVec<RValue> = (0..t.shape()[0])\n                .map(|i| Self::dump_rec_tensor(&t.index_axis(Axis(0), i), el))\n                .collect();\n            array(values)\n        }\n    }\n\n    fn do_konst(\n        &mut self,\n        name: impl AsRef<str>,\n        tensor: &Arc<Tensor>,\n        force_variable: bool,\n    ) -> TractResult<Arc<RValue>> {\n        let mut name: Identifier = name.as_ref().into();\n        let have_tract_core = self.ensure_registry(&\"tract_core\".into()).is_ok();\n        if tensor.datum_type() == TDim::datum_type() {\n            return Ok(Self::dump_rec_tensor(&tensor.to_plain_array_view::<TDim>()?, tdim).into());\n        }\n        if !force_variable\n            && !self.framework.extern_all_constants\n            && tensor.len() <= 8\n            && tensor.len() > 0\n        {\n            if tensor.datum_type() == String::datum_type() {\n                return Ok(Self::dump_rec_tensor(&tensor.to_plain_array_view::<String>()?, |f| {\n                    string(f)\n                })\n                .into());\n            } else if tensor.datum_type() == DatumType::F32 {\n                return Ok(Self::dump_rec_tensor(&tensor.to_plain_array_view::<f32>()?, |f| {\n                    numeric(f)\n                })\n                .into());\n            } else if have_tract_core && tensor.datum_type() == DatumType::F16 {\n                let array =\n                    Self::dump_rec_tensor(&tensor.to_plain_array_view::<f16>()?, |f| numeric(f))\n                        .into();\n                return Ok(invocation(\"tract_core_cast\", &[array], &[(\"to\", string(\"f16\"))]));\n            } else if have_tract_core\n                && tensor.datum_type().is_integer()\n                && let Ok(value) = tensor.cast_to::<i64>()\n            {\n                let value =\n                    Self::dump_rec_tensor(&value.to_plain_array_view::<i64>().unwrap(), |i| {\n                        numeric(i)\n                    });\n                let to = string(format!(\"{:?}\", tensor.datum_type()).to_lowercase());\n                return Ok(invocation(\"tract_core_cast\", &[value.into()], &[(\"to\", to)]));\n            };\n        }\n\n        if self.tensors.contains_key(&name) {\n            name = (0..)\n                .map(|it| Identifier::from(&*format!(\"{}_{}\", name.0, it)))\n                .find(|it| !self.tensors.contains_key(it))\n                .unwrap();\n        }\n\n        self.tensors.insert(name.clone(), tensor.clone());\n        let id = self.scoped_id(&name);\n        let shape = tensor.shape().to_vec();\n        self.assignment(\n            id.clone(),\n            RValue::Invocation(Invocation {\n                id: \"variable\".into(),\n                generic_type_name: Some(TypeName::Scalar),\n                arguments: vec![\n                    named_arg(\"label\", string(name.0)),\n                    named_arg(\"shape\", ints(&shape)),\n                ],\n            })\n            .into(),\n        );\n        if let Some(qp) = QuantFormat::from_dt(tensor.datum_type()) {\n            self.quantization.insert(id.clone(), qp);\n        }\n        Ok(ident(id).into())\n    }\n\n    fn assignment(&mut self, name: impl AsRef<str>, right: Arc<RValue>) {\n        let name = name.as_ref();\n        if *right == ident(name) {\n            return;\n        }\n        self.body.push(assignment(name, right))\n    }\n}\n\npub fn assignment(name: impl AsRef<str>, right: Arc<RValue>) -> Assignment {\n    Assignment { left: LValue::Identifier(name.as_ref().into()), right: right.as_ref().to_owned() }\n}\n\npub fn ints(shape: &[usize]) -> RValue {\n    RValue::Array(shape.iter().map(|s| RValue::Literal(Literal::Numeric(s.to_string()))).collect())\n}\n\npub fn tdims(shape: &[TDim]) -> RValue {\n    RValue::Array(shape.iter().map(tdim).collect())\n}\n\npub fn tdim(dim: &TDim) -> RValue {\n    match dim {\n        TDim::Val(x) => numeric(x),\n        TDim::Sym(s) => ident(s.to_string()),\n        TDim::Add(terms) => terms\n            .iter()\n            .map(tdim)\n            .reduce(|x, y| RValue::Binary(x.boxed(), \"+\".to_string(), y.boxed()))\n            .unwrap(),\n        TDim::Mul(terms) => terms\n            .iter()\n            .map(tdim)\n            .reduce(|x, y| RValue::Binary(x.boxed(), \"*\".to_string(), y.boxed()))\n            .unwrap(),\n        TDim::MulInt(x, y) => RValue::Binary(numeric(x).boxed(), \"*\".to_string(), tdim(y).boxed()),\n        TDim::Div(x, y) => RValue::Binary(tdim(x).boxed(), \"/\".to_string(), numeric(y).boxed()),\n        TDim::Broadcast(_) => todo!(),\n        TDim::Min(_) | TDim::Max(_) => todo!(),\n        TDim::Ge(_, _) | TDim::Eq(_, _) => {\n            panic!(\"Comparison/boolean TDim variants are transient and cannot be serialized\")\n        }\n    }\n}\n\npub fn string(s: impl AsRef<str>) -> RValue {\n    RValue::Literal(Literal::String(s.as_ref().into()))\n}\n\npub fn datum_type(dt: DatumType) -> RValue {\n    string(format!(\"{:?}\", dt.unquantized()).to_lowercase())\n}\n\npub fn logical(b: bool) -> RValue {\n    RValue::Literal(Literal::Logical(b))\n}\n\npub fn lident(s: impl AsRef<str>) -> LValue {\n    LValue::Identifier(s.as_ref().into())\n}\n\npub fn ident(s: impl AsRef<str>) -> RValue {\n    RValue::Identifier(s.as_ref().into())\n}\n\npub fn array(items: impl AsRef<[RValue]>) -> RValue {\n    RValue::Array(items.as_ref().to_vec())\n}\n\npub fn tuple_2(a: RValue, b: RValue) -> RValue {\n    RValue::Tuple(vec![a, b])\n}\n\npub fn tuple_3(a: RValue, b: RValue, c: RValue) -> RValue {\n    RValue::Tuple(vec![a, b, c])\n}\n\npub fn tuple_4(a: RValue, b: RValue, c: RValue, d: RValue) -> RValue {\n    RValue::Tuple(vec![a, b, c, d])\n}\n\npub fn numeric<D: std::fmt::Debug>(num: D) -> RValue {\n    RValue::Literal(Literal::Numeric(format!(\"{num:?}\")))\n}\n\npub fn named_arg(id: &str, rv: RValue) -> Argument {\n    Argument { id: Some(id.into()), rvalue: rv }\n}\n\npub fn invocation(\n    id: impl AsRef<str>,\n    positional: &[Arc<RValue>],\n    named: &[(&str, RValue)],\n) -> Arc<RValue> {\n    let arguments = positional\n        .iter()\n        .map(|rv| Argument { id: None, rvalue: rv.as_ref().clone() })\n        .chain(named.iter().map(|(n, v)| named_arg(n, v.clone())))\n        .collect();\n    RValue::Invocation(Invocation { id: id.as_ref().into(), generic_type_name: None, arguments })\n        .into()\n}\n"
  },
  {
    "path": "nnef/src/tensors.rs",
    "content": "use std::io::{Read, Write};\n\nuse byteorder::{LE, ReadBytesExt, WriteBytesExt};\nuse tract_core::internal::*;\nuse tract_core::tract_linalg::block_quant::Q8_1;\nuse tract_linalg::block_quant::{BlockQuant, BlockQuantStorage, Q4_0};\n\nconst TRACT_ITEM_TYPE_VENDOR: u16 = ((b'T' as u16) << 8u16) | b'R' as u16;\n\n#[repr(C)]\n#[derive(Debug)]\nstruct Header {\n    magic: [u8; 2],\n    version_maj: u8,\n    version_min: u8,\n    data_size_bytes: u32,\n    rank: u32,\n    dims: [u32; 8],\n    bits_per_item: u32,\n    item_type: u16,\n    item_type_vendor: u16,\n    item_type_params_deprecated: [u8; 32],\n    padding: [u32; 11],\n}\n\nimpl Default for Header {\n    fn default() -> Self {\n        let mut header: Header = unsafe { std::mem::zeroed() };\n        header.magic = [0x4e, 0xef];\n        header.version_maj = 1;\n        header.version_min = 0;\n        header\n    }\n}\n\nimpl Header {\n    pub fn write(&self, w: &mut impl Write) -> TractResult<()> {\n        let slice = unsafe { std::mem::transmute::<&Header, &[u8; 128]>(self) };\n        w.write_all(slice)?;\n        Ok(())\n    }\n\n    pub fn read(reader: &mut impl Read) -> TractResult<Header> {\n        let mut header: Header = unsafe { std::mem::zeroed() };\n        reader.read_exact(unsafe {\n            std::mem::transmute::<&mut Header, &mut [u8; 128]>(&mut header)\n        })?;\n        if header.magic != [0x4e, 0xef] {\n            bail!(\"Wrong magic number\");\n        };\n        if header.version_maj != 1 && header.version_min != 0 {\n            bail!(\"Wrong version number\");\n        }\n        if header.rank > 8 {\n            bail!(\"Wrong tensor rank {}\", header.rank);\n        }\n        Ok(header)\n    }\n}\n\npub fn read_tensor(mut reader: impl Read) -> TractResult<Tensor> {\n    let header = Header::read(&mut reader)?;\n    let shape: TVec<usize> = header.dims[0..header.rank as usize].iter().map(|d| *d as _).collect();\n    let len = shape.iter().product::<usize>();\n\n    if header.item_type == 5 {\n        let expected_bit_size = len * header.bits_per_item as usize;\n        let real_bit_size = header.data_size_bytes as usize * 8;\n        if !(real_bit_size - 8 <= expected_bit_size && expected_bit_size <= real_bit_size) {\n            bail!(\n                \"Shape and len mismatch: shape:{:?}, bits_per_item:{}, bytes:{} \",\n                shape,\n                header.bits_per_item,\n                header.data_size_bytes\n            );\n        }\n    } else if header.bits_per_item != u32::MAX\n        && len * (header.bits_per_item as usize / 8) != header.data_size_bytes as usize\n    {\n        bail!(\n            \"Shape and len mismatch: shape:{:?}, bits_per_item:{}, bytes:{} \",\n            shape,\n            header.bits_per_item,\n            header.data_size_bytes\n        );\n    }\n    if header.item_type_vendor != 0 && header.item_type_vendor != TRACT_ITEM_TYPE_VENDOR {\n        bail!(\"Unknownn item type vendor {}\", header.item_type_vendor);\n    }\n\n    // last checked with spec 1.0.5: https://registry.khronos.org/NNEF/specs/1.0/nnef-1.0.5.html\n    //\n    // Quantized types are not instanciated as DatumType::Q* here since\n    // quant infos are joined later from .quant file (\n    //  see: ops/nnef/deser.rs\n    // )\n    let dt = match (header.item_type_vendor, header.item_type, header.bits_per_item) {\n        // 0 - 0b0000 - float values in IEEE format, valid bits per item is 16, 32, 64\n        (0, 0, 16) => DatumType::F16,\n        (0, 0, 32) => DatumType::F32,\n        (0, 0, 64) => DatumType::F64,\n\n        // 1 - 0b0001 - unsigned integer values, maximum bits per item is 64.\n        (0, 1, 8) => DatumType::U8,\n        (0, 1, 16) => DatumType::U16,\n        (0, 1, 32) => DatumType::U32,\n        (0, 1, 64) => DatumType::U64,\n\n        // 2 - 0b0010 - quantized unsigned integer values, maximum bits per item is 64.\n        (0, 2, 8) => DatumType::U8,\n        (0, 2, 16) => DatumType::U16,\n        (0, 2, 32) => DatumType::U32,\n        (0, 2, 64) => DatumType::U64,\n\n        // 3 - 0b0011 - quantized signed integer values, maximum bits per item is 64.\n        (0, 3, 8) => DatumType::I8,\n        (0, 3, 16) => DatumType::I16,\n        (0, 3, 32) => DatumType::I32,\n        (0, 3, 64) => DatumType::I64,\n\n        // 4 - 0b0100 - signed integer values, maximum bits per item is 64.\n        (0, 4, 8) => DatumType::I8,\n        (0, 4, 16) => DatumType::I16,\n        (0, 4, 32) => DatumType::I32,\n        (0, 4, 64) => DatumType::I64,\n\n        // 5 - 0b0101 - bool values, 1 bit or 8 bits (0 means false, non-zero means true)\n        (0, 5, 1 | 8) => DatumType::Bool,\n        (TRACT_ITEM_TYPE_VENDOR, 0x1000, _) => DatumType::String,\n        #[cfg(feature = \"complex\")]\n        (TRACT_ITEM_TYPE_VENDOR, 0, 32) => DatumType::ComplexF16,\n        #[cfg(feature = \"complex\")]\n        (TRACT_ITEM_TYPE_VENDOR, 0, 64) => DatumType::ComplexF32,\n        #[cfg(feature = \"complex\")]\n        (TRACT_ITEM_TYPE_VENDOR, 0, 128) => DatumType::ComplexF64,\n        #[cfg(feature = \"complex\")]\n        (TRACT_ITEM_TYPE_VENDOR, 4, 32) => DatumType::ComplexI16,\n        #[cfg(feature = \"complex\")]\n        (TRACT_ITEM_TYPE_VENDOR, 4, 64) => DatumType::ComplexI32,\n        #[cfg(feature = \"complex\")]\n        (TRACT_ITEM_TYPE_VENDOR, 4, 128) => DatumType::ComplexI64,\n        (TRACT_ITEM_TYPE_VENDOR, it, _)\n            if ((it & 0x2000) == 0x2000) || ((it & 0x3000) == 0x3000) =>\n        {\n            return read_block_quant_value(&mut reader, &header);\n        }\n        _ => bail!(\n            \"Unsupported type in tensor type:{} bits_per_item:{}\",\n            header.item_type,\n            header.bits_per_item\n        ),\n    };\n    if dt.is_copy() {\n        let mut tensor = unsafe { Tensor::uninitialized_dt(dt, &shape)? };\n        let mut plain = tensor.try_as_plain_mut()?;\n        if dt == DatumType::Bool && header.bits_per_item == 1 {\n            let buf = plain.as_slice_mut::<bool>()?;\n\n            let mut current_byte = 0;\n            for (ix, value) in buf.iter_mut().enumerate() {\n                let bit_ix = ix % 8;\n                if bit_ix == 0 {\n                    current_byte = reader.read_u8()?;\n                }\n                *value = ((current_byte >> (7 - bit_ix)) & 0x1) != 0;\n            }\n        } else {\n            reader.read_exact(plain.as_bytes_mut())?;\n        }\n        Ok(tensor)\n    } else if dt == DatumType::String {\n        let mut tensor = Tensor::zero_dt(dt, &shape)?;\n        let mut plain = tensor.try_as_plain_mut()?;\n        for item in plain.as_slice_mut::<String>()? {\n            let len: u32 = reader.read_u32::<LE>()?;\n            let mut bytes = Vec::with_capacity(len as usize);\n            #[allow(clippy::uninit_vec)]\n            unsafe {\n                bytes.set_len(len as usize);\n            };\n            reader.read_exact(&mut bytes)?;\n            *item = String::from_utf8(bytes)?;\n        }\n        Ok(tensor)\n    } else {\n        todo!()\n    }\n}\n\npub fn write_tensor(w: &mut impl Write, tensor: &Tensor) -> TractResult<()> {\n    ensure!(tensor.datum_type() != TDim::datum_type());\n    if tensor.storage_as::<BlockQuantStorage>().is_some() {\n        return write_block_quant_value(w, tensor);\n    }\n    let plain = tensor.try_as_plain()?;\n    let mut header = Header::default();\n    if tensor.rank() > 8 {\n        bail!(\"Only rank up to 8 are supported\");\n    }\n    header.rank = tensor.rank() as u32;\n    for d in 0..tensor.rank() {\n        header.dims[d] = tensor.shape()[d] as u32;\n    }\n    header.data_size_bytes = (tensor.len() * tensor.datum_type().size_of()) as u32;\n    header.bits_per_item = (tensor.datum_type().size_of() * 8) as u32;\n\n    let (itv, it) = match tensor.datum_type() {\n        DatumType::F16 | DatumType::F32 | DatumType::F64 => (0, 0),\n        DatumType::U8 | DatumType::U16 | DatumType::U32 | DatumType::U64 | DatumType::QU8(_) => {\n            (0, 2)\n        }\n        DatumType::I8\n        | DatumType::I16\n        | DatumType::I32\n        | DatumType::I64\n        | DatumType::QI8(_)\n        | DatumType::QI32(_) => (0, 3),\n        DatumType::String => {\n            header.bits_per_item = u32::MAX;\n            (TRACT_ITEM_TYPE_VENDOR, 0x1000)\n        }\n        #[cfg(feature = \"complex\")]\n        DatumType::ComplexF16 | DatumType::ComplexF32 | DatumType::ComplexF64 => {\n            (TRACT_ITEM_TYPE_VENDOR, 0)\n        }\n        #[cfg(feature = \"complex\")]\n        DatumType::ComplexI16 | DatumType::ComplexI32 | DatumType::ComplexI64 => {\n            (TRACT_ITEM_TYPE_VENDOR, 4)\n        }\n        DatumType::Bool => (0, 5),\n        DatumType::TDim | DatumType::Blob => {\n            bail!(\"Don't know how to serialize {:?}\", tensor.datum_type())\n        }\n    };\n    header.item_type = it;\n    header.item_type_vendor = itv;\n    header.write(w)?;\n    if tensor.datum_type().is_copy() {\n        w.write_all(plain.as_bytes())?;\n    } else if tensor.datum_type() == DatumType::String {\n        for s in plain.as_slice::<String>()? {\n            w.write_u32::<LE>(s.len() as u32)?;\n            w.write_all(s.as_bytes())?;\n        }\n    }\n    Ok(())\n}\n\npub fn tract_to_gguf_q4_0_packing(data: &mut Blob) -> TractResult<()> {\n    let block_size = Q4_0.block_bytes();\n    ensure!(data.layout().size() % block_size == 0);\n\n    let n_block = data.layout().size() / block_size;\n    let data_bytes = data.as_bytes_mut();\n\n    for b in 0..n_block {\n        let offset = b * block_size + 2;\n        let nibbles = &mut data_bytes[offset..offset + 16];\n        let second_part: &mut [u8; 8] = &mut [0; 8];\n        second_part.clone_from_slice(&nibbles[8..]);\n        for i in (0..16).rev() {\n            let lsb = if i % 2 == 0 { nibbles[i / 2] & 0x0F } else { (nibbles[i / 2] & 0xF0) >> 4 };\n            let msb = if i % 2 == 0 {\n                (second_part[i / 2] & 0x0F) << 4\n            } else {\n                second_part[i / 2] & 0xF0\n            };\n            nibbles[i] = msb | lsb;\n        }\n    }\n    Ok(())\n}\n\nfn read_block_quant_value(r: &mut impl Read, header: &Header) -> TractResult<Tensor> {\n    let format: Box<dyn BlockQuant> = match header.item_type {\n        0x2040 | 0x3040 => Box::new(Q4_0),\n        0x3080 => Box::new(Q8_1),\n        _ => bail!(\"Unexpected block quant format\"),\n    };\n    ensure!(header.rank >= 2);\n    let shape: TVec<_> =\n        header.dims.iter().take(header.rank as usize).map(|d| *d as usize).collect();\n    let q_m = shape[0];\n    let q_k = shape.iter().skip(1).product::<usize>();\n    ensure!(q_k % format.block_len() == 0);\n    let expected_len = (q_m * q_k) / format.block_len() * format.block_bytes();\n    ensure!(expected_len == header.data_size_bytes as usize);\n    let mut blob = unsafe { Blob::new_for_size_and_align(expected_len, 128) };\n    r.read_exact(&mut blob)?;\n    if header.item_type == 0x2040 {\n        tract_to_gguf_q4_0_packing(&mut blob)?;\n    }\n    let tensor = BlockQuantStorage::new(format, q_m, q_k, Arc::new(blob))?\n        .into_tensor_with_shape(f32::datum_type(), &shape);\n    Ok(tensor)\n}\n\n#[allow(clippy::field_reassign_with_default)]\nfn write_block_quant_value(w: &mut impl Write, tensor: &Tensor) -> TractResult<()> {\n    let bqs = tensor.try_storage_as::<BlockQuantStorage>()?;\n    let format = bqs.format();\n    ensure!(format.dyn_eq(&Q4_0) || format.dyn_eq(&Q8_1));\n    let s = tensor.shape();\n    let flat_shape: [usize; 2] = [s[..s.len() - 1].iter().product(), *s.last().unwrap()];\n\n    let mut header = Header::default();\n    header.rank = flat_shape.len() as u32;\n    for (h, v) in header.dims.iter_mut().zip(flat_shape.iter()) {\n        *h = *v as u32;\n    }\n    header.bits_per_item = u32::MAX;\n    header.data_size_bytes = bqs.value().len() as _;\n    header.item_type_vendor = TRACT_ITEM_TYPE_VENDOR;\n    // 0x3040 3 is for GGML formats, 0 for Q formats then 4 and 0\n    header.item_type = if format.dyn_eq(&Q4_0) { 0x3040 } else { 0x3081 };\n    header.write(w)?;\n    w.write_all(bqs.value())?;\n    Ok(())\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn header_is_128_bytes() {\n        assert_eq!(std::mem::size_of::<Header>(), 128);\n    }\n\n    #[test]\n    #[cfg(feature = \"complex\")]\n    fn serde_tensor_complex_f32() -> TractResult<()> {\n        let t = tensor2(&[\n            [Complex::new(1.0f32, 2.0), Complex::new(2.0, 1.0), Complex::new(3.5, 2.4)],\n            [Complex::new(3.0, 4.5), Complex::new(3.0, 2.5), Complex::new(1.5, 2.5)],\n        ]);\n        let mut buffer = Vec::<u8>::new();\n        write_tensor(&mut buffer, &t)?;\n        let serde_tensor = read_tensor(buffer.as_slice())?;\n        assert_eq!(t, serde_tensor);\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"complex\")]\n    fn serde_tensor_complex_f64() -> TractResult<()> {\n        let t = tensor2(&[\n            [Complex::new(1.0f64, 2.0), Complex::new(2.0, 1.0), Complex::new(3.5, 2.4)],\n            [Complex::new(3.0, 4.5), Complex::new(3.0, 2.5), Complex::new(1.5, 2.5)],\n        ]);\n        let mut buffer = Vec::<u8>::new();\n        write_tensor(&mut buffer, &t)?;\n        let serde_tensor = read_tensor(buffer.as_slice())?;\n        assert_eq!(t, serde_tensor);\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"complex\")]\n    fn serde_tensor_complex_i32() -> TractResult<()> {\n        let t = tensor2(&[\n            [Complex::new(1i32, 2), Complex::new(2, 1), Complex::new(3, 2)],\n            [Complex::new(3, 4), Complex::new(3, 2), Complex::new(1, 2)],\n        ]);\n        let mut buffer = Vec::<u8>::new();\n        write_tensor(&mut buffer, &t)?;\n        let serde_tensor = read_tensor(buffer.as_slice())?;\n        assert_eq!(t, serde_tensor);\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"complex\")]\n    fn serde_tensor_complex_i64() -> TractResult<()> {\n        let t = tensor2(&[\n            [Complex::new(1i64, 2), Complex::new(2, 1), Complex::new(3, 2)],\n            [Complex::new(3, 4), Complex::new(3, 2), Complex::new(1, 2)],\n        ]);\n        let mut buffer = Vec::<u8>::new();\n        write_tensor(&mut buffer, &t)?;\n        let serde_tensor = read_tensor(buffer.as_slice())?;\n        assert_eq!(t, serde_tensor);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "nnef/src/transform.rs",
    "content": "use crate::ast::parse::parse_assignments;\nuse crate::ast::*;\nuse crate::deser::{ModelBuilder, Value};\nuse tract_core::internal::*;\nuse tract_core::model::TypedModelPatch;\nuse tract_core::transform::{ModelTransform, ModelTransformFactory};\n\n#[derive(Debug, serde::Deserialize)]\nstruct PatchConfig {\n    body: String,\n    #[serde(default)]\n    new_outputs: Vec<String>,\n}\n\n#[derive(Debug)]\nstruct PatchTransform(PatchConfig);\n\nimpl ModelTransform for PatchTransform {\n    fn name(&self) -> StaticName {\n        \"patch\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        let assignments = parse_assignments(&self.0.body)\n            .with_context(|| format!(\"Parsing wire statement: {:?}\", self.0.body))?;\n\n        let lhs_names: Vec<String> = assignments\n            .iter()\n            .filter_map(|a| match &a.left {\n                LValue::Identifier(id) => Some(id.0.clone()),\n                _ => None,\n            })\n            .collect();\n\n        // Run the builder in a block so it (and its borrow of model) is dropped before we mutate model\n        let (patch_model, taps, scope) = {\n            let framework = crate::nnef().with_tract_core();\n\n            let doc = Document {\n                version: \"1.0\".into(),\n                extension: vec![],\n                fragments: vec![],\n                graph_def: GraphDef {\n                    id: Identifier(\"patch\".into()),\n                    parameters: vec![],\n                    results: vec![],\n                    body: vec![],\n                },\n            };\n            let proto_model = ProtoModel {\n                doc,\n                tensors: Default::default(),\n                quantization: None,\n                resources: Default::default(),\n            };\n\n            let model_ref: &TypedModel = &*model;\n            let mut taps = HashMap::<OutletId, OutletId>::default();\n\n            let template = TypedModel { symbols: model.symbols.clone(), ..TypedModel::default() };\n            let mut builder = ModelBuilder::new(&framework, &proto_model, template);\n            builder.registries.push(\"tract_core\".into());\n            builder.scopes.push(HashMap::default());\n\n            let taps_ref = &mut taps;\n            builder.wire_resolver =\n                Some(Box::new(move |name: &str, patch_model: &mut TypedModel| {\n                    let Some(node_id) =\n                        model_ref.nodes.iter().find(|n| n.name == name).map(|n| n.id)\n                    else {\n                        return Ok(None);\n                    };\n                    let original_outlet = OutletId::new(node_id, 0);\n                    let fact = model_ref.outlet_fact(original_outlet)?.clone();\n                    let patch_outlet = patch_model.add_source(name, fact)?;\n                    taps_ref.insert(patch_outlet, original_outlet);\n                    Ok(Some(patch_outlet))\n                }));\n\n            builder.wire_body(&assignments).with_context(|| \"Executing wire statements\")?;\n\n            let scope = builder.scopes.last().unwrap().clone();\n            builder.wire_resolver.take();\n            let patch_model = std::mem::take(&mut builder.model);\n            drop(builder);\n            (patch_model, taps, scope)\n        };\n\n        // Build the TypedModelPatch\n        let mut patch = TypedModelPatch { model: patch_model, taps, ..TypedModelPatch::default() };\n\n        let mut inputs_to_remove = vec![];\n        let mut new_output_names = vec![];\n\n        for (i, lhs_name) in lhs_names.iter().enumerate() {\n            let patch_outlet = match scope.get(&Identifier(lhs_name.clone())) {\n                Some(Value::Wire(o)) => *o,\n                _ => continue,\n            };\n\n            let is_model_input =\n                model.inputs.iter().find(|&&inp| model.node(inp.node).name == *lhs_name).copied();\n\n            if let Some(input_outlet) = is_model_input {\n                let expected_fact = model.outlet_fact(input_outlet)?;\n                let mut wire = patch_outlet;\n                let patch_fact = patch.model.outlet_fact(wire)?.clone();\n                // Cast dtype if needed (e.g. TDim → I64)\n                if patch_fact.datum_type != expected_fact.datum_type {\n                    wire = patch.model.wire_node(\n                        format!(\"{}_cast\", lhs_name),\n                        tract_core::ops::cast::cast(expected_fact.datum_type),\n                        &[wire],\n                    )?[0];\n                }\n                // Reshape if needed (e.g. scalar → [1])\n                let wire_fact = patch.model.outlet_fact(wire)?.clone();\n                if wire_fact.shape != expected_fact.shape {\n                    wire = patch.model.wire_node(\n                        format!(\"{}_reshape\", lhs_name),\n                        tract_core::ops::change_axes::AxisOp::Reshape(\n                            0,\n                            wire_fact.shape.to_tvec(),\n                            expected_fact.shape.to_tvec(),\n                        ),\n                        &[wire],\n                    )?[0];\n                }\n                patch.shunt_outside(model, input_outlet, wire)?;\n                inputs_to_remove.push(input_outlet);\n            } else if self.0.new_outputs.contains(lhs_name) {\n                new_output_names.push(lhs_name.clone());\n            } else {\n                let is_intermediate = i < lhs_names.len() - 1;\n                if !is_intermediate {\n                    bail!(\n                        \"Wire '{}' is not a model input and not declared in new_outputs\",\n                        lhs_name\n                    );\n                }\n            }\n        }\n\n        patch.apply(model)?;\n\n        for inp in &inputs_to_remove {\n            model.inputs.retain(|o| o != inp);\n        }\n        for name in &new_output_names {\n            let node_id = model.node_id_by_name(name)?;\n            model.outputs.push(OutletId::new(node_id, 0));\n        }\n\n        model.refresh_output_facts()?;\n        Ok(())\n    }\n}\n\ninventory::submit! {\n    ModelTransformFactory {\n        name: \"patch\",\n        build_default: || {\n            bail!(\"patch transform requires a 'body' parameter, e.g. patch(body: \\\"x = some_op(y);\\\")\")\n        },\n        build: |de| {\n            let config: PatchConfig = erased_serde::deserialize(de)\n                .map_err(|e| anyhow!(\"deserializing patch config: {e}\"))?;\n            Ok(Box::new(PatchTransform(config)))\n        },\n    }\n}\n"
  },
  {
    "path": "nnef/stdlib.nnef",
    "content": "# Copyright (c) 2017 The Khronos Group Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\n# tensor declaration operations\n\nfragment external<? = scalar>( shape: integer[] ) -> ( output: tensor<?> );\nfragment variable<? = scalar>( shape: integer[], label: string ) -> ( output: tensor<?> );\nfragment constant<? = scalar>( shape: integer[], value: ?[] ) ->  ( output: tensor<?> );\n\nfragment update<?>( variable: tensor<?>, value: tensor<?> ) -> ( result: tensor<?> );\n\n\n# tensor shape operations\n\nfragment reshape<?>( input: tensor<?>, shape: integer[], axis_start: integer = 0, axis_count: integer = -1 ) -> ( output: tensor<?> );\nfragment transpose<?>( input: tensor<?>, axes: integer[] ) -> ( output: tensor<?> );\nfragment concat<?>( values: tensor<?>[], axis: integer ) -> ( value: tensor<?> );\nfragment split<?>( value: tensor<?>, axis: integer, ratios: integer[] ) -> ( values: tensor<?>[] );\nfragment slice<?>( input: tensor<?>, axes: integer[], begin: integer[], end: integer[] ) -> ( output: tensor<?> );\nfragment squeeze<?>( input: tensor<?>, axes: integer[] ) -> ( output: tensor<?> );\nfragment unsqueeze<?>( input: tensor<?>, axes: integer[] ) -> ( output: tensor<?> );\nfragment stack<?>( values: tensor<?>[], axis: integer ) -> ( value: tensor<?> );\nfragment unstack<?>( value: tensor<?>, axis: integer ) -> ( values: tensor<?>[] );\nfragment tile<?>( input: tensor<?>, repeats: integer[] ) -> ( output: tensor<?> );\nfragment pad( input: tensor<scalar>, padding: (integer, integer)[], border: string = 'constant', value: scalar = 0.0 ) -> ( output: tensor<scalar> );\n\n\n# element-wise arithmetic operations\n\nfragment add( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<scalar> );\nfragment sub( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<scalar> );\nfragment mul( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<scalar> );\nfragment div( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<scalar> );\nfragment pow( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<scalar> );\n\nfragment exp( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment log( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment sin( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment cos( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment abs( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment sign( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment rcp( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment neg( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment copy<?>( x: tensor<?> ) -> ( y: tensor<?> );\n\n# element-wise comparison operations\n\nfragment lt( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> );\nfragment gt( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> );\nfragment le( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> );\nfragment ge( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> );\nfragment eq( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> );\nfragment ne( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<logical> );\n\n# element-wise logical operations\n\nfragment and( x: tensor<logical>, y: tensor<logical> ) -> ( z: tensor<logical> );\nfragment or( x: tensor<logical>, y: tensor<logical> ) -> ( z: tensor<logical> );\nfragment not( x: tensor<logical> ) -> ( y: tensor<logical> );\n\n# element-wise rounding operations\n\nfragment floor( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment ceil( x: tensor<scalar> ) -> ( y: tensor<scalar> );\nfragment round( x: tensor<scalar> ) -> ( y: tensor<scalar> );\n\n# element-wise select operation\n\nfragment select<?>( condition: tensor<logical>, true_value: tensor<?>, false_value: tensor<?> ) -> ( output: tensor<?> );\n\n# simplifier operations\n\nfragment sqr( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = x ^ 2.0;\n}\n\nfragment sqrt( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = x ^ 0.5;\n}\n\nfragment rsqr( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = x ^ -2.0;\n}\n\nfragment rsqrt( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = x ^ -0.5;\n}\n\nfragment log2( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = log(x) / log(2.0);\n}\n\nfragment min( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<scalar> )\n{\n    z = select(x < y, x, y);\n}\n\nfragment max( x: tensor<scalar>, y: tensor<scalar> ) -> ( z: tensor<scalar> )\n{\n    z = select(x > y, x, y);\n}\n\nfragment clamp( x: tensor<scalar>, a: tensor<scalar>, b: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = max(min(x, b), a);\n}\n\n\n# matrix multiplication\n\nfragment matmul( A: tensor<scalar>, B: tensor<scalar>, transposeA: logical = false, transposeB: logical = false ) -> ( C: tensor<scalar> );\n\n\n# sliding-window operations\n\nfragment conv(\n    input: tensor<scalar>,\n    filter: tensor<scalar>,\n    bias: tensor<scalar> = 0.0,\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [],\n    groups: integer = 1 )\n-> ( output: tensor<scalar> );\n\nfragment deconv(\n    input: tensor<scalar>,\n    filter: tensor<scalar>,\n    bias: tensor<scalar> = 0.0,\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [],\n    output_shape: integer[] = [],\n    groups: integer = 1 )\n-> ( output: tensor<scalar> );\n\n\nfragment box(\n    input: tensor<scalar>,\n    size: integer[],\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [],\n    normalize: logical = false )\n-> ( output: tensor<scalar> );\n\nfragment debox(\n    input: tensor<scalar>,\n    size: integer[],\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [],\n    output_shape: integer[] = [],\n    normalize: logical = false )\n-> ( output: tensor<scalar> );\n\n\nfragment argmax_pool(\n    input: tensor<scalar>,\n    size: integer[],\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [] )\n-> ( index: tensor<integer> );\n\n\nfragment sample(\n    input: tensor<scalar>,\n    index: tensor<integer>,\n    size: integer[],\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [] )\n-> ( output: tensor<scalar> );\n\nfragment desample(\n    input: tensor<scalar>,\n    index: tensor<integer>,\n    size: integer[],\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [],\n    output_shape: integer[] = [] )\n-> ( output: tensor<scalar> );\n\n\n# up/down-sampling operations\n\nfragment nearest_downsample( input: tensor<scalar>, factor: integer[] ) -> ( output: tensor<scalar> )\n{\n    dims = 2 + length_of(factor);\n    output = box(input, size = [1] * dims, stride = [1,1] + factor, padding = [(0,0)] * dims);\n}\n\nfragment area_downsample( input: tensor<scalar>, factor: integer[] ) -> ( output: tensor<scalar> )\n{\n    dims = 2 + length_of(factor);\n    output = box(input, size = [1,1] + factor, stride = [1,1] + factor, padding = [(0,0)] * dims, normalize = true);\n}\n\nfragment nearest_upsample( input: tensor<scalar>, factor: integer[] ) -> ( output: tensor<scalar> )\n{\n    dims = 2 + length_of(factor);\n    output = debox(input, size = [1,1] + factor, stride = [1,1] + factor, padding = [(0,0)] * dims);\n}\n\nfragment multilinear_upsample( input: tensor<scalar>, factor: integer[], method: string = 'symmetric', border: string = 'replicate' )\n-> ( output: tensor<scalar> );\n\n\n# reduce operations\n\nfragment sum_reduce( input: tensor<scalar>, axes: integer[], normalize: logical = false ) -> ( output: tensor<scalar> );\nfragment max_reduce( input: tensor<scalar>, axes: integer[] ) -> ( output: tensor<scalar> );\nfragment min_reduce( input: tensor<scalar>, axes: integer[] ) -> ( output: tensor<scalar> );\nfragment argmax_reduce( input: tensor<scalar>, axes: integer[] ) -> ( output: tensor<integer> );\nfragment argmin_reduce( input: tensor<scalar>, axes: integer[] ) -> ( output: tensor<integer> );\nfragment any_reduce( input: tensor<logical>, axes: integer[] ) -> ( output: tensor<logical> );\nfragment all_reduce( input: tensor<logical>, axes: integer[] ) -> ( output: tensor<logical> );\n\nfragment mean_reduce( input: tensor<scalar>, axes: integer[] ) -> ( output: tensor<scalar> )\n{\n    output = sum_reduce(input, axes = axes, normalize = true);\n}\n\nfragment moments( input: tensor<scalar>, axes: integer[] ) -> ( mean: tensor<scalar>, variance: tensor<scalar> )\n{\n    mean = mean_reduce(input, axes = axes);\n    variance = mean_reduce(sqr(input - mean), axes = axes);\n}\n\n\n# activation functions\n\nfragment relu( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = max(x, 0.0);\n}\n\nfragment sigmoid( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = 1.0 / (1.0 + exp(-x));\n}\n\nfragment tanh( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = (exp(x) - exp(-x)) / (exp(x) + exp(-x));\n}\n\nfragment softabs( x: tensor<scalar>, epsilon: scalar ) -> ( y: tensor<scalar> )\n{\n    y = sqrt(sqr(x) + epsilon);\n}\n\nfragment softmax( x: tensor<scalar>, axes: integer[] = [1] ) -> ( y: tensor<scalar> )\n{\n    m = max_reduce(x, axes = axes);\n    e = exp(x - m);\n    y = e / sum_reduce(e, axes = axes);\n}\n\nfragment softplus( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = log(exp(x) + 1.0);\n}\n\nfragment elu( x: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = select(x < 0.0, exp(x) - 1.0, x);\n}\n\nfragment prelu( x: tensor<scalar>, alpha: tensor<scalar> ) -> ( y: tensor<scalar> )\n{\n    y = select(x < 0.0, alpha * x, x);\n}\n\nfragment leaky_relu( x: tensor<scalar>, alpha: scalar ) -> ( y: tensor<scalar> )\n{\n    y = prelu(x, alpha = alpha);\n}\n\n\n# pooling operations\n\nfragment max_pool_with_index(\n    input: tensor<scalar>,\n    size: integer[],\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [] )\n-> ( output: tensor<scalar>, index: tensor<integer> )\n{\n    index = argmax_pool(input, size = size, border = border, padding = padding, stride = stride, dilation = dilation);\n    output = sample(input, index, size = size, border = border, padding = padding, stride = stride, dilation = dilation);\n}\n\nfragment max_pool(\n    input: tensor<scalar>,\n    size: integer[],\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [] )\n-> ( output: tensor<scalar> )\n{\n    output, index = max_pool_with_index(input, size = size, border = border, padding = padding, stride = stride, dilation = dilation);\n}\n\nfragment avg_pool(\n    input: tensor<scalar>,\n    size: integer[],\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [] )\n-> ( output: tensor<scalar> )\n{\n    output = box(input, size = size, border = border, padding = padding, stride = stride, dilation = dilation, normalize = true);\n}\n\nfragment rms_pool(\n    input: tensor<scalar>,\n    size: integer[],\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [] )\n-> ( output: tensor<scalar> )\n{\n    output = sqrt(avg_pool(sqr(input), size = size, border = border, padding = padding, stride = stride, dilation = dilation));\n}\n\n\n# linear operations\n\nfragment linear(\n    input: tensor<scalar>,\n    filter: tensor<scalar>,\n    bias: tensor<scalar> = 0.0 )\n-> ( output: tensor<scalar> )\n{\n    output = matmul(input, filter, transposeB = true) + bias;\n}\n\nfragment separable_conv(\n    input: tensor<scalar>,\n    plane_filter: tensor<scalar>,\n    point_filter: tensor<scalar>,\n    bias: tensor<scalar> = 0.0,\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [],\n    groups: integer = 1 )\n-> ( output: tensor<scalar> )\n{\n    filtered = conv(input, plane_filter, border = border, padding = padding,\n                    stride = stride, dilation = dilation, groups = 0);\n    output = conv(filtered, point_filter, bias, groups = groups);\n}\n\nfragment separable_deconv(\n    input: tensor<scalar>,\n    plane_filter: tensor<scalar>,\n    point_filter: tensor<scalar>,\n    bias: tensor<scalar> = 0.0,\n    border: string = 'constant',\n    padding: (integer,integer)[] = [],\n    stride: integer[] = [],\n    dilation: integer[] = [],\n    output_shape: integer[] = [],\n    groups: integer = 1 )\n-> ( output: tensor<scalar> )\n{\n    filtered = deconv(input, point_filter, groups = groups);\n    output = deconv(filtered, plane_filter, bias, border = border, padding = padding,\n                    stride = stride, dilation = dilation, output_shape = output_shape, groups = 0);\n}\n\n\n# normalization operations\n\nfragment local_response_normalization(\n    input: tensor<scalar>,\n    size: integer[],\n    alpha: scalar = 1.0,\n    beta: scalar = 0.5,\n    bias: scalar = 1.0 )\n-> ( output: tensor<scalar> )\n{\n    sigma = bias + alpha * box(sqr(input), size = size, normalize = true);\n    output = input / (sigma ^ beta);\n}\n\nfragment local_mean_normalization( input: tensor<scalar>, size: integer[] ) -> ( output: tensor<scalar> )\n{\n    mean = box(input, size = size, normalize = true);\n    output = sub(input, mean);\n}\n\nfragment local_variance_normalization( input: tensor<scalar>, size: integer[], bias: scalar = 0.0, epsilon: scalar = 0.0 ) -> ( output: tensor<scalar> )\n{\n    sigma = sqrt(box(sqr(input), size = size, normalize = true));\n    output = input / max(sigma + bias, epsilon);\n}\n\nfragment local_contrast_normalization( input: tensor<scalar>, size: integer[], bias: scalar = 0.0, epsilon: scalar = 0.0 ) -> ( output: tensor<scalar> )\n{\n    centered = local_mean_normalization(input, size = size);\n    output = local_variance_normalization(centered, size = size, bias = bias, epsilon = epsilon);\n}\n\nfragment l1_normalization( input: tensor<scalar>, axes: integer[], bias: scalar = 0.0, epsilon: scalar = 0.0 ) -> ( output: tensor<scalar> )\n{\n    sigma = sum_reduce(abs(input), axes = axes);\n    output = input / max(sigma + bias, epsilon);\n}\n\nfragment l2_normalization( input: tensor<scalar>, axes: integer[], bias: scalar = 0.0, epsilon: scalar = 0.0 ) -> ( output: tensor<scalar> )\n{\n    sigma = sqrt(sum_reduce(sqr(input), axes = axes));\n    output = input / max(sigma + bias, epsilon);\n}\n\nfragment batch_normalization( input: tensor<scalar>, mean: tensor<scalar>, variance: tensor<scalar>, offset: tensor<scalar>, scale: tensor<scalar>, epsilon: scalar )\n-> ( output: tensor<scalar> )\n{\n    output = offset + scale * (input - mean) / sqrt(variance + epsilon);\n}\n\n\n# roi operations\n\nfragment avg_roi_pool(\n    input: tensor<scalar>,\n    rois: tensor<scalar>,\n    batch_index: tensor<integer>,\n    output_size: integer[] )\n-> ( output: tensor<scalar> );\n\nfragment max_roi_pool(\n    input: tensor<scalar>,\n    rois: tensor<scalar>,\n    batch_index: tensor<integer>,\n    output_size: integer[] )\n-> ( output: tensor<scalar> );\n\nfragment roi_resample(\n    input: tensor<scalar>,\n    rois: tensor<scalar>,\n    batch_index: tensor<integer>,\n    output_size: integer[],\n    method: string = 'symmetric' )\n-> ( output: tensor<scalar> );\n\nfragment avg_roi_align(\n    input: tensor<scalar>,\n    rois: tensor<scalar>,\n    batch_index: tensor<integer>,\n    output_size: integer[],\n    sampling_rate: integer[],\n    resize_method: string = 'symmetric' )\n-> ( output: tensor<scalar> )\n{\n    size = [for i in range_of(output_size) yield output_size[i] * sampling_rate[i]];\n    resized = roi_resample(input, rois, batch_index, output_size = size,\n                         method = resize_method);\n    output = avg_pool(resized, size = sampling_rate, stride = sampling_rate);\n}\n\nfragment max_roi_align(\n    input: tensor<scalar>,\n    rois: tensor<scalar>,\n    batch_index: tensor<integer>,\n    output_size: integer[],\n    sampling_rate: integer[],\n    resize_method: string = 'symmetric' )\n-> ( output: tensor<scalar> )\n{\n    size = [for i in range_of(output_size) yield output_size[i] * sampling_rate[i]];\n    resized = roi_resample(input, rois, batch_index, output_size = size,\n                         method = resize_method);\n    output = max_pool(resized, size = sampling_rate, stride = sampling_rate);\n}\n\n\n# quantization operations\n\nfragment min_max_linear_quantize(\n    x: tensor<scalar>,\n    min: tensor<scalar>,\n    max: tensor<scalar>,\n    bits: integer,\n    signed: logical,\n    symmetric: logical )\n-> ( y: tensor<scalar> )\n{\n    r = scalar(2 ^ bits - 1 - integer(signed && symmetric));\n    z = clamp(x, min, max);\n    p = scalar(2 ^ (bits - 1) - integer(symmetric) if signed else 0);\n    q = round((z - min) / (max - min) * r) - p;\n    y = (q + p) / r * (max - min) + min;\n}\n\nfragment zero_point_linear_quantize(\n    x: tensor<scalar>,\n    zero_point: integer,\n    scale: scalar,\n    bits: integer,\n    signed: logical,\n    symmetric: logical )\n-> ( y: tensor<scalar> )\n{\n    z = round(x / scale) + scalar(zero_point);\n    r = scalar(2 ^ (bits - 1) - 1 if signed else 2 ^ bits - 1);\n    q = clamp(z, 0.0 if !signed else -r if symmetric else -r - 1.0, r);\n    y = (q - scalar(zero_point)) * scale;\n}\n\nfragment linear_quantize(\n    x: tensor<scalar>,\n    min: tensor<scalar>,\n    max: tensor<scalar>,\n    bits: integer )\n-> ( y: tensor<scalar> )\n{\n    y = min_max_linear_quantize(x, min = min, max = max, bits = bits,\n                                signed = false, symmetric = false);\n}\n\nfragment logarithmic_quantize(\n    x: tensor<scalar>,\n    max: tensor<scalar>,\n    bits: integer )\n-> ( y: tensor<scalar> )\n{\n    m = ceil(log2(max));\n    r = scalar(2 ^ bits - 1);\n    q = round(clamp(log2(abs(x)), m - r, m));\n    y = sign(x) * 2.0 ^ q;\n}\n\n\n# misc operations\n\nfragment copy_n<?>( x: tensor<?>, times: integer ) -> ( y: tensor<?>[] )\n{\n    y = [x] * times;\n}\n\nfragment add_n( x: tensor<scalar>[] ) -> ( y: tensor<scalar> )\n{\n    y = x[0] + add_n(x[1:]) if length_of(x) > 0 else constant(shape = [1], value = [0.0]);\n}\n"
  },
  {
    "path": "nnef/tests/alexnet.nnef",
    "content": "# Copyright (c) 2017 The Khronos Group Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nversion 1.0;\n\ngraph alexnet( input ) -> ( output )\n{\n    input = external(shape = [1, 3, 224, 224]);\n    kernel1 = variable(shape = [64, 3, 11, 11], label = 'alexnet_v2/conv1/kernel');\n    bias1 = variable(shape = [1, 64], label = 'alexnet_v2/conv1/bias');\n    conv1 = conv(input, kernel1, bias1, padding = [(0, 0), (0, 0)], border = 'constant', stride = [4, 4], dilation = [1, 1]);\n    relu1 = relu(conv1);\n    pool1 = max_pool(relu1, size = [1, 1, 3, 3], padding = [(0, 0), (0, 0), (0, 0), (0, 0)], border = 'ignore', stride = [1, 1, 2, 2]);\n    kernel2 = variable(shape = [192, 64, 5, 5], label = 'alexnet_v2/conv2/kernel');\n    bias2 = variable(shape = [1, 192], label = 'alexnet_v2/conv2/bias');\n    conv2 = conv(pool1, kernel2, bias2, padding = [], border = 'constant', stride = [1, 1], dilation = [1, 1]);\n    relu2 = relu(conv2);\n    pool2 = max_pool(relu2, size = [1, 1, 3, 3], padding = [(0, 0), (0, 0), (0, 0), (0, 0)], border = 'ignore', stride = [1, 1, 2, 2]);\n    kernel3 = variable(shape = [384, 192, 3, 3], label = 'alexnet_v2/conv3/kernel');\n    bias3 = variable(shape = [1, 384], label = 'alexnet_v2/conv3/bias');\n    conv3 = conv(pool2, kernel3, bias3, padding = [], border = 'constant', stride = [1, 1], dilation = [1, 1]);\n    relu3 = relu(conv3);\n    kernel4 = variable(shape = [384, 384, 3, 3], label = 'alexnet_v2/conv4/kernel');\n    bias4 = variable(shape = [1, 384], label = 'alexnet_v2/conv4/bias');\n    conv4 = conv(relu3, kernel4, bias4, padding = [], border = 'constant', stride = [1, 1], dilation = [1, 1]);\n    relu4 = relu(conv4);\n    kernel5 = variable(shape = [256, 384, 3, 3], label = 'alexnet_v2/conv5/kernel');\n    bias5 = variable(shape = [1, 256], label = 'alexnet_v2/conv5/bias');\n    conv5 = conv(relu4, kernel5, bias5, padding = [], border = 'constant', stride = [1, 1], dilation = [1, 1]);\n    relu5 = relu(conv5);\n    pool3 = max_pool(relu5, size = [1, 1, 3, 3], padding = [(0, 0), (0, 0), (0, 0), (0, 0)], border = 'ignore', stride = [1, 1, 2, 2]);\n    kernel6 = variable(shape = [4096, 256, 5, 5], label = 'alexnet_v2/fc6/kernel');\n    bias6 = variable(shape = [1, 4096], label = 'alexnet_v2/fc6/bias');\n    conv6 = conv(pool3, kernel6, bias6, padding = [(0, 0), (0, 0)], border = 'constant', stride = [1, 1], dilation = [1, 1]);\n    relu6 = relu(conv6);\n    kernel7 = variable(shape = [4096, 4096, 1, 1], label = 'alexnet_v2/fc7/kernel');\n    bias7 = variable(shape = [1, 4096], label = 'alexnet_v2/fc7/bias');\n    conv7 = conv(relu6, kernel7, bias7, padding = [], border = 'constant', stride = [1, 1], dilation = [1, 1]);\n    relu7 = relu(conv7);\n    kernel8 = variable(shape = [1000, 4096, 1, 1], label = 'alexnet_v2/fc8/kernel');\n    bias8 = variable(shape = [1, 1000], label = 'alexnet_v2/fc8/bias');\n    output = conv(relu7, kernel8, bias8, padding = [], border = 'constant', stride = [1, 1], dilation = [1, 1]);\n}\n"
  },
  {
    "path": "nnef/tests/parse.rs",
    "content": "use tract_nnef::ast::dump;\nuse tract_nnef::ast::parse;\nuse tract_nnef::internal::Nnef;\n\n#[test]\nfn parse_alexnet() {\n    let content = std::fs::read_to_string(\"tests/alexnet.nnef\").unwrap();\n    parse::parse_document(&content).unwrap();\n}\n\n#[test]\nfn parse_dump_parse_alexnet() {\n    let content = std::fs::read_to_string(\"tests/alexnet.nnef\").unwrap();\n    let ast = parse::parse_document(&content).unwrap();\n    let mut dumped = vec![];\n    dump::Dumper::new(&Nnef::default(), &mut dumped).document(&ast).unwrap();\n\n    let dumped = String::from_utf8(dumped).unwrap();\n    let ast2 = parse::parse_document(&dumped).unwrap();\n\n    assert_eq!(ast, ast2);\n}\n\n#[test]\nfn parse_stdlib() {\n    let content = std::fs::read_to_string(\"stdlib.nnef\").unwrap();\n    parse::parse_fragments(&content).unwrap();\n}\n\n#[test]\nfn parse_dump_parse_stdlib() {\n    let content = std::fs::read_to_string(\"stdlib.nnef\").unwrap();\n    let mut ast = parse::parse_fragments(&content).unwrap();\n    ast.sort_by_key(|f| f.decl.id.clone());\n    let mut dumped = vec![];\n    dump::Dumper::new(&Nnef::default(), &mut dumped).fragments(&ast).unwrap();\n\n    let dumped = String::from_utf8(dumped).unwrap();\n    println!(\"{dumped}\");\n    let mut ast2 = parse::parse_fragments(&dumped).unwrap();\n\n    assert_eq!(ast.len(), ast2.len());\n    ast2.sort_by_key(|f| f.decl.id.clone());\n    for (a, b) in ast.iter().zip(ast2.iter()) {\n        assert_eq!(a, b);\n    }\n}\n"
  },
  {
    "path": "onnx/Cargo.toml",
    "content": "[package]\nname = \"tract-onnx\"\nversion = \"0.23.0-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\", \"ONNX\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nexclude = [ \"test_cases\" ]\n# build = \"build-proto-rs\"\nrust-version.workspace = true\n\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nbytes.workspace = true\ndyn-eq.workspace = true\nderive-new.workspace = true\nlog.workspace = true\nmemmap2.workspace = true\nnum-integer.workspace = true\nprost.workspace = true\nsmallvec.workspace = true\ntract-nnef.workspace = true\ntract-hir.workspace = true\ntract-onnx-opl.workspace = true\ntract-extra.workspace = true\n\n[dev-dependencies]\ncriterion.workspace = true\nenv_logger.workspace = true\nrand.workspace = true\nrayon.workspace = true\n\n# [build-dependencies]\n# protobuf-src = \"1.0.5+3.19.3\"\n# prost-build = \"0.14\"\n\n[[bench]]\nname = \"linear_classifier\"\nharness = false\n\n[[bench]]\nname = \"linear_regressor\"\nharness = false\n\n[features]\ndefault = []\ngetrandom-js = [\"tract-onnx-opl/getrandom-js\"]\n"
  },
  {
    "path": "onnx/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "onnx/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "onnx/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "onnx/benches/linear_classifier.rs",
    "content": "use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};\n\nuse rand::RngExt;\nuse rayon::prelude::*;\nuse std::path::PathBuf;\nuse std::sync::Arc;\nuse std::time::Instant;\nuse tract_hir::internal::*;\nuse tract_onnx::tract_core::dims;\n\nfn bench_linear_classifier(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"onnx_linear_classifier\");\n\n    // Load model\n    let model_path = \"test_cases/linear_classifier/model.onnx\";\n    let onnx_path = PathBuf::from(&model_path);\n    let model = tract_onnx::onnx().model_for_path(&onnx_path).unwrap();\n\n    // Configure dimensions\n    let n = model.sym(\"N\");\n    let model = model\n        .with_input_fact(0, f32::fact(dims!(n, 12)).into())\n        .unwrap()\n        .with_output_fact(0, i64::fact(dims!(n)).into())\n        .unwrap()\n        .with_output_fact(1, f32::fact(dims!(n, 14)).into())\n        .unwrap()\n        .into_optimized()\n        .unwrap();\n\n    let input_fact = model.input_fact(0).unwrap().clone();\n    let shape: TVec<usize> = input_fact\n        .shape\n        .as_concrete()\n        .map(|s| s.iter().copied().collect())\n        .unwrap_or_else(|| tvec![1, 12]);\n    let num_features = shape[1];\n\n    // Pre-generate random input tensors\n    let mut rng = rand::rng();\n    let input_tensors: Arc<Vec<Tensor>> = Arc::new(\n        (0..1_000_000)\n            .map(|_| {\n                let sample: Vec<f32> =\n                    (0..num_features).map(|_| rng.random_range(-30.0f32..30.0f32)).collect();\n                Tensor::from_shape(&shape, &sample).unwrap()\n            })\n            .collect(),\n    );\n\n    let runnable = Arc::new(model.clone().into_runnable().unwrap());\n\n    let iteration_counts = vec![1_000, 10_000, 100_000, 1_000_000];\n\n    for &iterations in &iteration_counts {\n        group.bench_function(BenchmarkId::new(\"load_opt_run_parallel\", iterations), |b| {\n            let runnable = Arc::clone(&runnable);\n            let tensors = Arc::clone(&input_tensors);\n\n            b.iter_custom(|_| {\n                let start = Instant::now();\n\n                (0..iterations).into_par_iter().for_each(|i| {\n                    let runnable = Arc::clone(&runnable);\n                    let input_val = tensors[i % 1_000_000].clone().into_tvalue();\n                    let _ = runnable.run(tvec!(input_val)).unwrap();\n                });\n\n                start.elapsed()\n            });\n        });\n    }\n\n    group.finish();\n}\n\ncriterion_group!(benches, bench_linear_classifier);\ncriterion_main!(benches);\n"
  },
  {
    "path": "onnx/benches/linear_regressor.rs",
    "content": "use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};\n\nuse rand::RngExt;\nuse rayon::prelude::*;\nuse std::path::PathBuf;\nuse std::sync::Arc;\nuse std::time::Instant;\nuse tract_hir::internal::*;\nuse tract_onnx::tract_core::dims;\n\nfn bench_linear_regressor(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"onnx_linear_regressor\");\n\n    // Load model\n    let model_path = \"test_cases/linear_regressor/model.onnx\";\n    let onnx_path = PathBuf::from(&model_path);\n    let model = tract_onnx::onnx().model_for_path(&onnx_path).unwrap();\n\n    // Configure dimensions\n    let n = model.sym(\"N\");\n    let model = model\n        .with_input_fact(0, f32::fact(dims!(n, 21)).into())\n        .unwrap()\n        .with_output_fact(0, f32::fact(dims!(n, 1)).into())\n        .unwrap()\n        .into_optimized()\n        .unwrap();\n\n    // Configure dimensions\n    let input_fact = model.input_fact(0).unwrap().clone();\n    let shape: TVec<usize> = input_fact\n        .shape\n        .as_concrete()\n        .map(|s| s.iter().copied().collect())\n        .unwrap_or_else(|| tvec![1, 21]);\n    let num_features = shape[1];\n\n    // Pre-generate random input tensors\n    let mut rng = rand::rng();\n    let input_tensors: Arc<Vec<Tensor>> = Arc::new(\n        (0..1_000_000)\n            .map(|_| {\n                let sample: Vec<f32> =\n                    (0..num_features).map(|_| rng.random_range(-30.0f32..30.0f32)).collect();\n                Tensor::from_shape(&shape, &sample).unwrap()\n            })\n            .collect(),\n    );\n\n    let runnable = Arc::new(model.clone().into_runnable().unwrap());\n\n    let iteration_counts = vec![1_000, 10_000, 100_000, 1_000_000];\n\n    for &iterations in &iteration_counts {\n        group.bench_function(BenchmarkId::new(\"load_opt_run_parallel\", iterations), |b| {\n            let runnable = Arc::clone(&runnable);\n            let tensors = Arc::clone(&input_tensors);\n\n            b.iter_custom(|_| {\n                let start = Instant::now();\n\n                (0..iterations).into_par_iter().for_each(|i| {\n                    let runnable = Arc::clone(&runnable);\n                    let input_val = tensors[i % 1_000_000].clone().into_tvalue();\n                    let _ = runnable.run(tvec!(input_val)).unwrap();\n                });\n\n                start.elapsed()\n            });\n        });\n    }\n\n    group.finish();\n}\n\ncriterion_group!(benches, bench_linear_regressor);\ncriterion_main!(benches);\n"
  },
  {
    "path": "onnx/build-proto.rs",
    "content": "fn main() {\n    let _ = std::fs::create_dir_all(\"src/prost\");\n    std::env::set_var(\"PROTOC\", protobuf_src::protoc());\n    prost_build::Config::new()\n        .out_dir(\"src/prost\")\n        .compile_protos(&[\"protos/onnx/onnx.proto3\"], &[\"protos/\"])\n        .unwrap();\n}\n"
  },
  {
    "path": "onnx/protos/onnx/onnx-operators.proto3",
    "content": "//\n// WARNING: This file is automatically generated!  Please edit onnx.in.proto.\n//\n\n\n// Copyright (c) Facebook Inc. and Microsoft Corporation.\n// Licensed under the MIT license.\n\nsyntax = \"proto3\";\n\npackage onnx;\nimport \"onnx/onnx.proto3\";\n\n//\n// This file contains the proto definitions for OperatorSetProto and\n// OperatorProto.  OperatorSetProtos are used to describe a versioned\n// set of operators that can be used by a ModelProto.\n//\n// Like ModelProto, OperatorSetProto is defined as a top-level file/wire\n// format, however their usage is different.\n//\n// ModelProto files are used to describe executable graphs that can be\n// executed directly by a framework, runtime, or engine.\n//\n// OperatorSetProto files are used to describe a set of operators that are\n// available in a given environment.  The file TBD.TBD is the OperatorSetProto\n// that describes the ONNX standard operators.\n//\n\n// Operator/function status.\nenum OperatorStatus {\n    EXPERIMENTAL = 0;\n    STABLE = 1;\n}\n\nmessage FunctionProto {\n  // The name of the function, similar usage of op_type in OperatorProto.\n  string name = 1;\n  \n  // The first version of a function set which contains this function.\n  // When there's any breaking change for this function, the function set\n  // contains the function needs to bump its version, and since_version of\n  // the updated function will be changed to the updated function set version.  \n  int64 since_version = 2;\n\n  // This field indicates whether the syntax, semantics, or presence\n  // of this function is in an experimental or stable stage. Once an\n  // function is published as STABLE, its syntax and semantics MUST NOT\n  // change in subsequent versions of the operator set.\n  // When a function is published as EXPERIMENTAL, the syntax and semantics\n  // of the function MAY change across operator set versions.\n  // Functions \"become\" stable by deprecating the experimental version and\n  // introducing a new stable function with the same name.\n  OperatorStatus status = 3;\n\n  // The inputs and outputs of the function.\n  repeated string input = 4;\n  repeated string output = 5;\n\n  // The attributes of the function.\n  repeated string attribute= 6;\n  \n  // The nodes in the function.\n  repeated NodeProto node = 7;\n  // A human-readable documentation for this function. Markdown is allowed.\n  string doc_string = 8;\n}\n\n// An OperatorProto represents the immutable specification of the signature\n// and semantics of an operator.\n//\n// Operators are declared as part of an OperatorSet, which also defines the\n// domain name for the set.\n//\n// Operators are uniquely identified by a three part identifier\n//   (domain, op_type, since_version)\n// where\n//   *domain* is the domain of an operator set that\n//      contains this operator specification.\n//\n//   *op_type* is the name of the operator as referenced by a\n//      NodeProto.op_type\n//\n//   *since_version* is the version of the operator set that\n//      this operator was initially declared in.\n//\nmessage OperatorProto {  \n\n  // The name of the operator within a domain.\n  // This field MUST be present in this version of the IR.\n  string op_type = 1;\n\n  // The version of the operator set that first introduced this\n  // operator. This value MUST be the same value as the\n  // opset_version of the operator set that first published this operator.\n  // Subsequent versions of the operator set MUST NOT alter the signature\n  // or semantics of the operator once published as STABLE.\n  // This field MUST be present in this version of the IR.\n  int64 since_version = 2;\n\n  // This field indicates whether the syntax, semantics, or presence\n  // of this operator is in an experimental or stable stage. Once an\n  // operator is published as STABLE, it's syntax and semantics MUST NOT\n  // change in subsequent versions of the operator set.\n  // When an operator is published as EXPERIMENTAL, the syntax and semantics\n  // of the operator MAY change across operator set versions.\n  // Operators \"become\" stable by deprecating the experimental version and\n  // introducing a new stable operator with the same op_type.\n  OperatorStatus status = 3;\n\n  // Eventually we will declare the signature of the operator here\n\n  // A human-readable documentation for this operator. Markdown is allowed.\n  string doc_string = 10;\n}\n\n// An OperatorSetProto represents an immutable set of immutable operator\n// specifications.\n//\n// The domain of the set (OperatorSetProto.domain) is a reverse-DNS name\n// that disambiguates operator sets defined by independent entities.\n//\n// The version of the set (opset_version) is a monotonically increasing\n// integer that indicates changes to the membership of the operator set.\n//\n//\n// Operator sets are uniquely identified by a two part identifier (domain, opset_version)\n//\n// Like ModelProto, OperatorSetProto is intended as a top-level file/wire format,\n// and thus has the standard format headers in addition to the operator set information.\n//\nmessage OperatorSetProto {\n  // All OperatorSetProtos start with a distingushed byte sequence to disambiguate\n  // protobuf files containing OperatorSets from other content.\n  // This field MUST be \"ONNXOPSET\"\n  // This field MUST be present in this version of the IR\n  string magic = 1;\n\n  // All OperatorSetProtos indicate the version of the IR syntax and semantics\n  // they adhere to. It is always IR_VERSION.\n  // This field MUST be present in this version of the IR\n  int32 ir_version = 2;\n\n  // The prerelease component of the SemVer of the IR.\n  // This field MAY be absent in this version of the IR\n  string ir_version_prerelease = 3;\n\n  // The build metadata component of the SemVer of the IR.\n  // This field MAY be absent in this version of the IR\n  string ir_build_metadata = 7;\n\n  // Domain name of the operator set, in reverse DNS form (e.g., com.acme.dnnops).\n  string domain = 4;\n\n  // The version of the set of operators. This is a simple int value\n  // that is monotonically increasing as new versions of operator set\n  // are published. All operators in this set MUST have version\n  // numbers no greater than opset_version.\n  int64 opset_version = 5;\n\n  // A human-readable documentation for this set of operators. Markdown is allowed.\n  string doc_string = 6;\n\n  // The operators specified by this operator set.\n  // The (name, version) MUST be unique across all OperatorProtos in operator\n  repeated OperatorProto operator = 8;\n  \n  // The functions specified by this operator set.\n  // The (name, version) MUST be unique across all OperatorProtos/FunctionProtos in operator/functions\n  repeated FunctionProto functions = 9;\n}"
  },
  {
    "path": "onnx/protos/onnx/onnx.proto",
    "content": "//\n// WARNING: This file is automatically generated!  Please edit onnx.in.proto.\n//\n\n\n// Copyright (c) Facebook Inc. and Microsoft Corporation.\n// Licensed under the MIT license.\n\nsyntax = \"proto2\";\n\npackage onnx;\n\n// Overview\n//\n// ONNX is an open specification that is comprised of the following components:\n//\n// 1)  A definition of an extensible computation graph model.\n// 2)  Definitions of standard data types.\n// 3)  Definitions of built-in operators.\n//\n// This document describes the syntax of models and their computation graphs,\n// as well as the standard data types. Together, they are referred to as the ONNX\n// Intermediate Representation, or 'IR' for short. \n//\n// The normative semantic specification of the ONNX IR is found in docs/IR.md.\n// Definitions of the built-in neural network operators may be found in docs/Operators.md.\n\n// Notes\n//\n// Release\n//\n// We are still in the very early stage of defining ONNX. The current\n// version of ONNX is a starting point. While we are actively working\n// towards a complete spec, we would like to get the community involved\n// by sharing our working version of ONNX.\n//\n// Protobuf compatibility\n// \n// To simplify framework compatibility, ONNX is defined using the subset of protobuf \n// that is compatible with both protobuf v2 and v3. This means that we do not use any\n// protobuf features that are only available in one of the two versions.\n//\n// Here are the most notable contortions we have to carry out to work around\n// these limitations:\n//\n//   - No 'map' (added protobuf 3.0). We instead represent mappings as lists\n//     of key-value pairs, where order does not matter and duplicates\n//     are not allowed.\n\n\n// Versioning\n//\n// ONNX versioning is specified in docs/IR.md and elaborated on in docs/Versioning.md\n//\n// To be compatible with both proto2 and proto3, we will use a version number\n// that is not defined by the default value but an explicit enum number.\nenum Version {\n  // proto3 requires the first enum value to be zero.\n  // We add this just to appease the compiler.\n  _START_VERSION = 0;\n  // The version field is always serialized and we will use it to store the\n  // version that the  graph is generated from. This helps us set up version\n  // control. \n  // For the IR, we are using simple numbers starting with with 0x00000001, \n  // which was the version we published on Oct 10, 2017.\n  IR_VERSION_2017_10_10 = 0x0000000000000001;\n\n  // IR_VERSION 2 published on Oct 30, 2017\n  // - Added type discriminator to AttributeProto to support proto3 users\n  IR_VERSION_2017_10_30 = 0x0000000000000002;\n\n  // IR VERSION 3 published on Nov 3, 2017\n  // - For operator versioning:\n  //    - Added new message OperatorSetIdProto\n  //    - Added opset_import in ModelProto\n  // - For vendor extensions, added domain in NodeProto\n  IR_VERSION = 0x0000000000000003;\n}\n\n// Attributes\n//\n// A named attribute containing either singular float, integer, string, graph,\n// and tensor values, or repeated float, integer, string, graph, and tensor values.\n// An AttributeProto MUST contain the name field, and *only one* of the\n// following content fields, effectively enforcing a C/C++ union equivalent.\nmessage AttributeProto {\n\n  // Note: this enum is structurally identical to the OpSchema::AttrType\n  // enum defined in schema.h.  If you rev one, you likely need to rev the other.\n  enum AttributeType {\n    UNDEFINED = 0;\n    FLOAT = 1;\n    INT = 2;\n    STRING = 3;\n    TENSOR = 4;\n    GRAPH = 5;\n\n    FLOATS = 6;\n    INTS = 7;\n    STRINGS = 8;\n    TENSORS = 9;\n    GRAPHS = 10;\n  }\n\n  // The name field MUST be present for this version of the IR.\n  optional string name = 1;           // namespace Attribute\n \n  // if ref_attr_name is not empty, ref_attr_name is the attribute name in parent function.\n  // In this case, this AttributeProto does not contain data, and it's a reference of attribute\n  // in parent scope.\n  // NOTE: This should ONLY be used in function (sub-graph). It's invalid to be used in main graph.\n  optional string ref_attr_name = 21;\n\n  // A human-readable documentation for this attribute. Markdown is allowed.\n  optional string doc_string = 13;\n\n  // The type field MUST be present for this version of the IR.\n  // For 0.0.1 versions of the IR, this field was not defined, and\n  // implementations needed to use has_field hueristics to determine\n  // which value field was in use.  For IR_VERSION 0.0.2 or later, this\n  // field MUST be set and match the f|i|s|t|... field in use.  This\n  // change was made to accomodate proto3 implementations.\n  optional AttributeType type = 20;   // discriminator that indicates which field below is in use\n\n  // Exactly ONE of the following fields must be present for this version of the IR\n  optional float f = 2;               // float\n  optional int64 i = 3;               // int\n  optional bytes s = 4;               // UTF-8 string\n  optional TensorProto t = 5;         // tensor value\n  optional GraphProto g = 6;          // graph\n  // Do not use field below, it's deprecated.\n  // optional ValueProto v = 12;         // value - subsumes everything but graph\n\n  repeated float floats = 7;          // list of floats\n  repeated int64 ints = 8;            // list of ints\n  repeated bytes strings = 9;         // list of UTF-8 strings\n  repeated TensorProto tensors = 10;  // list of tensors\n  repeated GraphProto graphs = 11;    // list of graph\n}\n\n// Defines information on value, including the name, the type, and\n// the shape of the value.\nmessage ValueInfoProto {\n  // This field MUST be present in this version of the IR.\n  optional string name = 1;     // namespace Value\n  // This field MUST be present in this version of the IR.\n  optional TypeProto type = 2;\n  // A human-readable documentation for this value. Markdown is allowed.\n  optional string doc_string = 3;\n}\n\n// Nodes\n//\n// Computation graphs are made up of a DAG of nodes, which represent what is\n// commonly called a \"layer\" or \"pipeline stage\" in machine learning frameworks.\n//\n// For example, it can be a node of type \"Conv\" that takes in an image, a filter \n// tensor and a bias tensor, and produces the convolved output.\nmessage NodeProto {\n  repeated string input = 1;    // namespace Value\n  repeated string output = 2;   // namespace Value\n\n  // An optional identifier for this node in a graph.\n  // This field MAY be absent in ths version of the IR.\n  optional string name = 3;     // namespace Node\n\n  // The symbolic identifier of the Operator to execute.\n  optional string op_type = 4;  // namespace Operator\n  // The domain of the OperatorSet that specifies the operator named by op_type.\n  optional string domain = 7;   // namespace Domain\n\n  // Additional named attributes.\n  repeated AttributeProto attribute = 5;\n\n  // A human-readable documentation for this node. Markdown is allowed.\n  optional string doc_string = 6;\n}\n\n// Models\n//\n// ModelProto is a top-level file/container format for bundling a ML model and\n// associating its computation graph with metadata.\n//\n// The semantics of the model are described by the associated GraphProto.\nmessage ModelProto {\n  // The version of the IR this model targets. See Version enum above.\n  // This field MUST be present.\n  optional int64 ir_version = 1;\n\n  // The OperatorSets this model relies on.\n  // All ModelProtos MUST have at least one entry that\n  // specifies which version of the ONNX OperatorSet is\n  // being imported.\n  //\n  // All nodes in the ModelProto's graph will bind against the operator\n  // with the same-domain/same-op_type operator with the HIGHEST version\n  // in the referenced operator sets.\n  repeated OperatorSetIdProto opset_import = 8;\n\n  // The name of the framework or tool used to generate this model.\n  // This field SHOULD be present to indicate which implementation/tool/framework\n  // emitted the model.\n  optional string producer_name = 2;\n\n  // The version of the framework or tool used to generate this model.\n  // This field SHOULD be present to indicate which implementation/tool/framework\n  // emitted the model.\n  optional string producer_version = 3;\n\n  // Domain name of the model.\n  // We use reverse domain names as name space indicators. For example:\n  // `com.facebook.fair` or `com.microsoft.cognitiveservices`\n  //\n  // Together with `model_version` and GraphProto.name, this forms the unique identity of\n  // the graph.\n  optional string domain = 4;\n\n  // The version of the graph encoded. See Version enum below.\n  optional int64 model_version = 5;\n\n  // A human-readable documentation for this model. Markdown is allowed.\n  optional string doc_string = 6;\n\n  // The parameterized graph that is evaluated to execute the model.\n  optional GraphProto graph = 7;\n\n  // Named metadata values; keys should be distinct.\n  repeated StringStringEntryProto metadata_props = 14;\n};\n\n// StringStringEntryProto follows the pattern for cross-proto-version maps.\n// See https://developers.google.com/protocol-buffers/docs/proto3#maps\nmessage StringStringEntryProto {\n  optional string key = 1;\n  optional string value= 2;\n};\n\n// Graphs\n//\n// A graph defines the computational logic of a model and is comprised of a parameterized \n// list of nodes that form a directed acyclic graph based on their inputs and outputs.\n// This is the equivalent of the \"network\" or \"graph\" in many deep learning\n// frameworks.\nmessage GraphProto {\n  // The nodes in the graph, sorted topologically.\n  repeated NodeProto node = 1;\n\n  // The name of the graph.\n  optional string name = 2;   // namespace Graph\n\n  // A list of named tensor values, used to specify constant inputs of the graph.\n  // Each TensorProto entry must have a distinct name (within the list) that\n  // also appears in the input list.\n  repeated TensorProto initializer = 5;\n\n  // A human-readable documentation for this graph. Markdown is allowed.\n  optional string doc_string = 10;\n\n  // The inputs and outputs of the graph.\n  repeated ValueInfoProto input = 11;\n  repeated ValueInfoProto output = 12;\n\n  // Information for the values in the graph. The ValueInfoProto.name's\n  // must be distinct. It is optional for a value to appear in value_info list.\n  repeated ValueInfoProto value_info = 13;\n\n  // DO NOT USE the following fields, they were deprecated from earlier versions.\n  // repeated string input = 3;\n  // repeated string output = 4;\n  // optional int64 ir_version = 6;\n  // optional int64 producer_version = 7;\n  // optional string producer_tag = 8;\n  // optional string domain = 9;\n}\n\n// Tensors\n//\n// A serialized tensor value.\nmessage TensorProto {\n  enum DataType {\n    UNDEFINED = 0;\n    // Basic types.\n    FLOAT = 1;   // float\n    UINT8 = 2;   // uint8_t\n    INT8 = 3;    // int8_t\n    UINT16 = 4;  // uint16_t\n    INT16 = 5;   // int16_t\n    INT32 = 6;   // int32_t\n    INT64 = 7;   // int64_t\n    STRING = 8;  // string\n    BOOL = 9;    // bool\n\n    // Advanced types\n    FLOAT16 = 10;\n    DOUBLE = 11;\n    UINT32 = 12;\n    UINT64 = 13;\n    COMPLEX64 = 14;     // complex with float32 real and imaginary components\n    COMPLEX128 = 15;    // complex with float64 real and imaginary components\n    // Future extensions go here.\n  }\n\n  // The shape of the tensor.\n  repeated int64 dims = 1;\n\n  // The data type of the tensor.\n  optional DataType data_type = 2;\n\n  // For very large tensors, we may want to store them in chunks, in which\n  // case the following fields will specify the segment that is stored in\n  // the current TensorProto.\n  message Segment {\n    optional int64 begin = 1;\n    optional int64 end = 2;\n  }\n  optional Segment segment = 3;\n\n  // Tensor content must be organized in row-major order.\n  //\n  // Depending on the data_type field, exactly one of the fields below with\n  // name ending in _data is used to store the elements of the tensor.\n\n  // For float and complex64 values\n  // Complex64 tensors are encoded as a single array of floats,\n  // with the real components appearing in odd numbered positions,\n  // and the corresponding imaginary component apparing in the\n  // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i]\n  // is encoded as [1.0, 2.0 ,3.0 ,4.0]\n  // When this field is present, the data_type field MUST be FLOAT or COMPLEX64.\n  repeated float float_data = 4 [packed = true];\n\n  // For int32, uint8, int8, uint16, int16, bool, and float16 values\n  // float16 values must be bit-wise converted to an uint16_t prior\n  // to writing to the buffer.\n  // When this field is present, the data_type field MUST be\n  // INT32, INT16, INT8, UINT16, INT8, BOOL, or FLOAT16\n  repeated int32 int32_data = 5 [packed = true];\n\n  // For strings.\n  // Each element of string_data is a UTF-8 encoded Unicode\n  // string. No trailing null, no leading BOM. The protobuf \"string\"\n  // scalar type is not used to match ML community conventions.\n  // When this field is present, the data_type field MUST be STRING\n  repeated bytes string_data = 6;\n\n  // For int64.\n  // When this field is present, the data_type field MUST be INT64\n  repeated int64 int64_data = 7 [packed = true];\n\n  // Optionally, a name for the tensor.\n  optional string name = 8; // namespace Value\n\n  // A human-readable documentation for this tensor. Markdown is allowed.\n  optional string doc_string = 12;\n\n  // Serializations can either use one of the fields above, or use this\n  // raw bytes field. The only exception is the string case, where one is\n  // required to store the content in the repeated bytes string_data field.\n  //\n  // When this raw_data field is used to store tensor value, elements MUST\n  // be stored in as fixed-width, little-endian order.\n  // Floating-point data types MUST be stored in IEEE 754 format.\n  // Complex64 elements must be written as two consecutive FLOAT values, real component first.\n  // Complex128 elements must be written as two consecutive DOUBLE values, real component first.\n  // Boolean type MUST be written one byte per tensor element (00000001 for true, 00000000 for false).\n  //\n  // Note: the advantage of specific field rather than the raw_data field is\n  // that in some cases (e.g. int data), protobuf does a better packing via\n  // variable length storage, and may lead to smaller binary footprint.\n  // When this field is present, the data_type field MUST NOT be STRING or UNDEFINED\n  optional bytes raw_data = 9;\n\n  // For double\n  // Complex64 tensors are encoded as a single array of doubles,\n  // with the real components appearing in odd numbered positions,\n  // and the corresponding imaginary component apparing in the\n  // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i]\n  // is encoded as [1.0, 2.0 ,3.0 ,4.0]\n  // When this field is present, the data_type field MUST be DOUBLE or COMPLEX128\n  repeated double double_data = 10 [packed = true];\n\n  // For uint64 and uint32 values\n  // When this field is present, the data_type field MUST be\n  // UINT32 or UINT64\n  repeated uint64 uint64_data = 11 [packed = true];\n}\n\n// Defines a tensor shape. A dimension can be either an integer value\n// or a symbolic variable. A symbolic variable represents an unknown\n// dimension.\nmessage TensorShapeProto {\n  message Dimension {\n    oneof value {\n      int64 dim_value = 1;\n      string dim_param = 2;   // namespace Shape\n    };\n    // Standard denotation can optionally be used to denote tensor\n    // dimensions with standard semantic descriptions to ensure\n    // that operations are applied to the correct axis of a tensor.\n    // Refer to https://github.com/onnx/onnx/blob/master/docs/DimensionDenotation.md#denotation-definition\n    // for pre-defined dimension denotations.\n    optional string denotation = 3;\n  };\n  repeated Dimension dim = 1;\n}\n\n// Types\n//\n// The standard ONNX data types.\nmessage TypeProto {\n\n  message Tensor {\n    // This field MUST NOT have the value of UNDEFINED\n    // This field MUST be present for this version of the IR.\n    optional TensorProto.DataType elem_type = 1;\n    optional TensorShapeProto shape = 2;\n  }\n\n\n  oneof value {\n    // The type of a tensor.\n    Tensor tensor_type = 1;\n\n  }\n\n  // An optional denotation can be used to denote the whole \n  // type with a standard semantic description as to what is \n  // stored inside. Refer to https://github.com/onnx/onnx/blob/master/docs/TypeDenotation.md#type-denotation-definition\n  // for pre-defined type denotations.\n  optional string denotation = 6;\n}\n\n// Operator Sets\n//\n// OperatorSets are uniquely identified by a (domain, opset_version) pair.\nmessage OperatorSetIdProto {\n  // The domain of the operator set being identified.\n  // The empty string (\"\") or absence of this field implies the operator\n  // set that is defined as part of the ONNX specification.\n  // This field MUST be present in this version of the IR when referring to any other operator set.\n  optional string domain = 1;\n\n  // The version of the operator set being identified.\n  // This field MUST be present in this version of the IR.\n  optional int64 version = 2;\n}"
  },
  {
    "path": "onnx/protos/onnx/onnx.proto3",
    "content": "//\n// WARNING: This file is automatically generated!  Please edit onnx.in.proto.\n//\n\n\n// Copyright (c) Facebook Inc. and Microsoft Corporation.\n// Licensed under the MIT license.\n\nsyntax = \"proto3\";\n\npackage onnx;\n\n// Overview\n//\n// ONNX is an open specification that is comprised of the following components:\n//\n// 1)  A definition of an extensible computation graph model.\n// 2)  Definitions of standard data types.\n// 3)  Definitions of built-in operators.\n//\n// This document describes the syntax of models and their computation graphs,\n// as well as the standard data types. Together, they are referred to as the ONNX\n// Intermediate Representation, or 'IR' for short. \n//\n// The normative semantic specification of the ONNX IR is found in docs/IR.md.\n// Definitions of the built-in neural network operators may be found in docs/Operators.md.\n\n// Notes\n//\n// Release\n//\n// We are still in the very early stage of defining ONNX. The current\n// version of ONNX is a starting point. While we are actively working\n// towards a complete spec, we would like to get the community involved\n// by sharing our working version of ONNX.\n//\n// Protobuf compatibility\n// \n// To simplify framework compatibility, ONNX is defined using the subset of protobuf \n// that is compatible with both protobuf v2 and v3. This means that we do not use any\n// protobuf features that are only available in one of the two versions.\n//\n// Here are the most notable contortions we have to carry out to work around\n// these limitations:\n//\n//   - No 'map' (added protobuf 3.0). We instead represent mappings as lists\n//     of key-value pairs, where order does not matter and duplicates\n//     are not allowed.\n\n\n// Versioning\n//\n// ONNX versioning is specified in docs/IR.md and elaborated on in docs/Versioning.md\n//\n// To be compatible with both proto2 and proto3, we will use a version number\n// that is not defined by the default value but an explicit enum number.\nenum Version {\n  // proto3 requires the first enum value to be zero.\n  // We add this just to appease the compiler.\n  _START_VERSION = 0;\n  // The version field is always serialized and we will use it to store the\n  // version that the  graph is generated from. This helps us set up version\n  // control.\n  // For the IR, we are using simple numbers starting with 0x00000001,\n  // which was the version we published on Oct 10, 2017.\n  IR_VERSION_2017_10_10 = 0x0000000000000001;\n\n  // IR_VERSION 2 published on Oct 30, 2017\n  // - Added type discriminator to AttributeProto to support proto3 users\n  IR_VERSION_2017_10_30 = 0x0000000000000002;\n\n  // IR VERSION 3 published on Nov 3, 2017\n  // - For operator versioning:\n  //    - Added new message OperatorSetIdProto\n  //    - Added opset_import in ModelProto\n  // - For vendor extensions, added domain in NodeProto\n  IR_VERSION_2017_11_3 = 0x0000000000000003;\n\n  // IR VERSION 4 published on Jan 22, 2019\n  // - Relax constraint that initializers should be a subset of graph inputs\n  // - Add type BFLOAT16\n  IR_VERSION_2019_1_22 = 0x0000000000000004;\n\n  // IR VERSION 5 published on March 18, 2019\n  // - Add message TensorAnnotation.\n  // - Add quantization annotation in GraphProto to map tensor with its scale and zero point quantization parameters.\n  IR_VERSION_2019_3_18 = 0x0000000000000005;\n\n  // IR VERSION 6 published on Sep 19, 2019\n  // - Add support for sparse tensor constants stored in model.\n  //   - Add message SparseTensorProto\n  //   - Add sparse initializers\n  IR_VERSION_2019_9_19 = 0x0000000000000006;\n\n  // IR VERSION 7 published on May 8, 2020\n  // - Add support to allow function body graph to rely on multiple external opreator sets.\n  // - Add a list to promote inference graph's initializers to global and\n  //   mutable variables. Global variables are visible in all graphs of the\n  //   stored models.\n  // - Add message TrainingInfoProto to store initialization\n  //   method and training algorithm. The execution of TrainingInfoProto\n  //   can modify the values of mutable variables.\n  // - Implicitly add inference graph into each TrainingInfoProto's algorithm.\n  IR_VERSION_2020_5_8 = 0x0000000000000007;\n\n  // IR VERSION 8 published on <TBD>\n  // Introduce TypeProto.SparseTensor\n  // Introduce TypeProto.Optional\n  // Added a list of FunctionProtos local to the model\n  // Deprecated since_version and operator status from FunctionProto\n  IR_VERSION = 0x0000000000000008;\n}\n\n// Attributes\n//\n// A named attribute containing either singular float, integer, string, graph,\n// and tensor values, or repeated float, integer, string, graph, and tensor values.\n// An AttributeProto MUST contain the name field, and *only one* of the\n// following content fields, effectively enforcing a C/C++ union equivalent.\nmessage AttributeProto {\n\n  // Note: this enum is structurally identical to the OpSchema::AttrType\n  // enum defined in schema.h.  If you rev one, you likely need to rev the other.\n  enum AttributeType {\n    UNDEFINED = 0;\n    FLOAT = 1;\n    INT = 2;\n    STRING = 3;\n    TENSOR = 4;\n    GRAPH = 5;\n    SPARSE_TENSOR = 11;\n    TYPE_PROTO = 13;\n\n    FLOATS = 6;\n    INTS = 7;\n    STRINGS = 8;\n    TENSORS = 9;\n    GRAPHS = 10;\n    SPARSE_TENSORS = 12;\n    TYPE_PROTOS = 14;\n  }\n\n  // The name field MUST be present for this version of the IR.\n  string name = 1;           // namespace Attribute\n \n  // if ref_attr_name is not empty, ref_attr_name is the attribute name in parent function.\n  // In this case, this AttributeProto does not contain data, and it's a reference of attribute\n  // in parent scope.\n  // NOTE: This should ONLY be used in function (sub-graph). It's invalid to be used in main graph.\n  string ref_attr_name = 21;\n\n  // A human-readable documentation for this attribute. Markdown is allowed.\n  string doc_string = 13;\n\n  // The type field MUST be present for this version of the IR.\n  // For 0.0.1 versions of the IR, this field was not defined, and\n  // implementations needed to use has_field hueristics to determine\n  // which value field was in use.  For IR_VERSION 0.0.2 or later, this\n  // field MUST be set and match the f|i|s|t|... field in use.  This\n  // change was made to accomodate proto3 implementations.\n  AttributeType type = 20;   // discriminator that indicates which field below is in use\n\n  // Exactly ONE of the following fields must be present for this version of the IR\n  float f = 2;               // float\n  int64 i = 3;               // int\n  bytes s = 4;               // UTF-8 string\n  TensorProto t = 5;         // tensor value\n  GraphProto g = 6;          // graph\n  SparseTensorProto sparse_tensor = 22;  // sparse tensor value\n  // Do not use field below, it's deprecated.\n  // optional ValueProto v = 12;         // value - subsumes everything but graph\n\n  repeated float floats = 7;          // list of floats\n  repeated int64 ints = 8;            // list of ints\n  repeated bytes strings = 9;         // list of UTF-8 strings\n  repeated TensorProto tensors = 10;  // list of tensors\n  repeated GraphProto graphs = 11;    // list of graph\n  repeated SparseTensorProto sparse_tensors = 23; // list of sparse tensors\n  repeated TypeProto type_protos = 15;// list of type protos\n}\n\n// Defines information on value, including the name, the type, and\n// the shape of the value.\nmessage ValueInfoProto {\n  // This field MUST be present in this version of the IR.\n  string name = 1;     // namespace Value\n  // This field MUST be present in this version of the IR.\n  TypeProto type = 2;\n  // A human-readable documentation for this value. Markdown is allowed.\n  string doc_string = 3;\n}\n\n// Nodes\n//\n// Computation graphs are made up of a DAG of nodes, which represent what is\n// commonly called a \"layer\" or \"pipeline stage\" in machine learning frameworks.\n//\n// For example, it can be a node of type \"Conv\" that takes in an image, a filter \n// tensor and a bias tensor, and produces the convolved output.\nmessage NodeProto {\n  repeated string input = 1;    // namespace Value\n  repeated string output = 2;   // namespace Value\n\n  // An optional identifier for this node in a graph.\n  // This field MAY be absent in ths version of the IR.\n  string name = 3;     // namespace Node\n\n  // The symbolic identifier of the Operator to execute.\n  string op_type = 4;  // namespace Operator\n  // The domain of the OperatorSet that specifies the operator named by op_type.\n  string domain = 7;   // namespace Domain\n\n  // Additional named attributes.\n  repeated AttributeProto attribute = 5;\n\n  // A human-readable documentation for this node. Markdown is allowed.\n  string doc_string = 6;\n}\n\n// Models\n//\n// ModelProto is a top-level file/container format for bundling a ML model and\n// associating its computation graph with metadata.\n//\n// The semantics of the model are described by the associated GraphProto.\nmessage ModelProto {\n  // The version of the IR this model targets. See Version enum above.\n  // This field MUST be present.\n  int64 ir_version = 1;\n\n  // The OperatorSets this model relies on.\n  // All ModelProtos MUST have at least one entry that\n  // specifies which version of the ONNX OperatorSet is\n  // being imported.\n  //\n  // All nodes in the ModelProto's graph will bind against the operator\n  // with the same-domain/same-op_type operator with the HIGHEST version\n  // in the referenced operator sets.\n  repeated OperatorSetIdProto opset_import = 8;\n\n  // The name of the framework or tool used to generate this model.\n  // This field SHOULD be present to indicate which implementation/tool/framework\n  // emitted the model.\n  string producer_name = 2;\n\n  // The version of the framework or tool used to generate this model.\n  // This field SHOULD be present to indicate which implementation/tool/framework\n  // emitted the model.\n  string producer_version = 3;\n\n  // Domain name of the model.\n  // We use reverse domain names as name space indicators. For example:\n  // `com.facebook.fair` or `com.microsoft.cognitiveservices`\n  //\n  // Together with `model_version` and GraphProto.name, this forms the unique identity of\n  // the graph.\n  string domain = 4;\n\n  // The version of the graph encoded. See Version enum below.\n  int64 model_version = 5;\n\n  // A human-readable documentation for this model. Markdown is allowed.\n  string doc_string = 6;\n\n  // The parameterized graph that is evaluated to execute the model.\n  GraphProto graph = 7;\n\n  // Named metadata values; keys should be distinct.\n  repeated StringStringEntryProto metadata_props = 14;\n\n  // Training-specific information. Sequentially executing all stored\n  // `TrainingInfoProto.algorithm`s and assigning their outputs following\n  // the corresponding `TrainingInfoProto.update_binding`s is one training\n  // iteration. Similarly, to initialize the model\n  // (as if training hasn't happened), the user should sequentially execute\n  // all stored `TrainingInfoProto.initialization`s and assigns their outputs\n  // using `TrainingInfoProto.initialization_binding`s.\n  //\n  // If this field is empty, the training behavior of the model is undefined.\n  repeated TrainingInfoProto training_info = 20;\n\n  // A list of function protos local to the model.\n  //\n  // Name of the function \"FunctionProto.name\" should be unique within the domain \"FunctionProto.domain\".\n  // In case of any conflicts the behavior (whether the model local functions are given higher priority,\n  // or standard opserator sets are given higher priotity or this is treated as error) is defined by\n  // the runtimes.\n  //\n  // The operator sets imported by FunctionProto should be compatible with the ones\n  // imported by ModelProto and other model local FunctionProtos.\n  // Example, if same operator set say 'A' is imported by a FunctionProto and ModelProto\n  // or by 2 FunctionProtos then versions for the operator set may be different but,\n  // the operator schema returned for op_type, domain, version combination\n  // for both the versions should be same for every node in the function body.\n  //\n  // One FunctionProto can reference other FunctionProto in the model, however, recursive reference\n  // is not allowed.\n  repeated FunctionProto functions = 25;\n};\n\n// StringStringEntryProto follows the pattern for cross-proto-version maps.\n// See https://developers.google.com/protocol-buffers/docs/proto3#maps\nmessage StringStringEntryProto {\n  string key = 1;\n  string value= 2;\n};\n\nmessage TensorAnnotation {\n  optional string tensor_name = 1;\n  // <key, value> pairs to annotate tensor specified by <tensor_name> above.\n  // The keys used in the mapping below must be pre-defined in ONNX spec.\n  // For example, for 8-bit linear quantization case, 'SCALE_TENSOR', 'ZERO_POINT_TENSOR' will be pre-defined as\n  // quantization parameter keys.\n  repeated StringStringEntryProto quant_parameter_tensor_names = 2;\n}\n\n// Graphs\n//\n// A graph defines the computational logic of a model and is comprised of a parameterized \n// list of nodes that form a directed acyclic graph based on their inputs and outputs.\n// This is the equivalent of the \"network\" or \"graph\" in many deep learning\n// frameworks.\nmessage GraphProto {\n  // The nodes in the graph, sorted topologically.\n  repeated NodeProto node = 1;\n\n  // The name of the graph.\n  string name = 2;   // namespace Graph\n\n  // A list of named tensor values, used to specify constant inputs of the graph.\n  // Each initializer (both TensorProto as well SparseTensorProto) MUST have a name.\n  // The name MUST be unique across both initializer and sparse_initializer,\n  // but the name MAY also appear in the input list.\n  repeated TensorProto initializer = 5;\n\n  // Initializers (see above) stored in sparse format.\n  repeated SparseTensorProto sparse_initializer = 15;\n\n  // A human-readable documentation for this graph. Markdown is allowed.\n  string doc_string = 10;\n\n  // The inputs and outputs of the graph.\n  repeated ValueInfoProto input = 11;\n  repeated ValueInfoProto output = 12;\n\n  // Information for the values in the graph. The ValueInfoProto.name's\n  // must be distinct. It is optional for a value to appear in value_info list.\n  repeated ValueInfoProto value_info = 13;\n\n  // This field carries information to indicate the mapping among a tensor and its\n  // quantization parameter tensors. For example:\n  // For tensor 'a', it may have {'SCALE_TENSOR', 'a_scale'} and {'ZERO_POINT_TENSOR', 'a_zero_point'} annotated,\n  // which means, tensor 'a_scale' and tensor 'a_zero_point' are scale and zero point of tensor 'a' in the model.\n  repeated TensorAnnotation quantization_annotation = 14;\n\n  reserved 3, 4, 6 to 9;\n  reserved \"ir_version\", \"producer_version\", \"producer_tag\", \"domain\";\n}\n\n// Training information\n// TrainingInfoProto stores information for training a model.\n// In particular, this defines two functionalities: an initialization-step\n// and a training-algorithm-step. Initialization resets the model\n// back to its original state as if no training has been performed.\n// Training algorithm improves the model based on input data.\n//\n// The semantics of the initialization-step is that the initializers\n// in ModelProto.graph and in TrainingInfoProto.algorithm are first\n// initialized as specified by the initializers in the graph, and then\n// updated by the \"initialization_binding\" in every instance in\n// ModelProto.training_info.\n//\n// The field \"algorithm\" defines a computation graph which represents a\n// training algorithm's step. After the execution of a\n// TrainingInfoProto.algorithm, the initializers specified by \"update_binding\"\n// may be immediately updated. If the targeted training algorithm contains\n// consecutive update steps (such as block coordinate descent methods),\n// the user needs to create a TrainingInfoProto for each step.\nmessage TrainingInfoProto {\n  // This field describes a graph to compute the initial tensors\n  // upon starting the training process. Initialization graph has no input\n  // and can have multiple outputs. Usually, trainable tensors in neural\n  // networks are randomly initialized. To achieve that, for each tensor,\n  // the user can put a random number operator such as RandomNormal or\n  // RandomUniform in TrainingInfoProto.initialization.node and assign its\n  // random output to the specific tensor using \"initialization_binding\".\n  // This graph can also set the initializers in \"algorithm\" in the same\n  // TrainingInfoProto; a use case is resetting the number of training\n  // iteration to zero.\n  //\n  // By default, this field is an empty graph and its evaluation does not\n  // produce any output. Thus, no initializer would be changed by default.\n  optional GraphProto initialization = 1;\n\n  // This field represents a training algorithm step. Given required inputs,\n  // it computes outputs to update initializers in its own or inference graph's\n  // initializer lists. In general, this field contains loss node, gradient node,\n  // optimizer node, increment of iteration count.\n  //\n  // An execution of the training algorithm step is performed by executing the\n  // graph obtained by combining the inference graph (namely \"ModelProto.graph\")\n  // and the \"algorithm\" graph. That is, the actual the actual\n  // input/initializer/output/node/value_info/sparse_initializer list of\n  // the training graph is the concatenation of\n  // \"ModelProto.graph.input/initializer/output/node/value_info/sparse_initializer\"\n  // and \"algorithm.input/initializer/output/node/value_info/sparse_initializer\"\n  // in that order. This combined graph must satisfy the normal ONNX conditions.\n  // Now, let's provide a visualization of graph combination for clarity.\n  // Let the inference graph (i.e., \"ModelProto.graph\") be\n  //    tensor_a, tensor_b -> MatMul -> tensor_c -> Sigmoid -> tensor_d\n  // and the \"algorithm\" graph be\n  //    tensor_d -> Add -> tensor_e\n  // The combination process results\n  //    tensor_a, tensor_b -> MatMul -> tensor_c -> Sigmoid -> tensor_d -> Add -> tensor_e\n  //\n  // Notice that an input of a node in the \"algorithm\" graph may reference the\n  // output of a node in the inference graph (but not the other way round). Also, inference\n  // node cannot reference inputs of \"algorithm\". With these restrictions, inference graph\n  // can always be run independently without training information.\n  //\n  // By default, this field is an empty graph and its evaluation does not\n  // produce any output. Evaluating the default training step never\n  // update any initializers.\n  optional GraphProto algorithm = 2;\n\n  // This field specifies the bindings from the outputs of \"initialization\" to\n  // some initializers in \"ModelProto.graph.initializer\" and\n  // the \"algorithm.initializer\" in the same TrainingInfoProto.\n  // See \"update_binding\" below for details.\n  //\n  // By default, this field is empty and no initializer would be changed\n  // by the execution of \"initialization\".\n  repeated StringStringEntryProto initialization_binding = 3;\n\n  // Gradient-based training is usually an iterative procedure. In one gradient\n  // descent iteration, we apply\n  //\n  // x = x - r * g\n  //\n  // where \"x\" is the optimized tensor, \"r\" stands for learning rate, and \"g\" is\n  // gradient of \"x\" with respect to a chosen loss. To avoid adding assignments\n  // into the training graph, we split the update equation into\n  //\n  // y = x - r * g\n  // x = y\n  //\n  // The user needs to save \"y = x - r * g\" into TrainingInfoProto.algorithm. To\n  // tell that \"y\" should be assigned to \"x\", the field \"update_binding\" may\n  // contain a key-value pair of strings, \"x\" (key of StringStringEntryProto)\n  // and \"y\" (value of StringStringEntryProto).\n  // For a neural network with multiple trainable (mutable) tensors, there can\n  // be multiple key-value pairs in \"update_binding\".\n  //\n  // The initializers appears as keys in \"update_binding\" are considered\n  // mutable variables. This implies some behaviors\n  // as described below.\n  //\n  //  1. We have only unique keys in all \"update_binding\"s so that two\n  //     variables may not have the same name. This ensures that one\n  //     variable is assigned up to once.\n  //  2. The keys must appear in names of \"ModelProto.graph.initializer\" or\n  //     \"TrainingInfoProto.algorithm.initializer\".\n  //  3. The values must be output names of \"algorithm\" or \"ModelProto.graph.output\".\n  //  4. Mutable variables are initialized to the value specified by the\n  //     corresponding initializer, and then potentially updated by\n  //     \"initializer_binding\"s and \"update_binding\"s in \"TrainingInfoProto\"s.\n  //\n  // This field usually contains names of trainable tensors\n  // (in ModelProto.graph), optimizer states such as momentums in advanced\n  // stochastic gradient methods (in TrainingInfoProto.graph),\n  // and number of training iterations (in TrainingInfoProto.graph).\n  //\n  // By default, this field is empty and no initializer would be changed\n  // by the execution of \"algorithm\".\n  repeated StringStringEntryProto update_binding = 4;\n}\n\n\n// Tensors\n//\n// A serialized tensor value.\nmessage TensorProto {\n  enum DataType {\n    UNDEFINED = 0;\n    // Basic types.\n    FLOAT = 1;   // float\n    UINT8 = 2;   // uint8_t\n    INT8 = 3;    // int8_t\n    UINT16 = 4;  // uint16_t\n    INT16 = 5;   // int16_t\n    INT32 = 6;   // int32_t\n    INT64 = 7;   // int64_t\n    STRING = 8;  // string\n    BOOL = 9;    // bool\n\n    // IEEE754 half-precision floating-point format (16 bits wide).\n    // This format has 1 sign bit, 5 exponent bits, and 10 mantissa bits.\n    FLOAT16 = 10;\n\n    DOUBLE = 11;\n    UINT32 = 12;\n    UINT64 = 13;\n    COMPLEX64 = 14;     // complex with float32 real and imaginary components\n    COMPLEX128 = 15;    // complex with float64 real and imaginary components\n\n    // Non-IEEE floating-point format based on IEEE754 single-precision\n    // floating-point number truncated to 16 bits.\n    // This format has 1 sign bit, 8 exponent bits, and 7 mantissa bits.\n    BFLOAT16 = 16;\n  }\n\n  // The shape of the tensor.\n  repeated int64 dims = 1;\n\n  // The data type of the tensor.\n  DataType data_type = 2;\n\n  // For very large tensors, we may want to store them in chunks, in which\n  // case the following fields will specify the segment that is stored in\n  // the current TensorProto.\n  message Segment {\n    int64 begin = 1;\n    int64 end = 2;\n  }\n  Segment segment = 3;\n\n  // Tensor content must be organized in row-major order.\n  //\n  // Depending on the data_type field, exactly one of the fields below with\n  // name ending in _data is used to store the elements of the tensor.\n\n  // For float and complex64 values\n  // Complex64 tensors are encoded as a single array of floats,\n  // with the real components appearing in odd numbered positions,\n  // and the corresponding imaginary component apparing in the\n  // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i]\n  // is encoded as [1.0, 2.0 ,3.0 ,4.0]\n  // When this field is present, the data_type field MUST be FLOAT or COMPLEX64.\n  repeated float float_data = 4 [packed = true];\n\n  // For int32, uint8, int8, uint16, int16, bool, and float16 values\n  // float16 values must be bit-wise converted to an uint16_t prior\n  // to writing to the buffer.\n  // When this field is present, the data_type field MUST be\n  // INT32, INT16, INT8, UINT16, INT8, BOOL, or FLOAT16\n  repeated int32 int32_data = 5 [packed = true];\n\n  // For strings.\n  // Each element of string_data is a UTF-8 encoded Unicode\n  // string. No trailing null, no leading BOM. The protobuf \"string\"\n  // scalar type is not used to match ML community conventions.\n  // When this field is present, the data_type field MUST be STRING\n  repeated bytes string_data = 6;\n\n  // For int64.\n  // When this field is present, the data_type field MUST be INT64\n  repeated int64 int64_data = 7 [packed = true];\n\n  // Optionally, a name for the tensor.\n  string name = 8; // namespace Value\n\n  // A human-readable documentation for this tensor. Markdown is allowed.\n  string doc_string = 12;\n\n  // Serializations can either use one of the fields above, or use this\n  // raw bytes field. The only exception is the string case, where one is\n  // required to store the content in the repeated bytes string_data field.\n  //\n  // When this raw_data field is used to store tensor value, elements MUST\n  // be stored in as fixed-width, little-endian order.\n  // Floating-point data types MUST be stored in IEEE 754 format.\n  // Complex64 elements must be written as two consecutive FLOAT values, real component first.\n  // Complex128 elements must be written as two consecutive DOUBLE values, real component first.\n  // Boolean type MUST be written one byte per tensor element (00000001 for true, 00000000 for false).\n  //\n  // Note: the advantage of specific field rather than the raw_data field is\n  // that in some cases (e.g. int data), protobuf does a better packing via\n  // variable length storage, and may lead to smaller binary footprint.\n  // When this field is present, the data_type field MUST NOT be STRING or UNDEFINED\n  bytes raw_data = 9;\n\n  // For double\n  // Complex64 tensors are encoded as a single array of doubles,\n  // with the real components appearing in odd numbered positions,\n  // and the corresponding imaginary component apparing in the\n  // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i]\n  // is encoded as [1.0, 2.0 ,3.0 ,4.0]\n  // When this field is present, the data_type field MUST be DOUBLE or COMPLEX128\n  repeated double double_data = 10 [packed = true];\n\n  // For uint64 and uint32 values\n  // When this field is present, the data_type field MUST be\n  // UINT32 or UINT64\n  repeated uint64 uint64_data = 11 [packed = true];\n\n  // Location of the data for this tensor. MUST be one of:\n  // - DEFAULT - data stored inside the protobuf message. Data is stored in raw_data (if set) otherwise in type-specified field.\n  // - EXTERNAL - data stored in an external location as described by external_data field.\n  enum DataLocation {\n    DEFAULT = 0;\n    EXTERNAL = 1;\n  }\n\n  // If value not set, data is stored in raw_data (if set) otherwise in type-specified field.\n  optional DataLocation data_location = 14;\n\n  // Data can be stored inside the protobuf file using type-specific fields or raw_data.\n  // Alternatively, raw bytes data can be stored in an external file, using the external_data field.\n  // external_data stores key-value pairs describing data location. Recognized keys are:\n  // - \"location\" (required) - POSIX filesystem path relative to the directory where the ONNX\n  //                           protobuf model was stored\n  // - \"offset\" (optional) - position of byte at which stored data begins. Integer stored as string.\n  //                         Offset values SHOULD be multiples 4096 (page size) to enable mmap support.\n  // - \"length\" (optional) - number of bytes containing data. Integer stored as string.\n  // - \"checksum\" (optional) - SHA1 digest of file specified in under 'location' key.\n  repeated StringStringEntryProto external_data = 13;\n}\n\n// A serialized sparse-tensor value\nmessage SparseTensorProto {\n  // The sequence of non-default values are encoded as a tensor of shape [NNZ].\n  // The default-value is zero for numeric tensors, and empty-string for string tensors.\n  // values must have a non-empty name present which serves as a name for SparseTensorProto\n  // when used in sparse_initializer list.\n  optional TensorProto values = 1;\n\n  // The indices of the non-default values, which may be stored in one of two formats.\n  // (a) Indices can be a tensor of shape [NNZ, rank] with the [i,j]-th value\n  // corresponding to the j-th index of the i-th value (in the values tensor).\n  // (b) Indices can be a tensor of shape [NNZ], in which case the i-th value\n  // must be the linearized-index of the i-th value (in the values tensor).\n  // The linearized-index can be converted into an index tuple (k_1,...,k_rank)\n  // using the shape provided below.\n  // The indices must appear in ascending order without duplication.\n  // In the first format, the ordering is lexicographic-ordering:\n  // e.g., index-value [1,4] must appear before [2,1]\n  optional TensorProto indices = 2;\n\n  // The shape of the underlying dense-tensor: [dim_1, dim_2, ... dim_rank]\n  repeated int64 dims = 3;\n}\n\n// Defines a tensor shape. A dimension can be either an integer value\n// or a symbolic variable. A symbolic variable represents an unknown\n// dimension.\nmessage TensorShapeProto {\n  message Dimension {\n    oneof value {\n      int64 dim_value = 1;\n      string dim_param = 2;   // namespace Shape\n    };\n    // Standard denotation can optionally be used to denote tensor\n    // dimensions with standard semantic descriptions to ensure\n    // that operations are applied to the correct axis of a tensor.\n    // Refer to https://github.com/onnx/onnx/blob/master/docs/DimensionDenotation.md#denotation-definition\n    // for pre-defined dimension denotations.\n    string denotation = 3;\n  };\n  repeated Dimension dim = 1;\n}\n\n// Types\n//\n// The standard ONNX data types.\nmessage TypeProto {\n\n  message Tensor {\n    // This field MUST NOT have the value of UNDEFINED\n    // This field MUST be present for this version of the IR.\n    TensorProto.DataType elem_type = 1;\n    TensorShapeProto shape = 2;\n  }\n\n\n  oneof value {\n    // The type of a tensor.\n    Tensor tensor_type = 1;\n\n  }\n\n  // An optional denotation can be used to denote the whole \n  // type with a standard semantic description as to what is \n  // stored inside. Refer to https://github.com/onnx/onnx/blob/master/docs/TypeDenotation.md#type-denotation-definition\n  // for pre-defined type denotations.\n  string denotation = 6;\n}\n\n// Operator Sets\n//\n// OperatorSets are uniquely identified by a (domain, opset_version) pair.\nmessage OperatorSetIdProto {\n  // The domain of the operator set being identified.\n  // The empty string (\"\") or absence of this field implies the operator\n  // set that is defined as part of the ONNX specification.\n  // This field MUST be present in this version of the IR when referring to any other operator set.\n  string domain = 1;\n\n  // The version of the operator set being identified.\n  // This field MUST be present in this version of the IR.\n  int64 version = 2;\n}\n\n// Operator/function status.\nenum OperatorStatus {\n    EXPERIMENTAL = 0;\n    STABLE = 1;\n}\n\nmessage FunctionProto {\n  // The name of the function, similar usage of op_type in OperatorProto.\n  // Combined with FunctionProto.domain, this forms the unique identity of\n  // the FunctionProto.\n  optional string name = 1;\n\n  // Deprecated since IR Version 8\n  // optional int64 since_version = 2;\n  reserved 2;\n  reserved \"since_version\";\n\n  // Deprecated since IR Version 8\n  // optional OperatorStatus status = 3;\n  reserved 3;\n  reserved \"status\";\n\n  // The inputs and outputs of the function.\n  repeated string input = 4;\n  repeated string output = 5;\n\n  // The attributes of the function.\n  repeated string attribute = 6;\n\n  // The nodes in the function.\n  repeated NodeProto node = 7;\n  // A human-readable documentation for this function. Markdown is allowed.\n  optional string doc_string = 8;\n\n  // The OperatorSets this function body (graph) relies on.\n  //\n  // All nodes in the function body (graph) will bind against the operator\n  // with the same-domain/same-op_type operator with the HIGHEST version\n  // in the referenced operator sets. This means at most one version can be relied\n  // for one domain.\n  //\n  // The operator sets imported by FunctionProto should be compatible with the ones\n  // imported by ModelProto. Example, if same operator set say 'A' is imported by FunctionProto\n  // and ModelProto then versions for the operator set may be different but,\n  // the operator schema returned for op_type, domain, version combination\n  // for both the versions should be same.\n\n  repeated OperatorSetIdProto opset_import = 9;\n\n  // The domain which this function belongs to. Combined with FunctionProto.name, this forms the unique identity of\n  // the FunctionProto.\n  optional string domain = 10;\n}"
  },
  {
    "path": "onnx/src/data_resolver.rs",
    "content": "use std::fs::File;\nuse std::io::{BufRead, BufReader};\nuse std::path::Path;\nuse tract_hir::internal::*;\n\nuse tract_hir::internal::TractResult;\n\n#[cfg(not(target_family = \"wasm\"))]\npub fn default() -> Box<dyn ModelDataResolver> {\n    Box::new(MmapDataResolver)\n}\n\n#[cfg(target_family = \"wasm\")]\npub fn default() -> Box<dyn ModelDataResolver> {\n    Box::new(FopenDataResolver)\n}\n\npub trait ModelDataResolver {\n    fn read_bytes_from_path(\n        &self,\n        buf: &mut Vec<u8>,\n        p: &Path,\n        offset: usize,\n        length: Option<usize>,\n    ) -> TractResult<()>;\n}\n\npub struct FopenDataResolver;\n\nimpl ModelDataResolver for FopenDataResolver {\n    fn read_bytes_from_path(\n        &self,\n        buf: &mut Vec<u8>,\n        p: &Path,\n        offset: usize,\n        length: Option<usize>,\n    ) -> TractResult<()> {\n        let file = File::open(p).with_context(|| format!(\"Opening {p:?}\"))?;\n        let file_size = file.metadata()?.len() as usize;\n        let length = length.unwrap_or(file_size - offset);\n        buf.reserve(length);\n\n        let mut reader = BufReader::new(file);\n        reader.seek_relative(offset as i64)?;\n        while reader.fill_buf()?.len() > 0 {\n            let num_read = std::cmp::min(reader.buffer().len(), length - buf.len());\n            buf.extend_from_slice(&reader.buffer()[..num_read]);\n            if buf.len() == length {\n                break;\n            }\n            reader.consume(reader.buffer().len());\n        }\n        Ok(())\n    }\n}\n\npub struct MmapDataResolver;\n\nimpl ModelDataResolver for MmapDataResolver {\n    fn read_bytes_from_path(\n        &self,\n        buf: &mut Vec<u8>,\n        p: &Path,\n        offset: usize,\n        length: Option<usize>,\n    ) -> TractResult<()> {\n        let file = File::open(p).with_context(|| format!(\"Opening {p:?}\"))?;\n        let mmap = unsafe { memmap2::Mmap::map(&file)? };\n        match length {\n            Some(length) => buf.extend_from_slice(&mmap[offset..offset + length]),\n            None => buf.extend_from_slice(&mmap[offset..]),\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "onnx/src/lib.rs",
    "content": "#![allow(clippy::len_zero)]\n#[allow(unused_imports)]\n#[macro_use]\nextern crate derive_new;\n#[allow(unused_imports)]\n#[macro_use]\nextern crate log;\nextern crate num_integer;\n#[allow(unused_imports)]\n#[macro_use]\npub extern crate tract_hir;\n\npub mod model;\npub mod ops;\n\n#[allow(clippy::all)]\npub mod pb {\n    include!(\"prost/onnx.rs\");\n}\n\npub mod data_resolver;\npub mod pb_helpers;\npub mod tensor;\n\npub use model::Onnx;\n\npub use tract_hir::tract_core;\npub mod prelude {\n    pub use crate::onnx;\n    pub use tract_hir::prelude::*;\n    pub use tract_onnx_opl::WithOnnx;\n}\npub use tract_onnx_opl::WithOnnx;\n\nuse tract_hir::prelude::*;\n\n#[deprecated(note = \"Please use onnx().model_for_path(..)\")]\npub fn for_path(p: impl AsRef<std::path::Path>) -> TractResult<InferenceModel> {\n    onnx().model_for_path(p)\n}\n\n#[deprecated(note = \"Please use onnx().model_for_read(..)\")]\npub fn for_reader<R: std::io::Read>(mut r: R) -> TractResult<InferenceModel> {\n    onnx().model_for_read(&mut r)\n}\n\npub fn onnx() -> Onnx {\n    let mut ops = crate::model::OnnxOpRegister::default();\n    ops::register_all_ops(&mut ops);\n    Onnx { op_register: ops, ..Onnx::default() }\n}\n"
  },
  {
    "path": "onnx/src/model.rs",
    "content": "use std::path::PathBuf;\nuse std::{fs, path};\n\nuse std::collections::HashMap;\n\nuse tract_hir::internal::*;\nuse tract_hir::prelude::tract_itertools::Itertools;\n\nuse crate::data_resolver::{self, ModelDataResolver};\nuse crate::pb::type_proto::Value;\nuse crate::pb::{self, TensorProto, TypeProto};\nuse crate::tensor::{load_tensor, translate_inference_fact};\nuse prost::Message;\n\npub fn optional_inputs(pb: &pb::NodeProto) -> impl Iterator<Item = Option<usize>> + '_ {\n    let mut real_input = 0;\n    (0..).map(move |i| {\n        if pb.input.get(i).filter(|s| !s.is_empty()).is_some() {\n            real_input += 1;\n            Some(real_input - 1)\n        } else {\n            None\n        }\n    })\n}\n\npub fn optional_outputs(pb: &pb::NodeProto) -> impl Iterator<Item = Option<usize>> + '_ {\n    let mut real_output = 0;\n    (0..).map(move |i| {\n        if pb.output.get(i).filter(|s| !s.is_empty()).is_some() {\n            real_output += 1;\n            Some(real_output - 1)\n        } else {\n            None\n        }\n    })\n}\n\n#[derive(Clone)]\npub struct ParsingContext<'a> {\n    pub onnx_operator_set_version: i64,\n    pub framework: &'a Onnx,\n    pub model: &'a pb::ModelProto,\n    pub parent_graphs: Vec<&'a pb::GraphProto>,\n    pub model_dir: Option<&'a str>,\n    pub template: InferenceModel,\n}\n\n#[derive(Clone, Debug)]\npub struct ParseResult {\n    pub model: InferenceModel,\n    pub unresolved_inputs: Vec<String>,\n    pub outlets_by_name: HashMap<String, OutletId>,\n}\n\nimpl ParsingContext<'_> {\n    pub fn load_tensor(&self, proto: &TensorProto) -> TractResult<Tensor> {\n        load_tensor(&*self.framework.provider, proto, self.model_dir)\n    }\n\n    pub fn parse_graph(&self, graph: &pb::GraphProto) -> TractResult<ParseResult> {\n        let mut ctx = self.clone();\n        ctx.parent_graphs.push(graph);\n        let mut model = self.template.clone();\n        let mut unresolved_inputs = vec![];\n        let mut closures_to_wire = vec![];\n        trace!(\"trying to initialize initializers hashmap...\");\n        #[allow(unused_assignments)]\n        let mut initializers: HashMap<&str, Tensor> = graph\n            .initializer\n            .iter()\n            .map(|name| {\n                let t = self.load_tensor(name)?;\n                Ok((&*name.name, t))\n            })\n            .collect::<TractResult<_>>()?;\n        for (k, v) in initializers.iter().sorted_by_key(|kv| kv.0) {\n            trace!(\"Initializer: {k} {v:?}\");\n        }\n        let mut outlets_by_name = HashMap::<String, OutletId>::new();\n        for input in graph.input.iter() {\n            if let Some(init) = initializers.remove(&*input.name) {\n                trace!(\"Input: {} initialized by {:?}\", input.name, init);\n                let id = model.add_const(input.name.to_owned(), init)?;\n                outlets_by_name.insert(input.name.to_owned(), id);\n            } else {\n                let fact = input.r#type.as_ref().unwrap().value.as_ref().unwrap();\n                #[allow(irrefutable_let_patterns)]\n                let fact: InferenceFact = if let pb::type_proto::Value::TensorType(fact) = fact {\n                    translate_inference_fact(&ctx, fact, true)\n                        .with_context(|| format!(\"translating to fact: {fact:?}\"))?\n                } else {\n                    bail!(\"Can not parse tensor type\");\n                };\n                trace!(\"Input: {} is a source ({:?})\", input.name, fact);\n                let id = model.add_source(&*input.name, fact)?;\n                outlets_by_name.insert(input.name.to_owned(), id);\n            }\n        }\n        for output in graph.output.iter() {\n            trace!(\"Model output: {output:?}\");\n        }\n        for (name, t) in initializers.into_iter().sorted_by_key(|kv| kv.0) {\n            let id = model.add_const(name, t)?;\n            outlets_by_name.insert(name.to_string(), id);\n        }\n        let consts = model.nodes().len();\n        for pbnode in graph.node.iter() {\n            let name = if !pbnode.name.is_empty() {\n                pbnode.name.to_string()\n            } else if pbnode.output.len() > 0 && !pbnode.output[0].is_empty() {\n                pbnode.output[0].to_owned()\n            } else {\n                format!(\"{}-{}\", model.nodes().len(), pbnode.op_type)\n            };\n            trace!(\"Creating node {name}\");\n            let facts = pbnode\n                .output\n                .iter()\n                .filter(|s| !s.is_empty())\n                .map(|_| InferenceFact::default())\n                .collect();\n            trace!(\"  outputs {:?}\", pbnode.output);\n            let (op, closures) = match self.framework.op_register.0.get(&pbnode.op_type) {\n                Some(builder) => (builder)(&ctx, pbnode).with_context(|| {\n                    format!(\"Building node {} ({})\", pbnode.name, pbnode.op_type)\n                })?,\n                None => (\n                    tract_hir::ops::unimpl::UnimplementedOp::new(\n                        pbnode.output.len(),\n                        &*pbnode.op_type,\n                        format!(\"{pbnode:?}\"),\n                    )\n                    .into(),\n                    vec![],\n                ),\n            };\n            let id = model.add_node(name, op, facts)?;\n            for (ix, output) in pbnode.output.iter().filter(|s| !s.is_empty()).enumerate() {\n                outlets_by_name.insert(output.to_owned(), OutletId::new(id, ix));\n                model.set_outlet_label(OutletId::new(id, ix), output.to_owned())?;\n            }\n            for closure in closures {\n                trace!(\"Node {} closes on {}\", model.nodes()[id], closure);\n                closures_to_wire.push((id, closure))\n            }\n        }\n        for (id, pbnode) in graph.node.iter().enumerate() {\n            for (ix, input) in pbnode.input.iter().filter(|s| !s.is_empty()).enumerate() {\n                if !outlets_by_name.contains_key(input) {\n                    let id = model.add_source(input.clone(), InferenceFact::default())?;\n                    unresolved_inputs.push(input.to_string());\n                    outlets_by_name.insert(input.to_string(), id);\n                }\n                let outlet = outlets_by_name[input];\n                model.add_edge(outlet, InletId::new(id + consts, ix))?;\n            }\n        }\n        for (id, closure) in closures_to_wire {\n            if !outlets_by_name.contains_key(&*closure) {\n                let id = model.add_source(closure.clone(), InferenceFact::default())?;\n                unresolved_inputs.push(closure.to_string());\n                outlets_by_name.insert(closure.to_string(), id);\n            }\n            let outlet = outlets_by_name[&*closure];\n            let ix = model.nodes()[id].inputs.len();\n            model.add_edge(outlet, InletId::new(id, ix))?;\n        }\n        let mut outputs = vec![];\n        for output in graph.output.iter() {\n            let mut fact = InferenceFact::default();\n            if self.framework.use_output_shapes {\n                if let Some(f) = output.r#type.as_ref().and_then(|t| t.value.as_ref()) {\n                    let pb::type_proto::Value::TensorType(f) = f;\n                    fact = translate_inference_fact(&ctx, f, false)?\n                };\n            }\n            if self.framework.ignore_output_types {\n                fact = fact.without_datum_type();\n            }\n            let outlet = outlets_by_name[&*output.name];\n            outputs.push(outlet);\n            model.set_outlet_label(outlet, output.name.clone())?;\n            model.set_outlet_fact(outlet, fact)?;\n        }\n        model.select_output_outlets(&outputs)?;\n        if !self.framework.ignore_value_info {\n            for info in &graph.value_info {\n                if let Some(TypeProto { value: Some(Value::TensorType(t)), .. }) = &info.r#type {\n                    if let Some(outlet) = outlets_by_name.get(&info.name) {\n                        let mut pbfact = translate_inference_fact(&ctx, t, false)?;\n                        // be conservative, these are likely to be TDim\n                        if pbfact.datum_type() == Some(i64::datum_type()) {\n                            pbfact = pbfact.without_datum_type();\n                        }\n                        model.set_outlet_fact(*outlet, pbfact)?;\n                    }\n                }\n            }\n        }\n        let result = ParseResult { model, unresolved_inputs, outlets_by_name };\n        Ok(result)\n    }\n}\n\ntype OpBuilder =\n    fn(&ParsingContext, node: &pb::NodeProto) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)>;\n\n#[derive(Clone, Default)]\npub struct OnnxOpRegister(pub HashMap<String, OpBuilder>);\n\nimpl OnnxOpRegister {\n    pub fn insert(&mut self, s: &'static str, builder: OpBuilder) {\n        self.0.insert(s.into(), builder);\n    }\n}\n\n#[derive(Clone)]\npub struct Onnx {\n    pub op_register: OnnxOpRegister,\n    pub use_output_shapes: bool,\n    pub ignore_output_types: bool,\n    pub provider: Arc<dyn ModelDataResolver + Send + Sync>,\n    pub ignore_value_info: bool,\n}\n\nimpl Default for Onnx {\n    fn default() -> Self {\n        Onnx {\n            op_register: Default::default(),\n            use_output_shapes: Default::default(),\n            ignore_output_types: Default::default(),\n            provider: Arc::new(data_resolver::MmapDataResolver),\n            ignore_value_info: false,\n        }\n    }\n}\n\nimpl Onnx {\n    pub fn parse(&self, proto: &pb::ModelProto, path: Option<&str>) -> TractResult<ParseResult> {\n        self.parse_with_template(proto, path, Default::default())\n    }\n    pub fn parse_with_template(\n        &self,\n        proto: &pb::ModelProto,\n        model_dir: Option<&str>,\n        template: InferenceModel,\n    ) -> TractResult<ParseResult> {\n        let onnx_operator_set_version = proto\n            .opset_import\n            .iter()\n            .find(|import| import.domain.is_empty() || import.domain == \"ai.onnx\")\n            .map(|op| op.version)\n            .unwrap_or(0);\n        let graph =\n            proto.graph.as_ref().ok_or_else(|| anyhow!(\"model proto does not contain a graph\"))?;\n        debug!(\"ONNX operator set version: {onnx_operator_set_version:?}\");\n        if onnx_operator_set_version != 0 && !(9..19).contains(&onnx_operator_set_version) {\n            warn!(\n                \"ONNX operator for your model is {onnx_operator_set_version}, tract is only tested against \\\n                  operator set 9 to 18 (included). Your model may still work so this is not a hard fail.\"\n            );\n        }\n        let ctx = ParsingContext {\n            framework: self,\n            model: proto,\n            parent_graphs: vec![],\n            onnx_operator_set_version,\n            model_dir,\n            template,\n        };\n        trace!(\"created ParsingContext\");\n        ctx.parse_graph(graph)\n    }\n\n    pub fn with_ignore_output_shapes(self, ignore: bool) -> Onnx {\n        Self { use_output_shapes: !ignore, ..self }\n    }\n\n    pub fn with_ignore_output_types(self, ignore: bool) -> Onnx {\n        Self { ignore_output_types: ignore, ..self }\n    }\n\n    pub fn with_ignore_value_info(self, ignore: bool) -> Onnx {\n        Self { ignore_value_info: ignore, ..self }\n    }\n\n    pub fn determinize(model: &mut InferenceModel) -> TractResult<()> {\n        use crate::ops::multinomial::Multinomial;\n        for node in model.nodes_mut() {\n            if let Some(op) = node.op_as_mut::<Box<dyn Expansion>>() {\n                if let Some(op) = op.as_any_mut().downcast_mut::<Multinomial>() {\n                    op.seed.get_or_insert(1.0);\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl Framework<pb::ModelProto, InferenceModel> for Onnx {\n    fn model_for_path(&self, p: impl AsRef<path::Path>) -> TractResult<InferenceModel> {\n        let mut path = PathBuf::new();\n        path.push(&p);\n        let mut dir: Option<&str> = None;\n        if let Some(dir_opt) = path.parent() {\n            dir = dir_opt.to_str();\n        }\n        let proto = self.proto_model_for_path(p)?;\n        let ParseResult { model, unresolved_inputs, .. } = self.parse(&proto, dir)?;\n        if unresolved_inputs.len() > 0 {\n            bail!(\"Could not resolve inputs at top-level: {:?}\", unresolved_inputs)\n        }\n        Ok(model)\n    }\n\n    #[cfg(target_family = \"wasm\")]\n    fn proto_model_for_path(&self, p: impl AsRef<path::Path>) -> TractResult<pb::ModelProto> {\n        let p = p.as_ref();\n        let mut file = fs::File::open(p).with_context(|| format!(\"Opening {p:?}\"))?;\n        Ok(self.proto_model_for_read(&mut file)?)\n    }\n\n    #[cfg(not(target_family = \"wasm\"))]\n    fn proto_model_for_path(&self, p: impl AsRef<path::Path>) -> TractResult<pb::ModelProto> {\n        let p = p.as_ref();\n        let map = unsafe {\n            memmap2::Mmap::map(&fs::File::open(p).with_context(|| format!(\"Opening {p:?}\"))?)?\n        };\n        Ok(crate::pb::ModelProto::decode(&*map)?)\n    }\n\n    fn proto_model_for_read(&self, r: &mut dyn std::io::Read) -> TractResult<pb::ModelProto> {\n        let mut v = vec![];\n        r.read_to_end(&mut v)?;\n        let b = bytes::Bytes::from(v);\n        Ok(crate::pb::ModelProto::decode(b)?)\n    }\n\n    fn model_for_proto_model_with_model_template(\n        &self,\n        proto: &pb::ModelProto,\n        template: InferenceModel,\n    ) -> TractResult<InferenceModel> {\n        let ParseResult { model, unresolved_inputs, .. } =\n            self.parse_with_template(proto, None, template)?;\n        if unresolved_inputs.len() > 0 {\n            bail!(\"Could not resolve inputs at top-level: {:?}\", unresolved_inputs)\n        }\n        Ok(model)\n    }\n\n    fn model_for_read(&self, r: &mut dyn std::io::Read) -> TractResult<InferenceModel> {\n        let proto_model = self.proto_model_for_read(r).context(\"Reading proto model\")?;\n        self.model_for_proto_model(&proto_model).context(\"Translating proto model to model\")\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/array/compress.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\n\npub fn compress(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    Ok((Box::new(Compress::new(node.get_attr_opt(\"axis\")?)), vec![]))\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Compress {\n    axis: Option<isize>,\n}\n\nimpl Compress {\n    unsafe fn eval_t<T: Datum>(\n        axis: Option<usize>,\n        input: &Tensor,\n        conds: &[bool],\n        output: &mut Tensor,\n    ) {\n        use tract_ndarray::*;\n        let input = unsafe { input.to_array_view_unchecked::<T>() };\n        if let Some(ax) = axis {\n            for (ixo, ixi) in\n                conds.iter().enumerate().filter(|(_, c)| **c).map(|(ix, _)| ix).enumerate()\n            {\n                unsafe {\n                    output\n                        .to_array_view_mut_unchecked::<T>()\n                        .index_axis_mut(Axis(ax), ixo)\n                        .assign(&input.index_axis(Axis(ax), ixi))\n                };\n            }\n        } else {\n            let output = unsafe { output.as_slice_mut_unchecked::<T>() };\n            let mut ix = 0;\n            for (c, i) in conds.iter().zip(input.iter()) {\n                if *c {\n                    output[ix] = i.clone();\n                    ix += 1;\n                }\n            }\n        }\n    }\n}\n\nimpl Op for Compress {\n    fn name(&self) -> StaticName {\n        \"Compress\".into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for Compress {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (input, conds) = args_2!(inputs);\n        let conds = conds.try_as_plain()?.as_slice()?;\n        let compressed_dim = conds.iter().filter(|c| **c).count();\n        let (shape, axis) = if let Some(axis) = self.axis {\n            let axis = if axis < 0 { axis + input.rank() as isize } else { axis } as usize;\n            let mut shape: TVec<usize> = input.shape().into();\n            shape[axis] = compressed_dim;\n            (shape, Some(axis))\n        } else {\n            (tvec!(compressed_dim), None)\n        };\n        unsafe {\n            let mut output = Tensor::uninitialized_dt(input.datum_type(), &shape)?;\n            dispatch_datum_by_size!(Self::eval_t(input.datum_type())(\n                axis,\n                &input,\n                conds,\n                &mut output\n            ));\n            Ok(tvec!(output.into_tvalue()))\n        }\n    }\n}\n\nimpl InferenceRulesOp for Compress {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()> {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[1].datum_type, bool::datum_type())?;\n        s.equals(&inputs[1].rank, 1)?;\n        if let Some(op_axis) = self.axis {\n            s.equals(&inputs[0].rank, &outputs[0].rank)?;\n            s.given(&inputs[0].rank, move |s, rank| {\n                let rank = rank as usize;\n                let op_axis = if op_axis < 0 { op_axis + rank as isize } else { op_axis } as usize;\n                for axis in 0..rank {\n                    if axis != op_axis {\n                        s.equals(&inputs[0].shape[axis], &outputs[0].shape[axis])?;\n                    }\n                }\n                Ok(())\n            })?;\n        } else {\n            s.equals(&outputs[0].rank, 1)?;\n        }\n        Ok(())\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "onnx/src/ops/array/mod.rs",
    "content": "mod compress;\nmod nonzero;\nmod one_hot;\nmod pad;\nmod shape;\nmod slice;\nmod split;\nmod squeeze;\nmod topk;\nmod trilu;\nmod unsqueeze;\n\nuse tract_hir::internal::*;\nuse tract_hir::ops::array;\n\nuse crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::*;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"ArrayFeatureExtractor\", array_feature_extractor);\n    reg.insert(\"Compress\", compress::compress);\n    reg.insert(\"Concat\", concat);\n    reg.insert(\"ConstantLike\", constant_like);\n    reg.insert(\"ConstantOfShape\", constant_of_shape);\n    reg.insert(\"Expand\", |_, _| Ok((expand(array::MultiBroadcastTo), vec![])));\n    reg.insert(\"EyeLike\", eye_like);\n    reg.insert(\"Flatten\", flatten);\n    reg.insert(\"Gather\", gather);\n    reg.insert(\"GatherElements\", gather_elements);\n    reg.insert(\"GatherND\", gather_nd);\n    reg.insert(\"NonZero\", nonzero::non_zero);\n    reg.insert(\"OneHot\", one_hot::one_hot);\n    reg.insert(\"Range\", |_, _| Ok((expand(array::Range), vec![])));\n    reg.insert(\"Pad\", pad::pad);\n    reg.insert(\"Reshape\", |_, _| Ok((expand(array::Reshape::default()), vec![])));\n    reg.insert(\"Scatter\", scatter_elements);\n    reg.insert(\"ScatterElements\", scatter_elements);\n    reg.insert(\"ScatterND\", |_, node| {\n        let reduction =\n            array::ScatterReduction::parse(node.get_attr_opt(\"reduction\")?.unwrap_or(\"none\"))?;\n        Ok((Box::new(array::ScatterNd::new(reduction)), vec![]))\n    });\n    reg.insert(\"Shape\", shape::shape);\n    reg.insert(\"Size\", |_, _| Ok((expand(array::Size::new(DatumType::TDim)), vec![])));\n    reg.insert(\"Slice\", slice::slice);\n    reg.insert(\"Split\", split::split);\n    reg.insert(\"Squeeze\", squeeze::squeeze);\n    reg.insert(\"Tile\", |_, _| Ok((expand(array::Tile), vec![])));\n    reg.insert(\"TopK\", topk::topk);\n    reg.insert(\"Transpose\", transpose);\n    reg.insert(\"Trilu\", trilu::trilu);\n    reg.insert(\"Unsqueeze\", unsqueeze::unsqueeze);\n}\n\npub fn array_feature_extractor(\n    _ctx: &ParsingContext,\n    _node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    Ok((expand(array::ArrayFeatureExtractor), vec![]))\n}\n\npub fn concat(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr(\"axis\")?;\n    Ok((expand(array::Concat::new(axis)), vec![]))\n}\n\npub fn constant_like(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let value = node.get_attr_opt(\"value\")?.unwrap_or(0.);\n    if node.input.len() == 0 {\n        let dt = node.get_attr_opt(\"dtype\")?.unwrap_or(DatumType::F32);\n        let shape: Vec<usize> = node.get_attr_vec(\"shape\")?;\n        let tensor =\n            tensor0(value).cast_to_dt(dt)?.broadcast_scalar_to_shape(&shape)?.into_arc_tensor();\n        Ok((Box::new(tract_hir::ops::konst::Const::new(tensor)?), vec![]))\n    } else {\n        Ok((Box::new(array::ConstantLike::new(value)), vec![]))\n    }\n}\n\npub fn constant_of_shape(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mut value = match node.get_attr_opt(\"value\")? {\n        Some(val) => ctx.load_tensor(val)?.into_arc_tensor(),\n        None => rctensor0(0.0),\n    };\n    if value.rank() > 0 {\n        if value.len() != 1 {\n            bail!(\"Expected scalar (or vector of length 1), got {:?}\", value);\n        }\n        value = value.nth(0)?.into_arc_tensor();\n    }\n    Ok((expand(array::ConstantOfShape::new(value)), vec![]))\n}\n\npub fn eye_like(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let dt = node.get_attr_opt(\"dtype\")?;\n    let k = node.get_attr_opt(\"k\")?.unwrap_or(0);\n    Ok((Box::new(array::EyeLike::new(dt, k)), vec![]))\n}\n\npub fn flatten(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis: i64 = node.get_attr_opt(\"axis\")?.unwrap_or(1);\n    Ok((expand(array::Flatten::new(axis)), vec![]))\n}\n\npub fn gather(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?.unwrap_or(0);\n    Ok((expand(array::Gather::new(axis)), vec![]))\n}\n\npub fn gather_elements(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?.unwrap_or(0);\n    Ok((expand(array::GatherElements::new(axis)), vec![]))\n}\n\npub fn gather_nd(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let batch_dims = node.get_attr_opt(\"batch_dims\")?.unwrap_or(0);\n    Ok((Box::new(array::GatherNd::new(batch_dims)), vec![]))\n}\n\npub fn scatter_elements(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?.unwrap_or(0);\n    let reduction =\n        array::ScatterReduction::parse(node.get_attr_opt(\"reduction\")?.unwrap_or(\"none\"))?;\n    Ok((expand(array::ScatterElements::new(axis, reduction)), vec![]))\n}\n\npub fn transpose(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let perm = node.get_attr_opt_vec(\"perm\")?;\n    Ok((expand(array::PermuteAxes::new(perm.map(|t| t.into()))), vec![]))\n}\n"
  },
  {
    "path": "onnx/src/ops/array/nonzero.rs",
    "content": "use tract_hir::internal::*;\nuse tract_ndarray::Dimension;\n\nuse crate::model::ParsingContext;\nuse crate::pb::NodeProto;\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct NonZero(Symbol);\n\npub fn non_zero(\n    ctx: &ParsingContext,\n    _node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    // symbol table is shared between all templates and models\n    let x = ctx.template.symbols.new_with_prefix(\"x\");\n    Ok((Box::new(NonZero(x)) as _, vec![]))\n}\n\nimpl NonZero {\n    unsafe fn eval_t<T: Datum + tract_num_traits::Zero>(input: &Tensor) -> TractResult<Tensor> {\n        unsafe {\n            let count = input.as_slice_unchecked::<T>().iter().filter(|d| !d.is_zero()).count();\n            let view = input.to_array_view_unchecked::<T>();\n            let mut output = Tensor::uninitialized::<i64>(&[input.rank(), count])?;\n            let mut view_mut: tract_ndarray::ArrayViewMut2<i64> =\n                output.to_array_view_mut_unchecked::<i64>().into_dimensionality().unwrap();\n            for (i, (coords, _)) in\n                view.indexed_iter().filter(|(_, value)| !value.is_zero()).enumerate()\n            {\n                view_mut\n                    .index_axis_mut(tract_ndarray::Axis(1), i)\n                    .assign(&coords.as_array_view().map(|d| *d as i64));\n            }\n            Ok(output)\n        }\n    }\n}\n\nimpl Op for NonZero {\n    fn name(&self) -> StaticName {\n        \"NonZero\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for NonZero {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        unsafe {\n            let input = args_1!(inputs);\n            let output = if input.datum_type() == bool::datum_type() {\n                Self::eval_t::<u8>(&input)?\n            } else {\n                dispatch_numbers!(Self::eval_t(input.datum_type())(&input))?\n            };\n            Ok(tvec!(output.into_tvalue()))\n        }\n    }\n}\n\nimpl InferenceRulesOp for NonZero {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()> {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, i64::datum_type())?;\n        s.equals(&outputs[0].rank, 2)?;\n        s.equals(&outputs[0].shape[0], inputs[0].rank.bex().to_dim())?;\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n\nimpl TypedOp for NonZero {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(i64::fact(dims![inputs[0].rank(), self.0])))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "onnx/src/ops/array/one_hot.rs",
    "content": "use tract_hir::internal::*;\n\nuse crate::model::ParsingContext;\nuse crate::pb::NodeProto;\n\npub fn one_hot(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?.unwrap_or(-1);\n    Ok((expand(OneHot::new(axis)), vec![]))\n}\n\n#[derive(Debug, PartialEq, Clone, new, Hash)]\nstruct OneHot {\n    axis: i64,\n}\n\nimpl Expansion for OneHot {\n    fn name(&self) -> StaticName {\n        \"OneHot\".into()\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let dim = model.outlet_fact(inputs[1])?;\n        let values = model.outlet_fact(inputs[2])?;\n        if let (Some(dim), Some(values)) = (&dim.konst, &values.konst) {\n            let rank = model.outlet_fact(inputs[0])?.rank();\n            let axis = if self.axis < 0 { self.axis + rank as i64 + 1 } else { self.axis } as usize;\n            let dim = dim.cast_to::<i64>()?;\n            let dim = dim.try_as_plain()?.as_slice::<i64>()?[0];\n            if dim < 0 {\n                bail!(\"Expected positive dimension, got {}\", dim)\n            }\n            let off = values.nth(0)?;\n            let on = values.nth(1)?;\n            let op = tract_core::ops::array::OneHot {\n                axis,\n                dim: dim as usize,\n                off: off.into_arc_tensor(),\n                on: on.into_arc_tensor(),\n            };\n            model.wire_node(prefix, op, &[inputs[0]])\n        } else {\n            bail!(\"Expected dim and value to be determined, got {:?} and {:?}\", dim, values)\n        }\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[2].datum_type, &outputs[0].datum_type)?;\n        s.equals(inputs[0].rank.bex() + 1, &outputs[0].rank)?;\n        s.equals(&inputs[2].rank, 1)?;\n        s.equals(&inputs[2].shape[0], 2.to_dim())?;\n        s.given(&inputs[0].rank, move |s, irank| {\n            let axis = if self.axis < 0 { self.axis + irank + 1 } else { self.axis } as usize;\n            for ix in 0..axis {\n                s.equals(&inputs[0].shape[ix], &outputs[0].shape[ix])?;\n            }\n            for ix in axis + 1..irank as usize + 1 {\n                s.equals(&inputs[0].shape[ix - 1], &outputs[0].shape[ix])?;\n            }\n            s.given(&inputs[1].value, move |s, value| {\n                let dim = value.cast_to_scalar::<i64>()?;\n                s.equals(&outputs[0].shape[axis], dim.to_dim())\n            })\n        })\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/array/pad.rs",
    "content": "use crate::model::{ParsingContext, optional_inputs};\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_hir::ops::array;\nuse tract_hir::prelude::tract_itertools::Itertools;\n\npub fn pad(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    match ctx.onnx_operator_set_version {\n        2..=10 => pad_2(ctx, node),\n        11.. => pad_18(ctx, node), // pad 11 is more restrivie that pad 18 (no axes input)\n        _ => bail!(\"Unsupported operator set for Pad operator\"),\n    }\n}\n\npub fn pad_mode(node: &NodeProto) -> TractResult<array::PadMode> {\n    let value: f32 = node.get_attr_opt(\"value\")?.unwrap_or(0.0);\n    let mode = match node.get_attr_opt(\"mode\")? {\n        None | Some(\"constant\") => None,\n        Some(mode) => node.check_value(\n            \"mode\",\n            match mode {\n                \"reflect\" => TractResult::Ok(Some(array::PadMode::Reflect)),\n                \"edge\" => Ok(Some(array::PadMode::Edge)),\n                _ => bail!(\"Unsupported mode {mode}\"),\n            },\n        )?,\n    }\n    .unwrap_or_else(|| tract_hir::ops::array::PadMode::Constant(Arc::new(value.into())));\n    Ok(mode)\n}\n\npub fn pad_2(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let pads = node.get_attr_tvec(\"pads\")?;\n    let rank = pads.len() / 2;\n    let pads = (0..rank).map(|ax| (pads[ax], pads[ax + rank])).collect();\n    let mode = pad_mode(node)?;\n    Ok((Box::new(tract_hir::ops::array::Pad::new(pads, mode)), vec![]))\n}\n\npub fn pad_18(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mode = pad_mode(node)?;\n    let mut inputs = optional_inputs(node).skip(2);\n    let op = Pad18::new(mode, inputs.next().unwrap(), inputs.next().unwrap());\n    Ok((expand(op), vec![]))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Pad18 {\n    mode: array::PadMode,\n    constant_input: Option<usize>,\n    axes_input: Option<usize>,\n}\n\nimpl Expansion for Pad18 {\n    fn name(&self) -> StaticName {\n        \"Pad\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(\n            inputs,\n            2 + self.constant_input.is_some() as usize + self.axes_input.is_some() as usize,\n        )?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        s.equals(&inputs[1].rank, 1)?;\n        if let Some(constant) = self.constant_input {\n            s.equals(&inputs[0].datum_type, &inputs[constant].datum_type)?;\n            s.equals(&inputs[constant].rank, 0)?;\n        }\n\n        fn do_output_shape<'a, 'r, 'p: 'r>(\n            s: &mut Solver<'r>,\n            inputs: &'p [TensorProxy],\n            outputs: &'p [TensorProxy],\n            axes: Vec<usize>,\n        ) -> TractResult<()> {\n            s.given(&inputs[1].value, move |s, pads| {\n                let pads = pads.cast_to::<TDim>()?;\n                let pads = pads.try_as_plain()?.as_slice::<TDim>()?;\n                let rank = pads.len() / 2;\n                for (ix, axis) in axes.iter().enumerate() {\n                    let left = pads[ix].clone();\n                    let right = pads[ix + rank].clone();\n                    s.equals(\n                        &outputs[0].shape[*axis],\n                        inputs[0].shape[*axis].bex() + left + right,\n                    )?;\n                }\n                Ok(())\n            })?;\n            Ok(())\n        }\n\n        if let Some(axes) = self.axes_input {\n            s.equals(&inputs[axes].rank, 1)?;\n            s.equals(&inputs[1].shape[0], 2 * inputs[axes].shape[0].bex())?;\n            s.given_2(&inputs[0].rank, &inputs[axes].value, move |s, rank, axes| {\n                let axes = axes\n                    .cast_to::<i64>()?\n                    .try_as_plain()?\n                    .as_slice::<i64>()?\n                    .iter()\n                    .map(|x| (if *x < 0 { *x + rank } else { *x }) as usize)\n                    .collect_vec();\n                do_output_shape(s, inputs, outputs, axes)\n            })\n        } else {\n            s.equals(&inputs[1].shape[0], 2 * inputs[0].rank.bex().to_dim())?;\n            s.given(&inputs[0].rank, move |s, rank| {\n                let axes = (0..rank as usize).collect_vec();\n                do_output_shape(s, inputs, outputs, axes)\n            })\n        }\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let mode = if let Some(constant) = self.constant_input {\n            if let Some(c) = model.outlet_fact(inputs[constant])?.konst.clone() {\n                array::PadMode::Constant(c)\n            } else {\n                bail!(\"Pad constant input must be constant\")\n            }\n        } else {\n            self.mode.clone()\n        };\n        let rank = model.outlet_fact(inputs[0])?.rank();\n        let axes = if let Some(axes) = self.axes_input {\n            model\n                .outlet_fact(inputs[axes])?\n                .konst\n                .as_ref()\n                .context(\"Axes must be a constant\")?\n                .cast_to::<i64>()?\n                .try_as_plain()?\n                .as_slice::<i64>()?\n                .iter()\n                .map(|x| (if *x < 0 { *x + rank as i64 } else { *x }) as usize)\n                .collect_vec()\n        } else {\n            (0..rank).collect_vec()\n        };\n        let pads = model\n            .outlet_fact(inputs[1])?\n            .konst\n            .as_ref()\n            .context(\"Expect padding to be constant\")?\n            .cast_to::<i64>()?;\n        let pads = pads.try_as_plain()?.as_slice::<i64>()?;\n\n        let mut fixed_pads = vec![(0, 0); rank];\n        for (ix, &axis) in axes.iter().enumerate() {\n            fixed_pads[axis] = (pads[ix] as usize, pads[ix + pads.len() / 2] as usize);\n        }\n        model.wire_node(name, array::Pad { mode, pads: fixed_pads }, &inputs[0..1])\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/array/shape.rs",
    "content": "use std::ops::Range;\n\nuse crate::model::ParsingContext;\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\n\npub fn shape(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let start = node.get_attr_opt(\"start\")?.unwrap_or(0);\n    let end = node.get_attr_opt(\"end\")?;\n    Ok((expand(Shape { start, end }), vec![]))\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\nstruct Shape {\n    start: i64,\n    end: Option<i64>,\n}\n\nimpl Shape {\n    fn resolve(&self, rank: i64) -> Range<usize> {\n        let start =\n            if self.start >= 0 { self.start } else { (rank + self.start).clamp(0, rank) } as usize;\n        let end =\n            if let Some(end) = self.end { if end >= 0 { end } else { end + rank } } else { rank }\n                .clamp(0, rank) as usize;\n        start..end\n    }\n}\n\nimpl Expansion for Shape {\n    fn name(&self) -> StaticName {\n        \"Shape\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].rank, 1)?;\n        s.equals(&outputs[0].datum_type, TDim::datum_type())?;\n        s.given(&inputs[0].shape, |s, shape| {\n            let rank = shape.len() as i64;\n            let range = self.resolve(rank);\n            s.equals(&outputs[0].value, rctensor1(&shape[range]))?;\n            Ok(())\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let fact = model.outlet_fact(inputs[0])?;\n        let range = self.resolve(fact.rank() as i64);\n        let shape = fact.shape.to_tvec();\n        let wire = model.add_const(prefix, tensor1(&shape[range]))?;\n        Ok(tvec!(wire))\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/array/slice.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\n\npub fn slice(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let v = ctx.onnx_operator_set_version;\n    if (1..10).contains(&v) { slice1(ctx, node) } else { slice10(ctx, node) }\n}\n\nfn slice1(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axes = node.get_attr_opt_vec(\"axes\")?;\n    let begin = node.get_attr_vec(\"starts\")?;\n    let end = node.get_attr_vec(\"ends\")?;\n    Ok((expand(Slice1::new(axes, begin, end)), vec![]))\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Slice1 {\n    axes: Option<Vec<usize>>,\n    starts: Vec<i64>,\n    ends: Vec<i64>,\n}\n\nimpl Expansion for Slice1 {\n    fn name(&self) -> StaticName {\n        \"Slice1\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()> {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        if self.axes.is_none() {\n            s.equals(&inputs[0].rank, self.starts.len() as i64)?;\n            s.equals(&inputs[0].rank, self.ends.len() as i64)?;\n        }\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            (0..shape.len()).try_for_each(move |axis| {\n                let d = &shape[axis];\n                let spec = if let Some(axes) = self.axes.as_ref() {\n                    axes.iter().position(|&a| a == axis).map(|ix| (self.starts[ix], self.ends[ix]))\n                } else {\n                    Some((self.starts[axis], self.ends[axis]))\n                };\n                if let Some((mut b, mut e)) = spec {\n                    if let Ok(d) = d.to_i64() {\n                        if b > d {\n                            b = d;\n                        }\n                        if e > d {\n                            e = d;\n                        }\n                    }\n                    let b = if b < 0 { d.bex() + TDim::from(b) } else { TDim::from(b).bex() };\n                    let e = if e < 0 { d.bex() + TDim::from(e) } else { TDim::from(e).bex() };\n                    s.equals(&outputs[0].shape[axis], e - b)\n                } else {\n                    s.equals(&outputs[0].shape[axis], &shape[axis])\n                }\n            })\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input = target.outlet_fact(inputs[0])?.clone();\n        let mut wire = inputs[0];\n        for (ix, (&b, &e)) in self.starts.iter().zip(self.ends.iter()).enumerate() {\n            let axis = self.axes.as_ref().map(|axes| axes[ix]).unwrap_or(ix);\n            let dim = &input.shape[axis];\n            if let Ok(dim) = dim.to_i64() {\n                let b = (if b >= 0 { b.min(dim) } else { dim + b }) as usize;\n                let e = (if e >= 0 { e.min(dim) } else { dim + e }) as usize;\n                if b > 0 || e < dim as usize {\n                    wire = target.wire_node(\n                        format!(\"{prefix}.axis-{axis}\"),\n                        tract_hir::ops::array::Slice::new(axis, b, e),\n                        [wire].as_ref(),\n                    )?[0];\n                }\n            } else {\n                bail!(\"Can't translate slice: axis={} dim={} b={} e={}\", axis, dim, b, e)\n            }\n        }\n        target.rename_node(wire.node, prefix)?;\n        Ok(tvec!(wire))\n    }\n}\n\nfn slice10(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mut optional_inputs = crate::model::optional_inputs(node).skip(3);\n    Ok((\n        Box::new(tract_hir::ops::array::StridedSlice {\n            begin_mask: 0,\n            end_mask: 0,\n            shrink_axis_mask: 0,\n            optional_axes_input: optional_inputs.next().unwrap(),\n            optional_steps_input: optional_inputs.next().unwrap(),\n        }),\n        vec![],\n    ))\n}\n"
  },
  {
    "path": "onnx/src/ops/array/split.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops::array;\n\nuse crate::model::ParsingContext;\nuse crate::pb::*;\n\npub fn split(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?.unwrap_or(0);\n    if ctx.onnx_operator_set_version < 13 || node.input.len() == 1 {\n        let split = node.get_attr_opt_vec(\"split\")?;\n        Ok((expand(array::Split::new(axis, node.output.len(), split)), vec![]))\n    } else {\n        Ok((expand(Split13 { axis, outputs: node.output.len() }), vec![]))\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct Split13 {\n    axis: isize,\n    outputs: usize,\n}\n\nimpl Expansion for Split13 {\n    fn name(&self) -> StaticName {\n        \"Split13\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        for o in outputs {\n            s.equals(&inputs[0].rank, &o.rank)?;\n            s.equals(&inputs[0].datum_type, &o.datum_type)?;\n        }\n        s.given(&inputs[0].rank, move |s, rank| {\n            let axis = (self.axis + if self.axis < 0 { rank as isize } else { 0 }) as usize;\n            for a in 0..rank as usize {\n                if a != axis {\n                    for o in outputs {\n                        s.equals(&inputs[0].shape[a], &o.shape[a])?;\n                    }\n                }\n            }\n            Ok(())\n        })?;\n        s.given_2(&inputs[0].shape, &inputs[1].value, move |s, shape, splits| {\n            let splits = splits.cast_to::<TDim>()?;\n            let splits = splits.try_as_plain()?.as_slice::<TDim>()?;\n            let axis = self.axis + if self.axis < 0 { shape.len() as isize } else { 0 };\n            for (o, dim) in outputs.iter().zip(splits.iter()) {\n                s.equals(&o.shape[axis as usize], dim)?;\n            }\n            Ok(())\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(splits) = model.outlet_fact(inputs[1])?.konst.as_ref() {\n            let axis = self.axis\n                + if self.axis < 0 { model.outlet_fact(inputs[0])?.rank() as isize } else { 0 };\n            let splits = splits.cast_to::<i64>()?;\n            let splits = splits\n                .try_as_plain()?\n                .as_slice::<i64>()?\n                .iter()\n                .map(|i| *i as usize)\n                .collect::<Vec<_>>();\n            let op = tract_hir::ops::array::Split::new(axis, splits.len(), Some(splits));\n            return op.wire(prefix, model, &inputs[0..1]);\n        }\n        bail!(\"Need splits to be a constant and explicit (constant integers)\")\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(self.outputs)\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/array/squeeze.rs",
    "content": "use tract_hir::internal::*;\n\nuse crate::model::ParsingContext;\nuse crate::pb::*;\n\npub fn squeeze(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    if ctx.onnx_operator_set_version < 13 {\n        let axes = node.get_attr_vec::<i64>(\"axes\")?.into_iter().map(|x| x as isize).collect();\n        Ok((expand(tract_hir::ops::array::Squeeze::new(Some(axes))), vec![]))\n    } else {\n        Ok((expand(Squeeze13), vec![]))\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct Squeeze13;\n\nimpl Expansion for Squeeze13 {\n    fn name(&self) -> StaticName {\n        \"Squeeze13\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        if inputs.len() == 2 {\n            s.given_2(&inputs[0].shape, &inputs[1].value, move |s, shape, axes| {\n                let axes = axes\n                    .cast_to::<i64>()?\n                    .try_as_plain()?\n                    .as_slice::<i64>()?\n                    .iter()\n                    .map(|i| *i as isize)\n                    .collect();\n                let op = tract_hir::ops::array::Squeeze::new(Some(axes));\n                let out_shape = op.output_shape(&shape)?;\n                s.equals(&outputs[0].shape, out_shape)\n            })\n        } else {\n            s.given(&inputs[0].shape, move |s, shape| {\n                let axes = shape\n                    .iter()\n                    .enumerate()\n                    .filter(|(_, dim)| dim.is_one())\n                    .map(|(pos, _)| pos as isize)\n                    .collect();\n                let op = tract_hir::ops::array::Squeeze::new(Some(axes));\n                let out_shape = op.output_shape(&shape)?;\n                s.equals(&outputs[0].shape, out_shape)\n            })\n        }\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if inputs.len() == 2 {\n            if let Some(axes) = model.outlet_fact(inputs[1])?.konst.as_ref() {\n                let axes = axes\n                    .cast_to::<i64>()?\n                    .try_as_plain()?\n                    .as_slice::<i64>()?\n                    .iter()\n                    .map(|i| *i as isize)\n                    .collect();\n                let op = tract_hir::ops::array::Squeeze::new(Some(axes));\n                op.wire(prefix, model, &inputs[0..1])\n            } else {\n                bail!(\"Need axes to be a constant\")\n            }\n        } else {\n            let axes = model\n                .outlet_fact(inputs[0])?\n                .shape\n                .iter()\n                .enumerate()\n                .filter(|(_, dim)| dim.is_one())\n                .map(|(pos, _)| pos as isize)\n                .collect();\n            let op = tract_hir::ops::array::Squeeze::new(Some(axes));\n            op.wire(prefix, model, &inputs[0..1])\n        }\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/array/topk.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\n\npub fn topk(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?.unwrap_or(-1i64);\n    let largest = node.get_attr_opt(\"largest\")?.unwrap_or(1i64) == 1;\n    Ok((expand(Topk { axis, largest }), vec![]))\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\nstruct Topk {\n    axis: i64,\n    largest: bool,\n}\n\nimpl Expansion for Topk {\n    fn name(&self) -> StaticName {\n        \"Topk\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        solver: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_input_arity(outputs, 2)?;\n\n        solver.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        solver.equals(&outputs[1].datum_type, i64::datum_type())?;\n\n        solver.equals(&inputs[0].rank, &outputs[0].rank)?;\n        solver.equals(&inputs[0].rank, &outputs[1].rank)?;\n        solver.equals(&inputs[1].rank, 1)?;\n\n        solver.equals(&inputs[1].shape[0], 1.to_dim())?;\n\n        solver.given(&inputs[0].rank, move |s, rank| {\n            let axis = if self.axis >= 0 { self.axis } else { self.axis + rank } as usize;\n            for ix in 0..rank as usize {\n                if ix != axis {\n                    s.equals(&inputs[0].shape[ix], &outputs[0].shape[ix])?;\n                    s.equals(&inputs[0].shape[ix], &outputs[1].shape[ix])?;\n                } else {\n                    s.given(&inputs[1].value, move |s, k| {\n                        if let Ok(k) = k\n                            .cast_to::<TDim>()\n                            .and_then(|t| t.try_as_plain()?.to_scalar::<TDim>().cloned())\n                        {\n                            s.equals(&outputs[0].shape[ix], k.clone())?;\n                            s.equals(&outputs[1].shape[ix], k)?;\n                        }\n                        Ok(())\n                    })?;\n                }\n            }\n            Ok(())\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input = model.outlet_fact(inputs[0])?;\n        let rank = input.rank();\n        let axis = if self.axis >= 0 { self.axis } else { self.axis + rank as i64 } as usize;\n        let fallback_k = model.symbols.new_with_prefix(\"k\").into();\n        model.wire_node(\n            prefix,\n            tract_core::ops::array::Topk { axis, fallback_k, largest: self.largest },\n            &[inputs[0], inputs[1]],\n        )\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(2)\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/array/trilu.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\n\npub fn trilu(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let upper: i64 = node.get_attr_opt(\"upper\")?.unwrap_or(1);\n    let has_k = node.input.len() == 2;\n    Ok((expand(Trilu { upper: upper == 1, has_k }), vec![]))\n}\n\n#[derive(Debug, Clone)]\nstruct Trilu {\n    upper: bool,\n    has_k: bool,\n}\n\nimpl Expansion for Trilu {\n    fn name(&self) -> StaticName {\n        \"Trilu\".into()\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let augmented_inputs = if self.has_k {\n            inputs.into()\n        } else {\n            let k = model.add_const(format!(\"{prefix}.k\"), tensor0(0i64))?;\n            tvec!(inputs[0], k)\n        };\n        model.wire_node(\n            prefix,\n            tract_core::ops::array::Trilu { upper: self.upper },\n            &augmented_inputs,\n        )\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        solver: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1 + self.has_k as usize)?;\n        check_output_arity(outputs, 1)?;\n        solver.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        solver.equals(&inputs[0].shape, &outputs[0].shape)?;\n        if self.has_k {\n            solver.equals(&inputs[1].datum_type, i64::datum_type())?;\n            solver.equals(&inputs[1].rank, 0)?;\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/array/unsqueeze.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops::array;\n\nuse crate::model::ParsingContext;\nuse crate::pb::*;\n\npub fn unsqueeze(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    if ctx.onnx_operator_set_version < 13 {\n        let axes = node.get_attr_vec::<i64>(\"axes\")?.into_iter().map(|x| x as isize).collect();\n        Ok((expand(array::AddDims::new(axes)), vec![]))\n    } else {\n        Ok((expand(Unsqueeze13), vec![]))\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct Unsqueeze13;\n\nimpl Expansion for Unsqueeze13 {\n    fn name(&self) -> StaticName {\n        \"Unsqueeze13\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.given_2(&inputs[0].shape, &inputs[1].value, move |s, shape, axes| {\n            let axes = axes\n                .cast_to::<i64>()?\n                .try_as_plain()?\n                .as_slice::<i64>()?\n                .iter()\n                .map(|i| *i as isize)\n                .collect();\n            let op = tract_hir::ops::array::AddDims::new(axes);\n            let out_shape = op.output_shape(&shape);\n            s.equals(&outputs[0].shape, out_shape)\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(axes) = model.outlet_fact(inputs[1])?.konst.as_ref() {\n            let axes = axes\n                .cast_to::<i64>()?\n                .try_as_plain()?\n                .as_slice::<i64>()?\n                .iter()\n                .map(|i| *i as isize)\n                .collect();\n            let op = tract_hir::ops::array::AddDims::new(axes);\n            op.wire(prefix, model, &inputs[0..1])\n        } else {\n            bail!(\"Need axes to be a constant\")\n        }\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/cast.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_hir::ops::identity::Identity;\nuse tract_hir::tract_core::ops::element_wise::*;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"Cast\", cast);\n    reg.insert(\"CastLike\", cast_like);\n}\n\nfn cast(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mut to = node.get_attr::<DatumType>(\"to\")?;\n    if to == i64::datum_type() {\n        to = TDim::datum_type();\n    }\n    Ok((ElementWiseOp(Box::new(Cast::new(to)), None).into_hir(), vec![]))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Cast {\n    to: DatumType,\n}\n\nimpl ElementWiseMiniOp for Cast {\n    fn name(&self) -> String {\n        \"onnx.Cast\".into()\n    }\n\n    fn output_type(&self, _input_type: DatumType) -> Option<DatumType> {\n        Some(self.to)\n    }\n\n    fn eval_out_of_place(&self, t: &Tensor, _out_dt: Option<DatumType>) -> TractResult<Tensor> {\n        if t.datum_type() == String::datum_type() && self.to == f32::datum_type() {\n            unsafe {\n                let mut output = Tensor::uninitialized::<f32>(t.shape())?;\n                let output_slice = output.as_slice_mut_unchecked();\n                let input = t.as_slice_unchecked::<String>();\n                for i in 0..input.len() {\n                    output_slice[i] = match &*input[i] {\n                        \"-INF\" => f32::NEG_INFINITY,\n                        \"INF\" | \"+INF\" => f32::INFINITY,\n                        v => v.parse()?,\n                    };\n                }\n                Ok(output)\n            }\n        } else {\n            tract_hir::ops::cast::cast(self.to)\n                .eval_with_session(\n                    usize::MAX,\n                    &TurnState::default(),\n                    tvec!(t.clone().into_tvalue()),\n                )\n                .map(|mut t| t.remove(0).into_tensor())\n        }\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let from = model.outlet_fact(node.inputs[0])?.datum_type;\n        if from == self.to {\n            Ok(Some(TypedModelPatch::replace_single_op(model, node, &node.inputs, Identity)?))\n        } else if from == String::datum_type() && self.to == f32::datum_type() {\n            Ok(None)\n        } else {\n            Ok(Some(TypedModelPatch::replace_single_op(\n                model,\n                node,\n                &node.inputs,\n                tract_hir::ops::cast::cast(self.to),\n            )?))\n        }\n    }\n}\n\nfn cast_like(\n    _ctx: &ParsingContext,\n    _node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    Ok((expand(CastLike), vec![]))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct CastLike;\n\nimpl Expansion for CastLike {\n    fn name(&self) -> StaticName {\n        \"CastLike\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[1].datum_type)?;\n        s.equals(&outputs[0].rank, &inputs[0].rank)?;\n        s.equals(&outputs[0].shape, &inputs[0].shape)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let dt = model.outlet_fact(inputs[1])?.datum_type;\n        model.wire_node(prefix, tract_core::ops::cast::cast(dt), &[inputs[0]])\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/cumsum.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::tract_core::ops::scan::ScanInfo;\n\nuse crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::*;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"CumSum\", cumsum);\n}\n\nfn cumsum(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let reverse = node.get_attr_opt::<i64>(\"reverse\")? == Some(1);\n    let exclusive = node.get_attr_opt::<i64>(\"exclusive\")? == Some(1);\n    Ok((expand(CumSum { reverse, exclusive }), vec![]))\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct CumSum {\n    pub reverse: bool,\n    pub exclusive: bool,\n}\n\nimpl Expansion for CumSum {\n    fn name(&self) -> StaticName {\n        \"CumSum\".into()\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        use tract_core::ops::scan;\n        let axis =\n            model.outlet_fact(inputs[1])?.konst.as_ref().context(\"Axis expected to be a const\")?;\n        let axis = axis.cast_to_scalar::<i64>()?;\n        let data = model.outlet_fact(inputs[0])?.clone();\n        let mut var_shape = data.shape.clone();\n        let axis = if axis < 0 { (axis + data.rank() as i64) as usize } else { axis as usize };\n        let zero = model.add_const(\n            format!(\"{prefix}.zero\"),\n            Tensor::zero_dt(data.datum_type, &[])?.into_arc_tensor(),\n        )?;\n        var_shape.set(axis, 1.to_dim());\n        let init = model.wire_node(\n            format!(\"{prefix}.init\"),\n            tract_core::ops::array::MultiBroadcastTo::new(var_shape.clone()),\n            &[zero],\n        )?[0];\n        let chunk = if self.reverse { -1 } else { 1 };\n        let input_mapping =\n            vec![scan::InputMapping::Scan(ScanInfo { axis, chunk }), scan::InputMapping::State];\n        // outputs will be\n        // acc + x (!exclusive)\n        // acc input (exclusive)\n        let output_mapping = vec![\n            scan::OutputMapping {\n                scan: Some((0, ScanInfo { axis, chunk })),\n                full_dim_hint: None,\n                last_value_slot: None,\n                state: true,\n            },\n            scan::OutputMapping {\n                scan: Some((1, ScanInfo { axis, chunk })),\n                full_dim_hint: None,\n                last_value_slot: None,\n                state: false,\n            },\n        ];\n        let mut body = TypedModel { symbols: model.symbols.clone(), ..TypedModel::default() };\n        let var_fact = data.datum_type.fact(var_shape);\n        let x = body.add_source(\"scan_input\", var_fact.clone())?;\n        let acc = body.add_source(\"acc_input\", var_fact)?;\n        let sum = body.wire_node(\"add\", tract_core::ops::math::add(), &[x, acc])?[0];\n        body.select_output_outlets(&[sum, acc])?;\n        let scan = scan::Scan::new(body, input_mapping, output_mapping, 0)?;\n        let wires = model.wire_node(prefix, scan, &[inputs[0], init])?;\n        let output = wires[self.exclusive as usize];\n        Ok(tvec![output])\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        s.equals(&inputs[1].rank, 0)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/d2s.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"DepthToSpace\", depth_to_space);\n}\n\npub fn depth_to_space(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let blocksize = node.get_attr_opt(\"blocksize\")?.unwrap_or(2);\n    let mode = depth_to_space_mode(node)?;\n    Ok((expand(DepthToSpace { blocksize, mode }), vec![]))\n}\n\npub fn depth_to_space_mode(node: &NodeProto) -> TractResult<DepthToSpaceMode> {\n    let mode = match node.get_attr_opt(\"mode\")? {\n        None => None,\n        Some(mode) => node.check_value(\n            \"mode\",\n            match mode {\n                \"DCR\" => Ok(Some(DepthToSpaceMode::Dcr)),\n                \"CRD\" => Ok(Some(DepthToSpaceMode::Crd)),\n                _ => Err(mode),\n            },\n        )?,\n    }\n    .unwrap_or(DepthToSpaceMode::Dcr);\n    Ok(mode)\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub enum DepthToSpaceMode {\n    Dcr,\n    Crd,\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct DepthToSpace {\n    blocksize: usize,\n    mode: DepthToSpaceMode,\n}\n\nimpl DepthToSpace {\n    pub fn compute_shape(&self, shape: &[TDim]) -> TVec<TDim> {\n        tvec!(\n            shape[0].clone(),\n            shape[1].clone() / (self.blocksize * self.blocksize),\n            shape[2].clone() * self.blocksize,\n            shape[3].clone() * self.blocksize,\n        )\n    }\n\n    pub fn to_axis_ops(&self, shape: &[TDim]) -> TractResult<TVec<AxisOp>> {\n        let mut stack: TVec<AxisOp> = tvec!();\n\n        let ishape_from = tvec!(shape[1].clone());\n        let mut ishape_to = tvec!(\n            self.blocksize.into(),\n            self.blocksize.into(),\n            shape[1].clone() / (self.blocksize * self.blocksize)\n        );\n\n        let oshape_from =\n            tvec!(shape[2].clone(), self.blocksize.into(), shape[3].clone(), self.blocksize.into());\n        let oshape_to = tvec!(shape[2].clone() * self.blocksize, shape[3].clone() * self.blocksize);\n\n        match self.mode {\n            DepthToSpaceMode::Dcr => {\n                stack.push(AxisOp::Reshape(1, ishape_from, ishape_to));\n                stack.push(AxisOp::Move(2, 5));\n                stack.push(AxisOp::Move(1, 3));\n                stack.push(AxisOp::Reshape(2, oshape_from, oshape_to));\n            }\n            DepthToSpaceMode::Crd => {\n                ishape_to.reverse();\n                stack.push(AxisOp::Reshape(1, ishape_from, ishape_to));\n                stack.push(AxisOp::Move(3, 5));\n                stack.push(AxisOp::Move(2, 3));\n                stack.push(AxisOp::Reshape(2, oshape_from, oshape_to));\n            }\n        };\n\n        Ok(stack)\n    }\n}\n\nimpl Expansion for DepthToSpace {\n    fn name(&self) -> StaticName {\n        \"DepthToSpace\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].rank, 4)?;\n        s.equals(&outputs[0].rank, 4)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.given(&inputs[0].shape, move |s, ishape| {\n            let oshape = self.compute_shape(&ishape);\n            s.equals(&outputs[0].shape, ShapeFactoid::from(oshape))\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let ishape = model.outlet_fact(inputs[0])?.shape.to_tvec();\n        let idepth = ishape[1].to_usize()?;\n\n        if idepth % (self.blocksize * self.blocksize) != 0 {\n            bail!(\"DepthToSpace requires input depth to be a multiple of (blocksize * bloksize)\")\n        }\n        let mut wire = tvec!(inputs[0]);\n        for (ix, op) in self.to_axis_ops(&ishape)?.into_iter().enumerate() {\n            wire = model.wire_node(format!(\"{prefix}.{ix}\"), op, &wire)?;\n        }\n        Ok(wire)\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/einsum.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\n\npub fn einsum(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let expr = node.get_attr::<String>(\"equation\")?;\n    let expr: AxesMapping = expr.replace(\"...\", \"*\").parse()?;\n    Ok((expand(EinSum { expr }), vec![]))\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct EinSum {\n    pub expr: AxesMapping,\n}\n\nimpl Expansion for EinSum {\n    fn name(&self) -> StaticName {\n        \"EinSum\".into()\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let ranks = inputs\n            .iter()\n            .map(|o| model.outlet_fact(*o).map(|f| f.rank()))\n            .collect::<TractResult<TVec<_>>>()?;\n        let expr = resolve_ellipsis(&self.expr, &ranks)?;\n        let operating_dt = model.outlet_fact(inputs[0])?.datum_type;\n        model.wire_node(\n            prefix,\n            tract_core::ops::einsum::EinSum { axes: expr, operating_dt, q_params: None },\n            inputs,\n        )\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, self.expr.input_count())?;\n        check_output_arity(outputs, 1)?;\n        for (ix, input) in inputs.iter().enumerate() {\n            s.equals(&input.datum_type, &outputs[0].datum_type)?;\n            // if no elipsis in input spec then rank is known\n            // onnx specifies that all ellipsis usage must have the same rank, but this rule is\n            // broken by pytorch exporter which assumes numpy convention\n            if !self.expr.iter_all_axes().any(|axis| axis.repr == '*' && axis.inputs[ix].len() == 1)\n            {\n                let rank =\n                    self.expr.iter_all_axes().map(|axis| axis.inputs[ix].len()).sum::<usize>();\n                s.equals(rank as i64, &input.rank)?;\n            }\n        }\n\n        let ranks: Vec<_> = inputs.iter().map(|i| &i.rank).collect();\n        s.given_all(ranks, move |s, ranks| {\n            let ranks = ranks.iter().map(|r| *r as usize).collect::<TVec<_>>();\n            let expr = resolve_ellipsis(&self.expr, &ranks)?;\n            s.equals(&outputs[0].rank, expr.rank(InOut::Out(0)) as i64)?;\n            for axis in expr.iter_all_axes() {\n                let mut axes = vec![];\n                if let Some(result) = axis.outputs[0].first() {\n                    axes.push(outputs[0].shape[*result].bex())\n                }\n                for (input_id, input_axis_positions) in axis.inputs.iter().enumerate() {\n                    for position in input_axis_positions {\n                        axes.push(inputs[input_id].shape[*position].bex());\n                    }\n                }\n                s.equals_all(axes)?;\n            }\n            Ok(())\n        })\n    }\n}\n\nfn resolve_ellipsis(expr: &AxesMapping, ranks: &[usize]) -> TractResult<AxesMapping> {\n    if expr.axis('*').is_err() {\n        return Ok(expr.clone());\n    }\n    let elipsed_axes: TVec<usize> = ranks\n        .iter()\n        .enumerate()\n        .filter_map(|(ix, rank)| {\n            if expr.axis_positions(InOut::In(ix), '*').is_ok() {\n                Some(rank + 1 - expr.rank(InOut::In(ix)))\n            } else {\n                None\n            }\n        })\n        .collect();\n    let max_axes = *elipsed_axes.iter().max().unwrap();\n    let axis_resolved: String = ('a'..)\n        .filter(|l| expr.iter_all_axes().all(|axis| *l != axis.repr))\n        .take(max_axes)\n        .collect();\n    //let mut resolved = expr.iter_all_axes().filter(|axis| axis.repr != '*').collect();\n    // lol.\n    let mut resolved = expr.to_string();\n    for axes in elipsed_axes {\n        resolved = resolved.replacen(\n            '*',\n            &axis_resolved.chars().skip(max_axes - axes).collect::<String>(),\n            1,\n        );\n    }\n    // replace in output\n    resolved = resolved.replacen('*', &axis_resolved, 1);\n    resolved.parse()\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_resolve_ellipsis_0() {\n        assert_eq!(\n            resolve_ellipsis(&\"*ii->*i\".parse().unwrap(), &[6]).unwrap().to_string(),\n            \"abcdii->abcdi\"\n        )\n    }\n\n    #[test]\n    fn test_resolve_ellipsis_1() {\n        assert_eq!(\n            resolve_ellipsis(&\"*mk,*kn->*mn\".parse().unwrap(), &[4, 4]).unwrap().to_string(),\n            \"abmk,abkn->abmn\"\n        )\n    }\n\n    #[test]\n    fn test_resolve_ellipsis_2() {\n        assert_eq!(\n            resolve_ellipsis(&\"*ab,*bc->*ac\".parse().unwrap(), &[4, 4]).unwrap().to_string(),\n            \"deab,debc->deac\"\n        )\n    }\n\n    #[test]\n    fn test_resolve_numpy_ellipsis_1() -> TractResult<()> {\n        let expr: AxesMapping = \"*gi,*gih->*gh\".parse()?;\n        let resolved = resolve_ellipsis(&expr, &[4, 3])?;\n        assert_eq!(resolved, \"abgi,gih->abgh\".parse().unwrap());\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/fft.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\nuse tract_hir::ops::array::Pad;\nuse tract_hir::ops::cast::cast;\nuse tract_hir::ops::math::div;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"DFT\", dft);\n    reg.insert(\"STFT\", stft);\n    reg.insert(\"MelWeightMatrix\", mel_weight_matrix);\n    reg.insert(\"BlackmanWindow\", window);\n    reg.insert(\"HammingWindow\", window);\n    reg.insert(\"HannWindow\", window);\n}\n\nfn dft(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?.unwrap_or(1);\n    let inverse = node.get_attr_opt(\"inverse\")?.unwrap_or(0i64) != 0;\n    let onesided = node.get_attr_opt(\"onesided\")?.unwrap_or(0) != 0;\n    if node.input.len() > 1 {\n        bail!(\"length input is not implemented\")\n    }\n    Ok((expand(Dft { axis, inverse, onesided, has_length_input: node.input.len() == 2 }), vec![]))\n}\n\nfn stft(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let onesided = node.get_attr_opt(\"onesided\")?.unwrap_or(1) != 0;\n    let mut options = crate::model::optional_inputs(node).skip(2);\n    Ok((\n        expand(Stft {\n            onesided,\n            optional_window_input: options.next().unwrap(),\n            optional_frame_length_input: options.next().unwrap(),\n        }),\n        vec![],\n    ))\n}\n\nfn mel_weight_matrix(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let datum_type = node.get_attr_opt(\"output_datatype\")?.unwrap_or(DatumType::F32);\n    Ok((expand(MelWeightMatrix { datum_type }), vec![]))\n}\n\nfn window(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let datum_type = node.get_attr_opt(\"output_datatype\")?.unwrap_or(DatumType::F32);\n    let periodic = node.get_attr_opt(\"periodic\")?.unwrap_or(1i64) == 1i64;\n    let window = match &*node.op_type {\n        \"BlackmanWindow\" => StftWindowType::Blackman,\n        \"HammingWindow\" => StftWindowType::Hamming,\n        \"HannWindow\" => StftWindowType::Hann,\n        _ => unreachable!(),\n    };\n    Ok((expand(StftWindow { datum_type, periodic, window }), vec![]))\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\nstruct Dft {\n    axis: usize,\n    inverse: bool,\n    onesided: bool,\n    has_length_input: bool,\n}\n\nimpl Expansion for Dft {\n    fn name(&self) -> StaticName {\n        \"DFT\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1 + self.has_length_input as usize)?;\n        check_output_arity(outputs, 1)?;\n\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        if self.has_length_input {\n            s.equals(&inputs[1].rank, 0)?;\n        }\n        s.given(&inputs[0].rank, |s, rank| {\n            for ax in 0..rank as usize - 1 {\n                if ax != self.axis {\n                    s.equals(&inputs[0].shape[ax], &outputs[0].shape[ax])?;\n                }\n            }\n            s.equals(&outputs[0].shape[rank as usize - 1], 2.to_dim())?;\n            Ok(())\n        })?;\n        if self.has_length_input {\n            s.given(&inputs[1].value[0], |s, len| {\n                s.equals(len.to_dim(), &outputs[0].shape[self.axis])\n            })?;\n        } else {\n            s.equals(&inputs[0].shape[self.axis], &outputs[0].shape[self.axis])?;\n        }\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let fact = model.outlet_fact(inputs[0])?.clone();\n        let mut wire: TVec<OutletId> = inputs.into();\n        if fact.shape.last() == Some(&1.to_dim()) {\n            let mut pads = vec![(0, 0); fact.rank() - 1];\n            pads.push((0, 1));\n            wire = model.wire_node(\n                format!(\"{prefix}.add_imaginary\"),\n                Pad { mode: tract_hir::ops::array::PadMode::Constant(rctensor0(0f32)), pads },\n                &wire,\n            )?;\n        };\n        wire = model.wire_node(\n            format!(\"{prefix}.fft\"),\n            tract_core::ops::fft::Fft { axis: self.axis, inverse: self.inverse },\n            &wire,\n        )?;\n        if self.inverse {\n            let len = model.add_const(\n                format!(\"{prefix}.len\"),\n                tensor0(fact.shape[self.axis].clone()).broadcast_into_rank(fact.rank())?,\n            )?;\n            let casted =\n                model.wire_node(format!(\"{prefix}.cast\"), cast(fact.datum_type), &[len])?;\n            wire = model.wire_node(format!(\"{prefix}.norm\"), div(), &[wire[0], casted[0]])?;\n        }\n        if self.onesided {\n            let frame = fact.shape[self.axis].clone() / 2 + 1;\n            wire = model.wire_node(\n                format!(\"{prefix}.onesided\"),\n                tract_core::ops::array::Slice::new(2, 0, frame),\n                &wire,\n            )?;\n        }\n        Ok(wire)\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\nstruct Stft {\n    onesided: bool,\n    optional_window_input: Option<usize>,\n    optional_frame_length_input: Option<usize>,\n}\n\nimpl Expansion for Stft {\n    fn name(&self) -> StaticName {\n        \"STFT\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(\n            inputs,\n            2 + self.optional_window_input.is_some() as usize\n                + self.optional_frame_length_input.is_some() as usize,\n        )?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, 3)?;\n        s.equals(&outputs[0].rank, 4)?;\n        s.equals(&outputs[0].shape[0], &inputs[0].shape[0])?;\n        s.equals(&outputs[0].shape[3], 2.to_dim())?;\n        let mut frame_len = None;\n        let mut frame_len_2 = None; // ugly ! but exps are not clonable\n        if let Some(l) = self.optional_frame_length_input {\n            s.equals(&inputs[l].datum_type, i64::datum_type())?;\n            s.equals(&inputs[l].rank, 0)?;\n            frame_len = Some(inputs[l].value[0].bex().to_dim());\n            frame_len_2 = Some(inputs[l].value[0].bex().to_dim());\n        }\n        if let Some(w) = self.optional_window_input {\n            s.equals(&inputs[w].datum_type, &inputs[0].datum_type)?;\n            s.equals(&inputs[w].rank, 1)?;\n            frame_len = Some(inputs[w].shape[0].bex());\n            frame_len_2 = Some(inputs[w].shape[0].bex());\n        }\n        if let (Some(w), Some(l)) = (self.optional_window_input, self.optional_frame_length_input) {\n            s.equals(inputs[l].value[0].bex().to_dim(), &inputs[w].shape[0])?;\n        }\n        if let Some(frame_len) = frame_len {\n            s.given_3(\n                &inputs[0].shape[1],\n                frame_len,\n                &inputs[1].value[0],\n                |s, signal, frame, stride| {\n                    let frames = (signal - frame) / stride + 1;\n                    s.equals(&outputs[0].shape[1], frames)?;\n                    Ok(())\n                },\n            )?;\n        }\n        if let Some(frame_len) = frame_len_2 {\n            s.given(frame_len, |s, frame_len| {\n                let fst_len = if self.onesided { frame_len / 2 + 1 } else { frame_len };\n                s.equals(&outputs[0].shape[2], fst_len)\n            })?;\n        }\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let fact = model.outlet_fact(inputs[0])?.clone();\n        let mut wire: TVec<OutletId> = tvec!(inputs[0]);\n        let (frame, window) = if let Some(w) = self.optional_window_input {\n            let window = model\n                .outlet_fact(inputs[w])?\n                .konst\n                .clone()\n                .context(\"STFT expects a constant window\")?;\n            (window.len(), Some(window))\n        } else if let Some(fl) = self.optional_frame_length_input {\n            let frame = model\n                .outlet_fact(inputs[fl])?\n                .konst\n                .as_ref()\n                .context(\"STFT expects a constant frame length\")?\n                .cast_to_scalar::<i64>()? as usize;\n            (frame, None)\n        } else {\n            bail!(\"Need window or frame len\")\n        };\n        let stride = model\n            .outlet_fact(inputs[1])?\n            .konst\n            .as_ref()\n            .context(\"STFT expects a constant frame_step\")?\n            .cast_to_scalar::<i64>()? as usize;\n        if fact.shape.last() == Some(&1.to_dim()) {\n            let mut pads = vec![(0, 0); fact.rank() - 1];\n            pads.push((0, 1));\n            wire = model.wire_node(\n                format!(\"{prefix}.add_imaginary\"),\n                Pad { mode: tract_hir::ops::array::PadMode::Constant(rctensor0(0f32)), pads },\n                &wire,\n            )?;\n        };\n        wire = model.wire_node(\n            format!(\"{prefix}.fft\"),\n            tract_core::ops::fft::Stft { axis: 1, frame, window, stride },\n            &wire,\n        )?;\n        if self.onesided {\n            wire = model.wire_node(\n                format!(\"{prefix}.onesided\"),\n                tract_core::ops::array::Slice::new(2, 0, frame / 2 + 1),\n                &wire,\n            )?;\n        }\n        Ok(wire)\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct MelWeightMatrix {\n    datum_type: DatumType,\n}\n\nimpl Expansion for MelWeightMatrix {\n    fn name(&self) -> StaticName {\n        \"MelWeightMatrix\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 5)?;\n        check_output_arity(outputs, 1)?;\n        for input in inputs {\n            s.equals(&input.rank, 0)?;\n        }\n        s.equals(&outputs[0].datum_type, self.datum_type)?;\n        s.equals(&outputs[0].rank, 2)?;\n        s.given(&inputs[1].value[0], |s, dft_length| {\n            s.equals(&outputs[0].shape[0], (dft_length / 2 + 1).to_dim())\n        })?;\n        s.given(&inputs[0].value[0], |s, num_mel_bins| {\n            s.equals(&outputs[0].shape[1], num_mel_bins.to_dim())\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let (\n            Some(num_mel_bins),\n            Some(dft_length),\n            Some(sample_rate),\n            Some(lower_edge_hertz),\n            Some(upper_edge_hertz),\n        ) = (\n            model.outlet_fact(inputs[0])?.konst.as_ref(),\n            model.outlet_fact(inputs[1])?.konst.as_ref(),\n            model.outlet_fact(inputs[2])?.konst.as_ref(),\n            model.outlet_fact(inputs[3])?.konst.as_ref(),\n            model.outlet_fact(inputs[4])?.konst.as_ref(),\n        )\n        else {\n            bail!(\"Expect all inputs to be constants\")\n        };\n        let num_mel_bins = num_mel_bins.cast_to_scalar::<i64>()? as usize;\n        let dft_length = dft_length.cast_to_scalar::<i64>()? as usize;\n        let sample_rate = sample_rate.cast_to_scalar::<i64>()? as usize;\n        let lower_edge_hertz = lower_edge_hertz.cast_to_scalar::<f32>()?;\n        let upper_edge_hertz = upper_edge_hertz.cast_to_scalar::<f32>()?;\n\n        let num_spectrogram_bins = dft_length / 2 + 1;\n        let low_frequency_mel = 2595. * (1. + lower_edge_hertz / 700.).log10();\n        let high_frequency_mel = 2595. * (1. + upper_edge_hertz / 700.).log10();\n        let mel_step = (high_frequency_mel - low_frequency_mel) / (num_mel_bins + 2) as f32;\n\n        let frequency_bins: Vec<usize> = (0..num_mel_bins + 2)\n            .map(|ix| {\n                let freq = ix as f32 * mel_step + low_frequency_mel;\n                let freq = 700. * (10f32.powf(freq / 2596.) - 1.);\n                let freq = ((dft_length + 1) as f32 * freq) / sample_rate as f32;\n                freq as usize\n            })\n            .collect();\n\n        let mut output = Tensor::zero::<f32>(&[num_spectrogram_bins, num_mel_bins])?;\n        let mut output_plain = output.try_as_plain_mut()?;\n        let mut view = output_plain.to_array_view_mut::<f32>()?.into_dimensionality()?;\n        for i in 0..num_mel_bins {\n            let lower = frequency_bins[i];\n            let center = frequency_bins[i + 1];\n            let higher = frequency_bins[i + 2];\n            if center == lower {\n                view[(center, i)] = 1.;\n            } else {\n                for j in lower..center + 1 {\n                    view[(j, i)] = (j - lower) as f32 / (center - lower) as f32;\n                }\n            }\n            if higher > center {\n                for j in center..higher {\n                    view[(j, i)] = (higher - j) as f32 / (higher - center) as f32;\n                }\n            }\n        }\n        let wire = model.add_const(prefix, output.cast_to_dt(self.datum_type)?.into_owned())?;\n        Ok(tvec!(wire))\n    }\n}\n\n#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]\nenum StftWindowType {\n    Blackman,\n    Hamming,\n    Hann,\n}\n\nimpl StftWindowType {\n    fn generate(&self, size: usize, periodic: bool) -> TractResult<Tensor> {\n        use std::f32::consts::PI;\n        let divisor = ((size - 1 + periodic as usize) as f32).recip();\n        let mut output = Tensor::zero::<f32>(&[size])?;\n        match self {\n            Self::Blackman => {\n                output.try_as_plain_mut()?.as_slice_mut::<f32>()?.iter_mut().enumerate().for_each(\n                    |(ix, y)| {\n                        *y = 0.42 - 0.5 * (2. * PI * ix as f32 * divisor).cos()\n                            + 0.08 * (4. * PI * ix as f32 * divisor).cos()\n                    },\n                )\n            }\n            Self::Hamming => {\n                output.try_as_plain_mut()?.as_slice_mut::<f32>()?.iter_mut().enumerate().for_each(\n                    |(ix, y)| {\n                        *y = (25. / 46.) - (21. / 46.) * (2. * PI * ix as f32 * divisor).cos()\n                    },\n                )\n            }\n            Self::Hann => output\n                .try_as_plain_mut()?\n                .as_slice_mut::<f32>()?\n                .iter_mut()\n                .enumerate()\n                .for_each(|(ix, y)| *y = 0.5 - 0.5 * (2. * PI * ix as f32 * divisor).cos()),\n        }\n        Ok(output)\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct StftWindow {\n    datum_type: DatumType,\n    periodic: bool,\n    window: StftWindowType,\n}\n\nimpl Expansion for StftWindow {\n    fn name(&self) -> StaticName {\n        format!(\"StftWindow<{:?}>\", self.window).into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].rank, 0)?;\n        s.equals(&outputs[0].datum_type, self.datum_type)?;\n        s.equals(&outputs[0].rank, 1)?;\n        s.given(&inputs[0].value[0], |s, length| s.equals(&outputs[0].shape[0], length.to_dim()))\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let len = model\n            .outlet_fact(inputs[0])?\n            .konst\n            .as_ref()\n            .context(\"Expect constant input size\")?\n            .cast_to_scalar::<i64>()? as usize;\n        let window =\n            self.window.generate(len, self.periodic)?.cast_to_dt(self.datum_type)?.into_owned();\n        let wire = model.add_const(prefix, window)?;\n        Ok(tvec!(wire))\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/grid_sample.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_onnx_opl::grid_sample::{InterpolationMode, PaddingMode};\n\npub fn grid_sample(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mode = match node.get_attr_opt(\"mode\")?.unwrap_or(\"linear\") {\n        \"bilinear\" | \"linear\" => InterpolationMode::Bilinear,\n        \"nearest\" => InterpolationMode::Nearest,\n        \"bicubic\" | \"cubic\" => InterpolationMode::Bicubic,\n        s => bail!(\"Unsupported GridSample mode: {}\", s),\n    };\n    let padding_mode = match node.get_attr_opt(\"padding_mode\")?.unwrap_or(\"zeros\") {\n        \"zeros\" => PaddingMode::Zeros,\n        \"border\" => PaddingMode::Border,\n        \"reflection\" => PaddingMode::Reflection,\n        s => bail!(\"Unsupported GridSample padding_mode: {}\", s),\n    };\n    let align_corners = node.get_attr_opt::<i64>(\"align_corners\")?.unwrap_or(0) != 0;\n\n    match ctx.onnx_operator_set_version {\n        16.. => {}\n        v => bail!(\"Unsupported operator set for GridSample operator ({v})\"),\n    }\n\n    Ok((expand(GridSampleInference { mode, padding_mode, align_corners }), vec![]))\n}\n\n#[derive(Clone, Debug)]\nstruct GridSampleInference {\n    mode: InterpolationMode,\n    padding_mode: PaddingMode,\n    align_corners: bool,\n}\n\nimpl Expansion for GridSampleInference {\n    fn name(&self) -> StaticName {\n        \"GridSample\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n\n        s.equals(&inputs[0].rank, &inputs[1].rank)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n\n        s.equals(&inputs[0].shape[0], &inputs[1].shape[0])?;\n        s.equals(&inputs[0].shape[0], &outputs[0].shape[0])?;\n\n        s.equals(&inputs[0].shape[1], &outputs[0].shape[1])?;\n\n        s.given(&inputs[0].rank, move |s, rank| {\n            let rank = rank as usize;\n            let spatial_rank = rank - 2;\n            for d in 0..spatial_rank {\n                s.equals(&outputs[0].shape[2 + d], &inputs[1].shape[1 + d])?;\n            }\n            s.equals(&inputs[1].shape[rank - 1], (spatial_rank as i64).to_dim())?;\n            Ok(())\n        })\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        model.wire_node(\n            name,\n            tract_onnx_opl::grid_sample::GridSample {\n                mode: self.mode.clone(),\n                padding_mode: self.padding_mode.clone(),\n                align_corners: self.align_corners,\n            },\n            inputs,\n        )\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/logic.rs",
    "content": "use crate::model::OnnxOpRegister;\nuse crate::model::ParseResult;\nuse crate::model::ParsingContext;\nuse crate::pb::NodeProto;\nuse tract_core::ops;\nuse tract_hir::internal::*;\nuse tract_hir::ops::binary::BinIntoHir;\nuse tract_itertools::Itertools;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"Not\", |_, _| Ok((ops::logic::not().into_hir(), vec![])));\n    reg.insert(\"And\", |_, _| Ok((ops::logic::And.into_hir(), vec![])));\n    reg.insert(\"Or\", |_, _| Ok((ops::logic::Or.into_hir(), vec![])));\n    reg.insert(\"Xor\", |_, _| Ok((ops::logic::Xor.into_hir(), vec![])));\n\n    reg.insert(\"Equal\", |_, _| Ok((ops::logic::CompEq.into_hir(), vec![])));\n    reg.insert(\"Greater\", |_, _| Ok((ops::logic::CompGT.into_hir(), vec![])));\n    reg.insert(\"Less\", |_, _| Ok((ops::logic::CompLT.into_hir(), vec![])));\n    reg.insert(\"LessOrEqual\", |_, _| Ok((ops::logic::CompLTE.into_hir(), vec![])));\n    reg.insert(\"GreaterOrEqual\", |_, _| Ok((ops::logic::CompGTE.into_hir(), vec![])));\n\n    reg.insert(\"Where\", |_, _| Ok((expand(tract_hir::ops::logic::Iff), vec![])));\n\n    reg.insert(\"If\", _if)\n}\n\npub fn _if(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let graph_then = node.get_attr(\"then_branch\")?;\n    let graph_else = node.get_attr(\"else_branch\")?;\n    let ParseResult { model: then_body, unresolved_inputs: unresolved_inputs_then, .. } =\n        ctx.parse_graph(graph_then)?;\n    let ParseResult { model: else_body, unresolved_inputs: unresolved_inputs_else, .. } =\n        ctx.parse_graph(graph_else)?;\n    let unresolved_inputs: Vec<String> = unresolved_inputs_then\n        .iter()\n        .chain(unresolved_inputs_else.iter())\n        .sorted()\n        .unique()\n        .cloned()\n        .collect();\n    let then_input_mapping = unresolved_inputs_then\n        .iter()\n        .map(|i| unresolved_inputs.iter().position(|s| s == i).unwrap() + 1)\n        .collect();\n    let else_input_mapping = unresolved_inputs_else\n        .iter()\n        .map(|i| unresolved_inputs.iter().position(|s| s == i).unwrap() + 1)\n        .collect();\n    Ok((\n        Box::new(If { then_body, then_input_mapping, else_body, else_input_mapping }),\n        unresolved_inputs,\n    ))\n}\n\n#[derive(Debug, Clone, new)]\npub struct If {\n    pub then_body: InferenceModel,\n    then_input_mapping: Vec<usize>,\n    pub else_body: InferenceModel,\n    else_input_mapping: Vec<usize>,\n}\nimpl PartialEq for If {\n    fn eq(&self, _: &Self) -> bool {\n        false\n    }\n}\nimpl Eq for If {}\n\nimpl Op for If {\n    fn name(&self) -> StaticName {\n        \"If\".into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for If {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let cond = inputs[0].cast_to_scalar::<bool>()?;\n        let (input_mapping, body) = if cond {\n            (&self.then_input_mapping, &self.then_body)\n        } else {\n            (&self.else_input_mapping, &self.else_body)\n        };\n        let inputs: TVec<TValue> = input_mapping.iter().map(|&ix| inputs[ix].clone()).collect();\n        body.clone().into_runnable()?.run(inputs)\n    }\n}\n\nimpl InferenceOp for If {\n    fn infer_facts(\n        &mut self,\n        inputs: TVec<&InferenceFact>,\n        outputs: TVec<&InferenceFact>,\n        observed: TVec<&InferenceFact>,\n    ) -> TractResult<(TVec<InferenceFact>, TVec<InferenceFact>, TVec<InferenceFact>)> {\n        let mut inputs: TVec<InferenceFact> = inputs.into_iter().cloned().collect();\n        let mut outputs: TVec<InferenceFact> = outputs.into_iter().cloned().collect();\n        loop {\n            let mut changed = false;\n            changed = changed || inputs[0].datum_type.unify_with(&bool::datum_type().into())?;\n            for (body_ix, outer_ix) in self.then_input_mapping.iter().enumerate() {\n                changed = changed\n                    || self\n                        .then_body\n                        .input_fact_mut(body_ix)?\n                        .unify_with_mut(&mut inputs[*outer_ix])?;\n            }\n            for (body_ix, outer_ix) in self.else_input_mapping.iter().enumerate() {\n                changed = changed\n                    || self\n                        .else_body\n                        .input_fact_mut(body_ix)?\n                        .unify_with_mut(&mut inputs[*outer_ix])?;\n            }\n            if let Some(a) = inputs[0].value.concretize() {\n                let a = a.cast_to_scalar()?;\n                let body = if a { &mut self.then_body } else { &mut self.else_body };\n                for oix in 0..body.output_outlets()?.len() {\n                    changed =\n                        changed || body.output_fact_mut(oix)?.unify_with_mut(&mut outputs[oix])?;\n                }\n            } else {\n                for ix in 0..self.nboutputs()? {\n                    changed = changed\n                        || self\n                            .then_body\n                            .output_fact_mut(ix)?\n                            .shape\n                            .unify_with_mut(&mut outputs[ix].shape)?\n                        || self\n                            .else_body\n                            .output_fact_mut(ix)?\n                            .shape\n                            .unify_with_mut(&mut outputs[ix].shape)?\n                        || self\n                            .then_body\n                            .output_fact_mut(ix)?\n                            .datum_type\n                            .unify_with_mut(&mut outputs[ix].datum_type)?\n                        || self\n                            .else_body\n                            .output_fact_mut(ix)?\n                            .datum_type\n                            .unify_with_mut(&mut outputs[ix].datum_type)?;\n                }\n            }\n            changed = changed || self.then_body.analyse(false)?;\n            changed = changed || self.else_body.analyse(false)?;\n            if !changed {\n                return Ok((inputs, outputs, observed.into_iter().cloned().collect()));\n            }\n        }\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        let then_outputs = self.then_body.outputs.len();\n        let else_outputs = self.else_body.outputs.len();\n        ensure!(\n            then_outputs == else_outputs,\n            \"If Operators expect the `then_branch` {} and `else_branch` {} to produce the same number of outputs\",\n            then_outputs,\n            else_outputs\n        );\n        Ok(then_outputs)\n    }\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        let then_body = self.then_body.clone().into_typed()?;\n        let else_body = self.else_body.clone().into_typed()?;\n        let inputs: TVec<_> = node.inputs.iter().map(|o| mapping[o]).collect();\n        let op = tract_core::ops::logic::IfThenElse {\n            then_body,\n            else_body,\n            then_input_mapping: self.then_input_mapping.clone(),\n            else_input_mapping: self.else_input_mapping.clone(),\n        };\n        target.wire_node(self.name(), op, &inputs)\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "onnx/src/ops/math/clip.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_hir::ops::logic::wire_with_rank_broadcast;\n\npub fn clip(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    match ctx.onnx_operator_set_version {\n        6..=10 => clip_6(ctx, node),\n        v if v >= 10 => clip_11(ctx, node),\n        _ => bail!(\"Unsupported operator set for Clip operator\"),\n    }\n}\n\npub fn clip_6(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let min: Option<f32> = node.get_attr_opt(\"min\")?;\n    let max: Option<f32> = node.get_attr_opt(\"max\")?;\n    Ok((expand(tract_hir::ops::activations::Clip::new(min, max)), vec![]))\n}\n\npub fn clip_11(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mut options = crate::model::optional_inputs(node).skip(1);\n    let op = Clip11::new(options.next().unwrap(), options.next().unwrap());\n    Ok((expand(op), vec![]))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Clip11 {\n    input_min: Option<usize>,\n    input_max: Option<usize>,\n}\n\nimpl Expansion for Clip11 {\n    fn name(&self) -> StaticName {\n        \"Clip\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(\n            inputs,\n            1 + self.input_min.is_some() as usize + self.input_max.is_some() as usize,\n        )?;\n        check_output_arity(outputs, 1)?;\n        if let Some(input) = self.input_min {\n            s.equals(&inputs[0].datum_type, &inputs[input].datum_type)?;\n        }\n        if let Some(input) = self.input_max {\n            s.equals(&inputs[0].datum_type, &inputs[input].datum_type)?;\n        }\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let mut wire = inputs[0];\n        if let Some(min) = self.input_min {\n            wire = wire_with_rank_broadcast(\n                format!(\"{name}.min\"),\n                model,\n                tract_hir::ops::math::max(),\n                &[wire, inputs[min]],\n            )?[0];\n        }\n        if let Some(max) = self.input_max {\n            wire = wire_with_rank_broadcast(\n                format!(\"{name}.max\"),\n                model,\n                tract_hir::ops::math::min(),\n                &[wire, inputs[max]],\n            )?[0];\n        }\n        Ok(tvec!(wire))\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/math/gemm.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_hir::ops;\nuse tract_hir::tract_core::ops::einsum::EinSum;\n\npub fn gemm(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr_opt(\"alpha\")?.unwrap_or(1.);\n    let beta = node.get_attr_opt(\"beta\")?.unwrap_or(1.);\n    let trans_a = node.get_attr_opt(\"transA\")?.unwrap_or(false);\n    let trans_b = node.get_attr_opt(\"transB\")?.unwrap_or(false);\n    Ok((expand(Gemm::new(alpha, beta, trans_a, trans_b)), vec![]))\n}\n\n#[derive(Debug, Clone, new)]\npub struct Gemm {\n    alpha: f32,\n    beta: f32,\n    trans_a: bool,\n    trans_b: bool,\n}\n\nimpl Expansion for Gemm {\n    fn name(&self) -> StaticName {\n        \"Gemm\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        if inputs.len() == 3 {\n            s.equals(&inputs[2].datum_type, &outputs[0].datum_type)?;\n        }\n        s.equals(&inputs[0].rank, 2)?;\n        s.equals(&inputs[1].rank, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].rank, 2)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[1].datum_type, &outputs[0].datum_type)?;\n        let (ca, ra) = if self.trans_a { (0, 1) } else { (1, 0) };\n        let (cb, rb) = if self.trans_b { (0, 1) } else { (1, 0) };\n        s.equals(&inputs[0].shape[ra], &outputs[0].shape[0])?;\n        s.equals(&inputs[0].shape[ca], &inputs[1].shape[rb])?;\n        s.equals(&inputs[1].shape[cb], &outputs[0].shape[1])?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let (a, b, c) = (inputs[0], inputs[1], inputs.get(2));\n        let axes = AxesMapping::for_numpy_matmul(2, self.trans_a, self.trans_b, false)?;\n        let mut wire = model.wire_node(\n            format!(\"{name}.ab\"),\n            EinSum::new(axes, model.outlet_fact(a)?.datum_type),\n            [a, b].as_ref(),\n        )?[0];\n        if self.alpha != 1.0 {\n            let alpha = tensor0(self.alpha).broadcast_into_rank(model.outlet_fact(wire)?.rank())?;\n            let alpha = model.add_const(name.to_string() + \".alpha_ab.cst\", alpha)?;\n            wire = model.wire_node(\n                name.to_string() + \".alpha_ab\",\n                ops::math::mul(),\n                &[alpha, wire],\n            )?[0];\n        }\n        if self.beta != 0.0f32 && c.is_some() {\n            let mut c = c.copied().unwrap();\n            while model.outlet_fact(wire)?.rank() > model.outlet_fact(c)?.rank() {\n                c = model.wire_node(\n                    format!(\"{}.c_add_axis_{}\", name, model.outlet_fact(c)?.rank()),\n                    tract_hir::tract_core::ops::change_axes::AxisOp::Add(0),\n                    &[c],\n                )?[0];\n            }\n            let beta = tensor0(self.beta).broadcast_into_rank(model.outlet_fact(wire)?.rank())?;\n            let beta = model.add_const(name.to_string() + \".beta_c.cst\", beta)?;\n            let beta_c =\n                model.wire_node(name.to_string() + \".beta_c\", ops::math::mul(), &[beta, c])?[0];\n            wire = model.wire_node(name, ops::math::add(), &[wire, beta_c])?[0];\n        }\n        Ok(tvec!(wire))\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/math/mat_mul_integer.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\n\npub fn mat_mul_integer(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mut options = crate::model::optional_inputs(node).skip(2);\n    let op = MatMulInteger::new(options.next().unwrap(), options.next().unwrap());\n    Ok((expand(op), vec![]))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\nstruct MatMulInteger {\n    pub optional_a_zero_point_input: Option<usize>,\n    pub optional_b_zero_point_input: Option<usize>,\n}\n\nimpl Expansion for MatMulInteger {\n    fn name(&self) -> StaticName {\n        \"MatMulInteger\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()> {\n        check_input_arity(\n            inputs,\n            2 + self.optional_a_zero_point_input.is_some() as usize\n                + self.optional_b_zero_point_input.is_some() as usize,\n        )?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, i32::datum_type())?;\n        if let Some(a_zp) = self.optional_a_zero_point_input {\n            s.equals(&inputs[a_zp].datum_type, &inputs[0].datum_type)?\n        }\n        if let Some(b_zp) = self.optional_b_zero_point_input {\n            s.equals(&inputs[b_zp].datum_type, &inputs[1].datum_type)?\n        }\n        s.given_2(&inputs[0].shape, &inputs[1].shape, move |s, ashape, bshape| {\n            let (_, _, cshape, _) =\n                tract_hir::ops::matmul::compute_shapes(ashape, bshape, false, false, false)?;\n            s.equals(&outputs[0].shape, cshape)\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let mut new_inputs =\n            tract_hir::ops::binary::wire_rank_broadcast(prefix, target, &[inputs[0], inputs[1]])?;\n        new_inputs.push(target.add_const(format!(\"{prefix}.bias\"), tensor0(0i32))?);\n        if let Some(o) = self.optional_a_zero_point_input {\n            new_inputs.push(inputs[o]);\n        } else {\n            new_inputs.push(target.add_const(format!(\"{prefix}.a0\"), tensor0(0i32))?);\n        };\n        new_inputs.push(target.add_const(format!(\"{prefix}.a_scale\"), tensor0(1f32))?);\n        if let Some(o) = self.optional_b_zero_point_input {\n            new_inputs.push(inputs[o]);\n        } else {\n            new_inputs.push(target.add_const(format!(\"{prefix}.b0\"), tensor0(0i32))?);\n        };\n        new_inputs.push(target.add_const(format!(\"{prefix}.b_scale\"), tensor0(1f32))?);\n        new_inputs.push(target.add_const(format!(\"{prefix}.c0\"), tensor0(0i32))?);\n        new_inputs.push(target.add_const(format!(\"{prefix}.c_scale\"), tensor0(1f32))?);\n        wire_as_einsum(prefix, target, &new_inputs, i32::datum_type())\n    }\n}\n\npub fn q_linear_mat_mul(\n    _ctx: &ParsingContext,\n    _node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    Ok((expand(QLinearMatMul), vec![]))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\nstruct QLinearMatMul;\n\nimpl Expansion for QLinearMatMul {\n    fn name(&self) -> StaticName {\n        \"QLinearMatMul\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()> {\n        check_input_arity(inputs, 8)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &inputs[2].datum_type)?;\n        s.equals(&inputs[3].datum_type, &inputs[5].datum_type)?;\n        s.equals(&inputs[1].datum_type, f32::datum_type())?;\n        s.equals(&inputs[4].datum_type, f32::datum_type())?;\n        s.equals(&inputs[6].datum_type, f32::datum_type())?;\n        s.equals(&outputs[0].datum_type, &inputs[7].datum_type)?;\n        s.equals(&inputs[1].rank, &inputs[2].rank)?;\n        s.equals(&inputs[4].rank, &inputs[5].rank)?;\n        s.equals(&inputs[6].rank, &inputs[7].rank)?;\n        s.given_2(&inputs[0].shape, &inputs[3].shape, move |s, ashape, bshape| {\n            let (_, _, _, cshape) =\n                tract_hir::ops::matmul::compute_shapes(ashape, bshape, false, false, false)?;\n            s.equals(&outputs[0].shape, cshape)\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let mut new_inputs =\n            tract_hir::ops::binary::wire_rank_broadcast(prefix, target, &[inputs[0], inputs[3]])?;\n        new_inputs.push(target.add_const(format!(\"{prefix}.bias\"), tensor0(0i32))?);\n        for i in [2, 1, 5, 4, 7, 6] {\n            new_inputs.push(inputs[i]);\n        }\n        wire_as_einsum(prefix, target, &new_inputs, target.outlet_fact(inputs[7])?.datum_type)\n    }\n}\n\nfn wire_as_einsum(\n    prefix: &str,\n    target: &mut TypedModel,\n    inputs: &[OutletId],\n    output: DatumType,\n) -> TractResult<TVec<OutletId>> {\n    assert!(inputs.len() == 9);\n    let rank = target.outlet_fact(inputs[0])?.rank();\n    let ranks = inputs\n        .iter()\n        .map(|i| Ok(target.outlet_fact(*i)?.rank()))\n        .collect::<TractResult<Vec<_>>>()?;\n    let mut expr = AxesMapping::disconnected_for_ranks(&ranks, &ranks[0..1])?;\n    expr = expr\n        .renaming((InOut::In(0), rank - 2), 'm')?\n        .linking('m', (InOut::Out(0), rank - 2))?\n        .renaming((InOut::In(1), rank - 1), 'n')?\n        .linking('n', (InOut::Out(0), rank - 1))?\n        .renaming((InOut::In(0), rank - 1), 'k')?\n        .linking('k', (InOut::In(1), rank - 2))?;\n    for ax in 0..rank - 2 {\n        expr = expr\n            .linking((InOut::In(0), ax), (InOut::In(1), ax))?\n            .linking((InOut::In(0), ax), (InOut::Out(0), ax))?;\n    }\n    if ranks[2] == 1 {\n        expr = expr.linking('m', (InOut::In(2), 0))?;\n    }\n    if ranks[3] == 1 {\n        expr = expr.linking('m', (InOut::In(3), 0))?;\n    }\n    if ranks[4] == 1 {\n        expr = expr.linking('m', (InOut::In(4), 0))?;\n    }\n    if ranks[5] == 1 {\n        expr = expr.linking('n', (InOut::In(5), 0))?;\n    }\n    if ranks[6] == 1 {\n        expr = expr.linking('n', (InOut::In(6), 0))?;\n    }\n    if ranks[7] == 1 {\n        expr = expr.linking('m', (InOut::In(7), 0))?;\n    }\n    if ranks[8] == 1 {\n        expr = expr.linking('m', (InOut::In(8), 0))?;\n    }\n    let op = tract_core::ops::einsum::EinSum {\n        axes: expr,\n        operating_dt: i32::datum_type(),\n        q_params: Some(output),\n    };\n    target.wire_node(prefix, op, inputs)\n}\n"
  },
  {
    "path": "onnx/src/ops/math/pow.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\n\npub fn pow(\n    _ctx: &ParsingContext,\n    _node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    Ok((expand(Pow), vec![]))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Pow;\n\nimpl Expansion for Pow {\n    fn name(&self) -> StaticName {\n        \"Pow\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n\n        s.with(&inputs[0].shape, move |s, a_shape| {\n            s.with(&inputs[1].shape, move |s, b_shape| {\n                if let Ok(Some(c_shape)) =\n                    tract_hir::infer::helpers::infer_shape_broadcasting(&[&a_shape, &b_shape])\n                {\n                    s.equals(&outputs[0].shape, c_shape)?;\n                }\n                Ok(())\n            })\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        use DatumType::*;\n        let dta = model.outlet_fact(inputs[0])?.datum_type;\n        let dtb = model.outlet_fact(inputs[1])?.datum_type;\n        let mut wires = tract_hir::ops::binary::wire_rank_broadcast(name, model, inputs)?;\n        if dta.is_integer() != dtb.is_integer() {\n            wires = tract_hir::ops::binary::wire_cast(name, model, &wires, F64)?;\n            wires = model.wire_node(format!(\"{name}.pow\"), tract_hir::ops::math::pow(), &wires)?;\n            model.wire_node(name, tract_hir::ops::cast::cast(dta), &wires)\n        } else {\n            let dt = dta.common_super_type(dtb).unwrap();\n            wires = tract_hir::ops::binary::wire_cast(name, model, &wires, dt)?;\n            model.wire_node(name, tract_hir::ops::math::pow(), &wires)\n        }\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/math/rem.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_core::ops::binary::TypedBinOp;\nuse tract_core::ops::change_axes::wire_rank_broadcast;\nuse tract_core::ops::logic::{comp_gt, comp_lt};\nuse tract_hir::internal::*;\nuse tract_hir::ops;\n\npub fn rem(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    if node.get_attr_opt::<i64>(\"fmod\")? == Some(1) {\n        Ok((ops::math::Rem.into_hir(), vec![]))\n    } else {\n        Ok((expand(RemInt), vec![]))\n    }\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct RemInt;\n\nimpl Expansion for RemInt {\n    fn name(&self) -> StaticName {\n        \"Remint\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        tract_hir::ops::binary::rules(s, inputs, outputs, move |a, b| {\n            a.common_super_type(b).with_context(|| format!(\"No super type for {a:?} and {b:?}\"))\n        })\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let zero = tract_hir::ops::activations::broadcast_scalar(0.0, model, inputs)?;\n        let a = model.outlet_fact(inputs[0])?.datum_type;\n        let b = model.outlet_fact(inputs[1])?.datum_type;\n        let dt =\n            a.common_super_type(b).with_context(|| format!(\"No super type for {a:?} and {b:?}\"))?;\n        let wires = tract_hir::ops::binary::wire_rank_broadcast(name, model, inputs)?;\n        let wires = tract_hir::ops::binary::wire_cast(name, model, &wires, dt)?;\n        if dt.is_unsigned() || dt == DatumType::TDim {\n            return model.wire_node(name, tract_hir::ops::math::rem(), &wires);\n        }\n        // from onnx runtime:\n        // auto res = x % y;\n        // if ((res < 0 && y > 0) || (res > 0 && y < 0)) { res += y; }\n        let zero = model.add_const(name.to_string() + \".zero\", zero)?;\n        let rem =\n            model.wire_node(name.to_string() + \".rem\", tract_hir::ops::math::rem(), &wires)?[0];\n        let rem_inputs = wire_rank_broadcast(name, model, &[zero, rem])?;\n        let rem_is_neg = model.wire_node(\n            name.to_string() + \".rem_is_neg\",\n            TypedBinOp(comp_gt(), None),\n            &rem_inputs,\n        )?;\n        let rem_is_pos = model.wire_node(\n            name.to_string() + \".rem_is_pos\",\n            TypedBinOp(comp_lt(), None),\n            &rem_inputs,\n        )?;\n        let b_inputs = wire_rank_broadcast(name, model, &[zero, wires[1]])?;\n        let b_is_neg = model.wire_node(\n            name.to_string() + \".b_is_neg\",\n            TypedBinOp(comp_gt(), None),\n            &b_inputs,\n        )?;\n        let b_is_pos = model.wire_node(\n            name.to_string() + \".b_is_pos\",\n            TypedBinOp(comp_lt(), None),\n            &b_inputs,\n        )?;\n        let rem_is_neg_b_is_pos = model.wire_node(\n            name.to_string() + \".rem_is_neg_b_is_pos\",\n            tract_hir::ops::logic::and(),\n            &[rem_is_neg[0], b_is_pos[0]],\n        )?;\n        let rem_is_pos_b_is_neg = model.wire_node(\n            name.to_string() + \".rem_is_pos_b_is_neg\",\n            tract_hir::ops::logic::and(),\n            &[rem_is_pos[0], b_is_neg[0]],\n        )?;\n        let adjust = model.wire_node(\n            name.to_string() + \".adjust\",\n            tract_hir::ops::logic::or(),\n            &[rem_is_pos_b_is_neg[0], rem_is_neg_b_is_pos[0]],\n        )?;\n        let adjusted = model.wire_node(\n            name.to_string() + \".adjusted\",\n            tract_hir::ops::math::add(),\n            &[rem, wires[1]],\n        )?;\n        model.wire_node(\n            name.to_string(),\n            tract_core::ops::logic::Iff,\n            &[adjust[0], adjusted[0], rem],\n        )\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/math.rs",
    "content": "use crate::model::OnnxOpRegister;\nuse crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_hir::ops;\nuse tract_hir::ops::binary::Nary;\n\nmod clip;\nmod gemm;\nmod mat_mul_integer;\nmod pow;\nmod rem;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"Add\", |_, _| Ok((ops::math::Add.into_hir(), vec![])));\n    reg.insert(\"Sub\", |_, _| Ok((ops::math::Sub.into_hir(), vec![])));\n    reg.insert(\"Mul\", |_, _| Ok((ops::math::Mul.into_hir(), vec![])));\n    reg.insert(\"Div\", |_, _| Ok((ops::math::Div.into_hir(), vec![])));\n    reg.insert(\"Mod\", rem::rem);\n\n    reg.insert(\"BitShift\", bitshift);\n    reg.insert(\"BitwiseAnd\", |_, _| Ok((ops::logic::BitAnd.into_hir(), vec![])));\n    reg.insert(\"BitwiseOr\", |_, _| Ok((ops::logic::BitOr.into_hir(), vec![])));\n    reg.insert(\"BitwiseXor\", |_, _| Ok((ops::logic::BitXor.into_hir(), vec![])));\n    reg.insert(\"BitwiseNot\", |_, _| Ok((ops::logic::bitnot().into_hir(), vec![])));\n\n    reg.insert(\"Sum\", |_, _| Ok((Box::new(Nary(Box::new(ops::math::Add), false)), vec![])));\n    reg.insert(\"Max\", |_, _| Ok((Box::new(Nary(Box::new(ops::math::Max), false)), vec![])));\n    reg.insert(\"Min\", |_, _| Ok((Box::new(Nary(Box::new(ops::math::Min), false)), vec![])));\n    reg.insert(\"Mean\", |_, _| Ok((Box::new(Nary(Box::new(ops::math::Add), true)), vec![])));\n\n    reg.insert(\"Abs\", |_, _| Ok((ops::math::abs().into_hir(), vec![])));\n    reg.insert(\"Ceil\", |_, _| Ok((ops::math::ceil().into_hir(), vec![])));\n    reg.insert(\"Floor\", |_, _| Ok((ops::math::floor().into_hir(), vec![])));\n    reg.insert(\"Round\", |_, _| Ok((ops::math::round_half_to_even().into_hir(), vec![])));\n    reg.insert(\"Clip\", clip::clip);\n\n    reg.insert(\"Cos\", |_, _| Ok((ops::math::cos().into_hir(), vec![])));\n    reg.insert(\"Sin\", |_, _| Ok((ops::math::sin().into_hir(), vec![])));\n    reg.insert(\"Tan\", |_, _| Ok((ops::math::tan().into_hir(), vec![])));\n    reg.insert(\"Acos\", |_, _| Ok((ops::math::acos().into_hir(), vec![])));\n    reg.insert(\"Asin\", |_, _| Ok((ops::math::asin().into_hir(), vec![])));\n    reg.insert(\"Atan\", |_, _| Ok((ops::math::atan().into_hir(), vec![])));\n\n    reg.insert(\"Cosh\", |_, _| Ok((ops::math::cosh().into_hir(), vec![])));\n    reg.insert(\"Sinh\", |_, _| Ok((ops::math::sinh().into_hir(), vec![])));\n    reg.insert(\"Tanh\", |_, _| Ok((ops::math::tanh().into_hir(), vec![])));\n    reg.insert(\"Acosh\", |_, _| Ok((ops::math::acosh().into_hir(), vec![])));\n    reg.insert(\"Asinh\", |_, _| Ok((ops::math::asinh().into_hir(), vec![])));\n    reg.insert(\"Atanh\", |_, _| Ok((ops::math::atanh().into_hir(), vec![])));\n\n    reg.insert(\"Erf\", |_, _| Ok((ops::math::erf().into_hir(), vec![])));\n    reg.insert(\"Exp\", |_, _| Ok((ops::math::exp().into_hir(), vec![])));\n    reg.insert(\"Log\", |_, _| Ok((ops::math::ln().into_hir(), vec![])));\n    reg.insert(\"Sqrt\", |_, _| Ok((ops::math::sqrt().into_hir(), vec![])));\n    reg.insert(\"Rsqrt\", |_, _| Ok((ops::math::rsqrt().into_hir(), vec![])));\n\n    reg.insert(\"IsNaN\", |_, _| Ok((tract_core::ops::math::is_nan().into_hir(), vec![])));\n    reg.insert(\"IsInf\", isinf);\n    reg.insert(\"Neg\", |_, _| Ok((ops::math::neg().into_hir(), vec![])));\n    reg.insert(\"Sign\", |_, _| Ok((ops::math::sign().into_hir(), vec![])));\n    reg.insert(\"Reciprocal\", |_, _| Ok((ops::math::recip().into_hir(), vec![])));\n\n    reg.insert(\"Pow\", pow::pow);\n\n    reg.insert(\"MatMul\", |_, _| Ok((expand(ops::matmul::MatMulInference::default()), vec![])));\n    reg.insert(\"MatMulInteger\", mat_mul_integer::mat_mul_integer);\n    reg.insert(\"QLinearMatMul\", mat_mul_integer::q_linear_mat_mul);\n    reg.insert(\"Gemm\", gemm::gemm);\n}\n\nfn isinf(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let detect_positive = node.get_attr_opt(\"detect_positive\")?.unwrap_or(1) != 0;\n    let detect_negative = node.get_attr_opt(\"detect_negative\")?.unwrap_or(1) != 0;\n    Ok((tract_core::ops::math::is_inf(detect_positive, detect_negative).into_hir(), vec![]))\n}\n\nfn bitshift(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let op: Box<dyn InferenceOp> = if node.get_attr_opt(\"direction\")?.unwrap_or(\"LEFT\") == \"RIGHT\" {\n        ops::math::ShiftRight.into_hir()\n    } else {\n        ops::math::ShiftLeft.into_hir()\n    };\n    Ok((op, vec![]))\n}\n"
  },
  {
    "path": "onnx/src/ops/ml/category_mapper.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_onnx_opl::ml::*;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"CategoryMapper\", category_mapper);\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct CategoryMapper {\n    pub from: Arc<Tensor>,\n    pub to: Arc<Tensor>,\n    pub fallback: Arc<Tensor>,\n}\n\nimpl Expansion for CategoryMapper {\n    fn name(&self) -> StaticName {\n        \"CategoryMapper\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        s.equals(&inputs[0].datum_type, self.from.datum_type())?;\n        s.equals(&outputs[0].datum_type, self.to.datum_type())?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let wire = model.wire_node(\n            format!(\"{prefix}.reverse\"),\n            ReverseLookup::new(self.from.clone(), -1)?,\n            inputs,\n        )?;\n        model.wire_node(\n            format!(\"{prefix}.direct\"),\n            DirectLookup::new(self.to.clone(), self.fallback.clone())?,\n            &wire,\n        )\n    }\n}\n\nfn category_mapper(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let ints: Vec<i64> = node.get_attr_vec(\"cats_int64s\")?;\n    let strings: Vec<String> = node.get_attr_vec(\"cats_strings\")?;\n    let default_int: Option<i64> = node.get_attr_opt(\"default_int64\")?;\n    let default_string: Option<String> = node.get_attr_opt(\"default_string\")?;\n    let op: Box<dyn InferenceOp> = match (default_int, default_string.as_ref()) {\n        (None, None) | (Some(_), Some(_)) => bail!(\n            \"CategoryMapper requires exactly one of default_int64 and default_string (found {:?})\",\n            (default_int, default_string)\n        ),\n        (Some(def), None) => expand(CategoryMapper {\n            from: rctensor1(&strings),\n            to: rctensor1(&ints),\n            fallback: rctensor0(def),\n        }),\n        (None, Some(def)) => expand(CategoryMapper {\n            from: rctensor1(&ints),\n            to: rctensor1(&strings),\n            fallback: rctensor0(def.clone()),\n        }),\n    };\n    Ok((op, vec![]))\n}\n"
  },
  {
    "path": "onnx/src/ops/ml/linear_classifier.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\nuse tract_hir::ops::array::TypedConcat;\nuse tract_hir::tract_core::ops::einsum::EinSum;\nuse tract_onnx_opl::ml::*;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"LinearClassifier\", linear_classifier);\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\npub enum PostTransform {\n    Softmax,\n    Logistic,\n}\n\npub fn parse_post_transform(s: &str) -> TractResult<Option<PostTransform>> {\n    match s {\n        \"NONE\" => Ok(None),\n        \"SOFTMAX\" => Ok(Some(PostTransform::Softmax)),\n        \"LOGISTIC\" => Ok(Some(PostTransform::Logistic)),\n        \"PROBIT\" | \"SOFTMAX_ZERO\" => bail!(\"PROBIT and SOFTMAX_ZERO unsupported\"),\n        _ => bail!(\"Invalid post transform: {}\", s),\n    }\n}\n\nfn parse_class_data(node: &NodeProto) -> TractResult<Arc<Tensor>> {\n    let ints = node.get_attr_opt_slice::<i64>(\"classlabels_ints\")?;\n    let strs = node.get_attr_opt_tvec::<&str>(\"classlabels_strings\")?;\n    match (ints, strs) {\n        (Some(n), None) => Ok(rctensor1(n)),\n        (None, Some(n)) => Ok(rctensor1(&n.iter().map(|d| d.to_string()).collect::<Vec<_>>())),\n        (None, None) => bail!(\"cannot find neither 'classlabels_ints' not 'classlabels_strings'\"),\n        (Some(_), Some(_)) => {\n            bail!(\"only one of 'classlabels_ints' and 'classlabels_strings' can be set\")\n        }\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct LinearClassifier {\n    pub class_labels: Arc<Tensor>,\n    pub coefficients: Arc<Tensor>,\n    pub intercepts: Option<Arc<Tensor>>,\n    pub post_transform: Option<PostTransform>,\n    pub binary_result_layout: bool,\n    pub num_models: usize,\n}\n\nfn linear_classifier(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let class_labels = parse_class_data(node)?;\n    let n_classes = class_labels.len();\n    let multi_class: i64 = node.get_attr_opt(\"multi_class\")?.unwrap_or(0);\n    let raw_coeffs: Vec<f32> = node.get_attr_vec(\"coefficients\")?;\n    node.expect(!raw_coeffs.is_empty(), \"coefficients not empty\")?;\n\n    let intercepts_raw: Option<Vec<f32>> = node.get_attr_opt_vec(\"intercepts\")?;\n\n    let (e_prime, binary_result_layout) = match intercepts_raw.as_ref() {\n        Some(v) => {\n            node.expect(\n                raw_coeffs.len() % v.len() == 0,\n                \"coefficients length must be a multiple of intercepts length\",\n            )?;\n            let e_prime = v.len();\n            let binary = n_classes == 2 && e_prime == 1 && multi_class == 0;\n            (e_prime, binary)\n        }\n        None if n_classes == 2 && multi_class == 0 => (1, true),\n        None if raw_coeffs.len() % n_classes == 0 => (n_classes, false),\n        None => bail!(\n            \"coefficients length {} not compatible with number of classes {}\",\n            raw_coeffs.len(),\n            n_classes\n        ),\n    };\n\n    node.expect(\n        raw_coeffs.len() % e_prime == 0,\n        \"coefficients length must be a multiple of number of models\",\n    )?;\n\n    if binary_result_layout {\n        node.expect(n_classes == 2, \"binary result layout requires exactly 2 class labels\")?;\n    } else {\n        node.expect(\n            n_classes == e_prime,\n            \"class labels length must match number of models when not using binary single-model layout\",\n        )?;\n    }\n\n    let c = raw_coeffs.len() / e_prime;\n    let coeffs_ec = tensor1(&raw_coeffs).into_shape(&[e_prime, c])?;\n    let coefficients = coeffs_ec.permute_axes(&[1, 0])?.into_arc_tensor();\n\n    let intercepts = match intercepts_raw {\n        Some(v) => {\n            node.expect(v.len() == e_prime, \"intercepts length should match number of models\")?;\n            Some(rctensor1(&v))\n        }\n        None => None,\n    };\n\n    let post_transform =\n        node.get_attr_opt(\"post_transform\")?.map(parse_post_transform).transpose()?.unwrap_or(None);\n\n    Ok((\n        expand(LinearClassifier {\n            class_labels,\n            coefficients,\n            intercepts,\n            post_transform,\n            binary_result_layout,\n            num_models: e_prime,\n        }),\n        vec![],\n    ))\n}\n\nimpl Expansion for LinearClassifier {\n    fn name(&self) -> StaticName {\n        \"LinearClassifier\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 2)?;\n\n        s.equals(&outputs[0].datum_type, self.class_labels.datum_type())?;\n        s.equals(&outputs[1].datum_type, DatumType::F32)?;\n\n        s.equals(&outputs[0].rank, 1)?;\n        s.equals(&outputs[1].rank, 2)?;\n        s.equals(&outputs[0].shape[0], &inputs[0].shape[0])?;\n        s.equals(&outputs[1].shape[0], &inputs[0].shape[0])?;\n        // Scores second dim depends on layout\n        if self.binary_result_layout {\n            s.equals(&outputs[1].shape[1], 2.to_dim())?;\n        } else {\n            s.equals(&outputs[1].shape[1], (self.num_models as i64).to_dim())?;\n        }\n\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        use tract_core::ops::nn::*;\n\n        let mut x = inputs[0];\n        if model.outlet_fact(x)?.rank() == 1 {\n            x = model.wire_node(format!(\"{prefix}.add_batch_axis\"), AxisOp::Add(0), &[x])?[0];\n        }\n\n        if model.outlet_fact(x)?.datum_type != f32::datum_type() {\n            x = model.wire_node(\n                format!(\"{prefix}.to_f32\"),\n                tract_core::ops::cast::cast(f32::datum_type()),\n                &[x],\n            )?[0];\n        }\n\n        let w = model.add_const(format!(\"{prefix}.coefficients\"), self.coefficients.clone())?;\n        let axes = AxesMapping::for_numpy_matmul(2, false, false, false)?;\n        let mut scores = model.wire_node(\n            format!(\"{prefix}.matmul\"),\n            EinSum::new(axes, model.outlet_fact(x)?.datum_type),\n            [x, w].as_ref(),\n        )?;\n\n        if let Some(intercepts) = self.intercepts.as_deref() {\n            let bias = intercepts.clone().broadcast_into_rank(2)?.into_arc_tensor();\n            let bias = model.add_const(format!(\"{prefix}.intercepts\"), bias)?;\n            scores = model.wire_node(\n                format!(\"{prefix}.add_bias\"),\n                tract_core::ops::math::add(),\n                &[scores[0], bias],\n            )?;\n        }\n\n        let final_scores = if self.binary_result_layout {\n            match self.post_transform {\n                None => {\n                    // logits [-s, s]\n                    let m1 = model.add_const(format!(\"{prefix}.m1\"), rctensor2(&[[-1f32]]))?;\n                    let neg = model.wire_node(\n                        format!(\"{prefix}.binary.neg\"),\n                        tract_core::ops::math::mul(),\n                        &[scores[0], m1],\n                    )?;\n                    model.wire_node(\n                        format!(\"{prefix}.binary.concat\"),\n                        TypedConcat::new(1),\n                        &[neg[0], scores[0]],\n                    )?\n                }\n                Some(PostTransform::Logistic) => {\n                    // probabilities [1 - sigmoid(s), sigmoid(s)]\n                    let p = model.wire_node(\n                        format!(\"{prefix}.logistic\"),\n                        tract_core::ops::nn::sigmoid(),\n                        &scores,\n                    )?;\n                    let one = model.add_const(prefix.to_string() + \".one\", rctensor2(&[[1f32]]))?;\n                    let complement = model.wire_node(\n                        format!(\"{prefix}.binary.complement\"),\n                        tract_core::ops::math::sub(),\n                        &[one, p[0]],\n                    )?;\n                    model.wire_node(\n                        format!(\"{prefix}.binary.concat\"),\n                        TypedConcat::new(1),\n                        &[complement[0], p[0]],\n                    )?\n                }\n                Some(PostTransform::Softmax) => {\n                    let m1 = model.add_const(format!(\"{prefix}.m1\"), rctensor2(&[[-1f32]]))?;\n                    let neg = model.wire_node(\n                        format!(\"{prefix}.binary.neg\"),\n                        tract_core::ops::math::mul(),\n                        &[scores[0], m1],\n                    )?;\n                    let logits2 = model.wire_node(\n                        format!(\"{prefix}.binary.logits2\"),\n                        TypedConcat::new(1),\n                        &[neg[0], scores[0]],\n                    )?;\n                    model.wire_node(\n                        format!(\"{prefix}.softmax\"),\n                        tract_core::ops::nn::Softmax { axes: tvec!(1), ..Softmax::default() },\n                        &logits2,\n                    )?\n                }\n            }\n        } else {\n            let mut tmp = scores.clone();\n            match self.post_transform {\n                None => {}\n                Some(PostTransform::Softmax) => {\n                    tmp = model.wire_node(\n                        format!(\"{prefix}.softmax\"),\n                        tract_core::ops::nn::Softmax { axes: tvec!(1), ..Softmax::default() },\n                        &tmp,\n                    )?;\n                }\n                Some(PostTransform::Logistic) => {\n                    tmp = model.wire_node(\n                        format!(\"{prefix}.logistic\"),\n                        tract_core::ops::nn::sigmoid(),\n                        &tmp,\n                    )?;\n                }\n            }\n            tmp\n        };\n\n        let winners = model.wire_node(\n            format!(\"{prefix}.argmax\"),\n            tract_core::ops::nn::Reduce::new(tvec!(1), tract_core::ops::nn::Reducer::ArgMax(false)),\n            &final_scores,\n        )?;\n        let reduced = model.wire_node(\n            format!(\"{prefix}.rm_axis\"),\n            tract_core::ops::change_axes::AxisOp::Rm(1),\n            &winners,\n        )?;\n        let casted = model.wire_node(\n            format!(\"{prefix}.casted\"),\n            tract_core::ops::cast::cast(i32::datum_type()),\n            &reduced,\n        )?;\n        let labels = model.wire_node(\n            format!(\"{prefix}.labels\"),\n            DirectLookup::new(\n                self.class_labels.clone(),\n                Tensor::zero_dt(self.class_labels.datum_type(), &[])?.into_arc_tensor(),\n            )?,\n            &casted,\n        )?[0];\n\n        Ok(tvec!(labels, final_scores[0]))\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(2)\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/ml/linear_regressor.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\nuse tract_hir::tract_core::ops::einsum::EinSum;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"LinearRegressor\", linear_regressor);\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\npub enum PostTransform {\n    Softmax,\n    Logistic,\n}\n\npub fn parse_post_transform(s: &str) -> TractResult<Option<PostTransform>> {\n    match s {\n        \"NONE\" => Ok(None),\n        \"SOFTMAX\" => Ok(Some(PostTransform::Softmax)),\n        \"LOGISTIC\" => Ok(Some(PostTransform::Logistic)),\n        \"PROBIT\" | \"SOFTMAX_ZERO\" => bail!(\"PROBIT and SOFTMAX_ZERO unsupported\"),\n        _ => bail!(\"Invalid post transform: {}\", s),\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct LinearRegressor {\n    pub coefficients: Arc<Tensor>,\n    pub intercepts: Option<Arc<Tensor>>,\n    pub post_transform: Option<PostTransform>,\n    pub targets: usize,\n}\n\nfn linear_regressor(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let targets_i64: i64 = node.get_attr_opt(\"targets\")?.unwrap_or(1);\n    node.expect(targets_i64 > 0, \"targets must be > 0\")?;\n    let targets: usize = usize::try_from(targets_i64)\n        .map_err(|_| format_err!(\"targets out of range: {}\", targets_i64))?;\n\n    let raw_coeffs: Vec<f32> = node.get_attr_vec(\"coefficients\")?;\n    node.expect(!raw_coeffs.is_empty(), \"coefficients not empty\")?;\n\n    node.expect(\n        raw_coeffs.len() % targets == 0,\n        \"coefficients length must be a multiple of targets\",\n    )?;\n    let c = raw_coeffs.len() / targets;\n\n    let coeffs_tc = tensor1(&raw_coeffs).into_shape(&[targets, c])?;\n    let coefficients = coeffs_tc.permute_axes(&[1, 0])?.into_arc_tensor();\n\n    let intercepts: Option<Vec<f32>> = node.get_attr_opt_vec(\"intercepts\")?;\n    let intercepts = match intercepts {\n        Some(v) => {\n            node.expect(v.len() == targets, \"intercepts length matches number of targets\")?;\n            Some(rctensor1(&v))\n        }\n        None => None,\n    };\n\n    let post_transform =\n        node.get_attr_opt(\"post_transform\")?.map(parse_post_transform).transpose()?.unwrap_or(None);\n\n    Ok((expand(LinearRegressor { coefficients, intercepts, post_transform, targets }), vec![]))\n}\n\nimpl Expansion for LinearRegressor {\n    fn name(&self) -> StaticName {\n        \"LinearRegressor\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n\n        s.equals(&outputs[0].datum_type, DatumType::F32)?;\n        s.equals(&outputs[0].rank, 2)?;\n        s.equals(&outputs[0].shape[0], &inputs[0].shape[0])?;\n        s.equals(&outputs[0].shape[1], self.targets.to_dim())?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        use tract_core::ops::nn::*;\n\n        let mut x = inputs[0];\n        if model.outlet_fact(x)?.rank() == 1 {\n            x = model.wire_node(format!(\"{prefix}.add_batch_axis\"), AxisOp::Add(0), &[x])?[0];\n        }\n        if model.outlet_fact(x)?.datum_type != f32::datum_type() {\n            x = model.wire_node(\n                format!(\"{prefix}.to_f32\"),\n                tract_core::ops::cast::cast(f32::datum_type()),\n                &[x],\n            )?[0];\n        }\n\n        let w = model.add_const(format!(\"{prefix}.coefficients\"), self.coefficients.clone())?;\n        let axes = AxesMapping::for_numpy_matmul(2, false, false, false)?;\n        let mut y = model.wire_node(\n            format!(\"{prefix}.matmul\"),\n            EinSum::new(axes, model.outlet_fact(x)?.datum_type),\n            [x, w].as_ref(),\n        )?;\n\n        if let Some(intercepts) = self.intercepts.as_deref() {\n            let bias = intercepts.clone().broadcast_into_rank(2)?.into_arc_tensor();\n            let bias = model.add_const(format!(\"{prefix}.intercepts\"), bias)?;\n            y = model.wire_node(\n                format!(\"{prefix}.add_bias\"),\n                tract_core::ops::math::add(),\n                &[y[0], bias],\n            )?;\n        }\n\n        match self.post_transform {\n            None => {}\n            Some(PostTransform::Softmax) => {\n                y = model.wire_node(\n                    format!(\"{prefix}.softmax\"),\n                    tract_core::ops::nn::Softmax { axes: tvec!(1), ..Softmax::default() },\n                    &y,\n                )?;\n            }\n            Some(PostTransform::Logistic) => {\n                y = model.wire_node(\n                    format!(\"{prefix}.logistic\"),\n                    tract_core::ops::nn::sigmoid(),\n                    &y,\n                )?;\n            }\n        }\n\n        Ok(tvec!(y[0]))\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(1)\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/ml/mod.rs",
    "content": "mod category_mapper;\nmod linear_classifier;\nmod linear_regressor;\nmod normalizer;\nmod tree_ensemble_classifier;\n\nuse crate::model::OnnxOpRegister;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    category_mapper::register_all_ops(reg);\n    linear_classifier::register_all_ops(reg);\n    linear_regressor::register_all_ops(reg);\n    normalizer::register_all_ops(reg);\n    tree_ensemble_classifier::register_all_ops(reg);\n}\n"
  },
  {
    "path": "onnx/src/ops/ml/normalizer.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::NodeProto;\nuse tract_core::ops::math::{abs, div, max, mul, rsqrt, square};\nuse tract_core::ops::nn::{Reduce, Reducer};\nuse tract_hir::internal::*;\nuse tract_hir::ops::logic::wire_with_rank_broadcast;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"Normalizer\", normalizer);\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\nenum NormKind {\n    Max,\n    L1,\n    L2,\n}\n\nfn parse_norm_kind(s: &str) -> TractResult<NormKind> {\n    match s.to_ascii_uppercase().as_str() {\n        \"MAX\" => Ok(NormKind::Max),\n        \"L1\" => Ok(NormKind::L1),\n        \"L2\" => Ok(NormKind::L2),\n        other => bail!(\"Invalid norm kind: {}\", other),\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct Normalizer {\n    kind: NormKind,\n}\n\nfn normalizer(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let norm: String = node.get_attr_opt(\"norm\")?.unwrap_or_else(|| \"MAX\".to_string());\n    let kind = parse_norm_kind(&norm)?;\n    Ok((expand(Normalizer { kind }), vec![]))\n}\n\nimpl Expansion for Normalizer {\n    fn name(&self) -> StaticName {\n        \"Normalizer\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, DatumType::F32)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input_fact = model.outlet_fact(inputs[0])?.clone();\n        let rank = input_fact.rank();\n        ensure!(rank >= 1, \"Normalizer expects rank 1 or 2 inputs\");\n        let axis = rank - 1;\n\n        let mut x = inputs[0];\n        let x_fact = model.outlet_fact(x)?.clone();\n\n        if x_fact.datum_type != f32::datum_type() {\n            x = model.wire_node(\n                format!(\"{prefix}.to_f32\"),\n                tract_core::ops::cast::cast(f32::datum_type()),\n                &[x],\n            )?[0];\n        }\n\n        let eps = model.add_const(format!(\"{prefix}.eps\"), rctensor0(1e-12f32))?;\n\n        let y = match self.kind {\n            NormKind::Max => {\n                let ax = model.wire_node(format!(\"{prefix}.abs\"), abs(), &[x])?;\n                let d0 = model.wire_node(\n                    format!(\"{prefix}.max\"),\n                    Reduce { axes: tvec![axis], reducer: Reducer::Max },\n                    &ax,\n                )?[0];\n                let d = wire_with_rank_broadcast(\n                    format!(\"{prefix}.clamp_max\"),\n                    model,\n                    max(),\n                    &[d0, eps],\n                )?[0];\n                wire_with_rank_broadcast(format!(\"{prefix}.div_max\"), model, div(), &[x, d])?[0]\n            }\n            NormKind::L1 => {\n                let ax = model.wire_node(format!(\"{prefix}.abs\"), abs(), &[x])?;\n                let d0 = model.wire_node(\n                    format!(\"{prefix}.sum_abs\"),\n                    Reduce { axes: tvec![axis], reducer: Reducer::Sum },\n                    &ax,\n                )?[0];\n                let d = wire_with_rank_broadcast(\n                    format!(\"{prefix}.clamp_l1\"),\n                    model,\n                    max(),\n                    &[d0, eps],\n                )?[0];\n                wire_with_rank_broadcast(format!(\"{prefix}.div_sum\"), model, div(), &[x, d])?[0]\n            }\n            NormKind::L2 => {\n                let x2 = model.wire_node(format!(\"{prefix}.square\"), square(), &[x])?;\n                let ss0 = model.wire_node(\n                    format!(\"{prefix}.sum_sq\"),\n                    Reduce { axes: tvec![axis], reducer: Reducer::Sum },\n                    &x2,\n                )?[0];\n                let ss = wire_with_rank_broadcast(\n                    format!(\"{prefix}.clamp_l2\"),\n                    model,\n                    max(),\n                    &[ss0, eps],\n                )?[0];\n                let inv = model.wire_node(format!(\"{prefix}.rsqrt\"), rsqrt(), &[ss])?[0];\n                wire_with_rank_broadcast(format!(\"{prefix}.mul_invnorm\"), model, mul(), &[x, inv])?\n                    [0]\n            }\n        };\n\n        Ok(tvec!(y))\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(1)\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/ml/tree_ensemble_classifier.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::NodeProto;\nuse crate::pb_helpers::*;\nuse std::iter;\nuse tract_hir::internal::*;\nuse tract_hir::ops::array::{Slice, TypedConcat};\nuse tract_onnx_opl::ml::tree::*;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"TreeEnsembleClassifier\", tree_classifier);\n}\n\nfn tree_classifier(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let ensemble = parse_nodes_data(node, true)?;\n    let class_labels = parse_class_data(node)?;\n    let base_class_score =\n        get_vec_attr_opt::<f32>(node, \"base_values\", ensemble.n_classes())?.map(|t| rctensor1(&t));\n    let post_transform =\n        node.get_attr_opt(\"post_transform\")?.map(parse_post_transform).transpose()?.unwrap_or(None);\n\n    // even numbers in leaves are categories id target of leaf contrib\n    let binary_result_layout = class_labels.len() < 3\n        && ensemble\n            .data\n            .leaves\n            .try_as_plain()?\n            .as_slice::<u32>()?\n            .iter()\n            .enumerate()\n            .all(|(ix, v)| ix % 2 == 1 || *v == 0);\n\n    Ok((\n        expand(TreeEnsembleClassifier {\n            ensemble,\n            class_labels,\n            base_class_score,\n            post_transform,\n            binary_result_layout,\n        }),\n        vec![],\n    ))\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\npub enum PostTransform {\n    Softmax,\n    Logistic,\n    // SoftmaxZero,\n    // Probit, // probit, especially multinomial, is p.i.t.a. - so let's ignore it for now\n}\n\npub fn parse_post_transform(s: &str) -> TractResult<Option<PostTransform>> {\n    match s {\n        \"NONE\" => Ok(None),\n        \"SOFTMAX\" => Ok(Some(PostTransform::Softmax)),\n        \"LOGISTIC\" => Ok(Some(PostTransform::Logistic)),\n        \"PROBIT\" | \"SOFTMAX_ZERO\" => bail!(\"PROBIT and SOFTMAX_ZERO unsupported\"),\n        _ => bail!(\"Invalid post transform: {}\", s),\n    }\n}\n\nfn parse_node_mode(s: &str) -> TractResult<Option<Cmp>> {\n    match s {\n        \"BRANCH_LEQ\" => Ok(Some(Cmp::LessEqual)),\n        \"BRANCH_LT\" => Ok(Some(Cmp::Less)),\n        \"BRANCH_GTE\" => Ok(Some(Cmp::GreaterEqual)),\n        \"BRANCH_GT\" => Ok(Some(Cmp::Greater)),\n        \"BRANCH_EQ\" => Ok(Some(Cmp::Equal)),\n        \"BRANCH_NEQ\" => Ok(Some(Cmp::NotEqual)),\n        \"LEAF\" => Ok(None),\n        _ => bail!(\"Unsupported mode node: {}\", s),\n    }\n}\n\nfn get_vec_attr<'a, T>(node: &'a NodeProto, attr: &str, n: usize) -> TractResult<Vec<T>>\nwhere\n    T: AttrTVecType<'a>,\n{\n    let vec = node.get_attr_vec(attr)?;\n    node.expect_attr(attr, vec.len() == n, || format!(\"length {}, got {}\", vec.len(), n))?;\n    Ok(vec)\n}\n\nfn get_vec_attr_opt<'a, T>(node: &'a NodeProto, attr: &str, n: usize) -> TractResult<Option<Vec<T>>>\nwhere\n    T: AttrTVecType<'a>,\n{\n    match node.get_attr_opt_vec(attr)? {\n        Some(vec) => {\n            node.expect_attr(attr, vec.len() == n, || {\n                format!(\"length {} (or undefined), got {}\", vec.len(), n)\n            })?;\n            Ok(Some(vec))\n        }\n        None => Ok(None),\n    }\n}\n\nfn parse_class_data(node: &NodeProto) -> TractResult<Arc<Tensor>> {\n    // parse n_classes from protobuf\n    let ints = node.get_attr_opt_slice::<i64>(\"classlabels_int64s\")?;\n    let strs = node.get_attr_opt_tvec::<&str>(\"classlabels_strings\")?;\n    match (ints, strs) {\n        (Some(n), None) => Ok(rctensor1(n)),\n        (None, Some(n)) => Ok(rctensor1(&n.iter().map(|d| d.to_string()).collect::<Vec<_>>())),\n        (None, None) => {\n            bail!(\"cannot find neither 'classlabels_int64s' not 'classlabels_strings'\")\n        }\n        (Some(_), Some(_)) => {\n            bail!(\"only one of 'classlabels_int64s' and 'classlabels_strings' can be set\")\n        }\n    }\n}\n\nfn parse_nodes_data(node: &NodeProto, is_classifier: bool) -> TractResult<TreeEnsemble> {\n    // parse n_classes from protobuf\n    let n_classes = if is_classifier {\n        let ints = node.get_attr_opt_slice::<i64>(\"classlabels_int64s\")?;\n        let strs = node.get_attr_opt_tvec::<&str>(\"classlabels_strings\")?;\n        match (ints, strs) {\n            (Some(n), None) => n.len(),\n            (None, Some(n)) => n.len(),\n            (None, None) => {\n                bail!(\"cannot find neither 'classlabels_int64s' not 'classlabels_strings'\")\n            }\n            (Some(_), Some(_)) => {\n                bail!(\"only one of 'classlabels_int64s' and 'classlabels_strings' can be set\")\n            }\n        }\n    } else {\n        node.get_attr(\"n_targets\")?\n    };\n    let n_nodes = node.get_attr_slice::<i64>(\"nodes_featureids\")?.len();\n    node.expect_attr(\"nodes_featureids\", n_nodes != 0, \"at least one node\")?;\n\n    // parse base_values from protobuf\n    let node_ids = get_vec_attr::<usize>(node, \"nodes_nodeids\", n_nodes)?;\n    let tree_ids = get_vec_attr::<usize>(node, \"nodes_treeids\", n_nodes)?;\n    let feature_ids = get_vec_attr::<usize>(node, \"nodes_featureids\", n_nodes)?;\n    let true_ids = get_vec_attr::<usize>(node, \"nodes_truenodeids\", n_nodes)?;\n    let false_ids = get_vec_attr::<usize>(node, \"nodes_falsenodeids\", n_nodes)?;\n    let node_values = get_vec_attr::<f32>(node, \"nodes_values\", n_nodes)?;\n    let nan_is_true = get_vec_attr_opt::<bool>(node, \"nodes_missing_value_tracks_true\", n_nodes)?\n        .unwrap_or_else(|| iter::repeat_n(false, n_nodes).collect());\n    let node_modes: Vec<Option<Cmp>> = get_vec_attr::<&str>(node, \"nodes_modes\", n_nodes)?\n        .iter()\n        .map(|s| parse_node_mode(s))\n        .collect::<TractResult<_>>()?;\n\n    let max_used_features = feature_ids.iter().max().copied().unwrap_or(0);\n\n    use tract_onnx_opl::ml::tree_ensemble_classifier::parse_aggregate;\n    // parse post_transform from protobuf\n    // parse aggregate_fn from protobuf (for regressors)\n    let aggregate_fn = parse_aggregate(if is_classifier {\n        \"SUM\"\n    } else {\n        node.get_attr_opt(\"aggregate\")?.unwrap_or(\"SUM\")\n    })?;\n\n    // parse leaf data from protobuf\n    let leaf_prefix = if is_classifier { \"class\" } else { \"target\" };\n    let cls = |name| format!(\"{leaf_prefix}_{name}\");\n\n    let n_leaves = node.get_attr_slice::<i64>(&cls(\"ids\"))?.len();\n    node.expect_attr(&cls(\"ids\"), n_leaves != 0, \"at least one leaf\")?;\n\n    let leaf_node_ids = get_vec_attr::<usize>(node, &cls(\"nodeids\"), n_leaves)?;\n    let leaf_tree_ids = get_vec_attr::<usize>(node, &cls(\"treeids\"), n_leaves)?;\n    let leaf_class_ids = get_vec_attr::<usize>(node, &cls(\"ids\"), n_leaves)?;\n    let leaf_weights = get_vec_attr::<f32>(node, &cls(\"weights\"), n_leaves)?;\n\n    let inc_by_1 = |x: &[_]| x.iter().zip(x.iter().skip(1)).all(|(&x, &y)| y == x || y == x + 1);\n    node.expect_attr(\"nodes_treeids\", inc_by_1(&tree_ids), \"tree ids to increase by 1\")?;\n    node.expect_attr(&cls(\"treeids\"), inc_by_1(&leaf_tree_ids), \"leaf tree ids to increase by 1\")?;\n    node.expect_attr(\"nodes_treeids\", tree_ids[0] == 0, \"tree ids to start from 0\")?;\n    node.expect_attr(&cls(\"treeids\"), leaf_tree_ids[0] == 0, \"leaf tree ids to start from 0\")?;\n    let n_trees = *tree_ids.last().unwrap() + 1;\n    node.expect(\n        leaf_tree_ids.last() == Some(&(n_trees - 1)),\n        \"mismatching # of trees (nodes/leaves)\",\n    )?;\n\n    let mut node_order: Vec<usize> = (0usize..node_ids.len()).collect();\n    node_order.sort_by_key(|&ix| (tree_ids[ix], node_ids[ix]));\n\n    let mut leaf_order: Vec<usize> = (0usize..leaf_node_ids.len()).collect();\n    leaf_order.sort_by_key(|&ix| (leaf_tree_ids[ix], leaf_node_ids[ix]));\n\n    let mut trees = vec![];\n    let mut nodes: Vec<u32> = vec![];\n    let mut leaves: Vec<u32> = vec![];\n    let mut current_tree_id = None;\n    let mut in_leaf_ix = 0;\n    for n in node_order.into_iter() {\n        let node_id = node_ids[n];\n        let tree_id = tree_ids[n];\n        if Some(tree_id) != current_tree_id {\n            current_tree_id = Some(tree_id);\n            trees.push(nodes.len() as u32 / 5);\n        }\n        if let Some(mode) = node_modes[n] {\n            let mut row = [0u32; 5];\n            row[0] = feature_ids[n] as u32;\n            row[1] = true_ids[n] as u32 + trees.last().unwrap();\n            row[2] = false_ids[n] as u32 + trees.last().unwrap();\n            row[3] = node_values[n].to_bits();\n            row[4] = (0x0100u32 * nan_is_true[n] as u32) | mode as u32;\n            nodes.extend(row.iter());\n        } else {\n            let mut row = [0u32; 5];\n            row[0] = leaves.len() as u32 / 2;\n            loop {\n                if in_leaf_ix >= leaf_order.len()\n                    || leaf_tree_ids[leaf_order[in_leaf_ix]] != tree_id\n                    || leaf_node_ids[leaf_order[in_leaf_ix]] != node_id\n                {\n                    break;\n                }\n                let leaf_ix = leaf_order[in_leaf_ix];\n                leaves.push(leaf_class_ids[leaf_ix] as u32);\n                leaves.push(leaf_weights[leaf_ix].to_bits());\n                in_leaf_ix += 1;\n            }\n            row[1] = leaves.len() as u32 / 2;\n            nodes.extend(row.iter());\n        };\n    }\n    let trees = rctensor1(&trees);\n    let nodes = tensor1(&nodes).into_shape(&[nodes.len() / 5, 5])?.into_arc_tensor();\n    let leaves = tensor1(&leaves).into_shape(&[leaves.len() / 2, 2])?.into_arc_tensor();\n    let data = TreeEnsembleData { trees, nodes, leaves };\n    TreeEnsemble::build(data, max_used_features, n_classes, aggregate_fn)\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct TreeEnsembleClassifier {\n    pub ensemble: TreeEnsemble,\n    pub class_labels: Arc<Tensor>,\n    pub base_class_score: Option<Arc<Tensor>>,\n    pub post_transform: Option<PostTransform>,\n    pub binary_result_layout: bool,\n}\n\nimpl Expansion for TreeEnsembleClassifier {\n    fn name(&self) -> StaticName {\n        \"TreeEnsembleClassifier\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"binary result layout kludge: {:?}\", self.binary_result_layout)])\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 2)?;\n\n        s.equals(&outputs[0].datum_type, self.class_labels.datum_type())?;\n        s.equals(&outputs[1].datum_type, DatumType::F32)?;\n\n        s.equals(&outputs[0].rank, 1)?;\n        s.equals(&outputs[1].rank, 2)?;\n        s.equals(&outputs[0].shape[0], &inputs[0].shape[0])?;\n        s.equals(&outputs[1].shape[0], &inputs[0].shape[0])?;\n        if self.binary_result_layout {\n            s.equals(&outputs[1].shape[1], 2.to_dim())?;\n        } else {\n            s.equals(&outputs[1].shape[1], self.class_labels.len().to_dim())?;\n        }\n\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        use tract_core::ops::nn::*;\n\n        let mut scores = model.wire_node(\n            format!(\"{prefix}.classifier\"),\n            tract_onnx_opl::ml::tree_ensemble_classifier::TreeEnsembleClassifier {\n                ensemble: self.ensemble.clone(),\n            },\n            inputs,\n        )?;\n        if let Some(base_class_score) = self.base_class_score.as_deref() {\n            let base = base_class_score.clone().broadcast_into_rank(2)?.into_arc_tensor();\n            let base = model.add_const(prefix.to_string() + \".base\", base)?;\n            scores = model.wire_node(\n                format!(\"{prefix}.base_class_score\"),\n                tract_core::ops::math::add(),\n                &[scores[0], base],\n            )?;\n        }\n        match self.post_transform {\n            None => (),\n            Some(PostTransform::Softmax) => {\n                scores = tract_hir::ops::nn::LayerSoftmax::new(1, false).wire(\n                    &format!(\"{prefix}.softmax\"),\n                    model,\n                    &scores,\n                )?;\n            }\n            Some(PostTransform::Logistic) => {\n                scores = model.wire_node(\n                    format!(\"{prefix}.logistic\"),\n                    tract_core::ops::nn::sigmoid(),\n                    &scores,\n                )?;\n            }\n        }\n        let processed_scores = scores.clone();\n        if self.binary_result_layout {\n            scores = model.wire_node(\n                format!(\"{prefix}.binary_result_slice\"),\n                Slice::new(1, 0, 1),\n                &scores,\n            )?;\n            let one = model.add_const(prefix.to_string() + \".one\", rctensor2(&[[1f32]]))?;\n            let complement = model.wire_node(\n                format!(\"{prefix}.binary_result_complement\"),\n                tract_core::ops::math::sub(),\n                &[one, scores[0]],\n            )?;\n            scores = model.wire_node(\n                format!(\"{prefix}.binary_result\"),\n                TypedConcat::new(1),\n                &[complement[0], scores[0]],\n            )?;\n        }\n        let winners = model.wire_node(\n            format!(\"{prefix}.argmax\"),\n            Reduce::new(tvec!(1), Reducer::ArgMax(false)),\n            &processed_scores,\n        )?;\n        let reduced = model.wire_node(\n            format!(\"{prefix}.rm_axis\"),\n            tract_core::ops::change_axes::AxisOp::Rm(1),\n            &winners,\n        )?;\n        let casted = model.wire_node(\n            format!(\"{prefix}.casted\"),\n            tract_core::ops::cast::cast(i32::datum_type()),\n            &reduced,\n        )?;\n        let labels = model.wire_node(\n            format!(\"{prefix}.labels\"),\n            tract_onnx_opl::ml::DirectLookup::new(\n                self.class_labels.clone(),\n                Tensor::zero_dt(self.class_labels.datum_type(), &[])?.into_arc_tensor(),\n            )?,\n            &casted,\n        )?[0];\n        Ok(tvec!(labels, scores[0]))\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(2)\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/mod.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::*;\nuse tract_hir::internal::*;\n\nmod array;\nmod cast;\npub mod cumsum;\nmod d2s;\nmod einsum;\nmod fft;\nmod grid_sample;\npub mod logic;\nmod math;\nmod ml;\npub mod multinomial;\nmod nn;\nmod non_max_suppression;\nmod quant;\nmod random;\npub mod rec;\nmod resize;\nmod s2d;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"Constant\", konst);\n    reg.insert(\"Einsum\", einsum::einsum);\n    reg.insert(\"Identity\", |_, _| {\n        Ok((Box::<tract_hir::ops::identity::Identity>::default(), vec![]))\n    });\n    reg.insert(\"GridSample\", grid_sample::grid_sample);\n    reg.insert(\"Resize\", resize::resize);\n    reg.insert(\"NonMaxSuppression\", non_max_suppression::non_max_suppression);\n    reg.insert(\"Multinomial\", multinomial::multinomial);\n    array::register_all_ops(reg);\n    cast::register_all_ops(reg);\n    cumsum::register_all_ops(reg);\n    d2s::register_all_ops(reg);\n    fft::register_all_ops(reg);\n    logic::register_all_ops(reg);\n    math::register_all_ops(reg);\n    ml::register_all_ops(reg);\n    nn::register_all_ops(reg);\n    quant::register_all_ops(reg);\n    random::register_all_ops(reg);\n    rec::register_all_ops(reg);\n    s2d::register_all_ops(reg);\n}\n\nfn konst(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let value = if let Some(v) = node.get_attr_opt(\"value\")? {\n        ctx.load_tensor(v)?\n    } else if let Some(i) = node.get_attr_opt::<i64>(\"value_int\")? {\n        tensor0(i)\n    } else if let Some(v) = node.get_attr_opt::<f32>(\"value_float\")? {\n        tensor0(v)\n    } else if let Some(ints) = node.get_attr_opt_slice::<i64>(\"value_ints\")? {\n        tensor1(ints)\n    } else if let Some(floats) = node.get_attr_opt_slice::<f32>(\"value_floats\")? {\n        tensor1(floats)\n    } else if let Some(s) = node.get_attr_opt::<String>(\"value_string\")? {\n        tensor0(s)\n    } else if let Some(strings) = node.get_attr_opt_slice::<Vec<u8>>(\"value_strings\")? {\n        let strings: Vec<String> =\n            strings.iter().map(|b| String::from_utf8_lossy(b).into_owned()).collect();\n        tensor1(&strings)\n    } else {\n        bail!(\"Could not extract value out of Constant node\")\n    };\n    Ok((Box::new(tract_hir::ops::konst::Const::new(value.into_arc_tensor())?), vec![]))\n}\n"
  },
  {
    "path": "onnx/src/ops/multinomial.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\n\npub fn multinomial(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let dtype = match node.get_attr_opt(\"dtype\")?.unwrap_or(6) {\n        6 => DatumType::I32,\n        7 => DatumType::I64,\n        i => bail!(\"Unsupported datum type {} for ONNX Multinomial\", i),\n    };\n    let sample_size = node.get_attr_opt(\"sample_size\")?.unwrap_or(1);\n    let seed = node.get_attr::<f32>(\"seed\").ok();\n\n    Ok((expand(Multinomial { dtype, sample_size, seed }), vec![]))\n}\n\n#[derive(Clone, Debug)]\npub struct Multinomial {\n    dtype: DatumType,\n    sample_size: i32,\n    pub seed: Option<f32>,\n}\nimpl PartialEq for Multinomial {\n    fn eq(&self, _: &Self) -> bool {\n        false\n    }\n}\nimpl Eq for Multinomial {}\n\nimpl Expansion for Multinomial {\n    fn name(&self) -> StaticName {\n        \"Multinomial\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1)?;\n        check_input_arity(inputs, 1)?;\n\n        // inputs[0]: tensor(float16), tensor(float), tensor(double) ; [batch_size, class_size]\n        // outputs[0]: tensor(int32), tensor(int64) {depending on self.datum_type} ; [batch_size, sample_size]\n\n        s.equals(&inputs[0].rank, 2)?;\n        s.equals(&outputs[0].rank, 2)?;\n        s.equals(&outputs[0].datum_type, self.dtype)?;\n        s.equals(&inputs[0].shape[0], &outputs[0].shape[0])?; // batch_size\n        s.equals(&outputs[0].shape[1], self.sample_size.to_dim())?; // sample_size\n\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        model.wire_node(\n            name,\n            tract_onnx_opl::multinomial::Multinomial {\n                dtype: self.dtype,\n                sample_size: self.sample_size,\n                seed: self.seed,\n            },\n            &[inputs[0]],\n        )\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/nn/batch_norm.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops::nn::DataFormat;\nuse tract_num_traits::AsPrimitive;\n\n#[derive(Debug, Clone, new, Default)]\npub struct BatchNorm {\n    data_format: DataFormat,\n    epsilon: f32,\n    #[allow(dead_code)]\n    spatial: bool,\n}\n\nimpl BatchNorm {\n    fn to_slope_and_inter<T>(\n        &self,\n        c_dim: usize,\n        scale: &Tensor,\n        beta: &Tensor,\n        mean: &Tensor,\n        var: &Tensor,\n    ) -> TractResult<(Tensor, Tensor)>\n    where\n        T: Datum + tract_num_traits::Float,\n        f32: AsPrimitive<T>,\n    {\n        let scale = scale.to_plain_array_view::<T>()?.into_shape_with_order((c_dim,))?;\n        let beta = beta.to_plain_array_view::<T>()?.into_shape_with_order((c_dim,))?;\n        let mean = mean.to_plain_array_view::<T>()?.into_shape_with_order((c_dim,))?;\n        let var = var.to_plain_array_view::<T>()?.into_shape_with_order((c_dim,))?;\n\n        let denominator = var.mapv(|x| (x + self.epsilon.as_()).sqrt());\n\n        let slope = &scale / &denominator;\n        let intercept = beta.to_owned() - (&mean * &scale) / denominator;\n        Ok((slope.into_tensor(), intercept.into_tensor()))\n    }\n}\n\nimpl Expansion for BatchNorm {\n    fn name(&self) -> StaticName {\n        \"BatchNorm\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 5)?;\n        check_output_arity(outputs, 1)?;\n        s.equals_all(wrap!(\n            &outputs[0].datum_type,\n            &inputs[0].datum_type,\n            &inputs[1].datum_type,\n            &inputs[2].datum_type,\n            &inputs[3].datum_type,\n            &inputs[4].datum_type\n        ))?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        s.equals_all(wrap!(\n            &inputs[1].shape,\n            &inputs[2].shape,\n            &inputs[3].shape,\n            &inputs[4].shape\n        ))?;\n        s.given(&inputs[0].shape, move |s, shape| {\n            let shape = self.data_format.shape(shape)?;\n            s.equals(&inputs[1].shape[0], shape.c_dim())\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let x = target.outlet_fact(inputs[0])?;\n        let params = (1..5)\n            .map(|i| Ok(target.outlet_fact(inputs[i])?.konst.clone()))\n            .collect::<TractResult<TVec<Option<Arc<Tensor>>>>>()?;\n\n        if let (Some(scale), Some(beta), Some(mean), Some(var)) =\n            (params[0].as_ref(), params[1].as_ref(), params[2].as_ref(), params[3].as_ref())\n        {\n            let x_shape = x.shape.to_tvec();\n            let c_axis = self.data_format.shape(&x_shape)?.c_axis();\n            let c_dim = self.data_format.shape(&x_shape)?.c_dim().to_usize()?;\n\n            let (mut slope, mut inter) =\n                dispatch_floatlike!(Self::to_slope_and_inter(x.datum_type)(\n                    self, c_dim, scale, beta, mean, var\n                ))?;\n\n            let mut const_shape = tvec!(1; x_shape.len());\n            const_shape[c_axis] = c_dim;\n\n            slope.set_shape(&const_shape)?;\n            inter.set_shape(&const_shape)?;\n\n            let slope = target.add_const(prefix.to_string() + \".slope\", slope)?;\n            let inter = target.add_const(prefix.to_string() + \".inter\", inter)?;\n            let wire = target.wire_node(\n                format!(\"{prefix}.mul\"),\n                tract_hir::ops::math::mul(),\n                &[slope, inputs[0]],\n            )?;\n            return target.wire_node(prefix, tract_hir::ops::math::add(), &[inter, wire[0]]);\n        }\n        bail!(\"Params are not const\")\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/nn/conv_transpose.rs",
    "content": "use std::str;\n\nuse crate::model::ParsingContext;\nuse crate::pb::NodeProto;\nuse tract_core::ops::cnn::KernelFormat;\nuse tract_core::ops::cnn::deconv::adjustments;\nuse tract_hir::ops::cnn::PaddingSpec;\nuse tract_hir::ops::nn::DataFormat;\nuse tract_hir::{internal::*, ops::cnn::PoolSpec};\n\npub fn conv_transpose(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let padding_spec = super::pad(node, false)?;\n    let strides = super::strides(node)?;\n    let dilations = super::dilations(node)?;\n    let adjustments = node.get_attr_opt_tvec::<usize>(\"output_padding\")?;\n    let output_shape = node.get_attr_opt_tvec::<usize>(\"output_shape\")?;\n    let group = node.get_attr_opt::<usize>(\"group\")?.unwrap_or(1);\n    Ok((\n        expand(ConvTranspose::new(\n            padding_spec,\n            strides,\n            dilations,\n            adjustments,\n            output_shape,\n            group,\n            node.input.len() == 3,\n        )),\n        vec![],\n    ))\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct ConvTranspose {\n    padding_spec: PaddingSpec,\n    strides: Option<TVec<usize>>,\n    dilations: Option<TVec<usize>>,\n    adjustments: Option<TVec<usize>>,\n    output_shape: Option<TVec<usize>>,\n    group: usize,\n    have_bias: bool,\n}\n\nimpl Expansion for ConvTranspose {\n    fn name(&self) -> StaticName {\n        \"ConvTranspose\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2 + self.have_bias as usize)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].datum_type, &inputs[1].datum_type)?;\n        s.equals(&inputs[0].rank, &inputs[1].rank)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        s.equals(&outputs[0].shape[0], &inputs[0].shape[0])?; // N\n        s.equals(&inputs[0].shape[1], &inputs[1].shape[0])?; // O\n        s.equals(&outputs[0].shape[1], (self.group as i64) * inputs[1].shape[1].bex())?; // I\n\n        s.given_2(&inputs[0].shape, &inputs[1].shape, move |s, x_shape, w_shape| {\n            if let (Ok(x_shape), Ok(w_shape)) = (\n                x_shape.iter().map(|d| d.to_usize()).collect::<TractResult<TVec<usize>>>(),\n                w_shape.iter().map(|d| d.to_usize()).collect::<TractResult<TVec<usize>>>(),\n            ) {\n                let y_shape = if let Some(output_shape) = &self.output_shape {\n                    let mut y_shape = x_shape;\n                    y_shape[1] = w_shape[1] * self.group;\n                    for (ix, d) in output_shape.iter().enumerate() {\n                        y_shape[ix + 2] = *d;\n                    }\n                    y_shape\n                } else {\n                    // ONNX deconv kernels are stored as gi_o_h_w (convolution are go_i_hw)\n                    // so tract KernelFormat (in|out)put_channel functions do not work.\n                    let ci = w_shape[0];\n                    let co = w_shape[1] * self.group;\n                    let pool_spec = PoolSpec::new(\n                        DataFormat::NCHW,\n                        w_shape[2..].into(),\n                        self.padding_spec.clone(),\n                        self.dilations.clone(),\n                        self.strides.clone(),\n                        ci,\n                        co,\n                    );\n                    tract_core::ops::cnn::deconv::output_shape(\n                        &pool_spec,\n                        &x_shape,\n                        &self.adjustments.clone().unwrap_or_else(|| tvec!(0; x_shape.len() - 2 )),\n                    )?\n                };\n                let y_shape = y_shape.iter().map(|x| x.to_dim()).collect::<TVec<TDim>>();\n                s.equals(&outputs[0].shape, y_shape)?;\n            }\n            Ok(())\n        })?;\n        if self.have_bias {\n            s.equals(&inputs[2].datum_type, &inputs[0].datum_type)?;\n            s.equals(&inputs[2].rank, 1)?;\n            s.equals(&inputs[2].shape[0], &outputs[0].shape[1])?;\n        }\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        // ONNX deconv kernels are stored as gi_o_h_w (convolution are go_i_hw)\n        /*\n        let kernel =\n            k.into_tensor().split_axis(0, self.group)?.move_axis(1, 2)?.collapse_axis_with_next(0);\n        */\n        let mut kernel = AxisOp::wire_split_axis(\n            target,\n            format!(\"{prefix}.kernel_split_group\"),\n            inputs[1],\n            0,\n            self.group,\n        )?;\n        kernel =\n            target.wire_node(format!(\"{prefix}.kernel_reorder\"), AxisOp::Move(1, 2), &kernel)?;\n        kernel = AxisOp::wire_collapse_axis(\n            target,\n            format!(\"{prefix}.kernel_merge_group\"),\n            kernel[0],\n            0,\n        )?;\n\n        let bias = if self.have_bias {\n            inputs[2]\n        } else {\n            target.add_const(\n                format!(\"{prefix}.bias\"),\n                Tensor::zero_scalar_dt(target.outlet_fact(inputs[0])?.datum_type)?,\n            )?\n        };\n\n        let kernel_shape = target\n            .outlet_fact(kernel[0])?\n            .shape\n            .as_concrete()\n            .context(\"Expects concrete kernel shape\")?;\n        let ci = KernelFormat::OIHW.input_channels(kernel_shape, self.group).into_owned();\n        let co = KernelFormat::OIHW.output_channels(kernel_shape, self.group).into_owned();\n        let pool_spec = PoolSpec::new(\n            DataFormat::NCHW,\n            kernel_shape[2..].into(),\n            self.padding_spec.clone(),\n            self.dilations.clone(),\n            self.strides.clone(),\n            ci,\n            co,\n        );\n        let op = if let Some(output_shape) = &self.output_shape {\n            let x_shape = &target.outlet_fact(inputs[0])?.shape;\n            let adjustments = adjustments(\n                &pool_spec,\n                &x_shape.as_concrete().context(\"expects concrete dim for deconv\")?[2..],\n                output_shape,\n            )?;\n            tract_core::ops::cnn::Deconv::new(\n                pool_spec,\n                KernelFormat::OIHW,\n                adjustments,\n                self.group,\n            )\n        } else {\n            tract_core::ops::cnn::Deconv::new(\n                pool_spec,\n                KernelFormat::OIHW,\n                self.adjustments.clone().unwrap_or_else(|| tvec!(0; kernel_shape.len() - 2)),\n                self.group,\n            )\n        };\n        target.wire_node(prefix, op, &[inputs[0], kernel[0], bias])\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/nn/dropout.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\n\npub fn dropout(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    Ok((Box::new(Dropout::new(node.output.len() == 2)), vec![]))\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct Dropout {\n    output_mask: bool,\n}\n\nimpl Op for Dropout {\n    fn name(&self) -> StaticName {\n        \"Dropout\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Dropout {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        if self.output_mask {\n            let input = args_1!(inputs);\n            let mask = tract_ndarray::ArrayD::from_elem(input.shape(), true);\n            Ok(tvec!(input, mask.into_tvalue()))\n        } else {\n            Ok(inputs)\n        }\n    }\n}\n\nimpl InferenceRulesOp for Dropout {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1 + self.output_mask as usize)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        if outputs.len() == 2 {\n            s.equals(&outputs[1].datum_type, bool::datum_type())?;\n            s.equals(&inputs[0].shape, &outputs[1].shape)?;\n        }\n        Ok(())\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(1 + self.output_mask as usize)\n    }\n\n    as_op!();\n    to_typed!();\n}\n\nimpl TypedOp for Dropout {\n    as_op!();\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if node.outputs.len() == 1 || node.outputs[1].successors.len() == 0 {\n            Ok(Some(TypedModelPatch::single_unary_op(\n                model,\n                node,\n                tract_hir::ops::identity::Identity,\n            )?))\n        } else {\n            Ok(None)\n        }\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/nn/instance_norm.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\n\npub fn instance_normalization(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let epsilon = node.get_attr_opt(\"epsilon\")?.unwrap_or(1e-5);\n    Ok((expand(InstanceNorm::new(epsilon)), vec![]))\n}\n\n#[derive(Debug, Clone, new, Default)]\npub struct InstanceNorm {\n    epsilon: f32,\n}\n\nimpl Expansion for InstanceNorm {\n    fn name(&self) -> StaticName {\n        \"InstanceNorm\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].datum_type, &inputs[1].datum_type)?;\n        s.equals(&inputs[0].datum_type, &inputs[2].datum_type)?;\n        s.equals(&inputs[1].shape, &inputs[2].shape)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        s.equals(&inputs[1].shape[0], &inputs[0].shape[1])?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input_fact = model.outlet_fact(inputs[0])?.clone();\n        let rank = input_fact.rank();\n        let axes: Vec<_> = (0..rank as i64).filter(|&axis| axis != 1).collect();\n        let mean = tract_hir::ops::nn::Reduce::new(\n            Some(axes.clone()),\n            true,\n            tract_hir::ops::nn::Reducer::Mean,\n        )\n        .wire(&format!(\"{name}.mean\"), model, &inputs[0..1])?[0];\n        let diff = model.wire_node(\n            format!(\"{name}.diff\"),\n            tract_hir::ops::math::sub(),\n            &[inputs[0], mean],\n        )?;\n        let sqr_diff =\n            model.wire_node(format!(\"{name}.sqr\"), tract_hir::ops::math::square(), &diff)?;\n        let vari =\n            tract_hir::ops::nn::Reduce::new(Some(axes), true, tract_hir::ops::nn::Reducer::Mean)\n                .wire(&format!(\"{name}.variance\"), model, &sqr_diff)?[0];\n        let epsilon = model.add_const(\n            format!(\"{name}.epsilon.cst\"),\n            tensor0(self.epsilon)\n                .cast_to_dt(input_fact.datum_type)?\n                .into_owned()\n                .broadcast_into_rank(rank)?\n                .into_arc_tensor(),\n        )?;\n        let vari_sane = model.wire_node(\n            format!(\"{name}.epsilon\"),\n            tract_hir::ops::math::add(),\n            &[vari, epsilon],\n        )?;\n        let div =\n            model.wire_node(format!(\"{name}.rsqrt\"), tract_hir::ops::math::rsqrt(), &vari_sane)?;\n        let divised = model.wire_node(\n            format!(\"{name}.div\"),\n            tract_hir::ops::math::mul(),\n            &[diff[0], div[0]],\n        )?;\n        let mut scale =\n            model.wire_node(format!(\"{name}.add-scale-axis-n\"), AxisOp::Add(0), &inputs[1..2])?;\n        for i in 2..rank {\n            scale =\n                model.wire_node(format!(\"{name}.add-scale-axis-{i}\"), AxisOp::Add(2), &scale)?;\n        }\n        let scaled = model.wire_node(\n            format!(\"{name}.scaled\"),\n            tract_hir::ops::math::mul(),\n            &[divised[0], scale[0]],\n        )?;\n        let mut bias =\n            model.wire_node(format!(\"{name}.add-bias-axis-n\"), AxisOp::Add(0), &inputs[2..3])?;\n        for i in 2..rank {\n            bias = model.wire_node(format!(\"{name}.add-bias-axis-{i}\"), AxisOp::Add(2), &bias)?;\n        }\n        model.wire_node(name, tract_hir::ops::math::add(), &[scaled[0], bias[0]])\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/nn/layer_norm.rs",
    "content": "use crate::model::{ParsingContext, optional_outputs};\nuse crate::pb::NodeProto;\nuse tract_core::ops::cast::cast;\nuse tract_core::ops::math::{add, div, mul, rsqrt, square, sub};\nuse tract_core::ops::nn::{Reduce, Reducer};\nuse tract_hir::internal::*;\nuse tract_hir::ops::logic::wire_with_rank_broadcast;\n\npub fn layer_norm(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?.unwrap_or(-1);\n    let epsilon = node.get_attr_opt(\"epsilon\")?.unwrap_or(1e-5);\n    let datum_type = node.get_attr_opt(\"stash_type\")?.unwrap_or(DatumType::F32);\n    let have_bias = node.input.len() == 3;\n    let mut optional_outputs = optional_outputs(node).skip(1);\n    let mean_output = optional_outputs.next().unwrap();\n    let invstddev_output = optional_outputs.next().unwrap();\n    Ok((\n        expand(LayerNorm { axis, epsilon, datum_type, have_bias, mean_output, invstddev_output }),\n        vec![],\n    ))\n}\n\n#[derive(Debug, Clone)]\npub struct LayerNorm {\n    axis: isize,\n    epsilon: f32,\n    datum_type: DatumType,\n    have_bias: bool,\n    mean_output: Option<usize>,\n    invstddev_output: Option<usize>,\n}\n\nimpl Expansion for LayerNorm {\n    fn name(&self) -> StaticName {\n        \"LayerNorm\".into()\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(1 + self.mean_output.is_some() as usize + self.invstddev_output.is_some() as usize)\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2 + self.have_bias as usize)?;\n        check_output_arity(outputs, self.nboutputs()?)?;\n        s.equals(&inputs[0].datum_type, &inputs[1].datum_type)?;\n        if self.have_bias {\n            s.equals(&inputs[0].datum_type, &inputs[2].datum_type)?;\n        }\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n\n        if let Some(mean) = self.mean_output {\n            s.equals(&outputs[mean].datum_type, self.datum_type)?;\n            s.equals(&inputs[0].rank, &outputs[mean].rank)?;\n        }\n        if let Some(invstddev) = self.invstddev_output {\n            s.equals(&outputs[invstddev].datum_type, self.datum_type)?;\n            s.equals(&inputs[0].rank, &outputs[invstddev].rank)?;\n        }\n        s.given(&inputs[0].rank, move |s, rank| {\n            let axis = if self.axis < 0 {\n                (self.axis + rank as isize) as usize\n            } else {\n                self.axis as usize\n            };\n            for ax in 0..axis {\n                if let Some(mean) = self.mean_output {\n                    s.equals(&inputs[0].shape[ax], &outputs[mean].shape[ax])?;\n                }\n                if let Some(invstddev) = self.invstddev_output {\n                    s.equals(&inputs[0].shape[ax], &outputs[invstddev].shape[ax])?;\n                }\n            }\n            for ax in axis..rank as usize {\n                if let Some(mean) = self.mean_output {\n                    s.equals(&outputs[mean].shape[ax], 1.to_dim())?;\n                }\n                if let Some(invstddev) = self.invstddev_output {\n                    s.equals(&outputs[invstddev].shape[ax], 1.to_dim())?;\n                }\n            }\n            Ok(())\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        // Mean = ReduceMean<axes=normalized_axes>(X) D = Sub(X, Mean) DD = Mul(D, D) Var = ReduceMean<axes=normalized_axes>(DD) VarEps = Add(Var, epsilon) StdDev = Sqrt(VarEps) InvStdDev = Reciprocal(StdDev) Normalized = Mul(D, InvStdDev) }\n        let fact = model.outlet_fact(inputs[0])?.clone();\n        let axis = if self.axis < 0 {\n            (self.axis + fact.rank() as isize) as usize\n        } else {\n            self.axis as usize\n        };\n        let cast_x =\n            model.wire_node(format!(\"{prefix}.cast_x\"), cast(self.datum_type), &[inputs[0]])?;\n        let cast_scale =\n            model.wire_node(format!(\"{prefix}.cast_scale\"), cast(self.datum_type), &[inputs[1]])?;\n        let cast_bias = if self.have_bias {\n            Some(model.wire_node(\n                format!(\"{prefix}.cast_bias\"),\n                cast(self.datum_type),\n                &[inputs[2]],\n            )?)\n        } else {\n            None\n        };\n        let axes: TVec<usize> = (axis..fact.rank()).collect();\n        let reduced_sum_x = model.wire_node(\n            format!(\"{prefix}.reduced_sum\"),\n            Reduce { axes: axes.clone(), reducer: Reducer::Sum },\n            &cast_x,\n        )?;\n        let len = axes.iter().map(|ax| fact.shape[*ax].clone()).product::<TDim>();\n        let len = model.add_const(format!(\"{prefix}.len\"), tensor0(len))?;\n        let cast_len =\n            model.wire_node(format!(\"{prefix}.cast_len\"), cast(self.datum_type), &[len])?;\n        let reduced_mean_x = wire_with_rank_broadcast(\n            format!(\"{prefix}.reduced_mean_x\"),\n            model,\n            div(),\n            &[reduced_sum_x[0], cast_len[0]],\n        )?;\n        let d = model.wire_node(format!(\"{prefix}.d\"), sub(), &[cast_x[0], reduced_mean_x[0]])?;\n        let dd = model.wire_node(format!(\"{prefix}.dd\"), square(), &d)?;\n        let reduced_sum_dd = model.wire_node(\n            format!(\"{prefix}.reduced_sum_dd\"),\n            Reduce { axes, reducer: Reducer::Sum },\n            &dd,\n        )?;\n        let var = wire_with_rank_broadcast(\n            format!(\"{prefix}.var\"),\n            model,\n            div(),\n            &[reduced_sum_dd[0], cast_len[0]],\n        )?;\n        let epsilon = model.add_const(\n            format!(\"{prefix}.epsilon\"),\n            tensor0(self.epsilon).cast_to_dt(self.datum_type)?.into_owned(),\n        )?;\n        let var_eps = wire_with_rank_broadcast(\n            format!(\"{prefix}.var_eps\"),\n            model,\n            add(),\n            &[var[0], epsilon],\n        )?;\n        let inv_std_dev = model.wire_node(format!(\"{prefix}.inv_std_dev\"), rsqrt(), &var_eps)?;\n        let normalized =\n            model.wire_node(format!(\"{prefix}.normalized\"), mul(), &[d[0], inv_std_dev[0]])?;\n        // NormalizedScaled = Mul(Normalized, Scale) Y = Add(NormalizedScaled, B)\n        let cast_normalized = model.wire_node(\n            format!(\"{prefix}.cast_normalized\"),\n            cast(fact.datum_type),\n            &normalized,\n        )?;\n        let normalized_scaled = wire_with_rank_broadcast(\n            format!(\"{prefix}.normalized_scaled\"),\n            model,\n            mul(),\n            &[cast_normalized[0], cast_scale[0]],\n        )?;\n        let y = if let Some(bias) = cast_bias {\n            wire_with_rank_broadcast(\n                format!(\"{prefix}.y\"),\n                model,\n                add(),\n                &[normalized_scaled[0], bias[0]],\n            )?\n        } else {\n            normalized_scaled\n        };\n        let mut outputs = tvec!(y[0]);\n        if self.mean_output.is_some() {\n            outputs.push(reduced_mean_x[0]);\n        }\n        if self.invstddev_output.is_some() {\n            outputs.push(inv_std_dev[0]);\n        }\n        Ok(outputs)\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/nn/lrn.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\n\nuse tract_onnx_opl::lrn::Lrn;\n\npub fn lrn(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr_opt(\"alpha\")?.unwrap_or(0.0001);\n    let beta = node.get_attr_opt(\"beta\")?.unwrap_or(0.75);\n    let bias = node.get_attr_opt(\"bias\")?.unwrap_or(1.);\n    let size = node.get_attr(\"size\")?;\n    Ok((inference_wrap(Lrn { alpha, beta, bias, size }, 1, lrn_rules), vec![]))\n}\n\nfn lrn_rules<'p>(\n    _op: &dyn Op,\n    s: &mut Solver,\n    inputs: &'p [TensorProxy],\n    outputs: &'p [TensorProxy],\n) -> InferenceResult {\n    check_input_arity(inputs, 1)?;\n    check_output_arity(outputs, 1)?;\n    s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n    s.equals(&inputs[0].shape, &outputs[0].shape)?;\n    Ok(())\n}\n"
  },
  {
    "path": "onnx/src/ops/nn/mod.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops;\n\nuse tract_hir::ops::{cnn, nn};\n\nuse crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::NodeProto;\nuse crate::pb_helpers::OptionExt;\n\nmod batch_norm;\nmod conv_transpose;\nmod dropout;\nmod instance_norm;\nmod layer_norm;\nmod lrn;\nmod reduce;\n\npub fn arg_max_min(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?.unwrap_or(0);\n    let keepdims = node.get_attr_opt(\"keepdims\")?.unwrap_or(true);\n    let take_last = node.get_attr_opt(\"select_last_index\")?.unwrap_or(false);\n    let red = if node.op_type == \"ArgMax\" {\n        nn::Reducer::ArgMax(take_last)\n    } else {\n        nn::Reducer::ArgMin(take_last)\n    };\n    Ok((expand(nn::Reduce::new(Some(vec![axis]), keepdims, red)), vec![]))\n}\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"ArgMax\", arg_max_min);\n    reg.insert(\"ArgMin\", arg_max_min);\n    reg.insert(\"AveragePool\", average_pool);\n    reg.insert(\"BatchNormalization\", batch_normalization);\n    reg.insert(\"Celu\", celu);\n    reg.insert(\"Conv\", conv);\n    reg.insert(\"ConvInteger\", conv_integer);\n    reg.insert(\"ConvTranspose\", conv_transpose::conv_transpose);\n    reg.insert(\"Dropout\", dropout::dropout);\n    reg.insert(\"Elu\", elu);\n    reg.insert(\"GlobalAveragePool\", |_, _| Ok((expand(ops::nn::GlobalAvgPool), vec![])));\n    reg.insert(\"GlobalLpPool\", global_lp_pool);\n    reg.insert(\"GlobalMaxPool\", |_, _| Ok((expand(ops::nn::GlobalMaxPool), vec![])));\n    reg.insert(\"Hardmax\", layer_hard_max);\n    reg.insert(\"HardSigmoid\", hard_sigmoid);\n    reg.insert(\"InstanceNormalization\", instance_norm::instance_normalization);\n    reg.insert(\"LayerNormalization\", layer_norm::layer_norm);\n    reg.insert(\"LeakyRelu\", leaky_relu);\n    reg.insert(\"LogSoftmax\", layer_log_soft_max);\n    reg.insert(\"LRN\", lrn::lrn);\n    reg.insert(\"MaxPool\", max_pool);\n    reg.insert(\"ParametricSoftplus\", parametric_softplus);\n    reg.insert(\"QLinearConv\", conv_qlinear);\n    reg.insert(\"PRelu\", |_, _| Ok((expand(Prelu), vec![])));\n    reg.insert(\"ReduceL1\", |c, node| reduce::reduce(c, node, nn::Reducer::L1));\n    reg.insert(\"ReduceL2\", |c, node| reduce::reduce(c, node, nn::Reducer::L2));\n    reg.insert(\"ReduceLogSum\", |c, node| reduce::reduce(c, node, nn::Reducer::LogSum));\n    reg.insert(\"ReduceLogSumExp\", |c, node| reduce::reduce(c, node, nn::Reducer::LogSumExp));\n    reg.insert(\"ReduceMax\", |c, node| reduce::reduce(c, node, nn::Reducer::Max));\n    reg.insert(\"ReduceMean\", |c, node| reduce::reduce(c, node, nn::Reducer::Mean));\n    reg.insert(\"ReduceMin\", |c, node| reduce::reduce(c, node, nn::Reducer::Min));\n    reg.insert(\"ReduceProd\", |c, node| reduce::reduce(c, node, nn::Reducer::Prod));\n    reg.insert(\"ReduceSum\", |c, node| reduce::reduce(c, node, nn::Reducer::Sum));\n    reg.insert(\"ReduceSumSquare\", |c, node| reduce::reduce(c, node, nn::Reducer::SumSquare));\n    reg.insert(\"Relu\", |_, _| Ok((expand(ops::activations::Clip::new(Some(0.0), None)), vec![])));\n    reg.insert(\"ScaledTanh\", scaled_tanh);\n    reg.insert(\"Shrink\", shrink);\n    reg.insert(\"ThresholdedRelu\", thresholded_relu);\n    reg.insert(\"Selu\", selu);\n    reg.insert(\"Sigmoid\", |_, _| Ok((ops::nn::sigmoid().into_hir(), vec![])));\n    reg.insert(\"HardSwish\", |_, _| Ok((ops::nn::hard_swish().into_hir(), vec![])));\n    reg.insert(\"Softmax\", layer_soft_max);\n    reg.insert(\"Softplus\", |_, _| Ok((expand(ops::activations::Softplus), vec![])));\n    reg.insert(\"Softsign\", |_, _| Ok((expand(ops::activations::Softsign), vec![])));\n}\n\nfn pad(node: &NodeProto, pool_rules: bool) -> TractResult<cnn::PaddingSpec> {\n    let ceil_mode = node.get_attr_opt::<isize>(\"ceil_mode\")?.unwrap_or(0) == 1;\n    let default = match node.get_attr_opt_vec::<isize>(\"kernel_shape\")? {\n        Some(shape) => {\n            if pool_rules {\n                cnn::PaddingSpec::ExplicitOnnxPool(\n                    tvec!(0; shape.len()),\n                    tvec!(0; shape.len()),\n                    ceil_mode,\n                )\n            } else {\n                cnn::PaddingSpec::Explicit(tvec!(0; shape.len()), tvec!(0; shape.len()))\n            }\n        }\n        None => cnn::PaddingSpec::Valid,\n    };\n    if let Some(pads) = node.get_attr_opt_tvec(\"pads\")? {\n        let len = pads.len();\n        let left = pads.iter().cloned().take(len / 2).collect();\n        let right = pads.iter().cloned().skip(len / 2).collect();\n        if pool_rules {\n            return Ok(cnn::PaddingSpec::ExplicitOnnxPool(left, right, ceil_mode));\n        } else {\n            return Ok(cnn::PaddingSpec::Explicit(left, right));\n        }\n    }\n    Ok(node\n        .get_attr_opt(\"auto_pad\")?\n        .and_try(|s| {\n            node.check_value(\n                \"auto_pad\",\n                match s {\n                    \"NOTSET\" => Ok(default.clone()),\n                    \"VALID\" => Ok(cnn::PaddingSpec::Valid),\n                    \"SAME_UPPER\" => Ok(cnn::PaddingSpec::SameUpper),\n                    \"SAME_LOWER\" => Ok(cnn::PaddingSpec::SameLower),\n                    _ => Err(s),\n                },\n            )\n        })?\n        .unwrap_or(default))\n}\n\nfn dilations(node: &NodeProto) -> TractResult<Option<TVec<usize>>> {\n    node.get_attr_opt_tvec(\"dilations\")\n}\n\nfn strides(node: &NodeProto) -> TractResult<Option<TVec<usize>>> {\n    node.get_attr_opt_tvec(\"strides\")\n}\n\npub fn batch_normalization(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let epsilon = node.get_attr_opt(\"epsilon\")?.unwrap_or(1e-5);\n    let spatial = node.get_attr_opt(\"spatial\")?.unwrap_or(1);\n    if spatial != 1 {\n        bail!(\n            \"BatchNormalization: attribute 'spatial' is not supported (deprecated by ONNX operator set 9)\"\n        )\n    }\n    Ok((expand(batch_norm::BatchNorm::new(nn::DataFormat::NCHW, epsilon, spatial != 0)), vec![]))\n}\n\nfn common_conv(node: &NodeProto) -> TractResult<cnn::Conv> {\n    let mut op = ops::cnn::Conv::default().padding(pad(node, false)?);\n    if let Some(kernel_shape) = node.get_attr_opt_tvec(\"kernel_shape\")? {\n        op = op.kernel_shape(kernel_shape);\n    }\n    if let Some(group) = node.get_attr_opt(\"group\")? {\n        op = op.group(group);\n    }\n    if let Some(v) = dilations(node)? {\n        op = op.dilations(v);\n    }\n    if let Some(v) = strides(node)? {\n        op = op.strides(v);\n    }\n    Ok(op)\n}\n\npub fn conv(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mut op = common_conv(node)?;\n    if node.input.len() == 3 {\n        op = op.bias_input(2);\n    }\n    Ok((expand(op), vec![]))\n}\n\npub fn conv_integer(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mut op = common_conv(node)?;\n    let mut options = crate::model::optional_inputs(node).skip(2);\n    if let Some(i) = options.next().unwrap() {\n        op = op.x_zero_point_input(i);\n    }\n    if let Some(i) = options.next().unwrap() {\n        op = op.k_zero_point_input(i);\n    }\n    op.override_output_datum_type = Some(i32::datum_type());\n    Ok((expand(op), vec![]))\n}\n\npub fn conv_qlinear(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let mut op = common_conv(node)?;\n    op.x_scale_input = Some(1);\n    op.x_zero_point_input = Some(2);\n    op.k_input = Some(3);\n    op.k_scale_input = Some(4);\n    op.k_zero_point_input = Some(5);\n    op.y_scale_input = Some(6);\n    op.y_zero_point_input = Some(7);\n    if node.input.len() == 9 {\n        op.bias_input = Some(8);\n    }\n    Ok((expand(op), vec![]))\n}\n\npub fn average_pool(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let kernel_shape = node.get_attr_tvec(\"kernel_shape\")?;\n    let pad = pad(node, true)?;\n    let strides = strides(node)?;\n    let count_include_pad = node.get_attr_opt(\"count_include_pad\")?.unwrap_or(false);\n    Ok((\n        expand(cnn::HirSumPool::new(\n            cnn::PoolSpec::new(nn::DataFormat::NCHW, kernel_shape, pad, None, strides, 0, 0),\n            count_include_pad,\n            true,\n        )),\n        vec![],\n    ))\n}\n\npub fn celu(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr_opt(\"alpha\")?.unwrap_or(1.);\n    Ok((expand(ops::activations::Celu(alpha)), vec![]))\n}\n\npub fn elu(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr_opt(\"alpha\")?.unwrap_or(1.);\n    Ok((expand(ops::activations::Elu(alpha)), vec![]))\n}\n\npub fn global_lp_pool(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let p: usize = node.get_attr_opt(\"p\")?.unwrap_or(2);\n    Ok((expand(ops::nn::GlobalLpPool::new(p)), vec![]))\n}\n\npub fn hard_sigmoid(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr_opt(\"alpha\")?.unwrap_or(0.2);\n    let beta = node.get_attr_opt(\"beta\")?.unwrap_or(0.5);\n    Ok((expand(ops::activations::HardSigmoid(alpha, beta)), vec![]))\n}\n\npub fn layer_hard_max(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?;\n    if ctx.onnx_operator_set_version < 13 {\n        Ok((expand(ops::nn::LayerHardmax::new(axis.unwrap_or(1), true)), vec![]))\n    } else {\n        Ok((expand(ops::nn::LayerHardmax::new(axis.unwrap_or(-1), false)), vec![]))\n    }\n}\n\npub fn layer_log_soft_max(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?;\n    if ctx.onnx_operator_set_version < 13 {\n        Ok((expand(ops::nn::LayerLogSoftmax::new(axis.unwrap_or(1), true)), vec![]))\n    } else {\n        Ok((expand(ops::nn::LayerLogSoftmax::new(axis.unwrap_or(-1), false)), vec![]))\n    }\n}\n\npub fn layer_soft_max(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let axis = node.get_attr_opt(\"axis\")?;\n    if ctx.onnx_operator_set_version < 13 {\n        Ok((expand(ops::nn::LayerSoftmax::new(axis.unwrap_or(1), true)), vec![]))\n    } else {\n        Ok((expand(ops::nn::Softmax::new(axis.unwrap_or(-1))), vec![]))\n    }\n}\n\npub fn leaky_relu(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr_opt(\"alpha\")?.unwrap_or(0.01);\n    Ok((expand(ops::activations::LeakyRelu(alpha)), vec![]))\n}\n\npub fn max_pool(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let kernel_shape = node.get_attr_tvec(\"kernel_shape\")?;\n    let pad = pad(node, true)?;\n    let strides = strides(node)?;\n    Ok((\n        expand(cnn::HirMaxPool::new(\n            cnn::PoolSpec::new(nn::DataFormat::NCHW, kernel_shape, pad, None, strides, 0, 0),\n            if node.output.len() == 2 { Some(DatumType::I64) } else { None },\n        )),\n        vec![],\n    ))\n}\n\npub fn parametric_softplus(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr(\"alpha\")?;\n    let beta = node.get_attr(\"beta\")?;\n    Ok((expand(ops::activations::ParametricSoftplus(alpha, beta)), vec![]))\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct Prelu;\n\nimpl Expansion for Prelu {\n    fn name(&self) -> StaticName {\n        \"Prelu\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let a = inputs[0];\n        let mut b = inputs[1];\n        let rank = model.outlet_fact(a)?.rank();\n        while model.outlet_fact(b)?.rank() < rank {\n            b = model.wire_node(\n                format!(\"{}.add-axis-{}\", name, model.outlet_fact(b)?.rank()),\n                AxisOp::Add(0),\n                &[b],\n            )?[0];\n        }\n        let zero = tensor0(0.0)\n            .cast_to_dt(model.outlet_fact(a)?.datum_type)?\n            .into_owned()\n            .broadcast_into_rank(rank)?;\n        let ab = model.wire_node(format!(\"{name}.mul\"), tract_hir::ops::math::mul(), &[a, b])?[0];\n        let zero = model.add_const(name.to_string() + \".zero\", zero)?;\n        let test = model.wire_node(\n            name.to_string() + \".test\",\n            tract_core::ops::binary::TypedBinOp(tract_core::ops::logic::comp_gt(), None),\n            &[zero, a],\n        )?;\n        model.wire_node(name.to_string() + \".iff\", tract_core::ops::logic::Iff, &[test[0], ab, a])\n    }\n}\n\npub fn scaled_tanh(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr(\"alpha\")?;\n    let beta = node.get_attr(\"beta\")?;\n    Ok((expand(ops::activations::ScaledTanh(alpha, beta)), vec![]))\n}\n\npub fn shrink(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let bias = node.get_attr_opt(\"bias\")?.unwrap_or(0.0);\n    let lambd = node.get_attr_opt(\"lambd\")?.unwrap_or(0.5);\n    Ok((expand(ops::activations::Shrink(bias, lambd)), vec![]))\n}\n\npub fn selu(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr_opt(\"alpha\")?.unwrap_or(1.67326);\n    let gamma = node.get_attr_opt(\"gamma\")?.unwrap_or(1.0507);\n    Ok((expand(ops::activations::Selu(alpha, gamma)), vec![]))\n}\n\npub fn thresholded_relu(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let alpha = node.get_attr_opt(\"alpha\")?.unwrap_or(1.);\n    Ok((expand(ops::activations::ThresholdRelu(alpha)), vec![]))\n}\n"
  },
  {
    "path": "onnx/src/ops/nn/reduce.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\n\npub(crate) fn reduce(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n    reducer: tract_hir::ops::nn::Reducer,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    // this is crazy. sum changed semantics at opset 13, other reducers switched at 18!\n    if (ctx.onnx_operator_set_version >= 13 && \"ReduceSum\" == node.op_type)\n        || ctx.onnx_operator_set_version >= 18\n    {\n        let have_axis_input = node.input.len() == 2;\n        let keep_dims = node.get_attr_opt(\"keepdims\")?.unwrap_or(1i64) == 1;\n        let noop_with_empty_axes = node.get_attr_opt(\"noop_with_empty_axes\")?.unwrap_or(0i64) == 1;\n        Ok((\n            expand(ReduceSum13 { have_axis_input, keep_dims, noop_with_empty_axes, reducer }),\n            vec![],\n        ))\n    } else {\n        let axes = node.get_attr_opt_vec(\"axes\")?;\n        let keep_dims = node.get_attr_opt(\"keepdims\")?.unwrap_or(1i64) == 1;\n        Ok((expand(tract_hir::ops::nn::Reduce::new(axes, keep_dims, reducer)), vec![]))\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct ReduceSum13 {\n    pub have_axis_input: bool,\n    pub keep_dims: bool,\n    pub noop_with_empty_axes: bool,\n    pub reducer: tract_hir::ops::nn::Reducer,\n}\n\nimpl Expansion for ReduceSum13 {\n    fn name(&self) -> StaticName {\n        \"Reduce13\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1 + self.have_axis_input as usize)?;\n        check_output_arity(outputs, 1)?;\n        if let tract_hir::ops::nn::Reducer::ArgMax(_) | tract_hir::ops::nn::Reducer::ArgMin(_) =\n            self.reducer\n        {\n            s.equals(&outputs[0].datum_type, DatumType::I64)?;\n        } else {\n            s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        }\n        if self.have_axis_input {\n            s.given_2(&inputs[0].rank, &inputs[1].value, move |s, rank, axes| {\n                let mut axes = axes.cast_to::<i64>()?.try_as_plain()?.as_slice::<i64>()?.to_vec();\n                if axes.len() == 0 && !self.noop_with_empty_axes {\n                    axes = (0..rank).collect()\n                };\n                let op = tract_hir::ops::nn::Reduce::new(\n                    Some(axes.clone()),\n                    self.keep_dims,\n                    self.reducer,\n                );\n                if self.keep_dims {\n                    s.equals(&inputs[0].rank, &outputs[0].rank)?;\n                } else {\n                    s.equals(inputs[0].rank.bex() - axes.len() as i64, &outputs[0].rank)?;\n                }\n                s.given(&inputs[0].shape, move |s, shape| {\n                    let out_shape = op.output_shape(&shape);\n                    s.equals(&outputs[0].shape, out_shape)\n                })\n            })\n        } else {\n            s.given(&inputs[0].rank, move |s, rank| {\n                let axes = if self.noop_with_empty_axes { vec![] } else { (0..rank).collect() };\n                let op = tract_hir::ops::nn::Reduce::new(\n                    Some(axes.clone()),\n                    self.keep_dims,\n                    self.reducer,\n                );\n                if self.keep_dims {\n                    s.equals(&inputs[0].rank, &outputs[0].rank)?;\n                } else {\n                    s.equals(inputs[0].rank.bex() - axes.len() as i64, &outputs[0].rank)?;\n                }\n                s.given(&inputs[0].shape, move |s, shape| {\n                    let out_shape = op.output_shape(&shape);\n                    s.equals(&outputs[0].shape, out_shape)\n                })\n            })\n        }\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let axes: Vec<i64> = if self.have_axis_input {\n            model\n                .outlet_fact(inputs[1])?\n                .konst\n                .as_ref()\n                .context(\"expected axes as a constant\")?\n                .try_as_plain()?\n                .as_slice::<i64>()?\n                .to_vec()\n        } else {\n            vec![]\n        };\n        let axes: Vec<i64> = if axes.len() == 0 {\n            if self.noop_with_empty_axes {\n                vec![]\n            } else {\n                (0..model.outlet_fact(inputs[0])?.rank()).map(|ax| ax as i64).collect()\n            }\n        } else {\n            axes\n        };\n        let op = tract_hir::ops::nn::Reduce::new(Some(axes), self.keep_dims, self.reducer);\n        op.wire(prefix, model, &inputs[0..1])\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/non_max_suppression.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_onnx_opl::non_max_suppression::BoxRepr;\n\npub fn non_max_suppression(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let center_point_box =\n        BoxRepr::from_i64(node.get_attr_opt(\"center_point_box\")?.unwrap_or(0i64))?;\n\n    let mut options = crate::model::optional_inputs(node).skip(2);\n    Ok((\n        expand(NonMaxSuppression {\n            optional_max_output_boxes_per_class_input: options.next().unwrap(),\n            optional_iou_threshold_input: options.next().unwrap(),\n            optional_score_threshold_input: options.next().unwrap(),\n            center_point_box,\n            num_selected_indices_symbol: ctx.template.symbols.new_with_prefix(\"x\"),\n        }),\n        vec![],\n    ))\n}\n\n#[derive(Clone, new, Debug, Hash, PartialEq, Eq)]\nstruct NonMaxSuppression {\n    optional_max_output_boxes_per_class_input: Option<usize>,\n    optional_iou_threshold_input: Option<usize>,\n    optional_score_threshold_input: Option<usize>,\n    center_point_box: BoxRepr,\n    num_selected_indices_symbol: Symbol,\n}\n\nimpl Expansion for NonMaxSuppression {\n    fn name(&self) -> StaticName {\n        \"NonMaxSuppression\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        let input_count = 2\n            + self.optional_max_output_boxes_per_class_input.is_some() as usize\n            + self.optional_iou_threshold_input.is_some() as usize\n            + self.optional_score_threshold_input.is_some() as usize;\n        check_input_arity(inputs, input_count)?;\n        check_output_arity(outputs, 1)?;\n\n        // [out] selected_indices: shape=[num_selected_indices, 3], type=int64\n        s.equals(&outputs[0].rank, 2)?;\n        s.equals(&outputs[0].shape[0], self.num_selected_indices_symbol.to_dim())?;\n        s.equals(&outputs[0].shape[1], 3usize.to_dim())?;\n        s.equals(&outputs[0].datum_type, i64::datum_type())?;\n\n        // [in] boxes: shape=[num_batches, spatial_dimension, 4], type=float\n        s.equals(&inputs[0].rank, 3)?;\n        s.equals(&inputs[0].shape[2], 4usize.to_dim())?;\n        s.equals(&inputs[0].datum_type, f32::datum_type())?;\n\n        // [in] scores: shape=[num_batches, num_classes, spatial_dimension], type=float\n        s.equals(&inputs[1].rank, 3)?;\n        s.equals(&inputs[1].datum_type, f32::datum_type())?;\n\n        // num_batches in boxes, scores\n        s.equals(&inputs[0].shape[0], &inputs[1].shape[0])?;\n        // spatial_dimension in boxes, scores\n        s.equals(&inputs[0].shape[1], &inputs[1].shape[2])?;\n\n        // [in, optional] max_output_boxes_per_class: scalar, type=int64\n        if let Some(index) = self.optional_max_output_boxes_per_class_input {\n            s.equals(&inputs[index].rank, 1)?;\n            s.equals(&inputs[index].shape[0], 1usize.to_dim())?;\n            s.equals(&inputs[index].datum_type, i64::datum_type())?;\n        }\n\n        // [in, optional] iou_threshold: scalar, type=float\n        if let Some(index) = self.optional_iou_threshold_input {\n            s.equals(&inputs[index].rank, 1)?;\n            s.equals(&inputs[index].shape[0], 1usize.to_dim())?;\n            s.equals(&inputs[index].datum_type, f32::datum_type())?;\n        }\n\n        // [in, optional] score_threshold: scalar, type=float\n        if let Some(index) = self.optional_score_threshold_input {\n            s.equals(&inputs[index].rank, 1)?;\n            s.equals(&inputs[index].shape[0], 1usize.to_dim())?;\n            s.equals(&inputs[index].datum_type, f32::datum_type())?;\n        }\n\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let max_output_boxes_per_class = self\n            .optional_max_output_boxes_per_class_input\n            .map(|index| Ok(inputs[index]))\n            .unwrap_or_else(|| {\n                model.add_const(format!(\"{name}.max_output_boxes_per_class\"), tensor0(0i64))\n            })?;\n        let iou_threshold = self\n            .optional_iou_threshold_input\n            .map(|index| Ok(inputs[index]))\n            .unwrap_or_else(|| model.add_const(format!(\"{name}.iou_threshold\"), tensor0(0.0f32)))?;\n        // score_threshold is an optional input, but we cannot assing it a meaningful default value\n        let score_threshold = self.optional_score_threshold_input.map(|index| inputs[index]);\n\n        let op = tract_onnx_opl::non_max_suppression::NonMaxSuppression {\n            center_point_box: self.center_point_box,\n            num_selected_indices_symbol: self.num_selected_indices_symbol.clone(),\n            has_score_threshold: score_threshold.is_some(),\n        };\n\n        if let Some(score_threshold) = score_threshold {\n            model.wire_node(\n                name,\n                op,\n                &[\n                    inputs[0], // boxes\n                    inputs[1], // scores\n                    max_output_boxes_per_class,\n                    iou_threshold,\n                    score_threshold,\n                ],\n            )\n        } else {\n            model.wire_node(\n                name,\n                op,\n                &[\n                    inputs[0], // boxes\n                    inputs[1], // scores\n                    max_output_boxes_per_class,\n                    iou_threshold,\n                ],\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/quant.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\nuse tract_hir::ops::quant::*;\nuse tract_ndarray::ArrayViewD;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"QuantizeLinear\", quantize_linear);\n    reg.insert(\"DequantizeLinear\", dequantize_linear);\n    reg.insert(\"DynamicQuantizeLinear\", dynamic_quantize_linear);\n}\n\nfn quantize_linear(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let op = QuantizeLinear::new(Some(2).filter(|_| node.input.len() == 3));\n    Ok((expand(op), vec![]))\n}\n\nfn dequantize_linear(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let op = DequantizeLinear::new(Some(2).filter(|_| node.input.len() == 3));\n    Ok((expand(op), vec![]))\n}\n\nfn dynamic_quantize_linear(\n    _ctx: &ParsingContext,\n    _node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let op = DynamicQuantizeLinear::new();\n    Ok((expand(op), vec![]))\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct QuantizeLinear {\n    optional_zero_point_input: Option<usize>,\n}\n\nimpl Expansion for QuantizeLinear {\n    fn name(&self) -> StaticName {\n        \"QuantizeLinear\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()> {\n        check_input_arity(inputs, 2 + self.optional_zero_point_input.is_some() as usize)?;\n        check_output_arity(outputs, 1)?;\n        //         s.equals(&inputs[1].rank, 0)?; broken in Onnx test suite\n        s.equals(&inputs[1].datum_type, f32::datum_type())?;\n        if self.optional_zero_point_input.is_some() {\n            s.equals(&outputs[0].datum_type, &inputs[2].datum_type)?;\n        //            s.equals(&inputs[2].rank, 0)?; // broken in Onnx test suite\n        } else {\n            s.equals(&outputs[0].datum_type, u8::datum_type())?;\n        }\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        use tract_hir::ops::quant::*;\n        let scale = target\n            .outlet_fact(inputs[1])?\n            .konst\n            .as_ref()\n            .context(\"y_scale must be a const\")?\n            .try_as_plain()?\n            .as_slice::<f32>()?[0]\n            .recip();\n        let zero_point = if self.optional_zero_point_input.is_some() {\n            target\n                .outlet_fact(inputs[2])?\n                .konst\n                .as_ref()\n                .context(\"y_zero_point must be a const\")?\n                .clone()\n        } else {\n            rctensor0(0u8)\n        };\n        let op: Box<dyn TypedOp> = if zero_point.datum_type() == u8::datum_type() {\n            Box::new(quantize_linear_u8(scale, zero_point.try_as_plain()?.as_slice::<u8>()?[0]))\n        } else {\n            Box::new(quantize_linear_i8(scale, zero_point.try_as_plain()?.as_slice::<i8>()?[0]))\n        };\n        target.wire_node(prefix, op, &[inputs[0]])\n    }\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct DequantizeLinear {\n    optional_zero_point_input: Option<usize>,\n}\n\nimpl Expansion for DequantizeLinear {\n    fn name(&self) -> StaticName {\n        \"DequantizeLinear\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()> {\n        check_input_arity(inputs, 2 + self.optional_zero_point_input.is_some() as usize)?;\n        check_output_arity(outputs, 1)?;\n        //         s.equals(&inputs[1].rank, 0)?; broken in Onnx test suite\n        s.equals(&inputs[1].datum_type, f32::datum_type())?;\n        s.equals(&outputs[0].datum_type, f32::datum_type())?;\n        if self.optional_zero_point_input.is_some() {\n            s.equals(&inputs[0].datum_type, &inputs[2].datum_type)?;\n            //            s.equals(&inputs[2].rank, 0)?; // broken in Onnx test suite\n        }\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let scale = target\n            .outlet_fact(inputs[1])?\n            .konst\n            .as_ref()\n            .context(\"y_scale must be a const\")?\n            .try_as_plain()?\n            .as_slice::<f32>()?[0];\n        let zero_point = if self.optional_zero_point_input.is_some() {\n            target\n                .outlet_fact(inputs[2])?\n                .konst\n                .as_ref()\n                .context(\"y_zero_point must be a const\")?\n                .clone()\n        } else {\n            rctensor0(0u8)\n        };\n        let op: Box<dyn TypedOp> = if zero_point.datum_type() == u8::datum_type() {\n            Box::new(DequantizeLinearF32::new(\n                scale,\n                zero_point.try_as_plain()?.as_slice::<u8>()?[0] as i32,\n            ))\n        } else if zero_point.datum_type() == i8::datum_type() {\n            Box::new(DequantizeLinearF32::new(\n                scale,\n                zero_point.try_as_plain()?.as_slice::<i8>()?[0] as i32,\n            ))\n        } else {\n            Box::new(DequantizeLinearF32::new(\n                scale,\n                zero_point.try_as_plain()?.as_slice::<i32>()?[0],\n            ))\n        };\n        target.wire_node(prefix, op, &[inputs[0]])\n    }\n}\n\n#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]\npub struct DynamicQuantizeLinear {}\n\nimpl Expansion for DynamicQuantizeLinear {\n    fn name(&self) -> StaticName {\n        \"DynamicQuantizeLinear\".into()\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(3)\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()> {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 3)?;\n        s.equals(&inputs[0].datum_type, f32::datum_type())?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        s.equals(&outputs[0].datum_type, u8::datum_type())?;\n        s.equals(&outputs[1].datum_type, f32::datum_type())?;\n        s.equals(&outputs[1].rank, 0)?;\n        s.equals(&outputs[2].datum_type, u8::datum_type())?;\n        s.equals(&outputs[2].rank, 0)?;\n\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let op: Box<dyn TypedOp> = Box::new(DynamicQuantizeLinearU8::new());\n        target.wire_node(format!(\"{prefix}.dynamic_quantize\"), op, &[inputs[0]])\n    }\n}\n\nfn dynamic_quantize_linear_f32_u8(x: f32, scale: f32, zero_point: u8) -> u8 {\n    (((x / scale).round() as i32) + zero_point as i32).clamp(u8::MIN as i32, u8::MAX as i32) as u8\n}\n\nfn dynamic_quantize_linear_u8(scale: f32, zero_point: u8, xs: &[f32], ys: &mut [u8]) {\n    xs.iter()\n        .zip(ys.iter_mut())\n        .for_each(|(x, y)| *y = dynamic_quantize_linear_f32_u8(*x, scale, zero_point));\n}\n\nfn scale_and_zero_point(v: ArrayViewD<f32>) -> (f32, u8) {\n    // get the min and max of v and extend it to have zero included\n    // in the interval [min, max]\n    let (min, max) = v.fold((0., 0.), |(a_min, a_max), &v| {\n        if v < a_min {\n            (v, a_max)\n        } else if v > a_max {\n            (a_min, v)\n        } else {\n            (a_min, a_max)\n        }\n    });\n\n    // quantize range\n    let min_t = u8::MIN as f32;\n    let max_t = u8::MAX as f32;\n\n    let scale = (max - min) / max_t;\n\n    let zero_point = -min / scale;\n    let zero_point = zero_point.round();\n    // clipping to [0, 255]\n    let zero_point = zero_point.max(min_t);\n    let zero_point = zero_point.min(max_t);\n\n    let zero_point: u8 = zero_point as u8;\n\n    (scale, zero_point)\n}\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\npub struct DynamicQuantizeLinearU8;\n\nimpl Op for DynamicQuantizeLinearU8 {\n    fn name(&self) -> StaticName {\n        \"DynamicQuantizeLinearU8\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![])\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Accurate\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DynamicQuantizeLinearU8 {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = &inputs[0];\n        let input = input.cast_to::<f32>()?;\n        let a_input = input.to_plain_array_view::<f32>()?;\n        let (scale, zero_point) = scale_and_zero_point(a_input);\n\n        let mut dst = unsafe { Tensor::uninitialized_dt(u8::datum_type(), input.shape())? };\n        // We cannot use quantize_linear_u8 here because it does `x * scale.recip()`\n        // instead of `x / scale`. This change some number enough to be rounded to another integer.\n        dynamic_quantize_linear_u8(\n            scale,\n            zero_point,\n            input.try_as_plain()?.as_slice::<f32>()?,\n            dst.try_as_plain_mut()?.as_slice_mut::<u8>()?,\n        );\n\n        let quantized_tensor = dst.into_tvalue();\n        let scale_tensor = tensor0(scale).into();\n        let zero_point_tensor = tensor0(zero_point).into();\n\n        Ok(tvec!(quantized_tensor, scale_tensor, zero_point_tensor))\n    }\n}\n\nimpl TypedOp for DynamicQuantizeLinearU8 {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut quantized_fact = inputs[0].clone();\n        quantized_fact.datum_type = u8::datum_type();\n        let scale_fact = f32::fact([0; 0]);\n        let zero_fact = u8::fact([0; 0]);\n        Ok(tvec!(quantized_fact, scale_fact, zero_fact))\n    }\n\n    as_op!();\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use tract_ndarray::arr1;\n\n    // Data for tests is from:\n    // https://github.com/onnx/onnx/blob/master/docs/Operators.md#DynamicQuantizeLinear\n    #[test]\n    fn test_scale_and_zero_point() {\n        let data: [(&[f32], f32, u8); 3] = [\n            (&[0., 2., -3., -2.5, 1.34, 0.5], 0.019_607_844, 153),\n            (&[-1., -2.1, -1.3, -2.5, -3.34, -4.], 0.015_686_275, 255),\n            (&[1., 2.1, 1.3, 2.5, 3.34, 4., 1.5, 2.6, 3.9, 4., 3., 2.345], 0.015_686_275, 0),\n        ];\n\n        let epsilon = 0.00000001;\n        for (v, scale_ok, zero_point_ok) in &data {\n            let v = arr1(v).into_dyn();\n            let v = v.view();\n            let (scale, zero_point) = scale_and_zero_point(v);\n            assert!((scale - scale_ok).abs() < epsilon);\n            assert_eq!(zero_point, *zero_point_ok);\n        }\n    }\n\n    #[test]\n    fn test_dynamic_quantize_linear_u8() {\n        let data: [(&[f32], &[u8]); 3] = [\n            (&[0., 2., -3., -2.5, 1.34, 0.5], &[153, 255, 0, 26, 221, 179]),\n            (&[-1., -2.1, -1.3, -2.5, -3.34, -4.], &[191, 121, 172, 96, 42, 0]),\n            (\n                &[1., 2.1, 1.3, 2.5, 3.34, 4., 1.5, 2.6, 3.9, 4., 3., 2.345],\n                &[64, 134, 83, 159, 213, 255, 96, 166, 249, 255, 191, 149],\n            ),\n        ];\n\n        for (v, quantized_ok) in &data {\n            let v = arr1(v).into_dyn();\n            let (scale, zero_point) = scale_and_zero_point(v.view());\n\n            // same shape of v but with u8 type, values will be overwritten\n            let mut quantized = v.mapv(|_| 0_u8);\n            dynamic_quantize_linear_u8(\n                scale,\n                zero_point,\n                v.as_slice().unwrap(),\n                quantized.as_slice_mut().unwrap(),\n            );\n            assert_eq!(quantized.as_slice().unwrap(), *quantized_ok);\n        }\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/random.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::ops::OnnxOpRegister;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_onnx_opl::random::Dist;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"RandomUniform\", random);\n    reg.insert(\"RandomUniformLike\", random);\n    reg.insert(\"RandomNormal\", random);\n    reg.insert(\"RandomNormalLike\", random);\n}\n\npub fn random(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let dt: Option<DatumType> = node.get_attr_opt(\"dtype\")?;\n    let seed = node.get_attr_opt::<f32>(\"seed\")?;\n\n    let dist = if node.op_type.starts_with(\"RandomNormal\") {\n        Dist::Normal {\n            mean: rctensor0(node.get_attr::<f32>(\"mean\").unwrap_or(0.0)),\n            dev: rctensor0(node.get_attr::<f32>(\"scale\").unwrap_or(1.0)),\n        }\n    } else {\n        Dist::Uniform {\n            low: rctensor0(node.get_attr::<f32>(\"low\").unwrap_or(0.0)),\n            high: rctensor0(node.get_attr::<f32>(\"high\").unwrap_or(1.0)),\n        }\n    };\n\n    if node.name.ends_with(\"Like\") {\n        Ok((expand(RandomLike { dt, dist, seed }), vec![]))\n    } else {\n        let shape = node.get_attr_slice::<i64>(\"shape\")?.iter().map(|i| i.to_dim()).collect();\n        Ok((expand(Random { dt: dt.unwrap_or(DatumType::F32), dist, shape, seed }), vec![]))\n    }\n}\n\n#[derive(Debug, Clone)]\nstruct Random {\n    dt: DatumType,\n    dist: Dist,\n    shape: TVec<TDim>,\n    seed: Option<f32>,\n}\n\nimpl Expansion for Random {\n    fn name(&self) -> StaticName {\n        \"Random\".into()\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Random\n    }\n\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 0)?;\n        check_output_arity(outputs, 1)?;\n\n        s.equals(&outputs[0].shape, self.shape.clone())?;\n        s.equals(&outputs[0].datum_type, self.dt)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        _inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        model.wire_node(\n            prefix,\n            tract_onnx_opl::random::Random {\n                dist: self.dist.clone(),\n                fact: self.dt.fact(&self.shape),\n                seed: self.seed.map(|f| f.to_bits() as u64),\n            },\n            &[],\n        )\n    }\n}\n\n#[derive(Debug, Clone)]\nstruct RandomLike {\n    dt: Option<DatumType>,\n    dist: Dist,\n    seed: Option<f32>,\n}\n\nimpl Expansion for RandomLike {\n    fn name(&self) -> StaticName {\n        \"RandomLike\".into()\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Random\n    }\n\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n\n        s.equals(&outputs[0].shape, &inputs[0].shape)?;\n        if let Some(dt) = self.dt {\n            s.equals(&outputs[0].datum_type, dt)?;\n        } else {\n            s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        }\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let mut fact = model.outlet_fact(inputs[0])?.without_value();\n        if let Some(dt) = self.dt {\n            fact.datum_type = dt;\n        }\n        model.wire_node(\n            prefix,\n            tract_onnx_opl::random::Random {\n                dist: self.dist.clone(),\n                fact,\n                seed: self.seed.map(|f| f.to_bits() as u64),\n            },\n            &[],\n        )\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/rec/common.rs",
    "content": "use std::fmt::Debug;\n\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_hir::tract_core::dyn_clone::{DynClone, clone_trait_object};\nuse tract_hir::tract_core::ops::scan::ScanInfo;\n\npub trait WireBody: Debug + DynClone + Send + Sync {\n    fn name(&self) -> &'static str;\n    fn wire_body(&self, prefix: &str, body: &mut TypedModel) -> TractResult<()>;\n    fn w_b_multipliers(&self) -> (usize, usize);\n    fn have_extra_c_state(&self) -> bool;\n}\n\nclone_trait_object!(WireBody);\n\n#[derive(Debug, Clone)]\npub struct CommonRec {\n    pub optional_bias_input: Option<usize>,\n    pub optional_sequence_lens_input: Option<usize>,\n    pub optional_initial_h_input: Option<usize>,\n    pub optional_initial_c_input: Option<usize>,\n    pub optional_p_input: Option<usize>,\n    pub optional_y_output: Option<usize>,\n    pub optional_y_h_output: Option<usize>,\n    pub optional_y_c_output: Option<usize>,\n    pub batch_first: bool,\n    pub body: Box<dyn WireBody>,\n}\n\nimpl CommonRec {\n    pub fn from_node_and_options(\n        pb: &NodeProto,\n        fixed_input: usize,\n        fixed_outputs: usize,\n        body: Box<dyn WireBody>,\n    ) -> TractResult<Self> {\n        let mut inputs = crate::model::optional_inputs(pb).skip(fixed_input);\n        let mut outputs = crate::model::optional_outputs(pb).skip(fixed_outputs);\n        Ok(Self {\n            optional_bias_input: inputs.next().unwrap(),\n            optional_sequence_lens_input: inputs.next().unwrap(),\n            optional_initial_h_input: inputs.next().unwrap(),\n            optional_initial_c_input: inputs.next().unwrap(),\n            optional_p_input: inputs.next().unwrap(),\n\n            optional_y_output: outputs.next().unwrap(),\n            optional_y_h_output: outputs.next().unwrap(),\n            optional_y_c_output: outputs.next().unwrap(),\n\n            batch_first: pb.get_attr_opt(\"layout\")?.unwrap_or(0) == 1,\n            body,\n        })\n    }\n\n    #[allow(non_snake_case)]\n    fn wire_one_side(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n        dir: usize,\n    ) -> TractResult<TVec<OutletId>> {\n        use tract_hir::ops::{array, scan};\n\n        let x_fact = target.outlet_fact(inputs[0])?.clone();\n        let r_fact = target.outlet_fact(inputs[2])?.clone();\n\n        if let Some(seqlen) = self.optional_sequence_lens_input {\n            let Some(seqlen) = &target.outlet_fact(inputs[seqlen])?.konst else {\n                bail!(\"Non constant seq_len is not supported\");\n            };\n            let Some(seqlen) = seqlen.as_uniform() else {\n                bail!(\"Non uniform seq_len is not supported\");\n            };\n            let seqlen = seqlen.cast_to::<TDim>()?;\n            if seqlen.try_as_plain()?.to_scalar::<TDim>()?\n                != &x_fact.shape[self.batch_first as usize]\n            {\n                bail!(\"seq_len only supported for trivial noop case\");\n            };\n        }\n\n        let b_size = &x_fact.shape[1 - self.batch_first as usize];\n        let h_size = &r_fact.shape[2];\n\n        let chunk = if dir == 0 { 1 } else { -1 };\n\n        let mut body = TypedModel { symbols: target.symbols.clone(), ..TypedModel::default() };\n        let mut outer_inputs = vec![];\n        let mut input_mapping = vec![];\n\n        macro_rules! target_wire {\n            ($name: ident = $op: expr, $($param: expr),*) => {\n                let $name = target.wire_node(\n                    format!(\"{}.{}\", prefix, stringify!($name)),\n                    $op, [$($param),*].as_ref())?[0];\n            }\n        }\n\n        macro_rules! wire {\n            ($name: ident = $op: expr, $($param: expr),*) => {\n                #[allow(unused_variables)]\n                let $name = body.wire_node(\n                    stringify!($name),\n                    $op, [$($param),*].as_ref())?[0];\n            }\n        }\n\n        // X: onnx interface: [batch_size, seq_length, input_size]\n        // move batch first\n        let x_batch_first = if self.batch_first {\n            inputs[0]\n        } else {\n            target_wire!(x_batch_first = AxisOp::Move(1, 0), inputs[0]);\n            x_batch_first\n        };\n        // scan outer interface: idem\n        // scann inner interface: [chunk=1, batch_size, input_size]\n        // onnx inner interface: [batch_size, input_size]\n        outer_inputs.push(x_batch_first);\n        input_mapping.push(scan::InputMapping::Scan(ScanInfo { axis: 1, chunk }));\n        let mut x_source_fact = target.outlet_fact(x_batch_first)?.without_value();\n        x_source_fact.shape.set(1, 1.to_dim());\n        let x_source = body.add_source(\"x_source\", x_source_fact)?;\n        wire!(Xt = AxisOp::Rm(1), x_source);\n\n        // W: onnx interface: [num_directions, 3*hidden_size, input_size]\n        // scan interfaces: [3*hidden_size, input_size]\n        target_wire!(w_dir = array::Slice::new(0, dir, dir + 1), inputs[1]);\n        target_wire!(w = AxisOp::Rm(0), w_dir);\n        outer_inputs.push(w);\n        input_mapping.push(scan::InputMapping::Full);\n        body.add_source(\"W\", target.outlet_fact(w)?.clone())?;\n\n        // R: onnx interface: [num_directions, 3*hidden_size, hidden_size]\n        // scan interfaces: [3*hidden_size, hidden_size]\n        target_wire!(r_dir = array::Slice::new(0, dir, dir + 1), inputs[2]);\n        target_wire!(r = AxisOp::Rm(0), r_dir);\n        outer_inputs.push(r);\n        input_mapping.push(scan::InputMapping::Full);\n        body.add_source(\"R\", target.outlet_fact(r)?.clone())?;\n\n        // B: onnx interface: [num_directions, 6*hidden_size]\n        if let Some(slot) = self.optional_bias_input {\n            target_wire!(b_dir = array::Slice::new(0, dir, dir + 1), inputs[slot]);\n            outer_inputs.push(b_dir);\n            input_mapping.push(scan::InputMapping::Full);\n            let b = body.add_source(\"b\", target.outlet_fact(b_dir)?.clone())?;\n            Some(b)\n        } else {\n            None\n        };\n\n        // initial h, optional: onnx: [num_directions, batch_size, hidden_size]\n        // scan outer: [batch_size, chunk=1, hidden_size]\n        // scan inner: [batch_size, chunk=1, hidden_size]\n        // onnx inner: [batch_size, hidden_size]\n        let initializer = if let Some(initial_h_input) = self.optional_initial_h_input {\n            let mut input = inputs[initial_h_input];\n            if self.batch_first {\n                target_wire!(h_batch_first = AxisOp::Move(1, 0), input);\n                input = h_batch_first;\n            };\n            target_wire!(h_dir = array::Slice::new(0, dir, dir + 1), input);\n            target_wire!(h = AxisOp::Rm(0), h_dir);\n            target_wire!(h_chunk_ = AxisOp::Add(0), h);\n            target_wire!(h_chunk = AxisOp::Move(1, 0), h_chunk_);\n            h_chunk\n        } else {\n            target.add_const(\n                format!(\"{prefix}.h0\"),\n                tensor0(0.0f32)\n                    .broadcast_scalar_to_shape(&[\n                        b_size.to_usize().unwrap(),\n                        1,\n                        h_size.to_usize().unwrap(),\n                    ])?\n                    .into_arc_tensor(),\n            )?\n        };\n        outer_inputs.push(initializer);\n        input_mapping.push(scan::InputMapping::State);\n\n        let h_source = body.add_source(\n            \"h_source\",\n            x_fact.datum_type.fact(&[b_size.clone(), 1.to_dim(), h_size.clone()]),\n        )?;\n        wire!(Ht_1 = AxisOp::Rm(1), h_source);\n\n        if self.body.have_extra_c_state() {\n            let initializer = if let Some(initial_c_input) = self.optional_initial_c_input {\n                let mut input = inputs[initial_c_input];\n                if self.batch_first {\n                    target_wire!(c_batch_first = AxisOp::Move(1, 0), input);\n                    input = c_batch_first;\n                };\n                target_wire!(c_dir = array::Slice::new(0, dir, dir + 1), input);\n                target_wire!(c = AxisOp::Rm(0), c_dir);\n                target_wire!(c_chunk_ = AxisOp::Add(0), c);\n                target_wire!(c_chunk = AxisOp::Move(1, 0), c_chunk_);\n                c_chunk\n            } else {\n                target.add_const(\n                    format!(\"{prefix}.c0\"),\n                    tensor0(0.0f32)\n                        .broadcast_scalar_to_shape(&[\n                            b_size.to_usize().unwrap(),\n                            1,\n                            h_size.to_usize().unwrap(),\n                        ])?\n                        .into_arc_tensor(),\n                )?\n            };\n            outer_inputs.push(initializer);\n            input_mapping.push(scan::InputMapping::State);\n            let c_source = body.add_source(\n                \"c_source\",\n                x_fact.datum_type.fact(&[b_size.clone(), 1.to_dim(), h_size.clone()]),\n            )?;\n            wire!(Ct_1 = AxisOp::Rm(1), c_source);\n        }\n\n        // P: onnx [num_directions, 3*hidde_size]\n        if let Some(slot) = self.optional_p_input {\n            target_wire!(p = array::Slice::new(0, dir, dir + 1), inputs[slot]);\n            outer_inputs.push(p);\n            input_mapping.push(scan::InputMapping::Full);\n            body.add_source(\"peepholes\", target.outlet_fact(p)?.clone())?;\n        };\n\n        self.body.wire_body(prefix, &mut body).context(\"Wiring body\")?;\n\n        let mut output_mapping = vec![scan::OutputMapping {\n            state: true,\n            full_dim_hint: None,\n            last_value_slot: self.optional_y_h_output,\n            scan: self.optional_y_output.map(|slot| (slot, ScanInfo { axis: 1, chunk })),\n        }];\n        if self.body.have_extra_c_state() {\n            output_mapping.push(scan::OutputMapping {\n                state: true,\n                full_dim_hint: None,\n                last_value_slot: self.optional_y_c_output,\n                scan: None,\n            });\n        }\n\n        let scan_outputs = target.wire_node(\n            prefix,\n            tract_core::ops::scan::Scan::new(body, input_mapping, output_mapping, 0)?,\n            &outer_inputs,\n        )?;\n\n        let mut result = tvec!();\n        if let Some(slot) = self.optional_y_output {\n            // scan: [batch_size, seq_len, hidden_size]\n            if self.batch_first {\n                // onnx: Y.shape = [batch_size, seq_length, num_directions, hidden_size]\n                target_wire!(y = AxisOp::Add(2), scan_outputs[slot]);\n                result.push(y);\n            } else {\n                // onnx: Y.shape = [seq_length, num_directions, batch_size, hidden_size]\n                target_wire!(y_batch_middle = AxisOp::Move(1, 0), scan_outputs[slot]);\n                target_wire!(y = AxisOp::Add(1), y_batch_middle);\n                result.push(y);\n            }\n        }\n        if let Some(slot) = self.optional_y_h_output {\n            if self.batch_first {\n                result.push(scan_outputs[slot]);\n            } else {\n                target_wire!(y_h_batch_middle = AxisOp::Move(1, 0), scan_outputs[slot]);\n                result.push(y_h_batch_middle);\n            }\n        }\n        if let Some(slot) = self.optional_y_c_output {\n            if self.batch_first {\n                result.push(scan_outputs[slot]);\n            } else {\n                target_wire!(y_c_batch_middle = AxisOp::Move(1, 0), scan_outputs[slot]);\n                result.push(y_c_batch_middle);\n            }\n        }\n\n        Ok(result)\n    }\n}\n\nimpl Expansion for CommonRec {\n    fn name(&self) -> StaticName {\n        self.body.name().into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"batch_first: {:?}\", self.batch_first)])\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Rounding\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(self.optional_y_output.is_some() as usize\n            + self.optional_y_h_output.is_some() as usize\n            + self.optional_y_c_output.is_some() as usize)\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> TractResult<()> {\n        let input_count = 3\n            + self.optional_bias_input.is_some() as usize\n            + self.optional_sequence_lens_input.is_some() as usize\n            + self.optional_initial_h_input.is_some() as usize\n            + self.optional_initial_c_input.is_some() as usize\n            + self.optional_p_input.is_some() as usize;\n        check_input_arity(inputs, input_count)?;\n        let output_count = self.optional_y_output.is_some() as usize\n            + self.optional_y_h_output.is_some() as usize\n            + self.optional_y_c_output.is_some() as usize;\n        check_output_arity(outputs, output_count)?;\n        s.equals(&inputs[0].datum_type, &inputs[1].datum_type)?;\n        s.equals(&inputs[0].datum_type, &inputs[2].datum_type)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, 3)?;\n        s.equals(&inputs[1].rank, 3)?;\n        s.equals(&inputs[2].rank, 3)?;\n\n        /* If 0\n         *      X.shape = [seq_length, batch_size, input_size],\n         *      Y.shape = [seq_length, num_directions, batch_size, hidden_size],\n         *      initial_h.shape = Y_h.shape = [num_directions, batch_size, hidden_size].\n         *  If 1,\n         *      X.shape = [batch_size, seq_length, input_size],\n         *      Y.shape = [batch_size, seq_length, num_directions, hidden_size],\n         *      initial_h.shape = Y_h.shape = [batch_size, num_directions, hidden_size].\n         */\n\n        let b = if self.batch_first { 0 } else { 1 };\n        let b_in_y = if self.batch_first { 0 } else { 2 };\n        let seq_len = if self.batch_first { 1 } else { 0 };\n        let dirs = if self.batch_first { 1 } else { 0 };\n        let dirs_in_y = if self.batch_first { 2 } else { 1 };\n\n        let (w_mul, b_mul) = self.body.w_b_multipliers();\n\n        s.equals(&inputs[1].shape[0], &inputs[2].shape[0])?; // num_directions\n        s.equals(&inputs[1].shape[1], (w_mul as i64) * inputs[2].shape[2].bex())?; // hidden_size\n        s.equals(&inputs[2].shape[1], (w_mul as i64) * inputs[2].shape[2].bex())?; // hidden_size\n        if let Some(bias) = self.optional_bias_input {\n            s.equals(&inputs[bias].datum_type, &inputs[0].datum_type)?;\n            s.equals(&inputs[bias].rank, 2)?;\n            s.equals(&inputs[bias].shape[0], &inputs[2].shape[0])?; // num_directions\n            s.equals(&inputs[bias].shape[1], (b_mul as i64) * inputs[2].shape[2].bex())?;\n            // 6 * hidden_size\n        }\n        if let Some(seq_len) = self.optional_sequence_lens_input {\n            s.equals(&inputs[seq_len].rank, 1)?;\n            s.equals(&inputs[seq_len].shape[0], &inputs[0].shape[b])?; // batch_size\n        }\n        if let Some(initial_h) = self.optional_initial_h_input {\n            s.equals(&inputs[initial_h].datum_type, &inputs[0].datum_type)?;\n            s.equals(&inputs[initial_h].rank, 3)?;\n            s.equals(&inputs[initial_h].shape[dirs], &inputs[1].shape[0])?; // num_directions\n            s.equals(&inputs[initial_h].shape[b], &inputs[0].shape[b])?; // batch_size\n            s.equals(&inputs[initial_h].shape[2], &inputs[2].shape[2])?; // hidden_size\n        }\n        if let Some(initial_c) = self.optional_initial_c_input {\n            s.equals(&inputs[initial_c].datum_type, &inputs[0].datum_type)?;\n            s.equals(&inputs[initial_c].rank, 3)?;\n            s.equals(&inputs[initial_c].shape[dirs], &inputs[1].shape[0])?; // num_directions\n            s.equals(&inputs[initial_c].shape[b], &inputs[0].shape[b])?; // batch_size\n            s.equals(&inputs[initial_c].shape[2], &inputs[2].shape[2])?; // hidden_size\n        }\n        if let Some(p) = self.optional_p_input {\n            s.equals(&inputs[p].datum_type, &inputs[0].datum_type)?;\n            s.equals(&inputs[p].rank, 2)?;\n            s.equals(&inputs[p].shape[0], &inputs[1].shape[0])?; // num_directions\n            s.equals(&inputs[p].shape[1], 3 * inputs[2].shape[2].bex())?; // hidden_size\n        }\n        if let Some(y) = self.optional_y_output {\n            s.equals(&outputs[y].datum_type, &inputs[0].datum_type)?;\n            s.equals(&outputs[y].rank, 4)?;\n            s.equals(&outputs[y].shape[seq_len], &inputs[0].shape[seq_len])?; // seq_lenght\n            s.equals(&outputs[y].shape[dirs_in_y], &inputs[1].shape[0])?; // num_directions\n            s.equals(&outputs[y].shape[b_in_y], &inputs[0].shape[b])?; // batch_size\n            s.equals(&outputs[y].shape[3], &inputs[2].shape[2])?; // hidden_size\n        }\n        if let Some(y_h) = self.optional_y_h_output {\n            s.equals(&outputs[y_h].datum_type, &inputs[0].datum_type)?;\n            s.equals(&outputs[y_h].rank, 3)?;\n            s.equals(&outputs[y_h].shape[dirs], &inputs[1].shape[0])?; // num_directions\n            s.equals(&outputs[y_h].shape[b], &inputs[0].shape[b])?; // batch_size\n            s.equals(&outputs[y_h].shape[2], &inputs[2].shape[2])?; // hidden_size\n        }\n        if let Some(y_c) = self.optional_y_c_output {\n            s.equals(&outputs[y_c].datum_type, &inputs[0].datum_type)?;\n            s.equals(&outputs[y_c].rank, 3)?;\n            s.equals(&outputs[y_c].shape[dirs], &inputs[1].shape[0])?; // num_directions\n            s.equals(&outputs[y_c].shape[b], &inputs[0].shape[b])?; // batch_size\n            s.equals(&outputs[y_c].shape[2], &inputs[2].shape[2])?; // hidden_size\n        }\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        use tract_hir::tract_core::ops::array::TypedConcat;\n        let fore = self.wire_one_side(prefix, target, inputs, 0)?;\n        let w_fact = target.outlet_fact(inputs[1])?;\n        if w_fact.shape[0] == 2.into() {\n            let back = self.wire_one_side(&format!(\"{prefix}.back\"), target, inputs, 1)?;\n            let mut outputs = tvec!(0.into(); self.nboutputs()?);\n            if let Some(ix) = self.optional_y_output {\n                outputs[ix] = target.wire_node(\n                    format!(\"{prefix}.merge_y_output\"),\n                    TypedConcat::new(1),\n                    &[fore[ix], back[ix]],\n                )?[0];\n            }\n            if let Some(ix) = self.optional_y_h_output {\n                outputs[ix] = target.wire_node(\n                    format!(\"{prefix}.merge_y_h_output\"),\n                    TypedConcat::new(0),\n                    &[fore[ix], back[ix]],\n                )?[0];\n            }\n            if let Some(ix) = self.optional_y_c_output {\n                outputs[ix] = target.wire_node(\n                    format!(\"{prefix}.merge_y_c_output\"),\n                    TypedConcat::new(0),\n                    &[fore[ix], back[ix]],\n                )?[0];\n            }\n            Ok(outputs)\n        } else {\n            Ok(fore)\n        }\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/rec/gru.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_hir::ops;\nuse tract_hir::tract_core::ops::einsum::EinSum;\n\nuse super::common::CommonRec;\nuse super::common::WireBody;\n\npub fn gru(\n    _ctx: &ParsingContext,\n    pb: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let gru = GRU {\n        f: Box::new(ops::nn::sigmoid()),\n        g: Box::new(ops::math::tanh()),\n        linear_before_reset: pb.get_attr(\"linear_before_reset\").unwrap_or(false),\n    };\n    let common = CommonRec::from_node_and_options(pb, 3, 0, Box::new(gru))?;\n\n    Ok((expand(common), vec![]))\n}\n\n#[derive(Debug, Clone)]\npub struct GRU {\n    pub f: Box<dyn TypedOp>,\n    pub g: Box<dyn TypedOp>,\n    pub linear_before_reset: bool,\n}\n\nimpl WireBody for GRU {\n    fn name(&self) -> &'static str {\n        \"GRU\"\n    }\n\n    fn w_b_multipliers(&self) -> (usize, usize) {\n        (3, 6)\n    }\n\n    fn have_extra_c_state(&self) -> bool {\n        false\n    }\n\n    #[allow(non_snake_case)]\n    fn wire_body(&self, prefix: &str, body: &mut TypedModel) -> TractResult<()> {\n        use tract_hir::ops::{array, math};\n        macro_rules! wire {\n            ($name: ident = $op: expr, $($param: expr),*) => {\n                let $name = body.wire_node(\n                    format!(\"{}.{}\", prefix, stringify!($name)),\n                    $op, [$($param),*].as_ref())?[0];\n            }\n        }\n\n        let Xt: OutletId = body.node_by_name(\"Xt\").unwrap().id.into();\n        let W: OutletId = body.node_by_name(\"W\").unwrap().id.into();\n        let R: OutletId = body.node_by_name(\"R\").unwrap().id.into();\n        let Ht_1: OutletId = body.node_by_name(\"Ht_1\").unwrap().id.into();\n        let b: Option<OutletId> = body.node_by_name(\"b\").ok().map(|n| n.id.into());\n\n        let h_size = body.outlet_fact(R)?.shape[1].clone();\n\n        wire!(Rz = array::Slice::new(0, 0.to_dim() * &h_size, 1.to_dim() * &h_size), R);\n        wire!(Rr = array::Slice::new(0, 1.to_dim() * &h_size, 2.to_dim() * &h_size), R);\n        wire!(Rh = array::Slice::new(0, 2.to_dim() * &h_size, 3.to_dim() * &h_size), R);\n\n        wire!(Wz = array::Slice::new(0, 0.to_dim() * &h_size, 1.to_dim() * &h_size), W);\n        wire!(Wr = array::Slice::new(0, 1.to_dim() * &h_size, 2.to_dim() * &h_size), W);\n        wire!(Wh = array::Slice::new(0, 2.to_dim() * &h_size, 3.to_dim() * &h_size), W);\n\n        let dt = body.outlet_fact(Xt)?.datum_type;\n        let matmul_t = EinSum::new(\"mk,nk->mn\".parse()?, dt);\n\n        // zt = f(Xt*(Wz^T) + Ht-1*(Rz^T) + Wbz + Rbz)\n        wire!(Xt_WzT = matmul_t.clone(), Xt, Wz);\n        wire!(Ht_1_RzT = matmul_t.clone(), Ht_1, Rz);\n        wire!(zt0 = math::add(), Xt_WzT, Ht_1_RzT);\n        let mut zt0 = zt0;\n        if let Some(b) = b {\n            wire!(Wbz = array::Slice::new(1, 0.to_dim() * &h_size, 1.to_dim() * &h_size), b);\n            wire!(Rbz = array::Slice::new(1, 3.to_dim() * &h_size, 4.to_dim() * &h_size), b);\n            wire!(Wbz_Rbz = math::add(), Wbz, Rbz);\n            wire!(zt0_biased = math::add(), zt0, Wbz_Rbz);\n            zt0 = zt0_biased\n        };\n        wire!(zt = self.f.clone(), zt0);\n\n        // rt = f(Xt*(Wr^T) + Ht-1*(Rr^T) + Wbr + Rbr)\n        wire!(Xt_WrT = matmul_t.clone(), Xt, Wr);\n        wire!(Ht_1_RrT = matmul_t.clone(), Ht_1, Rr);\n        wire!(rt0 = math::add(), Xt_WrT, Ht_1_RrT);\n        let mut rt0 = rt0;\n        if let Some(b) = b {\n            wire!(Wbr = array::Slice::new(1, 1.to_dim() * &h_size, 2.to_dim() * &h_size), b);\n            wire!(Rbr = array::Slice::new(1, 4.to_dim() * &h_size, 5.to_dim() * &h_size), b);\n            wire!(Wbr_Rbr = math::add(), Wbr, Rbr);\n            wire!(rt0_biased = math::add(), rt0, Wbr_Rbr);\n            rt0 = rt0_biased\n        };\n        wire!(rt = self.f.clone(), rt0);\n\n        // ht = g(Xt*(Wh^T) + (rt (.) Ht-1)*(Rh^T) + Rbh + Wbh) # default, when linear_before_reset = 0\n        // ht = g(Xt*(Wh^T) + (rt (.) (Ht-1*(Rh^T) + Rbh)) + Wbh) # when linear_before_reset != 0\n        wire!(Xt_WhT = matmul_t.clone(), Xt, Wh);\n        let rt_Ht_1_RhT_Rbh = if self.linear_before_reset {\n            // rt (.) (Ht-1*(Rh^T) + Rbh)\n            wire!(Ht_1_RhT = matmul_t, Ht_1, Rh);\n            let Ht_1_RhT_Rbh = if let Some(b) = b {\n                wire!(Rbh = array::Slice::new(1, 5.to_dim() * &h_size, 6.to_dim() * &h_size), b);\n                wire!(Ht_1_RhT_Rbh = math::add(), Ht_1_RhT, Rbh);\n                Ht_1_RhT_Rbh\n            } else {\n                Ht_1_RhT\n            };\n            wire!(rt_Ht_1_RhT_Rbh = math::mul(), rt, Ht_1_RhT_Rbh);\n            rt_Ht_1_RhT_Rbh\n        } else {\n            // (rt (.) Ht-1)*(Rh^T) + Rbh\n            wire!(rt_Ht_1 = math::mul(), rt, Ht_1);\n            wire!(rt_Ht_1_RhT = matmul_t, rt_Ht_1, Rh);\n            if let Some(b) = b {\n                wire!(Rbh = array::Slice::new(1, 5.to_dim() * &h_size, 6.to_dim() * &h_size), b);\n                wire!(rt_Ht_1_RhT_Rbh = math::add(), rt_Ht_1_RhT, Rbh);\n                rt_Ht_1_RhT_Rbh\n            } else {\n                rt_Ht_1_RhT\n            }\n        };\n        wire!(ht0 = math::add(), Xt_WhT, rt_Ht_1_RhT_Rbh);\n        let mut ht0 = ht0;\n        if let Some(b) = b {\n            wire!(Wbh = array::Slice::new(1, 2.to_dim() * &h_size, 3.to_dim() * &h_size), b);\n            wire!(ht0_biased = math::add(), ht0, Wbh);\n            ht0 = ht0_biased\n        }\n        wire!(ht = self.g.clone(), ht0);\n\n        // Ht = (1 - zt) (.) ht + zt (.) Ht-1\n        let one: OutletId =\n            body.add_const(\"one\", tensor2(&[[1f32]]).cast_to_dt(dt)?.into_owned())?;\n        wire!(one_sub_zt = math::sub(), one, zt);\n        wire!(one_sub_zt_ht = math::mul(), one_sub_zt, ht);\n        wire!(zt_Ht_1 = math::mul(), zt, Ht_1);\n        wire!(Ht = math::add(), one_sub_zt_ht, zt_Ht_1);\n\n        wire!(y_h = AxisOp::Add(1), Ht);\n        body.select_output_outlets(&[y_h])?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/rec/lstm.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_hir::ops;\nuse tract_hir::tract_core::ops::einsum::EinSum;\n\nuse super::common::CommonRec;\nuse super::common::WireBody;\n\npub fn lstm(\n    _ctx: &ParsingContext,\n    pb: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let lstm = LSTM {\n        f: Box::new(ops::nn::sigmoid()),\n        g: Box::new(ops::math::tanh()),\n        h: Box::new(ops::math::tanh()),\n    };\n    let common = CommonRec::from_node_and_options(pb, 3, 0, Box::new(lstm))?;\n    Ok((expand(common), vec![]))\n}\n\n#[derive(Debug, Clone)]\npub struct LSTM {\n    pub f: Box<dyn TypedOp>,\n    pub g: Box<dyn TypedOp>,\n    pub h: Box<dyn TypedOp>,\n}\n\nimpl WireBody for LSTM {\n    fn name(&self) -> &'static str {\n        \"LSTM\"\n    }\n\n    fn w_b_multipliers(&self) -> (usize, usize) {\n        (4, 8)\n    }\n\n    fn have_extra_c_state(&self) -> bool {\n        true\n    }\n\n    #[allow(non_snake_case)]\n    fn wire_body(&self, prefix: &str, body: &mut TypedModel) -> TractResult<()> {\n        use tract_hir::ops::{array, math};\n        macro_rules! wire {\n            ($name: ident = $op: expr, $($param: expr),*) => {\n                let $name = body.wire_node(\n                    format!(\"{}.{}\", prefix, stringify!($name)),\n                    $op, [$($param),*].as_ref())?[0];\n            }\n        }\n\n        let Xt: OutletId = body.node_by_name(\"Xt\").unwrap().id.into();\n        let W: OutletId = body.node_by_name(\"W\").unwrap().id.into();\n        let R: OutletId = body.node_by_name(\"R\").unwrap().id.into();\n        let Ht_1: OutletId = body.node_by_name(\"Ht_1\").unwrap().id.into();\n        let Ct_1: OutletId = body.node_by_name(\"Ct_1\").unwrap().id.into();\n        let b: Option<OutletId> = body.node_by_name(\"b\").ok().map(|n| n.id.into());\n        let peepholes: Option<OutletId> = body.node_by_name(\"peepholes\").ok().map(|n| n.id.into());\n\n        let h_size = body.outlet_fact(R)?.shape[1].clone();\n        let dt = body.outlet_fact(R)?.datum_type;\n\n        wire!(Wi = array::Slice::new(0, 0.to_dim() * &h_size, 1.to_dim() * &h_size), W);\n        wire!(Wo = array::Slice::new(0, 1.to_dim() * &h_size, 2.to_dim() * &h_size), W);\n        wire!(Wf = array::Slice::new(0, 2.to_dim() * &h_size, 3.to_dim() * &h_size), W);\n        wire!(Wc = array::Slice::new(0, 3.to_dim() * &h_size, 4.to_dim() * &h_size), W);\n\n        wire!(Ri = array::Slice::new(0, 0.to_dim() * &h_size, 1.to_dim() * &h_size), R);\n        wire!(Ro = array::Slice::new(0, 1.to_dim() * &h_size, 2.to_dim() * &h_size), R);\n        wire!(Rf = array::Slice::new(0, 2.to_dim() * &h_size, 3.to_dim() * &h_size), R);\n        wire!(Rc = array::Slice::new(0, 3.to_dim() * &h_size, 4.to_dim() * &h_size), R);\n\n        let biases = if let Some(b) = b {\n            wire!(Wbi = array::Slice::new(1, 0.to_dim() * &h_size, 1.to_dim() * &h_size), b);\n            wire!(Wbo = array::Slice::new(1, 1.to_dim() * &h_size, 2.to_dim() * &h_size), b);\n            wire!(Wbf = array::Slice::new(1, 2.to_dim() * &h_size, 3.to_dim() * &h_size), b);\n            wire!(Wbc = array::Slice::new(1, 3.to_dim() * &h_size, 4.to_dim() * &h_size), b);\n\n            wire!(Rbi = array::Slice::new(1, 4.to_dim() * &h_size, 5.to_dim() * &h_size), b);\n            wire!(Rbo = array::Slice::new(1, 5.to_dim() * &h_size, 6.to_dim() * &h_size), b);\n            wire!(Rbf = array::Slice::new(1, 6.to_dim() * &h_size, 7.to_dim() * &h_size), b);\n            wire!(Rbc = array::Slice::new(1, 7.to_dim() * &h_size, 8.to_dim() * &h_size), b);\n\n            wire!(bi = math::add(), Wbi, Rbi);\n            wire!(bo = math::add(), Wbo, Rbo);\n            wire!(bf = math::add(), Wbf, Rbf);\n            wire!(bc = math::add(), Wbc, Rbc);\n\n            Some((bi, bo, bf, bc))\n        } else {\n            None\n        };\n\n        let peepholes = if let Some(p) = peepholes {\n            wire!(pi = array::Slice::new(1, 0.to_dim() * &h_size, 1.to_dim() * &h_size), p);\n            wire!(po = array::Slice::new(1, 1.to_dim() * &h_size, 2.to_dim() * &h_size), p);\n            wire!(pf = array::Slice::new(1, 2.to_dim() * &h_size, 3.to_dim() * &h_size), p);\n            Some((pi, po, pf))\n        } else {\n            None\n        };\n\n        let matmul_t = EinSum::new(\"mk,nk->mn\".parse()?, dt);\n\n        // it = f(Xt*(Wi^T) + Ht-1*(Ri^T) + Pi (.) Ct-1 + Wbi + Rbi)\n        wire!(Xt_WiT = matmul_t.clone(), Xt, Wi);\n        wire!(Ht_1_RiT = matmul_t.clone(), Ht_1, Ri);\n        wire!(it0 = math::add(), Xt_WiT, Ht_1_RiT);\n        let mut it0 = it0;\n        if let Some(biases) = biases {\n            wire!(it_bias = math::add(), it0, biases.0);\n            it0 = it_bias;\n        };\n        if let Some(peephole) = peepholes {\n            wire!(Pi_Ct_1 = math::mul(), peephole.0, Ct_1);\n            wire!(it_peep = math::add(), Pi_Ct_1, it0);\n            it0 = it_peep;\n        }\n        wire!(it = self.f.clone(), it0);\n\n        // ft = f(Xt*(Wf^T) + Ht-1*(Rf^T) + Pf (.) Ct-1 + Wbf + Rbf)\n        wire!(Xt_WfT = matmul_t.clone(), Xt, Wf);\n        wire!(Ht_1_RfT = matmul_t.clone(), Ht_1, Rf);\n        wire!(ft0 = math::add(), Xt_WfT, Ht_1_RfT);\n        let mut ft0 = ft0;\n        if let Some(biases) = biases {\n            wire!(ft_bias = math::add(), ft0, biases.2);\n            ft0 = ft_bias;\n        };\n        if let Some(peephole) = peepholes {\n            wire!(Pf_Ct_1 = math::mul(), peephole.2, Ct_1);\n            wire!(ft_peep = math::add(), Pf_Ct_1, ft0);\n            ft0 = ft_peep;\n        }\n        wire!(ft = self.f.clone(), ft0);\n\n        // ct = g(Xt*(Wc^T) + Ht-1*(Rc^T) + Wbc + Rbc)\n        wire!(Xt_WcT = matmul_t.clone(), Xt, Wc);\n        wire!(Ht_1_RcT = matmul_t.clone(), Ht_1, Rc);\n        wire!(ct0 = math::add(), Xt_WcT, Ht_1_RcT);\n        let mut ct0 = ct0;\n        if let Some(biases) = biases {\n            wire!(ct_bias = math::add(), ct0, biases.3);\n            ct0 = ct_bias\n        };\n        wire!(ct = self.g.clone(), ct0);\n\n        // Ct = ft (.) Ct-1 + it (.) ct\n        wire!(ft_Ct_1 = math::mul(), ft, Ct_1);\n        wire!(it_ct = math::mul(), it, ct);\n        wire!(Ct = math::add(), ft_Ct_1, it_ct);\n\n        // ot = f(Xt*(Wo^T) + Ht-1*(Ro^T) + Po (.) Ct + Wbo + Rbo)\n        wire!(Xt_WoT = matmul_t.clone(), Xt, Wo);\n        wire!(Ht_1_RoT = matmul_t, Ht_1, Ro);\n        wire!(ot0 = math::add(), Xt_WoT, Ht_1_RoT);\n        let mut ot0 = ot0;\n        if let Some(biases) = biases {\n            wire!(ot_bias = math::add(), ot0, biases.1);\n            ot0 = ot_bias\n        };\n        if let Some(peephole) = peepholes {\n            wire!(Po_Ct = math::mul(), peephole.1, Ct);\n            wire!(ot_peep = math::add(), Po_Ct, ot0);\n            ot0 = ot_peep;\n        }\n        wire!(ot = self.f.clone(), ot0);\n\n        // Ht = ot (.) h(Ct)\n        wire!(h_Ct = self.h.clone(), Ct);\n        wire!(Ht = math::mul(), ot, h_Ct);\n\n        // onnx inner interface: [batch_size, input_size]\n        // add sequence axis (chunk == 1)\n        wire!(Ht_fixed = AxisOp::Add(1), Ht);\n        wire!(Ct_fixed = AxisOp::Add(1), Ct);\n        body.select_output_outlets(&[Ht_fixed, Ct_fixed])?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/rec/rnn.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_hir::ops;\nuse tract_hir::tract_core::ops::einsum::EinSum;\n\nuse super::common::CommonRec;\nuse super::common::WireBody;\n\npub fn rnn(\n    _ctx: &ParsingContext,\n    pb: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let rnn = RNN { fore: Box::new(ops::math::tanh()), back: Box::new(ops::math::tanh()) };\n    let common = CommonRec::from_node_and_options(pb, 3, 0, Box::new(rnn))?;\n    Ok((expand(common), vec![]))\n}\n\n#[derive(Debug, Clone, new)]\npub struct RNN {\n    pub fore: Box<dyn TypedOp>,\n    pub back: Box<dyn TypedOp>,\n}\n\nimpl WireBody for RNN {\n    fn name(&self) -> &'static str {\n        \"RNN\"\n    }\n\n    fn w_b_multipliers(&self) -> (usize, usize) {\n        (1, 2)\n    }\n\n    fn have_extra_c_state(&self) -> bool {\n        false\n    }\n\n    #[allow(non_snake_case)]\n    fn wire_body(&self, prefix: &str, body: &mut TypedModel) -> TractResult<()> {\n        use tract_hir::ops::{array, math};\n        macro_rules! wire {\n            ($name: ident = $op: expr, $($param: expr),*) => {\n                let $name = body.wire_node(\n                    format!(\"{}.{}\", prefix, stringify!($name)),\n                    $op, [$($param),*].as_ref())?[0];\n            }\n        }\n\n        let Xt: OutletId = body.node_by_name(\"Xt\").unwrap().id.into();\n        let W: OutletId = body.node_by_name(\"W\").unwrap().id.into();\n        let R: OutletId = body.node_by_name(\"R\").unwrap().id.into();\n        let Ht_1: OutletId = body.node_by_name(\"Ht_1\").unwrap().id.into();\n        let b: Option<OutletId> = body.node_by_name(\"b\").ok().map(|n| n.id.into());\n\n        let h_size = body.outlet_fact(R)?.shape[1].clone();\n        let dt = body.outlet_fact(R)?.datum_type;\n\n        let bias = if let Some(b) = b {\n            wire!(Wbi = array::Slice::new(1, 0.to_dim() * &h_size, 1.to_dim() * &h_size), b);\n            wire!(Rbi = array::Slice::new(1, 1.to_dim() * &h_size, 2.to_dim() * &h_size), b);\n            wire!(bi = math::add(), Wbi, Rbi);\n            Some(bi)\n        } else {\n            None\n        };\n\n        let matmul_t = EinSum::new(\"mk,nk->mn\".parse()?, dt);\n\n        // Ht = f(Xt*(Wi^T) + Ht-1*(Ri^T) + Wbi + Rbi)\n        wire!(Xt_WiT = matmul_t.clone(), Xt, W);\n        wire!(Ht_1_RiT = matmul_t, Ht_1, R);\n\n        wire!(ht0 = math::add(), Xt_WiT, Ht_1_RiT);\n        let mut ht0 = ht0;\n        if let Some(bias) = bias {\n            wire!(ht_bias = math::add(), ht0, bias);\n            ht0 = ht_bias;\n        }\n        wire!(Ht = self.fore.clone(), ht0);\n\n        wire!(y_h = AxisOp::Add(1), Ht);\n        body.select_output_outlets(&[y_h])?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "onnx/src/ops/rec/scan.rs",
    "content": "use crate::model::{ParseResult, ParsingContext};\nuse crate::pb::*;\nuse crate::tract_core::ops::scan::ScanInfo;\nuse tract_hir::internal::*;\n\nuse tract_hir::ops;\n\npub fn scan(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let num_scan_inputs = node.get_attr(\"num_scan_inputs\")?;\n    let graph: &GraphProto = node.get_attr(\"body\")?;\n    let ParseResult { mut model, unresolved_inputs, .. } = ctx.parse_graph(graph)?;\n    let scan_input_axes =\n        node.get_attr_opt_vec(\"scan_input_axes\")?.unwrap_or_else(|| vec![0; num_scan_inputs]);\n    let closure_inputs = unresolved_inputs.len();\n    let num_hidden_state = model.input_outlets()?.len() - closure_inputs - num_scan_inputs;\n    let num_scan_outputs = model.output_outlets()?.len() - num_hidden_state;\n    let scan_output_axes =\n        node.get_attr_opt_vec(\"scan_output_axes\")?.unwrap_or_else(|| vec![0; num_scan_outputs]);\n\n    let mut mapped_inputs = vec![];\n    let mut mapped_outputs = vec![];\n    for ix in 0..num_hidden_state {\n        mapped_inputs.push(ops::scan::InputMapping::State);\n        mapped_outputs.push(ops::scan::OutputMapping {\n            state: true,\n            last_value_slot: Some(ix),\n            scan: None,\n            full_dim_hint: None,\n        });\n    }\n\n    for (ix, ax) in scan_input_axes.iter().enumerate() {\n        let op = expand(ops::array::RmDims::new(vec![*ax]));\n        let outlet = model.input_outlets()?[num_hidden_state + ix];\n        InferenceModelPatch::intercept(\n            &model,\n            outlet,\n            format!(\"{}.input-{}.adjust-dim\", node.name, ix),\n            op,\n            model.outlet_fact(outlet)?.clone(),\n        )?\n        .apply(&mut model)?;\n        model.set_outlet_fact(outlet, InferenceFact::default())?;\n        mapped_inputs\n            .push(ops::scan::InputMapping::Scan(ScanInfo { axis: *ax as usize, chunk: 1 }));\n    }\n\n    for _input in unresolved_inputs.iter() {\n        mapped_inputs.push(ops::scan::InputMapping::Full);\n    }\n\n    for (ix, ax) in scan_output_axes.iter().enumerate() {\n        let op = ops::array::AddDims::new(vec![*ax]);\n        let outlet = model.output_outlets()?[num_hidden_state + ix];\n        InferenceModelPatch::intercept(\n            &model,\n            outlet,\n            format!(\"{}.output-{}-adjust-dim\", node.name, ix),\n            expand(op),\n            InferenceFact::default(),\n        )?\n        .apply(&mut model)?;\n        mapped_outputs.push(ops::scan::OutputMapping {\n            state: false,\n            scan: Some((ix + num_hidden_state, ScanInfo { axis: *ax as usize, chunk: 1 })),\n            full_dim_hint: None,\n            last_value_slot: None,\n        });\n    }\n\n    Ok((\n        Box::new(ops::scan::InferenceScan::new(\n            model,\n            mapped_inputs,\n            mapped_outputs,\n            true,\n            GenericFactoid::default(),\n        )),\n        unresolved_inputs,\n    ))\n}\n"
  },
  {
    "path": "onnx/src/ops/rec.rs",
    "content": "use crate::model::OnnxOpRegister;\n\npub mod common;\npub mod gru;\npub mod lstm;\npub mod rnn;\npub mod scan;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"GRU\", gru::gru);\n    reg.insert(\"LSTM\", lstm::lstm);\n    reg.insert(\"RNN\", rnn::rnn);\n    reg.insert(\"Scan\", scan::scan);\n}\n"
  },
  {
    "path": "onnx/src/ops/resize.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::pb::*;\nuse tract_hir::internal::*;\nuse tract_nnef::tract_num_traits::Zero as _;\nuse tract_onnx_opl::resize::{CoordTransformer, Interpolator, Nearest, Resize};\n\npub fn resize(\n    ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let op = match ctx.onnx_operator_set_version {\n        10 => resize_10(node)?,\n        11..=12 => resize_11(node)?,\n        13..=17 => resize_13(node)?,\n        18.. => resize_18(node)?,\n        v => bail!(\"Unsupported operator set for Resize operator ({v})\"),\n    };\n    Ok((expand(ResizeInference(op)), vec![]))\n}\n\nfn resize_10(node: &NodeProto) -> TractResult<Resize> {\n    Ok(Resize {\n        axes: None,\n        optional_roi_input: None,\n        optional_scales_input: Some(1),\n        optional_sizes_input: None,\n        coord_transformer: coord_transformer_from_node(node)?,\n        interpolator: interpolator_from_node(node)?,\n        nearest: nearest_from_node(node)?,\n    })\n}\n\nfn resize_11(node: &NodeProto) -> TractResult<Resize> {\n    let mut options = crate::model::optional_inputs(node).skip(3);\n    Ok(Resize {\n        axes: None,\n        optional_roi_input: Some(1),\n        optional_scales_input: Some(2),\n        optional_sizes_input: options.next().unwrap(),\n        coord_transformer: coord_transformer_from_node(node)?,\n        interpolator: interpolator_from_node(node)?,\n        nearest: nearest_from_node(node)?,\n    })\n}\n\nfn resize_13(node: &NodeProto) -> TractResult<Resize> {\n    let mut options = crate::model::optional_inputs(node).skip(1);\n    Ok(Resize {\n        axes: None,\n        optional_roi_input: options.next().unwrap(),\n        optional_scales_input: options.next().unwrap(),\n        optional_sizes_input: options.next().unwrap(),\n        coord_transformer: coord_transformer_from_node(node)?,\n        interpolator: interpolator_from_node(node)?,\n        nearest: nearest_from_node(node)?,\n    })\n}\n\nfn resize_18(node: &NodeProto) -> TractResult<Resize> {\n    let mut options = crate::model::optional_inputs(node).skip(1);\n    Ok(Resize {\n        axes: node.get_attr_opt_vec(\"axes\")?,\n        optional_roi_input: options.next().unwrap(),\n        optional_scales_input: options.next().unwrap(),\n        optional_sizes_input: options.next().unwrap(),\n        coord_transformer: coord_transformer_from_node(node)?,\n        interpolator: interpolator_from_node(node)?,\n        nearest: nearest_from_node(node)?,\n    })\n}\n\nfn coord_transformer_from_node(node: &NodeProto) -> TractResult<CoordTransformer> {\n    CoordTransformer::parse(\n        node.get_attr_opt(\"coordinate_transformation_mode\")?.unwrap_or(\"half_pixel\"),\n    )\n}\n\nfn interpolator_from_node(node: &NodeProto) -> TractResult<Interpolator> {\n    Interpolator::parse(node.get_attr_opt(\"mode\")?.unwrap_or(\"nearest\"))\n}\n\nfn nearest_from_node(node: &NodeProto) -> TractResult<Nearest> {\n    Nearest::parse(node.get_attr_opt(\"nearest_mode\")?.unwrap_or(\"round_prefer_floor\"))\n}\n\n#[derive(Clone, Debug)]\nstruct ResizeInference(Resize);\n\nimpl Expansion for ResizeInference {\n    fn name(&self) -> StaticName {\n        \"Resize\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        let op = &self.0;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        if let Some(scales) = op.optional_scales_input {\n            s.given(&inputs[scales].shape[0], move |s, len| {\n                if len.is_zero() {\n                    rules_with_sizes(op, s, inputs, outputs)\n                } else {\n                    rules_with_scales(op, s, inputs, outputs)\n                }\n            })\n        } else if op.optional_sizes_input.is_some() {\n            rules_with_sizes(op, s, inputs, outputs)\n        } else {\n            todo!()\n        }\n    }\n\n    fn wire(\n        &self,\n        name: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        model.wire_node(name, self.0.clone(), inputs)\n    }\n}\n\nfn rules_with_scales<'r, 'p: 'r, 's: 'r>(\n    op: &'s Resize,\n    s: &mut Solver<'r>,\n    inputs: &'p [TensorProxy],\n    outputs: &'p [TensorProxy],\n) -> InferenceResult {\n    let scales_input = op.optional_scales_input.unwrap();\n    let scales = &inputs[scales_input];\n    s.equals(&scales.datum_type, f32::datum_type())?;\n    s.equals(&scales.rank, 1)?;\n    s.equals(&scales.shape[0], inputs[0].rank.bex().to_dim())?;\n    s.given_2(&inputs[0].shape, &inputs[scales_input].value, move |s, input_shape, scales| {\n        let output_size = op.compute_output_shape(&input_shape, Some(scales.as_ref()), None)?;\n        let rank = input_shape.len();\n        for i in 0..rank {\n            s.equals(&outputs[0].shape[i], output_size[i].to_dim())?;\n        }\n        Ok(())\n    })\n}\n\nfn rules_with_sizes<'r, 'p: 'r, 's: 'r>(\n    op: &'s Resize,\n    s: &mut Solver<'r>,\n    inputs: &'p [TensorProxy],\n    outputs: &'p [TensorProxy],\n) -> InferenceResult {\n    let sizes = &inputs[op.optional_sizes_input.unwrap()];\n    s.equals(&sizes.rank, 1)?;\n    s.equals(&sizes.shape[0], inputs[0].rank.bex().to_dim())?;\n    s.given(&inputs[0].rank, move |s, rank| {\n        for i in 0..(rank as usize) {\n            s.equals(&outputs[0].shape[i], sizes.value[i].bex().to_dim())?;\n        }\n        Ok(())\n    })\n}\n"
  },
  {
    "path": "onnx/src/ops/s2d.rs",
    "content": "use crate::model::{OnnxOpRegister, ParsingContext};\nuse crate::pb::NodeProto;\nuse tract_hir::internal::*;\n\npub fn register_all_ops(reg: &mut OnnxOpRegister) {\n    reg.insert(\"SpaceToDepth\", space_to_depth);\n}\n\npub fn space_to_depth(\n    _ctx: &ParsingContext,\n    node: &NodeProto,\n) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {\n    let blocksize = node.get_attr_opt(\"blocksize\")?.unwrap_or(2);\n    Ok((expand(SpaceToDepth { blocksize }), vec![]))\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct SpaceToDepth {\n    blocksize: usize,\n}\n\nimpl SpaceToDepth {\n    pub fn compute_shape(&self, shape: &[TDim]) -> TVec<TDim> {\n        tvec!(\n            shape[0].clone(),\n            shape[1].clone() * self.blocksize * self.blocksize,\n            shape[2].clone() / self.blocksize,\n            shape[3].clone() / self.blocksize,\n        )\n    }\n\n    pub fn to_axis_ops(&self, shape: &[TDim]) -> TractResult<TVec<AxisOp>> {\n        let mut stack: TVec<AxisOp> = tvec!();\n\n        let ishape_from = tvec!(shape[2].clone(), shape[3].clone());\n        let ishape_to = tvec!(\n            shape[2].clone() / self.blocksize,\n            self.blocksize.into(),\n            shape[3].clone() / self.blocksize,\n            self.blocksize.into(),\n        );\n\n        let oshape_from = tvec!(self.blocksize.into(), self.blocksize.into(), shape[1].clone());\n        let oshape_to = tvec!(shape[1].clone() * self.blocksize * self.blocksize);\n\n        stack.push(AxisOp::Reshape(2, ishape_from, ishape_to));\n        stack.push(AxisOp::Move(3, 1));\n        stack.push(AxisOp::Move(5, 2));\n        stack.push(AxisOp::Reshape(1, oshape_from, oshape_to));\n\n        Ok(stack)\n    }\n}\n\nimpl Expansion for SpaceToDepth {\n    fn name(&self) -> StaticName {\n        \"SpaceToDepth\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].rank, 4)?;\n        s.equals(&outputs[0].rank, 4)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.given(&inputs[0].shape, move |s, ishape| {\n            let oshape = self.compute_shape(&ishape);\n            s.equals(&outputs[0].shape, ShapeFactoid::from(oshape))\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let ishape = model.outlet_fact(inputs[0])?.shape.to_tvec();\n        let iheight = ishape[2].to_usize()?;\n        let iwidth = ishape[3].to_usize()?;\n\n        if iheight % self.blocksize != 0 {\n            bail!(\"SpaceToDepth requires input height to be a multiple of blocksize\")\n        }\n        if iwidth % self.blocksize != 0 {\n            bail!(\"SpaceToDepth requires input width to be a multiple of blocksize\")\n        }\n\n        let mut wire = tvec!(inputs[0]);\n        for (ix, op) in self.to_axis_ops(&ishape)?.into_iter().enumerate() {\n            wire = model.wire_node(format!(\"{prefix}.{ix}\"), op, &wire)?;\n        }\n        Ok(wire)\n    }\n}\n"
  },
  {
    "path": "onnx/src/pb_helpers.rs",
    "content": "use crate::pb::*;\nuse attribute_proto::AttributeType;\nuse tract_hir::internal::*;\n\nuse tract_num_traits::{AsPrimitive, Bounded};\n\nuse std::fmt::{self, Debug, Display};\nuse std::str;\n\nuse std::convert::TryInto;\n\npub trait TryCollect<T, E>: Iterator<Item = Result<T, E>> + Sized {\n    fn try_collect<B: Default + Extend<T>>(self) -> Result<B, E> {\n        let mut out = B::default();\n        for item in self {\n            out.extend(Some(item?));\n        }\n        Ok(out)\n    }\n}\n\nimpl<T, E, I> TryCollect<T, E> for I where I: Iterator<Item = Result<T, E>> + Sized {}\n\npub trait Reason {\n    fn reason(&self) -> StaticName;\n}\n\nimpl Reason for &'static str {\n    fn reason(&self) -> StaticName {\n        (*self).into()\n    }\n}\n\nimpl<F> Reason for F\nwhere\n    F: Fn() -> String,\n{\n    fn reason(&self) -> StaticName {\n        self().into()\n    }\n}\n\npub trait OptionExt {\n    type Item;\n\n    fn and_try<F, T>(self, f: F) -> TractResult<Option<T>>\n    where\n        F: Fn(Self::Item) -> TractResult<T>;\n\n    fn and_ok<F, T>(self, f: F) -> TractResult<Option<T>>\n    where\n        F: Fn(Self::Item) -> T;\n}\n\nimpl<A> OptionExt for Option<A> {\n    type Item = A;\n\n    fn and_try<F, T>(self, f: F) -> TractResult<Option<T>>\n    where\n        F: Fn(Self::Item) -> TractResult<T>,\n    {\n        match self {\n            Some(attr) => f(attr).map(Some),\n            None => Ok(None),\n        }\n    }\n\n    fn and_ok<F, T>(self, f: F) -> TractResult<Option<T>>\n    where\n        F: Fn(Self::Item) -> T,\n    {\n        Ok(self.map(f))\n    }\n}\n\nimpl Display for attribute_proto::AttributeType {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        f.write_str(match self {\n            AttributeType::Int => \"int\",\n            AttributeType::Float => \"float\",\n            AttributeType::Tensor => \"tensor\",\n            AttributeType::String => \"string\",\n            AttributeType::Ints => \"list of ints\",\n            AttributeType::Floats => \"list of floats\",\n            AttributeType::Tensors => \"list of tensors\",\n            AttributeType::Strings => \"list of strings\",\n            AttributeType::Graph => \"graph\",\n            AttributeType::Graphs => \"graphs\",\n            _ => \"<undefined>\",\n        })\n    }\n}\n\npub trait AttrScalarType<'a>: 'a + Sized {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>>;\n}\n\nimpl<'a> AttrScalarType<'a> for DatumType {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        i32::get_attr_opt_scalar(node, name)?\n            .map(|d| tensor_proto::DataType::try_from(d).unwrap().try_into())\n            .transpose()\n    }\n}\n\nimpl<'a> AttrScalarType<'a> for &'a TensorProto {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        Ok(node\n            .get_attr_opt_with_type(name, AttributeType::Tensor)?\n            .map(|attr| attr.t.as_ref().unwrap()))\n    }\n}\n\nimpl<'a> AttrScalarType<'a> for &'a [u8] {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        Ok(node.get_attr_opt_with_type(name, AttributeType::String)?.map(|attr| &*attr.s))\n    }\n}\n\nimpl<'a> AttrScalarType<'a> for &'a str {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        let bytes: Option<&[u8]> = AttrScalarType::get_attr_opt_scalar(node, name)?;\n        bytes.and_try(|b| str::from_utf8(b).map_err(Into::into))\n    }\n}\n\nimpl<'a> AttrScalarType<'a> for String {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        let string: Option<&'a str> = AttrScalarType::get_attr_opt_scalar(node, name)?;\n        string.and_ok(Into::into)\n    }\n}\n\nimpl<'a> AttrScalarType<'a> for i64 {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        node.get_attr_opt_with_type(name, AttributeType::Int)?.and_ok(|a| a.i)\n    }\n}\n\nimpl<'a> AttrScalarType<'a> for bool {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        let int: Option<i64> = AttrScalarType::get_attr_opt_scalar(node, name)?;\n        int.and_try(|int| {\n            node.expect_attr(name, int == 0 || int == 1, \"boolean (0 or 1)\")?;\n            Ok(int == 1)\n        })\n    }\n}\n\nimpl<'a> AttrScalarType<'a> for usize {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        let int: Option<i64> = AttrScalarType::get_attr_opt_scalar(node, name)?;\n        int.and_try(|int| {\n            node.expect_attr(name, int >= 0, \"non-negative int\")?;\n            Ok(int as _)\n        })\n    }\n}\n\nimpl<'a> AttrScalarType<'a> for &'a GraphProto {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        node.get_attr_opt_with_type(name, AttributeType::Graph)?.and_ok(|a| a.g.as_ref().unwrap())\n    }\n}\n\nfn check_int<T>(node: &NodeProto, attr: &str, int: i64, is_list: bool) -> TractResult<T>\nwhere\n    T: AsPrimitive<i64> + Bounded + Display,\n    i64: AsPrimitive<T>,\n{\n    let desc = if is_list { \"list of ints\" } else { \"int\" };\n    node.expect_attr(attr, int <= T::max_value().as_(), || {\n        format!(\"{} <= {}, got {}\", desc, T::max_value(), int)\n    })?;\n    node.expect_attr(attr, int >= T::min_value().as_(), || {\n        format!(\"{} >= {}, got {}\", desc, T::min_value(), int)\n    })?;\n    Ok(int.as_())\n}\n\nmacro_rules! impl_attr_scalar_type_int {\n    ($ty:ident) => {\n        impl<'a> AttrScalarType<'a> for $ty {\n            fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n                AttrScalarType::get_attr_opt_scalar(node, name)?\n                    .and_try(|int| check_int(node, name, int, false))\n            }\n        }\n\n        impl<'a> AttrTVecType<'a> for $ty {\n            fn get_attr_opt_tvec(\n                node: &'a NodeProto,\n                name: &str,\n            ) -> TractResult<Option<TVec<Self>>> {\n                AttrTVecType::get_attr_opt_tvec(node, name)?.and_try(|ints| {\n                    ints.into_iter().map(|int| check_int(node, name, int, true)).try_collect()\n                })\n            }\n        }\n    };\n}\n\nimpl_attr_scalar_type_int!(i8);\nimpl_attr_scalar_type_int!(i16);\nimpl_attr_scalar_type_int!(i32);\nimpl_attr_scalar_type_int!(isize);\n\nimpl<'a> AttrScalarType<'a> for f32 {\n    fn get_attr_opt_scalar(node: &'a NodeProto, name: &str) -> TractResult<Option<Self>> {\n        node.get_attr_opt_with_type(name, AttributeType::Float)?.and_ok(|x| x.f)\n    }\n}\n\npub trait AttrSliceType<'a>: 'a + Sized {\n    fn get_attr_opt_slice(node: &'a NodeProto, name: &str) -> TractResult<Option<&'a [Self]>>;\n}\n\nimpl<'a> AttrSliceType<'a> for Vec<u8> {\n    fn get_attr_opt_slice(node: &'a NodeProto, name: &str) -> TractResult<Option<&'a [Self]>> {\n        node.get_attr_opt_with_type(name, AttributeType::Strings)?.and_ok(|x| &*x.strings)\n    }\n}\n\nimpl<'a> AttrSliceType<'a> for i64 {\n    fn get_attr_opt_slice(node: &'a NodeProto, name: &str) -> TractResult<Option<&'a [Self]>> {\n        node.get_attr_opt_with_type(name, AttributeType::Ints)?.and_ok(|a| &*a.ints)\n    }\n}\n\nimpl<'a> AttrSliceType<'a> for f32 {\n    fn get_attr_opt_slice(node: &'a NodeProto, name: &str) -> TractResult<Option<&'a [Self]>> {\n        node.get_attr_opt_with_type(name, AttributeType::Floats)?.and_ok(|a| &*a.floats)\n    }\n}\n\npub trait AttrTVecType<'a>: 'a + Sized {\n    fn get_attr_opt_tvec(node: &'a NodeProto, name: &str) -> TractResult<Option<TVec<Self>>>;\n}\n\nimpl<'a, T> AttrTVecType<'a> for T\nwhere\n    T: AttrSliceType<'a> + Clone,\n{\n    fn get_attr_opt_tvec(node: &'a NodeProto, name: &str) -> TractResult<Option<TVec<Self>>> {\n        T::get_attr_opt_slice(node, name)?.and_ok(Into::into)\n    }\n}\n\nimpl<'a> AttrTVecType<'a> for &'a str {\n    fn get_attr_opt_tvec(node: &'a NodeProto, name: &str) -> TractResult<Option<TVec<Self>>> {\n        <Vec<u8>>::get_attr_opt_slice(node, name)?\n            .and_try(|b| b.iter().map(|v| str::from_utf8(v)).try_collect().map_err(Into::into))\n    }\n}\n\nimpl<'a> AttrTVecType<'a> for String {\n    fn get_attr_opt_tvec(node: &'a NodeProto, name: &str) -> TractResult<Option<TVec<Self>>> {\n        <Vec<u8>>::get_attr_opt_slice(node, name)?.and_try(|b| {\n            b.iter().map(|v| str::from_utf8(v).map(Into::into)).try_collect().map_err(Into::into)\n        })\n    }\n}\n\nimpl<'a> AttrTVecType<'a> for bool {\n    fn get_attr_opt_tvec(node: &'a NodeProto, name: &str) -> TractResult<Option<TVec<Self>>> {\n        let ints: Option<&[i64]> = AttrSliceType::get_attr_opt_slice(node, name)?;\n        ints.and_try(|ints| {\n            for int in ints.iter() {\n                node.expect_attr(name, *int == 0 || *int == 1, \"list of booleans (0 or 1)\")?;\n            }\n            Ok(ints.iter().map(|&x| x == 1).collect())\n        })\n    }\n}\n\nimpl<'a> AttrTVecType<'a> for usize {\n    fn get_attr_opt_tvec(node: &'a NodeProto, name: &str) -> TractResult<Option<TVec<Self>>> {\n        let ints: Option<&[i64]> = AttrSliceType::get_attr_opt_slice(node, name)?;\n        ints.and_try(|ints| {\n            for int in ints.iter() {\n                node.expect_attr(name, *int >= 0, \"list of non-negative ints\")?;\n            }\n            Ok(ints.iter().map(|&x| x as _).collect())\n        })\n    }\n}\n\nimpl NodeProto {\n    pub fn bail<T>(&self, msg: &str) -> TractResult<T> {\n        bail!(\"Node {} ({}): {}\", self.name, self.op_type, msg)\n    }\n\n    pub fn bail_attr<T>(&self, attr: &str, msg: &str) -> TractResult<T> {\n        bail!(\"Node {} ({}), attribute '{}': {}\", self.name, self.op_type, attr, msg)\n    }\n\n    pub fn expect<R: Reason>(&self, cond: bool, what: R) -> TractResult<()> {\n        if !cond { self.bail(&format!(\"expected {}\", what.reason())) } else { Ok(()) }\n    }\n\n    pub fn expect_attr<R: Reason>(&self, attr: &str, cond: bool, what: R) -> TractResult<()> {\n        if !cond { self.bail_attr(attr, &format!(\"expected {}\", what.reason())) } else { Ok(()) }\n    }\n\n    pub fn expect_ok_or_else<T, R: Reason>(&self, result: Option<T>, what: R) -> TractResult<T> {\n        match result {\n            Some(v) => Ok(v),\n            None => Err(self.expect(false, what).unwrap_err()),\n        }\n    }\n\n    fn get_attr_opt_with_type(\n        &self,\n        name: &str,\n        ty: AttributeType,\n    ) -> TractResult<Option<&AttributeProto>> {\n        let attr = match self.attribute.iter().find(|a| a.name == name) {\n            Some(attr) => attr,\n            _ => return Ok(None),\n        };\n        self.expect_attr(name, AttributeType::try_from(attr.r#type).unwrap() == ty, || {\n            format!(\"{}, got {}\", ty, attr.r#type)\n        })?;\n        Ok(Some(attr))\n    }\n\n    pub fn get_attr_opt<'a, T>(&'a self, name: &str) -> TractResult<Option<T>>\n    where\n        T: AttrScalarType<'a>,\n    {\n        T::get_attr_opt_scalar(self, name)\n    }\n\n    pub fn get_attr<'a, T>(&'a self, name: &str) -> TractResult<T>\n    where\n        T: AttrScalarType<'a>,\n    {\n        self.expect_ok_or_else(self.get_attr_opt(name)?, || format!(\"attribute '{name}'\"))\n    }\n\n    pub fn check_value<T, V: Debug>(&self, attr: &str, value: Result<T, V>) -> TractResult<T> {\n        match value {\n            Ok(value) => Ok(value),\n            Err(err) => self.bail_attr(attr, &format!(\"unexpected value: {err:?}\")),\n        }\n    }\n\n    pub fn get_attr_opt_slice<'a, T>(&'a self, name: &str) -> TractResult<Option<&'a [T]>>\n    where\n        T: AttrSliceType<'a>,\n    {\n        T::get_attr_opt_slice(self, name)\n    }\n\n    pub fn get_attr_slice<'a, T>(&'a self, name: &str) -> TractResult<&'a [T]>\n    where\n        T: AttrSliceType<'a>,\n    {\n        self.expect_ok_or_else(self.get_attr_opt_slice(name)?, || format!(\"attribute '{name}'\"))\n    }\n\n    pub fn get_attr_opt_tvec<'a, T>(&'a self, name: &str) -> TractResult<Option<TVec<T>>>\n    where\n        T: AttrTVecType<'a>,\n    {\n        T::get_attr_opt_tvec(self, name)\n    }\n\n    pub fn get_attr_tvec<'a, T>(&'a self, name: &str) -> TractResult<TVec<T>>\n    where\n        T: AttrTVecType<'a>,\n    {\n        self.expect_ok_or_else(self.get_attr_opt_tvec(name)?, || format!(\"attribute '{name}'\"))\n    }\n\n    pub fn get_attr_opt_vec<'a, T>(&'a self, name: &str) -> TractResult<Option<Vec<T>>>\n    where\n        T: AttrTVecType<'a>,\n    {\n        Ok(self.get_attr_opt_tvec(name)?.map(TVec::into_vec))\n    }\n\n    pub fn get_attr_vec<'a, T>(&'a self, name: &str) -> TractResult<Vec<T>>\n    where\n        T: AttrTVecType<'a>,\n    {\n        self.get_attr_tvec(name).map(TVec::into_vec)\n    }\n}\n"
  },
  {
    "path": "onnx/src/prost/onnx.rs",
    "content": "/// Attributes\n///\n/// A named attribute containing either singular float, integer, string, graph,\n/// and tensor values, or repeated float, integer, string, graph, and tensor values.\n/// An AttributeProto MUST contain the name field, and *only one* of the\n/// following content fields, effectively enforcing a C/C++ union equivalent.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct AttributeProto {\n    /// The name field MUST be present for this version of the IR.\n    ///\n    /// namespace Attribute\n    #[prost(string, tag=\"1\")]\n    pub name: ::prost::alloc::string::String,\n    /// if ref_attr_name is not empty, ref_attr_name is the attribute name in parent function.\n    /// In this case, this AttributeProto does not contain data, and it's a reference of attribute\n    /// in parent scope.\n    /// NOTE: This should ONLY be used in function (sub-graph). It's invalid to be used in main graph.\n    #[prost(string, tag=\"21\")]\n    pub ref_attr_name: ::prost::alloc::string::String,\n    /// A human-readable documentation for this attribute. Markdown is allowed.\n    #[prost(string, tag=\"13\")]\n    pub doc_string: ::prost::alloc::string::String,\n    /// The type field MUST be present for this version of the IR.\n    /// For 0.0.1 versions of the IR, this field was not defined, and\n    /// implementations needed to use has_field hueristics to determine\n    /// which value field was in use.  For IR_VERSION 0.0.2 or later, this\n    /// field MUST be set and match the f|i|s|t|... field in use.  This\n    /// change was made to accomodate proto3 implementations.\n    ///\n    /// discriminator that indicates which field below is in use\n    #[prost(enumeration=\"attribute_proto::AttributeType\", tag=\"20\")]\n    pub r#type: i32,\n    /// Exactly ONE of the following fields must be present for this version of the IR\n    ///\n    /// float\n    #[prost(float, tag=\"2\")]\n    pub f: f32,\n    /// int\n    #[prost(int64, tag=\"3\")]\n    pub i: i64,\n    /// UTF-8 string\n    #[prost(bytes=\"vec\", tag=\"4\")]\n    pub s: ::prost::alloc::vec::Vec<u8>,\n    /// tensor value\n    #[prost(message, optional, tag=\"5\")]\n    pub t: ::core::option::Option<TensorProto>,\n    /// graph\n    #[prost(message, optional, tag=\"6\")]\n    pub g: ::core::option::Option<GraphProto>,\n    /// sparse tensor value\n    #[prost(message, optional, tag=\"22\")]\n    pub sparse_tensor: ::core::option::Option<SparseTensorProto>,\n    // Do not use field below, it's deprecated.\n    // optional ValueProto v = 12;         // value - subsumes everything but graph\n\n    /// list of floats\n    #[prost(float, repeated, tag=\"7\")]\n    pub floats: ::prost::alloc::vec::Vec<f32>,\n    /// list of ints\n    #[prost(int64, repeated, tag=\"8\")]\n    pub ints: ::prost::alloc::vec::Vec<i64>,\n    /// list of UTF-8 strings\n    #[prost(bytes=\"vec\", repeated, tag=\"9\")]\n    pub strings: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,\n    /// list of tensors\n    #[prost(message, repeated, tag=\"10\")]\n    pub tensors: ::prost::alloc::vec::Vec<TensorProto>,\n    /// list of graph\n    #[prost(message, repeated, tag=\"11\")]\n    pub graphs: ::prost::alloc::vec::Vec<GraphProto>,\n    /// list of sparse tensors\n    #[prost(message, repeated, tag=\"23\")]\n    pub sparse_tensors: ::prost::alloc::vec::Vec<SparseTensorProto>,\n    /// list of type protos\n    #[prost(message, repeated, tag=\"15\")]\n    pub type_protos: ::prost::alloc::vec::Vec<TypeProto>,\n}\n/// Nested message and enum types in `AttributeProto`.\npub mod attribute_proto {\n    /// Note: this enum is structurally identical to the OpSchema::AttrType\n    /// enum defined in schema.h.  If you rev one, you likely need to rev the other.\n    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n    #[repr(i32)]\n    pub enum AttributeType {\n        Undefined = 0,\n        Float = 1,\n        Int = 2,\n        String = 3,\n        Tensor = 4,\n        Graph = 5,\n        SparseTensor = 11,\n        TypeProto = 13,\n        Floats = 6,\n        Ints = 7,\n        Strings = 8,\n        Tensors = 9,\n        Graphs = 10,\n        SparseTensors = 12,\n        TypeProtos = 14,\n    }\n    impl AttributeType {\n        /// String value of the enum field names used in the ProtoBuf definition.\n        ///\n        /// The values are not transformed in any way and thus are considered stable\n        /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n        pub fn as_str_name(&self) -> &'static str {\n            match self {\n                AttributeType::Undefined => \"UNDEFINED\",\n                AttributeType::Float => \"FLOAT\",\n                AttributeType::Int => \"INT\",\n                AttributeType::String => \"STRING\",\n                AttributeType::Tensor => \"TENSOR\",\n                AttributeType::Graph => \"GRAPH\",\n                AttributeType::SparseTensor => \"SPARSE_TENSOR\",\n                AttributeType::TypeProto => \"TYPE_PROTO\",\n                AttributeType::Floats => \"FLOATS\",\n                AttributeType::Ints => \"INTS\",\n                AttributeType::Strings => \"STRINGS\",\n                AttributeType::Tensors => \"TENSORS\",\n                AttributeType::Graphs => \"GRAPHS\",\n                AttributeType::SparseTensors => \"SPARSE_TENSORS\",\n                AttributeType::TypeProtos => \"TYPE_PROTOS\",\n            }\n        }\n    }\n}\n/// Defines information on value, including the name, the type, and\n/// the shape of the value.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct ValueInfoProto {\n    /// This field MUST be present in this version of the IR.\n    ///\n    /// namespace Value\n    #[prost(string, tag=\"1\")]\n    pub name: ::prost::alloc::string::String,\n    /// This field MUST be present in this version of the IR.\n    #[prost(message, optional, tag=\"2\")]\n    pub r#type: ::core::option::Option<TypeProto>,\n    /// A human-readable documentation for this value. Markdown is allowed.\n    #[prost(string, tag=\"3\")]\n    pub doc_string: ::prost::alloc::string::String,\n}\n/// Nodes\n///\n/// Computation graphs are made up of a DAG of nodes, which represent what is\n/// commonly called a \"layer\" or \"pipeline stage\" in machine learning frameworks.\n///\n/// For example, it can be a node of type \"Conv\" that takes in an image, a filter \n/// tensor and a bias tensor, and produces the convolved output.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct NodeProto {\n    /// namespace Value\n    #[prost(string, repeated, tag=\"1\")]\n    pub input: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n    /// namespace Value\n    #[prost(string, repeated, tag=\"2\")]\n    pub output: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n    /// An optional identifier for this node in a graph.\n    /// This field MAY be absent in ths version of the IR.\n    ///\n    /// namespace Node\n    #[prost(string, tag=\"3\")]\n    pub name: ::prost::alloc::string::String,\n    /// The symbolic identifier of the Operator to execute.\n    ///\n    /// namespace Operator\n    #[prost(string, tag=\"4\")]\n    pub op_type: ::prost::alloc::string::String,\n    /// The domain of the OperatorSet that specifies the operator named by op_type.\n    ///\n    /// namespace Domain\n    #[prost(string, tag=\"7\")]\n    pub domain: ::prost::alloc::string::String,\n    /// Additional named attributes.\n    #[prost(message, repeated, tag=\"5\")]\n    pub attribute: ::prost::alloc::vec::Vec<AttributeProto>,\n    /// A human-readable documentation for this node. Markdown is allowed.\n    #[prost(string, tag=\"6\")]\n    pub doc_string: ::prost::alloc::string::String,\n}\n/// Models\n///\n/// ModelProto is a top-level file/container format for bundling a ML model and\n/// associating its computation graph with metadata.\n///\n/// The semantics of the model are described by the associated GraphProto.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct ModelProto {\n    /// The version of the IR this model targets. See Version enum above.\n    /// This field MUST be present.\n    #[prost(int64, tag=\"1\")]\n    pub ir_version: i64,\n    /// The OperatorSets this model relies on.\n    /// All ModelProtos MUST have at least one entry that\n    /// specifies which version of the ONNX OperatorSet is\n    /// being imported.\n    ///\n    /// All nodes in the ModelProto's graph will bind against the operator\n    /// with the same-domain/same-op_type operator with the HIGHEST version\n    /// in the referenced operator sets.\n    #[prost(message, repeated, tag=\"8\")]\n    pub opset_import: ::prost::alloc::vec::Vec<OperatorSetIdProto>,\n    /// The name of the framework or tool used to generate this model.\n    /// This field SHOULD be present to indicate which implementation/tool/framework\n    /// emitted the model.\n    #[prost(string, tag=\"2\")]\n    pub producer_name: ::prost::alloc::string::String,\n    /// The version of the framework or tool used to generate this model.\n    /// This field SHOULD be present to indicate which implementation/tool/framework\n    /// emitted the model.\n    #[prost(string, tag=\"3\")]\n    pub producer_version: ::prost::alloc::string::String,\n    /// Domain name of the model.\n    /// We use reverse domain names as name space indicators. For example:\n    /// `com.facebook.fair` or `com.microsoft.cognitiveservices`\n    ///\n    /// Together with `model_version` and GraphProto.name, this forms the unique identity of\n    /// the graph.\n    #[prost(string, tag=\"4\")]\n    pub domain: ::prost::alloc::string::String,\n    /// The version of the graph encoded. See Version enum below.\n    #[prost(int64, tag=\"5\")]\n    pub model_version: i64,\n    /// A human-readable documentation for this model. Markdown is allowed.\n    #[prost(string, tag=\"6\")]\n    pub doc_string: ::prost::alloc::string::String,\n    /// The parameterized graph that is evaluated to execute the model.\n    #[prost(message, optional, tag=\"7\")]\n    pub graph: ::core::option::Option<GraphProto>,\n    /// Named metadata values; keys should be distinct.\n    #[prost(message, repeated, tag=\"14\")]\n    pub metadata_props: ::prost::alloc::vec::Vec<StringStringEntryProto>,\n    /// Training-specific information. Sequentially executing all stored\n    /// `TrainingInfoProto.algorithm`s and assigning their outputs following\n    /// the corresponding `TrainingInfoProto.update_binding`s is one training\n    /// iteration. Similarly, to initialize the model\n    /// (as if training hasn't happened), the user should sequentially execute\n    /// all stored `TrainingInfoProto.initialization`s and assigns their outputs\n    /// using `TrainingInfoProto.initialization_binding`s.\n    ///\n    /// If this field is empty, the training behavior of the model is undefined.\n    #[prost(message, repeated, tag=\"20\")]\n    pub training_info: ::prost::alloc::vec::Vec<TrainingInfoProto>,\n    /// A list of function protos local to the model.\n    ///\n    /// Name of the function \"FunctionProto.name\" should be unique within the domain \"FunctionProto.domain\".\n    /// In case of any conflicts the behavior (whether the model local functions are given higher priority,\n    /// or standard opserator sets are given higher priotity or this is treated as error) is defined by\n    /// the runtimes.\n    ///\n    /// The operator sets imported by FunctionProto should be compatible with the ones\n    /// imported by ModelProto and other model local FunctionProtos.\n    /// Example, if same operator set say 'A' is imported by a FunctionProto and ModelProto\n    /// or by 2 FunctionProtos then versions for the operator set may be different but,\n    /// the operator schema returned for op_type, domain, version combination\n    /// for both the versions should be same for every node in the function body.\n    ///\n    /// One FunctionProto can reference other FunctionProto in the model, however, recursive reference\n    /// is not allowed.\n    #[prost(message, repeated, tag=\"25\")]\n    pub functions: ::prost::alloc::vec::Vec<FunctionProto>,\n}\n/// StringStringEntryProto follows the pattern for cross-proto-version maps.\n/// See <https://developers.google.com/protocol-buffers/docs/proto3#maps>\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct StringStringEntryProto {\n    #[prost(string, tag=\"1\")]\n    pub key: ::prost::alloc::string::String,\n    #[prost(string, tag=\"2\")]\n    pub value: ::prost::alloc::string::String,\n}\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TensorAnnotation {\n    #[prost(string, optional, tag=\"1\")]\n    pub tensor_name: ::core::option::Option<::prost::alloc::string::String>,\n    /// <key, value> pairs to annotate tensor specified by <tensor_name> above.\n    /// The keys used in the mapping below must be pre-defined in ONNX spec.\n    /// For example, for 8-bit linear quantization case, 'SCALE_TENSOR', 'ZERO_POINT_TENSOR' will be pre-defined as\n    /// quantization parameter keys.\n    #[prost(message, repeated, tag=\"2\")]\n    pub quant_parameter_tensor_names: ::prost::alloc::vec::Vec<StringStringEntryProto>,\n}\n/// Graphs\n///\n/// A graph defines the computational logic of a model and is comprised of a parameterized \n/// list of nodes that form a directed acyclic graph based on their inputs and outputs.\n/// This is the equivalent of the \"network\" or \"graph\" in many deep learning\n/// frameworks.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct GraphProto {\n    /// The nodes in the graph, sorted topologically.\n    #[prost(message, repeated, tag=\"1\")]\n    pub node: ::prost::alloc::vec::Vec<NodeProto>,\n    /// The name of the graph.\n    ///\n    /// namespace Graph\n    #[prost(string, tag=\"2\")]\n    pub name: ::prost::alloc::string::String,\n    /// A list of named tensor values, used to specify constant inputs of the graph.\n    /// Each initializer (both TensorProto as well SparseTensorProto) MUST have a name.\n    /// The name MUST be unique across both initializer and sparse_initializer,\n    /// but the name MAY also appear in the input list.\n    #[prost(message, repeated, tag=\"5\")]\n    pub initializer: ::prost::alloc::vec::Vec<TensorProto>,\n    /// Initializers (see above) stored in sparse format.\n    #[prost(message, repeated, tag=\"15\")]\n    pub sparse_initializer: ::prost::alloc::vec::Vec<SparseTensorProto>,\n    /// A human-readable documentation for this graph. Markdown is allowed.\n    #[prost(string, tag=\"10\")]\n    pub doc_string: ::prost::alloc::string::String,\n    /// The inputs and outputs of the graph.\n    #[prost(message, repeated, tag=\"11\")]\n    pub input: ::prost::alloc::vec::Vec<ValueInfoProto>,\n    #[prost(message, repeated, tag=\"12\")]\n    pub output: ::prost::alloc::vec::Vec<ValueInfoProto>,\n    /// Information for the values in the graph. The ValueInfoProto.name's\n    /// must be distinct. It is optional for a value to appear in value_info list.\n    #[prost(message, repeated, tag=\"13\")]\n    pub value_info: ::prost::alloc::vec::Vec<ValueInfoProto>,\n    /// This field carries information to indicate the mapping among a tensor and its\n    /// quantization parameter tensors. For example:\n    /// For tensor 'a', it may have {'SCALE_TENSOR', 'a_scale'} and {'ZERO_POINT_TENSOR', 'a_zero_point'} annotated,\n    /// which means, tensor 'a_scale' and tensor 'a_zero_point' are scale and zero point of tensor 'a' in the model.\n    #[prost(message, repeated, tag=\"14\")]\n    pub quantization_annotation: ::prost::alloc::vec::Vec<TensorAnnotation>,\n}\n/// Training information\n/// TrainingInfoProto stores information for training a model.\n/// In particular, this defines two functionalities: an initialization-step\n/// and a training-algorithm-step. Initialization resets the model\n/// back to its original state as if no training has been performed.\n/// Training algorithm improves the model based on input data.\n///\n/// The semantics of the initialization-step is that the initializers\n/// in ModelProto.graph and in TrainingInfoProto.algorithm are first\n/// initialized as specified by the initializers in the graph, and then\n/// updated by the \"initialization_binding\" in every instance in\n/// ModelProto.training_info.\n///\n/// The field \"algorithm\" defines a computation graph which represents a\n/// training algorithm's step. After the execution of a\n/// TrainingInfoProto.algorithm, the initializers specified by \"update_binding\"\n/// may be immediately updated. If the targeted training algorithm contains\n/// consecutive update steps (such as block coordinate descent methods),\n/// the user needs to create a TrainingInfoProto for each step.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TrainingInfoProto {\n    /// This field describes a graph to compute the initial tensors\n    /// upon starting the training process. Initialization graph has no input\n    /// and can have multiple outputs. Usually, trainable tensors in neural\n    /// networks are randomly initialized. To achieve that, for each tensor,\n    /// the user can put a random number operator such as RandomNormal or\n    /// RandomUniform in TrainingInfoProto.initialization.node and assign its\n    /// random output to the specific tensor using \"initialization_binding\".\n    /// This graph can also set the initializers in \"algorithm\" in the same\n    /// TrainingInfoProto; a use case is resetting the number of training\n    /// iteration to zero.\n    ///\n    /// By default, this field is an empty graph and its evaluation does not\n    /// produce any output. Thus, no initializer would be changed by default.\n    #[prost(message, optional, tag=\"1\")]\n    pub initialization: ::core::option::Option<GraphProto>,\n    /// This field represents a training algorithm step. Given required inputs,\n    /// it computes outputs to update initializers in its own or inference graph's\n    /// initializer lists. In general, this field contains loss node, gradient node,\n    /// optimizer node, increment of iteration count.\n    ///\n    /// An execution of the training algorithm step is performed by executing the\n    /// graph obtained by combining the inference graph (namely \"ModelProto.graph\")\n    /// and the \"algorithm\" graph. That is, the actual the actual\n    /// input/initializer/output/node/value_info/sparse_initializer list of\n    /// the training graph is the concatenation of\n    /// \"ModelProto.graph.input/initializer/output/node/value_info/sparse_initializer\"\n    /// and \"algorithm.input/initializer/output/node/value_info/sparse_initializer\"\n    /// in that order. This combined graph must satisfy the normal ONNX conditions.\n    /// Now, let's provide a visualization of graph combination for clarity.\n    /// Let the inference graph (i.e., \"ModelProto.graph\") be\n    ///     tensor_a, tensor_b -> MatMul -> tensor_c -> Sigmoid -> tensor_d\n    /// and the \"algorithm\" graph be\n    ///     tensor_d -> Add -> tensor_e\n    /// The combination process results\n    ///     tensor_a, tensor_b -> MatMul -> tensor_c -> Sigmoid -> tensor_d -> Add -> tensor_e\n    ///\n    /// Notice that an input of a node in the \"algorithm\" graph may reference the\n    /// output of a node in the inference graph (but not the other way round). Also, inference\n    /// node cannot reference inputs of \"algorithm\". With these restrictions, inference graph\n    /// can always be run independently without training information.\n    ///\n    /// By default, this field is an empty graph and its evaluation does not\n    /// produce any output. Evaluating the default training step never\n    /// update any initializers.\n    #[prost(message, optional, tag=\"2\")]\n    pub algorithm: ::core::option::Option<GraphProto>,\n    /// This field specifies the bindings from the outputs of \"initialization\" to\n    /// some initializers in \"ModelProto.graph.initializer\" and\n    /// the \"algorithm.initializer\" in the same TrainingInfoProto.\n    /// See \"update_binding\" below for details.\n    ///\n    /// By default, this field is empty and no initializer would be changed\n    /// by the execution of \"initialization\".\n    #[prost(message, repeated, tag=\"3\")]\n    pub initialization_binding: ::prost::alloc::vec::Vec<StringStringEntryProto>,\n    /// Gradient-based training is usually an iterative procedure. In one gradient\n    /// descent iteration, we apply\n    ///\n    /// x = x - r * g\n    ///\n    /// where \"x\" is the optimized tensor, \"r\" stands for learning rate, and \"g\" is\n    /// gradient of \"x\" with respect to a chosen loss. To avoid adding assignments\n    /// into the training graph, we split the update equation into\n    ///\n    /// y = x - r * g\n    /// x = y\n    ///\n    /// The user needs to save \"y = x - r * g\" into TrainingInfoProto.algorithm. To\n    /// tell that \"y\" should be assigned to \"x\", the field \"update_binding\" may\n    /// contain a key-value pair of strings, \"x\" (key of StringStringEntryProto)\n    /// and \"y\" (value of StringStringEntryProto).\n    /// For a neural network with multiple trainable (mutable) tensors, there can\n    /// be multiple key-value pairs in \"update_binding\".\n    ///\n    /// The initializers appears as keys in \"update_binding\" are considered\n    /// mutable variables. This implies some behaviors\n    /// as described below.\n    ///\n    ///   1. We have only unique keys in all \"update_binding\"s so that two\n    ///      variables may not have the same name. This ensures that one\n    ///      variable is assigned up to once.\n    ///   2. The keys must appear in names of \"ModelProto.graph.initializer\" or\n    ///      \"TrainingInfoProto.algorithm.initializer\".\n    ///   3. The values must be output names of \"algorithm\" or \"ModelProto.graph.output\".\n    ///   4. Mutable variables are initialized to the value specified by the\n    ///      corresponding initializer, and then potentially updated by\n    ///      \"initializer_binding\"s and \"update_binding\"s in \"TrainingInfoProto\"s.\n    ///\n    /// This field usually contains names of trainable tensors\n    /// (in ModelProto.graph), optimizer states such as momentums in advanced\n    /// stochastic gradient methods (in TrainingInfoProto.graph),\n    /// and number of training iterations (in TrainingInfoProto.graph).\n    ///\n    /// By default, this field is empty and no initializer would be changed\n    /// by the execution of \"algorithm\".\n    #[prost(message, repeated, tag=\"4\")]\n    pub update_binding: ::prost::alloc::vec::Vec<StringStringEntryProto>,\n}\n/// Tensors\n///\n/// A serialized tensor value.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TensorProto {\n    /// The shape of the tensor.\n    #[prost(int64, repeated, tag=\"1\")]\n    pub dims: ::prost::alloc::vec::Vec<i64>,\n    /// The data type of the tensor.\n    #[prost(enumeration=\"tensor_proto::DataType\", tag=\"2\")]\n    pub data_type: i32,\n    #[prost(message, optional, tag=\"3\")]\n    pub segment: ::core::option::Option<tensor_proto::Segment>,\n    // Tensor content must be organized in row-major order.\n    //\n    // Depending on the data_type field, exactly one of the fields below with\n    // name ending in _data is used to store the elements of the tensor.\n\n    /// For float and complex64 values\n    /// Complex64 tensors are encoded as a single array of floats,\n    /// with the real components appearing in odd numbered positions,\n    /// and the corresponding imaginary component apparing in the\n    /// subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i]\n    /// is encoded as [1.0, 2.0 ,3.0 ,4.0]\n    /// When this field is present, the data_type field MUST be FLOAT or COMPLEX64.\n    #[prost(float, repeated, tag=\"4\")]\n    pub float_data: ::prost::alloc::vec::Vec<f32>,\n    /// For int32, uint8, int8, uint16, int16, bool, and float16 values\n    /// float16 values must be bit-wise converted to an uint16_t prior\n    /// to writing to the buffer.\n    /// When this field is present, the data_type field MUST be\n    /// INT32, INT16, INT8, UINT16, INT8, BOOL, or FLOAT16\n    #[prost(int32, repeated, tag=\"5\")]\n    pub int32_data: ::prost::alloc::vec::Vec<i32>,\n    /// For strings.\n    /// Each element of string_data is a UTF-8 encoded Unicode\n    /// string. No trailing null, no leading BOM. The protobuf \"string\"\n    /// scalar type is not used to match ML community conventions.\n    /// When this field is present, the data_type field MUST be STRING\n    #[prost(bytes=\"vec\", repeated, tag=\"6\")]\n    pub string_data: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,\n    /// For int64.\n    /// When this field is present, the data_type field MUST be INT64\n    #[prost(int64, repeated, tag=\"7\")]\n    pub int64_data: ::prost::alloc::vec::Vec<i64>,\n    /// Optionally, a name for the tensor.\n    ///\n    /// namespace Value\n    #[prost(string, tag=\"8\")]\n    pub name: ::prost::alloc::string::String,\n    /// A human-readable documentation for this tensor. Markdown is allowed.\n    #[prost(string, tag=\"12\")]\n    pub doc_string: ::prost::alloc::string::String,\n    /// Serializations can either use one of the fields above, or use this\n    /// raw bytes field. The only exception is the string case, where one is\n    /// required to store the content in the repeated bytes string_data field.\n    ///\n    /// When this raw_data field is used to store tensor value, elements MUST\n    /// be stored in as fixed-width, little-endian order.\n    /// Floating-point data types MUST be stored in IEEE 754 format.\n    /// Complex64 elements must be written as two consecutive FLOAT values, real component first.\n    /// Complex128 elements must be written as two consecutive DOUBLE values, real component first.\n    /// Boolean type MUST be written one byte per tensor element (00000001 for true, 00000000 for false).\n    ///\n    /// Note: the advantage of specific field rather than the raw_data field is\n    /// that in some cases (e.g. int data), protobuf does a better packing via\n    /// variable length storage, and may lead to smaller binary footprint.\n    /// When this field is present, the data_type field MUST NOT be STRING or UNDEFINED\n    #[prost(bytes=\"vec\", tag=\"9\")]\n    pub raw_data: ::prost::alloc::vec::Vec<u8>,\n    /// For double\n    /// Complex64 tensors are encoded as a single array of doubles,\n    /// with the real components appearing in odd numbered positions,\n    /// and the corresponding imaginary component apparing in the\n    /// subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i]\n    /// is encoded as [1.0, 2.0 ,3.0 ,4.0]\n    /// When this field is present, the data_type field MUST be DOUBLE or COMPLEX128\n    #[prost(double, repeated, tag=\"10\")]\n    pub double_data: ::prost::alloc::vec::Vec<f64>,\n    /// For uint64 and uint32 values\n    /// When this field is present, the data_type field MUST be\n    /// UINT32 or UINT64\n    #[prost(uint64, repeated, tag=\"11\")]\n    pub uint64_data: ::prost::alloc::vec::Vec<u64>,\n    /// If value not set, data is stored in raw_data (if set) otherwise in type-specified field.\n    #[prost(enumeration=\"tensor_proto::DataLocation\", optional, tag=\"14\")]\n    pub data_location: ::core::option::Option<i32>,\n    /// Data can be stored inside the protobuf file using type-specific fields or raw_data.\n    /// Alternatively, raw bytes data can be stored in an external file, using the external_data field.\n    /// external_data stores key-value pairs describing data location. Recognized keys are:\n    /// - \"location\" (required) - POSIX filesystem path relative to the directory where the ONNX\n    ///                            protobuf model was stored\n    /// - \"offset\" (optional) - position of byte at which stored data begins. Integer stored as string.\n    ///                          Offset values SHOULD be multiples 4096 (page size) to enable mmap support.\n    /// - \"length\" (optional) - number of bytes containing data. Integer stored as string.\n    /// - \"checksum\" (optional) - SHA1 digest of file specified in under 'location' key.\n    #[prost(message, repeated, tag=\"13\")]\n    pub external_data: ::prost::alloc::vec::Vec<StringStringEntryProto>,\n}\n/// Nested message and enum types in `TensorProto`.\npub mod tensor_proto {\n    /// For very large tensors, we may want to store them in chunks, in which\n    /// case the following fields will specify the segment that is stored in\n    /// the current TensorProto.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct Segment {\n        #[prost(int64, tag=\"1\")]\n        pub begin: i64,\n        #[prost(int64, tag=\"2\")]\n        pub end: i64,\n    }\n    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n    #[repr(i32)]\n    pub enum DataType {\n        Undefined = 0,\n        /// Basic types.\n        ///\n        /// float\n        Float = 1,\n        /// uint8_t\n        Uint8 = 2,\n        /// int8_t\n        Int8 = 3,\n        /// uint16_t\n        Uint16 = 4,\n        /// int16_t\n        Int16 = 5,\n        /// int32_t\n        Int32 = 6,\n        /// int64_t\n        Int64 = 7,\n        /// string\n        String = 8,\n        /// bool\n        Bool = 9,\n        /// IEEE754 half-precision floating-point format (16 bits wide).\n        /// This format has 1 sign bit, 5 exponent bits, and 10 mantissa bits.\n        Float16 = 10,\n        Double = 11,\n        Uint32 = 12,\n        Uint64 = 13,\n        /// complex with float32 real and imaginary components\n        Complex64 = 14,\n        /// complex with float64 real and imaginary components\n        Complex128 = 15,\n        /// Non-IEEE floating-point format based on IEEE754 single-precision\n        /// floating-point number truncated to 16 bits.\n        /// This format has 1 sign bit, 8 exponent bits, and 7 mantissa bits.\n        Bfloat16 = 16,\n    }\n    impl DataType {\n        /// String value of the enum field names used in the ProtoBuf definition.\n        ///\n        /// The values are not transformed in any way and thus are considered stable\n        /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n        pub fn as_str_name(&self) -> &'static str {\n            match self {\n                DataType::Undefined => \"UNDEFINED\",\n                DataType::Float => \"FLOAT\",\n                DataType::Uint8 => \"UINT8\",\n                DataType::Int8 => \"INT8\",\n                DataType::Uint16 => \"UINT16\",\n                DataType::Int16 => \"INT16\",\n                DataType::Int32 => \"INT32\",\n                DataType::Int64 => \"INT64\",\n                DataType::String => \"STRING\",\n                DataType::Bool => \"BOOL\",\n                DataType::Float16 => \"FLOAT16\",\n                DataType::Double => \"DOUBLE\",\n                DataType::Uint32 => \"UINT32\",\n                DataType::Uint64 => \"UINT64\",\n                DataType::Complex64 => \"COMPLEX64\",\n                DataType::Complex128 => \"COMPLEX128\",\n                DataType::Bfloat16 => \"BFLOAT16\",\n            }\n        }\n    }\n    /// Location of the data for this tensor. MUST be one of:\n    /// - DEFAULT - data stored inside the protobuf message. Data is stored in raw_data (if set) otherwise in type-specified field.\n    /// - EXTERNAL - data stored in an external location as described by external_data field.\n    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n    #[repr(i32)]\n    pub enum DataLocation {\n        Default = 0,\n        External = 1,\n    }\n    impl DataLocation {\n        /// String value of the enum field names used in the ProtoBuf definition.\n        ///\n        /// The values are not transformed in any way and thus are considered stable\n        /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n        pub fn as_str_name(&self) -> &'static str {\n            match self {\n                DataLocation::Default => \"DEFAULT\",\n                DataLocation::External => \"EXTERNAL\",\n            }\n        }\n    }\n}\n/// A serialized sparse-tensor value\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SparseTensorProto {\n    /// The sequence of non-default values are encoded as a tensor of shape \\[NNZ\\].\n    /// The default-value is zero for numeric tensors, and empty-string for string tensors.\n    /// values must have a non-empty name present which serves as a name for SparseTensorProto\n    /// when used in sparse_initializer list.\n    #[prost(message, optional, tag=\"1\")]\n    pub values: ::core::option::Option<TensorProto>,\n    /// The indices of the non-default values, which may be stored in one of two formats.\n    /// (a) Indices can be a tensor of shape [NNZ, rank] with the \\[i,j\\]-th value\n    /// corresponding to the j-th index of the i-th value (in the values tensor).\n    /// (b) Indices can be a tensor of shape \\[NNZ\\], in which case the i-th value\n    /// must be the linearized-index of the i-th value (in the values tensor).\n    /// The linearized-index can be converted into an index tuple (k_1,...,k_rank)\n    /// using the shape provided below.\n    /// The indices must appear in ascending order without duplication.\n    /// In the first format, the ordering is lexicographic-ordering:\n    /// e.g., index-value \\[1,4\\] must appear before \\[2,1\\]\n    #[prost(message, optional, tag=\"2\")]\n    pub indices: ::core::option::Option<TensorProto>,\n    /// The shape of the underlying dense-tensor: [dim_1, dim_2, ... dim_rank]\n    #[prost(int64, repeated, tag=\"3\")]\n    pub dims: ::prost::alloc::vec::Vec<i64>,\n}\n/// Defines a tensor shape. A dimension can be either an integer value\n/// or a symbolic variable. A symbolic variable represents an unknown\n/// dimension.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TensorShapeProto {\n    #[prost(message, repeated, tag=\"1\")]\n    pub dim: ::prost::alloc::vec::Vec<tensor_shape_proto::Dimension>,\n}\n/// Nested message and enum types in `TensorShapeProto`.\npub mod tensor_shape_proto {\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct Dimension {\n        /// Standard denotation can optionally be used to denote tensor\n        /// dimensions with standard semantic descriptions to ensure\n        /// that operations are applied to the correct axis of a tensor.\n        /// Refer to <https://github.com/onnx/onnx/blob/master/docs/DimensionDenotation.md#denotation-definition>\n        /// for pre-defined dimension denotations.\n        #[prost(string, tag=\"3\")]\n        pub denotation: ::prost::alloc::string::String,\n        #[prost(oneof=\"dimension::Value\", tags=\"1, 2\")]\n        pub value: ::core::option::Option<dimension::Value>,\n    }\n    /// Nested message and enum types in `Dimension`.\n    pub mod dimension {\n        #[derive(Clone, PartialEq, ::prost::Oneof)]\n        pub enum Value {\n            #[prost(int64, tag=\"1\")]\n            DimValue(i64),\n            /// namespace Shape\n            #[prost(string, tag=\"2\")]\n            DimParam(::prost::alloc::string::String),\n        }\n    }\n}\n/// Types\n///\n/// The standard ONNX data types.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TypeProto {\n    /// An optional denotation can be used to denote the whole \n    /// type with a standard semantic description as to what is \n    /// stored inside. Refer to <https://github.com/onnx/onnx/blob/master/docs/TypeDenotation.md#type-denotation-definition>\n    /// for pre-defined type denotations.\n    #[prost(string, tag=\"6\")]\n    pub denotation: ::prost::alloc::string::String,\n    #[prost(oneof=\"type_proto::Value\", tags=\"1\")]\n    pub value: ::core::option::Option<type_proto::Value>,\n}\n/// Nested message and enum types in `TypeProto`.\npub mod type_proto {\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct Tensor {\n        /// This field MUST NOT have the value of UNDEFINED\n        /// This field MUST be present for this version of the IR.\n        #[prost(enumeration=\"super::tensor_proto::DataType\", tag=\"1\")]\n        pub elem_type: i32,\n        #[prost(message, optional, tag=\"2\")]\n        pub shape: ::core::option::Option<super::TensorShapeProto>,\n    }\n    #[derive(Clone, PartialEq, ::prost::Oneof)]\n    pub enum Value {\n        /// The type of a tensor.\n        #[prost(message, tag=\"1\")]\n        TensorType(Tensor),\n    }\n}\n/// Operator Sets\n///\n/// OperatorSets are uniquely identified by a (domain, opset_version) pair.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct OperatorSetIdProto {\n    /// The domain of the operator set being identified.\n    /// The empty string (\"\") or absence of this field implies the operator\n    /// set that is defined as part of the ONNX specification.\n    /// This field MUST be present in this version of the IR when referring to any other operator set.\n    #[prost(string, tag=\"1\")]\n    pub domain: ::prost::alloc::string::String,\n    /// The version of the operator set being identified.\n    /// This field MUST be present in this version of the IR.\n    #[prost(int64, tag=\"2\")]\n    pub version: i64,\n}\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct FunctionProto {\n    /// The name of the function, similar usage of op_type in OperatorProto.\n    /// Combined with FunctionProto.domain, this forms the unique identity of\n    /// the FunctionProto.\n    #[prost(string, optional, tag=\"1\")]\n    pub name: ::core::option::Option<::prost::alloc::string::String>,\n    /// The inputs and outputs of the function.\n    #[prost(string, repeated, tag=\"4\")]\n    pub input: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n    #[prost(string, repeated, tag=\"5\")]\n    pub output: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n    /// The attributes of the function.\n    #[prost(string, repeated, tag=\"6\")]\n    pub attribute: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n    /// The nodes in the function.\n    #[prost(message, repeated, tag=\"7\")]\n    pub node: ::prost::alloc::vec::Vec<NodeProto>,\n    /// A human-readable documentation for this function. Markdown is allowed.\n    #[prost(string, optional, tag=\"8\")]\n    pub doc_string: ::core::option::Option<::prost::alloc::string::String>,\n    // The OperatorSets this function body (graph) relies on.\n    //\n    // All nodes in the function body (graph) will bind against the operator\n    // with the same-domain/same-op_type operator with the HIGHEST version\n    // in the referenced operator sets. This means at most one version can be relied\n    // for one domain.\n    //\n    // The operator sets imported by FunctionProto should be compatible with the ones\n    // imported by ModelProto. Example, if same operator set say 'A' is imported by FunctionProto\n    // and ModelProto then versions for the operator set may be different but,\n    // the operator schema returned for op_type, domain, version combination\n    // for both the versions should be same.\n\n    #[prost(message, repeated, tag=\"9\")]\n    pub opset_import: ::prost::alloc::vec::Vec<OperatorSetIdProto>,\n    /// The domain which this function belongs to. Combined with FunctionProto.name, this forms the unique identity of\n    /// the FunctionProto.\n    #[prost(string, optional, tag=\"10\")]\n    pub domain: ::core::option::Option<::prost::alloc::string::String>,\n}\n// Overview\n//\n// ONNX is an open specification that is comprised of the following components:\n//\n// 1)  A definition of an extensible computation graph model.\n// 2)  Definitions of standard data types.\n// 3)  Definitions of built-in operators.\n//\n// This document describes the syntax of models and their computation graphs,\n// as well as the standard data types. Together, they are referred to as the ONNX\n// Intermediate Representation, or 'IR' for short. \n//\n// The normative semantic specification of the ONNX IR is found in docs/IR.md.\n// Definitions of the built-in neural network operators may be found in docs/Operators.md.\n\n// Notes\n//\n// Release\n//\n// We are still in the very early stage of defining ONNX. The current\n// version of ONNX is a starting point. While we are actively working\n// towards a complete spec, we would like to get the community involved\n// by sharing our working version of ONNX.\n//\n// Protobuf compatibility\n// \n// To simplify framework compatibility, ONNX is defined using the subset of protobuf \n// that is compatible with both protobuf v2 and v3. This means that we do not use any\n// protobuf features that are only available in one of the two versions.\n//\n// Here are the most notable contortions we have to carry out to work around\n// these limitations:\n//\n//    - No 'map' (added protobuf 3.0). We instead represent mappings as lists\n//      of key-value pairs, where order does not matter and duplicates\n//      are not allowed.\n\n/// Versioning\n///\n/// ONNX versioning is specified in docs/IR.md and elaborated on in docs/Versioning.md\n///\n/// To be compatible with both proto2 and proto3, we will use a version number\n/// that is not defined by the default value but an explicit enum number.\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n#[repr(i32)]\npub enum Version {\n    /// proto3 requires the first enum value to be zero.\n    /// We add this just to appease the compiler.\n    StartVersion = 0,\n    /// The version field is always serialized and we will use it to store the\n    /// version that the  graph is generated from. This helps us set up version\n    /// control.\n    /// For the IR, we are using simple numbers starting with 0x00000001,\n    /// which was the version we published on Oct 10, 2017.\n    IrVersion20171010 = 1,\n    /// IR_VERSION 2 published on Oct 30, 2017\n    /// - Added type discriminator to AttributeProto to support proto3 users\n    IrVersion20171030 = 2,\n    /// IR VERSION 3 published on Nov 3, 2017\n    /// - For operator versioning:\n    ///     - Added new message OperatorSetIdProto\n    ///     - Added opset_import in ModelProto\n    /// - For vendor extensions, added domain in NodeProto\n    IrVersion2017113 = 3,\n    /// IR VERSION 4 published on Jan 22, 2019\n    /// - Relax constraint that initializers should be a subset of graph inputs\n    /// - Add type BFLOAT16\n    IrVersion2019122 = 4,\n    /// IR VERSION 5 published on March 18, 2019\n    /// - Add message TensorAnnotation.\n    /// - Add quantization annotation in GraphProto to map tensor with its scale and zero point quantization parameters.\n    IrVersion2019318 = 5,\n    /// IR VERSION 6 published on Sep 19, 2019\n    /// - Add support for sparse tensor constants stored in model.\n    ///    - Add message SparseTensorProto\n    ///    - Add sparse initializers\n    IrVersion2019919 = 6,\n    /// IR VERSION 7 published on May 8, 2020\n    /// - Add support to allow function body graph to rely on multiple external opreator sets.\n    /// - Add a list to promote inference graph's initializers to global and\n    ///    mutable variables. Global variables are visible in all graphs of the\n    ///    stored models.\n    /// - Add message TrainingInfoProto to store initialization\n    ///    method and training algorithm. The execution of TrainingInfoProto\n    ///    can modify the values of mutable variables.\n    /// - Implicitly add inference graph into each TrainingInfoProto's algorithm.\n    IrVersion202058 = 7,\n    /// IR VERSION 8 published on <TBD>\n    /// Introduce TypeProto.SparseTensor\n    /// Introduce TypeProto.Optional\n    /// Added a list of FunctionProtos local to the model\n    /// Deprecated since_version and operator status from FunctionProto\n    IrVersion = 8,\n}\nimpl Version {\n    /// String value of the enum field names used in the ProtoBuf definition.\n    ///\n    /// The values are not transformed in any way and thus are considered stable\n    /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n    pub fn as_str_name(&self) -> &'static str {\n        match self {\n            Version::StartVersion => \"_START_VERSION\",\n            Version::IrVersion20171010 => \"IR_VERSION_2017_10_10\",\n            Version::IrVersion20171030 => \"IR_VERSION_2017_10_30\",\n            Version::IrVersion2017113 => \"IR_VERSION_2017_11_3\",\n            Version::IrVersion2019122 => \"IR_VERSION_2019_1_22\",\n            Version::IrVersion2019318 => \"IR_VERSION_2019_3_18\",\n            Version::IrVersion2019919 => \"IR_VERSION_2019_9_19\",\n            Version::IrVersion202058 => \"IR_VERSION_2020_5_8\",\n            Version::IrVersion => \"IR_VERSION\",\n        }\n    }\n}\n/// Operator/function status.\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n#[repr(i32)]\npub enum OperatorStatus {\n    Experimental = 0,\n    Stable = 1,\n}\nimpl OperatorStatus {\n    /// String value of the enum field names used in the ProtoBuf definition.\n    ///\n    /// The values are not transformed in any way and thus are considered stable\n    /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n    pub fn as_str_name(&self) -> &'static str {\n        match self {\n            OperatorStatus::Experimental => \"EXPERIMENTAL\",\n            OperatorStatus::Stable => \"STABLE\",\n        }\n    }\n}\n"
  },
  {
    "path": "onnx/src/tensor.rs",
    "content": "use crate::data_resolver::ModelDataResolver;\nuse crate::model::ParsingContext;\nuse crate::pb::tensor_proto::DataType;\nuse crate::pb::*;\nuse prost::Message;\nuse std::convert::{TryFrom, TryInto};\nuse std::path::PathBuf;\nuse tract_hir::internal::*;\n\nimpl TryFrom<DataType> for DatumType {\n    type Error = TractError;\n    fn try_from(t: DataType) -> TractResult<DatumType> {\n        match t {\n            DataType::Bool => Ok(DatumType::Bool),\n            DataType::Uint8 => Ok(DatumType::U8),\n            DataType::Uint16 => Ok(DatumType::U16),\n            DataType::Uint32 => Ok(DatumType::U32),\n            DataType::Uint64 => Ok(DatumType::U64),\n            DataType::Int8 => Ok(DatumType::I8),\n            DataType::Int16 => Ok(DatumType::I16),\n            DataType::Int32 => Ok(DatumType::I32),\n            DataType::Int64 => Ok(DatumType::I64),\n            DataType::Float16 => Ok(DatumType::F16),\n            DataType::Float => Ok(DatumType::F32),\n            DataType::Double => Ok(DatumType::F64),\n            DataType::String => Ok(DatumType::String),\n            _ => bail!(\"Unknown DatumType {:?}\", t),\n        }\n    }\n}\n\npub fn translate_inference_fact(\n    ctx: &ParsingContext,\n    t: &type_proto::Tensor,\n    include_unknown_symbols: bool,\n) -> TractResult<InferenceFact> {\n    let mut fact = InferenceFact::default();\n    fact = fact.with_datum_type(DataType::try_from(t.elem_type).unwrap().try_into()?);\n    if let Some(shape) = &t.shape {\n        let shape: TVec<DimFact> = shape\n            .dim\n            .iter()\n            .map(|d| -> TractResult<DimFact> {\n                match &d.value {\n                    Some(tensor_shape_proto::dimension::Value::DimValue(v)) if *v >= 0 => {\n                        Ok(DimFact::from(v.to_dim()))\n                    }\n                    Some(tensor_shape_proto::dimension::Value::DimParam(v)) => {\n                        if v == \"?\" || (v.starts_with(\"unk__\") && !include_unknown_symbols) {\n                            Ok(DimFact::default())\n                        } else if let Ok(dim) = parse_tdim(&ctx.template.symbols, v) {\n                            Ok(DimFact::from(dim))\n                        } else {\n                            // Non-standard dim_param (e.g. sympy expressions from torch dynamo):\n                            // treat as unknown and let tract infer from the graph.\n                            log::debug!(\n                                \"Could not parse dim_param `{v}` as TDim, treating as unknown\"\n                            );\n                            Ok(DimFact::default())\n                        }\n                    }\n                    _ => Ok(DimFact::default()),\n                }\n            })\n            .collect::<TractResult<_>>()?;\n        fact = fact.with_shape(ShapeFactoid::closed(shape));\n    }\n    Ok(fact)\n}\n\nfn get_external_resources(\n    provider: &dyn ModelDataResolver,\n    t: &TensorProto,\n    path: &str,\n) -> TractResult<Vec<u8>> {\n    let mut tensor_data: Vec<u8> = Vec::new();\n    trace!(\"number of external file needed for this tensor: {}\", t.external_data.len());\n    let location = t\n        .external_data\n        .iter()\n        .find(|it| it.key == \"location\")\n        .map(|it| it.value.as_str())\n        .context(\"Could not find external data location\")?;\n\n    let offset: usize = t\n        .external_data\n        .iter()\n        .find(|it| it.key == \"offset\")\n        .map(|it| it.value.parse())\n        .transpose()\n        .context(\"Error while parsing offset value on external data description\")?\n        .unwrap_or(0);\n\n    let length: Option<usize> = t\n        .external_data\n        .iter()\n        .find(|it| it.key == \"length\")\n        .map(|it| it.value.parse())\n        .transpose()\n        .context(\"Error while parsing length value on external data description\")?;\n\n    let p = PathBuf::from(path).join(location);\n\n    trace!(\"external file detected: {p:?}, offset {offset:?}, length: {length:?}\");\n    provider.read_bytes_from_path(&mut tensor_data, &p, offset, length)?;\n    trace!(\"external file loaded\");\n    Ok(tensor_data)\n}\n\nfn create_tensor(shape: Vec<usize>, dt: DatumType, data: &[u8]) -> TractResult<Tensor> {\n    unsafe {\n        match dt {\n            DatumType::U8 => Tensor::from_raw::<u8>(&shape, data),\n            DatumType::U16 => Tensor::from_raw::<u16>(&shape, data),\n            DatumType::U32 => Tensor::from_raw::<u32>(&shape, data),\n            DatumType::U64 => Tensor::from_raw::<u64>(&shape, data),\n            DatumType::I8 => Tensor::from_raw::<i8>(&shape, data),\n            DatumType::I16 => Tensor::from_raw::<i16>(&shape, data),\n            DatumType::I32 => Tensor::from_raw::<i32>(&shape, data),\n            DatumType::I64 => Tensor::from_raw::<i64>(&shape, data),\n            DatumType::F16 => Tensor::from_raw::<f16>(&shape, data),\n            DatumType::F32 => Tensor::from_raw::<f32>(&shape, data),\n            DatumType::F64 => Tensor::from_raw::<f64>(&shape, data),\n            DatumType::Bool => Ok(Tensor::from_raw::<u8>(&shape, data)?\n                .into_plain_array::<u8>()?\n                .mapv(|x| x != 0)\n                .into()),\n            _ => unimplemented!(\"FIXME, raw tensor loading\"),\n        }\n    }\n}\n\npub fn load_tensor(\n    provider: &dyn ModelDataResolver,\n    t: &TensorProto,\n    path: Option<&str>,\n) -> TractResult<Tensor> {\n    let dt = DataType::try_from(t.data_type).unwrap().try_into()?;\n    let shape: Vec<usize> = t.dims.iter().map(|&i| i as usize).collect();\n    // detect if the tensor is rather in an external file than inside the onnx file directly\n    let is_external = t.data_location.is_some()\n        && t.data_location == Some(tensor_proto::DataLocation::External as i32);\n    if t.raw_data.len() > 0 {\n        create_tensor(shape, dt, &t.raw_data)\n    } else if is_external {\n        if let Some(model_path) = path {\n            // external files will be loaded and fed to the tensor if necessary\n            let external_data = get_external_resources(provider, t, model_path)?;\n            create_tensor(shape, dt, &external_data)\n        } else {\n            bail!(\n                \"no model path was specified in the parsing context, yet external data was detected. aborting\"\n            );\n        }\n    } else {\n        use tract_ndarray::Array;\n        let it = match dt {\n            DatumType::Bool => {\n                Array::from_shape_vec(&*shape, t.int32_data.iter().map(|&x| x != 0).collect())?\n                    .into()\n            }\n            DatumType::U8 => {\n                Array::from_shape_vec(&*shape, t.int32_data.iter().map(|&x| x as u8).collect())?\n                    .into()\n            }\n            DatumType::U16 => {\n                Array::from_shape_vec(&*shape, t.int32_data.iter().map(|&x| x as u16).collect())?\n                    .into()\n            }\n            DatumType::U32 => Array::from_shape_vec(&*shape, t.int32_data.to_vec())?.into(),\n            DatumType::U64 => Array::from_shape_vec(&*shape, t.int64_data.to_vec())?.into(),\n            DatumType::I8 => {\n                Array::from_shape_vec(&*shape, t.int32_data.iter().map(|&x| x as i8).collect())?\n                    .into()\n            }\n            DatumType::I16 => {\n                Array::from_shape_vec(&*shape, t.int32_data.iter().map(|&x| x as i16).collect())?\n                    .into()\n            }\n            DatumType::I32 => Array::from_shape_vec(&*shape, t.int32_data.to_vec())?.into(),\n            DatumType::I64 => Array::from_shape_vec(&*shape, t.int64_data.to_vec())?.into(),\n            DatumType::F16 => Array::from_shape_vec(\n                &*shape,\n                t.int32_data.iter().map(|&x| f16::from_bits(x as u16)).collect(),\n            )?\n            .into(),\n            DatumType::F32 => Array::from_shape_vec(&*shape, t.float_data.to_vec())?.into(),\n            DatumType::F64 => Array::from_shape_vec(&*shape, t.double_data.to_vec())?.into(),\n            DatumType::String => {\n                let strings = t\n                    .string_data\n                    .iter()\n                    .cloned()\n                    .map(String::from_utf8)\n                    .collect::<Result<Vec<String>, _>>()\n                    .context(\"Invalid UTF8 buffer\")?;\n                Array::from_shape_vec(&*shape, strings)?.into()\n            }\n            _ => unimplemented!(\"FIXME, struct tensor loading: {:?}\", dt),\n        };\n        Ok(it)\n    }\n}\n\npub fn proto_from_reader<R: ::std::io::Read>(mut r: R) -> TractResult<TensorProto> {\n    let mut v = vec![];\n    r.read_to_end(&mut v)?;\n    let b = bytes::Bytes::from(v);\n    TensorProto::decode(b).context(\"Can not parse protobuf input\")\n}\n"
  },
  {
    "path": "onnx/test_cases/byte_sb_bidi_lstm/README.md",
    "content": "# Byte-level sentence boundary detection with a bidirectional LSTM\n\nSee [here](https://app.wandb.ai/bminixhofer/nnsplit/runs/3ty2i90r/overview) for the code used to generate `model.onnx`.\n"
  },
  {
    "path": "onnx/test_cases/byte_sb_bidi_lstm/generate_io.py",
    "content": "import onnxruntime\nimport numpy as np\n\nn_pad = 5\ntexts = [\n    \"Das ist ein Test. Das ist noch ein Test.\",\n    \"Das ist ein weiterer Test. Und ein zweiter Satz.\",\n]\nboundaries = [[17, 39], [26, 47]]\n\n# pad text by zeros and encode as utf-8 bytes\nencoded = [[0] * n_pad + list(x.encode(\"utf-8\")) + [0] * n_pad for x in texts]\n\n# pda to same length with zeros at the end\nmax_length = max(len(x) for x in encoded)\npadded = [np.pad(x, ((0, max(max_length - len(x), 0)),)) for x in encoded]\ninputs = np.stack(padded, 0).astype(np.uint8)\n\nsess = onnxruntime.InferenceSession(\"model.onnx\")\ninput_name = sess.get_inputs()[0].name\n\n# shape batch x len x 2\n# value 1 in last dimension are token boundaries, value 0 are sentence boundaries\n# outputs are logits, in practice there would be a sigmoid afterwards\noutputs = sess.run(None, {input_name: inputs})[0]\n\nassert len(outputs) == len(texts)\n\nfor i in range(len(outputs)):\n    assert (np.where(outputs[i, :, 0] > 0)[0] - n_pad).tolist() == boundaries[i]\n\nnp.savez_compressed(open(\"io.npz\", \"wb\"), input=inputs, Add_26=outputs)\n"
  },
  {
    "path": "onnx/test_cases/deconv_group/vars.sh",
    "content": ""
  },
  {
    "path": "onnx/test_cases/lgbm_classifier_tensor/generate_io.py",
    "content": "import numpy as np\nfrom sklearn import datasets\nfrom lightgbm.sklearn import LGBMClassifier\nfrom hummingbird.ml import convert\nimport onnxruntime\nimport torch\n\nx, y = datasets.load_wine(return_X_y=True)\nx = x.astype(np.float32)\n\nmodel = LGBMClassifier(n_estimators=5)\nmodel.fit(x, y)\npreds = model.predict_proba(x)\n\npytorch_model = convert(model, \"pytorch\")\n\ntorch.onnx.export(\n    pytorch_model.model,\n    (torch.from_numpy(x)),\n    \"model.onnx\",\n    input_names=[\"input\"],\n    output_names=[\"output\", \"probabilities\"],\n    dynamic_axes={\n        \"input\": {0: \"batch\"},\n        \"output\": {0: \"batch\"},\n        \"probabilities\": {0: \"batch\"},\n    },\n)\n\nnp.savez_compressed(\n    open(\"io.npz\", \"wb\"), input=x[:1], probabilities=preds[:1],\n)\n\n# sanity check - onnxruntime inference\n\nsess = onnxruntime.InferenceSession(\"model.onnx\")\noutputs = sess.run([\"probabilities\"], {\"input\": x[:1]})[0]\n\nassert np.allclose(outputs, preds[:1])\n"
  },
  {
    "path": "onnx/test_cases/lgbm_classifier_tensor/vars.sh",
    "content": "IGNORE=\nOPTIONS=\"--output-node probabilities\"\n"
  },
  {
    "path": "onnx/test_cases/lgbm_regressor_tensor/generate_io.py",
    "content": "import numpy as np\nfrom sklearn import datasets\nfrom lightgbm.sklearn import LGBMRegressor\nfrom hummingbird.ml import convert\nimport onnxruntime\nimport torch\n\nx, y = datasets.load_wine(return_X_y=True)\nx = x.astype(np.float32)\n\nmodel = LGBMRegressor(n_estimators=10)\nmodel.fit(x, y)\npreds = model.predict(x)\n\npytorch_model = convert(model, \"pytorch\")\n\ntorch.onnx.export(\n    pytorch_model.model,\n    (torch.from_numpy(x)),\n    \"model.onnx\",\n    input_names=[\"input\"],\n    output_names=[\"output\"],\n    dynamic_axes={\"input\": {0: \"batch\"}, \"output\": {0: \"batch\"}},\n)\n\nnp.savez_compressed(\n    open(\"io.npz\", \"wb\"), input=x[:1], output=preds[:1],\n)\n\n# sanity check - onnxruntime inference\n\nsess = onnxruntime.InferenceSession(\"model.onnx\")\noutputs = sess.run(None, {\"input\": x[:1]})[0][:, 0]\n\nassert np.allclose(outputs, preds[:1])\n"
  },
  {
    "path": "onnx/test_cases/lgbm_regressor_tensor/vars.sh",
    "content": "IGNORE=\"plain decl opti nnef\"\n"
  },
  {
    "path": "onnx/test_cases/qlstm_3-2-3_T3_S1/vars.sh",
    "content": "left_context=0\nright_context=0\nsubsampling=1\nIGNORE=nnef\n"
  },
  {
    "path": "onnx/test_cases/qrelu_1/vars.sh",
    "content": "left_context=0\nright_context=0\nsubsampling=1\nIGNORE=nnef\n"
  },
  {
    "path": "onnx/test_cases/qrelu_2/vars.sh",
    "content": "left_context=0\nright_context=0\nsubsampling=1\nIGNORE=nnef\n"
  },
  {
    "path": "onnx/test_cases/qsigmoid_1/vars.sh",
    "content": "left_context=0\nright_context=0\nsubsampling=1\nIGNORE=nnef\n"
  },
  {
    "path": "onnx/test_cases/qsigmoid_2/vars.sh",
    "content": "left_context=0\nright_context=0\nsubsampling=1\nIGNORE=nnef\n"
  },
  {
    "path": "onnx/test_cases/qtanh_1/vars.sh",
    "content": "left_context=0\nright_context=0\nsubsampling=1\nIGNORE=nnef\n"
  },
  {
    "path": "onnx/test_cases/qtanh_2/vars.sh",
    "content": "left_context=0\nright_context=0\nsubsampling=1\nIGNORE=nnef\n"
  },
  {
    "path": "onnx/test_cases/qtdnn_10x5_101_i32_biases/vars.sh",
    "content": "left_context=1\nright_context=1\nsubsampling=1\nIGNORE=nnef\n"
  },
  {
    "path": "onnx/test_cases/run_all.sh",
    "content": "#!/bin/bash\n\nset -e\n\nif [ -z \"$CACHEDIR\" ]\nthen\n    CACHEDIR=`dirname $0`/../../.cached\nfi\n\nTEST_CASE_DIR=$(dirname $0)\nFAILURES=\"\"\nFAILED=()\n\nif [ \"$#\" -gt 0 ]\nthen\n    TEST_CASES=\"$@\"\nelse\n    TEST_CASES=\"$TEST_CASE_DIR/*\"\nfi\n\n: ${TRACT_RUN:=cargo run -p tract-cli $CARGO_OPTS --}\n\nfor tc in $TEST_CASES\ndo\n    if [ ! -e \"$tc/vars.sh\" ]\n    then\n        continue\n    fi\n    unset OPTIONS IGNORE MODEL left_context right_context subsampling\n    . $tc/vars.sh\n    for file in $CACHE_FILES\n    do\n        $TEST_CASE_DIR/../../.travis/cache_file.sh $file\n    done\n    : ${MODEL:=$tc/model.onnx}\n    for pass in plain decl opti nnef\n    do\n        printf \"$tc ($pass) \"\n        if [[ $IGNORE == *$pass* ]]\n        then\n            printf \"\\e[93mignored\\e[39m\\n\"\n            continue\n        fi\n        case $pass in\n            plain) opti=\"--pass incorporate\" ;;\n            decl) opti=\"\" ;;\n            opti) opti=\"-O\" ;;\n            nnef) opti=\"--nnef-cycle --nnef-tract-core\" ;;\n        esac\n        options=\"$OPTIONS\"\n        if [ -n \"$left_context\" -a \"$left_context\" != \"0\" ]\n        then\n            options=\"$options --edge-left-context $left_context\"\n        fi\n        if [ -n \"$right_context\" -a \"$right_context\" != \"0\" ]\n        then\n            options=\"$options --edge-right-context $right_context\"\n        fi\n        cmd=\"$TRACT_RUN \\\n            $MODEL \\\n            --input-facts-from-bundle $tc/io.npz \\\n            --onnx-ignore-output-shapes \\\n            $options \\\n            $opti \\\n            run \\\n            --input-from-bundle $tc/io.npz \\\n            --assert-output-bundle $tc/io.npz\"\n\n        if $($cmd 2> /dev/null > /dev/null)\n        then\n            printf \"\\e[92mOK\\e[39m\\n\"\n        else\n            printf \"\\e[91mFAIL\\e[39m\\n\"\n            FAILED+=(\"$cmd\")\n            FAILURES=\"$FAILURES $tc\"\n        fi\n    done\ndone\n\nif [ -n \"$FAILURES\" ]\nthen\n    echo \n    printf \"    \\e[91m$(echo $FAILURES | wc -w) FAILURES\\e[39m\\n\"\n    echo\nfi\n\nfor cmd in \"${FAILED[@]}\"\ndo\n    echo $cmd\ndone\n\n[ -z \"$FAILURES\" ]\n"
  },
  {
    "path": "onnx/test_cases/tinyyolov2/vars.sh",
    "content": "CACHE_FILES=tinyyolov2-7-model.onnx\nMODEL=$CACHEDIR/tinyyolov2-7-model.onnx\nIGNORE=\"nnef\"\n"
  },
  {
    "path": "onnx/test_cases/transformer-mlm/generate_io.py",
    "content": "import torch\nimport numpy as np\nfrom transformers import AutoTokenizer, AutoModelForMaskedLM\nimport onnxruntime\n\nmodel_name = \"distilbert-base-uncased\"\nmodel_path = \"model.onnx\"\n\ntext = \"tract is a machine [MASK] library.\"\nfiller = \"learning\"\n\ntokenizer = AutoTokenizer.from_pretrained(model_name)\nmodel = AutoModelForMaskedLM.from_pretrained(model_name)\n\nencoded = tokenizer.encode_plus(text)\nmask_idx = encoded[\"input_ids\"].index(tokenizer.mask_token_id)\n\ninput_ids = torch.tensor([encoded[\"input_ids\"]], dtype=torch.long)\nattention_mask = torch.tensor([encoded[\"attention_mask\"]], dtype=torch.long)\n\ntorch.onnx.export(\n    model,\n    (input_ids, attention_mask),\n    model_path,\n    input_names=[\"input_ids\", \"attention_mask\"],\n    output_names=[\"output\"],\n    dynamic_axes={\n        \"input_ids\": {0: \"batch\", 1: \"seq\"},\n        \"attention_mask\": {0: \"batch\", 1: \"seq\"},\n        \"output\": {0: \"batch\", 1: \"seq\"},\n    },\n)\n\nsess = onnxruntime.InferenceSession(model_path)\n\noutputs = sess.run(\n    None, {\"input_ids\": input_ids.numpy(), \"attention_mask\": attention_mask.numpy()}\n)[0]\nassert tokenizer.convert_ids_to_tokens(int(np.argmax(outputs[0, mask_idx]))) == filler\n\nnp.savez_compressed(\n    open(\"io.npz\", \"wb\"),\n    input_ids=input_ids.numpy(),\n    attention_mask=attention_mask.numpy(),\n    output=outputs,\n)\n"
  },
  {
    "path": "onnx/test_cases/transformer-mlm/vars.sh",
    "content": "CACHE_FILES=transformer-mlm-model.onnx\nMODEL=$CACHEDIR/transformer-mlm-model.onnx\nIGNORE=\"nnef\"\n"
  },
  {
    "path": "onnx/test_cases/xgboost_classifier_tree/generate_io.py",
    "content": "import numpy as np\nfrom sklearn import datasets\nfrom xgboost.sklearn import XGBClassifier\nfrom onnxmltools.convert.common import data_types\nimport onnxmltools\nimport onnx\nimport onnxruntime\n\nx, y = datasets.load_wine(return_X_y=True)\nx = x.astype(np.float32)\n\nmodel = XGBClassifier(n_estimators=10)\nmodel.fit(x, y)\npreds = model.predict_proba(x)\n\nonnx_model = onnxmltools.convert_xgboost(\n    model, initial_types=[(\"input\", data_types.FloatTensorType([None, x.shape[1]]))],\n)\n\nonnx.save(onnx_model, \"model.onnx\")\n\nnp.savez_compressed(\n    open(\"io.npz\", \"wb\"), input=x[:1], probabilities=preds[:1],\n)\n\n# sanity check - onnxruntime inference\n\nsess = onnxruntime.InferenceSession(\"model.onnx\")\noutputs = sess.run([\"probabilities\"], {\"input\": x[:1]})[0]\n\nassert np.allclose(outputs, preds[:1])\n"
  },
  {
    "path": "onnx/test_cases/xgboost_classifier_tree/vars.sh",
    "content": "IGNORE=\nOPTIONS=\"--output-node probabilities --nnef-tract-onnx\"\n"
  },
  {
    "path": "onnx/test_cases/xgboost_regressor_tree/generate_io.py",
    "content": "import numpy as np\nfrom sklearn import datasets\nfrom xgboost.sklearn import XGBRegressor\nfrom onnxmltools.convert.common import data_types\nimport onnxmltools\nimport onnx\nimport onnxruntime\n\nx, y = datasets.load_wine(return_X_y=True)\nx = x.astype(np.float32)\n\nmodel = XGBRegressor(n_estimators=10)\nmodel.fit(x, y)\npreds = model.predict(x)\n\nonnx_model = onnxmltools.convert_xgboost(\n    model, initial_types=[(\"input\", data_types.FloatTensorType([None, x.shape[1]]))],\n)\n\nonnx.save(onnx_model, \"model.onnx\")\n\nnp.savez_compressed(\n    open(\"io.npz\", \"wb\"), input=x[:1], variable=preds[:1],\n)\n\n# sanity check - onnxruntime inference\n\nsess = onnxruntime.InferenceSession(\"model.onnx\")\noutputs = sess.run(None, {\"input\": x[:1]})[0][:, 0]\n\nassert np.allclose(outputs, preds[:1])\n"
  },
  {
    "path": "onnx/test_cases/xgboost_regressor_tree/vars.sh",
    "content": "IGNORE=\"plain decl opti nnef\"\n"
  },
  {
    "path": "onnx-opl/Cargo.toml",
    "content": "[package]\nname = \"tract-onnx-opl\"\nversion = \"0.23.0-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [\"TensorFlow\", \"NeuralNetworks\", \"ONNX\"]\ncategories = [\"science\"]\nautobenches = false\nedition = \"2024\"\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\ndyn-eq.workspace = true\ngetrandom.workspace = true\nlog.workspace = true\nrand.workspace = true\nrand_distr.workspace = true\nrustfft.workspace = true\ntract-nnef.workspace = true\ntract-extra.workspace = true\n\n[dev-dependencies]\nenv_logger.workspace = true\n\n[features]\ndefault = []\ngetrandom-js = [\"getrandom/wasm_js\"]\n"
  },
  {
    "path": "onnx-opl/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "onnx-opl/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "onnx-opl/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "onnx-opl/src/grid_sample.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef::tract_core::ops::math::round_ties_to_even;\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_onnx_grid_sample\",\n        &parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        load,\n    );\n    registry.register_dumper(dump);\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum InterpolationMode {\n    Bilinear,\n    Nearest,\n    Bicubic,\n}\n\nimpl InterpolationMode {\n    fn as_str(&self) -> &'static str {\n        match self {\n            InterpolationMode::Bilinear => \"bilinear\",\n            InterpolationMode::Nearest => \"nearest\",\n            InterpolationMode::Bicubic => \"bicubic\",\n        }\n    }\n\n    fn from_str(s: &str) -> TractResult<Self> {\n        Ok(match s {\n            \"bilinear\" => InterpolationMode::Bilinear,\n            \"nearest\" => InterpolationMode::Nearest,\n            \"bicubic\" => InterpolationMode::Bicubic,\n            _ => bail!(\"Unsupported GridSample mode: {}\", s),\n        })\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum PaddingMode {\n    Zeros,\n    Border,\n    Reflection,\n}\n\nimpl PaddingMode {\n    fn as_str(&self) -> &'static str {\n        match self {\n            PaddingMode::Zeros => \"zeros\",\n            PaddingMode::Border => \"border\",\n            PaddingMode::Reflection => \"reflection\",\n        }\n    }\n\n    fn from_str(s: &str) -> TractResult<Self> {\n        Ok(match s {\n            \"zeros\" => PaddingMode::Zeros,\n            \"border\" => PaddingMode::Border,\n            \"reflection\" => PaddingMode::Reflection,\n            _ => bail!(\"Unsupported GridSample padding_mode: {}\", s),\n        })\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct GridSample {\n    pub mode: InterpolationMode,\n    pub padding_mode: PaddingMode,\n    pub align_corners: bool,\n}\n\nimpl GridSample {\n    fn denormalize(&self, coord: f32, size: usize) -> f32 {\n        if self.align_corners {\n            (coord + 1.0) / 2.0 * (size as f32 - 1.0)\n        } else {\n            ((coord + 1.0) * size as f32 - 1.0) / 2.0\n        }\n    }\n\n    fn bounds(&self, size: usize) -> (f32, f32) {\n        if self.align_corners { (0.0, size as f32 - 1.0) } else { (-0.5, size as f32 - 0.5) }\n    }\n\n    fn pixel_at_nd(\n        &self,\n        x: &tract_ndarray::ArrayViewD<'_, f32>,\n        batch: usize,\n        channel: usize,\n        coords: &[isize],\n        spatial_sizes: &[usize],\n    ) -> f32 {\n        match self.padding_mode {\n            PaddingMode::Zeros => {\n                for (&c, &s) in coords.iter().zip(spatial_sizes.iter()) {\n                    if c < 0 || c >= s as isize {\n                        return 0.0;\n                    }\n                }\n                let mut idx = vec![batch, channel];\n                idx.extend(coords.iter().map(|&c| c as usize));\n                x[idx.as_slice()]\n            }\n            PaddingMode::Border => {\n                let mut idx = vec![batch, channel];\n                for (&c, &s) in coords.iter().zip(spatial_sizes.iter()) {\n                    idx.push((c.max(0) as usize).min(s - 1));\n                }\n                x[idx.as_slice()]\n            }\n            PaddingMode::Reflection => {\n                let mut idx = vec![batch, channel];\n                for (&c, &s) in coords.iter().zip(spatial_sizes.iter()) {\n                    let (lo, hi) = self.bounds(s);\n                    idx.push(gs_reflect(c as f32, lo, hi) as usize);\n                }\n                x[idx.as_slice()]\n            }\n        }\n    }\n\n    fn apply_padding(&self, coord: f32, lo: f32, hi: f32) -> f32 {\n        match self.padding_mode {\n            PaddingMode::Border => coord.clamp(0.0, hi + lo),\n            PaddingMode::Reflection => gs_reflect(coord, lo, hi),\n            PaddingMode::Zeros => coord,\n        }\n    }\n\n    fn is_oob(&self, coords: &[f32], bounds: &[(f32, f32)]) -> bool {\n        coords.iter().zip(bounds.iter()).any(|(&c, &(lo, hi))| c < lo || c > hi)\n    }\n\n    fn pad_coords(&self, coords: &mut [f32], bounds: &[(f32, f32)]) {\n        for (c, &(lo, hi)) in coords.iter_mut().zip(bounds.iter()) {\n            *c = self.apply_padding(*c, lo, hi);\n        }\n    }\n\n    fn sample_nd(\n        &self,\n        x: &tract_ndarray::ArrayViewD<'_, f32>,\n        batch: usize,\n        channel: usize,\n        pixel_coords: &[f32],\n        spatial_sizes: &[usize],\n    ) -> f32 {\n        let ndim = pixel_coords.len();\n        let bounds: Vec<(f32, f32)> = spatial_sizes.iter().map(|&s| self.bounds(s)).collect();\n\n        match self.mode {\n            InterpolationMode::Nearest => {\n                let mut coords: Vec<f32> =\n                    pixel_coords.iter().map(|&c| round_ties_to_even(c)).collect();\n                if self.is_oob(&coords, &bounds) {\n                    self.pad_coords(&mut coords, &bounds);\n                }\n                let icoords: Vec<isize> = coords.iter().map(|&c| c as isize).collect();\n                self.pixel_at_nd(x, batch, channel, &icoords, spatial_sizes)\n            }\n            InterpolationMode::Bilinear => {\n                let mut coords: Vec<f32> = pixel_coords.to_vec();\n                if self.is_oob(&coords, &bounds) {\n                    self.pad_coords(&mut coords, &bounds);\n                }\n                let num_corners = 1 << ndim;\n                let mut result = 0.0f32;\n                for corner in 0..num_corners {\n                    let mut weight = 1.0f32;\n                    let mut icoords = Vec::with_capacity(ndim);\n                    for (d, &c) in coords.iter().enumerate() {\n                        let lo = c.floor() as isize;\n                        if (corner >> d) & 1 == 0 {\n                            icoords.push(lo);\n                            weight *= (lo + 1) as f32 - c;\n                        } else {\n                            icoords.push(lo + 1);\n                            weight *= c - lo as f32;\n                        }\n                    }\n                    result += weight * self.pixel_at_nd(x, batch, channel, &icoords, spatial_sizes);\n                }\n                result\n            }\n            InterpolationMode::Bicubic => {\n                assert!(ndim == 2, \"Bicubic interpolation only supports 2D spatial dimensions\");\n                let (mut px, mut py) = (pixel_coords[0], pixel_coords[1]);\n                if self.is_oob(&[px, py], &bounds) {\n                    px = self.apply_padding(px, bounds[0].0, bounds[0].1);\n                    py = self.apply_padding(py, bounds[1].0, bounds[1].1);\n                }\n                let x0 = px.floor() as isize - 1;\n                let y0 = py.floor() as isize - 1;\n                let dx = px - x0 as f32 - 1.0;\n                let dy = py - y0 as f32 - 1.0;\n\n                let mut p = [[0.0f32; 4]; 4];\n                for (h, row) in p.iter_mut().enumerate() {\n                    for (w, val) in row.iter_mut().enumerate() {\n                        *val = self.pixel_at_nd(\n                            x,\n                            batch,\n                            channel,\n                            &[x0 + w as isize, y0 + h as isize],\n                            spatial_sizes,\n                        );\n                    }\n                }\n                bicubic_interpolate(&p, dx, dy)\n            }\n        }\n    }\n}\n\nfn gs_reflect(x: f32, x_min: f32, x_max: f32) -> f32 {\n    let rng = x_max - x_min;\n    if rng == 0.0 {\n        return x_min;\n    }\n    if x < x_min {\n        let dx = x_min - x;\n        let n = (dx / rng) as i32;\n        let r = dx - n as f32 * rng;\n        if n % 2 == 0 { x_min + r } else { x_max - r }\n    } else if x > x_max {\n        let dx = x - x_max;\n        let n = (dx / rng) as i32;\n        let r = dx - n as f32 * rng;\n        if n % 2 == 0 { x_max - r } else { x_min + r }\n    } else {\n        x\n    }\n}\n\nfn bicubic_interpolate(p: &[[f32; 4]; 4], dx: f32, dy: f32) -> f32 {\n    let mut v = [0.0f32; 4];\n    let mut coeffs = [0.0f32; 4];\n    cubic_coeffs(dx, &mut coeffs);\n    for i in 0..4 {\n        v[i] =\n            coeffs[0] * p[i][0] + coeffs[1] * p[i][1] + coeffs[2] * p[i][2] + coeffs[3] * p[i][3];\n    }\n    cubic_coeffs(dy, &mut coeffs);\n    coeffs[0] * v[0] + coeffs[1] * v[1] + coeffs[2] * v[2] + coeffs[3] * v[3]\n}\n\nfn cubic_coeffs(x: f32, coeffs: &mut [f32; 4]) {\n    let a = -0.75f32;\n    let xp1 = x + 1.0;\n    let xm1 = 1.0 - x;\n    let xm2 = 2.0 - x;\n    coeffs[0] = ((a * xp1 - 5.0 * a) * xp1 + 8.0 * a) * xp1 - 4.0 * a;\n    coeffs[1] = ((a + 2.0) * x - (a + 3.0)) * x * x + 1.0;\n    coeffs[2] = ((a + 2.0) * xm1 - (a + 3.0)) * xm1 * xm1 + 1.0;\n    coeffs[3] = ((a * xm2 - 5.0 * a) * xm2 + 8.0 * a) * xm2 - 4.0 * a;\n}\n\nimpl Op for GridSample {\n    fn name(&self) -> StaticName {\n        \"GridSample\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for GridSample {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (x, grid) = args_2!(inputs);\n        let input_dt = x.datum_type();\n        let x_tensor = x.into_tensor();\n        let x_cow = x_tensor.cast_to::<f32>()?;\n        let x = x_cow.to_plain_array_view::<f32>()?;\n        let grid_tensor = grid.into_tensor();\n        let grid_cow = grid_tensor.cast_to::<f32>()?;\n        let grid = grid_cow.to_plain_array_view::<f32>()?;\n\n        let x_shape = x.shape();\n        let grid_shape = grid.shape();\n        let rank = x_shape.len();\n        let spatial_rank = rank - 2;\n\n        let n_batch = x_shape[0];\n        let n_channel = x_shape[1];\n        let spatial_sizes: Vec<usize> = x_shape[2..].to_vec();\n\n        let mut output_shape = vec![n_batch, n_channel];\n        output_shape.extend_from_slice(&grid_shape[1..rank - 1]);\n\n        let output = tract_ndarray::ArrayD::from_shape_fn(&*output_shape, |idx| -> f32 {\n            let batch = idx[0];\n            let channel = idx[1];\n            let out_spatial: Vec<usize> = (2..rank).map(|d| idx[d]).collect();\n\n            let mut grid_idx = vec![batch];\n            grid_idx.extend_from_slice(&out_spatial);\n            grid_idx.push(0);\n\n            let mut pixel_coords = Vec::with_capacity(spatial_rank);\n            for (d, &size) in spatial_sizes.iter().enumerate() {\n                *grid_idx.last_mut().unwrap() = spatial_rank - 1 - d;\n                let norm_coord = grid[grid_idx.as_slice()];\n                pixel_coords.push(self.denormalize(norm_coord, size));\n            }\n\n            self.sample_nd(&x, batch, channel, &pixel_coords, &spatial_sizes)\n        });\n\n        Ok(tvec!(output.into_tensor().cast_to_dt(input_dt)?.into_owned().into_tvalue()))\n    }\n}\n\nimpl TypedOp for GridSample {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let x_shape = &inputs[0].shape;\n        let grid_shape = &inputs[1].shape;\n        let rank = x_shape.len();\n\n        let mut output_shape: TVec<TDim> = tvec![x_shape[0].clone(), x_shape[1].clone()];\n        for d in 1..rank - 1 {\n            output_shape.push(grid_shape[d].clone());\n        }\n\n        Ok(tvec!(inputs[0].datum_type.fact(&output_shape)))\n    }\n}\n\nfn parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"input\"),\n        TypeName::Scalar.tensor().named(\"grid\"),\n        TypeName::String.named(\"mode\").default(\"bilinear\"),\n        TypeName::String.named(\"padding_mode\").default(\"zeros\"),\n        TypeName::Logical.named(\"align_corners\").default(false),\n    ]\n}\n\nfn dump(ast: &mut IntoAst, node: &TypedNode, op: &GridSample) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let grid = ast.mapping[&node.inputs[1]].clone();\n    Ok(Some(invocation(\n        \"tract_onnx_grid_sample\",\n        &[input, grid],\n        &[\n            (\"mode\", string(op.mode.as_str())),\n            (\"padding_mode\", string(op.padding_mode.as_str())),\n            (\"align_corners\", logical(op.align_corners)),\n        ],\n    )))\n}\n\nfn load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let grid = invocation.named_arg_as(builder, \"grid\")?;\n    let mode: String = invocation.named_arg_as(builder, \"mode\")?;\n    let padding_mode: String = invocation.named_arg_as(builder, \"padding_mode\")?;\n    let align_corners: bool = invocation.named_arg_as(builder, \"align_corners\")?;\n    let op = GridSample {\n        mode: InterpolationMode::from_str(&mode)?,\n        padding_mode: PaddingMode::from_str(&padding_mode)?,\n        align_corners,\n    };\n    builder.wire(op, &[input, grid])\n}\n"
  },
  {
    "path": "onnx-opl/src/lib.rs",
    "content": "#![allow(clippy::len_zero)]\n\nuse tract_nnef::internal::*;\n\npub mod grid_sample;\npub mod lrn;\npub mod ml;\npub mod multinomial;\npub mod non_max_suppression;\npub mod random;\npub mod resize;\n\npub trait WithOnnx {\n    fn with_onnx(self) -> Self;\n    fn enable_onnx(&mut self);\n}\n\nimpl WithOnnx for tract_nnef::framework::Nnef {\n    fn enable_onnx(&mut self) {\n        self.enable_tract_core();\n        self.registries.push(onnx_opl_registry());\n    }\n    fn with_onnx(mut self) -> Self {\n        self.enable_onnx();\n        self\n    }\n}\n\npub fn onnx_opl_registry() -> Registry {\n    let mut registry: Registry = Registry::new(\"tract_onnx\")\n        .with_doc(\n            \"Extension `tract_onnx` extends NNEF for supporting some corner case ONNX operators.\",\n        )\n        .with_doc(\"\")\n        .with_doc(\"Add `extension tract_onnx` to `graph.nnef`\");\n    grid_sample::register(&mut registry);\n    ml::register(&mut registry);\n    non_max_suppression::register(&mut registry);\n    multinomial::register(&mut registry);\n    random::register(&mut registry);\n    resize::register(&mut registry);\n    registry.register_dumper(lrn::dump);\n    registry.register_primitive(\n        \"tract_onnx_lrn\",\n        &lrn::parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        lrn::load,\n    );\n    registry\n}\n"
  },
  {
    "path": "onnx-opl/src/lrn.rs",
    "content": "use tract_ndarray::prelude::*;\nuse tract_nnef::internal::*;\n\n#[derive(Debug, Clone, Default, PartialEq)]\npub struct Lrn {\n    pub alpha: f32,\n    pub beta: f32,\n    pub bias: f32,\n    pub size: usize,\n}\nimpl Eq for Lrn {}\n\nimpl Lrn {\n    fn eval_t<T>(&self, input: TValue) -> TractResult<TVec<TValue>>\n    where\n        T: Datum + tract_num_traits::Float + ::std::iter::Sum,\n    {\n        let input = input.to_plain_array_view::<T>()?;\n        let channels = input.shape()[1];\n        let output = Array::from_shape_fn(input.shape(), |mut coords| {\n            let c = coords[1];\n            let x = input[&coords];\n            let c_min = c.saturating_sub((self.size - 1) / 2);\n            let c_max = (c + ((self.size - 1).divceil(2))).min(channels - 1);\n            let square_sum: T = (c_min..=c_max)\n                .map(|c| {\n                    coords[1] = c;\n                    input[&coords].powi(2)\n                })\n                .sum();\n            x / (T::from(self.bias).unwrap()\n                + T::from(self.alpha).unwrap() / T::from(self.size).unwrap() * square_sum)\n                .powf(T::from(self.beta).unwrap())\n        });\n        Ok(tvec!(output.into_tvalue()))\n    }\n}\n\nimpl Op for Lrn {\n    fn name(&self) -> StaticName {\n        \"Lrn\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Lrn {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        dispatch_floatlike!(Self::eval_t(input.datum_type())(self, input))\n    }\n}\n\nimpl TypedOp for Lrn {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n}\n\npub fn parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"input\"),\n        TypeName::Scalar.named(\"alpha\").default(0.0001),\n        TypeName::Scalar.named(\"beta\").default(0.75),\n        TypeName::Scalar.named(\"bias\").default(1.0),\n        TypeName::Integer.named(\"size\"),\n    ]\n}\n\npub fn dump(ast: &mut IntoAst, node: &TypedNode, lrn: &Lrn) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\n        \"tract_onnx_lrn\",\n        &[input],\n        &[\n            (\"alpha\", numeric(lrn.alpha)),\n            (\"beta\", numeric(lrn.beta)),\n            (\"bias\", numeric(lrn.bias)),\n            (\"size\", numeric(lrn.size)),\n        ],\n    )))\n}\n\npub fn load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let alpha = invocation.named_arg_as(builder, \"alpha\")?;\n    let beta = invocation.named_arg_as(builder, \"beta\")?;\n    let bias = invocation.named_arg_as(builder, \"bias\")?;\n    let size = invocation.named_arg_as(builder, \"size\")?;\n    let op = Lrn { alpha, beta, bias, size };\n    builder.wire(op, &[input])\n}\n"
  },
  {
    "path": "onnx-opl/src/ml/category_mapper.rs",
    "content": "use std::hash::*;\nuse tract_itertools::Itertools;\nuse tract_nnef::internal::*;\nuse tract_smallvec::SmallVec;\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_onnx_ml_direct_lookup\",\n        &parameters_direct_lookup(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        load_direct_lookup,\n    );\n    registry.register_primitive(\n        \"tract_onnx_ml_reverse_lookup\",\n        &parameters_reverse_lookup(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        load_reverse_lookup,\n    );\n    registry.register_dumper(dump_direct_lookup);\n    registry.register_dumper(dump_reverse_lookup);\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct DirectLookup {\n    values: Arc<Tensor>,\n    fallback_value: Arc<Tensor>,\n}\n\nimpl DirectLookup {\n    pub fn new(values: Arc<Tensor>, fallback_value: Arc<Tensor>) -> TractResult<DirectLookup> {\n        Ok(DirectLookup { values, fallback_value })\n    }\n\n    fn eval_t<T: Datum>(&self, input: &Tensor) -> TractResult<Tensor> {\n        let values = self.values.try_as_plain()?.as_slice::<T>()?;\n        let fallback_value = self.fallback_value.try_as_plain()?.to_scalar::<T>()?;\n        Ok(input\n            .to_plain_array_view::<i32>()?\n            .mapv(|ix| values.get(ix as usize).unwrap_or(fallback_value).clone())\n            .into_tensor())\n    }\n}\n\nimpl Op for DirectLookup {\n    fn name(&self) -> StaticName {\n        \"DirectLookup\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DirectLookup {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let output = dispatch_hash!(Self::eval_t(self.values.datum_type())(self, &input))?;\n        Ok(tvec!(output.into_tvalue()))\n    }\n}\n\nimpl TypedOp for DirectLookup {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        if self.values.datum_type() != self.fallback_value.datum_type() {\n            bail!(\n                \"values and fallback value should be of the same type, got {:?}, {:?}\",\n                self.values,\n                self.fallback_value\n            )\n        }\n        Ok(tvec!(self.values.datum_type().fact(inputs[0].shape.iter())))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        Ok(Some(AxisChangeConsequence::new(model, node, None, change)))\n    }\n\n    as_op!();\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct ReverseLookup {\n    keys: Arc<Tensor>,\n    index: HashMap<u64, SmallVec<[i32; 1]>>,\n    fallback_value: i32,\n}\n\n#[allow(clippy::manual_hash_one)]\nimpl ReverseLookup {\n    pub fn new(keys: Arc<Tensor>, fallback_value: i32) -> TractResult<ReverseLookup> {\n        unsafe fn new_t<T: Datum + Hash>(keys: &Tensor) -> HashMap<u64, SmallVec<[i32; 1]>> {\n            let keys = unsafe { keys.as_slice_unchecked::<T>() };\n            let mut hashmap = HashMap::<u64, SmallVec<[i32; 1]>>::default();\n            for (ix, k) in keys.iter().enumerate() {\n                let mut hasher = hashmap.hasher().build_hasher();\n                k.hash(&mut hasher);\n                let u = hasher.finish();\n                hashmap.entry(u).or_default().push(ix as i32);\n            }\n            hashmap\n        }\n        let index = unsafe { dispatch_hash!(new_t(keys.datum_type())(&keys)) };\n        Ok(ReverseLookup { index, keys, fallback_value })\n    }\n\n    unsafe fn search_t<T: Datum + Hash>(&self, needle: &T) -> Option<i32> {\n        let keys = unsafe { self.keys.as_slice_unchecked::<T>() };\n        let mut hasher = self.index.hasher().build_hasher();\n        needle.hash(&mut hasher);\n        let u = hasher.finish();\n        if let Some(candidates) = self.index.get(&u) {\n            for candidate in candidates {\n                if &keys[*candidate as usize] == needle {\n                    return Some(*candidate);\n                }\n            }\n        }\n        None\n    }\n\n    fn eval_t<T: Datum + Hash>(&self, input: &Tensor) -> TractResult<Tensor> {\n        unsafe {\n            let mut output = Tensor::uninitialized_dt(i32::datum_type(), input.shape())?;\n            for (i, o) in input\n                .try_as_plain()?\n                .as_slice::<T>()?\n                .iter()\n                .zip(output.as_slice_mut_unchecked::<i32>().iter_mut())\n            {\n                *o = self.search_t(i).unwrap_or(self.fallback_value);\n            }\n            Ok(output)\n        }\n    }\n}\n\nimpl Hash for ReverseLookup {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        self.keys.hash(state);\n        self.fallback_value.hash(state);\n        self.index.iter().sorted().for_each(|v| Hash::hash(&v, state));\n    }\n}\n\nimpl Op for ReverseLookup {\n    fn name(&self) -> StaticName {\n        \"ReverseLookup\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for ReverseLookup {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let output = dispatch_hash!(Self::eval_t(self.keys.datum_type())(self, &input))?;\n        Ok(tvec!(output.into_tvalue()))\n    }\n}\n\nimpl TypedOp for ReverseLookup {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(i32::fact(inputs[0].shape.iter())))\n    }\n\n    fn axes_mapping(\n        &self,\n        inputs: &[&TypedFact],\n        outputs: &[&TypedFact],\n    ) -> TractResult<AxesMapping> {\n        AxesMapping::natural(inputs, outputs)\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        Ok(Some(AxisChangeConsequence::new(model, node, None, change)))\n    }\n\n    as_op!();\n}\n\nfn parameters_direct_lookup() -> Vec<Parameter> {\n    vec![\n        TypeName::String.tensor().named(\"input\"),\n        TypeName::Scalar.tensor().named(\"values\"),\n        TypeName::Scalar.tensor().named(\"fallback\"),\n    ]\n}\n\nfn parameters_reverse_lookup() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"input\"),\n        TypeName::Scalar.tensor().named(\"keys\"),\n        TypeName::Scalar.named(\"fallback\"),\n    ]\n}\n\nfn dump_direct_lookup(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &DirectLookup,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let keys = ast.konst_variable(format!(\"{}.values\", node.name), &op.values)?;\n    let fallback = ast.konst_variable(format!(\"{}.fallback\", node.name), &op.fallback_value)?;\n    Ok(Some(invocation(\"tract_onnx_ml_direct_lookup\", &[input, keys, fallback], &[])))\n}\n\nfn dump_reverse_lookup(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ReverseLookup,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let values = ast.konst_variable(format!(\"{}.keys\", node.name), &op.keys)?;\n    Ok(Some(invocation(\n        \"tract_onnx_ml_reverse_lookup\",\n        &[input, values],\n        &[(\"fallback\", numeric(op.fallback_value))],\n    )))\n}\n\nfn load_direct_lookup(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let values: Arc<Tensor> = invocation.named_arg_as(builder, \"values\")?;\n    let fallback_value = invocation.named_arg_as(builder, \"fallback\")?;\n    let op = DirectLookup { fallback_value, values };\n    builder.wire(op, &[input])\n}\n\nfn load_reverse_lookup(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let keys: isize = invocation.named_arg_as(builder, \"keys\")?;\n    let fallback_value = invocation.named_arg_as(builder, \"fallback\")?;\n    let op = ReverseLookup::new(fallback_value, keys as i32)?;\n    builder.wire(op, &[input])\n}\n"
  },
  {
    "path": "onnx-opl/src/ml/mod.rs",
    "content": "use tract_nnef::internal::*;\n\npub mod category_mapper;\npub mod tree;\npub mod tree_ensemble_classifier;\n\npub use category_mapper::{DirectLookup, ReverseLookup};\n\npub fn register(registry: &mut Registry) {\n    category_mapper::register(registry);\n    tree_ensemble_classifier::register(registry);\n}\n"
  },
  {
    "path": "onnx-opl/src/ml/tree.rs",
    "content": "use std::convert::TryFrom;\nuse std::convert::TryInto;\nuse std::fmt::{self, Debug, Display};\nuse std::iter;\n\nuse tract_nnef::internal::*;\n\nuse tract_ndarray::{\n    Array1, Array2, ArrayD, ArrayView1, ArrayView2, ArrayViewD, ArrayViewMut1, Axis, Ix1, Ix2,\n};\n\nuse tract_num_traits::AsPrimitive;\n\nmacro_rules! ensure {\n    ($cond: expr, $($rest: expr),* $(,)?) => {\n        if !$cond {\n            bail!($($rest),*)\n        }\n    }\n}\n\n#[repr(u8)]\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum Cmp {\n    Equal = 1,\n    NotEqual = 2,\n    Less = 3,\n    Greater = 4,\n    LessEqual = 5,\n    GreaterEqual = 6,\n}\n\nimpl Cmp {\n    pub fn compare<T>(&self, x: T, y: T) -> bool\n    where\n        T: PartialOrd + Copy,\n    {\n        match self {\n            Cmp::LessEqual => x <= y,\n            Cmp::Less => x < y,\n            Cmp::GreaterEqual => x >= y,\n            Cmp::Greater => x > y,\n            Cmp::Equal => x == y,\n            Cmp::NotEqual => x != y,\n        }\n    }\n    pub fn to_u8(&self) -> u8 {\n        unsafe { std::mem::transmute(*self) }\n    }\n}\n\nimpl TryFrom<u8> for Cmp {\n    type Error = TractError;\n    fn try_from(value: u8) -> Result<Self, Self::Error> {\n        if (1..=5).contains(&value) {\n            unsafe { Ok(std::mem::transmute::<u8, Cmp>(value)) }\n        } else {\n            bail!(\"Invalid value for Cmp: {}\", value);\n        }\n    }\n}\n\nimpl Display for Cmp {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        f.write_str(match self {\n            Cmp::LessEqual => \"<=\",\n            Cmp::Less => \"<\",\n            Cmp::GreaterEqual => \">=\",\n            Cmp::Greater => \">\",\n            Cmp::Equal => \"==\",\n            Cmp::NotEqual => \"!=\",\n        })\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct TreeEnsembleData {\n    // u32, [Ntrees], root row of each tree in nodes array (in rows)\n    pub trees: Arc<Tensor>,\n    // u32, [_, 5],\n    // 5th number is flags: last byte is comparator, 0 for leaves, transmuted Cmp for the internal nodes\n    //                      is_nan is 0x100 bit\n    // intern nodes:    feature_id, true_id, false_id, value.to_bits(),\n    //                  comp | (0x100 if nan_is_true)\n    // leaves:          start row, end row in leaves array, 3 zeros for padding\n    pub nodes: Arc<Tensor>,\n    // categ,\n    pub leaves: Arc<Tensor>,\n}\n\nimpl Display for TreeEnsembleData {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let tree = self.trees.try_as_plain().unwrap().as_slice::<u32>().unwrap();\n        for t in 0..tree.len() {\n            let last_node = tree.get(t + 1).cloned().unwrap_or(self.nodes.len() as u32 / 5);\n            writeln!(f, \"Tree {}, nodes {:?}\", t, tree[t]..last_node)?;\n            for n in tree[t]..last_node {\n                unsafe {\n                    let node = self.get_unchecked(n as _);\n                    if let TreeNode::Leaf(leaf) = node {\n                        for vote in leaf.start_id..leaf.end_id {\n                            let cat =\n                                self.leaves.try_as_plain().unwrap().as_slice::<u32>().unwrap()\n                                    [vote * 2];\n                            let contrib =\n                                self.leaves.try_as_plain().unwrap().as_slice::<u32>().unwrap()\n                                    [vote * 2 + 1];\n                            let contrib = f32::from_bits(contrib);\n                            writeln!(f, \"{n} categ:{cat} add:{contrib}\")?;\n                        }\n                    } else {\n                        writeln!(f, \"{} {:?}\", n, self.get_unchecked(n as _))?;\n                    }\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl TreeEnsembleData {\n    unsafe fn get_unchecked(&self, node: usize) -> TreeNode {\n        let row = &unsafe { self.nodes.as_slice_unchecked::<u32>() }[node * 5..][..5];\n        if let Ok(cmp) = ((row[4] & 0xFF) as u8).try_into() {\n            let feature_id = row[0];\n            let true_id = row[1];\n            let false_id = row[2];\n            let value = f32::from_bits(row[3]);\n            let nan_is_true = (row[4] & 0x0100) != 0;\n            TreeNode::Branch(BranchNode { cmp, feature_id, value, true_id, false_id, nan_is_true })\n        } else {\n            TreeNode::Leaf(LeafNode { start_id: row[0] as usize, end_id: row[1] as usize })\n        }\n    }\n\n    unsafe fn get_leaf_unchecked<T>(&self, tree: usize, input: &ArrayView1<T>) -> LeafNode\n    where\n        T: AsPrimitive<f32>,\n    {\n        unsafe {\n            let mut node_id = self.trees.as_slice_unchecked::<u32>()[tree] as usize;\n            loop {\n                let node = self.get_unchecked(node_id);\n                match node {\n                    TreeNode::Branch(ref b) => {\n                        let feature = *input.uget(b.feature_id as usize);\n                        node_id = b.get_child_id(feature.as_());\n                    }\n                    TreeNode::Leaf(l) => return l,\n                }\n            }\n        }\n    }\n\n    unsafe fn eval_unchecked<A, T>(\n        &self,\n        tree: usize,\n        input: &ArrayView1<T>,\n        output: &mut ArrayViewMut1<f32>,\n        aggs: &mut [A],\n    ) where\n        A: AggregateFn,\n        T: AsPrimitive<f32>,\n    {\n        unsafe {\n            let leaf = self.get_leaf_unchecked(tree, input);\n            for leaf in self\n                .leaves\n                .to_array_view_unchecked::<u32>()\n                .outer_iter()\n                .skip(leaf.start_id)\n                .take(leaf.end_id - leaf.start_id)\n            {\n                let class_id = leaf[0] as usize;\n                let weight = f32::from_bits(leaf[1]);\n                let agg_fn = aggs.get_unchecked_mut(class_id);\n                agg_fn.aggregate(weight, output.uget_mut(class_id));\n            }\n        }\n    }\n}\n\n#[derive(Copy, Clone)]\nstruct BranchNode {\n    pub cmp: Cmp, // TODO: perf: most real forests have only 1 type of comparison\n    pub feature_id: u32,\n    pub value: f32,\n    pub true_id: u32,\n    pub false_id: u32,\n    pub nan_is_true: bool,\n}\n\nimpl std::fmt::Debug for BranchNode {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"if feat({}) {} {} then {} else {}\",\n            self.feature_id, self.cmp, self.value, self.true_id, self.false_id\n        )\n    }\n}\n\nimpl BranchNode {\n    pub fn get_child_id(&self, feature: f32) -> usize {\n        let condition =\n            if feature.is_nan() { self.nan_is_true } else { self.cmp.compare(feature, self.value) };\n        if condition { self.true_id as usize } else { self.false_id as usize }\n    }\n}\n\n#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]\nstruct LeafNode {\n    pub start_id: usize,\n    pub end_id: usize,\n}\n\n#[derive(Copy, Clone, Debug)]\nenum TreeNode {\n    Branch(BranchNode),\n    Leaf(LeafNode),\n}\n\npub trait AggregateFn: Default {\n    fn aggregate(&mut self, score: f32, total: &mut f32);\n\n    fn post_aggregate(&mut self, _total: &mut f32) {}\n}\n\n#[derive(Clone, Copy, Default, Debug)]\npub struct SumFn;\n\nimpl AggregateFn for SumFn {\n    fn aggregate(&mut self, score: f32, total: &mut f32) {\n        *total += score;\n    }\n}\n\n#[derive(Clone, Copy, Default, Debug)]\npub struct AvgFn {\n    count: usize,\n}\n\nimpl AggregateFn for AvgFn {\n    fn aggregate(&mut self, score: f32, total: &mut f32) {\n        *total += score;\n        self.count += 1;\n    }\n\n    fn post_aggregate(&mut self, total: &mut f32) {\n        if self.count > 1 {\n            *total /= self.count as f32;\n        }\n        self.count = 0;\n    }\n}\n\n#[derive(Clone, Copy, Default, Debug)]\npub struct MaxFn;\n\nimpl AggregateFn for MaxFn {\n    fn aggregate(&mut self, score: f32, total: &mut f32) {\n        *total = total.max(score);\n    }\n}\n\n#[derive(Clone, Copy, Default, Debug)]\npub struct MinFn;\n\nimpl AggregateFn for MinFn {\n    fn aggregate(&mut self, score: f32, total: &mut f32) {\n        *total = total.min(score);\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]\npub enum Aggregate {\n    #[default]\n    Sum,\n    Avg,\n    Max,\n    Min,\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct TreeEnsemble {\n    pub data: TreeEnsembleData,\n    pub max_used_feature: usize,\n    pub n_classes: usize,\n    pub aggregate_fn: Aggregate, // TODO: should this be an argument to eval()?\n}\n\nimpl TreeEnsemble {\n    pub fn build(\n        data: TreeEnsembleData,\n        max_used_feature: usize,\n        n_classes: usize,\n        aggregate_fn: Aggregate,\n    ) -> TractResult<Self> {\n        Ok(Self { data, max_used_feature, n_classes, aggregate_fn })\n    }\n\n    pub fn n_classes(&self) -> usize {\n        self.n_classes\n    }\n\n    unsafe fn eval_one_unchecked<A, T>(\n        &self,\n        input: &ArrayView1<T>,\n        output: &mut ArrayViewMut1<f32>,\n        aggs: &mut [A],\n    ) where\n        A: AggregateFn,\n        T: AsPrimitive<f32>,\n    {\n        unsafe {\n            for t in 0..self.data.trees.len() {\n                self.data.eval_unchecked(t, input, output, aggs)\n            }\n            for i in 0..self.n_classes {\n                aggs.get_unchecked_mut(i).post_aggregate(output.uget_mut(i));\n            }\n        }\n    }\n\n    pub fn check_n_features(&self, n_features: usize) -> TractResult<()> {\n        ensure!(\n            n_features > self.max_used_feature,\n            \"Invalid input shape: input has {} features, tree ensemble use feature #{}\",\n            n_features,\n            self.max_used_feature\n        );\n        Ok(())\n    }\n\n    fn eval_2d<A, T>(&self, input: &ArrayView2<T>) -> TractResult<Array2<f32>>\n    where\n        A: AggregateFn,\n        T: AsPrimitive<f32>,\n    {\n        self.check_n_features(input.shape()[1])?;\n        let n = input.shape()[0];\n        let mut output = Array2::zeros((n, self.n_classes));\n        let mut aggs: tract_smallvec::SmallVec<[A; 16]> =\n            iter::repeat_with(Default::default).take(self.n_classes).collect();\n        for i in 0..n {\n            unsafe {\n                self.eval_one_unchecked::<A, T>(\n                    &input.index_axis(Axis(0), i),\n                    &mut output.index_axis_mut(Axis(0), i),\n                    &mut aggs,\n                );\n            }\n        }\n        Ok(output)\n    }\n\n    fn eval_1d<A, T>(&self, input: &ArrayView1<T>) -> TractResult<Array1<f32>>\n    where\n        A: AggregateFn,\n        T: AsPrimitive<f32>,\n    {\n        self.check_n_features(input.len())?;\n        let mut output = Array1::zeros(self.n_classes);\n        let mut aggs: tract_smallvec::SmallVec<[A; 16]> =\n            iter::repeat_with(Default::default).take(self.n_classes).collect();\n        unsafe {\n            self.eval_one_unchecked::<A, T>(input, &mut output.view_mut(), &mut aggs);\n        }\n        Ok(output)\n    }\n\n    pub fn eval<'i, I, T>(&self, input: I) -> TractResult<ArrayD<f32>>\n    where\n        I: Into<ArrayViewD<'i, T>>, // TODO: accept generic dimensions, not just IxDyn\n        T: Datum + AsPrimitive<f32>,\n    {\n        let input = input.into();\n        if let Ok(input) = input.view().into_dimensionality::<Ix1>() {\n            Ok(match self.aggregate_fn {\n                Aggregate::Sum => self.eval_1d::<SumFn, T>(&input),\n                Aggregate::Avg => self.eval_1d::<AvgFn, T>(&input),\n                Aggregate::Min => self.eval_1d::<MinFn, T>(&input),\n                Aggregate::Max => self.eval_1d::<MaxFn, T>(&input),\n            }?\n            .into_dyn())\n        } else if let Ok(input) = input.view().into_dimensionality::<Ix2>() {\n            Ok(match self.aggregate_fn {\n                Aggregate::Sum => self.eval_2d::<SumFn, T>(&input),\n                Aggregate::Avg => self.eval_2d::<AvgFn, T>(&input),\n                Aggregate::Min => self.eval_2d::<MinFn, T>(&input),\n                Aggregate::Max => self.eval_2d::<MaxFn, T>(&input),\n            }?\n            .into_dyn())\n        } else {\n            bail!(\"Invalid input dimensionality for tree ensemble: {:?}\", input.shape());\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use tract_ndarray::prelude::*;\n\n    fn b(\n        node_offset: usize,\n        cmp: Cmp,\n        feat: usize,\n        v: f32,\n        left: usize,\n        right: usize,\n        nan_is_true: bool,\n    ) -> [u32; 5] {\n        [\n            feat as u32,\n            (node_offset + left) as u32,\n            (node_offset + right) as u32,\n            v.to_bits(),\n            cmp as u32 | if nan_is_true { 0x100 } else { 0 },\n        ]\n    }\n\n    fn l(leaf_offset: usize, start_id: usize, end_id: usize) -> [u32; 5] {\n        [(leaf_offset + start_id) as u32, (leaf_offset + end_id) as u32, 0, 0, 0]\n    }\n\n    fn w(categ: usize, weight: f32) -> [u32; 2] {\n        [categ as u32, weight.to_bits()]\n    }\n\n    fn generate_gbm_trees() -> TreeEnsembleData {\n        let trees = rctensor1(&[0u32, 5u32, 14, 21, 30, 41]);\n        let nodes = rctensor2(&[\n            b(0, Cmp::LessEqual, 2, 3.15, 1, 2, true),\n            b(0, Cmp::LessEqual, 1, 3.35, 3, 4, true),\n            l(0, 0, 1),\n            l(0, 1, 2),\n            l(0, 2, 3),\n            //\n            b(5, Cmp::LessEqual, 2, 1.8, 1, 2, true),\n            l(3, 0, 1),\n            b(5, Cmp::LessEqual, 3, 1.65, 3, 4, true),\n            b(5, Cmp::LessEqual, 2, 4.45, 5, 6, true),\n            b(5, Cmp::LessEqual, 2, 5.35, 7, 8, true),\n            l(3, 1, 2),\n            l(3, 2, 3),\n            l(3, 3, 4),\n            l(3, 4, 5),\n            //\n            b(14, Cmp::LessEqual, 3, 1.65, 1, 2, true),\n            b(14, Cmp::LessEqual, 2, 4.45, 3, 4, true),\n            b(14, Cmp::LessEqual, 2, 5.35, 5, 6, true),\n            l(8, 0, 1),\n            l(8, 1, 2),\n            l(8, 2, 3),\n            l(8, 3, 4),\n            //\n            b(21, Cmp::LessEqual, 2, 3.15, 1, 2, true),\n            b(21, Cmp::LessEqual, 1, 3.35, 3, 4, true),\n            b(21, Cmp::LessEqual, 2, 4.45, 5, 6, true),\n            l(12, 0, 1),\n            l(12, 1, 2),\n            l(12, 2, 3),\n            b(21, Cmp::LessEqual, 2, 5.35, 7, 8, true),\n            l(12, 3, 4),\n            l(12, 4, 5),\n            //\n            b(30, Cmp::LessEqual, 3, 0.45, 1, 2, true),\n            b(30, Cmp::LessEqual, 2, 1.45, 3, 4, true),\n            b(30, Cmp::LessEqual, 3, 1.65, 5, 6, true),\n            l(17, 0, 1),\n            l(17, 1, 2),\n            b(30, Cmp::LessEqual, 2, 4.45, 7, 8, true),\n            b(30, Cmp::LessEqual, 2, 5.35, 9, 10, true),\n            l(17, 2, 3),\n            l(17, 3, 4),\n            l(17, 4, 5),\n            l(17, 5, 6),\n            //\n            b(41, Cmp::LessEqual, 2, 4.75, 1, 2, true),\n            b(41, Cmp::LessEqual, 1, 2.75, 3, 4, true),\n            b(41, Cmp::LessEqual, 2, 5.15, 7, 8, true),\n            l(23, 0, 1),\n            b(41, Cmp::LessEqual, 2, 4.15, 5, 6, true),\n            l(23, 1, 2),\n            l(23, 2, 3),\n            l(23, 3, 4),\n            l(23, 4, 5),\n        ]);\n        assert_eq!(nodes.shape(), &[50, 5]);\n        let leaves = rctensor2(&[\n            w(0, -0.075),\n            w(0, 0.13928571),\n            w(0, 0.15),\n            //\n            w(1, -0.075),\n            w(1, 0.13548388),\n            w(1, 0.110869564),\n            w(1, -0.052500002),\n            w(1, -0.075),\n            //\n            w(2, -0.075),\n            w(2, -0.035869565),\n            w(2, 0.1275),\n            w(2, 0.15),\n            //\n            w(0, 0.12105576),\n            w(0, 0.1304589),\n            w(0, -0.07237862),\n            w(0, -0.07226522),\n            w(0, -0.07220469),\n            //\n            w(1, -0.07226842),\n            w(1, -0.07268012),\n            w(1, 0.119391434),\n            w(1, 0.097440675),\n            w(1, -0.049815115),\n            w(1, -0.07219931),\n            //\n            w(2, -0.061642267),\n            w(2, -0.0721846),\n            w(2, -0.07319043),\n            w(2, 0.076814815),\n            w(2, 0.1315959),\n        ]);\n        assert_eq!(leaves.shape(), &[28, 2]);\n        TreeEnsembleData { nodes, trees, leaves }\n    }\n\n    fn generate_gbm_ensemble() -> TreeEnsemble {\n        // converted manually from LightGBM, fitted on iris dataset\n        let trees = generate_gbm_trees();\n        TreeEnsemble::build(trees, 4, 3, Aggregate::Sum).unwrap()\n    }\n\n    fn generate_gbm_input() -> Array2<f32> {\n        arr2(&[\n            [5.1, 3.5, 1.4, 0.2],\n            [5.4, 3.7, 1.5, 0.2],\n            [5.4, 3.4, 1.7, 0.2],\n            [4.8, 3.1, 1.6, 0.2],\n            [5.0, 3.5, 1.3, 0.3],\n            [7.0, 3.2, 4.7, 1.4],\n            [5.0, 2.0, 3.5, 1.0],\n            [5.9, 3.2, 4.8, 1.8],\n            [5.5, 2.4, 3.8, 1.1],\n            [5.5, 2.6, 4.4, 1.2],\n            [6.3, 3.3, 6.0, 2.5],\n            [6.5, 3.2, 5.1, 2.0],\n            [6.9, 3.2, 5.7, 2.3],\n            [7.4, 2.8, 6.1, 1.9],\n            [6.7, 3.1, 5.6, 2.4],\n        ])\n    }\n\n    fn generate_gbm_raw_output() -> Array2<f32> {\n        arr2(&[\n            [0.28045893, -0.14726841, -0.14718461],\n            [0.28045893, -0.14768013, -0.14718461],\n            [0.28045893, -0.14768013, -0.14718461],\n            [0.26034147, -0.14768013, -0.14718461],\n            [0.28045893, -0.14726841, -0.14718461],\n            [-0.14726523, 0.20831025, -0.10905999],\n            [-0.14737862, 0.254_875_3, -0.13664228],\n            [-0.14726523, -0.10231511, 0.20431481],\n            [-0.14737862, 0.254_875_3, -0.13664228],\n            [-0.14737862, 0.254_875_3, -0.13664228],\n            [-0.147_204_7, -0.147_199_3, 0.281_595_9],\n            [-0.14726523, -0.10231511, 0.20431481],\n            [-0.147_204_7, -0.147_199_3, 0.281_595_9],\n            [-0.147_204_7, -0.147_199_3, 0.281_595_9],\n            [-0.147_204_7, -0.147_199_3, 0.281_595_9],\n        ])\n    }\n\n    #[test]\n    #[ignore]\n    fn test_tree_ensemble() {\n        let ensemble = generate_gbm_ensemble();\n        let input = generate_gbm_input();\n        let output = ensemble.eval(input.view().into_dyn()).unwrap();\n        assert_eq!(output, generate_gbm_raw_output().into_dyn());\n    }\n}\n"
  },
  {
    "path": "onnx-opl/src/ml/tree_ensemble_classifier.rs",
    "content": "pub use super::tree::{Aggregate, Cmp, TreeEnsemble, TreeEnsembleData};\nuse tract_nnef::internal::*;\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_onnx_ml_tree_ensemble_classifier\",\n        &parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        load,\n    );\n    registry.register_dumper(dump);\n}\n\npub fn parse_aggregate(s: &str) -> TractResult<Aggregate> {\n    match s {\n        \"SUM\" => Ok(Aggregate::Sum),\n        \"AVERAGE\" => Ok(Aggregate::Avg),\n        \"MAX\" => Ok(Aggregate::Max),\n        \"MIN\" => Ok(Aggregate::Min),\n        _ => bail!(\"Invalid aggregate function: {}\", s),\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct TreeEnsembleClassifier {\n    pub ensemble: TreeEnsemble,\n}\n\nimpl Op for TreeEnsembleClassifier {\n    fn name(&self) -> StaticName {\n        \"TreeEnsembleClassifier\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for TreeEnsembleClassifier {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let input = input.cast_to::<f32>()?;\n        let input = input.to_plain_array_view::<f32>()?;\n        let scores = self.ensemble.eval(input)?;\n        Ok(tvec!(scores.into_tvalue()))\n    }\n}\n\nimpl TypedOp for TreeEnsembleClassifier {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let n = &inputs[0].shape[0];\n        Ok(tvec!(f32::fact(&[n.clone(), self.ensemble.n_classes().into()])))\n    }\n\n    as_op!();\n}\n\nfn parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"input\"),\n        TypeName::Scalar.tensor().named(\"trees\"),\n        TypeName::Scalar.tensor().named(\"nodes\"),\n        TypeName::Scalar.tensor().named(\"leaves\"),\n        TypeName::Integer.named(\"max_used_feature\"),\n        TypeName::Integer.named(\"n_classes\"),\n        TypeName::String.named(\"aggregate_fn\"),\n    ]\n}\n\nfn dump(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &TreeEnsembleClassifier,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let trees = ast.konst_variable(format!(\"{}_trees\", node.name), &op.ensemble.data.trees)?;\n    let nodes = ast.konst_variable(format!(\"{}_nodes\", node.name), &op.ensemble.data.nodes)?;\n    let leaves = ast.konst_variable(format!(\"{}_leaves\", node.name), &op.ensemble.data.leaves)?;\n    let agg = match op.ensemble.aggregate_fn {\n        Aggregate::Min => \"MIN\",\n        Aggregate::Max => \"MAX\",\n        Aggregate::Sum => \"SUM\",\n        Aggregate::Avg => \"AVERAGE\",\n    };\n    Ok(Some(invocation(\n        \"tract_onnx_ml_tree_ensemble_classifier\",\n        &[input, trees, nodes, leaves],\n        &[\n            (\"max_used_feature\", numeric(op.ensemble.max_used_feature)),\n            (\"n_classes\", numeric(op.ensemble.n_classes)),\n            (\"aggregate_fn\", string(agg)),\n        ],\n    )))\n}\n\nfn load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let trees = invocation.named_arg_as(builder, \"trees\")?;\n    let nodes = invocation.named_arg_as(builder, \"nodes\")?;\n    let leaves = invocation.named_arg_as(builder, \"leaves\")?;\n    let max_used_feature = invocation.named_arg_as(builder, \"max_used_feature\")?;\n    let n_classes = invocation.named_arg_as(builder, \"n_classes\")?;\n    let aggregate_fn: String = invocation.named_arg_as(builder, \"aggregate_fn\")?;\n    let aggregate_fn = parse_aggregate(&aggregate_fn)?;\n    let data = TreeEnsembleData { trees, nodes, leaves };\n    let ensemble = TreeEnsemble { data, n_classes, max_used_feature, aggregate_fn };\n    let op = TreeEnsembleClassifier { ensemble };\n    builder.wire(op, &[input])\n}\n"
  },
  {
    "path": "onnx-opl/src/multinomial.rs",
    "content": "use rand::distr::Distribution;\nuse rand::distr::StandardUniform;\nuse rand::rngs::SmallRng;\nuse rand::{RngExt, SeedableRng};\n\nuse tract_nnef::internal::*;\nuse tract_nnef::tract_ndarray::s;\nuse tract_nnef::tract_num_traits::{AsPrimitive, Float, Zero};\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_onnx_multinomial\",\n        &parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        load,\n    );\n    registry.register_dumper(dump);\n}\n\n/// https://github.com/onnx/onnx/blob/main/docs/Operators.md#Multinomial\n#[derive(Clone, Debug)]\npub struct Multinomial {\n    pub dtype: DatumType,\n    pub sample_size: i32,\n    pub seed: Option<f32>,\n}\nimpl PartialEq for Multinomial {\n    fn eq(&self, _: &Self) -> bool {\n        false\n    }\n}\nimpl Eq for Multinomial {}\n\nimpl Multinomial {\n    fn eval_t0<T1>(&self, input: TValue) -> TractResult<TValue>\n    where\n        T1: Datum + std::ops::SubAssign + Float + std::iter::Sum,\n        StandardUniform: Distribution<T1>,\n    {\n        match self.dtype {\n            DatumType::I32 => self.eval_t::<T1, i32>(input),\n            DatumType::I64 => self.eval_t::<T1, i64>(input),\n            dt => bail!(\"Unsupported output datum type for Multinomial: {:?}\", dt),\n        }\n    }\n    fn eval_t<T1, T2>(&self, input: TValue) -> TractResult<TValue>\n    where\n        T1: Datum + std::ops::SubAssign + Float + std::iter::Sum,\n        StandardUniform: Distribution<T1>,\n        T2: Datum + Zero + Copy,\n        usize: AsPrimitive<T2>,\n    {\n        let batch_size = input.shape()[0];\n        let class_size = input.shape()[1];\n\n        let mut rng = self\n            .seed\n            .map_or_else(rand::make_rng, |seed| SmallRng::seed_from_u64(seed.to_bits() as _));\n\n        // shape: [batch_size, class_size]\n        let input = input.to_plain_array_view::<T1>()?;\n\n        // ONNX Multinomial inputs are \"unnormalized log probabilities\".\n        // This means that we need to compute the maximum for each batch beforehand,\n        //  and we also need to exp every value.\n\n        let maximums: TVec<_> = input\n            .rows()\n            .into_iter()\n            .map(|r: tract_ndarray::ArrayView1<'_, T1>| r.iter().map(|e| e.exp()).sum::<T1>())\n            .collect();\n\n        // shape: [batch_size, sample_size]\n        let out_shape: &[_] = &[batch_size, self.sample_size as usize];\n        let output = tract_ndarray::ArrayD::from_shape_fn(out_shape, |co_o| -> T2 {\n            let batch = co_o[0];\n\n            let mut rand = rng.random::<T1>() * maximums[batch];\n            let mut ret: T2 = usize::as_(class_size - 1);\n\n            for (i, prob) in input.slice(s![batch, ..]).iter().enumerate() {\n                let prob = prob.exp();\n                if rand < prob {\n                    ret = usize::as_(i);\n                    break;\n                }\n                rand -= prob;\n            }\n\n            ret\n        });\n\n        Ok(output.into_tvalue())\n    }\n}\n\nimpl Op for Multinomial {\n    fn name(&self) -> StaticName {\n        \"Multinomial\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Multinomial {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n\n        let output = match input.datum_type() {\n            // DatumType::F16 => self.eval_t0::<f16>(input), // TODO: implement random for f16\n            DatumType::F32 => self.eval_t0::<f32>(input),\n            DatumType::F64 => self.eval_t0::<f64>(input),\n            dt => bail!(\"Unsupported input datum type for Multinomial: {:?}\", dt),\n        }?;\n\n        Ok(tvec![output])\n    }\n}\n\nimpl TypedOp for Multinomial {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let input_shape = if let Some(s) = inputs[0].shape.as_concrete() {\n            s\n        } else {\n            bail!(\"Only constant input shape are supported in Multinomial\")\n        };\n\n        let batch_size = input_shape[0];\n        Ok(tvec!(self.dtype.fact([batch_size, self.sample_size as usize])))\n    }\n\n    as_op!();\n}\n\nfn parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Integer.tensor().named(\"input\"),\n        TypeName::Integer.named(\"dtype\").default(6),\n        TypeName::Integer.named(\"sample_size\").default(1),\n        TypeName::Integer.named(\"seed\"),\n    ]\n}\n\nfn dump(ast: &mut IntoAst, node: &TypedNode, op: &Multinomial) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n\n    let dtype = match op.dtype {\n        DatumType::I32 => 6,\n        DatumType::I64 => 7,\n        dt => bail!(\"Unsupported datum type {:?} for ONNX Multinomial\", dt),\n    };\n\n    let inv = if let Some(seed) = op.seed {\n        invocation(\n            \"tract_onnx_multinomial\",\n            &[input],\n            &[\n                (\"dtype\", numeric(dtype)),\n                (\"sample_size\", numeric(op.sample_size)),\n                (\"seed\", numeric(seed)),\n            ],\n        )\n    } else {\n        invocation(\n            \"tract_onnx_multinomial\",\n            &[input],\n            &[(\"dtype\", numeric(dtype)), (\"sample_size\", numeric(op.sample_size))],\n        )\n    };\n\n    Ok(Some(inv))\n}\n\nfn load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let dtype = match invocation.named_arg_as::<i64>(builder, \"dtype\")? {\n        6 => DatumType::I32,\n        7 => DatumType::I64,\n        i => bail!(\"Unsupported datum type {} for ONNX Multinomial\", i),\n    };\n    let sample_size = invocation.named_arg_as::<i64>(builder, \"sample_size\")? as _;\n    let seed = invocation.named_arg_as(builder, \"seed\").ok();\n\n    let op = Multinomial { dtype, sample_size, seed };\n    builder.wire(op, &[input])\n}\n"
  },
  {
    "path": "onnx-opl/src/non_max_suppression.rs",
    "content": "use std::cmp::Ordering;\n\nuse rustfft::num_traits::Float;\nuse tract_nnef::{\n    internal::*,\n    tract_ndarray::{ArrayView1, s},\n};\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_onnx_non_max_suppression\",\n        &parameters(),\n        &[(\"output\", TypeName::Integer.tensor())],\n        load,\n    );\n    registry.register_dumper(dump);\n}\n\n#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]\npub enum BoxRepr {\n    // boxes data format [y1, x1, y2, x2]\n    TwoPoints,\n    // boxes data format [x_center, y_center, width, height]\n    CenterWidthHeight,\n}\n\nfn get_min_max<T: Float>(lhs: T, rhs: T) -> (T, T) {\n    if lhs >= rhs { (rhs, lhs) } else { (lhs, rhs) }\n}\n\nimpl BoxRepr {\n    pub fn from_i64(val: i64) -> TractResult<BoxRepr> {\n        Ok(match val {\n            0 => BoxRepr::TwoPoints,\n            1 => BoxRepr::CenterWidthHeight,\n            other => bail!(\"unsupported center_point_box argument value: {}\", other),\n        })\n    }\n\n    pub fn into_i64(self) -> i64 {\n        match self {\n            BoxRepr::TwoPoints => 0,\n            BoxRepr::CenterWidthHeight => 1,\n        }\n    }\n\n    // iou: intersection over union\n    fn should_suppress_by_iou<T: Datum + Float>(\n        &self,\n        box1: ArrayView1<T>,\n        box2: ArrayView1<T>,\n        iou_threshold: T,\n    ) -> bool {\n        let two = T::one() + T::one();\n        let (x1_min, x1_max, x2_min, x2_max, y1_min, y1_max, y2_min, y2_max) = match self {\n            BoxRepr::TwoPoints => {\n                let (x1_min, x1_max) = get_min_max(box1[[1]], box1[[3]]);\n                let (x2_min, x2_max) = get_min_max(box2[[1]], box2[[3]]);\n\n                let (y1_min, y1_max) = get_min_max(box1[[0]], box1[[2]]);\n                let (y2_min, y2_max) = get_min_max(box2[[0]], box2[[2]]);\n\n                (x1_min, x1_max, x2_min, x2_max, y1_min, y1_max, y2_min, y2_max)\n            }\n            BoxRepr::CenterWidthHeight => {\n                let (box1_width_half, box1_height_half) = (box1[[2]] / two, box1[[3]] / two);\n                let (box2_width_half, box2_height_half) = (box2[[2]] / two, box2[[3]] / two);\n\n                let (x1_min, x1_max) = (box1[[0]] - box1_width_half, box1[[0]] + box1_width_half);\n                let (x2_min, x2_max) = (box2[[0]] - box2_width_half, box2[[0]] + box2_width_half);\n\n                let (y1_min, y1_max) = (box1[[1]] - box1_height_half, box1[[1]] + box1_height_half);\n                let (y2_min, y2_max) = (box2[[1]] - box2_height_half, box2[[1]] + box2_height_half);\n\n                (x1_min, x1_max, x2_min, x2_max, y1_min, y1_max, y2_min, y2_max)\n            }\n        };\n\n        let intersection_y_min = T::max(y1_min, y2_min);\n        let intersection_y_max = T::min(y1_max, y2_max);\n        if intersection_y_max <= intersection_y_min {\n            return false;\n        }\n\n        let intersection_x_min = T::max(x1_min, x2_min);\n        let intersection_x_max = T::min(x1_max, x2_max);\n        if intersection_x_max <= intersection_x_min {\n            return false;\n        }\n\n        let intersection_area =\n            (intersection_x_max - intersection_x_min) * (intersection_y_max - intersection_y_min);\n\n        if intersection_area.is_sign_negative() {\n            return false;\n        }\n\n        let area1 = (x1_max - x1_min) * (y1_max - y1_min);\n        let area2 = (x2_max - x2_min) * (y2_max - y2_min);\n\n        let union_area = area1 + area2 - intersection_area;\n\n        if area1.is_sign_negative() || area2.is_sign_negative() || union_area.is_sign_negative() {\n            return false;\n        }\n\n        let intersection_over_union = intersection_area / union_area;\n\n        intersection_over_union > iou_threshold\n    }\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct NonMaxSuppression {\n    pub center_point_box: BoxRepr,\n    pub num_selected_indices_symbol: Symbol,\n    pub has_score_threshold: bool,\n}\n\nimpl NonMaxSuppression {\n    fn eval_t<T: Datum + Float>(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold) =\n            if self.has_score_threshold {\n                let (t1, t2, t3, t4, t5) = args_5!(inputs);\n                (t1, t2, t3, t4, Some(t5))\n            } else {\n                let (t1, t2, t3, t4) = args_4!(inputs);\n                (t1, t2, t3, t4, None)\n            };\n\n        let mut max_output_boxes_per_class =\n            *max_output_boxes_per_class.try_as_plain()?.to_scalar::<i64>()?;\n        let iou_threshold = *iou_threshold.try_as_plain()?.to_scalar::<T>()?;\n        let score_threshold = score_threshold.map_or(Ok::<_, TractError>(None), |val| {\n            Ok(Some(*val.try_as_plain()?.to_scalar::<T>()?))\n        })?;\n\n        if max_output_boxes_per_class == 0 {\n            max_output_boxes_per_class = i64::MAX;\n        }\n        //        ensure!((0.0..=1.0).contains(&iou_threshold), \"iou_threshold must be between 0 and 1\");\n\n        let num_batches = scores.shape()[0];\n        let num_classes = scores.shape()[1];\n        let num_dim = scores.shape()[2];\n\n        let boxes = boxes.to_plain_array_view::<T>()?;\n        let scores = scores.to_plain_array_view::<T>()?;\n\n        // items: (batch, class, index)\n        let mut selected_global: TVec<(usize, usize, usize)> = tvec![];\n\n        for batch in 0..num_batches {\n            for class in 0..num_classes {\n                // items: (score, index)\n                let mut candidates: TVec<(T, usize)> =\n                    if let Some(score_threshold) = score_threshold {\n                        (0..num_dim)\n                            .map(|i| (scores[[batch, class, i]], i))\n                            .filter(|(score, _)| *score > score_threshold)\n                            .collect()\n                    } else {\n                        (0..num_dim).map(|i| (scores[[batch, class, i]], i)).collect()\n                    };\n\n                candidates.sort_by(|(a, _), (b, _)| b.partial_cmp(a).unwrap_or(Ordering::Equal));\n\n                // items: (score, index)\n                let mut selected_in_class: TVec<(T, usize)> = tvec![];\n\n                for (score, index) in candidates {\n                    if selected_in_class.len() as i64 >= max_output_boxes_per_class {\n                        break;\n                    }\n\n                    let box1 = boxes.slice(s![batch, index, ..]);\n                    let suppr = selected_in_class.iter().any(|(_, index)| {\n                        let box2 = boxes.slice(s![batch, *index, ..]);\n                        self.center_point_box.should_suppress_by_iou(box1, box2, iou_threshold)\n                    });\n                    if !suppr {\n                        selected_in_class.push((score, index));\n                        selected_global.push((batch, class, index));\n                    }\n                }\n            }\n        }\n\n        // output shape is [num_selected_indices, 3]; format is [batch_index, class_index, box_index]\n        let num_selected = selected_global.len();\n        let v = selected_global\n            .into_iter()\n            .flat_map(|(batch, class, index)| [batch as i64, class as i64, index as i64])\n            .collect();\n        let res = tract_ndarray::ArrayD::from_shape_vec(&*tvec![num_selected, 3], v)?;\n\n        Ok(tvec![res.into_tvalue()])\n    }\n}\n\nimpl Op for NonMaxSuppression {\n    fn name(&self) -> StaticName {\n        \"NonMaxSuppression\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for NonMaxSuppression {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let dt = inputs[0].datum_type();\n        dispatch_floatlike!(Self::eval_t(dt)(self, inputs))\n    }\n}\n\nimpl TypedOp for NonMaxSuppression {\n    fn output_facts(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec![i64::fact([self.num_selected_indices_symbol.to_dim(), 3usize.to_dim()])])\n    }\n\n    as_op!();\n}\n\nfn parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Integer.tensor().named(\"boxes\"),\n        TypeName::Scalar.tensor().named(\"scores\"),\n        TypeName::Integer.named(\"max_output_boxes_per_class\").default(0),\n        TypeName::Scalar.named(\"iou_threshold\").default(0.0),\n        TypeName::Scalar.named(\"score_threshold\"),\n        TypeName::Integer.named(\"center_point_box\").default(0),\n    ]\n}\n\nfn dump(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &NonMaxSuppression,\n) -> TractResult<Option<Arc<RValue>>> {\n    let boxes = ast.mapping[&node.inputs[0]].clone();\n    let scores = ast.mapping[&node.inputs[1]].clone();\n    let max_output_boxes_per_class = ast.mapping[&node.inputs[2]].clone();\n    let iou_threshold = ast.mapping[&node.inputs[3]].clone();\n    let score_threshold = node.inputs.get(4).map(|v| ast.mapping[v].clone());\n\n    let inv = if let Some(score_threshold) = score_threshold {\n        invocation(\n            \"tract_onnx_non_max_suppression\",\n            &[boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold],\n            &[(\"center_point_box\", numeric(op.center_point_box.into_i64()))],\n        )\n    } else {\n        invocation(\n            \"tract_onnx_non_max_suppression\",\n            &[boxes, scores, max_output_boxes_per_class, iou_threshold],\n            &[(\"center_point_box\", numeric(op.center_point_box.into_i64()))],\n        )\n    };\n\n    Ok(Some(inv))\n}\n\nfn load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let boxes = invocation.named_arg_as(builder, \"boxes\")?;\n    let scores = invocation.named_arg_as(builder, \"scores\")?;\n    let max_output_boxes_per_class =\n        invocation.named_arg_as(builder, \"max_output_boxes_per_class\")?;\n    let iou_threshold = invocation.named_arg_as(builder, \"iou_threshold\")?;\n    let score_threshold = invocation.named_arg_as(builder, \"score_threshold\").ok();\n\n    let center_point_box =\n        BoxRepr::from_i64(invocation.named_arg_as(builder, \"center_point_box\")?)?;\n\n    let n = builder.model.symbols.sym(\"n\");\n    let op = NonMaxSuppression {\n        center_point_box,\n        num_selected_indices_symbol: n,\n        has_score_threshold: score_threshold.is_some(),\n    };\n    if let Some(score_threshold) = score_threshold {\n        builder\n            .wire(op, &[boxes, scores, max_output_boxes_per_class, iou_threshold, score_threshold])\n    } else {\n        builder.wire(op, &[boxes, scores, max_output_boxes_per_class, iou_threshold])\n    }\n}\n"
  },
  {
    "path": "onnx-opl/src/random.rs",
    "content": "use rand::SeedableRng;\nuse rand::distr::Distribution;\nuse rand::distr::uniform::SampleUniform;\nuse rand::rngs::SmallRng;\nuse rand_distr::StandardNormal;\nuse rand_distr::num_traits::Float;\nuse tract_nnef::internal::*;\nuse tract_nnef::ser::{array, tdims};\nuse tract_nnef::tract_core::trivial_op_state_freeze;\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_onnx_random\",\n        &[\n            TypeName::String.named(\"datum_type\"),\n            TypeName::Integer.array().named(\"shape\"),\n            TypeName::String.named(\"dist\"),\n            TypeName::Scalar.array().named(\"parameters\"),\n            TypeName::Integer.named(\"seed\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        load,\n    );\n    registry.register_dumper(dump);\n}\n\nfn load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let dt: DatumType = invocation.named_arg_as::<String>(builder, \"datum_type\")?.parse()?;\n    let shape: TVec<TDim> = invocation.named_arg_as(builder, \"shape\")?;\n    let fact = dt.fact(&shape);\n    let dist: String = invocation.named_arg_as(builder, \"dist\")?;\n    let parameters: TVec<Arc<Tensor>> = invocation.named_arg_as(builder, \"parameters\")?;\n    let [p1, p2] = &*parameters else { bail!(\"Random expect two parameters\") };\n    let dist = match &*dist {\n        \"normal\" => Dist::Normal { mean: p1.clone(), dev: p2.clone() },\n        \"uniform\" => Dist::Uniform { low: p1.clone(), high: p2.clone() },\n        _ => bail!(\"Unexpected distribution {}\", dist),\n    };\n    let seed = invocation.get_named_arg_as(builder, \"seed\")?;\n    let op = Random { fact, dist, seed };\n    builder.wire(op, &[])\n}\n\nfn dump(_ast: &mut IntoAst, _node: &TypedNode, op: &Random) -> TractResult<Option<Arc<RValue>>> {\n    let mut named = vec![\n        (\"datum_type\", string(format!(\"{:?}\", op.fact.datum_type))),\n        (\"shape\", tdims(&op.fact.shape)),\n    ];\n    if let Some(seed) = op.seed {\n        named.push((\"seed\", numeric(seed)));\n    }\n    match &op.dist {\n        Dist::Uniform { low, high } => {\n            named.push((\"dist\", string(\"uniform\")));\n            named.push((\n                \"parameters\",\n                array(&[\n                    numeric(low.cast_to_scalar::<f32>()?),\n                    numeric(high.cast_to_scalar::<f32>()?),\n                ]),\n            ));\n        }\n        Dist::Normal { mean, dev } => {\n            named.push((\"dist\", string(\"normal\")));\n            named.push((\n                \"parameters\",\n                array(&[\n                    numeric(mean.cast_to_scalar::<f32>()?),\n                    numeric(dev.cast_to_scalar::<f32>()?),\n                ]),\n            ));\n        }\n    }\n    Ok(Some(invocation(\"tract_onnx_random\", &[], &named)))\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub enum Dist {\n    Uniform { low: Arc<Tensor>, high: Arc<Tensor> },\n    Normal { mean: Arc<Tensor>, dev: Arc<Tensor> },\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct Random {\n    pub fact: TypedFact,\n    pub dist: Dist,\n    pub seed: Option<u64>,\n}\n\nimpl Op for Random {\n    fn name(&self) -> StaticName {\n        \"Random\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl TypedOp for Random {\n    fn output_facts(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(self.fact.clone()))\n    }\n\n    as_op!();\n}\n\nimpl EvalOp for Random {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        let rng = self.seed.map(SmallRng::seed_from_u64).unwrap_or_else(rand::make_rng);\n        Ok(Some(Box::new(RandomState(rng))))\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct RandomState(SmallRng);\n\nimpl OpState for RandomState {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        _inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let op = op.downcast_ref::<Random>().context(\"op and state mismatch\")?;\n        let mut tensor = unsafe {\n            Tensor::uninitialized_dt(\n                op.fact.datum_type,\n                &op.fact.shape.eval_to_usize(&session.resolved_symbols)?,\n            )?\n        };\n        match &op.dist {\n            Dist::Uniform { low, high } => match op.fact.datum_type {\n                DatumType::F32 => sample_uniform::<f32>(&mut tensor, &mut self.0, low, high)?,\n                DatumType::F64 => sample_uniform::<f64>(&mut tensor, &mut self.0, low, high)?,\n                DatumType::F16 => {\n                    sample_uniform::<f32>(&mut tensor, &mut self.0, low, high)?;\n                    tensor = tensor.cast_to::<f16>()?.into_owned();\n                }\n                _ => bail!(\"Random only support float types\"),\n            },\n            Dist::Normal { mean, dev } => match op.fact.datum_type {\n                DatumType::F32 => sample_normal::<f32>(&mut tensor, &mut self.0, mean, dev)?,\n                DatumType::F64 => sample_normal::<f64>(&mut tensor, &mut self.0, mean, dev)?,\n                DatumType::F16 => {\n                    sample_uniform::<f32>(&mut tensor, &mut self.0, mean, dev)?;\n                    tensor = tensor.cast_to::<f16>()?.into_owned();\n                }\n                _ => bail!(\"Random only support float types\"),\n            },\n        }\n        Ok(tvec!(tensor.into_tvalue()))\n    }\n}\n\ntrivial_op_state_freeze!(RandomState);\n\nfn sample_uniform<T: Datum + SampleUniform + Copy>(\n    t: &mut Tensor,\n    r: &mut SmallRng,\n    low: &Tensor,\n    high: &Tensor,\n) -> TractResult<()> {\n    let dist = rand::distr::Uniform::new(low.cast_to_scalar::<T>()?, high.cast_to_scalar::<T>()?)?;\n    t.try_as_plain_mut()?\n        .as_slice_mut::<T>()?\n        .iter_mut()\n        .zip(dist.sample_iter(r))\n        .for_each(|(v, r)| *v = r);\n    Ok(())\n}\n\nfn sample_normal<T: Datum + Float + Copy>(\n    t: &mut Tensor,\n    r: &mut SmallRng,\n    mean: &Tensor,\n    dev: &Tensor,\n) -> TractResult<()>\nwhere\n    StandardNormal: Distribution<T>,\n{\n    let dist =\n        rand_distr::Normal::<T>::new(mean.cast_to_scalar::<T>()?, dev.cast_to_scalar::<T>()?)?;\n    t.try_as_plain_mut()?\n        .as_slice_mut::<T>()?\n        .iter_mut()\n        .zip(dist.sample_iter(r))\n        .for_each(|(v, r)| *v = r);\n    Ok(())\n}\n"
  },
  {
    "path": "onnx-opl/src/resize.rs",
    "content": "use tract_nnef::internal::*;\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub enum CoordTransformer {\n    HalfPixel,\n    AlignCorners,\n    Asymmetric,\n}\n\nimpl CoordTransformer {\n    pub fn transform(&self, x_out: usize, scale: f32, len_in: usize, len_out: usize) -> f32 {\n        match self {\n            CoordTransformer::HalfPixel => (x_out as f32 + 0.5) / scale - 0.5,\n            CoordTransformer::AlignCorners => {\n                (x_out as f32 * (len_in as f32 - 1.0)) / (len_out as f32 - 1.0)\n            }\n            CoordTransformer::Asymmetric => (x_out as f32) / scale,\n        }\n    }\n\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            CoordTransformer::HalfPixel => \"half_pixel\",\n            CoordTransformer::AlignCorners => \"align_corners\",\n            CoordTransformer::Asymmetric => \"asymmetric\",\n        }\n    }\n\n    pub fn parse(s: &str) -> TractResult<Self> {\n        Ok(match s {\n            \"half_pixel\" => CoordTransformer::HalfPixel,\n            \"align_corners\" => CoordTransformer::AlignCorners,\n            \"asymmetric\" => CoordTransformer::Asymmetric,\n            s => bail!(\"coordinate_transformation_mode: {s}\"),\n        })\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub enum Interpolator {\n    Linear,\n    Nearest,\n}\n\nimpl Interpolator {\n    pub fn interpolate(\n        &self,\n        y_left: f32,\n        y_right: f32,\n        x_ratio: f32,\n        nearest_mode: Nearest,\n    ) -> f32 {\n        match self {\n            Interpolator::Linear => y_left * (1.0 - x_ratio) + y_right * x_ratio,\n            Interpolator::Nearest => match nearest_mode {\n                Nearest::Floor => y_left,\n                Nearest::Ceil => y_right,\n                Nearest::RoundPreferFloor => {\n                    if x_ratio <= 0.5 {\n                        y_left\n                    } else {\n                        y_right\n                    }\n                }\n                Nearest::RoundPreferCeil => {\n                    if x_ratio < 0.5 {\n                        y_left\n                    } else {\n                        y_right\n                    }\n                }\n            },\n        }\n    }\n\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            Interpolator::Linear => \"linear\",\n            Interpolator::Nearest => \"nearest\",\n        }\n    }\n\n    pub fn parse(s: &str) -> TractResult<Self> {\n        Ok(match s {\n            \"linear\" => Interpolator::Linear,\n            \"nearest\" => Interpolator::Nearest,\n            s => bail!(\"mode: {s}\"),\n        })\n    }\n}\n\n#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]\npub enum Nearest {\n    Floor,\n    Ceil,\n    RoundPreferFloor,\n    RoundPreferCeil,\n}\n\nimpl Nearest {\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            Nearest::Floor => \"floor\",\n            Nearest::Ceil => \"ceil\",\n            Nearest::RoundPreferFloor => \"round_prefer_floor\",\n            Nearest::RoundPreferCeil => \"round_prefer_ceil\",\n        }\n    }\n\n    pub fn parse(s: &str) -> TractResult<Self> {\n        Ok(match s {\n            \"floor\" => Nearest::Floor,\n            \"ceil\" => Nearest::Ceil,\n            \"round_prefer_floor\" => Nearest::RoundPreferFloor,\n            \"round_prefer_ceil\" => Nearest::RoundPreferCeil,\n            s => bail!(\"nearest_mode: {s}\"),\n        })\n    }\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct Resize {\n    pub axes: Option<Vec<i64>>,\n    pub coord_transformer: CoordTransformer,\n    pub interpolator: Interpolator,\n    pub nearest: Nearest,\n    pub optional_roi_input: Option<usize>,\n    pub optional_scales_input: Option<usize>,\n    pub optional_sizes_input: Option<usize>,\n}\n\nimpl Resize {\n    pub fn compute_output_shape<D: DimLike>(\n        &self,\n        input_shape: &[D],\n        input_scale: Option<&Tensor>,\n        input_sizes: Option<&Tensor>,\n    ) -> TractResult<TVec<D>> {\n        if let Some(scale) = input_scale {\n            if scale.len() == input_shape.len() {\n                let mut shape = tvec!();\n                for (i, s) in input_shape\n                    .iter()\n                    .zip(scale.cast_to::<f32>()?.try_as_plain()?.as_slice::<f32>()?.iter())\n                {\n                    if s.round() == *s {\n                        shape.push(i.clone() * (*s as usize));\n                    } else if let Ok(i) = i.to_usize() {\n                        shape.push(((i as f32 * s) as usize).into());\n                    } else {\n                        bail!(\n                            \"Can not compute output shape. inputs are {input_shape:?} and scale {scale:?}\"\n                        )\n                    }\n                }\n                return Ok(shape);\n            }\n        }\n        if let Some(sizes) = input_sizes {\n            if sizes.len() == input_shape.len() {\n                return sizes\n                    .cast_to::<TDim>()?\n                    .try_as_plain()?\n                    .as_slice::<TDim>()?\n                    .iter()\n                    .map(|i| i.try_into())\n                    .collect();\n            }\n        }\n        bail!(\n            \"Neither sizes nor scales makes sense: input_shape: {:?}, scale: {:?}, sizes: {:?}\",\n            input_shape,\n            input_scale,\n            input_sizes,\n        );\n    }\n}\n\nimpl Op for Resize {\n    fn name(&self) -> StaticName {\n        \"Resize\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Resize {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, mut inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let scales = self.optional_scales_input.and_then(|ix| inputs.get(ix));\n        let sizes = self.optional_sizes_input.and_then(|ix| inputs.get(ix));\n        let output_shape = self.compute_output_shape(\n            inputs[0].shape(),\n            scales.map(|t| &**t),\n            sizes.map(|t| &**t),\n        )?;\n        let scales: TVec<f32> = if let Some(scales) = scales {\n            scales.try_as_plain()?.as_slice::<f32>()?.into()\n        } else {\n            output_shape.iter().zip(inputs[0].shape()).map(|(o, i)| *o as f32 / *i as f32).collect()\n        };\n        let mut data = inputs.remove(0).into_tensor().into_plain_array::<f32>()?;\n        for (axis, scale) in scales.into_iter().enumerate().filter(|(_, s)| *s != 1.0) {\n            let mut new_shape: TVec<usize> = data.shape().into();\n            new_shape[axis] = output_shape[axis];\n            data = tract_ndarray::ArrayD::from_shape_fn(&*new_shape, |co_o| -> f32 {\n                let x_out = co_o[axis];\n                let x_in = self.coord_transformer.transform(\n                    x_out,\n                    scale,\n                    data.shape()[axis],\n                    new_shape[axis],\n                );\n                let mut co_i = co_o;\n                let x_left = (x_in as usize).clamp(0, data.shape()[axis] - 1);\n                co_i[axis] = x_left;\n                let y_left = data[&co_i];\n                let x_right = (x_left + 1).min(data.shape()[axis] - 1);\n                co_i[axis] = x_right;\n                let y_right = data[&co_i];\n                let x_frac = x_in - x_left as f32;\n                self.interpolator.interpolate(y_left, y_right, x_frac, self.nearest)\n            })\n        }\n        Ok(tvec!(data.into_tvalue()))\n    }\n}\n\nimpl TypedOp for Resize {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let _roi = self.optional_roi_input.and_then(|ix| inputs.get(ix));\n        let scales = self.optional_scales_input.and_then(|ix| inputs.get(ix));\n        let sizes = self.optional_sizes_input.and_then(|ix| inputs.get(ix));\n        let output_shape = self.compute_output_shape(\n            &inputs[0].shape,\n            scales.and_then(|f| f.konst.as_deref()),\n            sizes.and_then(|f| f.konst.as_deref()),\n        )?;\n        Ok(tvec!(inputs[0].datum_type.fact(&output_shape)))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        // Lower nearest-neighbor integer-scale upsamples to Reshape → Tile → Reshape\n        if !matches!(self.interpolator, Interpolator::Nearest) {\n            return Ok(None);\n        }\n        let Some(scales_input) = self.optional_scales_input else { return Ok(None) };\n        let input_fact = model.outlet_fact(node.inputs[0])?;\n        let scales_fact = model.outlet_fact(node.inputs[scales_input])?;\n        let Some(scales_tensor) = &scales_fact.konst else { return Ok(None) };\n        let scales: Vec<f32> =\n            scales_tensor.cast_to::<f32>()?.try_as_plain()?.as_slice::<f32>()?.to_vec();\n\n        // Check all scales are positive integers\n        let int_scales: Vec<usize> = scales.iter().map(|&s| s.round() as usize).collect();\n        if scales.iter().zip(&int_scales).any(|(&s, &i)| (s - i as f32).abs() > 1e-5 || i == 0) {\n            return Ok(None);\n        }\n        // Only if at least one axis actually upsamples\n        if int_scales.iter().all(|&s| s == 1) {\n            return Ok(None);\n        }\n\n        let input_shape = &input_fact.shape;\n\n        let mut patch = TypedModelPatch::default();\n        let mut wire = patch.tap_model(model, node.inputs[0])?;\n\n        // Step 1: Reshape to interleave size-1 axes after each upsampled dim\n        // e.g. (N, C, H, W) with scales (1,1,2,2) → (N, C, H, 1, W, 1)\n        let mut from_dims: TVec<TDim> = tvec![];\n        let mut to_dims: TVec<TDim> = tvec![];\n        let mut tile_multipliers: TVec<TDim> = tvec![];\n        let mut first_upsampled = None;\n\n        for (i, &scale) in int_scales.iter().enumerate() {\n            from_dims.push(input_shape[i].clone());\n            to_dims.push(input_shape[i].clone());\n            tile_multipliers.push(1.into());\n            if scale > 1 {\n                if first_upsampled.is_none() {\n                    first_upsampled = Some(i);\n                }\n                to_dims.push(1.into());\n                tile_multipliers.push(scale.into());\n            }\n        }\n\n        if to_dims.len() > from_dims.len() {\n            let first = first_upsampled.unwrap();\n            wire = patch.wire_node(\n                format!(\"{}.reshape_pre\", node.name),\n                AxisOp::Reshape(first, from_dims[first..].into(), to_dims[first..].into()),\n                &[wire],\n            )?[0];\n        }\n\n        // Step 2: Tile the size-1 axes\n        use tract_core::ops::array::Tile;\n        wire = patch.wire_node(\n            format!(\"{}.tile\", node.name),\n            Tile { multipliers: tile_multipliers },\n            &[wire],\n        )?[0];\n\n        // Step 3: Reshape back to merge the tiled dims\n        // e.g. (N, C, H, 2, W, 2) → (N, C, H*2, W*2)\n        let tiled_shape: TVec<TDim> = to_dims\n            .iter()\n            .zip(int_scales.iter().flat_map(|&s| if s > 1 { vec![1usize, s] } else { vec![1] }))\n            .map(|(d, s)| d.clone() * s)\n            .collect();\n        let mut final_dims: TVec<TDim> = tvec![];\n        let mut idx = 0;\n        for &scale in &int_scales {\n            if scale > 1 {\n                final_dims.push(tiled_shape[idx].clone() * tiled_shape[idx + 1].clone());\n                idx += 2;\n            } else {\n                final_dims.push(tiled_shape[idx].clone());\n                idx += 1;\n            }\n        }\n\n        if tiled_shape.len() > final_dims.len() {\n            let first = first_upsampled.unwrap();\n            wire = patch.wire_node(\n                format!(\"{}.reshape_post\", node.name),\n                AxisOp::Reshape(first, tiled_shape[first..].into(), final_dims[first..].into()),\n                &[wire],\n            )?[0];\n        }\n\n        patch.shunt_outside(model, node.id.into(), wire)?;\n        Ok(Some(patch))\n    }\n}\n\n// --- NNEF serialization ---\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_onnx_resize\",\n        &parameters(),\n        &[(\"output\", TypeName::Scalar.tensor())],\n        load,\n    );\n    registry.register_dumper(dump);\n}\n\nfn parameters() -> Vec<Parameter> {\n    vec![\n        TypeName::Scalar.tensor().named(\"input\"),\n        TypeName::Scalar.tensor().named(\"scales\"),\n        TypeName::String.named(\"coord_transformer\").default(\"half_pixel\"),\n        TypeName::String.named(\"interpolator\").default(\"nearest\"),\n        TypeName::String.named(\"nearest_mode\").default(\"floor\"),\n    ]\n}\n\nfn dump(ast: &mut IntoAst, node: &TypedNode, op: &Resize) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let scales =\n        ast.mapping[&node.inputs[op.optional_scales_input.context(\"no scales input\")?]].clone();\n    Ok(Some(invocation(\n        \"tract_onnx_resize\",\n        &[input, scales],\n        &[\n            (\"coord_transformer\", string(op.coord_transformer.as_str())),\n            (\"interpolator\", string(op.interpolator.as_str())),\n            (\"nearest_mode\", string(op.nearest.as_str())),\n        ],\n    )))\n}\n\nfn load(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let scales = invocation.named_arg_as(builder, \"scales\")?;\n    let coord_transformer: String = invocation.named_arg_as(builder, \"coord_transformer\")?;\n    let interpolator: String = invocation.named_arg_as(builder, \"interpolator\")?;\n    let nearest_mode: String = invocation.named_arg_as(builder, \"nearest_mode\")?;\n\n    let op = Resize {\n        axes: None,\n        coord_transformer: CoordTransformer::parse(&coord_transformer)?,\n        interpolator: Interpolator::parse(&interpolator)?,\n        nearest: Nearest::parse(&nearest_mode)?,\n        optional_roi_input: None,\n        optional_scales_input: Some(1),\n        optional_sizes_input: None,\n    };\n\n    builder.wire(op, &[input, scales])\n}\n"
  },
  {
    "path": "post-release.sh",
    "content": "#!/bin/sh\n\nVERSION=$1\n. ./.all_crates.sh\n\nif [ `uname` = \"Darwin\" ]\nthen\n    SED=gsed\nelse\n    SED=sed\nfi\n\nif [ -z \"$VERSION\" ]\nthen\n    echo \"Usage: $0 <version>\" \n    exit 1\nfi\n\nfor path in $ALL_CRATES_PATH\ndo\n    crate=$(tomato get package.name $path/Cargo.toml)\n    echo $crate\n    tomato set package.version $VERSION $path/Cargo.toml > /dev/null\n    tomato set workspace.dependencies.$crate.version $VERSION Cargo.toml\ndone\n\ngit commit . -m \"post-release $VERSION\"\ngit push\n"
  },
  {
    "path": "pulse/Cargo.toml",
    "content": "[package]\nname = \"tract-pulse\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\ndowncast-rs.workspace = true\ndyn-eq.workspace = true\nerased-serde.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\nserde.workspace = true\ntract-pulse-opl.workspace = true\n\n"
  },
  {
    "path": "pulse/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "pulse/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "pulse/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "pulse/src/fact.rs",
    "content": "use crate::internal::*;\nuse dyn_eq::DynEq;\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct StreamInfo {\n    pub axis: usize,\n    pub dim: TDim,\n    pub delay: usize,\n}\n\npub trait StreamFact {\n    fn stream_info(&self, stream_sym: &Symbol) -> Option<(usize, &TDim)>;\n}\n\nimpl StreamFact for ShapeFact {\n    fn stream_info(&self, stream_sym: &Symbol) -> Option<(usize, &TDim)> {\n        let streaming_dims: TVec<(usize, &TDim)> = (**self)\n            .iter()\n            .enumerate()\n            .filter(|(_ix, d)| d.symbols().contains(stream_sym))\n            .collect();\n        if streaming_dims.len() != 1 { None } else { Some(streaming_dims[0]) }\n    }\n}\n\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct PulsedFact {\n    pub datum_type: DatumType,\n    pub shape: ShapeFact,\n    pub stream: Option<StreamInfo>,\n}\n\nimpl PulsedFact {\n    pub fn from_tensor_fact_pulse(\n        tf: &TypedFact,\n        symbol: &Symbol,\n        pulse: &TDim,\n    ) -> TractResult<PulsedFact> {\n        let datum_type = tf.datum_type;\n        let (axis, len) = tf\n            .shape\n            .stream_info(symbol)\n            .ok_or_else(|| format_err!(\"Can not pulse a tensor with no streaming dim\"))?;\n        let mut shape: TVec<TDim> = tf.shape.to_tvec();\n        shape[axis] = pulse.clone();\n        Ok(PulsedFact {\n            datum_type,\n            shape: shape.into(),\n            stream: Some(StreamInfo { axis, dim: len.clone(), delay: 0 }),\n        })\n    }\n\n    pub fn pulse(&self) -> Option<&TDim> {\n        if let Some(stream) = &self.stream { Some(&self.shape[stream.axis]) } else { None }\n    }\n\n    pub fn to_pulse_fact(&self) -> TypedFact {\n        self.datum_type.fact(self.shape.clone())\n    }\n\n    pub fn streaming_shape(&self) -> TVec<TDim> {\n        if let Some(stream) = &self.stream {\n            self.shape\n                .iter()\n                .enumerate()\n                .map(|(ix, d)| if ix == stream.axis { stream.dim.clone() } else { d.clone() })\n                .collect()\n        } else {\n            self.shape.to_tvec()\n        }\n    }\n\n    pub fn to_streaming_fact(&self) -> TypedFact {\n        let mut info = self.to_pulse_fact();\n        info.shape = self.streaming_shape().into();\n        info\n    }\n}\n\nimpl fmt::Debug for PulsedFact {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        use tract_itertools::Itertools;\n        if let Some(stream) = &self.stream {\n            write!(\n                fmt,\n                \"{},{:?} [pulse axis:{} ∂:{} full dim:{}]\",\n                self.shape.iter().join(\",\"),\n                self.datum_type,\n                stream.axis,\n                stream.delay,\n                stream.dim\n            )\n        } else {\n            write!(fmt, \"{:?}\", self.to_pulse_fact())\n        }\n    }\n}\n\nimpl Fact for PulsedFact {\n    fn to_typed_fact(&self) -> TractResult<Cow<'_, TypedFact>> {\n        Ok(Cow::Owned(self.into()))\n    }\n\n    fn compatible_with(&self, other: &dyn Fact) -> bool {\n        self.dyn_eq(other)\n    }\n\n    fn datum_type(&self) -> Option<DatumType> {\n        Some(self.datum_type)\n    }\n}\n\nimpl From<PulsedFact> for TypedFact {\n    fn from(fact: PulsedFact) -> TypedFact {\n        fact.datum_type.fact(fact.shape)\n    }\n}\n\nimpl<'a> From<&'a PulsedFact> for TypedFact {\n    fn from(fact: &'a PulsedFact) -> TypedFact {\n        fact.datum_type.fact(fact.shape.clone())\n    }\n}\n"
  },
  {
    "path": "pulse/src/lib.rs",
    "content": "#![allow(clippy::len_zero)]\n#[macro_use]\npub mod macros;\n\npub mod fact;\npub mod model;\npub mod ops;\n\npub mod internal {\n    pub use std::fmt;\n    pub use tract_nnef::internal::*;\n    pub use tract_pulse_opl::tract_nnef;\n\n    pub use downcast_rs::Downcast;\n\n    pub use crate::fact::PulsedFact;\n    pub use crate::model::{PulsedModel, PulsedModelExt};\n    pub use crate::ops::{OpPulsifier, PulsedOp};\n}\n\nuse std::ops::ControlFlow;\n\nuse internal::*;\nuse tract_core::transform::ModelTransform;\nuse tract_pulse_opl::tract_nnef::tract_core;\n\npub use ops::PulsedOp;\n\n#[derive(Debug, Default, serde::Deserialize)]\npub struct PulseConfig {\n    pub symbol: Option<String>,\n    pub pulse: String,\n}\n\n#[derive(Debug)]\nstruct PulseTransform(PulseConfig);\n\nimpl ModelTransform for PulseTransform {\n    fn name(&self) -> std::borrow::Cow<'static, str> {\n        \"pulse\".into()\n    }\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        let symbol = self.0.symbol.as_deref().unwrap_or(\"S\");\n        let sym = model.symbols.sym(symbol);\n        let pulse_dim = parse_tdim(&model.symbols, &self.0.pulse)?;\n        let pulsed = model::PulsedModel::new(model, sym, &pulse_dim)?;\n        *model = pulsed.into_typed()?;\n        Ok(())\n    }\n}\n\nregister_model_transform!(\"pulse\", PulseConfig, |config| Ok(Box::new(PulseTransform(config))));\n\npub trait WithPulse {\n    fn enable_pulse(&mut self);\n    fn with_pulse(self) -> Self;\n}\n\nimpl WithPulse for tract_nnef::framework::Nnef {\n    fn enable_pulse(&mut self) {\n        self.enable_tract_core();\n        self.registries.push(tract_nnef_registry());\n    }\n    fn with_pulse(mut self) -> Self {\n        self.enable_pulse();\n        self\n    }\n}\n\npub fn tract_nnef_registry() -> Registry {\n    let mut reg = tract_pulse_opl::tract_nnef_registry();\n    ops::delay::register(&mut reg);\n    reg.extensions.push(Box::new(decl_stream_symbol));\n    reg\n}\n\nfn decl_stream_symbol(\n    _proto_model: &mut ModelBuilder,\n    name: &Identifier,\n    _rest: &str,\n) -> TractResult<ControlFlow<(), ()>> {\n    if name.0 == \"tract_pulse_streaming_symbol\" {\n        Ok(ControlFlow::Break(()))\n    } else {\n        Ok(ControlFlow::Continue(()))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_source_must_stream() {\n        let mut model = TypedModel::default();\n        let s = model.symbols.sym(\"S\");\n        let _a = model.add_source(\"a\", f32::fact([1, 2, 3])).unwrap();\n        model.auto_outputs().unwrap();\n        assert!(PulsedModel::new(&model, s.clone(), &4.to_dim()).is_err());\n\n        let mut model = TypedModel::default();\n        let _a = model.add_source(\"a\", f32::fact(dims![1, s, 3].as_ref())).unwrap();\n        model.auto_outputs().unwrap();\n        let pulse = PulsedModel::new(&model, s, &4.to_dim()).unwrap();\n        assert_eq!(\n            *pulse.outlet_fact(OutletId::new(0, 0)).unwrap().to_typed_fact().unwrap(),\n            f32::fact([1usize, 4, 3])\n        );\n    }\n\n    #[test]\n    fn test_immediate() {\n        let mut model = TypedModel::default();\n        let s = model.symbols.sym(\"S\");\n        let _a = model.add_source(\"a\", f32::fact(dims![s, 2, 3].as_ref())).unwrap();\n        model.auto_outputs().unwrap();\n\n        let pulse = PulsedModel::new(&model, s, &4.to_dim()).unwrap();\n\n        assert_eq!(*pulse.input_fact(0).unwrap().to_typed_fact().unwrap(), f32::fact([4, 2, 3]));\n        assert_eq!(*pulse.output_fact(0).unwrap().to_typed_fact().unwrap(), f32::fact([4, 2, 3]));\n    }\n}\n"
  },
  {
    "path": "pulse/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! pulsed_op_to_typed_op {\n    () => {\n        fn to_typed(&self) -> Box<dyn TypedOp> {\n            tract_core::dyn_clone::clone_box(self)\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! register_all_mod {\n    ($($m: ident),*) => {\n        pub fn register_all(inventory: &mut HashMap<TypeId, OpPulsifier>) {\n            $( $m::register_all(inventory); )*\n        }\n    }\n}\n\n#[macro_export]\nmacro_rules! register_all {\n    ($($op: ty: $func: expr),*) => {\n        pub fn register_all(inventory: &mut HashMap<TypeId, OpPulsifier>) {\n            $(\n            inventory.insert(\n                std::any::TypeId::of::<$op>(),\n                OpPulsifier {\n                    type_id: std::any::TypeId::of::<$op>(),\n                    func: |source: &TypedModel,\n                           node: &TypedNode,\n                           target: &mut PulsedModel,\n                           mapping: &HashMap<OutletId, OutletId>,\n                           symbol: &Symbol,\n                           pulse: &TDim|\n                     -> TractResult<Option<TVec<OutletId>>> {\n                        let op = node.op_as::<$op>().unwrap();\n                        ($func)(op, source, node, target, mapping, symbol, pulse)\n                    },\n                    name: stringify!($op)\n                }\n            );)*\n        }\n    };\n}\n"
  },
  {
    "path": "pulse/src/model.rs",
    "content": "#![allow(clippy::collapsible_if)]\nuse std::sync::RwLock;\n\nuse crate::fact::StreamInfo;\nuse crate::{internal::*, ops::sync_inputs};\nuse tract_core::model::translator::Translate;\nuse tract_pulse_opl::tract_core::ops::konst::Const;\nuse tract_pulse_opl::tract_core::ops::source::TypedSource;\n\npub type PulsedModel = Graph<PulsedFact, Box<dyn PulsedOp>>;\npub type PulsedNode = Node<PulsedFact, Box<dyn PulsedOp>>;\n\n#[allow(clippy::new_ret_no_self)]\npub trait PulsedModelExt {\n    fn new(source: &TypedModel, symbol: Symbol, pulse: &TDim) -> TractResult<PulsedModel>;\n\n    fn new_with_mapping(\n        source: &TypedModel,\n        symbol: Symbol,\n        pulse: &TDim,\n    ) -> TractResult<(PulsedModel, HashMap<OutletId, OutletId>)>;\n\n    fn into_typed(self) -> TractResult<TypedModel>;\n}\n\nimpl PulsedModelExt for PulsedModel {\n    fn new(source: &TypedModel, symbol: Symbol, pulse: &TDim) -> TractResult<PulsedModel> {\n        Ok(PulsedModel::new_with_mapping(source, symbol, pulse)?.0)\n    }\n\n    fn new_with_mapping(\n        source: &TypedModel,\n        symbol: Symbol,\n        pulse: &TDim,\n    ) -> TractResult<(PulsedModel, HashMap<OutletId, OutletId>)> {\n        let pulsifiers = crate::ops::OpPulsifier::inventory();\n        Pulsifier(symbol, pulse.to_owned(), pulsifiers).translate_model_with_mappings(source)\n    }\n\n    fn into_typed(self) -> TractResult<TypedModel> {\n        let mut typed = tract_core::model::translator::IntoTranslator.translate_model(&self)?;\n        ensure!(\n            self.input_outlets()?.iter().all(|o| self.outlet_fact(*o).unwrap().stream.is_some())\n        );\n        ensure!(\n            self.output_outlets()?.iter().all(|o| self.outlet_fact(*o).unwrap().stream.is_some())\n        );\n        let delays = tensor1(\n            &self\n                .output_outlets()?\n                .iter()\n                .map(|oo| Ok(self.outlet_fact(*oo)?.stream.as_ref().unwrap().delay as _))\n                .collect::<TractResult<TVec<i64>>>()?,\n        );\n        typed.properties.insert(\"pulse.delay\".to_string(), delays.into_arc_tensor());\n        let input_axes = tensor1(\n            &self\n                .input_outlets()?\n                .iter()\n                .map(|oo| Ok(self.outlet_fact(*oo)?.stream.as_ref().unwrap().axis as _))\n                .collect::<TractResult<TVec<i64>>>()?,\n        );\n        typed.properties.insert(\"pulse.input_axes\".to_string(), input_axes.into_arc_tensor());\n        let output_axes = tensor1(\n            &self\n                .output_outlets()?\n                .iter()\n                .map(|oo| Ok(self.outlet_fact(*oo)?.stream.as_ref().unwrap().axis as _))\n                .collect::<TractResult<TVec<i64>>>()?,\n        );\n        typed.properties.insert(\"pulse.output_axes\".to_string(), output_axes.into_arc_tensor());\n        Ok(typed)\n    }\n}\n\nimpl SpecialOps<PulsedFact, Box<dyn PulsedOp>> for PulsedModel {\n    fn is_source(op: &Box<dyn PulsedOp>) -> bool {\n        op.as_op().downcast_ref::<crate::ops::source::PulsedSource>().is_some()\n    }\n\n    fn create_source(&self, fact: PulsedFact) -> Box<dyn PulsedOp> {\n        Box::new(crate::ops::source::PulsedSource(fact))\n    }\n\n    fn create_dummy(&self) -> Box<dyn PulsedOp> {\n        Box::new(tract_core::ops::dummy::Dummy::new())\n    }\n\n    fn wire_node(\n        &mut self,\n        name: impl Into<String>,\n        op: impl Into<Box<dyn PulsedOp>>,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let op = op.into();\n        let output_facts = {\n            let input_facts =\n                inputs.iter().map(|o| self.outlet_fact(*o)).collect::<TractResult<TVec<_>>>()?;\n            op.pulsed_output_facts(&input_facts)?\n        };\n        let id = self.add_node(name, op, output_facts)?;\n        inputs\n            .iter()\n            .enumerate()\n            .try_for_each(|(ix, i)| self.add_edge(*i, InletId::new(id, ix)))?;\n        Ok(self.node(id).outputs.iter().enumerate().map(|(ix, _)| OutletId::new(id, ix)).collect())\n    }\n\n    fn add_const(\n        &mut self,\n        name: impl Into<String>,\n        v: impl IntoArcTensor,\n    ) -> TractResult<OutletId> {\n        let v = v.into_arc_tensor();\n        for node in &self.nodes {\n            if let Some(op) = node.op_as::<Const>() {\n                if op.val() == &v {\n                    return Ok(node.id.into());\n                }\n            }\n        }\n        let op = NonPulsingWrappingOp(Box::new(Const::new(v)?));\n        Ok(self.wire_node(name, op, &[])?[0])\n    }\n}\n\nstruct Pulsifier(\n    Symbol,\n    TDim,\n    #[allow(dead_code)] Arc<RwLock<HashMap<TypeId, crate::ops::OpPulsifier>>>,\n);\n\nimpl std::fmt::Debug for Pulsifier {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Pulsifier({})\", self.0)\n    }\n}\n\nimpl\n    tract_core::model::translator::Translate<\n        TypedFact,\n        Box<dyn TypedOp>,\n        PulsedFact,\n        Box<dyn PulsedOp>,\n    > for Pulsifier\n{\n    fn translate_node(\n        &self,\n        source: &TypedModel,\n        node: &TypedNode,\n        target: &mut PulsedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(op) = node.op_as::<TypedSource>() {\n            return Ok(crate::ops::source::pulsify(\n                op, source, node, target, mapping, &self.0, &self.1,\n            )?\n            .unwrap());\n        }\n        log::debug!(\"Pulsifying node {node}\");\n\n        if !source\n            .node_input_facts(node.id)?\n            .iter()\n            .any(|f| f.shape.iter().any(|d| d.symbols().contains(&self.0)))\n            && !node\n                .outputs\n                .iter()\n                .any(|o| o.fact.shape.iter().any(|d| d.symbols().contains(&self.0)))\n        {\n            let pulse_op = NonPulsingWrappingOp(node.op.clone());\n            let inputs: TVec<OutletId> = node.inputs.iter().map(|i| mapping[i]).collect();\n            log::debug!(\"Pulsified node {node} with NonPulsingWrappingOp\");\n            return target.wire_node(&node.name, pulse_op, &inputs);\n        }\n\n        if let Some(pulsified) =\n            OpPulsifier::pulsify(source, node, target, mapping, &self.0, &self.1)?\n        {\n            log::debug!(\"Pulsified node {node} with adhoc pulsifier\");\n            return Ok(pulsified);\n        }\n\n        let pulse_facts: TVec<PulsedFact> =\n            node.inputs.iter().map(|i| target.outlet_fact(mapping[i]).unwrap().clone()).collect();\n        if pulse_facts.iter().all(|pf| pf.stream.is_none()) {\n            let pulse_op = NonPulsingWrappingOp(node.op.clone());\n            let inputs: TVec<OutletId> = node.inputs.iter().map(|i| mapping[i]).collect();\n            log::debug!(\"Pulsified node {node} with NonPulsingWrappingOp\");\n            return target.wire_node(&node.name, pulse_op, &inputs);\n        }\n\n        let (stream_input_ix, pulse_fact) =\n            pulse_facts.iter().enumerate().find(|(_ix, pf)| pf.stream.is_some()).unwrap();\n        let (input_facts, output_facts) = source.node_facts(node.id)?;\n        let axes_mapping = node.op.axes_mapping(&input_facts, &output_facts)?;\n        let axis_info = axes_mapping\n            .axis((InOut::In(stream_input_ix), pulse_fact.stream.as_ref().unwrap().axis))?;\n        if axis_info.outputs[0].len() == 1 {\n            let pulse_op = PulseWrappingOp(node.op.clone());\n            let inputs = sync_inputs(node, target, mapping)?;\n            log::debug!(\"Pulsified node {node} with PulsingWrappingOp\");\n            return target.wire_node(&node.name, pulse_op, &inputs);\n        }\n\n        bail!(\"No specific pulse transformation for {}, and could not track pulsing axis.\", node)\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub(crate) struct PulseWrappingOp(pub Box<dyn TypedOp>);\n\nimpl Op for PulseWrappingOp {\n    fn name(&self) -> StaticName {\n        format!(\"PulseWrapping({}\", self.0.name()).into()\n    }\n\n    fn as_typed(&self) -> Option<&dyn TypedOp> {\n        Some(self.0.as_ref())\n    }\n}\n\nimpl EvalOp for PulseWrappingOp {\n    fn is_stateless(&self) -> bool {\n        self.0.is_stateless()\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        self.0.eval(inputs)\n    }\n\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        self.0.state(session, node_id)\n    }\n}\n\nimpl PulsedOp for PulseWrappingOp {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let (pulsing_input, stream) = if let Some((ix, fact)) =\n            &inputs.iter().enumerate().find(|(_ix, f)| f.stream.is_some())\n        {\n            (*ix, fact.stream.as_ref().unwrap())\n        } else {\n            bail!(\"PulseWrappingOp used on non streaming input\")\n        };\n        let input_facts =\n            inputs.iter().map(|pf| pf.to_typed_fact()).collect::<TractResult<TVec<_>>>()?;\n        let input_facts_ref = input_facts.iter().map(|f| f.as_ref()).collect::<TVec<_>>();\n        let output_facts = self.0.output_facts(&input_facts_ref)?;\n        let output_facts_ref = output_facts.iter().collect::<TVec<_>>();\n        let axes_mapping = self.0.axes_mapping(&input_facts_ref, &output_facts_ref)?;\n        let axis_info = axes_mapping.axis((InOut::In(pulsing_input), stream.axis))?;\n        std::mem::drop(output_facts_ref);\n        output_facts\n            .into_iter()\n            .enumerate()\n            .map(|(ix, tf)| {\n                if let &[axis] = &*axis_info.outputs[ix] {\n                    Ok(PulsedFact {\n                        shape: tf.shape,\n                        datum_type: tf.datum_type,\n                        stream: Some(StreamInfo {\n                            delay: stream.delay,\n                            axis,\n                            dim: stream.dim.clone(),\n                        }),\n                    })\n                } else {\n                    bail!(\"Disappearing pulsing axis\")\n                }\n            })\n            .collect()\n    }\n\n    as_op!();\n\n    fn to_typed(&self) -> Box<dyn TypedOp> {\n        self.0.clone()\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub(crate) struct NonPulsingWrappingOp(pub Box<dyn TypedOp>);\n\nimpl Op for NonPulsingWrappingOp {\n    fn name(&self) -> StaticName {\n        format!(\"NonePulsingWrapping({}\", self.0.name()).into()\n    }\n\n    fn as_typed(&self) -> Option<&dyn TypedOp> {\n        Some(self.0.as_ref())\n    }\n}\n\nimpl EvalOp for NonPulsingWrappingOp {\n    fn is_stateless(&self) -> bool {\n        self.0.is_stateless()\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        self.0.eval(inputs)\n    }\n\n    fn state(&self, session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        self.0.state(session, node_id)\n    }\n}\n\nimpl PulsedOp for NonPulsingWrappingOp {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let input_facts =\n            inputs.iter().map(|pf| pf.to_typed_fact()).collect::<TractResult<TVec<_>>>()?;\n        let input_facts_ref = input_facts.iter().map(|f| f.as_ref()).collect::<TVec<_>>();\n        let output_facts = self.0.output_facts(&input_facts_ref)?;\n        let output_facts_ref = output_facts.iter().collect::<TVec<_>>();\n        std::mem::drop(output_facts_ref);\n        output_facts\n            .into_iter()\n            .map(|tf| Ok(PulsedFact { shape: tf.shape, datum_type: tf.datum_type, stream: None }))\n            .collect()\n    }\n\n    as_op!();\n\n    fn to_typed(&self) -> Box<dyn TypedOp> {\n        self.0.clone()\n    }\n}\n"
  },
  {
    "path": "pulse/src/ops/array/broadcast.rs",
    "content": "use crate::fact::StreamInfo;\nuse crate::internal::*;\nuse tract_pulse_opl::tract_core::ops::array::MultiBroadcastTo;\n\nregister_all!(MultiBroadcastTo: pulsify);\n\nfn pulsify(\n    op: &MultiBroadcastTo,\n    _source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    symbol: &Symbol,\n    pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    if let Some(axis) = op.shape.iter().position(|dim| dim.symbols().contains(symbol)) {\n        let full_dim = op.shape[axis].clone();\n        let fact = PulsedFact {\n            datum_type: _source.outlet_fact(node.inputs[0])?.datum_type,\n            shape: op\n                .shape\n                .iter()\n                .map(|dim| dim.substitute(symbol, pulse))\n                .collect::<TractResult<_>>()?,\n            stream: Some(StreamInfo { axis, dim: full_dim, delay: 0 }),\n        };\n        let new_op = PulsedMultibroadcastTo { fact };\n        target.wire_node(&node.name, new_op, &[mapping[&node.inputs[0]]]).map(Some)\n    } else {\n        Ok(None)\n    }\n}\n\n/// Concat with pulse along concat axis\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\nstruct PulsedMultibroadcastTo {\n    fact: PulsedFact,\n}\n\nimpl Op for PulsedMultibroadcastTo {\n    fn name(&self) -> StaticName {\n        \"PulsedMultibroadcastTo\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl TypedOp for PulsedMultibroadcastTo {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].datum_type.fact(self.fact.to_pulse_fact().shape)))\n    }\n    as_op!();\n}\n\nimpl EvalOp for PulsedMultibroadcastTo {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        self.to_typed().eval(inputs)\n    }\n}\n\nimpl PulsedOp for PulsedMultibroadcastTo {\n    fn pulsed_output_facts(&self, _inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        Ok(tvec!(self.fact.clone()))\n    }\n\n    fn to_typed(&self) -> Box<dyn TypedOp> {\n        Box::new(MultiBroadcastTo { shape: self.fact.to_pulse_fact().shape })\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/array/concat.rs",
    "content": "use crate::internal::*;\nuse crate::model::NonPulsingWrappingOp;\nuse tract_core::ops::array::{Slice, TypedConcat};\nuse tract_pulse_opl::concat::PulsedSameAxisConcat;\nuse tract_pulse_opl::ops::Delay;\nuse tract_pulse_opl::tract_core::ops::array::MultiBroadcastTo;\nuse tract_pulse_opl::tract_core::tract_data::itertools::Itertools;\n\nregister_all!(TypedConcat: pulsify);\n\nfn pulsify(\n    op: &TypedConcat,\n    source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    symbol: &Symbol,\n    _pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let pulse_facts: TVec<PulsedFact> =\n        node.inputs.iter().map(|i| target.outlet_fact(mapping[i]).unwrap().clone()).collect();\n    let (_stream_input_ix, pulse_fact) =\n        pulse_facts.iter().enumerate().find(|(_ix, pf)| pf.stream.is_some()).unwrap();\n\n    if pulse_fact.stream.as_ref().unwrap().axis == op.axis {\n        pulsify_along_concat_axis(op, source, node, target, mapping, symbol)\n    } else {\n        Ok(None)\n    }\n}\n\nfn pulsify_along_concat_axis(\n    op: &TypedConcat,\n    source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    symbol: &Symbol,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let name = &node.name;\n    let axis = op.axis;\n    let source_facts: TVec<TypedFact> =\n        node.inputs.iter().map(|i| source.outlet_fact(*i).unwrap().clone()).collect();\n    ensure!(\n        source_facts.iter().filter(|fact| fact.shape[axis].symbols().contains(symbol)).count() == 1,\n        \"Concat over pulse axis (#{axis}, {symbol:?}) expcts one single streaming input. Got: {source_facts:?}\"\n    );\n    let pulsed_inputs: TVec<OutletId> = node.inputs.iter().map(|i| mapping[i]).collect();\n    let pulse_facts: TVec<PulsedFact> = pulsed_inputs\n        .iter()\n        .map(|i| target.outlet_fact(*i).cloned())\n        .collect::<TractResult<_>>()?;\n\n    // Use the source model to identify which input carries the streaming symbol,\n    // rather than the first pulsed fact with stream info. Inputs derived from the\n    // stream (e.g. a slice selecting a single element) may also have stream info in\n    // the pulsed model but their source shape is concrete, so they are treated as\n    // pre/post rather than the main streaming input.\n    let stream_input_ix =\n        source_facts.iter().position(|f| f.shape[axis].symbols().contains(symbol)).unwrap();\n    let pulse_fact = &pulse_facts[stream_input_ix];\n    ensure!(\n        pulse_fact.stream.is_some(),\n        \"Expected pulsed fact at stream_input_ix={stream_input_ix} to be streaming: {pulse_fact:?}\"\n    );\n    let stream = pulse_fact.stream.as_ref().unwrap();\n\n    // before_len and after_len come from the source model where non-streaming\n    // inputs have concrete shapes.\n    let before_len: usize = source_facts[..stream_input_ix]\n        .iter()\n        .map(|f| f.shape[axis].to_usize())\n        .collect::<TractResult<Vec<_>>>()?\n        .into_iter()\n        .sum();\n    let after_len: usize = source_facts[stream_input_ix + 1..]\n        .iter()\n        .map(|f| f.shape[axis].to_usize())\n        .collect::<TractResult<Vec<_>>>()?\n        .into_iter()\n        .sum();\n\n    let zero = target\n        .add_const(format!(\"{name}.zero\"), Tensor::zero_scalar_dt(source_facts[0].datum_type)?)?;\n    let mut shape = pulse_fact.shape.clone();\n    shape.set(axis, 0.to_dim());\n    let empty = target.wire_node(\n        format!(\"{name}.pre\"),\n        NonPulsingWrappingOp(Box::new(MultiBroadcastTo { shape })),\n        &[zero],\n    )?[0];\n\n    // Build pre node: concat of pulsed inputs before the main stream, then sliced\n    // to before_len so the runtime tensor has the right size at axis regardless of\n    // whether the inputs are themselves pulsed (pulse-sized) or constant.\n    let pre = if stream_input_ix > 0 {\n        let pre_concat = target.wire_node(\n            format!(\"{name}.pre\"),\n            NonPulsingWrappingOp(Box::new(TypedConcat::new(axis))),\n            &pulsed_inputs.iter().take(stream_input_ix).cloned().collect_vec(),\n        )?[0];\n        target.wire_node(\n            format!(\"{name}.pre.slice\"),\n            NonPulsingWrappingOp(Box::new(Slice {\n                axis,\n                start: 0.to_dim(),\n                end: before_len.to_dim(),\n            })),\n            &[pre_concat],\n        )?[0]\n    } else {\n        empty\n    };\n\n    // Build post node: same pattern, sliced to after_len.\n    let post = if stream_input_ix + 1 < pulsed_inputs.len() {\n        let post_concat = target.wire_node(\n            format!(\"{name}.post\"),\n            NonPulsingWrappingOp(Box::new(TypedConcat::new(axis))),\n            &pulsed_inputs.iter().skip(stream_input_ix + 1).cloned().collect_vec(),\n        )?[0];\n        target.wire_node(\n            format!(\"{name}.post.slice\"),\n            NonPulsingWrappingOp(Box::new(Slice {\n                axis,\n                start: 0.to_dim(),\n                end: after_len.to_dim(),\n            })),\n            &[post_concat],\n        )?[0]\n    } else {\n        empty\n    };\n\n    let mut input = pulsed_inputs[stream_input_ix];\n    // The main stream must arrive late enough that the pre content fits before it.\n    // effective_delay = max(stream.delay, before_len).\n    let effective_delay = if stream.delay < before_len {\n        input = target.wire_node(\n            format!(\"{}.Delay\", node.name),\n            Delay::new_typed(\n                source.outlet_fact(node.inputs[stream_input_ix])?,\n                stream.axis,\n                before_len - stream.delay,\n                0,\n            ),\n            &[input],\n        )?[0];\n        before_len\n    } else {\n        stream.delay\n    };\n\n    let main_op = PulsedSameAxisConcat {\n        axis: op.axis,\n        before_len,\n        after_len,\n        input_delay: effective_delay,\n        input_len: stream.dim.clone(),\n    };\n    Ok(Some(target.wire_node(&*node.name, main_op, &[pre, input, post])?))\n}\n\nimpl PulsedOp for PulsedSameAxisConcat {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let &[_pre, fact, _post] = inputs else { bail!(\"Expect 3 inputs\") };\n        let mut fact: PulsedFact = fact.clone();\n        let stream = fact.stream.as_mut().unwrap();\n        stream.dim += (self.before_len + self.after_len).to_dim();\n        stream.delay -= self.before_len;\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/array/mask.rs",
    "content": "use crate::internal::*;\nuse tract_pulse_opl::ops::PulseMask;\n\nimpl PulsedOp for PulseMask {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        Ok(inputs.iter().cloned().cloned().collect())\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/array/mod.rs",
    "content": "use crate::internal::*;\n\nmod broadcast;\nmod concat;\nmod mask;\nmod pad;\nmod slice;\n\nregister_all_mod!(broadcast, concat, pad, slice);\n"
  },
  {
    "path": "pulse/src/ops/array/pad.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::array::{Pad, PadMode};\nuse tract_pulse_opl::ops::{Delay, PulsePad};\n\nregister_all!(Pad: pulsify);\n\nfn pulsify(\n    op: &Pad,\n    _source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    _symbol: &Symbol,\n    _pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let mut input = mapping[&node.inputs[0]];\n    let fact = target.outlet_fact(input)?.clone();\n    let stream = fact.stream.as_ref().unwrap();\n    if !op.pads.iter().enumerate().all(|(ax, &(a, b))| ax == stream.axis || (a == 0 && b == 0)) {\n        return Ok(None);\n    }\n    let (before, after) = op.pads[stream.axis];\n    let pulse = fact.pulse().unwrap();\n    let mut extra_delay = before.saturating_sub(stream.delay);\n    match op.mode {\n        PadMode::Constant(_) => (),\n        PadMode::Edge => {\n            let pulse = if let Ok(pulse) = pulse.to_usize() {\n                pulse\n            } else {\n                bail!(\"Edge padding can only by pulsified with concrete integer values\")\n            };\n            if before < pulse {\n                let start_offset = (stream.delay + extra_delay) % pulse;\n                if before > start_offset {\n                    extra_delay += before - start_offset;\n                }\n            } else {\n                bail!(\n                    \"Edge padding mode needs pulse strictly bigger than left padding (pulse={} padding={})\",\n                    pulse,\n                    before\n                )\n            }\n        }\n        PadMode::Reflect => bail!(\"Reflect padding mode pulsing is not supported\"),\n    };\n    if extra_delay > 0 {\n        input = target.wire_node(\n            format!(\"{}.Delay\", node.name),\n            Delay::new_typed(&(&fact).into(), stream.axis, extra_delay, 0),\n            &[input],\n        )?[0];\n    }\n    let op = PulsePad {\n        axis: stream.axis,\n        before,\n        after: after.into(),\n        begin_input: stream.delay + extra_delay,\n        end_input: stream.delay.to_dim() + extra_delay + &stream.dim,\n        mode: op.mode.clone(),\n        overlap: 0,\n    };\n    Ok(Some(target.wire_node(&*node.name, op, &[input])?))\n}\n\nimpl PulsedOp for PulsePad {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let mut fact = inputs[0].clone();\n        let stream = fact.stream.as_mut().unwrap();\n        stream.dim += self.before.to_dim() + &self.after;\n        stream.delay -= self.before;\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/array/slice.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::array::Slice;\n\nregister_all!(Slice: pulsify);\n\nfn pulsify(\n    op: &Slice,\n    _source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    symbol: &Symbol,\n    pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let input = mapping[&node.inputs[0]];\n    let fact = target.outlet_fact(input)?.clone();\n    let stream = fact.stream.with_context(|| {\n        format!(\n            \"Unexpected streamless fact in pulsify {node}\\ninput:{:?}\",\n            target.outlet_fact(input).unwrap()\n        )\n    })?;\n    if op.axis == stream.axis {\n        let start = op.start.substitute(symbol, pulse)?;\n        let skip = start.to_usize()?;\n        let take = node.outputs[0].fact.shape[op.axis].clone();\n        let op = PulsedAxisSlice { axis: op.axis, skip, take };\n        Ok(Some(target.wire_node(&*node.name, op, &[input])?))\n    } else {\n        Ok(None)\n    }\n}\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\npub struct PulsedAxisSlice {\n    pub axis: usize,\n    pub skip: usize,\n    pub take: TDim,\n}\n\nimpl Op for PulsedAxisSlice {\n    fn name(&self) -> StaticName {\n        \"PulsedAxisSlice\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis:{}, skip:{} take:{}\", self.axis, self.skip, self.take)])\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for PulsedAxisSlice {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(inputs)\n    }\n}\n\nimpl PulsedOp for PulsedAxisSlice {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let mut fact = inputs[0].clone();\n        let stream = fact.stream.as_mut().unwrap();\n        stream.delay += self.skip;\n        stream.dim = self.take.clone();\n        Ok(tvec!(fact))\n    }\n\n    fn to_typed(&self) -> Box<dyn TypedOp> {\n        Box::<tract_pulse_opl::tract_core::ops::identity::Identity>::default()\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/cnn/conv.rs",
    "content": "use crate::internal::*;\nuse crate::ops::cnn::pools::pulsify_pooled_input;\nuse tract_core::ops::cnn::Conv;\n\nregister_all!(Conv: pulsify);\n\nfn pulsify(\n    op: &Conv,\n    source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    _symbol: &Symbol,\n    _pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let fact = target.outlet_fact(mapping[&node.inputs[0]])?;\n    let zero = Tensor::zero_scalar_dt(fact.datum_type)?;\n    if let Some((wire, pool_spec)) =\n        pulsify_pooled_input(&op.pool_spec, source, node, target, mapping, Some(zero))?\n    {\n        let mut wires: TVec<_> = node.inputs.iter().map(|i| mapping[i]).collect();\n        wires[0] = wire;\n        Ok(Some(target.wire_node(&node.name, Conv { pool_spec, ..op.clone() }, &wires)?))\n    } else {\n        Ok(None)\n    }\n}\n\nimpl PulsedOp for Conv {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let dt = self.q_params.unwrap_or(inputs[0].datum_type);\n        super::pools::pulsed_output_facts(&self.pool_spec, inputs, dt)\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/cnn/deconv.rs",
    "content": "use crate::internal::*;\nuse tract_core::num_traits::Zero;\nuse tract_core::ops::cnn::Deconv;\nuse tract_core::ops::cnn::PaddingSpec;\nuse tract_pulse_opl::ops::DeconvDelay;\nuse tract_pulse_opl::ops::PulseMask;\n\nregister_all!(Deconv: pulsify);\n\nfn pulsify(\n    op: &Deconv,\n    source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    _symbol: &Symbol,\n    _pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let fact = target.outlet_fact(mapping[&node.inputs[0]])?.clone();\n    let pulse = fact.pulse().unwrap();\n    let stream = fact.stream.as_ref().unwrap();\n    let c_axis = op.pool_spec.data_format.shape(&fact.shape)?.c_axis();\n    if c_axis == stream.axis {\n        bail!(\"Pulsification on C axis is not supported\");\n    }\n    if op\n        .axes_mapping(&source.node_input_facts(node.id)?, &source.node_output_facts(node.id)?)?\n        .axis((InOut::In(0), stream.axis))?\n        .outputs[0]\n        .len()\n        == 1\n    {\n        // general case for invariants will manage\n        return Ok(None);\n    }\n    let geo_axis = stream.axis - op.pool_spec.data_format.h_axis();\n    let stride = op.pool_spec.stride(geo_axis);\n    let mut pulse_op = op.clone();\n    pulse_op.adjustments[geo_axis] = stride - 1;\n    pulse_op.pool_spec.padding = PaddingSpec::Valid;\n    let mut wire = tvec![mapping[&node.inputs[0]]];\n    let mask = PulseMask {\n        axis: stream.axis,\n        begin: stream.delay,\n        end: stream.dim.clone() + stream.delay,\n        value: Tensor::zero_scalar_dt(fact.datum_type)?,\n    };\n    wire = target.wire_node(format!(\"{}.mask\", node.name), mask, &wire)?;\n    wire.push(mapping[&node.inputs[1]]);\n    wire.push(mapping[&node.inputs[2]]);\n    wire = target.wire_node(format!(\"{}.deconv\", node.name), pulse_op, &wire)?;\n    let overlap = overlap(stream.axis, op);\n    let deconv_input_dim = (stream.dim.clone() - 1) * stride + 1;\n    let output_shape = tract_core::ops::cnn::deconv::output_shape(\n        &op.pool_spec,\n        &fact.streaming_shape(),\n        &op.adjustments,\n    )?;\n    let kernel_spatial_shape = &op.pool_spec.kernel_shape;\n    let shape = op.pool_spec.data_format.shape(fact.streaming_shape())?;\n    let paddings = op.pool_spec.padding.compute_for_deconv(\n        shape.hw_dims(),\n        kernel_spatial_shape,\n        &op.pool_spec.dilations(),\n        &op.pool_spec.strides(),\n        &op.adjustments,\n    )?;\n    wire = target.wire_node(\n        &node.name,\n        DeconvDelay {\n            axis: stream.axis,\n            overlap,\n            delay: paddings[geo_axis].pad_before.to_usize()? + stream.delay,\n            deconv_input_dim,\n            stride,\n            pulse: pulse.to_owned(),\n            deconv_output_dim: output_shape[stream.axis].clone(),\n        },\n        &wire,\n    )?;\n\n    for (geo_axis, padding) in paddings.iter().enumerate() {\n        if !padding.pad_before.is_zero() || !padding.pad_after.is_zero() {\n            let axis = geo_axis + shape.h_axis();\n            if axis == stream.axis {\n                continue;\n            };\n            let op = crate::model::PulseWrappingOp(Box::new(tract_core::ops::array::Slice::new(\n                axis,\n                padding.pad_before.clone(),\n                padding.deconvoluted.clone() + &padding.pad_before,\n            )));\n            wire = target.wire_node(format!(\"{}.padding.{}\", node.name, geo_axis), op, &wire)?;\n        }\n    }\n\n    Ok(Some(wire))\n}\n\nfn overlap(pulse_axis: usize, op: &Deconv) -> usize {\n    let geo_axis = pulse_axis - op.pool_spec.data_format.h_axis();\n    (op.pool_spec.kernel_shape[geo_axis] - 1) * op.pool_spec.dilation(geo_axis)\n}\n\nimpl PulsedOp for Deconv {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let mut fact = inputs[0].clone();\n        let stream = fact.stream.as_mut().unwrap();\n        let overlap = overlap(stream.axis, self);\n        let geo_axis = stream.axis - self.pool_spec.data_format.h_axis();\n        let stride = self.pool_spec.stride(geo_axis);\n        let mut output_shape = tract_core::ops::cnn::deconv::output_shape(\n            &self.pool_spec,\n            &inputs[0].streaming_shape(),\n            &self.adjustments,\n        )?;\n        stream.dim = output_shape[stream.axis].clone();\n        let pulse_len = fact.shape[stream.axis].clone() * stride;\n        output_shape[stream.axis] = pulse_len + overlap;\n        let c_axis = self.pool_spec.data_format.shape(&output_shape)?.c_axis();\n        output_shape[c_axis] = self.pool_spec.output_channels.into();\n        fact.shape = output_shape.into();\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n\nimpl PulsedOp for DeconvDelay {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let mut fact = inputs[0].clone();\n        let stream = fact.stream.as_mut().unwrap();\n        stream.dim = self.deconv_output_dim.clone();\n        let pulse_len = fact.shape[stream.axis].clone();\n        fact.shape.set(stream.axis, pulse_len - self.overlap);\n        stream.delay = self.delay;\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/cnn/mod.rs",
    "content": "use crate::internal::*;\n\nmod conv;\nmod deconv;\nmod pools;\n\nregister_all_mod!(conv, deconv, pools);\n"
  },
  {
    "path": "pulse/src/ops/cnn/pools.rs",
    "content": "use crate::internal::*;\nuse tract_core::num_traits::Zero;\nuse tract_core::ops::cnn::{MaxPool, PaddingSpec, PoolSpec, SumPool};\n\nregister_all!(MaxPool: pulsify_max_pool, SumPool: pulsify_sum_pool);\n\nfn pulsify_max_pool(\n    op: &MaxPool,\n    source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    _symbol: &Symbol,\n    _pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    fn min_value<D: Datum + tract_core::num_traits::Bounded>() -> Tensor {\n        tensor0(D::min_value())\n    }\n    let fact = target.outlet_fact(mapping[&node.inputs[0]])?;\n    let min = dispatch_numbers!(min_value(fact.datum_type)());\n    if let Some((wire, pool_spec)) =\n        pulsify_pooled_input(&op.pool_spec, source, node, target, mapping, Some(min))?\n    {\n        Ok(Some(target.wire_node(&node.name, MaxPool { pool_spec, ..op.clone() }, &[wire])?))\n    } else {\n        Ok(None)\n    }\n}\n\nfn pulsify_sum_pool(\n    op: &SumPool,\n    source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    _symbol: &Symbol,\n    _pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    if let Some((wire, pool_spec)) =\n        pulsify_pooled_input(&op.pool_spec, source, node, target, mapping, None)?\n    {\n        Ok(Some(target.wire_node(&node.name, SumPool { pool_spec, ..op.clone() }, &[wire])?))\n    } else {\n        Ok(None)\n    }\n}\n\nimpl PulsedOp for SumPool {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        pulsed_output_facts(&self.pool_spec, inputs, inputs[0].datum_type)\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n\nimpl PulsedOp for MaxPool {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let mut facts = pulsed_output_facts(&self.pool_spec, inputs, inputs[0].datum_type)?;\n        if let Some(idt) = self.with_index_outputs {\n            facts.push(facts[0].clone());\n            facts[1].datum_type = idt;\n        }\n        Ok(facts)\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n\npub fn pulsed_output_facts(\n    spec: &PoolSpec,\n    inputs: &[&PulsedFact],\n    output_dt: DatumType,\n) -> TractResult<TVec<PulsedFact>> {\n    let ishape = spec.data_format.shape(&inputs[0].shape)?;\n    let computed = spec.padding.compute(\n        ishape.hw_dims(),\n        &spec.kernel_shape,\n        &spec.dilations(),\n        &spec.strides(),\n    );\n    let spatial_dims = computed.into_iter().map(|d| d.convoluted).collect::<TVec<TDim>>();\n    let oshape = spec.data_format.from_n_c_hw(\n        ishape.n().cloned().unwrap_or_else(|| 1.to_dim()),\n        spec.output_channels.into(),\n        spatial_dims,\n    )?;\n    let mut fact = inputs[0].clone();\n    let stream = fact.stream.as_mut().unwrap();\n    let input_shape = spec.data_format.shape(&*fact.shape)?;\n    let geo_axis = stream.axis - input_shape.h_axis();\n    let dilation = spec.dilations.as_ref().map(|d| d[geo_axis]).unwrap_or(1);\n    let kernel_len = (spec.kernel_shape[geo_axis] - 1) * dilation;\n    let stride = spec.strides.as_ref().and_then(|v| v.get(geo_axis).cloned()).unwrap_or(1);\n    stream.delay /= stride;\n    stream.dim = (stream.dim.clone() - kernel_len.to_dim()).div_ceil(stride as _);\n    fact.shape = oshape.shape.into();\n    fact.datum_type = output_dt;\n    Ok(tvec!(fact))\n}\n\npub fn pulsify_pooled_input(\n    spec: &PoolSpec,\n    _source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    padding_value: Option<Tensor>,\n) -> TractResult<Option<(OutletId, PoolSpec)>> {\n    let mut wire = mapping[&node.inputs[0]];\n    let input_fact: PulsedFact = target.outlet_fact(wire)?.clone();\n    let input_stream = input_fact.stream.as_ref().unwrap();\n    let input_shape = spec.data_format.shape(input_fact.shape.clone())?;\n    if Some(input_stream.axis) == input_shape.n_axis() {\n        return Ok(None);\n    }\n    if input_stream.axis == input_shape.c_axis() {\n        bail!(\"Can not pulsify cnn pooling ops along the input channel axis\");\n    }\n\n    let geo_axis = input_stream.axis - input_shape.h_axis();\n    let stride = spec.strides.as_ref().and_then(|v| v.get(geo_axis).cloned()).unwrap_or(1);\n    let pulse = input_fact.pulse().unwrap();\n    if !(pulse.to_owned() % (stride as i64)).is_zero() {\n        bail!(\"Pulsification requires pulse ({}) to be a stride ({}) multiple\", pulse, stride)\n    }\n\n    let dilation = spec.dilations.as_ref().map(|d| d[geo_axis]).unwrap_or(1);\n    let kernel_len = (spec.kernel_shape[geo_axis] - 1) * dilation;\n    let overlap = (kernel_len + 1).saturating_sub(stride);\n\n    let computed_padding = spec.padding.compute_one(\n        geo_axis,\n        &input_stream.dim,\n        spec.kernel_shape[geo_axis],\n        spec.dilation(geo_axis),\n        spec.stride(geo_axis),\n    );\n\n    let before = computed_padding.pad_before.to_usize()?;\n    let early = input_stream.delay as isize + overlap as isize - before as isize;\n    let mut extra_delay = if early < 0 { (-early) as usize } else { 0 };\n    let delayed_input = input_stream.delay + overlap + extra_delay - before;\n    let misalignment = delayed_input % stride;\n    if misalignment > 0 {\n        extra_delay += stride - misalignment;\n    }\n\n    if overlap > 0 || extra_delay > 0 {\n        wire = target.wire_node(\n            format!(\"{}.delay\", node.name),\n            tract_pulse_opl::ops::Delay::new_typed(\n                &(&input_fact).into(),\n                input_stream.axis,\n                extra_delay,\n                overlap,\n            ),\n            &[wire],\n        )?[0];\n    }\n\n    let has_padding =\n        !computed_padding.pad_before.is_zero() || !computed_padding.pad_after.is_zero();\n\n    if has_padding {\n        use tract_core::ops::array::PadMode;\n        let value = if let Some(tensor) = padding_value {\n            tensor.into_arc_tensor()\n        } else {\n            bail!(\"No padding value for streaming pool operation\");\n        };\n        let op = tract_pulse_opl::ops::PulsePad {\n            axis: input_stream.axis,\n            before,\n            after: computed_padding.pad_after,\n            begin_input: input_stream.delay + extra_delay + overlap,\n            end_input: input_stream.dim.clone()\n                + input_stream.delay\n                + extra_delay\n                + overlap.to_dim(),\n            mode: PadMode::Constant(value),\n            overlap,\n        };\n        wire = target.wire_node(format!(\"{}.pulse-pad\", node.name), op, &[wire])?[0];\n    }\n\n    if has_padding {\n        let mut bef = tvec!();\n        let mut aft = tvec!();\n        for ix in 0..input_shape.hw_rank() {\n            if ix == geo_axis {\n                bef.push(0);\n                aft.push(0);\n            } else {\n                let c = spec.padding.compute_one(\n                    ix,\n                    &input_shape.hw_dims()[ix],\n                    spec.kernel_shape[ix],\n                    spec.dilations()[ix],\n                    spec.strides()[ix],\n                );\n                bef.push(c.pad_before.to_usize()?);\n                aft.push(c.pad_after.to_usize()?);\n            };\n        }\n        Ok(Some((\n            wire,\n            PoolSpec { padding: PaddingSpec::ExplicitOnnxPool(bef, aft, false), ..spec.clone() },\n        )))\n    } else {\n        Ok(Some((wire, spec.clone())))\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use tract_pulse_opl::tract_core::ops::cnn::{Conv, PoolSpec};\n    use tract_pulse_opl::tract_nnef::internal::*;\n\n    use crate::model::{PulsedModel, PulsedModelExt};\n\n    #[test]\n    fn left_padded_conv_wo_delay() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let stream_sym = model.symbols.sym(\"S\");\n        let stream_dim = stream_sym.to_dim();\n        let source = model.add_source(\"source\", f32::fact(dims!(1, stream_dim)))?;\n        let kernel = model.add_const(\"kernel\", rctensor3(&[[[1f32, 2f32]]]))?;\n        let bias = model.add_const(\"bias\", rctensor0(0f32))?;\n        let conv = model.wire_node(\n            \"conv\",\n            Conv {\n                pool_spec: PoolSpec {\n                    data_format: tract_core::ops::nn::DataFormat::CHW,\n                    dilations: None,\n                    strides: None,\n                    kernel_shape: tvec![2],\n                    padding: tract_core::ops::cnn::PaddingSpec::ExplicitOnnxPool(\n                        tvec![1],\n                        tvec![0],\n                        false,\n                    ),\n                    input_channels: 1,\n                    output_channels: 1,\n                },\n                kernel_fmt: tract_core::ops::cnn::KernelFormat::OIHW,\n                group: 1,\n                q_params: None,\n            },\n            &[source, kernel, bias],\n        )?;\n        model.select_output_outlets(&conv)?;\n        let pulsed = PulsedModel::new(&model, stream_sym, &1.to_dim())?;\n        let output_fact = pulsed.output_fact(0)?;\n        assert_eq!(output_fact.stream.as_ref().unwrap().delay, 0);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "pulse/src/ops/delay.rs",
    "content": "use crate::internal::*;\nuse tract_pulse_opl::ops::Delay;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_delay)\n}\n\nfn ser_delay(ast: &mut IntoAst, node: &TypedNode, op: &Delay) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\n        \"tract_pulse_delay\",\n        &[wire],\n        &[\n            (\"axis\", numeric(op.axis)),\n            (\"delay\", numeric(op.delay)),\n            (\"overlap\", numeric(op.overlap)),\n        ],\n    )))\n}\n\nimpl PulsedOp for Delay {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        ensure!(inputs.len() == 1);\n        let mut fact = inputs[0].clone();\n        let stream = fact.stream.as_mut().unwrap();\n        fact.shape.set(self.axis, fact.shape[self.axis].clone() + self.overlap);\n        stream.delay += self.delay + self.overlap;\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n\n#[cfg(test)]\nmod test {\n    use crate::fact::StreamInfo;\n\n    use super::*;\n\n    fn test_pulse_delay_over(pulse: usize, delay: usize, overlap: usize) {\n        let mut model = PulsedModel::default();\n        let stream_dim = model.symbols.sym(\"S\").to_dim();\n        let fact1 = PulsedFact {\n            datum_type: u8::datum_type(),\n            shape: (&[pulse]).into(),\n            stream: Some(StreamInfo { axis: 0, dim: stream_dim, delay: 0 }),\n        };\n        let source = model.add_source(\"source\", fact1.clone()).unwrap();\n        model\n            .wire_node(\n                \"delay\",\n                Delay::new_typed(&(&fact1).into(), fact1.stream.unwrap().axis, delay, overlap),\n                &[source],\n            )\n            .unwrap();\n        model.auto_outputs().unwrap();\n\n        let plan = model.into_runnable().unwrap();\n        let mut state = plan.spawn().unwrap();\n\n        for i in 0..5 {\n            let input: Vec<u8> = (pulse * i..(pulse * (i + 1))).map(|a| a as u8).collect();\n            let expect: Vec<u8> = (pulse * i..(pulse * (i + 1) + overlap))\n                .map(|i| i.saturating_sub(delay + overlap) as u8)\n                .collect();\n            let output = state.run(tvec!(tensor1(&input).into())).unwrap();\n            let skip = (delay + overlap).saturating_sub(i * pulse).min(pulse + overlap);\n            assert_eq!(\n                &output[0].try_as_plain().unwrap().as_slice::<u8>().unwrap()[skip..],\n                &expect[skip..]\n            );\n        }\n    }\n\n    #[test]\n    fn sub_pulse() {\n        test_pulse_delay_over(4, 1, 0);\n    }\n\n    #[test]\n    fn supra_pulse() {\n        test_pulse_delay_over(4, 5, 0);\n    }\n\n    #[test]\n    fn sub_pulse_context() {\n        test_pulse_delay_over(4, 0, 2);\n    }\n\n    #[test]\n    fn supra_pulse_context() {\n        test_pulse_delay_over(4, 0, 6);\n    }\n\n    #[test]\n    fn test_two_delays() {\n        let pulse = 4usize;\n        let mut model = PulsedModel::default();\n        let stream_dim = model.symbols.sym(\"S\").to_dim();\n        let fact_0 = PulsedFact {\n            datum_type: u8::datum_type(),\n            shape: (&[pulse]).into(),\n            stream: Some(StreamInfo { axis: 0, dim: stream_dim, delay: 0 }),\n        };\n        let stream = fact_0.stream.as_ref().unwrap();\n        let source = model.add_source(\"source\", fact_0.clone()).unwrap();\n        let delay_1 = model\n            .wire_node(\"delay-1\", Delay::new_typed(&(&fact_0).into(), stream.axis, 2, 0), &[source])\n            .unwrap()[0];\n        let fact_1 = model.outlet_fact(delay_1).unwrap().clone();\n        let delay_2 = model\n            .wire_node(\n                \"delay-1\",\n                Delay::new_typed(&(&fact_1).into(), stream.axis, 2, 0),\n                &[delay_1],\n            )\n            .unwrap();\n        model.select_output_outlets(&delay_2).unwrap();\n\n        let plan = model.into_runnable().unwrap();\n        let mut state = plan.spawn().unwrap();\n\n        for i in 0..5 {\n            let input: Vec<u8> = (pulse * i..(pulse * (i + 1))).map(|a| a as u8).collect();\n            let expect: Vec<u8> =\n                (pulse * i..(pulse * (i + 1))).map(|i| i.saturating_sub(4) as u8).collect();\n            let skip = 4usize.saturating_sub(i * pulse).min(pulse);\n            let output = state.run(tvec!(tensor1(&input).into())).unwrap();\n            assert_eq!(\n                &output[0].try_as_plain().unwrap().as_slice::<u8>().unwrap()[skip..],\n                &expect[skip..]\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "pulse/src/ops/downsample.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::Downsample;\nuse tract_pulse_opl::ops::PulsedAxisSlice;\nuse tract_pulse_opl::tract_nnef::tract_num_traits::Zero;\n\nregister_all!(Downsample: pulsify);\n\nfn pulsify(\n    op: &Downsample,\n    _source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    _symbol: &Symbol,\n    _pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let input = mapping[&node.inputs[0]];\n    let fact = target.outlet_fact(input)?.clone();\n    if let Some(stream) = fact.stream.as_ref() {\n        if stream.axis != op.axis {\n            return Ok(None);\n        }\n        let stride = if op.stride > 0 {\n            op.stride as usize\n        } else {\n            bail!(\"Negative strides are not causal, can not pulsify.\")\n        };\n        let pulse = fact.pulse().unwrap();\n        if !(pulse.clone() % stride).is_zero() {\n            bail!(\"Pulsification requires pulse ({}) to be a stride ({}) multiple\", pulse, stride)\n        }\n        let mut wire = tvec!(input);\n        let first_offset = stream.delay + op.modulo;\n        let new_op = Downsample { modulo: first_offset % stride, axis: op.axis, stride: op.stride };\n        wire = target.wire_node(format!(\"{}.downsample\", node.name), new_op, &wire)?;\n        wire = target.wire_node(\n            &node.name,\n            PulsedAxisSlice {\n                axis: stream.axis,\n                skip: first_offset / stride,\n                take: (stream.dim.to_owned() - op.modulo).divceil(stride),\n            },\n            &wire,\n        )?;\n        target.rename_node(wire[0].node, &node.name)?;\n        Ok(Some(wire))\n    } else {\n        Ok(None)\n    }\n}\n\nimpl PulsedOp for Downsample {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let mut fact = inputs[0].clone();\n        let stream = fact.stream.as_mut().unwrap();\n        fact.shape.set(self.axis, fact.shape[self.axis].clone() / self.stride as usize);\n        stream.dim = (stream.dim.clone() + stream.delay).divceil(self.stride as _);\n        stream.delay = 0;\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/dummy.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::dummy::Dummy;\n\nimpl PulsedOp for Dummy {\n    fn pulsed_output_facts(&self, _inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        Ok(tvec!())\n    }\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/fft.rs",
    "content": "use crate::fact::StreamInfo;\nuse crate::internal::*;\nuse tract_core::ops::fft::Stft;\nuse tract_pulse_opl::ops::Delay;\n\nregister_all!(Stft: pulsify);\n\nfn pulsify(\n    op: &Stft,\n    _source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    _symbol: &Symbol,\n    _pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let mut wire = mapping[&node.inputs[0]];\n    let input_fact = target.outlet_fact(wire)?.clone();\n\n    let stream = match &input_fact.stream {\n        Some(s) => s.clone(),\n        None => return Ok(None),\n    };\n\n    if stream.axis != op.axis {\n        return Ok(None);\n    }\n\n    let overlap = op.frame - op.stride;\n\n    // Compute extra delay so that (stream.delay + overlap + extra_delay) % stride == 0\n    let delayed = stream.delay + overlap;\n    let misalignment = delayed % op.stride;\n    let extra_delay = if misalignment > 0 { op.stride - misalignment } else { 0 };\n\n    if overlap > 0 || extra_delay > 0 {\n        wire = target.wire_node(\n            format!(\"{}.delay\", node.name),\n            Delay::new_typed(&(&input_fact).into(), stream.axis, extra_delay, overlap),\n            &[wire],\n        )?[0];\n    }\n\n    Ok(Some(target.wire_node(&node.name, op.clone(), &[wire])?))\n}\n\nimpl PulsedOp for Stft {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let input = inputs[0];\n        let stream = input.stream.as_ref().unwrap();\n\n        // pulse after delay = original_pulse + overlap\n        let pulse = &input.shape[stream.axis];\n        let out_pulse = (pulse.clone() - self.frame) / self.stride + 1;\n\n        let mut shape = input.shape.to_tvec();\n        shape[self.axis] = out_pulse;\n        shape.insert(self.axis + 1, self.frame.to_dim());\n\n        Ok(tvec!(PulsedFact {\n            datum_type: input.datum_type,\n            shape: shape.into(),\n            stream: Some(StreamInfo {\n                axis: self.axis,\n                dim: (stream.dim.clone() - self.frame) / self.stride + 1,\n                delay: stream.delay / self.stride,\n            }),\n        }))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/identity.rs",
    "content": "\n\nimpl PulsedOp for Identity {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/mask.rs",
    "content": "\n"
  },
  {
    "path": "pulse/src/ops/mod.rs",
    "content": "#![allow(clippy::collapsible_if)]\nuse std::any::Any;\nuse std::sync::RwLock;\n\nuse crate::internal::*;\nuse lazy_static::lazy_static;\nuse tract_pulse_opl::ops::Delay;\n\npub mod array;\npub mod cnn;\npub mod delay;\npub mod downsample;\npub mod dummy;\npub mod fft;\npub mod mask;\npub mod scan;\npub mod slice;\npub mod source;\n\npub(crate) fn sync_inputs(\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n) -> TractResult<TVec<OutletId>> {\n    let mut max_delay = 0;\n    for input in &node.inputs {\n        let fact = target.outlet_fact(mapping[input])?;\n        if let Some(stream) = &fact.stream {\n            max_delay = max_delay.max(stream.delay);\n        }\n    }\n    let mut inputs = tvec!();\n    for input in &node.inputs {\n        let mut input = mapping[input];\n        let fact = target.outlet_fact(input)?.clone();\n        if let Some(stream) = &fact.stream {\n            if stream.delay < max_delay {\n                let add_delay = max_delay - stream.delay;\n                let delay_axis = stream.axis;\n                input = target.wire_node(\n                    format!(\"{}.Delay\", &*node.name),\n                    Delay::new_typed(&fact.into(), delay_axis, add_delay, 0),\n                    &[input],\n                )?[0];\n            }\n        }\n        inputs.push(input);\n    }\n    Ok(inputs)\n}\n\nregister_all_mod!(array, cnn, downsample, fft, scan, source);\n\ntype PulsifierFn = fn(\n    &TypedModel,\n    &TypedNode,\n    &mut PulsedModel,\n    &HashMap<OutletId, OutletId>,\n    &Symbol,\n    &TDim,\n) -> TractResult<Option<TVec<OutletId>>>;\n\npub struct OpPulsifier {\n    pub type_id: std::any::TypeId,\n    pub name: &'static str,\n    pub func: PulsifierFn,\n}\n\nimpl OpPulsifier {\n    pub fn inventory() -> Arc<RwLock<HashMap<TypeId, OpPulsifier>>> {\n        lazy_static! {\n            static ref INVENTORY: Arc<RwLock<HashMap<TypeId, OpPulsifier>>> = {\n                let mut it = HashMap::default();\n                register_all(&mut it);\n                Arc::new(RwLock::new(it))\n            };\n        };\n        (*INVENTORY).clone()\n    }\n\n    pub fn register<T: Any>(func: PulsifierFn) -> TractResult<()> {\n        let inv = Self::inventory();\n        let mut inv = inv.write().map_err(|e| anyhow!(\"Fail to lock inventory {e}\"))?;\n        inv.insert(\n            std::any::TypeId::of::<T>(),\n            OpPulsifier {\n                type_id: std::any::TypeId::of::<T>(),\n                name: std::any::type_name::<T>(),\n                func,\n            },\n        );\n        Ok(())\n    }\n\n    pub fn pulsify(\n        source: &TypedModel,\n        node: &TypedNode,\n        target: &mut PulsedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n        symbol: &Symbol,\n        pulse: &TDim,\n    ) -> TractResult<Option<TVec<OutletId>>> {\n        let inv = Self::inventory();\n        let inv = inv.read().map_err(|e| anyhow!(\"Fail to lock inventory {e}\"))?;\n        if let Some(pulsifier) = inv.get(&(*node.op).type_id()) {\n            if let Some(pulsified) = (pulsifier.func)(source, node, target, mapping, symbol, pulse)?\n            {\n                return Ok(Some(pulsified));\n            }\n        }\n        Ok(None)\n    }\n}\n\npub trait PulsedOp:\n    Op + fmt::Debug + tract_core::dyn_clone::DynClone + Send + Sync + 'static + Downcast + EvalOp\n{\n    /// Reinterpret the PulsedOp as an Op.\n    fn as_op(&self) -> &dyn Op;\n\n    /// Reinterpret the PulsedOp as an Op, mutably.\n    fn as_op_mut(&mut self) -> &mut dyn Op;\n\n    /// Reinterpret the PulsedOp as an TypedOp.\n    fn to_typed(&self) -> Box<dyn TypedOp>;\n\n    /// Deduce output facts from input facts.\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>>;\n}\n\ntract_core::dyn_clone::clone_trait_object!(PulsedOp);\n\nimpl<O: PulsedOp> From<O> for Box<dyn PulsedOp> {\n    fn from(it: O) -> Box<dyn PulsedOp> {\n        Box::new(it)\n    }\n}\n\nimpl AsMut<dyn Op> for Box<dyn PulsedOp> {\n    fn as_mut(&mut self) -> &mut dyn Op {\n        self.as_op_mut()\n    }\n}\n\nimpl AsRef<dyn Op> for dyn PulsedOp {\n    fn as_ref(&self) -> &dyn Op {\n        self.as_op()\n    }\n}\n\nimpl AsRef<dyn Op> for Box<dyn PulsedOp> {\n    fn as_ref(&self) -> &dyn Op {\n        self.as_op()\n    }\n}\n\nimpl AsMut<dyn Op> for dyn PulsedOp {\n    fn as_mut(&mut self) -> &mut dyn Op {\n        self.as_op_mut()\n    }\n}\n\nimpl std::fmt::Display for Box<dyn PulsedOp> {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        write!(fmt, \"{}\", self.name())\n    }\n}\n\nimpl<'a> From<&'a Box<dyn PulsedOp>> for Box<dyn TypedOp> {\n    fn from(op: &'a Box<dyn PulsedOp>) -> Box<dyn TypedOp> {\n        op.to_typed()\n    }\n}\n"
  },
  {
    "path": "pulse/src/ops/scan.rs",
    "content": "use crate::fact::StreamInfo;\nuse crate::internal::*;\nuse tract_core::ops::scan::{InputMapping, Scan};\n\nregister_all!(Scan: pulsify);\n\nfn pulsify(\n    op: &Scan,\n    source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    mapping: &HashMap<OutletId, OutletId>,\n    symbol: &Symbol,\n    pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    /*\n\n        dbg!(source.node_axes_mapping(node.id)?.to_string());\n        for input_id in &node.inputs {\n            dbg!(target.outlet_fact(mapping[input_id]))?;\n        }\n        for input_id in 0..node.inputs.len() {\n            let input = mapping[&node.inputs[input_id]];\n            let input_fact = target.outlet_fact(input)?;\n            if let Some(info) = op.input_mapping[input_id].as_scan() {\n                if info.chunk < 0 {\n                    bail!(\"Can not pulsify a backward scan.\")\n                }\n                if input_fact.stream.as_ref().context(\"scan on non-streamed input\")?.axis != info.axis {\n                    bail!(\"Scan pulsification limited to scanning axis\");\n                }\n            }\n        }\n    */\n\n    let pulse_inputs = node.inputs.iter().map(|i| mapping[i]).collect::<TVec<_>>();\n\n    let axes_mapping = source.node_axes_mapping(node.id)?;\n    let first_scan_slot = op.input_mapping.iter().position(InputMapping::is_scan).unwrap();\n    let first_scan_axis =\n        target.outlet_fact(pulse_inputs[first_scan_slot])?.stream.as_ref().unwrap().axis;\n    let scan_axis = axes_mapping.axis((InOut::In(first_scan_slot), first_scan_axis))?;\n    if first_scan_axis == op.input_mapping[first_scan_slot].as_scan().unwrap().axis {\n        let mut op = op.clone();\n        op.skip = target.outlet_fact(pulse_inputs[first_scan_slot])?.stream.as_ref().unwrap().delay;\n        for om in op.output_mapping.iter_mut() {\n            if om.scan.is_some() {\n                om.full_dim_hint = None;\n            }\n        }\n        Ok(Some(target.wire_node(&*node.name, op, &pulse_inputs)?))\n    } else if scan_axis.outputs.iter().all(|x| x.len() == 1) {\n        let body = PulsedModel::new(&op.body, symbol.clone(), pulse)?.into_typed()?;\n        let mut new_op = Scan::new(body, op.input_mapping.clone(), op.output_mapping.clone(), 0)?;\n        new_op.reset_every_turn = true;\n        target.wire_node(&node.name, new_op, &pulse_inputs).map(Some)\n    } else {\n        todo!(\"Unsupported pulsification\")\n    }\n}\n\nimpl PulsedOp for Scan {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let outer_output_count = self\n            .output_mapping\n            .iter()\n            .map(|om| om.scan.map(|s| s.0).unwrap_or(0).max(om.last_value_slot.unwrap_or(0)))\n            .max()\n            .context(\"no output?\")?\n            + 1;\n\n        let first_scan_slot = self.input_mapping.iter().position(InputMapping::is_scan).unwrap();\n        let first_pulse_axis = inputs[first_scan_slot].stream.as_ref().unwrap().axis;\n        let first_scan_axis = self.input_mapping[first_scan_slot].as_scan().as_ref().unwrap().axis;\n        let tracking = self.body.axes_mapping()?;\n        let pulse_axis = tracking.axis((InOut::In(first_scan_slot), first_pulse_axis))?;\n        let mut facts = tvec!();\n        for output_slot in 0..outer_output_count {\n            let (output_body_ix, output_mapping) = self\n                .output_mapping\n                .iter()\n                .enumerate()\n                .find(|(_ix, om)| om.scan.map(|s| s.0) == Some(output_slot))\n                .context(\"Scan pulse only supports full outputs\")?;\n            let output_body_fact = self.body.output_fact(output_body_ix)?;\n            let fact = if first_scan_axis == first_pulse_axis {\n                let shape: ShapeFact = output_body_fact\n                    .shape\n                    .iter()\n                    .enumerate()\n                    .map(|(axis, d)| {\n                        if axis == output_mapping.scan.unwrap().1.axis {\n                            inputs[first_scan_slot].pulse().unwrap().to_dim()\n                        } else {\n                            d.clone()\n                        }\n                    })\n                    .collect();\n                PulsedFact {\n                    datum_type: output_body_fact.datum_type,\n                    shape,\n                    stream: Some(StreamInfo {\n                        axis: output_mapping.scan.unwrap().1.axis,\n                        dim: inputs[first_scan_slot].stream.as_ref().unwrap().dim.clone(),\n                        delay: inputs[first_scan_slot].stream.as_ref().unwrap().delay,\n                    }),\n                }\n            } else {\n                let pulse_axis = pulse_axis.outputs[output_body_ix][0];\n                let mut shape = output_body_fact.shape.clone();\n                if let Some(info) = output_mapping.scan {\n                    shape.set(info.0, inputs[first_scan_slot].shape[first_scan_axis].clone());\n                }\n                PulsedFact {\n                    datum_type: output_body_fact.datum_type,\n                    shape,\n                    stream: Some(StreamInfo {\n                        axis: pulse_axis,\n                        dim: inputs[first_scan_slot].stream.as_ref().unwrap().dim.clone(),\n                        delay: inputs[first_scan_slot].stream.as_ref().unwrap().delay,\n                    }),\n                }\n            };\n            facts.push(fact);\n        }\n        Ok(facts)\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/slice.rs",
    "content": "use crate::internal::*;\nuse tract_pulse_opl::ops::PulsedAxisSlice;\n\nimpl PulsedOp for PulsedAxisSlice {\n    fn pulsed_output_facts(&self, inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        let mut fact = inputs[0].clone();\n        let stream = fact.stream.as_mut().unwrap();\n        stream.delay += self.skip;\n        stream.dim = self.take.clone();\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n    pulsed_op_to_typed_op!();\n}\n"
  },
  {
    "path": "pulse/src/ops/source.rs",
    "content": "use crate::internal::*;\nuse tract_core::ops::source::*;\n\nregister_all!(TypedSource: pulsify);\n\npub fn pulsify(\n    _op: &TypedSource,\n    _source: &TypedModel,\n    node: &TypedNode,\n    target: &mut PulsedModel,\n    _mapping: &HashMap<OutletId, OutletId>,\n    stream_symbol: &Symbol,\n    pulse: &TDim,\n) -> TractResult<Option<TVec<OutletId>>> {\n    let pulsed_fact =\n        PulsedFact::from_tensor_fact_pulse(&node.outputs[0].fact, stream_symbol, pulse)?;\n    let id = target.add_source(node.name.clone(), pulsed_fact)?;\n    Ok(Some(tvec!(id)))\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct PulsedSource(pub PulsedFact);\n\nimpl Op for PulsedSource {\n    fn name(&self) -> StaticName {\n        \"PulsedSource\".into()\n    }\n    not_a_typed_op!();\n}\n\nimpl EvalOp for PulsedSource {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(&self, _session: &TurnState, node_id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(SourceState(node_id))))\n    }\n}\n\nimpl PulsedOp for PulsedSource {\n    fn pulsed_output_facts(&self, _inputs: &[&PulsedFact]) -> TractResult<TVec<PulsedFact>> {\n        Ok(tvec!(self.0.clone()))\n    }\n\n    fn to_typed(&self) -> Box<dyn TypedOp> {\n        Box::new(TypedSource::new(self.0.datum_type.fact(self.0.shape.clone())))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "pulse-opl/Cargo.toml",
    "content": "[package]\nname = \"tract-pulse-opl\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\ndowncast-rs.workspace = true\ndyn-eq.workspace = true\nlazy_static.workspace = true\ntract-nnef.workspace = true\n\n[features]\ncomplex = [ \"tract-nnef/complex\" ]\n"
  },
  {
    "path": "pulse-opl/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "pulse-opl/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "pulse-opl/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "pulse-opl/src/concat.rs",
    "content": "use std::ops::Range;\nuse tract_nnef::internal::*;\nuse tract_nnef::tract_core::trivial_op_state_freeze;\n\n/// Concat with pulse along concat axis\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct PulsedSameAxisConcat {\n    pub axis: usize,\n    pub before_len: usize,\n    pub after_len: usize,\n    pub input_delay: usize,\n    pub input_len: TDim,\n}\n\nimpl Op for PulsedSameAxisConcat {\n    fn name(&self) -> StaticName {\n        \"PulsedSameAxisConcat\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for PulsedSameAxisConcat {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::<PulsedSameAxisConcatState>::default()))\n    }\n}\n\nimpl TypedOp for PulsedSameAxisConcat {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n}\n\n#[derive(Clone, Debug, Default)]\npub struct PulsedSameAxisConcatState {\n    current_pos: usize,\n}\ntrivial_op_state_freeze!(PulsedSameAxisConcatState);\n\nimpl OpState for PulsedSameAxisConcatState {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let op = op\n            .downcast_ref::<PulsedSameAxisConcat>()\n            .ok_or_else(|| format_err!(\"Wrong Op type\"))?;\n        let (pre, input, post) = args_3!(inputs);\n        let mut data = input.into_tensor();\n        let pulse = data.shape()[op.axis];\n        let current_pos = self.current_pos;\n        self.current_pos += pulse;\n\n        let pre_length = pre.shape()[op.axis];\n        let pre_offset = op.input_delay - pre_length;\n        overwrite_part_of_pulse(op.axis, &mut data, current_pos, &pre, pre_offset)?;\n        if let Ok(l) = op.input_len.eval(&session.resolved_symbols).to_usize() {\n            let post_offset = op.input_delay + l;\n            overwrite_part_of_pulse(op.axis, &mut data, current_pos, &post, post_offset)?;\n        }\n\n        Ok(tvec!(data.into_tvalue()))\n    }\n}\n\npub fn overwrite_part_of_pulse(\n    axis: usize,\n    pulse_data: &mut Tensor,\n    current_pos: usize,\n    const_data: &Tensor,\n    const_offset: usize,\n) -> TractResult<()> {\n    let pulse = pulse_data.shape()[axis];\n    let const_length = const_data.shape()[axis];\n    let const_range = const_offset..const_offset + const_length;\n    let pulse_range = current_pos..current_pos + pulse;\n\n    match range_in_range(&pulse_range, &const_range) {\n        RangeInRange::Before(_) | RangeInRange::After(_) => (),\n        RangeInRange::Begin(offset) => {\n            // ----[<----->HHH]HH----\n            pulse_data.assign_slice(offset..pulse, const_data, 0..pulse - offset, axis)?;\n        }\n        RangeInRange::Contain(offset) => {\n            // ----[<----->HHHHHHH-]---\n            pulse_data.assign_slice(\n                offset..offset + const_length,\n                const_data,\n                0..const_length,\n                axis,\n            )?;\n        }\n        RangeInRange::Inside(offset) => {\n            // ----------<H>[HH]HH----\n            pulse_data.assign_slice(0..pulse, const_data, offset..offset + pulse, axis)?;\n        }\n        RangeInRange::End(offset) => {\n            // --------<HHH>[HHHH-]---\n            pulse_data.assign_slice(\n                0..const_length - offset,\n                const_data,\n                offset..const_length,\n                axis,\n            )?;\n        }\n    }\n    Ok(())\n}\n\n#[derive(Copy, Clone, Debug)]\n#[allow(dead_code)]\npub enum RangeInRange {\n    /// ----[--]<-->HHHH----\n    Before(usize),\n    /// ----[<----->HHH]HH----\n    Begin(usize),\n    /// ----[<----->HHHHHHH-]---\n    Contain(usize),\n    /// ----------<H>[HH]HH----\n    Inside(usize),\n    /// --------<HHH>[HHHH-]---\n    End(usize),\n    /// --------HHHHHHH<->[--]---\n    After(usize),\n}\n\npub fn range_in_range(needle: &Range<usize>, haystack: &Range<usize>) -> RangeInRange {\n    if needle.end <= haystack.start {\n        RangeInRange::Before(haystack.start - needle.end)\n    } else if needle.start < haystack.start {\n        if needle.end < haystack.end {\n            RangeInRange::Begin(haystack.start - needle.start)\n        } else {\n            RangeInRange::Contain(haystack.start - needle.start)\n        }\n    } else if needle.start >= haystack.end {\n        RangeInRange::After(needle.start - haystack.end)\n    } else if needle.end > haystack.end {\n        RangeInRange::End(needle.start - haystack.start)\n    } else {\n        RangeInRange::Inside(needle.start - haystack.start)\n    }\n}\n"
  },
  {
    "path": "pulse-opl/src/deconv_delay.rs",
    "content": "use std::ops::AddAssign;\n\nuse tract_ndarray::Axis;\nuse tract_nnef::internal::*;\nuse tract_nnef::tract_core::ops::OpStateFreeze;\nuse tract_num_traits::Zero;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub struct DeconvDelay {\n    pub axis: usize,\n    pub overlap: usize,\n    pub delay: usize,\n    pub stride: usize,\n    pub pulse: TDim,\n    pub deconv_input_dim: TDim,\n    pub deconv_output_dim: TDim,\n}\n\nimpl Op for DeconvDelay {\n    fn name(&self) -> StaticName {\n        \"DeconvDelay\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DeconvDelay {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn eval(&self, _inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        unreachable!()\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(DeconvDelayState { valid_inputed: -(self.delay as isize), buffer: None })))\n    }\n}\n\nimpl TypedOp for DeconvDelay {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact = inputs[0].clone();\n        let len = fact.shape[self.axis].clone();\n        fact.shape.set(self.axis, len - self.overlap);\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]\npub struct DeconvDelayState {\n    valid_inputed: isize,\n    buffer: Option<Tensor>,\n}\n\nimpl OpState for DeconvDelayState {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let op = op.downcast_ref::<DeconvDelay>().context(\"Wrong op\")?;\n        if self.buffer.is_none() {\n            let mut buffer_size: TVec<usize> = inputs[0].shape().into();\n            buffer_size[op.axis] = op.overlap; //+ (op.stride - 1) * (op.pulse - 1);\n            self.buffer = Some(Tensor::zero_dt(inputs[0].datum_type(), &buffer_size)?);\n        }\n        let mut input = inputs[0].clone().into_tensor();\n        dispatch_numbers!(Self::eval_t(input.datum_type())(self, session, op, &mut input))?;\n        let output = input.slice(op.axis, 0, input.shape()[op.axis] - op.overlap)?;\n        Ok(tvec!(output.into_tvalue()))\n    }\n}\n\nimpl DeconvDelayState {\n    fn eval_t<T: Datum + AddAssign + Zero>(\n        &mut self,\n        session: &TurnState,\n        op: &DeconvDelay,\n        input: &mut Tensor,\n    ) -> TractResult<()> {\n        let buffer = self.buffer.as_mut().unwrap();\n        let mut buffer_plain = buffer.try_as_plain_mut()?;\n        let mut buffer = buffer_plain.to_array_view_mut::<T>()?;\n        let mut input_plain = input.try_as_plain_mut()?;\n        let mut input = input_plain.to_array_view_mut::<T>()?;\n        let input_pulse = input.shape()[op.axis];\n        let output_pulse = input_pulse - op.overlap;\n        self.valid_inputed += output_pulse as isize;\n        if let Ok(input_dim) = op.deconv_input_dim.eval(&session.resolved_symbols).to_isize() {\n            if self.valid_inputed > input_dim {\n                let to_be_zeroed = ((self.valid_inputed - input_dim) as usize).min(input_pulse);\n                let mut zeroed =\n                    input.slice_axis_mut(Axis(op.axis), (input_pulse - to_be_zeroed..).into());\n                zeroed.fill(T::zero());\n            }\n        }\n        {\n            let mut input_view = input.slice_axis_mut(Axis(op.axis), (0..op.overlap).into());\n            input_view += &buffer;\n        }\n        buffer.assign(&input.slice_axis(Axis(op.axis), (output_pulse..).into()));\n\n        Ok(())\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]\nstruct FrozenDeconvDelayState {\n    valid_inputed: isize,\n    buffer: Option<Arc<Tensor>>,\n}\n\nimpl OpStateFreeze for DeconvDelayState {\n    fn freeze(&self) -> Box<dyn FrozenOpState> {\n        Box::new(FrozenDeconvDelayState {\n            valid_inputed: self.valid_inputed,\n            buffer: self.buffer.as_ref().map(|t| t.clone().into_arc_tensor()),\n        })\n    }\n}\n\nimpl FrozenOpState for FrozenDeconvDelayState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(DeconvDelayState {\n            valid_inputed: self.valid_inputed,\n            buffer: self.buffer.as_ref().map(|t| t.clone().into_tensor()),\n        })\n    }\n}\n"
  },
  {
    "path": "pulse-opl/src/delay.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef::tract_core::ops::OpStateFreeze;\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_pulse_delay\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Integer.named(\"delay\"),\n            TypeName::Integer.named(\"overlap\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_delay,\n    );\n}\n\nfn de_delay(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let axis = invocation.named_arg_as::<i64>(builder, \"axis\")? as usize;\n    let delay = invocation.named_arg_as::<i64>(builder, \"delay\")? as usize;\n    let overlap = invocation.named_arg_as::<i64>(builder, \"overlap\")? as usize;\n    let input_fact = builder.model.outlet_fact(wire)?;\n    let op = Delay::new_typed(input_fact, axis, delay, overlap);\n    builder.wire(op, &[wire])\n}\n\n#[derive(Debug, Clone)]\npub struct DelayState {\n    pub buffer: Option<Tensor>,\n}\n\nimpl DelayState {\n    /// Apply delay op on input and store the result in the output tensor\n    /// This method doesn't use allocation.\n    ///\n    /// # Safety\n    ///\n    /// Input and Ouput tensors shape must be compatible with this operator, otherwise it could lead\n    /// to an undefined behaviour.\n    pub unsafe fn apply_delay_unchecked(\n        &mut self,\n        op: &Delay,\n        input: &Tensor,\n        output: &mut Tensor,\n    ) {\n        unsafe {\n            let buffered = op.delay + op.overlap;\n            let input_pulse = input.shape()[op.axis];\n            let output_pulse = input_pulse + op.overlap;\n            let buffer = self.buffer.as_mut().unwrap();\n\n            let from_input = input_pulse.saturating_sub(op.delay);\n            let from_buffer = output_pulse.saturating_sub(from_input);\n            output.assign_slice_unchecked(..from_buffer, buffer, ..from_buffer, op.axis);\n            output.assign_slice_unchecked(from_buffer.., input, ..from_input, op.axis);\n\n            // maintain buffer\n            if buffered < input_pulse {\n                buffer.assign_slice_unchecked(.., input, (input_pulse - buffered).., op.axis);\n            } else {\n                let stride =\n                    buffer.strides()[op.axis] as usize * input.datum_type().size_of() * input_pulse;\n                std::slice::from_raw_parts_mut(\n                    buffer.as_ptr_mut_unchecked::<u8>(),\n                    buffer.len() * input.datum_type().size_of(),\n                )\n                .rotate_left(stride);\n                buffer.assign_slice_unchecked((buffered - input_pulse).., input, .., op.axis);\n            }\n        }\n    }\n}\n\nimpl OpState for DelayState {\n    fn eval(\n        &mut self,\n        _state: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let op = op.downcast_ref::<Delay>().ok_or_else(|| format_err!(\"Wrong Op type\"))?;\n        let buffered = op.delay + op.overlap;\n        let input_pulse = input.shape()[op.axis];\n        let output_pulse = input_pulse + op.overlap;\n        let mut output_shape: TVec<usize> = input.shape().into();\n        output_shape[op.axis] = output_pulse;\n        // build output\n        unsafe {\n            if self.buffer.is_none() {\n                let mut shape = input.shape().to_owned();\n                shape[op.axis] = buffered;\n                self.buffer = Some(Tensor::uninitialized_dt(input.datum_type(), &shape)?);\n            };\n            let mut output = Tensor::uninitialized_dt(input.datum_type(), &output_shape)?;\n            self.apply_delay_unchecked(op, &input, &mut output);\n            Ok(tvec!(output.into()))\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub struct Delay {\n    pub buffer_shape: TVec<TDim>,\n    pub axis: usize,\n    pub delay: usize,\n    pub overlap: usize,\n}\n\nimpl Delay {\n    pub fn new_typed(input_fact: &TypedFact, axis: usize, delay: usize, overlap: usize) -> Delay {\n        let mut buffer_shape: TVec<TDim> = input_fact.shape.to_tvec();\n        buffer_shape[axis] = (delay + overlap).to_dim();\n        Delay { buffer_shape, axis, delay, overlap }\n    }\n}\n\nimpl Op for Delay {\n    fn name(&self) -> StaticName {\n        \"Delay\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![\n            format!(\"axis: {} delay: {} overlap: {}\", self.axis, self.delay, self.overlap),\n            format!(\"buffer: {:?}\", self.buffer_shape),\n        ])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Delay {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(DelayState { buffer: None })))\n    }\n}\n\nimpl TypedOp for Delay {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let mut fact = inputs[0].clone();\n        fact.shape.set(self.axis, fact.shape[self.axis].clone() + self.overlap.to_dim());\n        Ok(tvec!(fact))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        Ok(tvec!((Cost::Buffer(inputs[0].datum_type), self.buffer_shape.iter().product())))\n    }\n\n    fn suggested_axis_changes(&self) -> TractResult<TVec<(InOut, AxisOp)>> {\n        if self.axis != 0 {\n            Ok(tvec!((InOut::In(0), AxisOp::Move(self.axis, 0))))\n        } else {\n            Ok(tvec!())\n        }\n    }\n\n    fn change_axes(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n        _io: InOut,\n        change: &AxisOp,\n    ) -> TractResult<Option<AxisChangeConsequence>> {\n        if let Some(axis) = change.transform_axis(self.axis) {\n            if axis != self.axis {\n                Ok(Some(AxisChangeConsequence::new(\n                    model,\n                    node,\n                    Some(Box::new(Self { axis, ..self.clone() }) as _),\n                    change,\n                )))\n            } else {\n                Ok(Some(AxisChangeConsequence::new(model, node, None, change)))\n            }\n        } else {\n            Ok(None)\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]\nstruct FrozenDelayState {\n    buffer: Option<Arc<Tensor>>,\n}\n\nimpl OpStateFreeze for DelayState {\n    fn freeze(&self) -> Box<dyn FrozenOpState> {\n        Box::new(FrozenDelayState {\n            buffer: self.buffer.as_ref().map(|t| t.clone().into_arc_tensor()),\n        })\n    }\n}\n\nimpl FrozenOpState for FrozenDelayState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(DelayState { buffer: self.buffer.as_ref().map(|t| t.clone().into_tensor()) })\n    }\n}\n"
  },
  {
    "path": "pulse-opl/src/lib.rs",
    "content": "#![allow(clippy::collapsible_if)]\nuse tract_nnef::internal::*;\n\npub mod concat;\nmod deconv_delay;\nmod delay;\nmod mask;\nmod pad;\nmod slice;\n\npub use tract_nnef;\npub use tract_nnef::tract_core;\n\npub mod prelude {\n    pub use crate::WithPulse;\n    pub use tract_nnef::tract_core::internal::DimLike;\n}\n\npub mod ops {\n    pub use super::deconv_delay::DeconvDelay;\n    pub use super::delay::{Delay, DelayState};\n    pub use super::mask::PulseMask;\n    pub use super::pad::PulsePad;\n    pub use super::slice::PulsedAxisSlice;\n}\n\npub trait WithPulse {\n    fn enable_pulse(&mut self);\n    fn with_pulse(self) -> Self;\n}\n\nimpl WithPulse for tract_nnef::framework::Nnef {\n    fn enable_pulse(&mut self) {\n        self.enable_tract_core();\n        self.registries.push(tract_nnef_registry());\n    }\n    fn with_pulse(mut self) -> Self {\n        self.enable_pulse();\n        self\n    }\n}\n\npub fn tract_nnef_registry() -> Registry {\n    let mut reg = Registry::new(\"tract_pulse\")\n        .with_doc(\"Extension `tract_resource` extends NNEF with operators\")\n        .with_doc(\"for pulsified networks.\")\n        .with_doc(\"\")\n        .with_doc(\"Add `extension tract_pulse` to `graph.nnef`\");\n\n    reg.aliases.push(\"pulse\".into());\n    delay::register(&mut reg);\n    mask::register(&mut reg);\n    pad::register(&mut reg);\n    reg\n}\n"
  },
  {
    "path": "pulse-opl/src/mask.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef::ser::tdim;\nuse tract_nnef::tract_core::trivial_op_state_freeze;\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_pulse_mask\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Integer.named(\"begin\"),\n            TypeName::Integer.named(\"end\"),\n            TypeName::Scalar.named(\"value\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        deser,\n    );\n    registry.register_dumper(ser)\n}\n\nfn ser(ast: &mut IntoAst, node: &TypedNode, op: &PulseMask) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let params = vec![\n        (\"axis\", numeric(op.axis)),\n        (\"begin\", numeric(op.begin)),\n        (\"end\", tdim(&op.end)),\n        (\"value\", numeric(op.value.cast_to_scalar::<f32>())),\n    ];\n    Ok(Some(invocation(\"tract_pulse_mask\", &[wire], &params)))\n}\n\nfn deser(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let axis = invocation.named_arg_as(builder, \"axis\")?;\n    let begin = invocation.named_arg_as(builder, \"begin\")?;\n    let value: Tensor = tensor0(invocation.named_arg_as::<f32>(builder, \"value\")?);\n    let end = builder.allowing_new_symbols(|builder| invocation.named_arg_as(builder, \"end\"))?;\n    let op = PulseMask { axis, begin, end, value };\n    builder.wire(op, &[wire])\n}\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\nstruct PulseMaskOpState {\n    current_pos: usize,\n}\n\nimpl OpState for PulseMaskOpState {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs).into_tensor();\n        let op = op.downcast_ref::<PulseMask>().ok_or_else(|| format_err!(\"Wrong Op type\"))?;\n        let tensor = self.pad(session, op, input)?;\n        Ok(tvec!(tensor.into_tvalue()))\n    }\n}\n\nimpl PulseMaskOpState {\n    fn pad(\n        &mut self,\n        session: &TurnState,\n        op: &PulseMask,\n        mut input: Tensor,\n    ) -> TractResult<Tensor> {\n        let pulse = input.shape()[op.axis];\n        let pulse_begin = self.current_pos;\n        let pulse_end = self.current_pos + pulse;\n        self.current_pos += pulse;\n        let end = op.end.eval(&session.resolved_symbols).to_usize().unwrap_or(usize::MAX);\n\n        // pulse is entirely in valid input, just forward\n        if pulse_begin >= op.begin && pulse_end <= end {\n            return Ok(input);\n        }\n\n        if pulse_begin < op.begin {\n            let fill_up_to = (op.begin - pulse_begin).min(pulse);\n            unsafe {\n                dispatch_copy_by_size!(crate::pad::fill_slice_constant(input.datum_type())(\n                    &mut input,\n                    &op.value,\n                    op.axis,\n                    0..fill_up_to\n                ))\n            };\n        }\n        if pulse_end > end {\n            let fill_from = pulse - (pulse_end - end).min(pulse);\n            unsafe {\n                dispatch_copy_by_size!(crate::pad::fill_slice_constant(input.datum_type())(\n                    &mut input,\n                    &op.value,\n                    op.axis,\n                    fill_from..pulse\n                ))\n            }\n        }\n\n        Ok(input)\n    }\n}\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\npub struct PulseMask {\n    pub axis: usize,\n    pub begin: usize,\n    pub end: TDim,\n    pub value: Tensor,\n}\n\nimpl Op for PulseMask {\n    fn name(&self) -> StaticName {\n        \"PulseMask\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis: {} begin: {} end: {}\", self.axis, self.begin, self.end,)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for PulseMask {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::<PulseMaskOpState>::default()))\n    }\n}\n\nimpl TypedOp for PulseMask {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n\n    as_op!();\n}\n\ntrivial_op_state_freeze!(PulseMaskOpState);\n"
  },
  {
    "path": "pulse-opl/src/pad.rs",
    "content": "use tract_core::ndarray::*;\nuse tract_core::ops::array::PadMode;\nuse tract_nnef::internal::*;\nuse tract_nnef::ser::tdim;\nuse tract_nnef::tract_core::ops::OpStateFreeze;\n\npub fn register(registry: &mut Registry) {\n    registry.register_primitive(\n        \"tract_pulse_pulse_pad\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::Integer.named(\"before\"),\n            TypeName::Integer.named(\"after\"),\n            TypeName::Integer.named(\"begin_input\"),\n            TypeName::Integer.named(\"end_input\"),\n            TypeName::String.named(\"border\"),\n            TypeName::Scalar.named(\"value\"),\n            TypeName::Integer.named(\"overlap\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        deser,\n    );\n    registry.register_dumper(ser)\n}\n\nfn ser(ast: &mut IntoAst, node: &TypedNode, op: &PulsePad) -> TractResult<Option<Arc<RValue>>> {\n    let wire = ast.mapping[&node.inputs[0]].clone();\n    let dt = ast.model.outlet_fact(node.inputs[0])?.datum_type;\n    let (border, value) = tract_nnef::ops::nnef::ser::pad_mode(&op.mode, dt)?;\n    let mut params = vec![\n        (\"axis\", numeric(op.axis)),\n        (\"before\", numeric(op.before)),\n        (\"begin_input\", numeric(op.begin_input)),\n        (\"overlap\", numeric(op.overlap)),\n        (\"after\", tdim(&op.after)),\n        (\"end_input\", tdim(&op.end_input)),\n    ];\n    params.push((\"border\", string(border)));\n    if let Some(value) = value {\n        params.push((\"value\", value));\n    }\n    Ok(Some(invocation(\"tract_pulse_pulse_pad\", &[wire], &params)))\n}\n\nfn deser(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let wire = invocation.named_arg_as(builder, \"input\")?;\n    let axis = invocation.named_arg_as(builder, \"axis\")?;\n    let before = invocation.named_arg_as(builder, \"before\")?;\n    let begin_input = invocation.named_arg_as(builder, \"begin_input\")?;\n    let overlap = invocation.named_arg_as(builder, \"overlap\")?;\n    let border = invocation.named_arg_as::<String>(builder, \"border\")?;\n    let value: Tensor = tensor0(invocation.named_arg_as::<f32>(builder, \"value\")?);\n    let (after, end_input) = builder.allowing_new_symbols(|builder| {\n        TractResult::Ok((\n            invocation.named_arg_as(builder, \"after\")?,\n            invocation.named_arg_as(builder, \"end_input\")?,\n        ))\n    })?;\n\n    let mode = tract_nnef::ops::nnef::deser::pad_mode(&border, value)?;\n    let op = PulsePad { axis, before, after, begin_input, end_input, mode, overlap };\n    builder.wire(op, &[wire])\n}\n\npub(crate) unsafe fn fill_slice_constant<T: Datum + Copy>(\n    data: &mut Tensor,\n    constant: &Tensor,\n    axis: usize,\n    range: std::ops::Range<usize>,\n) {\n    unsafe {\n        let c = constant.to_scalar_unchecked::<T>();\n        data.to_array_view_mut_unchecked::<T>().slice_axis_mut(Axis(axis), range.into()).fill(*c);\n    }\n}\n\nunsafe fn fill_slice_with_frame<T: Datum + Copy>(\n    data: &mut Tensor,\n    axis: usize,\n    valid: &Tensor,\n    range: std::ops::Range<usize>,\n) {\n    unsafe {\n        let mut data = data.to_array_view_mut_unchecked::<T>();\n        let valid = valid.to_array_view_unchecked::<T>();\n        for i in range {\n            data.slice_axis_mut(Axis(axis), (i..i + 1).into()).assign(&valid);\n        }\n    }\n}\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\nstruct PulsePadOpState {\n    current_pos: usize,\n    last_valid_frame: Option<Tensor>,\n}\n\nimpl OpState for PulsePadOpState {\n    fn eval(\n        &mut self,\n        session: &mut TurnState,\n        op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs).into_tensor();\n        let op = op.downcast_ref::<PulsePad>().ok_or_else(|| format_err!(\"Wrong Op type\"))?;\n        let tensor = self.pad(session, op, input)?;\n        Ok(tvec!(tensor.into_tvalue()))\n    }\n}\n\nimpl PulsePadOpState {\n    unsafe fn save_frame<T: Datum + Copy>(&mut self, op: &PulsePad, input: &Tensor, frame: usize) {\n        let data = unsafe { input.to_array_view_unchecked::<T>() };\n        self.last_valid_frame =\n            Some(data.index_axis(Axis(op.axis), frame).to_owned().into_tensor());\n    }\n\n    fn pad(\n        &mut self,\n        session: &TurnState,\n        op: &PulsePad,\n        mut input: Tensor,\n    ) -> TractResult<Tensor> {\n        let pulse = input.shape()[op.axis];\n        let pulse_begin = self.current_pos;\n        let pulse_end = self.current_pos + pulse;\n        self.current_pos += pulse - op.overlap;\n        let end_input =\n            op.end_input.eval(&session.resolved_symbols).to_usize().unwrap_or(usize::MAX);\n        let after = op.after.eval(&session.resolved_symbols).to_usize().unwrap_or(usize::MAX);\n\n        if let PadMode::Edge = op.mode {\n            if after != 0 && pulse_begin < end_input {\n                let latest_valid_frame = (end_input - pulse_begin).min(pulse) - 1;\n                unsafe {\n                    dispatch_copy_by_size!(Self::save_frame(input.datum_type())(\n                        self,\n                        op,\n                        &input,\n                        latest_valid_frame\n                    ))\n                }\n            }\n        }\n\n        // pulse is entirely in valid input, just forward\n        if pulse_begin >= op.begin_input && pulse_end <= end_input {\n            return Ok(input);\n        }\n        // pulse is entirely before or after output is valid, just forward\n        if pulse_end <= op.begin_input - op.before || pulse_begin >= end_input.saturating_add(after)\n        {\n            return Ok(input);\n        }\n\n        if pulse_begin < op.begin_input {\n            let fill_up_to = (op.begin_input - pulse_begin).min(pulse);\n            match &op.mode {\n                PadMode::Constant(c) => unsafe {\n                    dispatch_copy_by_size!(fill_slice_constant(input.datum_type())(\n                        &mut input,\n                        c,\n                        op.axis,\n                        0..fill_up_to\n                    ))\n                },\n                PadMode::Edge => {\n                    let frame = input.slice(op.axis, fill_up_to, fill_up_to + 1)?;\n                    unsafe {\n                        dispatch_copy_by_size!(fill_slice_with_frame(input.datum_type())(\n                            &mut input,\n                            op.axis,\n                            &frame,\n                            0..fill_up_to\n                        ))\n                    }\n                }\n                _ => unimplemented!(),\n            }\n        }\n        if pulse_end > end_input && after > 0 {\n            let fill_from = pulse - (pulse_end - end_input).min(pulse);\n            match &op.mode {\n                PadMode::Constant(c) => unsafe {\n                    dispatch_copy_by_size!(fill_slice_constant(input.datum_type())(\n                        &mut input,\n                        c,\n                        op.axis,\n                        fill_from..pulse\n                    ))\n                },\n                PadMode::Edge => {\n                    let last_frame = self.last_valid_frame.as_ref().unwrap();\n                    unsafe {\n                        dispatch_copy_by_size!(fill_slice_with_frame(input.datum_type())(\n                            &mut input,\n                            op.axis,\n                            last_frame,\n                            fill_from..pulse\n                        ))\n                    }\n                }\n                _ => unimplemented!(),\n            }\n        }\n\n        Ok(input)\n    }\n}\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\npub struct PulsePad {\n    pub axis: usize,\n    pub before: usize,\n    pub after: TDim,\n    pub begin_input: usize,\n    pub end_input: TDim,\n    pub mode: PadMode,\n    pub overlap: usize,\n}\n\nimpl Op for PulsePad {\n    fn name(&self) -> StaticName {\n        \"PulsePad\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\n            \"Mode: {:?}, axis: {} before: {} after: {}\",\n            self.mode, self.axis, self.before, self.after,\n        )])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for PulsePad {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::<PulsePadOpState>::default()))\n    }\n}\n\nimpl TypedOp for PulsePad {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n\n    as_op!();\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]\nstruct FrozenPulsePadOpState {\n    current_pos: usize,\n    last_valid_frame: Option<Arc<Tensor>>,\n}\n\nimpl OpStateFreeze for PulsePadOpState {\n    fn freeze(&self) -> Box<dyn FrozenOpState> {\n        Box::new(FrozenPulsePadOpState {\n            current_pos: self.current_pos,\n            last_valid_frame: self.last_valid_frame.as_ref().map(|t| t.clone().into_arc_tensor()),\n        })\n    }\n}\n\nimpl FrozenOpState for FrozenPulsePadOpState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(PulsePadOpState {\n            current_pos: self.current_pos,\n            last_valid_frame: self.last_valid_frame.as_ref().map(|t| t.clone().into_tensor()),\n        })\n    }\n}\n"
  },
  {
    "path": "pulse-opl/src/slice.rs",
    "content": "use tract_nnef::internal::*;\n\n#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]\npub struct PulsedAxisSlice {\n    pub axis: usize,\n    pub skip: usize,\n    pub take: TDim,\n}\n\nimpl Op for PulsedAxisSlice {\n    fn name(&self) -> StaticName {\n        \"PulsedAxisSlice\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"axis:{}, skip:{} take:{}\", self.axis, self.skip, self.take)])\n    }\n\n    not_a_typed_op!();\n}\n\nimpl TypedOp for PulsedAxisSlice {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].clone()))\n    }\n\n    as_op!();\n}\n\nimpl EvalOp for PulsedAxisSlice {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(inputs)\n    }\n}\n"
  },
  {
    "path": "release.sh",
    "content": "#!/bin/bash\n\nset -e\n\ngit pull # make sure we are in sync\ngit push\n\nwhich tomato || cargo install tomato-toml\n\nCRATE_PATH=$1\nVERSION=$2\n. ./.all_crates.sh\n\nif [ -z \"$VERSION\" ]\nthen\n    echo \"Usage: $0 <crate> <version>\" \n    echo crates order is: $ALL_CRATES_PATH\n    exit 1\nfi\n\nset -ex\n\nif [ \"$CRATE_PATH\" = \"all\" ]\nthen\n    for c in $ALL_CRATES_PATH\n    do\n        $0 $c $VERSION\n    done\n    exit 0\nfi\n\ncrate=$(tomato get package.name $CRATE_PATH/Cargo.toml)\ntomato set package.version $VERSION $CRATE_PATH/Cargo.toml\n./.change_crate_dep.sh $crate $VERSION\n\nif [ \"$crate\" = \"tract-metal\" -o \"$crate\" = 'tract-cuda' ]\nthen\n    cargo publish -q --allow-dirty --no-verify -p $crate \nelse\n    cargo publish -q --allow-dirty -p $crate\nfi\n\n\nif [ \"$CRATE_PATH\" = \"cli\" ]\nthen\n    git commit -m \"release $VERSION\" .\n    git tag -f v\"$VERSION\"\n    git push -f --tags\nfi\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "use_small_heuristics = \"Max\"\nuse_field_init_shorthand = true\nuse_try_shorthand = true\nedition = \"2018\"\n"
  },
  {
    "path": "tensorflow/Cargo.toml",
    "content": "[package]\nname = \"tract-tensorflow\"\nversion = \"0.23.0-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\", \"ONNX\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n# build = \"build-proto.rs\"\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nbytes.workspace = true\nderive-new.workspace = true\nlog.workspace = true\nmemmap2.workspace = true\nprost.workspace = true\nprost-types.workspace = true\ntensorflow = { workspace = true, optional = true }\ntract-hir.workspace = true\ntract-pulse.workspace = true\n\n# [build-dependencies]\n# protobuf-src = \"1.0.5+3.19.3\"\n# prost-build = \"0.14\"\n\n[features]\nconform = [ \"tensorflow\" ]\n\n[dev-dependencies]\ncriterion.workspace = true\nenv_logger.workspace = true\nproptest.workspace = true\nrand.workspace = true\n\n# [[bench]]\n# name = \"conv\"\n# harness = false\n"
  },
  {
    "path": "tensorflow/LICENSE",
    "content": "## License\n\nLicensed under either of\n * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\nat your option.\n\n### Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall\nbe dual licensed as above, without any additional terms or conditions.\n"
  },
  {
    "path": "tensorflow/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "tensorflow/LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "tensorflow/build-proto.rs",
    "content": "use std::{env, fs, path};\n\nfn main() -> std::io::Result<()> {\n    env::set_var(\"PROTOC\", protobuf_src::protoc());\n    let inputs: Vec<path::PathBuf> = {\n        let mut inputs: Vec<path::PathBuf> = vec![];\n\n        for dir in &[\"protos/tensorflow/core/framework\", \"protos/tensorflow/core/protobuf\"] {\n            for pb in fs::read_dir(dir)? {\n                inputs.push(pb?.path())\n            }\n        }\n\n        inputs.sort();\n        inputs\n    };\n\n    let gen = \"src/prost\";\n    let _ = fs::create_dir_all(&gen);\n    prost_build::Config::new()\n        .out_dir(gen)\n        .compile_protos(&inputs, &[path::PathBuf::from(\"protos/\")])?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "tensorflow/examples/plus3.rs",
    "content": "extern crate tract_tensorflow;\nuse std::convert::TryFrom;\nuse tract_hir::prelude::*;\nuse tract_tensorflow::tfpb;\nuse tract_tensorflow::tfpb::tensorflow::DataType::DtFloat;\nuse tract_tensorflow::tfpb::tensorflow::TensorProto;\n\nfn main() {\n    let plus3 =\n        tfpb::node().op(\"Add\").name(\"output\").attr(\"T\", DtFloat).input(\"input\").input(\"three\");\n    let konst = tfpb::node()\n        .op(\"Const\")\n        .name(\"three\")\n        .attr(\"dtype\", DtFloat)\n        .attr(\"value\", TensorProto::try_from(&tensor1(&[3.0f32])).unwrap());\n    let input = tfpb::node().op(\"Placeholder\").name(\"input\").attr(\"dtype\", DtFloat);\n    let graph = tfpb::graph().node(input).node(konst).node(plus3);\n    graph.save_to(\"tests/plus3.pb\").unwrap();\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/attr_value.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"AttrValueProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\nimport \"tensorflow/core/framework/tensor.proto\";\nimport \"tensorflow/core/framework/tensor_shape.proto\";\nimport \"tensorflow/core/framework/types.proto\";\n\n// Protocol buffer representing the value for an attr used to configure an Op.\n// Comment indicates the corresponding attr type.  Only the field matching the\n// attr type may be filled.\nmessage AttrValue {\n  // LINT.IfChange\n  message ListValue {\n    repeated bytes s = 2;                        // \"list(string)\"\n    repeated int64 i = 3 [packed = true];        // \"list(int)\"\n    repeated float f = 4 [packed = true];        // \"list(float)\"\n    repeated bool b = 5 [packed = true];         // \"list(bool)\"\n    repeated DataType type = 6 [packed = true];  // \"list(type)\"\n    repeated TensorShapeProto shape = 7;         // \"list(shape)\"\n    repeated TensorProto tensor = 8;             // \"list(tensor)\"\n    repeated NameAttrList func = 9;              // \"list(attr)\"\n  }\n  // LINT.ThenChange(https://www.tensorflow.org/code/tensorflow/c/c_api.cc)\n\n  oneof value {\n    bytes s = 2;                 // \"string\"\n    int64 i = 3;                 // \"int\"\n    float f = 4;                 // \"float\"\n    bool b = 5;                  // \"bool\"\n    DataType type = 6;           // \"type\"\n    TensorShapeProto shape = 7;  // \"shape\"\n    TensorProto tensor = 8;      // \"tensor\"\n    ListValue list = 1;          // any \"list(...)\"\n\n    // \"func\" represents a function. func.name is a function's name or\n    // a primitive op's name. func.attr.first is the name of an attr\n    // defined for that function. func.attr.second is the value for\n    // that attr in the instantiation.\n    NameAttrList func = 10;\n\n    // This is a placeholder only used in nodes defined inside a\n    // function.  It indicates the attr value will be supplied when\n    // the function is instantiated.  For example, let us suppose a\n    // node \"N\" in function \"FN\". \"N\" has an attr \"A\" with value\n    // placeholder = \"foo\". When FN is instantiated with attr \"foo\"\n    // set to \"bar\", the instantiated node N's attr A will have been\n    // given the value \"bar\".\n    string placeholder = 9;\n  }\n}\n\n// A list of attr names and their values. The whole list is attached\n// with a string name.  E.g., MatMul[T=float].\nmessage NameAttrList {\n  string name = 1;\n  map<string, AttrValue> attr = 2;\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/function.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"FunctionProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\n\nimport \"tensorflow/core/framework/attr_value.proto\";\nimport \"tensorflow/core/framework/node_def.proto\";\nimport \"tensorflow/core/framework/op_def.proto\";\n\n// A library is a set of named functions.\nmessage FunctionDefLibrary {\n  repeated FunctionDef function = 1;\n  repeated GradientDef gradient = 2;\n}\n\n// A function can be instantiated when the runtime can bind every attr\n// with a value. When a GraphDef has a call to a function, it must\n// have binding for every attr defined in the signature.\n//\n// TODO(zhifengc):\n//   * device spec, etc.\nmessage FunctionDef {\n  // The definition of the function's name, arguments, return values,\n  // attrs etc.\n  OpDef signature = 1;\n\n  // Attributes specific to this function definition.\n  map<string, AttrValue> attr = 5;\n\n  // NOTE: field id 2 deleted on Jan 11, 2016, GraphDef version 21.\n\n  // In both of the following fields, there is the need to specify an\n  // output that is used as either the input to another node (in\n  // `node_def`) or as a return value of the function (in `ret`).\n  // Unlike the NodeDefs in GraphDef, we need to be able to specify a\n  // list in some cases (instead of just single outputs).  Also, we\n  // need to be able to deal with lists of unknown length (so the\n  // output index may not be known at function definition time).  So\n  // we use the following format instead:\n  // * \"fun_in\" where \"fun_in\" is the name of a function input arg in\n  //   the `signature` field above.  This represents that input, whether\n  //   it is a single tensor or a list.\n  // * \"fun_in:0\" gives the first element of a function input arg (a\n  //   non-list input is considered a list of length 1 for these\n  //   purposes).\n  // * \"node:out\" where \"node\" is the name of a node in `node_def` and\n  //   \"out\" is the name one of its op's output arguments (the name\n  //   comes from the OpDef of the node's op). This represents that\n  //   node's output, whether it is a single tensor or a list.\n  //   Note: We enforce that an op's output arguments are never\n  //   renamed in the backwards-compatibility test.\n  // * \"node:out:0\" gives the first element of a node output arg (a\n  //   non-list output is considered a list of length 1 for these\n  //   purposes).\n  //\n  // NOT CURRENTLY SUPPORTED (but may be in the future):\n  // * \"node:out:-1\" gives last element in a node output list\n  // * \"node:out:1:\" gives a list with all but the first element in a\n  //   node output list\n  // * \"node:out::-1\" gives a list with all but the last element in a\n  //   node output list\n\n  // The body of the function.  Unlike the NodeDefs in a GraphDef, attrs\n  // may have values of type `placeholder` and the `input` field uses\n  // the \"output\" format above.\n\n  // By convention, \"op\" in node_def is resolved by consulting with a\n  // user-defined library first. If not resolved, \"func\" is assumed to\n  // be a builtin op.\n  repeated NodeDef node_def = 3;\n\n  // A mapping from the output arg names from `signature` to the\n  // outputs from `node_def` that should be returned by the function.\n  map<string, string> ret = 4;\n}\n\n// GradientDef defines the gradient function of a function defined in\n// a function library.\n//\n// A gradient function g (specified by gradient_func) for a function f\n// (specified by function_name) must follow the following:\n//\n// The function 'f' must be a numerical function which takes N inputs\n// and produces M outputs. Its gradient function 'g', which is a\n// function taking N + M inputs and produces N outputs.\n//\n// I.e. if we have\n//    (y1, y2, ..., y_M) = f(x1, x2, ..., x_N),\n// then, g is\n//    (dL/dx1, dL/dx2, ..., dL/dx_N) = g(x1, x2, ..., x_N,\n//                                      dL/dy1, dL/dy2, ..., dL/dy_M),\n// where L is a scalar-value function of (x1, x2, ..., xN) (e.g., the\n// loss function). dL/dx_i is the partial derivative of L with respect\n// to x_i.\nmessage GradientDef {\n  string function_name = 1;  // The function name.\n  string gradient_func = 2;  // The gradient function's name.\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/graph.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"GraphProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\nimport \"tensorflow/core/framework/node_def.proto\";\nimport \"tensorflow/core/framework/function.proto\";\nimport \"tensorflow/core/framework/versions.proto\";\n\n// Represents the graph of operations\nmessage GraphDef {\n  repeated NodeDef node = 1;\n\n  // Compatibility versions of the graph.  See core/public/version.h for version\n  // history.  The GraphDef version is distinct from the TensorFlow version, and\n  // each release of TensorFlow will support a range of GraphDef versions.\n  VersionDef versions = 4;\n\n  // Deprecated single version field; use versions above instead.  Since all\n  // GraphDef changes before \"versions\" was introduced were forward\n  // compatible, this field is entirely ignored.\n  int32 version = 3 [deprecated = true];\n\n  // EXPERIMENTAL. DO NOT USE OR DEPEND ON THIS YET.\n  //\n  // \"library\" provides user-defined functions.\n  //\n  // Naming:\n  //   * library.function.name are in a flat namespace.\n  //     NOTE: We may need to change it to be hierarchical to support\n  //     different orgs. E.g.,\n  //     { \"/google/nn\", { ... }},\n  //     { \"/google/vision\", { ... }}\n  //     { \"/org_foo/module_bar\", { ... }}\n  //     map<string, FunctionDefLib> named_lib;\n  //   * If node[i].op is the name of one function in \"library\",\n  //     node[i] is deemed as a function call. Otherwise, node[i].op\n  //     must be a primitive operation supported by the runtime.\n  //\n  //\n  // Function call semantics:\n  //\n  //   * The callee may start execution as soon as some of its inputs\n  //     are ready. The caller may want to use Tuple() mechanism to\n  //     ensure all inputs are ready in the same time.\n  //\n  //   * The consumer of return values may start executing as soon as\n  //     the return values the consumer depends on are ready.  The\n  //     consumer may want to use Tuple() mechanism to ensure the\n  //     consumer does not start until all return values of the callee\n  //     function are ready.\n  FunctionDefLibrary library = 2;\n};\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/node_def.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"NodeProto\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\nimport \"tensorflow/core/framework/attr_value.proto\";\n\nmessage NodeDef {\n  // The name given to this operator. Used for naming inputs,\n  // logging, visualization, etc.  Unique within a single GraphDef.\n  // Must match the regexp \"[A-Za-z0-9.][A-Za-z0-9_./]*\".\n  string name = 1;\n\n  // The operation name.  There may be custom parameters in attrs.\n  // Op names starting with an underscore are reserved for internal use.\n  string op = 2;\n\n  // Each input is \"node:src_output\" with \"node\" being a string name and\n  // \"src_output\" indicating which output tensor to use from \"node\". If\n  // \"src_output\" is 0 the \":0\" suffix can be omitted.  Regular inputs\n  // may optionally be followed by control inputs that have the format\n  // \"^node\".\n  repeated string input = 3;\n\n  // A (possibly partial) specification for the device on which this\n  // node should be placed.\n  // The expected syntax for this string is as follows:\n  //\n  // DEVICE_SPEC ::= PARTIAL_SPEC\n  //\n  // PARTIAL_SPEC ::= (\"/\" CONSTRAINT) *\n  // CONSTRAINT ::= (\"job:\" JOB_NAME)\n  //              | (\"replica:\" [1-9][0-9]*)\n  //              | (\"task:\" [1-9][0-9]*)\n  //              | (\"device:\" [A-Za-z]* \":\" ([1-9][0-9]* | \"*\") )\n  //\n  // Valid values for this string include:\n  // * \"/job:worker/replica:0/task:1/device:GPU:3\"  (full specification)\n  // * \"/job:worker/device:GPU:3\"                   (partial specification)\n  // * \"\"                                    (no specification)\n  //\n  // If the constraints do not resolve to a single device (or if this\n  // field is empty or not present), the runtime will attempt to\n  // choose a device automatically.\n  string device = 4;\n\n  // Operation-specific graph-construction-time configuration.\n  // Note that this should include all attrs defined in the\n  // corresponding OpDef, including those with a value matching\n  // the default -- this allows the default to change and makes\n  // NodeDefs easier to interpret on their own.  However, if\n  // an attr with a default is not specified in this list, the\n  // default will be used.\n  // The \"names\" (keys) must match the regexp \"[a-z][a-z0-9_]+\" (and\n  // one of the names from the corresponding OpDef's attr field).\n  // The values must have a type matching the corresponding OpDef\n  // attr's type field.\n  // TODO(josh11b): Add some examples here showing best practices.\n  map<string, AttrValue> attr = 5;\n};\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/op_def.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"OpDefProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\nimport \"tensorflow/core/framework/attr_value.proto\";\nimport \"tensorflow/core/framework/types.proto\";\n\n// Defines an operation. A NodeDef in a GraphDef specifies an Op by\n// using the \"op\" field which should match the name of a OpDef.\n// LINT.IfChange\nmessage OpDef {\n  // Op names starting with an underscore are reserved for internal use.\n  // Names should be CamelCase and match the regexp \"[A-Z][a-zA-Z0-9_]*\".\n  string name = 1;\n\n  // For describing inputs and outputs.\n  message ArgDef {\n    // Name for the input/output.  Should match the regexp \"[a-z][a-z0-9_]*\".\n    string name = 1;\n\n    // Human readable description.\n    string description = 2;\n\n    // Describes the type of one or more tensors that are accepted/produced\n    // by this input/output arg.  The only legal combinations are:\n    // * For a single tensor: either the \"type\" field is set or the\n    //   \"type_attr\" field is set to the name of an attr with type \"type\".\n    // * For a sequence of tensors with the same type: the \"number_attr\"\n    //   field will be set to the name of an attr with type \"int\", and\n    //   either the \"type\" or \"type_attr\" field will be set as for\n    //   single tensors.\n    // * For a sequence of tensors, the \"type_list_attr\" field will be set\n    //   to the name of an attr with type \"list(type)\".\n    DataType type = 3;\n    string type_attr = 4;    // if specified, attr must have type \"type\"\n    string number_attr = 5;  // if specified, attr must have type \"int\"\n    // If specified, attr must have type \"list(type)\", and none of\n    // type, type_attr, and number_attr may be specified.\n    string type_list_attr = 6;\n\n    // For inputs: if true, the inputs are required to be refs.\n    //   By default, inputs can be either refs or non-refs.\n    // For outputs: if true, outputs are refs, otherwise they are not.\n    bool is_ref = 16;\n  };\n\n  // Description of the input(s).\n  repeated ArgDef input_arg = 2;\n\n  // Description of the output(s).\n  repeated ArgDef output_arg = 3;\n\n  // Description of the graph-construction-time configuration of this\n  // Op.  That is to say, this describes the attr fields that will\n  // be specified in the NodeDef.\n  message AttrDef {\n    // A descriptive name for the argument.  May be used, e.g. by the\n    // Python client, as a keyword argument name, and so should match\n    // the regexp \"[a-z][a-z0-9_]+\".\n    string name = 1;\n\n    // One of the type names from attr_value.proto (\"string\", \"list(string)\",\n    // \"int\", etc.).\n    string type = 2;\n\n    // A reasonable default for this attribute if the user does not supply\n    // a value.  If not specified, the user must supply a value.\n    AttrValue default_value = 3;\n\n    // Human-readable description.\n    string description = 4;\n\n    // TODO(josh11b): bool is_optional?\n\n    // --- Constraints ---\n    // These constraints are only in effect if specified.  Default is no\n    // constraints.\n\n    // For type == \"int\", this is a minimum value.  For \"list(___)\"\n    // types, this is the minimum length.\n    bool has_minimum = 5;\n    int64 minimum = 6;\n\n    // The set of allowed values.  Has type that is the \"list\" version\n    // of the \"type\" field above (uses the \"list\" field of AttrValue).\n    // If type == \"type\" or \"list(type)\" above, then the \"type\" field\n    // of \"allowed_values.list\" has the set of allowed DataTypes.\n    // If type == \"string\" or \"list(string)\", then the \"s\" field of\n    // \"allowed_values.list\" has the set of allowed strings.\n    AttrValue allowed_values = 7;\n  }\n  repeated AttrDef attr = 4;\n\n  // Optional deprecation based on GraphDef versions.\n  OpDeprecation deprecation = 8;\n\n  // One-line human-readable description of what the Op does.\n  string summary = 5;\n\n  // Additional, longer human-readable description of what the Op does.\n  string description = 6;\n\n  // -------------------------------------------------------------------------\n  // Which optimizations this operation can participate in.\n\n  // True if the operation is commutative (\"op(a,b) == op(b,a)\" for all inputs)\n  bool is_commutative = 18;\n\n  // If is_aggregate is true, then this operation accepts N >= 2\n  // inputs and produces 1 output all of the same type.  Should be\n  // associative and commutative, and produce output with the same\n  // shape as the input.  The optimizer may replace an aggregate op\n  // taking input from multiple devices with a tree of aggregate ops\n  // that aggregate locally within each device (and possibly within\n  // groups of nearby devices) before communicating.\n  // TODO(josh11b): Implement that optimization.\n  bool is_aggregate = 16;  // for things like add\n\n  // Other optimizations go here, like\n  //   can_alias_input, rewrite_when_output_unused, partitioning_strategy, etc.\n\n  // -------------------------------------------------------------------------\n  // Optimization constraints.\n\n  // Ops are marked as stateful if their behavior depends on some state beyond\n  // their input tensors (e.g. variable reading op) or if they have\n  // a side-effect (e.g. printing or asserting ops). Equivalently, stateless ops\n  // must always produce the same output for the same input and have\n  // no side-effects.\n  //\n  // By default Ops may be moved between devices.  Stateful ops should\n  // either not be moved, or should only be moved if that state can also\n  // be moved (e.g. via some sort of save / restore).\n  // Stateful ops are guaranteed to never be optimized away by Common\n  // Subexpression Elimination (CSE).\n  bool is_stateful = 17;  // for things like variables, queue\n\n  // -------------------------------------------------------------------------\n  // Non-standard options.\n\n  // By default, all inputs to an Op must be initialized Tensors.  Ops\n  // that may initialize tensors for the first time should set this\n  // field to true, to allow the Op to take an uninitialized Tensor as\n  // input.\n  bool allows_uninitialized_input = 19;  // for Assign, etc.\n};\n// LINT.ThenChange(\n//     https://www.tensorflow.org/code/tensorflow/core/framework/op_def_util.cc)\n\n// Information about version-dependent deprecation of an op\nmessage OpDeprecation {\n  // First GraphDef version at which the op is disallowed.\n  int32 version = 1;\n\n  // Explanation of why it was deprecated and what to use instead.\n  string explanation = 2;\n};\n\n// A collection of OpDefs\nmessage OpList {\n  repeated OpDef op = 1;\n};\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/resource_handle.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"ResourceHandle\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\n\n// Protocol buffer representing a handle to a tensorflow resource. Handles are\n// not valid across executions, but can be serialized back and forth from within\n// a single run.\nmessage ResourceHandleProto {\n  // Unique name for the device containing the resource.\n  string device = 1;\n\n  // Container in which this resource is placed.\n  string container = 2;\n\n  // Unique name of this resource.\n  string name = 3;\n\n  // Hash code for the type of the resource. Is only valid in the same device\n  // and in the same execution.\n  uint64 hash_code = 4;\n\n  // For debug-only, the name of the type pointed to by this handle, if\n  // available.\n  string maybe_type_name = 5;\n};\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/tensor.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"TensorProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\nimport \"tensorflow/core/framework/resource_handle.proto\";\nimport \"tensorflow/core/framework/tensor_shape.proto\";\nimport \"tensorflow/core/framework/types.proto\";\n\n// Protocol buffer representing a tensor.\nmessage TensorProto {\n  DataType dtype = 1;\n\n  // Shape of the tensor.  TODO(touts): sort out the 0-rank issues.\n  TensorShapeProto tensor_shape = 2;\n\n  // Only one of the representations below is set, one of \"tensor_contents\" and\n  // the \"xxx_val\" attributes.  We are not using oneof because as oneofs cannot\n  // contain repeated fields it would require another extra set of messages.\n\n  // Version number.\n  //\n  // In version 0, if the \"repeated xxx\" representations contain only one\n  // element, that element is repeated to fill the shape.  This makes it easy\n  // to represent a constant Tensor with a single value.\n  int32 version_number = 3;\n\n  // Serialized raw tensor content from either Tensor::AsProtoTensorContent or\n  // memcpy in tensorflow::grpc::EncodeTensorToByteBuffer. This representation\n  // can be used for all tensor types. The purpose of this representation is to\n  // reduce serialization overhead during RPC call by avoiding serialization of\n  // many repeated small items.\n  bytes tensor_content = 4;\n\n  // Type specific representations that make it easy to create tensor protos in\n  // all languages.  Only the representation corresponding to \"dtype\" can\n  // be set.  The values hold the flattened representation of the tensor in\n  // row major order.\n\n  // DT_HALF, DT_BFLOAT16. Note that since protobuf has no int16 type, we'll\n  // have some pointless zero padding for each value here.\n  repeated int32 half_val = 13 [packed = true];\n\n  // DT_FLOAT.\n  repeated float float_val = 5 [packed = true];\n\n  // DT_DOUBLE.\n  repeated double double_val = 6 [packed = true];\n\n  // DT_INT32, DT_INT16, DT_INT8, DT_UINT8.\n  repeated int32 int_val = 7 [packed = true];\n\n  // DT_STRING\n  repeated bytes string_val = 8;\n\n  // DT_COMPLEX64. scomplex_val(2*i) and scomplex_val(2*i+1) are real\n  // and imaginary parts of i-th single precision complex.\n  repeated float scomplex_val = 9 [packed = true];\n\n  // DT_INT64\n  repeated int64 int64_val = 10 [packed = true];\n\n  // DT_BOOL\n  repeated bool bool_val = 11 [packed = true];\n\n  // DT_COMPLEX128. dcomplex_val(2*i) and dcomplex_val(2*i+1) are real\n  // and imaginary parts of i-th double precision complex.\n  repeated double dcomplex_val = 12 [packed = true];\n\n  // DT_RESOURCE\n  repeated ResourceHandleProto resource_handle_val = 14;\n\n  // DT_VARIANT\n  repeated VariantTensorDataProto variant_val = 15;\n\n  // DT_UINT32\n  repeated uint32 uint32_val = 16 [packed = true];\n\n  // DT_UINT64\n  repeated uint64 uint64_val = 17 [packed = true];\n};\n\n// Protocol buffer representing the serialization format of DT_VARIANT tensors.\nmessage VariantTensorDataProto {\n  // Name of the type of objects being serialized.\n  string type_name = 1;\n  // Portions of the object that are not Tensors.\n  bytes metadata = 2;\n  // Tensors contained within objects being serialized.\n  repeated TensorProto tensors = 3;\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/tensor_shape.proto",
    "content": "// Protocol buffer representing the shape of tensors.\n\nsyntax = \"proto3\";\noption cc_enable_arenas = true;\noption java_outer_classname = \"TensorShapeProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\n\npackage tensorflow;\n\n// Dimensions of a tensor.\nmessage TensorShapeProto {\n  // One dimension of the tensor.\n  message Dim {\n    // Size of the tensor in that dimension.\n    // This value must be >= -1, but values of -1 are reserved for \"unknown\"\n    // shapes (values of -1 mean \"unknown\" dimension).  Certain wrappers\n    // that work with TensorShapeProto may fail at runtime when deserializing\n    // a TensorShapeProto containing a dim value of -1.\n    int64 size = 1;\n\n    // Optional name of the tensor dimension.\n    string name = 2;\n  };\n\n  // Dimensions of the tensor, such as {\"input\", 30}, {\"output\", 40}\n  // for a 30 x 40 2D tensor.  If an entry has size -1, this\n  // corresponds to a dimension of unknown size. The names are\n  // optional.\n  //\n  // The order of entries in \"dim\" matters: It indicates the layout of the\n  // values in the tensor in-memory representation.\n  //\n  // The first entry in \"dim\" is the outermost dimension used to layout the\n  // values, the last entry is the innermost dimension.  This matches the\n  // in-memory layout of RowMajor Eigen tensors.\n  //\n  // If \"dim.size()\" > 0, \"unknown_rank\" must be false.\n  repeated Dim dim = 2;\n\n  // If true, the number of dimensions in the shape is unknown.\n  //\n  // If true, \"dim.size()\" must be 0.\n  bool unknown_rank = 3;\n};\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/types.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"TypesProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\n\n// LINT.IfChange\nenum DataType {\n  // Not a legal value for DataType.  Used to indicate a DataType field\n  // has not been set.\n  DT_INVALID = 0;\n\n  // Data types that all computation devices are expected to be\n  // capable to support.\n  DT_FLOAT = 1;\n  DT_DOUBLE = 2;\n  DT_INT32 = 3;\n  DT_UINT8 = 4;\n  DT_INT16 = 5;\n  DT_INT8 = 6;\n  DT_STRING = 7;\n  DT_COMPLEX64 = 8;  // Single-precision complex\n  DT_INT64 = 9;\n  DT_BOOL = 10;\n  DT_QINT8 = 11;     // Quantized int8\n  DT_QUINT8 = 12;    // Quantized uint8\n  DT_QINT32 = 13;    // Quantized int32\n  DT_BFLOAT16 = 14;  // Float32 truncated to 16 bits.  Only for cast ops.\n  DT_QINT16 = 15;    // Quantized int16\n  DT_QUINT16 = 16;   // Quantized uint16\n  DT_UINT16 = 17;\n  DT_COMPLEX128 = 18;  // Double-precision complex\n  DT_HALF = 19;\n  DT_RESOURCE = 20;\n  DT_VARIANT = 21;  // Arbitrary C++ data types\n  DT_UINT32 = 22;\n  DT_UINT64 = 23;\n\n  // Do not use!  These are only for parameters.  Every enum above\n  // should have a corresponding value below (verified by types_test).\n  DT_FLOAT_REF = 101;\n  DT_DOUBLE_REF = 102;\n  DT_INT32_REF = 103;\n  DT_UINT8_REF = 104;\n  DT_INT16_REF = 105;\n  DT_INT8_REF = 106;\n  DT_STRING_REF = 107;\n  DT_COMPLEX64_REF = 108;\n  DT_INT64_REF = 109;\n  DT_BOOL_REF = 110;\n  DT_QINT8_REF = 111;\n  DT_QUINT8_REF = 112;\n  DT_QINT32_REF = 113;\n  DT_BFLOAT16_REF = 114;\n  DT_QINT16_REF = 115;\n  DT_QUINT16_REF = 116;\n  DT_UINT16_REF = 117;\n  DT_COMPLEX128_REF = 118;\n  DT_HALF_REF = 119;\n  DT_RESOURCE_REF = 120;\n  DT_VARIANT_REF = 121;\n  DT_UINT32_REF = 122;\n  DT_UINT64_REF = 123;\n}\n// LINT.ThenChange(\n//    https://www.tensorflow.org/code/tensorflow/c/c_api.h,\n//    https://www.tensorflow.org/code/tensorflow/go/tensor.go,\n//    https://www.tensorflow.org/code/tensorflow/core/framework/tensor.cc,\n//    https://www.tensorflow.org/code/tensorflow/core/framework/types.h,\n//    https://www.tensorflow.org/code/tensorflow/core/framework/types.cc,\n//    https://www.tensorflow.org/code/tensorflow/python/framework/dtypes.py,\n//    https://www.tensorflow.org/code/tensorflow/python/framework/function.py)\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/variable.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\n\noption cc_enable_arenas = true;\noption java_outer_classname = \"VariableProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\n\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\n\n// Indicates when a distributed variable will be synced.\nenum VariableSynchronization {\n  // `AUTO`: Indicates that the synchronization will be determined by the\n  // current `DistributionStrategy` (eg. With `MirroredStrategy` this would be\n  // `ON_WRITE`).\n  VARIABLE_SYNCHRONIZATION_AUTO = 0;\n  // `NONE`: Indicates that there will only be one copy of the variable, so\n  // there is no need to sync.\n  VARIABLE_SYNCHRONIZATION_NONE = 1;\n  // `ON_WRITE`: Indicates that the variable will be updated across devices\n  // every time it is written.\n  VARIABLE_SYNCHRONIZATION_ON_WRITE = 2;\n  // `ON_READ`: Indicates that the variable will be aggregated across devices\n  // when it is read (eg. when checkpointing or when evaluating an op that uses\n  // the variable).\n  VARIABLE_SYNCHRONIZATION_ON_READ = 3;\n}\n\n// Indicates how a distributed variable will be aggregated.\nenum VariableAggregation {\n  // `NONE`: This is the default, giving an error if you use a\n  // variable-update operation with multiple replicas.\n  VARIABLE_AGGREGATION_NONE = 0;\n  // `SUM`: Add the updates across replicas.\n  VARIABLE_AGGREGATION_SUM = 1;\n  // `MEAN`: Take the arithmetic mean (\"average\") of the updates across\n  // replicas.\n  VARIABLE_AGGREGATION_MEAN = 2;\n  // `ONLY_FIRST_REPLICA`: This is for when every replica is performing the same\n  // update, but we only want to perform the update once. Used, e.g., for the\n  // global step counter.\n  VARIABLE_AGGREGATION_ONLY_FIRST_REPLICA = 3;\n}\n\n// Protocol buffer representing a Variable.\nmessage VariableDef {\n  // Name of the variable tensor.\n  string variable_name = 1;\n\n  // Name of the tensor holding the variable's initial value.\n  string initial_value_name = 6;\n\n  // Name of the initializer op.\n  string initializer_name = 2;\n\n  // Name of the snapshot tensor.\n  string snapshot_name = 3;\n\n  // Support for saving variables as slices of a larger variable.\n  SaveSliceInfoDef save_slice_info_def = 4;\n\n  // Whether to represent this as a ResourceVariable.\n  bool is_resource = 5;\n\n  // Whether this variable should be trained.\n  bool trainable = 7;\n\n  // Indicates when a distributed variable will be synced.\n  VariableSynchronization synchronization = 8;\n\n  // Indicates how a distributed variable will be aggregated.\n  VariableAggregation aggregation = 9;\n}\n\nmessage SaveSliceInfoDef {\n  // Name of the full variable of which this is a slice.\n  string full_name = 1;\n  // Shape of the full variable.\n  repeated int64 full_shape = 2;\n  // Offset of this variable into the full variable.\n  repeated int64 var_offset = 3;\n  // Shape of this variable.\n  repeated int64 var_shape = 4;\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/framework/versions.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"VersionsProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/framework\";\n\n// Version information for a piece of serialized data\n//\n// There are different types of versions for each type of data\n// (GraphDef, etc.), but they all have the same common shape\n// described here.\n//\n// Each consumer has \"consumer\" and \"min_producer\" versions (specified\n// elsewhere).  A consumer is allowed to consume this data if\n//\n//   producer >= min_producer\n//   consumer >= min_consumer\n//   consumer not in bad_consumers\n//\nmessage VersionDef {\n  // The version of the code that produced this data.\n  int32 producer = 1;\n\n  // Any consumer below this version is not allowed to consume this data.\n  int32 min_consumer = 2;\n\n  // Specific consumer versions which are disallowed (e.g. due to bugs).\n  repeated int32 bad_consumers = 3;\n};\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/protobuf/meta_graph.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"MetaGraphProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/protobuf\";\nimport \"google/protobuf/any.proto\";\n\nimport \"tensorflow/core/framework/graph.proto\";\nimport \"tensorflow/core/framework/op_def.proto\";\nimport \"tensorflow/core/framework/tensor_shape.proto\";\nimport \"tensorflow/core/framework/types.proto\";\nimport \"tensorflow/core/protobuf/saved_object_graph.proto\";\nimport \"tensorflow/core/protobuf/saver.proto\";\nimport \"tensorflow/core/protobuf/struct.proto\";\n\n// NOTE: This protocol buffer is evolving, and will go through revisions in the\n// coming months.\n//\n// Protocol buffer containing the following which are necessary to restart\n// training, run inference. It can be used to serialize/de-serialize memory\n// objects necessary for running computation in a graph when crossing the\n// process boundary. It can be used for long term storage of graphs,\n// cross-language execution of graphs, etc.\n//   MetaInfoDef\n//   GraphDef\n//   SaverDef\n//   CollectionDef\n//   TensorInfo\n//   SignatureDef\nmessage MetaGraphDef {\n  // Meta information regarding the graph to be exported.  To be used by users\n  // of this protocol buffer to encode information regarding their meta graph.\n  message MetaInfoDef {\n    // User specified Version string. Can be the name of the model and revision,\n    // steps this model has been trained to, etc.\n    string meta_graph_version = 1;\n\n    // A copy of the OpDefs used by the producer of this graph_def.\n    // Descriptions and Ops not used in graph_def are stripped out.\n    OpList stripped_op_list = 2;\n\n    // A serialized protobuf. Can be the time this meta graph is created, or\n    // modified, or name of the model.\n    google.protobuf.Any any_info = 3;\n\n    // User supplied tag(s) on the meta_graph and included graph_def.\n    //\n    // MetaGraphDefs should be tagged with their capabilities or use-cases.\n    // Examples: \"train\", \"serve\", \"gpu\", \"tpu\", etc.\n    // These tags enable loaders to access the MetaGraph(s) appropriate for a\n    // specific use-case or runtime environment.\n    repeated string tags = 4;\n\n    // The __version__ string of the tensorflow build used to write this graph.\n    // This will be populated by the framework, which will overwrite any user\n    // supplied value.\n    string tensorflow_version = 5;\n\n    // The __git_version__ string of the tensorflow build used to write this\n    // graph. This will be populated by the framework, which will overwrite any\n    // user supplied value.\n    string tensorflow_git_version = 6;\n\n    // A flag to denote whether default-valued attrs have been stripped from\n    // the nodes in this graph_def.\n    bool stripped_default_attrs = 7;\n  }\n  MetaInfoDef meta_info_def = 1;\n\n  // GraphDef.\n  GraphDef graph_def = 2;\n\n  // SaverDef.\n  SaverDef saver_def = 3;\n\n  // collection_def: Map from collection name to collections.\n  // See CollectionDef section for details.\n  map<string, CollectionDef> collection_def = 4;\n\n  // signature_def: Map from user supplied key for a signature to a single\n  // SignatureDef.\n  map<string, SignatureDef> signature_def = 5;\n\n  // Asset file def to be used with the defined graph.\n  repeated AssetFileDef asset_file_def = 6;\n\n  // Extra information about the structure of functions and stateful objects.\n  SavedObjectGraph object_graph_def = 7;\n}\n\n// CollectionDef should cover most collections.\n// To add a user-defined collection, do one of the following:\n// 1. For simple data types, such as string, int, float:\n//      tf.add_to_collection(\"your_collection_name\", your_simple_value)\n//    strings will be stored as bytes_list.\n//\n// 2. For Protobuf types, there are three ways to add them:\n//    1) tf.add_to_collection(\"your_collection_name\",\n//         your_proto.SerializeToString())\n//\n//       collection_def {\n//         key: \"user_defined_bytes_collection\"\n//         value {\n//           bytes_list {\n//             value: \"queue_name: \\\"test_queue\\\"\\n\"\n//           }\n//         }\n//       }\n//\n//  or\n//\n//    2) tf.add_to_collection(\"your_collection_name\", str(your_proto))\n//\n//       collection_def {\n//         key: \"user_defined_string_collection\"\n//         value {\n//          bytes_list {\n//             value: \"\\n\\ntest_queue\"\n//           }\n//         }\n//       }\n//\n//  or\n//\n//    3) any_buf = any_pb2.Any()\n//       tf.add_to_collection(\"your_collection_name\",\n//         any_buf.Pack(your_proto))\n//\n//       collection_def {\n//         key: \"user_defined_any_collection\"\n//         value {\n//           any_list {\n//             value {\n//               type_url: \"type.googleapis.com/tensorflow.QueueRunnerDef\"\n//               value: \"\\n\\ntest_queue\"\n//             }\n//           }\n//         }\n//       }\n//\n// 3. For Python objects, implement to_proto() and from_proto(), and register\n//    them in the following manner:\n//    ops.register_proto_function(\"your_collection_name\",\n//                                proto_type,\n//                                to_proto=YourPythonObject.to_proto,\n//                                from_proto=YourPythonObject.from_proto)\n//    These functions will be invoked to serialize and de-serialize the\n//    collection. For example,\n//    ops.register_proto_function(ops.GraphKeys.GLOBAL_VARIABLES,\n//                                proto_type=variable_pb2.VariableDef,\n//                                to_proto=Variable.to_proto,\n//                                from_proto=Variable.from_proto)\nmessage CollectionDef {\n  // NodeList is used for collecting nodes in graph. For example\n  // collection_def {\n  //   key: \"summaries\"\n  //   value {\n  //     node_list {\n  //       value: \"input_producer/ScalarSummary:0\"\n  //       value: \"shuffle_batch/ScalarSummary:0\"\n  //       value: \"ImageSummary:0\"\n  //     }\n  //   }\n  message NodeList {\n    repeated string value = 1;\n  }\n\n  // BytesList is used for collecting strings and serialized protobufs. For\n  // example:\n  // collection_def {\n  //   key: \"trainable_variables\"\n  //   value {\n  //     bytes_list {\n  //       value: \"\\n\\017conv1/weights:0\\022\\024conv1/weights/Assign\n  //              \\032\\024conv1/weights/read:0\"\n  //       value: \"\\n\\016conv1/biases:0\\022\\023conv1/biases/Assign\\032\n  //              \\023conv1/biases/read:0\"\n  //     }\n  //   }\n  // }\n  message BytesList {\n    repeated bytes value = 1;\n  }\n\n  // Int64List is used for collecting int, int64 and long values.\n  message Int64List {\n    repeated int64 value = 1 [packed = true];\n  }\n\n  // FloatList is used for collecting float values.\n  message FloatList {\n    repeated float value = 1 [packed = true];\n  }\n\n  // AnyList is used for collecting Any protos.\n  message AnyList {\n    repeated google.protobuf.Any value = 1;\n  }\n\n  oneof kind {\n    NodeList node_list = 1;\n    BytesList bytes_list = 2;\n    Int64List int64_list = 3;\n    FloatList float_list = 4;\n    AnyList any_list = 5;\n  }\n}\n\n// Information about a Tensor necessary for feeding or retrieval.\nmessage TensorInfo {\n  // For sparse tensors, The COO encoding stores a triple of values, indices,\n  // and shape.\n  message CooSparse {\n    // The shape of the values Tensor is [?].  Its dtype must be the dtype of\n    // the SparseTensor as a whole, given in the enclosing TensorInfo.\n    string values_tensor_name = 1;\n\n    // The indices Tensor must have dtype int64 and shape [?, ?].\n    string indices_tensor_name = 2;\n\n    // The dynamic logical shape represented by the SparseTensor is recorded in\n    // the Tensor referenced here.  It must have dtype int64 and shape [?].\n    string dense_shape_tensor_name = 3;\n  }\n\n  // Generic encoding for composite tensors.\n  message CompositeTensor {\n    // The serialized TypeSpec for the composite tensor.\n    TypeSpecProto type_spec = 1;\n\n    // A TensorInfo for each flattened component tensor.\n    repeated TensorInfo components = 2;\n  }\n\n  oneof encoding {\n    // For dense `Tensor`s, the name of the tensor in the graph.\n    string name = 1;\n    // There are many possible encodings of sparse matrices\n    // (https://en.wikipedia.org/wiki/Sparse_matrix).  Currently, TensorFlow\n    // uses only the COO encoding.  This is supported and documented in the\n    // SparseTensor Python class.\n    CooSparse coo_sparse = 4;\n    // Generic encoding for CompositeTensors.\n    CompositeTensor composite_tensor = 5;\n  }\n  DataType dtype = 2;\n  // The static shape should be recorded here, to the extent that it can\n  // be known in advance.  In the case of a SparseTensor, this field describes\n  // the logical shape of the represented tensor (aka dense_shape).\n  TensorShapeProto tensor_shape = 3;\n}\n\n// SignatureDef defines the signature of a computation supported by a TensorFlow\n// graph.\n//\n// For example, a model with two loss computations, sharing a single input,\n// might have the following signature_def map.\n//\n// Note that across the two SignatureDefs \"loss_A\" and \"loss_B\", the input key,\n// output key, and method_name are identical, and will be used by system(s) that\n// implement or rely upon this particular loss method. The output tensor names\n// differ, demonstrating how different outputs can exist for the same method.\n//\n// signature_def {\n//   key: \"loss_A\"\n//   value {\n//     inputs {\n//       key: \"input\"\n//       value {\n//         name: \"input:0\"\n//         dtype: DT_STRING\n//         tensor_shape: ...\n//       }\n//     }\n//     outputs {\n//       key: \"loss_output\"\n//       value {\n//         name: \"loss_output_A:0\"\n//         dtype: DT_FLOAT\n//         tensor_shape: ...\n//       }\n//     }\n//   }\n//   ...\n//   method_name: \"some/package/compute_loss\"\n// }\n// signature_def {\n//   key: \"loss_B\"\n//   value {\n//     inputs {\n//       key: \"input\"\n//       value {\n//         name: \"input:0\"\n//         dtype: DT_STRING\n//         tensor_shape: ...\n//       }\n//     }\n//     outputs {\n//       key: \"loss_output\"\n//       value {\n//         name: \"loss_output_B:0\"\n//         dtype: DT_FLOAT\n//         tensor_shape: ...\n//       }\n//     }\n//   }\n//   ...\n//   method_name: \"some/package/compute_loss\"\n// }\nmessage SignatureDef {\n  // Named input parameters.\n  map<string, TensorInfo> inputs = 1;\n  // Named output parameters.\n  map<string, TensorInfo> outputs = 2;\n  // Extensible method_name information enabling third-party users to mark a\n  // SignatureDef as supporting a particular method. This enables producers and\n  // consumers of SignatureDefs, e.g. a model definition library and a serving\n  // library to have a clear hand-off regarding the semantics of a computation.\n  //\n  // Note that multiple SignatureDefs in a single MetaGraphDef may have the same\n  // method_name. This is commonly used to support multi-headed computation,\n  // where a single graph computation may return multiple results.\n  string method_name = 3;\n}\n\n// An asset file def for a single file or a set of sharded files with the same\n// name.\nmessage AssetFileDef {\n  // The tensor to bind the asset filename to.\n  TensorInfo tensor_info = 1;\n  // The filename within an assets directory. Note: does not include the path\n  // prefix, i.e. directories. For an asset at /tmp/path/vocab.txt, the filename\n  // would be \"vocab.txt\".\n  string filename = 2;\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/protobuf/saved_model.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"SavedModelProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.framework\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/protobuf\";\nimport \"tensorflow/core/protobuf/meta_graph.proto\";\n\n// SavedModel is the high level serialization format for TensorFlow Models.\n// See [todo: doc links, similar to session_bundle] for more information.\nmessage SavedModel {\n  // The schema version of the SavedModel instance. Used for versioning when\n  // making future changes to the specification/implementation. Initial value\n  // at release will be 1.\n  int64 saved_model_schema_version = 1;\n\n  // One or more MetaGraphs.\n  repeated MetaGraphDef meta_graphs = 2;\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/protobuf/saved_object_graph.proto",
    "content": "syntax = \"proto3\";\n\nimport \"tensorflow/core/protobuf/trackable_object_graph.proto\";\nimport \"tensorflow/core/protobuf/struct.proto\";\nimport \"tensorflow/core/framework/tensor_shape.proto\";\nimport \"tensorflow/core/framework/types.proto\";\nimport \"tensorflow/core/framework/versions.proto\";\nimport \"tensorflow/core/framework/variable.proto\";\n\noption cc_enable_arenas = true;\n\npackage tensorflow;\n\n// A SavedObjectGraph is part of object-based SavedModels in TF 2.0. It\n// describes the directed graph of Python objects (or equivalent in other\n// languages) that make up a model, with nodes[0] at the root.\n\n// SavedObjectGraph shares some structure with TrackableObjectGraph, but\n// SavedObjectGraph belongs to the MetaGraph and contains pointers to functions\n// and type information, while TrackableObjectGraph lives in the checkpoint\n// and contains pointers only to variable values.\n\nmessage SavedObjectGraph {\n  // Flattened list of objects in the object graph.\n  //\n  // The position of the object in this list indicates its id.\n  // Nodes[0] is considered the root node.\n  repeated SavedObject nodes = 1;\n\n  // Information about captures and output structures in concrete functions.\n  // Referenced from SavedBareConcreteFunction and SavedFunction.\n  map<string, SavedConcreteFunction> concrete_functions = 2;\n}\n\nmessage SavedObject {\n  // Objects which this object depends on: named edges in the dependency\n  // graph.\n  //\n  // Note: currently only valid if kind == \"user_object\".\n  repeated TrackableObjectGraph.TrackableObject.ObjectReference\n      children = 1;\n\n  // Removed when forking SavedObject from TrackableObjectGraph.\n  reserved \"attributes\";\n  reserved 2;\n\n  // Slot variables owned by this object. This describes the three-way\n  // (optimizer, variable, slot variable) relationship; none of the three\n  // depend on the others directly.\n  //\n  // Note: currently only valid if kind == \"user_object\".\n  repeated TrackableObjectGraph.TrackableObject.SlotVariableReference\n      slot_variables = 3;\n\n  oneof kind {\n    SavedUserObject user_object = 4;\n    SavedAsset asset = 5;\n    SavedFunction function = 6;\n    SavedVariable variable = 7;\n    SavedBareConcreteFunction bare_concrete_function = 8;\n    SavedConstant constant = 9;\n    SavedResource resource = 10;\n  }\n}\n\n// A SavedUserObject is an object (in the object-oriented language of the\n// TensorFlow program) of some user- or framework-defined class other than\n// those handled specifically by the other kinds of SavedObjects.\n//\n// This object cannot be evaluated as a tensor, and therefore cannot be bound\n// to an input of a function.\nmessage SavedUserObject {\n  // Corresponds to a registration of the type to use in the loading program.\n  string identifier = 1;\n  // Version information from the producer of this SavedUserObject.\n  VersionDef version = 2;\n  // Initialization-related metadata.\n  string metadata = 3;\n}\n\n// A SavedAsset points to an asset in the MetaGraph.\n//\n// When bound to a function this object evaluates to a tensor with the absolute\n// filename. Users should not depend on a particular part of the filename to\n// remain stable (e.g. basename could be changed).\nmessage SavedAsset {\n  // Index into `MetaGraphDef.asset_file_def[]` that describes the Asset.\n  //\n  // Only the field `AssetFileDef.filename` is used. Other fields, such as\n  // `AssetFileDef.tensor_info`, MUST be ignored.\n  int32 asset_file_def_index = 1;\n}\n\n// A function with multiple signatures, possibly with non-Tensor arguments.\nmessage SavedFunction {\n  repeated string concrete_functions = 1;\n  FunctionSpec function_spec = 2;\n}\n\n// Stores low-level information about a concrete function. Referenced in either\n// a SavedFunction or a SavedBareConcreteFunction.\nmessage SavedConcreteFunction {\n  // Bound inputs to the function. The SavedObjects identified by the node ids\n  // given here are appended as extra inputs to the caller-supplied inputs.\n  // The only types of SavedObjects valid here are SavedVariable, SavedResource\n  // and SavedAsset.\n  repeated int32 bound_inputs = 2;\n  // Input in canonicalized form that was received to create this concrete\n  // function.\n  StructuredValue canonicalized_input_signature = 3;\n  // Output that was the return value of this function after replacing all\n  // Tensors with TensorSpecs. This can be an arbitrary nested function and will\n  // be used to reconstruct the full structure from pure tensors.\n  StructuredValue output_signature = 4;\n}\n\nmessage SavedBareConcreteFunction {\n  // Identifies a SavedConcreteFunction.\n  string concrete_function_name = 1;\n\n  // A sequence of unique strings, one per Tensor argument.\n  repeated string argument_keywords = 2;\n  // The prefix of `argument_keywords` which may be identified by position.\n  int64 allowed_positional_arguments = 3;\n}\n\nmessage SavedConstant {\n  // An Operation name for a ConstantOp in this SavedObjectGraph's MetaGraph.\n  string operation = 1;\n}\n\n// Represents a Variable that is initialized by loading the contents from the\n// checkpoint.\nmessage SavedVariable {\n  DataType dtype = 1;\n  TensorShapeProto shape = 2;\n  bool trainable = 3;\n  VariableSynchronization synchronization = 4;\n  VariableAggregation aggregation = 5;\n  string name = 6;\n}\n\n// Represents `FunctionSpec` used in `Function`. This represents a\n// function that has been wrapped as a TensorFlow `Function`.\nmessage FunctionSpec {\n  // Full arg spec from inspect.getfullargspec().\n  StructuredValue fullargspec = 1;\n  // Whether this represents a class method.\n  bool is_method = 2;\n  // The input signature, if specified.\n  StructuredValue input_signature = 5;\n\n  reserved 3, 4;\n}\n\n// A SavedResource represents a TF object that holds state during its lifetime.\n// An object of this type can have a reference to a:\n// create_resource() and an initialize() function.\nmessage SavedResource {\n  // A device specification indicating a required placement for the resource\n  // creation function, e.g. \"CPU\". An empty string allows the user to select a\n  // device.\n  string device = 1;\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/protobuf/saver.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\noption cc_enable_arenas = true;\noption java_outer_classname = \"SaverProtos\";\noption java_multiple_files = true;\noption java_package = \"org.tensorflow.util\";\noption go_package = \"github.com/tensorflow/tensorflow/tensorflow/go/core/protobuf\";\n\n// Protocol buffer representing the configuration of a Saver.\nmessage SaverDef {\n  // The name of the tensor in which to specify the filename when saving or\n  // restoring a model checkpoint.\n  string filename_tensor_name = 1;\n\n  // The operation to run when saving a model checkpoint.\n  string save_tensor_name = 2;\n\n  // The operation to run when restoring a model checkpoint.\n  string restore_op_name = 3;\n\n  // Maximum number of checkpoints to keep.  If 0, no checkpoints are deleted.\n  int32 max_to_keep = 4;\n\n  // Shard the save files, one per device that has Variable nodes.\n  bool sharded = 5;\n\n  // How often to keep an additional checkpoint. If not specified, only the last\n  // \"max_to_keep\" checkpoints are kept; if specified, in addition to keeping\n  // the last \"max_to_keep\" checkpoints, an additional checkpoint will be kept\n  // for every n hours of training.\n  float keep_checkpoint_every_n_hours = 6;\n\n  // A version number that identifies a different on-disk checkpoint format.\n  // Usually, each subclass of BaseSaverBuilder works with a particular\n  // version/format.  However, it is possible that the same builder may be\n  // upgraded to support a newer checkpoint format in the future.\n  enum CheckpointFormatVersion {\n    // Internal legacy format.\n    LEGACY = 0;\n    // Deprecated format: tf.Saver() which works with tensorflow::table::Table.\n    V1 = 1;\n    // Current format: more efficient.\n    V2 = 2;\n  }\n  CheckpointFormatVersion version = 7;\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/protobuf/struct.proto",
    "content": "syntax = \"proto3\";\n\npackage tensorflow;\n\nimport \"tensorflow/core/framework/tensor_shape.proto\";\nimport \"tensorflow/core/framework/types.proto\";\n\n// `StructuredValue` represents a dynamically typed value representing various\n// data structures that are inspired by Python data structures typically used in\n// TensorFlow functions as inputs and outputs.\n//\n// For example when saving a Layer there may be a `training` argument. If the\n// user passes a boolean True/False, that switches between two concrete\n// TensorFlow functions. In order to switch between them in the same way after\n// loading the SavedModel, we need to represent \"True\" and \"False\".\n//\n// A more advanced example might be a function which takes a list of\n// dictionaries mapping from strings to Tensors. In order to map from\n// user-specified arguments `[{\"a\": tf.constant(1.)}, {\"q\": tf.constant(3.)}]`\n// after load to the right saved TensorFlow function, we need to represent the\n// nested structure and the strings, recording that we have a trace for anything\n// matching `[{\"a\": tf.TensorSpec(None, tf.float32)}, {\"q\": tf.TensorSpec([],\n// tf.float64)}]` as an example.\n//\n// Likewise functions may return nested structures of Tensors, for example\n// returning a dictionary mapping from strings to Tensors. In order for the\n// loaded function to return the same structure we need to serialize it.\n//\n// This is an ergonomic aid for working with loaded SavedModels, not a promise\n// to serialize all possible function signatures. For example we do not expect\n// to pickle generic Python objects, and ideally we'd stay language-agnostic.\nmessage StructuredValue {\n  // The kind of value.\n  oneof kind {\n    // Represents None.\n    NoneValue none_value = 1;\n\n    // Represents a double-precision floating-point value (a Python `float`).\n    double float64_value = 11;\n    // Represents a signed integer value, limited to 64 bits.\n    // Larger values from Python's arbitrary-precision integers are unsupported.\n    sint64 int64_value = 12;\n    // Represents a string of Unicode characters stored in a Python `str`.\n    // In Python 3, this is exactly what type `str` is.\n    // In Python 2, this is the UTF-8 encoding of the characters.\n    // For strings with ASCII characters only (as often used in TensorFlow code)\n    // there is effectively no difference between the language versions.\n    // The obsolescent `unicode` type of Python 2 is not supported here.\n    string string_value = 13;\n    // Represents a boolean value.\n    bool bool_value = 14;\n\n    // Represents a TensorShape.\n    tensorflow.TensorShapeProto tensor_shape_value = 31;\n    // Represents an enum value for dtype.\n    tensorflow.DataType tensor_dtype_value = 32;\n    // Represents a value for tf.TensorSpec.\n    TensorSpecProto tensor_spec_value = 33;\n    // Represents a value for tf.TypeSpec.\n    TypeSpecProto type_spec_value = 34;\n\n    // Represents a list of `Value`.\n    ListValue list_value = 51;\n    // Represents a tuple of `Value`.\n    TupleValue tuple_value = 52;\n    // Represents a dict `Value`.\n    DictValue dict_value = 53;\n    // Represents Python's namedtuple.\n    NamedTupleValue named_tuple_value = 54;\n  }\n}\n\n// Represents None.\nmessage NoneValue {}\n\n// Represents a Python list.\nmessage ListValue {\n  repeated StructuredValue values = 1;\n}\n\n// Represents a Python tuple.\nmessage TupleValue {\n  repeated StructuredValue values = 1;\n}\n\n// Represents a Python dict keyed by `str`.\n// The comment on Unicode from Value.string_value applies analogously.\nmessage DictValue {\n  map<string, StructuredValue> fields = 1;\n}\n\n// Represents a (key, value) pair.\nmessage PairValue {\n  string key = 1;\n  StructuredValue value = 2;\n}\n\n// Represents Python's namedtuple.\nmessage NamedTupleValue {\n  string name = 1;\n  repeated PairValue values = 2;\n}\n\n// A protobuf to tf.TensorSpec.\nmessage TensorSpecProto {\n  string name = 1;\n  tensorflow.TensorShapeProto shape = 2;\n  tensorflow.DataType dtype = 3;\n}\n\n// Represents a tf.TypeSpec\nmessage TypeSpecProto {\n  enum TypeSpecClass {\n    UNKNOWN = 0;\n    SPARSE_TENSOR_SPEC = 1;   // tf.SparseTensorSpec\n    INDEXED_SLICES_SPEC = 2;  // tf.IndexedSlicesSpec\n    RAGGED_TENSOR_SPEC = 3;   // tf.RaggedTensorSpec\n    TENSOR_ARRAY_SPEC = 4;    // tf.TensorArraySpec\n    DATA_DATASET_SPEC = 5;    // tf.data.DatasetSpec\n    DATA_ITERATOR_SPEC = 6;   // IteratorSpec from data/ops/iterator_ops.py\n    OPTIONAL_SPEC = 7;        // tf.OptionalSpec\n    PER_REPLICA_SPEC = 8;     // PerReplicaSpec from distribute/values.py\n    VARIABLE_SPEC = 9;        // tf.VariableSpec\n  }\n  TypeSpecClass type_spec_class = 1;\n\n  // The value returned by TypeSpec._serialize().\n  StructuredValue type_state = 2;\n\n  // This is currently redundant with the type_spec_class enum, and is only\n  // used for error reporting.  In particular, if you use an older binary to\n  // load a newer model, and the model uses a TypeSpecClass that the older\n  // binary doesn't support, then this lets us display a useful error message.\n  string type_spec_class_name = 3;\n}\n"
  },
  {
    "path": "tensorflow/protos/tensorflow/core/protobuf/trackable_object_graph.proto",
    "content": "syntax = \"proto3\";\n\noption cc_enable_arenas = true;\n\npackage tensorflow;\n\n// A TensorBundle addition which saves extra information about the objects which\n// own variables, allowing for more robust checkpoint loading into modified\n// programs.\n\nmessage TrackableObjectGraph {\n  message TrackableObject {\n    message ObjectReference {\n      // An index into `TrackableObjectGraph.nodes`, indicating the object\n      // being referenced.\n      int32 node_id = 1;\n      // A user-provided name for the edge.\n      string local_name = 2;\n    }\n\n    message SerializedTensor {\n      // A name for the Tensor. Simple variables have only one\n      // `SerializedTensor` named \"VARIABLE_VALUE\" by convention. This value may\n      // be restored on object creation as an optimization.\n      string name = 1;\n      // The full name of the variable/tensor, if applicable. Used to allow\n      // name-based loading of checkpoints which were saved using an\n      // object-based API. Should match the checkpoint key which would have been\n      // assigned by tf.train.Saver.\n      string full_name = 2;\n      // The generated name of the Tensor in the checkpoint.\n      string checkpoint_key = 3;\n      // Whether checkpoints should be considered as matching even without this\n      // value restored. Used for non-critical values which don't affect the\n      // TensorFlow graph, such as layer configurations.\n      bool optional_restore = 4;\n    }\n\n    message SlotVariableReference {\n      // An index into `TrackableObjectGraph.nodes`, indicating the\n      // variable object this slot was created for.\n      int32 original_variable_node_id = 1;\n      // The name of the slot (e.g. \"m\"/\"v\").\n      string slot_name = 2;\n      // An index into `TrackableObjectGraph.nodes`, indicating the\n      // `Object` with the value of the slot variable.\n      int32 slot_variable_node_id = 3;\n    }\n\n    // Objects which this object depends on.\n    repeated ObjectReference children = 1;\n    // Serialized data specific to this object.\n    repeated SerializedTensor attributes = 2;\n    // Slot variables owned by this object.\n    repeated SlotVariableReference slot_variables = 3;\n  }\n\n  repeated TrackableObject nodes = 1;\n}\n"
  },
  {
    "path": "tensorflow/src/conform/mod.rs",
    "content": "#![allow(unused)]\n#![allow(deprecated)]\n#![allow(non_snake_case)]\n\npub mod tf;\n\nuse crate::tfpb;\nuse crate::tfpb::tensorflow::tensor_shape_proto::Dim;\nuse crate::tfpb::tensorflow::{DataType, TensorProto, TensorShapeProto};\nuse std::convert::TryInto;\nuse tract_hir::internal::*;\n\npub fn placeholder<Shape: Into<Option<TensorShapeProto>>>(\n    name: &str,\n    t: DataType,\n    shape: Shape,\n) -> tfpb::tensorflow::NodeDef {\n    let mut node = tfpb::node().name(name).op(\"Placeholder\").attr(\"dtype\", t);\n    if let Some(shape) = shape.into() {\n        node = node.attr(\"shape\", shape)\n    }\n    node\n}\n\npub fn tensor_shape(dims: &[usize]) -> TensorShapeProto {\n    TensorShapeProto {\n        dim: dims.iter().map(|&d| Dim { size: d as i64, name: String::new() }).collect(),\n        unknown_rank: false,\n    }\n}\n\npub fn const_f32(name: &str, t: &Tensor) -> tfpb::tensorflow::NodeDef {\n    let tf: TensorProto = t.cast_to::<f32>().unwrap().as_ref().try_into().unwrap();\n    tfpb::node().name(name).op(\"Const\").attr(\"dtype\", DataType::DtFloat).attr(\"value\", tf)\n}\n\npub fn placeholder_f32(name: &str) -> tfpb::tensorflow::NodeDef {\n    placeholder(name, DataType::DtFloat, None)\n}\n\npub fn const_i32(name: &str, t: &Tensor) -> tfpb::tensorflow::NodeDef {\n    let tf: TensorProto = t.cast_to::<i32>().unwrap().as_ref().try_into().unwrap();\n    tfpb::node().name(name).op(\"Const\").attr(\"dtype\", DataType::DtInt32).attr(\"value\", tf)\n}\n\npub fn placeholder_i32(name: &str) -> tfpb::tensorflow::NodeDef {\n    placeholder(name, DataType::DtInt32, None)\n}\n"
  },
  {
    "path": "tensorflow/src/conform/tf.rs",
    "content": "#![allow(dead_code)]\n\nuse std::{fs, path};\n\nuse tensorflow as tf;\nuse tensorflow::DataType;\nuse tensorflow::FetchToken;\nuse tensorflow::Graph;\nuse tensorflow::Session;\nuse tensorflow::SessionRunArgs;\n\nuse tract_hir::internal::*;\nuse tract_ndarray::prelude::*;\n\nuse std::collections::HashMap;\nuse std::collections::HashSet;\n\npub struct Tensorflow {\n    graph: Graph,\n}\n\npub fn version() -> String {\n    tf::version().unwrap()\n}\n\npub fn for_path<P: AsRef<path::Path>>(p: P) -> TractResult<Tensorflow> {\n    use std::io::Read;\n    let mut model = vec![];\n    fs::File::open(p)?.read_to_end(&mut model)?;\n    for_slice(&*model)\n}\n\npub fn for_slice(buf: &[u8]) -> TractResult<Tensorflow> {\n    let mut graph = Graph::new();\n    graph.import_graph_def(buf, &::tensorflow::ImportGraphDefOptions::new())?;\n    Ok(Tensorflow { graph })\n}\n\nenum TensorHolder {\n    Bool(tf::Tensor<bool>),\n    F16(tf::Tensor<::tensorflow::BFloat16>),\n    F32(tf::Tensor<f32>),\n    F64(tf::Tensor<f64>),\n    U8(tf::Tensor<u8>),\n    U16(tf::Tensor<u16>),\n    I8(tf::Tensor<i8>),\n    I16(tf::Tensor<i16>),\n    I32(tf::Tensor<i32>),\n    I64(tf::Tensor<i64>),\n    String(tf::Tensor<i8>),\n}\n\nimpl TensorHolder {\n    fn to_tensor<T: ::tensorflow::TensorType + Copy>(m: ArrayD<T>) -> tf::Tensor<T> {\n        let dims: Vec<u64> = m.shape().iter().map(|d| *d as _).collect();\n        let mut tensor = tf::Tensor::<T>::new(&*dims);\n        tensor.copy_from_slice(m.as_slice().unwrap());\n        tensor\n    }\n}\n\nimpl From<Tensor> for TensorHolder {\n    fn from(m: Tensor) -> TensorHolder {\n        match m.datum_type() {\n            DatumType::Bool => TensorHolder::Bool(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::F16 => unimplemented!(),\n            DatumType::F32 => TensorHolder::F32(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::F64 => TensorHolder::F64(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::I8 => TensorHolder::I8(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::I16 => TensorHolder::I16(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::I32 => TensorHolder::I32(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::I64 => TensorHolder::I64(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::U8 => TensorHolder::U8(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::U16 => TensorHolder::U16(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::U32 => TensorHolder::U16(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::U64 => TensorHolder::U16(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::QU8(_) => TensorHolder::U8(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::QI8(_) => TensorHolder::I8(Self::to_tensor(m.into_plain_array().unwrap())),\n            DatumType::QI32(_) => TensorHolder::I32(Self::to_tensor(m.into_plain_array().unwrap())),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI16 => unimplemented!(),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI32 => unimplemented!(),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexI64 => unimplemented!(),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF16 => unimplemented!(),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF32 => unimplemented!(),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF64 => unimplemented!(),\n            DatumType::TDim => {\n                let dims = m.to_plain_array_view::<TDim>().unwrap();\n                if let Ok(dims) = dims.iter().map(|d| d.to_i32()).collect::<TractResult<Vec<_>>>() {\n                    TensorHolder::I32(Self::to_tensor(arr1(&dims).into_dyn()))\n                } else {\n                    panic!(\"Streaming used in tensorflow settings\")\n                }\n            }\n            DatumType::String => {\n                TensorHolder::String(Self::to_tensor(m.into_plain_array().unwrap()))\n            }\n            DatumType::Blob => TensorHolder::String(Self::to_tensor(m.into_plain_array().unwrap())),\n            _ => panic!(\"No support for {:?} DT in tensorflow\", m.datum_type()),\n        }\n    }\n}\n\nfn tensor_to_array<T: ::tensorflow::TensorType>(tensor: &tf::Tensor<T>) -> TractResult<ArrayD<T>> {\n    let shape: Vec<usize> = tensor.dims().iter().map(|d| *d as _).collect();\n    Ok(Array::from(tensor.into_iter().cloned().collect::<Vec<_>>()).into_shape_with_order(shape)?)\n}\n\nimpl Tensorflow {\n    /// Executes the graph in one batch.\n    pub fn run(\n        &mut self,\n        inputs: Vec<(&str, Tensor)>,\n        output_name: &str,\n    ) -> TractResult<Vec<Tensor>> {\n        let tensors: Vec<(&str, TensorHolder)> =\n            inputs.into_iter().map(|(name, mat)| (name, mat.into())).collect();\n\n        let mut step = SessionRunArgs::new();\n        for t in &tensors {\n            let op = self.graph.operation_by_name_required(t.0)?;\n            match t.1 {\n                TensorHolder::Bool(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::U8(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::U16(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::I8(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::I16(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::I32(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::I64(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::F16(_) => unimplemented!(),\n                TensorHolder::F32(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::F64(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::String(ref it) => step.add_feed(&op, 0, &it),\n            }\n        }\n\n        let op = &self.graph.operation_by_name_required(output_name)?;\n        let tokens =\n            (0..op.num_outputs()).map(|ix| step.request_fetch(&op, ix as i32)).collect::<Vec<_>>();\n\n        let mut session = Session::new(&::tensorflow::SessionOptions::new(), &self.graph)?;\n        session.run(&mut step)?;\n\n        tokens\n            .into_iter()\n            .enumerate()\n            .map(|(ix, tok)| {\n                let output_type =\n                    &self.graph.operation_by_name_required(&output_name)?.output_type(ix);\n                convert_output(&mut step, output_type, tok)\n            })\n            .collect()\n    }\n\n    /// Executes the graph in one batch, and returns the output for every node but the inputs.\n    pub fn run_get_many<'a>(\n        &mut self,\n        inputs: Vec<(&'a str, Tensor)>,\n        targets: Vec<&'a str>,\n    ) -> TractResult<HashMap<&'a str, Vec<Tensor>>> {\n        let mut input_pairs: Vec<(&str, TensorHolder)> = Vec::new();\n        let mut excluded = HashSet::new();\n\n        for (name, mat) in inputs {\n            input_pairs.push((name, mat.into()));\n            excluded.insert(name.to_string());\n        }\n\n        let mut step = SessionRunArgs::new();\n        for t in &input_pairs {\n            let op = self.graph.operation_by_name_required(t.0)?;\n            match t.1 {\n                TensorHolder::Bool(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::U8(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::U16(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::I8(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::I16(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::I32(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::I64(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::F16(_) => unimplemented!(),\n                TensorHolder::F32(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::F64(ref it) => step.add_feed(&op, 0, &it),\n                TensorHolder::String(ref it) => step.add_feed(&op, 0, &it),\n            }\n        }\n\n        let mut tokens = HashMap::new();\n        trace!(\"Targets: {:?}\", targets);\n        for name in targets {\n            if excluded.contains(name) {\n                continue;\n            }\n\n            if let Some(operation) = self.graph.operation_by_name(name)? {\n                // switch only computes one of its outputs. tf explodes during\n                // the call to run() if we registers them\n                if operation.op_type()? == \"Switch\" {\n                    continue;\n                }\n\n                // this one pretends to have 5 outputs, but has only one\n                if operation.op_type()? == \"FusedBatchNorm\" {\n                    continue;\n                }\n\n                let outputs = (0..operation.num_outputs())\n                    .map(|ix| step.request_fetch(&operation, ix as i32))\n                    .collect::<Vec<_>>();\n\n                tokens.insert(name, outputs);\n            }\n        }\n        trace!(\"Generated all output tokens\");\n        trace!(\"{:?}\", tokens);\n\n        // Execute the graph using tensorflow.\n        let mut session = Session::new(&::tensorflow::SessionOptions::new(), &self.graph)?;\n        session.run(&mut step)?;\n        trace!(\"Tensorflow ran succesfully\");\n\n        // Return the output for every node.\n        let mut outputs = HashMap::new();\n        for (name, tokens) in tokens {\n            let tensors = tokens\n                .iter()\n                .enumerate()\n                .map(|(ix, tok)| {\n                    let output_type =\n                        &self.graph.operation_by_name_required(&name)?.output_type(ix);\n                    convert_output(&mut step, output_type, *tok)\n                })\n                .collect::<TractResult<Vec<_>>>()?;\n            outputs.insert(name, tensors);\n        }\n\n        Ok(outputs)\n    }\n}\n\n/// Converts the output of a Tensorflow node into a Tensor.\nfn convert_output(\n    step: &mut SessionRunArgs,\n    output_type: &DataType,\n    output: FetchToken,\n) -> TractResult<Tensor> {\n    macro_rules! convert {\n        ($dt:ident) => {\n            match step.fetch(output) {\n                Err(r) => Err(r)?,\n                Ok(output) => tensor_to_array::<$dt>(&output)?.into(),\n            }\n        };\n    };\n\n    let tract_tensor = match output_type {\n        DataType::Bool => convert!(bool),\n        DataType::Float => convert!(f32),\n        DataType::Double => convert!(f64),\n        DataType::UInt8 => convert!(u8),\n        DataType::Int8 => convert!(i8),\n        DataType::Int32 => convert!(i32),\n        DataType::Int64 => convert!(i64),\n        t => bail!(\"Missing conversion for tensorflow to tract (type: {:?})\", t),\n    };\n\n    Ok(tract_tensor)\n}\n"
  },
  {
    "path": "tensorflow/src/lib.rs",
    "content": "#![allow(clippy::len_zero)]\n//! # Tract TensorFlow module\n//!\n//! Tiny, no-nonsense, self contained, portable inference.\n//!\n//! ## Example\n//!\n//! ```\n//! # extern crate tract_tensorflow;\n//! # fn main() {\n//! use tract_tensorflow::prelude::*;\n//!\n//! // build a simple model that just add 3 to each input component\n//! let tf = tensorflow();\n//! let mut model = tf.model_for_path(\"tests/models/plus3.pb\").unwrap();\n//!\n//! // set input input type and shape, then optimize the network.\n//! model.set_input_fact(0, f32::fact(&[3]).into()).unwrap();\n//! let model = model.into_optimized().unwrap();\n//!\n//! // we build an execution plan. default input and output are inferred from\n//! // the model graph\n//! let plan = SimplePlan::new(&model).unwrap();\n//!\n//! // run the computation.\n//! let input = tensor1(&[1.0f32, 2.5, 5.0]);\n//! let mut outputs = plan.run(tvec![input]).unwrap();\n//!\n//! // take the first and only output tensor\n//! let mut tensor = outputs.pop().unwrap();\n//!\n//! assert_eq!(tensor, rctensor1(&[4.0f32, 5.5, 8.0]));\n//! # }\n//! ```\n//!\n\n#[macro_use]\nextern crate derive_new;\n#[allow(unused_imports)]\n#[macro_use]\nextern crate log;\n#[cfg(test)]\nextern crate env_logger;\nextern crate prost;\nextern crate prost_types;\n#[cfg(feature = \"conform\")]\nextern crate tensorflow;\npub extern crate tract_hir;\n\n#[cfg(feature = \"conform\")]\npub mod conform;\n\npub mod model;\npub mod ops;\npub mod tensor;\npub mod tfpb;\n\npub use model::Tensorflow;\n\npub fn tensorflow() -> Tensorflow {\n    let mut ops = crate::model::TfOpRegister::default();\n    ops::register_all_ops(&mut ops);\n    Tensorflow { op_register: ops }\n}\n\npub use tract_hir::tract_core;\npub mod prelude {\n    pub use crate::tensorflow;\n    pub use tract_hir::prelude::*;\n    pub use tract_hir::tract_core;\n}\n\n#[cfg(test)]\n#[allow(dead_code)]\npub fn setup_test_logger() {\n    env_logger::Builder::from_default_env().filter_level(log::LevelFilter::Trace).init();\n}\n"
  },
  {
    "path": "tensorflow/src/model.rs",
    "content": "use crate::tfpb::tensorflow::{GraphDef, NodeDef, SavedModel};\nuse prost::Message;\nuse std::{fs, path};\nuse tract_hir::internal::*;\n\n#[derive(Default)]\npub struct ParsingContext {\n    pub node_output_arities: HashMap<String, usize>,\n}\n\ntype OpBuilder = fn(&ParsingContext, node: &NodeDef) -> TractResult<Box<dyn InferenceOp>>;\n\n#[derive(Clone, Default)]\npub struct TfOpRegister(pub HashMap<String, OpBuilder>);\n\nimpl TfOpRegister {\n    pub fn insert(&mut self, s: &'static str, builder: OpBuilder) {\n        self.0.insert(s.into(), builder);\n    }\n}\n\npub struct Tensorflow {\n    pub op_register: TfOpRegister,\n}\n\npub struct TfModelExtensions {\n    pub control_inputs: Vec<(usize, usize)>,\n}\n\nimpl TfModelExtensions {\n    pub fn preproc(&self, original: InferenceModel) -> TractResult<InferenceModel> {\n        Ok(original)\n    }\n}\n\npub struct TfModelAndExtensions(pub InferenceModel, pub TfModelExtensions);\n\nimpl Tensorflow {\n    // From the node_def.proto documentation:\n    // Each input is \"node:src_output\" with \"node\" being a string name and\n    // \"src_output\" indicating which output tensor to use from \"node\". If\n    // \"src_output\" is 0 the \":0\" suffix can be omitted. Regular inputs may\n    // optionally be followed by control inputs that have the format \"^node\".\n    fn parse_input(i: &str) -> TractResult<(&str, usize)> {\n        let pair = if let Some(stripped) = i.strip_prefix('^') {\n            (stripped, 0)\n        } else {\n            let splits: Vec<_> = i.splitn(2, ':').collect();\n            (splits[0], if splits.len() > 1 { splits[1].parse::<usize>()? } else { 0 })\n        };\n        Ok(pair)\n    }\n\n    pub fn determinize(model: &mut GraphDef) -> TractResult<()> {\n        for pbnode in &mut model.node {\n            if pbnode.op == \"RandomUniform\"\n                && pbnode.get_attr_int::<i64>(\"seed\")? == 0\n                && pbnode.get_attr_int::<i64>(\"seed2\")? == 0\n            {\n                pbnode.attr.insert(\"seed\".to_string(), 1.into());\n                pbnode.attr.insert(\"seed2\".to_string(), 1.into());\n            }\n        }\n        Ok(())\n    }\n\n    #[cfg(target_family = \"wasm\")]\n    pub fn read_frozen_from_path(&self, p: impl AsRef<path::Path>) -> TractResult<GraphDef> {\n        use std::io::Read;\n        let mut file = fs::File::open(p)?;\n        let mut v = Vec::with_capacity(file.metadata()?.len() as usize);\n        file.read_to_end(&mut v)?;\n        let b = bytes::Bytes::from(v);\n        Ok(GraphDef::decode(b)?)\n    }\n\n    #[cfg(all(any(windows, unix), not(target_os = \"emscripten\")))]\n    pub fn read_frozen_from_path(&self, p: impl AsRef<path::Path>) -> TractResult<GraphDef> {\n        let map = unsafe { memmap2::Mmap::map(&fs::File::open(p)?)? };\n        Ok(GraphDef::decode(&*map)?)\n    }\n\n    pub fn read_frozen_model(&self, r: &mut dyn std::io::Read) -> TractResult<GraphDef> {\n        let mut v = vec![];\n        r.read_to_end(&mut v)?;\n        let b = bytes::Bytes::from(v);\n        Ok(GraphDef::decode(b)?)\n    }\n\n    pub fn open_saved_model(&self, r: &mut dyn std::io::Read) -> TractResult<SavedModel> {\n        let mut v = vec![];\n        r.read_to_end(&mut v)?;\n        let b = bytes::Bytes::from(v);\n        Ok(SavedModel::decode(b)?)\n    }\n\n    /// Convenience method: will read the first model in the saved model\n    /// container. Use open_avec_model for more control.\n    pub fn read_saved_model(&self, r: &mut dyn std::io::Read) -> TractResult<GraphDef> {\n        let mut saved = self.open_saved_model(r)?;\n        Ok(saved.meta_graphs.remove(0).graph_def.unwrap())\n    }\n\n    pub fn parse_graph(&self, graph: &GraphDef) -> TractResult<TfModelAndExtensions> {\n        self.parse_graph_with_template(graph, Default::default())\n    }\n\n    pub fn parse_graph_with_template(\n        &self,\n        graph: &GraphDef,\n        mut model: InferenceModel,\n    ) -> TractResult<TfModelAndExtensions> {\n        use crate::ops::control_flow as cf;\n\n        let mut inputs = tvec!();\n        let mut context = ParsingContext::default();\n        let mut control_inputs = vec![];\n\n        // compute min output arity for all nodes\n        for pbnode in &graph.node {\n            for i in &pbnode.input {\n                let (node, slot) = Self::parse_input(i)?;\n                let arity = context.node_output_arities.entry(node.to_string()).or_insert(1);\n                *arity = (*arity).max(slot + 1);\n            }\n        }\n\n        for pbnode in &graph.node {\n            let name = &pbnode.name;\n\n            if pbnode.op == \"NextIteration\" {\n                let source_op = cf::NextIteration::new(name.clone(), cf::NextIterationRole::Source);\n                let sink_op = cf::NextIteration::new(name.clone(), cf::NextIterationRole::Sink);\n                let _source =\n                    model.add_node(name.clone(), source_op, tvec!(InferenceFact::default()))?;\n                let _sink = model.add_node(format!(\"{name}-Sink\"), sink_op, tvec!())?;\n                continue;\n            }\n\n            let op = match self.op_register.0.get(&pbnode.op) {\n                Some(builder) => (builder)(&context, pbnode)?,\n                None => tract_hir::ops::unimpl::UnimplementedOp::new(\n                    context.node_output_arities.get(name).cloned().unwrap_or(1),\n                    &pbnode.op,\n                    format!(\"{pbnode:?}\"),\n                )\n                .into(),\n            };\n\n            let noutputs =\n                op.nboutputs()?.max(context.node_output_arities.get(name).cloned().unwrap_or(1));\n            let facts = tvec!(InferenceFact::default(); noutputs);\n\n            let node_id = model.add_node(name.clone(), op, facts)?;\n            if pbnode.op == \"Placeholder\" {\n                let dt = pbnode.get_attr_datum_type(\"dtype\")?;\n                let mut fact = InferenceFact::dt(dt);\n                if let Some(shape) = pbnode.get_attr_opt_shape(\"shape\")? {\n                    let shape_factoid = ShapeFactoid::closed(\n                        shape\n                            .iter()\n                            .map(|d| {\n                                if *d == -1 {\n                                    GenericFactoid::Any\n                                } else {\n                                    GenericFactoid::Only(d.to_dim())\n                                }\n                            })\n                            .collect(),\n                    );\n                    fact = fact.with_shape(shape_factoid);\n                }\n                inputs.push(OutletId::new(node_id, 0));\n                model.set_outlet_fact(OutletId::new(node_id, 0), fact)?;\n            }\n        }\n\n        for pbnode in &graph.node {\n            let node_id = if pbnode.op == \"NextIteration\" {\n                model.node_by_name(&*format!(\"{}-Sink\", &pbnode.name))?.id\n            } else {\n                model.node_by_name(&pbnode.name)?.id\n            };\n            for (ix, i) in pbnode.input.iter().filter(|n| !n.starts_with('^')).enumerate() {\n                let input = Self::parse_input(i)?;\n                let prec = model.node_by_name(input.0)?.id;\n                let outlet = OutletId::new(prec, input.1);\n                let inlet = InletId::new(node_id, ix);\n                model.add_edge(outlet, inlet)?;\n                model.set_outlet_label(outlet, i.to_string())?;\n            }\n            for i in pbnode.input.iter().filter(|n| n.starts_with('^')) {\n                let input = Self::parse_input(i)?;\n                let prec = model.node_by_name(input.0)?.id;\n                control_inputs.push((model.node_id_by_name(&pbnode.name)?, prec));\n            }\n        }\n\n        // variable -> assign rewire\n        //  * Assign consumes this by_ref tensor on #0 and somehow performs\n        //      updates on it (it has a second input on #1 for the value to\n        //      assign)\n        //\n        // in tract:\n        //  * VariableV2 outputs a regular tensor stored in the session state\n        //  * Assign has the same inputs, but do not uses the #0, udating the\n        //      state session instead\n        //\n        // 2026-01: remove vars support in tract core\n        //\n        // for id in 0..model.nodes().len() {\n        //     use crate::ops::vars::*;\n        //     if model.node(id).op_is::<Assign>() {\n        //         let prec = model.node(id).inputs[0];\n        //         let var_id = model.node(prec.node).op_as::<VariableV2>().map(|v| v.id.clone());\n        //         if let (Some(var_id), Some(assign)) =\n        //             (var_id, model.node_mut(id).op_as_mut::<Assign>())\n        //         {\n        //             assign.var_id = Some(var_id);\n        //         } else {\n        //             bail!(\"Model contains unlinked Assign/Variable2\");\n        //         }\n        //     }\n        // }\n        model.set_input_outlets(&inputs)?;\n        model.auto_outputs()?;\n        let extensions = TfModelExtensions { control_inputs };\n        Ok(TfModelAndExtensions(model, extensions))\n    }\n}\n\nimpl Framework<GraphDef, InferenceModel> for Tensorflow {\n    /// This method will try to read as frozen model, then as a saved model.\n    fn proto_model_for_path(&self, r: impl AsRef<path::Path>) -> TractResult<GraphDef> {\n        self.read_frozen_model(&mut fs::File::open(r.as_ref())?)\n            .or_else(|_| self.read_saved_model(&mut fs::File::open(r.as_ref())?))\n    }\n\n    /// This method expects a frozen model, use open_saved_model for TF2 saved\n    /// model format.\n    fn proto_model_for_read(&self, r: &mut dyn std::io::Read) -> TractResult<GraphDef> {\n        self.read_frozen_model(r)\n    }\n\n    fn model_for_proto_model_with_model_template(\n        &self,\n        proto: &GraphDef,\n        template: InferenceModel,\n    ) -> TractResult<InferenceModel> {\n        Ok(self.parse_graph_with_template(proto, template)?.0)\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/concatv2.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\nuse tract_hir::internal::*;\nuse tract_hir::ops::array::TypedConcat;\n\npub fn build(_ctx: &ParsingContext, _pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    Ok(expand(ConcatV2))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct ConcatV2;\n\nimpl Expansion for ConcatV2 {\n    fn name(&self) -> StaticName {\n        \"ConcatV2\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(outputs, 1)?;\n        let n = inputs.len() - 1;\n        s.equals_all((0..n).map(|i| (&inputs[i].datum_type).bex()).collect())?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.equals(&inputs[n].datum_type, DatumType::I32)?;\n        s.equals_all((0..n).map(|i| (&inputs[i].rank).bex()).collect())?;\n        s.equals(&inputs[n].rank, 0)?;\n        s.equals(&outputs[0].rank, &inputs[0].rank)?;\n        s.given(&inputs[n].value, move |s, axis| {\n            let axis = *axis.try_as_plain()?.to_scalar::<i32>()? as usize;\n            trace!(\"axis for ConcatV2: {axis}\");\n            for d in 0..axis {\n                s.equals_all((0..n).map(|i| (&inputs[i].shape[d]).bex()).collect())?;\n            }\n            for d in 0..axis {\n                s.equals(&inputs[0].shape[d], &outputs[0].shape[d])?;\n            }\n            s.given(&inputs[0].rank, move |s, rank| {\n                trace!(\"Given rank {rank}\");\n                for d in (axis + 1)..(rank as usize) {\n                    s.equals(&inputs[0].shape[d], &outputs[0].shape[d])?;\n                }\n                for d in (axis + 1)..(rank as usize) {\n                    s.equals_all((0..n).map(|i| (&inputs[i].shape[d]).bex()).collect())?;\n                }\n                Ok(())\n            })?;\n\n            let mut concat_dim = -1 * outputs[0].shape[axis].bex();\n            for input in inputs.iter().take(n) {\n                concat_dim = concat_dim + input.shape[axis].bex();\n            }\n            s.equals_zero(concat_dim)\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(ref axis) = model.outlet_fact(*inputs.last().unwrap())?.konst {\n            let axis = *axis.try_as_plain()?.to_scalar::<i32>()? as usize;\n            let inputs = inputs.iter().copied().rev().skip(1).rev().collect::<TVec<_>>();\n            model.wire_node(prefix, TypedConcat::new(axis), &inputs)\n        } else {\n            bail!(\"Except axis to be a constant\")\n        }\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/expand_dims.rs",
    "content": "use tract_hir::internal::*;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\npub fn build(_ctx: &ParsingContext, _pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    Ok(expand(ExpandDims))\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct ExpandDims;\n\nimpl Expansion for ExpandDims {\n    fn name(&self) -> StaticName {\n        \"ExpandDims\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        let data = &inputs[0];\n        let dims = &inputs[1];\n        let output = &outputs[0];\n\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&dims.datum_type, DatumType::I32)?;\n        s.equals(&data.datum_type, &output.datum_type)?;\n        s.equals(data.rank.bex() + 1, &output.rank)?;\n        s.given_2(&dims.value, &data.rank, move |s, index, rank| {\n            let mut index = index.cast_to_scalar::<i64>()?;\n            if index < 0 {\n                index += rank + 1\n            }\n            let index = index as usize;\n\n            for i in 0..index {\n                s.equals(&output.shape[i], &data.shape[i])?;\n            }\n\n            s.equals(output.shape[index].bex(), 1i64.to_dim().bex())?;\n\n            s.given(&data.rank, move |s, rank| {\n                for i in index..(rank as usize) {\n                    s.equals(&output.shape[i + 1], &data.shape[i])?;\n                }\n                Ok(())\n            })\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(ref axes) = target.outlet_fact(inputs[1])?.konst {\n            let mut axes = axes\n                .cast_to::<i32>()?\n                .try_as_plain()?\n                .as_slice::<i32>()?\n                .iter()\n                .map(|&axis| {\n                    Ok(if axis < 0 {\n                        axis + target.outlet_fact(inputs[0])?.shape.rank() as i32\n                    } else {\n                        axis\n                    })\n                })\n                .collect::<TractResult<Vec<_>>>()?;\n            axes.sort();\n            let mut wire = inputs[0];\n            for axis in axes.iter().rev() {\n                wire = target.wire_node(\n                    format!(\"{prefix}.axis-{axis}\"),\n                    AxisOp::Add(*axis as _),\n                    &[wire],\n                )?[0];\n            }\n            Ok(tvec!(wire))\n        } else {\n            bail!(\"Need axes to be const\")\n        }\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/fill.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\nuse tract_hir::internal::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Fill {\n    dt: DatumType,\n}\n\npub fn fill(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let dtype = pb.get_attr_datum_type(\"T\")?;\n    Ok(Box::new(Fill::new(dtype)))\n}\n\nimpl Fill {\n    fn eval_t<T: Datum>(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (shape, value) = args_2!(inputs);\n        let value = value.try_as_plain()?.to_scalar::<T>()?;\n        let shape = shape.cast_to::<i32>()?;\n        let shape = shape.to_plain_array_view::<i32>()?;\n        let array = tract_ndarray::Array::from_shape_fn(\n            shape.iter().map(|i| *i as usize).collect::<Vec<usize>>(),\n            |_| value.clone(),\n        );\n        Ok(tvec![array.into_tvalue()])\n    }\n}\n\nimpl Op for Fill {\n    fn name(&self) -> StaticName {\n        \"Fill\".into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for Fill {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        dispatch_datum!(Self::eval_t(self.dt)(self, inputs))\n    }\n}\n\nimpl InferenceRulesOp for Fill {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, self.dt)?;\n        s.equals(&inputs[1].datum_type, self.dt)?;\n        s.equals(&inputs[0].rank, 1)?;\n        s.equals(&inputs[1].rank, 0)?;\n        s.equals(outputs[0].rank.bex().to_dim(), &inputs[0].shape[0])?;\n        s.given(&outputs[0].rank, move |s, rank| {\n            for dim in 0..(rank as usize) {\n                s.equals(&outputs[0].shape[dim], inputs[0].value[dim].bex().to_dim())?;\n            }\n            Ok(())\n        })\n    }\n\n    as_op!();\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        if let (Some(shape), Some(value)) = (\n            target.outlet_fact(mapping[&node.inputs[0]])?.konst.as_ref(),\n            target.outlet_fact(mapping[&node.inputs[1]])?.konst.as_ref(),\n        ) {\n            let mut value = dispatch_datum!(Self::eval_t(value.datum_type())(\n                self,\n                tvec!(shape.clone().into_tvalue(), value.clone().into_tvalue())\n            ))?;\n            let id = target.add_const(&*node.name, value.remove(0))?;\n            Ok(tvec!(id))\n        } else {\n            bail!(\"Can not type Fill op\")\n        }\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/gather_nd.rs",
    "content": "use tract_hir::internal::*;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\nuse tract_hir::ops::array::GatherNd;\n\npub fn gather_nd(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let batch_dims = pb.get_attr_opt_int(\"batch_dims\")?.unwrap_or(0);\n    Ok(Box::new(GatherNd::new(batch_dims)))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    // https://www.tensorflow.org/api_docs/python/tf/gather_nd\n    #[test]\n    fn simple_indexing() {\n        let g = GatherNd::new(0);\n        assert_eq!(\n            g.eval(tvec!(tensor2(&[[1, 2], [3, 4]]).into(), tensor2(&[[0, 0], [1, 1]]).into()))\n                .unwrap(),\n            tvec!(tensor1(&[1, 4]).into())\n        );\n    }\n\n    #[test]\n    fn slice_indexing() {\n        let g = GatherNd::new(0);\n        assert_eq!(\n            g.eval(tvec!(tensor2(&[[1, 2], [3, 4]]).into(), tensor2(&[[1], [0]]).into())).unwrap(),\n            tvec!(tensor2(&[[3, 4], [1, 2]]).into())\n        );\n    }\n\n    #[test]\n    fn tensor_3d_1() {\n        let g = GatherNd::new(0);\n        let t = tensor3(&[[[10, 20], [30, 40]], [[11, 21], [31, 41]]]);\n        assert_eq!(\n            g.eval(tvec!(t.into(), tensor2(&[[1]]).into())).unwrap(),\n            tvec!(tensor3(&[[[11, 21], [31, 41]]]).into())\n        );\n    }\n\n    #[test]\n    fn tensor_3d_2() {\n        let g = GatherNd::new(0);\n        let t = tensor3(&[[[10, 20], [30, 40]], [[11, 21], [31, 41]]]);\n        assert_eq!(\n            g.eval(tvec!(t.into(), tensor2(&[[0, 1], [1, 0]]).into())).unwrap(),\n            tvec!(tensor2(&[[30, 40], [11, 21]]).into())\n        );\n    }\n\n    #[test]\n    fn tensor_3d_3() {\n        let g = GatherNd::new(0);\n        let t = tensor3(&[[[10, 20], [30, 40]], [[11, 21], [31, 41]]]);\n        assert_eq!(\n            g.eval(tvec!(t.into(), tensor2(&[[0, 0, 1], [1, 0, 1]]).into())).unwrap(),\n            tvec!(tensor1(&[20, 21]).into())\n        );\n    }\n\n    #[test]\n    fn tensor_bd1_1() {\n        let g = GatherNd::new(1);\n        let t = tensor3(&[[[10, 20], [30, 40]], [[11, 21], [31, 41]]]);\n        assert_eq!(\n            g.eval(tvec!(t.into(), tensor2(&[[1], [0]]).into())).unwrap(),\n            tvec!(tensor2(&[[30, 40], [11, 21]]).into())\n        );\n    }\n\n    #[test]\n    fn tensor_bd1_2() {\n        let g = GatherNd::new(1);\n        let t = tensor3(&[[[10, 20], [30, 40]], [[11, 21], [31, 41]]]);\n        assert_eq!(\n            g.eval(tvec!(t.into(), tensor3(&[[[1]], [[0]]]).into())).unwrap(),\n            tvec!(tensor3(&[[[30, 40]], [[11, 21]]]).into())\n        );\n    }\n\n    #[test]\n    fn tensor_bd1_3() {\n        let g = GatherNd::new(1);\n        let t = tensor3(&[[[10, 20], [30, 40]], [[11, 21], [31, 41]]]);\n        assert_eq!(\n            g.eval(tvec!(t.into(), tensor3(&[[[1, 0]], [[0, 1]]]).into())).unwrap(),\n            tvec!(tensor2(&[[30], [21]]).into())\n        );\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/gather_v2.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\nuse tract_hir::internal::*;\n\npub fn gather_v2(_ctx: &ParsingContext, _pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    Ok(expand(GatherV2))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct GatherV2;\n\nimpl Expansion for GatherV2 {\n    fn name(&self) -> StaticName {\n        \"GatherV2\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[1].datum_type, i32::datum_type())?;\n        s.equals(&inputs[2].datum_type, i32::datum_type())?;\n        s.equals(&inputs[2].rank, 0)?;\n        s.given_3(\n            &inputs[0].shape,\n            &inputs[1].shape,\n            &inputs[2].value,\n            move |s, input_shape, indices_shape, axis| {\n                let axis = axis.cast_to_scalar::<i64>()?;\n                let op = tract_hir::ops::array::Gather::new(axis);\n                let output_shape = op\n                    .to_type_op(input_shape.len())\n                    .compute_output_shape(&input_shape, &indices_shape)?;\n                s.equals(&outputs[0].shape, output_shape)\n            },\n        )\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(axis) = target.outlet_fact(inputs[2])?.konst.as_ref() {\n            let axis = axis.cast_to_scalar::<i64>()?;\n            let input_fact = target.outlet_fact(inputs[0])?;\n            let op = tract_hir::ops::array::Gather::new(axis).to_type_op(input_fact.rank());\n            target.wire_node(prefix, op, &inputs[0..2])\n        } else {\n            bail!(\"Need to know axis to type GatherV2\")\n        }\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/mod.rs",
    "content": "use crate::model::TfOpRegister;\nuse tract_hir::internal::*;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\nuse tract_core::ops::array::StridedSlice;\n\nmod concatv2;\nmod expand_dims;\nmod fill;\nmod gather_nd;\nmod gather_v2;\nmod pack;\nmod pad;\nmod squeeze;\nmod transpose;\n\npub fn register_all_ops(reg: &mut TfOpRegister) {\n    reg.insert(\"ConcatV2\", concatv2::build);\n    reg.insert(\"ExpandDims\", expand_dims::build);\n    reg.insert(\"Fill\", fill::fill);\n    reg.insert(\"GatherNd\", gather_nd::gather_nd);\n    reg.insert(\"GatherV2\", gather_v2::gather_v2);\n    reg.insert(\"Pack\", pack::pack);\n    reg.insert(\"Pad\", pad::pad);\n    reg.insert(\"Range\", |_, _| Ok(expand(tract_hir::ops::array::Range)));\n    reg.insert(\"Reshape\", |_, _| Ok(expand(tract_hir::ops::array::Reshape::new())));\n    reg.insert(\"Shape\", |_, _| Ok(expand(tract_hir::ops::array::Shape::new(DatumType::TDim))));\n    reg.insert(\"Slice\", slice);\n    reg.insert(\"Squeeze\", squeeze::squeeze);\n    reg.insert(\"StridedSlice\", strided_slice);\n    reg.insert(\"Tile\", |_, _| Ok(expand(::tract_hir::ops::array::Tile)));\n    reg.insert(\"Transpose\", transpose::transpose);\n}\n\nfn strided_slice(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let begin_mask = pb.get_attr_opt_int(\"begin_mask\")?.unwrap_or(0);\n    let end_mask = pb.get_attr_opt_int(\"end_mask\")?.unwrap_or(0);\n    let shrink_axis_mask = pb.get_attr_opt_int(\"shrink_axis_mask\")?.unwrap_or(0);\n    Ok(Box::new(StridedSlice {\n        begin_mask,\n        end_mask,\n        shrink_axis_mask,\n        optional_axes_input: None,\n        optional_steps_input: Some(3),\n    }))\n}\n\nfn slice(_ctx: &ParsingContext, _pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    Ok(Box::new(StridedSlice {\n        optional_axes_input: None,\n        optional_steps_input: None,\n        begin_mask: 0,\n        end_mask: 0,\n        shrink_axis_mask: 0,\n    }))\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/pack.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops::array::TypedConcat;\nuse tract_hir::ops::binary::wire_cast;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\npub fn pack(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let n = pb.input.len();\n    let axis = pb.get_attr_int(\"axis\")?;\n\n    Ok(expand(Pack::new(n, axis)))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Pack {\n    n: usize, // The number of inputs\n    axis: usize,\n}\n\nimpl Expansion for Pack {\n    fn name(&self) -> StaticName {\n        \"Pack\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        let axis = self.axis;\n        check_input_arity(inputs, self.n)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].rank, inputs[0].rank.bex() + 1)?;\n        s.equals_all((0..self.n).map(|i| inputs[i].rank.bex()).collect())?;\n        s.given_all((0..self.n).map(move |i| &inputs[i].datum_type), move |s, dts| {\n            if let Some(dt) = DatumType::super_type_for(dts) {\n                s.equals(&outputs[0].datum_type, dt)?;\n            }\n            Ok(())\n        })?;\n        s.given(&inputs[0].rank, move |s, r| {\n            for d in 0..r as usize {\n                s.equals_all((0..self.n).map(|i| inputs[i].shape[d].bex()).collect())?;\n            }\n            Ok(())\n        })?;\n        s.given(&inputs[0].rank, move |s, r| {\n            for d in 0..axis {\n                s.equals(&outputs[0].shape[d], &inputs[0].shape[d])?;\n            }\n            if r > 0 {\n                for d in axis..r as usize {\n                    s.equals(&outputs[0].shape[d + 1], &inputs[0].shape[d])?\n                }\n            }\n            Ok(())\n        })?;\n        s.equals(&outputs[0].shape[axis], self.n.to_dim())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let dt = inputs\n            .iter()\n            .map(|&i| Ok(model.outlet_fact(i)?.datum_type))\n            .collect::<TractResult<TVec<DatumType>>>()?;\n        let dt = DatumType::super_type_for(dt.iter()).context(\"No supertype\")?;\n        let wires = wire_cast(prefix, model, inputs, dt)?;\n        let inputs: TVec<OutletId> = wires\n            .iter()\n            .enumerate()\n            .map(|(ix, &o)| {\n                Ok(model.wire_node(\n                    format!(\"{prefix}.add_dims-{ix}\"),\n                    AxisOp::Add(self.axis),\n                    &[o],\n                )?[0])\n            })\n            .collect::<TractResult<TVec<OutletId>>>()?;\n        model.wire_node(prefix, TypedConcat::new(self.axis), &inputs)\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/pad.rs",
    "content": "use tract_hir::internal::*;\nuse tract_ndarray::{Array, ArrayView2};\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\n#[derive(Debug, Clone, Default, new, Hash, PartialEq, Eq)]\npub struct Pad;\n\npub fn pad(_ctx: &ParsingContext, _pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    Ok(Box::<Pad>::default())\n}\n\nimpl Pad {\n    fn compute_t<T: Datum + Default + Copy>(\n        input: &Tensor,\n        paddings: ArrayView2<i32>,\n        stream_dim: Option<usize>,\n    ) -> TractResult<TValue> {\n        let shape: Vec<usize> = input\n            .shape()\n            .iter()\n            .enumerate()\n            .map(|(ix, &dim)| {\n                if Some(ix) != stream_dim {\n                    dim + (paddings[(ix, 0)] + paddings[(ix, 1)]) as usize\n                } else {\n                    dim\n                }\n            })\n            .collect();\n        let mut index_in_input = vec![0; input.rank()];\n        let input = input.to_plain_array_view::<T>()?;\n        let result = Array::from_shape_fn(shape, |index| {\n            for i in 0..input.ndim() {\n                if index[i] < paddings[(i, 0)] as usize\n                    || index[i] - paddings[(i, 0)] as usize >= input.shape()[i]\n                {\n                    return T::default();\n                } else {\n                    index_in_input[i] = index[i] - paddings[(i, 0)] as usize;\n                };\n            }\n            input[&*index_in_input]\n        });\n        Ok(result.into_tvalue())\n    }\n}\n\nimpl Op for Pad {\n    fn name(&self) -> StaticName {\n        \"Pad\".into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for Pad {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (input, paddings) = args_2!(inputs);\n        let paddings = paddings.to_plain_array_view::<i32>()?.into_dimensionality()?;\n        Ok(tvec![dispatch_copy!(Self::compute_t(input.datum_type())(&input, paddings, None))?])\n    }\n}\n\nimpl InferenceRulesOp for Pad {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        let input = &inputs[0];\n        let padding = &inputs[1];\n        let output = &outputs[0];\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&output.datum_type, &input.datum_type)?;\n        s.equals(&padding.datum_type, DatumType::TDim)?;\n        s.equals(&input.rank, &output.rank)?;\n        s.equals(&padding.rank, 2)?;\n        s.equals(&padding.shape[0], input.rank.bex().to_dim())?;\n        s.equals(&padding.shape[1], 2.to_dim())?;\n        s.given(&input.rank, move |s, rank| {\n            for d in 0..rank as usize {\n                s.equals(\n                    &output.shape[d],\n                    input.shape[d].bex()\n                        + padding.value[d][0].bex().to_dim()\n                        + padding.value[d][1].bex().to_dim(),\n                )?\n            }\n            Ok(())\n        })\n    }\n\n    as_op!();\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn pad_0() {\n        let inputs =\n            tvec![tensor2(&[[1, 2, 3], [4, 5, 6]]).into(), tensor2(&[[1, 1], [2, 2]]).into(),];\n\n        let expected: TVec<_> = tvec!(\n            tensor2(&[\n                [0, 0, 0, 0, 0, 0, 0],\n                [0, 0, 1, 2, 3, 0, 0],\n                [0, 0, 4, 5, 6, 0, 0],\n                [0, 0, 0, 0, 0, 0, 0],\n            ])\n            .into()\n        );\n\n        assert_eq!(Pad::new().eval(inputs).unwrap(), expected);\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/squeeze.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\nuse tract_hir::internal::*;\nuse tract_hir::ops::array::Squeeze;\n\npub fn squeeze(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let squeeze_dims = pb.get_attr_opt_list_int(\"squeeze_dims\")?;\n    if let Some(mut squeeze_dims) = squeeze_dims {\n        if squeeze_dims.len() > 0 {\n            squeeze_dims.sort();\n            return Ok(expand(Squeeze::new(Some(squeeze_dims))));\n        }\n    }\n    Ok(expand(Squeeze::default()))\n}\n\n#[cfg(test)]\nmod tests {\n    #![allow(non_snake_case)]\n    use super::*;\n    use tract_ndarray::Array;\n\n    fn run<I>(op: Squeeze, input: I) -> Tensor\n    where\n        I: Into<Tensor>,\n    {\n        expand(op).eval(tvec![input.into().into()]).unwrap().pop().unwrap().into_tensor()\n    }\n\n    #[test]\n    fn squeeze_1() {\n        assert_eq!(\n            run(Squeeze::new(None), Array::from_elem([1, 2, 1, 3, 1, 1], 0)).shape(),\n            &[2, 3]\n        );\n    }\n\n    #[test]\n    fn squeeze_2() {\n        assert_eq!(\n            run(Squeeze::new(Some(vec![2, 4])), Array::from_elem([1, 2, 1, 3, 1, 1], 0)).shape(),\n            &[1, 2, 3, 1]\n        );\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/array/transpose.rs",
    "content": "use tract_hir::internal::*;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Transpose {\n    t: DatumType,\n    t_perm: DatumType,\n}\n\npub fn transpose(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let t = pb.get_attr_datum_type(\"T\")?;\n    let t_perm = pb.get_attr_datum_type(\"Tperm\")?;\n    Ok(expand(Transpose::new(t, t_perm)))\n}\n\nimpl Transpose {\n    fn compute_shape<D: DimLike>(shape: &[D], perm: &[i32]) -> TVec<D> {\n        let mut new_shape = tvec![D::zero(); shape.len()];\n        for (ix, &d) in perm.iter().enumerate() {\n            new_shape[ix] = shape[d as usize].clone();\n        }\n        new_shape\n    }\n}\n\nimpl Expansion for Transpose {\n    fn name(&self) -> StaticName {\n        \"Transpose\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_output_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, self.t)?;\n        s.equals(&inputs[1].datum_type, self.t_perm)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        s.equals(&outputs[0].rank, &inputs[0].rank)?;\n        s.equals(&inputs[1].rank, 1)?;\n        s.equals(&inputs[1].shape[0], inputs[0].rank.bex().to_dim())?;\n        s.given_2(&inputs[0].shape, &inputs[1].value, move |s, shape, perm| {\n            let perm = perm.cast_to::<i32>()?;\n            let output_shape = Self::compute_shape(&shape, perm.try_as_plain()?.as_slice::<i32>()?);\n            s.equals(&outputs[0].shape, output_shape)\n        })\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(axes) = &target.outlet_fact(inputs[1])?.konst {\n            let axes: TVec<usize> = axes\n                .cast_to::<i64>()?\n                .try_as_plain()?\n                .as_slice::<i64>()?\n                .iter()\n                .map(|i| *i as usize)\n                .collect();\n            let mut wire = tvec!(inputs[0]);\n            for pair in tract_hir::tract_core::ops::change_axes::perm_to_ops(&axes) {\n                wire = target.wire_node(format!(\"{prefix}.{pair:?}\"), pair, &wire)?;\n            }\n            Ok(wire)\n        } else {\n            bail!(\"Expect permutation input to be const\")\n        }\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/control_flow.rs",
    "content": "use tract_hir::internal::*;\n\nuse crate::model::TfOpRegister;\n\npub fn register_all_ops(reg: &mut TfOpRegister) {\n    reg.insert(\"Enter\", |_, node| {\n        Ok(Box::new(LoopGate(LoopGateRole::Enter(node.get_attr_str(\"frame_name\")?))))\n    });\n    reg.insert(\"Exit\", |_, _| Ok(Box::new(LoopGate(LoopGateRole::Exit))));\n    reg.insert(\"LoopCond\", |_, _| Ok(Box::new(LoopGate(LoopGateRole::LoopCond))));\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub enum LoopGateRole {\n    Enter(String),\n    Exit,\n    LoopCond,\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub struct LoopGate(LoopGateRole);\n\nimpl Op for LoopGate {\n    fn name(&self) -> StaticName {\n        format!(\"{:?}\", self.0).into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for LoopGate {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(inputs)\n    }\n}\n\nimpl InferenceRulesOp for LoopGate {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n\n    as_op!();\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]\npub enum NextIterationRole {\n    Source,\n    Sink,\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct NextIteration {\n    name: String,\n    role: NextIterationRole,\n}\n\nimpl Op for NextIteration {\n    fn name(&self) -> StaticName {\n        format!(\"{:?}({})\", self.role, self.name).into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for NextIteration {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(&self, _state: &TurnState, _id: usize) -> TractResult<Option<Box<dyn OpState>>> {\n        unimplemented!();\n    }\n}\n\nimpl InferenceRulesOp for NextIteration {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        _s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        match self.role {\n            NextIterationRole::Source => {\n                check_input_arity(inputs, 0)?;\n                check_output_arity(outputs, 1)?;\n            }\n            NextIterationRole::Sink => {\n                check_input_arity(inputs, 1)?;\n                check_output_arity(outputs, 0)?;\n            }\n        }\n        Ok(())\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "tensorflow/src/ops/logic.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops;\nuse tract_hir::ops::binary::BinIntoHir;\nuse tract_hir::ops::logic::{CompEq, CompGT, CompGTE, CompLT, CompLTE};\n\nuse crate::model::ParsingContext;\nuse crate::model::TfOpRegister;\nuse crate::tfpb::tensorflow::NodeDef;\nuse std::collections::HashSet;\n\npub fn register_all_ops(reg: &mut TfOpRegister) {\n    reg.insert(\"Equal\", |_, _| Ok(CompEq.into_hir()));\n    reg.insert(\"Greater\", |_, _| Ok(CompGT.into_hir()));\n    reg.insert(\"GreaterEqual\", |_, _| Ok(CompGTE.into_hir()));\n    reg.insert(\"Less\", |_, _| Ok(CompLT.into_hir()));\n    reg.insert(\"LessEqual\", |_, _| Ok(CompLTE.into_hir()));\n    reg.insert(\"LogicalAnd\", |_, _| Ok(ops::logic::And.into_hir()));\n    reg.insert(\"LogicalOr\", |_, _| Ok(ops::logic::Or.into_hir()));\n    reg.insert(\"Merge\", merge);\n    reg.insert(\"Switch\", |_, _| Ok(Box::new(Switch)));\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Switch;\n\nimpl Op for Switch {\n    fn name(&self) -> StaticName {\n        \"Switch\".into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for Switch {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(None)\n    }\n}\n\nimpl InferenceRulesOp for Switch {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 2)?;\n        s.equals(&inputs[1].datum_type, DatumType::Bool)?;\n        s.equals(&inputs[1].shape, shapefactoid!())?;\n        for output in outputs {\n            s.equals(&inputs[0].datum_type, &output.datum_type)?;\n            s.equals(&inputs[0].shape, &output.shape)?;\n        }\n        Ok(())\n    }\n\n    fn incorporate(\n        &self,\n        model: &InferenceModel,\n        node: &InferenceNode,\n    ) -> TractResult<Option<InferenceModelPatch>> {\n        let pred = model.outlet_fact(node.inputs[1])?;\n        if let Some(pred) = pred.concretize() {\n            let pred = *pred.try_as_plain()?.to_scalar::<bool>()?;\n            let mut dead_to_visit = HashSet::new();\n            let mut dead_done = HashSet::new();\n            let mut patch = InferenceModelPatch::default();\n            dead_to_visit.insert(OutletId::new(node.id, !pred as usize));\n            while let Some(dead_outlet) = dead_to_visit.iter().cloned().next() {\n                dead_to_visit.remove(&dead_outlet);\n                dead_done.insert(dead_outlet);\n                for succ in model.outlet_successors(dead_outlet) {\n                    if model.node(succ.node).op_is::<Merge>() {\n                        let outlet = model.node(succ.node).inputs[(succ.slot == 0) as usize];\n                        let tap = patch.tap_model(model, outlet)?;\n                        patch.shunt_outside(model, succ.node.into(), tap)?;\n                    } else {\n                        for slot in 0..model.node(succ.node).outputs.len() {\n                            let new = OutletId::new(succ.node, slot);\n                            if !dead_done.contains(&new) {\n                                dead_to_visit.insert(new);\n                            }\n                        }\n                    }\n                }\n            }\n            let tap = patch.tap_model(model, node.inputs[0])?;\n            patch.shunt_outside(model, OutletId::new(node.id, 0), tap)?;\n            patch.shunt_outside(model, OutletId::new(node.id, 1), tap)?;\n            return Ok(Some(patch));\n        }\n        Ok(None)\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(2)\n    }\n\n    as_op!();\n}\n\nfn merge(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let inputs = pb.get_attr_int::<i32>(\"N\")?;\n    Ok(Box::new(Merge::new(inputs as usize)))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Merge {\n    n: usize,\n}\n\nimpl Op for Merge {\n    fn name(&self) -> StaticName {\n        \"Merge\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Merge {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(None)\n    }\n}\n\nimpl InferenceRulesOp for Merge {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, self.n)?;\n        check_output_arity(outputs, 1)?;\n        for i in 1..self.n {\n            s.equals(&inputs[0].datum_type, &inputs[i].datum_type)?;\n            s.equals(&inputs[0].shape, &inputs[i].shape)?;\n        }\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n\nimpl TypedOp for Merge {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(f32::fact(inputs[0].shape.iter()), i32::fact([0; 0])))\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/math/reduce.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops::nn;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct Reduce {\n    t: DatumType,\n    t_idx: DatumType,\n    keep_dims: bool,\n    reducer: nn::Reducer,\n}\n\npub fn max(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    reduce(pb, nn::Reducer::Max)\n}\n\npub fn mean(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    reduce(pb, nn::Reducer::Mean)\n}\n\npub fn min(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    reduce(pb, nn::Reducer::Min)\n}\n\npub fn prod(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    reduce(pb, nn::Reducer::Prod)\n}\n\npub fn sum(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    reduce(pb, nn::Reducer::Sum)\n}\n\npub fn reduce(pb: &NodeDef, op: nn::Reducer) -> TractResult<Box<dyn InferenceOp>> {\n    let t = pb.get_attr_datum_type(\"T\")?;\n    let t_idx = pb.get_attr_datum_type(\"Tidx\")?;\n    let keep_dims = pb.get_attr_bool(\"keep_dims\")?;\n    Ok(Box::new(Reduce::new(t, t_idx, keep_dims, op)))\n}\n\nimpl Op for Reduce {\n    fn name(&self) -> StaticName {\n        format!(\"{:?}\", self.reducer).into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for Reduce {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (input, axes) = args_2!(inputs);\n        let axes: Vec<i64> = axes.cast_to::<i64>()?.try_as_plain()?.as_slice::<i64>()?.to_vec();\n        let op = nn::Reduce::new(Some(axes), self.keep_dims, self.reducer);\n        expand(op).eval(tvec!(input))\n    }\n}\n\nimpl InferenceRulesOp for Reduce {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, &inputs[0].datum_type)?;\n        if self.keep_dims {\n            s.equals(&inputs[0].rank, &outputs[0].rank)?;\n        } else {\n            s.given(&inputs[1].rank, move |s, rank| {\n                if rank == 1 {\n                    s.equals(\n                        inputs[0].rank.bex().to_dim(),\n                        inputs[1].shape[0].bex() + outputs[0].rank.bex().to_dim(),\n                    )\n                } else {\n                    s.equals(\n                        inputs[0].rank.bex().to_dim(),\n                        outputs[0].rank.bex().to_dim() + 1.to_dim(),\n                    )\n                }\n            })?;\n        }\n        s.given_3(\n            &inputs[0].rank,\n            &outputs[0].rank,\n            &inputs[1].value,\n            move |s, irank, orank, axes| {\n                let axes: TVec<usize> = axes\n                    .cast_to::<i64>()?\n                    .try_as_plain()?\n                    .as_slice::<i64>()?\n                    .iter()\n                    .map(|&ax| if ax > 0 { ax } else { ax + irank } as usize)\n                    .collect();\n                let mut od = 0;\n                for id in 0..(irank as usize) {\n                    if axes.contains(&id) {\n                        if self.keep_dims {\n                            s.equals(&outputs[0].shape[od], 1.to_dim())?;\n                            od += 1;\n                        }\n                    } else if od < orank as usize {\n                        s.equals(&outputs[0].shape[od], &inputs[0].shape[id])?;\n                        od += 1;\n                    }\n                }\n                Ok(())\n            },\n        )?;\n        Ok(())\n    }\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(ref axes) = target.outlet_fact(mapping[&node.inputs[1]])?.konst {\n            let axes: Vec<i64> = axes.cast_to::<i64>()?.try_as_plain()?.as_slice::<i64>()?.to_vec();\n            let op = nn::Reduce::new(Some(axes), self.keep_dims, self.reducer);\n            op.wire(&node.name, target, &[mapping[&node.inputs[0]]])\n        } else {\n            bail!(\"Nees axes to be const\")\n        }\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "tensorflow/src/ops/math.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops;\n\nuse crate::model::ParsingContext;\nuse crate::model::TfOpRegister;\nuse crate::tfpb::tensorflow::NodeDef;\n\nmod reduce;\n\npub fn register_all_ops(reg: &mut TfOpRegister) {\n    reg.insert(\"Abs\", |_, _| Ok(ops::math::abs().into_hir()));\n    reg.insert(\"Add\", |_, _| Ok(ops::math::Add.into_hir()));\n    reg.insert(\"AddN\", add_n);\n    reg.insert(\"AddV2\", |_, _| Ok(ops::math::Add.into_hir()));\n    reg.insert(\"BiasAdd\", |_, _| Ok(ops::math::Add.into_hir()));\n    reg.insert(\"Ceil\", |_, _| Ok(ops::math::ceil().into_hir()));\n    reg.insert(\"Div\", |_, _| Ok(ops::math::Div.into_hir()));\n    reg.insert(\"FloorMod\", |_, _| Ok(ops::math::Rem.into_hir()));\n    reg.insert(\"MatMul\", mat_mul);\n    reg.insert(\"Max\", reduce::max);\n    reg.insert(\"Mean\", reduce::mean);\n    reg.insert(\"Min\", reduce::min);\n    reg.insert(\"Prod\", reduce::prod);\n    reg.insert(\"Sum\", reduce::sum);\n    reg.insert(\"Maximum\", |_, _| Ok(ops::math::Max.into_hir()));\n    reg.insert(\"Minimum\", |_, _| Ok(ops::math::Min.into_hir()));\n    reg.insert(\"Log\", |_, _| Ok(ops::math::ln().into_hir()));\n    reg.insert(\"Mul\", |_, _| Ok(ops::math::Mul.into_hir()));\n    reg.insert(\"Pow\", |_, _| Ok(ops::math::Pow.into_hir()));\n    reg.insert(\"Neg\", |_, _| Ok(ops::math::neg().into_hir()));\n    reg.insert(\"RealDiv\", |_, _| Ok(ops::math::Div.into_hir()));\n    reg.insert(\"Rsqrt\", |_, _| Ok(ops::math::rsqrt().into_hir()));\n    reg.insert(\"Sub\", |_, _| Ok(ops::math::Sub.into_hir()));\n    reg.insert(\"Tanh\", |_, _| Ok(ops::math::tanh().into_hir()));\n}\n\npub fn add_n(_ctx: &ParsingContext, _pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    Ok(Box::new(ops::binary::Nary(Box::new(ops::math::Add), false)))\n}\n\npub fn mat_mul(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let trans_a = pb.get_attr_bool(\"transpose_a\")?;\n    let trans_b = pb.get_attr_bool(\"transpose_b\")?;\n    Ok(expand(ops::matmul::MatMulInference::default().with_a_trans(trans_a).with_b_trans(trans_b)))\n}\n"
  },
  {
    "path": "tensorflow/src/ops/mod.rs",
    "content": "use tract_hir::internal::*;\n\nuse crate::model::ParsingContext;\nuse crate::model::TfOpRegister;\nuse crate::tfpb::tensorflow::NodeDef;\n\npub mod array;\npub mod control_flow;\npub mod logic;\npub mod math;\npub mod nn;\npub mod quant;\npub mod random;\npub mod rec;\n// pub mod vars;\n\npub fn register_all_ops(reg: &mut TfOpRegister) {\n    array::register_all_ops(reg);\n    control_flow::register_all_ops(reg);\n    logic::register_all_ops(reg);\n    math::register_all_ops(reg);\n    nn::register_all_ops(reg);\n    quant::register_all_ops(reg);\n    random::register_all_ops(reg);\n    rec::register_all_ops(reg);\n    // vars::register_all_ops(reg);\n    reg.insert(\"Cast\", cast);\n    reg.insert(\"Const\", konst);\n    reg.insert(\"Identity\", |_, _| Ok(Box::new(tract_hir::ops::identity::Identity)));\n    reg.insert(\"NoOp\", |_, _| Ok(Box::new(Noop)));\n    reg.insert(\"Placeholder\", |_, _| Ok(Box::new(tract_hir::ops::source::Source::new())));\n}\n\nfn cast(_ctx: &ParsingContext, node: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let dtype = node.get_attr_datum_type(\"DstT\")?;\n    Ok(Box::new(tract_hir::ops::cast::cast(dtype)))\n}\n\nfn konst(_ctx: &ParsingContext, node: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let dtype = node.get_attr_datum_type(\"dtype\")?;\n    let mat = node.get_attr_tensor(\"value\")?;\n\n    if mat.datum_type() != dtype {\n        bail!(\"Const node {:?} doesn't have the expected {:?} type.\", mat, dtype);\n    }\n\n    Ok(Box::new(tract_hir::ops::konst::Const::new(mat.into())?))\n}\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\npub struct Noop;\n\nimpl Op for Noop {\n    fn name(&self) -> StaticName {\n        \"Noop\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Noop {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, _inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        Ok(tvec!(Tensor::from(false).into()))\n    }\n}\n\nimpl InferenceRulesOp for Noop {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        _inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        s.equals(&outputs[0].datum_type, bool::datum_type())?;\n        s.equals(&outputs[0].rank, 0)?;\n        Ok(())\n    }\n\n    as_op!();\n    to_typed!();\n}\n\nimpl TypedOp for Noop {\n    fn output_facts(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(bool::scalar_fact()))\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "tensorflow/src/ops/nn/conv2d.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops::cnn;\nuse tract_hir::ops::nn::DataFormat;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\npub fn conv2d(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let strides = super::strides(pb)?;\n    let mut op =\n        cnn::Conv::default().hwio().padding(super::padding(pb)?).strides(strides[1..3].into());\n    if super::data_format(pb)? == DataFormat::NHWC {\n        op = op.nhwc()\n    }\n    Ok(expand(op))\n}\n\n#[cfg(test)]\nmod tests {\n    #![allow(non_snake_case)]\n    use super::*;\n    use tract_hir::ops::cnn::{Conv, PaddingSpec};\n    use tract_ndarray::*;\n\n    fn mk(sizes: &[usize]) -> Tensor {\n        Array::range(1f32, sizes.iter().product::<usize>() as f32 + 1.0, 1.0)\n            .into_shape_with_order(sizes)\n            .unwrap()\n            .into()\n    }\n\n    fn make_conv(h_stride: usize, v_stride: usize, padding: PaddingSpec) -> Box<dyn InferenceOp> {\n        expand(Conv::default().nhwc().hwio().padding(padding).strides(tvec![v_stride, h_stride]))\n    }\n\n    fn verify(input: Tensor, filter: Tensor, stride: usize, padding: PaddingSpec, expect: &[f32]) {\n        let result = make_conv(stride, stride, padding)\n            .eval(tvec![input.into(), filter.into()])\n            .unwrap()\n            .remove(0);\n        assert_eq!(expect.len(), result.shape().iter().product::<usize>());\n        let found = result.to_plain_array_view::<f32>().unwrap();\n        let expect = ArrayD::from_shape_vec(found.shape(), expect.to_vec()).unwrap();\n        assert_eq!(expect, found);\n    }\n\n    #[test]\n    fn testConv2D3CNoopFilter() {\n        verify(\n            mk(&[1, 2, 3, 3]),\n            tensor4(&[[[[1.0f32, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]]]),\n            1,\n            PaddingSpec::Valid,\n            &[\n                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,\n                16.0, 17.0, 18.0,\n            ],\n        )\n    }\n\n    #[test]\n    fn testConv2D1x1Filter() {\n        verify(\n            mk(&[1, 2, 3, 3]),\n            mk(&[1, 1, 3, 3]),\n            1,\n            PaddingSpec::Valid,\n            &[\n                30.0, 36.0, 42.0, 66.0, 81.0, 96.0, 102.0, 126.0, 150.0, 138.0, 171.0, 204.0,\n                174.0, 216.0, 258.0, 210.0, 261.0, 312.0,\n            ],\n        );\n    }\n\n    #[test]\n    fn testConv2D1x2Filter() {\n        verify(\n            mk(&[1, 2, 3, 3]),\n            mk(&[1, 2, 3, 3]),\n            1,\n            PaddingSpec::Valid,\n            &[231.0, 252.0, 273.0, 384.0, 423.0, 462.0, 690.0, 765.0, 840.0, 843.0, 936.0, 1029.0],\n        )\n    }\n\n    #[test]\n    fn testConv2D2x1Filter() {\n        verify(\n            mk(&[1, 2, 3, 3]),\n            mk(&[2, 1, 3, 3]),\n            1,\n            PaddingSpec::Valid,\n            &[465.0, 504.0, 543.0, 618.0, 675.0, 732.0, 771.0, 846.0, 921.0],\n        );\n    }\n\n    #[test]\n    fn testConv2D2x2Filter() {\n        verify(\n            mk(&[1, 2, 3, 3]),\n            mk(&[2, 2, 3, 3]),\n            1,\n            PaddingSpec::Valid,\n            &[2271.0, 2367.0, 2463.0, 2901.0, 3033.0, 3165.0],\n        )\n    }\n\n    #[test]\n    fn testConv2D2x2FilterStride2() {\n        verify(\n            mk(&[1, 2, 3, 3]),\n            mk(&[2, 2, 3, 3]),\n            2,\n            PaddingSpec::Valid,\n            &[2271.0, 2367.0, 2463.0],\n        )\n    }\n\n    #[test]\n    fn testConv2D2x2FilterStride2Same() {\n        verify(\n            mk(&[1, 2, 3, 3]),\n            mk(&[2, 2, 3, 3]),\n            2,\n            PaddingSpec::SameUpper,\n            &[2271.0, 2367.0, 2463.0, 1230.0, 1305.0, 1380.0],\n        );\n    }\n\n    #[test]\n    fn test_conv_1() {\n        let conv = make_conv(1, 1, PaddingSpec::SameUpper);\n        // NHWC\n        let data = tensor4(&[[[[1f32]]]]);\n        // HWIO\n        let filter = tensor4(&[[[[0.0f32]]], [[[1.0]]], [[[0.0]]]]);\n        let exp = tensor4(&[[[[1f32]]]]);\n\n        let result = conv.eval(tvec![data.into(), filter.into()]).unwrap();\n        result[0].close_enough(&exp, Approximation::Approximate).unwrap()\n    }\n\n    #[test]\n    fn test_conv_2() {\n        let conv = make_conv(1, 1, PaddingSpec::SameUpper);\n        let data = tensor4(&[[[[142.3088f32], [48.891083]], [[208.3187], [-11.274994]]]]);\n        let filter = tensor4(&[[[[160.72833f32]], [[107.84076]]], [[[247.50552]], [[-38.738464]]]]);\n        let exp = tensor4(&[[[[80142.31f32], [5067.5586]], [[32266.81], [-1812.2109]]]]);\n        let got = &conv.eval(tvec![data.into(), filter.into()]).unwrap()[0];\n        //println!(\"{:?}\", got);\n        //println!(\"{:?}\", exp);\n        exp.close_enough(got, true).unwrap()\n    }\n\n    #[test]\n    fn inference_1() {\n        let mut op = make_conv(1, 3, PaddingSpec::Valid);\n        let img = InferenceFact::from(Tensor::zero::<f32>(&[1, 1, 7, 1]).unwrap());\n        let ker = InferenceFact::from(Tensor::zero::<f32>(&[1, 3, 1, 1]).unwrap());\n        let any = InferenceFact::default();\n\n        let (_, output_facts, _) = op.infer_facts(tvec![&img, &ker], tvec![&any], tvec!()).unwrap();\n\n        assert_eq!(output_facts, tvec![f32::fact([1, 1, (7 - 3 + 1), 1]).into()]);\n    }\n\n    #[test]\n    fn inference_2() {\n        let mut op = make_conv(1, 1, PaddingSpec::SameUpper);\n        let img = InferenceFact::from(Tensor::zero::<f32>(&[1, 1, 1, 1]).unwrap());\n        let ker = InferenceFact::from(Tensor::zero::<f32>(&[1, 1, 1, 1]).unwrap());\n        let any = InferenceFact::default();\n\n        let (_, output_facts, _) = op.infer_facts(tvec![&img, &ker], tvec![&any], tvec!()).unwrap();\n\n        assert_eq!(output_facts, tvec![f32::fact([1, 1, 1, 1]).into()]);\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/nn/dw_conv2d.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\nuse tract_hir::internal::*;\nuse tract_hir::ops::cnn::*;\nuse tract_hir::ops::nn::*;\n\npub fn depthwise_conv2d(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let data_format = super::data_format(pb)?;\n    let padding = super::padding(pb)?;\n    let strides = super::strides(pb)?.into();\n    let dilations: TVec<usize> = pb.get_attr_list_int(\"dilations\")?.into();\n    if dilations.len() != 4 || dilations[0] != 1 && dilations[3] != 1 {\n        bail!(\"dilations must be of the form [1, h, v, 1], found {:?}\", dilations)\n    };\n    Ok(expand(DepthwiseConv2d::new(data_format, padding, strides, dilations)))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct DepthwiseConv2d {\n    data_format: DataFormat,\n    padding: PaddingSpec,\n    strides: TVec<usize>,\n    dilations: TVec<usize>,\n}\n\nimpl Expansion for DepthwiseConv2d {\n    fn name(&self) -> StaticName {\n        \"DepthwiseConv2dNative\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 2)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].rank, 4)?;\n        s.equals(&inputs[1].rank, 4)?;\n        s.equals(&inputs[0].datum_type, &inputs[1].datum_type)?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&outputs[0].rank, 4)?;\n        s.given_2(&inputs[0].shape, &inputs[1].shape, move |s, img, ker| {\n            let img = self.data_format.shape(img)?;\n            s.equals(&inputs[1].shape[2], &inputs[0].shape[img.c_axis()])?;\n            s.equals(&outputs[0].shape[img.n_axis().unwrap()], img.n_dim().unwrap())?;\n            if let Ok(ker) = ker.iter().map(|d| d.to_usize()).collect::<TractResult<TVec<_>>>() {\n                let output_shape = self.padding.compute(\n                    img.hw_dims(),\n                    &ker[0..2],\n                    &self.dilations[img.hw_axes()],\n                    &self.strides[img.hw_axes()],\n                );\n                let in_channels = ker[2].to_usize()?;\n                let multiplier = ker[3].to_usize()?;\n                s.equals(&outputs[0].shape[img.h_axis()], &output_shape[0].convoluted)?;\n                s.equals(&outputs[0].shape[img.h_axis() + 1], &output_shape[1].convoluted)?;\n                s.equals(&outputs[0].shape[img.c_axis()], (in_channels * multiplier).to_dim())?;\n            }\n            Ok(())\n        })?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let input = model.outlet_fact(inputs[0])?;\n        let kernel = model.outlet_fact(inputs[1])?;\n        let input_shape = input.shape.to_tvec();\n        let kernel_shape = if let Some(s) = kernel.shape.as_concrete() {\n            s\n        } else {\n            bail!(\"Do not expect streaming on kernel dims\");\n        };\n        let shape = self.data_format.shape(&input_shape)?;\n        let mut conv = Conv::default()\n            .hwio()\n            .group(kernel_shape[2])\n            .dilations(self.dilations[shape.hw_axes()].into())\n            .strides(self.strides[shape.hw_axes()].into())\n            .padding(self.padding.clone());\n        if self.data_format == DataFormat::NHWC {\n            conv = conv.nhwc()\n        }\n        conv.wire(prefix, model, inputs)\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/nn/fused_batch_norm.rs",
    "content": "use tract_hir::internal::*;\nuse tract_itertools::izip;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\npub fn fused_batch_norm(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let epsilon = pb.get_attr_float::<f32>(\"epsilon\")?;\n    Ok(expand(FusedBatchNorm::new(epsilon)))\n}\n\n#[derive(Debug, Clone, new)]\nstruct FusedBatchNorm {\n    epsilon: f32,\n}\n\nimpl Expansion for FusedBatchNorm {\n    fn name(&self) -> StaticName {\n        \"FusedBatchNorm\".into()\n    }\n\n    fn validation(&self) -> Validation {\n        Validation::Rounding\n    }\n\n    /// Registers the inference rules of the operator.\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 5)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, f32::datum_type())?;\n        s.equals(&inputs[1].datum_type, f32::datum_type())?;\n        s.equals(&inputs[2].datum_type, f32::datum_type())?;\n        s.equals(&inputs[3].datum_type, f32::datum_type())?;\n        s.equals(&inputs[4].datum_type, f32::datum_type())?;\n        s.equals(&outputs[0].datum_type, f32::datum_type())?;\n        s.equals(&outputs[0].shape, &inputs[0].shape)?;\n        s.equals(&inputs[0].rank, 4)?;\n        s.equals(&inputs[1].rank, 1)?;\n        s.equals(&inputs[2].rank, 1)?;\n        s.equals(&inputs[3].rank, 1)?;\n        s.equals(&inputs[4].rank, 1)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        s.equals(&inputs[1].shape[0], &inputs[0].shape[3])?;\n        s.equals(&inputs[2].shape[0], &inputs[0].shape[3])?;\n        s.equals(&inputs[3].shape[0], &inputs[0].shape[3])?;\n        s.equals(&inputs[4].shape[0], &inputs[0].shape[3])?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        let scale = target.outlet_fact(inputs[1])?;\n        let offset = target.outlet_fact(inputs[2])?;\n        let mean = target.outlet_fact(inputs[3])?;\n        let variance = target.outlet_fact(inputs[4])?;\n        if let (Some(scale), Some(offset), Some(mean), Some(variance)) =\n            (&scale.konst, &offset.konst, &mean.konst, &variance.konst)\n        {\n            let scale = scale.try_as_plain()?.as_slice::<f32>()?;\n            let offset = offset.try_as_plain()?.as_slice::<f32>()?;\n            let mean = mean.try_as_plain()?.as_slice::<f32>()?;\n            let variance = variance.try_as_plain()?.as_slice::<f32>()?;\n            let slope: Vec<f32> =\n                izip!(variance, scale).map(|(v, s)| s / (v + self.epsilon).sqrt()).collect();\n            let inter: Vec<f32> = izip!(offset, mean, &slope).map(|(o, m, s)| o - m * s).collect();\n            let shape = tvec!(1, 1, 1, scale.len());\n            let slope = tensor1(&slope).into_shape(&shape)?;\n            let inter = tensor1(&inter).into_shape(&shape)?;\n            let slope = target.add_const(prefix.to_string() + \".slope\", slope)?;\n            let inter = target.add_const(prefix.to_string() + \".inter\", inter)?;\n            let wire = target.wire_node(\n                format!(\"{prefix}.mul\"),\n                tract_hir::ops::math::mul(),\n                &[inputs[0], slope],\n            )?;\n            return target.wire_node(\n                format!(\"{prefix}.add\"),\n                tract_hir::ops::math::add(),\n                &[wire[0], inter],\n            );\n        };\n        bail!(\"Batch norm parameters expected to be known\")\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/nn/mod.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops::cnn::PaddingSpec;\nuse tract_hir::ops::nn::{DataFormat, LayerSoftmax};\n\nuse crate::model::TfOpRegister;\nuse crate::tfpb::tensorflow::NodeDef;\n\npub mod conv2d;\npub mod dw_conv2d;\npub mod fused_batch_norm;\npub mod pools;\npub mod s2b;\n\npub fn register_all_ops(reg: &mut TfOpRegister) {\n    reg.insert(\"AvgPool\", pools::avgpool);\n    reg.insert(\"Conv2D\", conv2d::conv2d);\n    reg.insert(\"DepthwiseConv2dNative\", dw_conv2d::depthwise_conv2d);\n    reg.insert(\"FusedBatchNorm\", fused_batch_norm::fused_batch_norm);\n    reg.insert(\"MaxPool\", pools::maxpool);\n    reg.insert(\"Relu\", |_, _| Ok(expand(tract_hir::ops::activations::Clip::new(Some(0.0), None))));\n    reg.insert(\"Relu6\", |_, _| {\n        Ok(expand(tract_hir::ops::activations::Clip::new(Some(0.0), Some(6.0))))\n    });\n    reg.insert(\"Sigmoid\", |_, _| Ok(tract_hir::ops::nn::sigmoid().into_hir()));\n    reg.insert(\"Softmax\", |_, _| Ok(expand(LayerSoftmax::new(1, true))));\n    reg.insert(\"SpaceToBatchND\", s2b::space_to_batch_nd);\n    reg.insert(\"BatchToSpaceND\", s2b::batch_to_space_nd);\n}\n\npub fn strides(pb: &NodeDef) -> TractResult<Vec<usize>> {\n    let strides: Vec<usize> = pb.get_attr_list_int(\"strides\")?;\n    if strides.len() != 4 || strides[0] != 1 && strides[3] != 1 {\n        bail!(\"strides must be of the form [1, h, v, 1], found {:?}\", strides)\n    };\n    Ok(strides)\n}\n\npub fn data_format(pb: &NodeDef) -> TractResult<DataFormat> {\n    let df = if pb.get_attr_opt_raw_str(\"data_format\")?.unwrap_or(b\"NHWC\") == b\"NHWC\" {\n        DataFormat::NHWC\n    } else {\n        DataFormat::NCHW\n    };\n    Ok(df)\n}\n\npub fn padding(pb: &NodeDef) -> TractResult<PaddingSpec> {\n    let padding = pb.get_attr_raw_str(\"padding\")?;\n    match padding {\n        b\"VALID\" => Ok(PaddingSpec::Valid),\n        b\"SAME\" => Ok(PaddingSpec::SameUpper),\n        s => bail!(\"unsupported Padding {}\", String::from_utf8_lossy(s)),\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/nn/pools.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\nuse tract_hir::internal::*;\nuse tract_hir::ops::cnn::*;\n\npub fn avgpool(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let ksize: Vec<usize> = pb.get_attr_list_int(\"ksize\")?;\n    let data_format = super::data_format(pb)?;\n    let kshape = data_format.shape(ksize)?;\n    let strides = super::strides(pb)?;\n    let padding = super::padding(pb)?;\n    Ok(expand(HirSumPool::new(\n        PoolSpec::new(\n            data_format,\n            kshape.hw_dims().into(),\n            padding,\n            None,\n            Some(strides[kshape.hw_axes()].into()),\n            0,\n            0,\n        ),\n        false,\n        true,\n    )))\n}\n\npub fn maxpool(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let ksize: Vec<usize> = pb.get_attr_list_int(\"ksize\")?;\n    let data_format = super::data_format(pb)?;\n    let kshape = data_format.shape(ksize)?;\n    let strides = super::strides(pb)?;\n    let padding = super::padding(pb)?;\n    Ok(expand(HirMaxPool::new(\n        PoolSpec::new(\n            data_format,\n            kshape.hw_dims().into(),\n            padding,\n            None,\n            Some(strides[kshape.hw_axes()].into()),\n            0,\n            0,\n        ),\n        None,\n    )))\n}\n"
  },
  {
    "path": "tensorflow/src/ops/nn/s2b/mod.rs",
    "content": "use tract_hir::internal::*;\nuse tract_ndarray::prelude::*;\nuse tract_num_traits::Zero;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\npub mod raw;\npub mod unary;\n\npub fn space_to_batch_nd(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let datum_type = pb.get_attr_datum_type(\"T\")?;\n    Ok(Box::new(raw::SpaceToBatch::new(datum_type)))\n}\n\npub fn batch_to_space_nd(_ctx: &ParsingContext, pb: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let datum_type = pb.get_attr_datum_type(\"T\")?;\n    Ok(Box::new(raw::BatchToSpace::new(datum_type)))\n}\n\nfn space_to_batch<T: Copy + Datum + Zero>(\n    input: TValue,\n    block_shape: &ArrayView1<i32>,\n    paddings: &ArrayView2<i32>,\n) -> TractResult<TValue> {\n    let mut data = input.into_tensor();\n\n    for (ix, pad) in paddings.view().outer_iter().enumerate() {\n        if pad[0] == 0 && pad[1] == 0 {\n            continue;\n        }\n        let mut stack = tvec!();\n        let mut pad_shape = data.shape().to_vec();\n        if pad[0] != 0 {\n            pad_shape[ix + 1] = pad[0] as usize;\n            stack.push(Tensor::zero::<T>(&pad_shape)?);\n        }\n        stack.push(data);\n        if pad[1] != 0 {\n            pad_shape[ix + 1] = pad[1] as usize;\n            stack.push(Tensor::zero::<T>(&pad_shape)?);\n        }\n        data = Tensor::stack_tensors(ix + 1, &stack)?;\n    }\n\n    let mut reshaped = vec![data.shape()[0]];\n    let block_size = block_shape.iter().map(|a| *a as usize).product::<usize>();\n    let mut final_shape = vec![block_size * data.shape()[0]];\n    for (m, &block_shape_dim) in block_shape.iter().enumerate() {\n        reshaped.push(data.shape()[m + 1] / block_shape_dim as usize);\n        reshaped.push(block_shape_dim as usize);\n        final_shape.push(data.shape()[m + 1] / block_shape_dim as usize);\n    }\n    reshaped.extend(&data.shape()[block_shape.len() + 1..]);\n    final_shape.extend(&data.shape()[block_shape.len() + 1..]);\n    let data = data.into_shape(&reshaped)?;\n\n    let mut permuted_axes: Vec<_> = (0..block_shape.len()).map(|x| 2 * x + 2).collect();\n    permuted_axes.push(0);\n    permuted_axes.extend((0..block_shape.len()).map(|x| 2 * x + 1));\n    permuted_axes.extend((block_shape.len() * 2 + 1)..data.rank());\n    let data = data.permute_axes(&permuted_axes)?;\n    let data = data.into_shape(&final_shape)?;\n\n    Ok(data.into_tvalue())\n}\n\nfn batch_to_space<T: Copy + Datum + Zero>(\n    input: TValue,\n    block_shape: &ArrayView1<i32>,\n    crops: &ArrayView2<i32>,\n) -> TractResult<TValue> {\n    let data = input.into_tensor().into_plain_array()?;\n    let input_shape = data.shape().to_vec();\n    let crops: ArrayView2<i32> = crops.view().into_dimensionality()?;\n\n    let block_size = block_shape.iter().map(|a| *a as usize).product::<usize>();\n\n    // block_dim_1 .. block_dim_n, batches/bloc_size, dim_1, .. dim_n, chan_1, .., chan_n\n    let mut unflatten_blocked_shape = vec![];\n    unflatten_blocked_shape.extend(block_shape.iter().map(|a| *a as usize));\n    let batches = data.shape()[0] / block_size;\n    unflatten_blocked_shape.push(batches);\n    unflatten_blocked_shape.extend(&data.shape()[1..]);\n    let data = data.into_shape_with_order(&*unflatten_blocked_shape)?;\n    let mut permuted_axes = vec![block_shape.len()];\n    let mut padded_shape = vec![batches];\n    for i in 0..block_shape.shape()[0] {\n        permuted_axes.push(block_shape.len() + 1 + i);\n        permuted_axes.push(i);\n        padded_shape.push(block_shape[i] as usize * input_shape[i + 1]);\n    }\n    permuted_axes.extend((1 + block_shape.len() * 2)..data.ndim());\n    padded_shape.extend(&input_shape[1 + block_shape.len()..]);\n    let data = data.permuted_axes(permuted_axes);\n    let data: Vec<T> = data.iter().copied().collect();\n    let data = tract_ndarray::ArrayD::from_shape_vec(padded_shape, data)?;\n    let mut data = data;\n    for (i, crop) in crops.outer_iter().enumerate() {\n        if crop[0] != 0 || crop[1] != 0 {\n            let end = data.shape()[1 + i];\n            let range = (crop[0] as usize)..(end - crop[1] as usize);\n            data = data.slice_axis(Axis(i + 1), range.into()).map(|x| *x).to_owned();\n        }\n    }\n    Ok(data.into_tvalue())\n}\n\n#[cfg(test)]\nmod tests {\n    #![allow(non_snake_case)]\n    use super::raw::{BatchToSpace, SpaceToBatch};\n    use super::*;\n\n    // https://www.tensorflow.org/api_docs/python/tf/space_to_batch_nd\n    #[test]\n    fn space_to_batch_nd_1() {\n        assert_eq!(\n            SpaceToBatch::new(i32::datum_type())\n                .eval(tvec![\n                    tensor4(&[[[[1i32], [2]], [[3], [4]]]]).into(),\n                    tensor1(&[2, 2]).into(),\n                    tensor2(&[[0, 0], [0, 0]]).into(),\n                ])\n                .unwrap(),\n            tvec![tensor4(&[[[[1i32]]], [[[2]]], [[[3]]], [[[4]]]]).into()],\n        )\n    }\n\n    #[test]\n    fn space_to_batch_nd_2() {\n        assert_eq!(\n            SpaceToBatch::new(i32::datum_type())\n                .eval(tvec![\n                    tensor4(&[[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]]).into(),\n                    tensor1(&[2, 2]).into(),\n                    tensor2(&[[0, 0], [0, 0]]).into(),\n                ])\n                .unwrap(),\n            tvec![\n                tensor4(&[[[[1i32, 2, 3]]], [[[4, 5, 6]]], [[[7, 8, 9]]], [[[10, 11, 12]]],])\n                    .into(),\n            ],\n        )\n    }\n\n    #[test]\n    fn space_to_batch_nd_3() {\n        assert_eq!(\n            SpaceToBatch::new(i32::datum_type())\n                .eval(tvec![\n                    tensor4(&[[\n                        [[1], [2], [3], [4]],\n                        [[5], [6], [7], [8]],\n                        [[9], [10], [11], [12]],\n                        [[13], [14], [15], [16]],\n                    ]])\n                    .into(),\n                    tensor1(&[2, 2]).into(),\n                    tensor2(&[[0, 0], [0, 0]]).into(),\n                ])\n                .unwrap(),\n            tvec![\n                tensor4(&[\n                    [[[1], [3]], [[9], [11]]],\n                    [[[2], [4]], [[10], [12]]],\n                    [[[5], [7]], [[13], [15]]],\n                    [[[6], [8]], [[14], [16]]],\n                ])\n                .into()\n            ],\n        )\n    }\n\n    #[test]\n    fn space_to_batch_nd_4() {\n        assert_eq!(\n            SpaceToBatch::new(i32::datum_type())\n                .eval(tvec![\n                    tensor4(&[\n                        [[[1], [2], [3], [4]], [[5], [6], [7], [8]]],\n                        [[[9], [10], [11], [12]], [[13], [14], [15], [16]]],\n                    ])\n                    .into(),\n                    tensor1(&[2, 2]).into(),\n                    tensor2(&[[0, 0], [2, 0]]).into(),\n                ])\n                .unwrap(),\n            tvec![\n                tensor4(&[\n                    [[[0], [1], [3]]],\n                    [[[0], [9], [11]]],\n                    [[[0], [2], [4]]],\n                    [[[0], [10], [12]]],\n                    [[[0], [5], [7]]],\n                    [[[0], [13], [15]]],\n                    [[[0], [6], [8]]],\n                    [[[0], [14], [16]]],\n                ])\n                .into(),\n            ],\n        )\n    }\n\n    #[test]\n    fn space_to_batch_nd_infer_1() {\n        let mut op = SpaceToBatch::new(f32::datum_type());\n        let data = f32::fact([1, 4, 16]).into();\n        let block_shape = InferenceFact::from(Tensor::from(arr1(&[2])));\n        let paddings = InferenceFact::from(Tensor::from(arr2(&[[0.to_dim(), 0.to_dim()]])));\n        let any = InferenceFact::default();\n\n        let (_, outputs, _) =\n            op.infer_facts(tvec!(&data, &block_shape, &paddings), tvec!(&any), tvec!()).unwrap();\n\n        assert_eq!(outputs[0], f32::fact([2, 2, 16]).into())\n    }\n\n    #[test]\n    fn space_to_batch_nd_infer_2() {\n        let table = SymbolScope::default();\n        let s = table.sym(\"S\");\n        let mut op = SpaceToBatch::new(f32::datum_type());\n        let data = f32::fact(dims!(1, s.to_dim() - 4, 16)).into();\n        let block_shape = InferenceFact::from(Tensor::from(arr1(&[2])));\n        let paddings = InferenceFact::from(Tensor::from(arr2(&[[0.to_dim(), (s.to_dim() % 2)]])));\n        let any = InferenceFact::default();\n\n        let (_, outputs, _) =\n            op.infer_facts(tvec!(&data, &block_shape, &paddings), tvec!(&any), tvec!()).unwrap();\n        assert_eq!(\n            outputs[0],\n            f32::fact(dims!(2, (s.to_dim() + s.to_dim() % 2 - 4) / 2, 16)).into()\n        );\n    }\n\n    #[test]\n    fn batch_to_space_nd_1() {\n        assert_eq!(\n            BatchToSpace::new(i32::datum_type())\n                .eval(tvec![\n                    tensor4(&[[[[1]]], [[[2]]], [[[3]]], [[[4]]]]).into(),\n                    tensor1(&[2, 2]).into(),\n                    tensor2(&[[0, 0], [0, 0]]).into(),\n                ])\n                .unwrap(),\n            tvec![tensor4(&[[[[1], [2]], [[3], [4]]]]).into()]\n        )\n    }\n\n    #[test]\n    fn batch_to_space_nd_2() {\n        assert_eq!(\n            BatchToSpace::new(i32::datum_type())\n                .eval(tvec![\n                    tensor4(&[[[[1i32, 2, 3]]], [[[4, 5, 6]]], [[[7, 8, 9]]], [[[10, 11, 12]]],])\n                        .into(),\n                    tensor1(&[2, 2]).into(),\n                    tensor2(&[[0, 0], [0, 0]]).into(),\n                ])\n                .unwrap(),\n            tvec![tensor4(&[[[[1i32, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]]).into()]\n        )\n    }\n\n    #[test]\n    fn batch_to_space_nd_3() {\n        assert_eq!(\n            BatchToSpace::new(i32::datum_type())\n                .eval(tvec![\n                    tensor4(&[\n                        [[[1i32], [3]], [[9], [11]]],\n                        [[[2], [4]], [[10], [12]]],\n                        [[[5], [7]], [[13], [15]]],\n                        [[[6], [8]], [[14], [16]]],\n                    ])\n                    .into(),\n                    tensor1(&[2, 2]).into(),\n                    tensor2(&[[0, 0], [0, 0]]).into(),\n                ])\n                .unwrap(),\n            tvec![\n                tensor4(&[[\n                    [[1i32], [2], [3], [4]],\n                    [[5], [6], [7], [8]],\n                    [[9], [10], [11], [12]],\n                    [[13], [14], [15], [16]],\n                ]])\n                .into(),\n            ]\n        )\n    }\n\n    #[test]\n    fn batch_to_space_nd_4() {\n        assert_eq!(\n            BatchToSpace::new(i32::datum_type())\n                .eval(tvec![\n                    tensor4(&[\n                        [[[0i32], [1], [3]]],\n                        [[[0], [9], [11]]],\n                        [[[0], [2], [4]]],\n                        [[[0], [10], [12]]],\n                        [[[0], [5], [7]]],\n                        [[[0], [13], [15]]],\n                        [[[0], [6], [8]]],\n                        [[[0], [14], [16]]],\n                    ])\n                    .into(),\n                    tensor1(&[2, 2]).into(),\n                    tensor2(&[[0, 0], [2, 0]]).into(),\n                ])\n                .unwrap(),\n            tvec![\n                tensor4(&[\n                    [[[1], [2], [3], [4]], [[5], [6], [7], [8]]],\n                    [[[9], [10], [11], [12]], [[13], [14], [15], [16]]],\n                ])\n                .into(),\n            ]\n        )\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/nn/s2b/raw.rs",
    "content": "use tract_hir::internal::*;\nuse tract_ndarray::prelude::*;\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct SpaceToBatch {\n    datum_type: DatumType,\n}\n\nimpl Op for SpaceToBatch {\n    fn name(&self) -> StaticName {\n        \"SpaceToBatch\".into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for SpaceToBatch {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (input, block_shape, paddings) = args_3!(inputs);\n        let block_shape = block_shape.cast_to::<i32>()?;\n        let block_shape = block_shape.to_plain_array_view::<i32>()?.into_dimensionality()?;\n        let paddings = paddings.cast_to::<i32>()?;\n        let paddings = paddings.to_plain_array_view::<i32>()?.into_dimensionality()?;\n        let r = dispatch_numbers!(super::space_to_batch(input.datum_type())(\n            input,\n            &block_shape.view(),\n            &paddings.view()\n        ))?;\n        Ok(tvec!(r))\n    }\n}\n\nimpl InferenceRulesOp for SpaceToBatch {\n    /// Registers the inference rules of the operator.\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        rules(s, self.datum_type, &outputs[0], &inputs[0], &inputs[1], &inputs[2])\n    }\n\n    as_op!();\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        if let (Some(block_shape), Some(paddings)) = (\n            target.outlet_fact(mapping[&node.inputs[1]])?.konst.clone(),\n            target.outlet_fact(mapping[&node.inputs[2]])?.konst.clone(),\n        ) {\n            let paddings = paddings.cast_to::<TDim>()?;\n            let paddings_view =\n                paddings.to_plain_array_view::<TDim>()?.into_dimensionality::<Ix2>()?;\n            let mut paddings = tvec![];\n            for p in paddings_view.outer_iter() {\n                let pad = match (p[0].to_usize(), p[1].to_usize()) {\n                    (Ok(bef), Ok(aft)) => super::unary::PaddingStrat::FixedFixed(bef, aft),\n                    (_, Ok(aft)) => super::unary::PaddingStrat::FlexFixed(aft),\n                    (Ok(bef), _) => super::unary::PaddingStrat::FixedFlex(bef),\n                    _ => bail!(\"Failed to unarize SpaceToBatch because of padding\"),\n                };\n                paddings.push(pad);\n            }\n            let op = super::unary::SpaceToBatchUnary::new(\n                self.datum_type,\n                target.outlet_fact(mapping[&node.inputs[0]])?.shape.to_tvec(),\n                node.outputs[0]\n                    .fact\n                    .shape\n                    .concretize()\n                    .unwrap()\n                    .iter()\n                    .cloned()\n                    .collect::<TVec<_>>(),\n                block_shape.into_tensor().into_plain_array::<i32>()?.into_dimensionality()?,\n                paddings,\n            );\n            target.wire_node(&*node.name, op, [mapping[&node.inputs[0]]].as_ref())\n        } else {\n            bail!(\"Need fixed block shape and padding\")\n        }\n    }\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct BatchToSpace {\n    datum_type: DatumType,\n}\n\nimpl Op for BatchToSpace {\n    fn name(&self) -> StaticName {\n        \"BatchToSpace\".into()\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for BatchToSpace {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (input, block_shape, crops) = args_3!(inputs);\n        let block_shape = block_shape.cast_to::<i32>()?;\n        let block_shape = block_shape.to_plain_array_view::<i32>()?.into_dimensionality()?;\n        let crops = crops.cast_to::<i32>()?;\n        let crops = crops.to_plain_array_view::<i32>()?.into_dimensionality()?;\n        let r = dispatch_numbers!(super::batch_to_space(input.datum_type())(\n            input,\n            &block_shape.view(),\n            &crops.view()\n        ))?;\n        Ok(tvec!(r))\n    }\n}\n\nimpl InferenceRulesOp for BatchToSpace {\n    /// Registers the inference rules of the operator.\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        rules(s, self.datum_type, &inputs[0], &outputs[0], &inputs[1], &inputs[2])\n    }\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        if let (Some(block_shape), Some(paddings)) = (\n            target.outlet_fact(mapping[&node.inputs[1]])?.konst.clone(),\n            target.outlet_fact(mapping[&node.inputs[2]])?.konst.clone(),\n        ) {\n            let paddings = paddings.cast_to::<TDim>()?;\n            let paddings = paddings.to_plain_array_view::<TDim>()?.into_dimensionality::<Ix2>()?;\n            let paddings = paddings\n                .outer_iter()\n                .map(|p| {\n                    Ok(match (p[0].to_usize(), p[1].to_usize()) {\n                        (Ok(bef), Ok(aft)) => super::unary::PaddingStrat::FixedFixed(bef, aft),\n                        (_, Ok(aft)) => super::unary::PaddingStrat::FlexFixed(aft),\n                        (Ok(bef), _) => super::unary::PaddingStrat::FixedFlex(bef),\n                        _ => bail!(\"Failed to unarize SpaceToBatch because of padding\"),\n                    })\n                })\n                .collect::<TractResult<_>>()?;\n            let op = super::unary::BatchToSpaceUnary::new(\n                self.datum_type,\n                target.outlet_fact(mapping[&node.inputs[0]])?.shape.to_tvec(),\n                node.outputs[0]\n                    .fact\n                    .shape\n                    .concretize()\n                    .unwrap()\n                    .iter()\n                    .cloned()\n                    .collect::<TVec<_>>(),\n                block_shape.into_tensor().into_plain_array::<i32>()?.into_dimensionality()?,\n                paddings,\n            );\n            target.wire_node(&*node.name, op, [mapping[&node.inputs[0]]].as_ref())\n        } else {\n            bail!(\"Need fixed block shape and padding\")\n        }\n    }\n    as_op!();\n}\n\nfn rules<'r, 'p: 'r>(\n    s: &mut Solver<'r>,\n    datum_type: DatumType,\n    batch: &'p TensorProxy,\n    space: &'p TensorProxy,\n    block_shape: &'p TensorProxy,\n    paddings: &'p TensorProxy,\n) -> InferenceResult {\n    s.equals(&batch.datum_type, datum_type)?;\n    s.equals(&batch.datum_type, &space.datum_type)?;\n    s.equals(&block_shape.datum_type, DatumType::I32)?;\n    s.equals(&batch.rank, &space.rank)?;\n    s.equals(&block_shape.rank, 1)?;\n    s.equals(&paddings.rank, 2)?;\n    s.equals(&block_shape.shape[0], &paddings.shape[0])?;\n    s.given(&block_shape.value, move |s, block_shape| {\n        let block_shape = block_shape.into_tensor().into_plain_array::<i32>()?;\n        let block_shape_prod = block_shape.iter().map(|s| *s as usize).product::<usize>();\n        s.equals(&batch.shape[0], (block_shape_prod as i64) * space.shape[0].bex())?;\n        s.given(&paddings.value, move |s, paddings| {\n            let paddings = paddings.cast_to::<TDim>()?;\n            let paddings = paddings.to_plain_array_view::<TDim>()?.into_dimensionality()?;\n            for d in 0..block_shape.len() {\n                s.equals(\n                    space.shape[1 + d].bex() + &paddings[(d, 0)] + &paddings[(d, 1)],\n                    (block_shape[d] as i64) * batch.shape[1 + d].bex(),\n                )?;\n            }\n            Ok(())\n        })\n    })?;\n    s.given(&block_shape.value, move |s, block_shape| {\n        let block_shape = block_shape.into_tensor().into_plain_array::<i32>()?;\n        s.given(&space.rank, move |s, rank: i64| {\n            for d in block_shape.len() + 1..(rank as usize) {\n                s.equals(&space.shape[d], &batch.shape[d])?\n            }\n            Ok(())\n        })\n    })\n}\n"
  },
  {
    "path": "tensorflow/src/ops/nn/s2b/unary.rs",
    "content": "use tract_hir::internal::*;\nuse tract_ndarray::prelude::*;\n\nuse tract_hir::tract_core::ops::cnn::{Conv, PoolSpec};\n\n#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]\npub enum PaddingStrat {\n    FlexFixed(usize),\n    FixedFlex(usize),\n    FixedFixed(usize, usize),\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct SpaceToBatchUnary {\n    pub datum_type: DatumType,\n    pub space_shape: TVec<TDim>,\n    pub batch_shape: TVec<TDim>,\n    pub block_shape: Array1<i32>,\n    pub pad: TVec<PaddingStrat>,\n}\n\nimpl Op for SpaceToBatchUnary {\n    fn name(&self) -> StaticName {\n        \"SpaceToBatchUnary\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for SpaceToBatchUnary {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let mut paddings = Array2::zeros((self.block_shape.len(), 2));\n        for (ax, &strat) in self.pad.iter().enumerate() {\n            let spread = (self.batch_shape[2 + ax].clone() * self.block_shape[ax]\n                - &self.space_shape[2 + ax])\n                .to_usize()?;\n            let (bef, aft) = match strat {\n                PaddingStrat::FlexFixed(f) => (spread - f, f),\n                PaddingStrat::FixedFlex(f) => (f, spread - f),\n                PaddingStrat::FixedFixed(a, b) => (a, b),\n            };\n            paddings[(ax, 0)] = bef as i32;\n            paddings[(ax, 1)] = aft as i32;\n        }\n        let r = dispatch_numbers!(super::space_to_batch(input.datum_type())(\n            input,\n            &self.block_shape.view(),\n            &paddings.view()\n        ))?;\n        Ok(tvec!(r))\n    }\n}\n\nimpl TypedOp for SpaceToBatchUnary {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].datum_type.fact(&self.batch_shape)))\n    }\n\n    fn declutter(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        // Find any successor chain: S2B → Conv → B2S\n        // (S2B may have multiple successors after PushSplitDown merges duplicate nodes)\n        for succ in &model.node(node.id).outputs[0].successors {\n            let conv_node = model.node(succ.node);\n            let Some(conv_op) = conv_node.op_as::<Conv>() else { continue };\n            for conv_succ in &conv_node.outputs[0].successors {\n                let b2s_node = model.node(conv_succ.node);\n                if b2s_node.op_as::<BatchToSpaceUnary>().is_none() {\n                    continue;\n                }\n                let op = Conv {\n                    pool_spec: PoolSpec {\n                        dilations: Some(self.block_shape.iter().map(|&i| i as usize).collect()),\n                        ..conv_op.pool_spec.clone()\n                    },\n                    ..conv_op.clone()\n                };\n                let mut patch = TypedModelPatch::default();\n                let taps_s2b = patch.taps(model, &node.inputs)?;\n                let mut taps_conv = patch.taps(model, &conv_node.inputs)?;\n                taps_conv[0] = taps_s2b[0];\n                let out = patch.model.wire_node(&*conv_node.name, op, &taps_conv)?[0];\n                patch.shunt_outside(model, OutletId::new(b2s_node.id, 0), out)?;\n                return Ok(Some(patch));\n            }\n        }\n        Ok(None)\n    }\n\n    as_op!();\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct BatchToSpaceUnary {\n    datum_type: DatumType,\n    batch_shape: TVec<TDim>,\n    space_shape: TVec<TDim>,\n    block_shape: Array1<i32>,\n    pad: Vec<PaddingStrat>,\n}\n\nimpl Op for BatchToSpaceUnary {\n    fn name(&self) -> StaticName {\n        \"BatchToSpaceUnary\".into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for BatchToSpaceUnary {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let mut paddings = Array2::zeros((self.block_shape.len(), 2));\n        for (ax, &strat) in self.pad.iter().enumerate() {\n            let spread = (self.batch_shape[2 + ax].clone() * self.block_shape[ax]\n                - &self.space_shape[2 + ax])\n                .to_usize()?;\n            let (bef, aft) = match strat {\n                PaddingStrat::FlexFixed(f) => (spread - f, f),\n                PaddingStrat::FixedFlex(f) => (f, spread - f),\n                PaddingStrat::FixedFixed(a, b) => (a, b),\n            };\n            paddings[(ax, 0)] = bef as i32;\n            paddings[(ax, 1)] = aft as i32;\n        }\n        let r = dispatch_numbers!(super::batch_to_space(input.datum_type())(\n            input,\n            &self.block_shape.view(),\n            &paddings.view()\n        ))?;\n        Ok(tvec!(r))\n    }\n}\n\nimpl TypedOp for BatchToSpaceUnary {\n    as_op!();\n\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(inputs[0].datum_type.fact(&self.space_shape)))\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/quant.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::ops;\nuse tract_hir::ops::math::round_ties_to_even;\n\nuse crate::model::ParsingContext;\nuse crate::model::TfOpRegister;\nuse crate::tfpb::tensorflow::NodeDef;\n\npub fn register_all_ops(reg: &mut TfOpRegister) {\n    reg.insert(\"FakeQuantWithMinMaxVars\", fake_quant_with_min_max_vars);\n}\n\nfn fake_quant_with_min_max_vars(\n    _ctx: &ParsingContext,\n    node: &NodeDef,\n) -> TractResult<Box<dyn InferenceOp>> {\n    let narrow_range = node.get_attr_bool(\"narrow_range\")?;\n    let num_bits = node.get_attr_int(\"num_bits\")?;\n    Ok(expand(FakeQuantWithMinMaxVars::new(narrow_range, num_bits)))\n}\n\n#[derive(Clone, Debug, new, Hash, PartialEq, Eq)]\nstruct FakeQuantWithMinMaxVars {\n    narrow_range: bool,\n    num_bits: usize,\n}\n\nimpl FakeQuantWithMinMaxVars {\n    fn step(&self, min: &Tensor, max: &Tensor) -> TractResult<f32> {\n        let min = min.try_as_plain()?.to_scalar::<f32>()?;\n        let max = max.try_as_plain()?.to_scalar::<f32>()?;\n        let amplitude = max - min;\n        let scale_len = 2_usize.pow(self.num_bits as u32) - 1 - self.narrow_range as usize;\n        Ok(amplitude / scale_len as f32)\n    }\n}\n\nimpl Expansion for FakeQuantWithMinMaxVars {\n    fn name(&self) -> StaticName {\n        \"FakeQuantWithMinMaxVars\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&inputs[0].datum_type, &inputs[1].datum_type)?;\n        s.equals(&inputs[0].datum_type, &inputs[2].datum_type)?;\n        s.equals(&inputs[1].shape, shapefactoid!())?;\n        s.equals(&inputs[2].shape, shapefactoid!())?;\n        s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;\n        s.equals(&inputs[0].shape, &outputs[0].shape)?;\n        Ok(())\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        target: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        if let (Some(min), Some(max)) = (\n            target.outlet_fact(inputs[1])?.konst.as_ref(),\n            target.outlet_fact(inputs[2])?.konst.as_ref(),\n        ) {\n            let rank = target.outlet_fact(inputs[0])?.rank();\n            macro_rules! cst {\n                ($id:ident, $value: expr) => {\n                    let $id = tensor0($value).broadcast_into_rank(rank)?;\n                    let $id = target.add_const(prefix.to_string() + \".\" + stringify!($id), $id)?;\n                };\n            }\n            let step = self.step(min, max)?;\n            let min = *min.try_as_plain()?.to_scalar::<f32>()?;\n            let max = *max.try_as_plain()?.to_scalar::<f32>()?;\n            let min_adj = step * round_ties_to_even(min / step);\n            let max_adj = max - min + min_adj;\n            let wire = inputs[0];\n            cst!(min_adj, min_adj);\n            cst!(max_adj, max_adj);\n            cst!(step, step);\n            let wire = target.wire_node(\n                format!(\"{prefix}.clamp_min\"),\n                ops::math::max(),\n                &[wire, min_adj],\n            )?[0];\n            let wire = target.wire_node(\n                format!(\"{prefix}.clamp_max\"),\n                ops::math::min(),\n                &[max_adj, wire],\n            )?[0];\n            let wire = target.wire_node(\n                format!(\"{prefix}.sub-min\"),\n                ops::math::sub(),\n                &[wire, min_adj],\n            )?[0];\n            let wire =\n                target.wire_node(format!(\"{prefix}.div-step\"), ops::math::div(), &[wire, step])?[0];\n            let wire = target.wire_node(\n                format!(\"{prefix}.round\"),\n                ops::math::round_half_to_even(),\n                &[wire],\n            )?[0];\n            let wire =\n                target.wire_node(format!(\"{prefix}.mul-step\"), ops::math::mul(), &[wire, step])?[0];\n            target.wire_node(format!(\"{prefix}.add-min\"), ops::math::add(), &[wire, min_adj])\n        } else {\n            bail!(\"Operator can not be made a TypedOp.\")\n        }\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/random/mod.rs",
    "content": "mod philox;\nmod random_uniform;\n\nuse crate::model::TfOpRegister;\n\npub fn register_all_ops(reg: &mut TfOpRegister) {\n    reg.insert(\"RandomUniform\", random_uniform::random_uniform);\n    reg.insert(\"RandomUniformInt\", random_uniform::random_uniform_int);\n}\n"
  },
  {
    "path": "tensorflow/src/ops/random/philox.rs",
    "content": "// from https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/lib/random/philox_random.h\n\nuse tract_hir::internal::*;\n\n#[derive(Copy, Clone)]\npub struct Philox4x32x10 {\n    key: u64,\n    counter: u128,\n}\n\nfn mul_hilo(a: u32, b: u32) -> (u32, u32) {\n    ((((a as u64) * (b as u64)) >> 32) as u32, ((a as u64) * (b as u64)) as u32)\n}\n\n#[allow(non_upper_case_globals)]\nimpl Philox4x32x10 {\n    pub fn weird_tf_constructor(seed_lo: u64, seed_hi: u64) -> Philox4x32x10 {\n        let mut ph = Self::for_seed(seed_lo);\n        ph.skip_fast((seed_hi as u128) << 64);\n        ph\n    }\n\n    #[allow(unused)]\n    pub fn for_seeds(seed1: u32, seed2: u32) -> Philox4x32x10 {\n        Self::for_seed(((seed2 as u64) << 32) | seed1 as u64)\n    }\n\n    pub fn for_seed(seed: u64) -> Philox4x32x10 {\n        Philox4x32x10 { key: seed, counter: 0 }\n    }\n\n    pub fn skip_fast(&mut self, n: u128) {\n        self.counter = self.counter.wrapping_add(n);\n    }\n\n    #[allow(unused)]\n    pub fn next_as_u32s(&mut self) -> [u32; 4] {\n        let v = self.next();\n        [v as u32, (v >> 32) as u32, (v >> 64) as u32, (v >> 96) as u32]\n    }\n\n    pub fn next(&mut self) -> u128 {\n        let mut key = self.key;\n        let mut counter = self.counter;\n\n        // 0\n        Self::compute_one(&mut counter, key);\n        Self::raise_key(&mut key);\n        // 1\n        Self::compute_one(&mut counter, key);\n        Self::raise_key(&mut key);\n        // 2\n        Self::compute_one(&mut counter, key);\n        Self::raise_key(&mut key);\n        // 3\n        Self::compute_one(&mut counter, key);\n        Self::raise_key(&mut key);\n        // 4\n        Self::compute_one(&mut counter, key);\n        Self::raise_key(&mut key);\n        // 5\n        Self::compute_one(&mut counter, key);\n        Self::raise_key(&mut key);\n        // 6\n        Self::compute_one(&mut counter, key);\n        Self::raise_key(&mut key);\n        // 7\n        Self::compute_one(&mut counter, key);\n        Self::raise_key(&mut key);\n        // 8\n        Self::compute_one(&mut counter, key);\n        Self::raise_key(&mut key);\n        // 9\n        Self::compute_one(&mut counter, key);\n\n        self.counter = self.counter.wrapping_add(1);\n        counter\n    }\n\n    fn raise_key(key: &mut u64) {\n        const kPhiloxW32A: u32 = 0x9E3779B9;\n        const kPhiloxW32B: u32 = 0xBB67AE85;\n\n        let k0 = *key as u32;\n        let k1 = (*key >> 32) as u32;\n        let k0 = k0.wrapping_add(kPhiloxW32A) as u64;\n        let k1 = k1.wrapping_add(kPhiloxW32B) as u64;\n\n        *key = (k1 << 32) | k0;\n    }\n\n    fn compute_one(counter: &mut u128, key: u64) {\n        const kPhiloxM4x32A: u32 = 0xD2511F53;\n        const kPhiloxM4x32B: u32 = 0xCD9E8D57;\n\n        let c0 = *counter as u32;\n        let c1 = (*counter >> 32) as u32;\n        let c2 = (*counter >> 64) as u32;\n        let c3 = (*counter >> 96) as u32;\n\n        let (hi0, lo0) = mul_hilo(kPhiloxM4x32A, c0);\n        let (hi1, lo1) = mul_hilo(kPhiloxM4x32B, c2);\n\n        let r0 = (hi1 ^ c1 ^ (key as u32)) as u128;\n        let r1 = lo1 as u128;\n        let r2 = (hi0 ^ c3 ^ ((key >> 32) as u32)) as u128;\n        let r3 = lo0 as u128;\n\n        *counter = (r3 << 96) | (r2 << 64) | (r1 << 32) | r0\n    }\n\n    pub fn u32_iter(self) -> impl Iterator<Item = u32> {\n        self.flat_map(|big| {\n            tvec![big as u32, (big >> 32) as u32, (big >> 64) as u32, (big >> 96) as u32]\n                .into_iter()\n        })\n    }\n}\n\nimpl Iterator for Philox4x32x10 {\n    type Item = u128;\n    fn next(&mut self) -> Option<u128> {\n        Some(Philox4x32x10::next(self))\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    // checked against https://github.com/dominikwerder/philox\n    // https://github.com/dominikwerder/philox/blob/master/src/test.rs#L62\n    #[test]\n    fn seed() {\n        let mut ph = Philox4x32x10::for_seeds(1, 2);\n        assert_eq!(ph.next_as_u32s(), [0x598de3a, 0x98d2802e, 0x270f8f9e, 0xeab709d3]);\n    }\n\n    #[test]\n    fn zeros() {\n        let mut ph = Philox4x32x10::for_seeds(0, 0);\n        assert_eq!(ph.next_as_u32s(), [0x6627e8d5, 0xe169c58d, 0xbc57ac4c, 0x9b00dbd8]);\n    }\n\n    #[test]\n    fn ffff() {\n        let mut ph = Philox4x32x10::for_seeds(0xffffffff, 0xffffffff);\n        ph.skip_fast(0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff);\n        assert_eq!(ph.next_as_u32s(), [0x408f276d, 0x41c83b0e, 0xa20bc7c6, 0x6d5451fd]);\n    }\n\n    #[test]\n    fn x243f6a88() {\n        let mut ph = Philox4x32x10::for_seeds(0xa4093822, 0x299f31d0);\n        ph.skip_fast(0x0370_7344_1319_8a2e_85a3_08d3_243f_6a88);\n        assert_eq!(ph.next_as_u32s(), [0xd16cfe09, 0x94fdcceb, 0x5001e420, 0x24126ea1]);\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/ops/random/random_uniform.rs",
    "content": "use crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\nuse tract_hir::internal::*;\n\nuse super::philox::Philox4x32x10;\n\npub fn random_uniform(_ctx: &ParsingContext, node: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let dtype = node.get_attr_datum_type(\"dtype\")?;\n    let seed: u64 = node.get_attr_int(\"seed\")?;\n    let seed2: u64 = node.get_attr_int(\"seed2\")?;\n    Ok(Box::new(RandomUniform::new(dtype, seed, seed2)))\n}\n\npub fn random_uniform_int(\n    _ctx: &ParsingContext,\n    node: &NodeDef,\n) -> TractResult<Box<dyn InferenceOp>> {\n    let dtype = node.get_attr_datum_type(\"Tout\")?;\n    let seed: u64 = node.get_attr_int(\"seed\")?;\n    let seed2: u64 = node.get_attr_int(\"seed2\")?;\n    Ok(Box::new(RandomUniformInt::new(dtype, seed, seed2)))\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct RandomUniform {\n    t: DatumType,\n    seed1: u64,\n    seed2: u64,\n}\n\nimpl Op for RandomUniform {\n    fn name(&self) -> StaticName {\n        \"RandomUniform\".into()\n    }\n\n    fn validation(&self) -> Validation {\n        if self.seed1 == 0 && self.seed2 == 0 { Validation::Random } else { Validation::Accurate }\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for RandomUniform {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let shape: TVec<usize> = inputs[0]\n            .cast_to::<i64>()?\n            .try_as_plain()?\n            .as_slice::<i64>()?\n            .iter()\n            .map(|&x| x as usize)\n            .collect();\n        match self.t {\n            DatumType::F32 => Ok(tvec!(make_f32(&shape, self.seed1, self.seed2)?)),\n            dt => bail!(\"RandomUniform not implemented for {:?}\", dt),\n        }\n    }\n}\n\nimpl InferenceRulesOp for RandomUniform {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 1)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, self.t)?;\n        s.equals(&inputs[0].rank, 1)?;\n        s.equals(&inputs[0].shape[0], outputs[0].rank.bex().to_dim())?;\n        s.given(&inputs[0].value, move |s, value| {\n            let shape: TVec<TDim> = value\n                .cast_to::<i64>()?\n                .try_as_plain()?\n                .as_slice::<i64>()?\n                .iter()\n                .map(|&x| x.to_dim())\n                .collect();\n            s.equals(&outputs[0].shape, shape.bex())\n        })?;\n        Ok(())\n    }\n\n    as_op!();\n\n    fn to_typed(\n        &self,\n        _source: &InferenceModel,\n        node: &InferenceNode,\n        target: &mut TypedModel,\n        mapping: &HashMap<OutletId, OutletId>,\n    ) -> TractResult<TVec<OutletId>> {\n        if let Some(ref shape) = target.outlet_fact(mapping[&node.inputs[0]])?.konst {\n            let op = TypedRandomUniform::new(\n                self.t,\n                self.seed1,\n                self.seed2,\n                shape.cast_to::<TDim>()?.try_as_plain()?.as_slice::<TDim>()?.into(),\n            );\n            target.wire_node(&*node.name, op, &[node.inputs[0]])\n        } else {\n            bail!(\"Dynamic shape\")\n        }\n    }\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct TypedRandomUniform {\n    t: DatumType,\n    seed1: u64,\n    seed2: u64,\n    shape: TVec<TDim>,\n}\n\nimpl Op for TypedRandomUniform {\n    fn name(&self) -> StaticName {\n        \"TypedRandomUniform\".into()\n    }\n\n    fn validation(&self) -> Validation {\n        if self.seed1 == 0 && self.seed2 == 0 { Validation::Random } else { Validation::Accurate }\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for TypedRandomUniform {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, _inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let shape = self.shape.iter().map(|d| d.to_usize()).collect::<TractResult<TVec<_>>>()?;\n        match self.t {\n            DatumType::F32 => Ok(tvec!(make_f32(&shape, self.seed1, self.seed2)?)),\n            dt => bail!(\"RandomUniform not implemented for {:?}\", dt),\n        }\n    }\n}\n\nimpl TypedOp for TypedRandomUniform {\n    fn output_facts(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        Ok(tvec!(self.t.fact(&self.shape)))\n    }\n\n    as_op!();\n}\n\npub fn make_f32(shape: &[usize], seed1: u64, seed2: u64) -> TractResult<TValue> {\n    let mut rng = Philox4x32x10::weird_tf_constructor(seed1, seed2).u32_iter();\n    unsafe {\n        let mut tensor = Tensor::uninitialized::<f32>(shape)?;\n        tensor.try_as_plain_mut()?.as_slice_mut::<f32>()?.iter_mut().for_each(|x| {\n            let mantissa = rng.next().unwrap() & 0x7fffff;\n            let exp = 127u32;\n            let f = (exp << 23) | mantissa;\n            *x = f32::from_bits(f) - 1.0\n        });\n        Ok(tensor.into_tvalue())\n    }\n}\n\n#[derive(Debug, Clone, new, Hash, PartialEq, Eq)]\npub struct RandomUniformInt {\n    t: DatumType,\n    seed1: u64,\n    seed2: u64,\n}\n\nimpl RandomUniformInt {\n    pub fn make_i32(&self, shape: &[usize], lo: i32, hi: i32) -> TractResult<TValue> {\n        let mut rng = Philox4x32x10::weird_tf_constructor(self.seed1, self.seed2).u32_iter();\n        unsafe {\n            let mut tensor = Tensor::uninitialized::<i32>(shape)?;\n            tensor.try_as_plain_mut()?.as_slice_mut::<i32>()?.iter_mut().for_each(|x| {\n                // reproduce TF casts, with no conviction\n                let lo = lo as u32;\n                let hi = hi as u32;\n                *x = (lo + rng.next().unwrap() % (hi - lo)) as i32;\n            });\n            Ok(tensor.into_tvalue())\n        }\n    }\n}\n\nimpl Op for RandomUniformInt {\n    fn name(&self) -> StaticName {\n        \"RandomUniformInt\".into()\n    }\n\n    fn validation(&self) -> Validation {\n        if self.seed1 == 0 && self.seed2 == 0 { Validation::Random } else { Validation::Accurate }\n    }\n\n    not_a_typed_op!();\n}\n\nimpl EvalOp for RandomUniformInt {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let shape: TVec<usize> = inputs[0]\n            .cast_to::<i64>()?\n            .try_as_plain()?\n            .as_slice::<i64>()?\n            .iter()\n            .map(|&x| x as usize)\n            .collect();\n        match self.t {\n            DatumType::I32 => Ok(tvec!(Self::make_i32(\n                self,\n                &shape,\n                *inputs[1].try_as_plain()?.to_scalar::<i32>()?,\n                *inputs[2].try_as_plain()?.to_scalar::<i32>()?\n            )?)),\n            dt => bail!(\"RandomUniformInt not implemented for {:?}\", dt),\n        }\n    }\n}\n\nimpl InferenceRulesOp for RandomUniformInt {\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 3)?;\n        check_output_arity(outputs, 1)?;\n        s.equals(&outputs[0].datum_type, self.t)?;\n        s.equals(&inputs[1].datum_type, self.t)?;\n        s.equals(&inputs[2].datum_type, self.t)?;\n        s.equals(&inputs[0].rank, 1)?;\n        s.equals(&inputs[1].rank, 0)?;\n        s.equals(&inputs[2].rank, 0)?;\n        s.equals(&inputs[0].shape[0], outputs[0].rank.bex().to_dim())?;\n        s.given(&inputs[0].value, move |s, value| {\n            let shape: TVec<TDim> = value\n                .cast_to::<i64>()?\n                .try_as_plain()?\n                .as_slice::<i64>()?\n                .iter()\n                .map(|&x| x.to_dim())\n                .collect();\n            s.equals(&outputs[0].shape, shape.bex())\n        })?;\n        Ok(())\n    }\n\n    as_op!();\n}\n"
  },
  {
    "path": "tensorflow/src/ops/rec/block_lstm.rs",
    "content": "use tract_hir::internal::*;\nuse tract_hir::tract_core::ops::einsum::EinSum;\nuse tract_hir::tract_core::ops::scan::ScanInfo;\n\nuse crate::model::ParsingContext;\nuse crate::tfpb::tensorflow::NodeDef;\n\npub fn block_lstm(_ctx: &ParsingContext, node: &NodeDef) -> TractResult<Box<dyn InferenceOp>> {\n    let forget_bias = node.get_attr_opt_float(\"forget_bias\")?.unwrap_or(1.0);\n    let cell_clip = node.get_attr_opt_float(\"cell_clip\")?.unwrap_or(3.0);\n    let t = node.get_attr_datum_type(\"T\")?;\n    let use_peephole = node.get_attr_opt_bool(\"use_peephole\")?.unwrap_or(false);\n    if use_peephole {\n        unimplemented!(\"Block LSTM peeplholes\");\n    }\n    Ok(expand(BlockLSTM::new(forget_bias, cell_clip, t, use_peephole)))\n}\n\n#[derive(Clone, Debug, new)]\n#[allow(dead_code)]\npub struct BlockLSTM {\n    forget_bias: f32,\n    cell_clip: f32,\n    t: DatumType,\n    use_peephole: bool,\n}\n\nimpl Expansion for BlockLSTM {\n    fn name(&self) -> StaticName {\n        \"BlockLSTM\".into()\n    }\n\n    fn rules<'r, 'p: 'r, 's: 'r>(\n        &'s self,\n        s: &mut Solver<'r>,\n        inputs: &'p [TensorProxy],\n        outputs: &'p [TensorProxy],\n    ) -> InferenceResult {\n        check_input_arity(inputs, 9)?;\n        check_input_arity(outputs, 7)?;\n\n        s.equals(&inputs[0].rank, 0)?; // seq_len_max\n        s.equals(&inputs[0].datum_type, i64::datum_type())?;\n\n        // other inputs and outputs are consistent float-like\n        s.equals_all((1..=7).map(move |i| (&inputs[i].datum_type).bex()).collect())?;\n\n        s.equals(&inputs[1].rank, 3)?; // x:  [ time, batch, cell_size ]\n        s.equals(&inputs[2].rank, 2)?; // cs_prev: [batch, cell_size]\n        s.equals(&inputs[3].rank, 2)?; // h_prev: [batch, cell_size]\n        s.equals(&inputs[4].rank, 2)?; // w: []\n        s.equals(&inputs[5].rank, 1)?; // peephole input\n        s.equals(&inputs[6].rank, 1)?; // peephole forget\n        s.equals(&inputs[7].rank, 1)?; // peephole output\n        s.equals(&inputs[8].rank, 1)?; // bias: [ 4*cell_size ]\n        s.equals(&inputs[8].shape[0], 4 * inputs[1].shape[2].bex())?; // bias: [ 4*cell_size ]\n\n        // i, cs, f, o, ci, co, h\n        for (i, output) in outputs.iter().take(7).enumerate() {\n            s.equals(&inputs[1].datum_type, &output.datum_type)?;\n            s.equals(&outputs[i].shape, &inputs[1].shape)?;\n        }\n\n        Ok(())\n    }\n\n    fn nboutputs(&self) -> TractResult<usize> {\n        Ok(7)\n    }\n\n    fn wire(\n        &self,\n        prefix: &str,\n        model: &mut TypedModel,\n        inputs: &[OutletId],\n    ) -> TractResult<TVec<OutletId>> {\n        use tract_hir::tract_core::ops::{array, math, nn, scan};\n\n        let mut body = TypedModel { symbols: model.symbols.clone(), ..TypedModel::default() };\n        let mut outer_inputs = vec![];\n        let mut input_mapping = vec![];\n        let mut output_mapping = vec![];\n\n        let w = model.outlet_fact(inputs[4])?.konst.clone().context(\"W must be cosntant\")?;\n        let b = model.outlet_fact(inputs[8])?.konst.clone().context(\"B must be constant\")?;\n        let cell_size = w.shape()[1] / 4;\n        let mut b = b.into_tensor();\n        b.insert_axis(0)?;\n\n        macro_rules! wire {\n            ($name: ident = $op: expr, $($param: expr),*) => {\n                let $name = body.wire_node(\n                    format!(\"{}-{}\", prefix, stringify!($name)),\n                    $op, [$($param),*].as_ref())?[0];\n            }\n        }\n\n        // X: body input 0: X, new outside input 0 (was 1)\n        outer_inputs.push(inputs[1]);\n        input_mapping.push(scan::InputMapping::Scan(ScanInfo { axis: 0, chunk: 1 }));\n        let mut x_source_fact = model.outlet_fact(inputs[1])?.clone();\n        x_source_fact.shape.set(0, 1.to_dim());\n        let x_source = body.add_source(\"x_source\", x_source_fact)?;\n        wire!(x = AxisOp::Rm(0), x_source);\n\n        // CS: body input 1\n        let cs = model.wire_node(format!(\"{prefix}.cs-axis\"), AxisOp::Add(0), &[inputs[2]])?[0];\n        outer_inputs.push(cs);\n        let cs_fact = model.outlet_fact(cs)?.clone();\n        let cs_source = body.add_source(\"cs_source\", cs_fact)?;\n        input_mapping.push(scan::InputMapping::State);\n        wire!(cs_prev = AxisOp::Rm(0), cs_source);\n\n        // H: body input 2\n        let h = model.wire_node(format!(\"{prefix}.h-axis\"), AxisOp::Add(0), &[inputs[3]])?[0];\n        outer_inputs.push(h);\n        let h_fact = model.outlet_fact(h)?.clone();\n        let h_source = body.add_source(\"h_source\", h_fact)?;\n        input_mapping.push(scan::InputMapping::State);\n        wire!(h_prev = AxisOp::Rm(0), h_source);\n\n        wire!(xh = array::TypedConcat::new(1), x, h_prev);\n\n        let w = body.add_const(format!(\"{prefix}-w\"), w)?;\n        let b = body.add_const(format!(\"{prefix}-b\"), b)?;\n        wire!(i_ci_f_o_1 = EinSum::new(\"mk,kn->mn\".parse()?, f32::datum_type()), xh, w);\n        wire!(i_ci_f_o = math::add(), b, i_ci_f_o_1);\n\n        wire!(i_1 = array::Slice::new(1, 0, cell_size), i_ci_f_o);\n        wire!(i = nn::sigmoid(), i_1);\n\n        wire!(f_1 = array::Slice::new(1, 2 * cell_size, 3 * cell_size), i_ci_f_o);\n        let bias = body.add_const(format!(\"{prefix}-bias\"), rctensor2(&[[self.forget_bias]]))?;\n        wire!(f_2 = math::add(), f_1, bias);\n        wire!(f = nn::sigmoid(), f_2);\n\n        wire!(ci_1 = array::Slice::new(1, cell_size, 2 * cell_size), i_ci_f_o);\n        wire!(ci = math::tanh(), ci_1);\n\n        wire!(o_1 = array::Slice::new(1, 3 * cell_size, 4 * cell_size), i_ci_f_o);\n        wire!(o = nn::sigmoid(), o_1);\n\n        wire!(ci_i = math::mul(), ci, i);\n        wire!(cs_1 = math::mul(), cs_prev, f);\n        wire!(cs = math::add(), cs_1, ci_i);\n\n        wire!(co = math::tanh(), cs);\n        wire!(h = math::mul(), co, o);\n\n        wire!(i_ = AxisOp::Add(0), i);\n        wire!(cs_ = AxisOp::Add(0), cs);\n        wire!(f_ = AxisOp::Add(0), f);\n        wire!(o_ = AxisOp::Add(0), o);\n        wire!(ci_ = AxisOp::Add(0), ci);\n        wire!(co_ = AxisOp::Add(0), co);\n        wire!(h_ = AxisOp::Add(0), h);\n        body.select_output_outlets(&[i_, cs_, f_, o_, ci_, co_, h_])?;\n        for ix in 0..7 {\n            output_mapping.push(scan::OutputMapping::<TDim> {\n                state: ix == 1 || ix == 6,\n                full_dim_hint: None,\n                last_value_slot: None,\n                scan: Some((ix, ScanInfo { axis: 0, chunk: 1 })),\n            })\n        }\n\n        let Some(seqlen) = &model.outlet_fact(inputs[0])?.konst else {\n            bail!(\"Non constant seq_len is not supported\");\n        };\n        let Some(seqlen) = seqlen.as_uniform() else {\n            bail!(\"Non uniform seq_len is not supported\");\n        };\n        let seqlen = seqlen.cast_to::<TDim>()?;\n        if seqlen.try_as_plain()?.to_scalar::<TDim>()? != &model.outlet_fact(inputs[1])?.shape[0] {\n            bail!(\"seq_len only supported for trivial noop case\");\n        };\n        let scan = scan::Scan::new(body, input_mapping, output_mapping, 0)?;\n        model.wire_node(prefix, scan, &outer_inputs)\n    }\n}\n\n/*\n// TODO: rewrite this logic as a tf.Assign declutter ?\nimpl BlockLSTM {\nfn inline_var_assign(\n&self,\nmodel: &TypedModel,\nnode: &TypedNode,\ninput_id: usize,\noutput_id: usize,\npatch: &mut TypedModelPatch,\n) -> TractResult<Option<Arc<Tensor>>> {\nlet var_2 = model.node(node.inputs[input_id].node);\nlet var_2_op = if let Some(op) = var_2.op_as::<crate::ops::vars::VariableV2>() {\nop\n} else {\nreturn Ok(None);\n};\nif var_2.outputs[0].successors.len() != 2 {\nreturn Ok(None);\n}\nlet assign = if let Some(assign_node) = var_2.outputs[0]\n.successors\n.iter()\n.map(|s| model.node(s.node))\n.filter(|s| s.op_is::<crate::ops::vars::Assign>())\n.next()\n{\nassign_node\n} else {\nreturn Ok(None);\n};\nlet rm_axis_node = model.node(assign.inputs[1].node);\nlet rm_axis_op = if let Some(op) = rm_axis_node.op_as::<tract_hir::internal::AxisOp>() {\nop\n} else {\nreturn Ok(None);\n};\nif rm_axis_op != &tract_hir::internal::AxisOp::Rm(0) {\nreturn Ok(None);\n}\nlet slice_node = model.node(rm_axis_node.inputs[0].node);\nlet slice_op = if let Some(op) = slice_node.op_as::<tract_hir::ops::array::Slice<usize>>() {\nop\n} else {\nreturn Ok(None);\n};\nif slice_node.inputs[0] != (node.id, output_id).into() {\nreturn Ok(None);\n}\nlet lstm_output_fact = model.outlet_fact(slice_node.inputs[0])?;\nif slice_op.axis != 0\n|| slice_op.end != slice_op.start + 1\n|| slice_op.end.to_dim() != lstm_output_fact.shape.dim(0)\n{\nreturn Ok(None);\n}\nlet tap = patch.tap_model(model, rm_axis_node.id.into())?;\npatch.shunt_outside(model, assign.id.into(), tap)?;\nOk(var_2_op.initializer.clone())\n}\n}\n*/\n"
  },
  {
    "path": "tensorflow/src/ops/rec/mod.rs",
    "content": "use crate::model::TfOpRegister;\n\npub mod block_lstm;\n\npub fn register_all_ops(reg: &mut TfOpRegister) {\n    reg.insert(\"BlockLSTM\", block_lstm::block_lstm);\n}\n"
  },
  {
    "path": "tensorflow/src/prost/google.protobuf.rs",
    "content": ""
  },
  {
    "path": "tensorflow/src/prost/tensorflow.rs",
    "content": "/// Protocol buffer representing a handle to a tensorflow resource. Handles are\n/// not valid across executions, but can be serialized back and forth from within\n/// a single run.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct ResourceHandleProto {\n    /// Unique name for the device containing the resource.\n    #[prost(string, tag=\"1\")]\n    pub device: ::prost::alloc::string::String,\n    /// Container in which this resource is placed.\n    #[prost(string, tag=\"2\")]\n    pub container: ::prost::alloc::string::String,\n    /// Unique name of this resource.\n    #[prost(string, tag=\"3\")]\n    pub name: ::prost::alloc::string::String,\n    /// Hash code for the type of the resource. Is only valid in the same device\n    /// and in the same execution.\n    #[prost(uint64, tag=\"4\")]\n    pub hash_code: u64,\n    /// For debug-only, the name of the type pointed to by this handle, if\n    /// available.\n    #[prost(string, tag=\"5\")]\n    pub maybe_type_name: ::prost::alloc::string::String,\n}\n/// Dimensions of a tensor.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TensorShapeProto {\n    /// Dimensions of the tensor, such as {\"input\", 30}, {\"output\", 40}\n    /// for a 30 x 40 2D tensor.  If an entry has size -1, this\n    /// corresponds to a dimension of unknown size. The names are\n    /// optional.\n    ///\n    /// The order of entries in \"dim\" matters: It indicates the layout of the\n    /// values in the tensor in-memory representation.\n    ///\n    /// The first entry in \"dim\" is the outermost dimension used to layout the\n    /// values, the last entry is the innermost dimension.  This matches the\n    /// in-memory layout of RowMajor Eigen tensors.\n    ///\n    /// If \"dim.size()\" > 0, \"unknown_rank\" must be false.\n    #[prost(message, repeated, tag=\"2\")]\n    pub dim: ::prost::alloc::vec::Vec<tensor_shape_proto::Dim>,\n    /// If true, the number of dimensions in the shape is unknown.\n    ///\n    /// If true, \"dim.size()\" must be 0.\n    #[prost(bool, tag=\"3\")]\n    pub unknown_rank: bool,\n}\n/// Nested message and enum types in `TensorShapeProto`.\npub mod tensor_shape_proto {\n    /// One dimension of the tensor.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct Dim {\n        /// Size of the tensor in that dimension.\n        /// This value must be >= -1, but values of -1 are reserved for \"unknown\"\n        /// shapes (values of -1 mean \"unknown\" dimension).  Certain wrappers\n        /// that work with TensorShapeProto may fail at runtime when deserializing\n        /// a TensorShapeProto containing a dim value of -1.\n        #[prost(int64, tag=\"1\")]\n        pub size: i64,\n        /// Optional name of the tensor dimension.\n        #[prost(string, tag=\"2\")]\n        pub name: ::prost::alloc::string::String,\n    }\n}\n/// LINT.IfChange\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n#[repr(i32)]\npub enum DataType {\n    /// Not a legal value for DataType.  Used to indicate a DataType field\n    /// has not been set.\n    DtInvalid = 0,\n    /// Data types that all computation devices are expected to be\n    /// capable to support.\n    DtFloat = 1,\n    DtDouble = 2,\n    DtInt32 = 3,\n    DtUint8 = 4,\n    DtInt16 = 5,\n    DtInt8 = 6,\n    DtString = 7,\n    /// Single-precision complex\n    DtComplex64 = 8,\n    DtInt64 = 9,\n    DtBool = 10,\n    /// Quantized int8\n    DtQint8 = 11,\n    /// Quantized uint8\n    DtQuint8 = 12,\n    /// Quantized int32\n    DtQint32 = 13,\n    /// Float32 truncated to 16 bits.  Only for cast ops.\n    DtBfloat16 = 14,\n    /// Quantized int16\n    DtQint16 = 15,\n    /// Quantized uint16\n    DtQuint16 = 16,\n    DtUint16 = 17,\n    /// Double-precision complex\n    DtComplex128 = 18,\n    DtHalf = 19,\n    DtResource = 20,\n    /// Arbitrary C++ data types\n    DtVariant = 21,\n    DtUint32 = 22,\n    DtUint64 = 23,\n    /// Do not use!  These are only for parameters.  Every enum above\n    /// should have a corresponding value below (verified by types_test).\n    DtFloatRef = 101,\n    DtDoubleRef = 102,\n    DtInt32Ref = 103,\n    DtUint8Ref = 104,\n    DtInt16Ref = 105,\n    DtInt8Ref = 106,\n    DtStringRef = 107,\n    DtComplex64Ref = 108,\n    DtInt64Ref = 109,\n    DtBoolRef = 110,\n    DtQint8Ref = 111,\n    DtQuint8Ref = 112,\n    DtQint32Ref = 113,\n    DtBfloat16Ref = 114,\n    DtQint16Ref = 115,\n    DtQuint16Ref = 116,\n    DtUint16Ref = 117,\n    DtComplex128Ref = 118,\n    DtHalfRef = 119,\n    DtResourceRef = 120,\n    DtVariantRef = 121,\n    DtUint32Ref = 122,\n    DtUint64Ref = 123,\n}\nimpl DataType {\n    /// String value of the enum field names used in the ProtoBuf definition.\n    ///\n    /// The values are not transformed in any way and thus are considered stable\n    /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n    pub fn as_str_name(&self) -> &'static str {\n        match self {\n            DataType::DtInvalid => \"DT_INVALID\",\n            DataType::DtFloat => \"DT_FLOAT\",\n            DataType::DtDouble => \"DT_DOUBLE\",\n            DataType::DtInt32 => \"DT_INT32\",\n            DataType::DtUint8 => \"DT_UINT8\",\n            DataType::DtInt16 => \"DT_INT16\",\n            DataType::DtInt8 => \"DT_INT8\",\n            DataType::DtString => \"DT_STRING\",\n            DataType::DtComplex64 => \"DT_COMPLEX64\",\n            DataType::DtInt64 => \"DT_INT64\",\n            DataType::DtBool => \"DT_BOOL\",\n            DataType::DtQint8 => \"DT_QINT8\",\n            DataType::DtQuint8 => \"DT_QUINT8\",\n            DataType::DtQint32 => \"DT_QINT32\",\n            DataType::DtBfloat16 => \"DT_BFLOAT16\",\n            DataType::DtQint16 => \"DT_QINT16\",\n            DataType::DtQuint16 => \"DT_QUINT16\",\n            DataType::DtUint16 => \"DT_UINT16\",\n            DataType::DtComplex128 => \"DT_COMPLEX128\",\n            DataType::DtHalf => \"DT_HALF\",\n            DataType::DtResource => \"DT_RESOURCE\",\n            DataType::DtVariant => \"DT_VARIANT\",\n            DataType::DtUint32 => \"DT_UINT32\",\n            DataType::DtUint64 => \"DT_UINT64\",\n            DataType::DtFloatRef => \"DT_FLOAT_REF\",\n            DataType::DtDoubleRef => \"DT_DOUBLE_REF\",\n            DataType::DtInt32Ref => \"DT_INT32_REF\",\n            DataType::DtUint8Ref => \"DT_UINT8_REF\",\n            DataType::DtInt16Ref => \"DT_INT16_REF\",\n            DataType::DtInt8Ref => \"DT_INT8_REF\",\n            DataType::DtStringRef => \"DT_STRING_REF\",\n            DataType::DtComplex64Ref => \"DT_COMPLEX64_REF\",\n            DataType::DtInt64Ref => \"DT_INT64_REF\",\n            DataType::DtBoolRef => \"DT_BOOL_REF\",\n            DataType::DtQint8Ref => \"DT_QINT8_REF\",\n            DataType::DtQuint8Ref => \"DT_QUINT8_REF\",\n            DataType::DtQint32Ref => \"DT_QINT32_REF\",\n            DataType::DtBfloat16Ref => \"DT_BFLOAT16_REF\",\n            DataType::DtQint16Ref => \"DT_QINT16_REF\",\n            DataType::DtQuint16Ref => \"DT_QUINT16_REF\",\n            DataType::DtUint16Ref => \"DT_UINT16_REF\",\n            DataType::DtComplex128Ref => \"DT_COMPLEX128_REF\",\n            DataType::DtHalfRef => \"DT_HALF_REF\",\n            DataType::DtResourceRef => \"DT_RESOURCE_REF\",\n            DataType::DtVariantRef => \"DT_VARIANT_REF\",\n            DataType::DtUint32Ref => \"DT_UINT32_REF\",\n            DataType::DtUint64Ref => \"DT_UINT64_REF\",\n        }\n    }\n}\n/// Protocol buffer representing a tensor.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TensorProto {\n    #[prost(enumeration=\"DataType\", tag=\"1\")]\n    pub dtype: i32,\n    /// Shape of the tensor.  TODO(touts): sort out the 0-rank issues.\n    #[prost(message, optional, tag=\"2\")]\n    pub tensor_shape: ::core::option::Option<TensorShapeProto>,\n    // Only one of the representations below is set, one of \"tensor_contents\" and\n    // the \"xxx_val\" attributes.  We are not using oneof because as oneofs cannot\n    // contain repeated fields it would require another extra set of messages.\n\n    /// Version number.\n    ///\n    /// In version 0, if the \"repeated xxx\" representations contain only one\n    /// element, that element is repeated to fill the shape.  This makes it easy\n    /// to represent a constant Tensor with a single value.\n    #[prost(int32, tag=\"3\")]\n    pub version_number: i32,\n    /// Serialized raw tensor content from either Tensor::AsProtoTensorContent or\n    /// memcpy in tensorflow::grpc::EncodeTensorToByteBuffer. This representation\n    /// can be used for all tensor types. The purpose of this representation is to\n    /// reduce serialization overhead during RPC call by avoiding serialization of\n    /// many repeated small items.\n    #[prost(bytes=\"vec\", tag=\"4\")]\n    pub tensor_content: ::prost::alloc::vec::Vec<u8>,\n    // Type specific representations that make it easy to create tensor protos in\n    // all languages.  Only the representation corresponding to \"dtype\" can\n    // be set.  The values hold the flattened representation of the tensor in\n    // row major order.\n\n    /// DT_HALF, DT_BFLOAT16. Note that since protobuf has no int16 type, we'll\n    /// have some pointless zero padding for each value here.\n    #[prost(int32, repeated, tag=\"13\")]\n    pub half_val: ::prost::alloc::vec::Vec<i32>,\n    /// DT_FLOAT.\n    #[prost(float, repeated, tag=\"5\")]\n    pub float_val: ::prost::alloc::vec::Vec<f32>,\n    /// DT_DOUBLE.\n    #[prost(double, repeated, tag=\"6\")]\n    pub double_val: ::prost::alloc::vec::Vec<f64>,\n    /// DT_INT32, DT_INT16, DT_INT8, DT_UINT8.\n    #[prost(int32, repeated, tag=\"7\")]\n    pub int_val: ::prost::alloc::vec::Vec<i32>,\n    /// DT_STRING\n    #[prost(bytes=\"vec\", repeated, tag=\"8\")]\n    pub string_val: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,\n    /// DT_COMPLEX64. scomplex_val(2*i) and scomplex_val(2*i+1) are real\n    /// and imaginary parts of i-th single precision complex.\n    #[prost(float, repeated, tag=\"9\")]\n    pub scomplex_val: ::prost::alloc::vec::Vec<f32>,\n    /// DT_INT64\n    #[prost(int64, repeated, tag=\"10\")]\n    pub int64_val: ::prost::alloc::vec::Vec<i64>,\n    /// DT_BOOL\n    #[prost(bool, repeated, tag=\"11\")]\n    pub bool_val: ::prost::alloc::vec::Vec<bool>,\n    /// DT_COMPLEX128. dcomplex_val(2*i) and dcomplex_val(2*i+1) are real\n    /// and imaginary parts of i-th double precision complex.\n    #[prost(double, repeated, tag=\"12\")]\n    pub dcomplex_val: ::prost::alloc::vec::Vec<f64>,\n    /// DT_RESOURCE\n    #[prost(message, repeated, tag=\"14\")]\n    pub resource_handle_val: ::prost::alloc::vec::Vec<ResourceHandleProto>,\n    /// DT_VARIANT\n    #[prost(message, repeated, tag=\"15\")]\n    pub variant_val: ::prost::alloc::vec::Vec<VariantTensorDataProto>,\n    /// DT_UINT32\n    #[prost(uint32, repeated, tag=\"16\")]\n    pub uint32_val: ::prost::alloc::vec::Vec<u32>,\n    /// DT_UINT64\n    #[prost(uint64, repeated, tag=\"17\")]\n    pub uint64_val: ::prost::alloc::vec::Vec<u64>,\n}\n/// Protocol buffer representing the serialization format of DT_VARIANT tensors.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct VariantTensorDataProto {\n    /// Name of the type of objects being serialized.\n    #[prost(string, tag=\"1\")]\n    pub type_name: ::prost::alloc::string::String,\n    /// Portions of the object that are not Tensors.\n    #[prost(bytes=\"vec\", tag=\"2\")]\n    pub metadata: ::prost::alloc::vec::Vec<u8>,\n    /// Tensors contained within objects being serialized.\n    #[prost(message, repeated, tag=\"3\")]\n    pub tensors: ::prost::alloc::vec::Vec<TensorProto>,\n}\n/// Protocol buffer representing the value for an attr used to configure an Op.\n/// Comment indicates the corresponding attr type.  Only the field matching the\n/// attr type may be filled.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct AttrValue {\n    #[prost(oneof=\"attr_value::Value\", tags=\"2, 3, 4, 5, 6, 7, 8, 1, 10, 9\")]\n    pub value: ::core::option::Option<attr_value::Value>,\n}\n/// Nested message and enum types in `AttrValue`.\npub mod attr_value {\n    /// LINT.IfChange\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct ListValue {\n        /// \"list(string)\"\n        #[prost(bytes=\"vec\", repeated, tag=\"2\")]\n        pub s: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,\n        /// \"list(int)\"\n        #[prost(int64, repeated, tag=\"3\")]\n        pub i: ::prost::alloc::vec::Vec<i64>,\n        /// \"list(float)\"\n        #[prost(float, repeated, tag=\"4\")]\n        pub f: ::prost::alloc::vec::Vec<f32>,\n        /// \"list(bool)\"\n        #[prost(bool, repeated, tag=\"5\")]\n        pub b: ::prost::alloc::vec::Vec<bool>,\n        /// \"list(type)\"\n        #[prost(enumeration=\"super::DataType\", repeated, tag=\"6\")]\n        pub r#type: ::prost::alloc::vec::Vec<i32>,\n        /// \"list(shape)\"\n        #[prost(message, repeated, tag=\"7\")]\n        pub shape: ::prost::alloc::vec::Vec<super::TensorShapeProto>,\n        /// \"list(tensor)\"\n        #[prost(message, repeated, tag=\"8\")]\n        pub tensor: ::prost::alloc::vec::Vec<super::TensorProto>,\n        /// \"list(attr)\"\n        #[prost(message, repeated, tag=\"9\")]\n        pub func: ::prost::alloc::vec::Vec<super::NameAttrList>,\n    }\n    #[derive(Clone, PartialEq, ::prost::Oneof)]\n    pub enum Value {\n        /// \"string\"\n        #[prost(bytes, tag=\"2\")]\n        S(::prost::alloc::vec::Vec<u8>),\n        /// \"int\"\n        #[prost(int64, tag=\"3\")]\n        I(i64),\n        /// \"float\"\n        #[prost(float, tag=\"4\")]\n        F(f32),\n        /// \"bool\"\n        #[prost(bool, tag=\"5\")]\n        B(bool),\n        /// \"type\"\n        #[prost(enumeration=\"super::DataType\", tag=\"6\")]\n        Type(i32),\n        /// \"shape\"\n        #[prost(message, tag=\"7\")]\n        Shape(super::TensorShapeProto),\n        /// \"tensor\"\n        #[prost(message, tag=\"8\")]\n        Tensor(super::TensorProto),\n        /// any \"list(...)\"\n        #[prost(message, tag=\"1\")]\n        List(ListValue),\n        /// \"func\" represents a function. func.name is a function's name or\n        /// a primitive op's name. func.attr.first is the name of an attr\n        /// defined for that function. func.attr.second is the value for\n        /// that attr in the instantiation.\n        #[prost(message, tag=\"10\")]\n        Func(super::NameAttrList),\n        /// This is a placeholder only used in nodes defined inside a\n        /// function.  It indicates the attr value will be supplied when\n        /// the function is instantiated.  For example, let us suppose a\n        /// node \"N\" in function \"FN\". \"N\" has an attr \"A\" with value\n        /// placeholder = \"foo\". When FN is instantiated with attr \"foo\"\n        /// set to \"bar\", the instantiated node N's attr A will have been\n        /// given the value \"bar\".\n        #[prost(string, tag=\"9\")]\n        Placeholder(::prost::alloc::string::String),\n    }\n}\n/// A list of attr names and their values. The whole list is attached\n/// with a string name.  E.g., MatMul\\[T=float\\].\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct NameAttrList {\n    #[prost(string, tag=\"1\")]\n    pub name: ::prost::alloc::string::String,\n    #[prost(map=\"string, message\", tag=\"2\")]\n    pub attr: ::std::collections::HashMap<::prost::alloc::string::String, AttrValue>,\n}\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct NodeDef {\n    /// The name given to this operator. Used for naming inputs,\n    /// logging, visualization, etc.  Unique within a single GraphDef.\n    /// Must match the regexp \"\\[A-Za-z0-9.][A-Za-z0-9_./\\]*\".\n    #[prost(string, tag=\"1\")]\n    pub name: ::prost::alloc::string::String,\n    /// The operation name.  There may be custom parameters in attrs.\n    /// Op names starting with an underscore are reserved for internal use.\n    #[prost(string, tag=\"2\")]\n    pub op: ::prost::alloc::string::String,\n    /// Each input is \"node:src_output\" with \"node\" being a string name and\n    /// \"src_output\" indicating which output tensor to use from \"node\". If\n    /// \"src_output\" is 0 the \":0\" suffix can be omitted.  Regular inputs\n    /// may optionally be followed by control inputs that have the format\n    /// \"^node\".\n    #[prost(string, repeated, tag=\"3\")]\n    pub input: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n    /// A (possibly partial) specification for the device on which this\n    /// node should be placed.\n    /// The expected syntax for this string is as follows:\n    ///\n    /// DEVICE_SPEC ::= PARTIAL_SPEC\n    ///\n    /// PARTIAL_SPEC ::= (\"/\" CONSTRAINT) *\n    /// CONSTRAINT ::= (\"job:\" JOB_NAME)\n    ///               | (\"replica:\" \\[1-9][0-9\\]*)\n    ///               | (\"task:\" \\[1-9][0-9\\]*)\n    ///               | (\"device:\" \\[A-Za-z\\]* \":\" (\\[1-9][0-9\\]* | \"*\") )\n    ///\n    /// Valid values for this string include:\n    /// * \"/job:worker/replica:0/task:1/device:GPU:3\"  (full specification)\n    /// * \"/job:worker/device:GPU:3\"                   (partial specification)\n    /// * \"\"                                    (no specification)\n    ///\n    /// If the constraints do not resolve to a single device (or if this\n    /// field is empty or not present), the runtime will attempt to\n    /// choose a device automatically.\n    #[prost(string, tag=\"4\")]\n    pub device: ::prost::alloc::string::String,\n    /// Operation-specific graph-construction-time configuration.\n    /// Note that this should include all attrs defined in the\n    /// corresponding OpDef, including those with a value matching\n    /// the default -- this allows the default to change and makes\n    /// NodeDefs easier to interpret on their own.  However, if\n    /// an attr with a default is not specified in this list, the\n    /// default will be used.\n    /// The \"names\" (keys) must match the regexp \"\\[a-z][a-z0-9_\\]+\" (and\n    /// one of the names from the corresponding OpDef's attr field).\n    /// The values must have a type matching the corresponding OpDef\n    /// attr's type field.\n    /// TODO(josh11b): Add some examples here showing best practices.\n    #[prost(map=\"string, message\", tag=\"5\")]\n    pub attr: ::std::collections::HashMap<::prost::alloc::string::String, AttrValue>,\n}\n/// Defines an operation. A NodeDef in a GraphDef specifies an Op by\n/// using the \"op\" field which should match the name of a OpDef.\n/// LINT.IfChange\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct OpDef {\n    /// Op names starting with an underscore are reserved for internal use.\n    /// Names should be CamelCase and match the regexp \"\\[A-Z][a-zA-Z0-9_\\]*\".\n    #[prost(string, tag=\"1\")]\n    pub name: ::prost::alloc::string::String,\n    /// Description of the input(s).\n    #[prost(message, repeated, tag=\"2\")]\n    pub input_arg: ::prost::alloc::vec::Vec<op_def::ArgDef>,\n    /// Description of the output(s).\n    #[prost(message, repeated, tag=\"3\")]\n    pub output_arg: ::prost::alloc::vec::Vec<op_def::ArgDef>,\n    #[prost(message, repeated, tag=\"4\")]\n    pub attr: ::prost::alloc::vec::Vec<op_def::AttrDef>,\n    /// Optional deprecation based on GraphDef versions.\n    #[prost(message, optional, tag=\"8\")]\n    pub deprecation: ::core::option::Option<OpDeprecation>,\n    /// One-line human-readable description of what the Op does.\n    #[prost(string, tag=\"5\")]\n    pub summary: ::prost::alloc::string::String,\n    /// Additional, longer human-readable description of what the Op does.\n    #[prost(string, tag=\"6\")]\n    pub description: ::prost::alloc::string::String,\n    // -------------------------------------------------------------------------\n    // Which optimizations this operation can participate in.\n\n    /// True if the operation is commutative (\"op(a,b) == op(b,a)\" for all inputs)\n    #[prost(bool, tag=\"18\")]\n    pub is_commutative: bool,\n    /// If is_aggregate is true, then this operation accepts N >= 2\n    /// inputs and produces 1 output all of the same type.  Should be\n    /// associative and commutative, and produce output with the same\n    /// shape as the input.  The optimizer may replace an aggregate op\n    /// taking input from multiple devices with a tree of aggregate ops\n    /// that aggregate locally within each device (and possibly within\n    /// groups of nearby devices) before communicating.\n    /// TODO(josh11b): Implement that optimization.\n    ///\n    /// for things like add\n    #[prost(bool, tag=\"16\")]\n    pub is_aggregate: bool,\n    // Other optimizations go here, like\n    //    can_alias_input, rewrite_when_output_unused, partitioning_strategy, etc.\n\n    // -------------------------------------------------------------------------\n    // Optimization constraints.\n\n    /// Ops are marked as stateful if their behavior depends on some state beyond\n    /// their input tensors (e.g. variable reading op) or if they have\n    /// a side-effect (e.g. printing or asserting ops). Equivalently, stateless ops\n    /// must always produce the same output for the same input and have\n    /// no side-effects.\n    ///\n    /// By default Ops may be moved between devices.  Stateful ops should\n    /// either not be moved, or should only be moved if that state can also\n    /// be moved (e.g. via some sort of save / restore).\n    /// Stateful ops are guaranteed to never be optimized away by Common\n    /// Subexpression Elimination (CSE).\n    ///\n    /// for things like variables, queue\n    #[prost(bool, tag=\"17\")]\n    pub is_stateful: bool,\n    // -------------------------------------------------------------------------\n    // Non-standard options.\n\n    /// By default, all inputs to an Op must be initialized Tensors.  Ops\n    /// that may initialize tensors for the first time should set this\n    /// field to true, to allow the Op to take an uninitialized Tensor as\n    /// input.\n    ///\n    /// for Assign, etc.\n    #[prost(bool, tag=\"19\")]\n    pub allows_uninitialized_input: bool,\n}\n/// Nested message and enum types in `OpDef`.\npub mod op_def {\n    /// For describing inputs and outputs.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct ArgDef {\n        /// Name for the input/output.  Should match the regexp \"\\[a-z][a-z0-9_\\]*\".\n        #[prost(string, tag=\"1\")]\n        pub name: ::prost::alloc::string::String,\n        /// Human readable description.\n        #[prost(string, tag=\"2\")]\n        pub description: ::prost::alloc::string::String,\n        /// Describes the type of one or more tensors that are accepted/produced\n        /// by this input/output arg.  The only legal combinations are:\n        /// * For a single tensor: either the \"type\" field is set or the\n        ///    \"type_attr\" field is set to the name of an attr with type \"type\".\n        /// * For a sequence of tensors with the same type: the \"number_attr\"\n        ///    field will be set to the name of an attr with type \"int\", and\n        ///    either the \"type\" or \"type_attr\" field will be set as for\n        ///    single tensors.\n        /// * For a sequence of tensors, the \"type_list_attr\" field will be set\n        ///    to the name of an attr with type \"list(type)\".\n        #[prost(enumeration=\"super::DataType\", tag=\"3\")]\n        pub r#type: i32,\n        /// if specified, attr must have type \"type\"\n        #[prost(string, tag=\"4\")]\n        pub type_attr: ::prost::alloc::string::String,\n        /// if specified, attr must have type \"int\"\n        #[prost(string, tag=\"5\")]\n        pub number_attr: ::prost::alloc::string::String,\n        /// If specified, attr must have type \"list(type)\", and none of\n        /// type, type_attr, and number_attr may be specified.\n        #[prost(string, tag=\"6\")]\n        pub type_list_attr: ::prost::alloc::string::String,\n        /// For inputs: if true, the inputs are required to be refs.\n        ///    By default, inputs can be either refs or non-refs.\n        /// For outputs: if true, outputs are refs, otherwise they are not.\n        #[prost(bool, tag=\"16\")]\n        pub is_ref: bool,\n    }\n    /// Description of the graph-construction-time configuration of this\n    /// Op.  That is to say, this describes the attr fields that will\n    /// be specified in the NodeDef.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct AttrDef {\n        /// A descriptive name for the argument.  May be used, e.g. by the\n        /// Python client, as a keyword argument name, and so should match\n        /// the regexp \"\\[a-z][a-z0-9_\\]+\".\n        #[prost(string, tag=\"1\")]\n        pub name: ::prost::alloc::string::String,\n        /// One of the type names from attr_value.proto (\"string\", \"list(string)\",\n        /// \"int\", etc.).\n        #[prost(string, tag=\"2\")]\n        pub r#type: ::prost::alloc::string::String,\n        /// A reasonable default for this attribute if the user does not supply\n        /// a value.  If not specified, the user must supply a value.\n        #[prost(message, optional, tag=\"3\")]\n        pub default_value: ::core::option::Option<super::AttrValue>,\n        /// Human-readable description.\n        #[prost(string, tag=\"4\")]\n        pub description: ::prost::alloc::string::String,\n        // TODO(josh11b): bool is_optional?\n\n        // --- Constraints ---\n        // These constraints are only in effect if specified.  Default is no\n        // constraints.\n\n        /// For type == \"int\", this is a minimum value.  For \"list(___)\"\n        /// types, this is the minimum length.\n        #[prost(bool, tag=\"5\")]\n        pub has_minimum: bool,\n        #[prost(int64, tag=\"6\")]\n        pub minimum: i64,\n        /// The set of allowed values.  Has type that is the \"list\" version\n        /// of the \"type\" field above (uses the \"list\" field of AttrValue).\n        /// If type == \"type\" or \"list(type)\" above, then the \"type\" field\n        /// of \"allowed_values.list\" has the set of allowed DataTypes.\n        /// If type == \"string\" or \"list(string)\", then the \"s\" field of\n        /// \"allowed_values.list\" has the set of allowed strings.\n        #[prost(message, optional, tag=\"7\")]\n        pub allowed_values: ::core::option::Option<super::AttrValue>,\n    }\n}\n/// Information about version-dependent deprecation of an op\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct OpDeprecation {\n    /// First GraphDef version at which the op is disallowed.\n    #[prost(int32, tag=\"1\")]\n    pub version: i32,\n    /// Explanation of why it was deprecated and what to use instead.\n    #[prost(string, tag=\"2\")]\n    pub explanation: ::prost::alloc::string::String,\n}\n/// A collection of OpDefs\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct OpList {\n    #[prost(message, repeated, tag=\"1\")]\n    pub op: ::prost::alloc::vec::Vec<OpDef>,\n}\n/// A library is a set of named functions.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct FunctionDefLibrary {\n    #[prost(message, repeated, tag=\"1\")]\n    pub function: ::prost::alloc::vec::Vec<FunctionDef>,\n    #[prost(message, repeated, tag=\"2\")]\n    pub gradient: ::prost::alloc::vec::Vec<GradientDef>,\n}\n/// A function can be instantiated when the runtime can bind every attr\n/// with a value. When a GraphDef has a call to a function, it must\n/// have binding for every attr defined in the signature.\n///\n/// TODO(zhifengc):\n///    * device spec, etc.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct FunctionDef {\n    /// The definition of the function's name, arguments, return values,\n    /// attrs etc.\n    #[prost(message, optional, tag=\"1\")]\n    pub signature: ::core::option::Option<OpDef>,\n    /// Attributes specific to this function definition.\n    #[prost(map=\"string, message\", tag=\"5\")]\n    pub attr: ::std::collections::HashMap<::prost::alloc::string::String, AttrValue>,\n    // NOTE: field id 2 deleted on Jan 11, 2016, GraphDef version 21.\n\n    // In both of the following fields, there is the need to specify an\n    // output that is used as either the input to another node (in\n    // `node_def`) or as a return value of the function (in `ret`).\n    // Unlike the NodeDefs in GraphDef, we need to be able to specify a\n    // list in some cases (instead of just single outputs).  Also, we\n    // need to be able to deal with lists of unknown length (so the\n    // output index may not be known at function definition time).  So\n    // we use the following format instead:\n    // * \"fun_in\" where \"fun_in\" is the name of a function input arg in\n    //    the `signature` field above.  This represents that input, whether\n    //    it is a single tensor or a list.\n    // * \"fun_in:0\" gives the first element of a function input arg (a\n    //    non-list input is considered a list of length 1 for these\n    //    purposes).\n    // * \"node:out\" where \"node\" is the name of a node in `node_def` and\n    //    \"out\" is the name one of its op's output arguments (the name\n    //    comes from the OpDef of the node's op). This represents that\n    //    node's output, whether it is a single tensor or a list.\n    //    Note: We enforce that an op's output arguments are never\n    //    renamed in the backwards-compatibility test.\n    // * \"node:out:0\" gives the first element of a node output arg (a\n    //    non-list output is considered a list of length 1 for these\n    //    purposes).\n    //\n    // NOT CURRENTLY SUPPORTED (but may be in the future):\n    // * \"node:out:-1\" gives last element in a node output list\n    // * \"node:out:1:\" gives a list with all but the first element in a\n    //    node output list\n    // * \"node:out::-1\" gives a list with all but the last element in a\n    //    node output list\n\n    // The body of the function.  Unlike the NodeDefs in a GraphDef, attrs\n    // may have values of type `placeholder` and the `input` field uses\n    // the \"output\" format above.\n\n    /// By convention, \"op\" in node_def is resolved by consulting with a\n    /// user-defined library first. If not resolved, \"func\" is assumed to\n    /// be a builtin op.\n    #[prost(message, repeated, tag=\"3\")]\n    pub node_def: ::prost::alloc::vec::Vec<NodeDef>,\n    /// A mapping from the output arg names from `signature` to the\n    /// outputs from `node_def` that should be returned by the function.\n    #[prost(map=\"string, string\", tag=\"4\")]\n    pub ret: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>,\n}\n/// GradientDef defines the gradient function of a function defined in\n/// a function library.\n///\n/// A gradient function g (specified by gradient_func) for a function f\n/// (specified by function_name) must follow the following:\n///\n/// The function 'f' must be a numerical function which takes N inputs\n/// and produces M outputs. Its gradient function 'g', which is a\n/// function taking N + M inputs and produces N outputs.\n///\n/// I.e. if we have\n///     (y1, y2, ..., y_M) = f(x1, x2, ..., x_N),\n/// then, g is\n///     (dL/dx1, dL/dx2, ..., dL/dx_N) = g(x1, x2, ..., x_N,\n///                                       dL/dy1, dL/dy2, ..., dL/dy_M),\n/// where L is a scalar-value function of (x1, x2, ..., xN) (e.g., the\n/// loss function). dL/dx_i is the partial derivative of L with respect\n/// to x_i.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct GradientDef {\n    /// The function name.\n    #[prost(string, tag=\"1\")]\n    pub function_name: ::prost::alloc::string::String,\n    /// The gradient function's name.\n    #[prost(string, tag=\"2\")]\n    pub gradient_func: ::prost::alloc::string::String,\n}\n/// Version information for a piece of serialized data\n///\n/// There are different types of versions for each type of data\n/// (GraphDef, etc.), but they all have the same common shape\n/// described here.\n///\n/// Each consumer has \"consumer\" and \"min_producer\" versions (specified\n/// elsewhere).  A consumer is allowed to consume this data if\n///\n///    producer >= min_producer\n///    consumer >= min_consumer\n///    consumer not in bad_consumers\n///\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct VersionDef {\n    /// The version of the code that produced this data.\n    #[prost(int32, tag=\"1\")]\n    pub producer: i32,\n    /// Any consumer below this version is not allowed to consume this data.\n    #[prost(int32, tag=\"2\")]\n    pub min_consumer: i32,\n    /// Specific consumer versions which are disallowed (e.g. due to bugs).\n    #[prost(int32, repeated, tag=\"3\")]\n    pub bad_consumers: ::prost::alloc::vec::Vec<i32>,\n}\n/// Represents the graph of operations\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct GraphDef {\n    #[prost(message, repeated, tag=\"1\")]\n    pub node: ::prost::alloc::vec::Vec<NodeDef>,\n    /// Compatibility versions of the graph.  See core/public/version.h for version\n    /// history.  The GraphDef version is distinct from the TensorFlow version, and\n    /// each release of TensorFlow will support a range of GraphDef versions.\n    #[prost(message, optional, tag=\"4\")]\n    pub versions: ::core::option::Option<VersionDef>,\n    /// Deprecated single version field; use versions above instead.  Since all\n    /// GraphDef changes before \"versions\" was introduced were forward\n    /// compatible, this field is entirely ignored.\n    #[deprecated]\n    #[prost(int32, tag=\"3\")]\n    pub version: i32,\n    /// EXPERIMENTAL. DO NOT USE OR DEPEND ON THIS YET.\n    ///\n    /// \"library\" provides user-defined functions.\n    ///\n    /// Naming:\n    ///    * library.function.name are in a flat namespace.\n    ///      NOTE: We may need to change it to be hierarchical to support\n    ///      different orgs. E.g.,\n    ///      { \"/google/nn\", { ... }},\n    ///      { \"/google/vision\", { ... }}\n    ///      { \"/org_foo/module_bar\", { ... }}\n    ///      map<string, FunctionDefLib> named_lib;\n    ///    * If node\\[i\\].op is the name of one function in \"library\",\n    ///      node\\[i\\] is deemed as a function call. Otherwise, node\\[i\\].op\n    ///      must be a primitive operation supported by the runtime.\n    ///\n    ///\n    /// Function call semantics:\n    ///\n    ///    * The callee may start execution as soon as some of its inputs\n    ///      are ready. The caller may want to use Tuple() mechanism to\n    ///      ensure all inputs are ready in the same time.\n    ///\n    ///    * The consumer of return values may start executing as soon as\n    ///      the return values the consumer depends on are ready.  The\n    ///      consumer may want to use Tuple() mechanism to ensure the\n    ///      consumer does not start until all return values of the callee\n    ///      function are ready.\n    #[prost(message, optional, tag=\"2\")]\n    pub library: ::core::option::Option<FunctionDefLibrary>,\n}\n/// Protocol buffer representing a Variable.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct VariableDef {\n    /// Name of the variable tensor.\n    #[prost(string, tag=\"1\")]\n    pub variable_name: ::prost::alloc::string::String,\n    /// Name of the tensor holding the variable's initial value.\n    #[prost(string, tag=\"6\")]\n    pub initial_value_name: ::prost::alloc::string::String,\n    /// Name of the initializer op.\n    #[prost(string, tag=\"2\")]\n    pub initializer_name: ::prost::alloc::string::String,\n    /// Name of the snapshot tensor.\n    #[prost(string, tag=\"3\")]\n    pub snapshot_name: ::prost::alloc::string::String,\n    /// Support for saving variables as slices of a larger variable.\n    #[prost(message, optional, tag=\"4\")]\n    pub save_slice_info_def: ::core::option::Option<SaveSliceInfoDef>,\n    /// Whether to represent this as a ResourceVariable.\n    #[prost(bool, tag=\"5\")]\n    pub is_resource: bool,\n    /// Whether this variable should be trained.\n    #[prost(bool, tag=\"7\")]\n    pub trainable: bool,\n    /// Indicates when a distributed variable will be synced.\n    #[prost(enumeration=\"VariableSynchronization\", tag=\"8\")]\n    pub synchronization: i32,\n    /// Indicates how a distributed variable will be aggregated.\n    #[prost(enumeration=\"VariableAggregation\", tag=\"9\")]\n    pub aggregation: i32,\n}\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SaveSliceInfoDef {\n    /// Name of the full variable of which this is a slice.\n    #[prost(string, tag=\"1\")]\n    pub full_name: ::prost::alloc::string::String,\n    /// Shape of the full variable.\n    #[prost(int64, repeated, tag=\"2\")]\n    pub full_shape: ::prost::alloc::vec::Vec<i64>,\n    /// Offset of this variable into the full variable.\n    #[prost(int64, repeated, tag=\"3\")]\n    pub var_offset: ::prost::alloc::vec::Vec<i64>,\n    /// Shape of this variable.\n    #[prost(int64, repeated, tag=\"4\")]\n    pub var_shape: ::prost::alloc::vec::Vec<i64>,\n}\n/// Indicates when a distributed variable will be synced.\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n#[repr(i32)]\npub enum VariableSynchronization {\n    /// `AUTO`: Indicates that the synchronization will be determined by the\n    /// current `DistributionStrategy` (eg. With `MirroredStrategy` this would be\n    /// `ON_WRITE`).\n    Auto = 0,\n    /// `NONE`: Indicates that there will only be one copy of the variable, so\n    /// there is no need to sync.\n    None = 1,\n    /// `ON_WRITE`: Indicates that the variable will be updated across devices\n    /// every time it is written.\n    OnWrite = 2,\n    /// `ON_READ`: Indicates that the variable will be aggregated across devices\n    /// when it is read (eg. when checkpointing or when evaluating an op that uses\n    /// the variable).\n    OnRead = 3,\n}\nimpl VariableSynchronization {\n    /// String value of the enum field names used in the ProtoBuf definition.\n    ///\n    /// The values are not transformed in any way and thus are considered stable\n    /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n    pub fn as_str_name(&self) -> &'static str {\n        match self {\n            VariableSynchronization::Auto => \"VARIABLE_SYNCHRONIZATION_AUTO\",\n            VariableSynchronization::None => \"VARIABLE_SYNCHRONIZATION_NONE\",\n            VariableSynchronization::OnWrite => \"VARIABLE_SYNCHRONIZATION_ON_WRITE\",\n            VariableSynchronization::OnRead => \"VARIABLE_SYNCHRONIZATION_ON_READ\",\n        }\n    }\n}\n/// Indicates how a distributed variable will be aggregated.\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n#[repr(i32)]\npub enum VariableAggregation {\n    /// `NONE`: This is the default, giving an error if you use a\n    /// variable-update operation with multiple replicas.\n    None = 0,\n    /// `SUM`: Add the updates across replicas.\n    Sum = 1,\n    /// `MEAN`: Take the arithmetic mean (\"average\") of the updates across\n    /// replicas.\n    Mean = 2,\n    /// `ONLY_FIRST_REPLICA`: This is for when every replica is performing the same\n    /// update, but we only want to perform the update once. Used, e.g., for the\n    /// global step counter.\n    OnlyFirstReplica = 3,\n}\nimpl VariableAggregation {\n    /// String value of the enum field names used in the ProtoBuf definition.\n    ///\n    /// The values are not transformed in any way and thus are considered stable\n    /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n    pub fn as_str_name(&self) -> &'static str {\n        match self {\n            VariableAggregation::None => \"VARIABLE_AGGREGATION_NONE\",\n            VariableAggregation::Sum => \"VARIABLE_AGGREGATION_SUM\",\n            VariableAggregation::Mean => \"VARIABLE_AGGREGATION_MEAN\",\n            VariableAggregation::OnlyFirstReplica => \"VARIABLE_AGGREGATION_ONLY_FIRST_REPLICA\",\n        }\n    }\n}\n// A TensorBundle addition which saves extra information about the objects which\n// own variables, allowing for more robust checkpoint loading into modified\n// programs.\n\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TrackableObjectGraph {\n    #[prost(message, repeated, tag=\"1\")]\n    pub nodes: ::prost::alloc::vec::Vec<trackable_object_graph::TrackableObject>,\n}\n/// Nested message and enum types in `TrackableObjectGraph`.\npub mod trackable_object_graph {\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct TrackableObject {\n        /// Objects which this object depends on.\n        #[prost(message, repeated, tag=\"1\")]\n        pub children: ::prost::alloc::vec::Vec<trackable_object::ObjectReference>,\n        /// Serialized data specific to this object.\n        #[prost(message, repeated, tag=\"2\")]\n        pub attributes: ::prost::alloc::vec::Vec<trackable_object::SerializedTensor>,\n        /// Slot variables owned by this object.\n        #[prost(message, repeated, tag=\"3\")]\n        pub slot_variables: ::prost::alloc::vec::Vec<trackable_object::SlotVariableReference>,\n    }\n    /// Nested message and enum types in `TrackableObject`.\n    pub mod trackable_object {\n        #[derive(Clone, PartialEq, ::prost::Message)]\n        pub struct ObjectReference {\n            /// An index into `TrackableObjectGraph.nodes`, indicating the object\n            /// being referenced.\n            #[prost(int32, tag=\"1\")]\n            pub node_id: i32,\n            /// A user-provided name for the edge.\n            #[prost(string, tag=\"2\")]\n            pub local_name: ::prost::alloc::string::String,\n        }\n        #[derive(Clone, PartialEq, ::prost::Message)]\n        pub struct SerializedTensor {\n            /// A name for the Tensor. Simple variables have only one\n            /// `SerializedTensor` named \"VARIABLE_VALUE\" by convention. This value may\n            /// be restored on object creation as an optimization.\n            #[prost(string, tag=\"1\")]\n            pub name: ::prost::alloc::string::String,\n            /// The full name of the variable/tensor, if applicable. Used to allow\n            /// name-based loading of checkpoints which were saved using an\n            /// object-based API. Should match the checkpoint key which would have been\n            /// assigned by tf.train.Saver.\n            #[prost(string, tag=\"2\")]\n            pub full_name: ::prost::alloc::string::String,\n            /// The generated name of the Tensor in the checkpoint.\n            #[prost(string, tag=\"3\")]\n            pub checkpoint_key: ::prost::alloc::string::String,\n            /// Whether checkpoints should be considered as matching even without this\n            /// value restored. Used for non-critical values which don't affect the\n            /// TensorFlow graph, such as layer configurations.\n            #[prost(bool, tag=\"4\")]\n            pub optional_restore: bool,\n        }\n        #[derive(Clone, PartialEq, ::prost::Message)]\n        pub struct SlotVariableReference {\n            /// An index into `TrackableObjectGraph.nodes`, indicating the\n            /// variable object this slot was created for.\n            #[prost(int32, tag=\"1\")]\n            pub original_variable_node_id: i32,\n            /// The name of the slot (e.g. \"m\"/\"v\").\n            #[prost(string, tag=\"2\")]\n            pub slot_name: ::prost::alloc::string::String,\n            /// An index into `TrackableObjectGraph.nodes`, indicating the\n            /// `Object` with the value of the slot variable.\n            #[prost(int32, tag=\"3\")]\n            pub slot_variable_node_id: i32,\n        }\n    }\n}\n/// `StructuredValue` represents a dynamically typed value representing various\n/// data structures that are inspired by Python data structures typically used in\n/// TensorFlow functions as inputs and outputs.\n///\n/// For example when saving a Layer there may be a `training` argument. If the\n/// user passes a boolean True/False, that switches between two concrete\n/// TensorFlow functions. In order to switch between them in the same way after\n/// loading the SavedModel, we need to represent \"True\" and \"False\".\n///\n/// A more advanced example might be a function which takes a list of\n/// dictionaries mapping from strings to Tensors. In order to map from\n/// user-specified arguments `[{\"a\": tf.constant(1.)}, {\"q\": tf.constant(3.)}]`\n/// after load to the right saved TensorFlow function, we need to represent the\n/// nested structure and the strings, recording that we have a trace for anything\n/// matching `[{\"a\": tf.TensorSpec(None, tf.float32)}, {\"q\": tf.TensorSpec([],\n/// tf.float64)}]` as an example.\n///\n/// Likewise functions may return nested structures of Tensors, for example\n/// returning a dictionary mapping from strings to Tensors. In order for the\n/// loaded function to return the same structure we need to serialize it.\n///\n/// This is an ergonomic aid for working with loaded SavedModels, not a promise\n/// to serialize all possible function signatures. For example we do not expect\n/// to pickle generic Python objects, and ideally we'd stay language-agnostic.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct StructuredValue {\n    /// The kind of value.\n    #[prost(oneof=\"structured_value::Kind\", tags=\"1, 11, 12, 13, 14, 31, 32, 33, 34, 51, 52, 53, 54\")]\n    pub kind: ::core::option::Option<structured_value::Kind>,\n}\n/// Nested message and enum types in `StructuredValue`.\npub mod structured_value {\n    /// The kind of value.\n    #[derive(Clone, PartialEq, ::prost::Oneof)]\n    pub enum Kind {\n        /// Represents None.\n        #[prost(message, tag=\"1\")]\n        NoneValue(super::NoneValue),\n        /// Represents a double-precision floating-point value (a Python `float`).\n        #[prost(double, tag=\"11\")]\n        Float64Value(f64),\n        /// Represents a signed integer value, limited to 64 bits.\n        /// Larger values from Python's arbitrary-precision integers are unsupported.\n        #[prost(sint64, tag=\"12\")]\n        Int64Value(i64),\n        /// Represents a string of Unicode characters stored in a Python `str`.\n        /// In Python 3, this is exactly what type `str` is.\n        /// In Python 2, this is the UTF-8 encoding of the characters.\n        /// For strings with ASCII characters only (as often used in TensorFlow code)\n        /// there is effectively no difference between the language versions.\n        /// The obsolescent `unicode` type of Python 2 is not supported here.\n        #[prost(string, tag=\"13\")]\n        StringValue(::prost::alloc::string::String),\n        /// Represents a boolean value.\n        #[prost(bool, tag=\"14\")]\n        BoolValue(bool),\n        /// Represents a TensorShape.\n        #[prost(message, tag=\"31\")]\n        TensorShapeValue(super::TensorShapeProto),\n        /// Represents an enum value for dtype.\n        #[prost(enumeration=\"super::DataType\", tag=\"32\")]\n        TensorDtypeValue(i32),\n        /// Represents a value for tf.TensorSpec.\n        #[prost(message, tag=\"33\")]\n        TensorSpecValue(super::TensorSpecProto),\n        /// Represents a value for tf.TypeSpec.\n        #[prost(message, tag=\"34\")]\n        TypeSpecValue(::prost::alloc::boxed::Box<super::TypeSpecProto>),\n        /// Represents a list of `Value`.\n        #[prost(message, tag=\"51\")]\n        ListValue(super::ListValue),\n        /// Represents a tuple of `Value`.\n        #[prost(message, tag=\"52\")]\n        TupleValue(super::TupleValue),\n        /// Represents a dict `Value`.\n        #[prost(message, tag=\"53\")]\n        DictValue(super::DictValue),\n        /// Represents Python's namedtuple.\n        #[prost(message, tag=\"54\")]\n        NamedTupleValue(super::NamedTupleValue),\n    }\n}\n/// Represents None.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct NoneValue {\n}\n/// Represents a Python list.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct ListValue {\n    #[prost(message, repeated, tag=\"1\")]\n    pub values: ::prost::alloc::vec::Vec<StructuredValue>,\n}\n/// Represents a Python tuple.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TupleValue {\n    #[prost(message, repeated, tag=\"1\")]\n    pub values: ::prost::alloc::vec::Vec<StructuredValue>,\n}\n/// Represents a Python dict keyed by `str`.\n/// The comment on Unicode from Value.string_value applies analogously.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct DictValue {\n    #[prost(map=\"string, message\", tag=\"1\")]\n    pub fields: ::std::collections::HashMap<::prost::alloc::string::String, StructuredValue>,\n}\n/// Represents a (key, value) pair.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct PairValue {\n    #[prost(string, tag=\"1\")]\n    pub key: ::prost::alloc::string::String,\n    #[prost(message, optional, tag=\"2\")]\n    pub value: ::core::option::Option<StructuredValue>,\n}\n/// Represents Python's namedtuple.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct NamedTupleValue {\n    #[prost(string, tag=\"1\")]\n    pub name: ::prost::alloc::string::String,\n    #[prost(message, repeated, tag=\"2\")]\n    pub values: ::prost::alloc::vec::Vec<PairValue>,\n}\n/// A protobuf to tf.TensorSpec.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TensorSpecProto {\n    #[prost(string, tag=\"1\")]\n    pub name: ::prost::alloc::string::String,\n    #[prost(message, optional, tag=\"2\")]\n    pub shape: ::core::option::Option<TensorShapeProto>,\n    #[prost(enumeration=\"DataType\", tag=\"3\")]\n    pub dtype: i32,\n}\n/// Represents a tf.TypeSpec\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TypeSpecProto {\n    #[prost(enumeration=\"type_spec_proto::TypeSpecClass\", tag=\"1\")]\n    pub type_spec_class: i32,\n    /// The value returned by TypeSpec._serialize().\n    #[prost(message, optional, boxed, tag=\"2\")]\n    pub type_state: ::core::option::Option<::prost::alloc::boxed::Box<StructuredValue>>,\n    /// This is currently redundant with the type_spec_class enum, and is only\n    /// used for error reporting.  In particular, if you use an older binary to\n    /// load a newer model, and the model uses a TypeSpecClass that the older\n    /// binary doesn't support, then this lets us display a useful error message.\n    #[prost(string, tag=\"3\")]\n    pub type_spec_class_name: ::prost::alloc::string::String,\n}\n/// Nested message and enum types in `TypeSpecProto`.\npub mod type_spec_proto {\n    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n    #[repr(i32)]\n    pub enum TypeSpecClass {\n        Unknown = 0,\n        /// tf.SparseTensorSpec\n        SparseTensorSpec = 1,\n        /// tf.IndexedSlicesSpec\n        IndexedSlicesSpec = 2,\n        /// tf.RaggedTensorSpec\n        RaggedTensorSpec = 3,\n        /// tf.TensorArraySpec\n        TensorArraySpec = 4,\n        /// tf.data.DatasetSpec\n        DataDatasetSpec = 5,\n        /// IteratorSpec from data/ops/iterator_ops.py\n        DataIteratorSpec = 6,\n        /// tf.OptionalSpec\n        OptionalSpec = 7,\n        /// PerReplicaSpec from distribute/values.py\n        PerReplicaSpec = 8,\n        /// tf.VariableSpec\n        VariableSpec = 9,\n    }\n    impl TypeSpecClass {\n        /// String value of the enum field names used in the ProtoBuf definition.\n        ///\n        /// The values are not transformed in any way and thus are considered stable\n        /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n        pub fn as_str_name(&self) -> &'static str {\n            match self {\n                TypeSpecClass::Unknown => \"UNKNOWN\",\n                TypeSpecClass::SparseTensorSpec => \"SPARSE_TENSOR_SPEC\",\n                TypeSpecClass::IndexedSlicesSpec => \"INDEXED_SLICES_SPEC\",\n                TypeSpecClass::RaggedTensorSpec => \"RAGGED_TENSOR_SPEC\",\n                TypeSpecClass::TensorArraySpec => \"TENSOR_ARRAY_SPEC\",\n                TypeSpecClass::DataDatasetSpec => \"DATA_DATASET_SPEC\",\n                TypeSpecClass::DataIteratorSpec => \"DATA_ITERATOR_SPEC\",\n                TypeSpecClass::OptionalSpec => \"OPTIONAL_SPEC\",\n                TypeSpecClass::PerReplicaSpec => \"PER_REPLICA_SPEC\",\n                TypeSpecClass::VariableSpec => \"VARIABLE_SPEC\",\n            }\n        }\n    }\n}\n// A SavedObjectGraph is part of object-based SavedModels in TF 2.0. It\n// describes the directed graph of Python objects (or equivalent in other\n// languages) that make up a model, with nodes\\[0\\] at the root.\n\n// SavedObjectGraph shares some structure with TrackableObjectGraph, but\n// SavedObjectGraph belongs to the MetaGraph and contains pointers to functions\n// and type information, while TrackableObjectGraph lives in the checkpoint\n// and contains pointers only to variable values.\n\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedObjectGraph {\n    /// Flattened list of objects in the object graph.\n    ///\n    /// The position of the object in this list indicates its id.\n    /// Nodes\\[0\\] is considered the root node.\n    #[prost(message, repeated, tag=\"1\")]\n    pub nodes: ::prost::alloc::vec::Vec<SavedObject>,\n    /// Information about captures and output structures in concrete functions.\n    /// Referenced from SavedBareConcreteFunction and SavedFunction.\n    #[prost(map=\"string, message\", tag=\"2\")]\n    pub concrete_functions: ::std::collections::HashMap<::prost::alloc::string::String, SavedConcreteFunction>,\n}\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedObject {\n    /// Objects which this object depends on: named edges in the dependency\n    /// graph.\n    ///\n    /// Note: currently only valid if kind == \"user_object\".\n    #[prost(message, repeated, tag=\"1\")]\n    pub children: ::prost::alloc::vec::Vec<trackable_object_graph::trackable_object::ObjectReference>,\n    /// Slot variables owned by this object. This describes the three-way\n    /// (optimizer, variable, slot variable) relationship; none of the three\n    /// depend on the others directly.\n    ///\n    /// Note: currently only valid if kind == \"user_object\".\n    #[prost(message, repeated, tag=\"3\")]\n    pub slot_variables: ::prost::alloc::vec::Vec<trackable_object_graph::trackable_object::SlotVariableReference>,\n    #[prost(oneof=\"saved_object::Kind\", tags=\"4, 5, 6, 7, 8, 9, 10\")]\n    pub kind: ::core::option::Option<saved_object::Kind>,\n}\n/// Nested message and enum types in `SavedObject`.\npub mod saved_object {\n    #[derive(Clone, PartialEq, ::prost::Oneof)]\n    pub enum Kind {\n        #[prost(message, tag=\"4\")]\n        UserObject(super::SavedUserObject),\n        #[prost(message, tag=\"5\")]\n        Asset(super::SavedAsset),\n        #[prost(message, tag=\"6\")]\n        Function(super::SavedFunction),\n        #[prost(message, tag=\"7\")]\n        Variable(super::SavedVariable),\n        #[prost(message, tag=\"8\")]\n        BareConcreteFunction(super::SavedBareConcreteFunction),\n        #[prost(message, tag=\"9\")]\n        Constant(super::SavedConstant),\n        #[prost(message, tag=\"10\")]\n        Resource(super::SavedResource),\n    }\n}\n/// A SavedUserObject is an object (in the object-oriented language of the\n/// TensorFlow program) of some user- or framework-defined class other than\n/// those handled specifically by the other kinds of SavedObjects.\n///\n/// This object cannot be evaluated as a tensor, and therefore cannot be bound\n/// to an input of a function.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedUserObject {\n    /// Corresponds to a registration of the type to use in the loading program.\n    #[prost(string, tag=\"1\")]\n    pub identifier: ::prost::alloc::string::String,\n    /// Version information from the producer of this SavedUserObject.\n    #[prost(message, optional, tag=\"2\")]\n    pub version: ::core::option::Option<VersionDef>,\n    /// Initialization-related metadata.\n    #[prost(string, tag=\"3\")]\n    pub metadata: ::prost::alloc::string::String,\n}\n/// A SavedAsset points to an asset in the MetaGraph.\n///\n/// When bound to a function this object evaluates to a tensor with the absolute\n/// filename. Users should not depend on a particular part of the filename to\n/// remain stable (e.g. basename could be changed).\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedAsset {\n    /// Index into `MetaGraphDef.asset_file_def[]` that describes the Asset.\n    ///\n    /// Only the field `AssetFileDef.filename` is used. Other fields, such as\n    /// `AssetFileDef.tensor_info`, MUST be ignored.\n    #[prost(int32, tag=\"1\")]\n    pub asset_file_def_index: i32,\n}\n/// A function with multiple signatures, possibly with non-Tensor arguments.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedFunction {\n    #[prost(string, repeated, tag=\"1\")]\n    pub concrete_functions: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n    #[prost(message, optional, tag=\"2\")]\n    pub function_spec: ::core::option::Option<FunctionSpec>,\n}\n/// Stores low-level information about a concrete function. Referenced in either\n/// a SavedFunction or a SavedBareConcreteFunction.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedConcreteFunction {\n    /// Bound inputs to the function. The SavedObjects identified by the node ids\n    /// given here are appended as extra inputs to the caller-supplied inputs.\n    /// The only types of SavedObjects valid here are SavedVariable, SavedResource\n    /// and SavedAsset.\n    #[prost(int32, repeated, tag=\"2\")]\n    pub bound_inputs: ::prost::alloc::vec::Vec<i32>,\n    /// Input in canonicalized form that was received to create this concrete\n    /// function.\n    #[prost(message, optional, tag=\"3\")]\n    pub canonicalized_input_signature: ::core::option::Option<StructuredValue>,\n    /// Output that was the return value of this function after replacing all\n    /// Tensors with TensorSpecs. This can be an arbitrary nested function and will\n    /// be used to reconstruct the full structure from pure tensors.\n    #[prost(message, optional, tag=\"4\")]\n    pub output_signature: ::core::option::Option<StructuredValue>,\n}\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedBareConcreteFunction {\n    /// Identifies a SavedConcreteFunction.\n    #[prost(string, tag=\"1\")]\n    pub concrete_function_name: ::prost::alloc::string::String,\n    /// A sequence of unique strings, one per Tensor argument.\n    #[prost(string, repeated, tag=\"2\")]\n    pub argument_keywords: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n    /// The prefix of `argument_keywords` which may be identified by position.\n    #[prost(int64, tag=\"3\")]\n    pub allowed_positional_arguments: i64,\n}\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedConstant {\n    /// An Operation name for a ConstantOp in this SavedObjectGraph's MetaGraph.\n    #[prost(string, tag=\"1\")]\n    pub operation: ::prost::alloc::string::String,\n}\n/// Represents a Variable that is initialized by loading the contents from the\n/// checkpoint.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedVariable {\n    #[prost(enumeration=\"DataType\", tag=\"1\")]\n    pub dtype: i32,\n    #[prost(message, optional, tag=\"2\")]\n    pub shape: ::core::option::Option<TensorShapeProto>,\n    #[prost(bool, tag=\"3\")]\n    pub trainable: bool,\n    #[prost(enumeration=\"VariableSynchronization\", tag=\"4\")]\n    pub synchronization: i32,\n    #[prost(enumeration=\"VariableAggregation\", tag=\"5\")]\n    pub aggregation: i32,\n    #[prost(string, tag=\"6\")]\n    pub name: ::prost::alloc::string::String,\n}\n/// Represents `FunctionSpec` used in `Function`. This represents a\n/// function that has been wrapped as a TensorFlow `Function`.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct FunctionSpec {\n    /// Full arg spec from inspect.getfullargspec().\n    #[prost(message, optional, tag=\"1\")]\n    pub fullargspec: ::core::option::Option<StructuredValue>,\n    /// Whether this represents a class method.\n    #[prost(bool, tag=\"2\")]\n    pub is_method: bool,\n    /// The input signature, if specified.\n    #[prost(message, optional, tag=\"5\")]\n    pub input_signature: ::core::option::Option<StructuredValue>,\n}\n/// A SavedResource represents a TF object that holds state during its lifetime.\n/// An object of this type can have a reference to a:\n/// create_resource() and an initialize() function.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedResource {\n    /// A device specification indicating a required placement for the resource\n    /// creation function, e.g. \"CPU\". An empty string allows the user to select a\n    /// device.\n    #[prost(string, tag=\"1\")]\n    pub device: ::prost::alloc::string::String,\n}\n/// Protocol buffer representing the configuration of a Saver.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SaverDef {\n    /// The name of the tensor in which to specify the filename when saving or\n    /// restoring a model checkpoint.\n    #[prost(string, tag=\"1\")]\n    pub filename_tensor_name: ::prost::alloc::string::String,\n    /// The operation to run when saving a model checkpoint.\n    #[prost(string, tag=\"2\")]\n    pub save_tensor_name: ::prost::alloc::string::String,\n    /// The operation to run when restoring a model checkpoint.\n    #[prost(string, tag=\"3\")]\n    pub restore_op_name: ::prost::alloc::string::String,\n    /// Maximum number of checkpoints to keep.  If 0, no checkpoints are deleted.\n    #[prost(int32, tag=\"4\")]\n    pub max_to_keep: i32,\n    /// Shard the save files, one per device that has Variable nodes.\n    #[prost(bool, tag=\"5\")]\n    pub sharded: bool,\n    /// How often to keep an additional checkpoint. If not specified, only the last\n    /// \"max_to_keep\" checkpoints are kept; if specified, in addition to keeping\n    /// the last \"max_to_keep\" checkpoints, an additional checkpoint will be kept\n    /// for every n hours of training.\n    #[prost(float, tag=\"6\")]\n    pub keep_checkpoint_every_n_hours: f32,\n    #[prost(enumeration=\"saver_def::CheckpointFormatVersion\", tag=\"7\")]\n    pub version: i32,\n}\n/// Nested message and enum types in `SaverDef`.\npub mod saver_def {\n    /// A version number that identifies a different on-disk checkpoint format.\n    /// Usually, each subclass of BaseSaverBuilder works with a particular\n    /// version/format.  However, it is possible that the same builder may be\n    /// upgraded to support a newer checkpoint format in the future.\n    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]\n    #[repr(i32)]\n    pub enum CheckpointFormatVersion {\n        /// Internal legacy format.\n        Legacy = 0,\n        /// Deprecated format: tf.Saver() which works with tensorflow::table::Table.\n        V1 = 1,\n        /// Current format: more efficient.\n        V2 = 2,\n    }\n    impl CheckpointFormatVersion {\n        /// String value of the enum field names used in the ProtoBuf definition.\n        ///\n        /// The values are not transformed in any way and thus are considered stable\n        /// (if the ProtoBuf definition does not change) and safe for programmatic use.\n        pub fn as_str_name(&self) -> &'static str {\n            match self {\n                CheckpointFormatVersion::Legacy => \"LEGACY\",\n                CheckpointFormatVersion::V1 => \"V1\",\n                CheckpointFormatVersion::V2 => \"V2\",\n            }\n        }\n    }\n}\n/// NOTE: This protocol buffer is evolving, and will go through revisions in the\n/// coming months.\n///\n/// Protocol buffer containing the following which are necessary to restart\n/// training, run inference. It can be used to serialize/de-serialize memory\n/// objects necessary for running computation in a graph when crossing the\n/// process boundary. It can be used for long term storage of graphs,\n/// cross-language execution of graphs, etc.\n///    MetaInfoDef\n///    GraphDef\n///    SaverDef\n///    CollectionDef\n///    TensorInfo\n///    SignatureDef\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct MetaGraphDef {\n    #[prost(message, optional, tag=\"1\")]\n    pub meta_info_def: ::core::option::Option<meta_graph_def::MetaInfoDef>,\n    /// GraphDef.\n    #[prost(message, optional, tag=\"2\")]\n    pub graph_def: ::core::option::Option<GraphDef>,\n    /// SaverDef.\n    #[prost(message, optional, tag=\"3\")]\n    pub saver_def: ::core::option::Option<SaverDef>,\n    /// collection_def: Map from collection name to collections.\n    /// See CollectionDef section for details.\n    #[prost(map=\"string, message\", tag=\"4\")]\n    pub collection_def: ::std::collections::HashMap<::prost::alloc::string::String, CollectionDef>,\n    /// signature_def: Map from user supplied key for a signature to a single\n    /// SignatureDef.\n    #[prost(map=\"string, message\", tag=\"5\")]\n    pub signature_def: ::std::collections::HashMap<::prost::alloc::string::String, SignatureDef>,\n    /// Asset file def to be used with the defined graph.\n    #[prost(message, repeated, tag=\"6\")]\n    pub asset_file_def: ::prost::alloc::vec::Vec<AssetFileDef>,\n    /// Extra information about the structure of functions and stateful objects.\n    #[prost(message, optional, tag=\"7\")]\n    pub object_graph_def: ::core::option::Option<SavedObjectGraph>,\n}\n/// Nested message and enum types in `MetaGraphDef`.\npub mod meta_graph_def {\n    /// Meta information regarding the graph to be exported.  To be used by users\n    /// of this protocol buffer to encode information regarding their meta graph.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct MetaInfoDef {\n        /// User specified Version string. Can be the name of the model and revision,\n        /// steps this model has been trained to, etc.\n        #[prost(string, tag=\"1\")]\n        pub meta_graph_version: ::prost::alloc::string::String,\n        /// A copy of the OpDefs used by the producer of this graph_def.\n        /// Descriptions and Ops not used in graph_def are stripped out.\n        #[prost(message, optional, tag=\"2\")]\n        pub stripped_op_list: ::core::option::Option<super::OpList>,\n        /// A serialized protobuf. Can be the time this meta graph is created, or\n        /// modified, or name of the model.\n        #[prost(message, optional, tag=\"3\")]\n        pub any_info: ::core::option::Option<::prost_types::Any>,\n        /// User supplied tag(s) on the meta_graph and included graph_def.\n        ///\n        /// MetaGraphDefs should be tagged with their capabilities or use-cases.\n        /// Examples: \"train\", \"serve\", \"gpu\", \"tpu\", etc.\n        /// These tags enable loaders to access the MetaGraph(s) appropriate for a\n        /// specific use-case or runtime environment.\n        #[prost(string, repeated, tag=\"4\")]\n        pub tags: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n        /// The __version__ string of the tensorflow build used to write this graph.\n        /// This will be populated by the framework, which will overwrite any user\n        /// supplied value.\n        #[prost(string, tag=\"5\")]\n        pub tensorflow_version: ::prost::alloc::string::String,\n        /// The __git_version__ string of the tensorflow build used to write this\n        /// graph. This will be populated by the framework, which will overwrite any\n        /// user supplied value.\n        #[prost(string, tag=\"6\")]\n        pub tensorflow_git_version: ::prost::alloc::string::String,\n        /// A flag to denote whether default-valued attrs have been stripped from\n        /// the nodes in this graph_def.\n        #[prost(bool, tag=\"7\")]\n        pub stripped_default_attrs: bool,\n    }\n}\n/// CollectionDef should cover most collections.\n/// To add a user-defined collection, do one of the following:\n/// 1. For simple data types, such as string, int, float:\n///       tf.add_to_collection(\"your_collection_name\", your_simple_value)\n///     strings will be stored as bytes_list.\n///\n/// 2. For Protobuf types, there are three ways to add them:\n///     1) tf.add_to_collection(\"your_collection_name\",\n///          your_proto.SerializeToString())\n///\n///        collection_def {\n///          key: \"user_defined_bytes_collection\"\n///          value {\n///            bytes_list {\n///              value: \"queue_name: \\\"test_queue\\\"\\n\"\n///            }\n///          }\n///        }\n///\n///   or\n///\n///     2) tf.add_to_collection(\"your_collection_name\", str(your_proto))\n///\n///        collection_def {\n///          key: \"user_defined_string_collection\"\n///          value {\n///           bytes_list {\n///              value: \"\\n\\ntest_queue\"\n///            }\n///          }\n///        }\n///\n///   or\n///\n///     3) any_buf = any_pb2.Any()\n///        tf.add_to_collection(\"your_collection_name\",\n///          any_buf.Pack(your_proto))\n///\n///        collection_def {\n///          key: \"user_defined_any_collection\"\n///          value {\n///            any_list {\n///              value {\n///                type_url: \"type.googleapis.com/tensorflow.QueueRunnerDef\"\n///                value: \"\\n\\ntest_queue\"\n///              }\n///            }\n///          }\n///        }\n///\n/// 3. For Python objects, implement to_proto() and from_proto(), and register\n///     them in the following manner:\n///     ops.register_proto_function(\"your_collection_name\",\n///                                 proto_type,\n///                                 to_proto=YourPythonObject.to_proto,\n///                                 from_proto=YourPythonObject.from_proto)\n///     These functions will be invoked to serialize and de-serialize the\n///     collection. For example,\n///     ops.register_proto_function(ops.GraphKeys.GLOBAL_VARIABLES,\n///                                 proto_type=variable_pb2.VariableDef,\n///                                 to_proto=Variable.to_proto,\n///                                 from_proto=Variable.from_proto)\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct CollectionDef {\n    #[prost(oneof=\"collection_def::Kind\", tags=\"1, 2, 3, 4, 5\")]\n    pub kind: ::core::option::Option<collection_def::Kind>,\n}\n/// Nested message and enum types in `CollectionDef`.\npub mod collection_def {\n    /// NodeList is used for collecting nodes in graph. For example\n    /// collection_def {\n    ///    key: \"summaries\"\n    ///    value {\n    ///      node_list {\n    ///        value: \"input_producer/ScalarSummary:0\"\n    ///        value: \"shuffle_batch/ScalarSummary:0\"\n    ///        value: \"ImageSummary:0\"\n    ///      }\n    ///    }\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct NodeList {\n        #[prost(string, repeated, tag=\"1\")]\n        pub value: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,\n    }\n    /// BytesList is used for collecting strings and serialized protobufs. For\n    /// example:\n    /// collection_def {\n    ///    key: \"trainable_variables\"\n    ///    value {\n    ///      bytes_list {\n    ///        value: \"\\n\\017conv1/weights:0\\022\\024conv1/weights/Assign\n    ///               \\032\\024conv1/weights/read:0\"\n    ///        value: \"\\n\\016conv1/biases:0\\022\\023conv1/biases/Assign\\032\n    ///               \\023conv1/biases/read:0\"\n    ///      }\n    ///    }\n    /// }\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct BytesList {\n        #[prost(bytes=\"vec\", repeated, tag=\"1\")]\n        pub value: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,\n    }\n    /// Int64List is used for collecting int, int64 and long values.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct Int64List {\n        #[prost(int64, repeated, tag=\"1\")]\n        pub value: ::prost::alloc::vec::Vec<i64>,\n    }\n    /// FloatList is used for collecting float values.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct FloatList {\n        #[prost(float, repeated, tag=\"1\")]\n        pub value: ::prost::alloc::vec::Vec<f32>,\n    }\n    /// AnyList is used for collecting Any protos.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct AnyList {\n        #[prost(message, repeated, tag=\"1\")]\n        pub value: ::prost::alloc::vec::Vec<::prost_types::Any>,\n    }\n    #[derive(Clone, PartialEq, ::prost::Oneof)]\n    pub enum Kind {\n        #[prost(message, tag=\"1\")]\n        NodeList(NodeList),\n        #[prost(message, tag=\"2\")]\n        BytesList(BytesList),\n        #[prost(message, tag=\"3\")]\n        Int64List(Int64List),\n        #[prost(message, tag=\"4\")]\n        FloatList(FloatList),\n        #[prost(message, tag=\"5\")]\n        AnyList(AnyList),\n    }\n}\n/// Information about a Tensor necessary for feeding or retrieval.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct TensorInfo {\n    #[prost(enumeration=\"DataType\", tag=\"2\")]\n    pub dtype: i32,\n    /// The static shape should be recorded here, to the extent that it can\n    /// be known in advance.  In the case of a SparseTensor, this field describes\n    /// the logical shape of the represented tensor (aka dense_shape).\n    #[prost(message, optional, tag=\"3\")]\n    pub tensor_shape: ::core::option::Option<TensorShapeProto>,\n    #[prost(oneof=\"tensor_info::Encoding\", tags=\"1, 4, 5\")]\n    pub encoding: ::core::option::Option<tensor_info::Encoding>,\n}\n/// Nested message and enum types in `TensorInfo`.\npub mod tensor_info {\n    /// For sparse tensors, The COO encoding stores a triple of values, indices,\n    /// and shape.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct CooSparse {\n        /// The shape of the values Tensor is \\[?\\].  Its dtype must be the dtype of\n        /// the SparseTensor as a whole, given in the enclosing TensorInfo.\n        #[prost(string, tag=\"1\")]\n        pub values_tensor_name: ::prost::alloc::string::String,\n        /// The indices Tensor must have dtype int64 and shape [?, ?].\n        #[prost(string, tag=\"2\")]\n        pub indices_tensor_name: ::prost::alloc::string::String,\n        /// The dynamic logical shape represented by the SparseTensor is recorded in\n        /// the Tensor referenced here.  It must have dtype int64 and shape \\[?\\].\n        #[prost(string, tag=\"3\")]\n        pub dense_shape_tensor_name: ::prost::alloc::string::String,\n    }\n    /// Generic encoding for composite tensors.\n    #[derive(Clone, PartialEq, ::prost::Message)]\n    pub struct CompositeTensor {\n        /// The serialized TypeSpec for the composite tensor.\n        #[prost(message, optional, tag=\"1\")]\n        pub type_spec: ::core::option::Option<super::TypeSpecProto>,\n        /// A TensorInfo for each flattened component tensor.\n        #[prost(message, repeated, tag=\"2\")]\n        pub components: ::prost::alloc::vec::Vec<super::TensorInfo>,\n    }\n    #[derive(Clone, PartialEq, ::prost::Oneof)]\n    pub enum Encoding {\n        /// For dense `Tensor`s, the name of the tensor in the graph.\n        #[prost(string, tag=\"1\")]\n        Name(::prost::alloc::string::String),\n        /// There are many possible encodings of sparse matrices\n        /// (<https://en.wikipedia.org/wiki/Sparse_matrix>).  Currently, TensorFlow\n        /// uses only the COO encoding.  This is supported and documented in the\n        /// SparseTensor Python class.\n        #[prost(message, tag=\"4\")]\n        CooSparse(CooSparse),\n        /// Generic encoding for CompositeTensors.\n        #[prost(message, tag=\"5\")]\n        CompositeTensor(CompositeTensor),\n    }\n}\n/// SignatureDef defines the signature of a computation supported by a TensorFlow\n/// graph.\n///\n/// For example, a model with two loss computations, sharing a single input,\n/// might have the following signature_def map.\n///\n/// Note that across the two SignatureDefs \"loss_A\" and \"loss_B\", the input key,\n/// output key, and method_name are identical, and will be used by system(s) that\n/// implement or rely upon this particular loss method. The output tensor names\n/// differ, demonstrating how different outputs can exist for the same method.\n///\n/// signature_def {\n///    key: \"loss_A\"\n///    value {\n///      inputs {\n///        key: \"input\"\n///        value {\n///          name: \"input:0\"\n///          dtype: DT_STRING\n///          tensor_shape: ...\n///        }\n///      }\n///      outputs {\n///        key: \"loss_output\"\n///        value {\n///          name: \"loss_output_A:0\"\n///          dtype: DT_FLOAT\n///          tensor_shape: ...\n///        }\n///      }\n///    }\n///    ...\n///    method_name: \"some/package/compute_loss\"\n/// }\n/// signature_def {\n///    key: \"loss_B\"\n///    value {\n///      inputs {\n///        key: \"input\"\n///        value {\n///          name: \"input:0\"\n///          dtype: DT_STRING\n///          tensor_shape: ...\n///        }\n///      }\n///      outputs {\n///        key: \"loss_output\"\n///        value {\n///          name: \"loss_output_B:0\"\n///          dtype: DT_FLOAT\n///          tensor_shape: ...\n///        }\n///      }\n///    }\n///    ...\n///    method_name: \"some/package/compute_loss\"\n/// }\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SignatureDef {\n    /// Named input parameters.\n    #[prost(map=\"string, message\", tag=\"1\")]\n    pub inputs: ::std::collections::HashMap<::prost::alloc::string::String, TensorInfo>,\n    /// Named output parameters.\n    #[prost(map=\"string, message\", tag=\"2\")]\n    pub outputs: ::std::collections::HashMap<::prost::alloc::string::String, TensorInfo>,\n    /// Extensible method_name information enabling third-party users to mark a\n    /// SignatureDef as supporting a particular method. This enables producers and\n    /// consumers of SignatureDefs, e.g. a model definition library and a serving\n    /// library to have a clear hand-off regarding the semantics of a computation.\n    ///\n    /// Note that multiple SignatureDefs in a single MetaGraphDef may have the same\n    /// method_name. This is commonly used to support multi-headed computation,\n    /// where a single graph computation may return multiple results.\n    #[prost(string, tag=\"3\")]\n    pub method_name: ::prost::alloc::string::String,\n}\n/// An asset file def for a single file or a set of sharded files with the same\n/// name.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct AssetFileDef {\n    /// The tensor to bind the asset filename to.\n    #[prost(message, optional, tag=\"1\")]\n    pub tensor_info: ::core::option::Option<TensorInfo>,\n    /// The filename within an assets directory. Note: does not include the path\n    /// prefix, i.e. directories. For an asset at /tmp/path/vocab.txt, the filename\n    /// would be \"vocab.txt\".\n    #[prost(string, tag=\"2\")]\n    pub filename: ::prost::alloc::string::String,\n}\n/// SavedModel is the high level serialization format for TensorFlow Models.\n/// See [todo: doc links, similar to session_bundle] for more information.\n#[derive(Clone, PartialEq, ::prost::Message)]\npub struct SavedModel {\n    /// The schema version of the SavedModel instance. Used for versioning when\n    /// making future changes to the specification/implementation. Initial value\n    /// at release will be 1.\n    #[prost(int64, tag=\"1\")]\n    pub saved_model_schema_version: i64,\n    /// One or more MetaGraphs.\n    #[prost(message, repeated, tag=\"2\")]\n    pub meta_graphs: ::prost::alloc::vec::Vec<MetaGraphDef>,\n}\n"
  },
  {
    "path": "tensorflow/src/tensor.rs",
    "content": "use tract_hir::internal::*;\n\nuse crate::tfpb::tensorflow::tensor_shape_proto::Dim;\nuse crate::tfpb::tensorflow::{TensorProto, TensorShapeProto};\n\nuse crate::tfpb::tensorflow::DataType;\nuse std::convert::TryFrom;\n\nimpl TryFrom<DataType> for DatumType {\n    type Error = TractError;\n    fn try_from(t: DataType) -> TractResult<DatumType> {\n        match t {\n            DataType::DtBool => Ok(DatumType::Bool),\n            DataType::DtUint8 => Ok(DatumType::U8),\n            DataType::DtUint16 => Ok(DatumType::U16),\n            DataType::DtUint32 => Ok(DatumType::U32),\n            DataType::DtUint64 => Ok(DatumType::U64),\n            DataType::DtInt8 => Ok(DatumType::I8),\n            DataType::DtInt16 => Ok(DatumType::I16),\n            DataType::DtInt32 => Ok(DatumType::I32),\n            DataType::DtInt64 => Ok(DatumType::I64),\n            DataType::DtHalf => Ok(DatumType::F16),\n            DataType::DtFloat => Ok(DatumType::F32),\n            DataType::DtDouble => Ok(DatumType::F64),\n            DataType::DtString => Ok(DatumType::Blob),\n            _ => Err(format_err!(\"Unknown DatumType {:?}\", t))?,\n        }\n    }\n}\n\nimpl<'a> TryFrom<&'a TensorShapeProto> for TVec<isize> {\n    type Error = TractError;\n    fn try_from(t: &'a TensorShapeProto) -> TractResult<TVec<isize>> {\n        Ok(t.dim.iter().map(|d| d.size as isize).collect::<TVec<_>>())\n    }\n}\n\nimpl<'a> TryFrom<&'a TensorShapeProto> for TVec<usize> {\n    type Error = TractError;\n    fn try_from(t: &'a TensorShapeProto) -> TractResult<TVec<usize>> {\n        if t.dim.iter().any(|d| d.size < 0) {\n            bail!(\"Negative dim found\")\n        }\n        Ok(t.dim.iter().map(|d| d.size as usize).collect::<TVec<_>>())\n    }\n}\n\nimpl TryFrom<DatumType> for DataType {\n    type Error = TractError;\n    fn try_from(dt: DatumType) -> TractResult<DataType> {\n        match dt {\n            DatumType::Bool => Ok(DataType::DtBool),\n            DatumType::U8 => Ok(DataType::DtUint8),\n            DatumType::U16 => Ok(DataType::DtUint16),\n            DatumType::U32 => Ok(DataType::DtUint32),\n            DatumType::U64 => Ok(DataType::DtUint64),\n            DatumType::I8 => Ok(DataType::DtInt8),\n            DatumType::I16 => Ok(DataType::DtInt16),\n            DatumType::I32 => Ok(DataType::DtInt32),\n            DatumType::I64 => Ok(DataType::DtInt64),\n            DatumType::F16 => Ok(DataType::DtHalf),\n            DatumType::F32 => Ok(DataType::DtFloat),\n            DatumType::F64 => Ok(DataType::DtDouble),\n            DatumType::Blob => Ok(DataType::DtString),\n            DatumType::String => Ok(DataType::DtString),\n            DatumType::QI8(_) => Ok(DataType::DtQint8),\n            DatumType::QU8(_) => Ok(DataType::DtQint8),\n            DatumType::QI32(_) => Ok(DataType::DtQint32),\n            _ => bail!(\"DatumType is not translatable in protobuf\"),\n        }\n    }\n}\n\nfn tensor_from_repeated_field<T: Datum>(shape: &[usize], data: Vec<T>) -> TractResult<Tensor> {\n    let t = if data.len() == 1 {\n        tract_ndarray::ArrayD::from_elem(shape, data[0].clone()).into()\n    } else {\n        tract_ndarray::ArrayD::from_shape_vec(shape, data.to_vec())?.into()\n    };\n    Ok(t)\n}\n\nimpl TryFrom<&TensorProto> for Tensor {\n    type Error = TractError;\n    fn try_from(t: &TensorProto) -> TractResult<Tensor> {\n        let dims: TVec<usize> =\n            t.tensor_shape.as_ref().unwrap().dim.iter().map(|x| x.size as _).collect();\n        let rank = dims.len();\n        let content = &t.tensor_content;\n        let dtype = DataType::try_from(t.dtype).unwrap();\n        let mat: Tensor = if content.len() != 0 {\n            unsafe {\n                match dtype {\n                    DataType::DtFloat => Self::from_raw::<f32>(&dims, content)?,\n                    DataType::DtDouble => Self::from_raw::<f64>(&dims, content)?,\n                    DataType::DtInt32 => Self::from_raw::<i32>(&dims, content)?,\n                    DataType::DtInt64 => Self::from_raw::<i64>(&dims, content)?,\n                    _ => unimplemented!(\"missing type (for get_tensor_content) {:?}\", dtype),\n                }\n            }\n        } else {\n            match dtype {\n                DataType::DtInt32 => tensor_from_repeated_field(&dims, t.int_val.to_vec())?,\n                DataType::DtInt64 => tensor_from_repeated_field(&dims, t.int64_val.to_vec())?,\n                DataType::DtFloat => tensor_from_repeated_field(&dims, t.float_val.to_vec())?,\n                DataType::DtDouble => tensor_from_repeated_field(&dims, t.double_val.to_vec())?,\n                DataType::DtString => {\n                    let strings = t\n                        .string_val\n                        .iter()\n                        .map(|s| Blob::try_from(&**s))\n                        .collect::<TractResult<Vec<Blob>>>()?;\n                    tensor_from_repeated_field(&dims, strings)?\n                }\n                _ => unimplemented!(\"missing type (for _val()) {:?}\", t.dtype),\n            }\n        };\n        assert_eq!(rank, mat.shape().len());\n        Ok(mat)\n    }\n}\n\nfn empty_tensor_proto() -> TensorProto {\n    TensorProto {\n        dtype: 0,\n        tensor_shape: None,\n        version_number: 0,\n        tensor_content: vec![],\n        half_val: vec![],\n        float_val: vec![],\n        double_val: vec![],\n        int_val: vec![],\n        string_val: vec![],\n        scomplex_val: vec![],\n        dcomplex_val: vec![],\n        resource_handle_val: vec![],\n        variant_val: vec![],\n        uint32_val: vec![],\n        uint64_val: vec![],\n        int64_val: vec![],\n        bool_val: vec![],\n    }\n}\n\nimpl TryFrom<&Tensor> for TensorProto {\n    type Error = TractError;\n    fn try_from(from: &Tensor) -> TractResult<TensorProto> {\n        let mut tensor = empty_tensor_proto();\n        let shape = TensorShapeProto {\n            dim: from.shape().iter().map(|d| Dim { size: *d as _, name: String::new() }).collect(),\n            unknown_rank: false,\n        };\n        tensor.tensor_shape = Some(shape);\n        let dt = DataType::try_from(from.datum_type())?;\n        tensor.dtype = dt.into();\n        match from.datum_type() {\n            DatumType::F32 => {\n                tensor.float_val = from.to_plain_array_view::<f32>()?.iter().cloned().collect();\n            }\n            DatumType::F64 => {\n                tensor.double_val = from.to_plain_array_view::<f64>()?.iter().cloned().collect();\n            }\n            DatumType::I32 => {\n                tensor.int_val = from.to_plain_array_view::<i32>()?.iter().cloned().collect();\n            }\n            DatumType::I64 => {\n                tensor.int64_val = from.to_plain_array_view::<i64>()?.iter().cloned().collect();\n            }\n            _ => unimplemented!(\"missing type {:?}\", from.datum_type()),\n        }\n        Ok(tensor)\n    }\n}\n"
  },
  {
    "path": "tensorflow/src/tfpb.rs",
    "content": "use tract_hir::internal::*;\n\nuse std::fs;\n\n#[allow(clippy::all)]\nmod google {\n    mod protobuf {\n        include!(\"prost/google.protobuf.rs\");\n    }\n}\n\n#[allow(clippy::all)]\npub mod tensorflow {\n    include!(\"prost/tensorflow.rs\");\n}\n\nuse self::tensorflow::attr_value::ListValue;\nuse self::tensorflow::attr_value::Value;\nuse self::tensorflow::{AttrValue, DataType, GraphDef, NodeDef, TensorProto, TensorShapeProto};\n\nuse std::convert::TryInto;\n\npub fn graph() -> GraphDef {\n    GraphDef::default()\n}\n\npub fn node() -> NodeDef {\n    NodeDef {\n        name: String::new(),\n        op: String::new(),\n        input: vec![],\n        device: String::new(),\n        attr: HashMap::new(),\n    }\n}\n\nimpl GraphDef {\n    pub fn node(mut self, n: NodeDef) -> Self {\n        self.node.push(n);\n        self\n    }\n    pub fn write_to_bytes(&self) -> TractResult<Vec<u8>> {\n        use prost::Message;\n        let mut buf = vec![];\n        self.encode(&mut buf)?;\n        Ok(buf)\n    }\n    pub fn save_to<P: AsRef<::std::path::Path>>(self, p: P) -> TractResult<()> {\n        let buf = self.write_to_bytes()?;\n        fs::write(p, buf)?;\n        Ok(())\n    }\n}\n\nimpl NodeDef {\n    pub fn name<S: ToString>(mut self, n: S) -> NodeDef {\n        self.name = n.to_string();\n        self\n    }\n    pub fn op<S: ToString>(mut self, n: S) -> NodeDef {\n        self.op = n.to_string();\n        self\n    }\n    pub fn input<S: ToString>(mut self, n: S) -> NodeDef {\n        self.input.push(n.to_string());\n        self\n    }\n    pub fn attr<S: ToString, V: Into<AttrValue>>(mut self, n: S, v: V) -> NodeDef {\n        self.attr.insert(n.to_string(), v.into());\n        self\n    }\n}\n\nimpl NodeDef {\n    pub fn get_attr_raw_str(&self, name: &str) -> TractResult<&[u8]> {\n        self.get_attr_opt_raw_str(name)?.with_context(|| {\n            format!(\"Node {} ({}) expected string attribute '{}'\", self.name, self.op, name)\n        })\n    }\n\n    pub fn get_attr_opt_raw_str(&self, name: &str) -> TractResult<Option<&[u8]>> {\n        if let Some(a) = self.attr.get(name) {\n            if let Value::S(bytes) = a.value.as_ref().unwrap() {\n                return Ok(Some(bytes));\n            }\n        };\n        Ok(None)\n    }\n\n    pub fn get_attr_str(&self, name: &str) -> TractResult<String> {\n        self.get_attr_opt_str(name)?.with_context(|| {\n            format!(\"Node {} ({}) expected UTF-8 string attribute '{}'\", self.name, self.op, name)\n        })\n    }\n\n    pub fn get_attr_opt_str(&self, name: &str) -> TractResult<Option<String>> {\n        if let Some(s) = self.get_attr_opt_raw_str(name)? {\n            Ok(Some(String::from_utf8(s.to_vec()).map_err(|_| {\n                format_err!(\n                    \"Node {} ({}) expected an UTF-8 string for attribute '{}'\",\n                    self.name,\n                    self.op,\n                    name\n                )\n            })?))\n        } else {\n            Ok(None)\n        }\n    }\n\n    pub fn get_attr_bool(&self, name: &str) -> TractResult<bool> {\n        self.get_attr_opt_bool(name)?.with_context(|| {\n            format!(\"Node {} ({}) expected bool attribute '{}'\", self.name, self.op, name)\n        })\n    }\n\n    pub fn get_attr_opt_bool(&self, name: &str) -> TractResult<Option<bool>> {\n        if let Some(a) = self.attr.get(name) {\n            if let Value::B(v) = a.value.as_ref().unwrap() {\n                return Ok(Some(*v));\n            }\n        };\n        Ok(None)\n    }\n\n    pub fn get_attr_datum_type(&self, name: &str) -> TractResult<DatumType> {\n        self.get_attr_opt_datum_type(name)?.with_context(|| {\n            format!(\"Node {} ({}) expected datum_type attribute '{}'\", self.name, self.op, name)\n        })\n    }\n\n    pub fn get_attr_opt_datum_type(&self, name: &str) -> TractResult<Option<DatumType>> {\n        if let Some(a) = self.attr.get(name) {\n            if let Value::Type(v) = a.value.as_ref().unwrap() {\n                return Ok(Some(DataType::try_from(*v).unwrap().try_into()?));\n            }\n        };\n        Ok(None)\n    }\n\n    pub fn get_attr_shape(&self, name: &str) -> TractResult<TVec<isize>> {\n        self.get_attr_opt_shape(name)?.with_context(|| {\n            format!(\"Node {} ({}) expected shape attribute '{}'\", self.name, self.op, name)\n        })\n    }\n\n    pub fn get_attr_opt_shape(&self, name: &str) -> TractResult<Option<TVec<isize>>> {\n        if let Some(a) = self.attr.get(name) {\n            if let Value::Shape(shape) = a.value.as_ref().unwrap() {\n                return Ok(Some(shape.try_into()?));\n            }\n        };\n        Ok(None)\n    }\n\n    pub fn get_attr_tensor(&self, name: &str) -> TractResult<Tensor> {\n        self.get_attr_opt_tensor(name)?.with_context(|| {\n            format!(\"Node {} ({}) expected tensor attribute '{}'\", self.name, self.op, name)\n        })\n    }\n\n    pub fn get_attr_opt_tensor(&self, name: &str) -> TractResult<Option<Tensor>> {\n        if let Some(a) = self.attr.get(name) {\n            if let Value::Tensor(t) = a.value.as_ref().unwrap() {\n                return Ok(Some(t.try_into()?));\n            }\n        };\n        Ok(None)\n    }\n\n    pub fn get_attr_int<T: tract_num_traits::FromPrimitive>(&self, name: &str) -> TractResult<T> {\n        self.get_attr_opt_int(name)?.with_context(|| {\n            format!(\"Node {} ({}) expected int attribute '{}'\", self.name, self.op, name)\n        })\n    }\n\n    pub fn get_attr_opt_int<T: tract_num_traits::FromPrimitive>(\n        &self,\n        name: &str,\n    ) -> TractResult<Option<T>> {\n        if let Some(a) = self.attr.get(name) {\n            if let Value::I(i) = a.value.as_ref().unwrap() {\n                return Ok(Some(T::from_i64(*i).unwrap()));\n            }\n        };\n        Ok(None)\n    }\n\n    pub fn get_attr_float<T: tract_num_traits::FromPrimitive>(&self, name: &str) -> TractResult<T> {\n        self.get_attr_opt_float(name)?.with_context(|| {\n            format!(\"Node {} ({}) expected int attribute '{}'\", self.name, self.op, name)\n        })\n    }\n\n    pub fn get_attr_opt_float<T: tract_num_traits::FromPrimitive>(\n        &self,\n        name: &str,\n    ) -> TractResult<Option<T>> {\n        if let Some(a) = self.attr.get(name) {\n            if let Value::F(i) = a.value.as_ref().unwrap() {\n                return Ok(Some(T::from_f32(*i).unwrap()));\n            }\n        };\n        Ok(None)\n    }\n\n    pub fn get_attr_list_int<T: tract_num_traits::FromPrimitive>(\n        &self,\n        name: &str,\n    ) -> TractResult<Vec<T>> {\n        self.get_attr_opt_list_int(name)?.with_context(|| {\n            format!(\"Node {} ({}) expected list<int> attribute '{}'\", self.name, self.op, name)\n        })\n    }\n\n    pub fn get_attr_opt_list_int<T: tract_num_traits::FromPrimitive>(\n        &self,\n        name: &str,\n    ) -> TractResult<Option<Vec<T>>> {\n        if let Some(a) = self.attr.get(name) {\n            if let Value::List(list) = a.value.as_ref().unwrap() {\n                return Ok(Some(list.i.iter().map(|&i| T::from_i64(i).unwrap()).collect()));\n            }\n        };\n        Ok(None)\n    }\n}\n\nimpl From<DataType> for AttrValue {\n    fn from(t: DataType) -> AttrValue {\n        AttrValue { value: Some(Value::Type(t.into())) }\n    }\n}\n\nimpl<'a> From<&'a str> for AttrValue {\n    fn from(t: &'a str) -> AttrValue {\n        AttrValue { value: Some(Value::S(t.as_bytes().to_vec())) }\n    }\n}\n\nimpl From<i32> for AttrValue {\n    fn from(t: i32) -> AttrValue {\n        AttrValue::from(t as i64)\n    }\n}\n\nimpl From<i64> for AttrValue {\n    fn from(t: i64) -> AttrValue {\n        AttrValue { value: Some(Value::I(t)) }\n    }\n}\n\nimpl From<f32> for AttrValue {\n    fn from(t: f32) -> AttrValue {\n        AttrValue { value: Some(Value::F(t)) }\n    }\n}\n\nimpl From<Vec<i64>> for AttrValue {\n    fn from(t: Vec<i64>) -> AttrValue {\n        AttrValue {\n            value: Some(Value::List(ListValue {\n                s: vec![],\n                i: t,\n                f: vec![],\n                b: vec![],\n                r#type: vec![],\n                shape: vec![],\n                tensor: vec![],\n                func: vec![],\n            })),\n        }\n    }\n}\n\nimpl From<TensorProto> for AttrValue {\n    fn from(t: TensorProto) -> AttrValue {\n        AttrValue { value: Some(Value::Tensor(t)) }\n    }\n}\n\nimpl From<TensorShapeProto> for AttrValue {\n    fn from(t: TensorShapeProto) -> AttrValue {\n        AttrValue { value: Some(Value::Shape(t)) }\n    }\n}\n\nimpl From<bool> for AttrValue {\n    fn from(t: bool) -> AttrValue {\n        AttrValue { value: Some(Value::B(t)) }\n    }\n}\n"
  },
  {
    "path": "tensorflow/tests/ops_array_pack.rs",
    "content": "#![cfg(feature = \"conform\")]\n#![allow(non_snake_case)]\nextern crate env_logger;\n#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate proptest;\nextern crate tract_tensorflow;\n\nmod utils;\n\nuse crate::utils::*;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_tensorflow::conform::*;\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tfpb;\nuse tract_tensorflow::tfpb::tensorflow::DataType::DtInt32;\n\nfn strat() -> BoxedStrategy<(usize, Vec<Tensor>)> {\n    // input rank\n    (1usize..8)\n        // rank, dimensions, number of inputs\n        .prop_flat_map(|r| (0usize..r, vec(1usize..5, r..r + 1), 1..5))\n        .prop_map(|(ax, dims, n)| {\n            let size = dims.iter().map(|a| *a).product::<usize>();\n            let mats: Vec<Tensor> = (0..n)\n                .map(|ix| {\n                    Tensor::from(\n                        tract_ndarray::Array::from_shape_vec(dims.clone(), ((ix * 1000)..).take(size).collect())\n                            .unwrap(),\n                    )\n                })\n                .collect();\n            (ax, mats)\n        })\n        .boxed()\n}\n\nproptest! {\n    #[test]\n    fn pack((axis, ref inputs) in strat()) {\n        let mut graph = tfpb::graph();\n        let mut graph_inputs = vec!();\n        let mut pack = tfpb::node()\n            .name(\"op\")\n            .op(\"Pack\")\n            .attr(\"T\", DtInt32)\n            .attr(\"N\", inputs.len() as i64)\n            .attr(\"axis\", axis as i64);\n        for (ix,input) in inputs.iter().enumerate() {\n            let input_name = format!(\"input-{}\", ix);\n            graph = graph.node(placeholder_i32(&input_name));\n            pack = pack.input(&input_name);\n            graph_inputs.push((input_name, input.clone()));\n        }\n        graph = graph.node(pack);\n        let graph = graph.write_to_bytes().unwrap();\n        compare(&graph, graph_inputs, \"op\")?\n    }\n}\n"
  },
  {
    "path": "tensorflow/tests/ops_array_strided_slice.proptest-regressions",
    "content": "# Seeds for failure cases proptest has generated in the past. It is\n# automatically read and these particular cases re-run before any\n# novel cases are generated.\n#\n# It is recommended to check this file in to source control so that\n# everyone who runs the test benefits from these saved cases.\nxs 107361357 1164668208 2912097219 625237657 # shrinks to (ref i, ref b, ref e, ref s, ref masks) = (I32([0] shape=[1], strides=[1], layout=C | F (0x3)), I32([0] shape=[1], strides=[1], layout=C | F (0x3)), I32([0] shape=[1], strides=[1], layout=C | F (0x3)), I32([1] shape=[1], strides=[1], layout=C | F (0x3)), (0, 0, 0, 0, 0))\nxs 1302349240 1330784477 4100086807 922471477 # shrinks to (ref i, ref b, ref e, ref s, ref masks) = (I32([[[0, 1, 2]]] shape=[1, 1, 3], strides=[3, 3, 1], layout=C (0x1)), I32([-1, -1, -1] shape=[3], strides=[1], layout=C | F (0x3)), I32([0, 0, -2] shape=[3], strides=[1], layout=C | F (0x3)), I32([1, 1, -1] shape=[3], strides=[1], layout=C | F (0x3)), (0, 3, 0, 0, 1))\ncc 3a723fc42d67eb51993f93b6a4b13e1f21aa974145049fc49a19047d0b868fb6 # shrinks to (ref i, ref b, ref e, ref s, ref masks) = (2xI32 0, 1, 1xI32 1, 1xI32 0, 1xI32 -1, (0, 0, 0, 0, 0))\ncc e5ad056ed488ae866246b3eb77347b7687d1b7cbc86554bf5917ef138f89e401 # shrinks to (ref i, ref b, ref e, ref s, ref masks) = (1x2xI32 0, 1, 2xI32 0, 0, 2xI32 0, 0, 2xI32 1, 1, (0, 2, 0, 0, 0))\ncc 5a9e569b2790687d9aabe4cbf53d9701a80a229664634013bf5f8f8bd56ea411 # shrinks to (ref i, ref b, ref e, ref s, ref masks) = (2xI32 0, 1, 1xI32 1, 1xI32 1, 1xI32 1, (1, 0, 0, 0, 1))\n"
  },
  {
    "path": "tensorflow/tests/ops_array_strided_slice.rs",
    "content": "#![cfg(feature = \"conform\")]\n#![allow(non_snake_case)]\nextern crate env_logger;\n#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate proptest;\nextern crate tract_tensorflow;\n\nmod utils;\n\nuse crate::utils::*;\nuse proptest::prelude::*;\nuse tract_tensorflow::conform::*;\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tfpb;\nuse tract_tensorflow::tfpb::tensorflow::DataType::DtInt32;\n\nfn strided_slice_strat()\n-> BoxedStrategy<(Tensor, Tensor, Tensor, Tensor, (i32, i32, i32, i32, i32))> {\n    ::proptest::collection::vec(\n        (1..5).prop_flat_map(|n| {\n            // each dim max\n            (\n                Just(n),       // input size\n                0..n,          // begin\n                0..n,          // end\n                1..2.max(n),   // stride, abs\n                any::<bool>(), // make begin negative\n                any::<bool>(), // make end negative\n            )\n        }),\n        1..=2, // rank\n    )\n    .prop_map(|mut tuples| {\n        tuples.iter_mut().for_each(|tuple| {\n            if tuple.1 > tuple.2 {\n                std::mem::swap(&mut tuple.1, &mut tuple.2)\n            }\n        });\n        tuples\n    })\n    .prop_flat_map(|dims| {\n        let rank = dims.iter().len();\n        (Just(dims), (0..(1 << rank), 0..(1 << rank), Just(0), Just(0), 0..(1 << rank)))\n    })\n    .prop_map(|(dims, masks)| {\n        let shape = dims.iter().map(|d| d.0 as usize).collect::<Vec<_>>();\n        let size: i32 = shape.iter().map(|d| *d as i32).product();\n        (\n            Tensor::from(tract_ndarray::Array::from_shape_vec(shape, (0..size).collect()).unwrap()),\n            tract_ndarray::Array::from(\n                dims.iter().map(|d| if d.4 { d.1 - d.0 } else { d.1 }).collect::<Vec<_>>(),\n            )\n            .into(),\n            tract_ndarray::Array::from(\n                dims.iter().map(|d| if d.5 { d.2 - d.0 } else { d.2 }).collect::<Vec<_>>(),\n            )\n            .into(),\n            tract_ndarray::Array::from(\n                dims.iter()\n                    .enumerate()\n                    .map(|(ix, d)| {\n                        if d.2 == d.1 || masks.4 & (1 << ix) != 0 {\n                            1\n                        } else {\n                            d.3 as i32 * (d.2 as i32 - d.1 as i32).signum()\n                        }\n                    })\n                    .collect::<Vec<_>>(),\n            )\n            .into(),\n            masks,\n        )\n    })\n    .boxed()\n}\n\nproptest! {\n    #[test]\n    fn strided_slice((ref i, ref b, ref e, ref s, ref masks) in strided_slice_strat()) {\n        let graph = tfpb::graph()\n            .node(placeholder_i32(\"input\"))\n            .node(const_i32(\"begin\", b))\n            .node(const_i32(\"end\", e))\n            .node(const_i32(\"stride\", s))\n            .node(tfpb::node().name(\"op\")\n                  .attr(\"T\", DtInt32)\n                  .attr(\"Index\", DtInt32)\n                  .attr(\"begin_mask\", masks.0 as i64)\n                  .attr(\"end_mask\", masks.1 as i64)\n                  .attr(\"shrink_axis_mask\", masks.4 as i64)\n                  .input(\"input\").input(\"begin\")\n                  .input(\"end\").input(\"stride\")\n                  .op(\"StridedSlice\")\n                 ).write_to_bytes().unwrap();\n\n        let inputs = vec!((\"input\", i.clone()));\n        let res = compare(&graph, inputs, \"op\")?;\n        res\n    }\n}\n\n#[test]\nfn strided_slice_1() {\n    let graph = tfpb::graph()\n        .node(placeholder_i32(\"input\"))\n        .node(const_i32(\"begin\", &tensor1(&[0])))\n        .node(const_i32(\"end\", &tensor1(&[2])))\n        .node(const_i32(\"stride\", &tensor1(&[1])))\n        .node(\n            tfpb::node()\n                .name(\"op\")\n                .attr(\"T\", DtInt32)\n                .attr(\"Index\", DtInt32)\n                .input(\"input\")\n                .input(\"begin\")\n                .input(\"end\")\n                .input(\"stride\")\n                .op(\"StridedSlice\"),\n        )\n        .write_to_bytes()\n        .unwrap();\n\n    let inputs = vec![(\"input\", tensor2(&[[0, 6], [0, 0]]))];\n    compare(&graph, inputs, \"op\").unwrap()\n}\n\n#[test]\nfn strided_slice_2() {\n    let graph = tfpb::graph()\n        .node(placeholder_i32(\"input\"))\n        .node(const_i32(\"begin\", &tensor1(&[0])))\n        .node(const_i32(\"end\", &tensor1(&[0])))\n        .node(const_i32(\"stride\", &tensor1(&[1])))\n        .node(\n            tfpb::node()\n                .name(\"op\")\n                .attr(\"T\", DtInt32)\n                .attr(\"Index\", DtInt32)\n                .attr(\"shrink_axis_mask\", 1 as i64)\n                .input(\"input\")\n                .input(\"begin\")\n                .input(\"end\")\n                .input(\"stride\")\n                .op(\"StridedSlice\"),\n        )\n        .write_to_bytes()\n        .unwrap();\n\n    let inputs = vec![(\"input\", tensor1(&[0]))];\n    compare(&graph, inputs, \"op\").unwrap()\n}\n\n#[test]\nfn strided_slice_3() {\n    let graph = tfpb::graph()\n        .node(placeholder_i32(\"input\"))\n        .node(const_i32(\"begin\", &tensor1(&[0])))\n        .node(const_i32(\"end\", &tensor1(&[0])))\n        .node(const_i32(\"stride\", &tensor1(&[1])))\n        .node(\n            tfpb::node()\n                .name(\"op\")\n                .attr(\"T\", DtInt32)\n                .attr(\"Index\", DtInt32)\n                .attr(\"shrink_axis_mask\", 1 as i64)\n                .input(\"input\")\n                .input(\"begin\")\n                .input(\"end\")\n                .input(\"stride\")\n                .op(\"StridedSlice\"),\n        );\n    let graph = graph.write_to_bytes().unwrap();\n    let inputs = vec![(\"input\", tensor1(&[0, 1]))];\n    compare(&graph, inputs, \"op\").unwrap()\n}\n\n#[ignore] // negative stride\n#[test]\nfn strided_slice_4() {\n    let graph = tfpb::graph()\n        .node(placeholder_i32(\"input\"))\n        .node(const_i32(\"begin\", &tensor1(&[1])))\n        .node(const_i32(\"end\", &tensor1(&[0])))\n        .node(const_i32(\"stride\", &tensor1(&[-1])))\n        .node(\n            tfpb::node()\n                .name(\"op\")\n                .attr(\"T\", DtInt32)\n                .attr(\"Index\", DtInt32)\n                .input(\"input\")\n                .input(\"begin\")\n                .input(\"end\")\n                .input(\"stride\")\n                .op(\"StridedSlice\"),\n        );\n    let graph = graph.write_to_bytes().unwrap();\n    let inputs = vec![(\"input\", tensor1(&[0, 1]))];\n    compare(&graph, inputs, \"op\").unwrap()\n}\n\n#[test]\nfn strided_slice_5() {\n    let graph = tfpb::graph()\n        .node(placeholder_i32(\"input\"))\n        .node(const_i32(\"begin\", &tensor1(&[0, 0])))\n        .node(const_i32(\"end\", &tensor1(&[0, 0])))\n        .node(const_i32(\"stride\", &tensor1(&[1, 1])))\n        .node(\n            tfpb::node()\n                .name(\"op\")\n                .attr(\"T\", DtInt32)\n                .attr(\"Index\", DtInt32)\n                .attr(\"end_mask\", 2 as i64)\n                .input(\"input\")\n                .input(\"begin\")\n                .input(\"end\")\n                .input(\"stride\")\n                .op(\"StridedSlice\"),\n        );\n    let graph = graph.write_to_bytes().unwrap();\n    let inputs = vec![(\"input\", tensor2(&[[0, 1]]))];\n    compare(&graph, inputs, \"op\").unwrap()\n}\n\n#[test]\nfn strided_slice_shrink_override_begin_mask() {\n    let graph = tfpb::graph()\n        .node(placeholder_i32(\"input\"))\n        .node(const_i32(\"begin\", &tensor1(&[1])))\n        .node(const_i32(\"end\", &tensor1(&[1])))\n        .node(const_i32(\"stride\", &tensor1(&[1])))\n        .node(\n            tfpb::node()\n                .name(\"op\")\n                .attr(\"T\", DtInt32)\n                .attr(\"Index\", DtInt32)\n                .attr(\"begin_mask\", 1 as i64)\n                .attr(\"shrink_axis_mask\", 1 as i64)\n                .input(\"input\")\n                .input(\"begin\")\n                .input(\"end\")\n                .input(\"stride\")\n                .op(\"StridedSlice\"),\n        );\n    let graph = graph.write_to_bytes().unwrap();\n    let inputs = vec![(\"input\", tensor1(&[0, 1]))];\n    compare(&graph, inputs, \"op\").unwrap()\n}\n"
  },
  {
    "path": "tensorflow/tests/ops_fake_quant_with_min_max_vars.rs",
    "content": "#![cfg(feature = \"conform\")]\n#![allow(non_snake_case)]\nextern crate env_logger;\n#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate proptest;\nextern crate tract_tensorflow;\n\nmod utils;\n\nuse crate::utils::*;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_tensorflow::conform::*;\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tfpb;\nuse tract_tensorflow::tfpb::tensorflow::DataType;\n\nfn fake_quant_with_min_max_vars(\n    input: f32,\n    min: f32,\n    max: f32,\n    narrow_range: bool,\n    num_bits: i64,\n) -> proptest::test_runner::TestCaseResult {\n    let graph = tfpb::graph()\n        .node(const_f32(\"input\", &tensor0(input)))\n        .node(const_f32(\"min\", &tensor0(min)))\n        .node(const_f32(\"max\", &tensor0(max)))\n        .node(\n            tfpb::node()\n                .name(\"op\")\n                .op(\"FakeQuantWithMinMaxVars\")\n                .input(\"input\")\n                .input(\"min\")\n                .input(\"max\")\n                .attr(\"narrow_range\", narrow_range)\n                .attr(\"num_bits\", num_bits),\n        );\n    let graph = graph.write_to_bytes().unwrap();\n    compare::<&'static str>(&graph, vec![], \"op\")\n}\n\n//may fails due to rounding errors\nproptest! {\n    #[test]\n    #[ignore]\n    fn ops_fake_quant_with_min_max_vars(input in -10f32..10f32, min in -10f32..-0.1f32, max in 0.1f32..10f32, narrow_range: bool, num_bits in 2..8i64) {\n\n        fake_quant_with_min_max_vars(input, min, max, narrow_range, num_bits)?\n    }\n}\n\n#[test]\nfn trivial0() -> std::result::Result<(), TestCaseError> {\n    fake_quant_with_min_max_vars(0.0, -1., 1., false, 2)\n}\n\n//fails due to rounding errors (recip != div)\n#[test]\n#[ignore]\nfn trivial1() -> std::result::Result<(), TestCaseError> {\n    fake_quant_with_min_max_vars(-0.059596814, -0.9537212, 0.8271718, true, 8)\n}\n"
  },
  {
    "path": "tensorflow/tests/ops_nn_conv2d.proptest-regressions",
    "content": "# Seeds for failure cases proptest has generated in the past. It is\n# automatically read and these particular cases re-run before any\n# novel cases are generated.\n#\n# It is recommended to check this file in to source control so that\n# everyone who runs the test benefits from these saved cases.\nxs 148868935 2656186008 3130107924 971183391 # shrinks to (ref i, ref k, ref strides) = (F32([[[[0.0]]]] shape=[1, 1, 1, 1], strides=[1, 1, 1, 1], layout=C (0x1)), F32([[[[0.0]]]] shape=[1, 1, 1, 1], strides=[1, 1, 1, 1], layout=C (0x1)), (1, 1)), valid = false\ncc 26c8e393370a681ca5715cc50972bfedf5e306e8fb14d0de9ad9e4127b7bdb23 # shrinks to (ref i, ref k, ref strides) = (1x1x3x1xF32 1, 1, 0, 1x2x1x2xF32 0, 0, 0, -1, (1, 1)), valid = true\ncc b124ee0315131f2371cff1a3c07f454b37277dbf1fb2127d9d28d8bf01d01a45 # shrinks to (ref i, ref k, ref strides) = (1x2x1x2xF32 0, 0, 0, -1, 2x1x2x2xF32 0, 0, 0, 0, 0, 0, 0, -1, (1, 1)), valid = false\ncc 693fb4b86aff1b5ffd69b2f155949e014989d038ed3b86716d0ead375132586d # shrinks to (ref i, ref k, ref strides) = (1x2x2x1xF32 0, 0, 0, 1, 1x2x1x1xF32 0, 1, (1, 1)), valid = false\ncc 09991fe7a0d4a11a9b1c9e680a4ff7482b30ee49bab291c7f33532a8e9b442a1 # shrinks to (ref i, ref k, ref strides) = (1x2x2x1xF32 0, 0, 0, -1, 1x1x1x1xF32 1, (1, 1)), valid = false\n"
  },
  {
    "path": "tensorflow/tests/ops_nn_conv2d.rs",
    "content": "#![cfg(feature = \"conform\")]\n#![allow(non_snake_case)]\nextern crate env_logger;\n#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate proptest;\nextern crate tract_tensorflow;\n\nmod utils;\n\nuse crate::utils::*;\nuse proptest::prelude::*;\nuse tract_tensorflow::conform::*;\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tfpb;\nuse tract_tensorflow::tfpb::tensorflow::DataType::DtFloat;\n\nfn convolution_pb(\n    v_stride: usize,\n    h_stride: usize,\n    valid: bool,\n    kernel: &Tensor,\n) -> TractResult<Vec<u8>> {\n    let conv = tfpb::node()\n        .name(\"conv\")\n        .op(\"Conv2D\")\n        .input(\"data\")\n        .input(\"kernel\")\n        .attr(\"strides\", vec![1, v_stride as i64, h_stride as i64, 1])\n        .attr(\"padding\", if valid { \"VALID\" } else { \"SAME\" })\n        .attr(\"T\", DtFloat);\n\n    let graph =\n        tfpb::graph().node(placeholder_f32(\"data\")).node(const_f32(\"kernel\", kernel)).node(conv);\n\n    Ok(graph.write_to_bytes()?)\n}\n\nfn img_and_ker() -> BoxedStrategy<(Tensor, Tensor, (usize, usize))> {\n    (1usize..4, 1usize..3, 1usize..3, 1usize..4)\n        .prop_flat_map(|(ic, kh, kw, kc)| (1usize..2, kh..10, kw..10, Just((ic, kh, kw, kc))))\n        .prop_flat_map(|(ib, ih, iw, (ic, kh, kw, kc))| {\n            let i_size = ib * iw * ih * ic;\n            let k_size = kw * kh * kc * ic;\n            (\n                Just((ib, ih, iw, ic)),\n                Just((kh, kw, ic, kc)),\n                ::proptest::collection::vec(-9i32..9, i_size..i_size + 1),\n                ::proptest::collection::vec(-9i32..9, k_size..k_size + 1),\n                (1..(kh + 1), 1..(kw + 1)),\n            )\n        })\n        .prop_map(|(img_shape, ker_shape, img, ker, strides)| {\n            (\n                tract_ndarray::Array::from(img.into_iter().map(|i| i as f32).collect::<Vec<_>>())\n                    .into_shape_with_order(img_shape)\n                    .unwrap()\n                    .into(),\n                tract_ndarray::Array::from(ker.into_iter().map(|i| i as f32).collect::<Vec<_>>())\n                    .into_shape_with_order(ker_shape)\n                    .unwrap()\n                    .into(),\n                strides,\n            )\n        })\n        .boxed()\n}\n\nproptest! {\n    #[test]\n    fn conv_compare((ref i, ref k, ref strides) in img_and_ker(),\n                       valid in ::proptest::bool::ANY) {\n        let model = convolution_pb(strides.0, strides.1, valid,& k).unwrap();\n        compare(&model, vec!((\"data\", i.clone())), \"conv\")?;\n    }\n\n    #[test]\n    fn conv_infer_facts((ref i, ref k, ref strides) in img_and_ker(),\n                       valid in ::proptest::bool::ANY) {\n        if valid {\n            prop_assume!(i.shape()[1] >= k.shape()[0]);\n            prop_assume!(i.shape()[2] >= k.shape()[1]);\n        }\n        let model = convolution_pb(strides.0, strides.1, valid, &k).unwrap();\n        infer(&model, vec!((\"data\", i.clone())),  \"conv\")?;\n    }\n}\n\n#[test]\nfn conv_infer_facts_1() {\n    let i: Tensor = tract_ndarray::ArrayD::<f32>::zeros(vec![1, 2, 2, 2]).into();\n    let k: Tensor = tract_ndarray::ArrayD::<f32>::zeros(vec![2, 2, 2, 1]).into();\n    let model = convolution_pb(1, 1, false, &k).unwrap();\n    infer(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_1() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32, 0.0], [1.0, 0.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0.0f32], [0.0]], [[1.0], [0.0]]]]));\n    let model = convolution_pb(1, 1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_2() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32, -1.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0.0f32, 0.0], [1.0, 0.0]]]]));\n    let model = convolution_pb(1, 1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_3() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0.0f32]]]]));\n    let model = convolution_pb(1, 1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_4() {\n    let i: Tensor = Tensor::from(arr4(&[[[[1.0f32], [1.0], [0.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0f32, 0.0]], [[0.0, -1.0]]]]));\n    let model = convolution_pb(1, 1, true, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_5() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32, 0.0]], [[0.0, 1.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0f32, 0.0], [0.0, 0.0]]], [[[0.0, 0.0], [0.0, 1.0]]]]));\n    let model = convolution_pb(1, 1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_6() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32], [0.0]], [[0.0], [1.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0f32]], [[1.0]]]]));\n    let model = convolution_pb(1, 1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_7() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32]], [[0.0]], [[0.0]], [[1.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0.0f32]]], [[[1.0]]]]));\n    let model = convolution_pb(2, 1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_8() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32], [0.0]], [[0.0], [1.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[1.0f32]]]]));\n    let model = convolution_pb(1, 1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_mobilenet_v2() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32, -1.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0.0f32, 0.0], [1.0, 0.0]]]]));\n    let model = convolution_pb(1, 1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n"
  },
  {
    "path": "tensorflow/tests/ops_nn_dwconv2d.proptest-regressions",
    "content": "# Seeds for failure cases proptest has generated in the past. It is\n# automatically read and these particular cases re-run before any\n# novel cases are generated.\n#\n# It is recommended to check this file in to source control so that\n# everyone who runs the test benefits from these saved cases.\nxs 3626947671 567190746 1066259331 2169673451 # shrinks to (ref i, ref k, ref strides) = (1x1x1x1xF32 0, 1x1x1x1xF32 0, (1, 1)), valid = false\nxs 2095200658 2521600292 167531540 27545920 # shrinks to (ref i, ref k, ref strides) = (1x1x1x1xF32 0, 1x1x1x1xF32 0, (1, 1)), valid = false\nxs 3785992409 1022059155 3135754526 805113969 # shrinks to (ref i, ref k, stride) = (1x1x2x1xF32 1, 0, 1x1x1x1xF32 1, 2), valid = false\nxs 144961368 3467274053 4120060015 4072688890 # shrinks to (ref i, ref k, stride) = (1x3x8x1xF32 0, 0, 0, 0..., 1x1x1x1xF32 -1, 2), valid = false\nxs 3441037282 1568902391 1947917976 3753470628 # shrinks to (ref i, ref k, stride) = (1x3x4x1xF32 0, 0, 0, 0..., 1x1x1x1xF32 -1, 2), valid = false\nxs 1210613092 3469268474 2211162269 2728189630 # shrinks to (ref i, ref k, stride) = ([[[[0.0],    [0.0]],   [[0.0],    [0.0]],   [[0.0],    [0.0]]],  [[[0.0],    [-1.0]],   [[0.0],    [0.0]],   [[0.0],    [0.0]]]] shape=[2, 3, 2, 1], strides=[6, 2, 1, 1], layout=C (0x1), const ndim=4, [[[[0.0]]],  [[[-1.0]]]] shape=[2, 1, 1, 1], strides=[1, 1, 1, 1], layout=C (0x1), const ndim=4, 1), valid = false\nxs 3872883304 3578968752 800499419 963336543 # shrinks to (ref i, ref k, stride) = ([[[[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],    [3.0],    [0.0],    [0.0],    [-1.0]]]] shape=[1, 5, 5, 1], strides=[25, 5, 1, 1], layout=C (0x1), const ndim=4, [[[[0.0, 0.0]],   [[1.0, 0.0]]],  [[[4.0, 6.0]],   [[-3.0, 8.0]]]] shape=[2, 2, 1, 2], strides=[4, 2, 2, 1], layout=C (0x1), const ndim=4, 1), valid = false\ncc 8e8f6d5b3a36324175a0f916e75c5d293ef168682b6f1ea6c970d947795693f1 # shrinks to (ref i, ref k, stride) = ([[[[0.0],    [0.0]],   [[0.0],    [0.0]]],  [[[0.0],    [0.0]],   [[0.0],    [0.0]]]] shape=[2, 2, 2, 1], strides=[4, 2, 1, 1], layout=C (0x1), const ndim=4, [[[[0.0, 0.0]]]] shape=[1, 1, 1, 2], strides=[2, 2, 2, 1], layout=C (0x1), const ndim=4, 1), valid = true\ncc f7f232c45db8c3fe8207d437f98f8b17008bbbc4ef7394ee19f6ca11bf0be034 # shrinks to (ref i, ref k, stride) = ([[[[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, 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, -6.0, -8.0],    [-3.0, -7.0, 6.0, -1.0, -4.0]]]], shape=[1, 3, 3, 5], strides=[45, 15, 5, 1], layout=C (0x1), const ndim=4, [[[[0.0, 1.0, 6.0],    [5.0, 7.0, -7.0],    [3.0, -7.0, -8.0],    [6.0, -8.0, -5.0],    [-4.0, -4.0, -2.0]]]], shape=[1, 1, 5, 3], strides=[15, 15, 3, 1], layout=C (0x1), const ndim=4, 2), valid = true\n"
  },
  {
    "path": "tensorflow/tests/ops_nn_dwconv2d.rs",
    "content": "#![cfg(feature = \"conform\")]\n#![allow(non_snake_case)]\nextern crate env_logger;\n#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate proptest;\nextern crate tract_tensorflow;\n\nmod utils;\n\nuse crate::utils::*;\nuse proptest::prelude::*;\nuse tract_ndarray::prelude::*;\nuse tract_tensorflow::conform::*;\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tfpb;\nuse tract_tensorflow::tfpb::tensorflow::DataType::DtFloat;\n\nfn convolution_pb(stride: usize, padded: bool, k: &Tensor) -> TractResult<Vec<u8>> {\n    let conv = tfpb::node()\n        .name(\"conv\")\n        .op(\"DepthwiseConv2dNative\")\n        .input(\"data\")\n        .input(\"kernel\")\n        .attr(\"strides\", vec![1, stride as i64, stride as i64, 1])\n        .attr(\"dilations\", vec![1, 1, 1, 1])\n        .attr(\"padding\", if padded { \"SAME\" } else { \"VALID\" })\n        .attr(\"T\", DtFloat);\n\n    let graph = tfpb::graph().node(placeholder_f32(\"data\")).node(const_f32(\"kernel\", k)).node(conv);\n\n    Ok(graph.write_to_bytes()?)\n}\n\nfn img_and_ker() -> BoxedStrategy<(Array4<f32>, Array4<f32>, usize)> {\n    (1usize..=5, 1usize..=3, 1usize..=3, 1usize..=3, 1usize..=3)\n        .prop_flat_map(|(ic, kh, kw, q, s)| {\n            (1usize..3, (kh..2 * kh + 4 * s), (kw..2 * kw + 4 * s), Just((ic, kh, kw, q, s)))\n        })\n        .prop_flat_map(|(ib, ih, iw, (ic, kh, kw, q, s))| {\n            let i_size = ib * iw * ih * ic;\n            let k_size = kw * kh * ic * q;\n            (\n                Just((ib, ih, iw, ic)),\n                Just((kh, kw, ic, q)),\n                ::proptest::collection::vec(-9i32..9, i_size..i_size + 1),\n                ::proptest::collection::vec(-9i32..9, k_size..k_size + 1),\n                Just(s),\n            )\n        })\n        .prop_map(|(img_shape, ker_shape, img, ker, stride)| {\n            (\n                Array::from(img.into_iter().map(|i| i as f32).collect::<Vec<_>>())\n                    .into_shape_with_order(img_shape)\n                    .unwrap(),\n                Array::from(ker.into_iter().map(|i| i as f32).collect::<Vec<_>>())\n                    .into_shape_with_order(ker_shape)\n                    .unwrap(),\n                stride,\n            )\n        })\n        .boxed()\n}\n\nproptest! {\n    #[test]\n    fn conv_compare((ref i, ref k, stride) in img_and_ker(),\n                    padded in any::<bool>()) {\n        assert!(i.shape()[1] >= k.shape()[0] && i.shape()[2] >= k.shape()[1]);\n        let k = Tensor::from(k.clone());\n        let model = convolution_pb(stride, padded, &k).unwrap();\n        compare(&model, vec!((\"data\", i.clone().into()), ), \"conv\")?;\n    }\n}\n\nproptest! {\n    #[test]\n    fn conv_infer_facts((ref i, ref k, stride) in img_and_ker(),\n                        padded in any::<bool>()) {\n        let k = Tensor::from(k.clone());\n        let model = convolution_pb(stride, padded, &k).unwrap();\n        infer(&model, vec!((\"data\", i.clone().into())), \"conv\")?;\n    }\n}\n\n#[test]\nfn conv_infer_facts_1() {\n    let i: Tensor = ArrayD::<f32>::zeros(vec![1, 2, 2, 2]).into();\n    let k: Tensor = ArrayD::<f32>::zeros(vec![2, 2, 2, 1]).into();\n    let model = convolution_pb(1, true, &k).unwrap();\n    infer(&model, vec![(\"data\", i.clone().into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_1() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32, 0.0], [1.0, 0.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0.0f32], [0.0]], [[1.0], [0.0]]]]));\n    let model = convolution_pb(1, true, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_2() {\n    let i: Tensor = Tensor::from(arr4::<f32, _, _, _>(&[[[[-1.0], [0.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[1.0f32]]]]));\n    let model = convolution_pb(2, true, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_3() {\n    let i: Tensor =\n        Tensor::from(Array::from_shape_fn((1, 112, 112, 48), |_| rand::random::<f32>()));\n    let k: Tensor = Tensor::from(Array::from_shape_fn((3, 3, 48, 1), |_| rand::random::<f32>()));\n    let conv = tfpb::node()\n        .name(\"conv\")\n        .op(\"DepthwiseConv2dNative\")\n        .input(\"data\")\n        .input(\"kernel\")\n        .attr(\"strides\", vec![1, 1, 1, 1])\n        .attr(\"dilations\", vec![1, 1, 1, 1])\n        .attr(\"padding\", \"SAME\")\n        .attr(\"T\", DtFloat);\n\n    let graph =\n        tfpb::graph().node(placeholder_f32(\"data\")).node(const_f32(\"kernel\", &k)).node(conv);\n\n    let model = graph.write_to_bytes().unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_4() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32], [0.0]], [[0.0], [-1.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0.0f32, -1.0]]]]));\n    let model = convolution_pb(1, true, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_5() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32, 0.0], [0.0, 0.0]], [[0.0, 0.0], [0.0, 1.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0.0f32, 0.0], [1.0, 0.0]]]]));\n    let model = convolution_pb(1, true, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_6() {\n    let i: Tensor = Tensor::from(arr4(&[[[[0.0f32, 0.0], [0.0, 0.0]], [[0.0, 0.0], [0.0, 1.0]]]]));\n    let k: Tensor = Tensor::from(arr4(&[[[[0.0f32], [1.0]]]]));\n    let model = convolution_pb(1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_7() {\n    let i: Tensor = tensor4(&[[[[1.0f32, 2.0]]]]);\n    let k: Tensor = tensor4(&[[[[3.0f32, 5.0], [7.0, 11.0]]]]);\n    let model = convolution_pb(1, true, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_8() {\n    let i: Tensor =\n        tensor4(&[[[[0.0f32], [0.0]], [[0.0], [0.0]]], [[[0.0], [0.0]], [[0.0], [0.0]]]]);\n    let k: Tensor = tensor4(&[[[[0.0f32, 0.0]]]]);\n    let model = convolution_pb(1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n\n#[test]\nfn conv_eval_9() {\n    let i: Tensor = tensor4(&[[[[0f32, 0.0, 0.0, 0.0, 1.0]]]]);\n    let mut k = Tensor::zero::<f32>(&[1, 1, 5, 3]).unwrap();\n    *k.try_as_plain_mut().unwrap().as_slice_mut::<f32>().unwrap().last_mut().unwrap() = 1.0;\n    let model = convolution_pb(1, false, &k).unwrap();\n    compare(&model, vec![(\"data\", i.into())], \"conv\").unwrap();\n}\n"
  },
  {
    "path": "tensorflow/tests/ops_nn_pools.proptest-regressions",
    "content": "# Seeds for failure cases proptest has generated in the past. It is\n# automatically read and these particular cases re-run before any\n# novel cases are generated.\n#\n# It is recommended to check this file in to source control so that\n# everyone who runs the test benefits from these saved cases.\nxs 4157211250 2256075456 81453571 64911648 # shrinks to (ref i, k, ref padding, stride) = (F32([[[[0.0]]]] shape=[1, 1, 1, 1], strides=[1, 1, 1, 1], layout=C (0x1)), (1, 1), \"VALID\", 1)\ncc 77a89c09806f55ec2f67377514f77ccad551c7eb9f91cff206b7b1e831606506 # shrinks to (ref i, k, ref padding, stride) = ([[[[0.0]]]] shape=[1, 1, 1, 1], strides=[1, 1, 1, 1], layout=C (0x1), const ndim=4, (1, 2), \"VALID\", 1)\ncc e80892b5e8e6eb45cb799e7844c5af46775f03de51133d55b5db1a9c1d58083d # shrinks to (ref i, k, ref padding, stride) = ([[[[0.0]]]] shape=[1, 1, 1, 1], strides=[1, 1, 1, 1], layout=C (0x1), const ndim=4, (2, 1), \"VALID\", 1)\ncc 70eccb53ff68aaab2bb835e6e83a20a90cdff149101dbab926eefe1222e706ef # shrinks to (ref i, k, ref padding, stride) = ([[[[1.0]]]] shape=[1, 1, 1, 1], strides=[1, 1, 1, 1], layout=C (0x1), const ndim=4, (1, 2), \"SAME\", 1)\ncc 9cecfd4a142ead272fa863e0999eb1a092224fee791a8fe5ff2652dd72ff6603 # shrinks to (ref i, k, ref padding, stride) = ([[[[-1.0]]]] shape=[1, 1, 1, 1], strides=[1, 1, 1, 1], layout=C (0x1), const ndim=4, (1, 2), \"SAME\", 1)\n"
  },
  {
    "path": "tensorflow/tests/ops_nn_pools.rs",
    "content": "#![cfg(feature = \"conform\")]\n#![allow(non_snake_case)]\n#[macro_use]\nextern crate log;\nextern crate env_logger;\n#[macro_use]\nextern crate proptest;\nextern crate tensorflow;\nextern crate tract_tensorflow;\n\nmod utils;\n\nuse crate::utils::*;\nuse proptest::prelude::*;\nuse proptest::test_runner::TestCaseResult;\nuse tract_ndarray::prelude::*;\nuse tract_tensorflow::conform::*;\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tfpb;\nuse tract_tensorflow::tfpb::tensorflow::DataType::DtFloat;\n\nfn img_and_pool() -> BoxedStrategy<(Array4<f32>, (usize, usize), String, usize)> {\n    (1usize..5, 1usize..5, 1usize..5, (1usize..3, 1usize..3))\n        .prop_flat_map(|(ih, iw, ic, k)| {\n            let i_size = iw * ih * ic;\n            (\n                Just((1, ih, iw, ic)),\n                Just(k),\n                ::proptest::collection::vec((-10..10).prop_map(|a| a as f32), i_size..i_size + 1),\n                prop_oneof!(\"VALID\", \"SAME\"),\n                1usize..3,\n            )\n        })\n        .prop_map(|(img_shape, k, img, padding, stride)| {\n            (Array::from(img).into_shape_with_order(img_shape).unwrap(), k, padding, stride)\n        })\n        .boxed()\n}\n\nfn pool(\n    op: &str,\n    i: &Array4<f32>,\n    k: (usize, usize),\n    padding: &str,\n    stride: usize,\n) -> TestCaseResult {\n    if padding == \"VALID\" {\n        prop_assume!(i.shape()[1] >= k.0);\n        prop_assume!(i.shape()[2] >= k.1);\n    }\n    let graph = tfpb::graph()\n        .node(placeholder_f32(\"data\"))\n        .node(\n            tfpb::node()\n                .name(\"pool\")\n                .op(op)\n                .input(\"data\")\n                .attr(\"T\", DtFloat)\n                .attr(\"strides\", vec![1, stride as i64, stride as i64, 1])\n                .attr(\"ksize\", vec![1, k.0 as i64, k.1 as i64, 1])\n                .attr(\"padding\", padding),\n        )\n        .write_to_bytes()\n        .unwrap();\n    compare(&graph, vec![(\"data\", i.clone().into())], \"pool\")\n}\n\nproptest! {\n    #[test]\n    fn proptest_maxpool((ref i, k, ref padding, stride) in img_and_pool()) {\n        pool(\"MaxPool\", i, k, padding, stride)?;\n    }\n}\n\nproptest! {\n    #[test]\n    fn proptest_avgpool((ref i, k, ref padding, stride) in img_and_pool()) {\n        pool(\"AvgPool\", i, k, padding, stride)?;\n    }\n}\n\n#[test]\nfn maxpool_1() {\n    pool(\"MaxPool\", &Array4::<f32>::zeros((1, 1, 4, 1)), (1, 2), \"SAME\", 1).unwrap();\n}\n\n#[test]\nfn maxpool_2() {\n    pool(\"MaxPool\", &arr4(&[[[[0.0]], [[-1.0]]]]), (2, 1), \"SAME\", 1).unwrap();\n}\n\n#[test]\nfn maxpool_3() {\n    pool(\"MaxPool\", &arr4(&[[[[-1.0]]]]), (1, 2), \"SAME\", 1).unwrap();\n}\n\n#[test]\nfn avgpool_1() {\n    pool(\"AvgPool\", &Array4::<f32>::zeros((1, 1, 6, 1)), (1, 1), \"SAME\", 2).unwrap();\n}\n"
  },
  {
    "path": "tensorflow/tests/ops_nn_space_to_batch.proptest-regressions",
    "content": "# Seeds for failure cases proptest has generated in the past. It is\n# automatically read and these particular cases re-run before any\n# novel cases are generated.\n#\n# It is recommended to check this file in to source control so that\n# everyone who runs the test benefits from these saved cases.\nxs 734568891 2529215138 1297005414 89662159 # shrinks to (ref b, ref bs, ref c) = (F32([[[[[[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]],     [[0.0, 0.0, 0.0],      [0.0, 0.0, 0.0]],     [[0.0, 0.0, 0.0],      [0.0, 0.0, 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],      [16.0, 17.0, 18.0]],     [[19.0, 20.0, 21.0],      [22.0, 23.0, 24.0]],     [[25.0, 26.0, 27.0],      [28.0, 29.0, 30.0]],     [[31.0, 32.0, 33.0],      [34.0, 35.0, 36.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.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.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.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.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.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.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.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]]],    [[[37.0, 38.0, 39.0],      [40.0, 41.0, 42.0]],     [[43.0, 44.0, 45.0],      [46.0, 47.0, 48.0]],     [[49.0, 50.0, 51.0],      [52.0, 53.0, 54.0]],     [[55.0, 56.0, 57.0],      [58.0, 59.0, 60.0]],     [[61.0, 62.0, 63.0],      [64.0, 65.0, 66.0]],     [[67.0, 68.0, 69.0],      [70.0, 71.0, 72.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.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.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.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.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.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.0]]]]]] shape=[2, 2, 3, 6, 2, 3], strides=[216, 108, 36, 6, 3, 1], layout=C (0x1)), I32([2, 1] shape=[2], strides=[1], layout=C | F (0x3)), I32([[0, 2],  [1, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 411003962 2830010119 2443466156 1680642744 # shrinks to (ref i, ref bs, ref p) = (F32([[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],    [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0],    [29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],    [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0]],   [[43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],    [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0],    [57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],    [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0],    [71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],    [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0]],   [[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],    [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],    [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0],    [106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0],    [113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],    [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0]],   [[127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],    [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0],    [141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],    [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],    [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],    [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0]],   [[169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0],    [176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],    [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],    [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0],    [197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],    [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0]]],  [[[211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],    [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0],    [225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],    [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],    [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0],    [246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0]],   [[253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],    [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],    [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],    [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0],    [281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],    [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0]],   [[295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],    [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0],    [309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0],    [316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],    [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],    [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0]],   [[337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],    [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0],    [351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],    [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0],    [365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],    [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0]],   [[379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0],    [386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0],    [393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],    [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],    [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],    [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]]],  [[[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],    [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],    [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],    [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0],    [449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0],    [456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0]],   [[463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],    [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0],    [477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],    [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0],    [491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],    [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0]],   [[505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],    [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],    [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0],    [526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0],    [533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],    [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0]],   [[547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],    [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0],    [561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],    [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],    [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],    [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0]],   [[589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0],    [596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],    [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],    [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0],    [617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],    [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0]]]] shape=[3, 5, 6, 7], strides=[210, 42, 7, 1], layout=C (0x1)), I32([2] shape=[1], strides=[1], layout=C | F (0x3)), I32([[3, 2]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 4181824025 559530094 3721056842 3878587004 # shrinks to (ref b, ref bs, ref c) = (F32([[[[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, 0.0]],   [[51.0, 52.0, 53.0, 54.0, 55.0],    [56.0, 57.0, 58.0, 59.0, 60.0],    [61.0, 62.0, 63.0, 64.0, 65.0],    [66.0, 67.0, 68.0, 69.0, 70.0],    [71.0, 72.0, 73.0, 74.0, 75.0]],   [[126.0, 127.0, 128.0, 129.0, 130.0],    [131.0, 132.0, 133.0, 134.0, 135.0],    [136.0, 137.0, 138.0, 139.0, 140.0],    [141.0, 142.0, 143.0, 144.0, 145.0],    [146.0, 147.0, 148.0, 149.0, 150.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.0, 0.0]],   [[201.0, 202.0, 203.0, 204.0, 205.0],    [206.0, 207.0, 208.0, 209.0, 210.0],    [211.0, 212.0, 213.0, 214.0, 215.0],    [216.0, 217.0, 218.0, 219.0, 220.0],    [221.0, 222.0, 223.0, 224.0, 225.0]],   [[276.0, 277.0, 278.0, 279.0, 280.0],    [281.0, 282.0, 283.0, 284.0, 285.0],    [286.0, 287.0, 288.0, 289.0, 290.0],    [291.0, 292.0, 293.0, 294.0, 295.0],    [296.0, 297.0, 298.0, 299.0, 300.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],    [16.0, 17.0, 18.0, 19.0, 20.0],    [21.0, 22.0, 23.0, 24.0, 25.0]],   [[76.0, 77.0, 78.0, 79.0, 80.0],    [81.0, 82.0, 83.0, 84.0, 85.0],    [86.0, 87.0, 88.0, 89.0, 90.0],    [91.0, 92.0, 93.0, 94.0, 95.0],    [96.0, 97.0, 98.0, 99.0, 100.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.0, 0.0]]],  [[[151.0, 152.0, 153.0, 154.0, 155.0],    [156.0, 157.0, 158.0, 159.0, 160.0],    [161.0, 162.0, 163.0, 164.0, 165.0],    [166.0, 167.0, 168.0, 169.0, 170.0],    [171.0, 172.0, 173.0, 174.0, 175.0]],   [[226.0, 227.0, 228.0, 229.0, 230.0],    [231.0, 232.0, 233.0, 234.0, 235.0],    [236.0, 237.0, 238.0, 239.0, 240.0],    [241.0, 242.0, 243.0, 244.0, 245.0],    [246.0, 247.0, 248.0, 249.0, 250.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.0, 0.0]]],  [[[26.0, 27.0, 28.0, 29.0, 30.0],    [31.0, 32.0, 33.0, 34.0, 35.0],    [36.0, 37.0, 38.0, 39.0, 40.0],    [41.0, 42.0, 43.0, 44.0, 45.0],    [46.0, 47.0, 48.0, 49.0, 50.0]],   [[101.0, 102.0, 103.0, 104.0, 105.0],    [106.0, 107.0, 108.0, 109.0, 110.0],    [111.0, 112.0, 113.0, 114.0, 115.0],    [116.0, 117.0, 118.0, 119.0, 120.0],    [121.0, 122.0, 123.0, 124.0, 125.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.0, 0.0]]],  [[[176.0, 177.0, 178.0, 179.0, 180.0],    [181.0, 182.0, 183.0, 184.0, 185.0],    [186.0, 187.0, 188.0, 189.0, 190.0],    [191.0, 192.0, 193.0, 194.0, 195.0],    [196.0, 197.0, 198.0, 199.0, 200.0]],   [[251.0, 252.0, 253.0, 254.0, 255.0],    [256.0, 257.0, 258.0, 259.0, 260.0],    [261.0, 262.0, 263.0, 264.0, 265.0],    [266.0, 267.0, 268.0, 269.0, 270.0],    [271.0, 272.0, 273.0, 274.0, 275.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.0, 0.0]]]] shape=[6, 3, 5, 5], strides=[75, 25, 5, 1], layout=C (0x1)), I32([3] shape=[1], strides=[1], layout=C | F (0x3)), I32([[1, 2]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 4193473029 3556217132 3271733469 2131532162 # shrinks to (ref i, ref bs, ref p) = (F32([[[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, 16.0],   [17.0, 18.0],   [19.0, 20.0],   [21.0, 22.0],   [23.0, 24.0],   [25.0, 26.0],   [27.0, 28.0]],  [[29.0, 30.0],   [31.0, 32.0],   [33.0, 34.0],   [35.0, 36.0],   [37.0, 38.0],   [39.0, 40.0],   [41.0, 42.0]]] shape=[3, 7, 2], strides=[14, 2, 1], layout=C (0x1)), I32([1] shape=[1], strides=[1], layout=C | F (0x3)), I32([[2, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 2919562556 1152727052 1831867138 1283103872 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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],      [16.0, 17.0, 18.0],      [19.0, 20.0, 21.0]],     [[22.0, 23.0, 24.0],      [25.0, 26.0, 27.0],      [28.0, 29.0, 30.0],      [31.0, 32.0, 33.0],      [34.0, 35.0, 36.0],      [37.0, 38.0, 39.0],      [40.0, 41.0, 42.0]],     [[43.0, 44.0, 45.0],      [46.0, 47.0, 48.0],      [49.0, 50.0, 51.0],      [52.0, 53.0, 54.0],      [55.0, 56.0, 57.0],      [58.0, 59.0, 60.0],      [61.0, 62.0, 63.0]],     [[64.0, 65.0, 66.0],      [67.0, 68.0, 69.0],      [70.0, 71.0, 72.0],      [73.0, 74.0, 75.0],      [76.0, 77.0, 78.0],      [79.0, 80.0, 81.0],      [82.0, 83.0, 84.0]],     [[85.0, 86.0, 87.0],      [88.0, 89.0, 90.0],      [91.0, 92.0, 93.0],      [94.0, 95.0, 96.0],      [97.0, 98.0, 99.0],      [100.0, 101.0, 102.0],      [103.0, 104.0, 105.0]],     [[106.0, 107.0, 108.0],      [109.0, 110.0, 111.0],      [112.0, 113.0, 114.0],      [115.0, 116.0, 117.0],      [118.0, 119.0, 120.0],      [121.0, 122.0, 123.0],      [124.0, 125.0, 126.0]]],    [[[127.0, 128.0, 129.0],      [130.0, 131.0, 132.0],      [133.0, 134.0, 135.0],      [136.0, 137.0, 138.0],      [139.0, 140.0, 141.0],      [142.0, 143.0, 144.0],      [145.0, 146.0, 147.0]],     [[148.0, 149.0, 150.0],      [151.0, 152.0, 153.0],      [154.0, 155.0, 156.0],      [157.0, 158.0, 159.0],      [160.0, 161.0, 162.0],      [163.0, 164.0, 165.0],      [166.0, 167.0, 168.0]],     [[169.0, 170.0, 171.0],      [172.0, 173.0, 174.0],      [175.0, 176.0, 177.0],      [178.0, 179.0, 180.0],      [181.0, 182.0, 183.0],      [184.0, 185.0, 186.0],      [187.0, 188.0, 189.0]],     [[190.0, 191.0, 192.0],      [193.0, 194.0, 195.0],      [196.0, 197.0, 198.0],      [199.0, 200.0, 201.0],      [202.0, 203.0, 204.0],      [205.0, 206.0, 207.0],      [208.0, 209.0, 210.0]],     [[211.0, 212.0, 213.0],      [214.0, 215.0, 216.0],      [217.0, 218.0, 219.0],      [220.0, 221.0, 222.0],      [223.0, 224.0, 225.0],      [226.0, 227.0, 228.0],      [229.0, 230.0, 231.0]],     [[232.0, 233.0, 234.0],      [235.0, 236.0, 237.0],      [238.0, 239.0, 240.0],      [241.0, 242.0, 243.0],      [244.0, 245.0, 246.0],      [247.0, 248.0, 249.0],      [250.0, 251.0, 252.0]]],    [[[253.0, 254.0, 255.0],      [256.0, 257.0, 258.0],      [259.0, 260.0, 261.0],      [262.0, 263.0, 264.0],      [265.0, 266.0, 267.0],      [268.0, 269.0, 270.0],      [271.0, 272.0, 273.0]],     [[274.0, 275.0, 276.0],      [277.0, 278.0, 279.0],      [280.0, 281.0, 282.0],      [283.0, 284.0, 285.0],      [286.0, 287.0, 288.0],      [289.0, 290.0, 291.0],      [292.0, 293.0, 294.0]],     [[295.0, 296.0, 297.0],      [298.0, 299.0, 300.0],      [301.0, 302.0, 303.0],      [304.0, 305.0, 306.0],      [307.0, 308.0, 309.0],      [310.0, 311.0, 312.0],      [313.0, 314.0, 315.0]],     [[316.0, 317.0, 318.0],      [319.0, 320.0, 321.0],      [322.0, 323.0, 324.0],      [325.0, 326.0, 327.0],      [328.0, 329.0, 330.0],      [331.0, 332.0, 333.0],      [334.0, 335.0, 336.0]],     [[337.0, 338.0, 339.0],      [340.0, 341.0, 342.0],      [343.0, 344.0, 345.0],      [346.0, 347.0, 348.0],      [349.0, 350.0, 351.0],      [352.0, 353.0, 354.0],      [355.0, 356.0, 357.0]],     [[358.0, 359.0, 360.0],      [361.0, 362.0, 363.0],      [364.0, 365.0, 366.0],      [367.0, 368.0, 369.0],      [370.0, 371.0, 372.0],      [373.0, 374.0, 375.0],      [376.0, 377.0, 378.0]]],    [[[379.0, 380.0, 381.0],      [382.0, 383.0, 384.0],      [385.0, 386.0, 387.0],      [388.0, 389.0, 390.0],      [391.0, 392.0, 393.0],      [394.0, 395.0, 396.0],      [397.0, 398.0, 399.0]],     [[400.0, 401.0, 402.0],      [403.0, 404.0, 405.0],      [406.0, 407.0, 408.0],      [409.0, 410.0, 411.0],      [412.0, 413.0, 414.0],      [415.0, 416.0, 417.0],      [418.0, 419.0, 420.0]],     [[421.0, 422.0, 423.0],      [424.0, 425.0, 426.0],      [427.0, 428.0, 429.0],      [430.0, 431.0, 432.0],      [433.0, 434.0, 435.0],      [436.0, 437.0, 438.0],      [439.0, 440.0, 441.0]],     [[442.0, 443.0, 444.0],      [445.0, 446.0, 447.0],      [448.0, 449.0, 450.0],      [451.0, 452.0, 453.0],      [454.0, 455.0, 456.0],      [457.0, 458.0, 459.0],      [460.0, 461.0, 462.0]],     [[463.0, 464.0, 465.0],      [466.0, 467.0, 468.0],      [469.0, 470.0, 471.0],      [472.0, 473.0, 474.0],      [475.0, 476.0, 477.0],      [478.0, 479.0, 480.0],      [481.0, 482.0, 483.0]],     [[484.0, 485.0, 486.0],      [487.0, 488.0, 489.0],      [490.0, 491.0, 492.0],      [493.0, 494.0, 495.0],      [496.0, 497.0, 498.0],      [499.0, 500.0, 501.0],      [502.0, 503.0, 504.0]]],    [[[505.0, 506.0, 507.0],      [508.0, 509.0, 510.0],      [511.0, 512.0, 513.0],      [514.0, 515.0, 516.0],      [517.0, 518.0, 519.0],      [520.0, 521.0, 522.0],      [523.0, 524.0, 525.0]],     [[526.0, 527.0, 528.0],      [529.0, 530.0, 531.0],      [532.0, 533.0, 534.0],      [535.0, 536.0, 537.0],      [538.0, 539.0, 540.0],      [541.0, 542.0, 543.0],      [544.0, 545.0, 546.0]],     [[547.0, 548.0, 549.0],      [550.0, 551.0, 552.0],      [553.0, 554.0, 555.0],      [556.0, 557.0, 558.0],      [559.0, 560.0, 561.0],      [562.0, 563.0, 564.0],      [565.0, 566.0, 567.0]],     [[568.0, 569.0, 570.0],      [571.0, 572.0, 573.0],      [574.0, 575.0, 576.0],      [577.0, 578.0, 579.0],      [580.0, 581.0, 582.0],      [583.0, 584.0, 585.0],      [586.0, 587.0, 588.0]],     [[589.0, 590.0, 591.0],      [592.0, 593.0, 594.0],      [595.0, 596.0, 597.0],      [598.0, 599.0, 600.0],      [601.0, 602.0, 603.0],      [604.0, 605.0, 606.0],      [607.0, 608.0, 609.0]],     [[610.0, 611.0, 612.0],      [613.0, 614.0, 615.0],      [616.0, 617.0, 618.0],      [619.0, 620.0, 621.0],      [622.0, 623.0, 624.0],      [625.0, 626.0, 627.0],      [628.0, 629.0, 630.0]]],    [[[631.0, 632.0, 633.0],      [634.0, 635.0, 636.0],      [637.0, 638.0, 639.0],      [640.0, 641.0, 642.0],      [643.0, 644.0, 645.0],      [646.0, 647.0, 648.0],      [649.0, 650.0, 651.0]],     [[652.0, 653.0, 654.0],      [655.0, 656.0, 657.0],      [658.0, 659.0, 660.0],      [661.0, 662.0, 663.0],      [664.0, 665.0, 666.0],      [667.0, 668.0, 669.0],      [670.0, 671.0, 672.0]],     [[673.0, 674.0, 675.0],      [676.0, 677.0, 678.0],      [679.0, 680.0, 681.0],      [682.0, 683.0, 684.0],      [685.0, 686.0, 687.0],      [688.0, 689.0, 690.0],      [691.0, 692.0, 693.0]],     [[694.0, 695.0, 696.0],      [697.0, 698.0, 699.0],      [700.0, 701.0, 702.0],      [703.0, 704.0, 705.0],      [706.0, 707.0, 708.0],      [709.0, 710.0, 711.0],      [712.0, 713.0, 714.0]],     [[715.0, 716.0, 717.0],      [718.0, 719.0, 720.0],      [721.0, 722.0, 723.0],      [724.0, 725.0, 726.0],      [727.0, 728.0, 729.0],      [730.0, 731.0, 732.0],      [733.0, 734.0, 735.0]],     [[736.0, 737.0, 738.0],      [739.0, 740.0, 741.0],      [742.0, 743.0, 744.0],      [745.0, 746.0, 747.0],      [748.0, 749.0, 750.0],      [751.0, 752.0, 753.0],      [754.0, 755.0, 756.0]]]],   [[[[757.0, 758.0, 759.0],      [760.0, 761.0, 762.0],      [763.0, 764.0, 765.0],      [766.0, 767.0, 768.0],      [769.0, 770.0, 771.0],      [772.0, 773.0, 774.0],      [775.0, 776.0, 777.0]],     [[778.0, 779.0, 780.0],      [781.0, 782.0, 783.0],      [784.0, 785.0, 786.0],      [787.0, 788.0, 789.0],      [790.0, 791.0, 792.0],      [793.0, 794.0, 795.0],      [796.0, 797.0, 798.0]],     [[799.0, 800.0, 801.0],      [802.0, 803.0, 804.0],      [805.0, 806.0, 807.0],      [808.0, 809.0, 810.0],      [811.0, 812.0, 813.0],      [814.0, 815.0, 816.0],      [817.0, 818.0, 819.0]],     [[820.0, 821.0, 822.0],      [823.0, 824.0, 825.0],      [826.0, 827.0, 828.0],      [829.0, 830.0, 831.0],      [832.0, 833.0, 834.0],      [835.0, 836.0, 837.0],      [838.0, 839.0, 840.0]],     [[841.0, 842.0, 843.0],      [844.0, 845.0, 846.0],      [847.0, 848.0, 849.0],      [850.0, 851.0, 852.0],      [853.0, 854.0, 855.0],      [856.0, 857.0, 858.0],      [859.0, 860.0, 861.0]],     [[862.0, 863.0, 864.0],      [865.0, 866.0, 867.0],      [868.0, 869.0, 870.0],      [871.0, 872.0, 873.0],      [874.0, 875.0, 876.0],      [877.0, 878.0, 879.0],      [880.0, 881.0, 882.0]]],    [[[883.0, 884.0, 885.0],      [886.0, 887.0, 888.0],      [889.0, 890.0, 891.0],      [892.0, 893.0, 894.0],      [895.0, 896.0, 897.0],      [898.0, 899.0, 900.0],      [901.0, 902.0, 903.0]],     [[904.0, 905.0, 906.0],      [907.0, 908.0, 909.0],      [910.0, 911.0, 912.0],      [913.0, 914.0, 915.0],      [916.0, 917.0, 918.0],      [919.0, 920.0, 921.0],      [922.0, 923.0, 924.0]],     [[925.0, 926.0, 927.0],      [928.0, 929.0, 930.0],      [931.0, 932.0, 933.0],      [934.0, 935.0, 936.0],      [937.0, 938.0, 939.0],      [940.0, 941.0, 942.0],      [943.0, 944.0, 945.0]],     [[946.0, 947.0, 948.0],      [949.0, 950.0, 951.0],      [952.0, 953.0, 954.0],      [955.0, 956.0, 957.0],      [958.0, 959.0, 960.0],      [961.0, 962.0, 963.0],      [964.0, 965.0, 966.0]],     [[967.0, 968.0, 969.0],      [970.0, 971.0, 972.0],      [973.0, 974.0, 975.0],      [976.0, 977.0, 978.0],      [979.0, 980.0, 981.0],      [982.0, 983.0, 984.0],      [985.0, 986.0, 987.0]],     [[988.0, 989.0, 990.0],      [991.0, 992.0, 993.0],      [994.0, 995.0, 996.0],      [997.0, 998.0, 999.0],      [1000.0, 1001.0, 1002.0],      [1003.0, 1004.0, 1005.0],      [1006.0, 1007.0, 1008.0]]],    [[[1009.0, 1010.0, 1011.0],      [1012.0, 1013.0, 1014.0],      [1015.0, 1016.0, 1017.0],      [1018.0, 1019.0, 1020.0],      [1021.0, 1022.0, 1023.0],      [1024.0, 1025.0, 1026.0],      [1027.0, 1028.0, 1029.0]],     [[1030.0, 1031.0, 1032.0],      [1033.0, 1034.0, 1035.0],      [1036.0, 1037.0, 1038.0],      [1039.0, 1040.0, 1041.0],      [1042.0, 1043.0, 1044.0],      [1045.0, 1046.0, 1047.0],      [1048.0, 1049.0, 1050.0]],     [[1051.0, 1052.0, 1053.0],      [1054.0, 1055.0, 1056.0],      [1057.0, 1058.0, 1059.0],      [1060.0, 1061.0, 1062.0],      [1063.0, 1064.0, 1065.0],      [1066.0, 1067.0, 1068.0],      [1069.0, 1070.0, 1071.0]],     [[1072.0, 1073.0, 1074.0],      [1075.0, 1076.0, 1077.0],      [1078.0, 1079.0, 1080.0],      [1081.0, 1082.0, 1083.0],      [1084.0, 1085.0, 1086.0],      [1087.0, 1088.0, 1089.0],      [1090.0, 1091.0, 1092.0]],     [[1093.0, 1094.0, 1095.0],      [1096.0, 1097.0, 1098.0],      [1099.0, 1100.0, 1101.0],      [1102.0, 1103.0, 1104.0],      [1105.0, 1106.0, 1107.0],      [1108.0, 1109.0, 1110.0],      [1111.0, 1112.0, 1113.0]],     [[1114.0, 1115.0, 1116.0],      [1117.0, 1118.0, 1119.0],      [1120.0, 1121.0, 1122.0],      [1123.0, 1124.0, 1125.0],      [1126.0, 1127.0, 1128.0],      [1129.0, 1130.0, 1131.0],      [1132.0, 1133.0, 1134.0]]],    [[[1135.0, 1136.0, 1137.0],      [1138.0, 1139.0, 1140.0],      [1141.0, 1142.0, 1143.0],      [1144.0, 1145.0, 1146.0],      [1147.0, 1148.0, 1149.0],      [1150.0, 1151.0, 1152.0],      [1153.0, 1154.0, 1155.0]],     [[1156.0, 1157.0, 1158.0],      [1159.0, 1160.0, 1161.0],      [1162.0, 1163.0, 1164.0],      [1165.0, 1166.0, 1167.0],      [1168.0, 1169.0, 1170.0],      [1171.0, 1172.0, 1173.0],      [1174.0, 1175.0, 1176.0]],     [[1177.0, 1178.0, 1179.0],      [1180.0, 1181.0, 1182.0],      [1183.0, 1184.0, 1185.0],      [1186.0, 1187.0, 1188.0],      [1189.0, 1190.0, 1191.0],      [1192.0, 1193.0, 1194.0],      [1195.0, 1196.0, 1197.0]],     [[1198.0, 1199.0, 1200.0],      [1201.0, 1202.0, 1203.0],      [1204.0, 1205.0, 1206.0],      [1207.0, 1208.0, 1209.0],      [1210.0, 1211.0, 1212.0],      [1213.0, 1214.0, 1215.0],      [1216.0, 1217.0, 1218.0]],     [[1219.0, 1220.0, 1221.0],      [1222.0, 1223.0, 1224.0],      [1225.0, 1226.0, 1227.0],      [1228.0, 1229.0, 1230.0],      [1231.0, 1232.0, 1233.0],      [1234.0, 1235.0, 1236.0],      [1237.0, 1238.0, 1239.0]],     [[1240.0, 1241.0, 1242.0],      [1243.0, 1244.0, 1245.0],      [1246.0, 1247.0, 1248.0],      [1249.0, 1250.0, 1251.0],      [1252.0, 1253.0, 1254.0],      [1255.0, 1256.0, 1257.0],      [1258.0, 1259.0, 1260.0]]],    [[[1261.0, 1262.0, 1263.0],      [1264.0, 1265.0, 1266.0],      [1267.0, 1268.0, 1269.0],      [1270.0, 1271.0, 1272.0],      [1273.0, 1274.0, 1275.0],      [1276.0, 1277.0, 1278.0],      [1279.0, 1280.0, 1281.0]],     [[1282.0, 1283.0, 1284.0],      [1285.0, 1286.0, 1287.0],      [1288.0, 1289.0, 1290.0],      [1291.0, 1292.0, 1293.0],      [1294.0, 1295.0, 1296.0],      [1297.0, 1298.0, 1299.0],      [1300.0, 1301.0, 1302.0]],     [[1303.0, 1304.0, 1305.0],      [1306.0, 1307.0, 1308.0],      [1309.0, 1310.0, 1311.0],      [1312.0, 1313.0, 1314.0],      [1315.0, 1316.0, 1317.0],      [1318.0, 1319.0, 1320.0],      [1321.0, 1322.0, 1323.0]],     [[1324.0, 1325.0, 1326.0],      [1327.0, 1328.0, 1329.0],      [1330.0, 1331.0, 1332.0],      [1333.0, 1334.0, 1335.0],      [1336.0, 1337.0, 1338.0],      [1339.0, 1340.0, 1341.0],      [1342.0, 1343.0, 1344.0]],     [[1345.0, 1346.0, 1347.0],      [1348.0, 1349.0, 1350.0],      [1351.0, 1352.0, 1353.0],      [1354.0, 1355.0, 1356.0],      [1357.0, 1358.0, 1359.0],      [1360.0, 1361.0, 1362.0],      [1363.0, 1364.0, 1365.0]],     [[1366.0, 1367.0, 1368.0],      [1369.0, 1370.0, 1371.0],      [1372.0, 1373.0, 1374.0],      [1375.0, 1376.0, 1377.0],      [1378.0, 1379.0, 1380.0],      [1381.0, 1382.0, 1383.0],      [1384.0, 1385.0, 1386.0]]],    [[[1387.0, 1388.0, 1389.0],      [1390.0, 1391.0, 1392.0],      [1393.0, 1394.0, 1395.0],      [1396.0, 1397.0, 1398.0],      [1399.0, 1400.0, 1401.0],      [1402.0, 1403.0, 1404.0],      [1405.0, 1406.0, 1407.0]],     [[1408.0, 1409.0, 1410.0],      [1411.0, 1412.0, 1413.0],      [1414.0, 1415.0, 1416.0],      [1417.0, 1418.0, 1419.0],      [1420.0, 1421.0, 1422.0],      [1423.0, 1424.0, 1425.0],      [1426.0, 1427.0, 1428.0]],     [[1429.0, 1430.0, 1431.0],      [1432.0, 1433.0, 1434.0],      [1435.0, 1436.0, 1437.0],      [1438.0, 1439.0, 1440.0],      [1441.0, 1442.0, 1443.0],      [1444.0, 1445.0, 1446.0],      [1447.0, 1448.0, 1449.0]],     [[1450.0, 1451.0, 1452.0],      [1453.0, 1454.0, 1455.0],      [1456.0, 1457.0, 1458.0],      [1459.0, 1460.0, 1461.0],      [1462.0, 1463.0, 1464.0],      [1465.0, 1466.0, 1467.0],      [1468.0, 1469.0, 1470.0]],     [[1471.0, 1472.0, 1473.0],      [1474.0, 1475.0, 1476.0],      [1477.0, 1478.0, 1479.0],      [1480.0, 1481.0, 1482.0],      [1483.0, 1484.0, 1485.0],      [1486.0, 1487.0, 1488.0],      [1489.0, 1490.0, 1491.0]],     [[1492.0, 1493.0, 1494.0],      [1495.0, 1496.0, 1497.0],      [1498.0, 1499.0, 1500.0],      [1501.0, 1502.0, 1503.0],      [1504.0, 1505.0, 1506.0],      [1507.0, 1508.0, 1509.0],      [1510.0, 1511.0, 1512.0]]]],   [[[[1513.0, 1514.0, 1515.0],      [1516.0, 1517.0, 1518.0],      [1519.0, 1520.0, 1521.0],      [1522.0, 1523.0, 1524.0],      [1525.0, 1526.0, 1527.0],      [1528.0, 1529.0, 1530.0],      [1531.0, 1532.0, 1533.0]],     [[1534.0, 1535.0, 1536.0],      [1537.0, 1538.0, 1539.0],      [1540.0, 1541.0, 1542.0],      [1543.0, 1544.0, 1545.0],      [1546.0, 1547.0, 1548.0],      [1549.0, 1550.0, 1551.0],      [1552.0, 1553.0, 1554.0]],     [[1555.0, 1556.0, 1557.0],      [1558.0, 1559.0, 1560.0],      [1561.0, 1562.0, 1563.0],      [1564.0, 1565.0, 1566.0],      [1567.0, 1568.0, 1569.0],      [1570.0, 1571.0, 1572.0],      [1573.0, 1574.0, 1575.0]],     [[1576.0, 1577.0, 1578.0],      [1579.0, 1580.0, 1581.0],      [1582.0, 1583.0, 1584.0],      [1585.0, 1586.0, 1587.0],      [1588.0, 1589.0, 1590.0],      [1591.0, 1592.0, 1593.0],      [1594.0, 1595.0, 1596.0]],     [[1597.0, 1598.0, 1599.0],      [1600.0, 1601.0, 1602.0],      [1603.0, 1604.0, 1605.0],      [1606.0, 1607.0, 1608.0],      [1609.0, 1610.0, 1611.0],      [1612.0, 1613.0, 1614.0],      [1615.0, 1616.0, 1617.0]],     [[1618.0, 1619.0, 1620.0],      [1621.0, 1622.0, 1623.0],      [1624.0, 1625.0, 1626.0],      [1627.0, 1628.0, 1629.0],      [1630.0, 1631.0, 1632.0],      [1633.0, 1634.0, 1635.0],      [1636.0, 1637.0, 1638.0]]],    [[[1639.0, 1640.0, 1641.0],      [1642.0, 1643.0, 1644.0],      [1645.0, 1646.0, 1647.0],      [1648.0, 1649.0, 1650.0],      [1651.0, 1652.0, 1653.0],      [1654.0, 1655.0, 1656.0],      [1657.0, 1658.0, 1659.0]],     [[1660.0, 1661.0, 1662.0],      [1663.0, 1664.0, 1665.0],      [1666.0, 1667.0, 1668.0],      [1669.0, 1670.0, 1671.0],      [1672.0, 1673.0, 1674.0],      [1675.0, 1676.0, 1677.0],      [1678.0, 1679.0, 1680.0]],     [[1681.0, 1682.0, 1683.0],      [1684.0, 1685.0, 1686.0],      [1687.0, 1688.0, 1689.0],      [1690.0, 1691.0, 1692.0],      [1693.0, 1694.0, 1695.0],      [1696.0, 1697.0, 1698.0],      [1699.0, 1700.0, 1701.0]],     [[1702.0, 1703.0, 1704.0],      [1705.0, 1706.0, 1707.0],      [1708.0, 1709.0, 1710.0],      [1711.0, 1712.0, 1713.0],      [1714.0, 1715.0, 1716.0],      [1717.0, 1718.0, 1719.0],      [1720.0, 1721.0, 1722.0]],     [[1723.0, 1724.0, 1725.0],      [1726.0, 1727.0, 1728.0],      [1729.0, 1730.0, 1731.0],      [1732.0, 1733.0, 1734.0],      [1735.0, 1736.0, 1737.0],      [1738.0, 1739.0, 1740.0],      [1741.0, 1742.0, 1743.0]],     [[1744.0, 1745.0, 1746.0],      [1747.0, 1748.0, 1749.0],      [1750.0, 1751.0, 1752.0],      [1753.0, 1754.0, 1755.0],      [1756.0, 1757.0, 1758.0],      [1759.0, 1760.0, 1761.0],      [1762.0, 1763.0, 1764.0]]],    [[[1765.0, 1766.0, 1767.0],      [1768.0, 1769.0, 1770.0],      [1771.0, 1772.0, 1773.0],      [1774.0, 1775.0, 1776.0],      [1777.0, 1778.0, 1779.0],      [1780.0, 1781.0, 1782.0],      [1783.0, 1784.0, 1785.0]],     [[1786.0, 1787.0, 1788.0],      [1789.0, 1790.0, 1791.0],      [1792.0, 1793.0, 1794.0],      [1795.0, 1796.0, 1797.0],      [1798.0, 1799.0, 1800.0],      [1801.0, 1802.0, 1803.0],      [1804.0, 1805.0, 1806.0]],     [[1807.0, 1808.0, 1809.0],      [1810.0, 1811.0, 1812.0],      [1813.0, 1814.0, 1815.0],      [1816.0, 1817.0, 1818.0],      [1819.0, 1820.0, 1821.0],      [1822.0, 1823.0, 1824.0],      [1825.0, 1826.0, 1827.0]],     [[1828.0, 1829.0, 1830.0],      [1831.0, 1832.0, 1833.0],      [1834.0, 1835.0, 1836.0],      [1837.0, 1838.0, 1839.0],      [1840.0, 1841.0, 1842.0],      [1843.0, 1844.0, 1845.0],      [1846.0, 1847.0, 1848.0]],     [[1849.0, 1850.0, 1851.0],      [1852.0, 1853.0, 1854.0],      [1855.0, 1856.0, 1857.0],      [1858.0, 1859.0, 1860.0],      [1861.0, 1862.0, 1863.0],      [1864.0, 1865.0, 1866.0],      [1867.0, 1868.0, 1869.0]],     [[1870.0, 1871.0, 1872.0],      [1873.0, 1874.0, 1875.0],      [1876.0, 1877.0, 1878.0],      [1879.0, 1880.0, 1881.0],      [1882.0, 1883.0, 1884.0],      [1885.0, 1886.0, 1887.0],      [1888.0, 1889.0, 1890.0]]],    [[[1891.0, 1892.0, 1893.0],      [1894.0, 1895.0, 1896.0],      [1897.0, 1898.0, 1899.0],      [1900.0, 1901.0, 1902.0],      [1903.0, 1904.0, 1905.0],      [1906.0, 1907.0, 1908.0],      [1909.0, 1910.0, 1911.0]],     [[1912.0, 1913.0, 1914.0],      [1915.0, 1916.0, 1917.0],      [1918.0, 1919.0, 1920.0],      [1921.0, 1922.0, 1923.0],      [1924.0, 1925.0, 1926.0],      [1927.0, 1928.0, 1929.0],      [1930.0, 1931.0, 1932.0]],     [[1933.0, 1934.0, 1935.0],      [1936.0, 1937.0, 1938.0],      [1939.0, 1940.0, 1941.0],      [1942.0, 1943.0, 1944.0],      [1945.0, 1946.0, 1947.0],      [1948.0, 1949.0, 1950.0],      [1951.0, 1952.0, 1953.0]],     [[1954.0, 1955.0, 1956.0],      [1957.0, 1958.0, 1959.0],      [1960.0, 1961.0, 1962.0],      [1963.0, 1964.0, 1965.0],      [1966.0, 1967.0, 1968.0],      [1969.0, 1970.0, 1971.0],      [1972.0, 1973.0, 1974.0]],     [[1975.0, 1976.0, 1977.0],      [1978.0, 1979.0, 1980.0],      [1981.0, 1982.0, 1983.0],      [1984.0, 1985.0, 1986.0],      [1987.0, 1988.0, 1989.0],      [1990.0, 1991.0, 1992.0],      [1993.0, 1994.0, 1995.0]],     [[1996.0, 1997.0, 1998.0],      [1999.0, 2000.0, 2001.0],      [2002.0, 2003.0, 2004.0],      [2005.0, 2006.0, 2007.0],      [2008.0, 2009.0, 2010.0],      [2011.0, 2012.0, 2013.0],      [2014.0, 2015.0, 2016.0]]],    [[[2017.0, 2018.0, 2019.0],      [2020.0, 2021.0, 2022.0],      [2023.0, 2024.0, 2025.0],      [2026.0, 2027.0, 2028.0],      [2029.0, 2030.0, 2031.0],      [2032.0, 2033.0, 2034.0],      [2035.0, 2036.0, 2037.0]],     [[2038.0, 2039.0, 2040.0],      [2041.0, 2042.0, 2043.0],      [2044.0, 2045.0, 2046.0],      [2047.0, 2048.0, 2049.0],      [2050.0, 2051.0, 2052.0],      [2053.0, 2054.0, 2055.0],      [2056.0, 2057.0, 2058.0]],     [[2059.0, 2060.0, 2061.0],      [2062.0, 2063.0, 2064.0],      [2065.0, 2066.0, 2067.0],      [2068.0, 2069.0, 2070.0],      [2071.0, 2072.0, 2073.0],      [2074.0, 2075.0, 2076.0],      [2077.0, 2078.0, 2079.0]],     [[2080.0, 2081.0, 2082.0],      [2083.0, 2084.0, 2085.0],      [2086.0, 2087.0, 2088.0],      [2089.0, 2090.0, 2091.0],      [2092.0, 2093.0, 2094.0],      [2095.0, 2096.0, 2097.0],      [2098.0, 2099.0, 2100.0]],     [[2101.0, 2102.0, 2103.0],      [2104.0, 2105.0, 2106.0],      [2107.0, 2108.0, 2109.0],      [2110.0, 2111.0, 2112.0],      [2113.0, 2114.0, 2115.0],      [2116.0, 2117.0, 2118.0],      [2119.0, 2120.0, 2121.0]],     [[2122.0, 2123.0, 2124.0],      [2125.0, 2126.0, 2127.0],      [2128.0, 2129.0, 2130.0],      [2131.0, 2132.0, 2133.0],      [2134.0, 2135.0, 2136.0],      [2137.0, 2138.0, 2139.0],      [2140.0, 2141.0, 2142.0]]],    [[[2143.0, 2144.0, 2145.0],      [2146.0, 2147.0, 2148.0],      [2149.0, 2150.0, 2151.0],      [2152.0, 2153.0, 2154.0],      [2155.0, 2156.0, 2157.0],      [2158.0, 2159.0, 2160.0],      [2161.0, 2162.0, 2163.0]],     [[2164.0, 2165.0, 2166.0],      [2167.0, 2168.0, 2169.0],      [2170.0, 2171.0, 2172.0],      [2173.0, 2174.0, 2175.0],      [2176.0, 2177.0, 2178.0],      [2179.0, 2180.0, 2181.0],      [2182.0, 2183.0, 2184.0]],     [[2185.0, 2186.0, 2187.0],      [2188.0, 2189.0, 2190.0],      [2191.0, 2192.0, 2193.0],      [2194.0, 2195.0, 2196.0],      [2197.0, 2198.0, 2199.0],      [2200.0, 2201.0, 2202.0],      [2203.0, 2204.0, 2205.0]],     [[2206.0, 2207.0, 2208.0],      [2209.0, 2210.0, 2211.0],      [2212.0, 2213.0, 2214.0],      [2215.0, 2216.0, 2217.0],      [2218.0, 2219.0, 2220.0],      [2221.0, 2222.0, 2223.0],      [2224.0, 2225.0, 2226.0]],     [[2227.0, 2228.0, 2229.0],      [2230.0, 2231.0, 2232.0],      [2233.0, 2234.0, 2235.0],      [2236.0, 2237.0, 2238.0],      [2239.0, 2240.0, 2241.0],      [2242.0, 2243.0, 2244.0],      [2245.0, 2246.0, 2247.0]],     [[2248.0, 2249.0, 2250.0],      [2251.0, 2252.0, 2253.0],      [2254.0, 2255.0, 2256.0],      [2257.0, 2258.0, 2259.0],      [2260.0, 2261.0, 2262.0],      [2263.0, 2264.0, 2265.0],      [2266.0, 2267.0, 2268.0]]]],   [[[[2269.0, 2270.0, 2271.0],      [2272.0, 2273.0, 2274.0],      [2275.0, 2276.0, 2277.0],      [2278.0, 2279.0, 2280.0],      [2281.0, 2282.0, 2283.0],      [2284.0, 2285.0, 2286.0],      [2287.0, 2288.0, 2289.0]],     [[2290.0, 2291.0, 2292.0],      [2293.0, 2294.0, 2295.0],      [2296.0, 2297.0, 2298.0],      [2299.0, 2300.0, 2301.0],      [2302.0, 2303.0, 2304.0],      [2305.0, 2306.0, 2307.0],      [2308.0, 2309.0, 2310.0]],     [[2311.0, 2312.0, 2313.0],      [2314.0, 2315.0, 2316.0],      [2317.0, 2318.0, 2319.0],      [2320.0, 2321.0, 2322.0],      [2323.0, 2324.0, 2325.0],      [2326.0, 2327.0, 2328.0],      [2329.0, 2330.0, 2331.0]],     [[2332.0, 2333.0, 2334.0],      [2335.0, 2336.0, 2337.0],      [2338.0, 2339.0, 2340.0],      [2341.0, 2342.0, 2343.0],      [2344.0, 2345.0, 2346.0],      [2347.0, 2348.0, 2349.0],      [2350.0, 2351.0, 2352.0]],     [[2353.0, 2354.0, 2355.0],      [2356.0, 2357.0, 2358.0],      [2359.0, 2360.0, 2361.0],      [2362.0, 2363.0, 2364.0],      [2365.0, 2366.0, 2367.0],      [2368.0, 2369.0, 2370.0],      [2371.0, 2372.0, 2373.0]],     [[2374.0, 2375.0, 2376.0],      [2377.0, 2378.0, 2379.0],      [2380.0, 2381.0, 2382.0],      [2383.0, 2384.0, 2385.0],      [2386.0, 2387.0, 2388.0],      [2389.0, 2390.0, 2391.0],      [2392.0, 2393.0, 2394.0]]],    [[[2395.0, 2396.0, 2397.0],      [2398.0, 2399.0, 2400.0],      [2401.0, 2402.0, 2403.0],      [2404.0, 2405.0, 2406.0],      [2407.0, 2408.0, 2409.0],      [2410.0, 2411.0, 2412.0],      [2413.0, 2414.0, 2415.0]],     [[2416.0, 2417.0, 2418.0],      [2419.0, 2420.0, 2421.0],      [2422.0, 2423.0, 2424.0],      [2425.0, 2426.0, 2427.0],      [2428.0, 2429.0, 2430.0],      [2431.0, 2432.0, 2433.0],      [2434.0, 2435.0, 2436.0]],     [[2437.0, 2438.0, 2439.0],      [2440.0, 2441.0, 2442.0],      [2443.0, 2444.0, 2445.0],      [2446.0, 2447.0, 2448.0],      [2449.0, 2450.0, 2451.0],      [2452.0, 2453.0, 2454.0],      [2455.0, 2456.0, 2457.0]],     [[2458.0, 2459.0, 2460.0],      [2461.0, 2462.0, 2463.0],      [2464.0, 2465.0, 2466.0],      [2467.0, 2468.0, 2469.0],      [2470.0, 2471.0, 2472.0],      [2473.0, 2474.0, 2475.0],      [2476.0, 2477.0, 2478.0]],     [[2479.0, 2480.0, 2481.0],      [2482.0, 2483.0, 2484.0],      [2485.0, 2486.0, 2487.0],      [2488.0, 2489.0, 2490.0],      [2491.0, 2492.0, 2493.0],      [2494.0, 2495.0, 2496.0],      [2497.0, 2498.0, 2499.0]],     [[2500.0, 2501.0, 2502.0],      [2503.0, 2504.0, 2505.0],      [2506.0, 2507.0, 2508.0],      [2509.0, 2510.0, 2511.0],      [2512.0, 2513.0, 2514.0],      [2515.0, 2516.0, 2517.0],      [2518.0, 2519.0, 2520.0]]],    [[[2521.0, 2522.0, 2523.0],      [2524.0, 2525.0, 2526.0],      [2527.0, 2528.0, 2529.0],      [2530.0, 2531.0, 2532.0],      [2533.0, 2534.0, 2535.0],      [2536.0, 2537.0, 2538.0],      [2539.0, 2540.0, 2541.0]],     [[2542.0, 2543.0, 2544.0],      [2545.0, 2546.0, 2547.0],      [2548.0, 2549.0, 2550.0],      [2551.0, 2552.0, 2553.0],      [2554.0, 2555.0, 2556.0],      [2557.0, 2558.0, 2559.0],      [2560.0, 2561.0, 2562.0]],     [[2563.0, 2564.0, 2565.0],      [2566.0, 2567.0, 2568.0],      [2569.0, 2570.0, 2571.0],      [2572.0, 2573.0, 2574.0],      [2575.0, 2576.0, 2577.0],      [2578.0, 2579.0, 2580.0],      [2581.0, 2582.0, 2583.0]],     [[2584.0, 2585.0, 2586.0],      [2587.0, 2588.0, 2589.0],      [2590.0, 2591.0, 2592.0],      [2593.0, 2594.0, 2595.0],      [2596.0, 2597.0, 2598.0],      [2599.0, 2600.0, 2601.0],      [2602.0, 2603.0, 2604.0]],     [[2605.0, 2606.0, 2607.0],      [2608.0, 2609.0, 2610.0],      [2611.0, 2612.0, 2613.0],      [2614.0, 2615.0, 2616.0],      [2617.0, 2618.0, 2619.0],      [2620.0, 2621.0, 2622.0],      [2623.0, 2624.0, 2625.0]],     [[2626.0, 2627.0, 2628.0],      [2629.0, 2630.0, 2631.0],      [2632.0, 2633.0, 2634.0],      [2635.0, 2636.0, 2637.0],      [2638.0, 2639.0, 2640.0],      [2641.0, 2642.0, 2643.0],      [2644.0, 2645.0, 2646.0]]],    [[[2647.0, 2648.0, 2649.0],      [2650.0, 2651.0, 2652.0],      [2653.0, 2654.0, 2655.0],      [2656.0, 2657.0, 2658.0],      [2659.0, 2660.0, 2661.0],      [2662.0, 2663.0, 2664.0],      [2665.0, 2666.0, 2667.0]],     [[2668.0, 2669.0, 2670.0],      [2671.0, 2672.0, 2673.0],      [2674.0, 2675.0, 2676.0],      [2677.0, 2678.0, 2679.0],      [2680.0, 2681.0, 2682.0],      [2683.0, 2684.0, 2685.0],      [2686.0, 2687.0, 2688.0]],     [[2689.0, 2690.0, 2691.0],      [2692.0, 2693.0, 2694.0],      [2695.0, 2696.0, 2697.0],      [2698.0, 2699.0, 2700.0],      [2701.0, 2702.0, 2703.0],      [2704.0, 2705.0, 2706.0],      [2707.0, 2708.0, 2709.0]],     [[2710.0, 2711.0, 2712.0],      [2713.0, 2714.0, 2715.0],      [2716.0, 2717.0, 2718.0],      [2719.0, 2720.0, 2721.0],      [2722.0, 2723.0, 2724.0],      [2725.0, 2726.0, 2727.0],      [2728.0, 2729.0, 2730.0]],     [[2731.0, 2732.0, 2733.0],      [2734.0, 2735.0, 2736.0],      [2737.0, 2738.0, 2739.0],      [2740.0, 2741.0, 2742.0],      [2743.0, 2744.0, 2745.0],      [2746.0, 2747.0, 2748.0],      [2749.0, 2750.0, 2751.0]],     [[2752.0, 2753.0, 2754.0],      [2755.0, 2756.0, 2757.0],      [2758.0, 2759.0, 2760.0],      [2761.0, 2762.0, 2763.0],      [2764.0, 2765.0, 2766.0],      [2767.0, 2768.0, 2769.0],      [2770.0, 2771.0, 2772.0]]],    [[[2773.0, 2774.0, 2775.0],      [2776.0, 2777.0, 2778.0],      [2779.0, 2780.0, 2781.0],      [2782.0, 2783.0, 2784.0],      [2785.0, 2786.0, 2787.0],      [2788.0, 2789.0, 2790.0],      [2791.0, 2792.0, 2793.0]],     [[2794.0, 2795.0, 2796.0],      [2797.0, 2798.0, 2799.0],      [2800.0, 2801.0, 2802.0],      [2803.0, 2804.0, 2805.0],      [2806.0, 2807.0, 2808.0],      [2809.0, 2810.0, 2811.0],      [2812.0, 2813.0, 2814.0]],     [[2815.0, 2816.0, 2817.0],      [2818.0, 2819.0, 2820.0],      [2821.0, 2822.0, 2823.0],      [2824.0, 2825.0, 2826.0],      [2827.0, 2828.0, 2829.0],      [2830.0, 2831.0, 2832.0],      [2833.0, 2834.0, 2835.0]],     [[2836.0, 2837.0, 2838.0],      [2839.0, 2840.0, 2841.0],      [2842.0, 2843.0, 2844.0],      [2845.0, 2846.0, 2847.0],      [2848.0, 2849.0, 2850.0],      [2851.0, 2852.0, 2853.0],      [2854.0, 2855.0, 2856.0]],     [[2857.0, 2858.0, 2859.0],      [2860.0, 2861.0, 2862.0],      [2863.0, 2864.0, 2865.0],      [2866.0, 2867.0, 2868.0],      [2869.0, 2870.0, 2871.0],      [2872.0, 2873.0, 2874.0],      [2875.0, 2876.0, 2877.0]],     [[2878.0, 2879.0, 2880.0],      [2881.0, 2882.0, 2883.0],      [2884.0, 2885.0, 2886.0],      [2887.0, 2888.0, 2889.0],      [2890.0, 2891.0, 2892.0],      [2893.0, 2894.0, 2895.0],      [2896.0, 2897.0, 2898.0]]],    [[[2899.0, 2900.0, 2901.0],      [2902.0, 2903.0, 2904.0],      [2905.0, 2906.0, 2907.0],      [2908.0, 2909.0, 2910.0],      [2911.0, 2912.0, 2913.0],      [2914.0, 2915.0, 2916.0],      [2917.0, 2918.0, 2919.0]],     [[2920.0, 2921.0, 2922.0],      [2923.0, 2924.0, 2925.0],      [2926.0, 2927.0, 2928.0],      [2929.0, 2930.0, 2931.0],      [2932.0, 2933.0, 2934.0],      [2935.0, 2936.0, 2937.0],      [2938.0, 2939.0, 2940.0]],     [[2941.0, 2942.0, 2943.0],      [2944.0, 2945.0, 2946.0],      [2947.0, 2948.0, 2949.0],      [2950.0, 2951.0, 2952.0],      [2953.0, 2954.0, 2955.0],      [2956.0, 2957.0, 2958.0],      [2959.0, 2960.0, 2961.0]],     [[2962.0, 2963.0, 2964.0],      [2965.0, 2966.0, 2967.0],      [2968.0, 2969.0, 2970.0],      [2971.0, 2972.0, 2973.0],      [2974.0, 2975.0, 2976.0],      [2977.0, 2978.0, 2979.0],      [2980.0, 2981.0, 2982.0]],     [[2983.0, 2984.0, 2985.0],      [2986.0, 2987.0, 2988.0],      [2989.0, 2990.0, 2991.0],      [2992.0, 2993.0, 2994.0],      [2995.0, 2996.0, 2997.0],      [2998.0, 2999.0, 3000.0],      [3001.0, 3002.0, 3003.0]],     [[3004.0, 3005.0, 3006.0],      [3007.0, 3008.0, 3009.0],      [3010.0, 3011.0, 3012.0],      [3013.0, 3014.0, 3015.0],      [3016.0, 3017.0, 3018.0],      [3019.0, 3020.0, 3021.0],      [3022.0, 3023.0, 3024.0]]]],   [[[[3025.0, 3026.0, 3027.0],      [3028.0, 3029.0, 3030.0],      [3031.0, 3032.0, 3033.0],      [3034.0, 3035.0, 3036.0],      [3037.0, 3038.0, 3039.0],      [3040.0, 3041.0, 3042.0],      [3043.0, 3044.0, 3045.0]],     [[3046.0, 3047.0, 3048.0],      [3049.0, 3050.0, 3051.0],      [3052.0, 3053.0, 3054.0],      [3055.0, 3056.0, 3057.0],      [3058.0, 3059.0, 3060.0],      [3061.0, 3062.0, 3063.0],      [3064.0, 3065.0, 3066.0]],     [[3067.0, 3068.0, 3069.0],      [3070.0, 3071.0, 3072.0],      [3073.0, 3074.0, 3075.0],      [3076.0, 3077.0, 3078.0],      [3079.0, 3080.0, 3081.0],      [3082.0, 3083.0, 3084.0],      [3085.0, 3086.0, 3087.0]],     [[3088.0, 3089.0, 3090.0],      [3091.0, 3092.0, 3093.0],      [3094.0, 3095.0, 3096.0],      [3097.0, 3098.0, 3099.0],      [3100.0, 3101.0, 3102.0],      [3103.0, 3104.0, 3105.0],      [3106.0, 3107.0, 3108.0]],     [[3109.0, 3110.0, 3111.0],      [3112.0, 3113.0, 3114.0],      [3115.0, 3116.0, 3117.0],      [3118.0, 3119.0, 3120.0],      [3121.0, 3122.0, 3123.0],      [3124.0, 3125.0, 3126.0],      [3127.0, 3128.0, 3129.0]],     [[3130.0, 3131.0, 3132.0],      [3133.0, 3134.0, 3135.0],      [3136.0, 3137.0, 3138.0],      [3139.0, 3140.0, 3141.0],      [3142.0, 3143.0, 3144.0],      [3145.0, 3146.0, 3147.0],      [3148.0, 3149.0, 3150.0]]],    [[[3151.0, 3152.0, 3153.0],      [3154.0, 3155.0, 3156.0],      [3157.0, 3158.0, 3159.0],      [3160.0, 3161.0, 3162.0],      [3163.0, 3164.0, 3165.0],      [3166.0, 3167.0, 3168.0],      [3169.0, 3170.0, 3171.0]],     [[3172.0, 3173.0, 3174.0],      [3175.0, 3176.0, 3177.0],      [3178.0, 3179.0, 3180.0],      [3181.0, 3182.0, 3183.0],      [3184.0, 3185.0, 3186.0],      [3187.0, 3188.0, 3189.0],      [3190.0, 3191.0, 3192.0]],     [[3193.0, 3194.0, 3195.0],      [3196.0, 3197.0, 3198.0],      [3199.0, 3200.0, 3201.0],      [3202.0, 3203.0, 3204.0],      [3205.0, 3206.0, 3207.0],      [3208.0, 3209.0, 3210.0],      [3211.0, 3212.0, 3213.0]],     [[3214.0, 3215.0, 3216.0],      [3217.0, 3218.0, 3219.0],      [3220.0, 3221.0, 3222.0],      [3223.0, 3224.0, 3225.0],      [3226.0, 3227.0, 3228.0],      [3229.0, 3230.0, 3231.0],      [3232.0, 3233.0, 3234.0]],     [[3235.0, 3236.0, 3237.0],      [3238.0, 3239.0, 3240.0],      [3241.0, 3242.0, 3243.0],      [3244.0, 3245.0, 3246.0],      [3247.0, 3248.0, 3249.0],      [3250.0, 3251.0, 3252.0],      [3253.0, 3254.0, 3255.0]],     [[3256.0, 3257.0, 3258.0],      [3259.0, 3260.0, 3261.0],      [3262.0, 3263.0, 3264.0],      [3265.0, 3266.0, 3267.0],      [3268.0, 3269.0, 3270.0],      [3271.0, 3272.0, 3273.0],      [3274.0, 3275.0, 3276.0]]],    [[[3277.0, 3278.0, 3279.0],      [3280.0, 3281.0, 3282.0],      [3283.0, 3284.0, 3285.0],      [3286.0, 3287.0, 3288.0],      [3289.0, 3290.0, 3291.0],      [3292.0, 3293.0, 3294.0],      [3295.0, 3296.0, 3297.0]],     [[3298.0, 3299.0, 3300.0],      [3301.0, 3302.0, 3303.0],      [3304.0, 3305.0, 3306.0],      [3307.0, 3308.0, 3309.0],      [3310.0, 3311.0, 3312.0],      [3313.0, 3314.0, 3315.0],      [3316.0, 3317.0, 3318.0]],     [[3319.0, 3320.0, 3321.0],      [3322.0, 3323.0, 3324.0],      [3325.0, 3326.0, 3327.0],      [3328.0, 3329.0, 3330.0],      [3331.0, 3332.0, 3333.0],      [3334.0, 3335.0, 3336.0],      [3337.0, 3338.0, 3339.0]],     [[3340.0, 3341.0, 3342.0],      [3343.0, 3344.0, 3345.0],      [3346.0, 3347.0, 3348.0],      [3349.0, 3350.0, 3351.0],      [3352.0, 3353.0, 3354.0],      [3355.0, 3356.0, 3357.0],      [3358.0, 3359.0, 3360.0]],     [[3361.0, 3362.0, 3363.0],      [3364.0, 3365.0, 3366.0],      [3367.0, 3368.0, 3369.0],      [3370.0, 3371.0, 3372.0],      [3373.0, 3374.0, 3375.0],      [3376.0, 3377.0, 3378.0],      [3379.0, 3380.0, 3381.0]],     [[3382.0, 3383.0, 3384.0],      [3385.0, 3386.0, 3387.0],      [3388.0, 3389.0, 3390.0],      [3391.0, 3392.0, 3393.0],      [3394.0, 3395.0, 3396.0],      [3397.0, 3398.0, 3399.0],      [3400.0, 3401.0, 3402.0]]],    [[[3403.0, 3404.0, 3405.0],      [3406.0, 3407.0, 3408.0],      [3409.0, 3410.0, 3411.0],      [3412.0, 3413.0, 3414.0],      [3415.0, 3416.0, 3417.0],      [3418.0, 3419.0, 3420.0],      [3421.0, 3422.0, 3423.0]],     [[3424.0, 3425.0, 3426.0],      [3427.0, 3428.0, 3429.0],      [3430.0, 3431.0, 3432.0],      [3433.0, 3434.0, 3435.0],      [3436.0, 3437.0, 3438.0],      [3439.0, 3440.0, 3441.0],      [3442.0, 3443.0, 3444.0]],     [[3445.0, 3446.0, 3447.0],      [3448.0, 3449.0, 3450.0],      [3451.0, 3452.0, 3453.0],      [3454.0, 3455.0, 3456.0],      [3457.0, 3458.0, 3459.0],      [3460.0, 3461.0, 3462.0],      [3463.0, 3464.0, 3465.0]],     [[3466.0, 3467.0, 3468.0],      [3469.0, 3470.0, 3471.0],      [3472.0, 3473.0, 3474.0],      [3475.0, 3476.0, 3477.0],      [3478.0, 3479.0, 3480.0],      [3481.0, 3482.0, 3483.0],      [3484.0, 3485.0, 3486.0]],     [[3487.0, 3488.0, 3489.0],      [3490.0, 3491.0, 3492.0],      [3493.0, 3494.0, 3495.0],      [3496.0, 3497.0, 3498.0],      [3499.0, 3500.0, 3501.0],      [3502.0, 3503.0, 3504.0],      [3505.0, 3506.0, 3507.0]],     [[3508.0, 3509.0, 3510.0],      [3511.0, 3512.0, 3513.0],      [3514.0, 3515.0, 3516.0],      [3517.0, 3518.0, 3519.0],      [3520.0, 3521.0, 3522.0],      [3523.0, 3524.0, 3525.0],      [3526.0, 3527.0, 3528.0]]],    [[[3529.0, 3530.0, 3531.0],      [3532.0, 3533.0, 3534.0],      [3535.0, 3536.0, 3537.0],      [3538.0, 3539.0, 3540.0],      [3541.0, 3542.0, 3543.0],      [3544.0, 3545.0, 3546.0],      [3547.0, 3548.0, 3549.0]],     [[3550.0, 3551.0, 3552.0],      [3553.0, 3554.0, 3555.0],      [3556.0, 3557.0, 3558.0],      [3559.0, 3560.0, 3561.0],      [3562.0, 3563.0, 3564.0],      [3565.0, 3566.0, 3567.0],      [3568.0, 3569.0, 3570.0]],     [[3571.0, 3572.0, 3573.0],      [3574.0, 3575.0, 3576.0],      [3577.0, 3578.0, 3579.0],      [3580.0, 3581.0, 3582.0],      [3583.0, 3584.0, 3585.0],      [3586.0, 3587.0, 3588.0],      [3589.0, 3590.0, 3591.0]],     [[3592.0, 3593.0, 3594.0],      [3595.0, 3596.0, 3597.0],      [3598.0, 3599.0, 3600.0],      [3601.0, 3602.0, 3603.0],      [3604.0, 3605.0, 3606.0],      [3607.0, 3608.0, 3609.0],      [3610.0, 3611.0, 3612.0]],     [[3613.0, 3614.0, 3615.0],      [3616.0, 3617.0, 3618.0],      [3619.0, 3620.0, 3621.0],      [3622.0, 3623.0, 3624.0],      [3625.0, 3626.0, 3627.0],      [3628.0, 3629.0, 3630.0],      [3631.0, 3632.0, 3633.0]],     [[3634.0, 3635.0, 3636.0],      [3637.0, 3638.0, 3639.0],      [3640.0, 3641.0, 3642.0],      [3643.0, 3644.0, 3645.0],      [3646.0, 3647.0, 3648.0],      [3649.0, 3650.0, 3651.0],      [3652.0, 3653.0, 3654.0]]],    [[[3655.0, 3656.0, 3657.0],      [3658.0, 3659.0, 3660.0],      [3661.0, 3662.0, 3663.0],      [3664.0, 3665.0, 3666.0],      [3667.0, 3668.0, 3669.0],      [3670.0, 3671.0, 3672.0],      [3673.0, 3674.0, 3675.0]],     [[3676.0, 3677.0, 3678.0],      [3679.0, 3680.0, 3681.0],      [3682.0, 3683.0, 3684.0],      [3685.0, 3686.0, 3687.0],      [3688.0, 3689.0, 3690.0],      [3691.0, 3692.0, 3693.0],      [3694.0, 3695.0, 3696.0]],     [[3697.0, 3698.0, 3699.0],      [3700.0, 3701.0, 3702.0],      [3703.0, 3704.0, 3705.0],      [3706.0, 3707.0, 3708.0],      [3709.0, 3710.0, 3711.0],      [3712.0, 3713.0, 3714.0],      [3715.0, 3716.0, 3717.0]],     [[3718.0, 3719.0, 3720.0],      [3721.0, 3722.0, 3723.0],      [3724.0, 3725.0, 3726.0],      [3727.0, 3728.0, 3729.0],      [3730.0, 3731.0, 3732.0],      [3733.0, 3734.0, 3735.0],      [3736.0, 3737.0, 3738.0]],     [[3739.0, 3740.0, 3741.0],      [3742.0, 3743.0, 3744.0],      [3745.0, 3746.0, 3747.0],      [3748.0, 3749.0, 3750.0],      [3751.0, 3752.0, 3753.0],      [3754.0, 3755.0, 3756.0],      [3757.0, 3758.0, 3759.0]],     [[3760.0, 3761.0, 3762.0],      [3763.0, 3764.0, 3765.0],      [3766.0, 3767.0, 3768.0],      [3769.0, 3770.0, 3771.0],      [3772.0, 3773.0, 3774.0],      [3775.0, 3776.0, 3777.0],      [3778.0, 3779.0, 3780.0]]]],   [[[[3781.0, 3782.0, 3783.0],      [3784.0, 3785.0, 3786.0],      [3787.0, 3788.0, 3789.0],      [3790.0, 3791.0, 3792.0],      [3793.0, 3794.0, 3795.0],      [3796.0, 3797.0, 3798.0],      [3799.0, 3800.0, 3801.0]],     [[3802.0, 3803.0, 3804.0],      [3805.0, 3806.0, 3807.0],      [3808.0, 3809.0, 3810.0],      [3811.0, 3812.0, 3813.0],      [3814.0, 3815.0, 3816.0],      [3817.0, 3818.0, 3819.0],      [3820.0, 3821.0, 3822.0]],     [[3823.0, 3824.0, 3825.0],      [3826.0, 3827.0, 3828.0],      [3829.0, 3830.0, 3831.0],      [3832.0, 3833.0, 3834.0],      [3835.0, 3836.0, 3837.0],      [3838.0, 3839.0, 3840.0],      [3841.0, 3842.0, 3843.0]],     [[3844.0, 3845.0, 3846.0],      [3847.0, 3848.0, 3849.0],      [3850.0, 3851.0, 3852.0],      [3853.0, 3854.0, 3855.0],      [3856.0, 3857.0, 3858.0],      [3859.0, 3860.0, 3861.0],      [3862.0, 3863.0, 3864.0]],     [[3865.0, 3866.0, 3867.0],      [3868.0, 3869.0, 3870.0],      [3871.0, 3872.0, 3873.0],      [3874.0, 3875.0, 3876.0],      [3877.0, 3878.0, 3879.0],      [3880.0, 3881.0, 3882.0],      [3883.0, 3884.0, 3885.0]],     [[3886.0, 3887.0, 3888.0],      [3889.0, 3890.0, 3891.0],      [3892.0, 3893.0, 3894.0],      [3895.0, 3896.0, 3897.0],      [3898.0, 3899.0, 3900.0],      [3901.0, 3902.0, 3903.0],      [3904.0, 3905.0, 3906.0]]],    [[[3907.0, 3908.0, 3909.0],      [3910.0, 3911.0, 3912.0],      [3913.0, 3914.0, 3915.0],      [3916.0, 3917.0, 3918.0],      [3919.0, 3920.0, 3921.0],      [3922.0, 3923.0, 3924.0],      [3925.0, 3926.0, 3927.0]],     [[3928.0, 3929.0, 3930.0],      [3931.0, 3932.0, 3933.0],      [3934.0, 3935.0, 3936.0],      [3937.0, 3938.0, 3939.0],      [3940.0, 3941.0, 3942.0],      [3943.0, 3944.0, 3945.0],      [3946.0, 3947.0, 3948.0]],     [[3949.0, 3950.0, 3951.0],      [3952.0, 3953.0, 3954.0],      [3955.0, 3956.0, 3957.0],      [3958.0, 3959.0, 3960.0],      [3961.0, 3962.0, 3963.0],      [3964.0, 3965.0, 3966.0],      [3967.0, 3968.0, 3969.0]],     [[3970.0, 3971.0, 3972.0],      [3973.0, 3974.0, 3975.0],      [3976.0, 3977.0, 3978.0],      [3979.0, 3980.0, 3981.0],      [3982.0, 3983.0, 3984.0],      [3985.0, 3986.0, 3987.0],      [3988.0, 3989.0, 3990.0]],     [[3991.0, 3992.0, 3993.0],      [3994.0, 3995.0, 3996.0],      [3997.0, 3998.0, 3999.0],      [4000.0, 4001.0, 4002.0],      [4003.0, 4004.0, 4005.0],      [4006.0, 4007.0, 4008.0],      [4009.0, 4010.0, 4011.0]],     [[4012.0, 4013.0, 4014.0],      [4015.0, 4016.0, 4017.0],      [4018.0, 4019.0, 4020.0],      [4021.0, 4022.0, 4023.0],      [4024.0, 4025.0, 4026.0],      [4027.0, 4028.0, 4029.0],      [4030.0, 4031.0, 4032.0]]],    [[[4033.0, 4034.0, 4035.0],      [4036.0, 4037.0, 4038.0],      [4039.0, 4040.0, 4041.0],      [4042.0, 4043.0, 4044.0],      [4045.0, 4046.0, 4047.0],      [4048.0, 4049.0, 4050.0],      [4051.0, 4052.0, 4053.0]],     [[4054.0, 4055.0, 4056.0],      [4057.0, 4058.0, 4059.0],      [4060.0, 4061.0, 4062.0],      [4063.0, 4064.0, 4065.0],      [4066.0, 4067.0, 4068.0],      [4069.0, 4070.0, 4071.0],      [4072.0, 4073.0, 4074.0]],     [[4075.0, 4076.0, 4077.0],      [4078.0, 4079.0, 4080.0],      [4081.0, 4082.0, 4083.0],      [4084.0, 4085.0, 4086.0],      [4087.0, 4088.0, 4089.0],      [4090.0, 4091.0, 4092.0],      [4093.0, 4094.0, 4095.0]],     [[4096.0, 4097.0, 4098.0],      [4099.0, 4100.0, 4101.0],      [4102.0, 4103.0, 4104.0],      [4105.0, 4106.0, 4107.0],      [4108.0, 4109.0, 4110.0],      [4111.0, 4112.0, 4113.0],      [4114.0, 4115.0, 4116.0]],     [[4117.0, 4118.0, 4119.0],      [4120.0, 4121.0, 4122.0],      [4123.0, 4124.0, 4125.0],      [4126.0, 4127.0, 4128.0],      [4129.0, 4130.0, 4131.0],      [4132.0, 4133.0, 4134.0],      [4135.0, 4136.0, 4137.0]],     [[4138.0, 4139.0, 4140.0],      [4141.0, 4142.0, 4143.0],      [4144.0, 4145.0, 4146.0],      [4147.0, 4148.0, 4149.0],      [4150.0, 4151.0, 4152.0],      [4153.0, 4154.0, 4155.0],      [4156.0, 4157.0, 4158.0]]],    [[[4159.0, 4160.0, 4161.0],      [4162.0, 4163.0, 4164.0],      [4165.0, 4166.0, 4167.0],      [4168.0, 4169.0, 4170.0],      [4171.0, 4172.0, 4173.0],      [4174.0, 4175.0, 4176.0],      [4177.0, 4178.0, 4179.0]],     [[4180.0, 4181.0, 4182.0],      [4183.0, 4184.0, 4185.0],      [4186.0, 4187.0, 4188.0],      [4189.0, 4190.0, 4191.0],      [4192.0, 4193.0, 4194.0],      [4195.0, 4196.0, 4197.0],      [4198.0, 4199.0, 4200.0]],     [[4201.0, 4202.0, 4203.0],      [4204.0, 4205.0, 4206.0],      [4207.0, 4208.0, 4209.0],      [4210.0, 4211.0, 4212.0],      [4213.0, 4214.0, 4215.0],      [4216.0, 4217.0, 4218.0],      [4219.0, 4220.0, 4221.0]],     [[4222.0, 4223.0, 4224.0],      [4225.0, 4226.0, 4227.0],      [4228.0, 4229.0, 4230.0],      [4231.0, 4232.0, 4233.0],      [4234.0, 4235.0, 4236.0],      [4237.0, 4238.0, 4239.0],      [4240.0, 4241.0, 4242.0]],     [[4243.0, 4244.0, 4245.0],      [4246.0, 4247.0, 4248.0],      [4249.0, 4250.0, 4251.0],      [4252.0, 4253.0, 4254.0],      [4255.0, 4256.0, 4257.0],      [4258.0, 4259.0, 4260.0],      [4261.0, 4262.0, 4263.0]],     [[4264.0, 4265.0, 4266.0],      [4267.0, 4268.0, 4269.0],      [4270.0, 4271.0, 4272.0],      [4273.0, 4274.0, 4275.0],      [4276.0, 4277.0, 4278.0],      [4279.0, 4280.0, 4281.0],      [4282.0, 4283.0, 4284.0]]],    [[[4285.0, 4286.0, 4287.0],      [4288.0, 4289.0, 4290.0],      [4291.0, 4292.0, 4293.0],      [4294.0, 4295.0, 4296.0],      [4297.0, 4298.0, 4299.0],      [4300.0, 4301.0, 4302.0],      [4303.0, 4304.0, 4305.0]],     [[4306.0, 4307.0, 4308.0],      [4309.0, 4310.0, 4311.0],      [4312.0, 4313.0, 4314.0],      [4315.0, 4316.0, 4317.0],      [4318.0, 4319.0, 4320.0],      [4321.0, 4322.0, 4323.0],      [4324.0, 4325.0, 4326.0]],     [[4327.0, 4328.0, 4329.0],      [4330.0, 4331.0, 4332.0],      [4333.0, 4334.0, 4335.0],      [4336.0, 4337.0, 4338.0],      [4339.0, 4340.0, 4341.0],      [4342.0, 4343.0, 4344.0],      [4345.0, 4346.0, 4347.0]],     [[4348.0, 4349.0, 4350.0],      [4351.0, 4352.0, 4353.0],      [4354.0, 4355.0, 4356.0],      [4357.0, 4358.0, 4359.0],      [4360.0, 4361.0, 4362.0],      [4363.0, 4364.0, 4365.0],      [4366.0, 4367.0, 4368.0]],     [[4369.0, 4370.0, 4371.0],      [4372.0, 4373.0, 4374.0],      [4375.0, 4376.0, 4377.0],      [4378.0, 4379.0, 4380.0],      [4381.0, 4382.0, 4383.0],      [4384.0, 4385.0, 4386.0],      [4387.0, 4388.0, 4389.0]],     [[4390.0, 4391.0, 4392.0],      [4393.0, 4394.0, 4395.0],      [4396.0, 4397.0, 4398.0],      [4399.0, 4400.0, 4401.0],      [4402.0, 4403.0, 4404.0],      [4405.0, 4406.0, 4407.0],      [4408.0, 4409.0, 4410.0]]],    [[[4411.0, 4412.0, 4413.0],      [4414.0, 4415.0, 4416.0],      [4417.0, 4418.0, 4419.0],      [4420.0, 4421.0, 4422.0],      [4423.0, 4424.0, 4425.0],      [4426.0, 4427.0, 4428.0],      [4429.0, 4430.0, 4431.0]],     [[4432.0, 4433.0, 4434.0],      [4435.0, 4436.0, 4437.0],      [4438.0, 4439.0, 4440.0],      [4441.0, 4442.0, 4443.0],      [4444.0, 4445.0, 4446.0],      [4447.0, 4448.0, 4449.0],      [4450.0, 4451.0, 4452.0]],     [[4453.0, 4454.0, 4455.0],      [4456.0, 4457.0, 4458.0],      [4459.0, 4460.0, 4461.0],      [4462.0, 4463.0, 4464.0],      [4465.0, 4466.0, 4467.0],      [4468.0, 4469.0, 4470.0],      [4471.0, 4472.0, 4473.0]],     [[4474.0, 4475.0, 4476.0],      [4477.0, 4478.0, 4479.0],      [4480.0, 4481.0, 4482.0],      [4483.0, 4484.0, 4485.0],      [4486.0, 4487.0, 4488.0],      [4489.0, 4490.0, 4491.0],      [4492.0, 4493.0, 4494.0]],     [[4495.0, 4496.0, 4497.0],      [4498.0, 4499.0, 4500.0],      [4501.0, 4502.0, 4503.0],      [4504.0, 4505.0, 4506.0],      [4507.0, 4508.0, 4509.0],      [4510.0, 4511.0, 4512.0],      [4513.0, 4514.0, 4515.0]],     [[4516.0, 4517.0, 4518.0],      [4519.0, 4520.0, 4521.0],      [4522.0, 4523.0, 4524.0],      [4525.0, 4526.0, 4527.0],      [4528.0, 4529.0, 4530.0],      [4531.0, 4532.0, 4533.0],      [4534.0, 4535.0, 4536.0]]]]],  [[[[[4537.0, 4538.0, 4539.0],      [4540.0, 4541.0, 4542.0],      [4543.0, 4544.0, 4545.0],      [4546.0, 4547.0, 4548.0],      [4549.0, 4550.0, 4551.0],      [4552.0, 4553.0, 4554.0],      [4555.0, 4556.0, 4557.0]],     [[4558.0, 4559.0, 4560.0],      [4561.0, 4562.0, 4563.0],      [4564.0, 4565.0, 4566.0],      [4567.0, 4568.0, 4569.0],      [4570.0, 4571.0, 4572.0],      [4573.0, 4574.0, 4575.0],      [4576.0, 4577.0, 4578.0]],     [[4579.0, 4580.0, 4581.0],      [4582.0, 4583.0, 4584.0],      [4585.0, 4586.0, 4587.0],      [4588.0, 4589.0, 4590.0],      [4591.0, 4592.0, 4593.0],      [4594.0, 4595.0, 4596.0],      [4597.0, 4598.0, 4599.0]],     [[4600.0, 4601.0, 4602.0],      [4603.0, 4604.0, 4605.0],      [4606.0, 4607.0, 4608.0],      [4609.0, 4610.0, 4611.0],      [4612.0, 4613.0, 4614.0],      [4615.0, 4616.0, 4617.0],      [4618.0, 4619.0, 4620.0]],     [[4621.0, 4622.0, 4623.0],      [4624.0, 4625.0, 4626.0],      [4627.0, 4628.0, 4629.0],      [4630.0, 4631.0, 4632.0],      [4633.0, 4634.0, 4635.0],      [4636.0, 4637.0, 4638.0],      [4639.0, 4640.0, 4641.0]],     [[4642.0, 4643.0, 4644.0],      [4645.0, 4646.0, 4647.0],      [4648.0, 4649.0, 4650.0],      [4651.0, 4652.0, 4653.0],      [4654.0, 4655.0, 4656.0],      [4657.0, 4658.0, 4659.0],      [4660.0, 4661.0, 4662.0]]],    [[[4663.0, 4664.0, 4665.0],      [4666.0, 4667.0, 4668.0],      [4669.0, 4670.0, 4671.0],      [4672.0, 4673.0, 4674.0],      [4675.0, 4676.0, 4677.0],      [4678.0, 4679.0, 4680.0],      [4681.0, 4682.0, 4683.0]],     [[4684.0, 4685.0, 4686.0],      [4687.0, 4688.0, 4689.0],      [4690.0, 4691.0, 4692.0],      [4693.0, 4694.0, 4695.0],      [4696.0, 4697.0, 4698.0],      [4699.0, 4700.0, 4701.0],      [4702.0, 4703.0, 4704.0]],     [[4705.0, 4706.0, 4707.0],      [4708.0, 4709.0, 4710.0],      [4711.0, 4712.0, 4713.0],      [4714.0, 4715.0, 4716.0],      [4717.0, 4718.0, 4719.0],      [4720.0, 4721.0, 4722.0],      [4723.0, 4724.0, 4725.0]],     [[4726.0, 4727.0, 4728.0],      [4729.0, 4730.0, 4731.0],      [4732.0, 4733.0, 4734.0],      [4735.0, 4736.0, 4737.0],      [4738.0, 4739.0, 4740.0],      [4741.0, 4742.0, 4743.0],      [4744.0, 4745.0, 4746.0]],     [[4747.0, 4748.0, 4749.0],      [4750.0, 4751.0, 4752.0],      [4753.0, 4754.0, 4755.0],      [4756.0, 4757.0, 4758.0],      [4759.0, 4760.0, 4761.0],      [4762.0, 4763.0, 4764.0],      [4765.0, 4766.0, 4767.0]],     [[4768.0, 4769.0, 4770.0],      [4771.0, 4772.0, 4773.0],      [4774.0, 4775.0, 4776.0],      [4777.0, 4778.0, 4779.0],      [4780.0, 4781.0, 4782.0],      [4783.0, 4784.0, 4785.0],      [4786.0, 4787.0, 4788.0]]],    [[[4789.0, 4790.0, 4791.0],      [4792.0, 4793.0, 4794.0],      [4795.0, 4796.0, 4797.0],      [4798.0, 4799.0, 4800.0],      [4801.0, 4802.0, 4803.0],      [4804.0, 4805.0, 4806.0],      [4807.0, 4808.0, 4809.0]],     [[4810.0, 4811.0, 4812.0],      [4813.0, 4814.0, 4815.0],      [4816.0, 4817.0, 4818.0],      [4819.0, 4820.0, 4821.0],      [4822.0, 4823.0, 4824.0],      [4825.0, 4826.0, 4827.0],      [4828.0, 4829.0, 4830.0]],     [[4831.0, 4832.0, 4833.0],      [4834.0, 4835.0, 4836.0],      [4837.0, 4838.0, 4839.0],      [4840.0, 4841.0, 4842.0],      [4843.0, 4844.0, 4845.0],      [4846.0, 4847.0, 4848.0],      [4849.0, 4850.0, 4851.0]],     [[4852.0, 4853.0, 4854.0],      [4855.0, 4856.0, 4857.0],      [4858.0, 4859.0, 4860.0],      [4861.0, 4862.0, 4863.0],      [4864.0, 4865.0, 4866.0],      [4867.0, 4868.0, 4869.0],      [4870.0, 4871.0, 4872.0]],     [[4873.0, 4874.0, 4875.0],      [4876.0, 4877.0, 4878.0],      [4879.0, 4880.0, 4881.0],      [4882.0, 4883.0, 4884.0],      [4885.0, 4886.0, 4887.0],      [4888.0, 4889.0, 4890.0],      [4891.0, 4892.0, 4893.0]],     [[4894.0, 4895.0, 4896.0],      [4897.0, 4898.0, 4899.0],      [4900.0, 4901.0, 4902.0],      [4903.0, 4904.0, 4905.0],      [4906.0, 4907.0, 4908.0],      [4909.0, 4910.0, 4911.0],      [4912.0, 4913.0, 4914.0]]],    [[[4915.0, 4916.0, 4917.0],      [4918.0, 4919.0, 4920.0],      [4921.0, 4922.0, 4923.0],      [4924.0, 4925.0, 4926.0],      [4927.0, 4928.0, 4929.0],      [4930.0, 4931.0, 4932.0],      [4933.0, 4934.0, 4935.0]],     [[4936.0, 4937.0, 4938.0],      [4939.0, 4940.0, 4941.0],      [4942.0, 4943.0, 4944.0],      [4945.0, 4946.0, 4947.0],      [4948.0, 4949.0, 4950.0],      [4951.0, 4952.0, 4953.0],      [4954.0, 4955.0, 4956.0]],     [[4957.0, 4958.0, 4959.0],      [4960.0, 4961.0, 4962.0],      [4963.0, 4964.0, 4965.0],      [4966.0, 4967.0, 4968.0],      [4969.0, 4970.0, 4971.0],      [4972.0, 4973.0, 4974.0],      [4975.0, 4976.0, 4977.0]],     [[4978.0, 4979.0, 4980.0],      [4981.0, 4982.0, 4983.0],      [4984.0, 4985.0, 4986.0],      [4987.0, 4988.0, 4989.0],      [4990.0, 4991.0, 4992.0],      [4993.0, 4994.0, 4995.0],      [4996.0, 4997.0, 4998.0]],     [[4999.0, 5000.0, 5001.0],      [5002.0, 5003.0, 5004.0],      [5005.0, 5006.0, 5007.0],      [5008.0, 5009.0, 5010.0],      [5011.0, 5012.0, 5013.0],      [5014.0, 5015.0, 5016.0],      [5017.0, 5018.0, 5019.0]],     [[5020.0, 5021.0, 5022.0],      [5023.0, 5024.0, 5025.0],      [5026.0, 5027.0, 5028.0],      [5029.0, 5030.0, 5031.0],      [5032.0, 5033.0, 5034.0],      [5035.0, 5036.0, 5037.0],      [5038.0, 5039.0, 5040.0]]],    [[[5041.0, 5042.0, 5043.0],      [5044.0, 5045.0, 5046.0],      [5047.0, 5048.0, 5049.0],      [5050.0, 5051.0, 5052.0],      [5053.0, 5054.0, 5055.0],      [5056.0, 5057.0, 5058.0],      [5059.0, 5060.0, 5061.0]],     [[5062.0, 5063.0, 5064.0],      [5065.0, 5066.0, 5067.0],      [5068.0, 5069.0, 5070.0],      [5071.0, 5072.0, 5073.0],      [5074.0, 5075.0, 5076.0],      [5077.0, 5078.0, 5079.0],      [5080.0, 5081.0, 5082.0]],     [[5083.0, 5084.0, 5085.0],      [5086.0, 5087.0, 5088.0],      [5089.0, 5090.0, 5091.0],      [5092.0, 5093.0, 5094.0],      [5095.0, 5096.0, 5097.0],      [5098.0, 5099.0, 5100.0],      [5101.0, 5102.0, 5103.0]],     [[5104.0, 5105.0, 5106.0],      [5107.0, 5108.0, 5109.0],      [5110.0, 5111.0, 5112.0],      [5113.0, 5114.0, 5115.0],      [5116.0, 5117.0, 5118.0],      [5119.0, 5120.0, 5121.0],      [5122.0, 5123.0, 5124.0]],     [[5125.0, 5126.0, 5127.0],      [5128.0, 5129.0, 5130.0],      [5131.0, 5132.0, 5133.0],      [5134.0, 5135.0, 5136.0],      [5137.0, 5138.0, 5139.0],      [5140.0, 5141.0, 5142.0],      [5143.0, 5144.0, 5145.0]],     [[5146.0, 5147.0, 5148.0],      [5149.0, 5150.0, 5151.0],      [5152.0, 5153.0, 5154.0],      [5155.0, 5156.0, 5157.0],      [5158.0, 5159.0, 5160.0],      [5161.0, 5162.0, 5163.0],      [5164.0, 5165.0, 5166.0]]],    [[[5167.0, 5168.0, 5169.0],      [5170.0, 5171.0, 5172.0],      [5173.0, 5174.0, 5175.0],      [5176.0, 5177.0, 5178.0],      [5179.0, 5180.0, 5181.0],      [5182.0, 5183.0, 5184.0],      [5185.0, 5186.0, 5187.0]],     [[5188.0, 5189.0, 5190.0],      [5191.0, 5192.0, 5193.0],      [5194.0, 5195.0, 5196.0],      [5197.0, 5198.0, 5199.0],      [5200.0, 5201.0, 5202.0],      [5203.0, 5204.0, 5205.0],      [5206.0, 5207.0, 5208.0]],     [[5209.0, 5210.0, 5211.0],      [5212.0, 5213.0, 5214.0],      [5215.0, 5216.0, 5217.0],      [5218.0, 5219.0, 5220.0],      [5221.0, 5222.0, 5223.0],      [5224.0, 5225.0, 5226.0],      [5227.0, 5228.0, 5229.0]],     [[5230.0, 5231.0, 5232.0],      [5233.0, 5234.0, 5235.0],      [5236.0, 5237.0, 5238.0],      [5239.0, 5240.0, 5241.0],      [5242.0, 5243.0, 5244.0],      [5245.0, 5246.0, 5247.0],      [5248.0, 5249.0, 5250.0]],     [[5251.0, 5252.0, 5253.0],      [5254.0, 5255.0, 5256.0],      [5257.0, 5258.0, 5259.0],      [5260.0, 5261.0, 5262.0],      [5263.0, 5264.0, 5265.0],      [5266.0, 5267.0, 5268.0],      [5269.0, 5270.0, 5271.0]],     [[5272.0, 5273.0, 5274.0],      [5275.0, 5276.0, 5277.0],      [5278.0, 5279.0, 5280.0],      [5281.0, 5282.0, 5283.0],      [5284.0, 5285.0, 5286.0],      [5287.0, 5288.0, 5289.0],      [5290.0, 5291.0, 5292.0]]]],   [[[[5293.0, 5294.0, 5295.0],      [5296.0, 5297.0, 5298.0],      [5299.0, 5300.0, 5301.0],      [5302.0, 5303.0, 5304.0],      [5305.0, 5306.0, 5307.0],      [5308.0, 5309.0, 5310.0],      [5311.0, 5312.0, 5313.0]],     [[5314.0, 5315.0, 5316.0],      [5317.0, 5318.0, 5319.0],      [5320.0, 5321.0, 5322.0],      [5323.0, 5324.0, 5325.0],      [5326.0, 5327.0, 5328.0],      [5329.0, 5330.0, 5331.0],      [5332.0, 5333.0, 5334.0]],     [[5335.0, 5336.0, 5337.0],      [5338.0, 5339.0, 5340.0],      [5341.0, 5342.0, 5343.0],      [5344.0, 5345.0, 5346.0],      [5347.0, 5348.0, 5349.0],      [5350.0, 5351.0, 5352.0],      [5353.0, 5354.0, 5355.0]],     [[5356.0, 5357.0, 5358.0],      [5359.0, 5360.0, 5361.0],      [5362.0, 5363.0, 5364.0],      [5365.0, 5366.0, 5367.0],      [5368.0, 5369.0, 5370.0],      [5371.0, 5372.0, 5373.0],      [5374.0, 5375.0, 5376.0]],     [[5377.0, 5378.0, 5379.0],      [5380.0, 5381.0, 5382.0],      [5383.0, 5384.0, 5385.0],      [5386.0, 5387.0, 5388.0],      [5389.0, 5390.0, 5391.0],      [5392.0, 5393.0, 5394.0],      [5395.0, 5396.0, 5397.0]],     [[5398.0, 5399.0, 5400.0],      [5401.0, 5402.0, 5403.0],      [5404.0, 5405.0, 5406.0],      [5407.0, 5408.0, 5409.0],      [5410.0, 5411.0, 5412.0],      [5413.0, 5414.0, 5415.0],      [5416.0, 5417.0, 5418.0]]],    [[[5419.0, 5420.0, 5421.0],      [5422.0, 5423.0, 5424.0],      [5425.0, 5426.0, 5427.0],      [5428.0, 5429.0, 5430.0],      [5431.0, 5432.0, 5433.0],      [5434.0, 5435.0, 5436.0],      [5437.0, 5438.0, 5439.0]],     [[5440.0, 5441.0, 5442.0],      [5443.0, 5444.0, 5445.0],      [5446.0, 5447.0, 5448.0],      [5449.0, 5450.0, 5451.0],      [5452.0, 5453.0, 5454.0],      [5455.0, 5456.0, 5457.0],      [5458.0, 5459.0, 5460.0]],     [[5461.0, 5462.0, 5463.0],      [5464.0, 5465.0, 5466.0],      [5467.0, 5468.0, 5469.0],      [5470.0, 5471.0, 5472.0],      [5473.0, 5474.0, 5475.0],      [5476.0, 5477.0, 5478.0],      [5479.0, 5480.0, 5481.0]],     [[5482.0, 5483.0, 5484.0],      [5485.0, 5486.0, 5487.0],      [5488.0, 5489.0, 5490.0],      [5491.0, 5492.0, 5493.0],      [5494.0, 5495.0, 5496.0],      [5497.0, 5498.0, 5499.0],      [5500.0, 5501.0, 5502.0]],     [[5503.0, 5504.0, 5505.0],      [5506.0, 5507.0, 5508.0],      [5509.0, 5510.0, 5511.0],      [5512.0, 5513.0, 5514.0],      [5515.0, 5516.0, 5517.0],      [5518.0, 5519.0, 5520.0],      [5521.0, 5522.0, 5523.0]],     [[5524.0, 5525.0, 5526.0],      [5527.0, 5528.0, 5529.0],      [5530.0, 5531.0, 5532.0],      [5533.0, 5534.0, 5535.0],      [5536.0, 5537.0, 5538.0],      [5539.0, 5540.0, 5541.0],      [5542.0, 5543.0, 5544.0]]],    [[[5545.0, 5546.0, 5547.0],      [5548.0, 5549.0, 5550.0],      [5551.0, 5552.0, 5553.0],      [5554.0, 5555.0, 5556.0],      [5557.0, 5558.0, 5559.0],      [5560.0, 5561.0, 5562.0],      [5563.0, 5564.0, 5565.0]],     [[5566.0, 5567.0, 5568.0],      [5569.0, 5570.0, 5571.0],      [5572.0, 5573.0, 5574.0],      [5575.0, 5576.0, 5577.0],      [5578.0, 5579.0, 5580.0],      [5581.0, 5582.0, 5583.0],      [5584.0, 5585.0, 5586.0]],     [[5587.0, 5588.0, 5589.0],      [5590.0, 5591.0, 5592.0],      [5593.0, 5594.0, 5595.0],      [5596.0, 5597.0, 5598.0],      [5599.0, 5600.0, 5601.0],      [5602.0, 5603.0, 5604.0],      [5605.0, 5606.0, 5607.0]],     [[5608.0, 5609.0, 5610.0],      [5611.0, 5612.0, 5613.0],      [5614.0, 5615.0, 5616.0],      [5617.0, 5618.0, 5619.0],      [5620.0, 5621.0, 5622.0],      [5623.0, 5624.0, 5625.0],      [5626.0, 5627.0, 5628.0]],     [[5629.0, 5630.0, 5631.0],      [5632.0, 5633.0, 5634.0],      [5635.0, 5636.0, 5637.0],      [5638.0, 5639.0, 5640.0],      [5641.0, 5642.0, 5643.0],      [5644.0, 5645.0, 5646.0],      [5647.0, 5648.0, 5649.0]],     [[5650.0, 5651.0, 5652.0],      [5653.0, 5654.0, 5655.0],      [5656.0, 5657.0, 5658.0],      [5659.0, 5660.0, 5661.0],      [5662.0, 5663.0, 5664.0],      [5665.0, 5666.0, 5667.0],      [5668.0, 5669.0, 5670.0]]],    [[[5671.0, 5672.0, 5673.0],      [5674.0, 5675.0, 5676.0],      [5677.0, 5678.0, 5679.0],      [5680.0, 5681.0, 5682.0],      [5683.0, 5684.0, 5685.0],      [5686.0, 5687.0, 5688.0],      [5689.0, 5690.0, 5691.0]],     [[5692.0, 5693.0, 5694.0],      [5695.0, 5696.0, 5697.0],      [5698.0, 5699.0, 5700.0],      [5701.0, 5702.0, 5703.0],      [5704.0, 5705.0, 5706.0],      [5707.0, 5708.0, 5709.0],      [5710.0, 5711.0, 5712.0]],     [[5713.0, 5714.0, 5715.0],      [5716.0, 5717.0, 5718.0],      [5719.0, 5720.0, 5721.0],      [5722.0, 5723.0, 5724.0],      [5725.0, 5726.0, 5727.0],      [5728.0, 5729.0, 5730.0],      [5731.0, 5732.0, 5733.0]],     [[5734.0, 5735.0, 5736.0],      [5737.0, 5738.0, 5739.0],      [5740.0, 5741.0, 5742.0],      [5743.0, 5744.0, 5745.0],      [5746.0, 5747.0, 5748.0],      [5749.0, 5750.0, 5751.0],      [5752.0, 5753.0, 5754.0]],     [[5755.0, 5756.0, 5757.0],      [5758.0, 5759.0, 5760.0],      [5761.0, 5762.0, 5763.0],      [5764.0, 5765.0, 5766.0],      [5767.0, 5768.0, 5769.0],      [5770.0, 5771.0, 5772.0],      [5773.0, 5774.0, 5775.0]],     [[5776.0, 5777.0, 5778.0],      [5779.0, 5780.0, 5781.0],      [5782.0, 5783.0, 5784.0],      [5785.0, 5786.0, 5787.0],      [5788.0, 5789.0, 5790.0],      [5791.0, 5792.0, 5793.0],      [5794.0, 5795.0, 5796.0]]],    [[[5797.0, 5798.0, 5799.0],      [5800.0, 5801.0, 5802.0],      [5803.0, 5804.0, 5805.0],      [5806.0, 5807.0, 5808.0],      [5809.0, 5810.0, 5811.0],      [5812.0, 5813.0, 5814.0],      [5815.0, 5816.0, 5817.0]],     [[5818.0, 5819.0, 5820.0],      [5821.0, 5822.0, 5823.0],      [5824.0, 5825.0, 5826.0],      [5827.0, 5828.0, 5829.0],      [5830.0, 5831.0, 5832.0],      [5833.0, 5834.0, 5835.0],      [5836.0, 5837.0, 5838.0]],     [[5839.0, 5840.0, 5841.0],      [5842.0, 5843.0, 5844.0],      [5845.0, 5846.0, 5847.0],      [5848.0, 5849.0, 5850.0],      [5851.0, 5852.0, 5853.0],      [5854.0, 5855.0, 5856.0],      [5857.0, 5858.0, 5859.0]],     [[5860.0, 5861.0, 5862.0],      [5863.0, 5864.0, 5865.0],      [5866.0, 5867.0, 5868.0],      [5869.0, 5870.0, 5871.0],      [5872.0, 5873.0, 5874.0],      [5875.0, 5876.0, 5877.0],      [5878.0, 5879.0, 5880.0]],     [[5881.0, 5882.0, 5883.0],      [5884.0, 5885.0, 5886.0],      [5887.0, 5888.0, 5889.0],      [5890.0, 5891.0, 5892.0],      [5893.0, 5894.0, 5895.0],      [5896.0, 5897.0, 5898.0],      [5899.0, 5900.0, 5901.0]],     [[5902.0, 5903.0, 5904.0],      [5905.0, 5906.0, 5907.0],      [5908.0, 5909.0, 5910.0],      [5911.0, 5912.0, 5913.0],      [5914.0, 5915.0, 5916.0],      [5917.0, 5918.0, 5919.0],      [5920.0, 5921.0, 5922.0]]],    [[[5923.0, 5924.0, 5925.0],      [5926.0, 5927.0, 5928.0],      [5929.0, 5930.0, 5931.0],      [5932.0, 5933.0, 5934.0],      [5935.0, 5936.0, 5937.0],      [5938.0, 5939.0, 5940.0],      [5941.0, 5942.0, 5943.0]],     [[5944.0, 5945.0, 5946.0],      [5947.0, 5948.0, 5949.0],      [5950.0, 5951.0, 5952.0],      [5953.0, 5954.0, 5955.0],      [5956.0, 5957.0, 5958.0],      [5959.0, 5960.0, 5961.0],      [5962.0, 5963.0, 5964.0]],     [[5965.0, 5966.0, 5967.0],      [5968.0, 5969.0, 5970.0],      [5971.0, 5972.0, 5973.0],      [5974.0, 5975.0, 5976.0],      [5977.0, 5978.0, 5979.0],      [5980.0, 5981.0, 5982.0],      [5983.0, 5984.0, 5985.0]],     [[5986.0, 5987.0, 5988.0],      [5989.0, 5990.0, 5991.0],      [5992.0, 5993.0, 5994.0],      [5995.0, 5996.0, 5997.0],      [5998.0, 5999.0, 6000.0],      [6001.0, 6002.0, 6003.0],      [6004.0, 6005.0, 6006.0]],     [[6007.0, 6008.0, 6009.0],      [6010.0, 6011.0, 6012.0],      [6013.0, 6014.0, 6015.0],      [6016.0, 6017.0, 6018.0],      [6019.0, 6020.0, 6021.0],      [6022.0, 6023.0, 6024.0],      [6025.0, 6026.0, 6027.0]],     [[6028.0, 6029.0, 6030.0],      [6031.0, 6032.0, 6033.0],      [6034.0, 6035.0, 6036.0],      [6037.0, 6038.0, 6039.0],      [6040.0, 6041.0, 6042.0],      [6043.0, 6044.0, 6045.0],      [6046.0, 6047.0, 6048.0]]]],   [[[[6049.0, 6050.0, 6051.0],      [6052.0, 6053.0, 6054.0],      [6055.0, 6056.0, 6057.0],      [6058.0, 6059.0, 6060.0],      [6061.0, 6062.0, 6063.0],      [6064.0, 6065.0, 6066.0],      [6067.0, 6068.0, 6069.0]],     [[6070.0, 6071.0, 6072.0],      [6073.0, 6074.0, 6075.0],      [6076.0, 6077.0, 6078.0],      [6079.0, 6080.0, 6081.0],      [6082.0, 6083.0, 6084.0],      [6085.0, 6086.0, 6087.0],      [6088.0, 6089.0, 6090.0]],     [[6091.0, 6092.0, 6093.0],      [6094.0, 6095.0, 6096.0],      [6097.0, 6098.0, 6099.0],      [6100.0, 6101.0, 6102.0],      [6103.0, 6104.0, 6105.0],      [6106.0, 6107.0, 6108.0],      [6109.0, 6110.0, 6111.0]],     [[6112.0, 6113.0, 6114.0],      [6115.0, 6116.0, 6117.0],      [6118.0, 6119.0, 6120.0],      [6121.0, 6122.0, 6123.0],      [6124.0, 6125.0, 6126.0],      [6127.0, 6128.0, 6129.0],      [6130.0, 6131.0, 6132.0]],     [[6133.0, 6134.0, 6135.0],      [6136.0, 6137.0, 6138.0],      [6139.0, 6140.0, 6141.0],      [6142.0, 6143.0, 6144.0],      [6145.0, 6146.0, 6147.0],      [6148.0, 6149.0, 6150.0],      [6151.0, 6152.0, 6153.0]],     [[6154.0, 6155.0, 6156.0],      [6157.0, 6158.0, 6159.0],      [6160.0, 6161.0, 6162.0],      [6163.0, 6164.0, 6165.0],      [6166.0, 6167.0, 6168.0],      [6169.0, 6170.0, 6171.0],      [6172.0, 6173.0, 6174.0]]],    [[[6175.0, 6176.0, 6177.0],      [6178.0, 6179.0, 6180.0],      [6181.0, 6182.0, 6183.0],      [6184.0, 6185.0, 6186.0],      [6187.0, 6188.0, 6189.0],      [6190.0, 6191.0, 6192.0],      [6193.0, 6194.0, 6195.0]],     [[6196.0, 6197.0, 6198.0],      [6199.0, 6200.0, 6201.0],      [6202.0, 6203.0, 6204.0],      [6205.0, 6206.0, 6207.0],      [6208.0, 6209.0, 6210.0],      [6211.0, 6212.0, 6213.0],      [6214.0, 6215.0, 6216.0]],     [[6217.0, 6218.0, 6219.0],      [6220.0, 6221.0, 6222.0],      [6223.0, 6224.0, 6225.0],      [6226.0, 6227.0, 6228.0],      [6229.0, 6230.0, 6231.0],      [6232.0, 6233.0, 6234.0],      [6235.0, 6236.0, 6237.0]],     [[6238.0, 6239.0, 6240.0],      [6241.0, 6242.0, 6243.0],      [6244.0, 6245.0, 6246.0],      [6247.0, 6248.0, 6249.0],      [6250.0, 6251.0, 6252.0],      [6253.0, 6254.0, 6255.0],      [6256.0, 6257.0, 6258.0]],     [[6259.0, 6260.0, 6261.0],      [6262.0, 6263.0, 6264.0],      [6265.0, 6266.0, 6267.0],      [6268.0, 6269.0, 6270.0],      [6271.0, 6272.0, 6273.0],      [6274.0, 6275.0, 6276.0],      [6277.0, 6278.0, 6279.0]],     [[6280.0, 6281.0, 6282.0],      [6283.0, 6284.0, 6285.0],      [6286.0, 6287.0, 6288.0],      [6289.0, 6290.0, 6291.0],      [6292.0, 6293.0, 6294.0],      [6295.0, 6296.0, 6297.0],      [6298.0, 6299.0, 6300.0]]],    [[[6301.0, 6302.0, 6303.0],      [6304.0, 6305.0, 6306.0],      [6307.0, 6308.0, 6309.0],      [6310.0, 6311.0, 6312.0],      [6313.0, 6314.0, 6315.0],      [6316.0, 6317.0, 6318.0],      [6319.0, 6320.0, 6321.0]],     [[6322.0, 6323.0, 6324.0],      [6325.0, 6326.0, 6327.0],      [6328.0, 6329.0, 6330.0],      [6331.0, 6332.0, 6333.0],      [6334.0, 6335.0, 6336.0],      [6337.0, 6338.0, 6339.0],      [6340.0, 6341.0, 6342.0]],     [[6343.0, 6344.0, 6345.0],      [6346.0, 6347.0, 6348.0],      [6349.0, 6350.0, 6351.0],      [6352.0, 6353.0, 6354.0],      [6355.0, 6356.0, 6357.0],      [6358.0, 6359.0, 6360.0],      [6361.0, 6362.0, 6363.0]],     [[6364.0, 6365.0, 6366.0],      [6367.0, 6368.0, 6369.0],      [6370.0, 6371.0, 6372.0],      [6373.0, 6374.0, 6375.0],      [6376.0, 6377.0, 6378.0],      [6379.0, 6380.0, 6381.0],      [6382.0, 6383.0, 6384.0]],     [[6385.0, 6386.0, 6387.0],      [6388.0, 6389.0, 6390.0],      [6391.0, 6392.0, 6393.0],      [6394.0, 6395.0, 6396.0],      [6397.0, 6398.0, 6399.0],      [6400.0, 6401.0, 6402.0],      [6403.0, 6404.0, 6405.0]],     [[6406.0, 6407.0, 6408.0],      [6409.0, 6410.0, 6411.0],      [6412.0, 6413.0, 6414.0],      [6415.0, 6416.0, 6417.0],      [6418.0, 6419.0, 6420.0],      [6421.0, 6422.0, 6423.0],      [6424.0, 6425.0, 6426.0]]],    [[[6427.0, 6428.0, 6429.0],      [6430.0, 6431.0, 6432.0],      [6433.0, 6434.0, 6435.0],      [6436.0, 6437.0, 6438.0],      [6439.0, 6440.0, 6441.0],      [6442.0, 6443.0, 6444.0],      [6445.0, 6446.0, 6447.0]],     [[6448.0, 6449.0, 6450.0],      [6451.0, 6452.0, 6453.0],      [6454.0, 6455.0, 6456.0],      [6457.0, 6458.0, 6459.0],      [6460.0, 6461.0, 6462.0],      [6463.0, 6464.0, 6465.0],      [6466.0, 6467.0, 6468.0]],     [[6469.0, 6470.0, 6471.0],      [6472.0, 6473.0, 6474.0],      [6475.0, 6476.0, 6477.0],      [6478.0, 6479.0, 6480.0],      [6481.0, 6482.0, 6483.0],      [6484.0, 6485.0, 6486.0],      [6487.0, 6488.0, 6489.0]],     [[6490.0, 6491.0, 6492.0],      [6493.0, 6494.0, 6495.0],      [6496.0, 6497.0, 6498.0],      [6499.0, 6500.0, 6501.0],      [6502.0, 6503.0, 6504.0],      [6505.0, 6506.0, 6507.0],      [6508.0, 6509.0, 6510.0]],     [[6511.0, 6512.0, 6513.0],      [6514.0, 6515.0, 6516.0],      [6517.0, 6518.0, 6519.0],      [6520.0, 6521.0, 6522.0],      [6523.0, 6524.0, 6525.0],      [6526.0, 6527.0, 6528.0],      [6529.0, 6530.0, 6531.0]],     [[6532.0, 6533.0, 6534.0],      [6535.0, 6536.0, 6537.0],      [6538.0, 6539.0, 6540.0],      [6541.0, 6542.0, 6543.0],      [6544.0, 6545.0, 6546.0],      [6547.0, 6548.0, 6549.0],      [6550.0, 6551.0, 6552.0]]],    [[[6553.0, 6554.0, 6555.0],      [6556.0, 6557.0, 6558.0],      [6559.0, 6560.0, 6561.0],      [6562.0, 6563.0, 6564.0],      [6565.0, 6566.0, 6567.0],      [6568.0, 6569.0, 6570.0],      [6571.0, 6572.0, 6573.0]],     [[6574.0, 6575.0, 6576.0],      [6577.0, 6578.0, 6579.0],      [6580.0, 6581.0, 6582.0],      [6583.0, 6584.0, 6585.0],      [6586.0, 6587.0, 6588.0],      [6589.0, 6590.0, 6591.0],      [6592.0, 6593.0, 6594.0]],     [[6595.0, 6596.0, 6597.0],      [6598.0, 6599.0, 6600.0],      [6601.0, 6602.0, 6603.0],      [6604.0, 6605.0, 6606.0],      [6607.0, 6608.0, 6609.0],      [6610.0, 6611.0, 6612.0],      [6613.0, 6614.0, 6615.0]],     [[6616.0, 6617.0, 6618.0],      [6619.0, 6620.0, 6621.0],      [6622.0, 6623.0, 6624.0],      [6625.0, 6626.0, 6627.0],      [6628.0, 6629.0, 6630.0],      [6631.0, 6632.0, 6633.0],      [6634.0, 6635.0, 6636.0]],     [[6637.0, 6638.0, 6639.0],      [6640.0, 6641.0, 6642.0],      [6643.0, 6644.0, 6645.0],      [6646.0, 6647.0, 6648.0],      [6649.0, 6650.0, 6651.0],      [6652.0, 6653.0, 6654.0],      [6655.0, 6656.0, 6657.0]],     [[6658.0, 6659.0, 6660.0],      [6661.0, 6662.0, 6663.0],      [6664.0, 6665.0, 6666.0],      [6667.0, 6668.0, 6669.0],      [6670.0, 6671.0, 6672.0],      [6673.0, 6674.0, 6675.0],      [6676.0, 6677.0, 6678.0]]],    [[[6679.0, 6680.0, 6681.0],      [6682.0, 6683.0, 6684.0],      [6685.0, 6686.0, 6687.0],      [6688.0, 6689.0, 6690.0],      [6691.0, 6692.0, 6693.0],      [6694.0, 6695.0, 6696.0],      [6697.0, 6698.0, 6699.0]],     [[6700.0, 6701.0, 6702.0],      [6703.0, 6704.0, 6705.0],      [6706.0, 6707.0, 6708.0],      [6709.0, 6710.0, 6711.0],      [6712.0, 6713.0, 6714.0],      [6715.0, 6716.0, 6717.0],      [6718.0, 6719.0, 6720.0]],     [[6721.0, 6722.0, 6723.0],      [6724.0, 6725.0, 6726.0],      [6727.0, 6728.0, 6729.0],      [6730.0, 6731.0, 6732.0],      [6733.0, 6734.0, 6735.0],      [6736.0, 6737.0, 6738.0],      [6739.0, 6740.0, 6741.0]],     [[6742.0, 6743.0, 6744.0],      [6745.0, 6746.0, 6747.0],      [6748.0, 6749.0, 6750.0],      [6751.0, 6752.0, 6753.0],      [6754.0, 6755.0, 6756.0],      [6757.0, 6758.0, 6759.0],      [6760.0, 6761.0, 6762.0]],     [[6763.0, 6764.0, 6765.0],      [6766.0, 6767.0, 6768.0],      [6769.0, 6770.0, 6771.0],      [6772.0, 6773.0, 6774.0],      [6775.0, 6776.0, 6777.0],      [6778.0, 6779.0, 6780.0],      [6781.0, 6782.0, 6783.0]],     [[6784.0, 6785.0, 6786.0],      [6787.0, 6788.0, 6789.0],      [6790.0, 6791.0, 6792.0],      [6793.0, 6794.0, 6795.0],      [6796.0, 6797.0, 6798.0],      [6799.0, 6800.0, 6801.0],      [6802.0, 6803.0, 6804.0]]]],   [[[[6805.0, 6806.0, 6807.0],      [6808.0, 6809.0, 6810.0],      [6811.0, 6812.0, 6813.0],      [6814.0, 6815.0, 6816.0],      [6817.0, 6818.0, 6819.0],      [6820.0, 6821.0, 6822.0],      [6823.0, 6824.0, 6825.0]],     [[6826.0, 6827.0, 6828.0],      [6829.0, 6830.0, 6831.0],      [6832.0, 6833.0, 6834.0],      [6835.0, 6836.0, 6837.0],      [6838.0, 6839.0, 6840.0],      [6841.0, 6842.0, 6843.0],      [6844.0, 6845.0, 6846.0]],     [[6847.0, 6848.0, 6849.0],      [6850.0, 6851.0, 6852.0],      [6853.0, 6854.0, 6855.0],      [6856.0, 6857.0, 6858.0],      [6859.0, 6860.0, 6861.0],      [6862.0, 6863.0, 6864.0],      [6865.0, 6866.0, 6867.0]],     [[6868.0, 6869.0, 6870.0],      [6871.0, 6872.0, 6873.0],      [6874.0, 6875.0, 6876.0],      [6877.0, 6878.0, 6879.0],      [6880.0, 6881.0, 6882.0],      [6883.0, 6884.0, 6885.0],      [6886.0, 6887.0, 6888.0]],     [[6889.0, 6890.0, 6891.0],      [6892.0, 6893.0, 6894.0],      [6895.0, 6896.0, 6897.0],      [6898.0, 6899.0, 6900.0],      [6901.0, 6902.0, 6903.0],      [6904.0, 6905.0, 6906.0],      [6907.0, 6908.0, 6909.0]],     [[6910.0, 6911.0, 6912.0],      [6913.0, 6914.0, 6915.0],      [6916.0, 6917.0, 6918.0],      [6919.0, 6920.0, 6921.0],      [6922.0, 6923.0, 6924.0],      [6925.0, 6926.0, 6927.0],      [6928.0, 6929.0, 6930.0]]],    [[[6931.0, 6932.0, 6933.0],      [6934.0, 6935.0, 6936.0],      [6937.0, 6938.0, 6939.0],      [6940.0, 6941.0, 6942.0],      [6943.0, 6944.0, 6945.0],      [6946.0, 6947.0, 6948.0],      [6949.0, 6950.0, 6951.0]],     [[6952.0, 6953.0, 6954.0],      [6955.0, 6956.0, 6957.0],      [6958.0, 6959.0, 6960.0],      [6961.0, 6962.0, 6963.0],      [6964.0, 6965.0, 6966.0],      [6967.0, 6968.0, 6969.0],      [6970.0, 6971.0, 6972.0]],     [[6973.0, 6974.0, 6975.0],      [6976.0, 6977.0, 6978.0],      [6979.0, 6980.0, 6981.0],      [6982.0, 6983.0, 6984.0],      [6985.0, 6986.0, 6987.0],      [6988.0, 6989.0, 6990.0],      [6991.0, 6992.0, 6993.0]],     [[6994.0, 6995.0, 6996.0],      [6997.0, 6998.0, 6999.0],      [7000.0, 7001.0, 7002.0],      [7003.0, 7004.0, 7005.0],      [7006.0, 7007.0, 7008.0],      [7009.0, 7010.0, 7011.0],      [7012.0, 7013.0, 7014.0]],     [[7015.0, 7016.0, 7017.0],      [7018.0, 7019.0, 7020.0],      [7021.0, 7022.0, 7023.0],      [7024.0, 7025.0, 7026.0],      [7027.0, 7028.0, 7029.0],      [7030.0, 7031.0, 7032.0],      [7033.0, 7034.0, 7035.0]],     [[7036.0, 7037.0, 7038.0],      [7039.0, 7040.0, 7041.0],      [7042.0, 7043.0, 7044.0],      [7045.0, 7046.0, 7047.0],      [7048.0, 7049.0, 7050.0],      [7051.0, 7052.0, 7053.0],      [7054.0, 7055.0, 7056.0]]],    [[[7057.0, 7058.0, 7059.0],      [7060.0, 7061.0, 7062.0],      [7063.0, 7064.0, 7065.0],      [7066.0, 7067.0, 7068.0],      [7069.0, 7070.0, 7071.0],      [7072.0, 7073.0, 7074.0],      [7075.0, 7076.0, 7077.0]],     [[7078.0, 7079.0, 7080.0],      [7081.0, 7082.0, 7083.0],      [7084.0, 7085.0, 7086.0],      [7087.0, 7088.0, 7089.0],      [7090.0, 7091.0, 7092.0],      [7093.0, 7094.0, 7095.0],      [7096.0, 7097.0, 7098.0]],     [[7099.0, 7100.0, 7101.0],      [7102.0, 7103.0, 7104.0],      [7105.0, 7106.0, 7107.0],      [7108.0, 7109.0, 7110.0],      [7111.0, 7112.0, 7113.0],      [7114.0, 7115.0, 7116.0],      [7117.0, 7118.0, 7119.0]],     [[7120.0, 7121.0, 7122.0],      [7123.0, 7124.0, 7125.0],      [7126.0, 7127.0, 7128.0],      [7129.0, 7130.0, 7131.0],      [7132.0, 7133.0, 7134.0],      [7135.0, 7136.0, 7137.0],      [7138.0, 7139.0, 7140.0]],     [[7141.0, 7142.0, 7143.0],      [7144.0, 7145.0, 7146.0],      [7147.0, 7148.0, 7149.0],      [7150.0, 7151.0, 7152.0],      [7153.0, 7154.0, 7155.0],      [7156.0, 7157.0, 7158.0],      [7159.0, 7160.0, 7161.0]],     [[7162.0, 7163.0, 7164.0],      [7165.0, 7166.0, 7167.0],      [7168.0, 7169.0, 7170.0],      [7171.0, 7172.0, 7173.0],      [7174.0, 7175.0, 7176.0],      [7177.0, 7178.0, 7179.0],      [7180.0, 7181.0, 7182.0]]],    [[[7183.0, 7184.0, 7185.0],      [7186.0, 7187.0, 7188.0],      [7189.0, 7190.0, 7191.0],      [7192.0, 7193.0, 7194.0],      [7195.0, 7196.0, 7197.0],      [7198.0, 7199.0, 7200.0],      [7201.0, 7202.0, 7203.0]],     [[7204.0, 7205.0, 7206.0],      [7207.0, 7208.0, 7209.0],      [7210.0, 7211.0, 7212.0],      [7213.0, 7214.0, 7215.0],      [7216.0, 7217.0, 7218.0],      [7219.0, 7220.0, 7221.0],      [7222.0, 7223.0, 7224.0]],     [[7225.0, 7226.0, 7227.0],      [7228.0, 7229.0, 7230.0],      [7231.0, 7232.0, 7233.0],      [7234.0, 7235.0, 7236.0],      [7237.0, 7238.0, 7239.0],      [7240.0, 7241.0, 7242.0],      [7243.0, 7244.0, 7245.0]],     [[7246.0, 7247.0, 7248.0],      [7249.0, 7250.0, 7251.0],      [7252.0, 7253.0, 7254.0],      [7255.0, 7256.0, 7257.0],      [7258.0, 7259.0, 7260.0],      [7261.0, 7262.0, 7263.0],      [7264.0, 7265.0, 7266.0]],     [[7267.0, 7268.0, 7269.0],      [7270.0, 7271.0, 7272.0],      [7273.0, 7274.0, 7275.0],      [7276.0, 7277.0, 7278.0],      [7279.0, 7280.0, 7281.0],      [7282.0, 7283.0, 7284.0],      [7285.0, 7286.0, 7287.0]],     [[7288.0, 7289.0, 7290.0],      [7291.0, 7292.0, 7293.0],      [7294.0, 7295.0, 7296.0],      [7297.0, 7298.0, 7299.0],      [7300.0, 7301.0, 7302.0],      [7303.0, 7304.0, 7305.0],      [7306.0, 7307.0, 7308.0]]],    [[[7309.0, 7310.0, 7311.0],      [7312.0, 7313.0, 7314.0],      [7315.0, 7316.0, 7317.0],      [7318.0, 7319.0, 7320.0],      [7321.0, 7322.0, 7323.0],      [7324.0, 7325.0, 7326.0],      [7327.0, 7328.0, 7329.0]],     [[7330.0, 7331.0, 7332.0],      [7333.0, 7334.0, 7335.0],      [7336.0, 7337.0, 7338.0],      [7339.0, 7340.0, 7341.0],      [7342.0, 7343.0, 7344.0],      [7345.0, 7346.0, 7347.0],      [7348.0, 7349.0, 7350.0]],     [[7351.0, 7352.0, 7353.0],      [7354.0, 7355.0, 7356.0],      [7357.0, 7358.0, 7359.0],      [7360.0, 7361.0, 7362.0],      [7363.0, 7364.0, 7365.0],      [7366.0, 7367.0, 7368.0],      [7369.0, 7370.0, 7371.0]],     [[7372.0, 7373.0, 7374.0],      [7375.0, 7376.0, 7377.0],      [7378.0, 7379.0, 7380.0],      [7381.0, 7382.0, 7383.0],      [7384.0, 7385.0, 7386.0],      [7387.0, 7388.0, 7389.0],      [7390.0, 7391.0, 7392.0]],     [[7393.0, 7394.0, 7395.0],      [7396.0, 7397.0, 7398.0],      [7399.0, 7400.0, 7401.0],      [7402.0, 7403.0, 7404.0],      [7405.0, 7406.0, 7407.0],      [7408.0, 7409.0, 7410.0],      [7411.0, 7412.0, 7413.0]],     [[7414.0, 7415.0, 7416.0],      [7417.0, 7418.0, 7419.0],      [7420.0, 7421.0, 7422.0],      [7423.0, 7424.0, 7425.0],      [7426.0, 7427.0, 7428.0],      [7429.0, 7430.0, 7431.0],      [7432.0, 7433.0, 7434.0]]],    [[[7435.0, 7436.0, 7437.0],      [7438.0, 7439.0, 7440.0],      [7441.0, 7442.0, 7443.0],      [7444.0, 7445.0, 7446.0],      [7447.0, 7448.0, 7449.0],      [7450.0, 7451.0, 7452.0],      [7453.0, 7454.0, 7455.0]],     [[7456.0, 7457.0, 7458.0],      [7459.0, 7460.0, 7461.0],      [7462.0, 7463.0, 7464.0],      [7465.0, 7466.0, 7467.0],      [7468.0, 7469.0, 7470.0],      [7471.0, 7472.0, 7473.0],      [7474.0, 7475.0, 7476.0]],     [[7477.0, 7478.0, 7479.0],      [7480.0, 7481.0, 7482.0],      [7483.0, 7484.0, 7485.0],      [7486.0, 7487.0, 7488.0],      [7489.0, 7490.0, 7491.0],      [7492.0, 7493.0, 7494.0],      [7495.0, 7496.0, 7497.0]],     [[7498.0, 7499.0, 7500.0],      [7501.0, 7502.0, 7503.0],      [7504.0, 7505.0, 7506.0],      [7507.0, 7508.0, 7509.0],      [7510.0, 7511.0, 7512.0],      [7513.0, 7514.0, 7515.0],      [7516.0, 7517.0, 7518.0]],     [[7519.0, 7520.0, 7521.0],      [7522.0, 7523.0, 7524.0],      [7525.0, 7526.0, 7527.0],      [7528.0, 7529.0, 7530.0],      [7531.0, 7532.0, 7533.0],      [7534.0, 7535.0, 7536.0],      [7537.0, 7538.0, 7539.0]],     [[7540.0, 7541.0, 7542.0],      [7543.0, 7544.0, 7545.0],      [7546.0, 7547.0, 7548.0],      [7549.0, 7550.0, 7551.0],      [7552.0, 7553.0, 7554.0],      [7555.0, 7556.0, 7557.0],      [7558.0, 7559.0, 7560.0]]]],   [[[[7561.0, 7562.0, 7563.0],      [7564.0, 7565.0, 7566.0],      [7567.0, 7568.0, 7569.0],      [7570.0, 7571.0, 7572.0],      [7573.0, 7574.0, 7575.0],      [7576.0, 7577.0, 7578.0],      [7579.0, 7580.0, 7581.0]],     [[7582.0, 7583.0, 7584.0],      [7585.0, 7586.0, 7587.0],      [7588.0, 7589.0, 7590.0],      [7591.0, 7592.0, 7593.0],      [7594.0, 7595.0, 7596.0],      [7597.0, 7598.0, 7599.0],      [7600.0, 7601.0, 7602.0]],     [[7603.0, 7604.0, 7605.0],      [7606.0, 7607.0, 7608.0],      [7609.0, 7610.0, 7611.0],      [7612.0, 7613.0, 7614.0],      [7615.0, 7616.0, 7617.0],      [7618.0, 7619.0, 7620.0],      [7621.0, 7622.0, 7623.0]],     [[7624.0, 7625.0, 7626.0],      [7627.0, 7628.0, 7629.0],      [7630.0, 7631.0, 7632.0],      [7633.0, 7634.0, 7635.0],      [7636.0, 7637.0, 7638.0],      [7639.0, 7640.0, 7641.0],      [7642.0, 7643.0, 7644.0]],     [[7645.0, 7646.0, 7647.0],      [7648.0, 7649.0, 7650.0],      [7651.0, 7652.0, 7653.0],      [7654.0, 7655.0, 7656.0],      [7657.0, 7658.0, 7659.0],      [7660.0, 7661.0, 7662.0],      [7663.0, 7664.0, 7665.0]],     [[7666.0, 7667.0, 7668.0],      [7669.0, 7670.0, 7671.0],      [7672.0, 7673.0, 7674.0],      [7675.0, 7676.0, 7677.0],      [7678.0, 7679.0, 7680.0],      [7681.0, 7682.0, 7683.0],      [7684.0, 7685.0, 7686.0]]],    [[[7687.0, 7688.0, 7689.0],      [7690.0, 7691.0, 7692.0],      [7693.0, 7694.0, 7695.0],      [7696.0, 7697.0, 7698.0],      [7699.0, 7700.0, 7701.0],      [7702.0, 7703.0, 7704.0],      [7705.0, 7706.0, 7707.0]],     [[7708.0, 7709.0, 7710.0],      [7711.0, 7712.0, 7713.0],      [7714.0, 7715.0, 7716.0],      [7717.0, 7718.0, 7719.0],      [7720.0, 7721.0, 7722.0],      [7723.0, 7724.0, 7725.0],      [7726.0, 7727.0, 7728.0]],     [[7729.0, 7730.0, 7731.0],      [7732.0, 7733.0, 7734.0],      [7735.0, 7736.0, 7737.0],      [7738.0, 7739.0, 7740.0],      [7741.0, 7742.0, 7743.0],      [7744.0, 7745.0, 7746.0],      [7747.0, 7748.0, 7749.0]],     [[7750.0, 7751.0, 7752.0],      [7753.0, 7754.0, 7755.0],      [7756.0, 7757.0, 7758.0],      [7759.0, 7760.0, 7761.0],      [7762.0, 7763.0, 7764.0],      [7765.0, 7766.0, 7767.0],      [7768.0, 7769.0, 7770.0]],     [[7771.0, 7772.0, 7773.0],      [7774.0, 7775.0, 7776.0],      [7777.0, 7778.0, 7779.0],      [7780.0, 7781.0, 7782.0],      [7783.0, 7784.0, 7785.0],      [7786.0, 7787.0, 7788.0],      [7789.0, 7790.0, 7791.0]],     [[7792.0, 7793.0, 7794.0],      [7795.0, 7796.0, 7797.0],      [7798.0, 7799.0, 7800.0],      [7801.0, 7802.0, 7803.0],      [7804.0, 7805.0, 7806.0],      [7807.0, 7808.0, 7809.0],      [7810.0, 7811.0, 7812.0]]],    [[[7813.0, 7814.0, 7815.0],      [7816.0, 7817.0, 7818.0],      [7819.0, 7820.0, 7821.0],      [7822.0, 7823.0, 7824.0],      [7825.0, 7826.0, 7827.0],      [7828.0, 7829.0, 7830.0],      [7831.0, 7832.0, 7833.0]],     [[7834.0, 7835.0, 7836.0],      [7837.0, 7838.0, 7839.0],      [7840.0, 7841.0, 7842.0],      [7843.0, 7844.0, 7845.0],      [7846.0, 7847.0, 7848.0],      [7849.0, 7850.0, 7851.0],      [7852.0, 7853.0, 7854.0]],     [[7855.0, 7856.0, 7857.0],      [7858.0, 7859.0, 7860.0],      [7861.0, 7862.0, 7863.0],      [7864.0, 7865.0, 7866.0],      [7867.0, 7868.0, 7869.0],      [7870.0, 7871.0, 7872.0],      [7873.0, 7874.0, 7875.0]],     [[7876.0, 7877.0, 7878.0],      [7879.0, 7880.0, 7881.0],      [7882.0, 7883.0, 7884.0],      [7885.0, 7886.0, 7887.0],      [7888.0, 7889.0, 7890.0],      [7891.0, 7892.0, 7893.0],      [7894.0, 7895.0, 7896.0]],     [[7897.0, 7898.0, 7899.0],      [7900.0, 7901.0, 7902.0],      [7903.0, 7904.0, 7905.0],      [7906.0, 7907.0, 7908.0],      [7909.0, 7910.0, 7911.0],      [7912.0, 7913.0, 7914.0],      [7915.0, 7916.0, 7917.0]],     [[7918.0, 7919.0, 7920.0],      [7921.0, 7922.0, 7923.0],      [7924.0, 7925.0, 7926.0],      [7927.0, 7928.0, 7929.0],      [7930.0, 7931.0, 7932.0],      [7933.0, 7934.0, 7935.0],      [7936.0, 7937.0, 7938.0]]],    [[[7939.0, 7940.0, 7941.0],      [7942.0, 7943.0, 7944.0],      [7945.0, 7946.0, 7947.0],      [7948.0, 7949.0, 7950.0],      [7951.0, 7952.0, 7953.0],      [7954.0, 7955.0, 7956.0],      [7957.0, 7958.0, 7959.0]],     [[7960.0, 7961.0, 7962.0],      [7963.0, 7964.0, 7965.0],      [7966.0, 7967.0, 7968.0],      [7969.0, 7970.0, 7971.0],      [7972.0, 7973.0, 7974.0],      [7975.0, 7976.0, 7977.0],      [7978.0, 7979.0, 7980.0]],     [[7981.0, 7982.0, 7983.0],      [7984.0, 7985.0, 7986.0],      [7987.0, 7988.0, 7989.0],      [7990.0, 7991.0, 7992.0],      [7993.0, 7994.0, 7995.0],      [7996.0, 7997.0, 7998.0],      [7999.0, 8000.0, 8001.0]],     [[8002.0, 8003.0, 8004.0],      [8005.0, 8006.0, 8007.0],      [8008.0, 8009.0, 8010.0],      [8011.0, 8012.0, 8013.0],      [8014.0, 8015.0, 8016.0],      [8017.0, 8018.0, 8019.0],      [8020.0, 8021.0, 8022.0]],     [[8023.0, 8024.0, 8025.0],      [8026.0, 8027.0, 8028.0],      [8029.0, 8030.0, 8031.0],      [8032.0, 8033.0, 8034.0],      [8035.0, 8036.0, 8037.0],      [8038.0, 8039.0, 8040.0],      [8041.0, 8042.0, 8043.0]],     [[8044.0, 8045.0, 8046.0],      [8047.0, 8048.0, 8049.0],      [8050.0, 8051.0, 8052.0],      [8053.0, 8054.0, 8055.0],      [8056.0, 8057.0, 8058.0],      [8059.0, 8060.0, 8061.0],      [8062.0, 8063.0, 8064.0]]],    [[[8065.0, 8066.0, 8067.0],      [8068.0, 8069.0, 8070.0],      [8071.0, 8072.0, 8073.0],      [8074.0, 8075.0, 8076.0],      [8077.0, 8078.0, 8079.0],      [8080.0, 8081.0, 8082.0],      [8083.0, 8084.0, 8085.0]],     [[8086.0, 8087.0, 8088.0],      [8089.0, 8090.0, 8091.0],      [8092.0, 8093.0, 8094.0],      [8095.0, 8096.0, 8097.0],      [8098.0, 8099.0, 8100.0],      [8101.0, 8102.0, 8103.0],      [8104.0, 8105.0, 8106.0]],     [[8107.0, 8108.0, 8109.0],      [8110.0, 8111.0, 8112.0],      [8113.0, 8114.0, 8115.0],      [8116.0, 8117.0, 8118.0],      [8119.0, 8120.0, 8121.0],      [8122.0, 8123.0, 8124.0],      [8125.0, 8126.0, 8127.0]],     [[8128.0, 8129.0, 8130.0],      [8131.0, 8132.0, 8133.0],      [8134.0, 8135.0, 8136.0],      [8137.0, 8138.0, 8139.0],      [8140.0, 8141.0, 8142.0],      [8143.0, 8144.0, 8145.0],      [8146.0, 8147.0, 8148.0]],     [[8149.0, 8150.0, 8151.0],      [8152.0, 8153.0, 8154.0],      [8155.0, 8156.0, 8157.0],      [8158.0, 8159.0, 8160.0],      [8161.0, 8162.0, 8163.0],      [8164.0, 8165.0, 8166.0],      [8167.0, 8168.0, 8169.0]],     [[8170.0, 8171.0, 8172.0],      [8173.0, 8174.0, 8175.0],      [8176.0, 8177.0, 8178.0],      [8179.0, 8180.0, 8181.0],      [8182.0, 8183.0, 8184.0],      [8185.0, 8186.0, 8187.0],      [8188.0, 8189.0, 8190.0]]],    [[[8191.0, 8192.0, 8193.0],      [8194.0, 8195.0, 8196.0],      [8197.0, 8198.0, 8199.0],      [8200.0, 8201.0, 8202.0],      [8203.0, 8204.0, 8205.0],      [8206.0, 8207.0, 8208.0],      [8209.0, 8210.0, 8211.0]],     [[8212.0, 8213.0, 8214.0],      [8215.0, 8216.0, 8217.0],      [8218.0, 8219.0, 8220.0],      [8221.0, 8222.0, 8223.0],      [8224.0, 8225.0, 8226.0],      [8227.0, 8228.0, 8229.0],      [8230.0, 8231.0, 8232.0]],     [[8233.0, 8234.0, 8235.0],      [8236.0, 8237.0, 8238.0],      [8239.0, 8240.0, 8241.0],      [8242.0, 8243.0, 8244.0],      [8245.0, 8246.0, 8247.0],      [8248.0, 8249.0, 8250.0],      [8251.0, 8252.0, 8253.0]],     [[8254.0, 8255.0, 8256.0],      [8257.0, 8258.0, 8259.0],      [8260.0, 8261.0, 8262.0],      [8263.0, 8264.0, 8265.0],      [8266.0, 8267.0, 8268.0],      [8269.0, 8270.0, 8271.0],      [8272.0, 8273.0, 8274.0]],     [[8275.0, 8276.0, 8277.0],      [8278.0, 8279.0, 8280.0],      [8281.0, 8282.0, 8283.0],      [8284.0, 8285.0, 8286.0],      [8287.0, 8288.0, 8289.0],      [8290.0, 8291.0, 8292.0],      [8293.0, 8294.0, 8295.0]],     [[8296.0, 8297.0, 8298.0],      [8299.0, 8300.0, 8301.0],      [8302.0, 8303.0, 8304.0],      [8305.0, 8306.0, 8307.0],      [8308.0, 8309.0, 8310.0],      [8311.0, 8312.0, 8313.0],      [8314.0, 8315.0, 8316.0]]]],   [[[[8317.0, 8318.0, 8319.0],      [8320.0, 8321.0, 8322.0],      [8323.0, 8324.0, 8325.0],      [8326.0, 8327.0, 8328.0],      [8329.0, 8330.0, 8331.0],      [8332.0, 8333.0, 8334.0],      [8335.0, 8336.0, 8337.0]],     [[8338.0, 8339.0, 8340.0],      [8341.0, 8342.0, 8343.0],      [8344.0, 8345.0, 8346.0],      [8347.0, 8348.0, 8349.0],      [8350.0, 8351.0, 8352.0],      [8353.0, 8354.0, 8355.0],      [8356.0, 8357.0, 8358.0]],     [[8359.0, 8360.0, 8361.0],      [8362.0, 8363.0, 8364.0],      [8365.0, 8366.0, 8367.0],      [8368.0, 8369.0, 8370.0],      [8371.0, 8372.0, 8373.0],      [8374.0, 8375.0, 8376.0],      [8377.0, 8378.0, 8379.0]],     [[8380.0, 8381.0, 8382.0],      [8383.0, 8384.0, 8385.0],      [8386.0, 8387.0, 8388.0],      [8389.0, 8390.0, 8391.0],      [8392.0, 8393.0, 8394.0],      [8395.0, 8396.0, 8397.0],      [8398.0, 8399.0, 8400.0]],     [[8401.0, 8402.0, 8403.0],      [8404.0, 8405.0, 8406.0],      [8407.0, 8408.0, 8409.0],      [8410.0, 8411.0, 8412.0],      [8413.0, 8414.0, 8415.0],      [8416.0, 8417.0, 8418.0],      [8419.0, 8420.0, 8421.0]],     [[8422.0, 8423.0, 8424.0],      [8425.0, 8426.0, 8427.0],      [8428.0, 8429.0, 8430.0],      [8431.0, 8432.0, 8433.0],      [8434.0, 8435.0, 8436.0],      [8437.0, 8438.0, 8439.0],      [8440.0, 8441.0, 8442.0]]],    [[[8443.0, 8444.0, 8445.0],      [8446.0, 8447.0, 8448.0],      [8449.0, 8450.0, 8451.0],      [8452.0, 8453.0, 8454.0],      [8455.0, 8456.0, 8457.0],      [8458.0, 8459.0, 8460.0],      [8461.0, 8462.0, 8463.0]],     [[8464.0, 8465.0, 8466.0],      [8467.0, 8468.0, 8469.0],      [8470.0, 8471.0, 8472.0],      [8473.0, 8474.0, 8475.0],      [8476.0, 8477.0, 8478.0],      [8479.0, 8480.0, 8481.0],      [8482.0, 8483.0, 8484.0]],     [[8485.0, 8486.0, 8487.0],      [8488.0, 8489.0, 8490.0],      [8491.0, 8492.0, 8493.0],      [8494.0, 8495.0, 8496.0],      [8497.0, 8498.0, 8499.0],      [8500.0, 8501.0, 8502.0],      [8503.0, 8504.0, 8505.0]],     [[8506.0, 8507.0, 8508.0],      [8509.0, 8510.0, 8511.0],      [8512.0, 8513.0, 8514.0],      [8515.0, 8516.0, 8517.0],      [8518.0, 8519.0, 8520.0],      [8521.0, 8522.0, 8523.0],      [8524.0, 8525.0, 8526.0]],     [[8527.0, 8528.0, 8529.0],      [8530.0, 8531.0, 8532.0],      [8533.0, 8534.0, 8535.0],      [8536.0, 8537.0, 8538.0],      [8539.0, 8540.0, 8541.0],      [8542.0, 8543.0, 8544.0],      [8545.0, 8546.0, 8547.0]],     [[8548.0, 8549.0, 8550.0],      [8551.0, 8552.0, 8553.0],      [8554.0, 8555.0, 8556.0],      [8557.0, 8558.0, 8559.0],      [8560.0, 8561.0, 8562.0],      [8563.0, 8564.0, 8565.0],      [8566.0, 8567.0, 8568.0]]],    [[[8569.0, 8570.0, 8571.0],      [8572.0, 8573.0, 8574.0],      [8575.0, 8576.0, 8577.0],      [8578.0, 8579.0, 8580.0],      [8581.0, 8582.0, 8583.0],      [8584.0, 8585.0, 8586.0],      [8587.0, 8588.0, 8589.0]],     [[8590.0, 8591.0, 8592.0],      [8593.0, 8594.0, 8595.0],      [8596.0, 8597.0, 8598.0],      [8599.0, 8600.0, 8601.0],      [8602.0, 8603.0, 8604.0],      [8605.0, 8606.0, 8607.0],      [8608.0, 8609.0, 8610.0]],     [[8611.0, 8612.0, 8613.0],      [8614.0, 8615.0, 8616.0],      [8617.0, 8618.0, 8619.0],      [8620.0, 8621.0, 8622.0],      [8623.0, 8624.0, 8625.0],      [8626.0, 8627.0, 8628.0],      [8629.0, 8630.0, 8631.0]],     [[8632.0, 8633.0, 8634.0],      [8635.0, 8636.0, 8637.0],      [8638.0, 8639.0, 8640.0],      [8641.0, 8642.0, 8643.0],      [8644.0, 8645.0, 8646.0],      [8647.0, 8648.0, 8649.0],      [8650.0, 8651.0, 8652.0]],     [[8653.0, 8654.0, 8655.0],      [8656.0, 8657.0, 8658.0],      [8659.0, 8660.0, 8661.0],      [8662.0, 8663.0, 8664.0],      [8665.0, 8666.0, 8667.0],      [8668.0, 8669.0, 8670.0],      [8671.0, 8672.0, 8673.0]],     [[8674.0, 8675.0, 8676.0],      [8677.0, 8678.0, 8679.0],      [8680.0, 8681.0, 8682.0],      [8683.0, 8684.0, 8685.0],      [8686.0, 8687.0, 8688.0],      [8689.0, 8690.0, 8691.0],      [8692.0, 8693.0, 8694.0]]],    [[[8695.0, 8696.0, 8697.0],      [8698.0, 8699.0, 8700.0],      [8701.0, 8702.0, 8703.0],      [8704.0, 8705.0, 8706.0],      [8707.0, 8708.0, 8709.0],      [8710.0, 8711.0, 8712.0],      [8713.0, 8714.0, 8715.0]],     [[8716.0, 8717.0, 8718.0],      [8719.0, 8720.0, 8721.0],      [8722.0, 8723.0, 8724.0],      [8725.0, 8726.0, 8727.0],      [8728.0, 8729.0, 8730.0],      [8731.0, 8732.0, 8733.0],      [8734.0, 8735.0, 8736.0]],     [[8737.0, 8738.0, 8739.0],      [8740.0, 8741.0, 8742.0],      [8743.0, 8744.0, 8745.0],      [8746.0, 8747.0, 8748.0],      [8749.0, 8750.0, 8751.0],      [8752.0, 8753.0, 8754.0],      [8755.0, 8756.0, 8757.0]],     [[8758.0, 8759.0, 8760.0],      [8761.0, 8762.0, 8763.0],      [8764.0, 8765.0, 8766.0],      [8767.0, 8768.0, 8769.0],      [8770.0, 8771.0, 8772.0],      [8773.0, 8774.0, 8775.0],      [8776.0, 8777.0, 8778.0]],     [[8779.0, 8780.0, 8781.0],      [8782.0, 8783.0, 8784.0],      [8785.0, 8786.0, 8787.0],      [8788.0, 8789.0, 8790.0],      [8791.0, 8792.0, 8793.0],      [8794.0, 8795.0, 8796.0],      [8797.0, 8798.0, 8799.0]],     [[8800.0, 8801.0, 8802.0],      [8803.0, 8804.0, 8805.0],      [8806.0, 8807.0, 8808.0],      [8809.0, 8810.0, 8811.0],      [8812.0, 8813.0, 8814.0],      [8815.0, 8816.0, 8817.0],      [8818.0, 8819.0, 8820.0]]],    [[[8821.0, 8822.0, 8823.0],      [8824.0, 8825.0, 8826.0],      [8827.0, 8828.0, 8829.0],      [8830.0, 8831.0, 8832.0],      [8833.0, 8834.0, 8835.0],      [8836.0, 8837.0, 8838.0],      [8839.0, 8840.0, 8841.0]],     [[8842.0, 8843.0, 8844.0],      [8845.0, 8846.0, 8847.0],      [8848.0, 8849.0, 8850.0],      [8851.0, 8852.0, 8853.0],      [8854.0, 8855.0, 8856.0],      [8857.0, 8858.0, 8859.0],      [8860.0, 8861.0, 8862.0]],     [[8863.0, 8864.0, 8865.0],      [8866.0, 8867.0, 8868.0],      [8869.0, 8870.0, 8871.0],      [8872.0, 8873.0, 8874.0],      [8875.0, 8876.0, 8877.0],      [8878.0, 8879.0, 8880.0],      [8881.0, 8882.0, 8883.0]],     [[8884.0, 8885.0, 8886.0],      [8887.0, 8888.0, 8889.0],      [8890.0, 8891.0, 8892.0],      [8893.0, 8894.0, 8895.0],      [8896.0, 8897.0, 8898.0],      [8899.0, 8900.0, 8901.0],      [8902.0, 8903.0, 8904.0]],     [[8905.0, 8906.0, 8907.0],      [8908.0, 8909.0, 8910.0],      [8911.0, 8912.0, 8913.0],      [8914.0, 8915.0, 8916.0],      [8917.0, 8918.0, 8919.0],      [8920.0, 8921.0, 8922.0],      [8923.0, 8924.0, 8925.0]],     [[8926.0, 8927.0, 8928.0],      [8929.0, 8930.0, 8931.0],      [8932.0, 8933.0, 8934.0],      [8935.0, 8936.0, 8937.0],      [8938.0, 8939.0, 8940.0],      [8941.0, 8942.0, 8943.0],      [8944.0, 8945.0, 8946.0]]],    [[[8947.0, 8948.0, 8949.0],      [8950.0, 8951.0, 8952.0],      [8953.0, 8954.0, 8955.0],      [8956.0, 8957.0, 8958.0],      [8959.0, 8960.0, 8961.0],      [8962.0, 8963.0, 8964.0],      [8965.0, 8966.0, 8967.0]],     [[8968.0, 8969.0, 8970.0],      [8971.0, 8972.0, 8973.0],      [8974.0, 8975.0, 8976.0],      [8977.0, 8978.0, 8979.0],      [8980.0, 8981.0, 8982.0],      [8983.0, 8984.0, 8985.0],      [8986.0, 8987.0, 8988.0]],     [[8989.0, 8990.0, 8991.0],      [8992.0, 8993.0, 8994.0],      [8995.0, 8996.0, 8997.0],      [8998.0, 8999.0, 9000.0],      [9001.0, 9002.0, 9003.0],      [9004.0, 9005.0, 9006.0],      [9007.0, 9008.0, 9009.0]],     [[9010.0, 9011.0, 9012.0],      [9013.0, 9014.0, 9015.0],      [9016.0, 9017.0, 9018.0],      [9019.0, 9020.0, 9021.0],      [9022.0, 9023.0, 9024.0],      [9025.0, 9026.0, 9027.0],      [9028.0, 9029.0, 9030.0]],     [[9031.0, 9032.0, 9033.0],      [9034.0, 9035.0, 9036.0],      [9037.0, 9038.0, 9039.0],      [9040.0, 9041.0, 9042.0],      [9043.0, 9044.0, 9045.0],      [9046.0, 9047.0, 9048.0],      [9049.0, 9050.0, 9051.0]],     [[9052.0, 9053.0, 9054.0],      [9055.0, 9056.0, 9057.0],      [9058.0, 9059.0, 9060.0],      [9061.0, 9062.0, 9063.0],      [9064.0, 9065.0, 9066.0],      [9067.0, 9068.0, 9069.0],      [9070.0, 9071.0, 9072.0]]]]],  [[[[[9073.0, 9074.0, 9075.0],      [9076.0, 9077.0, 9078.0],      [9079.0, 9080.0, 9081.0],      [9082.0, 9083.0, 9084.0],      [9085.0, 9086.0, 9087.0],      [9088.0, 9089.0, 9090.0],      [9091.0, 9092.0, 9093.0]],     [[9094.0, 9095.0, 9096.0],      [9097.0, 9098.0, 9099.0],      [9100.0, 9101.0, 9102.0],      [9103.0, 9104.0, 9105.0],      [9106.0, 9107.0, 9108.0],      [9109.0, 9110.0, 9111.0],      [9112.0, 9113.0, 9114.0]],     [[9115.0, 9116.0, 9117.0],      [9118.0, 9119.0, 9120.0],      [9121.0, 9122.0, 9123.0],      [9124.0, 9125.0, 9126.0],      [9127.0, 9128.0, 9129.0],      [9130.0, 9131.0, 9132.0],      [9133.0, 9134.0, 9135.0]],     [[9136.0, 9137.0, 9138.0],      [9139.0, 9140.0, 9141.0],      [9142.0, 9143.0, 9144.0],      [9145.0, 9146.0, 9147.0],      [9148.0, 9149.0, 9150.0],      [9151.0, 9152.0, 9153.0],      [9154.0, 9155.0, 9156.0]],     [[9157.0, 9158.0, 9159.0],      [9160.0, 9161.0, 9162.0],      [9163.0, 9164.0, 9165.0],      [9166.0, 9167.0, 9168.0],      [9169.0, 9170.0, 9171.0],      [9172.0, 9173.0, 9174.0],      [9175.0, 9176.0, 9177.0]],     [[9178.0, 9179.0, 9180.0],      [9181.0, 9182.0, 9183.0],      [9184.0, 9185.0, 9186.0],      [9187.0, 9188.0, 9189.0],      [9190.0, 9191.0, 9192.0],      [9193.0, 9194.0, 9195.0],      [9196.0, 9197.0, 9198.0]]],    [[[9199.0, 9200.0, 9201.0],      [9202.0, 9203.0, 9204.0],      [9205.0, 9206.0, 9207.0],      [9208.0, 9209.0, 9210.0],      [9211.0, 9212.0, 9213.0],      [9214.0, 9215.0, 9216.0],      [9217.0, 9218.0, 9219.0]],     [[9220.0, 9221.0, 9222.0],      [9223.0, 9224.0, 9225.0],      [9226.0, 9227.0, 9228.0],      [9229.0, 9230.0, 9231.0],      [9232.0, 9233.0, 9234.0],      [9235.0, 9236.0, 9237.0],      [9238.0, 9239.0, 9240.0]],     [[9241.0, 9242.0, 9243.0],      [9244.0, 9245.0, 9246.0],      [9247.0, 9248.0, 9249.0],      [9250.0, 9251.0, 9252.0],      [9253.0, 9254.0, 9255.0],      [9256.0, 9257.0, 9258.0],      [9259.0, 9260.0, 9261.0]],     [[9262.0, 9263.0, 9264.0],      [9265.0, 9266.0, 9267.0],      [9268.0, 9269.0, 9270.0],      [9271.0, 9272.0, 9273.0],      [9274.0, 9275.0, 9276.0],      [9277.0, 9278.0, 9279.0],      [9280.0, 9281.0, 9282.0]],     [[9283.0, 9284.0, 9285.0],      [9286.0, 9287.0, 9288.0],      [9289.0, 9290.0, 9291.0],      [9292.0, 9293.0, 9294.0],      [9295.0, 9296.0, 9297.0],      [9298.0, 9299.0, 9300.0],      [9301.0, 9302.0, 9303.0]],     [[9304.0, 9305.0, 9306.0],      [9307.0, 9308.0, 9309.0],      [9310.0, 9311.0, 9312.0],      [9313.0, 9314.0, 9315.0],      [9316.0, 9317.0, 9318.0],      [9319.0, 9320.0, 9321.0],      [9322.0, 9323.0, 9324.0]]],    [[[9325.0, 9326.0, 9327.0],      [9328.0, 9329.0, 9330.0],      [9331.0, 9332.0, 9333.0],      [9334.0, 9335.0, 9336.0],      [9337.0, 9338.0, 9339.0],      [9340.0, 9341.0, 9342.0],      [9343.0, 9344.0, 9345.0]],     [[9346.0, 9347.0, 9348.0],      [9349.0, 9350.0, 9351.0],      [9352.0, 9353.0, 9354.0],      [9355.0, 9356.0, 9357.0],      [9358.0, 9359.0, 9360.0],      [9361.0, 9362.0, 9363.0],      [9364.0, 9365.0, 9366.0]],     [[9367.0, 9368.0, 9369.0],      [9370.0, 9371.0, 9372.0],      [9373.0, 9374.0, 9375.0],      [9376.0, 9377.0, 9378.0],      [9379.0, 9380.0, 9381.0],      [9382.0, 9383.0, 9384.0],      [9385.0, 9386.0, 9387.0]],     [[9388.0, 9389.0, 9390.0],      [9391.0, 9392.0, 9393.0],      [9394.0, 9395.0, 9396.0],      [9397.0, 9398.0, 9399.0],      [9400.0, 9401.0, 9402.0],      [9403.0, 9404.0, 9405.0],      [9406.0, 9407.0, 9408.0]],     [[9409.0, 9410.0, 9411.0],      [9412.0, 9413.0, 9414.0],      [9415.0, 9416.0, 9417.0],      [9418.0, 9419.0, 9420.0],      [9421.0, 9422.0, 9423.0],      [9424.0, 9425.0, 9426.0],      [9427.0, 9428.0, 9429.0]],     [[9430.0, 9431.0, 9432.0],      [9433.0, 9434.0, 9435.0],      [9436.0, 9437.0, 9438.0],      [9439.0, 9440.0, 9441.0],      [9442.0, 9443.0, 9444.0],      [9445.0, 9446.0, 9447.0],      [9448.0, 9449.0, 9450.0]]],    [[[9451.0, 9452.0, 9453.0],      [9454.0, 9455.0, 9456.0],      [9457.0, 9458.0, 9459.0],      [9460.0, 9461.0, 9462.0],      [9463.0, 9464.0, 9465.0],      [9466.0, 9467.0, 9468.0],      [9469.0, 9470.0, 9471.0]],     [[9472.0, 9473.0, 9474.0],      [9475.0, 9476.0, 9477.0],      [9478.0, 9479.0, 9480.0],      [9481.0, 9482.0, 9483.0],      [9484.0, 9485.0, 9486.0],      [9487.0, 9488.0, 9489.0],      [9490.0, 9491.0, 9492.0]],     [[9493.0, 9494.0, 9495.0],      [9496.0, 9497.0, 9498.0],      [9499.0, 9500.0, 9501.0],      [9502.0, 9503.0, 9504.0],      [9505.0, 9506.0, 9507.0],      [9508.0, 9509.0, 9510.0],      [9511.0, 9512.0, 9513.0]],     [[9514.0, 9515.0, 9516.0],      [9517.0, 9518.0, 9519.0],      [9520.0, 9521.0, 9522.0],      [9523.0, 9524.0, 9525.0],      [9526.0, 9527.0, 9528.0],      [9529.0, 9530.0, 9531.0],      [9532.0, 9533.0, 9534.0]],     [[9535.0, 9536.0, 9537.0],      [9538.0, 9539.0, 9540.0],      [9541.0, 9542.0, 9543.0],      [9544.0, 9545.0, 9546.0],      [9547.0, 9548.0, 9549.0],      [9550.0, 9551.0, 9552.0],      [9553.0, 9554.0, 9555.0]],     [[9556.0, 9557.0, 9558.0],      [9559.0, 9560.0, 9561.0],      [9562.0, 9563.0, 9564.0],      [9565.0, 9566.0, 9567.0],      [9568.0, 9569.0, 9570.0],      [9571.0, 9572.0, 9573.0],      [9574.0, 9575.0, 9576.0]]],    [[[9577.0, 9578.0, 9579.0],      [9580.0, 9581.0, 9582.0],      [9583.0, 9584.0, 9585.0],      [9586.0, 9587.0, 9588.0],      [9589.0, 9590.0, 9591.0],      [9592.0, 9593.0, 9594.0],      [9595.0, 9596.0, 9597.0]],     [[9598.0, 9599.0, 9600.0],      [9601.0, 9602.0, 9603.0],      [9604.0, 9605.0, 9606.0],      [9607.0, 9608.0, 9609.0],      [9610.0, 9611.0, 9612.0],      [9613.0, 9614.0, 9615.0],      [9616.0, 9617.0, 9618.0]],     [[9619.0, 9620.0, 9621.0],      [9622.0, 9623.0, 9624.0],      [9625.0, 9626.0, 9627.0],      [9628.0, 9629.0, 9630.0],      [9631.0, 9632.0, 9633.0],      [9634.0, 9635.0, 9636.0],      [9637.0, 9638.0, 9639.0]],     [[9640.0, 9641.0, 9642.0],      [9643.0, 9644.0, 9645.0],      [9646.0, 9647.0, 9648.0],      [9649.0, 9650.0, 9651.0],      [9652.0, 9653.0, 9654.0],      [9655.0, 9656.0, 9657.0],      [9658.0, 9659.0, 9660.0]],     [[9661.0, 9662.0, 9663.0],      [9664.0, 9665.0, 9666.0],      [9667.0, 9668.0, 9669.0],      [9670.0, 9671.0, 9672.0],      [9673.0, 9674.0, 9675.0],      [9676.0, 9677.0, 9678.0],      [9679.0, 9680.0, 9681.0]],     [[9682.0, 9683.0, 9684.0],      [9685.0, 9686.0, 9687.0],      [9688.0, 9689.0, 9690.0],      [9691.0, 9692.0, 9693.0],      [9694.0, 9695.0, 9696.0],      [9697.0, 9698.0, 9699.0],      [9700.0, 9701.0, 9702.0]]],    [[[9703.0, 9704.0, 9705.0],      [9706.0, 9707.0, 9708.0],      [9709.0, 9710.0, 9711.0],      [9712.0, 9713.0, 9714.0],      [9715.0, 9716.0, 9717.0],      [9718.0, 9719.0, 9720.0],      [9721.0, 9722.0, 9723.0]],     [[9724.0, 9725.0, 9726.0],      [9727.0, 9728.0, 9729.0],      [9730.0, 9731.0, 9732.0],      [9733.0, 9734.0, 9735.0],      [9736.0, 9737.0, 9738.0],      [9739.0, 9740.0, 9741.0],      [9742.0, 9743.0, 9744.0]],     [[9745.0, 9746.0, 9747.0],      [9748.0, 9749.0, 9750.0],      [9751.0, 9752.0, 9753.0],      [9754.0, 9755.0, 9756.0],      [9757.0, 9758.0, 9759.0],      [9760.0, 9761.0, 9762.0],      [9763.0, 9764.0, 9765.0]],     [[9766.0, 9767.0, 9768.0],      [9769.0, 9770.0, 9771.0],      [9772.0, 9773.0, 9774.0],      [9775.0, 9776.0, 9777.0],      [9778.0, 9779.0, 9780.0],      [9781.0, 9782.0, 9783.0],      [9784.0, 9785.0, 9786.0]],     [[9787.0, 9788.0, 9789.0],      [9790.0, 9791.0, 9792.0],      [9793.0, 9794.0, 9795.0],      [9796.0, 9797.0, 9798.0],      [9799.0, 9800.0, 9801.0],      [9802.0, 9803.0, 9804.0],      [9805.0, 9806.0, 9807.0]],     [[9808.0, 9809.0, 9810.0],      [9811.0, 9812.0, 9813.0],      [9814.0, 9815.0, 9816.0],      [9817.0, 9818.0, 9819.0],      [9820.0, 9821.0, 9822.0],      [9823.0, 9824.0, 9825.0],      [9826.0, 9827.0, 9828.0]]]],   [[[[9829.0, 9830.0, 9831.0],      [9832.0, 9833.0, 9834.0],      [9835.0, 9836.0, 9837.0],      [9838.0, 9839.0, 9840.0],      [9841.0, 9842.0, 9843.0],      [9844.0, 9845.0, 9846.0],      [9847.0, 9848.0, 9849.0]],     [[9850.0, 9851.0, 9852.0],      [9853.0, 9854.0, 9855.0],      [9856.0, 9857.0, 9858.0],      [9859.0, 9860.0, 9861.0],      [9862.0, 9863.0, 9864.0],      [9865.0, 9866.0, 9867.0],      [9868.0, 9869.0, 9870.0]],     [[9871.0, 9872.0, 9873.0],      [9874.0, 9875.0, 9876.0],      [9877.0, 9878.0, 9879.0],      [9880.0, 9881.0, 9882.0],      [9883.0, 9884.0, 9885.0],      [9886.0, 9887.0, 9888.0],      [9889.0, 9890.0, 9891.0]],     [[9892.0, 9893.0, 9894.0],      [9895.0, 9896.0, 9897.0],      [9898.0, 9899.0, 9900.0],      [9901.0, 9902.0, 9903.0],      [9904.0, 9905.0, 9906.0],      [9907.0, 9908.0, 9909.0],      [9910.0, 9911.0, 9912.0]],     [[9913.0, 9914.0, 9915.0],      [9916.0, 9917.0, 9918.0],      [9919.0, 9920.0, 9921.0],      [9922.0, 9923.0, 9924.0],      [9925.0, 9926.0, 9927.0],      [9928.0, 9929.0, 9930.0],      [9931.0, 9932.0, 9933.0]],     [[9934.0, 9935.0, 9936.0],      [9937.0, 9938.0, 9939.0],      [9940.0, 9941.0, 9942.0],      [9943.0, 9944.0, 9945.0],      [9946.0, 9947.0, 9948.0],      [9949.0, 9950.0, 9951.0],      [9952.0, 9953.0, 9954.0]]],    [[[9955.0, 9956.0, 9957.0],      [9958.0, 9959.0, 9960.0],      [9961.0, 9962.0, 9963.0],      [9964.0, 9965.0, 9966.0],      [9967.0, 9968.0, 9969.0],      [9970.0, 9971.0, 9972.0],      [9973.0, 9974.0, 9975.0]],     [[9976.0, 9977.0, 9978.0],      [9979.0, 9980.0, 9981.0],      [9982.0, 9983.0, 9984.0],      [9985.0, 9986.0, 9987.0],      [9988.0, 9989.0, 9990.0],      [9991.0, 9992.0, 9993.0],      [9994.0, 9995.0, 9996.0]],     [[9997.0, 9998.0, 9999.0],      [10000.0, 10001.0, 10002.0],      [10003.0, 10004.0, 10005.0],      [10006.0, 10007.0, 10008.0],      [10009.0, 10010.0, 10011.0],      [10012.0, 10013.0, 10014.0],      [10015.0, 10016.0, 10017.0]],     [[10018.0, 10019.0, 10020.0],      [10021.0, 10022.0, 10023.0],      [10024.0, 10025.0, 10026.0],      [10027.0, 10028.0, 10029.0],      [10030.0, 10031.0, 10032.0],      [10033.0, 10034.0, 10035.0],      [10036.0, 10037.0, 10038.0]],     [[10039.0, 10040.0, 10041.0],      [10042.0, 10043.0, 10044.0],      [10045.0, 10046.0, 10047.0],      [10048.0, 10049.0, 10050.0],      [10051.0, 10052.0, 10053.0],      [10054.0, 10055.0, 10056.0],      [10057.0, 10058.0, 10059.0]],     [[10060.0, 10061.0, 10062.0],      [10063.0, 10064.0, 10065.0],      [10066.0, 10067.0, 10068.0],      [10069.0, 10070.0, 10071.0],      [10072.0, 10073.0, 10074.0],      [10075.0, 10076.0, 10077.0],      [10078.0, 10079.0, 10080.0]]],    [[[10081.0, 10082.0, 10083.0],      [10084.0, 10085.0, 10086.0],      [10087.0, 10088.0, 10089.0],      [10090.0, 10091.0, 10092.0],      [10093.0, 10094.0, 10095.0],      [10096.0, 10097.0, 10098.0],      [10099.0, 10100.0, 10101.0]],     [[10102.0, 10103.0, 10104.0],      [10105.0, 10106.0, 10107.0],      [10108.0, 10109.0, 10110.0],      [10111.0, 10112.0, 10113.0],      [10114.0, 10115.0, 10116.0],      [10117.0, 10118.0, 10119.0],      [10120.0, 10121.0, 10122.0]],     [[10123.0, 10124.0, 10125.0],      [10126.0, 10127.0, 10128.0],      [10129.0, 10130.0, 10131.0],      [10132.0, 10133.0, 10134.0],      [10135.0, 10136.0, 10137.0],      [10138.0, 10139.0, 10140.0],      [10141.0, 10142.0, 10143.0]],     [[10144.0, 10145.0, 10146.0],      [10147.0, 10148.0, 10149.0],      [10150.0, 10151.0, 10152.0],      [10153.0, 10154.0, 10155.0],      [10156.0, 10157.0, 10158.0],      [10159.0, 10160.0, 10161.0],      [10162.0, 10163.0, 10164.0]],     [[10165.0, 10166.0, 10167.0],      [10168.0, 10169.0, 10170.0],      [10171.0, 10172.0, 10173.0],      [10174.0, 10175.0, 10176.0],      [10177.0, 10178.0, 10179.0],      [10180.0, 10181.0, 10182.0],      [10183.0, 10184.0, 10185.0]],     [[10186.0, 10187.0, 10188.0],      [10189.0, 10190.0, 10191.0],      [10192.0, 10193.0, 10194.0],      [10195.0, 10196.0, 10197.0],      [10198.0, 10199.0, 10200.0],      [10201.0, 10202.0, 10203.0],      [10204.0, 10205.0, 10206.0]]],    [[[10207.0, 10208.0, 10209.0],      [10210.0, 10211.0, 10212.0],      [10213.0, 10214.0, 10215.0],      [10216.0, 10217.0, 10218.0],      [10219.0, 10220.0, 10221.0],      [10222.0, 10223.0, 10224.0],      [10225.0, 10226.0, 10227.0]],     [[10228.0, 10229.0, 10230.0],      [10231.0, 10232.0, 10233.0],      [10234.0, 10235.0, 10236.0],      [10237.0, 10238.0, 10239.0],      [10240.0, 10241.0, 10242.0],      [10243.0, 10244.0, 10245.0],      [10246.0, 10247.0, 10248.0]],     [[10249.0, 10250.0, 10251.0],      [10252.0, 10253.0, 10254.0],      [10255.0, 10256.0, 10257.0],      [10258.0, 10259.0, 10260.0],      [10261.0, 10262.0, 10263.0],      [10264.0, 10265.0, 10266.0],      [10267.0, 10268.0, 10269.0]],     [[10270.0, 10271.0, 10272.0],      [10273.0, 10274.0, 10275.0],      [10276.0, 10277.0, 10278.0],      [10279.0, 10280.0, 10281.0],      [10282.0, 10283.0, 10284.0],      [10285.0, 10286.0, 10287.0],      [10288.0, 10289.0, 10290.0]],     [[10291.0, 10292.0, 10293.0],      [10294.0, 10295.0, 10296.0],      [10297.0, 10298.0, 10299.0],      [10300.0, 10301.0, 10302.0],      [10303.0, 10304.0, 10305.0],      [10306.0, 10307.0, 10308.0],      [10309.0, 10310.0, 10311.0]],     [[10312.0, 10313.0, 10314.0],      [10315.0, 10316.0, 10317.0],      [10318.0, 10319.0, 10320.0],      [10321.0, 10322.0, 10323.0],      [10324.0, 10325.0, 10326.0],      [10327.0, 10328.0, 10329.0],      [10330.0, 10331.0, 10332.0]]],    [[[10333.0, 10334.0, 10335.0],      [10336.0, 10337.0, 10338.0],      [10339.0, 10340.0, 10341.0],      [10342.0, 10343.0, 10344.0],      [10345.0, 10346.0, 10347.0],      [10348.0, 10349.0, 10350.0],      [10351.0, 10352.0, 10353.0]],     [[10354.0, 10355.0, 10356.0],      [10357.0, 10358.0, 10359.0],      [10360.0, 10361.0, 10362.0],      [10363.0, 10364.0, 10365.0],      [10366.0, 10367.0, 10368.0],      [10369.0, 10370.0, 10371.0],      [10372.0, 10373.0, 10374.0]],     [[10375.0, 10376.0, 10377.0],      [10378.0, 10379.0, 10380.0],      [10381.0, 10382.0, 10383.0],      [10384.0, 10385.0, 10386.0],      [10387.0, 10388.0, 10389.0],      [10390.0, 10391.0, 10392.0],      [10393.0, 10394.0, 10395.0]],     [[10396.0, 10397.0, 10398.0],      [10399.0, 10400.0, 10401.0],      [10402.0, 10403.0, 10404.0],      [10405.0, 10406.0, 10407.0],      [10408.0, 10409.0, 10410.0],      [10411.0, 10412.0, 10413.0],      [10414.0, 10415.0, 10416.0]],     [[10417.0, 10418.0, 10419.0],      [10420.0, 10421.0, 10422.0],      [10423.0, 10424.0, 10425.0],      [10426.0, 10427.0, 10428.0],      [10429.0, 10430.0, 10431.0],      [10432.0, 10433.0, 10434.0],      [10435.0, 10436.0, 10437.0]],     [[10438.0, 10439.0, 10440.0],      [10441.0, 10442.0, 10443.0],      [10444.0, 10445.0, 10446.0],      [10447.0, 10448.0, 10449.0],      [10450.0, 10451.0, 10452.0],      [10453.0, 10454.0, 10455.0],      [10456.0, 10457.0, 10458.0]]],    [[[10459.0, 10460.0, 10461.0],      [10462.0, 10463.0, 10464.0],      [10465.0, 10466.0, 10467.0],      [10468.0, 10469.0, 10470.0],      [10471.0, 10472.0, 10473.0],      [10474.0, 10475.0, 10476.0],      [10477.0, 10478.0, 10479.0]],     [[10480.0, 10481.0, 10482.0],      [10483.0, 10484.0, 10485.0],      [10486.0, 10487.0, 10488.0],      [10489.0, 10490.0, 10491.0],      [10492.0, 10493.0, 10494.0],      [10495.0, 10496.0, 10497.0],      [10498.0, 10499.0, 10500.0]],     [[10501.0, 10502.0, 10503.0],      [10504.0, 10505.0, 10506.0],      [10507.0, 10508.0, 10509.0],      [10510.0, 10511.0, 10512.0],      [10513.0, 10514.0, 10515.0],      [10516.0, 10517.0, 10518.0],      [10519.0, 10520.0, 10521.0]],     [[10522.0, 10523.0, 10524.0],      [10525.0, 10526.0, 10527.0],      [10528.0, 10529.0, 10530.0],      [10531.0, 10532.0, 10533.0],      [10534.0, 10535.0, 10536.0],      [10537.0, 10538.0, 10539.0],      [10540.0, 10541.0, 10542.0]],     [[10543.0, 10544.0, 10545.0],      [10546.0, 10547.0, 10548.0],      [10549.0, 10550.0, 10551.0],      [10552.0, 10553.0, 10554.0],      [10555.0, 10556.0, 10557.0],      [10558.0, 10559.0, 10560.0],      [10561.0, 10562.0, 10563.0]],     [[10564.0, 10565.0, 10566.0],      [10567.0, 10568.0, 10569.0],      [10570.0, 10571.0, 10572.0],      [10573.0, 10574.0, 10575.0],      [10576.0, 10577.0, 10578.0],      [10579.0, 10580.0, 10581.0],      [10582.0, 10583.0, 10584.0]]]],   [[[[10585.0, 10586.0, 10587.0],      [10588.0, 10589.0, 10590.0],      [10591.0, 10592.0, 10593.0],      [10594.0, 10595.0, 10596.0],      [10597.0, 10598.0, 10599.0],      [10600.0, 10601.0, 10602.0],      [10603.0, 10604.0, 10605.0]],     [[10606.0, 10607.0, 10608.0],      [10609.0, 10610.0, 10611.0],      [10612.0, 10613.0, 10614.0],      [10615.0, 10616.0, 10617.0],      [10618.0, 10619.0, 10620.0],      [10621.0, 10622.0, 10623.0],      [10624.0, 10625.0, 10626.0]],     [[10627.0, 10628.0, 10629.0],      [10630.0, 10631.0, 10632.0],      [10633.0, 10634.0, 10635.0],      [10636.0, 10637.0, 10638.0],      [10639.0, 10640.0, 10641.0],      [10642.0, 10643.0, 10644.0],      [10645.0, 10646.0, 10647.0]],     [[10648.0, 10649.0, 10650.0],      [10651.0, 10652.0, 10653.0],      [10654.0, 10655.0, 10656.0],      [10657.0, 10658.0, 10659.0],      [10660.0, 10661.0, 10662.0],      [10663.0, 10664.0, 10665.0],      [10666.0, 10667.0, 10668.0]],     [[10669.0, 10670.0, 10671.0],      [10672.0, 10673.0, 10674.0],      [10675.0, 10676.0, 10677.0],      [10678.0, 10679.0, 10680.0],      [10681.0, 10682.0, 10683.0],      [10684.0, 10685.0, 10686.0],      [10687.0, 10688.0, 10689.0]],     [[10690.0, 10691.0, 10692.0],      [10693.0, 10694.0, 10695.0],      [10696.0, 10697.0, 10698.0],      [10699.0, 10700.0, 10701.0],      [10702.0, 10703.0, 10704.0],      [10705.0, 10706.0, 10707.0],      [10708.0, 10709.0, 10710.0]]],    [[[10711.0, 10712.0, 10713.0],      [10714.0, 10715.0, 10716.0],      [10717.0, 10718.0, 10719.0],      [10720.0, 10721.0, 10722.0],      [10723.0, 10724.0, 10725.0],      [10726.0, 10727.0, 10728.0],      [10729.0, 10730.0, 10731.0]],     [[10732.0, 10733.0, 10734.0],      [10735.0, 10736.0, 10737.0],      [10738.0, 10739.0, 10740.0],      [10741.0, 10742.0, 10743.0],      [10744.0, 10745.0, 10746.0],      [10747.0, 10748.0, 10749.0],      [10750.0, 10751.0, 10752.0]],     [[10753.0, 10754.0, 10755.0],      [10756.0, 10757.0, 10758.0],      [10759.0, 10760.0, 10761.0],      [10762.0, 10763.0, 10764.0],      [10765.0, 10766.0, 10767.0],      [10768.0, 10769.0, 10770.0],      [10771.0, 10772.0, 10773.0]],     [[10774.0, 10775.0, 10776.0],      [10777.0, 10778.0, 10779.0],      [10780.0, 10781.0, 10782.0],      [10783.0, 10784.0, 10785.0],      [10786.0, 10787.0, 10788.0],      [10789.0, 10790.0, 10791.0],      [10792.0, 10793.0, 10794.0]],     [[10795.0, 10796.0, 10797.0],      [10798.0, 10799.0, 10800.0],      [10801.0, 10802.0, 10803.0],      [10804.0, 10805.0, 10806.0],      [10807.0, 10808.0, 10809.0],      [10810.0, 10811.0, 10812.0],      [10813.0, 10814.0, 10815.0]],     [[10816.0, 10817.0, 10818.0],      [10819.0, 10820.0, 10821.0],      [10822.0, 10823.0, 10824.0],      [10825.0, 10826.0, 10827.0],      [10828.0, 10829.0, 10830.0],      [10831.0, 10832.0, 10833.0],      [10834.0, 10835.0, 10836.0]]],    [[[10837.0, 10838.0, 10839.0],      [10840.0, 10841.0, 10842.0],      [10843.0, 10844.0, 10845.0],      [10846.0, 10847.0, 10848.0],      [10849.0, 10850.0, 10851.0],      [10852.0, 10853.0, 10854.0],      [10855.0, 10856.0, 10857.0]],     [[10858.0, 10859.0, 10860.0],      [10861.0, 10862.0, 10863.0],      [10864.0, 10865.0, 10866.0],      [10867.0, 10868.0, 10869.0],      [10870.0, 10871.0, 10872.0],      [10873.0, 10874.0, 10875.0],      [10876.0, 10877.0, 10878.0]],     [[10879.0, 10880.0, 10881.0],      [10882.0, 10883.0, 10884.0],      [10885.0, 10886.0, 10887.0],      [10888.0, 10889.0, 10890.0],      [10891.0, 10892.0, 10893.0],      [10894.0, 10895.0, 10896.0],      [10897.0, 10898.0, 10899.0]],     [[10900.0, 10901.0, 10902.0],      [10903.0, 10904.0, 10905.0],      [10906.0, 10907.0, 10908.0],      [10909.0, 10910.0, 10911.0],      [10912.0, 10913.0, 10914.0],      [10915.0, 10916.0, 10917.0],      [10918.0, 10919.0, 10920.0]],     [[10921.0, 10922.0, 10923.0],      [10924.0, 10925.0, 10926.0],      [10927.0, 10928.0, 10929.0],      [10930.0, 10931.0, 10932.0],      [10933.0, 10934.0, 10935.0],      [10936.0, 10937.0, 10938.0],      [10939.0, 10940.0, 10941.0]],     [[10942.0, 10943.0, 10944.0],      [10945.0, 10946.0, 10947.0],      [10948.0, 10949.0, 10950.0],      [10951.0, 10952.0, 10953.0],      [10954.0, 10955.0, 10956.0],      [10957.0, 10958.0, 10959.0],      [10960.0, 10961.0, 10962.0]]],    [[[10963.0, 10964.0, 10965.0],      [10966.0, 10967.0, 10968.0],      [10969.0, 10970.0, 10971.0],      [10972.0, 10973.0, 10974.0],      [10975.0, 10976.0, 10977.0],      [10978.0, 10979.0, 10980.0],      [10981.0, 10982.0, 10983.0]],     [[10984.0, 10985.0, 10986.0],      [10987.0, 10988.0, 10989.0],      [10990.0, 10991.0, 10992.0],      [10993.0, 10994.0, 10995.0],      [10996.0, 10997.0, 10998.0],      [10999.0, 11000.0, 11001.0],      [11002.0, 11003.0, 11004.0]],     [[11005.0, 11006.0, 11007.0],      [11008.0, 11009.0, 11010.0],      [11011.0, 11012.0, 11013.0],      [11014.0, 11015.0, 11016.0],      [11017.0, 11018.0, 11019.0],      [11020.0, 11021.0, 11022.0],      [11023.0, 11024.0, 11025.0]],     [[11026.0, 11027.0, 11028.0],      [11029.0, 11030.0, 11031.0],      [11032.0, 11033.0, 11034.0],      [11035.0, 11036.0, 11037.0],      [11038.0, 11039.0, 11040.0],      [11041.0, 11042.0, 11043.0],      [11044.0, 11045.0, 11046.0]],     [[11047.0, 11048.0, 11049.0],      [11050.0, 11051.0, 11052.0],      [11053.0, 11054.0, 11055.0],      [11056.0, 11057.0, 11058.0],      [11059.0, 11060.0, 11061.0],      [11062.0, 11063.0, 11064.0],      [11065.0, 11066.0, 11067.0]],     [[11068.0, 11069.0, 11070.0],      [11071.0, 11072.0, 11073.0],      [11074.0, 11075.0, 11076.0],      [11077.0, 11078.0, 11079.0],      [11080.0, 11081.0, 11082.0],      [11083.0, 11084.0, 11085.0],      [11086.0, 11087.0, 11088.0]]],    [[[11089.0, 11090.0, 11091.0],      [11092.0, 11093.0, 11094.0],      [11095.0, 11096.0, 11097.0],      [11098.0, 11099.0, 11100.0],      [11101.0, 11102.0, 11103.0],      [11104.0, 11105.0, 11106.0],      [11107.0, 11108.0, 11109.0]],     [[11110.0, 11111.0, 11112.0],      [11113.0, 11114.0, 11115.0],      [11116.0, 11117.0, 11118.0],      [11119.0, 11120.0, 11121.0],      [11122.0, 11123.0, 11124.0],      [11125.0, 11126.0, 11127.0],      [11128.0, 11129.0, 11130.0]],     [[11131.0, 11132.0, 11133.0],      [11134.0, 11135.0, 11136.0],      [11137.0, 11138.0, 11139.0],      [11140.0, 11141.0, 11142.0],      [11143.0, 11144.0, 11145.0],      [11146.0, 11147.0, 11148.0],      [11149.0, 11150.0, 11151.0]],     [[11152.0, 11153.0, 11154.0],      [11155.0, 11156.0, 11157.0],      [11158.0, 11159.0, 11160.0],      [11161.0, 11162.0, 11163.0],      [11164.0, 11165.0, 11166.0],      [11167.0, 11168.0, 11169.0],      [11170.0, 11171.0, 11172.0]],     [[11173.0, 11174.0, 11175.0],      [11176.0, 11177.0, 11178.0],      [11179.0, 11180.0, 11181.0],      [11182.0, 11183.0, 11184.0],      [11185.0, 11186.0, 11187.0],      [11188.0, 11189.0, 11190.0],      [11191.0, 11192.0, 11193.0]],     [[11194.0, 11195.0, 11196.0],      [11197.0, 11198.0, 11199.0],      [11200.0, 11201.0, 11202.0],      [11203.0, 11204.0, 11205.0],      [11206.0, 11207.0, 11208.0],      [11209.0, 11210.0, 11211.0],      [11212.0, 11213.0, 11214.0]]],    [[[11215.0, 11216.0, 11217.0],      [11218.0, 11219.0, 11220.0],      [11221.0, 11222.0, 11223.0],      [11224.0, 11225.0, 11226.0],      [11227.0, 11228.0, 11229.0],      [11230.0, 11231.0, 11232.0],      [11233.0, 11234.0, 11235.0]],     [[11236.0, 11237.0, 11238.0],      [11239.0, 11240.0, 11241.0],      [11242.0, 11243.0, 11244.0],      [11245.0, 11246.0, 11247.0],      [11248.0, 11249.0, 11250.0],      [11251.0, 11252.0, 11253.0],      [11254.0, 11255.0, 11256.0]],     [[11257.0, 11258.0, 11259.0],      [11260.0, 11261.0, 11262.0],      [11263.0, 11264.0, 11265.0],      [11266.0, 11267.0, 11268.0],      [11269.0, 11270.0, 11271.0],      [11272.0, 11273.0, 11274.0],      [11275.0, 11276.0, 11277.0]],     [[11278.0, 11279.0, 11280.0],      [11281.0, 11282.0, 11283.0],      [11284.0, 11285.0, 11286.0],      [11287.0, 11288.0, 11289.0],      [11290.0, 11291.0, 11292.0],      [11293.0, 11294.0, 11295.0],      [11296.0, 11297.0, 11298.0]],     [[11299.0, 11300.0, 11301.0],      [11302.0, 11303.0, 11304.0],      [11305.0, 11306.0, 11307.0],      [11308.0, 11309.0, 11310.0],      [11311.0, 11312.0, 11313.0],      [11314.0, 11315.0, 11316.0],      [11317.0, 11318.0, 11319.0]],     [[11320.0, 11321.0, 11322.0],      [11323.0, 11324.0, 11325.0],      [11326.0, 11327.0, 11328.0],      [11329.0, 11330.0, 11331.0],      [11332.0, 11333.0, 11334.0],      [11335.0, 11336.0, 11337.0],      [11338.0, 11339.0, 11340.0]]]],   [[[[11341.0, 11342.0, 11343.0],      [11344.0, 11345.0, 11346.0],      [11347.0, 11348.0, 11349.0],      [11350.0, 11351.0, 11352.0],      [11353.0, 11354.0, 11355.0],      [11356.0, 11357.0, 11358.0],      [11359.0, 11360.0, 11361.0]],     [[11362.0, 11363.0, 11364.0],      [11365.0, 11366.0, 11367.0],      [11368.0, 11369.0, 11370.0],      [11371.0, 11372.0, 11373.0],      [11374.0, 11375.0, 11376.0],      [11377.0, 11378.0, 11379.0],      [11380.0, 11381.0, 11382.0]],     [[11383.0, 11384.0, 11385.0],      [11386.0, 11387.0, 11388.0],      [11389.0, 11390.0, 11391.0],      [11392.0, 11393.0, 11394.0],      [11395.0, 11396.0, 11397.0],      [11398.0, 11399.0, 11400.0],      [11401.0, 11402.0, 11403.0]],     [[11404.0, 11405.0, 11406.0],      [11407.0, 11408.0, 11409.0],      [11410.0, 11411.0, 11412.0],      [11413.0, 11414.0, 11415.0],      [11416.0, 11417.0, 11418.0],      [11419.0, 11420.0, 11421.0],      [11422.0, 11423.0, 11424.0]],     [[11425.0, 11426.0, 11427.0],      [11428.0, 11429.0, 11430.0],      [11431.0, 11432.0, 11433.0],      [11434.0, 11435.0, 11436.0],      [11437.0, 11438.0, 11439.0],      [11440.0, 11441.0, 11442.0],      [11443.0, 11444.0, 11445.0]],     [[11446.0, 11447.0, 11448.0],      [11449.0, 11450.0, 11451.0],      [11452.0, 11453.0, 11454.0],      [11455.0, 11456.0, 11457.0],      [11458.0, 11459.0, 11460.0],      [11461.0, 11462.0, 11463.0],      [11464.0, 11465.0, 11466.0]]],    [[[11467.0, 11468.0, 11469.0],      [11470.0, 11471.0, 11472.0],      [11473.0, 11474.0, 11475.0],      [11476.0, 11477.0, 11478.0],      [11479.0, 11480.0, 11481.0],      [11482.0, 11483.0, 11484.0],      [11485.0, 11486.0, 11487.0]],     [[11488.0, 11489.0, 11490.0],      [11491.0, 11492.0, 11493.0],      [11494.0, 11495.0, 11496.0],      [11497.0, 11498.0, 11499.0],      [11500.0, 11501.0, 11502.0],      [11503.0, 11504.0, 11505.0],      [11506.0, 11507.0, 11508.0]],     [[11509.0, 11510.0, 11511.0],      [11512.0, 11513.0, 11514.0],      [11515.0, 11516.0, 11517.0],      [11518.0, 11519.0, 11520.0],      [11521.0, 11522.0, 11523.0],      [11524.0, 11525.0, 11526.0],      [11527.0, 11528.0, 11529.0]],     [[11530.0, 11531.0, 11532.0],      [11533.0, 11534.0, 11535.0],      [11536.0, 11537.0, 11538.0],      [11539.0, 11540.0, 11541.0],      [11542.0, 11543.0, 11544.0],      [11545.0, 11546.0, 11547.0],      [11548.0, 11549.0, 11550.0]],     [[11551.0, 11552.0, 11553.0],      [11554.0, 11555.0, 11556.0],      [11557.0, 11558.0, 11559.0],      [11560.0, 11561.0, 11562.0],      [11563.0, 11564.0, 11565.0],      [11566.0, 11567.0, 11568.0],      [11569.0, 11570.0, 11571.0]],     [[11572.0, 11573.0, 11574.0],      [11575.0, 11576.0, 11577.0],      [11578.0, 11579.0, 11580.0],      [11581.0, 11582.0, 11583.0],      [11584.0, 11585.0, 11586.0],      [11587.0, 11588.0, 11589.0],      [11590.0, 11591.0, 11592.0]]],    [[[11593.0, 11594.0, 11595.0],      [11596.0, 11597.0, 11598.0],      [11599.0, 11600.0, 11601.0],      [11602.0, 11603.0, 11604.0],      [11605.0, 11606.0, 11607.0],      [11608.0, 11609.0, 11610.0],      [11611.0, 11612.0, 11613.0]],     [[11614.0, 11615.0, 11616.0],      [11617.0, 11618.0, 11619.0],      [11620.0, 11621.0, 11622.0],      [11623.0, 11624.0, 11625.0],      [11626.0, 11627.0, 11628.0],      [11629.0, 11630.0, 11631.0],      [11632.0, 11633.0, 11634.0]],     [[11635.0, 11636.0, 11637.0],      [11638.0, 11639.0, 11640.0],      [11641.0, 11642.0, 11643.0],      [11644.0, 11645.0, 11646.0],      [11647.0, 11648.0, 11649.0],      [11650.0, 11651.0, 11652.0],      [11653.0, 11654.0, 11655.0]],     [[11656.0, 11657.0, 11658.0],      [11659.0, 11660.0, 11661.0],      [11662.0, 11663.0, 11664.0],      [11665.0, 11666.0, 11667.0],      [11668.0, 11669.0, 11670.0],      [11671.0, 11672.0, 11673.0],      [11674.0, 11675.0, 11676.0]],     [[11677.0, 11678.0, 11679.0],      [11680.0, 11681.0, 11682.0],      [11683.0, 11684.0, 11685.0],      [11686.0, 11687.0, 11688.0],      [11689.0, 11690.0, 11691.0],      [11692.0, 11693.0, 11694.0],      [11695.0, 11696.0, 11697.0]],     [[11698.0, 11699.0, 11700.0],      [11701.0, 11702.0, 11703.0],      [11704.0, 11705.0, 11706.0],      [11707.0, 11708.0, 11709.0],      [11710.0, 11711.0, 11712.0],      [11713.0, 11714.0, 11715.0],      [11716.0, 11717.0, 11718.0]]],    [[[11719.0, 11720.0, 11721.0],      [11722.0, 11723.0, 11724.0],      [11725.0, 11726.0, 11727.0],      [11728.0, 11729.0, 11730.0],      [11731.0, 11732.0, 11733.0],      [11734.0, 11735.0, 11736.0],      [11737.0, 11738.0, 11739.0]],     [[11740.0, 11741.0, 11742.0],      [11743.0, 11744.0, 11745.0],      [11746.0, 11747.0, 11748.0],      [11749.0, 11750.0, 11751.0],      [11752.0, 11753.0, 11754.0],      [11755.0, 11756.0, 11757.0],      [11758.0, 11759.0, 11760.0]],     [[11761.0, 11762.0, 11763.0],      [11764.0, 11765.0, 11766.0],      [11767.0, 11768.0, 11769.0],      [11770.0, 11771.0, 11772.0],      [11773.0, 11774.0, 11775.0],      [11776.0, 11777.0, 11778.0],      [11779.0, 11780.0, 11781.0]],     [[11782.0, 11783.0, 11784.0],      [11785.0, 11786.0, 11787.0],      [11788.0, 11789.0, 11790.0],      [11791.0, 11792.0, 11793.0],      [11794.0, 11795.0, 11796.0],      [11797.0, 11798.0, 11799.0],      [11800.0, 11801.0, 11802.0]],     [[11803.0, 11804.0, 11805.0],      [11806.0, 11807.0, 11808.0],      [11809.0, 11810.0, 11811.0],      [11812.0, 11813.0, 11814.0],      [11815.0, 11816.0, 11817.0],      [11818.0, 11819.0, 11820.0],      [11821.0, 11822.0, 11823.0]],     [[11824.0, 11825.0, 11826.0],      [11827.0, 11828.0, 11829.0],      [11830.0, 11831.0, 11832.0],      [11833.0, 11834.0, 11835.0],      [11836.0, 11837.0, 11838.0],      [11839.0, 11840.0, 11841.0],      [11842.0, 11843.0, 11844.0]]],    [[[11845.0, 11846.0, 11847.0],      [11848.0, 11849.0, 11850.0],      [11851.0, 11852.0, 11853.0],      [11854.0, 11855.0, 11856.0],      [11857.0, 11858.0, 11859.0],      [11860.0, 11861.0, 11862.0],      [11863.0, 11864.0, 11865.0]],     [[11866.0, 11867.0, 11868.0],      [11869.0, 11870.0, 11871.0],      [11872.0, 11873.0, 11874.0],      [11875.0, 11876.0, 11877.0],      [11878.0, 11879.0, 11880.0],      [11881.0, 11882.0, 11883.0],      [11884.0, 11885.0, 11886.0]],     [[11887.0, 11888.0, 11889.0],      [11890.0, 11891.0, 11892.0],      [11893.0, 11894.0, 11895.0],      [11896.0, 11897.0, 11898.0],      [11899.0, 11900.0, 11901.0],      [11902.0, 11903.0, 11904.0],      [11905.0, 11906.0, 11907.0]],     [[11908.0, 11909.0, 11910.0],      [11911.0, 11912.0, 11913.0],      [11914.0, 11915.0, 11916.0],      [11917.0, 11918.0, 11919.0],      [11920.0, 11921.0, 11922.0],      [11923.0, 11924.0, 11925.0],      [11926.0, 11927.0, 11928.0]],     [[11929.0, 11930.0, 11931.0],      [11932.0, 11933.0, 11934.0],      [11935.0, 11936.0, 11937.0],      [11938.0, 11939.0, 11940.0],      [11941.0, 11942.0, 11943.0],      [11944.0, 11945.0, 11946.0],      [11947.0, 11948.0, 11949.0]],     [[11950.0, 11951.0, 11952.0],      [11953.0, 11954.0, 11955.0],      [11956.0, 11957.0, 11958.0],      [11959.0, 11960.0, 11961.0],      [11962.0, 11963.0, 11964.0],      [11965.0, 11966.0, 11967.0],      [11968.0, 11969.0, 11970.0]]],    [[[11971.0, 11972.0, 11973.0],      [11974.0, 11975.0, 11976.0],      [11977.0, 11978.0, 11979.0],      [11980.0, 11981.0, 11982.0],      [11983.0, 11984.0, 11985.0],      [11986.0, 11987.0, 11988.0],      [11989.0, 11990.0, 11991.0]],     [[11992.0, 11993.0, 11994.0],      [11995.0, 11996.0, 11997.0],      [11998.0, 11999.0, 12000.0],      [12001.0, 12002.0, 12003.0],      [12004.0, 12005.0, 12006.0],      [12007.0, 12008.0, 12009.0],      [12010.0, 12011.0, 12012.0]],     [[12013.0, 12014.0, 12015.0],      [12016.0, 12017.0, 12018.0],      [12019.0, 12020.0, 12021.0],      [12022.0, 12023.0, 12024.0],      [12025.0, 12026.0, 12027.0],      [12028.0, 12029.0, 12030.0],      [12031.0, 12032.0, 12033.0]],     [[12034.0, 12035.0, 12036.0],      [12037.0, 12038.0, 12039.0],      [12040.0, 12041.0, 12042.0],      [12043.0, 12044.0, 12045.0],      [12046.0, 12047.0, 12048.0],      [12049.0, 12050.0, 12051.0],      [12052.0, 12053.0, 12054.0]],     [[12055.0, 12056.0, 12057.0],      [12058.0, 12059.0, 12060.0],      [12061.0, 12062.0, 12063.0],      [12064.0, 12065.0, 12066.0],      [12067.0, 12068.0, 12069.0],      [12070.0, 12071.0, 12072.0],      [12073.0, 12074.0, 12075.0]],     [[12076.0, 12077.0, 12078.0],      [12079.0, 12080.0, 12081.0],      [12082.0, 12083.0, 12084.0],      [12085.0, 12086.0, 12087.0],      [12088.0, 12089.0, 12090.0],      [12091.0, 12092.0, 12093.0],      [12094.0, 12095.0, 12096.0]]]],   [[[[12097.0, 12098.0, 12099.0],      [12100.0, 12101.0, 12102.0],      [12103.0, 12104.0, 12105.0],      [12106.0, 12107.0, 12108.0],      [12109.0, 12110.0, 12111.0],      [12112.0, 12113.0, 12114.0],      [12115.0, 12116.0, 12117.0]],     [[12118.0, 12119.0, 12120.0],      [12121.0, 12122.0, 12123.0],      [12124.0, 12125.0, 12126.0],      [12127.0, 12128.0, 12129.0],      [12130.0, 12131.0, 12132.0],      [12133.0, 12134.0, 12135.0],      [12136.0, 12137.0, 12138.0]],     [[12139.0, 12140.0, 12141.0],      [12142.0, 12143.0, 12144.0],      [12145.0, 12146.0, 12147.0],      [12148.0, 12149.0, 12150.0],      [12151.0, 12152.0, 12153.0],      [12154.0, 12155.0, 12156.0],      [12157.0, 12158.0, 12159.0]],     [[12160.0, 12161.0, 12162.0],      [12163.0, 12164.0, 12165.0],      [12166.0, 12167.0, 12168.0],      [12169.0, 12170.0, 12171.0],      [12172.0, 12173.0, 12174.0],      [12175.0, 12176.0, 12177.0],      [12178.0, 12179.0, 12180.0]],     [[12181.0, 12182.0, 12183.0],      [12184.0, 12185.0, 12186.0],      [12187.0, 12188.0, 12189.0],      [12190.0, 12191.0, 12192.0],      [12193.0, 12194.0, 12195.0],      [12196.0, 12197.0, 12198.0],      [12199.0, 12200.0, 12201.0]],     [[12202.0, 12203.0, 12204.0],      [12205.0, 12206.0, 12207.0],      [12208.0, 12209.0, 12210.0],      [12211.0, 12212.0, 12213.0],      [12214.0, 12215.0, 12216.0],      [12217.0, 12218.0, 12219.0],      [12220.0, 12221.0, 12222.0]]],    [[[12223.0, 12224.0, 12225.0],      [12226.0, 12227.0, 12228.0],      [12229.0, 12230.0, 12231.0],      [12232.0, 12233.0, 12234.0],      [12235.0, 12236.0, 12237.0],      [12238.0, 12239.0, 12240.0],      [12241.0, 12242.0, 12243.0]],     [[12244.0, 12245.0, 12246.0],      [12247.0, 12248.0, 12249.0],      [12250.0, 12251.0, 12252.0],      [12253.0, 12254.0, 12255.0],      [12256.0, 12257.0, 12258.0],      [12259.0, 12260.0, 12261.0],      [12262.0, 12263.0, 12264.0]],     [[12265.0, 12266.0, 12267.0],      [12268.0, 12269.0, 12270.0],      [12271.0, 12272.0, 12273.0],      [12274.0, 12275.0, 12276.0],      [12277.0, 12278.0, 12279.0],      [12280.0, 12281.0, 12282.0],      [12283.0, 12284.0, 12285.0]],     [[12286.0, 12287.0, 12288.0],      [12289.0, 12290.0, 12291.0],      [12292.0, 12293.0, 12294.0],      [12295.0, 12296.0, 12297.0],      [12298.0, 12299.0, 12300.0],      [12301.0, 12302.0, 12303.0],      [12304.0, 12305.0, 12306.0]],     [[12307.0, 12308.0, 12309.0],      [12310.0, 12311.0, 12312.0],      [12313.0, 12314.0, 12315.0],      [12316.0, 12317.0, 12318.0],      [12319.0, 12320.0, 12321.0],      [12322.0, 12323.0, 12324.0],      [12325.0, 12326.0, 12327.0]],     [[12328.0, 12329.0, 12330.0],      [12331.0, 12332.0, 12333.0],      [12334.0, 12335.0, 12336.0],      [12337.0, 12338.0, 12339.0],      [12340.0, 12341.0, 12342.0],      [12343.0, 12344.0, 12345.0],      [12346.0, 12347.0, 12348.0]]],    [[[12349.0, 12350.0, 12351.0],      [12352.0, 12353.0, 12354.0],      [12355.0, 12356.0, 12357.0],      [12358.0, 12359.0, 12360.0],      [12361.0, 12362.0, 12363.0],      [12364.0, 12365.0, 12366.0],      [12367.0, 12368.0, 12369.0]],     [[12370.0, 12371.0, 12372.0],      [12373.0, 12374.0, 12375.0],      [12376.0, 12377.0, 12378.0],      [12379.0, 12380.0, 12381.0],      [12382.0, 12383.0, 12384.0],      [12385.0, 12386.0, 12387.0],      [12388.0, 12389.0, 12390.0]],     [[12391.0, 12392.0, 12393.0],      [12394.0, 12395.0, 12396.0],      [12397.0, 12398.0, 12399.0],      [12400.0, 12401.0, 12402.0],      [12403.0, 12404.0, 12405.0],      [12406.0, 12407.0, 12408.0],      [12409.0, 12410.0, 12411.0]],     [[12412.0, 12413.0, 12414.0],      [12415.0, 12416.0, 12417.0],      [12418.0, 12419.0, 12420.0],      [12421.0, 12422.0, 12423.0],      [12424.0, 12425.0, 12426.0],      [12427.0, 12428.0, 12429.0],      [12430.0, 12431.0, 12432.0]],     [[12433.0, 12434.0, 12435.0],      [12436.0, 12437.0, 12438.0],      [12439.0, 12440.0, 12441.0],      [12442.0, 12443.0, 12444.0],      [12445.0, 12446.0, 12447.0],      [12448.0, 12449.0, 12450.0],      [12451.0, 12452.0, 12453.0]],     [[12454.0, 12455.0, 12456.0],      [12457.0, 12458.0, 12459.0],      [12460.0, 12461.0, 12462.0],      [12463.0, 12464.0, 12465.0],      [12466.0, 12467.0, 12468.0],      [12469.0, 12470.0, 12471.0],      [12472.0, 12473.0, 12474.0]]],    [[[12475.0, 12476.0, 12477.0],      [12478.0, 12479.0, 12480.0],      [12481.0, 12482.0, 12483.0],      [12484.0, 12485.0, 12486.0],      [12487.0, 12488.0, 12489.0],      [12490.0, 12491.0, 12492.0],      [12493.0, 12494.0, 12495.0]],     [[12496.0, 12497.0, 12498.0],      [12499.0, 12500.0, 12501.0],      [12502.0, 12503.0, 12504.0],      [12505.0, 12506.0, 12507.0],      [12508.0, 12509.0, 12510.0],      [12511.0, 12512.0, 12513.0],      [12514.0, 12515.0, 12516.0]],     [[12517.0, 12518.0, 12519.0],      [12520.0, 12521.0, 12522.0],      [12523.0, 12524.0, 12525.0],      [12526.0, 12527.0, 12528.0],      [12529.0, 12530.0, 12531.0],      [12532.0, 12533.0, 12534.0],      [12535.0, 12536.0, 12537.0]],     [[12538.0, 12539.0, 12540.0],      [12541.0, 12542.0, 12543.0],      [12544.0, 12545.0, 12546.0],      [12547.0, 12548.0, 12549.0],      [12550.0, 12551.0, 12552.0],      [12553.0, 12554.0, 12555.0],      [12556.0, 12557.0, 12558.0]],     [[12559.0, 12560.0, 12561.0],      [12562.0, 12563.0, 12564.0],      [12565.0, 12566.0, 12567.0],      [12568.0, 12569.0, 12570.0],      [12571.0, 12572.0, 12573.0],      [12574.0, 12575.0, 12576.0],      [12577.0, 12578.0, 12579.0]],     [[12580.0, 12581.0, 12582.0],      [12583.0, 12584.0, 12585.0],      [12586.0, 12587.0, 12588.0],      [12589.0, 12590.0, 12591.0],      [12592.0, 12593.0, 12594.0],      [12595.0, 12596.0, 12597.0],      [12598.0, 12599.0, 12600.0]]],    [[[12601.0, 12602.0, 12603.0],      [12604.0, 12605.0, 12606.0],      [12607.0, 12608.0, 12609.0],      [12610.0, 12611.0, 12612.0],      [12613.0, 12614.0, 12615.0],      [12616.0, 12617.0, 12618.0],      [12619.0, 12620.0, 12621.0]],     [[12622.0, 12623.0, 12624.0],      [12625.0, 12626.0, 12627.0],      [12628.0, 12629.0, 12630.0],      [12631.0, 12632.0, 12633.0],      [12634.0, 12635.0, 12636.0],      [12637.0, 12638.0, 12639.0],      [12640.0, 12641.0, 12642.0]],     [[12643.0, 12644.0, 12645.0],      [12646.0, 12647.0, 12648.0],      [12649.0, 12650.0, 12651.0],      [12652.0, 12653.0, 12654.0],      [12655.0, 12656.0, 12657.0],      [12658.0, 12659.0, 12660.0],      [12661.0, 12662.0, 12663.0]],     [[12664.0, 12665.0, 12666.0],      [12667.0, 12668.0, 12669.0],      [12670.0, 12671.0, 12672.0],      [12673.0, 12674.0, 12675.0],      [12676.0, 12677.0, 12678.0],      [12679.0, 12680.0, 12681.0],      [12682.0, 12683.0, 12684.0]],     [[12685.0, 12686.0, 12687.0],      [12688.0, 12689.0, 12690.0],      [12691.0, 12692.0, 12693.0],      [12694.0, 12695.0, 12696.0],      [12697.0, 12698.0, 12699.0],      [12700.0, 12701.0, 12702.0],      [12703.0, 12704.0, 12705.0]],     [[12706.0, 12707.0, 12708.0],      [12709.0, 12710.0, 12711.0],      [12712.0, 12713.0, 12714.0],      [12715.0, 12716.0, 12717.0],      [12718.0, 12719.0, 12720.0],      [12721.0, 12722.0, 12723.0],      [12724.0, 12725.0, 12726.0]]],    [[[12727.0, 12728.0, 12729.0],      [12730.0, 12731.0, 12732.0],      [12733.0, 12734.0, 12735.0],      [12736.0, 12737.0, 12738.0],      [12739.0, 12740.0, 12741.0],      [12742.0, 12743.0, 12744.0],      [12745.0, 12746.0, 12747.0]],     [[12748.0, 12749.0, 12750.0],      [12751.0, 12752.0, 12753.0],      [12754.0, 12755.0, 12756.0],      [12757.0, 12758.0, 12759.0],      [12760.0, 12761.0, 12762.0],      [12763.0, 12764.0, 12765.0],      [12766.0, 12767.0, 12768.0]],     [[12769.0, 12770.0, 12771.0],      [12772.0, 12773.0, 12774.0],      [12775.0, 12776.0, 12777.0],      [12778.0, 12779.0, 12780.0],      [12781.0, 12782.0, 12783.0],      [12784.0, 12785.0, 12786.0],      [12787.0, 12788.0, 12789.0]],     [[12790.0, 12791.0, 12792.0],      [12793.0, 12794.0, 12795.0],      [12796.0, 12797.0, 12798.0],      [12799.0, 12800.0, 12801.0],      [12802.0, 12803.0, 12804.0],      [12805.0, 12806.0, 12807.0],      [12808.0, 12809.0, 12810.0]],     [[12811.0, 12812.0, 12813.0],      [12814.0, 12815.0, 12816.0],      [12817.0, 12818.0, 12819.0],      [12820.0, 12821.0, 12822.0],      [12823.0, 12824.0, 12825.0],      [12826.0, 12827.0, 12828.0],      [12829.0, 12830.0, 12831.0]],     [[12832.0, 12833.0, 12834.0],      [12835.0, 12836.0, 12837.0],      [12838.0, 12839.0, 12840.0],      [12841.0, 12842.0, 12843.0],      [12844.0, 12845.0, 12846.0],      [12847.0, 12848.0, 12849.0],      [12850.0, 12851.0, 12852.0]]]],   [[[[12853.0, 12854.0, 12855.0],      [12856.0, 12857.0, 12858.0],      [12859.0, 12860.0, 12861.0],      [12862.0, 12863.0, 12864.0],      [12865.0, 12866.0, 12867.0],      [12868.0, 12869.0, 12870.0],      [12871.0, 12872.0, 12873.0]],     [[12874.0, 12875.0, 12876.0],      [12877.0, 12878.0, 12879.0],      [12880.0, 12881.0, 12882.0],      [12883.0, 12884.0, 12885.0],      [12886.0, 12887.0, 12888.0],      [12889.0, 12890.0, 12891.0],      [12892.0, 12893.0, 12894.0]],     [[12895.0, 12896.0, 12897.0],      [12898.0, 12899.0, 12900.0],      [12901.0, 12902.0, 12903.0],      [12904.0, 12905.0, 12906.0],      [12907.0, 12908.0, 12909.0],      [12910.0, 12911.0, 12912.0],      [12913.0, 12914.0, 12915.0]],     [[12916.0, 12917.0, 12918.0],      [12919.0, 12920.0, 12921.0],      [12922.0, 12923.0, 12924.0],      [12925.0, 12926.0, 12927.0],      [12928.0, 12929.0, 12930.0],      [12931.0, 12932.0, 12933.0],      [12934.0, 12935.0, 12936.0]],     [[12937.0, 12938.0, 12939.0],      [12940.0, 12941.0, 12942.0],      [12943.0, 12944.0, 12945.0],      [12946.0, 12947.0, 12948.0],      [12949.0, 12950.0, 12951.0],      [12952.0, 12953.0, 12954.0],      [12955.0, 12956.0, 12957.0]],     [[12958.0, 12959.0, 12960.0],      [12961.0, 12962.0, 12963.0],      [12964.0, 12965.0, 12966.0],      [12967.0, 12968.0, 12969.0],      [12970.0, 12971.0, 12972.0],      [12973.0, 12974.0, 12975.0],      [12976.0, 12977.0, 12978.0]]],    [[[12979.0, 12980.0, 12981.0],      [12982.0, 12983.0, 12984.0],      [12985.0, 12986.0, 12987.0],      [12988.0, 12989.0, 12990.0],      [12991.0, 12992.0, 12993.0],      [12994.0, 12995.0, 12996.0],      [12997.0, 12998.0, 12999.0]],     [[13000.0, 13001.0, 13002.0],      [13003.0, 13004.0, 13005.0],      [13006.0, 13007.0, 13008.0],      [13009.0, 13010.0, 13011.0],      [13012.0, 13013.0, 13014.0],      [13015.0, 13016.0, 13017.0],      [13018.0, 13019.0, 13020.0]],     [[13021.0, 13022.0, 13023.0],      [13024.0, 13025.0, 13026.0],      [13027.0, 13028.0, 13029.0],      [13030.0, 13031.0, 13032.0],      [13033.0, 13034.0, 13035.0],      [13036.0, 13037.0, 13038.0],      [13039.0, 13040.0, 13041.0]],     [[13042.0, 13043.0, 13044.0],      [13045.0, 13046.0, 13047.0],      [13048.0, 13049.0, 13050.0],      [13051.0, 13052.0, 13053.0],      [13054.0, 13055.0, 13056.0],      [13057.0, 13058.0, 13059.0],      [13060.0, 13061.0, 13062.0]],     [[13063.0, 13064.0, 13065.0],      [13066.0, 13067.0, 13068.0],      [13069.0, 13070.0, 13071.0],      [13072.0, 13073.0, 13074.0],      [13075.0, 13076.0, 13077.0],      [13078.0, 13079.0, 13080.0],      [13081.0, 13082.0, 13083.0]],     [[13084.0, 13085.0, 13086.0],      [13087.0, 13088.0, 13089.0],      [13090.0, 13091.0, 13092.0],      [13093.0, 13094.0, 13095.0],      [13096.0, 13097.0, 13098.0],      [13099.0, 13100.0, 13101.0],      [13102.0, 13103.0, 13104.0]]],    [[[13105.0, 13106.0, 13107.0],      [13108.0, 13109.0, 13110.0],      [13111.0, 13112.0, 13113.0],      [13114.0, 13115.0, 13116.0],      [13117.0, 13118.0, 13119.0],      [13120.0, 13121.0, 13122.0],      [13123.0, 13124.0, 13125.0]],     [[13126.0, 13127.0, 13128.0],      [13129.0, 13130.0, 13131.0],      [13132.0, 13133.0, 13134.0],      [13135.0, 13136.0, 13137.0],      [13138.0, 13139.0, 13140.0],      [13141.0, 13142.0, 13143.0],      [13144.0, 13145.0, 13146.0]],     [[13147.0, 13148.0, 13149.0],      [13150.0, 13151.0, 13152.0],      [13153.0, 13154.0, 13155.0],      [13156.0, 13157.0, 13158.0],      [13159.0, 13160.0, 13161.0],      [13162.0, 13163.0, 13164.0],      [13165.0, 13166.0, 13167.0]],     [[13168.0, 13169.0, 13170.0],      [13171.0, 13172.0, 13173.0],      [13174.0, 13175.0, 13176.0],      [13177.0, 13178.0, 13179.0],      [13180.0, 13181.0, 13182.0],      [13183.0, 13184.0, 13185.0],      [13186.0, 13187.0, 13188.0]],     [[13189.0, 13190.0, 13191.0],      [13192.0, 13193.0, 13194.0],      [13195.0, 13196.0, 13197.0],      [13198.0, 13199.0, 13200.0],      [13201.0, 13202.0, 13203.0],      [13204.0, 13205.0, 13206.0],      [13207.0, 13208.0, 13209.0]],     [[13210.0, 13211.0, 13212.0],      [13213.0, 13214.0, 13215.0],      [13216.0, 13217.0, 13218.0],      [13219.0, 13220.0, 13221.0],      [13222.0, 13223.0, 13224.0],      [13225.0, 13226.0, 13227.0],      [13228.0, 13229.0, 13230.0]]],    [[[13231.0, 13232.0, 13233.0],      [13234.0, 13235.0, 13236.0],      [13237.0, 13238.0, 13239.0],      [13240.0, 13241.0, 13242.0],      [13243.0, 13244.0, 13245.0],      [13246.0, 13247.0, 13248.0],      [13249.0, 13250.0, 13251.0]],     [[13252.0, 13253.0, 13254.0],      [13255.0, 13256.0, 13257.0],      [13258.0, 13259.0, 13260.0],      [13261.0, 13262.0, 13263.0],      [13264.0, 13265.0, 13266.0],      [13267.0, 13268.0, 13269.0],      [13270.0, 13271.0, 13272.0]],     [[13273.0, 13274.0, 13275.0],      [13276.0, 13277.0, 13278.0],      [13279.0, 13280.0, 13281.0],      [13282.0, 13283.0, 13284.0],      [13285.0, 13286.0, 13287.0],      [13288.0, 13289.0, 13290.0],      [13291.0, 13292.0, 13293.0]],     [[13294.0, 13295.0, 13296.0],      [13297.0, 13298.0, 13299.0],      [13300.0, 13301.0, 13302.0],      [13303.0, 13304.0, 13305.0],      [13306.0, 13307.0, 13308.0],      [13309.0, 13310.0, 13311.0],      [13312.0, 13313.0, 13314.0]],     [[13315.0, 13316.0, 13317.0],      [13318.0, 13319.0, 13320.0],      [13321.0, 13322.0, 13323.0],      [13324.0, 13325.0, 13326.0],      [13327.0, 13328.0, 13329.0],      [13330.0, 13331.0, 13332.0],      [13333.0, 13334.0, 13335.0]],     [[13336.0, 13337.0, 13338.0],      [13339.0, 13340.0, 13341.0],      [13342.0, 13343.0, 13344.0],      [13345.0, 13346.0, 13347.0],      [13348.0, 13349.0, 13350.0],      [13351.0, 13352.0, 13353.0],      [13354.0, 13355.0, 13356.0]]],    [[[13357.0, 13358.0, 13359.0],      [13360.0, 13361.0, 13362.0],      [13363.0, 13364.0, 13365.0],      [13366.0, 13367.0, 13368.0],      [13369.0, 13370.0, 13371.0],      [13372.0, 13373.0, 13374.0],      [13375.0, 13376.0, 13377.0]],     [[13378.0, 13379.0, 13380.0],      [13381.0, 13382.0, 13383.0],      [13384.0, 13385.0, 13386.0],      [13387.0, 13388.0, 13389.0],      [13390.0, 13391.0, 13392.0],      [13393.0, 13394.0, 13395.0],      [13396.0, 13397.0, 13398.0]],     [[13399.0, 13400.0, 13401.0],      [13402.0, 13403.0, 13404.0],      [13405.0, 13406.0, 13407.0],      [13408.0, 13409.0, 13410.0],      [13411.0, 13412.0, 13413.0],      [13414.0, 13415.0, 13416.0],      [13417.0, 13418.0, 13419.0]],     [[13420.0, 13421.0, 13422.0],      [13423.0, 13424.0, 13425.0],      [13426.0, 13427.0, 13428.0],      [13429.0, 13430.0, 13431.0],      [13432.0, 13433.0, 13434.0],      [13435.0, 13436.0, 13437.0],      [13438.0, 13439.0, 13440.0]],     [[13441.0, 13442.0, 13443.0],      [13444.0, 13445.0, 13446.0],      [13447.0, 13448.0, 13449.0],      [13450.0, 13451.0, 13452.0],      [13453.0, 13454.0, 13455.0],      [13456.0, 13457.0, 13458.0],      [13459.0, 13460.0, 13461.0]],     [[13462.0, 13463.0, 13464.0],      [13465.0, 13466.0, 13467.0],      [13468.0, 13469.0, 13470.0],      [13471.0, 13472.0, 13473.0],      [13474.0, 13475.0, 13476.0],      [13477.0, 13478.0, 13479.0],      [13480.0, 13481.0, 13482.0]]],    [[[13483.0, 13484.0, 13485.0],      [13486.0, 13487.0, 13488.0],      [13489.0, 13490.0, 13491.0],      [13492.0, 13493.0, 13494.0],      [13495.0, 13496.0, 13497.0],      [13498.0, 13499.0, 13500.0],      [13501.0, 13502.0, 13503.0]],     [[13504.0, 13505.0, 13506.0],      [13507.0, 13508.0, 13509.0],      [13510.0, 13511.0, 13512.0],      [13513.0, 13514.0, 13515.0],      [13516.0, 13517.0, 13518.0],      [13519.0, 13520.0, 13521.0],      [13522.0, 13523.0, 13524.0]],     [[13525.0, 13526.0, 13527.0],      [13528.0, 13529.0, 13530.0],      [13531.0, 13532.0, 13533.0],      [13534.0, 13535.0, 13536.0],      [13537.0, 13538.0, 13539.0],      [13540.0, 13541.0, 13542.0],      [13543.0, 13544.0, 13545.0]],     [[13546.0, 13547.0, 13548.0],      [13549.0, 13550.0, 13551.0],      [13552.0, 13553.0, 13554.0],      [13555.0, 13556.0, 13557.0],      [13558.0, 13559.0, 13560.0],      [13561.0, 13562.0, 13563.0],      [13564.0, 13565.0, 13566.0]],     [[13567.0, 13568.0, 13569.0],      [13570.0, 13571.0, 13572.0],      [13573.0, 13574.0, 13575.0],      [13576.0, 13577.0, 13578.0],      [13579.0, 13580.0, 13581.0],      [13582.0, 13583.0, 13584.0],      [13585.0, 13586.0, 13587.0]],     [[13588.0, 13589.0, 13590.0],      [13591.0, 13592.0, 13593.0],      [13594.0, 13595.0, 13596.0],      [13597.0, 13598.0, 13599.0],      [13600.0, 13601.0, 13602.0],      [13603.0, 13604.0, 13605.0],      [13606.0, 13607.0, 13608.0]]]]]] shape=[3, 6, 6, 6, 7, 3], strides=[4536, 756, 126, 21, 3, 1], layout=C (0x1)), I32([1, 3, 3] shape=[3], strides=[1], layout=C | F (0x3)), I32([[0, 1],  [3, 3],  [2, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 2455436335 2435272652 4081531820 2100981165 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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, 16.0]],     [[17.0, 18.0, 19.0, 20.0]],     [[21.0, 22.0, 23.0, 24.0]]],    [[[25.0, 26.0, 27.0, 28.0]],     [[29.0, 30.0, 31.0, 32.0]],     [[33.0, 34.0, 35.0, 36.0]]],    [[[37.0, 38.0, 39.0, 40.0]],     [[41.0, 42.0, 43.0, 44.0]],     [[45.0, 46.0, 47.0, 48.0]]]],   [[[[49.0, 50.0, 51.0, 52.0]],     [[53.0, 54.0, 55.0, 56.0]],     [[57.0, 58.0, 59.0, 60.0]]],    [[[61.0, 62.0, 63.0, 64.0]],     [[65.0, 66.0, 67.0, 68.0]],     [[69.0, 70.0, 71.0, 72.0]]],    [[[73.0, 74.0, 75.0, 76.0]],     [[77.0, 78.0, 79.0, 80.0]],     [[81.0, 82.0, 83.0, 84.0]]],    [[[85.0, 86.0, 87.0, 88.0]],     [[89.0, 90.0, 91.0, 92.0]],     [[93.0, 94.0, 95.0, 96.0]]]],   [[[[97.0, 98.0, 99.0, 100.0]],     [[101.0, 102.0, 103.0, 104.0]],     [[105.0, 106.0, 107.0, 108.0]]],    [[[109.0, 110.0, 111.0, 112.0]],     [[113.0, 114.0, 115.0, 116.0]],     [[117.0, 118.0, 119.0, 120.0]]],    [[[121.0, 122.0, 123.0, 124.0]],     [[125.0, 126.0, 127.0, 128.0]],     [[129.0, 130.0, 131.0, 132.0]]],    [[[133.0, 134.0, 135.0, 136.0]],     [[137.0, 138.0, 139.0, 140.0]],     [[141.0, 142.0, 143.0, 144.0]]]],   [[[[145.0, 146.0, 147.0, 148.0]],     [[149.0, 150.0, 151.0, 152.0]],     [[153.0, 154.0, 155.0, 156.0]]],    [[[157.0, 158.0, 159.0, 160.0]],     [[161.0, 162.0, 163.0, 164.0]],     [[165.0, 166.0, 167.0, 168.0]]],    [[[169.0, 170.0, 171.0, 172.0]],     [[173.0, 174.0, 175.0, 176.0]],     [[177.0, 178.0, 179.0, 180.0]]],    [[[181.0, 182.0, 183.0, 184.0]],     [[185.0, 186.0, 187.0, 188.0]],     [[189.0, 190.0, 191.0, 192.0]]]],   [[[[193.0, 194.0, 195.0, 196.0]],     [[197.0, 198.0, 199.0, 200.0]],     [[201.0, 202.0, 203.0, 204.0]]],    [[[205.0, 206.0, 207.0, 208.0]],     [[209.0, 210.0, 211.0, 212.0]],     [[213.0, 214.0, 215.0, 216.0]]],    [[[217.0, 218.0, 219.0, 220.0]],     [[221.0, 222.0, 223.0, 224.0]],     [[225.0, 226.0, 227.0, 228.0]]],    [[[229.0, 230.0, 231.0, 232.0]],     [[233.0, 234.0, 235.0, 236.0]],     [[237.0, 238.0, 239.0, 240.0]]]]],  [[[[[241.0, 242.0, 243.0, 244.0]],     [[245.0, 246.0, 247.0, 248.0]],     [[249.0, 250.0, 251.0, 252.0]]],    [[[253.0, 254.0, 255.0, 256.0]],     [[257.0, 258.0, 259.0, 260.0]],     [[261.0, 262.0, 263.0, 264.0]]],    [[[265.0, 266.0, 267.0, 268.0]],     [[269.0, 270.0, 271.0, 272.0]],     [[273.0, 274.0, 275.0, 276.0]]],    [[[277.0, 278.0, 279.0, 280.0]],     [[281.0, 282.0, 283.0, 284.0]],     [[285.0, 286.0, 287.0, 288.0]]]],   [[[[289.0, 290.0, 291.0, 292.0]],     [[293.0, 294.0, 295.0, 296.0]],     [[297.0, 298.0, 299.0, 300.0]]],    [[[301.0, 302.0, 303.0, 304.0]],     [[305.0, 306.0, 307.0, 308.0]],     [[309.0, 310.0, 311.0, 312.0]]],    [[[313.0, 314.0, 315.0, 316.0]],     [[317.0, 318.0, 319.0, 320.0]],     [[321.0, 322.0, 323.0, 324.0]]],    [[[325.0, 326.0, 327.0, 328.0]],     [[329.0, 330.0, 331.0, 332.0]],     [[333.0, 334.0, 335.0, 336.0]]]],   [[[[337.0, 338.0, 339.0, 340.0]],     [[341.0, 342.0, 343.0, 344.0]],     [[345.0, 346.0, 347.0, 348.0]]],    [[[349.0, 350.0, 351.0, 352.0]],     [[353.0, 354.0, 355.0, 356.0]],     [[357.0, 358.0, 359.0, 360.0]]],    [[[361.0, 362.0, 363.0, 364.0]],     [[365.0, 366.0, 367.0, 368.0]],     [[369.0, 370.0, 371.0, 372.0]]],    [[[373.0, 374.0, 375.0, 376.0]],     [[377.0, 378.0, 379.0, 380.0]],     [[381.0, 382.0, 383.0, 384.0]]]],   [[[[385.0, 386.0, 387.0, 388.0]],     [[389.0, 390.0, 391.0, 392.0]],     [[393.0, 394.0, 395.0, 396.0]]],    [[[397.0, 398.0, 399.0, 400.0]],     [[401.0, 402.0, 403.0, 404.0]],     [[405.0, 406.0, 407.0, 408.0]]],    [[[409.0, 410.0, 411.0, 412.0]],     [[413.0, 414.0, 415.0, 416.0]],     [[417.0, 418.0, 419.0, 420.0]]],    [[[421.0, 422.0, 423.0, 424.0]],     [[425.0, 426.0, 427.0, 428.0]],     [[429.0, 430.0, 431.0, 432.0]]]],   [[[[433.0, 434.0, 435.0, 436.0]],     [[437.0, 438.0, 439.0, 440.0]],     [[441.0, 442.0, 443.0, 444.0]]],    [[[445.0, 446.0, 447.0, 448.0]],     [[449.0, 450.0, 451.0, 452.0]],     [[453.0, 454.0, 455.0, 456.0]]],    [[[457.0, 458.0, 459.0, 460.0]],     [[461.0, 462.0, 463.0, 464.0]],     [[465.0, 466.0, 467.0, 468.0]]],    [[[469.0, 470.0, 471.0, 472.0]],     [[473.0, 474.0, 475.0, 476.0]],     [[477.0, 478.0, 479.0, 480.0]]]]]] shape=[2, 5, 4, 3, 1, 4], strides=[240, 48, 12, 4, 4, 1], layout=C (0x1)), I32([3, 3] shape=[2], strides=[1], layout=C | F (0x3)), I32([[2, 2],  [0, 2]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 2718423857 1217562331 4192127495 2874279647 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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, 16.0]],     [[17.0, 18.0]],     [[19.0, 20.0]],     [[21.0, 22.0]],     [[23.0, 24.0]],     [[25.0, 26.0]],     [[27.0, 28.0]]],    [[[29.0, 30.0]],     [[31.0, 32.0]],     [[33.0, 34.0]],     [[35.0, 36.0]],     [[37.0, 38.0]],     [[39.0, 40.0]],     [[41.0, 42.0]]],    [[[43.0, 44.0]],     [[45.0, 46.0]],     [[47.0, 48.0]],     [[49.0, 50.0]],     [[51.0, 52.0]],     [[53.0, 54.0]],     [[55.0, 56.0]]]],   [[[[57.0, 58.0]],     [[59.0, 60.0]],     [[61.0, 62.0]],     [[63.0, 64.0]],     [[65.0, 66.0]],     [[67.0, 68.0]],     [[69.0, 70.0]]],    [[[71.0, 72.0]],     [[73.0, 74.0]],     [[75.0, 76.0]],     [[77.0, 78.0]],     [[79.0, 80.0]],     [[81.0, 82.0]],     [[83.0, 84.0]]],    [[[85.0, 86.0]],     [[87.0, 88.0]],     [[89.0, 90.0]],     [[91.0, 92.0]],     [[93.0, 94.0]],     [[95.0, 96.0]],     [[97.0, 98.0]]],    [[[99.0, 100.0]],     [[101.0, 102.0]],     [[103.0, 104.0]],     [[105.0, 106.0]],     [[107.0, 108.0]],     [[109.0, 110.0]],     [[111.0, 112.0]]]],   [[[[113.0, 114.0]],     [[115.0, 116.0]],     [[117.0, 118.0]],     [[119.0, 120.0]],     [[121.0, 122.0]],     [[123.0, 124.0]],     [[125.0, 126.0]]],    [[[127.0, 128.0]],     [[129.0, 130.0]],     [[131.0, 132.0]],     [[133.0, 134.0]],     [[135.0, 136.0]],     [[137.0, 138.0]],     [[139.0, 140.0]]],    [[[141.0, 142.0]],     [[143.0, 144.0]],     [[145.0, 146.0]],     [[147.0, 148.0]],     [[149.0, 150.0]],     [[151.0, 152.0]],     [[153.0, 154.0]]],    [[[155.0, 156.0]],     [[157.0, 158.0]],     [[159.0, 160.0]],     [[161.0, 162.0]],     [[163.0, 164.0]],     [[165.0, 166.0]],     [[167.0, 168.0]]]]],  [[[[[169.0, 170.0]],     [[171.0, 172.0]],     [[173.0, 174.0]],     [[175.0, 176.0]],     [[177.0, 178.0]],     [[179.0, 180.0]],     [[181.0, 182.0]]],    [[[183.0, 184.0]],     [[185.0, 186.0]],     [[187.0, 188.0]],     [[189.0, 190.0]],     [[191.0, 192.0]],     [[193.0, 194.0]],     [[195.0, 196.0]]],    [[[197.0, 198.0]],     [[199.0, 200.0]],     [[201.0, 202.0]],     [[203.0, 204.0]],     [[205.0, 206.0]],     [[207.0, 208.0]],     [[209.0, 210.0]]],    [[[211.0, 212.0]],     [[213.0, 214.0]],     [[215.0, 216.0]],     [[217.0, 218.0]],     [[219.0, 220.0]],     [[221.0, 222.0]],     [[223.0, 224.0]]]],   [[[[225.0, 226.0]],     [[227.0, 228.0]],     [[229.0, 230.0]],     [[231.0, 232.0]],     [[233.0, 234.0]],     [[235.0, 236.0]],     [[237.0, 238.0]]],    [[[239.0, 240.0]],     [[241.0, 242.0]],     [[243.0, 244.0]],     [[245.0, 246.0]],     [[247.0, 248.0]],     [[249.0, 250.0]],     [[251.0, 252.0]]],    [[[253.0, 254.0]],     [[255.0, 256.0]],     [[257.0, 258.0]],     [[259.0, 260.0]],     [[261.0, 262.0]],     [[263.0, 264.0]],     [[265.0, 266.0]]],    [[[267.0, 268.0]],     [[269.0, 270.0]],     [[271.0, 272.0]],     [[273.0, 274.0]],     [[275.0, 276.0]],     [[277.0, 278.0]],     [[279.0, 280.0]]]],   [[[[281.0, 282.0]],     [[283.0, 284.0]],     [[285.0, 286.0]],     [[287.0, 288.0]],     [[289.0, 290.0]],     [[291.0, 292.0]],     [[293.0, 294.0]]],    [[[295.0, 296.0]],     [[297.0, 298.0]],     [[299.0, 300.0]],     [[301.0, 302.0]],     [[303.0, 304.0]],     [[305.0, 306.0]],     [[307.0, 308.0]]],    [[[309.0, 310.0]],     [[311.0, 312.0]],     [[313.0, 314.0]],     [[315.0, 316.0]],     [[317.0, 318.0]],     [[319.0, 320.0]],     [[321.0, 322.0]]],    [[[323.0, 324.0]],     [[325.0, 326.0]],     [[327.0, 328.0]],     [[329.0, 330.0]],     [[331.0, 332.0]],     [[333.0, 334.0]],     [[335.0, 336.0]]]]]] shape=[2, 3, 4, 7, 1, 2], strides=[168, 56, 14, 2, 2, 1], layout=C (0x1)), I32([1, 3, 3] shape=[3], strides=[1], layout=C | F (0x3)), I32([[1, 1],  [1, 1],  [1, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 3949851176 3199064296 137693726 2236308343 # shrinks to (ref b, ref bs, ref c) = (F32([[[[[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]],    [[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]],    [[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]]],   [[[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]],    [[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]],    [[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]]],   [[[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]],    [[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]],    [[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]]],   [[[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],     [16.0, 17.0, 18.0],     [19.0, 20.0, 21.0],     [22.0, 23.0, 24.0]],    [[25.0, 26.0, 27.0],     [28.0, 29.0, 30.0],     [31.0, 32.0, 33.0],     [34.0, 35.0, 36.0]],    [[37.0, 38.0, 39.0],     [40.0, 41.0, 42.0],     [43.0, 44.0, 45.0],     [46.0, 47.0, 48.0]],    [[49.0, 50.0, 51.0],     [52.0, 53.0, 54.0],     [55.0, 56.0, 57.0],     [58.0, 59.0, 60.0]],    [[61.0, 62.0, 63.0],     [64.0, 65.0, 66.0],     [67.0, 68.0, 69.0],     [70.0, 71.0, 72.0]]],   [[[73.0, 74.0, 75.0],     [76.0, 77.0, 78.0],     [79.0, 80.0, 81.0],     [82.0, 83.0, 84.0]],    [[85.0, 86.0, 87.0],     [88.0, 89.0, 90.0],     [91.0, 92.0, 93.0],     [94.0, 95.0, 96.0]],    [[97.0, 98.0, 99.0],     [100.0, 101.0, 102.0],     [103.0, 104.0, 105.0],     [106.0, 107.0, 108.0]],    [[109.0, 110.0, 111.0],     [112.0, 113.0, 114.0],     [115.0, 116.0, 117.0],     [118.0, 119.0, 120.0]],    [[121.0, 122.0, 123.0],     [124.0, 125.0, 126.0],     [127.0, 128.0, 129.0],     [130.0, 131.0, 132.0]],    [[133.0, 134.0, 135.0],     [136.0, 137.0, 138.0],     [139.0, 140.0, 141.0],     [142.0, 143.0, 144.0]]],   [[[145.0, 146.0, 147.0],     [148.0, 149.0, 150.0],     [151.0, 152.0, 153.0],     [154.0, 155.0, 156.0]],    [[157.0, 158.0, 159.0],     [160.0, 161.0, 162.0],     [163.0, 164.0, 165.0],     [166.0, 167.0, 168.0]],    [[169.0, 170.0, 171.0],     [172.0, 173.0, 174.0],     [175.0, 176.0, 177.0],     [178.0, 179.0, 180.0]],    [[181.0, 182.0, 183.0],     [184.0, 185.0, 186.0],     [187.0, 188.0, 189.0],     [190.0, 191.0, 192.0]],    [[193.0, 194.0, 195.0],     [196.0, 197.0, 198.0],     [199.0, 200.0, 201.0],     [202.0, 203.0, 204.0]],    [[205.0, 206.0, 207.0],     [208.0, 209.0, 210.0],     [211.0, 212.0, 213.0],     [214.0, 215.0, 216.0]]],   [[[217.0, 218.0, 219.0],     [220.0, 221.0, 222.0],     [223.0, 224.0, 225.0],     [226.0, 227.0, 228.0]],    [[229.0, 230.0, 231.0],     [232.0, 233.0, 234.0],     [235.0, 236.0, 237.0],     [238.0, 239.0, 240.0]],    [[241.0, 242.0, 243.0],     [244.0, 245.0, 246.0],     [247.0, 248.0, 249.0],     [250.0, 251.0, 252.0]],    [[253.0, 254.0, 255.0],     [256.0, 257.0, 258.0],     [259.0, 260.0, 261.0],     [262.0, 263.0, 264.0]],    [[265.0, 266.0, 267.0],     [268.0, 269.0, 270.0],     [271.0, 272.0, 273.0],     [274.0, 275.0, 276.0]],    [[277.0, 278.0, 279.0],     [280.0, 281.0, 282.0],     [283.0, 284.0, 285.0],     [286.0, 287.0, 288.0]]],   [[[289.0, 290.0, 291.0],     [292.0, 293.0, 294.0],     [295.0, 296.0, 297.0],     [298.0, 299.0, 300.0]],    [[301.0, 302.0, 303.0],     [304.0, 305.0, 306.0],     [307.0, 308.0, 309.0],     [310.0, 311.0, 312.0]],    [[313.0, 314.0, 315.0],     [316.0, 317.0, 318.0],     [319.0, 320.0, 321.0],     [322.0, 323.0, 324.0]],    [[325.0, 326.0, 327.0],     [328.0, 329.0, 330.0],     [331.0, 332.0, 333.0],     [334.0, 335.0, 336.0]],    [[337.0, 338.0, 339.0],     [340.0, 341.0, 342.0],     [343.0, 344.0, 345.0],     [346.0, 347.0, 348.0]],    [[349.0, 350.0, 351.0],     [352.0, 353.0, 354.0],     [355.0, 356.0, 357.0],     [358.0, 359.0, 360.0]]],   [[[361.0, 362.0, 363.0],     [364.0, 365.0, 366.0],     [367.0, 368.0, 369.0],     [370.0, 371.0, 372.0]],    [[373.0, 374.0, 375.0],     [376.0, 377.0, 378.0],     [379.0, 380.0, 381.0],     [382.0, 383.0, 384.0]],    [[385.0, 386.0, 387.0],     [388.0, 389.0, 390.0],     [391.0, 392.0, 393.0],     [394.0, 395.0, 396.0]],    [[397.0, 398.0, 399.0],     [400.0, 401.0, 402.0],     [403.0, 404.0, 405.0],     [406.0, 407.0, 408.0]],    [[409.0, 410.0, 411.0],     [412.0, 413.0, 414.0],     [415.0, 416.0, 417.0],     [418.0, 419.0, 420.0]],    [[421.0, 422.0, 423.0],     [424.0, 425.0, 426.0],     [427.0, 428.0, 429.0],     [430.0, 431.0, 432.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.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.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.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.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.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.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.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.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.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.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.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.0]]],   [[[433.0, 434.0, 435.0],     [436.0, 437.0, 438.0],     [439.0, 440.0, 441.0],     [442.0, 443.0, 444.0]],    [[445.0, 446.0, 447.0],     [448.0, 449.0, 450.0],     [451.0, 452.0, 453.0],     [454.0, 455.0, 456.0]],    [[457.0, 458.0, 459.0],     [460.0, 461.0, 462.0],     [463.0, 464.0, 465.0],     [466.0, 467.0, 468.0]],    [[469.0, 470.0, 471.0],     [472.0, 473.0, 474.0],     [475.0, 476.0, 477.0],     [478.0, 479.0, 480.0]],    [[481.0, 482.0, 483.0],     [484.0, 485.0, 486.0],     [487.0, 488.0, 489.0],     [490.0, 491.0, 492.0]],    [[493.0, 494.0, 495.0],     [496.0, 497.0, 498.0],     [499.0, 500.0, 501.0],     [502.0, 503.0, 504.0]]],   [[[505.0, 506.0, 507.0],     [508.0, 509.0, 510.0],     [511.0, 512.0, 513.0],     [514.0, 515.0, 516.0]],    [[517.0, 518.0, 519.0],     [520.0, 521.0, 522.0],     [523.0, 524.0, 525.0],     [526.0, 527.0, 528.0]],    [[529.0, 530.0, 531.0],     [532.0, 533.0, 534.0],     [535.0, 536.0, 537.0],     [538.0, 539.0, 540.0]],    [[541.0, 542.0, 543.0],     [544.0, 545.0, 546.0],     [547.0, 548.0, 549.0],     [550.0, 551.0, 552.0]],    [[553.0, 554.0, 555.0],     [556.0, 557.0, 558.0],     [559.0, 560.0, 561.0],     [562.0, 563.0, 564.0]],    [[565.0, 566.0, 567.0],     [568.0, 569.0, 570.0],     [571.0, 572.0, 573.0],     [574.0, 575.0, 576.0]]],   [[[577.0, 578.0, 579.0],     [580.0, 581.0, 582.0],     [583.0, 584.0, 585.0],     [586.0, 587.0, 588.0]],    [[589.0, 590.0, 591.0],     [592.0, 593.0, 594.0],     [595.0, 596.0, 597.0],     [598.0, 599.0, 600.0]],    [[601.0, 602.0, 603.0],     [604.0, 605.0, 606.0],     [607.0, 608.0, 609.0],     [610.0, 611.0, 612.0]],    [[613.0, 614.0, 615.0],     [616.0, 617.0, 618.0],     [619.0, 620.0, 621.0],     [622.0, 623.0, 624.0]],    [[625.0, 626.0, 627.0],     [628.0, 629.0, 630.0],     [631.0, 632.0, 633.0],     [634.0, 635.0, 636.0]],    [[637.0, 638.0, 639.0],     [640.0, 641.0, 642.0],     [643.0, 644.0, 645.0],     [646.0, 647.0, 648.0]]],   [[[649.0, 650.0, 651.0],     [652.0, 653.0, 654.0],     [655.0, 656.0, 657.0],     [658.0, 659.0, 660.0]],    [[661.0, 662.0, 663.0],     [664.0, 665.0, 666.0],     [667.0, 668.0, 669.0],     [670.0, 671.0, 672.0]],    [[673.0, 674.0, 675.0],     [676.0, 677.0, 678.0],     [679.0, 680.0, 681.0],     [682.0, 683.0, 684.0]],    [[685.0, 686.0, 687.0],     [688.0, 689.0, 690.0],     [691.0, 692.0, 693.0],     [694.0, 695.0, 696.0]],    [[697.0, 698.0, 699.0],     [700.0, 701.0, 702.0],     [703.0, 704.0, 705.0],     [706.0, 707.0, 708.0]],    [[709.0, 710.0, 711.0],     [712.0, 713.0, 714.0],     [715.0, 716.0, 717.0],     [718.0, 719.0, 720.0]]],   [[[721.0, 722.0, 723.0],     [724.0, 725.0, 726.0],     [727.0, 728.0, 729.0],     [730.0, 731.0, 732.0]],    [[733.0, 734.0, 735.0],     [736.0, 737.0, 738.0],     [739.0, 740.0, 741.0],     [742.0, 743.0, 744.0]],    [[745.0, 746.0, 747.0],     [748.0, 749.0, 750.0],     [751.0, 752.0, 753.0],     [754.0, 755.0, 756.0]],    [[757.0, 758.0, 759.0],     [760.0, 761.0, 762.0],     [763.0, 764.0, 765.0],     [766.0, 767.0, 768.0]],    [[769.0, 770.0, 771.0],     [772.0, 773.0, 774.0],     [775.0, 776.0, 777.0],     [778.0, 779.0, 780.0]],    [[781.0, 782.0, 783.0],     [784.0, 785.0, 786.0],     [787.0, 788.0, 789.0],     [790.0, 791.0, 792.0]]],   [[[793.0, 794.0, 795.0],     [796.0, 797.0, 798.0],     [799.0, 800.0, 801.0],     [802.0, 803.0, 804.0]],    [[805.0, 806.0, 807.0],     [808.0, 809.0, 810.0],     [811.0, 812.0, 813.0],     [814.0, 815.0, 816.0]],    [[817.0, 818.0, 819.0],     [820.0, 821.0, 822.0],     [823.0, 824.0, 825.0],     [826.0, 827.0, 828.0]],    [[829.0, 830.0, 831.0],     [832.0, 833.0, 834.0],     [835.0, 836.0, 837.0],     [838.0, 839.0, 840.0]],    [[841.0, 842.0, 843.0],     [844.0, 845.0, 846.0],     [847.0, 848.0, 849.0],     [850.0, 851.0, 852.0]],    [[853.0, 854.0, 855.0],     [856.0, 857.0, 858.0],     [859.0, 860.0, 861.0],     [862.0, 863.0, 864.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.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.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.0]]]]] shape=[2, 10, 6, 4, 3], strides=[720, 72, 12, 3, 1], layout=C (0x1)), I32([1] shape=[1], strides=[1], layout=C | F (0x3)), I32([[3, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 1528214438 2204478873 970992606 2116816964 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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, 16.0],       [17.0, 18.0]],      [[19.0, 20.0],       [21.0, 22.0],       [23.0, 24.0]]],     [[[25.0, 26.0],       [27.0, 28.0],       [29.0, 30.0]],      [[31.0, 32.0],       [33.0, 34.0],       [35.0, 36.0]]],     [[[37.0, 38.0],       [39.0, 40.0],       [41.0, 42.0]],      [[43.0, 44.0],       [45.0, 46.0],       [47.0, 48.0]]],     [[[49.0, 50.0],       [51.0, 52.0],       [53.0, 54.0]],      [[55.0, 56.0],       [57.0, 58.0],       [59.0, 60.0]]],     [[[61.0, 62.0],       [63.0, 64.0],       [65.0, 66.0]],      [[67.0, 68.0],       [69.0, 70.0],       [71.0, 72.0]]]],    [[[[73.0, 74.0],       [75.0, 76.0],       [77.0, 78.0]],      [[79.0, 80.0],       [81.0, 82.0],       [83.0, 84.0]]],     [[[85.0, 86.0],       [87.0, 88.0],       [89.0, 90.0]],      [[91.0, 92.0],       [93.0, 94.0],       [95.0, 96.0]]],     [[[97.0, 98.0],       [99.0, 100.0],       [101.0, 102.0]],      [[103.0, 104.0],       [105.0, 106.0],       [107.0, 108.0]]],     [[[109.0, 110.0],       [111.0, 112.0],       [113.0, 114.0]],      [[115.0, 116.0],       [117.0, 118.0],       [119.0, 120.0]]],     [[[121.0, 122.0],       [123.0, 124.0],       [125.0, 126.0]],      [[127.0, 128.0],       [129.0, 130.0],       [131.0, 132.0]]],     [[[133.0, 134.0],       [135.0, 136.0],       [137.0, 138.0]],      [[139.0, 140.0],       [141.0, 142.0],       [143.0, 144.0]]]],    [[[[145.0, 146.0],       [147.0, 148.0],       [149.0, 150.0]],      [[151.0, 152.0],       [153.0, 154.0],       [155.0, 156.0]]],     [[[157.0, 158.0],       [159.0, 160.0],       [161.0, 162.0]],      [[163.0, 164.0],       [165.0, 166.0],       [167.0, 168.0]]],     [[[169.0, 170.0],       [171.0, 172.0],       [173.0, 174.0]],      [[175.0, 176.0],       [177.0, 178.0],       [179.0, 180.0]]],     [[[181.0, 182.0],       [183.0, 184.0],       [185.0, 186.0]],      [[187.0, 188.0],       [189.0, 190.0],       [191.0, 192.0]]],     [[[193.0, 194.0],       [195.0, 196.0],       [197.0, 198.0]],      [[199.0, 200.0],       [201.0, 202.0],       [203.0, 204.0]]],     [[[205.0, 206.0],       [207.0, 208.0],       [209.0, 210.0]],      [[211.0, 212.0],       [213.0, 214.0],       [215.0, 216.0]]]]],   [[[[[217.0, 218.0],       [219.0, 220.0],       [221.0, 222.0]],      [[223.0, 224.0],       [225.0, 226.0],       [227.0, 228.0]]],     [[[229.0, 230.0],       [231.0, 232.0],       [233.0, 234.0]],      [[235.0, 236.0],       [237.0, 238.0],       [239.0, 240.0]]],     [[[241.0, 242.0],       [243.0, 244.0],       [245.0, 246.0]],      [[247.0, 248.0],       [249.0, 250.0],       [251.0, 252.0]]],     [[[253.0, 254.0],       [255.0, 256.0],       [257.0, 258.0]],      [[259.0, 260.0],       [261.0, 262.0],       [263.0, 264.0]]],     [[[265.0, 266.0],       [267.0, 268.0],       [269.0, 270.0]],      [[271.0, 272.0],       [273.0, 274.0],       [275.0, 276.0]]],     [[[277.0, 278.0],       [279.0, 280.0],       [281.0, 282.0]],      [[283.0, 284.0],       [285.0, 286.0],       [287.0, 288.0]]]],    [[[[289.0, 290.0],       [291.0, 292.0],       [293.0, 294.0]],      [[295.0, 296.0],       [297.0, 298.0],       [299.0, 300.0]]],     [[[301.0, 302.0],       [303.0, 304.0],       [305.0, 306.0]],      [[307.0, 308.0],       [309.0, 310.0],       [311.0, 312.0]]],     [[[313.0, 314.0],       [315.0, 316.0],       [317.0, 318.0]],      [[319.0, 320.0],       [321.0, 322.0],       [323.0, 324.0]]],     [[[325.0, 326.0],       [327.0, 328.0],       [329.0, 330.0]],      [[331.0, 332.0],       [333.0, 334.0],       [335.0, 336.0]]],     [[[337.0, 338.0],       [339.0, 340.0],       [341.0, 342.0]],      [[343.0, 344.0],       [345.0, 346.0],       [347.0, 348.0]]],     [[[349.0, 350.0],       [351.0, 352.0],       [353.0, 354.0]],      [[355.0, 356.0],       [357.0, 358.0],       [359.0, 360.0]]]],    [[[[361.0, 362.0],       [363.0, 364.0],       [365.0, 366.0]],      [[367.0, 368.0],       [369.0, 370.0],       [371.0, 372.0]]],     [[[373.0, 374.0],       [375.0, 376.0],       [377.0, 378.0]],      [[379.0, 380.0],       [381.0, 382.0],       [383.0, 384.0]]],     [[[385.0, 386.0],       [387.0, 388.0],       [389.0, 390.0]],      [[391.0, 392.0],       [393.0, 394.0],       [395.0, 396.0]]],     [[[397.0, 398.0],       [399.0, 400.0],       [401.0, 402.0]],      [[403.0, 404.0],       [405.0, 406.0],       [407.0, 408.0]]],     [[[409.0, 410.0],       [411.0, 412.0],       [413.0, 414.0]],      [[415.0, 416.0],       [417.0, 418.0],       [419.0, 420.0]]],     [[[421.0, 422.0],       [423.0, 424.0],       [425.0, 426.0]],      [[427.0, 428.0],       [429.0, 430.0],       [431.0, 432.0]]]]]],  [[[[[[433.0, 434.0],       [435.0, 436.0],       [437.0, 438.0]],      [[439.0, 440.0],       [441.0, 442.0],       [443.0, 444.0]]],     [[[445.0, 446.0],       [447.0, 448.0],       [449.0, 450.0]],      [[451.0, 452.0],       [453.0, 454.0],       [455.0, 456.0]]],     [[[457.0, 458.0],       [459.0, 460.0],       [461.0, 462.0]],      [[463.0, 464.0],       [465.0, 466.0],       [467.0, 468.0]]],     [[[469.0, 470.0],       [471.0, 472.0],       [473.0, 474.0]],      [[475.0, 476.0],       [477.0, 478.0],       [479.0, 480.0]]],     [[[481.0, 482.0],       [483.0, 484.0],       [485.0, 486.0]],      [[487.0, 488.0],       [489.0, 490.0],       [491.0, 492.0]]],     [[[493.0, 494.0],       [495.0, 496.0],       [497.0, 498.0]],      [[499.0, 500.0],       [501.0, 502.0],       [503.0, 504.0]]]],    [[[[505.0, 506.0],       [507.0, 508.0],       [509.0, 510.0]],      [[511.0, 512.0],       [513.0, 514.0],       [515.0, 516.0]]],     [[[517.0, 518.0],       [519.0, 520.0],       [521.0, 522.0]],      [[523.0, 524.0],       [525.0, 526.0],       [527.0, 528.0]]],     [[[529.0, 530.0],       [531.0, 532.0],       [533.0, 534.0]],      [[535.0, 536.0],       [537.0, 538.0],       [539.0, 540.0]]],     [[[541.0, 542.0],       [543.0, 544.0],       [545.0, 546.0]],      [[547.0, 548.0],       [549.0, 550.0],       [551.0, 552.0]]],     [[[553.0, 554.0],       [555.0, 556.0],       [557.0, 558.0]],      [[559.0, 560.0],       [561.0, 562.0],       [563.0, 564.0]]],     [[[565.0, 566.0],       [567.0, 568.0],       [569.0, 570.0]],      [[571.0, 572.0],       [573.0, 574.0],       [575.0, 576.0]]]],    [[[[577.0, 578.0],       [579.0, 580.0],       [581.0, 582.0]],      [[583.0, 584.0],       [585.0, 586.0],       [587.0, 588.0]]],     [[[589.0, 590.0],       [591.0, 592.0],       [593.0, 594.0]],      [[595.0, 596.0],       [597.0, 598.0],       [599.0, 600.0]]],     [[[601.0, 602.0],       [603.0, 604.0],       [605.0, 606.0]],      [[607.0, 608.0],       [609.0, 610.0],       [611.0, 612.0]]],     [[[613.0, 614.0],       [615.0, 616.0],       [617.0, 618.0]],      [[619.0, 620.0],       [621.0, 622.0],       [623.0, 624.0]]],     [[[625.0, 626.0],       [627.0, 628.0],       [629.0, 630.0]],      [[631.0, 632.0],       [633.0, 634.0],       [635.0, 636.0]]],     [[[637.0, 638.0],       [639.0, 640.0],       [641.0, 642.0]],      [[643.0, 644.0],       [645.0, 646.0],       [647.0, 648.0]]]]],   [[[[[649.0, 650.0],       [651.0, 652.0],       [653.0, 654.0]],      [[655.0, 656.0],       [657.0, 658.0],       [659.0, 660.0]]],     [[[661.0, 662.0],       [663.0, 664.0],       [665.0, 666.0]],      [[667.0, 668.0],       [669.0, 670.0],       [671.0, 672.0]]],     [[[673.0, 674.0],       [675.0, 676.0],       [677.0, 678.0]],      [[679.0, 680.0],       [681.0, 682.0],       [683.0, 684.0]]],     [[[685.0, 686.0],       [687.0, 688.0],       [689.0, 690.0]],      [[691.0, 692.0],       [693.0, 694.0],       [695.0, 696.0]]],     [[[697.0, 698.0],       [699.0, 700.0],       [701.0, 702.0]],      [[703.0, 704.0],       [705.0, 706.0],       [707.0, 708.0]]],     [[[709.0, 710.0],       [711.0, 712.0],       [713.0, 714.0]],      [[715.0, 716.0],       [717.0, 718.0],       [719.0, 720.0]]]],    [[[[721.0, 722.0],       [723.0, 724.0],       [725.0, 726.0]],      [[727.0, 728.0],       [729.0, 730.0],       [731.0, 732.0]]],     [[[733.0, 734.0],       [735.0, 736.0],       [737.0, 738.0]],      [[739.0, 740.0],       [741.0, 742.0],       [743.0, 744.0]]],     [[[745.0, 746.0],       [747.0, 748.0],       [749.0, 750.0]],      [[751.0, 752.0],       [753.0, 754.0],       [755.0, 756.0]]],     [[[757.0, 758.0],       [759.0, 760.0],       [761.0, 762.0]],      [[763.0, 764.0],       [765.0, 766.0],       [767.0, 768.0]]],     [[[769.0, 770.0],       [771.0, 772.0],       [773.0, 774.0]],      [[775.0, 776.0],       [777.0, 778.0],       [779.0, 780.0]]],     [[[781.0, 782.0],       [783.0, 784.0],       [785.0, 786.0]],      [[787.0, 788.0],       [789.0, 790.0],       [791.0, 792.0]]]],    [[[[793.0, 794.0],       [795.0, 796.0],       [797.0, 798.0]],      [[799.0, 800.0],       [801.0, 802.0],       [803.0, 804.0]]],     [[[805.0, 806.0],       [807.0, 808.0],       [809.0, 810.0]],      [[811.0, 812.0],       [813.0, 814.0],       [815.0, 816.0]]],     [[[817.0, 818.0],       [819.0, 820.0],       [821.0, 822.0]],      [[823.0, 824.0],       [825.0, 826.0],       [827.0, 828.0]]],     [[[829.0, 830.0],       [831.0, 832.0],       [833.0, 834.0]],      [[835.0, 836.0],       [837.0, 838.0],       [839.0, 840.0]]],     [[[841.0, 842.0],       [843.0, 844.0],       [845.0, 846.0]],      [[847.0, 848.0],       [849.0, 850.0],       [851.0, 852.0]]],     [[[853.0, 854.0],       [855.0, 856.0],       [857.0, 858.0]],      [[859.0, 860.0],       [861.0, 862.0],       [863.0, 864.0]]]]]],  [[[[[[865.0, 866.0],       [867.0, 868.0],       [869.0, 870.0]],      [[871.0, 872.0],       [873.0, 874.0],       [875.0, 876.0]]],     [[[877.0, 878.0],       [879.0, 880.0],       [881.0, 882.0]],      [[883.0, 884.0],       [885.0, 886.0],       [887.0, 888.0]]],     [[[889.0, 890.0],       [891.0, 892.0],       [893.0, 894.0]],      [[895.0, 896.0],       [897.0, 898.0],       [899.0, 900.0]]],     [[[901.0, 902.0],       [903.0, 904.0],       [905.0, 906.0]],      [[907.0, 908.0],       [909.0, 910.0],       [911.0, 912.0]]],     [[[913.0, 914.0],       [915.0, 916.0],       [917.0, 918.0]],      [[919.0, 920.0],       [921.0, 922.0],       [923.0, 924.0]]],     [[[925.0, 926.0],       [927.0, 928.0],       [929.0, 930.0]],      [[931.0, 932.0],       [933.0, 934.0],       [935.0, 936.0]]]],    [[[[937.0, 938.0],       [939.0, 940.0],       [941.0, 942.0]],      [[943.0, 944.0],       [945.0, 946.0],       [947.0, 948.0]]],     [[[949.0, 950.0],       [951.0, 952.0],       [953.0, 954.0]],      [[955.0, 956.0],       [957.0, 958.0],       [959.0, 960.0]]],     [[[961.0, 962.0],       [963.0, 964.0],       [965.0, 966.0]],      [[967.0, 968.0],       [969.0, 970.0],       [971.0, 972.0]]],     [[[973.0, 974.0],       [975.0, 976.0],       [977.0, 978.0]],      [[979.0, 980.0],       [981.0, 982.0],       [983.0, 984.0]]],     [[[985.0, 986.0],       [987.0, 988.0],       [989.0, 990.0]],      [[991.0, 992.0],       [993.0, 994.0],       [995.0, 996.0]]],     [[[997.0, 998.0],       [999.0, 1000.0],       [1001.0, 1002.0]],      [[1003.0, 1004.0],       [1005.0, 1006.0],       [1007.0, 1008.0]]]],    [[[[1009.0, 1010.0],       [1011.0, 1012.0],       [1013.0, 1014.0]],      [[1015.0, 1016.0],       [1017.0, 1018.0],       [1019.0, 1020.0]]],     [[[1021.0, 1022.0],       [1023.0, 1024.0],       [1025.0, 1026.0]],      [[1027.0, 1028.0],       [1029.0, 1030.0],       [1031.0, 1032.0]]],     [[[1033.0, 1034.0],       [1035.0, 1036.0],       [1037.0, 1038.0]],      [[1039.0, 1040.0],       [1041.0, 1042.0],       [1043.0, 1044.0]]],     [[[1045.0, 1046.0],       [1047.0, 1048.0],       [1049.0, 1050.0]],      [[1051.0, 1052.0],       [1053.0, 1054.0],       [1055.0, 1056.0]]],     [[[1057.0, 1058.0],       [1059.0, 1060.0],       [1061.0, 1062.0]],      [[1063.0, 1064.0],       [1065.0, 1066.0],       [1067.0, 1068.0]]],     [[[1069.0, 1070.0],       [1071.0, 1072.0],       [1073.0, 1074.0]],      [[1075.0, 1076.0],       [1077.0, 1078.0],       [1079.0, 1080.0]]]]],   [[[[[1081.0, 1082.0],       [1083.0, 1084.0],       [1085.0, 1086.0]],      [[1087.0, 1088.0],       [1089.0, 1090.0],       [1091.0, 1092.0]]],     [[[1093.0, 1094.0],       [1095.0, 1096.0],       [1097.0, 1098.0]],      [[1099.0, 1100.0],       [1101.0, 1102.0],       [1103.0, 1104.0]]],     [[[1105.0, 1106.0],       [1107.0, 1108.0],       [1109.0, 1110.0]],      [[1111.0, 1112.0],       [1113.0, 1114.0],       [1115.0, 1116.0]]],     [[[1117.0, 1118.0],       [1119.0, 1120.0],       [1121.0, 1122.0]],      [[1123.0, 1124.0],       [1125.0, 1126.0],       [1127.0, 1128.0]]],     [[[1129.0, 1130.0],       [1131.0, 1132.0],       [1133.0, 1134.0]],      [[1135.0, 1136.0],       [1137.0, 1138.0],       [1139.0, 1140.0]]],     [[[1141.0, 1142.0],       [1143.0, 1144.0],       [1145.0, 1146.0]],      [[1147.0, 1148.0],       [1149.0, 1150.0],       [1151.0, 1152.0]]]],    [[[[1153.0, 1154.0],       [1155.0, 1156.0],       [1157.0, 1158.0]],      [[1159.0, 1160.0],       [1161.0, 1162.0],       [1163.0, 1164.0]]],     [[[1165.0, 1166.0],       [1167.0, 1168.0],       [1169.0, 1170.0]],      [[1171.0, 1172.0],       [1173.0, 1174.0],       [1175.0, 1176.0]]],     [[[1177.0, 1178.0],       [1179.0, 1180.0],       [1181.0, 1182.0]],      [[1183.0, 1184.0],       [1185.0, 1186.0],       [1187.0, 1188.0]]],     [[[1189.0, 1190.0],       [1191.0, 1192.0],       [1193.0, 1194.0]],      [[1195.0, 1196.0],       [1197.0, 1198.0],       [1199.0, 1200.0]]],     [[[1201.0, 1202.0],       [1203.0, 1204.0],       [1205.0, 1206.0]],      [[1207.0, 1208.0],       [1209.0, 1210.0],       [1211.0, 1212.0]]],     [[[1213.0, 1214.0],       [1215.0, 1216.0],       [1217.0, 1218.0]],      [[1219.0, 1220.0],       [1221.0, 1222.0],       [1223.0, 1224.0]]]],    [[[[1225.0, 1226.0],       [1227.0, 1228.0],       [1229.0, 1230.0]],      [[1231.0, 1232.0],       [1233.0, 1234.0],       [1235.0, 1236.0]]],     [[[1237.0, 1238.0],       [1239.0, 1240.0],       [1241.0, 1242.0]],      [[1243.0, 1244.0],       [1245.0, 1246.0],       [1247.0, 1248.0]]],     [[[1249.0, 1250.0],       [1251.0, 1252.0],       [1253.0, 1254.0]],      [[1255.0, 1256.0],       [1257.0, 1258.0],       [1259.0, 1260.0]]],     [[[1261.0, 1262.0],       [1263.0, 1264.0],       [1265.0, 1266.0]],      [[1267.0, 1268.0],       [1269.0, 1270.0],       [1271.0, 1272.0]]],     [[[1273.0, 1274.0],       [1275.0, 1276.0],       [1277.0, 1278.0]],      [[1279.0, 1280.0],       [1281.0, 1282.0],       [1283.0, 1284.0]]],     [[[1285.0, 1286.0],       [1287.0, 1288.0],       [1289.0, 1290.0]],      [[1291.0, 1292.0],       [1293.0, 1294.0],       [1295.0, 1296.0]]]]]]] shape=[3, 2, 3, 6, 2, 3, 2], strides=[432, 216, 72, 12, 6, 2, 1], layout=C (0x1)), I32([1, 2, 2] shape=[3], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [3, 2],  [2, 2]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 3421406824 10690064 856202962 1554995406 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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],      [16.0, 17.0, 18.0],      [19.0, 20.0, 21.0]],     [[22.0, 23.0, 24.0],      [25.0, 26.0, 27.0],      [28.0, 29.0, 30.0],      [31.0, 32.0, 33.0],      [34.0, 35.0, 36.0],      [37.0, 38.0, 39.0],      [40.0, 41.0, 42.0]],     [[43.0, 44.0, 45.0],      [46.0, 47.0, 48.0],      [49.0, 50.0, 51.0],      [52.0, 53.0, 54.0],      [55.0, 56.0, 57.0],      [58.0, 59.0, 60.0],      [61.0, 62.0, 63.0]]],    [[[64.0, 65.0, 66.0],      [67.0, 68.0, 69.0],      [70.0, 71.0, 72.0],      [73.0, 74.0, 75.0],      [76.0, 77.0, 78.0],      [79.0, 80.0, 81.0],      [82.0, 83.0, 84.0]],     [[85.0, 86.0, 87.0],      [88.0, 89.0, 90.0],      [91.0, 92.0, 93.0],      [94.0, 95.0, 96.0],      [97.0, 98.0, 99.0],      [100.0, 101.0, 102.0],      [103.0, 104.0, 105.0]],     [[106.0, 107.0, 108.0],      [109.0, 110.0, 111.0],      [112.0, 113.0, 114.0],      [115.0, 116.0, 117.0],      [118.0, 119.0, 120.0],      [121.0, 122.0, 123.0],      [124.0, 125.0, 126.0]]],    [[[127.0, 128.0, 129.0],      [130.0, 131.0, 132.0],      [133.0, 134.0, 135.0],      [136.0, 137.0, 138.0],      [139.0, 140.0, 141.0],      [142.0, 143.0, 144.0],      [145.0, 146.0, 147.0]],     [[148.0, 149.0, 150.0],      [151.0, 152.0, 153.0],      [154.0, 155.0, 156.0],      [157.0, 158.0, 159.0],      [160.0, 161.0, 162.0],      [163.0, 164.0, 165.0],      [166.0, 167.0, 168.0]],     [[169.0, 170.0, 171.0],      [172.0, 173.0, 174.0],      [175.0, 176.0, 177.0],      [178.0, 179.0, 180.0],      [181.0, 182.0, 183.0],      [184.0, 185.0, 186.0],      [187.0, 188.0, 189.0]]],    [[[190.0, 191.0, 192.0],      [193.0, 194.0, 195.0],      [196.0, 197.0, 198.0],      [199.0, 200.0, 201.0],      [202.0, 203.0, 204.0],      [205.0, 206.0, 207.0],      [208.0, 209.0, 210.0]],     [[211.0, 212.0, 213.0],      [214.0, 215.0, 216.0],      [217.0, 218.0, 219.0],      [220.0, 221.0, 222.0],      [223.0, 224.0, 225.0],      [226.0, 227.0, 228.0],      [229.0, 230.0, 231.0]],     [[232.0, 233.0, 234.0],      [235.0, 236.0, 237.0],      [238.0, 239.0, 240.0],      [241.0, 242.0, 243.0],      [244.0, 245.0, 246.0],      [247.0, 248.0, 249.0],      [250.0, 251.0, 252.0]]],    [[[253.0, 254.0, 255.0],      [256.0, 257.0, 258.0],      [259.0, 260.0, 261.0],      [262.0, 263.0, 264.0],      [265.0, 266.0, 267.0],      [268.0, 269.0, 270.0],      [271.0, 272.0, 273.0]],     [[274.0, 275.0, 276.0],      [277.0, 278.0, 279.0],      [280.0, 281.0, 282.0],      [283.0, 284.0, 285.0],      [286.0, 287.0, 288.0],      [289.0, 290.0, 291.0],      [292.0, 293.0, 294.0]],     [[295.0, 296.0, 297.0],      [298.0, 299.0, 300.0],      [301.0, 302.0, 303.0],      [304.0, 305.0, 306.0],      [307.0, 308.0, 309.0],      [310.0, 311.0, 312.0],      [313.0, 314.0, 315.0]]],    [[[316.0, 317.0, 318.0],      [319.0, 320.0, 321.0],      [322.0, 323.0, 324.0],      [325.0, 326.0, 327.0],      [328.0, 329.0, 330.0],      [331.0, 332.0, 333.0],      [334.0, 335.0, 336.0]],     [[337.0, 338.0, 339.0],      [340.0, 341.0, 342.0],      [343.0, 344.0, 345.0],      [346.0, 347.0, 348.0],      [349.0, 350.0, 351.0],      [352.0, 353.0, 354.0],      [355.0, 356.0, 357.0]],     [[358.0, 359.0, 360.0],      [361.0, 362.0, 363.0],      [364.0, 365.0, 366.0],      [367.0, 368.0, 369.0],      [370.0, 371.0, 372.0],      [373.0, 374.0, 375.0],      [376.0, 377.0, 378.0]]],    [[[379.0, 380.0, 381.0],      [382.0, 383.0, 384.0],      [385.0, 386.0, 387.0],      [388.0, 389.0, 390.0],      [391.0, 392.0, 393.0],      [394.0, 395.0, 396.0],      [397.0, 398.0, 399.0]],     [[400.0, 401.0, 402.0],      [403.0, 404.0, 405.0],      [406.0, 407.0, 408.0],      [409.0, 410.0, 411.0],      [412.0, 413.0, 414.0],      [415.0, 416.0, 417.0],      [418.0, 419.0, 420.0]],     [[421.0, 422.0, 423.0],      [424.0, 425.0, 426.0],      [427.0, 428.0, 429.0],      [430.0, 431.0, 432.0],      [433.0, 434.0, 435.0],      [436.0, 437.0, 438.0],      [439.0, 440.0, 441.0]]]],   [[[[442.0, 443.0, 444.0],      [445.0, 446.0, 447.0],      [448.0, 449.0, 450.0],      [451.0, 452.0, 453.0],      [454.0, 455.0, 456.0],      [457.0, 458.0, 459.0],      [460.0, 461.0, 462.0]],     [[463.0, 464.0, 465.0],      [466.0, 467.0, 468.0],      [469.0, 470.0, 471.0],      [472.0, 473.0, 474.0],      [475.0, 476.0, 477.0],      [478.0, 479.0, 480.0],      [481.0, 482.0, 483.0]],     [[484.0, 485.0, 486.0],      [487.0, 488.0, 489.0],      [490.0, 491.0, 492.0],      [493.0, 494.0, 495.0],      [496.0, 497.0, 498.0],      [499.0, 500.0, 501.0],      [502.0, 503.0, 504.0]]],    [[[505.0, 506.0, 507.0],      [508.0, 509.0, 510.0],      [511.0, 512.0, 513.0],      [514.0, 515.0, 516.0],      [517.0, 518.0, 519.0],      [520.0, 521.0, 522.0],      [523.0, 524.0, 525.0]],     [[526.0, 527.0, 528.0],      [529.0, 530.0, 531.0],      [532.0, 533.0, 534.0],      [535.0, 536.0, 537.0],      [538.0, 539.0, 540.0],      [541.0, 542.0, 543.0],      [544.0, 545.0, 546.0]],     [[547.0, 548.0, 549.0],      [550.0, 551.0, 552.0],      [553.0, 554.0, 555.0],      [556.0, 557.0, 558.0],      [559.0, 560.0, 561.0],      [562.0, 563.0, 564.0],      [565.0, 566.0, 567.0]]],    [[[568.0, 569.0, 570.0],      [571.0, 572.0, 573.0],      [574.0, 575.0, 576.0],      [577.0, 578.0, 579.0],      [580.0, 581.0, 582.0],      [583.0, 584.0, 585.0],      [586.0, 587.0, 588.0]],     [[589.0, 590.0, 591.0],      [592.0, 593.0, 594.0],      [595.0, 596.0, 597.0],      [598.0, 599.0, 600.0],      [601.0, 602.0, 603.0],      [604.0, 605.0, 606.0],      [607.0, 608.0, 609.0]],     [[610.0, 611.0, 612.0],      [613.0, 614.0, 615.0],      [616.0, 617.0, 618.0],      [619.0, 620.0, 621.0],      [622.0, 623.0, 624.0],      [625.0, 626.0, 627.0],      [628.0, 629.0, 630.0]]],    [[[631.0, 632.0, 633.0],      [634.0, 635.0, 636.0],      [637.0, 638.0, 639.0],      [640.0, 641.0, 642.0],      [643.0, 644.0, 645.0],      [646.0, 647.0, 648.0],      [649.0, 650.0, 651.0]],     [[652.0, 653.0, 654.0],      [655.0, 656.0, 657.0],      [658.0, 659.0, 660.0],      [661.0, 662.0, 663.0],      [664.0, 665.0, 666.0],      [667.0, 668.0, 669.0],      [670.0, 671.0, 672.0]],     [[673.0, 674.0, 675.0],      [676.0, 677.0, 678.0],      [679.0, 680.0, 681.0],      [682.0, 683.0, 684.0],      [685.0, 686.0, 687.0],      [688.0, 689.0, 690.0],      [691.0, 692.0, 693.0]]],    [[[694.0, 695.0, 696.0],      [697.0, 698.0, 699.0],      [700.0, 701.0, 702.0],      [703.0, 704.0, 705.0],      [706.0, 707.0, 708.0],      [709.0, 710.0, 711.0],      [712.0, 713.0, 714.0]],     [[715.0, 716.0, 717.0],      [718.0, 719.0, 720.0],      [721.0, 722.0, 723.0],      [724.0, 725.0, 726.0],      [727.0, 728.0, 729.0],      [730.0, 731.0, 732.0],      [733.0, 734.0, 735.0]],     [[736.0, 737.0, 738.0],      [739.0, 740.0, 741.0],      [742.0, 743.0, 744.0],      [745.0, 746.0, 747.0],      [748.0, 749.0, 750.0],      [751.0, 752.0, 753.0],      [754.0, 755.0, 756.0]]],    [[[757.0, 758.0, 759.0],      [760.0, 761.0, 762.0],      [763.0, 764.0, 765.0],      [766.0, 767.0, 768.0],      [769.0, 770.0, 771.0],      [772.0, 773.0, 774.0],      [775.0, 776.0, 777.0]],     [[778.0, 779.0, 780.0],      [781.0, 782.0, 783.0],      [784.0, 785.0, 786.0],      [787.0, 788.0, 789.0],      [790.0, 791.0, 792.0],      [793.0, 794.0, 795.0],      [796.0, 797.0, 798.0]],     [[799.0, 800.0, 801.0],      [802.0, 803.0, 804.0],      [805.0, 806.0, 807.0],      [808.0, 809.0, 810.0],      [811.0, 812.0, 813.0],      [814.0, 815.0, 816.0],      [817.0, 818.0, 819.0]]],    [[[820.0, 821.0, 822.0],      [823.0, 824.0, 825.0],      [826.0, 827.0, 828.0],      [829.0, 830.0, 831.0],      [832.0, 833.0, 834.0],      [835.0, 836.0, 837.0],      [838.0, 839.0, 840.0]],     [[841.0, 842.0, 843.0],      [844.0, 845.0, 846.0],      [847.0, 848.0, 849.0],      [850.0, 851.0, 852.0],      [853.0, 854.0, 855.0],      [856.0, 857.0, 858.0],      [859.0, 860.0, 861.0]],     [[862.0, 863.0, 864.0],      [865.0, 866.0, 867.0],      [868.0, 869.0, 870.0],      [871.0, 872.0, 873.0],      [874.0, 875.0, 876.0],      [877.0, 878.0, 879.0],      [880.0, 881.0, 882.0]]]],   [[[[883.0, 884.0, 885.0],      [886.0, 887.0, 888.0],      [889.0, 890.0, 891.0],      [892.0, 893.0, 894.0],      [895.0, 896.0, 897.0],      [898.0, 899.0, 900.0],      [901.0, 902.0, 903.0]],     [[904.0, 905.0, 906.0],      [907.0, 908.0, 909.0],      [910.0, 911.0, 912.0],      [913.0, 914.0, 915.0],      [916.0, 917.0, 918.0],      [919.0, 920.0, 921.0],      [922.0, 923.0, 924.0]],     [[925.0, 926.0, 927.0],      [928.0, 929.0, 930.0],      [931.0, 932.0, 933.0],      [934.0, 935.0, 936.0],      [937.0, 938.0, 939.0],      [940.0, 941.0, 942.0],      [943.0, 944.0, 945.0]]],    [[[946.0, 947.0, 948.0],      [949.0, 950.0, 951.0],      [952.0, 953.0, 954.0],      [955.0, 956.0, 957.0],      [958.0, 959.0, 960.0],      [961.0, 962.0, 963.0],      [964.0, 965.0, 966.0]],     [[967.0, 968.0, 969.0],      [970.0, 971.0, 972.0],      [973.0, 974.0, 975.0],      [976.0, 977.0, 978.0],      [979.0, 980.0, 981.0],      [982.0, 983.0, 984.0],      [985.0, 986.0, 987.0]],     [[988.0, 989.0, 990.0],      [991.0, 992.0, 993.0],      [994.0, 995.0, 996.0],      [997.0, 998.0, 999.0],      [1000.0, 1001.0, 1002.0],      [1003.0, 1004.0, 1005.0],      [1006.0, 1007.0, 1008.0]]],    [[[1009.0, 1010.0, 1011.0],      [1012.0, 1013.0, 1014.0],      [1015.0, 1016.0, 1017.0],      [1018.0, 1019.0, 1020.0],      [1021.0, 1022.0, 1023.0],      [1024.0, 1025.0, 1026.0],      [1027.0, 1028.0, 1029.0]],     [[1030.0, 1031.0, 1032.0],      [1033.0, 1034.0, 1035.0],      [1036.0, 1037.0, 1038.0],      [1039.0, 1040.0, 1041.0],      [1042.0, 1043.0, 1044.0],      [1045.0, 1046.0, 1047.0],      [1048.0, 1049.0, 1050.0]],     [[1051.0, 1052.0, 1053.0],      [1054.0, 1055.0, 1056.0],      [1057.0, 1058.0, 1059.0],      [1060.0, 1061.0, 1062.0],      [1063.0, 1064.0, 1065.0],      [1066.0, 1067.0, 1068.0],      [1069.0, 1070.0, 1071.0]]],    [[[1072.0, 1073.0, 1074.0],      [1075.0, 1076.0, 1077.0],      [1078.0, 1079.0, 1080.0],      [1081.0, 1082.0, 1083.0],      [1084.0, 1085.0, 1086.0],      [1087.0, 1088.0, 1089.0],      [1090.0, 1091.0, 1092.0]],     [[1093.0, 1094.0, 1095.0],      [1096.0, 1097.0, 1098.0],      [1099.0, 1100.0, 1101.0],      [1102.0, 1103.0, 1104.0],      [1105.0, 1106.0, 1107.0],      [1108.0, 1109.0, 1110.0],      [1111.0, 1112.0, 1113.0]],     [[1114.0, 1115.0, 1116.0],      [1117.0, 1118.0, 1119.0],      [1120.0, 1121.0, 1122.0],      [1123.0, 1124.0, 1125.0],      [1126.0, 1127.0, 1128.0],      [1129.0, 1130.0, 1131.0],      [1132.0, 1133.0, 1134.0]]],    [[[1135.0, 1136.0, 1137.0],      [1138.0, 1139.0, 1140.0],      [1141.0, 1142.0, 1143.0],      [1144.0, 1145.0, 1146.0],      [1147.0, 1148.0, 1149.0],      [1150.0, 1151.0, 1152.0],      [1153.0, 1154.0, 1155.0]],     [[1156.0, 1157.0, 1158.0],      [1159.0, 1160.0, 1161.0],      [1162.0, 1163.0, 1164.0],      [1165.0, 1166.0, 1167.0],      [1168.0, 1169.0, 1170.0],      [1171.0, 1172.0, 1173.0],      [1174.0, 1175.0, 1176.0]],     [[1177.0, 1178.0, 1179.0],      [1180.0, 1181.0, 1182.0],      [1183.0, 1184.0, 1185.0],      [1186.0, 1187.0, 1188.0],      [1189.0, 1190.0, 1191.0],      [1192.0, 1193.0, 1194.0],      [1195.0, 1196.0, 1197.0]]],    [[[1198.0, 1199.0, 1200.0],      [1201.0, 1202.0, 1203.0],      [1204.0, 1205.0, 1206.0],      [1207.0, 1208.0, 1209.0],      [1210.0, 1211.0, 1212.0],      [1213.0, 1214.0, 1215.0],      [1216.0, 1217.0, 1218.0]],     [[1219.0, 1220.0, 1221.0],      [1222.0, 1223.0, 1224.0],      [1225.0, 1226.0, 1227.0],      [1228.0, 1229.0, 1230.0],      [1231.0, 1232.0, 1233.0],      [1234.0, 1235.0, 1236.0],      [1237.0, 1238.0, 1239.0]],     [[1240.0, 1241.0, 1242.0],      [1243.0, 1244.0, 1245.0],      [1246.0, 1247.0, 1248.0],      [1249.0, 1250.0, 1251.0],      [1252.0, 1253.0, 1254.0],      [1255.0, 1256.0, 1257.0],      [1258.0, 1259.0, 1260.0]]],    [[[1261.0, 1262.0, 1263.0],      [1264.0, 1265.0, 1266.0],      [1267.0, 1268.0, 1269.0],      [1270.0, 1271.0, 1272.0],      [1273.0, 1274.0, 1275.0],      [1276.0, 1277.0, 1278.0],      [1279.0, 1280.0, 1281.0]],     [[1282.0, 1283.0, 1284.0],      [1285.0, 1286.0, 1287.0],      [1288.0, 1289.0, 1290.0],      [1291.0, 1292.0, 1293.0],      [1294.0, 1295.0, 1296.0],      [1297.0, 1298.0, 1299.0],      [1300.0, 1301.0, 1302.0]],     [[1303.0, 1304.0, 1305.0],      [1306.0, 1307.0, 1308.0],      [1309.0, 1310.0, 1311.0],      [1312.0, 1313.0, 1314.0],      [1315.0, 1316.0, 1317.0],      [1318.0, 1319.0, 1320.0],      [1321.0, 1322.0, 1323.0]]]],   [[[[1324.0, 1325.0, 1326.0],      [1327.0, 1328.0, 1329.0],      [1330.0, 1331.0, 1332.0],      [1333.0, 1334.0, 1335.0],      [1336.0, 1337.0, 1338.0],      [1339.0, 1340.0, 1341.0],      [1342.0, 1343.0, 1344.0]],     [[1345.0, 1346.0, 1347.0],      [1348.0, 1349.0, 1350.0],      [1351.0, 1352.0, 1353.0],      [1354.0, 1355.0, 1356.0],      [1357.0, 1358.0, 1359.0],      [1360.0, 1361.0, 1362.0],      [1363.0, 1364.0, 1365.0]],     [[1366.0, 1367.0, 1368.0],      [1369.0, 1370.0, 1371.0],      [1372.0, 1373.0, 1374.0],      [1375.0, 1376.0, 1377.0],      [1378.0, 1379.0, 1380.0],      [1381.0, 1382.0, 1383.0],      [1384.0, 1385.0, 1386.0]]],    [[[1387.0, 1388.0, 1389.0],      [1390.0, 1391.0, 1392.0],      [1393.0, 1394.0, 1395.0],      [1396.0, 1397.0, 1398.0],      [1399.0, 1400.0, 1401.0],      [1402.0, 1403.0, 1404.0],      [1405.0, 1406.0, 1407.0]],     [[1408.0, 1409.0, 1410.0],      [1411.0, 1412.0, 1413.0],      [1414.0, 1415.0, 1416.0],      [1417.0, 1418.0, 1419.0],      [1420.0, 1421.0, 1422.0],      [1423.0, 1424.0, 1425.0],      [1426.0, 1427.0, 1428.0]],     [[1429.0, 1430.0, 1431.0],      [1432.0, 1433.0, 1434.0],      [1435.0, 1436.0, 1437.0],      [1438.0, 1439.0, 1440.0],      [1441.0, 1442.0, 1443.0],      [1444.0, 1445.0, 1446.0],      [1447.0, 1448.0, 1449.0]]],    [[[1450.0, 1451.0, 1452.0],      [1453.0, 1454.0, 1455.0],      [1456.0, 1457.0, 1458.0],      [1459.0, 1460.0, 1461.0],      [1462.0, 1463.0, 1464.0],      [1465.0, 1466.0, 1467.0],      [1468.0, 1469.0, 1470.0]],     [[1471.0, 1472.0, 1473.0],      [1474.0, 1475.0, 1476.0],      [1477.0, 1478.0, 1479.0],      [1480.0, 1481.0, 1482.0],      [1483.0, 1484.0, 1485.0],      [1486.0, 1487.0, 1488.0],      [1489.0, 1490.0, 1491.0]],     [[1492.0, 1493.0, 1494.0],      [1495.0, 1496.0, 1497.0],      [1498.0, 1499.0, 1500.0],      [1501.0, 1502.0, 1503.0],      [1504.0, 1505.0, 1506.0],      [1507.0, 1508.0, 1509.0],      [1510.0, 1511.0, 1512.0]]],    [[[1513.0, 1514.0, 1515.0],      [1516.0, 1517.0, 1518.0],      [1519.0, 1520.0, 1521.0],      [1522.0, 1523.0, 1524.0],      [1525.0, 1526.0, 1527.0],      [1528.0, 1529.0, 1530.0],      [1531.0, 1532.0, 1533.0]],     [[1534.0, 1535.0, 1536.0],      [1537.0, 1538.0, 1539.0],      [1540.0, 1541.0, 1542.0],      [1543.0, 1544.0, 1545.0],      [1546.0, 1547.0, 1548.0],      [1549.0, 1550.0, 1551.0],      [1552.0, 1553.0, 1554.0]],     [[1555.0, 1556.0, 1557.0],      [1558.0, 1559.0, 1560.0],      [1561.0, 1562.0, 1563.0],      [1564.0, 1565.0, 1566.0],      [1567.0, 1568.0, 1569.0],      [1570.0, 1571.0, 1572.0],      [1573.0, 1574.0, 1575.0]]],    [[[1576.0, 1577.0, 1578.0],      [1579.0, 1580.0, 1581.0],      [1582.0, 1583.0, 1584.0],      [1585.0, 1586.0, 1587.0],      [1588.0, 1589.0, 1590.0],      [1591.0, 1592.0, 1593.0],      [1594.0, 1595.0, 1596.0]],     [[1597.0, 1598.0, 1599.0],      [1600.0, 1601.0, 1602.0],      [1603.0, 1604.0, 1605.0],      [1606.0, 1607.0, 1608.0],      [1609.0, 1610.0, 1611.0],      [1612.0, 1613.0, 1614.0],      [1615.0, 1616.0, 1617.0]],     [[1618.0, 1619.0, 1620.0],      [1621.0, 1622.0, 1623.0],      [1624.0, 1625.0, 1626.0],      [1627.0, 1628.0, 1629.0],      [1630.0, 1631.0, 1632.0],      [1633.0, 1634.0, 1635.0],      [1636.0, 1637.0, 1638.0]]],    [[[1639.0, 1640.0, 1641.0],      [1642.0, 1643.0, 1644.0],      [1645.0, 1646.0, 1647.0],      [1648.0, 1649.0, 1650.0],      [1651.0, 1652.0, 1653.0],      [1654.0, 1655.0, 1656.0],      [1657.0, 1658.0, 1659.0]],     [[1660.0, 1661.0, 1662.0],      [1663.0, 1664.0, 1665.0],      [1666.0, 1667.0, 1668.0],      [1669.0, 1670.0, 1671.0],      [1672.0, 1673.0, 1674.0],      [1675.0, 1676.0, 1677.0],      [1678.0, 1679.0, 1680.0]],     [[1681.0, 1682.0, 1683.0],      [1684.0, 1685.0, 1686.0],      [1687.0, 1688.0, 1689.0],      [1690.0, 1691.0, 1692.0],      [1693.0, 1694.0, 1695.0],      [1696.0, 1697.0, 1698.0],      [1699.0, 1700.0, 1701.0]]],    [[[1702.0, 1703.0, 1704.0],      [1705.0, 1706.0, 1707.0],      [1708.0, 1709.0, 1710.0],      [1711.0, 1712.0, 1713.0],      [1714.0, 1715.0, 1716.0],      [1717.0, 1718.0, 1719.0],      [1720.0, 1721.0, 1722.0]],     [[1723.0, 1724.0, 1725.0],      [1726.0, 1727.0, 1728.0],      [1729.0, 1730.0, 1731.0],      [1732.0, 1733.0, 1734.0],      [1735.0, 1736.0, 1737.0],      [1738.0, 1739.0, 1740.0],      [1741.0, 1742.0, 1743.0]],     [[1744.0, 1745.0, 1746.0],      [1747.0, 1748.0, 1749.0],      [1750.0, 1751.0, 1752.0],      [1753.0, 1754.0, 1755.0],      [1756.0, 1757.0, 1758.0],      [1759.0, 1760.0, 1761.0],      [1762.0, 1763.0, 1764.0]]]],   [[[[1765.0, 1766.0, 1767.0],      [1768.0, 1769.0, 1770.0],      [1771.0, 1772.0, 1773.0],      [1774.0, 1775.0, 1776.0],      [1777.0, 1778.0, 1779.0],      [1780.0, 1781.0, 1782.0],      [1783.0, 1784.0, 1785.0]],     [[1786.0, 1787.0, 1788.0],      [1789.0, 1790.0, 1791.0],      [1792.0, 1793.0, 1794.0],      [1795.0, 1796.0, 1797.0],      [1798.0, 1799.0, 1800.0],      [1801.0, 1802.0, 1803.0],      [1804.0, 1805.0, 1806.0]],     [[1807.0, 1808.0, 1809.0],      [1810.0, 1811.0, 1812.0],      [1813.0, 1814.0, 1815.0],      [1816.0, 1817.0, 1818.0],      [1819.0, 1820.0, 1821.0],      [1822.0, 1823.0, 1824.0],      [1825.0, 1826.0, 1827.0]]],    [[[1828.0, 1829.0, 1830.0],      [1831.0, 1832.0, 1833.0],      [1834.0, 1835.0, 1836.0],      [1837.0, 1838.0, 1839.0],      [1840.0, 1841.0, 1842.0],      [1843.0, 1844.0, 1845.0],      [1846.0, 1847.0, 1848.0]],     [[1849.0, 1850.0, 1851.0],      [1852.0, 1853.0, 1854.0],      [1855.0, 1856.0, 1857.0],      [1858.0, 1859.0, 1860.0],      [1861.0, 1862.0, 1863.0],      [1864.0, 1865.0, 1866.0],      [1867.0, 1868.0, 1869.0]],     [[1870.0, 1871.0, 1872.0],      [1873.0, 1874.0, 1875.0],      [1876.0, 1877.0, 1878.0],      [1879.0, 1880.0, 1881.0],      [1882.0, 1883.0, 1884.0],      [1885.0, 1886.0, 1887.0],      [1888.0, 1889.0, 1890.0]]],    [[[1891.0, 1892.0, 1893.0],      [1894.0, 1895.0, 1896.0],      [1897.0, 1898.0, 1899.0],      [1900.0, 1901.0, 1902.0],      [1903.0, 1904.0, 1905.0],      [1906.0, 1907.0, 1908.0],      [1909.0, 1910.0, 1911.0]],     [[1912.0, 1913.0, 1914.0],      [1915.0, 1916.0, 1917.0],      [1918.0, 1919.0, 1920.0],      [1921.0, 1922.0, 1923.0],      [1924.0, 1925.0, 1926.0],      [1927.0, 1928.0, 1929.0],      [1930.0, 1931.0, 1932.0]],     [[1933.0, 1934.0, 1935.0],      [1936.0, 1937.0, 1938.0],      [1939.0, 1940.0, 1941.0],      [1942.0, 1943.0, 1944.0],      [1945.0, 1946.0, 1947.0],      [1948.0, 1949.0, 1950.0],      [1951.0, 1952.0, 1953.0]]],    [[[1954.0, 1955.0, 1956.0],      [1957.0, 1958.0, 1959.0],      [1960.0, 1961.0, 1962.0],      [1963.0, 1964.0, 1965.0],      [1966.0, 1967.0, 1968.0],      [1969.0, 1970.0, 1971.0],      [1972.0, 1973.0, 1974.0]],     [[1975.0, 1976.0, 1977.0],      [1978.0, 1979.0, 1980.0],      [1981.0, 1982.0, 1983.0],      [1984.0, 1985.0, 1986.0],      [1987.0, 1988.0, 1989.0],      [1990.0, 1991.0, 1992.0],      [1993.0, 1994.0, 1995.0]],     [[1996.0, 1997.0, 1998.0],      [1999.0, 2000.0, 2001.0],      [2002.0, 2003.0, 2004.0],      [2005.0, 2006.0, 2007.0],      [2008.0, 2009.0, 2010.0],      [2011.0, 2012.0, 2013.0],      [2014.0, 2015.0, 2016.0]]],    [[[2017.0, 2018.0, 2019.0],      [2020.0, 2021.0, 2022.0],      [2023.0, 2024.0, 2025.0],      [2026.0, 2027.0, 2028.0],      [2029.0, 2030.0, 2031.0],      [2032.0, 2033.0, 2034.0],      [2035.0, 2036.0, 2037.0]],     [[2038.0, 2039.0, 2040.0],      [2041.0, 2042.0, 2043.0],      [2044.0, 2045.0, 2046.0],      [2047.0, 2048.0, 2049.0],      [2050.0, 2051.0, 2052.0],      [2053.0, 2054.0, 2055.0],      [2056.0, 2057.0, 2058.0]],     [[2059.0, 2060.0, 2061.0],      [2062.0, 2063.0, 2064.0],      [2065.0, 2066.0, 2067.0],      [2068.0, 2069.0, 2070.0],      [2071.0, 2072.0, 2073.0],      [2074.0, 2075.0, 2076.0],      [2077.0, 2078.0, 2079.0]]],    [[[2080.0, 2081.0, 2082.0],      [2083.0, 2084.0, 2085.0],      [2086.0, 2087.0, 2088.0],      [2089.0, 2090.0, 2091.0],      [2092.0, 2093.0, 2094.0],      [2095.0, 2096.0, 2097.0],      [2098.0, 2099.0, 2100.0]],     [[2101.0, 2102.0, 2103.0],      [2104.0, 2105.0, 2106.0],      [2107.0, 2108.0, 2109.0],      [2110.0, 2111.0, 2112.0],      [2113.0, 2114.0, 2115.0],      [2116.0, 2117.0, 2118.0],      [2119.0, 2120.0, 2121.0]],     [[2122.0, 2123.0, 2124.0],      [2125.0, 2126.0, 2127.0],      [2128.0, 2129.0, 2130.0],      [2131.0, 2132.0, 2133.0],      [2134.0, 2135.0, 2136.0],      [2137.0, 2138.0, 2139.0],      [2140.0, 2141.0, 2142.0]]],    [[[2143.0, 2144.0, 2145.0],      [2146.0, 2147.0, 2148.0],      [2149.0, 2150.0, 2151.0],      [2152.0, 2153.0, 2154.0],      [2155.0, 2156.0, 2157.0],      [2158.0, 2159.0, 2160.0],      [2161.0, 2162.0, 2163.0]],     [[2164.0, 2165.0, 2166.0],      [2167.0, 2168.0, 2169.0],      [2170.0, 2171.0, 2172.0],      [2173.0, 2174.0, 2175.0],      [2176.0, 2177.0, 2178.0],      [2179.0, 2180.0, 2181.0],      [2182.0, 2183.0, 2184.0]],     [[2185.0, 2186.0, 2187.0],      [2188.0, 2189.0, 2190.0],      [2191.0, 2192.0, 2193.0],      [2194.0, 2195.0, 2196.0],      [2197.0, 2198.0, 2199.0],      [2200.0, 2201.0, 2202.0],      [2203.0, 2204.0, 2205.0]]]],   [[[[2206.0, 2207.0, 2208.0],      [2209.0, 2210.0, 2211.0],      [2212.0, 2213.0, 2214.0],      [2215.0, 2216.0, 2217.0],      [2218.0, 2219.0, 2220.0],      [2221.0, 2222.0, 2223.0],      [2224.0, 2225.0, 2226.0]],     [[2227.0, 2228.0, 2229.0],      [2230.0, 2231.0, 2232.0],      [2233.0, 2234.0, 2235.0],      [2236.0, 2237.0, 2238.0],      [2239.0, 2240.0, 2241.0],      [2242.0, 2243.0, 2244.0],      [2245.0, 2246.0, 2247.0]],     [[2248.0, 2249.0, 2250.0],      [2251.0, 2252.0, 2253.0],      [2254.0, 2255.0, 2256.0],      [2257.0, 2258.0, 2259.0],      [2260.0, 2261.0, 2262.0],      [2263.0, 2264.0, 2265.0],      [2266.0, 2267.0, 2268.0]]],    [[[2269.0, 2270.0, 2271.0],      [2272.0, 2273.0, 2274.0],      [2275.0, 2276.0, 2277.0],      [2278.0, 2279.0, 2280.0],      [2281.0, 2282.0, 2283.0],      [2284.0, 2285.0, 2286.0],      [2287.0, 2288.0, 2289.0]],     [[2290.0, 2291.0, 2292.0],      [2293.0, 2294.0, 2295.0],      [2296.0, 2297.0, 2298.0],      [2299.0, 2300.0, 2301.0],      [2302.0, 2303.0, 2304.0],      [2305.0, 2306.0, 2307.0],      [2308.0, 2309.0, 2310.0]],     [[2311.0, 2312.0, 2313.0],      [2314.0, 2315.0, 2316.0],      [2317.0, 2318.0, 2319.0],      [2320.0, 2321.0, 2322.0],      [2323.0, 2324.0, 2325.0],      [2326.0, 2327.0, 2328.0],      [2329.0, 2330.0, 2331.0]]],    [[[2332.0, 2333.0, 2334.0],      [2335.0, 2336.0, 2337.0],      [2338.0, 2339.0, 2340.0],      [2341.0, 2342.0, 2343.0],      [2344.0, 2345.0, 2346.0],      [2347.0, 2348.0, 2349.0],      [2350.0, 2351.0, 2352.0]],     [[2353.0, 2354.0, 2355.0],      [2356.0, 2357.0, 2358.0],      [2359.0, 2360.0, 2361.0],      [2362.0, 2363.0, 2364.0],      [2365.0, 2366.0, 2367.0],      [2368.0, 2369.0, 2370.0],      [2371.0, 2372.0, 2373.0]],     [[2374.0, 2375.0, 2376.0],      [2377.0, 2378.0, 2379.0],      [2380.0, 2381.0, 2382.0],      [2383.0, 2384.0, 2385.0],      [2386.0, 2387.0, 2388.0],      [2389.0, 2390.0, 2391.0],      [2392.0, 2393.0, 2394.0]]],    [[[2395.0, 2396.0, 2397.0],      [2398.0, 2399.0, 2400.0],      [2401.0, 2402.0, 2403.0],      [2404.0, 2405.0, 2406.0],      [2407.0, 2408.0, 2409.0],      [2410.0, 2411.0, 2412.0],      [2413.0, 2414.0, 2415.0]],     [[2416.0, 2417.0, 2418.0],      [2419.0, 2420.0, 2421.0],      [2422.0, 2423.0, 2424.0],      [2425.0, 2426.0, 2427.0],      [2428.0, 2429.0, 2430.0],      [2431.0, 2432.0, 2433.0],      [2434.0, 2435.0, 2436.0]],     [[2437.0, 2438.0, 2439.0],      [2440.0, 2441.0, 2442.0],      [2443.0, 2444.0, 2445.0],      [2446.0, 2447.0, 2448.0],      [2449.0, 2450.0, 2451.0],      [2452.0, 2453.0, 2454.0],      [2455.0, 2456.0, 2457.0]]],    [[[2458.0, 2459.0, 2460.0],      [2461.0, 2462.0, 2463.0],      [2464.0, 2465.0, 2466.0],      [2467.0, 2468.0, 2469.0],      [2470.0, 2471.0, 2472.0],      [2473.0, 2474.0, 2475.0],      [2476.0, 2477.0, 2478.0]],     [[2479.0, 2480.0, 2481.0],      [2482.0, 2483.0, 2484.0],      [2485.0, 2486.0, 2487.0],      [2488.0, 2489.0, 2490.0],      [2491.0, 2492.0, 2493.0],      [2494.0, 2495.0, 2496.0],      [2497.0, 2498.0, 2499.0]],     [[2500.0, 2501.0, 2502.0],      [2503.0, 2504.0, 2505.0],      [2506.0, 2507.0, 2508.0],      [2509.0, 2510.0, 2511.0],      [2512.0, 2513.0, 2514.0],      [2515.0, 2516.0, 2517.0],      [2518.0, 2519.0, 2520.0]]],    [[[2521.0, 2522.0, 2523.0],      [2524.0, 2525.0, 2526.0],      [2527.0, 2528.0, 2529.0],      [2530.0, 2531.0, 2532.0],      [2533.0, 2534.0, 2535.0],      [2536.0, 2537.0, 2538.0],      [2539.0, 2540.0, 2541.0]],     [[2542.0, 2543.0, 2544.0],      [2545.0, 2546.0, 2547.0],      [2548.0, 2549.0, 2550.0],      [2551.0, 2552.0, 2553.0],      [2554.0, 2555.0, 2556.0],      [2557.0, 2558.0, 2559.0],      [2560.0, 2561.0, 2562.0]],     [[2563.0, 2564.0, 2565.0],      [2566.0, 2567.0, 2568.0],      [2569.0, 2570.0, 2571.0],      [2572.0, 2573.0, 2574.0],      [2575.0, 2576.0, 2577.0],      [2578.0, 2579.0, 2580.0],      [2581.0, 2582.0, 2583.0]]],    [[[2584.0, 2585.0, 2586.0],      [2587.0, 2588.0, 2589.0],      [2590.0, 2591.0, 2592.0],      [2593.0, 2594.0, 2595.0],      [2596.0, 2597.0, 2598.0],      [2599.0, 2600.0, 2601.0],      [2602.0, 2603.0, 2604.0]],     [[2605.0, 2606.0, 2607.0],      [2608.0, 2609.0, 2610.0],      [2611.0, 2612.0, 2613.0],      [2614.0, 2615.0, 2616.0],      [2617.0, 2618.0, 2619.0],      [2620.0, 2621.0, 2622.0],      [2623.0, 2624.0, 2625.0]],     [[2626.0, 2627.0, 2628.0],      [2629.0, 2630.0, 2631.0],      [2632.0, 2633.0, 2634.0],      [2635.0, 2636.0, 2637.0],      [2638.0, 2639.0, 2640.0],      [2641.0, 2642.0, 2643.0],      [2644.0, 2645.0, 2646.0]]]]],  [[[[[2647.0, 2648.0, 2649.0],      [2650.0, 2651.0, 2652.0],      [2653.0, 2654.0, 2655.0],      [2656.0, 2657.0, 2658.0],      [2659.0, 2660.0, 2661.0],      [2662.0, 2663.0, 2664.0],      [2665.0, 2666.0, 2667.0]],     [[2668.0, 2669.0, 2670.0],      [2671.0, 2672.0, 2673.0],      [2674.0, 2675.0, 2676.0],      [2677.0, 2678.0, 2679.0],      [2680.0, 2681.0, 2682.0],      [2683.0, 2684.0, 2685.0],      [2686.0, 2687.0, 2688.0]],     [[2689.0, 2690.0, 2691.0],      [2692.0, 2693.0, 2694.0],      [2695.0, 2696.0, 2697.0],      [2698.0, 2699.0, 2700.0],      [2701.0, 2702.0, 2703.0],      [2704.0, 2705.0, 2706.0],      [2707.0, 2708.0, 2709.0]]],    [[[2710.0, 2711.0, 2712.0],      [2713.0, 2714.0, 2715.0],      [2716.0, 2717.0, 2718.0],      [2719.0, 2720.0, 2721.0],      [2722.0, 2723.0, 2724.0],      [2725.0, 2726.0, 2727.0],      [2728.0, 2729.0, 2730.0]],     [[2731.0, 2732.0, 2733.0],      [2734.0, 2735.0, 2736.0],      [2737.0, 2738.0, 2739.0],      [2740.0, 2741.0, 2742.0],      [2743.0, 2744.0, 2745.0],      [2746.0, 2747.0, 2748.0],      [2749.0, 2750.0, 2751.0]],     [[2752.0, 2753.0, 2754.0],      [2755.0, 2756.0, 2757.0],      [2758.0, 2759.0, 2760.0],      [2761.0, 2762.0, 2763.0],      [2764.0, 2765.0, 2766.0],      [2767.0, 2768.0, 2769.0],      [2770.0, 2771.0, 2772.0]]],    [[[2773.0, 2774.0, 2775.0],      [2776.0, 2777.0, 2778.0],      [2779.0, 2780.0, 2781.0],      [2782.0, 2783.0, 2784.0],      [2785.0, 2786.0, 2787.0],      [2788.0, 2789.0, 2790.0],      [2791.0, 2792.0, 2793.0]],     [[2794.0, 2795.0, 2796.0],      [2797.0, 2798.0, 2799.0],      [2800.0, 2801.0, 2802.0],      [2803.0, 2804.0, 2805.0],      [2806.0, 2807.0, 2808.0],      [2809.0, 2810.0, 2811.0],      [2812.0, 2813.0, 2814.0]],     [[2815.0, 2816.0, 2817.0],      [2818.0, 2819.0, 2820.0],      [2821.0, 2822.0, 2823.0],      [2824.0, 2825.0, 2826.0],      [2827.0, 2828.0, 2829.0],      [2830.0, 2831.0, 2832.0],      [2833.0, 2834.0, 2835.0]]],    [[[2836.0, 2837.0, 2838.0],      [2839.0, 2840.0, 2841.0],      [2842.0, 2843.0, 2844.0],      [2845.0, 2846.0, 2847.0],      [2848.0, 2849.0, 2850.0],      [2851.0, 2852.0, 2853.0],      [2854.0, 2855.0, 2856.0]],     [[2857.0, 2858.0, 2859.0],      [2860.0, 2861.0, 2862.0],      [2863.0, 2864.0, 2865.0],      [2866.0, 2867.0, 2868.0],      [2869.0, 2870.0, 2871.0],      [2872.0, 2873.0, 2874.0],      [2875.0, 2876.0, 2877.0]],     [[2878.0, 2879.0, 2880.0],      [2881.0, 2882.0, 2883.0],      [2884.0, 2885.0, 2886.0],      [2887.0, 2888.0, 2889.0],      [2890.0, 2891.0, 2892.0],      [2893.0, 2894.0, 2895.0],      [2896.0, 2897.0, 2898.0]]],    [[[2899.0, 2900.0, 2901.0],      [2902.0, 2903.0, 2904.0],      [2905.0, 2906.0, 2907.0],      [2908.0, 2909.0, 2910.0],      [2911.0, 2912.0, 2913.0],      [2914.0, 2915.0, 2916.0],      [2917.0, 2918.0, 2919.0]],     [[2920.0, 2921.0, 2922.0],      [2923.0, 2924.0, 2925.0],      [2926.0, 2927.0, 2928.0],      [2929.0, 2930.0, 2931.0],      [2932.0, 2933.0, 2934.0],      [2935.0, 2936.0, 2937.0],      [2938.0, 2939.0, 2940.0]],     [[2941.0, 2942.0, 2943.0],      [2944.0, 2945.0, 2946.0],      [2947.0, 2948.0, 2949.0],      [2950.0, 2951.0, 2952.0],      [2953.0, 2954.0, 2955.0],      [2956.0, 2957.0, 2958.0],      [2959.0, 2960.0, 2961.0]]],    [[[2962.0, 2963.0, 2964.0],      [2965.0, 2966.0, 2967.0],      [2968.0, 2969.0, 2970.0],      [2971.0, 2972.0, 2973.0],      [2974.0, 2975.0, 2976.0],      [2977.0, 2978.0, 2979.0],      [2980.0, 2981.0, 2982.0]],     [[2983.0, 2984.0, 2985.0],      [2986.0, 2987.0, 2988.0],      [2989.0, 2990.0, 2991.0],      [2992.0, 2993.0, 2994.0],      [2995.0, 2996.0, 2997.0],      [2998.0, 2999.0, 3000.0],      [3001.0, 3002.0, 3003.0]],     [[3004.0, 3005.0, 3006.0],      [3007.0, 3008.0, 3009.0],      [3010.0, 3011.0, 3012.0],      [3013.0, 3014.0, 3015.0],      [3016.0, 3017.0, 3018.0],      [3019.0, 3020.0, 3021.0],      [3022.0, 3023.0, 3024.0]]],    [[[3025.0, 3026.0, 3027.0],      [3028.0, 3029.0, 3030.0],      [3031.0, 3032.0, 3033.0],      [3034.0, 3035.0, 3036.0],      [3037.0, 3038.0, 3039.0],      [3040.0, 3041.0, 3042.0],      [3043.0, 3044.0, 3045.0]],     [[3046.0, 3047.0, 3048.0],      [3049.0, 3050.0, 3051.0],      [3052.0, 3053.0, 3054.0],      [3055.0, 3056.0, 3057.0],      [3058.0, 3059.0, 3060.0],      [3061.0, 3062.0, 3063.0],      [3064.0, 3065.0, 3066.0]],     [[3067.0, 3068.0, 3069.0],      [3070.0, 3071.0, 3072.0],      [3073.0, 3074.0, 3075.0],      [3076.0, 3077.0, 3078.0],      [3079.0, 3080.0, 3081.0],      [3082.0, 3083.0, 3084.0],      [3085.0, 3086.0, 3087.0]]]],   [[[[3088.0, 3089.0, 3090.0],      [3091.0, 3092.0, 3093.0],      [3094.0, 3095.0, 3096.0],      [3097.0, 3098.0, 3099.0],      [3100.0, 3101.0, 3102.0],      [3103.0, 3104.0, 3105.0],      [3106.0, 3107.0, 3108.0]],     [[3109.0, 3110.0, 3111.0],      [3112.0, 3113.0, 3114.0],      [3115.0, 3116.0, 3117.0],      [3118.0, 3119.0, 3120.0],      [3121.0, 3122.0, 3123.0],      [3124.0, 3125.0, 3126.0],      [3127.0, 3128.0, 3129.0]],     [[3130.0, 3131.0, 3132.0],      [3133.0, 3134.0, 3135.0],      [3136.0, 3137.0, 3138.0],      [3139.0, 3140.0, 3141.0],      [3142.0, 3143.0, 3144.0],      [3145.0, 3146.0, 3147.0],      [3148.0, 3149.0, 3150.0]]],    [[[3151.0, 3152.0, 3153.0],      [3154.0, 3155.0, 3156.0],      [3157.0, 3158.0, 3159.0],      [3160.0, 3161.0, 3162.0],      [3163.0, 3164.0, 3165.0],      [3166.0, 3167.0, 3168.0],      [3169.0, 3170.0, 3171.0]],     [[3172.0, 3173.0, 3174.0],      [3175.0, 3176.0, 3177.0],      [3178.0, 3179.0, 3180.0],      [3181.0, 3182.0, 3183.0],      [3184.0, 3185.0, 3186.0],      [3187.0, 3188.0, 3189.0],      [3190.0, 3191.0, 3192.0]],     [[3193.0, 3194.0, 3195.0],      [3196.0, 3197.0, 3198.0],      [3199.0, 3200.0, 3201.0],      [3202.0, 3203.0, 3204.0],      [3205.0, 3206.0, 3207.0],      [3208.0, 3209.0, 3210.0],      [3211.0, 3212.0, 3213.0]]],    [[[3214.0, 3215.0, 3216.0],      [3217.0, 3218.0, 3219.0],      [3220.0, 3221.0, 3222.0],      [3223.0, 3224.0, 3225.0],      [3226.0, 3227.0, 3228.0],      [3229.0, 3230.0, 3231.0],      [3232.0, 3233.0, 3234.0]],     [[3235.0, 3236.0, 3237.0],      [3238.0, 3239.0, 3240.0],      [3241.0, 3242.0, 3243.0],      [3244.0, 3245.0, 3246.0],      [3247.0, 3248.0, 3249.0],      [3250.0, 3251.0, 3252.0],      [3253.0, 3254.0, 3255.0]],     [[3256.0, 3257.0, 3258.0],      [3259.0, 3260.0, 3261.0],      [3262.0, 3263.0, 3264.0],      [3265.0, 3266.0, 3267.0],      [3268.0, 3269.0, 3270.0],      [3271.0, 3272.0, 3273.0],      [3274.0, 3275.0, 3276.0]]],    [[[3277.0, 3278.0, 3279.0],      [3280.0, 3281.0, 3282.0],      [3283.0, 3284.0, 3285.0],      [3286.0, 3287.0, 3288.0],      [3289.0, 3290.0, 3291.0],      [3292.0, 3293.0, 3294.0],      [3295.0, 3296.0, 3297.0]],     [[3298.0, 3299.0, 3300.0],      [3301.0, 3302.0, 3303.0],      [3304.0, 3305.0, 3306.0],      [3307.0, 3308.0, 3309.0],      [3310.0, 3311.0, 3312.0],      [3313.0, 3314.0, 3315.0],      [3316.0, 3317.0, 3318.0]],     [[3319.0, 3320.0, 3321.0],      [3322.0, 3323.0, 3324.0],      [3325.0, 3326.0, 3327.0],      [3328.0, 3329.0, 3330.0],      [3331.0, 3332.0, 3333.0],      [3334.0, 3335.0, 3336.0],      [3337.0, 3338.0, 3339.0]]],    [[[3340.0, 3341.0, 3342.0],      [3343.0, 3344.0, 3345.0],      [3346.0, 3347.0, 3348.0],      [3349.0, 3350.0, 3351.0],      [3352.0, 3353.0, 3354.0],      [3355.0, 3356.0, 3357.0],      [3358.0, 3359.0, 3360.0]],     [[3361.0, 3362.0, 3363.0],      [3364.0, 3365.0, 3366.0],      [3367.0, 3368.0, 3369.0],      [3370.0, 3371.0, 3372.0],      [3373.0, 3374.0, 3375.0],      [3376.0, 3377.0, 3378.0],      [3379.0, 3380.0, 3381.0]],     [[3382.0, 3383.0, 3384.0],      [3385.0, 3386.0, 3387.0],      [3388.0, 3389.0, 3390.0],      [3391.0, 3392.0, 3393.0],      [3394.0, 3395.0, 3396.0],      [3397.0, 3398.0, 3399.0],      [3400.0, 3401.0, 3402.0]]],    [[[3403.0, 3404.0, 3405.0],      [3406.0, 3407.0, 3408.0],      [3409.0, 3410.0, 3411.0],      [3412.0, 3413.0, 3414.0],      [3415.0, 3416.0, 3417.0],      [3418.0, 3419.0, 3420.0],      [3421.0, 3422.0, 3423.0]],     [[3424.0, 3425.0, 3426.0],      [3427.0, 3428.0, 3429.0],      [3430.0, 3431.0, 3432.0],      [3433.0, 3434.0, 3435.0],      [3436.0, 3437.0, 3438.0],      [3439.0, 3440.0, 3441.0],      [3442.0, 3443.0, 3444.0]],     [[3445.0, 3446.0, 3447.0],      [3448.0, 3449.0, 3450.0],      [3451.0, 3452.0, 3453.0],      [3454.0, 3455.0, 3456.0],      [3457.0, 3458.0, 3459.0],      [3460.0, 3461.0, 3462.0],      [3463.0, 3464.0, 3465.0]]],    [[[3466.0, 3467.0, 3468.0],      [3469.0, 3470.0, 3471.0],      [3472.0, 3473.0, 3474.0],      [3475.0, 3476.0, 3477.0],      [3478.0, 3479.0, 3480.0],      [3481.0, 3482.0, 3483.0],      [3484.0, 3485.0, 3486.0]],     [[3487.0, 3488.0, 3489.0],      [3490.0, 3491.0, 3492.0],      [3493.0, 3494.0, 3495.0],      [3496.0, 3497.0, 3498.0],      [3499.0, 3500.0, 3501.0],      [3502.0, 3503.0, 3504.0],      [3505.0, 3506.0, 3507.0]],     [[3508.0, 3509.0, 3510.0],      [3511.0, 3512.0, 3513.0],      [3514.0, 3515.0, 3516.0],      [3517.0, 3518.0, 3519.0],      [3520.0, 3521.0, 3522.0],      [3523.0, 3524.0, 3525.0],      [3526.0, 3527.0, 3528.0]]]],   [[[[3529.0, 3530.0, 3531.0],      [3532.0, 3533.0, 3534.0],      [3535.0, 3536.0, 3537.0],      [3538.0, 3539.0, 3540.0],      [3541.0, 3542.0, 3543.0],      [3544.0, 3545.0, 3546.0],      [3547.0, 3548.0, 3549.0]],     [[3550.0, 3551.0, 3552.0],      [3553.0, 3554.0, 3555.0],      [3556.0, 3557.0, 3558.0],      [3559.0, 3560.0, 3561.0],      [3562.0, 3563.0, 3564.0],      [3565.0, 3566.0, 3567.0],      [3568.0, 3569.0, 3570.0]],     [[3571.0, 3572.0, 3573.0],      [3574.0, 3575.0, 3576.0],      [3577.0, 3578.0, 3579.0],      [3580.0, 3581.0, 3582.0],      [3583.0, 3584.0, 3585.0],      [3586.0, 3587.0, 3588.0],      [3589.0, 3590.0, 3591.0]]],    [[[3592.0, 3593.0, 3594.0],      [3595.0, 3596.0, 3597.0],      [3598.0, 3599.0, 3600.0],      [3601.0, 3602.0, 3603.0],      [3604.0, 3605.0, 3606.0],      [3607.0, 3608.0, 3609.0],      [3610.0, 3611.0, 3612.0]],     [[3613.0, 3614.0, 3615.0],      [3616.0, 3617.0, 3618.0],      [3619.0, 3620.0, 3621.0],      [3622.0, 3623.0, 3624.0],      [3625.0, 3626.0, 3627.0],      [3628.0, 3629.0, 3630.0],      [3631.0, 3632.0, 3633.0]],     [[3634.0, 3635.0, 3636.0],      [3637.0, 3638.0, 3639.0],      [3640.0, 3641.0, 3642.0],      [3643.0, 3644.0, 3645.0],      [3646.0, 3647.0, 3648.0],      [3649.0, 3650.0, 3651.0],      [3652.0, 3653.0, 3654.0]]],    [[[3655.0, 3656.0, 3657.0],      [3658.0, 3659.0, 3660.0],      [3661.0, 3662.0, 3663.0],      [3664.0, 3665.0, 3666.0],      [3667.0, 3668.0, 3669.0],      [3670.0, 3671.0, 3672.0],      [3673.0, 3674.0, 3675.0]],     [[3676.0, 3677.0, 3678.0],      [3679.0, 3680.0, 3681.0],      [3682.0, 3683.0, 3684.0],      [3685.0, 3686.0, 3687.0],      [3688.0, 3689.0, 3690.0],      [3691.0, 3692.0, 3693.0],      [3694.0, 3695.0, 3696.0]],     [[3697.0, 3698.0, 3699.0],      [3700.0, 3701.0, 3702.0],      [3703.0, 3704.0, 3705.0],      [3706.0, 3707.0, 3708.0],      [3709.0, 3710.0, 3711.0],      [3712.0, 3713.0, 3714.0],      [3715.0, 3716.0, 3717.0]]],    [[[3718.0, 3719.0, 3720.0],      [3721.0, 3722.0, 3723.0],      [3724.0, 3725.0, 3726.0],      [3727.0, 3728.0, 3729.0],      [3730.0, 3731.0, 3732.0],      [3733.0, 3734.0, 3735.0],      [3736.0, 3737.0, 3738.0]],     [[3739.0, 3740.0, 3741.0],      [3742.0, 3743.0, 3744.0],      [3745.0, 3746.0, 3747.0],      [3748.0, 3749.0, 3750.0],      [3751.0, 3752.0, 3753.0],      [3754.0, 3755.0, 3756.0],      [3757.0, 3758.0, 3759.0]],     [[3760.0, 3761.0, 3762.0],      [3763.0, 3764.0, 3765.0],      [3766.0, 3767.0, 3768.0],      [3769.0, 3770.0, 3771.0],      [3772.0, 3773.0, 3774.0],      [3775.0, 3776.0, 3777.0],      [3778.0, 3779.0, 3780.0]]],    [[[3781.0, 3782.0, 3783.0],      [3784.0, 3785.0, 3786.0],      [3787.0, 3788.0, 3789.0],      [3790.0, 3791.0, 3792.0],      [3793.0, 3794.0, 3795.0],      [3796.0, 3797.0, 3798.0],      [3799.0, 3800.0, 3801.0]],     [[3802.0, 3803.0, 3804.0],      [3805.0, 3806.0, 3807.0],      [3808.0, 3809.0, 3810.0],      [3811.0, 3812.0, 3813.0],      [3814.0, 3815.0, 3816.0],      [3817.0, 3818.0, 3819.0],      [3820.0, 3821.0, 3822.0]],     [[3823.0, 3824.0, 3825.0],      [3826.0, 3827.0, 3828.0],      [3829.0, 3830.0, 3831.0],      [3832.0, 3833.0, 3834.0],      [3835.0, 3836.0, 3837.0],      [3838.0, 3839.0, 3840.0],      [3841.0, 3842.0, 3843.0]]],    [[[3844.0, 3845.0, 3846.0],      [3847.0, 3848.0, 3849.0],      [3850.0, 3851.0, 3852.0],      [3853.0, 3854.0, 3855.0],      [3856.0, 3857.0, 3858.0],      [3859.0, 3860.0, 3861.0],      [3862.0, 3863.0, 3864.0]],     [[3865.0, 3866.0, 3867.0],      [3868.0, 3869.0, 3870.0],      [3871.0, 3872.0, 3873.0],      [3874.0, 3875.0, 3876.0],      [3877.0, 3878.0, 3879.0],      [3880.0, 3881.0, 3882.0],      [3883.0, 3884.0, 3885.0]],     [[3886.0, 3887.0, 3888.0],      [3889.0, 3890.0, 3891.0],      [3892.0, 3893.0, 3894.0],      [3895.0, 3896.0, 3897.0],      [3898.0, 3899.0, 3900.0],      [3901.0, 3902.0, 3903.0],      [3904.0, 3905.0, 3906.0]]],    [[[3907.0, 3908.0, 3909.0],      [3910.0, 3911.0, 3912.0],      [3913.0, 3914.0, 3915.0],      [3916.0, 3917.0, 3918.0],      [3919.0, 3920.0, 3921.0],      [3922.0, 3923.0, 3924.0],      [3925.0, 3926.0, 3927.0]],     [[3928.0, 3929.0, 3930.0],      [3931.0, 3932.0, 3933.0],      [3934.0, 3935.0, 3936.0],      [3937.0, 3938.0, 3939.0],      [3940.0, 3941.0, 3942.0],      [3943.0, 3944.0, 3945.0],      [3946.0, 3947.0, 3948.0]],     [[3949.0, 3950.0, 3951.0],      [3952.0, 3953.0, 3954.0],      [3955.0, 3956.0, 3957.0],      [3958.0, 3959.0, 3960.0],      [3961.0, 3962.0, 3963.0],      [3964.0, 3965.0, 3966.0],      [3967.0, 3968.0, 3969.0]]]],   [[[[3970.0, 3971.0, 3972.0],      [3973.0, 3974.0, 3975.0],      [3976.0, 3977.0, 3978.0],      [3979.0, 3980.0, 3981.0],      [3982.0, 3983.0, 3984.0],      [3985.0, 3986.0, 3987.0],      [3988.0, 3989.0, 3990.0]],     [[3991.0, 3992.0, 3993.0],      [3994.0, 3995.0, 3996.0],      [3997.0, 3998.0, 3999.0],      [4000.0, 4001.0, 4002.0],      [4003.0, 4004.0, 4005.0],      [4006.0, 4007.0, 4008.0],      [4009.0, 4010.0, 4011.0]],     [[4012.0, 4013.0, 4014.0],      [4015.0, 4016.0, 4017.0],      [4018.0, 4019.0, 4020.0],      [4021.0, 4022.0, 4023.0],      [4024.0, 4025.0, 4026.0],      [4027.0, 4028.0, 4029.0],      [4030.0, 4031.0, 4032.0]]],    [[[4033.0, 4034.0, 4035.0],      [4036.0, 4037.0, 4038.0],      [4039.0, 4040.0, 4041.0],      [4042.0, 4043.0, 4044.0],      [4045.0, 4046.0, 4047.0],      [4048.0, 4049.0, 4050.0],      [4051.0, 4052.0, 4053.0]],     [[4054.0, 4055.0, 4056.0],      [4057.0, 4058.0, 4059.0],      [4060.0, 4061.0, 4062.0],      [4063.0, 4064.0, 4065.0],      [4066.0, 4067.0, 4068.0],      [4069.0, 4070.0, 4071.0],      [4072.0, 4073.0, 4074.0]],     [[4075.0, 4076.0, 4077.0],      [4078.0, 4079.0, 4080.0],      [4081.0, 4082.0, 4083.0],      [4084.0, 4085.0, 4086.0],      [4087.0, 4088.0, 4089.0],      [4090.0, 4091.0, 4092.0],      [4093.0, 4094.0, 4095.0]]],    [[[4096.0, 4097.0, 4098.0],      [4099.0, 4100.0, 4101.0],      [4102.0, 4103.0, 4104.0],      [4105.0, 4106.0, 4107.0],      [4108.0, 4109.0, 4110.0],      [4111.0, 4112.0, 4113.0],      [4114.0, 4115.0, 4116.0]],     [[4117.0, 4118.0, 4119.0],      [4120.0, 4121.0, 4122.0],      [4123.0, 4124.0, 4125.0],      [4126.0, 4127.0, 4128.0],      [4129.0, 4130.0, 4131.0],      [4132.0, 4133.0, 4134.0],      [4135.0, 4136.0, 4137.0]],     [[4138.0, 4139.0, 4140.0],      [4141.0, 4142.0, 4143.0],      [4144.0, 4145.0, 4146.0],      [4147.0, 4148.0, 4149.0],      [4150.0, 4151.0, 4152.0],      [4153.0, 4154.0, 4155.0],      [4156.0, 4157.0, 4158.0]]],    [[[4159.0, 4160.0, 4161.0],      [4162.0, 4163.0, 4164.0],      [4165.0, 4166.0, 4167.0],      [4168.0, 4169.0, 4170.0],      [4171.0, 4172.0, 4173.0],      [4174.0, 4175.0, 4176.0],      [4177.0, 4178.0, 4179.0]],     [[4180.0, 4181.0, 4182.0],      [4183.0, 4184.0, 4185.0],      [4186.0, 4187.0, 4188.0],      [4189.0, 4190.0, 4191.0],      [4192.0, 4193.0, 4194.0],      [4195.0, 4196.0, 4197.0],      [4198.0, 4199.0, 4200.0]],     [[4201.0, 4202.0, 4203.0],      [4204.0, 4205.0, 4206.0],      [4207.0, 4208.0, 4209.0],      [4210.0, 4211.0, 4212.0],      [4213.0, 4214.0, 4215.0],      [4216.0, 4217.0, 4218.0],      [4219.0, 4220.0, 4221.0]]],    [[[4222.0, 4223.0, 4224.0],      [4225.0, 4226.0, 4227.0],      [4228.0, 4229.0, 4230.0],      [4231.0, 4232.0, 4233.0],      [4234.0, 4235.0, 4236.0],      [4237.0, 4238.0, 4239.0],      [4240.0, 4241.0, 4242.0]],     [[4243.0, 4244.0, 4245.0],      [4246.0, 4247.0, 4248.0],      [4249.0, 4250.0, 4251.0],      [4252.0, 4253.0, 4254.0],      [4255.0, 4256.0, 4257.0],      [4258.0, 4259.0, 4260.0],      [4261.0, 4262.0, 4263.0]],     [[4264.0, 4265.0, 4266.0],      [4267.0, 4268.0, 4269.0],      [4270.0, 4271.0, 4272.0],      [4273.0, 4274.0, 4275.0],      [4276.0, 4277.0, 4278.0],      [4279.0, 4280.0, 4281.0],      [4282.0, 4283.0, 4284.0]]],    [[[4285.0, 4286.0, 4287.0],      [4288.0, 4289.0, 4290.0],      [4291.0, 4292.0, 4293.0],      [4294.0, 4295.0, 4296.0],      [4297.0, 4298.0, 4299.0],      [4300.0, 4301.0, 4302.0],      [4303.0, 4304.0, 4305.0]],     [[4306.0, 4307.0, 4308.0],      [4309.0, 4310.0, 4311.0],      [4312.0, 4313.0, 4314.0],      [4315.0, 4316.0, 4317.0],      [4318.0, 4319.0, 4320.0],      [4321.0, 4322.0, 4323.0],      [4324.0, 4325.0, 4326.0]],     [[4327.0, 4328.0, 4329.0],      [4330.0, 4331.0, 4332.0],      [4333.0, 4334.0, 4335.0],      [4336.0, 4337.0, 4338.0],      [4339.0, 4340.0, 4341.0],      [4342.0, 4343.0, 4344.0],      [4345.0, 4346.0, 4347.0]]],    [[[4348.0, 4349.0, 4350.0],      [4351.0, 4352.0, 4353.0],      [4354.0, 4355.0, 4356.0],      [4357.0, 4358.0, 4359.0],      [4360.0, 4361.0, 4362.0],      [4363.0, 4364.0, 4365.0],      [4366.0, 4367.0, 4368.0]],     [[4369.0, 4370.0, 4371.0],      [4372.0, 4373.0, 4374.0],      [4375.0, 4376.0, 4377.0],      [4378.0, 4379.0, 4380.0],      [4381.0, 4382.0, 4383.0],      [4384.0, 4385.0, 4386.0],      [4387.0, 4388.0, 4389.0]],     [[4390.0, 4391.0, 4392.0],      [4393.0, 4394.0, 4395.0],      [4396.0, 4397.0, 4398.0],      [4399.0, 4400.0, 4401.0],      [4402.0, 4403.0, 4404.0],      [4405.0, 4406.0, 4407.0],      [4408.0, 4409.0, 4410.0]]]],   [[[[4411.0, 4412.0, 4413.0],      [4414.0, 4415.0, 4416.0],      [4417.0, 4418.0, 4419.0],      [4420.0, 4421.0, 4422.0],      [4423.0, 4424.0, 4425.0],      [4426.0, 4427.0, 4428.0],      [4429.0, 4430.0, 4431.0]],     [[4432.0, 4433.0, 4434.0],      [4435.0, 4436.0, 4437.0],      [4438.0, 4439.0, 4440.0],      [4441.0, 4442.0, 4443.0],      [4444.0, 4445.0, 4446.0],      [4447.0, 4448.0, 4449.0],      [4450.0, 4451.0, 4452.0]],     [[4453.0, 4454.0, 4455.0],      [4456.0, 4457.0, 4458.0],      [4459.0, 4460.0, 4461.0],      [4462.0, 4463.0, 4464.0],      [4465.0, 4466.0, 4467.0],      [4468.0, 4469.0, 4470.0],      [4471.0, 4472.0, 4473.0]]],    [[[4474.0, 4475.0, 4476.0],      [4477.0, 4478.0, 4479.0],      [4480.0, 4481.0, 4482.0],      [4483.0, 4484.0, 4485.0],      [4486.0, 4487.0, 4488.0],      [4489.0, 4490.0, 4491.0],      [4492.0, 4493.0, 4494.0]],     [[4495.0, 4496.0, 4497.0],      [4498.0, 4499.0, 4500.0],      [4501.0, 4502.0, 4503.0],      [4504.0, 4505.0, 4506.0],      [4507.0, 4508.0, 4509.0],      [4510.0, 4511.0, 4512.0],      [4513.0, 4514.0, 4515.0]],     [[4516.0, 4517.0, 4518.0],      [4519.0, 4520.0, 4521.0],      [4522.0, 4523.0, 4524.0],      [4525.0, 4526.0, 4527.0],      [4528.0, 4529.0, 4530.0],      [4531.0, 4532.0, 4533.0],      [4534.0, 4535.0, 4536.0]]],    [[[4537.0, 4538.0, 4539.0],      [4540.0, 4541.0, 4542.0],      [4543.0, 4544.0, 4545.0],      [4546.0, 4547.0, 4548.0],      [4549.0, 4550.0, 4551.0],      [4552.0, 4553.0, 4554.0],      [4555.0, 4556.0, 4557.0]],     [[4558.0, 4559.0, 4560.0],      [4561.0, 4562.0, 4563.0],      [4564.0, 4565.0, 4566.0],      [4567.0, 4568.0, 4569.0],      [4570.0, 4571.0, 4572.0],      [4573.0, 4574.0, 4575.0],      [4576.0, 4577.0, 4578.0]],     [[4579.0, 4580.0, 4581.0],      [4582.0, 4583.0, 4584.0],      [4585.0, 4586.0, 4587.0],      [4588.0, 4589.0, 4590.0],      [4591.0, 4592.0, 4593.0],      [4594.0, 4595.0, 4596.0],      [4597.0, 4598.0, 4599.0]]],    [[[4600.0, 4601.0, 4602.0],      [4603.0, 4604.0, 4605.0],      [4606.0, 4607.0, 4608.0],      [4609.0, 4610.0, 4611.0],      [4612.0, 4613.0, 4614.0],      [4615.0, 4616.0, 4617.0],      [4618.0, 4619.0, 4620.0]],     [[4621.0, 4622.0, 4623.0],      [4624.0, 4625.0, 4626.0],      [4627.0, 4628.0, 4629.0],      [4630.0, 4631.0, 4632.0],      [4633.0, 4634.0, 4635.0],      [4636.0, 4637.0, 4638.0],      [4639.0, 4640.0, 4641.0]],     [[4642.0, 4643.0, 4644.0],      [4645.0, 4646.0, 4647.0],      [4648.0, 4649.0, 4650.0],      [4651.0, 4652.0, 4653.0],      [4654.0, 4655.0, 4656.0],      [4657.0, 4658.0, 4659.0],      [4660.0, 4661.0, 4662.0]]],    [[[4663.0, 4664.0, 4665.0],      [4666.0, 4667.0, 4668.0],      [4669.0, 4670.0, 4671.0],      [4672.0, 4673.0, 4674.0],      [4675.0, 4676.0, 4677.0],      [4678.0, 4679.0, 4680.0],      [4681.0, 4682.0, 4683.0]],     [[4684.0, 4685.0, 4686.0],      [4687.0, 4688.0, 4689.0],      [4690.0, 4691.0, 4692.0],      [4693.0, 4694.0, 4695.0],      [4696.0, 4697.0, 4698.0],      [4699.0, 4700.0, 4701.0],      [4702.0, 4703.0, 4704.0]],     [[4705.0, 4706.0, 4707.0],      [4708.0, 4709.0, 4710.0],      [4711.0, 4712.0, 4713.0],      [4714.0, 4715.0, 4716.0],      [4717.0, 4718.0, 4719.0],      [4720.0, 4721.0, 4722.0],      [4723.0, 4724.0, 4725.0]]],    [[[4726.0, 4727.0, 4728.0],      [4729.0, 4730.0, 4731.0],      [4732.0, 4733.0, 4734.0],      [4735.0, 4736.0, 4737.0],      [4738.0, 4739.0, 4740.0],      [4741.0, 4742.0, 4743.0],      [4744.0, 4745.0, 4746.0]],     [[4747.0, 4748.0, 4749.0],      [4750.0, 4751.0, 4752.0],      [4753.0, 4754.0, 4755.0],      [4756.0, 4757.0, 4758.0],      [4759.0, 4760.0, 4761.0],      [4762.0, 4763.0, 4764.0],      [4765.0, 4766.0, 4767.0]],     [[4768.0, 4769.0, 4770.0],      [4771.0, 4772.0, 4773.0],      [4774.0, 4775.0, 4776.0],      [4777.0, 4778.0, 4779.0],      [4780.0, 4781.0, 4782.0],      [4783.0, 4784.0, 4785.0],      [4786.0, 4787.0, 4788.0]]],    [[[4789.0, 4790.0, 4791.0],      [4792.0, 4793.0, 4794.0],      [4795.0, 4796.0, 4797.0],      [4798.0, 4799.0, 4800.0],      [4801.0, 4802.0, 4803.0],      [4804.0, 4805.0, 4806.0],      [4807.0, 4808.0, 4809.0]],     [[4810.0, 4811.0, 4812.0],      [4813.0, 4814.0, 4815.0],      [4816.0, 4817.0, 4818.0],      [4819.0, 4820.0, 4821.0],      [4822.0, 4823.0, 4824.0],      [4825.0, 4826.0, 4827.0],      [4828.0, 4829.0, 4830.0]],     [[4831.0, 4832.0, 4833.0],      [4834.0, 4835.0, 4836.0],      [4837.0, 4838.0, 4839.0],      [4840.0, 4841.0, 4842.0],      [4843.0, 4844.0, 4845.0],      [4846.0, 4847.0, 4848.0],      [4849.0, 4850.0, 4851.0]]]],   [[[[4852.0, 4853.0, 4854.0],      [4855.0, 4856.0, 4857.0],      [4858.0, 4859.0, 4860.0],      [4861.0, 4862.0, 4863.0],      [4864.0, 4865.0, 4866.0],      [4867.0, 4868.0, 4869.0],      [4870.0, 4871.0, 4872.0]],     [[4873.0, 4874.0, 4875.0],      [4876.0, 4877.0, 4878.0],      [4879.0, 4880.0, 4881.0],      [4882.0, 4883.0, 4884.0],      [4885.0, 4886.0, 4887.0],      [4888.0, 4889.0, 4890.0],      [4891.0, 4892.0, 4893.0]],     [[4894.0, 4895.0, 4896.0],      [4897.0, 4898.0, 4899.0],      [4900.0, 4901.0, 4902.0],      [4903.0, 4904.0, 4905.0],      [4906.0, 4907.0, 4908.0],      [4909.0, 4910.0, 4911.0],      [4912.0, 4913.0, 4914.0]]],    [[[4915.0, 4916.0, 4917.0],      [4918.0, 4919.0, 4920.0],      [4921.0, 4922.0, 4923.0],      [4924.0, 4925.0, 4926.0],      [4927.0, 4928.0, 4929.0],      [4930.0, 4931.0, 4932.0],      [4933.0, 4934.0, 4935.0]],     [[4936.0, 4937.0, 4938.0],      [4939.0, 4940.0, 4941.0],      [4942.0, 4943.0, 4944.0],      [4945.0, 4946.0, 4947.0],      [4948.0, 4949.0, 4950.0],      [4951.0, 4952.0, 4953.0],      [4954.0, 4955.0, 4956.0]],     [[4957.0, 4958.0, 4959.0],      [4960.0, 4961.0, 4962.0],      [4963.0, 4964.0, 4965.0],      [4966.0, 4967.0, 4968.0],      [4969.0, 4970.0, 4971.0],      [4972.0, 4973.0, 4974.0],      [4975.0, 4976.0, 4977.0]]],    [[[4978.0, 4979.0, 4980.0],      [4981.0, 4982.0, 4983.0],      [4984.0, 4985.0, 4986.0],      [4987.0, 4988.0, 4989.0],      [4990.0, 4991.0, 4992.0],      [4993.0, 4994.0, 4995.0],      [4996.0, 4997.0, 4998.0]],     [[4999.0, 5000.0, 5001.0],      [5002.0, 5003.0, 5004.0],      [5005.0, 5006.0, 5007.0],      [5008.0, 5009.0, 5010.0],      [5011.0, 5012.0, 5013.0],      [5014.0, 5015.0, 5016.0],      [5017.0, 5018.0, 5019.0]],     [[5020.0, 5021.0, 5022.0],      [5023.0, 5024.0, 5025.0],      [5026.0, 5027.0, 5028.0],      [5029.0, 5030.0, 5031.0],      [5032.0, 5033.0, 5034.0],      [5035.0, 5036.0, 5037.0],      [5038.0, 5039.0, 5040.0]]],    [[[5041.0, 5042.0, 5043.0],      [5044.0, 5045.0, 5046.0],      [5047.0, 5048.0, 5049.0],      [5050.0, 5051.0, 5052.0],      [5053.0, 5054.0, 5055.0],      [5056.0, 5057.0, 5058.0],      [5059.0, 5060.0, 5061.0]],     [[5062.0, 5063.0, 5064.0],      [5065.0, 5066.0, 5067.0],      [5068.0, 5069.0, 5070.0],      [5071.0, 5072.0, 5073.0],      [5074.0, 5075.0, 5076.0],      [5077.0, 5078.0, 5079.0],      [5080.0, 5081.0, 5082.0]],     [[5083.0, 5084.0, 5085.0],      [5086.0, 5087.0, 5088.0],      [5089.0, 5090.0, 5091.0],      [5092.0, 5093.0, 5094.0],      [5095.0, 5096.0, 5097.0],      [5098.0, 5099.0, 5100.0],      [5101.0, 5102.0, 5103.0]]],    [[[5104.0, 5105.0, 5106.0],      [5107.0, 5108.0, 5109.0],      [5110.0, 5111.0, 5112.0],      [5113.0, 5114.0, 5115.0],      [5116.0, 5117.0, 5118.0],      [5119.0, 5120.0, 5121.0],      [5122.0, 5123.0, 5124.0]],     [[5125.0, 5126.0, 5127.0],      [5128.0, 5129.0, 5130.0],      [5131.0, 5132.0, 5133.0],      [5134.0, 5135.0, 5136.0],      [5137.0, 5138.0, 5139.0],      [5140.0, 5141.0, 5142.0],      [5143.0, 5144.0, 5145.0]],     [[5146.0, 5147.0, 5148.0],      [5149.0, 5150.0, 5151.0],      [5152.0, 5153.0, 5154.0],      [5155.0, 5156.0, 5157.0],      [5158.0, 5159.0, 5160.0],      [5161.0, 5162.0, 5163.0],      [5164.0, 5165.0, 5166.0]]],    [[[5167.0, 5168.0, 5169.0],      [5170.0, 5171.0, 5172.0],      [5173.0, 5174.0, 5175.0],      [5176.0, 5177.0, 5178.0],      [5179.0, 5180.0, 5181.0],      [5182.0, 5183.0, 5184.0],      [5185.0, 5186.0, 5187.0]],     [[5188.0, 5189.0, 5190.0],      [5191.0, 5192.0, 5193.0],      [5194.0, 5195.0, 5196.0],      [5197.0, 5198.0, 5199.0],      [5200.0, 5201.0, 5202.0],      [5203.0, 5204.0, 5205.0],      [5206.0, 5207.0, 5208.0]],     [[5209.0, 5210.0, 5211.0],      [5212.0, 5213.0, 5214.0],      [5215.0, 5216.0, 5217.0],      [5218.0, 5219.0, 5220.0],      [5221.0, 5222.0, 5223.0],      [5224.0, 5225.0, 5226.0],      [5227.0, 5228.0, 5229.0]]],    [[[5230.0, 5231.0, 5232.0],      [5233.0, 5234.0, 5235.0],      [5236.0, 5237.0, 5238.0],      [5239.0, 5240.0, 5241.0],      [5242.0, 5243.0, 5244.0],      [5245.0, 5246.0, 5247.0],      [5248.0, 5249.0, 5250.0]],     [[5251.0, 5252.0, 5253.0],      [5254.0, 5255.0, 5256.0],      [5257.0, 5258.0, 5259.0],      [5260.0, 5261.0, 5262.0],      [5263.0, 5264.0, 5265.0],      [5266.0, 5267.0, 5268.0],      [5269.0, 5270.0, 5271.0]],     [[5272.0, 5273.0, 5274.0],      [5275.0, 5276.0, 5277.0],      [5278.0, 5279.0, 5280.0],      [5281.0, 5282.0, 5283.0],      [5284.0, 5285.0, 5286.0],      [5287.0, 5288.0, 5289.0],      [5290.0, 5291.0, 5292.0]]]]],  [[[[[5293.0, 5294.0, 5295.0],      [5296.0, 5297.0, 5298.0],      [5299.0, 5300.0, 5301.0],      [5302.0, 5303.0, 5304.0],      [5305.0, 5306.0, 5307.0],      [5308.0, 5309.0, 5310.0],      [5311.0, 5312.0, 5313.0]],     [[5314.0, 5315.0, 5316.0],      [5317.0, 5318.0, 5319.0],      [5320.0, 5321.0, 5322.0],      [5323.0, 5324.0, 5325.0],      [5326.0, 5327.0, 5328.0],      [5329.0, 5330.0, 5331.0],      [5332.0, 5333.0, 5334.0]],     [[5335.0, 5336.0, 5337.0],      [5338.0, 5339.0, 5340.0],      [5341.0, 5342.0, 5343.0],      [5344.0, 5345.0, 5346.0],      [5347.0, 5348.0, 5349.0],      [5350.0, 5351.0, 5352.0],      [5353.0, 5354.0, 5355.0]]],    [[[5356.0, 5357.0, 5358.0],      [5359.0, 5360.0, 5361.0],      [5362.0, 5363.0, 5364.0],      [5365.0, 5366.0, 5367.0],      [5368.0, 5369.0, 5370.0],      [5371.0, 5372.0, 5373.0],      [5374.0, 5375.0, 5376.0]],     [[5377.0, 5378.0, 5379.0],      [5380.0, 5381.0, 5382.0],      [5383.0, 5384.0, 5385.0],      [5386.0, 5387.0, 5388.0],      [5389.0, 5390.0, 5391.0],      [5392.0, 5393.0, 5394.0],      [5395.0, 5396.0, 5397.0]],     [[5398.0, 5399.0, 5400.0],      [5401.0, 5402.0, 5403.0],      [5404.0, 5405.0, 5406.0],      [5407.0, 5408.0, 5409.0],      [5410.0, 5411.0, 5412.0],      [5413.0, 5414.0, 5415.0],      [5416.0, 5417.0, 5418.0]]],    [[[5419.0, 5420.0, 5421.0],      [5422.0, 5423.0, 5424.0],      [5425.0, 5426.0, 5427.0],      [5428.0, 5429.0, 5430.0],      [5431.0, 5432.0, 5433.0],      [5434.0, 5435.0, 5436.0],      [5437.0, 5438.0, 5439.0]],     [[5440.0, 5441.0, 5442.0],      [5443.0, 5444.0, 5445.0],      [5446.0, 5447.0, 5448.0],      [5449.0, 5450.0, 5451.0],      [5452.0, 5453.0, 5454.0],      [5455.0, 5456.0, 5457.0],      [5458.0, 5459.0, 5460.0]],     [[5461.0, 5462.0, 5463.0],      [5464.0, 5465.0, 5466.0],      [5467.0, 5468.0, 5469.0],      [5470.0, 5471.0, 5472.0],      [5473.0, 5474.0, 5475.0],      [5476.0, 5477.0, 5478.0],      [5479.0, 5480.0, 5481.0]]],    [[[5482.0, 5483.0, 5484.0],      [5485.0, 5486.0, 5487.0],      [5488.0, 5489.0, 5490.0],      [5491.0, 5492.0, 5493.0],      [5494.0, 5495.0, 5496.0],      [5497.0, 5498.0, 5499.0],      [5500.0, 5501.0, 5502.0]],     [[5503.0, 5504.0, 5505.0],      [5506.0, 5507.0, 5508.0],      [5509.0, 5510.0, 5511.0],      [5512.0, 5513.0, 5514.0],      [5515.0, 5516.0, 5517.0],      [5518.0, 5519.0, 5520.0],      [5521.0, 5522.0, 5523.0]],     [[5524.0, 5525.0, 5526.0],      [5527.0, 5528.0, 5529.0],      [5530.0, 5531.0, 5532.0],      [5533.0, 5534.0, 5535.0],      [5536.0, 5537.0, 5538.0],      [5539.0, 5540.0, 5541.0],      [5542.0, 5543.0, 5544.0]]],    [[[5545.0, 5546.0, 5547.0],      [5548.0, 5549.0, 5550.0],      [5551.0, 5552.0, 5553.0],      [5554.0, 5555.0, 5556.0],      [5557.0, 5558.0, 5559.0],      [5560.0, 5561.0, 5562.0],      [5563.0, 5564.0, 5565.0]],     [[5566.0, 5567.0, 5568.0],      [5569.0, 5570.0, 5571.0],      [5572.0, 5573.0, 5574.0],      [5575.0, 5576.0, 5577.0],      [5578.0, 5579.0, 5580.0],      [5581.0, 5582.0, 5583.0],      [5584.0, 5585.0, 5586.0]],     [[5587.0, 5588.0, 5589.0],      [5590.0, 5591.0, 5592.0],      [5593.0, 5594.0, 5595.0],      [5596.0, 5597.0, 5598.0],      [5599.0, 5600.0, 5601.0],      [5602.0, 5603.0, 5604.0],      [5605.0, 5606.0, 5607.0]]],    [[[5608.0, 5609.0, 5610.0],      [5611.0, 5612.0, 5613.0],      [5614.0, 5615.0, 5616.0],      [5617.0, 5618.0, 5619.0],      [5620.0, 5621.0, 5622.0],      [5623.0, 5624.0, 5625.0],      [5626.0, 5627.0, 5628.0]],     [[5629.0, 5630.0, 5631.0],      [5632.0, 5633.0, 5634.0],      [5635.0, 5636.0, 5637.0],      [5638.0, 5639.0, 5640.0],      [5641.0, 5642.0, 5643.0],      [5644.0, 5645.0, 5646.0],      [5647.0, 5648.0, 5649.0]],     [[5650.0, 5651.0, 5652.0],      [5653.0, 5654.0, 5655.0],      [5656.0, 5657.0, 5658.0],      [5659.0, 5660.0, 5661.0],      [5662.0, 5663.0, 5664.0],      [5665.0, 5666.0, 5667.0],      [5668.0, 5669.0, 5670.0]]],    [[[5671.0, 5672.0, 5673.0],      [5674.0, 5675.0, 5676.0],      [5677.0, 5678.0, 5679.0],      [5680.0, 5681.0, 5682.0],      [5683.0, 5684.0, 5685.0],      [5686.0, 5687.0, 5688.0],      [5689.0, 5690.0, 5691.0]],     [[5692.0, 5693.0, 5694.0],      [5695.0, 5696.0, 5697.0],      [5698.0, 5699.0, 5700.0],      [5701.0, 5702.0, 5703.0],      [5704.0, 5705.0, 5706.0],      [5707.0, 5708.0, 5709.0],      [5710.0, 5711.0, 5712.0]],     [[5713.0, 5714.0, 5715.0],      [5716.0, 5717.0, 5718.0],      [5719.0, 5720.0, 5721.0],      [5722.0, 5723.0, 5724.0],      [5725.0, 5726.0, 5727.0],      [5728.0, 5729.0, 5730.0],      [5731.0, 5732.0, 5733.0]]]],   [[[[5734.0, 5735.0, 5736.0],      [5737.0, 5738.0, 5739.0],      [5740.0, 5741.0, 5742.0],      [5743.0, 5744.0, 5745.0],      [5746.0, 5747.0, 5748.0],      [5749.0, 5750.0, 5751.0],      [5752.0, 5753.0, 5754.0]],     [[5755.0, 5756.0, 5757.0],      [5758.0, 5759.0, 5760.0],      [5761.0, 5762.0, 5763.0],      [5764.0, 5765.0, 5766.0],      [5767.0, 5768.0, 5769.0],      [5770.0, 5771.0, 5772.0],      [5773.0, 5774.0, 5775.0]],     [[5776.0, 5777.0, 5778.0],      [5779.0, 5780.0, 5781.0],      [5782.0, 5783.0, 5784.0],      [5785.0, 5786.0, 5787.0],      [5788.0, 5789.0, 5790.0],      [5791.0, 5792.0, 5793.0],      [5794.0, 5795.0, 5796.0]]],    [[[5797.0, 5798.0, 5799.0],      [5800.0, 5801.0, 5802.0],      [5803.0, 5804.0, 5805.0],      [5806.0, 5807.0, 5808.0],      [5809.0, 5810.0, 5811.0],      [5812.0, 5813.0, 5814.0],      [5815.0, 5816.0, 5817.0]],     [[5818.0, 5819.0, 5820.0],      [5821.0, 5822.0, 5823.0],      [5824.0, 5825.0, 5826.0],      [5827.0, 5828.0, 5829.0],      [5830.0, 5831.0, 5832.0],      [5833.0, 5834.0, 5835.0],      [5836.0, 5837.0, 5838.0]],     [[5839.0, 5840.0, 5841.0],      [5842.0, 5843.0, 5844.0],      [5845.0, 5846.0, 5847.0],      [5848.0, 5849.0, 5850.0],      [5851.0, 5852.0, 5853.0],      [5854.0, 5855.0, 5856.0],      [5857.0, 5858.0, 5859.0]]],    [[[5860.0, 5861.0, 5862.0],      [5863.0, 5864.0, 5865.0],      [5866.0, 5867.0, 5868.0],      [5869.0, 5870.0, 5871.0],      [5872.0, 5873.0, 5874.0],      [5875.0, 5876.0, 5877.0],      [5878.0, 5879.0, 5880.0]],     [[5881.0, 5882.0, 5883.0],      [5884.0, 5885.0, 5886.0],      [5887.0, 5888.0, 5889.0],      [5890.0, 5891.0, 5892.0],      [5893.0, 5894.0, 5895.0],      [5896.0, 5897.0, 5898.0],      [5899.0, 5900.0, 5901.0]],     [[5902.0, 5903.0, 5904.0],      [5905.0, 5906.0, 5907.0],      [5908.0, 5909.0, 5910.0],      [5911.0, 5912.0, 5913.0],      [5914.0, 5915.0, 5916.0],      [5917.0, 5918.0, 5919.0],      [5920.0, 5921.0, 5922.0]]],    [[[5923.0, 5924.0, 5925.0],      [5926.0, 5927.0, 5928.0],      [5929.0, 5930.0, 5931.0],      [5932.0, 5933.0, 5934.0],      [5935.0, 5936.0, 5937.0],      [5938.0, 5939.0, 5940.0],      [5941.0, 5942.0, 5943.0]],     [[5944.0, 5945.0, 5946.0],      [5947.0, 5948.0, 5949.0],      [5950.0, 5951.0, 5952.0],      [5953.0, 5954.0, 5955.0],      [5956.0, 5957.0, 5958.0],      [5959.0, 5960.0, 5961.0],      [5962.0, 5963.0, 5964.0]],     [[5965.0, 5966.0, 5967.0],      [5968.0, 5969.0, 5970.0],      [5971.0, 5972.0, 5973.0],      [5974.0, 5975.0, 5976.0],      [5977.0, 5978.0, 5979.0],      [5980.0, 5981.0, 5982.0],      [5983.0, 5984.0, 5985.0]]],    [[[5986.0, 5987.0, 5988.0],      [5989.0, 5990.0, 5991.0],      [5992.0, 5993.0, 5994.0],      [5995.0, 5996.0, 5997.0],      [5998.0, 5999.0, 6000.0],      [6001.0, 6002.0, 6003.0],      [6004.0, 6005.0, 6006.0]],     [[6007.0, 6008.0, 6009.0],      [6010.0, 6011.0, 6012.0],      [6013.0, 6014.0, 6015.0],      [6016.0, 6017.0, 6018.0],      [6019.0, 6020.0, 6021.0],      [6022.0, 6023.0, 6024.0],      [6025.0, 6026.0, 6027.0]],     [[6028.0, 6029.0, 6030.0],      [6031.0, 6032.0, 6033.0],      [6034.0, 6035.0, 6036.0],      [6037.0, 6038.0, 6039.0],      [6040.0, 6041.0, 6042.0],      [6043.0, 6044.0, 6045.0],      [6046.0, 6047.0, 6048.0]]],    [[[6049.0, 6050.0, 6051.0],      [6052.0, 6053.0, 6054.0],      [6055.0, 6056.0, 6057.0],      [6058.0, 6059.0, 6060.0],      [6061.0, 6062.0, 6063.0],      [6064.0, 6065.0, 6066.0],      [6067.0, 6068.0, 6069.0]],     [[6070.0, 6071.0, 6072.0],      [6073.0, 6074.0, 6075.0],      [6076.0, 6077.0, 6078.0],      [6079.0, 6080.0, 6081.0],      [6082.0, 6083.0, 6084.0],      [6085.0, 6086.0, 6087.0],      [6088.0, 6089.0, 6090.0]],     [[6091.0, 6092.0, 6093.0],      [6094.0, 6095.0, 6096.0],      [6097.0, 6098.0, 6099.0],      [6100.0, 6101.0, 6102.0],      [6103.0, 6104.0, 6105.0],      [6106.0, 6107.0, 6108.0],      [6109.0, 6110.0, 6111.0]]],    [[[6112.0, 6113.0, 6114.0],      [6115.0, 6116.0, 6117.0],      [6118.0, 6119.0, 6120.0],      [6121.0, 6122.0, 6123.0],      [6124.0, 6125.0, 6126.0],      [6127.0, 6128.0, 6129.0],      [6130.0, 6131.0, 6132.0]],     [[6133.0, 6134.0, 6135.0],      [6136.0, 6137.0, 6138.0],      [6139.0, 6140.0, 6141.0],      [6142.0, 6143.0, 6144.0],      [6145.0, 6146.0, 6147.0],      [6148.0, 6149.0, 6150.0],      [6151.0, 6152.0, 6153.0]],     [[6154.0, 6155.0, 6156.0],      [6157.0, 6158.0, 6159.0],      [6160.0, 6161.0, 6162.0],      [6163.0, 6164.0, 6165.0],      [6166.0, 6167.0, 6168.0],      [6169.0, 6170.0, 6171.0],      [6172.0, 6173.0, 6174.0]]]],   [[[[6175.0, 6176.0, 6177.0],      [6178.0, 6179.0, 6180.0],      [6181.0, 6182.0, 6183.0],      [6184.0, 6185.0, 6186.0],      [6187.0, 6188.0, 6189.0],      [6190.0, 6191.0, 6192.0],      [6193.0, 6194.0, 6195.0]],     [[6196.0, 6197.0, 6198.0],      [6199.0, 6200.0, 6201.0],      [6202.0, 6203.0, 6204.0],      [6205.0, 6206.0, 6207.0],      [6208.0, 6209.0, 6210.0],      [6211.0, 6212.0, 6213.0],      [6214.0, 6215.0, 6216.0]],     [[6217.0, 6218.0, 6219.0],      [6220.0, 6221.0, 6222.0],      [6223.0, 6224.0, 6225.0],      [6226.0, 6227.0, 6228.0],      [6229.0, 6230.0, 6231.0],      [6232.0, 6233.0, 6234.0],      [6235.0, 6236.0, 6237.0]]],    [[[6238.0, 6239.0, 6240.0],      [6241.0, 6242.0, 6243.0],      [6244.0, 6245.0, 6246.0],      [6247.0, 6248.0, 6249.0],      [6250.0, 6251.0, 6252.0],      [6253.0, 6254.0, 6255.0],      [6256.0, 6257.0, 6258.0]],     [[6259.0, 6260.0, 6261.0],      [6262.0, 6263.0, 6264.0],      [6265.0, 6266.0, 6267.0],      [6268.0, 6269.0, 6270.0],      [6271.0, 6272.0, 6273.0],      [6274.0, 6275.0, 6276.0],      [6277.0, 6278.0, 6279.0]],     [[6280.0, 6281.0, 6282.0],      [6283.0, 6284.0, 6285.0],      [6286.0, 6287.0, 6288.0],      [6289.0, 6290.0, 6291.0],      [6292.0, 6293.0, 6294.0],      [6295.0, 6296.0, 6297.0],      [6298.0, 6299.0, 6300.0]]],    [[[6301.0, 6302.0, 6303.0],      [6304.0, 6305.0, 6306.0],      [6307.0, 6308.0, 6309.0],      [6310.0, 6311.0, 6312.0],      [6313.0, 6314.0, 6315.0],      [6316.0, 6317.0, 6318.0],      [6319.0, 6320.0, 6321.0]],     [[6322.0, 6323.0, 6324.0],      [6325.0, 6326.0, 6327.0],      [6328.0, 6329.0, 6330.0],      [6331.0, 6332.0, 6333.0],      [6334.0, 6335.0, 6336.0],      [6337.0, 6338.0, 6339.0],      [6340.0, 6341.0, 6342.0]],     [[6343.0, 6344.0, 6345.0],      [6346.0, 6347.0, 6348.0],      [6349.0, 6350.0, 6351.0],      [6352.0, 6353.0, 6354.0],      [6355.0, 6356.0, 6357.0],      [6358.0, 6359.0, 6360.0],      [6361.0, 6362.0, 6363.0]]],    [[[6364.0, 6365.0, 6366.0],      [6367.0, 6368.0, 6369.0],      [6370.0, 6371.0, 6372.0],      [6373.0, 6374.0, 6375.0],      [6376.0, 6377.0, 6378.0],      [6379.0, 6380.0, 6381.0],      [6382.0, 6383.0, 6384.0]],     [[6385.0, 6386.0, 6387.0],      [6388.0, 6389.0, 6390.0],      [6391.0, 6392.0, 6393.0],      [6394.0, 6395.0, 6396.0],      [6397.0, 6398.0, 6399.0],      [6400.0, 6401.0, 6402.0],      [6403.0, 6404.0, 6405.0]],     [[6406.0, 6407.0, 6408.0],      [6409.0, 6410.0, 6411.0],      [6412.0, 6413.0, 6414.0],      [6415.0, 6416.0, 6417.0],      [6418.0, 6419.0, 6420.0],      [6421.0, 6422.0, 6423.0],      [6424.0, 6425.0, 6426.0]]],    [[[6427.0, 6428.0, 6429.0],      [6430.0, 6431.0, 6432.0],      [6433.0, 6434.0, 6435.0],      [6436.0, 6437.0, 6438.0],      [6439.0, 6440.0, 6441.0],      [6442.0, 6443.0, 6444.0],      [6445.0, 6446.0, 6447.0]],     [[6448.0, 6449.0, 6450.0],      [6451.0, 6452.0, 6453.0],      [6454.0, 6455.0, 6456.0],      [6457.0, 6458.0, 6459.0],      [6460.0, 6461.0, 6462.0],      [6463.0, 6464.0, 6465.0],      [6466.0, 6467.0, 6468.0]],     [[6469.0, 6470.0, 6471.0],      [6472.0, 6473.0, 6474.0],      [6475.0, 6476.0, 6477.0],      [6478.0, 6479.0, 6480.0],      [6481.0, 6482.0, 6483.0],      [6484.0, 6485.0, 6486.0],      [6487.0, 6488.0, 6489.0]]],    [[[6490.0, 6491.0, 6492.0],      [6493.0, 6494.0, 6495.0],      [6496.0, 6497.0, 6498.0],      [6499.0, 6500.0, 6501.0],      [6502.0, 6503.0, 6504.0],      [6505.0, 6506.0, 6507.0],      [6508.0, 6509.0, 6510.0]],     [[6511.0, 6512.0, 6513.0],      [6514.0, 6515.0, 6516.0],      [6517.0, 6518.0, 6519.0],      [6520.0, 6521.0, 6522.0],      [6523.0, 6524.0, 6525.0],      [6526.0, 6527.0, 6528.0],      [6529.0, 6530.0, 6531.0]],     [[6532.0, 6533.0, 6534.0],      [6535.0, 6536.0, 6537.0],      [6538.0, 6539.0, 6540.0],      [6541.0, 6542.0, 6543.0],      [6544.0, 6545.0, 6546.0],      [6547.0, 6548.0, 6549.0],      [6550.0, 6551.0, 6552.0]]],    [[[6553.0, 6554.0, 6555.0],      [6556.0, 6557.0, 6558.0],      [6559.0, 6560.0, 6561.0],      [6562.0, 6563.0, 6564.0],      [6565.0, 6566.0, 6567.0],      [6568.0, 6569.0, 6570.0],      [6571.0, 6572.0, 6573.0]],     [[6574.0, 6575.0, 6576.0],      [6577.0, 6578.0, 6579.0],      [6580.0, 6581.0, 6582.0],      [6583.0, 6584.0, 6585.0],      [6586.0, 6587.0, 6588.0],      [6589.0, 6590.0, 6591.0],      [6592.0, 6593.0, 6594.0]],     [[6595.0, 6596.0, 6597.0],      [6598.0, 6599.0, 6600.0],      [6601.0, 6602.0, 6603.0],      [6604.0, 6605.0, 6606.0],      [6607.0, 6608.0, 6609.0],      [6610.0, 6611.0, 6612.0],      [6613.0, 6614.0, 6615.0]]]],   [[[[6616.0, 6617.0, 6618.0],      [6619.0, 6620.0, 6621.0],      [6622.0, 6623.0, 6624.0],      [6625.0, 6626.0, 6627.0],      [6628.0, 6629.0, 6630.0],      [6631.0, 6632.0, 6633.0],      [6634.0, 6635.0, 6636.0]],     [[6637.0, 6638.0, 6639.0],      [6640.0, 6641.0, 6642.0],      [6643.0, 6644.0, 6645.0],      [6646.0, 6647.0, 6648.0],      [6649.0, 6650.0, 6651.0],      [6652.0, 6653.0, 6654.0],      [6655.0, 6656.0, 6657.0]],     [[6658.0, 6659.0, 6660.0],      [6661.0, 6662.0, 6663.0],      [6664.0, 6665.0, 6666.0],      [6667.0, 6668.0, 6669.0],      [6670.0, 6671.0, 6672.0],      [6673.0, 6674.0, 6675.0],      [6676.0, 6677.0, 6678.0]]],    [[[6679.0, 6680.0, 6681.0],      [6682.0, 6683.0, 6684.0],      [6685.0, 6686.0, 6687.0],      [6688.0, 6689.0, 6690.0],      [6691.0, 6692.0, 6693.0],      [6694.0, 6695.0, 6696.0],      [6697.0, 6698.0, 6699.0]],     [[6700.0, 6701.0, 6702.0],      [6703.0, 6704.0, 6705.0],      [6706.0, 6707.0, 6708.0],      [6709.0, 6710.0, 6711.0],      [6712.0, 6713.0, 6714.0],      [6715.0, 6716.0, 6717.0],      [6718.0, 6719.0, 6720.0]],     [[6721.0, 6722.0, 6723.0],      [6724.0, 6725.0, 6726.0],      [6727.0, 6728.0, 6729.0],      [6730.0, 6731.0, 6732.0],      [6733.0, 6734.0, 6735.0],      [6736.0, 6737.0, 6738.0],      [6739.0, 6740.0, 6741.0]]],    [[[6742.0, 6743.0, 6744.0],      [6745.0, 6746.0, 6747.0],      [6748.0, 6749.0, 6750.0],      [6751.0, 6752.0, 6753.0],      [6754.0, 6755.0, 6756.0],      [6757.0, 6758.0, 6759.0],      [6760.0, 6761.0, 6762.0]],     [[6763.0, 6764.0, 6765.0],      [6766.0, 6767.0, 6768.0],      [6769.0, 6770.0, 6771.0],      [6772.0, 6773.0, 6774.0],      [6775.0, 6776.0, 6777.0],      [6778.0, 6779.0, 6780.0],      [6781.0, 6782.0, 6783.0]],     [[6784.0, 6785.0, 6786.0],      [6787.0, 6788.0, 6789.0],      [6790.0, 6791.0, 6792.0],      [6793.0, 6794.0, 6795.0],      [6796.0, 6797.0, 6798.0],      [6799.0, 6800.0, 6801.0],      [6802.0, 6803.0, 6804.0]]],    [[[6805.0, 6806.0, 6807.0],      [6808.0, 6809.0, 6810.0],      [6811.0, 6812.0, 6813.0],      [6814.0, 6815.0, 6816.0],      [6817.0, 6818.0, 6819.0],      [6820.0, 6821.0, 6822.0],      [6823.0, 6824.0, 6825.0]],     [[6826.0, 6827.0, 6828.0],      [6829.0, 6830.0, 6831.0],      [6832.0, 6833.0, 6834.0],      [6835.0, 6836.0, 6837.0],      [6838.0, 6839.0, 6840.0],      [6841.0, 6842.0, 6843.0],      [6844.0, 6845.0, 6846.0]],     [[6847.0, 6848.0, 6849.0],      [6850.0, 6851.0, 6852.0],      [6853.0, 6854.0, 6855.0],      [6856.0, 6857.0, 6858.0],      [6859.0, 6860.0, 6861.0],      [6862.0, 6863.0, 6864.0],      [6865.0, 6866.0, 6867.0]]],    [[[6868.0, 6869.0, 6870.0],      [6871.0, 6872.0, 6873.0],      [6874.0, 6875.0, 6876.0],      [6877.0, 6878.0, 6879.0],      [6880.0, 6881.0, 6882.0],      [6883.0, 6884.0, 6885.0],      [6886.0, 6887.0, 6888.0]],     [[6889.0, 6890.0, 6891.0],      [6892.0, 6893.0, 6894.0],      [6895.0, 6896.0, 6897.0],      [6898.0, 6899.0, 6900.0],      [6901.0, 6902.0, 6903.0],      [6904.0, 6905.0, 6906.0],      [6907.0, 6908.0, 6909.0]],     [[6910.0, 6911.0, 6912.0],      [6913.0, 6914.0, 6915.0],      [6916.0, 6917.0, 6918.0],      [6919.0, 6920.0, 6921.0],      [6922.0, 6923.0, 6924.0],      [6925.0, 6926.0, 6927.0],      [6928.0, 6929.0, 6930.0]]],    [[[6931.0, 6932.0, 6933.0],      [6934.0, 6935.0, 6936.0],      [6937.0, 6938.0, 6939.0],      [6940.0, 6941.0, 6942.0],      [6943.0, 6944.0, 6945.0],      [6946.0, 6947.0, 6948.0],      [6949.0, 6950.0, 6951.0]],     [[6952.0, 6953.0, 6954.0],      [6955.0, 6956.0, 6957.0],      [6958.0, 6959.0, 6960.0],      [6961.0, 6962.0, 6963.0],      [6964.0, 6965.0, 6966.0],      [6967.0, 6968.0, 6969.0],      [6970.0, 6971.0, 6972.0]],     [[6973.0, 6974.0, 6975.0],      [6976.0, 6977.0, 6978.0],      [6979.0, 6980.0, 6981.0],      [6982.0, 6983.0, 6984.0],      [6985.0, 6986.0, 6987.0],      [6988.0, 6989.0, 6990.0],      [6991.0, 6992.0, 6993.0]]],    [[[6994.0, 6995.0, 6996.0],      [6997.0, 6998.0, 6999.0],      [7000.0, 7001.0, 7002.0],      [7003.0, 7004.0, 7005.0],      [7006.0, 7007.0, 7008.0],      [7009.0, 7010.0, 7011.0],      [7012.0, 7013.0, 7014.0]],     [[7015.0, 7016.0, 7017.0],      [7018.0, 7019.0, 7020.0],      [7021.0, 7022.0, 7023.0],      [7024.0, 7025.0, 7026.0],      [7027.0, 7028.0, 7029.0],      [7030.0, 7031.0, 7032.0],      [7033.0, 7034.0, 7035.0]],     [[7036.0, 7037.0, 7038.0],      [7039.0, 7040.0, 7041.0],      [7042.0, 7043.0, 7044.0],      [7045.0, 7046.0, 7047.0],      [7048.0, 7049.0, 7050.0],      [7051.0, 7052.0, 7053.0],      [7054.0, 7055.0, 7056.0]]]],   [[[[7057.0, 7058.0, 7059.0],      [7060.0, 7061.0, 7062.0],      [7063.0, 7064.0, 7065.0],      [7066.0, 7067.0, 7068.0],      [7069.0, 7070.0, 7071.0],      [7072.0, 7073.0, 7074.0],      [7075.0, 7076.0, 7077.0]],     [[7078.0, 7079.0, 7080.0],      [7081.0, 7082.0, 7083.0],      [7084.0, 7085.0, 7086.0],      [7087.0, 7088.0, 7089.0],      [7090.0, 7091.0, 7092.0],      [7093.0, 7094.0, 7095.0],      [7096.0, 7097.0, 7098.0]],     [[7099.0, 7100.0, 7101.0],      [7102.0, 7103.0, 7104.0],      [7105.0, 7106.0, 7107.0],      [7108.0, 7109.0, 7110.0],      [7111.0, 7112.0, 7113.0],      [7114.0, 7115.0, 7116.0],      [7117.0, 7118.0, 7119.0]]],    [[[7120.0, 7121.0, 7122.0],      [7123.0, 7124.0, 7125.0],      [7126.0, 7127.0, 7128.0],      [7129.0, 7130.0, 7131.0],      [7132.0, 7133.0, 7134.0],      [7135.0, 7136.0, 7137.0],      [7138.0, 7139.0, 7140.0]],     [[7141.0, 7142.0, 7143.0],      [7144.0, 7145.0, 7146.0],      [7147.0, 7148.0, 7149.0],      [7150.0, 7151.0, 7152.0],      [7153.0, 7154.0, 7155.0],      [7156.0, 7157.0, 7158.0],      [7159.0, 7160.0, 7161.0]],     [[7162.0, 7163.0, 7164.0],      [7165.0, 7166.0, 7167.0],      [7168.0, 7169.0, 7170.0],      [7171.0, 7172.0, 7173.0],      [7174.0, 7175.0, 7176.0],      [7177.0, 7178.0, 7179.0],      [7180.0, 7181.0, 7182.0]]],    [[[7183.0, 7184.0, 7185.0],      [7186.0, 7187.0, 7188.0],      [7189.0, 7190.0, 7191.0],      [7192.0, 7193.0, 7194.0],      [7195.0, 7196.0, 7197.0],      [7198.0, 7199.0, 7200.0],      [7201.0, 7202.0, 7203.0]],     [[7204.0, 7205.0, 7206.0],      [7207.0, 7208.0, 7209.0],      [7210.0, 7211.0, 7212.0],      [7213.0, 7214.0, 7215.0],      [7216.0, 7217.0, 7218.0],      [7219.0, 7220.0, 7221.0],      [7222.0, 7223.0, 7224.0]],     [[7225.0, 7226.0, 7227.0],      [7228.0, 7229.0, 7230.0],      [7231.0, 7232.0, 7233.0],      [7234.0, 7235.0, 7236.0],      [7237.0, 7238.0, 7239.0],      [7240.0, 7241.0, 7242.0],      [7243.0, 7244.0, 7245.0]]],    [[[7246.0, 7247.0, 7248.0],      [7249.0, 7250.0, 7251.0],      [7252.0, 7253.0, 7254.0],      [7255.0, 7256.0, 7257.0],      [7258.0, 7259.0, 7260.0],      [7261.0, 7262.0, 7263.0],      [7264.0, 7265.0, 7266.0]],     [[7267.0, 7268.0, 7269.0],      [7270.0, 7271.0, 7272.0],      [7273.0, 7274.0, 7275.0],      [7276.0, 7277.0, 7278.0],      [7279.0, 7280.0, 7281.0],      [7282.0, 7283.0, 7284.0],      [7285.0, 7286.0, 7287.0]],     [[7288.0, 7289.0, 7290.0],      [7291.0, 7292.0, 7293.0],      [7294.0, 7295.0, 7296.0],      [7297.0, 7298.0, 7299.0],      [7300.0, 7301.0, 7302.0],      [7303.0, 7304.0, 7305.0],      [7306.0, 7307.0, 7308.0]]],    [[[7309.0, 7310.0, 7311.0],      [7312.0, 7313.0, 7314.0],      [7315.0, 7316.0, 7317.0],      [7318.0, 7319.0, 7320.0],      [7321.0, 7322.0, 7323.0],      [7324.0, 7325.0, 7326.0],      [7327.0, 7328.0, 7329.0]],     [[7330.0, 7331.0, 7332.0],      [7333.0, 7334.0, 7335.0],      [7336.0, 7337.0, 7338.0],      [7339.0, 7340.0, 7341.0],      [7342.0, 7343.0, 7344.0],      [7345.0, 7346.0, 7347.0],      [7348.0, 7349.0, 7350.0]],     [[7351.0, 7352.0, 7353.0],      [7354.0, 7355.0, 7356.0],      [7357.0, 7358.0, 7359.0],      [7360.0, 7361.0, 7362.0],      [7363.0, 7364.0, 7365.0],      [7366.0, 7367.0, 7368.0],      [7369.0, 7370.0, 7371.0]]],    [[[7372.0, 7373.0, 7374.0],      [7375.0, 7376.0, 7377.0],      [7378.0, 7379.0, 7380.0],      [7381.0, 7382.0, 7383.0],      [7384.0, 7385.0, 7386.0],      [7387.0, 7388.0, 7389.0],      [7390.0, 7391.0, 7392.0]],     [[7393.0, 7394.0, 7395.0],      [7396.0, 7397.0, 7398.0],      [7399.0, 7400.0, 7401.0],      [7402.0, 7403.0, 7404.0],      [7405.0, 7406.0, 7407.0],      [7408.0, 7409.0, 7410.0],      [7411.0, 7412.0, 7413.0]],     [[7414.0, 7415.0, 7416.0],      [7417.0, 7418.0, 7419.0],      [7420.0, 7421.0, 7422.0],      [7423.0, 7424.0, 7425.0],      [7426.0, 7427.0, 7428.0],      [7429.0, 7430.0, 7431.0],      [7432.0, 7433.0, 7434.0]]],    [[[7435.0, 7436.0, 7437.0],      [7438.0, 7439.0, 7440.0],      [7441.0, 7442.0, 7443.0],      [7444.0, 7445.0, 7446.0],      [7447.0, 7448.0, 7449.0],      [7450.0, 7451.0, 7452.0],      [7453.0, 7454.0, 7455.0]],     [[7456.0, 7457.0, 7458.0],      [7459.0, 7460.0, 7461.0],      [7462.0, 7463.0, 7464.0],      [7465.0, 7466.0, 7467.0],      [7468.0, 7469.0, 7470.0],      [7471.0, 7472.0, 7473.0],      [7474.0, 7475.0, 7476.0]],     [[7477.0, 7478.0, 7479.0],      [7480.0, 7481.0, 7482.0],      [7483.0, 7484.0, 7485.0],      [7486.0, 7487.0, 7488.0],      [7489.0, 7490.0, 7491.0],      [7492.0, 7493.0, 7494.0],      [7495.0, 7496.0, 7497.0]]]],   [[[[7498.0, 7499.0, 7500.0],      [7501.0, 7502.0, 7503.0],      [7504.0, 7505.0, 7506.0],      [7507.0, 7508.0, 7509.0],      [7510.0, 7511.0, 7512.0],      [7513.0, 7514.0, 7515.0],      [7516.0, 7517.0, 7518.0]],     [[7519.0, 7520.0, 7521.0],      [7522.0, 7523.0, 7524.0],      [7525.0, 7526.0, 7527.0],      [7528.0, 7529.0, 7530.0],      [7531.0, 7532.0, 7533.0],      [7534.0, 7535.0, 7536.0],      [7537.0, 7538.0, 7539.0]],     [[7540.0, 7541.0, 7542.0],      [7543.0, 7544.0, 7545.0],      [7546.0, 7547.0, 7548.0],      [7549.0, 7550.0, 7551.0],      [7552.0, 7553.0, 7554.0],      [7555.0, 7556.0, 7557.0],      [7558.0, 7559.0, 7560.0]]],    [[[7561.0, 7562.0, 7563.0],      [7564.0, 7565.0, 7566.0],      [7567.0, 7568.0, 7569.0],      [7570.0, 7571.0, 7572.0],      [7573.0, 7574.0, 7575.0],      [7576.0, 7577.0, 7578.0],      [7579.0, 7580.0, 7581.0]],     [[7582.0, 7583.0, 7584.0],      [7585.0, 7586.0, 7587.0],      [7588.0, 7589.0, 7590.0],      [7591.0, 7592.0, 7593.0],      [7594.0, 7595.0, 7596.0],      [7597.0, 7598.0, 7599.0],      [7600.0, 7601.0, 7602.0]],     [[7603.0, 7604.0, 7605.0],      [7606.0, 7607.0, 7608.0],      [7609.0, 7610.0, 7611.0],      [7612.0, 7613.0, 7614.0],      [7615.0, 7616.0, 7617.0],      [7618.0, 7619.0, 7620.0],      [7621.0, 7622.0, 7623.0]]],    [[[7624.0, 7625.0, 7626.0],      [7627.0, 7628.0, 7629.0],      [7630.0, 7631.0, 7632.0],      [7633.0, 7634.0, 7635.0],      [7636.0, 7637.0, 7638.0],      [7639.0, 7640.0, 7641.0],      [7642.0, 7643.0, 7644.0]],     [[7645.0, 7646.0, 7647.0],      [7648.0, 7649.0, 7650.0],      [7651.0, 7652.0, 7653.0],      [7654.0, 7655.0, 7656.0],      [7657.0, 7658.0, 7659.0],      [7660.0, 7661.0, 7662.0],      [7663.0, 7664.0, 7665.0]],     [[7666.0, 7667.0, 7668.0],      [7669.0, 7670.0, 7671.0],      [7672.0, 7673.0, 7674.0],      [7675.0, 7676.0, 7677.0],      [7678.0, 7679.0, 7680.0],      [7681.0, 7682.0, 7683.0],      [7684.0, 7685.0, 7686.0]]],    [[[7687.0, 7688.0, 7689.0],      [7690.0, 7691.0, 7692.0],      [7693.0, 7694.0, 7695.0],      [7696.0, 7697.0, 7698.0],      [7699.0, 7700.0, 7701.0],      [7702.0, 7703.0, 7704.0],      [7705.0, 7706.0, 7707.0]],     [[7708.0, 7709.0, 7710.0],      [7711.0, 7712.0, 7713.0],      [7714.0, 7715.0, 7716.0],      [7717.0, 7718.0, 7719.0],      [7720.0, 7721.0, 7722.0],      [7723.0, 7724.0, 7725.0],      [7726.0, 7727.0, 7728.0]],     [[7729.0, 7730.0, 7731.0],      [7732.0, 7733.0, 7734.0],      [7735.0, 7736.0, 7737.0],      [7738.0, 7739.0, 7740.0],      [7741.0, 7742.0, 7743.0],      [7744.0, 7745.0, 7746.0],      [7747.0, 7748.0, 7749.0]]],    [[[7750.0, 7751.0, 7752.0],      [7753.0, 7754.0, 7755.0],      [7756.0, 7757.0, 7758.0],      [7759.0, 7760.0, 7761.0],      [7762.0, 7763.0, 7764.0],      [7765.0, 7766.0, 7767.0],      [7768.0, 7769.0, 7770.0]],     [[7771.0, 7772.0, 7773.0],      [7774.0, 7775.0, 7776.0],      [7777.0, 7778.0, 7779.0],      [7780.0, 7781.0, 7782.0],      [7783.0, 7784.0, 7785.0],      [7786.0, 7787.0, 7788.0],      [7789.0, 7790.0, 7791.0]],     [[7792.0, 7793.0, 7794.0],      [7795.0, 7796.0, 7797.0],      [7798.0, 7799.0, 7800.0],      [7801.0, 7802.0, 7803.0],      [7804.0, 7805.0, 7806.0],      [7807.0, 7808.0, 7809.0],      [7810.0, 7811.0, 7812.0]]],    [[[7813.0, 7814.0, 7815.0],      [7816.0, 7817.0, 7818.0],      [7819.0, 7820.0, 7821.0],      [7822.0, 7823.0, 7824.0],      [7825.0, 7826.0, 7827.0],      [7828.0, 7829.0, 7830.0],      [7831.0, 7832.0, 7833.0]],     [[7834.0, 7835.0, 7836.0],      [7837.0, 7838.0, 7839.0],      [7840.0, 7841.0, 7842.0],      [7843.0, 7844.0, 7845.0],      [7846.0, 7847.0, 7848.0],      [7849.0, 7850.0, 7851.0],      [7852.0, 7853.0, 7854.0]],     [[7855.0, 7856.0, 7857.0],      [7858.0, 7859.0, 7860.0],      [7861.0, 7862.0, 7863.0],      [7864.0, 7865.0, 7866.0],      [7867.0, 7868.0, 7869.0],      [7870.0, 7871.0, 7872.0],      [7873.0, 7874.0, 7875.0]]],    [[[7876.0, 7877.0, 7878.0],      [7879.0, 7880.0, 7881.0],      [7882.0, 7883.0, 7884.0],      [7885.0, 7886.0, 7887.0],      [7888.0, 7889.0, 7890.0],      [7891.0, 7892.0, 7893.0],      [7894.0, 7895.0, 7896.0]],     [[7897.0, 7898.0, 7899.0],      [7900.0, 7901.0, 7902.0],      [7903.0, 7904.0, 7905.0],      [7906.0, 7907.0, 7908.0],      [7909.0, 7910.0, 7911.0],      [7912.0, 7913.0, 7914.0],      [7915.0, 7916.0, 7917.0]],     [[7918.0, 7919.0, 7920.0],      [7921.0, 7922.0, 7923.0],      [7924.0, 7925.0, 7926.0],      [7927.0, 7928.0, 7929.0],      [7930.0, 7931.0, 7932.0],      [7933.0, 7934.0, 7935.0],      [7936.0, 7937.0, 7938.0]]]]]] shape=[3, 6, 7, 3, 7, 3], strides=[2646, 441, 63, 21, 3, 1], layout=C (0x1)), I32([2, 3, 3] shape=[3], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [1, 1],  [3, 3]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 1387074125 1652777031 291473114 3531660464 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],     [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0],     [29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],     [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0]],    [[43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],     [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0],     [57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],     [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0],     [71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],     [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0]],    [[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],     [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],     [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0],     [106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0],     [113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],     [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0]],    [[127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],     [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0],     [141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],     [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],     [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],     [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0]],    [[169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0],     [176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],     [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],     [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0],     [197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],     [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0]],    [[211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],     [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0],     [225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],     [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],     [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0],     [246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0]],    [[253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],     [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],     [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],     [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0],     [281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],     [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0]]],   [[[295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],     [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0],     [309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0],     [316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],     [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],     [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0]],    [[337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],     [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0],     [351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],     [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0],     [365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],     [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0]],    [[379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0],     [386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0],     [393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],     [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],     [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],     [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]],    [[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],     [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],     [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],     [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0],     [449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0],     [456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0]],    [[463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],     [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0],     [477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],     [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0],     [491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],     [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0]],    [[505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],     [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],     [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0],     [526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0],     [533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],     [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0]],    [[547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],     [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0],     [561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],     [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],     [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],     [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0]]],   [[[589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0],     [596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],     [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],     [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0],     [617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],     [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0]],    [[631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],     [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0],     [645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],     [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0],     [659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0],     [666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0]],    [[673.0, 674.0, 675.0, 676.0, 677.0, 678.0, 679.0],     [680.0, 681.0, 682.0, 683.0, 684.0, 685.0, 686.0],     [687.0, 688.0, 689.0, 690.0, 691.0, 692.0, 693.0],     [694.0, 695.0, 696.0, 697.0, 698.0, 699.0, 700.0],     [701.0, 702.0, 703.0, 704.0, 705.0, 706.0, 707.0],     [708.0, 709.0, 710.0, 711.0, 712.0, 713.0, 714.0]],    [[715.0, 716.0, 717.0, 718.0, 719.0, 720.0, 721.0],     [722.0, 723.0, 724.0, 725.0, 726.0, 727.0, 728.0],     [729.0, 730.0, 731.0, 732.0, 733.0, 734.0, 735.0],     [736.0, 737.0, 738.0, 739.0, 740.0, 741.0, 742.0],     [743.0, 744.0, 745.0, 746.0, 747.0, 748.0, 749.0],     [750.0, 751.0, 752.0, 753.0, 754.0, 755.0, 756.0]],    [[757.0, 758.0, 759.0, 760.0, 761.0, 762.0, 763.0],     [764.0, 765.0, 766.0, 767.0, 768.0, 769.0, 770.0],     [771.0, 772.0, 773.0, 774.0, 775.0, 776.0, 777.0],     [778.0, 779.0, 780.0, 781.0, 782.0, 783.0, 784.0],     [785.0, 786.0, 787.0, 788.0, 789.0, 790.0, 791.0],     [792.0, 793.0, 794.0, 795.0, 796.0, 797.0, 798.0]],    [[799.0, 800.0, 801.0, 802.0, 803.0, 804.0, 805.0],     [806.0, 807.0, 808.0, 809.0, 810.0, 811.0, 812.0],     [813.0, 814.0, 815.0, 816.0, 817.0, 818.0, 819.0],     [820.0, 821.0, 822.0, 823.0, 824.0, 825.0, 826.0],     [827.0, 828.0, 829.0, 830.0, 831.0, 832.0, 833.0],     [834.0, 835.0, 836.0, 837.0, 838.0, 839.0, 840.0]],    [[841.0, 842.0, 843.0, 844.0, 845.0, 846.0, 847.0],     [848.0, 849.0, 850.0, 851.0, 852.0, 853.0, 854.0],     [855.0, 856.0, 857.0, 858.0, 859.0, 860.0, 861.0],     [862.0, 863.0, 864.0, 865.0, 866.0, 867.0, 868.0],     [869.0, 870.0, 871.0, 872.0, 873.0, 874.0, 875.0],     [876.0, 877.0, 878.0, 879.0, 880.0, 881.0, 882.0]]],   [[[883.0, 884.0, 885.0, 886.0, 887.0, 888.0, 889.0],     [890.0, 891.0, 892.0, 893.0, 894.0, 895.0, 896.0],     [897.0, 898.0, 899.0, 900.0, 901.0, 902.0, 903.0],     [904.0, 905.0, 906.0, 907.0, 908.0, 909.0, 910.0],     [911.0, 912.0, 913.0, 914.0, 915.0, 916.0, 917.0],     [918.0, 919.0, 920.0, 921.0, 922.0, 923.0, 924.0]],    [[925.0, 926.0, 927.0, 928.0, 929.0, 930.0, 931.0],     [932.0, 933.0, 934.0, 935.0, 936.0, 937.0, 938.0],     [939.0, 940.0, 941.0, 942.0, 943.0, 944.0, 945.0],     [946.0, 947.0, 948.0, 949.0, 950.0, 951.0, 952.0],     [953.0, 954.0, 955.0, 956.0, 957.0, 958.0, 959.0],     [960.0, 961.0, 962.0, 963.0, 964.0, 965.0, 966.0]],    [[967.0, 968.0, 969.0, 970.0, 971.0, 972.0, 973.0],     [974.0, 975.0, 976.0, 977.0, 978.0, 979.0, 980.0],     [981.0, 982.0, 983.0, 984.0, 985.0, 986.0, 987.0],     [988.0, 989.0, 990.0, 991.0, 992.0, 993.0, 994.0],     [995.0, 996.0, 997.0, 998.0, 999.0, 1000.0, 1001.0],     [1002.0, 1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0]],    [[1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0, 1015.0],     [1016.0, 1017.0, 1018.0, 1019.0, 1020.0, 1021.0, 1022.0],     [1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0],     [1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0],     [1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0],     [1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]],    [[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0, 1057.0],     [1058.0, 1059.0, 1060.0, 1061.0, 1062.0, 1063.0, 1064.0],     [1065.0, 1066.0, 1067.0, 1068.0, 1069.0, 1070.0, 1071.0],     [1072.0, 1073.0, 1074.0, 1075.0, 1076.0, 1077.0, 1078.0],     [1079.0, 1080.0, 1081.0, 1082.0, 1083.0, 1084.0, 1085.0],     [1086.0, 1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0]],    [[1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0, 1099.0],     [1100.0, 1101.0, 1102.0, 1103.0, 1104.0, 1105.0, 1106.0],     [1107.0, 1108.0, 1109.0, 1110.0, 1111.0, 1112.0, 1113.0],     [1114.0, 1115.0, 1116.0, 1117.0, 1118.0, 1119.0, 1120.0],     [1121.0, 1122.0, 1123.0, 1124.0, 1125.0, 1126.0, 1127.0],     [1128.0, 1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0]],    [[1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0, 1141.0],     [1142.0, 1143.0, 1144.0, 1145.0, 1146.0, 1147.0, 1148.0],     [1149.0, 1150.0, 1151.0, 1152.0, 1153.0, 1154.0, 1155.0],     [1156.0, 1157.0, 1158.0, 1159.0, 1160.0, 1161.0, 1162.0],     [1163.0, 1164.0, 1165.0, 1166.0, 1167.0, 1168.0, 1169.0],     [1170.0, 1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0]]],   [[[1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0, 1183.0],     [1184.0, 1185.0, 1186.0, 1187.0, 1188.0, 1189.0, 1190.0],     [1191.0, 1192.0, 1193.0, 1194.0, 1195.0, 1196.0, 1197.0],     [1198.0, 1199.0, 1200.0, 1201.0, 1202.0, 1203.0, 1204.0],     [1205.0, 1206.0, 1207.0, 1208.0, 1209.0, 1210.0, 1211.0],     [1212.0, 1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0]],    [[1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0, 1225.0],     [1226.0, 1227.0, 1228.0, 1229.0, 1230.0, 1231.0, 1232.0],     [1233.0, 1234.0, 1235.0, 1236.0, 1237.0, 1238.0, 1239.0],     [1240.0, 1241.0, 1242.0, 1243.0, 1244.0, 1245.0, 1246.0],     [1247.0, 1248.0, 1249.0, 1250.0, 1251.0, 1252.0, 1253.0],     [1254.0, 1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]],    [[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0, 1267.0],     [1268.0, 1269.0, 1270.0, 1271.0, 1272.0, 1273.0, 1274.0],     [1275.0, 1276.0, 1277.0, 1278.0, 1279.0, 1280.0, 1281.0],     [1282.0, 1283.0, 1284.0, 1285.0, 1286.0, 1287.0, 1288.0],     [1289.0, 1290.0, 1291.0, 1292.0, 1293.0, 1294.0, 1295.0],     [1296.0, 1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0]],    [[1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0, 1309.0],     [1310.0, 1311.0, 1312.0, 1313.0, 1314.0, 1315.0, 1316.0],     [1317.0, 1318.0, 1319.0, 1320.0, 1321.0, 1322.0, 1323.0],     [1324.0, 1325.0, 1326.0, 1327.0, 1328.0, 1329.0, 1330.0],     [1331.0, 1332.0, 1333.0, 1334.0, 1335.0, 1336.0, 1337.0],     [1338.0, 1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0]],    [[1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0, 1351.0],     [1352.0, 1353.0, 1354.0, 1355.0, 1356.0, 1357.0, 1358.0],     [1359.0, 1360.0, 1361.0, 1362.0, 1363.0, 1364.0, 1365.0],     [1366.0, 1367.0, 1368.0, 1369.0, 1370.0, 1371.0, 1372.0],     [1373.0, 1374.0, 1375.0, 1376.0, 1377.0, 1378.0, 1379.0],     [1380.0, 1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0]],    [[1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0, 1393.0],     [1394.0, 1395.0, 1396.0, 1397.0, 1398.0, 1399.0, 1400.0],     [1401.0, 1402.0, 1403.0, 1404.0, 1405.0, 1406.0, 1407.0],     [1408.0, 1409.0, 1410.0, 1411.0, 1412.0, 1413.0, 1414.0],     [1415.0, 1416.0, 1417.0, 1418.0, 1419.0, 1420.0, 1421.0],     [1422.0, 1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0]],    [[1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0, 1435.0],     [1436.0, 1437.0, 1438.0, 1439.0, 1440.0, 1441.0, 1442.0],     [1443.0, 1444.0, 1445.0, 1446.0, 1447.0, 1448.0, 1449.0],     [1450.0, 1451.0, 1452.0, 1453.0, 1454.0, 1455.0, 1456.0],     [1457.0, 1458.0, 1459.0, 1460.0, 1461.0, 1462.0, 1463.0],     [1464.0, 1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0]]],   [[[1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0, 1477.0],     [1478.0, 1479.0, 1480.0, 1481.0, 1482.0, 1483.0, 1484.0],     [1485.0, 1486.0, 1487.0, 1488.0, 1489.0, 1490.0, 1491.0],     [1492.0, 1493.0, 1494.0, 1495.0, 1496.0, 1497.0, 1498.0],     [1499.0, 1500.0, 1501.0, 1502.0, 1503.0, 1504.0, 1505.0],     [1506.0, 1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0]],    [[1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0, 1519.0],     [1520.0, 1521.0, 1522.0, 1523.0, 1524.0, 1525.0, 1526.0],     [1527.0, 1528.0, 1529.0, 1530.0, 1531.0, 1532.0, 1533.0],     [1534.0, 1535.0, 1536.0, 1537.0, 1538.0, 1539.0, 1540.0],     [1541.0, 1542.0, 1543.0, 1544.0, 1545.0, 1546.0, 1547.0],     [1548.0, 1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0]],    [[1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0, 1561.0],     [1562.0, 1563.0, 1564.0, 1565.0, 1566.0, 1567.0, 1568.0],     [1569.0, 1570.0, 1571.0, 1572.0, 1573.0, 1574.0, 1575.0],     [1576.0, 1577.0, 1578.0, 1579.0, 1580.0, 1581.0, 1582.0],     [1583.0, 1584.0, 1585.0, 1586.0, 1587.0, 1588.0, 1589.0],     [1590.0, 1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0]],    [[1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0, 1603.0],     [1604.0, 1605.0, 1606.0, 1607.0, 1608.0, 1609.0, 1610.0],     [1611.0, 1612.0, 1613.0, 1614.0, 1615.0, 1616.0, 1617.0],     [1618.0, 1619.0, 1620.0, 1621.0, 1622.0, 1623.0, 1624.0],     [1625.0, 1626.0, 1627.0, 1628.0, 1629.0, 1630.0, 1631.0],     [1632.0, 1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0]],    [[1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0, 1645.0],     [1646.0, 1647.0, 1648.0, 1649.0, 1650.0, 1651.0, 1652.0],     [1653.0, 1654.0, 1655.0, 1656.0, 1657.0, 1658.0, 1659.0],     [1660.0, 1661.0, 1662.0, 1663.0, 1664.0, 1665.0, 1666.0],     [1667.0, 1668.0, 1669.0, 1670.0, 1671.0, 1672.0, 1673.0],     [1674.0, 1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]],    [[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0, 1687.0],     [1688.0, 1689.0, 1690.0, 1691.0, 1692.0, 1693.0, 1694.0],     [1695.0, 1696.0, 1697.0, 1698.0, 1699.0, 1700.0, 1701.0],     [1702.0, 1703.0, 1704.0, 1705.0, 1706.0, 1707.0, 1708.0],     [1709.0, 1710.0, 1711.0, 1712.0, 1713.0, 1714.0, 1715.0],     [1716.0, 1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0]],    [[1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0, 1729.0],     [1730.0, 1731.0, 1732.0, 1733.0, 1734.0, 1735.0, 1736.0],     [1737.0, 1738.0, 1739.0, 1740.0, 1741.0, 1742.0, 1743.0],     [1744.0, 1745.0, 1746.0, 1747.0, 1748.0, 1749.0, 1750.0],     [1751.0, 1752.0, 1753.0, 1754.0, 1755.0, 1756.0, 1757.0],     [1758.0, 1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0]]],   [[[1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0, 1771.0],     [1772.0, 1773.0, 1774.0, 1775.0, 1776.0, 1777.0, 1778.0],     [1779.0, 1780.0, 1781.0, 1782.0, 1783.0, 1784.0, 1785.0],     [1786.0, 1787.0, 1788.0, 1789.0, 1790.0, 1791.0, 1792.0],     [1793.0, 1794.0, 1795.0, 1796.0, 1797.0, 1798.0, 1799.0],     [1800.0, 1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0]],    [[1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0, 1813.0],     [1814.0, 1815.0, 1816.0, 1817.0, 1818.0, 1819.0, 1820.0],     [1821.0, 1822.0, 1823.0, 1824.0, 1825.0, 1826.0, 1827.0],     [1828.0, 1829.0, 1830.0, 1831.0, 1832.0, 1833.0, 1834.0],     [1835.0, 1836.0, 1837.0, 1838.0, 1839.0, 1840.0, 1841.0],     [1842.0, 1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0]],    [[1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0, 1855.0],     [1856.0, 1857.0, 1858.0, 1859.0, 1860.0, 1861.0, 1862.0],     [1863.0, 1864.0, 1865.0, 1866.0, 1867.0, 1868.0, 1869.0],     [1870.0, 1871.0, 1872.0, 1873.0, 1874.0, 1875.0, 1876.0],     [1877.0, 1878.0, 1879.0, 1880.0, 1881.0, 1882.0, 1883.0],     [1884.0, 1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0]],    [[1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0, 1897.0],     [1898.0, 1899.0, 1900.0, 1901.0, 1902.0, 1903.0, 1904.0],     [1905.0, 1906.0, 1907.0, 1908.0, 1909.0, 1910.0, 1911.0],     [1912.0, 1913.0, 1914.0, 1915.0, 1916.0, 1917.0, 1918.0],     [1919.0, 1920.0, 1921.0, 1922.0, 1923.0, 1924.0, 1925.0],     [1926.0, 1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0]],    [[1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0, 1939.0],     [1940.0, 1941.0, 1942.0, 1943.0, 1944.0, 1945.0, 1946.0],     [1947.0, 1948.0, 1949.0, 1950.0, 1951.0, 1952.0, 1953.0],     [1954.0, 1955.0, 1956.0, 1957.0, 1958.0, 1959.0, 1960.0],     [1961.0, 1962.0, 1963.0, 1964.0, 1965.0, 1966.0, 1967.0],     [1968.0, 1969.0, 1970.0, 1971.0, 1972.0, 1973.0, 1974.0]],    [[1975.0, 1976.0, 1977.0, 1978.0, 1979.0, 1980.0, 1981.0],     [1982.0, 1983.0, 1984.0, 1985.0, 1986.0, 1987.0, 1988.0],     [1989.0, 1990.0, 1991.0, 1992.0, 1993.0, 1994.0, 1995.0],     [1996.0, 1997.0, 1998.0, 1999.0, 2000.0, 2001.0, 2002.0],     [2003.0, 2004.0, 2005.0, 2006.0, 2007.0, 2008.0, 2009.0],     [2010.0, 2011.0, 2012.0, 2013.0, 2014.0, 2015.0, 2016.0]],    [[2017.0, 2018.0, 2019.0, 2020.0, 2021.0, 2022.0, 2023.0],     [2024.0, 2025.0, 2026.0, 2027.0, 2028.0, 2029.0, 2030.0],     [2031.0, 2032.0, 2033.0, 2034.0, 2035.0, 2036.0, 2037.0],     [2038.0, 2039.0, 2040.0, 2041.0, 2042.0, 2043.0, 2044.0],     [2045.0, 2046.0, 2047.0, 2048.0, 2049.0, 2050.0, 2051.0],     [2052.0, 2053.0, 2054.0, 2055.0, 2056.0, 2057.0, 2058.0]]]],  [[[[2059.0, 2060.0, 2061.0, 2062.0, 2063.0, 2064.0, 2065.0],     [2066.0, 2067.0, 2068.0, 2069.0, 2070.0, 2071.0, 2072.0],     [2073.0, 2074.0, 2075.0, 2076.0, 2077.0, 2078.0, 2079.0],     [2080.0, 2081.0, 2082.0, 2083.0, 2084.0, 2085.0, 2086.0],     [2087.0, 2088.0, 2089.0, 2090.0, 2091.0, 2092.0, 2093.0],     [2094.0, 2095.0, 2096.0, 2097.0, 2098.0, 2099.0, 2100.0]],    [[2101.0, 2102.0, 2103.0, 2104.0, 2105.0, 2106.0, 2107.0],     [2108.0, 2109.0, 2110.0, 2111.0, 2112.0, 2113.0, 2114.0],     [2115.0, 2116.0, 2117.0, 2118.0, 2119.0, 2120.0, 2121.0],     [2122.0, 2123.0, 2124.0, 2125.0, 2126.0, 2127.0, 2128.0],     [2129.0, 2130.0, 2131.0, 2132.0, 2133.0, 2134.0, 2135.0],     [2136.0, 2137.0, 2138.0, 2139.0, 2140.0, 2141.0, 2142.0]],    [[2143.0, 2144.0, 2145.0, 2146.0, 2147.0, 2148.0, 2149.0],     [2150.0, 2151.0, 2152.0, 2153.0, 2154.0, 2155.0, 2156.0],     [2157.0, 2158.0, 2159.0, 2160.0, 2161.0, 2162.0, 2163.0],     [2164.0, 2165.0, 2166.0, 2167.0, 2168.0, 2169.0, 2170.0],     [2171.0, 2172.0, 2173.0, 2174.0, 2175.0, 2176.0, 2177.0],     [2178.0, 2179.0, 2180.0, 2181.0, 2182.0, 2183.0, 2184.0]],    [[2185.0, 2186.0, 2187.0, 2188.0, 2189.0, 2190.0, 2191.0],     [2192.0, 2193.0, 2194.0, 2195.0, 2196.0, 2197.0, 2198.0],     [2199.0, 2200.0, 2201.0, 2202.0, 2203.0, 2204.0, 2205.0],     [2206.0, 2207.0, 2208.0, 2209.0, 2210.0, 2211.0, 2212.0],     [2213.0, 2214.0, 2215.0, 2216.0, 2217.0, 2218.0, 2219.0],     [2220.0, 2221.0, 2222.0, 2223.0, 2224.0, 2225.0, 2226.0]],    [[2227.0, 2228.0, 2229.0, 2230.0, 2231.0, 2232.0, 2233.0],     [2234.0, 2235.0, 2236.0, 2237.0, 2238.0, 2239.0, 2240.0],     [2241.0, 2242.0, 2243.0, 2244.0, 2245.0, 2246.0, 2247.0],     [2248.0, 2249.0, 2250.0, 2251.0, 2252.0, 2253.0, 2254.0],     [2255.0, 2256.0, 2257.0, 2258.0, 2259.0, 2260.0, 2261.0],     [2262.0, 2263.0, 2264.0, 2265.0, 2266.0, 2267.0, 2268.0]],    [[2269.0, 2270.0, 2271.0, 2272.0, 2273.0, 2274.0, 2275.0],     [2276.0, 2277.0, 2278.0, 2279.0, 2280.0, 2281.0, 2282.0],     [2283.0, 2284.0, 2285.0, 2286.0, 2287.0, 2288.0, 2289.0],     [2290.0, 2291.0, 2292.0, 2293.0, 2294.0, 2295.0, 2296.0],     [2297.0, 2298.0, 2299.0, 2300.0, 2301.0, 2302.0, 2303.0],     [2304.0, 2305.0, 2306.0, 2307.0, 2308.0, 2309.0, 2310.0]],    [[2311.0, 2312.0, 2313.0, 2314.0, 2315.0, 2316.0, 2317.0],     [2318.0, 2319.0, 2320.0, 2321.0, 2322.0, 2323.0, 2324.0],     [2325.0, 2326.0, 2327.0, 2328.0, 2329.0, 2330.0, 2331.0],     [2332.0, 2333.0, 2334.0, 2335.0, 2336.0, 2337.0, 2338.0],     [2339.0, 2340.0, 2341.0, 2342.0, 2343.0, 2344.0, 2345.0],     [2346.0, 2347.0, 2348.0, 2349.0, 2350.0, 2351.0, 2352.0]]],   [[[2353.0, 2354.0, 2355.0, 2356.0, 2357.0, 2358.0, 2359.0],     [2360.0, 2361.0, 2362.0, 2363.0, 2364.0, 2365.0, 2366.0],     [2367.0, 2368.0, 2369.0, 2370.0, 2371.0, 2372.0, 2373.0],     [2374.0, 2375.0, 2376.0, 2377.0, 2378.0, 2379.0, 2380.0],     [2381.0, 2382.0, 2383.0, 2384.0, 2385.0, 2386.0, 2387.0],     [2388.0, 2389.0, 2390.0, 2391.0, 2392.0, 2393.0, 2394.0]],    [[2395.0, 2396.0, 2397.0, 2398.0, 2399.0, 2400.0, 2401.0],     [2402.0, 2403.0, 2404.0, 2405.0, 2406.0, 2407.0, 2408.0],     [2409.0, 2410.0, 2411.0, 2412.0, 2413.0, 2414.0, 2415.0],     [2416.0, 2417.0, 2418.0, 2419.0, 2420.0, 2421.0, 2422.0],     [2423.0, 2424.0, 2425.0, 2426.0, 2427.0, 2428.0, 2429.0],     [2430.0, 2431.0, 2432.0, 2433.0, 2434.0, 2435.0, 2436.0]],    [[2437.0, 2438.0, 2439.0, 2440.0, 2441.0, 2442.0, 2443.0],     [2444.0, 2445.0, 2446.0, 2447.0, 2448.0, 2449.0, 2450.0],     [2451.0, 2452.0, 2453.0, 2454.0, 2455.0, 2456.0, 2457.0],     [2458.0, 2459.0, 2460.0, 2461.0, 2462.0, 2463.0, 2464.0],     [2465.0, 2466.0, 2467.0, 2468.0, 2469.0, 2470.0, 2471.0],     [2472.0, 2473.0, 2474.0, 2475.0, 2476.0, 2477.0, 2478.0]],    [[2479.0, 2480.0, 2481.0, 2482.0, 2483.0, 2484.0, 2485.0],     [2486.0, 2487.0, 2488.0, 2489.0, 2490.0, 2491.0, 2492.0],     [2493.0, 2494.0, 2495.0, 2496.0, 2497.0, 2498.0, 2499.0],     [2500.0, 2501.0, 2502.0, 2503.0, 2504.0, 2505.0, 2506.0],     [2507.0, 2508.0, 2509.0, 2510.0, 2511.0, 2512.0, 2513.0],     [2514.0, 2515.0, 2516.0, 2517.0, 2518.0, 2519.0, 2520.0]],    [[2521.0, 2522.0, 2523.0, 2524.0, 2525.0, 2526.0, 2527.0],     [2528.0, 2529.0, 2530.0, 2531.0, 2532.0, 2533.0, 2534.0],     [2535.0, 2536.0, 2537.0, 2538.0, 2539.0, 2540.0, 2541.0],     [2542.0, 2543.0, 2544.0, 2545.0, 2546.0, 2547.0, 2548.0],     [2549.0, 2550.0, 2551.0, 2552.0, 2553.0, 2554.0, 2555.0],     [2556.0, 2557.0, 2558.0, 2559.0, 2560.0, 2561.0, 2562.0]],    [[2563.0, 2564.0, 2565.0, 2566.0, 2567.0, 2568.0, 2569.0],     [2570.0, 2571.0, 2572.0, 2573.0, 2574.0, 2575.0, 2576.0],     [2577.0, 2578.0, 2579.0, 2580.0, 2581.0, 2582.0, 2583.0],     [2584.0, 2585.0, 2586.0, 2587.0, 2588.0, 2589.0, 2590.0],     [2591.0, 2592.0, 2593.0, 2594.0, 2595.0, 2596.0, 2597.0],     [2598.0, 2599.0, 2600.0, 2601.0, 2602.0, 2603.0, 2604.0]],    [[2605.0, 2606.0, 2607.0, 2608.0, 2609.0, 2610.0, 2611.0],     [2612.0, 2613.0, 2614.0, 2615.0, 2616.0, 2617.0, 2618.0],     [2619.0, 2620.0, 2621.0, 2622.0, 2623.0, 2624.0, 2625.0],     [2626.0, 2627.0, 2628.0, 2629.0, 2630.0, 2631.0, 2632.0],     [2633.0, 2634.0, 2635.0, 2636.0, 2637.0, 2638.0, 2639.0],     [2640.0, 2641.0, 2642.0, 2643.0, 2644.0, 2645.0, 2646.0]]],   [[[2647.0, 2648.0, 2649.0, 2650.0, 2651.0, 2652.0, 2653.0],     [2654.0, 2655.0, 2656.0, 2657.0, 2658.0, 2659.0, 2660.0],     [2661.0, 2662.0, 2663.0, 2664.0, 2665.0, 2666.0, 2667.0],     [2668.0, 2669.0, 2670.0, 2671.0, 2672.0, 2673.0, 2674.0],     [2675.0, 2676.0, 2677.0, 2678.0, 2679.0, 2680.0, 2681.0],     [2682.0, 2683.0, 2684.0, 2685.0, 2686.0, 2687.0, 2688.0]],    [[2689.0, 2690.0, 2691.0, 2692.0, 2693.0, 2694.0, 2695.0],     [2696.0, 2697.0, 2698.0, 2699.0, 2700.0, 2701.0, 2702.0],     [2703.0, 2704.0, 2705.0, 2706.0, 2707.0, 2708.0, 2709.0],     [2710.0, 2711.0, 2712.0, 2713.0, 2714.0, 2715.0, 2716.0],     [2717.0, 2718.0, 2719.0, 2720.0, 2721.0, 2722.0, 2723.0],     [2724.0, 2725.0, 2726.0, 2727.0, 2728.0, 2729.0, 2730.0]],    [[2731.0, 2732.0, 2733.0, 2734.0, 2735.0, 2736.0, 2737.0],     [2738.0, 2739.0, 2740.0, 2741.0, 2742.0, 2743.0, 2744.0],     [2745.0, 2746.0, 2747.0, 2748.0, 2749.0, 2750.0, 2751.0],     [2752.0, 2753.0, 2754.0, 2755.0, 2756.0, 2757.0, 2758.0],     [2759.0, 2760.0, 2761.0, 2762.0, 2763.0, 2764.0, 2765.0],     [2766.0, 2767.0, 2768.0, 2769.0, 2770.0, 2771.0, 2772.0]],    [[2773.0, 2774.0, 2775.0, 2776.0, 2777.0, 2778.0, 2779.0],     [2780.0, 2781.0, 2782.0, 2783.0, 2784.0, 2785.0, 2786.0],     [2787.0, 2788.0, 2789.0, 2790.0, 2791.0, 2792.0, 2793.0],     [2794.0, 2795.0, 2796.0, 2797.0, 2798.0, 2799.0, 2800.0],     [2801.0, 2802.0, 2803.0, 2804.0, 2805.0, 2806.0, 2807.0],     [2808.0, 2809.0, 2810.0, 2811.0, 2812.0, 2813.0, 2814.0]],    [[2815.0, 2816.0, 2817.0, 2818.0, 2819.0, 2820.0, 2821.0],     [2822.0, 2823.0, 2824.0, 2825.0, 2826.0, 2827.0, 2828.0],     [2829.0, 2830.0, 2831.0, 2832.0, 2833.0, 2834.0, 2835.0],     [2836.0, 2837.0, 2838.0, 2839.0, 2840.0, 2841.0, 2842.0],     [2843.0, 2844.0, 2845.0, 2846.0, 2847.0, 2848.0, 2849.0],     [2850.0, 2851.0, 2852.0, 2853.0, 2854.0, 2855.0, 2856.0]],    [[2857.0, 2858.0, 2859.0, 2860.0, 2861.0, 2862.0, 2863.0],     [2864.0, 2865.0, 2866.0, 2867.0, 2868.0, 2869.0, 2870.0],     [2871.0, 2872.0, 2873.0, 2874.0, 2875.0, 2876.0, 2877.0],     [2878.0, 2879.0, 2880.0, 2881.0, 2882.0, 2883.0, 2884.0],     [2885.0, 2886.0, 2887.0, 2888.0, 2889.0, 2890.0, 2891.0],     [2892.0, 2893.0, 2894.0, 2895.0, 2896.0, 2897.0, 2898.0]],    [[2899.0, 2900.0, 2901.0, 2902.0, 2903.0, 2904.0, 2905.0],     [2906.0, 2907.0, 2908.0, 2909.0, 2910.0, 2911.0, 2912.0],     [2913.0, 2914.0, 2915.0, 2916.0, 2917.0, 2918.0, 2919.0],     [2920.0, 2921.0, 2922.0, 2923.0, 2924.0, 2925.0, 2926.0],     [2927.0, 2928.0, 2929.0, 2930.0, 2931.0, 2932.0, 2933.0],     [2934.0, 2935.0, 2936.0, 2937.0, 2938.0, 2939.0, 2940.0]]],   [[[2941.0, 2942.0, 2943.0, 2944.0, 2945.0, 2946.0, 2947.0],     [2948.0, 2949.0, 2950.0, 2951.0, 2952.0, 2953.0, 2954.0],     [2955.0, 2956.0, 2957.0, 2958.0, 2959.0, 2960.0, 2961.0],     [2962.0, 2963.0, 2964.0, 2965.0, 2966.0, 2967.0, 2968.0],     [2969.0, 2970.0, 2971.0, 2972.0, 2973.0, 2974.0, 2975.0],     [2976.0, 2977.0, 2978.0, 2979.0, 2980.0, 2981.0, 2982.0]],    [[2983.0, 2984.0, 2985.0, 2986.0, 2987.0, 2988.0, 2989.0],     [2990.0, 2991.0, 2992.0, 2993.0, 2994.0, 2995.0, 2996.0],     [2997.0, 2998.0, 2999.0, 3000.0, 3001.0, 3002.0, 3003.0],     [3004.0, 3005.0, 3006.0, 3007.0, 3008.0, 3009.0, 3010.0],     [3011.0, 3012.0, 3013.0, 3014.0, 3015.0, 3016.0, 3017.0],     [3018.0, 3019.0, 3020.0, 3021.0, 3022.0, 3023.0, 3024.0]],    [[3025.0, 3026.0, 3027.0, 3028.0, 3029.0, 3030.0, 3031.0],     [3032.0, 3033.0, 3034.0, 3035.0, 3036.0, 3037.0, 3038.0],     [3039.0, 3040.0, 3041.0, 3042.0, 3043.0, 3044.0, 3045.0],     [3046.0, 3047.0, 3048.0, 3049.0, 3050.0, 3051.0, 3052.0],     [3053.0, 3054.0, 3055.0, 3056.0, 3057.0, 3058.0, 3059.0],     [3060.0, 3061.0, 3062.0, 3063.0, 3064.0, 3065.0, 3066.0]],    [[3067.0, 3068.0, 3069.0, 3070.0, 3071.0, 3072.0, 3073.0],     [3074.0, 3075.0, 3076.0, 3077.0, 3078.0, 3079.0, 3080.0],     [3081.0, 3082.0, 3083.0, 3084.0, 3085.0, 3086.0, 3087.0],     [3088.0, 3089.0, 3090.0, 3091.0, 3092.0, 3093.0, 3094.0],     [3095.0, 3096.0, 3097.0, 3098.0, 3099.0, 3100.0, 3101.0],     [3102.0, 3103.0, 3104.0, 3105.0, 3106.0, 3107.0, 3108.0]],    [[3109.0, 3110.0, 3111.0, 3112.0, 3113.0, 3114.0, 3115.0],     [3116.0, 3117.0, 3118.0, 3119.0, 3120.0, 3121.0, 3122.0],     [3123.0, 3124.0, 3125.0, 3126.0, 3127.0, 3128.0, 3129.0],     [3130.0, 3131.0, 3132.0, 3133.0, 3134.0, 3135.0, 3136.0],     [3137.0, 3138.0, 3139.0, 3140.0, 3141.0, 3142.0, 3143.0],     [3144.0, 3145.0, 3146.0, 3147.0, 3148.0, 3149.0, 3150.0]],    [[3151.0, 3152.0, 3153.0, 3154.0, 3155.0, 3156.0, 3157.0],     [3158.0, 3159.0, 3160.0, 3161.0, 3162.0, 3163.0, 3164.0],     [3165.0, 3166.0, 3167.0, 3168.0, 3169.0, 3170.0, 3171.0],     [3172.0, 3173.0, 3174.0, 3175.0, 3176.0, 3177.0, 3178.0],     [3179.0, 3180.0, 3181.0, 3182.0, 3183.0, 3184.0, 3185.0],     [3186.0, 3187.0, 3188.0, 3189.0, 3190.0, 3191.0, 3192.0]],    [[3193.0, 3194.0, 3195.0, 3196.0, 3197.0, 3198.0, 3199.0],     [3200.0, 3201.0, 3202.0, 3203.0, 3204.0, 3205.0, 3206.0],     [3207.0, 3208.0, 3209.0, 3210.0, 3211.0, 3212.0, 3213.0],     [3214.0, 3215.0, 3216.0, 3217.0, 3218.0, 3219.0, 3220.0],     [3221.0, 3222.0, 3223.0, 3224.0, 3225.0, 3226.0, 3227.0],     [3228.0, 3229.0, 3230.0, 3231.0, 3232.0, 3233.0, 3234.0]]],   [[[3235.0, 3236.0, 3237.0, 3238.0, 3239.0, 3240.0, 3241.0],     [3242.0, 3243.0, 3244.0, 3245.0, 3246.0, 3247.0, 3248.0],     [3249.0, 3250.0, 3251.0, 3252.0, 3253.0, 3254.0, 3255.0],     [3256.0, 3257.0, 3258.0, 3259.0, 3260.0, 3261.0, 3262.0],     [3263.0, 3264.0, 3265.0, 3266.0, 3267.0, 3268.0, 3269.0],     [3270.0, 3271.0, 3272.0, 3273.0, 3274.0, 3275.0, 3276.0]],    [[3277.0, 3278.0, 3279.0, 3280.0, 3281.0, 3282.0, 3283.0],     [3284.0, 3285.0, 3286.0, 3287.0, 3288.0, 3289.0, 3290.0],     [3291.0, 3292.0, 3293.0, 3294.0, 3295.0, 3296.0, 3297.0],     [3298.0, 3299.0, 3300.0, 3301.0, 3302.0, 3303.0, 3304.0],     [3305.0, 3306.0, 3307.0, 3308.0, 3309.0, 3310.0, 3311.0],     [3312.0, 3313.0, 3314.0, 3315.0, 3316.0, 3317.0, 3318.0]],    [[3319.0, 3320.0, 3321.0, 3322.0, 3323.0, 3324.0, 3325.0],     [3326.0, 3327.0, 3328.0, 3329.0, 3330.0, 3331.0, 3332.0],     [3333.0, 3334.0, 3335.0, 3336.0, 3337.0, 3338.0, 3339.0],     [3340.0, 3341.0, 3342.0, 3343.0, 3344.0, 3345.0, 3346.0],     [3347.0, 3348.0, 3349.0, 3350.0, 3351.0, 3352.0, 3353.0],     [3354.0, 3355.0, 3356.0, 3357.0, 3358.0, 3359.0, 3360.0]],    [[3361.0, 3362.0, 3363.0, 3364.0, 3365.0, 3366.0, 3367.0],     [3368.0, 3369.0, 3370.0, 3371.0, 3372.0, 3373.0, 3374.0],     [3375.0, 3376.0, 3377.0, 3378.0, 3379.0, 3380.0, 3381.0],     [3382.0, 3383.0, 3384.0, 3385.0, 3386.0, 3387.0, 3388.0],     [3389.0, 3390.0, 3391.0, 3392.0, 3393.0, 3394.0, 3395.0],     [3396.0, 3397.0, 3398.0, 3399.0, 3400.0, 3401.0, 3402.0]],    [[3403.0, 3404.0, 3405.0, 3406.0, 3407.0, 3408.0, 3409.0],     [3410.0, 3411.0, 3412.0, 3413.0, 3414.0, 3415.0, 3416.0],     [3417.0, 3418.0, 3419.0, 3420.0, 3421.0, 3422.0, 3423.0],     [3424.0, 3425.0, 3426.0, 3427.0, 3428.0, 3429.0, 3430.0],     [3431.0, 3432.0, 3433.0, 3434.0, 3435.0, 3436.0, 3437.0],     [3438.0, 3439.0, 3440.0, 3441.0, 3442.0, 3443.0, 3444.0]],    [[3445.0, 3446.0, 3447.0, 3448.0, 3449.0, 3450.0, 3451.0],     [3452.0, 3453.0, 3454.0, 3455.0, 3456.0, 3457.0, 3458.0],     [3459.0, 3460.0, 3461.0, 3462.0, 3463.0, 3464.0, 3465.0],     [3466.0, 3467.0, 3468.0, 3469.0, 3470.0, 3471.0, 3472.0],     [3473.0, 3474.0, 3475.0, 3476.0, 3477.0, 3478.0, 3479.0],     [3480.0, 3481.0, 3482.0, 3483.0, 3484.0, 3485.0, 3486.0]],    [[3487.0, 3488.0, 3489.0, 3490.0, 3491.0, 3492.0, 3493.0],     [3494.0, 3495.0, 3496.0, 3497.0, 3498.0, 3499.0, 3500.0],     [3501.0, 3502.0, 3503.0, 3504.0, 3505.0, 3506.0, 3507.0],     [3508.0, 3509.0, 3510.0, 3511.0, 3512.0, 3513.0, 3514.0],     [3515.0, 3516.0, 3517.0, 3518.0, 3519.0, 3520.0, 3521.0],     [3522.0, 3523.0, 3524.0, 3525.0, 3526.0, 3527.0, 3528.0]]],   [[[3529.0, 3530.0, 3531.0, 3532.0, 3533.0, 3534.0, 3535.0],     [3536.0, 3537.0, 3538.0, 3539.0, 3540.0, 3541.0, 3542.0],     [3543.0, 3544.0, 3545.0, 3546.0, 3547.0, 3548.0, 3549.0],     [3550.0, 3551.0, 3552.0, 3553.0, 3554.0, 3555.0, 3556.0],     [3557.0, 3558.0, 3559.0, 3560.0, 3561.0, 3562.0, 3563.0],     [3564.0, 3565.0, 3566.0, 3567.0, 3568.0, 3569.0, 3570.0]],    [[3571.0, 3572.0, 3573.0, 3574.0, 3575.0, 3576.0, 3577.0],     [3578.0, 3579.0, 3580.0, 3581.0, 3582.0, 3583.0, 3584.0],     [3585.0, 3586.0, 3587.0, 3588.0, 3589.0, 3590.0, 3591.0],     [3592.0, 3593.0, 3594.0, 3595.0, 3596.0, 3597.0, 3598.0],     [3599.0, 3600.0, 3601.0, 3602.0, 3603.0, 3604.0, 3605.0],     [3606.0, 3607.0, 3608.0, 3609.0, 3610.0, 3611.0, 3612.0]],    [[3613.0, 3614.0, 3615.0, 3616.0, 3617.0, 3618.0, 3619.0],     [3620.0, 3621.0, 3622.0, 3623.0, 3624.0, 3625.0, 3626.0],     [3627.0, 3628.0, 3629.0, 3630.0, 3631.0, 3632.0, 3633.0],     [3634.0, 3635.0, 3636.0, 3637.0, 3638.0, 3639.0, 3640.0],     [3641.0, 3642.0, 3643.0, 3644.0, 3645.0, 3646.0, 3647.0],     [3648.0, 3649.0, 3650.0, 3651.0, 3652.0, 3653.0, 3654.0]],    [[3655.0, 3656.0, 3657.0, 3658.0, 3659.0, 3660.0, 3661.0],     [3662.0, 3663.0, 3664.0, 3665.0, 3666.0, 3667.0, 3668.0],     [3669.0, 3670.0, 3671.0, 3672.0, 3673.0, 3674.0, 3675.0],     [3676.0, 3677.0, 3678.0, 3679.0, 3680.0, 3681.0, 3682.0],     [3683.0, 3684.0, 3685.0, 3686.0, 3687.0, 3688.0, 3689.0],     [3690.0, 3691.0, 3692.0, 3693.0, 3694.0, 3695.0, 3696.0]],    [[3697.0, 3698.0, 3699.0, 3700.0, 3701.0, 3702.0, 3703.0],     [3704.0, 3705.0, 3706.0, 3707.0, 3708.0, 3709.0, 3710.0],     [3711.0, 3712.0, 3713.0, 3714.0, 3715.0, 3716.0, 3717.0],     [3718.0, 3719.0, 3720.0, 3721.0, 3722.0, 3723.0, 3724.0],     [3725.0, 3726.0, 3727.0, 3728.0, 3729.0, 3730.0, 3731.0],     [3732.0, 3733.0, 3734.0, 3735.0, 3736.0, 3737.0, 3738.0]],    [[3739.0, 3740.0, 3741.0, 3742.0, 3743.0, 3744.0, 3745.0],     [3746.0, 3747.0, 3748.0, 3749.0, 3750.0, 3751.0, 3752.0],     [3753.0, 3754.0, 3755.0, 3756.0, 3757.0, 3758.0, 3759.0],     [3760.0, 3761.0, 3762.0, 3763.0, 3764.0, 3765.0, 3766.0],     [3767.0, 3768.0, 3769.0, 3770.0, 3771.0, 3772.0, 3773.0],     [3774.0, 3775.0, 3776.0, 3777.0, 3778.0, 3779.0, 3780.0]],    [[3781.0, 3782.0, 3783.0, 3784.0, 3785.0, 3786.0, 3787.0],     [3788.0, 3789.0, 3790.0, 3791.0, 3792.0, 3793.0, 3794.0],     [3795.0, 3796.0, 3797.0, 3798.0, 3799.0, 3800.0, 3801.0],     [3802.0, 3803.0, 3804.0, 3805.0, 3806.0, 3807.0, 3808.0],     [3809.0, 3810.0, 3811.0, 3812.0, 3813.0, 3814.0, 3815.0],     [3816.0, 3817.0, 3818.0, 3819.0, 3820.0, 3821.0, 3822.0]]],   [[[3823.0, 3824.0, 3825.0, 3826.0, 3827.0, 3828.0, 3829.0],     [3830.0, 3831.0, 3832.0, 3833.0, 3834.0, 3835.0, 3836.0],     [3837.0, 3838.0, 3839.0, 3840.0, 3841.0, 3842.0, 3843.0],     [3844.0, 3845.0, 3846.0, 3847.0, 3848.0, 3849.0, 3850.0],     [3851.0, 3852.0, 3853.0, 3854.0, 3855.0, 3856.0, 3857.0],     [3858.0, 3859.0, 3860.0, 3861.0, 3862.0, 3863.0, 3864.0]],    [[3865.0, 3866.0, 3867.0, 3868.0, 3869.0, 3870.0, 3871.0],     [3872.0, 3873.0, 3874.0, 3875.0, 3876.0, 3877.0, 3878.0],     [3879.0, 3880.0, 3881.0, 3882.0, 3883.0, 3884.0, 3885.0],     [3886.0, 3887.0, 3888.0, 3889.0, 3890.0, 3891.0, 3892.0],     [3893.0, 3894.0, 3895.0, 3896.0, 3897.0, 3898.0, 3899.0],     [3900.0, 3901.0, 3902.0, 3903.0, 3904.0, 3905.0, 3906.0]],    [[3907.0, 3908.0, 3909.0, 3910.0, 3911.0, 3912.0, 3913.0],     [3914.0, 3915.0, 3916.0, 3917.0, 3918.0, 3919.0, 3920.0],     [3921.0, 3922.0, 3923.0, 3924.0, 3925.0, 3926.0, 3927.0],     [3928.0, 3929.0, 3930.0, 3931.0, 3932.0, 3933.0, 3934.0],     [3935.0, 3936.0, 3937.0, 3938.0, 3939.0, 3940.0, 3941.0],     [3942.0, 3943.0, 3944.0, 3945.0, 3946.0, 3947.0, 3948.0]],    [[3949.0, 3950.0, 3951.0, 3952.0, 3953.0, 3954.0, 3955.0],     [3956.0, 3957.0, 3958.0, 3959.0, 3960.0, 3961.0, 3962.0],     [3963.0, 3964.0, 3965.0, 3966.0, 3967.0, 3968.0, 3969.0],     [3970.0, 3971.0, 3972.0, 3973.0, 3974.0, 3975.0, 3976.0],     [3977.0, 3978.0, 3979.0, 3980.0, 3981.0, 3982.0, 3983.0],     [3984.0, 3985.0, 3986.0, 3987.0, 3988.0, 3989.0, 3990.0]],    [[3991.0, 3992.0, 3993.0, 3994.0, 3995.0, 3996.0, 3997.0],     [3998.0, 3999.0, 4000.0, 4001.0, 4002.0, 4003.0, 4004.0],     [4005.0, 4006.0, 4007.0, 4008.0, 4009.0, 4010.0, 4011.0],     [4012.0, 4013.0, 4014.0, 4015.0, 4016.0, 4017.0, 4018.0],     [4019.0, 4020.0, 4021.0, 4022.0, 4023.0, 4024.0, 4025.0],     [4026.0, 4027.0, 4028.0, 4029.0, 4030.0, 4031.0, 4032.0]],    [[4033.0, 4034.0, 4035.0, 4036.0, 4037.0, 4038.0, 4039.0],     [4040.0, 4041.0, 4042.0, 4043.0, 4044.0, 4045.0, 4046.0],     [4047.0, 4048.0, 4049.0, 4050.0, 4051.0, 4052.0, 4053.0],     [4054.0, 4055.0, 4056.0, 4057.0, 4058.0, 4059.0, 4060.0],     [4061.0, 4062.0, 4063.0, 4064.0, 4065.0, 4066.0, 4067.0],     [4068.0, 4069.0, 4070.0, 4071.0, 4072.0, 4073.0, 4074.0]],    [[4075.0, 4076.0, 4077.0, 4078.0, 4079.0, 4080.0, 4081.0],     [4082.0, 4083.0, 4084.0, 4085.0, 4086.0, 4087.0, 4088.0],     [4089.0, 4090.0, 4091.0, 4092.0, 4093.0, 4094.0, 4095.0],     [4096.0, 4097.0, 4098.0, 4099.0, 4100.0, 4101.0, 4102.0],     [4103.0, 4104.0, 4105.0, 4106.0, 4107.0, 4108.0, 4109.0],     [4110.0, 4111.0, 4112.0, 4113.0, 4114.0, 4115.0, 4116.0]]]],  [[[[4117.0, 4118.0, 4119.0, 4120.0, 4121.0, 4122.0, 4123.0],     [4124.0, 4125.0, 4126.0, 4127.0, 4128.0, 4129.0, 4130.0],     [4131.0, 4132.0, 4133.0, 4134.0, 4135.0, 4136.0, 4137.0],     [4138.0, 4139.0, 4140.0, 4141.0, 4142.0, 4143.0, 4144.0],     [4145.0, 4146.0, 4147.0, 4148.0, 4149.0, 4150.0, 4151.0],     [4152.0, 4153.0, 4154.0, 4155.0, 4156.0, 4157.0, 4158.0]],    [[4159.0, 4160.0, 4161.0, 4162.0, 4163.0, 4164.0, 4165.0],     [4166.0, 4167.0, 4168.0, 4169.0, 4170.0, 4171.0, 4172.0],     [4173.0, 4174.0, 4175.0, 4176.0, 4177.0, 4178.0, 4179.0],     [4180.0, 4181.0, 4182.0, 4183.0, 4184.0, 4185.0, 4186.0],     [4187.0, 4188.0, 4189.0, 4190.0, 4191.0, 4192.0, 4193.0],     [4194.0, 4195.0, 4196.0, 4197.0, 4198.0, 4199.0, 4200.0]],    [[4201.0, 4202.0, 4203.0, 4204.0, 4205.0, 4206.0, 4207.0],     [4208.0, 4209.0, 4210.0, 4211.0, 4212.0, 4213.0, 4214.0],     [4215.0, 4216.0, 4217.0, 4218.0, 4219.0, 4220.0, 4221.0],     [4222.0, 4223.0, 4224.0, 4225.0, 4226.0, 4227.0, 4228.0],     [4229.0, 4230.0, 4231.0, 4232.0, 4233.0, 4234.0, 4235.0],     [4236.0, 4237.0, 4238.0, 4239.0, 4240.0, 4241.0, 4242.0]],    [[4243.0, 4244.0, 4245.0, 4246.0, 4247.0, 4248.0, 4249.0],     [4250.0, 4251.0, 4252.0, 4253.0, 4254.0, 4255.0, 4256.0],     [4257.0, 4258.0, 4259.0, 4260.0, 4261.0, 4262.0, 4263.0],     [4264.0, 4265.0, 4266.0, 4267.0, 4268.0, 4269.0, 4270.0],     [4271.0, 4272.0, 4273.0, 4274.0, 4275.0, 4276.0, 4277.0],     [4278.0, 4279.0, 4280.0, 4281.0, 4282.0, 4283.0, 4284.0]],    [[4285.0, 4286.0, 4287.0, 4288.0, 4289.0, 4290.0, 4291.0],     [4292.0, 4293.0, 4294.0, 4295.0, 4296.0, 4297.0, 4298.0],     [4299.0, 4300.0, 4301.0, 4302.0, 4303.0, 4304.0, 4305.0],     [4306.0, 4307.0, 4308.0, 4309.0, 4310.0, 4311.0, 4312.0],     [4313.0, 4314.0, 4315.0, 4316.0, 4317.0, 4318.0, 4319.0],     [4320.0, 4321.0, 4322.0, 4323.0, 4324.0, 4325.0, 4326.0]],    [[4327.0, 4328.0, 4329.0, 4330.0, 4331.0, 4332.0, 4333.0],     [4334.0, 4335.0, 4336.0, 4337.0, 4338.0, 4339.0, 4340.0],     [4341.0, 4342.0, 4343.0, 4344.0, 4345.0, 4346.0, 4347.0],     [4348.0, 4349.0, 4350.0, 4351.0, 4352.0, 4353.0, 4354.0],     [4355.0, 4356.0, 4357.0, 4358.0, 4359.0, 4360.0, 4361.0],     [4362.0, 4363.0, 4364.0, 4365.0, 4366.0, 4367.0, 4368.0]],    [[4369.0, 4370.0, 4371.0, 4372.0, 4373.0, 4374.0, 4375.0],     [4376.0, 4377.0, 4378.0, 4379.0, 4380.0, 4381.0, 4382.0],     [4383.0, 4384.0, 4385.0, 4386.0, 4387.0, 4388.0, 4389.0],     [4390.0, 4391.0, 4392.0, 4393.0, 4394.0, 4395.0, 4396.0],     [4397.0, 4398.0, 4399.0, 4400.0, 4401.0, 4402.0, 4403.0],     [4404.0, 4405.0, 4406.0, 4407.0, 4408.0, 4409.0, 4410.0]]],   [[[4411.0, 4412.0, 4413.0, 4414.0, 4415.0, 4416.0, 4417.0],     [4418.0, 4419.0, 4420.0, 4421.0, 4422.0, 4423.0, 4424.0],     [4425.0, 4426.0, 4427.0, 4428.0, 4429.0, 4430.0, 4431.0],     [4432.0, 4433.0, 4434.0, 4435.0, 4436.0, 4437.0, 4438.0],     [4439.0, 4440.0, 4441.0, 4442.0, 4443.0, 4444.0, 4445.0],     [4446.0, 4447.0, 4448.0, 4449.0, 4450.0, 4451.0, 4452.0]],    [[4453.0, 4454.0, 4455.0, 4456.0, 4457.0, 4458.0, 4459.0],     [4460.0, 4461.0, 4462.0, 4463.0, 4464.0, 4465.0, 4466.0],     [4467.0, 4468.0, 4469.0, 4470.0, 4471.0, 4472.0, 4473.0],     [4474.0, 4475.0, 4476.0, 4477.0, 4478.0, 4479.0, 4480.0],     [4481.0, 4482.0, 4483.0, 4484.0, 4485.0, 4486.0, 4487.0],     [4488.0, 4489.0, 4490.0, 4491.0, 4492.0, 4493.0, 4494.0]],    [[4495.0, 4496.0, 4497.0, 4498.0, 4499.0, 4500.0, 4501.0],     [4502.0, 4503.0, 4504.0, 4505.0, 4506.0, 4507.0, 4508.0],     [4509.0, 4510.0, 4511.0, 4512.0, 4513.0, 4514.0, 4515.0],     [4516.0, 4517.0, 4518.0, 4519.0, 4520.0, 4521.0, 4522.0],     [4523.0, 4524.0, 4525.0, 4526.0, 4527.0, 4528.0, 4529.0],     [4530.0, 4531.0, 4532.0, 4533.0, 4534.0, 4535.0, 4536.0]],    [[4537.0, 4538.0, 4539.0, 4540.0, 4541.0, 4542.0, 4543.0],     [4544.0, 4545.0, 4546.0, 4547.0, 4548.0, 4549.0, 4550.0],     [4551.0, 4552.0, 4553.0, 4554.0, 4555.0, 4556.0, 4557.0],     [4558.0, 4559.0, 4560.0, 4561.0, 4562.0, 4563.0, 4564.0],     [4565.0, 4566.0, 4567.0, 4568.0, 4569.0, 4570.0, 4571.0],     [4572.0, 4573.0, 4574.0, 4575.0, 4576.0, 4577.0, 4578.0]],    [[4579.0, 4580.0, 4581.0, 4582.0, 4583.0, 4584.0, 4585.0],     [4586.0, 4587.0, 4588.0, 4589.0, 4590.0, 4591.0, 4592.0],     [4593.0, 4594.0, 4595.0, 4596.0, 4597.0, 4598.0, 4599.0],     [4600.0, 4601.0, 4602.0, 4603.0, 4604.0, 4605.0, 4606.0],     [4607.0, 4608.0, 4609.0, 4610.0, 4611.0, 4612.0, 4613.0],     [4614.0, 4615.0, 4616.0, 4617.0, 4618.0, 4619.0, 4620.0]],    [[4621.0, 4622.0, 4623.0, 4624.0, 4625.0, 4626.0, 4627.0],     [4628.0, 4629.0, 4630.0, 4631.0, 4632.0, 4633.0, 4634.0],     [4635.0, 4636.0, 4637.0, 4638.0, 4639.0, 4640.0, 4641.0],     [4642.0, 4643.0, 4644.0, 4645.0, 4646.0, 4647.0, 4648.0],     [4649.0, 4650.0, 4651.0, 4652.0, 4653.0, 4654.0, 4655.0],     [4656.0, 4657.0, 4658.0, 4659.0, 4660.0, 4661.0, 4662.0]],    [[4663.0, 4664.0, 4665.0, 4666.0, 4667.0, 4668.0, 4669.0],     [4670.0, 4671.0, 4672.0, 4673.0, 4674.0, 4675.0, 4676.0],     [4677.0, 4678.0, 4679.0, 4680.0, 4681.0, 4682.0, 4683.0],     [4684.0, 4685.0, 4686.0, 4687.0, 4688.0, 4689.0, 4690.0],     [4691.0, 4692.0, 4693.0, 4694.0, 4695.0, 4696.0, 4697.0],     [4698.0, 4699.0, 4700.0, 4701.0, 4702.0, 4703.0, 4704.0]]],   [[[4705.0, 4706.0, 4707.0, 4708.0, 4709.0, 4710.0, 4711.0],     [4712.0, 4713.0, 4714.0, 4715.0, 4716.0, 4717.0, 4718.0],     [4719.0, 4720.0, 4721.0, 4722.0, 4723.0, 4724.0, 4725.0],     [4726.0, 4727.0, 4728.0, 4729.0, 4730.0, 4731.0, 4732.0],     [4733.0, 4734.0, 4735.0, 4736.0, 4737.0, 4738.0, 4739.0],     [4740.0, 4741.0, 4742.0, 4743.0, 4744.0, 4745.0, 4746.0]],    [[4747.0, 4748.0, 4749.0, 4750.0, 4751.0, 4752.0, 4753.0],     [4754.0, 4755.0, 4756.0, 4757.0, 4758.0, 4759.0, 4760.0],     [4761.0, 4762.0, 4763.0, 4764.0, 4765.0, 4766.0, 4767.0],     [4768.0, 4769.0, 4770.0, 4771.0, 4772.0, 4773.0, 4774.0],     [4775.0, 4776.0, 4777.0, 4778.0, 4779.0, 4780.0, 4781.0],     [4782.0, 4783.0, 4784.0, 4785.0, 4786.0, 4787.0, 4788.0]],    [[4789.0, 4790.0, 4791.0, 4792.0, 4793.0, 4794.0, 4795.0],     [4796.0, 4797.0, 4798.0, 4799.0, 4800.0, 4801.0, 4802.0],     [4803.0, 4804.0, 4805.0, 4806.0, 4807.0, 4808.0, 4809.0],     [4810.0, 4811.0, 4812.0, 4813.0, 4814.0, 4815.0, 4816.0],     [4817.0, 4818.0, 4819.0, 4820.0, 4821.0, 4822.0, 4823.0],     [4824.0, 4825.0, 4826.0, 4827.0, 4828.0, 4829.0, 4830.0]],    [[4831.0, 4832.0, 4833.0, 4834.0, 4835.0, 4836.0, 4837.0],     [4838.0, 4839.0, 4840.0, 4841.0, 4842.0, 4843.0, 4844.0],     [4845.0, 4846.0, 4847.0, 4848.0, 4849.0, 4850.0, 4851.0],     [4852.0, 4853.0, 4854.0, 4855.0, 4856.0, 4857.0, 4858.0],     [4859.0, 4860.0, 4861.0, 4862.0, 4863.0, 4864.0, 4865.0],     [4866.0, 4867.0, 4868.0, 4869.0, 4870.0, 4871.0, 4872.0]],    [[4873.0, 4874.0, 4875.0, 4876.0, 4877.0, 4878.0, 4879.0],     [4880.0, 4881.0, 4882.0, 4883.0, 4884.0, 4885.0, 4886.0],     [4887.0, 4888.0, 4889.0, 4890.0, 4891.0, 4892.0, 4893.0],     [4894.0, 4895.0, 4896.0, 4897.0, 4898.0, 4899.0, 4900.0],     [4901.0, 4902.0, 4903.0, 4904.0, 4905.0, 4906.0, 4907.0],     [4908.0, 4909.0, 4910.0, 4911.0, 4912.0, 4913.0, 4914.0]],    [[4915.0, 4916.0, 4917.0, 4918.0, 4919.0, 4920.0, 4921.0],     [4922.0, 4923.0, 4924.0, 4925.0, 4926.0, 4927.0, 4928.0],     [4929.0, 4930.0, 4931.0, 4932.0, 4933.0, 4934.0, 4935.0],     [4936.0, 4937.0, 4938.0, 4939.0, 4940.0, 4941.0, 4942.0],     [4943.0, 4944.0, 4945.0, 4946.0, 4947.0, 4948.0, 4949.0],     [4950.0, 4951.0, 4952.0, 4953.0, 4954.0, 4955.0, 4956.0]],    [[4957.0, 4958.0, 4959.0, 4960.0, 4961.0, 4962.0, 4963.0],     [4964.0, 4965.0, 4966.0, 4967.0, 4968.0, 4969.0, 4970.0],     [4971.0, 4972.0, 4973.0, 4974.0, 4975.0, 4976.0, 4977.0],     [4978.0, 4979.0, 4980.0, 4981.0, 4982.0, 4983.0, 4984.0],     [4985.0, 4986.0, 4987.0, 4988.0, 4989.0, 4990.0, 4991.0],     [4992.0, 4993.0, 4994.0, 4995.0, 4996.0, 4997.0, 4998.0]]],   [[[4999.0, 5000.0, 5001.0, 5002.0, 5003.0, 5004.0, 5005.0],     [5006.0, 5007.0, 5008.0, 5009.0, 5010.0, 5011.0, 5012.0],     [5013.0, 5014.0, 5015.0, 5016.0, 5017.0, 5018.0, 5019.0],     [5020.0, 5021.0, 5022.0, 5023.0, 5024.0, 5025.0, 5026.0],     [5027.0, 5028.0, 5029.0, 5030.0, 5031.0, 5032.0, 5033.0],     [5034.0, 5035.0, 5036.0, 5037.0, 5038.0, 5039.0, 5040.0]],    [[5041.0, 5042.0, 5043.0, 5044.0, 5045.0, 5046.0, 5047.0],     [5048.0, 5049.0, 5050.0, 5051.0, 5052.0, 5053.0, 5054.0],     [5055.0, 5056.0, 5057.0, 5058.0, 5059.0, 5060.0, 5061.0],     [5062.0, 5063.0, 5064.0, 5065.0, 5066.0, 5067.0, 5068.0],     [5069.0, 5070.0, 5071.0, 5072.0, 5073.0, 5074.0, 5075.0],     [5076.0, 5077.0, 5078.0, 5079.0, 5080.0, 5081.0, 5082.0]],    [[5083.0, 5084.0, 5085.0, 5086.0, 5087.0, 5088.0, 5089.0],     [5090.0, 5091.0, 5092.0, 5093.0, 5094.0, 5095.0, 5096.0],     [5097.0, 5098.0, 5099.0, 5100.0, 5101.0, 5102.0, 5103.0],     [5104.0, 5105.0, 5106.0, 5107.0, 5108.0, 5109.0, 5110.0],     [5111.0, 5112.0, 5113.0, 5114.0, 5115.0, 5116.0, 5117.0],     [5118.0, 5119.0, 5120.0, 5121.0, 5122.0, 5123.0, 5124.0]],    [[5125.0, 5126.0, 5127.0, 5128.0, 5129.0, 5130.0, 5131.0],     [5132.0, 5133.0, 5134.0, 5135.0, 5136.0, 5137.0, 5138.0],     [5139.0, 5140.0, 5141.0, 5142.0, 5143.0, 5144.0, 5145.0],     [5146.0, 5147.0, 5148.0, 5149.0, 5150.0, 5151.0, 5152.0],     [5153.0, 5154.0, 5155.0, 5156.0, 5157.0, 5158.0, 5159.0],     [5160.0, 5161.0, 5162.0, 5163.0, 5164.0, 5165.0, 5166.0]],    [[5167.0, 5168.0, 5169.0, 5170.0, 5171.0, 5172.0, 5173.0],     [5174.0, 5175.0, 5176.0, 5177.0, 5178.0, 5179.0, 5180.0],     [5181.0, 5182.0, 5183.0, 5184.0, 5185.0, 5186.0, 5187.0],     [5188.0, 5189.0, 5190.0, 5191.0, 5192.0, 5193.0, 5194.0],     [5195.0, 5196.0, 5197.0, 5198.0, 5199.0, 5200.0, 5201.0],     [5202.0, 5203.0, 5204.0, 5205.0, 5206.0, 5207.0, 5208.0]],    [[5209.0, 5210.0, 5211.0, 5212.0, 5213.0, 5214.0, 5215.0],     [5216.0, 5217.0, 5218.0, 5219.0, 5220.0, 5221.0, 5222.0],     [5223.0, 5224.0, 5225.0, 5226.0, 5227.0, 5228.0, 5229.0],     [5230.0, 5231.0, 5232.0, 5233.0, 5234.0, 5235.0, 5236.0],     [5237.0, 5238.0, 5239.0, 5240.0, 5241.0, 5242.0, 5243.0],     [5244.0, 5245.0, 5246.0, 5247.0, 5248.0, 5249.0, 5250.0]],    [[5251.0, 5252.0, 5253.0, 5254.0, 5255.0, 5256.0, 5257.0],     [5258.0, 5259.0, 5260.0, 5261.0, 5262.0, 5263.0, 5264.0],     [5265.0, 5266.0, 5267.0, 5268.0, 5269.0, 5270.0, 5271.0],     [5272.0, 5273.0, 5274.0, 5275.0, 5276.0, 5277.0, 5278.0],     [5279.0, 5280.0, 5281.0, 5282.0, 5283.0, 5284.0, 5285.0],     [5286.0, 5287.0, 5288.0, 5289.0, 5290.0, 5291.0, 5292.0]]],   [[[5293.0, 5294.0, 5295.0, 5296.0, 5297.0, 5298.0, 5299.0],     [5300.0, 5301.0, 5302.0, 5303.0, 5304.0, 5305.0, 5306.0],     [5307.0, 5308.0, 5309.0, 5310.0, 5311.0, 5312.0, 5313.0],     [5314.0, 5315.0, 5316.0, 5317.0, 5318.0, 5319.0, 5320.0],     [5321.0, 5322.0, 5323.0, 5324.0, 5325.0, 5326.0, 5327.0],     [5328.0, 5329.0, 5330.0, 5331.0, 5332.0, 5333.0, 5334.0]],    [[5335.0, 5336.0, 5337.0, 5338.0, 5339.0, 5340.0, 5341.0],     [5342.0, 5343.0, 5344.0, 5345.0, 5346.0, 5347.0, 5348.0],     [5349.0, 5350.0, 5351.0, 5352.0, 5353.0, 5354.0, 5355.0],     [5356.0, 5357.0, 5358.0, 5359.0, 5360.0, 5361.0, 5362.0],     [5363.0, 5364.0, 5365.0, 5366.0, 5367.0, 5368.0, 5369.0],     [5370.0, 5371.0, 5372.0, 5373.0, 5374.0, 5375.0, 5376.0]],    [[5377.0, 5378.0, 5379.0, 5380.0, 5381.0, 5382.0, 5383.0],     [5384.0, 5385.0, 5386.0, 5387.0, 5388.0, 5389.0, 5390.0],     [5391.0, 5392.0, 5393.0, 5394.0, 5395.0, 5396.0, 5397.0],     [5398.0, 5399.0, 5400.0, 5401.0, 5402.0, 5403.0, 5404.0],     [5405.0, 5406.0, 5407.0, 5408.0, 5409.0, 5410.0, 5411.0],     [5412.0, 5413.0, 5414.0, 5415.0, 5416.0, 5417.0, 5418.0]],    [[5419.0, 5420.0, 5421.0, 5422.0, 5423.0, 5424.0, 5425.0],     [5426.0, 5427.0, 5428.0, 5429.0, 5430.0, 5431.0, 5432.0],     [5433.0, 5434.0, 5435.0, 5436.0, 5437.0, 5438.0, 5439.0],     [5440.0, 5441.0, 5442.0, 5443.0, 5444.0, 5445.0, 5446.0],     [5447.0, 5448.0, 5449.0, 5450.0, 5451.0, 5452.0, 5453.0],     [5454.0, 5455.0, 5456.0, 5457.0, 5458.0, 5459.0, 5460.0]],    [[5461.0, 5462.0, 5463.0, 5464.0, 5465.0, 5466.0, 5467.0],     [5468.0, 5469.0, 5470.0, 5471.0, 5472.0, 5473.0, 5474.0],     [5475.0, 5476.0, 5477.0, 5478.0, 5479.0, 5480.0, 5481.0],     [5482.0, 5483.0, 5484.0, 5485.0, 5486.0, 5487.0, 5488.0],     [5489.0, 5490.0, 5491.0, 5492.0, 5493.0, 5494.0, 5495.0],     [5496.0, 5497.0, 5498.0, 5499.0, 5500.0, 5501.0, 5502.0]],    [[5503.0, 5504.0, 5505.0, 5506.0, 5507.0, 5508.0, 5509.0],     [5510.0, 5511.0, 5512.0, 5513.0, 5514.0, 5515.0, 5516.0],     [5517.0, 5518.0, 5519.0, 5520.0, 5521.0, 5522.0, 5523.0],     [5524.0, 5525.0, 5526.0, 5527.0, 5528.0, 5529.0, 5530.0],     [5531.0, 5532.0, 5533.0, 5534.0, 5535.0, 5536.0, 5537.0],     [5538.0, 5539.0, 5540.0, 5541.0, 5542.0, 5543.0, 5544.0]],    [[5545.0, 5546.0, 5547.0, 5548.0, 5549.0, 5550.0, 5551.0],     [5552.0, 5553.0, 5554.0, 5555.0, 5556.0, 5557.0, 5558.0],     [5559.0, 5560.0, 5561.0, 5562.0, 5563.0, 5564.0, 5565.0],     [5566.0, 5567.0, 5568.0, 5569.0, 5570.0, 5571.0, 5572.0],     [5573.0, 5574.0, 5575.0, 5576.0, 5577.0, 5578.0, 5579.0],     [5580.0, 5581.0, 5582.0, 5583.0, 5584.0, 5585.0, 5586.0]]],   [[[5587.0, 5588.0, 5589.0, 5590.0, 5591.0, 5592.0, 5593.0],     [5594.0, 5595.0, 5596.0, 5597.0, 5598.0, 5599.0, 5600.0],     [5601.0, 5602.0, 5603.0, 5604.0, 5605.0, 5606.0, 5607.0],     [5608.0, 5609.0, 5610.0, 5611.0, 5612.0, 5613.0, 5614.0],     [5615.0, 5616.0, 5617.0, 5618.0, 5619.0, 5620.0, 5621.0],     [5622.0, 5623.0, 5624.0, 5625.0, 5626.0, 5627.0, 5628.0]],    [[5629.0, 5630.0, 5631.0, 5632.0, 5633.0, 5634.0, 5635.0],     [5636.0, 5637.0, 5638.0, 5639.0, 5640.0, 5641.0, 5642.0],     [5643.0, 5644.0, 5645.0, 5646.0, 5647.0, 5648.0, 5649.0],     [5650.0, 5651.0, 5652.0, 5653.0, 5654.0, 5655.0, 5656.0],     [5657.0, 5658.0, 5659.0, 5660.0, 5661.0, 5662.0, 5663.0],     [5664.0, 5665.0, 5666.0, 5667.0, 5668.0, 5669.0, 5670.0]],    [[5671.0, 5672.0, 5673.0, 5674.0, 5675.0, 5676.0, 5677.0],     [5678.0, 5679.0, 5680.0, 5681.0, 5682.0, 5683.0, 5684.0],     [5685.0, 5686.0, 5687.0, 5688.0, 5689.0, 5690.0, 5691.0],     [5692.0, 5693.0, 5694.0, 5695.0, 5696.0, 5697.0, 5698.0],     [5699.0, 5700.0, 5701.0, 5702.0, 5703.0, 5704.0, 5705.0],     [5706.0, 5707.0, 5708.0, 5709.0, 5710.0, 5711.0, 5712.0]],    [[5713.0, 5714.0, 5715.0, 5716.0, 5717.0, 5718.0, 5719.0],     [5720.0, 5721.0, 5722.0, 5723.0, 5724.0, 5725.0, 5726.0],     [5727.0, 5728.0, 5729.0, 5730.0, 5731.0, 5732.0, 5733.0],     [5734.0, 5735.0, 5736.0, 5737.0, 5738.0, 5739.0, 5740.0],     [5741.0, 5742.0, 5743.0, 5744.0, 5745.0, 5746.0, 5747.0],     [5748.0, 5749.0, 5750.0, 5751.0, 5752.0, 5753.0, 5754.0]],    [[5755.0, 5756.0, 5757.0, 5758.0, 5759.0, 5760.0, 5761.0],     [5762.0, 5763.0, 5764.0, 5765.0, 5766.0, 5767.0, 5768.0],     [5769.0, 5770.0, 5771.0, 5772.0, 5773.0, 5774.0, 5775.0],     [5776.0, 5777.0, 5778.0, 5779.0, 5780.0, 5781.0, 5782.0],     [5783.0, 5784.0, 5785.0, 5786.0, 5787.0, 5788.0, 5789.0],     [5790.0, 5791.0, 5792.0, 5793.0, 5794.0, 5795.0, 5796.0]],    [[5797.0, 5798.0, 5799.0, 5800.0, 5801.0, 5802.0, 5803.0],     [5804.0, 5805.0, 5806.0, 5807.0, 5808.0, 5809.0, 5810.0],     [5811.0, 5812.0, 5813.0, 5814.0, 5815.0, 5816.0, 5817.0],     [5818.0, 5819.0, 5820.0, 5821.0, 5822.0, 5823.0, 5824.0],     [5825.0, 5826.0, 5827.0, 5828.0, 5829.0, 5830.0, 5831.0],     [5832.0, 5833.0, 5834.0, 5835.0, 5836.0, 5837.0, 5838.0]],    [[5839.0, 5840.0, 5841.0, 5842.0, 5843.0, 5844.0, 5845.0],     [5846.0, 5847.0, 5848.0, 5849.0, 5850.0, 5851.0, 5852.0],     [5853.0, 5854.0, 5855.0, 5856.0, 5857.0, 5858.0, 5859.0],     [5860.0, 5861.0, 5862.0, 5863.0, 5864.0, 5865.0, 5866.0],     [5867.0, 5868.0, 5869.0, 5870.0, 5871.0, 5872.0, 5873.0],     [5874.0, 5875.0, 5876.0, 5877.0, 5878.0, 5879.0, 5880.0]]],   [[[5881.0, 5882.0, 5883.0, 5884.0, 5885.0, 5886.0, 5887.0],     [5888.0, 5889.0, 5890.0, 5891.0, 5892.0, 5893.0, 5894.0],     [5895.0, 5896.0, 5897.0, 5898.0, 5899.0, 5900.0, 5901.0],     [5902.0, 5903.0, 5904.0, 5905.0, 5906.0, 5907.0, 5908.0],     [5909.0, 5910.0, 5911.0, 5912.0, 5913.0, 5914.0, 5915.0],     [5916.0, 5917.0, 5918.0, 5919.0, 5920.0, 5921.0, 5922.0]],    [[5923.0, 5924.0, 5925.0, 5926.0, 5927.0, 5928.0, 5929.0],     [5930.0, 5931.0, 5932.0, 5933.0, 5934.0, 5935.0, 5936.0],     [5937.0, 5938.0, 5939.0, 5940.0, 5941.0, 5942.0, 5943.0],     [5944.0, 5945.0, 5946.0, 5947.0, 5948.0, 5949.0, 5950.0],     [5951.0, 5952.0, 5953.0, 5954.0, 5955.0, 5956.0, 5957.0],     [5958.0, 5959.0, 5960.0, 5961.0, 5962.0, 5963.0, 5964.0]],    [[5965.0, 5966.0, 5967.0, 5968.0, 5969.0, 5970.0, 5971.0],     [5972.0, 5973.0, 5974.0, 5975.0, 5976.0, 5977.0, 5978.0],     [5979.0, 5980.0, 5981.0, 5982.0, 5983.0, 5984.0, 5985.0],     [5986.0, 5987.0, 5988.0, 5989.0, 5990.0, 5991.0, 5992.0],     [5993.0, 5994.0, 5995.0, 5996.0, 5997.0, 5998.0, 5999.0],     [6000.0, 6001.0, 6002.0, 6003.0, 6004.0, 6005.0, 6006.0]],    [[6007.0, 6008.0, 6009.0, 6010.0, 6011.0, 6012.0, 6013.0],     [6014.0, 6015.0, 6016.0, 6017.0, 6018.0, 6019.0, 6020.0],     [6021.0, 6022.0, 6023.0, 6024.0, 6025.0, 6026.0, 6027.0],     [6028.0, 6029.0, 6030.0, 6031.0, 6032.0, 6033.0, 6034.0],     [6035.0, 6036.0, 6037.0, 6038.0, 6039.0, 6040.0, 6041.0],     [6042.0, 6043.0, 6044.0, 6045.0, 6046.0, 6047.0, 6048.0]],    [[6049.0, 6050.0, 6051.0, 6052.0, 6053.0, 6054.0, 6055.0],     [6056.0, 6057.0, 6058.0, 6059.0, 6060.0, 6061.0, 6062.0],     [6063.0, 6064.0, 6065.0, 6066.0, 6067.0, 6068.0, 6069.0],     [6070.0, 6071.0, 6072.0, 6073.0, 6074.0, 6075.0, 6076.0],     [6077.0, 6078.0, 6079.0, 6080.0, 6081.0, 6082.0, 6083.0],     [6084.0, 6085.0, 6086.0, 6087.0, 6088.0, 6089.0, 6090.0]],    [[6091.0, 6092.0, 6093.0, 6094.0, 6095.0, 6096.0, 6097.0],     [6098.0, 6099.0, 6100.0, 6101.0, 6102.0, 6103.0, 6104.0],     [6105.0, 6106.0, 6107.0, 6108.0, 6109.0, 6110.0, 6111.0],     [6112.0, 6113.0, 6114.0, 6115.0, 6116.0, 6117.0, 6118.0],     [6119.0, 6120.0, 6121.0, 6122.0, 6123.0, 6124.0, 6125.0],     [6126.0, 6127.0, 6128.0, 6129.0, 6130.0, 6131.0, 6132.0]],    [[6133.0, 6134.0, 6135.0, 6136.0, 6137.0, 6138.0, 6139.0],     [6140.0, 6141.0, 6142.0, 6143.0, 6144.0, 6145.0, 6146.0],     [6147.0, 6148.0, 6149.0, 6150.0, 6151.0, 6152.0, 6153.0],     [6154.0, 6155.0, 6156.0, 6157.0, 6158.0, 6159.0, 6160.0],     [6161.0, 6162.0, 6163.0, 6164.0, 6165.0, 6166.0, 6167.0],     [6168.0, 6169.0, 6170.0, 6171.0, 6172.0, 6173.0, 6174.0]]]]] shape=[3, 7, 7, 6, 7], strides=[2058, 294, 42, 7, 1], layout=C (0x1)), I32([1] shape=[1], strides=[1], layout=C | F (0x3)), I32([[2, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 2128799455 550984655 646522774 3494772426 # shrinks to (ref b, ref bs, ref c) = (F32([[[[0.0],    [0.0]],   [[0.0],    [0.0]],   [[0.0],    [2.0]],   [[0.0],    [4.0]],   [[0.0],    [6.0]],   [[0.0],    [0.0]]],  [[[0.0],    [0.0]],   [[0.0],    [0.0]],   [[1.0],    [0.0]],   [[3.0],    [0.0]],   [[5.0],    [0.0]],   [[0.0],    [0.0]]]] shape=[2, 6, 2, 1], strides=[12, 2, 1, 1], layout=C (0x1)), I32([1, 2] shape=[2], strides=[1], layout=C | F (0x3)), I32([[2, 1],  [1, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 661129098 2787562587 2664391140 2978828846 # shrinks to (ref b, ref bs, ref c) = (F32([[[[[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],     [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],     [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]]],   [[[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],     [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],     [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]]],   [[[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, 16.0, 17.0, 18.0],     [19.0, 20.0, 21.0, 22.0, 23.0, 24.0],     [25.0, 26.0, 27.0, 28.0, 29.0, 30.0],     [31.0, 32.0, 33.0, 34.0, 35.0, 36.0]],    [[37.0, 38.0, 39.0, 40.0, 41.0, 42.0],     [43.0, 44.0, 45.0, 46.0, 47.0, 48.0],     [49.0, 50.0, 51.0, 52.0, 53.0, 54.0],     [55.0, 56.0, 57.0, 58.0, 59.0, 60.0],     [61.0, 62.0, 63.0, 64.0, 65.0, 66.0],     [67.0, 68.0, 69.0, 70.0, 71.0, 72.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.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.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.0]]]]] shape=[1, 4, 2, 6, 6], strides=[288, 72, 36, 6, 1], layout=C (0x1)), I32([1] shape=[1], strides=[1], layout=C | F (0x3)), I32([[2, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 3999413022 1951376667 363951431 121259509 # shrinks to (ref b, ref bs, ref c) = (F32([[[[[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]],    [[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]],    [[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]],    [[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]],    [[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]]],   [[[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]],    [[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]],    [[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]],    [[1.0, 2.0, 3.0, 4.0],     [5.0, 6.0, 7.0, 8.0],     [9.0, 10.0, 11.0, 12.0]],    [[25.0, 26.0, 27.0, 28.0],     [29.0, 30.0, 31.0, 32.0],     [33.0, 34.0, 35.0, 36.0]],    [[49.0, 50.0, 51.0, 52.0],     [53.0, 54.0, 55.0, 56.0],     [57.0, 58.0, 59.0, 60.0]],    [[73.0, 74.0, 75.0, 76.0],     [77.0, 78.0, 79.0, 80.0],     [81.0, 82.0, 83.0, 84.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]],    [[85.0, 86.0, 87.0, 88.0],     [89.0, 90.0, 91.0, 92.0],     [93.0, 94.0, 95.0, 96.0]],    [[109.0, 110.0, 111.0, 112.0],     [113.0, 114.0, 115.0, 116.0],     [117.0, 118.0, 119.0, 120.0]],    [[133.0, 134.0, 135.0, 136.0],     [137.0, 138.0, 139.0, 140.0],     [141.0, 142.0, 143.0, 144.0]],    [[157.0, 158.0, 159.0, 160.0],     [161.0, 162.0, 163.0, 164.0],     [165.0, 166.0, 167.0, 168.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]],    [[169.0, 170.0, 171.0, 172.0],     [173.0, 174.0, 175.0, 176.0],     [177.0, 178.0, 179.0, 180.0]],    [[193.0, 194.0, 195.0, 196.0],     [197.0, 198.0, 199.0, 200.0],     [201.0, 202.0, 203.0, 204.0]],    [[217.0, 218.0, 219.0, 220.0],     [221.0, 222.0, 223.0, 224.0],     [225.0, 226.0, 227.0, 228.0]],    [[241.0, 242.0, 243.0, 244.0],     [245.0, 246.0, 247.0, 248.0],     [249.0, 250.0, 251.0, 252.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]],    [[253.0, 254.0, 255.0, 256.0],     [257.0, 258.0, 259.0, 260.0],     [261.0, 262.0, 263.0, 264.0]],    [[277.0, 278.0, 279.0, 280.0],     [281.0, 282.0, 283.0, 284.0],     [285.0, 286.0, 287.0, 288.0]],    [[301.0, 302.0, 303.0, 304.0],     [305.0, 306.0, 307.0, 308.0],     [309.0, 310.0, 311.0, 312.0]],    [[325.0, 326.0, 327.0, 328.0],     [329.0, 330.0, 331.0, 332.0],     [333.0, 334.0, 335.0, 336.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]],    [[337.0, 338.0, 339.0, 340.0],     [341.0, 342.0, 343.0, 344.0],     [345.0, 346.0, 347.0, 348.0]],    [[361.0, 362.0, 363.0, 364.0],     [365.0, 366.0, 367.0, 368.0],     [369.0, 370.0, 371.0, 372.0]],    [[385.0, 386.0, 387.0, 388.0],     [389.0, 390.0, 391.0, 392.0],     [393.0, 394.0, 395.0, 396.0]],    [[409.0, 410.0, 411.0, 412.0],     [413.0, 414.0, 415.0, 416.0],     [417.0, 418.0, 419.0, 420.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]],    [[421.0, 422.0, 423.0, 424.0],     [425.0, 426.0, 427.0, 428.0],     [429.0, 430.0, 431.0, 432.0]],    [[445.0, 446.0, 447.0, 448.0],     [449.0, 450.0, 451.0, 452.0],     [453.0, 454.0, 455.0, 456.0]],    [[469.0, 470.0, 471.0, 472.0],     [473.0, 474.0, 475.0, 476.0],     [477.0, 478.0, 479.0, 480.0]],    [[493.0, 494.0, 495.0, 496.0],     [497.0, 498.0, 499.0, 500.0],     [501.0, 502.0, 503.0, 504.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]],    [[505.0, 506.0, 507.0, 508.0],     [509.0, 510.0, 511.0, 512.0],     [513.0, 514.0, 515.0, 516.0]],    [[529.0, 530.0, 531.0, 532.0],     [533.0, 534.0, 535.0, 536.0],     [537.0, 538.0, 539.0, 540.0]],    [[553.0, 554.0, 555.0, 556.0],     [557.0, 558.0, 559.0, 560.0],     [561.0, 562.0, 563.0, 564.0]],    [[577.0, 578.0, 579.0, 580.0],     [581.0, 582.0, 583.0, 584.0],     [585.0, 586.0, 587.0, 588.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.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.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.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.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.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.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.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.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.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.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]],    [[589.0, 590.0, 591.0, 592.0],     [593.0, 594.0, 595.0, 596.0],     [597.0, 598.0, 599.0, 600.0]],    [[613.0, 614.0, 615.0, 616.0],     [617.0, 618.0, 619.0, 620.0],     [621.0, 622.0, 623.0, 624.0]],    [[637.0, 638.0, 639.0, 640.0],     [641.0, 642.0, 643.0, 644.0],     [645.0, 646.0, 647.0, 648.0]],    [[661.0, 662.0, 663.0, 664.0],     [665.0, 666.0, 667.0, 668.0],     [669.0, 670.0, 671.0, 672.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]],    [[673.0, 674.0, 675.0, 676.0],     [677.0, 678.0, 679.0, 680.0],     [681.0, 682.0, 683.0, 684.0]],    [[697.0, 698.0, 699.0, 700.0],     [701.0, 702.0, 703.0, 704.0],     [705.0, 706.0, 707.0, 708.0]],    [[721.0, 722.0, 723.0, 724.0],     [725.0, 726.0, 727.0, 728.0],     [729.0, 730.0, 731.0, 732.0]],    [[745.0, 746.0, 747.0, 748.0],     [749.0, 750.0, 751.0, 752.0],     [753.0, 754.0, 755.0, 756.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]],    [[757.0, 758.0, 759.0, 760.0],     [761.0, 762.0, 763.0, 764.0],     [765.0, 766.0, 767.0, 768.0]],    [[781.0, 782.0, 783.0, 784.0],     [785.0, 786.0, 787.0, 788.0],     [789.0, 790.0, 791.0, 792.0]],    [[805.0, 806.0, 807.0, 808.0],     [809.0, 810.0, 811.0, 812.0],     [813.0, 814.0, 815.0, 816.0]],    [[829.0, 830.0, 831.0, 832.0],     [833.0, 834.0, 835.0, 836.0],     [837.0, 838.0, 839.0, 840.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]],    [[841.0, 842.0, 843.0, 844.0],     [845.0, 846.0, 847.0, 848.0],     [849.0, 850.0, 851.0, 852.0]],    [[865.0, 866.0, 867.0, 868.0],     [869.0, 870.0, 871.0, 872.0],     [873.0, 874.0, 875.0, 876.0]],    [[889.0, 890.0, 891.0, 892.0],     [893.0, 894.0, 895.0, 896.0],     [897.0, 898.0, 899.0, 900.0]],    [[913.0, 914.0, 915.0, 916.0],     [917.0, 918.0, 919.0, 920.0],     [921.0, 922.0, 923.0, 924.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]],    [[925.0, 926.0, 927.0, 928.0],     [929.0, 930.0, 931.0, 932.0],     [933.0, 934.0, 935.0, 936.0]],    [[949.0, 950.0, 951.0, 952.0],     [953.0, 954.0, 955.0, 956.0],     [957.0, 958.0, 959.0, 960.0]],    [[973.0, 974.0, 975.0, 976.0],     [977.0, 978.0, 979.0, 980.0],     [981.0, 982.0, 983.0, 984.0]],    [[997.0, 998.0, 999.0, 1000.0],     [1001.0, 1002.0, 1003.0, 1004.0],     [1005.0, 1006.0, 1007.0, 1008.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]],    [[1009.0, 1010.0, 1011.0, 1012.0],     [1013.0, 1014.0, 1015.0, 1016.0],     [1017.0, 1018.0, 1019.0, 1020.0]],    [[1033.0, 1034.0, 1035.0, 1036.0],     [1037.0, 1038.0, 1039.0, 1040.0],     [1041.0, 1042.0, 1043.0, 1044.0]],    [[1057.0, 1058.0, 1059.0, 1060.0],     [1061.0, 1062.0, 1063.0, 1064.0],     [1065.0, 1066.0, 1067.0, 1068.0]],    [[1081.0, 1082.0, 1083.0, 1084.0],     [1085.0, 1086.0, 1087.0, 1088.0],     [1089.0, 1090.0, 1091.0, 1092.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]],    [[1093.0, 1094.0, 1095.0, 1096.0],     [1097.0, 1098.0, 1099.0, 1100.0],     [1101.0, 1102.0, 1103.0, 1104.0]],    [[1117.0, 1118.0, 1119.0, 1120.0],     [1121.0, 1122.0, 1123.0, 1124.0],     [1125.0, 1126.0, 1127.0, 1128.0]],    [[1141.0, 1142.0, 1143.0, 1144.0],     [1145.0, 1146.0, 1147.0, 1148.0],     [1149.0, 1150.0, 1151.0, 1152.0]],    [[1165.0, 1166.0, 1167.0, 1168.0],     [1169.0, 1170.0, 1171.0, 1172.0],     [1173.0, 1174.0, 1175.0, 1176.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.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.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.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.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.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.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.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.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.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.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]],    [[1177.0, 1178.0, 1179.0, 1180.0],     [1181.0, 1182.0, 1183.0, 1184.0],     [1185.0, 1186.0, 1187.0, 1188.0]],    [[1201.0, 1202.0, 1203.0, 1204.0],     [1205.0, 1206.0, 1207.0, 1208.0],     [1209.0, 1210.0, 1211.0, 1212.0]],    [[1225.0, 1226.0, 1227.0, 1228.0],     [1229.0, 1230.0, 1231.0, 1232.0],     [1233.0, 1234.0, 1235.0, 1236.0]],    [[1249.0, 1250.0, 1251.0, 1252.0],     [1253.0, 1254.0, 1255.0, 1256.0],     [1257.0, 1258.0, 1259.0, 1260.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]],    [[1261.0, 1262.0, 1263.0, 1264.0],     [1265.0, 1266.0, 1267.0, 1268.0],     [1269.0, 1270.0, 1271.0, 1272.0]],    [[1285.0, 1286.0, 1287.0, 1288.0],     [1289.0, 1290.0, 1291.0, 1292.0],     [1293.0, 1294.0, 1295.0, 1296.0]],    [[1309.0, 1310.0, 1311.0, 1312.0],     [1313.0, 1314.0, 1315.0, 1316.0],     [1317.0, 1318.0, 1319.0, 1320.0]],    [[1333.0, 1334.0, 1335.0, 1336.0],     [1337.0, 1338.0, 1339.0, 1340.0],     [1341.0, 1342.0, 1343.0, 1344.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]],    [[1345.0, 1346.0, 1347.0, 1348.0],     [1349.0, 1350.0, 1351.0, 1352.0],     [1353.0, 1354.0, 1355.0, 1356.0]],    [[1369.0, 1370.0, 1371.0, 1372.0],     [1373.0, 1374.0, 1375.0, 1376.0],     [1377.0, 1378.0, 1379.0, 1380.0]],    [[1393.0, 1394.0, 1395.0, 1396.0],     [1397.0, 1398.0, 1399.0, 1400.0],     [1401.0, 1402.0, 1403.0, 1404.0]],    [[1417.0, 1418.0, 1419.0, 1420.0],     [1421.0, 1422.0, 1423.0, 1424.0],     [1425.0, 1426.0, 1427.0, 1428.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]],    [[1429.0, 1430.0, 1431.0, 1432.0],     [1433.0, 1434.0, 1435.0, 1436.0],     [1437.0, 1438.0, 1439.0, 1440.0]],    [[1453.0, 1454.0, 1455.0, 1456.0],     [1457.0, 1458.0, 1459.0, 1460.0],     [1461.0, 1462.0, 1463.0, 1464.0]],    [[1477.0, 1478.0, 1479.0, 1480.0],     [1481.0, 1482.0, 1483.0, 1484.0],     [1485.0, 1486.0, 1487.0, 1488.0]],    [[1501.0, 1502.0, 1503.0, 1504.0],     [1505.0, 1506.0, 1507.0, 1508.0],     [1509.0, 1510.0, 1511.0, 1512.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]],    [[1513.0, 1514.0, 1515.0, 1516.0],     [1517.0, 1518.0, 1519.0, 1520.0],     [1521.0, 1522.0, 1523.0, 1524.0]],    [[1537.0, 1538.0, 1539.0, 1540.0],     [1541.0, 1542.0, 1543.0, 1544.0],     [1545.0, 1546.0, 1547.0, 1548.0]],    [[1561.0, 1562.0, 1563.0, 1564.0],     [1565.0, 1566.0, 1567.0, 1568.0],     [1569.0, 1570.0, 1571.0, 1572.0]],    [[1585.0, 1586.0, 1587.0, 1588.0],     [1589.0, 1590.0, 1591.0, 1592.0],     [1593.0, 1594.0, 1595.0, 1596.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]],    [[1597.0, 1598.0, 1599.0, 1600.0],     [1601.0, 1602.0, 1603.0, 1604.0],     [1605.0, 1606.0, 1607.0, 1608.0]],    [[1621.0, 1622.0, 1623.0, 1624.0],     [1625.0, 1626.0, 1627.0, 1628.0],     [1629.0, 1630.0, 1631.0, 1632.0]],    [[1645.0, 1646.0, 1647.0, 1648.0],     [1649.0, 1650.0, 1651.0, 1652.0],     [1653.0, 1654.0, 1655.0, 1656.0]],    [[1669.0, 1670.0, 1671.0, 1672.0],     [1673.0, 1674.0, 1675.0, 1676.0],     [1677.0, 1678.0, 1679.0, 1680.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]],    [[1681.0, 1682.0, 1683.0, 1684.0],     [1685.0, 1686.0, 1687.0, 1688.0],     [1689.0, 1690.0, 1691.0, 1692.0]],    [[1705.0, 1706.0, 1707.0, 1708.0],     [1709.0, 1710.0, 1711.0, 1712.0],     [1713.0, 1714.0, 1715.0, 1716.0]],    [[1729.0, 1730.0, 1731.0, 1732.0],     [1733.0, 1734.0, 1735.0, 1736.0],     [1737.0, 1738.0, 1739.0, 1740.0]],    [[1753.0, 1754.0, 1755.0, 1756.0],     [1757.0, 1758.0, 1759.0, 1760.0],     [1761.0, 1762.0, 1763.0, 1764.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.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.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.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.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.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.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.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.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.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.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]],    [[13.0, 14.0, 15.0, 16.0],     [17.0, 18.0, 19.0, 20.0],     [21.0, 22.0, 23.0, 24.0]],    [[37.0, 38.0, 39.0, 40.0],     [41.0, 42.0, 43.0, 44.0],     [45.0, 46.0, 47.0, 48.0]],    [[61.0, 62.0, 63.0, 64.0],     [65.0, 66.0, 67.0, 68.0],     [69.0, 70.0, 71.0, 72.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.0]],    [[97.0, 98.0, 99.0, 100.0],     [101.0, 102.0, 103.0, 104.0],     [105.0, 106.0, 107.0, 108.0]],    [[121.0, 122.0, 123.0, 124.0],     [125.0, 126.0, 127.0, 128.0],     [129.0, 130.0, 131.0, 132.0]],    [[145.0, 146.0, 147.0, 148.0],     [149.0, 150.0, 151.0, 152.0],     [153.0, 154.0, 155.0, 156.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.0]],    [[181.0, 182.0, 183.0, 184.0],     [185.0, 186.0, 187.0, 188.0],     [189.0, 190.0, 191.0, 192.0]],    [[205.0, 206.0, 207.0, 208.0],     [209.0, 210.0, 211.0, 212.0],     [213.0, 214.0, 215.0, 216.0]],    [[229.0, 230.0, 231.0, 232.0],     [233.0, 234.0, 235.0, 236.0],     [237.0, 238.0, 239.0, 240.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.0]],    [[265.0, 266.0, 267.0, 268.0],     [269.0, 270.0, 271.0, 272.0],     [273.0, 274.0, 275.0, 276.0]],    [[289.0, 290.0, 291.0, 292.0],     [293.0, 294.0, 295.0, 296.0],     [297.0, 298.0, 299.0, 300.0]],    [[313.0, 314.0, 315.0, 316.0],     [317.0, 318.0, 319.0, 320.0],     [321.0, 322.0, 323.0, 324.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.0]],    [[349.0, 350.0, 351.0, 352.0],     [353.0, 354.0, 355.0, 356.0],     [357.0, 358.0, 359.0, 360.0]],    [[373.0, 374.0, 375.0, 376.0],     [377.0, 378.0, 379.0, 380.0],     [381.0, 382.0, 383.0, 384.0]],    [[397.0, 398.0, 399.0, 400.0],     [401.0, 402.0, 403.0, 404.0],     [405.0, 406.0, 407.0, 408.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.0]],    [[433.0, 434.0, 435.0, 436.0],     [437.0, 438.0, 439.0, 440.0],     [441.0, 442.0, 443.0, 444.0]],    [[457.0, 458.0, 459.0, 460.0],     [461.0, 462.0, 463.0, 464.0],     [465.0, 466.0, 467.0, 468.0]],    [[481.0, 482.0, 483.0, 484.0],     [485.0, 486.0, 487.0, 488.0],     [489.0, 490.0, 491.0, 492.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.0]],    [[517.0, 518.0, 519.0, 520.0],     [521.0, 522.0, 523.0, 524.0],     [525.0, 526.0, 527.0, 528.0]],    [[541.0, 542.0, 543.0, 544.0],     [545.0, 546.0, 547.0, 548.0],     [549.0, 550.0, 551.0, 552.0]],    [[565.0, 566.0, 567.0, 568.0],     [569.0, 570.0, 571.0, 572.0],     [573.0, 574.0, 575.0, 576.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.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.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.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.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.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.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.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.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.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.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.0]],    [[601.0, 602.0, 603.0, 604.0],     [605.0, 606.0, 607.0, 608.0],     [609.0, 610.0, 611.0, 612.0]],    [[625.0, 626.0, 627.0, 628.0],     [629.0, 630.0, 631.0, 632.0],     [633.0, 634.0, 635.0, 636.0]],    [[649.0, 650.0, 651.0, 652.0],     [653.0, 654.0, 655.0, 656.0],     [657.0, 658.0, 659.0, 660.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.0]],    [[685.0, 686.0, 687.0, 688.0],     [689.0, 690.0, 691.0, 692.0],     [693.0, 694.0, 695.0, 696.0]],    [[709.0, 710.0, 711.0, 712.0],     [713.0, 714.0, 715.0, 716.0],     [717.0, 718.0, 719.0, 720.0]],    [[733.0, 734.0, 735.0, 736.0],     [737.0, 738.0, 739.0, 740.0],     [741.0, 742.0, 743.0, 744.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.0]],    [[769.0, 770.0, 771.0, 772.0],     [773.0, 774.0, 775.0, 776.0],     [777.0, 778.0, 779.0, 780.0]],    [[793.0, 794.0, 795.0, 796.0],     [797.0, 798.0, 799.0, 800.0],     [801.0, 802.0, 803.0, 804.0]],    [[817.0, 818.0, 819.0, 820.0],     [821.0, 822.0, 823.0, 824.0],     [825.0, 826.0, 827.0, 828.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.0]],    [[853.0, 854.0, 855.0, 856.0],     [857.0, 858.0, 859.0, 860.0],     [861.0, 862.0, 863.0, 864.0]],    [[877.0, 878.0, 879.0, 880.0],     [881.0, 882.0, 883.0, 884.0],     [885.0, 886.0, 887.0, 888.0]],    [[901.0, 902.0, 903.0, 904.0],     [905.0, 906.0, 907.0, 908.0],     [909.0, 910.0, 911.0, 912.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.0]],    [[937.0, 938.0, 939.0, 940.0],     [941.0, 942.0, 943.0, 944.0],     [945.0, 946.0, 947.0, 948.0]],    [[961.0, 962.0, 963.0, 964.0],     [965.0, 966.0, 967.0, 968.0],     [969.0, 970.0, 971.0, 972.0]],    [[985.0, 986.0, 987.0, 988.0],     [989.0, 990.0, 991.0, 992.0],     [993.0, 994.0, 995.0, 996.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.0]],    [[1021.0, 1022.0, 1023.0, 1024.0],     [1025.0, 1026.0, 1027.0, 1028.0],     [1029.0, 1030.0, 1031.0, 1032.0]],    [[1045.0, 1046.0, 1047.0, 1048.0],     [1049.0, 1050.0, 1051.0, 1052.0],     [1053.0, 1054.0, 1055.0, 1056.0]],    [[1069.0, 1070.0, 1071.0, 1072.0],     [1073.0, 1074.0, 1075.0, 1076.0],     [1077.0, 1078.0, 1079.0, 1080.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.0]],    [[1105.0, 1106.0, 1107.0, 1108.0],     [1109.0, 1110.0, 1111.0, 1112.0],     [1113.0, 1114.0, 1115.0, 1116.0]],    [[1129.0, 1130.0, 1131.0, 1132.0],     [1133.0, 1134.0, 1135.0, 1136.0],     [1137.0, 1138.0, 1139.0, 1140.0]],    [[1153.0, 1154.0, 1155.0, 1156.0],     [1157.0, 1158.0, 1159.0, 1160.0],     [1161.0, 1162.0, 1163.0, 1164.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.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.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.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.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.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.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.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.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.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.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.0]],    [[1189.0, 1190.0, 1191.0, 1192.0],     [1193.0, 1194.0, 1195.0, 1196.0],     [1197.0, 1198.0, 1199.0, 1200.0]],    [[1213.0, 1214.0, 1215.0, 1216.0],     [1217.0, 1218.0, 1219.0, 1220.0],     [1221.0, 1222.0, 1223.0, 1224.0]],    [[1237.0, 1238.0, 1239.0, 1240.0],     [1241.0, 1242.0, 1243.0, 1244.0],     [1245.0, 1246.0, 1247.0, 1248.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.0]],    [[1273.0, 1274.0, 1275.0, 1276.0],     [1277.0, 1278.0, 1279.0, 1280.0],     [1281.0, 1282.0, 1283.0, 1284.0]],    [[1297.0, 1298.0, 1299.0, 1300.0],     [1301.0, 1302.0, 1303.0, 1304.0],     [1305.0, 1306.0, 1307.0, 1308.0]],    [[1321.0, 1322.0, 1323.0, 1324.0],     [1325.0, 1326.0, 1327.0, 1328.0],     [1329.0, 1330.0, 1331.0, 1332.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.0]],    [[1357.0, 1358.0, 1359.0, 1360.0],     [1361.0, 1362.0, 1363.0, 1364.0],     [1365.0, 1366.0, 1367.0, 1368.0]],    [[1381.0, 1382.0, 1383.0, 1384.0],     [1385.0, 1386.0, 1387.0, 1388.0],     [1389.0, 1390.0, 1391.0, 1392.0]],    [[1405.0, 1406.0, 1407.0, 1408.0],     [1409.0, 1410.0, 1411.0, 1412.0],     [1413.0, 1414.0, 1415.0, 1416.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.0]],    [[1441.0, 1442.0, 1443.0, 1444.0],     [1445.0, 1446.0, 1447.0, 1448.0],     [1449.0, 1450.0, 1451.0, 1452.0]],    [[1465.0, 1466.0, 1467.0, 1468.0],     [1469.0, 1470.0, 1471.0, 1472.0],     [1473.0, 1474.0, 1475.0, 1476.0]],    [[1489.0, 1490.0, 1491.0, 1492.0],     [1493.0, 1494.0, 1495.0, 1496.0],     [1497.0, 1498.0, 1499.0, 1500.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.0]],    [[1525.0, 1526.0, 1527.0, 1528.0],     [1529.0, 1530.0, 1531.0, 1532.0],     [1533.0, 1534.0, 1535.0, 1536.0]],    [[1549.0, 1550.0, 1551.0, 1552.0],     [1553.0, 1554.0, 1555.0, 1556.0],     [1557.0, 1558.0, 1559.0, 1560.0]],    [[1573.0, 1574.0, 1575.0, 1576.0],     [1577.0, 1578.0, 1579.0, 1580.0],     [1581.0, 1582.0, 1583.0, 1584.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.0]],    [[1609.0, 1610.0, 1611.0, 1612.0],     [1613.0, 1614.0, 1615.0, 1616.0],     [1617.0, 1618.0, 1619.0, 1620.0]],    [[1633.0, 1634.0, 1635.0, 1636.0],     [1637.0, 1638.0, 1639.0, 1640.0],     [1641.0, 1642.0, 1643.0, 1644.0]],    [[1657.0, 1658.0, 1659.0, 1660.0],     [1661.0, 1662.0, 1663.0, 1664.0],     [1665.0, 1666.0, 1667.0, 1668.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.0]],    [[1693.0, 1694.0, 1695.0, 1696.0],     [1697.0, 1698.0, 1699.0, 1700.0],     [1701.0, 1702.0, 1703.0, 1704.0]],    [[1717.0, 1718.0, 1719.0, 1720.0],     [1721.0, 1722.0, 1723.0, 1724.0],     [1725.0, 1726.0, 1727.0, 1728.0]],    [[1741.0, 1742.0, 1743.0, 1744.0],     [1745.0, 1746.0, 1747.0, 1748.0],     [1749.0, 1750.0, 1751.0, 1752.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.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.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.0]]]]] shape=[6, 11, 5, 3, 4], strides=[660, 60, 12, 4, 1], layout=C (0x1)), I32([1, 2] shape=[2], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [2, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 3185783731 933522725 726568694 1737619865 # shrinks to (ref b, ref bs, ref c) = (F32([[[[0.0, 0.0, 0.0, 0.0],    [0.0, 0.0, 0.0, 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, 16.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.0],    [0.0, 0.0, 0.0, 0.0],    [0.0, 0.0, 0.0, 0.0]]]] shape=[1, 2, 7, 4], strides=[56, 28, 4, 1], layout=C (0x1)), I32([1, 1] shape=[2], strides=[1], layout=C | F (0x3)), I32([[0, 1],  [2, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 1312994265 4088602256 3635909562 3335228408 # shrinks to (ref b, ref bs, ref c) = (F32([[[[[[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, 16.0],      [17.0, 18.0, 19.0, 20.0]],     [[21.0, 22.0, 23.0, 24.0],      [25.0, 26.0, 27.0, 28.0],      [29.0, 30.0, 31.0, 32.0],      [33.0, 34.0, 35.0, 36.0],      [37.0, 38.0, 39.0, 40.0]],     [[41.0, 42.0, 43.0, 44.0],      [45.0, 46.0, 47.0, 48.0],      [49.0, 50.0, 51.0, 52.0],      [53.0, 54.0, 55.0, 56.0],      [57.0, 58.0, 59.0, 60.0]],     [[61.0, 62.0, 63.0, 64.0],      [65.0, 66.0, 67.0, 68.0],      [69.0, 70.0, 71.0, 72.0],      [73.0, 74.0, 75.0, 76.0],      [77.0, 78.0, 79.0, 80.0]]],    [[[81.0, 82.0, 83.0, 84.0],      [85.0, 86.0, 87.0, 88.0],      [89.0, 90.0, 91.0, 92.0],      [93.0, 94.0, 95.0, 96.0],      [97.0, 98.0, 99.0, 100.0]],     [[101.0, 102.0, 103.0, 104.0],      [105.0, 106.0, 107.0, 108.0],      [109.0, 110.0, 111.0, 112.0],      [113.0, 114.0, 115.0, 116.0],      [117.0, 118.0, 119.0, 120.0]],     [[121.0, 122.0, 123.0, 124.0],      [125.0, 126.0, 127.0, 128.0],      [129.0, 130.0, 131.0, 132.0],      [133.0, 134.0, 135.0, 136.0],      [137.0, 138.0, 139.0, 140.0]],     [[141.0, 142.0, 143.0, 144.0],      [145.0, 146.0, 147.0, 148.0],      [149.0, 150.0, 151.0, 152.0],      [153.0, 154.0, 155.0, 156.0],      [157.0, 158.0, 159.0, 160.0]]],    [[[161.0, 162.0, 163.0, 164.0],      [165.0, 166.0, 167.0, 168.0],      [169.0, 170.0, 171.0, 172.0],      [173.0, 174.0, 175.0, 176.0],      [177.0, 178.0, 179.0, 180.0]],     [[181.0, 182.0, 183.0, 184.0],      [185.0, 186.0, 187.0, 188.0],      [189.0, 190.0, 191.0, 192.0],      [193.0, 194.0, 195.0, 196.0],      [197.0, 198.0, 199.0, 200.0]],     [[201.0, 202.0, 203.0, 204.0],      [205.0, 206.0, 207.0, 208.0],      [209.0, 210.0, 211.0, 212.0],      [213.0, 214.0, 215.0, 216.0],      [217.0, 218.0, 219.0, 220.0]],     [[221.0, 222.0, 223.0, 224.0],      [225.0, 226.0, 227.0, 228.0],      [229.0, 230.0, 231.0, 232.0],      [233.0, 234.0, 235.0, 236.0],      [237.0, 238.0, 239.0, 240.0]]],    [[[241.0, 242.0, 243.0, 244.0],      [245.0, 246.0, 247.0, 248.0],      [249.0, 250.0, 251.0, 252.0],      [253.0, 254.0, 255.0, 256.0],      [257.0, 258.0, 259.0, 260.0]],     [[261.0, 262.0, 263.0, 264.0],      [265.0, 266.0, 267.0, 268.0],      [269.0, 270.0, 271.0, 272.0],      [273.0, 274.0, 275.0, 276.0],      [277.0, 278.0, 279.0, 280.0]],     [[281.0, 282.0, 283.0, 284.0],      [285.0, 286.0, 287.0, 288.0],      [289.0, 290.0, 291.0, 292.0],      [293.0, 294.0, 295.0, 296.0],      [297.0, 298.0, 299.0, 300.0]],     [[301.0, 302.0, 303.0, 304.0],      [305.0, 306.0, 307.0, 308.0],      [309.0, 310.0, 311.0, 312.0],      [313.0, 314.0, 315.0, 316.0],      [317.0, 318.0, 319.0, 320.0]]],    [[[321.0, 322.0, 323.0, 324.0],      [325.0, 326.0, 327.0, 328.0],      [329.0, 330.0, 331.0, 332.0],      [333.0, 334.0, 335.0, 336.0],      [337.0, 338.0, 339.0, 340.0]],     [[341.0, 342.0, 343.0, 344.0],      [345.0, 346.0, 347.0, 348.0],      [349.0, 350.0, 351.0, 352.0],      [353.0, 354.0, 355.0, 356.0],      [357.0, 358.0, 359.0, 360.0]],     [[361.0, 362.0, 363.0, 364.0],      [365.0, 366.0, 367.0, 368.0],      [369.0, 370.0, 371.0, 372.0],      [373.0, 374.0, 375.0, 376.0],      [377.0, 378.0, 379.0, 380.0]],     [[381.0, 382.0, 383.0, 384.0],      [385.0, 386.0, 387.0, 388.0],      [389.0, 390.0, 391.0, 392.0],      [393.0, 394.0, 395.0, 396.0],      [397.0, 398.0, 399.0, 400.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]],   [[[[1201.0, 1202.0, 1203.0, 1204.0],      [1205.0, 1206.0, 1207.0, 1208.0],      [1209.0, 1210.0, 1211.0, 1212.0],      [1213.0, 1214.0, 1215.0, 1216.0],      [1217.0, 1218.0, 1219.0, 1220.0]],     [[1221.0, 1222.0, 1223.0, 1224.0],      [1225.0, 1226.0, 1227.0, 1228.0],      [1229.0, 1230.0, 1231.0, 1232.0],      [1233.0, 1234.0, 1235.0, 1236.0],      [1237.0, 1238.0, 1239.0, 1240.0]],     [[1241.0, 1242.0, 1243.0, 1244.0],      [1245.0, 1246.0, 1247.0, 1248.0],      [1249.0, 1250.0, 1251.0, 1252.0],      [1253.0, 1254.0, 1255.0, 1256.0],      [1257.0, 1258.0, 1259.0, 1260.0]],     [[1261.0, 1262.0, 1263.0, 1264.0],      [1265.0, 1266.0, 1267.0, 1268.0],      [1269.0, 1270.0, 1271.0, 1272.0],      [1273.0, 1274.0, 1275.0, 1276.0],      [1277.0, 1278.0, 1279.0, 1280.0]]],    [[[1281.0, 1282.0, 1283.0, 1284.0],      [1285.0, 1286.0, 1287.0, 1288.0],      [1289.0, 1290.0, 1291.0, 1292.0],      [1293.0, 1294.0, 1295.0, 1296.0],      [1297.0, 1298.0, 1299.0, 1300.0]],     [[1301.0, 1302.0, 1303.0, 1304.0],      [1305.0, 1306.0, 1307.0, 1308.0],      [1309.0, 1310.0, 1311.0, 1312.0],      [1313.0, 1314.0, 1315.0, 1316.0],      [1317.0, 1318.0, 1319.0, 1320.0]],     [[1321.0, 1322.0, 1323.0, 1324.0],      [1325.0, 1326.0, 1327.0, 1328.0],      [1329.0, 1330.0, 1331.0, 1332.0],      [1333.0, 1334.0, 1335.0, 1336.0],      [1337.0, 1338.0, 1339.0, 1340.0]],     [[1341.0, 1342.0, 1343.0, 1344.0],      [1345.0, 1346.0, 1347.0, 1348.0],      [1349.0, 1350.0, 1351.0, 1352.0],      [1353.0, 1354.0, 1355.0, 1356.0],      [1357.0, 1358.0, 1359.0, 1360.0]]],    [[[1361.0, 1362.0, 1363.0, 1364.0],      [1365.0, 1366.0, 1367.0, 1368.0],      [1369.0, 1370.0, 1371.0, 1372.0],      [1373.0, 1374.0, 1375.0, 1376.0],      [1377.0, 1378.0, 1379.0, 1380.0]],     [[1381.0, 1382.0, 1383.0, 1384.0],      [1385.0, 1386.0, 1387.0, 1388.0],      [1389.0, 1390.0, 1391.0, 1392.0],      [1393.0, 1394.0, 1395.0, 1396.0],      [1397.0, 1398.0, 1399.0, 1400.0]],     [[1401.0, 1402.0, 1403.0, 1404.0],      [1405.0, 1406.0, 1407.0, 1408.0],      [1409.0, 1410.0, 1411.0, 1412.0],      [1413.0, 1414.0, 1415.0, 1416.0],      [1417.0, 1418.0, 1419.0, 1420.0]],     [[1421.0, 1422.0, 1423.0, 1424.0],      [1425.0, 1426.0, 1427.0, 1428.0],      [1429.0, 1430.0, 1431.0, 1432.0],      [1433.0, 1434.0, 1435.0, 1436.0],      [1437.0, 1438.0, 1439.0, 1440.0]]],    [[[1441.0, 1442.0, 1443.0, 1444.0],      [1445.0, 1446.0, 1447.0, 1448.0],      [1449.0, 1450.0, 1451.0, 1452.0],      [1453.0, 1454.0, 1455.0, 1456.0],      [1457.0, 1458.0, 1459.0, 1460.0]],     [[1461.0, 1462.0, 1463.0, 1464.0],      [1465.0, 1466.0, 1467.0, 1468.0],      [1469.0, 1470.0, 1471.0, 1472.0],      [1473.0, 1474.0, 1475.0, 1476.0],      [1477.0, 1478.0, 1479.0, 1480.0]],     [[1481.0, 1482.0, 1483.0, 1484.0],      [1485.0, 1486.0, 1487.0, 1488.0],      [1489.0, 1490.0, 1491.0, 1492.0],      [1493.0, 1494.0, 1495.0, 1496.0],      [1497.0, 1498.0, 1499.0, 1500.0]],     [[1501.0, 1502.0, 1503.0, 1504.0],      [1505.0, 1506.0, 1507.0, 1508.0],      [1509.0, 1510.0, 1511.0, 1512.0],      [1513.0, 1514.0, 1515.0, 1516.0],      [1517.0, 1518.0, 1519.0, 1520.0]]],    [[[1521.0, 1522.0, 1523.0, 1524.0],      [1525.0, 1526.0, 1527.0, 1528.0],      [1529.0, 1530.0, 1531.0, 1532.0],      [1533.0, 1534.0, 1535.0, 1536.0],      [1537.0, 1538.0, 1539.0, 1540.0]],     [[1541.0, 1542.0, 1543.0, 1544.0],      [1545.0, 1546.0, 1547.0, 1548.0],      [1549.0, 1550.0, 1551.0, 1552.0],      [1553.0, 1554.0, 1555.0, 1556.0],      [1557.0, 1558.0, 1559.0, 1560.0]],     [[1561.0, 1562.0, 1563.0, 1564.0],      [1565.0, 1566.0, 1567.0, 1568.0],      [1569.0, 1570.0, 1571.0, 1572.0],      [1573.0, 1574.0, 1575.0, 1576.0],      [1577.0, 1578.0, 1579.0, 1580.0]],     [[1581.0, 1582.0, 1583.0, 1584.0],      [1585.0, 1586.0, 1587.0, 1588.0],      [1589.0, 1590.0, 1591.0, 1592.0],      [1593.0, 1594.0, 1595.0, 1596.0],      [1597.0, 1598.0, 1599.0, 1600.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]]],  [[[[[2001.0, 2002.0, 2003.0, 2004.0],      [2005.0, 2006.0, 2007.0, 2008.0],      [2009.0, 2010.0, 2011.0, 2012.0],      [2013.0, 2014.0, 2015.0, 2016.0],      [2017.0, 2018.0, 2019.0, 2020.0]],     [[2021.0, 2022.0, 2023.0, 2024.0],      [2025.0, 2026.0, 2027.0, 2028.0],      [2029.0, 2030.0, 2031.0, 2032.0],      [2033.0, 2034.0, 2035.0, 2036.0],      [2037.0, 2038.0, 2039.0, 2040.0]],     [[2041.0, 2042.0, 2043.0, 2044.0],      [2045.0, 2046.0, 2047.0, 2048.0],      [2049.0, 2050.0, 2051.0, 2052.0],      [2053.0, 2054.0, 2055.0, 2056.0],      [2057.0, 2058.0, 2059.0, 2060.0]],     [[2061.0, 2062.0, 2063.0, 2064.0],      [2065.0, 2066.0, 2067.0, 2068.0],      [2069.0, 2070.0, 2071.0, 2072.0],      [2073.0, 2074.0, 2075.0, 2076.0],      [2077.0, 2078.0, 2079.0, 2080.0]]],    [[[2081.0, 2082.0, 2083.0, 2084.0],      [2085.0, 2086.0, 2087.0, 2088.0],      [2089.0, 2090.0, 2091.0, 2092.0],      [2093.0, 2094.0, 2095.0, 2096.0],      [2097.0, 2098.0, 2099.0, 2100.0]],     [[2101.0, 2102.0, 2103.0, 2104.0],      [2105.0, 2106.0, 2107.0, 2108.0],      [2109.0, 2110.0, 2111.0, 2112.0],      [2113.0, 2114.0, 2115.0, 2116.0],      [2117.0, 2118.0, 2119.0, 2120.0]],     [[2121.0, 2122.0, 2123.0, 2124.0],      [2125.0, 2126.0, 2127.0, 2128.0],      [2129.0, 2130.0, 2131.0, 2132.0],      [2133.0, 2134.0, 2135.0, 2136.0],      [2137.0, 2138.0, 2139.0, 2140.0]],     [[2141.0, 2142.0, 2143.0, 2144.0],      [2145.0, 2146.0, 2147.0, 2148.0],      [2149.0, 2150.0, 2151.0, 2152.0],      [2153.0, 2154.0, 2155.0, 2156.0],      [2157.0, 2158.0, 2159.0, 2160.0]]],    [[[2161.0, 2162.0, 2163.0, 2164.0],      [2165.0, 2166.0, 2167.0, 2168.0],      [2169.0, 2170.0, 2171.0, 2172.0],      [2173.0, 2174.0, 2175.0, 2176.0],      [2177.0, 2178.0, 2179.0, 2180.0]],     [[2181.0, 2182.0, 2183.0, 2184.0],      [2185.0, 2186.0, 2187.0, 2188.0],      [2189.0, 2190.0, 2191.0, 2192.0],      [2193.0, 2194.0, 2195.0, 2196.0],      [2197.0, 2198.0, 2199.0, 2200.0]],     [[2201.0, 2202.0, 2203.0, 2204.0],      [2205.0, 2206.0, 2207.0, 2208.0],      [2209.0, 2210.0, 2211.0, 2212.0],      [2213.0, 2214.0, 2215.0, 2216.0],      [2217.0, 2218.0, 2219.0, 2220.0]],     [[2221.0, 2222.0, 2223.0, 2224.0],      [2225.0, 2226.0, 2227.0, 2228.0],      [2229.0, 2230.0, 2231.0, 2232.0],      [2233.0, 2234.0, 2235.0, 2236.0],      [2237.0, 2238.0, 2239.0, 2240.0]]],    [[[2241.0, 2242.0, 2243.0, 2244.0],      [2245.0, 2246.0, 2247.0, 2248.0],      [2249.0, 2250.0, 2251.0, 2252.0],      [2253.0, 2254.0, 2255.0, 2256.0],      [2257.0, 2258.0, 2259.0, 2260.0]],     [[2261.0, 2262.0, 2263.0, 2264.0],      [2265.0, 2266.0, 2267.0, 2268.0],      [2269.0, 2270.0, 2271.0, 2272.0],      [2273.0, 2274.0, 2275.0, 2276.0],      [2277.0, 2278.0, 2279.0, 2280.0]],     [[2281.0, 2282.0, 2283.0, 2284.0],      [2285.0, 2286.0, 2287.0, 2288.0],      [2289.0, 2290.0, 2291.0, 2292.0],      [2293.0, 2294.0, 2295.0, 2296.0],      [2297.0, 2298.0, 2299.0, 2300.0]],     [[2301.0, 2302.0, 2303.0, 2304.0],      [2305.0, 2306.0, 2307.0, 2308.0],      [2309.0, 2310.0, 2311.0, 2312.0],      [2313.0, 2314.0, 2315.0, 2316.0],      [2317.0, 2318.0, 2319.0, 2320.0]]],    [[[2321.0, 2322.0, 2323.0, 2324.0],      [2325.0, 2326.0, 2327.0, 2328.0],      [2329.0, 2330.0, 2331.0, 2332.0],      [2333.0, 2334.0, 2335.0, 2336.0],      [2337.0, 2338.0, 2339.0, 2340.0]],     [[2341.0, 2342.0, 2343.0, 2344.0],      [2345.0, 2346.0, 2347.0, 2348.0],      [2349.0, 2350.0, 2351.0, 2352.0],      [2353.0, 2354.0, 2355.0, 2356.0],      [2357.0, 2358.0, 2359.0, 2360.0]],     [[2361.0, 2362.0, 2363.0, 2364.0],      [2365.0, 2366.0, 2367.0, 2368.0],      [2369.0, 2370.0, 2371.0, 2372.0],      [2373.0, 2374.0, 2375.0, 2376.0],      [2377.0, 2378.0, 2379.0, 2380.0]],     [[2381.0, 2382.0, 2383.0, 2384.0],      [2385.0, 2386.0, 2387.0, 2388.0],      [2389.0, 2390.0, 2391.0, 2392.0],      [2393.0, 2394.0, 2395.0, 2396.0],      [2397.0, 2398.0, 2399.0, 2400.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]],   [[[[3201.0, 3202.0, 3203.0, 3204.0],      [3205.0, 3206.0, 3207.0, 3208.0],      [3209.0, 3210.0, 3211.0, 3212.0],      [3213.0, 3214.0, 3215.0, 3216.0],      [3217.0, 3218.0, 3219.0, 3220.0]],     [[3221.0, 3222.0, 3223.0, 3224.0],      [3225.0, 3226.0, 3227.0, 3228.0],      [3229.0, 3230.0, 3231.0, 3232.0],      [3233.0, 3234.0, 3235.0, 3236.0],      [3237.0, 3238.0, 3239.0, 3240.0]],     [[3241.0, 3242.0, 3243.0, 3244.0],      [3245.0, 3246.0, 3247.0, 3248.0],      [3249.0, 3250.0, 3251.0, 3252.0],      [3253.0, 3254.0, 3255.0, 3256.0],      [3257.0, 3258.0, 3259.0, 3260.0]],     [[3261.0, 3262.0, 3263.0, 3264.0],      [3265.0, 3266.0, 3267.0, 3268.0],      [3269.0, 3270.0, 3271.0, 3272.0],      [3273.0, 3274.0, 3275.0, 3276.0],      [3277.0, 3278.0, 3279.0, 3280.0]]],    [[[3281.0, 3282.0, 3283.0, 3284.0],      [3285.0, 3286.0, 3287.0, 3288.0],      [3289.0, 3290.0, 3291.0, 3292.0],      [3293.0, 3294.0, 3295.0, 3296.0],      [3297.0, 3298.0, 3299.0, 3300.0]],     [[3301.0, 3302.0, 3303.0, 3304.0],      [3305.0, 3306.0, 3307.0, 3308.0],      [3309.0, 3310.0, 3311.0, 3312.0],      [3313.0, 3314.0, 3315.0, 3316.0],      [3317.0, 3318.0, 3319.0, 3320.0]],     [[3321.0, 3322.0, 3323.0, 3324.0],      [3325.0, 3326.0, 3327.0, 3328.0],      [3329.0, 3330.0, 3331.0, 3332.0],      [3333.0, 3334.0, 3335.0, 3336.0],      [3337.0, 3338.0, 3339.0, 3340.0]],     [[3341.0, 3342.0, 3343.0, 3344.0],      [3345.0, 3346.0, 3347.0, 3348.0],      [3349.0, 3350.0, 3351.0, 3352.0],      [3353.0, 3354.0, 3355.0, 3356.0],      [3357.0, 3358.0, 3359.0, 3360.0]]],    [[[3361.0, 3362.0, 3363.0, 3364.0],      [3365.0, 3366.0, 3367.0, 3368.0],      [3369.0, 3370.0, 3371.0, 3372.0],      [3373.0, 3374.0, 3375.0, 3376.0],      [3377.0, 3378.0, 3379.0, 3380.0]],     [[3381.0, 3382.0, 3383.0, 3384.0],      [3385.0, 3386.0, 3387.0, 3388.0],      [3389.0, 3390.0, 3391.0, 3392.0],      [3393.0, 3394.0, 3395.0, 3396.0],      [3397.0, 3398.0, 3399.0, 3400.0]],     [[3401.0, 3402.0, 3403.0, 3404.0],      [3405.0, 3406.0, 3407.0, 3408.0],      [3409.0, 3410.0, 3411.0, 3412.0],      [3413.0, 3414.0, 3415.0, 3416.0],      [3417.0, 3418.0, 3419.0, 3420.0]],     [[3421.0, 3422.0, 3423.0, 3424.0],      [3425.0, 3426.0, 3427.0, 3428.0],      [3429.0, 3430.0, 3431.0, 3432.0],      [3433.0, 3434.0, 3435.0, 3436.0],      [3437.0, 3438.0, 3439.0, 3440.0]]],    [[[3441.0, 3442.0, 3443.0, 3444.0],      [3445.0, 3446.0, 3447.0, 3448.0],      [3449.0, 3450.0, 3451.0, 3452.0],      [3453.0, 3454.0, 3455.0, 3456.0],      [3457.0, 3458.0, 3459.0, 3460.0]],     [[3461.0, 3462.0, 3463.0, 3464.0],      [3465.0, 3466.0, 3467.0, 3468.0],      [3469.0, 3470.0, 3471.0, 3472.0],      [3473.0, 3474.0, 3475.0, 3476.0],      [3477.0, 3478.0, 3479.0, 3480.0]],     [[3481.0, 3482.0, 3483.0, 3484.0],      [3485.0, 3486.0, 3487.0, 3488.0],      [3489.0, 3490.0, 3491.0, 3492.0],      [3493.0, 3494.0, 3495.0, 3496.0],      [3497.0, 3498.0, 3499.0, 3500.0]],     [[3501.0, 3502.0, 3503.0, 3504.0],      [3505.0, 3506.0, 3507.0, 3508.0],      [3509.0, 3510.0, 3511.0, 3512.0],      [3513.0, 3514.0, 3515.0, 3516.0],      [3517.0, 3518.0, 3519.0, 3520.0]]],    [[[3521.0, 3522.0, 3523.0, 3524.0],      [3525.0, 3526.0, 3527.0, 3528.0],      [3529.0, 3530.0, 3531.0, 3532.0],      [3533.0, 3534.0, 3535.0, 3536.0],      [3537.0, 3538.0, 3539.0, 3540.0]],     [[3541.0, 3542.0, 3543.0, 3544.0],      [3545.0, 3546.0, 3547.0, 3548.0],      [3549.0, 3550.0, 3551.0, 3552.0],      [3553.0, 3554.0, 3555.0, 3556.0],      [3557.0, 3558.0, 3559.0, 3560.0]],     [[3561.0, 3562.0, 3563.0, 3564.0],      [3565.0, 3566.0, 3567.0, 3568.0],      [3569.0, 3570.0, 3571.0, 3572.0],      [3573.0, 3574.0, 3575.0, 3576.0],      [3577.0, 3578.0, 3579.0, 3580.0]],     [[3581.0, 3582.0, 3583.0, 3584.0],      [3585.0, 3586.0, 3587.0, 3588.0],      [3589.0, 3590.0, 3591.0, 3592.0],      [3593.0, 3594.0, 3595.0, 3596.0],      [3597.0, 3598.0, 3599.0, 3600.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]]],  [[[[[401.0, 402.0, 403.0, 404.0],      [405.0, 406.0, 407.0, 408.0],      [409.0, 410.0, 411.0, 412.0],      [413.0, 414.0, 415.0, 416.0],      [417.0, 418.0, 419.0, 420.0]],     [[421.0, 422.0, 423.0, 424.0],      [425.0, 426.0, 427.0, 428.0],      [429.0, 430.0, 431.0, 432.0],      [433.0, 434.0, 435.0, 436.0],      [437.0, 438.0, 439.0, 440.0]],     [[441.0, 442.0, 443.0, 444.0],      [445.0, 446.0, 447.0, 448.0],      [449.0, 450.0, 451.0, 452.0],      [453.0, 454.0, 455.0, 456.0],      [457.0, 458.0, 459.0, 460.0]],     [[461.0, 462.0, 463.0, 464.0],      [465.0, 466.0, 467.0, 468.0],      [469.0, 470.0, 471.0, 472.0],      [473.0, 474.0, 475.0, 476.0],      [477.0, 478.0, 479.0, 480.0]]],    [[[481.0, 482.0, 483.0, 484.0],      [485.0, 486.0, 487.0, 488.0],      [489.0, 490.0, 491.0, 492.0],      [493.0, 494.0, 495.0, 496.0],      [497.0, 498.0, 499.0, 500.0]],     [[501.0, 502.0, 503.0, 504.0],      [505.0, 506.0, 507.0, 508.0],      [509.0, 510.0, 511.0, 512.0],      [513.0, 514.0, 515.0, 516.0],      [517.0, 518.0, 519.0, 520.0]],     [[521.0, 522.0, 523.0, 524.0],      [525.0, 526.0, 527.0, 528.0],      [529.0, 530.0, 531.0, 532.0],      [533.0, 534.0, 535.0, 536.0],      [537.0, 538.0, 539.0, 540.0]],     [[541.0, 542.0, 543.0, 544.0],      [545.0, 546.0, 547.0, 548.0],      [549.0, 550.0, 551.0, 552.0],      [553.0, 554.0, 555.0, 556.0],      [557.0, 558.0, 559.0, 560.0]]],    [[[561.0, 562.0, 563.0, 564.0],      [565.0, 566.0, 567.0, 568.0],      [569.0, 570.0, 571.0, 572.0],      [573.0, 574.0, 575.0, 576.0],      [577.0, 578.0, 579.0, 580.0]],     [[581.0, 582.0, 583.0, 584.0],      [585.0, 586.0, 587.0, 588.0],      [589.0, 590.0, 591.0, 592.0],      [593.0, 594.0, 595.0, 596.0],      [597.0, 598.0, 599.0, 600.0]],     [[601.0, 602.0, 603.0, 604.0],      [605.0, 606.0, 607.0, 608.0],      [609.0, 610.0, 611.0, 612.0],      [613.0, 614.0, 615.0, 616.0],      [617.0, 618.0, 619.0, 620.0]],     [[621.0, 622.0, 623.0, 624.0],      [625.0, 626.0, 627.0, 628.0],      [629.0, 630.0, 631.0, 632.0],      [633.0, 634.0, 635.0, 636.0],      [637.0, 638.0, 639.0, 640.0]]],    [[[641.0, 642.0, 643.0, 644.0],      [645.0, 646.0, 647.0, 648.0],      [649.0, 650.0, 651.0, 652.0],      [653.0, 654.0, 655.0, 656.0],      [657.0, 658.0, 659.0, 660.0]],     [[661.0, 662.0, 663.0, 664.0],      [665.0, 666.0, 667.0, 668.0],      [669.0, 670.0, 671.0, 672.0],      [673.0, 674.0, 675.0, 676.0],      [677.0, 678.0, 679.0, 680.0]],     [[681.0, 682.0, 683.0, 684.0],      [685.0, 686.0, 687.0, 688.0],      [689.0, 690.0, 691.0, 692.0],      [693.0, 694.0, 695.0, 696.0],      [697.0, 698.0, 699.0, 700.0]],     [[701.0, 702.0, 703.0, 704.0],      [705.0, 706.0, 707.0, 708.0],      [709.0, 710.0, 711.0, 712.0],      [713.0, 714.0, 715.0, 716.0],      [717.0, 718.0, 719.0, 720.0]]],    [[[721.0, 722.0, 723.0, 724.0],      [725.0, 726.0, 727.0, 728.0],      [729.0, 730.0, 731.0, 732.0],      [733.0, 734.0, 735.0, 736.0],      [737.0, 738.0, 739.0, 740.0]],     [[741.0, 742.0, 743.0, 744.0],      [745.0, 746.0, 747.0, 748.0],      [749.0, 750.0, 751.0, 752.0],      [753.0, 754.0, 755.0, 756.0],      [757.0, 758.0, 759.0, 760.0]],     [[761.0, 762.0, 763.0, 764.0],      [765.0, 766.0, 767.0, 768.0],      [769.0, 770.0, 771.0, 772.0],      [773.0, 774.0, 775.0, 776.0],      [777.0, 778.0, 779.0, 780.0]],     [[781.0, 782.0, 783.0, 784.0],      [785.0, 786.0, 787.0, 788.0],      [789.0, 790.0, 791.0, 792.0],      [793.0, 794.0, 795.0, 796.0],      [797.0, 798.0, 799.0, 800.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]],   [[[[1601.0, 1602.0, 1603.0, 1604.0],      [1605.0, 1606.0, 1607.0, 1608.0],      [1609.0, 1610.0, 1611.0, 1612.0],      [1613.0, 1614.0, 1615.0, 1616.0],      [1617.0, 1618.0, 1619.0, 1620.0]],     [[1621.0, 1622.0, 1623.0, 1624.0],      [1625.0, 1626.0, 1627.0, 1628.0],      [1629.0, 1630.0, 1631.0, 1632.0],      [1633.0, 1634.0, 1635.0, 1636.0],      [1637.0, 1638.0, 1639.0, 1640.0]],     [[1641.0, 1642.0, 1643.0, 1644.0],      [1645.0, 1646.0, 1647.0, 1648.0],      [1649.0, 1650.0, 1651.0, 1652.0],      [1653.0, 1654.0, 1655.0, 1656.0],      [1657.0, 1658.0, 1659.0, 1660.0]],     [[1661.0, 1662.0, 1663.0, 1664.0],      [1665.0, 1666.0, 1667.0, 1668.0],      [1669.0, 1670.0, 1671.0, 1672.0],      [1673.0, 1674.0, 1675.0, 1676.0],      [1677.0, 1678.0, 1679.0, 1680.0]]],    [[[1681.0, 1682.0, 1683.0, 1684.0],      [1685.0, 1686.0, 1687.0, 1688.0],      [1689.0, 1690.0, 1691.0, 1692.0],      [1693.0, 1694.0, 1695.0, 1696.0],      [1697.0, 1698.0, 1699.0, 1700.0]],     [[1701.0, 1702.0, 1703.0, 1704.0],      [1705.0, 1706.0, 1707.0, 1708.0],      [1709.0, 1710.0, 1711.0, 1712.0],      [1713.0, 1714.0, 1715.0, 1716.0],      [1717.0, 1718.0, 1719.0, 1720.0]],     [[1721.0, 1722.0, 1723.0, 1724.0],      [1725.0, 1726.0, 1727.0, 1728.0],      [1729.0, 1730.0, 1731.0, 1732.0],      [1733.0, 1734.0, 1735.0, 1736.0],      [1737.0, 1738.0, 1739.0, 1740.0]],     [[1741.0, 1742.0, 1743.0, 1744.0],      [1745.0, 1746.0, 1747.0, 1748.0],      [1749.0, 1750.0, 1751.0, 1752.0],      [1753.0, 1754.0, 1755.0, 1756.0],      [1757.0, 1758.0, 1759.0, 1760.0]]],    [[[1761.0, 1762.0, 1763.0, 1764.0],      [1765.0, 1766.0, 1767.0, 1768.0],      [1769.0, 1770.0, 1771.0, 1772.0],      [1773.0, 1774.0, 1775.0, 1776.0],      [1777.0, 1778.0, 1779.0, 1780.0]],     [[1781.0, 1782.0, 1783.0, 1784.0],      [1785.0, 1786.0, 1787.0, 1788.0],      [1789.0, 1790.0, 1791.0, 1792.0],      [1793.0, 1794.0, 1795.0, 1796.0],      [1797.0, 1798.0, 1799.0, 1800.0]],     [[1801.0, 1802.0, 1803.0, 1804.0],      [1805.0, 1806.0, 1807.0, 1808.0],      [1809.0, 1810.0, 1811.0, 1812.0],      [1813.0, 1814.0, 1815.0, 1816.0],      [1817.0, 1818.0, 1819.0, 1820.0]],     [[1821.0, 1822.0, 1823.0, 1824.0],      [1825.0, 1826.0, 1827.0, 1828.0],      [1829.0, 1830.0, 1831.0, 1832.0],      [1833.0, 1834.0, 1835.0, 1836.0],      [1837.0, 1838.0, 1839.0, 1840.0]]],    [[[1841.0, 1842.0, 1843.0, 1844.0],      [1845.0, 1846.0, 1847.0, 1848.0],      [1849.0, 1850.0, 1851.0, 1852.0],      [1853.0, 1854.0, 1855.0, 1856.0],      [1857.0, 1858.0, 1859.0, 1860.0]],     [[1861.0, 1862.0, 1863.0, 1864.0],      [1865.0, 1866.0, 1867.0, 1868.0],      [1869.0, 1870.0, 1871.0, 1872.0],      [1873.0, 1874.0, 1875.0, 1876.0],      [1877.0, 1878.0, 1879.0, 1880.0]],     [[1881.0, 1882.0, 1883.0, 1884.0],      [1885.0, 1886.0, 1887.0, 1888.0],      [1889.0, 1890.0, 1891.0, 1892.0],      [1893.0, 1894.0, 1895.0, 1896.0],      [1897.0, 1898.0, 1899.0, 1900.0]],     [[1901.0, 1902.0, 1903.0, 1904.0],      [1905.0, 1906.0, 1907.0, 1908.0],      [1909.0, 1910.0, 1911.0, 1912.0],      [1913.0, 1914.0, 1915.0, 1916.0],      [1917.0, 1918.0, 1919.0, 1920.0]]],    [[[1921.0, 1922.0, 1923.0, 1924.0],      [1925.0, 1926.0, 1927.0, 1928.0],      [1929.0, 1930.0, 1931.0, 1932.0],      [1933.0, 1934.0, 1935.0, 1936.0],      [1937.0, 1938.0, 1939.0, 1940.0]],     [[1941.0, 1942.0, 1943.0, 1944.0],      [1945.0, 1946.0, 1947.0, 1948.0],      [1949.0, 1950.0, 1951.0, 1952.0],      [1953.0, 1954.0, 1955.0, 1956.0],      [1957.0, 1958.0, 1959.0, 1960.0]],     [[1961.0, 1962.0, 1963.0, 1964.0],      [1965.0, 1966.0, 1967.0, 1968.0],      [1969.0, 1970.0, 1971.0, 1972.0],      [1973.0, 1974.0, 1975.0, 1976.0],      [1977.0, 1978.0, 1979.0, 1980.0]],     [[1981.0, 1982.0, 1983.0, 1984.0],      [1985.0, 1986.0, 1987.0, 1988.0],      [1989.0, 1990.0, 1991.0, 1992.0],      [1993.0, 1994.0, 1995.0, 1996.0],      [1997.0, 1998.0, 1999.0, 2000.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]]],  [[[[[2401.0, 2402.0, 2403.0, 2404.0],      [2405.0, 2406.0, 2407.0, 2408.0],      [2409.0, 2410.0, 2411.0, 2412.0],      [2413.0, 2414.0, 2415.0, 2416.0],      [2417.0, 2418.0, 2419.0, 2420.0]],     [[2421.0, 2422.0, 2423.0, 2424.0],      [2425.0, 2426.0, 2427.0, 2428.0],      [2429.0, 2430.0, 2431.0, 2432.0],      [2433.0, 2434.0, 2435.0, 2436.0],      [2437.0, 2438.0, 2439.0, 2440.0]],     [[2441.0, 2442.0, 2443.0, 2444.0],      [2445.0, 2446.0, 2447.0, 2448.0],      [2449.0, 2450.0, 2451.0, 2452.0],      [2453.0, 2454.0, 2455.0, 2456.0],      [2457.0, 2458.0, 2459.0, 2460.0]],     [[2461.0, 2462.0, 2463.0, 2464.0],      [2465.0, 2466.0, 2467.0, 2468.0],      [2469.0, 2470.0, 2471.0, 2472.0],      [2473.0, 2474.0, 2475.0, 2476.0],      [2477.0, 2478.0, 2479.0, 2480.0]]],    [[[2481.0, 2482.0, 2483.0, 2484.0],      [2485.0, 2486.0, 2487.0, 2488.0],      [2489.0, 2490.0, 2491.0, 2492.0],      [2493.0, 2494.0, 2495.0, 2496.0],      [2497.0, 2498.0, 2499.0, 2500.0]],     [[2501.0, 2502.0, 2503.0, 2504.0],      [2505.0, 2506.0, 2507.0, 2508.0],      [2509.0, 2510.0, 2511.0, 2512.0],      [2513.0, 2514.0, 2515.0, 2516.0],      [2517.0, 2518.0, 2519.0, 2520.0]],     [[2521.0, 2522.0, 2523.0, 2524.0],      [2525.0, 2526.0, 2527.0, 2528.0],      [2529.0, 2530.0, 2531.0, 2532.0],      [2533.0, 2534.0, 2535.0, 2536.0],      [2537.0, 2538.0, 2539.0, 2540.0]],     [[2541.0, 2542.0, 2543.0, 2544.0],      [2545.0, 2546.0, 2547.0, 2548.0],      [2549.0, 2550.0, 2551.0, 2552.0],      [2553.0, 2554.0, 2555.0, 2556.0],      [2557.0, 2558.0, 2559.0, 2560.0]]],    [[[2561.0, 2562.0, 2563.0, 2564.0],      [2565.0, 2566.0, 2567.0, 2568.0],      [2569.0, 2570.0, 2571.0, 2572.0],      [2573.0, 2574.0, 2575.0, 2576.0],      [2577.0, 2578.0, 2579.0, 2580.0]],     [[2581.0, 2582.0, 2583.0, 2584.0],      [2585.0, 2586.0, 2587.0, 2588.0],      [2589.0, 2590.0, 2591.0, 2592.0],      [2593.0, 2594.0, 2595.0, 2596.0],      [2597.0, 2598.0, 2599.0, 2600.0]],     [[2601.0, 2602.0, 2603.0, 2604.0],      [2605.0, 2606.0, 2607.0, 2608.0],      [2609.0, 2610.0, 2611.0, 2612.0],      [2613.0, 2614.0, 2615.0, 2616.0],      [2617.0, 2618.0, 2619.0, 2620.0]],     [[2621.0, 2622.0, 2623.0, 2624.0],      [2625.0, 2626.0, 2627.0, 2628.0],      [2629.0, 2630.0, 2631.0, 2632.0],      [2633.0, 2634.0, 2635.0, 2636.0],      [2637.0, 2638.0, 2639.0, 2640.0]]],    [[[2641.0, 2642.0, 2643.0, 2644.0],      [2645.0, 2646.0, 2647.0, 2648.0],      [2649.0, 2650.0, 2651.0, 2652.0],      [2653.0, 2654.0, 2655.0, 2656.0],      [2657.0, 2658.0, 2659.0, 2660.0]],     [[2661.0, 2662.0, 2663.0, 2664.0],      [2665.0, 2666.0, 2667.0, 2668.0],      [2669.0, 2670.0, 2671.0, 2672.0],      [2673.0, 2674.0, 2675.0, 2676.0],      [2677.0, 2678.0, 2679.0, 2680.0]],     [[2681.0, 2682.0, 2683.0, 2684.0],      [2685.0, 2686.0, 2687.0, 2688.0],      [2689.0, 2690.0, 2691.0, 2692.0],      [2693.0, 2694.0, 2695.0, 2696.0],      [2697.0, 2698.0, 2699.0, 2700.0]],     [[2701.0, 2702.0, 2703.0, 2704.0],      [2705.0, 2706.0, 2707.0, 2708.0],      [2709.0, 2710.0, 2711.0, 2712.0],      [2713.0, 2714.0, 2715.0, 2716.0],      [2717.0, 2718.0, 2719.0, 2720.0]]],    [[[2721.0, 2722.0, 2723.0, 2724.0],      [2725.0, 2726.0, 2727.0, 2728.0],      [2729.0, 2730.0, 2731.0, 2732.0],      [2733.0, 2734.0, 2735.0, 2736.0],      [2737.0, 2738.0, 2739.0, 2740.0]],     [[2741.0, 2742.0, 2743.0, 2744.0],      [2745.0, 2746.0, 2747.0, 2748.0],      [2749.0, 2750.0, 2751.0, 2752.0],      [2753.0, 2754.0, 2755.0, 2756.0],      [2757.0, 2758.0, 2759.0, 2760.0]],     [[2761.0, 2762.0, 2763.0, 2764.0],      [2765.0, 2766.0, 2767.0, 2768.0],      [2769.0, 2770.0, 2771.0, 2772.0],      [2773.0, 2774.0, 2775.0, 2776.0],      [2777.0, 2778.0, 2779.0, 2780.0]],     [[2781.0, 2782.0, 2783.0, 2784.0],      [2785.0, 2786.0, 2787.0, 2788.0],      [2789.0, 2790.0, 2791.0, 2792.0],      [2793.0, 2794.0, 2795.0, 2796.0],      [2797.0, 2798.0, 2799.0, 2800.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]],   [[[[3601.0, 3602.0, 3603.0, 3604.0],      [3605.0, 3606.0, 3607.0, 3608.0],      [3609.0, 3610.0, 3611.0, 3612.0],      [3613.0, 3614.0, 3615.0, 3616.0],      [3617.0, 3618.0, 3619.0, 3620.0]],     [[3621.0, 3622.0, 3623.0, 3624.0],      [3625.0, 3626.0, 3627.0, 3628.0],      [3629.0, 3630.0, 3631.0, 3632.0],      [3633.0, 3634.0, 3635.0, 3636.0],      [3637.0, 3638.0, 3639.0, 3640.0]],     [[3641.0, 3642.0, 3643.0, 3644.0],      [3645.0, 3646.0, 3647.0, 3648.0],      [3649.0, 3650.0, 3651.0, 3652.0],      [3653.0, 3654.0, 3655.0, 3656.0],      [3657.0, 3658.0, 3659.0, 3660.0]],     [[3661.0, 3662.0, 3663.0, 3664.0],      [3665.0, 3666.0, 3667.0, 3668.0],      [3669.0, 3670.0, 3671.0, 3672.0],      [3673.0, 3674.0, 3675.0, 3676.0],      [3677.0, 3678.0, 3679.0, 3680.0]]],    [[[3681.0, 3682.0, 3683.0, 3684.0],      [3685.0, 3686.0, 3687.0, 3688.0],      [3689.0, 3690.0, 3691.0, 3692.0],      [3693.0, 3694.0, 3695.0, 3696.0],      [3697.0, 3698.0, 3699.0, 3700.0]],     [[3701.0, 3702.0, 3703.0, 3704.0],      [3705.0, 3706.0, 3707.0, 3708.0],      [3709.0, 3710.0, 3711.0, 3712.0],      [3713.0, 3714.0, 3715.0, 3716.0],      [3717.0, 3718.0, 3719.0, 3720.0]],     [[3721.0, 3722.0, 3723.0, 3724.0],      [3725.0, 3726.0, 3727.0, 3728.0],      [3729.0, 3730.0, 3731.0, 3732.0],      [3733.0, 3734.0, 3735.0, 3736.0],      [3737.0, 3738.0, 3739.0, 3740.0]],     [[3741.0, 3742.0, 3743.0, 3744.0],      [3745.0, 3746.0, 3747.0, 3748.0],      [3749.0, 3750.0, 3751.0, 3752.0],      [3753.0, 3754.0, 3755.0, 3756.0],      [3757.0, 3758.0, 3759.0, 3760.0]]],    [[[3761.0, 3762.0, 3763.0, 3764.0],      [3765.0, 3766.0, 3767.0, 3768.0],      [3769.0, 3770.0, 3771.0, 3772.0],      [3773.0, 3774.0, 3775.0, 3776.0],      [3777.0, 3778.0, 3779.0, 3780.0]],     [[3781.0, 3782.0, 3783.0, 3784.0],      [3785.0, 3786.0, 3787.0, 3788.0],      [3789.0, 3790.0, 3791.0, 3792.0],      [3793.0, 3794.0, 3795.0, 3796.0],      [3797.0, 3798.0, 3799.0, 3800.0]],     [[3801.0, 3802.0, 3803.0, 3804.0],      [3805.0, 3806.0, 3807.0, 3808.0],      [3809.0, 3810.0, 3811.0, 3812.0],      [3813.0, 3814.0, 3815.0, 3816.0],      [3817.0, 3818.0, 3819.0, 3820.0]],     [[3821.0, 3822.0, 3823.0, 3824.0],      [3825.0, 3826.0, 3827.0, 3828.0],      [3829.0, 3830.0, 3831.0, 3832.0],      [3833.0, 3834.0, 3835.0, 3836.0],      [3837.0, 3838.0, 3839.0, 3840.0]]],    [[[3841.0, 3842.0, 3843.0, 3844.0],      [3845.0, 3846.0, 3847.0, 3848.0],      [3849.0, 3850.0, 3851.0, 3852.0],      [3853.0, 3854.0, 3855.0, 3856.0],      [3857.0, 3858.0, 3859.0, 3860.0]],     [[3861.0, 3862.0, 3863.0, 3864.0],      [3865.0, 3866.0, 3867.0, 3868.0],      [3869.0, 3870.0, 3871.0, 3872.0],      [3873.0, 3874.0, 3875.0, 3876.0],      [3877.0, 3878.0, 3879.0, 3880.0]],     [[3881.0, 3882.0, 3883.0, 3884.0],      [3885.0, 3886.0, 3887.0, 3888.0],      [3889.0, 3890.0, 3891.0, 3892.0],      [3893.0, 3894.0, 3895.0, 3896.0],      [3897.0, 3898.0, 3899.0, 3900.0]],     [[3901.0, 3902.0, 3903.0, 3904.0],      [3905.0, 3906.0, 3907.0, 3908.0],      [3909.0, 3910.0, 3911.0, 3912.0],      [3913.0, 3914.0, 3915.0, 3916.0],      [3917.0, 3918.0, 3919.0, 3920.0]]],    [[[3921.0, 3922.0, 3923.0, 3924.0],      [3925.0, 3926.0, 3927.0, 3928.0],      [3929.0, 3930.0, 3931.0, 3932.0],      [3933.0, 3934.0, 3935.0, 3936.0],      [3937.0, 3938.0, 3939.0, 3940.0]],     [[3941.0, 3942.0, 3943.0, 3944.0],      [3945.0, 3946.0, 3947.0, 3948.0],      [3949.0, 3950.0, 3951.0, 3952.0],      [3953.0, 3954.0, 3955.0, 3956.0],      [3957.0, 3958.0, 3959.0, 3960.0]],     [[3961.0, 3962.0, 3963.0, 3964.0],      [3965.0, 3966.0, 3967.0, 3968.0],      [3969.0, 3970.0, 3971.0, 3972.0],      [3973.0, 3974.0, 3975.0, 3976.0],      [3977.0, 3978.0, 3979.0, 3980.0]],     [[3981.0, 3982.0, 3983.0, 3984.0],      [3985.0, 3986.0, 3987.0, 3988.0],      [3989.0, 3990.0, 3991.0, 3992.0],      [3993.0, 3994.0, 3995.0, 3996.0],      [3997.0, 3998.0, 3999.0, 4000.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]]],  [[[[[801.0, 802.0, 803.0, 804.0],      [805.0, 806.0, 807.0, 808.0],      [809.0, 810.0, 811.0, 812.0],      [813.0, 814.0, 815.0, 816.0],      [817.0, 818.0, 819.0, 820.0]],     [[821.0, 822.0, 823.0, 824.0],      [825.0, 826.0, 827.0, 828.0],      [829.0, 830.0, 831.0, 832.0],      [833.0, 834.0, 835.0, 836.0],      [837.0, 838.0, 839.0, 840.0]],     [[841.0, 842.0, 843.0, 844.0],      [845.0, 846.0, 847.0, 848.0],      [849.0, 850.0, 851.0, 852.0],      [853.0, 854.0, 855.0, 856.0],      [857.0, 858.0, 859.0, 860.0]],     [[861.0, 862.0, 863.0, 864.0],      [865.0, 866.0, 867.0, 868.0],      [869.0, 870.0, 871.0, 872.0],      [873.0, 874.0, 875.0, 876.0],      [877.0, 878.0, 879.0, 880.0]]],    [[[881.0, 882.0, 883.0, 884.0],      [885.0, 886.0, 887.0, 888.0],      [889.0, 890.0, 891.0, 892.0],      [893.0, 894.0, 895.0, 896.0],      [897.0, 898.0, 899.0, 900.0]],     [[901.0, 902.0, 903.0, 904.0],      [905.0, 906.0, 907.0, 908.0],      [909.0, 910.0, 911.0, 912.0],      [913.0, 914.0, 915.0, 916.0],      [917.0, 918.0, 919.0, 920.0]],     [[921.0, 922.0, 923.0, 924.0],      [925.0, 926.0, 927.0, 928.0],      [929.0, 930.0, 931.0, 932.0],      [933.0, 934.0, 935.0, 936.0],      [937.0, 938.0, 939.0, 940.0]],     [[941.0, 942.0, 943.0, 944.0],      [945.0, 946.0, 947.0, 948.0],      [949.0, 950.0, 951.0, 952.0],      [953.0, 954.0, 955.0, 956.0],      [957.0, 958.0, 959.0, 960.0]]],    [[[961.0, 962.0, 963.0, 964.0],      [965.0, 966.0, 967.0, 968.0],      [969.0, 970.0, 971.0, 972.0],      [973.0, 974.0, 975.0, 976.0],      [977.0, 978.0, 979.0, 980.0]],     [[981.0, 982.0, 983.0, 984.0],      [985.0, 986.0, 987.0, 988.0],      [989.0, 990.0, 991.0, 992.0],      [993.0, 994.0, 995.0, 996.0],      [997.0, 998.0, 999.0, 1000.0]],     [[1001.0, 1002.0, 1003.0, 1004.0],      [1005.0, 1006.0, 1007.0, 1008.0],      [1009.0, 1010.0, 1011.0, 1012.0],      [1013.0, 1014.0, 1015.0, 1016.0],      [1017.0, 1018.0, 1019.0, 1020.0]],     [[1021.0, 1022.0, 1023.0, 1024.0],      [1025.0, 1026.0, 1027.0, 1028.0],      [1029.0, 1030.0, 1031.0, 1032.0],      [1033.0, 1034.0, 1035.0, 1036.0],      [1037.0, 1038.0, 1039.0, 1040.0]]],    [[[1041.0, 1042.0, 1043.0, 1044.0],      [1045.0, 1046.0, 1047.0, 1048.0],      [1049.0, 1050.0, 1051.0, 1052.0],      [1053.0, 1054.0, 1055.0, 1056.0],      [1057.0, 1058.0, 1059.0, 1060.0]],     [[1061.0, 1062.0, 1063.0, 1064.0],      [1065.0, 1066.0, 1067.0, 1068.0],      [1069.0, 1070.0, 1071.0, 1072.0],      [1073.0, 1074.0, 1075.0, 1076.0],      [1077.0, 1078.0, 1079.0, 1080.0]],     [[1081.0, 1082.0, 1083.0, 1084.0],      [1085.0, 1086.0, 1087.0, 1088.0],      [1089.0, 1090.0, 1091.0, 1092.0],      [1093.0, 1094.0, 1095.0, 1096.0],      [1097.0, 1098.0, 1099.0, 1100.0]],     [[1101.0, 1102.0, 1103.0, 1104.0],      [1105.0, 1106.0, 1107.0, 1108.0],      [1109.0, 1110.0, 1111.0, 1112.0],      [1113.0, 1114.0, 1115.0, 1116.0],      [1117.0, 1118.0, 1119.0, 1120.0]]],    [[[1121.0, 1122.0, 1123.0, 1124.0],      [1125.0, 1126.0, 1127.0, 1128.0],      [1129.0, 1130.0, 1131.0, 1132.0],      [1133.0, 1134.0, 1135.0, 1136.0],      [1137.0, 1138.0, 1139.0, 1140.0]],     [[1141.0, 1142.0, 1143.0, 1144.0],      [1145.0, 1146.0, 1147.0, 1148.0],      [1149.0, 1150.0, 1151.0, 1152.0],      [1153.0, 1154.0, 1155.0, 1156.0],      [1157.0, 1158.0, 1159.0, 1160.0]],     [[1161.0, 1162.0, 1163.0, 1164.0],      [1165.0, 1166.0, 1167.0, 1168.0],      [1169.0, 1170.0, 1171.0, 1172.0],      [1173.0, 1174.0, 1175.0, 1176.0],      [1177.0, 1178.0, 1179.0, 1180.0]],     [[1181.0, 1182.0, 1183.0, 1184.0],      [1185.0, 1186.0, 1187.0, 1188.0],      [1189.0, 1190.0, 1191.0, 1192.0],      [1193.0, 1194.0, 1195.0, 1196.0],      [1197.0, 1198.0, 1199.0, 1200.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]]],  [[[[[2801.0, 2802.0, 2803.0, 2804.0],      [2805.0, 2806.0, 2807.0, 2808.0],      [2809.0, 2810.0, 2811.0, 2812.0],      [2813.0, 2814.0, 2815.0, 2816.0],      [2817.0, 2818.0, 2819.0, 2820.0]],     [[2821.0, 2822.0, 2823.0, 2824.0],      [2825.0, 2826.0, 2827.0, 2828.0],      [2829.0, 2830.0, 2831.0, 2832.0],      [2833.0, 2834.0, 2835.0, 2836.0],      [2837.0, 2838.0, 2839.0, 2840.0]],     [[2841.0, 2842.0, 2843.0, 2844.0],      [2845.0, 2846.0, 2847.0, 2848.0],      [2849.0, 2850.0, 2851.0, 2852.0],      [2853.0, 2854.0, 2855.0, 2856.0],      [2857.0, 2858.0, 2859.0, 2860.0]],     [[2861.0, 2862.0, 2863.0, 2864.0],      [2865.0, 2866.0, 2867.0, 2868.0],      [2869.0, 2870.0, 2871.0, 2872.0],      [2873.0, 2874.0, 2875.0, 2876.0],      [2877.0, 2878.0, 2879.0, 2880.0]]],    [[[2881.0, 2882.0, 2883.0, 2884.0],      [2885.0, 2886.0, 2887.0, 2888.0],      [2889.0, 2890.0, 2891.0, 2892.0],      [2893.0, 2894.0, 2895.0, 2896.0],      [2897.0, 2898.0, 2899.0, 2900.0]],     [[2901.0, 2902.0, 2903.0, 2904.0],      [2905.0, 2906.0, 2907.0, 2908.0],      [2909.0, 2910.0, 2911.0, 2912.0],      [2913.0, 2914.0, 2915.0, 2916.0],      [2917.0, 2918.0, 2919.0, 2920.0]],     [[2921.0, 2922.0, 2923.0, 2924.0],      [2925.0, 2926.0, 2927.0, 2928.0],      [2929.0, 2930.0, 2931.0, 2932.0],      [2933.0, 2934.0, 2935.0, 2936.0],      [2937.0, 2938.0, 2939.0, 2940.0]],     [[2941.0, 2942.0, 2943.0, 2944.0],      [2945.0, 2946.0, 2947.0, 2948.0],      [2949.0, 2950.0, 2951.0, 2952.0],      [2953.0, 2954.0, 2955.0, 2956.0],      [2957.0, 2958.0, 2959.0, 2960.0]]],    [[[2961.0, 2962.0, 2963.0, 2964.0],      [2965.0, 2966.0, 2967.0, 2968.0],      [2969.0, 2970.0, 2971.0, 2972.0],      [2973.0, 2974.0, 2975.0, 2976.0],      [2977.0, 2978.0, 2979.0, 2980.0]],     [[2981.0, 2982.0, 2983.0, 2984.0],      [2985.0, 2986.0, 2987.0, 2988.0],      [2989.0, 2990.0, 2991.0, 2992.0],      [2993.0, 2994.0, 2995.0, 2996.0],      [2997.0, 2998.0, 2999.0, 3000.0]],     [[3001.0, 3002.0, 3003.0, 3004.0],      [3005.0, 3006.0, 3007.0, 3008.0],      [3009.0, 3010.0, 3011.0, 3012.0],      [3013.0, 3014.0, 3015.0, 3016.0],      [3017.0, 3018.0, 3019.0, 3020.0]],     [[3021.0, 3022.0, 3023.0, 3024.0],      [3025.0, 3026.0, 3027.0, 3028.0],      [3029.0, 3030.0, 3031.0, 3032.0],      [3033.0, 3034.0, 3035.0, 3036.0],      [3037.0, 3038.0, 3039.0, 3040.0]]],    [[[3041.0, 3042.0, 3043.0, 3044.0],      [3045.0, 3046.0, 3047.0, 3048.0],      [3049.0, 3050.0, 3051.0, 3052.0],      [3053.0, 3054.0, 3055.0, 3056.0],      [3057.0, 3058.0, 3059.0, 3060.0]],     [[3061.0, 3062.0, 3063.0, 3064.0],      [3065.0, 3066.0, 3067.0, 3068.0],      [3069.0, 3070.0, 3071.0, 3072.0],      [3073.0, 3074.0, 3075.0, 3076.0],      [3077.0, 3078.0, 3079.0, 3080.0]],     [[3081.0, 3082.0, 3083.0, 3084.0],      [3085.0, 3086.0, 3087.0, 3088.0],      [3089.0, 3090.0, 3091.0, 3092.0],      [3093.0, 3094.0, 3095.0, 3096.0],      [3097.0, 3098.0, 3099.0, 3100.0]],     [[3101.0, 3102.0, 3103.0, 3104.0],      [3105.0, 3106.0, 3107.0, 3108.0],      [3109.0, 3110.0, 3111.0, 3112.0],      [3113.0, 3114.0, 3115.0, 3116.0],      [3117.0, 3118.0, 3119.0, 3120.0]]],    [[[3121.0, 3122.0, 3123.0, 3124.0],      [3125.0, 3126.0, 3127.0, 3128.0],      [3129.0, 3130.0, 3131.0, 3132.0],      [3133.0, 3134.0, 3135.0, 3136.0],      [3137.0, 3138.0, 3139.0, 3140.0]],     [[3141.0, 3142.0, 3143.0, 3144.0],      [3145.0, 3146.0, 3147.0, 3148.0],      [3149.0, 3150.0, 3151.0, 3152.0],      [3153.0, 3154.0, 3155.0, 3156.0],      [3157.0, 3158.0, 3159.0, 3160.0]],     [[3161.0, 3162.0, 3163.0, 3164.0],      [3165.0, 3166.0, 3167.0, 3168.0],      [3169.0, 3170.0, 3171.0, 3172.0],      [3173.0, 3174.0, 3175.0, 3176.0],      [3177.0, 3178.0, 3179.0, 3180.0]],     [[3181.0, 3182.0, 3183.0, 3184.0],      [3185.0, 3186.0, 3187.0, 3188.0],      [3189.0, 3190.0, 3191.0, 3192.0],      [3193.0, 3194.0, 3195.0, 3196.0],      [3197.0, 3198.0, 3199.0, 3200.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.0],      [0.0, 0.0, 0.0, 0.0],      [0.0, 0.0, 0.0, 0.0]]]]]] shape=[6, 2, 6, 4, 5, 4], strides=[960, 480, 80, 20, 4, 1], layout=C (0x1)), I32([3, 1] shape=[2], strides=[1], layout=C | F (0x3)), I32([[0, 1],  [0, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 949314028 2364610026 3159385384 4282123370 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],      [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0]],     [[29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],      [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0]],     [[43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],      [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0]],     [[57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],      [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0]]],    [[[71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],      [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0]],     [[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],      [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0]],     [[99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0],      [106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0]],     [[113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],      [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0]],     [[127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],      [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0]]],    [[[141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],      [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0]],     [[155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],      [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0]],     [[169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0],      [176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0]],     [[183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],      [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0]],     [[197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],      [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0]]],    [[[211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],      [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0]],     [[225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],      [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0]],     [[239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0],      [246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0]],     [[253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],      [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0]],     [[267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],      [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0]]],    [[[281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],      [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0]],     [[295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],      [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0]],     [[309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0],      [316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0]],     [[323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],      [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0]],     [[337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],      [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0]]],    [[[351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],      [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0]],     [[365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],      [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0]],     [[379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0],      [386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0]],     [[393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],      [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0]],     [[407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],      [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]]]],   [[[[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],      [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0]],     [[435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],      [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0]],     [[449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0],      [456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0]],     [[463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],      [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0]],     [[477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],      [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0]]],    [[[491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],      [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0]],     [[505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],      [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0]],     [[519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0],      [526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0]],     [[533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],      [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0]],     [[547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],      [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0]]],    [[[561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],      [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0]],     [[575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],      [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0]],     [[589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0],      [596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0]],     [[603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],      [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0]],     [[617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],      [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0]]],    [[[631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],      [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0]],     [[645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],      [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0]],     [[659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0],      [666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0]],     [[673.0, 674.0, 675.0, 676.0, 677.0, 678.0, 679.0],      [680.0, 681.0, 682.0, 683.0, 684.0, 685.0, 686.0]],     [[687.0, 688.0, 689.0, 690.0, 691.0, 692.0, 693.0],      [694.0, 695.0, 696.0, 697.0, 698.0, 699.0, 700.0]]],    [[[701.0, 702.0, 703.0, 704.0, 705.0, 706.0, 707.0],      [708.0, 709.0, 710.0, 711.0, 712.0, 713.0, 714.0]],     [[715.0, 716.0, 717.0, 718.0, 719.0, 720.0, 721.0],      [722.0, 723.0, 724.0, 725.0, 726.0, 727.0, 728.0]],     [[729.0, 730.0, 731.0, 732.0, 733.0, 734.0, 735.0],      [736.0, 737.0, 738.0, 739.0, 740.0, 741.0, 742.0]],     [[743.0, 744.0, 745.0, 746.0, 747.0, 748.0, 749.0],      [750.0, 751.0, 752.0, 753.0, 754.0, 755.0, 756.0]],     [[757.0, 758.0, 759.0, 760.0, 761.0, 762.0, 763.0],      [764.0, 765.0, 766.0, 767.0, 768.0, 769.0, 770.0]]],    [[[771.0, 772.0, 773.0, 774.0, 775.0, 776.0, 777.0],      [778.0, 779.0, 780.0, 781.0, 782.0, 783.0, 784.0]],     [[785.0, 786.0, 787.0, 788.0, 789.0, 790.0, 791.0],      [792.0, 793.0, 794.0, 795.0, 796.0, 797.0, 798.0]],     [[799.0, 800.0, 801.0, 802.0, 803.0, 804.0, 805.0],      [806.0, 807.0, 808.0, 809.0, 810.0, 811.0, 812.0]],     [[813.0, 814.0, 815.0, 816.0, 817.0, 818.0, 819.0],      [820.0, 821.0, 822.0, 823.0, 824.0, 825.0, 826.0]],     [[827.0, 828.0, 829.0, 830.0, 831.0, 832.0, 833.0],      [834.0, 835.0, 836.0, 837.0, 838.0, 839.0, 840.0]]]],   [[[[841.0, 842.0, 843.0, 844.0, 845.0, 846.0, 847.0],      [848.0, 849.0, 850.0, 851.0, 852.0, 853.0, 854.0]],     [[855.0, 856.0, 857.0, 858.0, 859.0, 860.0, 861.0],      [862.0, 863.0, 864.0, 865.0, 866.0, 867.0, 868.0]],     [[869.0, 870.0, 871.0, 872.0, 873.0, 874.0, 875.0],      [876.0, 877.0, 878.0, 879.0, 880.0, 881.0, 882.0]],     [[883.0, 884.0, 885.0, 886.0, 887.0, 888.0, 889.0],      [890.0, 891.0, 892.0, 893.0, 894.0, 895.0, 896.0]],     [[897.0, 898.0, 899.0, 900.0, 901.0, 902.0, 903.0],      [904.0, 905.0, 906.0, 907.0, 908.0, 909.0, 910.0]]],    [[[911.0, 912.0, 913.0, 914.0, 915.0, 916.0, 917.0],      [918.0, 919.0, 920.0, 921.0, 922.0, 923.0, 924.0]],     [[925.0, 926.0, 927.0, 928.0, 929.0, 930.0, 931.0],      [932.0, 933.0, 934.0, 935.0, 936.0, 937.0, 938.0]],     [[939.0, 940.0, 941.0, 942.0, 943.0, 944.0, 945.0],      [946.0, 947.0, 948.0, 949.0, 950.0, 951.0, 952.0]],     [[953.0, 954.0, 955.0, 956.0, 957.0, 958.0, 959.0],      [960.0, 961.0, 962.0, 963.0, 964.0, 965.0, 966.0]],     [[967.0, 968.0, 969.0, 970.0, 971.0, 972.0, 973.0],      [974.0, 975.0, 976.0, 977.0, 978.0, 979.0, 980.0]]],    [[[981.0, 982.0, 983.0, 984.0, 985.0, 986.0, 987.0],      [988.0, 989.0, 990.0, 991.0, 992.0, 993.0, 994.0]],     [[995.0, 996.0, 997.0, 998.0, 999.0, 1000.0, 1001.0],      [1002.0, 1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0]],     [[1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0, 1015.0],      [1016.0, 1017.0, 1018.0, 1019.0, 1020.0, 1021.0, 1022.0]],     [[1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0],      [1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0]],     [[1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0],      [1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]]],    [[[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0, 1057.0],      [1058.0, 1059.0, 1060.0, 1061.0, 1062.0, 1063.0, 1064.0]],     [[1065.0, 1066.0, 1067.0, 1068.0, 1069.0, 1070.0, 1071.0],      [1072.0, 1073.0, 1074.0, 1075.0, 1076.0, 1077.0, 1078.0]],     [[1079.0, 1080.0, 1081.0, 1082.0, 1083.0, 1084.0, 1085.0],      [1086.0, 1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0]],     [[1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0, 1099.0],      [1100.0, 1101.0, 1102.0, 1103.0, 1104.0, 1105.0, 1106.0]],     [[1107.0, 1108.0, 1109.0, 1110.0, 1111.0, 1112.0, 1113.0],      [1114.0, 1115.0, 1116.0, 1117.0, 1118.0, 1119.0, 1120.0]]],    [[[1121.0, 1122.0, 1123.0, 1124.0, 1125.0, 1126.0, 1127.0],      [1128.0, 1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0]],     [[1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0, 1141.0],      [1142.0, 1143.0, 1144.0, 1145.0, 1146.0, 1147.0, 1148.0]],     [[1149.0, 1150.0, 1151.0, 1152.0, 1153.0, 1154.0, 1155.0],      [1156.0, 1157.0, 1158.0, 1159.0, 1160.0, 1161.0, 1162.0]],     [[1163.0, 1164.0, 1165.0, 1166.0, 1167.0, 1168.0, 1169.0],      [1170.0, 1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0]],     [[1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0, 1183.0],      [1184.0, 1185.0, 1186.0, 1187.0, 1188.0, 1189.0, 1190.0]]],    [[[1191.0, 1192.0, 1193.0, 1194.0, 1195.0, 1196.0, 1197.0],      [1198.0, 1199.0, 1200.0, 1201.0, 1202.0, 1203.0, 1204.0]],     [[1205.0, 1206.0, 1207.0, 1208.0, 1209.0, 1210.0, 1211.0],      [1212.0, 1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0]],     [[1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0, 1225.0],      [1226.0, 1227.0, 1228.0, 1229.0, 1230.0, 1231.0, 1232.0]],     [[1233.0, 1234.0, 1235.0, 1236.0, 1237.0, 1238.0, 1239.0],      [1240.0, 1241.0, 1242.0, 1243.0, 1244.0, 1245.0, 1246.0]],     [[1247.0, 1248.0, 1249.0, 1250.0, 1251.0, 1252.0, 1253.0],      [1254.0, 1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]]]],   [[[[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0, 1267.0],      [1268.0, 1269.0, 1270.0, 1271.0, 1272.0, 1273.0, 1274.0]],     [[1275.0, 1276.0, 1277.0, 1278.0, 1279.0, 1280.0, 1281.0],      [1282.0, 1283.0, 1284.0, 1285.0, 1286.0, 1287.0, 1288.0]],     [[1289.0, 1290.0, 1291.0, 1292.0, 1293.0, 1294.0, 1295.0],      [1296.0, 1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0]],     [[1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0, 1309.0],      [1310.0, 1311.0, 1312.0, 1313.0, 1314.0, 1315.0, 1316.0]],     [[1317.0, 1318.0, 1319.0, 1320.0, 1321.0, 1322.0, 1323.0],      [1324.0, 1325.0, 1326.0, 1327.0, 1328.0, 1329.0, 1330.0]]],    [[[1331.0, 1332.0, 1333.0, 1334.0, 1335.0, 1336.0, 1337.0],      [1338.0, 1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0]],     [[1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0, 1351.0],      [1352.0, 1353.0, 1354.0, 1355.0, 1356.0, 1357.0, 1358.0]],     [[1359.0, 1360.0, 1361.0, 1362.0, 1363.0, 1364.0, 1365.0],      [1366.0, 1367.0, 1368.0, 1369.0, 1370.0, 1371.0, 1372.0]],     [[1373.0, 1374.0, 1375.0, 1376.0, 1377.0, 1378.0, 1379.0],      [1380.0, 1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0]],     [[1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0, 1393.0],      [1394.0, 1395.0, 1396.0, 1397.0, 1398.0, 1399.0, 1400.0]]],    [[[1401.0, 1402.0, 1403.0, 1404.0, 1405.0, 1406.0, 1407.0],      [1408.0, 1409.0, 1410.0, 1411.0, 1412.0, 1413.0, 1414.0]],     [[1415.0, 1416.0, 1417.0, 1418.0, 1419.0, 1420.0, 1421.0],      [1422.0, 1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0]],     [[1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0, 1435.0],      [1436.0, 1437.0, 1438.0, 1439.0, 1440.0, 1441.0, 1442.0]],     [[1443.0, 1444.0, 1445.0, 1446.0, 1447.0, 1448.0, 1449.0],      [1450.0, 1451.0, 1452.0, 1453.0, 1454.0, 1455.0, 1456.0]],     [[1457.0, 1458.0, 1459.0, 1460.0, 1461.0, 1462.0, 1463.0],      [1464.0, 1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0]]],    [[[1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0, 1477.0],      [1478.0, 1479.0, 1480.0, 1481.0, 1482.0, 1483.0, 1484.0]],     [[1485.0, 1486.0, 1487.0, 1488.0, 1489.0, 1490.0, 1491.0],      [1492.0, 1493.0, 1494.0, 1495.0, 1496.0, 1497.0, 1498.0]],     [[1499.0, 1500.0, 1501.0, 1502.0, 1503.0, 1504.0, 1505.0],      [1506.0, 1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0]],     [[1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0, 1519.0],      [1520.0, 1521.0, 1522.0, 1523.0, 1524.0, 1525.0, 1526.0]],     [[1527.0, 1528.0, 1529.0, 1530.0, 1531.0, 1532.0, 1533.0],      [1534.0, 1535.0, 1536.0, 1537.0, 1538.0, 1539.0, 1540.0]]],    [[[1541.0, 1542.0, 1543.0, 1544.0, 1545.0, 1546.0, 1547.0],      [1548.0, 1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0]],     [[1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0, 1561.0],      [1562.0, 1563.0, 1564.0, 1565.0, 1566.0, 1567.0, 1568.0]],     [[1569.0, 1570.0, 1571.0, 1572.0, 1573.0, 1574.0, 1575.0],      [1576.0, 1577.0, 1578.0, 1579.0, 1580.0, 1581.0, 1582.0]],     [[1583.0, 1584.0, 1585.0, 1586.0, 1587.0, 1588.0, 1589.0],      [1590.0, 1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0]],     [[1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0, 1603.0],      [1604.0, 1605.0, 1606.0, 1607.0, 1608.0, 1609.0, 1610.0]]],    [[[1611.0, 1612.0, 1613.0, 1614.0, 1615.0, 1616.0, 1617.0],      [1618.0, 1619.0, 1620.0, 1621.0, 1622.0, 1623.0, 1624.0]],     [[1625.0, 1626.0, 1627.0, 1628.0, 1629.0, 1630.0, 1631.0],      [1632.0, 1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0]],     [[1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0, 1645.0],      [1646.0, 1647.0, 1648.0, 1649.0, 1650.0, 1651.0, 1652.0]],     [[1653.0, 1654.0, 1655.0, 1656.0, 1657.0, 1658.0, 1659.0],      [1660.0, 1661.0, 1662.0, 1663.0, 1664.0, 1665.0, 1666.0]],     [[1667.0, 1668.0, 1669.0, 1670.0, 1671.0, 1672.0, 1673.0],      [1674.0, 1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]]]],   [[[[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0, 1687.0],      [1688.0, 1689.0, 1690.0, 1691.0, 1692.0, 1693.0, 1694.0]],     [[1695.0, 1696.0, 1697.0, 1698.0, 1699.0, 1700.0, 1701.0],      [1702.0, 1703.0, 1704.0, 1705.0, 1706.0, 1707.0, 1708.0]],     [[1709.0, 1710.0, 1711.0, 1712.0, 1713.0, 1714.0, 1715.0],      [1716.0, 1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0]],     [[1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0, 1729.0],      [1730.0, 1731.0, 1732.0, 1733.0, 1734.0, 1735.0, 1736.0]],     [[1737.0, 1738.0, 1739.0, 1740.0, 1741.0, 1742.0, 1743.0],      [1744.0, 1745.0, 1746.0, 1747.0, 1748.0, 1749.0, 1750.0]]],    [[[1751.0, 1752.0, 1753.0, 1754.0, 1755.0, 1756.0, 1757.0],      [1758.0, 1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0]],     [[1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0, 1771.0],      [1772.0, 1773.0, 1774.0, 1775.0, 1776.0, 1777.0, 1778.0]],     [[1779.0, 1780.0, 1781.0, 1782.0, 1783.0, 1784.0, 1785.0],      [1786.0, 1787.0, 1788.0, 1789.0, 1790.0, 1791.0, 1792.0]],     [[1793.0, 1794.0, 1795.0, 1796.0, 1797.0, 1798.0, 1799.0],      [1800.0, 1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0]],     [[1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0, 1813.0],      [1814.0, 1815.0, 1816.0, 1817.0, 1818.0, 1819.0, 1820.0]]],    [[[1821.0, 1822.0, 1823.0, 1824.0, 1825.0, 1826.0, 1827.0],      [1828.0, 1829.0, 1830.0, 1831.0, 1832.0, 1833.0, 1834.0]],     [[1835.0, 1836.0, 1837.0, 1838.0, 1839.0, 1840.0, 1841.0],      [1842.0, 1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0]],     [[1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0, 1855.0],      [1856.0, 1857.0, 1858.0, 1859.0, 1860.0, 1861.0, 1862.0]],     [[1863.0, 1864.0, 1865.0, 1866.0, 1867.0, 1868.0, 1869.0],      [1870.0, 1871.0, 1872.0, 1873.0, 1874.0, 1875.0, 1876.0]],     [[1877.0, 1878.0, 1879.0, 1880.0, 1881.0, 1882.0, 1883.0],      [1884.0, 1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0]]],    [[[1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0, 1897.0],      [1898.0, 1899.0, 1900.0, 1901.0, 1902.0, 1903.0, 1904.0]],     [[1905.0, 1906.0, 1907.0, 1908.0, 1909.0, 1910.0, 1911.0],      [1912.0, 1913.0, 1914.0, 1915.0, 1916.0, 1917.0, 1918.0]],     [[1919.0, 1920.0, 1921.0, 1922.0, 1923.0, 1924.0, 1925.0],      [1926.0, 1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0]],     [[1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0, 1939.0],      [1940.0, 1941.0, 1942.0, 1943.0, 1944.0, 1945.0, 1946.0]],     [[1947.0, 1948.0, 1949.0, 1950.0, 1951.0, 1952.0, 1953.0],      [1954.0, 1955.0, 1956.0, 1957.0, 1958.0, 1959.0, 1960.0]]],    [[[1961.0, 1962.0, 1963.0, 1964.0, 1965.0, 1966.0, 1967.0],      [1968.0, 1969.0, 1970.0, 1971.0, 1972.0, 1973.0, 1974.0]],     [[1975.0, 1976.0, 1977.0, 1978.0, 1979.0, 1980.0, 1981.0],      [1982.0, 1983.0, 1984.0, 1985.0, 1986.0, 1987.0, 1988.0]],     [[1989.0, 1990.0, 1991.0, 1992.0, 1993.0, 1994.0, 1995.0],      [1996.0, 1997.0, 1998.0, 1999.0, 2000.0, 2001.0, 2002.0]],     [[2003.0, 2004.0, 2005.0, 2006.0, 2007.0, 2008.0, 2009.0],      [2010.0, 2011.0, 2012.0, 2013.0, 2014.0, 2015.0, 2016.0]],     [[2017.0, 2018.0, 2019.0, 2020.0, 2021.0, 2022.0, 2023.0],      [2024.0, 2025.0, 2026.0, 2027.0, 2028.0, 2029.0, 2030.0]]],    [[[2031.0, 2032.0, 2033.0, 2034.0, 2035.0, 2036.0, 2037.0],      [2038.0, 2039.0, 2040.0, 2041.0, 2042.0, 2043.0, 2044.0]],     [[2045.0, 2046.0, 2047.0, 2048.0, 2049.0, 2050.0, 2051.0],      [2052.0, 2053.0, 2054.0, 2055.0, 2056.0, 2057.0, 2058.0]],     [[2059.0, 2060.0, 2061.0, 2062.0, 2063.0, 2064.0, 2065.0],      [2066.0, 2067.0, 2068.0, 2069.0, 2070.0, 2071.0, 2072.0]],     [[2073.0, 2074.0, 2075.0, 2076.0, 2077.0, 2078.0, 2079.0],      [2080.0, 2081.0, 2082.0, 2083.0, 2084.0, 2085.0, 2086.0]],     [[2087.0, 2088.0, 2089.0, 2090.0, 2091.0, 2092.0, 2093.0],      [2094.0, 2095.0, 2096.0, 2097.0, 2098.0, 2099.0, 2100.0]]]],   [[[[2101.0, 2102.0, 2103.0, 2104.0, 2105.0, 2106.0, 2107.0],      [2108.0, 2109.0, 2110.0, 2111.0, 2112.0, 2113.0, 2114.0]],     [[2115.0, 2116.0, 2117.0, 2118.0, 2119.0, 2120.0, 2121.0],      [2122.0, 2123.0, 2124.0, 2125.0, 2126.0, 2127.0, 2128.0]],     [[2129.0, 2130.0, 2131.0, 2132.0, 2133.0, 2134.0, 2135.0],      [2136.0, 2137.0, 2138.0, 2139.0, 2140.0, 2141.0, 2142.0]],     [[2143.0, 2144.0, 2145.0, 2146.0, 2147.0, 2148.0, 2149.0],      [2150.0, 2151.0, 2152.0, 2153.0, 2154.0, 2155.0, 2156.0]],     [[2157.0, 2158.0, 2159.0, 2160.0, 2161.0, 2162.0, 2163.0],      [2164.0, 2165.0, 2166.0, 2167.0, 2168.0, 2169.0, 2170.0]]],    [[[2171.0, 2172.0, 2173.0, 2174.0, 2175.0, 2176.0, 2177.0],      [2178.0, 2179.0, 2180.0, 2181.0, 2182.0, 2183.0, 2184.0]],     [[2185.0, 2186.0, 2187.0, 2188.0, 2189.0, 2190.0, 2191.0],      [2192.0, 2193.0, 2194.0, 2195.0, 2196.0, 2197.0, 2198.0]],     [[2199.0, 2200.0, 2201.0, 2202.0, 2203.0, 2204.0, 2205.0],      [2206.0, 2207.0, 2208.0, 2209.0, 2210.0, 2211.0, 2212.0]],     [[2213.0, 2214.0, 2215.0, 2216.0, 2217.0, 2218.0, 2219.0],      [2220.0, 2221.0, 2222.0, 2223.0, 2224.0, 2225.0, 2226.0]],     [[2227.0, 2228.0, 2229.0, 2230.0, 2231.0, 2232.0, 2233.0],      [2234.0, 2235.0, 2236.0, 2237.0, 2238.0, 2239.0, 2240.0]]],    [[[2241.0, 2242.0, 2243.0, 2244.0, 2245.0, 2246.0, 2247.0],      [2248.0, 2249.0, 2250.0, 2251.0, 2252.0, 2253.0, 2254.0]],     [[2255.0, 2256.0, 2257.0, 2258.0, 2259.0, 2260.0, 2261.0],      [2262.0, 2263.0, 2264.0, 2265.0, 2266.0, 2267.0, 2268.0]],     [[2269.0, 2270.0, 2271.0, 2272.0, 2273.0, 2274.0, 2275.0],      [2276.0, 2277.0, 2278.0, 2279.0, 2280.0, 2281.0, 2282.0]],     [[2283.0, 2284.0, 2285.0, 2286.0, 2287.0, 2288.0, 2289.0],      [2290.0, 2291.0, 2292.0, 2293.0, 2294.0, 2295.0, 2296.0]],     [[2297.0, 2298.0, 2299.0, 2300.0, 2301.0, 2302.0, 2303.0],      [2304.0, 2305.0, 2306.0, 2307.0, 2308.0, 2309.0, 2310.0]]],    [[[2311.0, 2312.0, 2313.0, 2314.0, 2315.0, 2316.0, 2317.0],      [2318.0, 2319.0, 2320.0, 2321.0, 2322.0, 2323.0, 2324.0]],     [[2325.0, 2326.0, 2327.0, 2328.0, 2329.0, 2330.0, 2331.0],      [2332.0, 2333.0, 2334.0, 2335.0, 2336.0, 2337.0, 2338.0]],     [[2339.0, 2340.0, 2341.0, 2342.0, 2343.0, 2344.0, 2345.0],      [2346.0, 2347.0, 2348.0, 2349.0, 2350.0, 2351.0, 2352.0]],     [[2353.0, 2354.0, 2355.0, 2356.0, 2357.0, 2358.0, 2359.0],      [2360.0, 2361.0, 2362.0, 2363.0, 2364.0, 2365.0, 2366.0]],     [[2367.0, 2368.0, 2369.0, 2370.0, 2371.0, 2372.0, 2373.0],      [2374.0, 2375.0, 2376.0, 2377.0, 2378.0, 2379.0, 2380.0]]],    [[[2381.0, 2382.0, 2383.0, 2384.0, 2385.0, 2386.0, 2387.0],      [2388.0, 2389.0, 2390.0, 2391.0, 2392.0, 2393.0, 2394.0]],     [[2395.0, 2396.0, 2397.0, 2398.0, 2399.0, 2400.0, 2401.0],      [2402.0, 2403.0, 2404.0, 2405.0, 2406.0, 2407.0, 2408.0]],     [[2409.0, 2410.0, 2411.0, 2412.0, 2413.0, 2414.0, 2415.0],      [2416.0, 2417.0, 2418.0, 2419.0, 2420.0, 2421.0, 2422.0]],     [[2423.0, 2424.0, 2425.0, 2426.0, 2427.0, 2428.0, 2429.0],      [2430.0, 2431.0, 2432.0, 2433.0, 2434.0, 2435.0, 2436.0]],     [[2437.0, 2438.0, 2439.0, 2440.0, 2441.0, 2442.0, 2443.0],      [2444.0, 2445.0, 2446.0, 2447.0, 2448.0, 2449.0, 2450.0]]],    [[[2451.0, 2452.0, 2453.0, 2454.0, 2455.0, 2456.0, 2457.0],      [2458.0, 2459.0, 2460.0, 2461.0, 2462.0, 2463.0, 2464.0]],     [[2465.0, 2466.0, 2467.0, 2468.0, 2469.0, 2470.0, 2471.0],      [2472.0, 2473.0, 2474.0, 2475.0, 2476.0, 2477.0, 2478.0]],     [[2479.0, 2480.0, 2481.0, 2482.0, 2483.0, 2484.0, 2485.0],      [2486.0, 2487.0, 2488.0, 2489.0, 2490.0, 2491.0, 2492.0]],     [[2493.0, 2494.0, 2495.0, 2496.0, 2497.0, 2498.0, 2499.0],      [2500.0, 2501.0, 2502.0, 2503.0, 2504.0, 2505.0, 2506.0]],     [[2507.0, 2508.0, 2509.0, 2510.0, 2511.0, 2512.0, 2513.0],      [2514.0, 2515.0, 2516.0, 2517.0, 2518.0, 2519.0, 2520.0]]]],   [[[[2521.0, 2522.0, 2523.0, 2524.0, 2525.0, 2526.0, 2527.0],      [2528.0, 2529.0, 2530.0, 2531.0, 2532.0, 2533.0, 2534.0]],     [[2535.0, 2536.0, 2537.0, 2538.0, 2539.0, 2540.0, 2541.0],      [2542.0, 2543.0, 2544.0, 2545.0, 2546.0, 2547.0, 2548.0]],     [[2549.0, 2550.0, 2551.0, 2552.0, 2553.0, 2554.0, 2555.0],      [2556.0, 2557.0, 2558.0, 2559.0, 2560.0, 2561.0, 2562.0]],     [[2563.0, 2564.0, 2565.0, 2566.0, 2567.0, 2568.0, 2569.0],      [2570.0, 2571.0, 2572.0, 2573.0, 2574.0, 2575.0, 2576.0]],     [[2577.0, 2578.0, 2579.0, 2580.0, 2581.0, 2582.0, 2583.0],      [2584.0, 2585.0, 2586.0, 2587.0, 2588.0, 2589.0, 2590.0]]],    [[[2591.0, 2592.0, 2593.0, 2594.0, 2595.0, 2596.0, 2597.0],      [2598.0, 2599.0, 2600.0, 2601.0, 2602.0, 2603.0, 2604.0]],     [[2605.0, 2606.0, 2607.0, 2608.0, 2609.0, 2610.0, 2611.0],      [2612.0, 2613.0, 2614.0, 2615.0, 2616.0, 2617.0, 2618.0]],     [[2619.0, 2620.0, 2621.0, 2622.0, 2623.0, 2624.0, 2625.0],      [2626.0, 2627.0, 2628.0, 2629.0, 2630.0, 2631.0, 2632.0]],     [[2633.0, 2634.0, 2635.0, 2636.0, 2637.0, 2638.0, 2639.0],      [2640.0, 2641.0, 2642.0, 2643.0, 2644.0, 2645.0, 2646.0]],     [[2647.0, 2648.0, 2649.0, 2650.0, 2651.0, 2652.0, 2653.0],      [2654.0, 2655.0, 2656.0, 2657.0, 2658.0, 2659.0, 2660.0]]],    [[[2661.0, 2662.0, 2663.0, 2664.0, 2665.0, 2666.0, 2667.0],      [2668.0, 2669.0, 2670.0, 2671.0, 2672.0, 2673.0, 2674.0]],     [[2675.0, 2676.0, 2677.0, 2678.0, 2679.0, 2680.0, 2681.0],      [2682.0, 2683.0, 2684.0, 2685.0, 2686.0, 2687.0, 2688.0]],     [[2689.0, 2690.0, 2691.0, 2692.0, 2693.0, 2694.0, 2695.0],      [2696.0, 2697.0, 2698.0, 2699.0, 2700.0, 2701.0, 2702.0]],     [[2703.0, 2704.0, 2705.0, 2706.0, 2707.0, 2708.0, 2709.0],      [2710.0, 2711.0, 2712.0, 2713.0, 2714.0, 2715.0, 2716.0]],     [[2717.0, 2718.0, 2719.0, 2720.0, 2721.0, 2722.0, 2723.0],      [2724.0, 2725.0, 2726.0, 2727.0, 2728.0, 2729.0, 2730.0]]],    [[[2731.0, 2732.0, 2733.0, 2734.0, 2735.0, 2736.0, 2737.0],      [2738.0, 2739.0, 2740.0, 2741.0, 2742.0, 2743.0, 2744.0]],     [[2745.0, 2746.0, 2747.0, 2748.0, 2749.0, 2750.0, 2751.0],      [2752.0, 2753.0, 2754.0, 2755.0, 2756.0, 2757.0, 2758.0]],     [[2759.0, 2760.0, 2761.0, 2762.0, 2763.0, 2764.0, 2765.0],      [2766.0, 2767.0, 2768.0, 2769.0, 2770.0, 2771.0, 2772.0]],     [[2773.0, 2774.0, 2775.0, 2776.0, 2777.0, 2778.0, 2779.0],      [2780.0, 2781.0, 2782.0, 2783.0, 2784.0, 2785.0, 2786.0]],     [[2787.0, 2788.0, 2789.0, 2790.0, 2791.0, 2792.0, 2793.0],      [2794.0, 2795.0, 2796.0, 2797.0, 2798.0, 2799.0, 2800.0]]],    [[[2801.0, 2802.0, 2803.0, 2804.0, 2805.0, 2806.0, 2807.0],      [2808.0, 2809.0, 2810.0, 2811.0, 2812.0, 2813.0, 2814.0]],     [[2815.0, 2816.0, 2817.0, 2818.0, 2819.0, 2820.0, 2821.0],      [2822.0, 2823.0, 2824.0, 2825.0, 2826.0, 2827.0, 2828.0]],     [[2829.0, 2830.0, 2831.0, 2832.0, 2833.0, 2834.0, 2835.0],      [2836.0, 2837.0, 2838.0, 2839.0, 2840.0, 2841.0, 2842.0]],     [[2843.0, 2844.0, 2845.0, 2846.0, 2847.0, 2848.0, 2849.0],      [2850.0, 2851.0, 2852.0, 2853.0, 2854.0, 2855.0, 2856.0]],     [[2857.0, 2858.0, 2859.0, 2860.0, 2861.0, 2862.0, 2863.0],      [2864.0, 2865.0, 2866.0, 2867.0, 2868.0, 2869.0, 2870.0]]],    [[[2871.0, 2872.0, 2873.0, 2874.0, 2875.0, 2876.0, 2877.0],      [2878.0, 2879.0, 2880.0, 2881.0, 2882.0, 2883.0, 2884.0]],     [[2885.0, 2886.0, 2887.0, 2888.0, 2889.0, 2890.0, 2891.0],      [2892.0, 2893.0, 2894.0, 2895.0, 2896.0, 2897.0, 2898.0]],     [[2899.0, 2900.0, 2901.0, 2902.0, 2903.0, 2904.0, 2905.0],      [2906.0, 2907.0, 2908.0, 2909.0, 2910.0, 2911.0, 2912.0]],     [[2913.0, 2914.0, 2915.0, 2916.0, 2917.0, 2918.0, 2919.0],      [2920.0, 2921.0, 2922.0, 2923.0, 2924.0, 2925.0, 2926.0]],     [[2927.0, 2928.0, 2929.0, 2930.0, 2931.0, 2932.0, 2933.0],      [2934.0, 2935.0, 2936.0, 2937.0, 2938.0, 2939.0, 2940.0]]]]],  [[[[[2941.0, 2942.0, 2943.0, 2944.0, 2945.0, 2946.0, 2947.0],      [2948.0, 2949.0, 2950.0, 2951.0, 2952.0, 2953.0, 2954.0]],     [[2955.0, 2956.0, 2957.0, 2958.0, 2959.0, 2960.0, 2961.0],      [2962.0, 2963.0, 2964.0, 2965.0, 2966.0, 2967.0, 2968.0]],     [[2969.0, 2970.0, 2971.0, 2972.0, 2973.0, 2974.0, 2975.0],      [2976.0, 2977.0, 2978.0, 2979.0, 2980.0, 2981.0, 2982.0]],     [[2983.0, 2984.0, 2985.0, 2986.0, 2987.0, 2988.0, 2989.0],      [2990.0, 2991.0, 2992.0, 2993.0, 2994.0, 2995.0, 2996.0]],     [[2997.0, 2998.0, 2999.0, 3000.0, 3001.0, 3002.0, 3003.0],      [3004.0, 3005.0, 3006.0, 3007.0, 3008.0, 3009.0, 3010.0]]],    [[[3011.0, 3012.0, 3013.0, 3014.0, 3015.0, 3016.0, 3017.0],      [3018.0, 3019.0, 3020.0, 3021.0, 3022.0, 3023.0, 3024.0]],     [[3025.0, 3026.0, 3027.0, 3028.0, 3029.0, 3030.0, 3031.0],      [3032.0, 3033.0, 3034.0, 3035.0, 3036.0, 3037.0, 3038.0]],     [[3039.0, 3040.0, 3041.0, 3042.0, 3043.0, 3044.0, 3045.0],      [3046.0, 3047.0, 3048.0, 3049.0, 3050.0, 3051.0, 3052.0]],     [[3053.0, 3054.0, 3055.0, 3056.0, 3057.0, 3058.0, 3059.0],      [3060.0, 3061.0, 3062.0, 3063.0, 3064.0, 3065.0, 3066.0]],     [[3067.0, 3068.0, 3069.0, 3070.0, 3071.0, 3072.0, 3073.0],      [3074.0, 3075.0, 3076.0, 3077.0, 3078.0, 3079.0, 3080.0]]],    [[[3081.0, 3082.0, 3083.0, 3084.0, 3085.0, 3086.0, 3087.0],      [3088.0, 3089.0, 3090.0, 3091.0, 3092.0, 3093.0, 3094.0]],     [[3095.0, 3096.0, 3097.0, 3098.0, 3099.0, 3100.0, 3101.0],      [3102.0, 3103.0, 3104.0, 3105.0, 3106.0, 3107.0, 3108.0]],     [[3109.0, 3110.0, 3111.0, 3112.0, 3113.0, 3114.0, 3115.0],      [3116.0, 3117.0, 3118.0, 3119.0, 3120.0, 3121.0, 3122.0]],     [[3123.0, 3124.0, 3125.0, 3126.0, 3127.0, 3128.0, 3129.0],      [3130.0, 3131.0, 3132.0, 3133.0, 3134.0, 3135.0, 3136.0]],     [[3137.0, 3138.0, 3139.0, 3140.0, 3141.0, 3142.0, 3143.0],      [3144.0, 3145.0, 3146.0, 3147.0, 3148.0, 3149.0, 3150.0]]],    [[[3151.0, 3152.0, 3153.0, 3154.0, 3155.0, 3156.0, 3157.0],      [3158.0, 3159.0, 3160.0, 3161.0, 3162.0, 3163.0, 3164.0]],     [[3165.0, 3166.0, 3167.0, 3168.0, 3169.0, 3170.0, 3171.0],      [3172.0, 3173.0, 3174.0, 3175.0, 3176.0, 3177.0, 3178.0]],     [[3179.0, 3180.0, 3181.0, 3182.0, 3183.0, 3184.0, 3185.0],      [3186.0, 3187.0, 3188.0, 3189.0, 3190.0, 3191.0, 3192.0]],     [[3193.0, 3194.0, 3195.0, 3196.0, 3197.0, 3198.0, 3199.0],      [3200.0, 3201.0, 3202.0, 3203.0, 3204.0, 3205.0, 3206.0]],     [[3207.0, 3208.0, 3209.0, 3210.0, 3211.0, 3212.0, 3213.0],      [3214.0, 3215.0, 3216.0, 3217.0, 3218.0, 3219.0, 3220.0]]],    [[[3221.0, 3222.0, 3223.0, 3224.0, 3225.0, 3226.0, 3227.0],      [3228.0, 3229.0, 3230.0, 3231.0, 3232.0, 3233.0, 3234.0]],     [[3235.0, 3236.0, 3237.0, 3238.0, 3239.0, 3240.0, 3241.0],      [3242.0, 3243.0, 3244.0, 3245.0, 3246.0, 3247.0, 3248.0]],     [[3249.0, 3250.0, 3251.0, 3252.0, 3253.0, 3254.0, 3255.0],      [3256.0, 3257.0, 3258.0, 3259.0, 3260.0, 3261.0, 3262.0]],     [[3263.0, 3264.0, 3265.0, 3266.0, 3267.0, 3268.0, 3269.0],      [3270.0, 3271.0, 3272.0, 3273.0, 3274.0, 3275.0, 3276.0]],     [[3277.0, 3278.0, 3279.0, 3280.0, 3281.0, 3282.0, 3283.0],      [3284.0, 3285.0, 3286.0, 3287.0, 3288.0, 3289.0, 3290.0]]],    [[[3291.0, 3292.0, 3293.0, 3294.0, 3295.0, 3296.0, 3297.0],      [3298.0, 3299.0, 3300.0, 3301.0, 3302.0, 3303.0, 3304.0]],     [[3305.0, 3306.0, 3307.0, 3308.0, 3309.0, 3310.0, 3311.0],      [3312.0, 3313.0, 3314.0, 3315.0, 3316.0, 3317.0, 3318.0]],     [[3319.0, 3320.0, 3321.0, 3322.0, 3323.0, 3324.0, 3325.0],      [3326.0, 3327.0, 3328.0, 3329.0, 3330.0, 3331.0, 3332.0]],     [[3333.0, 3334.0, 3335.0, 3336.0, 3337.0, 3338.0, 3339.0],      [3340.0, 3341.0, 3342.0, 3343.0, 3344.0, 3345.0, 3346.0]],     [[3347.0, 3348.0, 3349.0, 3350.0, 3351.0, 3352.0, 3353.0],      [3354.0, 3355.0, 3356.0, 3357.0, 3358.0, 3359.0, 3360.0]]]],   [[[[3361.0, 3362.0, 3363.0, 3364.0, 3365.0, 3366.0, 3367.0],      [3368.0, 3369.0, 3370.0, 3371.0, 3372.0, 3373.0, 3374.0]],     [[3375.0, 3376.0, 3377.0, 3378.0, 3379.0, 3380.0, 3381.0],      [3382.0, 3383.0, 3384.0, 3385.0, 3386.0, 3387.0, 3388.0]],     [[3389.0, 3390.0, 3391.0, 3392.0, 3393.0, 3394.0, 3395.0],      [3396.0, 3397.0, 3398.0, 3399.0, 3400.0, 3401.0, 3402.0]],     [[3403.0, 3404.0, 3405.0, 3406.0, 3407.0, 3408.0, 3409.0],      [3410.0, 3411.0, 3412.0, 3413.0, 3414.0, 3415.0, 3416.0]],     [[3417.0, 3418.0, 3419.0, 3420.0, 3421.0, 3422.0, 3423.0],      [3424.0, 3425.0, 3426.0, 3427.0, 3428.0, 3429.0, 3430.0]]],    [[[3431.0, 3432.0, 3433.0, 3434.0, 3435.0, 3436.0, 3437.0],      [3438.0, 3439.0, 3440.0, 3441.0, 3442.0, 3443.0, 3444.0]],     [[3445.0, 3446.0, 3447.0, 3448.0, 3449.0, 3450.0, 3451.0],      [3452.0, 3453.0, 3454.0, 3455.0, 3456.0, 3457.0, 3458.0]],     [[3459.0, 3460.0, 3461.0, 3462.0, 3463.0, 3464.0, 3465.0],      [3466.0, 3467.0, 3468.0, 3469.0, 3470.0, 3471.0, 3472.0]],     [[3473.0, 3474.0, 3475.0, 3476.0, 3477.0, 3478.0, 3479.0],      [3480.0, 3481.0, 3482.0, 3483.0, 3484.0, 3485.0, 3486.0]],     [[3487.0, 3488.0, 3489.0, 3490.0, 3491.0, 3492.0, 3493.0],      [3494.0, 3495.0, 3496.0, 3497.0, 3498.0, 3499.0, 3500.0]]],    [[[3501.0, 3502.0, 3503.0, 3504.0, 3505.0, 3506.0, 3507.0],      [3508.0, 3509.0, 3510.0, 3511.0, 3512.0, 3513.0, 3514.0]],     [[3515.0, 3516.0, 3517.0, 3518.0, 3519.0, 3520.0, 3521.0],      [3522.0, 3523.0, 3524.0, 3525.0, 3526.0, 3527.0, 3528.0]],     [[3529.0, 3530.0, 3531.0, 3532.0, 3533.0, 3534.0, 3535.0],      [3536.0, 3537.0, 3538.0, 3539.0, 3540.0, 3541.0, 3542.0]],     [[3543.0, 3544.0, 3545.0, 3546.0, 3547.0, 3548.0, 3549.0],      [3550.0, 3551.0, 3552.0, 3553.0, 3554.0, 3555.0, 3556.0]],     [[3557.0, 3558.0, 3559.0, 3560.0, 3561.0, 3562.0, 3563.0],      [3564.0, 3565.0, 3566.0, 3567.0, 3568.0, 3569.0, 3570.0]]],    [[[3571.0, 3572.0, 3573.0, 3574.0, 3575.0, 3576.0, 3577.0],      [3578.0, 3579.0, 3580.0, 3581.0, 3582.0, 3583.0, 3584.0]],     [[3585.0, 3586.0, 3587.0, 3588.0, 3589.0, 3590.0, 3591.0],      [3592.0, 3593.0, 3594.0, 3595.0, 3596.0, 3597.0, 3598.0]],     [[3599.0, 3600.0, 3601.0, 3602.0, 3603.0, 3604.0, 3605.0],      [3606.0, 3607.0, 3608.0, 3609.0, 3610.0, 3611.0, 3612.0]],     [[3613.0, 3614.0, 3615.0, 3616.0, 3617.0, 3618.0, 3619.0],      [3620.0, 3621.0, 3622.0, 3623.0, 3624.0, 3625.0, 3626.0]],     [[3627.0, 3628.0, 3629.0, 3630.0, 3631.0, 3632.0, 3633.0],      [3634.0, 3635.0, 3636.0, 3637.0, 3638.0, 3639.0, 3640.0]]],    [[[3641.0, 3642.0, 3643.0, 3644.0, 3645.0, 3646.0, 3647.0],      [3648.0, 3649.0, 3650.0, 3651.0, 3652.0, 3653.0, 3654.0]],     [[3655.0, 3656.0, 3657.0, 3658.0, 3659.0, 3660.0, 3661.0],      [3662.0, 3663.0, 3664.0, 3665.0, 3666.0, 3667.0, 3668.0]],     [[3669.0, 3670.0, 3671.0, 3672.0, 3673.0, 3674.0, 3675.0],      [3676.0, 3677.0, 3678.0, 3679.0, 3680.0, 3681.0, 3682.0]],     [[3683.0, 3684.0, 3685.0, 3686.0, 3687.0, 3688.0, 3689.0],      [3690.0, 3691.0, 3692.0, 3693.0, 3694.0, 3695.0, 3696.0]],     [[3697.0, 3698.0, 3699.0, 3700.0, 3701.0, 3702.0, 3703.0],      [3704.0, 3705.0, 3706.0, 3707.0, 3708.0, 3709.0, 3710.0]]],    [[[3711.0, 3712.0, 3713.0, 3714.0, 3715.0, 3716.0, 3717.0],      [3718.0, 3719.0, 3720.0, 3721.0, 3722.0, 3723.0, 3724.0]],     [[3725.0, 3726.0, 3727.0, 3728.0, 3729.0, 3730.0, 3731.0],      [3732.0, 3733.0, 3734.0, 3735.0, 3736.0, 3737.0, 3738.0]],     [[3739.0, 3740.0, 3741.0, 3742.0, 3743.0, 3744.0, 3745.0],      [3746.0, 3747.0, 3748.0, 3749.0, 3750.0, 3751.0, 3752.0]],     [[3753.0, 3754.0, 3755.0, 3756.0, 3757.0, 3758.0, 3759.0],      [3760.0, 3761.0, 3762.0, 3763.0, 3764.0, 3765.0, 3766.0]],     [[3767.0, 3768.0, 3769.0, 3770.0, 3771.0, 3772.0, 3773.0],      [3774.0, 3775.0, 3776.0, 3777.0, 3778.0, 3779.0, 3780.0]]]],   [[[[3781.0, 3782.0, 3783.0, 3784.0, 3785.0, 3786.0, 3787.0],      [3788.0, 3789.0, 3790.0, 3791.0, 3792.0, 3793.0, 3794.0]],     [[3795.0, 3796.0, 3797.0, 3798.0, 3799.0, 3800.0, 3801.0],      [3802.0, 3803.0, 3804.0, 3805.0, 3806.0, 3807.0, 3808.0]],     [[3809.0, 3810.0, 3811.0, 3812.0, 3813.0, 3814.0, 3815.0],      [3816.0, 3817.0, 3818.0, 3819.0, 3820.0, 3821.0, 3822.0]],     [[3823.0, 3824.0, 3825.0, 3826.0, 3827.0, 3828.0, 3829.0],      [3830.0, 3831.0, 3832.0, 3833.0, 3834.0, 3835.0, 3836.0]],     [[3837.0, 3838.0, 3839.0, 3840.0, 3841.0, 3842.0, 3843.0],      [3844.0, 3845.0, 3846.0, 3847.0, 3848.0, 3849.0, 3850.0]]],    [[[3851.0, 3852.0, 3853.0, 3854.0, 3855.0, 3856.0, 3857.0],      [3858.0, 3859.0, 3860.0, 3861.0, 3862.0, 3863.0, 3864.0]],     [[3865.0, 3866.0, 3867.0, 3868.0, 3869.0, 3870.0, 3871.0],      [3872.0, 3873.0, 3874.0, 3875.0, 3876.0, 3877.0, 3878.0]],     [[3879.0, 3880.0, 3881.0, 3882.0, 3883.0, 3884.0, 3885.0],      [3886.0, 3887.0, 3888.0, 3889.0, 3890.0, 3891.0, 3892.0]],     [[3893.0, 3894.0, 3895.0, 3896.0, 3897.0, 3898.0, 3899.0],      [3900.0, 3901.0, 3902.0, 3903.0, 3904.0, 3905.0, 3906.0]],     [[3907.0, 3908.0, 3909.0, 3910.0, 3911.0, 3912.0, 3913.0],      [3914.0, 3915.0, 3916.0, 3917.0, 3918.0, 3919.0, 3920.0]]],    [[[3921.0, 3922.0, 3923.0, 3924.0, 3925.0, 3926.0, 3927.0],      [3928.0, 3929.0, 3930.0, 3931.0, 3932.0, 3933.0, 3934.0]],     [[3935.0, 3936.0, 3937.0, 3938.0, 3939.0, 3940.0, 3941.0],      [3942.0, 3943.0, 3944.0, 3945.0, 3946.0, 3947.0, 3948.0]],     [[3949.0, 3950.0, 3951.0, 3952.0, 3953.0, 3954.0, 3955.0],      [3956.0, 3957.0, 3958.0, 3959.0, 3960.0, 3961.0, 3962.0]],     [[3963.0, 3964.0, 3965.0, 3966.0, 3967.0, 3968.0, 3969.0],      [3970.0, 3971.0, 3972.0, 3973.0, 3974.0, 3975.0, 3976.0]],     [[3977.0, 3978.0, 3979.0, 3980.0, 3981.0, 3982.0, 3983.0],      [3984.0, 3985.0, 3986.0, 3987.0, 3988.0, 3989.0, 3990.0]]],    [[[3991.0, 3992.0, 3993.0, 3994.0, 3995.0, 3996.0, 3997.0],      [3998.0, 3999.0, 4000.0, 4001.0, 4002.0, 4003.0, 4004.0]],     [[4005.0, 4006.0, 4007.0, 4008.0, 4009.0, 4010.0, 4011.0],      [4012.0, 4013.0, 4014.0, 4015.0, 4016.0, 4017.0, 4018.0]],     [[4019.0, 4020.0, 4021.0, 4022.0, 4023.0, 4024.0, 4025.0],      [4026.0, 4027.0, 4028.0, 4029.0, 4030.0, 4031.0, 4032.0]],     [[4033.0, 4034.0, 4035.0, 4036.0, 4037.0, 4038.0, 4039.0],      [4040.0, 4041.0, 4042.0, 4043.0, 4044.0, 4045.0, 4046.0]],     [[4047.0, 4048.0, 4049.0, 4050.0, 4051.0, 4052.0, 4053.0],      [4054.0, 4055.0, 4056.0, 4057.0, 4058.0, 4059.0, 4060.0]]],    [[[4061.0, 4062.0, 4063.0, 4064.0, 4065.0, 4066.0, 4067.0],      [4068.0, 4069.0, 4070.0, 4071.0, 4072.0, 4073.0, 4074.0]],     [[4075.0, 4076.0, 4077.0, 4078.0, 4079.0, 4080.0, 4081.0],      [4082.0, 4083.0, 4084.0, 4085.0, 4086.0, 4087.0, 4088.0]],     [[4089.0, 4090.0, 4091.0, 4092.0, 4093.0, 4094.0, 4095.0],      [4096.0, 4097.0, 4098.0, 4099.0, 4100.0, 4101.0, 4102.0]],     [[4103.0, 4104.0, 4105.0, 4106.0, 4107.0, 4108.0, 4109.0],      [4110.0, 4111.0, 4112.0, 4113.0, 4114.0, 4115.0, 4116.0]],     [[4117.0, 4118.0, 4119.0, 4120.0, 4121.0, 4122.0, 4123.0],      [4124.0, 4125.0, 4126.0, 4127.0, 4128.0, 4129.0, 4130.0]]],    [[[4131.0, 4132.0, 4133.0, 4134.0, 4135.0, 4136.0, 4137.0],      [4138.0, 4139.0, 4140.0, 4141.0, 4142.0, 4143.0, 4144.0]],     [[4145.0, 4146.0, 4147.0, 4148.0, 4149.0, 4150.0, 4151.0],      [4152.0, 4153.0, 4154.0, 4155.0, 4156.0, 4157.0, 4158.0]],     [[4159.0, 4160.0, 4161.0, 4162.0, 4163.0, 4164.0, 4165.0],      [4166.0, 4167.0, 4168.0, 4169.0, 4170.0, 4171.0, 4172.0]],     [[4173.0, 4174.0, 4175.0, 4176.0, 4177.0, 4178.0, 4179.0],      [4180.0, 4181.0, 4182.0, 4183.0, 4184.0, 4185.0, 4186.0]],     [[4187.0, 4188.0, 4189.0, 4190.0, 4191.0, 4192.0, 4193.0],      [4194.0, 4195.0, 4196.0, 4197.0, 4198.0, 4199.0, 4200.0]]]],   [[[[4201.0, 4202.0, 4203.0, 4204.0, 4205.0, 4206.0, 4207.0],      [4208.0, 4209.0, 4210.0, 4211.0, 4212.0, 4213.0, 4214.0]],     [[4215.0, 4216.0, 4217.0, 4218.0, 4219.0, 4220.0, 4221.0],      [4222.0, 4223.0, 4224.0, 4225.0, 4226.0, 4227.0, 4228.0]],     [[4229.0, 4230.0, 4231.0, 4232.0, 4233.0, 4234.0, 4235.0],      [4236.0, 4237.0, 4238.0, 4239.0, 4240.0, 4241.0, 4242.0]],     [[4243.0, 4244.0, 4245.0, 4246.0, 4247.0, 4248.0, 4249.0],      [4250.0, 4251.0, 4252.0, 4253.0, 4254.0, 4255.0, 4256.0]],     [[4257.0, 4258.0, 4259.0, 4260.0, 4261.0, 4262.0, 4263.0],      [4264.0, 4265.0, 4266.0, 4267.0, 4268.0, 4269.0, 4270.0]]],    [[[4271.0, 4272.0, 4273.0, 4274.0, 4275.0, 4276.0, 4277.0],      [4278.0, 4279.0, 4280.0, 4281.0, 4282.0, 4283.0, 4284.0]],     [[4285.0, 4286.0, 4287.0, 4288.0, 4289.0, 4290.0, 4291.0],      [4292.0, 4293.0, 4294.0, 4295.0, 4296.0, 4297.0, 4298.0]],     [[4299.0, 4300.0, 4301.0, 4302.0, 4303.0, 4304.0, 4305.0],      [4306.0, 4307.0, 4308.0, 4309.0, 4310.0, 4311.0, 4312.0]],     [[4313.0, 4314.0, 4315.0, 4316.0, 4317.0, 4318.0, 4319.0],      [4320.0, 4321.0, 4322.0, 4323.0, 4324.0, 4325.0, 4326.0]],     [[4327.0, 4328.0, 4329.0, 4330.0, 4331.0, 4332.0, 4333.0],      [4334.0, 4335.0, 4336.0, 4337.0, 4338.0, 4339.0, 4340.0]]],    [[[4341.0, 4342.0, 4343.0, 4344.0, 4345.0, 4346.0, 4347.0],      [4348.0, 4349.0, 4350.0, 4351.0, 4352.0, 4353.0, 4354.0]],     [[4355.0, 4356.0, 4357.0, 4358.0, 4359.0, 4360.0, 4361.0],      [4362.0, 4363.0, 4364.0, 4365.0, 4366.0, 4367.0, 4368.0]],     [[4369.0, 4370.0, 4371.0, 4372.0, 4373.0, 4374.0, 4375.0],      [4376.0, 4377.0, 4378.0, 4379.0, 4380.0, 4381.0, 4382.0]],     [[4383.0, 4384.0, 4385.0, 4386.0, 4387.0, 4388.0, 4389.0],      [4390.0, 4391.0, 4392.0, 4393.0, 4394.0, 4395.0, 4396.0]],     [[4397.0, 4398.0, 4399.0, 4400.0, 4401.0, 4402.0, 4403.0],      [4404.0, 4405.0, 4406.0, 4407.0, 4408.0, 4409.0, 4410.0]]],    [[[4411.0, 4412.0, 4413.0, 4414.0, 4415.0, 4416.0, 4417.0],      [4418.0, 4419.0, 4420.0, 4421.0, 4422.0, 4423.0, 4424.0]],     [[4425.0, 4426.0, 4427.0, 4428.0, 4429.0, 4430.0, 4431.0],      [4432.0, 4433.0, 4434.0, 4435.0, 4436.0, 4437.0, 4438.0]],     [[4439.0, 4440.0, 4441.0, 4442.0, 4443.0, 4444.0, 4445.0],      [4446.0, 4447.0, 4448.0, 4449.0, 4450.0, 4451.0, 4452.0]],     [[4453.0, 4454.0, 4455.0, 4456.0, 4457.0, 4458.0, 4459.0],      [4460.0, 4461.0, 4462.0, 4463.0, 4464.0, 4465.0, 4466.0]],     [[4467.0, 4468.0, 4469.0, 4470.0, 4471.0, 4472.0, 4473.0],      [4474.0, 4475.0, 4476.0, 4477.0, 4478.0, 4479.0, 4480.0]]],    [[[4481.0, 4482.0, 4483.0, 4484.0, 4485.0, 4486.0, 4487.0],      [4488.0, 4489.0, 4490.0, 4491.0, 4492.0, 4493.0, 4494.0]],     [[4495.0, 4496.0, 4497.0, 4498.0, 4499.0, 4500.0, 4501.0],      [4502.0, 4503.0, 4504.0, 4505.0, 4506.0, 4507.0, 4508.0]],     [[4509.0, 4510.0, 4511.0, 4512.0, 4513.0, 4514.0, 4515.0],      [4516.0, 4517.0, 4518.0, 4519.0, 4520.0, 4521.0, 4522.0]],     [[4523.0, 4524.0, 4525.0, 4526.0, 4527.0, 4528.0, 4529.0],      [4530.0, 4531.0, 4532.0, 4533.0, 4534.0, 4535.0, 4536.0]],     [[4537.0, 4538.0, 4539.0, 4540.0, 4541.0, 4542.0, 4543.0],      [4544.0, 4545.0, 4546.0, 4547.0, 4548.0, 4549.0, 4550.0]]],    [[[4551.0, 4552.0, 4553.0, 4554.0, 4555.0, 4556.0, 4557.0],      [4558.0, 4559.0, 4560.0, 4561.0, 4562.0, 4563.0, 4564.0]],     [[4565.0, 4566.0, 4567.0, 4568.0, 4569.0, 4570.0, 4571.0],      [4572.0, 4573.0, 4574.0, 4575.0, 4576.0, 4577.0, 4578.0]],     [[4579.0, 4580.0, 4581.0, 4582.0, 4583.0, 4584.0, 4585.0],      [4586.0, 4587.0, 4588.0, 4589.0, 4590.0, 4591.0, 4592.0]],     [[4593.0, 4594.0, 4595.0, 4596.0, 4597.0, 4598.0, 4599.0],      [4600.0, 4601.0, 4602.0, 4603.0, 4604.0, 4605.0, 4606.0]],     [[4607.0, 4608.0, 4609.0, 4610.0, 4611.0, 4612.0, 4613.0],      [4614.0, 4615.0, 4616.0, 4617.0, 4618.0, 4619.0, 4620.0]]]],   [[[[4621.0, 4622.0, 4623.0, 4624.0, 4625.0, 4626.0, 4627.0],      [4628.0, 4629.0, 4630.0, 4631.0, 4632.0, 4633.0, 4634.0]],     [[4635.0, 4636.0, 4637.0, 4638.0, 4639.0, 4640.0, 4641.0],      [4642.0, 4643.0, 4644.0, 4645.0, 4646.0, 4647.0, 4648.0]],     [[4649.0, 4650.0, 4651.0, 4652.0, 4653.0, 4654.0, 4655.0],      [4656.0, 4657.0, 4658.0, 4659.0, 4660.0, 4661.0, 4662.0]],     [[4663.0, 4664.0, 4665.0, 4666.0, 4667.0, 4668.0, 4669.0],      [4670.0, 4671.0, 4672.0, 4673.0, 4674.0, 4675.0, 4676.0]],     [[4677.0, 4678.0, 4679.0, 4680.0, 4681.0, 4682.0, 4683.0],      [4684.0, 4685.0, 4686.0, 4687.0, 4688.0, 4689.0, 4690.0]]],    [[[4691.0, 4692.0, 4693.0, 4694.0, 4695.0, 4696.0, 4697.0],      [4698.0, 4699.0, 4700.0, 4701.0, 4702.0, 4703.0, 4704.0]],     [[4705.0, 4706.0, 4707.0, 4708.0, 4709.0, 4710.0, 4711.0],      [4712.0, 4713.0, 4714.0, 4715.0, 4716.0, 4717.0, 4718.0]],     [[4719.0, 4720.0, 4721.0, 4722.0, 4723.0, 4724.0, 4725.0],      [4726.0, 4727.0, 4728.0, 4729.0, 4730.0, 4731.0, 4732.0]],     [[4733.0, 4734.0, 4735.0, 4736.0, 4737.0, 4738.0, 4739.0],      [4740.0, 4741.0, 4742.0, 4743.0, 4744.0, 4745.0, 4746.0]],     [[4747.0, 4748.0, 4749.0, 4750.0, 4751.0, 4752.0, 4753.0],      [4754.0, 4755.0, 4756.0, 4757.0, 4758.0, 4759.0, 4760.0]]],    [[[4761.0, 4762.0, 4763.0, 4764.0, 4765.0, 4766.0, 4767.0],      [4768.0, 4769.0, 4770.0, 4771.0, 4772.0, 4773.0, 4774.0]],     [[4775.0, 4776.0, 4777.0, 4778.0, 4779.0, 4780.0, 4781.0],      [4782.0, 4783.0, 4784.0, 4785.0, 4786.0, 4787.0, 4788.0]],     [[4789.0, 4790.0, 4791.0, 4792.0, 4793.0, 4794.0, 4795.0],      [4796.0, 4797.0, 4798.0, 4799.0, 4800.0, 4801.0, 4802.0]],     [[4803.0, 4804.0, 4805.0, 4806.0, 4807.0, 4808.0, 4809.0],      [4810.0, 4811.0, 4812.0, 4813.0, 4814.0, 4815.0, 4816.0]],     [[4817.0, 4818.0, 4819.0, 4820.0, 4821.0, 4822.0, 4823.0],      [4824.0, 4825.0, 4826.0, 4827.0, 4828.0, 4829.0, 4830.0]]],    [[[4831.0, 4832.0, 4833.0, 4834.0, 4835.0, 4836.0, 4837.0],      [4838.0, 4839.0, 4840.0, 4841.0, 4842.0, 4843.0, 4844.0]],     [[4845.0, 4846.0, 4847.0, 4848.0, 4849.0, 4850.0, 4851.0],      [4852.0, 4853.0, 4854.0, 4855.0, 4856.0, 4857.0, 4858.0]],     [[4859.0, 4860.0, 4861.0, 4862.0, 4863.0, 4864.0, 4865.0],      [4866.0, 4867.0, 4868.0, 4869.0, 4870.0, 4871.0, 4872.0]],     [[4873.0, 4874.0, 4875.0, 4876.0, 4877.0, 4878.0, 4879.0],      [4880.0, 4881.0, 4882.0, 4883.0, 4884.0, 4885.0, 4886.0]],     [[4887.0, 4888.0, 4889.0, 4890.0, 4891.0, 4892.0, 4893.0],      [4894.0, 4895.0, 4896.0, 4897.0, 4898.0, 4899.0, 4900.0]]],    [[[4901.0, 4902.0, 4903.0, 4904.0, 4905.0, 4906.0, 4907.0],      [4908.0, 4909.0, 4910.0, 4911.0, 4912.0, 4913.0, 4914.0]],     [[4915.0, 4916.0, 4917.0, 4918.0, 4919.0, 4920.0, 4921.0],      [4922.0, 4923.0, 4924.0, 4925.0, 4926.0, 4927.0, 4928.0]],     [[4929.0, 4930.0, 4931.0, 4932.0, 4933.0, 4934.0, 4935.0],      [4936.0, 4937.0, 4938.0, 4939.0, 4940.0, 4941.0, 4942.0]],     [[4943.0, 4944.0, 4945.0, 4946.0, 4947.0, 4948.0, 4949.0],      [4950.0, 4951.0, 4952.0, 4953.0, 4954.0, 4955.0, 4956.0]],     [[4957.0, 4958.0, 4959.0, 4960.0, 4961.0, 4962.0, 4963.0],      [4964.0, 4965.0, 4966.0, 4967.0, 4968.0, 4969.0, 4970.0]]],    [[[4971.0, 4972.0, 4973.0, 4974.0, 4975.0, 4976.0, 4977.0],      [4978.0, 4979.0, 4980.0, 4981.0, 4982.0, 4983.0, 4984.0]],     [[4985.0, 4986.0, 4987.0, 4988.0, 4989.0, 4990.0, 4991.0],      [4992.0, 4993.0, 4994.0, 4995.0, 4996.0, 4997.0, 4998.0]],     [[4999.0, 5000.0, 5001.0, 5002.0, 5003.0, 5004.0, 5005.0],      [5006.0, 5007.0, 5008.0, 5009.0, 5010.0, 5011.0, 5012.0]],     [[5013.0, 5014.0, 5015.0, 5016.0, 5017.0, 5018.0, 5019.0],      [5020.0, 5021.0, 5022.0, 5023.0, 5024.0, 5025.0, 5026.0]],     [[5027.0, 5028.0, 5029.0, 5030.0, 5031.0, 5032.0, 5033.0],      [5034.0, 5035.0, 5036.0, 5037.0, 5038.0, 5039.0, 5040.0]]]],   [[[[5041.0, 5042.0, 5043.0, 5044.0, 5045.0, 5046.0, 5047.0],      [5048.0, 5049.0, 5050.0, 5051.0, 5052.0, 5053.0, 5054.0]],     [[5055.0, 5056.0, 5057.0, 5058.0, 5059.0, 5060.0, 5061.0],      [5062.0, 5063.0, 5064.0, 5065.0, 5066.0, 5067.0, 5068.0]],     [[5069.0, 5070.0, 5071.0, 5072.0, 5073.0, 5074.0, 5075.0],      [5076.0, 5077.0, 5078.0, 5079.0, 5080.0, 5081.0, 5082.0]],     [[5083.0, 5084.0, 5085.0, 5086.0, 5087.0, 5088.0, 5089.0],      [5090.0, 5091.0, 5092.0, 5093.0, 5094.0, 5095.0, 5096.0]],     [[5097.0, 5098.0, 5099.0, 5100.0, 5101.0, 5102.0, 5103.0],      [5104.0, 5105.0, 5106.0, 5107.0, 5108.0, 5109.0, 5110.0]]],    [[[5111.0, 5112.0, 5113.0, 5114.0, 5115.0, 5116.0, 5117.0],      [5118.0, 5119.0, 5120.0, 5121.0, 5122.0, 5123.0, 5124.0]],     [[5125.0, 5126.0, 5127.0, 5128.0, 5129.0, 5130.0, 5131.0],      [5132.0, 5133.0, 5134.0, 5135.0, 5136.0, 5137.0, 5138.0]],     [[5139.0, 5140.0, 5141.0, 5142.0, 5143.0, 5144.0, 5145.0],      [5146.0, 5147.0, 5148.0, 5149.0, 5150.0, 5151.0, 5152.0]],     [[5153.0, 5154.0, 5155.0, 5156.0, 5157.0, 5158.0, 5159.0],      [5160.0, 5161.0, 5162.0, 5163.0, 5164.0, 5165.0, 5166.0]],     [[5167.0, 5168.0, 5169.0, 5170.0, 5171.0, 5172.0, 5173.0],      [5174.0, 5175.0, 5176.0, 5177.0, 5178.0, 5179.0, 5180.0]]],    [[[5181.0, 5182.0, 5183.0, 5184.0, 5185.0, 5186.0, 5187.0],      [5188.0, 5189.0, 5190.0, 5191.0, 5192.0, 5193.0, 5194.0]],     [[5195.0, 5196.0, 5197.0, 5198.0, 5199.0, 5200.0, 5201.0],      [5202.0, 5203.0, 5204.0, 5205.0, 5206.0, 5207.0, 5208.0]],     [[5209.0, 5210.0, 5211.0, 5212.0, 5213.0, 5214.0, 5215.0],      [5216.0, 5217.0, 5218.0, 5219.0, 5220.0, 5221.0, 5222.0]],     [[5223.0, 5224.0, 5225.0, 5226.0, 5227.0, 5228.0, 5229.0],      [5230.0, 5231.0, 5232.0, 5233.0, 5234.0, 5235.0, 5236.0]],     [[5237.0, 5238.0, 5239.0, 5240.0, 5241.0, 5242.0, 5243.0],      [5244.0, 5245.0, 5246.0, 5247.0, 5248.0, 5249.0, 5250.0]]],    [[[5251.0, 5252.0, 5253.0, 5254.0, 5255.0, 5256.0, 5257.0],      [5258.0, 5259.0, 5260.0, 5261.0, 5262.0, 5263.0, 5264.0]],     [[5265.0, 5266.0, 5267.0, 5268.0, 5269.0, 5270.0, 5271.0],      [5272.0, 5273.0, 5274.0, 5275.0, 5276.0, 5277.0, 5278.0]],     [[5279.0, 5280.0, 5281.0, 5282.0, 5283.0, 5284.0, 5285.0],      [5286.0, 5287.0, 5288.0, 5289.0, 5290.0, 5291.0, 5292.0]],     [[5293.0, 5294.0, 5295.0, 5296.0, 5297.0, 5298.0, 5299.0],      [5300.0, 5301.0, 5302.0, 5303.0, 5304.0, 5305.0, 5306.0]],     [[5307.0, 5308.0, 5309.0, 5310.0, 5311.0, 5312.0, 5313.0],      [5314.0, 5315.0, 5316.0, 5317.0, 5318.0, 5319.0, 5320.0]]],    [[[5321.0, 5322.0, 5323.0, 5324.0, 5325.0, 5326.0, 5327.0],      [5328.0, 5329.0, 5330.0, 5331.0, 5332.0, 5333.0, 5334.0]],     [[5335.0, 5336.0, 5337.0, 5338.0, 5339.0, 5340.0, 5341.0],      [5342.0, 5343.0, 5344.0, 5345.0, 5346.0, 5347.0, 5348.0]],     [[5349.0, 5350.0, 5351.0, 5352.0, 5353.0, 5354.0, 5355.0],      [5356.0, 5357.0, 5358.0, 5359.0, 5360.0, 5361.0, 5362.0]],     [[5363.0, 5364.0, 5365.0, 5366.0, 5367.0, 5368.0, 5369.0],      [5370.0, 5371.0, 5372.0, 5373.0, 5374.0, 5375.0, 5376.0]],     [[5377.0, 5378.0, 5379.0, 5380.0, 5381.0, 5382.0, 5383.0],      [5384.0, 5385.0, 5386.0, 5387.0, 5388.0, 5389.0, 5390.0]]],    [[[5391.0, 5392.0, 5393.0, 5394.0, 5395.0, 5396.0, 5397.0],      [5398.0, 5399.0, 5400.0, 5401.0, 5402.0, 5403.0, 5404.0]],     [[5405.0, 5406.0, 5407.0, 5408.0, 5409.0, 5410.0, 5411.0],      [5412.0, 5413.0, 5414.0, 5415.0, 5416.0, 5417.0, 5418.0]],     [[5419.0, 5420.0, 5421.0, 5422.0, 5423.0, 5424.0, 5425.0],      [5426.0, 5427.0, 5428.0, 5429.0, 5430.0, 5431.0, 5432.0]],     [[5433.0, 5434.0, 5435.0, 5436.0, 5437.0, 5438.0, 5439.0],      [5440.0, 5441.0, 5442.0, 5443.0, 5444.0, 5445.0, 5446.0]],     [[5447.0, 5448.0, 5449.0, 5450.0, 5451.0, 5452.0, 5453.0],      [5454.0, 5455.0, 5456.0, 5457.0, 5458.0, 5459.0, 5460.0]]]],   [[[[5461.0, 5462.0, 5463.0, 5464.0, 5465.0, 5466.0, 5467.0],      [5468.0, 5469.0, 5470.0, 5471.0, 5472.0, 5473.0, 5474.0]],     [[5475.0, 5476.0, 5477.0, 5478.0, 5479.0, 5480.0, 5481.0],      [5482.0, 5483.0, 5484.0, 5485.0, 5486.0, 5487.0, 5488.0]],     [[5489.0, 5490.0, 5491.0, 5492.0, 5493.0, 5494.0, 5495.0],      [5496.0, 5497.0, 5498.0, 5499.0, 5500.0, 5501.0, 5502.0]],     [[5503.0, 5504.0, 5505.0, 5506.0, 5507.0, 5508.0, 5509.0],      [5510.0, 5511.0, 5512.0, 5513.0, 5514.0, 5515.0, 5516.0]],     [[5517.0, 5518.0, 5519.0, 5520.0, 5521.0, 5522.0, 5523.0],      [5524.0, 5525.0, 5526.0, 5527.0, 5528.0, 5529.0, 5530.0]]],    [[[5531.0, 5532.0, 5533.0, 5534.0, 5535.0, 5536.0, 5537.0],      [5538.0, 5539.0, 5540.0, 5541.0, 5542.0, 5543.0, 5544.0]],     [[5545.0, 5546.0, 5547.0, 5548.0, 5549.0, 5550.0, 5551.0],      [5552.0, 5553.0, 5554.0, 5555.0, 5556.0, 5557.0, 5558.0]],     [[5559.0, 5560.0, 5561.0, 5562.0, 5563.0, 5564.0, 5565.0],      [5566.0, 5567.0, 5568.0, 5569.0, 5570.0, 5571.0, 5572.0]],     [[5573.0, 5574.0, 5575.0, 5576.0, 5577.0, 5578.0, 5579.0],      [5580.0, 5581.0, 5582.0, 5583.0, 5584.0, 5585.0, 5586.0]],     [[5587.0, 5588.0, 5589.0, 5590.0, 5591.0, 5592.0, 5593.0],      [5594.0, 5595.0, 5596.0, 5597.0, 5598.0, 5599.0, 5600.0]]],    [[[5601.0, 5602.0, 5603.0, 5604.0, 5605.0, 5606.0, 5607.0],      [5608.0, 5609.0, 5610.0, 5611.0, 5612.0, 5613.0, 5614.0]],     [[5615.0, 5616.0, 5617.0, 5618.0, 5619.0, 5620.0, 5621.0],      [5622.0, 5623.0, 5624.0, 5625.0, 5626.0, 5627.0, 5628.0]],     [[5629.0, 5630.0, 5631.0, 5632.0, 5633.0, 5634.0, 5635.0],      [5636.0, 5637.0, 5638.0, 5639.0, 5640.0, 5641.0, 5642.0]],     [[5643.0, 5644.0, 5645.0, 5646.0, 5647.0, 5648.0, 5649.0],      [5650.0, 5651.0, 5652.0, 5653.0, 5654.0, 5655.0, 5656.0]],     [[5657.0, 5658.0, 5659.0, 5660.0, 5661.0, 5662.0, 5663.0],      [5664.0, 5665.0, 5666.0, 5667.0, 5668.0, 5669.0, 5670.0]]],    [[[5671.0, 5672.0, 5673.0, 5674.0, 5675.0, 5676.0, 5677.0],      [5678.0, 5679.0, 5680.0, 5681.0, 5682.0, 5683.0, 5684.0]],     [[5685.0, 5686.0, 5687.0, 5688.0, 5689.0, 5690.0, 5691.0],      [5692.0, 5693.0, 5694.0, 5695.0, 5696.0, 5697.0, 5698.0]],     [[5699.0, 5700.0, 5701.0, 5702.0, 5703.0, 5704.0, 5705.0],      [5706.0, 5707.0, 5708.0, 5709.0, 5710.0, 5711.0, 5712.0]],     [[5713.0, 5714.0, 5715.0, 5716.0, 5717.0, 5718.0, 5719.0],      [5720.0, 5721.0, 5722.0, 5723.0, 5724.0, 5725.0, 5726.0]],     [[5727.0, 5728.0, 5729.0, 5730.0, 5731.0, 5732.0, 5733.0],      [5734.0, 5735.0, 5736.0, 5737.0, 5738.0, 5739.0, 5740.0]]],    [[[5741.0, 5742.0, 5743.0, 5744.0, 5745.0, 5746.0, 5747.0],      [5748.0, 5749.0, 5750.0, 5751.0, 5752.0, 5753.0, 5754.0]],     [[5755.0, 5756.0, 5757.0, 5758.0, 5759.0, 5760.0, 5761.0],      [5762.0, 5763.0, 5764.0, 5765.0, 5766.0, 5767.0, 5768.0]],     [[5769.0, 5770.0, 5771.0, 5772.0, 5773.0, 5774.0, 5775.0],      [5776.0, 5777.0, 5778.0, 5779.0, 5780.0, 5781.0, 5782.0]],     [[5783.0, 5784.0, 5785.0, 5786.0, 5787.0, 5788.0, 5789.0],      [5790.0, 5791.0, 5792.0, 5793.0, 5794.0, 5795.0, 5796.0]],     [[5797.0, 5798.0, 5799.0, 5800.0, 5801.0, 5802.0, 5803.0],      [5804.0, 5805.0, 5806.0, 5807.0, 5808.0, 5809.0, 5810.0]]],    [[[5811.0, 5812.0, 5813.0, 5814.0, 5815.0, 5816.0, 5817.0],      [5818.0, 5819.0, 5820.0, 5821.0, 5822.0, 5823.0, 5824.0]],     [[5825.0, 5826.0, 5827.0, 5828.0, 5829.0, 5830.0, 5831.0],      [5832.0, 5833.0, 5834.0, 5835.0, 5836.0, 5837.0, 5838.0]],     [[5839.0, 5840.0, 5841.0, 5842.0, 5843.0, 5844.0, 5845.0],      [5846.0, 5847.0, 5848.0, 5849.0, 5850.0, 5851.0, 5852.0]],     [[5853.0, 5854.0, 5855.0, 5856.0, 5857.0, 5858.0, 5859.0],      [5860.0, 5861.0, 5862.0, 5863.0, 5864.0, 5865.0, 5866.0]],     [[5867.0, 5868.0, 5869.0, 5870.0, 5871.0, 5872.0, 5873.0],      [5874.0, 5875.0, 5876.0, 5877.0, 5878.0, 5879.0, 5880.0]]]]]] shape=[2, 7, 6, 5, 2, 7], strides=[2940, 420, 70, 14, 7, 1], layout=C (0x1)), I32([1, 1] shape=[2], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [0, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 2514446536 3654250445 3180802555 3889266564 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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, 16.0, 17.0, 18.0],     [19.0, 20.0, 21.0, 22.0, 23.0, 24.0]],    [[25.0, 26.0, 27.0, 28.0, 29.0, 30.0],     [31.0, 32.0, 33.0, 34.0, 35.0, 36.0]],    [[37.0, 38.0, 39.0, 40.0, 41.0, 42.0],     [43.0, 44.0, 45.0, 46.0, 47.0, 48.0]],    [[49.0, 50.0, 51.0, 52.0, 53.0, 54.0],     [55.0, 56.0, 57.0, 58.0, 59.0, 60.0]]],   [[[61.0, 62.0, 63.0, 64.0, 65.0, 66.0],     [67.0, 68.0, 69.0, 70.0, 71.0, 72.0]],    [[73.0, 74.0, 75.0, 76.0, 77.0, 78.0],     [79.0, 80.0, 81.0, 82.0, 83.0, 84.0]],    [[85.0, 86.0, 87.0, 88.0, 89.0, 90.0],     [91.0, 92.0, 93.0, 94.0, 95.0, 96.0]],    [[97.0, 98.0, 99.0, 100.0, 101.0, 102.0],     [103.0, 104.0, 105.0, 106.0, 107.0, 108.0]],    [[109.0, 110.0, 111.0, 112.0, 113.0, 114.0],     [115.0, 116.0, 117.0, 118.0, 119.0, 120.0]]]],  [[[[121.0, 122.0, 123.0, 124.0, 125.0, 126.0],     [127.0, 128.0, 129.0, 130.0, 131.0, 132.0]],    [[133.0, 134.0, 135.0, 136.0, 137.0, 138.0],     [139.0, 140.0, 141.0, 142.0, 143.0, 144.0]],    [[145.0, 146.0, 147.0, 148.0, 149.0, 150.0],     [151.0, 152.0, 153.0, 154.0, 155.0, 156.0]],    [[157.0, 158.0, 159.0, 160.0, 161.0, 162.0],     [163.0, 164.0, 165.0, 166.0, 167.0, 168.0]],    [[169.0, 170.0, 171.0, 172.0, 173.0, 174.0],     [175.0, 176.0, 177.0, 178.0, 179.0, 180.0]]],   [[[181.0, 182.0, 183.0, 184.0, 185.0, 186.0],     [187.0, 188.0, 189.0, 190.0, 191.0, 192.0]],    [[193.0, 194.0, 195.0, 196.0, 197.0, 198.0],     [199.0, 200.0, 201.0, 202.0, 203.0, 204.0]],    [[205.0, 206.0, 207.0, 208.0, 209.0, 210.0],     [211.0, 212.0, 213.0, 214.0, 215.0, 216.0]],    [[217.0, 218.0, 219.0, 220.0, 221.0, 222.0],     [223.0, 224.0, 225.0, 226.0, 227.0, 228.0]],    [[229.0, 230.0, 231.0, 232.0, 233.0, 234.0],     [235.0, 236.0, 237.0, 238.0, 239.0, 240.0]]]]] shape=[2, 2, 5, 2, 6], strides=[120, 60, 12, 6, 1], layout=C (0x1)), I32([2] shape=[1], strides=[1], layout=C | F (0x3)), I32([[1, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 1399071358 3728216013 2021915633 924837630 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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],     [16.0]]]],  [[[[17.0],     [18.0],     [19.0],     [20.0]],    [[21.0],     [22.0],     [23.0],     [24.0]],    [[25.0],     [26.0],     [27.0],     [28.0]],    [[29.0],     [30.0],     [31.0],     [32.0]]]],  [[[[33.0],     [34.0],     [35.0],     [36.0]],    [[37.0],     [38.0],     [39.0],     [40.0]],    [[41.0],     [42.0],     [43.0],     [44.0]],    [[45.0],     [46.0],     [47.0],     [48.0]]]]] shape=[3, 1, 4, 4, 1], strides=[16, 16, 4, 1, 1], layout=C (0x1)), I32([1, 3, 2] shape=[3], strides=[1], layout=C | F (0x3)), I32([[1, 1],  [2, 3],  [2, 2]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 1239969046 1165753001 2616270403 2760672970 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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, 16.0]]],     [[[17.0, 18.0, 19.0, 20.0],       [21.0, 22.0, 23.0, 24.0]],      [[25.0, 26.0, 27.0, 28.0],       [29.0, 30.0, 31.0, 32.0]]],     [[[33.0, 34.0, 35.0, 36.0],       [37.0, 38.0, 39.0, 40.0]],      [[41.0, 42.0, 43.0, 44.0],       [45.0, 46.0, 47.0, 48.0]]]],    [[[[49.0, 50.0, 51.0, 52.0],       [53.0, 54.0, 55.0, 56.0]],      [[57.0, 58.0, 59.0, 60.0],       [61.0, 62.0, 63.0, 64.0]]],     [[[65.0, 66.0, 67.0, 68.0],       [69.0, 70.0, 71.0, 72.0]],      [[73.0, 74.0, 75.0, 76.0],       [77.0, 78.0, 79.0, 80.0]]],     [[[81.0, 82.0, 83.0, 84.0],       [85.0, 86.0, 87.0, 88.0]],      [[89.0, 90.0, 91.0, 92.0],       [93.0, 94.0, 95.0, 96.0]]]],    [[[[97.0, 98.0, 99.0, 100.0],       [101.0, 102.0, 103.0, 104.0]],      [[105.0, 106.0, 107.0, 108.0],       [109.0, 110.0, 111.0, 112.0]]],     [[[113.0, 114.0, 115.0, 116.0],       [117.0, 118.0, 119.0, 120.0]],      [[121.0, 122.0, 123.0, 124.0],       [125.0, 126.0, 127.0, 128.0]]],     [[[129.0, 130.0, 131.0, 132.0],       [133.0, 134.0, 135.0, 136.0]],      [[137.0, 138.0, 139.0, 140.0],       [141.0, 142.0, 143.0, 144.0]]]],    [[[[145.0, 146.0, 147.0, 148.0],       [149.0, 150.0, 151.0, 152.0]],      [[153.0, 154.0, 155.0, 156.0],       [157.0, 158.0, 159.0, 160.0]]],     [[[161.0, 162.0, 163.0, 164.0],       [165.0, 166.0, 167.0, 168.0]],      [[169.0, 170.0, 171.0, 172.0],       [173.0, 174.0, 175.0, 176.0]]],     [[[177.0, 178.0, 179.0, 180.0],       [181.0, 182.0, 183.0, 184.0]],      [[185.0, 186.0, 187.0, 188.0],       [189.0, 190.0, 191.0, 192.0]]]],    [[[[193.0, 194.0, 195.0, 196.0],       [197.0, 198.0, 199.0, 200.0]],      [[201.0, 202.0, 203.0, 204.0],       [205.0, 206.0, 207.0, 208.0]]],     [[[209.0, 210.0, 211.0, 212.0],       [213.0, 214.0, 215.0, 216.0]],      [[217.0, 218.0, 219.0, 220.0],       [221.0, 222.0, 223.0, 224.0]]],     [[[225.0, 226.0, 227.0, 228.0],       [229.0, 230.0, 231.0, 232.0]],      [[233.0, 234.0, 235.0, 236.0],       [237.0, 238.0, 239.0, 240.0]]]]],   [[[[[241.0, 242.0, 243.0, 244.0],       [245.0, 246.0, 247.0, 248.0]],      [[249.0, 250.0, 251.0, 252.0],       [253.0, 254.0, 255.0, 256.0]]],     [[[257.0, 258.0, 259.0, 260.0],       [261.0, 262.0, 263.0, 264.0]],      [[265.0, 266.0, 267.0, 268.0],       [269.0, 270.0, 271.0, 272.0]]],     [[[273.0, 274.0, 275.0, 276.0],       [277.0, 278.0, 279.0, 280.0]],      [[281.0, 282.0, 283.0, 284.0],       [285.0, 286.0, 287.0, 288.0]]]],    [[[[289.0, 290.0, 291.0, 292.0],       [293.0, 294.0, 295.0, 296.0]],      [[297.0, 298.0, 299.0, 300.0],       [301.0, 302.0, 303.0, 304.0]]],     [[[305.0, 306.0, 307.0, 308.0],       [309.0, 310.0, 311.0, 312.0]],      [[313.0, 314.0, 315.0, 316.0],       [317.0, 318.0, 319.0, 320.0]]],     [[[321.0, 322.0, 323.0, 324.0],       [325.0, 326.0, 327.0, 328.0]],      [[329.0, 330.0, 331.0, 332.0],       [333.0, 334.0, 335.0, 336.0]]]],    [[[[337.0, 338.0, 339.0, 340.0],       [341.0, 342.0, 343.0, 344.0]],      [[345.0, 346.0, 347.0, 348.0],       [349.0, 350.0, 351.0, 352.0]]],     [[[353.0, 354.0, 355.0, 356.0],       [357.0, 358.0, 359.0, 360.0]],      [[361.0, 362.0, 363.0, 364.0],       [365.0, 366.0, 367.0, 368.0]]],     [[[369.0, 370.0, 371.0, 372.0],       [373.0, 374.0, 375.0, 376.0]],      [[377.0, 378.0, 379.0, 380.0],       [381.0, 382.0, 383.0, 384.0]]]],    [[[[385.0, 386.0, 387.0, 388.0],       [389.0, 390.0, 391.0, 392.0]],      [[393.0, 394.0, 395.0, 396.0],       [397.0, 398.0, 399.0, 400.0]]],     [[[401.0, 402.0, 403.0, 404.0],       [405.0, 406.0, 407.0, 408.0]],      [[409.0, 410.0, 411.0, 412.0],       [413.0, 414.0, 415.0, 416.0]]],     [[[417.0, 418.0, 419.0, 420.0],       [421.0, 422.0, 423.0, 424.0]],      [[425.0, 426.0, 427.0, 428.0],       [429.0, 430.0, 431.0, 432.0]]]],    [[[[433.0, 434.0, 435.0, 436.0],       [437.0, 438.0, 439.0, 440.0]],      [[441.0, 442.0, 443.0, 444.0],       [445.0, 446.0, 447.0, 448.0]]],     [[[449.0, 450.0, 451.0, 452.0],       [453.0, 454.0, 455.0, 456.0]],      [[457.0, 458.0, 459.0, 460.0],       [461.0, 462.0, 463.0, 464.0]]],     [[[465.0, 466.0, 467.0, 468.0],       [469.0, 470.0, 471.0, 472.0]],      [[473.0, 474.0, 475.0, 476.0],       [477.0, 478.0, 479.0, 480.0]]]]],   [[[[[481.0, 482.0, 483.0, 484.0],       [485.0, 486.0, 487.0, 488.0]],      [[489.0, 490.0, 491.0, 492.0],       [493.0, 494.0, 495.0, 496.0]]],     [[[497.0, 498.0, 499.0, 500.0],       [501.0, 502.0, 503.0, 504.0]],      [[505.0, 506.0, 507.0, 508.0],       [509.0, 510.0, 511.0, 512.0]]],     [[[513.0, 514.0, 515.0, 516.0],       [517.0, 518.0, 519.0, 520.0]],      [[521.0, 522.0, 523.0, 524.0],       [525.0, 526.0, 527.0, 528.0]]]],    [[[[529.0, 530.0, 531.0, 532.0],       [533.0, 534.0, 535.0, 536.0]],      [[537.0, 538.0, 539.0, 540.0],       [541.0, 542.0, 543.0, 544.0]]],     [[[545.0, 546.0, 547.0, 548.0],       [549.0, 550.0, 551.0, 552.0]],      [[553.0, 554.0, 555.0, 556.0],       [557.0, 558.0, 559.0, 560.0]]],     [[[561.0, 562.0, 563.0, 564.0],       [565.0, 566.0, 567.0, 568.0]],      [[569.0, 570.0, 571.0, 572.0],       [573.0, 574.0, 575.0, 576.0]]]],    [[[[577.0, 578.0, 579.0, 580.0],       [581.0, 582.0, 583.0, 584.0]],      [[585.0, 586.0, 587.0, 588.0],       [589.0, 590.0, 591.0, 592.0]]],     [[[593.0, 594.0, 595.0, 596.0],       [597.0, 598.0, 599.0, 600.0]],      [[601.0, 602.0, 603.0, 604.0],       [605.0, 606.0, 607.0, 608.0]]],     [[[609.0, 610.0, 611.0, 612.0],       [613.0, 614.0, 615.0, 616.0]],      [[617.0, 618.0, 619.0, 620.0],       [621.0, 622.0, 623.0, 624.0]]]],    [[[[625.0, 626.0, 627.0, 628.0],       [629.0, 630.0, 631.0, 632.0]],      [[633.0, 634.0, 635.0, 636.0],       [637.0, 638.0, 639.0, 640.0]]],     [[[641.0, 642.0, 643.0, 644.0],       [645.0, 646.0, 647.0, 648.0]],      [[649.0, 650.0, 651.0, 652.0],       [653.0, 654.0, 655.0, 656.0]]],     [[[657.0, 658.0, 659.0, 660.0],       [661.0, 662.0, 663.0, 664.0]],      [[665.0, 666.0, 667.0, 668.0],       [669.0, 670.0, 671.0, 672.0]]]],    [[[[673.0, 674.0, 675.0, 676.0],       [677.0, 678.0, 679.0, 680.0]],      [[681.0, 682.0, 683.0, 684.0],       [685.0, 686.0, 687.0, 688.0]]],     [[[689.0, 690.0, 691.0, 692.0],       [693.0, 694.0, 695.0, 696.0]],      [[697.0, 698.0, 699.0, 700.0],       [701.0, 702.0, 703.0, 704.0]]],     [[[705.0, 706.0, 707.0, 708.0],       [709.0, 710.0, 711.0, 712.0]],      [[713.0, 714.0, 715.0, 716.0],       [717.0, 718.0, 719.0, 720.0]]]]],   [[[[[721.0, 722.0, 723.0, 724.0],       [725.0, 726.0, 727.0, 728.0]],      [[729.0, 730.0, 731.0, 732.0],       [733.0, 734.0, 735.0, 736.0]]],     [[[737.0, 738.0, 739.0, 740.0],       [741.0, 742.0, 743.0, 744.0]],      [[745.0, 746.0, 747.0, 748.0],       [749.0, 750.0, 751.0, 752.0]]],     [[[753.0, 754.0, 755.0, 756.0],       [757.0, 758.0, 759.0, 760.0]],      [[761.0, 762.0, 763.0, 764.0],       [765.0, 766.0, 767.0, 768.0]]]],    [[[[769.0, 770.0, 771.0, 772.0],       [773.0, 774.0, 775.0, 776.0]],      [[777.0, 778.0, 779.0, 780.0],       [781.0, 782.0, 783.0, 784.0]]],     [[[785.0, 786.0, 787.0, 788.0],       [789.0, 790.0, 791.0, 792.0]],      [[793.0, 794.0, 795.0, 796.0],       [797.0, 798.0, 799.0, 800.0]]],     [[[801.0, 802.0, 803.0, 804.0],       [805.0, 806.0, 807.0, 808.0]],      [[809.0, 810.0, 811.0, 812.0],       [813.0, 814.0, 815.0, 816.0]]]],    [[[[817.0, 818.0, 819.0, 820.0],       [821.0, 822.0, 823.0, 824.0]],      [[825.0, 826.0, 827.0, 828.0],       [829.0, 830.0, 831.0, 832.0]]],     [[[833.0, 834.0, 835.0, 836.0],       [837.0, 838.0, 839.0, 840.0]],      [[841.0, 842.0, 843.0, 844.0],       [845.0, 846.0, 847.0, 848.0]]],     [[[849.0, 850.0, 851.0, 852.0],       [853.0, 854.0, 855.0, 856.0]],      [[857.0, 858.0, 859.0, 860.0],       [861.0, 862.0, 863.0, 864.0]]]],    [[[[865.0, 866.0, 867.0, 868.0],       [869.0, 870.0, 871.0, 872.0]],      [[873.0, 874.0, 875.0, 876.0],       [877.0, 878.0, 879.0, 880.0]]],     [[[881.0, 882.0, 883.0, 884.0],       [885.0, 886.0, 887.0, 888.0]],      [[889.0, 890.0, 891.0, 892.0],       [893.0, 894.0, 895.0, 896.0]]],     [[[897.0, 898.0, 899.0, 900.0],       [901.0, 902.0, 903.0, 904.0]],      [[905.0, 906.0, 907.0, 908.0],       [909.0, 910.0, 911.0, 912.0]]]],    [[[[913.0, 914.0, 915.0, 916.0],       [917.0, 918.0, 919.0, 920.0]],      [[921.0, 922.0, 923.0, 924.0],       [925.0, 926.0, 927.0, 928.0]]],     [[[929.0, 930.0, 931.0, 932.0],       [933.0, 934.0, 935.0, 936.0]],      [[937.0, 938.0, 939.0, 940.0],       [941.0, 942.0, 943.0, 944.0]]],     [[[945.0, 946.0, 947.0, 948.0],       [949.0, 950.0, 951.0, 952.0]],      [[953.0, 954.0, 955.0, 956.0],       [957.0, 958.0, 959.0, 960.0]]]]],   [[[[[961.0, 962.0, 963.0, 964.0],       [965.0, 966.0, 967.0, 968.0]],      [[969.0, 970.0, 971.0, 972.0],       [973.0, 974.0, 975.0, 976.0]]],     [[[977.0, 978.0, 979.0, 980.0],       [981.0, 982.0, 983.0, 984.0]],      [[985.0, 986.0, 987.0, 988.0],       [989.0, 990.0, 991.0, 992.0]]],     [[[993.0, 994.0, 995.0, 996.0],       [997.0, 998.0, 999.0, 1000.0]],      [[1001.0, 1002.0, 1003.0, 1004.0],       [1005.0, 1006.0, 1007.0, 1008.0]]]],    [[[[1009.0, 1010.0, 1011.0, 1012.0],       [1013.0, 1014.0, 1015.0, 1016.0]],      [[1017.0, 1018.0, 1019.0, 1020.0],       [1021.0, 1022.0, 1023.0, 1024.0]]],     [[[1025.0, 1026.0, 1027.0, 1028.0],       [1029.0, 1030.0, 1031.0, 1032.0]],      [[1033.0, 1034.0, 1035.0, 1036.0],       [1037.0, 1038.0, 1039.0, 1040.0]]],     [[[1041.0, 1042.0, 1043.0, 1044.0],       [1045.0, 1046.0, 1047.0, 1048.0]],      [[1049.0, 1050.0, 1051.0, 1052.0],       [1053.0, 1054.0, 1055.0, 1056.0]]]],    [[[[1057.0, 1058.0, 1059.0, 1060.0],       [1061.0, 1062.0, 1063.0, 1064.0]],      [[1065.0, 1066.0, 1067.0, 1068.0],       [1069.0, 1070.0, 1071.0, 1072.0]]],     [[[1073.0, 1074.0, 1075.0, 1076.0],       [1077.0, 1078.0, 1079.0, 1080.0]],      [[1081.0, 1082.0, 1083.0, 1084.0],       [1085.0, 1086.0, 1087.0, 1088.0]]],     [[[1089.0, 1090.0, 1091.0, 1092.0],       [1093.0, 1094.0, 1095.0, 1096.0]],      [[1097.0, 1098.0, 1099.0, 1100.0],       [1101.0, 1102.0, 1103.0, 1104.0]]]],    [[[[1105.0, 1106.0, 1107.0, 1108.0],       [1109.0, 1110.0, 1111.0, 1112.0]],      [[1113.0, 1114.0, 1115.0, 1116.0],       [1117.0, 1118.0, 1119.0, 1120.0]]],     [[[1121.0, 1122.0, 1123.0, 1124.0],       [1125.0, 1126.0, 1127.0, 1128.0]],      [[1129.0, 1130.0, 1131.0, 1132.0],       [1133.0, 1134.0, 1135.0, 1136.0]]],     [[[1137.0, 1138.0, 1139.0, 1140.0],       [1141.0, 1142.0, 1143.0, 1144.0]],      [[1145.0, 1146.0, 1147.0, 1148.0],       [1149.0, 1150.0, 1151.0, 1152.0]]]],    [[[[1153.0, 1154.0, 1155.0, 1156.0],       [1157.0, 1158.0, 1159.0, 1160.0]],      [[1161.0, 1162.0, 1163.0, 1164.0],       [1165.0, 1166.0, 1167.0, 1168.0]]],     [[[1169.0, 1170.0, 1171.0, 1172.0],       [1173.0, 1174.0, 1175.0, 1176.0]],      [[1177.0, 1178.0, 1179.0, 1180.0],       [1181.0, 1182.0, 1183.0, 1184.0]]],     [[[1185.0, 1186.0, 1187.0, 1188.0],       [1189.0, 1190.0, 1191.0, 1192.0]],      [[1193.0, 1194.0, 1195.0, 1196.0],       [1197.0, 1198.0, 1199.0, 1200.0]]]]],   [[[[[1201.0, 1202.0, 1203.0, 1204.0],       [1205.0, 1206.0, 1207.0, 1208.0]],      [[1209.0, 1210.0, 1211.0, 1212.0],       [1213.0, 1214.0, 1215.0, 1216.0]]],     [[[1217.0, 1218.0, 1219.0, 1220.0],       [1221.0, 1222.0, 1223.0, 1224.0]],      [[1225.0, 1226.0, 1227.0, 1228.0],       [1229.0, 1230.0, 1231.0, 1232.0]]],     [[[1233.0, 1234.0, 1235.0, 1236.0],       [1237.0, 1238.0, 1239.0, 1240.0]],      [[1241.0, 1242.0, 1243.0, 1244.0],       [1245.0, 1246.0, 1247.0, 1248.0]]]],    [[[[1249.0, 1250.0, 1251.0, 1252.0],       [1253.0, 1254.0, 1255.0, 1256.0]],      [[1257.0, 1258.0, 1259.0, 1260.0],       [1261.0, 1262.0, 1263.0, 1264.0]]],     [[[1265.0, 1266.0, 1267.0, 1268.0],       [1269.0, 1270.0, 1271.0, 1272.0]],      [[1273.0, 1274.0, 1275.0, 1276.0],       [1277.0, 1278.0, 1279.0, 1280.0]]],     [[[1281.0, 1282.0, 1283.0, 1284.0],       [1285.0, 1286.0, 1287.0, 1288.0]],      [[1289.0, 1290.0, 1291.0, 1292.0],       [1293.0, 1294.0, 1295.0, 1296.0]]]],    [[[[1297.0, 1298.0, 1299.0, 1300.0],       [1301.0, 1302.0, 1303.0, 1304.0]],      [[1305.0, 1306.0, 1307.0, 1308.0],       [1309.0, 1310.0, 1311.0, 1312.0]]],     [[[1313.0, 1314.0, 1315.0, 1316.0],       [1317.0, 1318.0, 1319.0, 1320.0]],      [[1321.0, 1322.0, 1323.0, 1324.0],       [1325.0, 1326.0, 1327.0, 1328.0]]],     [[[1329.0, 1330.0, 1331.0, 1332.0],       [1333.0, 1334.0, 1335.0, 1336.0]],      [[1337.0, 1338.0, 1339.0, 1340.0],       [1341.0, 1342.0, 1343.0, 1344.0]]]],    [[[[1345.0, 1346.0, 1347.0, 1348.0],       [1349.0, 1350.0, 1351.0, 1352.0]],      [[1353.0, 1354.0, 1355.0, 1356.0],       [1357.0, 1358.0, 1359.0, 1360.0]]],     [[[1361.0, 1362.0, 1363.0, 1364.0],       [1365.0, 1366.0, 1367.0, 1368.0]],      [[1369.0, 1370.0, 1371.0, 1372.0],       [1373.0, 1374.0, 1375.0, 1376.0]]],     [[[1377.0, 1378.0, 1379.0, 1380.0],       [1381.0, 1382.0, 1383.0, 1384.0]],      [[1385.0, 1386.0, 1387.0, 1388.0],       [1389.0, 1390.0, 1391.0, 1392.0]]]],    [[[[1393.0, 1394.0, 1395.0, 1396.0],       [1397.0, 1398.0, 1399.0, 1400.0]],      [[1401.0, 1402.0, 1403.0, 1404.0],       [1405.0, 1406.0, 1407.0, 1408.0]]],     [[[1409.0, 1410.0, 1411.0, 1412.0],       [1413.0, 1414.0, 1415.0, 1416.0]],      [[1417.0, 1418.0, 1419.0, 1420.0],       [1421.0, 1422.0, 1423.0, 1424.0]]],     [[[1425.0, 1426.0, 1427.0, 1428.0],       [1429.0, 1430.0, 1431.0, 1432.0]],      [[1433.0, 1434.0, 1435.0, 1436.0],       [1437.0, 1438.0, 1439.0, 1440.0]]]]]]] shape=[1, 6, 5, 3, 2, 2, 4], strides=[1440, 240, 48, 16, 8, 4, 1], layout=C (0x1)), I32([3, 2, 1] shape=[3], strides=[1], layout=C | F (0x3)), I32([[1, 2],  [0, 1],  [2, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 2674844818 2514457199 2511386163 1149925133 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],      [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0],      [29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],      [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0]],     [[43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],      [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0],      [57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],      [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0],      [71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],      [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0]],     [[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],      [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],      [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0],      [106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0],      [113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],      [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0]]],    [[[127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],      [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0],      [141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],      [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],      [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],      [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0]],     [[169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0],      [176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],      [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],      [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0],      [197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],      [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0]],     [[211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],      [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0],      [225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],      [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],      [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0],      [246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0]]],    [[[253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],      [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],      [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],      [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0],      [281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],      [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0]],     [[295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],      [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0],      [309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0],      [316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],      [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],      [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0]],     [[337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],      [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0],      [351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],      [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0],      [365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],      [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0]]]],   [[[[379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0],      [386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0],      [393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],      [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],      [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],      [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]],     [[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],      [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],      [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],      [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0],      [449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0],      [456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0]],     [[463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],      [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0],      [477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],      [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0],      [491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],      [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0]]],    [[[505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],      [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],      [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0],      [526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0],      [533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],      [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0]],     [[547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],      [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0],      [561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],      [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],      [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],      [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0]],     [[589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0],      [596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],      [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],      [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0],      [617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],      [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0]]],    [[[631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],      [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0],      [645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],      [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0],      [659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0],      [666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0]],     [[673.0, 674.0, 675.0, 676.0, 677.0, 678.0, 679.0],      [680.0, 681.0, 682.0, 683.0, 684.0, 685.0, 686.0],      [687.0, 688.0, 689.0, 690.0, 691.0, 692.0, 693.0],      [694.0, 695.0, 696.0, 697.0, 698.0, 699.0, 700.0],      [701.0, 702.0, 703.0, 704.0, 705.0, 706.0, 707.0],      [708.0, 709.0, 710.0, 711.0, 712.0, 713.0, 714.0]],     [[715.0, 716.0, 717.0, 718.0, 719.0, 720.0, 721.0],      [722.0, 723.0, 724.0, 725.0, 726.0, 727.0, 728.0],      [729.0, 730.0, 731.0, 732.0, 733.0, 734.0, 735.0],      [736.0, 737.0, 738.0, 739.0, 740.0, 741.0, 742.0],      [743.0, 744.0, 745.0, 746.0, 747.0, 748.0, 749.0],      [750.0, 751.0, 752.0, 753.0, 754.0, 755.0, 756.0]]]],   [[[[757.0, 758.0, 759.0, 760.0, 761.0, 762.0, 763.0],      [764.0, 765.0, 766.0, 767.0, 768.0, 769.0, 770.0],      [771.0, 772.0, 773.0, 774.0, 775.0, 776.0, 777.0],      [778.0, 779.0, 780.0, 781.0, 782.0, 783.0, 784.0],      [785.0, 786.0, 787.0, 788.0, 789.0, 790.0, 791.0],      [792.0, 793.0, 794.0, 795.0, 796.0, 797.0, 798.0]],     [[799.0, 800.0, 801.0, 802.0, 803.0, 804.0, 805.0],      [806.0, 807.0, 808.0, 809.0, 810.0, 811.0, 812.0],      [813.0, 814.0, 815.0, 816.0, 817.0, 818.0, 819.0],      [820.0, 821.0, 822.0, 823.0, 824.0, 825.0, 826.0],      [827.0, 828.0, 829.0, 830.0, 831.0, 832.0, 833.0],      [834.0, 835.0, 836.0, 837.0, 838.0, 839.0, 840.0]],     [[841.0, 842.0, 843.0, 844.0, 845.0, 846.0, 847.0],      [848.0, 849.0, 850.0, 851.0, 852.0, 853.0, 854.0],      [855.0, 856.0, 857.0, 858.0, 859.0, 860.0, 861.0],      [862.0, 863.0, 864.0, 865.0, 866.0, 867.0, 868.0],      [869.0, 870.0, 871.0, 872.0, 873.0, 874.0, 875.0],      [876.0, 877.0, 878.0, 879.0, 880.0, 881.0, 882.0]]],    [[[883.0, 884.0, 885.0, 886.0, 887.0, 888.0, 889.0],      [890.0, 891.0, 892.0, 893.0, 894.0, 895.0, 896.0],      [897.0, 898.0, 899.0, 900.0, 901.0, 902.0, 903.0],      [904.0, 905.0, 906.0, 907.0, 908.0, 909.0, 910.0],      [911.0, 912.0, 913.0, 914.0, 915.0, 916.0, 917.0],      [918.0, 919.0, 920.0, 921.0, 922.0, 923.0, 924.0]],     [[925.0, 926.0, 927.0, 928.0, 929.0, 930.0, 931.0],      [932.0, 933.0, 934.0, 935.0, 936.0, 937.0, 938.0],      [939.0, 940.0, 941.0, 942.0, 943.0, 944.0, 945.0],      [946.0, 947.0, 948.0, 949.0, 950.0, 951.0, 952.0],      [953.0, 954.0, 955.0, 956.0, 957.0, 958.0, 959.0],      [960.0, 961.0, 962.0, 963.0, 964.0, 965.0, 966.0]],     [[967.0, 968.0, 969.0, 970.0, 971.0, 972.0, 973.0],      [974.0, 975.0, 976.0, 977.0, 978.0, 979.0, 980.0],      [981.0, 982.0, 983.0, 984.0, 985.0, 986.0, 987.0],      [988.0, 989.0, 990.0, 991.0, 992.0, 993.0, 994.0],      [995.0, 996.0, 997.0, 998.0, 999.0, 1000.0, 1001.0],      [1002.0, 1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0]]],    [[[1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0, 1015.0],      [1016.0, 1017.0, 1018.0, 1019.0, 1020.0, 1021.0, 1022.0],      [1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0],      [1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0],      [1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0],      [1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]],     [[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0, 1057.0],      [1058.0, 1059.0, 1060.0, 1061.0, 1062.0, 1063.0, 1064.0],      [1065.0, 1066.0, 1067.0, 1068.0, 1069.0, 1070.0, 1071.0],      [1072.0, 1073.0, 1074.0, 1075.0, 1076.0, 1077.0, 1078.0],      [1079.0, 1080.0, 1081.0, 1082.0, 1083.0, 1084.0, 1085.0],      [1086.0, 1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0]],     [[1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0, 1099.0],      [1100.0, 1101.0, 1102.0, 1103.0, 1104.0, 1105.0, 1106.0],      [1107.0, 1108.0, 1109.0, 1110.0, 1111.0, 1112.0, 1113.0],      [1114.0, 1115.0, 1116.0, 1117.0, 1118.0, 1119.0, 1120.0],      [1121.0, 1122.0, 1123.0, 1124.0, 1125.0, 1126.0, 1127.0],      [1128.0, 1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0]]]],   [[[[1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0, 1141.0],      [1142.0, 1143.0, 1144.0, 1145.0, 1146.0, 1147.0, 1148.0],      [1149.0, 1150.0, 1151.0, 1152.0, 1153.0, 1154.0, 1155.0],      [1156.0, 1157.0, 1158.0, 1159.0, 1160.0, 1161.0, 1162.0],      [1163.0, 1164.0, 1165.0, 1166.0, 1167.0, 1168.0, 1169.0],      [1170.0, 1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0]],     [[1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0, 1183.0],      [1184.0, 1185.0, 1186.0, 1187.0, 1188.0, 1189.0, 1190.0],      [1191.0, 1192.0, 1193.0, 1194.0, 1195.0, 1196.0, 1197.0],      [1198.0, 1199.0, 1200.0, 1201.0, 1202.0, 1203.0, 1204.0],      [1205.0, 1206.0, 1207.0, 1208.0, 1209.0, 1210.0, 1211.0],      [1212.0, 1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0]],     [[1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0, 1225.0],      [1226.0, 1227.0, 1228.0, 1229.0, 1230.0, 1231.0, 1232.0],      [1233.0, 1234.0, 1235.0, 1236.0, 1237.0, 1238.0, 1239.0],      [1240.0, 1241.0, 1242.0, 1243.0, 1244.0, 1245.0, 1246.0],      [1247.0, 1248.0, 1249.0, 1250.0, 1251.0, 1252.0, 1253.0],      [1254.0, 1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]]],    [[[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0, 1267.0],      [1268.0, 1269.0, 1270.0, 1271.0, 1272.0, 1273.0, 1274.0],      [1275.0, 1276.0, 1277.0, 1278.0, 1279.0, 1280.0, 1281.0],      [1282.0, 1283.0, 1284.0, 1285.0, 1286.0, 1287.0, 1288.0],      [1289.0, 1290.0, 1291.0, 1292.0, 1293.0, 1294.0, 1295.0],      [1296.0, 1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0]],     [[1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0, 1309.0],      [1310.0, 1311.0, 1312.0, 1313.0, 1314.0, 1315.0, 1316.0],      [1317.0, 1318.0, 1319.0, 1320.0, 1321.0, 1322.0, 1323.0],      [1324.0, 1325.0, 1326.0, 1327.0, 1328.0, 1329.0, 1330.0],      [1331.0, 1332.0, 1333.0, 1334.0, 1335.0, 1336.0, 1337.0],      [1338.0, 1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0]],     [[1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0, 1351.0],      [1352.0, 1353.0, 1354.0, 1355.0, 1356.0, 1357.0, 1358.0],      [1359.0, 1360.0, 1361.0, 1362.0, 1363.0, 1364.0, 1365.0],      [1366.0, 1367.0, 1368.0, 1369.0, 1370.0, 1371.0, 1372.0],      [1373.0, 1374.0, 1375.0, 1376.0, 1377.0, 1378.0, 1379.0],      [1380.0, 1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0]]],    [[[1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0, 1393.0],      [1394.0, 1395.0, 1396.0, 1397.0, 1398.0, 1399.0, 1400.0],      [1401.0, 1402.0, 1403.0, 1404.0, 1405.0, 1406.0, 1407.0],      [1408.0, 1409.0, 1410.0, 1411.0, 1412.0, 1413.0, 1414.0],      [1415.0, 1416.0, 1417.0, 1418.0, 1419.0, 1420.0, 1421.0],      [1422.0, 1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0]],     [[1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0, 1435.0],      [1436.0, 1437.0, 1438.0, 1439.0, 1440.0, 1441.0, 1442.0],      [1443.0, 1444.0, 1445.0, 1446.0, 1447.0, 1448.0, 1449.0],      [1450.0, 1451.0, 1452.0, 1453.0, 1454.0, 1455.0, 1456.0],      [1457.0, 1458.0, 1459.0, 1460.0, 1461.0, 1462.0, 1463.0],      [1464.0, 1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0]],     [[1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0, 1477.0],      [1478.0, 1479.0, 1480.0, 1481.0, 1482.0, 1483.0, 1484.0],      [1485.0, 1486.0, 1487.0, 1488.0, 1489.0, 1490.0, 1491.0],      [1492.0, 1493.0, 1494.0, 1495.0, 1496.0, 1497.0, 1498.0],      [1499.0, 1500.0, 1501.0, 1502.0, 1503.0, 1504.0, 1505.0],      [1506.0, 1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0]]]],   [[[[1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0, 1519.0],      [1520.0, 1521.0, 1522.0, 1523.0, 1524.0, 1525.0, 1526.0],      [1527.0, 1528.0, 1529.0, 1530.0, 1531.0, 1532.0, 1533.0],      [1534.0, 1535.0, 1536.0, 1537.0, 1538.0, 1539.0, 1540.0],      [1541.0, 1542.0, 1543.0, 1544.0, 1545.0, 1546.0, 1547.0],      [1548.0, 1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0]],     [[1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0, 1561.0],      [1562.0, 1563.0, 1564.0, 1565.0, 1566.0, 1567.0, 1568.0],      [1569.0, 1570.0, 1571.0, 1572.0, 1573.0, 1574.0, 1575.0],      [1576.0, 1577.0, 1578.0, 1579.0, 1580.0, 1581.0, 1582.0],      [1583.0, 1584.0, 1585.0, 1586.0, 1587.0, 1588.0, 1589.0],      [1590.0, 1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0]],     [[1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0, 1603.0],      [1604.0, 1605.0, 1606.0, 1607.0, 1608.0, 1609.0, 1610.0],      [1611.0, 1612.0, 1613.0, 1614.0, 1615.0, 1616.0, 1617.0],      [1618.0, 1619.0, 1620.0, 1621.0, 1622.0, 1623.0, 1624.0],      [1625.0, 1626.0, 1627.0, 1628.0, 1629.0, 1630.0, 1631.0],      [1632.0, 1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0]]],    [[[1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0, 1645.0],      [1646.0, 1647.0, 1648.0, 1649.0, 1650.0, 1651.0, 1652.0],      [1653.0, 1654.0, 1655.0, 1656.0, 1657.0, 1658.0, 1659.0],      [1660.0, 1661.0, 1662.0, 1663.0, 1664.0, 1665.0, 1666.0],      [1667.0, 1668.0, 1669.0, 1670.0, 1671.0, 1672.0, 1673.0],      [1674.0, 1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]],     [[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0, 1687.0],      [1688.0, 1689.0, 1690.0, 1691.0, 1692.0, 1693.0, 1694.0],      [1695.0, 1696.0, 1697.0, 1698.0, 1699.0, 1700.0, 1701.0],      [1702.0, 1703.0, 1704.0, 1705.0, 1706.0, 1707.0, 1708.0],      [1709.0, 1710.0, 1711.0, 1712.0, 1713.0, 1714.0, 1715.0],      [1716.0, 1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0]],     [[1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0, 1729.0],      [1730.0, 1731.0, 1732.0, 1733.0, 1734.0, 1735.0, 1736.0],      [1737.0, 1738.0, 1739.0, 1740.0, 1741.0, 1742.0, 1743.0],      [1744.0, 1745.0, 1746.0, 1747.0, 1748.0, 1749.0, 1750.0],      [1751.0, 1752.0, 1753.0, 1754.0, 1755.0, 1756.0, 1757.0],      [1758.0, 1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0]]],    [[[1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0, 1771.0],      [1772.0, 1773.0, 1774.0, 1775.0, 1776.0, 1777.0, 1778.0],      [1779.0, 1780.0, 1781.0, 1782.0, 1783.0, 1784.0, 1785.0],      [1786.0, 1787.0, 1788.0, 1789.0, 1790.0, 1791.0, 1792.0],      [1793.0, 1794.0, 1795.0, 1796.0, 1797.0, 1798.0, 1799.0],      [1800.0, 1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0]],     [[1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0, 1813.0],      [1814.0, 1815.0, 1816.0, 1817.0, 1818.0, 1819.0, 1820.0],      [1821.0, 1822.0, 1823.0, 1824.0, 1825.0, 1826.0, 1827.0],      [1828.0, 1829.0, 1830.0, 1831.0, 1832.0, 1833.0, 1834.0],      [1835.0, 1836.0, 1837.0, 1838.0, 1839.0, 1840.0, 1841.0],      [1842.0, 1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0]],     [[1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0, 1855.0],      [1856.0, 1857.0, 1858.0, 1859.0, 1860.0, 1861.0, 1862.0],      [1863.0, 1864.0, 1865.0, 1866.0, 1867.0, 1868.0, 1869.0],      [1870.0, 1871.0, 1872.0, 1873.0, 1874.0, 1875.0, 1876.0],      [1877.0, 1878.0, 1879.0, 1880.0, 1881.0, 1882.0, 1883.0],      [1884.0, 1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0]]]]],  [[[[[1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0, 1897.0],      [1898.0, 1899.0, 1900.0, 1901.0, 1902.0, 1903.0, 1904.0],      [1905.0, 1906.0, 1907.0, 1908.0, 1909.0, 1910.0, 1911.0],      [1912.0, 1913.0, 1914.0, 1915.0, 1916.0, 1917.0, 1918.0],      [1919.0, 1920.0, 1921.0, 1922.0, 1923.0, 1924.0, 1925.0],      [1926.0, 1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0]],     [[1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0, 1939.0],      [1940.0, 1941.0, 1942.0, 1943.0, 1944.0, 1945.0, 1946.0],      [1947.0, 1948.0, 1949.0, 1950.0, 1951.0, 1952.0, 1953.0],      [1954.0, 1955.0, 1956.0, 1957.0, 1958.0, 1959.0, 1960.0],      [1961.0, 1962.0, 1963.0, 1964.0, 1965.0, 1966.0, 1967.0],      [1968.0, 1969.0, 1970.0, 1971.0, 1972.0, 1973.0, 1974.0]],     [[1975.0, 1976.0, 1977.0, 1978.0, 1979.0, 1980.0, 1981.0],      [1982.0, 1983.0, 1984.0, 1985.0, 1986.0, 1987.0, 1988.0],      [1989.0, 1990.0, 1991.0, 1992.0, 1993.0, 1994.0, 1995.0],      [1996.0, 1997.0, 1998.0, 1999.0, 2000.0, 2001.0, 2002.0],      [2003.0, 2004.0, 2005.0, 2006.0, 2007.0, 2008.0, 2009.0],      [2010.0, 2011.0, 2012.0, 2013.0, 2014.0, 2015.0, 2016.0]]],    [[[2017.0, 2018.0, 2019.0, 2020.0, 2021.0, 2022.0, 2023.0],      [2024.0, 2025.0, 2026.0, 2027.0, 2028.0, 2029.0, 2030.0],      [2031.0, 2032.0, 2033.0, 2034.0, 2035.0, 2036.0, 2037.0],      [2038.0, 2039.0, 2040.0, 2041.0, 2042.0, 2043.0, 2044.0],      [2045.0, 2046.0, 2047.0, 2048.0, 2049.0, 2050.0, 2051.0],      [2052.0, 2053.0, 2054.0, 2055.0, 2056.0, 2057.0, 2058.0]],     [[2059.0, 2060.0, 2061.0, 2062.0, 2063.0, 2064.0, 2065.0],      [2066.0, 2067.0, 2068.0, 2069.0, 2070.0, 2071.0, 2072.0],      [2073.0, 2074.0, 2075.0, 2076.0, 2077.0, 2078.0, 2079.0],      [2080.0, 2081.0, 2082.0, 2083.0, 2084.0, 2085.0, 2086.0],      [2087.0, 2088.0, 2089.0, 2090.0, 2091.0, 2092.0, 2093.0],      [2094.0, 2095.0, 2096.0, 2097.0, 2098.0, 2099.0, 2100.0]],     [[2101.0, 2102.0, 2103.0, 2104.0, 2105.0, 2106.0, 2107.0],      [2108.0, 2109.0, 2110.0, 2111.0, 2112.0, 2113.0, 2114.0],      [2115.0, 2116.0, 2117.0, 2118.0, 2119.0, 2120.0, 2121.0],      [2122.0, 2123.0, 2124.0, 2125.0, 2126.0, 2127.0, 2128.0],      [2129.0, 2130.0, 2131.0, 2132.0, 2133.0, 2134.0, 2135.0],      [2136.0, 2137.0, 2138.0, 2139.0, 2140.0, 2141.0, 2142.0]]],    [[[2143.0, 2144.0, 2145.0, 2146.0, 2147.0, 2148.0, 2149.0],      [2150.0, 2151.0, 2152.0, 2153.0, 2154.0, 2155.0, 2156.0],      [2157.0, 2158.0, 2159.0, 2160.0, 2161.0, 2162.0, 2163.0],      [2164.0, 2165.0, 2166.0, 2167.0, 2168.0, 2169.0, 2170.0],      [2171.0, 2172.0, 2173.0, 2174.0, 2175.0, 2176.0, 2177.0],      [2178.0, 2179.0, 2180.0, 2181.0, 2182.0, 2183.0, 2184.0]],     [[2185.0, 2186.0, 2187.0, 2188.0, 2189.0, 2190.0, 2191.0],      [2192.0, 2193.0, 2194.0, 2195.0, 2196.0, 2197.0, 2198.0],      [2199.0, 2200.0, 2201.0, 2202.0, 2203.0, 2204.0, 2205.0],      [2206.0, 2207.0, 2208.0, 2209.0, 2210.0, 2211.0, 2212.0],      [2213.0, 2214.0, 2215.0, 2216.0, 2217.0, 2218.0, 2219.0],      [2220.0, 2221.0, 2222.0, 2223.0, 2224.0, 2225.0, 2226.0]],     [[2227.0, 2228.0, 2229.0, 2230.0, 2231.0, 2232.0, 2233.0],      [2234.0, 2235.0, 2236.0, 2237.0, 2238.0, 2239.0, 2240.0],      [2241.0, 2242.0, 2243.0, 2244.0, 2245.0, 2246.0, 2247.0],      [2248.0, 2249.0, 2250.0, 2251.0, 2252.0, 2253.0, 2254.0],      [2255.0, 2256.0, 2257.0, 2258.0, 2259.0, 2260.0, 2261.0],      [2262.0, 2263.0, 2264.0, 2265.0, 2266.0, 2267.0, 2268.0]]]],   [[[[2269.0, 2270.0, 2271.0, 2272.0, 2273.0, 2274.0, 2275.0],      [2276.0, 2277.0, 2278.0, 2279.0, 2280.0, 2281.0, 2282.0],      [2283.0, 2284.0, 2285.0, 2286.0, 2287.0, 2288.0, 2289.0],      [2290.0, 2291.0, 2292.0, 2293.0, 2294.0, 2295.0, 2296.0],      [2297.0, 2298.0, 2299.0, 2300.0, 2301.0, 2302.0, 2303.0],      [2304.0, 2305.0, 2306.0, 2307.0, 2308.0, 2309.0, 2310.0]],     [[2311.0, 2312.0, 2313.0, 2314.0, 2315.0, 2316.0, 2317.0],      [2318.0, 2319.0, 2320.0, 2321.0, 2322.0, 2323.0, 2324.0],      [2325.0, 2326.0, 2327.0, 2328.0, 2329.0, 2330.0, 2331.0],      [2332.0, 2333.0, 2334.0, 2335.0, 2336.0, 2337.0, 2338.0],      [2339.0, 2340.0, 2341.0, 2342.0, 2343.0, 2344.0, 2345.0],      [2346.0, 2347.0, 2348.0, 2349.0, 2350.0, 2351.0, 2352.0]],     [[2353.0, 2354.0, 2355.0, 2356.0, 2357.0, 2358.0, 2359.0],      [2360.0, 2361.0, 2362.0, 2363.0, 2364.0, 2365.0, 2366.0],      [2367.0, 2368.0, 2369.0, 2370.0, 2371.0, 2372.0, 2373.0],      [2374.0, 2375.0, 2376.0, 2377.0, 2378.0, 2379.0, 2380.0],      [2381.0, 2382.0, 2383.0, 2384.0, 2385.0, 2386.0, 2387.0],      [2388.0, 2389.0, 2390.0, 2391.0, 2392.0, 2393.0, 2394.0]]],    [[[2395.0, 2396.0, 2397.0, 2398.0, 2399.0, 2400.0, 2401.0],      [2402.0, 2403.0, 2404.0, 2405.0, 2406.0, 2407.0, 2408.0],      [2409.0, 2410.0, 2411.0, 2412.0, 2413.0, 2414.0, 2415.0],      [2416.0, 2417.0, 2418.0, 2419.0, 2420.0, 2421.0, 2422.0],      [2423.0, 2424.0, 2425.0, 2426.0, 2427.0, 2428.0, 2429.0],      [2430.0, 2431.0, 2432.0, 2433.0, 2434.0, 2435.0, 2436.0]],     [[2437.0, 2438.0, 2439.0, 2440.0, 2441.0, 2442.0, 2443.0],      [2444.0, 2445.0, 2446.0, 2447.0, 2448.0, 2449.0, 2450.0],      [2451.0, 2452.0, 2453.0, 2454.0, 2455.0, 2456.0, 2457.0],      [2458.0, 2459.0, 2460.0, 2461.0, 2462.0, 2463.0, 2464.0],      [2465.0, 2466.0, 2467.0, 2468.0, 2469.0, 2470.0, 2471.0],      [2472.0, 2473.0, 2474.0, 2475.0, 2476.0, 2477.0, 2478.0]],     [[2479.0, 2480.0, 2481.0, 2482.0, 2483.0, 2484.0, 2485.0],      [2486.0, 2487.0, 2488.0, 2489.0, 2490.0, 2491.0, 2492.0],      [2493.0, 2494.0, 2495.0, 2496.0, 2497.0, 2498.0, 2499.0],      [2500.0, 2501.0, 2502.0, 2503.0, 2504.0, 2505.0, 2506.0],      [2507.0, 2508.0, 2509.0, 2510.0, 2511.0, 2512.0, 2513.0],      [2514.0, 2515.0, 2516.0, 2517.0, 2518.0, 2519.0, 2520.0]]],    [[[2521.0, 2522.0, 2523.0, 2524.0, 2525.0, 2526.0, 2527.0],      [2528.0, 2529.0, 2530.0, 2531.0, 2532.0, 2533.0, 2534.0],      [2535.0, 2536.0, 2537.0, 2538.0, 2539.0, 2540.0, 2541.0],      [2542.0, 2543.0, 2544.0, 2545.0, 2546.0, 2547.0, 2548.0],      [2549.0, 2550.0, 2551.0, 2552.0, 2553.0, 2554.0, 2555.0],      [2556.0, 2557.0, 2558.0, 2559.0, 2560.0, 2561.0, 2562.0]],     [[2563.0, 2564.0, 2565.0, 2566.0, 2567.0, 2568.0, 2569.0],      [2570.0, 2571.0, 2572.0, 2573.0, 2574.0, 2575.0, 2576.0],      [2577.0, 2578.0, 2579.0, 2580.0, 2581.0, 2582.0, 2583.0],      [2584.0, 2585.0, 2586.0, 2587.0, 2588.0, 2589.0, 2590.0],      [2591.0, 2592.0, 2593.0, 2594.0, 2595.0, 2596.0, 2597.0],      [2598.0, 2599.0, 2600.0, 2601.0, 2602.0, 2603.0, 2604.0]],     [[2605.0, 2606.0, 2607.0, 2608.0, 2609.0, 2610.0, 2611.0],      [2612.0, 2613.0, 2614.0, 2615.0, 2616.0, 2617.0, 2618.0],      [2619.0, 2620.0, 2621.0, 2622.0, 2623.0, 2624.0, 2625.0],      [2626.0, 2627.0, 2628.0, 2629.0, 2630.0, 2631.0, 2632.0],      [2633.0, 2634.0, 2635.0, 2636.0, 2637.0, 2638.0, 2639.0],      [2640.0, 2641.0, 2642.0, 2643.0, 2644.0, 2645.0, 2646.0]]]],   [[[[2647.0, 2648.0, 2649.0, 2650.0, 2651.0, 2652.0, 2653.0],      [2654.0, 2655.0, 2656.0, 2657.0, 2658.0, 2659.0, 2660.0],      [2661.0, 2662.0, 2663.0, 2664.0, 2665.0, 2666.0, 2667.0],      [2668.0, 2669.0, 2670.0, 2671.0, 2672.0, 2673.0, 2674.0],      [2675.0, 2676.0, 2677.0, 2678.0, 2679.0, 2680.0, 2681.0],      [2682.0, 2683.0, 2684.0, 2685.0, 2686.0, 2687.0, 2688.0]],     [[2689.0, 2690.0, 2691.0, 2692.0, 2693.0, 2694.0, 2695.0],      [2696.0, 2697.0, 2698.0, 2699.0, 2700.0, 2701.0, 2702.0],      [2703.0, 2704.0, 2705.0, 2706.0, 2707.0, 2708.0, 2709.0],      [2710.0, 2711.0, 2712.0, 2713.0, 2714.0, 2715.0, 2716.0],      [2717.0, 2718.0, 2719.0, 2720.0, 2721.0, 2722.0, 2723.0],      [2724.0, 2725.0, 2726.0, 2727.0, 2728.0, 2729.0, 2730.0]],     [[2731.0, 2732.0, 2733.0, 2734.0, 2735.0, 2736.0, 2737.0],      [2738.0, 2739.0, 2740.0, 2741.0, 2742.0, 2743.0, 2744.0],      [2745.0, 2746.0, 2747.0, 2748.0, 2749.0, 2750.0, 2751.0],      [2752.0, 2753.0, 2754.0, 2755.0, 2756.0, 2757.0, 2758.0],      [2759.0, 2760.0, 2761.0, 2762.0, 2763.0, 2764.0, 2765.0],      [2766.0, 2767.0, 2768.0, 2769.0, 2770.0, 2771.0, 2772.0]]],    [[[2773.0, 2774.0, 2775.0, 2776.0, 2777.0, 2778.0, 2779.0],      [2780.0, 2781.0, 2782.0, 2783.0, 2784.0, 2785.0, 2786.0],      [2787.0, 2788.0, 2789.0, 2790.0, 2791.0, 2792.0, 2793.0],      [2794.0, 2795.0, 2796.0, 2797.0, 2798.0, 2799.0, 2800.0],      [2801.0, 2802.0, 2803.0, 2804.0, 2805.0, 2806.0, 2807.0],      [2808.0, 2809.0, 2810.0, 2811.0, 2812.0, 2813.0, 2814.0]],     [[2815.0, 2816.0, 2817.0, 2818.0, 2819.0, 2820.0, 2821.0],      [2822.0, 2823.0, 2824.0, 2825.0, 2826.0, 2827.0, 2828.0],      [2829.0, 2830.0, 2831.0, 2832.0, 2833.0, 2834.0, 2835.0],      [2836.0, 2837.0, 2838.0, 2839.0, 2840.0, 2841.0, 2842.0],      [2843.0, 2844.0, 2845.0, 2846.0, 2847.0, 2848.0, 2849.0],      [2850.0, 2851.0, 2852.0, 2853.0, 2854.0, 2855.0, 2856.0]],     [[2857.0, 2858.0, 2859.0, 2860.0, 2861.0, 2862.0, 2863.0],      [2864.0, 2865.0, 2866.0, 2867.0, 2868.0, 2869.0, 2870.0],      [2871.0, 2872.0, 2873.0, 2874.0, 2875.0, 2876.0, 2877.0],      [2878.0, 2879.0, 2880.0, 2881.0, 2882.0, 2883.0, 2884.0],      [2885.0, 2886.0, 2887.0, 2888.0, 2889.0, 2890.0, 2891.0],      [2892.0, 2893.0, 2894.0, 2895.0, 2896.0, 2897.0, 2898.0]]],    [[[2899.0, 2900.0, 2901.0, 2902.0, 2903.0, 2904.0, 2905.0],      [2906.0, 2907.0, 2908.0, 2909.0, 2910.0, 2911.0, 2912.0],      [2913.0, 2914.0, 2915.0, 2916.0, 2917.0, 2918.0, 2919.0],      [2920.0, 2921.0, 2922.0, 2923.0, 2924.0, 2925.0, 2926.0],      [2927.0, 2928.0, 2929.0, 2930.0, 2931.0, 2932.0, 2933.0],      [2934.0, 2935.0, 2936.0, 2937.0, 2938.0, 2939.0, 2940.0]],     [[2941.0, 2942.0, 2943.0, 2944.0, 2945.0, 2946.0, 2947.0],      [2948.0, 2949.0, 2950.0, 2951.0, 2952.0, 2953.0, 2954.0],      [2955.0, 2956.0, 2957.0, 2958.0, 2959.0, 2960.0, 2961.0],      [2962.0, 2963.0, 2964.0, 2965.0, 2966.0, 2967.0, 2968.0],      [2969.0, 2970.0, 2971.0, 2972.0, 2973.0, 2974.0, 2975.0],      [2976.0, 2977.0, 2978.0, 2979.0, 2980.0, 2981.0, 2982.0]],     [[2983.0, 2984.0, 2985.0, 2986.0, 2987.0, 2988.0, 2989.0],      [2990.0, 2991.0, 2992.0, 2993.0, 2994.0, 2995.0, 2996.0],      [2997.0, 2998.0, 2999.0, 3000.0, 3001.0, 3002.0, 3003.0],      [3004.0, 3005.0, 3006.0, 3007.0, 3008.0, 3009.0, 3010.0],      [3011.0, 3012.0, 3013.0, 3014.0, 3015.0, 3016.0, 3017.0],      [3018.0, 3019.0, 3020.0, 3021.0, 3022.0, 3023.0, 3024.0]]]],   [[[[3025.0, 3026.0, 3027.0, 3028.0, 3029.0, 3030.0, 3031.0],      [3032.0, 3033.0, 3034.0, 3035.0, 3036.0, 3037.0, 3038.0],      [3039.0, 3040.0, 3041.0, 3042.0, 3043.0, 3044.0, 3045.0],      [3046.0, 3047.0, 3048.0, 3049.0, 3050.0, 3051.0, 3052.0],      [3053.0, 3054.0, 3055.0, 3056.0, 3057.0, 3058.0, 3059.0],      [3060.0, 3061.0, 3062.0, 3063.0, 3064.0, 3065.0, 3066.0]],     [[3067.0, 3068.0, 3069.0, 3070.0, 3071.0, 3072.0, 3073.0],      [3074.0, 3075.0, 3076.0, 3077.0, 3078.0, 3079.0, 3080.0],      [3081.0, 3082.0, 3083.0, 3084.0, 3085.0, 3086.0, 3087.0],      [3088.0, 3089.0, 3090.0, 3091.0, 3092.0, 3093.0, 3094.0],      [3095.0, 3096.0, 3097.0, 3098.0, 3099.0, 3100.0, 3101.0],      [3102.0, 3103.0, 3104.0, 3105.0, 3106.0, 3107.0, 3108.0]],     [[3109.0, 3110.0, 3111.0, 3112.0, 3113.0, 3114.0, 3115.0],      [3116.0, 3117.0, 3118.0, 3119.0, 3120.0, 3121.0, 3122.0],      [3123.0, 3124.0, 3125.0, 3126.0, 3127.0, 3128.0, 3129.0],      [3130.0, 3131.0, 3132.0, 3133.0, 3134.0, 3135.0, 3136.0],      [3137.0, 3138.0, 3139.0, 3140.0, 3141.0, 3142.0, 3143.0],      [3144.0, 3145.0, 3146.0, 3147.0, 3148.0, 3149.0, 3150.0]]],    [[[3151.0, 3152.0, 3153.0, 3154.0, 3155.0, 3156.0, 3157.0],      [3158.0, 3159.0, 3160.0, 3161.0, 3162.0, 3163.0, 3164.0],      [3165.0, 3166.0, 3167.0, 3168.0, 3169.0, 3170.0, 3171.0],      [3172.0, 3173.0, 3174.0, 3175.0, 3176.0, 3177.0, 3178.0],      [3179.0, 3180.0, 3181.0, 3182.0, 3183.0, 3184.0, 3185.0],      [3186.0, 3187.0, 3188.0, 3189.0, 3190.0, 3191.0, 3192.0]],     [[3193.0, 3194.0, 3195.0, 3196.0, 3197.0, 3198.0, 3199.0],      [3200.0, 3201.0, 3202.0, 3203.0, 3204.0, 3205.0, 3206.0],      [3207.0, 3208.0, 3209.0, 3210.0, 3211.0, 3212.0, 3213.0],      [3214.0, 3215.0, 3216.0, 3217.0, 3218.0, 3219.0, 3220.0],      [3221.0, 3222.0, 3223.0, 3224.0, 3225.0, 3226.0, 3227.0],      [3228.0, 3229.0, 3230.0, 3231.0, 3232.0, 3233.0, 3234.0]],     [[3235.0, 3236.0, 3237.0, 3238.0, 3239.0, 3240.0, 3241.0],      [3242.0, 3243.0, 3244.0, 3245.0, 3246.0, 3247.0, 3248.0],      [3249.0, 3250.0, 3251.0, 3252.0, 3253.0, 3254.0, 3255.0],      [3256.0, 3257.0, 3258.0, 3259.0, 3260.0, 3261.0, 3262.0],      [3263.0, 3264.0, 3265.0, 3266.0, 3267.0, 3268.0, 3269.0],      [3270.0, 3271.0, 3272.0, 3273.0, 3274.0, 3275.0, 3276.0]]],    [[[3277.0, 3278.0, 3279.0, 3280.0, 3281.0, 3282.0, 3283.0],      [3284.0, 3285.0, 3286.0, 3287.0, 3288.0, 3289.0, 3290.0],      [3291.0, 3292.0, 3293.0, 3294.0, 3295.0, 3296.0, 3297.0],      [3298.0, 3299.0, 3300.0, 3301.0, 3302.0, 3303.0, 3304.0],      [3305.0, 3306.0, 3307.0, 3308.0, 3309.0, 3310.0, 3311.0],      [3312.0, 3313.0, 3314.0, 3315.0, 3316.0, 3317.0, 3318.0]],     [[3319.0, 3320.0, 3321.0, 3322.0, 3323.0, 3324.0, 3325.0],      [3326.0, 3327.0, 3328.0, 3329.0, 3330.0, 3331.0, 3332.0],      [3333.0, 3334.0, 3335.0, 3336.0, 3337.0, 3338.0, 3339.0],      [3340.0, 3341.0, 3342.0, 3343.0, 3344.0, 3345.0, 3346.0],      [3347.0, 3348.0, 3349.0, 3350.0, 3351.0, 3352.0, 3353.0],      [3354.0, 3355.0, 3356.0, 3357.0, 3358.0, 3359.0, 3360.0]],     [[3361.0, 3362.0, 3363.0, 3364.0, 3365.0, 3366.0, 3367.0],      [3368.0, 3369.0, 3370.0, 3371.0, 3372.0, 3373.0, 3374.0],      [3375.0, 3376.0, 3377.0, 3378.0, 3379.0, 3380.0, 3381.0],      [3382.0, 3383.0, 3384.0, 3385.0, 3386.0, 3387.0, 3388.0],      [3389.0, 3390.0, 3391.0, 3392.0, 3393.0, 3394.0, 3395.0],      [3396.0, 3397.0, 3398.0, 3399.0, 3400.0, 3401.0, 3402.0]]]],   [[[[3403.0, 3404.0, 3405.0, 3406.0, 3407.0, 3408.0, 3409.0],      [3410.0, 3411.0, 3412.0, 3413.0, 3414.0, 3415.0, 3416.0],      [3417.0, 3418.0, 3419.0, 3420.0, 3421.0, 3422.0, 3423.0],      [3424.0, 3425.0, 3426.0, 3427.0, 3428.0, 3429.0, 3430.0],      [3431.0, 3432.0, 3433.0, 3434.0, 3435.0, 3436.0, 3437.0],      [3438.0, 3439.0, 3440.0, 3441.0, 3442.0, 3443.0, 3444.0]],     [[3445.0, 3446.0, 3447.0, 3448.0, 3449.0, 3450.0, 3451.0],      [3452.0, 3453.0, 3454.0, 3455.0, 3456.0, 3457.0, 3458.0],      [3459.0, 3460.0, 3461.0, 3462.0, 3463.0, 3464.0, 3465.0],      [3466.0, 3467.0, 3468.0, 3469.0, 3470.0, 3471.0, 3472.0],      [3473.0, 3474.0, 3475.0, 3476.0, 3477.0, 3478.0, 3479.0],      [3480.0, 3481.0, 3482.0, 3483.0, 3484.0, 3485.0, 3486.0]],     [[3487.0, 3488.0, 3489.0, 3490.0, 3491.0, 3492.0, 3493.0],      [3494.0, 3495.0, 3496.0, 3497.0, 3498.0, 3499.0, 3500.0],      [3501.0, 3502.0, 3503.0, 3504.0, 3505.0, 3506.0, 3507.0],      [3508.0, 3509.0, 3510.0, 3511.0, 3512.0, 3513.0, 3514.0],      [3515.0, 3516.0, 3517.0, 3518.0, 3519.0, 3520.0, 3521.0],      [3522.0, 3523.0, 3524.0, 3525.0, 3526.0, 3527.0, 3528.0]]],    [[[3529.0, 3530.0, 3531.0, 3532.0, 3533.0, 3534.0, 3535.0],      [3536.0, 3537.0, 3538.0, 3539.0, 3540.0, 3541.0, 3542.0],      [3543.0, 3544.0, 3545.0, 3546.0, 3547.0, 3548.0, 3549.0],      [3550.0, 3551.0, 3552.0, 3553.0, 3554.0, 3555.0, 3556.0],      [3557.0, 3558.0, 3559.0, 3560.0, 3561.0, 3562.0, 3563.0],      [3564.0, 3565.0, 3566.0, 3567.0, 3568.0, 3569.0, 3570.0]],     [[3571.0, 3572.0, 3573.0, 3574.0, 3575.0, 3576.0, 3577.0],      [3578.0, 3579.0, 3580.0, 3581.0, 3582.0, 3583.0, 3584.0],      [3585.0, 3586.0, 3587.0, 3588.0, 3589.0, 3590.0, 3591.0],      [3592.0, 3593.0, 3594.0, 3595.0, 3596.0, 3597.0, 3598.0],      [3599.0, 3600.0, 3601.0, 3602.0, 3603.0, 3604.0, 3605.0],      [3606.0, 3607.0, 3608.0, 3609.0, 3610.0, 3611.0, 3612.0]],     [[3613.0, 3614.0, 3615.0, 3616.0, 3617.0, 3618.0, 3619.0],      [3620.0, 3621.0, 3622.0, 3623.0, 3624.0, 3625.0, 3626.0],      [3627.0, 3628.0, 3629.0, 3630.0, 3631.0, 3632.0, 3633.0],      [3634.0, 3635.0, 3636.0, 3637.0, 3638.0, 3639.0, 3640.0],      [3641.0, 3642.0, 3643.0, 3644.0, 3645.0, 3646.0, 3647.0],      [3648.0, 3649.0, 3650.0, 3651.0, 3652.0, 3653.0, 3654.0]]],    [[[3655.0, 3656.0, 3657.0, 3658.0, 3659.0, 3660.0, 3661.0],      [3662.0, 3663.0, 3664.0, 3665.0, 3666.0, 3667.0, 3668.0],      [3669.0, 3670.0, 3671.0, 3672.0, 3673.0, 3674.0, 3675.0],      [3676.0, 3677.0, 3678.0, 3679.0, 3680.0, 3681.0, 3682.0],      [3683.0, 3684.0, 3685.0, 3686.0, 3687.0, 3688.0, 3689.0],      [3690.0, 3691.0, 3692.0, 3693.0, 3694.0, 3695.0, 3696.0]],     [[3697.0, 3698.0, 3699.0, 3700.0, 3701.0, 3702.0, 3703.0],      [3704.0, 3705.0, 3706.0, 3707.0, 3708.0, 3709.0, 3710.0],      [3711.0, 3712.0, 3713.0, 3714.0, 3715.0, 3716.0, 3717.0],      [3718.0, 3719.0, 3720.0, 3721.0, 3722.0, 3723.0, 3724.0],      [3725.0, 3726.0, 3727.0, 3728.0, 3729.0, 3730.0, 3731.0],      [3732.0, 3733.0, 3734.0, 3735.0, 3736.0, 3737.0, 3738.0]],     [[3739.0, 3740.0, 3741.0, 3742.0, 3743.0, 3744.0, 3745.0],      [3746.0, 3747.0, 3748.0, 3749.0, 3750.0, 3751.0, 3752.0],      [3753.0, 3754.0, 3755.0, 3756.0, 3757.0, 3758.0, 3759.0],      [3760.0, 3761.0, 3762.0, 3763.0, 3764.0, 3765.0, 3766.0],      [3767.0, 3768.0, 3769.0, 3770.0, 3771.0, 3772.0, 3773.0],      [3774.0, 3775.0, 3776.0, 3777.0, 3778.0, 3779.0, 3780.0]]]]],  [[[[[3781.0, 3782.0, 3783.0, 3784.0, 3785.0, 3786.0, 3787.0],      [3788.0, 3789.0, 3790.0, 3791.0, 3792.0, 3793.0, 3794.0],      [3795.0, 3796.0, 3797.0, 3798.0, 3799.0, 3800.0, 3801.0],      [3802.0, 3803.0, 3804.0, 3805.0, 3806.0, 3807.0, 3808.0],      [3809.0, 3810.0, 3811.0, 3812.0, 3813.0, 3814.0, 3815.0],      [3816.0, 3817.0, 3818.0, 3819.0, 3820.0, 3821.0, 3822.0]],     [[3823.0, 3824.0, 3825.0, 3826.0, 3827.0, 3828.0, 3829.0],      [3830.0, 3831.0, 3832.0, 3833.0, 3834.0, 3835.0, 3836.0],      [3837.0, 3838.0, 3839.0, 3840.0, 3841.0, 3842.0, 3843.0],      [3844.0, 3845.0, 3846.0, 3847.0, 3848.0, 3849.0, 3850.0],      [3851.0, 3852.0, 3853.0, 3854.0, 3855.0, 3856.0, 3857.0],      [3858.0, 3859.0, 3860.0, 3861.0, 3862.0, 3863.0, 3864.0]],     [[3865.0, 3866.0, 3867.0, 3868.0, 3869.0, 3870.0, 3871.0],      [3872.0, 3873.0, 3874.0, 3875.0, 3876.0, 3877.0, 3878.0],      [3879.0, 3880.0, 3881.0, 3882.0, 3883.0, 3884.0, 3885.0],      [3886.0, 3887.0, 3888.0, 3889.0, 3890.0, 3891.0, 3892.0],      [3893.0, 3894.0, 3895.0, 3896.0, 3897.0, 3898.0, 3899.0],      [3900.0, 3901.0, 3902.0, 3903.0, 3904.0, 3905.0, 3906.0]]],    [[[3907.0, 3908.0, 3909.0, 3910.0, 3911.0, 3912.0, 3913.0],      [3914.0, 3915.0, 3916.0, 3917.0, 3918.0, 3919.0, 3920.0],      [3921.0, 3922.0, 3923.0, 3924.0, 3925.0, 3926.0, 3927.0],      [3928.0, 3929.0, 3930.0, 3931.0, 3932.0, 3933.0, 3934.0],      [3935.0, 3936.0, 3937.0, 3938.0, 3939.0, 3940.0, 3941.0],      [3942.0, 3943.0, 3944.0, 3945.0, 3946.0, 3947.0, 3948.0]],     [[3949.0, 3950.0, 3951.0, 3952.0, 3953.0, 3954.0, 3955.0],      [3956.0, 3957.0, 3958.0, 3959.0, 3960.0, 3961.0, 3962.0],      [3963.0, 3964.0, 3965.0, 3966.0, 3967.0, 3968.0, 3969.0],      [3970.0, 3971.0, 3972.0, 3973.0, 3974.0, 3975.0, 3976.0],      [3977.0, 3978.0, 3979.0, 3980.0, 3981.0, 3982.0, 3983.0],      [3984.0, 3985.0, 3986.0, 3987.0, 3988.0, 3989.0, 3990.0]],     [[3991.0, 3992.0, 3993.0, 3994.0, 3995.0, 3996.0, 3997.0],      [3998.0, 3999.0, 4000.0, 4001.0, 4002.0, 4003.0, 4004.0],      [4005.0, 4006.0, 4007.0, 4008.0, 4009.0, 4010.0, 4011.0],      [4012.0, 4013.0, 4014.0, 4015.0, 4016.0, 4017.0, 4018.0],      [4019.0, 4020.0, 4021.0, 4022.0, 4023.0, 4024.0, 4025.0],      [4026.0, 4027.0, 4028.0, 4029.0, 4030.0, 4031.0, 4032.0]]],    [[[4033.0, 4034.0, 4035.0, 4036.0, 4037.0, 4038.0, 4039.0],      [4040.0, 4041.0, 4042.0, 4043.0, 4044.0, 4045.0, 4046.0],      [4047.0, 4048.0, 4049.0, 4050.0, 4051.0, 4052.0, 4053.0],      [4054.0, 4055.0, 4056.0, 4057.0, 4058.0, 4059.0, 4060.0],      [4061.0, 4062.0, 4063.0, 4064.0, 4065.0, 4066.0, 4067.0],      [4068.0, 4069.0, 4070.0, 4071.0, 4072.0, 4073.0, 4074.0]],     [[4075.0, 4076.0, 4077.0, 4078.0, 4079.0, 4080.0, 4081.0],      [4082.0, 4083.0, 4084.0, 4085.0, 4086.0, 4087.0, 4088.0],      [4089.0, 4090.0, 4091.0, 4092.0, 4093.0, 4094.0, 4095.0],      [4096.0, 4097.0, 4098.0, 4099.0, 4100.0, 4101.0, 4102.0],      [4103.0, 4104.0, 4105.0, 4106.0, 4107.0, 4108.0, 4109.0],      [4110.0, 4111.0, 4112.0, 4113.0, 4114.0, 4115.0, 4116.0]],     [[4117.0, 4118.0, 4119.0, 4120.0, 4121.0, 4122.0, 4123.0],      [4124.0, 4125.0, 4126.0, 4127.0, 4128.0, 4129.0, 4130.0],      [4131.0, 4132.0, 4133.0, 4134.0, 4135.0, 4136.0, 4137.0],      [4138.0, 4139.0, 4140.0, 4141.0, 4142.0, 4143.0, 4144.0],      [4145.0, 4146.0, 4147.0, 4148.0, 4149.0, 4150.0, 4151.0],      [4152.0, 4153.0, 4154.0, 4155.0, 4156.0, 4157.0, 4158.0]]]],   [[[[4159.0, 4160.0, 4161.0, 4162.0, 4163.0, 4164.0, 4165.0],      [4166.0, 4167.0, 4168.0, 4169.0, 4170.0, 4171.0, 4172.0],      [4173.0, 4174.0, 4175.0, 4176.0, 4177.0, 4178.0, 4179.0],      [4180.0, 4181.0, 4182.0, 4183.0, 4184.0, 4185.0, 4186.0],      [4187.0, 4188.0, 4189.0, 4190.0, 4191.0, 4192.0, 4193.0],      [4194.0, 4195.0, 4196.0, 4197.0, 4198.0, 4199.0, 4200.0]],     [[4201.0, 4202.0, 4203.0, 4204.0, 4205.0, 4206.0, 4207.0],      [4208.0, 4209.0, 4210.0, 4211.0, 4212.0, 4213.0, 4214.0],      [4215.0, 4216.0, 4217.0, 4218.0, 4219.0, 4220.0, 4221.0],      [4222.0, 4223.0, 4224.0, 4225.0, 4226.0, 4227.0, 4228.0],      [4229.0, 4230.0, 4231.0, 4232.0, 4233.0, 4234.0, 4235.0],      [4236.0, 4237.0, 4238.0, 4239.0, 4240.0, 4241.0, 4242.0]],     [[4243.0, 4244.0, 4245.0, 4246.0, 4247.0, 4248.0, 4249.0],      [4250.0, 4251.0, 4252.0, 4253.0, 4254.0, 4255.0, 4256.0],      [4257.0, 4258.0, 4259.0, 4260.0, 4261.0, 4262.0, 4263.0],      [4264.0, 4265.0, 4266.0, 4267.0, 4268.0, 4269.0, 4270.0],      [4271.0, 4272.0, 4273.0, 4274.0, 4275.0, 4276.0, 4277.0],      [4278.0, 4279.0, 4280.0, 4281.0, 4282.0, 4283.0, 4284.0]]],    [[[4285.0, 4286.0, 4287.0, 4288.0, 4289.0, 4290.0, 4291.0],      [4292.0, 4293.0, 4294.0, 4295.0, 4296.0, 4297.0, 4298.0],      [4299.0, 4300.0, 4301.0, 4302.0, 4303.0, 4304.0, 4305.0],      [4306.0, 4307.0, 4308.0, 4309.0, 4310.0, 4311.0, 4312.0],      [4313.0, 4314.0, 4315.0, 4316.0, 4317.0, 4318.0, 4319.0],      [4320.0, 4321.0, 4322.0, 4323.0, 4324.0, 4325.0, 4326.0]],     [[4327.0, 4328.0, 4329.0, 4330.0, 4331.0, 4332.0, 4333.0],      [4334.0, 4335.0, 4336.0, 4337.0, 4338.0, 4339.0, 4340.0],      [4341.0, 4342.0, 4343.0, 4344.0, 4345.0, 4346.0, 4347.0],      [4348.0, 4349.0, 4350.0, 4351.0, 4352.0, 4353.0, 4354.0],      [4355.0, 4356.0, 4357.0, 4358.0, 4359.0, 4360.0, 4361.0],      [4362.0, 4363.0, 4364.0, 4365.0, 4366.0, 4367.0, 4368.0]],     [[4369.0, 4370.0, 4371.0, 4372.0, 4373.0, 4374.0, 4375.0],      [4376.0, 4377.0, 4378.0, 4379.0, 4380.0, 4381.0, 4382.0],      [4383.0, 4384.0, 4385.0, 4386.0, 4387.0, 4388.0, 4389.0],      [4390.0, 4391.0, 4392.0, 4393.0, 4394.0, 4395.0, 4396.0],      [4397.0, 4398.0, 4399.0, 4400.0, 4401.0, 4402.0, 4403.0],      [4404.0, 4405.0, 4406.0, 4407.0, 4408.0, 4409.0, 4410.0]]],    [[[4411.0, 4412.0, 4413.0, 4414.0, 4415.0, 4416.0, 4417.0],      [4418.0, 4419.0, 4420.0, 4421.0, 4422.0, 4423.0, 4424.0],      [4425.0, 4426.0, 4427.0, 4428.0, 4429.0, 4430.0, 4431.0],      [4432.0, 4433.0, 4434.0, 4435.0, 4436.0, 4437.0, 4438.0],      [4439.0, 4440.0, 4441.0, 4442.0, 4443.0, 4444.0, 4445.0],      [4446.0, 4447.0, 4448.0, 4449.0, 4450.0, 4451.0, 4452.0]],     [[4453.0, 4454.0, 4455.0, 4456.0, 4457.0, 4458.0, 4459.0],      [4460.0, 4461.0, 4462.0, 4463.0, 4464.0, 4465.0, 4466.0],      [4467.0, 4468.0, 4469.0, 4470.0, 4471.0, 4472.0, 4473.0],      [4474.0, 4475.0, 4476.0, 4477.0, 4478.0, 4479.0, 4480.0],      [4481.0, 4482.0, 4483.0, 4484.0, 4485.0, 4486.0, 4487.0],      [4488.0, 4489.0, 4490.0, 4491.0, 4492.0, 4493.0, 4494.0]],     [[4495.0, 4496.0, 4497.0, 4498.0, 4499.0, 4500.0, 4501.0],      [4502.0, 4503.0, 4504.0, 4505.0, 4506.0, 4507.0, 4508.0],      [4509.0, 4510.0, 4511.0, 4512.0, 4513.0, 4514.0, 4515.0],      [4516.0, 4517.0, 4518.0, 4519.0, 4520.0, 4521.0, 4522.0],      [4523.0, 4524.0, 4525.0, 4526.0, 4527.0, 4528.0, 4529.0],      [4530.0, 4531.0, 4532.0, 4533.0, 4534.0, 4535.0, 4536.0]]]],   [[[[4537.0, 4538.0, 4539.0, 4540.0, 4541.0, 4542.0, 4543.0],      [4544.0, 4545.0, 4546.0, 4547.0, 4548.0, 4549.0, 4550.0],      [4551.0, 4552.0, 4553.0, 4554.0, 4555.0, 4556.0, 4557.0],      [4558.0, 4559.0, 4560.0, 4561.0, 4562.0, 4563.0, 4564.0],      [4565.0, 4566.0, 4567.0, 4568.0, 4569.0, 4570.0, 4571.0],      [4572.0, 4573.0, 4574.0, 4575.0, 4576.0, 4577.0, 4578.0]],     [[4579.0, 4580.0, 4581.0, 4582.0, 4583.0, 4584.0, 4585.0],      [4586.0, 4587.0, 4588.0, 4589.0, 4590.0, 4591.0, 4592.0],      [4593.0, 4594.0, 4595.0, 4596.0, 4597.0, 4598.0, 4599.0],      [4600.0, 4601.0, 4602.0, 4603.0, 4604.0, 4605.0, 4606.0],      [4607.0, 4608.0, 4609.0, 4610.0, 4611.0, 4612.0, 4613.0],      [4614.0, 4615.0, 4616.0, 4617.0, 4618.0, 4619.0, 4620.0]],     [[4621.0, 4622.0, 4623.0, 4624.0, 4625.0, 4626.0, 4627.0],      [4628.0, 4629.0, 4630.0, 4631.0, 4632.0, 4633.0, 4634.0],      [4635.0, 4636.0, 4637.0, 4638.0, 4639.0, 4640.0, 4641.0],      [4642.0, 4643.0, 4644.0, 4645.0, 4646.0, 4647.0, 4648.0],      [4649.0, 4650.0, 4651.0, 4652.0, 4653.0, 4654.0, 4655.0],      [4656.0, 4657.0, 4658.0, 4659.0, 4660.0, 4661.0, 4662.0]]],    [[[4663.0, 4664.0, 4665.0, 4666.0, 4667.0, 4668.0, 4669.0],      [4670.0, 4671.0, 4672.0, 4673.0, 4674.0, 4675.0, 4676.0],      [4677.0, 4678.0, 4679.0, 4680.0, 4681.0, 4682.0, 4683.0],      [4684.0, 4685.0, 4686.0, 4687.0, 4688.0, 4689.0, 4690.0],      [4691.0, 4692.0, 4693.0, 4694.0, 4695.0, 4696.0, 4697.0],      [4698.0, 4699.0, 4700.0, 4701.0, 4702.0, 4703.0, 4704.0]],     [[4705.0, 4706.0, 4707.0, 4708.0, 4709.0, 4710.0, 4711.0],      [4712.0, 4713.0, 4714.0, 4715.0, 4716.0, 4717.0, 4718.0],      [4719.0, 4720.0, 4721.0, 4722.0, 4723.0, 4724.0, 4725.0],      [4726.0, 4727.0, 4728.0, 4729.0, 4730.0, 4731.0, 4732.0],      [4733.0, 4734.0, 4735.0, 4736.0, 4737.0, 4738.0, 4739.0],      [4740.0, 4741.0, 4742.0, 4743.0, 4744.0, 4745.0, 4746.0]],     [[4747.0, 4748.0, 4749.0, 4750.0, 4751.0, 4752.0, 4753.0],      [4754.0, 4755.0, 4756.0, 4757.0, 4758.0, 4759.0, 4760.0],      [4761.0, 4762.0, 4763.0, 4764.0, 4765.0, 4766.0, 4767.0],      [4768.0, 4769.0, 4770.0, 4771.0, 4772.0, 4773.0, 4774.0],      [4775.0, 4776.0, 4777.0, 4778.0, 4779.0, 4780.0, 4781.0],      [4782.0, 4783.0, 4784.0, 4785.0, 4786.0, 4787.0, 4788.0]]],    [[[4789.0, 4790.0, 4791.0, 4792.0, 4793.0, 4794.0, 4795.0],      [4796.0, 4797.0, 4798.0, 4799.0, 4800.0, 4801.0, 4802.0],      [4803.0, 4804.0, 4805.0, 4806.0, 4807.0, 4808.0, 4809.0],      [4810.0, 4811.0, 4812.0, 4813.0, 4814.0, 4815.0, 4816.0],      [4817.0, 4818.0, 4819.0, 4820.0, 4821.0, 4822.0, 4823.0],      [4824.0, 4825.0, 4826.0, 4827.0, 4828.0, 4829.0, 4830.0]],     [[4831.0, 4832.0, 4833.0, 4834.0, 4835.0, 4836.0, 4837.0],      [4838.0, 4839.0, 4840.0, 4841.0, 4842.0, 4843.0, 4844.0],      [4845.0, 4846.0, 4847.0, 4848.0, 4849.0, 4850.0, 4851.0],      [4852.0, 4853.0, 4854.0, 4855.0, 4856.0, 4857.0, 4858.0],      [4859.0, 4860.0, 4861.0, 4862.0, 4863.0, 4864.0, 4865.0],      [4866.0, 4867.0, 4868.0, 4869.0, 4870.0, 4871.0, 4872.0]],     [[4873.0, 4874.0, 4875.0, 4876.0, 4877.0, 4878.0, 4879.0],      [4880.0, 4881.0, 4882.0, 4883.0, 4884.0, 4885.0, 4886.0],      [4887.0, 4888.0, 4889.0, 4890.0, 4891.0, 4892.0, 4893.0],      [4894.0, 4895.0, 4896.0, 4897.0, 4898.0, 4899.0, 4900.0],      [4901.0, 4902.0, 4903.0, 4904.0, 4905.0, 4906.0, 4907.0],      [4908.0, 4909.0, 4910.0, 4911.0, 4912.0, 4913.0, 4914.0]]]],   [[[[4915.0, 4916.0, 4917.0, 4918.0, 4919.0, 4920.0, 4921.0],      [4922.0, 4923.0, 4924.0, 4925.0, 4926.0, 4927.0, 4928.0],      [4929.0, 4930.0, 4931.0, 4932.0, 4933.0, 4934.0, 4935.0],      [4936.0, 4937.0, 4938.0, 4939.0, 4940.0, 4941.0, 4942.0],      [4943.0, 4944.0, 4945.0, 4946.0, 4947.0, 4948.0, 4949.0],      [4950.0, 4951.0, 4952.0, 4953.0, 4954.0, 4955.0, 4956.0]],     [[4957.0, 4958.0, 4959.0, 4960.0, 4961.0, 4962.0, 4963.0],      [4964.0, 4965.0, 4966.0, 4967.0, 4968.0, 4969.0, 4970.0],      [4971.0, 4972.0, 4973.0, 4974.0, 4975.0, 4976.0, 4977.0],      [4978.0, 4979.0, 4980.0, 4981.0, 4982.0, 4983.0, 4984.0],      [4985.0, 4986.0, 4987.0, 4988.0, 4989.0, 4990.0, 4991.0],      [4992.0, 4993.0, 4994.0, 4995.0, 4996.0, 4997.0, 4998.0]],     [[4999.0, 5000.0, 5001.0, 5002.0, 5003.0, 5004.0, 5005.0],      [5006.0, 5007.0, 5008.0, 5009.0, 5010.0, 5011.0, 5012.0],      [5013.0, 5014.0, 5015.0, 5016.0, 5017.0, 5018.0, 5019.0],      [5020.0, 5021.0, 5022.0, 5023.0, 5024.0, 5025.0, 5026.0],      [5027.0, 5028.0, 5029.0, 5030.0, 5031.0, 5032.0, 5033.0],      [5034.0, 5035.0, 5036.0, 5037.0, 5038.0, 5039.0, 5040.0]]],    [[[5041.0, 5042.0, 5043.0, 5044.0, 5045.0, 5046.0, 5047.0],      [5048.0, 5049.0, 5050.0, 5051.0, 5052.0, 5053.0, 5054.0],      [5055.0, 5056.0, 5057.0, 5058.0, 5059.0, 5060.0, 5061.0],      [5062.0, 5063.0, 5064.0, 5065.0, 5066.0, 5067.0, 5068.0],      [5069.0, 5070.0, 5071.0, 5072.0, 5073.0, 5074.0, 5075.0],      [5076.0, 5077.0, 5078.0, 5079.0, 5080.0, 5081.0, 5082.0]],     [[5083.0, 5084.0, 5085.0, 5086.0, 5087.0, 5088.0, 5089.0],      [5090.0, 5091.0, 5092.0, 5093.0, 5094.0, 5095.0, 5096.0],      [5097.0, 5098.0, 5099.0, 5100.0, 5101.0, 5102.0, 5103.0],      [5104.0, 5105.0, 5106.0, 5107.0, 5108.0, 5109.0, 5110.0],      [5111.0, 5112.0, 5113.0, 5114.0, 5115.0, 5116.0, 5117.0],      [5118.0, 5119.0, 5120.0, 5121.0, 5122.0, 5123.0, 5124.0]],     [[5125.0, 5126.0, 5127.0, 5128.0, 5129.0, 5130.0, 5131.0],      [5132.0, 5133.0, 5134.0, 5135.0, 5136.0, 5137.0, 5138.0],      [5139.0, 5140.0, 5141.0, 5142.0, 5143.0, 5144.0, 5145.0],      [5146.0, 5147.0, 5148.0, 5149.0, 5150.0, 5151.0, 5152.0],      [5153.0, 5154.0, 5155.0, 5156.0, 5157.0, 5158.0, 5159.0],      [5160.0, 5161.0, 5162.0, 5163.0, 5164.0, 5165.0, 5166.0]]],    [[[5167.0, 5168.0, 5169.0, 5170.0, 5171.0, 5172.0, 5173.0],      [5174.0, 5175.0, 5176.0, 5177.0, 5178.0, 5179.0, 5180.0],      [5181.0, 5182.0, 5183.0, 5184.0, 5185.0, 5186.0, 5187.0],      [5188.0, 5189.0, 5190.0, 5191.0, 5192.0, 5193.0, 5194.0],      [5195.0, 5196.0, 5197.0, 5198.0, 5199.0, 5200.0, 5201.0],      [5202.0, 5203.0, 5204.0, 5205.0, 5206.0, 5207.0, 5208.0]],     [[5209.0, 5210.0, 5211.0, 5212.0, 5213.0, 5214.0, 5215.0],      [5216.0, 5217.0, 5218.0, 5219.0, 5220.0, 5221.0, 5222.0],      [5223.0, 5224.0, 5225.0, 5226.0, 5227.0, 5228.0, 5229.0],      [5230.0, 5231.0, 5232.0, 5233.0, 5234.0, 5235.0, 5236.0],      [5237.0, 5238.0, 5239.0, 5240.0, 5241.0, 5242.0, 5243.0],      [5244.0, 5245.0, 5246.0, 5247.0, 5248.0, 5249.0, 5250.0]],     [[5251.0, 5252.0, 5253.0, 5254.0, 5255.0, 5256.0, 5257.0],      [5258.0, 5259.0, 5260.0, 5261.0, 5262.0, 5263.0, 5264.0],      [5265.0, 5266.0, 5267.0, 5268.0, 5269.0, 5270.0, 5271.0],      [5272.0, 5273.0, 5274.0, 5275.0, 5276.0, 5277.0, 5278.0],      [5279.0, 5280.0, 5281.0, 5282.0, 5283.0, 5284.0, 5285.0],      [5286.0, 5287.0, 5288.0, 5289.0, 5290.0, 5291.0, 5292.0]]]],   [[[[5293.0, 5294.0, 5295.0, 5296.0, 5297.0, 5298.0, 5299.0],      [5300.0, 5301.0, 5302.0, 5303.0, 5304.0, 5305.0, 5306.0],      [5307.0, 5308.0, 5309.0, 5310.0, 5311.0, 5312.0, 5313.0],      [5314.0, 5315.0, 5316.0, 5317.0, 5318.0, 5319.0, 5320.0],      [5321.0, 5322.0, 5323.0, 5324.0, 5325.0, 5326.0, 5327.0],      [5328.0, 5329.0, 5330.0, 5331.0, 5332.0, 5333.0, 5334.0]],     [[5335.0, 5336.0, 5337.0, 5338.0, 5339.0, 5340.0, 5341.0],      [5342.0, 5343.0, 5344.0, 5345.0, 5346.0, 5347.0, 5348.0],      [5349.0, 5350.0, 5351.0, 5352.0, 5353.0, 5354.0, 5355.0],      [5356.0, 5357.0, 5358.0, 5359.0, 5360.0, 5361.0, 5362.0],      [5363.0, 5364.0, 5365.0, 5366.0, 5367.0, 5368.0, 5369.0],      [5370.0, 5371.0, 5372.0, 5373.0, 5374.0, 5375.0, 5376.0]],     [[5377.0, 5378.0, 5379.0, 5380.0, 5381.0, 5382.0, 5383.0],      [5384.0, 5385.0, 5386.0, 5387.0, 5388.0, 5389.0, 5390.0],      [5391.0, 5392.0, 5393.0, 5394.0, 5395.0, 5396.0, 5397.0],      [5398.0, 5399.0, 5400.0, 5401.0, 5402.0, 5403.0, 5404.0],      [5405.0, 5406.0, 5407.0, 5408.0, 5409.0, 5410.0, 5411.0],      [5412.0, 5413.0, 5414.0, 5415.0, 5416.0, 5417.0, 5418.0]]],    [[[5419.0, 5420.0, 5421.0, 5422.0, 5423.0, 5424.0, 5425.0],      [5426.0, 5427.0, 5428.0, 5429.0, 5430.0, 5431.0, 5432.0],      [5433.0, 5434.0, 5435.0, 5436.0, 5437.0, 5438.0, 5439.0],      [5440.0, 5441.0, 5442.0, 5443.0, 5444.0, 5445.0, 5446.0],      [5447.0, 5448.0, 5449.0, 5450.0, 5451.0, 5452.0, 5453.0],      [5454.0, 5455.0, 5456.0, 5457.0, 5458.0, 5459.0, 5460.0]],     [[5461.0, 5462.0, 5463.0, 5464.0, 5465.0, 5466.0, 5467.0],      [5468.0, 5469.0, 5470.0, 5471.0, 5472.0, 5473.0, 5474.0],      [5475.0, 5476.0, 5477.0, 5478.0, 5479.0, 5480.0, 5481.0],      [5482.0, 5483.0, 5484.0, 5485.0, 5486.0, 5487.0, 5488.0],      [5489.0, 5490.0, 5491.0, 5492.0, 5493.0, 5494.0, 5495.0],      [5496.0, 5497.0, 5498.0, 5499.0, 5500.0, 5501.0, 5502.0]],     [[5503.0, 5504.0, 5505.0, 5506.0, 5507.0, 5508.0, 5509.0],      [5510.0, 5511.0, 5512.0, 5513.0, 5514.0, 5515.0, 5516.0],      [5517.0, 5518.0, 5519.0, 5520.0, 5521.0, 5522.0, 5523.0],      [5524.0, 5525.0, 5526.0, 5527.0, 5528.0, 5529.0, 5530.0],      [5531.0, 5532.0, 5533.0, 5534.0, 5535.0, 5536.0, 5537.0],      [5538.0, 5539.0, 5540.0, 5541.0, 5542.0, 5543.0, 5544.0]]],    [[[5545.0, 5546.0, 5547.0, 5548.0, 5549.0, 5550.0, 5551.0],      [5552.0, 5553.0, 5554.0, 5555.0, 5556.0, 5557.0, 5558.0],      [5559.0, 5560.0, 5561.0, 5562.0, 5563.0, 5564.0, 5565.0],      [5566.0, 5567.0, 5568.0, 5569.0, 5570.0, 5571.0, 5572.0],      [5573.0, 5574.0, 5575.0, 5576.0, 5577.0, 5578.0, 5579.0],      [5580.0, 5581.0, 5582.0, 5583.0, 5584.0, 5585.0, 5586.0]],     [[5587.0, 5588.0, 5589.0, 5590.0, 5591.0, 5592.0, 5593.0],      [5594.0, 5595.0, 5596.0, 5597.0, 5598.0, 5599.0, 5600.0],      [5601.0, 5602.0, 5603.0, 5604.0, 5605.0, 5606.0, 5607.0],      [5608.0, 5609.0, 5610.0, 5611.0, 5612.0, 5613.0, 5614.0],      [5615.0, 5616.0, 5617.0, 5618.0, 5619.0, 5620.0, 5621.0],      [5622.0, 5623.0, 5624.0, 5625.0, 5626.0, 5627.0, 5628.0]],     [[5629.0, 5630.0, 5631.0, 5632.0, 5633.0, 5634.0, 5635.0],      [5636.0, 5637.0, 5638.0, 5639.0, 5640.0, 5641.0, 5642.0],      [5643.0, 5644.0, 5645.0, 5646.0, 5647.0, 5648.0, 5649.0],      [5650.0, 5651.0, 5652.0, 5653.0, 5654.0, 5655.0, 5656.0],      [5657.0, 5658.0, 5659.0, 5660.0, 5661.0, 5662.0, 5663.0],      [5664.0, 5665.0, 5666.0, 5667.0, 5668.0, 5669.0, 5670.0]]]]]] shape=[3, 5, 3, 3, 6, 7], strides=[1890, 378, 126, 42, 7, 1], layout=C (0x1)), I32([3, 1] shape=[2], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [2, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 3199994800 3258041763 205857558 1701255492 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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, 16.0],     [17.0, 18.0]],    [[19.0, 20.0],     [21.0, 22.0],     [23.0, 24.0]],    [[25.0, 26.0],     [27.0, 28.0],     [29.0, 30.0]],    [[31.0, 32.0],     [33.0, 34.0],     [35.0, 36.0]]],   [[[37.0, 38.0],     [39.0, 40.0],     [41.0, 42.0]],    [[43.0, 44.0],     [45.0, 46.0],     [47.0, 48.0]],    [[49.0, 50.0],     [51.0, 52.0],     [53.0, 54.0]],    [[55.0, 56.0],     [57.0, 58.0],     [59.0, 60.0]],    [[61.0, 62.0],     [63.0, 64.0],     [65.0, 66.0]],    [[67.0, 68.0],     [69.0, 70.0],     [71.0, 72.0]]],   [[[73.0, 74.0],     [75.0, 76.0],     [77.0, 78.0]],    [[79.0, 80.0],     [81.0, 82.0],     [83.0, 84.0]],    [[85.0, 86.0],     [87.0, 88.0],     [89.0, 90.0]],    [[91.0, 92.0],     [93.0, 94.0],     [95.0, 96.0]],    [[97.0, 98.0],     [99.0, 100.0],     [101.0, 102.0]],    [[103.0, 104.0],     [105.0, 106.0],     [107.0, 108.0]]],   [[[109.0, 110.0],     [111.0, 112.0],     [113.0, 114.0]],    [[115.0, 116.0],     [117.0, 118.0],     [119.0, 120.0]],    [[121.0, 122.0],     [123.0, 124.0],     [125.0, 126.0]],    [[127.0, 128.0],     [129.0, 130.0],     [131.0, 132.0]],    [[133.0, 134.0],     [135.0, 136.0],     [137.0, 138.0]],    [[139.0, 140.0],     [141.0, 142.0],     [143.0, 144.0]]],   [[[145.0, 146.0],     [147.0, 148.0],     [149.0, 150.0]],    [[151.0, 152.0],     [153.0, 154.0],     [155.0, 156.0]],    [[157.0, 158.0],     [159.0, 160.0],     [161.0, 162.0]],    [[163.0, 164.0],     [165.0, 166.0],     [167.0, 168.0]],    [[169.0, 170.0],     [171.0, 172.0],     [173.0, 174.0]],    [[175.0, 176.0],     [177.0, 178.0],     [179.0, 180.0]]],   [[[181.0, 182.0],     [183.0, 184.0],     [185.0, 186.0]],    [[187.0, 188.0],     [189.0, 190.0],     [191.0, 192.0]],    [[193.0, 194.0],     [195.0, 196.0],     [197.0, 198.0]],    [[199.0, 200.0],     [201.0, 202.0],     [203.0, 204.0]],    [[205.0, 206.0],     [207.0, 208.0],     [209.0, 210.0]],    [[211.0, 212.0],     [213.0, 214.0],     [215.0, 216.0]]],   [[[217.0, 218.0],     [219.0, 220.0],     [221.0, 222.0]],    [[223.0, 224.0],     [225.0, 226.0],     [227.0, 228.0]],    [[229.0, 230.0],     [231.0, 232.0],     [233.0, 234.0]],    [[235.0, 236.0],     [237.0, 238.0],     [239.0, 240.0]],    [[241.0, 242.0],     [243.0, 244.0],     [245.0, 246.0]],    [[247.0, 248.0],     [249.0, 250.0],     [251.0, 252.0]]]],  [[[[253.0, 254.0],     [255.0, 256.0],     [257.0, 258.0]],    [[259.0, 260.0],     [261.0, 262.0],     [263.0, 264.0]],    [[265.0, 266.0],     [267.0, 268.0],     [269.0, 270.0]],    [[271.0, 272.0],     [273.0, 274.0],     [275.0, 276.0]],    [[277.0, 278.0],     [279.0, 280.0],     [281.0, 282.0]],    [[283.0, 284.0],     [285.0, 286.0],     [287.0, 288.0]]],   [[[289.0, 290.0],     [291.0, 292.0],     [293.0, 294.0]],    [[295.0, 296.0],     [297.0, 298.0],     [299.0, 300.0]],    [[301.0, 302.0],     [303.0, 304.0],     [305.0, 306.0]],    [[307.0, 308.0],     [309.0, 310.0],     [311.0, 312.0]],    [[313.0, 314.0],     [315.0, 316.0],     [317.0, 318.0]],    [[319.0, 320.0],     [321.0, 322.0],     [323.0, 324.0]]],   [[[325.0, 326.0],     [327.0, 328.0],     [329.0, 330.0]],    [[331.0, 332.0],     [333.0, 334.0],     [335.0, 336.0]],    [[337.0, 338.0],     [339.0, 340.0],     [341.0, 342.0]],    [[343.0, 344.0],     [345.0, 346.0],     [347.0, 348.0]],    [[349.0, 350.0],     [351.0, 352.0],     [353.0, 354.0]],    [[355.0, 356.0],     [357.0, 358.0],     [359.0, 360.0]]],   [[[361.0, 362.0],     [363.0, 364.0],     [365.0, 366.0]],    [[367.0, 368.0],     [369.0, 370.0],     [371.0, 372.0]],    [[373.0, 374.0],     [375.0, 376.0],     [377.0, 378.0]],    [[379.0, 380.0],     [381.0, 382.0],     [383.0, 384.0]],    [[385.0, 386.0],     [387.0, 388.0],     [389.0, 390.0]],    [[391.0, 392.0],     [393.0, 394.0],     [395.0, 396.0]]],   [[[397.0, 398.0],     [399.0, 400.0],     [401.0, 402.0]],    [[403.0, 404.0],     [405.0, 406.0],     [407.0, 408.0]],    [[409.0, 410.0],     [411.0, 412.0],     [413.0, 414.0]],    [[415.0, 416.0],     [417.0, 418.0],     [419.0, 420.0]],    [[421.0, 422.0],     [423.0, 424.0],     [425.0, 426.0]],    [[427.0, 428.0],     [429.0, 430.0],     [431.0, 432.0]]],   [[[433.0, 434.0],     [435.0, 436.0],     [437.0, 438.0]],    [[439.0, 440.0],     [441.0, 442.0],     [443.0, 444.0]],    [[445.0, 446.0],     [447.0, 448.0],     [449.0, 450.0]],    [[451.0, 452.0],     [453.0, 454.0],     [455.0, 456.0]],    [[457.0, 458.0],     [459.0, 460.0],     [461.0, 462.0]],    [[463.0, 464.0],     [465.0, 466.0],     [467.0, 468.0]]],   [[[469.0, 470.0],     [471.0, 472.0],     [473.0, 474.0]],    [[475.0, 476.0],     [477.0, 478.0],     [479.0, 480.0]],    [[481.0, 482.0],     [483.0, 484.0],     [485.0, 486.0]],    [[487.0, 488.0],     [489.0, 490.0],     [491.0, 492.0]],    [[493.0, 494.0],     [495.0, 496.0],     [497.0, 498.0]],    [[499.0, 500.0],     [501.0, 502.0],     [503.0, 504.0]]]]] shape=[2, 7, 6, 3, 2], strides=[252, 36, 6, 2, 1], layout=C (0x1)), I32([2, 3, 3] shape=[3], strides=[1], layout=C | F (0x3)), I32([[2, 1],  [3, 3],  [0, 3]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 3945896962 1778438073 3860423294 3447446989 # shrinks to (ref i, ref bs, ref p) = (F32([[[[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],    [16.0, 17.0, 18.0, 19.0, 20.0],    [21.0, 22.0, 23.0, 24.0, 25.0]],   [[26.0, 27.0, 28.0, 29.0, 30.0],    [31.0, 32.0, 33.0, 34.0, 35.0],    [36.0, 37.0, 38.0, 39.0, 40.0],    [41.0, 42.0, 43.0, 44.0, 45.0],    [46.0, 47.0, 48.0, 49.0, 50.0]],   [[51.0, 52.0, 53.0, 54.0, 55.0],    [56.0, 57.0, 58.0, 59.0, 60.0],    [61.0, 62.0, 63.0, 64.0, 65.0],    [66.0, 67.0, 68.0, 69.0, 70.0],    [71.0, 72.0, 73.0, 74.0, 75.0]]],  [[[76.0, 77.0, 78.0, 79.0, 80.0],    [81.0, 82.0, 83.0, 84.0, 85.0],    [86.0, 87.0, 88.0, 89.0, 90.0],    [91.0, 92.0, 93.0, 94.0, 95.0],    [96.0, 97.0, 98.0, 99.0, 100.0]],   [[101.0, 102.0, 103.0, 104.0, 105.0],    [106.0, 107.0, 108.0, 109.0, 110.0],    [111.0, 112.0, 113.0, 114.0, 115.0],    [116.0, 117.0, 118.0, 119.0, 120.0],    [121.0, 122.0, 123.0, 124.0, 125.0]],   [[126.0, 127.0, 128.0, 129.0, 130.0],    [131.0, 132.0, 133.0, 134.0, 135.0],    [136.0, 137.0, 138.0, 139.0, 140.0],    [141.0, 142.0, 143.0, 144.0, 145.0],    [146.0, 147.0, 148.0, 149.0, 150.0]]],  [[[151.0, 152.0, 153.0, 154.0, 155.0],    [156.0, 157.0, 158.0, 159.0, 160.0],    [161.0, 162.0, 163.0, 164.0, 165.0],    [166.0, 167.0, 168.0, 169.0, 170.0],    [171.0, 172.0, 173.0, 174.0, 175.0]],   [[176.0, 177.0, 178.0, 179.0, 180.0],    [181.0, 182.0, 183.0, 184.0, 185.0],    [186.0, 187.0, 188.0, 189.0, 190.0],    [191.0, 192.0, 193.0, 194.0, 195.0],    [196.0, 197.0, 198.0, 199.0, 200.0]],   [[201.0, 202.0, 203.0, 204.0, 205.0],    [206.0, 207.0, 208.0, 209.0, 210.0],    [211.0, 212.0, 213.0, 214.0, 215.0],    [216.0, 217.0, 218.0, 219.0, 220.0],    [221.0, 222.0, 223.0, 224.0, 225.0]]]] shape=[3, 3, 5, 5], strides=[75, 25, 5, 1], layout=C (0x1)), I32([3, 1] shape=[2], strides=[1], layout=C | F (0x3)), I32([[0, 3],  [0, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 1830892940 1323159383 1761980141 1935468957 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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, 16.0]]],     [[[17.0, 18.0],       [19.0, 20.0]],      [[21.0, 22.0],       [23.0, 24.0]]],     [[[25.0, 26.0],       [27.0, 28.0]],      [[29.0, 30.0],       [31.0, 32.0]]],     [[[33.0, 34.0],       [35.0, 36.0]],      [[37.0, 38.0],       [39.0, 40.0]]],     [[[41.0, 42.0],       [43.0, 44.0]],      [[45.0, 46.0],       [47.0, 48.0]]],     [[[49.0, 50.0],       [51.0, 52.0]],      [[53.0, 54.0],       [55.0, 56.0]]]],    [[[[57.0, 58.0],       [59.0, 60.0]],      [[61.0, 62.0],       [63.0, 64.0]]],     [[[65.0, 66.0],       [67.0, 68.0]],      [[69.0, 70.0],       [71.0, 72.0]]],     [[[73.0, 74.0],       [75.0, 76.0]],      [[77.0, 78.0],       [79.0, 80.0]]],     [[[81.0, 82.0],       [83.0, 84.0]],      [[85.0, 86.0],       [87.0, 88.0]]],     [[[89.0, 90.0],       [91.0, 92.0]],      [[93.0, 94.0],       [95.0, 96.0]]],     [[[97.0, 98.0],       [99.0, 100.0]],      [[101.0, 102.0],       [103.0, 104.0]]],     [[[105.0, 106.0],       [107.0, 108.0]],      [[109.0, 110.0],       [111.0, 112.0]]]],    [[[[113.0, 114.0],       [115.0, 116.0]],      [[117.0, 118.0],       [119.0, 120.0]]],     [[[121.0, 122.0],       [123.0, 124.0]],      [[125.0, 126.0],       [127.0, 128.0]]],     [[[129.0, 130.0],       [131.0, 132.0]],      [[133.0, 134.0],       [135.0, 136.0]]],     [[[137.0, 138.0],       [139.0, 140.0]],      [[141.0, 142.0],       [143.0, 144.0]]],     [[[145.0, 146.0],       [147.0, 148.0]],      [[149.0, 150.0],       [151.0, 152.0]]],     [[[153.0, 154.0],       [155.0, 156.0]],      [[157.0, 158.0],       [159.0, 160.0]]],     [[[161.0, 162.0],       [163.0, 164.0]],      [[165.0, 166.0],       [167.0, 168.0]]]],    [[[[169.0, 170.0],       [171.0, 172.0]],      [[173.0, 174.0],       [175.0, 176.0]]],     [[[177.0, 178.0],       [179.0, 180.0]],      [[181.0, 182.0],       [183.0, 184.0]]],     [[[185.0, 186.0],       [187.0, 188.0]],      [[189.0, 190.0],       [191.0, 192.0]]],     [[[193.0, 194.0],       [195.0, 196.0]],      [[197.0, 198.0],       [199.0, 200.0]]],     [[[201.0, 202.0],       [203.0, 204.0]],      [[205.0, 206.0],       [207.0, 208.0]]],     [[[209.0, 210.0],       [211.0, 212.0]],      [[213.0, 214.0],       [215.0, 216.0]]],     [[[217.0, 218.0],       [219.0, 220.0]],      [[221.0, 222.0],       [223.0, 224.0]]]],    [[[[225.0, 226.0],       [227.0, 228.0]],      [[229.0, 230.0],       [231.0, 232.0]]],     [[[233.0, 234.0],       [235.0, 236.0]],      [[237.0, 238.0],       [239.0, 240.0]]],     [[[241.0, 242.0],       [243.0, 244.0]],      [[245.0, 246.0],       [247.0, 248.0]]],     [[[249.0, 250.0],       [251.0, 252.0]],      [[253.0, 254.0],       [255.0, 256.0]]],     [[[257.0, 258.0],       [259.0, 260.0]],      [[261.0, 262.0],       [263.0, 264.0]]],     [[[265.0, 266.0],       [267.0, 268.0]],      [[269.0, 270.0],       [271.0, 272.0]]],     [[[273.0, 274.0],       [275.0, 276.0]],      [[277.0, 278.0],       [279.0, 280.0]]]],    [[[[281.0, 282.0],       [283.0, 284.0]],      [[285.0, 286.0],       [287.0, 288.0]]],     [[[289.0, 290.0],       [291.0, 292.0]],      [[293.0, 294.0],       [295.0, 296.0]]],     [[[297.0, 298.0],       [299.0, 300.0]],      [[301.0, 302.0],       [303.0, 304.0]]],     [[[305.0, 306.0],       [307.0, 308.0]],      [[309.0, 310.0],       [311.0, 312.0]]],     [[[313.0, 314.0],       [315.0, 316.0]],      [[317.0, 318.0],       [319.0, 320.0]]],     [[[321.0, 322.0],       [323.0, 324.0]],      [[325.0, 326.0],       [327.0, 328.0]]],     [[[329.0, 330.0],       [331.0, 332.0]],      [[333.0, 334.0],       [335.0, 336.0]]]]],   [[[[[337.0, 338.0],       [339.0, 340.0]],      [[341.0, 342.0],       [343.0, 344.0]]],     [[[345.0, 346.0],       [347.0, 348.0]],      [[349.0, 350.0],       [351.0, 352.0]]],     [[[353.0, 354.0],       [355.0, 356.0]],      [[357.0, 358.0],       [359.0, 360.0]]],     [[[361.0, 362.0],       [363.0, 364.0]],      [[365.0, 366.0],       [367.0, 368.0]]],     [[[369.0, 370.0],       [371.0, 372.0]],      [[373.0, 374.0],       [375.0, 376.0]]],     [[[377.0, 378.0],       [379.0, 380.0]],      [[381.0, 382.0],       [383.0, 384.0]]],     [[[385.0, 386.0],       [387.0, 388.0]],      [[389.0, 390.0],       [391.0, 392.0]]]],    [[[[393.0, 394.0],       [395.0, 396.0]],      [[397.0, 398.0],       [399.0, 400.0]]],     [[[401.0, 402.0],       [403.0, 404.0]],      [[405.0, 406.0],       [407.0, 408.0]]],     [[[409.0, 410.0],       [411.0, 412.0]],      [[413.0, 414.0],       [415.0, 416.0]]],     [[[417.0, 418.0],       [419.0, 420.0]],      [[421.0, 422.0],       [423.0, 424.0]]],     [[[425.0, 426.0],       [427.0, 428.0]],      [[429.0, 430.0],       [431.0, 432.0]]],     [[[433.0, 434.0],       [435.0, 436.0]],      [[437.0, 438.0],       [439.0, 440.0]]],     [[[441.0, 442.0],       [443.0, 444.0]],      [[445.0, 446.0],       [447.0, 448.0]]]],    [[[[449.0, 450.0],       [451.0, 452.0]],      [[453.0, 454.0],       [455.0, 456.0]]],     [[[457.0, 458.0],       [459.0, 460.0]],      [[461.0, 462.0],       [463.0, 464.0]]],     [[[465.0, 466.0],       [467.0, 468.0]],      [[469.0, 470.0],       [471.0, 472.0]]],     [[[473.0, 474.0],       [475.0, 476.0]],      [[477.0, 478.0],       [479.0, 480.0]]],     [[[481.0, 482.0],       [483.0, 484.0]],      [[485.0, 486.0],       [487.0, 488.0]]],     [[[489.0, 490.0],       [491.0, 492.0]],      [[493.0, 494.0],       [495.0, 496.0]]],     [[[497.0, 498.0],       [499.0, 500.0]],      [[501.0, 502.0],       [503.0, 504.0]]]],    [[[[505.0, 506.0],       [507.0, 508.0]],      [[509.0, 510.0],       [511.0, 512.0]]],     [[[513.0, 514.0],       [515.0, 516.0]],      [[517.0, 518.0],       [519.0, 520.0]]],     [[[521.0, 522.0],       [523.0, 524.0]],      [[525.0, 526.0],       [527.0, 528.0]]],     [[[529.0, 530.0],       [531.0, 532.0]],      [[533.0, 534.0],       [535.0, 536.0]]],     [[[537.0, 538.0],       [539.0, 540.0]],      [[541.0, 542.0],       [543.0, 544.0]]],     [[[545.0, 546.0],       [547.0, 548.0]],      [[549.0, 550.0],       [551.0, 552.0]]],     [[[553.0, 554.0],       [555.0, 556.0]],      [[557.0, 558.0],       [559.0, 560.0]]]],    [[[[561.0, 562.0],       [563.0, 564.0]],      [[565.0, 566.0],       [567.0, 568.0]]],     [[[569.0, 570.0],       [571.0, 572.0]],      [[573.0, 574.0],       [575.0, 576.0]]],     [[[577.0, 578.0],       [579.0, 580.0]],      [[581.0, 582.0],       [583.0, 584.0]]],     [[[585.0, 586.0],       [587.0, 588.0]],      [[589.0, 590.0],       [591.0, 592.0]]],     [[[593.0, 594.0],       [595.0, 596.0]],      [[597.0, 598.0],       [599.0, 600.0]]],     [[[601.0, 602.0],       [603.0, 604.0]],      [[605.0, 606.0],       [607.0, 608.0]]],     [[[609.0, 610.0],       [611.0, 612.0]],      [[613.0, 614.0],       [615.0, 616.0]]]],    [[[[617.0, 618.0],       [619.0, 620.0]],      [[621.0, 622.0],       [623.0, 624.0]]],     [[[625.0, 626.0],       [627.0, 628.0]],      [[629.0, 630.0],       [631.0, 632.0]]],     [[[633.0, 634.0],       [635.0, 636.0]],      [[637.0, 638.0],       [639.0, 640.0]]],     [[[641.0, 642.0],       [643.0, 644.0]],      [[645.0, 646.0],       [647.0, 648.0]]],     [[[649.0, 650.0],       [651.0, 652.0]],      [[653.0, 654.0],       [655.0, 656.0]]],     [[[657.0, 658.0],       [659.0, 660.0]],      [[661.0, 662.0],       [663.0, 664.0]]],     [[[665.0, 666.0],       [667.0, 668.0]],      [[669.0, 670.0],       [671.0, 672.0]]]]]]] shape=[1, 2, 6, 7, 2, 2, 2], strides=[672, 336, 56, 8, 4, 2, 1], layout=C (0x1)), I32([2, 3, 1] shape=[3], strides=[1], layout=C | F (0x3)), I32([[2, 2],  [0, 3],  [1, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 603688422 2356550593 511821555 3646878586 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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],     [16.0, 17.0, 18.0, 19.0, 20.0]],    [[21.0, 22.0, 23.0, 24.0, 25.0],     [26.0, 27.0, 28.0, 29.0, 30.0]]],   [[[31.0, 32.0, 33.0, 34.0, 35.0],     [36.0, 37.0, 38.0, 39.0, 40.0]],    [[41.0, 42.0, 43.0, 44.0, 45.0],     [46.0, 47.0, 48.0, 49.0, 50.0]],    [[51.0, 52.0, 53.0, 54.0, 55.0],     [56.0, 57.0, 58.0, 59.0, 60.0]]],   [[[61.0, 62.0, 63.0, 64.0, 65.0],     [66.0, 67.0, 68.0, 69.0, 70.0]],    [[71.0, 72.0, 73.0, 74.0, 75.0],     [76.0, 77.0, 78.0, 79.0, 80.0]],    [[81.0, 82.0, 83.0, 84.0, 85.0],     [86.0, 87.0, 88.0, 89.0, 90.0]]],   [[[91.0, 92.0, 93.0, 94.0, 95.0],     [96.0, 97.0, 98.0, 99.0, 100.0]],    [[101.0, 102.0, 103.0, 104.0, 105.0],     [106.0, 107.0, 108.0, 109.0, 110.0]],    [[111.0, 112.0, 113.0, 114.0, 115.0],     [116.0, 117.0, 118.0, 119.0, 120.0]]]],  [[[[121.0, 122.0, 123.0, 124.0, 125.0],     [126.0, 127.0, 128.0, 129.0, 130.0]],    [[131.0, 132.0, 133.0, 134.0, 135.0],     [136.0, 137.0, 138.0, 139.0, 140.0]],    [[141.0, 142.0, 143.0, 144.0, 145.0],     [146.0, 147.0, 148.0, 149.0, 150.0]]],   [[[151.0, 152.0, 153.0, 154.0, 155.0],     [156.0, 157.0, 158.0, 159.0, 160.0]],    [[161.0, 162.0, 163.0, 164.0, 165.0],     [166.0, 167.0, 168.0, 169.0, 170.0]],    [[171.0, 172.0, 173.0, 174.0, 175.0],     [176.0, 177.0, 178.0, 179.0, 180.0]]],   [[[181.0, 182.0, 183.0, 184.0, 185.0],     [186.0, 187.0, 188.0, 189.0, 190.0]],    [[191.0, 192.0, 193.0, 194.0, 195.0],     [196.0, 197.0, 198.0, 199.0, 200.0]],    [[201.0, 202.0, 203.0, 204.0, 205.0],     [206.0, 207.0, 208.0, 209.0, 210.0]]],   [[[211.0, 212.0, 213.0, 214.0, 215.0],     [216.0, 217.0, 218.0, 219.0, 220.0]],    [[221.0, 222.0, 223.0, 224.0, 225.0],     [226.0, 227.0, 228.0, 229.0, 230.0]],    [[231.0, 232.0, 233.0, 234.0, 235.0],     [236.0, 237.0, 238.0, 239.0, 240.0]]]],  [[[[241.0, 242.0, 243.0, 244.0, 245.0],     [246.0, 247.0, 248.0, 249.0, 250.0]],    [[251.0, 252.0, 253.0, 254.0, 255.0],     [256.0, 257.0, 258.0, 259.0, 260.0]],    [[261.0, 262.0, 263.0, 264.0, 265.0],     [266.0, 267.0, 268.0, 269.0, 270.0]]],   [[[271.0, 272.0, 273.0, 274.0, 275.0],     [276.0, 277.0, 278.0, 279.0, 280.0]],    [[281.0, 282.0, 283.0, 284.0, 285.0],     [286.0, 287.0, 288.0, 289.0, 290.0]],    [[291.0, 292.0, 293.0, 294.0, 295.0],     [296.0, 297.0, 298.0, 299.0, 300.0]]],   [[[301.0, 302.0, 303.0, 304.0, 305.0],     [306.0, 307.0, 308.0, 309.0, 310.0]],    [[311.0, 312.0, 313.0, 314.0, 315.0],     [316.0, 317.0, 318.0, 319.0, 320.0]],    [[321.0, 322.0, 323.0, 324.0, 325.0],     [326.0, 327.0, 328.0, 329.0, 330.0]]],   [[[331.0, 332.0, 333.0, 334.0, 335.0],     [336.0, 337.0, 338.0, 339.0, 340.0]],    [[341.0, 342.0, 343.0, 344.0, 345.0],     [346.0, 347.0, 348.0, 349.0, 350.0]],    [[351.0, 352.0, 353.0, 354.0, 355.0],     [356.0, 357.0, 358.0, 359.0, 360.0]]]]] shape=[3, 4, 3, 2, 5], strides=[120, 30, 10, 5, 1], layout=C (0x1)), I32([2] shape=[1], strides=[1], layout=C | F (0x3)), I32([[1, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 4093654868 4061763967 881279373 2911816562 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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, 16.0]],      [[17.0, 18.0],       [19.0, 20.0],       [21.0, 22.0],       [23.0, 24.0]]],     [[[25.0, 26.0],       [27.0, 28.0],       [29.0, 30.0],       [31.0, 32.0]],      [[33.0, 34.0],       [35.0, 36.0],       [37.0, 38.0],       [39.0, 40.0]],      [[41.0, 42.0],       [43.0, 44.0],       [45.0, 46.0],       [47.0, 48.0]]],     [[[49.0, 50.0],       [51.0, 52.0],       [53.0, 54.0],       [55.0, 56.0]],      [[57.0, 58.0],       [59.0, 60.0],       [61.0, 62.0],       [63.0, 64.0]],      [[65.0, 66.0],       [67.0, 68.0],       [69.0, 70.0],       [71.0, 72.0]]],     [[[73.0, 74.0],       [75.0, 76.0],       [77.0, 78.0],       [79.0, 80.0]],      [[81.0, 82.0],       [83.0, 84.0],       [85.0, 86.0],       [87.0, 88.0]],      [[89.0, 90.0],       [91.0, 92.0],       [93.0, 94.0],       [95.0, 96.0]]],     [[[97.0, 98.0],       [99.0, 100.0],       [101.0, 102.0],       [103.0, 104.0]],      [[105.0, 106.0],       [107.0, 108.0],       [109.0, 110.0],       [111.0, 112.0]],      [[113.0, 114.0],       [115.0, 116.0],       [117.0, 118.0],       [119.0, 120.0]]]],    [[[[121.0, 122.0],       [123.0, 124.0],       [125.0, 126.0],       [127.0, 128.0]],      [[129.0, 130.0],       [131.0, 132.0],       [133.0, 134.0],       [135.0, 136.0]],      [[137.0, 138.0],       [139.0, 140.0],       [141.0, 142.0],       [143.0, 144.0]]],     [[[145.0, 146.0],       [147.0, 148.0],       [149.0, 150.0],       [151.0, 152.0]],      [[153.0, 154.0],       [155.0, 156.0],       [157.0, 158.0],       [159.0, 160.0]],      [[161.0, 162.0],       [163.0, 164.0],       [165.0, 166.0],       [167.0, 168.0]]],     [[[169.0, 170.0],       [171.0, 172.0],       [173.0, 174.0],       [175.0, 176.0]],      [[177.0, 178.0],       [179.0, 180.0],       [181.0, 182.0],       [183.0, 184.0]],      [[185.0, 186.0],       [187.0, 188.0],       [189.0, 190.0],       [191.0, 192.0]]],     [[[193.0, 194.0],       [195.0, 196.0],       [197.0, 198.0],       [199.0, 200.0]],      [[201.0, 202.0],       [203.0, 204.0],       [205.0, 206.0],       [207.0, 208.0]],      [[209.0, 210.0],       [211.0, 212.0],       [213.0, 214.0],       [215.0, 216.0]]],     [[[217.0, 218.0],       [219.0, 220.0],       [221.0, 222.0],       [223.0, 224.0]],      [[225.0, 226.0],       [227.0, 228.0],       [229.0, 230.0],       [231.0, 232.0]],      [[233.0, 234.0],       [235.0, 236.0],       [237.0, 238.0],       [239.0, 240.0]]]],    [[[[241.0, 242.0],       [243.0, 244.0],       [245.0, 246.0],       [247.0, 248.0]],      [[249.0, 250.0],       [251.0, 252.0],       [253.0, 254.0],       [255.0, 256.0]],      [[257.0, 258.0],       [259.0, 260.0],       [261.0, 262.0],       [263.0, 264.0]]],     [[[265.0, 266.0],       [267.0, 268.0],       [269.0, 270.0],       [271.0, 272.0]],      [[273.0, 274.0],       [275.0, 276.0],       [277.0, 278.0],       [279.0, 280.0]],      [[281.0, 282.0],       [283.0, 284.0],       [285.0, 286.0],       [287.0, 288.0]]],     [[[289.0, 290.0],       [291.0, 292.0],       [293.0, 294.0],       [295.0, 296.0]],      [[297.0, 298.0],       [299.0, 300.0],       [301.0, 302.0],       [303.0, 304.0]],      [[305.0, 306.0],       [307.0, 308.0],       [309.0, 310.0],       [311.0, 312.0]]],     [[[313.0, 314.0],       [315.0, 316.0],       [317.0, 318.0],       [319.0, 320.0]],      [[321.0, 322.0],       [323.0, 324.0],       [325.0, 326.0],       [327.0, 328.0]],      [[329.0, 330.0],       [331.0, 332.0],       [333.0, 334.0],       [335.0, 336.0]]],     [[[337.0, 338.0],       [339.0, 340.0],       [341.0, 342.0],       [343.0, 344.0]],      [[345.0, 346.0],       [347.0, 348.0],       [349.0, 350.0],       [351.0, 352.0]],      [[353.0, 354.0],       [355.0, 356.0],       [357.0, 358.0],       [359.0, 360.0]]]]],   [[[[[361.0, 362.0],       [363.0, 364.0],       [365.0, 366.0],       [367.0, 368.0]],      [[369.0, 370.0],       [371.0, 372.0],       [373.0, 374.0],       [375.0, 376.0]],      [[377.0, 378.0],       [379.0, 380.0],       [381.0, 382.0],       [383.0, 384.0]]],     [[[385.0, 386.0],       [387.0, 388.0],       [389.0, 390.0],       [391.0, 392.0]],      [[393.0, 394.0],       [395.0, 396.0],       [397.0, 398.0],       [399.0, 400.0]],      [[401.0, 402.0],       [403.0, 404.0],       [405.0, 406.0],       [407.0, 408.0]]],     [[[409.0, 410.0],       [411.0, 412.0],       [413.0, 414.0],       [415.0, 416.0]],      [[417.0, 418.0],       [419.0, 420.0],       [421.0, 422.0],       [423.0, 424.0]],      [[425.0, 426.0],       [427.0, 428.0],       [429.0, 430.0],       [431.0, 432.0]]],     [[[433.0, 434.0],       [435.0, 436.0],       [437.0, 438.0],       [439.0, 440.0]],      [[441.0, 442.0],       [443.0, 444.0],       [445.0, 446.0],       [447.0, 448.0]],      [[449.0, 450.0],       [451.0, 452.0],       [453.0, 454.0],       [455.0, 456.0]]],     [[[457.0, 458.0],       [459.0, 460.0],       [461.0, 462.0],       [463.0, 464.0]],      [[465.0, 466.0],       [467.0, 468.0],       [469.0, 470.0],       [471.0, 472.0]],      [[473.0, 474.0],       [475.0, 476.0],       [477.0, 478.0],       [479.0, 480.0]]]],    [[[[481.0, 482.0],       [483.0, 484.0],       [485.0, 486.0],       [487.0, 488.0]],      [[489.0, 490.0],       [491.0, 492.0],       [493.0, 494.0],       [495.0, 496.0]],      [[497.0, 498.0],       [499.0, 500.0],       [501.0, 502.0],       [503.0, 504.0]]],     [[[505.0, 506.0],       [507.0, 508.0],       [509.0, 510.0],       [511.0, 512.0]],      [[513.0, 514.0],       [515.0, 516.0],       [517.0, 518.0],       [519.0, 520.0]],      [[521.0, 522.0],       [523.0, 524.0],       [525.0, 526.0],       [527.0, 528.0]]],     [[[529.0, 530.0],       [531.0, 532.0],       [533.0, 534.0],       [535.0, 536.0]],      [[537.0, 538.0],       [539.0, 540.0],       [541.0, 542.0],       [543.0, 544.0]],      [[545.0, 546.0],       [547.0, 548.0],       [549.0, 550.0],       [551.0, 552.0]]],     [[[553.0, 554.0],       [555.0, 556.0],       [557.0, 558.0],       [559.0, 560.0]],      [[561.0, 562.0],       [563.0, 564.0],       [565.0, 566.0],       [567.0, 568.0]],      [[569.0, 570.0],       [571.0, 572.0],       [573.0, 574.0],       [575.0, 576.0]]],     [[[577.0, 578.0],       [579.0, 580.0],       [581.0, 582.0],       [583.0, 584.0]],      [[585.0, 586.0],       [587.0, 588.0],       [589.0, 590.0],       [591.0, 592.0]],      [[593.0, 594.0],       [595.0, 596.0],       [597.0, 598.0],       [599.0, 600.0]]]],    [[[[601.0, 602.0],       [603.0, 604.0],       [605.0, 606.0],       [607.0, 608.0]],      [[609.0, 610.0],       [611.0, 612.0],       [613.0, 614.0],       [615.0, 616.0]],      [[617.0, 618.0],       [619.0, 620.0],       [621.0, 622.0],       [623.0, 624.0]]],     [[[625.0, 626.0],       [627.0, 628.0],       [629.0, 630.0],       [631.0, 632.0]],      [[633.0, 634.0],       [635.0, 636.0],       [637.0, 638.0],       [639.0, 640.0]],      [[641.0, 642.0],       [643.0, 644.0],       [645.0, 646.0],       [647.0, 648.0]]],     [[[649.0, 650.0],       [651.0, 652.0],       [653.0, 654.0],       [655.0, 656.0]],      [[657.0, 658.0],       [659.0, 660.0],       [661.0, 662.0],       [663.0, 664.0]],      [[665.0, 666.0],       [667.0, 668.0],       [669.0, 670.0],       [671.0, 672.0]]],     [[[673.0, 674.0],       [675.0, 676.0],       [677.0, 678.0],       [679.0, 680.0]],      [[681.0, 682.0],       [683.0, 684.0],       [685.0, 686.0],       [687.0, 688.0]],      [[689.0, 690.0],       [691.0, 692.0],       [693.0, 694.0],       [695.0, 696.0]]],     [[[697.0, 698.0],       [699.0, 700.0],       [701.0, 702.0],       [703.0, 704.0]],      [[705.0, 706.0],       [707.0, 708.0],       [709.0, 710.0],       [711.0, 712.0]],      [[713.0, 714.0],       [715.0, 716.0],       [717.0, 718.0],       [719.0, 720.0]]]]],   [[[[[721.0, 722.0],       [723.0, 724.0],       [725.0, 726.0],       [727.0, 728.0]],      [[729.0, 730.0],       [731.0, 732.0],       [733.0, 734.0],       [735.0, 736.0]],      [[737.0, 738.0],       [739.0, 740.0],       [741.0, 742.0],       [743.0, 744.0]]],     [[[745.0, 746.0],       [747.0, 748.0],       [749.0, 750.0],       [751.0, 752.0]],      [[753.0, 754.0],       [755.0, 756.0],       [757.0, 758.0],       [759.0, 760.0]],      [[761.0, 762.0],       [763.0, 764.0],       [765.0, 766.0],       [767.0, 768.0]]],     [[[769.0, 770.0],       [771.0, 772.0],       [773.0, 774.0],       [775.0, 776.0]],      [[777.0, 778.0],       [779.0, 780.0],       [781.0, 782.0],       [783.0, 784.0]],      [[785.0, 786.0],       [787.0, 788.0],       [789.0, 790.0],       [791.0, 792.0]]],     [[[793.0, 794.0],       [795.0, 796.0],       [797.0, 798.0],       [799.0, 800.0]],      [[801.0, 802.0],       [803.0, 804.0],       [805.0, 806.0],       [807.0, 808.0]],      [[809.0, 810.0],       [811.0, 812.0],       [813.0, 814.0],       [815.0, 816.0]]],     [[[817.0, 818.0],       [819.0, 820.0],       [821.0, 822.0],       [823.0, 824.0]],      [[825.0, 826.0],       [827.0, 828.0],       [829.0, 830.0],       [831.0, 832.0]],      [[833.0, 834.0],       [835.0, 836.0],       [837.0, 838.0],       [839.0, 840.0]]]],    [[[[841.0, 842.0],       [843.0, 844.0],       [845.0, 846.0],       [847.0, 848.0]],      [[849.0, 850.0],       [851.0, 852.0],       [853.0, 854.0],       [855.0, 856.0]],      [[857.0, 858.0],       [859.0, 860.0],       [861.0, 862.0],       [863.0, 864.0]]],     [[[865.0, 866.0],       [867.0, 868.0],       [869.0, 870.0],       [871.0, 872.0]],      [[873.0, 874.0],       [875.0, 876.0],       [877.0, 878.0],       [879.0, 880.0]],      [[881.0, 882.0],       [883.0, 884.0],       [885.0, 886.0],       [887.0, 888.0]]],     [[[889.0, 890.0],       [891.0, 892.0],       [893.0, 894.0],       [895.0, 896.0]],      [[897.0, 898.0],       [899.0, 900.0],       [901.0, 902.0],       [903.0, 904.0]],      [[905.0, 906.0],       [907.0, 908.0],       [909.0, 910.0],       [911.0, 912.0]]],     [[[913.0, 914.0],       [915.0, 916.0],       [917.0, 918.0],       [919.0, 920.0]],      [[921.0, 922.0],       [923.0, 924.0],       [925.0, 926.0],       [927.0, 928.0]],      [[929.0, 930.0],       [931.0, 932.0],       [933.0, 934.0],       [935.0, 936.0]]],     [[[937.0, 938.0],       [939.0, 940.0],       [941.0, 942.0],       [943.0, 944.0]],      [[945.0, 946.0],       [947.0, 948.0],       [949.0, 950.0],       [951.0, 952.0]],      [[953.0, 954.0],       [955.0, 956.0],       [957.0, 958.0],       [959.0, 960.0]]]],    [[[[961.0, 962.0],       [963.0, 964.0],       [965.0, 966.0],       [967.0, 968.0]],      [[969.0, 970.0],       [971.0, 972.0],       [973.0, 974.0],       [975.0, 976.0]],      [[977.0, 978.0],       [979.0, 980.0],       [981.0, 982.0],       [983.0, 984.0]]],     [[[985.0, 986.0],       [987.0, 988.0],       [989.0, 990.0],       [991.0, 992.0]],      [[993.0, 994.0],       [995.0, 996.0],       [997.0, 998.0],       [999.0, 1000.0]],      [[1001.0, 1002.0],       [1003.0, 1004.0],       [1005.0, 1006.0],       [1007.0, 1008.0]]],     [[[1009.0, 1010.0],       [1011.0, 1012.0],       [1013.0, 1014.0],       [1015.0, 1016.0]],      [[1017.0, 1018.0],       [1019.0, 1020.0],       [1021.0, 1022.0],       [1023.0, 1024.0]],      [[1025.0, 1026.0],       [1027.0, 1028.0],       [1029.0, 1030.0],       [1031.0, 1032.0]]],     [[[1033.0, 1034.0],       [1035.0, 1036.0],       [1037.0, 1038.0],       [1039.0, 1040.0]],      [[1041.0, 1042.0],       [1043.0, 1044.0],       [1045.0, 1046.0],       [1047.0, 1048.0]],      [[1049.0, 1050.0],       [1051.0, 1052.0],       [1053.0, 1054.0],       [1055.0, 1056.0]]],     [[[1057.0, 1058.0],       [1059.0, 1060.0],       [1061.0, 1062.0],       [1063.0, 1064.0]],      [[1065.0, 1066.0],       [1067.0, 1068.0],       [1069.0, 1070.0],       [1071.0, 1072.0]],      [[1073.0, 1074.0],       [1075.0, 1076.0],       [1077.0, 1078.0],       [1079.0, 1080.0]]]]],   [[[[[1081.0, 1082.0],       [1083.0, 1084.0],       [1085.0, 1086.0],       [1087.0, 1088.0]],      [[1089.0, 1090.0],       [1091.0, 1092.0],       [1093.0, 1094.0],       [1095.0, 1096.0]],      [[1097.0, 1098.0],       [1099.0, 1100.0],       [1101.0, 1102.0],       [1103.0, 1104.0]]],     [[[1105.0, 1106.0],       [1107.0, 1108.0],       [1109.0, 1110.0],       [1111.0, 1112.0]],      [[1113.0, 1114.0],       [1115.0, 1116.0],       [1117.0, 1118.0],       [1119.0, 1120.0]],      [[1121.0, 1122.0],       [1123.0, 1124.0],       [1125.0, 1126.0],       [1127.0, 1128.0]]],     [[[1129.0, 1130.0],       [1131.0, 1132.0],       [1133.0, 1134.0],       [1135.0, 1136.0]],      [[1137.0, 1138.0],       [1139.0, 1140.0],       [1141.0, 1142.0],       [1143.0, 1144.0]],      [[1145.0, 1146.0],       [1147.0, 1148.0],       [1149.0, 1150.0],       [1151.0, 1152.0]]],     [[[1153.0, 1154.0],       [1155.0, 1156.0],       [1157.0, 1158.0],       [1159.0, 1160.0]],      [[1161.0, 1162.0],       [1163.0, 1164.0],       [1165.0, 1166.0],       [1167.0, 1168.0]],      [[1169.0, 1170.0],       [1171.0, 1172.0],       [1173.0, 1174.0],       [1175.0, 1176.0]]],     [[[1177.0, 1178.0],       [1179.0, 1180.0],       [1181.0, 1182.0],       [1183.0, 1184.0]],      [[1185.0, 1186.0],       [1187.0, 1188.0],       [1189.0, 1190.0],       [1191.0, 1192.0]],      [[1193.0, 1194.0],       [1195.0, 1196.0],       [1197.0, 1198.0],       [1199.0, 1200.0]]]],    [[[[1201.0, 1202.0],       [1203.0, 1204.0],       [1205.0, 1206.0],       [1207.0, 1208.0]],      [[1209.0, 1210.0],       [1211.0, 1212.0],       [1213.0, 1214.0],       [1215.0, 1216.0]],      [[1217.0, 1218.0],       [1219.0, 1220.0],       [1221.0, 1222.0],       [1223.0, 1224.0]]],     [[[1225.0, 1226.0],       [1227.0, 1228.0],       [1229.0, 1230.0],       [1231.0, 1232.0]],      [[1233.0, 1234.0],       [1235.0, 1236.0],       [1237.0, 1238.0],       [1239.0, 1240.0]],      [[1241.0, 1242.0],       [1243.0, 1244.0],       [1245.0, 1246.0],       [1247.0, 1248.0]]],     [[[1249.0, 1250.0],       [1251.0, 1252.0],       [1253.0, 1254.0],       [1255.0, 1256.0]],      [[1257.0, 1258.0],       [1259.0, 1260.0],       [1261.0, 1262.0],       [1263.0, 1264.0]],      [[1265.0, 1266.0],       [1267.0, 1268.0],       [1269.0, 1270.0],       [1271.0, 1272.0]]],     [[[1273.0, 1274.0],       [1275.0, 1276.0],       [1277.0, 1278.0],       [1279.0, 1280.0]],      [[1281.0, 1282.0],       [1283.0, 1284.0],       [1285.0, 1286.0],       [1287.0, 1288.0]],      [[1289.0, 1290.0],       [1291.0, 1292.0],       [1293.0, 1294.0],       [1295.0, 1296.0]]],     [[[1297.0, 1298.0],       [1299.0, 1300.0],       [1301.0, 1302.0],       [1303.0, 1304.0]],      [[1305.0, 1306.0],       [1307.0, 1308.0],       [1309.0, 1310.0],       [1311.0, 1312.0]],      [[1313.0, 1314.0],       [1315.0, 1316.0],       [1317.0, 1318.0],       [1319.0, 1320.0]]]],    [[[[1321.0, 1322.0],       [1323.0, 1324.0],       [1325.0, 1326.0],       [1327.0, 1328.0]],      [[1329.0, 1330.0],       [1331.0, 1332.0],       [1333.0, 1334.0],       [1335.0, 1336.0]],      [[1337.0, 1338.0],       [1339.0, 1340.0],       [1341.0, 1342.0],       [1343.0, 1344.0]]],     [[[1345.0, 1346.0],       [1347.0, 1348.0],       [1349.0, 1350.0],       [1351.0, 1352.0]],      [[1353.0, 1354.0],       [1355.0, 1356.0],       [1357.0, 1358.0],       [1359.0, 1360.0]],      [[1361.0, 1362.0],       [1363.0, 1364.0],       [1365.0, 1366.0],       [1367.0, 1368.0]]],     [[[1369.0, 1370.0],       [1371.0, 1372.0],       [1373.0, 1374.0],       [1375.0, 1376.0]],      [[1377.0, 1378.0],       [1379.0, 1380.0],       [1381.0, 1382.0],       [1383.0, 1384.0]],      [[1385.0, 1386.0],       [1387.0, 1388.0],       [1389.0, 1390.0],       [1391.0, 1392.0]]],     [[[1393.0, 1394.0],       [1395.0, 1396.0],       [1397.0, 1398.0],       [1399.0, 1400.0]],      [[1401.0, 1402.0],       [1403.0, 1404.0],       [1405.0, 1406.0],       [1407.0, 1408.0]],      [[1409.0, 1410.0],       [1411.0, 1412.0],       [1413.0, 1414.0],       [1415.0, 1416.0]]],     [[[1417.0, 1418.0],       [1419.0, 1420.0],       [1421.0, 1422.0],       [1423.0, 1424.0]],      [[1425.0, 1426.0],       [1427.0, 1428.0],       [1429.0, 1430.0],       [1431.0, 1432.0]],      [[1433.0, 1434.0],       [1435.0, 1436.0],       [1437.0, 1438.0],       [1439.0, 1440.0]]]]],   [[[[[1441.0, 1442.0],       [1443.0, 1444.0],       [1445.0, 1446.0],       [1447.0, 1448.0]],      [[1449.0, 1450.0],       [1451.0, 1452.0],       [1453.0, 1454.0],       [1455.0, 1456.0]],      [[1457.0, 1458.0],       [1459.0, 1460.0],       [1461.0, 1462.0],       [1463.0, 1464.0]]],     [[[1465.0, 1466.0],       [1467.0, 1468.0],       [1469.0, 1470.0],       [1471.0, 1472.0]],      [[1473.0, 1474.0],       [1475.0, 1476.0],       [1477.0, 1478.0],       [1479.0, 1480.0]],      [[1481.0, 1482.0],       [1483.0, 1484.0],       [1485.0, 1486.0],       [1487.0, 1488.0]]],     [[[1489.0, 1490.0],       [1491.0, 1492.0],       [1493.0, 1494.0],       [1495.0, 1496.0]],      [[1497.0, 1498.0],       [1499.0, 1500.0],       [1501.0, 1502.0],       [1503.0, 1504.0]],      [[1505.0, 1506.0],       [1507.0, 1508.0],       [1509.0, 1510.0],       [1511.0, 1512.0]]],     [[[1513.0, 1514.0],       [1515.0, 1516.0],       [1517.0, 1518.0],       [1519.0, 1520.0]],      [[1521.0, 1522.0],       [1523.0, 1524.0],       [1525.0, 1526.0],       [1527.0, 1528.0]],      [[1529.0, 1530.0],       [1531.0, 1532.0],       [1533.0, 1534.0],       [1535.0, 1536.0]]],     [[[1537.0, 1538.0],       [1539.0, 1540.0],       [1541.0, 1542.0],       [1543.0, 1544.0]],      [[1545.0, 1546.0],       [1547.0, 1548.0],       [1549.0, 1550.0],       [1551.0, 1552.0]],      [[1553.0, 1554.0],       [1555.0, 1556.0],       [1557.0, 1558.0],       [1559.0, 1560.0]]]],    [[[[1561.0, 1562.0],       [1563.0, 1564.0],       [1565.0, 1566.0],       [1567.0, 1568.0]],      [[1569.0, 1570.0],       [1571.0, 1572.0],       [1573.0, 1574.0],       [1575.0, 1576.0]],      [[1577.0, 1578.0],       [1579.0, 1580.0],       [1581.0, 1582.0],       [1583.0, 1584.0]]],     [[[1585.0, 1586.0],       [1587.0, 1588.0],       [1589.0, 1590.0],       [1591.0, 1592.0]],      [[1593.0, 1594.0],       [1595.0, 1596.0],       [1597.0, 1598.0],       [1599.0, 1600.0]],      [[1601.0, 1602.0],       [1603.0, 1604.0],       [1605.0, 1606.0],       [1607.0, 1608.0]]],     [[[1609.0, 1610.0],       [1611.0, 1612.0],       [1613.0, 1614.0],       [1615.0, 1616.0]],      [[1617.0, 1618.0],       [1619.0, 1620.0],       [1621.0, 1622.0],       [1623.0, 1624.0]],      [[1625.0, 1626.0],       [1627.0, 1628.0],       [1629.0, 1630.0],       [1631.0, 1632.0]]],     [[[1633.0, 1634.0],       [1635.0, 1636.0],       [1637.0, 1638.0],       [1639.0, 1640.0]],      [[1641.0, 1642.0],       [1643.0, 1644.0],       [1645.0, 1646.0],       [1647.0, 1648.0]],      [[1649.0, 1650.0],       [1651.0, 1652.0],       [1653.0, 1654.0],       [1655.0, 1656.0]]],     [[[1657.0, 1658.0],       [1659.0, 1660.0],       [1661.0, 1662.0],       [1663.0, 1664.0]],      [[1665.0, 1666.0],       [1667.0, 1668.0],       [1669.0, 1670.0],       [1671.0, 1672.0]],      [[1673.0, 1674.0],       [1675.0, 1676.0],       [1677.0, 1678.0],       [1679.0, 1680.0]]]],    [[[[1681.0, 1682.0],       [1683.0, 1684.0],       [1685.0, 1686.0],       [1687.0, 1688.0]],      [[1689.0, 1690.0],       [1691.0, 1692.0],       [1693.0, 1694.0],       [1695.0, 1696.0]],      [[1697.0, 1698.0],       [1699.0, 1700.0],       [1701.0, 1702.0],       [1703.0, 1704.0]]],     [[[1705.0, 1706.0],       [1707.0, 1708.0],       [1709.0, 1710.0],       [1711.0, 1712.0]],      [[1713.0, 1714.0],       [1715.0, 1716.0],       [1717.0, 1718.0],       [1719.0, 1720.0]],      [[1721.0, 1722.0],       [1723.0, 1724.0],       [1725.0, 1726.0],       [1727.0, 1728.0]]],     [[[1729.0, 1730.0],       [1731.0, 1732.0],       [1733.0, 1734.0],       [1735.0, 1736.0]],      [[1737.0, 1738.0],       [1739.0, 1740.0],       [1741.0, 1742.0],       [1743.0, 1744.0]],      [[1745.0, 1746.0],       [1747.0, 1748.0],       [1749.0, 1750.0],       [1751.0, 1752.0]]],     [[[1753.0, 1754.0],       [1755.0, 1756.0],       [1757.0, 1758.0],       [1759.0, 1760.0]],      [[1761.0, 1762.0],       [1763.0, 1764.0],       [1765.0, 1766.0],       [1767.0, 1768.0]],      [[1769.0, 1770.0],       [1771.0, 1772.0],       [1773.0, 1774.0],       [1775.0, 1776.0]]],     [[[1777.0, 1778.0],       [1779.0, 1780.0],       [1781.0, 1782.0],       [1783.0, 1784.0]],      [[1785.0, 1786.0],       [1787.0, 1788.0],       [1789.0, 1790.0],       [1791.0, 1792.0]],      [[1793.0, 1794.0],       [1795.0, 1796.0],       [1797.0, 1798.0],       [1799.0, 1800.0]]]]],   [[[[[1801.0, 1802.0],       [1803.0, 1804.0],       [1805.0, 1806.0],       [1807.0, 1808.0]],      [[1809.0, 1810.0],       [1811.0, 1812.0],       [1813.0, 1814.0],       [1815.0, 1816.0]],      [[1817.0, 1818.0],       [1819.0, 1820.0],       [1821.0, 1822.0],       [1823.0, 1824.0]]],     [[[1825.0, 1826.0],       [1827.0, 1828.0],       [1829.0, 1830.0],       [1831.0, 1832.0]],      [[1833.0, 1834.0],       [1835.0, 1836.0],       [1837.0, 1838.0],       [1839.0, 1840.0]],      [[1841.0, 1842.0],       [1843.0, 1844.0],       [1845.0, 1846.0],       [1847.0, 1848.0]]],     [[[1849.0, 1850.0],       [1851.0, 1852.0],       [1853.0, 1854.0],       [1855.0, 1856.0]],      [[1857.0, 1858.0],       [1859.0, 1860.0],       [1861.0, 1862.0],       [1863.0, 1864.0]],      [[1865.0, 1866.0],       [1867.0, 1868.0],       [1869.0, 1870.0],       [1871.0, 1872.0]]],     [[[1873.0, 1874.0],       [1875.0, 1876.0],       [1877.0, 1878.0],       [1879.0, 1880.0]],      [[1881.0, 1882.0],       [1883.0, 1884.0],       [1885.0, 1886.0],       [1887.0, 1888.0]],      [[1889.0, 1890.0],       [1891.0, 1892.0],       [1893.0, 1894.0],       [1895.0, 1896.0]]],     [[[1897.0, 1898.0],       [1899.0, 1900.0],       [1901.0, 1902.0],       [1903.0, 1904.0]],      [[1905.0, 1906.0],       [1907.0, 1908.0],       [1909.0, 1910.0],       [1911.0, 1912.0]],      [[1913.0, 1914.0],       [1915.0, 1916.0],       [1917.0, 1918.0],       [1919.0, 1920.0]]]],    [[[[1921.0, 1922.0],       [1923.0, 1924.0],       [1925.0, 1926.0],       [1927.0, 1928.0]],      [[1929.0, 1930.0],       [1931.0, 1932.0],       [1933.0, 1934.0],       [1935.0, 1936.0]],      [[1937.0, 1938.0],       [1939.0, 1940.0],       [1941.0, 1942.0],       [1943.0, 1944.0]]],     [[[1945.0, 1946.0],       [1947.0, 1948.0],       [1949.0, 1950.0],       [1951.0, 1952.0]],      [[1953.0, 1954.0],       [1955.0, 1956.0],       [1957.0, 1958.0],       [1959.0, 1960.0]],      [[1961.0, 1962.0],       [1963.0, 1964.0],       [1965.0, 1966.0],       [1967.0, 1968.0]]],     [[[1969.0, 1970.0],       [1971.0, 1972.0],       [1973.0, 1974.0],       [1975.0, 1976.0]],      [[1977.0, 1978.0],       [1979.0, 1980.0],       [1981.0, 1982.0],       [1983.0, 1984.0]],      [[1985.0, 1986.0],       [1987.0, 1988.0],       [1989.0, 1990.0],       [1991.0, 1992.0]]],     [[[1993.0, 1994.0],       [1995.0, 1996.0],       [1997.0, 1998.0],       [1999.0, 2000.0]],      [[2001.0, 2002.0],       [2003.0, 2004.0],       [2005.0, 2006.0],       [2007.0, 2008.0]],      [[2009.0, 2010.0],       [2011.0, 2012.0],       [2013.0, 2014.0],       [2015.0, 2016.0]]],     [[[2017.0, 2018.0],       [2019.0, 2020.0],       [2021.0, 2022.0],       [2023.0, 2024.0]],      [[2025.0, 2026.0],       [2027.0, 2028.0],       [2029.0, 2030.0],       [2031.0, 2032.0]],      [[2033.0, 2034.0],       [2035.0, 2036.0],       [2037.0, 2038.0],       [2039.0, 2040.0]]]],    [[[[2041.0, 2042.0],       [2043.0, 2044.0],       [2045.0, 2046.0],       [2047.0, 2048.0]],      [[2049.0, 2050.0],       [2051.0, 2052.0],       [2053.0, 2054.0],       [2055.0, 2056.0]],      [[2057.0, 2058.0],       [2059.0, 2060.0],       [2061.0, 2062.0],       [2063.0, 2064.0]]],     [[[2065.0, 2066.0],       [2067.0, 2068.0],       [2069.0, 2070.0],       [2071.0, 2072.0]],      [[2073.0, 2074.0],       [2075.0, 2076.0],       [2077.0, 2078.0],       [2079.0, 2080.0]],      [[2081.0, 2082.0],       [2083.0, 2084.0],       [2085.0, 2086.0],       [2087.0, 2088.0]]],     [[[2089.0, 2090.0],       [2091.0, 2092.0],       [2093.0, 2094.0],       [2095.0, 2096.0]],      [[2097.0, 2098.0],       [2099.0, 2100.0],       [2101.0, 2102.0],       [2103.0, 2104.0]],      [[2105.0, 2106.0],       [2107.0, 2108.0],       [2109.0, 2110.0],       [2111.0, 2112.0]]],     [[[2113.0, 2114.0],       [2115.0, 2116.0],       [2117.0, 2118.0],       [2119.0, 2120.0]],      [[2121.0, 2122.0],       [2123.0, 2124.0],       [2125.0, 2126.0],       [2127.0, 2128.0]],      [[2129.0, 2130.0],       [2131.0, 2132.0],       [2133.0, 2134.0],       [2135.0, 2136.0]]],     [[[2137.0, 2138.0],       [2139.0, 2140.0],       [2141.0, 2142.0],       [2143.0, 2144.0]],      [[2145.0, 2146.0],       [2147.0, 2148.0],       [2149.0, 2150.0],       [2151.0, 2152.0]],      [[2153.0, 2154.0],       [2155.0, 2156.0],       [2157.0, 2158.0],       [2159.0, 2160.0]]]]],   [[[[[2161.0, 2162.0],       [2163.0, 2164.0],       [2165.0, 2166.0],       [2167.0, 2168.0]],      [[2169.0, 2170.0],       [2171.0, 2172.0],       [2173.0, 2174.0],       [2175.0, 2176.0]],      [[2177.0, 2178.0],       [2179.0, 2180.0],       [2181.0, 2182.0],       [2183.0, 2184.0]]],     [[[2185.0, 2186.0],       [2187.0, 2188.0],       [2189.0, 2190.0],       [2191.0, 2192.0]],      [[2193.0, 2194.0],       [2195.0, 2196.0],       [2197.0, 2198.0],       [2199.0, 2200.0]],      [[2201.0, 2202.0],       [2203.0, 2204.0],       [2205.0, 2206.0],       [2207.0, 2208.0]]],     [[[2209.0, 2210.0],       [2211.0, 2212.0],       [2213.0, 2214.0],       [2215.0, 2216.0]],      [[2217.0, 2218.0],       [2219.0, 2220.0],       [2221.0, 2222.0],       [2223.0, 2224.0]],      [[2225.0, 2226.0],       [2227.0, 2228.0],       [2229.0, 2230.0],       [2231.0, 2232.0]]],     [[[2233.0, 2234.0],       [2235.0, 2236.0],       [2237.0, 2238.0],       [2239.0, 2240.0]],      [[2241.0, 2242.0],       [2243.0, 2244.0],       [2245.0, 2246.0],       [2247.0, 2248.0]],      [[2249.0, 2250.0],       [2251.0, 2252.0],       [2253.0, 2254.0],       [2255.0, 2256.0]]],     [[[2257.0, 2258.0],       [2259.0, 2260.0],       [2261.0, 2262.0],       [2263.0, 2264.0]],      [[2265.0, 2266.0],       [2267.0, 2268.0],       [2269.0, 2270.0],       [2271.0, 2272.0]],      [[2273.0, 2274.0],       [2275.0, 2276.0],       [2277.0, 2278.0],       [2279.0, 2280.0]]]],    [[[[2281.0, 2282.0],       [2283.0, 2284.0],       [2285.0, 2286.0],       [2287.0, 2288.0]],      [[2289.0, 2290.0],       [2291.0, 2292.0],       [2293.0, 2294.0],       [2295.0, 2296.0]],      [[2297.0, 2298.0],       [2299.0, 2300.0],       [2301.0, 2302.0],       [2303.0, 2304.0]]],     [[[2305.0, 2306.0],       [2307.0, 2308.0],       [2309.0, 2310.0],       [2311.0, 2312.0]],      [[2313.0, 2314.0],       [2315.0, 2316.0],       [2317.0, 2318.0],       [2319.0, 2320.0]],      [[2321.0, 2322.0],       [2323.0, 2324.0],       [2325.0, 2326.0],       [2327.0, 2328.0]]],     [[[2329.0, 2330.0],       [2331.0, 2332.0],       [2333.0, 2334.0],       [2335.0, 2336.0]],      [[2337.0, 2338.0],       [2339.0, 2340.0],       [2341.0, 2342.0],       [2343.0, 2344.0]],      [[2345.0, 2346.0],       [2347.0, 2348.0],       [2349.0, 2350.0],       [2351.0, 2352.0]]],     [[[2353.0, 2354.0],       [2355.0, 2356.0],       [2357.0, 2358.0],       [2359.0, 2360.0]],      [[2361.0, 2362.0],       [2363.0, 2364.0],       [2365.0, 2366.0],       [2367.0, 2368.0]],      [[2369.0, 2370.0],       [2371.0, 2372.0],       [2373.0, 2374.0],       [2375.0, 2376.0]]],     [[[2377.0, 2378.0],       [2379.0, 2380.0],       [2381.0, 2382.0],       [2383.0, 2384.0]],      [[2385.0, 2386.0],       [2387.0, 2388.0],       [2389.0, 2390.0],       [2391.0, 2392.0]],      [[2393.0, 2394.0],       [2395.0, 2396.0],       [2397.0, 2398.0],       [2399.0, 2400.0]]]],    [[[[2401.0, 2402.0],       [2403.0, 2404.0],       [2405.0, 2406.0],       [2407.0, 2408.0]],      [[2409.0, 2410.0],       [2411.0, 2412.0],       [2413.0, 2414.0],       [2415.0, 2416.0]],      [[2417.0, 2418.0],       [2419.0, 2420.0],       [2421.0, 2422.0],       [2423.0, 2424.0]]],     [[[2425.0, 2426.0],       [2427.0, 2428.0],       [2429.0, 2430.0],       [2431.0, 2432.0]],      [[2433.0, 2434.0],       [2435.0, 2436.0],       [2437.0, 2438.0],       [2439.0, 2440.0]],      [[2441.0, 2442.0],       [2443.0, 2444.0],       [2445.0, 2446.0],       [2447.0, 2448.0]]],     [[[2449.0, 2450.0],       [2451.0, 2452.0],       [2453.0, 2454.0],       [2455.0, 2456.0]],      [[2457.0, 2458.0],       [2459.0, 2460.0],       [2461.0, 2462.0],       [2463.0, 2464.0]],      [[2465.0, 2466.0],       [2467.0, 2468.0],       [2469.0, 2470.0],       [2471.0, 2472.0]]],     [[[2473.0, 2474.0],       [2475.0, 2476.0],       [2477.0, 2478.0],       [2479.0, 2480.0]],      [[2481.0, 2482.0],       [2483.0, 2484.0],       [2485.0, 2486.0],       [2487.0, 2488.0]],      [[2489.0, 2490.0],       [2491.0, 2492.0],       [2493.0, 2494.0],       [2495.0, 2496.0]]],     [[[2497.0, 2498.0],       [2499.0, 2500.0],       [2501.0, 2502.0],       [2503.0, 2504.0]],      [[2505.0, 2506.0],       [2507.0, 2508.0],       [2509.0, 2510.0],       [2511.0, 2512.0]],      [[2513.0, 2514.0],       [2515.0, 2516.0],       [2517.0, 2518.0],       [2519.0, 2520.0]]]]]],  [[[[[[2521.0, 2522.0],       [2523.0, 2524.0],       [2525.0, 2526.0],       [2527.0, 2528.0]],      [[2529.0, 2530.0],       [2531.0, 2532.0],       [2533.0, 2534.0],       [2535.0, 2536.0]],      [[2537.0, 2538.0],       [2539.0, 2540.0],       [2541.0, 2542.0],       [2543.0, 2544.0]]],     [[[2545.0, 2546.0],       [2547.0, 2548.0],       [2549.0, 2550.0],       [2551.0, 2552.0]],      [[2553.0, 2554.0],       [2555.0, 2556.0],       [2557.0, 2558.0],       [2559.0, 2560.0]],      [[2561.0, 2562.0],       [2563.0, 2564.0],       [2565.0, 2566.0],       [2567.0, 2568.0]]],     [[[2569.0, 2570.0],       [2571.0, 2572.0],       [2573.0, 2574.0],       [2575.0, 2576.0]],      [[2577.0, 2578.0],       [2579.0, 2580.0],       [2581.0, 2582.0],       [2583.0, 2584.0]],      [[2585.0, 2586.0],       [2587.0, 2588.0],       [2589.0, 2590.0],       [2591.0, 2592.0]]],     [[[2593.0, 2594.0],       [2595.0, 2596.0],       [2597.0, 2598.0],       [2599.0, 2600.0]],      [[2601.0, 2602.0],       [2603.0, 2604.0],       [2605.0, 2606.0],       [2607.0, 2608.0]],      [[2609.0, 2610.0],       [2611.0, 2612.0],       [2613.0, 2614.0],       [2615.0, 2616.0]]],     [[[2617.0, 2618.0],       [2619.0, 2620.0],       [2621.0, 2622.0],       [2623.0, 2624.0]],      [[2625.0, 2626.0],       [2627.0, 2628.0],       [2629.0, 2630.0],       [2631.0, 2632.0]],      [[2633.0, 2634.0],       [2635.0, 2636.0],       [2637.0, 2638.0],       [2639.0, 2640.0]]]],    [[[[2641.0, 2642.0],       [2643.0, 2644.0],       [2645.0, 2646.0],       [2647.0, 2648.0]],      [[2649.0, 2650.0],       [2651.0, 2652.0],       [2653.0, 2654.0],       [2655.0, 2656.0]],      [[2657.0, 2658.0],       [2659.0, 2660.0],       [2661.0, 2662.0],       [2663.0, 2664.0]]],     [[[2665.0, 2666.0],       [2667.0, 2668.0],       [2669.0, 2670.0],       [2671.0, 2672.0]],      [[2673.0, 2674.0],       [2675.0, 2676.0],       [2677.0, 2678.0],       [2679.0, 2680.0]],      [[2681.0, 2682.0],       [2683.0, 2684.0],       [2685.0, 2686.0],       [2687.0, 2688.0]]],     [[[2689.0, 2690.0],       [2691.0, 2692.0],       [2693.0, 2694.0],       [2695.0, 2696.0]],      [[2697.0, 2698.0],       [2699.0, 2700.0],       [2701.0, 2702.0],       [2703.0, 2704.0]],      [[2705.0, 2706.0],       [2707.0, 2708.0],       [2709.0, 2710.0],       [2711.0, 2712.0]]],     [[[2713.0, 2714.0],       [2715.0, 2716.0],       [2717.0, 2718.0],       [2719.0, 2720.0]],      [[2721.0, 2722.0],       [2723.0, 2724.0],       [2725.0, 2726.0],       [2727.0, 2728.0]],      [[2729.0, 2730.0],       [2731.0, 2732.0],       [2733.0, 2734.0],       [2735.0, 2736.0]]],     [[[2737.0, 2738.0],       [2739.0, 2740.0],       [2741.0, 2742.0],       [2743.0, 2744.0]],      [[2745.0, 2746.0],       [2747.0, 2748.0],       [2749.0, 2750.0],       [2751.0, 2752.0]],      [[2753.0, 2754.0],       [2755.0, 2756.0],       [2757.0, 2758.0],       [2759.0, 2760.0]]]],    [[[[2761.0, 2762.0],       [2763.0, 2764.0],       [2765.0, 2766.0],       [2767.0, 2768.0]],      [[2769.0, 2770.0],       [2771.0, 2772.0],       [2773.0, 2774.0],       [2775.0, 2776.0]],      [[2777.0, 2778.0],       [2779.0, 2780.0],       [2781.0, 2782.0],       [2783.0, 2784.0]]],     [[[2785.0, 2786.0],       [2787.0, 2788.0],       [2789.0, 2790.0],       [2791.0, 2792.0]],      [[2793.0, 2794.0],       [2795.0, 2796.0],       [2797.0, 2798.0],       [2799.0, 2800.0]],      [[2801.0, 2802.0],       [2803.0, 2804.0],       [2805.0, 2806.0],       [2807.0, 2808.0]]],     [[[2809.0, 2810.0],       [2811.0, 2812.0],       [2813.0, 2814.0],       [2815.0, 2816.0]],      [[2817.0, 2818.0],       [2819.0, 2820.0],       [2821.0, 2822.0],       [2823.0, 2824.0]],      [[2825.0, 2826.0],       [2827.0, 2828.0],       [2829.0, 2830.0],       [2831.0, 2832.0]]],     [[[2833.0, 2834.0],       [2835.0, 2836.0],       [2837.0, 2838.0],       [2839.0, 2840.0]],      [[2841.0, 2842.0],       [2843.0, 2844.0],       [2845.0, 2846.0],       [2847.0, 2848.0]],      [[2849.0, 2850.0],       [2851.0, 2852.0],       [2853.0, 2854.0],       [2855.0, 2856.0]]],     [[[2857.0, 2858.0],       [2859.0, 2860.0],       [2861.0, 2862.0],       [2863.0, 2864.0]],      [[2865.0, 2866.0],       [2867.0, 2868.0],       [2869.0, 2870.0],       [2871.0, 2872.0]],      [[2873.0, 2874.0],       [2875.0, 2876.0],       [2877.0, 2878.0],       [2879.0, 2880.0]]]]],   [[[[[2881.0, 2882.0],       [2883.0, 2884.0],       [2885.0, 2886.0],       [2887.0, 2888.0]],      [[2889.0, 2890.0],       [2891.0, 2892.0],       [2893.0, 2894.0],       [2895.0, 2896.0]],      [[2897.0, 2898.0],       [2899.0, 2900.0],       [2901.0, 2902.0],       [2903.0, 2904.0]]],     [[[2905.0, 2906.0],       [2907.0, 2908.0],       [2909.0, 2910.0],       [2911.0, 2912.0]],      [[2913.0, 2914.0],       [2915.0, 2916.0],       [2917.0, 2918.0],       [2919.0, 2920.0]],      [[2921.0, 2922.0],       [2923.0, 2924.0],       [2925.0, 2926.0],       [2927.0, 2928.0]]],     [[[2929.0, 2930.0],       [2931.0, 2932.0],       [2933.0, 2934.0],       [2935.0, 2936.0]],      [[2937.0, 2938.0],       [2939.0, 2940.0],       [2941.0, 2942.0],       [2943.0, 2944.0]],      [[2945.0, 2946.0],       [2947.0, 2948.0],       [2949.0, 2950.0],       [2951.0, 2952.0]]],     [[[2953.0, 2954.0],       [2955.0, 2956.0],       [2957.0, 2958.0],       [2959.0, 2960.0]],      [[2961.0, 2962.0],       [2963.0, 2964.0],       [2965.0, 2966.0],       [2967.0, 2968.0]],      [[2969.0, 2970.0],       [2971.0, 2972.0],       [2973.0, 2974.0],       [2975.0, 2976.0]]],     [[[2977.0, 2978.0],       [2979.0, 2980.0],       [2981.0, 2982.0],       [2983.0, 2984.0]],      [[2985.0, 2986.0],       [2987.0, 2988.0],       [2989.0, 2990.0],       [2991.0, 2992.0]],      [[2993.0, 2994.0],       [2995.0, 2996.0],       [2997.0, 2998.0],       [2999.0, 3000.0]]]],    [[[[3001.0, 3002.0],       [3003.0, 3004.0],       [3005.0, 3006.0],       [3007.0, 3008.0]],      [[3009.0, 3010.0],       [3011.0, 3012.0],       [3013.0, 3014.0],       [3015.0, 3016.0]],      [[3017.0, 3018.0],       [3019.0, 3020.0],       [3021.0, 3022.0],       [3023.0, 3024.0]]],     [[[3025.0, 3026.0],       [3027.0, 3028.0],       [3029.0, 3030.0],       [3031.0, 3032.0]],      [[3033.0, 3034.0],       [3035.0, 3036.0],       [3037.0, 3038.0],       [3039.0, 3040.0]],      [[3041.0, 3042.0],       [3043.0, 3044.0],       [3045.0, 3046.0],       [3047.0, 3048.0]]],     [[[3049.0, 3050.0],       [3051.0, 3052.0],       [3053.0, 3054.0],       [3055.0, 3056.0]],      [[3057.0, 3058.0],       [3059.0, 3060.0],       [3061.0, 3062.0],       [3063.0, 3064.0]],      [[3065.0, 3066.0],       [3067.0, 3068.0],       [3069.0, 3070.0],       [3071.0, 3072.0]]],     [[[3073.0, 3074.0],       [3075.0, 3076.0],       [3077.0, 3078.0],       [3079.0, 3080.0]],      [[3081.0, 3082.0],       [3083.0, 3084.0],       [3085.0, 3086.0],       [3087.0, 3088.0]],      [[3089.0, 3090.0],       [3091.0, 3092.0],       [3093.0, 3094.0],       [3095.0, 3096.0]]],     [[[3097.0, 3098.0],       [3099.0, 3100.0],       [3101.0, 3102.0],       [3103.0, 3104.0]],      [[3105.0, 3106.0],       [3107.0, 3108.0],       [3109.0, 3110.0],       [3111.0, 3112.0]],      [[3113.0, 3114.0],       [3115.0, 3116.0],       [3117.0, 3118.0],       [3119.0, 3120.0]]]],    [[[[3121.0, 3122.0],       [3123.0, 3124.0],       [3125.0, 3126.0],       [3127.0, 3128.0]],      [[3129.0, 3130.0],       [3131.0, 3132.0],       [3133.0, 3134.0],       [3135.0, 3136.0]],      [[3137.0, 3138.0],       [3139.0, 3140.0],       [3141.0, 3142.0],       [3143.0, 3144.0]]],     [[[3145.0, 3146.0],       [3147.0, 3148.0],       [3149.0, 3150.0],       [3151.0, 3152.0]],      [[3153.0, 3154.0],       [3155.0, 3156.0],       [3157.0, 3158.0],       [3159.0, 3160.0]],      [[3161.0, 3162.0],       [3163.0, 3164.0],       [3165.0, 3166.0],       [3167.0, 3168.0]]],     [[[3169.0, 3170.0],       [3171.0, 3172.0],       [3173.0, 3174.0],       [3175.0, 3176.0]],      [[3177.0, 3178.0],       [3179.0, 3180.0],       [3181.0, 3182.0],       [3183.0, 3184.0]],      [[3185.0, 3186.0],       [3187.0, 3188.0],       [3189.0, 3190.0],       [3191.0, 3192.0]]],     [[[3193.0, 3194.0],       [3195.0, 3196.0],       [3197.0, 3198.0],       [3199.0, 3200.0]],      [[3201.0, 3202.0],       [3203.0, 3204.0],       [3205.0, 3206.0],       [3207.0, 3208.0]],      [[3209.0, 3210.0],       [3211.0, 3212.0],       [3213.0, 3214.0],       [3215.0, 3216.0]]],     [[[3217.0, 3218.0],       [3219.0, 3220.0],       [3221.0, 3222.0],       [3223.0, 3224.0]],      [[3225.0, 3226.0],       [3227.0, 3228.0],       [3229.0, 3230.0],       [3231.0, 3232.0]],      [[3233.0, 3234.0],       [3235.0, 3236.0],       [3237.0, 3238.0],       [3239.0, 3240.0]]]]],   [[[[[3241.0, 3242.0],       [3243.0, 3244.0],       [3245.0, 3246.0],       [3247.0, 3248.0]],      [[3249.0, 3250.0],       [3251.0, 3252.0],       [3253.0, 3254.0],       [3255.0, 3256.0]],      [[3257.0, 3258.0],       [3259.0, 3260.0],       [3261.0, 3262.0],       [3263.0, 3264.0]]],     [[[3265.0, 3266.0],       [3267.0, 3268.0],       [3269.0, 3270.0],       [3271.0, 3272.0]],      [[3273.0, 3274.0],       [3275.0, 3276.0],       [3277.0, 3278.0],       [3279.0, 3280.0]],      [[3281.0, 3282.0],       [3283.0, 3284.0],       [3285.0, 3286.0],       [3287.0, 3288.0]]],     [[[3289.0, 3290.0],       [3291.0, 3292.0],       [3293.0, 3294.0],       [3295.0, 3296.0]],      [[3297.0, 3298.0],       [3299.0, 3300.0],       [3301.0, 3302.0],       [3303.0, 3304.0]],      [[3305.0, 3306.0],       [3307.0, 3308.0],       [3309.0, 3310.0],       [3311.0, 3312.0]]],     [[[3313.0, 3314.0],       [3315.0, 3316.0],       [3317.0, 3318.0],       [3319.0, 3320.0]],      [[3321.0, 3322.0],       [3323.0, 3324.0],       [3325.0, 3326.0],       [3327.0, 3328.0]],      [[3329.0, 3330.0],       [3331.0, 3332.0],       [3333.0, 3334.0],       [3335.0, 3336.0]]],     [[[3337.0, 3338.0],       [3339.0, 3340.0],       [3341.0, 3342.0],       [3343.0, 3344.0]],      [[3345.0, 3346.0],       [3347.0, 3348.0],       [3349.0, 3350.0],       [3351.0, 3352.0]],      [[3353.0, 3354.0],       [3355.0, 3356.0],       [3357.0, 3358.0],       [3359.0, 3360.0]]]],    [[[[3361.0, 3362.0],       [3363.0, 3364.0],       [3365.0, 3366.0],       [3367.0, 3368.0]],      [[3369.0, 3370.0],       [3371.0, 3372.0],       [3373.0, 3374.0],       [3375.0, 3376.0]],      [[3377.0, 3378.0],       [3379.0, 3380.0],       [3381.0, 3382.0],       [3383.0, 3384.0]]],     [[[3385.0, 3386.0],       [3387.0, 3388.0],       [3389.0, 3390.0],       [3391.0, 3392.0]],      [[3393.0, 3394.0],       [3395.0, 3396.0],       [3397.0, 3398.0],       [3399.0, 3400.0]],      [[3401.0, 3402.0],       [3403.0, 3404.0],       [3405.0, 3406.0],       [3407.0, 3408.0]]],     [[[3409.0, 3410.0],       [3411.0, 3412.0],       [3413.0, 3414.0],       [3415.0, 3416.0]],      [[3417.0, 3418.0],       [3419.0, 3420.0],       [3421.0, 3422.0],       [3423.0, 3424.0]],      [[3425.0, 3426.0],       [3427.0, 3428.0],       [3429.0, 3430.0],       [3431.0, 3432.0]]],     [[[3433.0, 3434.0],       [3435.0, 3436.0],       [3437.0, 3438.0],       [3439.0, 3440.0]],      [[3441.0, 3442.0],       [3443.0, 3444.0],       [3445.0, 3446.0],       [3447.0, 3448.0]],      [[3449.0, 3450.0],       [3451.0, 3452.0],       [3453.0, 3454.0],       [3455.0, 3456.0]]],     [[[3457.0, 3458.0],       [3459.0, 3460.0],       [3461.0, 3462.0],       [3463.0, 3464.0]],      [[3465.0, 3466.0],       [3467.0, 3468.0],       [3469.0, 3470.0],       [3471.0, 3472.0]],      [[3473.0, 3474.0],       [3475.0, 3476.0],       [3477.0, 3478.0],       [3479.0, 3480.0]]]],    [[[[3481.0, 3482.0],       [3483.0, 3484.0],       [3485.0, 3486.0],       [3487.0, 3488.0]],      [[3489.0, 3490.0],       [3491.0, 3492.0],       [3493.0, 3494.0],       [3495.0, 3496.0]],      [[3497.0, 3498.0],       [3499.0, 3500.0],       [3501.0, 3502.0],       [3503.0, 3504.0]]],     [[[3505.0, 3506.0],       [3507.0, 3508.0],       [3509.0, 3510.0],       [3511.0, 3512.0]],      [[3513.0, 3514.0],       [3515.0, 3516.0],       [3517.0, 3518.0],       [3519.0, 3520.0]],      [[3521.0, 3522.0],       [3523.0, 3524.0],       [3525.0, 3526.0],       [3527.0, 3528.0]]],     [[[3529.0, 3530.0],       [3531.0, 3532.0],       [3533.0, 3534.0],       [3535.0, 3536.0]],      [[3537.0, 3538.0],       [3539.0, 3540.0],       [3541.0, 3542.0],       [3543.0, 3544.0]],      [[3545.0, 3546.0],       [3547.0, 3548.0],       [3549.0, 3550.0],       [3551.0, 3552.0]]],     [[[3553.0, 3554.0],       [3555.0, 3556.0],       [3557.0, 3558.0],       [3559.0, 3560.0]],      [[3561.0, 3562.0],       [3563.0, 3564.0],       [3565.0, 3566.0],       [3567.0, 3568.0]],      [[3569.0, 3570.0],       [3571.0, 3572.0],       [3573.0, 3574.0],       [3575.0, 3576.0]]],     [[[3577.0, 3578.0],       [3579.0, 3580.0],       [3581.0, 3582.0],       [3583.0, 3584.0]],      [[3585.0, 3586.0],       [3587.0, 3588.0],       [3589.0, 3590.0],       [3591.0, 3592.0]],      [[3593.0, 3594.0],       [3595.0, 3596.0],       [3597.0, 3598.0],       [3599.0, 3600.0]]]]],   [[[[[3601.0, 3602.0],       [3603.0, 3604.0],       [3605.0, 3606.0],       [3607.0, 3608.0]],      [[3609.0, 3610.0],       [3611.0, 3612.0],       [3613.0, 3614.0],       [3615.0, 3616.0]],      [[3617.0, 3618.0],       [3619.0, 3620.0],       [3621.0, 3622.0],       [3623.0, 3624.0]]],     [[[3625.0, 3626.0],       [3627.0, 3628.0],       [3629.0, 3630.0],       [3631.0, 3632.0]],      [[3633.0, 3634.0],       [3635.0, 3636.0],       [3637.0, 3638.0],       [3639.0, 3640.0]],      [[3641.0, 3642.0],       [3643.0, 3644.0],       [3645.0, 3646.0],       [3647.0, 3648.0]]],     [[[3649.0, 3650.0],       [3651.0, 3652.0],       [3653.0, 3654.0],       [3655.0, 3656.0]],      [[3657.0, 3658.0],       [3659.0, 3660.0],       [3661.0, 3662.0],       [3663.0, 3664.0]],      [[3665.0, 3666.0],       [3667.0, 3668.0],       [3669.0, 3670.0],       [3671.0, 3672.0]]],     [[[3673.0, 3674.0],       [3675.0, 3676.0],       [3677.0, 3678.0],       [3679.0, 3680.0]],      [[3681.0, 3682.0],       [3683.0, 3684.0],       [3685.0, 3686.0],       [3687.0, 3688.0]],      [[3689.0, 3690.0],       [3691.0, 3692.0],       [3693.0, 3694.0],       [3695.0, 3696.0]]],     [[[3697.0, 3698.0],       [3699.0, 3700.0],       [3701.0, 3702.0],       [3703.0, 3704.0]],      [[3705.0, 3706.0],       [3707.0, 3708.0],       [3709.0, 3710.0],       [3711.0, 3712.0]],      [[3713.0, 3714.0],       [3715.0, 3716.0],       [3717.0, 3718.0],       [3719.0, 3720.0]]]],    [[[[3721.0, 3722.0],       [3723.0, 3724.0],       [3725.0, 3726.0],       [3727.0, 3728.0]],      [[3729.0, 3730.0],       [3731.0, 3732.0],       [3733.0, 3734.0],       [3735.0, 3736.0]],      [[3737.0, 3738.0],       [3739.0, 3740.0],       [3741.0, 3742.0],       [3743.0, 3744.0]]],     [[[3745.0, 3746.0],       [3747.0, 3748.0],       [3749.0, 3750.0],       [3751.0, 3752.0]],      [[3753.0, 3754.0],       [3755.0, 3756.0],       [3757.0, 3758.0],       [3759.0, 3760.0]],      [[3761.0, 3762.0],       [3763.0, 3764.0],       [3765.0, 3766.0],       [3767.0, 3768.0]]],     [[[3769.0, 3770.0],       [3771.0, 3772.0],       [3773.0, 3774.0],       [3775.0, 3776.0]],      [[3777.0, 3778.0],       [3779.0, 3780.0],       [3781.0, 3782.0],       [3783.0, 3784.0]],      [[3785.0, 3786.0],       [3787.0, 3788.0],       [3789.0, 3790.0],       [3791.0, 3792.0]]],     [[[3793.0, 3794.0],       [3795.0, 3796.0],       [3797.0, 3798.0],       [3799.0, 3800.0]],      [[3801.0, 3802.0],       [3803.0, 3804.0],       [3805.0, 3806.0],       [3807.0, 3808.0]],      [[3809.0, 3810.0],       [3811.0, 3812.0],       [3813.0, 3814.0],       [3815.0, 3816.0]]],     [[[3817.0, 3818.0],       [3819.0, 3820.0],       [3821.0, 3822.0],       [3823.0, 3824.0]],      [[3825.0, 3826.0],       [3827.0, 3828.0],       [3829.0, 3830.0],       [3831.0, 3832.0]],      [[3833.0, 3834.0],       [3835.0, 3836.0],       [3837.0, 3838.0],       [3839.0, 3840.0]]]],    [[[[3841.0, 3842.0],       [3843.0, 3844.0],       [3845.0, 3846.0],       [3847.0, 3848.0]],      [[3849.0, 3850.0],       [3851.0, 3852.0],       [3853.0, 3854.0],       [3855.0, 3856.0]],      [[3857.0, 3858.0],       [3859.0, 3860.0],       [3861.0, 3862.0],       [3863.0, 3864.0]]],     [[[3865.0, 3866.0],       [3867.0, 3868.0],       [3869.0, 3870.0],       [3871.0, 3872.0]],      [[3873.0, 3874.0],       [3875.0, 3876.0],       [3877.0, 3878.0],       [3879.0, 3880.0]],      [[3881.0, 3882.0],       [3883.0, 3884.0],       [3885.0, 3886.0],       [3887.0, 3888.0]]],     [[[3889.0, 3890.0],       [3891.0, 3892.0],       [3893.0, 3894.0],       [3895.0, 3896.0]],      [[3897.0, 3898.0],       [3899.0, 3900.0],       [3901.0, 3902.0],       [3903.0, 3904.0]],      [[3905.0, 3906.0],       [3907.0, 3908.0],       [3909.0, 3910.0],       [3911.0, 3912.0]]],     [[[3913.0, 3914.0],       [3915.0, 3916.0],       [3917.0, 3918.0],       [3919.0, 3920.0]],      [[3921.0, 3922.0],       [3923.0, 3924.0],       [3925.0, 3926.0],       [3927.0, 3928.0]],      [[3929.0, 3930.0],       [3931.0, 3932.0],       [3933.0, 3934.0],       [3935.0, 3936.0]]],     [[[3937.0, 3938.0],       [3939.0, 3940.0],       [3941.0, 3942.0],       [3943.0, 3944.0]],      [[3945.0, 3946.0],       [3947.0, 3948.0],       [3949.0, 3950.0],       [3951.0, 3952.0]],      [[3953.0, 3954.0],       [3955.0, 3956.0],       [3957.0, 3958.0],       [3959.0, 3960.0]]]]],   [[[[[3961.0, 3962.0],       [3963.0, 3964.0],       [3965.0, 3966.0],       [3967.0, 3968.0]],      [[3969.0, 3970.0],       [3971.0, 3972.0],       [3973.0, 3974.0],       [3975.0, 3976.0]],      [[3977.0, 3978.0],       [3979.0, 3980.0],       [3981.0, 3982.0],       [3983.0, 3984.0]]],     [[[3985.0, 3986.0],       [3987.0, 3988.0],       [3989.0, 3990.0],       [3991.0, 3992.0]],      [[3993.0, 3994.0],       [3995.0, 3996.0],       [3997.0, 3998.0],       [3999.0, 4000.0]],      [[4001.0, 4002.0],       [4003.0, 4004.0],       [4005.0, 4006.0],       [4007.0, 4008.0]]],     [[[4009.0, 4010.0],       [4011.0, 4012.0],       [4013.0, 4014.0],       [4015.0, 4016.0]],      [[4017.0, 4018.0],       [4019.0, 4020.0],       [4021.0, 4022.0],       [4023.0, 4024.0]],      [[4025.0, 4026.0],       [4027.0, 4028.0],       [4029.0, 4030.0],       [4031.0, 4032.0]]],     [[[4033.0, 4034.0],       [4035.0, 4036.0],       [4037.0, 4038.0],       [4039.0, 4040.0]],      [[4041.0, 4042.0],       [4043.0, 4044.0],       [4045.0, 4046.0],       [4047.0, 4048.0]],      [[4049.0, 4050.0],       [4051.0, 4052.0],       [4053.0, 4054.0],       [4055.0, 4056.0]]],     [[[4057.0, 4058.0],       [4059.0, 4060.0],       [4061.0, 4062.0],       [4063.0, 4064.0]],      [[4065.0, 4066.0],       [4067.0, 4068.0],       [4069.0, 4070.0],       [4071.0, 4072.0]],      [[4073.0, 4074.0],       [4075.0, 4076.0],       [4077.0, 4078.0],       [4079.0, 4080.0]]]],    [[[[4081.0, 4082.0],       [4083.0, 4084.0],       [4085.0, 4086.0],       [4087.0, 4088.0]],      [[4089.0, 4090.0],       [4091.0, 4092.0],       [4093.0, 4094.0],       [4095.0, 4096.0]],      [[4097.0, 4098.0],       [4099.0, 4100.0],       [4101.0, 4102.0],       [4103.0, 4104.0]]],     [[[4105.0, 4106.0],       [4107.0, 4108.0],       [4109.0, 4110.0],       [4111.0, 4112.0]],      [[4113.0, 4114.0],       [4115.0, 4116.0],       [4117.0, 4118.0],       [4119.0, 4120.0]],      [[4121.0, 4122.0],       [4123.0, 4124.0],       [4125.0, 4126.0],       [4127.0, 4128.0]]],     [[[4129.0, 4130.0],       [4131.0, 4132.0],       [4133.0, 4134.0],       [4135.0, 4136.0]],      [[4137.0, 4138.0],       [4139.0, 4140.0],       [4141.0, 4142.0],       [4143.0, 4144.0]],      [[4145.0, 4146.0],       [4147.0, 4148.0],       [4149.0, 4150.0],       [4151.0, 4152.0]]],     [[[4153.0, 4154.0],       [4155.0, 4156.0],       [4157.0, 4158.0],       [4159.0, 4160.0]],      [[4161.0, 4162.0],       [4163.0, 4164.0],       [4165.0, 4166.0],       [4167.0, 4168.0]],      [[4169.0, 4170.0],       [4171.0, 4172.0],       [4173.0, 4174.0],       [4175.0, 4176.0]]],     [[[4177.0, 4178.0],       [4179.0, 4180.0],       [4181.0, 4182.0],       [4183.0, 4184.0]],      [[4185.0, 4186.0],       [4187.0, 4188.0],       [4189.0, 4190.0],       [4191.0, 4192.0]],      [[4193.0, 4194.0],       [4195.0, 4196.0],       [4197.0, 4198.0],       [4199.0, 4200.0]]]],    [[[[4201.0, 4202.0],       [4203.0, 4204.0],       [4205.0, 4206.0],       [4207.0, 4208.0]],      [[4209.0, 4210.0],       [4211.0, 4212.0],       [4213.0, 4214.0],       [4215.0, 4216.0]],      [[4217.0, 4218.0],       [4219.0, 4220.0],       [4221.0, 4222.0],       [4223.0, 4224.0]]],     [[[4225.0, 4226.0],       [4227.0, 4228.0],       [4229.0, 4230.0],       [4231.0, 4232.0]],      [[4233.0, 4234.0],       [4235.0, 4236.0],       [4237.0, 4238.0],       [4239.0, 4240.0]],      [[4241.0, 4242.0],       [4243.0, 4244.0],       [4245.0, 4246.0],       [4247.0, 4248.0]]],     [[[4249.0, 4250.0],       [4251.0, 4252.0],       [4253.0, 4254.0],       [4255.0, 4256.0]],      [[4257.0, 4258.0],       [4259.0, 4260.0],       [4261.0, 4262.0],       [4263.0, 4264.0]],      [[4265.0, 4266.0],       [4267.0, 4268.0],       [4269.0, 4270.0],       [4271.0, 4272.0]]],     [[[4273.0, 4274.0],       [4275.0, 4276.0],       [4277.0, 4278.0],       [4279.0, 4280.0]],      [[4281.0, 4282.0],       [4283.0, 4284.0],       [4285.0, 4286.0],       [4287.0, 4288.0]],      [[4289.0, 4290.0],       [4291.0, 4292.0],       [4293.0, 4294.0],       [4295.0, 4296.0]]],     [[[4297.0, 4298.0],       [4299.0, 4300.0],       [4301.0, 4302.0],       [4303.0, 4304.0]],      [[4305.0, 4306.0],       [4307.0, 4308.0],       [4309.0, 4310.0],       [4311.0, 4312.0]],      [[4313.0, 4314.0],       [4315.0, 4316.0],       [4317.0, 4318.0],       [4319.0, 4320.0]]]]],   [[[[[4321.0, 4322.0],       [4323.0, 4324.0],       [4325.0, 4326.0],       [4327.0, 4328.0]],      [[4329.0, 4330.0],       [4331.0, 4332.0],       [4333.0, 4334.0],       [4335.0, 4336.0]],      [[4337.0, 4338.0],       [4339.0, 4340.0],       [4341.0, 4342.0],       [4343.0, 4344.0]]],     [[[4345.0, 4346.0],       [4347.0, 4348.0],       [4349.0, 4350.0],       [4351.0, 4352.0]],      [[4353.0, 4354.0],       [4355.0, 4356.0],       [4357.0, 4358.0],       [4359.0, 4360.0]],      [[4361.0, 4362.0],       [4363.0, 4364.0],       [4365.0, 4366.0],       [4367.0, 4368.0]]],     [[[4369.0, 4370.0],       [4371.0, 4372.0],       [4373.0, 4374.0],       [4375.0, 4376.0]],      [[4377.0, 4378.0],       [4379.0, 4380.0],       [4381.0, 4382.0],       [4383.0, 4384.0]],      [[4385.0, 4386.0],       [4387.0, 4388.0],       [4389.0, 4390.0],       [4391.0, 4392.0]]],     [[[4393.0, 4394.0],       [4395.0, 4396.0],       [4397.0, 4398.0],       [4399.0, 4400.0]],      [[4401.0, 4402.0],       [4403.0, 4404.0],       [4405.0, 4406.0],       [4407.0, 4408.0]],      [[4409.0, 4410.0],       [4411.0, 4412.0],       [4413.0, 4414.0],       [4415.0, 4416.0]]],     [[[4417.0, 4418.0],       [4419.0, 4420.0],       [4421.0, 4422.0],       [4423.0, 4424.0]],      [[4425.0, 4426.0],       [4427.0, 4428.0],       [4429.0, 4430.0],       [4431.0, 4432.0]],      [[4433.0, 4434.0],       [4435.0, 4436.0],       [4437.0, 4438.0],       [4439.0, 4440.0]]]],    [[[[4441.0, 4442.0],       [4443.0, 4444.0],       [4445.0, 4446.0],       [4447.0, 4448.0]],      [[4449.0, 4450.0],       [4451.0, 4452.0],       [4453.0, 4454.0],       [4455.0, 4456.0]],      [[4457.0, 4458.0],       [4459.0, 4460.0],       [4461.0, 4462.0],       [4463.0, 4464.0]]],     [[[4465.0, 4466.0],       [4467.0, 4468.0],       [4469.0, 4470.0],       [4471.0, 4472.0]],      [[4473.0, 4474.0],       [4475.0, 4476.0],       [4477.0, 4478.0],       [4479.0, 4480.0]],      [[4481.0, 4482.0],       [4483.0, 4484.0],       [4485.0, 4486.0],       [4487.0, 4488.0]]],     [[[4489.0, 4490.0],       [4491.0, 4492.0],       [4493.0, 4494.0],       [4495.0, 4496.0]],      [[4497.0, 4498.0],       [4499.0, 4500.0],       [4501.0, 4502.0],       [4503.0, 4504.0]],      [[4505.0, 4506.0],       [4507.0, 4508.0],       [4509.0, 4510.0],       [4511.0, 4512.0]]],     [[[4513.0, 4514.0],       [4515.0, 4516.0],       [4517.0, 4518.0],       [4519.0, 4520.0]],      [[4521.0, 4522.0],       [4523.0, 4524.0],       [4525.0, 4526.0],       [4527.0, 4528.0]],      [[4529.0, 4530.0],       [4531.0, 4532.0],       [4533.0, 4534.0],       [4535.0, 4536.0]]],     [[[4537.0, 4538.0],       [4539.0, 4540.0],       [4541.0, 4542.0],       [4543.0, 4544.0]],      [[4545.0, 4546.0],       [4547.0, 4548.0],       [4549.0, 4550.0],       [4551.0, 4552.0]],      [[4553.0, 4554.0],       [4555.0, 4556.0],       [4557.0, 4558.0],       [4559.0, 4560.0]]]],    [[[[4561.0, 4562.0],       [4563.0, 4564.0],       [4565.0, 4566.0],       [4567.0, 4568.0]],      [[4569.0, 4570.0],       [4571.0, 4572.0],       [4573.0, 4574.0],       [4575.0, 4576.0]],      [[4577.0, 4578.0],       [4579.0, 4580.0],       [4581.0, 4582.0],       [4583.0, 4584.0]]],     [[[4585.0, 4586.0],       [4587.0, 4588.0],       [4589.0, 4590.0],       [4591.0, 4592.0]],      [[4593.0, 4594.0],       [4595.0, 4596.0],       [4597.0, 4598.0],       [4599.0, 4600.0]],      [[4601.0, 4602.0],       [4603.0, 4604.0],       [4605.0, 4606.0],       [4607.0, 4608.0]]],     [[[4609.0, 4610.0],       [4611.0, 4612.0],       [4613.0, 4614.0],       [4615.0, 4616.0]],      [[4617.0, 4618.0],       [4619.0, 4620.0],       [4621.0, 4622.0],       [4623.0, 4624.0]],      [[4625.0, 4626.0],       [4627.0, 4628.0],       [4629.0, 4630.0],       [4631.0, 4632.0]]],     [[[4633.0, 4634.0],       [4635.0, 4636.0],       [4637.0, 4638.0],       [4639.0, 4640.0]],      [[4641.0, 4642.0],       [4643.0, 4644.0],       [4645.0, 4646.0],       [4647.0, 4648.0]],      [[4649.0, 4650.0],       [4651.0, 4652.0],       [4653.0, 4654.0],       [4655.0, 4656.0]]],     [[[4657.0, 4658.0],       [4659.0, 4660.0],       [4661.0, 4662.0],       [4663.0, 4664.0]],      [[4665.0, 4666.0],       [4667.0, 4668.0],       [4669.0, 4670.0],       [4671.0, 4672.0]],      [[4673.0, 4674.0],       [4675.0, 4676.0],       [4677.0, 4678.0],       [4679.0, 4680.0]]]]],   [[[[[4681.0, 4682.0],       [4683.0, 4684.0],       [4685.0, 4686.0],       [4687.0, 4688.0]],      [[4689.0, 4690.0],       [4691.0, 4692.0],       [4693.0, 4694.0],       [4695.0, 4696.0]],      [[4697.0, 4698.0],       [4699.0, 4700.0],       [4701.0, 4702.0],       [4703.0, 4704.0]]],     [[[4705.0, 4706.0],       [4707.0, 4708.0],       [4709.0, 4710.0],       [4711.0, 4712.0]],      [[4713.0, 4714.0],       [4715.0, 4716.0],       [4717.0, 4718.0],       [4719.0, 4720.0]],      [[4721.0, 4722.0],       [4723.0, 4724.0],       [4725.0, 4726.0],       [4727.0, 4728.0]]],     [[[4729.0, 4730.0],       [4731.0, 4732.0],       [4733.0, 4734.0],       [4735.0, 4736.0]],      [[4737.0, 4738.0],       [4739.0, 4740.0],       [4741.0, 4742.0],       [4743.0, 4744.0]],      [[4745.0, 4746.0],       [4747.0, 4748.0],       [4749.0, 4750.0],       [4751.0, 4752.0]]],     [[[4753.0, 4754.0],       [4755.0, 4756.0],       [4757.0, 4758.0],       [4759.0, 4760.0]],      [[4761.0, 4762.0],       [4763.0, 4764.0],       [4765.0, 4766.0],       [4767.0, 4768.0]],      [[4769.0, 4770.0],       [4771.0, 4772.0],       [4773.0, 4774.0],       [4775.0, 4776.0]]],     [[[4777.0, 4778.0],       [4779.0, 4780.0],       [4781.0, 4782.0],       [4783.0, 4784.0]],      [[4785.0, 4786.0],       [4787.0, 4788.0],       [4789.0, 4790.0],       [4791.0, 4792.0]],      [[4793.0, 4794.0],       [4795.0, 4796.0],       [4797.0, 4798.0],       [4799.0, 4800.0]]]],    [[[[4801.0, 4802.0],       [4803.0, 4804.0],       [4805.0, 4806.0],       [4807.0, 4808.0]],      [[4809.0, 4810.0],       [4811.0, 4812.0],       [4813.0, 4814.0],       [4815.0, 4816.0]],      [[4817.0, 4818.0],       [4819.0, 4820.0],       [4821.0, 4822.0],       [4823.0, 4824.0]]],     [[[4825.0, 4826.0],       [4827.0, 4828.0],       [4829.0, 4830.0],       [4831.0, 4832.0]],      [[4833.0, 4834.0],       [4835.0, 4836.0],       [4837.0, 4838.0],       [4839.0, 4840.0]],      [[4841.0, 4842.0],       [4843.0, 4844.0],       [4845.0, 4846.0],       [4847.0, 4848.0]]],     [[[4849.0, 4850.0],       [4851.0, 4852.0],       [4853.0, 4854.0],       [4855.0, 4856.0]],      [[4857.0, 4858.0],       [4859.0, 4860.0],       [4861.0, 4862.0],       [4863.0, 4864.0]],      [[4865.0, 4866.0],       [4867.0, 4868.0],       [4869.0, 4870.0],       [4871.0, 4872.0]]],     [[[4873.0, 4874.0],       [4875.0, 4876.0],       [4877.0, 4878.0],       [4879.0, 4880.0]],      [[4881.0, 4882.0],       [4883.0, 4884.0],       [4885.0, 4886.0],       [4887.0, 4888.0]],      [[4889.0, 4890.0],       [4891.0, 4892.0],       [4893.0, 4894.0],       [4895.0, 4896.0]]],     [[[4897.0, 4898.0],       [4899.0, 4900.0],       [4901.0, 4902.0],       [4903.0, 4904.0]],      [[4905.0, 4906.0],       [4907.0, 4908.0],       [4909.0, 4910.0],       [4911.0, 4912.0]],      [[4913.0, 4914.0],       [4915.0, 4916.0],       [4917.0, 4918.0],       [4919.0, 4920.0]]]],    [[[[4921.0, 4922.0],       [4923.0, 4924.0],       [4925.0, 4926.0],       [4927.0, 4928.0]],      [[4929.0, 4930.0],       [4931.0, 4932.0],       [4933.0, 4934.0],       [4935.0, 4936.0]],      [[4937.0, 4938.0],       [4939.0, 4940.0],       [4941.0, 4942.0],       [4943.0, 4944.0]]],     [[[4945.0, 4946.0],       [4947.0, 4948.0],       [4949.0, 4950.0],       [4951.0, 4952.0]],      [[4953.0, 4954.0],       [4955.0, 4956.0],       [4957.0, 4958.0],       [4959.0, 4960.0]],      [[4961.0, 4962.0],       [4963.0, 4964.0],       [4965.0, 4966.0],       [4967.0, 4968.0]]],     [[[4969.0, 4970.0],       [4971.0, 4972.0],       [4973.0, 4974.0],       [4975.0, 4976.0]],      [[4977.0, 4978.0],       [4979.0, 4980.0],       [4981.0, 4982.0],       [4983.0, 4984.0]],      [[4985.0, 4986.0],       [4987.0, 4988.0],       [4989.0, 4990.0],       [4991.0, 4992.0]]],     [[[4993.0, 4994.0],       [4995.0, 4996.0],       [4997.0, 4998.0],       [4999.0, 5000.0]],      [[5001.0, 5002.0],       [5003.0, 5004.0],       [5005.0, 5006.0],       [5007.0, 5008.0]],      [[5009.0, 5010.0],       [5011.0, 5012.0],       [5013.0, 5014.0],       [5015.0, 5016.0]]],     [[[5017.0, 5018.0],       [5019.0, 5020.0],       [5021.0, 5022.0],       [5023.0, 5024.0]],      [[5025.0, 5026.0],       [5027.0, 5028.0],       [5029.0, 5030.0],       [5031.0, 5032.0]],      [[5033.0, 5034.0],       [5035.0, 5036.0],       [5037.0, 5038.0],       [5039.0, 5040.0]]]]]],  [[[[[[5041.0, 5042.0],       [5043.0, 5044.0],       [5045.0, 5046.0],       [5047.0, 5048.0]],      [[5049.0, 5050.0],       [5051.0, 5052.0],       [5053.0, 5054.0],       [5055.0, 5056.0]],      [[5057.0, 5058.0],       [5059.0, 5060.0],       [5061.0, 5062.0],       [5063.0, 5064.0]]],     [[[5065.0, 5066.0],       [5067.0, 5068.0],       [5069.0, 5070.0],       [5071.0, 5072.0]],      [[5073.0, 5074.0],       [5075.0, 5076.0],       [5077.0, 5078.0],       [5079.0, 5080.0]],      [[5081.0, 5082.0],       [5083.0, 5084.0],       [5085.0, 5086.0],       [5087.0, 5088.0]]],     [[[5089.0, 5090.0],       [5091.0, 5092.0],       [5093.0, 5094.0],       [5095.0, 5096.0]],      [[5097.0, 5098.0],       [5099.0, 5100.0],       [5101.0, 5102.0],       [5103.0, 5104.0]],      [[5105.0, 5106.0],       [5107.0, 5108.0],       [5109.0, 5110.0],       [5111.0, 5112.0]]],     [[[5113.0, 5114.0],       [5115.0, 5116.0],       [5117.0, 5118.0],       [5119.0, 5120.0]],      [[5121.0, 5122.0],       [5123.0, 5124.0],       [5125.0, 5126.0],       [5127.0, 5128.0]],      [[5129.0, 5130.0],       [5131.0, 5132.0],       [5133.0, 5134.0],       [5135.0, 5136.0]]],     [[[5137.0, 5138.0],       [5139.0, 5140.0],       [5141.0, 5142.0],       [5143.0, 5144.0]],      [[5145.0, 5146.0],       [5147.0, 5148.0],       [5149.0, 5150.0],       [5151.0, 5152.0]],      [[5153.0, 5154.0],       [5155.0, 5156.0],       [5157.0, 5158.0],       [5159.0, 5160.0]]]],    [[[[5161.0, 5162.0],       [5163.0, 5164.0],       [5165.0, 5166.0],       [5167.0, 5168.0]],      [[5169.0, 5170.0],       [5171.0, 5172.0],       [5173.0, 5174.0],       [5175.0, 5176.0]],      [[5177.0, 5178.0],       [5179.0, 5180.0],       [5181.0, 5182.0],       [5183.0, 5184.0]]],     [[[5185.0, 5186.0],       [5187.0, 5188.0],       [5189.0, 5190.0],       [5191.0, 5192.0]],      [[5193.0, 5194.0],       [5195.0, 5196.0],       [5197.0, 5198.0],       [5199.0, 5200.0]],      [[5201.0, 5202.0],       [5203.0, 5204.0],       [5205.0, 5206.0],       [5207.0, 5208.0]]],     [[[5209.0, 5210.0],       [5211.0, 5212.0],       [5213.0, 5214.0],       [5215.0, 5216.0]],      [[5217.0, 5218.0],       [5219.0, 5220.0],       [5221.0, 5222.0],       [5223.0, 5224.0]],      [[5225.0, 5226.0],       [5227.0, 5228.0],       [5229.0, 5230.0],       [5231.0, 5232.0]]],     [[[5233.0, 5234.0],       [5235.0, 5236.0],       [5237.0, 5238.0],       [5239.0, 5240.0]],      [[5241.0, 5242.0],       [5243.0, 5244.0],       [5245.0, 5246.0],       [5247.0, 5248.0]],      [[5249.0, 5250.0],       [5251.0, 5252.0],       [5253.0, 5254.0],       [5255.0, 5256.0]]],     [[[5257.0, 5258.0],       [5259.0, 5260.0],       [5261.0, 5262.0],       [5263.0, 5264.0]],      [[5265.0, 5266.0],       [5267.0, 5268.0],       [5269.0, 5270.0],       [5271.0, 5272.0]],      [[5273.0, 5274.0],       [5275.0, 5276.0],       [5277.0, 5278.0],       [5279.0, 5280.0]]]],    [[[[5281.0, 5282.0],       [5283.0, 5284.0],       [5285.0, 5286.0],       [5287.0, 5288.0]],      [[5289.0, 5290.0],       [5291.0, 5292.0],       [5293.0, 5294.0],       [5295.0, 5296.0]],      [[5297.0, 5298.0],       [5299.0, 5300.0],       [5301.0, 5302.0],       [5303.0, 5304.0]]],     [[[5305.0, 5306.0],       [5307.0, 5308.0],       [5309.0, 5310.0],       [5311.0, 5312.0]],      [[5313.0, 5314.0],       [5315.0, 5316.0],       [5317.0, 5318.0],       [5319.0, 5320.0]],      [[5321.0, 5322.0],       [5323.0, 5324.0],       [5325.0, 5326.0],       [5327.0, 5328.0]]],     [[[5329.0, 5330.0],       [5331.0, 5332.0],       [5333.0, 5334.0],       [5335.0, 5336.0]],      [[5337.0, 5338.0],       [5339.0, 5340.0],       [5341.0, 5342.0],       [5343.0, 5344.0]],      [[5345.0, 5346.0],       [5347.0, 5348.0],       [5349.0, 5350.0],       [5351.0, 5352.0]]],     [[[5353.0, 5354.0],       [5355.0, 5356.0],       [5357.0, 5358.0],       [5359.0, 5360.0]],      [[5361.0, 5362.0],       [5363.0, 5364.0],       [5365.0, 5366.0],       [5367.0, 5368.0]],      [[5369.0, 5370.0],       [5371.0, 5372.0],       [5373.0, 5374.0],       [5375.0, 5376.0]]],     [[[5377.0, 5378.0],       [5379.0, 5380.0],       [5381.0, 5382.0],       [5383.0, 5384.0]],      [[5385.0, 5386.0],       [5387.0, 5388.0],       [5389.0, 5390.0],       [5391.0, 5392.0]],      [[5393.0, 5394.0],       [5395.0, 5396.0],       [5397.0, 5398.0],       [5399.0, 5400.0]]]]],   [[[[[5401.0, 5402.0],       [5403.0, 5404.0],       [5405.0, 5406.0],       [5407.0, 5408.0]],      [[5409.0, 5410.0],       [5411.0, 5412.0],       [5413.0, 5414.0],       [5415.0, 5416.0]],      [[5417.0, 5418.0],       [5419.0, 5420.0],       [5421.0, 5422.0],       [5423.0, 5424.0]]],     [[[5425.0, 5426.0],       [5427.0, 5428.0],       [5429.0, 5430.0],       [5431.0, 5432.0]],      [[5433.0, 5434.0],       [5435.0, 5436.0],       [5437.0, 5438.0],       [5439.0, 5440.0]],      [[5441.0, 5442.0],       [5443.0, 5444.0],       [5445.0, 5446.0],       [5447.0, 5448.0]]],     [[[5449.0, 5450.0],       [5451.0, 5452.0],       [5453.0, 5454.0],       [5455.0, 5456.0]],      [[5457.0, 5458.0],       [5459.0, 5460.0],       [5461.0, 5462.0],       [5463.0, 5464.0]],      [[5465.0, 5466.0],       [5467.0, 5468.0],       [5469.0, 5470.0],       [5471.0, 5472.0]]],     [[[5473.0, 5474.0],       [5475.0, 5476.0],       [5477.0, 5478.0],       [5479.0, 5480.0]],      [[5481.0, 5482.0],       [5483.0, 5484.0],       [5485.0, 5486.0],       [5487.0, 5488.0]],      [[5489.0, 5490.0],       [5491.0, 5492.0],       [5493.0, 5494.0],       [5495.0, 5496.0]]],     [[[5497.0, 5498.0],       [5499.0, 5500.0],       [5501.0, 5502.0],       [5503.0, 5504.0]],      [[5505.0, 5506.0],       [5507.0, 5508.0],       [5509.0, 5510.0],       [5511.0, 5512.0]],      [[5513.0, 5514.0],       [5515.0, 5516.0],       [5517.0, 5518.0],       [5519.0, 5520.0]]]],    [[[[5521.0, 5522.0],       [5523.0, 5524.0],       [5525.0, 5526.0],       [5527.0, 5528.0]],      [[5529.0, 5530.0],       [5531.0, 5532.0],       [5533.0, 5534.0],       [5535.0, 5536.0]],      [[5537.0, 5538.0],       [5539.0, 5540.0],       [5541.0, 5542.0],       [5543.0, 5544.0]]],     [[[5545.0, 5546.0],       [5547.0, 5548.0],       [5549.0, 5550.0],       [5551.0, 5552.0]],      [[5553.0, 5554.0],       [5555.0, 5556.0],       [5557.0, 5558.0],       [5559.0, 5560.0]],      [[5561.0, 5562.0],       [5563.0, 5564.0],       [5565.0, 5566.0],       [5567.0, 5568.0]]],     [[[5569.0, 5570.0],       [5571.0, 5572.0],       [5573.0, 5574.0],       [5575.0, 5576.0]],      [[5577.0, 5578.0],       [5579.0, 5580.0],       [5581.0, 5582.0],       [5583.0, 5584.0]],      [[5585.0, 5586.0],       [5587.0, 5588.0],       [5589.0, 5590.0],       [5591.0, 5592.0]]],     [[[5593.0, 5594.0],       [5595.0, 5596.0],       [5597.0, 5598.0],       [5599.0, 5600.0]],      [[5601.0, 5602.0],       [5603.0, 5604.0],       [5605.0, 5606.0],       [5607.0, 5608.0]],      [[5609.0, 5610.0],       [5611.0, 5612.0],       [5613.0, 5614.0],       [5615.0, 5616.0]]],     [[[5617.0, 5618.0],       [5619.0, 5620.0],       [5621.0, 5622.0],       [5623.0, 5624.0]],      [[5625.0, 5626.0],       [5627.0, 5628.0],       [5629.0, 5630.0],       [5631.0, 5632.0]],      [[5633.0, 5634.0],       [5635.0, 5636.0],       [5637.0, 5638.0],       [5639.0, 5640.0]]]],    [[[[5641.0, 5642.0],       [5643.0, 5644.0],       [5645.0, 5646.0],       [5647.0, 5648.0]],      [[5649.0, 5650.0],       [5651.0, 5652.0],       [5653.0, 5654.0],       [5655.0, 5656.0]],      [[5657.0, 5658.0],       [5659.0, 5660.0],       [5661.0, 5662.0],       [5663.0, 5664.0]]],     [[[5665.0, 5666.0],       [5667.0, 5668.0],       [5669.0, 5670.0],       [5671.0, 5672.0]],      [[5673.0, 5674.0],       [5675.0, 5676.0],       [5677.0, 5678.0],       [5679.0, 5680.0]],      [[5681.0, 5682.0],       [5683.0, 5684.0],       [5685.0, 5686.0],       [5687.0, 5688.0]]],     [[[5689.0, 5690.0],       [5691.0, 5692.0],       [5693.0, 5694.0],       [5695.0, 5696.0]],      [[5697.0, 5698.0],       [5699.0, 5700.0],       [5701.0, 5702.0],       [5703.0, 5704.0]],      [[5705.0, 5706.0],       [5707.0, 5708.0],       [5709.0, 5710.0],       [5711.0, 5712.0]]],     [[[5713.0, 5714.0],       [5715.0, 5716.0],       [5717.0, 5718.0],       [5719.0, 5720.0]],      [[5721.0, 5722.0],       [5723.0, 5724.0],       [5725.0, 5726.0],       [5727.0, 5728.0]],      [[5729.0, 5730.0],       [5731.0, 5732.0],       [5733.0, 5734.0],       [5735.0, 5736.0]]],     [[[5737.0, 5738.0],       [5739.0, 5740.0],       [5741.0, 5742.0],       [5743.0, 5744.0]],      [[5745.0, 5746.0],       [5747.0, 5748.0],       [5749.0, 5750.0],       [5751.0, 5752.0]],      [[5753.0, 5754.0],       [5755.0, 5756.0],       [5757.0, 5758.0],       [5759.0, 5760.0]]]]],   [[[[[5761.0, 5762.0],       [5763.0, 5764.0],       [5765.0, 5766.0],       [5767.0, 5768.0]],      [[5769.0, 5770.0],       [5771.0, 5772.0],       [5773.0, 5774.0],       [5775.0, 5776.0]],      [[5777.0, 5778.0],       [5779.0, 5780.0],       [5781.0, 5782.0],       [5783.0, 5784.0]]],     [[[5785.0, 5786.0],       [5787.0, 5788.0],       [5789.0, 5790.0],       [5791.0, 5792.0]],      [[5793.0, 5794.0],       [5795.0, 5796.0],       [5797.0, 5798.0],       [5799.0, 5800.0]],      [[5801.0, 5802.0],       [5803.0, 5804.0],       [5805.0, 5806.0],       [5807.0, 5808.0]]],     [[[5809.0, 5810.0],       [5811.0, 5812.0],       [5813.0, 5814.0],       [5815.0, 5816.0]],      [[5817.0, 5818.0],       [5819.0, 5820.0],       [5821.0, 5822.0],       [5823.0, 5824.0]],      [[5825.0, 5826.0],       [5827.0, 5828.0],       [5829.0, 5830.0],       [5831.0, 5832.0]]],     [[[5833.0, 5834.0],       [5835.0, 5836.0],       [5837.0, 5838.0],       [5839.0, 5840.0]],      [[5841.0, 5842.0],       [5843.0, 5844.0],       [5845.0, 5846.0],       [5847.0, 5848.0]],      [[5849.0, 5850.0],       [5851.0, 5852.0],       [5853.0, 5854.0],       [5855.0, 5856.0]]],     [[[5857.0, 5858.0],       [5859.0, 5860.0],       [5861.0, 5862.0],       [5863.0, 5864.0]],      [[5865.0, 5866.0],       [5867.0, 5868.0],       [5869.0, 5870.0],       [5871.0, 5872.0]],      [[5873.0, 5874.0],       [5875.0, 5876.0],       [5877.0, 5878.0],       [5879.0, 5880.0]]]],    [[[[5881.0, 5882.0],       [5883.0, 5884.0],       [5885.0, 5886.0],       [5887.0, 5888.0]],      [[5889.0, 5890.0],       [5891.0, 5892.0],       [5893.0, 5894.0],       [5895.0, 5896.0]],      [[5897.0, 5898.0],       [5899.0, 5900.0],       [5901.0, 5902.0],       [5903.0, 5904.0]]],     [[[5905.0, 5906.0],       [5907.0, 5908.0],       [5909.0, 5910.0],       [5911.0, 5912.0]],      [[5913.0, 5914.0],       [5915.0, 5916.0],       [5917.0, 5918.0],       [5919.0, 5920.0]],      [[5921.0, 5922.0],       [5923.0, 5924.0],       [5925.0, 5926.0],       [5927.0, 5928.0]]],     [[[5929.0, 5930.0],       [5931.0, 5932.0],       [5933.0, 5934.0],       [5935.0, 5936.0]],      [[5937.0, 5938.0],       [5939.0, 5940.0],       [5941.0, 5942.0],       [5943.0, 5944.0]],      [[5945.0, 5946.0],       [5947.0, 5948.0],       [5949.0, 5950.0],       [5951.0, 5952.0]]],     [[[5953.0, 5954.0],       [5955.0, 5956.0],       [5957.0, 5958.0],       [5959.0, 5960.0]],      [[5961.0, 5962.0],       [5963.0, 5964.0],       [5965.0, 5966.0],       [5967.0, 5968.0]],      [[5969.0, 5970.0],       [5971.0, 5972.0],       [5973.0, 5974.0],       [5975.0, 5976.0]]],     [[[5977.0, 5978.0],       [5979.0, 5980.0],       [5981.0, 5982.0],       [5983.0, 5984.0]],      [[5985.0, 5986.0],       [5987.0, 5988.0],       [5989.0, 5990.0],       [5991.0, 5992.0]],      [[5993.0, 5994.0],       [5995.0, 5996.0],       [5997.0, 5998.0],       [5999.0, 6000.0]]]],    [[[[6001.0, 6002.0],       [6003.0, 6004.0],       [6005.0, 6006.0],       [6007.0, 6008.0]],      [[6009.0, 6010.0],       [6011.0, 6012.0],       [6013.0, 6014.0],       [6015.0, 6016.0]],      [[6017.0, 6018.0],       [6019.0, 6020.0],       [6021.0, 6022.0],       [6023.0, 6024.0]]],     [[[6025.0, 6026.0],       [6027.0, 6028.0],       [6029.0, 6030.0],       [6031.0, 6032.0]],      [[6033.0, 6034.0],       [6035.0, 6036.0],       [6037.0, 6038.0],       [6039.0, 6040.0]],      [[6041.0, 6042.0],       [6043.0, 6044.0],       [6045.0, 6046.0],       [6047.0, 6048.0]]],     [[[6049.0, 6050.0],       [6051.0, 6052.0],       [6053.0, 6054.0],       [6055.0, 6056.0]],      [[6057.0, 6058.0],       [6059.0, 6060.0],       [6061.0, 6062.0],       [6063.0, 6064.0]],      [[6065.0, 6066.0],       [6067.0, 6068.0],       [6069.0, 6070.0],       [6071.0, 6072.0]]],     [[[6073.0, 6074.0],       [6075.0, 6076.0],       [6077.0, 6078.0],       [6079.0, 6080.0]],      [[6081.0, 6082.0],       [6083.0, 6084.0],       [6085.0, 6086.0],       [6087.0, 6088.0]],      [[6089.0, 6090.0],       [6091.0, 6092.0],       [6093.0, 6094.0],       [6095.0, 6096.0]]],     [[[6097.0, 6098.0],       [6099.0, 6100.0],       [6101.0, 6102.0],       [6103.0, 6104.0]],      [[6105.0, 6106.0],       [6107.0, 6108.0],       [6109.0, 6110.0],       [6111.0, 6112.0]],      [[6113.0, 6114.0],       [6115.0, 6116.0],       [6117.0, 6118.0],       [6119.0, 6120.0]]]]],   [[[[[6121.0, 6122.0],       [6123.0, 6124.0],       [6125.0, 6126.0],       [6127.0, 6128.0]],      [[6129.0, 6130.0],       [6131.0, 6132.0],       [6133.0, 6134.0],       [6135.0, 6136.0]],      [[6137.0, 6138.0],       [6139.0, 6140.0],       [6141.0, 6142.0],       [6143.0, 6144.0]]],     [[[6145.0, 6146.0],       [6147.0, 6148.0],       [6149.0, 6150.0],       [6151.0, 6152.0]],      [[6153.0, 6154.0],       [6155.0, 6156.0],       [6157.0, 6158.0],       [6159.0, 6160.0]],      [[6161.0, 6162.0],       [6163.0, 6164.0],       [6165.0, 6166.0],       [6167.0, 6168.0]]],     [[[6169.0, 6170.0],       [6171.0, 6172.0],       [6173.0, 6174.0],       [6175.0, 6176.0]],      [[6177.0, 6178.0],       [6179.0, 6180.0],       [6181.0, 6182.0],       [6183.0, 6184.0]],      [[6185.0, 6186.0],       [6187.0, 6188.0],       [6189.0, 6190.0],       [6191.0, 6192.0]]],     [[[6193.0, 6194.0],       [6195.0, 6196.0],       [6197.0, 6198.0],       [6199.0, 6200.0]],      [[6201.0, 6202.0],       [6203.0, 6204.0],       [6205.0, 6206.0],       [6207.0, 6208.0]],      [[6209.0, 6210.0],       [6211.0, 6212.0],       [6213.0, 6214.0],       [6215.0, 6216.0]]],     [[[6217.0, 6218.0],       [6219.0, 6220.0],       [6221.0, 6222.0],       [6223.0, 6224.0]],      [[6225.0, 6226.0],       [6227.0, 6228.0],       [6229.0, 6230.0],       [6231.0, 6232.0]],      [[6233.0, 6234.0],       [6235.0, 6236.0],       [6237.0, 6238.0],       [6239.0, 6240.0]]]],    [[[[6241.0, 6242.0],       [6243.0, 6244.0],       [6245.0, 6246.0],       [6247.0, 6248.0]],      [[6249.0, 6250.0],       [6251.0, 6252.0],       [6253.0, 6254.0],       [6255.0, 6256.0]],      [[6257.0, 6258.0],       [6259.0, 6260.0],       [6261.0, 6262.0],       [6263.0, 6264.0]]],     [[[6265.0, 6266.0],       [6267.0, 6268.0],       [6269.0, 6270.0],       [6271.0, 6272.0]],      [[6273.0, 6274.0],       [6275.0, 6276.0],       [6277.0, 6278.0],       [6279.0, 6280.0]],      [[6281.0, 6282.0],       [6283.0, 6284.0],       [6285.0, 6286.0],       [6287.0, 6288.0]]],     [[[6289.0, 6290.0],       [6291.0, 6292.0],       [6293.0, 6294.0],       [6295.0, 6296.0]],      [[6297.0, 6298.0],       [6299.0, 6300.0],       [6301.0, 6302.0],       [6303.0, 6304.0]],      [[6305.0, 6306.0],       [6307.0, 6308.0],       [6309.0, 6310.0],       [6311.0, 6312.0]]],     [[[6313.0, 6314.0],       [6315.0, 6316.0],       [6317.0, 6318.0],       [6319.0, 6320.0]],      [[6321.0, 6322.0],       [6323.0, 6324.0],       [6325.0, 6326.0],       [6327.0, 6328.0]],      [[6329.0, 6330.0],       [6331.0, 6332.0],       [6333.0, 6334.0],       [6335.0, 6336.0]]],     [[[6337.0, 6338.0],       [6339.0, 6340.0],       [6341.0, 6342.0],       [6343.0, 6344.0]],      [[6345.0, 6346.0],       [6347.0, 6348.0],       [6349.0, 6350.0],       [6351.0, 6352.0]],      [[6353.0, 6354.0],       [6355.0, 6356.0],       [6357.0, 6358.0],       [6359.0, 6360.0]]]],    [[[[6361.0, 6362.0],       [6363.0, 6364.0],       [6365.0, 6366.0],       [6367.0, 6368.0]],      [[6369.0, 6370.0],       [6371.0, 6372.0],       [6373.0, 6374.0],       [6375.0, 6376.0]],      [[6377.0, 6378.0],       [6379.0, 6380.0],       [6381.0, 6382.0],       [6383.0, 6384.0]]],     [[[6385.0, 6386.0],       [6387.0, 6388.0],       [6389.0, 6390.0],       [6391.0, 6392.0]],      [[6393.0, 6394.0],       [6395.0, 6396.0],       [6397.0, 6398.0],       [6399.0, 6400.0]],      [[6401.0, 6402.0],       [6403.0, 6404.0],       [6405.0, 6406.0],       [6407.0, 6408.0]]],     [[[6409.0, 6410.0],       [6411.0, 6412.0],       [6413.0, 6414.0],       [6415.0, 6416.0]],      [[6417.0, 6418.0],       [6419.0, 6420.0],       [6421.0, 6422.0],       [6423.0, 6424.0]],      [[6425.0, 6426.0],       [6427.0, 6428.0],       [6429.0, 6430.0],       [6431.0, 6432.0]]],     [[[6433.0, 6434.0],       [6435.0, 6436.0],       [6437.0, 6438.0],       [6439.0, 6440.0]],      [[6441.0, 6442.0],       [6443.0, 6444.0],       [6445.0, 6446.0],       [6447.0, 6448.0]],      [[6449.0, 6450.0],       [6451.0, 6452.0],       [6453.0, 6454.0],       [6455.0, 6456.0]]],     [[[6457.0, 6458.0],       [6459.0, 6460.0],       [6461.0, 6462.0],       [6463.0, 6464.0]],      [[6465.0, 6466.0],       [6467.0, 6468.0],       [6469.0, 6470.0],       [6471.0, 6472.0]],      [[6473.0, 6474.0],       [6475.0, 6476.0],       [6477.0, 6478.0],       [6479.0, 6480.0]]]]],   [[[[[6481.0, 6482.0],       [6483.0, 6484.0],       [6485.0, 6486.0],       [6487.0, 6488.0]],      [[6489.0, 6490.0],       [6491.0, 6492.0],       [6493.0, 6494.0],       [6495.0, 6496.0]],      [[6497.0, 6498.0],       [6499.0, 6500.0],       [6501.0, 6502.0],       [6503.0, 6504.0]]],     [[[6505.0, 6506.0],       [6507.0, 6508.0],       [6509.0, 6510.0],       [6511.0, 6512.0]],      [[6513.0, 6514.0],       [6515.0, 6516.0],       [6517.0, 6518.0],       [6519.0, 6520.0]],      [[6521.0, 6522.0],       [6523.0, 6524.0],       [6525.0, 6526.0],       [6527.0, 6528.0]]],     [[[6529.0, 6530.0],       [6531.0, 6532.0],       [6533.0, 6534.0],       [6535.0, 6536.0]],      [[6537.0, 6538.0],       [6539.0, 6540.0],       [6541.0, 6542.0],       [6543.0, 6544.0]],      [[6545.0, 6546.0],       [6547.0, 6548.0],       [6549.0, 6550.0],       [6551.0, 6552.0]]],     [[[6553.0, 6554.0],       [6555.0, 6556.0],       [6557.0, 6558.0],       [6559.0, 6560.0]],      [[6561.0, 6562.0],       [6563.0, 6564.0],       [6565.0, 6566.0],       [6567.0, 6568.0]],      [[6569.0, 6570.0],       [6571.0, 6572.0],       [6573.0, 6574.0],       [6575.0, 6576.0]]],     [[[6577.0, 6578.0],       [6579.0, 6580.0],       [6581.0, 6582.0],       [6583.0, 6584.0]],      [[6585.0, 6586.0],       [6587.0, 6588.0],       [6589.0, 6590.0],       [6591.0, 6592.0]],      [[6593.0, 6594.0],       [6595.0, 6596.0],       [6597.0, 6598.0],       [6599.0, 6600.0]]]],    [[[[6601.0, 6602.0],       [6603.0, 6604.0],       [6605.0, 6606.0],       [6607.0, 6608.0]],      [[6609.0, 6610.0],       [6611.0, 6612.0],       [6613.0, 6614.0],       [6615.0, 6616.0]],      [[6617.0, 6618.0],       [6619.0, 6620.0],       [6621.0, 6622.0],       [6623.0, 6624.0]]],     [[[6625.0, 6626.0],       [6627.0, 6628.0],       [6629.0, 6630.0],       [6631.0, 6632.0]],      [[6633.0, 6634.0],       [6635.0, 6636.0],       [6637.0, 6638.0],       [6639.0, 6640.0]],      [[6641.0, 6642.0],       [6643.0, 6644.0],       [6645.0, 6646.0],       [6647.0, 6648.0]]],     [[[6649.0, 6650.0],       [6651.0, 6652.0],       [6653.0, 6654.0],       [6655.0, 6656.0]],      [[6657.0, 6658.0],       [6659.0, 6660.0],       [6661.0, 6662.0],       [6663.0, 6664.0]],      [[6665.0, 6666.0],       [6667.0, 6668.0],       [6669.0, 6670.0],       [6671.0, 6672.0]]],     [[[6673.0, 6674.0],       [6675.0, 6676.0],       [6677.0, 6678.0],       [6679.0, 6680.0]],      [[6681.0, 6682.0],       [6683.0, 6684.0],       [6685.0, 6686.0],       [6687.0, 6688.0]],      [[6689.0, 6690.0],       [6691.0, 6692.0],       [6693.0, 6694.0],       [6695.0, 6696.0]]],     [[[6697.0, 6698.0],       [6699.0, 6700.0],       [6701.0, 6702.0],       [6703.0, 6704.0]],      [[6705.0, 6706.0],       [6707.0, 6708.0],       [6709.0, 6710.0],       [6711.0, 6712.0]],      [[6713.0, 6714.0],       [6715.0, 6716.0],       [6717.0, 6718.0],       [6719.0, 6720.0]]]],    [[[[6721.0, 6722.0],       [6723.0, 6724.0],       [6725.0, 6726.0],       [6727.0, 6728.0]],      [[6729.0, 6730.0],       [6731.0, 6732.0],       [6733.0, 6734.0],       [6735.0, 6736.0]],      [[6737.0, 6738.0],       [6739.0, 6740.0],       [6741.0, 6742.0],       [6743.0, 6744.0]]],     [[[6745.0, 6746.0],       [6747.0, 6748.0],       [6749.0, 6750.0],       [6751.0, 6752.0]],      [[6753.0, 6754.0],       [6755.0, 6756.0],       [6757.0, 6758.0],       [6759.0, 6760.0]],      [[6761.0, 6762.0],       [6763.0, 6764.0],       [6765.0, 6766.0],       [6767.0, 6768.0]]],     [[[6769.0, 6770.0],       [6771.0, 6772.0],       [6773.0, 6774.0],       [6775.0, 6776.0]],      [[6777.0, 6778.0],       [6779.0, 6780.0],       [6781.0, 6782.0],       [6783.0, 6784.0]],      [[6785.0, 6786.0],       [6787.0, 6788.0],       [6789.0, 6790.0],       [6791.0, 6792.0]]],     [[[6793.0, 6794.0],       [6795.0, 6796.0],       [6797.0, 6798.0],       [6799.0, 6800.0]],      [[6801.0, 6802.0],       [6803.0, 6804.0],       [6805.0, 6806.0],       [6807.0, 6808.0]],      [[6809.0, 6810.0],       [6811.0, 6812.0],       [6813.0, 6814.0],       [6815.0, 6816.0]]],     [[[6817.0, 6818.0],       [6819.0, 6820.0],       [6821.0, 6822.0],       [6823.0, 6824.0]],      [[6825.0, 6826.0],       [6827.0, 6828.0],       [6829.0, 6830.0],       [6831.0, 6832.0]],      [[6833.0, 6834.0],       [6835.0, 6836.0],       [6837.0, 6838.0],       [6839.0, 6840.0]]]]],   [[[[[6841.0, 6842.0],       [6843.0, 6844.0],       [6845.0, 6846.0],       [6847.0, 6848.0]],      [[6849.0, 6850.0],       [6851.0, 6852.0],       [6853.0, 6854.0],       [6855.0, 6856.0]],      [[6857.0, 6858.0],       [6859.0, 6860.0],       [6861.0, 6862.0],       [6863.0, 6864.0]]],     [[[6865.0, 6866.0],       [6867.0, 6868.0],       [6869.0, 6870.0],       [6871.0, 6872.0]],      [[6873.0, 6874.0],       [6875.0, 6876.0],       [6877.0, 6878.0],       [6879.0, 6880.0]],      [[6881.0, 6882.0],       [6883.0, 6884.0],       [6885.0, 6886.0],       [6887.0, 6888.0]]],     [[[6889.0, 6890.0],       [6891.0, 6892.0],       [6893.0, 6894.0],       [6895.0, 6896.0]],      [[6897.0, 6898.0],       [6899.0, 6900.0],       [6901.0, 6902.0],       [6903.0, 6904.0]],      [[6905.0, 6906.0],       [6907.0, 6908.0],       [6909.0, 6910.0],       [6911.0, 6912.0]]],     [[[6913.0, 6914.0],       [6915.0, 6916.0],       [6917.0, 6918.0],       [6919.0, 6920.0]],      [[6921.0, 6922.0],       [6923.0, 6924.0],       [6925.0, 6926.0],       [6927.0, 6928.0]],      [[6929.0, 6930.0],       [6931.0, 6932.0],       [6933.0, 6934.0],       [6935.0, 6936.0]]],     [[[6937.0, 6938.0],       [6939.0, 6940.0],       [6941.0, 6942.0],       [6943.0, 6944.0]],      [[6945.0, 6946.0],       [6947.0, 6948.0],       [6949.0, 6950.0],       [6951.0, 6952.0]],      [[6953.0, 6954.0],       [6955.0, 6956.0],       [6957.0, 6958.0],       [6959.0, 6960.0]]]],    [[[[6961.0, 6962.0],       [6963.0, 6964.0],       [6965.0, 6966.0],       [6967.0, 6968.0]],      [[6969.0, 6970.0],       [6971.0, 6972.0],       [6973.0, 6974.0],       [6975.0, 6976.0]],      [[6977.0, 6978.0],       [6979.0, 6980.0],       [6981.0, 6982.0],       [6983.0, 6984.0]]],     [[[6985.0, 6986.0],       [6987.0, 6988.0],       [6989.0, 6990.0],       [6991.0, 6992.0]],      [[6993.0, 6994.0],       [6995.0, 6996.0],       [6997.0, 6998.0],       [6999.0, 7000.0]],      [[7001.0, 7002.0],       [7003.0, 7004.0],       [7005.0, 7006.0],       [7007.0, 7008.0]]],     [[[7009.0, 7010.0],       [7011.0, 7012.0],       [7013.0, 7014.0],       [7015.0, 7016.0]],      [[7017.0, 7018.0],       [7019.0, 7020.0],       [7021.0, 7022.0],       [7023.0, 7024.0]],      [[7025.0, 7026.0],       [7027.0, 7028.0],       [7029.0, 7030.0],       [7031.0, 7032.0]]],     [[[7033.0, 7034.0],       [7035.0, 7036.0],       [7037.0, 7038.0],       [7039.0, 7040.0]],      [[7041.0, 7042.0],       [7043.0, 7044.0],       [7045.0, 7046.0],       [7047.0, 7048.0]],      [[7049.0, 7050.0],       [7051.0, 7052.0],       [7053.0, 7054.0],       [7055.0, 7056.0]]],     [[[7057.0, 7058.0],       [7059.0, 7060.0],       [7061.0, 7062.0],       [7063.0, 7064.0]],      [[7065.0, 7066.0],       [7067.0, 7068.0],       [7069.0, 7070.0],       [7071.0, 7072.0]],      [[7073.0, 7074.0],       [7075.0, 7076.0],       [7077.0, 7078.0],       [7079.0, 7080.0]]]],    [[[[7081.0, 7082.0],       [7083.0, 7084.0],       [7085.0, 7086.0],       [7087.0, 7088.0]],      [[7089.0, 7090.0],       [7091.0, 7092.0],       [7093.0, 7094.0],       [7095.0, 7096.0]],      [[7097.0, 7098.0],       [7099.0, 7100.0],       [7101.0, 7102.0],       [7103.0, 7104.0]]],     [[[7105.0, 7106.0],       [7107.0, 7108.0],       [7109.0, 7110.0],       [7111.0, 7112.0]],      [[7113.0, 7114.0],       [7115.0, 7116.0],       [7117.0, 7118.0],       [7119.0, 7120.0]],      [[7121.0, 7122.0],       [7123.0, 7124.0],       [7125.0, 7126.0],       [7127.0, 7128.0]]],     [[[7129.0, 7130.0],       [7131.0, 7132.0],       [7133.0, 7134.0],       [7135.0, 7136.0]],      [[7137.0, 7138.0],       [7139.0, 7140.0],       [7141.0, 7142.0],       [7143.0, 7144.0]],      [[7145.0, 7146.0],       [7147.0, 7148.0],       [7149.0, 7150.0],       [7151.0, 7152.0]]],     [[[7153.0, 7154.0],       [7155.0, 7156.0],       [7157.0, 7158.0],       [7159.0, 7160.0]],      [[7161.0, 7162.0],       [7163.0, 7164.0],       [7165.0, 7166.0],       [7167.0, 7168.0]],      [[7169.0, 7170.0],       [7171.0, 7172.0],       [7173.0, 7174.0],       [7175.0, 7176.0]]],     [[[7177.0, 7178.0],       [7179.0, 7180.0],       [7181.0, 7182.0],       [7183.0, 7184.0]],      [[7185.0, 7186.0],       [7187.0, 7188.0],       [7189.0, 7190.0],       [7191.0, 7192.0]],      [[7193.0, 7194.0],       [7195.0, 7196.0],       [7197.0, 7198.0],       [7199.0, 7200.0]]]]],   [[[[[7201.0, 7202.0],       [7203.0, 7204.0],       [7205.0, 7206.0],       [7207.0, 7208.0]],      [[7209.0, 7210.0],       [7211.0, 7212.0],       [7213.0, 7214.0],       [7215.0, 7216.0]],      [[7217.0, 7218.0],       [7219.0, 7220.0],       [7221.0, 7222.0],       [7223.0, 7224.0]]],     [[[7225.0, 7226.0],       [7227.0, 7228.0],       [7229.0, 7230.0],       [7231.0, 7232.0]],      [[7233.0, 7234.0],       [7235.0, 7236.0],       [7237.0, 7238.0],       [7239.0, 7240.0]],      [[7241.0, 7242.0],       [7243.0, 7244.0],       [7245.0, 7246.0],       [7247.0, 7248.0]]],     [[[7249.0, 7250.0],       [7251.0, 7252.0],       [7253.0, 7254.0],       [7255.0, 7256.0]],      [[7257.0, 7258.0],       [7259.0, 7260.0],       [7261.0, 7262.0],       [7263.0, 7264.0]],      [[7265.0, 7266.0],       [7267.0, 7268.0],       [7269.0, 7270.0],       [7271.0, 7272.0]]],     [[[7273.0, 7274.0],       [7275.0, 7276.0],       [7277.0, 7278.0],       [7279.0, 7280.0]],      [[7281.0, 7282.0],       [7283.0, 7284.0],       [7285.0, 7286.0],       [7287.0, 7288.0]],      [[7289.0, 7290.0],       [7291.0, 7292.0],       [7293.0, 7294.0],       [7295.0, 7296.0]]],     [[[7297.0, 7298.0],       [7299.0, 7300.0],       [7301.0, 7302.0],       [7303.0, 7304.0]],      [[7305.0, 7306.0],       [7307.0, 7308.0],       [7309.0, 7310.0],       [7311.0, 7312.0]],      [[7313.0, 7314.0],       [7315.0, 7316.0],       [7317.0, 7318.0],       [7319.0, 7320.0]]]],    [[[[7321.0, 7322.0],       [7323.0, 7324.0],       [7325.0, 7326.0],       [7327.0, 7328.0]],      [[7329.0, 7330.0],       [7331.0, 7332.0],       [7333.0, 7334.0],       [7335.0, 7336.0]],      [[7337.0, 7338.0],       [7339.0, 7340.0],       [7341.0, 7342.0],       [7343.0, 7344.0]]],     [[[7345.0, 7346.0],       [7347.0, 7348.0],       [7349.0, 7350.0],       [7351.0, 7352.0]],      [[7353.0, 7354.0],       [7355.0, 7356.0],       [7357.0, 7358.0],       [7359.0, 7360.0]],      [[7361.0, 7362.0],       [7363.0, 7364.0],       [7365.0, 7366.0],       [7367.0, 7368.0]]],     [[[7369.0, 7370.0],       [7371.0, 7372.0],       [7373.0, 7374.0],       [7375.0, 7376.0]],      [[7377.0, 7378.0],       [7379.0, 7380.0],       [7381.0, 7382.0],       [7383.0, 7384.0]],      [[7385.0, 7386.0],       [7387.0, 7388.0],       [7389.0, 7390.0],       [7391.0, 7392.0]]],     [[[7393.0, 7394.0],       [7395.0, 7396.0],       [7397.0, 7398.0],       [7399.0, 7400.0]],      [[7401.0, 7402.0],       [7403.0, 7404.0],       [7405.0, 7406.0],       [7407.0, 7408.0]],      [[7409.0, 7410.0],       [7411.0, 7412.0],       [7413.0, 7414.0],       [7415.0, 7416.0]]],     [[[7417.0, 7418.0],       [7419.0, 7420.0],       [7421.0, 7422.0],       [7423.0, 7424.0]],      [[7425.0, 7426.0],       [7427.0, 7428.0],       [7429.0, 7430.0],       [7431.0, 7432.0]],      [[7433.0, 7434.0],       [7435.0, 7436.0],       [7437.0, 7438.0],       [7439.0, 7440.0]]]],    [[[[7441.0, 7442.0],       [7443.0, 7444.0],       [7445.0, 7446.0],       [7447.0, 7448.0]],      [[7449.0, 7450.0],       [7451.0, 7452.0],       [7453.0, 7454.0],       [7455.0, 7456.0]],      [[7457.0, 7458.0],       [7459.0, 7460.0],       [7461.0, 7462.0],       [7463.0, 7464.0]]],     [[[7465.0, 7466.0],       [7467.0, 7468.0],       [7469.0, 7470.0],       [7471.0, 7472.0]],      [[7473.0, 7474.0],       [7475.0, 7476.0],       [7477.0, 7478.0],       [7479.0, 7480.0]],      [[7481.0, 7482.0],       [7483.0, 7484.0],       [7485.0, 7486.0],       [7487.0, 7488.0]]],     [[[7489.0, 7490.0],       [7491.0, 7492.0],       [7493.0, 7494.0],       [7495.0, 7496.0]],      [[7497.0, 7498.0],       [7499.0, 7500.0],       [7501.0, 7502.0],       [7503.0, 7504.0]],      [[7505.0, 7506.0],       [7507.0, 7508.0],       [7509.0, 7510.0],       [7511.0, 7512.0]]],     [[[7513.0, 7514.0],       [7515.0, 7516.0],       [7517.0, 7518.0],       [7519.0, 7520.0]],      [[7521.0, 7522.0],       [7523.0, 7524.0],       [7525.0, 7526.0],       [7527.0, 7528.0]],      [[7529.0, 7530.0],       [7531.0, 7532.0],       [7533.0, 7534.0],       [7535.0, 7536.0]]],     [[[7537.0, 7538.0],       [7539.0, 7540.0],       [7541.0, 7542.0],       [7543.0, 7544.0]],      [[7545.0, 7546.0],       [7547.0, 7548.0],       [7549.0, 7550.0],       [7551.0, 7552.0]],      [[7553.0, 7554.0],       [7555.0, 7556.0],       [7557.0, 7558.0],       [7559.0, 7560.0]]]]]]] shape=[3, 7, 3, 5, 3, 4, 2], strides=[2520, 360, 120, 24, 8, 2, 1], layout=C (0x1)), I32([2, 2, 1] shape=[3], strides=[1], layout=C | F (0x3)), I32([[2, 1],  [1, 2],  [1, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 2399511889 1822809182 1126451318 533230088 # shrinks to (ref i, ref bs, ref p) = (F32([[[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],   [16.0, 17.0, 18.0]]] shape=[1, 6, 3], strides=[18, 3, 1], layout=C (0x1)), I32([2] shape=[1], strides=[1], layout=C | F (0x3)), I32([[2, 2]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 2346609117 3140942103 2279391288 2283599207 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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, 16.0],      [17.0, 18.0, 19.0, 20.0],      [21.0, 22.0, 23.0, 24.0],      [25.0, 26.0, 27.0, 28.0]],     [[29.0, 30.0, 31.0, 32.0],      [33.0, 34.0, 35.0, 36.0],      [37.0, 38.0, 39.0, 40.0],      [41.0, 42.0, 43.0, 44.0],      [45.0, 46.0, 47.0, 48.0],      [49.0, 50.0, 51.0, 52.0],      [53.0, 54.0, 55.0, 56.0]],     [[57.0, 58.0, 59.0, 60.0],      [61.0, 62.0, 63.0, 64.0],      [65.0, 66.0, 67.0, 68.0],      [69.0, 70.0, 71.0, 72.0],      [73.0, 74.0, 75.0, 76.0],      [77.0, 78.0, 79.0, 80.0],      [81.0, 82.0, 83.0, 84.0]],     [[85.0, 86.0, 87.0, 88.0],      [89.0, 90.0, 91.0, 92.0],      [93.0, 94.0, 95.0, 96.0],      [97.0, 98.0, 99.0, 100.0],      [101.0, 102.0, 103.0, 104.0],      [105.0, 106.0, 107.0, 108.0],      [109.0, 110.0, 111.0, 112.0]],     [[113.0, 114.0, 115.0, 116.0],      [117.0, 118.0, 119.0, 120.0],      [121.0, 122.0, 123.0, 124.0],      [125.0, 126.0, 127.0, 128.0],      [129.0, 130.0, 131.0, 132.0],      [133.0, 134.0, 135.0, 136.0],      [137.0, 138.0, 139.0, 140.0]],     [[141.0, 142.0, 143.0, 144.0],      [145.0, 146.0, 147.0, 148.0],      [149.0, 150.0, 151.0, 152.0],      [153.0, 154.0, 155.0, 156.0],      [157.0, 158.0, 159.0, 160.0],      [161.0, 162.0, 163.0, 164.0],      [165.0, 166.0, 167.0, 168.0]]],    [[[169.0, 170.0, 171.0, 172.0],      [173.0, 174.0, 175.0, 176.0],      [177.0, 178.0, 179.0, 180.0],      [181.0, 182.0, 183.0, 184.0],      [185.0, 186.0, 187.0, 188.0],      [189.0, 190.0, 191.0, 192.0],      [193.0, 194.0, 195.0, 196.0]],     [[197.0, 198.0, 199.0, 200.0],      [201.0, 202.0, 203.0, 204.0],      [205.0, 206.0, 207.0, 208.0],      [209.0, 210.0, 211.0, 212.0],      [213.0, 214.0, 215.0, 216.0],      [217.0, 218.0, 219.0, 220.0],      [221.0, 222.0, 223.0, 224.0]],     [[225.0, 226.0, 227.0, 228.0],      [229.0, 230.0, 231.0, 232.0],      [233.0, 234.0, 235.0, 236.0],      [237.0, 238.0, 239.0, 240.0],      [241.0, 242.0, 243.0, 244.0],      [245.0, 246.0, 247.0, 248.0],      [249.0, 250.0, 251.0, 252.0]],     [[253.0, 254.0, 255.0, 256.0],      [257.0, 258.0, 259.0, 260.0],      [261.0, 262.0, 263.0, 264.0],      [265.0, 266.0, 267.0, 268.0],      [269.0, 270.0, 271.0, 272.0],      [273.0, 274.0, 275.0, 276.0],      [277.0, 278.0, 279.0, 280.0]],     [[281.0, 282.0, 283.0, 284.0],      [285.0, 286.0, 287.0, 288.0],      [289.0, 290.0, 291.0, 292.0],      [293.0, 294.0, 295.0, 296.0],      [297.0, 298.0, 299.0, 300.0],      [301.0, 302.0, 303.0, 304.0],      [305.0, 306.0, 307.0, 308.0]],     [[309.0, 310.0, 311.0, 312.0],      [313.0, 314.0, 315.0, 316.0],      [317.0, 318.0, 319.0, 320.0],      [321.0, 322.0, 323.0, 324.0],      [325.0, 326.0, 327.0, 328.0],      [329.0, 330.0, 331.0, 332.0],      [333.0, 334.0, 335.0, 336.0]]],    [[[337.0, 338.0, 339.0, 340.0],      [341.0, 342.0, 343.0, 344.0],      [345.0, 346.0, 347.0, 348.0],      [349.0, 350.0, 351.0, 352.0],      [353.0, 354.0, 355.0, 356.0],      [357.0, 358.0, 359.0, 360.0],      [361.0, 362.0, 363.0, 364.0]],     [[365.0, 366.0, 367.0, 368.0],      [369.0, 370.0, 371.0, 372.0],      [373.0, 374.0, 375.0, 376.0],      [377.0, 378.0, 379.0, 380.0],      [381.0, 382.0, 383.0, 384.0],      [385.0, 386.0, 387.0, 388.0],      [389.0, 390.0, 391.0, 392.0]],     [[393.0, 394.0, 395.0, 396.0],      [397.0, 398.0, 399.0, 400.0],      [401.0, 402.0, 403.0, 404.0],      [405.0, 406.0, 407.0, 408.0],      [409.0, 410.0, 411.0, 412.0],      [413.0, 414.0, 415.0, 416.0],      [417.0, 418.0, 419.0, 420.0]],     [[421.0, 422.0, 423.0, 424.0],      [425.0, 426.0, 427.0, 428.0],      [429.0, 430.0, 431.0, 432.0],      [433.0, 434.0, 435.0, 436.0],      [437.0, 438.0, 439.0, 440.0],      [441.0, 442.0, 443.0, 444.0],      [445.0, 446.0, 447.0, 448.0]],     [[449.0, 450.0, 451.0, 452.0],      [453.0, 454.0, 455.0, 456.0],      [457.0, 458.0, 459.0, 460.0],      [461.0, 462.0, 463.0, 464.0],      [465.0, 466.0, 467.0, 468.0],      [469.0, 470.0, 471.0, 472.0],      [473.0, 474.0, 475.0, 476.0]],     [[477.0, 478.0, 479.0, 480.0],      [481.0, 482.0, 483.0, 484.0],      [485.0, 486.0, 487.0, 488.0],      [489.0, 490.0, 491.0, 492.0],      [493.0, 494.0, 495.0, 496.0],      [497.0, 498.0, 499.0, 500.0],      [501.0, 502.0, 503.0, 504.0]]],    [[[505.0, 506.0, 507.0, 508.0],      [509.0, 510.0, 511.0, 512.0],      [513.0, 514.0, 515.0, 516.0],      [517.0, 518.0, 519.0, 520.0],      [521.0, 522.0, 523.0, 524.0],      [525.0, 526.0, 527.0, 528.0],      [529.0, 530.0, 531.0, 532.0]],     [[533.0, 534.0, 535.0, 536.0],      [537.0, 538.0, 539.0, 540.0],      [541.0, 542.0, 543.0, 544.0],      [545.0, 546.0, 547.0, 548.0],      [549.0, 550.0, 551.0, 552.0],      [553.0, 554.0, 555.0, 556.0],      [557.0, 558.0, 559.0, 560.0]],     [[561.0, 562.0, 563.0, 564.0],      [565.0, 566.0, 567.0, 568.0],      [569.0, 570.0, 571.0, 572.0],      [573.0, 574.0, 575.0, 576.0],      [577.0, 578.0, 579.0, 580.0],      [581.0, 582.0, 583.0, 584.0],      [585.0, 586.0, 587.0, 588.0]],     [[589.0, 590.0, 591.0, 592.0],      [593.0, 594.0, 595.0, 596.0],      [597.0, 598.0, 599.0, 600.0],      [601.0, 602.0, 603.0, 604.0],      [605.0, 606.0, 607.0, 608.0],      [609.0, 610.0, 611.0, 612.0],      [613.0, 614.0, 615.0, 616.0]],     [[617.0, 618.0, 619.0, 620.0],      [621.0, 622.0, 623.0, 624.0],      [625.0, 626.0, 627.0, 628.0],      [629.0, 630.0, 631.0, 632.0],      [633.0, 634.0, 635.0, 636.0],      [637.0, 638.0, 639.0, 640.0],      [641.0, 642.0, 643.0, 644.0]],     [[645.0, 646.0, 647.0, 648.0],      [649.0, 650.0, 651.0, 652.0],      [653.0, 654.0, 655.0, 656.0],      [657.0, 658.0, 659.0, 660.0],      [661.0, 662.0, 663.0, 664.0],      [665.0, 666.0, 667.0, 668.0],      [669.0, 670.0, 671.0, 672.0]]],    [[[673.0, 674.0, 675.0, 676.0],      [677.0, 678.0, 679.0, 680.0],      [681.0, 682.0, 683.0, 684.0],      [685.0, 686.0, 687.0, 688.0],      [689.0, 690.0, 691.0, 692.0],      [693.0, 694.0, 695.0, 696.0],      [697.0, 698.0, 699.0, 700.0]],     [[701.0, 702.0, 703.0, 704.0],      [705.0, 706.0, 707.0, 708.0],      [709.0, 710.0, 711.0, 712.0],      [713.0, 714.0, 715.0, 716.0],      [717.0, 718.0, 719.0, 720.0],      [721.0, 722.0, 723.0, 724.0],      [725.0, 726.0, 727.0, 728.0]],     [[729.0, 730.0, 731.0, 732.0],      [733.0, 734.0, 735.0, 736.0],      [737.0, 738.0, 739.0, 740.0],      [741.0, 742.0, 743.0, 744.0],      [745.0, 746.0, 747.0, 748.0],      [749.0, 750.0, 751.0, 752.0],      [753.0, 754.0, 755.0, 756.0]],     [[757.0, 758.0, 759.0, 760.0],      [761.0, 762.0, 763.0, 764.0],      [765.0, 766.0, 767.0, 768.0],      [769.0, 770.0, 771.0, 772.0],      [773.0, 774.0, 775.0, 776.0],      [777.0, 778.0, 779.0, 780.0],      [781.0, 782.0, 783.0, 784.0]],     [[785.0, 786.0, 787.0, 788.0],      [789.0, 790.0, 791.0, 792.0],      [793.0, 794.0, 795.0, 796.0],      [797.0, 798.0, 799.0, 800.0],      [801.0, 802.0, 803.0, 804.0],      [805.0, 806.0, 807.0, 808.0],      [809.0, 810.0, 811.0, 812.0]],     [[813.0, 814.0, 815.0, 816.0],      [817.0, 818.0, 819.0, 820.0],      [821.0, 822.0, 823.0, 824.0],      [825.0, 826.0, 827.0, 828.0],      [829.0, 830.0, 831.0, 832.0],      [833.0, 834.0, 835.0, 836.0],      [837.0, 838.0, 839.0, 840.0]]],    [[[841.0, 842.0, 843.0, 844.0],      [845.0, 846.0, 847.0, 848.0],      [849.0, 850.0, 851.0, 852.0],      [853.0, 854.0, 855.0, 856.0],      [857.0, 858.0, 859.0, 860.0],      [861.0, 862.0, 863.0, 864.0],      [865.0, 866.0, 867.0, 868.0]],     [[869.0, 870.0, 871.0, 872.0],      [873.0, 874.0, 875.0, 876.0],      [877.0, 878.0, 879.0, 880.0],      [881.0, 882.0, 883.0, 884.0],      [885.0, 886.0, 887.0, 888.0],      [889.0, 890.0, 891.0, 892.0],      [893.0, 894.0, 895.0, 896.0]],     [[897.0, 898.0, 899.0, 900.0],      [901.0, 902.0, 903.0, 904.0],      [905.0, 906.0, 907.0, 908.0],      [909.0, 910.0, 911.0, 912.0],      [913.0, 914.0, 915.0, 916.0],      [917.0, 918.0, 919.0, 920.0],      [921.0, 922.0, 923.0, 924.0]],     [[925.0, 926.0, 927.0, 928.0],      [929.0, 930.0, 931.0, 932.0],      [933.0, 934.0, 935.0, 936.0],      [937.0, 938.0, 939.0, 940.0],      [941.0, 942.0, 943.0, 944.0],      [945.0, 946.0, 947.0, 948.0],      [949.0, 950.0, 951.0, 952.0]],     [[953.0, 954.0, 955.0, 956.0],      [957.0, 958.0, 959.0, 960.0],      [961.0, 962.0, 963.0, 964.0],      [965.0, 966.0, 967.0, 968.0],      [969.0, 970.0, 971.0, 972.0],      [973.0, 974.0, 975.0, 976.0],      [977.0, 978.0, 979.0, 980.0]],     [[981.0, 982.0, 983.0, 984.0],      [985.0, 986.0, 987.0, 988.0],      [989.0, 990.0, 991.0, 992.0],      [993.0, 994.0, 995.0, 996.0],      [997.0, 998.0, 999.0, 1000.0],      [1001.0, 1002.0, 1003.0, 1004.0],      [1005.0, 1006.0, 1007.0, 1008.0]]]],   [[[[1009.0, 1010.0, 1011.0, 1012.0],      [1013.0, 1014.0, 1015.0, 1016.0],      [1017.0, 1018.0, 1019.0, 1020.0],      [1021.0, 1022.0, 1023.0, 1024.0],      [1025.0, 1026.0, 1027.0, 1028.0],      [1029.0, 1030.0, 1031.0, 1032.0],      [1033.0, 1034.0, 1035.0, 1036.0]],     [[1037.0, 1038.0, 1039.0, 1040.0],      [1041.0, 1042.0, 1043.0, 1044.0],      [1045.0, 1046.0, 1047.0, 1048.0],      [1049.0, 1050.0, 1051.0, 1052.0],      [1053.0, 1054.0, 1055.0, 1056.0],      [1057.0, 1058.0, 1059.0, 1060.0],      [1061.0, 1062.0, 1063.0, 1064.0]],     [[1065.0, 1066.0, 1067.0, 1068.0],      [1069.0, 1070.0, 1071.0, 1072.0],      [1073.0, 1074.0, 1075.0, 1076.0],      [1077.0, 1078.0, 1079.0, 1080.0],      [1081.0, 1082.0, 1083.0, 1084.0],      [1085.0, 1086.0, 1087.0, 1088.0],      [1089.0, 1090.0, 1091.0, 1092.0]],     [[1093.0, 1094.0, 1095.0, 1096.0],      [1097.0, 1098.0, 1099.0, 1100.0],      [1101.0, 1102.0, 1103.0, 1104.0],      [1105.0, 1106.0, 1107.0, 1108.0],      [1109.0, 1110.0, 1111.0, 1112.0],      [1113.0, 1114.0, 1115.0, 1116.0],      [1117.0, 1118.0, 1119.0, 1120.0]],     [[1121.0, 1122.0, 1123.0, 1124.0],      [1125.0, 1126.0, 1127.0, 1128.0],      [1129.0, 1130.0, 1131.0, 1132.0],      [1133.0, 1134.0, 1135.0, 1136.0],      [1137.0, 1138.0, 1139.0, 1140.0],      [1141.0, 1142.0, 1143.0, 1144.0],      [1145.0, 1146.0, 1147.0, 1148.0]],     [[1149.0, 1150.0, 1151.0, 1152.0],      [1153.0, 1154.0, 1155.0, 1156.0],      [1157.0, 1158.0, 1159.0, 1160.0],      [1161.0, 1162.0, 1163.0, 1164.0],      [1165.0, 1166.0, 1167.0, 1168.0],      [1169.0, 1170.0, 1171.0, 1172.0],      [1173.0, 1174.0, 1175.0, 1176.0]]],    [[[1177.0, 1178.0, 1179.0, 1180.0],      [1181.0, 1182.0, 1183.0, 1184.0],      [1185.0, 1186.0, 1187.0, 1188.0],      [1189.0, 1190.0, 1191.0, 1192.0],      [1193.0, 1194.0, 1195.0, 1196.0],      [1197.0, 1198.0, 1199.0, 1200.0],      [1201.0, 1202.0, 1203.0, 1204.0]],     [[1205.0, 1206.0, 1207.0, 1208.0],      [1209.0, 1210.0, 1211.0, 1212.0],      [1213.0, 1214.0, 1215.0, 1216.0],      [1217.0, 1218.0, 1219.0, 1220.0],      [1221.0, 1222.0, 1223.0, 1224.0],      [1225.0, 1226.0, 1227.0, 1228.0],      [1229.0, 1230.0, 1231.0, 1232.0]],     [[1233.0, 1234.0, 1235.0, 1236.0],      [1237.0, 1238.0, 1239.0, 1240.0],      [1241.0, 1242.0, 1243.0, 1244.0],      [1245.0, 1246.0, 1247.0, 1248.0],      [1249.0, 1250.0, 1251.0, 1252.0],      [1253.0, 1254.0, 1255.0, 1256.0],      [1257.0, 1258.0, 1259.0, 1260.0]],     [[1261.0, 1262.0, 1263.0, 1264.0],      [1265.0, 1266.0, 1267.0, 1268.0],      [1269.0, 1270.0, 1271.0, 1272.0],      [1273.0, 1274.0, 1275.0, 1276.0],      [1277.0, 1278.0, 1279.0, 1280.0],      [1281.0, 1282.0, 1283.0, 1284.0],      [1285.0, 1286.0, 1287.0, 1288.0]],     [[1289.0, 1290.0, 1291.0, 1292.0],      [1293.0, 1294.0, 1295.0, 1296.0],      [1297.0, 1298.0, 1299.0, 1300.0],      [1301.0, 1302.0, 1303.0, 1304.0],      [1305.0, 1306.0, 1307.0, 1308.0],      [1309.0, 1310.0, 1311.0, 1312.0],      [1313.0, 1314.0, 1315.0, 1316.0]],     [[1317.0, 1318.0, 1319.0, 1320.0],      [1321.0, 1322.0, 1323.0, 1324.0],      [1325.0, 1326.0, 1327.0, 1328.0],      [1329.0, 1330.0, 1331.0, 1332.0],      [1333.0, 1334.0, 1335.0, 1336.0],      [1337.0, 1338.0, 1339.0, 1340.0],      [1341.0, 1342.0, 1343.0, 1344.0]]],    [[[1345.0, 1346.0, 1347.0, 1348.0],      [1349.0, 1350.0, 1351.0, 1352.0],      [1353.0, 1354.0, 1355.0, 1356.0],      [1357.0, 1358.0, 1359.0, 1360.0],      [1361.0, 1362.0, 1363.0, 1364.0],      [1365.0, 1366.0, 1367.0, 1368.0],      [1369.0, 1370.0, 1371.0, 1372.0]],     [[1373.0, 1374.0, 1375.0, 1376.0],      [1377.0, 1378.0, 1379.0, 1380.0],      [1381.0, 1382.0, 1383.0, 1384.0],      [1385.0, 1386.0, 1387.0, 1388.0],      [1389.0, 1390.0, 1391.0, 1392.0],      [1393.0, 1394.0, 1395.0, 1396.0],      [1397.0, 1398.0, 1399.0, 1400.0]],     [[1401.0, 1402.0, 1403.0, 1404.0],      [1405.0, 1406.0, 1407.0, 1408.0],      [1409.0, 1410.0, 1411.0, 1412.0],      [1413.0, 1414.0, 1415.0, 1416.0],      [1417.0, 1418.0, 1419.0, 1420.0],      [1421.0, 1422.0, 1423.0, 1424.0],      [1425.0, 1426.0, 1427.0, 1428.0]],     [[1429.0, 1430.0, 1431.0, 1432.0],      [1433.0, 1434.0, 1435.0, 1436.0],      [1437.0, 1438.0, 1439.0, 1440.0],      [1441.0, 1442.0, 1443.0, 1444.0],      [1445.0, 1446.0, 1447.0, 1448.0],      [1449.0, 1450.0, 1451.0, 1452.0],      [1453.0, 1454.0, 1455.0, 1456.0]],     [[1457.0, 1458.0, 1459.0, 1460.0],      [1461.0, 1462.0, 1463.0, 1464.0],      [1465.0, 1466.0, 1467.0, 1468.0],      [1469.0, 1470.0, 1471.0, 1472.0],      [1473.0, 1474.0, 1475.0, 1476.0],      [1477.0, 1478.0, 1479.0, 1480.0],      [1481.0, 1482.0, 1483.0, 1484.0]],     [[1485.0, 1486.0, 1487.0, 1488.0],      [1489.0, 1490.0, 1491.0, 1492.0],      [1493.0, 1494.0, 1495.0, 1496.0],      [1497.0, 1498.0, 1499.0, 1500.0],      [1501.0, 1502.0, 1503.0, 1504.0],      [1505.0, 1506.0, 1507.0, 1508.0],      [1509.0, 1510.0, 1511.0, 1512.0]]],    [[[1513.0, 1514.0, 1515.0, 1516.0],      [1517.0, 1518.0, 1519.0, 1520.0],      [1521.0, 1522.0, 1523.0, 1524.0],      [1525.0, 1526.0, 1527.0, 1528.0],      [1529.0, 1530.0, 1531.0, 1532.0],      [1533.0, 1534.0, 1535.0, 1536.0],      [1537.0, 1538.0, 1539.0, 1540.0]],     [[1541.0, 1542.0, 1543.0, 1544.0],      [1545.0, 1546.0, 1547.0, 1548.0],      [1549.0, 1550.0, 1551.0, 1552.0],      [1553.0, 1554.0, 1555.0, 1556.0],      [1557.0, 1558.0, 1559.0, 1560.0],      [1561.0, 1562.0, 1563.0, 1564.0],      [1565.0, 1566.0, 1567.0, 1568.0]],     [[1569.0, 1570.0, 1571.0, 1572.0],      [1573.0, 1574.0, 1575.0, 1576.0],      [1577.0, 1578.0, 1579.0, 1580.0],      [1581.0, 1582.0, 1583.0, 1584.0],      [1585.0, 1586.0, 1587.0, 1588.0],      [1589.0, 1590.0, 1591.0, 1592.0],      [1593.0, 1594.0, 1595.0, 1596.0]],     [[1597.0, 1598.0, 1599.0, 1600.0],      [1601.0, 1602.0, 1603.0, 1604.0],      [1605.0, 1606.0, 1607.0, 1608.0],      [1609.0, 1610.0, 1611.0, 1612.0],      [1613.0, 1614.0, 1615.0, 1616.0],      [1617.0, 1618.0, 1619.0, 1620.0],      [1621.0, 1622.0, 1623.0, 1624.0]],     [[1625.0, 1626.0, 1627.0, 1628.0],      [1629.0, 1630.0, 1631.0, 1632.0],      [1633.0, 1634.0, 1635.0, 1636.0],      [1637.0, 1638.0, 1639.0, 1640.0],      [1641.0, 1642.0, 1643.0, 1644.0],      [1645.0, 1646.0, 1647.0, 1648.0],      [1649.0, 1650.0, 1651.0, 1652.0]],     [[1653.0, 1654.0, 1655.0, 1656.0],      [1657.0, 1658.0, 1659.0, 1660.0],      [1661.0, 1662.0, 1663.0, 1664.0],      [1665.0, 1666.0, 1667.0, 1668.0],      [1669.0, 1670.0, 1671.0, 1672.0],      [1673.0, 1674.0, 1675.0, 1676.0],      [1677.0, 1678.0, 1679.0, 1680.0]]],    [[[1681.0, 1682.0, 1683.0, 1684.0],      [1685.0, 1686.0, 1687.0, 1688.0],      [1689.0, 1690.0, 1691.0, 1692.0],      [1693.0, 1694.0, 1695.0, 1696.0],      [1697.0, 1698.0, 1699.0, 1700.0],      [1701.0, 1702.0, 1703.0, 1704.0],      [1705.0, 1706.0, 1707.0, 1708.0]],     [[1709.0, 1710.0, 1711.0, 1712.0],      [1713.0, 1714.0, 1715.0, 1716.0],      [1717.0, 1718.0, 1719.0, 1720.0],      [1721.0, 1722.0, 1723.0, 1724.0],      [1725.0, 1726.0, 1727.0, 1728.0],      [1729.0, 1730.0, 1731.0, 1732.0],      [1733.0, 1734.0, 1735.0, 1736.0]],     [[1737.0, 1738.0, 1739.0, 1740.0],      [1741.0, 1742.0, 1743.0, 1744.0],      [1745.0, 1746.0, 1747.0, 1748.0],      [1749.0, 1750.0, 1751.0, 1752.0],      [1753.0, 1754.0, 1755.0, 1756.0],      [1757.0, 1758.0, 1759.0, 1760.0],      [1761.0, 1762.0, 1763.0, 1764.0]],     [[1765.0, 1766.0, 1767.0, 1768.0],      [1769.0, 1770.0, 1771.0, 1772.0],      [1773.0, 1774.0, 1775.0, 1776.0],      [1777.0, 1778.0, 1779.0, 1780.0],      [1781.0, 1782.0, 1783.0, 1784.0],      [1785.0, 1786.0, 1787.0, 1788.0],      [1789.0, 1790.0, 1791.0, 1792.0]],     [[1793.0, 1794.0, 1795.0, 1796.0],      [1797.0, 1798.0, 1799.0, 1800.0],      [1801.0, 1802.0, 1803.0, 1804.0],      [1805.0, 1806.0, 1807.0, 1808.0],      [1809.0, 1810.0, 1811.0, 1812.0],      [1813.0, 1814.0, 1815.0, 1816.0],      [1817.0, 1818.0, 1819.0, 1820.0]],     [[1821.0, 1822.0, 1823.0, 1824.0],      [1825.0, 1826.0, 1827.0, 1828.0],      [1829.0, 1830.0, 1831.0, 1832.0],      [1833.0, 1834.0, 1835.0, 1836.0],      [1837.0, 1838.0, 1839.0, 1840.0],      [1841.0, 1842.0, 1843.0, 1844.0],      [1845.0, 1846.0, 1847.0, 1848.0]]],    [[[1849.0, 1850.0, 1851.0, 1852.0],      [1853.0, 1854.0, 1855.0, 1856.0],      [1857.0, 1858.0, 1859.0, 1860.0],      [1861.0, 1862.0, 1863.0, 1864.0],      [1865.0, 1866.0, 1867.0, 1868.0],      [1869.0, 1870.0, 1871.0, 1872.0],      [1873.0, 1874.0, 1875.0, 1876.0]],     [[1877.0, 1878.0, 1879.0, 1880.0],      [1881.0, 1882.0, 1883.0, 1884.0],      [1885.0, 1886.0, 1887.0, 1888.0],      [1889.0, 1890.0, 1891.0, 1892.0],      [1893.0, 1894.0, 1895.0, 1896.0],      [1897.0, 1898.0, 1899.0, 1900.0],      [1901.0, 1902.0, 1903.0, 1904.0]],     [[1905.0, 1906.0, 1907.0, 1908.0],      [1909.0, 1910.0, 1911.0, 1912.0],      [1913.0, 1914.0, 1915.0, 1916.0],      [1917.0, 1918.0, 1919.0, 1920.0],      [1921.0, 1922.0, 1923.0, 1924.0],      [1925.0, 1926.0, 1927.0, 1928.0],      [1929.0, 1930.0, 1931.0, 1932.0]],     [[1933.0, 1934.0, 1935.0, 1936.0],      [1937.0, 1938.0, 1939.0, 1940.0],      [1941.0, 1942.0, 1943.0, 1944.0],      [1945.0, 1946.0, 1947.0, 1948.0],      [1949.0, 1950.0, 1951.0, 1952.0],      [1953.0, 1954.0, 1955.0, 1956.0],      [1957.0, 1958.0, 1959.0, 1960.0]],     [[1961.0, 1962.0, 1963.0, 1964.0],      [1965.0, 1966.0, 1967.0, 1968.0],      [1969.0, 1970.0, 1971.0, 1972.0],      [1973.0, 1974.0, 1975.0, 1976.0],      [1977.0, 1978.0, 1979.0, 1980.0],      [1981.0, 1982.0, 1983.0, 1984.0],      [1985.0, 1986.0, 1987.0, 1988.0]],     [[1989.0, 1990.0, 1991.0, 1992.0],      [1993.0, 1994.0, 1995.0, 1996.0],      [1997.0, 1998.0, 1999.0, 2000.0],      [2001.0, 2002.0, 2003.0, 2004.0],      [2005.0, 2006.0, 2007.0, 2008.0],      [2009.0, 2010.0, 2011.0, 2012.0],      [2013.0, 2014.0, 2015.0, 2016.0]]]],   [[[[2017.0, 2018.0, 2019.0, 2020.0],      [2021.0, 2022.0, 2023.0, 2024.0],      [2025.0, 2026.0, 2027.0, 2028.0],      [2029.0, 2030.0, 2031.0, 2032.0],      [2033.0, 2034.0, 2035.0, 2036.0],      [2037.0, 2038.0, 2039.0, 2040.0],      [2041.0, 2042.0, 2043.0, 2044.0]],     [[2045.0, 2046.0, 2047.0, 2048.0],      [2049.0, 2050.0, 2051.0, 2052.0],      [2053.0, 2054.0, 2055.0, 2056.0],      [2057.0, 2058.0, 2059.0, 2060.0],      [2061.0, 2062.0, 2063.0, 2064.0],      [2065.0, 2066.0, 2067.0, 2068.0],      [2069.0, 2070.0, 2071.0, 2072.0]],     [[2073.0, 2074.0, 2075.0, 2076.0],      [2077.0, 2078.0, 2079.0, 2080.0],      [2081.0, 2082.0, 2083.0, 2084.0],      [2085.0, 2086.0, 2087.0, 2088.0],      [2089.0, 2090.0, 2091.0, 2092.0],      [2093.0, 2094.0, 2095.0, 2096.0],      [2097.0, 2098.0, 2099.0, 2100.0]],     [[2101.0, 2102.0, 2103.0, 2104.0],      [2105.0, 2106.0, 2107.0, 2108.0],      [2109.0, 2110.0, 2111.0, 2112.0],      [2113.0, 2114.0, 2115.0, 2116.0],      [2117.0, 2118.0, 2119.0, 2120.0],      [2121.0, 2122.0, 2123.0, 2124.0],      [2125.0, 2126.0, 2127.0, 2128.0]],     [[2129.0, 2130.0, 2131.0, 2132.0],      [2133.0, 2134.0, 2135.0, 2136.0],      [2137.0, 2138.0, 2139.0, 2140.0],      [2141.0, 2142.0, 2143.0, 2144.0],      [2145.0, 2146.0, 2147.0, 2148.0],      [2149.0, 2150.0, 2151.0, 2152.0],      [2153.0, 2154.0, 2155.0, 2156.0]],     [[2157.0, 2158.0, 2159.0, 2160.0],      [2161.0, 2162.0, 2163.0, 2164.0],      [2165.0, 2166.0, 2167.0, 2168.0],      [2169.0, 2170.0, 2171.0, 2172.0],      [2173.0, 2174.0, 2175.0, 2176.0],      [2177.0, 2178.0, 2179.0, 2180.0],      [2181.0, 2182.0, 2183.0, 2184.0]]],    [[[2185.0, 2186.0, 2187.0, 2188.0],      [2189.0, 2190.0, 2191.0, 2192.0],      [2193.0, 2194.0, 2195.0, 2196.0],      [2197.0, 2198.0, 2199.0, 2200.0],      [2201.0, 2202.0, 2203.0, 2204.0],      [2205.0, 2206.0, 2207.0, 2208.0],      [2209.0, 2210.0, 2211.0, 2212.0]],     [[2213.0, 2214.0, 2215.0, 2216.0],      [2217.0, 2218.0, 2219.0, 2220.0],      [2221.0, 2222.0, 2223.0, 2224.0],      [2225.0, 2226.0, 2227.0, 2228.0],      [2229.0, 2230.0, 2231.0, 2232.0],      [2233.0, 2234.0, 2235.0, 2236.0],      [2237.0, 2238.0, 2239.0, 2240.0]],     [[2241.0, 2242.0, 2243.0, 2244.0],      [2245.0, 2246.0, 2247.0, 2248.0],      [2249.0, 2250.0, 2251.0, 2252.0],      [2253.0, 2254.0, 2255.0, 2256.0],      [2257.0, 2258.0, 2259.0, 2260.0],      [2261.0, 2262.0, 2263.0, 2264.0],      [2265.0, 2266.0, 2267.0, 2268.0]],     [[2269.0, 2270.0, 2271.0, 2272.0],      [2273.0, 2274.0, 2275.0, 2276.0],      [2277.0, 2278.0, 2279.0, 2280.0],      [2281.0, 2282.0, 2283.0, 2284.0],      [2285.0, 2286.0, 2287.0, 2288.0],      [2289.0, 2290.0, 2291.0, 2292.0],      [2293.0, 2294.0, 2295.0, 2296.0]],     [[2297.0, 2298.0, 2299.0, 2300.0],      [2301.0, 2302.0, 2303.0, 2304.0],      [2305.0, 2306.0, 2307.0, 2308.0],      [2309.0, 2310.0, 2311.0, 2312.0],      [2313.0, 2314.0, 2315.0, 2316.0],      [2317.0, 2318.0, 2319.0, 2320.0],      [2321.0, 2322.0, 2323.0, 2324.0]],     [[2325.0, 2326.0, 2327.0, 2328.0],      [2329.0, 2330.0, 2331.0, 2332.0],      [2333.0, 2334.0, 2335.0, 2336.0],      [2337.0, 2338.0, 2339.0, 2340.0],      [2341.0, 2342.0, 2343.0, 2344.0],      [2345.0, 2346.0, 2347.0, 2348.0],      [2349.0, 2350.0, 2351.0, 2352.0]]],    [[[2353.0, 2354.0, 2355.0, 2356.0],      [2357.0, 2358.0, 2359.0, 2360.0],      [2361.0, 2362.0, 2363.0, 2364.0],      [2365.0, 2366.0, 2367.0, 2368.0],      [2369.0, 2370.0, 2371.0, 2372.0],      [2373.0, 2374.0, 2375.0, 2376.0],      [2377.0, 2378.0, 2379.0, 2380.0]],     [[2381.0, 2382.0, 2383.0, 2384.0],      [2385.0, 2386.0, 2387.0, 2388.0],      [2389.0, 2390.0, 2391.0, 2392.0],      [2393.0, 2394.0, 2395.0, 2396.0],      [2397.0, 2398.0, 2399.0, 2400.0],      [2401.0, 2402.0, 2403.0, 2404.0],      [2405.0, 2406.0, 2407.0, 2408.0]],     [[2409.0, 2410.0, 2411.0, 2412.0],      [2413.0, 2414.0, 2415.0, 2416.0],      [2417.0, 2418.0, 2419.0, 2420.0],      [2421.0, 2422.0, 2423.0, 2424.0],      [2425.0, 2426.0, 2427.0, 2428.0],      [2429.0, 2430.0, 2431.0, 2432.0],      [2433.0, 2434.0, 2435.0, 2436.0]],     [[2437.0, 2438.0, 2439.0, 2440.0],      [2441.0, 2442.0, 2443.0, 2444.0],      [2445.0, 2446.0, 2447.0, 2448.0],      [2449.0, 2450.0, 2451.0, 2452.0],      [2453.0, 2454.0, 2455.0, 2456.0],      [2457.0, 2458.0, 2459.0, 2460.0],      [2461.0, 2462.0, 2463.0, 2464.0]],     [[2465.0, 2466.0, 2467.0, 2468.0],      [2469.0, 2470.0, 2471.0, 2472.0],      [2473.0, 2474.0, 2475.0, 2476.0],      [2477.0, 2478.0, 2479.0, 2480.0],      [2481.0, 2482.0, 2483.0, 2484.0],      [2485.0, 2486.0, 2487.0, 2488.0],      [2489.0, 2490.0, 2491.0, 2492.0]],     [[2493.0, 2494.0, 2495.0, 2496.0],      [2497.0, 2498.0, 2499.0, 2500.0],      [2501.0, 2502.0, 2503.0, 2504.0],      [2505.0, 2506.0, 2507.0, 2508.0],      [2509.0, 2510.0, 2511.0, 2512.0],      [2513.0, 2514.0, 2515.0, 2516.0],      [2517.0, 2518.0, 2519.0, 2520.0]]],    [[[2521.0, 2522.0, 2523.0, 2524.0],      [2525.0, 2526.0, 2527.0, 2528.0],      [2529.0, 2530.0, 2531.0, 2532.0],      [2533.0, 2534.0, 2535.0, 2536.0],      [2537.0, 2538.0, 2539.0, 2540.0],      [2541.0, 2542.0, 2543.0, 2544.0],      [2545.0, 2546.0, 2547.0, 2548.0]],     [[2549.0, 2550.0, 2551.0, 2552.0],      [2553.0, 2554.0, 2555.0, 2556.0],      [2557.0, 2558.0, 2559.0, 2560.0],      [2561.0, 2562.0, 2563.0, 2564.0],      [2565.0, 2566.0, 2567.0, 2568.0],      [2569.0, 2570.0, 2571.0, 2572.0],      [2573.0, 2574.0, 2575.0, 2576.0]],     [[2577.0, 2578.0, 2579.0, 2580.0],      [2581.0, 2582.0, 2583.0, 2584.0],      [2585.0, 2586.0, 2587.0, 2588.0],      [2589.0, 2590.0, 2591.0, 2592.0],      [2593.0, 2594.0, 2595.0, 2596.0],      [2597.0, 2598.0, 2599.0, 2600.0],      [2601.0, 2602.0, 2603.0, 2604.0]],     [[2605.0, 2606.0, 2607.0, 2608.0],      [2609.0, 2610.0, 2611.0, 2612.0],      [2613.0, 2614.0, 2615.0, 2616.0],      [2617.0, 2618.0, 2619.0, 2620.0],      [2621.0, 2622.0, 2623.0, 2624.0],      [2625.0, 2626.0, 2627.0, 2628.0],      [2629.0, 2630.0, 2631.0, 2632.0]],     [[2633.0, 2634.0, 2635.0, 2636.0],      [2637.0, 2638.0, 2639.0, 2640.0],      [2641.0, 2642.0, 2643.0, 2644.0],      [2645.0, 2646.0, 2647.0, 2648.0],      [2649.0, 2650.0, 2651.0, 2652.0],      [2653.0, 2654.0, 2655.0, 2656.0],      [2657.0, 2658.0, 2659.0, 2660.0]],     [[2661.0, 2662.0, 2663.0, 2664.0],      [2665.0, 2666.0, 2667.0, 2668.0],      [2669.0, 2670.0, 2671.0, 2672.0],      [2673.0, 2674.0, 2675.0, 2676.0],      [2677.0, 2678.0, 2679.0, 2680.0],      [2681.0, 2682.0, 2683.0, 2684.0],      [2685.0, 2686.0, 2687.0, 2688.0]]],    [[[2689.0, 2690.0, 2691.0, 2692.0],      [2693.0, 2694.0, 2695.0, 2696.0],      [2697.0, 2698.0, 2699.0, 2700.0],      [2701.0, 2702.0, 2703.0, 2704.0],      [2705.0, 2706.0, 2707.0, 2708.0],      [2709.0, 2710.0, 2711.0, 2712.0],      [2713.0, 2714.0, 2715.0, 2716.0]],     [[2717.0, 2718.0, 2719.0, 2720.0],      [2721.0, 2722.0, 2723.0, 2724.0],      [2725.0, 2726.0, 2727.0, 2728.0],      [2729.0, 2730.0, 2731.0, 2732.0],      [2733.0, 2734.0, 2735.0, 2736.0],      [2737.0, 2738.0, 2739.0, 2740.0],      [2741.0, 2742.0, 2743.0, 2744.0]],     [[2745.0, 2746.0, 2747.0, 2748.0],      [2749.0, 2750.0, 2751.0, 2752.0],      [2753.0, 2754.0, 2755.0, 2756.0],      [2757.0, 2758.0, 2759.0, 2760.0],      [2761.0, 2762.0, 2763.0, 2764.0],      [2765.0, 2766.0, 2767.0, 2768.0],      [2769.0, 2770.0, 2771.0, 2772.0]],     [[2773.0, 2774.0, 2775.0, 2776.0],      [2777.0, 2778.0, 2779.0, 2780.0],      [2781.0, 2782.0, 2783.0, 2784.0],      [2785.0, 2786.0, 2787.0, 2788.0],      [2789.0, 2790.0, 2791.0, 2792.0],      [2793.0, 2794.0, 2795.0, 2796.0],      [2797.0, 2798.0, 2799.0, 2800.0]],     [[2801.0, 2802.0, 2803.0, 2804.0],      [2805.0, 2806.0, 2807.0, 2808.0],      [2809.0, 2810.0, 2811.0, 2812.0],      [2813.0, 2814.0, 2815.0, 2816.0],      [2817.0, 2818.0, 2819.0, 2820.0],      [2821.0, 2822.0, 2823.0, 2824.0],      [2825.0, 2826.0, 2827.0, 2828.0]],     [[2829.0, 2830.0, 2831.0, 2832.0],      [2833.0, 2834.0, 2835.0, 2836.0],      [2837.0, 2838.0, 2839.0, 2840.0],      [2841.0, 2842.0, 2843.0, 2844.0],      [2845.0, 2846.0, 2847.0, 2848.0],      [2849.0, 2850.0, 2851.0, 2852.0],      [2853.0, 2854.0, 2855.0, 2856.0]]],    [[[2857.0, 2858.0, 2859.0, 2860.0],      [2861.0, 2862.0, 2863.0, 2864.0],      [2865.0, 2866.0, 2867.0, 2868.0],      [2869.0, 2870.0, 2871.0, 2872.0],      [2873.0, 2874.0, 2875.0, 2876.0],      [2877.0, 2878.0, 2879.0, 2880.0],      [2881.0, 2882.0, 2883.0, 2884.0]],     [[2885.0, 2886.0, 2887.0, 2888.0],      [2889.0, 2890.0, 2891.0, 2892.0],      [2893.0, 2894.0, 2895.0, 2896.0],      [2897.0, 2898.0, 2899.0, 2900.0],      [2901.0, 2902.0, 2903.0, 2904.0],      [2905.0, 2906.0, 2907.0, 2908.0],      [2909.0, 2910.0, 2911.0, 2912.0]],     [[2913.0, 2914.0, 2915.0, 2916.0],      [2917.0, 2918.0, 2919.0, 2920.0],      [2921.0, 2922.0, 2923.0, 2924.0],      [2925.0, 2926.0, 2927.0, 2928.0],      [2929.0, 2930.0, 2931.0, 2932.0],      [2933.0, 2934.0, 2935.0, 2936.0],      [2937.0, 2938.0, 2939.0, 2940.0]],     [[2941.0, 2942.0, 2943.0, 2944.0],      [2945.0, 2946.0, 2947.0, 2948.0],      [2949.0, 2950.0, 2951.0, 2952.0],      [2953.0, 2954.0, 2955.0, 2956.0],      [2957.0, 2958.0, 2959.0, 2960.0],      [2961.0, 2962.0, 2963.0, 2964.0],      [2965.0, 2966.0, 2967.0, 2968.0]],     [[2969.0, 2970.0, 2971.0, 2972.0],      [2973.0, 2974.0, 2975.0, 2976.0],      [2977.0, 2978.0, 2979.0, 2980.0],      [2981.0, 2982.0, 2983.0, 2984.0],      [2985.0, 2986.0, 2987.0, 2988.0],      [2989.0, 2990.0, 2991.0, 2992.0],      [2993.0, 2994.0, 2995.0, 2996.0]],     [[2997.0, 2998.0, 2999.0, 3000.0],      [3001.0, 3002.0, 3003.0, 3004.0],      [3005.0, 3006.0, 3007.0, 3008.0],      [3009.0, 3010.0, 3011.0, 3012.0],      [3013.0, 3014.0, 3015.0, 3016.0],      [3017.0, 3018.0, 3019.0, 3020.0],      [3021.0, 3022.0, 3023.0, 3024.0]]]]]] shape=[1, 3, 6, 6, 7, 4], strides=[3024, 1008, 168, 28, 4, 1], layout=C (0x1)), I32([2, 3] shape=[2], strides=[1], layout=C | F (0x3)), I32([[2, 1],  [3, 3]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 2086683851 929394702 2325782660 540397897 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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],       [16.0]],      [[17.0],       [18.0]],      [[19.0],       [20.0]],      [[21.0],       [22.0]],      [[23.0],       [24.0]],      [[25.0],       [26.0]],      [[27.0],       [28.0]]]],    [[[[29.0],       [30.0]],      [[31.0],       [32.0]],      [[33.0],       [34.0]],      [[35.0],       [36.0]],      [[37.0],       [38.0]],      [[39.0],       [40.0]],      [[41.0],       [42.0]]],     [[[43.0],       [44.0]],      [[45.0],       [46.0]],      [[47.0],       [48.0]],      [[49.0],       [50.0]],      [[51.0],       [52.0]],      [[53.0],       [54.0]],      [[55.0],       [56.0]]]],    [[[[57.0],       [58.0]],      [[59.0],       [60.0]],      [[61.0],       [62.0]],      [[63.0],       [64.0]],      [[65.0],       [66.0]],      [[67.0],       [68.0]],      [[69.0],       [70.0]]],     [[[71.0],       [72.0]],      [[73.0],       [74.0]],      [[75.0],       [76.0]],      [[77.0],       [78.0]],      [[79.0],       [80.0]],      [[81.0],       [82.0]],      [[83.0],       [84.0]]]],    [[[[85.0],       [86.0]],      [[87.0],       [88.0]],      [[89.0],       [90.0]],      [[91.0],       [92.0]],      [[93.0],       [94.0]],      [[95.0],       [96.0]],      [[97.0],       [98.0]]],     [[[99.0],       [100.0]],      [[101.0],       [102.0]],      [[103.0],       [104.0]],      [[105.0],       [106.0]],      [[107.0],       [108.0]],      [[109.0],       [110.0]],      [[111.0],       [112.0]]]],    [[[[113.0],       [114.0]],      [[115.0],       [116.0]],      [[117.0],       [118.0]],      [[119.0],       [120.0]],      [[121.0],       [122.0]],      [[123.0],       [124.0]],      [[125.0],       [126.0]]],     [[[127.0],       [128.0]],      [[129.0],       [130.0]],      [[131.0],       [132.0]],      [[133.0],       [134.0]],      [[135.0],       [136.0]],      [[137.0],       [138.0]],      [[139.0],       [140.0]]]],    [[[[141.0],       [142.0]],      [[143.0],       [144.0]],      [[145.0],       [146.0]],      [[147.0],       [148.0]],      [[149.0],       [150.0]],      [[151.0],       [152.0]],      [[153.0],       [154.0]]],     [[[155.0],       [156.0]],      [[157.0],       [158.0]],      [[159.0],       [160.0]],      [[161.0],       [162.0]],      [[163.0],       [164.0]],      [[165.0],       [166.0]],      [[167.0],       [168.0]]]],    [[[[169.0],       [170.0]],      [[171.0],       [172.0]],      [[173.0],       [174.0]],      [[175.0],       [176.0]],      [[177.0],       [178.0]],      [[179.0],       [180.0]],      [[181.0],       [182.0]]],     [[[183.0],       [184.0]],      [[185.0],       [186.0]],      [[187.0],       [188.0]],      [[189.0],       [190.0]],      [[191.0],       [192.0]],      [[193.0],       [194.0]],      [[195.0],       [196.0]]]]],   [[[[[197.0],       [198.0]],      [[199.0],       [200.0]],      [[201.0],       [202.0]],      [[203.0],       [204.0]],      [[205.0],       [206.0]],      [[207.0],       [208.0]],      [[209.0],       [210.0]]],     [[[211.0],       [212.0]],      [[213.0],       [214.0]],      [[215.0],       [216.0]],      [[217.0],       [218.0]],      [[219.0],       [220.0]],      [[221.0],       [222.0]],      [[223.0],       [224.0]]]],    [[[[225.0],       [226.0]],      [[227.0],       [228.0]],      [[229.0],       [230.0]],      [[231.0],       [232.0]],      [[233.0],       [234.0]],      [[235.0],       [236.0]],      [[237.0],       [238.0]]],     [[[239.0],       [240.0]],      [[241.0],       [242.0]],      [[243.0],       [244.0]],      [[245.0],       [246.0]],      [[247.0],       [248.0]],      [[249.0],       [250.0]],      [[251.0],       [252.0]]]],    [[[[253.0],       [254.0]],      [[255.0],       [256.0]],      [[257.0],       [258.0]],      [[259.0],       [260.0]],      [[261.0],       [262.0]],      [[263.0],       [264.0]],      [[265.0],       [266.0]]],     [[[267.0],       [268.0]],      [[269.0],       [270.0]],      [[271.0],       [272.0]],      [[273.0],       [274.0]],      [[275.0],       [276.0]],      [[277.0],       [278.0]],      [[279.0],       [280.0]]]],    [[[[281.0],       [282.0]],      [[283.0],       [284.0]],      [[285.0],       [286.0]],      [[287.0],       [288.0]],      [[289.0],       [290.0]],      [[291.0],       [292.0]],      [[293.0],       [294.0]]],     [[[295.0],       [296.0]],      [[297.0],       [298.0]],      [[299.0],       [300.0]],      [[301.0],       [302.0]],      [[303.0],       [304.0]],      [[305.0],       [306.0]],      [[307.0],       [308.0]]]],    [[[[309.0],       [310.0]],      [[311.0],       [312.0]],      [[313.0],       [314.0]],      [[315.0],       [316.0]],      [[317.0],       [318.0]],      [[319.0],       [320.0]],      [[321.0],       [322.0]]],     [[[323.0],       [324.0]],      [[325.0],       [326.0]],      [[327.0],       [328.0]],      [[329.0],       [330.0]],      [[331.0],       [332.0]],      [[333.0],       [334.0]],      [[335.0],       [336.0]]]],    [[[[337.0],       [338.0]],      [[339.0],       [340.0]],      [[341.0],       [342.0]],      [[343.0],       [344.0]],      [[345.0],       [346.0]],      [[347.0],       [348.0]],      [[349.0],       [350.0]]],     [[[351.0],       [352.0]],      [[353.0],       [354.0]],      [[355.0],       [356.0]],      [[357.0],       [358.0]],      [[359.0],       [360.0]],      [[361.0],       [362.0]],      [[363.0],       [364.0]]]],    [[[[365.0],       [366.0]],      [[367.0],       [368.0]],      [[369.0],       [370.0]],      [[371.0],       [372.0]],      [[373.0],       [374.0]],      [[375.0],       [376.0]],      [[377.0],       [378.0]]],     [[[379.0],       [380.0]],      [[381.0],       [382.0]],      [[383.0],       [384.0]],      [[385.0],       [386.0]],      [[387.0],       [388.0]],      [[389.0],       [390.0]],      [[391.0],       [392.0]]]]],   [[[[[393.0],       [394.0]],      [[395.0],       [396.0]],      [[397.0],       [398.0]],      [[399.0],       [400.0]],      [[401.0],       [402.0]],      [[403.0],       [404.0]],      [[405.0],       [406.0]]],     [[[407.0],       [408.0]],      [[409.0],       [410.0]],      [[411.0],       [412.0]],      [[413.0],       [414.0]],      [[415.0],       [416.0]],      [[417.0],       [418.0]],      [[419.0],       [420.0]]]],    [[[[421.0],       [422.0]],      [[423.0],       [424.0]],      [[425.0],       [426.0]],      [[427.0],       [428.0]],      [[429.0],       [430.0]],      [[431.0],       [432.0]],      [[433.0],       [434.0]]],     [[[435.0],       [436.0]],      [[437.0],       [438.0]],      [[439.0],       [440.0]],      [[441.0],       [442.0]],      [[443.0],       [444.0]],      [[445.0],       [446.0]],      [[447.0],       [448.0]]]],    [[[[449.0],       [450.0]],      [[451.0],       [452.0]],      [[453.0],       [454.0]],      [[455.0],       [456.0]],      [[457.0],       [458.0]],      [[459.0],       [460.0]],      [[461.0],       [462.0]]],     [[[463.0],       [464.0]],      [[465.0],       [466.0]],      [[467.0],       [468.0]],      [[469.0],       [470.0]],      [[471.0],       [472.0]],      [[473.0],       [474.0]],      [[475.0],       [476.0]]]],    [[[[477.0],       [478.0]],      [[479.0],       [480.0]],      [[481.0],       [482.0]],      [[483.0],       [484.0]],      [[485.0],       [486.0]],      [[487.0],       [488.0]],      [[489.0],       [490.0]]],     [[[491.0],       [492.0]],      [[493.0],       [494.0]],      [[495.0],       [496.0]],      [[497.0],       [498.0]],      [[499.0],       [500.0]],      [[501.0],       [502.0]],      [[503.0],       [504.0]]]],    [[[[505.0],       [506.0]],      [[507.0],       [508.0]],      [[509.0],       [510.0]],      [[511.0],       [512.0]],      [[513.0],       [514.0]],      [[515.0],       [516.0]],      [[517.0],       [518.0]]],     [[[519.0],       [520.0]],      [[521.0],       [522.0]],      [[523.0],       [524.0]],      [[525.0],       [526.0]],      [[527.0],       [528.0]],      [[529.0],       [530.0]],      [[531.0],       [532.0]]]],    [[[[533.0],       [534.0]],      [[535.0],       [536.0]],      [[537.0],       [538.0]],      [[539.0],       [540.0]],      [[541.0],       [542.0]],      [[543.0],       [544.0]],      [[545.0],       [546.0]]],     [[[547.0],       [548.0]],      [[549.0],       [550.0]],      [[551.0],       [552.0]],      [[553.0],       [554.0]],      [[555.0],       [556.0]],      [[557.0],       [558.0]],      [[559.0],       [560.0]]]],    [[[[561.0],       [562.0]],      [[563.0],       [564.0]],      [[565.0],       [566.0]],      [[567.0],       [568.0]],      [[569.0],       [570.0]],      [[571.0],       [572.0]],      [[573.0],       [574.0]]],     [[[575.0],       [576.0]],      [[577.0],       [578.0]],      [[579.0],       [580.0]],      [[581.0],       [582.0]],      [[583.0],       [584.0]],      [[585.0],       [586.0]],      [[587.0],       [588.0]]]]],   [[[[[589.0],       [590.0]],      [[591.0],       [592.0]],      [[593.0],       [594.0]],      [[595.0],       [596.0]],      [[597.0],       [598.0]],      [[599.0],       [600.0]],      [[601.0],       [602.0]]],     [[[603.0],       [604.0]],      [[605.0],       [606.0]],      [[607.0],       [608.0]],      [[609.0],       [610.0]],      [[611.0],       [612.0]],      [[613.0],       [614.0]],      [[615.0],       [616.0]]]],    [[[[617.0],       [618.0]],      [[619.0],       [620.0]],      [[621.0],       [622.0]],      [[623.0],       [624.0]],      [[625.0],       [626.0]],      [[627.0],       [628.0]],      [[629.0],       [630.0]]],     [[[631.0],       [632.0]],      [[633.0],       [634.0]],      [[635.0],       [636.0]],      [[637.0],       [638.0]],      [[639.0],       [640.0]],      [[641.0],       [642.0]],      [[643.0],       [644.0]]]],    [[[[645.0],       [646.0]],      [[647.0],       [648.0]],      [[649.0],       [650.0]],      [[651.0],       [652.0]],      [[653.0],       [654.0]],      [[655.0],       [656.0]],      [[657.0],       [658.0]]],     [[[659.0],       [660.0]],      [[661.0],       [662.0]],      [[663.0],       [664.0]],      [[665.0],       [666.0]],      [[667.0],       [668.0]],      [[669.0],       [670.0]],      [[671.0],       [672.0]]]],    [[[[673.0],       [674.0]],      [[675.0],       [676.0]],      [[677.0],       [678.0]],      [[679.0],       [680.0]],      [[681.0],       [682.0]],      [[683.0],       [684.0]],      [[685.0],       [686.0]]],     [[[687.0],       [688.0]],      [[689.0],       [690.0]],      [[691.0],       [692.0]],      [[693.0],       [694.0]],      [[695.0],       [696.0]],      [[697.0],       [698.0]],      [[699.0],       [700.0]]]],    [[[[701.0],       [702.0]],      [[703.0],       [704.0]],      [[705.0],       [706.0]],      [[707.0],       [708.0]],      [[709.0],       [710.0]],      [[711.0],       [712.0]],      [[713.0],       [714.0]]],     [[[715.0],       [716.0]],      [[717.0],       [718.0]],      [[719.0],       [720.0]],      [[721.0],       [722.0]],      [[723.0],       [724.0]],      [[725.0],       [726.0]],      [[727.0],       [728.0]]]],    [[[[729.0],       [730.0]],      [[731.0],       [732.0]],      [[733.0],       [734.0]],      [[735.0],       [736.0]],      [[737.0],       [738.0]],      [[739.0],       [740.0]],      [[741.0],       [742.0]]],     [[[743.0],       [744.0]],      [[745.0],       [746.0]],      [[747.0],       [748.0]],      [[749.0],       [750.0]],      [[751.0],       [752.0]],      [[753.0],       [754.0]],      [[755.0],       [756.0]]]],    [[[[757.0],       [758.0]],      [[759.0],       [760.0]],      [[761.0],       [762.0]],      [[763.0],       [764.0]],      [[765.0],       [766.0]],      [[767.0],       [768.0]],      [[769.0],       [770.0]]],     [[[771.0],       [772.0]],      [[773.0],       [774.0]],      [[775.0],       [776.0]],      [[777.0],       [778.0]],      [[779.0],       [780.0]],      [[781.0],       [782.0]],      [[783.0],       [784.0]]]]],   [[[[[785.0],       [786.0]],      [[787.0],       [788.0]],      [[789.0],       [790.0]],      [[791.0],       [792.0]],      [[793.0],       [794.0]],      [[795.0],       [796.0]],      [[797.0],       [798.0]]],     [[[799.0],       [800.0]],      [[801.0],       [802.0]],      [[803.0],       [804.0]],      [[805.0],       [806.0]],      [[807.0],       [808.0]],      [[809.0],       [810.0]],      [[811.0],       [812.0]]]],    [[[[813.0],       [814.0]],      [[815.0],       [816.0]],      [[817.0],       [818.0]],      [[819.0],       [820.0]],      [[821.0],       [822.0]],      [[823.0],       [824.0]],      [[825.0],       [826.0]]],     [[[827.0],       [828.0]],      [[829.0],       [830.0]],      [[831.0],       [832.0]],      [[833.0],       [834.0]],      [[835.0],       [836.0]],      [[837.0],       [838.0]],      [[839.0],       [840.0]]]],    [[[[841.0],       [842.0]],      [[843.0],       [844.0]],      [[845.0],       [846.0]],      [[847.0],       [848.0]],      [[849.0],       [850.0]],      [[851.0],       [852.0]],      [[853.0],       [854.0]]],     [[[855.0],       [856.0]],      [[857.0],       [858.0]],      [[859.0],       [860.0]],      [[861.0],       [862.0]],      [[863.0],       [864.0]],      [[865.0],       [866.0]],      [[867.0],       [868.0]]]],    [[[[869.0],       [870.0]],      [[871.0],       [872.0]],      [[873.0],       [874.0]],      [[875.0],       [876.0]],      [[877.0],       [878.0]],      [[879.0],       [880.0]],      [[881.0],       [882.0]]],     [[[883.0],       [884.0]],      [[885.0],       [886.0]],      [[887.0],       [888.0]],      [[889.0],       [890.0]],      [[891.0],       [892.0]],      [[893.0],       [894.0]],      [[895.0],       [896.0]]]],    [[[[897.0],       [898.0]],      [[899.0],       [900.0]],      [[901.0],       [902.0]],      [[903.0],       [904.0]],      [[905.0],       [906.0]],      [[907.0],       [908.0]],      [[909.0],       [910.0]]],     [[[911.0],       [912.0]],      [[913.0],       [914.0]],      [[915.0],       [916.0]],      [[917.0],       [918.0]],      [[919.0],       [920.0]],      [[921.0],       [922.0]],      [[923.0],       [924.0]]]],    [[[[925.0],       [926.0]],      [[927.0],       [928.0]],      [[929.0],       [930.0]],      [[931.0],       [932.0]],      [[933.0],       [934.0]],      [[935.0],       [936.0]],      [[937.0],       [938.0]]],     [[[939.0],       [940.0]],      [[941.0],       [942.0]],      [[943.0],       [944.0]],      [[945.0],       [946.0]],      [[947.0],       [948.0]],      [[949.0],       [950.0]],      [[951.0],       [952.0]]]],    [[[[953.0],       [954.0]],      [[955.0],       [956.0]],      [[957.0],       [958.0]],      [[959.0],       [960.0]],      [[961.0],       [962.0]],      [[963.0],       [964.0]],      [[965.0],       [966.0]]],     [[[967.0],       [968.0]],      [[969.0],       [970.0]],      [[971.0],       [972.0]],      [[973.0],       [974.0]],      [[975.0],       [976.0]],      [[977.0],       [978.0]],      [[979.0],       [980.0]]]]],   [[[[[981.0],       [982.0]],      [[983.0],       [984.0]],      [[985.0],       [986.0]],      [[987.0],       [988.0]],      [[989.0],       [990.0]],      [[991.0],       [992.0]],      [[993.0],       [994.0]]],     [[[995.0],       [996.0]],      [[997.0],       [998.0]],      [[999.0],       [1000.0]],      [[1001.0],       [1002.0]],      [[1003.0],       [1004.0]],      [[1005.0],       [1006.0]],      [[1007.0],       [1008.0]]]],    [[[[1009.0],       [1010.0]],      [[1011.0],       [1012.0]],      [[1013.0],       [1014.0]],      [[1015.0],       [1016.0]],      [[1017.0],       [1018.0]],      [[1019.0],       [1020.0]],      [[1021.0],       [1022.0]]],     [[[1023.0],       [1024.0]],      [[1025.0],       [1026.0]],      [[1027.0],       [1028.0]],      [[1029.0],       [1030.0]],      [[1031.0],       [1032.0]],      [[1033.0],       [1034.0]],      [[1035.0],       [1036.0]]]],    [[[[1037.0],       [1038.0]],      [[1039.0],       [1040.0]],      [[1041.0],       [1042.0]],      [[1043.0],       [1044.0]],      [[1045.0],       [1046.0]],      [[1047.0],       [1048.0]],      [[1049.0],       [1050.0]]],     [[[1051.0],       [1052.0]],      [[1053.0],       [1054.0]],      [[1055.0],       [1056.0]],      [[1057.0],       [1058.0]],      [[1059.0],       [1060.0]],      [[1061.0],       [1062.0]],      [[1063.0],       [1064.0]]]],    [[[[1065.0],       [1066.0]],      [[1067.0],       [1068.0]],      [[1069.0],       [1070.0]],      [[1071.0],       [1072.0]],      [[1073.0],       [1074.0]],      [[1075.0],       [1076.0]],      [[1077.0],       [1078.0]]],     [[[1079.0],       [1080.0]],      [[1081.0],       [1082.0]],      [[1083.0],       [1084.0]],      [[1085.0],       [1086.0]],      [[1087.0],       [1088.0]],      [[1089.0],       [1090.0]],      [[1091.0],       [1092.0]]]],    [[[[1093.0],       [1094.0]],      [[1095.0],       [1096.0]],      [[1097.0],       [1098.0]],      [[1099.0],       [1100.0]],      [[1101.0],       [1102.0]],      [[1103.0],       [1104.0]],      [[1105.0],       [1106.0]]],     [[[1107.0],       [1108.0]],      [[1109.0],       [1110.0]],      [[1111.0],       [1112.0]],      [[1113.0],       [1114.0]],      [[1115.0],       [1116.0]],      [[1117.0],       [1118.0]],      [[1119.0],       [1120.0]]]],    [[[[1121.0],       [1122.0]],      [[1123.0],       [1124.0]],      [[1125.0],       [1126.0]],      [[1127.0],       [1128.0]],      [[1129.0],       [1130.0]],      [[1131.0],       [1132.0]],      [[1133.0],       [1134.0]]],     [[[1135.0],       [1136.0]],      [[1137.0],       [1138.0]],      [[1139.0],       [1140.0]],      [[1141.0],       [1142.0]],      [[1143.0],       [1144.0]],      [[1145.0],       [1146.0]],      [[1147.0],       [1148.0]]]],    [[[[1149.0],       [1150.0]],      [[1151.0],       [1152.0]],      [[1153.0],       [1154.0]],      [[1155.0],       [1156.0]],      [[1157.0],       [1158.0]],      [[1159.0],       [1160.0]],      [[1161.0],       [1162.0]]],     [[[1163.0],       [1164.0]],      [[1165.0],       [1166.0]],      [[1167.0],       [1168.0]],      [[1169.0],       [1170.0]],      [[1171.0],       [1172.0]],      [[1173.0],       [1174.0]],      [[1175.0],       [1176.0]]]]]]] shape=[1, 6, 7, 2, 7, 2, 1], strides=[1176, 196, 28, 14, 2, 1, 1], layout=C (0x1)), I32([2, 1, 1] shape=[3], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [1, 1],  [0, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 1807712379 1150418439 3241773026 2048754559 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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, 16.0],       [17.0, 18.0]]],     [[[19.0, 20.0],       [21.0, 22.0],       [23.0, 24.0]],      [[25.0, 26.0],       [27.0, 28.0],       [29.0, 30.0]],      [[31.0, 32.0],       [33.0, 34.0],       [35.0, 36.0]]],     [[[37.0, 38.0],       [39.0, 40.0],       [41.0, 42.0]],      [[43.0, 44.0],       [45.0, 46.0],       [47.0, 48.0]],      [[49.0, 50.0],       [51.0, 52.0],       [53.0, 54.0]]],     [[[55.0, 56.0],       [57.0, 58.0],       [59.0, 60.0]],      [[61.0, 62.0],       [63.0, 64.0],       [65.0, 66.0]],      [[67.0, 68.0],       [69.0, 70.0],       [71.0, 72.0]]]],    [[[[73.0, 74.0],       [75.0, 76.0],       [77.0, 78.0]],      [[79.0, 80.0],       [81.0, 82.0],       [83.0, 84.0]],      [[85.0, 86.0],       [87.0, 88.0],       [89.0, 90.0]]],     [[[91.0, 92.0],       [93.0, 94.0],       [95.0, 96.0]],      [[97.0, 98.0],       [99.0, 100.0],       [101.0, 102.0]],      [[103.0, 104.0],       [105.0, 106.0],       [107.0, 108.0]]],     [[[109.0, 110.0],       [111.0, 112.0],       [113.0, 114.0]],      [[115.0, 116.0],       [117.0, 118.0],       [119.0, 120.0]],      [[121.0, 122.0],       [123.0, 124.0],       [125.0, 126.0]]],     [[[127.0, 128.0],       [129.0, 130.0],       [131.0, 132.0]],      [[133.0, 134.0],       [135.0, 136.0],       [137.0, 138.0]],      [[139.0, 140.0],       [141.0, 142.0],       [143.0, 144.0]]]],    [[[[145.0, 146.0],       [147.0, 148.0],       [149.0, 150.0]],      [[151.0, 152.0],       [153.0, 154.0],       [155.0, 156.0]],      [[157.0, 158.0],       [159.0, 160.0],       [161.0, 162.0]]],     [[[163.0, 164.0],       [165.0, 166.0],       [167.0, 168.0]],      [[169.0, 170.0],       [171.0, 172.0],       [173.0, 174.0]],      [[175.0, 176.0],       [177.0, 178.0],       [179.0, 180.0]]],     [[[181.0, 182.0],       [183.0, 184.0],       [185.0, 186.0]],      [[187.0, 188.0],       [189.0, 190.0],       [191.0, 192.0]],      [[193.0, 194.0],       [195.0, 196.0],       [197.0, 198.0]]],     [[[199.0, 200.0],       [201.0, 202.0],       [203.0, 204.0]],      [[205.0, 206.0],       [207.0, 208.0],       [209.0, 210.0]],      [[211.0, 212.0],       [213.0, 214.0],       [215.0, 216.0]]]],    [[[[217.0, 218.0],       [219.0, 220.0],       [221.0, 222.0]],      [[223.0, 224.0],       [225.0, 226.0],       [227.0, 228.0]],      [[229.0, 230.0],       [231.0, 232.0],       [233.0, 234.0]]],     [[[235.0, 236.0],       [237.0, 238.0],       [239.0, 240.0]],      [[241.0, 242.0],       [243.0, 244.0],       [245.0, 246.0]],      [[247.0, 248.0],       [249.0, 250.0],       [251.0, 252.0]]],     [[[253.0, 254.0],       [255.0, 256.0],       [257.0, 258.0]],      [[259.0, 260.0],       [261.0, 262.0],       [263.0, 264.0]],      [[265.0, 266.0],       [267.0, 268.0],       [269.0, 270.0]]],     [[[271.0, 272.0],       [273.0, 274.0],       [275.0, 276.0]],      [[277.0, 278.0],       [279.0, 280.0],       [281.0, 282.0]],      [[283.0, 284.0],       [285.0, 286.0],       [287.0, 288.0]]]],    [[[[289.0, 290.0],       [291.0, 292.0],       [293.0, 294.0]],      [[295.0, 296.0],       [297.0, 298.0],       [299.0, 300.0]],      [[301.0, 302.0],       [303.0, 304.0],       [305.0, 306.0]]],     [[[307.0, 308.0],       [309.0, 310.0],       [311.0, 312.0]],      [[313.0, 314.0],       [315.0, 316.0],       [317.0, 318.0]],      [[319.0, 320.0],       [321.0, 322.0],       [323.0, 324.0]]],     [[[325.0, 326.0],       [327.0, 328.0],       [329.0, 330.0]],      [[331.0, 332.0],       [333.0, 334.0],       [335.0, 336.0]],      [[337.0, 338.0],       [339.0, 340.0],       [341.0, 342.0]]],     [[[343.0, 344.0],       [345.0, 346.0],       [347.0, 348.0]],      [[349.0, 350.0],       [351.0, 352.0],       [353.0, 354.0]],      [[355.0, 356.0],       [357.0, 358.0],       [359.0, 360.0]]]],    [[[[361.0, 362.0],       [363.0, 364.0],       [365.0, 366.0]],      [[367.0, 368.0],       [369.0, 370.0],       [371.0, 372.0]],      [[373.0, 374.0],       [375.0, 376.0],       [377.0, 378.0]]],     [[[379.0, 380.0],       [381.0, 382.0],       [383.0, 384.0]],      [[385.0, 386.0],       [387.0, 388.0],       [389.0, 390.0]],      [[391.0, 392.0],       [393.0, 394.0],       [395.0, 396.0]]],     [[[397.0, 398.0],       [399.0, 400.0],       [401.0, 402.0]],      [[403.0, 404.0],       [405.0, 406.0],       [407.0, 408.0]],      [[409.0, 410.0],       [411.0, 412.0],       [413.0, 414.0]]],     [[[415.0, 416.0],       [417.0, 418.0],       [419.0, 420.0]],      [[421.0, 422.0],       [423.0, 424.0],       [425.0, 426.0]],      [[427.0, 428.0],       [429.0, 430.0],       [431.0, 432.0]]]]],   [[[[[433.0, 434.0],       [435.0, 436.0],       [437.0, 438.0]],      [[439.0, 440.0],       [441.0, 442.0],       [443.0, 444.0]],      [[445.0, 446.0],       [447.0, 448.0],       [449.0, 450.0]]],     [[[451.0, 452.0],       [453.0, 454.0],       [455.0, 456.0]],      [[457.0, 458.0],       [459.0, 460.0],       [461.0, 462.0]],      [[463.0, 464.0],       [465.0, 466.0],       [467.0, 468.0]]],     [[[469.0, 470.0],       [471.0, 472.0],       [473.0, 474.0]],      [[475.0, 476.0],       [477.0, 478.0],       [479.0, 480.0]],      [[481.0, 482.0],       [483.0, 484.0],       [485.0, 486.0]]],     [[[487.0, 488.0],       [489.0, 490.0],       [491.0, 492.0]],      [[493.0, 494.0],       [495.0, 496.0],       [497.0, 498.0]],      [[499.0, 500.0],       [501.0, 502.0],       [503.0, 504.0]]]],    [[[[505.0, 506.0],       [507.0, 508.0],       [509.0, 510.0]],      [[511.0, 512.0],       [513.0, 514.0],       [515.0, 516.0]],      [[517.0, 518.0],       [519.0, 520.0],       [521.0, 522.0]]],     [[[523.0, 524.0],       [525.0, 526.0],       [527.0, 528.0]],      [[529.0, 530.0],       [531.0, 532.0],       [533.0, 534.0]],      [[535.0, 536.0],       [537.0, 538.0],       [539.0, 540.0]]],     [[[541.0, 542.0],       [543.0, 544.0],       [545.0, 546.0]],      [[547.0, 548.0],       [549.0, 550.0],       [551.0, 552.0]],      [[553.0, 554.0],       [555.0, 556.0],       [557.0, 558.0]]],     [[[559.0, 560.0],       [561.0, 562.0],       [563.0, 564.0]],      [[565.0, 566.0],       [567.0, 568.0],       [569.0, 570.0]],      [[571.0, 572.0],       [573.0, 574.0],       [575.0, 576.0]]]],    [[[[577.0, 578.0],       [579.0, 580.0],       [581.0, 582.0]],      [[583.0, 584.0],       [585.0, 586.0],       [587.0, 588.0]],      [[589.0, 590.0],       [591.0, 592.0],       [593.0, 594.0]]],     [[[595.0, 596.0],       [597.0, 598.0],       [599.0, 600.0]],      [[601.0, 602.0],       [603.0, 604.0],       [605.0, 606.0]],      [[607.0, 608.0],       [609.0, 610.0],       [611.0, 612.0]]],     [[[613.0, 614.0],       [615.0, 616.0],       [617.0, 618.0]],      [[619.0, 620.0],       [621.0, 622.0],       [623.0, 624.0]],      [[625.0, 626.0],       [627.0, 628.0],       [629.0, 630.0]]],     [[[631.0, 632.0],       [633.0, 634.0],       [635.0, 636.0]],      [[637.0, 638.0],       [639.0, 640.0],       [641.0, 642.0]],      [[643.0, 644.0],       [645.0, 646.0],       [647.0, 648.0]]]],    [[[[649.0, 650.0],       [651.0, 652.0],       [653.0, 654.0]],      [[655.0, 656.0],       [657.0, 658.0],       [659.0, 660.0]],      [[661.0, 662.0],       [663.0, 664.0],       [665.0, 666.0]]],     [[[667.0, 668.0],       [669.0, 670.0],       [671.0, 672.0]],      [[673.0, 674.0],       [675.0, 676.0],       [677.0, 678.0]],      [[679.0, 680.0],       [681.0, 682.0],       [683.0, 684.0]]],     [[[685.0, 686.0],       [687.0, 688.0],       [689.0, 690.0]],      [[691.0, 692.0],       [693.0, 694.0],       [695.0, 696.0]],      [[697.0, 698.0],       [699.0, 700.0],       [701.0, 702.0]]],     [[[703.0, 704.0],       [705.0, 706.0],       [707.0, 708.0]],      [[709.0, 710.0],       [711.0, 712.0],       [713.0, 714.0]],      [[715.0, 716.0],       [717.0, 718.0],       [719.0, 720.0]]]],    [[[[721.0, 722.0],       [723.0, 724.0],       [725.0, 726.0]],      [[727.0, 728.0],       [729.0, 730.0],       [731.0, 732.0]],      [[733.0, 734.0],       [735.0, 736.0],       [737.0, 738.0]]],     [[[739.0, 740.0],       [741.0, 742.0],       [743.0, 744.0]],      [[745.0, 746.0],       [747.0, 748.0],       [749.0, 750.0]],      [[751.0, 752.0],       [753.0, 754.0],       [755.0, 756.0]]],     [[[757.0, 758.0],       [759.0, 760.0],       [761.0, 762.0]],      [[763.0, 764.0],       [765.0, 766.0],       [767.0, 768.0]],      [[769.0, 770.0],       [771.0, 772.0],       [773.0, 774.0]]],     [[[775.0, 776.0],       [777.0, 778.0],       [779.0, 780.0]],      [[781.0, 782.0],       [783.0, 784.0],       [785.0, 786.0]],      [[787.0, 788.0],       [789.0, 790.0],       [791.0, 792.0]]]],    [[[[793.0, 794.0],       [795.0, 796.0],       [797.0, 798.0]],      [[799.0, 800.0],       [801.0, 802.0],       [803.0, 804.0]],      [[805.0, 806.0],       [807.0, 808.0],       [809.0, 810.0]]],     [[[811.0, 812.0],       [813.0, 814.0],       [815.0, 816.0]],      [[817.0, 818.0],       [819.0, 820.0],       [821.0, 822.0]],      [[823.0, 824.0],       [825.0, 826.0],       [827.0, 828.0]]],     [[[829.0, 830.0],       [831.0, 832.0],       [833.0, 834.0]],      [[835.0, 836.0],       [837.0, 838.0],       [839.0, 840.0]],      [[841.0, 842.0],       [843.0, 844.0],       [845.0, 846.0]]],     [[[847.0, 848.0],       [849.0, 850.0],       [851.0, 852.0]],      [[853.0, 854.0],       [855.0, 856.0],       [857.0, 858.0]],      [[859.0, 860.0],       [861.0, 862.0],       [863.0, 864.0]]]]]],  [[[[[[865.0, 866.0],       [867.0, 868.0],       [869.0, 870.0]],      [[871.0, 872.0],       [873.0, 874.0],       [875.0, 876.0]],      [[877.0, 878.0],       [879.0, 880.0],       [881.0, 882.0]]],     [[[883.0, 884.0],       [885.0, 886.0],       [887.0, 888.0]],      [[889.0, 890.0],       [891.0, 892.0],       [893.0, 894.0]],      [[895.0, 896.0],       [897.0, 898.0],       [899.0, 900.0]]],     [[[901.0, 902.0],       [903.0, 904.0],       [905.0, 906.0]],      [[907.0, 908.0],       [909.0, 910.0],       [911.0, 912.0]],      [[913.0, 914.0],       [915.0, 916.0],       [917.0, 918.0]]],     [[[919.0, 920.0],       [921.0, 922.0],       [923.0, 924.0]],      [[925.0, 926.0],       [927.0, 928.0],       [929.0, 930.0]],      [[931.0, 932.0],       [933.0, 934.0],       [935.0, 936.0]]]],    [[[[937.0, 938.0],       [939.0, 940.0],       [941.0, 942.0]],      [[943.0, 944.0],       [945.0, 946.0],       [947.0, 948.0]],      [[949.0, 950.0],       [951.0, 952.0],       [953.0, 954.0]]],     [[[955.0, 956.0],       [957.0, 958.0],       [959.0, 960.0]],      [[961.0, 962.0],       [963.0, 964.0],       [965.0, 966.0]],      [[967.0, 968.0],       [969.0, 970.0],       [971.0, 972.0]]],     [[[973.0, 974.0],       [975.0, 976.0],       [977.0, 978.0]],      [[979.0, 980.0],       [981.0, 982.0],       [983.0, 984.0]],      [[985.0, 986.0],       [987.0, 988.0],       [989.0, 990.0]]],     [[[991.0, 992.0],       [993.0, 994.0],       [995.0, 996.0]],      [[997.0, 998.0],       [999.0, 1000.0],       [1001.0, 1002.0]],      [[1003.0, 1004.0],       [1005.0, 1006.0],       [1007.0, 1008.0]]]],    [[[[1009.0, 1010.0],       [1011.0, 1012.0],       [1013.0, 1014.0]],      [[1015.0, 1016.0],       [1017.0, 1018.0],       [1019.0, 1020.0]],      [[1021.0, 1022.0],       [1023.0, 1024.0],       [1025.0, 1026.0]]],     [[[1027.0, 1028.0],       [1029.0, 1030.0],       [1031.0, 1032.0]],      [[1033.0, 1034.0],       [1035.0, 1036.0],       [1037.0, 1038.0]],      [[1039.0, 1040.0],       [1041.0, 1042.0],       [1043.0, 1044.0]]],     [[[1045.0, 1046.0],       [1047.0, 1048.0],       [1049.0, 1050.0]],      [[1051.0, 1052.0],       [1053.0, 1054.0],       [1055.0, 1056.0]],      [[1057.0, 1058.0],       [1059.0, 1060.0],       [1061.0, 1062.0]]],     [[[1063.0, 1064.0],       [1065.0, 1066.0],       [1067.0, 1068.0]],      [[1069.0, 1070.0],       [1071.0, 1072.0],       [1073.0, 1074.0]],      [[1075.0, 1076.0],       [1077.0, 1078.0],       [1079.0, 1080.0]]]],    [[[[1081.0, 1082.0],       [1083.0, 1084.0],       [1085.0, 1086.0]],      [[1087.0, 1088.0],       [1089.0, 1090.0],       [1091.0, 1092.0]],      [[1093.0, 1094.0],       [1095.0, 1096.0],       [1097.0, 1098.0]]],     [[[1099.0, 1100.0],       [1101.0, 1102.0],       [1103.0, 1104.0]],      [[1105.0, 1106.0],       [1107.0, 1108.0],       [1109.0, 1110.0]],      [[1111.0, 1112.0],       [1113.0, 1114.0],       [1115.0, 1116.0]]],     [[[1117.0, 1118.0],       [1119.0, 1120.0],       [1121.0, 1122.0]],      [[1123.0, 1124.0],       [1125.0, 1126.0],       [1127.0, 1128.0]],      [[1129.0, 1130.0],       [1131.0, 1132.0],       [1133.0, 1134.0]]],     [[[1135.0, 1136.0],       [1137.0, 1138.0],       [1139.0, 1140.0]],      [[1141.0, 1142.0],       [1143.0, 1144.0],       [1145.0, 1146.0]],      [[1147.0, 1148.0],       [1149.0, 1150.0],       [1151.0, 1152.0]]]],    [[[[1153.0, 1154.0],       [1155.0, 1156.0],       [1157.0, 1158.0]],      [[1159.0, 1160.0],       [1161.0, 1162.0],       [1163.0, 1164.0]],      [[1165.0, 1166.0],       [1167.0, 1168.0],       [1169.0, 1170.0]]],     [[[1171.0, 1172.0],       [1173.0, 1174.0],       [1175.0, 1176.0]],      [[1177.0, 1178.0],       [1179.0, 1180.0],       [1181.0, 1182.0]],      [[1183.0, 1184.0],       [1185.0, 1186.0],       [1187.0, 1188.0]]],     [[[1189.0, 1190.0],       [1191.0, 1192.0],       [1193.0, 1194.0]],      [[1195.0, 1196.0],       [1197.0, 1198.0],       [1199.0, 1200.0]],      [[1201.0, 1202.0],       [1203.0, 1204.0],       [1205.0, 1206.0]]],     [[[1207.0, 1208.0],       [1209.0, 1210.0],       [1211.0, 1212.0]],      [[1213.0, 1214.0],       [1215.0, 1216.0],       [1217.0, 1218.0]],      [[1219.0, 1220.0],       [1221.0, 1222.0],       [1223.0, 1224.0]]]],    [[[[1225.0, 1226.0],       [1227.0, 1228.0],       [1229.0, 1230.0]],      [[1231.0, 1232.0],       [1233.0, 1234.0],       [1235.0, 1236.0]],      [[1237.0, 1238.0],       [1239.0, 1240.0],       [1241.0, 1242.0]]],     [[[1243.0, 1244.0],       [1245.0, 1246.0],       [1247.0, 1248.0]],      [[1249.0, 1250.0],       [1251.0, 1252.0],       [1253.0, 1254.0]],      [[1255.0, 1256.0],       [1257.0, 1258.0],       [1259.0, 1260.0]]],     [[[1261.0, 1262.0],       [1263.0, 1264.0],       [1265.0, 1266.0]],      [[1267.0, 1268.0],       [1269.0, 1270.0],       [1271.0, 1272.0]],      [[1273.0, 1274.0],       [1275.0, 1276.0],       [1277.0, 1278.0]]],     [[[1279.0, 1280.0],       [1281.0, 1282.0],       [1283.0, 1284.0]],      [[1285.0, 1286.0],       [1287.0, 1288.0],       [1289.0, 1290.0]],      [[1291.0, 1292.0],       [1293.0, 1294.0],       [1295.0, 1296.0]]]]],   [[[[[1297.0, 1298.0],       [1299.0, 1300.0],       [1301.0, 1302.0]],      [[1303.0, 1304.0],       [1305.0, 1306.0],       [1307.0, 1308.0]],      [[1309.0, 1310.0],       [1311.0, 1312.0],       [1313.0, 1314.0]]],     [[[1315.0, 1316.0],       [1317.0, 1318.0],       [1319.0, 1320.0]],      [[1321.0, 1322.0],       [1323.0, 1324.0],       [1325.0, 1326.0]],      [[1327.0, 1328.0],       [1329.0, 1330.0],       [1331.0, 1332.0]]],     [[[1333.0, 1334.0],       [1335.0, 1336.0],       [1337.0, 1338.0]],      [[1339.0, 1340.0],       [1341.0, 1342.0],       [1343.0, 1344.0]],      [[1345.0, 1346.0],       [1347.0, 1348.0],       [1349.0, 1350.0]]],     [[[1351.0, 1352.0],       [1353.0, 1354.0],       [1355.0, 1356.0]],      [[1357.0, 1358.0],       [1359.0, 1360.0],       [1361.0, 1362.0]],      [[1363.0, 1364.0],       [1365.0, 1366.0],       [1367.0, 1368.0]]]],    [[[[1369.0, 1370.0],       [1371.0, 1372.0],       [1373.0, 1374.0]],      [[1375.0, 1376.0],       [1377.0, 1378.0],       [1379.0, 1380.0]],      [[1381.0, 1382.0],       [1383.0, 1384.0],       [1385.0, 1386.0]]],     [[[1387.0, 1388.0],       [1389.0, 1390.0],       [1391.0, 1392.0]],      [[1393.0, 1394.0],       [1395.0, 1396.0],       [1397.0, 1398.0]],      [[1399.0, 1400.0],       [1401.0, 1402.0],       [1403.0, 1404.0]]],     [[[1405.0, 1406.0],       [1407.0, 1408.0],       [1409.0, 1410.0]],      [[1411.0, 1412.0],       [1413.0, 1414.0],       [1415.0, 1416.0]],      [[1417.0, 1418.0],       [1419.0, 1420.0],       [1421.0, 1422.0]]],     [[[1423.0, 1424.0],       [1425.0, 1426.0],       [1427.0, 1428.0]],      [[1429.0, 1430.0],       [1431.0, 1432.0],       [1433.0, 1434.0]],      [[1435.0, 1436.0],       [1437.0, 1438.0],       [1439.0, 1440.0]]]],    [[[[1441.0, 1442.0],       [1443.0, 1444.0],       [1445.0, 1446.0]],      [[1447.0, 1448.0],       [1449.0, 1450.0],       [1451.0, 1452.0]],      [[1453.0, 1454.0],       [1455.0, 1456.0],       [1457.0, 1458.0]]],     [[[1459.0, 1460.0],       [1461.0, 1462.0],       [1463.0, 1464.0]],      [[1465.0, 1466.0],       [1467.0, 1468.0],       [1469.0, 1470.0]],      [[1471.0, 1472.0],       [1473.0, 1474.0],       [1475.0, 1476.0]]],     [[[1477.0, 1478.0],       [1479.0, 1480.0],       [1481.0, 1482.0]],      [[1483.0, 1484.0],       [1485.0, 1486.0],       [1487.0, 1488.0]],      [[1489.0, 1490.0],       [1491.0, 1492.0],       [1493.0, 1494.0]]],     [[[1495.0, 1496.0],       [1497.0, 1498.0],       [1499.0, 1500.0]],      [[1501.0, 1502.0],       [1503.0, 1504.0],       [1505.0, 1506.0]],      [[1507.0, 1508.0],       [1509.0, 1510.0],       [1511.0, 1512.0]]]],    [[[[1513.0, 1514.0],       [1515.0, 1516.0],       [1517.0, 1518.0]],      [[1519.0, 1520.0],       [1521.0, 1522.0],       [1523.0, 1524.0]],      [[1525.0, 1526.0],       [1527.0, 1528.0],       [1529.0, 1530.0]]],     [[[1531.0, 1532.0],       [1533.0, 1534.0],       [1535.0, 1536.0]],      [[1537.0, 1538.0],       [1539.0, 1540.0],       [1541.0, 1542.0]],      [[1543.0, 1544.0],       [1545.0, 1546.0],       [1547.0, 1548.0]]],     [[[1549.0, 1550.0],       [1551.0, 1552.0],       [1553.0, 1554.0]],      [[1555.0, 1556.0],       [1557.0, 1558.0],       [1559.0, 1560.0]],      [[1561.0, 1562.0],       [1563.0, 1564.0],       [1565.0, 1566.0]]],     [[[1567.0, 1568.0],       [1569.0, 1570.0],       [1571.0, 1572.0]],      [[1573.0, 1574.0],       [1575.0, 1576.0],       [1577.0, 1578.0]],      [[1579.0, 1580.0],       [1581.0, 1582.0],       [1583.0, 1584.0]]]],    [[[[1585.0, 1586.0],       [1587.0, 1588.0],       [1589.0, 1590.0]],      [[1591.0, 1592.0],       [1593.0, 1594.0],       [1595.0, 1596.0]],      [[1597.0, 1598.0],       [1599.0, 1600.0],       [1601.0, 1602.0]]],     [[[1603.0, 1604.0],       [1605.0, 1606.0],       [1607.0, 1608.0]],      [[1609.0, 1610.0],       [1611.0, 1612.0],       [1613.0, 1614.0]],      [[1615.0, 1616.0],       [1617.0, 1618.0],       [1619.0, 1620.0]]],     [[[1621.0, 1622.0],       [1623.0, 1624.0],       [1625.0, 1626.0]],      [[1627.0, 1628.0],       [1629.0, 1630.0],       [1631.0, 1632.0]],      [[1633.0, 1634.0],       [1635.0, 1636.0],       [1637.0, 1638.0]]],     [[[1639.0, 1640.0],       [1641.0, 1642.0],       [1643.0, 1644.0]],      [[1645.0, 1646.0],       [1647.0, 1648.0],       [1649.0, 1650.0]],      [[1651.0, 1652.0],       [1653.0, 1654.0],       [1655.0, 1656.0]]]],    [[[[1657.0, 1658.0],       [1659.0, 1660.0],       [1661.0, 1662.0]],      [[1663.0, 1664.0],       [1665.0, 1666.0],       [1667.0, 1668.0]],      [[1669.0, 1670.0],       [1671.0, 1672.0],       [1673.0, 1674.0]]],     [[[1675.0, 1676.0],       [1677.0, 1678.0],       [1679.0, 1680.0]],      [[1681.0, 1682.0],       [1683.0, 1684.0],       [1685.0, 1686.0]],      [[1687.0, 1688.0],       [1689.0, 1690.0],       [1691.0, 1692.0]]],     [[[1693.0, 1694.0],       [1695.0, 1696.0],       [1697.0, 1698.0]],      [[1699.0, 1700.0],       [1701.0, 1702.0],       [1703.0, 1704.0]],      [[1705.0, 1706.0],       [1707.0, 1708.0],       [1709.0, 1710.0]]],     [[[1711.0, 1712.0],       [1713.0, 1714.0],       [1715.0, 1716.0]],      [[1717.0, 1718.0],       [1719.0, 1720.0],       [1721.0, 1722.0]],      [[1723.0, 1724.0],       [1725.0, 1726.0],       [1727.0, 1728.0]]]]]],  [[[[[[1729.0, 1730.0],       [1731.0, 1732.0],       [1733.0, 1734.0]],      [[1735.0, 1736.0],       [1737.0, 1738.0],       [1739.0, 1740.0]],      [[1741.0, 1742.0],       [1743.0, 1744.0],       [1745.0, 1746.0]]],     [[[1747.0, 1748.0],       [1749.0, 1750.0],       [1751.0, 1752.0]],      [[1753.0, 1754.0],       [1755.0, 1756.0],       [1757.0, 1758.0]],      [[1759.0, 1760.0],       [1761.0, 1762.0],       [1763.0, 1764.0]]],     [[[1765.0, 1766.0],       [1767.0, 1768.0],       [1769.0, 1770.0]],      [[1771.0, 1772.0],       [1773.0, 1774.0],       [1775.0, 1776.0]],      [[1777.0, 1778.0],       [1779.0, 1780.0],       [1781.0, 1782.0]]],     [[[1783.0, 1784.0],       [1785.0, 1786.0],       [1787.0, 1788.0]],      [[1789.0, 1790.0],       [1791.0, 1792.0],       [1793.0, 1794.0]],      [[1795.0, 1796.0],       [1797.0, 1798.0],       [1799.0, 1800.0]]]],    [[[[1801.0, 1802.0],       [1803.0, 1804.0],       [1805.0, 1806.0]],      [[1807.0, 1808.0],       [1809.0, 1810.0],       [1811.0, 1812.0]],      [[1813.0, 1814.0],       [1815.0, 1816.0],       [1817.0, 1818.0]]],     [[[1819.0, 1820.0],       [1821.0, 1822.0],       [1823.0, 1824.0]],      [[1825.0, 1826.0],       [1827.0, 1828.0],       [1829.0, 1830.0]],      [[1831.0, 1832.0],       [1833.0, 1834.0],       [1835.0, 1836.0]]],     [[[1837.0, 1838.0],       [1839.0, 1840.0],       [1841.0, 1842.0]],      [[1843.0, 1844.0],       [1845.0, 1846.0],       [1847.0, 1848.0]],      [[1849.0, 1850.0],       [1851.0, 1852.0],       [1853.0, 1854.0]]],     [[[1855.0, 1856.0],       [1857.0, 1858.0],       [1859.0, 1860.0]],      [[1861.0, 1862.0],       [1863.0, 1864.0],       [1865.0, 1866.0]],      [[1867.0, 1868.0],       [1869.0, 1870.0],       [1871.0, 1872.0]]]],    [[[[1873.0, 1874.0],       [1875.0, 1876.0],       [1877.0, 1878.0]],      [[1879.0, 1880.0],       [1881.0, 1882.0],       [1883.0, 1884.0]],      [[1885.0, 1886.0],       [1887.0, 1888.0],       [1889.0, 1890.0]]],     [[[1891.0, 1892.0],       [1893.0, 1894.0],       [1895.0, 1896.0]],      [[1897.0, 1898.0],       [1899.0, 1900.0],       [1901.0, 1902.0]],      [[1903.0, 1904.0],       [1905.0, 1906.0],       [1907.0, 1908.0]]],     [[[1909.0, 1910.0],       [1911.0, 1912.0],       [1913.0, 1914.0]],      [[1915.0, 1916.0],       [1917.0, 1918.0],       [1919.0, 1920.0]],      [[1921.0, 1922.0],       [1923.0, 1924.0],       [1925.0, 1926.0]]],     [[[1927.0, 1928.0],       [1929.0, 1930.0],       [1931.0, 1932.0]],      [[1933.0, 1934.0],       [1935.0, 1936.0],       [1937.0, 1938.0]],      [[1939.0, 1940.0],       [1941.0, 1942.0],       [1943.0, 1944.0]]]],    [[[[1945.0, 1946.0],       [1947.0, 1948.0],       [1949.0, 1950.0]],      [[1951.0, 1952.0],       [1953.0, 1954.0],       [1955.0, 1956.0]],      [[1957.0, 1958.0],       [1959.0, 1960.0],       [1961.0, 1962.0]]],     [[[1963.0, 1964.0],       [1965.0, 1966.0],       [1967.0, 1968.0]],      [[1969.0, 1970.0],       [1971.0, 1972.0],       [1973.0, 1974.0]],      [[1975.0, 1976.0],       [1977.0, 1978.0],       [1979.0, 1980.0]]],     [[[1981.0, 1982.0],       [1983.0, 1984.0],       [1985.0, 1986.0]],      [[1987.0, 1988.0],       [1989.0, 1990.0],       [1991.0, 1992.0]],      [[1993.0, 1994.0],       [1995.0, 1996.0],       [1997.0, 1998.0]]],     [[[1999.0, 2000.0],       [2001.0, 2002.0],       [2003.0, 2004.0]],      [[2005.0, 2006.0],       [2007.0, 2008.0],       [2009.0, 2010.0]],      [[2011.0, 2012.0],       [2013.0, 2014.0],       [2015.0, 2016.0]]]],    [[[[2017.0, 2018.0],       [2019.0, 2020.0],       [2021.0, 2022.0]],      [[2023.0, 2024.0],       [2025.0, 2026.0],       [2027.0, 2028.0]],      [[2029.0, 2030.0],       [2031.0, 2032.0],       [2033.0, 2034.0]]],     [[[2035.0, 2036.0],       [2037.0, 2038.0],       [2039.0, 2040.0]],      [[2041.0, 2042.0],       [2043.0, 2044.0],       [2045.0, 2046.0]],      [[2047.0, 2048.0],       [2049.0, 2050.0],       [2051.0, 2052.0]]],     [[[2053.0, 2054.0],       [2055.0, 2056.0],       [2057.0, 2058.0]],      [[2059.0, 2060.0],       [2061.0, 2062.0],       [2063.0, 2064.0]],      [[2065.0, 2066.0],       [2067.0, 2068.0],       [2069.0, 2070.0]]],     [[[2071.0, 2072.0],       [2073.0, 2074.0],       [2075.0, 2076.0]],      [[2077.0, 2078.0],       [2079.0, 2080.0],       [2081.0, 2082.0]],      [[2083.0, 2084.0],       [2085.0, 2086.0],       [2087.0, 2088.0]]]],    [[[[2089.0, 2090.0],       [2091.0, 2092.0],       [2093.0, 2094.0]],      [[2095.0, 2096.0],       [2097.0, 2098.0],       [2099.0, 2100.0]],      [[2101.0, 2102.0],       [2103.0, 2104.0],       [2105.0, 2106.0]]],     [[[2107.0, 2108.0],       [2109.0, 2110.0],       [2111.0, 2112.0]],      [[2113.0, 2114.0],       [2115.0, 2116.0],       [2117.0, 2118.0]],      [[2119.0, 2120.0],       [2121.0, 2122.0],       [2123.0, 2124.0]]],     [[[2125.0, 2126.0],       [2127.0, 2128.0],       [2129.0, 2130.0]],      [[2131.0, 2132.0],       [2133.0, 2134.0],       [2135.0, 2136.0]],      [[2137.0, 2138.0],       [2139.0, 2140.0],       [2141.0, 2142.0]]],     [[[2143.0, 2144.0],       [2145.0, 2146.0],       [2147.0, 2148.0]],      [[2149.0, 2150.0],       [2151.0, 2152.0],       [2153.0, 2154.0]],      [[2155.0, 2156.0],       [2157.0, 2158.0],       [2159.0, 2160.0]]]]],   [[[[[2161.0, 2162.0],       [2163.0, 2164.0],       [2165.0, 2166.0]],      [[2167.0, 2168.0],       [2169.0, 2170.0],       [2171.0, 2172.0]],      [[2173.0, 2174.0],       [2175.0, 2176.0],       [2177.0, 2178.0]]],     [[[2179.0, 2180.0],       [2181.0, 2182.0],       [2183.0, 2184.0]],      [[2185.0, 2186.0],       [2187.0, 2188.0],       [2189.0, 2190.0]],      [[2191.0, 2192.0],       [2193.0, 2194.0],       [2195.0, 2196.0]]],     [[[2197.0, 2198.0],       [2199.0, 2200.0],       [2201.0, 2202.0]],      [[2203.0, 2204.0],       [2205.0, 2206.0],       [2207.0, 2208.0]],      [[2209.0, 2210.0],       [2211.0, 2212.0],       [2213.0, 2214.0]]],     [[[2215.0, 2216.0],       [2217.0, 2218.0],       [2219.0, 2220.0]],      [[2221.0, 2222.0],       [2223.0, 2224.0],       [2225.0, 2226.0]],      [[2227.0, 2228.0],       [2229.0, 2230.0],       [2231.0, 2232.0]]]],    [[[[2233.0, 2234.0],       [2235.0, 2236.0],       [2237.0, 2238.0]],      [[2239.0, 2240.0],       [2241.0, 2242.0],       [2243.0, 2244.0]],      [[2245.0, 2246.0],       [2247.0, 2248.0],       [2249.0, 2250.0]]],     [[[2251.0, 2252.0],       [2253.0, 2254.0],       [2255.0, 2256.0]],      [[2257.0, 2258.0],       [2259.0, 2260.0],       [2261.0, 2262.0]],      [[2263.0, 2264.0],       [2265.0, 2266.0],       [2267.0, 2268.0]]],     [[[2269.0, 2270.0],       [2271.0, 2272.0],       [2273.0, 2274.0]],      [[2275.0, 2276.0],       [2277.0, 2278.0],       [2279.0, 2280.0]],      [[2281.0, 2282.0],       [2283.0, 2284.0],       [2285.0, 2286.0]]],     [[[2287.0, 2288.0],       [2289.0, 2290.0],       [2291.0, 2292.0]],      [[2293.0, 2294.0],       [2295.0, 2296.0],       [2297.0, 2298.0]],      [[2299.0, 2300.0],       [2301.0, 2302.0],       [2303.0, 2304.0]]]],    [[[[2305.0, 2306.0],       [2307.0, 2308.0],       [2309.0, 2310.0]],      [[2311.0, 2312.0],       [2313.0, 2314.0],       [2315.0, 2316.0]],      [[2317.0, 2318.0],       [2319.0, 2320.0],       [2321.0, 2322.0]]],     [[[2323.0, 2324.0],       [2325.0, 2326.0],       [2327.0, 2328.0]],      [[2329.0, 2330.0],       [2331.0, 2332.0],       [2333.0, 2334.0]],      [[2335.0, 2336.0],       [2337.0, 2338.0],       [2339.0, 2340.0]]],     [[[2341.0, 2342.0],       [2343.0, 2344.0],       [2345.0, 2346.0]],      [[2347.0, 2348.0],       [2349.0, 2350.0],       [2351.0, 2352.0]],      [[2353.0, 2354.0],       [2355.0, 2356.0],       [2357.0, 2358.0]]],     [[[2359.0, 2360.0],       [2361.0, 2362.0],       [2363.0, 2364.0]],      [[2365.0, 2366.0],       [2367.0, 2368.0],       [2369.0, 2370.0]],      [[2371.0, 2372.0],       [2373.0, 2374.0],       [2375.0, 2376.0]]]],    [[[[2377.0, 2378.0],       [2379.0, 2380.0],       [2381.0, 2382.0]],      [[2383.0, 2384.0],       [2385.0, 2386.0],       [2387.0, 2388.0]],      [[2389.0, 2390.0],       [2391.0, 2392.0],       [2393.0, 2394.0]]],     [[[2395.0, 2396.0],       [2397.0, 2398.0],       [2399.0, 2400.0]],      [[2401.0, 2402.0],       [2403.0, 2404.0],       [2405.0, 2406.0]],      [[2407.0, 2408.0],       [2409.0, 2410.0],       [2411.0, 2412.0]]],     [[[2413.0, 2414.0],       [2415.0, 2416.0],       [2417.0, 2418.0]],      [[2419.0, 2420.0],       [2421.0, 2422.0],       [2423.0, 2424.0]],      [[2425.0, 2426.0],       [2427.0, 2428.0],       [2429.0, 2430.0]]],     [[[2431.0, 2432.0],       [2433.0, 2434.0],       [2435.0, 2436.0]],      [[2437.0, 2438.0],       [2439.0, 2440.0],       [2441.0, 2442.0]],      [[2443.0, 2444.0],       [2445.0, 2446.0],       [2447.0, 2448.0]]]],    [[[[2449.0, 2450.0],       [2451.0, 2452.0],       [2453.0, 2454.0]],      [[2455.0, 2456.0],       [2457.0, 2458.0],       [2459.0, 2460.0]],      [[2461.0, 2462.0],       [2463.0, 2464.0],       [2465.0, 2466.0]]],     [[[2467.0, 2468.0],       [2469.0, 2470.0],       [2471.0, 2472.0]],      [[2473.0, 2474.0],       [2475.0, 2476.0],       [2477.0, 2478.0]],      [[2479.0, 2480.0],       [2481.0, 2482.0],       [2483.0, 2484.0]]],     [[[2485.0, 2486.0],       [2487.0, 2488.0],       [2489.0, 2490.0]],      [[2491.0, 2492.0],       [2493.0, 2494.0],       [2495.0, 2496.0]],      [[2497.0, 2498.0],       [2499.0, 2500.0],       [2501.0, 2502.0]]],     [[[2503.0, 2504.0],       [2505.0, 2506.0],       [2507.0, 2508.0]],      [[2509.0, 2510.0],       [2511.0, 2512.0],       [2513.0, 2514.0]],      [[2515.0, 2516.0],       [2517.0, 2518.0],       [2519.0, 2520.0]]]],    [[[[2521.0, 2522.0],       [2523.0, 2524.0],       [2525.0, 2526.0]],      [[2527.0, 2528.0],       [2529.0, 2530.0],       [2531.0, 2532.0]],      [[2533.0, 2534.0],       [2535.0, 2536.0],       [2537.0, 2538.0]]],     [[[2539.0, 2540.0],       [2541.0, 2542.0],       [2543.0, 2544.0]],      [[2545.0, 2546.0],       [2547.0, 2548.0],       [2549.0, 2550.0]],      [[2551.0, 2552.0],       [2553.0, 2554.0],       [2555.0, 2556.0]]],     [[[2557.0, 2558.0],       [2559.0, 2560.0],       [2561.0, 2562.0]],      [[2563.0, 2564.0],       [2565.0, 2566.0],       [2567.0, 2568.0]],      [[2569.0, 2570.0],       [2571.0, 2572.0],       [2573.0, 2574.0]]],     [[[2575.0, 2576.0],       [2577.0, 2578.0],       [2579.0, 2580.0]],      [[2581.0, 2582.0],       [2583.0, 2584.0],       [2585.0, 2586.0]],      [[2587.0, 2588.0],       [2589.0, 2590.0],       [2591.0, 2592.0]]]]]]] shape=[3, 2, 6, 4, 3, 3, 2], strides=[864, 432, 72, 18, 6, 2, 1], layout=C (0x1)), I32([2, 2, 2] shape=[3], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [2, 2],  [1, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 245574246 629907491 4025518263 4192555755 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],     [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0]],    [[29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],     [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0],     [43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],     [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0]],    [[57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],     [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0],     [71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],     [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0]]],   [[[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],     [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],     [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0],     [106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0]],    [[113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],     [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0],     [127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],     [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0]],    [[141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],     [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],     [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],     [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0]]],   [[[169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0],     [176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],     [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],     [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0]],    [[197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],     [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0],     [211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],     [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0]],    [[225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],     [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],     [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0],     [246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0]]],   [[[253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],     [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],     [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],     [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0]],    [[281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],     [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0],     [295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],     [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0]],    [[309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0],     [316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],     [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],     [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0]]]],  [[[[337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],     [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0],     [351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],     [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0]],    [[365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],     [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0],     [379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0],     [386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0]],    [[393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],     [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],     [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],     [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]]],   [[[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],     [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],     [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],     [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0]],    [[449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0],     [456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0],     [463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],     [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0]],    [[477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],     [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0],     [491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],     [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0]]],   [[[505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],     [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],     [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0],     [526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0]],    [[533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],     [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0],     [547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],     [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0]],    [[561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],     [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],     [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],     [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0]]],   [[[589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0],     [596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],     [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],     [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0]],    [[617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],     [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0],     [631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],     [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0]],    [[645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],     [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0],     [659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0],     [666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0]]]]] shape=[2, 4, 3, 4, 7], strides=[336, 84, 28, 7, 1], layout=C (0x1)), I32([2, 2] shape=[2], strides=[1], layout=C | F (0x3)), I32([[2, 2],  [2, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 1783788365 3663138222 2355781355 1309060000 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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],     [16.0, 17.0, 18.0]],    [[19.0, 20.0, 21.0],     [22.0, 23.0, 24.0],     [25.0, 26.0, 27.0],     [28.0, 29.0, 30.0],     [31.0, 32.0, 33.0],     [34.0, 35.0, 36.0]],    [[37.0, 38.0, 39.0],     [40.0, 41.0, 42.0],     [43.0, 44.0, 45.0],     [46.0, 47.0, 48.0],     [49.0, 50.0, 51.0],     [52.0, 53.0, 54.0]],    [[55.0, 56.0, 57.0],     [58.0, 59.0, 60.0],     [61.0, 62.0, 63.0],     [64.0, 65.0, 66.0],     [67.0, 68.0, 69.0],     [70.0, 71.0, 72.0]],    [[73.0, 74.0, 75.0],     [76.0, 77.0, 78.0],     [79.0, 80.0, 81.0],     [82.0, 83.0, 84.0],     [85.0, 86.0, 87.0],     [88.0, 89.0, 90.0]],    [[91.0, 92.0, 93.0],     [94.0, 95.0, 96.0],     [97.0, 98.0, 99.0],     [100.0, 101.0, 102.0],     [103.0, 104.0, 105.0],     [106.0, 107.0, 108.0]],    [[109.0, 110.0, 111.0],     [112.0, 113.0, 114.0],     [115.0, 116.0, 117.0],     [118.0, 119.0, 120.0],     [121.0, 122.0, 123.0],     [124.0, 125.0, 126.0]]],   [[[127.0, 128.0, 129.0],     [130.0, 131.0, 132.0],     [133.0, 134.0, 135.0],     [136.0, 137.0, 138.0],     [139.0, 140.0, 141.0],     [142.0, 143.0, 144.0]],    [[145.0, 146.0, 147.0],     [148.0, 149.0, 150.0],     [151.0, 152.0, 153.0],     [154.0, 155.0, 156.0],     [157.0, 158.0, 159.0],     [160.0, 161.0, 162.0]],    [[163.0, 164.0, 165.0],     [166.0, 167.0, 168.0],     [169.0, 170.0, 171.0],     [172.0, 173.0, 174.0],     [175.0, 176.0, 177.0],     [178.0, 179.0, 180.0]],    [[181.0, 182.0, 183.0],     [184.0, 185.0, 186.0],     [187.0, 188.0, 189.0],     [190.0, 191.0, 192.0],     [193.0, 194.0, 195.0],     [196.0, 197.0, 198.0]],    [[199.0, 200.0, 201.0],     [202.0, 203.0, 204.0],     [205.0, 206.0, 207.0],     [208.0, 209.0, 210.0],     [211.0, 212.0, 213.0],     [214.0, 215.0, 216.0]],    [[217.0, 218.0, 219.0],     [220.0, 221.0, 222.0],     [223.0, 224.0, 225.0],     [226.0, 227.0, 228.0],     [229.0, 230.0, 231.0],     [232.0, 233.0, 234.0]],    [[235.0, 236.0, 237.0],     [238.0, 239.0, 240.0],     [241.0, 242.0, 243.0],     [244.0, 245.0, 246.0],     [247.0, 248.0, 249.0],     [250.0, 251.0, 252.0]]],   [[[253.0, 254.0, 255.0],     [256.0, 257.0, 258.0],     [259.0, 260.0, 261.0],     [262.0, 263.0, 264.0],     [265.0, 266.0, 267.0],     [268.0, 269.0, 270.0]],    [[271.0, 272.0, 273.0],     [274.0, 275.0, 276.0],     [277.0, 278.0, 279.0],     [280.0, 281.0, 282.0],     [283.0, 284.0, 285.0],     [286.0, 287.0, 288.0]],    [[289.0, 290.0, 291.0],     [292.0, 293.0, 294.0],     [295.0, 296.0, 297.0],     [298.0, 299.0, 300.0],     [301.0, 302.0, 303.0],     [304.0, 305.0, 306.0]],    [[307.0, 308.0, 309.0],     [310.0, 311.0, 312.0],     [313.0, 314.0, 315.0],     [316.0, 317.0, 318.0],     [319.0, 320.0, 321.0],     [322.0, 323.0, 324.0]],    [[325.0, 326.0, 327.0],     [328.0, 329.0, 330.0],     [331.0, 332.0, 333.0],     [334.0, 335.0, 336.0],     [337.0, 338.0, 339.0],     [340.0, 341.0, 342.0]],    [[343.0, 344.0, 345.0],     [346.0, 347.0, 348.0],     [349.0, 350.0, 351.0],     [352.0, 353.0, 354.0],     [355.0, 356.0, 357.0],     [358.0, 359.0, 360.0]],    [[361.0, 362.0, 363.0],     [364.0, 365.0, 366.0],     [367.0, 368.0, 369.0],     [370.0, 371.0, 372.0],     [373.0, 374.0, 375.0],     [376.0, 377.0, 378.0]]],   [[[379.0, 380.0, 381.0],     [382.0, 383.0, 384.0],     [385.0, 386.0, 387.0],     [388.0, 389.0, 390.0],     [391.0, 392.0, 393.0],     [394.0, 395.0, 396.0]],    [[397.0, 398.0, 399.0],     [400.0, 401.0, 402.0],     [403.0, 404.0, 405.0],     [406.0, 407.0, 408.0],     [409.0, 410.0, 411.0],     [412.0, 413.0, 414.0]],    [[415.0, 416.0, 417.0],     [418.0, 419.0, 420.0],     [421.0, 422.0, 423.0],     [424.0, 425.0, 426.0],     [427.0, 428.0, 429.0],     [430.0, 431.0, 432.0]],    [[433.0, 434.0, 435.0],     [436.0, 437.0, 438.0],     [439.0, 440.0, 441.0],     [442.0, 443.0, 444.0],     [445.0, 446.0, 447.0],     [448.0, 449.0, 450.0]],    [[451.0, 452.0, 453.0],     [454.0, 455.0, 456.0],     [457.0, 458.0, 459.0],     [460.0, 461.0, 462.0],     [463.0, 464.0, 465.0],     [466.0, 467.0, 468.0]],    [[469.0, 470.0, 471.0],     [472.0, 473.0, 474.0],     [475.0, 476.0, 477.0],     [478.0, 479.0, 480.0],     [481.0, 482.0, 483.0],     [484.0, 485.0, 486.0]],    [[487.0, 488.0, 489.0],     [490.0, 491.0, 492.0],     [493.0, 494.0, 495.0],     [496.0, 497.0, 498.0],     [499.0, 500.0, 501.0],     [502.0, 503.0, 504.0]]],   [[[505.0, 506.0, 507.0],     [508.0, 509.0, 510.0],     [511.0, 512.0, 513.0],     [514.0, 515.0, 516.0],     [517.0, 518.0, 519.0],     [520.0, 521.0, 522.0]],    [[523.0, 524.0, 525.0],     [526.0, 527.0, 528.0],     [529.0, 530.0, 531.0],     [532.0, 533.0, 534.0],     [535.0, 536.0, 537.0],     [538.0, 539.0, 540.0]],    [[541.0, 542.0, 543.0],     [544.0, 545.0, 546.0],     [547.0, 548.0, 549.0],     [550.0, 551.0, 552.0],     [553.0, 554.0, 555.0],     [556.0, 557.0, 558.0]],    [[559.0, 560.0, 561.0],     [562.0, 563.0, 564.0],     [565.0, 566.0, 567.0],     [568.0, 569.0, 570.0],     [571.0, 572.0, 573.0],     [574.0, 575.0, 576.0]],    [[577.0, 578.0, 579.0],     [580.0, 581.0, 582.0],     [583.0, 584.0, 585.0],     [586.0, 587.0, 588.0],     [589.0, 590.0, 591.0],     [592.0, 593.0, 594.0]],    [[595.0, 596.0, 597.0],     [598.0, 599.0, 600.0],     [601.0, 602.0, 603.0],     [604.0, 605.0, 606.0],     [607.0, 608.0, 609.0],     [610.0, 611.0, 612.0]],    [[613.0, 614.0, 615.0],     [616.0, 617.0, 618.0],     [619.0, 620.0, 621.0],     [622.0, 623.0, 624.0],     [625.0, 626.0, 627.0],     [628.0, 629.0, 630.0]]],   [[[631.0, 632.0, 633.0],     [634.0, 635.0, 636.0],     [637.0, 638.0, 639.0],     [640.0, 641.0, 642.0],     [643.0, 644.0, 645.0],     [646.0, 647.0, 648.0]],    [[649.0, 650.0, 651.0],     [652.0, 653.0, 654.0],     [655.0, 656.0, 657.0],     [658.0, 659.0, 660.0],     [661.0, 662.0, 663.0],     [664.0, 665.0, 666.0]],    [[667.0, 668.0, 669.0],     [670.0, 671.0, 672.0],     [673.0, 674.0, 675.0],     [676.0, 677.0, 678.0],     [679.0, 680.0, 681.0],     [682.0, 683.0, 684.0]],    [[685.0, 686.0, 687.0],     [688.0, 689.0, 690.0],     [691.0, 692.0, 693.0],     [694.0, 695.0, 696.0],     [697.0, 698.0, 699.0],     [700.0, 701.0, 702.0]],    [[703.0, 704.0, 705.0],     [706.0, 707.0, 708.0],     [709.0, 710.0, 711.0],     [712.0, 713.0, 714.0],     [715.0, 716.0, 717.0],     [718.0, 719.0, 720.0]],    [[721.0, 722.0, 723.0],     [724.0, 725.0, 726.0],     [727.0, 728.0, 729.0],     [730.0, 731.0, 732.0],     [733.0, 734.0, 735.0],     [736.0, 737.0, 738.0]],    [[739.0, 740.0, 741.0],     [742.0, 743.0, 744.0],     [745.0, 746.0, 747.0],     [748.0, 749.0, 750.0],     [751.0, 752.0, 753.0],     [754.0, 755.0, 756.0]]],   [[[757.0, 758.0, 759.0],     [760.0, 761.0, 762.0],     [763.0, 764.0, 765.0],     [766.0, 767.0, 768.0],     [769.0, 770.0, 771.0],     [772.0, 773.0, 774.0]],    [[775.0, 776.0, 777.0],     [778.0, 779.0, 780.0],     [781.0, 782.0, 783.0],     [784.0, 785.0, 786.0],     [787.0, 788.0, 789.0],     [790.0, 791.0, 792.0]],    [[793.0, 794.0, 795.0],     [796.0, 797.0, 798.0],     [799.0, 800.0, 801.0],     [802.0, 803.0, 804.0],     [805.0, 806.0, 807.0],     [808.0, 809.0, 810.0]],    [[811.0, 812.0, 813.0],     [814.0, 815.0, 816.0],     [817.0, 818.0, 819.0],     [820.0, 821.0, 822.0],     [823.0, 824.0, 825.0],     [826.0, 827.0, 828.0]],    [[829.0, 830.0, 831.0],     [832.0, 833.0, 834.0],     [835.0, 836.0, 837.0],     [838.0, 839.0, 840.0],     [841.0, 842.0, 843.0],     [844.0, 845.0, 846.0]],    [[847.0, 848.0, 849.0],     [850.0, 851.0, 852.0],     [853.0, 854.0, 855.0],     [856.0, 857.0, 858.0],     [859.0, 860.0, 861.0],     [862.0, 863.0, 864.0]],    [[865.0, 866.0, 867.0],     [868.0, 869.0, 870.0],     [871.0, 872.0, 873.0],     [874.0, 875.0, 876.0],     [877.0, 878.0, 879.0],     [880.0, 881.0, 882.0]]]]] shape=[1, 7, 7, 6, 3], strides=[882, 126, 18, 3, 1], layout=C (0x1)), I32([2] shape=[1], strides=[1], layout=C | F (0x3)), I32([[2, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 2706897729 341089469 3086911944 426974114 # shrinks to (ref i, ref bs, ref p) = (F32([[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0]],  [[22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0],   [29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],   [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0]],  [[43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],   [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0],   [57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0]]] shape=[3, 3, 7], strides=[21, 7, 1], layout=C (0x1)), I32([1] shape=[1], strides=[1], layout=C | F (0x3)), I32([[1, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 4235681308 4165367766 2032108283 156636734 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],      [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0],      [29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0]],     [[36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0],      [43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],      [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0],      [57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],      [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0]],     [[71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],      [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0],      [85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],      [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],      [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0]]],    [[[106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0],      [113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],      [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0],      [127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],      [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0]],     [[141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],      [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],      [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],      [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0],      [169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0]],     [[176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],      [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],      [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0],      [197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],      [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0]]],    [[[211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],      [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0],      [225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],      [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],      [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0]],     [[246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0],      [253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],      [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],      [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],      [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0]],     [[281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],      [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0],      [295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],      [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0],      [309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0]]],    [[[316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],      [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],      [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0],      [337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],      [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0]],     [[351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],      [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0],      [365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],      [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0],      [379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0]],     [[386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0],      [393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],      [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],      [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],      [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]]],    [[[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],      [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],      [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],      [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0],      [449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0]],     [[456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0],      [463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],      [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0],      [477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],      [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0]],     [[491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],      [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0],      [505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],      [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],      [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0]]],    [[[526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0],      [533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],      [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0],      [547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],      [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0]],     [[561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],      [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],      [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],      [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0],      [589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0]],     [[596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],      [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],      [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0],      [617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],      [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0]]],    [[[631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],      [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0],      [645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],      [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0],      [659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0]],     [[666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0],      [673.0, 674.0, 675.0, 676.0, 677.0, 678.0, 679.0],      [680.0, 681.0, 682.0, 683.0, 684.0, 685.0, 686.0],      [687.0, 688.0, 689.0, 690.0, 691.0, 692.0, 693.0],      [694.0, 695.0, 696.0, 697.0, 698.0, 699.0, 700.0]],     [[701.0, 702.0, 703.0, 704.0, 705.0, 706.0, 707.0],      [708.0, 709.0, 710.0, 711.0, 712.0, 713.0, 714.0],      [715.0, 716.0, 717.0, 718.0, 719.0, 720.0, 721.0],      [722.0, 723.0, 724.0, 725.0, 726.0, 727.0, 728.0],      [729.0, 730.0, 731.0, 732.0, 733.0, 734.0, 735.0]]]],   [[[[736.0, 737.0, 738.0, 739.0, 740.0, 741.0, 742.0],      [743.0, 744.0, 745.0, 746.0, 747.0, 748.0, 749.0],      [750.0, 751.0, 752.0, 753.0, 754.0, 755.0, 756.0],      [757.0, 758.0, 759.0, 760.0, 761.0, 762.0, 763.0],      [764.0, 765.0, 766.0, 767.0, 768.0, 769.0, 770.0]],     [[771.0, 772.0, 773.0, 774.0, 775.0, 776.0, 777.0],      [778.0, 779.0, 780.0, 781.0, 782.0, 783.0, 784.0],      [785.0, 786.0, 787.0, 788.0, 789.0, 790.0, 791.0],      [792.0, 793.0, 794.0, 795.0, 796.0, 797.0, 798.0],      [799.0, 800.0, 801.0, 802.0, 803.0, 804.0, 805.0]],     [[806.0, 807.0, 808.0, 809.0, 810.0, 811.0, 812.0],      [813.0, 814.0, 815.0, 816.0, 817.0, 818.0, 819.0],      [820.0, 821.0, 822.0, 823.0, 824.0, 825.0, 826.0],      [827.0, 828.0, 829.0, 830.0, 831.0, 832.0, 833.0],      [834.0, 835.0, 836.0, 837.0, 838.0, 839.0, 840.0]]],    [[[841.0, 842.0, 843.0, 844.0, 845.0, 846.0, 847.0],      [848.0, 849.0, 850.0, 851.0, 852.0, 853.0, 854.0],      [855.0, 856.0, 857.0, 858.0, 859.0, 860.0, 861.0],      [862.0, 863.0, 864.0, 865.0, 866.0, 867.0, 868.0],      [869.0, 870.0, 871.0, 872.0, 873.0, 874.0, 875.0]],     [[876.0, 877.0, 878.0, 879.0, 880.0, 881.0, 882.0],      [883.0, 884.0, 885.0, 886.0, 887.0, 888.0, 889.0],      [890.0, 891.0, 892.0, 893.0, 894.0, 895.0, 896.0],      [897.0, 898.0, 899.0, 900.0, 901.0, 902.0, 903.0],      [904.0, 905.0, 906.0, 907.0, 908.0, 909.0, 910.0]],     [[911.0, 912.0, 913.0, 914.0, 915.0, 916.0, 917.0],      [918.0, 919.0, 920.0, 921.0, 922.0, 923.0, 924.0],      [925.0, 926.0, 927.0, 928.0, 929.0, 930.0, 931.0],      [932.0, 933.0, 934.0, 935.0, 936.0, 937.0, 938.0],      [939.0, 940.0, 941.0, 942.0, 943.0, 944.0, 945.0]]],    [[[946.0, 947.0, 948.0, 949.0, 950.0, 951.0, 952.0],      [953.0, 954.0, 955.0, 956.0, 957.0, 958.0, 959.0],      [960.0, 961.0, 962.0, 963.0, 964.0, 965.0, 966.0],      [967.0, 968.0, 969.0, 970.0, 971.0, 972.0, 973.0],      [974.0, 975.0, 976.0, 977.0, 978.0, 979.0, 980.0]],     [[981.0, 982.0, 983.0, 984.0, 985.0, 986.0, 987.0],      [988.0, 989.0, 990.0, 991.0, 992.0, 993.0, 994.0],      [995.0, 996.0, 997.0, 998.0, 999.0, 1000.0, 1001.0],      [1002.0, 1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0],      [1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0, 1015.0]],     [[1016.0, 1017.0, 1018.0, 1019.0, 1020.0, 1021.0, 1022.0],      [1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0],      [1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0],      [1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0],      [1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]]],    [[[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0, 1057.0],      [1058.0, 1059.0, 1060.0, 1061.0, 1062.0, 1063.0, 1064.0],      [1065.0, 1066.0, 1067.0, 1068.0, 1069.0, 1070.0, 1071.0],      [1072.0, 1073.0, 1074.0, 1075.0, 1076.0, 1077.0, 1078.0],      [1079.0, 1080.0, 1081.0, 1082.0, 1083.0, 1084.0, 1085.0]],     [[1086.0, 1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0],      [1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0, 1099.0],      [1100.0, 1101.0, 1102.0, 1103.0, 1104.0, 1105.0, 1106.0],      [1107.0, 1108.0, 1109.0, 1110.0, 1111.0, 1112.0, 1113.0],      [1114.0, 1115.0, 1116.0, 1117.0, 1118.0, 1119.0, 1120.0]],     [[1121.0, 1122.0, 1123.0, 1124.0, 1125.0, 1126.0, 1127.0],      [1128.0, 1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0],      [1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0, 1141.0],      [1142.0, 1143.0, 1144.0, 1145.0, 1146.0, 1147.0, 1148.0],      [1149.0, 1150.0, 1151.0, 1152.0, 1153.0, 1154.0, 1155.0]]],    [[[1156.0, 1157.0, 1158.0, 1159.0, 1160.0, 1161.0, 1162.0],      [1163.0, 1164.0, 1165.0, 1166.0, 1167.0, 1168.0, 1169.0],      [1170.0, 1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0],      [1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0, 1183.0],      [1184.0, 1185.0, 1186.0, 1187.0, 1188.0, 1189.0, 1190.0]],     [[1191.0, 1192.0, 1193.0, 1194.0, 1195.0, 1196.0, 1197.0],      [1198.0, 1199.0, 1200.0, 1201.0, 1202.0, 1203.0, 1204.0],      [1205.0, 1206.0, 1207.0, 1208.0, 1209.0, 1210.0, 1211.0],      [1212.0, 1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0],      [1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0, 1225.0]],     [[1226.0, 1227.0, 1228.0, 1229.0, 1230.0, 1231.0, 1232.0],      [1233.0, 1234.0, 1235.0, 1236.0, 1237.0, 1238.0, 1239.0],      [1240.0, 1241.0, 1242.0, 1243.0, 1244.0, 1245.0, 1246.0],      [1247.0, 1248.0, 1249.0, 1250.0, 1251.0, 1252.0, 1253.0],      [1254.0, 1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]]],    [[[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0, 1267.0],      [1268.0, 1269.0, 1270.0, 1271.0, 1272.0, 1273.0, 1274.0],      [1275.0, 1276.0, 1277.0, 1278.0, 1279.0, 1280.0, 1281.0],      [1282.0, 1283.0, 1284.0, 1285.0, 1286.0, 1287.0, 1288.0],      [1289.0, 1290.0, 1291.0, 1292.0, 1293.0, 1294.0, 1295.0]],     [[1296.0, 1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0],      [1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0, 1309.0],      [1310.0, 1311.0, 1312.0, 1313.0, 1314.0, 1315.0, 1316.0],      [1317.0, 1318.0, 1319.0, 1320.0, 1321.0, 1322.0, 1323.0],      [1324.0, 1325.0, 1326.0, 1327.0, 1328.0, 1329.0, 1330.0]],     [[1331.0, 1332.0, 1333.0, 1334.0, 1335.0, 1336.0, 1337.0],      [1338.0, 1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0],      [1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0, 1351.0],      [1352.0, 1353.0, 1354.0, 1355.0, 1356.0, 1357.0, 1358.0],      [1359.0, 1360.0, 1361.0, 1362.0, 1363.0, 1364.0, 1365.0]]],    [[[1366.0, 1367.0, 1368.0, 1369.0, 1370.0, 1371.0, 1372.0],      [1373.0, 1374.0, 1375.0, 1376.0, 1377.0, 1378.0, 1379.0],      [1380.0, 1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0],      [1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0, 1393.0],      [1394.0, 1395.0, 1396.0, 1397.0, 1398.0, 1399.0, 1400.0]],     [[1401.0, 1402.0, 1403.0, 1404.0, 1405.0, 1406.0, 1407.0],      [1408.0, 1409.0, 1410.0, 1411.0, 1412.0, 1413.0, 1414.0],      [1415.0, 1416.0, 1417.0, 1418.0, 1419.0, 1420.0, 1421.0],      [1422.0, 1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0],      [1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0, 1435.0]],     [[1436.0, 1437.0, 1438.0, 1439.0, 1440.0, 1441.0, 1442.0],      [1443.0, 1444.0, 1445.0, 1446.0, 1447.0, 1448.0, 1449.0],      [1450.0, 1451.0, 1452.0, 1453.0, 1454.0, 1455.0, 1456.0],      [1457.0, 1458.0, 1459.0, 1460.0, 1461.0, 1462.0, 1463.0],      [1464.0, 1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0]]]]],  [[[[[1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0, 1477.0],      [1478.0, 1479.0, 1480.0, 1481.0, 1482.0, 1483.0, 1484.0],      [1485.0, 1486.0, 1487.0, 1488.0, 1489.0, 1490.0, 1491.0],      [1492.0, 1493.0, 1494.0, 1495.0, 1496.0, 1497.0, 1498.0],      [1499.0, 1500.0, 1501.0, 1502.0, 1503.0, 1504.0, 1505.0]],     [[1506.0, 1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0],      [1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0, 1519.0],      [1520.0, 1521.0, 1522.0, 1523.0, 1524.0, 1525.0, 1526.0],      [1527.0, 1528.0, 1529.0, 1530.0, 1531.0, 1532.0, 1533.0],      [1534.0, 1535.0, 1536.0, 1537.0, 1538.0, 1539.0, 1540.0]],     [[1541.0, 1542.0, 1543.0, 1544.0, 1545.0, 1546.0, 1547.0],      [1548.0, 1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0],      [1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0, 1561.0],      [1562.0, 1563.0, 1564.0, 1565.0, 1566.0, 1567.0, 1568.0],      [1569.0, 1570.0, 1571.0, 1572.0, 1573.0, 1574.0, 1575.0]]],    [[[1576.0, 1577.0, 1578.0, 1579.0, 1580.0, 1581.0, 1582.0],      [1583.0, 1584.0, 1585.0, 1586.0, 1587.0, 1588.0, 1589.0],      [1590.0, 1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0],      [1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0, 1603.0],      [1604.0, 1605.0, 1606.0, 1607.0, 1608.0, 1609.0, 1610.0]],     [[1611.0, 1612.0, 1613.0, 1614.0, 1615.0, 1616.0, 1617.0],      [1618.0, 1619.0, 1620.0, 1621.0, 1622.0, 1623.0, 1624.0],      [1625.0, 1626.0, 1627.0, 1628.0, 1629.0, 1630.0, 1631.0],      [1632.0, 1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0],      [1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0, 1645.0]],     [[1646.0, 1647.0, 1648.0, 1649.0, 1650.0, 1651.0, 1652.0],      [1653.0, 1654.0, 1655.0, 1656.0, 1657.0, 1658.0, 1659.0],      [1660.0, 1661.0, 1662.0, 1663.0, 1664.0, 1665.0, 1666.0],      [1667.0, 1668.0, 1669.0, 1670.0, 1671.0, 1672.0, 1673.0],      [1674.0, 1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]]],    [[[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0, 1687.0],      [1688.0, 1689.0, 1690.0, 1691.0, 1692.0, 1693.0, 1694.0],      [1695.0, 1696.0, 1697.0, 1698.0, 1699.0, 1700.0, 1701.0],      [1702.0, 1703.0, 1704.0, 1705.0, 1706.0, 1707.0, 1708.0],      [1709.0, 1710.0, 1711.0, 1712.0, 1713.0, 1714.0, 1715.0]],     [[1716.0, 1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0],      [1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0, 1729.0],      [1730.0, 1731.0, 1732.0, 1733.0, 1734.0, 1735.0, 1736.0],      [1737.0, 1738.0, 1739.0, 1740.0, 1741.0, 1742.0, 1743.0],      [1744.0, 1745.0, 1746.0, 1747.0, 1748.0, 1749.0, 1750.0]],     [[1751.0, 1752.0, 1753.0, 1754.0, 1755.0, 1756.0, 1757.0],      [1758.0, 1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0],      [1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0, 1771.0],      [1772.0, 1773.0, 1774.0, 1775.0, 1776.0, 1777.0, 1778.0],      [1779.0, 1780.0, 1781.0, 1782.0, 1783.0, 1784.0, 1785.0]]],    [[[1786.0, 1787.0, 1788.0, 1789.0, 1790.0, 1791.0, 1792.0],      [1793.0, 1794.0, 1795.0, 1796.0, 1797.0, 1798.0, 1799.0],      [1800.0, 1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0],      [1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0, 1813.0],      [1814.0, 1815.0, 1816.0, 1817.0, 1818.0, 1819.0, 1820.0]],     [[1821.0, 1822.0, 1823.0, 1824.0, 1825.0, 1826.0, 1827.0],      [1828.0, 1829.0, 1830.0, 1831.0, 1832.0, 1833.0, 1834.0],      [1835.0, 1836.0, 1837.0, 1838.0, 1839.0, 1840.0, 1841.0],      [1842.0, 1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0],      [1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0, 1855.0]],     [[1856.0, 1857.0, 1858.0, 1859.0, 1860.0, 1861.0, 1862.0],      [1863.0, 1864.0, 1865.0, 1866.0, 1867.0, 1868.0, 1869.0],      [1870.0, 1871.0, 1872.0, 1873.0, 1874.0, 1875.0, 1876.0],      [1877.0, 1878.0, 1879.0, 1880.0, 1881.0, 1882.0, 1883.0],      [1884.0, 1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0]]],    [[[1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0, 1897.0],      [1898.0, 1899.0, 1900.0, 1901.0, 1902.0, 1903.0, 1904.0],      [1905.0, 1906.0, 1907.0, 1908.0, 1909.0, 1910.0, 1911.0],      [1912.0, 1913.0, 1914.0, 1915.0, 1916.0, 1917.0, 1918.0],      [1919.0, 1920.0, 1921.0, 1922.0, 1923.0, 1924.0, 1925.0]],     [[1926.0, 1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0],      [1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0, 1939.0],      [1940.0, 1941.0, 1942.0, 1943.0, 1944.0, 1945.0, 1946.0],      [1947.0, 1948.0, 1949.0, 1950.0, 1951.0, 1952.0, 1953.0],      [1954.0, 1955.0, 1956.0, 1957.0, 1958.0, 1959.0, 1960.0]],     [[1961.0, 1962.0, 1963.0, 1964.0, 1965.0, 1966.0, 1967.0],      [1968.0, 1969.0, 1970.0, 1971.0, 1972.0, 1973.0, 1974.0],      [1975.0, 1976.0, 1977.0, 1978.0, 1979.0, 1980.0, 1981.0],      [1982.0, 1983.0, 1984.0, 1985.0, 1986.0, 1987.0, 1988.0],      [1989.0, 1990.0, 1991.0, 1992.0, 1993.0, 1994.0, 1995.0]]],    [[[1996.0, 1997.0, 1998.0, 1999.0, 2000.0, 2001.0, 2002.0],      [2003.0, 2004.0, 2005.0, 2006.0, 2007.0, 2008.0, 2009.0],      [2010.0, 2011.0, 2012.0, 2013.0, 2014.0, 2015.0, 2016.0],      [2017.0, 2018.0, 2019.0, 2020.0, 2021.0, 2022.0, 2023.0],      [2024.0, 2025.0, 2026.0, 2027.0, 2028.0, 2029.0, 2030.0]],     [[2031.0, 2032.0, 2033.0, 2034.0, 2035.0, 2036.0, 2037.0],      [2038.0, 2039.0, 2040.0, 2041.0, 2042.0, 2043.0, 2044.0],      [2045.0, 2046.0, 2047.0, 2048.0, 2049.0, 2050.0, 2051.0],      [2052.0, 2053.0, 2054.0, 2055.0, 2056.0, 2057.0, 2058.0],      [2059.0, 2060.0, 2061.0, 2062.0, 2063.0, 2064.0, 2065.0]],     [[2066.0, 2067.0, 2068.0, 2069.0, 2070.0, 2071.0, 2072.0],      [2073.0, 2074.0, 2075.0, 2076.0, 2077.0, 2078.0, 2079.0],      [2080.0, 2081.0, 2082.0, 2083.0, 2084.0, 2085.0, 2086.0],      [2087.0, 2088.0, 2089.0, 2090.0, 2091.0, 2092.0, 2093.0],      [2094.0, 2095.0, 2096.0, 2097.0, 2098.0, 2099.0, 2100.0]]],    [[[2101.0, 2102.0, 2103.0, 2104.0, 2105.0, 2106.0, 2107.0],      [2108.0, 2109.0, 2110.0, 2111.0, 2112.0, 2113.0, 2114.0],      [2115.0, 2116.0, 2117.0, 2118.0, 2119.0, 2120.0, 2121.0],      [2122.0, 2123.0, 2124.0, 2125.0, 2126.0, 2127.0, 2128.0],      [2129.0, 2130.0, 2131.0, 2132.0, 2133.0, 2134.0, 2135.0]],     [[2136.0, 2137.0, 2138.0, 2139.0, 2140.0, 2141.0, 2142.0],      [2143.0, 2144.0, 2145.0, 2146.0, 2147.0, 2148.0, 2149.0],      [2150.0, 2151.0, 2152.0, 2153.0, 2154.0, 2155.0, 2156.0],      [2157.0, 2158.0, 2159.0, 2160.0, 2161.0, 2162.0, 2163.0],      [2164.0, 2165.0, 2166.0, 2167.0, 2168.0, 2169.0, 2170.0]],     [[2171.0, 2172.0, 2173.0, 2174.0, 2175.0, 2176.0, 2177.0],      [2178.0, 2179.0, 2180.0, 2181.0, 2182.0, 2183.0, 2184.0],      [2185.0, 2186.0, 2187.0, 2188.0, 2189.0, 2190.0, 2191.0],      [2192.0, 2193.0, 2194.0, 2195.0, 2196.0, 2197.0, 2198.0],      [2199.0, 2200.0, 2201.0, 2202.0, 2203.0, 2204.0, 2205.0]]]],   [[[[2206.0, 2207.0, 2208.0, 2209.0, 2210.0, 2211.0, 2212.0],      [2213.0, 2214.0, 2215.0, 2216.0, 2217.0, 2218.0, 2219.0],      [2220.0, 2221.0, 2222.0, 2223.0, 2224.0, 2225.0, 2226.0],      [2227.0, 2228.0, 2229.0, 2230.0, 2231.0, 2232.0, 2233.0],      [2234.0, 2235.0, 2236.0, 2237.0, 2238.0, 2239.0, 2240.0]],     [[2241.0, 2242.0, 2243.0, 2244.0, 2245.0, 2246.0, 2247.0],      [2248.0, 2249.0, 2250.0, 2251.0, 2252.0, 2253.0, 2254.0],      [2255.0, 2256.0, 2257.0, 2258.0, 2259.0, 2260.0, 2261.0],      [2262.0, 2263.0, 2264.0, 2265.0, 2266.0, 2267.0, 2268.0],      [2269.0, 2270.0, 2271.0, 2272.0, 2273.0, 2274.0, 2275.0]],     [[2276.0, 2277.0, 2278.0, 2279.0, 2280.0, 2281.0, 2282.0],      [2283.0, 2284.0, 2285.0, 2286.0, 2287.0, 2288.0, 2289.0],      [2290.0, 2291.0, 2292.0, 2293.0, 2294.0, 2295.0, 2296.0],      [2297.0, 2298.0, 2299.0, 2300.0, 2301.0, 2302.0, 2303.0],      [2304.0, 2305.0, 2306.0, 2307.0, 2308.0, 2309.0, 2310.0]]],    [[[2311.0, 2312.0, 2313.0, 2314.0, 2315.0, 2316.0, 2317.0],      [2318.0, 2319.0, 2320.0, 2321.0, 2322.0, 2323.0, 2324.0],      [2325.0, 2326.0, 2327.0, 2328.0, 2329.0, 2330.0, 2331.0],      [2332.0, 2333.0, 2334.0, 2335.0, 2336.0, 2337.0, 2338.0],      [2339.0, 2340.0, 2341.0, 2342.0, 2343.0, 2344.0, 2345.0]],     [[2346.0, 2347.0, 2348.0, 2349.0, 2350.0, 2351.0, 2352.0],      [2353.0, 2354.0, 2355.0, 2356.0, 2357.0, 2358.0, 2359.0],      [2360.0, 2361.0, 2362.0, 2363.0, 2364.0, 2365.0, 2366.0],      [2367.0, 2368.0, 2369.0, 2370.0, 2371.0, 2372.0, 2373.0],      [2374.0, 2375.0, 2376.0, 2377.0, 2378.0, 2379.0, 2380.0]],     [[2381.0, 2382.0, 2383.0, 2384.0, 2385.0, 2386.0, 2387.0],      [2388.0, 2389.0, 2390.0, 2391.0, 2392.0, 2393.0, 2394.0],      [2395.0, 2396.0, 2397.0, 2398.0, 2399.0, 2400.0, 2401.0],      [2402.0, 2403.0, 2404.0, 2405.0, 2406.0, 2407.0, 2408.0],      [2409.0, 2410.0, 2411.0, 2412.0, 2413.0, 2414.0, 2415.0]]],    [[[2416.0, 2417.0, 2418.0, 2419.0, 2420.0, 2421.0, 2422.0],      [2423.0, 2424.0, 2425.0, 2426.0, 2427.0, 2428.0, 2429.0],      [2430.0, 2431.0, 2432.0, 2433.0, 2434.0, 2435.0, 2436.0],      [2437.0, 2438.0, 2439.0, 2440.0, 2441.0, 2442.0, 2443.0],      [2444.0, 2445.0, 2446.0, 2447.0, 2448.0, 2449.0, 2450.0]],     [[2451.0, 2452.0, 2453.0, 2454.0, 2455.0, 2456.0, 2457.0],      [2458.0, 2459.0, 2460.0, 2461.0, 2462.0, 2463.0, 2464.0],      [2465.0, 2466.0, 2467.0, 2468.0, 2469.0, 2470.0, 2471.0],      [2472.0, 2473.0, 2474.0, 2475.0, 2476.0, 2477.0, 2478.0],      [2479.0, 2480.0, 2481.0, 2482.0, 2483.0, 2484.0, 2485.0]],     [[2486.0, 2487.0, 2488.0, 2489.0, 2490.0, 2491.0, 2492.0],      [2493.0, 2494.0, 2495.0, 2496.0, 2497.0, 2498.0, 2499.0],      [2500.0, 2501.0, 2502.0, 2503.0, 2504.0, 2505.0, 2506.0],      [2507.0, 2508.0, 2509.0, 2510.0, 2511.0, 2512.0, 2513.0],      [2514.0, 2515.0, 2516.0, 2517.0, 2518.0, 2519.0, 2520.0]]],    [[[2521.0, 2522.0, 2523.0, 2524.0, 2525.0, 2526.0, 2527.0],      [2528.0, 2529.0, 2530.0, 2531.0, 2532.0, 2533.0, 2534.0],      [2535.0, 2536.0, 2537.0, 2538.0, 2539.0, 2540.0, 2541.0],      [2542.0, 2543.0, 2544.0, 2545.0, 2546.0, 2547.0, 2548.0],      [2549.0, 2550.0, 2551.0, 2552.0, 2553.0, 2554.0, 2555.0]],     [[2556.0, 2557.0, 2558.0, 2559.0, 2560.0, 2561.0, 2562.0],      [2563.0, 2564.0, 2565.0, 2566.0, 2567.0, 2568.0, 2569.0],      [2570.0, 2571.0, 2572.0, 2573.0, 2574.0, 2575.0, 2576.0],      [2577.0, 2578.0, 2579.0, 2580.0, 2581.0, 2582.0, 2583.0],      [2584.0, 2585.0, 2586.0, 2587.0, 2588.0, 2589.0, 2590.0]],     [[2591.0, 2592.0, 2593.0, 2594.0, 2595.0, 2596.0, 2597.0],      [2598.0, 2599.0, 2600.0, 2601.0, 2602.0, 2603.0, 2604.0],      [2605.0, 2606.0, 2607.0, 2608.0, 2609.0, 2610.0, 2611.0],      [2612.0, 2613.0, 2614.0, 2615.0, 2616.0, 2617.0, 2618.0],      [2619.0, 2620.0, 2621.0, 2622.0, 2623.0, 2624.0, 2625.0]]],    [[[2626.0, 2627.0, 2628.0, 2629.0, 2630.0, 2631.0, 2632.0],      [2633.0, 2634.0, 2635.0, 2636.0, 2637.0, 2638.0, 2639.0],      [2640.0, 2641.0, 2642.0, 2643.0, 2644.0, 2645.0, 2646.0],      [2647.0, 2648.0, 2649.0, 2650.0, 2651.0, 2652.0, 2653.0],      [2654.0, 2655.0, 2656.0, 2657.0, 2658.0, 2659.0, 2660.0]],     [[2661.0, 2662.0, 2663.0, 2664.0, 2665.0, 2666.0, 2667.0],      [2668.0, 2669.0, 2670.0, 2671.0, 2672.0, 2673.0, 2674.0],      [2675.0, 2676.0, 2677.0, 2678.0, 2679.0, 2680.0, 2681.0],      [2682.0, 2683.0, 2684.0, 2685.0, 2686.0, 2687.0, 2688.0],      [2689.0, 2690.0, 2691.0, 2692.0, 2693.0, 2694.0, 2695.0]],     [[2696.0, 2697.0, 2698.0, 2699.0, 2700.0, 2701.0, 2702.0],      [2703.0, 2704.0, 2705.0, 2706.0, 2707.0, 2708.0, 2709.0],      [2710.0, 2711.0, 2712.0, 2713.0, 2714.0, 2715.0, 2716.0],      [2717.0, 2718.0, 2719.0, 2720.0, 2721.0, 2722.0, 2723.0],      [2724.0, 2725.0, 2726.0, 2727.0, 2728.0, 2729.0, 2730.0]]],    [[[2731.0, 2732.0, 2733.0, 2734.0, 2735.0, 2736.0, 2737.0],      [2738.0, 2739.0, 2740.0, 2741.0, 2742.0, 2743.0, 2744.0],      [2745.0, 2746.0, 2747.0, 2748.0, 2749.0, 2750.0, 2751.0],      [2752.0, 2753.0, 2754.0, 2755.0, 2756.0, 2757.0, 2758.0],      [2759.0, 2760.0, 2761.0, 2762.0, 2763.0, 2764.0, 2765.0]],     [[2766.0, 2767.0, 2768.0, 2769.0, 2770.0, 2771.0, 2772.0],      [2773.0, 2774.0, 2775.0, 2776.0, 2777.0, 2778.0, 2779.0],      [2780.0, 2781.0, 2782.0, 2783.0, 2784.0, 2785.0, 2786.0],      [2787.0, 2788.0, 2789.0, 2790.0, 2791.0, 2792.0, 2793.0],      [2794.0, 2795.0, 2796.0, 2797.0, 2798.0, 2799.0, 2800.0]],     [[2801.0, 2802.0, 2803.0, 2804.0, 2805.0, 2806.0, 2807.0],      [2808.0, 2809.0, 2810.0, 2811.0, 2812.0, 2813.0, 2814.0],      [2815.0, 2816.0, 2817.0, 2818.0, 2819.0, 2820.0, 2821.0],      [2822.0, 2823.0, 2824.0, 2825.0, 2826.0, 2827.0, 2828.0],      [2829.0, 2830.0, 2831.0, 2832.0, 2833.0, 2834.0, 2835.0]]],    [[[2836.0, 2837.0, 2838.0, 2839.0, 2840.0, 2841.0, 2842.0],      [2843.0, 2844.0, 2845.0, 2846.0, 2847.0, 2848.0, 2849.0],      [2850.0, 2851.0, 2852.0, 2853.0, 2854.0, 2855.0, 2856.0],      [2857.0, 2858.0, 2859.0, 2860.0, 2861.0, 2862.0, 2863.0],      [2864.0, 2865.0, 2866.0, 2867.0, 2868.0, 2869.0, 2870.0]],     [[2871.0, 2872.0, 2873.0, 2874.0, 2875.0, 2876.0, 2877.0],      [2878.0, 2879.0, 2880.0, 2881.0, 2882.0, 2883.0, 2884.0],      [2885.0, 2886.0, 2887.0, 2888.0, 2889.0, 2890.0, 2891.0],      [2892.0, 2893.0, 2894.0, 2895.0, 2896.0, 2897.0, 2898.0],      [2899.0, 2900.0, 2901.0, 2902.0, 2903.0, 2904.0, 2905.0]],     [[2906.0, 2907.0, 2908.0, 2909.0, 2910.0, 2911.0, 2912.0],      [2913.0, 2914.0, 2915.0, 2916.0, 2917.0, 2918.0, 2919.0],      [2920.0, 2921.0, 2922.0, 2923.0, 2924.0, 2925.0, 2926.0],      [2927.0, 2928.0, 2929.0, 2930.0, 2931.0, 2932.0, 2933.0],      [2934.0, 2935.0, 2936.0, 2937.0, 2938.0, 2939.0, 2940.0]]]]],  [[[[[2941.0, 2942.0, 2943.0, 2944.0, 2945.0, 2946.0, 2947.0],      [2948.0, 2949.0, 2950.0, 2951.0, 2952.0, 2953.0, 2954.0],      [2955.0, 2956.0, 2957.0, 2958.0, 2959.0, 2960.0, 2961.0],      [2962.0, 2963.0, 2964.0, 2965.0, 2966.0, 2967.0, 2968.0],      [2969.0, 2970.0, 2971.0, 2972.0, 2973.0, 2974.0, 2975.0]],     [[2976.0, 2977.0, 2978.0, 2979.0, 2980.0, 2981.0, 2982.0],      [2983.0, 2984.0, 2985.0, 2986.0, 2987.0, 2988.0, 2989.0],      [2990.0, 2991.0, 2992.0, 2993.0, 2994.0, 2995.0, 2996.0],      [2997.0, 2998.0, 2999.0, 3000.0, 3001.0, 3002.0, 3003.0],      [3004.0, 3005.0, 3006.0, 3007.0, 3008.0, 3009.0, 3010.0]],     [[3011.0, 3012.0, 3013.0, 3014.0, 3015.0, 3016.0, 3017.0],      [3018.0, 3019.0, 3020.0, 3021.0, 3022.0, 3023.0, 3024.0],      [3025.0, 3026.0, 3027.0, 3028.0, 3029.0, 3030.0, 3031.0],      [3032.0, 3033.0, 3034.0, 3035.0, 3036.0, 3037.0, 3038.0],      [3039.0, 3040.0, 3041.0, 3042.0, 3043.0, 3044.0, 3045.0]]],    [[[3046.0, 3047.0, 3048.0, 3049.0, 3050.0, 3051.0, 3052.0],      [3053.0, 3054.0, 3055.0, 3056.0, 3057.0, 3058.0, 3059.0],      [3060.0, 3061.0, 3062.0, 3063.0, 3064.0, 3065.0, 3066.0],      [3067.0, 3068.0, 3069.0, 3070.0, 3071.0, 3072.0, 3073.0],      [3074.0, 3075.0, 3076.0, 3077.0, 3078.0, 3079.0, 3080.0]],     [[3081.0, 3082.0, 3083.0, 3084.0, 3085.0, 3086.0, 3087.0],      [3088.0, 3089.0, 3090.0, 3091.0, 3092.0, 3093.0, 3094.0],      [3095.0, 3096.0, 3097.0, 3098.0, 3099.0, 3100.0, 3101.0],      [3102.0, 3103.0, 3104.0, 3105.0, 3106.0, 3107.0, 3108.0],      [3109.0, 3110.0, 3111.0, 3112.0, 3113.0, 3114.0, 3115.0]],     [[3116.0, 3117.0, 3118.0, 3119.0, 3120.0, 3121.0, 3122.0],      [3123.0, 3124.0, 3125.0, 3126.0, 3127.0, 3128.0, 3129.0],      [3130.0, 3131.0, 3132.0, 3133.0, 3134.0, 3135.0, 3136.0],      [3137.0, 3138.0, 3139.0, 3140.0, 3141.0, 3142.0, 3143.0],      [3144.0, 3145.0, 3146.0, 3147.0, 3148.0, 3149.0, 3150.0]]],    [[[3151.0, 3152.0, 3153.0, 3154.0, 3155.0, 3156.0, 3157.0],      [3158.0, 3159.0, 3160.0, 3161.0, 3162.0, 3163.0, 3164.0],      [3165.0, 3166.0, 3167.0, 3168.0, 3169.0, 3170.0, 3171.0],      [3172.0, 3173.0, 3174.0, 3175.0, 3176.0, 3177.0, 3178.0],      [3179.0, 3180.0, 3181.0, 3182.0, 3183.0, 3184.0, 3185.0]],     [[3186.0, 3187.0, 3188.0, 3189.0, 3190.0, 3191.0, 3192.0],      [3193.0, 3194.0, 3195.0, 3196.0, 3197.0, 3198.0, 3199.0],      [3200.0, 3201.0, 3202.0, 3203.0, 3204.0, 3205.0, 3206.0],      [3207.0, 3208.0, 3209.0, 3210.0, 3211.0, 3212.0, 3213.0],      [3214.0, 3215.0, 3216.0, 3217.0, 3218.0, 3219.0, 3220.0]],     [[3221.0, 3222.0, 3223.0, 3224.0, 3225.0, 3226.0, 3227.0],      [3228.0, 3229.0, 3230.0, 3231.0, 3232.0, 3233.0, 3234.0],      [3235.0, 3236.0, 3237.0, 3238.0, 3239.0, 3240.0, 3241.0],      [3242.0, 3243.0, 3244.0, 3245.0, 3246.0, 3247.0, 3248.0],      [3249.0, 3250.0, 3251.0, 3252.0, 3253.0, 3254.0, 3255.0]]],    [[[3256.0, 3257.0, 3258.0, 3259.0, 3260.0, 3261.0, 3262.0],      [3263.0, 3264.0, 3265.0, 3266.0, 3267.0, 3268.0, 3269.0],      [3270.0, 3271.0, 3272.0, 3273.0, 3274.0, 3275.0, 3276.0],      [3277.0, 3278.0, 3279.0, 3280.0, 3281.0, 3282.0, 3283.0],      [3284.0, 3285.0, 3286.0, 3287.0, 3288.0, 3289.0, 3290.0]],     [[3291.0, 3292.0, 3293.0, 3294.0, 3295.0, 3296.0, 3297.0],      [3298.0, 3299.0, 3300.0, 3301.0, 3302.0, 3303.0, 3304.0],      [3305.0, 3306.0, 3307.0, 3308.0, 3309.0, 3310.0, 3311.0],      [3312.0, 3313.0, 3314.0, 3315.0, 3316.0, 3317.0, 3318.0],      [3319.0, 3320.0, 3321.0, 3322.0, 3323.0, 3324.0, 3325.0]],     [[3326.0, 3327.0, 3328.0, 3329.0, 3330.0, 3331.0, 3332.0],      [3333.0, 3334.0, 3335.0, 3336.0, 3337.0, 3338.0, 3339.0],      [3340.0, 3341.0, 3342.0, 3343.0, 3344.0, 3345.0, 3346.0],      [3347.0, 3348.0, 3349.0, 3350.0, 3351.0, 3352.0, 3353.0],      [3354.0, 3355.0, 3356.0, 3357.0, 3358.0, 3359.0, 3360.0]]],    [[[3361.0, 3362.0, 3363.0, 3364.0, 3365.0, 3366.0, 3367.0],      [3368.0, 3369.0, 3370.0, 3371.0, 3372.0, 3373.0, 3374.0],      [3375.0, 3376.0, 3377.0, 3378.0, 3379.0, 3380.0, 3381.0],      [3382.0, 3383.0, 3384.0, 3385.0, 3386.0, 3387.0, 3388.0],      [3389.0, 3390.0, 3391.0, 3392.0, 3393.0, 3394.0, 3395.0]],     [[3396.0, 3397.0, 3398.0, 3399.0, 3400.0, 3401.0, 3402.0],      [3403.0, 3404.0, 3405.0, 3406.0, 3407.0, 3408.0, 3409.0],      [3410.0, 3411.0, 3412.0, 3413.0, 3414.0, 3415.0, 3416.0],      [3417.0, 3418.0, 3419.0, 3420.0, 3421.0, 3422.0, 3423.0],      [3424.0, 3425.0, 3426.0, 3427.0, 3428.0, 3429.0, 3430.0]],     [[3431.0, 3432.0, 3433.0, 3434.0, 3435.0, 3436.0, 3437.0],      [3438.0, 3439.0, 3440.0, 3441.0, 3442.0, 3443.0, 3444.0],      [3445.0, 3446.0, 3447.0, 3448.0, 3449.0, 3450.0, 3451.0],      [3452.0, 3453.0, 3454.0, 3455.0, 3456.0, 3457.0, 3458.0],      [3459.0, 3460.0, 3461.0, 3462.0, 3463.0, 3464.0, 3465.0]]],    [[[3466.0, 3467.0, 3468.0, 3469.0, 3470.0, 3471.0, 3472.0],      [3473.0, 3474.0, 3475.0, 3476.0, 3477.0, 3478.0, 3479.0],      [3480.0, 3481.0, 3482.0, 3483.0, 3484.0, 3485.0, 3486.0],      [3487.0, 3488.0, 3489.0, 3490.0, 3491.0, 3492.0, 3493.0],      [3494.0, 3495.0, 3496.0, 3497.0, 3498.0, 3499.0, 3500.0]],     [[3501.0, 3502.0, 3503.0, 3504.0, 3505.0, 3506.0, 3507.0],      [3508.0, 3509.0, 3510.0, 3511.0, 3512.0, 3513.0, 3514.0],      [3515.0, 3516.0, 3517.0, 3518.0, 3519.0, 3520.0, 3521.0],      [3522.0, 3523.0, 3524.0, 3525.0, 3526.0, 3527.0, 3528.0],      [3529.0, 3530.0, 3531.0, 3532.0, 3533.0, 3534.0, 3535.0]],     [[3536.0, 3537.0, 3538.0, 3539.0, 3540.0, 3541.0, 3542.0],      [3543.0, 3544.0, 3545.0, 3546.0, 3547.0, 3548.0, 3549.0],      [3550.0, 3551.0, 3552.0, 3553.0, 3554.0, 3555.0, 3556.0],      [3557.0, 3558.0, 3559.0, 3560.0, 3561.0, 3562.0, 3563.0],      [3564.0, 3565.0, 3566.0, 3567.0, 3568.0, 3569.0, 3570.0]]],    [[[3571.0, 3572.0, 3573.0, 3574.0, 3575.0, 3576.0, 3577.0],      [3578.0, 3579.0, 3580.0, 3581.0, 3582.0, 3583.0, 3584.0],      [3585.0, 3586.0, 3587.0, 3588.0, 3589.0, 3590.0, 3591.0],      [3592.0, 3593.0, 3594.0, 3595.0, 3596.0, 3597.0, 3598.0],      [3599.0, 3600.0, 3601.0, 3602.0, 3603.0, 3604.0, 3605.0]],     [[3606.0, 3607.0, 3608.0, 3609.0, 3610.0, 3611.0, 3612.0],      [3613.0, 3614.0, 3615.0, 3616.0, 3617.0, 3618.0, 3619.0],      [3620.0, 3621.0, 3622.0, 3623.0, 3624.0, 3625.0, 3626.0],      [3627.0, 3628.0, 3629.0, 3630.0, 3631.0, 3632.0, 3633.0],      [3634.0, 3635.0, 3636.0, 3637.0, 3638.0, 3639.0, 3640.0]],     [[3641.0, 3642.0, 3643.0, 3644.0, 3645.0, 3646.0, 3647.0],      [3648.0, 3649.0, 3650.0, 3651.0, 3652.0, 3653.0, 3654.0],      [3655.0, 3656.0, 3657.0, 3658.0, 3659.0, 3660.0, 3661.0],      [3662.0, 3663.0, 3664.0, 3665.0, 3666.0, 3667.0, 3668.0],      [3669.0, 3670.0, 3671.0, 3672.0, 3673.0, 3674.0, 3675.0]]]],   [[[[3676.0, 3677.0, 3678.0, 3679.0, 3680.0, 3681.0, 3682.0],      [3683.0, 3684.0, 3685.0, 3686.0, 3687.0, 3688.0, 3689.0],      [3690.0, 3691.0, 3692.0, 3693.0, 3694.0, 3695.0, 3696.0],      [3697.0, 3698.0, 3699.0, 3700.0, 3701.0, 3702.0, 3703.0],      [3704.0, 3705.0, 3706.0, 3707.0, 3708.0, 3709.0, 3710.0]],     [[3711.0, 3712.0, 3713.0, 3714.0, 3715.0, 3716.0, 3717.0],      [3718.0, 3719.0, 3720.0, 3721.0, 3722.0, 3723.0, 3724.0],      [3725.0, 3726.0, 3727.0, 3728.0, 3729.0, 3730.0, 3731.0],      [3732.0, 3733.0, 3734.0, 3735.0, 3736.0, 3737.0, 3738.0],      [3739.0, 3740.0, 3741.0, 3742.0, 3743.0, 3744.0, 3745.0]],     [[3746.0, 3747.0, 3748.0, 3749.0, 3750.0, 3751.0, 3752.0],      [3753.0, 3754.0, 3755.0, 3756.0, 3757.0, 3758.0, 3759.0],      [3760.0, 3761.0, 3762.0, 3763.0, 3764.0, 3765.0, 3766.0],      [3767.0, 3768.0, 3769.0, 3770.0, 3771.0, 3772.0, 3773.0],      [3774.0, 3775.0, 3776.0, 3777.0, 3778.0, 3779.0, 3780.0]]],    [[[3781.0, 3782.0, 3783.0, 3784.0, 3785.0, 3786.0, 3787.0],      [3788.0, 3789.0, 3790.0, 3791.0, 3792.0, 3793.0, 3794.0],      [3795.0, 3796.0, 3797.0, 3798.0, 3799.0, 3800.0, 3801.0],      [3802.0, 3803.0, 3804.0, 3805.0, 3806.0, 3807.0, 3808.0],      [3809.0, 3810.0, 3811.0, 3812.0, 3813.0, 3814.0, 3815.0]],     [[3816.0, 3817.0, 3818.0, 3819.0, 3820.0, 3821.0, 3822.0],      [3823.0, 3824.0, 3825.0, 3826.0, 3827.0, 3828.0, 3829.0],      [3830.0, 3831.0, 3832.0, 3833.0, 3834.0, 3835.0, 3836.0],      [3837.0, 3838.0, 3839.0, 3840.0, 3841.0, 3842.0, 3843.0],      [3844.0, 3845.0, 3846.0, 3847.0, 3848.0, 3849.0, 3850.0]],     [[3851.0, 3852.0, 3853.0, 3854.0, 3855.0, 3856.0, 3857.0],      [3858.0, 3859.0, 3860.0, 3861.0, 3862.0, 3863.0, 3864.0],      [3865.0, 3866.0, 3867.0, 3868.0, 3869.0, 3870.0, 3871.0],      [3872.0, 3873.0, 3874.0, 3875.0, 3876.0, 3877.0, 3878.0],      [3879.0, 3880.0, 3881.0, 3882.0, 3883.0, 3884.0, 3885.0]]],    [[[3886.0, 3887.0, 3888.0, 3889.0, 3890.0, 3891.0, 3892.0],      [3893.0, 3894.0, 3895.0, 3896.0, 3897.0, 3898.0, 3899.0],      [3900.0, 3901.0, 3902.0, 3903.0, 3904.0, 3905.0, 3906.0],      [3907.0, 3908.0, 3909.0, 3910.0, 3911.0, 3912.0, 3913.0],      [3914.0, 3915.0, 3916.0, 3917.0, 3918.0, 3919.0, 3920.0]],     [[3921.0, 3922.0, 3923.0, 3924.0, 3925.0, 3926.0, 3927.0],      [3928.0, 3929.0, 3930.0, 3931.0, 3932.0, 3933.0, 3934.0],      [3935.0, 3936.0, 3937.0, 3938.0, 3939.0, 3940.0, 3941.0],      [3942.0, 3943.0, 3944.0, 3945.0, 3946.0, 3947.0, 3948.0],      [3949.0, 3950.0, 3951.0, 3952.0, 3953.0, 3954.0, 3955.0]],     [[3956.0, 3957.0, 3958.0, 3959.0, 3960.0, 3961.0, 3962.0],      [3963.0, 3964.0, 3965.0, 3966.0, 3967.0, 3968.0, 3969.0],      [3970.0, 3971.0, 3972.0, 3973.0, 3974.0, 3975.0, 3976.0],      [3977.0, 3978.0, 3979.0, 3980.0, 3981.0, 3982.0, 3983.0],      [3984.0, 3985.0, 3986.0, 3987.0, 3988.0, 3989.0, 3990.0]]],    [[[3991.0, 3992.0, 3993.0, 3994.0, 3995.0, 3996.0, 3997.0],      [3998.0, 3999.0, 4000.0, 4001.0, 4002.0, 4003.0, 4004.0],      [4005.0, 4006.0, 4007.0, 4008.0, 4009.0, 4010.0, 4011.0],      [4012.0, 4013.0, 4014.0, 4015.0, 4016.0, 4017.0, 4018.0],      [4019.0, 4020.0, 4021.0, 4022.0, 4023.0, 4024.0, 4025.0]],     [[4026.0, 4027.0, 4028.0, 4029.0, 4030.0, 4031.0, 4032.0],      [4033.0, 4034.0, 4035.0, 4036.0, 4037.0, 4038.0, 4039.0],      [4040.0, 4041.0, 4042.0, 4043.0, 4044.0, 4045.0, 4046.0],      [4047.0, 4048.0, 4049.0, 4050.0, 4051.0, 4052.0, 4053.0],      [4054.0, 4055.0, 4056.0, 4057.0, 4058.0, 4059.0, 4060.0]],     [[4061.0, 4062.0, 4063.0, 4064.0, 4065.0, 4066.0, 4067.0],      [4068.0, 4069.0, 4070.0, 4071.0, 4072.0, 4073.0, 4074.0],      [4075.0, 4076.0, 4077.0, 4078.0, 4079.0, 4080.0, 4081.0],      [4082.0, 4083.0, 4084.0, 4085.0, 4086.0, 4087.0, 4088.0],      [4089.0, 4090.0, 4091.0, 4092.0, 4093.0, 4094.0, 4095.0]]],    [[[4096.0, 4097.0, 4098.0, 4099.0, 4100.0, 4101.0, 4102.0],      [4103.0, 4104.0, 4105.0, 4106.0, 4107.0, 4108.0, 4109.0],      [4110.0, 4111.0, 4112.0, 4113.0, 4114.0, 4115.0, 4116.0],      [4117.0, 4118.0, 4119.0, 4120.0, 4121.0, 4122.0, 4123.0],      [4124.0, 4125.0, 4126.0, 4127.0, 4128.0, 4129.0, 4130.0]],     [[4131.0, 4132.0, 4133.0, 4134.0, 4135.0, 4136.0, 4137.0],      [4138.0, 4139.0, 4140.0, 4141.0, 4142.0, 4143.0, 4144.0],      [4145.0, 4146.0, 4147.0, 4148.0, 4149.0, 4150.0, 4151.0],      [4152.0, 4153.0, 4154.0, 4155.0, 4156.0, 4157.0, 4158.0],      [4159.0, 4160.0, 4161.0, 4162.0, 4163.0, 4164.0, 4165.0]],     [[4166.0, 4167.0, 4168.0, 4169.0, 4170.0, 4171.0, 4172.0],      [4173.0, 4174.0, 4175.0, 4176.0, 4177.0, 4178.0, 4179.0],      [4180.0, 4181.0, 4182.0, 4183.0, 4184.0, 4185.0, 4186.0],      [4187.0, 4188.0, 4189.0, 4190.0, 4191.0, 4192.0, 4193.0],      [4194.0, 4195.0, 4196.0, 4197.0, 4198.0, 4199.0, 4200.0]]],    [[[4201.0, 4202.0, 4203.0, 4204.0, 4205.0, 4206.0, 4207.0],      [4208.0, 4209.0, 4210.0, 4211.0, 4212.0, 4213.0, 4214.0],      [4215.0, 4216.0, 4217.0, 4218.0, 4219.0, 4220.0, 4221.0],      [4222.0, 4223.0, 4224.0, 4225.0, 4226.0, 4227.0, 4228.0],      [4229.0, 4230.0, 4231.0, 4232.0, 4233.0, 4234.0, 4235.0]],     [[4236.0, 4237.0, 4238.0, 4239.0, 4240.0, 4241.0, 4242.0],      [4243.0, 4244.0, 4245.0, 4246.0, 4247.0, 4248.0, 4249.0],      [4250.0, 4251.0, 4252.0, 4253.0, 4254.0, 4255.0, 4256.0],      [4257.0, 4258.0, 4259.0, 4260.0, 4261.0, 4262.0, 4263.0],      [4264.0, 4265.0, 4266.0, 4267.0, 4268.0, 4269.0, 4270.0]],     [[4271.0, 4272.0, 4273.0, 4274.0, 4275.0, 4276.0, 4277.0],      [4278.0, 4279.0, 4280.0, 4281.0, 4282.0, 4283.0, 4284.0],      [4285.0, 4286.0, 4287.0, 4288.0, 4289.0, 4290.0, 4291.0],      [4292.0, 4293.0, 4294.0, 4295.0, 4296.0, 4297.0, 4298.0],      [4299.0, 4300.0, 4301.0, 4302.0, 4303.0, 4304.0, 4305.0]]],    [[[4306.0, 4307.0, 4308.0, 4309.0, 4310.0, 4311.0, 4312.0],      [4313.0, 4314.0, 4315.0, 4316.0, 4317.0, 4318.0, 4319.0],      [4320.0, 4321.0, 4322.0, 4323.0, 4324.0, 4325.0, 4326.0],      [4327.0, 4328.0, 4329.0, 4330.0, 4331.0, 4332.0, 4333.0],      [4334.0, 4335.0, 4336.0, 4337.0, 4338.0, 4339.0, 4340.0]],     [[4341.0, 4342.0, 4343.0, 4344.0, 4345.0, 4346.0, 4347.0],      [4348.0, 4349.0, 4350.0, 4351.0, 4352.0, 4353.0, 4354.0],      [4355.0, 4356.0, 4357.0, 4358.0, 4359.0, 4360.0, 4361.0],      [4362.0, 4363.0, 4364.0, 4365.0, 4366.0, 4367.0, 4368.0],      [4369.0, 4370.0, 4371.0, 4372.0, 4373.0, 4374.0, 4375.0]],     [[4376.0, 4377.0, 4378.0, 4379.0, 4380.0, 4381.0, 4382.0],      [4383.0, 4384.0, 4385.0, 4386.0, 4387.0, 4388.0, 4389.0],      [4390.0, 4391.0, 4392.0, 4393.0, 4394.0, 4395.0, 4396.0],      [4397.0, 4398.0, 4399.0, 4400.0, 4401.0, 4402.0, 4403.0],      [4404.0, 4405.0, 4406.0, 4407.0, 4408.0, 4409.0, 4410.0]]]]]] shape=[3, 2, 7, 3, 5, 7], strides=[1470, 735, 105, 35, 7, 1], layout=C (0x1)), I32([2, 2, 2] shape=[3], strides=[1], layout=C | F (0x3)), I32([[0, 2],  [1, 2],  [0, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 279325912 2790143389 3318244644 2032839674 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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, 16.0],       [17.0, 18.0, 19.0, 20.0],       [21.0, 22.0, 23.0, 24.0]],      [[25.0, 26.0, 27.0, 28.0],       [29.0, 30.0, 31.0, 32.0],       [33.0, 34.0, 35.0, 36.0],       [37.0, 38.0, 39.0, 40.0],       [41.0, 42.0, 43.0, 44.0],       [45.0, 46.0, 47.0, 48.0]],      [[49.0, 50.0, 51.0, 52.0],       [53.0, 54.0, 55.0, 56.0],       [57.0, 58.0, 59.0, 60.0],       [61.0, 62.0, 63.0, 64.0],       [65.0, 66.0, 67.0, 68.0],       [69.0, 70.0, 71.0, 72.0]],      [[73.0, 74.0, 75.0, 76.0],       [77.0, 78.0, 79.0, 80.0],       [81.0, 82.0, 83.0, 84.0],       [85.0, 86.0, 87.0, 88.0],       [89.0, 90.0, 91.0, 92.0],       [93.0, 94.0, 95.0, 96.0]]],     [[[97.0, 98.0, 99.0, 100.0],       [101.0, 102.0, 103.0, 104.0],       [105.0, 106.0, 107.0, 108.0],       [109.0, 110.0, 111.0, 112.0],       [113.0, 114.0, 115.0, 116.0],       [117.0, 118.0, 119.0, 120.0]],      [[121.0, 122.0, 123.0, 124.0],       [125.0, 126.0, 127.0, 128.0],       [129.0, 130.0, 131.0, 132.0],       [133.0, 134.0, 135.0, 136.0],       [137.0, 138.0, 139.0, 140.0],       [141.0, 142.0, 143.0, 144.0]],      [[145.0, 146.0, 147.0, 148.0],       [149.0, 150.0, 151.0, 152.0],       [153.0, 154.0, 155.0, 156.0],       [157.0, 158.0, 159.0, 160.0],       [161.0, 162.0, 163.0, 164.0],       [165.0, 166.0, 167.0, 168.0]],      [[169.0, 170.0, 171.0, 172.0],       [173.0, 174.0, 175.0, 176.0],       [177.0, 178.0, 179.0, 180.0],       [181.0, 182.0, 183.0, 184.0],       [185.0, 186.0, 187.0, 188.0],       [189.0, 190.0, 191.0, 192.0]]]],    [[[[193.0, 194.0, 195.0, 196.0],       [197.0, 198.0, 199.0, 200.0],       [201.0, 202.0, 203.0, 204.0],       [205.0, 206.0, 207.0, 208.0],       [209.0, 210.0, 211.0, 212.0],       [213.0, 214.0, 215.0, 216.0]],      [[217.0, 218.0, 219.0, 220.0],       [221.0, 222.0, 223.0, 224.0],       [225.0, 226.0, 227.0, 228.0],       [229.0, 230.0, 231.0, 232.0],       [233.0, 234.0, 235.0, 236.0],       [237.0, 238.0, 239.0, 240.0]],      [[241.0, 242.0, 243.0, 244.0],       [245.0, 246.0, 247.0, 248.0],       [249.0, 250.0, 251.0, 252.0],       [253.0, 254.0, 255.0, 256.0],       [257.0, 258.0, 259.0, 260.0],       [261.0, 262.0, 263.0, 264.0]],      [[265.0, 266.0, 267.0, 268.0],       [269.0, 270.0, 271.0, 272.0],       [273.0, 274.0, 275.0, 276.0],       [277.0, 278.0, 279.0, 280.0],       [281.0, 282.0, 283.0, 284.0],       [285.0, 286.0, 287.0, 288.0]]],     [[[289.0, 290.0, 291.0, 292.0],       [293.0, 294.0, 295.0, 296.0],       [297.0, 298.0, 299.0, 300.0],       [301.0, 302.0, 303.0, 304.0],       [305.0, 306.0, 307.0, 308.0],       [309.0, 310.0, 311.0, 312.0]],      [[313.0, 314.0, 315.0, 316.0],       [317.0, 318.0, 319.0, 320.0],       [321.0, 322.0, 323.0, 324.0],       [325.0, 326.0, 327.0, 328.0],       [329.0, 330.0, 331.0, 332.0],       [333.0, 334.0, 335.0, 336.0]],      [[337.0, 338.0, 339.0, 340.0],       [341.0, 342.0, 343.0, 344.0],       [345.0, 346.0, 347.0, 348.0],       [349.0, 350.0, 351.0, 352.0],       [353.0, 354.0, 355.0, 356.0],       [357.0, 358.0, 359.0, 360.0]],      [[361.0, 362.0, 363.0, 364.0],       [365.0, 366.0, 367.0, 368.0],       [369.0, 370.0, 371.0, 372.0],       [373.0, 374.0, 375.0, 376.0],       [377.0, 378.0, 379.0, 380.0],       [381.0, 382.0, 383.0, 384.0]]]],    [[[[385.0, 386.0, 387.0, 388.0],       [389.0, 390.0, 391.0, 392.0],       [393.0, 394.0, 395.0, 396.0],       [397.0, 398.0, 399.0, 400.0],       [401.0, 402.0, 403.0, 404.0],       [405.0, 406.0, 407.0, 408.0]],      [[409.0, 410.0, 411.0, 412.0],       [413.0, 414.0, 415.0, 416.0],       [417.0, 418.0, 419.0, 420.0],       [421.0, 422.0, 423.0, 424.0],       [425.0, 426.0, 427.0, 428.0],       [429.0, 430.0, 431.0, 432.0]],      [[433.0, 434.0, 435.0, 436.0],       [437.0, 438.0, 439.0, 440.0],       [441.0, 442.0, 443.0, 444.0],       [445.0, 446.0, 447.0, 448.0],       [449.0, 450.0, 451.0, 452.0],       [453.0, 454.0, 455.0, 456.0]],      [[457.0, 458.0, 459.0, 460.0],       [461.0, 462.0, 463.0, 464.0],       [465.0, 466.0, 467.0, 468.0],       [469.0, 470.0, 471.0, 472.0],       [473.0, 474.0, 475.0, 476.0],       [477.0, 478.0, 479.0, 480.0]]],     [[[481.0, 482.0, 483.0, 484.0],       [485.0, 486.0, 487.0, 488.0],       [489.0, 490.0, 491.0, 492.0],       [493.0, 494.0, 495.0, 496.0],       [497.0, 498.0, 499.0, 500.0],       [501.0, 502.0, 503.0, 504.0]],      [[505.0, 506.0, 507.0, 508.0],       [509.0, 510.0, 511.0, 512.0],       [513.0, 514.0, 515.0, 516.0],       [517.0, 518.0, 519.0, 520.0],       [521.0, 522.0, 523.0, 524.0],       [525.0, 526.0, 527.0, 528.0]],      [[529.0, 530.0, 531.0, 532.0],       [533.0, 534.0, 535.0, 536.0],       [537.0, 538.0, 539.0, 540.0],       [541.0, 542.0, 543.0, 544.0],       [545.0, 546.0, 547.0, 548.0],       [549.0, 550.0, 551.0, 552.0]],      [[553.0, 554.0, 555.0, 556.0],       [557.0, 558.0, 559.0, 560.0],       [561.0, 562.0, 563.0, 564.0],       [565.0, 566.0, 567.0, 568.0],       [569.0, 570.0, 571.0, 572.0],       [573.0, 574.0, 575.0, 576.0]]]],    [[[[577.0, 578.0, 579.0, 580.0],       [581.0, 582.0, 583.0, 584.0],       [585.0, 586.0, 587.0, 588.0],       [589.0, 590.0, 591.0, 592.0],       [593.0, 594.0, 595.0, 596.0],       [597.0, 598.0, 599.0, 600.0]],      [[601.0, 602.0, 603.0, 604.0],       [605.0, 606.0, 607.0, 608.0],       [609.0, 610.0, 611.0, 612.0],       [613.0, 614.0, 615.0, 616.0],       [617.0, 618.0, 619.0, 620.0],       [621.0, 622.0, 623.0, 624.0]],      [[625.0, 626.0, 627.0, 628.0],       [629.0, 630.0, 631.0, 632.0],       [633.0, 634.0, 635.0, 636.0],       [637.0, 638.0, 639.0, 640.0],       [641.0, 642.0, 643.0, 644.0],       [645.0, 646.0, 647.0, 648.0]],      [[649.0, 650.0, 651.0, 652.0],       [653.0, 654.0, 655.0, 656.0],       [657.0, 658.0, 659.0, 660.0],       [661.0, 662.0, 663.0, 664.0],       [665.0, 666.0, 667.0, 668.0],       [669.0, 670.0, 671.0, 672.0]]],     [[[673.0, 674.0, 675.0, 676.0],       [677.0, 678.0, 679.0, 680.0],       [681.0, 682.0, 683.0, 684.0],       [685.0, 686.0, 687.0, 688.0],       [689.0, 690.0, 691.0, 692.0],       [693.0, 694.0, 695.0, 696.0]],      [[697.0, 698.0, 699.0, 700.0],       [701.0, 702.0, 703.0, 704.0],       [705.0, 706.0, 707.0, 708.0],       [709.0, 710.0, 711.0, 712.0],       [713.0, 714.0, 715.0, 716.0],       [717.0, 718.0, 719.0, 720.0]],      [[721.0, 722.0, 723.0, 724.0],       [725.0, 726.0, 727.0, 728.0],       [729.0, 730.0, 731.0, 732.0],       [733.0, 734.0, 735.0, 736.0],       [737.0, 738.0, 739.0, 740.0],       [741.0, 742.0, 743.0, 744.0]],      [[745.0, 746.0, 747.0, 748.0],       [749.0, 750.0, 751.0, 752.0],       [753.0, 754.0, 755.0, 756.0],       [757.0, 758.0, 759.0, 760.0],       [761.0, 762.0, 763.0, 764.0],       [765.0, 766.0, 767.0, 768.0]]]],    [[[[769.0, 770.0, 771.0, 772.0],       [773.0, 774.0, 775.0, 776.0],       [777.0, 778.0, 779.0, 780.0],       [781.0, 782.0, 783.0, 784.0],       [785.0, 786.0, 787.0, 788.0],       [789.0, 790.0, 791.0, 792.0]],      [[793.0, 794.0, 795.0, 796.0],       [797.0, 798.0, 799.0, 800.0],       [801.0, 802.0, 803.0, 804.0],       [805.0, 806.0, 807.0, 808.0],       [809.0, 810.0, 811.0, 812.0],       [813.0, 814.0, 815.0, 816.0]],      [[817.0, 818.0, 819.0, 820.0],       [821.0, 822.0, 823.0, 824.0],       [825.0, 826.0, 827.0, 828.0],       [829.0, 830.0, 831.0, 832.0],       [833.0, 834.0, 835.0, 836.0],       [837.0, 838.0, 839.0, 840.0]],      [[841.0, 842.0, 843.0, 844.0],       [845.0, 846.0, 847.0, 848.0],       [849.0, 850.0, 851.0, 852.0],       [853.0, 854.0, 855.0, 856.0],       [857.0, 858.0, 859.0, 860.0],       [861.0, 862.0, 863.0, 864.0]]],     [[[865.0, 866.0, 867.0, 868.0],       [869.0, 870.0, 871.0, 872.0],       [873.0, 874.0, 875.0, 876.0],       [877.0, 878.0, 879.0, 880.0],       [881.0, 882.0, 883.0, 884.0],       [885.0, 886.0, 887.0, 888.0]],      [[889.0, 890.0, 891.0, 892.0],       [893.0, 894.0, 895.0, 896.0],       [897.0, 898.0, 899.0, 900.0],       [901.0, 902.0, 903.0, 904.0],       [905.0, 906.0, 907.0, 908.0],       [909.0, 910.0, 911.0, 912.0]],      [[913.0, 914.0, 915.0, 916.0],       [917.0, 918.0, 919.0, 920.0],       [921.0, 922.0, 923.0, 924.0],       [925.0, 926.0, 927.0, 928.0],       [929.0, 930.0, 931.0, 932.0],       [933.0, 934.0, 935.0, 936.0]],      [[937.0, 938.0, 939.0, 940.0],       [941.0, 942.0, 943.0, 944.0],       [945.0, 946.0, 947.0, 948.0],       [949.0, 950.0, 951.0, 952.0],       [953.0, 954.0, 955.0, 956.0],       [957.0, 958.0, 959.0, 960.0]]]],    [[[[961.0, 962.0, 963.0, 964.0],       [965.0, 966.0, 967.0, 968.0],       [969.0, 970.0, 971.0, 972.0],       [973.0, 974.0, 975.0, 976.0],       [977.0, 978.0, 979.0, 980.0],       [981.0, 982.0, 983.0, 984.0]],      [[985.0, 986.0, 987.0, 988.0],       [989.0, 990.0, 991.0, 992.0],       [993.0, 994.0, 995.0, 996.0],       [997.0, 998.0, 999.0, 1000.0],       [1001.0, 1002.0, 1003.0, 1004.0],       [1005.0, 1006.0, 1007.0, 1008.0]],      [[1009.0, 1010.0, 1011.0, 1012.0],       [1013.0, 1014.0, 1015.0, 1016.0],       [1017.0, 1018.0, 1019.0, 1020.0],       [1021.0, 1022.0, 1023.0, 1024.0],       [1025.0, 1026.0, 1027.0, 1028.0],       [1029.0, 1030.0, 1031.0, 1032.0]],      [[1033.0, 1034.0, 1035.0, 1036.0],       [1037.0, 1038.0, 1039.0, 1040.0],       [1041.0, 1042.0, 1043.0, 1044.0],       [1045.0, 1046.0, 1047.0, 1048.0],       [1049.0, 1050.0, 1051.0, 1052.0],       [1053.0, 1054.0, 1055.0, 1056.0]]],     [[[1057.0, 1058.0, 1059.0, 1060.0],       [1061.0, 1062.0, 1063.0, 1064.0],       [1065.0, 1066.0, 1067.0, 1068.0],       [1069.0, 1070.0, 1071.0, 1072.0],       [1073.0, 1074.0, 1075.0, 1076.0],       [1077.0, 1078.0, 1079.0, 1080.0]],      [[1081.0, 1082.0, 1083.0, 1084.0],       [1085.0, 1086.0, 1087.0, 1088.0],       [1089.0, 1090.0, 1091.0, 1092.0],       [1093.0, 1094.0, 1095.0, 1096.0],       [1097.0, 1098.0, 1099.0, 1100.0],       [1101.0, 1102.0, 1103.0, 1104.0]],      [[1105.0, 1106.0, 1107.0, 1108.0],       [1109.0, 1110.0, 1111.0, 1112.0],       [1113.0, 1114.0, 1115.0, 1116.0],       [1117.0, 1118.0, 1119.0, 1120.0],       [1121.0, 1122.0, 1123.0, 1124.0],       [1125.0, 1126.0, 1127.0, 1128.0]],      [[1129.0, 1130.0, 1131.0, 1132.0],       [1133.0, 1134.0, 1135.0, 1136.0],       [1137.0, 1138.0, 1139.0, 1140.0],       [1141.0, 1142.0, 1143.0, 1144.0],       [1145.0, 1146.0, 1147.0, 1148.0],       [1149.0, 1150.0, 1151.0, 1152.0]]]]],   [[[[[1153.0, 1154.0, 1155.0, 1156.0],       [1157.0, 1158.0, 1159.0, 1160.0],       [1161.0, 1162.0, 1163.0, 1164.0],       [1165.0, 1166.0, 1167.0, 1168.0],       [1169.0, 1170.0, 1171.0, 1172.0],       [1173.0, 1174.0, 1175.0, 1176.0]],      [[1177.0, 1178.0, 1179.0, 1180.0],       [1181.0, 1182.0, 1183.0, 1184.0],       [1185.0, 1186.0, 1187.0, 1188.0],       [1189.0, 1190.0, 1191.0, 1192.0],       [1193.0, 1194.0, 1195.0, 1196.0],       [1197.0, 1198.0, 1199.0, 1200.0]],      [[1201.0, 1202.0, 1203.0, 1204.0],       [1205.0, 1206.0, 1207.0, 1208.0],       [1209.0, 1210.0, 1211.0, 1212.0],       [1213.0, 1214.0, 1215.0, 1216.0],       [1217.0, 1218.0, 1219.0, 1220.0],       [1221.0, 1222.0, 1223.0, 1224.0]],      [[1225.0, 1226.0, 1227.0, 1228.0],       [1229.0, 1230.0, 1231.0, 1232.0],       [1233.0, 1234.0, 1235.0, 1236.0],       [1237.0, 1238.0, 1239.0, 1240.0],       [1241.0, 1242.0, 1243.0, 1244.0],       [1245.0, 1246.0, 1247.0, 1248.0]]],     [[[1249.0, 1250.0, 1251.0, 1252.0],       [1253.0, 1254.0, 1255.0, 1256.0],       [1257.0, 1258.0, 1259.0, 1260.0],       [1261.0, 1262.0, 1263.0, 1264.0],       [1265.0, 1266.0, 1267.0, 1268.0],       [1269.0, 1270.0, 1271.0, 1272.0]],      [[1273.0, 1274.0, 1275.0, 1276.0],       [1277.0, 1278.0, 1279.0, 1280.0],       [1281.0, 1282.0, 1283.0, 1284.0],       [1285.0, 1286.0, 1287.0, 1288.0],       [1289.0, 1290.0, 1291.0, 1292.0],       [1293.0, 1294.0, 1295.0, 1296.0]],      [[1297.0, 1298.0, 1299.0, 1300.0],       [1301.0, 1302.0, 1303.0, 1304.0],       [1305.0, 1306.0, 1307.0, 1308.0],       [1309.0, 1310.0, 1311.0, 1312.0],       [1313.0, 1314.0, 1315.0, 1316.0],       [1317.0, 1318.0, 1319.0, 1320.0]],      [[1321.0, 1322.0, 1323.0, 1324.0],       [1325.0, 1326.0, 1327.0, 1328.0],       [1329.0, 1330.0, 1331.0, 1332.0],       [1333.0, 1334.0, 1335.0, 1336.0],       [1337.0, 1338.0, 1339.0, 1340.0],       [1341.0, 1342.0, 1343.0, 1344.0]]]],    [[[[1345.0, 1346.0, 1347.0, 1348.0],       [1349.0, 1350.0, 1351.0, 1352.0],       [1353.0, 1354.0, 1355.0, 1356.0],       [1357.0, 1358.0, 1359.0, 1360.0],       [1361.0, 1362.0, 1363.0, 1364.0],       [1365.0, 1366.0, 1367.0, 1368.0]],      [[1369.0, 1370.0, 1371.0, 1372.0],       [1373.0, 1374.0, 1375.0, 1376.0],       [1377.0, 1378.0, 1379.0, 1380.0],       [1381.0, 1382.0, 1383.0, 1384.0],       [1385.0, 1386.0, 1387.0, 1388.0],       [1389.0, 1390.0, 1391.0, 1392.0]],      [[1393.0, 1394.0, 1395.0, 1396.0],       [1397.0, 1398.0, 1399.0, 1400.0],       [1401.0, 1402.0, 1403.0, 1404.0],       [1405.0, 1406.0, 1407.0, 1408.0],       [1409.0, 1410.0, 1411.0, 1412.0],       [1413.0, 1414.0, 1415.0, 1416.0]],      [[1417.0, 1418.0, 1419.0, 1420.0],       [1421.0, 1422.0, 1423.0, 1424.0],       [1425.0, 1426.0, 1427.0, 1428.0],       [1429.0, 1430.0, 1431.0, 1432.0],       [1433.0, 1434.0, 1435.0, 1436.0],       [1437.0, 1438.0, 1439.0, 1440.0]]],     [[[1441.0, 1442.0, 1443.0, 1444.0],       [1445.0, 1446.0, 1447.0, 1448.0],       [1449.0, 1450.0, 1451.0, 1452.0],       [1453.0, 1454.0, 1455.0, 1456.0],       [1457.0, 1458.0, 1459.0, 1460.0],       [1461.0, 1462.0, 1463.0, 1464.0]],      [[1465.0, 1466.0, 1467.0, 1468.0],       [1469.0, 1470.0, 1471.0, 1472.0],       [1473.0, 1474.0, 1475.0, 1476.0],       [1477.0, 1478.0, 1479.0, 1480.0],       [1481.0, 1482.0, 1483.0, 1484.0],       [1485.0, 1486.0, 1487.0, 1488.0]],      [[1489.0, 1490.0, 1491.0, 1492.0],       [1493.0, 1494.0, 1495.0, 1496.0],       [1497.0, 1498.0, 1499.0, 1500.0],       [1501.0, 1502.0, 1503.0, 1504.0],       [1505.0, 1506.0, 1507.0, 1508.0],       [1509.0, 1510.0, 1511.0, 1512.0]],      [[1513.0, 1514.0, 1515.0, 1516.0],       [1517.0, 1518.0, 1519.0, 1520.0],       [1521.0, 1522.0, 1523.0, 1524.0],       [1525.0, 1526.0, 1527.0, 1528.0],       [1529.0, 1530.0, 1531.0, 1532.0],       [1533.0, 1534.0, 1535.0, 1536.0]]]],    [[[[1537.0, 1538.0, 1539.0, 1540.0],       [1541.0, 1542.0, 1543.0, 1544.0],       [1545.0, 1546.0, 1547.0, 1548.0],       [1549.0, 1550.0, 1551.0, 1552.0],       [1553.0, 1554.0, 1555.0, 1556.0],       [1557.0, 1558.0, 1559.0, 1560.0]],      [[1561.0, 1562.0, 1563.0, 1564.0],       [1565.0, 1566.0, 1567.0, 1568.0],       [1569.0, 1570.0, 1571.0, 1572.0],       [1573.0, 1574.0, 1575.0, 1576.0],       [1577.0, 1578.0, 1579.0, 1580.0],       [1581.0, 1582.0, 1583.0, 1584.0]],      [[1585.0, 1586.0, 1587.0, 1588.0],       [1589.0, 1590.0, 1591.0, 1592.0],       [1593.0, 1594.0, 1595.0, 1596.0],       [1597.0, 1598.0, 1599.0, 1600.0],       [1601.0, 1602.0, 1603.0, 1604.0],       [1605.0, 1606.0, 1607.0, 1608.0]],      [[1609.0, 1610.0, 1611.0, 1612.0],       [1613.0, 1614.0, 1615.0, 1616.0],       [1617.0, 1618.0, 1619.0, 1620.0],       [1621.0, 1622.0, 1623.0, 1624.0],       [1625.0, 1626.0, 1627.0, 1628.0],       [1629.0, 1630.0, 1631.0, 1632.0]]],     [[[1633.0, 1634.0, 1635.0, 1636.0],       [1637.0, 1638.0, 1639.0, 1640.0],       [1641.0, 1642.0, 1643.0, 1644.0],       [1645.0, 1646.0, 1647.0, 1648.0],       [1649.0, 1650.0, 1651.0, 1652.0],       [1653.0, 1654.0, 1655.0, 1656.0]],      [[1657.0, 1658.0, 1659.0, 1660.0],       [1661.0, 1662.0, 1663.0, 1664.0],       [1665.0, 1666.0, 1667.0, 1668.0],       [1669.0, 1670.0, 1671.0, 1672.0],       [1673.0, 1674.0, 1675.0, 1676.0],       [1677.0, 1678.0, 1679.0, 1680.0]],      [[1681.0, 1682.0, 1683.0, 1684.0],       [1685.0, 1686.0, 1687.0, 1688.0],       [1689.0, 1690.0, 1691.0, 1692.0],       [1693.0, 1694.0, 1695.0, 1696.0],       [1697.0, 1698.0, 1699.0, 1700.0],       [1701.0, 1702.0, 1703.0, 1704.0]],      [[1705.0, 1706.0, 1707.0, 1708.0],       [1709.0, 1710.0, 1711.0, 1712.0],       [1713.0, 1714.0, 1715.0, 1716.0],       [1717.0, 1718.0, 1719.0, 1720.0],       [1721.0, 1722.0, 1723.0, 1724.0],       [1725.0, 1726.0, 1727.0, 1728.0]]]],    [[[[1729.0, 1730.0, 1731.0, 1732.0],       [1733.0, 1734.0, 1735.0, 1736.0],       [1737.0, 1738.0, 1739.0, 1740.0],       [1741.0, 1742.0, 1743.0, 1744.0],       [1745.0, 1746.0, 1747.0, 1748.0],       [1749.0, 1750.0, 1751.0, 1752.0]],      [[1753.0, 1754.0, 1755.0, 1756.0],       [1757.0, 1758.0, 1759.0, 1760.0],       [1761.0, 1762.0, 1763.0, 1764.0],       [1765.0, 1766.0, 1767.0, 1768.0],       [1769.0, 1770.0, 1771.0, 1772.0],       [1773.0, 1774.0, 1775.0, 1776.0]],      [[1777.0, 1778.0, 1779.0, 1780.0],       [1781.0, 1782.0, 1783.0, 1784.0],       [1785.0, 1786.0, 1787.0, 1788.0],       [1789.0, 1790.0, 1791.0, 1792.0],       [1793.0, 1794.0, 1795.0, 1796.0],       [1797.0, 1798.0, 1799.0, 1800.0]],      [[1801.0, 1802.0, 1803.0, 1804.0],       [1805.0, 1806.0, 1807.0, 1808.0],       [1809.0, 1810.0, 1811.0, 1812.0],       [1813.0, 1814.0, 1815.0, 1816.0],       [1817.0, 1818.0, 1819.0, 1820.0],       [1821.0, 1822.0, 1823.0, 1824.0]]],     [[[1825.0, 1826.0, 1827.0, 1828.0],       [1829.0, 1830.0, 1831.0, 1832.0],       [1833.0, 1834.0, 1835.0, 1836.0],       [1837.0, 1838.0, 1839.0, 1840.0],       [1841.0, 1842.0, 1843.0, 1844.0],       [1845.0, 1846.0, 1847.0, 1848.0]],      [[1849.0, 1850.0, 1851.0, 1852.0],       [1853.0, 1854.0, 1855.0, 1856.0],       [1857.0, 1858.0, 1859.0, 1860.0],       [1861.0, 1862.0, 1863.0, 1864.0],       [1865.0, 1866.0, 1867.0, 1868.0],       [1869.0, 1870.0, 1871.0, 1872.0]],      [[1873.0, 1874.0, 1875.0, 1876.0],       [1877.0, 1878.0, 1879.0, 1880.0],       [1881.0, 1882.0, 1883.0, 1884.0],       [1885.0, 1886.0, 1887.0, 1888.0],       [1889.0, 1890.0, 1891.0, 1892.0],       [1893.0, 1894.0, 1895.0, 1896.0]],      [[1897.0, 1898.0, 1899.0, 1900.0],       [1901.0, 1902.0, 1903.0, 1904.0],       [1905.0, 1906.0, 1907.0, 1908.0],       [1909.0, 1910.0, 1911.0, 1912.0],       [1913.0, 1914.0, 1915.0, 1916.0],       [1917.0, 1918.0, 1919.0, 1920.0]]]],    [[[[1921.0, 1922.0, 1923.0, 1924.0],       [1925.0, 1926.0, 1927.0, 1928.0],       [1929.0, 1930.0, 1931.0, 1932.0],       [1933.0, 1934.0, 1935.0, 1936.0],       [1937.0, 1938.0, 1939.0, 1940.0],       [1941.0, 1942.0, 1943.0, 1944.0]],      [[1945.0, 1946.0, 1947.0, 1948.0],       [1949.0, 1950.0, 1951.0, 1952.0],       [1953.0, 1954.0, 1955.0, 1956.0],       [1957.0, 1958.0, 1959.0, 1960.0],       [1961.0, 1962.0, 1963.0, 1964.0],       [1965.0, 1966.0, 1967.0, 1968.0]],      [[1969.0, 1970.0, 1971.0, 1972.0],       [1973.0, 1974.0, 1975.0, 1976.0],       [1977.0, 1978.0, 1979.0, 1980.0],       [1981.0, 1982.0, 1983.0, 1984.0],       [1985.0, 1986.0, 1987.0, 1988.0],       [1989.0, 1990.0, 1991.0, 1992.0]],      [[1993.0, 1994.0, 1995.0, 1996.0],       [1997.0, 1998.0, 1999.0, 2000.0],       [2001.0, 2002.0, 2003.0, 2004.0],       [2005.0, 2006.0, 2007.0, 2008.0],       [2009.0, 2010.0, 2011.0, 2012.0],       [2013.0, 2014.0, 2015.0, 2016.0]]],     [[[2017.0, 2018.0, 2019.0, 2020.0],       [2021.0, 2022.0, 2023.0, 2024.0],       [2025.0, 2026.0, 2027.0, 2028.0],       [2029.0, 2030.0, 2031.0, 2032.0],       [2033.0, 2034.0, 2035.0, 2036.0],       [2037.0, 2038.0, 2039.0, 2040.0]],      [[2041.0, 2042.0, 2043.0, 2044.0],       [2045.0, 2046.0, 2047.0, 2048.0],       [2049.0, 2050.0, 2051.0, 2052.0],       [2053.0, 2054.0, 2055.0, 2056.0],       [2057.0, 2058.0, 2059.0, 2060.0],       [2061.0, 2062.0, 2063.0, 2064.0]],      [[2065.0, 2066.0, 2067.0, 2068.0],       [2069.0, 2070.0, 2071.0, 2072.0],       [2073.0, 2074.0, 2075.0, 2076.0],       [2077.0, 2078.0, 2079.0, 2080.0],       [2081.0, 2082.0, 2083.0, 2084.0],       [2085.0, 2086.0, 2087.0, 2088.0]],      [[2089.0, 2090.0, 2091.0, 2092.0],       [2093.0, 2094.0, 2095.0, 2096.0],       [2097.0, 2098.0, 2099.0, 2100.0],       [2101.0, 2102.0, 2103.0, 2104.0],       [2105.0, 2106.0, 2107.0, 2108.0],       [2109.0, 2110.0, 2111.0, 2112.0]]]],    [[[[2113.0, 2114.0, 2115.0, 2116.0],       [2117.0, 2118.0, 2119.0, 2120.0],       [2121.0, 2122.0, 2123.0, 2124.0],       [2125.0, 2126.0, 2127.0, 2128.0],       [2129.0, 2130.0, 2131.0, 2132.0],       [2133.0, 2134.0, 2135.0, 2136.0]],      [[2137.0, 2138.0, 2139.0, 2140.0],       [2141.0, 2142.0, 2143.0, 2144.0],       [2145.0, 2146.0, 2147.0, 2148.0],       [2149.0, 2150.0, 2151.0, 2152.0],       [2153.0, 2154.0, 2155.0, 2156.0],       [2157.0, 2158.0, 2159.0, 2160.0]],      [[2161.0, 2162.0, 2163.0, 2164.0],       [2165.0, 2166.0, 2167.0, 2168.0],       [2169.0, 2170.0, 2171.0, 2172.0],       [2173.0, 2174.0, 2175.0, 2176.0],       [2177.0, 2178.0, 2179.0, 2180.0],       [2181.0, 2182.0, 2183.0, 2184.0]],      [[2185.0, 2186.0, 2187.0, 2188.0],       [2189.0, 2190.0, 2191.0, 2192.0],       [2193.0, 2194.0, 2195.0, 2196.0],       [2197.0, 2198.0, 2199.0, 2200.0],       [2201.0, 2202.0, 2203.0, 2204.0],       [2205.0, 2206.0, 2207.0, 2208.0]]],     [[[2209.0, 2210.0, 2211.0, 2212.0],       [2213.0, 2214.0, 2215.0, 2216.0],       [2217.0, 2218.0, 2219.0, 2220.0],       [2221.0, 2222.0, 2223.0, 2224.0],       [2225.0, 2226.0, 2227.0, 2228.0],       [2229.0, 2230.0, 2231.0, 2232.0]],      [[2233.0, 2234.0, 2235.0, 2236.0],       [2237.0, 2238.0, 2239.0, 2240.0],       [2241.0, 2242.0, 2243.0, 2244.0],       [2245.0, 2246.0, 2247.0, 2248.0],       [2249.0, 2250.0, 2251.0, 2252.0],       [2253.0, 2254.0, 2255.0, 2256.0]],      [[2257.0, 2258.0, 2259.0, 2260.0],       [2261.0, 2262.0, 2263.0, 2264.0],       [2265.0, 2266.0, 2267.0, 2268.0],       [2269.0, 2270.0, 2271.0, 2272.0],       [2273.0, 2274.0, 2275.0, 2276.0],       [2277.0, 2278.0, 2279.0, 2280.0]],      [[2281.0, 2282.0, 2283.0, 2284.0],       [2285.0, 2286.0, 2287.0, 2288.0],       [2289.0, 2290.0, 2291.0, 2292.0],       [2293.0, 2294.0, 2295.0, 2296.0],       [2297.0, 2298.0, 2299.0, 2300.0],       [2301.0, 2302.0, 2303.0, 2304.0]]]]],   [[[[[2305.0, 2306.0, 2307.0, 2308.0],       [2309.0, 2310.0, 2311.0, 2312.0],       [2313.0, 2314.0, 2315.0, 2316.0],       [2317.0, 2318.0, 2319.0, 2320.0],       [2321.0, 2322.0, 2323.0, 2324.0],       [2325.0, 2326.0, 2327.0, 2328.0]],      [[2329.0, 2330.0, 2331.0, 2332.0],       [2333.0, 2334.0, 2335.0, 2336.0],       [2337.0, 2338.0, 2339.0, 2340.0],       [2341.0, 2342.0, 2343.0, 2344.0],       [2345.0, 2346.0, 2347.0, 2348.0],       [2349.0, 2350.0, 2351.0, 2352.0]],      [[2353.0, 2354.0, 2355.0, 2356.0],       [2357.0, 2358.0, 2359.0, 2360.0],       [2361.0, 2362.0, 2363.0, 2364.0],       [2365.0, 2366.0, 2367.0, 2368.0],       [2369.0, 2370.0, 2371.0, 2372.0],       [2373.0, 2374.0, 2375.0, 2376.0]],      [[2377.0, 2378.0, 2379.0, 2380.0],       [2381.0, 2382.0, 2383.0, 2384.0],       [2385.0, 2386.0, 2387.0, 2388.0],       [2389.0, 2390.0, 2391.0, 2392.0],       [2393.0, 2394.0, 2395.0, 2396.0],       [2397.0, 2398.0, 2399.0, 2400.0]]],     [[[2401.0, 2402.0, 2403.0, 2404.0],       [2405.0, 2406.0, 2407.0, 2408.0],       [2409.0, 2410.0, 2411.0, 2412.0],       [2413.0, 2414.0, 2415.0, 2416.0],       [2417.0, 2418.0, 2419.0, 2420.0],       [2421.0, 2422.0, 2423.0, 2424.0]],      [[2425.0, 2426.0, 2427.0, 2428.0],       [2429.0, 2430.0, 2431.0, 2432.0],       [2433.0, 2434.0, 2435.0, 2436.0],       [2437.0, 2438.0, 2439.0, 2440.0],       [2441.0, 2442.0, 2443.0, 2444.0],       [2445.0, 2446.0, 2447.0, 2448.0]],      [[2449.0, 2450.0, 2451.0, 2452.0],       [2453.0, 2454.0, 2455.0, 2456.0],       [2457.0, 2458.0, 2459.0, 2460.0],       [2461.0, 2462.0, 2463.0, 2464.0],       [2465.0, 2466.0, 2467.0, 2468.0],       [2469.0, 2470.0, 2471.0, 2472.0]],      [[2473.0, 2474.0, 2475.0, 2476.0],       [2477.0, 2478.0, 2479.0, 2480.0],       [2481.0, 2482.0, 2483.0, 2484.0],       [2485.0, 2486.0, 2487.0, 2488.0],       [2489.0, 2490.0, 2491.0, 2492.0],       [2493.0, 2494.0, 2495.0, 2496.0]]]],    [[[[2497.0, 2498.0, 2499.0, 2500.0],       [2501.0, 2502.0, 2503.0, 2504.0],       [2505.0, 2506.0, 2507.0, 2508.0],       [2509.0, 2510.0, 2511.0, 2512.0],       [2513.0, 2514.0, 2515.0, 2516.0],       [2517.0, 2518.0, 2519.0, 2520.0]],      [[2521.0, 2522.0, 2523.0, 2524.0],       [2525.0, 2526.0, 2527.0, 2528.0],       [2529.0, 2530.0, 2531.0, 2532.0],       [2533.0, 2534.0, 2535.0, 2536.0],       [2537.0, 2538.0, 2539.0, 2540.0],       [2541.0, 2542.0, 2543.0, 2544.0]],      [[2545.0, 2546.0, 2547.0, 2548.0],       [2549.0, 2550.0, 2551.0, 2552.0],       [2553.0, 2554.0, 2555.0, 2556.0],       [2557.0, 2558.0, 2559.0, 2560.0],       [2561.0, 2562.0, 2563.0, 2564.0],       [2565.0, 2566.0, 2567.0, 2568.0]],      [[2569.0, 2570.0, 2571.0, 2572.0],       [2573.0, 2574.0, 2575.0, 2576.0],       [2577.0, 2578.0, 2579.0, 2580.0],       [2581.0, 2582.0, 2583.0, 2584.0],       [2585.0, 2586.0, 2587.0, 2588.0],       [2589.0, 2590.0, 2591.0, 2592.0]]],     [[[2593.0, 2594.0, 2595.0, 2596.0],       [2597.0, 2598.0, 2599.0, 2600.0],       [2601.0, 2602.0, 2603.0, 2604.0],       [2605.0, 2606.0, 2607.0, 2608.0],       [2609.0, 2610.0, 2611.0, 2612.0],       [2613.0, 2614.0, 2615.0, 2616.0]],      [[2617.0, 2618.0, 2619.0, 2620.0],       [2621.0, 2622.0, 2623.0, 2624.0],       [2625.0, 2626.0, 2627.0, 2628.0],       [2629.0, 2630.0, 2631.0, 2632.0],       [2633.0, 2634.0, 2635.0, 2636.0],       [2637.0, 2638.0, 2639.0, 2640.0]],      [[2641.0, 2642.0, 2643.0, 2644.0],       [2645.0, 2646.0, 2647.0, 2648.0],       [2649.0, 2650.0, 2651.0, 2652.0],       [2653.0, 2654.0, 2655.0, 2656.0],       [2657.0, 2658.0, 2659.0, 2660.0],       [2661.0, 2662.0, 2663.0, 2664.0]],      [[2665.0, 2666.0, 2667.0, 2668.0],       [2669.0, 2670.0, 2671.0, 2672.0],       [2673.0, 2674.0, 2675.0, 2676.0],       [2677.0, 2678.0, 2679.0, 2680.0],       [2681.0, 2682.0, 2683.0, 2684.0],       [2685.0, 2686.0, 2687.0, 2688.0]]]],    [[[[2689.0, 2690.0, 2691.0, 2692.0],       [2693.0, 2694.0, 2695.0, 2696.0],       [2697.0, 2698.0, 2699.0, 2700.0],       [2701.0, 2702.0, 2703.0, 2704.0],       [2705.0, 2706.0, 2707.0, 2708.0],       [2709.0, 2710.0, 2711.0, 2712.0]],      [[2713.0, 2714.0, 2715.0, 2716.0],       [2717.0, 2718.0, 2719.0, 2720.0],       [2721.0, 2722.0, 2723.0, 2724.0],       [2725.0, 2726.0, 2727.0, 2728.0],       [2729.0, 2730.0, 2731.0, 2732.0],       [2733.0, 2734.0, 2735.0, 2736.0]],      [[2737.0, 2738.0, 2739.0, 2740.0],       [2741.0, 2742.0, 2743.0, 2744.0],       [2745.0, 2746.0, 2747.0, 2748.0],       [2749.0, 2750.0, 2751.0, 2752.0],       [2753.0, 2754.0, 2755.0, 2756.0],       [2757.0, 2758.0, 2759.0, 2760.0]],      [[2761.0, 2762.0, 2763.0, 2764.0],       [2765.0, 2766.0, 2767.0, 2768.0],       [2769.0, 2770.0, 2771.0, 2772.0],       [2773.0, 2774.0, 2775.0, 2776.0],       [2777.0, 2778.0, 2779.0, 2780.0],       [2781.0, 2782.0, 2783.0, 2784.0]]],     [[[2785.0, 2786.0, 2787.0, 2788.0],       [2789.0, 2790.0, 2791.0, 2792.0],       [2793.0, 2794.0, 2795.0, 2796.0],       [2797.0, 2798.0, 2799.0, 2800.0],       [2801.0, 2802.0, 2803.0, 2804.0],       [2805.0, 2806.0, 2807.0, 2808.0]],      [[2809.0, 2810.0, 2811.0, 2812.0],       [2813.0, 2814.0, 2815.0, 2816.0],       [2817.0, 2818.0, 2819.0, 2820.0],       [2821.0, 2822.0, 2823.0, 2824.0],       [2825.0, 2826.0, 2827.0, 2828.0],       [2829.0, 2830.0, 2831.0, 2832.0]],      [[2833.0, 2834.0, 2835.0, 2836.0],       [2837.0, 2838.0, 2839.0, 2840.0],       [2841.0, 2842.0, 2843.0, 2844.0],       [2845.0, 2846.0, 2847.0, 2848.0],       [2849.0, 2850.0, 2851.0, 2852.0],       [2853.0, 2854.0, 2855.0, 2856.0]],      [[2857.0, 2858.0, 2859.0, 2860.0],       [2861.0, 2862.0, 2863.0, 2864.0],       [2865.0, 2866.0, 2867.0, 2868.0],       [2869.0, 2870.0, 2871.0, 2872.0],       [2873.0, 2874.0, 2875.0, 2876.0],       [2877.0, 2878.0, 2879.0, 2880.0]]]],    [[[[2881.0, 2882.0, 2883.0, 2884.0],       [2885.0, 2886.0, 2887.0, 2888.0],       [2889.0, 2890.0, 2891.0, 2892.0],       [2893.0, 2894.0, 2895.0, 2896.0],       [2897.0, 2898.0, 2899.0, 2900.0],       [2901.0, 2902.0, 2903.0, 2904.0]],      [[2905.0, 2906.0, 2907.0, 2908.0],       [2909.0, 2910.0, 2911.0, 2912.0],       [2913.0, 2914.0, 2915.0, 2916.0],       [2917.0, 2918.0, 2919.0, 2920.0],       [2921.0, 2922.0, 2923.0, 2924.0],       [2925.0, 2926.0, 2927.0, 2928.0]],      [[2929.0, 2930.0, 2931.0, 2932.0],       [2933.0, 2934.0, 2935.0, 2936.0],       [2937.0, 2938.0, 2939.0, 2940.0],       [2941.0, 2942.0, 2943.0, 2944.0],       [2945.0, 2946.0, 2947.0, 2948.0],       [2949.0, 2950.0, 2951.0, 2952.0]],      [[2953.0, 2954.0, 2955.0, 2956.0],       [2957.0, 2958.0, 2959.0, 2960.0],       [2961.0, 2962.0, 2963.0, 2964.0],       [2965.0, 2966.0, 2967.0, 2968.0],       [2969.0, 2970.0, 2971.0, 2972.0],       [2973.0, 2974.0, 2975.0, 2976.0]]],     [[[2977.0, 2978.0, 2979.0, 2980.0],       [2981.0, 2982.0, 2983.0, 2984.0],       [2985.0, 2986.0, 2987.0, 2988.0],       [2989.0, 2990.0, 2991.0, 2992.0],       [2993.0, 2994.0, 2995.0, 2996.0],       [2997.0, 2998.0, 2999.0, 3000.0]],      [[3001.0, 3002.0, 3003.0, 3004.0],       [3005.0, 3006.0, 3007.0, 3008.0],       [3009.0, 3010.0, 3011.0, 3012.0],       [3013.0, 3014.0, 3015.0, 3016.0],       [3017.0, 3018.0, 3019.0, 3020.0],       [3021.0, 3022.0, 3023.0, 3024.0]],      [[3025.0, 3026.0, 3027.0, 3028.0],       [3029.0, 3030.0, 3031.0, 3032.0],       [3033.0, 3034.0, 3035.0, 3036.0],       [3037.0, 3038.0, 3039.0, 3040.0],       [3041.0, 3042.0, 3043.0, 3044.0],       [3045.0, 3046.0, 3047.0, 3048.0]],      [[3049.0, 3050.0, 3051.0, 3052.0],       [3053.0, 3054.0, 3055.0, 3056.0],       [3057.0, 3058.0, 3059.0, 3060.0],       [3061.0, 3062.0, 3063.0, 3064.0],       [3065.0, 3066.0, 3067.0, 3068.0],       [3069.0, 3070.0, 3071.0, 3072.0]]]],    [[[[3073.0, 3074.0, 3075.0, 3076.0],       [3077.0, 3078.0, 3079.0, 3080.0],       [3081.0, 3082.0, 3083.0, 3084.0],       [3085.0, 3086.0, 3087.0, 3088.0],       [3089.0, 3090.0, 3091.0, 3092.0],       [3093.0, 3094.0, 3095.0, 3096.0]],      [[3097.0, 3098.0, 3099.0, 3100.0],       [3101.0, 3102.0, 3103.0, 3104.0],       [3105.0, 3106.0, 3107.0, 3108.0],       [3109.0, 3110.0, 3111.0, 3112.0],       [3113.0, 3114.0, 3115.0, 3116.0],       [3117.0, 3118.0, 3119.0, 3120.0]],      [[3121.0, 3122.0, 3123.0, 3124.0],       [3125.0, 3126.0, 3127.0, 3128.0],       [3129.0, 3130.0, 3131.0, 3132.0],       [3133.0, 3134.0, 3135.0, 3136.0],       [3137.0, 3138.0, 3139.0, 3140.0],       [3141.0, 3142.0, 3143.0, 3144.0]],      [[3145.0, 3146.0, 3147.0, 3148.0],       [3149.0, 3150.0, 3151.0, 3152.0],       [3153.0, 3154.0, 3155.0, 3156.0],       [3157.0, 3158.0, 3159.0, 3160.0],       [3161.0, 3162.0, 3163.0, 3164.0],       [3165.0, 3166.0, 3167.0, 3168.0]]],     [[[3169.0, 3170.0, 3171.0, 3172.0],       [3173.0, 3174.0, 3175.0, 3176.0],       [3177.0, 3178.0, 3179.0, 3180.0],       [3181.0, 3182.0, 3183.0, 3184.0],       [3185.0, 3186.0, 3187.0, 3188.0],       [3189.0, 3190.0, 3191.0, 3192.0]],      [[3193.0, 3194.0, 3195.0, 3196.0],       [3197.0, 3198.0, 3199.0, 3200.0],       [3201.0, 3202.0, 3203.0, 3204.0],       [3205.0, 3206.0, 3207.0, 3208.0],       [3209.0, 3210.0, 3211.0, 3212.0],       [3213.0, 3214.0, 3215.0, 3216.0]],      [[3217.0, 3218.0, 3219.0, 3220.0],       [3221.0, 3222.0, 3223.0, 3224.0],       [3225.0, 3226.0, 3227.0, 3228.0],       [3229.0, 3230.0, 3231.0, 3232.0],       [3233.0, 3234.0, 3235.0, 3236.0],       [3237.0, 3238.0, 3239.0, 3240.0]],      [[3241.0, 3242.0, 3243.0, 3244.0],       [3245.0, 3246.0, 3247.0, 3248.0],       [3249.0, 3250.0, 3251.0, 3252.0],       [3253.0, 3254.0, 3255.0, 3256.0],       [3257.0, 3258.0, 3259.0, 3260.0],       [3261.0, 3262.0, 3263.0, 3264.0]]]],    [[[[3265.0, 3266.0, 3267.0, 3268.0],       [3269.0, 3270.0, 3271.0, 3272.0],       [3273.0, 3274.0, 3275.0, 3276.0],       [3277.0, 3278.0, 3279.0, 3280.0],       [3281.0, 3282.0, 3283.0, 3284.0],       [3285.0, 3286.0, 3287.0, 3288.0]],      [[3289.0, 3290.0, 3291.0, 3292.0],       [3293.0, 3294.0, 3295.0, 3296.0],       [3297.0, 3298.0, 3299.0, 3300.0],       [3301.0, 3302.0, 3303.0, 3304.0],       [3305.0, 3306.0, 3307.0, 3308.0],       [3309.0, 3310.0, 3311.0, 3312.0]],      [[3313.0, 3314.0, 3315.0, 3316.0],       [3317.0, 3318.0, 3319.0, 3320.0],       [3321.0, 3322.0, 3323.0, 3324.0],       [3325.0, 3326.0, 3327.0, 3328.0],       [3329.0, 3330.0, 3331.0, 3332.0],       [3333.0, 3334.0, 3335.0, 3336.0]],      [[3337.0, 3338.0, 3339.0, 3340.0],       [3341.0, 3342.0, 3343.0, 3344.0],       [3345.0, 3346.0, 3347.0, 3348.0],       [3349.0, 3350.0, 3351.0, 3352.0],       [3353.0, 3354.0, 3355.0, 3356.0],       [3357.0, 3358.0, 3359.0, 3360.0]]],     [[[3361.0, 3362.0, 3363.0, 3364.0],       [3365.0, 3366.0, 3367.0, 3368.0],       [3369.0, 3370.0, 3371.0, 3372.0],       [3373.0, 3374.0, 3375.0, 3376.0],       [3377.0, 3378.0, 3379.0, 3380.0],       [3381.0, 3382.0, 3383.0, 3384.0]],      [[3385.0, 3386.0, 3387.0, 3388.0],       [3389.0, 3390.0, 3391.0, 3392.0],       [3393.0, 3394.0, 3395.0, 3396.0],       [3397.0, 3398.0, 3399.0, 3400.0],       [3401.0, 3402.0, 3403.0, 3404.0],       [3405.0, 3406.0, 3407.0, 3408.0]],      [[3409.0, 3410.0, 3411.0, 3412.0],       [3413.0, 3414.0, 3415.0, 3416.0],       [3417.0, 3418.0, 3419.0, 3420.0],       [3421.0, 3422.0, 3423.0, 3424.0],       [3425.0, 3426.0, 3427.0, 3428.0],       [3429.0, 3430.0, 3431.0, 3432.0]],      [[3433.0, 3434.0, 3435.0, 3436.0],       [3437.0, 3438.0, 3439.0, 3440.0],       [3441.0, 3442.0, 3443.0, 3444.0],       [3445.0, 3446.0, 3447.0, 3448.0],       [3449.0, 3450.0, 3451.0, 3452.0],       [3453.0, 3454.0, 3455.0, 3456.0]]]]],   [[[[[3457.0, 3458.0, 3459.0, 3460.0],       [3461.0, 3462.0, 3463.0, 3464.0],       [3465.0, 3466.0, 3467.0, 3468.0],       [3469.0, 3470.0, 3471.0, 3472.0],       [3473.0, 3474.0, 3475.0, 3476.0],       [3477.0, 3478.0, 3479.0, 3480.0]],      [[3481.0, 3482.0, 3483.0, 3484.0],       [3485.0, 3486.0, 3487.0, 3488.0],       [3489.0, 3490.0, 3491.0, 3492.0],       [3493.0, 3494.0, 3495.0, 3496.0],       [3497.0, 3498.0, 3499.0, 3500.0],       [3501.0, 3502.0, 3503.0, 3504.0]],      [[3505.0, 3506.0, 3507.0, 3508.0],       [3509.0, 3510.0, 3511.0, 3512.0],       [3513.0, 3514.0, 3515.0, 3516.0],       [3517.0, 3518.0, 3519.0, 3520.0],       [3521.0, 3522.0, 3523.0, 3524.0],       [3525.0, 3526.0, 3527.0, 3528.0]],      [[3529.0, 3530.0, 3531.0, 3532.0],       [3533.0, 3534.0, 3535.0, 3536.0],       [3537.0, 3538.0, 3539.0, 3540.0],       [3541.0, 3542.0, 3543.0, 3544.0],       [3545.0, 3546.0, 3547.0, 3548.0],       [3549.0, 3550.0, 3551.0, 3552.0]]],     [[[3553.0, 3554.0, 3555.0, 3556.0],       [3557.0, 3558.0, 3559.0, 3560.0],       [3561.0, 3562.0, 3563.0, 3564.0],       [3565.0, 3566.0, 3567.0, 3568.0],       [3569.0, 3570.0, 3571.0, 3572.0],       [3573.0, 3574.0, 3575.0, 3576.0]],      [[3577.0, 3578.0, 3579.0, 3580.0],       [3581.0, 3582.0, 3583.0, 3584.0],       [3585.0, 3586.0, 3587.0, 3588.0],       [3589.0, 3590.0, 3591.0, 3592.0],       [3593.0, 3594.0, 3595.0, 3596.0],       [3597.0, 3598.0, 3599.0, 3600.0]],      [[3601.0, 3602.0, 3603.0, 3604.0],       [3605.0, 3606.0, 3607.0, 3608.0],       [3609.0, 3610.0, 3611.0, 3612.0],       [3613.0, 3614.0, 3615.0, 3616.0],       [3617.0, 3618.0, 3619.0, 3620.0],       [3621.0, 3622.0, 3623.0, 3624.0]],      [[3625.0, 3626.0, 3627.0, 3628.0],       [3629.0, 3630.0, 3631.0, 3632.0],       [3633.0, 3634.0, 3635.0, 3636.0],       [3637.0, 3638.0, 3639.0, 3640.0],       [3641.0, 3642.0, 3643.0, 3644.0],       [3645.0, 3646.0, 3647.0, 3648.0]]]],    [[[[3649.0, 3650.0, 3651.0, 3652.0],       [3653.0, 3654.0, 3655.0, 3656.0],       [3657.0, 3658.0, 3659.0, 3660.0],       [3661.0, 3662.0, 3663.0, 3664.0],       [3665.0, 3666.0, 3667.0, 3668.0],       [3669.0, 3670.0, 3671.0, 3672.0]],      [[3673.0, 3674.0, 3675.0, 3676.0],       [3677.0, 3678.0, 3679.0, 3680.0],       [3681.0, 3682.0, 3683.0, 3684.0],       [3685.0, 3686.0, 3687.0, 3688.0],       [3689.0, 3690.0, 3691.0, 3692.0],       [3693.0, 3694.0, 3695.0, 3696.0]],      [[3697.0, 3698.0, 3699.0, 3700.0],       [3701.0, 3702.0, 3703.0, 3704.0],       [3705.0, 3706.0, 3707.0, 3708.0],       [3709.0, 3710.0, 3711.0, 3712.0],       [3713.0, 3714.0, 3715.0, 3716.0],       [3717.0, 3718.0, 3719.0, 3720.0]],      [[3721.0, 3722.0, 3723.0, 3724.0],       [3725.0, 3726.0, 3727.0, 3728.0],       [3729.0, 3730.0, 3731.0, 3732.0],       [3733.0, 3734.0, 3735.0, 3736.0],       [3737.0, 3738.0, 3739.0, 3740.0],       [3741.0, 3742.0, 3743.0, 3744.0]]],     [[[3745.0, 3746.0, 3747.0, 3748.0],       [3749.0, 3750.0, 3751.0, 3752.0],       [3753.0, 3754.0, 3755.0, 3756.0],       [3757.0, 3758.0, 3759.0, 3760.0],       [3761.0, 3762.0, 3763.0, 3764.0],       [3765.0, 3766.0, 3767.0, 3768.0]],      [[3769.0, 3770.0, 3771.0, 3772.0],       [3773.0, 3774.0, 3775.0, 3776.0],       [3777.0, 3778.0, 3779.0, 3780.0],       [3781.0, 3782.0, 3783.0, 3784.0],       [3785.0, 3786.0, 3787.0, 3788.0],       [3789.0, 3790.0, 3791.0, 3792.0]],      [[3793.0, 3794.0, 3795.0, 3796.0],       [3797.0, 3798.0, 3799.0, 3800.0],       [3801.0, 3802.0, 3803.0, 3804.0],       [3805.0, 3806.0, 3807.0, 3808.0],       [3809.0, 3810.0, 3811.0, 3812.0],       [3813.0, 3814.0, 3815.0, 3816.0]],      [[3817.0, 3818.0, 3819.0, 3820.0],       [3821.0, 3822.0, 3823.0, 3824.0],       [3825.0, 3826.0, 3827.0, 3828.0],       [3829.0, 3830.0, 3831.0, 3832.0],       [3833.0, 3834.0, 3835.0, 3836.0],       [3837.0, 3838.0, 3839.0, 3840.0]]]],    [[[[3841.0, 3842.0, 3843.0, 3844.0],       [3845.0, 3846.0, 3847.0, 3848.0],       [3849.0, 3850.0, 3851.0, 3852.0],       [3853.0, 3854.0, 3855.0, 3856.0],       [3857.0, 3858.0, 3859.0, 3860.0],       [3861.0, 3862.0, 3863.0, 3864.0]],      [[3865.0, 3866.0, 3867.0, 3868.0],       [3869.0, 3870.0, 3871.0, 3872.0],       [3873.0, 3874.0, 3875.0, 3876.0],       [3877.0, 3878.0, 3879.0, 3880.0],       [3881.0, 3882.0, 3883.0, 3884.0],       [3885.0, 3886.0, 3887.0, 3888.0]],      [[3889.0, 3890.0, 3891.0, 3892.0],       [3893.0, 3894.0, 3895.0, 3896.0],       [3897.0, 3898.0, 3899.0, 3900.0],       [3901.0, 3902.0, 3903.0, 3904.0],       [3905.0, 3906.0, 3907.0, 3908.0],       [3909.0, 3910.0, 3911.0, 3912.0]],      [[3913.0, 3914.0, 3915.0, 3916.0],       [3917.0, 3918.0, 3919.0, 3920.0],       [3921.0, 3922.0, 3923.0, 3924.0],       [3925.0, 3926.0, 3927.0, 3928.0],       [3929.0, 3930.0, 3931.0, 3932.0],       [3933.0, 3934.0, 3935.0, 3936.0]]],     [[[3937.0, 3938.0, 3939.0, 3940.0],       [3941.0, 3942.0, 3943.0, 3944.0],       [3945.0, 3946.0, 3947.0, 3948.0],       [3949.0, 3950.0, 3951.0, 3952.0],       [3953.0, 3954.0, 3955.0, 3956.0],       [3957.0, 3958.0, 3959.0, 3960.0]],      [[3961.0, 3962.0, 3963.0, 3964.0],       [3965.0, 3966.0, 3967.0, 3968.0],       [3969.0, 3970.0, 3971.0, 3972.0],       [3973.0, 3974.0, 3975.0, 3976.0],       [3977.0, 3978.0, 3979.0, 3980.0],       [3981.0, 3982.0, 3983.0, 3984.0]],      [[3985.0, 3986.0, 3987.0, 3988.0],       [3989.0, 3990.0, 3991.0, 3992.0],       [3993.0, 3994.0, 3995.0, 3996.0],       [3997.0, 3998.0, 3999.0, 4000.0],       [4001.0, 4002.0, 4003.0, 4004.0],       [4005.0, 4006.0, 4007.0, 4008.0]],      [[4009.0, 4010.0, 4011.0, 4012.0],       [4013.0, 4014.0, 4015.0, 4016.0],       [4017.0, 4018.0, 4019.0, 4020.0],       [4021.0, 4022.0, 4023.0, 4024.0],       [4025.0, 4026.0, 4027.0, 4028.0],       [4029.0, 4030.0, 4031.0, 4032.0]]]],    [[[[4033.0, 4034.0, 4035.0, 4036.0],       [4037.0, 4038.0, 4039.0, 4040.0],       [4041.0, 4042.0, 4043.0, 4044.0],       [4045.0, 4046.0, 4047.0, 4048.0],       [4049.0, 4050.0, 4051.0, 4052.0],       [4053.0, 4054.0, 4055.0, 4056.0]],      [[4057.0, 4058.0, 4059.0, 4060.0],       [4061.0, 4062.0, 4063.0, 4064.0],       [4065.0, 4066.0, 4067.0, 4068.0],       [4069.0, 4070.0, 4071.0, 4072.0],       [4073.0, 4074.0, 4075.0, 4076.0],       [4077.0, 4078.0, 4079.0, 4080.0]],      [[4081.0, 4082.0, 4083.0, 4084.0],       [4085.0, 4086.0, 4087.0, 4088.0],       [4089.0, 4090.0, 4091.0, 4092.0],       [4093.0, 4094.0, 4095.0, 4096.0],       [4097.0, 4098.0, 4099.0, 4100.0],       [4101.0, 4102.0, 4103.0, 4104.0]],      [[4105.0, 4106.0, 4107.0, 4108.0],       [4109.0, 4110.0, 4111.0, 4112.0],       [4113.0, 4114.0, 4115.0, 4116.0],       [4117.0, 4118.0, 4119.0, 4120.0],       [4121.0, 4122.0, 4123.0, 4124.0],       [4125.0, 4126.0, 4127.0, 4128.0]]],     [[[4129.0, 4130.0, 4131.0, 4132.0],       [4133.0, 4134.0, 4135.0, 4136.0],       [4137.0, 4138.0, 4139.0, 4140.0],       [4141.0, 4142.0, 4143.0, 4144.0],       [4145.0, 4146.0, 4147.0, 4148.0],       [4149.0, 4150.0, 4151.0, 4152.0]],      [[4153.0, 4154.0, 4155.0, 4156.0],       [4157.0, 4158.0, 4159.0, 4160.0],       [4161.0, 4162.0, 4163.0, 4164.0],       [4165.0, 4166.0, 4167.0, 4168.0],       [4169.0, 4170.0, 4171.0, 4172.0],       [4173.0, 4174.0, 4175.0, 4176.0]],      [[4177.0, 4178.0, 4179.0, 4180.0],       [4181.0, 4182.0, 4183.0, 4184.0],       [4185.0, 4186.0, 4187.0, 4188.0],       [4189.0, 4190.0, 4191.0, 4192.0],       [4193.0, 4194.0, 4195.0, 4196.0],       [4197.0, 4198.0, 4199.0, 4200.0]],      [[4201.0, 4202.0, 4203.0, 4204.0],       [4205.0, 4206.0, 4207.0, 4208.0],       [4209.0, 4210.0, 4211.0, 4212.0],       [4213.0, 4214.0, 4215.0, 4216.0],       [4217.0, 4218.0, 4219.0, 4220.0],       [4221.0, 4222.0, 4223.0, 4224.0]]]],    [[[[4225.0, 4226.0, 4227.0, 4228.0],       [4229.0, 4230.0, 4231.0, 4232.0],       [4233.0, 4234.0, 4235.0, 4236.0],       [4237.0, 4238.0, 4239.0, 4240.0],       [4241.0, 4242.0, 4243.0, 4244.0],       [4245.0, 4246.0, 4247.0, 4248.0]],      [[4249.0, 4250.0, 4251.0, 4252.0],       [4253.0, 4254.0, 4255.0, 4256.0],       [4257.0, 4258.0, 4259.0, 4260.0],       [4261.0, 4262.0, 4263.0, 4264.0],       [4265.0, 4266.0, 4267.0, 4268.0],       [4269.0, 4270.0, 4271.0, 4272.0]],      [[4273.0, 4274.0, 4275.0, 4276.0],       [4277.0, 4278.0, 4279.0, 4280.0],       [4281.0, 4282.0, 4283.0, 4284.0],       [4285.0, 4286.0, 4287.0, 4288.0],       [4289.0, 4290.0, 4291.0, 4292.0],       [4293.0, 4294.0, 4295.0, 4296.0]],      [[4297.0, 4298.0, 4299.0, 4300.0],       [4301.0, 4302.0, 4303.0, 4304.0],       [4305.0, 4306.0, 4307.0, 4308.0],       [4309.0, 4310.0, 4311.0, 4312.0],       [4313.0, 4314.0, 4315.0, 4316.0],       [4317.0, 4318.0, 4319.0, 4320.0]]],     [[[4321.0, 4322.0, 4323.0, 4324.0],       [4325.0, 4326.0, 4327.0, 4328.0],       [4329.0, 4330.0, 4331.0, 4332.0],       [4333.0, 4334.0, 4335.0, 4336.0],       [4337.0, 4338.0, 4339.0, 4340.0],       [4341.0, 4342.0, 4343.0, 4344.0]],      [[4345.0, 4346.0, 4347.0, 4348.0],       [4349.0, 4350.0, 4351.0, 4352.0],       [4353.0, 4354.0, 4355.0, 4356.0],       [4357.0, 4358.0, 4359.0, 4360.0],       [4361.0, 4362.0, 4363.0, 4364.0],       [4365.0, 4366.0, 4367.0, 4368.0]],      [[4369.0, 4370.0, 4371.0, 4372.0],       [4373.0, 4374.0, 4375.0, 4376.0],       [4377.0, 4378.0, 4379.0, 4380.0],       [4381.0, 4382.0, 4383.0, 4384.0],       [4385.0, 4386.0, 4387.0, 4388.0],       [4389.0, 4390.0, 4391.0, 4392.0]],      [[4393.0, 4394.0, 4395.0, 4396.0],       [4397.0, 4398.0, 4399.0, 4400.0],       [4401.0, 4402.0, 4403.0, 4404.0],       [4405.0, 4406.0, 4407.0, 4408.0],       [4409.0, 4410.0, 4411.0, 4412.0],       [4413.0, 4414.0, 4415.0, 4416.0]]]],    [[[[4417.0, 4418.0, 4419.0, 4420.0],       [4421.0, 4422.0, 4423.0, 4424.0],       [4425.0, 4426.0, 4427.0, 4428.0],       [4429.0, 4430.0, 4431.0, 4432.0],       [4433.0, 4434.0, 4435.0, 4436.0],       [4437.0, 4438.0, 4439.0, 4440.0]],      [[4441.0, 4442.0, 4443.0, 4444.0],       [4445.0, 4446.0, 4447.0, 4448.0],       [4449.0, 4450.0, 4451.0, 4452.0],       [4453.0, 4454.0, 4455.0, 4456.0],       [4457.0, 4458.0, 4459.0, 4460.0],       [4461.0, 4462.0, 4463.0, 4464.0]],      [[4465.0, 4466.0, 4467.0, 4468.0],       [4469.0, 4470.0, 4471.0, 4472.0],       [4473.0, 4474.0, 4475.0, 4476.0],       [4477.0, 4478.0, 4479.0, 4480.0],       [4481.0, 4482.0, 4483.0, 4484.0],       [4485.0, 4486.0, 4487.0, 4488.0]],      [[4489.0, 4490.0, 4491.0, 4492.0],       [4493.0, 4494.0, 4495.0, 4496.0],       [4497.0, 4498.0, 4499.0, 4500.0],       [4501.0, 4502.0, 4503.0, 4504.0],       [4505.0, 4506.0, 4507.0, 4508.0],       [4509.0, 4510.0, 4511.0, 4512.0]]],     [[[4513.0, 4514.0, 4515.0, 4516.0],       [4517.0, 4518.0, 4519.0, 4520.0],       [4521.0, 4522.0, 4523.0, 4524.0],       [4525.0, 4526.0, 4527.0, 4528.0],       [4529.0, 4530.0, 4531.0, 4532.0],       [4533.0, 4534.0, 4535.0, 4536.0]],      [[4537.0, 4538.0, 4539.0, 4540.0],       [4541.0, 4542.0, 4543.0, 4544.0],       [4545.0, 4546.0, 4547.0, 4548.0],       [4549.0, 4550.0, 4551.0, 4552.0],       [4553.0, 4554.0, 4555.0, 4556.0],       [4557.0, 4558.0, 4559.0, 4560.0]],      [[4561.0, 4562.0, 4563.0, 4564.0],       [4565.0, 4566.0, 4567.0, 4568.0],       [4569.0, 4570.0, 4571.0, 4572.0],       [4573.0, 4574.0, 4575.0, 4576.0],       [4577.0, 4578.0, 4579.0, 4580.0],       [4581.0, 4582.0, 4583.0, 4584.0]],      [[4585.0, 4586.0, 4587.0, 4588.0],       [4589.0, 4590.0, 4591.0, 4592.0],       [4593.0, 4594.0, 4595.0, 4596.0],       [4597.0, 4598.0, 4599.0, 4600.0],       [4601.0, 4602.0, 4603.0, 4604.0],       [4605.0, 4606.0, 4607.0, 4608.0]]]]],   [[[[[4609.0, 4610.0, 4611.0, 4612.0],       [4613.0, 4614.0, 4615.0, 4616.0],       [4617.0, 4618.0, 4619.0, 4620.0],       [4621.0, 4622.0, 4623.0, 4624.0],       [4625.0, 4626.0, 4627.0, 4628.0],       [4629.0, 4630.0, 4631.0, 4632.0]],      [[4633.0, 4634.0, 4635.0, 4636.0],       [4637.0, 4638.0, 4639.0, 4640.0],       [4641.0, 4642.0, 4643.0, 4644.0],       [4645.0, 4646.0, 4647.0, 4648.0],       [4649.0, 4650.0, 4651.0, 4652.0],       [4653.0, 4654.0, 4655.0, 4656.0]],      [[4657.0, 4658.0, 4659.0, 4660.0],       [4661.0, 4662.0, 4663.0, 4664.0],       [4665.0, 4666.0, 4667.0, 4668.0],       [4669.0, 4670.0, 4671.0, 4672.0],       [4673.0, 4674.0, 4675.0, 4676.0],       [4677.0, 4678.0, 4679.0, 4680.0]],      [[4681.0, 4682.0, 4683.0, 4684.0],       [4685.0, 4686.0, 4687.0, 4688.0],       [4689.0, 4690.0, 4691.0, 4692.0],       [4693.0, 4694.0, 4695.0, 4696.0],       [4697.0, 4698.0, 4699.0, 4700.0],       [4701.0, 4702.0, 4703.0, 4704.0]]],     [[[4705.0, 4706.0, 4707.0, 4708.0],       [4709.0, 4710.0, 4711.0, 4712.0],       [4713.0, 4714.0, 4715.0, 4716.0],       [4717.0, 4718.0, 4719.0, 4720.0],       [4721.0, 4722.0, 4723.0, 4724.0],       [4725.0, 4726.0, 4727.0, 4728.0]],      [[4729.0, 4730.0, 4731.0, 4732.0],       [4733.0, 4734.0, 4735.0, 4736.0],       [4737.0, 4738.0, 4739.0, 4740.0],       [4741.0, 4742.0, 4743.0, 4744.0],       [4745.0, 4746.0, 4747.0, 4748.0],       [4749.0, 4750.0, 4751.0, 4752.0]],      [[4753.0, 4754.0, 4755.0, 4756.0],       [4757.0, 4758.0, 4759.0, 4760.0],       [4761.0, 4762.0, 4763.0, 4764.0],       [4765.0, 4766.0, 4767.0, 4768.0],       [4769.0, 4770.0, 4771.0, 4772.0],       [4773.0, 4774.0, 4775.0, 4776.0]],      [[4777.0, 4778.0, 4779.0, 4780.0],       [4781.0, 4782.0, 4783.0, 4784.0],       [4785.0, 4786.0, 4787.0, 4788.0],       [4789.0, 4790.0, 4791.0, 4792.0],       [4793.0, 4794.0, 4795.0, 4796.0],       [4797.0, 4798.0, 4799.0, 4800.0]]]],    [[[[4801.0, 4802.0, 4803.0, 4804.0],       [4805.0, 4806.0, 4807.0, 4808.0],       [4809.0, 4810.0, 4811.0, 4812.0],       [4813.0, 4814.0, 4815.0, 4816.0],       [4817.0, 4818.0, 4819.0, 4820.0],       [4821.0, 4822.0, 4823.0, 4824.0]],      [[4825.0, 4826.0, 4827.0, 4828.0],       [4829.0, 4830.0, 4831.0, 4832.0],       [4833.0, 4834.0, 4835.0, 4836.0],       [4837.0, 4838.0, 4839.0, 4840.0],       [4841.0, 4842.0, 4843.0, 4844.0],       [4845.0, 4846.0, 4847.0, 4848.0]],      [[4849.0, 4850.0, 4851.0, 4852.0],       [4853.0, 4854.0, 4855.0, 4856.0],       [4857.0, 4858.0, 4859.0, 4860.0],       [4861.0, 4862.0, 4863.0, 4864.0],       [4865.0, 4866.0, 4867.0, 4868.0],       [4869.0, 4870.0, 4871.0, 4872.0]],      [[4873.0, 4874.0, 4875.0, 4876.0],       [4877.0, 4878.0, 4879.0, 4880.0],       [4881.0, 4882.0, 4883.0, 4884.0],       [4885.0, 4886.0, 4887.0, 4888.0],       [4889.0, 4890.0, 4891.0, 4892.0],       [4893.0, 4894.0, 4895.0, 4896.0]]],     [[[4897.0, 4898.0, 4899.0, 4900.0],       [4901.0, 4902.0, 4903.0, 4904.0],       [4905.0, 4906.0, 4907.0, 4908.0],       [4909.0, 4910.0, 4911.0, 4912.0],       [4913.0, 4914.0, 4915.0, 4916.0],       [4917.0, 4918.0, 4919.0, 4920.0]],      [[4921.0, 4922.0, 4923.0, 4924.0],       [4925.0, 4926.0, 4927.0, 4928.0],       [4929.0, 4930.0, 4931.0, 4932.0],       [4933.0, 4934.0, 4935.0, 4936.0],       [4937.0, 4938.0, 4939.0, 4940.0],       [4941.0, 4942.0, 4943.0, 4944.0]],      [[4945.0, 4946.0, 4947.0, 4948.0],       [4949.0, 4950.0, 4951.0, 4952.0],       [4953.0, 4954.0, 4955.0, 4956.0],       [4957.0, 4958.0, 4959.0, 4960.0],       [4961.0, 4962.0, 4963.0, 4964.0],       [4965.0, 4966.0, 4967.0, 4968.0]],      [[4969.0, 4970.0, 4971.0, 4972.0],       [4973.0, 4974.0, 4975.0, 4976.0],       [4977.0, 4978.0, 4979.0, 4980.0],       [4981.0, 4982.0, 4983.0, 4984.0],       [4985.0, 4986.0, 4987.0, 4988.0],       [4989.0, 4990.0, 4991.0, 4992.0]]]],    [[[[4993.0, 4994.0, 4995.0, 4996.0],       [4997.0, 4998.0, 4999.0, 5000.0],       [5001.0, 5002.0, 5003.0, 5004.0],       [5005.0, 5006.0, 5007.0, 5008.0],       [5009.0, 5010.0, 5011.0, 5012.0],       [5013.0, 5014.0, 5015.0, 5016.0]],      [[5017.0, 5018.0, 5019.0, 5020.0],       [5021.0, 5022.0, 5023.0, 5024.0],       [5025.0, 5026.0, 5027.0, 5028.0],       [5029.0, 5030.0, 5031.0, 5032.0],       [5033.0, 5034.0, 5035.0, 5036.0],       [5037.0, 5038.0, 5039.0, 5040.0]],      [[5041.0, 5042.0, 5043.0, 5044.0],       [5045.0, 5046.0, 5047.0, 5048.0],       [5049.0, 5050.0, 5051.0, 5052.0],       [5053.0, 5054.0, 5055.0, 5056.0],       [5057.0, 5058.0, 5059.0, 5060.0],       [5061.0, 5062.0, 5063.0, 5064.0]],      [[5065.0, 5066.0, 5067.0, 5068.0],       [5069.0, 5070.0, 5071.0, 5072.0],       [5073.0, 5074.0, 5075.0, 5076.0],       [5077.0, 5078.0, 5079.0, 5080.0],       [5081.0, 5082.0, 5083.0, 5084.0],       [5085.0, 5086.0, 5087.0, 5088.0]]],     [[[5089.0, 5090.0, 5091.0, 5092.0],       [5093.0, 5094.0, 5095.0, 5096.0],       [5097.0, 5098.0, 5099.0, 5100.0],       [5101.0, 5102.0, 5103.0, 5104.0],       [5105.0, 5106.0, 5107.0, 5108.0],       [5109.0, 5110.0, 5111.0, 5112.0]],      [[5113.0, 5114.0, 5115.0, 5116.0],       [5117.0, 5118.0, 5119.0, 5120.0],       [5121.0, 5122.0, 5123.0, 5124.0],       [5125.0, 5126.0, 5127.0, 5128.0],       [5129.0, 5130.0, 5131.0, 5132.0],       [5133.0, 5134.0, 5135.0, 5136.0]],      [[5137.0, 5138.0, 5139.0, 5140.0],       [5141.0, 5142.0, 5143.0, 5144.0],       [5145.0, 5146.0, 5147.0, 5148.0],       [5149.0, 5150.0, 5151.0, 5152.0],       [5153.0, 5154.0, 5155.0, 5156.0],       [5157.0, 5158.0, 5159.0, 5160.0]],      [[5161.0, 5162.0, 5163.0, 5164.0],       [5165.0, 5166.0, 5167.0, 5168.0],       [5169.0, 5170.0, 5171.0, 5172.0],       [5173.0, 5174.0, 5175.0, 5176.0],       [5177.0, 5178.0, 5179.0, 5180.0],       [5181.0, 5182.0, 5183.0, 5184.0]]]],    [[[[5185.0, 5186.0, 5187.0, 5188.0],       [5189.0, 5190.0, 5191.0, 5192.0],       [5193.0, 5194.0, 5195.0, 5196.0],       [5197.0, 5198.0, 5199.0, 5200.0],       [5201.0, 5202.0, 5203.0, 5204.0],       [5205.0, 5206.0, 5207.0, 5208.0]],      [[5209.0, 5210.0, 5211.0, 5212.0],       [5213.0, 5214.0, 5215.0, 5216.0],       [5217.0, 5218.0, 5219.0, 5220.0],       [5221.0, 5222.0, 5223.0, 5224.0],       [5225.0, 5226.0, 5227.0, 5228.0],       [5229.0, 5230.0, 5231.0, 5232.0]],      [[5233.0, 5234.0, 5235.0, 5236.0],       [5237.0, 5238.0, 5239.0, 5240.0],       [5241.0, 5242.0, 5243.0, 5244.0],       [5245.0, 5246.0, 5247.0, 5248.0],       [5249.0, 5250.0, 5251.0, 5252.0],       [5253.0, 5254.0, 5255.0, 5256.0]],      [[5257.0, 5258.0, 5259.0, 5260.0],       [5261.0, 5262.0, 5263.0, 5264.0],       [5265.0, 5266.0, 5267.0, 5268.0],       [5269.0, 5270.0, 5271.0, 5272.0],       [5273.0, 5274.0, 5275.0, 5276.0],       [5277.0, 5278.0, 5279.0, 5280.0]]],     [[[5281.0, 5282.0, 5283.0, 5284.0],       [5285.0, 5286.0, 5287.0, 5288.0],       [5289.0, 5290.0, 5291.0, 5292.0],       [5293.0, 5294.0, 5295.0, 5296.0],       [5297.0, 5298.0, 5299.0, 5300.0],       [5301.0, 5302.0, 5303.0, 5304.0]],      [[5305.0, 5306.0, 5307.0, 5308.0],       [5309.0, 5310.0, 5311.0, 5312.0],       [5313.0, 5314.0, 5315.0, 5316.0],       [5317.0, 5318.0, 5319.0, 5320.0],       [5321.0, 5322.0, 5323.0, 5324.0],       [5325.0, 5326.0, 5327.0, 5328.0]],      [[5329.0, 5330.0, 5331.0, 5332.0],       [5333.0, 5334.0, 5335.0, 5336.0],       [5337.0, 5338.0, 5339.0, 5340.0],       [5341.0, 5342.0, 5343.0, 5344.0],       [5345.0, 5346.0, 5347.0, 5348.0],       [5349.0, 5350.0, 5351.0, 5352.0]],      [[5353.0, 5354.0, 5355.0, 5356.0],       [5357.0, 5358.0, 5359.0, 5360.0],       [5361.0, 5362.0, 5363.0, 5364.0],       [5365.0, 5366.0, 5367.0, 5368.0],       [5369.0, 5370.0, 5371.0, 5372.0],       [5373.0, 5374.0, 5375.0, 5376.0]]]],    [[[[5377.0, 5378.0, 5379.0, 5380.0],       [5381.0, 5382.0, 5383.0, 5384.0],       [5385.0, 5386.0, 5387.0, 5388.0],       [5389.0, 5390.0, 5391.0, 5392.0],       [5393.0, 5394.0, 5395.0, 5396.0],       [5397.0, 5398.0, 5399.0, 5400.0]],      [[5401.0, 5402.0, 5403.0, 5404.0],       [5405.0, 5406.0, 5407.0, 5408.0],       [5409.0, 5410.0, 5411.0, 5412.0],       [5413.0, 5414.0, 5415.0, 5416.0],       [5417.0, 5418.0, 5419.0, 5420.0],       [5421.0, 5422.0, 5423.0, 5424.0]],      [[5425.0, 5426.0, 5427.0, 5428.0],       [5429.0, 5430.0, 5431.0, 5432.0],       [5433.0, 5434.0, 5435.0, 5436.0],       [5437.0, 5438.0, 5439.0, 5440.0],       [5441.0, 5442.0, 5443.0, 5444.0],       [5445.0, 5446.0, 5447.0, 5448.0]],      [[5449.0, 5450.0, 5451.0, 5452.0],       [5453.0, 5454.0, 5455.0, 5456.0],       [5457.0, 5458.0, 5459.0, 5460.0],       [5461.0, 5462.0, 5463.0, 5464.0],       [5465.0, 5466.0, 5467.0, 5468.0],       [5469.0, 5470.0, 5471.0, 5472.0]]],     [[[5473.0, 5474.0, 5475.0, 5476.0],       [5477.0, 5478.0, 5479.0, 5480.0],       [5481.0, 5482.0, 5483.0, 5484.0],       [5485.0, 5486.0, 5487.0, 5488.0],       [5489.0, 5490.0, 5491.0, 5492.0],       [5493.0, 5494.0, 5495.0, 5496.0]],      [[5497.0, 5498.0, 5499.0, 5500.0],       [5501.0, 5502.0, 5503.0, 5504.0],       [5505.0, 5506.0, 5507.0, 5508.0],       [5509.0, 5510.0, 5511.0, 5512.0],       [5513.0, 5514.0, 5515.0, 5516.0],       [5517.0, 5518.0, 5519.0, 5520.0]],      [[5521.0, 5522.0, 5523.0, 5524.0],       [5525.0, 5526.0, 5527.0, 5528.0],       [5529.0, 5530.0, 5531.0, 5532.0],       [5533.0, 5534.0, 5535.0, 5536.0],       [5537.0, 5538.0, 5539.0, 5540.0],       [5541.0, 5542.0, 5543.0, 5544.0]],      [[5545.0, 5546.0, 5547.0, 5548.0],       [5549.0, 5550.0, 5551.0, 5552.0],       [5553.0, 5554.0, 5555.0, 5556.0],       [5557.0, 5558.0, 5559.0, 5560.0],       [5561.0, 5562.0, 5563.0, 5564.0],       [5565.0, 5566.0, 5567.0, 5568.0]]]],    [[[[5569.0, 5570.0, 5571.0, 5572.0],       [5573.0, 5574.0, 5575.0, 5576.0],       [5577.0, 5578.0, 5579.0, 5580.0],       [5581.0, 5582.0, 5583.0, 5584.0],       [5585.0, 5586.0, 5587.0, 5588.0],       [5589.0, 5590.0, 5591.0, 5592.0]],      [[5593.0, 5594.0, 5595.0, 5596.0],       [5597.0, 5598.0, 5599.0, 5600.0],       [5601.0, 5602.0, 5603.0, 5604.0],       [5605.0, 5606.0, 5607.0, 5608.0],       [5609.0, 5610.0, 5611.0, 5612.0],       [5613.0, 5614.0, 5615.0, 5616.0]],      [[5617.0, 5618.0, 5619.0, 5620.0],       [5621.0, 5622.0, 5623.0, 5624.0],       [5625.0, 5626.0, 5627.0, 5628.0],       [5629.0, 5630.0, 5631.0, 5632.0],       [5633.0, 5634.0, 5635.0, 5636.0],       [5637.0, 5638.0, 5639.0, 5640.0]],      [[5641.0, 5642.0, 5643.0, 5644.0],       [5645.0, 5646.0, 5647.0, 5648.0],       [5649.0, 5650.0, 5651.0, 5652.0],       [5653.0, 5654.0, 5655.0, 5656.0],       [5657.0, 5658.0, 5659.0, 5660.0],       [5661.0, 5662.0, 5663.0, 5664.0]]],     [[[5665.0, 5666.0, 5667.0, 5668.0],       [5669.0, 5670.0, 5671.0, 5672.0],       [5673.0, 5674.0, 5675.0, 5676.0],       [5677.0, 5678.0, 5679.0, 5680.0],       [5681.0, 5682.0, 5683.0, 5684.0],       [5685.0, 5686.0, 5687.0, 5688.0]],      [[5689.0, 5690.0, 5691.0, 5692.0],       [5693.0, 5694.0, 5695.0, 5696.0],       [5697.0, 5698.0, 5699.0, 5700.0],       [5701.0, 5702.0, 5703.0, 5704.0],       [5705.0, 5706.0, 5707.0, 5708.0],       [5709.0, 5710.0, 5711.0, 5712.0]],      [[5713.0, 5714.0, 5715.0, 5716.0],       [5717.0, 5718.0, 5719.0, 5720.0],       [5721.0, 5722.0, 5723.0, 5724.0],       [5725.0, 5726.0, 5727.0, 5728.0],       [5729.0, 5730.0, 5731.0, 5732.0],       [5733.0, 5734.0, 5735.0, 5736.0]],      [[5737.0, 5738.0, 5739.0, 5740.0],       [5741.0, 5742.0, 5743.0, 5744.0],       [5745.0, 5746.0, 5747.0, 5748.0],       [5749.0, 5750.0, 5751.0, 5752.0],       [5753.0, 5754.0, 5755.0, 5756.0],       [5757.0, 5758.0, 5759.0, 5760.0]]]]],   [[[[[5761.0, 5762.0, 5763.0, 5764.0],       [5765.0, 5766.0, 5767.0, 5768.0],       [5769.0, 5770.0, 5771.0, 5772.0],       [5773.0, 5774.0, 5775.0, 5776.0],       [5777.0, 5778.0, 5779.0, 5780.0],       [5781.0, 5782.0, 5783.0, 5784.0]],      [[5785.0, 5786.0, 5787.0, 5788.0],       [5789.0, 5790.0, 5791.0, 5792.0],       [5793.0, 5794.0, 5795.0, 5796.0],       [5797.0, 5798.0, 5799.0, 5800.0],       [5801.0, 5802.0, 5803.0, 5804.0],       [5805.0, 5806.0, 5807.0, 5808.0]],      [[5809.0, 5810.0, 5811.0, 5812.0],       [5813.0, 5814.0, 5815.0, 5816.0],       [5817.0, 5818.0, 5819.0, 5820.0],       [5821.0, 5822.0, 5823.0, 5824.0],       [5825.0, 5826.0, 5827.0, 5828.0],       [5829.0, 5830.0, 5831.0, 5832.0]],      [[5833.0, 5834.0, 5835.0, 5836.0],       [5837.0, 5838.0, 5839.0, 5840.0],       [5841.0, 5842.0, 5843.0, 5844.0],       [5845.0, 5846.0, 5847.0, 5848.0],       [5849.0, 5850.0, 5851.0, 5852.0],       [5853.0, 5854.0, 5855.0, 5856.0]]],     [[[5857.0, 5858.0, 5859.0, 5860.0],       [5861.0, 5862.0, 5863.0, 5864.0],       [5865.0, 5866.0, 5867.0, 5868.0],       [5869.0, 5870.0, 5871.0, 5872.0],       [5873.0, 5874.0, 5875.0, 5876.0],       [5877.0, 5878.0, 5879.0, 5880.0]],      [[5881.0, 5882.0, 5883.0, 5884.0],       [5885.0, 5886.0, 5887.0, 5888.0],       [5889.0, 5890.0, 5891.0, 5892.0],       [5893.0, 5894.0, 5895.0, 5896.0],       [5897.0, 5898.0, 5899.0, 5900.0],       [5901.0, 5902.0, 5903.0, 5904.0]],      [[5905.0, 5906.0, 5907.0, 5908.0],       [5909.0, 5910.0, 5911.0, 5912.0],       [5913.0, 5914.0, 5915.0, 5916.0],       [5917.0, 5918.0, 5919.0, 5920.0],       [5921.0, 5922.0, 5923.0, 5924.0],       [5925.0, 5926.0, 5927.0, 5928.0]],      [[5929.0, 5930.0, 5931.0, 5932.0],       [5933.0, 5934.0, 5935.0, 5936.0],       [5937.0, 5938.0, 5939.0, 5940.0],       [5941.0, 5942.0, 5943.0, 5944.0],       [5945.0, 5946.0, 5947.0, 5948.0],       [5949.0, 5950.0, 5951.0, 5952.0]]]],    [[[[5953.0, 5954.0, 5955.0, 5956.0],       [5957.0, 5958.0, 5959.0, 5960.0],       [5961.0, 5962.0, 5963.0, 5964.0],       [5965.0, 5966.0, 5967.0, 5968.0],       [5969.0, 5970.0, 5971.0, 5972.0],       [5973.0, 5974.0, 5975.0, 5976.0]],      [[5977.0, 5978.0, 5979.0, 5980.0],       [5981.0, 5982.0, 5983.0, 5984.0],       [5985.0, 5986.0, 5987.0, 5988.0],       [5989.0, 5990.0, 5991.0, 5992.0],       [5993.0, 5994.0, 5995.0, 5996.0],       [5997.0, 5998.0, 5999.0, 6000.0]],      [[6001.0, 6002.0, 6003.0, 6004.0],       [6005.0, 6006.0, 6007.0, 6008.0],       [6009.0, 6010.0, 6011.0, 6012.0],       [6013.0, 6014.0, 6015.0, 6016.0],       [6017.0, 6018.0, 6019.0, 6020.0],       [6021.0, 6022.0, 6023.0, 6024.0]],      [[6025.0, 6026.0, 6027.0, 6028.0],       [6029.0, 6030.0, 6031.0, 6032.0],       [6033.0, 6034.0, 6035.0, 6036.0],       [6037.0, 6038.0, 6039.0, 6040.0],       [6041.0, 6042.0, 6043.0, 6044.0],       [6045.0, 6046.0, 6047.0, 6048.0]]],     [[[6049.0, 6050.0, 6051.0, 6052.0],       [6053.0, 6054.0, 6055.0, 6056.0],       [6057.0, 6058.0, 6059.0, 6060.0],       [6061.0, 6062.0, 6063.0, 6064.0],       [6065.0, 6066.0, 6067.0, 6068.0],       [6069.0, 6070.0, 6071.0, 6072.0]],      [[6073.0, 6074.0, 6075.0, 6076.0],       [6077.0, 6078.0, 6079.0, 6080.0],       [6081.0, 6082.0, 6083.0, 6084.0],       [6085.0, 6086.0, 6087.0, 6088.0],       [6089.0, 6090.0, 6091.0, 6092.0],       [6093.0, 6094.0, 6095.0, 6096.0]],      [[6097.0, 6098.0, 6099.0, 6100.0],       [6101.0, 6102.0, 6103.0, 6104.0],       [6105.0, 6106.0, 6107.0, 6108.0],       [6109.0, 6110.0, 6111.0, 6112.0],       [6113.0, 6114.0, 6115.0, 6116.0],       [6117.0, 6118.0, 6119.0, 6120.0]],      [[6121.0, 6122.0, 6123.0, 6124.0],       [6125.0, 6126.0, 6127.0, 6128.0],       [6129.0, 6130.0, 6131.0, 6132.0],       [6133.0, 6134.0, 6135.0, 6136.0],       [6137.0, 6138.0, 6139.0, 6140.0],       [6141.0, 6142.0, 6143.0, 6144.0]]]],    [[[[6145.0, 6146.0, 6147.0, 6148.0],       [6149.0, 6150.0, 6151.0, 6152.0],       [6153.0, 6154.0, 6155.0, 6156.0],       [6157.0, 6158.0, 6159.0, 6160.0],       [6161.0, 6162.0, 6163.0, 6164.0],       [6165.0, 6166.0, 6167.0, 6168.0]],      [[6169.0, 6170.0, 6171.0, 6172.0],       [6173.0, 6174.0, 6175.0, 6176.0],       [6177.0, 6178.0, 6179.0, 6180.0],       [6181.0, 6182.0, 6183.0, 6184.0],       [6185.0, 6186.0, 6187.0, 6188.0],       [6189.0, 6190.0, 6191.0, 6192.0]],      [[6193.0, 6194.0, 6195.0, 6196.0],       [6197.0, 6198.0, 6199.0, 6200.0],       [6201.0, 6202.0, 6203.0, 6204.0],       [6205.0, 6206.0, 6207.0, 6208.0],       [6209.0, 6210.0, 6211.0, 6212.0],       [6213.0, 6214.0, 6215.0, 6216.0]],      [[6217.0, 6218.0, 6219.0, 6220.0],       [6221.0, 6222.0, 6223.0, 6224.0],       [6225.0, 6226.0, 6227.0, 6228.0],       [6229.0, 6230.0, 6231.0, 6232.0],       [6233.0, 6234.0, 6235.0, 6236.0],       [6237.0, 6238.0, 6239.0, 6240.0]]],     [[[6241.0, 6242.0, 6243.0, 6244.0],       [6245.0, 6246.0, 6247.0, 6248.0],       [6249.0, 6250.0, 6251.0, 6252.0],       [6253.0, 6254.0, 6255.0, 6256.0],       [6257.0, 6258.0, 6259.0, 6260.0],       [6261.0, 6262.0, 6263.0, 6264.0]],      [[6265.0, 6266.0, 6267.0, 6268.0],       [6269.0, 6270.0, 6271.0, 6272.0],       [6273.0, 6274.0, 6275.0, 6276.0],       [6277.0, 6278.0, 6279.0, 6280.0],       [6281.0, 6282.0, 6283.0, 6284.0],       [6285.0, 6286.0, 6287.0, 6288.0]],      [[6289.0, 6290.0, 6291.0, 6292.0],       [6293.0, 6294.0, 6295.0, 6296.0],       [6297.0, 6298.0, 6299.0, 6300.0],       [6301.0, 6302.0, 6303.0, 6304.0],       [6305.0, 6306.0, 6307.0, 6308.0],       [6309.0, 6310.0, 6311.0, 6312.0]],      [[6313.0, 6314.0, 6315.0, 6316.0],       [6317.0, 6318.0, 6319.0, 6320.0],       [6321.0, 6322.0, 6323.0, 6324.0],       [6325.0, 6326.0, 6327.0, 6328.0],       [6329.0, 6330.0, 6331.0, 6332.0],       [6333.0, 6334.0, 6335.0, 6336.0]]]],    [[[[6337.0, 6338.0, 6339.0, 6340.0],       [6341.0, 6342.0, 6343.0, 6344.0],       [6345.0, 6346.0, 6347.0, 6348.0],       [6349.0, 6350.0, 6351.0, 6352.0],       [6353.0, 6354.0, 6355.0, 6356.0],       [6357.0, 6358.0, 6359.0, 6360.0]],      [[6361.0, 6362.0, 6363.0, 6364.0],       [6365.0, 6366.0, 6367.0, 6368.0],       [6369.0, 6370.0, 6371.0, 6372.0],       [6373.0, 6374.0, 6375.0, 6376.0],       [6377.0, 6378.0, 6379.0, 6380.0],       [6381.0, 6382.0, 6383.0, 6384.0]],      [[6385.0, 6386.0, 6387.0, 6388.0],       [6389.0, 6390.0, 6391.0, 6392.0],       [6393.0, 6394.0, 6395.0, 6396.0],       [6397.0, 6398.0, 6399.0, 6400.0],       [6401.0, 6402.0, 6403.0, 6404.0],       [6405.0, 6406.0, 6407.0, 6408.0]],      [[6409.0, 6410.0, 6411.0, 6412.0],       [6413.0, 6414.0, 6415.0, 6416.0],       [6417.0, 6418.0, 6419.0, 6420.0],       [6421.0, 6422.0, 6423.0, 6424.0],       [6425.0, 6426.0, 6427.0, 6428.0],       [6429.0, 6430.0, 6431.0, 6432.0]]],     [[[6433.0, 6434.0, 6435.0, 6436.0],       [6437.0, 6438.0, 6439.0, 6440.0],       [6441.0, 6442.0, 6443.0, 6444.0],       [6445.0, 6446.0, 6447.0, 6448.0],       [6449.0, 6450.0, 6451.0, 6452.0],       [6453.0, 6454.0, 6455.0, 6456.0]],      [[6457.0, 6458.0, 6459.0, 6460.0],       [6461.0, 6462.0, 6463.0, 6464.0],       [6465.0, 6466.0, 6467.0, 6468.0],       [6469.0, 6470.0, 6471.0, 6472.0],       [6473.0, 6474.0, 6475.0, 6476.0],       [6477.0, 6478.0, 6479.0, 6480.0]],      [[6481.0, 6482.0, 6483.0, 6484.0],       [6485.0, 6486.0, 6487.0, 6488.0],       [6489.0, 6490.0, 6491.0, 6492.0],       [6493.0, 6494.0, 6495.0, 6496.0],       [6497.0, 6498.0, 6499.0, 6500.0],       [6501.0, 6502.0, 6503.0, 6504.0]],      [[6505.0, 6506.0, 6507.0, 6508.0],       [6509.0, 6510.0, 6511.0, 6512.0],       [6513.0, 6514.0, 6515.0, 6516.0],       [6517.0, 6518.0, 6519.0, 6520.0],       [6521.0, 6522.0, 6523.0, 6524.0],       [6525.0, 6526.0, 6527.0, 6528.0]]]],    [[[[6529.0, 6530.0, 6531.0, 6532.0],       [6533.0, 6534.0, 6535.0, 6536.0],       [6537.0, 6538.0, 6539.0, 6540.0],       [6541.0, 6542.0, 6543.0, 6544.0],       [6545.0, 6546.0, 6547.0, 6548.0],       [6549.0, 6550.0, 6551.0, 6552.0]],      [[6553.0, 6554.0, 6555.0, 6556.0],       [6557.0, 6558.0, 6559.0, 6560.0],       [6561.0, 6562.0, 6563.0, 6564.0],       [6565.0, 6566.0, 6567.0, 6568.0],       [6569.0, 6570.0, 6571.0, 6572.0],       [6573.0, 6574.0, 6575.0, 6576.0]],      [[6577.0, 6578.0, 6579.0, 6580.0],       [6581.0, 6582.0, 6583.0, 6584.0],       [6585.0, 6586.0, 6587.0, 6588.0],       [6589.0, 6590.0, 6591.0, 6592.0],       [6593.0, 6594.0, 6595.0, 6596.0],       [6597.0, 6598.0, 6599.0, 6600.0]],      [[6601.0, 6602.0, 6603.0, 6604.0],       [6605.0, 6606.0, 6607.0, 6608.0],       [6609.0, 6610.0, 6611.0, 6612.0],       [6613.0, 6614.0, 6615.0, 6616.0],       [6617.0, 6618.0, 6619.0, 6620.0],       [6621.0, 6622.0, 6623.0, 6624.0]]],     [[[6625.0, 6626.0, 6627.0, 6628.0],       [6629.0, 6630.0, 6631.0, 6632.0],       [6633.0, 6634.0, 6635.0, 6636.0],       [6637.0, 6638.0, 6639.0, 6640.0],       [6641.0, 6642.0, 6643.0, 6644.0],       [6645.0, 6646.0, 6647.0, 6648.0]],      [[6649.0, 6650.0, 6651.0, 6652.0],       [6653.0, 6654.0, 6655.0, 6656.0],       [6657.0, 6658.0, 6659.0, 6660.0],       [6661.0, 6662.0, 6663.0, 6664.0],       [6665.0, 6666.0, 6667.0, 6668.0],       [6669.0, 6670.0, 6671.0, 6672.0]],      [[6673.0, 6674.0, 6675.0, 6676.0],       [6677.0, 6678.0, 6679.0, 6680.0],       [6681.0, 6682.0, 6683.0, 6684.0],       [6685.0, 6686.0, 6687.0, 6688.0],       [6689.0, 6690.0, 6691.0, 6692.0],       [6693.0, 6694.0, 6695.0, 6696.0]],      [[6697.0, 6698.0, 6699.0, 6700.0],       [6701.0, 6702.0, 6703.0, 6704.0],       [6705.0, 6706.0, 6707.0, 6708.0],       [6709.0, 6710.0, 6711.0, 6712.0],       [6713.0, 6714.0, 6715.0, 6716.0],       [6717.0, 6718.0, 6719.0, 6720.0]]]],    [[[[6721.0, 6722.0, 6723.0, 6724.0],       [6725.0, 6726.0, 6727.0, 6728.0],       [6729.0, 6730.0, 6731.0, 6732.0],       [6733.0, 6734.0, 6735.0, 6736.0],       [6737.0, 6738.0, 6739.0, 6740.0],       [6741.0, 6742.0, 6743.0, 6744.0]],      [[6745.0, 6746.0, 6747.0, 6748.0],       [6749.0, 6750.0, 6751.0, 6752.0],       [6753.0, 6754.0, 6755.0, 6756.0],       [6757.0, 6758.0, 6759.0, 6760.0],       [6761.0, 6762.0, 6763.0, 6764.0],       [6765.0, 6766.0, 6767.0, 6768.0]],      [[6769.0, 6770.0, 6771.0, 6772.0],       [6773.0, 6774.0, 6775.0, 6776.0],       [6777.0, 6778.0, 6779.0, 6780.0],       [6781.0, 6782.0, 6783.0, 6784.0],       [6785.0, 6786.0, 6787.0, 6788.0],       [6789.0, 6790.0, 6791.0, 6792.0]],      [[6793.0, 6794.0, 6795.0, 6796.0],       [6797.0, 6798.0, 6799.0, 6800.0],       [6801.0, 6802.0, 6803.0, 6804.0],       [6805.0, 6806.0, 6807.0, 6808.0],       [6809.0, 6810.0, 6811.0, 6812.0],       [6813.0, 6814.0, 6815.0, 6816.0]]],     [[[6817.0, 6818.0, 6819.0, 6820.0],       [6821.0, 6822.0, 6823.0, 6824.0],       [6825.0, 6826.0, 6827.0, 6828.0],       [6829.0, 6830.0, 6831.0, 6832.0],       [6833.0, 6834.0, 6835.0, 6836.0],       [6837.0, 6838.0, 6839.0, 6840.0]],      [[6841.0, 6842.0, 6843.0, 6844.0],       [6845.0, 6846.0, 6847.0, 6848.0],       [6849.0, 6850.0, 6851.0, 6852.0],       [6853.0, 6854.0, 6855.0, 6856.0],       [6857.0, 6858.0, 6859.0, 6860.0],       [6861.0, 6862.0, 6863.0, 6864.0]],      [[6865.0, 6866.0, 6867.0, 6868.0],       [6869.0, 6870.0, 6871.0, 6872.0],       [6873.0, 6874.0, 6875.0, 6876.0],       [6877.0, 6878.0, 6879.0, 6880.0],       [6881.0, 6882.0, 6883.0, 6884.0],       [6885.0, 6886.0, 6887.0, 6888.0]],      [[6889.0, 6890.0, 6891.0, 6892.0],       [6893.0, 6894.0, 6895.0, 6896.0],       [6897.0, 6898.0, 6899.0, 6900.0],       [6901.0, 6902.0, 6903.0, 6904.0],       [6905.0, 6906.0, 6907.0, 6908.0],       [6909.0, 6910.0, 6911.0, 6912.0]]]]],   [[[[[6913.0, 6914.0, 6915.0, 6916.0],       [6917.0, 6918.0, 6919.0, 6920.0],       [6921.0, 6922.0, 6923.0, 6924.0],       [6925.0, 6926.0, 6927.0, 6928.0],       [6929.0, 6930.0, 6931.0, 6932.0],       [6933.0, 6934.0, 6935.0, 6936.0]],      [[6937.0, 6938.0, 6939.0, 6940.0],       [6941.0, 6942.0, 6943.0, 6944.0],       [6945.0, 6946.0, 6947.0, 6948.0],       [6949.0, 6950.0, 6951.0, 6952.0],       [6953.0, 6954.0, 6955.0, 6956.0],       [6957.0, 6958.0, 6959.0, 6960.0]],      [[6961.0, 6962.0, 6963.0, 6964.0],       [6965.0, 6966.0, 6967.0, 6968.0],       [6969.0, 6970.0, 6971.0, 6972.0],       [6973.0, 6974.0, 6975.0, 6976.0],       [6977.0, 6978.0, 6979.0, 6980.0],       [6981.0, 6982.0, 6983.0, 6984.0]],      [[6985.0, 6986.0, 6987.0, 6988.0],       [6989.0, 6990.0, 6991.0, 6992.0],       [6993.0, 6994.0, 6995.0, 6996.0],       [6997.0, 6998.0, 6999.0, 7000.0],       [7001.0, 7002.0, 7003.0, 7004.0],       [7005.0, 7006.0, 7007.0, 7008.0]]],     [[[7009.0, 7010.0, 7011.0, 7012.0],       [7013.0, 7014.0, 7015.0, 7016.0],       [7017.0, 7018.0, 7019.0, 7020.0],       [7021.0, 7022.0, 7023.0, 7024.0],       [7025.0, 7026.0, 7027.0, 7028.0],       [7029.0, 7030.0, 7031.0, 7032.0]],      [[7033.0, 7034.0, 7035.0, 7036.0],       [7037.0, 7038.0, 7039.0, 7040.0],       [7041.0, 7042.0, 7043.0, 7044.0],       [7045.0, 7046.0, 7047.0, 7048.0],       [7049.0, 7050.0, 7051.0, 7052.0],       [7053.0, 7054.0, 7055.0, 7056.0]],      [[7057.0, 7058.0, 7059.0, 7060.0],       [7061.0, 7062.0, 7063.0, 7064.0],       [7065.0, 7066.0, 7067.0, 7068.0],       [7069.0, 7070.0, 7071.0, 7072.0],       [7073.0, 7074.0, 7075.0, 7076.0],       [7077.0, 7078.0, 7079.0, 7080.0]],      [[7081.0, 7082.0, 7083.0, 7084.0],       [7085.0, 7086.0, 7087.0, 7088.0],       [7089.0, 7090.0, 7091.0, 7092.0],       [7093.0, 7094.0, 7095.0, 7096.0],       [7097.0, 7098.0, 7099.0, 7100.0],       [7101.0, 7102.0, 7103.0, 7104.0]]]],    [[[[7105.0, 7106.0, 7107.0, 7108.0],       [7109.0, 7110.0, 7111.0, 7112.0],       [7113.0, 7114.0, 7115.0, 7116.0],       [7117.0, 7118.0, 7119.0, 7120.0],       [7121.0, 7122.0, 7123.0, 7124.0],       [7125.0, 7126.0, 7127.0, 7128.0]],      [[7129.0, 7130.0, 7131.0, 7132.0],       [7133.0, 7134.0, 7135.0, 7136.0],       [7137.0, 7138.0, 7139.0, 7140.0],       [7141.0, 7142.0, 7143.0, 7144.0],       [7145.0, 7146.0, 7147.0, 7148.0],       [7149.0, 7150.0, 7151.0, 7152.0]],      [[7153.0, 7154.0, 7155.0, 7156.0],       [7157.0, 7158.0, 7159.0, 7160.0],       [7161.0, 7162.0, 7163.0, 7164.0],       [7165.0, 7166.0, 7167.0, 7168.0],       [7169.0, 7170.0, 7171.0, 7172.0],       [7173.0, 7174.0, 7175.0, 7176.0]],      [[7177.0, 7178.0, 7179.0, 7180.0],       [7181.0, 7182.0, 7183.0, 7184.0],       [7185.0, 7186.0, 7187.0, 7188.0],       [7189.0, 7190.0, 7191.0, 7192.0],       [7193.0, 7194.0, 7195.0, 7196.0],       [7197.0, 7198.0, 7199.0, 7200.0]]],     [[[7201.0, 7202.0, 7203.0, 7204.0],       [7205.0, 7206.0, 7207.0, 7208.0],       [7209.0, 7210.0, 7211.0, 7212.0],       [7213.0, 7214.0, 7215.0, 7216.0],       [7217.0, 7218.0, 7219.0, 7220.0],       [7221.0, 7222.0, 7223.0, 7224.0]],      [[7225.0, 7226.0, 7227.0, 7228.0],       [7229.0, 7230.0, 7231.0, 7232.0],       [7233.0, 7234.0, 7235.0, 7236.0],       [7237.0, 7238.0, 7239.0, 7240.0],       [7241.0, 7242.0, 7243.0, 7244.0],       [7245.0, 7246.0, 7247.0, 7248.0]],      [[7249.0, 7250.0, 7251.0, 7252.0],       [7253.0, 7254.0, 7255.0, 7256.0],       [7257.0, 7258.0, 7259.0, 7260.0],       [7261.0, 7262.0, 7263.0, 7264.0],       [7265.0, 7266.0, 7267.0, 7268.0],       [7269.0, 7270.0, 7271.0, 7272.0]],      [[7273.0, 7274.0, 7275.0, 7276.0],       [7277.0, 7278.0, 7279.0, 7280.0],       [7281.0, 7282.0, 7283.0, 7284.0],       [7285.0, 7286.0, 7287.0, 7288.0],       [7289.0, 7290.0, 7291.0, 7292.0],       [7293.0, 7294.0, 7295.0, 7296.0]]]],    [[[[7297.0, 7298.0, 7299.0, 7300.0],       [7301.0, 7302.0, 7303.0, 7304.0],       [7305.0, 7306.0, 7307.0, 7308.0],       [7309.0, 7310.0, 7311.0, 7312.0],       [7313.0, 7314.0, 7315.0, 7316.0],       [7317.0, 7318.0, 7319.0, 7320.0]],      [[7321.0, 7322.0, 7323.0, 7324.0],       [7325.0, 7326.0, 7327.0, 7328.0],       [7329.0, 7330.0, 7331.0, 7332.0],       [7333.0, 7334.0, 7335.0, 7336.0],       [7337.0, 7338.0, 7339.0, 7340.0],       [7341.0, 7342.0, 7343.0, 7344.0]],      [[7345.0, 7346.0, 7347.0, 7348.0],       [7349.0, 7350.0, 7351.0, 7352.0],       [7353.0, 7354.0, 7355.0, 7356.0],       [7357.0, 7358.0, 7359.0, 7360.0],       [7361.0, 7362.0, 7363.0, 7364.0],       [7365.0, 7366.0, 7367.0, 7368.0]],      [[7369.0, 7370.0, 7371.0, 7372.0],       [7373.0, 7374.0, 7375.0, 7376.0],       [7377.0, 7378.0, 7379.0, 7380.0],       [7381.0, 7382.0, 7383.0, 7384.0],       [7385.0, 7386.0, 7387.0, 7388.0],       [7389.0, 7390.0, 7391.0, 7392.0]]],     [[[7393.0, 7394.0, 7395.0, 7396.0],       [7397.0, 7398.0, 7399.0, 7400.0],       [7401.0, 7402.0, 7403.0, 7404.0],       [7405.0, 7406.0, 7407.0, 7408.0],       [7409.0, 7410.0, 7411.0, 7412.0],       [7413.0, 7414.0, 7415.0, 7416.0]],      [[7417.0, 7418.0, 7419.0, 7420.0],       [7421.0, 7422.0, 7423.0, 7424.0],       [7425.0, 7426.0, 7427.0, 7428.0],       [7429.0, 7430.0, 7431.0, 7432.0],       [7433.0, 7434.0, 7435.0, 7436.0],       [7437.0, 7438.0, 7439.0, 7440.0]],      [[7441.0, 7442.0, 7443.0, 7444.0],       [7445.0, 7446.0, 7447.0, 7448.0],       [7449.0, 7450.0, 7451.0, 7452.0],       [7453.0, 7454.0, 7455.0, 7456.0],       [7457.0, 7458.0, 7459.0, 7460.0],       [7461.0, 7462.0, 7463.0, 7464.0]],      [[7465.0, 7466.0, 7467.0, 7468.0],       [7469.0, 7470.0, 7471.0, 7472.0],       [7473.0, 7474.0, 7475.0, 7476.0],       [7477.0, 7478.0, 7479.0, 7480.0],       [7481.0, 7482.0, 7483.0, 7484.0],       [7485.0, 7486.0, 7487.0, 7488.0]]]],    [[[[7489.0, 7490.0, 7491.0, 7492.0],       [7493.0, 7494.0, 7495.0, 7496.0],       [7497.0, 7498.0, 7499.0, 7500.0],       [7501.0, 7502.0, 7503.0, 7504.0],       [7505.0, 7506.0, 7507.0, 7508.0],       [7509.0, 7510.0, 7511.0, 7512.0]],      [[7513.0, 7514.0, 7515.0, 7516.0],       [7517.0, 7518.0, 7519.0, 7520.0],       [7521.0, 7522.0, 7523.0, 7524.0],       [7525.0, 7526.0, 7527.0, 7528.0],       [7529.0, 7530.0, 7531.0, 7532.0],       [7533.0, 7534.0, 7535.0, 7536.0]],      [[7537.0, 7538.0, 7539.0, 7540.0],       [7541.0, 7542.0, 7543.0, 7544.0],       [7545.0, 7546.0, 7547.0, 7548.0],       [7549.0, 7550.0, 7551.0, 7552.0],       [7553.0, 7554.0, 7555.0, 7556.0],       [7557.0, 7558.0, 7559.0, 7560.0]],      [[7561.0, 7562.0, 7563.0, 7564.0],       [7565.0, 7566.0, 7567.0, 7568.0],       [7569.0, 7570.0, 7571.0, 7572.0],       [7573.0, 7574.0, 7575.0, 7576.0],       [7577.0, 7578.0, 7579.0, 7580.0],       [7581.0, 7582.0, 7583.0, 7584.0]]],     [[[7585.0, 7586.0, 7587.0, 7588.0],       [7589.0, 7590.0, 7591.0, 7592.0],       [7593.0, 7594.0, 7595.0, 7596.0],       [7597.0, 7598.0, 7599.0, 7600.0],       [7601.0, 7602.0, 7603.0, 7604.0],       [7605.0, 7606.0, 7607.0, 7608.0]],      [[7609.0, 7610.0, 7611.0, 7612.0],       [7613.0, 7614.0, 7615.0, 7616.0],       [7617.0, 7618.0, 7619.0, 7620.0],       [7621.0, 7622.0, 7623.0, 7624.0],       [7625.0, 7626.0, 7627.0, 7628.0],       [7629.0, 7630.0, 7631.0, 7632.0]],      [[7633.0, 7634.0, 7635.0, 7636.0],       [7637.0, 7638.0, 7639.0, 7640.0],       [7641.0, 7642.0, 7643.0, 7644.0],       [7645.0, 7646.0, 7647.0, 7648.0],       [7649.0, 7650.0, 7651.0, 7652.0],       [7653.0, 7654.0, 7655.0, 7656.0]],      [[7657.0, 7658.0, 7659.0, 7660.0],       [7661.0, 7662.0, 7663.0, 7664.0],       [7665.0, 7666.0, 7667.0, 7668.0],       [7669.0, 7670.0, 7671.0, 7672.0],       [7673.0, 7674.0, 7675.0, 7676.0],       [7677.0, 7678.0, 7679.0, 7680.0]]]],    [[[[7681.0, 7682.0, 7683.0, 7684.0],       [7685.0, 7686.0, 7687.0, 7688.0],       [7689.0, 7690.0, 7691.0, 7692.0],       [7693.0, 7694.0, 7695.0, 7696.0],       [7697.0, 7698.0, 7699.0, 7700.0],       [7701.0, 7702.0, 7703.0, 7704.0]],      [[7705.0, 7706.0, 7707.0, 7708.0],       [7709.0, 7710.0, 7711.0, 7712.0],       [7713.0, 7714.0, 7715.0, 7716.0],       [7717.0, 7718.0, 7719.0, 7720.0],       [7721.0, 7722.0, 7723.0, 7724.0],       [7725.0, 7726.0, 7727.0, 7728.0]],      [[7729.0, 7730.0, 7731.0, 7732.0],       [7733.0, 7734.0, 7735.0, 7736.0],       [7737.0, 7738.0, 7739.0, 7740.0],       [7741.0, 7742.0, 7743.0, 7744.0],       [7745.0, 7746.0, 7747.0, 7748.0],       [7749.0, 7750.0, 7751.0, 7752.0]],      [[7753.0, 7754.0, 7755.0, 7756.0],       [7757.0, 7758.0, 7759.0, 7760.0],       [7761.0, 7762.0, 7763.0, 7764.0],       [7765.0, 7766.0, 7767.0, 7768.0],       [7769.0, 7770.0, 7771.0, 7772.0],       [7773.0, 7774.0, 7775.0, 7776.0]]],     [[[7777.0, 7778.0, 7779.0, 7780.0],       [7781.0, 7782.0, 7783.0, 7784.0],       [7785.0, 7786.0, 7787.0, 7788.0],       [7789.0, 7790.0, 7791.0, 7792.0],       [7793.0, 7794.0, 7795.0, 7796.0],       [7797.0, 7798.0, 7799.0, 7800.0]],      [[7801.0, 7802.0, 7803.0, 7804.0],       [7805.0, 7806.0, 7807.0, 7808.0],       [7809.0, 7810.0, 7811.0, 7812.0],       [7813.0, 7814.0, 7815.0, 7816.0],       [7817.0, 7818.0, 7819.0, 7820.0],       [7821.0, 7822.0, 7823.0, 7824.0]],      [[7825.0, 7826.0, 7827.0, 7828.0],       [7829.0, 7830.0, 7831.0, 7832.0],       [7833.0, 7834.0, 7835.0, 7836.0],       [7837.0, 7838.0, 7839.0, 7840.0],       [7841.0, 7842.0, 7843.0, 7844.0],       [7845.0, 7846.0, 7847.0, 7848.0]],      [[7849.0, 7850.0, 7851.0, 7852.0],       [7853.0, 7854.0, 7855.0, 7856.0],       [7857.0, 7858.0, 7859.0, 7860.0],       [7861.0, 7862.0, 7863.0, 7864.0],       [7865.0, 7866.0, 7867.0, 7868.0],       [7869.0, 7870.0, 7871.0, 7872.0]]]],    [[[[7873.0, 7874.0, 7875.0, 7876.0],       [7877.0, 7878.0, 7879.0, 7880.0],       [7881.0, 7882.0, 7883.0, 7884.0],       [7885.0, 7886.0, 7887.0, 7888.0],       [7889.0, 7890.0, 7891.0, 7892.0],       [7893.0, 7894.0, 7895.0, 7896.0]],      [[7897.0, 7898.0, 7899.0, 7900.0],       [7901.0, 7902.0, 7903.0, 7904.0],       [7905.0, 7906.0, 7907.0, 7908.0],       [7909.0, 7910.0, 7911.0, 7912.0],       [7913.0, 7914.0, 7915.0, 7916.0],       [7917.0, 7918.0, 7919.0, 7920.0]],      [[7921.0, 7922.0, 7923.0, 7924.0],       [7925.0, 7926.0, 7927.0, 7928.0],       [7929.0, 7930.0, 7931.0, 7932.0],       [7933.0, 7934.0, 7935.0, 7936.0],       [7937.0, 7938.0, 7939.0, 7940.0],       [7941.0, 7942.0, 7943.0, 7944.0]],      [[7945.0, 7946.0, 7947.0, 7948.0],       [7949.0, 7950.0, 7951.0, 7952.0],       [7953.0, 7954.0, 7955.0, 7956.0],       [7957.0, 7958.0, 7959.0, 7960.0],       [7961.0, 7962.0, 7963.0, 7964.0],       [7965.0, 7966.0, 7967.0, 7968.0]]],     [[[7969.0, 7970.0, 7971.0, 7972.0],       [7973.0, 7974.0, 7975.0, 7976.0],       [7977.0, 7978.0, 7979.0, 7980.0],       [7981.0, 7982.0, 7983.0, 7984.0],       [7985.0, 7986.0, 7987.0, 7988.0],       [7989.0, 7990.0, 7991.0, 7992.0]],      [[7993.0, 7994.0, 7995.0, 7996.0],       [7997.0, 7998.0, 7999.0, 8000.0],       [8001.0, 8002.0, 8003.0, 8004.0],       [8005.0, 8006.0, 8007.0, 8008.0],       [8009.0, 8010.0, 8011.0, 8012.0],       [8013.0, 8014.0, 8015.0, 8016.0]],      [[8017.0, 8018.0, 8019.0, 8020.0],       [8021.0, 8022.0, 8023.0, 8024.0],       [8025.0, 8026.0, 8027.0, 8028.0],       [8029.0, 8030.0, 8031.0, 8032.0],       [8033.0, 8034.0, 8035.0, 8036.0],       [8037.0, 8038.0, 8039.0, 8040.0]],      [[8041.0, 8042.0, 8043.0, 8044.0],       [8045.0, 8046.0, 8047.0, 8048.0],       [8049.0, 8050.0, 8051.0, 8052.0],       [8053.0, 8054.0, 8055.0, 8056.0],       [8057.0, 8058.0, 8059.0, 8060.0],       [8061.0, 8062.0, 8063.0, 8064.0]]]]]]] shape=[1, 7, 6, 2, 4, 6, 4], strides=[8064, 1152, 192, 96, 24, 4, 1], layout=C (0x1)), I32([1, 2, 2] shape=[3], strides=[1], layout=C | F (0x3)), I32([[2, 1],  [0, 2],  [3, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 538309877 2542581499 2686813102 1899512611 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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],       [16.0, 17.0, 18.0],       [19.0, 20.0, 21.0]]],     [[[22.0, 23.0, 24.0],       [25.0, 26.0, 27.0],       [28.0, 29.0, 30.0],       [31.0, 32.0, 33.0],       [34.0, 35.0, 36.0],       [37.0, 38.0, 39.0],       [40.0, 41.0, 42.0]]],     [[[43.0, 44.0, 45.0],       [46.0, 47.0, 48.0],       [49.0, 50.0, 51.0],       [52.0, 53.0, 54.0],       [55.0, 56.0, 57.0],       [58.0, 59.0, 60.0],       [61.0, 62.0, 63.0]]],     [[[64.0, 65.0, 66.0],       [67.0, 68.0, 69.0],       [70.0, 71.0, 72.0],       [73.0, 74.0, 75.0],       [76.0, 77.0, 78.0],       [79.0, 80.0, 81.0],       [82.0, 83.0, 84.0]]]],    [[[[85.0, 86.0, 87.0],       [88.0, 89.0, 90.0],       [91.0, 92.0, 93.0],       [94.0, 95.0, 96.0],       [97.0, 98.0, 99.0],       [100.0, 101.0, 102.0],       [103.0, 104.0, 105.0]]],     [[[106.0, 107.0, 108.0],       [109.0, 110.0, 111.0],       [112.0, 113.0, 114.0],       [115.0, 116.0, 117.0],       [118.0, 119.0, 120.0],       [121.0, 122.0, 123.0],       [124.0, 125.0, 126.0]]],     [[[127.0, 128.0, 129.0],       [130.0, 131.0, 132.0],       [133.0, 134.0, 135.0],       [136.0, 137.0, 138.0],       [139.0, 140.0, 141.0],       [142.0, 143.0, 144.0],       [145.0, 146.0, 147.0]]],     [[[148.0, 149.0, 150.0],       [151.0, 152.0, 153.0],       [154.0, 155.0, 156.0],       [157.0, 158.0, 159.0],       [160.0, 161.0, 162.0],       [163.0, 164.0, 165.0],       [166.0, 167.0, 168.0]]]],    [[[[169.0, 170.0, 171.0],       [172.0, 173.0, 174.0],       [175.0, 176.0, 177.0],       [178.0, 179.0, 180.0],       [181.0, 182.0, 183.0],       [184.0, 185.0, 186.0],       [187.0, 188.0, 189.0]]],     [[[190.0, 191.0, 192.0],       [193.0, 194.0, 195.0],       [196.0, 197.0, 198.0],       [199.0, 200.0, 201.0],       [202.0, 203.0, 204.0],       [205.0, 206.0, 207.0],       [208.0, 209.0, 210.0]]],     [[[211.0, 212.0, 213.0],       [214.0, 215.0, 216.0],       [217.0, 218.0, 219.0],       [220.0, 221.0, 222.0],       [223.0, 224.0, 225.0],       [226.0, 227.0, 228.0],       [229.0, 230.0, 231.0]]],     [[[232.0, 233.0, 234.0],       [235.0, 236.0, 237.0],       [238.0, 239.0, 240.0],       [241.0, 242.0, 243.0],       [244.0, 245.0, 246.0],       [247.0, 248.0, 249.0],       [250.0, 251.0, 252.0]]]],    [[[[253.0, 254.0, 255.0],       [256.0, 257.0, 258.0],       [259.0, 260.0, 261.0],       [262.0, 263.0, 264.0],       [265.0, 266.0, 267.0],       [268.0, 269.0, 270.0],       [271.0, 272.0, 273.0]]],     [[[274.0, 275.0, 276.0],       [277.0, 278.0, 279.0],       [280.0, 281.0, 282.0],       [283.0, 284.0, 285.0],       [286.0, 287.0, 288.0],       [289.0, 290.0, 291.0],       [292.0, 293.0, 294.0]]],     [[[295.0, 296.0, 297.0],       [298.0, 299.0, 300.0],       [301.0, 302.0, 303.0],       [304.0, 305.0, 306.0],       [307.0, 308.0, 309.0],       [310.0, 311.0, 312.0],       [313.0, 314.0, 315.0]]],     [[[316.0, 317.0, 318.0],       [319.0, 320.0, 321.0],       [322.0, 323.0, 324.0],       [325.0, 326.0, 327.0],       [328.0, 329.0, 330.0],       [331.0, 332.0, 333.0],       [334.0, 335.0, 336.0]]]],    [[[[337.0, 338.0, 339.0],       [340.0, 341.0, 342.0],       [343.0, 344.0, 345.0],       [346.0, 347.0, 348.0],       [349.0, 350.0, 351.0],       [352.0, 353.0, 354.0],       [355.0, 356.0, 357.0]]],     [[[358.0, 359.0, 360.0],       [361.0, 362.0, 363.0],       [364.0, 365.0, 366.0],       [367.0, 368.0, 369.0],       [370.0, 371.0, 372.0],       [373.0, 374.0, 375.0],       [376.0, 377.0, 378.0]]],     [[[379.0, 380.0, 381.0],       [382.0, 383.0, 384.0],       [385.0, 386.0, 387.0],       [388.0, 389.0, 390.0],       [391.0, 392.0, 393.0],       [394.0, 395.0, 396.0],       [397.0, 398.0, 399.0]]],     [[[400.0, 401.0, 402.0],       [403.0, 404.0, 405.0],       [406.0, 407.0, 408.0],       [409.0, 410.0, 411.0],       [412.0, 413.0, 414.0],       [415.0, 416.0, 417.0],       [418.0, 419.0, 420.0]]]]],   [[[[[421.0, 422.0, 423.0],       [424.0, 425.0, 426.0],       [427.0, 428.0, 429.0],       [430.0, 431.0, 432.0],       [433.0, 434.0, 435.0],       [436.0, 437.0, 438.0],       [439.0, 440.0, 441.0]]],     [[[442.0, 443.0, 444.0],       [445.0, 446.0, 447.0],       [448.0, 449.0, 450.0],       [451.0, 452.0, 453.0],       [454.0, 455.0, 456.0],       [457.0, 458.0, 459.0],       [460.0, 461.0, 462.0]]],     [[[463.0, 464.0, 465.0],       [466.0, 467.0, 468.0],       [469.0, 470.0, 471.0],       [472.0, 473.0, 474.0],       [475.0, 476.0, 477.0],       [478.0, 479.0, 480.0],       [481.0, 482.0, 483.0]]],     [[[484.0, 485.0, 486.0],       [487.0, 488.0, 489.0],       [490.0, 491.0, 492.0],       [493.0, 494.0, 495.0],       [496.0, 497.0, 498.0],       [499.0, 500.0, 501.0],       [502.0, 503.0, 504.0]]]],    [[[[505.0, 506.0, 507.0],       [508.0, 509.0, 510.0],       [511.0, 512.0, 513.0],       [514.0, 515.0, 516.0],       [517.0, 518.0, 519.0],       [520.0, 521.0, 522.0],       [523.0, 524.0, 525.0]]],     [[[526.0, 527.0, 528.0],       [529.0, 530.0, 531.0],       [532.0, 533.0, 534.0],       [535.0, 536.0, 537.0],       [538.0, 539.0, 540.0],       [541.0, 542.0, 543.0],       [544.0, 545.0, 546.0]]],     [[[547.0, 548.0, 549.0],       [550.0, 551.0, 552.0],       [553.0, 554.0, 555.0],       [556.0, 557.0, 558.0],       [559.0, 560.0, 561.0],       [562.0, 563.0, 564.0],       [565.0, 566.0, 567.0]]],     [[[568.0, 569.0, 570.0],       [571.0, 572.0, 573.0],       [574.0, 575.0, 576.0],       [577.0, 578.0, 579.0],       [580.0, 581.0, 582.0],       [583.0, 584.0, 585.0],       [586.0, 587.0, 588.0]]]],    [[[[589.0, 590.0, 591.0],       [592.0, 593.0, 594.0],       [595.0, 596.0, 597.0],       [598.0, 599.0, 600.0],       [601.0, 602.0, 603.0],       [604.0, 605.0, 606.0],       [607.0, 608.0, 609.0]]],     [[[610.0, 611.0, 612.0],       [613.0, 614.0, 615.0],       [616.0, 617.0, 618.0],       [619.0, 620.0, 621.0],       [622.0, 623.0, 624.0],       [625.0, 626.0, 627.0],       [628.0, 629.0, 630.0]]],     [[[631.0, 632.0, 633.0],       [634.0, 635.0, 636.0],       [637.0, 638.0, 639.0],       [640.0, 641.0, 642.0],       [643.0, 644.0, 645.0],       [646.0, 647.0, 648.0],       [649.0, 650.0, 651.0]]],     [[[652.0, 653.0, 654.0],       [655.0, 656.0, 657.0],       [658.0, 659.0, 660.0],       [661.0, 662.0, 663.0],       [664.0, 665.0, 666.0],       [667.0, 668.0, 669.0],       [670.0, 671.0, 672.0]]]],    [[[[673.0, 674.0, 675.0],       [676.0, 677.0, 678.0],       [679.0, 680.0, 681.0],       [682.0, 683.0, 684.0],       [685.0, 686.0, 687.0],       [688.0, 689.0, 690.0],       [691.0, 692.0, 693.0]]],     [[[694.0, 695.0, 696.0],       [697.0, 698.0, 699.0],       [700.0, 701.0, 702.0],       [703.0, 704.0, 705.0],       [706.0, 707.0, 708.0],       [709.0, 710.0, 711.0],       [712.0, 713.0, 714.0]]],     [[[715.0, 716.0, 717.0],       [718.0, 719.0, 720.0],       [721.0, 722.0, 723.0],       [724.0, 725.0, 726.0],       [727.0, 728.0, 729.0],       [730.0, 731.0, 732.0],       [733.0, 734.0, 735.0]]],     [[[736.0, 737.0, 738.0],       [739.0, 740.0, 741.0],       [742.0, 743.0, 744.0],       [745.0, 746.0, 747.0],       [748.0, 749.0, 750.0],       [751.0, 752.0, 753.0],       [754.0, 755.0, 756.0]]]],    [[[[757.0, 758.0, 759.0],       [760.0, 761.0, 762.0],       [763.0, 764.0, 765.0],       [766.0, 767.0, 768.0],       [769.0, 770.0, 771.0],       [772.0, 773.0, 774.0],       [775.0, 776.0, 777.0]]],     [[[778.0, 779.0, 780.0],       [781.0, 782.0, 783.0],       [784.0, 785.0, 786.0],       [787.0, 788.0, 789.0],       [790.0, 791.0, 792.0],       [793.0, 794.0, 795.0],       [796.0, 797.0, 798.0]]],     [[[799.0, 800.0, 801.0],       [802.0, 803.0, 804.0],       [805.0, 806.0, 807.0],       [808.0, 809.0, 810.0],       [811.0, 812.0, 813.0],       [814.0, 815.0, 816.0],       [817.0, 818.0, 819.0]]],     [[[820.0, 821.0, 822.0],       [823.0, 824.0, 825.0],       [826.0, 827.0, 828.0],       [829.0, 830.0, 831.0],       [832.0, 833.0, 834.0],       [835.0, 836.0, 837.0],       [838.0, 839.0, 840.0]]]]],   [[[[[841.0, 842.0, 843.0],       [844.0, 845.0, 846.0],       [847.0, 848.0, 849.0],       [850.0, 851.0, 852.0],       [853.0, 854.0, 855.0],       [856.0, 857.0, 858.0],       [859.0, 860.0, 861.0]]],     [[[862.0, 863.0, 864.0],       [865.0, 866.0, 867.0],       [868.0, 869.0, 870.0],       [871.0, 872.0, 873.0],       [874.0, 875.0, 876.0],       [877.0, 878.0, 879.0],       [880.0, 881.0, 882.0]]],     [[[883.0, 884.0, 885.0],       [886.0, 887.0, 888.0],       [889.0, 890.0, 891.0],       [892.0, 893.0, 894.0],       [895.0, 896.0, 897.0],       [898.0, 899.0, 900.0],       [901.0, 902.0, 903.0]]],     [[[904.0, 905.0, 906.0],       [907.0, 908.0, 909.0],       [910.0, 911.0, 912.0],       [913.0, 914.0, 915.0],       [916.0, 917.0, 918.0],       [919.0, 920.0, 921.0],       [922.0, 923.0, 924.0]]]],    [[[[925.0, 926.0, 927.0],       [928.0, 929.0, 930.0],       [931.0, 932.0, 933.0],       [934.0, 935.0, 936.0],       [937.0, 938.0, 939.0],       [940.0, 941.0, 942.0],       [943.0, 944.0, 945.0]]],     [[[946.0, 947.0, 948.0],       [949.0, 950.0, 951.0],       [952.0, 953.0, 954.0],       [955.0, 956.0, 957.0],       [958.0, 959.0, 960.0],       [961.0, 962.0, 963.0],       [964.0, 965.0, 966.0]]],     [[[967.0, 968.0, 969.0],       [970.0, 971.0, 972.0],       [973.0, 974.0, 975.0],       [976.0, 977.0, 978.0],       [979.0, 980.0, 981.0],       [982.0, 983.0, 984.0],       [985.0, 986.0, 987.0]]],     [[[988.0, 989.0, 990.0],       [991.0, 992.0, 993.0],       [994.0, 995.0, 996.0],       [997.0, 998.0, 999.0],       [1000.0, 1001.0, 1002.0],       [1003.0, 1004.0, 1005.0],       [1006.0, 1007.0, 1008.0]]]],    [[[[1009.0, 1010.0, 1011.0],       [1012.0, 1013.0, 1014.0],       [1015.0, 1016.0, 1017.0],       [1018.0, 1019.0, 1020.0],       [1021.0, 1022.0, 1023.0],       [1024.0, 1025.0, 1026.0],       [1027.0, 1028.0, 1029.0]]],     [[[1030.0, 1031.0, 1032.0],       [1033.0, 1034.0, 1035.0],       [1036.0, 1037.0, 1038.0],       [1039.0, 1040.0, 1041.0],       [1042.0, 1043.0, 1044.0],       [1045.0, 1046.0, 1047.0],       [1048.0, 1049.0, 1050.0]]],     [[[1051.0, 1052.0, 1053.0],       [1054.0, 1055.0, 1056.0],       [1057.0, 1058.0, 1059.0],       [1060.0, 1061.0, 1062.0],       [1063.0, 1064.0, 1065.0],       [1066.0, 1067.0, 1068.0],       [1069.0, 1070.0, 1071.0]]],     [[[1072.0, 1073.0, 1074.0],       [1075.0, 1076.0, 1077.0],       [1078.0, 1079.0, 1080.0],       [1081.0, 1082.0, 1083.0],       [1084.0, 1085.0, 1086.0],       [1087.0, 1088.0, 1089.0],       [1090.0, 1091.0, 1092.0]]]],    [[[[1093.0, 1094.0, 1095.0],       [1096.0, 1097.0, 1098.0],       [1099.0, 1100.0, 1101.0],       [1102.0, 1103.0, 1104.0],       [1105.0, 1106.0, 1107.0],       [1108.0, 1109.0, 1110.0],       [1111.0, 1112.0, 1113.0]]],     [[[1114.0, 1115.0, 1116.0],       [1117.0, 1118.0, 1119.0],       [1120.0, 1121.0, 1122.0],       [1123.0, 1124.0, 1125.0],       [1126.0, 1127.0, 1128.0],       [1129.0, 1130.0, 1131.0],       [1132.0, 1133.0, 1134.0]]],     [[[1135.0, 1136.0, 1137.0],       [1138.0, 1139.0, 1140.0],       [1141.0, 1142.0, 1143.0],       [1144.0, 1145.0, 1146.0],       [1147.0, 1148.0, 1149.0],       [1150.0, 1151.0, 1152.0],       [1153.0, 1154.0, 1155.0]]],     [[[1156.0, 1157.0, 1158.0],       [1159.0, 1160.0, 1161.0],       [1162.0, 1163.0, 1164.0],       [1165.0, 1166.0, 1167.0],       [1168.0, 1169.0, 1170.0],       [1171.0, 1172.0, 1173.0],       [1174.0, 1175.0, 1176.0]]]],    [[[[1177.0, 1178.0, 1179.0],       [1180.0, 1181.0, 1182.0],       [1183.0, 1184.0, 1185.0],       [1186.0, 1187.0, 1188.0],       [1189.0, 1190.0, 1191.0],       [1192.0, 1193.0, 1194.0],       [1195.0, 1196.0, 1197.0]]],     [[[1198.0, 1199.0, 1200.0],       [1201.0, 1202.0, 1203.0],       [1204.0, 1205.0, 1206.0],       [1207.0, 1208.0, 1209.0],       [1210.0, 1211.0, 1212.0],       [1213.0, 1214.0, 1215.0],       [1216.0, 1217.0, 1218.0]]],     [[[1219.0, 1220.0, 1221.0],       [1222.0, 1223.0, 1224.0],       [1225.0, 1226.0, 1227.0],       [1228.0, 1229.0, 1230.0],       [1231.0, 1232.0, 1233.0],       [1234.0, 1235.0, 1236.0],       [1237.0, 1238.0, 1239.0]]],     [[[1240.0, 1241.0, 1242.0],       [1243.0, 1244.0, 1245.0],       [1246.0, 1247.0, 1248.0],       [1249.0, 1250.0, 1251.0],       [1252.0, 1253.0, 1254.0],       [1255.0, 1256.0, 1257.0],       [1258.0, 1259.0, 1260.0]]]]]],  [[[[[[1261.0, 1262.0, 1263.0],       [1264.0, 1265.0, 1266.0],       [1267.0, 1268.0, 1269.0],       [1270.0, 1271.0, 1272.0],       [1273.0, 1274.0, 1275.0],       [1276.0, 1277.0, 1278.0],       [1279.0, 1280.0, 1281.0]]],     [[[1282.0, 1283.0, 1284.0],       [1285.0, 1286.0, 1287.0],       [1288.0, 1289.0, 1290.0],       [1291.0, 1292.0, 1293.0],       [1294.0, 1295.0, 1296.0],       [1297.0, 1298.0, 1299.0],       [1300.0, 1301.0, 1302.0]]],     [[[1303.0, 1304.0, 1305.0],       [1306.0, 1307.0, 1308.0],       [1309.0, 1310.0, 1311.0],       [1312.0, 1313.0, 1314.0],       [1315.0, 1316.0, 1317.0],       [1318.0, 1319.0, 1320.0],       [1321.0, 1322.0, 1323.0]]],     [[[1324.0, 1325.0, 1326.0],       [1327.0, 1328.0, 1329.0],       [1330.0, 1331.0, 1332.0],       [1333.0, 1334.0, 1335.0],       [1336.0, 1337.0, 1338.0],       [1339.0, 1340.0, 1341.0],       [1342.0, 1343.0, 1344.0]]]],    [[[[1345.0, 1346.0, 1347.0],       [1348.0, 1349.0, 1350.0],       [1351.0, 1352.0, 1353.0],       [1354.0, 1355.0, 1356.0],       [1357.0, 1358.0, 1359.0],       [1360.0, 1361.0, 1362.0],       [1363.0, 1364.0, 1365.0]]],     [[[1366.0, 1367.0, 1368.0],       [1369.0, 1370.0, 1371.0],       [1372.0, 1373.0, 1374.0],       [1375.0, 1376.0, 1377.0],       [1378.0, 1379.0, 1380.0],       [1381.0, 1382.0, 1383.0],       [1384.0, 1385.0, 1386.0]]],     [[[1387.0, 1388.0, 1389.0],       [1390.0, 1391.0, 1392.0],       [1393.0, 1394.0, 1395.0],       [1396.0, 1397.0, 1398.0],       [1399.0, 1400.0, 1401.0],       [1402.0, 1403.0, 1404.0],       [1405.0, 1406.0, 1407.0]]],     [[[1408.0, 1409.0, 1410.0],       [1411.0, 1412.0, 1413.0],       [1414.0, 1415.0, 1416.0],       [1417.0, 1418.0, 1419.0],       [1420.0, 1421.0, 1422.0],       [1423.0, 1424.0, 1425.0],       [1426.0, 1427.0, 1428.0]]]],    [[[[1429.0, 1430.0, 1431.0],       [1432.0, 1433.0, 1434.0],       [1435.0, 1436.0, 1437.0],       [1438.0, 1439.0, 1440.0],       [1441.0, 1442.0, 1443.0],       [1444.0, 1445.0, 1446.0],       [1447.0, 1448.0, 1449.0]]],     [[[1450.0, 1451.0, 1452.0],       [1453.0, 1454.0, 1455.0],       [1456.0, 1457.0, 1458.0],       [1459.0, 1460.0, 1461.0],       [1462.0, 1463.0, 1464.0],       [1465.0, 1466.0, 1467.0],       [1468.0, 1469.0, 1470.0]]],     [[[1471.0, 1472.0, 1473.0],       [1474.0, 1475.0, 1476.0],       [1477.0, 1478.0, 1479.0],       [1480.0, 1481.0, 1482.0],       [1483.0, 1484.0, 1485.0],       [1486.0, 1487.0, 1488.0],       [1489.0, 1490.0, 1491.0]]],     [[[1492.0, 1493.0, 1494.0],       [1495.0, 1496.0, 1497.0],       [1498.0, 1499.0, 1500.0],       [1501.0, 1502.0, 1503.0],       [1504.0, 1505.0, 1506.0],       [1507.0, 1508.0, 1509.0],       [1510.0, 1511.0, 1512.0]]]],    [[[[1513.0, 1514.0, 1515.0],       [1516.0, 1517.0, 1518.0],       [1519.0, 1520.0, 1521.0],       [1522.0, 1523.0, 1524.0],       [1525.0, 1526.0, 1527.0],       [1528.0, 1529.0, 1530.0],       [1531.0, 1532.0, 1533.0]]],     [[[1534.0, 1535.0, 1536.0],       [1537.0, 1538.0, 1539.0],       [1540.0, 1541.0, 1542.0],       [1543.0, 1544.0, 1545.0],       [1546.0, 1547.0, 1548.0],       [1549.0, 1550.0, 1551.0],       [1552.0, 1553.0, 1554.0]]],     [[[1555.0, 1556.0, 1557.0],       [1558.0, 1559.0, 1560.0],       [1561.0, 1562.0, 1563.0],       [1564.0, 1565.0, 1566.0],       [1567.0, 1568.0, 1569.0],       [1570.0, 1571.0, 1572.0],       [1573.0, 1574.0, 1575.0]]],     [[[1576.0, 1577.0, 1578.0],       [1579.0, 1580.0, 1581.0],       [1582.0, 1583.0, 1584.0],       [1585.0, 1586.0, 1587.0],       [1588.0, 1589.0, 1590.0],       [1591.0, 1592.0, 1593.0],       [1594.0, 1595.0, 1596.0]]]],    [[[[1597.0, 1598.0, 1599.0],       [1600.0, 1601.0, 1602.0],       [1603.0, 1604.0, 1605.0],       [1606.0, 1607.0, 1608.0],       [1609.0, 1610.0, 1611.0],       [1612.0, 1613.0, 1614.0],       [1615.0, 1616.0, 1617.0]]],     [[[1618.0, 1619.0, 1620.0],       [1621.0, 1622.0, 1623.0],       [1624.0, 1625.0, 1626.0],       [1627.0, 1628.0, 1629.0],       [1630.0, 1631.0, 1632.0],       [1633.0, 1634.0, 1635.0],       [1636.0, 1637.0, 1638.0]]],     [[[1639.0, 1640.0, 1641.0],       [1642.0, 1643.0, 1644.0],       [1645.0, 1646.0, 1647.0],       [1648.0, 1649.0, 1650.0],       [1651.0, 1652.0, 1653.0],       [1654.0, 1655.0, 1656.0],       [1657.0, 1658.0, 1659.0]]],     [[[1660.0, 1661.0, 1662.0],       [1663.0, 1664.0, 1665.0],       [1666.0, 1667.0, 1668.0],       [1669.0, 1670.0, 1671.0],       [1672.0, 1673.0, 1674.0],       [1675.0, 1676.0, 1677.0],       [1678.0, 1679.0, 1680.0]]]]],   [[[[[1681.0, 1682.0, 1683.0],       [1684.0, 1685.0, 1686.0],       [1687.0, 1688.0, 1689.0],       [1690.0, 1691.0, 1692.0],       [1693.0, 1694.0, 1695.0],       [1696.0, 1697.0, 1698.0],       [1699.0, 1700.0, 1701.0]]],     [[[1702.0, 1703.0, 1704.0],       [1705.0, 1706.0, 1707.0],       [1708.0, 1709.0, 1710.0],       [1711.0, 1712.0, 1713.0],       [1714.0, 1715.0, 1716.0],       [1717.0, 1718.0, 1719.0],       [1720.0, 1721.0, 1722.0]]],     [[[1723.0, 1724.0, 1725.0],       [1726.0, 1727.0, 1728.0],       [1729.0, 1730.0, 1731.0],       [1732.0, 1733.0, 1734.0],       [1735.0, 1736.0, 1737.0],       [1738.0, 1739.0, 1740.0],       [1741.0, 1742.0, 1743.0]]],     [[[1744.0, 1745.0, 1746.0],       [1747.0, 1748.0, 1749.0],       [1750.0, 1751.0, 1752.0],       [1753.0, 1754.0, 1755.0],       [1756.0, 1757.0, 1758.0],       [1759.0, 1760.0, 1761.0],       [1762.0, 1763.0, 1764.0]]]],    [[[[1765.0, 1766.0, 1767.0],       [1768.0, 1769.0, 1770.0],       [1771.0, 1772.0, 1773.0],       [1774.0, 1775.0, 1776.0],       [1777.0, 1778.0, 1779.0],       [1780.0, 1781.0, 1782.0],       [1783.0, 1784.0, 1785.0]]],     [[[1786.0, 1787.0, 1788.0],       [1789.0, 1790.0, 1791.0],       [1792.0, 1793.0, 1794.0],       [1795.0, 1796.0, 1797.0],       [1798.0, 1799.0, 1800.0],       [1801.0, 1802.0, 1803.0],       [1804.0, 1805.0, 1806.0]]],     [[[1807.0, 1808.0, 1809.0],       [1810.0, 1811.0, 1812.0],       [1813.0, 1814.0, 1815.0],       [1816.0, 1817.0, 1818.0],       [1819.0, 1820.0, 1821.0],       [1822.0, 1823.0, 1824.0],       [1825.0, 1826.0, 1827.0]]],     [[[1828.0, 1829.0, 1830.0],       [1831.0, 1832.0, 1833.0],       [1834.0, 1835.0, 1836.0],       [1837.0, 1838.0, 1839.0],       [1840.0, 1841.0, 1842.0],       [1843.0, 1844.0, 1845.0],       [1846.0, 1847.0, 1848.0]]]],    [[[[1849.0, 1850.0, 1851.0],       [1852.0, 1853.0, 1854.0],       [1855.0, 1856.0, 1857.0],       [1858.0, 1859.0, 1860.0],       [1861.0, 1862.0, 1863.0],       [1864.0, 1865.0, 1866.0],       [1867.0, 1868.0, 1869.0]]],     [[[1870.0, 1871.0, 1872.0],       [1873.0, 1874.0, 1875.0],       [1876.0, 1877.0, 1878.0],       [1879.0, 1880.0, 1881.0],       [1882.0, 1883.0, 1884.0],       [1885.0, 1886.0, 1887.0],       [1888.0, 1889.0, 1890.0]]],     [[[1891.0, 1892.0, 1893.0],       [1894.0, 1895.0, 1896.0],       [1897.0, 1898.0, 1899.0],       [1900.0, 1901.0, 1902.0],       [1903.0, 1904.0, 1905.0],       [1906.0, 1907.0, 1908.0],       [1909.0, 1910.0, 1911.0]]],     [[[1912.0, 1913.0, 1914.0],       [1915.0, 1916.0, 1917.0],       [1918.0, 1919.0, 1920.0],       [1921.0, 1922.0, 1923.0],       [1924.0, 1925.0, 1926.0],       [1927.0, 1928.0, 1929.0],       [1930.0, 1931.0, 1932.0]]]],    [[[[1933.0, 1934.0, 1935.0],       [1936.0, 1937.0, 1938.0],       [1939.0, 1940.0, 1941.0],       [1942.0, 1943.0, 1944.0],       [1945.0, 1946.0, 1947.0],       [1948.0, 1949.0, 1950.0],       [1951.0, 1952.0, 1953.0]]],     [[[1954.0, 1955.0, 1956.0],       [1957.0, 1958.0, 1959.0],       [1960.0, 1961.0, 1962.0],       [1963.0, 1964.0, 1965.0],       [1966.0, 1967.0, 1968.0],       [1969.0, 1970.0, 1971.0],       [1972.0, 1973.0, 1974.0]]],     [[[1975.0, 1976.0, 1977.0],       [1978.0, 1979.0, 1980.0],       [1981.0, 1982.0, 1983.0],       [1984.0, 1985.0, 1986.0],       [1987.0, 1988.0, 1989.0],       [1990.0, 1991.0, 1992.0],       [1993.0, 1994.0, 1995.0]]],     [[[1996.0, 1997.0, 1998.0],       [1999.0, 2000.0, 2001.0],       [2002.0, 2003.0, 2004.0],       [2005.0, 2006.0, 2007.0],       [2008.0, 2009.0, 2010.0],       [2011.0, 2012.0, 2013.0],       [2014.0, 2015.0, 2016.0]]]],    [[[[2017.0, 2018.0, 2019.0],       [2020.0, 2021.0, 2022.0],       [2023.0, 2024.0, 2025.0],       [2026.0, 2027.0, 2028.0],       [2029.0, 2030.0, 2031.0],       [2032.0, 2033.0, 2034.0],       [2035.0, 2036.0, 2037.0]]],     [[[2038.0, 2039.0, 2040.0],       [2041.0, 2042.0, 2043.0],       [2044.0, 2045.0, 2046.0],       [2047.0, 2048.0, 2049.0],       [2050.0, 2051.0, 2052.0],       [2053.0, 2054.0, 2055.0],       [2056.0, 2057.0, 2058.0]]],     [[[2059.0, 2060.0, 2061.0],       [2062.0, 2063.0, 2064.0],       [2065.0, 2066.0, 2067.0],       [2068.0, 2069.0, 2070.0],       [2071.0, 2072.0, 2073.0],       [2074.0, 2075.0, 2076.0],       [2077.0, 2078.0, 2079.0]]],     [[[2080.0, 2081.0, 2082.0],       [2083.0, 2084.0, 2085.0],       [2086.0, 2087.0, 2088.0],       [2089.0, 2090.0, 2091.0],       [2092.0, 2093.0, 2094.0],       [2095.0, 2096.0, 2097.0],       [2098.0, 2099.0, 2100.0]]]]],   [[[[[2101.0, 2102.0, 2103.0],       [2104.0, 2105.0, 2106.0],       [2107.0, 2108.0, 2109.0],       [2110.0, 2111.0, 2112.0],       [2113.0, 2114.0, 2115.0],       [2116.0, 2117.0, 2118.0],       [2119.0, 2120.0, 2121.0]]],     [[[2122.0, 2123.0, 2124.0],       [2125.0, 2126.0, 2127.0],       [2128.0, 2129.0, 2130.0],       [2131.0, 2132.0, 2133.0],       [2134.0, 2135.0, 2136.0],       [2137.0, 2138.0, 2139.0],       [2140.0, 2141.0, 2142.0]]],     [[[2143.0, 2144.0, 2145.0],       [2146.0, 2147.0, 2148.0],       [2149.0, 2150.0, 2151.0],       [2152.0, 2153.0, 2154.0],       [2155.0, 2156.0, 2157.0],       [2158.0, 2159.0, 2160.0],       [2161.0, 2162.0, 2163.0]]],     [[[2164.0, 2165.0, 2166.0],       [2167.0, 2168.0, 2169.0],       [2170.0, 2171.0, 2172.0],       [2173.0, 2174.0, 2175.0],       [2176.0, 2177.0, 2178.0],       [2179.0, 2180.0, 2181.0],       [2182.0, 2183.0, 2184.0]]]],    [[[[2185.0, 2186.0, 2187.0],       [2188.0, 2189.0, 2190.0],       [2191.0, 2192.0, 2193.0],       [2194.0, 2195.0, 2196.0],       [2197.0, 2198.0, 2199.0],       [2200.0, 2201.0, 2202.0],       [2203.0, 2204.0, 2205.0]]],     [[[2206.0, 2207.0, 2208.0],       [2209.0, 2210.0, 2211.0],       [2212.0, 2213.0, 2214.0],       [2215.0, 2216.0, 2217.0],       [2218.0, 2219.0, 2220.0],       [2221.0, 2222.0, 2223.0],       [2224.0, 2225.0, 2226.0]]],     [[[2227.0, 2228.0, 2229.0],       [2230.0, 2231.0, 2232.0],       [2233.0, 2234.0, 2235.0],       [2236.0, 2237.0, 2238.0],       [2239.0, 2240.0, 2241.0],       [2242.0, 2243.0, 2244.0],       [2245.0, 2246.0, 2247.0]]],     [[[2248.0, 2249.0, 2250.0],       [2251.0, 2252.0, 2253.0],       [2254.0, 2255.0, 2256.0],       [2257.0, 2258.0, 2259.0],       [2260.0, 2261.0, 2262.0],       [2263.0, 2264.0, 2265.0],       [2266.0, 2267.0, 2268.0]]]],    [[[[2269.0, 2270.0, 2271.0],       [2272.0, 2273.0, 2274.0],       [2275.0, 2276.0, 2277.0],       [2278.0, 2279.0, 2280.0],       [2281.0, 2282.0, 2283.0],       [2284.0, 2285.0, 2286.0],       [2287.0, 2288.0, 2289.0]]],     [[[2290.0, 2291.0, 2292.0],       [2293.0, 2294.0, 2295.0],       [2296.0, 2297.0, 2298.0],       [2299.0, 2300.0, 2301.0],       [2302.0, 2303.0, 2304.0],       [2305.0, 2306.0, 2307.0],       [2308.0, 2309.0, 2310.0]]],     [[[2311.0, 2312.0, 2313.0],       [2314.0, 2315.0, 2316.0],       [2317.0, 2318.0, 2319.0],       [2320.0, 2321.0, 2322.0],       [2323.0, 2324.0, 2325.0],       [2326.0, 2327.0, 2328.0],       [2329.0, 2330.0, 2331.0]]],     [[[2332.0, 2333.0, 2334.0],       [2335.0, 2336.0, 2337.0],       [2338.0, 2339.0, 2340.0],       [2341.0, 2342.0, 2343.0],       [2344.0, 2345.0, 2346.0],       [2347.0, 2348.0, 2349.0],       [2350.0, 2351.0, 2352.0]]]],    [[[[2353.0, 2354.0, 2355.0],       [2356.0, 2357.0, 2358.0],       [2359.0, 2360.0, 2361.0],       [2362.0, 2363.0, 2364.0],       [2365.0, 2366.0, 2367.0],       [2368.0, 2369.0, 2370.0],       [2371.0, 2372.0, 2373.0]]],     [[[2374.0, 2375.0, 2376.0],       [2377.0, 2378.0, 2379.0],       [2380.0, 2381.0, 2382.0],       [2383.0, 2384.0, 2385.0],       [2386.0, 2387.0, 2388.0],       [2389.0, 2390.0, 2391.0],       [2392.0, 2393.0, 2394.0]]],     [[[2395.0, 2396.0, 2397.0],       [2398.0, 2399.0, 2400.0],       [2401.0, 2402.0, 2403.0],       [2404.0, 2405.0, 2406.0],       [2407.0, 2408.0, 2409.0],       [2410.0, 2411.0, 2412.0],       [2413.0, 2414.0, 2415.0]]],     [[[2416.0, 2417.0, 2418.0],       [2419.0, 2420.0, 2421.0],       [2422.0, 2423.0, 2424.0],       [2425.0, 2426.0, 2427.0],       [2428.0, 2429.0, 2430.0],       [2431.0, 2432.0, 2433.0],       [2434.0, 2435.0, 2436.0]]]],    [[[[2437.0, 2438.0, 2439.0],       [2440.0, 2441.0, 2442.0],       [2443.0, 2444.0, 2445.0],       [2446.0, 2447.0, 2448.0],       [2449.0, 2450.0, 2451.0],       [2452.0, 2453.0, 2454.0],       [2455.0, 2456.0, 2457.0]]],     [[[2458.0, 2459.0, 2460.0],       [2461.0, 2462.0, 2463.0],       [2464.0, 2465.0, 2466.0],       [2467.0, 2468.0, 2469.0],       [2470.0, 2471.0, 2472.0],       [2473.0, 2474.0, 2475.0],       [2476.0, 2477.0, 2478.0]]],     [[[2479.0, 2480.0, 2481.0],       [2482.0, 2483.0, 2484.0],       [2485.0, 2486.0, 2487.0],       [2488.0, 2489.0, 2490.0],       [2491.0, 2492.0, 2493.0],       [2494.0, 2495.0, 2496.0],       [2497.0, 2498.0, 2499.0]]],     [[[2500.0, 2501.0, 2502.0],       [2503.0, 2504.0, 2505.0],       [2506.0, 2507.0, 2508.0],       [2509.0, 2510.0, 2511.0],       [2512.0, 2513.0, 2514.0],       [2515.0, 2516.0, 2517.0],       [2518.0, 2519.0, 2520.0]]]]]],  [[[[[[2521.0, 2522.0, 2523.0],       [2524.0, 2525.0, 2526.0],       [2527.0, 2528.0, 2529.0],       [2530.0, 2531.0, 2532.0],       [2533.0, 2534.0, 2535.0],       [2536.0, 2537.0, 2538.0],       [2539.0, 2540.0, 2541.0]]],     [[[2542.0, 2543.0, 2544.0],       [2545.0, 2546.0, 2547.0],       [2548.0, 2549.0, 2550.0],       [2551.0, 2552.0, 2553.0],       [2554.0, 2555.0, 2556.0],       [2557.0, 2558.0, 2559.0],       [2560.0, 2561.0, 2562.0]]],     [[[2563.0, 2564.0, 2565.0],       [2566.0, 2567.0, 2568.0],       [2569.0, 2570.0, 2571.0],       [2572.0, 2573.0, 2574.0],       [2575.0, 2576.0, 2577.0],       [2578.0, 2579.0, 2580.0],       [2581.0, 2582.0, 2583.0]]],     [[[2584.0, 2585.0, 2586.0],       [2587.0, 2588.0, 2589.0],       [2590.0, 2591.0, 2592.0],       [2593.0, 2594.0, 2595.0],       [2596.0, 2597.0, 2598.0],       [2599.0, 2600.0, 2601.0],       [2602.0, 2603.0, 2604.0]]]],    [[[[2605.0, 2606.0, 2607.0],       [2608.0, 2609.0, 2610.0],       [2611.0, 2612.0, 2613.0],       [2614.0, 2615.0, 2616.0],       [2617.0, 2618.0, 2619.0],       [2620.0, 2621.0, 2622.0],       [2623.0, 2624.0, 2625.0]]],     [[[2626.0, 2627.0, 2628.0],       [2629.0, 2630.0, 2631.0],       [2632.0, 2633.0, 2634.0],       [2635.0, 2636.0, 2637.0],       [2638.0, 2639.0, 2640.0],       [2641.0, 2642.0, 2643.0],       [2644.0, 2645.0, 2646.0]]],     [[[2647.0, 2648.0, 2649.0],       [2650.0, 2651.0, 2652.0],       [2653.0, 2654.0, 2655.0],       [2656.0, 2657.0, 2658.0],       [2659.0, 2660.0, 2661.0],       [2662.0, 2663.0, 2664.0],       [2665.0, 2666.0, 2667.0]]],     [[[2668.0, 2669.0, 2670.0],       [2671.0, 2672.0, 2673.0],       [2674.0, 2675.0, 2676.0],       [2677.0, 2678.0, 2679.0],       [2680.0, 2681.0, 2682.0],       [2683.0, 2684.0, 2685.0],       [2686.0, 2687.0, 2688.0]]]],    [[[[2689.0, 2690.0, 2691.0],       [2692.0, 2693.0, 2694.0],       [2695.0, 2696.0, 2697.0],       [2698.0, 2699.0, 2700.0],       [2701.0, 2702.0, 2703.0],       [2704.0, 2705.0, 2706.0],       [2707.0, 2708.0, 2709.0]]],     [[[2710.0, 2711.0, 2712.0],       [2713.0, 2714.0, 2715.0],       [2716.0, 2717.0, 2718.0],       [2719.0, 2720.0, 2721.0],       [2722.0, 2723.0, 2724.0],       [2725.0, 2726.0, 2727.0],       [2728.0, 2729.0, 2730.0]]],     [[[2731.0, 2732.0, 2733.0],       [2734.0, 2735.0, 2736.0],       [2737.0, 2738.0, 2739.0],       [2740.0, 2741.0, 2742.0],       [2743.0, 2744.0, 2745.0],       [2746.0, 2747.0, 2748.0],       [2749.0, 2750.0, 2751.0]]],     [[[2752.0, 2753.0, 2754.0],       [2755.0, 2756.0, 2757.0],       [2758.0, 2759.0, 2760.0],       [2761.0, 2762.0, 2763.0],       [2764.0, 2765.0, 2766.0],       [2767.0, 2768.0, 2769.0],       [2770.0, 2771.0, 2772.0]]]],    [[[[2773.0, 2774.0, 2775.0],       [2776.0, 2777.0, 2778.0],       [2779.0, 2780.0, 2781.0],       [2782.0, 2783.0, 2784.0],       [2785.0, 2786.0, 2787.0],       [2788.0, 2789.0, 2790.0],       [2791.0, 2792.0, 2793.0]]],     [[[2794.0, 2795.0, 2796.0],       [2797.0, 2798.0, 2799.0],       [2800.0, 2801.0, 2802.0],       [2803.0, 2804.0, 2805.0],       [2806.0, 2807.0, 2808.0],       [2809.0, 2810.0, 2811.0],       [2812.0, 2813.0, 2814.0]]],     [[[2815.0, 2816.0, 2817.0],       [2818.0, 2819.0, 2820.0],       [2821.0, 2822.0, 2823.0],       [2824.0, 2825.0, 2826.0],       [2827.0, 2828.0, 2829.0],       [2830.0, 2831.0, 2832.0],       [2833.0, 2834.0, 2835.0]]],     [[[2836.0, 2837.0, 2838.0],       [2839.0, 2840.0, 2841.0],       [2842.0, 2843.0, 2844.0],       [2845.0, 2846.0, 2847.0],       [2848.0, 2849.0, 2850.0],       [2851.0, 2852.0, 2853.0],       [2854.0, 2855.0, 2856.0]]]],    [[[[2857.0, 2858.0, 2859.0],       [2860.0, 2861.0, 2862.0],       [2863.0, 2864.0, 2865.0],       [2866.0, 2867.0, 2868.0],       [2869.0, 2870.0, 2871.0],       [2872.0, 2873.0, 2874.0],       [2875.0, 2876.0, 2877.0]]],     [[[2878.0, 2879.0, 2880.0],       [2881.0, 2882.0, 2883.0],       [2884.0, 2885.0, 2886.0],       [2887.0, 2888.0, 2889.0],       [2890.0, 2891.0, 2892.0],       [2893.0, 2894.0, 2895.0],       [2896.0, 2897.0, 2898.0]]],     [[[2899.0, 2900.0, 2901.0],       [2902.0, 2903.0, 2904.0],       [2905.0, 2906.0, 2907.0],       [2908.0, 2909.0, 2910.0],       [2911.0, 2912.0, 2913.0],       [2914.0, 2915.0, 2916.0],       [2917.0, 2918.0, 2919.0]]],     [[[2920.0, 2921.0, 2922.0],       [2923.0, 2924.0, 2925.0],       [2926.0, 2927.0, 2928.0],       [2929.0, 2930.0, 2931.0],       [2932.0, 2933.0, 2934.0],       [2935.0, 2936.0, 2937.0],       [2938.0, 2939.0, 2940.0]]]]],   [[[[[2941.0, 2942.0, 2943.0],       [2944.0, 2945.0, 2946.0],       [2947.0, 2948.0, 2949.0],       [2950.0, 2951.0, 2952.0],       [2953.0, 2954.0, 2955.0],       [2956.0, 2957.0, 2958.0],       [2959.0, 2960.0, 2961.0]]],     [[[2962.0, 2963.0, 2964.0],       [2965.0, 2966.0, 2967.0],       [2968.0, 2969.0, 2970.0],       [2971.0, 2972.0, 2973.0],       [2974.0, 2975.0, 2976.0],       [2977.0, 2978.0, 2979.0],       [2980.0, 2981.0, 2982.0]]],     [[[2983.0, 2984.0, 2985.0],       [2986.0, 2987.0, 2988.0],       [2989.0, 2990.0, 2991.0],       [2992.0, 2993.0, 2994.0],       [2995.0, 2996.0, 2997.0],       [2998.0, 2999.0, 3000.0],       [3001.0, 3002.0, 3003.0]]],     [[[3004.0, 3005.0, 3006.0],       [3007.0, 3008.0, 3009.0],       [3010.0, 3011.0, 3012.0],       [3013.0, 3014.0, 3015.0],       [3016.0, 3017.0, 3018.0],       [3019.0, 3020.0, 3021.0],       [3022.0, 3023.0, 3024.0]]]],    [[[[3025.0, 3026.0, 3027.0],       [3028.0, 3029.0, 3030.0],       [3031.0, 3032.0, 3033.0],       [3034.0, 3035.0, 3036.0],       [3037.0, 3038.0, 3039.0],       [3040.0, 3041.0, 3042.0],       [3043.0, 3044.0, 3045.0]]],     [[[3046.0, 3047.0, 3048.0],       [3049.0, 3050.0, 3051.0],       [3052.0, 3053.0, 3054.0],       [3055.0, 3056.0, 3057.0],       [3058.0, 3059.0, 3060.0],       [3061.0, 3062.0, 3063.0],       [3064.0, 3065.0, 3066.0]]],     [[[3067.0, 3068.0, 3069.0],       [3070.0, 3071.0, 3072.0],       [3073.0, 3074.0, 3075.0],       [3076.0, 3077.0, 3078.0],       [3079.0, 3080.0, 3081.0],       [3082.0, 3083.0, 3084.0],       [3085.0, 3086.0, 3087.0]]],     [[[3088.0, 3089.0, 3090.0],       [3091.0, 3092.0, 3093.0],       [3094.0, 3095.0, 3096.0],       [3097.0, 3098.0, 3099.0],       [3100.0, 3101.0, 3102.0],       [3103.0, 3104.0, 3105.0],       [3106.0, 3107.0, 3108.0]]]],    [[[[3109.0, 3110.0, 3111.0],       [3112.0, 3113.0, 3114.0],       [3115.0, 3116.0, 3117.0],       [3118.0, 3119.0, 3120.0],       [3121.0, 3122.0, 3123.0],       [3124.0, 3125.0, 3126.0],       [3127.0, 3128.0, 3129.0]]],     [[[3130.0, 3131.0, 3132.0],       [3133.0, 3134.0, 3135.0],       [3136.0, 3137.0, 3138.0],       [3139.0, 3140.0, 3141.0],       [3142.0, 3143.0, 3144.0],       [3145.0, 3146.0, 3147.0],       [3148.0, 3149.0, 3150.0]]],     [[[3151.0, 3152.0, 3153.0],       [3154.0, 3155.0, 3156.0],       [3157.0, 3158.0, 3159.0],       [3160.0, 3161.0, 3162.0],       [3163.0, 3164.0, 3165.0],       [3166.0, 3167.0, 3168.0],       [3169.0, 3170.0, 3171.0]]],     [[[3172.0, 3173.0, 3174.0],       [3175.0, 3176.0, 3177.0],       [3178.0, 3179.0, 3180.0],       [3181.0, 3182.0, 3183.0],       [3184.0, 3185.0, 3186.0],       [3187.0, 3188.0, 3189.0],       [3190.0, 3191.0, 3192.0]]]],    [[[[3193.0, 3194.0, 3195.0],       [3196.0, 3197.0, 3198.0],       [3199.0, 3200.0, 3201.0],       [3202.0, 3203.0, 3204.0],       [3205.0, 3206.0, 3207.0],       [3208.0, 3209.0, 3210.0],       [3211.0, 3212.0, 3213.0]]],     [[[3214.0, 3215.0, 3216.0],       [3217.0, 3218.0, 3219.0],       [3220.0, 3221.0, 3222.0],       [3223.0, 3224.0, 3225.0],       [3226.0, 3227.0, 3228.0],       [3229.0, 3230.0, 3231.0],       [3232.0, 3233.0, 3234.0]]],     [[[3235.0, 3236.0, 3237.0],       [3238.0, 3239.0, 3240.0],       [3241.0, 3242.0, 3243.0],       [3244.0, 3245.0, 3246.0],       [3247.0, 3248.0, 3249.0],       [3250.0, 3251.0, 3252.0],       [3253.0, 3254.0, 3255.0]]],     [[[3256.0, 3257.0, 3258.0],       [3259.0, 3260.0, 3261.0],       [3262.0, 3263.0, 3264.0],       [3265.0, 3266.0, 3267.0],       [3268.0, 3269.0, 3270.0],       [3271.0, 3272.0, 3273.0],       [3274.0, 3275.0, 3276.0]]]],    [[[[3277.0, 3278.0, 3279.0],       [3280.0, 3281.0, 3282.0],       [3283.0, 3284.0, 3285.0],       [3286.0, 3287.0, 3288.0],       [3289.0, 3290.0, 3291.0],       [3292.0, 3293.0, 3294.0],       [3295.0, 3296.0, 3297.0]]],     [[[3298.0, 3299.0, 3300.0],       [3301.0, 3302.0, 3303.0],       [3304.0, 3305.0, 3306.0],       [3307.0, 3308.0, 3309.0],       [3310.0, 3311.0, 3312.0],       [3313.0, 3314.0, 3315.0],       [3316.0, 3317.0, 3318.0]]],     [[[3319.0, 3320.0, 3321.0],       [3322.0, 3323.0, 3324.0],       [3325.0, 3326.0, 3327.0],       [3328.0, 3329.0, 3330.0],       [3331.0, 3332.0, 3333.0],       [3334.0, 3335.0, 3336.0],       [3337.0, 3338.0, 3339.0]]],     [[[3340.0, 3341.0, 3342.0],       [3343.0, 3344.0, 3345.0],       [3346.0, 3347.0, 3348.0],       [3349.0, 3350.0, 3351.0],       [3352.0, 3353.0, 3354.0],       [3355.0, 3356.0, 3357.0],       [3358.0, 3359.0, 3360.0]]]]],   [[[[[3361.0, 3362.0, 3363.0],       [3364.0, 3365.0, 3366.0],       [3367.0, 3368.0, 3369.0],       [3370.0, 3371.0, 3372.0],       [3373.0, 3374.0, 3375.0],       [3376.0, 3377.0, 3378.0],       [3379.0, 3380.0, 3381.0]]],     [[[3382.0, 3383.0, 3384.0],       [3385.0, 3386.0, 3387.0],       [3388.0, 3389.0, 3390.0],       [3391.0, 3392.0, 3393.0],       [3394.0, 3395.0, 3396.0],       [3397.0, 3398.0, 3399.0],       [3400.0, 3401.0, 3402.0]]],     [[[3403.0, 3404.0, 3405.0],       [3406.0, 3407.0, 3408.0],       [3409.0, 3410.0, 3411.0],       [3412.0, 3413.0, 3414.0],       [3415.0, 3416.0, 3417.0],       [3418.0, 3419.0, 3420.0],       [3421.0, 3422.0, 3423.0]]],     [[[3424.0, 3425.0, 3426.0],       [3427.0, 3428.0, 3429.0],       [3430.0, 3431.0, 3432.0],       [3433.0, 3434.0, 3435.0],       [3436.0, 3437.0, 3438.0],       [3439.0, 3440.0, 3441.0],       [3442.0, 3443.0, 3444.0]]]],    [[[[3445.0, 3446.0, 3447.0],       [3448.0, 3449.0, 3450.0],       [3451.0, 3452.0, 3453.0],       [3454.0, 3455.0, 3456.0],       [3457.0, 3458.0, 3459.0],       [3460.0, 3461.0, 3462.0],       [3463.0, 3464.0, 3465.0]]],     [[[3466.0, 3467.0, 3468.0],       [3469.0, 3470.0, 3471.0],       [3472.0, 3473.0, 3474.0],       [3475.0, 3476.0, 3477.0],       [3478.0, 3479.0, 3480.0],       [3481.0, 3482.0, 3483.0],       [3484.0, 3485.0, 3486.0]]],     [[[3487.0, 3488.0, 3489.0],       [3490.0, 3491.0, 3492.0],       [3493.0, 3494.0, 3495.0],       [3496.0, 3497.0, 3498.0],       [3499.0, 3500.0, 3501.0],       [3502.0, 3503.0, 3504.0],       [3505.0, 3506.0, 3507.0]]],     [[[3508.0, 3509.0, 3510.0],       [3511.0, 3512.0, 3513.0],       [3514.0, 3515.0, 3516.0],       [3517.0, 3518.0, 3519.0],       [3520.0, 3521.0, 3522.0],       [3523.0, 3524.0, 3525.0],       [3526.0, 3527.0, 3528.0]]]],    [[[[3529.0, 3530.0, 3531.0],       [3532.0, 3533.0, 3534.0],       [3535.0, 3536.0, 3537.0],       [3538.0, 3539.0, 3540.0],       [3541.0, 3542.0, 3543.0],       [3544.0, 3545.0, 3546.0],       [3547.0, 3548.0, 3549.0]]],     [[[3550.0, 3551.0, 3552.0],       [3553.0, 3554.0, 3555.0],       [3556.0, 3557.0, 3558.0],       [3559.0, 3560.0, 3561.0],       [3562.0, 3563.0, 3564.0],       [3565.0, 3566.0, 3567.0],       [3568.0, 3569.0, 3570.0]]],     [[[3571.0, 3572.0, 3573.0],       [3574.0, 3575.0, 3576.0],       [3577.0, 3578.0, 3579.0],       [3580.0, 3581.0, 3582.0],       [3583.0, 3584.0, 3585.0],       [3586.0, 3587.0, 3588.0],       [3589.0, 3590.0, 3591.0]]],     [[[3592.0, 3593.0, 3594.0],       [3595.0, 3596.0, 3597.0],       [3598.0, 3599.0, 3600.0],       [3601.0, 3602.0, 3603.0],       [3604.0, 3605.0, 3606.0],       [3607.0, 3608.0, 3609.0],       [3610.0, 3611.0, 3612.0]]]],    [[[[3613.0, 3614.0, 3615.0],       [3616.0, 3617.0, 3618.0],       [3619.0, 3620.0, 3621.0],       [3622.0, 3623.0, 3624.0],       [3625.0, 3626.0, 3627.0],       [3628.0, 3629.0, 3630.0],       [3631.0, 3632.0, 3633.0]]],     [[[3634.0, 3635.0, 3636.0],       [3637.0, 3638.0, 3639.0],       [3640.0, 3641.0, 3642.0],       [3643.0, 3644.0, 3645.0],       [3646.0, 3647.0, 3648.0],       [3649.0, 3650.0, 3651.0],       [3652.0, 3653.0, 3654.0]]],     [[[3655.0, 3656.0, 3657.0],       [3658.0, 3659.0, 3660.0],       [3661.0, 3662.0, 3663.0],       [3664.0, 3665.0, 3666.0],       [3667.0, 3668.0, 3669.0],       [3670.0, 3671.0, 3672.0],       [3673.0, 3674.0, 3675.0]]],     [[[3676.0, 3677.0, 3678.0],       [3679.0, 3680.0, 3681.0],       [3682.0, 3683.0, 3684.0],       [3685.0, 3686.0, 3687.0],       [3688.0, 3689.0, 3690.0],       [3691.0, 3692.0, 3693.0],       [3694.0, 3695.0, 3696.0]]]],    [[[[3697.0, 3698.0, 3699.0],       [3700.0, 3701.0, 3702.0],       [3703.0, 3704.0, 3705.0],       [3706.0, 3707.0, 3708.0],       [3709.0, 3710.0, 3711.0],       [3712.0, 3713.0, 3714.0],       [3715.0, 3716.0, 3717.0]]],     [[[3718.0, 3719.0, 3720.0],       [3721.0, 3722.0, 3723.0],       [3724.0, 3725.0, 3726.0],       [3727.0, 3728.0, 3729.0],       [3730.0, 3731.0, 3732.0],       [3733.0, 3734.0, 3735.0],       [3736.0, 3737.0, 3738.0]]],     [[[3739.0, 3740.0, 3741.0],       [3742.0, 3743.0, 3744.0],       [3745.0, 3746.0, 3747.0],       [3748.0, 3749.0, 3750.0],       [3751.0, 3752.0, 3753.0],       [3754.0, 3755.0, 3756.0],       [3757.0, 3758.0, 3759.0]]],     [[[3760.0, 3761.0, 3762.0],       [3763.0, 3764.0, 3765.0],       [3766.0, 3767.0, 3768.0],       [3769.0, 3770.0, 3771.0],       [3772.0, 3773.0, 3774.0],       [3775.0, 3776.0, 3777.0],       [3778.0, 3779.0, 3780.0]]]]]]] shape=[3, 3, 5, 4, 1, 7, 3], strides=[1260, 420, 84, 21, 21, 3, 1], layout=C (0x1)), I32([1, 1, 3] shape=[3], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [0, 1],  [2, 3]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 3498087451 3889198853 2092426475 1671607467 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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]],    [[16.0, 17.0, 18.0],     [19.0, 20.0, 21.0],     [22.0, 23.0, 24.0],     [25.0, 26.0, 27.0],     [28.0, 29.0, 30.0]],    [[31.0, 32.0, 33.0],     [34.0, 35.0, 36.0],     [37.0, 38.0, 39.0],     [40.0, 41.0, 42.0],     [43.0, 44.0, 45.0]],    [[46.0, 47.0, 48.0],     [49.0, 50.0, 51.0],     [52.0, 53.0, 54.0],     [55.0, 56.0, 57.0],     [58.0, 59.0, 60.0]]],   [[[61.0, 62.0, 63.0],     [64.0, 65.0, 66.0],     [67.0, 68.0, 69.0],     [70.0, 71.0, 72.0],     [73.0, 74.0, 75.0]],    [[76.0, 77.0, 78.0],     [79.0, 80.0, 81.0],     [82.0, 83.0, 84.0],     [85.0, 86.0, 87.0],     [88.0, 89.0, 90.0]],    [[91.0, 92.0, 93.0],     [94.0, 95.0, 96.0],     [97.0, 98.0, 99.0],     [100.0, 101.0, 102.0],     [103.0, 104.0, 105.0]],    [[106.0, 107.0, 108.0],     [109.0, 110.0, 111.0],     [112.0, 113.0, 114.0],     [115.0, 116.0, 117.0],     [118.0, 119.0, 120.0]]],   [[[121.0, 122.0, 123.0],     [124.0, 125.0, 126.0],     [127.0, 128.0, 129.0],     [130.0, 131.0, 132.0],     [133.0, 134.0, 135.0]],    [[136.0, 137.0, 138.0],     [139.0, 140.0, 141.0],     [142.0, 143.0, 144.0],     [145.0, 146.0, 147.0],     [148.0, 149.0, 150.0]],    [[151.0, 152.0, 153.0],     [154.0, 155.0, 156.0],     [157.0, 158.0, 159.0],     [160.0, 161.0, 162.0],     [163.0, 164.0, 165.0]],    [[166.0, 167.0, 168.0],     [169.0, 170.0, 171.0],     [172.0, 173.0, 174.0],     [175.0, 176.0, 177.0],     [178.0, 179.0, 180.0]]],   [[[181.0, 182.0, 183.0],     [184.0, 185.0, 186.0],     [187.0, 188.0, 189.0],     [190.0, 191.0, 192.0],     [193.0, 194.0, 195.0]],    [[196.0, 197.0, 198.0],     [199.0, 200.0, 201.0],     [202.0, 203.0, 204.0],     [205.0, 206.0, 207.0],     [208.0, 209.0, 210.0]],    [[211.0, 212.0, 213.0],     [214.0, 215.0, 216.0],     [217.0, 218.0, 219.0],     [220.0, 221.0, 222.0],     [223.0, 224.0, 225.0]],    [[226.0, 227.0, 228.0],     [229.0, 230.0, 231.0],     [232.0, 233.0, 234.0],     [235.0, 236.0, 237.0],     [238.0, 239.0, 240.0]]],   [[[241.0, 242.0, 243.0],     [244.0, 245.0, 246.0],     [247.0, 248.0, 249.0],     [250.0, 251.0, 252.0],     [253.0, 254.0, 255.0]],    [[256.0, 257.0, 258.0],     [259.0, 260.0, 261.0],     [262.0, 263.0, 264.0],     [265.0, 266.0, 267.0],     [268.0, 269.0, 270.0]],    [[271.0, 272.0, 273.0],     [274.0, 275.0, 276.0],     [277.0, 278.0, 279.0],     [280.0, 281.0, 282.0],     [283.0, 284.0, 285.0]],    [[286.0, 287.0, 288.0],     [289.0, 290.0, 291.0],     [292.0, 293.0, 294.0],     [295.0, 296.0, 297.0],     [298.0, 299.0, 300.0]]],   [[[301.0, 302.0, 303.0],     [304.0, 305.0, 306.0],     [307.0, 308.0, 309.0],     [310.0, 311.0, 312.0],     [313.0, 314.0, 315.0]],    [[316.0, 317.0, 318.0],     [319.0, 320.0, 321.0],     [322.0, 323.0, 324.0],     [325.0, 326.0, 327.0],     [328.0, 329.0, 330.0]],    [[331.0, 332.0, 333.0],     [334.0, 335.0, 336.0],     [337.0, 338.0, 339.0],     [340.0, 341.0, 342.0],     [343.0, 344.0, 345.0]],    [[346.0, 347.0, 348.0],     [349.0, 350.0, 351.0],     [352.0, 353.0, 354.0],     [355.0, 356.0, 357.0],     [358.0, 359.0, 360.0]]],   [[[361.0, 362.0, 363.0],     [364.0, 365.0, 366.0],     [367.0, 368.0, 369.0],     [370.0, 371.0, 372.0],     [373.0, 374.0, 375.0]],    [[376.0, 377.0, 378.0],     [379.0, 380.0, 381.0],     [382.0, 383.0, 384.0],     [385.0, 386.0, 387.0],     [388.0, 389.0, 390.0]],    [[391.0, 392.0, 393.0],     [394.0, 395.0, 396.0],     [397.0, 398.0, 399.0],     [400.0, 401.0, 402.0],     [403.0, 404.0, 405.0]],    [[406.0, 407.0, 408.0],     [409.0, 410.0, 411.0],     [412.0, 413.0, 414.0],     [415.0, 416.0, 417.0],     [418.0, 419.0, 420.0]]]],  [[[[421.0, 422.0, 423.0],     [424.0, 425.0, 426.0],     [427.0, 428.0, 429.0],     [430.0, 431.0, 432.0],     [433.0, 434.0, 435.0]],    [[436.0, 437.0, 438.0],     [439.0, 440.0, 441.0],     [442.0, 443.0, 444.0],     [445.0, 446.0, 447.0],     [448.0, 449.0, 450.0]],    [[451.0, 452.0, 453.0],     [454.0, 455.0, 456.0],     [457.0, 458.0, 459.0],     [460.0, 461.0, 462.0],     [463.0, 464.0, 465.0]],    [[466.0, 467.0, 468.0],     [469.0, 470.0, 471.0],     [472.0, 473.0, 474.0],     [475.0, 476.0, 477.0],     [478.0, 479.0, 480.0]]],   [[[481.0, 482.0, 483.0],     [484.0, 485.0, 486.0],     [487.0, 488.0, 489.0],     [490.0, 491.0, 492.0],     [493.0, 494.0, 495.0]],    [[496.0, 497.0, 498.0],     [499.0, 500.0, 501.0],     [502.0, 503.0, 504.0],     [505.0, 506.0, 507.0],     [508.0, 509.0, 510.0]],    [[511.0, 512.0, 513.0],     [514.0, 515.0, 516.0],     [517.0, 518.0, 519.0],     [520.0, 521.0, 522.0],     [523.0, 524.0, 525.0]],    [[526.0, 527.0, 528.0],     [529.0, 530.0, 531.0],     [532.0, 533.0, 534.0],     [535.0, 536.0, 537.0],     [538.0, 539.0, 540.0]]],   [[[541.0, 542.0, 543.0],     [544.0, 545.0, 546.0],     [547.0, 548.0, 549.0],     [550.0, 551.0, 552.0],     [553.0, 554.0, 555.0]],    [[556.0, 557.0, 558.0],     [559.0, 560.0, 561.0],     [562.0, 563.0, 564.0],     [565.0, 566.0, 567.0],     [568.0, 569.0, 570.0]],    [[571.0, 572.0, 573.0],     [574.0, 575.0, 576.0],     [577.0, 578.0, 579.0],     [580.0, 581.0, 582.0],     [583.0, 584.0, 585.0]],    [[586.0, 587.0, 588.0],     [589.0, 590.0, 591.0],     [592.0, 593.0, 594.0],     [595.0, 596.0, 597.0],     [598.0, 599.0, 600.0]]],   [[[601.0, 602.0, 603.0],     [604.0, 605.0, 606.0],     [607.0, 608.0, 609.0],     [610.0, 611.0, 612.0],     [613.0, 614.0, 615.0]],    [[616.0, 617.0, 618.0],     [619.0, 620.0, 621.0],     [622.0, 623.0, 624.0],     [625.0, 626.0, 627.0],     [628.0, 629.0, 630.0]],    [[631.0, 632.0, 633.0],     [634.0, 635.0, 636.0],     [637.0, 638.0, 639.0],     [640.0, 641.0, 642.0],     [643.0, 644.0, 645.0]],    [[646.0, 647.0, 648.0],     [649.0, 650.0, 651.0],     [652.0, 653.0, 654.0],     [655.0, 656.0, 657.0],     [658.0, 659.0, 660.0]]],   [[[661.0, 662.0, 663.0],     [664.0, 665.0, 666.0],     [667.0, 668.0, 669.0],     [670.0, 671.0, 672.0],     [673.0, 674.0, 675.0]],    [[676.0, 677.0, 678.0],     [679.0, 680.0, 681.0],     [682.0, 683.0, 684.0],     [685.0, 686.0, 687.0],     [688.0, 689.0, 690.0]],    [[691.0, 692.0, 693.0],     [694.0, 695.0, 696.0],     [697.0, 698.0, 699.0],     [700.0, 701.0, 702.0],     [703.0, 704.0, 705.0]],    [[706.0, 707.0, 708.0],     [709.0, 710.0, 711.0],     [712.0, 713.0, 714.0],     [715.0, 716.0, 717.0],     [718.0, 719.0, 720.0]]],   [[[721.0, 722.0, 723.0],     [724.0, 725.0, 726.0],     [727.0, 728.0, 729.0],     [730.0, 731.0, 732.0],     [733.0, 734.0, 735.0]],    [[736.0, 737.0, 738.0],     [739.0, 740.0, 741.0],     [742.0, 743.0, 744.0],     [745.0, 746.0, 747.0],     [748.0, 749.0, 750.0]],    [[751.0, 752.0, 753.0],     [754.0, 755.0, 756.0],     [757.0, 758.0, 759.0],     [760.0, 761.0, 762.0],     [763.0, 764.0, 765.0]],    [[766.0, 767.0, 768.0],     [769.0, 770.0, 771.0],     [772.0, 773.0, 774.0],     [775.0, 776.0, 777.0],     [778.0, 779.0, 780.0]]],   [[[781.0, 782.0, 783.0],     [784.0, 785.0, 786.0],     [787.0, 788.0, 789.0],     [790.0, 791.0, 792.0],     [793.0, 794.0, 795.0]],    [[796.0, 797.0, 798.0],     [799.0, 800.0, 801.0],     [802.0, 803.0, 804.0],     [805.0, 806.0, 807.0],     [808.0, 809.0, 810.0]],    [[811.0, 812.0, 813.0],     [814.0, 815.0, 816.0],     [817.0, 818.0, 819.0],     [820.0, 821.0, 822.0],     [823.0, 824.0, 825.0]],    [[826.0, 827.0, 828.0],     [829.0, 830.0, 831.0],     [832.0, 833.0, 834.0],     [835.0, 836.0, 837.0],     [838.0, 839.0, 840.0]]]]] shape=[2, 7, 4, 5, 3], strides=[420, 60, 15, 3, 1], layout=C (0x1)), I32([1, 3, 3] shape=[3], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [1, 1],  [1, 3]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 938324481 3685659208 4001621855 62650732 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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],      [16.0]],     [[17.0],      [18.0]],     [[19.0],      [20.0]],     [[21.0],      [22.0]],     [[23.0],      [24.0]],     [[25.0],      [26.0]],     [[27.0],      [28.0]]],    [[[29.0],      [30.0]],     [[31.0],      [32.0]],     [[33.0],      [34.0]],     [[35.0],      [36.0]],     [[37.0],      [38.0]],     [[39.0],      [40.0]],     [[41.0],      [42.0]]]],   [[[[43.0],      [44.0]],     [[45.0],      [46.0]],     [[47.0],      [48.0]],     [[49.0],      [50.0]],     [[51.0],      [52.0]],     [[53.0],      [54.0]],     [[55.0],      [56.0]]],    [[[57.0],      [58.0]],     [[59.0],      [60.0]],     [[61.0],      [62.0]],     [[63.0],      [64.0]],     [[65.0],      [66.0]],     [[67.0],      [68.0]],     [[69.0],      [70.0]]],    [[[71.0],      [72.0]],     [[73.0],      [74.0]],     [[75.0],      [76.0]],     [[77.0],      [78.0]],     [[79.0],      [80.0]],     [[81.0],      [82.0]],     [[83.0],      [84.0]]]],   [[[[85.0],      [86.0]],     [[87.0],      [88.0]],     [[89.0],      [90.0]],     [[91.0],      [92.0]],     [[93.0],      [94.0]],     [[95.0],      [96.0]],     [[97.0],      [98.0]]],    [[[99.0],      [100.0]],     [[101.0],      [102.0]],     [[103.0],      [104.0]],     [[105.0],      [106.0]],     [[107.0],      [108.0]],     [[109.0],      [110.0]],     [[111.0],      [112.0]]],    [[[113.0],      [114.0]],     [[115.0],      [116.0]],     [[117.0],      [118.0]],     [[119.0],      [120.0]],     [[121.0],      [122.0]],     [[123.0],      [124.0]],     [[125.0],      [126.0]]]]]] shape=[1, 3, 3, 7, 2, 1], strides=[126, 42, 14, 2, 1, 1], layout=C (0x1)), I32([3, 1, 2] shape=[3], strides=[1], layout=C | F (0x3)), I32([[0, 3],  [0, 1],  [2, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 251430923 1502781701 921563853 629611462 # shrinks to (ref i, ref bs, ref p) = (F32([[[[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],    [16.0, 17.0, 18.0, 19.0, 20.0],    [21.0, 22.0, 23.0, 24.0, 25.0]]]] shape=[1, 1, 5, 5], strides=[25, 25, 5, 1], layout=C (0x1)), I32([1, 2] shape=[2], strides=[1], layout=C | F (0x3)), I32([[1, 1],  [3, 2]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 2113652809 1557540221 4151330462 465836177 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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],     [16.0, 17.0, 18.0],     [19.0, 20.0, 21.0],     [22.0, 23.0, 24.0]],    [[25.0, 26.0, 27.0],     [28.0, 29.0, 30.0],     [31.0, 32.0, 33.0],     [34.0, 35.0, 36.0]],    [[37.0, 38.0, 39.0],     [40.0, 41.0, 42.0],     [43.0, 44.0, 45.0],     [46.0, 47.0, 48.0]]],   [[[49.0, 50.0, 51.0],     [52.0, 53.0, 54.0],     [55.0, 56.0, 57.0],     [58.0, 59.0, 60.0]],    [[61.0, 62.0, 63.0],     [64.0, 65.0, 66.0],     [67.0, 68.0, 69.0],     [70.0, 71.0, 72.0]],    [[73.0, 74.0, 75.0],     [76.0, 77.0, 78.0],     [79.0, 80.0, 81.0],     [82.0, 83.0, 84.0]],    [[85.0, 86.0, 87.0],     [88.0, 89.0, 90.0],     [91.0, 92.0, 93.0],     [94.0, 95.0, 96.0]]]],  [[[[97.0, 98.0, 99.0],     [100.0, 101.0, 102.0],     [103.0, 104.0, 105.0],     [106.0, 107.0, 108.0]],    [[109.0, 110.0, 111.0],     [112.0, 113.0, 114.0],     [115.0, 116.0, 117.0],     [118.0, 119.0, 120.0]],    [[121.0, 122.0, 123.0],     [124.0, 125.0, 126.0],     [127.0, 128.0, 129.0],     [130.0, 131.0, 132.0]],    [[133.0, 134.0, 135.0],     [136.0, 137.0, 138.0],     [139.0, 140.0, 141.0],     [142.0, 143.0, 144.0]]],   [[[145.0, 146.0, 147.0],     [148.0, 149.0, 150.0],     [151.0, 152.0, 153.0],     [154.0, 155.0, 156.0]],    [[157.0, 158.0, 159.0],     [160.0, 161.0, 162.0],     [163.0, 164.0, 165.0],     [166.0, 167.0, 168.0]],    [[169.0, 170.0, 171.0],     [172.0, 173.0, 174.0],     [175.0, 176.0, 177.0],     [178.0, 179.0, 180.0]],    [[181.0, 182.0, 183.0],     [184.0, 185.0, 186.0],     [187.0, 188.0, 189.0],     [190.0, 191.0, 192.0]]]]] shape=[2, 2, 4, 4, 3], strides=[96, 48, 12, 3, 1], layout=C (0x1)), I32([1, 3] shape=[2], strides=[1], layout=C | F (0x3)), I32([[2, 1],  [1, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 308502085 4090842210 240333265 1186707120 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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, 16.0, 17.0, 18.0],      [19.0, 20.0, 21.0, 22.0, 23.0, 24.0],      [25.0, 26.0, 27.0, 28.0, 29.0, 30.0]],     [[31.0, 32.0, 33.0, 34.0, 35.0, 36.0],      [37.0, 38.0, 39.0, 40.0, 41.0, 42.0],      [43.0, 44.0, 45.0, 46.0, 47.0, 48.0],      [49.0, 50.0, 51.0, 52.0, 53.0, 54.0],      [55.0, 56.0, 57.0, 58.0, 59.0, 60.0]],     [[61.0, 62.0, 63.0, 64.0, 65.0, 66.0],      [67.0, 68.0, 69.0, 70.0, 71.0, 72.0],      [73.0, 74.0, 75.0, 76.0, 77.0, 78.0],      [79.0, 80.0, 81.0, 82.0, 83.0, 84.0],      [85.0, 86.0, 87.0, 88.0, 89.0, 90.0]],     [[91.0, 92.0, 93.0, 94.0, 95.0, 96.0],      [97.0, 98.0, 99.0, 100.0, 101.0, 102.0],      [103.0, 104.0, 105.0, 106.0, 107.0, 108.0],      [109.0, 110.0, 111.0, 112.0, 113.0, 114.0],      [115.0, 116.0, 117.0, 118.0, 119.0, 120.0]],     [[121.0, 122.0, 123.0, 124.0, 125.0, 126.0],      [127.0, 128.0, 129.0, 130.0, 131.0, 132.0],      [133.0, 134.0, 135.0, 136.0, 137.0, 138.0],      [139.0, 140.0, 141.0, 142.0, 143.0, 144.0],      [145.0, 146.0, 147.0, 148.0, 149.0, 150.0]]],    [[[151.0, 152.0, 153.0, 154.0, 155.0, 156.0],      [157.0, 158.0, 159.0, 160.0, 161.0, 162.0],      [163.0, 164.0, 165.0, 166.0, 167.0, 168.0],      [169.0, 170.0, 171.0, 172.0, 173.0, 174.0],      [175.0, 176.0, 177.0, 178.0, 179.0, 180.0]],     [[181.0, 182.0, 183.0, 184.0, 185.0, 186.0],      [187.0, 188.0, 189.0, 190.0, 191.0, 192.0],      [193.0, 194.0, 195.0, 196.0, 197.0, 198.0],      [199.0, 200.0, 201.0, 202.0, 203.0, 204.0],      [205.0, 206.0, 207.0, 208.0, 209.0, 210.0]],     [[211.0, 212.0, 213.0, 214.0, 215.0, 216.0],      [217.0, 218.0, 219.0, 220.0, 221.0, 222.0],      [223.0, 224.0, 225.0, 226.0, 227.0, 228.0],      [229.0, 230.0, 231.0, 232.0, 233.0, 234.0],      [235.0, 236.0, 237.0, 238.0, 239.0, 240.0]],     [[241.0, 242.0, 243.0, 244.0, 245.0, 246.0],      [247.0, 248.0, 249.0, 250.0, 251.0, 252.0],      [253.0, 254.0, 255.0, 256.0, 257.0, 258.0],      [259.0, 260.0, 261.0, 262.0, 263.0, 264.0],      [265.0, 266.0, 267.0, 268.0, 269.0, 270.0]],     [[271.0, 272.0, 273.0, 274.0, 275.0, 276.0],      [277.0, 278.0, 279.0, 280.0, 281.0, 282.0],      [283.0, 284.0, 285.0, 286.0, 287.0, 288.0],      [289.0, 290.0, 291.0, 292.0, 293.0, 294.0],      [295.0, 296.0, 297.0, 298.0, 299.0, 300.0]]],    [[[301.0, 302.0, 303.0, 304.0, 305.0, 306.0],      [307.0, 308.0, 309.0, 310.0, 311.0, 312.0],      [313.0, 314.0, 315.0, 316.0, 317.0, 318.0],      [319.0, 320.0, 321.0, 322.0, 323.0, 324.0],      [325.0, 326.0, 327.0, 328.0, 329.0, 330.0]],     [[331.0, 332.0, 333.0, 334.0, 335.0, 336.0],      [337.0, 338.0, 339.0, 340.0, 341.0, 342.0],      [343.0, 344.0, 345.0, 346.0, 347.0, 348.0],      [349.0, 350.0, 351.0, 352.0, 353.0, 354.0],      [355.0, 356.0, 357.0, 358.0, 359.0, 360.0]],     [[361.0, 362.0, 363.0, 364.0, 365.0, 366.0],      [367.0, 368.0, 369.0, 370.0, 371.0, 372.0],      [373.0, 374.0, 375.0, 376.0, 377.0, 378.0],      [379.0, 380.0, 381.0, 382.0, 383.0, 384.0],      [385.0, 386.0, 387.0, 388.0, 389.0, 390.0]],     [[391.0, 392.0, 393.0, 394.0, 395.0, 396.0],      [397.0, 398.0, 399.0, 400.0, 401.0, 402.0],      [403.0, 404.0, 405.0, 406.0, 407.0, 408.0],      [409.0, 410.0, 411.0, 412.0, 413.0, 414.0],      [415.0, 416.0, 417.0, 418.0, 419.0, 420.0]],     [[421.0, 422.0, 423.0, 424.0, 425.0, 426.0],      [427.0, 428.0, 429.0, 430.0, 431.0, 432.0],      [433.0, 434.0, 435.0, 436.0, 437.0, 438.0],      [439.0, 440.0, 441.0, 442.0, 443.0, 444.0],      [445.0, 446.0, 447.0, 448.0, 449.0, 450.0]]],    [[[451.0, 452.0, 453.0, 454.0, 455.0, 456.0],      [457.0, 458.0, 459.0, 460.0, 461.0, 462.0],      [463.0, 464.0, 465.0, 466.0, 467.0, 468.0],      [469.0, 470.0, 471.0, 472.0, 473.0, 474.0],      [475.0, 476.0, 477.0, 478.0, 479.0, 480.0]],     [[481.0, 482.0, 483.0, 484.0, 485.0, 486.0],      [487.0, 488.0, 489.0, 490.0, 491.0, 492.0],      [493.0, 494.0, 495.0, 496.0, 497.0, 498.0],      [499.0, 500.0, 501.0, 502.0, 503.0, 504.0],      [505.0, 506.0, 507.0, 508.0, 509.0, 510.0]],     [[511.0, 512.0, 513.0, 514.0, 515.0, 516.0],      [517.0, 518.0, 519.0, 520.0, 521.0, 522.0],      [523.0, 524.0, 525.0, 526.0, 527.0, 528.0],      [529.0, 530.0, 531.0, 532.0, 533.0, 534.0],      [535.0, 536.0, 537.0, 538.0, 539.0, 540.0]],     [[541.0, 542.0, 543.0, 544.0, 545.0, 546.0],      [547.0, 548.0, 549.0, 550.0, 551.0, 552.0],      [553.0, 554.0, 555.0, 556.0, 557.0, 558.0],      [559.0, 560.0, 561.0, 562.0, 563.0, 564.0],      [565.0, 566.0, 567.0, 568.0, 569.0, 570.0]],     [[571.0, 572.0, 573.0, 574.0, 575.0, 576.0],      [577.0, 578.0, 579.0, 580.0, 581.0, 582.0],      [583.0, 584.0, 585.0, 586.0, 587.0, 588.0],      [589.0, 590.0, 591.0, 592.0, 593.0, 594.0],      [595.0, 596.0, 597.0, 598.0, 599.0, 600.0]]],    [[[601.0, 602.0, 603.0, 604.0, 605.0, 606.0],      [607.0, 608.0, 609.0, 610.0, 611.0, 612.0],      [613.0, 614.0, 615.0, 616.0, 617.0, 618.0],      [619.0, 620.0, 621.0, 622.0, 623.0, 624.0],      [625.0, 626.0, 627.0, 628.0, 629.0, 630.0]],     [[631.0, 632.0, 633.0, 634.0, 635.0, 636.0],      [637.0, 638.0, 639.0, 640.0, 641.0, 642.0],      [643.0, 644.0, 645.0, 646.0, 647.0, 648.0],      [649.0, 650.0, 651.0, 652.0, 653.0, 654.0],      [655.0, 656.0, 657.0, 658.0, 659.0, 660.0]],     [[661.0, 662.0, 663.0, 664.0, 665.0, 666.0],      [667.0, 668.0, 669.0, 670.0, 671.0, 672.0],      [673.0, 674.0, 675.0, 676.0, 677.0, 678.0],      [679.0, 680.0, 681.0, 682.0, 683.0, 684.0],      [685.0, 686.0, 687.0, 688.0, 689.0, 690.0]],     [[691.0, 692.0, 693.0, 694.0, 695.0, 696.0],      [697.0, 698.0, 699.0, 700.0, 701.0, 702.0],      [703.0, 704.0, 705.0, 706.0, 707.0, 708.0],      [709.0, 710.0, 711.0, 712.0, 713.0, 714.0],      [715.0, 716.0, 717.0, 718.0, 719.0, 720.0]],     [[721.0, 722.0, 723.0, 724.0, 725.0, 726.0],      [727.0, 728.0, 729.0, 730.0, 731.0, 732.0],      [733.0, 734.0, 735.0, 736.0, 737.0, 738.0],      [739.0, 740.0, 741.0, 742.0, 743.0, 744.0],      [745.0, 746.0, 747.0, 748.0, 749.0, 750.0]]],    [[[751.0, 752.0, 753.0, 754.0, 755.0, 756.0],      [757.0, 758.0, 759.0, 760.0, 761.0, 762.0],      [763.0, 764.0, 765.0, 766.0, 767.0, 768.0],      [769.0, 770.0, 771.0, 772.0, 773.0, 774.0],      [775.0, 776.0, 777.0, 778.0, 779.0, 780.0]],     [[781.0, 782.0, 783.0, 784.0, 785.0, 786.0],      [787.0, 788.0, 789.0, 790.0, 791.0, 792.0],      [793.0, 794.0, 795.0, 796.0, 797.0, 798.0],      [799.0, 800.0, 801.0, 802.0, 803.0, 804.0],      [805.0, 806.0, 807.0, 808.0, 809.0, 810.0]],     [[811.0, 812.0, 813.0, 814.0, 815.0, 816.0],      [817.0, 818.0, 819.0, 820.0, 821.0, 822.0],      [823.0, 824.0, 825.0, 826.0, 827.0, 828.0],      [829.0, 830.0, 831.0, 832.0, 833.0, 834.0],      [835.0, 836.0, 837.0, 838.0, 839.0, 840.0]],     [[841.0, 842.0, 843.0, 844.0, 845.0, 846.0],      [847.0, 848.0, 849.0, 850.0, 851.0, 852.0],      [853.0, 854.0, 855.0, 856.0, 857.0, 858.0],      [859.0, 860.0, 861.0, 862.0, 863.0, 864.0],      [865.0, 866.0, 867.0, 868.0, 869.0, 870.0]],     [[871.0, 872.0, 873.0, 874.0, 875.0, 876.0],      [877.0, 878.0, 879.0, 880.0, 881.0, 882.0],      [883.0, 884.0, 885.0, 886.0, 887.0, 888.0],      [889.0, 890.0, 891.0, 892.0, 893.0, 894.0],      [895.0, 896.0, 897.0, 898.0, 899.0, 900.0]]]],   [[[[901.0, 902.0, 903.0, 904.0, 905.0, 906.0],      [907.0, 908.0, 909.0, 910.0, 911.0, 912.0],      [913.0, 914.0, 915.0, 916.0, 917.0, 918.0],      [919.0, 920.0, 921.0, 922.0, 923.0, 924.0],      [925.0, 926.0, 927.0, 928.0, 929.0, 930.0]],     [[931.0, 932.0, 933.0, 934.0, 935.0, 936.0],      [937.0, 938.0, 939.0, 940.0, 941.0, 942.0],      [943.0, 944.0, 945.0, 946.0, 947.0, 948.0],      [949.0, 950.0, 951.0, 952.0, 953.0, 954.0],      [955.0, 956.0, 957.0, 958.0, 959.0, 960.0]],     [[961.0, 962.0, 963.0, 964.0, 965.0, 966.0],      [967.0, 968.0, 969.0, 970.0, 971.0, 972.0],      [973.0, 974.0, 975.0, 976.0, 977.0, 978.0],      [979.0, 980.0, 981.0, 982.0, 983.0, 984.0],      [985.0, 986.0, 987.0, 988.0, 989.0, 990.0]],     [[991.0, 992.0, 993.0, 994.0, 995.0, 996.0],      [997.0, 998.0, 999.0, 1000.0, 1001.0, 1002.0],      [1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0],      [1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0],      [1015.0, 1016.0, 1017.0, 1018.0, 1019.0, 1020.0]],     [[1021.0, 1022.0, 1023.0, 1024.0, 1025.0, 1026.0],      [1027.0, 1028.0, 1029.0, 1030.0, 1031.0, 1032.0],      [1033.0, 1034.0, 1035.0, 1036.0, 1037.0, 1038.0],      [1039.0, 1040.0, 1041.0, 1042.0, 1043.0, 1044.0],      [1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]]],    [[[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0],      [1057.0, 1058.0, 1059.0, 1060.0, 1061.0, 1062.0],      [1063.0, 1064.0, 1065.0, 1066.0, 1067.0, 1068.0],      [1069.0, 1070.0, 1071.0, 1072.0, 1073.0, 1074.0],      [1075.0, 1076.0, 1077.0, 1078.0, 1079.0, 1080.0]],     [[1081.0, 1082.0, 1083.0, 1084.0, 1085.0, 1086.0],      [1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0],      [1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0],      [1099.0, 1100.0, 1101.0, 1102.0, 1103.0, 1104.0],      [1105.0, 1106.0, 1107.0, 1108.0, 1109.0, 1110.0]],     [[1111.0, 1112.0, 1113.0, 1114.0, 1115.0, 1116.0],      [1117.0, 1118.0, 1119.0, 1120.0, 1121.0, 1122.0],      [1123.0, 1124.0, 1125.0, 1126.0, 1127.0, 1128.0],      [1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0],      [1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0]],     [[1141.0, 1142.0, 1143.0, 1144.0, 1145.0, 1146.0],      [1147.0, 1148.0, 1149.0, 1150.0, 1151.0, 1152.0],      [1153.0, 1154.0, 1155.0, 1156.0, 1157.0, 1158.0],      [1159.0, 1160.0, 1161.0, 1162.0, 1163.0, 1164.0],      [1165.0, 1166.0, 1167.0, 1168.0, 1169.0, 1170.0]],     [[1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0],      [1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0],      [1183.0, 1184.0, 1185.0, 1186.0, 1187.0, 1188.0],      [1189.0, 1190.0, 1191.0, 1192.0, 1193.0, 1194.0],      [1195.0, 1196.0, 1197.0, 1198.0, 1199.0, 1200.0]]],    [[[1201.0, 1202.0, 1203.0, 1204.0, 1205.0, 1206.0],      [1207.0, 1208.0, 1209.0, 1210.0, 1211.0, 1212.0],      [1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0],      [1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0],      [1225.0, 1226.0, 1227.0, 1228.0, 1229.0, 1230.0]],     [[1231.0, 1232.0, 1233.0, 1234.0, 1235.0, 1236.0],      [1237.0, 1238.0, 1239.0, 1240.0, 1241.0, 1242.0],      [1243.0, 1244.0, 1245.0, 1246.0, 1247.0, 1248.0],      [1249.0, 1250.0, 1251.0, 1252.0, 1253.0, 1254.0],      [1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]],     [[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0],      [1267.0, 1268.0, 1269.0, 1270.0, 1271.0, 1272.0],      [1273.0, 1274.0, 1275.0, 1276.0, 1277.0, 1278.0],      [1279.0, 1280.0, 1281.0, 1282.0, 1283.0, 1284.0],      [1285.0, 1286.0, 1287.0, 1288.0, 1289.0, 1290.0]],     [[1291.0, 1292.0, 1293.0, 1294.0, 1295.0, 1296.0],      [1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0],      [1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0],      [1309.0, 1310.0, 1311.0, 1312.0, 1313.0, 1314.0],      [1315.0, 1316.0, 1317.0, 1318.0, 1319.0, 1320.0]],     [[1321.0, 1322.0, 1323.0, 1324.0, 1325.0, 1326.0],      [1327.0, 1328.0, 1329.0, 1330.0, 1331.0, 1332.0],      [1333.0, 1334.0, 1335.0, 1336.0, 1337.0, 1338.0],      [1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0],      [1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0]]],    [[[1351.0, 1352.0, 1353.0, 1354.0, 1355.0, 1356.0],      [1357.0, 1358.0, 1359.0, 1360.0, 1361.0, 1362.0],      [1363.0, 1364.0, 1365.0, 1366.0, 1367.0, 1368.0],      [1369.0, 1370.0, 1371.0, 1372.0, 1373.0, 1374.0],      [1375.0, 1376.0, 1377.0, 1378.0, 1379.0, 1380.0]],     [[1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0],      [1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0],      [1393.0, 1394.0, 1395.0, 1396.0, 1397.0, 1398.0],      [1399.0, 1400.0, 1401.0, 1402.0, 1403.0, 1404.0],      [1405.0, 1406.0, 1407.0, 1408.0, 1409.0, 1410.0]],     [[1411.0, 1412.0, 1413.0, 1414.0, 1415.0, 1416.0],      [1417.0, 1418.0, 1419.0, 1420.0, 1421.0, 1422.0],      [1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0],      [1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0],      [1435.0, 1436.0, 1437.0, 1438.0, 1439.0, 1440.0]],     [[1441.0, 1442.0, 1443.0, 1444.0, 1445.0, 1446.0],      [1447.0, 1448.0, 1449.0, 1450.0, 1451.0, 1452.0],      [1453.0, 1454.0, 1455.0, 1456.0, 1457.0, 1458.0],      [1459.0, 1460.0, 1461.0, 1462.0, 1463.0, 1464.0],      [1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0]],     [[1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0],      [1477.0, 1478.0, 1479.0, 1480.0, 1481.0, 1482.0],      [1483.0, 1484.0, 1485.0, 1486.0, 1487.0, 1488.0],      [1489.0, 1490.0, 1491.0, 1492.0, 1493.0, 1494.0],      [1495.0, 1496.0, 1497.0, 1498.0, 1499.0, 1500.0]]],    [[[1501.0, 1502.0, 1503.0, 1504.0, 1505.0, 1506.0],      [1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0],      [1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0],      [1519.0, 1520.0, 1521.0, 1522.0, 1523.0, 1524.0],      [1525.0, 1526.0, 1527.0, 1528.0, 1529.0, 1530.0]],     [[1531.0, 1532.0, 1533.0, 1534.0, 1535.0, 1536.0],      [1537.0, 1538.0, 1539.0, 1540.0, 1541.0, 1542.0],      [1543.0, 1544.0, 1545.0, 1546.0, 1547.0, 1548.0],      [1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0],      [1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0]],     [[1561.0, 1562.0, 1563.0, 1564.0, 1565.0, 1566.0],      [1567.0, 1568.0, 1569.0, 1570.0, 1571.0, 1572.0],      [1573.0, 1574.0, 1575.0, 1576.0, 1577.0, 1578.0],      [1579.0, 1580.0, 1581.0, 1582.0, 1583.0, 1584.0],      [1585.0, 1586.0, 1587.0, 1588.0, 1589.0, 1590.0]],     [[1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0],      [1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0],      [1603.0, 1604.0, 1605.0, 1606.0, 1607.0, 1608.0],      [1609.0, 1610.0, 1611.0, 1612.0, 1613.0, 1614.0],      [1615.0, 1616.0, 1617.0, 1618.0, 1619.0, 1620.0]],     [[1621.0, 1622.0, 1623.0, 1624.0, 1625.0, 1626.0],      [1627.0, 1628.0, 1629.0, 1630.0, 1631.0, 1632.0],      [1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0],      [1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0],      [1645.0, 1646.0, 1647.0, 1648.0, 1649.0, 1650.0]]],    [[[1651.0, 1652.0, 1653.0, 1654.0, 1655.0, 1656.0],      [1657.0, 1658.0, 1659.0, 1660.0, 1661.0, 1662.0],      [1663.0, 1664.0, 1665.0, 1666.0, 1667.0, 1668.0],      [1669.0, 1670.0, 1671.0, 1672.0, 1673.0, 1674.0],      [1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]],     [[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0],      [1687.0, 1688.0, 1689.0, 1690.0, 1691.0, 1692.0],      [1693.0, 1694.0, 1695.0, 1696.0, 1697.0, 1698.0],      [1699.0, 1700.0, 1701.0, 1702.0, 1703.0, 1704.0],      [1705.0, 1706.0, 1707.0, 1708.0, 1709.0, 1710.0]],     [[1711.0, 1712.0, 1713.0, 1714.0, 1715.0, 1716.0],      [1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0],      [1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0],      [1729.0, 1730.0, 1731.0, 1732.0, 1733.0, 1734.0],      [1735.0, 1736.0, 1737.0, 1738.0, 1739.0, 1740.0]],     [[1741.0, 1742.0, 1743.0, 1744.0, 1745.0, 1746.0],      [1747.0, 1748.0, 1749.0, 1750.0, 1751.0, 1752.0],      [1753.0, 1754.0, 1755.0, 1756.0, 1757.0, 1758.0],      [1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0],      [1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0]],     [[1771.0, 1772.0, 1773.0, 1774.0, 1775.0, 1776.0],      [1777.0, 1778.0, 1779.0, 1780.0, 1781.0, 1782.0],      [1783.0, 1784.0, 1785.0, 1786.0, 1787.0, 1788.0],      [1789.0, 1790.0, 1791.0, 1792.0, 1793.0, 1794.0],      [1795.0, 1796.0, 1797.0, 1798.0, 1799.0, 1800.0]]]],   [[[[1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0],      [1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0],      [1813.0, 1814.0, 1815.0, 1816.0, 1817.0, 1818.0],      [1819.0, 1820.0, 1821.0, 1822.0, 1823.0, 1824.0],      [1825.0, 1826.0, 1827.0, 1828.0, 1829.0, 1830.0]],     [[1831.0, 1832.0, 1833.0, 1834.0, 1835.0, 1836.0],      [1837.0, 1838.0, 1839.0, 1840.0, 1841.0, 1842.0],      [1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0],      [1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0],      [1855.0, 1856.0, 1857.0, 1858.0, 1859.0, 1860.0]],     [[1861.0, 1862.0, 1863.0, 1864.0, 1865.0, 1866.0],      [1867.0, 1868.0, 1869.0, 1870.0, 1871.0, 1872.0],      [1873.0, 1874.0, 1875.0, 1876.0, 1877.0, 1878.0],      [1879.0, 1880.0, 1881.0, 1882.0, 1883.0, 1884.0],      [1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0]],     [[1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0],      [1897.0, 1898.0, 1899.0, 1900.0, 1901.0, 1902.0],      [1903.0, 1904.0, 1905.0, 1906.0, 1907.0, 1908.0],      [1909.0, 1910.0, 1911.0, 1912.0, 1913.0, 1914.0],      [1915.0, 1916.0, 1917.0, 1918.0, 1919.0, 1920.0]],     [[1921.0, 1922.0, 1923.0, 1924.0, 1925.0, 1926.0],      [1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0],      [1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0],      [1939.0, 1940.0, 1941.0, 1942.0, 1943.0, 1944.0],      [1945.0, 1946.0, 1947.0, 1948.0, 1949.0, 1950.0]]],    [[[1951.0, 1952.0, 1953.0, 1954.0, 1955.0, 1956.0],      [1957.0, 1958.0, 1959.0, 1960.0, 1961.0, 1962.0],      [1963.0, 1964.0, 1965.0, 1966.0, 1967.0, 1968.0],      [1969.0, 1970.0, 1971.0, 1972.0, 1973.0, 1974.0],      [1975.0, 1976.0, 1977.0, 1978.0, 1979.0, 1980.0]],     [[1981.0, 1982.0, 1983.0, 1984.0, 1985.0, 1986.0],      [1987.0, 1988.0, 1989.0, 1990.0, 1991.0, 1992.0],      [1993.0, 1994.0, 1995.0, 1996.0, 1997.0, 1998.0],      [1999.0, 2000.0, 2001.0, 2002.0, 2003.0, 2004.0],      [2005.0, 2006.0, 2007.0, 2008.0, 2009.0, 2010.0]],     [[2011.0, 2012.0, 2013.0, 2014.0, 2015.0, 2016.0],      [2017.0, 2018.0, 2019.0, 2020.0, 2021.0, 2022.0],      [2023.0, 2024.0, 2025.0, 2026.0, 2027.0, 2028.0],      [2029.0, 2030.0, 2031.0, 2032.0, 2033.0, 2034.0],      [2035.0, 2036.0, 2037.0, 2038.0, 2039.0, 2040.0]],     [[2041.0, 2042.0, 2043.0, 2044.0, 2045.0, 2046.0],      [2047.0, 2048.0, 2049.0, 2050.0, 2051.0, 2052.0],      [2053.0, 2054.0, 2055.0, 2056.0, 2057.0, 2058.0],      [2059.0, 2060.0, 2061.0, 2062.0, 2063.0, 2064.0],      [2065.0, 2066.0, 2067.0, 2068.0, 2069.0, 2070.0]],     [[2071.0, 2072.0, 2073.0, 2074.0, 2075.0, 2076.0],      [2077.0, 2078.0, 2079.0, 2080.0, 2081.0, 2082.0],      [2083.0, 2084.0, 2085.0, 2086.0, 2087.0, 2088.0],      [2089.0, 2090.0, 2091.0, 2092.0, 2093.0, 2094.0],      [2095.0, 2096.0, 2097.0, 2098.0, 2099.0, 2100.0]]],    [[[2101.0, 2102.0, 2103.0, 2104.0, 2105.0, 2106.0],      [2107.0, 2108.0, 2109.0, 2110.0, 2111.0, 2112.0],      [2113.0, 2114.0, 2115.0, 2116.0, 2117.0, 2118.0],      [2119.0, 2120.0, 2121.0, 2122.0, 2123.0, 2124.0],      [2125.0, 2126.0, 2127.0, 2128.0, 2129.0, 2130.0]],     [[2131.0, 2132.0, 2133.0, 2134.0, 2135.0, 2136.0],      [2137.0, 2138.0, 2139.0, 2140.0, 2141.0, 2142.0],      [2143.0, 2144.0, 2145.0, 2146.0, 2147.0, 2148.0],      [2149.0, 2150.0, 2151.0, 2152.0, 2153.0, 2154.0],      [2155.0, 2156.0, 2157.0, 2158.0, 2159.0, 2160.0]],     [[2161.0, 2162.0, 2163.0, 2164.0, 2165.0, 2166.0],      [2167.0, 2168.0, 2169.0, 2170.0, 2171.0, 2172.0],      [2173.0, 2174.0, 2175.0, 2176.0, 2177.0, 2178.0],      [2179.0, 2180.0, 2181.0, 2182.0, 2183.0, 2184.0],      [2185.0, 2186.0, 2187.0, 2188.0, 2189.0, 2190.0]],     [[2191.0, 2192.0, 2193.0, 2194.0, 2195.0, 2196.0],      [2197.0, 2198.0, 2199.0, 2200.0, 2201.0, 2202.0],      [2203.0, 2204.0, 2205.0, 2206.0, 2207.0, 2208.0],      [2209.0, 2210.0, 2211.0, 2212.0, 2213.0, 2214.0],      [2215.0, 2216.0, 2217.0, 2218.0, 2219.0, 2220.0]],     [[2221.0, 2222.0, 2223.0, 2224.0, 2225.0, 2226.0],      [2227.0, 2228.0, 2229.0, 2230.0, 2231.0, 2232.0],      [2233.0, 2234.0, 2235.0, 2236.0, 2237.0, 2238.0],      [2239.0, 2240.0, 2241.0, 2242.0, 2243.0, 2244.0],      [2245.0, 2246.0, 2247.0, 2248.0, 2249.0, 2250.0]]],    [[[2251.0, 2252.0, 2253.0, 2254.0, 2255.0, 2256.0],      [2257.0, 2258.0, 2259.0, 2260.0, 2261.0, 2262.0],      [2263.0, 2264.0, 2265.0, 2266.0, 2267.0, 2268.0],      [2269.0, 2270.0, 2271.0, 2272.0, 2273.0, 2274.0],      [2275.0, 2276.0, 2277.0, 2278.0, 2279.0, 2280.0]],     [[2281.0, 2282.0, 2283.0, 2284.0, 2285.0, 2286.0],      [2287.0, 2288.0, 2289.0, 2290.0, 2291.0, 2292.0],      [2293.0, 2294.0, 2295.0, 2296.0, 2297.0, 2298.0],      [2299.0, 2300.0, 2301.0, 2302.0, 2303.0, 2304.0],      [2305.0, 2306.0, 2307.0, 2308.0, 2309.0, 2310.0]],     [[2311.0, 2312.0, 2313.0, 2314.0, 2315.0, 2316.0],      [2317.0, 2318.0, 2319.0, 2320.0, 2321.0, 2322.0],      [2323.0, 2324.0, 2325.0, 2326.0, 2327.0, 2328.0],      [2329.0, 2330.0, 2331.0, 2332.0, 2333.0, 2334.0],      [2335.0, 2336.0, 2337.0, 2338.0, 2339.0, 2340.0]],     [[2341.0, 2342.0, 2343.0, 2344.0, 2345.0, 2346.0],      [2347.0, 2348.0, 2349.0, 2350.0, 2351.0, 2352.0],      [2353.0, 2354.0, 2355.0, 2356.0, 2357.0, 2358.0],      [2359.0, 2360.0, 2361.0, 2362.0, 2363.0, 2364.0],      [2365.0, 2366.0, 2367.0, 2368.0, 2369.0, 2370.0]],     [[2371.0, 2372.0, 2373.0, 2374.0, 2375.0, 2376.0],      [2377.0, 2378.0, 2379.0, 2380.0, 2381.0, 2382.0],      [2383.0, 2384.0, 2385.0, 2386.0, 2387.0, 2388.0],      [2389.0, 2390.0, 2391.0, 2392.0, 2393.0, 2394.0],      [2395.0, 2396.0, 2397.0, 2398.0, 2399.0, 2400.0]]],    [[[2401.0, 2402.0, 2403.0, 2404.0, 2405.0, 2406.0],      [2407.0, 2408.0, 2409.0, 2410.0, 2411.0, 2412.0],      [2413.0, 2414.0, 2415.0, 2416.0, 2417.0, 2418.0],      [2419.0, 2420.0, 2421.0, 2422.0, 2423.0, 2424.0],      [2425.0, 2426.0, 2427.0, 2428.0, 2429.0, 2430.0]],     [[2431.0, 2432.0, 2433.0, 2434.0, 2435.0, 2436.0],      [2437.0, 2438.0, 2439.0, 2440.0, 2441.0, 2442.0],      [2443.0, 2444.0, 2445.0, 2446.0, 2447.0, 2448.0],      [2449.0, 2450.0, 2451.0, 2452.0, 2453.0, 2454.0],      [2455.0, 2456.0, 2457.0, 2458.0, 2459.0, 2460.0]],     [[2461.0, 2462.0, 2463.0, 2464.0, 2465.0, 2466.0],      [2467.0, 2468.0, 2469.0, 2470.0, 2471.0, 2472.0],      [2473.0, 2474.0, 2475.0, 2476.0, 2477.0, 2478.0],      [2479.0, 2480.0, 2481.0, 2482.0, 2483.0, 2484.0],      [2485.0, 2486.0, 2487.0, 2488.0, 2489.0, 2490.0]],     [[2491.0, 2492.0, 2493.0, 2494.0, 2495.0, 2496.0],      [2497.0, 2498.0, 2499.0, 2500.0, 2501.0, 2502.0],      [2503.0, 2504.0, 2505.0, 2506.0, 2507.0, 2508.0],      [2509.0, 2510.0, 2511.0, 2512.0, 2513.0, 2514.0],      [2515.0, 2516.0, 2517.0, 2518.0, 2519.0, 2520.0]],     [[2521.0, 2522.0, 2523.0, 2524.0, 2525.0, 2526.0],      [2527.0, 2528.0, 2529.0, 2530.0, 2531.0, 2532.0],      [2533.0, 2534.0, 2535.0, 2536.0, 2537.0, 2538.0],      [2539.0, 2540.0, 2541.0, 2542.0, 2543.0, 2544.0],      [2545.0, 2546.0, 2547.0, 2548.0, 2549.0, 2550.0]]],    [[[2551.0, 2552.0, 2553.0, 2554.0, 2555.0, 2556.0],      [2557.0, 2558.0, 2559.0, 2560.0, 2561.0, 2562.0],      [2563.0, 2564.0, 2565.0, 2566.0, 2567.0, 2568.0],      [2569.0, 2570.0, 2571.0, 2572.0, 2573.0, 2574.0],      [2575.0, 2576.0, 2577.0, 2578.0, 2579.0, 2580.0]],     [[2581.0, 2582.0, 2583.0, 2584.0, 2585.0, 2586.0],      [2587.0, 2588.0, 2589.0, 2590.0, 2591.0, 2592.0],      [2593.0, 2594.0, 2595.0, 2596.0, 2597.0, 2598.0],      [2599.0, 2600.0, 2601.0, 2602.0, 2603.0, 2604.0],      [2605.0, 2606.0, 2607.0, 2608.0, 2609.0, 2610.0]],     [[2611.0, 2612.0, 2613.0, 2614.0, 2615.0, 2616.0],      [2617.0, 2618.0, 2619.0, 2620.0, 2621.0, 2622.0],      [2623.0, 2624.0, 2625.0, 2626.0, 2627.0, 2628.0],      [2629.0, 2630.0, 2631.0, 2632.0, 2633.0, 2634.0],      [2635.0, 2636.0, 2637.0, 2638.0, 2639.0, 2640.0]],     [[2641.0, 2642.0, 2643.0, 2644.0, 2645.0, 2646.0],      [2647.0, 2648.0, 2649.0, 2650.0, 2651.0, 2652.0],      [2653.0, 2654.0, 2655.0, 2656.0, 2657.0, 2658.0],      [2659.0, 2660.0, 2661.0, 2662.0, 2663.0, 2664.0],      [2665.0, 2666.0, 2667.0, 2668.0, 2669.0, 2670.0]],     [[2671.0, 2672.0, 2673.0, 2674.0, 2675.0, 2676.0],      [2677.0, 2678.0, 2679.0, 2680.0, 2681.0, 2682.0],      [2683.0, 2684.0, 2685.0, 2686.0, 2687.0, 2688.0],      [2689.0, 2690.0, 2691.0, 2692.0, 2693.0, 2694.0],      [2695.0, 2696.0, 2697.0, 2698.0, 2699.0, 2700.0]]]],   [[[[2701.0, 2702.0, 2703.0, 2704.0, 2705.0, 2706.0],      [2707.0, 2708.0, 2709.0, 2710.0, 2711.0, 2712.0],      [2713.0, 2714.0, 2715.0, 2716.0, 2717.0, 2718.0],      [2719.0, 2720.0, 2721.0, 2722.0, 2723.0, 2724.0],      [2725.0, 2726.0, 2727.0, 2728.0, 2729.0, 2730.0]],     [[2731.0, 2732.0, 2733.0, 2734.0, 2735.0, 2736.0],      [2737.0, 2738.0, 2739.0, 2740.0, 2741.0, 2742.0],      [2743.0, 2744.0, 2745.0, 2746.0, 2747.0, 2748.0],      [2749.0, 2750.0, 2751.0, 2752.0, 2753.0, 2754.0],      [2755.0, 2756.0, 2757.0, 2758.0, 2759.0, 2760.0]],     [[2761.0, 2762.0, 2763.0, 2764.0, 2765.0, 2766.0],      [2767.0, 2768.0, 2769.0, 2770.0, 2771.0, 2772.0],      [2773.0, 2774.0, 2775.0, 2776.0, 2777.0, 2778.0],      [2779.0, 2780.0, 2781.0, 2782.0, 2783.0, 2784.0],      [2785.0, 2786.0, 2787.0, 2788.0, 2789.0, 2790.0]],     [[2791.0, 2792.0, 2793.0, 2794.0, 2795.0, 2796.0],      [2797.0, 2798.0, 2799.0, 2800.0, 2801.0, 2802.0],      [2803.0, 2804.0, 2805.0, 2806.0, 2807.0, 2808.0],      [2809.0, 2810.0, 2811.0, 2812.0, 2813.0, 2814.0],      [2815.0, 2816.0, 2817.0, 2818.0, 2819.0, 2820.0]],     [[2821.0, 2822.0, 2823.0, 2824.0, 2825.0, 2826.0],      [2827.0, 2828.0, 2829.0, 2830.0, 2831.0, 2832.0],      [2833.0, 2834.0, 2835.0, 2836.0, 2837.0, 2838.0],      [2839.0, 2840.0, 2841.0, 2842.0, 2843.0, 2844.0],      [2845.0, 2846.0, 2847.0, 2848.0, 2849.0, 2850.0]]],    [[[2851.0, 2852.0, 2853.0, 2854.0, 2855.0, 2856.0],      [2857.0, 2858.0, 2859.0, 2860.0, 2861.0, 2862.0],      [2863.0, 2864.0, 2865.0, 2866.0, 2867.0, 2868.0],      [2869.0, 2870.0, 2871.0, 2872.0, 2873.0, 2874.0],      [2875.0, 2876.0, 2877.0, 2878.0, 2879.0, 2880.0]],     [[2881.0, 2882.0, 2883.0, 2884.0, 2885.0, 2886.0],      [2887.0, 2888.0, 2889.0, 2890.0, 2891.0, 2892.0],      [2893.0, 2894.0, 2895.0, 2896.0, 2897.0, 2898.0],      [2899.0, 2900.0, 2901.0, 2902.0, 2903.0, 2904.0],      [2905.0, 2906.0, 2907.0, 2908.0, 2909.0, 2910.0]],     [[2911.0, 2912.0, 2913.0, 2914.0, 2915.0, 2916.0],      [2917.0, 2918.0, 2919.0, 2920.0, 2921.0, 2922.0],      [2923.0, 2924.0, 2925.0, 2926.0, 2927.0, 2928.0],      [2929.0, 2930.0, 2931.0, 2932.0, 2933.0, 2934.0],      [2935.0, 2936.0, 2937.0, 2938.0, 2939.0, 2940.0]],     [[2941.0, 2942.0, 2943.0, 2944.0, 2945.0, 2946.0],      [2947.0, 2948.0, 2949.0, 2950.0, 2951.0, 2952.0],      [2953.0, 2954.0, 2955.0, 2956.0, 2957.0, 2958.0],      [2959.0, 2960.0, 2961.0, 2962.0, 2963.0, 2964.0],      [2965.0, 2966.0, 2967.0, 2968.0, 2969.0, 2970.0]],     [[2971.0, 2972.0, 2973.0, 2974.0, 2975.0, 2976.0],      [2977.0, 2978.0, 2979.0, 2980.0, 2981.0, 2982.0],      [2983.0, 2984.0, 2985.0, 2986.0, 2987.0, 2988.0],      [2989.0, 2990.0, 2991.0, 2992.0, 2993.0, 2994.0],      [2995.0, 2996.0, 2997.0, 2998.0, 2999.0, 3000.0]]],    [[[3001.0, 3002.0, 3003.0, 3004.0, 3005.0, 3006.0],      [3007.0, 3008.0, 3009.0, 3010.0, 3011.0, 3012.0],      [3013.0, 3014.0, 3015.0, 3016.0, 3017.0, 3018.0],      [3019.0, 3020.0, 3021.0, 3022.0, 3023.0, 3024.0],      [3025.0, 3026.0, 3027.0, 3028.0, 3029.0, 3030.0]],     [[3031.0, 3032.0, 3033.0, 3034.0, 3035.0, 3036.0],      [3037.0, 3038.0, 3039.0, 3040.0, 3041.0, 3042.0],      [3043.0, 3044.0, 3045.0, 3046.0, 3047.0, 3048.0],      [3049.0, 3050.0, 3051.0, 3052.0, 3053.0, 3054.0],      [3055.0, 3056.0, 3057.0, 3058.0, 3059.0, 3060.0]],     [[3061.0, 3062.0, 3063.0, 3064.0, 3065.0, 3066.0],      [3067.0, 3068.0, 3069.0, 3070.0, 3071.0, 3072.0],      [3073.0, 3074.0, 3075.0, 3076.0, 3077.0, 3078.0],      [3079.0, 3080.0, 3081.0, 3082.0, 3083.0, 3084.0],      [3085.0, 3086.0, 3087.0, 3088.0, 3089.0, 3090.0]],     [[3091.0, 3092.0, 3093.0, 3094.0, 3095.0, 3096.0],      [3097.0, 3098.0, 3099.0, 3100.0, 3101.0, 3102.0],      [3103.0, 3104.0, 3105.0, 3106.0, 3107.0, 3108.0],      [3109.0, 3110.0, 3111.0, 3112.0, 3113.0, 3114.0],      [3115.0, 3116.0, 3117.0, 3118.0, 3119.0, 3120.0]],     [[3121.0, 3122.0, 3123.0, 3124.0, 3125.0, 3126.0],      [3127.0, 3128.0, 3129.0, 3130.0, 3131.0, 3132.0],      [3133.0, 3134.0, 3135.0, 3136.0, 3137.0, 3138.0],      [3139.0, 3140.0, 3141.0, 3142.0, 3143.0, 3144.0],      [3145.0, 3146.0, 3147.0, 3148.0, 3149.0, 3150.0]]],    [[[3151.0, 3152.0, 3153.0, 3154.0, 3155.0, 3156.0],      [3157.0, 3158.0, 3159.0, 3160.0, 3161.0, 3162.0],      [3163.0, 3164.0, 3165.0, 3166.0, 3167.0, 3168.0],      [3169.0, 3170.0, 3171.0, 3172.0, 3173.0, 3174.0],      [3175.0, 3176.0, 3177.0, 3178.0, 3179.0, 3180.0]],     [[3181.0, 3182.0, 3183.0, 3184.0, 3185.0, 3186.0],      [3187.0, 3188.0, 3189.0, 3190.0, 3191.0, 3192.0],      [3193.0, 3194.0, 3195.0, 3196.0, 3197.0, 3198.0],      [3199.0, 3200.0, 3201.0, 3202.0, 3203.0, 3204.0],      [3205.0, 3206.0, 3207.0, 3208.0, 3209.0, 3210.0]],     [[3211.0, 3212.0, 3213.0, 3214.0, 3215.0, 3216.0],      [3217.0, 3218.0, 3219.0, 3220.0, 3221.0, 3222.0],      [3223.0, 3224.0, 3225.0, 3226.0, 3227.0, 3228.0],      [3229.0, 3230.0, 3231.0, 3232.0, 3233.0, 3234.0],      [3235.0, 3236.0, 3237.0, 3238.0, 3239.0, 3240.0]],     [[3241.0, 3242.0, 3243.0, 3244.0, 3245.0, 3246.0],      [3247.0, 3248.0, 3249.0, 3250.0, 3251.0, 3252.0],      [3253.0, 3254.0, 3255.0, 3256.0, 3257.0, 3258.0],      [3259.0, 3260.0, 3261.0, 3262.0, 3263.0, 3264.0],      [3265.0, 3266.0, 3267.0, 3268.0, 3269.0, 3270.0]],     [[3271.0, 3272.0, 3273.0, 3274.0, 3275.0, 3276.0],      [3277.0, 3278.0, 3279.0, 3280.0, 3281.0, 3282.0],      [3283.0, 3284.0, 3285.0, 3286.0, 3287.0, 3288.0],      [3289.0, 3290.0, 3291.0, 3292.0, 3293.0, 3294.0],      [3295.0, 3296.0, 3297.0, 3298.0, 3299.0, 3300.0]]],    [[[3301.0, 3302.0, 3303.0, 3304.0, 3305.0, 3306.0],      [3307.0, 3308.0, 3309.0, 3310.0, 3311.0, 3312.0],      [3313.0, 3314.0, 3315.0, 3316.0, 3317.0, 3318.0],      [3319.0, 3320.0, 3321.0, 3322.0, 3323.0, 3324.0],      [3325.0, 3326.0, 3327.0, 3328.0, 3329.0, 3330.0]],     [[3331.0, 3332.0, 3333.0, 3334.0, 3335.0, 3336.0],      [3337.0, 3338.0, 3339.0, 3340.0, 3341.0, 3342.0],      [3343.0, 3344.0, 3345.0, 3346.0, 3347.0, 3348.0],      [3349.0, 3350.0, 3351.0, 3352.0, 3353.0, 3354.0],      [3355.0, 3356.0, 3357.0, 3358.0, 3359.0, 3360.0]],     [[3361.0, 3362.0, 3363.0, 3364.0, 3365.0, 3366.0],      [3367.0, 3368.0, 3369.0, 3370.0, 3371.0, 3372.0],      [3373.0, 3374.0, 3375.0, 3376.0, 3377.0, 3378.0],      [3379.0, 3380.0, 3381.0, 3382.0, 3383.0, 3384.0],      [3385.0, 3386.0, 3387.0, 3388.0, 3389.0, 3390.0]],     [[3391.0, 3392.0, 3393.0, 3394.0, 3395.0, 3396.0],      [3397.0, 3398.0, 3399.0, 3400.0, 3401.0, 3402.0],      [3403.0, 3404.0, 3405.0, 3406.0, 3407.0, 3408.0],      [3409.0, 3410.0, 3411.0, 3412.0, 3413.0, 3414.0],      [3415.0, 3416.0, 3417.0, 3418.0, 3419.0, 3420.0]],     [[3421.0, 3422.0, 3423.0, 3424.0, 3425.0, 3426.0],      [3427.0, 3428.0, 3429.0, 3430.0, 3431.0, 3432.0],      [3433.0, 3434.0, 3435.0, 3436.0, 3437.0, 3438.0],      [3439.0, 3440.0, 3441.0, 3442.0, 3443.0, 3444.0],      [3445.0, 3446.0, 3447.0, 3448.0, 3449.0, 3450.0]]],    [[[3451.0, 3452.0, 3453.0, 3454.0, 3455.0, 3456.0],      [3457.0, 3458.0, 3459.0, 3460.0, 3461.0, 3462.0],      [3463.0, 3464.0, 3465.0, 3466.0, 3467.0, 3468.0],      [3469.0, 3470.0, 3471.0, 3472.0, 3473.0, 3474.0],      [3475.0, 3476.0, 3477.0, 3478.0, 3479.0, 3480.0]],     [[3481.0, 3482.0, 3483.0, 3484.0, 3485.0, 3486.0],      [3487.0, 3488.0, 3489.0, 3490.0, 3491.0, 3492.0],      [3493.0, 3494.0, 3495.0, 3496.0, 3497.0, 3498.0],      [3499.0, 3500.0, 3501.0, 3502.0, 3503.0, 3504.0],      [3505.0, 3506.0, 3507.0, 3508.0, 3509.0, 3510.0]],     [[3511.0, 3512.0, 3513.0, 3514.0, 3515.0, 3516.0],      [3517.0, 3518.0, 3519.0, 3520.0, 3521.0, 3522.0],      [3523.0, 3524.0, 3525.0, 3526.0, 3527.0, 3528.0],      [3529.0, 3530.0, 3531.0, 3532.0, 3533.0, 3534.0],      [3535.0, 3536.0, 3537.0, 3538.0, 3539.0, 3540.0]],     [[3541.0, 3542.0, 3543.0, 3544.0, 3545.0, 3546.0],      [3547.0, 3548.0, 3549.0, 3550.0, 3551.0, 3552.0],      [3553.0, 3554.0, 3555.0, 3556.0, 3557.0, 3558.0],      [3559.0, 3560.0, 3561.0, 3562.0, 3563.0, 3564.0],      [3565.0, 3566.0, 3567.0, 3568.0, 3569.0, 3570.0]],     [[3571.0, 3572.0, 3573.0, 3574.0, 3575.0, 3576.0],      [3577.0, 3578.0, 3579.0, 3580.0, 3581.0, 3582.0],      [3583.0, 3584.0, 3585.0, 3586.0, 3587.0, 3588.0],      [3589.0, 3590.0, 3591.0, 3592.0, 3593.0, 3594.0],      [3595.0, 3596.0, 3597.0, 3598.0, 3599.0, 3600.0]]]],   [[[[3601.0, 3602.0, 3603.0, 3604.0, 3605.0, 3606.0],      [3607.0, 3608.0, 3609.0, 3610.0, 3611.0, 3612.0],      [3613.0, 3614.0, 3615.0, 3616.0, 3617.0, 3618.0],      [3619.0, 3620.0, 3621.0, 3622.0, 3623.0, 3624.0],      [3625.0, 3626.0, 3627.0, 3628.0, 3629.0, 3630.0]],     [[3631.0, 3632.0, 3633.0, 3634.0, 3635.0, 3636.0],      [3637.0, 3638.0, 3639.0, 3640.0, 3641.0, 3642.0],      [3643.0, 3644.0, 3645.0, 3646.0, 3647.0, 3648.0],      [3649.0, 3650.0, 3651.0, 3652.0, 3653.0, 3654.0],      [3655.0, 3656.0, 3657.0, 3658.0, 3659.0, 3660.0]],     [[3661.0, 3662.0, 3663.0, 3664.0, 3665.0, 3666.0],      [3667.0, 3668.0, 3669.0, 3670.0, 3671.0, 3672.0],      [3673.0, 3674.0, 3675.0, 3676.0, 3677.0, 3678.0],      [3679.0, 3680.0, 3681.0, 3682.0, 3683.0, 3684.0],      [3685.0, 3686.0, 3687.0, 3688.0, 3689.0, 3690.0]],     [[3691.0, 3692.0, 3693.0, 3694.0, 3695.0, 3696.0],      [3697.0, 3698.0, 3699.0, 3700.0, 3701.0, 3702.0],      [3703.0, 3704.0, 3705.0, 3706.0, 3707.0, 3708.0],      [3709.0, 3710.0, 3711.0, 3712.0, 3713.0, 3714.0],      [3715.0, 3716.0, 3717.0, 3718.0, 3719.0, 3720.0]],     [[3721.0, 3722.0, 3723.0, 3724.0, 3725.0, 3726.0],      [3727.0, 3728.0, 3729.0, 3730.0, 3731.0, 3732.0],      [3733.0, 3734.0, 3735.0, 3736.0, 3737.0, 3738.0],      [3739.0, 3740.0, 3741.0, 3742.0, 3743.0, 3744.0],      [3745.0, 3746.0, 3747.0, 3748.0, 3749.0, 3750.0]]],    [[[3751.0, 3752.0, 3753.0, 3754.0, 3755.0, 3756.0],      [3757.0, 3758.0, 3759.0, 3760.0, 3761.0, 3762.0],      [3763.0, 3764.0, 3765.0, 3766.0, 3767.0, 3768.0],      [3769.0, 3770.0, 3771.0, 3772.0, 3773.0, 3774.0],      [3775.0, 3776.0, 3777.0, 3778.0, 3779.0, 3780.0]],     [[3781.0, 3782.0, 3783.0, 3784.0, 3785.0, 3786.0],      [3787.0, 3788.0, 3789.0, 3790.0, 3791.0, 3792.0],      [3793.0, 3794.0, 3795.0, 3796.0, 3797.0, 3798.0],      [3799.0, 3800.0, 3801.0, 3802.0, 3803.0, 3804.0],      [3805.0, 3806.0, 3807.0, 3808.0, 3809.0, 3810.0]],     [[3811.0, 3812.0, 3813.0, 3814.0, 3815.0, 3816.0],      [3817.0, 3818.0, 3819.0, 3820.0, 3821.0, 3822.0],      [3823.0, 3824.0, 3825.0, 3826.0, 3827.0, 3828.0],      [3829.0, 3830.0, 3831.0, 3832.0, 3833.0, 3834.0],      [3835.0, 3836.0, 3837.0, 3838.0, 3839.0, 3840.0]],     [[3841.0, 3842.0, 3843.0, 3844.0, 3845.0, 3846.0],      [3847.0, 3848.0, 3849.0, 3850.0, 3851.0, 3852.0],      [3853.0, 3854.0, 3855.0, 3856.0, 3857.0, 3858.0],      [3859.0, 3860.0, 3861.0, 3862.0, 3863.0, 3864.0],      [3865.0, 3866.0, 3867.0, 3868.0, 3869.0, 3870.0]],     [[3871.0, 3872.0, 3873.0, 3874.0, 3875.0, 3876.0],      [3877.0, 3878.0, 3879.0, 3880.0, 3881.0, 3882.0],      [3883.0, 3884.0, 3885.0, 3886.0, 3887.0, 3888.0],      [3889.0, 3890.0, 3891.0, 3892.0, 3893.0, 3894.0],      [3895.0, 3896.0, 3897.0, 3898.0, 3899.0, 3900.0]]],    [[[3901.0, 3902.0, 3903.0, 3904.0, 3905.0, 3906.0],      [3907.0, 3908.0, 3909.0, 3910.0, 3911.0, 3912.0],      [3913.0, 3914.0, 3915.0, 3916.0, 3917.0, 3918.0],      [3919.0, 3920.0, 3921.0, 3922.0, 3923.0, 3924.0],      [3925.0, 3926.0, 3927.0, 3928.0, 3929.0, 3930.0]],     [[3931.0, 3932.0, 3933.0, 3934.0, 3935.0, 3936.0],      [3937.0, 3938.0, 3939.0, 3940.0, 3941.0, 3942.0],      [3943.0, 3944.0, 3945.0, 3946.0, 3947.0, 3948.0],      [3949.0, 3950.0, 3951.0, 3952.0, 3953.0, 3954.0],      [3955.0, 3956.0, 3957.0, 3958.0, 3959.0, 3960.0]],     [[3961.0, 3962.0, 3963.0, 3964.0, 3965.0, 3966.0],      [3967.0, 3968.0, 3969.0, 3970.0, 3971.0, 3972.0],      [3973.0, 3974.0, 3975.0, 3976.0, 3977.0, 3978.0],      [3979.0, 3980.0, 3981.0, 3982.0, 3983.0, 3984.0],      [3985.0, 3986.0, 3987.0, 3988.0, 3989.0, 3990.0]],     [[3991.0, 3992.0, 3993.0, 3994.0, 3995.0, 3996.0],      [3997.0, 3998.0, 3999.0, 4000.0, 4001.0, 4002.0],      [4003.0, 4004.0, 4005.0, 4006.0, 4007.0, 4008.0],      [4009.0, 4010.0, 4011.0, 4012.0, 4013.0, 4014.0],      [4015.0, 4016.0, 4017.0, 4018.0, 4019.0, 4020.0]],     [[4021.0, 4022.0, 4023.0, 4024.0, 4025.0, 4026.0],      [4027.0, 4028.0, 4029.0, 4030.0, 4031.0, 4032.0],      [4033.0, 4034.0, 4035.0, 4036.0, 4037.0, 4038.0],      [4039.0, 4040.0, 4041.0, 4042.0, 4043.0, 4044.0],      [4045.0, 4046.0, 4047.0, 4048.0, 4049.0, 4050.0]]],    [[[4051.0, 4052.0, 4053.0, 4054.0, 4055.0, 4056.0],      [4057.0, 4058.0, 4059.0, 4060.0, 4061.0, 4062.0],      [4063.0, 4064.0, 4065.0, 4066.0, 4067.0, 4068.0],      [4069.0, 4070.0, 4071.0, 4072.0, 4073.0, 4074.0],      [4075.0, 4076.0, 4077.0, 4078.0, 4079.0, 4080.0]],     [[4081.0, 4082.0, 4083.0, 4084.0, 4085.0, 4086.0],      [4087.0, 4088.0, 4089.0, 4090.0, 4091.0, 4092.0],      [4093.0, 4094.0, 4095.0, 4096.0, 4097.0, 4098.0],      [4099.0, 4100.0, 4101.0, 4102.0, 4103.0, 4104.0],      [4105.0, 4106.0, 4107.0, 4108.0, 4109.0, 4110.0]],     [[4111.0, 4112.0, 4113.0, 4114.0, 4115.0, 4116.0],      [4117.0, 4118.0, 4119.0, 4120.0, 4121.0, 4122.0],      [4123.0, 4124.0, 4125.0, 4126.0, 4127.0, 4128.0],      [4129.0, 4130.0, 4131.0, 4132.0, 4133.0, 4134.0],      [4135.0, 4136.0, 4137.0, 4138.0, 4139.0, 4140.0]],     [[4141.0, 4142.0, 4143.0, 4144.0, 4145.0, 4146.0],      [4147.0, 4148.0, 4149.0, 4150.0, 4151.0, 4152.0],      [4153.0, 4154.0, 4155.0, 4156.0, 4157.0, 4158.0],      [4159.0, 4160.0, 4161.0, 4162.0, 4163.0, 4164.0],      [4165.0, 4166.0, 4167.0, 4168.0, 4169.0, 4170.0]],     [[4171.0, 4172.0, 4173.0, 4174.0, 4175.0, 4176.0],      [4177.0, 4178.0, 4179.0, 4180.0, 4181.0, 4182.0],      [4183.0, 4184.0, 4185.0, 4186.0, 4187.0, 4188.0],      [4189.0, 4190.0, 4191.0, 4192.0, 4193.0, 4194.0],      [4195.0, 4196.0, 4197.0, 4198.0, 4199.0, 4200.0]]],    [[[4201.0, 4202.0, 4203.0, 4204.0, 4205.0, 4206.0],      [4207.0, 4208.0, 4209.0, 4210.0, 4211.0, 4212.0],      [4213.0, 4214.0, 4215.0, 4216.0, 4217.0, 4218.0],      [4219.0, 4220.0, 4221.0, 4222.0, 4223.0, 4224.0],      [4225.0, 4226.0, 4227.0, 4228.0, 4229.0, 4230.0]],     [[4231.0, 4232.0, 4233.0, 4234.0, 4235.0, 4236.0],      [4237.0, 4238.0, 4239.0, 4240.0, 4241.0, 4242.0],      [4243.0, 4244.0, 4245.0, 4246.0, 4247.0, 4248.0],      [4249.0, 4250.0, 4251.0, 4252.0, 4253.0, 4254.0],      [4255.0, 4256.0, 4257.0, 4258.0, 4259.0, 4260.0]],     [[4261.0, 4262.0, 4263.0, 4264.0, 4265.0, 4266.0],      [4267.0, 4268.0, 4269.0, 4270.0, 4271.0, 4272.0],      [4273.0, 4274.0, 4275.0, 4276.0, 4277.0, 4278.0],      [4279.0, 4280.0, 4281.0, 4282.0, 4283.0, 4284.0],      [4285.0, 4286.0, 4287.0, 4288.0, 4289.0, 4290.0]],     [[4291.0, 4292.0, 4293.0, 4294.0, 4295.0, 4296.0],      [4297.0, 4298.0, 4299.0, 4300.0, 4301.0, 4302.0],      [4303.0, 4304.0, 4305.0, 4306.0, 4307.0, 4308.0],      [4309.0, 4310.0, 4311.0, 4312.0, 4313.0, 4314.0],      [4315.0, 4316.0, 4317.0, 4318.0, 4319.0, 4320.0]],     [[4321.0, 4322.0, 4323.0, 4324.0, 4325.0, 4326.0],      [4327.0, 4328.0, 4329.0, 4330.0, 4331.0, 4332.0],      [4333.0, 4334.0, 4335.0, 4336.0, 4337.0, 4338.0],      [4339.0, 4340.0, 4341.0, 4342.0, 4343.0, 4344.0],      [4345.0, 4346.0, 4347.0, 4348.0, 4349.0, 4350.0]]],    [[[4351.0, 4352.0, 4353.0, 4354.0, 4355.0, 4356.0],      [4357.0, 4358.0, 4359.0, 4360.0, 4361.0, 4362.0],      [4363.0, 4364.0, 4365.0, 4366.0, 4367.0, 4368.0],      [4369.0, 4370.0, 4371.0, 4372.0, 4373.0, 4374.0],      [4375.0, 4376.0, 4377.0, 4378.0, 4379.0, 4380.0]],     [[4381.0, 4382.0, 4383.0, 4384.0, 4385.0, 4386.0],      [4387.0, 4388.0, 4389.0, 4390.0, 4391.0, 4392.0],      [4393.0, 4394.0, 4395.0, 4396.0, 4397.0, 4398.0],      [4399.0, 4400.0, 4401.0, 4402.0, 4403.0, 4404.0],      [4405.0, 4406.0, 4407.0, 4408.0, 4409.0, 4410.0]],     [[4411.0, 4412.0, 4413.0, 4414.0, 4415.0, 4416.0],      [4417.0, 4418.0, 4419.0, 4420.0, 4421.0, 4422.0],      [4423.0, 4424.0, 4425.0, 4426.0, 4427.0, 4428.0],      [4429.0, 4430.0, 4431.0, 4432.0, 4433.0, 4434.0],      [4435.0, 4436.0, 4437.0, 4438.0, 4439.0, 4440.0]],     [[4441.0, 4442.0, 4443.0, 4444.0, 4445.0, 4446.0],      [4447.0, 4448.0, 4449.0, 4450.0, 4451.0, 4452.0],      [4453.0, 4454.0, 4455.0, 4456.0, 4457.0, 4458.0],      [4459.0, 4460.0, 4461.0, 4462.0, 4463.0, 4464.0],      [4465.0, 4466.0, 4467.0, 4468.0, 4469.0, 4470.0]],     [[4471.0, 4472.0, 4473.0, 4474.0, 4475.0, 4476.0],      [4477.0, 4478.0, 4479.0, 4480.0, 4481.0, 4482.0],      [4483.0, 4484.0, 4485.0, 4486.0, 4487.0, 4488.0],      [4489.0, 4490.0, 4491.0, 4492.0, 4493.0, 4494.0],      [4495.0, 4496.0, 4497.0, 4498.0, 4499.0, 4500.0]]]]]] shape=[1, 5, 6, 5, 5, 6], strides=[4500, 900, 150, 30, 6, 1], layout=C (0x1)), I32([2, 2] shape=[2], strides=[1], layout=C | F (0x3)), I32([[2, 1],  [2, 2]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 1838335261 4037936958 2743816387 3423551424 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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]],    [[16.0, 17.0, 18.0, 19.0, 20.0],     [21.0, 22.0, 23.0, 24.0, 25.0],     [26.0, 27.0, 28.0, 29.0, 30.0]],    [[31.0, 32.0, 33.0, 34.0, 35.0],     [36.0, 37.0, 38.0, 39.0, 40.0],     [41.0, 42.0, 43.0, 44.0, 45.0]],    [[46.0, 47.0, 48.0, 49.0, 50.0],     [51.0, 52.0, 53.0, 54.0, 55.0],     [56.0, 57.0, 58.0, 59.0, 60.0]],    [[61.0, 62.0, 63.0, 64.0, 65.0],     [66.0, 67.0, 68.0, 69.0, 70.0],     [71.0, 72.0, 73.0, 74.0, 75.0]]],   [[[76.0, 77.0, 78.0, 79.0, 80.0],     [81.0, 82.0, 83.0, 84.0, 85.0],     [86.0, 87.0, 88.0, 89.0, 90.0]],    [[91.0, 92.0, 93.0, 94.0, 95.0],     [96.0, 97.0, 98.0, 99.0, 100.0],     [101.0, 102.0, 103.0, 104.0, 105.0]],    [[106.0, 107.0, 108.0, 109.0, 110.0],     [111.0, 112.0, 113.0, 114.0, 115.0],     [116.0, 117.0, 118.0, 119.0, 120.0]],    [[121.0, 122.0, 123.0, 124.0, 125.0],     [126.0, 127.0, 128.0, 129.0, 130.0],     [131.0, 132.0, 133.0, 134.0, 135.0]],    [[136.0, 137.0, 138.0, 139.0, 140.0],     [141.0, 142.0, 143.0, 144.0, 145.0],     [146.0, 147.0, 148.0, 149.0, 150.0]]],   [[[151.0, 152.0, 153.0, 154.0, 155.0],     [156.0, 157.0, 158.0, 159.0, 160.0],     [161.0, 162.0, 163.0, 164.0, 165.0]],    [[166.0, 167.0, 168.0, 169.0, 170.0],     [171.0, 172.0, 173.0, 174.0, 175.0],     [176.0, 177.0, 178.0, 179.0, 180.0]],    [[181.0, 182.0, 183.0, 184.0, 185.0],     [186.0, 187.0, 188.0, 189.0, 190.0],     [191.0, 192.0, 193.0, 194.0, 195.0]],    [[196.0, 197.0, 198.0, 199.0, 200.0],     [201.0, 202.0, 203.0, 204.0, 205.0],     [206.0, 207.0, 208.0, 209.0, 210.0]],    [[211.0, 212.0, 213.0, 214.0, 215.0],     [216.0, 217.0, 218.0, 219.0, 220.0],     [221.0, 222.0, 223.0, 224.0, 225.0]]],   [[[226.0, 227.0, 228.0, 229.0, 230.0],     [231.0, 232.0, 233.0, 234.0, 235.0],     [236.0, 237.0, 238.0, 239.0, 240.0]],    [[241.0, 242.0, 243.0, 244.0, 245.0],     [246.0, 247.0, 248.0, 249.0, 250.0],     [251.0, 252.0, 253.0, 254.0, 255.0]],    [[256.0, 257.0, 258.0, 259.0, 260.0],     [261.0, 262.0, 263.0, 264.0, 265.0],     [266.0, 267.0, 268.0, 269.0, 270.0]],    [[271.0, 272.0, 273.0, 274.0, 275.0],     [276.0, 277.0, 278.0, 279.0, 280.0],     [281.0, 282.0, 283.0, 284.0, 285.0]],    [[286.0, 287.0, 288.0, 289.0, 290.0],     [291.0, 292.0, 293.0, 294.0, 295.0],     [296.0, 297.0, 298.0, 299.0, 300.0]]],   [[[301.0, 302.0, 303.0, 304.0, 305.0],     [306.0, 307.0, 308.0, 309.0, 310.0],     [311.0, 312.0, 313.0, 314.0, 315.0]],    [[316.0, 317.0, 318.0, 319.0, 320.0],     [321.0, 322.0, 323.0, 324.0, 325.0],     [326.0, 327.0, 328.0, 329.0, 330.0]],    [[331.0, 332.0, 333.0, 334.0, 335.0],     [336.0, 337.0, 338.0, 339.0, 340.0],     [341.0, 342.0, 343.0, 344.0, 345.0]],    [[346.0, 347.0, 348.0, 349.0, 350.0],     [351.0, 352.0, 353.0, 354.0, 355.0],     [356.0, 357.0, 358.0, 359.0, 360.0]],    [[361.0, 362.0, 363.0, 364.0, 365.0],     [366.0, 367.0, 368.0, 369.0, 370.0],     [371.0, 372.0, 373.0, 374.0, 375.0]]],   [[[376.0, 377.0, 378.0, 379.0, 380.0],     [381.0, 382.0, 383.0, 384.0, 385.0],     [386.0, 387.0, 388.0, 389.0, 390.0]],    [[391.0, 392.0, 393.0, 394.0, 395.0],     [396.0, 397.0, 398.0, 399.0, 400.0],     [401.0, 402.0, 403.0, 404.0, 405.0]],    [[406.0, 407.0, 408.0, 409.0, 410.0],     [411.0, 412.0, 413.0, 414.0, 415.0],     [416.0, 417.0, 418.0, 419.0, 420.0]],    [[421.0, 422.0, 423.0, 424.0, 425.0],     [426.0, 427.0, 428.0, 429.0, 430.0],     [431.0, 432.0, 433.0, 434.0, 435.0]],    [[436.0, 437.0, 438.0, 439.0, 440.0],     [441.0, 442.0, 443.0, 444.0, 445.0],     [446.0, 447.0, 448.0, 449.0, 450.0]]],   [[[451.0, 452.0, 453.0, 454.0, 455.0],     [456.0, 457.0, 458.0, 459.0, 460.0],     [461.0, 462.0, 463.0, 464.0, 465.0]],    [[466.0, 467.0, 468.0, 469.0, 470.0],     [471.0, 472.0, 473.0, 474.0, 475.0],     [476.0, 477.0, 478.0, 479.0, 480.0]],    [[481.0, 482.0, 483.0, 484.0, 485.0],     [486.0, 487.0, 488.0, 489.0, 490.0],     [491.0, 492.0, 493.0, 494.0, 495.0]],    [[496.0, 497.0, 498.0, 499.0, 500.0],     [501.0, 502.0, 503.0, 504.0, 505.0],     [506.0, 507.0, 508.0, 509.0, 510.0]],    [[511.0, 512.0, 513.0, 514.0, 515.0],     [516.0, 517.0, 518.0, 519.0, 520.0],     [521.0, 522.0, 523.0, 524.0, 525.0]]]],  [[[[526.0, 527.0, 528.0, 529.0, 530.0],     [531.0, 532.0, 533.0, 534.0, 535.0],     [536.0, 537.0, 538.0, 539.0, 540.0]],    [[541.0, 542.0, 543.0, 544.0, 545.0],     [546.0, 547.0, 548.0, 549.0, 550.0],     [551.0, 552.0, 553.0, 554.0, 555.0]],    [[556.0, 557.0, 558.0, 559.0, 560.0],     [561.0, 562.0, 563.0, 564.0, 565.0],     [566.0, 567.0, 568.0, 569.0, 570.0]],    [[571.0, 572.0, 573.0, 574.0, 575.0],     [576.0, 577.0, 578.0, 579.0, 580.0],     [581.0, 582.0, 583.0, 584.0, 585.0]],    [[586.0, 587.0, 588.0, 589.0, 590.0],     [591.0, 592.0, 593.0, 594.0, 595.0],     [596.0, 597.0, 598.0, 599.0, 600.0]]],   [[[601.0, 602.0, 603.0, 604.0, 605.0],     [606.0, 607.0, 608.0, 609.0, 610.0],     [611.0, 612.0, 613.0, 614.0, 615.0]],    [[616.0, 617.0, 618.0, 619.0, 620.0],     [621.0, 622.0, 623.0, 624.0, 625.0],     [626.0, 627.0, 628.0, 629.0, 630.0]],    [[631.0, 632.0, 633.0, 634.0, 635.0],     [636.0, 637.0, 638.0, 639.0, 640.0],     [641.0, 642.0, 643.0, 644.0, 645.0]],    [[646.0, 647.0, 648.0, 649.0, 650.0],     [651.0, 652.0, 653.0, 654.0, 655.0],     [656.0, 657.0, 658.0, 659.0, 660.0]],    [[661.0, 662.0, 663.0, 664.0, 665.0],     [666.0, 667.0, 668.0, 669.0, 670.0],     [671.0, 672.0, 673.0, 674.0, 675.0]]],   [[[676.0, 677.0, 678.0, 679.0, 680.0],     [681.0, 682.0, 683.0, 684.0, 685.0],     [686.0, 687.0, 688.0, 689.0, 690.0]],    [[691.0, 692.0, 693.0, 694.0, 695.0],     [696.0, 697.0, 698.0, 699.0, 700.0],     [701.0, 702.0, 703.0, 704.0, 705.0]],    [[706.0, 707.0, 708.0, 709.0, 710.0],     [711.0, 712.0, 713.0, 714.0, 715.0],     [716.0, 717.0, 718.0, 719.0, 720.0]],    [[721.0, 722.0, 723.0, 724.0, 725.0],     [726.0, 727.0, 728.0, 729.0, 730.0],     [731.0, 732.0, 733.0, 734.0, 735.0]],    [[736.0, 737.0, 738.0, 739.0, 740.0],     [741.0, 742.0, 743.0, 744.0, 745.0],     [746.0, 747.0, 748.0, 749.0, 750.0]]],   [[[751.0, 752.0, 753.0, 754.0, 755.0],     [756.0, 757.0, 758.0, 759.0, 760.0],     [761.0, 762.0, 763.0, 764.0, 765.0]],    [[766.0, 767.0, 768.0, 769.0, 770.0],     [771.0, 772.0, 773.0, 774.0, 775.0],     [776.0, 777.0, 778.0, 779.0, 780.0]],    [[781.0, 782.0, 783.0, 784.0, 785.0],     [786.0, 787.0, 788.0, 789.0, 790.0],     [791.0, 792.0, 793.0, 794.0, 795.0]],    [[796.0, 797.0, 798.0, 799.0, 800.0],     [801.0, 802.0, 803.0, 804.0, 805.0],     [806.0, 807.0, 808.0, 809.0, 810.0]],    [[811.0, 812.0, 813.0, 814.0, 815.0],     [816.0, 817.0, 818.0, 819.0, 820.0],     [821.0, 822.0, 823.0, 824.0, 825.0]]],   [[[826.0, 827.0, 828.0, 829.0, 830.0],     [831.0, 832.0, 833.0, 834.0, 835.0],     [836.0, 837.0, 838.0, 839.0, 840.0]],    [[841.0, 842.0, 843.0, 844.0, 845.0],     [846.0, 847.0, 848.0, 849.0, 850.0],     [851.0, 852.0, 853.0, 854.0, 855.0]],    [[856.0, 857.0, 858.0, 859.0, 860.0],     [861.0, 862.0, 863.0, 864.0, 865.0],     [866.0, 867.0, 868.0, 869.0, 870.0]],    [[871.0, 872.0, 873.0, 874.0, 875.0],     [876.0, 877.0, 878.0, 879.0, 880.0],     [881.0, 882.0, 883.0, 884.0, 885.0]],    [[886.0, 887.0, 888.0, 889.0, 890.0],     [891.0, 892.0, 893.0, 894.0, 895.0],     [896.0, 897.0, 898.0, 899.0, 900.0]]],   [[[901.0, 902.0, 903.0, 904.0, 905.0],     [906.0, 907.0, 908.0, 909.0, 910.0],     [911.0, 912.0, 913.0, 914.0, 915.0]],    [[916.0, 917.0, 918.0, 919.0, 920.0],     [921.0, 922.0, 923.0, 924.0, 925.0],     [926.0, 927.0, 928.0, 929.0, 930.0]],    [[931.0, 932.0, 933.0, 934.0, 935.0],     [936.0, 937.0, 938.0, 939.0, 940.0],     [941.0, 942.0, 943.0, 944.0, 945.0]],    [[946.0, 947.0, 948.0, 949.0, 950.0],     [951.0, 952.0, 953.0, 954.0, 955.0],     [956.0, 957.0, 958.0, 959.0, 960.0]],    [[961.0, 962.0, 963.0, 964.0, 965.0],     [966.0, 967.0, 968.0, 969.0, 970.0],     [971.0, 972.0, 973.0, 974.0, 975.0]]],   [[[976.0, 977.0, 978.0, 979.0, 980.0],     [981.0, 982.0, 983.0, 984.0, 985.0],     [986.0, 987.0, 988.0, 989.0, 990.0]],    [[991.0, 992.0, 993.0, 994.0, 995.0],     [996.0, 997.0, 998.0, 999.0, 1000.0],     [1001.0, 1002.0, 1003.0, 1004.0, 1005.0]],    [[1006.0, 1007.0, 1008.0, 1009.0, 1010.0],     [1011.0, 1012.0, 1013.0, 1014.0, 1015.0],     [1016.0, 1017.0, 1018.0, 1019.0, 1020.0]],    [[1021.0, 1022.0, 1023.0, 1024.0, 1025.0],     [1026.0, 1027.0, 1028.0, 1029.0, 1030.0],     [1031.0, 1032.0, 1033.0, 1034.0, 1035.0]],    [[1036.0, 1037.0, 1038.0, 1039.0, 1040.0],     [1041.0, 1042.0, 1043.0, 1044.0, 1045.0],     [1046.0, 1047.0, 1048.0, 1049.0, 1050.0]]]],  [[[[1051.0, 1052.0, 1053.0, 1054.0, 1055.0],     [1056.0, 1057.0, 1058.0, 1059.0, 1060.0],     [1061.0, 1062.0, 1063.0, 1064.0, 1065.0]],    [[1066.0, 1067.0, 1068.0, 1069.0, 1070.0],     [1071.0, 1072.0, 1073.0, 1074.0, 1075.0],     [1076.0, 1077.0, 1078.0, 1079.0, 1080.0]],    [[1081.0, 1082.0, 1083.0, 1084.0, 1085.0],     [1086.0, 1087.0, 1088.0, 1089.0, 1090.0],     [1091.0, 1092.0, 1093.0, 1094.0, 1095.0]],    [[1096.0, 1097.0, 1098.0, 1099.0, 1100.0],     [1101.0, 1102.0, 1103.0, 1104.0, 1105.0],     [1106.0, 1107.0, 1108.0, 1109.0, 1110.0]],    [[1111.0, 1112.0, 1113.0, 1114.0, 1115.0],     [1116.0, 1117.0, 1118.0, 1119.0, 1120.0],     [1121.0, 1122.0, 1123.0, 1124.0, 1125.0]]],   [[[1126.0, 1127.0, 1128.0, 1129.0, 1130.0],     [1131.0, 1132.0, 1133.0, 1134.0, 1135.0],     [1136.0, 1137.0, 1138.0, 1139.0, 1140.0]],    [[1141.0, 1142.0, 1143.0, 1144.0, 1145.0],     [1146.0, 1147.0, 1148.0, 1149.0, 1150.0],     [1151.0, 1152.0, 1153.0, 1154.0, 1155.0]],    [[1156.0, 1157.0, 1158.0, 1159.0, 1160.0],     [1161.0, 1162.0, 1163.0, 1164.0, 1165.0],     [1166.0, 1167.0, 1168.0, 1169.0, 1170.0]],    [[1171.0, 1172.0, 1173.0, 1174.0, 1175.0],     [1176.0, 1177.0, 1178.0, 1179.0, 1180.0],     [1181.0, 1182.0, 1183.0, 1184.0, 1185.0]],    [[1186.0, 1187.0, 1188.0, 1189.0, 1190.0],     [1191.0, 1192.0, 1193.0, 1194.0, 1195.0],     [1196.0, 1197.0, 1198.0, 1199.0, 1200.0]]],   [[[1201.0, 1202.0, 1203.0, 1204.0, 1205.0],     [1206.0, 1207.0, 1208.0, 1209.0, 1210.0],     [1211.0, 1212.0, 1213.0, 1214.0, 1215.0]],    [[1216.0, 1217.0, 1218.0, 1219.0, 1220.0],     [1221.0, 1222.0, 1223.0, 1224.0, 1225.0],     [1226.0, 1227.0, 1228.0, 1229.0, 1230.0]],    [[1231.0, 1232.0, 1233.0, 1234.0, 1235.0],     [1236.0, 1237.0, 1238.0, 1239.0, 1240.0],     [1241.0, 1242.0, 1243.0, 1244.0, 1245.0]],    [[1246.0, 1247.0, 1248.0, 1249.0, 1250.0],     [1251.0, 1252.0, 1253.0, 1254.0, 1255.0],     [1256.0, 1257.0, 1258.0, 1259.0, 1260.0]],    [[1261.0, 1262.0, 1263.0, 1264.0, 1265.0],     [1266.0, 1267.0, 1268.0, 1269.0, 1270.0],     [1271.0, 1272.0, 1273.0, 1274.0, 1275.0]]],   [[[1276.0, 1277.0, 1278.0, 1279.0, 1280.0],     [1281.0, 1282.0, 1283.0, 1284.0, 1285.0],     [1286.0, 1287.0, 1288.0, 1289.0, 1290.0]],    [[1291.0, 1292.0, 1293.0, 1294.0, 1295.0],     [1296.0, 1297.0, 1298.0, 1299.0, 1300.0],     [1301.0, 1302.0, 1303.0, 1304.0, 1305.0]],    [[1306.0, 1307.0, 1308.0, 1309.0, 1310.0],     [1311.0, 1312.0, 1313.0, 1314.0, 1315.0],     [1316.0, 1317.0, 1318.0, 1319.0, 1320.0]],    [[1321.0, 1322.0, 1323.0, 1324.0, 1325.0],     [1326.0, 1327.0, 1328.0, 1329.0, 1330.0],     [1331.0, 1332.0, 1333.0, 1334.0, 1335.0]],    [[1336.0, 1337.0, 1338.0, 1339.0, 1340.0],     [1341.0, 1342.0, 1343.0, 1344.0, 1345.0],     [1346.0, 1347.0, 1348.0, 1349.0, 1350.0]]],   [[[1351.0, 1352.0, 1353.0, 1354.0, 1355.0],     [1356.0, 1357.0, 1358.0, 1359.0, 1360.0],     [1361.0, 1362.0, 1363.0, 1364.0, 1365.0]],    [[1366.0, 1367.0, 1368.0, 1369.0, 1370.0],     [1371.0, 1372.0, 1373.0, 1374.0, 1375.0],     [1376.0, 1377.0, 1378.0, 1379.0, 1380.0]],    [[1381.0, 1382.0, 1383.0, 1384.0, 1385.0],     [1386.0, 1387.0, 1388.0, 1389.0, 1390.0],     [1391.0, 1392.0, 1393.0, 1394.0, 1395.0]],    [[1396.0, 1397.0, 1398.0, 1399.0, 1400.0],     [1401.0, 1402.0, 1403.0, 1404.0, 1405.0],     [1406.0, 1407.0, 1408.0, 1409.0, 1410.0]],    [[1411.0, 1412.0, 1413.0, 1414.0, 1415.0],     [1416.0, 1417.0, 1418.0, 1419.0, 1420.0],     [1421.0, 1422.0, 1423.0, 1424.0, 1425.0]]],   [[[1426.0, 1427.0, 1428.0, 1429.0, 1430.0],     [1431.0, 1432.0, 1433.0, 1434.0, 1435.0],     [1436.0, 1437.0, 1438.0, 1439.0, 1440.0]],    [[1441.0, 1442.0, 1443.0, 1444.0, 1445.0],     [1446.0, 1447.0, 1448.0, 1449.0, 1450.0],     [1451.0, 1452.0, 1453.0, 1454.0, 1455.0]],    [[1456.0, 1457.0, 1458.0, 1459.0, 1460.0],     [1461.0, 1462.0, 1463.0, 1464.0, 1465.0],     [1466.0, 1467.0, 1468.0, 1469.0, 1470.0]],    [[1471.0, 1472.0, 1473.0, 1474.0, 1475.0],     [1476.0, 1477.0, 1478.0, 1479.0, 1480.0],     [1481.0, 1482.0, 1483.0, 1484.0, 1485.0]],    [[1486.0, 1487.0, 1488.0, 1489.0, 1490.0],     [1491.0, 1492.0, 1493.0, 1494.0, 1495.0],     [1496.0, 1497.0, 1498.0, 1499.0, 1500.0]]],   [[[1501.0, 1502.0, 1503.0, 1504.0, 1505.0],     [1506.0, 1507.0, 1508.0, 1509.0, 1510.0],     [1511.0, 1512.0, 1513.0, 1514.0, 1515.0]],    [[1516.0, 1517.0, 1518.0, 1519.0, 1520.0],     [1521.0, 1522.0, 1523.0, 1524.0, 1525.0],     [1526.0, 1527.0, 1528.0, 1529.0, 1530.0]],    [[1531.0, 1532.0, 1533.0, 1534.0, 1535.0],     [1536.0, 1537.0, 1538.0, 1539.0, 1540.0],     [1541.0, 1542.0, 1543.0, 1544.0, 1545.0]],    [[1546.0, 1547.0, 1548.0, 1549.0, 1550.0],     [1551.0, 1552.0, 1553.0, 1554.0, 1555.0],     [1556.0, 1557.0, 1558.0, 1559.0, 1560.0]],    [[1561.0, 1562.0, 1563.0, 1564.0, 1565.0],     [1566.0, 1567.0, 1568.0, 1569.0, 1570.0],     [1571.0, 1572.0, 1573.0, 1574.0, 1575.0]]]]] shape=[3, 7, 5, 3, 5], strides=[525, 75, 15, 5, 1], layout=C (0x1)), I32([2, 3] shape=[2], strides=[1], layout=C | F (0x3)), I32([[3, 2],  [2, 2]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 135607928 3500279943 4150006874 784578252 # shrinks to (ref i, ref bs, ref p) = (F32([[[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, 16.0],   [17.0, 18.0, 19.0, 20.0],   [21.0, 22.0, 23.0, 24.0]],  [[25.0, 26.0, 27.0, 28.0],   [29.0, 30.0, 31.0, 32.0],   [33.0, 34.0, 35.0, 36.0]]] shape=[3, 3, 4], strides=[12, 4, 1], layout=C (0x1)), I32([3] shape=[1], strides=[1], layout=C | F (0x3)), I32([[0, 3]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 1580535696 2219159280 1254821716 25060782 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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, 16.0, 17.0, 18.0],     [19.0, 20.0, 21.0, 22.0, 23.0, 24.0],     [25.0, 26.0, 27.0, 28.0, 29.0, 30.0],     [31.0, 32.0, 33.0, 34.0, 35.0, 36.0]]],   [[[37.0, 38.0, 39.0, 40.0, 41.0, 42.0],     [43.0, 44.0, 45.0, 46.0, 47.0, 48.0],     [49.0, 50.0, 51.0, 52.0, 53.0, 54.0],     [55.0, 56.0, 57.0, 58.0, 59.0, 60.0],     [61.0, 62.0, 63.0, 64.0, 65.0, 66.0],     [67.0, 68.0, 69.0, 70.0, 71.0, 72.0]]],   [[[73.0, 74.0, 75.0, 76.0, 77.0, 78.0],     [79.0, 80.0, 81.0, 82.0, 83.0, 84.0],     [85.0, 86.0, 87.0, 88.0, 89.0, 90.0],     [91.0, 92.0, 93.0, 94.0, 95.0, 96.0],     [97.0, 98.0, 99.0, 100.0, 101.0, 102.0],     [103.0, 104.0, 105.0, 106.0, 107.0, 108.0]]],   [[[109.0, 110.0, 111.0, 112.0, 113.0, 114.0],     [115.0, 116.0, 117.0, 118.0, 119.0, 120.0],     [121.0, 122.0, 123.0, 124.0, 125.0, 126.0],     [127.0, 128.0, 129.0, 130.0, 131.0, 132.0],     [133.0, 134.0, 135.0, 136.0, 137.0, 138.0],     [139.0, 140.0, 141.0, 142.0, 143.0, 144.0]]],   [[[145.0, 146.0, 147.0, 148.0, 149.0, 150.0],     [151.0, 152.0, 153.0, 154.0, 155.0, 156.0],     [157.0, 158.0, 159.0, 160.0, 161.0, 162.0],     [163.0, 164.0, 165.0, 166.0, 167.0, 168.0],     [169.0, 170.0, 171.0, 172.0, 173.0, 174.0],     [175.0, 176.0, 177.0, 178.0, 179.0, 180.0]]]],  [[[[181.0, 182.0, 183.0, 184.0, 185.0, 186.0],     [187.0, 188.0, 189.0, 190.0, 191.0, 192.0],     [193.0, 194.0, 195.0, 196.0, 197.0, 198.0],     [199.0, 200.0, 201.0, 202.0, 203.0, 204.0],     [205.0, 206.0, 207.0, 208.0, 209.0, 210.0],     [211.0, 212.0, 213.0, 214.0, 215.0, 216.0]]],   [[[217.0, 218.0, 219.0, 220.0, 221.0, 222.0],     [223.0, 224.0, 225.0, 226.0, 227.0, 228.0],     [229.0, 230.0, 231.0, 232.0, 233.0, 234.0],     [235.0, 236.0, 237.0, 238.0, 239.0, 240.0],     [241.0, 242.0, 243.0, 244.0, 245.0, 246.0],     [247.0, 248.0, 249.0, 250.0, 251.0, 252.0]]],   [[[253.0, 254.0, 255.0, 256.0, 257.0, 258.0],     [259.0, 260.0, 261.0, 262.0, 263.0, 264.0],     [265.0, 266.0, 267.0, 268.0, 269.0, 270.0],     [271.0, 272.0, 273.0, 274.0, 275.0, 276.0],     [277.0, 278.0, 279.0, 280.0, 281.0, 282.0],     [283.0, 284.0, 285.0, 286.0, 287.0, 288.0]]],   [[[289.0, 290.0, 291.0, 292.0, 293.0, 294.0],     [295.0, 296.0, 297.0, 298.0, 299.0, 300.0],     [301.0, 302.0, 303.0, 304.0, 305.0, 306.0],     [307.0, 308.0, 309.0, 310.0, 311.0, 312.0],     [313.0, 314.0, 315.0, 316.0, 317.0, 318.0],     [319.0, 320.0, 321.0, 322.0, 323.0, 324.0]]],   [[[325.0, 326.0, 327.0, 328.0, 329.0, 330.0],     [331.0, 332.0, 333.0, 334.0, 335.0, 336.0],     [337.0, 338.0, 339.0, 340.0, 341.0, 342.0],     [343.0, 344.0, 345.0, 346.0, 347.0, 348.0],     [349.0, 350.0, 351.0, 352.0, 353.0, 354.0],     [355.0, 356.0, 357.0, 358.0, 359.0, 360.0]]]]] shape=[2, 5, 1, 6, 6], strides=[180, 36, 36, 6, 1], layout=C (0x1)), I32([3] shape=[1], strides=[1], layout=C | F (0x3)), I32([[3, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 113717519 2019313255 2346069503 1116384741 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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],     [16.0, 17.0, 18.0, 19.0, 20.0],     [21.0, 22.0, 23.0, 24.0, 25.0],     [26.0, 27.0, 28.0, 29.0, 30.0],     [31.0, 32.0, 33.0, 34.0, 35.0]],    [[36.0, 37.0, 38.0, 39.0, 40.0],     [41.0, 42.0, 43.0, 44.0, 45.0],     [46.0, 47.0, 48.0, 49.0, 50.0],     [51.0, 52.0, 53.0, 54.0, 55.0],     [56.0, 57.0, 58.0, 59.0, 60.0],     [61.0, 62.0, 63.0, 64.0, 65.0],     [66.0, 67.0, 68.0, 69.0, 70.0]],    [[71.0, 72.0, 73.0, 74.0, 75.0],     [76.0, 77.0, 78.0, 79.0, 80.0],     [81.0, 82.0, 83.0, 84.0, 85.0],     [86.0, 87.0, 88.0, 89.0, 90.0],     [91.0, 92.0, 93.0, 94.0, 95.0],     [96.0, 97.0, 98.0, 99.0, 100.0],     [101.0, 102.0, 103.0, 104.0, 105.0]],    [[106.0, 107.0, 108.0, 109.0, 110.0],     [111.0, 112.0, 113.0, 114.0, 115.0],     [116.0, 117.0, 118.0, 119.0, 120.0],     [121.0, 122.0, 123.0, 124.0, 125.0],     [126.0, 127.0, 128.0, 129.0, 130.0],     [131.0, 132.0, 133.0, 134.0, 135.0],     [136.0, 137.0, 138.0, 139.0, 140.0]],    [[141.0, 142.0, 143.0, 144.0, 145.0],     [146.0, 147.0, 148.0, 149.0, 150.0],     [151.0, 152.0, 153.0, 154.0, 155.0],     [156.0, 157.0, 158.0, 159.0, 160.0],     [161.0, 162.0, 163.0, 164.0, 165.0],     [166.0, 167.0, 168.0, 169.0, 170.0],     [171.0, 172.0, 173.0, 174.0, 175.0]],    [[176.0, 177.0, 178.0, 179.0, 180.0],     [181.0, 182.0, 183.0, 184.0, 185.0],     [186.0, 187.0, 188.0, 189.0, 190.0],     [191.0, 192.0, 193.0, 194.0, 195.0],     [196.0, 197.0, 198.0, 199.0, 200.0],     [201.0, 202.0, 203.0, 204.0, 205.0],     [206.0, 207.0, 208.0, 209.0, 210.0]]],   [[[211.0, 212.0, 213.0, 214.0, 215.0],     [216.0, 217.0, 218.0, 219.0, 220.0],     [221.0, 222.0, 223.0, 224.0, 225.0],     [226.0, 227.0, 228.0, 229.0, 230.0],     [231.0, 232.0, 233.0, 234.0, 235.0],     [236.0, 237.0, 238.0, 239.0, 240.0],     [241.0, 242.0, 243.0, 244.0, 245.0]],    [[246.0, 247.0, 248.0, 249.0, 250.0],     [251.0, 252.0, 253.0, 254.0, 255.0],     [256.0, 257.0, 258.0, 259.0, 260.0],     [261.0, 262.0, 263.0, 264.0, 265.0],     [266.0, 267.0, 268.0, 269.0, 270.0],     [271.0, 272.0, 273.0, 274.0, 275.0],     [276.0, 277.0, 278.0, 279.0, 280.0]],    [[281.0, 282.0, 283.0, 284.0, 285.0],     [286.0, 287.0, 288.0, 289.0, 290.0],     [291.0, 292.0, 293.0, 294.0, 295.0],     [296.0, 297.0, 298.0, 299.0, 300.0],     [301.0, 302.0, 303.0, 304.0, 305.0],     [306.0, 307.0, 308.0, 309.0, 310.0],     [311.0, 312.0, 313.0, 314.0, 315.0]],    [[316.0, 317.0, 318.0, 319.0, 320.0],     [321.0, 322.0, 323.0, 324.0, 325.0],     [326.0, 327.0, 328.0, 329.0, 330.0],     [331.0, 332.0, 333.0, 334.0, 335.0],     [336.0, 337.0, 338.0, 339.0, 340.0],     [341.0, 342.0, 343.0, 344.0, 345.0],     [346.0, 347.0, 348.0, 349.0, 350.0]],    [[351.0, 352.0, 353.0, 354.0, 355.0],     [356.0, 357.0, 358.0, 359.0, 360.0],     [361.0, 362.0, 363.0, 364.0, 365.0],     [366.0, 367.0, 368.0, 369.0, 370.0],     [371.0, 372.0, 373.0, 374.0, 375.0],     [376.0, 377.0, 378.0, 379.0, 380.0],     [381.0, 382.0, 383.0, 384.0, 385.0]],    [[386.0, 387.0, 388.0, 389.0, 390.0],     [391.0, 392.0, 393.0, 394.0, 395.0],     [396.0, 397.0, 398.0, 399.0, 400.0],     [401.0, 402.0, 403.0, 404.0, 405.0],     [406.0, 407.0, 408.0, 409.0, 410.0],     [411.0, 412.0, 413.0, 414.0, 415.0],     [416.0, 417.0, 418.0, 419.0, 420.0]]],   [[[421.0, 422.0, 423.0, 424.0, 425.0],     [426.0, 427.0, 428.0, 429.0, 430.0],     [431.0, 432.0, 433.0, 434.0, 435.0],     [436.0, 437.0, 438.0, 439.0, 440.0],     [441.0, 442.0, 443.0, 444.0, 445.0],     [446.0, 447.0, 448.0, 449.0, 450.0],     [451.0, 452.0, 453.0, 454.0, 455.0]],    [[456.0, 457.0, 458.0, 459.0, 460.0],     [461.0, 462.0, 463.0, 464.0, 465.0],     [466.0, 467.0, 468.0, 469.0, 470.0],     [471.0, 472.0, 473.0, 474.0, 475.0],     [476.0, 477.0, 478.0, 479.0, 480.0],     [481.0, 482.0, 483.0, 484.0, 485.0],     [486.0, 487.0, 488.0, 489.0, 490.0]],    [[491.0, 492.0, 493.0, 494.0, 495.0],     [496.0, 497.0, 498.0, 499.0, 500.0],     [501.0, 502.0, 503.0, 504.0, 505.0],     [506.0, 507.0, 508.0, 509.0, 510.0],     [511.0, 512.0, 513.0, 514.0, 515.0],     [516.0, 517.0, 518.0, 519.0, 520.0],     [521.0, 522.0, 523.0, 524.0, 525.0]],    [[526.0, 527.0, 528.0, 529.0, 530.0],     [531.0, 532.0, 533.0, 534.0, 535.0],     [536.0, 537.0, 538.0, 539.0, 540.0],     [541.0, 542.0, 543.0, 544.0, 545.0],     [546.0, 547.0, 548.0, 549.0, 550.0],     [551.0, 552.0, 553.0, 554.0, 555.0],     [556.0, 557.0, 558.0, 559.0, 560.0]],    [[561.0, 562.0, 563.0, 564.0, 565.0],     [566.0, 567.0, 568.0, 569.0, 570.0],     [571.0, 572.0, 573.0, 574.0, 575.0],     [576.0, 577.0, 578.0, 579.0, 580.0],     [581.0, 582.0, 583.0, 584.0, 585.0],     [586.0, 587.0, 588.0, 589.0, 590.0],     [591.0, 592.0, 593.0, 594.0, 595.0]],    [[596.0, 597.0, 598.0, 599.0, 600.0],     [601.0, 602.0, 603.0, 604.0, 605.0],     [606.0, 607.0, 608.0, 609.0, 610.0],     [611.0, 612.0, 613.0, 614.0, 615.0],     [616.0, 617.0, 618.0, 619.0, 620.0],     [621.0, 622.0, 623.0, 624.0, 625.0],     [626.0, 627.0, 628.0, 629.0, 630.0]]],   [[[631.0, 632.0, 633.0, 634.0, 635.0],     [636.0, 637.0, 638.0, 639.0, 640.0],     [641.0, 642.0, 643.0, 644.0, 645.0],     [646.0, 647.0, 648.0, 649.0, 650.0],     [651.0, 652.0, 653.0, 654.0, 655.0],     [656.0, 657.0, 658.0, 659.0, 660.0],     [661.0, 662.0, 663.0, 664.0, 665.0]],    [[666.0, 667.0, 668.0, 669.0, 670.0],     [671.0, 672.0, 673.0, 674.0, 675.0],     [676.0, 677.0, 678.0, 679.0, 680.0],     [681.0, 682.0, 683.0, 684.0, 685.0],     [686.0, 687.0, 688.0, 689.0, 690.0],     [691.0, 692.0, 693.0, 694.0, 695.0],     [696.0, 697.0, 698.0, 699.0, 700.0]],    [[701.0, 702.0, 703.0, 704.0, 705.0],     [706.0, 707.0, 708.0, 709.0, 710.0],     [711.0, 712.0, 713.0, 714.0, 715.0],     [716.0, 717.0, 718.0, 719.0, 720.0],     [721.0, 722.0, 723.0, 724.0, 725.0],     [726.0, 727.0, 728.0, 729.0, 730.0],     [731.0, 732.0, 733.0, 734.0, 735.0]],    [[736.0, 737.0, 738.0, 739.0, 740.0],     [741.0, 742.0, 743.0, 744.0, 745.0],     [746.0, 747.0, 748.0, 749.0, 750.0],     [751.0, 752.0, 753.0, 754.0, 755.0],     [756.0, 757.0, 758.0, 759.0, 760.0],     [761.0, 762.0, 763.0, 764.0, 765.0],     [766.0, 767.0, 768.0, 769.0, 770.0]],    [[771.0, 772.0, 773.0, 774.0, 775.0],     [776.0, 777.0, 778.0, 779.0, 780.0],     [781.0, 782.0, 783.0, 784.0, 785.0],     [786.0, 787.0, 788.0, 789.0, 790.0],     [791.0, 792.0, 793.0, 794.0, 795.0],     [796.0, 797.0, 798.0, 799.0, 800.0],     [801.0, 802.0, 803.0, 804.0, 805.0]],    [[806.0, 807.0, 808.0, 809.0, 810.0],     [811.0, 812.0, 813.0, 814.0, 815.0],     [816.0, 817.0, 818.0, 819.0, 820.0],     [821.0, 822.0, 823.0, 824.0, 825.0],     [826.0, 827.0, 828.0, 829.0, 830.0],     [831.0, 832.0, 833.0, 834.0, 835.0],     [836.0, 837.0, 838.0, 839.0, 840.0]]]],  [[[[841.0, 842.0, 843.0, 844.0, 845.0],     [846.0, 847.0, 848.0, 849.0, 850.0],     [851.0, 852.0, 853.0, 854.0, 855.0],     [856.0, 857.0, 858.0, 859.0, 860.0],     [861.0, 862.0, 863.0, 864.0, 865.0],     [866.0, 867.0, 868.0, 869.0, 870.0],     [871.0, 872.0, 873.0, 874.0, 875.0]],    [[876.0, 877.0, 878.0, 879.0, 880.0],     [881.0, 882.0, 883.0, 884.0, 885.0],     [886.0, 887.0, 888.0, 889.0, 890.0],     [891.0, 892.0, 893.0, 894.0, 895.0],     [896.0, 897.0, 898.0, 899.0, 900.0],     [901.0, 902.0, 903.0, 904.0, 905.0],     [906.0, 907.0, 908.0, 909.0, 910.0]],    [[911.0, 912.0, 913.0, 914.0, 915.0],     [916.0, 917.0, 918.0, 919.0, 920.0],     [921.0, 922.0, 923.0, 924.0, 925.0],     [926.0, 927.0, 928.0, 929.0, 930.0],     [931.0, 932.0, 933.0, 934.0, 935.0],     [936.0, 937.0, 938.0, 939.0, 940.0],     [941.0, 942.0, 943.0, 944.0, 945.0]],    [[946.0, 947.0, 948.0, 949.0, 950.0],     [951.0, 952.0, 953.0, 954.0, 955.0],     [956.0, 957.0, 958.0, 959.0, 960.0],     [961.0, 962.0, 963.0, 964.0, 965.0],     [966.0, 967.0, 968.0, 969.0, 970.0],     [971.0, 972.0, 973.0, 974.0, 975.0],     [976.0, 977.0, 978.0, 979.0, 980.0]],    [[981.0, 982.0, 983.0, 984.0, 985.0],     [986.0, 987.0, 988.0, 989.0, 990.0],     [991.0, 992.0, 993.0, 994.0, 995.0],     [996.0, 997.0, 998.0, 999.0, 1000.0],     [1001.0, 1002.0, 1003.0, 1004.0, 1005.0],     [1006.0, 1007.0, 1008.0, 1009.0, 1010.0],     [1011.0, 1012.0, 1013.0, 1014.0, 1015.0]],    [[1016.0, 1017.0, 1018.0, 1019.0, 1020.0],     [1021.0, 1022.0, 1023.0, 1024.0, 1025.0],     [1026.0, 1027.0, 1028.0, 1029.0, 1030.0],     [1031.0, 1032.0, 1033.0, 1034.0, 1035.0],     [1036.0, 1037.0, 1038.0, 1039.0, 1040.0],     [1041.0, 1042.0, 1043.0, 1044.0, 1045.0],     [1046.0, 1047.0, 1048.0, 1049.0, 1050.0]]],   [[[1051.0, 1052.0, 1053.0, 1054.0, 1055.0],     [1056.0, 1057.0, 1058.0, 1059.0, 1060.0],     [1061.0, 1062.0, 1063.0, 1064.0, 1065.0],     [1066.0, 1067.0, 1068.0, 1069.0, 1070.0],     [1071.0, 1072.0, 1073.0, 1074.0, 1075.0],     [1076.0, 1077.0, 1078.0, 1079.0, 1080.0],     [1081.0, 1082.0, 1083.0, 1084.0, 1085.0]],    [[1086.0, 1087.0, 1088.0, 1089.0, 1090.0],     [1091.0, 1092.0, 1093.0, 1094.0, 1095.0],     [1096.0, 1097.0, 1098.0, 1099.0, 1100.0],     [1101.0, 1102.0, 1103.0, 1104.0, 1105.0],     [1106.0, 1107.0, 1108.0, 1109.0, 1110.0],     [1111.0, 1112.0, 1113.0, 1114.0, 1115.0],     [1116.0, 1117.0, 1118.0, 1119.0, 1120.0]],    [[1121.0, 1122.0, 1123.0, 1124.0, 1125.0],     [1126.0, 1127.0, 1128.0, 1129.0, 1130.0],     [1131.0, 1132.0, 1133.0, 1134.0, 1135.0],     [1136.0, 1137.0, 1138.0, 1139.0, 1140.0],     [1141.0, 1142.0, 1143.0, 1144.0, 1145.0],     [1146.0, 1147.0, 1148.0, 1149.0, 1150.0],     [1151.0, 1152.0, 1153.0, 1154.0, 1155.0]],    [[1156.0, 1157.0, 1158.0, 1159.0, 1160.0],     [1161.0, 1162.0, 1163.0, 1164.0, 1165.0],     [1166.0, 1167.0, 1168.0, 1169.0, 1170.0],     [1171.0, 1172.0, 1173.0, 1174.0, 1175.0],     [1176.0, 1177.0, 1178.0, 1179.0, 1180.0],     [1181.0, 1182.0, 1183.0, 1184.0, 1185.0],     [1186.0, 1187.0, 1188.0, 1189.0, 1190.0]],    [[1191.0, 1192.0, 1193.0, 1194.0, 1195.0],     [1196.0, 1197.0, 1198.0, 1199.0, 1200.0],     [1201.0, 1202.0, 1203.0, 1204.0, 1205.0],     [1206.0, 1207.0, 1208.0, 1209.0, 1210.0],     [1211.0, 1212.0, 1213.0, 1214.0, 1215.0],     [1216.0, 1217.0, 1218.0, 1219.0, 1220.0],     [1221.0, 1222.0, 1223.0, 1224.0, 1225.0]],    [[1226.0, 1227.0, 1228.0, 1229.0, 1230.0],     [1231.0, 1232.0, 1233.0, 1234.0, 1235.0],     [1236.0, 1237.0, 1238.0, 1239.0, 1240.0],     [1241.0, 1242.0, 1243.0, 1244.0, 1245.0],     [1246.0, 1247.0, 1248.0, 1249.0, 1250.0],     [1251.0, 1252.0, 1253.0, 1254.0, 1255.0],     [1256.0, 1257.0, 1258.0, 1259.0, 1260.0]]],   [[[1261.0, 1262.0, 1263.0, 1264.0, 1265.0],     [1266.0, 1267.0, 1268.0, 1269.0, 1270.0],     [1271.0, 1272.0, 1273.0, 1274.0, 1275.0],     [1276.0, 1277.0, 1278.0, 1279.0, 1280.0],     [1281.0, 1282.0, 1283.0, 1284.0, 1285.0],     [1286.0, 1287.0, 1288.0, 1289.0, 1290.0],     [1291.0, 1292.0, 1293.0, 1294.0, 1295.0]],    [[1296.0, 1297.0, 1298.0, 1299.0, 1300.0],     [1301.0, 1302.0, 1303.0, 1304.0, 1305.0],     [1306.0, 1307.0, 1308.0, 1309.0, 1310.0],     [1311.0, 1312.0, 1313.0, 1314.0, 1315.0],     [1316.0, 1317.0, 1318.0, 1319.0, 1320.0],     [1321.0, 1322.0, 1323.0, 1324.0, 1325.0],     [1326.0, 1327.0, 1328.0, 1329.0, 1330.0]],    [[1331.0, 1332.0, 1333.0, 1334.0, 1335.0],     [1336.0, 1337.0, 1338.0, 1339.0, 1340.0],     [1341.0, 1342.0, 1343.0, 1344.0, 1345.0],     [1346.0, 1347.0, 1348.0, 1349.0, 1350.0],     [1351.0, 1352.0, 1353.0, 1354.0, 1355.0],     [1356.0, 1357.0, 1358.0, 1359.0, 1360.0],     [1361.0, 1362.0, 1363.0, 1364.0, 1365.0]],    [[1366.0, 1367.0, 1368.0, 1369.0, 1370.0],     [1371.0, 1372.0, 1373.0, 1374.0, 1375.0],     [1376.0, 1377.0, 1378.0, 1379.0, 1380.0],     [1381.0, 1382.0, 1383.0, 1384.0, 1385.0],     [1386.0, 1387.0, 1388.0, 1389.0, 1390.0],     [1391.0, 1392.0, 1393.0, 1394.0, 1395.0],     [1396.0, 1397.0, 1398.0, 1399.0, 1400.0]],    [[1401.0, 1402.0, 1403.0, 1404.0, 1405.0],     [1406.0, 1407.0, 1408.0, 1409.0, 1410.0],     [1411.0, 1412.0, 1413.0, 1414.0, 1415.0],     [1416.0, 1417.0, 1418.0, 1419.0, 1420.0],     [1421.0, 1422.0, 1423.0, 1424.0, 1425.0],     [1426.0, 1427.0, 1428.0, 1429.0, 1430.0],     [1431.0, 1432.0, 1433.0, 1434.0, 1435.0]],    [[1436.0, 1437.0, 1438.0, 1439.0, 1440.0],     [1441.0, 1442.0, 1443.0, 1444.0, 1445.0],     [1446.0, 1447.0, 1448.0, 1449.0, 1450.0],     [1451.0, 1452.0, 1453.0, 1454.0, 1455.0],     [1456.0, 1457.0, 1458.0, 1459.0, 1460.0],     [1461.0, 1462.0, 1463.0, 1464.0, 1465.0],     [1466.0, 1467.0, 1468.0, 1469.0, 1470.0]]],   [[[1471.0, 1472.0, 1473.0, 1474.0, 1475.0],     [1476.0, 1477.0, 1478.0, 1479.0, 1480.0],     [1481.0, 1482.0, 1483.0, 1484.0, 1485.0],     [1486.0, 1487.0, 1488.0, 1489.0, 1490.0],     [1491.0, 1492.0, 1493.0, 1494.0, 1495.0],     [1496.0, 1497.0, 1498.0, 1499.0, 1500.0],     [1501.0, 1502.0, 1503.0, 1504.0, 1505.0]],    [[1506.0, 1507.0, 1508.0, 1509.0, 1510.0],     [1511.0, 1512.0, 1513.0, 1514.0, 1515.0],     [1516.0, 1517.0, 1518.0, 1519.0, 1520.0],     [1521.0, 1522.0, 1523.0, 1524.0, 1525.0],     [1526.0, 1527.0, 1528.0, 1529.0, 1530.0],     [1531.0, 1532.0, 1533.0, 1534.0, 1535.0],     [1536.0, 1537.0, 1538.0, 1539.0, 1540.0]],    [[1541.0, 1542.0, 1543.0, 1544.0, 1545.0],     [1546.0, 1547.0, 1548.0, 1549.0, 1550.0],     [1551.0, 1552.0, 1553.0, 1554.0, 1555.0],     [1556.0, 1557.0, 1558.0, 1559.0, 1560.0],     [1561.0, 1562.0, 1563.0, 1564.0, 1565.0],     [1566.0, 1567.0, 1568.0, 1569.0, 1570.0],     [1571.0, 1572.0, 1573.0, 1574.0, 1575.0]],    [[1576.0, 1577.0, 1578.0, 1579.0, 1580.0],     [1581.0, 1582.0, 1583.0, 1584.0, 1585.0],     [1586.0, 1587.0, 1588.0, 1589.0, 1590.0],     [1591.0, 1592.0, 1593.0, 1594.0, 1595.0],     [1596.0, 1597.0, 1598.0, 1599.0, 1600.0],     [1601.0, 1602.0, 1603.0, 1604.0, 1605.0],     [1606.0, 1607.0, 1608.0, 1609.0, 1610.0]],    [[1611.0, 1612.0, 1613.0, 1614.0, 1615.0],     [1616.0, 1617.0, 1618.0, 1619.0, 1620.0],     [1621.0, 1622.0, 1623.0, 1624.0, 1625.0],     [1626.0, 1627.0, 1628.0, 1629.0, 1630.0],     [1631.0, 1632.0, 1633.0, 1634.0, 1635.0],     [1636.0, 1637.0, 1638.0, 1639.0, 1640.0],     [1641.0, 1642.0, 1643.0, 1644.0, 1645.0]],    [[1646.0, 1647.0, 1648.0, 1649.0, 1650.0],     [1651.0, 1652.0, 1653.0, 1654.0, 1655.0],     [1656.0, 1657.0, 1658.0, 1659.0, 1660.0],     [1661.0, 1662.0, 1663.0, 1664.0, 1665.0],     [1666.0, 1667.0, 1668.0, 1669.0, 1670.0],     [1671.0, 1672.0, 1673.0, 1674.0, 1675.0],     [1676.0, 1677.0, 1678.0, 1679.0, 1680.0]]]],  [[[[1681.0, 1682.0, 1683.0, 1684.0, 1685.0],     [1686.0, 1687.0, 1688.0, 1689.0, 1690.0],     [1691.0, 1692.0, 1693.0, 1694.0, 1695.0],     [1696.0, 1697.0, 1698.0, 1699.0, 1700.0],     [1701.0, 1702.0, 1703.0, 1704.0, 1705.0],     [1706.0, 1707.0, 1708.0, 1709.0, 1710.0],     [1711.0, 1712.0, 1713.0, 1714.0, 1715.0]],    [[1716.0, 1717.0, 1718.0, 1719.0, 1720.0],     [1721.0, 1722.0, 1723.0, 1724.0, 1725.0],     [1726.0, 1727.0, 1728.0, 1729.0, 1730.0],     [1731.0, 1732.0, 1733.0, 1734.0, 1735.0],     [1736.0, 1737.0, 1738.0, 1739.0, 1740.0],     [1741.0, 1742.0, 1743.0, 1744.0, 1745.0],     [1746.0, 1747.0, 1748.0, 1749.0, 1750.0]],    [[1751.0, 1752.0, 1753.0, 1754.0, 1755.0],     [1756.0, 1757.0, 1758.0, 1759.0, 1760.0],     [1761.0, 1762.0, 1763.0, 1764.0, 1765.0],     [1766.0, 1767.0, 1768.0, 1769.0, 1770.0],     [1771.0, 1772.0, 1773.0, 1774.0, 1775.0],     [1776.0, 1777.0, 1778.0, 1779.0, 1780.0],     [1781.0, 1782.0, 1783.0, 1784.0, 1785.0]],    [[1786.0, 1787.0, 1788.0, 1789.0, 1790.0],     [1791.0, 1792.0, 1793.0, 1794.0, 1795.0],     [1796.0, 1797.0, 1798.0, 1799.0, 1800.0],     [1801.0, 1802.0, 1803.0, 1804.0, 1805.0],     [1806.0, 1807.0, 1808.0, 1809.0, 1810.0],     [1811.0, 1812.0, 1813.0, 1814.0, 1815.0],     [1816.0, 1817.0, 1818.0, 1819.0, 1820.0]],    [[1821.0, 1822.0, 1823.0, 1824.0, 1825.0],     [1826.0, 1827.0, 1828.0, 1829.0, 1830.0],     [1831.0, 1832.0, 1833.0, 1834.0, 1835.0],     [1836.0, 1837.0, 1838.0, 1839.0, 1840.0],     [1841.0, 1842.0, 1843.0, 1844.0, 1845.0],     [1846.0, 1847.0, 1848.0, 1849.0, 1850.0],     [1851.0, 1852.0, 1853.0, 1854.0, 1855.0]],    [[1856.0, 1857.0, 1858.0, 1859.0, 1860.0],     [1861.0, 1862.0, 1863.0, 1864.0, 1865.0],     [1866.0, 1867.0, 1868.0, 1869.0, 1870.0],     [1871.0, 1872.0, 1873.0, 1874.0, 1875.0],     [1876.0, 1877.0, 1878.0, 1879.0, 1880.0],     [1881.0, 1882.0, 1883.0, 1884.0, 1885.0],     [1886.0, 1887.0, 1888.0, 1889.0, 1890.0]]],   [[[1891.0, 1892.0, 1893.0, 1894.0, 1895.0],     [1896.0, 1897.0, 1898.0, 1899.0, 1900.0],     [1901.0, 1902.0, 1903.0, 1904.0, 1905.0],     [1906.0, 1907.0, 1908.0, 1909.0, 1910.0],     [1911.0, 1912.0, 1913.0, 1914.0, 1915.0],     [1916.0, 1917.0, 1918.0, 1919.0, 1920.0],     [1921.0, 1922.0, 1923.0, 1924.0, 1925.0]],    [[1926.0, 1927.0, 1928.0, 1929.0, 1930.0],     [1931.0, 1932.0, 1933.0, 1934.0, 1935.0],     [1936.0, 1937.0, 1938.0, 1939.0, 1940.0],     [1941.0, 1942.0, 1943.0, 1944.0, 1945.0],     [1946.0, 1947.0, 1948.0, 1949.0, 1950.0],     [1951.0, 1952.0, 1953.0, 1954.0, 1955.0],     [1956.0, 1957.0, 1958.0, 1959.0, 1960.0]],    [[1961.0, 1962.0, 1963.0, 1964.0, 1965.0],     [1966.0, 1967.0, 1968.0, 1969.0, 1970.0],     [1971.0, 1972.0, 1973.0, 1974.0, 1975.0],     [1976.0, 1977.0, 1978.0, 1979.0, 1980.0],     [1981.0, 1982.0, 1983.0, 1984.0, 1985.0],     [1986.0, 1987.0, 1988.0, 1989.0, 1990.0],     [1991.0, 1992.0, 1993.0, 1994.0, 1995.0]],    [[1996.0, 1997.0, 1998.0, 1999.0, 2000.0],     [2001.0, 2002.0, 2003.0, 2004.0, 2005.0],     [2006.0, 2007.0, 2008.0, 2009.0, 2010.0],     [2011.0, 2012.0, 2013.0, 2014.0, 2015.0],     [2016.0, 2017.0, 2018.0, 2019.0, 2020.0],     [2021.0, 2022.0, 2023.0, 2024.0, 2025.0],     [2026.0, 2027.0, 2028.0, 2029.0, 2030.0]],    [[2031.0, 2032.0, 2033.0, 2034.0, 2035.0],     [2036.0, 2037.0, 2038.0, 2039.0, 2040.0],     [2041.0, 2042.0, 2043.0, 2044.0, 2045.0],     [2046.0, 2047.0, 2048.0, 2049.0, 2050.0],     [2051.0, 2052.0, 2053.0, 2054.0, 2055.0],     [2056.0, 2057.0, 2058.0, 2059.0, 2060.0],     [2061.0, 2062.0, 2063.0, 2064.0, 2065.0]],    [[2066.0, 2067.0, 2068.0, 2069.0, 2070.0],     [2071.0, 2072.0, 2073.0, 2074.0, 2075.0],     [2076.0, 2077.0, 2078.0, 2079.0, 2080.0],     [2081.0, 2082.0, 2083.0, 2084.0, 2085.0],     [2086.0, 2087.0, 2088.0, 2089.0, 2090.0],     [2091.0, 2092.0, 2093.0, 2094.0, 2095.0],     [2096.0, 2097.0, 2098.0, 2099.0, 2100.0]]],   [[[2101.0, 2102.0, 2103.0, 2104.0, 2105.0],     [2106.0, 2107.0, 2108.0, 2109.0, 2110.0],     [2111.0, 2112.0, 2113.0, 2114.0, 2115.0],     [2116.0, 2117.0, 2118.0, 2119.0, 2120.0],     [2121.0, 2122.0, 2123.0, 2124.0, 2125.0],     [2126.0, 2127.0, 2128.0, 2129.0, 2130.0],     [2131.0, 2132.0, 2133.0, 2134.0, 2135.0]],    [[2136.0, 2137.0, 2138.0, 2139.0, 2140.0],     [2141.0, 2142.0, 2143.0, 2144.0, 2145.0],     [2146.0, 2147.0, 2148.0, 2149.0, 2150.0],     [2151.0, 2152.0, 2153.0, 2154.0, 2155.0],     [2156.0, 2157.0, 2158.0, 2159.0, 2160.0],     [2161.0, 2162.0, 2163.0, 2164.0, 2165.0],     [2166.0, 2167.0, 2168.0, 2169.0, 2170.0]],    [[2171.0, 2172.0, 2173.0, 2174.0, 2175.0],     [2176.0, 2177.0, 2178.0, 2179.0, 2180.0],     [2181.0, 2182.0, 2183.0, 2184.0, 2185.0],     [2186.0, 2187.0, 2188.0, 2189.0, 2190.0],     [2191.0, 2192.0, 2193.0, 2194.0, 2195.0],     [2196.0, 2197.0, 2198.0, 2199.0, 2200.0],     [2201.0, 2202.0, 2203.0, 2204.0, 2205.0]],    [[2206.0, 2207.0, 2208.0, 2209.0, 2210.0],     [2211.0, 2212.0, 2213.0, 2214.0, 2215.0],     [2216.0, 2217.0, 2218.0, 2219.0, 2220.0],     [2221.0, 2222.0, 2223.0, 2224.0, 2225.0],     [2226.0, 2227.0, 2228.0, 2229.0, 2230.0],     [2231.0, 2232.0, 2233.0, 2234.0, 2235.0],     [2236.0, 2237.0, 2238.0, 2239.0, 2240.0]],    [[2241.0, 2242.0, 2243.0, 2244.0, 2245.0],     [2246.0, 2247.0, 2248.0, 2249.0, 2250.0],     [2251.0, 2252.0, 2253.0, 2254.0, 2255.0],     [2256.0, 2257.0, 2258.0, 2259.0, 2260.0],     [2261.0, 2262.0, 2263.0, 2264.0, 2265.0],     [2266.0, 2267.0, 2268.0, 2269.0, 2270.0],     [2271.0, 2272.0, 2273.0, 2274.0, 2275.0]],    [[2276.0, 2277.0, 2278.0, 2279.0, 2280.0],     [2281.0, 2282.0, 2283.0, 2284.0, 2285.0],     [2286.0, 2287.0, 2288.0, 2289.0, 2290.0],     [2291.0, 2292.0, 2293.0, 2294.0, 2295.0],     [2296.0, 2297.0, 2298.0, 2299.0, 2300.0],     [2301.0, 2302.0, 2303.0, 2304.0, 2305.0],     [2306.0, 2307.0, 2308.0, 2309.0, 2310.0]]],   [[[2311.0, 2312.0, 2313.0, 2314.0, 2315.0],     [2316.0, 2317.0, 2318.0, 2319.0, 2320.0],     [2321.0, 2322.0, 2323.0, 2324.0, 2325.0],     [2326.0, 2327.0, 2328.0, 2329.0, 2330.0],     [2331.0, 2332.0, 2333.0, 2334.0, 2335.0],     [2336.0, 2337.0, 2338.0, 2339.0, 2340.0],     [2341.0, 2342.0, 2343.0, 2344.0, 2345.0]],    [[2346.0, 2347.0, 2348.0, 2349.0, 2350.0],     [2351.0, 2352.0, 2353.0, 2354.0, 2355.0],     [2356.0, 2357.0, 2358.0, 2359.0, 2360.0],     [2361.0, 2362.0, 2363.0, 2364.0, 2365.0],     [2366.0, 2367.0, 2368.0, 2369.0, 2370.0],     [2371.0, 2372.0, 2373.0, 2374.0, 2375.0],     [2376.0, 2377.0, 2378.0, 2379.0, 2380.0]],    [[2381.0, 2382.0, 2383.0, 2384.0, 2385.0],     [2386.0, 2387.0, 2388.0, 2389.0, 2390.0],     [2391.0, 2392.0, 2393.0, 2394.0, 2395.0],     [2396.0, 2397.0, 2398.0, 2399.0, 2400.0],     [2401.0, 2402.0, 2403.0, 2404.0, 2405.0],     [2406.0, 2407.0, 2408.0, 2409.0, 2410.0],     [2411.0, 2412.0, 2413.0, 2414.0, 2415.0]],    [[2416.0, 2417.0, 2418.0, 2419.0, 2420.0],     [2421.0, 2422.0, 2423.0, 2424.0, 2425.0],     [2426.0, 2427.0, 2428.0, 2429.0, 2430.0],     [2431.0, 2432.0, 2433.0, 2434.0, 2435.0],     [2436.0, 2437.0, 2438.0, 2439.0, 2440.0],     [2441.0, 2442.0, 2443.0, 2444.0, 2445.0],     [2446.0, 2447.0, 2448.0, 2449.0, 2450.0]],    [[2451.0, 2452.0, 2453.0, 2454.0, 2455.0],     [2456.0, 2457.0, 2458.0, 2459.0, 2460.0],     [2461.0, 2462.0, 2463.0, 2464.0, 2465.0],     [2466.0, 2467.0, 2468.0, 2469.0, 2470.0],     [2471.0, 2472.0, 2473.0, 2474.0, 2475.0],     [2476.0, 2477.0, 2478.0, 2479.0, 2480.0],     [2481.0, 2482.0, 2483.0, 2484.0, 2485.0]],    [[2486.0, 2487.0, 2488.0, 2489.0, 2490.0],     [2491.0, 2492.0, 2493.0, 2494.0, 2495.0],     [2496.0, 2497.0, 2498.0, 2499.0, 2500.0],     [2501.0, 2502.0, 2503.0, 2504.0, 2505.0],     [2506.0, 2507.0, 2508.0, 2509.0, 2510.0],     [2511.0, 2512.0, 2513.0, 2514.0, 2515.0],     [2516.0, 2517.0, 2518.0, 2519.0, 2520.0]]]]] shape=[3, 4, 6, 7, 5], strides=[840, 210, 35, 5, 1], layout=C (0x1)), I32([2, 2, 3] shape=[3], strides=[1], layout=C | F (0x3)), I32([[1, 1],  [0, 2],  [1, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 4184330367 178619895 2368119189 3931333916 # shrinks to (ref i, ref bs, ref p) = (F32([[[[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, 16.0],    [17.0, 18.0],    [19.0, 20.0]],   [[21.0, 22.0],    [23.0, 24.0],    [25.0, 26.0],    [27.0, 28.0],    [29.0, 30.0]],   [[31.0, 32.0],    [33.0, 34.0],    [35.0, 36.0],    [37.0, 38.0],    [39.0, 40.0]]],  [[[41.0, 42.0],    [43.0, 44.0],    [45.0, 46.0],    [47.0, 48.0],    [49.0, 50.0]],   [[51.0, 52.0],    [53.0, 54.0],    [55.0, 56.0],    [57.0, 58.0],    [59.0, 60.0]],   [[61.0, 62.0],    [63.0, 64.0],    [65.0, 66.0],    [67.0, 68.0],    [69.0, 70.0]],   [[71.0, 72.0],    [73.0, 74.0],    [75.0, 76.0],    [77.0, 78.0],    [79.0, 80.0]]]] shape=[2, 4, 5, 2], strides=[40, 10, 2, 1], layout=C (0x1)), I32([2, 1] shape=[2], strides=[1], layout=C | F (0x3)), I32([[2, 2],  [2, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 2984237747 3768862472 3468400897 4102129388 # shrinks to (ref i, ref bs, ref p) = (F32([[[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],   [16.0],   [17.0],   [18.0]]] shape=[3, 6, 1], strides=[6, 1, 1], layout=C (0x1)), I32([1] shape=[1], strides=[1], layout=C | F (0x3)), I32([[1, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 2244523614 1803874248 3670981810 2893772378 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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, 16.0, 17.0, 18.0],       [19.0, 20.0, 21.0, 22.0, 23.0, 24.0],       [25.0, 26.0, 27.0, 28.0, 29.0, 30.0],       [31.0, 32.0, 33.0, 34.0, 35.0, 36.0],       [37.0, 38.0, 39.0, 40.0, 41.0, 42.0]],      [[43.0, 44.0, 45.0, 46.0, 47.0, 48.0],       [49.0, 50.0, 51.0, 52.0, 53.0, 54.0],       [55.0, 56.0, 57.0, 58.0, 59.0, 60.0],       [61.0, 62.0, 63.0, 64.0, 65.0, 66.0],       [67.0, 68.0, 69.0, 70.0, 71.0, 72.0],       [73.0, 74.0, 75.0, 76.0, 77.0, 78.0],       [79.0, 80.0, 81.0, 82.0, 83.0, 84.0]],      [[85.0, 86.0, 87.0, 88.0, 89.0, 90.0],       [91.0, 92.0, 93.0, 94.0, 95.0, 96.0],       [97.0, 98.0, 99.0, 100.0, 101.0, 102.0],       [103.0, 104.0, 105.0, 106.0, 107.0, 108.0],       [109.0, 110.0, 111.0, 112.0, 113.0, 114.0],       [115.0, 116.0, 117.0, 118.0, 119.0, 120.0],       [121.0, 122.0, 123.0, 124.0, 125.0, 126.0]],      [[127.0, 128.0, 129.0, 130.0, 131.0, 132.0],       [133.0, 134.0, 135.0, 136.0, 137.0, 138.0],       [139.0, 140.0, 141.0, 142.0, 143.0, 144.0],       [145.0, 146.0, 147.0, 148.0, 149.0, 150.0],       [151.0, 152.0, 153.0, 154.0, 155.0, 156.0],       [157.0, 158.0, 159.0, 160.0, 161.0, 162.0],       [163.0, 164.0, 165.0, 166.0, 167.0, 168.0]],      [[169.0, 170.0, 171.0, 172.0, 173.0, 174.0],       [175.0, 176.0, 177.0, 178.0, 179.0, 180.0],       [181.0, 182.0, 183.0, 184.0, 185.0, 186.0],       [187.0, 188.0, 189.0, 190.0, 191.0, 192.0],       [193.0, 194.0, 195.0, 196.0, 197.0, 198.0],       [199.0, 200.0, 201.0, 202.0, 203.0, 204.0],       [205.0, 206.0, 207.0, 208.0, 209.0, 210.0]],      [[211.0, 212.0, 213.0, 214.0, 215.0, 216.0],       [217.0, 218.0, 219.0, 220.0, 221.0, 222.0],       [223.0, 224.0, 225.0, 226.0, 227.0, 228.0],       [229.0, 230.0, 231.0, 232.0, 233.0, 234.0],       [235.0, 236.0, 237.0, 238.0, 239.0, 240.0],       [241.0, 242.0, 243.0, 244.0, 245.0, 246.0],       [247.0, 248.0, 249.0, 250.0, 251.0, 252.0]]],     [[[253.0, 254.0, 255.0, 256.0, 257.0, 258.0],       [259.0, 260.0, 261.0, 262.0, 263.0, 264.0],       [265.0, 266.0, 267.0, 268.0, 269.0, 270.0],       [271.0, 272.0, 273.0, 274.0, 275.0, 276.0],       [277.0, 278.0, 279.0, 280.0, 281.0, 282.0],       [283.0, 284.0, 285.0, 286.0, 287.0, 288.0],       [289.0, 290.0, 291.0, 292.0, 293.0, 294.0]],      [[295.0, 296.0, 297.0, 298.0, 299.0, 300.0],       [301.0, 302.0, 303.0, 304.0, 305.0, 306.0],       [307.0, 308.0, 309.0, 310.0, 311.0, 312.0],       [313.0, 314.0, 315.0, 316.0, 317.0, 318.0],       [319.0, 320.0, 321.0, 322.0, 323.0, 324.0],       [325.0, 326.0, 327.0, 328.0, 329.0, 330.0],       [331.0, 332.0, 333.0, 334.0, 335.0, 336.0]],      [[337.0, 338.0, 339.0, 340.0, 341.0, 342.0],       [343.0, 344.0, 345.0, 346.0, 347.0, 348.0],       [349.0, 350.0, 351.0, 352.0, 353.0, 354.0],       [355.0, 356.0, 357.0, 358.0, 359.0, 360.0],       [361.0, 362.0, 363.0, 364.0, 365.0, 366.0],       [367.0, 368.0, 369.0, 370.0, 371.0, 372.0],       [373.0, 374.0, 375.0, 376.0, 377.0, 378.0]],      [[379.0, 380.0, 381.0, 382.0, 383.0, 384.0],       [385.0, 386.0, 387.0, 388.0, 389.0, 390.0],       [391.0, 392.0, 393.0, 394.0, 395.0, 396.0],       [397.0, 398.0, 399.0, 400.0, 401.0, 402.0],       [403.0, 404.0, 405.0, 406.0, 407.0, 408.0],       [409.0, 410.0, 411.0, 412.0, 413.0, 414.0],       [415.0, 416.0, 417.0, 418.0, 419.0, 420.0]],      [[421.0, 422.0, 423.0, 424.0, 425.0, 426.0],       [427.0, 428.0, 429.0, 430.0, 431.0, 432.0],       [433.0, 434.0, 435.0, 436.0, 437.0, 438.0],       [439.0, 440.0, 441.0, 442.0, 443.0, 444.0],       [445.0, 446.0, 447.0, 448.0, 449.0, 450.0],       [451.0, 452.0, 453.0, 454.0, 455.0, 456.0],       [457.0, 458.0, 459.0, 460.0, 461.0, 462.0]],      [[463.0, 464.0, 465.0, 466.0, 467.0, 468.0],       [469.0, 470.0, 471.0, 472.0, 473.0, 474.0],       [475.0, 476.0, 477.0, 478.0, 479.0, 480.0],       [481.0, 482.0, 483.0, 484.0, 485.0, 486.0],       [487.0, 488.0, 489.0, 490.0, 491.0, 492.0],       [493.0, 494.0, 495.0, 496.0, 497.0, 498.0],       [499.0, 500.0, 501.0, 502.0, 503.0, 504.0]]],     [[[505.0, 506.0, 507.0, 508.0, 509.0, 510.0],       [511.0, 512.0, 513.0, 514.0, 515.0, 516.0],       [517.0, 518.0, 519.0, 520.0, 521.0, 522.0],       [523.0, 524.0, 525.0, 526.0, 527.0, 528.0],       [529.0, 530.0, 531.0, 532.0, 533.0, 534.0],       [535.0, 536.0, 537.0, 538.0, 539.0, 540.0],       [541.0, 542.0, 543.0, 544.0, 545.0, 546.0]],      [[547.0, 548.0, 549.0, 550.0, 551.0, 552.0],       [553.0, 554.0, 555.0, 556.0, 557.0, 558.0],       [559.0, 560.0, 561.0, 562.0, 563.0, 564.0],       [565.0, 566.0, 567.0, 568.0, 569.0, 570.0],       [571.0, 572.0, 573.0, 574.0, 575.0, 576.0],       [577.0, 578.0, 579.0, 580.0, 581.0, 582.0],       [583.0, 584.0, 585.0, 586.0, 587.0, 588.0]],      [[589.0, 590.0, 591.0, 592.0, 593.0, 594.0],       [595.0, 596.0, 597.0, 598.0, 599.0, 600.0],       [601.0, 602.0, 603.0, 604.0, 605.0, 606.0],       [607.0, 608.0, 609.0, 610.0, 611.0, 612.0],       [613.0, 614.0, 615.0, 616.0, 617.0, 618.0],       [619.0, 620.0, 621.0, 622.0, 623.0, 624.0],       [625.0, 626.0, 627.0, 628.0, 629.0, 630.0]],      [[631.0, 632.0, 633.0, 634.0, 635.0, 636.0],       [637.0, 638.0, 639.0, 640.0, 641.0, 642.0],       [643.0, 644.0, 645.0, 646.0, 647.0, 648.0],       [649.0, 650.0, 651.0, 652.0, 653.0, 654.0],       [655.0, 656.0, 657.0, 658.0, 659.0, 660.0],       [661.0, 662.0, 663.0, 664.0, 665.0, 666.0],       [667.0, 668.0, 669.0, 670.0, 671.0, 672.0]],      [[673.0, 674.0, 675.0, 676.0, 677.0, 678.0],       [679.0, 680.0, 681.0, 682.0, 683.0, 684.0],       [685.0, 686.0, 687.0, 688.0, 689.0, 690.0],       [691.0, 692.0, 693.0, 694.0, 695.0, 696.0],       [697.0, 698.0, 699.0, 700.0, 701.0, 702.0],       [703.0, 704.0, 705.0, 706.0, 707.0, 708.0],       [709.0, 710.0, 711.0, 712.0, 713.0, 714.0]],      [[715.0, 716.0, 717.0, 718.0, 719.0, 720.0],       [721.0, 722.0, 723.0, 724.0, 725.0, 726.0],       [727.0, 728.0, 729.0, 730.0, 731.0, 732.0],       [733.0, 734.0, 735.0, 736.0, 737.0, 738.0],       [739.0, 740.0, 741.0, 742.0, 743.0, 744.0],       [745.0, 746.0, 747.0, 748.0, 749.0, 750.0],       [751.0, 752.0, 753.0, 754.0, 755.0, 756.0]]],     [[[757.0, 758.0, 759.0, 760.0, 761.0, 762.0],       [763.0, 764.0, 765.0, 766.0, 767.0, 768.0],       [769.0, 770.0, 771.0, 772.0, 773.0, 774.0],       [775.0, 776.0, 777.0, 778.0, 779.0, 780.0],       [781.0, 782.0, 783.0, 784.0, 785.0, 786.0],       [787.0, 788.0, 789.0, 790.0, 791.0, 792.0],       [793.0, 794.0, 795.0, 796.0, 797.0, 798.0]],      [[799.0, 800.0, 801.0, 802.0, 803.0, 804.0],       [805.0, 806.0, 807.0, 808.0, 809.0, 810.0],       [811.0, 812.0, 813.0, 814.0, 815.0, 816.0],       [817.0, 818.0, 819.0, 820.0, 821.0, 822.0],       [823.0, 824.0, 825.0, 826.0, 827.0, 828.0],       [829.0, 830.0, 831.0, 832.0, 833.0, 834.0],       [835.0, 836.0, 837.0, 838.0, 839.0, 840.0]],      [[841.0, 842.0, 843.0, 844.0, 845.0, 846.0],       [847.0, 848.0, 849.0, 850.0, 851.0, 852.0],       [853.0, 854.0, 855.0, 856.0, 857.0, 858.0],       [859.0, 860.0, 861.0, 862.0, 863.0, 864.0],       [865.0, 866.0, 867.0, 868.0, 869.0, 870.0],       [871.0, 872.0, 873.0, 874.0, 875.0, 876.0],       [877.0, 878.0, 879.0, 880.0, 881.0, 882.0]],      [[883.0, 884.0, 885.0, 886.0, 887.0, 888.0],       [889.0, 890.0, 891.0, 892.0, 893.0, 894.0],       [895.0, 896.0, 897.0, 898.0, 899.0, 900.0],       [901.0, 902.0, 903.0, 904.0, 905.0, 906.0],       [907.0, 908.0, 909.0, 910.0, 911.0, 912.0],       [913.0, 914.0, 915.0, 916.0, 917.0, 918.0],       [919.0, 920.0, 921.0, 922.0, 923.0, 924.0]],      [[925.0, 926.0, 927.0, 928.0, 929.0, 930.0],       [931.0, 932.0, 933.0, 934.0, 935.0, 936.0],       [937.0, 938.0, 939.0, 940.0, 941.0, 942.0],       [943.0, 944.0, 945.0, 946.0, 947.0, 948.0],       [949.0, 950.0, 951.0, 952.0, 953.0, 954.0],       [955.0, 956.0, 957.0, 958.0, 959.0, 960.0],       [961.0, 962.0, 963.0, 964.0, 965.0, 966.0]],      [[967.0, 968.0, 969.0, 970.0, 971.0, 972.0],       [973.0, 974.0, 975.0, 976.0, 977.0, 978.0],       [979.0, 980.0, 981.0, 982.0, 983.0, 984.0],       [985.0, 986.0, 987.0, 988.0, 989.0, 990.0],       [991.0, 992.0, 993.0, 994.0, 995.0, 996.0],       [997.0, 998.0, 999.0, 1000.0, 1001.0, 1002.0],       [1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0]]]],    [[[[1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0],       [1015.0, 1016.0, 1017.0, 1018.0, 1019.0, 1020.0],       [1021.0, 1022.0, 1023.0, 1024.0, 1025.0, 1026.0],       [1027.0, 1028.0, 1029.0, 1030.0, 1031.0, 1032.0],       [1033.0, 1034.0, 1035.0, 1036.0, 1037.0, 1038.0],       [1039.0, 1040.0, 1041.0, 1042.0, 1043.0, 1044.0],       [1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]],      [[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0],       [1057.0, 1058.0, 1059.0, 1060.0, 1061.0, 1062.0],       [1063.0, 1064.0, 1065.0, 1066.0, 1067.0, 1068.0],       [1069.0, 1070.0, 1071.0, 1072.0, 1073.0, 1074.0],       [1075.0, 1076.0, 1077.0, 1078.0, 1079.0, 1080.0],       [1081.0, 1082.0, 1083.0, 1084.0, 1085.0, 1086.0],       [1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0]],      [[1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0],       [1099.0, 1100.0, 1101.0, 1102.0, 1103.0, 1104.0],       [1105.0, 1106.0, 1107.0, 1108.0, 1109.0, 1110.0],       [1111.0, 1112.0, 1113.0, 1114.0, 1115.0, 1116.0],       [1117.0, 1118.0, 1119.0, 1120.0, 1121.0, 1122.0],       [1123.0, 1124.0, 1125.0, 1126.0, 1127.0, 1128.0],       [1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0]],      [[1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0],       [1141.0, 1142.0, 1143.0, 1144.0, 1145.0, 1146.0],       [1147.0, 1148.0, 1149.0, 1150.0, 1151.0, 1152.0],       [1153.0, 1154.0, 1155.0, 1156.0, 1157.0, 1158.0],       [1159.0, 1160.0, 1161.0, 1162.0, 1163.0, 1164.0],       [1165.0, 1166.0, 1167.0, 1168.0, 1169.0, 1170.0],       [1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0]],      [[1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0],       [1183.0, 1184.0, 1185.0, 1186.0, 1187.0, 1188.0],       [1189.0, 1190.0, 1191.0, 1192.0, 1193.0, 1194.0],       [1195.0, 1196.0, 1197.0, 1198.0, 1199.0, 1200.0],       [1201.0, 1202.0, 1203.0, 1204.0, 1205.0, 1206.0],       [1207.0, 1208.0, 1209.0, 1210.0, 1211.0, 1212.0],       [1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0]],      [[1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0],       [1225.0, 1226.0, 1227.0, 1228.0, 1229.0, 1230.0],       [1231.0, 1232.0, 1233.0, 1234.0, 1235.0, 1236.0],       [1237.0, 1238.0, 1239.0, 1240.0, 1241.0, 1242.0],       [1243.0, 1244.0, 1245.0, 1246.0, 1247.0, 1248.0],       [1249.0, 1250.0, 1251.0, 1252.0, 1253.0, 1254.0],       [1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]]],     [[[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0],       [1267.0, 1268.0, 1269.0, 1270.0, 1271.0, 1272.0],       [1273.0, 1274.0, 1275.0, 1276.0, 1277.0, 1278.0],       [1279.0, 1280.0, 1281.0, 1282.0, 1283.0, 1284.0],       [1285.0, 1286.0, 1287.0, 1288.0, 1289.0, 1290.0],       [1291.0, 1292.0, 1293.0, 1294.0, 1295.0, 1296.0],       [1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0]],      [[1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0],       [1309.0, 1310.0, 1311.0, 1312.0, 1313.0, 1314.0],       [1315.0, 1316.0, 1317.0, 1318.0, 1319.0, 1320.0],       [1321.0, 1322.0, 1323.0, 1324.0, 1325.0, 1326.0],       [1327.0, 1328.0, 1329.0, 1330.0, 1331.0, 1332.0],       [1333.0, 1334.0, 1335.0, 1336.0, 1337.0, 1338.0],       [1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0]],      [[1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0],       [1351.0, 1352.0, 1353.0, 1354.0, 1355.0, 1356.0],       [1357.0, 1358.0, 1359.0, 1360.0, 1361.0, 1362.0],       [1363.0, 1364.0, 1365.0, 1366.0, 1367.0, 1368.0],       [1369.0, 1370.0, 1371.0, 1372.0, 1373.0, 1374.0],       [1375.0, 1376.0, 1377.0, 1378.0, 1379.0, 1380.0],       [1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0]],      [[1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0],       [1393.0, 1394.0, 1395.0, 1396.0, 1397.0, 1398.0],       [1399.0, 1400.0, 1401.0, 1402.0, 1403.0, 1404.0],       [1405.0, 1406.0, 1407.0, 1408.0, 1409.0, 1410.0],       [1411.0, 1412.0, 1413.0, 1414.0, 1415.0, 1416.0],       [1417.0, 1418.0, 1419.0, 1420.0, 1421.0, 1422.0],       [1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0]],      [[1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0],       [1435.0, 1436.0, 1437.0, 1438.0, 1439.0, 1440.0],       [1441.0, 1442.0, 1443.0, 1444.0, 1445.0, 1446.0],       [1447.0, 1448.0, 1449.0, 1450.0, 1451.0, 1452.0],       [1453.0, 1454.0, 1455.0, 1456.0, 1457.0, 1458.0],       [1459.0, 1460.0, 1461.0, 1462.0, 1463.0, 1464.0],       [1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0]],      [[1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0],       [1477.0, 1478.0, 1479.0, 1480.0, 1481.0, 1482.0],       [1483.0, 1484.0, 1485.0, 1486.0, 1487.0, 1488.0],       [1489.0, 1490.0, 1491.0, 1492.0, 1493.0, 1494.0],       [1495.0, 1496.0, 1497.0, 1498.0, 1499.0, 1500.0],       [1501.0, 1502.0, 1503.0, 1504.0, 1505.0, 1506.0],       [1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0]]],     [[[1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0],       [1519.0, 1520.0, 1521.0, 1522.0, 1523.0, 1524.0],       [1525.0, 1526.0, 1527.0, 1528.0, 1529.0, 1530.0],       [1531.0, 1532.0, 1533.0, 1534.0, 1535.0, 1536.0],       [1537.0, 1538.0, 1539.0, 1540.0, 1541.0, 1542.0],       [1543.0, 1544.0, 1545.0, 1546.0, 1547.0, 1548.0],       [1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0]],      [[1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0],       [1561.0, 1562.0, 1563.0, 1564.0, 1565.0, 1566.0],       [1567.0, 1568.0, 1569.0, 1570.0, 1571.0, 1572.0],       [1573.0, 1574.0, 1575.0, 1576.0, 1577.0, 1578.0],       [1579.0, 1580.0, 1581.0, 1582.0, 1583.0, 1584.0],       [1585.0, 1586.0, 1587.0, 1588.0, 1589.0, 1590.0],       [1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0]],      [[1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0],       [1603.0, 1604.0, 1605.0, 1606.0, 1607.0, 1608.0],       [1609.0, 1610.0, 1611.0, 1612.0, 1613.0, 1614.0],       [1615.0, 1616.0, 1617.0, 1618.0, 1619.0, 1620.0],       [1621.0, 1622.0, 1623.0, 1624.0, 1625.0, 1626.0],       [1627.0, 1628.0, 1629.0, 1630.0, 1631.0, 1632.0],       [1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0]],      [[1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0],       [1645.0, 1646.0, 1647.0, 1648.0, 1649.0, 1650.0],       [1651.0, 1652.0, 1653.0, 1654.0, 1655.0, 1656.0],       [1657.0, 1658.0, 1659.0, 1660.0, 1661.0, 1662.0],       [1663.0, 1664.0, 1665.0, 1666.0, 1667.0, 1668.0],       [1669.0, 1670.0, 1671.0, 1672.0, 1673.0, 1674.0],       [1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]],      [[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0],       [1687.0, 1688.0, 1689.0, 1690.0, 1691.0, 1692.0],       [1693.0, 1694.0, 1695.0, 1696.0, 1697.0, 1698.0],       [1699.0, 1700.0, 1701.0, 1702.0, 1703.0, 1704.0],       [1705.0, 1706.0, 1707.0, 1708.0, 1709.0, 1710.0],       [1711.0, 1712.0, 1713.0, 1714.0, 1715.0, 1716.0],       [1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0]],      [[1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0],       [1729.0, 1730.0, 1731.0, 1732.0, 1733.0, 1734.0],       [1735.0, 1736.0, 1737.0, 1738.0, 1739.0, 1740.0],       [1741.0, 1742.0, 1743.0, 1744.0, 1745.0, 1746.0],       [1747.0, 1748.0, 1749.0, 1750.0, 1751.0, 1752.0],       [1753.0, 1754.0, 1755.0, 1756.0, 1757.0, 1758.0],       [1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0]]],     [[[1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0],       [1771.0, 1772.0, 1773.0, 1774.0, 1775.0, 1776.0],       [1777.0, 1778.0, 1779.0, 1780.0, 1781.0, 1782.0],       [1783.0, 1784.0, 1785.0, 1786.0, 1787.0, 1788.0],       [1789.0, 1790.0, 1791.0, 1792.0, 1793.0, 1794.0],       [1795.0, 1796.0, 1797.0, 1798.0, 1799.0, 1800.0],       [1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0]],      [[1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0],       [1813.0, 1814.0, 1815.0, 1816.0, 1817.0, 1818.0],       [1819.0, 1820.0, 1821.0, 1822.0, 1823.0, 1824.0],       [1825.0, 1826.0, 1827.0, 1828.0, 1829.0, 1830.0],       [1831.0, 1832.0, 1833.0, 1834.0, 1835.0, 1836.0],       [1837.0, 1838.0, 1839.0, 1840.0, 1841.0, 1842.0],       [1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0]],      [[1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0],       [1855.0, 1856.0, 1857.0, 1858.0, 1859.0, 1860.0],       [1861.0, 1862.0, 1863.0, 1864.0, 1865.0, 1866.0],       [1867.0, 1868.0, 1869.0, 1870.0, 1871.0, 1872.0],       [1873.0, 1874.0, 1875.0, 1876.0, 1877.0, 1878.0],       [1879.0, 1880.0, 1881.0, 1882.0, 1883.0, 1884.0],       [1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0]],      [[1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0],       [1897.0, 1898.0, 1899.0, 1900.0, 1901.0, 1902.0],       [1903.0, 1904.0, 1905.0, 1906.0, 1907.0, 1908.0],       [1909.0, 1910.0, 1911.0, 1912.0, 1913.0, 1914.0],       [1915.0, 1916.0, 1917.0, 1918.0, 1919.0, 1920.0],       [1921.0, 1922.0, 1923.0, 1924.0, 1925.0, 1926.0],       [1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0]],      [[1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0],       [1939.0, 1940.0, 1941.0, 1942.0, 1943.0, 1944.0],       [1945.0, 1946.0, 1947.0, 1948.0, 1949.0, 1950.0],       [1951.0, 1952.0, 1953.0, 1954.0, 1955.0, 1956.0],       [1957.0, 1958.0, 1959.0, 1960.0, 1961.0, 1962.0],       [1963.0, 1964.0, 1965.0, 1966.0, 1967.0, 1968.0],       [1969.0, 1970.0, 1971.0, 1972.0, 1973.0, 1974.0]],      [[1975.0, 1976.0, 1977.0, 1978.0, 1979.0, 1980.0],       [1981.0, 1982.0, 1983.0, 1984.0, 1985.0, 1986.0],       [1987.0, 1988.0, 1989.0, 1990.0, 1991.0, 1992.0],       [1993.0, 1994.0, 1995.0, 1996.0, 1997.0, 1998.0],       [1999.0, 2000.0, 2001.0, 2002.0, 2003.0, 2004.0],       [2005.0, 2006.0, 2007.0, 2008.0, 2009.0, 2010.0],       [2011.0, 2012.0, 2013.0, 2014.0, 2015.0, 2016.0]]]],    [[[[2017.0, 2018.0, 2019.0, 2020.0, 2021.0, 2022.0],       [2023.0, 2024.0, 2025.0, 2026.0, 2027.0, 2028.0],       [2029.0, 2030.0, 2031.0, 2032.0, 2033.0, 2034.0],       [2035.0, 2036.0, 2037.0, 2038.0, 2039.0, 2040.0],       [2041.0, 2042.0, 2043.0, 2044.0, 2045.0, 2046.0],       [2047.0, 2048.0, 2049.0, 2050.0, 2051.0, 2052.0],       [2053.0, 2054.0, 2055.0, 2056.0, 2057.0, 2058.0]],      [[2059.0, 2060.0, 2061.0, 2062.0, 2063.0, 2064.0],       [2065.0, 2066.0, 2067.0, 2068.0, 2069.0, 2070.0],       [2071.0, 2072.0, 2073.0, 2074.0, 2075.0, 2076.0],       [2077.0, 2078.0, 2079.0, 2080.0, 2081.0, 2082.0],       [2083.0, 2084.0, 2085.0, 2086.0, 2087.0, 2088.0],       [2089.0, 2090.0, 2091.0, 2092.0, 2093.0, 2094.0],       [2095.0, 2096.0, 2097.0, 2098.0, 2099.0, 2100.0]],      [[2101.0, 2102.0, 2103.0, 2104.0, 2105.0, 2106.0],       [2107.0, 2108.0, 2109.0, 2110.0, 2111.0, 2112.0],       [2113.0, 2114.0, 2115.0, 2116.0, 2117.0, 2118.0],       [2119.0, 2120.0, 2121.0, 2122.0, 2123.0, 2124.0],       [2125.0, 2126.0, 2127.0, 2128.0, 2129.0, 2130.0],       [2131.0, 2132.0, 2133.0, 2134.0, 2135.0, 2136.0],       [2137.0, 2138.0, 2139.0, 2140.0, 2141.0, 2142.0]],      [[2143.0, 2144.0, 2145.0, 2146.0, 2147.0, 2148.0],       [2149.0, 2150.0, 2151.0, 2152.0, 2153.0, 2154.0],       [2155.0, 2156.0, 2157.0, 2158.0, 2159.0, 2160.0],       [2161.0, 2162.0, 2163.0, 2164.0, 2165.0, 2166.0],       [2167.0, 2168.0, 2169.0, 2170.0, 2171.0, 2172.0],       [2173.0, 2174.0, 2175.0, 2176.0, 2177.0, 2178.0],       [2179.0, 2180.0, 2181.0, 2182.0, 2183.0, 2184.0]],      [[2185.0, 2186.0, 2187.0, 2188.0, 2189.0, 2190.0],       [2191.0, 2192.0, 2193.0, 2194.0, 2195.0, 2196.0],       [2197.0, 2198.0, 2199.0, 2200.0, 2201.0, 2202.0],       [2203.0, 2204.0, 2205.0, 2206.0, 2207.0, 2208.0],       [2209.0, 2210.0, 2211.0, 2212.0, 2213.0, 2214.0],       [2215.0, 2216.0, 2217.0, 2218.0, 2219.0, 2220.0],       [2221.0, 2222.0, 2223.0, 2224.0, 2225.0, 2226.0]],      [[2227.0, 2228.0, 2229.0, 2230.0, 2231.0, 2232.0],       [2233.0, 2234.0, 2235.0, 2236.0, 2237.0, 2238.0],       [2239.0, 2240.0, 2241.0, 2242.0, 2243.0, 2244.0],       [2245.0, 2246.0, 2247.0, 2248.0, 2249.0, 2250.0],       [2251.0, 2252.0, 2253.0, 2254.0, 2255.0, 2256.0],       [2257.0, 2258.0, 2259.0, 2260.0, 2261.0, 2262.0],       [2263.0, 2264.0, 2265.0, 2266.0, 2267.0, 2268.0]]],     [[[2269.0, 2270.0, 2271.0, 2272.0, 2273.0, 2274.0],       [2275.0, 2276.0, 2277.0, 2278.0, 2279.0, 2280.0],       [2281.0, 2282.0, 2283.0, 2284.0, 2285.0, 2286.0],       [2287.0, 2288.0, 2289.0, 2290.0, 2291.0, 2292.0],       [2293.0, 2294.0, 2295.0, 2296.0, 2297.0, 2298.0],       [2299.0, 2300.0, 2301.0, 2302.0, 2303.0, 2304.0],       [2305.0, 2306.0, 2307.0, 2308.0, 2309.0, 2310.0]],      [[2311.0, 2312.0, 2313.0, 2314.0, 2315.0, 2316.0],       [2317.0, 2318.0, 2319.0, 2320.0, 2321.0, 2322.0],       [2323.0, 2324.0, 2325.0, 2326.0, 2327.0, 2328.0],       [2329.0, 2330.0, 2331.0, 2332.0, 2333.0, 2334.0],       [2335.0, 2336.0, 2337.0, 2338.0, 2339.0, 2340.0],       [2341.0, 2342.0, 2343.0, 2344.0, 2345.0, 2346.0],       [2347.0, 2348.0, 2349.0, 2350.0, 2351.0, 2352.0]],      [[2353.0, 2354.0, 2355.0, 2356.0, 2357.0, 2358.0],       [2359.0, 2360.0, 2361.0, 2362.0, 2363.0, 2364.0],       [2365.0, 2366.0, 2367.0, 2368.0, 2369.0, 2370.0],       [2371.0, 2372.0, 2373.0, 2374.0, 2375.0, 2376.0],       [2377.0, 2378.0, 2379.0, 2380.0, 2381.0, 2382.0],       [2383.0, 2384.0, 2385.0, 2386.0, 2387.0, 2388.0],       [2389.0, 2390.0, 2391.0, 2392.0, 2393.0, 2394.0]],      [[2395.0, 2396.0, 2397.0, 2398.0, 2399.0, 2400.0],       [2401.0, 2402.0, 2403.0, 2404.0, 2405.0, 2406.0],       [2407.0, 2408.0, 2409.0, 2410.0, 2411.0, 2412.0],       [2413.0, 2414.0, 2415.0, 2416.0, 2417.0, 2418.0],       [2419.0, 2420.0, 2421.0, 2422.0, 2423.0, 2424.0],       [2425.0, 2426.0, 2427.0, 2428.0, 2429.0, 2430.0],       [2431.0, 2432.0, 2433.0, 2434.0, 2435.0, 2436.0]],      [[2437.0, 2438.0, 2439.0, 2440.0, 2441.0, 2442.0],       [2443.0, 2444.0, 2445.0, 2446.0, 2447.0, 2448.0],       [2449.0, 2450.0, 2451.0, 2452.0, 2453.0, 2454.0],       [2455.0, 2456.0, 2457.0, 2458.0, 2459.0, 2460.0],       [2461.0, 2462.0, 2463.0, 2464.0, 2465.0, 2466.0],       [2467.0, 2468.0, 2469.0, 2470.0, 2471.0, 2472.0],       [2473.0, 2474.0, 2475.0, 2476.0, 2477.0, 2478.0]],      [[2479.0, 2480.0, 2481.0, 2482.0, 2483.0, 2484.0],       [2485.0, 2486.0, 2487.0, 2488.0, 2489.0, 2490.0],       [2491.0, 2492.0, 2493.0, 2494.0, 2495.0, 2496.0],       [2497.0, 2498.0, 2499.0, 2500.0, 2501.0, 2502.0],       [2503.0, 2504.0, 2505.0, 2506.0, 2507.0, 2508.0],       [2509.0, 2510.0, 2511.0, 2512.0, 2513.0, 2514.0],       [2515.0, 2516.0, 2517.0, 2518.0, 2519.0, 2520.0]]],     [[[2521.0, 2522.0, 2523.0, 2524.0, 2525.0, 2526.0],       [2527.0, 2528.0, 2529.0, 2530.0, 2531.0, 2532.0],       [2533.0, 2534.0, 2535.0, 2536.0, 2537.0, 2538.0],       [2539.0, 2540.0, 2541.0, 2542.0, 2543.0, 2544.0],       [2545.0, 2546.0, 2547.0, 2548.0, 2549.0, 2550.0],       [2551.0, 2552.0, 2553.0, 2554.0, 2555.0, 2556.0],       [2557.0, 2558.0, 2559.0, 2560.0, 2561.0, 2562.0]],      [[2563.0, 2564.0, 2565.0, 2566.0, 2567.0, 2568.0],       [2569.0, 2570.0, 2571.0, 2572.0, 2573.0, 2574.0],       [2575.0, 2576.0, 2577.0, 2578.0, 2579.0, 2580.0],       [2581.0, 2582.0, 2583.0, 2584.0, 2585.0, 2586.0],       [2587.0, 2588.0, 2589.0, 2590.0, 2591.0, 2592.0],       [2593.0, 2594.0, 2595.0, 2596.0, 2597.0, 2598.0],       [2599.0, 2600.0, 2601.0, 2602.0, 2603.0, 2604.0]],      [[2605.0, 2606.0, 2607.0, 2608.0, 2609.0, 2610.0],       [2611.0, 2612.0, 2613.0, 2614.0, 2615.0, 2616.0],       [2617.0, 2618.0, 2619.0, 2620.0, 2621.0, 2622.0],       [2623.0, 2624.0, 2625.0, 2626.0, 2627.0, 2628.0],       [2629.0, 2630.0, 2631.0, 2632.0, 2633.0, 2634.0],       [2635.0, 2636.0, 2637.0, 2638.0, 2639.0, 2640.0],       [2641.0, 2642.0, 2643.0, 2644.0, 2645.0, 2646.0]],      [[2647.0, 2648.0, 2649.0, 2650.0, 2651.0, 2652.0],       [2653.0, 2654.0, 2655.0, 2656.0, 2657.0, 2658.0],       [2659.0, 2660.0, 2661.0, 2662.0, 2663.0, 2664.0],       [2665.0, 2666.0, 2667.0, 2668.0, 2669.0, 2670.0],       [2671.0, 2672.0, 2673.0, 2674.0, 2675.0, 2676.0],       [2677.0, 2678.0, 2679.0, 2680.0, 2681.0, 2682.0],       [2683.0, 2684.0, 2685.0, 2686.0, 2687.0, 2688.0]],      [[2689.0, 2690.0, 2691.0, 2692.0, 2693.0, 2694.0],       [2695.0, 2696.0, 2697.0, 2698.0, 2699.0, 2700.0],       [2701.0, 2702.0, 2703.0, 2704.0, 2705.0, 2706.0],       [2707.0, 2708.0, 2709.0, 2710.0, 2711.0, 2712.0],       [2713.0, 2714.0, 2715.0, 2716.0, 2717.0, 2718.0],       [2719.0, 2720.0, 2721.0, 2722.0, 2723.0, 2724.0],       [2725.0, 2726.0, 2727.0, 2728.0, 2729.0, 2730.0]],      [[2731.0, 2732.0, 2733.0, 2734.0, 2735.0, 2736.0],       [2737.0, 2738.0, 2739.0, 2740.0, 2741.0, 2742.0],       [2743.0, 2744.0, 2745.0, 2746.0, 2747.0, 2748.0],       [2749.0, 2750.0, 2751.0, 2752.0, 2753.0, 2754.0],       [2755.0, 2756.0, 2757.0, 2758.0, 2759.0, 2760.0],       [2761.0, 2762.0, 2763.0, 2764.0, 2765.0, 2766.0],       [2767.0, 2768.0, 2769.0, 2770.0, 2771.0, 2772.0]]],     [[[2773.0, 2774.0, 2775.0, 2776.0, 2777.0, 2778.0],       [2779.0, 2780.0, 2781.0, 2782.0, 2783.0, 2784.0],       [2785.0, 2786.0, 2787.0, 2788.0, 2789.0, 2790.0],       [2791.0, 2792.0, 2793.0, 2794.0, 2795.0, 2796.0],       [2797.0, 2798.0, 2799.0, 2800.0, 2801.0, 2802.0],       [2803.0, 2804.0, 2805.0, 2806.0, 2807.0, 2808.0],       [2809.0, 2810.0, 2811.0, 2812.0, 2813.0, 2814.0]],      [[2815.0, 2816.0, 2817.0, 2818.0, 2819.0, 2820.0],       [2821.0, 2822.0, 2823.0, 2824.0, 2825.0, 2826.0],       [2827.0, 2828.0, 2829.0, 2830.0, 2831.0, 2832.0],       [2833.0, 2834.0, 2835.0, 2836.0, 2837.0, 2838.0],       [2839.0, 2840.0, 2841.0, 2842.0, 2843.0, 2844.0],       [2845.0, 2846.0, 2847.0, 2848.0, 2849.0, 2850.0],       [2851.0, 2852.0, 2853.0, 2854.0, 2855.0, 2856.0]],      [[2857.0, 2858.0, 2859.0, 2860.0, 2861.0, 2862.0],       [2863.0, 2864.0, 2865.0, 2866.0, 2867.0, 2868.0],       [2869.0, 2870.0, 2871.0, 2872.0, 2873.0, 2874.0],       [2875.0, 2876.0, 2877.0, 2878.0, 2879.0, 2880.0],       [2881.0, 2882.0, 2883.0, 2884.0, 2885.0, 2886.0],       [2887.0, 2888.0, 2889.0, 2890.0, 2891.0, 2892.0],       [2893.0, 2894.0, 2895.0, 2896.0, 2897.0, 2898.0]],      [[2899.0, 2900.0, 2901.0, 2902.0, 2903.0, 2904.0],       [2905.0, 2906.0, 2907.0, 2908.0, 2909.0, 2910.0],       [2911.0, 2912.0, 2913.0, 2914.0, 2915.0, 2916.0],       [2917.0, 2918.0, 2919.0, 2920.0, 2921.0, 2922.0],       [2923.0, 2924.0, 2925.0, 2926.0, 2927.0, 2928.0],       [2929.0, 2930.0, 2931.0, 2932.0, 2933.0, 2934.0],       [2935.0, 2936.0, 2937.0, 2938.0, 2939.0, 2940.0]],      [[2941.0, 2942.0, 2943.0, 2944.0, 2945.0, 2946.0],       [2947.0, 2948.0, 2949.0, 2950.0, 2951.0, 2952.0],       [2953.0, 2954.0, 2955.0, 2956.0, 2957.0, 2958.0],       [2959.0, 2960.0, 2961.0, 2962.0, 2963.0, 2964.0],       [2965.0, 2966.0, 2967.0, 2968.0, 2969.0, 2970.0],       [2971.0, 2972.0, 2973.0, 2974.0, 2975.0, 2976.0],       [2977.0, 2978.0, 2979.0, 2980.0, 2981.0, 2982.0]],      [[2983.0, 2984.0, 2985.0, 2986.0, 2987.0, 2988.0],       [2989.0, 2990.0, 2991.0, 2992.0, 2993.0, 2994.0],       [2995.0, 2996.0, 2997.0, 2998.0, 2999.0, 3000.0],       [3001.0, 3002.0, 3003.0, 3004.0, 3005.0, 3006.0],       [3007.0, 3008.0, 3009.0, 3010.0, 3011.0, 3012.0],       [3013.0, 3014.0, 3015.0, 3016.0, 3017.0, 3018.0],       [3019.0, 3020.0, 3021.0, 3022.0, 3023.0, 3024.0]]]],    [[[[3025.0, 3026.0, 3027.0, 3028.0, 3029.0, 3030.0],       [3031.0, 3032.0, 3033.0, 3034.0, 3035.0, 3036.0],       [3037.0, 3038.0, 3039.0, 3040.0, 3041.0, 3042.0],       [3043.0, 3044.0, 3045.0, 3046.0, 3047.0, 3048.0],       [3049.0, 3050.0, 3051.0, 3052.0, 3053.0, 3054.0],       [3055.0, 3056.0, 3057.0, 3058.0, 3059.0, 3060.0],       [3061.0, 3062.0, 3063.0, 3064.0, 3065.0, 3066.0]],      [[3067.0, 3068.0, 3069.0, 3070.0, 3071.0, 3072.0],       [3073.0, 3074.0, 3075.0, 3076.0, 3077.0, 3078.0],       [3079.0, 3080.0, 3081.0, 3082.0, 3083.0, 3084.0],       [3085.0, 3086.0, 3087.0, 3088.0, 3089.0, 3090.0],       [3091.0, 3092.0, 3093.0, 3094.0, 3095.0, 3096.0],       [3097.0, 3098.0, 3099.0, 3100.0, 3101.0, 3102.0],       [3103.0, 3104.0, 3105.0, 3106.0, 3107.0, 3108.0]],      [[3109.0, 3110.0, 3111.0, 3112.0, 3113.0, 3114.0],       [3115.0, 3116.0, 3117.0, 3118.0, 3119.0, 3120.0],       [3121.0, 3122.0, 3123.0, 3124.0, 3125.0, 3126.0],       [3127.0, 3128.0, 3129.0, 3130.0, 3131.0, 3132.0],       [3133.0, 3134.0, 3135.0, 3136.0, 3137.0, 3138.0],       [3139.0, 3140.0, 3141.0, 3142.0, 3143.0, 3144.0],       [3145.0, 3146.0, 3147.0, 3148.0, 3149.0, 3150.0]],      [[3151.0, 3152.0, 3153.0, 3154.0, 3155.0, 3156.0],       [3157.0, 3158.0, 3159.0, 3160.0, 3161.0, 3162.0],       [3163.0, 3164.0, 3165.0, 3166.0, 3167.0, 3168.0],       [3169.0, 3170.0, 3171.0, 3172.0, 3173.0, 3174.0],       [3175.0, 3176.0, 3177.0, 3178.0, 3179.0, 3180.0],       [3181.0, 3182.0, 3183.0, 3184.0, 3185.0, 3186.0],       [3187.0, 3188.0, 3189.0, 3190.0, 3191.0, 3192.0]],      [[3193.0, 3194.0, 3195.0, 3196.0, 3197.0, 3198.0],       [3199.0, 3200.0, 3201.0, 3202.0, 3203.0, 3204.0],       [3205.0, 3206.0, 3207.0, 3208.0, 3209.0, 3210.0],       [3211.0, 3212.0, 3213.0, 3214.0, 3215.0, 3216.0],       [3217.0, 3218.0, 3219.0, 3220.0, 3221.0, 3222.0],       [3223.0, 3224.0, 3225.0, 3226.0, 3227.0, 3228.0],       [3229.0, 3230.0, 3231.0, 3232.0, 3233.0, 3234.0]],      [[3235.0, 3236.0, 3237.0, 3238.0, 3239.0, 3240.0],       [3241.0, 3242.0, 3243.0, 3244.0, 3245.0, 3246.0],       [3247.0, 3248.0, 3249.0, 3250.0, 3251.0, 3252.0],       [3253.0, 3254.0, 3255.0, 3256.0, 3257.0, 3258.0],       [3259.0, 3260.0, 3261.0, 3262.0, 3263.0, 3264.0],       [3265.0, 3266.0, 3267.0, 3268.0, 3269.0, 3270.0],       [3271.0, 3272.0, 3273.0, 3274.0, 3275.0, 3276.0]]],     [[[3277.0, 3278.0, 3279.0, 3280.0, 3281.0, 3282.0],       [3283.0, 3284.0, 3285.0, 3286.0, 3287.0, 3288.0],       [3289.0, 3290.0, 3291.0, 3292.0, 3293.0, 3294.0],       [3295.0, 3296.0, 3297.0, 3298.0, 3299.0, 3300.0],       [3301.0, 3302.0, 3303.0, 3304.0, 3305.0, 3306.0],       [3307.0, 3308.0, 3309.0, 3310.0, 3311.0, 3312.0],       [3313.0, 3314.0, 3315.0, 3316.0, 3317.0, 3318.0]],      [[3319.0, 3320.0, 3321.0, 3322.0, 3323.0, 3324.0],       [3325.0, 3326.0, 3327.0, 3328.0, 3329.0, 3330.0],       [3331.0, 3332.0, 3333.0, 3334.0, 3335.0, 3336.0],       [3337.0, 3338.0, 3339.0, 3340.0, 3341.0, 3342.0],       [3343.0, 3344.0, 3345.0, 3346.0, 3347.0, 3348.0],       [3349.0, 3350.0, 3351.0, 3352.0, 3353.0, 3354.0],       [3355.0, 3356.0, 3357.0, 3358.0, 3359.0, 3360.0]],      [[3361.0, 3362.0, 3363.0, 3364.0, 3365.0, 3366.0],       [3367.0, 3368.0, 3369.0, 3370.0, 3371.0, 3372.0],       [3373.0, 3374.0, 3375.0, 3376.0, 3377.0, 3378.0],       [3379.0, 3380.0, 3381.0, 3382.0, 3383.0, 3384.0],       [3385.0, 3386.0, 3387.0, 3388.0, 3389.0, 3390.0],       [3391.0, 3392.0, 3393.0, 3394.0, 3395.0, 3396.0],       [3397.0, 3398.0, 3399.0, 3400.0, 3401.0, 3402.0]],      [[3403.0, 3404.0, 3405.0, 3406.0, 3407.0, 3408.0],       [3409.0, 3410.0, 3411.0, 3412.0, 3413.0, 3414.0],       [3415.0, 3416.0, 3417.0, 3418.0, 3419.0, 3420.0],       [3421.0, 3422.0, 3423.0, 3424.0, 3425.0, 3426.0],       [3427.0, 3428.0, 3429.0, 3430.0, 3431.0, 3432.0],       [3433.0, 3434.0, 3435.0, 3436.0, 3437.0, 3438.0],       [3439.0, 3440.0, 3441.0, 3442.0, 3443.0, 3444.0]],      [[3445.0, 3446.0, 3447.0, 3448.0, 3449.0, 3450.0],       [3451.0, 3452.0, 3453.0, 3454.0, 3455.0, 3456.0],       [3457.0, 3458.0, 3459.0, 3460.0, 3461.0, 3462.0],       [3463.0, 3464.0, 3465.0, 3466.0, 3467.0, 3468.0],       [3469.0, 3470.0, 3471.0, 3472.0, 3473.0, 3474.0],       [3475.0, 3476.0, 3477.0, 3478.0, 3479.0, 3480.0],       [3481.0, 3482.0, 3483.0, 3484.0, 3485.0, 3486.0]],      [[3487.0, 3488.0, 3489.0, 3490.0, 3491.0, 3492.0],       [3493.0, 3494.0, 3495.0, 3496.0, 3497.0, 3498.0],       [3499.0, 3500.0, 3501.0, 3502.0, 3503.0, 3504.0],       [3505.0, 3506.0, 3507.0, 3508.0, 3509.0, 3510.0],       [3511.0, 3512.0, 3513.0, 3514.0, 3515.0, 3516.0],       [3517.0, 3518.0, 3519.0, 3520.0, 3521.0, 3522.0],       [3523.0, 3524.0, 3525.0, 3526.0, 3527.0, 3528.0]]],     [[[3529.0, 3530.0, 3531.0, 3532.0, 3533.0, 3534.0],       [3535.0, 3536.0, 3537.0, 3538.0, 3539.0, 3540.0],       [3541.0, 3542.0, 3543.0, 3544.0, 3545.0, 3546.0],       [3547.0, 3548.0, 3549.0, 3550.0, 3551.0, 3552.0],       [3553.0, 3554.0, 3555.0, 3556.0, 3557.0, 3558.0],       [3559.0, 3560.0, 3561.0, 3562.0, 3563.0, 3564.0],       [3565.0, 3566.0, 3567.0, 3568.0, 3569.0, 3570.0]],      [[3571.0, 3572.0, 3573.0, 3574.0, 3575.0, 3576.0],       [3577.0, 3578.0, 3579.0, 3580.0, 3581.0, 3582.0],       [3583.0, 3584.0, 3585.0, 3586.0, 3587.0, 3588.0],       [3589.0, 3590.0, 3591.0, 3592.0, 3593.0, 3594.0],       [3595.0, 3596.0, 3597.0, 3598.0, 3599.0, 3600.0],       [3601.0, 3602.0, 3603.0, 3604.0, 3605.0, 3606.0],       [3607.0, 3608.0, 3609.0, 3610.0, 3611.0, 3612.0]],      [[3613.0, 3614.0, 3615.0, 3616.0, 3617.0, 3618.0],       [3619.0, 3620.0, 3621.0, 3622.0, 3623.0, 3624.0],       [3625.0, 3626.0, 3627.0, 3628.0, 3629.0, 3630.0],       [3631.0, 3632.0, 3633.0, 3634.0, 3635.0, 3636.0],       [3637.0, 3638.0, 3639.0, 3640.0, 3641.0, 3642.0],       [3643.0, 3644.0, 3645.0, 3646.0, 3647.0, 3648.0],       [3649.0, 3650.0, 3651.0, 3652.0, 3653.0, 3654.0]],      [[3655.0, 3656.0, 3657.0, 3658.0, 3659.0, 3660.0],       [3661.0, 3662.0, 3663.0, 3664.0, 3665.0, 3666.0],       [3667.0, 3668.0, 3669.0, 3670.0, 3671.0, 3672.0],       [3673.0, 3674.0, 3675.0, 3676.0, 3677.0, 3678.0],       [3679.0, 3680.0, 3681.0, 3682.0, 3683.0, 3684.0],       [3685.0, 3686.0, 3687.0, 3688.0, 3689.0, 3690.0],       [3691.0, 3692.0, 3693.0, 3694.0, 3695.0, 3696.0]],      [[3697.0, 3698.0, 3699.0, 3700.0, 3701.0, 3702.0],       [3703.0, 3704.0, 3705.0, 3706.0, 3707.0, 3708.0],       [3709.0, 3710.0, 3711.0, 3712.0, 3713.0, 3714.0],       [3715.0, 3716.0, 3717.0, 3718.0, 3719.0, 3720.0],       [3721.0, 3722.0, 3723.0, 3724.0, 3725.0, 3726.0],       [3727.0, 3728.0, 3729.0, 3730.0, 3731.0, 3732.0],       [3733.0, 3734.0, 3735.0, 3736.0, 3737.0, 3738.0]],      [[3739.0, 3740.0, 3741.0, 3742.0, 3743.0, 3744.0],       [3745.0, 3746.0, 3747.0, 3748.0, 3749.0, 3750.0],       [3751.0, 3752.0, 3753.0, 3754.0, 3755.0, 3756.0],       [3757.0, 3758.0, 3759.0, 3760.0, 3761.0, 3762.0],       [3763.0, 3764.0, 3765.0, 3766.0, 3767.0, 3768.0],       [3769.0, 3770.0, 3771.0, 3772.0, 3773.0, 3774.0],       [3775.0, 3776.0, 3777.0, 3778.0, 3779.0, 3780.0]]],     [[[3781.0, 3782.0, 3783.0, 3784.0, 3785.0, 3786.0],       [3787.0, 3788.0, 3789.0, 3790.0, 3791.0, 3792.0],       [3793.0, 3794.0, 3795.0, 3796.0, 3797.0, 3798.0],       [3799.0, 3800.0, 3801.0, 3802.0, 3803.0, 3804.0],       [3805.0, 3806.0, 3807.0, 3808.0, 3809.0, 3810.0],       [3811.0, 3812.0, 3813.0, 3814.0, 3815.0, 3816.0],       [3817.0, 3818.0, 3819.0, 3820.0, 3821.0, 3822.0]],      [[3823.0, 3824.0, 3825.0, 3826.0, 3827.0, 3828.0],       [3829.0, 3830.0, 3831.0, 3832.0, 3833.0, 3834.0],       [3835.0, 3836.0, 3837.0, 3838.0, 3839.0, 3840.0],       [3841.0, 3842.0, 3843.0, 3844.0, 3845.0, 3846.0],       [3847.0, 3848.0, 3849.0, 3850.0, 3851.0, 3852.0],       [3853.0, 3854.0, 3855.0, 3856.0, 3857.0, 3858.0],       [3859.0, 3860.0, 3861.0, 3862.0, 3863.0, 3864.0]],      [[3865.0, 3866.0, 3867.0, 3868.0, 3869.0, 3870.0],       [3871.0, 3872.0, 3873.0, 3874.0, 3875.0, 3876.0],       [3877.0, 3878.0, 3879.0, 3880.0, 3881.0, 3882.0],       [3883.0, 3884.0, 3885.0, 3886.0, 3887.0, 3888.0],       [3889.0, 3890.0, 3891.0, 3892.0, 3893.0, 3894.0],       [3895.0, 3896.0, 3897.0, 3898.0, 3899.0, 3900.0],       [3901.0, 3902.0, 3903.0, 3904.0, 3905.0, 3906.0]],      [[3907.0, 3908.0, 3909.0, 3910.0, 3911.0, 3912.0],       [3913.0, 3914.0, 3915.0, 3916.0, 3917.0, 3918.0],       [3919.0, 3920.0, 3921.0, 3922.0, 3923.0, 3924.0],       [3925.0, 3926.0, 3927.0, 3928.0, 3929.0, 3930.0],       [3931.0, 3932.0, 3933.0, 3934.0, 3935.0, 3936.0],       [3937.0, 3938.0, 3939.0, 3940.0, 3941.0, 3942.0],       [3943.0, 3944.0, 3945.0, 3946.0, 3947.0, 3948.0]],      [[3949.0, 3950.0, 3951.0, 3952.0, 3953.0, 3954.0],       [3955.0, 3956.0, 3957.0, 3958.0, 3959.0, 3960.0],       [3961.0, 3962.0, 3963.0, 3964.0, 3965.0, 3966.0],       [3967.0, 3968.0, 3969.0, 3970.0, 3971.0, 3972.0],       [3973.0, 3974.0, 3975.0, 3976.0, 3977.0, 3978.0],       [3979.0, 3980.0, 3981.0, 3982.0, 3983.0, 3984.0],       [3985.0, 3986.0, 3987.0, 3988.0, 3989.0, 3990.0]],      [[3991.0, 3992.0, 3993.0, 3994.0, 3995.0, 3996.0],       [3997.0, 3998.0, 3999.0, 4000.0, 4001.0, 4002.0],       [4003.0, 4004.0, 4005.0, 4006.0, 4007.0, 4008.0],       [4009.0, 4010.0, 4011.0, 4012.0, 4013.0, 4014.0],       [4015.0, 4016.0, 4017.0, 4018.0, 4019.0, 4020.0],       [4021.0, 4022.0, 4023.0, 4024.0, 4025.0, 4026.0],       [4027.0, 4028.0, 4029.0, 4030.0, 4031.0, 4032.0]]]],    [[[[4033.0, 4034.0, 4035.0, 4036.0, 4037.0, 4038.0],       [4039.0, 4040.0, 4041.0, 4042.0, 4043.0, 4044.0],       [4045.0, 4046.0, 4047.0, 4048.0, 4049.0, 4050.0],       [4051.0, 4052.0, 4053.0, 4054.0, 4055.0, 4056.0],       [4057.0, 4058.0, 4059.0, 4060.0, 4061.0, 4062.0],       [4063.0, 4064.0, 4065.0, 4066.0, 4067.0, 4068.0],       [4069.0, 4070.0, 4071.0, 4072.0, 4073.0, 4074.0]],      [[4075.0, 4076.0, 4077.0, 4078.0, 4079.0, 4080.0],       [4081.0, 4082.0, 4083.0, 4084.0, 4085.0, 4086.0],       [4087.0, 4088.0, 4089.0, 4090.0, 4091.0, 4092.0],       [4093.0, 4094.0, 4095.0, 4096.0, 4097.0, 4098.0],       [4099.0, 4100.0, 4101.0, 4102.0, 4103.0, 4104.0],       [4105.0, 4106.0, 4107.0, 4108.0, 4109.0, 4110.0],       [4111.0, 4112.0, 4113.0, 4114.0, 4115.0, 4116.0]],      [[4117.0, 4118.0, 4119.0, 4120.0, 4121.0, 4122.0],       [4123.0, 4124.0, 4125.0, 4126.0, 4127.0, 4128.0],       [4129.0, 4130.0, 4131.0, 4132.0, 4133.0, 4134.0],       [4135.0, 4136.0, 4137.0, 4138.0, 4139.0, 4140.0],       [4141.0, 4142.0, 4143.0, 4144.0, 4145.0, 4146.0],       [4147.0, 4148.0, 4149.0, 4150.0, 4151.0, 4152.0],       [4153.0, 4154.0, 4155.0, 4156.0, 4157.0, 4158.0]],      [[4159.0, 4160.0, 4161.0, 4162.0, 4163.0, 4164.0],       [4165.0, 4166.0, 4167.0, 4168.0, 4169.0, 4170.0],       [4171.0, 4172.0, 4173.0, 4174.0, 4175.0, 4176.0],       [4177.0, 4178.0, 4179.0, 4180.0, 4181.0, 4182.0],       [4183.0, 4184.0, 4185.0, 4186.0, 4187.0, 4188.0],       [4189.0, 4190.0, 4191.0, 4192.0, 4193.0, 4194.0],       [4195.0, 4196.0, 4197.0, 4198.0, 4199.0, 4200.0]],      [[4201.0, 4202.0, 4203.0, 4204.0, 4205.0, 4206.0],       [4207.0, 4208.0, 4209.0, 4210.0, 4211.0, 4212.0],       [4213.0, 4214.0, 4215.0, 4216.0, 4217.0, 4218.0],       [4219.0, 4220.0, 4221.0, 4222.0, 4223.0, 4224.0],       [4225.0, 4226.0, 4227.0, 4228.0, 4229.0, 4230.0],       [4231.0, 4232.0, 4233.0, 4234.0, 4235.0, 4236.0],       [4237.0, 4238.0, 4239.0, 4240.0, 4241.0, 4242.0]],      [[4243.0, 4244.0, 4245.0, 4246.0, 4247.0, 4248.0],       [4249.0, 4250.0, 4251.0, 4252.0, 4253.0, 4254.0],       [4255.0, 4256.0, 4257.0, 4258.0, 4259.0, 4260.0],       [4261.0, 4262.0, 4263.0, 4264.0, 4265.0, 4266.0],       [4267.0, 4268.0, 4269.0, 4270.0, 4271.0, 4272.0],       [4273.0, 4274.0, 4275.0, 4276.0, 4277.0, 4278.0],       [4279.0, 4280.0, 4281.0, 4282.0, 4283.0, 4284.0]]],     [[[4285.0, 4286.0, 4287.0, 4288.0, 4289.0, 4290.0],       [4291.0, 4292.0, 4293.0, 4294.0, 4295.0, 4296.0],       [4297.0, 4298.0, 4299.0, 4300.0, 4301.0, 4302.0],       [4303.0, 4304.0, 4305.0, 4306.0, 4307.0, 4308.0],       [4309.0, 4310.0, 4311.0, 4312.0, 4313.0, 4314.0],       [4315.0, 4316.0, 4317.0, 4318.0, 4319.0, 4320.0],       [4321.0, 4322.0, 4323.0, 4324.0, 4325.0, 4326.0]],      [[4327.0, 4328.0, 4329.0, 4330.0, 4331.0, 4332.0],       [4333.0, 4334.0, 4335.0, 4336.0, 4337.0, 4338.0],       [4339.0, 4340.0, 4341.0, 4342.0, 4343.0, 4344.0],       [4345.0, 4346.0, 4347.0, 4348.0, 4349.0, 4350.0],       [4351.0, 4352.0, 4353.0, 4354.0, 4355.0, 4356.0],       [4357.0, 4358.0, 4359.0, 4360.0, 4361.0, 4362.0],       [4363.0, 4364.0, 4365.0, 4366.0, 4367.0, 4368.0]],      [[4369.0, 4370.0, 4371.0, 4372.0, 4373.0, 4374.0],       [4375.0, 4376.0, 4377.0, 4378.0, 4379.0, 4380.0],       [4381.0, 4382.0, 4383.0, 4384.0, 4385.0, 4386.0],       [4387.0, 4388.0, 4389.0, 4390.0, 4391.0, 4392.0],       [4393.0, 4394.0, 4395.0, 4396.0, 4397.0, 4398.0],       [4399.0, 4400.0, 4401.0, 4402.0, 4403.0, 4404.0],       [4405.0, 4406.0, 4407.0, 4408.0, 4409.0, 4410.0]],      [[4411.0, 4412.0, 4413.0, 4414.0, 4415.0, 4416.0],       [4417.0, 4418.0, 4419.0, 4420.0, 4421.0, 4422.0],       [4423.0, 4424.0, 4425.0, 4426.0, 4427.0, 4428.0],       [4429.0, 4430.0, 4431.0, 4432.0, 4433.0, 4434.0],       [4435.0, 4436.0, 4437.0, 4438.0, 4439.0, 4440.0],       [4441.0, 4442.0, 4443.0, 4444.0, 4445.0, 4446.0],       [4447.0, 4448.0, 4449.0, 4450.0, 4451.0, 4452.0]],      [[4453.0, 4454.0, 4455.0, 4456.0, 4457.0, 4458.0],       [4459.0, 4460.0, 4461.0, 4462.0, 4463.0, 4464.0],       [4465.0, 4466.0, 4467.0, 4468.0, 4469.0, 4470.0],       [4471.0, 4472.0, 4473.0, 4474.0, 4475.0, 4476.0],       [4477.0, 4478.0, 4479.0, 4480.0, 4481.0, 4482.0],       [4483.0, 4484.0, 4485.0, 4486.0, 4487.0, 4488.0],       [4489.0, 4490.0, 4491.0, 4492.0, 4493.0, 4494.0]],      [[4495.0, 4496.0, 4497.0, 4498.0, 4499.0, 4500.0],       [4501.0, 4502.0, 4503.0, 4504.0, 4505.0, 4506.0],       [4507.0, 4508.0, 4509.0, 4510.0, 4511.0, 4512.0],       [4513.0, 4514.0, 4515.0, 4516.0, 4517.0, 4518.0],       [4519.0, 4520.0, 4521.0, 4522.0, 4523.0, 4524.0],       [4525.0, 4526.0, 4527.0, 4528.0, 4529.0, 4530.0],       [4531.0, 4532.0, 4533.0, 4534.0, 4535.0, 4536.0]]],     [[[4537.0, 4538.0, 4539.0, 4540.0, 4541.0, 4542.0],       [4543.0, 4544.0, 4545.0, 4546.0, 4547.0, 4548.0],       [4549.0, 4550.0, 4551.0, 4552.0, 4553.0, 4554.0],       [4555.0, 4556.0, 4557.0, 4558.0, 4559.0, 4560.0],       [4561.0, 4562.0, 4563.0, 4564.0, 4565.0, 4566.0],       [4567.0, 4568.0, 4569.0, 4570.0, 4571.0, 4572.0],       [4573.0, 4574.0, 4575.0, 4576.0, 4577.0, 4578.0]],      [[4579.0, 4580.0, 4581.0, 4582.0, 4583.0, 4584.0],       [4585.0, 4586.0, 4587.0, 4588.0, 4589.0, 4590.0],       [4591.0, 4592.0, 4593.0, 4594.0, 4595.0, 4596.0],       [4597.0, 4598.0, 4599.0, 4600.0, 4601.0, 4602.0],       [4603.0, 4604.0, 4605.0, 4606.0, 4607.0, 4608.0],       [4609.0, 4610.0, 4611.0, 4612.0, 4613.0, 4614.0],       [4615.0, 4616.0, 4617.0, 4618.0, 4619.0, 4620.0]],      [[4621.0, 4622.0, 4623.0, 4624.0, 4625.0, 4626.0],       [4627.0, 4628.0, 4629.0, 4630.0, 4631.0, 4632.0],       [4633.0, 4634.0, 4635.0, 4636.0, 4637.0, 4638.0],       [4639.0, 4640.0, 4641.0, 4642.0, 4643.0, 4644.0],       [4645.0, 4646.0, 4647.0, 4648.0, 4649.0, 4650.0],       [4651.0, 4652.0, 4653.0, 4654.0, 4655.0, 4656.0],       [4657.0, 4658.0, 4659.0, 4660.0, 4661.0, 4662.0]],      [[4663.0, 4664.0, 4665.0, 4666.0, 4667.0, 4668.0],       [4669.0, 4670.0, 4671.0, 4672.0, 4673.0, 4674.0],       [4675.0, 4676.0, 4677.0, 4678.0, 4679.0, 4680.0],       [4681.0, 4682.0, 4683.0, 4684.0, 4685.0, 4686.0],       [4687.0, 4688.0, 4689.0, 4690.0, 4691.0, 4692.0],       [4693.0, 4694.0, 4695.0, 4696.0, 4697.0, 4698.0],       [4699.0, 4700.0, 4701.0, 4702.0, 4703.0, 4704.0]],      [[4705.0, 4706.0, 4707.0, 4708.0, 4709.0, 4710.0],       [4711.0, 4712.0, 4713.0, 4714.0, 4715.0, 4716.0],       [4717.0, 4718.0, 4719.0, 4720.0, 4721.0, 4722.0],       [4723.0, 4724.0, 4725.0, 4726.0, 4727.0, 4728.0],       [4729.0, 4730.0, 4731.0, 4732.0, 4733.0, 4734.0],       [4735.0, 4736.0, 4737.0, 4738.0, 4739.0, 4740.0],       [4741.0, 4742.0, 4743.0, 4744.0, 4745.0, 4746.0]],      [[4747.0, 4748.0, 4749.0, 4750.0, 4751.0, 4752.0],       [4753.0, 4754.0, 4755.0, 4756.0, 4757.0, 4758.0],       [4759.0, 4760.0, 4761.0, 4762.0, 4763.0, 4764.0],       [4765.0, 4766.0, 4767.0, 4768.0, 4769.0, 4770.0],       [4771.0, 4772.0, 4773.0, 4774.0, 4775.0, 4776.0],       [4777.0, 4778.0, 4779.0, 4780.0, 4781.0, 4782.0],       [4783.0, 4784.0, 4785.0, 4786.0, 4787.0, 4788.0]]],     [[[4789.0, 4790.0, 4791.0, 4792.0, 4793.0, 4794.0],       [4795.0, 4796.0, 4797.0, 4798.0, 4799.0, 4800.0],       [4801.0, 4802.0, 4803.0, 4804.0, 4805.0, 4806.0],       [4807.0, 4808.0, 4809.0, 4810.0, 4811.0, 4812.0],       [4813.0, 4814.0, 4815.0, 4816.0, 4817.0, 4818.0],       [4819.0, 4820.0, 4821.0, 4822.0, 4823.0, 4824.0],       [4825.0, 4826.0, 4827.0, 4828.0, 4829.0, 4830.0]],      [[4831.0, 4832.0, 4833.0, 4834.0, 4835.0, 4836.0],       [4837.0, 4838.0, 4839.0, 4840.0, 4841.0, 4842.0],       [4843.0, 4844.0, 4845.0, 4846.0, 4847.0, 4848.0],       [4849.0, 4850.0, 4851.0, 4852.0, 4853.0, 4854.0],       [4855.0, 4856.0, 4857.0, 4858.0, 4859.0, 4860.0],       [4861.0, 4862.0, 4863.0, 4864.0, 4865.0, 4866.0],       [4867.0, 4868.0, 4869.0, 4870.0, 4871.0, 4872.0]],      [[4873.0, 4874.0, 4875.0, 4876.0, 4877.0, 4878.0],       [4879.0, 4880.0, 4881.0, 4882.0, 4883.0, 4884.0],       [4885.0, 4886.0, 4887.0, 4888.0, 4889.0, 4890.0],       [4891.0, 4892.0, 4893.0, 4894.0, 4895.0, 4896.0],       [4897.0, 4898.0, 4899.0, 4900.0, 4901.0, 4902.0],       [4903.0, 4904.0, 4905.0, 4906.0, 4907.0, 4908.0],       [4909.0, 4910.0, 4911.0, 4912.0, 4913.0, 4914.0]],      [[4915.0, 4916.0, 4917.0, 4918.0, 4919.0, 4920.0],       [4921.0, 4922.0, 4923.0, 4924.0, 4925.0, 4926.0],       [4927.0, 4928.0, 4929.0, 4930.0, 4931.0, 4932.0],       [4933.0, 4934.0, 4935.0, 4936.0, 4937.0, 4938.0],       [4939.0, 4940.0, 4941.0, 4942.0, 4943.0, 4944.0],       [4945.0, 4946.0, 4947.0, 4948.0, 4949.0, 4950.0],       [4951.0, 4952.0, 4953.0, 4954.0, 4955.0, 4956.0]],      [[4957.0, 4958.0, 4959.0, 4960.0, 4961.0, 4962.0],       [4963.0, 4964.0, 4965.0, 4966.0, 4967.0, 4968.0],       [4969.0, 4970.0, 4971.0, 4972.0, 4973.0, 4974.0],       [4975.0, 4976.0, 4977.0, 4978.0, 4979.0, 4980.0],       [4981.0, 4982.0, 4983.0, 4984.0, 4985.0, 4986.0],       [4987.0, 4988.0, 4989.0, 4990.0, 4991.0, 4992.0],       [4993.0, 4994.0, 4995.0, 4996.0, 4997.0, 4998.0]],      [[4999.0, 5000.0, 5001.0, 5002.0, 5003.0, 5004.0],       [5005.0, 5006.0, 5007.0, 5008.0, 5009.0, 5010.0],       [5011.0, 5012.0, 5013.0, 5014.0, 5015.0, 5016.0],       [5017.0, 5018.0, 5019.0, 5020.0, 5021.0, 5022.0],       [5023.0, 5024.0, 5025.0, 5026.0, 5027.0, 5028.0],       [5029.0, 5030.0, 5031.0, 5032.0, 5033.0, 5034.0],       [5035.0, 5036.0, 5037.0, 5038.0, 5039.0, 5040.0]]]],    [[[[5041.0, 5042.0, 5043.0, 5044.0, 5045.0, 5046.0],       [5047.0, 5048.0, 5049.0, 5050.0, 5051.0, 5052.0],       [5053.0, 5054.0, 5055.0, 5056.0, 5057.0, 5058.0],       [5059.0, 5060.0, 5061.0, 5062.0, 5063.0, 5064.0],       [5065.0, 5066.0, 5067.0, 5068.0, 5069.0, 5070.0],       [5071.0, 5072.0, 5073.0, 5074.0, 5075.0, 5076.0],       [5077.0, 5078.0, 5079.0, 5080.0, 5081.0, 5082.0]],      [[5083.0, 5084.0, 5085.0, 5086.0, 5087.0, 5088.0],       [5089.0, 5090.0, 5091.0, 5092.0, 5093.0, 5094.0],       [5095.0, 5096.0, 5097.0, 5098.0, 5099.0, 5100.0],       [5101.0, 5102.0, 5103.0, 5104.0, 5105.0, 5106.0],       [5107.0, 5108.0, 5109.0, 5110.0, 5111.0, 5112.0],       [5113.0, 5114.0, 5115.0, 5116.0, 5117.0, 5118.0],       [5119.0, 5120.0, 5121.0, 5122.0, 5123.0, 5124.0]],      [[5125.0, 5126.0, 5127.0, 5128.0, 5129.0, 5130.0],       [5131.0, 5132.0, 5133.0, 5134.0, 5135.0, 5136.0],       [5137.0, 5138.0, 5139.0, 5140.0, 5141.0, 5142.0],       [5143.0, 5144.0, 5145.0, 5146.0, 5147.0, 5148.0],       [5149.0, 5150.0, 5151.0, 5152.0, 5153.0, 5154.0],       [5155.0, 5156.0, 5157.0, 5158.0, 5159.0, 5160.0],       [5161.0, 5162.0, 5163.0, 5164.0, 5165.0, 5166.0]],      [[5167.0, 5168.0, 5169.0, 5170.0, 5171.0, 5172.0],       [5173.0, 5174.0, 5175.0, 5176.0, 5177.0, 5178.0],       [5179.0, 5180.0, 5181.0, 5182.0, 5183.0, 5184.0],       [5185.0, 5186.0, 5187.0, 5188.0, 5189.0, 5190.0],       [5191.0, 5192.0, 5193.0, 5194.0, 5195.0, 5196.0],       [5197.0, 5198.0, 5199.0, 5200.0, 5201.0, 5202.0],       [5203.0, 5204.0, 5205.0, 5206.0, 5207.0, 5208.0]],      [[5209.0, 5210.0, 5211.0, 5212.0, 5213.0, 5214.0],       [5215.0, 5216.0, 5217.0, 5218.0, 5219.0, 5220.0],       [5221.0, 5222.0, 5223.0, 5224.0, 5225.0, 5226.0],       [5227.0, 5228.0, 5229.0, 5230.0, 5231.0, 5232.0],       [5233.0, 5234.0, 5235.0, 5236.0, 5237.0, 5238.0],       [5239.0, 5240.0, 5241.0, 5242.0, 5243.0, 5244.0],       [5245.0, 5246.0, 5247.0, 5248.0, 5249.0, 5250.0]],      [[5251.0, 5252.0, 5253.0, 5254.0, 5255.0, 5256.0],       [5257.0, 5258.0, 5259.0, 5260.0, 5261.0, 5262.0],       [5263.0, 5264.0, 5265.0, 5266.0, 5267.0, 5268.0],       [5269.0, 5270.0, 5271.0, 5272.0, 5273.0, 5274.0],       [5275.0, 5276.0, 5277.0, 5278.0, 5279.0, 5280.0],       [5281.0, 5282.0, 5283.0, 5284.0, 5285.0, 5286.0],       [5287.0, 5288.0, 5289.0, 5290.0, 5291.0, 5292.0]]],     [[[5293.0, 5294.0, 5295.0, 5296.0, 5297.0, 5298.0],       [5299.0, 5300.0, 5301.0, 5302.0, 5303.0, 5304.0],       [5305.0, 5306.0, 5307.0, 5308.0, 5309.0, 5310.0],       [5311.0, 5312.0, 5313.0, 5314.0, 5315.0, 5316.0],       [5317.0, 5318.0, 5319.0, 5320.0, 5321.0, 5322.0],       [5323.0, 5324.0, 5325.0, 5326.0, 5327.0, 5328.0],       [5329.0, 5330.0, 5331.0, 5332.0, 5333.0, 5334.0]],      [[5335.0, 5336.0, 5337.0, 5338.0, 5339.0, 5340.0],       [5341.0, 5342.0, 5343.0, 5344.0, 5345.0, 5346.0],       [5347.0, 5348.0, 5349.0, 5350.0, 5351.0, 5352.0],       [5353.0, 5354.0, 5355.0, 5356.0, 5357.0, 5358.0],       [5359.0, 5360.0, 5361.0, 5362.0, 5363.0, 5364.0],       [5365.0, 5366.0, 5367.0, 5368.0, 5369.0, 5370.0],       [5371.0, 5372.0, 5373.0, 5374.0, 5375.0, 5376.0]],      [[5377.0, 5378.0, 5379.0, 5380.0, 5381.0, 5382.0],       [5383.0, 5384.0, 5385.0, 5386.0, 5387.0, 5388.0],       [5389.0, 5390.0, 5391.0, 5392.0, 5393.0, 5394.0],       [5395.0, 5396.0, 5397.0, 5398.0, 5399.0, 5400.0],       [5401.0, 5402.0, 5403.0, 5404.0, 5405.0, 5406.0],       [5407.0, 5408.0, 5409.0, 5410.0, 5411.0, 5412.0],       [5413.0, 5414.0, 5415.0, 5416.0, 5417.0, 5418.0]],      [[5419.0, 5420.0, 5421.0, 5422.0, 5423.0, 5424.0],       [5425.0, 5426.0, 5427.0, 5428.0, 5429.0, 5430.0],       [5431.0, 5432.0, 5433.0, 5434.0, 5435.0, 5436.0],       [5437.0, 5438.0, 5439.0, 5440.0, 5441.0, 5442.0],       [5443.0, 5444.0, 5445.0, 5446.0, 5447.0, 5448.0],       [5449.0, 5450.0, 5451.0, 5452.0, 5453.0, 5454.0],       [5455.0, 5456.0, 5457.0, 5458.0, 5459.0, 5460.0]],      [[5461.0, 5462.0, 5463.0, 5464.0, 5465.0, 5466.0],       [5467.0, 5468.0, 5469.0, 5470.0, 5471.0, 5472.0],       [5473.0, 5474.0, 5475.0, 5476.0, 5477.0, 5478.0],       [5479.0, 5480.0, 5481.0, 5482.0, 5483.0, 5484.0],       [5485.0, 5486.0, 5487.0, 5488.0, 5489.0, 5490.0],       [5491.0, 5492.0, 5493.0, 5494.0, 5495.0, 5496.0],       [5497.0, 5498.0, 5499.0, 5500.0, 5501.0, 5502.0]],      [[5503.0, 5504.0, 5505.0, 5506.0, 5507.0, 5508.0],       [5509.0, 5510.0, 5511.0, 5512.0, 5513.0, 5514.0],       [5515.0, 5516.0, 5517.0, 5518.0, 5519.0, 5520.0],       [5521.0, 5522.0, 5523.0, 5524.0, 5525.0, 5526.0],       [5527.0, 5528.0, 5529.0, 5530.0, 5531.0, 5532.0],       [5533.0, 5534.0, 5535.0, 5536.0, 5537.0, 5538.0],       [5539.0, 5540.0, 5541.0, 5542.0, 5543.0, 5544.0]]],     [[[5545.0, 5546.0, 5547.0, 5548.0, 5549.0, 5550.0],       [5551.0, 5552.0, 5553.0, 5554.0, 5555.0, 5556.0],       [5557.0, 5558.0, 5559.0, 5560.0, 5561.0, 5562.0],       [5563.0, 5564.0, 5565.0, 5566.0, 5567.0, 5568.0],       [5569.0, 5570.0, 5571.0, 5572.0, 5573.0, 5574.0],       [5575.0, 5576.0, 5577.0, 5578.0, 5579.0, 5580.0],       [5581.0, 5582.0, 5583.0, 5584.0, 5585.0, 5586.0]],      [[5587.0, 5588.0, 5589.0, 5590.0, 5591.0, 5592.0],       [5593.0, 5594.0, 5595.0, 5596.0, 5597.0, 5598.0],       [5599.0, 5600.0, 5601.0, 5602.0, 5603.0, 5604.0],       [5605.0, 5606.0, 5607.0, 5608.0, 5609.0, 5610.0],       [5611.0, 5612.0, 5613.0, 5614.0, 5615.0, 5616.0],       [5617.0, 5618.0, 5619.0, 5620.0, 5621.0, 5622.0],       [5623.0, 5624.0, 5625.0, 5626.0, 5627.0, 5628.0]],      [[5629.0, 5630.0, 5631.0, 5632.0, 5633.0, 5634.0],       [5635.0, 5636.0, 5637.0, 5638.0, 5639.0, 5640.0],       [5641.0, 5642.0, 5643.0, 5644.0, 5645.0, 5646.0],       [5647.0, 5648.0, 5649.0, 5650.0, 5651.0, 5652.0],       [5653.0, 5654.0, 5655.0, 5656.0, 5657.0, 5658.0],       [5659.0, 5660.0, 5661.0, 5662.0, 5663.0, 5664.0],       [5665.0, 5666.0, 5667.0, 5668.0, 5669.0, 5670.0]],      [[5671.0, 5672.0, 5673.0, 5674.0, 5675.0, 5676.0],       [5677.0, 5678.0, 5679.0, 5680.0, 5681.0, 5682.0],       [5683.0, 5684.0, 5685.0, 5686.0, 5687.0, 5688.0],       [5689.0, 5690.0, 5691.0, 5692.0, 5693.0, 5694.0],       [5695.0, 5696.0, 5697.0, 5698.0, 5699.0, 5700.0],       [5701.0, 5702.0, 5703.0, 5704.0, 5705.0, 5706.0],       [5707.0, 5708.0, 5709.0, 5710.0, 5711.0, 5712.0]],      [[5713.0, 5714.0, 5715.0, 5716.0, 5717.0, 5718.0],       [5719.0, 5720.0, 5721.0, 5722.0, 5723.0, 5724.0],       [5725.0, 5726.0, 5727.0, 5728.0, 5729.0, 5730.0],       [5731.0, 5732.0, 5733.0, 5734.0, 5735.0, 5736.0],       [5737.0, 5738.0, 5739.0, 5740.0, 5741.0, 5742.0],       [5743.0, 5744.0, 5745.0, 5746.0, 5747.0, 5748.0],       [5749.0, 5750.0, 5751.0, 5752.0, 5753.0, 5754.0]],      [[5755.0, 5756.0, 5757.0, 5758.0, 5759.0, 5760.0],       [5761.0, 5762.0, 5763.0, 5764.0, 5765.0, 5766.0],       [5767.0, 5768.0, 5769.0, 5770.0, 5771.0, 5772.0],       [5773.0, 5774.0, 5775.0, 5776.0, 5777.0, 5778.0],       [5779.0, 5780.0, 5781.0, 5782.0, 5783.0, 5784.0],       [5785.0, 5786.0, 5787.0, 5788.0, 5789.0, 5790.0],       [5791.0, 5792.0, 5793.0, 5794.0, 5795.0, 5796.0]]],     [[[5797.0, 5798.0, 5799.0, 5800.0, 5801.0, 5802.0],       [5803.0, 5804.0, 5805.0, 5806.0, 5807.0, 5808.0],       [5809.0, 5810.0, 5811.0, 5812.0, 5813.0, 5814.0],       [5815.0, 5816.0, 5817.0, 5818.0, 5819.0, 5820.0],       [5821.0, 5822.0, 5823.0, 5824.0, 5825.0, 5826.0],       [5827.0, 5828.0, 5829.0, 5830.0, 5831.0, 5832.0],       [5833.0, 5834.0, 5835.0, 5836.0, 5837.0, 5838.0]],      [[5839.0, 5840.0, 5841.0, 5842.0, 5843.0, 5844.0],       [5845.0, 5846.0, 5847.0, 5848.0, 5849.0, 5850.0],       [5851.0, 5852.0, 5853.0, 5854.0, 5855.0, 5856.0],       [5857.0, 5858.0, 5859.0, 5860.0, 5861.0, 5862.0],       [5863.0, 5864.0, 5865.0, 5866.0, 5867.0, 5868.0],       [5869.0, 5870.0, 5871.0, 5872.0, 5873.0, 5874.0],       [5875.0, 5876.0, 5877.0, 5878.0, 5879.0, 5880.0]],      [[5881.0, 5882.0, 5883.0, 5884.0, 5885.0, 5886.0],       [5887.0, 5888.0, 5889.0, 5890.0, 5891.0, 5892.0],       [5893.0, 5894.0, 5895.0, 5896.0, 5897.0, 5898.0],       [5899.0, 5900.0, 5901.0, 5902.0, 5903.0, 5904.0],       [5905.0, 5906.0, 5907.0, 5908.0, 5909.0, 5910.0],       [5911.0, 5912.0, 5913.0, 5914.0, 5915.0, 5916.0],       [5917.0, 5918.0, 5919.0, 5920.0, 5921.0, 5922.0]],      [[5923.0, 5924.0, 5925.0, 5926.0, 5927.0, 5928.0],       [5929.0, 5930.0, 5931.0, 5932.0, 5933.0, 5934.0],       [5935.0, 5936.0, 5937.0, 5938.0, 5939.0, 5940.0],       [5941.0, 5942.0, 5943.0, 5944.0, 5945.0, 5946.0],       [5947.0, 5948.0, 5949.0, 5950.0, 5951.0, 5952.0],       [5953.0, 5954.0, 5955.0, 5956.0, 5957.0, 5958.0],       [5959.0, 5960.0, 5961.0, 5962.0, 5963.0, 5964.0]],      [[5965.0, 5966.0, 5967.0, 5968.0, 5969.0, 5970.0],       [5971.0, 5972.0, 5973.0, 5974.0, 5975.0, 5976.0],       [5977.0, 5978.0, 5979.0, 5980.0, 5981.0, 5982.0],       [5983.0, 5984.0, 5985.0, 5986.0, 5987.0, 5988.0],       [5989.0, 5990.0, 5991.0, 5992.0, 5993.0, 5994.0],       [5995.0, 5996.0, 5997.0, 5998.0, 5999.0, 6000.0],       [6001.0, 6002.0, 6003.0, 6004.0, 6005.0, 6006.0]],      [[6007.0, 6008.0, 6009.0, 6010.0, 6011.0, 6012.0],       [6013.0, 6014.0, 6015.0, 6016.0, 6017.0, 6018.0],       [6019.0, 6020.0, 6021.0, 6022.0, 6023.0, 6024.0],       [6025.0, 6026.0, 6027.0, 6028.0, 6029.0, 6030.0],       [6031.0, 6032.0, 6033.0, 6034.0, 6035.0, 6036.0],       [6037.0, 6038.0, 6039.0, 6040.0, 6041.0, 6042.0],       [6043.0, 6044.0, 6045.0, 6046.0, 6047.0, 6048.0]]]]],   [[[[[6049.0, 6050.0, 6051.0, 6052.0, 6053.0, 6054.0],       [6055.0, 6056.0, 6057.0, 6058.0, 6059.0, 6060.0],       [6061.0, 6062.0, 6063.0, 6064.0, 6065.0, 6066.0],       [6067.0, 6068.0, 6069.0, 6070.0, 6071.0, 6072.0],       [6073.0, 6074.0, 6075.0, 6076.0, 6077.0, 6078.0],       [6079.0, 6080.0, 6081.0, 6082.0, 6083.0, 6084.0],       [6085.0, 6086.0, 6087.0, 6088.0, 6089.0, 6090.0]],      [[6091.0, 6092.0, 6093.0, 6094.0, 6095.0, 6096.0],       [6097.0, 6098.0, 6099.0, 6100.0, 6101.0, 6102.0],       [6103.0, 6104.0, 6105.0, 6106.0, 6107.0, 6108.0],       [6109.0, 6110.0, 6111.0, 6112.0, 6113.0, 6114.0],       [6115.0, 6116.0, 6117.0, 6118.0, 6119.0, 6120.0],       [6121.0, 6122.0, 6123.0, 6124.0, 6125.0, 6126.0],       [6127.0, 6128.0, 6129.0, 6130.0, 6131.0, 6132.0]],      [[6133.0, 6134.0, 6135.0, 6136.0, 6137.0, 6138.0],       [6139.0, 6140.0, 6141.0, 6142.0, 6143.0, 6144.0],       [6145.0, 6146.0, 6147.0, 6148.0, 6149.0, 6150.0],       [6151.0, 6152.0, 6153.0, 6154.0, 6155.0, 6156.0],       [6157.0, 6158.0, 6159.0, 6160.0, 6161.0, 6162.0],       [6163.0, 6164.0, 6165.0, 6166.0, 6167.0, 6168.0],       [6169.0, 6170.0, 6171.0, 6172.0, 6173.0, 6174.0]],      [[6175.0, 6176.0, 6177.0, 6178.0, 6179.0, 6180.0],       [6181.0, 6182.0, 6183.0, 6184.0, 6185.0, 6186.0],       [6187.0, 6188.0, 6189.0, 6190.0, 6191.0, 6192.0],       [6193.0, 6194.0, 6195.0, 6196.0, 6197.0, 6198.0],       [6199.0, 6200.0, 6201.0, 6202.0, 6203.0, 6204.0],       [6205.0, 6206.0, 6207.0, 6208.0, 6209.0, 6210.0],       [6211.0, 6212.0, 6213.0, 6214.0, 6215.0, 6216.0]],      [[6217.0, 6218.0, 6219.0, 6220.0, 6221.0, 6222.0],       [6223.0, 6224.0, 6225.0, 6226.0, 6227.0, 6228.0],       [6229.0, 6230.0, 6231.0, 6232.0, 6233.0, 6234.0],       [6235.0, 6236.0, 6237.0, 6238.0, 6239.0, 6240.0],       [6241.0, 6242.0, 6243.0, 6244.0, 6245.0, 6246.0],       [6247.0, 6248.0, 6249.0, 6250.0, 6251.0, 6252.0],       [6253.0, 6254.0, 6255.0, 6256.0, 6257.0, 6258.0]],      [[6259.0, 6260.0, 6261.0, 6262.0, 6263.0, 6264.0],       [6265.0, 6266.0, 6267.0, 6268.0, 6269.0, 6270.0],       [6271.0, 6272.0, 6273.0, 6274.0, 6275.0, 6276.0],       [6277.0, 6278.0, 6279.0, 6280.0, 6281.0, 6282.0],       [6283.0, 6284.0, 6285.0, 6286.0, 6287.0, 6288.0],       [6289.0, 6290.0, 6291.0, 6292.0, 6293.0, 6294.0],       [6295.0, 6296.0, 6297.0, 6298.0, 6299.0, 6300.0]]],     [[[6301.0, 6302.0, 6303.0, 6304.0, 6305.0, 6306.0],       [6307.0, 6308.0, 6309.0, 6310.0, 6311.0, 6312.0],       [6313.0, 6314.0, 6315.0, 6316.0, 6317.0, 6318.0],       [6319.0, 6320.0, 6321.0, 6322.0, 6323.0, 6324.0],       [6325.0, 6326.0, 6327.0, 6328.0, 6329.0, 6330.0],       [6331.0, 6332.0, 6333.0, 6334.0, 6335.0, 6336.0],       [6337.0, 6338.0, 6339.0, 6340.0, 6341.0, 6342.0]],      [[6343.0, 6344.0, 6345.0, 6346.0, 6347.0, 6348.0],       [6349.0, 6350.0, 6351.0, 6352.0, 6353.0, 6354.0],       [6355.0, 6356.0, 6357.0, 6358.0, 6359.0, 6360.0],       [6361.0, 6362.0, 6363.0, 6364.0, 6365.0, 6366.0],       [6367.0, 6368.0, 6369.0, 6370.0, 6371.0, 6372.0],       [6373.0, 6374.0, 6375.0, 6376.0, 6377.0, 6378.0],       [6379.0, 6380.0, 6381.0, 6382.0, 6383.0, 6384.0]],      [[6385.0, 6386.0, 6387.0, 6388.0, 6389.0, 6390.0],       [6391.0, 6392.0, 6393.0, 6394.0, 6395.0, 6396.0],       [6397.0, 6398.0, 6399.0, 6400.0, 6401.0, 6402.0],       [6403.0, 6404.0, 6405.0, 6406.0, 6407.0, 6408.0],       [6409.0, 6410.0, 6411.0, 6412.0, 6413.0, 6414.0],       [6415.0, 6416.0, 6417.0, 6418.0, 6419.0, 6420.0],       [6421.0, 6422.0, 6423.0, 6424.0, 6425.0, 6426.0]],      [[6427.0, 6428.0, 6429.0, 6430.0, 6431.0, 6432.0],       [6433.0, 6434.0, 6435.0, 6436.0, 6437.0, 6438.0],       [6439.0, 6440.0, 6441.0, 6442.0, 6443.0, 6444.0],       [6445.0, 6446.0, 6447.0, 6448.0, 6449.0, 6450.0],       [6451.0, 6452.0, 6453.0, 6454.0, 6455.0, 6456.0],       [6457.0, 6458.0, 6459.0, 6460.0, 6461.0, 6462.0],       [6463.0, 6464.0, 6465.0, 6466.0, 6467.0, 6468.0]],      [[6469.0, 6470.0, 6471.0, 6472.0, 6473.0, 6474.0],       [6475.0, 6476.0, 6477.0, 6478.0, 6479.0, 6480.0],       [6481.0, 6482.0, 6483.0, 6484.0, 6485.0, 6486.0],       [6487.0, 6488.0, 6489.0, 6490.0, 6491.0, 6492.0],       [6493.0, 6494.0, 6495.0, 6496.0, 6497.0, 6498.0],       [6499.0, 6500.0, 6501.0, 6502.0, 6503.0, 6504.0],       [6505.0, 6506.0, 6507.0, 6508.0, 6509.0, 6510.0]],      [[6511.0, 6512.0, 6513.0, 6514.0, 6515.0, 6516.0],       [6517.0, 6518.0, 6519.0, 6520.0, 6521.0, 6522.0],       [6523.0, 6524.0, 6525.0, 6526.0, 6527.0, 6528.0],       [6529.0, 6530.0, 6531.0, 6532.0, 6533.0, 6534.0],       [6535.0, 6536.0, 6537.0, 6538.0, 6539.0, 6540.0],       [6541.0, 6542.0, 6543.0, 6544.0, 6545.0, 6546.0],       [6547.0, 6548.0, 6549.0, 6550.0, 6551.0, 6552.0]]],     [[[6553.0, 6554.0, 6555.0, 6556.0, 6557.0, 6558.0],       [6559.0, 6560.0, 6561.0, 6562.0, 6563.0, 6564.0],       [6565.0, 6566.0, 6567.0, 6568.0, 6569.0, 6570.0],       [6571.0, 6572.0, 6573.0, 6574.0, 6575.0, 6576.0],       [6577.0, 6578.0, 6579.0, 6580.0, 6581.0, 6582.0],       [6583.0, 6584.0, 6585.0, 6586.0, 6587.0, 6588.0],       [6589.0, 6590.0, 6591.0, 6592.0, 6593.0, 6594.0]],      [[6595.0, 6596.0, 6597.0, 6598.0, 6599.0, 6600.0],       [6601.0, 6602.0, 6603.0, 6604.0, 6605.0, 6606.0],       [6607.0, 6608.0, 6609.0, 6610.0, 6611.0, 6612.0],       [6613.0, 6614.0, 6615.0, 6616.0, 6617.0, 6618.0],       [6619.0, 6620.0, 6621.0, 6622.0, 6623.0, 6624.0],       [6625.0, 6626.0, 6627.0, 6628.0, 6629.0, 6630.0],       [6631.0, 6632.0, 6633.0, 6634.0, 6635.0, 6636.0]],      [[6637.0, 6638.0, 6639.0, 6640.0, 6641.0, 6642.0],       [6643.0, 6644.0, 6645.0, 6646.0, 6647.0, 6648.0],       [6649.0, 6650.0, 6651.0, 6652.0, 6653.0, 6654.0],       [6655.0, 6656.0, 6657.0, 6658.0, 6659.0, 6660.0],       [6661.0, 6662.0, 6663.0, 6664.0, 6665.0, 6666.0],       [6667.0, 6668.0, 6669.0, 6670.0, 6671.0, 6672.0],       [6673.0, 6674.0, 6675.0, 6676.0, 6677.0, 6678.0]],      [[6679.0, 6680.0, 6681.0, 6682.0, 6683.0, 6684.0],       [6685.0, 6686.0, 6687.0, 6688.0, 6689.0, 6690.0],       [6691.0, 6692.0, 6693.0, 6694.0, 6695.0, 6696.0],       [6697.0, 6698.0, 6699.0, 6700.0, 6701.0, 6702.0],       [6703.0, 6704.0, 6705.0, 6706.0, 6707.0, 6708.0],       [6709.0, 6710.0, 6711.0, 6712.0, 6713.0, 6714.0],       [6715.0, 6716.0, 6717.0, 6718.0, 6719.0, 6720.0]],      [[6721.0, 6722.0, 6723.0, 6724.0, 6725.0, 6726.0],       [6727.0, 6728.0, 6729.0, 6730.0, 6731.0, 6732.0],       [6733.0, 6734.0, 6735.0, 6736.0, 6737.0, 6738.0],       [6739.0, 6740.0, 6741.0, 6742.0, 6743.0, 6744.0],       [6745.0, 6746.0, 6747.0, 6748.0, 6749.0, 6750.0],       [6751.0, 6752.0, 6753.0, 6754.0, 6755.0, 6756.0],       [6757.0, 6758.0, 6759.0, 6760.0, 6761.0, 6762.0]],      [[6763.0, 6764.0, 6765.0, 6766.0, 6767.0, 6768.0],       [6769.0, 6770.0, 6771.0, 6772.0, 6773.0, 6774.0],       [6775.0, 6776.0, 6777.0, 6778.0, 6779.0, 6780.0],       [6781.0, 6782.0, 6783.0, 6784.0, 6785.0, 6786.0],       [6787.0, 6788.0, 6789.0, 6790.0, 6791.0, 6792.0],       [6793.0, 6794.0, 6795.0, 6796.0, 6797.0, 6798.0],       [6799.0, 6800.0, 6801.0, 6802.0, 6803.0, 6804.0]]],     [[[6805.0, 6806.0, 6807.0, 6808.0, 6809.0, 6810.0],       [6811.0, 6812.0, 6813.0, 6814.0, 6815.0, 6816.0],       [6817.0, 6818.0, 6819.0, 6820.0, 6821.0, 6822.0],       [6823.0, 6824.0, 6825.0, 6826.0, 6827.0, 6828.0],       [6829.0, 6830.0, 6831.0, 6832.0, 6833.0, 6834.0],       [6835.0, 6836.0, 6837.0, 6838.0, 6839.0, 6840.0],       [6841.0, 6842.0, 6843.0, 6844.0, 6845.0, 6846.0]],      [[6847.0, 6848.0, 6849.0, 6850.0, 6851.0, 6852.0],       [6853.0, 6854.0, 6855.0, 6856.0, 6857.0, 6858.0],       [6859.0, 6860.0, 6861.0, 6862.0, 6863.0, 6864.0],       [6865.0, 6866.0, 6867.0, 6868.0, 6869.0, 6870.0],       [6871.0, 6872.0, 6873.0, 6874.0, 6875.0, 6876.0],       [6877.0, 6878.0, 6879.0, 6880.0, 6881.0, 6882.0],       [6883.0, 6884.0, 6885.0, 6886.0, 6887.0, 6888.0]],      [[6889.0, 6890.0, 6891.0, 6892.0, 6893.0, 6894.0],       [6895.0, 6896.0, 6897.0, 6898.0, 6899.0, 6900.0],       [6901.0, 6902.0, 6903.0, 6904.0, 6905.0, 6906.0],       [6907.0, 6908.0, 6909.0, 6910.0, 6911.0, 6912.0],       [6913.0, 6914.0, 6915.0, 6916.0, 6917.0, 6918.0],       [6919.0, 6920.0, 6921.0, 6922.0, 6923.0, 6924.0],       [6925.0, 6926.0, 6927.0, 6928.0, 6929.0, 6930.0]],      [[6931.0, 6932.0, 6933.0, 6934.0, 6935.0, 6936.0],       [6937.0, 6938.0, 6939.0, 6940.0, 6941.0, 6942.0],       [6943.0, 6944.0, 6945.0, 6946.0, 6947.0, 6948.0],       [6949.0, 6950.0, 6951.0, 6952.0, 6953.0, 6954.0],       [6955.0, 6956.0, 6957.0, 6958.0, 6959.0, 6960.0],       [6961.0, 6962.0, 6963.0, 6964.0, 6965.0, 6966.0],       [6967.0, 6968.0, 6969.0, 6970.0, 6971.0, 6972.0]],      [[6973.0, 6974.0, 6975.0, 6976.0, 6977.0, 6978.0],       [6979.0, 6980.0, 6981.0, 6982.0, 6983.0, 6984.0],       [6985.0, 6986.0, 6987.0, 6988.0, 6989.0, 6990.0],       [6991.0, 6992.0, 6993.0, 6994.0, 6995.0, 6996.0],       [6997.0, 6998.0, 6999.0, 7000.0, 7001.0, 7002.0],       [7003.0, 7004.0, 7005.0, 7006.0, 7007.0, 7008.0],       [7009.0, 7010.0, 7011.0, 7012.0, 7013.0, 7014.0]],      [[7015.0, 7016.0, 7017.0, 7018.0, 7019.0, 7020.0],       [7021.0, 7022.0, 7023.0, 7024.0, 7025.0, 7026.0],       [7027.0, 7028.0, 7029.0, 7030.0, 7031.0, 7032.0],       [7033.0, 7034.0, 7035.0, 7036.0, 7037.0, 7038.0],       [7039.0, 7040.0, 7041.0, 7042.0, 7043.0, 7044.0],       [7045.0, 7046.0, 7047.0, 7048.0, 7049.0, 7050.0],       [7051.0, 7052.0, 7053.0, 7054.0, 7055.0, 7056.0]]]],    [[[[7057.0, 7058.0, 7059.0, 7060.0, 7061.0, 7062.0],       [7063.0, 7064.0, 7065.0, 7066.0, 7067.0, 7068.0],       [7069.0, 7070.0, 7071.0, 7072.0, 7073.0, 7074.0],       [7075.0, 7076.0, 7077.0, 7078.0, 7079.0, 7080.0],       [7081.0, 7082.0, 7083.0, 7084.0, 7085.0, 7086.0],       [7087.0, 7088.0, 7089.0, 7090.0, 7091.0, 7092.0],       [7093.0, 7094.0, 7095.0, 7096.0, 7097.0, 7098.0]],      [[7099.0, 7100.0, 7101.0, 7102.0, 7103.0, 7104.0],       [7105.0, 7106.0, 7107.0, 7108.0, 7109.0, 7110.0],       [7111.0, 7112.0, 7113.0, 7114.0, 7115.0, 7116.0],       [7117.0, 7118.0, 7119.0, 7120.0, 7121.0, 7122.0],       [7123.0, 7124.0, 7125.0, 7126.0, 7127.0, 7128.0],       [7129.0, 7130.0, 7131.0, 7132.0, 7133.0, 7134.0],       [7135.0, 7136.0, 7137.0, 7138.0, 7139.0, 7140.0]],      [[7141.0, 7142.0, 7143.0, 7144.0, 7145.0, 7146.0],       [7147.0, 7148.0, 7149.0, 7150.0, 7151.0, 7152.0],       [7153.0, 7154.0, 7155.0, 7156.0, 7157.0, 7158.0],       [7159.0, 7160.0, 7161.0, 7162.0, 7163.0, 7164.0],       [7165.0, 7166.0, 7167.0, 7168.0, 7169.0, 7170.0],       [7171.0, 7172.0, 7173.0, 7174.0, 7175.0, 7176.0],       [7177.0, 7178.0, 7179.0, 7180.0, 7181.0, 7182.0]],      [[7183.0, 7184.0, 7185.0, 7186.0, 7187.0, 7188.0],       [7189.0, 7190.0, 7191.0, 7192.0, 7193.0, 7194.0],       [7195.0, 7196.0, 7197.0, 7198.0, 7199.0, 7200.0],       [7201.0, 7202.0, 7203.0, 7204.0, 7205.0, 7206.0],       [7207.0, 7208.0, 7209.0, 7210.0, 7211.0, 7212.0],       [7213.0, 7214.0, 7215.0, 7216.0, 7217.0, 7218.0],       [7219.0, 7220.0, 7221.0, 7222.0, 7223.0, 7224.0]],      [[7225.0, 7226.0, 7227.0, 7228.0, 7229.0, 7230.0],       [7231.0, 7232.0, 7233.0, 7234.0, 7235.0, 7236.0],       [7237.0, 7238.0, 7239.0, 7240.0, 7241.0, 7242.0],       [7243.0, 7244.0, 7245.0, 7246.0, 7247.0, 7248.0],       [7249.0, 7250.0, 7251.0, 7252.0, 7253.0, 7254.0],       [7255.0, 7256.0, 7257.0, 7258.0, 7259.0, 7260.0],       [7261.0, 7262.0, 7263.0, 7264.0, 7265.0, 7266.0]],      [[7267.0, 7268.0, 7269.0, 7270.0, 7271.0, 7272.0],       [7273.0, 7274.0, 7275.0, 7276.0, 7277.0, 7278.0],       [7279.0, 7280.0, 7281.0, 7282.0, 7283.0, 7284.0],       [7285.0, 7286.0, 7287.0, 7288.0, 7289.0, 7290.0],       [7291.0, 7292.0, 7293.0, 7294.0, 7295.0, 7296.0],       [7297.0, 7298.0, 7299.0, 7300.0, 7301.0, 7302.0],       [7303.0, 7304.0, 7305.0, 7306.0, 7307.0, 7308.0]]],     [[[7309.0, 7310.0, 7311.0, 7312.0, 7313.0, 7314.0],       [7315.0, 7316.0, 7317.0, 7318.0, 7319.0, 7320.0],       [7321.0, 7322.0, 7323.0, 7324.0, 7325.0, 7326.0],       [7327.0, 7328.0, 7329.0, 7330.0, 7331.0, 7332.0],       [7333.0, 7334.0, 7335.0, 7336.0, 7337.0, 7338.0],       [7339.0, 7340.0, 7341.0, 7342.0, 7343.0, 7344.0],       [7345.0, 7346.0, 7347.0, 7348.0, 7349.0, 7350.0]],      [[7351.0, 7352.0, 7353.0, 7354.0, 7355.0, 7356.0],       [7357.0, 7358.0, 7359.0, 7360.0, 7361.0, 7362.0],       [7363.0, 7364.0, 7365.0, 7366.0, 7367.0, 7368.0],       [7369.0, 7370.0, 7371.0, 7372.0, 7373.0, 7374.0],       [7375.0, 7376.0, 7377.0, 7378.0, 7379.0, 7380.0],       [7381.0, 7382.0, 7383.0, 7384.0, 7385.0, 7386.0],       [7387.0, 7388.0, 7389.0, 7390.0, 7391.0, 7392.0]],      [[7393.0, 7394.0, 7395.0, 7396.0, 7397.0, 7398.0],       [7399.0, 7400.0, 7401.0, 7402.0, 7403.0, 7404.0],       [7405.0, 7406.0, 7407.0, 7408.0, 7409.0, 7410.0],       [7411.0, 7412.0, 7413.0, 7414.0, 7415.0, 7416.0],       [7417.0, 7418.0, 7419.0, 7420.0, 7421.0, 7422.0],       [7423.0, 7424.0, 7425.0, 7426.0, 7427.0, 7428.0],       [7429.0, 7430.0, 7431.0, 7432.0, 7433.0, 7434.0]],      [[7435.0, 7436.0, 7437.0, 7438.0, 7439.0, 7440.0],       [7441.0, 7442.0, 7443.0, 7444.0, 7445.0, 7446.0],       [7447.0, 7448.0, 7449.0, 7450.0, 7451.0, 7452.0],       [7453.0, 7454.0, 7455.0, 7456.0, 7457.0, 7458.0],       [7459.0, 7460.0, 7461.0, 7462.0, 7463.0, 7464.0],       [7465.0, 7466.0, 7467.0, 7468.0, 7469.0, 7470.0],       [7471.0, 7472.0, 7473.0, 7474.0, 7475.0, 7476.0]],      [[7477.0, 7478.0, 7479.0, 7480.0, 7481.0, 7482.0],       [7483.0, 7484.0, 7485.0, 7486.0, 7487.0, 7488.0],       [7489.0, 7490.0, 7491.0, 7492.0, 7493.0, 7494.0],       [7495.0, 7496.0, 7497.0, 7498.0, 7499.0, 7500.0],       [7501.0, 7502.0, 7503.0, 7504.0, 7505.0, 7506.0],       [7507.0, 7508.0, 7509.0, 7510.0, 7511.0, 7512.0],       [7513.0, 7514.0, 7515.0, 7516.0, 7517.0, 7518.0]],      [[7519.0, 7520.0, 7521.0, 7522.0, 7523.0, 7524.0],       [7525.0, 7526.0, 7527.0, 7528.0, 7529.0, 7530.0],       [7531.0, 7532.0, 7533.0, 7534.0, 7535.0, 7536.0],       [7537.0, 7538.0, 7539.0, 7540.0, 7541.0, 7542.0],       [7543.0, 7544.0, 7545.0, 7546.0, 7547.0, 7548.0],       [7549.0, 7550.0, 7551.0, 7552.0, 7553.0, 7554.0],       [7555.0, 7556.0, 7557.0, 7558.0, 7559.0, 7560.0]]],     [[[7561.0, 7562.0, 7563.0, 7564.0, 7565.0, 7566.0],       [7567.0, 7568.0, 7569.0, 7570.0, 7571.0, 7572.0],       [7573.0, 7574.0, 7575.0, 7576.0, 7577.0, 7578.0],       [7579.0, 7580.0, 7581.0, 7582.0, 7583.0, 7584.0],       [7585.0, 7586.0, 7587.0, 7588.0, 7589.0, 7590.0],       [7591.0, 7592.0, 7593.0, 7594.0, 7595.0, 7596.0],       [7597.0, 7598.0, 7599.0, 7600.0, 7601.0, 7602.0]],      [[7603.0, 7604.0, 7605.0, 7606.0, 7607.0, 7608.0],       [7609.0, 7610.0, 7611.0, 7612.0, 7613.0, 7614.0],       [7615.0, 7616.0, 7617.0, 7618.0, 7619.0, 7620.0],       [7621.0, 7622.0, 7623.0, 7624.0, 7625.0, 7626.0],       [7627.0, 7628.0, 7629.0, 7630.0, 7631.0, 7632.0],       [7633.0, 7634.0, 7635.0, 7636.0, 7637.0, 7638.0],       [7639.0, 7640.0, 7641.0, 7642.0, 7643.0, 7644.0]],      [[7645.0, 7646.0, 7647.0, 7648.0, 7649.0, 7650.0],       [7651.0, 7652.0, 7653.0, 7654.0, 7655.0, 7656.0],       [7657.0, 7658.0, 7659.0, 7660.0, 7661.0, 7662.0],       [7663.0, 7664.0, 7665.0, 7666.0, 7667.0, 7668.0],       [7669.0, 7670.0, 7671.0, 7672.0, 7673.0, 7674.0],       [7675.0, 7676.0, 7677.0, 7678.0, 7679.0, 7680.0],       [7681.0, 7682.0, 7683.0, 7684.0, 7685.0, 7686.0]],      [[7687.0, 7688.0, 7689.0, 7690.0, 7691.0, 7692.0],       [7693.0, 7694.0, 7695.0, 7696.0, 7697.0, 7698.0],       [7699.0, 7700.0, 7701.0, 7702.0, 7703.0, 7704.0],       [7705.0, 7706.0, 7707.0, 7708.0, 7709.0, 7710.0],       [7711.0, 7712.0, 7713.0, 7714.0, 7715.0, 7716.0],       [7717.0, 7718.0, 7719.0, 7720.0, 7721.0, 7722.0],       [7723.0, 7724.0, 7725.0, 7726.0, 7727.0, 7728.0]],      [[7729.0, 7730.0, 7731.0, 7732.0, 7733.0, 7734.0],       [7735.0, 7736.0, 7737.0, 7738.0, 7739.0, 7740.0],       [7741.0, 7742.0, 7743.0, 7744.0, 7745.0, 7746.0],       [7747.0, 7748.0, 7749.0, 7750.0, 7751.0, 7752.0],       [7753.0, 7754.0, 7755.0, 7756.0, 7757.0, 7758.0],       [7759.0, 7760.0, 7761.0, 7762.0, 7763.0, 7764.0],       [7765.0, 7766.0, 7767.0, 7768.0, 7769.0, 7770.0]],      [[7771.0, 7772.0, 7773.0, 7774.0, 7775.0, 7776.0],       [7777.0, 7778.0, 7779.0, 7780.0, 7781.0, 7782.0],       [7783.0, 7784.0, 7785.0, 7786.0, 7787.0, 7788.0],       [7789.0, 7790.0, 7791.0, 7792.0, 7793.0, 7794.0],       [7795.0, 7796.0, 7797.0, 7798.0, 7799.0, 7800.0],       [7801.0, 7802.0, 7803.0, 7804.0, 7805.0, 7806.0],       [7807.0, 7808.0, 7809.0, 7810.0, 7811.0, 7812.0]]],     [[[7813.0, 7814.0, 7815.0, 7816.0, 7817.0, 7818.0],       [7819.0, 7820.0, 7821.0, 7822.0, 7823.0, 7824.0],       [7825.0, 7826.0, 7827.0, 7828.0, 7829.0, 7830.0],       [7831.0, 7832.0, 7833.0, 7834.0, 7835.0, 7836.0],       [7837.0, 7838.0, 7839.0, 7840.0, 7841.0, 7842.0],       [7843.0, 7844.0, 7845.0, 7846.0, 7847.0, 7848.0],       [7849.0, 7850.0, 7851.0, 7852.0, 7853.0, 7854.0]],      [[7855.0, 7856.0, 7857.0, 7858.0, 7859.0, 7860.0],       [7861.0, 7862.0, 7863.0, 7864.0, 7865.0, 7866.0],       [7867.0, 7868.0, 7869.0, 7870.0, 7871.0, 7872.0],       [7873.0, 7874.0, 7875.0, 7876.0, 7877.0, 7878.0],       [7879.0, 7880.0, 7881.0, 7882.0, 7883.0, 7884.0],       [7885.0, 7886.0, 7887.0, 7888.0, 7889.0, 7890.0],       [7891.0, 7892.0, 7893.0, 7894.0, 7895.0, 7896.0]],      [[7897.0, 7898.0, 7899.0, 7900.0, 7901.0, 7902.0],       [7903.0, 7904.0, 7905.0, 7906.0, 7907.0, 7908.0],       [7909.0, 7910.0, 7911.0, 7912.0, 7913.0, 7914.0],       [7915.0, 7916.0, 7917.0, 7918.0, 7919.0, 7920.0],       [7921.0, 7922.0, 7923.0, 7924.0, 7925.0, 7926.0],       [7927.0, 7928.0, 7929.0, 7930.0, 7931.0, 7932.0],       [7933.0, 7934.0, 7935.0, 7936.0, 7937.0, 7938.0]],      [[7939.0, 7940.0, 7941.0, 7942.0, 7943.0, 7944.0],       [7945.0, 7946.0, 7947.0, 7948.0, 7949.0, 7950.0],       [7951.0, 7952.0, 7953.0, 7954.0, 7955.0, 7956.0],       [7957.0, 7958.0, 7959.0, 7960.0, 7961.0, 7962.0],       [7963.0, 7964.0, 7965.0, 7966.0, 7967.0, 7968.0],       [7969.0, 7970.0, 7971.0, 7972.0, 7973.0, 7974.0],       [7975.0, 7976.0, 7977.0, 7978.0, 7979.0, 7980.0]],      [[7981.0, 7982.0, 7983.0, 7984.0, 7985.0, 7986.0],       [7987.0, 7988.0, 7989.0, 7990.0, 7991.0, 7992.0],       [7993.0, 7994.0, 7995.0, 7996.0, 7997.0, 7998.0],       [7999.0, 8000.0, 8001.0, 8002.0, 8003.0, 8004.0],       [8005.0, 8006.0, 8007.0, 8008.0, 8009.0, 8010.0],       [8011.0, 8012.0, 8013.0, 8014.0, 8015.0, 8016.0],       [8017.0, 8018.0, 8019.0, 8020.0, 8021.0, 8022.0]],      [[8023.0, 8024.0, 8025.0, 8026.0, 8027.0, 8028.0],       [8029.0, 8030.0, 8031.0, 8032.0, 8033.0, 8034.0],       [8035.0, 8036.0, 8037.0, 8038.0, 8039.0, 8040.0],       [8041.0, 8042.0, 8043.0, 8044.0, 8045.0, 8046.0],       [8047.0, 8048.0, 8049.0, 8050.0, 8051.0, 8052.0],       [8053.0, 8054.0, 8055.0, 8056.0, 8057.0, 8058.0],       [8059.0, 8060.0, 8061.0, 8062.0, 8063.0, 8064.0]]]],    [[[[8065.0, 8066.0, 8067.0, 8068.0, 8069.0, 8070.0],       [8071.0, 8072.0, 8073.0, 8074.0, 8075.0, 8076.0],       [8077.0, 8078.0, 8079.0, 8080.0, 8081.0, 8082.0],       [8083.0, 8084.0, 8085.0, 8086.0, 8087.0, 8088.0],       [8089.0, 8090.0, 8091.0, 8092.0, 8093.0, 8094.0],       [8095.0, 8096.0, 8097.0, 8098.0, 8099.0, 8100.0],       [8101.0, 8102.0, 8103.0, 8104.0, 8105.0, 8106.0]],      [[8107.0, 8108.0, 8109.0, 8110.0, 8111.0, 8112.0],       [8113.0, 8114.0, 8115.0, 8116.0, 8117.0, 8118.0],       [8119.0, 8120.0, 8121.0, 8122.0, 8123.0, 8124.0],       [8125.0, 8126.0, 8127.0, 8128.0, 8129.0, 8130.0],       [8131.0, 8132.0, 8133.0, 8134.0, 8135.0, 8136.0],       [8137.0, 8138.0, 8139.0, 8140.0, 8141.0, 8142.0],       [8143.0, 8144.0, 8145.0, 8146.0, 8147.0, 8148.0]],      [[8149.0, 8150.0, 8151.0, 8152.0, 8153.0, 8154.0],       [8155.0, 8156.0, 8157.0, 8158.0, 8159.0, 8160.0],       [8161.0, 8162.0, 8163.0, 8164.0, 8165.0, 8166.0],       [8167.0, 8168.0, 8169.0, 8170.0, 8171.0, 8172.0],       [8173.0, 8174.0, 8175.0, 8176.0, 8177.0, 8178.0],       [8179.0, 8180.0, 8181.0, 8182.0, 8183.0, 8184.0],       [8185.0, 8186.0, 8187.0, 8188.0, 8189.0, 8190.0]],      [[8191.0, 8192.0, 8193.0, 8194.0, 8195.0, 8196.0],       [8197.0, 8198.0, 8199.0, 8200.0, 8201.0, 8202.0],       [8203.0, 8204.0, 8205.0, 8206.0, 8207.0, 8208.0],       [8209.0, 8210.0, 8211.0, 8212.0, 8213.0, 8214.0],       [8215.0, 8216.0, 8217.0, 8218.0, 8219.0, 8220.0],       [8221.0, 8222.0, 8223.0, 8224.0, 8225.0, 8226.0],       [8227.0, 8228.0, 8229.0, 8230.0, 8231.0, 8232.0]],      [[8233.0, 8234.0, 8235.0, 8236.0, 8237.0, 8238.0],       [8239.0, 8240.0, 8241.0, 8242.0, 8243.0, 8244.0],       [8245.0, 8246.0, 8247.0, 8248.0, 8249.0, 8250.0],       [8251.0, 8252.0, 8253.0, 8254.0, 8255.0, 8256.0],       [8257.0, 8258.0, 8259.0, 8260.0, 8261.0, 8262.0],       [8263.0, 8264.0, 8265.0, 8266.0, 8267.0, 8268.0],       [8269.0, 8270.0, 8271.0, 8272.0, 8273.0, 8274.0]],      [[8275.0, 8276.0, 8277.0, 8278.0, 8279.0, 8280.0],       [8281.0, 8282.0, 8283.0, 8284.0, 8285.0, 8286.0],       [8287.0, 8288.0, 8289.0, 8290.0, 8291.0, 8292.0],       [8293.0, 8294.0, 8295.0, 8296.0, 8297.0, 8298.0],       [8299.0, 8300.0, 8301.0, 8302.0, 8303.0, 8304.0],       [8305.0, 8306.0, 8307.0, 8308.0, 8309.0, 8310.0],       [8311.0, 8312.0, 8313.0, 8314.0, 8315.0, 8316.0]]],     [[[8317.0, 8318.0, 8319.0, 8320.0, 8321.0, 8322.0],       [8323.0, 8324.0, 8325.0, 8326.0, 8327.0, 8328.0],       [8329.0, 8330.0, 8331.0, 8332.0, 8333.0, 8334.0],       [8335.0, 8336.0, 8337.0, 8338.0, 8339.0, 8340.0],       [8341.0, 8342.0, 8343.0, 8344.0, 8345.0, 8346.0],       [8347.0, 8348.0, 8349.0, 8350.0, 8351.0, 8352.0],       [8353.0, 8354.0, 8355.0, 8356.0, 8357.0, 8358.0]],      [[8359.0, 8360.0, 8361.0, 8362.0, 8363.0, 8364.0],       [8365.0, 8366.0, 8367.0, 8368.0, 8369.0, 8370.0],       [8371.0, 8372.0, 8373.0, 8374.0, 8375.0, 8376.0],       [8377.0, 8378.0, 8379.0, 8380.0, 8381.0, 8382.0],       [8383.0, 8384.0, 8385.0, 8386.0, 8387.0, 8388.0],       [8389.0, 8390.0, 8391.0, 8392.0, 8393.0, 8394.0],       [8395.0, 8396.0, 8397.0, 8398.0, 8399.0, 8400.0]],      [[8401.0, 8402.0, 8403.0, 8404.0, 8405.0, 8406.0],       [8407.0, 8408.0, 8409.0, 8410.0, 8411.0, 8412.0],       [8413.0, 8414.0, 8415.0, 8416.0, 8417.0, 8418.0],       [8419.0, 8420.0, 8421.0, 8422.0, 8423.0, 8424.0],       [8425.0, 8426.0, 8427.0, 8428.0, 8429.0, 8430.0],       [8431.0, 8432.0, 8433.0, 8434.0, 8435.0, 8436.0],       [8437.0, 8438.0, 8439.0, 8440.0, 8441.0, 8442.0]],      [[8443.0, 8444.0, 8445.0, 8446.0, 8447.0, 8448.0],       [8449.0, 8450.0, 8451.0, 8452.0, 8453.0, 8454.0],       [8455.0, 8456.0, 8457.0, 8458.0, 8459.0, 8460.0],       [8461.0, 8462.0, 8463.0, 8464.0, 8465.0, 8466.0],       [8467.0, 8468.0, 8469.0, 8470.0, 8471.0, 8472.0],       [8473.0, 8474.0, 8475.0, 8476.0, 8477.0, 8478.0],       [8479.0, 8480.0, 8481.0, 8482.0, 8483.0, 8484.0]],      [[8485.0, 8486.0, 8487.0, 8488.0, 8489.0, 8490.0],       [8491.0, 8492.0, 8493.0, 8494.0, 8495.0, 8496.0],       [8497.0, 8498.0, 8499.0, 8500.0, 8501.0, 8502.0],       [8503.0, 8504.0, 8505.0, 8506.0, 8507.0, 8508.0],       [8509.0, 8510.0, 8511.0, 8512.0, 8513.0, 8514.0],       [8515.0, 8516.0, 8517.0, 8518.0, 8519.0, 8520.0],       [8521.0, 8522.0, 8523.0, 8524.0, 8525.0, 8526.0]],      [[8527.0, 8528.0, 8529.0, 8530.0, 8531.0, 8532.0],       [8533.0, 8534.0, 8535.0, 8536.0, 8537.0, 8538.0],       [8539.0, 8540.0, 8541.0, 8542.0, 8543.0, 8544.0],       [8545.0, 8546.0, 8547.0, 8548.0, 8549.0, 8550.0],       [8551.0, 8552.0, 8553.0, 8554.0, 8555.0, 8556.0],       [8557.0, 8558.0, 8559.0, 8560.0, 8561.0, 8562.0],       [8563.0, 8564.0, 8565.0, 8566.0, 8567.0, 8568.0]]],     [[[8569.0, 8570.0, 8571.0, 8572.0, 8573.0, 8574.0],       [8575.0, 8576.0, 8577.0, 8578.0, 8579.0, 8580.0],       [8581.0, 8582.0, 8583.0, 8584.0, 8585.0, 8586.0],       [8587.0, 8588.0, 8589.0, 8590.0, 8591.0, 8592.0],       [8593.0, 8594.0, 8595.0, 8596.0, 8597.0, 8598.0],       [8599.0, 8600.0, 8601.0, 8602.0, 8603.0, 8604.0],       [8605.0, 8606.0, 8607.0, 8608.0, 8609.0, 8610.0]],      [[8611.0, 8612.0, 8613.0, 8614.0, 8615.0, 8616.0],       [8617.0, 8618.0, 8619.0, 8620.0, 8621.0, 8622.0],       [8623.0, 8624.0, 8625.0, 8626.0, 8627.0, 8628.0],       [8629.0, 8630.0, 8631.0, 8632.0, 8633.0, 8634.0],       [8635.0, 8636.0, 8637.0, 8638.0, 8639.0, 8640.0],       [8641.0, 8642.0, 8643.0, 8644.0, 8645.0, 8646.0],       [8647.0, 8648.0, 8649.0, 8650.0, 8651.0, 8652.0]],      [[8653.0, 8654.0, 8655.0, 8656.0, 8657.0, 8658.0],       [8659.0, 8660.0, 8661.0, 8662.0, 8663.0, 8664.0],       [8665.0, 8666.0, 8667.0, 8668.0, 8669.0, 8670.0],       [8671.0, 8672.0, 8673.0, 8674.0, 8675.0, 8676.0],       [8677.0, 8678.0, 8679.0, 8680.0, 8681.0, 8682.0],       [8683.0, 8684.0, 8685.0, 8686.0, 8687.0, 8688.0],       [8689.0, 8690.0, 8691.0, 8692.0, 8693.0, 8694.0]],      [[8695.0, 8696.0, 8697.0, 8698.0, 8699.0, 8700.0],       [8701.0, 8702.0, 8703.0, 8704.0, 8705.0, 8706.0],       [8707.0, 8708.0, 8709.0, 8710.0, 8711.0, 8712.0],       [8713.0, 8714.0, 8715.0, 8716.0, 8717.0, 8718.0],       [8719.0, 8720.0, 8721.0, 8722.0, 8723.0, 8724.0],       [8725.0, 8726.0, 8727.0, 8728.0, 8729.0, 8730.0],       [8731.0, 8732.0, 8733.0, 8734.0, 8735.0, 8736.0]],      [[8737.0, 8738.0, 8739.0, 8740.0, 8741.0, 8742.0],       [8743.0, 8744.0, 8745.0, 8746.0, 8747.0, 8748.0],       [8749.0, 8750.0, 8751.0, 8752.0, 8753.0, 8754.0],       [8755.0, 8756.0, 8757.0, 8758.0, 8759.0, 8760.0],       [8761.0, 8762.0, 8763.0, 8764.0, 8765.0, 8766.0],       [8767.0, 8768.0, 8769.0, 8770.0, 8771.0, 8772.0],       [8773.0, 8774.0, 8775.0, 8776.0, 8777.0, 8778.0]],      [[8779.0, 8780.0, 8781.0, 8782.0, 8783.0, 8784.0],       [8785.0, 8786.0, 8787.0, 8788.0, 8789.0, 8790.0],       [8791.0, 8792.0, 8793.0, 8794.0, 8795.0, 8796.0],       [8797.0, 8798.0, 8799.0, 8800.0, 8801.0, 8802.0],       [8803.0, 8804.0, 8805.0, 8806.0, 8807.0, 8808.0],       [8809.0, 8810.0, 8811.0, 8812.0, 8813.0, 8814.0],       [8815.0, 8816.0, 8817.0, 8818.0, 8819.0, 8820.0]]],     [[[8821.0, 8822.0, 8823.0, 8824.0, 8825.0, 8826.0],       [8827.0, 8828.0, 8829.0, 8830.0, 8831.0, 8832.0],       [8833.0, 8834.0, 8835.0, 8836.0, 8837.0, 8838.0],       [8839.0, 8840.0, 8841.0, 8842.0, 8843.0, 8844.0],       [8845.0, 8846.0, 8847.0, 8848.0, 8849.0, 8850.0],       [8851.0, 8852.0, 8853.0, 8854.0, 8855.0, 8856.0],       [8857.0, 8858.0, 8859.0, 8860.0, 8861.0, 8862.0]],      [[8863.0, 8864.0, 8865.0, 8866.0, 8867.0, 8868.0],       [8869.0, 8870.0, 8871.0, 8872.0, 8873.0, 8874.0],       [8875.0, 8876.0, 8877.0, 8878.0, 8879.0, 8880.0],       [8881.0, 8882.0, 8883.0, 8884.0, 8885.0, 8886.0],       [8887.0, 8888.0, 8889.0, 8890.0, 8891.0, 8892.0],       [8893.0, 8894.0, 8895.0, 8896.0, 8897.0, 8898.0],       [8899.0, 8900.0, 8901.0, 8902.0, 8903.0, 8904.0]],      [[8905.0, 8906.0, 8907.0, 8908.0, 8909.0, 8910.0],       [8911.0, 8912.0, 8913.0, 8914.0, 8915.0, 8916.0],       [8917.0, 8918.0, 8919.0, 8920.0, 8921.0, 8922.0],       [8923.0, 8924.0, 8925.0, 8926.0, 8927.0, 8928.0],       [8929.0, 8930.0, 8931.0, 8932.0, 8933.0, 8934.0],       [8935.0, 8936.0, 8937.0, 8938.0, 8939.0, 8940.0],       [8941.0, 8942.0, 8943.0, 8944.0, 8945.0, 8946.0]],      [[8947.0, 8948.0, 8949.0, 8950.0, 8951.0, 8952.0],       [8953.0, 8954.0, 8955.0, 8956.0, 8957.0, 8958.0],       [8959.0, 8960.0, 8961.0, 8962.0, 8963.0, 8964.0],       [8965.0, 8966.0, 8967.0, 8968.0, 8969.0, 8970.0],       [8971.0, 8972.0, 8973.0, 8974.0, 8975.0, 8976.0],       [8977.0, 8978.0, 8979.0, 8980.0, 8981.0, 8982.0],       [8983.0, 8984.0, 8985.0, 8986.0, 8987.0, 8988.0]],      [[8989.0, 8990.0, 8991.0, 8992.0, 8993.0, 8994.0],       [8995.0, 8996.0, 8997.0, 8998.0, 8999.0, 9000.0],       [9001.0, 9002.0, 9003.0, 9004.0, 9005.0, 9006.0],       [9007.0, 9008.0, 9009.0, 9010.0, 9011.0, 9012.0],       [9013.0, 9014.0, 9015.0, 9016.0, 9017.0, 9018.0],       [9019.0, 9020.0, 9021.0, 9022.0, 9023.0, 9024.0],       [9025.0, 9026.0, 9027.0, 9028.0, 9029.0, 9030.0]],      [[9031.0, 9032.0, 9033.0, 9034.0, 9035.0, 9036.0],       [9037.0, 9038.0, 9039.0, 9040.0, 9041.0, 9042.0],       [9043.0, 9044.0, 9045.0, 9046.0, 9047.0, 9048.0],       [9049.0, 9050.0, 9051.0, 9052.0, 9053.0, 9054.0],       [9055.0, 9056.0, 9057.0, 9058.0, 9059.0, 9060.0],       [9061.0, 9062.0, 9063.0, 9064.0, 9065.0, 9066.0],       [9067.0, 9068.0, 9069.0, 9070.0, 9071.0, 9072.0]]]],    [[[[9073.0, 9074.0, 9075.0, 9076.0, 9077.0, 9078.0],       [9079.0, 9080.0, 9081.0, 9082.0, 9083.0, 9084.0],       [9085.0, 9086.0, 9087.0, 9088.0, 9089.0, 9090.0],       [9091.0, 9092.0, 9093.0, 9094.0, 9095.0, 9096.0],       [9097.0, 9098.0, 9099.0, 9100.0, 9101.0, 9102.0],       [9103.0, 9104.0, 9105.0, 9106.0, 9107.0, 9108.0],       [9109.0, 9110.0, 9111.0, 9112.0, 9113.0, 9114.0]],      [[9115.0, 9116.0, 9117.0, 9118.0, 9119.0, 9120.0],       [9121.0, 9122.0, 9123.0, 9124.0, 9125.0, 9126.0],       [9127.0, 9128.0, 9129.0, 9130.0, 9131.0, 9132.0],       [9133.0, 9134.0, 9135.0, 9136.0, 9137.0, 9138.0],       [9139.0, 9140.0, 9141.0, 9142.0, 9143.0, 9144.0],       [9145.0, 9146.0, 9147.0, 9148.0, 9149.0, 9150.0],       [9151.0, 9152.0, 9153.0, 9154.0, 9155.0, 9156.0]],      [[9157.0, 9158.0, 9159.0, 9160.0, 9161.0, 9162.0],       [9163.0, 9164.0, 9165.0, 9166.0, 9167.0, 9168.0],       [9169.0, 9170.0, 9171.0, 9172.0, 9173.0, 9174.0],       [9175.0, 9176.0, 9177.0, 9178.0, 9179.0, 9180.0],       [9181.0, 9182.0, 9183.0, 9184.0, 9185.0, 9186.0],       [9187.0, 9188.0, 9189.0, 9190.0, 9191.0, 9192.0],       [9193.0, 9194.0, 9195.0, 9196.0, 9197.0, 9198.0]],      [[9199.0, 9200.0, 9201.0, 9202.0, 9203.0, 9204.0],       [9205.0, 9206.0, 9207.0, 9208.0, 9209.0, 9210.0],       [9211.0, 9212.0, 9213.0, 9214.0, 9215.0, 9216.0],       [9217.0, 9218.0, 9219.0, 9220.0, 9221.0, 9222.0],       [9223.0, 9224.0, 9225.0, 9226.0, 9227.0, 9228.0],       [9229.0, 9230.0, 9231.0, 9232.0, 9233.0, 9234.0],       [9235.0, 9236.0, 9237.0, 9238.0, 9239.0, 9240.0]],      [[9241.0, 9242.0, 9243.0, 9244.0, 9245.0, 9246.0],       [9247.0, 9248.0, 9249.0, 9250.0, 9251.0, 9252.0],       [9253.0, 9254.0, 9255.0, 9256.0, 9257.0, 9258.0],       [9259.0, 9260.0, 9261.0, 9262.0, 9263.0, 9264.0],       [9265.0, 9266.0, 9267.0, 9268.0, 9269.0, 9270.0],       [9271.0, 9272.0, 9273.0, 9274.0, 9275.0, 9276.0],       [9277.0, 9278.0, 9279.0, 9280.0, 9281.0, 9282.0]],      [[9283.0, 9284.0, 9285.0, 9286.0, 9287.0, 9288.0],       [9289.0, 9290.0, 9291.0, 9292.0, 9293.0, 9294.0],       [9295.0, 9296.0, 9297.0, 9298.0, 9299.0, 9300.0],       [9301.0, 9302.0, 9303.0, 9304.0, 9305.0, 9306.0],       [9307.0, 9308.0, 9309.0, 9310.0, 9311.0, 9312.0],       [9313.0, 9314.0, 9315.0, 9316.0, 9317.0, 9318.0],       [9319.0, 9320.0, 9321.0, 9322.0, 9323.0, 9324.0]]],     [[[9325.0, 9326.0, 9327.0, 9328.0, 9329.0, 9330.0],       [9331.0, 9332.0, 9333.0, 9334.0, 9335.0, 9336.0],       [9337.0, 9338.0, 9339.0, 9340.0, 9341.0, 9342.0],       [9343.0, 9344.0, 9345.0, 9346.0, 9347.0, 9348.0],       [9349.0, 9350.0, 9351.0, 9352.0, 9353.0, 9354.0],       [9355.0, 9356.0, 9357.0, 9358.0, 9359.0, 9360.0],       [9361.0, 9362.0, 9363.0, 9364.0, 9365.0, 9366.0]],      [[9367.0, 9368.0, 9369.0, 9370.0, 9371.0, 9372.0],       [9373.0, 9374.0, 9375.0, 9376.0, 9377.0, 9378.0],       [9379.0, 9380.0, 9381.0, 9382.0, 9383.0, 9384.0],       [9385.0, 9386.0, 9387.0, 9388.0, 9389.0, 9390.0],       [9391.0, 9392.0, 9393.0, 9394.0, 9395.0, 9396.0],       [9397.0, 9398.0, 9399.0, 9400.0, 9401.0, 9402.0],       [9403.0, 9404.0, 9405.0, 9406.0, 9407.0, 9408.0]],      [[9409.0, 9410.0, 9411.0, 9412.0, 9413.0, 9414.0],       [9415.0, 9416.0, 9417.0, 9418.0, 9419.0, 9420.0],       [9421.0, 9422.0, 9423.0, 9424.0, 9425.0, 9426.0],       [9427.0, 9428.0, 9429.0, 9430.0, 9431.0, 9432.0],       [9433.0, 9434.0, 9435.0, 9436.0, 9437.0, 9438.0],       [9439.0, 9440.0, 9441.0, 9442.0, 9443.0, 9444.0],       [9445.0, 9446.0, 9447.0, 9448.0, 9449.0, 9450.0]],      [[9451.0, 9452.0, 9453.0, 9454.0, 9455.0, 9456.0],       [9457.0, 9458.0, 9459.0, 9460.0, 9461.0, 9462.0],       [9463.0, 9464.0, 9465.0, 9466.0, 9467.0, 9468.0],       [9469.0, 9470.0, 9471.0, 9472.0, 9473.0, 9474.0],       [9475.0, 9476.0, 9477.0, 9478.0, 9479.0, 9480.0],       [9481.0, 9482.0, 9483.0, 9484.0, 9485.0, 9486.0],       [9487.0, 9488.0, 9489.0, 9490.0, 9491.0, 9492.0]],      [[9493.0, 9494.0, 9495.0, 9496.0, 9497.0, 9498.0],       [9499.0, 9500.0, 9501.0, 9502.0, 9503.0, 9504.0],       [9505.0, 9506.0, 9507.0, 9508.0, 9509.0, 9510.0],       [9511.0, 9512.0, 9513.0, 9514.0, 9515.0, 9516.0],       [9517.0, 9518.0, 9519.0, 9520.0, 9521.0, 9522.0],       [9523.0, 9524.0, 9525.0, 9526.0, 9527.0, 9528.0],       [9529.0, 9530.0, 9531.0, 9532.0, 9533.0, 9534.0]],      [[9535.0, 9536.0, 9537.0, 9538.0, 9539.0, 9540.0],       [9541.0, 9542.0, 9543.0, 9544.0, 9545.0, 9546.0],       [9547.0, 9548.0, 9549.0, 9550.0, 9551.0, 9552.0],       [9553.0, 9554.0, 9555.0, 9556.0, 9557.0, 9558.0],       [9559.0, 9560.0, 9561.0, 9562.0, 9563.0, 9564.0],       [9565.0, 9566.0, 9567.0, 9568.0, 9569.0, 9570.0],       [9571.0, 9572.0, 9573.0, 9574.0, 9575.0, 9576.0]]],     [[[9577.0, 9578.0, 9579.0, 9580.0, 9581.0, 9582.0],       [9583.0, 9584.0, 9585.0, 9586.0, 9587.0, 9588.0],       [9589.0, 9590.0, 9591.0, 9592.0, 9593.0, 9594.0],       [9595.0, 9596.0, 9597.0, 9598.0, 9599.0, 9600.0],       [9601.0, 9602.0, 9603.0, 9604.0, 9605.0, 9606.0],       [9607.0, 9608.0, 9609.0, 9610.0, 9611.0, 9612.0],       [9613.0, 9614.0, 9615.0, 9616.0, 9617.0, 9618.0]],      [[9619.0, 9620.0, 9621.0, 9622.0, 9623.0, 9624.0],       [9625.0, 9626.0, 9627.0, 9628.0, 9629.0, 9630.0],       [9631.0, 9632.0, 9633.0, 9634.0, 9635.0, 9636.0],       [9637.0, 9638.0, 9639.0, 9640.0, 9641.0, 9642.0],       [9643.0, 9644.0, 9645.0, 9646.0, 9647.0, 9648.0],       [9649.0, 9650.0, 9651.0, 9652.0, 9653.0, 9654.0],       [9655.0, 9656.0, 9657.0, 9658.0, 9659.0, 9660.0]],      [[9661.0, 9662.0, 9663.0, 9664.0, 9665.0, 9666.0],       [9667.0, 9668.0, 9669.0, 9670.0, 9671.0, 9672.0],       [9673.0, 9674.0, 9675.0, 9676.0, 9677.0, 9678.0],       [9679.0, 9680.0, 9681.0, 9682.0, 9683.0, 9684.0],       [9685.0, 9686.0, 9687.0, 9688.0, 9689.0, 9690.0],       [9691.0, 9692.0, 9693.0, 9694.0, 9695.0, 9696.0],       [9697.0, 9698.0, 9699.0, 9700.0, 9701.0, 9702.0]],      [[9703.0, 9704.0, 9705.0, 9706.0, 9707.0, 9708.0],       [9709.0, 9710.0, 9711.0, 9712.0, 9713.0, 9714.0],       [9715.0, 9716.0, 9717.0, 9718.0, 9719.0, 9720.0],       [9721.0, 9722.0, 9723.0, 9724.0, 9725.0, 9726.0],       [9727.0, 9728.0, 9729.0, 9730.0, 9731.0, 9732.0],       [9733.0, 9734.0, 9735.0, 9736.0, 9737.0, 9738.0],       [9739.0, 9740.0, 9741.0, 9742.0, 9743.0, 9744.0]],      [[9745.0, 9746.0, 9747.0, 9748.0, 9749.0, 9750.0],       [9751.0, 9752.0, 9753.0, 9754.0, 9755.0, 9756.0],       [9757.0, 9758.0, 9759.0, 9760.0, 9761.0, 9762.0],       [9763.0, 9764.0, 9765.0, 9766.0, 9767.0, 9768.0],       [9769.0, 9770.0, 9771.0, 9772.0, 9773.0, 9774.0],       [9775.0, 9776.0, 9777.0, 9778.0, 9779.0, 9780.0],       [9781.0, 9782.0, 9783.0, 9784.0, 9785.0, 9786.0]],      [[9787.0, 9788.0, 9789.0, 9790.0, 9791.0, 9792.0],       [9793.0, 9794.0, 9795.0, 9796.0, 9797.0, 9798.0],       [9799.0, 9800.0, 9801.0, 9802.0, 9803.0, 9804.0],       [9805.0, 9806.0, 9807.0, 9808.0, 9809.0, 9810.0],       [9811.0, 9812.0, 9813.0, 9814.0, 9815.0, 9816.0],       [9817.0, 9818.0, 9819.0, 9820.0, 9821.0, 9822.0],       [9823.0, 9824.0, 9825.0, 9826.0, 9827.0, 9828.0]]],     [[[9829.0, 9830.0, 9831.0, 9832.0, 9833.0, 9834.0],       [9835.0, 9836.0, 9837.0, 9838.0, 9839.0, 9840.0],       [9841.0, 9842.0, 9843.0, 9844.0, 9845.0, 9846.0],       [9847.0, 9848.0, 9849.0, 9850.0, 9851.0, 9852.0],       [9853.0, 9854.0, 9855.0, 9856.0, 9857.0, 9858.0],       [9859.0, 9860.0, 9861.0, 9862.0, 9863.0, 9864.0],       [9865.0, 9866.0, 9867.0, 9868.0, 9869.0, 9870.0]],      [[9871.0, 9872.0, 9873.0, 9874.0, 9875.0, 9876.0],       [9877.0, 9878.0, 9879.0, 9880.0, 9881.0, 9882.0],       [9883.0, 9884.0, 9885.0, 9886.0, 9887.0, 9888.0],       [9889.0, 9890.0, 9891.0, 9892.0, 9893.0, 9894.0],       [9895.0, 9896.0, 9897.0, 9898.0, 9899.0, 9900.0],       [9901.0, 9902.0, 9903.0, 9904.0, 9905.0, 9906.0],       [9907.0, 9908.0, 9909.0, 9910.0, 9911.0, 9912.0]],      [[9913.0, 9914.0, 9915.0, 9916.0, 9917.0, 9918.0],       [9919.0, 9920.0, 9921.0, 9922.0, 9923.0, 9924.0],       [9925.0, 9926.0, 9927.0, 9928.0, 9929.0, 9930.0],       [9931.0, 9932.0, 9933.0, 9934.0, 9935.0, 9936.0],       [9937.0, 9938.0, 9939.0, 9940.0, 9941.0, 9942.0],       [9943.0, 9944.0, 9945.0, 9946.0, 9947.0, 9948.0],       [9949.0, 9950.0, 9951.0, 9952.0, 9953.0, 9954.0]],      [[9955.0, 9956.0, 9957.0, 9958.0, 9959.0, 9960.0],       [9961.0, 9962.0, 9963.0, 9964.0, 9965.0, 9966.0],       [9967.0, 9968.0, 9969.0, 9970.0, 9971.0, 9972.0],       [9973.0, 9974.0, 9975.0, 9976.0, 9977.0, 9978.0],       [9979.0, 9980.0, 9981.0, 9982.0, 9983.0, 9984.0],       [9985.0, 9986.0, 9987.0, 9988.0, 9989.0, 9990.0],       [9991.0, 9992.0, 9993.0, 9994.0, 9995.0, 9996.0]],      [[9997.0, 9998.0, 9999.0, 10000.0, 10001.0, 10002.0],       [10003.0, 10004.0, 10005.0, 10006.0, 10007.0, 10008.0],       [10009.0, 10010.0, 10011.0, 10012.0, 10013.0, 10014.0],       [10015.0, 10016.0, 10017.0, 10018.0, 10019.0, 10020.0],       [10021.0, 10022.0, 10023.0, 10024.0, 10025.0, 10026.0],       [10027.0, 10028.0, 10029.0, 10030.0, 10031.0, 10032.0],       [10033.0, 10034.0, 10035.0, 10036.0, 10037.0, 10038.0]],      [[10039.0, 10040.0, 10041.0, 10042.0, 10043.0, 10044.0],       [10045.0, 10046.0, 10047.0, 10048.0, 10049.0, 10050.0],       [10051.0, 10052.0, 10053.0, 10054.0, 10055.0, 10056.0],       [10057.0, 10058.0, 10059.0, 10060.0, 10061.0, 10062.0],       [10063.0, 10064.0, 10065.0, 10066.0, 10067.0, 10068.0],       [10069.0, 10070.0, 10071.0, 10072.0, 10073.0, 10074.0],       [10075.0, 10076.0, 10077.0, 10078.0, 10079.0, 10080.0]]]],    [[[[10081.0, 10082.0, 10083.0, 10084.0, 10085.0, 10086.0],       [10087.0, 10088.0, 10089.0, 10090.0, 10091.0, 10092.0],       [10093.0, 10094.0, 10095.0, 10096.0, 10097.0, 10098.0],       [10099.0, 10100.0, 10101.0, 10102.0, 10103.0, 10104.0],       [10105.0, 10106.0, 10107.0, 10108.0, 10109.0, 10110.0],       [10111.0, 10112.0, 10113.0, 10114.0, 10115.0, 10116.0],       [10117.0, 10118.0, 10119.0, 10120.0, 10121.0, 10122.0]],      [[10123.0, 10124.0, 10125.0, 10126.0, 10127.0, 10128.0],       [10129.0, 10130.0, 10131.0, 10132.0, 10133.0, 10134.0],       [10135.0, 10136.0, 10137.0, 10138.0, 10139.0, 10140.0],       [10141.0, 10142.0, 10143.0, 10144.0, 10145.0, 10146.0],       [10147.0, 10148.0, 10149.0, 10150.0, 10151.0, 10152.0],       [10153.0, 10154.0, 10155.0, 10156.0, 10157.0, 10158.0],       [10159.0, 10160.0, 10161.0, 10162.0, 10163.0, 10164.0]],      [[10165.0, 10166.0, 10167.0, 10168.0, 10169.0, 10170.0],       [10171.0, 10172.0, 10173.0, 10174.0, 10175.0, 10176.0],       [10177.0, 10178.0, 10179.0, 10180.0, 10181.0, 10182.0],       [10183.0, 10184.0, 10185.0, 10186.0, 10187.0, 10188.0],       [10189.0, 10190.0, 10191.0, 10192.0, 10193.0, 10194.0],       [10195.0, 10196.0, 10197.0, 10198.0, 10199.0, 10200.0],       [10201.0, 10202.0, 10203.0, 10204.0, 10205.0, 10206.0]],      [[10207.0, 10208.0, 10209.0, 10210.0, 10211.0, 10212.0],       [10213.0, 10214.0, 10215.0, 10216.0, 10217.0, 10218.0],       [10219.0, 10220.0, 10221.0, 10222.0, 10223.0, 10224.0],       [10225.0, 10226.0, 10227.0, 10228.0, 10229.0, 10230.0],       [10231.0, 10232.0, 10233.0, 10234.0, 10235.0, 10236.0],       [10237.0, 10238.0, 10239.0, 10240.0, 10241.0, 10242.0],       [10243.0, 10244.0, 10245.0, 10246.0, 10247.0, 10248.0]],      [[10249.0, 10250.0, 10251.0, 10252.0, 10253.0, 10254.0],       [10255.0, 10256.0, 10257.0, 10258.0, 10259.0, 10260.0],       [10261.0, 10262.0, 10263.0, 10264.0, 10265.0, 10266.0],       [10267.0, 10268.0, 10269.0, 10270.0, 10271.0, 10272.0],       [10273.0, 10274.0, 10275.0, 10276.0, 10277.0, 10278.0],       [10279.0, 10280.0, 10281.0, 10282.0, 10283.0, 10284.0],       [10285.0, 10286.0, 10287.0, 10288.0, 10289.0, 10290.0]],      [[10291.0, 10292.0, 10293.0, 10294.0, 10295.0, 10296.0],       [10297.0, 10298.0, 10299.0, 10300.0, 10301.0, 10302.0],       [10303.0, 10304.0, 10305.0, 10306.0, 10307.0, 10308.0],       [10309.0, 10310.0, 10311.0, 10312.0, 10313.0, 10314.0],       [10315.0, 10316.0, 10317.0, 10318.0, 10319.0, 10320.0],       [10321.0, 10322.0, 10323.0, 10324.0, 10325.0, 10326.0],       [10327.0, 10328.0, 10329.0, 10330.0, 10331.0, 10332.0]]],     [[[10333.0, 10334.0, 10335.0, 10336.0, 10337.0, 10338.0],       [10339.0, 10340.0, 10341.0, 10342.0, 10343.0, 10344.0],       [10345.0, 10346.0, 10347.0, 10348.0, 10349.0, 10350.0],       [10351.0, 10352.0, 10353.0, 10354.0, 10355.0, 10356.0],       [10357.0, 10358.0, 10359.0, 10360.0, 10361.0, 10362.0],       [10363.0, 10364.0, 10365.0, 10366.0, 10367.0, 10368.0],       [10369.0, 10370.0, 10371.0, 10372.0, 10373.0, 10374.0]],      [[10375.0, 10376.0, 10377.0, 10378.0, 10379.0, 10380.0],       [10381.0, 10382.0, 10383.0, 10384.0, 10385.0, 10386.0],       [10387.0, 10388.0, 10389.0, 10390.0, 10391.0, 10392.0],       [10393.0, 10394.0, 10395.0, 10396.0, 10397.0, 10398.0],       [10399.0, 10400.0, 10401.0, 10402.0, 10403.0, 10404.0],       [10405.0, 10406.0, 10407.0, 10408.0, 10409.0, 10410.0],       [10411.0, 10412.0, 10413.0, 10414.0, 10415.0, 10416.0]],      [[10417.0, 10418.0, 10419.0, 10420.0, 10421.0, 10422.0],       [10423.0, 10424.0, 10425.0, 10426.0, 10427.0, 10428.0],       [10429.0, 10430.0, 10431.0, 10432.0, 10433.0, 10434.0],       [10435.0, 10436.0, 10437.0, 10438.0, 10439.0, 10440.0],       [10441.0, 10442.0, 10443.0, 10444.0, 10445.0, 10446.0],       [10447.0, 10448.0, 10449.0, 10450.0, 10451.0, 10452.0],       [10453.0, 10454.0, 10455.0, 10456.0, 10457.0, 10458.0]],      [[10459.0, 10460.0, 10461.0, 10462.0, 10463.0, 10464.0],       [10465.0, 10466.0, 10467.0, 10468.0, 10469.0, 10470.0],       [10471.0, 10472.0, 10473.0, 10474.0, 10475.0, 10476.0],       [10477.0, 10478.0, 10479.0, 10480.0, 10481.0, 10482.0],       [10483.0, 10484.0, 10485.0, 10486.0, 10487.0, 10488.0],       [10489.0, 10490.0, 10491.0, 10492.0, 10493.0, 10494.0],       [10495.0, 10496.0, 10497.0, 10498.0, 10499.0, 10500.0]],      [[10501.0, 10502.0, 10503.0, 10504.0, 10505.0, 10506.0],       [10507.0, 10508.0, 10509.0, 10510.0, 10511.0, 10512.0],       [10513.0, 10514.0, 10515.0, 10516.0, 10517.0, 10518.0],       [10519.0, 10520.0, 10521.0, 10522.0, 10523.0, 10524.0],       [10525.0, 10526.0, 10527.0, 10528.0, 10529.0, 10530.0],       [10531.0, 10532.0, 10533.0, 10534.0, 10535.0, 10536.0],       [10537.0, 10538.0, 10539.0, 10540.0, 10541.0, 10542.0]],      [[10543.0, 10544.0, 10545.0, 10546.0, 10547.0, 10548.0],       [10549.0, 10550.0, 10551.0, 10552.0, 10553.0, 10554.0],       [10555.0, 10556.0, 10557.0, 10558.0, 10559.0, 10560.0],       [10561.0, 10562.0, 10563.0, 10564.0, 10565.0, 10566.0],       [10567.0, 10568.0, 10569.0, 10570.0, 10571.0, 10572.0],       [10573.0, 10574.0, 10575.0, 10576.0, 10577.0, 10578.0],       [10579.0, 10580.0, 10581.0, 10582.0, 10583.0, 10584.0]]],     [[[10585.0, 10586.0, 10587.0, 10588.0, 10589.0, 10590.0],       [10591.0, 10592.0, 10593.0, 10594.0, 10595.0, 10596.0],       [10597.0, 10598.0, 10599.0, 10600.0, 10601.0, 10602.0],       [10603.0, 10604.0, 10605.0, 10606.0, 10607.0, 10608.0],       [10609.0, 10610.0, 10611.0, 10612.0, 10613.0, 10614.0],       [10615.0, 10616.0, 10617.0, 10618.0, 10619.0, 10620.0],       [10621.0, 10622.0, 10623.0, 10624.0, 10625.0, 10626.0]],      [[10627.0, 10628.0, 10629.0, 10630.0, 10631.0, 10632.0],       [10633.0, 10634.0, 10635.0, 10636.0, 10637.0, 10638.0],       [10639.0, 10640.0, 10641.0, 10642.0, 10643.0, 10644.0],       [10645.0, 10646.0, 10647.0, 10648.0, 10649.0, 10650.0],       [10651.0, 10652.0, 10653.0, 10654.0, 10655.0, 10656.0],       [10657.0, 10658.0, 10659.0, 10660.0, 10661.0, 10662.0],       [10663.0, 10664.0, 10665.0, 10666.0, 10667.0, 10668.0]],      [[10669.0, 10670.0, 10671.0, 10672.0, 10673.0, 10674.0],       [10675.0, 10676.0, 10677.0, 10678.0, 10679.0, 10680.0],       [10681.0, 10682.0, 10683.0, 10684.0, 10685.0, 10686.0],       [10687.0, 10688.0, 10689.0, 10690.0, 10691.0, 10692.0],       [10693.0, 10694.0, 10695.0, 10696.0, 10697.0, 10698.0],       [10699.0, 10700.0, 10701.0, 10702.0, 10703.0, 10704.0],       [10705.0, 10706.0, 10707.0, 10708.0, 10709.0, 10710.0]],      [[10711.0, 10712.0, 10713.0, 10714.0, 10715.0, 10716.0],       [10717.0, 10718.0, 10719.0, 10720.0, 10721.0, 10722.0],       [10723.0, 10724.0, 10725.0, 10726.0, 10727.0, 10728.0],       [10729.0, 10730.0, 10731.0, 10732.0, 10733.0, 10734.0],       [10735.0, 10736.0, 10737.0, 10738.0, 10739.0, 10740.0],       [10741.0, 10742.0, 10743.0, 10744.0, 10745.0, 10746.0],       [10747.0, 10748.0, 10749.0, 10750.0, 10751.0, 10752.0]],      [[10753.0, 10754.0, 10755.0, 10756.0, 10757.0, 10758.0],       [10759.0, 10760.0, 10761.0, 10762.0, 10763.0, 10764.0],       [10765.0, 10766.0, 10767.0, 10768.0, 10769.0, 10770.0],       [10771.0, 10772.0, 10773.0, 10774.0, 10775.0, 10776.0],       [10777.0, 10778.0, 10779.0, 10780.0, 10781.0, 10782.0],       [10783.0, 10784.0, 10785.0, 10786.0, 10787.0, 10788.0],       [10789.0, 10790.0, 10791.0, 10792.0, 10793.0, 10794.0]],      [[10795.0, 10796.0, 10797.0, 10798.0, 10799.0, 10800.0],       [10801.0, 10802.0, 10803.0, 10804.0, 10805.0, 10806.0],       [10807.0, 10808.0, 10809.0, 10810.0, 10811.0, 10812.0],       [10813.0, 10814.0, 10815.0, 10816.0, 10817.0, 10818.0],       [10819.0, 10820.0, 10821.0, 10822.0, 10823.0, 10824.0],       [10825.0, 10826.0, 10827.0, 10828.0, 10829.0, 10830.0],       [10831.0, 10832.0, 10833.0, 10834.0, 10835.0, 10836.0]]],     [[[10837.0, 10838.0, 10839.0, 10840.0, 10841.0, 10842.0],       [10843.0, 10844.0, 10845.0, 10846.0, 10847.0, 10848.0],       [10849.0, 10850.0, 10851.0, 10852.0, 10853.0, 10854.0],       [10855.0, 10856.0, 10857.0, 10858.0, 10859.0, 10860.0],       [10861.0, 10862.0, 10863.0, 10864.0, 10865.0, 10866.0],       [10867.0, 10868.0, 10869.0, 10870.0, 10871.0, 10872.0],       [10873.0, 10874.0, 10875.0, 10876.0, 10877.0, 10878.0]],      [[10879.0, 10880.0, 10881.0, 10882.0, 10883.0, 10884.0],       [10885.0, 10886.0, 10887.0, 10888.0, 10889.0, 10890.0],       [10891.0, 10892.0, 10893.0, 10894.0, 10895.0, 10896.0],       [10897.0, 10898.0, 10899.0, 10900.0, 10901.0, 10902.0],       [10903.0, 10904.0, 10905.0, 10906.0, 10907.0, 10908.0],       [10909.0, 10910.0, 10911.0, 10912.0, 10913.0, 10914.0],       [10915.0, 10916.0, 10917.0, 10918.0, 10919.0, 10920.0]],      [[10921.0, 10922.0, 10923.0, 10924.0, 10925.0, 10926.0],       [10927.0, 10928.0, 10929.0, 10930.0, 10931.0, 10932.0],       [10933.0, 10934.0, 10935.0, 10936.0, 10937.0, 10938.0],       [10939.0, 10940.0, 10941.0, 10942.0, 10943.0, 10944.0],       [10945.0, 10946.0, 10947.0, 10948.0, 10949.0, 10950.0],       [10951.0, 10952.0, 10953.0, 10954.0, 10955.0, 10956.0],       [10957.0, 10958.0, 10959.0, 10960.0, 10961.0, 10962.0]],      [[10963.0, 10964.0, 10965.0, 10966.0, 10967.0, 10968.0],       [10969.0, 10970.0, 10971.0, 10972.0, 10973.0, 10974.0],       [10975.0, 10976.0, 10977.0, 10978.0, 10979.0, 10980.0],       [10981.0, 10982.0, 10983.0, 10984.0, 10985.0, 10986.0],       [10987.0, 10988.0, 10989.0, 10990.0, 10991.0, 10992.0],       [10993.0, 10994.0, 10995.0, 10996.0, 10997.0, 10998.0],       [10999.0, 11000.0, 11001.0, 11002.0, 11003.0, 11004.0]],      [[11005.0, 11006.0, 11007.0, 11008.0, 11009.0, 11010.0],       [11011.0, 11012.0, 11013.0, 11014.0, 11015.0, 11016.0],       [11017.0, 11018.0, 11019.0, 11020.0, 11021.0, 11022.0],       [11023.0, 11024.0, 11025.0, 11026.0, 11027.0, 11028.0],       [11029.0, 11030.0, 11031.0, 11032.0, 11033.0, 11034.0],       [11035.0, 11036.0, 11037.0, 11038.0, 11039.0, 11040.0],       [11041.0, 11042.0, 11043.0, 11044.0, 11045.0, 11046.0]],      [[11047.0, 11048.0, 11049.0, 11050.0, 11051.0, 11052.0],       [11053.0, 11054.0, 11055.0, 11056.0, 11057.0, 11058.0],       [11059.0, 11060.0, 11061.0, 11062.0, 11063.0, 11064.0],       [11065.0, 11066.0, 11067.0, 11068.0, 11069.0, 11070.0],       [11071.0, 11072.0, 11073.0, 11074.0, 11075.0, 11076.0],       [11077.0, 11078.0, 11079.0, 11080.0, 11081.0, 11082.0],       [11083.0, 11084.0, 11085.0, 11086.0, 11087.0, 11088.0]]]],    [[[[11089.0, 11090.0, 11091.0, 11092.0, 11093.0, 11094.0],       [11095.0, 11096.0, 11097.0, 11098.0, 11099.0, 11100.0],       [11101.0, 11102.0, 11103.0, 11104.0, 11105.0, 11106.0],       [11107.0, 11108.0, 11109.0, 11110.0, 11111.0, 11112.0],       [11113.0, 11114.0, 11115.0, 11116.0, 11117.0, 11118.0],       [11119.0, 11120.0, 11121.0, 11122.0, 11123.0, 11124.0],       [11125.0, 11126.0, 11127.0, 11128.0, 11129.0, 11130.0]],      [[11131.0, 11132.0, 11133.0, 11134.0, 11135.0, 11136.0],       [11137.0, 11138.0, 11139.0, 11140.0, 11141.0, 11142.0],       [11143.0, 11144.0, 11145.0, 11146.0, 11147.0, 11148.0],       [11149.0, 11150.0, 11151.0, 11152.0, 11153.0, 11154.0],       [11155.0, 11156.0, 11157.0, 11158.0, 11159.0, 11160.0],       [11161.0, 11162.0, 11163.0, 11164.0, 11165.0, 11166.0],       [11167.0, 11168.0, 11169.0, 11170.0, 11171.0, 11172.0]],      [[11173.0, 11174.0, 11175.0, 11176.0, 11177.0, 11178.0],       [11179.0, 11180.0, 11181.0, 11182.0, 11183.0, 11184.0],       [11185.0, 11186.0, 11187.0, 11188.0, 11189.0, 11190.0],       [11191.0, 11192.0, 11193.0, 11194.0, 11195.0, 11196.0],       [11197.0, 11198.0, 11199.0, 11200.0, 11201.0, 11202.0],       [11203.0, 11204.0, 11205.0, 11206.0, 11207.0, 11208.0],       [11209.0, 11210.0, 11211.0, 11212.0, 11213.0, 11214.0]],      [[11215.0, 11216.0, 11217.0, 11218.0, 11219.0, 11220.0],       [11221.0, 11222.0, 11223.0, 11224.0, 11225.0, 11226.0],       [11227.0, 11228.0, 11229.0, 11230.0, 11231.0, 11232.0],       [11233.0, 11234.0, 11235.0, 11236.0, 11237.0, 11238.0],       [11239.0, 11240.0, 11241.0, 11242.0, 11243.0, 11244.0],       [11245.0, 11246.0, 11247.0, 11248.0, 11249.0, 11250.0],       [11251.0, 11252.0, 11253.0, 11254.0, 11255.0, 11256.0]],      [[11257.0, 11258.0, 11259.0, 11260.0, 11261.0, 11262.0],       [11263.0, 11264.0, 11265.0, 11266.0, 11267.0, 11268.0],       [11269.0, 11270.0, 11271.0, 11272.0, 11273.0, 11274.0],       [11275.0, 11276.0, 11277.0, 11278.0, 11279.0, 11280.0],       [11281.0, 11282.0, 11283.0, 11284.0, 11285.0, 11286.0],       [11287.0, 11288.0, 11289.0, 11290.0, 11291.0, 11292.0],       [11293.0, 11294.0, 11295.0, 11296.0, 11297.0, 11298.0]],      [[11299.0, 11300.0, 11301.0, 11302.0, 11303.0, 11304.0],       [11305.0, 11306.0, 11307.0, 11308.0, 11309.0, 11310.0],       [11311.0, 11312.0, 11313.0, 11314.0, 11315.0, 11316.0],       [11317.0, 11318.0, 11319.0, 11320.0, 11321.0, 11322.0],       [11323.0, 11324.0, 11325.0, 11326.0, 11327.0, 11328.0],       [11329.0, 11330.0, 11331.0, 11332.0, 11333.0, 11334.0],       [11335.0, 11336.0, 11337.0, 11338.0, 11339.0, 11340.0]]],     [[[11341.0, 11342.0, 11343.0, 11344.0, 11345.0, 11346.0],       [11347.0, 11348.0, 11349.0, 11350.0, 11351.0, 11352.0],       [11353.0, 11354.0, 11355.0, 11356.0, 11357.0, 11358.0],       [11359.0, 11360.0, 11361.0, 11362.0, 11363.0, 11364.0],       [11365.0, 11366.0, 11367.0, 11368.0, 11369.0, 11370.0],       [11371.0, 11372.0, 11373.0, 11374.0, 11375.0, 11376.0],       [11377.0, 11378.0, 11379.0, 11380.0, 11381.0, 11382.0]],      [[11383.0, 11384.0, 11385.0, 11386.0, 11387.0, 11388.0],       [11389.0, 11390.0, 11391.0, 11392.0, 11393.0, 11394.0],       [11395.0, 11396.0, 11397.0, 11398.0, 11399.0, 11400.0],       [11401.0, 11402.0, 11403.0, 11404.0, 11405.0, 11406.0],       [11407.0, 11408.0, 11409.0, 11410.0, 11411.0, 11412.0],       [11413.0, 11414.0, 11415.0, 11416.0, 11417.0, 11418.0],       [11419.0, 11420.0, 11421.0, 11422.0, 11423.0, 11424.0]],      [[11425.0, 11426.0, 11427.0, 11428.0, 11429.0, 11430.0],       [11431.0, 11432.0, 11433.0, 11434.0, 11435.0, 11436.0],       [11437.0, 11438.0, 11439.0, 11440.0, 11441.0, 11442.0],       [11443.0, 11444.0, 11445.0, 11446.0, 11447.0, 11448.0],       [11449.0, 11450.0, 11451.0, 11452.0, 11453.0, 11454.0],       [11455.0, 11456.0, 11457.0, 11458.0, 11459.0, 11460.0],       [11461.0, 11462.0, 11463.0, 11464.0, 11465.0, 11466.0]],      [[11467.0, 11468.0, 11469.0, 11470.0, 11471.0, 11472.0],       [11473.0, 11474.0, 11475.0, 11476.0, 11477.0, 11478.0],       [11479.0, 11480.0, 11481.0, 11482.0, 11483.0, 11484.0],       [11485.0, 11486.0, 11487.0, 11488.0, 11489.0, 11490.0],       [11491.0, 11492.0, 11493.0, 11494.0, 11495.0, 11496.0],       [11497.0, 11498.0, 11499.0, 11500.0, 11501.0, 11502.0],       [11503.0, 11504.0, 11505.0, 11506.0, 11507.0, 11508.0]],      [[11509.0, 11510.0, 11511.0, 11512.0, 11513.0, 11514.0],       [11515.0, 11516.0, 11517.0, 11518.0, 11519.0, 11520.0],       [11521.0, 11522.0, 11523.0, 11524.0, 11525.0, 11526.0],       [11527.0, 11528.0, 11529.0, 11530.0, 11531.0, 11532.0],       [11533.0, 11534.0, 11535.0, 11536.0, 11537.0, 11538.0],       [11539.0, 11540.0, 11541.0, 11542.0, 11543.0, 11544.0],       [11545.0, 11546.0, 11547.0, 11548.0, 11549.0, 11550.0]],      [[11551.0, 11552.0, 11553.0, 11554.0, 11555.0, 11556.0],       [11557.0, 11558.0, 11559.0, 11560.0, 11561.0, 11562.0],       [11563.0, 11564.0, 11565.0, 11566.0, 11567.0, 11568.0],       [11569.0, 11570.0, 11571.0, 11572.0, 11573.0, 11574.0],       [11575.0, 11576.0, 11577.0, 11578.0, 11579.0, 11580.0],       [11581.0, 11582.0, 11583.0, 11584.0, 11585.0, 11586.0],       [11587.0, 11588.0, 11589.0, 11590.0, 11591.0, 11592.0]]],     [[[11593.0, 11594.0, 11595.0, 11596.0, 11597.0, 11598.0],       [11599.0, 11600.0, 11601.0, 11602.0, 11603.0, 11604.0],       [11605.0, 11606.0, 11607.0, 11608.0, 11609.0, 11610.0],       [11611.0, 11612.0, 11613.0, 11614.0, 11615.0, 11616.0],       [11617.0, 11618.0, 11619.0, 11620.0, 11621.0, 11622.0],       [11623.0, 11624.0, 11625.0, 11626.0, 11627.0, 11628.0],       [11629.0, 11630.0, 11631.0, 11632.0, 11633.0, 11634.0]],      [[11635.0, 11636.0, 11637.0, 11638.0, 11639.0, 11640.0],       [11641.0, 11642.0, 11643.0, 11644.0, 11645.0, 11646.0],       [11647.0, 11648.0, 11649.0, 11650.0, 11651.0, 11652.0],       [11653.0, 11654.0, 11655.0, 11656.0, 11657.0, 11658.0],       [11659.0, 11660.0, 11661.0, 11662.0, 11663.0, 11664.0],       [11665.0, 11666.0, 11667.0, 11668.0, 11669.0, 11670.0],       [11671.0, 11672.0, 11673.0, 11674.0, 11675.0, 11676.0]],      [[11677.0, 11678.0, 11679.0, 11680.0, 11681.0, 11682.0],       [11683.0, 11684.0, 11685.0, 11686.0, 11687.0, 11688.0],       [11689.0, 11690.0, 11691.0, 11692.0, 11693.0, 11694.0],       [11695.0, 11696.0, 11697.0, 11698.0, 11699.0, 11700.0],       [11701.0, 11702.0, 11703.0, 11704.0, 11705.0, 11706.0],       [11707.0, 11708.0, 11709.0, 11710.0, 11711.0, 11712.0],       [11713.0, 11714.0, 11715.0, 11716.0, 11717.0, 11718.0]],      [[11719.0, 11720.0, 11721.0, 11722.0, 11723.0, 11724.0],       [11725.0, 11726.0, 11727.0, 11728.0, 11729.0, 11730.0],       [11731.0, 11732.0, 11733.0, 11734.0, 11735.0, 11736.0],       [11737.0, 11738.0, 11739.0, 11740.0, 11741.0, 11742.0],       [11743.0, 11744.0, 11745.0, 11746.0, 11747.0, 11748.0],       [11749.0, 11750.0, 11751.0, 11752.0, 11753.0, 11754.0],       [11755.0, 11756.0, 11757.0, 11758.0, 11759.0, 11760.0]],      [[11761.0, 11762.0, 11763.0, 11764.0, 11765.0, 11766.0],       [11767.0, 11768.0, 11769.0, 11770.0, 11771.0, 11772.0],       [11773.0, 11774.0, 11775.0, 11776.0, 11777.0, 11778.0],       [11779.0, 11780.0, 11781.0, 11782.0, 11783.0, 11784.0],       [11785.0, 11786.0, 11787.0, 11788.0, 11789.0, 11790.0],       [11791.0, 11792.0, 11793.0, 11794.0, 11795.0, 11796.0],       [11797.0, 11798.0, 11799.0, 11800.0, 11801.0, 11802.0]],      [[11803.0, 11804.0, 11805.0, 11806.0, 11807.0, 11808.0],       [11809.0, 11810.0, 11811.0, 11812.0, 11813.0, 11814.0],       [11815.0, 11816.0, 11817.0, 11818.0, 11819.0, 11820.0],       [11821.0, 11822.0, 11823.0, 11824.0, 11825.0, 11826.0],       [11827.0, 11828.0, 11829.0, 11830.0, 11831.0, 11832.0],       [11833.0, 11834.0, 11835.0, 11836.0, 11837.0, 11838.0],       [11839.0, 11840.0, 11841.0, 11842.0, 11843.0, 11844.0]]],     [[[11845.0, 11846.0, 11847.0, 11848.0, 11849.0, 11850.0],       [11851.0, 11852.0, 11853.0, 11854.0, 11855.0, 11856.0],       [11857.0, 11858.0, 11859.0, 11860.0, 11861.0, 11862.0],       [11863.0, 11864.0, 11865.0, 11866.0, 11867.0, 11868.0],       [11869.0, 11870.0, 11871.0, 11872.0, 11873.0, 11874.0],       [11875.0, 11876.0, 11877.0, 11878.0, 11879.0, 11880.0],       [11881.0, 11882.0, 11883.0, 11884.0, 11885.0, 11886.0]],      [[11887.0, 11888.0, 11889.0, 11890.0, 11891.0, 11892.0],       [11893.0, 11894.0, 11895.0, 11896.0, 11897.0, 11898.0],       [11899.0, 11900.0, 11901.0, 11902.0, 11903.0, 11904.0],       [11905.0, 11906.0, 11907.0, 11908.0, 11909.0, 11910.0],       [11911.0, 11912.0, 11913.0, 11914.0, 11915.0, 11916.0],       [11917.0, 11918.0, 11919.0, 11920.0, 11921.0, 11922.0],       [11923.0, 11924.0, 11925.0, 11926.0, 11927.0, 11928.0]],      [[11929.0, 11930.0, 11931.0, 11932.0, 11933.0, 11934.0],       [11935.0, 11936.0, 11937.0, 11938.0, 11939.0, 11940.0],       [11941.0, 11942.0, 11943.0, 11944.0, 11945.0, 11946.0],       [11947.0, 11948.0, 11949.0, 11950.0, 11951.0, 11952.0],       [11953.0, 11954.0, 11955.0, 11956.0, 11957.0, 11958.0],       [11959.0, 11960.0, 11961.0, 11962.0, 11963.0, 11964.0],       [11965.0, 11966.0, 11967.0, 11968.0, 11969.0, 11970.0]],      [[11971.0, 11972.0, 11973.0, 11974.0, 11975.0, 11976.0],       [11977.0, 11978.0, 11979.0, 11980.0, 11981.0, 11982.0],       [11983.0, 11984.0, 11985.0, 11986.0, 11987.0, 11988.0],       [11989.0, 11990.0, 11991.0, 11992.0, 11993.0, 11994.0],       [11995.0, 11996.0, 11997.0, 11998.0, 11999.0, 12000.0],       [12001.0, 12002.0, 12003.0, 12004.0, 12005.0, 12006.0],       [12007.0, 12008.0, 12009.0, 12010.0, 12011.0, 12012.0]],      [[12013.0, 12014.0, 12015.0, 12016.0, 12017.0, 12018.0],       [12019.0, 12020.0, 12021.0, 12022.0, 12023.0, 12024.0],       [12025.0, 12026.0, 12027.0, 12028.0, 12029.0, 12030.0],       [12031.0, 12032.0, 12033.0, 12034.0, 12035.0, 12036.0],       [12037.0, 12038.0, 12039.0, 12040.0, 12041.0, 12042.0],       [12043.0, 12044.0, 12045.0, 12046.0, 12047.0, 12048.0],       [12049.0, 12050.0, 12051.0, 12052.0, 12053.0, 12054.0]],      [[12055.0, 12056.0, 12057.0, 12058.0, 12059.0, 12060.0],       [12061.0, 12062.0, 12063.0, 12064.0, 12065.0, 12066.0],       [12067.0, 12068.0, 12069.0, 12070.0, 12071.0, 12072.0],       [12073.0, 12074.0, 12075.0, 12076.0, 12077.0, 12078.0],       [12079.0, 12080.0, 12081.0, 12082.0, 12083.0, 12084.0],       [12085.0, 12086.0, 12087.0, 12088.0, 12089.0, 12090.0],       [12091.0, 12092.0, 12093.0, 12094.0, 12095.0, 12096.0]]]]],   [[[[[12097.0, 12098.0, 12099.0, 12100.0, 12101.0, 12102.0],       [12103.0, 12104.0, 12105.0, 12106.0, 12107.0, 12108.0],       [12109.0, 12110.0, 12111.0, 12112.0, 12113.0, 12114.0],       [12115.0, 12116.0, 12117.0, 12118.0, 12119.0, 12120.0],       [12121.0, 12122.0, 12123.0, 12124.0, 12125.0, 12126.0],       [12127.0, 12128.0, 12129.0, 12130.0, 12131.0, 12132.0],       [12133.0, 12134.0, 12135.0, 12136.0, 12137.0, 12138.0]],      [[12139.0, 12140.0, 12141.0, 12142.0, 12143.0, 12144.0],       [12145.0, 12146.0, 12147.0, 12148.0, 12149.0, 12150.0],       [12151.0, 12152.0, 12153.0, 12154.0, 12155.0, 12156.0],       [12157.0, 12158.0, 12159.0, 12160.0, 12161.0, 12162.0],       [12163.0, 12164.0, 12165.0, 12166.0, 12167.0, 12168.0],       [12169.0, 12170.0, 12171.0, 12172.0, 12173.0, 12174.0],       [12175.0, 12176.0, 12177.0, 12178.0, 12179.0, 12180.0]],      [[12181.0, 12182.0, 12183.0, 12184.0, 12185.0, 12186.0],       [12187.0, 12188.0, 12189.0, 12190.0, 12191.0, 12192.0],       [12193.0, 12194.0, 12195.0, 12196.0, 12197.0, 12198.0],       [12199.0, 12200.0, 12201.0, 12202.0, 12203.0, 12204.0],       [12205.0, 12206.0, 12207.0, 12208.0, 12209.0, 12210.0],       [12211.0, 12212.0, 12213.0, 12214.0, 12215.0, 12216.0],       [12217.0, 12218.0, 12219.0, 12220.0, 12221.0, 12222.0]],      [[12223.0, 12224.0, 12225.0, 12226.0, 12227.0, 12228.0],       [12229.0, 12230.0, 12231.0, 12232.0, 12233.0, 12234.0],       [12235.0, 12236.0, 12237.0, 12238.0, 12239.0, 12240.0],       [12241.0, 12242.0, 12243.0, 12244.0, 12245.0, 12246.0],       [12247.0, 12248.0, 12249.0, 12250.0, 12251.0, 12252.0],       [12253.0, 12254.0, 12255.0, 12256.0, 12257.0, 12258.0],       [12259.0, 12260.0, 12261.0, 12262.0, 12263.0, 12264.0]],      [[12265.0, 12266.0, 12267.0, 12268.0, 12269.0, 12270.0],       [12271.0, 12272.0, 12273.0, 12274.0, 12275.0, 12276.0],       [12277.0, 12278.0, 12279.0, 12280.0, 12281.0, 12282.0],       [12283.0, 12284.0, 12285.0, 12286.0, 12287.0, 12288.0],       [12289.0, 12290.0, 12291.0, 12292.0, 12293.0, 12294.0],       [12295.0, 12296.0, 12297.0, 12298.0, 12299.0, 12300.0],       [12301.0, 12302.0, 12303.0, 12304.0, 12305.0, 12306.0]],      [[12307.0, 12308.0, 12309.0, 12310.0, 12311.0, 12312.0],       [12313.0, 12314.0, 12315.0, 12316.0, 12317.0, 12318.0],       [12319.0, 12320.0, 12321.0, 12322.0, 12323.0, 12324.0],       [12325.0, 12326.0, 12327.0, 12328.0, 12329.0, 12330.0],       [12331.0, 12332.0, 12333.0, 12334.0, 12335.0, 12336.0],       [12337.0, 12338.0, 12339.0, 12340.0, 12341.0, 12342.0],       [12343.0, 12344.0, 12345.0, 12346.0, 12347.0, 12348.0]]],     [[[12349.0, 12350.0, 12351.0, 12352.0, 12353.0, 12354.0],       [12355.0, 12356.0, 12357.0, 12358.0, 12359.0, 12360.0],       [12361.0, 12362.0, 12363.0, 12364.0, 12365.0, 12366.0],       [12367.0, 12368.0, 12369.0, 12370.0, 12371.0, 12372.0],       [12373.0, 12374.0, 12375.0, 12376.0, 12377.0, 12378.0],       [12379.0, 12380.0, 12381.0, 12382.0, 12383.0, 12384.0],       [12385.0, 12386.0, 12387.0, 12388.0, 12389.0, 12390.0]],      [[12391.0, 12392.0, 12393.0, 12394.0, 12395.0, 12396.0],       [12397.0, 12398.0, 12399.0, 12400.0, 12401.0, 12402.0],       [12403.0, 12404.0, 12405.0, 12406.0, 12407.0, 12408.0],       [12409.0, 12410.0, 12411.0, 12412.0, 12413.0, 12414.0],       [12415.0, 12416.0, 12417.0, 12418.0, 12419.0, 12420.0],       [12421.0, 12422.0, 12423.0, 12424.0, 12425.0, 12426.0],       [12427.0, 12428.0, 12429.0, 12430.0, 12431.0, 12432.0]],      [[12433.0, 12434.0, 12435.0, 12436.0, 12437.0, 12438.0],       [12439.0, 12440.0, 12441.0, 12442.0, 12443.0, 12444.0],       [12445.0, 12446.0, 12447.0, 12448.0, 12449.0, 12450.0],       [12451.0, 12452.0, 12453.0, 12454.0, 12455.0, 12456.0],       [12457.0, 12458.0, 12459.0, 12460.0, 12461.0, 12462.0],       [12463.0, 12464.0, 12465.0, 12466.0, 12467.0, 12468.0],       [12469.0, 12470.0, 12471.0, 12472.0, 12473.0, 12474.0]],      [[12475.0, 12476.0, 12477.0, 12478.0, 12479.0, 12480.0],       [12481.0, 12482.0, 12483.0, 12484.0, 12485.0, 12486.0],       [12487.0, 12488.0, 12489.0, 12490.0, 12491.0, 12492.0],       [12493.0, 12494.0, 12495.0, 12496.0, 12497.0, 12498.0],       [12499.0, 12500.0, 12501.0, 12502.0, 12503.0, 12504.0],       [12505.0, 12506.0, 12507.0, 12508.0, 12509.0, 12510.0],       [12511.0, 12512.0, 12513.0, 12514.0, 12515.0, 12516.0]],      [[12517.0, 12518.0, 12519.0, 12520.0, 12521.0, 12522.0],       [12523.0, 12524.0, 12525.0, 12526.0, 12527.0, 12528.0],       [12529.0, 12530.0, 12531.0, 12532.0, 12533.0, 12534.0],       [12535.0, 12536.0, 12537.0, 12538.0, 12539.0, 12540.0],       [12541.0, 12542.0, 12543.0, 12544.0, 12545.0, 12546.0],       [12547.0, 12548.0, 12549.0, 12550.0, 12551.0, 12552.0],       [12553.0, 12554.0, 12555.0, 12556.0, 12557.0, 12558.0]],      [[12559.0, 12560.0, 12561.0, 12562.0, 12563.0, 12564.0],       [12565.0, 12566.0, 12567.0, 12568.0, 12569.0, 12570.0],       [12571.0, 12572.0, 12573.0, 12574.0, 12575.0, 12576.0],       [12577.0, 12578.0, 12579.0, 12580.0, 12581.0, 12582.0],       [12583.0, 12584.0, 12585.0, 12586.0, 12587.0, 12588.0],       [12589.0, 12590.0, 12591.0, 12592.0, 12593.0, 12594.0],       [12595.0, 12596.0, 12597.0, 12598.0, 12599.0, 12600.0]]],     [[[12601.0, 12602.0, 12603.0, 12604.0, 12605.0, 12606.0],       [12607.0, 12608.0, 12609.0, 12610.0, 12611.0, 12612.0],       [12613.0, 12614.0, 12615.0, 12616.0, 12617.0, 12618.0],       [12619.0, 12620.0, 12621.0, 12622.0, 12623.0, 12624.0],       [12625.0, 12626.0, 12627.0, 12628.0, 12629.0, 12630.0],       [12631.0, 12632.0, 12633.0, 12634.0, 12635.0, 12636.0],       [12637.0, 12638.0, 12639.0, 12640.0, 12641.0, 12642.0]],      [[12643.0, 12644.0, 12645.0, 12646.0, 12647.0, 12648.0],       [12649.0, 12650.0, 12651.0, 12652.0, 12653.0, 12654.0],       [12655.0, 12656.0, 12657.0, 12658.0, 12659.0, 12660.0],       [12661.0, 12662.0, 12663.0, 12664.0, 12665.0, 12666.0],       [12667.0, 12668.0, 12669.0, 12670.0, 12671.0, 12672.0],       [12673.0, 12674.0, 12675.0, 12676.0, 12677.0, 12678.0],       [12679.0, 12680.0, 12681.0, 12682.0, 12683.0, 12684.0]],      [[12685.0, 12686.0, 12687.0, 12688.0, 12689.0, 12690.0],       [12691.0, 12692.0, 12693.0, 12694.0, 12695.0, 12696.0],       [12697.0, 12698.0, 12699.0, 12700.0, 12701.0, 12702.0],       [12703.0, 12704.0, 12705.0, 12706.0, 12707.0, 12708.0],       [12709.0, 12710.0, 12711.0, 12712.0, 12713.0, 12714.0],       [12715.0, 12716.0, 12717.0, 12718.0, 12719.0, 12720.0],       [12721.0, 12722.0, 12723.0, 12724.0, 12725.0, 12726.0]],      [[12727.0, 12728.0, 12729.0, 12730.0, 12731.0, 12732.0],       [12733.0, 12734.0, 12735.0, 12736.0, 12737.0, 12738.0],       [12739.0, 12740.0, 12741.0, 12742.0, 12743.0, 12744.0],       [12745.0, 12746.0, 12747.0, 12748.0, 12749.0, 12750.0],       [12751.0, 12752.0, 12753.0, 12754.0, 12755.0, 12756.0],       [12757.0, 12758.0, 12759.0, 12760.0, 12761.0, 12762.0],       [12763.0, 12764.0, 12765.0, 12766.0, 12767.0, 12768.0]],      [[12769.0, 12770.0, 12771.0, 12772.0, 12773.0, 12774.0],       [12775.0, 12776.0, 12777.0, 12778.0, 12779.0, 12780.0],       [12781.0, 12782.0, 12783.0, 12784.0, 12785.0, 12786.0],       [12787.0, 12788.0, 12789.0, 12790.0, 12791.0, 12792.0],       [12793.0, 12794.0, 12795.0, 12796.0, 12797.0, 12798.0],       [12799.0, 12800.0, 12801.0, 12802.0, 12803.0, 12804.0],       [12805.0, 12806.0, 12807.0, 12808.0, 12809.0, 12810.0]],      [[12811.0, 12812.0, 12813.0, 12814.0, 12815.0, 12816.0],       [12817.0, 12818.0, 12819.0, 12820.0, 12821.0, 12822.0],       [12823.0, 12824.0, 12825.0, 12826.0, 12827.0, 12828.0],       [12829.0, 12830.0, 12831.0, 12832.0, 12833.0, 12834.0],       [12835.0, 12836.0, 12837.0, 12838.0, 12839.0, 12840.0],       [12841.0, 12842.0, 12843.0, 12844.0, 12845.0, 12846.0],       [12847.0, 12848.0, 12849.0, 12850.0, 12851.0, 12852.0]]],     [[[12853.0, 12854.0, 12855.0, 12856.0, 12857.0, 12858.0],       [12859.0, 12860.0, 12861.0, 12862.0, 12863.0, 12864.0],       [12865.0, 12866.0, 12867.0, 12868.0, 12869.0, 12870.0],       [12871.0, 12872.0, 12873.0, 12874.0, 12875.0, 12876.0],       [12877.0, 12878.0, 12879.0, 12880.0, 12881.0, 12882.0],       [12883.0, 12884.0, 12885.0, 12886.0, 12887.0, 12888.0],       [12889.0, 12890.0, 12891.0, 12892.0, 12893.0, 12894.0]],      [[12895.0, 12896.0, 12897.0, 12898.0, 12899.0, 12900.0],       [12901.0, 12902.0, 12903.0, 12904.0, 12905.0, 12906.0],       [12907.0, 12908.0, 12909.0, 12910.0, 12911.0, 12912.0],       [12913.0, 12914.0, 12915.0, 12916.0, 12917.0, 12918.0],       [12919.0, 12920.0, 12921.0, 12922.0, 12923.0, 12924.0],       [12925.0, 12926.0, 12927.0, 12928.0, 12929.0, 12930.0],       [12931.0, 12932.0, 12933.0, 12934.0, 12935.0, 12936.0]],      [[12937.0, 12938.0, 12939.0, 12940.0, 12941.0, 12942.0],       [12943.0, 12944.0, 12945.0, 12946.0, 12947.0, 12948.0],       [12949.0, 12950.0, 12951.0, 12952.0, 12953.0, 12954.0],       [12955.0, 12956.0, 12957.0, 12958.0, 12959.0, 12960.0],       [12961.0, 12962.0, 12963.0, 12964.0, 12965.0, 12966.0],       [12967.0, 12968.0, 12969.0, 12970.0, 12971.0, 12972.0],       [12973.0, 12974.0, 12975.0, 12976.0, 12977.0, 12978.0]],      [[12979.0, 12980.0, 12981.0, 12982.0, 12983.0, 12984.0],       [12985.0, 12986.0, 12987.0, 12988.0, 12989.0, 12990.0],       [12991.0, 12992.0, 12993.0, 12994.0, 12995.0, 12996.0],       [12997.0, 12998.0, 12999.0, 13000.0, 13001.0, 13002.0],       [13003.0, 13004.0, 13005.0, 13006.0, 13007.0, 13008.0],       [13009.0, 13010.0, 13011.0, 13012.0, 13013.0, 13014.0],       [13015.0, 13016.0, 13017.0, 13018.0, 13019.0, 13020.0]],      [[13021.0, 13022.0, 13023.0, 13024.0, 13025.0, 13026.0],       [13027.0, 13028.0, 13029.0, 13030.0, 13031.0, 13032.0],       [13033.0, 13034.0, 13035.0, 13036.0, 13037.0, 13038.0],       [13039.0, 13040.0, 13041.0, 13042.0, 13043.0, 13044.0],       [13045.0, 13046.0, 13047.0, 13048.0, 13049.0, 13050.0],       [13051.0, 13052.0, 13053.0, 13054.0, 13055.0, 13056.0],       [13057.0, 13058.0, 13059.0, 13060.0, 13061.0, 13062.0]],      [[13063.0, 13064.0, 13065.0, 13066.0, 13067.0, 13068.0],       [13069.0, 13070.0, 13071.0, 13072.0, 13073.0, 13074.0],       [13075.0, 13076.0, 13077.0, 13078.0, 13079.0, 13080.0],       [13081.0, 13082.0, 13083.0, 13084.0, 13085.0, 13086.0],       [13087.0, 13088.0, 13089.0, 13090.0, 13091.0, 13092.0],       [13093.0, 13094.0, 13095.0, 13096.0, 13097.0, 13098.0],       [13099.0, 13100.0, 13101.0, 13102.0, 13103.0, 13104.0]]]],    [[[[13105.0, 13106.0, 13107.0, 13108.0, 13109.0, 13110.0],       [13111.0, 13112.0, 13113.0, 13114.0, 13115.0, 13116.0],       [13117.0, 13118.0, 13119.0, 13120.0, 13121.0, 13122.0],       [13123.0, 13124.0, 13125.0, 13126.0, 13127.0, 13128.0],       [13129.0, 13130.0, 13131.0, 13132.0, 13133.0, 13134.0],       [13135.0, 13136.0, 13137.0, 13138.0, 13139.0, 13140.0],       [13141.0, 13142.0, 13143.0, 13144.0, 13145.0, 13146.0]],      [[13147.0, 13148.0, 13149.0, 13150.0, 13151.0, 13152.0],       [13153.0, 13154.0, 13155.0, 13156.0, 13157.0, 13158.0],       [13159.0, 13160.0, 13161.0, 13162.0, 13163.0, 13164.0],       [13165.0, 13166.0, 13167.0, 13168.0, 13169.0, 13170.0],       [13171.0, 13172.0, 13173.0, 13174.0, 13175.0, 13176.0],       [13177.0, 13178.0, 13179.0, 13180.0, 13181.0, 13182.0],       [13183.0, 13184.0, 13185.0, 13186.0, 13187.0, 13188.0]],      [[13189.0, 13190.0, 13191.0, 13192.0, 13193.0, 13194.0],       [13195.0, 13196.0, 13197.0, 13198.0, 13199.0, 13200.0],       [13201.0, 13202.0, 13203.0, 13204.0, 13205.0, 13206.0],       [13207.0, 13208.0, 13209.0, 13210.0, 13211.0, 13212.0],       [13213.0, 13214.0, 13215.0, 13216.0, 13217.0, 13218.0],       [13219.0, 13220.0, 13221.0, 13222.0, 13223.0, 13224.0],       [13225.0, 13226.0, 13227.0, 13228.0, 13229.0, 13230.0]],      [[13231.0, 13232.0, 13233.0, 13234.0, 13235.0, 13236.0],       [13237.0, 13238.0, 13239.0, 13240.0, 13241.0, 13242.0],       [13243.0, 13244.0, 13245.0, 13246.0, 13247.0, 13248.0],       [13249.0, 13250.0, 13251.0, 13252.0, 13253.0, 13254.0],       [13255.0, 13256.0, 13257.0, 13258.0, 13259.0, 13260.0],       [13261.0, 13262.0, 13263.0, 13264.0, 13265.0, 13266.0],       [13267.0, 13268.0, 13269.0, 13270.0, 13271.0, 13272.0]],      [[13273.0, 13274.0, 13275.0, 13276.0, 13277.0, 13278.0],       [13279.0, 13280.0, 13281.0, 13282.0, 13283.0, 13284.0],       [13285.0, 13286.0, 13287.0, 13288.0, 13289.0, 13290.0],       [13291.0, 13292.0, 13293.0, 13294.0, 13295.0, 13296.0],       [13297.0, 13298.0, 13299.0, 13300.0, 13301.0, 13302.0],       [13303.0, 13304.0, 13305.0, 13306.0, 13307.0, 13308.0],       [13309.0, 13310.0, 13311.0, 13312.0, 13313.0, 13314.0]],      [[13315.0, 13316.0, 13317.0, 13318.0, 13319.0, 13320.0],       [13321.0, 13322.0, 13323.0, 13324.0, 13325.0, 13326.0],       [13327.0, 13328.0, 13329.0, 13330.0, 13331.0, 13332.0],       [13333.0, 13334.0, 13335.0, 13336.0, 13337.0, 13338.0],       [13339.0, 13340.0, 13341.0, 13342.0, 13343.0, 13344.0],       [13345.0, 13346.0, 13347.0, 13348.0, 13349.0, 13350.0],       [13351.0, 13352.0, 13353.0, 13354.0, 13355.0, 13356.0]]],     [[[13357.0, 13358.0, 13359.0, 13360.0, 13361.0, 13362.0],       [13363.0, 13364.0, 13365.0, 13366.0, 13367.0, 13368.0],       [13369.0, 13370.0, 13371.0, 13372.0, 13373.0, 13374.0],       [13375.0, 13376.0, 13377.0, 13378.0, 13379.0, 13380.0],       [13381.0, 13382.0, 13383.0, 13384.0, 13385.0, 13386.0],       [13387.0, 13388.0, 13389.0, 13390.0, 13391.0, 13392.0],       [13393.0, 13394.0, 13395.0, 13396.0, 13397.0, 13398.0]],      [[13399.0, 13400.0, 13401.0, 13402.0, 13403.0, 13404.0],       [13405.0, 13406.0, 13407.0, 13408.0, 13409.0, 13410.0],       [13411.0, 13412.0, 13413.0, 13414.0, 13415.0, 13416.0],       [13417.0, 13418.0, 13419.0, 13420.0, 13421.0, 13422.0],       [13423.0, 13424.0, 13425.0, 13426.0, 13427.0, 13428.0],       [13429.0, 13430.0, 13431.0, 13432.0, 13433.0, 13434.0],       [13435.0, 13436.0, 13437.0, 13438.0, 13439.0, 13440.0]],      [[13441.0, 13442.0, 13443.0, 13444.0, 13445.0, 13446.0],       [13447.0, 13448.0, 13449.0, 13450.0, 13451.0, 13452.0],       [13453.0, 13454.0, 13455.0, 13456.0, 13457.0, 13458.0],       [13459.0, 13460.0, 13461.0, 13462.0, 13463.0, 13464.0],       [13465.0, 13466.0, 13467.0, 13468.0, 13469.0, 13470.0],       [13471.0, 13472.0, 13473.0, 13474.0, 13475.0, 13476.0],       [13477.0, 13478.0, 13479.0, 13480.0, 13481.0, 13482.0]],      [[13483.0, 13484.0, 13485.0, 13486.0, 13487.0, 13488.0],       [13489.0, 13490.0, 13491.0, 13492.0, 13493.0, 13494.0],       [13495.0, 13496.0, 13497.0, 13498.0, 13499.0, 13500.0],       [13501.0, 13502.0, 13503.0, 13504.0, 13505.0, 13506.0],       [13507.0, 13508.0, 13509.0, 13510.0, 13511.0, 13512.0],       [13513.0, 13514.0, 13515.0, 13516.0, 13517.0, 13518.0],       [13519.0, 13520.0, 13521.0, 13522.0, 13523.0, 13524.0]],      [[13525.0, 13526.0, 13527.0, 13528.0, 13529.0, 13530.0],       [13531.0, 13532.0, 13533.0, 13534.0, 13535.0, 13536.0],       [13537.0, 13538.0, 13539.0, 13540.0, 13541.0, 13542.0],       [13543.0, 13544.0, 13545.0, 13546.0, 13547.0, 13548.0],       [13549.0, 13550.0, 13551.0, 13552.0, 13553.0, 13554.0],       [13555.0, 13556.0, 13557.0, 13558.0, 13559.0, 13560.0],       [13561.0, 13562.0, 13563.0, 13564.0, 13565.0, 13566.0]],      [[13567.0, 13568.0, 13569.0, 13570.0, 13571.0, 13572.0],       [13573.0, 13574.0, 13575.0, 13576.0, 13577.0, 13578.0],       [13579.0, 13580.0, 13581.0, 13582.0, 13583.0, 13584.0],       [13585.0, 13586.0, 13587.0, 13588.0, 13589.0, 13590.0],       [13591.0, 13592.0, 13593.0, 13594.0, 13595.0, 13596.0],       [13597.0, 13598.0, 13599.0, 13600.0, 13601.0, 13602.0],       [13603.0, 13604.0, 13605.0, 13606.0, 13607.0, 13608.0]]],     [[[13609.0, 13610.0, 13611.0, 13612.0, 13613.0, 13614.0],       [13615.0, 13616.0, 13617.0, 13618.0, 13619.0, 13620.0],       [13621.0, 13622.0, 13623.0, 13624.0, 13625.0, 13626.0],       [13627.0, 13628.0, 13629.0, 13630.0, 13631.0, 13632.0],       [13633.0, 13634.0, 13635.0, 13636.0, 13637.0, 13638.0],       [13639.0, 13640.0, 13641.0, 13642.0, 13643.0, 13644.0],       [13645.0, 13646.0, 13647.0, 13648.0, 13649.0, 13650.0]],      [[13651.0, 13652.0, 13653.0, 13654.0, 13655.0, 13656.0],       [13657.0, 13658.0, 13659.0, 13660.0, 13661.0, 13662.0],       [13663.0, 13664.0, 13665.0, 13666.0, 13667.0, 13668.0],       [13669.0, 13670.0, 13671.0, 13672.0, 13673.0, 13674.0],       [13675.0, 13676.0, 13677.0, 13678.0, 13679.0, 13680.0],       [13681.0, 13682.0, 13683.0, 13684.0, 13685.0, 13686.0],       [13687.0, 13688.0, 13689.0, 13690.0, 13691.0, 13692.0]],      [[13693.0, 13694.0, 13695.0, 13696.0, 13697.0, 13698.0],       [13699.0, 13700.0, 13701.0, 13702.0, 13703.0, 13704.0],       [13705.0, 13706.0, 13707.0, 13708.0, 13709.0, 13710.0],       [13711.0, 13712.0, 13713.0, 13714.0, 13715.0, 13716.0],       [13717.0, 13718.0, 13719.0, 13720.0, 13721.0, 13722.0],       [13723.0, 13724.0, 13725.0, 13726.0, 13727.0, 13728.0],       [13729.0, 13730.0, 13731.0, 13732.0, 13733.0, 13734.0]],      [[13735.0, 13736.0, 13737.0, 13738.0, 13739.0, 13740.0],       [13741.0, 13742.0, 13743.0, 13744.0, 13745.0, 13746.0],       [13747.0, 13748.0, 13749.0, 13750.0, 13751.0, 13752.0],       [13753.0, 13754.0, 13755.0, 13756.0, 13757.0, 13758.0],       [13759.0, 13760.0, 13761.0, 13762.0, 13763.0, 13764.0],       [13765.0, 13766.0, 13767.0, 13768.0, 13769.0, 13770.0],       [13771.0, 13772.0, 13773.0, 13774.0, 13775.0, 13776.0]],      [[13777.0, 13778.0, 13779.0, 13780.0, 13781.0, 13782.0],       [13783.0, 13784.0, 13785.0, 13786.0, 13787.0, 13788.0],       [13789.0, 13790.0, 13791.0, 13792.0, 13793.0, 13794.0],       [13795.0, 13796.0, 13797.0, 13798.0, 13799.0, 13800.0],       [13801.0, 13802.0, 13803.0, 13804.0, 13805.0, 13806.0],       [13807.0, 13808.0, 13809.0, 13810.0, 13811.0, 13812.0],       [13813.0, 13814.0, 13815.0, 13816.0, 13817.0, 13818.0]],      [[13819.0, 13820.0, 13821.0, 13822.0, 13823.0, 13824.0],       [13825.0, 13826.0, 13827.0, 13828.0, 13829.0, 13830.0],       [13831.0, 13832.0, 13833.0, 13834.0, 13835.0, 13836.0],       [13837.0, 13838.0, 13839.0, 13840.0, 13841.0, 13842.0],       [13843.0, 13844.0, 13845.0, 13846.0, 13847.0, 13848.0],       [13849.0, 13850.0, 13851.0, 13852.0, 13853.0, 13854.0],       [13855.0, 13856.0, 13857.0, 13858.0, 13859.0, 13860.0]]],     [[[13861.0, 13862.0, 13863.0, 13864.0, 13865.0, 13866.0],       [13867.0, 13868.0, 13869.0, 13870.0, 13871.0, 13872.0],       [13873.0, 13874.0, 13875.0, 13876.0, 13877.0, 13878.0],       [13879.0, 13880.0, 13881.0, 13882.0, 13883.0, 13884.0],       [13885.0, 13886.0, 13887.0, 13888.0, 13889.0, 13890.0],       [13891.0, 13892.0, 13893.0, 13894.0, 13895.0, 13896.0],       [13897.0, 13898.0, 13899.0, 13900.0, 13901.0, 13902.0]],      [[13903.0, 13904.0, 13905.0, 13906.0, 13907.0, 13908.0],       [13909.0, 13910.0, 13911.0, 13912.0, 13913.0, 13914.0],       [13915.0, 13916.0, 13917.0, 13918.0, 13919.0, 13920.0],       [13921.0, 13922.0, 13923.0, 13924.0, 13925.0, 13926.0],       [13927.0, 13928.0, 13929.0, 13930.0, 13931.0, 13932.0],       [13933.0, 13934.0, 13935.0, 13936.0, 13937.0, 13938.0],       [13939.0, 13940.0, 13941.0, 13942.0, 13943.0, 13944.0]],      [[13945.0, 13946.0, 13947.0, 13948.0, 13949.0, 13950.0],       [13951.0, 13952.0, 13953.0, 13954.0, 13955.0, 13956.0],       [13957.0, 13958.0, 13959.0, 13960.0, 13961.0, 13962.0],       [13963.0, 13964.0, 13965.0, 13966.0, 13967.0, 13968.0],       [13969.0, 13970.0, 13971.0, 13972.0, 13973.0, 13974.0],       [13975.0, 13976.0, 13977.0, 13978.0, 13979.0, 13980.0],       [13981.0, 13982.0, 13983.0, 13984.0, 13985.0, 13986.0]],      [[13987.0, 13988.0, 13989.0, 13990.0, 13991.0, 13992.0],       [13993.0, 13994.0, 13995.0, 13996.0, 13997.0, 13998.0],       [13999.0, 14000.0, 14001.0, 14002.0, 14003.0, 14004.0],       [14005.0, 14006.0, 14007.0, 14008.0, 14009.0, 14010.0],       [14011.0, 14012.0, 14013.0, 14014.0, 14015.0, 14016.0],       [14017.0, 14018.0, 14019.0, 14020.0, 14021.0, 14022.0],       [14023.0, 14024.0, 14025.0, 14026.0, 14027.0, 14028.0]],      [[14029.0, 14030.0, 14031.0, 14032.0, 14033.0, 14034.0],       [14035.0, 14036.0, 14037.0, 14038.0, 14039.0, 14040.0],       [14041.0, 14042.0, 14043.0, 14044.0, 14045.0, 14046.0],       [14047.0, 14048.0, 14049.0, 14050.0, 14051.0, 14052.0],       [14053.0, 14054.0, 14055.0, 14056.0, 14057.0, 14058.0],       [14059.0, 14060.0, 14061.0, 14062.0, 14063.0, 14064.0],       [14065.0, 14066.0, 14067.0, 14068.0, 14069.0, 14070.0]],      [[14071.0, 14072.0, 14073.0, 14074.0, 14075.0, 14076.0],       [14077.0, 14078.0, 14079.0, 14080.0, 14081.0, 14082.0],       [14083.0, 14084.0, 14085.0, 14086.0, 14087.0, 14088.0],       [14089.0, 14090.0, 14091.0, 14092.0, 14093.0, 14094.0],       [14095.0, 14096.0, 14097.0, 14098.0, 14099.0, 14100.0],       [14101.0, 14102.0, 14103.0, 14104.0, 14105.0, 14106.0],       [14107.0, 14108.0, 14109.0, 14110.0, 14111.0, 14112.0]]]],    [[[[14113.0, 14114.0, 14115.0, 14116.0, 14117.0, 14118.0],       [14119.0, 14120.0, 14121.0, 14122.0, 14123.0, 14124.0],       [14125.0, 14126.0, 14127.0, 14128.0, 14129.0, 14130.0],       [14131.0, 14132.0, 14133.0, 14134.0, 14135.0, 14136.0],       [14137.0, 14138.0, 14139.0, 14140.0, 14141.0, 14142.0],       [14143.0, 14144.0, 14145.0, 14146.0, 14147.0, 14148.0],       [14149.0, 14150.0, 14151.0, 14152.0, 14153.0, 14154.0]],      [[14155.0, 14156.0, 14157.0, 14158.0, 14159.0, 14160.0],       [14161.0, 14162.0, 14163.0, 14164.0, 14165.0, 14166.0],       [14167.0, 14168.0, 14169.0, 14170.0, 14171.0, 14172.0],       [14173.0, 14174.0, 14175.0, 14176.0, 14177.0, 14178.0],       [14179.0, 14180.0, 14181.0, 14182.0, 14183.0, 14184.0],       [14185.0, 14186.0, 14187.0, 14188.0, 14189.0, 14190.0],       [14191.0, 14192.0, 14193.0, 14194.0, 14195.0, 14196.0]],      [[14197.0, 14198.0, 14199.0, 14200.0, 14201.0, 14202.0],       [14203.0, 14204.0, 14205.0, 14206.0, 14207.0, 14208.0],       [14209.0, 14210.0, 14211.0, 14212.0, 14213.0, 14214.0],       [14215.0, 14216.0, 14217.0, 14218.0, 14219.0, 14220.0],       [14221.0, 14222.0, 14223.0, 14224.0, 14225.0, 14226.0],       [14227.0, 14228.0, 14229.0, 14230.0, 14231.0, 14232.0],       [14233.0, 14234.0, 14235.0, 14236.0, 14237.0, 14238.0]],      [[14239.0, 14240.0, 14241.0, 14242.0, 14243.0, 14244.0],       [14245.0, 14246.0, 14247.0, 14248.0, 14249.0, 14250.0],       [14251.0, 14252.0, 14253.0, 14254.0, 14255.0, 14256.0],       [14257.0, 14258.0, 14259.0, 14260.0, 14261.0, 14262.0],       [14263.0, 14264.0, 14265.0, 14266.0, 14267.0, 14268.0],       [14269.0, 14270.0, 14271.0, 14272.0, 14273.0, 14274.0],       [14275.0, 14276.0, 14277.0, 14278.0, 14279.0, 14280.0]],      [[14281.0, 14282.0, 14283.0, 14284.0, 14285.0, 14286.0],       [14287.0, 14288.0, 14289.0, 14290.0, 14291.0, 14292.0],       [14293.0, 14294.0, 14295.0, 14296.0, 14297.0, 14298.0],       [14299.0, 14300.0, 14301.0, 14302.0, 14303.0, 14304.0],       [14305.0, 14306.0, 14307.0, 14308.0, 14309.0, 14310.0],       [14311.0, 14312.0, 14313.0, 14314.0, 14315.0, 14316.0],       [14317.0, 14318.0, 14319.0, 14320.0, 14321.0, 14322.0]],      [[14323.0, 14324.0, 14325.0, 14326.0, 14327.0, 14328.0],       [14329.0, 14330.0, 14331.0, 14332.0, 14333.0, 14334.0],       [14335.0, 14336.0, 14337.0, 14338.0, 14339.0, 14340.0],       [14341.0, 14342.0, 14343.0, 14344.0, 14345.0, 14346.0],       [14347.0, 14348.0, 14349.0, 14350.0, 14351.0, 14352.0],       [14353.0, 14354.0, 14355.0, 14356.0, 14357.0, 14358.0],       [14359.0, 14360.0, 14361.0, 14362.0, 14363.0, 14364.0]]],     [[[14365.0, 14366.0, 14367.0, 14368.0, 14369.0, 14370.0],       [14371.0, 14372.0, 14373.0, 14374.0, 14375.0, 14376.0],       [14377.0, 14378.0, 14379.0, 14380.0, 14381.0, 14382.0],       [14383.0, 14384.0, 14385.0, 14386.0, 14387.0, 14388.0],       [14389.0, 14390.0, 14391.0, 14392.0, 14393.0, 14394.0],       [14395.0, 14396.0, 14397.0, 14398.0, 14399.0, 14400.0],       [14401.0, 14402.0, 14403.0, 14404.0, 14405.0, 14406.0]],      [[14407.0, 14408.0, 14409.0, 14410.0, 14411.0, 14412.0],       [14413.0, 14414.0, 14415.0, 14416.0, 14417.0, 14418.0],       [14419.0, 14420.0, 14421.0, 14422.0, 14423.0, 14424.0],       [14425.0, 14426.0, 14427.0, 14428.0, 14429.0, 14430.0],       [14431.0, 14432.0, 14433.0, 14434.0, 14435.0, 14436.0],       [14437.0, 14438.0, 14439.0, 14440.0, 14441.0, 14442.0],       [14443.0, 14444.0, 14445.0, 14446.0, 14447.0, 14448.0]],      [[14449.0, 14450.0, 14451.0, 14452.0, 14453.0, 14454.0],       [14455.0, 14456.0, 14457.0, 14458.0, 14459.0, 14460.0],       [14461.0, 14462.0, 14463.0, 14464.0, 14465.0, 14466.0],       [14467.0, 14468.0, 14469.0, 14470.0, 14471.0, 14472.0],       [14473.0, 14474.0, 14475.0, 14476.0, 14477.0, 14478.0],       [14479.0, 14480.0, 14481.0, 14482.0, 14483.0, 14484.0],       [14485.0, 14486.0, 14487.0, 14488.0, 14489.0, 14490.0]],      [[14491.0, 14492.0, 14493.0, 14494.0, 14495.0, 14496.0],       [14497.0, 14498.0, 14499.0, 14500.0, 14501.0, 14502.0],       [14503.0, 14504.0, 14505.0, 14506.0, 14507.0, 14508.0],       [14509.0, 14510.0, 14511.0, 14512.0, 14513.0, 14514.0],       [14515.0, 14516.0, 14517.0, 14518.0, 14519.0, 14520.0],       [14521.0, 14522.0, 14523.0, 14524.0, 14525.0, 14526.0],       [14527.0, 14528.0, 14529.0, 14530.0, 14531.0, 14532.0]],      [[14533.0, 14534.0, 14535.0, 14536.0, 14537.0, 14538.0],       [14539.0, 14540.0, 14541.0, 14542.0, 14543.0, 14544.0],       [14545.0, 14546.0, 14547.0, 14548.0, 14549.0, 14550.0],       [14551.0, 14552.0, 14553.0, 14554.0, 14555.0, 14556.0],       [14557.0, 14558.0, 14559.0, 14560.0, 14561.0, 14562.0],       [14563.0, 14564.0, 14565.0, 14566.0, 14567.0, 14568.0],       [14569.0, 14570.0, 14571.0, 14572.0, 14573.0, 14574.0]],      [[14575.0, 14576.0, 14577.0, 14578.0, 14579.0, 14580.0],       [14581.0, 14582.0, 14583.0, 14584.0, 14585.0, 14586.0],       [14587.0, 14588.0, 14589.0, 14590.0, 14591.0, 14592.0],       [14593.0, 14594.0, 14595.0, 14596.0, 14597.0, 14598.0],       [14599.0, 14600.0, 14601.0, 14602.0, 14603.0, 14604.0],       [14605.0, 14606.0, 14607.0, 14608.0, 14609.0, 14610.0],       [14611.0, 14612.0, 14613.0, 14614.0, 14615.0, 14616.0]]],     [[[14617.0, 14618.0, 14619.0, 14620.0, 14621.0, 14622.0],       [14623.0, 14624.0, 14625.0, 14626.0, 14627.0, 14628.0],       [14629.0, 14630.0, 14631.0, 14632.0, 14633.0, 14634.0],       [14635.0, 14636.0, 14637.0, 14638.0, 14639.0, 14640.0],       [14641.0, 14642.0, 14643.0, 14644.0, 14645.0, 14646.0],       [14647.0, 14648.0, 14649.0, 14650.0, 14651.0, 14652.0],       [14653.0, 14654.0, 14655.0, 14656.0, 14657.0, 14658.0]],      [[14659.0, 14660.0, 14661.0, 14662.0, 14663.0, 14664.0],       [14665.0, 14666.0, 14667.0, 14668.0, 14669.0, 14670.0],       [14671.0, 14672.0, 14673.0, 14674.0, 14675.0, 14676.0],       [14677.0, 14678.0, 14679.0, 14680.0, 14681.0, 14682.0],       [14683.0, 14684.0, 14685.0, 14686.0, 14687.0, 14688.0],       [14689.0, 14690.0, 14691.0, 14692.0, 14693.0, 14694.0],       [14695.0, 14696.0, 14697.0, 14698.0, 14699.0, 14700.0]],      [[14701.0, 14702.0, 14703.0, 14704.0, 14705.0, 14706.0],       [14707.0, 14708.0, 14709.0, 14710.0, 14711.0, 14712.0],       [14713.0, 14714.0, 14715.0, 14716.0, 14717.0, 14718.0],       [14719.0, 14720.0, 14721.0, 14722.0, 14723.0, 14724.0],       [14725.0, 14726.0, 14727.0, 14728.0, 14729.0, 14730.0],       [14731.0, 14732.0, 14733.0, 14734.0, 14735.0, 14736.0],       [14737.0, 14738.0, 14739.0, 14740.0, 14741.0, 14742.0]],      [[14743.0, 14744.0, 14745.0, 14746.0, 14747.0, 14748.0],       [14749.0, 14750.0, 14751.0, 14752.0, 14753.0, 14754.0],       [14755.0, 14756.0, 14757.0, 14758.0, 14759.0, 14760.0],       [14761.0, 14762.0, 14763.0, 14764.0, 14765.0, 14766.0],       [14767.0, 14768.0, 14769.0, 14770.0, 14771.0, 14772.0],       [14773.0, 14774.0, 14775.0, 14776.0, 14777.0, 14778.0],       [14779.0, 14780.0, 14781.0, 14782.0, 14783.0, 14784.0]],      [[14785.0, 14786.0, 14787.0, 14788.0, 14789.0, 14790.0],       [14791.0, 14792.0, 14793.0, 14794.0, 14795.0, 14796.0],       [14797.0, 14798.0, 14799.0, 14800.0, 14801.0, 14802.0],       [14803.0, 14804.0, 14805.0, 14806.0, 14807.0, 14808.0],       [14809.0, 14810.0, 14811.0, 14812.0, 14813.0, 14814.0],       [14815.0, 14816.0, 14817.0, 14818.0, 14819.0, 14820.0],       [14821.0, 14822.0, 14823.0, 14824.0, 14825.0, 14826.0]],      [[14827.0, 14828.0, 14829.0, 14830.0, 14831.0, 14832.0],       [14833.0, 14834.0, 14835.0, 14836.0, 14837.0, 14838.0],       [14839.0, 14840.0, 14841.0, 14842.0, 14843.0, 14844.0],       [14845.0, 14846.0, 14847.0, 14848.0, 14849.0, 14850.0],       [14851.0, 14852.0, 14853.0, 14854.0, 14855.0, 14856.0],       [14857.0, 14858.0, 14859.0, 14860.0, 14861.0, 14862.0],       [14863.0, 14864.0, 14865.0, 14866.0, 14867.0, 14868.0]]],     [[[14869.0, 14870.0, 14871.0, 14872.0, 14873.0, 14874.0],       [14875.0, 14876.0, 14877.0, 14878.0, 14879.0, 14880.0],       [14881.0, 14882.0, 14883.0, 14884.0, 14885.0, 14886.0],       [14887.0, 14888.0, 14889.0, 14890.0, 14891.0, 14892.0],       [14893.0, 14894.0, 14895.0, 14896.0, 14897.0, 14898.0],       [14899.0, 14900.0, 14901.0, 14902.0, 14903.0, 14904.0],       [14905.0, 14906.0, 14907.0, 14908.0, 14909.0, 14910.0]],      [[14911.0, 14912.0, 14913.0, 14914.0, 14915.0, 14916.0],       [14917.0, 14918.0, 14919.0, 14920.0, 14921.0, 14922.0],       [14923.0, 14924.0, 14925.0, 14926.0, 14927.0, 14928.0],       [14929.0, 14930.0, 14931.0, 14932.0, 14933.0, 14934.0],       [14935.0, 14936.0, 14937.0, 14938.0, 14939.0, 14940.0],       [14941.0, 14942.0, 14943.0, 14944.0, 14945.0, 14946.0],       [14947.0, 14948.0, 14949.0, 14950.0, 14951.0, 14952.0]],      [[14953.0, 14954.0, 14955.0, 14956.0, 14957.0, 14958.0],       [14959.0, 14960.0, 14961.0, 14962.0, 14963.0, 14964.0],       [14965.0, 14966.0, 14967.0, 14968.0, 14969.0, 14970.0],       [14971.0, 14972.0, 14973.0, 14974.0, 14975.0, 14976.0],       [14977.0, 14978.0, 14979.0, 14980.0, 14981.0, 14982.0],       [14983.0, 14984.0, 14985.0, 14986.0, 14987.0, 14988.0],       [14989.0, 14990.0, 14991.0, 14992.0, 14993.0, 14994.0]],      [[14995.0, 14996.0, 14997.0, 14998.0, 14999.0, 15000.0],       [15001.0, 15002.0, 15003.0, 15004.0, 15005.0, 15006.0],       [15007.0, 15008.0, 15009.0, 15010.0, 15011.0, 15012.0],       [15013.0, 15014.0, 15015.0, 15016.0, 15017.0, 15018.0],       [15019.0, 15020.0, 15021.0, 15022.0, 15023.0, 15024.0],       [15025.0, 15026.0, 15027.0, 15028.0, 15029.0, 15030.0],       [15031.0, 15032.0, 15033.0, 15034.0, 15035.0, 15036.0]],      [[15037.0, 15038.0, 15039.0, 15040.0, 15041.0, 15042.0],       [15043.0, 15044.0, 15045.0, 15046.0, 15047.0, 15048.0],       [15049.0, 15050.0, 15051.0, 15052.0, 15053.0, 15054.0],       [15055.0, 15056.0, 15057.0, 15058.0, 15059.0, 15060.0],       [15061.0, 15062.0, 15063.0, 15064.0, 15065.0, 15066.0],       [15067.0, 15068.0, 15069.0, 15070.0, 15071.0, 15072.0],       [15073.0, 15074.0, 15075.0, 15076.0, 15077.0, 15078.0]],      [[15079.0, 15080.0, 15081.0, 15082.0, 15083.0, 15084.0],       [15085.0, 15086.0, 15087.0, 15088.0, 15089.0, 15090.0],       [15091.0, 15092.0, 15093.0, 15094.0, 15095.0, 15096.0],       [15097.0, 15098.0, 15099.0, 15100.0, 15101.0, 15102.0],       [15103.0, 15104.0, 15105.0, 15106.0, 15107.0, 15108.0],       [15109.0, 15110.0, 15111.0, 15112.0, 15113.0, 15114.0],       [15115.0, 15116.0, 15117.0, 15118.0, 15119.0, 15120.0]]]],    [[[[15121.0, 15122.0, 15123.0, 15124.0, 15125.0, 15126.0],       [15127.0, 15128.0, 15129.0, 15130.0, 15131.0, 15132.0],       [15133.0, 15134.0, 15135.0, 15136.0, 15137.0, 15138.0],       [15139.0, 15140.0, 15141.0, 15142.0, 15143.0, 15144.0],       [15145.0, 15146.0, 15147.0, 15148.0, 15149.0, 15150.0],       [15151.0, 15152.0, 15153.0, 15154.0, 15155.0, 15156.0],       [15157.0, 15158.0, 15159.0, 15160.0, 15161.0, 15162.0]],      [[15163.0, 15164.0, 15165.0, 15166.0, 15167.0, 15168.0],       [15169.0, 15170.0, 15171.0, 15172.0, 15173.0, 15174.0],       [15175.0, 15176.0, 15177.0, 15178.0, 15179.0, 15180.0],       [15181.0, 15182.0, 15183.0, 15184.0, 15185.0, 15186.0],       [15187.0, 15188.0, 15189.0, 15190.0, 15191.0, 15192.0],       [15193.0, 15194.0, 15195.0, 15196.0, 15197.0, 15198.0],       [15199.0, 15200.0, 15201.0, 15202.0, 15203.0, 15204.0]],      [[15205.0, 15206.0, 15207.0, 15208.0, 15209.0, 15210.0],       [15211.0, 15212.0, 15213.0, 15214.0, 15215.0, 15216.0],       [15217.0, 15218.0, 15219.0, 15220.0, 15221.0, 15222.0],       [15223.0, 15224.0, 15225.0, 15226.0, 15227.0, 15228.0],       [15229.0, 15230.0, 15231.0, 15232.0, 15233.0, 15234.0],       [15235.0, 15236.0, 15237.0, 15238.0, 15239.0, 15240.0],       [15241.0, 15242.0, 15243.0, 15244.0, 15245.0, 15246.0]],      [[15247.0, 15248.0, 15249.0, 15250.0, 15251.0, 15252.0],       [15253.0, 15254.0, 15255.0, 15256.0, 15257.0, 15258.0],       [15259.0, 15260.0, 15261.0, 15262.0, 15263.0, 15264.0],       [15265.0, 15266.0, 15267.0, 15268.0, 15269.0, 15270.0],       [15271.0, 15272.0, 15273.0, 15274.0, 15275.0, 15276.0],       [15277.0, 15278.0, 15279.0, 15280.0, 15281.0, 15282.0],       [15283.0, 15284.0, 15285.0, 15286.0, 15287.0, 15288.0]],      [[15289.0, 15290.0, 15291.0, 15292.0, 15293.0, 15294.0],       [15295.0, 15296.0, 15297.0, 15298.0, 15299.0, 15300.0],       [15301.0, 15302.0, 15303.0, 15304.0, 15305.0, 15306.0],       [15307.0, 15308.0, 15309.0, 15310.0, 15311.0, 15312.0],       [15313.0, 15314.0, 15315.0, 15316.0, 15317.0, 15318.0],       [15319.0, 15320.0, 15321.0, 15322.0, 15323.0, 15324.0],       [15325.0, 15326.0, 15327.0, 15328.0, 15329.0, 15330.0]],      [[15331.0, 15332.0, 15333.0, 15334.0, 15335.0, 15336.0],       [15337.0, 15338.0, 15339.0, 15340.0, 15341.0, 15342.0],       [15343.0, 15344.0, 15345.0, 15346.0, 15347.0, 15348.0],       [15349.0, 15350.0, 15351.0, 15352.0, 15353.0, 15354.0],       [15355.0, 15356.0, 15357.0, 15358.0, 15359.0, 15360.0],       [15361.0, 15362.0, 15363.0, 15364.0, 15365.0, 15366.0],       [15367.0, 15368.0, 15369.0, 15370.0, 15371.0, 15372.0]]],     [[[15373.0, 15374.0, 15375.0, 15376.0, 15377.0, 15378.0],       [15379.0, 15380.0, 15381.0, 15382.0, 15383.0, 15384.0],       [15385.0, 15386.0, 15387.0, 15388.0, 15389.0, 15390.0],       [15391.0, 15392.0, 15393.0, 15394.0, 15395.0, 15396.0],       [15397.0, 15398.0, 15399.0, 15400.0, 15401.0, 15402.0],       [15403.0, 15404.0, 15405.0, 15406.0, 15407.0, 15408.0],       [15409.0, 15410.0, 15411.0, 15412.0, 15413.0, 15414.0]],      [[15415.0, 15416.0, 15417.0, 15418.0, 15419.0, 15420.0],       [15421.0, 15422.0, 15423.0, 15424.0, 15425.0, 15426.0],       [15427.0, 15428.0, 15429.0, 15430.0, 15431.0, 15432.0],       [15433.0, 15434.0, 15435.0, 15436.0, 15437.0, 15438.0],       [15439.0, 15440.0, 15441.0, 15442.0, 15443.0, 15444.0],       [15445.0, 15446.0, 15447.0, 15448.0, 15449.0, 15450.0],       [15451.0, 15452.0, 15453.0, 15454.0, 15455.0, 15456.0]],      [[15457.0, 15458.0, 15459.0, 15460.0, 15461.0, 15462.0],       [15463.0, 15464.0, 15465.0, 15466.0, 15467.0, 15468.0],       [15469.0, 15470.0, 15471.0, 15472.0, 15473.0, 15474.0],       [15475.0, 15476.0, 15477.0, 15478.0, 15479.0, 15480.0],       [15481.0, 15482.0, 15483.0, 15484.0, 15485.0, 15486.0],       [15487.0, 15488.0, 15489.0, 15490.0, 15491.0, 15492.0],       [15493.0, 15494.0, 15495.0, 15496.0, 15497.0, 15498.0]],      [[15499.0, 15500.0, 15501.0, 15502.0, 15503.0, 15504.0],       [15505.0, 15506.0, 15507.0, 15508.0, 15509.0, 15510.0],       [15511.0, 15512.0, 15513.0, 15514.0, 15515.0, 15516.0],       [15517.0, 15518.0, 15519.0, 15520.0, 15521.0, 15522.0],       [15523.0, 15524.0, 15525.0, 15526.0, 15527.0, 15528.0],       [15529.0, 15530.0, 15531.0, 15532.0, 15533.0, 15534.0],       [15535.0, 15536.0, 15537.0, 15538.0, 15539.0, 15540.0]],      [[15541.0, 15542.0, 15543.0, 15544.0, 15545.0, 15546.0],       [15547.0, 15548.0, 15549.0, 15550.0, 15551.0, 15552.0],       [15553.0, 15554.0, 15555.0, 15556.0, 15557.0, 15558.0],       [15559.0, 15560.0, 15561.0, 15562.0, 15563.0, 15564.0],       [15565.0, 15566.0, 15567.0, 15568.0, 15569.0, 15570.0],       [15571.0, 15572.0, 15573.0, 15574.0, 15575.0, 15576.0],       [15577.0, 15578.0, 15579.0, 15580.0, 15581.0, 15582.0]],      [[15583.0, 15584.0, 15585.0, 15586.0, 15587.0, 15588.0],       [15589.0, 15590.0, 15591.0, 15592.0, 15593.0, 15594.0],       [15595.0, 15596.0, 15597.0, 15598.0, 15599.0, 15600.0],       [15601.0, 15602.0, 15603.0, 15604.0, 15605.0, 15606.0],       [15607.0, 15608.0, 15609.0, 15610.0, 15611.0, 15612.0],       [15613.0, 15614.0, 15615.0, 15616.0, 15617.0, 15618.0],       [15619.0, 15620.0, 15621.0, 15622.0, 15623.0, 15624.0]]],     [[[15625.0, 15626.0, 15627.0, 15628.0, 15629.0, 15630.0],       [15631.0, 15632.0, 15633.0, 15634.0, 15635.0, 15636.0],       [15637.0, 15638.0, 15639.0, 15640.0, 15641.0, 15642.0],       [15643.0, 15644.0, 15645.0, 15646.0, 15647.0, 15648.0],       [15649.0, 15650.0, 15651.0, 15652.0, 15653.0, 15654.0],       [15655.0, 15656.0, 15657.0, 15658.0, 15659.0, 15660.0],       [15661.0, 15662.0, 15663.0, 15664.0, 15665.0, 15666.0]],      [[15667.0, 15668.0, 15669.0, 15670.0, 15671.0, 15672.0],       [15673.0, 15674.0, 15675.0, 15676.0, 15677.0, 15678.0],       [15679.0, 15680.0, 15681.0, 15682.0, 15683.0, 15684.0],       [15685.0, 15686.0, 15687.0, 15688.0, 15689.0, 15690.0],       [15691.0, 15692.0, 15693.0, 15694.0, 15695.0, 15696.0],       [15697.0, 15698.0, 15699.0, 15700.0, 15701.0, 15702.0],       [15703.0, 15704.0, 15705.0, 15706.0, 15707.0, 15708.0]],      [[15709.0, 15710.0, 15711.0, 15712.0, 15713.0, 15714.0],       [15715.0, 15716.0, 15717.0, 15718.0, 15719.0, 15720.0],       [15721.0, 15722.0, 15723.0, 15724.0, 15725.0, 15726.0],       [15727.0, 15728.0, 15729.0, 15730.0, 15731.0, 15732.0],       [15733.0, 15734.0, 15735.0, 15736.0, 15737.0, 15738.0],       [15739.0, 15740.0, 15741.0, 15742.0, 15743.0, 15744.0],       [15745.0, 15746.0, 15747.0, 15748.0, 15749.0, 15750.0]],      [[15751.0, 15752.0, 15753.0, 15754.0, 15755.0, 15756.0],       [15757.0, 15758.0, 15759.0, 15760.0, 15761.0, 15762.0],       [15763.0, 15764.0, 15765.0, 15766.0, 15767.0, 15768.0],       [15769.0, 15770.0, 15771.0, 15772.0, 15773.0, 15774.0],       [15775.0, 15776.0, 15777.0, 15778.0, 15779.0, 15780.0],       [15781.0, 15782.0, 15783.0, 15784.0, 15785.0, 15786.0],       [15787.0, 15788.0, 15789.0, 15790.0, 15791.0, 15792.0]],      [[15793.0, 15794.0, 15795.0, 15796.0, 15797.0, 15798.0],       [15799.0, 15800.0, 15801.0, 15802.0, 15803.0, 15804.0],       [15805.0, 15806.0, 15807.0, 15808.0, 15809.0, 15810.0],       [15811.0, 15812.0, 15813.0, 15814.0, 15815.0, 15816.0],       [15817.0, 15818.0, 15819.0, 15820.0, 15821.0, 15822.0],       [15823.0, 15824.0, 15825.0, 15826.0, 15827.0, 15828.0],       [15829.0, 15830.0, 15831.0, 15832.0, 15833.0, 15834.0]],      [[15835.0, 15836.0, 15837.0, 15838.0, 15839.0, 15840.0],       [15841.0, 15842.0, 15843.0, 15844.0, 15845.0, 15846.0],       [15847.0, 15848.0, 15849.0, 15850.0, 15851.0, 15852.0],       [15853.0, 15854.0, 15855.0, 15856.0, 15857.0, 15858.0],       [15859.0, 15860.0, 15861.0, 15862.0, 15863.0, 15864.0],       [15865.0, 15866.0, 15867.0, 15868.0, 15869.0, 15870.0],       [15871.0, 15872.0, 15873.0, 15874.0, 15875.0, 15876.0]]],     [[[15877.0, 15878.0, 15879.0, 15880.0, 15881.0, 15882.0],       [15883.0, 15884.0, 15885.0, 15886.0, 15887.0, 15888.0],       [15889.0, 15890.0, 15891.0, 15892.0, 15893.0, 15894.0],       [15895.0, 15896.0, 15897.0, 15898.0, 15899.0, 15900.0],       [15901.0, 15902.0, 15903.0, 15904.0, 15905.0, 15906.0],       [15907.0, 15908.0, 15909.0, 15910.0, 15911.0, 15912.0],       [15913.0, 15914.0, 15915.0, 15916.0, 15917.0, 15918.0]],      [[15919.0, 15920.0, 15921.0, 15922.0, 15923.0, 15924.0],       [15925.0, 15926.0, 15927.0, 15928.0, 15929.0, 15930.0],       [15931.0, 15932.0, 15933.0, 15934.0, 15935.0, 15936.0],       [15937.0, 15938.0, 15939.0, 15940.0, 15941.0, 15942.0],       [15943.0, 15944.0, 15945.0, 15946.0, 15947.0, 15948.0],       [15949.0, 15950.0, 15951.0, 15952.0, 15953.0, 15954.0],       [15955.0, 15956.0, 15957.0, 15958.0, 15959.0, 15960.0]],      [[15961.0, 15962.0, 15963.0, 15964.0, 15965.0, 15966.0],       [15967.0, 15968.0, 15969.0, 15970.0, 15971.0, 15972.0],       [15973.0, 15974.0, 15975.0, 15976.0, 15977.0, 15978.0],       [15979.0, 15980.0, 15981.0, 15982.0, 15983.0, 15984.0],       [15985.0, 15986.0, 15987.0, 15988.0, 15989.0, 15990.0],       [15991.0, 15992.0, 15993.0, 15994.0, 15995.0, 15996.0],       [15997.0, 15998.0, 15999.0, 16000.0, 16001.0, 16002.0]],      [[16003.0, 16004.0, 16005.0, 16006.0, 16007.0, 16008.0],       [16009.0, 16010.0, 16011.0, 16012.0, 16013.0, 16014.0],       [16015.0, 16016.0, 16017.0, 16018.0, 16019.0, 16020.0],       [16021.0, 16022.0, 16023.0, 16024.0, 16025.0, 16026.0],       [16027.0, 16028.0, 16029.0, 16030.0, 16031.0, 16032.0],       [16033.0, 16034.0, 16035.0, 16036.0, 16037.0, 16038.0],       [16039.0, 16040.0, 16041.0, 16042.0, 16043.0, 16044.0]],      [[16045.0, 16046.0, 16047.0, 16048.0, 16049.0, 16050.0],       [16051.0, 16052.0, 16053.0, 16054.0, 16055.0, 16056.0],       [16057.0, 16058.0, 16059.0, 16060.0, 16061.0, 16062.0],       [16063.0, 16064.0, 16065.0, 16066.0, 16067.0, 16068.0],       [16069.0, 16070.0, 16071.0, 16072.0, 16073.0, 16074.0],       [16075.0, 16076.0, 16077.0, 16078.0, 16079.0, 16080.0],       [16081.0, 16082.0, 16083.0, 16084.0, 16085.0, 16086.0]],      [[16087.0, 16088.0, 16089.0, 16090.0, 16091.0, 16092.0],       [16093.0, 16094.0, 16095.0, 16096.0, 16097.0, 16098.0],       [16099.0, 16100.0, 16101.0, 16102.0, 16103.0, 16104.0],       [16105.0, 16106.0, 16107.0, 16108.0, 16109.0, 16110.0],       [16111.0, 16112.0, 16113.0, 16114.0, 16115.0, 16116.0],       [16117.0, 16118.0, 16119.0, 16120.0, 16121.0, 16122.0],       [16123.0, 16124.0, 16125.0, 16126.0, 16127.0, 16128.0]]]],    [[[[16129.0, 16130.0, 16131.0, 16132.0, 16133.0, 16134.0],       [16135.0, 16136.0, 16137.0, 16138.0, 16139.0, 16140.0],       [16141.0, 16142.0, 16143.0, 16144.0, 16145.0, 16146.0],       [16147.0, 16148.0, 16149.0, 16150.0, 16151.0, 16152.0],       [16153.0, 16154.0, 16155.0, 16156.0, 16157.0, 16158.0],       [16159.0, 16160.0, 16161.0, 16162.0, 16163.0, 16164.0],       [16165.0, 16166.0, 16167.0, 16168.0, 16169.0, 16170.0]],      [[16171.0, 16172.0, 16173.0, 16174.0, 16175.0, 16176.0],       [16177.0, 16178.0, 16179.0, 16180.0, 16181.0, 16182.0],       [16183.0, 16184.0, 16185.0, 16186.0, 16187.0, 16188.0],       [16189.0, 16190.0, 16191.0, 16192.0, 16193.0, 16194.0],       [16195.0, 16196.0, 16197.0, 16198.0, 16199.0, 16200.0],       [16201.0, 16202.0, 16203.0, 16204.0, 16205.0, 16206.0],       [16207.0, 16208.0, 16209.0, 16210.0, 16211.0, 16212.0]],      [[16213.0, 16214.0, 16215.0, 16216.0, 16217.0, 16218.0],       [16219.0, 16220.0, 16221.0, 16222.0, 16223.0, 16224.0],       [16225.0, 16226.0, 16227.0, 16228.0, 16229.0, 16230.0],       [16231.0, 16232.0, 16233.0, 16234.0, 16235.0, 16236.0],       [16237.0, 16238.0, 16239.0, 16240.0, 16241.0, 16242.0],       [16243.0, 16244.0, 16245.0, 16246.0, 16247.0, 16248.0],       [16249.0, 16250.0, 16251.0, 16252.0, 16253.0, 16254.0]],      [[16255.0, 16256.0, 16257.0, 16258.0, 16259.0, 16260.0],       [16261.0, 16262.0, 16263.0, 16264.0, 16265.0, 16266.0],       [16267.0, 16268.0, 16269.0, 16270.0, 16271.0, 16272.0],       [16273.0, 16274.0, 16275.0, 16276.0, 16277.0, 16278.0],       [16279.0, 16280.0, 16281.0, 16282.0, 16283.0, 16284.0],       [16285.0, 16286.0, 16287.0, 16288.0, 16289.0, 16290.0],       [16291.0, 16292.0, 16293.0, 16294.0, 16295.0, 16296.0]],      [[16297.0, 16298.0, 16299.0, 16300.0, 16301.0, 16302.0],       [16303.0, 16304.0, 16305.0, 16306.0, 16307.0, 16308.0],       [16309.0, 16310.0, 16311.0, 16312.0, 16313.0, 16314.0],       [16315.0, 16316.0, 16317.0, 16318.0, 16319.0, 16320.0],       [16321.0, 16322.0, 16323.0, 16324.0, 16325.0, 16326.0],       [16327.0, 16328.0, 16329.0, 16330.0, 16331.0, 16332.0],       [16333.0, 16334.0, 16335.0, 16336.0, 16337.0, 16338.0]],      [[16339.0, 16340.0, 16341.0, 16342.0, 16343.0, 16344.0],       [16345.0, 16346.0, 16347.0, 16348.0, 16349.0, 16350.0],       [16351.0, 16352.0, 16353.0, 16354.0, 16355.0, 16356.0],       [16357.0, 16358.0, 16359.0, 16360.0, 16361.0, 16362.0],       [16363.0, 16364.0, 16365.0, 16366.0, 16367.0, 16368.0],       [16369.0, 16370.0, 16371.0, 16372.0, 16373.0, 16374.0],       [16375.0, 16376.0, 16377.0, 16378.0, 16379.0, 16380.0]]],     [[[16381.0, 16382.0, 16383.0, 16384.0, 16385.0, 16386.0],       [16387.0, 16388.0, 16389.0, 16390.0, 16391.0, 16392.0],       [16393.0, 16394.0, 16395.0, 16396.0, 16397.0, 16398.0],       [16399.0, 16400.0, 16401.0, 16402.0, 16403.0, 16404.0],       [16405.0, 16406.0, 16407.0, 16408.0, 16409.0, 16410.0],       [16411.0, 16412.0, 16413.0, 16414.0, 16415.0, 16416.0],       [16417.0, 16418.0, 16419.0, 16420.0, 16421.0, 16422.0]],      [[16423.0, 16424.0, 16425.0, 16426.0, 16427.0, 16428.0],       [16429.0, 16430.0, 16431.0, 16432.0, 16433.0, 16434.0],       [16435.0, 16436.0, 16437.0, 16438.0, 16439.0, 16440.0],       [16441.0, 16442.0, 16443.0, 16444.0, 16445.0, 16446.0],       [16447.0, 16448.0, 16449.0, 16450.0, 16451.0, 16452.0],       [16453.0, 16454.0, 16455.0, 16456.0, 16457.0, 16458.0],       [16459.0, 16460.0, 16461.0, 16462.0, 16463.0, 16464.0]],      [[16465.0, 16466.0, 16467.0, 16468.0, 16469.0, 16470.0],       [16471.0, 16472.0, 16473.0, 16474.0, 16475.0, 16476.0],       [16477.0, 16478.0, 16479.0, 16480.0, 16481.0, 16482.0],       [16483.0, 16484.0, 16485.0, 16486.0, 16487.0, 16488.0],       [16489.0, 16490.0, 16491.0, 16492.0, 16493.0, 16494.0],       [16495.0, 16496.0, 16497.0, 16498.0, 16499.0, 16500.0],       [16501.0, 16502.0, 16503.0, 16504.0, 16505.0, 16506.0]],      [[16507.0, 16508.0, 16509.0, 16510.0, 16511.0, 16512.0],       [16513.0, 16514.0, 16515.0, 16516.0, 16517.0, 16518.0],       [16519.0, 16520.0, 16521.0, 16522.0, 16523.0, 16524.0],       [16525.0, 16526.0, 16527.0, 16528.0, 16529.0, 16530.0],       [16531.0, 16532.0, 16533.0, 16534.0, 16535.0, 16536.0],       [16537.0, 16538.0, 16539.0, 16540.0, 16541.0, 16542.0],       [16543.0, 16544.0, 16545.0, 16546.0, 16547.0, 16548.0]],      [[16549.0, 16550.0, 16551.0, 16552.0, 16553.0, 16554.0],       [16555.0, 16556.0, 16557.0, 16558.0, 16559.0, 16560.0],       [16561.0, 16562.0, 16563.0, 16564.0, 16565.0, 16566.0],       [16567.0, 16568.0, 16569.0, 16570.0, 16571.0, 16572.0],       [16573.0, 16574.0, 16575.0, 16576.0, 16577.0, 16578.0],       [16579.0, 16580.0, 16581.0, 16582.0, 16583.0, 16584.0],       [16585.0, 16586.0, 16587.0, 16588.0, 16589.0, 16590.0]],      [[16591.0, 16592.0, 16593.0, 16594.0, 16595.0, 16596.0],       [16597.0, 16598.0, 16599.0, 16600.0, 16601.0, 16602.0],       [16603.0, 16604.0, 16605.0, 16606.0, 16607.0, 16608.0],       [16609.0, 16610.0, 16611.0, 16612.0, 16613.0, 16614.0],       [16615.0, 16616.0, 16617.0, 16618.0, 16619.0, 16620.0],       [16621.0, 16622.0, 16623.0, 16624.0, 16625.0, 16626.0],       [16627.0, 16628.0, 16629.0, 16630.0, 16631.0, 16632.0]]],     [[[16633.0, 16634.0, 16635.0, 16636.0, 16637.0, 16638.0],       [16639.0, 16640.0, 16641.0, 16642.0, 16643.0, 16644.0],       [16645.0, 16646.0, 16647.0, 16648.0, 16649.0, 16650.0],       [16651.0, 16652.0, 16653.0, 16654.0, 16655.0, 16656.0],       [16657.0, 16658.0, 16659.0, 16660.0, 16661.0, 16662.0],       [16663.0, 16664.0, 16665.0, 16666.0, 16667.0, 16668.0],       [16669.0, 16670.0, 16671.0, 16672.0, 16673.0, 16674.0]],      [[16675.0, 16676.0, 16677.0, 16678.0, 16679.0, 16680.0],       [16681.0, 16682.0, 16683.0, 16684.0, 16685.0, 16686.0],       [16687.0, 16688.0, 16689.0, 16690.0, 16691.0, 16692.0],       [16693.0, 16694.0, 16695.0, 16696.0, 16697.0, 16698.0],       [16699.0, 16700.0, 16701.0, 16702.0, 16703.0, 16704.0],       [16705.0, 16706.0, 16707.0, 16708.0, 16709.0, 16710.0],       [16711.0, 16712.0, 16713.0, 16714.0, 16715.0, 16716.0]],      [[16717.0, 16718.0, 16719.0, 16720.0, 16721.0, 16722.0],       [16723.0, 16724.0, 16725.0, 16726.0, 16727.0, 16728.0],       [16729.0, 16730.0, 16731.0, 16732.0, 16733.0, 16734.0],       [16735.0, 16736.0, 16737.0, 16738.0, 16739.0, 16740.0],       [16741.0, 16742.0, 16743.0, 16744.0, 16745.0, 16746.0],       [16747.0, 16748.0, 16749.0, 16750.0, 16751.0, 16752.0],       [16753.0, 16754.0, 16755.0, 16756.0, 16757.0, 16758.0]],      [[16759.0, 16760.0, 16761.0, 16762.0, 16763.0, 16764.0],       [16765.0, 16766.0, 16767.0, 16768.0, 16769.0, 16770.0],       [16771.0, 16772.0, 16773.0, 16774.0, 16775.0, 16776.0],       [16777.0, 16778.0, 16779.0, 16780.0, 16781.0, 16782.0],       [16783.0, 16784.0, 16785.0, 16786.0, 16787.0, 16788.0],       [16789.0, 16790.0, 16791.0, 16792.0, 16793.0, 16794.0],       [16795.0, 16796.0, 16797.0, 16798.0, 16799.0, 16800.0]],      [[16801.0, 16802.0, 16803.0, 16804.0, 16805.0, 16806.0],       [16807.0, 16808.0, 16809.0, 16810.0, 16811.0, 16812.0],       [16813.0, 16814.0, 16815.0, 16816.0, 16817.0, 16818.0],       [16819.0, 16820.0, 16821.0, 16822.0, 16823.0, 16824.0],       [16825.0, 16826.0, 16827.0, 16828.0, 16829.0, 16830.0],       [16831.0, 16832.0, 16833.0, 16834.0, 16835.0, 16836.0],       [16837.0, 16838.0, 16839.0, 16840.0, 16841.0, 16842.0]],      [[16843.0, 16844.0, 16845.0, 16846.0, 16847.0, 16848.0],       [16849.0, 16850.0, 16851.0, 16852.0, 16853.0, 16854.0],       [16855.0, 16856.0, 16857.0, 16858.0, 16859.0, 16860.0],       [16861.0, 16862.0, 16863.0, 16864.0, 16865.0, 16866.0],       [16867.0, 16868.0, 16869.0, 16870.0, 16871.0, 16872.0],       [16873.0, 16874.0, 16875.0, 16876.0, 16877.0, 16878.0],       [16879.0, 16880.0, 16881.0, 16882.0, 16883.0, 16884.0]]],     [[[16885.0, 16886.0, 16887.0, 16888.0, 16889.0, 16890.0],       [16891.0, 16892.0, 16893.0, 16894.0, 16895.0, 16896.0],       [16897.0, 16898.0, 16899.0, 16900.0, 16901.0, 16902.0],       [16903.0, 16904.0, 16905.0, 16906.0, 16907.0, 16908.0],       [16909.0, 16910.0, 16911.0, 16912.0, 16913.0, 16914.0],       [16915.0, 16916.0, 16917.0, 16918.0, 16919.0, 16920.0],       [16921.0, 16922.0, 16923.0, 16924.0, 16925.0, 16926.0]],      [[16927.0, 16928.0, 16929.0, 16930.0, 16931.0, 16932.0],       [16933.0, 16934.0, 16935.0, 16936.0, 16937.0, 16938.0],       [16939.0, 16940.0, 16941.0, 16942.0, 16943.0, 16944.0],       [16945.0, 16946.0, 16947.0, 16948.0, 16949.0, 16950.0],       [16951.0, 16952.0, 16953.0, 16954.0, 16955.0, 16956.0],       [16957.0, 16958.0, 16959.0, 16960.0, 16961.0, 16962.0],       [16963.0, 16964.0, 16965.0, 16966.0, 16967.0, 16968.0]],      [[16969.0, 16970.0, 16971.0, 16972.0, 16973.0, 16974.0],       [16975.0, 16976.0, 16977.0, 16978.0, 16979.0, 16980.0],       [16981.0, 16982.0, 16983.0, 16984.0, 16985.0, 16986.0],       [16987.0, 16988.0, 16989.0, 16990.0, 16991.0, 16992.0],       [16993.0, 16994.0, 16995.0, 16996.0, 16997.0, 16998.0],       [16999.0, 17000.0, 17001.0, 17002.0, 17003.0, 17004.0],       [17005.0, 17006.0, 17007.0, 17008.0, 17009.0, 17010.0]],      [[17011.0, 17012.0, 17013.0, 17014.0, 17015.0, 17016.0],       [17017.0, 17018.0, 17019.0, 17020.0, 17021.0, 17022.0],       [17023.0, 17024.0, 17025.0, 17026.0, 17027.0, 17028.0],       [17029.0, 17030.0, 17031.0, 17032.0, 17033.0, 17034.0],       [17035.0, 17036.0, 17037.0, 17038.0, 17039.0, 17040.0],       [17041.0, 17042.0, 17043.0, 17044.0, 17045.0, 17046.0],       [17047.0, 17048.0, 17049.0, 17050.0, 17051.0, 17052.0]],      [[17053.0, 17054.0, 17055.0, 17056.0, 17057.0, 17058.0],       [17059.0, 17060.0, 17061.0, 17062.0, 17063.0, 17064.0],       [17065.0, 17066.0, 17067.0, 17068.0, 17069.0, 17070.0],       [17071.0, 17072.0, 17073.0, 17074.0, 17075.0, 17076.0],       [17077.0, 17078.0, 17079.0, 17080.0, 17081.0, 17082.0],       [17083.0, 17084.0, 17085.0, 17086.0, 17087.0, 17088.0],       [17089.0, 17090.0, 17091.0, 17092.0, 17093.0, 17094.0]],      [[17095.0, 17096.0, 17097.0, 17098.0, 17099.0, 17100.0],       [17101.0, 17102.0, 17103.0, 17104.0, 17105.0, 17106.0],       [17107.0, 17108.0, 17109.0, 17110.0, 17111.0, 17112.0],       [17113.0, 17114.0, 17115.0, 17116.0, 17117.0, 17118.0],       [17119.0, 17120.0, 17121.0, 17122.0, 17123.0, 17124.0],       [17125.0, 17126.0, 17127.0, 17128.0, 17129.0, 17130.0],       [17131.0, 17132.0, 17133.0, 17134.0, 17135.0, 17136.0]]]],    [[[[17137.0, 17138.0, 17139.0, 17140.0, 17141.0, 17142.0],       [17143.0, 17144.0, 17145.0, 17146.0, 17147.0, 17148.0],       [17149.0, 17150.0, 17151.0, 17152.0, 17153.0, 17154.0],       [17155.0, 17156.0, 17157.0, 17158.0, 17159.0, 17160.0],       [17161.0, 17162.0, 17163.0, 17164.0, 17165.0, 17166.0],       [17167.0, 17168.0, 17169.0, 17170.0, 17171.0, 17172.0],       [17173.0, 17174.0, 17175.0, 17176.0, 17177.0, 17178.0]],      [[17179.0, 17180.0, 17181.0, 17182.0, 17183.0, 17184.0],       [17185.0, 17186.0, 17187.0, 17188.0, 17189.0, 17190.0],       [17191.0, 17192.0, 17193.0, 17194.0, 17195.0, 17196.0],       [17197.0, 17198.0, 17199.0, 17200.0, 17201.0, 17202.0],       [17203.0, 17204.0, 17205.0, 17206.0, 17207.0, 17208.0],       [17209.0, 17210.0, 17211.0, 17212.0, 17213.0, 17214.0],       [17215.0, 17216.0, 17217.0, 17218.0, 17219.0, 17220.0]],      [[17221.0, 17222.0, 17223.0, 17224.0, 17225.0, 17226.0],       [17227.0, 17228.0, 17229.0, 17230.0, 17231.0, 17232.0],       [17233.0, 17234.0, 17235.0, 17236.0, 17237.0, 17238.0],       [17239.0, 17240.0, 17241.0, 17242.0, 17243.0, 17244.0],       [17245.0, 17246.0, 17247.0, 17248.0, 17249.0, 17250.0],       [17251.0, 17252.0, 17253.0, 17254.0, 17255.0, 17256.0],       [17257.0, 17258.0, 17259.0, 17260.0, 17261.0, 17262.0]],      [[17263.0, 17264.0, 17265.0, 17266.0, 17267.0, 17268.0],       [17269.0, 17270.0, 17271.0, 17272.0, 17273.0, 17274.0],       [17275.0, 17276.0, 17277.0, 17278.0, 17279.0, 17280.0],       [17281.0, 17282.0, 17283.0, 17284.0, 17285.0, 17286.0],       [17287.0, 17288.0, 17289.0, 17290.0, 17291.0, 17292.0],       [17293.0, 17294.0, 17295.0, 17296.0, 17297.0, 17298.0],       [17299.0, 17300.0, 17301.0, 17302.0, 17303.0, 17304.0]],      [[17305.0, 17306.0, 17307.0, 17308.0, 17309.0, 17310.0],       [17311.0, 17312.0, 17313.0, 17314.0, 17315.0, 17316.0],       [17317.0, 17318.0, 17319.0, 17320.0, 17321.0, 17322.0],       [17323.0, 17324.0, 17325.0, 17326.0, 17327.0, 17328.0],       [17329.0, 17330.0, 17331.0, 17332.0, 17333.0, 17334.0],       [17335.0, 17336.0, 17337.0, 17338.0, 17339.0, 17340.0],       [17341.0, 17342.0, 17343.0, 17344.0, 17345.0, 17346.0]],      [[17347.0, 17348.0, 17349.0, 17350.0, 17351.0, 17352.0],       [17353.0, 17354.0, 17355.0, 17356.0, 17357.0, 17358.0],       [17359.0, 17360.0, 17361.0, 17362.0, 17363.0, 17364.0],       [17365.0, 17366.0, 17367.0, 17368.0, 17369.0, 17370.0],       [17371.0, 17372.0, 17373.0, 17374.0, 17375.0, 17376.0],       [17377.0, 17378.0, 17379.0, 17380.0, 17381.0, 17382.0],       [17383.0, 17384.0, 17385.0, 17386.0, 17387.0, 17388.0]]],     [[[17389.0, 17390.0, 17391.0, 17392.0, 17393.0, 17394.0],       [17395.0, 17396.0, 17397.0, 17398.0, 17399.0, 17400.0],       [17401.0, 17402.0, 17403.0, 17404.0, 17405.0, 17406.0],       [17407.0, 17408.0, 17409.0, 17410.0, 17411.0, 17412.0],       [17413.0, 17414.0, 17415.0, 17416.0, 17417.0, 17418.0],       [17419.0, 17420.0, 17421.0, 17422.0, 17423.0, 17424.0],       [17425.0, 17426.0, 17427.0, 17428.0, 17429.0, 17430.0]],      [[17431.0, 17432.0, 17433.0, 17434.0, 17435.0, 17436.0],       [17437.0, 17438.0, 17439.0, 17440.0, 17441.0, 17442.0],       [17443.0, 17444.0, 17445.0, 17446.0, 17447.0, 17448.0],       [17449.0, 17450.0, 17451.0, 17452.0, 17453.0, 17454.0],       [17455.0, 17456.0, 17457.0, 17458.0, 17459.0, 17460.0],       [17461.0, 17462.0, 17463.0, 17464.0, 17465.0, 17466.0],       [17467.0, 17468.0, 17469.0, 17470.0, 17471.0, 17472.0]],      [[17473.0, 17474.0, 17475.0, 17476.0, 17477.0, 17478.0],       [17479.0, 17480.0, 17481.0, 17482.0, 17483.0, 17484.0],       [17485.0, 17486.0, 17487.0, 17488.0, 17489.0, 17490.0],       [17491.0, 17492.0, 17493.0, 17494.0, 17495.0, 17496.0],       [17497.0, 17498.0, 17499.0, 17500.0, 17501.0, 17502.0],       [17503.0, 17504.0, 17505.0, 17506.0, 17507.0, 17508.0],       [17509.0, 17510.0, 17511.0, 17512.0, 17513.0, 17514.0]],      [[17515.0, 17516.0, 17517.0, 17518.0, 17519.0, 17520.0],       [17521.0, 17522.0, 17523.0, 17524.0, 17525.0, 17526.0],       [17527.0, 17528.0, 17529.0, 17530.0, 17531.0, 17532.0],       [17533.0, 17534.0, 17535.0, 17536.0, 17537.0, 17538.0],       [17539.0, 17540.0, 17541.0, 17542.0, 17543.0, 17544.0],       [17545.0, 17546.0, 17547.0, 17548.0, 17549.0, 17550.0],       [17551.0, 17552.0, 17553.0, 17554.0, 17555.0, 17556.0]],      [[17557.0, 17558.0, 17559.0, 17560.0, 17561.0, 17562.0],       [17563.0, 17564.0, 17565.0, 17566.0, 17567.0, 17568.0],       [17569.0, 17570.0, 17571.0, 17572.0, 17573.0, 17574.0],       [17575.0, 17576.0, 17577.0, 17578.0, 17579.0, 17580.0],       [17581.0, 17582.0, 17583.0, 17584.0, 17585.0, 17586.0],       [17587.0, 17588.0, 17589.0, 17590.0, 17591.0, 17592.0],       [17593.0, 17594.0, 17595.0, 17596.0, 17597.0, 17598.0]],      [[17599.0, 17600.0, 17601.0, 17602.0, 17603.0, 17604.0],       [17605.0, 17606.0, 17607.0, 17608.0, 17609.0, 17610.0],       [17611.0, 17612.0, 17613.0, 17614.0, 17615.0, 17616.0],       [17617.0, 17618.0, 17619.0, 17620.0, 17621.0, 17622.0],       [17623.0, 17624.0, 17625.0, 17626.0, 17627.0, 17628.0],       [17629.0, 17630.0, 17631.0, 17632.0, 17633.0, 17634.0],       [17635.0, 17636.0, 17637.0, 17638.0, 17639.0, 17640.0]]],     [[[17641.0, 17642.0, 17643.0, 17644.0, 17645.0, 17646.0],       [17647.0, 17648.0, 17649.0, 17650.0, 17651.0, 17652.0],       [17653.0, 17654.0, 17655.0, 17656.0, 17657.0, 17658.0],       [17659.0, 17660.0, 17661.0, 17662.0, 17663.0, 17664.0],       [17665.0, 17666.0, 17667.0, 17668.0, 17669.0, 17670.0],       [17671.0, 17672.0, 17673.0, 17674.0, 17675.0, 17676.0],       [17677.0, 17678.0, 17679.0, 17680.0, 17681.0, 17682.0]],      [[17683.0, 17684.0, 17685.0, 17686.0, 17687.0, 17688.0],       [17689.0, 17690.0, 17691.0, 17692.0, 17693.0, 17694.0],       [17695.0, 17696.0, 17697.0, 17698.0, 17699.0, 17700.0],       [17701.0, 17702.0, 17703.0, 17704.0, 17705.0, 17706.0],       [17707.0, 17708.0, 17709.0, 17710.0, 17711.0, 17712.0],       [17713.0, 17714.0, 17715.0, 17716.0, 17717.0, 17718.0],       [17719.0, 17720.0, 17721.0, 17722.0, 17723.0, 17724.0]],      [[17725.0, 17726.0, 17727.0, 17728.0, 17729.0, 17730.0],       [17731.0, 17732.0, 17733.0, 17734.0, 17735.0, 17736.0],       [17737.0, 17738.0, 17739.0, 17740.0, 17741.0, 17742.0],       [17743.0, 17744.0, 17745.0, 17746.0, 17747.0, 17748.0],       [17749.0, 17750.0, 17751.0, 17752.0, 17753.0, 17754.0],       [17755.0, 17756.0, 17757.0, 17758.0, 17759.0, 17760.0],       [17761.0, 17762.0, 17763.0, 17764.0, 17765.0, 17766.0]],      [[17767.0, 17768.0, 17769.0, 17770.0, 17771.0, 17772.0],       [17773.0, 17774.0, 17775.0, 17776.0, 17777.0, 17778.0],       [17779.0, 17780.0, 17781.0, 17782.0, 17783.0, 17784.0],       [17785.0, 17786.0, 17787.0, 17788.0, 17789.0, 17790.0],       [17791.0, 17792.0, 17793.0, 17794.0, 17795.0, 17796.0],       [17797.0, 17798.0, 17799.0, 17800.0, 17801.0, 17802.0],       [17803.0, 17804.0, 17805.0, 17806.0, 17807.0, 17808.0]],      [[17809.0, 17810.0, 17811.0, 17812.0, 17813.0, 17814.0],       [17815.0, 17816.0, 17817.0, 17818.0, 17819.0, 17820.0],       [17821.0, 17822.0, 17823.0, 17824.0, 17825.0, 17826.0],       [17827.0, 17828.0, 17829.0, 17830.0, 17831.0, 17832.0],       [17833.0, 17834.0, 17835.0, 17836.0, 17837.0, 17838.0],       [17839.0, 17840.0, 17841.0, 17842.0, 17843.0, 17844.0],       [17845.0, 17846.0, 17847.0, 17848.0, 17849.0, 17850.0]],      [[17851.0, 17852.0, 17853.0, 17854.0, 17855.0, 17856.0],       [17857.0, 17858.0, 17859.0, 17860.0, 17861.0, 17862.0],       [17863.0, 17864.0, 17865.0, 17866.0, 17867.0, 17868.0],       [17869.0, 17870.0, 17871.0, 17872.0, 17873.0, 17874.0],       [17875.0, 17876.0, 17877.0, 17878.0, 17879.0, 17880.0],       [17881.0, 17882.0, 17883.0, 17884.0, 17885.0, 17886.0],       [17887.0, 17888.0, 17889.0, 17890.0, 17891.0, 17892.0]]],     [[[17893.0, 17894.0, 17895.0, 17896.0, 17897.0, 17898.0],       [17899.0, 17900.0, 17901.0, 17902.0, 17903.0, 17904.0],       [17905.0, 17906.0, 17907.0, 17908.0, 17909.0, 17910.0],       [17911.0, 17912.0, 17913.0, 17914.0, 17915.0, 17916.0],       [17917.0, 17918.0, 17919.0, 17920.0, 17921.0, 17922.0],       [17923.0, 17924.0, 17925.0, 17926.0, 17927.0, 17928.0],       [17929.0, 17930.0, 17931.0, 17932.0, 17933.0, 17934.0]],      [[17935.0, 17936.0, 17937.0, 17938.0, 17939.0, 17940.0],       [17941.0, 17942.0, 17943.0, 17944.0, 17945.0, 17946.0],       [17947.0, 17948.0, 17949.0, 17950.0, 17951.0, 17952.0],       [17953.0, 17954.0, 17955.0, 17956.0, 17957.0, 17958.0],       [17959.0, 17960.0, 17961.0, 17962.0, 17963.0, 17964.0],       [17965.0, 17966.0, 17967.0, 17968.0, 17969.0, 17970.0],       [17971.0, 17972.0, 17973.0, 17974.0, 17975.0, 17976.0]],      [[17977.0, 17978.0, 17979.0, 17980.0, 17981.0, 17982.0],       [17983.0, 17984.0, 17985.0, 17986.0, 17987.0, 17988.0],       [17989.0, 17990.0, 17991.0, 17992.0, 17993.0, 17994.0],       [17995.0, 17996.0, 17997.0, 17998.0, 17999.0, 18000.0],       [18001.0, 18002.0, 18003.0, 18004.0, 18005.0, 18006.0],       [18007.0, 18008.0, 18009.0, 18010.0, 18011.0, 18012.0],       [18013.0, 18014.0, 18015.0, 18016.0, 18017.0, 18018.0]],      [[18019.0, 18020.0, 18021.0, 18022.0, 18023.0, 18024.0],       [18025.0, 18026.0, 18027.0, 18028.0, 18029.0, 18030.0],       [18031.0, 18032.0, 18033.0, 18034.0, 18035.0, 18036.0],       [18037.0, 18038.0, 18039.0, 18040.0, 18041.0, 18042.0],       [18043.0, 18044.0, 18045.0, 18046.0, 18047.0, 18048.0],       [18049.0, 18050.0, 18051.0, 18052.0, 18053.0, 18054.0],       [18055.0, 18056.0, 18057.0, 18058.0, 18059.0, 18060.0]],      [[18061.0, 18062.0, 18063.0, 18064.0, 18065.0, 18066.0],       [18067.0, 18068.0, 18069.0, 18070.0, 18071.0, 18072.0],       [18073.0, 18074.0, 18075.0, 18076.0, 18077.0, 18078.0],       [18079.0, 18080.0, 18081.0, 18082.0, 18083.0, 18084.0],       [18085.0, 18086.0, 18087.0, 18088.0, 18089.0, 18090.0],       [18091.0, 18092.0, 18093.0, 18094.0, 18095.0, 18096.0],       [18097.0, 18098.0, 18099.0, 18100.0, 18101.0, 18102.0]],      [[18103.0, 18104.0, 18105.0, 18106.0, 18107.0, 18108.0],       [18109.0, 18110.0, 18111.0, 18112.0, 18113.0, 18114.0],       [18115.0, 18116.0, 18117.0, 18118.0, 18119.0, 18120.0],       [18121.0, 18122.0, 18123.0, 18124.0, 18125.0, 18126.0],       [18127.0, 18128.0, 18129.0, 18130.0, 18131.0, 18132.0],       [18133.0, 18134.0, 18135.0, 18136.0, 18137.0, 18138.0],       [18139.0, 18140.0, 18141.0, 18142.0, 18143.0, 18144.0]]]]],   [[[[[18145.0, 18146.0, 18147.0, 18148.0, 18149.0, 18150.0],       [18151.0, 18152.0, 18153.0, 18154.0, 18155.0, 18156.0],       [18157.0, 18158.0, 18159.0, 18160.0, 18161.0, 18162.0],       [18163.0, 18164.0, 18165.0, 18166.0, 18167.0, 18168.0],       [18169.0, 18170.0, 18171.0, 18172.0, 18173.0, 18174.0],       [18175.0, 18176.0, 18177.0, 18178.0, 18179.0, 18180.0],       [18181.0, 18182.0, 18183.0, 18184.0, 18185.0, 18186.0]],      [[18187.0, 18188.0, 18189.0, 18190.0, 18191.0, 18192.0],       [18193.0, 18194.0, 18195.0, 18196.0, 18197.0, 18198.0],       [18199.0, 18200.0, 18201.0, 18202.0, 18203.0, 18204.0],       [18205.0, 18206.0, 18207.0, 18208.0, 18209.0, 18210.0],       [18211.0, 18212.0, 18213.0, 18214.0, 18215.0, 18216.0],       [18217.0, 18218.0, 18219.0, 18220.0, 18221.0, 18222.0],       [18223.0, 18224.0, 18225.0, 18226.0, 18227.0, 18228.0]],      [[18229.0, 18230.0, 18231.0, 18232.0, 18233.0, 18234.0],       [18235.0, 18236.0, 18237.0, 18238.0, 18239.0, 18240.0],       [18241.0, 18242.0, 18243.0, 18244.0, 18245.0, 18246.0],       [18247.0, 18248.0, 18249.0, 18250.0, 18251.0, 18252.0],       [18253.0, 18254.0, 18255.0, 18256.0, 18257.0, 18258.0],       [18259.0, 18260.0, 18261.0, 18262.0, 18263.0, 18264.0],       [18265.0, 18266.0, 18267.0, 18268.0, 18269.0, 18270.0]],      [[18271.0, 18272.0, 18273.0, 18274.0, 18275.0, 18276.0],       [18277.0, 18278.0, 18279.0, 18280.0, 18281.0, 18282.0],       [18283.0, 18284.0, 18285.0, 18286.0, 18287.0, 18288.0],       [18289.0, 18290.0, 18291.0, 18292.0, 18293.0, 18294.0],       [18295.0, 18296.0, 18297.0, 18298.0, 18299.0, 18300.0],       [18301.0, 18302.0, 18303.0, 18304.0, 18305.0, 18306.0],       [18307.0, 18308.0, 18309.0, 18310.0, 18311.0, 18312.0]],      [[18313.0, 18314.0, 18315.0, 18316.0, 18317.0, 18318.0],       [18319.0, 18320.0, 18321.0, 18322.0, 18323.0, 18324.0],       [18325.0, 18326.0, 18327.0, 18328.0, 18329.0, 18330.0],       [18331.0, 18332.0, 18333.0, 18334.0, 18335.0, 18336.0],       [18337.0, 18338.0, 18339.0, 18340.0, 18341.0, 18342.0],       [18343.0, 18344.0, 18345.0, 18346.0, 18347.0, 18348.0],       [18349.0, 18350.0, 18351.0, 18352.0, 18353.0, 18354.0]],      [[18355.0, 18356.0, 18357.0, 18358.0, 18359.0, 18360.0],       [18361.0, 18362.0, 18363.0, 18364.0, 18365.0, 18366.0],       [18367.0, 18368.0, 18369.0, 18370.0, 18371.0, 18372.0],       [18373.0, 18374.0, 18375.0, 18376.0, 18377.0, 18378.0],       [18379.0, 18380.0, 18381.0, 18382.0, 18383.0, 18384.0],       [18385.0, 18386.0, 18387.0, 18388.0, 18389.0, 18390.0],       [18391.0, 18392.0, 18393.0, 18394.0, 18395.0, 18396.0]]],     [[[18397.0, 18398.0, 18399.0, 18400.0, 18401.0, 18402.0],       [18403.0, 18404.0, 18405.0, 18406.0, 18407.0, 18408.0],       [18409.0, 18410.0, 18411.0, 18412.0, 18413.0, 18414.0],       [18415.0, 18416.0, 18417.0, 18418.0, 18419.0, 18420.0],       [18421.0, 18422.0, 18423.0, 18424.0, 18425.0, 18426.0],       [18427.0, 18428.0, 18429.0, 18430.0, 18431.0, 18432.0],       [18433.0, 18434.0, 18435.0, 18436.0, 18437.0, 18438.0]],      [[18439.0, 18440.0, 18441.0, 18442.0, 18443.0, 18444.0],       [18445.0, 18446.0, 18447.0, 18448.0, 18449.0, 18450.0],       [18451.0, 18452.0, 18453.0, 18454.0, 18455.0, 18456.0],       [18457.0, 18458.0, 18459.0, 18460.0, 18461.0, 18462.0],       [18463.0, 18464.0, 18465.0, 18466.0, 18467.0, 18468.0],       [18469.0, 18470.0, 18471.0, 18472.0, 18473.0, 18474.0],       [18475.0, 18476.0, 18477.0, 18478.0, 18479.0, 18480.0]],      [[18481.0, 18482.0, 18483.0, 18484.0, 18485.0, 18486.0],       [18487.0, 18488.0, 18489.0, 18490.0, 18491.0, 18492.0],       [18493.0, 18494.0, 18495.0, 18496.0, 18497.0, 18498.0],       [18499.0, 18500.0, 18501.0, 18502.0, 18503.0, 18504.0],       [18505.0, 18506.0, 18507.0, 18508.0, 18509.0, 18510.0],       [18511.0, 18512.0, 18513.0, 18514.0, 18515.0, 18516.0],       [18517.0, 18518.0, 18519.0, 18520.0, 18521.0, 18522.0]],      [[18523.0, 18524.0, 18525.0, 18526.0, 18527.0, 18528.0],       [18529.0, 18530.0, 18531.0, 18532.0, 18533.0, 18534.0],       [18535.0, 18536.0, 18537.0, 18538.0, 18539.0, 18540.0],       [18541.0, 18542.0, 18543.0, 18544.0, 18545.0, 18546.0],       [18547.0, 18548.0, 18549.0, 18550.0, 18551.0, 18552.0],       [18553.0, 18554.0, 18555.0, 18556.0, 18557.0, 18558.0],       [18559.0, 18560.0, 18561.0, 18562.0, 18563.0, 18564.0]],      [[18565.0, 18566.0, 18567.0, 18568.0, 18569.0, 18570.0],       [18571.0, 18572.0, 18573.0, 18574.0, 18575.0, 18576.0],       [18577.0, 18578.0, 18579.0, 18580.0, 18581.0, 18582.0],       [18583.0, 18584.0, 18585.0, 18586.0, 18587.0, 18588.0],       [18589.0, 18590.0, 18591.0, 18592.0, 18593.0, 18594.0],       [18595.0, 18596.0, 18597.0, 18598.0, 18599.0, 18600.0],       [18601.0, 18602.0, 18603.0, 18604.0, 18605.0, 18606.0]],      [[18607.0, 18608.0, 18609.0, 18610.0, 18611.0, 18612.0],       [18613.0, 18614.0, 18615.0, 18616.0, 18617.0, 18618.0],       [18619.0, 18620.0, 18621.0, 18622.0, 18623.0, 18624.0],       [18625.0, 18626.0, 18627.0, 18628.0, 18629.0, 18630.0],       [18631.0, 18632.0, 18633.0, 18634.0, 18635.0, 18636.0],       [18637.0, 18638.0, 18639.0, 18640.0, 18641.0, 18642.0],       [18643.0, 18644.0, 18645.0, 18646.0, 18647.0, 18648.0]]],     [[[18649.0, 18650.0, 18651.0, 18652.0, 18653.0, 18654.0],       [18655.0, 18656.0, 18657.0, 18658.0, 18659.0, 18660.0],       [18661.0, 18662.0, 18663.0, 18664.0, 18665.0, 18666.0],       [18667.0, 18668.0, 18669.0, 18670.0, 18671.0, 18672.0],       [18673.0, 18674.0, 18675.0, 18676.0, 18677.0, 18678.0],       [18679.0, 18680.0, 18681.0, 18682.0, 18683.0, 18684.0],       [18685.0, 18686.0, 18687.0, 18688.0, 18689.0, 18690.0]],      [[18691.0, 18692.0, 18693.0, 18694.0, 18695.0, 18696.0],       [18697.0, 18698.0, 18699.0, 18700.0, 18701.0, 18702.0],       [18703.0, 18704.0, 18705.0, 18706.0, 18707.0, 18708.0],       [18709.0, 18710.0, 18711.0, 18712.0, 18713.0, 18714.0],       [18715.0, 18716.0, 18717.0, 18718.0, 18719.0, 18720.0],       [18721.0, 18722.0, 18723.0, 18724.0, 18725.0, 18726.0],       [18727.0, 18728.0, 18729.0, 18730.0, 18731.0, 18732.0]],      [[18733.0, 18734.0, 18735.0, 18736.0, 18737.0, 18738.0],       [18739.0, 18740.0, 18741.0, 18742.0, 18743.0, 18744.0],       [18745.0, 18746.0, 18747.0, 18748.0, 18749.0, 18750.0],       [18751.0, 18752.0, 18753.0, 18754.0, 18755.0, 18756.0],       [18757.0, 18758.0, 18759.0, 18760.0, 18761.0, 18762.0],       [18763.0, 18764.0, 18765.0, 18766.0, 18767.0, 18768.0],       [18769.0, 18770.0, 18771.0, 18772.0, 18773.0, 18774.0]],      [[18775.0, 18776.0, 18777.0, 18778.0, 18779.0, 18780.0],       [18781.0, 18782.0, 18783.0, 18784.0, 18785.0, 18786.0],       [18787.0, 18788.0, 18789.0, 18790.0, 18791.0, 18792.0],       [18793.0, 18794.0, 18795.0, 18796.0, 18797.0, 18798.0],       [18799.0, 18800.0, 18801.0, 18802.0, 18803.0, 18804.0],       [18805.0, 18806.0, 18807.0, 18808.0, 18809.0, 18810.0],       [18811.0, 18812.0, 18813.0, 18814.0, 18815.0, 18816.0]],      [[18817.0, 18818.0, 18819.0, 18820.0, 18821.0, 18822.0],       [18823.0, 18824.0, 18825.0, 18826.0, 18827.0, 18828.0],       [18829.0, 18830.0, 18831.0, 18832.0, 18833.0, 18834.0],       [18835.0, 18836.0, 18837.0, 18838.0, 18839.0, 18840.0],       [18841.0, 18842.0, 18843.0, 18844.0, 18845.0, 18846.0],       [18847.0, 18848.0, 18849.0, 18850.0, 18851.0, 18852.0],       [18853.0, 18854.0, 18855.0, 18856.0, 18857.0, 18858.0]],      [[18859.0, 18860.0, 18861.0, 18862.0, 18863.0, 18864.0],       [18865.0, 18866.0, 18867.0, 18868.0, 18869.0, 18870.0],       [18871.0, 18872.0, 18873.0, 18874.0, 18875.0, 18876.0],       [18877.0, 18878.0, 18879.0, 18880.0, 18881.0, 18882.0],       [18883.0, 18884.0, 18885.0, 18886.0, 18887.0, 18888.0],       [18889.0, 18890.0, 18891.0, 18892.0, 18893.0, 18894.0],       [18895.0, 18896.0, 18897.0, 18898.0, 18899.0, 18900.0]]],     [[[18901.0, 18902.0, 18903.0, 18904.0, 18905.0, 18906.0],       [18907.0, 18908.0, 18909.0, 18910.0, 18911.0, 18912.0],       [18913.0, 18914.0, 18915.0, 18916.0, 18917.0, 18918.0],       [18919.0, 18920.0, 18921.0, 18922.0, 18923.0, 18924.0],       [18925.0, 18926.0, 18927.0, 18928.0, 18929.0, 18930.0],       [18931.0, 18932.0, 18933.0, 18934.0, 18935.0, 18936.0],       [18937.0, 18938.0, 18939.0, 18940.0, 18941.0, 18942.0]],      [[18943.0, 18944.0, 18945.0, 18946.0, 18947.0, 18948.0],       [18949.0, 18950.0, 18951.0, 18952.0, 18953.0, 18954.0],       [18955.0, 18956.0, 18957.0, 18958.0, 18959.0, 18960.0],       [18961.0, 18962.0, 18963.0, 18964.0, 18965.0, 18966.0],       [18967.0, 18968.0, 18969.0, 18970.0, 18971.0, 18972.0],       [18973.0, 18974.0, 18975.0, 18976.0, 18977.0, 18978.0],       [18979.0, 18980.0, 18981.0, 18982.0, 18983.0, 18984.0]],      [[18985.0, 18986.0, 18987.0, 18988.0, 18989.0, 18990.0],       [18991.0, 18992.0, 18993.0, 18994.0, 18995.0, 18996.0],       [18997.0, 18998.0, 18999.0, 19000.0, 19001.0, 19002.0],       [19003.0, 19004.0, 19005.0, 19006.0, 19007.0, 19008.0],       [19009.0, 19010.0, 19011.0, 19012.0, 19013.0, 19014.0],       [19015.0, 19016.0, 19017.0, 19018.0, 19019.0, 19020.0],       [19021.0, 19022.0, 19023.0, 19024.0, 19025.0, 19026.0]],      [[19027.0, 19028.0, 19029.0, 19030.0, 19031.0, 19032.0],       [19033.0, 19034.0, 19035.0, 19036.0, 19037.0, 19038.0],       [19039.0, 19040.0, 19041.0, 19042.0, 19043.0, 19044.0],       [19045.0, 19046.0, 19047.0, 19048.0, 19049.0, 19050.0],       [19051.0, 19052.0, 19053.0, 19054.0, 19055.0, 19056.0],       [19057.0, 19058.0, 19059.0, 19060.0, 19061.0, 19062.0],       [19063.0, 19064.0, 19065.0, 19066.0, 19067.0, 19068.0]],      [[19069.0, 19070.0, 19071.0, 19072.0, 19073.0, 19074.0],       [19075.0, 19076.0, 19077.0, 19078.0, 19079.0, 19080.0],       [19081.0, 19082.0, 19083.0, 19084.0, 19085.0, 19086.0],       [19087.0, 19088.0, 19089.0, 19090.0, 19091.0, 19092.0],       [19093.0, 19094.0, 19095.0, 19096.0, 19097.0, 19098.0],       [19099.0, 19100.0, 19101.0, 19102.0, 19103.0, 19104.0],       [19105.0, 19106.0, 19107.0, 19108.0, 19109.0, 19110.0]],      [[19111.0, 19112.0, 19113.0, 19114.0, 19115.0, 19116.0],       [19117.0, 19118.0, 19119.0, 19120.0, 19121.0, 19122.0],       [19123.0, 19124.0, 19125.0, 19126.0, 19127.0, 19128.0],       [19129.0, 19130.0, 19131.0, 19132.0, 19133.0, 19134.0],       [19135.0, 19136.0, 19137.0, 19138.0, 19139.0, 19140.0],       [19141.0, 19142.0, 19143.0, 19144.0, 19145.0, 19146.0],       [19147.0, 19148.0, 19149.0, 19150.0, 19151.0, 19152.0]]]],    [[[[19153.0, 19154.0, 19155.0, 19156.0, 19157.0, 19158.0],       [19159.0, 19160.0, 19161.0, 19162.0, 19163.0, 19164.0],       [19165.0, 19166.0, 19167.0, 19168.0, 19169.0, 19170.0],       [19171.0, 19172.0, 19173.0, 19174.0, 19175.0, 19176.0],       [19177.0, 19178.0, 19179.0, 19180.0, 19181.0, 19182.0],       [19183.0, 19184.0, 19185.0, 19186.0, 19187.0, 19188.0],       [19189.0, 19190.0, 19191.0, 19192.0, 19193.0, 19194.0]],      [[19195.0, 19196.0, 19197.0, 19198.0, 19199.0, 19200.0],       [19201.0, 19202.0, 19203.0, 19204.0, 19205.0, 19206.0],       [19207.0, 19208.0, 19209.0, 19210.0, 19211.0, 19212.0],       [19213.0, 19214.0, 19215.0, 19216.0, 19217.0, 19218.0],       [19219.0, 19220.0, 19221.0, 19222.0, 19223.0, 19224.0],       [19225.0, 19226.0, 19227.0, 19228.0, 19229.0, 19230.0],       [19231.0, 19232.0, 19233.0, 19234.0, 19235.0, 19236.0]],      [[19237.0, 19238.0, 19239.0, 19240.0, 19241.0, 19242.0],       [19243.0, 19244.0, 19245.0, 19246.0, 19247.0, 19248.0],       [19249.0, 19250.0, 19251.0, 19252.0, 19253.0, 19254.0],       [19255.0, 19256.0, 19257.0, 19258.0, 19259.0, 19260.0],       [19261.0, 19262.0, 19263.0, 19264.0, 19265.0, 19266.0],       [19267.0, 19268.0, 19269.0, 19270.0, 19271.0, 19272.0],       [19273.0, 19274.0, 19275.0, 19276.0, 19277.0, 19278.0]],      [[19279.0, 19280.0, 19281.0, 19282.0, 19283.0, 19284.0],       [19285.0, 19286.0, 19287.0, 19288.0, 19289.0, 19290.0],       [19291.0, 19292.0, 19293.0, 19294.0, 19295.0, 19296.0],       [19297.0, 19298.0, 19299.0, 19300.0, 19301.0, 19302.0],       [19303.0, 19304.0, 19305.0, 19306.0, 19307.0, 19308.0],       [19309.0, 19310.0, 19311.0, 19312.0, 19313.0, 19314.0],       [19315.0, 19316.0, 19317.0, 19318.0, 19319.0, 19320.0]],      [[19321.0, 19322.0, 19323.0, 19324.0, 19325.0, 19326.0],       [19327.0, 19328.0, 19329.0, 19330.0, 19331.0, 19332.0],       [19333.0, 19334.0, 19335.0, 19336.0, 19337.0, 19338.0],       [19339.0, 19340.0, 19341.0, 19342.0, 19343.0, 19344.0],       [19345.0, 19346.0, 19347.0, 19348.0, 19349.0, 19350.0],       [19351.0, 19352.0, 19353.0, 19354.0, 19355.0, 19356.0],       [19357.0, 19358.0, 19359.0, 19360.0, 19361.0, 19362.0]],      [[19363.0, 19364.0, 19365.0, 19366.0, 19367.0, 19368.0],       [19369.0, 19370.0, 19371.0, 19372.0, 19373.0, 19374.0],       [19375.0, 19376.0, 19377.0, 19378.0, 19379.0, 19380.0],       [19381.0, 19382.0, 19383.0, 19384.0, 19385.0, 19386.0],       [19387.0, 19388.0, 19389.0, 19390.0, 19391.0, 19392.0],       [19393.0, 19394.0, 19395.0, 19396.0, 19397.0, 19398.0],       [19399.0, 19400.0, 19401.0, 19402.0, 19403.0, 19404.0]]],     [[[19405.0, 19406.0, 19407.0, 19408.0, 19409.0, 19410.0],       [19411.0, 19412.0, 19413.0, 19414.0, 19415.0, 19416.0],       [19417.0, 19418.0, 19419.0, 19420.0, 19421.0, 19422.0],       [19423.0, 19424.0, 19425.0, 19426.0, 19427.0, 19428.0],       [19429.0, 19430.0, 19431.0, 19432.0, 19433.0, 19434.0],       [19435.0, 19436.0, 19437.0, 19438.0, 19439.0, 19440.0],       [19441.0, 19442.0, 19443.0, 19444.0, 19445.0, 19446.0]],      [[19447.0, 19448.0, 19449.0, 19450.0, 19451.0, 19452.0],       [19453.0, 19454.0, 19455.0, 19456.0, 19457.0, 19458.0],       [19459.0, 19460.0, 19461.0, 19462.0, 19463.0, 19464.0],       [19465.0, 19466.0, 19467.0, 19468.0, 19469.0, 19470.0],       [19471.0, 19472.0, 19473.0, 19474.0, 19475.0, 19476.0],       [19477.0, 19478.0, 19479.0, 19480.0, 19481.0, 19482.0],       [19483.0, 19484.0, 19485.0, 19486.0, 19487.0, 19488.0]],      [[19489.0, 19490.0, 19491.0, 19492.0, 19493.0, 19494.0],       [19495.0, 19496.0, 19497.0, 19498.0, 19499.0, 19500.0],       [19501.0, 19502.0, 19503.0, 19504.0, 19505.0, 19506.0],       [19507.0, 19508.0, 19509.0, 19510.0, 19511.0, 19512.0],       [19513.0, 19514.0, 19515.0, 19516.0, 19517.0, 19518.0],       [19519.0, 19520.0, 19521.0, 19522.0, 19523.0, 19524.0],       [19525.0, 19526.0, 19527.0, 19528.0, 19529.0, 19530.0]],      [[19531.0, 19532.0, 19533.0, 19534.0, 19535.0, 19536.0],       [19537.0, 19538.0, 19539.0, 19540.0, 19541.0, 19542.0],       [19543.0, 19544.0, 19545.0, 19546.0, 19547.0, 19548.0],       [19549.0, 19550.0, 19551.0, 19552.0, 19553.0, 19554.0],       [19555.0, 19556.0, 19557.0, 19558.0, 19559.0, 19560.0],       [19561.0, 19562.0, 19563.0, 19564.0, 19565.0, 19566.0],       [19567.0, 19568.0, 19569.0, 19570.0, 19571.0, 19572.0]],      [[19573.0, 19574.0, 19575.0, 19576.0, 19577.0, 19578.0],       [19579.0, 19580.0, 19581.0, 19582.0, 19583.0, 19584.0],       [19585.0, 19586.0, 19587.0, 19588.0, 19589.0, 19590.0],       [19591.0, 19592.0, 19593.0, 19594.0, 19595.0, 19596.0],       [19597.0, 19598.0, 19599.0, 19600.0, 19601.0, 19602.0],       [19603.0, 19604.0, 19605.0, 19606.0, 19607.0, 19608.0],       [19609.0, 19610.0, 19611.0, 19612.0, 19613.0, 19614.0]],      [[19615.0, 19616.0, 19617.0, 19618.0, 19619.0, 19620.0],       [19621.0, 19622.0, 19623.0, 19624.0, 19625.0, 19626.0],       [19627.0, 19628.0, 19629.0, 19630.0, 19631.0, 19632.0],       [19633.0, 19634.0, 19635.0, 19636.0, 19637.0, 19638.0],       [19639.0, 19640.0, 19641.0, 19642.0, 19643.0, 19644.0],       [19645.0, 19646.0, 19647.0, 19648.0, 19649.0, 19650.0],       [19651.0, 19652.0, 19653.0, 19654.0, 19655.0, 19656.0]]],     [[[19657.0, 19658.0, 19659.0, 19660.0, 19661.0, 19662.0],       [19663.0, 19664.0, 19665.0, 19666.0, 19667.0, 19668.0],       [19669.0, 19670.0, 19671.0, 19672.0, 19673.0, 19674.0],       [19675.0, 19676.0, 19677.0, 19678.0, 19679.0, 19680.0],       [19681.0, 19682.0, 19683.0, 19684.0, 19685.0, 19686.0],       [19687.0, 19688.0, 19689.0, 19690.0, 19691.0, 19692.0],       [19693.0, 19694.0, 19695.0, 19696.0, 19697.0, 19698.0]],      [[19699.0, 19700.0, 19701.0, 19702.0, 19703.0, 19704.0],       [19705.0, 19706.0, 19707.0, 19708.0, 19709.0, 19710.0],       [19711.0, 19712.0, 19713.0, 19714.0, 19715.0, 19716.0],       [19717.0, 19718.0, 19719.0, 19720.0, 19721.0, 19722.0],       [19723.0, 19724.0, 19725.0, 19726.0, 19727.0, 19728.0],       [19729.0, 19730.0, 19731.0, 19732.0, 19733.0, 19734.0],       [19735.0, 19736.0, 19737.0, 19738.0, 19739.0, 19740.0]],      [[19741.0, 19742.0, 19743.0, 19744.0, 19745.0, 19746.0],       [19747.0, 19748.0, 19749.0, 19750.0, 19751.0, 19752.0],       [19753.0, 19754.0, 19755.0, 19756.0, 19757.0, 19758.0],       [19759.0, 19760.0, 19761.0, 19762.0, 19763.0, 19764.0],       [19765.0, 19766.0, 19767.0, 19768.0, 19769.0, 19770.0],       [19771.0, 19772.0, 19773.0, 19774.0, 19775.0, 19776.0],       [19777.0, 19778.0, 19779.0, 19780.0, 19781.0, 19782.0]],      [[19783.0, 19784.0, 19785.0, 19786.0, 19787.0, 19788.0],       [19789.0, 19790.0, 19791.0, 19792.0, 19793.0, 19794.0],       [19795.0, 19796.0, 19797.0, 19798.0, 19799.0, 19800.0],       [19801.0, 19802.0, 19803.0, 19804.0, 19805.0, 19806.0],       [19807.0, 19808.0, 19809.0, 19810.0, 19811.0, 19812.0],       [19813.0, 19814.0, 19815.0, 19816.0, 19817.0, 19818.0],       [19819.0, 19820.0, 19821.0, 19822.0, 19823.0, 19824.0]],      [[19825.0, 19826.0, 19827.0, 19828.0, 19829.0, 19830.0],       [19831.0, 19832.0, 19833.0, 19834.0, 19835.0, 19836.0],       [19837.0, 19838.0, 19839.0, 19840.0, 19841.0, 19842.0],       [19843.0, 19844.0, 19845.0, 19846.0, 19847.0, 19848.0],       [19849.0, 19850.0, 19851.0, 19852.0, 19853.0, 19854.0],       [19855.0, 19856.0, 19857.0, 19858.0, 19859.0, 19860.0],       [19861.0, 19862.0, 19863.0, 19864.0, 19865.0, 19866.0]],      [[19867.0, 19868.0, 19869.0, 19870.0, 19871.0, 19872.0],       [19873.0, 19874.0, 19875.0, 19876.0, 19877.0, 19878.0],       [19879.0, 19880.0, 19881.0, 19882.0, 19883.0, 19884.0],       [19885.0, 19886.0, 19887.0, 19888.0, 19889.0, 19890.0],       [19891.0, 19892.0, 19893.0, 19894.0, 19895.0, 19896.0],       [19897.0, 19898.0, 19899.0, 19900.0, 19901.0, 19902.0],       [19903.0, 19904.0, 19905.0, 19906.0, 19907.0, 19908.0]]],     [[[19909.0, 19910.0, 19911.0, 19912.0, 19913.0, 19914.0],       [19915.0, 19916.0, 19917.0, 19918.0, 19919.0, 19920.0],       [19921.0, 19922.0, 19923.0, 19924.0, 19925.0, 19926.0],       [19927.0, 19928.0, 19929.0, 19930.0, 19931.0, 19932.0],       [19933.0, 19934.0, 19935.0, 19936.0, 19937.0, 19938.0],       [19939.0, 19940.0, 19941.0, 19942.0, 19943.0, 19944.0],       [19945.0, 19946.0, 19947.0, 19948.0, 19949.0, 19950.0]],      [[19951.0, 19952.0, 19953.0, 19954.0, 19955.0, 19956.0],       [19957.0, 19958.0, 19959.0, 19960.0, 19961.0, 19962.0],       [19963.0, 19964.0, 19965.0, 19966.0, 19967.0, 19968.0],       [19969.0, 19970.0, 19971.0, 19972.0, 19973.0, 19974.0],       [19975.0, 19976.0, 19977.0, 19978.0, 19979.0, 19980.0],       [19981.0, 19982.0, 19983.0, 19984.0, 19985.0, 19986.0],       [19987.0, 19988.0, 19989.0, 19990.0, 19991.0, 19992.0]],      [[19993.0, 19994.0, 19995.0, 19996.0, 19997.0, 19998.0],       [19999.0, 20000.0, 20001.0, 20002.0, 20003.0, 20004.0],       [20005.0, 20006.0, 20007.0, 20008.0, 20009.0, 20010.0],       [20011.0, 20012.0, 20013.0, 20014.0, 20015.0, 20016.0],       [20017.0, 20018.0, 20019.0, 20020.0, 20021.0, 20022.0],       [20023.0, 20024.0, 20025.0, 20026.0, 20027.0, 20028.0],       [20029.0, 20030.0, 20031.0, 20032.0, 20033.0, 20034.0]],      [[20035.0, 20036.0, 20037.0, 20038.0, 20039.0, 20040.0],       [20041.0, 20042.0, 20043.0, 20044.0, 20045.0, 20046.0],       [20047.0, 20048.0, 20049.0, 20050.0, 20051.0, 20052.0],       [20053.0, 20054.0, 20055.0, 20056.0, 20057.0, 20058.0],       [20059.0, 20060.0, 20061.0, 20062.0, 20063.0, 20064.0],       [20065.0, 20066.0, 20067.0, 20068.0, 20069.0, 20070.0],       [20071.0, 20072.0, 20073.0, 20074.0, 20075.0, 20076.0]],      [[20077.0, 20078.0, 20079.0, 20080.0, 20081.0, 20082.0],       [20083.0, 20084.0, 20085.0, 20086.0, 20087.0, 20088.0],       [20089.0, 20090.0, 20091.0, 20092.0, 20093.0, 20094.0],       [20095.0, 20096.0, 20097.0, 20098.0, 20099.0, 20100.0],       [20101.0, 20102.0, 20103.0, 20104.0, 20105.0, 20106.0],       [20107.0, 20108.0, 20109.0, 20110.0, 20111.0, 20112.0],       [20113.0, 20114.0, 20115.0, 20116.0, 20117.0, 20118.0]],      [[20119.0, 20120.0, 20121.0, 20122.0, 20123.0, 20124.0],       [20125.0, 20126.0, 20127.0, 20128.0, 20129.0, 20130.0],       [20131.0, 20132.0, 20133.0, 20134.0, 20135.0, 20136.0],       [20137.0, 20138.0, 20139.0, 20140.0, 20141.0, 20142.0],       [20143.0, 20144.0, 20145.0, 20146.0, 20147.0, 20148.0],       [20149.0, 20150.0, 20151.0, 20152.0, 20153.0, 20154.0],       [20155.0, 20156.0, 20157.0, 20158.0, 20159.0, 20160.0]]]],    [[[[20161.0, 20162.0, 20163.0, 20164.0, 20165.0, 20166.0],       [20167.0, 20168.0, 20169.0, 20170.0, 20171.0, 20172.0],       [20173.0, 20174.0, 20175.0, 20176.0, 20177.0, 20178.0],       [20179.0, 20180.0, 20181.0, 20182.0, 20183.0, 20184.0],       [20185.0, 20186.0, 20187.0, 20188.0, 20189.0, 20190.0],       [20191.0, 20192.0, 20193.0, 20194.0, 20195.0, 20196.0],       [20197.0, 20198.0, 20199.0, 20200.0, 20201.0, 20202.0]],      [[20203.0, 20204.0, 20205.0, 20206.0, 20207.0, 20208.0],       [20209.0, 20210.0, 20211.0, 20212.0, 20213.0, 20214.0],       [20215.0, 20216.0, 20217.0, 20218.0, 20219.0, 20220.0],       [20221.0, 20222.0, 20223.0, 20224.0, 20225.0, 20226.0],       [20227.0, 20228.0, 20229.0, 20230.0, 20231.0, 20232.0],       [20233.0, 20234.0, 20235.0, 20236.0, 20237.0, 20238.0],       [20239.0, 20240.0, 20241.0, 20242.0, 20243.0, 20244.0]],      [[20245.0, 20246.0, 20247.0, 20248.0, 20249.0, 20250.0],       [20251.0, 20252.0, 20253.0, 20254.0, 20255.0, 20256.0],       [20257.0, 20258.0, 20259.0, 20260.0, 20261.0, 20262.0],       [20263.0, 20264.0, 20265.0, 20266.0, 20267.0, 20268.0],       [20269.0, 20270.0, 20271.0, 20272.0, 20273.0, 20274.0],       [20275.0, 20276.0, 20277.0, 20278.0, 20279.0, 20280.0],       [20281.0, 20282.0, 20283.0, 20284.0, 20285.0, 20286.0]],      [[20287.0, 20288.0, 20289.0, 20290.0, 20291.0, 20292.0],       [20293.0, 20294.0, 20295.0, 20296.0, 20297.0, 20298.0],       [20299.0, 20300.0, 20301.0, 20302.0, 20303.0, 20304.0],       [20305.0, 20306.0, 20307.0, 20308.0, 20309.0, 20310.0],       [20311.0, 20312.0, 20313.0, 20314.0, 20315.0, 20316.0],       [20317.0, 20318.0, 20319.0, 20320.0, 20321.0, 20322.0],       [20323.0, 20324.0, 20325.0, 20326.0, 20327.0, 20328.0]],      [[20329.0, 20330.0, 20331.0, 20332.0, 20333.0, 20334.0],       [20335.0, 20336.0, 20337.0, 20338.0, 20339.0, 20340.0],       [20341.0, 20342.0, 20343.0, 20344.0, 20345.0, 20346.0],       [20347.0, 20348.0, 20349.0, 20350.0, 20351.0, 20352.0],       [20353.0, 20354.0, 20355.0, 20356.0, 20357.0, 20358.0],       [20359.0, 20360.0, 20361.0, 20362.0, 20363.0, 20364.0],       [20365.0, 20366.0, 20367.0, 20368.0, 20369.0, 20370.0]],      [[20371.0, 20372.0, 20373.0, 20374.0, 20375.0, 20376.0],       [20377.0, 20378.0, 20379.0, 20380.0, 20381.0, 20382.0],       [20383.0, 20384.0, 20385.0, 20386.0, 20387.0, 20388.0],       [20389.0, 20390.0, 20391.0, 20392.0, 20393.0, 20394.0],       [20395.0, 20396.0, 20397.0, 20398.0, 20399.0, 20400.0],       [20401.0, 20402.0, 20403.0, 20404.0, 20405.0, 20406.0],       [20407.0, 20408.0, 20409.0, 20410.0, 20411.0, 20412.0]]],     [[[20413.0, 20414.0, 20415.0, 20416.0, 20417.0, 20418.0],       [20419.0, 20420.0, 20421.0, 20422.0, 20423.0, 20424.0],       [20425.0, 20426.0, 20427.0, 20428.0, 20429.0, 20430.0],       [20431.0, 20432.0, 20433.0, 20434.0, 20435.0, 20436.0],       [20437.0, 20438.0, 20439.0, 20440.0, 20441.0, 20442.0],       [20443.0, 20444.0, 20445.0, 20446.0, 20447.0, 20448.0],       [20449.0, 20450.0, 20451.0, 20452.0, 20453.0, 20454.0]],      [[20455.0, 20456.0, 20457.0, 20458.0, 20459.0, 20460.0],       [20461.0, 20462.0, 20463.0, 20464.0, 20465.0, 20466.0],       [20467.0, 20468.0, 20469.0, 20470.0, 20471.0, 20472.0],       [20473.0, 20474.0, 20475.0, 20476.0, 20477.0, 20478.0],       [20479.0, 20480.0, 20481.0, 20482.0, 20483.0, 20484.0],       [20485.0, 20486.0, 20487.0, 20488.0, 20489.0, 20490.0],       [20491.0, 20492.0, 20493.0, 20494.0, 20495.0, 20496.0]],      [[20497.0, 20498.0, 20499.0, 20500.0, 20501.0, 20502.0],       [20503.0, 20504.0, 20505.0, 20506.0, 20507.0, 20508.0],       [20509.0, 20510.0, 20511.0, 20512.0, 20513.0, 20514.0],       [20515.0, 20516.0, 20517.0, 20518.0, 20519.0, 20520.0],       [20521.0, 20522.0, 20523.0, 20524.0, 20525.0, 20526.0],       [20527.0, 20528.0, 20529.0, 20530.0, 20531.0, 20532.0],       [20533.0, 20534.0, 20535.0, 20536.0, 20537.0, 20538.0]],      [[20539.0, 20540.0, 20541.0, 20542.0, 20543.0, 20544.0],       [20545.0, 20546.0, 20547.0, 20548.0, 20549.0, 20550.0],       [20551.0, 20552.0, 20553.0, 20554.0, 20555.0, 20556.0],       [20557.0, 20558.0, 20559.0, 20560.0, 20561.0, 20562.0],       [20563.0, 20564.0, 20565.0, 20566.0, 20567.0, 20568.0],       [20569.0, 20570.0, 20571.0, 20572.0, 20573.0, 20574.0],       [20575.0, 20576.0, 20577.0, 20578.0, 20579.0, 20580.0]],      [[20581.0, 20582.0, 20583.0, 20584.0, 20585.0, 20586.0],       [20587.0, 20588.0, 20589.0, 20590.0, 20591.0, 20592.0],       [20593.0, 20594.0, 20595.0, 20596.0, 20597.0, 20598.0],       [20599.0, 20600.0, 20601.0, 20602.0, 20603.0, 20604.0],       [20605.0, 20606.0, 20607.0, 20608.0, 20609.0, 20610.0],       [20611.0, 20612.0, 20613.0, 20614.0, 20615.0, 20616.0],       [20617.0, 20618.0, 20619.0, 20620.0, 20621.0, 20622.0]],      [[20623.0, 20624.0, 20625.0, 20626.0, 20627.0, 20628.0],       [20629.0, 20630.0, 20631.0, 20632.0, 20633.0, 20634.0],       [20635.0, 20636.0, 20637.0, 20638.0, 20639.0, 20640.0],       [20641.0, 20642.0, 20643.0, 20644.0, 20645.0, 20646.0],       [20647.0, 20648.0, 20649.0, 20650.0, 20651.0, 20652.0],       [20653.0, 20654.0, 20655.0, 20656.0, 20657.0, 20658.0],       [20659.0, 20660.0, 20661.0, 20662.0, 20663.0, 20664.0]]],     [[[20665.0, 20666.0, 20667.0, 20668.0, 20669.0, 20670.0],       [20671.0, 20672.0, 20673.0, 20674.0, 20675.0, 20676.0],       [20677.0, 20678.0, 20679.0, 20680.0, 20681.0, 20682.0],       [20683.0, 20684.0, 20685.0, 20686.0, 20687.0, 20688.0],       [20689.0, 20690.0, 20691.0, 20692.0, 20693.0, 20694.0],       [20695.0, 20696.0, 20697.0, 20698.0, 20699.0, 20700.0],       [20701.0, 20702.0, 20703.0, 20704.0, 20705.0, 20706.0]],      [[20707.0, 20708.0, 20709.0, 20710.0, 20711.0, 20712.0],       [20713.0, 20714.0, 20715.0, 20716.0, 20717.0, 20718.0],       [20719.0, 20720.0, 20721.0, 20722.0, 20723.0, 20724.0],       [20725.0, 20726.0, 20727.0, 20728.0, 20729.0, 20730.0],       [20731.0, 20732.0, 20733.0, 20734.0, 20735.0, 20736.0],       [20737.0, 20738.0, 20739.0, 20740.0, 20741.0, 20742.0],       [20743.0, 20744.0, 20745.0, 20746.0, 20747.0, 20748.0]],      [[20749.0, 20750.0, 20751.0, 20752.0, 20753.0, 20754.0],       [20755.0, 20756.0, 20757.0, 20758.0, 20759.0, 20760.0],       [20761.0, 20762.0, 20763.0, 20764.0, 20765.0, 20766.0],       [20767.0, 20768.0, 20769.0, 20770.0, 20771.0, 20772.0],       [20773.0, 20774.0, 20775.0, 20776.0, 20777.0, 20778.0],       [20779.0, 20780.0, 20781.0, 20782.0, 20783.0, 20784.0],       [20785.0, 20786.0, 20787.0, 20788.0, 20789.0, 20790.0]],      [[20791.0, 20792.0, 20793.0, 20794.0, 20795.0, 20796.0],       [20797.0, 20798.0, 20799.0, 20800.0, 20801.0, 20802.0],       [20803.0, 20804.0, 20805.0, 20806.0, 20807.0, 20808.0],       [20809.0, 20810.0, 20811.0, 20812.0, 20813.0, 20814.0],       [20815.0, 20816.0, 20817.0, 20818.0, 20819.0, 20820.0],       [20821.0, 20822.0, 20823.0, 20824.0, 20825.0, 20826.0],       [20827.0, 20828.0, 20829.0, 20830.0, 20831.0, 20832.0]],      [[20833.0, 20834.0, 20835.0, 20836.0, 20837.0, 20838.0],       [20839.0, 20840.0, 20841.0, 20842.0, 20843.0, 20844.0],       [20845.0, 20846.0, 20847.0, 20848.0, 20849.0, 20850.0],       [20851.0, 20852.0, 20853.0, 20854.0, 20855.0, 20856.0],       [20857.0, 20858.0, 20859.0, 20860.0, 20861.0, 20862.0],       [20863.0, 20864.0, 20865.0, 20866.0, 20867.0, 20868.0],       [20869.0, 20870.0, 20871.0, 20872.0, 20873.0, 20874.0]],      [[20875.0, 20876.0, 20877.0, 20878.0, 20879.0, 20880.0],       [20881.0, 20882.0, 20883.0, 20884.0, 20885.0, 20886.0],       [20887.0, 20888.0, 20889.0, 20890.0, 20891.0, 20892.0],       [20893.0, 20894.0, 20895.0, 20896.0, 20897.0, 20898.0],       [20899.0, 20900.0, 20901.0, 20902.0, 20903.0, 20904.0],       [20905.0, 20906.0, 20907.0, 20908.0, 20909.0, 20910.0],       [20911.0, 20912.0, 20913.0, 20914.0, 20915.0, 20916.0]]],     [[[20917.0, 20918.0, 20919.0, 20920.0, 20921.0, 20922.0],       [20923.0, 20924.0, 20925.0, 20926.0, 20927.0, 20928.0],       [20929.0, 20930.0, 20931.0, 20932.0, 20933.0, 20934.0],       [20935.0, 20936.0, 20937.0, 20938.0, 20939.0, 20940.0],       [20941.0, 20942.0, 20943.0, 20944.0, 20945.0, 20946.0],       [20947.0, 20948.0, 20949.0, 20950.0, 20951.0, 20952.0],       [20953.0, 20954.0, 20955.0, 20956.0, 20957.0, 20958.0]],      [[20959.0, 20960.0, 20961.0, 20962.0, 20963.0, 20964.0],       [20965.0, 20966.0, 20967.0, 20968.0, 20969.0, 20970.0],       [20971.0, 20972.0, 20973.0, 20974.0, 20975.0, 20976.0],       [20977.0, 20978.0, 20979.0, 20980.0, 20981.0, 20982.0],       [20983.0, 20984.0, 20985.0, 20986.0, 20987.0, 20988.0],       [20989.0, 20990.0, 20991.0, 20992.0, 20993.0, 20994.0],       [20995.0, 20996.0, 20997.0, 20998.0, 20999.0, 21000.0]],      [[21001.0, 21002.0, 21003.0, 21004.0, 21005.0, 21006.0],       [21007.0, 21008.0, 21009.0, 21010.0, 21011.0, 21012.0],       [21013.0, 21014.0, 21015.0, 21016.0, 21017.0, 21018.0],       [21019.0, 21020.0, 21021.0, 21022.0, 21023.0, 21024.0],       [21025.0, 21026.0, 21027.0, 21028.0, 21029.0, 21030.0],       [21031.0, 21032.0, 21033.0, 21034.0, 21035.0, 21036.0],       [21037.0, 21038.0, 21039.0, 21040.0, 21041.0, 21042.0]],      [[21043.0, 21044.0, 21045.0, 21046.0, 21047.0, 21048.0],       [21049.0, 21050.0, 21051.0, 21052.0, 21053.0, 21054.0],       [21055.0, 21056.0, 21057.0, 21058.0, 21059.0, 21060.0],       [21061.0, 21062.0, 21063.0, 21064.0, 21065.0, 21066.0],       [21067.0, 21068.0, 21069.0, 21070.0, 21071.0, 21072.0],       [21073.0, 21074.0, 21075.0, 21076.0, 21077.0, 21078.0],       [21079.0, 21080.0, 21081.0, 21082.0, 21083.0, 21084.0]],      [[21085.0, 21086.0, 21087.0, 21088.0, 21089.0, 21090.0],       [21091.0, 21092.0, 21093.0, 21094.0, 21095.0, 21096.0],       [21097.0, 21098.0, 21099.0, 21100.0, 21101.0, 21102.0],       [21103.0, 21104.0, 21105.0, 21106.0, 21107.0, 21108.0],       [21109.0, 21110.0, 21111.0, 21112.0, 21113.0, 21114.0],       [21115.0, 21116.0, 21117.0, 21118.0, 21119.0, 21120.0],       [21121.0, 21122.0, 21123.0, 21124.0, 21125.0, 21126.0]],      [[21127.0, 21128.0, 21129.0, 21130.0, 21131.0, 21132.0],       [21133.0, 21134.0, 21135.0, 21136.0, 21137.0, 21138.0],       [21139.0, 21140.0, 21141.0, 21142.0, 21143.0, 21144.0],       [21145.0, 21146.0, 21147.0, 21148.0, 21149.0, 21150.0],       [21151.0, 21152.0, 21153.0, 21154.0, 21155.0, 21156.0],       [21157.0, 21158.0, 21159.0, 21160.0, 21161.0, 21162.0],       [21163.0, 21164.0, 21165.0, 21166.0, 21167.0, 21168.0]]]],    [[[[21169.0, 21170.0, 21171.0, 21172.0, 21173.0, 21174.0],       [21175.0, 21176.0, 21177.0, 21178.0, 21179.0, 21180.0],       [21181.0, 21182.0, 21183.0, 21184.0, 21185.0, 21186.0],       [21187.0, 21188.0, 21189.0, 21190.0, 21191.0, 21192.0],       [21193.0, 21194.0, 21195.0, 21196.0, 21197.0, 21198.0],       [21199.0, 21200.0, 21201.0, 21202.0, 21203.0, 21204.0],       [21205.0, 21206.0, 21207.0, 21208.0, 21209.0, 21210.0]],      [[21211.0, 21212.0, 21213.0, 21214.0, 21215.0, 21216.0],       [21217.0, 21218.0, 21219.0, 21220.0, 21221.0, 21222.0],       [21223.0, 21224.0, 21225.0, 21226.0, 21227.0, 21228.0],       [21229.0, 21230.0, 21231.0, 21232.0, 21233.0, 21234.0],       [21235.0, 21236.0, 21237.0, 21238.0, 21239.0, 21240.0],       [21241.0, 21242.0, 21243.0, 21244.0, 21245.0, 21246.0],       [21247.0, 21248.0, 21249.0, 21250.0, 21251.0, 21252.0]],      [[21253.0, 21254.0, 21255.0, 21256.0, 21257.0, 21258.0],       [21259.0, 21260.0, 21261.0, 21262.0, 21263.0, 21264.0],       [21265.0, 21266.0, 21267.0, 21268.0, 21269.0, 21270.0],       [21271.0, 21272.0, 21273.0, 21274.0, 21275.0, 21276.0],       [21277.0, 21278.0, 21279.0, 21280.0, 21281.0, 21282.0],       [21283.0, 21284.0, 21285.0, 21286.0, 21287.0, 21288.0],       [21289.0, 21290.0, 21291.0, 21292.0, 21293.0, 21294.0]],      [[21295.0, 21296.0, 21297.0, 21298.0, 21299.0, 21300.0],       [21301.0, 21302.0, 21303.0, 21304.0, 21305.0, 21306.0],       [21307.0, 21308.0, 21309.0, 21310.0, 21311.0, 21312.0],       [21313.0, 21314.0, 21315.0, 21316.0, 21317.0, 21318.0],       [21319.0, 21320.0, 21321.0, 21322.0, 21323.0, 21324.0],       [21325.0, 21326.0, 21327.0, 21328.0, 21329.0, 21330.0],       [21331.0, 21332.0, 21333.0, 21334.0, 21335.0, 21336.0]],      [[21337.0, 21338.0, 21339.0, 21340.0, 21341.0, 21342.0],       [21343.0, 21344.0, 21345.0, 21346.0, 21347.0, 21348.0],       [21349.0, 21350.0, 21351.0, 21352.0, 21353.0, 21354.0],       [21355.0, 21356.0, 21357.0, 21358.0, 21359.0, 21360.0],       [21361.0, 21362.0, 21363.0, 21364.0, 21365.0, 21366.0],       [21367.0, 21368.0, 21369.0, 21370.0, 21371.0, 21372.0],       [21373.0, 21374.0, 21375.0, 21376.0, 21377.0, 21378.0]],      [[21379.0, 21380.0, 21381.0, 21382.0, 21383.0, 21384.0],       [21385.0, 21386.0, 21387.0, 21388.0, 21389.0, 21390.0],       [21391.0, 21392.0, 21393.0, 21394.0, 21395.0, 21396.0],       [21397.0, 21398.0, 21399.0, 21400.0, 21401.0, 21402.0],       [21403.0, 21404.0, 21405.0, 21406.0, 21407.0, 21408.0],       [21409.0, 21410.0, 21411.0, 21412.0, 21413.0, 21414.0],       [21415.0, 21416.0, 21417.0, 21418.0, 21419.0, 21420.0]]],     [[[21421.0, 21422.0, 21423.0, 21424.0, 21425.0, 21426.0],       [21427.0, 21428.0, 21429.0, 21430.0, 21431.0, 21432.0],       [21433.0, 21434.0, 21435.0, 21436.0, 21437.0, 21438.0],       [21439.0, 21440.0, 21441.0, 21442.0, 21443.0, 21444.0],       [21445.0, 21446.0, 21447.0, 21448.0, 21449.0, 21450.0],       [21451.0, 21452.0, 21453.0, 21454.0, 21455.0, 21456.0],       [21457.0, 21458.0, 21459.0, 21460.0, 21461.0, 21462.0]],      [[21463.0, 21464.0, 21465.0, 21466.0, 21467.0, 21468.0],       [21469.0, 21470.0, 21471.0, 21472.0, 21473.0, 21474.0],       [21475.0, 21476.0, 21477.0, 21478.0, 21479.0, 21480.0],       [21481.0, 21482.0, 21483.0, 21484.0, 21485.0, 21486.0],       [21487.0, 21488.0, 21489.0, 21490.0, 21491.0, 21492.0],       [21493.0, 21494.0, 21495.0, 21496.0, 21497.0, 21498.0],       [21499.0, 21500.0, 21501.0, 21502.0, 21503.0, 21504.0]],      [[21505.0, 21506.0, 21507.0, 21508.0, 21509.0, 21510.0],       [21511.0, 21512.0, 21513.0, 21514.0, 21515.0, 21516.0],       [21517.0, 21518.0, 21519.0, 21520.0, 21521.0, 21522.0],       [21523.0, 21524.0, 21525.0, 21526.0, 21527.0, 21528.0],       [21529.0, 21530.0, 21531.0, 21532.0, 21533.0, 21534.0],       [21535.0, 21536.0, 21537.0, 21538.0, 21539.0, 21540.0],       [21541.0, 21542.0, 21543.0, 21544.0, 21545.0, 21546.0]],      [[21547.0, 21548.0, 21549.0, 21550.0, 21551.0, 21552.0],       [21553.0, 21554.0, 21555.0, 21556.0, 21557.0, 21558.0],       [21559.0, 21560.0, 21561.0, 21562.0, 21563.0, 21564.0],       [21565.0, 21566.0, 21567.0, 21568.0, 21569.0, 21570.0],       [21571.0, 21572.0, 21573.0, 21574.0, 21575.0, 21576.0],       [21577.0, 21578.0, 21579.0, 21580.0, 21581.0, 21582.0],       [21583.0, 21584.0, 21585.0, 21586.0, 21587.0, 21588.0]],      [[21589.0, 21590.0, 21591.0, 21592.0, 21593.0, 21594.0],       [21595.0, 21596.0, 21597.0, 21598.0, 21599.0, 21600.0],       [21601.0, 21602.0, 21603.0, 21604.0, 21605.0, 21606.0],       [21607.0, 21608.0, 21609.0, 21610.0, 21611.0, 21612.0],       [21613.0, 21614.0, 21615.0, 21616.0, 21617.0, 21618.0],       [21619.0, 21620.0, 21621.0, 21622.0, 21623.0, 21624.0],       [21625.0, 21626.0, 21627.0, 21628.0, 21629.0, 21630.0]],      [[21631.0, 21632.0, 21633.0, 21634.0, 21635.0, 21636.0],       [21637.0, 21638.0, 21639.0, 21640.0, 21641.0, 21642.0],       [21643.0, 21644.0, 21645.0, 21646.0, 21647.0, 21648.0],       [21649.0, 21650.0, 21651.0, 21652.0, 21653.0, 21654.0],       [21655.0, 21656.0, 21657.0, 21658.0, 21659.0, 21660.0],       [21661.0, 21662.0, 21663.0, 21664.0, 21665.0, 21666.0],       [21667.0, 21668.0, 21669.0, 21670.0, 21671.0, 21672.0]]],     [[[21673.0, 21674.0, 21675.0, 21676.0, 21677.0, 21678.0],       [21679.0, 21680.0, 21681.0, 21682.0, 21683.0, 21684.0],       [21685.0, 21686.0, 21687.0, 21688.0, 21689.0, 21690.0],       [21691.0, 21692.0, 21693.0, 21694.0, 21695.0, 21696.0],       [21697.0, 21698.0, 21699.0, 21700.0, 21701.0, 21702.0],       [21703.0, 21704.0, 21705.0, 21706.0, 21707.0, 21708.0],       [21709.0, 21710.0, 21711.0, 21712.0, 21713.0, 21714.0]],      [[21715.0, 21716.0, 21717.0, 21718.0, 21719.0, 21720.0],       [21721.0, 21722.0, 21723.0, 21724.0, 21725.0, 21726.0],       [21727.0, 21728.0, 21729.0, 21730.0, 21731.0, 21732.0],       [21733.0, 21734.0, 21735.0, 21736.0, 21737.0, 21738.0],       [21739.0, 21740.0, 21741.0, 21742.0, 21743.0, 21744.0],       [21745.0, 21746.0, 21747.0, 21748.0, 21749.0, 21750.0],       [21751.0, 21752.0, 21753.0, 21754.0, 21755.0, 21756.0]],      [[21757.0, 21758.0, 21759.0, 21760.0, 21761.0, 21762.0],       [21763.0, 21764.0, 21765.0, 21766.0, 21767.0, 21768.0],       [21769.0, 21770.0, 21771.0, 21772.0, 21773.0, 21774.0],       [21775.0, 21776.0, 21777.0, 21778.0, 21779.0, 21780.0],       [21781.0, 21782.0, 21783.0, 21784.0, 21785.0, 21786.0],       [21787.0, 21788.0, 21789.0, 21790.0, 21791.0, 21792.0],       [21793.0, 21794.0, 21795.0, 21796.0, 21797.0, 21798.0]],      [[21799.0, 21800.0, 21801.0, 21802.0, 21803.0, 21804.0],       [21805.0, 21806.0, 21807.0, 21808.0, 21809.0, 21810.0],       [21811.0, 21812.0, 21813.0, 21814.0, 21815.0, 21816.0],       [21817.0, 21818.0, 21819.0, 21820.0, 21821.0, 21822.0],       [21823.0, 21824.0, 21825.0, 21826.0, 21827.0, 21828.0],       [21829.0, 21830.0, 21831.0, 21832.0, 21833.0, 21834.0],       [21835.0, 21836.0, 21837.0, 21838.0, 21839.0, 21840.0]],      [[21841.0, 21842.0, 21843.0, 21844.0, 21845.0, 21846.0],       [21847.0, 21848.0, 21849.0, 21850.0, 21851.0, 21852.0],       [21853.0, 21854.0, 21855.0, 21856.0, 21857.0, 21858.0],       [21859.0, 21860.0, 21861.0, 21862.0, 21863.0, 21864.0],       [21865.0, 21866.0, 21867.0, 21868.0, 21869.0, 21870.0],       [21871.0, 21872.0, 21873.0, 21874.0, 21875.0, 21876.0],       [21877.0, 21878.0, 21879.0, 21880.0, 21881.0, 21882.0]],      [[21883.0, 21884.0, 21885.0, 21886.0, 21887.0, 21888.0],       [21889.0, 21890.0, 21891.0, 21892.0, 21893.0, 21894.0],       [21895.0, 21896.0, 21897.0, 21898.0, 21899.0, 21900.0],       [21901.0, 21902.0, 21903.0, 21904.0, 21905.0, 21906.0],       [21907.0, 21908.0, 21909.0, 21910.0, 21911.0, 21912.0],       [21913.0, 21914.0, 21915.0, 21916.0, 21917.0, 21918.0],       [21919.0, 21920.0, 21921.0, 21922.0, 21923.0, 21924.0]]],     [[[21925.0, 21926.0, 21927.0, 21928.0, 21929.0, 21930.0],       [21931.0, 21932.0, 21933.0, 21934.0, 21935.0, 21936.0],       [21937.0, 21938.0, 21939.0, 21940.0, 21941.0, 21942.0],       [21943.0, 21944.0, 21945.0, 21946.0, 21947.0, 21948.0],       [21949.0, 21950.0, 21951.0, 21952.0, 21953.0, 21954.0],       [21955.0, 21956.0, 21957.0, 21958.0, 21959.0, 21960.0],       [21961.0, 21962.0, 21963.0, 21964.0, 21965.0, 21966.0]],      [[21967.0, 21968.0, 21969.0, 21970.0, 21971.0, 21972.0],       [21973.0, 21974.0, 21975.0, 21976.0, 21977.0, 21978.0],       [21979.0, 21980.0, 21981.0, 21982.0, 21983.0, 21984.0],       [21985.0, 21986.0, 21987.0, 21988.0, 21989.0, 21990.0],       [21991.0, 21992.0, 21993.0, 21994.0, 21995.0, 21996.0],       [21997.0, 21998.0, 21999.0, 22000.0, 22001.0, 22002.0],       [22003.0, 22004.0, 22005.0, 22006.0, 22007.0, 22008.0]],      [[22009.0, 22010.0, 22011.0, 22012.0, 22013.0, 22014.0],       [22015.0, 22016.0, 22017.0, 22018.0, 22019.0, 22020.0],       [22021.0, 22022.0, 22023.0, 22024.0, 22025.0, 22026.0],       [22027.0, 22028.0, 22029.0, 22030.0, 22031.0, 22032.0],       [22033.0, 22034.0, 22035.0, 22036.0, 22037.0, 22038.0],       [22039.0, 22040.0, 22041.0, 22042.0, 22043.0, 22044.0],       [22045.0, 22046.0, 22047.0, 22048.0, 22049.0, 22050.0]],      [[22051.0, 22052.0, 22053.0, 22054.0, 22055.0, 22056.0],       [22057.0, 22058.0, 22059.0, 22060.0, 22061.0, 22062.0],       [22063.0, 22064.0, 22065.0, 22066.0, 22067.0, 22068.0],       [22069.0, 22070.0, 22071.0, 22072.0, 22073.0, 22074.0],       [22075.0, 22076.0, 22077.0, 22078.0, 22079.0, 22080.0],       [22081.0, 22082.0, 22083.0, 22084.0, 22085.0, 22086.0],       [22087.0, 22088.0, 22089.0, 22090.0, 22091.0, 22092.0]],      [[22093.0, 22094.0, 22095.0, 22096.0, 22097.0, 22098.0],       [22099.0, 22100.0, 22101.0, 22102.0, 22103.0, 22104.0],       [22105.0, 22106.0, 22107.0, 22108.0, 22109.0, 22110.0],       [22111.0, 22112.0, 22113.0, 22114.0, 22115.0, 22116.0],       [22117.0, 22118.0, 22119.0, 22120.0, 22121.0, 22122.0],       [22123.0, 22124.0, 22125.0, 22126.0, 22127.0, 22128.0],       [22129.0, 22130.0, 22131.0, 22132.0, 22133.0, 22134.0]],      [[22135.0, 22136.0, 22137.0, 22138.0, 22139.0, 22140.0],       [22141.0, 22142.0, 22143.0, 22144.0, 22145.0, 22146.0],       [22147.0, 22148.0, 22149.0, 22150.0, 22151.0, 22152.0],       [22153.0, 22154.0, 22155.0, 22156.0, 22157.0, 22158.0],       [22159.0, 22160.0, 22161.0, 22162.0, 22163.0, 22164.0],       [22165.0, 22166.0, 22167.0, 22168.0, 22169.0, 22170.0],       [22171.0, 22172.0, 22173.0, 22174.0, 22175.0, 22176.0]]]],    [[[[22177.0, 22178.0, 22179.0, 22180.0, 22181.0, 22182.0],       [22183.0, 22184.0, 22185.0, 22186.0, 22187.0, 22188.0],       [22189.0, 22190.0, 22191.0, 22192.0, 22193.0, 22194.0],       [22195.0, 22196.0, 22197.0, 22198.0, 22199.0, 22200.0],       [22201.0, 22202.0, 22203.0, 22204.0, 22205.0, 22206.0],       [22207.0, 22208.0, 22209.0, 22210.0, 22211.0, 22212.0],       [22213.0, 22214.0, 22215.0, 22216.0, 22217.0, 22218.0]],      [[22219.0, 22220.0, 22221.0, 22222.0, 22223.0, 22224.0],       [22225.0, 22226.0, 22227.0, 22228.0, 22229.0, 22230.0],       [22231.0, 22232.0, 22233.0, 22234.0, 22235.0, 22236.0],       [22237.0, 22238.0, 22239.0, 22240.0, 22241.0, 22242.0],       [22243.0, 22244.0, 22245.0, 22246.0, 22247.0, 22248.0],       [22249.0, 22250.0, 22251.0, 22252.0, 22253.0, 22254.0],       [22255.0, 22256.0, 22257.0, 22258.0, 22259.0, 22260.0]],      [[22261.0, 22262.0, 22263.0, 22264.0, 22265.0, 22266.0],       [22267.0, 22268.0, 22269.0, 22270.0, 22271.0, 22272.0],       [22273.0, 22274.0, 22275.0, 22276.0, 22277.0, 22278.0],       [22279.0, 22280.0, 22281.0, 22282.0, 22283.0, 22284.0],       [22285.0, 22286.0, 22287.0, 22288.0, 22289.0, 22290.0],       [22291.0, 22292.0, 22293.0, 22294.0, 22295.0, 22296.0],       [22297.0, 22298.0, 22299.0, 22300.0, 22301.0, 22302.0]],      [[22303.0, 22304.0, 22305.0, 22306.0, 22307.0, 22308.0],       [22309.0, 22310.0, 22311.0, 22312.0, 22313.0, 22314.0],       [22315.0, 22316.0, 22317.0, 22318.0, 22319.0, 22320.0],       [22321.0, 22322.0, 22323.0, 22324.0, 22325.0, 22326.0],       [22327.0, 22328.0, 22329.0, 22330.0, 22331.0, 22332.0],       [22333.0, 22334.0, 22335.0, 22336.0, 22337.0, 22338.0],       [22339.0, 22340.0, 22341.0, 22342.0, 22343.0, 22344.0]],      [[22345.0, 22346.0, 22347.0, 22348.0, 22349.0, 22350.0],       [22351.0, 22352.0, 22353.0, 22354.0, 22355.0, 22356.0],       [22357.0, 22358.0, 22359.0, 22360.0, 22361.0, 22362.0],       [22363.0, 22364.0, 22365.0, 22366.0, 22367.0, 22368.0],       [22369.0, 22370.0, 22371.0, 22372.0, 22373.0, 22374.0],       [22375.0, 22376.0, 22377.0, 22378.0, 22379.0, 22380.0],       [22381.0, 22382.0, 22383.0, 22384.0, 22385.0, 22386.0]],      [[22387.0, 22388.0, 22389.0, 22390.0, 22391.0, 22392.0],       [22393.0, 22394.0, 22395.0, 22396.0, 22397.0, 22398.0],       [22399.0, 22400.0, 22401.0, 22402.0, 22403.0, 22404.0],       [22405.0, 22406.0, 22407.0, 22408.0, 22409.0, 22410.0],       [22411.0, 22412.0, 22413.0, 22414.0, 22415.0, 22416.0],       [22417.0, 22418.0, 22419.0, 22420.0, 22421.0, 22422.0],       [22423.0, 22424.0, 22425.0, 22426.0, 22427.0, 22428.0]]],     [[[22429.0, 22430.0, 22431.0, 22432.0, 22433.0, 22434.0],       [22435.0, 22436.0, 22437.0, 22438.0, 22439.0, 22440.0],       [22441.0, 22442.0, 22443.0, 22444.0, 22445.0, 22446.0],       [22447.0, 22448.0, 22449.0, 22450.0, 22451.0, 22452.0],       [22453.0, 22454.0, 22455.0, 22456.0, 22457.0, 22458.0],       [22459.0, 22460.0, 22461.0, 22462.0, 22463.0, 22464.0],       [22465.0, 22466.0, 22467.0, 22468.0, 22469.0, 22470.0]],      [[22471.0, 22472.0, 22473.0, 22474.0, 22475.0, 22476.0],       [22477.0, 22478.0, 22479.0, 22480.0, 22481.0, 22482.0],       [22483.0, 22484.0, 22485.0, 22486.0, 22487.0, 22488.0],       [22489.0, 22490.0, 22491.0, 22492.0, 22493.0, 22494.0],       [22495.0, 22496.0, 22497.0, 22498.0, 22499.0, 22500.0],       [22501.0, 22502.0, 22503.0, 22504.0, 22505.0, 22506.0],       [22507.0, 22508.0, 22509.0, 22510.0, 22511.0, 22512.0]],      [[22513.0, 22514.0, 22515.0, 22516.0, 22517.0, 22518.0],       [22519.0, 22520.0, 22521.0, 22522.0, 22523.0, 22524.0],       [22525.0, 22526.0, 22527.0, 22528.0, 22529.0, 22530.0],       [22531.0, 22532.0, 22533.0, 22534.0, 22535.0, 22536.0],       [22537.0, 22538.0, 22539.0, 22540.0, 22541.0, 22542.0],       [22543.0, 22544.0, 22545.0, 22546.0, 22547.0, 22548.0],       [22549.0, 22550.0, 22551.0, 22552.0, 22553.0, 22554.0]],      [[22555.0, 22556.0, 22557.0, 22558.0, 22559.0, 22560.0],       [22561.0, 22562.0, 22563.0, 22564.0, 22565.0, 22566.0],       [22567.0, 22568.0, 22569.0, 22570.0, 22571.0, 22572.0],       [22573.0, 22574.0, 22575.0, 22576.0, 22577.0, 22578.0],       [22579.0, 22580.0, 22581.0, 22582.0, 22583.0, 22584.0],       [22585.0, 22586.0, 22587.0, 22588.0, 22589.0, 22590.0],       [22591.0, 22592.0, 22593.0, 22594.0, 22595.0, 22596.0]],      [[22597.0, 22598.0, 22599.0, 22600.0, 22601.0, 22602.0],       [22603.0, 22604.0, 22605.0, 22606.0, 22607.0, 22608.0],       [22609.0, 22610.0, 22611.0, 22612.0, 22613.0, 22614.0],       [22615.0, 22616.0, 22617.0, 22618.0, 22619.0, 22620.0],       [22621.0, 22622.0, 22623.0, 22624.0, 22625.0, 22626.0],       [22627.0, 22628.0, 22629.0, 22630.0, 22631.0, 22632.0],       [22633.0, 22634.0, 22635.0, 22636.0, 22637.0, 22638.0]],      [[22639.0, 22640.0, 22641.0, 22642.0, 22643.0, 22644.0],       [22645.0, 22646.0, 22647.0, 22648.0, 22649.0, 22650.0],       [22651.0, 22652.0, 22653.0, 22654.0, 22655.0, 22656.0],       [22657.0, 22658.0, 22659.0, 22660.0, 22661.0, 22662.0],       [22663.0, 22664.0, 22665.0, 22666.0, 22667.0, 22668.0],       [22669.0, 22670.0, 22671.0, 22672.0, 22673.0, 22674.0],       [22675.0, 22676.0, 22677.0, 22678.0, 22679.0, 22680.0]]],     [[[22681.0, 22682.0, 22683.0, 22684.0, 22685.0, 22686.0],       [22687.0, 22688.0, 22689.0, 22690.0, 22691.0, 22692.0],       [22693.0, 22694.0, 22695.0, 22696.0, 22697.0, 22698.0],       [22699.0, 22700.0, 22701.0, 22702.0, 22703.0, 22704.0],       [22705.0, 22706.0, 22707.0, 22708.0, 22709.0, 22710.0],       [22711.0, 22712.0, 22713.0, 22714.0, 22715.0, 22716.0],       [22717.0, 22718.0, 22719.0, 22720.0, 22721.0, 22722.0]],      [[22723.0, 22724.0, 22725.0, 22726.0, 22727.0, 22728.0],       [22729.0, 22730.0, 22731.0, 22732.0, 22733.0, 22734.0],       [22735.0, 22736.0, 22737.0, 22738.0, 22739.0, 22740.0],       [22741.0, 22742.0, 22743.0, 22744.0, 22745.0, 22746.0],       [22747.0, 22748.0, 22749.0, 22750.0, 22751.0, 22752.0],       [22753.0, 22754.0, 22755.0, 22756.0, 22757.0, 22758.0],       [22759.0, 22760.0, 22761.0, 22762.0, 22763.0, 22764.0]],      [[22765.0, 22766.0, 22767.0, 22768.0, 22769.0, 22770.0],       [22771.0, 22772.0, 22773.0, 22774.0, 22775.0, 22776.0],       [22777.0, 22778.0, 22779.0, 22780.0, 22781.0, 22782.0],       [22783.0, 22784.0, 22785.0, 22786.0, 22787.0, 22788.0],       [22789.0, 22790.0, 22791.0, 22792.0, 22793.0, 22794.0],       [22795.0, 22796.0, 22797.0, 22798.0, 22799.0, 22800.0],       [22801.0, 22802.0, 22803.0, 22804.0, 22805.0, 22806.0]],      [[22807.0, 22808.0, 22809.0, 22810.0, 22811.0, 22812.0],       [22813.0, 22814.0, 22815.0, 22816.0, 22817.0, 22818.0],       [22819.0, 22820.0, 22821.0, 22822.0, 22823.0, 22824.0],       [22825.0, 22826.0, 22827.0, 22828.0, 22829.0, 22830.0],       [22831.0, 22832.0, 22833.0, 22834.0, 22835.0, 22836.0],       [22837.0, 22838.0, 22839.0, 22840.0, 22841.0, 22842.0],       [22843.0, 22844.0, 22845.0, 22846.0, 22847.0, 22848.0]],      [[22849.0, 22850.0, 22851.0, 22852.0, 22853.0, 22854.0],       [22855.0, 22856.0, 22857.0, 22858.0, 22859.0, 22860.0],       [22861.0, 22862.0, 22863.0, 22864.0, 22865.0, 22866.0],       [22867.0, 22868.0, 22869.0, 22870.0, 22871.0, 22872.0],       [22873.0, 22874.0, 22875.0, 22876.0, 22877.0, 22878.0],       [22879.0, 22880.0, 22881.0, 22882.0, 22883.0, 22884.0],       [22885.0, 22886.0, 22887.0, 22888.0, 22889.0, 22890.0]],      [[22891.0, 22892.0, 22893.0, 22894.0, 22895.0, 22896.0],       [22897.0, 22898.0, 22899.0, 22900.0, 22901.0, 22902.0],       [22903.0, 22904.0, 22905.0, 22906.0, 22907.0, 22908.0],       [22909.0, 22910.0, 22911.0, 22912.0, 22913.0, 22914.0],       [22915.0, 22916.0, 22917.0, 22918.0, 22919.0, 22920.0],       [22921.0, 22922.0, 22923.0, 22924.0, 22925.0, 22926.0],       [22927.0, 22928.0, 22929.0, 22930.0, 22931.0, 22932.0]]],     [[[22933.0, 22934.0, 22935.0, 22936.0, 22937.0, 22938.0],       [22939.0, 22940.0, 22941.0, 22942.0, 22943.0, 22944.0],       [22945.0, 22946.0, 22947.0, 22948.0, 22949.0, 22950.0],       [22951.0, 22952.0, 22953.0, 22954.0, 22955.0, 22956.0],       [22957.0, 22958.0, 22959.0, 22960.0, 22961.0, 22962.0],       [22963.0, 22964.0, 22965.0, 22966.0, 22967.0, 22968.0],       [22969.0, 22970.0, 22971.0, 22972.0, 22973.0, 22974.0]],      [[22975.0, 22976.0, 22977.0, 22978.0, 22979.0, 22980.0],       [22981.0, 22982.0, 22983.0, 22984.0, 22985.0, 22986.0],       [22987.0, 22988.0, 22989.0, 22990.0, 22991.0, 22992.0],       [22993.0, 22994.0, 22995.0, 22996.0, 22997.0, 22998.0],       [22999.0, 23000.0, 23001.0, 23002.0, 23003.0, 23004.0],       [23005.0, 23006.0, 23007.0, 23008.0, 23009.0, 23010.0],       [23011.0, 23012.0, 23013.0, 23014.0, 23015.0, 23016.0]],      [[23017.0, 23018.0, 23019.0, 23020.0, 23021.0, 23022.0],       [23023.0, 23024.0, 23025.0, 23026.0, 23027.0, 23028.0],       [23029.0, 23030.0, 23031.0, 23032.0, 23033.0, 23034.0],       [23035.0, 23036.0, 23037.0, 23038.0, 23039.0, 23040.0],       [23041.0, 23042.0, 23043.0, 23044.0, 23045.0, 23046.0],       [23047.0, 23048.0, 23049.0, 23050.0, 23051.0, 23052.0],       [23053.0, 23054.0, 23055.0, 23056.0, 23057.0, 23058.0]],      [[23059.0, 23060.0, 23061.0, 23062.0, 23063.0, 23064.0],       [23065.0, 23066.0, 23067.0, 23068.0, 23069.0, 23070.0],       [23071.0, 23072.0, 23073.0, 23074.0, 23075.0, 23076.0],       [23077.0, 23078.0, 23079.0, 23080.0, 23081.0, 23082.0],       [23083.0, 23084.0, 23085.0, 23086.0, 23087.0, 23088.0],       [23089.0, 23090.0, 23091.0, 23092.0, 23093.0, 23094.0],       [23095.0, 23096.0, 23097.0, 23098.0, 23099.0, 23100.0]],      [[23101.0, 23102.0, 23103.0, 23104.0, 23105.0, 23106.0],       [23107.0, 23108.0, 23109.0, 23110.0, 23111.0, 23112.0],       [23113.0, 23114.0, 23115.0, 23116.0, 23117.0, 23118.0],       [23119.0, 23120.0, 23121.0, 23122.0, 23123.0, 23124.0],       [23125.0, 23126.0, 23127.0, 23128.0, 23129.0, 23130.0],       [23131.0, 23132.0, 23133.0, 23134.0, 23135.0, 23136.0],       [23137.0, 23138.0, 23139.0, 23140.0, 23141.0, 23142.0]],      [[23143.0, 23144.0, 23145.0, 23146.0, 23147.0, 23148.0],       [23149.0, 23150.0, 23151.0, 23152.0, 23153.0, 23154.0],       [23155.0, 23156.0, 23157.0, 23158.0, 23159.0, 23160.0],       [23161.0, 23162.0, 23163.0, 23164.0, 23165.0, 23166.0],       [23167.0, 23168.0, 23169.0, 23170.0, 23171.0, 23172.0],       [23173.0, 23174.0, 23175.0, 23176.0, 23177.0, 23178.0],       [23179.0, 23180.0, 23181.0, 23182.0, 23183.0, 23184.0]]]],    [[[[23185.0, 23186.0, 23187.0, 23188.0, 23189.0, 23190.0],       [23191.0, 23192.0, 23193.0, 23194.0, 23195.0, 23196.0],       [23197.0, 23198.0, 23199.0, 23200.0, 23201.0, 23202.0],       [23203.0, 23204.0, 23205.0, 23206.0, 23207.0, 23208.0],       [23209.0, 23210.0, 23211.0, 23212.0, 23213.0, 23214.0],       [23215.0, 23216.0, 23217.0, 23218.0, 23219.0, 23220.0],       [23221.0, 23222.0, 23223.0, 23224.0, 23225.0, 23226.0]],      [[23227.0, 23228.0, 23229.0, 23230.0, 23231.0, 23232.0],       [23233.0, 23234.0, 23235.0, 23236.0, 23237.0, 23238.0],       [23239.0, 23240.0, 23241.0, 23242.0, 23243.0, 23244.0],       [23245.0, 23246.0, 23247.0, 23248.0, 23249.0, 23250.0],       [23251.0, 23252.0, 23253.0, 23254.0, 23255.0, 23256.0],       [23257.0, 23258.0, 23259.0, 23260.0, 23261.0, 23262.0],       [23263.0, 23264.0, 23265.0, 23266.0, 23267.0, 23268.0]],      [[23269.0, 23270.0, 23271.0, 23272.0, 23273.0, 23274.0],       [23275.0, 23276.0, 23277.0, 23278.0, 23279.0, 23280.0],       [23281.0, 23282.0, 23283.0, 23284.0, 23285.0, 23286.0],       [23287.0, 23288.0, 23289.0, 23290.0, 23291.0, 23292.0],       [23293.0, 23294.0, 23295.0, 23296.0, 23297.0, 23298.0],       [23299.0, 23300.0, 23301.0, 23302.0, 23303.0, 23304.0],       [23305.0, 23306.0, 23307.0, 23308.0, 23309.0, 23310.0]],      [[23311.0, 23312.0, 23313.0, 23314.0, 23315.0, 23316.0],       [23317.0, 23318.0, 23319.0, 23320.0, 23321.0, 23322.0],       [23323.0, 23324.0, 23325.0, 23326.0, 23327.0, 23328.0],       [23329.0, 23330.0, 23331.0, 23332.0, 23333.0, 23334.0],       [23335.0, 23336.0, 23337.0, 23338.0, 23339.0, 23340.0],       [23341.0, 23342.0, 23343.0, 23344.0, 23345.0, 23346.0],       [23347.0, 23348.0, 23349.0, 23350.0, 23351.0, 23352.0]],      [[23353.0, 23354.0, 23355.0, 23356.0, 23357.0, 23358.0],       [23359.0, 23360.0, 23361.0, 23362.0, 23363.0, 23364.0],       [23365.0, 23366.0, 23367.0, 23368.0, 23369.0, 23370.0],       [23371.0, 23372.0, 23373.0, 23374.0, 23375.0, 23376.0],       [23377.0, 23378.0, 23379.0, 23380.0, 23381.0, 23382.0],       [23383.0, 23384.0, 23385.0, 23386.0, 23387.0, 23388.0],       [23389.0, 23390.0, 23391.0, 23392.0, 23393.0, 23394.0]],      [[23395.0, 23396.0, 23397.0, 23398.0, 23399.0, 23400.0],       [23401.0, 23402.0, 23403.0, 23404.0, 23405.0, 23406.0],       [23407.0, 23408.0, 23409.0, 23410.0, 23411.0, 23412.0],       [23413.0, 23414.0, 23415.0, 23416.0, 23417.0, 23418.0],       [23419.0, 23420.0, 23421.0, 23422.0, 23423.0, 23424.0],       [23425.0, 23426.0, 23427.0, 23428.0, 23429.0, 23430.0],       [23431.0, 23432.0, 23433.0, 23434.0, 23435.0, 23436.0]]],     [[[23437.0, 23438.0, 23439.0, 23440.0, 23441.0, 23442.0],       [23443.0, 23444.0, 23445.0, 23446.0, 23447.0, 23448.0],       [23449.0, 23450.0, 23451.0, 23452.0, 23453.0, 23454.0],       [23455.0, 23456.0, 23457.0, 23458.0, 23459.0, 23460.0],       [23461.0, 23462.0, 23463.0, 23464.0, 23465.0, 23466.0],       [23467.0, 23468.0, 23469.0, 23470.0, 23471.0, 23472.0],       [23473.0, 23474.0, 23475.0, 23476.0, 23477.0, 23478.0]],      [[23479.0, 23480.0, 23481.0, 23482.0, 23483.0, 23484.0],       [23485.0, 23486.0, 23487.0, 23488.0, 23489.0, 23490.0],       [23491.0, 23492.0, 23493.0, 23494.0, 23495.0, 23496.0],       [23497.0, 23498.0, 23499.0, 23500.0, 23501.0, 23502.0],       [23503.0, 23504.0, 23505.0, 23506.0, 23507.0, 23508.0],       [23509.0, 23510.0, 23511.0, 23512.0, 23513.0, 23514.0],       [23515.0, 23516.0, 23517.0, 23518.0, 23519.0, 23520.0]],      [[23521.0, 23522.0, 23523.0, 23524.0, 23525.0, 23526.0],       [23527.0, 23528.0, 23529.0, 23530.0, 23531.0, 23532.0],       [23533.0, 23534.0, 23535.0, 23536.0, 23537.0, 23538.0],       [23539.0, 23540.0, 23541.0, 23542.0, 23543.0, 23544.0],       [23545.0, 23546.0, 23547.0, 23548.0, 23549.0, 23550.0],       [23551.0, 23552.0, 23553.0, 23554.0, 23555.0, 23556.0],       [23557.0, 23558.0, 23559.0, 23560.0, 23561.0, 23562.0]],      [[23563.0, 23564.0, 23565.0, 23566.0, 23567.0, 23568.0],       [23569.0, 23570.0, 23571.0, 23572.0, 23573.0, 23574.0],       [23575.0, 23576.0, 23577.0, 23578.0, 23579.0, 23580.0],       [23581.0, 23582.0, 23583.0, 23584.0, 23585.0, 23586.0],       [23587.0, 23588.0, 23589.0, 23590.0, 23591.0, 23592.0],       [23593.0, 23594.0, 23595.0, 23596.0, 23597.0, 23598.0],       [23599.0, 23600.0, 23601.0, 23602.0, 23603.0, 23604.0]],      [[23605.0, 23606.0, 23607.0, 23608.0, 23609.0, 23610.0],       [23611.0, 23612.0, 23613.0, 23614.0, 23615.0, 23616.0],       [23617.0, 23618.0, 23619.0, 23620.0, 23621.0, 23622.0],       [23623.0, 23624.0, 23625.0, 23626.0, 23627.0, 23628.0],       [23629.0, 23630.0, 23631.0, 23632.0, 23633.0, 23634.0],       [23635.0, 23636.0, 23637.0, 23638.0, 23639.0, 23640.0],       [23641.0, 23642.0, 23643.0, 23644.0, 23645.0, 23646.0]],      [[23647.0, 23648.0, 23649.0, 23650.0, 23651.0, 23652.0],       [23653.0, 23654.0, 23655.0, 23656.0, 23657.0, 23658.0],       [23659.0, 23660.0, 23661.0, 23662.0, 23663.0, 23664.0],       [23665.0, 23666.0, 23667.0, 23668.0, 23669.0, 23670.0],       [23671.0, 23672.0, 23673.0, 23674.0, 23675.0, 23676.0],       [23677.0, 23678.0, 23679.0, 23680.0, 23681.0, 23682.0],       [23683.0, 23684.0, 23685.0, 23686.0, 23687.0, 23688.0]]],     [[[23689.0, 23690.0, 23691.0, 23692.0, 23693.0, 23694.0],       [23695.0, 23696.0, 23697.0, 23698.0, 23699.0, 23700.0],       [23701.0, 23702.0, 23703.0, 23704.0, 23705.0, 23706.0],       [23707.0, 23708.0, 23709.0, 23710.0, 23711.0, 23712.0],       [23713.0, 23714.0, 23715.0, 23716.0, 23717.0, 23718.0],       [23719.0, 23720.0, 23721.0, 23722.0, 23723.0, 23724.0],       [23725.0, 23726.0, 23727.0, 23728.0, 23729.0, 23730.0]],      [[23731.0, 23732.0, 23733.0, 23734.0, 23735.0, 23736.0],       [23737.0, 23738.0, 23739.0, 23740.0, 23741.0, 23742.0],       [23743.0, 23744.0, 23745.0, 23746.0, 23747.0, 23748.0],       [23749.0, 23750.0, 23751.0, 23752.0, 23753.0, 23754.0],       [23755.0, 23756.0, 23757.0, 23758.0, 23759.0, 23760.0],       [23761.0, 23762.0, 23763.0, 23764.0, 23765.0, 23766.0],       [23767.0, 23768.0, 23769.0, 23770.0, 23771.0, 23772.0]],      [[23773.0, 23774.0, 23775.0, 23776.0, 23777.0, 23778.0],       [23779.0, 23780.0, 23781.0, 23782.0, 23783.0, 23784.0],       [23785.0, 23786.0, 23787.0, 23788.0, 23789.0, 23790.0],       [23791.0, 23792.0, 23793.0, 23794.0, 23795.0, 23796.0],       [23797.0, 23798.0, 23799.0, 23800.0, 23801.0, 23802.0],       [23803.0, 23804.0, 23805.0, 23806.0, 23807.0, 23808.0],       [23809.0, 23810.0, 23811.0, 23812.0, 23813.0, 23814.0]],      [[23815.0, 23816.0, 23817.0, 23818.0, 23819.0, 23820.0],       [23821.0, 23822.0, 23823.0, 23824.0, 23825.0, 23826.0],       [23827.0, 23828.0, 23829.0, 23830.0, 23831.0, 23832.0],       [23833.0, 23834.0, 23835.0, 23836.0, 23837.0, 23838.0],       [23839.0, 23840.0, 23841.0, 23842.0, 23843.0, 23844.0],       [23845.0, 23846.0, 23847.0, 23848.0, 23849.0, 23850.0],       [23851.0, 23852.0, 23853.0, 23854.0, 23855.0, 23856.0]],      [[23857.0, 23858.0, 23859.0, 23860.0, 23861.0, 23862.0],       [23863.0, 23864.0, 23865.0, 23866.0, 23867.0, 23868.0],       [23869.0, 23870.0, 23871.0, 23872.0, 23873.0, 23874.0],       [23875.0, 23876.0, 23877.0, 23878.0, 23879.0, 23880.0],       [23881.0, 23882.0, 23883.0, 23884.0, 23885.0, 23886.0],       [23887.0, 23888.0, 23889.0, 23890.0, 23891.0, 23892.0],       [23893.0, 23894.0, 23895.0, 23896.0, 23897.0, 23898.0]],      [[23899.0, 23900.0, 23901.0, 23902.0, 23903.0, 23904.0],       [23905.0, 23906.0, 23907.0, 23908.0, 23909.0, 23910.0],       [23911.0, 23912.0, 23913.0, 23914.0, 23915.0, 23916.0],       [23917.0, 23918.0, 23919.0, 23920.0, 23921.0, 23922.0],       [23923.0, 23924.0, 23925.0, 23926.0, 23927.0, 23928.0],       [23929.0, 23930.0, 23931.0, 23932.0, 23933.0, 23934.0],       [23935.0, 23936.0, 23937.0, 23938.0, 23939.0, 23940.0]]],     [[[23941.0, 23942.0, 23943.0, 23944.0, 23945.0, 23946.0],       [23947.0, 23948.0, 23949.0, 23950.0, 23951.0, 23952.0],       [23953.0, 23954.0, 23955.0, 23956.0, 23957.0, 23958.0],       [23959.0, 23960.0, 23961.0, 23962.0, 23963.0, 23964.0],       [23965.0, 23966.0, 23967.0, 23968.0, 23969.0, 23970.0],       [23971.0, 23972.0, 23973.0, 23974.0, 23975.0, 23976.0],       [23977.0, 23978.0, 23979.0, 23980.0, 23981.0, 23982.0]],      [[23983.0, 23984.0, 23985.0, 23986.0, 23987.0, 23988.0],       [23989.0, 23990.0, 23991.0, 23992.0, 23993.0, 23994.0],       [23995.0, 23996.0, 23997.0, 23998.0, 23999.0, 24000.0],       [24001.0, 24002.0, 24003.0, 24004.0, 24005.0, 24006.0],       [24007.0, 24008.0, 24009.0, 24010.0, 24011.0, 24012.0],       [24013.0, 24014.0, 24015.0, 24016.0, 24017.0, 24018.0],       [24019.0, 24020.0, 24021.0, 24022.0, 24023.0, 24024.0]],      [[24025.0, 24026.0, 24027.0, 24028.0, 24029.0, 24030.0],       [24031.0, 24032.0, 24033.0, 24034.0, 24035.0, 24036.0],       [24037.0, 24038.0, 24039.0, 24040.0, 24041.0, 24042.0],       [24043.0, 24044.0, 24045.0, 24046.0, 24047.0, 24048.0],       [24049.0, 24050.0, 24051.0, 24052.0, 24053.0, 24054.0],       [24055.0, 24056.0, 24057.0, 24058.0, 24059.0, 24060.0],       [24061.0, 24062.0, 24063.0, 24064.0, 24065.0, 24066.0]],      [[24067.0, 24068.0, 24069.0, 24070.0, 24071.0, 24072.0],       [24073.0, 24074.0, 24075.0, 24076.0, 24077.0, 24078.0],       [24079.0, 24080.0, 24081.0, 24082.0, 24083.0, 24084.0],       [24085.0, 24086.0, 24087.0, 24088.0, 24089.0, 24090.0],       [24091.0, 24092.0, 24093.0, 24094.0, 24095.0, 24096.0],       [24097.0, 24098.0, 24099.0, 24100.0, 24101.0, 24102.0],       [24103.0, 24104.0, 24105.0, 24106.0, 24107.0, 24108.0]],      [[24109.0, 24110.0, 24111.0, 24112.0, 24113.0, 24114.0],       [24115.0, 24116.0, 24117.0, 24118.0, 24119.0, 24120.0],       [24121.0, 24122.0, 24123.0, 24124.0, 24125.0, 24126.0],       [24127.0, 24128.0, 24129.0, 24130.0, 24131.0, 24132.0],       [24133.0, 24134.0, 24135.0, 24136.0, 24137.0, 24138.0],       [24139.0, 24140.0, 24141.0, 24142.0, 24143.0, 24144.0],       [24145.0, 24146.0, 24147.0, 24148.0, 24149.0, 24150.0]],      [[24151.0, 24152.0, 24153.0, 24154.0, 24155.0, 24156.0],       [24157.0, 24158.0, 24159.0, 24160.0, 24161.0, 24162.0],       [24163.0, 24164.0, 24165.0, 24166.0, 24167.0, 24168.0],       [24169.0, 24170.0, 24171.0, 24172.0, 24173.0, 24174.0],       [24175.0, 24176.0, 24177.0, 24178.0, 24179.0, 24180.0],       [24181.0, 24182.0, 24183.0, 24184.0, 24185.0, 24186.0],       [24187.0, 24188.0, 24189.0, 24190.0, 24191.0, 24192.0]]]]],   [[[[[24193.0, 24194.0, 24195.0, 24196.0, 24197.0, 24198.0],       [24199.0, 24200.0, 24201.0, 24202.0, 24203.0, 24204.0],       [24205.0, 24206.0, 24207.0, 24208.0, 24209.0, 24210.0],       [24211.0, 24212.0, 24213.0, 24214.0, 24215.0, 24216.0],       [24217.0, 24218.0, 24219.0, 24220.0, 24221.0, 24222.0],       [24223.0, 24224.0, 24225.0, 24226.0, 24227.0, 24228.0],       [24229.0, 24230.0, 24231.0, 24232.0, 24233.0, 24234.0]],      [[24235.0, 24236.0, 24237.0, 24238.0, 24239.0, 24240.0],       [24241.0, 24242.0, 24243.0, 24244.0, 24245.0, 24246.0],       [24247.0, 24248.0, 24249.0, 24250.0, 24251.0, 24252.0],       [24253.0, 24254.0, 24255.0, 24256.0, 24257.0, 24258.0],       [24259.0, 24260.0, 24261.0, 24262.0, 24263.0, 24264.0],       [24265.0, 24266.0, 24267.0, 24268.0, 24269.0, 24270.0],       [24271.0, 24272.0, 24273.0, 24274.0, 24275.0, 24276.0]],      [[24277.0, 24278.0, 24279.0, 24280.0, 24281.0, 24282.0],       [24283.0, 24284.0, 24285.0, 24286.0, 24287.0, 24288.0],       [24289.0, 24290.0, 24291.0, 24292.0, 24293.0, 24294.0],       [24295.0, 24296.0, 24297.0, 24298.0, 24299.0, 24300.0],       [24301.0, 24302.0, 24303.0, 24304.0, 24305.0, 24306.0],       [24307.0, 24308.0, 24309.0, 24310.0, 24311.0, 24312.0],       [24313.0, 24314.0, 24315.0, 24316.0, 24317.0, 24318.0]],      [[24319.0, 24320.0, 24321.0, 24322.0, 24323.0, 24324.0],       [24325.0, 24326.0, 24327.0, 24328.0, 24329.0, 24330.0],       [24331.0, 24332.0, 24333.0, 24334.0, 24335.0, 24336.0],       [24337.0, 24338.0, 24339.0, 24340.0, 24341.0, 24342.0],       [24343.0, 24344.0, 24345.0, 24346.0, 24347.0, 24348.0],       [24349.0, 24350.0, 24351.0, 24352.0, 24353.0, 24354.0],       [24355.0, 24356.0, 24357.0, 24358.0, 24359.0, 24360.0]],      [[24361.0, 24362.0, 24363.0, 24364.0, 24365.0, 24366.0],       [24367.0, 24368.0, 24369.0, 24370.0, 24371.0, 24372.0],       [24373.0, 24374.0, 24375.0, 24376.0, 24377.0, 24378.0],       [24379.0, 24380.0, 24381.0, 24382.0, 24383.0, 24384.0],       [24385.0, 24386.0, 24387.0, 24388.0, 24389.0, 24390.0],       [24391.0, 24392.0, 24393.0, 24394.0, 24395.0, 24396.0],       [24397.0, 24398.0, 24399.0, 24400.0, 24401.0, 24402.0]],      [[24403.0, 24404.0, 24405.0, 24406.0, 24407.0, 24408.0],       [24409.0, 24410.0, 24411.0, 24412.0, 24413.0, 24414.0],       [24415.0, 24416.0, 24417.0, 24418.0, 24419.0, 24420.0],       [24421.0, 24422.0, 24423.0, 24424.0, 24425.0, 24426.0],       [24427.0, 24428.0, 24429.0, 24430.0, 24431.0, 24432.0],       [24433.0, 24434.0, 24435.0, 24436.0, 24437.0, 24438.0],       [24439.0, 24440.0, 24441.0, 24442.0, 24443.0, 24444.0]]],     [[[24445.0, 24446.0, 24447.0, 24448.0, 24449.0, 24450.0],       [24451.0, 24452.0, 24453.0, 24454.0, 24455.0, 24456.0],       [24457.0, 24458.0, 24459.0, 24460.0, 24461.0, 24462.0],       [24463.0, 24464.0, 24465.0, 24466.0, 24467.0, 24468.0],       [24469.0, 24470.0, 24471.0, 24472.0, 24473.0, 24474.0],       [24475.0, 24476.0, 24477.0, 24478.0, 24479.0, 24480.0],       [24481.0, 24482.0, 24483.0, 24484.0, 24485.0, 24486.0]],      [[24487.0, 24488.0, 24489.0, 24490.0, 24491.0, 24492.0],       [24493.0, 24494.0, 24495.0, 24496.0, 24497.0, 24498.0],       [24499.0, 24500.0, 24501.0, 24502.0, 24503.0, 24504.0],       [24505.0, 24506.0, 24507.0, 24508.0, 24509.0, 24510.0],       [24511.0, 24512.0, 24513.0, 24514.0, 24515.0, 24516.0],       [24517.0, 24518.0, 24519.0, 24520.0, 24521.0, 24522.0],       [24523.0, 24524.0, 24525.0, 24526.0, 24527.0, 24528.0]],      [[24529.0, 24530.0, 24531.0, 24532.0, 24533.0, 24534.0],       [24535.0, 24536.0, 24537.0, 24538.0, 24539.0, 24540.0],       [24541.0, 24542.0, 24543.0, 24544.0, 24545.0, 24546.0],       [24547.0, 24548.0, 24549.0, 24550.0, 24551.0, 24552.0],       [24553.0, 24554.0, 24555.0, 24556.0, 24557.0, 24558.0],       [24559.0, 24560.0, 24561.0, 24562.0, 24563.0, 24564.0],       [24565.0, 24566.0, 24567.0, 24568.0, 24569.0, 24570.0]],      [[24571.0, 24572.0, 24573.0, 24574.0, 24575.0, 24576.0],       [24577.0, 24578.0, 24579.0, 24580.0, 24581.0, 24582.0],       [24583.0, 24584.0, 24585.0, 24586.0, 24587.0, 24588.0],       [24589.0, 24590.0, 24591.0, 24592.0, 24593.0, 24594.0],       [24595.0, 24596.0, 24597.0, 24598.0, 24599.0, 24600.0],       [24601.0, 24602.0, 24603.0, 24604.0, 24605.0, 24606.0],       [24607.0, 24608.0, 24609.0, 24610.0, 24611.0, 24612.0]],      [[24613.0, 24614.0, 24615.0, 24616.0, 24617.0, 24618.0],       [24619.0, 24620.0, 24621.0, 24622.0, 24623.0, 24624.0],       [24625.0, 24626.0, 24627.0, 24628.0, 24629.0, 24630.0],       [24631.0, 24632.0, 24633.0, 24634.0, 24635.0, 24636.0],       [24637.0, 24638.0, 24639.0, 24640.0, 24641.0, 24642.0],       [24643.0, 24644.0, 24645.0, 24646.0, 24647.0, 24648.0],       [24649.0, 24650.0, 24651.0, 24652.0, 24653.0, 24654.0]],      [[24655.0, 24656.0, 24657.0, 24658.0, 24659.0, 24660.0],       [24661.0, 24662.0, 24663.0, 24664.0, 24665.0, 24666.0],       [24667.0, 24668.0, 24669.0, 24670.0, 24671.0, 24672.0],       [24673.0, 24674.0, 24675.0, 24676.0, 24677.0, 24678.0],       [24679.0, 24680.0, 24681.0, 24682.0, 24683.0, 24684.0],       [24685.0, 24686.0, 24687.0, 24688.0, 24689.0, 24690.0],       [24691.0, 24692.0, 24693.0, 24694.0, 24695.0, 24696.0]]],     [[[24697.0, 24698.0, 24699.0, 24700.0, 24701.0, 24702.0],       [24703.0, 24704.0, 24705.0, 24706.0, 24707.0, 24708.0],       [24709.0, 24710.0, 24711.0, 24712.0, 24713.0, 24714.0],       [24715.0, 24716.0, 24717.0, 24718.0, 24719.0, 24720.0],       [24721.0, 24722.0, 24723.0, 24724.0, 24725.0, 24726.0],       [24727.0, 24728.0, 24729.0, 24730.0, 24731.0, 24732.0],       [24733.0, 24734.0, 24735.0, 24736.0, 24737.0, 24738.0]],      [[24739.0, 24740.0, 24741.0, 24742.0, 24743.0, 24744.0],       [24745.0, 24746.0, 24747.0, 24748.0, 24749.0, 24750.0],       [24751.0, 24752.0, 24753.0, 24754.0, 24755.0, 24756.0],       [24757.0, 24758.0, 24759.0, 24760.0, 24761.0, 24762.0],       [24763.0, 24764.0, 24765.0, 24766.0, 24767.0, 24768.0],       [24769.0, 24770.0, 24771.0, 24772.0, 24773.0, 24774.0],       [24775.0, 24776.0, 24777.0, 24778.0, 24779.0, 24780.0]],      [[24781.0, 24782.0, 24783.0, 24784.0, 24785.0, 24786.0],       [24787.0, 24788.0, 24789.0, 24790.0, 24791.0, 24792.0],       [24793.0, 24794.0, 24795.0, 24796.0, 24797.0, 24798.0],       [24799.0, 24800.0, 24801.0, 24802.0, 24803.0, 24804.0],       [24805.0, 24806.0, 24807.0, 24808.0, 24809.0, 24810.0],       [24811.0, 24812.0, 24813.0, 24814.0, 24815.0, 24816.0],       [24817.0, 24818.0, 24819.0, 24820.0, 24821.0, 24822.0]],      [[24823.0, 24824.0, 24825.0, 24826.0, 24827.0, 24828.0],       [24829.0, 24830.0, 24831.0, 24832.0, 24833.0, 24834.0],       [24835.0, 24836.0, 24837.0, 24838.0, 24839.0, 24840.0],       [24841.0, 24842.0, 24843.0, 24844.0, 24845.0, 24846.0],       [24847.0, 24848.0, 24849.0, 24850.0, 24851.0, 24852.0],       [24853.0, 24854.0, 24855.0, 24856.0, 24857.0, 24858.0],       [24859.0, 24860.0, 24861.0, 24862.0, 24863.0, 24864.0]],      [[24865.0, 24866.0, 24867.0, 24868.0, 24869.0, 24870.0],       [24871.0, 24872.0, 24873.0, 24874.0, 24875.0, 24876.0],       [24877.0, 24878.0, 24879.0, 24880.0, 24881.0, 24882.0],       [24883.0, 24884.0, 24885.0, 24886.0, 24887.0, 24888.0],       [24889.0, 24890.0, 24891.0, 24892.0, 24893.0, 24894.0],       [24895.0, 24896.0, 24897.0, 24898.0, 24899.0, 24900.0],       [24901.0, 24902.0, 24903.0, 24904.0, 24905.0, 24906.0]],      [[24907.0, 24908.0, 24909.0, 24910.0, 24911.0, 24912.0],       [24913.0, 24914.0, 24915.0, 24916.0, 24917.0, 24918.0],       [24919.0, 24920.0, 24921.0, 24922.0, 24923.0, 24924.0],       [24925.0, 24926.0, 24927.0, 24928.0, 24929.0, 24930.0],       [24931.0, 24932.0, 24933.0, 24934.0, 24935.0, 24936.0],       [24937.0, 24938.0, 24939.0, 24940.0, 24941.0, 24942.0],       [24943.0, 24944.0, 24945.0, 24946.0, 24947.0, 24948.0]]],     [[[24949.0, 24950.0, 24951.0, 24952.0, 24953.0, 24954.0],       [24955.0, 24956.0, 24957.0, 24958.0, 24959.0, 24960.0],       [24961.0, 24962.0, 24963.0, 24964.0, 24965.0, 24966.0],       [24967.0, 24968.0, 24969.0, 24970.0, 24971.0, 24972.0],       [24973.0, 24974.0, 24975.0, 24976.0, 24977.0, 24978.0],       [24979.0, 24980.0, 24981.0, 24982.0, 24983.0, 24984.0],       [24985.0, 24986.0, 24987.0, 24988.0, 24989.0, 24990.0]],      [[24991.0, 24992.0, 24993.0, 24994.0, 24995.0, 24996.0],       [24997.0, 24998.0, 24999.0, 25000.0, 25001.0, 25002.0],       [25003.0, 25004.0, 25005.0, 25006.0, 25007.0, 25008.0],       [25009.0, 25010.0, 25011.0, 25012.0, 25013.0, 25014.0],       [25015.0, 25016.0, 25017.0, 25018.0, 25019.0, 25020.0],       [25021.0, 25022.0, 25023.0, 25024.0, 25025.0, 25026.0],       [25027.0, 25028.0, 25029.0, 25030.0, 25031.0, 25032.0]],      [[25033.0, 25034.0, 25035.0, 25036.0, 25037.0, 25038.0],       [25039.0, 25040.0, 25041.0, 25042.0, 25043.0, 25044.0],       [25045.0, 25046.0, 25047.0, 25048.0, 25049.0, 25050.0],       [25051.0, 25052.0, 25053.0, 25054.0, 25055.0, 25056.0],       [25057.0, 25058.0, 25059.0, 25060.0, 25061.0, 25062.0],       [25063.0, 25064.0, 25065.0, 25066.0, 25067.0, 25068.0],       [25069.0, 25070.0, 25071.0, 25072.0, 25073.0, 25074.0]],      [[25075.0, 25076.0, 25077.0, 25078.0, 25079.0, 25080.0],       [25081.0, 25082.0, 25083.0, 25084.0, 25085.0, 25086.0],       [25087.0, 25088.0, 25089.0, 25090.0, 25091.0, 25092.0],       [25093.0, 25094.0, 25095.0, 25096.0, 25097.0, 25098.0],       [25099.0, 25100.0, 25101.0, 25102.0, 25103.0, 25104.0],       [25105.0, 25106.0, 25107.0, 25108.0, 25109.0, 25110.0],       [25111.0, 25112.0, 25113.0, 25114.0, 25115.0, 25116.0]],      [[25117.0, 25118.0, 25119.0, 25120.0, 25121.0, 25122.0],       [25123.0, 25124.0, 25125.0, 25126.0, 25127.0, 25128.0],       [25129.0, 25130.0, 25131.0, 25132.0, 25133.0, 25134.0],       [25135.0, 25136.0, 25137.0, 25138.0, 25139.0, 25140.0],       [25141.0, 25142.0, 25143.0, 25144.0, 25145.0, 25146.0],       [25147.0, 25148.0, 25149.0, 25150.0, 25151.0, 25152.0],       [25153.0, 25154.0, 25155.0, 25156.0, 25157.0, 25158.0]],      [[25159.0, 25160.0, 25161.0, 25162.0, 25163.0, 25164.0],       [25165.0, 25166.0, 25167.0, 25168.0, 25169.0, 25170.0],       [25171.0, 25172.0, 25173.0, 25174.0, 25175.0, 25176.0],       [25177.0, 25178.0, 25179.0, 25180.0, 25181.0, 25182.0],       [25183.0, 25184.0, 25185.0, 25186.0, 25187.0, 25188.0],       [25189.0, 25190.0, 25191.0, 25192.0, 25193.0, 25194.0],       [25195.0, 25196.0, 25197.0, 25198.0, 25199.0, 25200.0]]]],    [[[[25201.0, 25202.0, 25203.0, 25204.0, 25205.0, 25206.0],       [25207.0, 25208.0, 25209.0, 25210.0, 25211.0, 25212.0],       [25213.0, 25214.0, 25215.0, 25216.0, 25217.0, 25218.0],       [25219.0, 25220.0, 25221.0, 25222.0, 25223.0, 25224.0],       [25225.0, 25226.0, 25227.0, 25228.0, 25229.0, 25230.0],       [25231.0, 25232.0, 25233.0, 25234.0, 25235.0, 25236.0],       [25237.0, 25238.0, 25239.0, 25240.0, 25241.0, 25242.0]],      [[25243.0, 25244.0, 25245.0, 25246.0, 25247.0, 25248.0],       [25249.0, 25250.0, 25251.0, 25252.0, 25253.0, 25254.0],       [25255.0, 25256.0, 25257.0, 25258.0, 25259.0, 25260.0],       [25261.0, 25262.0, 25263.0, 25264.0, 25265.0, 25266.0],       [25267.0, 25268.0, 25269.0, 25270.0, 25271.0, 25272.0],       [25273.0, 25274.0, 25275.0, 25276.0, 25277.0, 25278.0],       [25279.0, 25280.0, 25281.0, 25282.0, 25283.0, 25284.0]],      [[25285.0, 25286.0, 25287.0, 25288.0, 25289.0, 25290.0],       [25291.0, 25292.0, 25293.0, 25294.0, 25295.0, 25296.0],       [25297.0, 25298.0, 25299.0, 25300.0, 25301.0, 25302.0],       [25303.0, 25304.0, 25305.0, 25306.0, 25307.0, 25308.0],       [25309.0, 25310.0, 25311.0, 25312.0, 25313.0, 25314.0],       [25315.0, 25316.0, 25317.0, 25318.0, 25319.0, 25320.0],       [25321.0, 25322.0, 25323.0, 25324.0, 25325.0, 25326.0]],      [[25327.0, 25328.0, 25329.0, 25330.0, 25331.0, 25332.0],       [25333.0, 25334.0, 25335.0, 25336.0, 25337.0, 25338.0],       [25339.0, 25340.0, 25341.0, 25342.0, 25343.0, 25344.0],       [25345.0, 25346.0, 25347.0, 25348.0, 25349.0, 25350.0],       [25351.0, 25352.0, 25353.0, 25354.0, 25355.0, 25356.0],       [25357.0, 25358.0, 25359.0, 25360.0, 25361.0, 25362.0],       [25363.0, 25364.0, 25365.0, 25366.0, 25367.0, 25368.0]],      [[25369.0, 25370.0, 25371.0, 25372.0, 25373.0, 25374.0],       [25375.0, 25376.0, 25377.0, 25378.0, 25379.0, 25380.0],       [25381.0, 25382.0, 25383.0, 25384.0, 25385.0, 25386.0],       [25387.0, 25388.0, 25389.0, 25390.0, 25391.0, 25392.0],       [25393.0, 25394.0, 25395.0, 25396.0, 25397.0, 25398.0],       [25399.0, 25400.0, 25401.0, 25402.0, 25403.0, 25404.0],       [25405.0, 25406.0, 25407.0, 25408.0, 25409.0, 25410.0]],      [[25411.0, 25412.0, 25413.0, 25414.0, 25415.0, 25416.0],       [25417.0, 25418.0, 25419.0, 25420.0, 25421.0, 25422.0],       [25423.0, 25424.0, 25425.0, 25426.0, 25427.0, 25428.0],       [25429.0, 25430.0, 25431.0, 25432.0, 25433.0, 25434.0],       [25435.0, 25436.0, 25437.0, 25438.0, 25439.0, 25440.0],       [25441.0, 25442.0, 25443.0, 25444.0, 25445.0, 25446.0],       [25447.0, 25448.0, 25449.0, 25450.0, 25451.0, 25452.0]]],     [[[25453.0, 25454.0, 25455.0, 25456.0, 25457.0, 25458.0],       [25459.0, 25460.0, 25461.0, 25462.0, 25463.0, 25464.0],       [25465.0, 25466.0, 25467.0, 25468.0, 25469.0, 25470.0],       [25471.0, 25472.0, 25473.0, 25474.0, 25475.0, 25476.0],       [25477.0, 25478.0, 25479.0, 25480.0, 25481.0, 25482.0],       [25483.0, 25484.0, 25485.0, 25486.0, 25487.0, 25488.0],       [25489.0, 25490.0, 25491.0, 25492.0, 25493.0, 25494.0]],      [[25495.0, 25496.0, 25497.0, 25498.0, 25499.0, 25500.0],       [25501.0, 25502.0, 25503.0, 25504.0, 25505.0, 25506.0],       [25507.0, 25508.0, 25509.0, 25510.0, 25511.0, 25512.0],       [25513.0, 25514.0, 25515.0, 25516.0, 25517.0, 25518.0],       [25519.0, 25520.0, 25521.0, 25522.0, 25523.0, 25524.0],       [25525.0, 25526.0, 25527.0, 25528.0, 25529.0, 25530.0],       [25531.0, 25532.0, 25533.0, 25534.0, 25535.0, 25536.0]],      [[25537.0, 25538.0, 25539.0, 25540.0, 25541.0, 25542.0],       [25543.0, 25544.0, 25545.0, 25546.0, 25547.0, 25548.0],       [25549.0, 25550.0, 25551.0, 25552.0, 25553.0, 25554.0],       [25555.0, 25556.0, 25557.0, 25558.0, 25559.0, 25560.0],       [25561.0, 25562.0, 25563.0, 25564.0, 25565.0, 25566.0],       [25567.0, 25568.0, 25569.0, 25570.0, 25571.0, 25572.0],       [25573.0, 25574.0, 25575.0, 25576.0, 25577.0, 25578.0]],      [[25579.0, 25580.0, 25581.0, 25582.0, 25583.0, 25584.0],       [25585.0, 25586.0, 25587.0, 25588.0, 25589.0, 25590.0],       [25591.0, 25592.0, 25593.0, 25594.0, 25595.0, 25596.0],       [25597.0, 25598.0, 25599.0, 25600.0, 25601.0, 25602.0],       [25603.0, 25604.0, 25605.0, 25606.0, 25607.0, 25608.0],       [25609.0, 25610.0, 25611.0, 25612.0, 25613.0, 25614.0],       [25615.0, 25616.0, 25617.0, 25618.0, 25619.0, 25620.0]],      [[25621.0, 25622.0, 25623.0, 25624.0, 25625.0, 25626.0],       [25627.0, 25628.0, 25629.0, 25630.0, 25631.0, 25632.0],       [25633.0, 25634.0, 25635.0, 25636.0, 25637.0, 25638.0],       [25639.0, 25640.0, 25641.0, 25642.0, 25643.0, 25644.0],       [25645.0, 25646.0, 25647.0, 25648.0, 25649.0, 25650.0],       [25651.0, 25652.0, 25653.0, 25654.0, 25655.0, 25656.0],       [25657.0, 25658.0, 25659.0, 25660.0, 25661.0, 25662.0]],      [[25663.0, 25664.0, 25665.0, 25666.0, 25667.0, 25668.0],       [25669.0, 25670.0, 25671.0, 25672.0, 25673.0, 25674.0],       [25675.0, 25676.0, 25677.0, 25678.0, 25679.0, 25680.0],       [25681.0, 25682.0, 25683.0, 25684.0, 25685.0, 25686.0],       [25687.0, 25688.0, 25689.0, 25690.0, 25691.0, 25692.0],       [25693.0, 25694.0, 25695.0, 25696.0, 25697.0, 25698.0],       [25699.0, 25700.0, 25701.0, 25702.0, 25703.0, 25704.0]]],     [[[25705.0, 25706.0, 25707.0, 25708.0, 25709.0, 25710.0],       [25711.0, 25712.0, 25713.0, 25714.0, 25715.0, 25716.0],       [25717.0, 25718.0, 25719.0, 25720.0, 25721.0, 25722.0],       [25723.0, 25724.0, 25725.0, 25726.0, 25727.0, 25728.0],       [25729.0, 25730.0, 25731.0, 25732.0, 25733.0, 25734.0],       [25735.0, 25736.0, 25737.0, 25738.0, 25739.0, 25740.0],       [25741.0, 25742.0, 25743.0, 25744.0, 25745.0, 25746.0]],      [[25747.0, 25748.0, 25749.0, 25750.0, 25751.0, 25752.0],       [25753.0, 25754.0, 25755.0, 25756.0, 25757.0, 25758.0],       [25759.0, 25760.0, 25761.0, 25762.0, 25763.0, 25764.0],       [25765.0, 25766.0, 25767.0, 25768.0, 25769.0, 25770.0],       [25771.0, 25772.0, 25773.0, 25774.0, 25775.0, 25776.0],       [25777.0, 25778.0, 25779.0, 25780.0, 25781.0, 25782.0],       [25783.0, 25784.0, 25785.0, 25786.0, 25787.0, 25788.0]],      [[25789.0, 25790.0, 25791.0, 25792.0, 25793.0, 25794.0],       [25795.0, 25796.0, 25797.0, 25798.0, 25799.0, 25800.0],       [25801.0, 25802.0, 25803.0, 25804.0, 25805.0, 25806.0],       [25807.0, 25808.0, 25809.0, 25810.0, 25811.0, 25812.0],       [25813.0, 25814.0, 25815.0, 25816.0, 25817.0, 25818.0],       [25819.0, 25820.0, 25821.0, 25822.0, 25823.0, 25824.0],       [25825.0, 25826.0, 25827.0, 25828.0, 25829.0, 25830.0]],      [[25831.0, 25832.0, 25833.0, 25834.0, 25835.0, 25836.0],       [25837.0, 25838.0, 25839.0, 25840.0, 25841.0, 25842.0],       [25843.0, 25844.0, 25845.0, 25846.0, 25847.0, 25848.0],       [25849.0, 25850.0, 25851.0, 25852.0, 25853.0, 25854.0],       [25855.0, 25856.0, 25857.0, 25858.0, 25859.0, 25860.0],       [25861.0, 25862.0, 25863.0, 25864.0, 25865.0, 25866.0],       [25867.0, 25868.0, 25869.0, 25870.0, 25871.0, 25872.0]],      [[25873.0, 25874.0, 25875.0, 25876.0, 25877.0, 25878.0],       [25879.0, 25880.0, 25881.0, 25882.0, 25883.0, 25884.0],       [25885.0, 25886.0, 25887.0, 25888.0, 25889.0, 25890.0],       [25891.0, 25892.0, 25893.0, 25894.0, 25895.0, 25896.0],       [25897.0, 25898.0, 25899.0, 25900.0, 25901.0, 25902.0],       [25903.0, 25904.0, 25905.0, 25906.0, 25907.0, 25908.0],       [25909.0, 25910.0, 25911.0, 25912.0, 25913.0, 25914.0]],      [[25915.0, 25916.0, 25917.0, 25918.0, 25919.0, 25920.0],       [25921.0, 25922.0, 25923.0, 25924.0, 25925.0, 25926.0],       [25927.0, 25928.0, 25929.0, 25930.0, 25931.0, 25932.0],       [25933.0, 25934.0, 25935.0, 25936.0, 25937.0, 25938.0],       [25939.0, 25940.0, 25941.0, 25942.0, 25943.0, 25944.0],       [25945.0, 25946.0, 25947.0, 25948.0, 25949.0, 25950.0],       [25951.0, 25952.0, 25953.0, 25954.0, 25955.0, 25956.0]]],     [[[25957.0, 25958.0, 25959.0, 25960.0, 25961.0, 25962.0],       [25963.0, 25964.0, 25965.0, 25966.0, 25967.0, 25968.0],       [25969.0, 25970.0, 25971.0, 25972.0, 25973.0, 25974.0],       [25975.0, 25976.0, 25977.0, 25978.0, 25979.0, 25980.0],       [25981.0, 25982.0, 25983.0, 25984.0, 25985.0, 25986.0],       [25987.0, 25988.0, 25989.0, 25990.0, 25991.0, 25992.0],       [25993.0, 25994.0, 25995.0, 25996.0, 25997.0, 25998.0]],      [[25999.0, 26000.0, 26001.0, 26002.0, 26003.0, 26004.0],       [26005.0, 26006.0, 26007.0, 26008.0, 26009.0, 26010.0],       [26011.0, 26012.0, 26013.0, 26014.0, 26015.0, 26016.0],       [26017.0, 26018.0, 26019.0, 26020.0, 26021.0, 26022.0],       [26023.0, 26024.0, 26025.0, 26026.0, 26027.0, 26028.0],       [26029.0, 26030.0, 26031.0, 26032.0, 26033.0, 26034.0],       [26035.0, 26036.0, 26037.0, 26038.0, 26039.0, 26040.0]],      [[26041.0, 26042.0, 26043.0, 26044.0, 26045.0, 26046.0],       [26047.0, 26048.0, 26049.0, 26050.0, 26051.0, 26052.0],       [26053.0, 26054.0, 26055.0, 26056.0, 26057.0, 26058.0],       [26059.0, 26060.0, 26061.0, 26062.0, 26063.0, 26064.0],       [26065.0, 26066.0, 26067.0, 26068.0, 26069.0, 26070.0],       [26071.0, 26072.0, 26073.0, 26074.0, 26075.0, 26076.0],       [26077.0, 26078.0, 26079.0, 26080.0, 26081.0, 26082.0]],      [[26083.0, 26084.0, 26085.0, 26086.0, 26087.0, 26088.0],       [26089.0, 26090.0, 26091.0, 26092.0, 26093.0, 26094.0],       [26095.0, 26096.0, 26097.0, 26098.0, 26099.0, 26100.0],       [26101.0, 26102.0, 26103.0, 26104.0, 26105.0, 26106.0],       [26107.0, 26108.0, 26109.0, 26110.0, 26111.0, 26112.0],       [26113.0, 26114.0, 26115.0, 26116.0, 26117.0, 26118.0],       [26119.0, 26120.0, 26121.0, 26122.0, 26123.0, 26124.0]],      [[26125.0, 26126.0, 26127.0, 26128.0, 26129.0, 26130.0],       [26131.0, 26132.0, 26133.0, 26134.0, 26135.0, 26136.0],       [26137.0, 26138.0, 26139.0, 26140.0, 26141.0, 26142.0],       [26143.0, 26144.0, 26145.0, 26146.0, 26147.0, 26148.0],       [26149.0, 26150.0, 26151.0, 26152.0, 26153.0, 26154.0],       [26155.0, 26156.0, 26157.0, 26158.0, 26159.0, 26160.0],       [26161.0, 26162.0, 26163.0, 26164.0, 26165.0, 26166.0]],      [[26167.0, 26168.0, 26169.0, 26170.0, 26171.0, 26172.0],       [26173.0, 26174.0, 26175.0, 26176.0, 26177.0, 26178.0],       [26179.0, 26180.0, 26181.0, 26182.0, 26183.0, 26184.0],       [26185.0, 26186.0, 26187.0, 26188.0, 26189.0, 26190.0],       [26191.0, 26192.0, 26193.0, 26194.0, 26195.0, 26196.0],       [26197.0, 26198.0, 26199.0, 26200.0, 26201.0, 26202.0],       [26203.0, 26204.0, 26205.0, 26206.0, 26207.0, 26208.0]]]],    [[[[26209.0, 26210.0, 26211.0, 26212.0, 26213.0, 26214.0],       [26215.0, 26216.0, 26217.0, 26218.0, 26219.0, 26220.0],       [26221.0, 26222.0, 26223.0, 26224.0, 26225.0, 26226.0],       [26227.0, 26228.0, 26229.0, 26230.0, 26231.0, 26232.0],       [26233.0, 26234.0, 26235.0, 26236.0, 26237.0, 26238.0],       [26239.0, 26240.0, 26241.0, 26242.0, 26243.0, 26244.0],       [26245.0, 26246.0, 26247.0, 26248.0, 26249.0, 26250.0]],      [[26251.0, 26252.0, 26253.0, 26254.0, 26255.0, 26256.0],       [26257.0, 26258.0, 26259.0, 26260.0, 26261.0, 26262.0],       [26263.0, 26264.0, 26265.0, 26266.0, 26267.0, 26268.0],       [26269.0, 26270.0, 26271.0, 26272.0, 26273.0, 26274.0],       [26275.0, 26276.0, 26277.0, 26278.0, 26279.0, 26280.0],       [26281.0, 26282.0, 26283.0, 26284.0, 26285.0, 26286.0],       [26287.0, 26288.0, 26289.0, 26290.0, 26291.0, 26292.0]],      [[26293.0, 26294.0, 26295.0, 26296.0, 26297.0, 26298.0],       [26299.0, 26300.0, 26301.0, 26302.0, 26303.0, 26304.0],       [26305.0, 26306.0, 26307.0, 26308.0, 26309.0, 26310.0],       [26311.0, 26312.0, 26313.0, 26314.0, 26315.0, 26316.0],       [26317.0, 26318.0, 26319.0, 26320.0, 26321.0, 26322.0],       [26323.0, 26324.0, 26325.0, 26326.0, 26327.0, 26328.0],       [26329.0, 26330.0, 26331.0, 26332.0, 26333.0, 26334.0]],      [[26335.0, 26336.0, 26337.0, 26338.0, 26339.0, 26340.0],       [26341.0, 26342.0, 26343.0, 26344.0, 26345.0, 26346.0],       [26347.0, 26348.0, 26349.0, 26350.0, 26351.0, 26352.0],       [26353.0, 26354.0, 26355.0, 26356.0, 26357.0, 26358.0],       [26359.0, 26360.0, 26361.0, 26362.0, 26363.0, 26364.0],       [26365.0, 26366.0, 26367.0, 26368.0, 26369.0, 26370.0],       [26371.0, 26372.0, 26373.0, 26374.0, 26375.0, 26376.0]],      [[26377.0, 26378.0, 26379.0, 26380.0, 26381.0, 26382.0],       [26383.0, 26384.0, 26385.0, 26386.0, 26387.0, 26388.0],       [26389.0, 26390.0, 26391.0, 26392.0, 26393.0, 26394.0],       [26395.0, 26396.0, 26397.0, 26398.0, 26399.0, 26400.0],       [26401.0, 26402.0, 26403.0, 26404.0, 26405.0, 26406.0],       [26407.0, 26408.0, 26409.0, 26410.0, 26411.0, 26412.0],       [26413.0, 26414.0, 26415.0, 26416.0, 26417.0, 26418.0]],      [[26419.0, 26420.0, 26421.0, 26422.0, 26423.0, 26424.0],       [26425.0, 26426.0, 26427.0, 26428.0, 26429.0, 26430.0],       [26431.0, 26432.0, 26433.0, 26434.0, 26435.0, 26436.0],       [26437.0, 26438.0, 26439.0, 26440.0, 26441.0, 26442.0],       [26443.0, 26444.0, 26445.0, 26446.0, 26447.0, 26448.0],       [26449.0, 26450.0, 26451.0, 26452.0, 26453.0, 26454.0],       [26455.0, 26456.0, 26457.0, 26458.0, 26459.0, 26460.0]]],     [[[26461.0, 26462.0, 26463.0, 26464.0, 26465.0, 26466.0],       [26467.0, 26468.0, 26469.0, 26470.0, 26471.0, 26472.0],       [26473.0, 26474.0, 26475.0, 26476.0, 26477.0, 26478.0],       [26479.0, 26480.0, 26481.0, 26482.0, 26483.0, 26484.0],       [26485.0, 26486.0, 26487.0, 26488.0, 26489.0, 26490.0],       [26491.0, 26492.0, 26493.0, 26494.0, 26495.0, 26496.0],       [26497.0, 26498.0, 26499.0, 26500.0, 26501.0, 26502.0]],      [[26503.0, 26504.0, 26505.0, 26506.0, 26507.0, 26508.0],       [26509.0, 26510.0, 26511.0, 26512.0, 26513.0, 26514.0],       [26515.0, 26516.0, 26517.0, 26518.0, 26519.0, 26520.0],       [26521.0, 26522.0, 26523.0, 26524.0, 26525.0, 26526.0],       [26527.0, 26528.0, 26529.0, 26530.0, 26531.0, 26532.0],       [26533.0, 26534.0, 26535.0, 26536.0, 26537.0, 26538.0],       [26539.0, 26540.0, 26541.0, 26542.0, 26543.0, 26544.0]],      [[26545.0, 26546.0, 26547.0, 26548.0, 26549.0, 26550.0],       [26551.0, 26552.0, 26553.0, 26554.0, 26555.0, 26556.0],       [26557.0, 26558.0, 26559.0, 26560.0, 26561.0, 26562.0],       [26563.0, 26564.0, 26565.0, 26566.0, 26567.0, 26568.0],       [26569.0, 26570.0, 26571.0, 26572.0, 26573.0, 26574.0],       [26575.0, 26576.0, 26577.0, 26578.0, 26579.0, 26580.0],       [26581.0, 26582.0, 26583.0, 26584.0, 26585.0, 26586.0]],      [[26587.0, 26588.0, 26589.0, 26590.0, 26591.0, 26592.0],       [26593.0, 26594.0, 26595.0, 26596.0, 26597.0, 26598.0],       [26599.0, 26600.0, 26601.0, 26602.0, 26603.0, 26604.0],       [26605.0, 26606.0, 26607.0, 26608.0, 26609.0, 26610.0],       [26611.0, 26612.0, 26613.0, 26614.0, 26615.0, 26616.0],       [26617.0, 26618.0, 26619.0, 26620.0, 26621.0, 26622.0],       [26623.0, 26624.0, 26625.0, 26626.0, 26627.0, 26628.0]],      [[26629.0, 26630.0, 26631.0, 26632.0, 26633.0, 26634.0],       [26635.0, 26636.0, 26637.0, 26638.0, 26639.0, 26640.0],       [26641.0, 26642.0, 26643.0, 26644.0, 26645.0, 26646.0],       [26647.0, 26648.0, 26649.0, 26650.0, 26651.0, 26652.0],       [26653.0, 26654.0, 26655.0, 26656.0, 26657.0, 26658.0],       [26659.0, 26660.0, 26661.0, 26662.0, 26663.0, 26664.0],       [26665.0, 26666.0, 26667.0, 26668.0, 26669.0, 26670.0]],      [[26671.0, 26672.0, 26673.0, 26674.0, 26675.0, 26676.0],       [26677.0, 26678.0, 26679.0, 26680.0, 26681.0, 26682.0],       [26683.0, 26684.0, 26685.0, 26686.0, 26687.0, 26688.0],       [26689.0, 26690.0, 26691.0, 26692.0, 26693.0, 26694.0],       [26695.0, 26696.0, 26697.0, 26698.0, 26699.0, 26700.0],       [26701.0, 26702.0, 26703.0, 26704.0, 26705.0, 26706.0],       [26707.0, 26708.0, 26709.0, 26710.0, 26711.0, 26712.0]]],     [[[26713.0, 26714.0, 26715.0, 26716.0, 26717.0, 26718.0],       [26719.0, 26720.0, 26721.0, 26722.0, 26723.0, 26724.0],       [26725.0, 26726.0, 26727.0, 26728.0, 26729.0, 26730.0],       [26731.0, 26732.0, 26733.0, 26734.0, 26735.0, 26736.0],       [26737.0, 26738.0, 26739.0, 26740.0, 26741.0, 26742.0],       [26743.0, 26744.0, 26745.0, 26746.0, 26747.0, 26748.0],       [26749.0, 26750.0, 26751.0, 26752.0, 26753.0, 26754.0]],      [[26755.0, 26756.0, 26757.0, 26758.0, 26759.0, 26760.0],       [26761.0, 26762.0, 26763.0, 26764.0, 26765.0, 26766.0],       [26767.0, 26768.0, 26769.0, 26770.0, 26771.0, 26772.0],       [26773.0, 26774.0, 26775.0, 26776.0, 26777.0, 26778.0],       [26779.0, 26780.0, 26781.0, 26782.0, 26783.0, 26784.0],       [26785.0, 26786.0, 26787.0, 26788.0, 26789.0, 26790.0],       [26791.0, 26792.0, 26793.0, 26794.0, 26795.0, 26796.0]],      [[26797.0, 26798.0, 26799.0, 26800.0, 26801.0, 26802.0],       [26803.0, 26804.0, 26805.0, 26806.0, 26807.0, 26808.0],       [26809.0, 26810.0, 26811.0, 26812.0, 26813.0, 26814.0],       [26815.0, 26816.0, 26817.0, 26818.0, 26819.0, 26820.0],       [26821.0, 26822.0, 26823.0, 26824.0, 26825.0, 26826.0],       [26827.0, 26828.0, 26829.0, 26830.0, 26831.0, 26832.0],       [26833.0, 26834.0, 26835.0, 26836.0, 26837.0, 26838.0]],      [[26839.0, 26840.0, 26841.0, 26842.0, 26843.0, 26844.0],       [26845.0, 26846.0, 26847.0, 26848.0, 26849.0, 26850.0],       [26851.0, 26852.0, 26853.0, 26854.0, 26855.0, 26856.0],       [26857.0, 26858.0, 26859.0, 26860.0, 26861.0, 26862.0],       [26863.0, 26864.0, 26865.0, 26866.0, 26867.0, 26868.0],       [26869.0, 26870.0, 26871.0, 26872.0, 26873.0, 26874.0],       [26875.0, 26876.0, 26877.0, 26878.0, 26879.0, 26880.0]],      [[26881.0, 26882.0, 26883.0, 26884.0, 26885.0, 26886.0],       [26887.0, 26888.0, 26889.0, 26890.0, 26891.0, 26892.0],       [26893.0, 26894.0, 26895.0, 26896.0, 26897.0, 26898.0],       [26899.0, 26900.0, 26901.0, 26902.0, 26903.0, 26904.0],       [26905.0, 26906.0, 26907.0, 26908.0, 26909.0, 26910.0],       [26911.0, 26912.0, 26913.0, 26914.0, 26915.0, 26916.0],       [26917.0, 26918.0, 26919.0, 26920.0, 26921.0, 26922.0]],      [[26923.0, 26924.0, 26925.0, 26926.0, 26927.0, 26928.0],       [26929.0, 26930.0, 26931.0, 26932.0, 26933.0, 26934.0],       [26935.0, 26936.0, 26937.0, 26938.0, 26939.0, 26940.0],       [26941.0, 26942.0, 26943.0, 26944.0, 26945.0, 26946.0],       [26947.0, 26948.0, 26949.0, 26950.0, 26951.0, 26952.0],       [26953.0, 26954.0, 26955.0, 26956.0, 26957.0, 26958.0],       [26959.0, 26960.0, 26961.0, 26962.0, 26963.0, 26964.0]]],     [[[26965.0, 26966.0, 26967.0, 26968.0, 26969.0, 26970.0],       [26971.0, 26972.0, 26973.0, 26974.0, 26975.0, 26976.0],       [26977.0, 26978.0, 26979.0, 26980.0, 26981.0, 26982.0],       [26983.0, 26984.0, 26985.0, 26986.0, 26987.0, 26988.0],       [26989.0, 26990.0, 26991.0, 26992.0, 26993.0, 26994.0],       [26995.0, 26996.0, 26997.0, 26998.0, 26999.0, 27000.0],       [27001.0, 27002.0, 27003.0, 27004.0, 27005.0, 27006.0]],      [[27007.0, 27008.0, 27009.0, 27010.0, 27011.0, 27012.0],       [27013.0, 27014.0, 27015.0, 27016.0, 27017.0, 27018.0],       [27019.0, 27020.0, 27021.0, 27022.0, 27023.0, 27024.0],       [27025.0, 27026.0, 27027.0, 27028.0, 27029.0, 27030.0],       [27031.0, 27032.0, 27033.0, 27034.0, 27035.0, 27036.0],       [27037.0, 27038.0, 27039.0, 27040.0, 27041.0, 27042.0],       [27043.0, 27044.0, 27045.0, 27046.0, 27047.0, 27048.0]],      [[27049.0, 27050.0, 27051.0, 27052.0, 27053.0, 27054.0],       [27055.0, 27056.0, 27057.0, 27058.0, 27059.0, 27060.0],       [27061.0, 27062.0, 27063.0, 27064.0, 27065.0, 27066.0],       [27067.0, 27068.0, 27069.0, 27070.0, 27071.0, 27072.0],       [27073.0, 27074.0, 27075.0, 27076.0, 27077.0, 27078.0],       [27079.0, 27080.0, 27081.0, 27082.0, 27083.0, 27084.0],       [27085.0, 27086.0, 27087.0, 27088.0, 27089.0, 27090.0]],      [[27091.0, 27092.0, 27093.0, 27094.0, 27095.0, 27096.0],       [27097.0, 27098.0, 27099.0, 27100.0, 27101.0, 27102.0],       [27103.0, 27104.0, 27105.0, 27106.0, 27107.0, 27108.0],       [27109.0, 27110.0, 27111.0, 27112.0, 27113.0, 27114.0],       [27115.0, 27116.0, 27117.0, 27118.0, 27119.0, 27120.0],       [27121.0, 27122.0, 27123.0, 27124.0, 27125.0, 27126.0],       [27127.0, 27128.0, 27129.0, 27130.0, 27131.0, 27132.0]],      [[27133.0, 27134.0, 27135.0, 27136.0, 27137.0, 27138.0],       [27139.0, 27140.0, 27141.0, 27142.0, 27143.0, 27144.0],       [27145.0, 27146.0, 27147.0, 27148.0, 27149.0, 27150.0],       [27151.0, 27152.0, 27153.0, 27154.0, 27155.0, 27156.0],       [27157.0, 27158.0, 27159.0, 27160.0, 27161.0, 27162.0],       [27163.0, 27164.0, 27165.0, 27166.0, 27167.0, 27168.0],       [27169.0, 27170.0, 27171.0, 27172.0, 27173.0, 27174.0]],      [[27175.0, 27176.0, 27177.0, 27178.0, 27179.0, 27180.0],       [27181.0, 27182.0, 27183.0, 27184.0, 27185.0, 27186.0],       [27187.0, 27188.0, 27189.0, 27190.0, 27191.0, 27192.0],       [27193.0, 27194.0, 27195.0, 27196.0, 27197.0, 27198.0],       [27199.0, 27200.0, 27201.0, 27202.0, 27203.0, 27204.0],       [27205.0, 27206.0, 27207.0, 27208.0, 27209.0, 27210.0],       [27211.0, 27212.0, 27213.0, 27214.0, 27215.0, 27216.0]]]],    [[[[27217.0, 27218.0, 27219.0, 27220.0, 27221.0, 27222.0],       [27223.0, 27224.0, 27225.0, 27226.0, 27227.0, 27228.0],       [27229.0, 27230.0, 27231.0, 27232.0, 27233.0, 27234.0],       [27235.0, 27236.0, 27237.0, 27238.0, 27239.0, 27240.0],       [27241.0, 27242.0, 27243.0, 27244.0, 27245.0, 27246.0],       [27247.0, 27248.0, 27249.0, 27250.0, 27251.0, 27252.0],       [27253.0, 27254.0, 27255.0, 27256.0, 27257.0, 27258.0]],      [[27259.0, 27260.0, 27261.0, 27262.0, 27263.0, 27264.0],       [27265.0, 27266.0, 27267.0, 27268.0, 27269.0, 27270.0],       [27271.0, 27272.0, 27273.0, 27274.0, 27275.0, 27276.0],       [27277.0, 27278.0, 27279.0, 27280.0, 27281.0, 27282.0],       [27283.0, 27284.0, 27285.0, 27286.0, 27287.0, 27288.0],       [27289.0, 27290.0, 27291.0, 27292.0, 27293.0, 27294.0],       [27295.0, 27296.0, 27297.0, 27298.0, 27299.0, 27300.0]],      [[27301.0, 27302.0, 27303.0, 27304.0, 27305.0, 27306.0],       [27307.0, 27308.0, 27309.0, 27310.0, 27311.0, 27312.0],       [27313.0, 27314.0, 27315.0, 27316.0, 27317.0, 27318.0],       [27319.0, 27320.0, 27321.0, 27322.0, 27323.0, 27324.0],       [27325.0, 27326.0, 27327.0, 27328.0, 27329.0, 27330.0],       [27331.0, 27332.0, 27333.0, 27334.0, 27335.0, 27336.0],       [27337.0, 27338.0, 27339.0, 27340.0, 27341.0, 27342.0]],      [[27343.0, 27344.0, 27345.0, 27346.0, 27347.0, 27348.0],       [27349.0, 27350.0, 27351.0, 27352.0, 27353.0, 27354.0],       [27355.0, 27356.0, 27357.0, 27358.0, 27359.0, 27360.0],       [27361.0, 27362.0, 27363.0, 27364.0, 27365.0, 27366.0],       [27367.0, 27368.0, 27369.0, 27370.0, 27371.0, 27372.0],       [27373.0, 27374.0, 27375.0, 27376.0, 27377.0, 27378.0],       [27379.0, 27380.0, 27381.0, 27382.0, 27383.0, 27384.0]],      [[27385.0, 27386.0, 27387.0, 27388.0, 27389.0, 27390.0],       [27391.0, 27392.0, 27393.0, 27394.0, 27395.0, 27396.0],       [27397.0, 27398.0, 27399.0, 27400.0, 27401.0, 27402.0],       [27403.0, 27404.0, 27405.0, 27406.0, 27407.0, 27408.0],       [27409.0, 27410.0, 27411.0, 27412.0, 27413.0, 27414.0],       [27415.0, 27416.0, 27417.0, 27418.0, 27419.0, 27420.0],       [27421.0, 27422.0, 27423.0, 27424.0, 27425.0, 27426.0]],      [[27427.0, 27428.0, 27429.0, 27430.0, 27431.0, 27432.0],       [27433.0, 27434.0, 27435.0, 27436.0, 27437.0, 27438.0],       [27439.0, 27440.0, 27441.0, 27442.0, 27443.0, 27444.0],       [27445.0, 27446.0, 27447.0, 27448.0, 27449.0, 27450.0],       [27451.0, 27452.0, 27453.0, 27454.0, 27455.0, 27456.0],       [27457.0, 27458.0, 27459.0, 27460.0, 27461.0, 27462.0],       [27463.0, 27464.0, 27465.0, 27466.0, 27467.0, 27468.0]]],     [[[27469.0, 27470.0, 27471.0, 27472.0, 27473.0, 27474.0],       [27475.0, 27476.0, 27477.0, 27478.0, 27479.0, 27480.0],       [27481.0, 27482.0, 27483.0, 27484.0, 27485.0, 27486.0],       [27487.0, 27488.0, 27489.0, 27490.0, 27491.0, 27492.0],       [27493.0, 27494.0, 27495.0, 27496.0, 27497.0, 27498.0],       [27499.0, 27500.0, 27501.0, 27502.0, 27503.0, 27504.0],       [27505.0, 27506.0, 27507.0, 27508.0, 27509.0, 27510.0]],      [[27511.0, 27512.0, 27513.0, 27514.0, 27515.0, 27516.0],       [27517.0, 27518.0, 27519.0, 27520.0, 27521.0, 27522.0],       [27523.0, 27524.0, 27525.0, 27526.0, 27527.0, 27528.0],       [27529.0, 27530.0, 27531.0, 27532.0, 27533.0, 27534.0],       [27535.0, 27536.0, 27537.0, 27538.0, 27539.0, 27540.0],       [27541.0, 27542.0, 27543.0, 27544.0, 27545.0, 27546.0],       [27547.0, 27548.0, 27549.0, 27550.0, 27551.0, 27552.0]],      [[27553.0, 27554.0, 27555.0, 27556.0, 27557.0, 27558.0],       [27559.0, 27560.0, 27561.0, 27562.0, 27563.0, 27564.0],       [27565.0, 27566.0, 27567.0, 27568.0, 27569.0, 27570.0],       [27571.0, 27572.0, 27573.0, 27574.0, 27575.0, 27576.0],       [27577.0, 27578.0, 27579.0, 27580.0, 27581.0, 27582.0],       [27583.0, 27584.0, 27585.0, 27586.0, 27587.0, 27588.0],       [27589.0, 27590.0, 27591.0, 27592.0, 27593.0, 27594.0]],      [[27595.0, 27596.0, 27597.0, 27598.0, 27599.0, 27600.0],       [27601.0, 27602.0, 27603.0, 27604.0, 27605.0, 27606.0],       [27607.0, 27608.0, 27609.0, 27610.0, 27611.0, 27612.0],       [27613.0, 27614.0, 27615.0, 27616.0, 27617.0, 27618.0],       [27619.0, 27620.0, 27621.0, 27622.0, 27623.0, 27624.0],       [27625.0, 27626.0, 27627.0, 27628.0, 27629.0, 27630.0],       [27631.0, 27632.0, 27633.0, 27634.0, 27635.0, 27636.0]],      [[27637.0, 27638.0, 27639.0, 27640.0, 27641.0, 27642.0],       [27643.0, 27644.0, 27645.0, 27646.0, 27647.0, 27648.0],       [27649.0, 27650.0, 27651.0, 27652.0, 27653.0, 27654.0],       [27655.0, 27656.0, 27657.0, 27658.0, 27659.0, 27660.0],       [27661.0, 27662.0, 27663.0, 27664.0, 27665.0, 27666.0],       [27667.0, 27668.0, 27669.0, 27670.0, 27671.0, 27672.0],       [27673.0, 27674.0, 27675.0, 27676.0, 27677.0, 27678.0]],      [[27679.0, 27680.0, 27681.0, 27682.0, 27683.0, 27684.0],       [27685.0, 27686.0, 27687.0, 27688.0, 27689.0, 27690.0],       [27691.0, 27692.0, 27693.0, 27694.0, 27695.0, 27696.0],       [27697.0, 27698.0, 27699.0, 27700.0, 27701.0, 27702.0],       [27703.0, 27704.0, 27705.0, 27706.0, 27707.0, 27708.0],       [27709.0, 27710.0, 27711.0, 27712.0, 27713.0, 27714.0],       [27715.0, 27716.0, 27717.0, 27718.0, 27719.0, 27720.0]]],     [[[27721.0, 27722.0, 27723.0, 27724.0, 27725.0, 27726.0],       [27727.0, 27728.0, 27729.0, 27730.0, 27731.0, 27732.0],       [27733.0, 27734.0, 27735.0, 27736.0, 27737.0, 27738.0],       [27739.0, 27740.0, 27741.0, 27742.0, 27743.0, 27744.0],       [27745.0, 27746.0, 27747.0, 27748.0, 27749.0, 27750.0],       [27751.0, 27752.0, 27753.0, 27754.0, 27755.0, 27756.0],       [27757.0, 27758.0, 27759.0, 27760.0, 27761.0, 27762.0]],      [[27763.0, 27764.0, 27765.0, 27766.0, 27767.0, 27768.0],       [27769.0, 27770.0, 27771.0, 27772.0, 27773.0, 27774.0],       [27775.0, 27776.0, 27777.0, 27778.0, 27779.0, 27780.0],       [27781.0, 27782.0, 27783.0, 27784.0, 27785.0, 27786.0],       [27787.0, 27788.0, 27789.0, 27790.0, 27791.0, 27792.0],       [27793.0, 27794.0, 27795.0, 27796.0, 27797.0, 27798.0],       [27799.0, 27800.0, 27801.0, 27802.0, 27803.0, 27804.0]],      [[27805.0, 27806.0, 27807.0, 27808.0, 27809.0, 27810.0],       [27811.0, 27812.0, 27813.0, 27814.0, 27815.0, 27816.0],       [27817.0, 27818.0, 27819.0, 27820.0, 27821.0, 27822.0],       [27823.0, 27824.0, 27825.0, 27826.0, 27827.0, 27828.0],       [27829.0, 27830.0, 27831.0, 27832.0, 27833.0, 27834.0],       [27835.0, 27836.0, 27837.0, 27838.0, 27839.0, 27840.0],       [27841.0, 27842.0, 27843.0, 27844.0, 27845.0, 27846.0]],      [[27847.0, 27848.0, 27849.0, 27850.0, 27851.0, 27852.0],       [27853.0, 27854.0, 27855.0, 27856.0, 27857.0, 27858.0],       [27859.0, 27860.0, 27861.0, 27862.0, 27863.0, 27864.0],       [27865.0, 27866.0, 27867.0, 27868.0, 27869.0, 27870.0],       [27871.0, 27872.0, 27873.0, 27874.0, 27875.0, 27876.0],       [27877.0, 27878.0, 27879.0, 27880.0, 27881.0, 27882.0],       [27883.0, 27884.0, 27885.0, 27886.0, 27887.0, 27888.0]],      [[27889.0, 27890.0, 27891.0, 27892.0, 27893.0, 27894.0],       [27895.0, 27896.0, 27897.0, 27898.0, 27899.0, 27900.0],       [27901.0, 27902.0, 27903.0, 27904.0, 27905.0, 27906.0],       [27907.0, 27908.0, 27909.0, 27910.0, 27911.0, 27912.0],       [27913.0, 27914.0, 27915.0, 27916.0, 27917.0, 27918.0],       [27919.0, 27920.0, 27921.0, 27922.0, 27923.0, 27924.0],       [27925.0, 27926.0, 27927.0, 27928.0, 27929.0, 27930.0]],      [[27931.0, 27932.0, 27933.0, 27934.0, 27935.0, 27936.0],       [27937.0, 27938.0, 27939.0, 27940.0, 27941.0, 27942.0],       [27943.0, 27944.0, 27945.0, 27946.0, 27947.0, 27948.0],       [27949.0, 27950.0, 27951.0, 27952.0, 27953.0, 27954.0],       [27955.0, 27956.0, 27957.0, 27958.0, 27959.0, 27960.0],       [27961.0, 27962.0, 27963.0, 27964.0, 27965.0, 27966.0],       [27967.0, 27968.0, 27969.0, 27970.0, 27971.0, 27972.0]]],     [[[27973.0, 27974.0, 27975.0, 27976.0, 27977.0, 27978.0],       [27979.0, 27980.0, 27981.0, 27982.0, 27983.0, 27984.0],       [27985.0, 27986.0, 27987.0, 27988.0, 27989.0, 27990.0],       [27991.0, 27992.0, 27993.0, 27994.0, 27995.0, 27996.0],       [27997.0, 27998.0, 27999.0, 28000.0, 28001.0, 28002.0],       [28003.0, 28004.0, 28005.0, 28006.0, 28007.0, 28008.0],       [28009.0, 28010.0, 28011.0, 28012.0, 28013.0, 28014.0]],      [[28015.0, 28016.0, 28017.0, 28018.0, 28019.0, 28020.0],       [28021.0, 28022.0, 28023.0, 28024.0, 28025.0, 28026.0],       [28027.0, 28028.0, 28029.0, 28030.0, 28031.0, 28032.0],       [28033.0, 28034.0, 28035.0, 28036.0, 28037.0, 28038.0],       [28039.0, 28040.0, 28041.0, 28042.0, 28043.0, 28044.0],       [28045.0, 28046.0, 28047.0, 28048.0, 28049.0, 28050.0],       [28051.0, 28052.0, 28053.0, 28054.0, 28055.0, 28056.0]],      [[28057.0, 28058.0, 28059.0, 28060.0, 28061.0, 28062.0],       [28063.0, 28064.0, 28065.0, 28066.0, 28067.0, 28068.0],       [28069.0, 28070.0, 28071.0, 28072.0, 28073.0, 28074.0],       [28075.0, 28076.0, 28077.0, 28078.0, 28079.0, 28080.0],       [28081.0, 28082.0, 28083.0, 28084.0, 28085.0, 28086.0],       [28087.0, 28088.0, 28089.0, 28090.0, 28091.0, 28092.0],       [28093.0, 28094.0, 28095.0, 28096.0, 28097.0, 28098.0]],      [[28099.0, 28100.0, 28101.0, 28102.0, 28103.0, 28104.0],       [28105.0, 28106.0, 28107.0, 28108.0, 28109.0, 28110.0],       [28111.0, 28112.0, 28113.0, 28114.0, 28115.0, 28116.0],       [28117.0, 28118.0, 28119.0, 28120.0, 28121.0, 28122.0],       [28123.0, 28124.0, 28125.0, 28126.0, 28127.0, 28128.0],       [28129.0, 28130.0, 28131.0, 28132.0, 28133.0, 28134.0],       [28135.0, 28136.0, 28137.0, 28138.0, 28139.0, 28140.0]],      [[28141.0, 28142.0, 28143.0, 28144.0, 28145.0, 28146.0],       [28147.0, 28148.0, 28149.0, 28150.0, 28151.0, 28152.0],       [28153.0, 28154.0, 28155.0, 28156.0, 28157.0, 28158.0],       [28159.0, 28160.0, 28161.0, 28162.0, 28163.0, 28164.0],       [28165.0, 28166.0, 28167.0, 28168.0, 28169.0, 28170.0],       [28171.0, 28172.0, 28173.0, 28174.0, 28175.0, 28176.0],       [28177.0, 28178.0, 28179.0, 28180.0, 28181.0, 28182.0]],      [[28183.0, 28184.0, 28185.0, 28186.0, 28187.0, 28188.0],       [28189.0, 28190.0, 28191.0, 28192.0, 28193.0, 28194.0],       [28195.0, 28196.0, 28197.0, 28198.0, 28199.0, 28200.0],       [28201.0, 28202.0, 28203.0, 28204.0, 28205.0, 28206.0],       [28207.0, 28208.0, 28209.0, 28210.0, 28211.0, 28212.0],       [28213.0, 28214.0, 28215.0, 28216.0, 28217.0, 28218.0],       [28219.0, 28220.0, 28221.0, 28222.0, 28223.0, 28224.0]]]],    [[[[28225.0, 28226.0, 28227.0, 28228.0, 28229.0, 28230.0],       [28231.0, 28232.0, 28233.0, 28234.0, 28235.0, 28236.0],       [28237.0, 28238.0, 28239.0, 28240.0, 28241.0, 28242.0],       [28243.0, 28244.0, 28245.0, 28246.0, 28247.0, 28248.0],       [28249.0, 28250.0, 28251.0, 28252.0, 28253.0, 28254.0],       [28255.0, 28256.0, 28257.0, 28258.0, 28259.0, 28260.0],       [28261.0, 28262.0, 28263.0, 28264.0, 28265.0, 28266.0]],      [[28267.0, 28268.0, 28269.0, 28270.0, 28271.0, 28272.0],       [28273.0, 28274.0, 28275.0, 28276.0, 28277.0, 28278.0],       [28279.0, 28280.0, 28281.0, 28282.0, 28283.0, 28284.0],       [28285.0, 28286.0, 28287.0, 28288.0, 28289.0, 28290.0],       [28291.0, 28292.0, 28293.0, 28294.0, 28295.0, 28296.0],       [28297.0, 28298.0, 28299.0, 28300.0, 28301.0, 28302.0],       [28303.0, 28304.0, 28305.0, 28306.0, 28307.0, 28308.0]],      [[28309.0, 28310.0, 28311.0, 28312.0, 28313.0, 28314.0],       [28315.0, 28316.0, 28317.0, 28318.0, 28319.0, 28320.0],       [28321.0, 28322.0, 28323.0, 28324.0, 28325.0, 28326.0],       [28327.0, 28328.0, 28329.0, 28330.0, 28331.0, 28332.0],       [28333.0, 28334.0, 28335.0, 28336.0, 28337.0, 28338.0],       [28339.0, 28340.0, 28341.0, 28342.0, 28343.0, 28344.0],       [28345.0, 28346.0, 28347.0, 28348.0, 28349.0, 28350.0]],      [[28351.0, 28352.0, 28353.0, 28354.0, 28355.0, 28356.0],       [28357.0, 28358.0, 28359.0, 28360.0, 28361.0, 28362.0],       [28363.0, 28364.0, 28365.0, 28366.0, 28367.0, 28368.0],       [28369.0, 28370.0, 28371.0, 28372.0, 28373.0, 28374.0],       [28375.0, 28376.0, 28377.0, 28378.0, 28379.0, 28380.0],       [28381.0, 28382.0, 28383.0, 28384.0, 28385.0, 28386.0],       [28387.0, 28388.0, 28389.0, 28390.0, 28391.0, 28392.0]],      [[28393.0, 28394.0, 28395.0, 28396.0, 28397.0, 28398.0],       [28399.0, 28400.0, 28401.0, 28402.0, 28403.0, 28404.0],       [28405.0, 28406.0, 28407.0, 28408.0, 28409.0, 28410.0],       [28411.0, 28412.0, 28413.0, 28414.0, 28415.0, 28416.0],       [28417.0, 28418.0, 28419.0, 28420.0, 28421.0, 28422.0],       [28423.0, 28424.0, 28425.0, 28426.0, 28427.0, 28428.0],       [28429.0, 28430.0, 28431.0, 28432.0, 28433.0, 28434.0]],      [[28435.0, 28436.0, 28437.0, 28438.0, 28439.0, 28440.0],       [28441.0, 28442.0, 28443.0, 28444.0, 28445.0, 28446.0],       [28447.0, 28448.0, 28449.0, 28450.0, 28451.0, 28452.0],       [28453.0, 28454.0, 28455.0, 28456.0, 28457.0, 28458.0],       [28459.0, 28460.0, 28461.0, 28462.0, 28463.0, 28464.0],       [28465.0, 28466.0, 28467.0, 28468.0, 28469.0, 28470.0],       [28471.0, 28472.0, 28473.0, 28474.0, 28475.0, 28476.0]]],     [[[28477.0, 28478.0, 28479.0, 28480.0, 28481.0, 28482.0],       [28483.0, 28484.0, 28485.0, 28486.0, 28487.0, 28488.0],       [28489.0, 28490.0, 28491.0, 28492.0, 28493.0, 28494.0],       [28495.0, 28496.0, 28497.0, 28498.0, 28499.0, 28500.0],       [28501.0, 28502.0, 28503.0, 28504.0, 28505.0, 28506.0],       [28507.0, 28508.0, 28509.0, 28510.0, 28511.0, 28512.0],       [28513.0, 28514.0, 28515.0, 28516.0, 28517.0, 28518.0]],      [[28519.0, 28520.0, 28521.0, 28522.0, 28523.0, 28524.0],       [28525.0, 28526.0, 28527.0, 28528.0, 28529.0, 28530.0],       [28531.0, 28532.0, 28533.0, 28534.0, 28535.0, 28536.0],       [28537.0, 28538.0, 28539.0, 28540.0, 28541.0, 28542.0],       [28543.0, 28544.0, 28545.0, 28546.0, 28547.0, 28548.0],       [28549.0, 28550.0, 28551.0, 28552.0, 28553.0, 28554.0],       [28555.0, 28556.0, 28557.0, 28558.0, 28559.0, 28560.0]],      [[28561.0, 28562.0, 28563.0, 28564.0, 28565.0, 28566.0],       [28567.0, 28568.0, 28569.0, 28570.0, 28571.0, 28572.0],       [28573.0, 28574.0, 28575.0, 28576.0, 28577.0, 28578.0],       [28579.0, 28580.0, 28581.0, 28582.0, 28583.0, 28584.0],       [28585.0, 28586.0, 28587.0, 28588.0, 28589.0, 28590.0],       [28591.0, 28592.0, 28593.0, 28594.0, 28595.0, 28596.0],       [28597.0, 28598.0, 28599.0, 28600.0, 28601.0, 28602.0]],      [[28603.0, 28604.0, 28605.0, 28606.0, 28607.0, 28608.0],       [28609.0, 28610.0, 28611.0, 28612.0, 28613.0, 28614.0],       [28615.0, 28616.0, 28617.0, 28618.0, 28619.0, 28620.0],       [28621.0, 28622.0, 28623.0, 28624.0, 28625.0, 28626.0],       [28627.0, 28628.0, 28629.0, 28630.0, 28631.0, 28632.0],       [28633.0, 28634.0, 28635.0, 28636.0, 28637.0, 28638.0],       [28639.0, 28640.0, 28641.0, 28642.0, 28643.0, 28644.0]],      [[28645.0, 28646.0, 28647.0, 28648.0, 28649.0, 28650.0],       [28651.0, 28652.0, 28653.0, 28654.0, 28655.0, 28656.0],       [28657.0, 28658.0, 28659.0, 28660.0, 28661.0, 28662.0],       [28663.0, 28664.0, 28665.0, 28666.0, 28667.0, 28668.0],       [28669.0, 28670.0, 28671.0, 28672.0, 28673.0, 28674.0],       [28675.0, 28676.0, 28677.0, 28678.0, 28679.0, 28680.0],       [28681.0, 28682.0, 28683.0, 28684.0, 28685.0, 28686.0]],      [[28687.0, 28688.0, 28689.0, 28690.0, 28691.0, 28692.0],       [28693.0, 28694.0, 28695.0, 28696.0, 28697.0, 28698.0],       [28699.0, 28700.0, 28701.0, 28702.0, 28703.0, 28704.0],       [28705.0, 28706.0, 28707.0, 28708.0, 28709.0, 28710.0],       [28711.0, 28712.0, 28713.0, 28714.0, 28715.0, 28716.0],       [28717.0, 28718.0, 28719.0, 28720.0, 28721.0, 28722.0],       [28723.0, 28724.0, 28725.0, 28726.0, 28727.0, 28728.0]]],     [[[28729.0, 28730.0, 28731.0, 28732.0, 28733.0, 28734.0],       [28735.0, 28736.0, 28737.0, 28738.0, 28739.0, 28740.0],       [28741.0, 28742.0, 28743.0, 28744.0, 28745.0, 28746.0],       [28747.0, 28748.0, 28749.0, 28750.0, 28751.0, 28752.0],       [28753.0, 28754.0, 28755.0, 28756.0, 28757.0, 28758.0],       [28759.0, 28760.0, 28761.0, 28762.0, 28763.0, 28764.0],       [28765.0, 28766.0, 28767.0, 28768.0, 28769.0, 28770.0]],      [[28771.0, 28772.0, 28773.0, 28774.0, 28775.0, 28776.0],       [28777.0, 28778.0, 28779.0, 28780.0, 28781.0, 28782.0],       [28783.0, 28784.0, 28785.0, 28786.0, 28787.0, 28788.0],       [28789.0, 28790.0, 28791.0, 28792.0, 28793.0, 28794.0],       [28795.0, 28796.0, 28797.0, 28798.0, 28799.0, 28800.0],       [28801.0, 28802.0, 28803.0, 28804.0, 28805.0, 28806.0],       [28807.0, 28808.0, 28809.0, 28810.0, 28811.0, 28812.0]],      [[28813.0, 28814.0, 28815.0, 28816.0, 28817.0, 28818.0],       [28819.0, 28820.0, 28821.0, 28822.0, 28823.0, 28824.0],       [28825.0, 28826.0, 28827.0, 28828.0, 28829.0, 28830.0],       [28831.0, 28832.0, 28833.0, 28834.0, 28835.0, 28836.0],       [28837.0, 28838.0, 28839.0, 28840.0, 28841.0, 28842.0],       [28843.0, 28844.0, 28845.0, 28846.0, 28847.0, 28848.0],       [28849.0, 28850.0, 28851.0, 28852.0, 28853.0, 28854.0]],      [[28855.0, 28856.0, 28857.0, 28858.0, 28859.0, 28860.0],       [28861.0, 28862.0, 28863.0, 28864.0, 28865.0, 28866.0],       [28867.0, 28868.0, 28869.0, 28870.0, 28871.0, 28872.0],       [28873.0, 28874.0, 28875.0, 28876.0, 28877.0, 28878.0],       [28879.0, 28880.0, 28881.0, 28882.0, 28883.0, 28884.0],       [28885.0, 28886.0, 28887.0, 28888.0, 28889.0, 28890.0],       [28891.0, 28892.0, 28893.0, 28894.0, 28895.0, 28896.0]],      [[28897.0, 28898.0, 28899.0, 28900.0, 28901.0, 28902.0],       [28903.0, 28904.0, 28905.0, 28906.0, 28907.0, 28908.0],       [28909.0, 28910.0, 28911.0, 28912.0, 28913.0, 28914.0],       [28915.0, 28916.0, 28917.0, 28918.0, 28919.0, 28920.0],       [28921.0, 28922.0, 28923.0, 28924.0, 28925.0, 28926.0],       [28927.0, 28928.0, 28929.0, 28930.0, 28931.0, 28932.0],       [28933.0, 28934.0, 28935.0, 28936.0, 28937.0, 28938.0]],      [[28939.0, 28940.0, 28941.0, 28942.0, 28943.0, 28944.0],       [28945.0, 28946.0, 28947.0, 28948.0, 28949.0, 28950.0],       [28951.0, 28952.0, 28953.0, 28954.0, 28955.0, 28956.0],       [28957.0, 28958.0, 28959.0, 28960.0, 28961.0, 28962.0],       [28963.0, 28964.0, 28965.0, 28966.0, 28967.0, 28968.0],       [28969.0, 28970.0, 28971.0, 28972.0, 28973.0, 28974.0],       [28975.0, 28976.0, 28977.0, 28978.0, 28979.0, 28980.0]]],     [[[28981.0, 28982.0, 28983.0, 28984.0, 28985.0, 28986.0],       [28987.0, 28988.0, 28989.0, 28990.0, 28991.0, 28992.0],       [28993.0, 28994.0, 28995.0, 28996.0, 28997.0, 28998.0],       [28999.0, 29000.0, 29001.0, 29002.0, 29003.0, 29004.0],       [29005.0, 29006.0, 29007.0, 29008.0, 29009.0, 29010.0],       [29011.0, 29012.0, 29013.0, 29014.0, 29015.0, 29016.0],       [29017.0, 29018.0, 29019.0, 29020.0, 29021.0, 29022.0]],      [[29023.0, 29024.0, 29025.0, 29026.0, 29027.0, 29028.0],       [29029.0, 29030.0, 29031.0, 29032.0, 29033.0, 29034.0],       [29035.0, 29036.0, 29037.0, 29038.0, 29039.0, 29040.0],       [29041.0, 29042.0, 29043.0, 29044.0, 29045.0, 29046.0],       [29047.0, 29048.0, 29049.0, 29050.0, 29051.0, 29052.0],       [29053.0, 29054.0, 29055.0, 29056.0, 29057.0, 29058.0],       [29059.0, 29060.0, 29061.0, 29062.0, 29063.0, 29064.0]],      [[29065.0, 29066.0, 29067.0, 29068.0, 29069.0, 29070.0],       [29071.0, 29072.0, 29073.0, 29074.0, 29075.0, 29076.0],       [29077.0, 29078.0, 29079.0, 29080.0, 29081.0, 29082.0],       [29083.0, 29084.0, 29085.0, 29086.0, 29087.0, 29088.0],       [29089.0, 29090.0, 29091.0, 29092.0, 29093.0, 29094.0],       [29095.0, 29096.0, 29097.0, 29098.0, 29099.0, 29100.0],       [29101.0, 29102.0, 29103.0, 29104.0, 29105.0, 29106.0]],      [[29107.0, 29108.0, 29109.0, 29110.0, 29111.0, 29112.0],       [29113.0, 29114.0, 29115.0, 29116.0, 29117.0, 29118.0],       [29119.0, 29120.0, 29121.0, 29122.0, 29123.0, 29124.0],       [29125.0, 29126.0, 29127.0, 29128.0, 29129.0, 29130.0],       [29131.0, 29132.0, 29133.0, 29134.0, 29135.0, 29136.0],       [29137.0, 29138.0, 29139.0, 29140.0, 29141.0, 29142.0],       [29143.0, 29144.0, 29145.0, 29146.0, 29147.0, 29148.0]],      [[29149.0, 29150.0, 29151.0, 29152.0, 29153.0, 29154.0],       [29155.0, 29156.0, 29157.0, 29158.0, 29159.0, 29160.0],       [29161.0, 29162.0, 29163.0, 29164.0, 29165.0, 29166.0],       [29167.0, 29168.0, 29169.0, 29170.0, 29171.0, 29172.0],       [29173.0, 29174.0, 29175.0, 29176.0, 29177.0, 29178.0],       [29179.0, 29180.0, 29181.0, 29182.0, 29183.0, 29184.0],       [29185.0, 29186.0, 29187.0, 29188.0, 29189.0, 29190.0]],      [[29191.0, 29192.0, 29193.0, 29194.0, 29195.0, 29196.0],       [29197.0, 29198.0, 29199.0, 29200.0, 29201.0, 29202.0],       [29203.0, 29204.0, 29205.0, 29206.0, 29207.0, 29208.0],       [29209.0, 29210.0, 29211.0, 29212.0, 29213.0, 29214.0],       [29215.0, 29216.0, 29217.0, 29218.0, 29219.0, 29220.0],       [29221.0, 29222.0, 29223.0, 29224.0, 29225.0, 29226.0],       [29227.0, 29228.0, 29229.0, 29230.0, 29231.0, 29232.0]]]],    [[[[29233.0, 29234.0, 29235.0, 29236.0, 29237.0, 29238.0],       [29239.0, 29240.0, 29241.0, 29242.0, 29243.0, 29244.0],       [29245.0, 29246.0, 29247.0, 29248.0, 29249.0, 29250.0],       [29251.0, 29252.0, 29253.0, 29254.0, 29255.0, 29256.0],       [29257.0, 29258.0, 29259.0, 29260.0, 29261.0, 29262.0],       [29263.0, 29264.0, 29265.0, 29266.0, 29267.0, 29268.0],       [29269.0, 29270.0, 29271.0, 29272.0, 29273.0, 29274.0]],      [[29275.0, 29276.0, 29277.0, 29278.0, 29279.0, 29280.0],       [29281.0, 29282.0, 29283.0, 29284.0, 29285.0, 29286.0],       [29287.0, 29288.0, 29289.0, 29290.0, 29291.0, 29292.0],       [29293.0, 29294.0, 29295.0, 29296.0, 29297.0, 29298.0],       [29299.0, 29300.0, 29301.0, 29302.0, 29303.0, 29304.0],       [29305.0, 29306.0, 29307.0, 29308.0, 29309.0, 29310.0],       [29311.0, 29312.0, 29313.0, 29314.0, 29315.0, 29316.0]],      [[29317.0, 29318.0, 29319.0, 29320.0, 29321.0, 29322.0],       [29323.0, 29324.0, 29325.0, 29326.0, 29327.0, 29328.0],       [29329.0, 29330.0, 29331.0, 29332.0, 29333.0, 29334.0],       [29335.0, 29336.0, 29337.0, 29338.0, 29339.0, 29340.0],       [29341.0, 29342.0, 29343.0, 29344.0, 29345.0, 29346.0],       [29347.0, 29348.0, 29349.0, 29350.0, 29351.0, 29352.0],       [29353.0, 29354.0, 29355.0, 29356.0, 29357.0, 29358.0]],      [[29359.0, 29360.0, 29361.0, 29362.0, 29363.0, 29364.0],       [29365.0, 29366.0, 29367.0, 29368.0, 29369.0, 29370.0],       [29371.0, 29372.0, 29373.0, 29374.0, 29375.0, 29376.0],       [29377.0, 29378.0, 29379.0, 29380.0, 29381.0, 29382.0],       [29383.0, 29384.0, 29385.0, 29386.0, 29387.0, 29388.0],       [29389.0, 29390.0, 29391.0, 29392.0, 29393.0, 29394.0],       [29395.0, 29396.0, 29397.0, 29398.0, 29399.0, 29400.0]],      [[29401.0, 29402.0, 29403.0, 29404.0, 29405.0, 29406.0],       [29407.0, 29408.0, 29409.0, 29410.0, 29411.0, 29412.0],       [29413.0, 29414.0, 29415.0, 29416.0, 29417.0, 29418.0],       [29419.0, 29420.0, 29421.0, 29422.0, 29423.0, 29424.0],       [29425.0, 29426.0, 29427.0, 29428.0, 29429.0, 29430.0],       [29431.0, 29432.0, 29433.0, 29434.0, 29435.0, 29436.0],       [29437.0, 29438.0, 29439.0, 29440.0, 29441.0, 29442.0]],      [[29443.0, 29444.0, 29445.0, 29446.0, 29447.0, 29448.0],       [29449.0, 29450.0, 29451.0, 29452.0, 29453.0, 29454.0],       [29455.0, 29456.0, 29457.0, 29458.0, 29459.0, 29460.0],       [29461.0, 29462.0, 29463.0, 29464.0, 29465.0, 29466.0],       [29467.0, 29468.0, 29469.0, 29470.0, 29471.0, 29472.0],       [29473.0, 29474.0, 29475.0, 29476.0, 29477.0, 29478.0],       [29479.0, 29480.0, 29481.0, 29482.0, 29483.0, 29484.0]]],     [[[29485.0, 29486.0, 29487.0, 29488.0, 29489.0, 29490.0],       [29491.0, 29492.0, 29493.0, 29494.0, 29495.0, 29496.0],       [29497.0, 29498.0, 29499.0, 29500.0, 29501.0, 29502.0],       [29503.0, 29504.0, 29505.0, 29506.0, 29507.0, 29508.0],       [29509.0, 29510.0, 29511.0, 29512.0, 29513.0, 29514.0],       [29515.0, 29516.0, 29517.0, 29518.0, 29519.0, 29520.0],       [29521.0, 29522.0, 29523.0, 29524.0, 29525.0, 29526.0]],      [[29527.0, 29528.0, 29529.0, 29530.0, 29531.0, 29532.0],       [29533.0, 29534.0, 29535.0, 29536.0, 29537.0, 29538.0],       [29539.0, 29540.0, 29541.0, 29542.0, 29543.0, 29544.0],       [29545.0, 29546.0, 29547.0, 29548.0, 29549.0, 29550.0],       [29551.0, 29552.0, 29553.0, 29554.0, 29555.0, 29556.0],       [29557.0, 29558.0, 29559.0, 29560.0, 29561.0, 29562.0],       [29563.0, 29564.0, 29565.0, 29566.0, 29567.0, 29568.0]],      [[29569.0, 29570.0, 29571.0, 29572.0, 29573.0, 29574.0],       [29575.0, 29576.0, 29577.0, 29578.0, 29579.0, 29580.0],       [29581.0, 29582.0, 29583.0, 29584.0, 29585.0, 29586.0],       [29587.0, 29588.0, 29589.0, 29590.0, 29591.0, 29592.0],       [29593.0, 29594.0, 29595.0, 29596.0, 29597.0, 29598.0],       [29599.0, 29600.0, 29601.0, 29602.0, 29603.0, 29604.0],       [29605.0, 29606.0, 29607.0, 29608.0, 29609.0, 29610.0]],      [[29611.0, 29612.0, 29613.0, 29614.0, 29615.0, 29616.0],       [29617.0, 29618.0, 29619.0, 29620.0, 29621.0, 29622.0],       [29623.0, 29624.0, 29625.0, 29626.0, 29627.0, 29628.0],       [29629.0, 29630.0, 29631.0, 29632.0, 29633.0, 29634.0],       [29635.0, 29636.0, 29637.0, 29638.0, 29639.0, 29640.0],       [29641.0, 29642.0, 29643.0, 29644.0, 29645.0, 29646.0],       [29647.0, 29648.0, 29649.0, 29650.0, 29651.0, 29652.0]],      [[29653.0, 29654.0, 29655.0, 29656.0, 29657.0, 29658.0],       [29659.0, 29660.0, 29661.0, 29662.0, 29663.0, 29664.0],       [29665.0, 29666.0, 29667.0, 29668.0, 29669.0, 29670.0],       [29671.0, 29672.0, 29673.0, 29674.0, 29675.0, 29676.0],       [29677.0, 29678.0, 29679.0, 29680.0, 29681.0, 29682.0],       [29683.0, 29684.0, 29685.0, 29686.0, 29687.0, 29688.0],       [29689.0, 29690.0, 29691.0, 29692.0, 29693.0, 29694.0]],      [[29695.0, 29696.0, 29697.0, 29698.0, 29699.0, 29700.0],       [29701.0, 29702.0, 29703.0, 29704.0, 29705.0, 29706.0],       [29707.0, 29708.0, 29709.0, 29710.0, 29711.0, 29712.0],       [29713.0, 29714.0, 29715.0, 29716.0, 29717.0, 29718.0],       [29719.0, 29720.0, 29721.0, 29722.0, 29723.0, 29724.0],       [29725.0, 29726.0, 29727.0, 29728.0, 29729.0, 29730.0],       [29731.0, 29732.0, 29733.0, 29734.0, 29735.0, 29736.0]]],     [[[29737.0, 29738.0, 29739.0, 29740.0, 29741.0, 29742.0],       [29743.0, 29744.0, 29745.0, 29746.0, 29747.0, 29748.0],       [29749.0, 29750.0, 29751.0, 29752.0, 29753.0, 29754.0],       [29755.0, 29756.0, 29757.0, 29758.0, 29759.0, 29760.0],       [29761.0, 29762.0, 29763.0, 29764.0, 29765.0, 29766.0],       [29767.0, 29768.0, 29769.0, 29770.0, 29771.0, 29772.0],       [29773.0, 29774.0, 29775.0, 29776.0, 29777.0, 29778.0]],      [[29779.0, 29780.0, 29781.0, 29782.0, 29783.0, 29784.0],       [29785.0, 29786.0, 29787.0, 29788.0, 29789.0, 29790.0],       [29791.0, 29792.0, 29793.0, 29794.0, 29795.0, 29796.0],       [29797.0, 29798.0, 29799.0, 29800.0, 29801.0, 29802.0],       [29803.0, 29804.0, 29805.0, 29806.0, 29807.0, 29808.0],       [29809.0, 29810.0, 29811.0, 29812.0, 29813.0, 29814.0],       [29815.0, 29816.0, 29817.0, 29818.0, 29819.0, 29820.0]],      [[29821.0, 29822.0, 29823.0, 29824.0, 29825.0, 29826.0],       [29827.0, 29828.0, 29829.0, 29830.0, 29831.0, 29832.0],       [29833.0, 29834.0, 29835.0, 29836.0, 29837.0, 29838.0],       [29839.0, 29840.0, 29841.0, 29842.0, 29843.0, 29844.0],       [29845.0, 29846.0, 29847.0, 29848.0, 29849.0, 29850.0],       [29851.0, 29852.0, 29853.0, 29854.0, 29855.0, 29856.0],       [29857.0, 29858.0, 29859.0, 29860.0, 29861.0, 29862.0]],      [[29863.0, 29864.0, 29865.0, 29866.0, 29867.0, 29868.0],       [29869.0, 29870.0, 29871.0, 29872.0, 29873.0, 29874.0],       [29875.0, 29876.0, 29877.0, 29878.0, 29879.0, 29880.0],       [29881.0, 29882.0, 29883.0, 29884.0, 29885.0, 29886.0],       [29887.0, 29888.0, 29889.0, 29890.0, 29891.0, 29892.0],       [29893.0, 29894.0, 29895.0, 29896.0, 29897.0, 29898.0],       [29899.0, 29900.0, 29901.0, 29902.0, 29903.0, 29904.0]],      [[29905.0, 29906.0, 29907.0, 29908.0, 29909.0, 29910.0],       [29911.0, 29912.0, 29913.0, 29914.0, 29915.0, 29916.0],       [29917.0, 29918.0, 29919.0, 29920.0, 29921.0, 29922.0],       [29923.0, 29924.0, 29925.0, 29926.0, 29927.0, 29928.0],       [29929.0, 29930.0, 29931.0, 29932.0, 29933.0, 29934.0],       [29935.0, 29936.0, 29937.0, 29938.0, 29939.0, 29940.0],       [29941.0, 29942.0, 29943.0, 29944.0, 29945.0, 29946.0]],      [[29947.0, 29948.0, 29949.0, 29950.0, 29951.0, 29952.0],       [29953.0, 29954.0, 29955.0, 29956.0, 29957.0, 29958.0],       [29959.0, 29960.0, 29961.0, 29962.0, 29963.0, 29964.0],       [29965.0, 29966.0, 29967.0, 29968.0, 29969.0, 29970.0],       [29971.0, 29972.0, 29973.0, 29974.0, 29975.0, 29976.0],       [29977.0, 29978.0, 29979.0, 29980.0, 29981.0, 29982.0],       [29983.0, 29984.0, 29985.0, 29986.0, 29987.0, 29988.0]]],     [[[29989.0, 29990.0, 29991.0, 29992.0, 29993.0, 29994.0],       [29995.0, 29996.0, 29997.0, 29998.0, 29999.0, 30000.0],       [30001.0, 30002.0, 30003.0, 30004.0, 30005.0, 30006.0],       [30007.0, 30008.0, 30009.0, 30010.0, 30011.0, 30012.0],       [30013.0, 30014.0, 30015.0, 30016.0, 30017.0, 30018.0],       [30019.0, 30020.0, 30021.0, 30022.0, 30023.0, 30024.0],       [30025.0, 30026.0, 30027.0, 30028.0, 30029.0, 30030.0]],      [[30031.0, 30032.0, 30033.0, 30034.0, 30035.0, 30036.0],       [30037.0, 30038.0, 30039.0, 30040.0, 30041.0, 30042.0],       [30043.0, 30044.0, 30045.0, 30046.0, 30047.0, 30048.0],       [30049.0, 30050.0, 30051.0, 30052.0, 30053.0, 30054.0],       [30055.0, 30056.0, 30057.0, 30058.0, 30059.0, 30060.0],       [30061.0, 30062.0, 30063.0, 30064.0, 30065.0, 30066.0],       [30067.0, 30068.0, 30069.0, 30070.0, 30071.0, 30072.0]],      [[30073.0, 30074.0, 30075.0, 30076.0, 30077.0, 30078.0],       [30079.0, 30080.0, 30081.0, 30082.0, 30083.0, 30084.0],       [30085.0, 30086.0, 30087.0, 30088.0, 30089.0, 30090.0],       [30091.0, 30092.0, 30093.0, 30094.0, 30095.0, 30096.0],       [30097.0, 30098.0, 30099.0, 30100.0, 30101.0, 30102.0],       [30103.0, 30104.0, 30105.0, 30106.0, 30107.0, 30108.0],       [30109.0, 30110.0, 30111.0, 30112.0, 30113.0, 30114.0]],      [[30115.0, 30116.0, 30117.0, 30118.0, 30119.0, 30120.0],       [30121.0, 30122.0, 30123.0, 30124.0, 30125.0, 30126.0],       [30127.0, 30128.0, 30129.0, 30130.0, 30131.0, 30132.0],       [30133.0, 30134.0, 30135.0, 30136.0, 30137.0, 30138.0],       [30139.0, 30140.0, 30141.0, 30142.0, 30143.0, 30144.0],       [30145.0, 30146.0, 30147.0, 30148.0, 30149.0, 30150.0],       [30151.0, 30152.0, 30153.0, 30154.0, 30155.0, 30156.0]],      [[30157.0, 30158.0, 30159.0, 30160.0, 30161.0, 30162.0],       [30163.0, 30164.0, 30165.0, 30166.0, 30167.0, 30168.0],       [30169.0, 30170.0, 30171.0, 30172.0, 30173.0, 30174.0],       [30175.0, 30176.0, 30177.0, 30178.0, 30179.0, 30180.0],       [30181.0, 30182.0, 30183.0, 30184.0, 30185.0, 30186.0],       [30187.0, 30188.0, 30189.0, 30190.0, 30191.0, 30192.0],       [30193.0, 30194.0, 30195.0, 30196.0, 30197.0, 30198.0]],      [[30199.0, 30200.0, 30201.0, 30202.0, 30203.0, 30204.0],       [30205.0, 30206.0, 30207.0, 30208.0, 30209.0, 30210.0],       [30211.0, 30212.0, 30213.0, 30214.0, 30215.0, 30216.0],       [30217.0, 30218.0, 30219.0, 30220.0, 30221.0, 30222.0],       [30223.0, 30224.0, 30225.0, 30226.0, 30227.0, 30228.0],       [30229.0, 30230.0, 30231.0, 30232.0, 30233.0, 30234.0],       [30235.0, 30236.0, 30237.0, 30238.0, 30239.0, 30240.0]]]]],   [[[[[30241.0, 30242.0, 30243.0, 30244.0, 30245.0, 30246.0],       [30247.0, 30248.0, 30249.0, 30250.0, 30251.0, 30252.0],       [30253.0, 30254.0, 30255.0, 30256.0, 30257.0, 30258.0],       [30259.0, 30260.0, 30261.0, 30262.0, 30263.0, 30264.0],       [30265.0, 30266.0, 30267.0, 30268.0, 30269.0, 30270.0],       [30271.0, 30272.0, 30273.0, 30274.0, 30275.0, 30276.0],       [30277.0, 30278.0, 30279.0, 30280.0, 30281.0, 30282.0]],      [[30283.0, 30284.0, 30285.0, 30286.0, 30287.0, 30288.0],       [30289.0, 30290.0, 30291.0, 30292.0, 30293.0, 30294.0],       [30295.0, 30296.0, 30297.0, 30298.0, 30299.0, 30300.0],       [30301.0, 30302.0, 30303.0, 30304.0, 30305.0, 30306.0],       [30307.0, 30308.0, 30309.0, 30310.0, 30311.0, 30312.0],       [30313.0, 30314.0, 30315.0, 30316.0, 30317.0, 30318.0],       [30319.0, 30320.0, 30321.0, 30322.0, 30323.0, 30324.0]],      [[30325.0, 30326.0, 30327.0, 30328.0, 30329.0, 30330.0],       [30331.0, 30332.0, 30333.0, 30334.0, 30335.0, 30336.0],       [30337.0, 30338.0, 30339.0, 30340.0, 30341.0, 30342.0],       [30343.0, 30344.0, 30345.0, 30346.0, 30347.0, 30348.0],       [30349.0, 30350.0, 30351.0, 30352.0, 30353.0, 30354.0],       [30355.0, 30356.0, 30357.0, 30358.0, 30359.0, 30360.0],       [30361.0, 30362.0, 30363.0, 30364.0, 30365.0, 30366.0]],      [[30367.0, 30368.0, 30369.0, 30370.0, 30371.0, 30372.0],       [30373.0, 30374.0, 30375.0, 30376.0, 30377.0, 30378.0],       [30379.0, 30380.0, 30381.0, 30382.0, 30383.0, 30384.0],       [30385.0, 30386.0, 30387.0, 30388.0, 30389.0, 30390.0],       [30391.0, 30392.0, 30393.0, 30394.0, 30395.0, 30396.0],       [30397.0, 30398.0, 30399.0, 30400.0, 30401.0, 30402.0],       [30403.0, 30404.0, 30405.0, 30406.0, 30407.0, 30408.0]],      [[30409.0, 30410.0, 30411.0, 30412.0, 30413.0, 30414.0],       [30415.0, 30416.0, 30417.0, 30418.0, 30419.0, 30420.0],       [30421.0, 30422.0, 30423.0, 30424.0, 30425.0, 30426.0],       [30427.0, 30428.0, 30429.0, 30430.0, 30431.0, 30432.0],       [30433.0, 30434.0, 30435.0, 30436.0, 30437.0, 30438.0],       [30439.0, 30440.0, 30441.0, 30442.0, 30443.0, 30444.0],       [30445.0, 30446.0, 30447.0, 30448.0, 30449.0, 30450.0]],      [[30451.0, 30452.0, 30453.0, 30454.0, 30455.0, 30456.0],       [30457.0, 30458.0, 30459.0, 30460.0, 30461.0, 30462.0],       [30463.0, 30464.0, 30465.0, 30466.0, 30467.0, 30468.0],       [30469.0, 30470.0, 30471.0, 30472.0, 30473.0, 30474.0],       [30475.0, 30476.0, 30477.0, 30478.0, 30479.0, 30480.0],       [30481.0, 30482.0, 30483.0, 30484.0, 30485.0, 30486.0],       [30487.0, 30488.0, 30489.0, 30490.0, 30491.0, 30492.0]]],     [[[30493.0, 30494.0, 30495.0, 30496.0, 30497.0, 30498.0],       [30499.0, 30500.0, 30501.0, 30502.0, 30503.0, 30504.0],       [30505.0, 30506.0, 30507.0, 30508.0, 30509.0, 30510.0],       [30511.0, 30512.0, 30513.0, 30514.0, 30515.0, 30516.0],       [30517.0, 30518.0, 30519.0, 30520.0, 30521.0, 30522.0],       [30523.0, 30524.0, 30525.0, 30526.0, 30527.0, 30528.0],       [30529.0, 30530.0, 30531.0, 30532.0, 30533.0, 30534.0]],      [[30535.0, 30536.0, 30537.0, 30538.0, 30539.0, 30540.0],       [30541.0, 30542.0, 30543.0, 30544.0, 30545.0, 30546.0],       [30547.0, 30548.0, 30549.0, 30550.0, 30551.0, 30552.0],       [30553.0, 30554.0, 30555.0, 30556.0, 30557.0, 30558.0],       [30559.0, 30560.0, 30561.0, 30562.0, 30563.0, 30564.0],       [30565.0, 30566.0, 30567.0, 30568.0, 30569.0, 30570.0],       [30571.0, 30572.0, 30573.0, 30574.0, 30575.0, 30576.0]],      [[30577.0, 30578.0, 30579.0, 30580.0, 30581.0, 30582.0],       [30583.0, 30584.0, 30585.0, 30586.0, 30587.0, 30588.0],       [30589.0, 30590.0, 30591.0, 30592.0, 30593.0, 30594.0],       [30595.0, 30596.0, 30597.0, 30598.0, 30599.0, 30600.0],       [30601.0, 30602.0, 30603.0, 30604.0, 30605.0, 30606.0],       [30607.0, 30608.0, 30609.0, 30610.0, 30611.0, 30612.0],       [30613.0, 30614.0, 30615.0, 30616.0, 30617.0, 30618.0]],      [[30619.0, 30620.0, 30621.0, 30622.0, 30623.0, 30624.0],       [30625.0, 30626.0, 30627.0, 30628.0, 30629.0, 30630.0],       [30631.0, 30632.0, 30633.0, 30634.0, 30635.0, 30636.0],       [30637.0, 30638.0, 30639.0, 30640.0, 30641.0, 30642.0],       [30643.0, 30644.0, 30645.0, 30646.0, 30647.0, 30648.0],       [30649.0, 30650.0, 30651.0, 30652.0, 30653.0, 30654.0],       [30655.0, 30656.0, 30657.0, 30658.0, 30659.0, 30660.0]],      [[30661.0, 30662.0, 30663.0, 30664.0, 30665.0, 30666.0],       [30667.0, 30668.0, 30669.0, 30670.0, 30671.0, 30672.0],       [30673.0, 30674.0, 30675.0, 30676.0, 30677.0, 30678.0],       [30679.0, 30680.0, 30681.0, 30682.0, 30683.0, 30684.0],       [30685.0, 30686.0, 30687.0, 30688.0, 30689.0, 30690.0],       [30691.0, 30692.0, 30693.0, 30694.0, 30695.0, 30696.0],       [30697.0, 30698.0, 30699.0, 30700.0, 30701.0, 30702.0]],      [[30703.0, 30704.0, 30705.0, 30706.0, 30707.0, 30708.0],       [30709.0, 30710.0, 30711.0, 30712.0, 30713.0, 30714.0],       [30715.0, 30716.0, 30717.0, 30718.0, 30719.0, 30720.0],       [30721.0, 30722.0, 30723.0, 30724.0, 30725.0, 30726.0],       [30727.0, 30728.0, 30729.0, 30730.0, 30731.0, 30732.0],       [30733.0, 30734.0, 30735.0, 30736.0, 30737.0, 30738.0],       [30739.0, 30740.0, 30741.0, 30742.0, 30743.0, 30744.0]]],     [[[30745.0, 30746.0, 30747.0, 30748.0, 30749.0, 30750.0],       [30751.0, 30752.0, 30753.0, 30754.0, 30755.0, 30756.0],       [30757.0, 30758.0, 30759.0, 30760.0, 30761.0, 30762.0],       [30763.0, 30764.0, 30765.0, 30766.0, 30767.0, 30768.0],       [30769.0, 30770.0, 30771.0, 30772.0, 30773.0, 30774.0],       [30775.0, 30776.0, 30777.0, 30778.0, 30779.0, 30780.0],       [30781.0, 30782.0, 30783.0, 30784.0, 30785.0, 30786.0]],      [[30787.0, 30788.0, 30789.0, 30790.0, 30791.0, 30792.0],       [30793.0, 30794.0, 30795.0, 30796.0, 30797.0, 30798.0],       [30799.0, 30800.0, 30801.0, 30802.0, 30803.0, 30804.0],       [30805.0, 30806.0, 30807.0, 30808.0, 30809.0, 30810.0],       [30811.0, 30812.0, 30813.0, 30814.0, 30815.0, 30816.0],       [30817.0, 30818.0, 30819.0, 30820.0, 30821.0, 30822.0],       [30823.0, 30824.0, 30825.0, 30826.0, 30827.0, 30828.0]],      [[30829.0, 30830.0, 30831.0, 30832.0, 30833.0, 30834.0],       [30835.0, 30836.0, 30837.0, 30838.0, 30839.0, 30840.0],       [30841.0, 30842.0, 30843.0, 30844.0, 30845.0, 30846.0],       [30847.0, 30848.0, 30849.0, 30850.0, 30851.0, 30852.0],       [30853.0, 30854.0, 30855.0, 30856.0, 30857.0, 30858.0],       [30859.0, 30860.0, 30861.0, 30862.0, 30863.0, 30864.0],       [30865.0, 30866.0, 30867.0, 30868.0, 30869.0, 30870.0]],      [[30871.0, 30872.0, 30873.0, 30874.0, 30875.0, 30876.0],       [30877.0, 30878.0, 30879.0, 30880.0, 30881.0, 30882.0],       [30883.0, 30884.0, 30885.0, 30886.0, 30887.0, 30888.0],       [30889.0, 30890.0, 30891.0, 30892.0, 30893.0, 30894.0],       [30895.0, 30896.0, 30897.0, 30898.0, 30899.0, 30900.0],       [30901.0, 30902.0, 30903.0, 30904.0, 30905.0, 30906.0],       [30907.0, 30908.0, 30909.0, 30910.0, 30911.0, 30912.0]],      [[30913.0, 30914.0, 30915.0, 30916.0, 30917.0, 30918.0],       [30919.0, 30920.0, 30921.0, 30922.0, 30923.0, 30924.0],       [30925.0, 30926.0, 30927.0, 30928.0, 30929.0, 30930.0],       [30931.0, 30932.0, 30933.0, 30934.0, 30935.0, 30936.0],       [30937.0, 30938.0, 30939.0, 30940.0, 30941.0, 30942.0],       [30943.0, 30944.0, 30945.0, 30946.0, 30947.0, 30948.0],       [30949.0, 30950.0, 30951.0, 30952.0, 30953.0, 30954.0]],      [[30955.0, 30956.0, 30957.0, 30958.0, 30959.0, 30960.0],       [30961.0, 30962.0, 30963.0, 30964.0, 30965.0, 30966.0],       [30967.0, 30968.0, 30969.0, 30970.0, 30971.0, 30972.0],       [30973.0, 30974.0, 30975.0, 30976.0, 30977.0, 30978.0],       [30979.0, 30980.0, 30981.0, 30982.0, 30983.0, 30984.0],       [30985.0, 30986.0, 30987.0, 30988.0, 30989.0, 30990.0],       [30991.0, 30992.0, 30993.0, 30994.0, 30995.0, 30996.0]]],     [[[30997.0, 30998.0, 30999.0, 31000.0, 31001.0, 31002.0],       [31003.0, 31004.0, 31005.0, 31006.0, 31007.0, 31008.0],       [31009.0, 31010.0, 31011.0, 31012.0, 31013.0, 31014.0],       [31015.0, 31016.0, 31017.0, 31018.0, 31019.0, 31020.0],       [31021.0, 31022.0, 31023.0, 31024.0, 31025.0, 31026.0],       [31027.0, 31028.0, 31029.0, 31030.0, 31031.0, 31032.0],       [31033.0, 31034.0, 31035.0, 31036.0, 31037.0, 31038.0]],      [[31039.0, 31040.0, 31041.0, 31042.0, 31043.0, 31044.0],       [31045.0, 31046.0, 31047.0, 31048.0, 31049.0, 31050.0],       [31051.0, 31052.0, 31053.0, 31054.0, 31055.0, 31056.0],       [31057.0, 31058.0, 31059.0, 31060.0, 31061.0, 31062.0],       [31063.0, 31064.0, 31065.0, 31066.0, 31067.0, 31068.0],       [31069.0, 31070.0, 31071.0, 31072.0, 31073.0, 31074.0],       [31075.0, 31076.0, 31077.0, 31078.0, 31079.0, 31080.0]],      [[31081.0, 31082.0, 31083.0, 31084.0, 31085.0, 31086.0],       [31087.0, 31088.0, 31089.0, 31090.0, 31091.0, 31092.0],       [31093.0, 31094.0, 31095.0, 31096.0, 31097.0, 31098.0],       [31099.0, 31100.0, 31101.0, 31102.0, 31103.0, 31104.0],       [31105.0, 31106.0, 31107.0, 31108.0, 31109.0, 31110.0],       [31111.0, 31112.0, 31113.0, 31114.0, 31115.0, 31116.0],       [31117.0, 31118.0, 31119.0, 31120.0, 31121.0, 31122.0]],      [[31123.0, 31124.0, 31125.0, 31126.0, 31127.0, 31128.0],       [31129.0, 31130.0, 31131.0, 31132.0, 31133.0, 31134.0],       [31135.0, 31136.0, 31137.0, 31138.0, 31139.0, 31140.0],       [31141.0, 31142.0, 31143.0, 31144.0, 31145.0, 31146.0],       [31147.0, 31148.0, 31149.0, 31150.0, 31151.0, 31152.0],       [31153.0, 31154.0, 31155.0, 31156.0, 31157.0, 31158.0],       [31159.0, 31160.0, 31161.0, 31162.0, 31163.0, 31164.0]],      [[31165.0, 31166.0, 31167.0, 31168.0, 31169.0, 31170.0],       [31171.0, 31172.0, 31173.0, 31174.0, 31175.0, 31176.0],       [31177.0, 31178.0, 31179.0, 31180.0, 31181.0, 31182.0],       [31183.0, 31184.0, 31185.0, 31186.0, 31187.0, 31188.0],       [31189.0, 31190.0, 31191.0, 31192.0, 31193.0, 31194.0],       [31195.0, 31196.0, 31197.0, 31198.0, 31199.0, 31200.0],       [31201.0, 31202.0, 31203.0, 31204.0, 31205.0, 31206.0]],      [[31207.0, 31208.0, 31209.0, 31210.0, 31211.0, 31212.0],       [31213.0, 31214.0, 31215.0, 31216.0, 31217.0, 31218.0],       [31219.0, 31220.0, 31221.0, 31222.0, 31223.0, 31224.0],       [31225.0, 31226.0, 31227.0, 31228.0, 31229.0, 31230.0],       [31231.0, 31232.0, 31233.0, 31234.0, 31235.0, 31236.0],       [31237.0, 31238.0, 31239.0, 31240.0, 31241.0, 31242.0],       [31243.0, 31244.0, 31245.0, 31246.0, 31247.0, 31248.0]]]],    [[[[31249.0, 31250.0, 31251.0, 31252.0, 31253.0, 31254.0],       [31255.0, 31256.0, 31257.0, 31258.0, 31259.0, 31260.0],       [31261.0, 31262.0, 31263.0, 31264.0, 31265.0, 31266.0],       [31267.0, 31268.0, 31269.0, 31270.0, 31271.0, 31272.0],       [31273.0, 31274.0, 31275.0, 31276.0, 31277.0, 31278.0],       [31279.0, 31280.0, 31281.0, 31282.0, 31283.0, 31284.0],       [31285.0, 31286.0, 31287.0, 31288.0, 31289.0, 31290.0]],      [[31291.0, 31292.0, 31293.0, 31294.0, 31295.0, 31296.0],       [31297.0, 31298.0, 31299.0, 31300.0, 31301.0, 31302.0],       [31303.0, 31304.0, 31305.0, 31306.0, 31307.0, 31308.0],       [31309.0, 31310.0, 31311.0, 31312.0, 31313.0, 31314.0],       [31315.0, 31316.0, 31317.0, 31318.0, 31319.0, 31320.0],       [31321.0, 31322.0, 31323.0, 31324.0, 31325.0, 31326.0],       [31327.0, 31328.0, 31329.0, 31330.0, 31331.0, 31332.0]],      [[31333.0, 31334.0, 31335.0, 31336.0, 31337.0, 31338.0],       [31339.0, 31340.0, 31341.0, 31342.0, 31343.0, 31344.0],       [31345.0, 31346.0, 31347.0, 31348.0, 31349.0, 31350.0],       [31351.0, 31352.0, 31353.0, 31354.0, 31355.0, 31356.0],       [31357.0, 31358.0, 31359.0, 31360.0, 31361.0, 31362.0],       [31363.0, 31364.0, 31365.0, 31366.0, 31367.0, 31368.0],       [31369.0, 31370.0, 31371.0, 31372.0, 31373.0, 31374.0]],      [[31375.0, 31376.0, 31377.0, 31378.0, 31379.0, 31380.0],       [31381.0, 31382.0, 31383.0, 31384.0, 31385.0, 31386.0],       [31387.0, 31388.0, 31389.0, 31390.0, 31391.0, 31392.0],       [31393.0, 31394.0, 31395.0, 31396.0, 31397.0, 31398.0],       [31399.0, 31400.0, 31401.0, 31402.0, 31403.0, 31404.0],       [31405.0, 31406.0, 31407.0, 31408.0, 31409.0, 31410.0],       [31411.0, 31412.0, 31413.0, 31414.0, 31415.0, 31416.0]],      [[31417.0, 31418.0, 31419.0, 31420.0, 31421.0, 31422.0],       [31423.0, 31424.0, 31425.0, 31426.0, 31427.0, 31428.0],       [31429.0, 31430.0, 31431.0, 31432.0, 31433.0, 31434.0],       [31435.0, 31436.0, 31437.0, 31438.0, 31439.0, 31440.0],       [31441.0, 31442.0, 31443.0, 31444.0, 31445.0, 31446.0],       [31447.0, 31448.0, 31449.0, 31450.0, 31451.0, 31452.0],       [31453.0, 31454.0, 31455.0, 31456.0, 31457.0, 31458.0]],      [[31459.0, 31460.0, 31461.0, 31462.0, 31463.0, 31464.0],       [31465.0, 31466.0, 31467.0, 31468.0, 31469.0, 31470.0],       [31471.0, 31472.0, 31473.0, 31474.0, 31475.0, 31476.0],       [31477.0, 31478.0, 31479.0, 31480.0, 31481.0, 31482.0],       [31483.0, 31484.0, 31485.0, 31486.0, 31487.0, 31488.0],       [31489.0, 31490.0, 31491.0, 31492.0, 31493.0, 31494.0],       [31495.0, 31496.0, 31497.0, 31498.0, 31499.0, 31500.0]]],     [[[31501.0, 31502.0, 31503.0, 31504.0, 31505.0, 31506.0],       [31507.0, 31508.0, 31509.0, 31510.0, 31511.0, 31512.0],       [31513.0, 31514.0, 31515.0, 31516.0, 31517.0, 31518.0],       [31519.0, 31520.0, 31521.0, 31522.0, 31523.0, 31524.0],       [31525.0, 31526.0, 31527.0, 31528.0, 31529.0, 31530.0],       [31531.0, 31532.0, 31533.0, 31534.0, 31535.0, 31536.0],       [31537.0, 31538.0, 31539.0, 31540.0, 31541.0, 31542.0]],      [[31543.0, 31544.0, 31545.0, 31546.0, 31547.0, 31548.0],       [31549.0, 31550.0, 31551.0, 31552.0, 31553.0, 31554.0],       [31555.0, 31556.0, 31557.0, 31558.0, 31559.0, 31560.0],       [31561.0, 31562.0, 31563.0, 31564.0, 31565.0, 31566.0],       [31567.0, 31568.0, 31569.0, 31570.0, 31571.0, 31572.0],       [31573.0, 31574.0, 31575.0, 31576.0, 31577.0, 31578.0],       [31579.0, 31580.0, 31581.0, 31582.0, 31583.0, 31584.0]],      [[31585.0, 31586.0, 31587.0, 31588.0, 31589.0, 31590.0],       [31591.0, 31592.0, 31593.0, 31594.0, 31595.0, 31596.0],       [31597.0, 31598.0, 31599.0, 31600.0, 31601.0, 31602.0],       [31603.0, 31604.0, 31605.0, 31606.0, 31607.0, 31608.0],       [31609.0, 31610.0, 31611.0, 31612.0, 31613.0, 31614.0],       [31615.0, 31616.0, 31617.0, 31618.0, 31619.0, 31620.0],       [31621.0, 31622.0, 31623.0, 31624.0, 31625.0, 31626.0]],      [[31627.0, 31628.0, 31629.0, 31630.0, 31631.0, 31632.0],       [31633.0, 31634.0, 31635.0, 31636.0, 31637.0, 31638.0],       [31639.0, 31640.0, 31641.0, 31642.0, 31643.0, 31644.0],       [31645.0, 31646.0, 31647.0, 31648.0, 31649.0, 31650.0],       [31651.0, 31652.0, 31653.0, 31654.0, 31655.0, 31656.0],       [31657.0, 31658.0, 31659.0, 31660.0, 31661.0, 31662.0],       [31663.0, 31664.0, 31665.0, 31666.0, 31667.0, 31668.0]],      [[31669.0, 31670.0, 31671.0, 31672.0, 31673.0, 31674.0],       [31675.0, 31676.0, 31677.0, 31678.0, 31679.0, 31680.0],       [31681.0, 31682.0, 31683.0, 31684.0, 31685.0, 31686.0],       [31687.0, 31688.0, 31689.0, 31690.0, 31691.0, 31692.0],       [31693.0, 31694.0, 31695.0, 31696.0, 31697.0, 31698.0],       [31699.0, 31700.0, 31701.0, 31702.0, 31703.0, 31704.0],       [31705.0, 31706.0, 31707.0, 31708.0, 31709.0, 31710.0]],      [[31711.0, 31712.0, 31713.0, 31714.0, 31715.0, 31716.0],       [31717.0, 31718.0, 31719.0, 31720.0, 31721.0, 31722.0],       [31723.0, 31724.0, 31725.0, 31726.0, 31727.0, 31728.0],       [31729.0, 31730.0, 31731.0, 31732.0, 31733.0, 31734.0],       [31735.0, 31736.0, 31737.0, 31738.0, 31739.0, 31740.0],       [31741.0, 31742.0, 31743.0, 31744.0, 31745.0, 31746.0],       [31747.0, 31748.0, 31749.0, 31750.0, 31751.0, 31752.0]]],     [[[31753.0, 31754.0, 31755.0, 31756.0, 31757.0, 31758.0],       [31759.0, 31760.0, 31761.0, 31762.0, 31763.0, 31764.0],       [31765.0, 31766.0, 31767.0, 31768.0, 31769.0, 31770.0],       [31771.0, 31772.0, 31773.0, 31774.0, 31775.0, 31776.0],       [31777.0, 31778.0, 31779.0, 31780.0, 31781.0, 31782.0],       [31783.0, 31784.0, 31785.0, 31786.0, 31787.0, 31788.0],       [31789.0, 31790.0, 31791.0, 31792.0, 31793.0, 31794.0]],      [[31795.0, 31796.0, 31797.0, 31798.0, 31799.0, 31800.0],       [31801.0, 31802.0, 31803.0, 31804.0, 31805.0, 31806.0],       [31807.0, 31808.0, 31809.0, 31810.0, 31811.0, 31812.0],       [31813.0, 31814.0, 31815.0, 31816.0, 31817.0, 31818.0],       [31819.0, 31820.0, 31821.0, 31822.0, 31823.0, 31824.0],       [31825.0, 31826.0, 31827.0, 31828.0, 31829.0, 31830.0],       [31831.0, 31832.0, 31833.0, 31834.0, 31835.0, 31836.0]],      [[31837.0, 31838.0, 31839.0, 31840.0, 31841.0, 31842.0],       [31843.0, 31844.0, 31845.0, 31846.0, 31847.0, 31848.0],       [31849.0, 31850.0, 31851.0, 31852.0, 31853.0, 31854.0],       [31855.0, 31856.0, 31857.0, 31858.0, 31859.0, 31860.0],       [31861.0, 31862.0, 31863.0, 31864.0, 31865.0, 31866.0],       [31867.0, 31868.0, 31869.0, 31870.0, 31871.0, 31872.0],       [31873.0, 31874.0, 31875.0, 31876.0, 31877.0, 31878.0]],      [[31879.0, 31880.0, 31881.0, 31882.0, 31883.0, 31884.0],       [31885.0, 31886.0, 31887.0, 31888.0, 31889.0, 31890.0],       [31891.0, 31892.0, 31893.0, 31894.0, 31895.0, 31896.0],       [31897.0, 31898.0, 31899.0, 31900.0, 31901.0, 31902.0],       [31903.0, 31904.0, 31905.0, 31906.0, 31907.0, 31908.0],       [31909.0, 31910.0, 31911.0, 31912.0, 31913.0, 31914.0],       [31915.0, 31916.0, 31917.0, 31918.0, 31919.0, 31920.0]],      [[31921.0, 31922.0, 31923.0, 31924.0, 31925.0, 31926.0],       [31927.0, 31928.0, 31929.0, 31930.0, 31931.0, 31932.0],       [31933.0, 31934.0, 31935.0, 31936.0, 31937.0, 31938.0],       [31939.0, 31940.0, 31941.0, 31942.0, 31943.0, 31944.0],       [31945.0, 31946.0, 31947.0, 31948.0, 31949.0, 31950.0],       [31951.0, 31952.0, 31953.0, 31954.0, 31955.0, 31956.0],       [31957.0, 31958.0, 31959.0, 31960.0, 31961.0, 31962.0]],      [[31963.0, 31964.0, 31965.0, 31966.0, 31967.0, 31968.0],       [31969.0, 31970.0, 31971.0, 31972.0, 31973.0, 31974.0],       [31975.0, 31976.0, 31977.0, 31978.0, 31979.0, 31980.0],       [31981.0, 31982.0, 31983.0, 31984.0, 31985.0, 31986.0],       [31987.0, 31988.0, 31989.0, 31990.0, 31991.0, 31992.0],       [31993.0, 31994.0, 31995.0, 31996.0, 31997.0, 31998.0],       [31999.0, 32000.0, 32001.0, 32002.0, 32003.0, 32004.0]]],     [[[32005.0, 32006.0, 32007.0, 32008.0, 32009.0, 32010.0],       [32011.0, 32012.0, 32013.0, 32014.0, 32015.0, 32016.0],       [32017.0, 32018.0, 32019.0, 32020.0, 32021.0, 32022.0],       [32023.0, 32024.0, 32025.0, 32026.0, 32027.0, 32028.0],       [32029.0, 32030.0, 32031.0, 32032.0, 32033.0, 32034.0],       [32035.0, 32036.0, 32037.0, 32038.0, 32039.0, 32040.0],       [32041.0, 32042.0, 32043.0, 32044.0, 32045.0, 32046.0]],      [[32047.0, 32048.0, 32049.0, 32050.0, 32051.0, 32052.0],       [32053.0, 32054.0, 32055.0, 32056.0, 32057.0, 32058.0],       [32059.0, 32060.0, 32061.0, 32062.0, 32063.0, 32064.0],       [32065.0, 32066.0, 32067.0, 32068.0, 32069.0, 32070.0],       [32071.0, 32072.0, 32073.0, 32074.0, 32075.0, 32076.0],       [32077.0, 32078.0, 32079.0, 32080.0, 32081.0, 32082.0],       [32083.0, 32084.0, 32085.0, 32086.0, 32087.0, 32088.0]],      [[32089.0, 32090.0, 32091.0, 32092.0, 32093.0, 32094.0],       [32095.0, 32096.0, 32097.0, 32098.0, 32099.0, 32100.0],       [32101.0, 32102.0, 32103.0, 32104.0, 32105.0, 32106.0],       [32107.0, 32108.0, 32109.0, 32110.0, 32111.0, 32112.0],       [32113.0, 32114.0, 32115.0, 32116.0, 32117.0, 32118.0],       [32119.0, 32120.0, 32121.0, 32122.0, 32123.0, 32124.0],       [32125.0, 32126.0, 32127.0, 32128.0, 32129.0, 32130.0]],      [[32131.0, 32132.0, 32133.0, 32134.0, 32135.0, 32136.0],       [32137.0, 32138.0, 32139.0, 32140.0, 32141.0, 32142.0],       [32143.0, 32144.0, 32145.0, 32146.0, 32147.0, 32148.0],       [32149.0, 32150.0, 32151.0, 32152.0, 32153.0, 32154.0],       [32155.0, 32156.0, 32157.0, 32158.0, 32159.0, 32160.0],       [32161.0, 32162.0, 32163.0, 32164.0, 32165.0, 32166.0],       [32167.0, 32168.0, 32169.0, 32170.0, 32171.0, 32172.0]],      [[32173.0, 32174.0, 32175.0, 32176.0, 32177.0, 32178.0],       [32179.0, 32180.0, 32181.0, 32182.0, 32183.0, 32184.0],       [32185.0, 32186.0, 32187.0, 32188.0, 32189.0, 32190.0],       [32191.0, 32192.0, 32193.0, 32194.0, 32195.0, 32196.0],       [32197.0, 32198.0, 32199.0, 32200.0, 32201.0, 32202.0],       [32203.0, 32204.0, 32205.0, 32206.0, 32207.0, 32208.0],       [32209.0, 32210.0, 32211.0, 32212.0, 32213.0, 32214.0]],      [[32215.0, 32216.0, 32217.0, 32218.0, 32219.0, 32220.0],       [32221.0, 32222.0, 32223.0, 32224.0, 32225.0, 32226.0],       [32227.0, 32228.0, 32229.0, 32230.0, 32231.0, 32232.0],       [32233.0, 32234.0, 32235.0, 32236.0, 32237.0, 32238.0],       [32239.0, 32240.0, 32241.0, 32242.0, 32243.0, 32244.0],       [32245.0, 32246.0, 32247.0, 32248.0, 32249.0, 32250.0],       [32251.0, 32252.0, 32253.0, 32254.0, 32255.0, 32256.0]]]],    [[[[32257.0, 32258.0, 32259.0, 32260.0, 32261.0, 32262.0],       [32263.0, 32264.0, 32265.0, 32266.0, 32267.0, 32268.0],       [32269.0, 32270.0, 32271.0, 32272.0, 32273.0, 32274.0],       [32275.0, 32276.0, 32277.0, 32278.0, 32279.0, 32280.0],       [32281.0, 32282.0, 32283.0, 32284.0, 32285.0, 32286.0],       [32287.0, 32288.0, 32289.0, 32290.0, 32291.0, 32292.0],       [32293.0, 32294.0, 32295.0, 32296.0, 32297.0, 32298.0]],      [[32299.0, 32300.0, 32301.0, 32302.0, 32303.0, 32304.0],       [32305.0, 32306.0, 32307.0, 32308.0, 32309.0, 32310.0],       [32311.0, 32312.0, 32313.0, 32314.0, 32315.0, 32316.0],       [32317.0, 32318.0, 32319.0, 32320.0, 32321.0, 32322.0],       [32323.0, 32324.0, 32325.0, 32326.0, 32327.0, 32328.0],       [32329.0, 32330.0, 32331.0, 32332.0, 32333.0, 32334.0],       [32335.0, 32336.0, 32337.0, 32338.0, 32339.0, 32340.0]],      [[32341.0, 32342.0, 32343.0, 32344.0, 32345.0, 32346.0],       [32347.0, 32348.0, 32349.0, 32350.0, 32351.0, 32352.0],       [32353.0, 32354.0, 32355.0, 32356.0, 32357.0, 32358.0],       [32359.0, 32360.0, 32361.0, 32362.0, 32363.0, 32364.0],       [32365.0, 32366.0, 32367.0, 32368.0, 32369.0, 32370.0],       [32371.0, 32372.0, 32373.0, 32374.0, 32375.0, 32376.0],       [32377.0, 32378.0, 32379.0, 32380.0, 32381.0, 32382.0]],      [[32383.0, 32384.0, 32385.0, 32386.0, 32387.0, 32388.0],       [32389.0, 32390.0, 32391.0, 32392.0, 32393.0, 32394.0],       [32395.0, 32396.0, 32397.0, 32398.0, 32399.0, 32400.0],       [32401.0, 32402.0, 32403.0, 32404.0, 32405.0, 32406.0],       [32407.0, 32408.0, 32409.0, 32410.0, 32411.0, 32412.0],       [32413.0, 32414.0, 32415.0, 32416.0, 32417.0, 32418.0],       [32419.0, 32420.0, 32421.0, 32422.0, 32423.0, 32424.0]],      [[32425.0, 32426.0, 32427.0, 32428.0, 32429.0, 32430.0],       [32431.0, 32432.0, 32433.0, 32434.0, 32435.0, 32436.0],       [32437.0, 32438.0, 32439.0, 32440.0, 32441.0, 32442.0],       [32443.0, 32444.0, 32445.0, 32446.0, 32447.0, 32448.0],       [32449.0, 32450.0, 32451.0, 32452.0, 32453.0, 32454.0],       [32455.0, 32456.0, 32457.0, 32458.0, 32459.0, 32460.0],       [32461.0, 32462.0, 32463.0, 32464.0, 32465.0, 32466.0]],      [[32467.0, 32468.0, 32469.0, 32470.0, 32471.0, 32472.0],       [32473.0, 32474.0, 32475.0, 32476.0, 32477.0, 32478.0],       [32479.0, 32480.0, 32481.0, 32482.0, 32483.0, 32484.0],       [32485.0, 32486.0, 32487.0, 32488.0, 32489.0, 32490.0],       [32491.0, 32492.0, 32493.0, 32494.0, 32495.0, 32496.0],       [32497.0, 32498.0, 32499.0, 32500.0, 32501.0, 32502.0],       [32503.0, 32504.0, 32505.0, 32506.0, 32507.0, 32508.0]]],     [[[32509.0, 32510.0, 32511.0, 32512.0, 32513.0, 32514.0],       [32515.0, 32516.0, 32517.0, 32518.0, 32519.0, 32520.0],       [32521.0, 32522.0, 32523.0, 32524.0, 32525.0, 32526.0],       [32527.0, 32528.0, 32529.0, 32530.0, 32531.0, 32532.0],       [32533.0, 32534.0, 32535.0, 32536.0, 32537.0, 32538.0],       [32539.0, 32540.0, 32541.0, 32542.0, 32543.0, 32544.0],       [32545.0, 32546.0, 32547.0, 32548.0, 32549.0, 32550.0]],      [[32551.0, 32552.0, 32553.0, 32554.0, 32555.0, 32556.0],       [32557.0, 32558.0, 32559.0, 32560.0, 32561.0, 32562.0],       [32563.0, 32564.0, 32565.0, 32566.0, 32567.0, 32568.0],       [32569.0, 32570.0, 32571.0, 32572.0, 32573.0, 32574.0],       [32575.0, 32576.0, 32577.0, 32578.0, 32579.0, 32580.0],       [32581.0, 32582.0, 32583.0, 32584.0, 32585.0, 32586.0],       [32587.0, 32588.0, 32589.0, 32590.0, 32591.0, 32592.0]],      [[32593.0, 32594.0, 32595.0, 32596.0, 32597.0, 32598.0],       [32599.0, 32600.0, 32601.0, 32602.0, 32603.0, 32604.0],       [32605.0, 32606.0, 32607.0, 32608.0, 32609.0, 32610.0],       [32611.0, 32612.0, 32613.0, 32614.0, 32615.0, 32616.0],       [32617.0, 32618.0, 32619.0, 32620.0, 32621.0, 32622.0],       [32623.0, 32624.0, 32625.0, 32626.0, 32627.0, 32628.0],       [32629.0, 32630.0, 32631.0, 32632.0, 32633.0, 32634.0]],      [[32635.0, 32636.0, 32637.0, 32638.0, 32639.0, 32640.0],       [32641.0, 32642.0, 32643.0, 32644.0, 32645.0, 32646.0],       [32647.0, 32648.0, 32649.0, 32650.0, 32651.0, 32652.0],       [32653.0, 32654.0, 32655.0, 32656.0, 32657.0, 32658.0],       [32659.0, 32660.0, 32661.0, 32662.0, 32663.0, 32664.0],       [32665.0, 32666.0, 32667.0, 32668.0, 32669.0, 32670.0],       [32671.0, 32672.0, 32673.0, 32674.0, 32675.0, 32676.0]],      [[32677.0, 32678.0, 32679.0, 32680.0, 32681.0, 32682.0],       [32683.0, 32684.0, 32685.0, 32686.0, 32687.0, 32688.0],       [32689.0, 32690.0, 32691.0, 32692.0, 32693.0, 32694.0],       [32695.0, 32696.0, 32697.0, 32698.0, 32699.0, 32700.0],       [32701.0, 32702.0, 32703.0, 32704.0, 32705.0, 32706.0],       [32707.0, 32708.0, 32709.0, 32710.0, 32711.0, 32712.0],       [32713.0, 32714.0, 32715.0, 32716.0, 32717.0, 32718.0]],      [[32719.0, 32720.0, 32721.0, 32722.0, 32723.0, 32724.0],       [32725.0, 32726.0, 32727.0, 32728.0, 32729.0, 32730.0],       [32731.0, 32732.0, 32733.0, 32734.0, 32735.0, 32736.0],       [32737.0, 32738.0, 32739.0, 32740.0, 32741.0, 32742.0],       [32743.0, 32744.0, 32745.0, 32746.0, 32747.0, 32748.0],       [32749.0, 32750.0, 32751.0, 32752.0, 32753.0, 32754.0],       [32755.0, 32756.0, 32757.0, 32758.0, 32759.0, 32760.0]]],     [[[32761.0, 32762.0, 32763.0, 32764.0, 32765.0, 32766.0],       [32767.0, 32768.0, 32769.0, 32770.0, 32771.0, 32772.0],       [32773.0, 32774.0, 32775.0, 32776.0, 32777.0, 32778.0],       [32779.0, 32780.0, 32781.0, 32782.0, 32783.0, 32784.0],       [32785.0, 32786.0, 32787.0, 32788.0, 32789.0, 32790.0],       [32791.0, 32792.0, 32793.0, 32794.0, 32795.0, 32796.0],       [32797.0, 32798.0, 32799.0, 32800.0, 32801.0, 32802.0]],      [[32803.0, 32804.0, 32805.0, 32806.0, 32807.0, 32808.0],       [32809.0, 32810.0, 32811.0, 32812.0, 32813.0, 32814.0],       [32815.0, 32816.0, 32817.0, 32818.0, 32819.0, 32820.0],       [32821.0, 32822.0, 32823.0, 32824.0, 32825.0, 32826.0],       [32827.0, 32828.0, 32829.0, 32830.0, 32831.0, 32832.0],       [32833.0, 32834.0, 32835.0, 32836.0, 32837.0, 32838.0],       [32839.0, 32840.0, 32841.0, 32842.0, 32843.0, 32844.0]],      [[32845.0, 32846.0, 32847.0, 32848.0, 32849.0, 32850.0],       [32851.0, 32852.0, 32853.0, 32854.0, 32855.0, 32856.0],       [32857.0, 32858.0, 32859.0, 32860.0, 32861.0, 32862.0],       [32863.0, 32864.0, 32865.0, 32866.0, 32867.0, 32868.0],       [32869.0, 32870.0, 32871.0, 32872.0, 32873.0, 32874.0],       [32875.0, 32876.0, 32877.0, 32878.0, 32879.0, 32880.0],       [32881.0, 32882.0, 32883.0, 32884.0, 32885.0, 32886.0]],      [[32887.0, 32888.0, 32889.0, 32890.0, 32891.0, 32892.0],       [32893.0, 32894.0, 32895.0, 32896.0, 32897.0, 32898.0],       [32899.0, 32900.0, 32901.0, 32902.0, 32903.0, 32904.0],       [32905.0, 32906.0, 32907.0, 32908.0, 32909.0, 32910.0],       [32911.0, 32912.0, 32913.0, 32914.0, 32915.0, 32916.0],       [32917.0, 32918.0, 32919.0, 32920.0, 32921.0, 32922.0],       [32923.0, 32924.0, 32925.0, 32926.0, 32927.0, 32928.0]],      [[32929.0, 32930.0, 32931.0, 32932.0, 32933.0, 32934.0],       [32935.0, 32936.0, 32937.0, 32938.0, 32939.0, 32940.0],       [32941.0, 32942.0, 32943.0, 32944.0, 32945.0, 32946.0],       [32947.0, 32948.0, 32949.0, 32950.0, 32951.0, 32952.0],       [32953.0, 32954.0, 32955.0, 32956.0, 32957.0, 32958.0],       [32959.0, 32960.0, 32961.0, 32962.0, 32963.0, 32964.0],       [32965.0, 32966.0, 32967.0, 32968.0, 32969.0, 32970.0]],      [[32971.0, 32972.0, 32973.0, 32974.0, 32975.0, 32976.0],       [32977.0, 32978.0, 32979.0, 32980.0, 32981.0, 32982.0],       [32983.0, 32984.0, 32985.0, 32986.0, 32987.0, 32988.0],       [32989.0, 32990.0, 32991.0, 32992.0, 32993.0, 32994.0],       [32995.0, 32996.0, 32997.0, 32998.0, 32999.0, 33000.0],       [33001.0, 33002.0, 33003.0, 33004.0, 33005.0, 33006.0],       [33007.0, 33008.0, 33009.0, 33010.0, 33011.0, 33012.0]]],     [[[33013.0, 33014.0, 33015.0, 33016.0, 33017.0, 33018.0],       [33019.0, 33020.0, 33021.0, 33022.0, 33023.0, 33024.0],       [33025.0, 33026.0, 33027.0, 33028.0, 33029.0, 33030.0],       [33031.0, 33032.0, 33033.0, 33034.0, 33035.0, 33036.0],       [33037.0, 33038.0, 33039.0, 33040.0, 33041.0, 33042.0],       [33043.0, 33044.0, 33045.0, 33046.0, 33047.0, 33048.0],       [33049.0, 33050.0, 33051.0, 33052.0, 33053.0, 33054.0]],      [[33055.0, 33056.0, 33057.0, 33058.0, 33059.0, 33060.0],       [33061.0, 33062.0, 33063.0, 33064.0, 33065.0, 33066.0],       [33067.0, 33068.0, 33069.0, 33070.0, 33071.0, 33072.0],       [33073.0, 33074.0, 33075.0, 33076.0, 33077.0, 33078.0],       [33079.0, 33080.0, 33081.0, 33082.0, 33083.0, 33084.0],       [33085.0, 33086.0, 33087.0, 33088.0, 33089.0, 33090.0],       [33091.0, 33092.0, 33093.0, 33094.0, 33095.0, 33096.0]],      [[33097.0, 33098.0, 33099.0, 33100.0, 33101.0, 33102.0],       [33103.0, 33104.0, 33105.0, 33106.0, 33107.0, 33108.0],       [33109.0, 33110.0, 33111.0, 33112.0, 33113.0, 33114.0],       [33115.0, 33116.0, 33117.0, 33118.0, 33119.0, 33120.0],       [33121.0, 33122.0, 33123.0, 33124.0, 33125.0, 33126.0],       [33127.0, 33128.0, 33129.0, 33130.0, 33131.0, 33132.0],       [33133.0, 33134.0, 33135.0, 33136.0, 33137.0, 33138.0]],      [[33139.0, 33140.0, 33141.0, 33142.0, 33143.0, 33144.0],       [33145.0, 33146.0, 33147.0, 33148.0, 33149.0, 33150.0],       [33151.0, 33152.0, 33153.0, 33154.0, 33155.0, 33156.0],       [33157.0, 33158.0, 33159.0, 33160.0, 33161.0, 33162.0],       [33163.0, 33164.0, 33165.0, 33166.0, 33167.0, 33168.0],       [33169.0, 33170.0, 33171.0, 33172.0, 33173.0, 33174.0],       [33175.0, 33176.0, 33177.0, 33178.0, 33179.0, 33180.0]],      [[33181.0, 33182.0, 33183.0, 33184.0, 33185.0, 33186.0],       [33187.0, 33188.0, 33189.0, 33190.0, 33191.0, 33192.0],       [33193.0, 33194.0, 33195.0, 33196.0, 33197.0, 33198.0],       [33199.0, 33200.0, 33201.0, 33202.0, 33203.0, 33204.0],       [33205.0, 33206.0, 33207.0, 33208.0, 33209.0, 33210.0],       [33211.0, 33212.0, 33213.0, 33214.0, 33215.0, 33216.0],       [33217.0, 33218.0, 33219.0, 33220.0, 33221.0, 33222.0]],      [[33223.0, 33224.0, 33225.0, 33226.0, 33227.0, 33228.0],       [33229.0, 33230.0, 33231.0, 33232.0, 33233.0, 33234.0],       [33235.0, 33236.0, 33237.0, 33238.0, 33239.0, 33240.0],       [33241.0, 33242.0, 33243.0, 33244.0, 33245.0, 33246.0],       [33247.0, 33248.0, 33249.0, 33250.0, 33251.0, 33252.0],       [33253.0, 33254.0, 33255.0, 33256.0, 33257.0, 33258.0],       [33259.0, 33260.0, 33261.0, 33262.0, 33263.0, 33264.0]]]],    [[[[33265.0, 33266.0, 33267.0, 33268.0, 33269.0, 33270.0],       [33271.0, 33272.0, 33273.0, 33274.0, 33275.0, 33276.0],       [33277.0, 33278.0, 33279.0, 33280.0, 33281.0, 33282.0],       [33283.0, 33284.0, 33285.0, 33286.0, 33287.0, 33288.0],       [33289.0, 33290.0, 33291.0, 33292.0, 33293.0, 33294.0],       [33295.0, 33296.0, 33297.0, 33298.0, 33299.0, 33300.0],       [33301.0, 33302.0, 33303.0, 33304.0, 33305.0, 33306.0]],      [[33307.0, 33308.0, 33309.0, 33310.0, 33311.0, 33312.0],       [33313.0, 33314.0, 33315.0, 33316.0, 33317.0, 33318.0],       [33319.0, 33320.0, 33321.0, 33322.0, 33323.0, 33324.0],       [33325.0, 33326.0, 33327.0, 33328.0, 33329.0, 33330.0],       [33331.0, 33332.0, 33333.0, 33334.0, 33335.0, 33336.0],       [33337.0, 33338.0, 33339.0, 33340.0, 33341.0, 33342.0],       [33343.0, 33344.0, 33345.0, 33346.0, 33347.0, 33348.0]],      [[33349.0, 33350.0, 33351.0, 33352.0, 33353.0, 33354.0],       [33355.0, 33356.0, 33357.0, 33358.0, 33359.0, 33360.0],       [33361.0, 33362.0, 33363.0, 33364.0, 33365.0, 33366.0],       [33367.0, 33368.0, 33369.0, 33370.0, 33371.0, 33372.0],       [33373.0, 33374.0, 33375.0, 33376.0, 33377.0, 33378.0],       [33379.0, 33380.0, 33381.0, 33382.0, 33383.0, 33384.0],       [33385.0, 33386.0, 33387.0, 33388.0, 33389.0, 33390.0]],      [[33391.0, 33392.0, 33393.0, 33394.0, 33395.0, 33396.0],       [33397.0, 33398.0, 33399.0, 33400.0, 33401.0, 33402.0],       [33403.0, 33404.0, 33405.0, 33406.0, 33407.0, 33408.0],       [33409.0, 33410.0, 33411.0, 33412.0, 33413.0, 33414.0],       [33415.0, 33416.0, 33417.0, 33418.0, 33419.0, 33420.0],       [33421.0, 33422.0, 33423.0, 33424.0, 33425.0, 33426.0],       [33427.0, 33428.0, 33429.0, 33430.0, 33431.0, 33432.0]],      [[33433.0, 33434.0, 33435.0, 33436.0, 33437.0, 33438.0],       [33439.0, 33440.0, 33441.0, 33442.0, 33443.0, 33444.0],       [33445.0, 33446.0, 33447.0, 33448.0, 33449.0, 33450.0],       [33451.0, 33452.0, 33453.0, 33454.0, 33455.0, 33456.0],       [33457.0, 33458.0, 33459.0, 33460.0, 33461.0, 33462.0],       [33463.0, 33464.0, 33465.0, 33466.0, 33467.0, 33468.0],       [33469.0, 33470.0, 33471.0, 33472.0, 33473.0, 33474.0]],      [[33475.0, 33476.0, 33477.0, 33478.0, 33479.0, 33480.0],       [33481.0, 33482.0, 33483.0, 33484.0, 33485.0, 33486.0],       [33487.0, 33488.0, 33489.0, 33490.0, 33491.0, 33492.0],       [33493.0, 33494.0, 33495.0, 33496.0, 33497.0, 33498.0],       [33499.0, 33500.0, 33501.0, 33502.0, 33503.0, 33504.0],       [33505.0, 33506.0, 33507.0, 33508.0, 33509.0, 33510.0],       [33511.0, 33512.0, 33513.0, 33514.0, 33515.0, 33516.0]]],     [[[33517.0, 33518.0, 33519.0, 33520.0, 33521.0, 33522.0],       [33523.0, 33524.0, 33525.0, 33526.0, 33527.0, 33528.0],       [33529.0, 33530.0, 33531.0, 33532.0, 33533.0, 33534.0],       [33535.0, 33536.0, 33537.0, 33538.0, 33539.0, 33540.0],       [33541.0, 33542.0, 33543.0, 33544.0, 33545.0, 33546.0],       [33547.0, 33548.0, 33549.0, 33550.0, 33551.0, 33552.0],       [33553.0, 33554.0, 33555.0, 33556.0, 33557.0, 33558.0]],      [[33559.0, 33560.0, 33561.0, 33562.0, 33563.0, 33564.0],       [33565.0, 33566.0, 33567.0, 33568.0, 33569.0, 33570.0],       [33571.0, 33572.0, 33573.0, 33574.0, 33575.0, 33576.0],       [33577.0, 33578.0, 33579.0, 33580.0, 33581.0, 33582.0],       [33583.0, 33584.0, 33585.0, 33586.0, 33587.0, 33588.0],       [33589.0, 33590.0, 33591.0, 33592.0, 33593.0, 33594.0],       [33595.0, 33596.0, 33597.0, 33598.0, 33599.0, 33600.0]],      [[33601.0, 33602.0, 33603.0, 33604.0, 33605.0, 33606.0],       [33607.0, 33608.0, 33609.0, 33610.0, 33611.0, 33612.0],       [33613.0, 33614.0, 33615.0, 33616.0, 33617.0, 33618.0],       [33619.0, 33620.0, 33621.0, 33622.0, 33623.0, 33624.0],       [33625.0, 33626.0, 33627.0, 33628.0, 33629.0, 33630.0],       [33631.0, 33632.0, 33633.0, 33634.0, 33635.0, 33636.0],       [33637.0, 33638.0, 33639.0, 33640.0, 33641.0, 33642.0]],      [[33643.0, 33644.0, 33645.0, 33646.0, 33647.0, 33648.0],       [33649.0, 33650.0, 33651.0, 33652.0, 33653.0, 33654.0],       [33655.0, 33656.0, 33657.0, 33658.0, 33659.0, 33660.0],       [33661.0, 33662.0, 33663.0, 33664.0, 33665.0, 33666.0],       [33667.0, 33668.0, 33669.0, 33670.0, 33671.0, 33672.0],       [33673.0, 33674.0, 33675.0, 33676.0, 33677.0, 33678.0],       [33679.0, 33680.0, 33681.0, 33682.0, 33683.0, 33684.0]],      [[33685.0, 33686.0, 33687.0, 33688.0, 33689.0, 33690.0],       [33691.0, 33692.0, 33693.0, 33694.0, 33695.0, 33696.0],       [33697.0, 33698.0, 33699.0, 33700.0, 33701.0, 33702.0],       [33703.0, 33704.0, 33705.0, 33706.0, 33707.0, 33708.0],       [33709.0, 33710.0, 33711.0, 33712.0, 33713.0, 33714.0],       [33715.0, 33716.0, 33717.0, 33718.0, 33719.0, 33720.0],       [33721.0, 33722.0, 33723.0, 33724.0, 33725.0, 33726.0]],      [[33727.0, 33728.0, 33729.0, 33730.0, 33731.0, 33732.0],       [33733.0, 33734.0, 33735.0, 33736.0, 33737.0, 33738.0],       [33739.0, 33740.0, 33741.0, 33742.0, 33743.0, 33744.0],       [33745.0, 33746.0, 33747.0, 33748.0, 33749.0, 33750.0],       [33751.0, 33752.0, 33753.0, 33754.0, 33755.0, 33756.0],       [33757.0, 33758.0, 33759.0, 33760.0, 33761.0, 33762.0],       [33763.0, 33764.0, 33765.0, 33766.0, 33767.0, 33768.0]]],     [[[33769.0, 33770.0, 33771.0, 33772.0, 33773.0, 33774.0],       [33775.0, 33776.0, 33777.0, 33778.0, 33779.0, 33780.0],       [33781.0, 33782.0, 33783.0, 33784.0, 33785.0, 33786.0],       [33787.0, 33788.0, 33789.0, 33790.0, 33791.0, 33792.0],       [33793.0, 33794.0, 33795.0, 33796.0, 33797.0, 33798.0],       [33799.0, 33800.0, 33801.0, 33802.0, 33803.0, 33804.0],       [33805.0, 33806.0, 33807.0, 33808.0, 33809.0, 33810.0]],      [[33811.0, 33812.0, 33813.0, 33814.0, 33815.0, 33816.0],       [33817.0, 33818.0, 33819.0, 33820.0, 33821.0, 33822.0],       [33823.0, 33824.0, 33825.0, 33826.0, 33827.0, 33828.0],       [33829.0, 33830.0, 33831.0, 33832.0, 33833.0, 33834.0],       [33835.0, 33836.0, 33837.0, 33838.0, 33839.0, 33840.0],       [33841.0, 33842.0, 33843.0, 33844.0, 33845.0, 33846.0],       [33847.0, 33848.0, 33849.0, 33850.0, 33851.0, 33852.0]],      [[33853.0, 33854.0, 33855.0, 33856.0, 33857.0, 33858.0],       [33859.0, 33860.0, 33861.0, 33862.0, 33863.0, 33864.0],       [33865.0, 33866.0, 33867.0, 33868.0, 33869.0, 33870.0],       [33871.0, 33872.0, 33873.0, 33874.0, 33875.0, 33876.0],       [33877.0, 33878.0, 33879.0, 33880.0, 33881.0, 33882.0],       [33883.0, 33884.0, 33885.0, 33886.0, 33887.0, 33888.0],       [33889.0, 33890.0, 33891.0, 33892.0, 33893.0, 33894.0]],      [[33895.0, 33896.0, 33897.0, 33898.0, 33899.0, 33900.0],       [33901.0, 33902.0, 33903.0, 33904.0, 33905.0, 33906.0],       [33907.0, 33908.0, 33909.0, 33910.0, 33911.0, 33912.0],       [33913.0, 33914.0, 33915.0, 33916.0, 33917.0, 33918.0],       [33919.0, 33920.0, 33921.0, 33922.0, 33923.0, 33924.0],       [33925.0, 33926.0, 33927.0, 33928.0, 33929.0, 33930.0],       [33931.0, 33932.0, 33933.0, 33934.0, 33935.0, 33936.0]],      [[33937.0, 33938.0, 33939.0, 33940.0, 33941.0, 33942.0],       [33943.0, 33944.0, 33945.0, 33946.0, 33947.0, 33948.0],       [33949.0, 33950.0, 33951.0, 33952.0, 33953.0, 33954.0],       [33955.0, 33956.0, 33957.0, 33958.0, 33959.0, 33960.0],       [33961.0, 33962.0, 33963.0, 33964.0, 33965.0, 33966.0],       [33967.0, 33968.0, 33969.0, 33970.0, 33971.0, 33972.0],       [33973.0, 33974.0, 33975.0, 33976.0, 33977.0, 33978.0]],      [[33979.0, 33980.0, 33981.0, 33982.0, 33983.0, 33984.0],       [33985.0, 33986.0, 33987.0, 33988.0, 33989.0, 33990.0],       [33991.0, 33992.0, 33993.0, 33994.0, 33995.0, 33996.0],       [33997.0, 33998.0, 33999.0, 34000.0, 34001.0, 34002.0],       [34003.0, 34004.0, 34005.0, 34006.0, 34007.0, 34008.0],       [34009.0, 34010.0, 34011.0, 34012.0, 34013.0, 34014.0],       [34015.0, 34016.0, 34017.0, 34018.0, 34019.0, 34020.0]]],     [[[34021.0, 34022.0, 34023.0, 34024.0, 34025.0, 34026.0],       [34027.0, 34028.0, 34029.0, 34030.0, 34031.0, 34032.0],       [34033.0, 34034.0, 34035.0, 34036.0, 34037.0, 34038.0],       [34039.0, 34040.0, 34041.0, 34042.0, 34043.0, 34044.0],       [34045.0, 34046.0, 34047.0, 34048.0, 34049.0, 34050.0],       [34051.0, 34052.0, 34053.0, 34054.0, 34055.0, 34056.0],       [34057.0, 34058.0, 34059.0, 34060.0, 34061.0, 34062.0]],      [[34063.0, 34064.0, 34065.0, 34066.0, 34067.0, 34068.0],       [34069.0, 34070.0, 34071.0, 34072.0, 34073.0, 34074.0],       [34075.0, 34076.0, 34077.0, 34078.0, 34079.0, 34080.0],       [34081.0, 34082.0, 34083.0, 34084.0, 34085.0, 34086.0],       [34087.0, 34088.0, 34089.0, 34090.0, 34091.0, 34092.0],       [34093.0, 34094.0, 34095.0, 34096.0, 34097.0, 34098.0],       [34099.0, 34100.0, 34101.0, 34102.0, 34103.0, 34104.0]],      [[34105.0, 34106.0, 34107.0, 34108.0, 34109.0, 34110.0],       [34111.0, 34112.0, 34113.0, 34114.0, 34115.0, 34116.0],       [34117.0, 34118.0, 34119.0, 34120.0, 34121.0, 34122.0],       [34123.0, 34124.0, 34125.0, 34126.0, 34127.0, 34128.0],       [34129.0, 34130.0, 34131.0, 34132.0, 34133.0, 34134.0],       [34135.0, 34136.0, 34137.0, 34138.0, 34139.0, 34140.0],       [34141.0, 34142.0, 34143.0, 34144.0, 34145.0, 34146.0]],      [[34147.0, 34148.0, 34149.0, 34150.0, 34151.0, 34152.0],       [34153.0, 34154.0, 34155.0, 34156.0, 34157.0, 34158.0],       [34159.0, 34160.0, 34161.0, 34162.0, 34163.0, 34164.0],       [34165.0, 34166.0, 34167.0, 34168.0, 34169.0, 34170.0],       [34171.0, 34172.0, 34173.0, 34174.0, 34175.0, 34176.0],       [34177.0, 34178.0, 34179.0, 34180.0, 34181.0, 34182.0],       [34183.0, 34184.0, 34185.0, 34186.0, 34187.0, 34188.0]],      [[34189.0, 34190.0, 34191.0, 34192.0, 34193.0, 34194.0],       [34195.0, 34196.0, 34197.0, 34198.0, 34199.0, 34200.0],       [34201.0, 34202.0, 34203.0, 34204.0, 34205.0, 34206.0],       [34207.0, 34208.0, 34209.0, 34210.0, 34211.0, 34212.0],       [34213.0, 34214.0, 34215.0, 34216.0, 34217.0, 34218.0],       [34219.0, 34220.0, 34221.0, 34222.0, 34223.0, 34224.0],       [34225.0, 34226.0, 34227.0, 34228.0, 34229.0, 34230.0]],      [[34231.0, 34232.0, 34233.0, 34234.0, 34235.0, 34236.0],       [34237.0, 34238.0, 34239.0, 34240.0, 34241.0, 34242.0],       [34243.0, 34244.0, 34245.0, 34246.0, 34247.0, 34248.0],       [34249.0, 34250.0, 34251.0, 34252.0, 34253.0, 34254.0],       [34255.0, 34256.0, 34257.0, 34258.0, 34259.0, 34260.0],       [34261.0, 34262.0, 34263.0, 34264.0, 34265.0, 34266.0],       [34267.0, 34268.0, 34269.0, 34270.0, 34271.0, 34272.0]]]],    [[[[34273.0, 34274.0, 34275.0, 34276.0, 34277.0, 34278.0],       [34279.0, 34280.0, 34281.0, 34282.0, 34283.0, 34284.0],       [34285.0, 34286.0, 34287.0, 34288.0, 34289.0, 34290.0],       [34291.0, 34292.0, 34293.0, 34294.0, 34295.0, 34296.0],       [34297.0, 34298.0, 34299.0, 34300.0, 34301.0, 34302.0],       [34303.0, 34304.0, 34305.0, 34306.0, 34307.0, 34308.0],       [34309.0, 34310.0, 34311.0, 34312.0, 34313.0, 34314.0]],      [[34315.0, 34316.0, 34317.0, 34318.0, 34319.0, 34320.0],       [34321.0, 34322.0, 34323.0, 34324.0, 34325.0, 34326.0],       [34327.0, 34328.0, 34329.0, 34330.0, 34331.0, 34332.0],       [34333.0, 34334.0, 34335.0, 34336.0, 34337.0, 34338.0],       [34339.0, 34340.0, 34341.0, 34342.0, 34343.0, 34344.0],       [34345.0, 34346.0, 34347.0, 34348.0, 34349.0, 34350.0],       [34351.0, 34352.0, 34353.0, 34354.0, 34355.0, 34356.0]],      [[34357.0, 34358.0, 34359.0, 34360.0, 34361.0, 34362.0],       [34363.0, 34364.0, 34365.0, 34366.0, 34367.0, 34368.0],       [34369.0, 34370.0, 34371.0, 34372.0, 34373.0, 34374.0],       [34375.0, 34376.0, 34377.0, 34378.0, 34379.0, 34380.0],       [34381.0, 34382.0, 34383.0, 34384.0, 34385.0, 34386.0],       [34387.0, 34388.0, 34389.0, 34390.0, 34391.0, 34392.0],       [34393.0, 34394.0, 34395.0, 34396.0, 34397.0, 34398.0]],      [[34399.0, 34400.0, 34401.0, 34402.0, 34403.0, 34404.0],       [34405.0, 34406.0, 34407.0, 34408.0, 34409.0, 34410.0],       [34411.0, 34412.0, 34413.0, 34414.0, 34415.0, 34416.0],       [34417.0, 34418.0, 34419.0, 34420.0, 34421.0, 34422.0],       [34423.0, 34424.0, 34425.0, 34426.0, 34427.0, 34428.0],       [34429.0, 34430.0, 34431.0, 34432.0, 34433.0, 34434.0],       [34435.0, 34436.0, 34437.0, 34438.0, 34439.0, 34440.0]],      [[34441.0, 34442.0, 34443.0, 34444.0, 34445.0, 34446.0],       [34447.0, 34448.0, 34449.0, 34450.0, 34451.0, 34452.0],       [34453.0, 34454.0, 34455.0, 34456.0, 34457.0, 34458.0],       [34459.0, 34460.0, 34461.0, 34462.0, 34463.0, 34464.0],       [34465.0, 34466.0, 34467.0, 34468.0, 34469.0, 34470.0],       [34471.0, 34472.0, 34473.0, 34474.0, 34475.0, 34476.0],       [34477.0, 34478.0, 34479.0, 34480.0, 34481.0, 34482.0]],      [[34483.0, 34484.0, 34485.0, 34486.0, 34487.0, 34488.0],       [34489.0, 34490.0, 34491.0, 34492.0, 34493.0, 34494.0],       [34495.0, 34496.0, 34497.0, 34498.0, 34499.0, 34500.0],       [34501.0, 34502.0, 34503.0, 34504.0, 34505.0, 34506.0],       [34507.0, 34508.0, 34509.0, 34510.0, 34511.0, 34512.0],       [34513.0, 34514.0, 34515.0, 34516.0, 34517.0, 34518.0],       [34519.0, 34520.0, 34521.0, 34522.0, 34523.0, 34524.0]]],     [[[34525.0, 34526.0, 34527.0, 34528.0, 34529.0, 34530.0],       [34531.0, 34532.0, 34533.0, 34534.0, 34535.0, 34536.0],       [34537.0, 34538.0, 34539.0, 34540.0, 34541.0, 34542.0],       [34543.0, 34544.0, 34545.0, 34546.0, 34547.0, 34548.0],       [34549.0, 34550.0, 34551.0, 34552.0, 34553.0, 34554.0],       [34555.0, 34556.0, 34557.0, 34558.0, 34559.0, 34560.0],       [34561.0, 34562.0, 34563.0, 34564.0, 34565.0, 34566.0]],      [[34567.0, 34568.0, 34569.0, 34570.0, 34571.0, 34572.0],       [34573.0, 34574.0, 34575.0, 34576.0, 34577.0, 34578.0],       [34579.0, 34580.0, 34581.0, 34582.0, 34583.0, 34584.0],       [34585.0, 34586.0, 34587.0, 34588.0, 34589.0, 34590.0],       [34591.0, 34592.0, 34593.0, 34594.0, 34595.0, 34596.0],       [34597.0, 34598.0, 34599.0, 34600.0, 34601.0, 34602.0],       [34603.0, 34604.0, 34605.0, 34606.0, 34607.0, 34608.0]],      [[34609.0, 34610.0, 34611.0, 34612.0, 34613.0, 34614.0],       [34615.0, 34616.0, 34617.0, 34618.0, 34619.0, 34620.0],       [34621.0, 34622.0, 34623.0, 34624.0, 34625.0, 34626.0],       [34627.0, 34628.0, 34629.0, 34630.0, 34631.0, 34632.0],       [34633.0, 34634.0, 34635.0, 34636.0, 34637.0, 34638.0],       [34639.0, 34640.0, 34641.0, 34642.0, 34643.0, 34644.0],       [34645.0, 34646.0, 34647.0, 34648.0, 34649.0, 34650.0]],      [[34651.0, 34652.0, 34653.0, 34654.0, 34655.0, 34656.0],       [34657.0, 34658.0, 34659.0, 34660.0, 34661.0, 34662.0],       [34663.0, 34664.0, 34665.0, 34666.0, 34667.0, 34668.0],       [34669.0, 34670.0, 34671.0, 34672.0, 34673.0, 34674.0],       [34675.0, 34676.0, 34677.0, 34678.0, 34679.0, 34680.0],       [34681.0, 34682.0, 34683.0, 34684.0, 34685.0, 34686.0],       [34687.0, 34688.0, 34689.0, 34690.0, 34691.0, 34692.0]],      [[34693.0, 34694.0, 34695.0, 34696.0, 34697.0, 34698.0],       [34699.0, 34700.0, 34701.0, 34702.0, 34703.0, 34704.0],       [34705.0, 34706.0, 34707.0, 34708.0, 34709.0, 34710.0],       [34711.0, 34712.0, 34713.0, 34714.0, 34715.0, 34716.0],       [34717.0, 34718.0, 34719.0, 34720.0, 34721.0, 34722.0],       [34723.0, 34724.0, 34725.0, 34726.0, 34727.0, 34728.0],       [34729.0, 34730.0, 34731.0, 34732.0, 34733.0, 34734.0]],      [[34735.0, 34736.0, 34737.0, 34738.0, 34739.0, 34740.0],       [34741.0, 34742.0, 34743.0, 34744.0, 34745.0, 34746.0],       [34747.0, 34748.0, 34749.0, 34750.0, 34751.0, 34752.0],       [34753.0, 34754.0, 34755.0, 34756.0, 34757.0, 34758.0],       [34759.0, 34760.0, 34761.0, 34762.0, 34763.0, 34764.0],       [34765.0, 34766.0, 34767.0, 34768.0, 34769.0, 34770.0],       [34771.0, 34772.0, 34773.0, 34774.0, 34775.0, 34776.0]]],     [[[34777.0, 34778.0, 34779.0, 34780.0, 34781.0, 34782.0],       [34783.0, 34784.0, 34785.0, 34786.0, 34787.0, 34788.0],       [34789.0, 34790.0, 34791.0, 34792.0, 34793.0, 34794.0],       [34795.0, 34796.0, 34797.0, 34798.0, 34799.0, 34800.0],       [34801.0, 34802.0, 34803.0, 34804.0, 34805.0, 34806.0],       [34807.0, 34808.0, 34809.0, 34810.0, 34811.0, 34812.0],       [34813.0, 34814.0, 34815.0, 34816.0, 34817.0, 34818.0]],      [[34819.0, 34820.0, 34821.0, 34822.0, 34823.0, 34824.0],       [34825.0, 34826.0, 34827.0, 34828.0, 34829.0, 34830.0],       [34831.0, 34832.0, 34833.0, 34834.0, 34835.0, 34836.0],       [34837.0, 34838.0, 34839.0, 34840.0, 34841.0, 34842.0],       [34843.0, 34844.0, 34845.0, 34846.0, 34847.0, 34848.0],       [34849.0, 34850.0, 34851.0, 34852.0, 34853.0, 34854.0],       [34855.0, 34856.0, 34857.0, 34858.0, 34859.0, 34860.0]],      [[34861.0, 34862.0, 34863.0, 34864.0, 34865.0, 34866.0],       [34867.0, 34868.0, 34869.0, 34870.0, 34871.0, 34872.0],       [34873.0, 34874.0, 34875.0, 34876.0, 34877.0, 34878.0],       [34879.0, 34880.0, 34881.0, 34882.0, 34883.0, 34884.0],       [34885.0, 34886.0, 34887.0, 34888.0, 34889.0, 34890.0],       [34891.0, 34892.0, 34893.0, 34894.0, 34895.0, 34896.0],       [34897.0, 34898.0, 34899.0, 34900.0, 34901.0, 34902.0]],      [[34903.0, 34904.0, 34905.0, 34906.0, 34907.0, 34908.0],       [34909.0, 34910.0, 34911.0, 34912.0, 34913.0, 34914.0],       [34915.0, 34916.0, 34917.0, 34918.0, 34919.0, 34920.0],       [34921.0, 34922.0, 34923.0, 34924.0, 34925.0, 34926.0],       [34927.0, 34928.0, 34929.0, 34930.0, 34931.0, 34932.0],       [34933.0, 34934.0, 34935.0, 34936.0, 34937.0, 34938.0],       [34939.0, 34940.0, 34941.0, 34942.0, 34943.0, 34944.0]],      [[34945.0, 34946.0, 34947.0, 34948.0, 34949.0, 34950.0],       [34951.0, 34952.0, 34953.0, 34954.0, 34955.0, 34956.0],       [34957.0, 34958.0, 34959.0, 34960.0, 34961.0, 34962.0],       [34963.0, 34964.0, 34965.0, 34966.0, 34967.0, 34968.0],       [34969.0, 34970.0, 34971.0, 34972.0, 34973.0, 34974.0],       [34975.0, 34976.0, 34977.0, 34978.0, 34979.0, 34980.0],       [34981.0, 34982.0, 34983.0, 34984.0, 34985.0, 34986.0]],      [[34987.0, 34988.0, 34989.0, 34990.0, 34991.0, 34992.0],       [34993.0, 34994.0, 34995.0, 34996.0, 34997.0, 34998.0],       [34999.0, 35000.0, 35001.0, 35002.0, 35003.0, 35004.0],       [35005.0, 35006.0, 35007.0, 35008.0, 35009.0, 35010.0],       [35011.0, 35012.0, 35013.0, 35014.0, 35015.0, 35016.0],       [35017.0, 35018.0, 35019.0, 35020.0, 35021.0, 35022.0],       [35023.0, 35024.0, 35025.0, 35026.0, 35027.0, 35028.0]]],     [[[35029.0, 35030.0, 35031.0, 35032.0, 35033.0, 35034.0],       [35035.0, 35036.0, 35037.0, 35038.0, 35039.0, 35040.0],       [35041.0, 35042.0, 35043.0, 35044.0, 35045.0, 35046.0],       [35047.0, 35048.0, 35049.0, 35050.0, 35051.0, 35052.0],       [35053.0, 35054.0, 35055.0, 35056.0, 35057.0, 35058.0],       [35059.0, 35060.0, 35061.0, 35062.0, 35063.0, 35064.0],       [35065.0, 35066.0, 35067.0, 35068.0, 35069.0, 35070.0]],      [[35071.0, 35072.0, 35073.0, 35074.0, 35075.0, 35076.0],       [35077.0, 35078.0, 35079.0, 35080.0, 35081.0, 35082.0],       [35083.0, 35084.0, 35085.0, 35086.0, 35087.0, 35088.0],       [35089.0, 35090.0, 35091.0, 35092.0, 35093.0, 35094.0],       [35095.0, 35096.0, 35097.0, 35098.0, 35099.0, 35100.0],       [35101.0, 35102.0, 35103.0, 35104.0, 35105.0, 35106.0],       [35107.0, 35108.0, 35109.0, 35110.0, 35111.0, 35112.0]],      [[35113.0, 35114.0, 35115.0, 35116.0, 35117.0, 35118.0],       [35119.0, 35120.0, 35121.0, 35122.0, 35123.0, 35124.0],       [35125.0, 35126.0, 35127.0, 35128.0, 35129.0, 35130.0],       [35131.0, 35132.0, 35133.0, 35134.0, 35135.0, 35136.0],       [35137.0, 35138.0, 35139.0, 35140.0, 35141.0, 35142.0],       [35143.0, 35144.0, 35145.0, 35146.0, 35147.0, 35148.0],       [35149.0, 35150.0, 35151.0, 35152.0, 35153.0, 35154.0]],      [[35155.0, 35156.0, 35157.0, 35158.0, 35159.0, 35160.0],       [35161.0, 35162.0, 35163.0, 35164.0, 35165.0, 35166.0],       [35167.0, 35168.0, 35169.0, 35170.0, 35171.0, 35172.0],       [35173.0, 35174.0, 35175.0, 35176.0, 35177.0, 35178.0],       [35179.0, 35180.0, 35181.0, 35182.0, 35183.0, 35184.0],       [35185.0, 35186.0, 35187.0, 35188.0, 35189.0, 35190.0],       [35191.0, 35192.0, 35193.0, 35194.0, 35195.0, 35196.0]],      [[35197.0, 35198.0, 35199.0, 35200.0, 35201.0, 35202.0],       [35203.0, 35204.0, 35205.0, 35206.0, 35207.0, 35208.0],       [35209.0, 35210.0, 35211.0, 35212.0, 35213.0, 35214.0],       [35215.0, 35216.0, 35217.0, 35218.0, 35219.0, 35220.0],       [35221.0, 35222.0, 35223.0, 35224.0, 35225.0, 35226.0],       [35227.0, 35228.0, 35229.0, 35230.0, 35231.0, 35232.0],       [35233.0, 35234.0, 35235.0, 35236.0, 35237.0, 35238.0]],      [[35239.0, 35240.0, 35241.0, 35242.0, 35243.0, 35244.0],       [35245.0, 35246.0, 35247.0, 35248.0, 35249.0, 35250.0],       [35251.0, 35252.0, 35253.0, 35254.0, 35255.0, 35256.0],       [35257.0, 35258.0, 35259.0, 35260.0, 35261.0, 35262.0],       [35263.0, 35264.0, 35265.0, 35266.0, 35267.0, 35268.0],       [35269.0, 35270.0, 35271.0, 35272.0, 35273.0, 35274.0],       [35275.0, 35276.0, 35277.0, 35278.0, 35279.0, 35280.0]]]],    [[[[35281.0, 35282.0, 35283.0, 35284.0, 35285.0, 35286.0],       [35287.0, 35288.0, 35289.0, 35290.0, 35291.0, 35292.0],       [35293.0, 35294.0, 35295.0, 35296.0, 35297.0, 35298.0],       [35299.0, 35300.0, 35301.0, 35302.0, 35303.0, 35304.0],       [35305.0, 35306.0, 35307.0, 35308.0, 35309.0, 35310.0],       [35311.0, 35312.0, 35313.0, 35314.0, 35315.0, 35316.0],       [35317.0, 35318.0, 35319.0, 35320.0, 35321.0, 35322.0]],      [[35323.0, 35324.0, 35325.0, 35326.0, 35327.0, 35328.0],       [35329.0, 35330.0, 35331.0, 35332.0, 35333.0, 35334.0],       [35335.0, 35336.0, 35337.0, 35338.0, 35339.0, 35340.0],       [35341.0, 35342.0, 35343.0, 35344.0, 35345.0, 35346.0],       [35347.0, 35348.0, 35349.0, 35350.0, 35351.0, 35352.0],       [35353.0, 35354.0, 35355.0, 35356.0, 35357.0, 35358.0],       [35359.0, 35360.0, 35361.0, 35362.0, 35363.0, 35364.0]],      [[35365.0, 35366.0, 35367.0, 35368.0, 35369.0, 35370.0],       [35371.0, 35372.0, 35373.0, 35374.0, 35375.0, 35376.0],       [35377.0, 35378.0, 35379.0, 35380.0, 35381.0, 35382.0],       [35383.0, 35384.0, 35385.0, 35386.0, 35387.0, 35388.0],       [35389.0, 35390.0, 35391.0, 35392.0, 35393.0, 35394.0],       [35395.0, 35396.0, 35397.0, 35398.0, 35399.0, 35400.0],       [35401.0, 35402.0, 35403.0, 35404.0, 35405.0, 35406.0]],      [[35407.0, 35408.0, 35409.0, 35410.0, 35411.0, 35412.0],       [35413.0, 35414.0, 35415.0, 35416.0, 35417.0, 35418.0],       [35419.0, 35420.0, 35421.0, 35422.0, 35423.0, 35424.0],       [35425.0, 35426.0, 35427.0, 35428.0, 35429.0, 35430.0],       [35431.0, 35432.0, 35433.0, 35434.0, 35435.0, 35436.0],       [35437.0, 35438.0, 35439.0, 35440.0, 35441.0, 35442.0],       [35443.0, 35444.0, 35445.0, 35446.0, 35447.0, 35448.0]],      [[35449.0, 35450.0, 35451.0, 35452.0, 35453.0, 35454.0],       [35455.0, 35456.0, 35457.0, 35458.0, 35459.0, 35460.0],       [35461.0, 35462.0, 35463.0, 35464.0, 35465.0, 35466.0],       [35467.0, 35468.0, 35469.0, 35470.0, 35471.0, 35472.0],       [35473.0, 35474.0, 35475.0, 35476.0, 35477.0, 35478.0],       [35479.0, 35480.0, 35481.0, 35482.0, 35483.0, 35484.0],       [35485.0, 35486.0, 35487.0, 35488.0, 35489.0, 35490.0]],      [[35491.0, 35492.0, 35493.0, 35494.0, 35495.0, 35496.0],       [35497.0, 35498.0, 35499.0, 35500.0, 35501.0, 35502.0],       [35503.0, 35504.0, 35505.0, 35506.0, 35507.0, 35508.0],       [35509.0, 35510.0, 35511.0, 35512.0, 35513.0, 35514.0],       [35515.0, 35516.0, 35517.0, 35518.0, 35519.0, 35520.0],       [35521.0, 35522.0, 35523.0, 35524.0, 35525.0, 35526.0],       [35527.0, 35528.0, 35529.0, 35530.0, 35531.0, 35532.0]]],     [[[35533.0, 35534.0, 35535.0, 35536.0, 35537.0, 35538.0],       [35539.0, 35540.0, 35541.0, 35542.0, 35543.0, 35544.0],       [35545.0, 35546.0, 35547.0, 35548.0, 35549.0, 35550.0],       [35551.0, 35552.0, 35553.0, 35554.0, 35555.0, 35556.0],       [35557.0, 35558.0, 35559.0, 35560.0, 35561.0, 35562.0],       [35563.0, 35564.0, 35565.0, 35566.0, 35567.0, 35568.0],       [35569.0, 35570.0, 35571.0, 35572.0, 35573.0, 35574.0]],      [[35575.0, 35576.0, 35577.0, 35578.0, 35579.0, 35580.0],       [35581.0, 35582.0, 35583.0, 35584.0, 35585.0, 35586.0],       [35587.0, 35588.0, 35589.0, 35590.0, 35591.0, 35592.0],       [35593.0, 35594.0, 35595.0, 35596.0, 35597.0, 35598.0],       [35599.0, 35600.0, 35601.0, 35602.0, 35603.0, 35604.0],       [35605.0, 35606.0, 35607.0, 35608.0, 35609.0, 35610.0],       [35611.0, 35612.0, 35613.0, 35614.0, 35615.0, 35616.0]],      [[35617.0, 35618.0, 35619.0, 35620.0, 35621.0, 35622.0],       [35623.0, 35624.0, 35625.0, 35626.0, 35627.0, 35628.0],       [35629.0, 35630.0, 35631.0, 35632.0, 35633.0, 35634.0],       [35635.0, 35636.0, 35637.0, 35638.0, 35639.0, 35640.0],       [35641.0, 35642.0, 35643.0, 35644.0, 35645.0, 35646.0],       [35647.0, 35648.0, 35649.0, 35650.0, 35651.0, 35652.0],       [35653.0, 35654.0, 35655.0, 35656.0, 35657.0, 35658.0]],      [[35659.0, 35660.0, 35661.0, 35662.0, 35663.0, 35664.0],       [35665.0, 35666.0, 35667.0, 35668.0, 35669.0, 35670.0],       [35671.0, 35672.0, 35673.0, 35674.0, 35675.0, 35676.0],       [35677.0, 35678.0, 35679.0, 35680.0, 35681.0, 35682.0],       [35683.0, 35684.0, 35685.0, 35686.0, 35687.0, 35688.0],       [35689.0, 35690.0, 35691.0, 35692.0, 35693.0, 35694.0],       [35695.0, 35696.0, 35697.0, 35698.0, 35699.0, 35700.0]],      [[35701.0, 35702.0, 35703.0, 35704.0, 35705.0, 35706.0],       [35707.0, 35708.0, 35709.0, 35710.0, 35711.0, 35712.0],       [35713.0, 35714.0, 35715.0, 35716.0, 35717.0, 35718.0],       [35719.0, 35720.0, 35721.0, 35722.0, 35723.0, 35724.0],       [35725.0, 35726.0, 35727.0, 35728.0, 35729.0, 35730.0],       [35731.0, 35732.0, 35733.0, 35734.0, 35735.0, 35736.0],       [35737.0, 35738.0, 35739.0, 35740.0, 35741.0, 35742.0]],      [[35743.0, 35744.0, 35745.0, 35746.0, 35747.0, 35748.0],       [35749.0, 35750.0, 35751.0, 35752.0, 35753.0, 35754.0],       [35755.0, 35756.0, 35757.0, 35758.0, 35759.0, 35760.0],       [35761.0, 35762.0, 35763.0, 35764.0, 35765.0, 35766.0],       [35767.0, 35768.0, 35769.0, 35770.0, 35771.0, 35772.0],       [35773.0, 35774.0, 35775.0, 35776.0, 35777.0, 35778.0],       [35779.0, 35780.0, 35781.0, 35782.0, 35783.0, 35784.0]]],     [[[35785.0, 35786.0, 35787.0, 35788.0, 35789.0, 35790.0],       [35791.0, 35792.0, 35793.0, 35794.0, 35795.0, 35796.0],       [35797.0, 35798.0, 35799.0, 35800.0, 35801.0, 35802.0],       [35803.0, 35804.0, 35805.0, 35806.0, 35807.0, 35808.0],       [35809.0, 35810.0, 35811.0, 35812.0, 35813.0, 35814.0],       [35815.0, 35816.0, 35817.0, 35818.0, 35819.0, 35820.0],       [35821.0, 35822.0, 35823.0, 35824.0, 35825.0, 35826.0]],      [[35827.0, 35828.0, 35829.0, 35830.0, 35831.0, 35832.0],       [35833.0, 35834.0, 35835.0, 35836.0, 35837.0, 35838.0],       [35839.0, 35840.0, 35841.0, 35842.0, 35843.0, 35844.0],       [35845.0, 35846.0, 35847.0, 35848.0, 35849.0, 35850.0],       [35851.0, 35852.0, 35853.0, 35854.0, 35855.0, 35856.0],       [35857.0, 35858.0, 35859.0, 35860.0, 35861.0, 35862.0],       [35863.0, 35864.0, 35865.0, 35866.0, 35867.0, 35868.0]],      [[35869.0, 35870.0, 35871.0, 35872.0, 35873.0, 35874.0],       [35875.0, 35876.0, 35877.0, 35878.0, 35879.0, 35880.0],       [35881.0, 35882.0, 35883.0, 35884.0, 35885.0, 35886.0],       [35887.0, 35888.0, 35889.0, 35890.0, 35891.0, 35892.0],       [35893.0, 35894.0, 35895.0, 35896.0, 35897.0, 35898.0],       [35899.0, 35900.0, 35901.0, 35902.0, 35903.0, 35904.0],       [35905.0, 35906.0, 35907.0, 35908.0, 35909.0, 35910.0]],      [[35911.0, 35912.0, 35913.0, 35914.0, 35915.0, 35916.0],       [35917.0, 35918.0, 35919.0, 35920.0, 35921.0, 35922.0],       [35923.0, 35924.0, 35925.0, 35926.0, 35927.0, 35928.0],       [35929.0, 35930.0, 35931.0, 35932.0, 35933.0, 35934.0],       [35935.0, 35936.0, 35937.0, 35938.0, 35939.0, 35940.0],       [35941.0, 35942.0, 35943.0, 35944.0, 35945.0, 35946.0],       [35947.0, 35948.0, 35949.0, 35950.0, 35951.0, 35952.0]],      [[35953.0, 35954.0, 35955.0, 35956.0, 35957.0, 35958.0],       [35959.0, 35960.0, 35961.0, 35962.0, 35963.0, 35964.0],       [35965.0, 35966.0, 35967.0, 35968.0, 35969.0, 35970.0],       [35971.0, 35972.0, 35973.0, 35974.0, 35975.0, 35976.0],       [35977.0, 35978.0, 35979.0, 35980.0, 35981.0, 35982.0],       [35983.0, 35984.0, 35985.0, 35986.0, 35987.0, 35988.0],       [35989.0, 35990.0, 35991.0, 35992.0, 35993.0, 35994.0]],      [[35995.0, 35996.0, 35997.0, 35998.0, 35999.0, 36000.0],       [36001.0, 36002.0, 36003.0, 36004.0, 36005.0, 36006.0],       [36007.0, 36008.0, 36009.0, 36010.0, 36011.0, 36012.0],       [36013.0, 36014.0, 36015.0, 36016.0, 36017.0, 36018.0],       [36019.0, 36020.0, 36021.0, 36022.0, 36023.0, 36024.0],       [36025.0, 36026.0, 36027.0, 36028.0, 36029.0, 36030.0],       [36031.0, 36032.0, 36033.0, 36034.0, 36035.0, 36036.0]]],     [[[36037.0, 36038.0, 36039.0, 36040.0, 36041.0, 36042.0],       [36043.0, 36044.0, 36045.0, 36046.0, 36047.0, 36048.0],       [36049.0, 36050.0, 36051.0, 36052.0, 36053.0, 36054.0],       [36055.0, 36056.0, 36057.0, 36058.0, 36059.0, 36060.0],       [36061.0, 36062.0, 36063.0, 36064.0, 36065.0, 36066.0],       [36067.0, 36068.0, 36069.0, 36070.0, 36071.0, 36072.0],       [36073.0, 36074.0, 36075.0, 36076.0, 36077.0, 36078.0]],      [[36079.0, 36080.0, 36081.0, 36082.0, 36083.0, 36084.0],       [36085.0, 36086.0, 36087.0, 36088.0, 36089.0, 36090.0],       [36091.0, 36092.0, 36093.0, 36094.0, 36095.0, 36096.0],       [36097.0, 36098.0, 36099.0, 36100.0, 36101.0, 36102.0],       [36103.0, 36104.0, 36105.0, 36106.0, 36107.0, 36108.0],       [36109.0, 36110.0, 36111.0, 36112.0, 36113.0, 36114.0],       [36115.0, 36116.0, 36117.0, 36118.0, 36119.0, 36120.0]],      [[36121.0, 36122.0, 36123.0, 36124.0, 36125.0, 36126.0],       [36127.0, 36128.0, 36129.0, 36130.0, 36131.0, 36132.0],       [36133.0, 36134.0, 36135.0, 36136.0, 36137.0, 36138.0],       [36139.0, 36140.0, 36141.0, 36142.0, 36143.0, 36144.0],       [36145.0, 36146.0, 36147.0, 36148.0, 36149.0, 36150.0],       [36151.0, 36152.0, 36153.0, 36154.0, 36155.0, 36156.0],       [36157.0, 36158.0, 36159.0, 36160.0, 36161.0, 36162.0]],      [[36163.0, 36164.0, 36165.0, 36166.0, 36167.0, 36168.0],       [36169.0, 36170.0, 36171.0, 36172.0, 36173.0, 36174.0],       [36175.0, 36176.0, 36177.0, 36178.0, 36179.0, 36180.0],       [36181.0, 36182.0, 36183.0, 36184.0, 36185.0, 36186.0],       [36187.0, 36188.0, 36189.0, 36190.0, 36191.0, 36192.0],       [36193.0, 36194.0, 36195.0, 36196.0, 36197.0, 36198.0],       [36199.0, 36200.0, 36201.0, 36202.0, 36203.0, 36204.0]],      [[36205.0, 36206.0, 36207.0, 36208.0, 36209.0, 36210.0],       [36211.0, 36212.0, 36213.0, 36214.0, 36215.0, 36216.0],       [36217.0, 36218.0, 36219.0, 36220.0, 36221.0, 36222.0],       [36223.0, 36224.0, 36225.0, 36226.0, 36227.0, 36228.0],       [36229.0, 36230.0, 36231.0, 36232.0, 36233.0, 36234.0],       [36235.0, 36236.0, 36237.0, 36238.0, 36239.0, 36240.0],       [36241.0, 36242.0, 36243.0, 36244.0, 36245.0, 36246.0]],      [[36247.0, 36248.0, 36249.0, 36250.0, 36251.0, 36252.0],       [36253.0, 36254.0, 36255.0, 36256.0, 36257.0, 36258.0],       [36259.0, 36260.0, 36261.0, 36262.0, 36263.0, 36264.0],       [36265.0, 36266.0, 36267.0, 36268.0, 36269.0, 36270.0],       [36271.0, 36272.0, 36273.0, 36274.0, 36275.0, 36276.0],       [36277.0, 36278.0, 36279.0, 36280.0, 36281.0, 36282.0],       [36283.0, 36284.0, 36285.0, 36286.0, 36287.0, 36288.0]]]]]],  [[[[[[36289.0, 36290.0, 36291.0, 36292.0, 36293.0, 36294.0],       [36295.0, 36296.0, 36297.0, 36298.0, 36299.0, 36300.0],       [36301.0, 36302.0, 36303.0, 36304.0, 36305.0, 36306.0],       [36307.0, 36308.0, 36309.0, 36310.0, 36311.0, 36312.0],       [36313.0, 36314.0, 36315.0, 36316.0, 36317.0, 36318.0],       [36319.0, 36320.0, 36321.0, 36322.0, 36323.0, 36324.0],       [36325.0, 36326.0, 36327.0, 36328.0, 36329.0, 36330.0]],      [[36331.0, 36332.0, 36333.0, 36334.0, 36335.0, 36336.0],       [36337.0, 36338.0, 36339.0, 36340.0, 36341.0, 36342.0],       [36343.0, 36344.0, 36345.0, 36346.0, 36347.0, 36348.0],       [36349.0, 36350.0, 36351.0, 36352.0, 36353.0, 36354.0],       [36355.0, 36356.0, 36357.0, 36358.0, 36359.0, 36360.0],       [36361.0, 36362.0, 36363.0, 36364.0, 36365.0, 36366.0],       [36367.0, 36368.0, 36369.0, 36370.0, 36371.0, 36372.0]],      [[36373.0, 36374.0, 36375.0, 36376.0, 36377.0, 36378.0],       [36379.0, 36380.0, 36381.0, 36382.0, 36383.0, 36384.0],       [36385.0, 36386.0, 36387.0, 36388.0, 36389.0, 36390.0],       [36391.0, 36392.0, 36393.0, 36394.0, 36395.0, 36396.0],       [36397.0, 36398.0, 36399.0, 36400.0, 36401.0, 36402.0],       [36403.0, 36404.0, 36405.0, 36406.0, 36407.0, 36408.0],       [36409.0, 36410.0, 36411.0, 36412.0, 36413.0, 36414.0]],      [[36415.0, 36416.0, 36417.0, 36418.0, 36419.0, 36420.0],       [36421.0, 36422.0, 36423.0, 36424.0, 36425.0, 36426.0],       [36427.0, 36428.0, 36429.0, 36430.0, 36431.0, 36432.0],       [36433.0, 36434.0, 36435.0, 36436.0, 36437.0, 36438.0],       [36439.0, 36440.0, 36441.0, 36442.0, 36443.0, 36444.0],       [36445.0, 36446.0, 36447.0, 36448.0, 36449.0, 36450.0],       [36451.0, 36452.0, 36453.0, 36454.0, 36455.0, 36456.0]],      [[36457.0, 36458.0, 36459.0, 36460.0, 36461.0, 36462.0],       [36463.0, 36464.0, 36465.0, 36466.0, 36467.0, 36468.0],       [36469.0, 36470.0, 36471.0, 36472.0, 36473.0, 36474.0],       [36475.0, 36476.0, 36477.0, 36478.0, 36479.0, 36480.0],       [36481.0, 36482.0, 36483.0, 36484.0, 36485.0, 36486.0],       [36487.0, 36488.0, 36489.0, 36490.0, 36491.0, 36492.0],       [36493.0, 36494.0, 36495.0, 36496.0, 36497.0, 36498.0]],      [[36499.0, 36500.0, 36501.0, 36502.0, 36503.0, 36504.0],       [36505.0, 36506.0, 36507.0, 36508.0, 36509.0, 36510.0],       [36511.0, 36512.0, 36513.0, 36514.0, 36515.0, 36516.0],       [36517.0, 36518.0, 36519.0, 36520.0, 36521.0, 36522.0],       [36523.0, 36524.0, 36525.0, 36526.0, 36527.0, 36528.0],       [36529.0, 36530.0, 36531.0, 36532.0, 36533.0, 36534.0],       [36535.0, 36536.0, 36537.0, 36538.0, 36539.0, 36540.0]]],     [[[36541.0, 36542.0, 36543.0, 36544.0, 36545.0, 36546.0],       [36547.0, 36548.0, 36549.0, 36550.0, 36551.0, 36552.0],       [36553.0, 36554.0, 36555.0, 36556.0, 36557.0, 36558.0],       [36559.0, 36560.0, 36561.0, 36562.0, 36563.0, 36564.0],       [36565.0, 36566.0, 36567.0, 36568.0, 36569.0, 36570.0],       [36571.0, 36572.0, 36573.0, 36574.0, 36575.0, 36576.0],       [36577.0, 36578.0, 36579.0, 36580.0, 36581.0, 36582.0]],      [[36583.0, 36584.0, 36585.0, 36586.0, 36587.0, 36588.0],       [36589.0, 36590.0, 36591.0, 36592.0, 36593.0, 36594.0],       [36595.0, 36596.0, 36597.0, 36598.0, 36599.0, 36600.0],       [36601.0, 36602.0, 36603.0, 36604.0, 36605.0, 36606.0],       [36607.0, 36608.0, 36609.0, 36610.0, 36611.0, 36612.0],       [36613.0, 36614.0, 36615.0, 36616.0, 36617.0, 36618.0],       [36619.0, 36620.0, 36621.0, 36622.0, 36623.0, 36624.0]],      [[36625.0, 36626.0, 36627.0, 36628.0, 36629.0, 36630.0],       [36631.0, 36632.0, 36633.0, 36634.0, 36635.0, 36636.0],       [36637.0, 36638.0, 36639.0, 36640.0, 36641.0, 36642.0],       [36643.0, 36644.0, 36645.0, 36646.0, 36647.0, 36648.0],       [36649.0, 36650.0, 36651.0, 36652.0, 36653.0, 36654.0],       [36655.0, 36656.0, 36657.0, 36658.0, 36659.0, 36660.0],       [36661.0, 36662.0, 36663.0, 36664.0, 36665.0, 36666.0]],      [[36667.0, 36668.0, 36669.0, 36670.0, 36671.0, 36672.0],       [36673.0, 36674.0, 36675.0, 36676.0, 36677.0, 36678.0],       [36679.0, 36680.0, 36681.0, 36682.0, 36683.0, 36684.0],       [36685.0, 36686.0, 36687.0, 36688.0, 36689.0, 36690.0],       [36691.0, 36692.0, 36693.0, 36694.0, 36695.0, 36696.0],       [36697.0, 36698.0, 36699.0, 36700.0, 36701.0, 36702.0],       [36703.0, 36704.0, 36705.0, 36706.0, 36707.0, 36708.0]],      [[36709.0, 36710.0, 36711.0, 36712.0, 36713.0, 36714.0],       [36715.0, 36716.0, 36717.0, 36718.0, 36719.0, 36720.0],       [36721.0, 36722.0, 36723.0, 36724.0, 36725.0, 36726.0],       [36727.0, 36728.0, 36729.0, 36730.0, 36731.0, 36732.0],       [36733.0, 36734.0, 36735.0, 36736.0, 36737.0, 36738.0],       [36739.0, 36740.0, 36741.0, 36742.0, 36743.0, 36744.0],       [36745.0, 36746.0, 36747.0, 36748.0, 36749.0, 36750.0]],      [[36751.0, 36752.0, 36753.0, 36754.0, 36755.0, 36756.0],       [36757.0, 36758.0, 36759.0, 36760.0, 36761.0, 36762.0],       [36763.0, 36764.0, 36765.0, 36766.0, 36767.0, 36768.0],       [36769.0, 36770.0, 36771.0, 36772.0, 36773.0, 36774.0],       [36775.0, 36776.0, 36777.0, 36778.0, 36779.0, 36780.0],       [36781.0, 36782.0, 36783.0, 36784.0, 36785.0, 36786.0],       [36787.0, 36788.0, 36789.0, 36790.0, 36791.0, 36792.0]]],     [[[36793.0, 36794.0, 36795.0, 36796.0, 36797.0, 36798.0],       [36799.0, 36800.0, 36801.0, 36802.0, 36803.0, 36804.0],       [36805.0, 36806.0, 36807.0, 36808.0, 36809.0, 36810.0],       [36811.0, 36812.0, 36813.0, 36814.0, 36815.0, 36816.0],       [36817.0, 36818.0, 36819.0, 36820.0, 36821.0, 36822.0],       [36823.0, 36824.0, 36825.0, 36826.0, 36827.0, 36828.0],       [36829.0, 36830.0, 36831.0, 36832.0, 36833.0, 36834.0]],      [[36835.0, 36836.0, 36837.0, 36838.0, 36839.0, 36840.0],       [36841.0, 36842.0, 36843.0, 36844.0, 36845.0, 36846.0],       [36847.0, 36848.0, 36849.0, 36850.0, 36851.0, 36852.0],       [36853.0, 36854.0, 36855.0, 36856.0, 36857.0, 36858.0],       [36859.0, 36860.0, 36861.0, 36862.0, 36863.0, 36864.0],       [36865.0, 36866.0, 36867.0, 36868.0, 36869.0, 36870.0],       [36871.0, 36872.0, 36873.0, 36874.0, 36875.0, 36876.0]],      [[36877.0, 36878.0, 36879.0, 36880.0, 36881.0, 36882.0],       [36883.0, 36884.0, 36885.0, 36886.0, 36887.0, 36888.0],       [36889.0, 36890.0, 36891.0, 36892.0, 36893.0, 36894.0],       [36895.0, 36896.0, 36897.0, 36898.0, 36899.0, 36900.0],       [36901.0, 36902.0, 36903.0, 36904.0, 36905.0, 36906.0],       [36907.0, 36908.0, 36909.0, 36910.0, 36911.0, 36912.0],       [36913.0, 36914.0, 36915.0, 36916.0, 36917.0, 36918.0]],      [[36919.0, 36920.0, 36921.0, 36922.0, 36923.0, 36924.0],       [36925.0, 36926.0, 36927.0, 36928.0, 36929.0, 36930.0],       [36931.0, 36932.0, 36933.0, 36934.0, 36935.0, 36936.0],       [36937.0, 36938.0, 36939.0, 36940.0, 36941.0, 36942.0],       [36943.0, 36944.0, 36945.0, 36946.0, 36947.0, 36948.0],       [36949.0, 36950.0, 36951.0, 36952.0, 36953.0, 36954.0],       [36955.0, 36956.0, 36957.0, 36958.0, 36959.0, 36960.0]],      [[36961.0, 36962.0, 36963.0, 36964.0, 36965.0, 36966.0],       [36967.0, 36968.0, 36969.0, 36970.0, 36971.0, 36972.0],       [36973.0, 36974.0, 36975.0, 36976.0, 36977.0, 36978.0],       [36979.0, 36980.0, 36981.0, 36982.0, 36983.0, 36984.0],       [36985.0, 36986.0, 36987.0, 36988.0, 36989.0, 36990.0],       [36991.0, 36992.0, 36993.0, 36994.0, 36995.0, 36996.0],       [36997.0, 36998.0, 36999.0, 37000.0, 37001.0, 37002.0]],      [[37003.0, 37004.0, 37005.0, 37006.0, 37007.0, 37008.0],       [37009.0, 37010.0, 37011.0, 37012.0, 37013.0, 37014.0],       [37015.0, 37016.0, 37017.0, 37018.0, 37019.0, 37020.0],       [37021.0, 37022.0, 37023.0, 37024.0, 37025.0, 37026.0],       [37027.0, 37028.0, 37029.0, 37030.0, 37031.0, 37032.0],       [37033.0, 37034.0, 37035.0, 37036.0, 37037.0, 37038.0],       [37039.0, 37040.0, 37041.0, 37042.0, 37043.0, 37044.0]]],     [[[37045.0, 37046.0, 37047.0, 37048.0, 37049.0, 37050.0],       [37051.0, 37052.0, 37053.0, 37054.0, 37055.0, 37056.0],       [37057.0, 37058.0, 37059.0, 37060.0, 37061.0, 37062.0],       [37063.0, 37064.0, 37065.0, 37066.0, 37067.0, 37068.0],       [37069.0, 37070.0, 37071.0, 37072.0, 37073.0, 37074.0],       [37075.0, 37076.0, 37077.0, 37078.0, 37079.0, 37080.0],       [37081.0, 37082.0, 37083.0, 37084.0, 37085.0, 37086.0]],      [[37087.0, 37088.0, 37089.0, 37090.0, 37091.0, 37092.0],       [37093.0, 37094.0, 37095.0, 37096.0, 37097.0, 37098.0],       [37099.0, 37100.0, 37101.0, 37102.0, 37103.0, 37104.0],       [37105.0, 37106.0, 37107.0, 37108.0, 37109.0, 37110.0],       [37111.0, 37112.0, 37113.0, 37114.0, 37115.0, 37116.0],       [37117.0, 37118.0, 37119.0, 37120.0, 37121.0, 37122.0],       [37123.0, 37124.0, 37125.0, 37126.0, 37127.0, 37128.0]],      [[37129.0, 37130.0, 37131.0, 37132.0, 37133.0, 37134.0],       [37135.0, 37136.0, 37137.0, 37138.0, 37139.0, 37140.0],       [37141.0, 37142.0, 37143.0, 37144.0, 37145.0, 37146.0],       [37147.0, 37148.0, 37149.0, 37150.0, 37151.0, 37152.0],       [37153.0, 37154.0, 37155.0, 37156.0, 37157.0, 37158.0],       [37159.0, 37160.0, 37161.0, 37162.0, 37163.0, 37164.0],       [37165.0, 37166.0, 37167.0, 37168.0, 37169.0, 37170.0]],      [[37171.0, 37172.0, 37173.0, 37174.0, 37175.0, 37176.0],       [37177.0, 37178.0, 37179.0, 37180.0, 37181.0, 37182.0],       [37183.0, 37184.0, 37185.0, 37186.0, 37187.0, 37188.0],       [37189.0, 37190.0, 37191.0, 37192.0, 37193.0, 37194.0],       [37195.0, 37196.0, 37197.0, 37198.0, 37199.0, 37200.0],       [37201.0, 37202.0, 37203.0, 37204.0, 37205.0, 37206.0],       [37207.0, 37208.0, 37209.0, 37210.0, 37211.0, 37212.0]],      [[37213.0, 37214.0, 37215.0, 37216.0, 37217.0, 37218.0],       [37219.0, 37220.0, 37221.0, 37222.0, 37223.0, 37224.0],       [37225.0, 37226.0, 37227.0, 37228.0, 37229.0, 37230.0],       [37231.0, 37232.0, 37233.0, 37234.0, 37235.0, 37236.0],       [37237.0, 37238.0, 37239.0, 37240.0, 37241.0, 37242.0],       [37243.0, 37244.0, 37245.0, 37246.0, 37247.0, 37248.0],       [37249.0, 37250.0, 37251.0, 37252.0, 37253.0, 37254.0]],      [[37255.0, 37256.0, 37257.0, 37258.0, 37259.0, 37260.0],       [37261.0, 37262.0, 37263.0, 37264.0, 37265.0, 37266.0],       [37267.0, 37268.0, 37269.0, 37270.0, 37271.0, 37272.0],       [37273.0, 37274.0, 37275.0, 37276.0, 37277.0, 37278.0],       [37279.0, 37280.0, 37281.0, 37282.0, 37283.0, 37284.0],       [37285.0, 37286.0, 37287.0, 37288.0, 37289.0, 37290.0],       [37291.0, 37292.0, 37293.0, 37294.0, 37295.0, 37296.0]]]],    [[[[37297.0, 37298.0, 37299.0, 37300.0, 37301.0, 37302.0],       [37303.0, 37304.0, 37305.0, 37306.0, 37307.0, 37308.0],       [37309.0, 37310.0, 37311.0, 37312.0, 37313.0, 37314.0],       [37315.0, 37316.0, 37317.0, 37318.0, 37319.0, 37320.0],       [37321.0, 37322.0, 37323.0, 37324.0, 37325.0, 37326.0],       [37327.0, 37328.0, 37329.0, 37330.0, 37331.0, 37332.0],       [37333.0, 37334.0, 37335.0, 37336.0, 37337.0, 37338.0]],      [[37339.0, 37340.0, 37341.0, 37342.0, 37343.0, 37344.0],       [37345.0, 37346.0, 37347.0, 37348.0, 37349.0, 37350.0],       [37351.0, 37352.0, 37353.0, 37354.0, 37355.0, 37356.0],       [37357.0, 37358.0, 37359.0, 37360.0, 37361.0, 37362.0],       [37363.0, 37364.0, 37365.0, 37366.0, 37367.0, 37368.0],       [37369.0, 37370.0, 37371.0, 37372.0, 37373.0, 37374.0],       [37375.0, 37376.0, 37377.0, 37378.0, 37379.0, 37380.0]],      [[37381.0, 37382.0, 37383.0, 37384.0, 37385.0, 37386.0],       [37387.0, 37388.0, 37389.0, 37390.0, 37391.0, 37392.0],       [37393.0, 37394.0, 37395.0, 37396.0, 37397.0, 37398.0],       [37399.0, 37400.0, 37401.0, 37402.0, 37403.0, 37404.0],       [37405.0, 37406.0, 37407.0, 37408.0, 37409.0, 37410.0],       [37411.0, 37412.0, 37413.0, 37414.0, 37415.0, 37416.0],       [37417.0, 37418.0, 37419.0, 37420.0, 37421.0, 37422.0]],      [[37423.0, 37424.0, 37425.0, 37426.0, 37427.0, 37428.0],       [37429.0, 37430.0, 37431.0, 37432.0, 37433.0, 37434.0],       [37435.0, 37436.0, 37437.0, 37438.0, 37439.0, 37440.0],       [37441.0, 37442.0, 37443.0, 37444.0, 37445.0, 37446.0],       [37447.0, 37448.0, 37449.0, 37450.0, 37451.0, 37452.0],       [37453.0, 37454.0, 37455.0, 37456.0, 37457.0, 37458.0],       [37459.0, 37460.0, 37461.0, 37462.0, 37463.0, 37464.0]],      [[37465.0, 37466.0, 37467.0, 37468.0, 37469.0, 37470.0],       [37471.0, 37472.0, 37473.0, 37474.0, 37475.0, 37476.0],       [37477.0, 37478.0, 37479.0, 37480.0, 37481.0, 37482.0],       [37483.0, 37484.0, 37485.0, 37486.0, 37487.0, 37488.0],       [37489.0, 37490.0, 37491.0, 37492.0, 37493.0, 37494.0],       [37495.0, 37496.0, 37497.0, 37498.0, 37499.0, 37500.0],       [37501.0, 37502.0, 37503.0, 37504.0, 37505.0, 37506.0]],      [[37507.0, 37508.0, 37509.0, 37510.0, 37511.0, 37512.0],       [37513.0, 37514.0, 37515.0, 37516.0, 37517.0, 37518.0],       [37519.0, 37520.0, 37521.0, 37522.0, 37523.0, 37524.0],       [37525.0, 37526.0, 37527.0, 37528.0, 37529.0, 37530.0],       [37531.0, 37532.0, 37533.0, 37534.0, 37535.0, 37536.0],       [37537.0, 37538.0, 37539.0, 37540.0, 37541.0, 37542.0],       [37543.0, 37544.0, 37545.0, 37546.0, 37547.0, 37548.0]]],     [[[37549.0, 37550.0, 37551.0, 37552.0, 37553.0, 37554.0],       [37555.0, 37556.0, 37557.0, 37558.0, 37559.0, 37560.0],       [37561.0, 37562.0, 37563.0, 37564.0, 37565.0, 37566.0],       [37567.0, 37568.0, 37569.0, 37570.0, 37571.0, 37572.0],       [37573.0, 37574.0, 37575.0, 37576.0, 37577.0, 37578.0],       [37579.0, 37580.0, 37581.0, 37582.0, 37583.0, 37584.0],       [37585.0, 37586.0, 37587.0, 37588.0, 37589.0, 37590.0]],      [[37591.0, 37592.0, 37593.0, 37594.0, 37595.0, 37596.0],       [37597.0, 37598.0, 37599.0, 37600.0, 37601.0, 37602.0],       [37603.0, 37604.0, 37605.0, 37606.0, 37607.0, 37608.0],       [37609.0, 37610.0, 37611.0, 37612.0, 37613.0, 37614.0],       [37615.0, 37616.0, 37617.0, 37618.0, 37619.0, 37620.0],       [37621.0, 37622.0, 37623.0, 37624.0, 37625.0, 37626.0],       [37627.0, 37628.0, 37629.0, 37630.0, 37631.0, 37632.0]],      [[37633.0, 37634.0, 37635.0, 37636.0, 37637.0, 37638.0],       [37639.0, 37640.0, 37641.0, 37642.0, 37643.0, 37644.0],       [37645.0, 37646.0, 37647.0, 37648.0, 37649.0, 37650.0],       [37651.0, 37652.0, 37653.0, 37654.0, 37655.0, 37656.0],       [37657.0, 37658.0, 37659.0, 37660.0, 37661.0, 37662.0],       [37663.0, 37664.0, 37665.0, 37666.0, 37667.0, 37668.0],       [37669.0, 37670.0, 37671.0, 37672.0, 37673.0, 37674.0]],      [[37675.0, 37676.0, 37677.0, 37678.0, 37679.0, 37680.0],       [37681.0, 37682.0, 37683.0, 37684.0, 37685.0, 37686.0],       [37687.0, 37688.0, 37689.0, 37690.0, 37691.0, 37692.0],       [37693.0, 37694.0, 37695.0, 37696.0, 37697.0, 37698.0],       [37699.0, 37700.0, 37701.0, 37702.0, 37703.0, 37704.0],       [37705.0, 37706.0, 37707.0, 37708.0, 37709.0, 37710.0],       [37711.0, 37712.0, 37713.0, 37714.0, 37715.0, 37716.0]],      [[37717.0, 37718.0, 37719.0, 37720.0, 37721.0, 37722.0],       [37723.0, 37724.0, 37725.0, 37726.0, 37727.0, 37728.0],       [37729.0, 37730.0, 37731.0, 37732.0, 37733.0, 37734.0],       [37735.0, 37736.0, 37737.0, 37738.0, 37739.0, 37740.0],       [37741.0, 37742.0, 37743.0, 37744.0, 37745.0, 37746.0],       [37747.0, 37748.0, 37749.0, 37750.0, 37751.0, 37752.0],       [37753.0, 37754.0, 37755.0, 37756.0, 37757.0, 37758.0]],      [[37759.0, 37760.0, 37761.0, 37762.0, 37763.0, 37764.0],       [37765.0, 37766.0, 37767.0, 37768.0, 37769.0, 37770.0],       [37771.0, 37772.0, 37773.0, 37774.0, 37775.0, 37776.0],       [37777.0, 37778.0, 37779.0, 37780.0, 37781.0, 37782.0],       [37783.0, 37784.0, 37785.0, 37786.0, 37787.0, 37788.0],       [37789.0, 37790.0, 37791.0, 37792.0, 37793.0, 37794.0],       [37795.0, 37796.0, 37797.0, 37798.0, 37799.0, 37800.0]]],     [[[37801.0, 37802.0, 37803.0, 37804.0, 37805.0, 37806.0],       [37807.0, 37808.0, 37809.0, 37810.0, 37811.0, 37812.0],       [37813.0, 37814.0, 37815.0, 37816.0, 37817.0, 37818.0],       [37819.0, 37820.0, 37821.0, 37822.0, 37823.0, 37824.0],       [37825.0, 37826.0, 37827.0, 37828.0, 37829.0, 37830.0],       [37831.0, 37832.0, 37833.0, 37834.0, 37835.0, 37836.0],       [37837.0, 37838.0, 37839.0, 37840.0, 37841.0, 37842.0]],      [[37843.0, 37844.0, 37845.0, 37846.0, 37847.0, 37848.0],       [37849.0, 37850.0, 37851.0, 37852.0, 37853.0, 37854.0],       [37855.0, 37856.0, 37857.0, 37858.0, 37859.0, 37860.0],       [37861.0, 37862.0, 37863.0, 37864.0, 37865.0, 37866.0],       [37867.0, 37868.0, 37869.0, 37870.0, 37871.0, 37872.0],       [37873.0, 37874.0, 37875.0, 37876.0, 37877.0, 37878.0],       [37879.0, 37880.0, 37881.0, 37882.0, 37883.0, 37884.0]],      [[37885.0, 37886.0, 37887.0, 37888.0, 37889.0, 37890.0],       [37891.0, 37892.0, 37893.0, 37894.0, 37895.0, 37896.0],       [37897.0, 37898.0, 37899.0, 37900.0, 37901.0, 37902.0],       [37903.0, 37904.0, 37905.0, 37906.0, 37907.0, 37908.0],       [37909.0, 37910.0, 37911.0, 37912.0, 37913.0, 37914.0],       [37915.0, 37916.0, 37917.0, 37918.0, 37919.0, 37920.0],       [37921.0, 37922.0, 37923.0, 37924.0, 37925.0, 37926.0]],      [[37927.0, 37928.0, 37929.0, 37930.0, 37931.0, 37932.0],       [37933.0, 37934.0, 37935.0, 37936.0, 37937.0, 37938.0],       [37939.0, 37940.0, 37941.0, 37942.0, 37943.0, 37944.0],       [37945.0, 37946.0, 37947.0, 37948.0, 37949.0, 37950.0],       [37951.0, 37952.0, 37953.0, 37954.0, 37955.0, 37956.0],       [37957.0, 37958.0, 37959.0, 37960.0, 37961.0, 37962.0],       [37963.0, 37964.0, 37965.0, 37966.0, 37967.0, 37968.0]],      [[37969.0, 37970.0, 37971.0, 37972.0, 37973.0, 37974.0],       [37975.0, 37976.0, 37977.0, 37978.0, 37979.0, 37980.0],       [37981.0, 37982.0, 37983.0, 37984.0, 37985.0, 37986.0],       [37987.0, 37988.0, 37989.0, 37990.0, 37991.0, 37992.0],       [37993.0, 37994.0, 37995.0, 37996.0, 37997.0, 37998.0],       [37999.0, 38000.0, 38001.0, 38002.0, 38003.0, 38004.0],       [38005.0, 38006.0, 38007.0, 38008.0, 38009.0, 38010.0]],      [[38011.0, 38012.0, 38013.0, 38014.0, 38015.0, 38016.0],       [38017.0, 38018.0, 38019.0, 38020.0, 38021.0, 38022.0],       [38023.0, 38024.0, 38025.0, 38026.0, 38027.0, 38028.0],       [38029.0, 38030.0, 38031.0, 38032.0, 38033.0, 38034.0],       [38035.0, 38036.0, 38037.0, 38038.0, 38039.0, 38040.0],       [38041.0, 38042.0, 38043.0, 38044.0, 38045.0, 38046.0],       [38047.0, 38048.0, 38049.0, 38050.0, 38051.0, 38052.0]]],     [[[38053.0, 38054.0, 38055.0, 38056.0, 38057.0, 38058.0],       [38059.0, 38060.0, 38061.0, 38062.0, 38063.0, 38064.0],       [38065.0, 38066.0, 38067.0, 38068.0, 38069.0, 38070.0],       [38071.0, 38072.0, 38073.0, 38074.0, 38075.0, 38076.0],       [38077.0, 38078.0, 38079.0, 38080.0, 38081.0, 38082.0],       [38083.0, 38084.0, 38085.0, 38086.0, 38087.0, 38088.0],       [38089.0, 38090.0, 38091.0, 38092.0, 38093.0, 38094.0]],      [[38095.0, 38096.0, 38097.0, 38098.0, 38099.0, 38100.0],       [38101.0, 38102.0, 38103.0, 38104.0, 38105.0, 38106.0],       [38107.0, 38108.0, 38109.0, 38110.0, 38111.0, 38112.0],       [38113.0, 38114.0, 38115.0, 38116.0, 38117.0, 38118.0],       [38119.0, 38120.0, 38121.0, 38122.0, 38123.0, 38124.0],       [38125.0, 38126.0, 38127.0, 38128.0, 38129.0, 38130.0],       [38131.0, 38132.0, 38133.0, 38134.0, 38135.0, 38136.0]],      [[38137.0, 38138.0, 38139.0, 38140.0, 38141.0, 38142.0],       [38143.0, 38144.0, 38145.0, 38146.0, 38147.0, 38148.0],       [38149.0, 38150.0, 38151.0, 38152.0, 38153.0, 38154.0],       [38155.0, 38156.0, 38157.0, 38158.0, 38159.0, 38160.0],       [38161.0, 38162.0, 38163.0, 38164.0, 38165.0, 38166.0],       [38167.0, 38168.0, 38169.0, 38170.0, 38171.0, 38172.0],       [38173.0, 38174.0, 38175.0, 38176.0, 38177.0, 38178.0]],      [[38179.0, 38180.0, 38181.0, 38182.0, 38183.0, 38184.0],       [38185.0, 38186.0, 38187.0, 38188.0, 38189.0, 38190.0],       [38191.0, 38192.0, 38193.0, 38194.0, 38195.0, 38196.0],       [38197.0, 38198.0, 38199.0, 38200.0, 38201.0, 38202.0],       [38203.0, 38204.0, 38205.0, 38206.0, 38207.0, 38208.0],       [38209.0, 38210.0, 38211.0, 38212.0, 38213.0, 38214.0],       [38215.0, 38216.0, 38217.0, 38218.0, 38219.0, 38220.0]],      [[38221.0, 38222.0, 38223.0, 38224.0, 38225.0, 38226.0],       [38227.0, 38228.0, 38229.0, 38230.0, 38231.0, 38232.0],       [38233.0, 38234.0, 38235.0, 38236.0, 38237.0, 38238.0],       [38239.0, 38240.0, 38241.0, 38242.0, 38243.0, 38244.0],       [38245.0, 38246.0, 38247.0, 38248.0, 38249.0, 38250.0],       [38251.0, 38252.0, 38253.0, 38254.0, 38255.0, 38256.0],       [38257.0, 38258.0, 38259.0, 38260.0, 38261.0, 38262.0]],      [[38263.0, 38264.0, 38265.0, 38266.0, 38267.0, 38268.0],       [38269.0, 38270.0, 38271.0, 38272.0, 38273.0, 38274.0],       [38275.0, 38276.0, 38277.0, 38278.0, 38279.0, 38280.0],       [38281.0, 38282.0, 38283.0, 38284.0, 38285.0, 38286.0],       [38287.0, 38288.0, 38289.0, 38290.0, 38291.0, 38292.0],       [38293.0, 38294.0, 38295.0, 38296.0, 38297.0, 38298.0],       [38299.0, 38300.0, 38301.0, 38302.0, 38303.0, 38304.0]]]],    [[[[38305.0, 38306.0, 38307.0, 38308.0, 38309.0, 38310.0],       [38311.0, 38312.0, 38313.0, 38314.0, 38315.0, 38316.0],       [38317.0, 38318.0, 38319.0, 38320.0, 38321.0, 38322.0],       [38323.0, 38324.0, 38325.0, 38326.0, 38327.0, 38328.0],       [38329.0, 38330.0, 38331.0, 38332.0, 38333.0, 38334.0],       [38335.0, 38336.0, 38337.0, 38338.0, 38339.0, 38340.0],       [38341.0, 38342.0, 38343.0, 38344.0, 38345.0, 38346.0]],      [[38347.0, 38348.0, 38349.0, 38350.0, 38351.0, 38352.0],       [38353.0, 38354.0, 38355.0, 38356.0, 38357.0, 38358.0],       [38359.0, 38360.0, 38361.0, 38362.0, 38363.0, 38364.0],       [38365.0, 38366.0, 38367.0, 38368.0, 38369.0, 38370.0],       [38371.0, 38372.0, 38373.0, 38374.0, 38375.0, 38376.0],       [38377.0, 38378.0, 38379.0, 38380.0, 38381.0, 38382.0],       [38383.0, 38384.0, 38385.0, 38386.0, 38387.0, 38388.0]],      [[38389.0, 38390.0, 38391.0, 38392.0, 38393.0, 38394.0],       [38395.0, 38396.0, 38397.0, 38398.0, 38399.0, 38400.0],       [38401.0, 38402.0, 38403.0, 38404.0, 38405.0, 38406.0],       [38407.0, 38408.0, 38409.0, 38410.0, 38411.0, 38412.0],       [38413.0, 38414.0, 38415.0, 38416.0, 38417.0, 38418.0],       [38419.0, 38420.0, 38421.0, 38422.0, 38423.0, 38424.0],       [38425.0, 38426.0, 38427.0, 38428.0, 38429.0, 38430.0]],      [[38431.0, 38432.0, 38433.0, 38434.0, 38435.0, 38436.0],       [38437.0, 38438.0, 38439.0, 38440.0, 38441.0, 38442.0],       [38443.0, 38444.0, 38445.0, 38446.0, 38447.0, 38448.0],       [38449.0, 38450.0, 38451.0, 38452.0, 38453.0, 38454.0],       [38455.0, 38456.0, 38457.0, 38458.0, 38459.0, 38460.0],       [38461.0, 38462.0, 38463.0, 38464.0, 38465.0, 38466.0],       [38467.0, 38468.0, 38469.0, 38470.0, 38471.0, 38472.0]],      [[38473.0, 38474.0, 38475.0, 38476.0, 38477.0, 38478.0],       [38479.0, 38480.0, 38481.0, 38482.0, 38483.0, 38484.0],       [38485.0, 38486.0, 38487.0, 38488.0, 38489.0, 38490.0],       [38491.0, 38492.0, 38493.0, 38494.0, 38495.0, 38496.0],       [38497.0, 38498.0, 38499.0, 38500.0, 38501.0, 38502.0],       [38503.0, 38504.0, 38505.0, 38506.0, 38507.0, 38508.0],       [38509.0, 38510.0, 38511.0, 38512.0, 38513.0, 38514.0]],      [[38515.0, 38516.0, 38517.0, 38518.0, 38519.0, 38520.0],       [38521.0, 38522.0, 38523.0, 38524.0, 38525.0, 38526.0],       [38527.0, 38528.0, 38529.0, 38530.0, 38531.0, 38532.0],       [38533.0, 38534.0, 38535.0, 38536.0, 38537.0, 38538.0],       [38539.0, 38540.0, 38541.0, 38542.0, 38543.0, 38544.0],       [38545.0, 38546.0, 38547.0, 38548.0, 38549.0, 38550.0],       [38551.0, 38552.0, 38553.0, 38554.0, 38555.0, 38556.0]]],     [[[38557.0, 38558.0, 38559.0, 38560.0, 38561.0, 38562.0],       [38563.0, 38564.0, 38565.0, 38566.0, 38567.0, 38568.0],       [38569.0, 38570.0, 38571.0, 38572.0, 38573.0, 38574.0],       [38575.0, 38576.0, 38577.0, 38578.0, 38579.0, 38580.0],       [38581.0, 38582.0, 38583.0, 38584.0, 38585.0, 38586.0],       [38587.0, 38588.0, 38589.0, 38590.0, 38591.0, 38592.0],       [38593.0, 38594.0, 38595.0, 38596.0, 38597.0, 38598.0]],      [[38599.0, 38600.0, 38601.0, 38602.0, 38603.0, 38604.0],       [38605.0, 38606.0, 38607.0, 38608.0, 38609.0, 38610.0],       [38611.0, 38612.0, 38613.0, 38614.0, 38615.0, 38616.0],       [38617.0, 38618.0, 38619.0, 38620.0, 38621.0, 38622.0],       [38623.0, 38624.0, 38625.0, 38626.0, 38627.0, 38628.0],       [38629.0, 38630.0, 38631.0, 38632.0, 38633.0, 38634.0],       [38635.0, 38636.0, 38637.0, 38638.0, 38639.0, 38640.0]],      [[38641.0, 38642.0, 38643.0, 38644.0, 38645.0, 38646.0],       [38647.0, 38648.0, 38649.0, 38650.0, 38651.0, 38652.0],       [38653.0, 38654.0, 38655.0, 38656.0, 38657.0, 38658.0],       [38659.0, 38660.0, 38661.0, 38662.0, 38663.0, 38664.0],       [38665.0, 38666.0, 38667.0, 38668.0, 38669.0, 38670.0],       [38671.0, 38672.0, 38673.0, 38674.0, 38675.0, 38676.0],       [38677.0, 38678.0, 38679.0, 38680.0, 38681.0, 38682.0]],      [[38683.0, 38684.0, 38685.0, 38686.0, 38687.0, 38688.0],       [38689.0, 38690.0, 38691.0, 38692.0, 38693.0, 38694.0],       [38695.0, 38696.0, 38697.0, 38698.0, 38699.0, 38700.0],       [38701.0, 38702.0, 38703.0, 38704.0, 38705.0, 38706.0],       [38707.0, 38708.0, 38709.0, 38710.0, 38711.0, 38712.0],       [38713.0, 38714.0, 38715.0, 38716.0, 38717.0, 38718.0],       [38719.0, 38720.0, 38721.0, 38722.0, 38723.0, 38724.0]],      [[38725.0, 38726.0, 38727.0, 38728.0, 38729.0, 38730.0],       [38731.0, 38732.0, 38733.0, 38734.0, 38735.0, 38736.0],       [38737.0, 38738.0, 38739.0, 38740.0, 38741.0, 38742.0],       [38743.0, 38744.0, 38745.0, 38746.0, 38747.0, 38748.0],       [38749.0, 38750.0, 38751.0, 38752.0, 38753.0, 38754.0],       [38755.0, 38756.0, 38757.0, 38758.0, 38759.0, 38760.0],       [38761.0, 38762.0, 38763.0, 38764.0, 38765.0, 38766.0]],      [[38767.0, 38768.0, 38769.0, 38770.0, 38771.0, 38772.0],       [38773.0, 38774.0, 38775.0, 38776.0, 38777.0, 38778.0],       [38779.0, 38780.0, 38781.0, 38782.0, 38783.0, 38784.0],       [38785.0, 38786.0, 38787.0, 38788.0, 38789.0, 38790.0],       [38791.0, 38792.0, 38793.0, 38794.0, 38795.0, 38796.0],       [38797.0, 38798.0, 38799.0, 38800.0, 38801.0, 38802.0],       [38803.0, 38804.0, 38805.0, 38806.0, 38807.0, 38808.0]]],     [[[38809.0, 38810.0, 38811.0, 38812.0, 38813.0, 38814.0],       [38815.0, 38816.0, 38817.0, 38818.0, 38819.0, 38820.0],       [38821.0, 38822.0, 38823.0, 38824.0, 38825.0, 38826.0],       [38827.0, 38828.0, 38829.0, 38830.0, 38831.0, 38832.0],       [38833.0, 38834.0, 38835.0, 38836.0, 38837.0, 38838.0],       [38839.0, 38840.0, 38841.0, 38842.0, 38843.0, 38844.0],       [38845.0, 38846.0, 38847.0, 38848.0, 38849.0, 38850.0]],      [[38851.0, 38852.0, 38853.0, 38854.0, 38855.0, 38856.0],       [38857.0, 38858.0, 38859.0, 38860.0, 38861.0, 38862.0],       [38863.0, 38864.0, 38865.0, 38866.0, 38867.0, 38868.0],       [38869.0, 38870.0, 38871.0, 38872.0, 38873.0, 38874.0],       [38875.0, 38876.0, 38877.0, 38878.0, 38879.0, 38880.0],       [38881.0, 38882.0, 38883.0, 38884.0, 38885.0, 38886.0],       [38887.0, 38888.0, 38889.0, 38890.0, 38891.0, 38892.0]],      [[38893.0, 38894.0, 38895.0, 38896.0, 38897.0, 38898.0],       [38899.0, 38900.0, 38901.0, 38902.0, 38903.0, 38904.0],       [38905.0, 38906.0, 38907.0, 38908.0, 38909.0, 38910.0],       [38911.0, 38912.0, 38913.0, 38914.0, 38915.0, 38916.0],       [38917.0, 38918.0, 38919.0, 38920.0, 38921.0, 38922.0],       [38923.0, 38924.0, 38925.0, 38926.0, 38927.0, 38928.0],       [38929.0, 38930.0, 38931.0, 38932.0, 38933.0, 38934.0]],      [[38935.0, 38936.0, 38937.0, 38938.0, 38939.0, 38940.0],       [38941.0, 38942.0, 38943.0, 38944.0, 38945.0, 38946.0],       [38947.0, 38948.0, 38949.0, 38950.0, 38951.0, 38952.0],       [38953.0, 38954.0, 38955.0, 38956.0, 38957.0, 38958.0],       [38959.0, 38960.0, 38961.0, 38962.0, 38963.0, 38964.0],       [38965.0, 38966.0, 38967.0, 38968.0, 38969.0, 38970.0],       [38971.0, 38972.0, 38973.0, 38974.0, 38975.0, 38976.0]],      [[38977.0, 38978.0, 38979.0, 38980.0, 38981.0, 38982.0],       [38983.0, 38984.0, 38985.0, 38986.0, 38987.0, 38988.0],       [38989.0, 38990.0, 38991.0, 38992.0, 38993.0, 38994.0],       [38995.0, 38996.0, 38997.0, 38998.0, 38999.0, 39000.0],       [39001.0, 39002.0, 39003.0, 39004.0, 39005.0, 39006.0],       [39007.0, 39008.0, 39009.0, 39010.0, 39011.0, 39012.0],       [39013.0, 39014.0, 39015.0, 39016.0, 39017.0, 39018.0]],      [[39019.0, 39020.0, 39021.0, 39022.0, 39023.0, 39024.0],       [39025.0, 39026.0, 39027.0, 39028.0, 39029.0, 39030.0],       [39031.0, 39032.0, 39033.0, 39034.0, 39035.0, 39036.0],       [39037.0, 39038.0, 39039.0, 39040.0, 39041.0, 39042.0],       [39043.0, 39044.0, 39045.0, 39046.0, 39047.0, 39048.0],       [39049.0, 39050.0, 39051.0, 39052.0, 39053.0, 39054.0],       [39055.0, 39056.0, 39057.0, 39058.0, 39059.0, 39060.0]]],     [[[39061.0, 39062.0, 39063.0, 39064.0, 39065.0, 39066.0],       [39067.0, 39068.0, 39069.0, 39070.0, 39071.0, 39072.0],       [39073.0, 39074.0, 39075.0, 39076.0, 39077.0, 39078.0],       [39079.0, 39080.0, 39081.0, 39082.0, 39083.0, 39084.0],       [39085.0, 39086.0, 39087.0, 39088.0, 39089.0, 39090.0],       [39091.0, 39092.0, 39093.0, 39094.0, 39095.0, 39096.0],       [39097.0, 39098.0, 39099.0, 39100.0, 39101.0, 39102.0]],      [[39103.0, 39104.0, 39105.0, 39106.0, 39107.0, 39108.0],       [39109.0, 39110.0, 39111.0, 39112.0, 39113.0, 39114.0],       [39115.0, 39116.0, 39117.0, 39118.0, 39119.0, 39120.0],       [39121.0, 39122.0, 39123.0, 39124.0, 39125.0, 39126.0],       [39127.0, 39128.0, 39129.0, 39130.0, 39131.0, 39132.0],       [39133.0, 39134.0, 39135.0, 39136.0, 39137.0, 39138.0],       [39139.0, 39140.0, 39141.0, 39142.0, 39143.0, 39144.0]],      [[39145.0, 39146.0, 39147.0, 39148.0, 39149.0, 39150.0],       [39151.0, 39152.0, 39153.0, 39154.0, 39155.0, 39156.0],       [39157.0, 39158.0, 39159.0, 39160.0, 39161.0, 39162.0],       [39163.0, 39164.0, 39165.0, 39166.0, 39167.0, 39168.0],       [39169.0, 39170.0, 39171.0, 39172.0, 39173.0, 39174.0],       [39175.0, 39176.0, 39177.0, 39178.0, 39179.0, 39180.0],       [39181.0, 39182.0, 39183.0, 39184.0, 39185.0, 39186.0]],      [[39187.0, 39188.0, 39189.0, 39190.0, 39191.0, 39192.0],       [39193.0, 39194.0, 39195.0, 39196.0, 39197.0, 39198.0],       [39199.0, 39200.0, 39201.0, 39202.0, 39203.0, 39204.0],       [39205.0, 39206.0, 39207.0, 39208.0, 39209.0, 39210.0],       [39211.0, 39212.0, 39213.0, 39214.0, 39215.0, 39216.0],       [39217.0, 39218.0, 39219.0, 39220.0, 39221.0, 39222.0],       [39223.0, 39224.0, 39225.0, 39226.0, 39227.0, 39228.0]],      [[39229.0, 39230.0, 39231.0, 39232.0, 39233.0, 39234.0],       [39235.0, 39236.0, 39237.0, 39238.0, 39239.0, 39240.0],       [39241.0, 39242.0, 39243.0, 39244.0, 39245.0, 39246.0],       [39247.0, 39248.0, 39249.0, 39250.0, 39251.0, 39252.0],       [39253.0, 39254.0, 39255.0, 39256.0, 39257.0, 39258.0],       [39259.0, 39260.0, 39261.0, 39262.0, 39263.0, 39264.0],       [39265.0, 39266.0, 39267.0, 39268.0, 39269.0, 39270.0]],      [[39271.0, 39272.0, 39273.0, 39274.0, 39275.0, 39276.0],       [39277.0, 39278.0, 39279.0, 39280.0, 39281.0, 39282.0],       [39283.0, 39284.0, 39285.0, 39286.0, 39287.0, 39288.0],       [39289.0, 39290.0, 39291.0, 39292.0, 39293.0, 39294.0],       [39295.0, 39296.0, 39297.0, 39298.0, 39299.0, 39300.0],       [39301.0, 39302.0, 39303.0, 39304.0, 39305.0, 39306.0],       [39307.0, 39308.0, 39309.0, 39310.0, 39311.0, 39312.0]]]],    [[[[39313.0, 39314.0, 39315.0, 39316.0, 39317.0, 39318.0],       [39319.0, 39320.0, 39321.0, 39322.0, 39323.0, 39324.0],       [39325.0, 39326.0, 39327.0, 39328.0, 39329.0, 39330.0],       [39331.0, 39332.0, 39333.0, 39334.0, 39335.0, 39336.0],       [39337.0, 39338.0, 39339.0, 39340.0, 39341.0, 39342.0],       [39343.0, 39344.0, 39345.0, 39346.0, 39347.0, 39348.0],       [39349.0, 39350.0, 39351.0, 39352.0, 39353.0, 39354.0]],      [[39355.0, 39356.0, 39357.0, 39358.0, 39359.0, 39360.0],       [39361.0, 39362.0, 39363.0, 39364.0, 39365.0, 39366.0],       [39367.0, 39368.0, 39369.0, 39370.0, 39371.0, 39372.0],       [39373.0, 39374.0, 39375.0, 39376.0, 39377.0, 39378.0],       [39379.0, 39380.0, 39381.0, 39382.0, 39383.0, 39384.0],       [39385.0, 39386.0, 39387.0, 39388.0, 39389.0, 39390.0],       [39391.0, 39392.0, 39393.0, 39394.0, 39395.0, 39396.0]],      [[39397.0, 39398.0, 39399.0, 39400.0, 39401.0, 39402.0],       [39403.0, 39404.0, 39405.0, 39406.0, 39407.0, 39408.0],       [39409.0, 39410.0, 39411.0, 39412.0, 39413.0, 39414.0],       [39415.0, 39416.0, 39417.0, 39418.0, 39419.0, 39420.0],       [39421.0, 39422.0, 39423.0, 39424.0, 39425.0, 39426.0],       [39427.0, 39428.0, 39429.0, 39430.0, 39431.0, 39432.0],       [39433.0, 39434.0, 39435.0, 39436.0, 39437.0, 39438.0]],      [[39439.0, 39440.0, 39441.0, 39442.0, 39443.0, 39444.0],       [39445.0, 39446.0, 39447.0, 39448.0, 39449.0, 39450.0],       [39451.0, 39452.0, 39453.0, 39454.0, 39455.0, 39456.0],       [39457.0, 39458.0, 39459.0, 39460.0, 39461.0, 39462.0],       [39463.0, 39464.0, 39465.0, 39466.0, 39467.0, 39468.0],       [39469.0, 39470.0, 39471.0, 39472.0, 39473.0, 39474.0],       [39475.0, 39476.0, 39477.0, 39478.0, 39479.0, 39480.0]],      [[39481.0, 39482.0, 39483.0, 39484.0, 39485.0, 39486.0],       [39487.0, 39488.0, 39489.0, 39490.0, 39491.0, 39492.0],       [39493.0, 39494.0, 39495.0, 39496.0, 39497.0, 39498.0],       [39499.0, 39500.0, 39501.0, 39502.0, 39503.0, 39504.0],       [39505.0, 39506.0, 39507.0, 39508.0, 39509.0, 39510.0],       [39511.0, 39512.0, 39513.0, 39514.0, 39515.0, 39516.0],       [39517.0, 39518.0, 39519.0, 39520.0, 39521.0, 39522.0]],      [[39523.0, 39524.0, 39525.0, 39526.0, 39527.0, 39528.0],       [39529.0, 39530.0, 39531.0, 39532.0, 39533.0, 39534.0],       [39535.0, 39536.0, 39537.0, 39538.0, 39539.0, 39540.0],       [39541.0, 39542.0, 39543.0, 39544.0, 39545.0, 39546.0],       [39547.0, 39548.0, 39549.0, 39550.0, 39551.0, 39552.0],       [39553.0, 39554.0, 39555.0, 39556.0, 39557.0, 39558.0],       [39559.0, 39560.0, 39561.0, 39562.0, 39563.0, 39564.0]]],     [[[39565.0, 39566.0, 39567.0, 39568.0, 39569.0, 39570.0],       [39571.0, 39572.0, 39573.0, 39574.0, 39575.0, 39576.0],       [39577.0, 39578.0, 39579.0, 39580.0, 39581.0, 39582.0],       [39583.0, 39584.0, 39585.0, 39586.0, 39587.0, 39588.0],       [39589.0, 39590.0, 39591.0, 39592.0, 39593.0, 39594.0],       [39595.0, 39596.0, 39597.0, 39598.0, 39599.0, 39600.0],       [39601.0, 39602.0, 39603.0, 39604.0, 39605.0, 39606.0]],      [[39607.0, 39608.0, 39609.0, 39610.0, 39611.0, 39612.0],       [39613.0, 39614.0, 39615.0, 39616.0, 39617.0, 39618.0],       [39619.0, 39620.0, 39621.0, 39622.0, 39623.0, 39624.0],       [39625.0, 39626.0, 39627.0, 39628.0, 39629.0, 39630.0],       [39631.0, 39632.0, 39633.0, 39634.0, 39635.0, 39636.0],       [39637.0, 39638.0, 39639.0, 39640.0, 39641.0, 39642.0],       [39643.0, 39644.0, 39645.0, 39646.0, 39647.0, 39648.0]],      [[39649.0, 39650.0, 39651.0, 39652.0, 39653.0, 39654.0],       [39655.0, 39656.0, 39657.0, 39658.0, 39659.0, 39660.0],       [39661.0, 39662.0, 39663.0, 39664.0, 39665.0, 39666.0],       [39667.0, 39668.0, 39669.0, 39670.0, 39671.0, 39672.0],       [39673.0, 39674.0, 39675.0, 39676.0, 39677.0, 39678.0],       [39679.0, 39680.0, 39681.0, 39682.0, 39683.0, 39684.0],       [39685.0, 39686.0, 39687.0, 39688.0, 39689.0, 39690.0]],      [[39691.0, 39692.0, 39693.0, 39694.0, 39695.0, 39696.0],       [39697.0, 39698.0, 39699.0, 39700.0, 39701.0, 39702.0],       [39703.0, 39704.0, 39705.0, 39706.0, 39707.0, 39708.0],       [39709.0, 39710.0, 39711.0, 39712.0, 39713.0, 39714.0],       [39715.0, 39716.0, 39717.0, 39718.0, 39719.0, 39720.0],       [39721.0, 39722.0, 39723.0, 39724.0, 39725.0, 39726.0],       [39727.0, 39728.0, 39729.0, 39730.0, 39731.0, 39732.0]],      [[39733.0, 39734.0, 39735.0, 39736.0, 39737.0, 39738.0],       [39739.0, 39740.0, 39741.0, 39742.0, 39743.0, 39744.0],       [39745.0, 39746.0, 39747.0, 39748.0, 39749.0, 39750.0],       [39751.0, 39752.0, 39753.0, 39754.0, 39755.0, 39756.0],       [39757.0, 39758.0, 39759.0, 39760.0, 39761.0, 39762.0],       [39763.0, 39764.0, 39765.0, 39766.0, 39767.0, 39768.0],       [39769.0, 39770.0, 39771.0, 39772.0, 39773.0, 39774.0]],      [[39775.0, 39776.0, 39777.0, 39778.0, 39779.0, 39780.0],       [39781.0, 39782.0, 39783.0, 39784.0, 39785.0, 39786.0],       [39787.0, 39788.0, 39789.0, 39790.0, 39791.0, 39792.0],       [39793.0, 39794.0, 39795.0, 39796.0, 39797.0, 39798.0],       [39799.0, 39800.0, 39801.0, 39802.0, 39803.0, 39804.0],       [39805.0, 39806.0, 39807.0, 39808.0, 39809.0, 39810.0],       [39811.0, 39812.0, 39813.0, 39814.0, 39815.0, 39816.0]]],     [[[39817.0, 39818.0, 39819.0, 39820.0, 39821.0, 39822.0],       [39823.0, 39824.0, 39825.0, 39826.0, 39827.0, 39828.0],       [39829.0, 39830.0, 39831.0, 39832.0, 39833.0, 39834.0],       [39835.0, 39836.0, 39837.0, 39838.0, 39839.0, 39840.0],       [39841.0, 39842.0, 39843.0, 39844.0, 39845.0, 39846.0],       [39847.0, 39848.0, 39849.0, 39850.0, 39851.0, 39852.0],       [39853.0, 39854.0, 39855.0, 39856.0, 39857.0, 39858.0]],      [[39859.0, 39860.0, 39861.0, 39862.0, 39863.0, 39864.0],       [39865.0, 39866.0, 39867.0, 39868.0, 39869.0, 39870.0],       [39871.0, 39872.0, 39873.0, 39874.0, 39875.0, 39876.0],       [39877.0, 39878.0, 39879.0, 39880.0, 39881.0, 39882.0],       [39883.0, 39884.0, 39885.0, 39886.0, 39887.0, 39888.0],       [39889.0, 39890.0, 39891.0, 39892.0, 39893.0, 39894.0],       [39895.0, 39896.0, 39897.0, 39898.0, 39899.0, 39900.0]],      [[39901.0, 39902.0, 39903.0, 39904.0, 39905.0, 39906.0],       [39907.0, 39908.0, 39909.0, 39910.0, 39911.0, 39912.0],       [39913.0, 39914.0, 39915.0, 39916.0, 39917.0, 39918.0],       [39919.0, 39920.0, 39921.0, 39922.0, 39923.0, 39924.0],       [39925.0, 39926.0, 39927.0, 39928.0, 39929.0, 39930.0],       [39931.0, 39932.0, 39933.0, 39934.0, 39935.0, 39936.0],       [39937.0, 39938.0, 39939.0, 39940.0, 39941.0, 39942.0]],      [[39943.0, 39944.0, 39945.0, 39946.0, 39947.0, 39948.0],       [39949.0, 39950.0, 39951.0, 39952.0, 39953.0, 39954.0],       [39955.0, 39956.0, 39957.0, 39958.0, 39959.0, 39960.0],       [39961.0, 39962.0, 39963.0, 39964.0, 39965.0, 39966.0],       [39967.0, 39968.0, 39969.0, 39970.0, 39971.0, 39972.0],       [39973.0, 39974.0, 39975.0, 39976.0, 39977.0, 39978.0],       [39979.0, 39980.0, 39981.0, 39982.0, 39983.0, 39984.0]],      [[39985.0, 39986.0, 39987.0, 39988.0, 39989.0, 39990.0],       [39991.0, 39992.0, 39993.0, 39994.0, 39995.0, 39996.0],       [39997.0, 39998.0, 39999.0, 40000.0, 40001.0, 40002.0],       [40003.0, 40004.0, 40005.0, 40006.0, 40007.0, 40008.0],       [40009.0, 40010.0, 40011.0, 40012.0, 40013.0, 40014.0],       [40015.0, 40016.0, 40017.0, 40018.0, 40019.0, 40020.0],       [40021.0, 40022.0, 40023.0, 40024.0, 40025.0, 40026.0]],      [[40027.0, 40028.0, 40029.0, 40030.0, 40031.0, 40032.0],       [40033.0, 40034.0, 40035.0, 40036.0, 40037.0, 40038.0],       [40039.0, 40040.0, 40041.0, 40042.0, 40043.0, 40044.0],       [40045.0, 40046.0, 40047.0, 40048.0, 40049.0, 40050.0],       [40051.0, 40052.0, 40053.0, 40054.0, 40055.0, 40056.0],       [40057.0, 40058.0, 40059.0, 40060.0, 40061.0, 40062.0],       [40063.0, 40064.0, 40065.0, 40066.0, 40067.0, 40068.0]]],     [[[40069.0, 40070.0, 40071.0, 40072.0, 40073.0, 40074.0],       [40075.0, 40076.0, 40077.0, 40078.0, 40079.0, 40080.0],       [40081.0, 40082.0, 40083.0, 40084.0, 40085.0, 40086.0],       [40087.0, 40088.0, 40089.0, 40090.0, 40091.0, 40092.0],       [40093.0, 40094.0, 40095.0, 40096.0, 40097.0, 40098.0],       [40099.0, 40100.0, 40101.0, 40102.0, 40103.0, 40104.0],       [40105.0, 40106.0, 40107.0, 40108.0, 40109.0, 40110.0]],      [[40111.0, 40112.0, 40113.0, 40114.0, 40115.0, 40116.0],       [40117.0, 40118.0, 40119.0, 40120.0, 40121.0, 40122.0],       [40123.0, 40124.0, 40125.0, 40126.0, 40127.0, 40128.0],       [40129.0, 40130.0, 40131.0, 40132.0, 40133.0, 40134.0],       [40135.0, 40136.0, 40137.0, 40138.0, 40139.0, 40140.0],       [40141.0, 40142.0, 40143.0, 40144.0, 40145.0, 40146.0],       [40147.0, 40148.0, 40149.0, 40150.0, 40151.0, 40152.0]],      [[40153.0, 40154.0, 40155.0, 40156.0, 40157.0, 40158.0],       [40159.0, 40160.0, 40161.0, 40162.0, 40163.0, 40164.0],       [40165.0, 40166.0, 40167.0, 40168.0, 40169.0, 40170.0],       [40171.0, 40172.0, 40173.0, 40174.0, 40175.0, 40176.0],       [40177.0, 40178.0, 40179.0, 40180.0, 40181.0, 40182.0],       [40183.0, 40184.0, 40185.0, 40186.0, 40187.0, 40188.0],       [40189.0, 40190.0, 40191.0, 40192.0, 40193.0, 40194.0]],      [[40195.0, 40196.0, 40197.0, 40198.0, 40199.0, 40200.0],       [40201.0, 40202.0, 40203.0, 40204.0, 40205.0, 40206.0],       [40207.0, 40208.0, 40209.0, 40210.0, 40211.0, 40212.0],       [40213.0, 40214.0, 40215.0, 40216.0, 40217.0, 40218.0],       [40219.0, 40220.0, 40221.0, 40222.0, 40223.0, 40224.0],       [40225.0, 40226.0, 40227.0, 40228.0, 40229.0, 40230.0],       [40231.0, 40232.0, 40233.0, 40234.0, 40235.0, 40236.0]],      [[40237.0, 40238.0, 40239.0, 40240.0, 40241.0, 40242.0],       [40243.0, 40244.0, 40245.0, 40246.0, 40247.0, 40248.0],       [40249.0, 40250.0, 40251.0, 40252.0, 40253.0, 40254.0],       [40255.0, 40256.0, 40257.0, 40258.0, 40259.0, 40260.0],       [40261.0, 40262.0, 40263.0, 40264.0, 40265.0, 40266.0],       [40267.0, 40268.0, 40269.0, 40270.0, 40271.0, 40272.0],       [40273.0, 40274.0, 40275.0, 40276.0, 40277.0, 40278.0]],      [[40279.0, 40280.0, 40281.0, 40282.0, 40283.0, 40284.0],       [40285.0, 40286.0, 40287.0, 40288.0, 40289.0, 40290.0],       [40291.0, 40292.0, 40293.0, 40294.0, 40295.0, 40296.0],       [40297.0, 40298.0, 40299.0, 40300.0, 40301.0, 40302.0],       [40303.0, 40304.0, 40305.0, 40306.0, 40307.0, 40308.0],       [40309.0, 40310.0, 40311.0, 40312.0, 40313.0, 40314.0],       [40315.0, 40316.0, 40317.0, 40318.0, 40319.0, 40320.0]]]],    [[[[40321.0, 40322.0, 40323.0, 40324.0, 40325.0, 40326.0],       [40327.0, 40328.0, 40329.0, 40330.0, 40331.0, 40332.0],       [40333.0, 40334.0, 40335.0, 40336.0, 40337.0, 40338.0],       [40339.0, 40340.0, 40341.0, 40342.0, 40343.0, 40344.0],       [40345.0, 40346.0, 40347.0, 40348.0, 40349.0, 40350.0],       [40351.0, 40352.0, 40353.0, 40354.0, 40355.0, 40356.0],       [40357.0, 40358.0, 40359.0, 40360.0, 40361.0, 40362.0]],      [[40363.0, 40364.0, 40365.0, 40366.0, 40367.0, 40368.0],       [40369.0, 40370.0, 40371.0, 40372.0, 40373.0, 40374.0],       [40375.0, 40376.0, 40377.0, 40378.0, 40379.0, 40380.0],       [40381.0, 40382.0, 40383.0, 40384.0, 40385.0, 40386.0],       [40387.0, 40388.0, 40389.0, 40390.0, 40391.0, 40392.0],       [40393.0, 40394.0, 40395.0, 40396.0, 40397.0, 40398.0],       [40399.0, 40400.0, 40401.0, 40402.0, 40403.0, 40404.0]],      [[40405.0, 40406.0, 40407.0, 40408.0, 40409.0, 40410.0],       [40411.0, 40412.0, 40413.0, 40414.0, 40415.0, 40416.0],       [40417.0, 40418.0, 40419.0, 40420.0, 40421.0, 40422.0],       [40423.0, 40424.0, 40425.0, 40426.0, 40427.0, 40428.0],       [40429.0, 40430.0, 40431.0, 40432.0, 40433.0, 40434.0],       [40435.0, 40436.0, 40437.0, 40438.0, 40439.0, 40440.0],       [40441.0, 40442.0, 40443.0, 40444.0, 40445.0, 40446.0]],      [[40447.0, 40448.0, 40449.0, 40450.0, 40451.0, 40452.0],       [40453.0, 40454.0, 40455.0, 40456.0, 40457.0, 40458.0],       [40459.0, 40460.0, 40461.0, 40462.0, 40463.0, 40464.0],       [40465.0, 40466.0, 40467.0, 40468.0, 40469.0, 40470.0],       [40471.0, 40472.0, 40473.0, 40474.0, 40475.0, 40476.0],       [40477.0, 40478.0, 40479.0, 40480.0, 40481.0, 40482.0],       [40483.0, 40484.0, 40485.0, 40486.0, 40487.0, 40488.0]],      [[40489.0, 40490.0, 40491.0, 40492.0, 40493.0, 40494.0],       [40495.0, 40496.0, 40497.0, 40498.0, 40499.0, 40500.0],       [40501.0, 40502.0, 40503.0, 40504.0, 40505.0, 40506.0],       [40507.0, 40508.0, 40509.0, 40510.0, 40511.0, 40512.0],       [40513.0, 40514.0, 40515.0, 40516.0, 40517.0, 40518.0],       [40519.0, 40520.0, 40521.0, 40522.0, 40523.0, 40524.0],       [40525.0, 40526.0, 40527.0, 40528.0, 40529.0, 40530.0]],      [[40531.0, 40532.0, 40533.0, 40534.0, 40535.0, 40536.0],       [40537.0, 40538.0, 40539.0, 40540.0, 40541.0, 40542.0],       [40543.0, 40544.0, 40545.0, 40546.0, 40547.0, 40548.0],       [40549.0, 40550.0, 40551.0, 40552.0, 40553.0, 40554.0],       [40555.0, 40556.0, 40557.0, 40558.0, 40559.0, 40560.0],       [40561.0, 40562.0, 40563.0, 40564.0, 40565.0, 40566.0],       [40567.0, 40568.0, 40569.0, 40570.0, 40571.0, 40572.0]]],     [[[40573.0, 40574.0, 40575.0, 40576.0, 40577.0, 40578.0],       [40579.0, 40580.0, 40581.0, 40582.0, 40583.0, 40584.0],       [40585.0, 40586.0, 40587.0, 40588.0, 40589.0, 40590.0],       [40591.0, 40592.0, 40593.0, 40594.0, 40595.0, 40596.0],       [40597.0, 40598.0, 40599.0, 40600.0, 40601.0, 40602.0],       [40603.0, 40604.0, 40605.0, 40606.0, 40607.0, 40608.0],       [40609.0, 40610.0, 40611.0, 40612.0, 40613.0, 40614.0]],      [[40615.0, 40616.0, 40617.0, 40618.0, 40619.0, 40620.0],       [40621.0, 40622.0, 40623.0, 40624.0, 40625.0, 40626.0],       [40627.0, 40628.0, 40629.0, 40630.0, 40631.0, 40632.0],       [40633.0, 40634.0, 40635.0, 40636.0, 40637.0, 40638.0],       [40639.0, 40640.0, 40641.0, 40642.0, 40643.0, 40644.0],       [40645.0, 40646.0, 40647.0, 40648.0, 40649.0, 40650.0],       [40651.0, 40652.0, 40653.0, 40654.0, 40655.0, 40656.0]],      [[40657.0, 40658.0, 40659.0, 40660.0, 40661.0, 40662.0],       [40663.0, 40664.0, 40665.0, 40666.0, 40667.0, 40668.0],       [40669.0, 40670.0, 40671.0, 40672.0, 40673.0, 40674.0],       [40675.0, 40676.0, 40677.0, 40678.0, 40679.0, 40680.0],       [40681.0, 40682.0, 40683.0, 40684.0, 40685.0, 40686.0],       [40687.0, 40688.0, 40689.0, 40690.0, 40691.0, 40692.0],       [40693.0, 40694.0, 40695.0, 40696.0, 40697.0, 40698.0]],      [[40699.0, 40700.0, 40701.0, 40702.0, 40703.0, 40704.0],       [40705.0, 40706.0, 40707.0, 40708.0, 40709.0, 40710.0],       [40711.0, 40712.0, 40713.0, 40714.0, 40715.0, 40716.0],       [40717.0, 40718.0, 40719.0, 40720.0, 40721.0, 40722.0],       [40723.0, 40724.0, 40725.0, 40726.0, 40727.0, 40728.0],       [40729.0, 40730.0, 40731.0, 40732.0, 40733.0, 40734.0],       [40735.0, 40736.0, 40737.0, 40738.0, 40739.0, 40740.0]],      [[40741.0, 40742.0, 40743.0, 40744.0, 40745.0, 40746.0],       [40747.0, 40748.0, 40749.0, 40750.0, 40751.0, 40752.0],       [40753.0, 40754.0, 40755.0, 40756.0, 40757.0, 40758.0],       [40759.0, 40760.0, 40761.0, 40762.0, 40763.0, 40764.0],       [40765.0, 40766.0, 40767.0, 40768.0, 40769.0, 40770.0],       [40771.0, 40772.0, 40773.0, 40774.0, 40775.0, 40776.0],       [40777.0, 40778.0, 40779.0, 40780.0, 40781.0, 40782.0]],      [[40783.0, 40784.0, 40785.0, 40786.0, 40787.0, 40788.0],       [40789.0, 40790.0, 40791.0, 40792.0, 40793.0, 40794.0],       [40795.0, 40796.0, 40797.0, 40798.0, 40799.0, 40800.0],       [40801.0, 40802.0, 40803.0, 40804.0, 40805.0, 40806.0],       [40807.0, 40808.0, 40809.0, 40810.0, 40811.0, 40812.0],       [40813.0, 40814.0, 40815.0, 40816.0, 40817.0, 40818.0],       [40819.0, 40820.0, 40821.0, 40822.0, 40823.0, 40824.0]]],     [[[40825.0, 40826.0, 40827.0, 40828.0, 40829.0, 40830.0],       [40831.0, 40832.0, 40833.0, 40834.0, 40835.0, 40836.0],       [40837.0, 40838.0, 40839.0, 40840.0, 40841.0, 40842.0],       [40843.0, 40844.0, 40845.0, 40846.0, 40847.0, 40848.0],       [40849.0, 40850.0, 40851.0, 40852.0, 40853.0, 40854.0],       [40855.0, 40856.0, 40857.0, 40858.0, 40859.0, 40860.0],       [40861.0, 40862.0, 40863.0, 40864.0, 40865.0, 40866.0]],      [[40867.0, 40868.0, 40869.0, 40870.0, 40871.0, 40872.0],       [40873.0, 40874.0, 40875.0, 40876.0, 40877.0, 40878.0],       [40879.0, 40880.0, 40881.0, 40882.0, 40883.0, 40884.0],       [40885.0, 40886.0, 40887.0, 40888.0, 40889.0, 40890.0],       [40891.0, 40892.0, 40893.0, 40894.0, 40895.0, 40896.0],       [40897.0, 40898.0, 40899.0, 40900.0, 40901.0, 40902.0],       [40903.0, 40904.0, 40905.0, 40906.0, 40907.0, 40908.0]],      [[40909.0, 40910.0, 40911.0, 40912.0, 40913.0, 40914.0],       [40915.0, 40916.0, 40917.0, 40918.0, 40919.0, 40920.0],       [40921.0, 40922.0, 40923.0, 40924.0, 40925.0, 40926.0],       [40927.0, 40928.0, 40929.0, 40930.0, 40931.0, 40932.0],       [40933.0, 40934.0, 40935.0, 40936.0, 40937.0, 40938.0],       [40939.0, 40940.0, 40941.0, 40942.0, 40943.0, 40944.0],       [40945.0, 40946.0, 40947.0, 40948.0, 40949.0, 40950.0]],      [[40951.0, 40952.0, 40953.0, 40954.0, 40955.0, 40956.0],       [40957.0, 40958.0, 40959.0, 40960.0, 40961.0, 40962.0],       [40963.0, 40964.0, 40965.0, 40966.0, 40967.0, 40968.0],       [40969.0, 40970.0, 40971.0, 40972.0, 40973.0, 40974.0],       [40975.0, 40976.0, 40977.0, 40978.0, 40979.0, 40980.0],       [40981.0, 40982.0, 40983.0, 40984.0, 40985.0, 40986.0],       [40987.0, 40988.0, 40989.0, 40990.0, 40991.0, 40992.0]],      [[40993.0, 40994.0, 40995.0, 40996.0, 40997.0, 40998.0],       [40999.0, 41000.0, 41001.0, 41002.0, 41003.0, 41004.0],       [41005.0, 41006.0, 41007.0, 41008.0, 41009.0, 41010.0],       [41011.0, 41012.0, 41013.0, 41014.0, 41015.0, 41016.0],       [41017.0, 41018.0, 41019.0, 41020.0, 41021.0, 41022.0],       [41023.0, 41024.0, 41025.0, 41026.0, 41027.0, 41028.0],       [41029.0, 41030.0, 41031.0, 41032.0, 41033.0, 41034.0]],      [[41035.0, 41036.0, 41037.0, 41038.0, 41039.0, 41040.0],       [41041.0, 41042.0, 41043.0, 41044.0, 41045.0, 41046.0],       [41047.0, 41048.0, 41049.0, 41050.0, 41051.0, 41052.0],       [41053.0, 41054.0, 41055.0, 41056.0, 41057.0, 41058.0],       [41059.0, 41060.0, 41061.0, 41062.0, 41063.0, 41064.0],       [41065.0, 41066.0, 41067.0, 41068.0, 41069.0, 41070.0],       [41071.0, 41072.0, 41073.0, 41074.0, 41075.0, 41076.0]]],     [[[41077.0, 41078.0, 41079.0, 41080.0, 41081.0, 41082.0],       [41083.0, 41084.0, 41085.0, 41086.0, 41087.0, 41088.0],       [41089.0, 41090.0, 41091.0, 41092.0, 41093.0, 41094.0],       [41095.0, 41096.0, 41097.0, 41098.0, 41099.0, 41100.0],       [41101.0, 41102.0, 41103.0, 41104.0, 41105.0, 41106.0],       [41107.0, 41108.0, 41109.0, 41110.0, 41111.0, 41112.0],       [41113.0, 41114.0, 41115.0, 41116.0, 41117.0, 41118.0]],      [[41119.0, 41120.0, 41121.0, 41122.0, 41123.0, 41124.0],       [41125.0, 41126.0, 41127.0, 41128.0, 41129.0, 41130.0],       [41131.0, 41132.0, 41133.0, 41134.0, 41135.0, 41136.0],       [41137.0, 41138.0, 41139.0, 41140.0, 41141.0, 41142.0],       [41143.0, 41144.0, 41145.0, 41146.0, 41147.0, 41148.0],       [41149.0, 41150.0, 41151.0, 41152.0, 41153.0, 41154.0],       [41155.0, 41156.0, 41157.0, 41158.0, 41159.0, 41160.0]],      [[41161.0, 41162.0, 41163.0, 41164.0, 41165.0, 41166.0],       [41167.0, 41168.0, 41169.0, 41170.0, 41171.0, 41172.0],       [41173.0, 41174.0, 41175.0, 41176.0, 41177.0, 41178.0],       [41179.0, 41180.0, 41181.0, 41182.0, 41183.0, 41184.0],       [41185.0, 41186.0, 41187.0, 41188.0, 41189.0, 41190.0],       [41191.0, 41192.0, 41193.0, 41194.0, 41195.0, 41196.0],       [41197.0, 41198.0, 41199.0, 41200.0, 41201.0, 41202.0]],      [[41203.0, 41204.0, 41205.0, 41206.0, 41207.0, 41208.0],       [41209.0, 41210.0, 41211.0, 41212.0, 41213.0, 41214.0],       [41215.0, 41216.0, 41217.0, 41218.0, 41219.0, 41220.0],       [41221.0, 41222.0, 41223.0, 41224.0, 41225.0, 41226.0],       [41227.0, 41228.0, 41229.0, 41230.0, 41231.0, 41232.0],       [41233.0, 41234.0, 41235.0, 41236.0, 41237.0, 41238.0],       [41239.0, 41240.0, 41241.0, 41242.0, 41243.0, 41244.0]],      [[41245.0, 41246.0, 41247.0, 41248.0, 41249.0, 41250.0],       [41251.0, 41252.0, 41253.0, 41254.0, 41255.0, 41256.0],       [41257.0, 41258.0, 41259.0, 41260.0, 41261.0, 41262.0],       [41263.0, 41264.0, 41265.0, 41266.0, 41267.0, 41268.0],       [41269.0, 41270.0, 41271.0, 41272.0, 41273.0, 41274.0],       [41275.0, 41276.0, 41277.0, 41278.0, 41279.0, 41280.0],       [41281.0, 41282.0, 41283.0, 41284.0, 41285.0, 41286.0]],      [[41287.0, 41288.0, 41289.0, 41290.0, 41291.0, 41292.0],       [41293.0, 41294.0, 41295.0, 41296.0, 41297.0, 41298.0],       [41299.0, 41300.0, 41301.0, 41302.0, 41303.0, 41304.0],       [41305.0, 41306.0, 41307.0, 41308.0, 41309.0, 41310.0],       [41311.0, 41312.0, 41313.0, 41314.0, 41315.0, 41316.0],       [41317.0, 41318.0, 41319.0, 41320.0, 41321.0, 41322.0],       [41323.0, 41324.0, 41325.0, 41326.0, 41327.0, 41328.0]]]],    [[[[41329.0, 41330.0, 41331.0, 41332.0, 41333.0, 41334.0],       [41335.0, 41336.0, 41337.0, 41338.0, 41339.0, 41340.0],       [41341.0, 41342.0, 41343.0, 41344.0, 41345.0, 41346.0],       [41347.0, 41348.0, 41349.0, 41350.0, 41351.0, 41352.0],       [41353.0, 41354.0, 41355.0, 41356.0, 41357.0, 41358.0],       [41359.0, 41360.0, 41361.0, 41362.0, 41363.0, 41364.0],       [41365.0, 41366.0, 41367.0, 41368.0, 41369.0, 41370.0]],      [[41371.0, 41372.0, 41373.0, 41374.0, 41375.0, 41376.0],       [41377.0, 41378.0, 41379.0, 41380.0, 41381.0, 41382.0],       [41383.0, 41384.0, 41385.0, 41386.0, 41387.0, 41388.0],       [41389.0, 41390.0, 41391.0, 41392.0, 41393.0, 41394.0],       [41395.0, 41396.0, 41397.0, 41398.0, 41399.0, 41400.0],       [41401.0, 41402.0, 41403.0, 41404.0, 41405.0, 41406.0],       [41407.0, 41408.0, 41409.0, 41410.0, 41411.0, 41412.0]],      [[41413.0, 41414.0, 41415.0, 41416.0, 41417.0, 41418.0],       [41419.0, 41420.0, 41421.0, 41422.0, 41423.0, 41424.0],       [41425.0, 41426.0, 41427.0, 41428.0, 41429.0, 41430.0],       [41431.0, 41432.0, 41433.0, 41434.0, 41435.0, 41436.0],       [41437.0, 41438.0, 41439.0, 41440.0, 41441.0, 41442.0],       [41443.0, 41444.0, 41445.0, 41446.0, 41447.0, 41448.0],       [41449.0, 41450.0, 41451.0, 41452.0, 41453.0, 41454.0]],      [[41455.0, 41456.0, 41457.0, 41458.0, 41459.0, 41460.0],       [41461.0, 41462.0, 41463.0, 41464.0, 41465.0, 41466.0],       [41467.0, 41468.0, 41469.0, 41470.0, 41471.0, 41472.0],       [41473.0, 41474.0, 41475.0, 41476.0, 41477.0, 41478.0],       [41479.0, 41480.0, 41481.0, 41482.0, 41483.0, 41484.0],       [41485.0, 41486.0, 41487.0, 41488.0, 41489.0, 41490.0],       [41491.0, 41492.0, 41493.0, 41494.0, 41495.0, 41496.0]],      [[41497.0, 41498.0, 41499.0, 41500.0, 41501.0, 41502.0],       [41503.0, 41504.0, 41505.0, 41506.0, 41507.0, 41508.0],       [41509.0, 41510.0, 41511.0, 41512.0, 41513.0, 41514.0],       [41515.0, 41516.0, 41517.0, 41518.0, 41519.0, 41520.0],       [41521.0, 41522.0, 41523.0, 41524.0, 41525.0, 41526.0],       [41527.0, 41528.0, 41529.0, 41530.0, 41531.0, 41532.0],       [41533.0, 41534.0, 41535.0, 41536.0, 41537.0, 41538.0]],      [[41539.0, 41540.0, 41541.0, 41542.0, 41543.0, 41544.0],       [41545.0, 41546.0, 41547.0, 41548.0, 41549.0, 41550.0],       [41551.0, 41552.0, 41553.0, 41554.0, 41555.0, 41556.0],       [41557.0, 41558.0, 41559.0, 41560.0, 41561.0, 41562.0],       [41563.0, 41564.0, 41565.0, 41566.0, 41567.0, 41568.0],       [41569.0, 41570.0, 41571.0, 41572.0, 41573.0, 41574.0],       [41575.0, 41576.0, 41577.0, 41578.0, 41579.0, 41580.0]]],     [[[41581.0, 41582.0, 41583.0, 41584.0, 41585.0, 41586.0],       [41587.0, 41588.0, 41589.0, 41590.0, 41591.0, 41592.0],       [41593.0, 41594.0, 41595.0, 41596.0, 41597.0, 41598.0],       [41599.0, 41600.0, 41601.0, 41602.0, 41603.0, 41604.0],       [41605.0, 41606.0, 41607.0, 41608.0, 41609.0, 41610.0],       [41611.0, 41612.0, 41613.0, 41614.0, 41615.0, 41616.0],       [41617.0, 41618.0, 41619.0, 41620.0, 41621.0, 41622.0]],      [[41623.0, 41624.0, 41625.0, 41626.0, 41627.0, 41628.0],       [41629.0, 41630.0, 41631.0, 41632.0, 41633.0, 41634.0],       [41635.0, 41636.0, 41637.0, 41638.0, 41639.0, 41640.0],       [41641.0, 41642.0, 41643.0, 41644.0, 41645.0, 41646.0],       [41647.0, 41648.0, 41649.0, 41650.0, 41651.0, 41652.0],       [41653.0, 41654.0, 41655.0, 41656.0, 41657.0, 41658.0],       [41659.0, 41660.0, 41661.0, 41662.0, 41663.0, 41664.0]],      [[41665.0, 41666.0, 41667.0, 41668.0, 41669.0, 41670.0],       [41671.0, 41672.0, 41673.0, 41674.0, 41675.0, 41676.0],       [41677.0, 41678.0, 41679.0, 41680.0, 41681.0, 41682.0],       [41683.0, 41684.0, 41685.0, 41686.0, 41687.0, 41688.0],       [41689.0, 41690.0, 41691.0, 41692.0, 41693.0, 41694.0],       [41695.0, 41696.0, 41697.0, 41698.0, 41699.0, 41700.0],       [41701.0, 41702.0, 41703.0, 41704.0, 41705.0, 41706.0]],      [[41707.0, 41708.0, 41709.0, 41710.0, 41711.0, 41712.0],       [41713.0, 41714.0, 41715.0, 41716.0, 41717.0, 41718.0],       [41719.0, 41720.0, 41721.0, 41722.0, 41723.0, 41724.0],       [41725.0, 41726.0, 41727.0, 41728.0, 41729.0, 41730.0],       [41731.0, 41732.0, 41733.0, 41734.0, 41735.0, 41736.0],       [41737.0, 41738.0, 41739.0, 41740.0, 41741.0, 41742.0],       [41743.0, 41744.0, 41745.0, 41746.0, 41747.0, 41748.0]],      [[41749.0, 41750.0, 41751.0, 41752.0, 41753.0, 41754.0],       [41755.0, 41756.0, 41757.0, 41758.0, 41759.0, 41760.0],       [41761.0, 41762.0, 41763.0, 41764.0, 41765.0, 41766.0],       [41767.0, 41768.0, 41769.0, 41770.0, 41771.0, 41772.0],       [41773.0, 41774.0, 41775.0, 41776.0, 41777.0, 41778.0],       [41779.0, 41780.0, 41781.0, 41782.0, 41783.0, 41784.0],       [41785.0, 41786.0, 41787.0, 41788.0, 41789.0, 41790.0]],      [[41791.0, 41792.0, 41793.0, 41794.0, 41795.0, 41796.0],       [41797.0, 41798.0, 41799.0, 41800.0, 41801.0, 41802.0],       [41803.0, 41804.0, 41805.0, 41806.0, 41807.0, 41808.0],       [41809.0, 41810.0, 41811.0, 41812.0, 41813.0, 41814.0],       [41815.0, 41816.0, 41817.0, 41818.0, 41819.0, 41820.0],       [41821.0, 41822.0, 41823.0, 41824.0, 41825.0, 41826.0],       [41827.0, 41828.0, 41829.0, 41830.0, 41831.0, 41832.0]]],     [[[41833.0, 41834.0, 41835.0, 41836.0, 41837.0, 41838.0],       [41839.0, 41840.0, 41841.0, 41842.0, 41843.0, 41844.0],       [41845.0, 41846.0, 41847.0, 41848.0, 41849.0, 41850.0],       [41851.0, 41852.0, 41853.0, 41854.0, 41855.0, 41856.0],       [41857.0, 41858.0, 41859.0, 41860.0, 41861.0, 41862.0],       [41863.0, 41864.0, 41865.0, 41866.0, 41867.0, 41868.0],       [41869.0, 41870.0, 41871.0, 41872.0, 41873.0, 41874.0]],      [[41875.0, 41876.0, 41877.0, 41878.0, 41879.0, 41880.0],       [41881.0, 41882.0, 41883.0, 41884.0, 41885.0, 41886.0],       [41887.0, 41888.0, 41889.0, 41890.0, 41891.0, 41892.0],       [41893.0, 41894.0, 41895.0, 41896.0, 41897.0, 41898.0],       [41899.0, 41900.0, 41901.0, 41902.0, 41903.0, 41904.0],       [41905.0, 41906.0, 41907.0, 41908.0, 41909.0, 41910.0],       [41911.0, 41912.0, 41913.0, 41914.0, 41915.0, 41916.0]],      [[41917.0, 41918.0, 41919.0, 41920.0, 41921.0, 41922.0],       [41923.0, 41924.0, 41925.0, 41926.0, 41927.0, 41928.0],       [41929.0, 41930.0, 41931.0, 41932.0, 41933.0, 41934.0],       [41935.0, 41936.0, 41937.0, 41938.0, 41939.0, 41940.0],       [41941.0, 41942.0, 41943.0, 41944.0, 41945.0, 41946.0],       [41947.0, 41948.0, 41949.0, 41950.0, 41951.0, 41952.0],       [41953.0, 41954.0, 41955.0, 41956.0, 41957.0, 41958.0]],      [[41959.0, 41960.0, 41961.0, 41962.0, 41963.0, 41964.0],       [41965.0, 41966.0, 41967.0, 41968.0, 41969.0, 41970.0],       [41971.0, 41972.0, 41973.0, 41974.0, 41975.0, 41976.0],       [41977.0, 41978.0, 41979.0, 41980.0, 41981.0, 41982.0],       [41983.0, 41984.0, 41985.0, 41986.0, 41987.0, 41988.0],       [41989.0, 41990.0, 41991.0, 41992.0, 41993.0, 41994.0],       [41995.0, 41996.0, 41997.0, 41998.0, 41999.0, 42000.0]],      [[42001.0, 42002.0, 42003.0, 42004.0, 42005.0, 42006.0],       [42007.0, 42008.0, 42009.0, 42010.0, 42011.0, 42012.0],       [42013.0, 42014.0, 42015.0, 42016.0, 42017.0, 42018.0],       [42019.0, 42020.0, 42021.0, 42022.0, 42023.0, 42024.0],       [42025.0, 42026.0, 42027.0, 42028.0, 42029.0, 42030.0],       [42031.0, 42032.0, 42033.0, 42034.0, 42035.0, 42036.0],       [42037.0, 42038.0, 42039.0, 42040.0, 42041.0, 42042.0]],      [[42043.0, 42044.0, 42045.0, 42046.0, 42047.0, 42048.0],       [42049.0, 42050.0, 42051.0, 42052.0, 42053.0, 42054.0],       [42055.0, 42056.0, 42057.0, 42058.0, 42059.0, 42060.0],       [42061.0, 42062.0, 42063.0, 42064.0, 42065.0, 42066.0],       [42067.0, 42068.0, 42069.0, 42070.0, 42071.0, 42072.0],       [42073.0, 42074.0, 42075.0, 42076.0, 42077.0, 42078.0],       [42079.0, 42080.0, 42081.0, 42082.0, 42083.0, 42084.0]]],     [[[42085.0, 42086.0, 42087.0, 42088.0, 42089.0, 42090.0],       [42091.0, 42092.0, 42093.0, 42094.0, 42095.0, 42096.0],       [42097.0, 42098.0, 42099.0, 42100.0, 42101.0, 42102.0],       [42103.0, 42104.0, 42105.0, 42106.0, 42107.0, 42108.0],       [42109.0, 42110.0, 42111.0, 42112.0, 42113.0, 42114.0],       [42115.0, 42116.0, 42117.0, 42118.0, 42119.0, 42120.0],       [42121.0, 42122.0, 42123.0, 42124.0, 42125.0, 42126.0]],      [[42127.0, 42128.0, 42129.0, 42130.0, 42131.0, 42132.0],       [42133.0, 42134.0, 42135.0, 42136.0, 42137.0, 42138.0],       [42139.0, 42140.0, 42141.0, 42142.0, 42143.0, 42144.0],       [42145.0, 42146.0, 42147.0, 42148.0, 42149.0, 42150.0],       [42151.0, 42152.0, 42153.0, 42154.0, 42155.0, 42156.0],       [42157.0, 42158.0, 42159.0, 42160.0, 42161.0, 42162.0],       [42163.0, 42164.0, 42165.0, 42166.0, 42167.0, 42168.0]],      [[42169.0, 42170.0, 42171.0, 42172.0, 42173.0, 42174.0],       [42175.0, 42176.0, 42177.0, 42178.0, 42179.0, 42180.0],       [42181.0, 42182.0, 42183.0, 42184.0, 42185.0, 42186.0],       [42187.0, 42188.0, 42189.0, 42190.0, 42191.0, 42192.0],       [42193.0, 42194.0, 42195.0, 42196.0, 42197.0, 42198.0],       [42199.0, 42200.0, 42201.0, 42202.0, 42203.0, 42204.0],       [42205.0, 42206.0, 42207.0, 42208.0, 42209.0, 42210.0]],      [[42211.0, 42212.0, 42213.0, 42214.0, 42215.0, 42216.0],       [42217.0, 42218.0, 42219.0, 42220.0, 42221.0, 42222.0],       [42223.0, 42224.0, 42225.0, 42226.0, 42227.0, 42228.0],       [42229.0, 42230.0, 42231.0, 42232.0, 42233.0, 42234.0],       [42235.0, 42236.0, 42237.0, 42238.0, 42239.0, 42240.0],       [42241.0, 42242.0, 42243.0, 42244.0, 42245.0, 42246.0],       [42247.0, 42248.0, 42249.0, 42250.0, 42251.0, 42252.0]],      [[42253.0, 42254.0, 42255.0, 42256.0, 42257.0, 42258.0],       [42259.0, 42260.0, 42261.0, 42262.0, 42263.0, 42264.0],       [42265.0, 42266.0, 42267.0, 42268.0, 42269.0, 42270.0],       [42271.0, 42272.0, 42273.0, 42274.0, 42275.0, 42276.0],       [42277.0, 42278.0, 42279.0, 42280.0, 42281.0, 42282.0],       [42283.0, 42284.0, 42285.0, 42286.0, 42287.0, 42288.0],       [42289.0, 42290.0, 42291.0, 42292.0, 42293.0, 42294.0]],      [[42295.0, 42296.0, 42297.0, 42298.0, 42299.0, 42300.0],       [42301.0, 42302.0, 42303.0, 42304.0, 42305.0, 42306.0],       [42307.0, 42308.0, 42309.0, 42310.0, 42311.0, 42312.0],       [42313.0, 42314.0, 42315.0, 42316.0, 42317.0, 42318.0],       [42319.0, 42320.0, 42321.0, 42322.0, 42323.0, 42324.0],       [42325.0, 42326.0, 42327.0, 42328.0, 42329.0, 42330.0],       [42331.0, 42332.0, 42333.0, 42334.0, 42335.0, 42336.0]]]]],   [[[[[42337.0, 42338.0, 42339.0, 42340.0, 42341.0, 42342.0],       [42343.0, 42344.0, 42345.0, 42346.0, 42347.0, 42348.0],       [42349.0, 42350.0, 42351.0, 42352.0, 42353.0, 42354.0],       [42355.0, 42356.0, 42357.0, 42358.0, 42359.0, 42360.0],       [42361.0, 42362.0, 42363.0, 42364.0, 42365.0, 42366.0],       [42367.0, 42368.0, 42369.0, 42370.0, 42371.0, 42372.0],       [42373.0, 42374.0, 42375.0, 42376.0, 42377.0, 42378.0]],      [[42379.0, 42380.0, 42381.0, 42382.0, 42383.0, 42384.0],       [42385.0, 42386.0, 42387.0, 42388.0, 42389.0, 42390.0],       [42391.0, 42392.0, 42393.0, 42394.0, 42395.0, 42396.0],       [42397.0, 42398.0, 42399.0, 42400.0, 42401.0, 42402.0],       [42403.0, 42404.0, 42405.0, 42406.0, 42407.0, 42408.0],       [42409.0, 42410.0, 42411.0, 42412.0, 42413.0, 42414.0],       [42415.0, 42416.0, 42417.0, 42418.0, 42419.0, 42420.0]],      [[42421.0, 42422.0, 42423.0, 42424.0, 42425.0, 42426.0],       [42427.0, 42428.0, 42429.0, 42430.0, 42431.0, 42432.0],       [42433.0, 42434.0, 42435.0, 42436.0, 42437.0, 42438.0],       [42439.0, 42440.0, 42441.0, 42442.0, 42443.0, 42444.0],       [42445.0, 42446.0, 42447.0, 42448.0, 42449.0, 42450.0],       [42451.0, 42452.0, 42453.0, 42454.0, 42455.0, 42456.0],       [42457.0, 42458.0, 42459.0, 42460.0, 42461.0, 42462.0]],      [[42463.0, 42464.0, 42465.0, 42466.0, 42467.0, 42468.0],       [42469.0, 42470.0, 42471.0, 42472.0, 42473.0, 42474.0],       [42475.0, 42476.0, 42477.0, 42478.0, 42479.0, 42480.0],       [42481.0, 42482.0, 42483.0, 42484.0, 42485.0, 42486.0],       [42487.0, 42488.0, 42489.0, 42490.0, 42491.0, 42492.0],       [42493.0, 42494.0, 42495.0, 42496.0, 42497.0, 42498.0],       [42499.0, 42500.0, 42501.0, 42502.0, 42503.0, 42504.0]],      [[42505.0, 42506.0, 42507.0, 42508.0, 42509.0, 42510.0],       [42511.0, 42512.0, 42513.0, 42514.0, 42515.0, 42516.0],       [42517.0, 42518.0, 42519.0, 42520.0, 42521.0, 42522.0],       [42523.0, 42524.0, 42525.0, 42526.0, 42527.0, 42528.0],       [42529.0, 42530.0, 42531.0, 42532.0, 42533.0, 42534.0],       [42535.0, 42536.0, 42537.0, 42538.0, 42539.0, 42540.0],       [42541.0, 42542.0, 42543.0, 42544.0, 42545.0, 42546.0]],      [[42547.0, 42548.0, 42549.0, 42550.0, 42551.0, 42552.0],       [42553.0, 42554.0, 42555.0, 42556.0, 42557.0, 42558.0],       [42559.0, 42560.0, 42561.0, 42562.0, 42563.0, 42564.0],       [42565.0, 42566.0, 42567.0, 42568.0, 42569.0, 42570.0],       [42571.0, 42572.0, 42573.0, 42574.0, 42575.0, 42576.0],       [42577.0, 42578.0, 42579.0, 42580.0, 42581.0, 42582.0],       [42583.0, 42584.0, 42585.0, 42586.0, 42587.0, 42588.0]]],     [[[42589.0, 42590.0, 42591.0, 42592.0, 42593.0, 42594.0],       [42595.0, 42596.0, 42597.0, 42598.0, 42599.0, 42600.0],       [42601.0, 42602.0, 42603.0, 42604.0, 42605.0, 42606.0],       [42607.0, 42608.0, 42609.0, 42610.0, 42611.0, 42612.0],       [42613.0, 42614.0, 42615.0, 42616.0, 42617.0, 42618.0],       [42619.0, 42620.0, 42621.0, 42622.0, 42623.0, 42624.0],       [42625.0, 42626.0, 42627.0, 42628.0, 42629.0, 42630.0]],      [[42631.0, 42632.0, 42633.0, 42634.0, 42635.0, 42636.0],       [42637.0, 42638.0, 42639.0, 42640.0, 42641.0, 42642.0],       [42643.0, 42644.0, 42645.0, 42646.0, 42647.0, 42648.0],       [42649.0, 42650.0, 42651.0, 42652.0, 42653.0, 42654.0],       [42655.0, 42656.0, 42657.0, 42658.0, 42659.0, 42660.0],       [42661.0, 42662.0, 42663.0, 42664.0, 42665.0, 42666.0],       [42667.0, 42668.0, 42669.0, 42670.0, 42671.0, 42672.0]],      [[42673.0, 42674.0, 42675.0, 42676.0, 42677.0, 42678.0],       [42679.0, 42680.0, 42681.0, 42682.0, 42683.0, 42684.0],       [42685.0, 42686.0, 42687.0, 42688.0, 42689.0, 42690.0],       [42691.0, 42692.0, 42693.0, 42694.0, 42695.0, 42696.0],       [42697.0, 42698.0, 42699.0, 42700.0, 42701.0, 42702.0],       [42703.0, 42704.0, 42705.0, 42706.0, 42707.0, 42708.0],       [42709.0, 42710.0, 42711.0, 42712.0, 42713.0, 42714.0]],      [[42715.0, 42716.0, 42717.0, 42718.0, 42719.0, 42720.0],       [42721.0, 42722.0, 42723.0, 42724.0, 42725.0, 42726.0],       [42727.0, 42728.0, 42729.0, 42730.0, 42731.0, 42732.0],       [42733.0, 42734.0, 42735.0, 42736.0, 42737.0, 42738.0],       [42739.0, 42740.0, 42741.0, 42742.0, 42743.0, 42744.0],       [42745.0, 42746.0, 42747.0, 42748.0, 42749.0, 42750.0],       [42751.0, 42752.0, 42753.0, 42754.0, 42755.0, 42756.0]],      [[42757.0, 42758.0, 42759.0, 42760.0, 42761.0, 42762.0],       [42763.0, 42764.0, 42765.0, 42766.0, 42767.0, 42768.0],       [42769.0, 42770.0, 42771.0, 42772.0, 42773.0, 42774.0],       [42775.0, 42776.0, 42777.0, 42778.0, 42779.0, 42780.0],       [42781.0, 42782.0, 42783.0, 42784.0, 42785.0, 42786.0],       [42787.0, 42788.0, 42789.0, 42790.0, 42791.0, 42792.0],       [42793.0, 42794.0, 42795.0, 42796.0, 42797.0, 42798.0]],      [[42799.0, 42800.0, 42801.0, 42802.0, 42803.0, 42804.0],       [42805.0, 42806.0, 42807.0, 42808.0, 42809.0, 42810.0],       [42811.0, 42812.0, 42813.0, 42814.0, 42815.0, 42816.0],       [42817.0, 42818.0, 42819.0, 42820.0, 42821.0, 42822.0],       [42823.0, 42824.0, 42825.0, 42826.0, 42827.0, 42828.0],       [42829.0, 42830.0, 42831.0, 42832.0, 42833.0, 42834.0],       [42835.0, 42836.0, 42837.0, 42838.0, 42839.0, 42840.0]]],     [[[42841.0, 42842.0, 42843.0, 42844.0, 42845.0, 42846.0],       [42847.0, 42848.0, 42849.0, 42850.0, 42851.0, 42852.0],       [42853.0, 42854.0, 42855.0, 42856.0, 42857.0, 42858.0],       [42859.0, 42860.0, 42861.0, 42862.0, 42863.0, 42864.0],       [42865.0, 42866.0, 42867.0, 42868.0, 42869.0, 42870.0],       [42871.0, 42872.0, 42873.0, 42874.0, 42875.0, 42876.0],       [42877.0, 42878.0, 42879.0, 42880.0, 42881.0, 42882.0]],      [[42883.0, 42884.0, 42885.0, 42886.0, 42887.0, 42888.0],       [42889.0, 42890.0, 42891.0, 42892.0, 42893.0, 42894.0],       [42895.0, 42896.0, 42897.0, 42898.0, 42899.0, 42900.0],       [42901.0, 42902.0, 42903.0, 42904.0, 42905.0, 42906.0],       [42907.0, 42908.0, 42909.0, 42910.0, 42911.0, 42912.0],       [42913.0, 42914.0, 42915.0, 42916.0, 42917.0, 42918.0],       [42919.0, 42920.0, 42921.0, 42922.0, 42923.0, 42924.0]],      [[42925.0, 42926.0, 42927.0, 42928.0, 42929.0, 42930.0],       [42931.0, 42932.0, 42933.0, 42934.0, 42935.0, 42936.0],       [42937.0, 42938.0, 42939.0, 42940.0, 42941.0, 42942.0],       [42943.0, 42944.0, 42945.0, 42946.0, 42947.0, 42948.0],       [42949.0, 42950.0, 42951.0, 42952.0, 42953.0, 42954.0],       [42955.0, 42956.0, 42957.0, 42958.0, 42959.0, 42960.0],       [42961.0, 42962.0, 42963.0, 42964.0, 42965.0, 42966.0]],      [[42967.0, 42968.0, 42969.0, 42970.0, 42971.0, 42972.0],       [42973.0, 42974.0, 42975.0, 42976.0, 42977.0, 42978.0],       [42979.0, 42980.0, 42981.0, 42982.0, 42983.0, 42984.0],       [42985.0, 42986.0, 42987.0, 42988.0, 42989.0, 42990.0],       [42991.0, 42992.0, 42993.0, 42994.0, 42995.0, 42996.0],       [42997.0, 42998.0, 42999.0, 43000.0, 43001.0, 43002.0],       [43003.0, 43004.0, 43005.0, 43006.0, 43007.0, 43008.0]],      [[43009.0, 43010.0, 43011.0, 43012.0, 43013.0, 43014.0],       [43015.0, 43016.0, 43017.0, 43018.0, 43019.0, 43020.0],       [43021.0, 43022.0, 43023.0, 43024.0, 43025.0, 43026.0],       [43027.0, 43028.0, 43029.0, 43030.0, 43031.0, 43032.0],       [43033.0, 43034.0, 43035.0, 43036.0, 43037.0, 43038.0],       [43039.0, 43040.0, 43041.0, 43042.0, 43043.0, 43044.0],       [43045.0, 43046.0, 43047.0, 43048.0, 43049.0, 43050.0]],      [[43051.0, 43052.0, 43053.0, 43054.0, 43055.0, 43056.0],       [43057.0, 43058.0, 43059.0, 43060.0, 43061.0, 43062.0],       [43063.0, 43064.0, 43065.0, 43066.0, 43067.0, 43068.0],       [43069.0, 43070.0, 43071.0, 43072.0, 43073.0, 43074.0],       [43075.0, 43076.0, 43077.0, 43078.0, 43079.0, 43080.0],       [43081.0, 43082.0, 43083.0, 43084.0, 43085.0, 43086.0],       [43087.0, 43088.0, 43089.0, 43090.0, 43091.0, 43092.0]]],     [[[43093.0, 43094.0, 43095.0, 43096.0, 43097.0, 43098.0],       [43099.0, 43100.0, 43101.0, 43102.0, 43103.0, 43104.0],       [43105.0, 43106.0, 43107.0, 43108.0, 43109.0, 43110.0],       [43111.0, 43112.0, 43113.0, 43114.0, 43115.0, 43116.0],       [43117.0, 43118.0, 43119.0, 43120.0, 43121.0, 43122.0],       [43123.0, 43124.0, 43125.0, 43126.0, 43127.0, 43128.0],       [43129.0, 43130.0, 43131.0, 43132.0, 43133.0, 43134.0]],      [[43135.0, 43136.0, 43137.0, 43138.0, 43139.0, 43140.0],       [43141.0, 43142.0, 43143.0, 43144.0, 43145.0, 43146.0],       [43147.0, 43148.0, 43149.0, 43150.0, 43151.0, 43152.0],       [43153.0, 43154.0, 43155.0, 43156.0, 43157.0, 43158.0],       [43159.0, 43160.0, 43161.0, 43162.0, 43163.0, 43164.0],       [43165.0, 43166.0, 43167.0, 43168.0, 43169.0, 43170.0],       [43171.0, 43172.0, 43173.0, 43174.0, 43175.0, 43176.0]],      [[43177.0, 43178.0, 43179.0, 43180.0, 43181.0, 43182.0],       [43183.0, 43184.0, 43185.0, 43186.0, 43187.0, 43188.0],       [43189.0, 43190.0, 43191.0, 43192.0, 43193.0, 43194.0],       [43195.0, 43196.0, 43197.0, 43198.0, 43199.0, 43200.0],       [43201.0, 43202.0, 43203.0, 43204.0, 43205.0, 43206.0],       [43207.0, 43208.0, 43209.0, 43210.0, 43211.0, 43212.0],       [43213.0, 43214.0, 43215.0, 43216.0, 43217.0, 43218.0]],      [[43219.0, 43220.0, 43221.0, 43222.0, 43223.0, 43224.0],       [43225.0, 43226.0, 43227.0, 43228.0, 43229.0, 43230.0],       [43231.0, 43232.0, 43233.0, 43234.0, 43235.0, 43236.0],       [43237.0, 43238.0, 43239.0, 43240.0, 43241.0, 43242.0],       [43243.0, 43244.0, 43245.0, 43246.0, 43247.0, 43248.0],       [43249.0, 43250.0, 43251.0, 43252.0, 43253.0, 43254.0],       [43255.0, 43256.0, 43257.0, 43258.0, 43259.0, 43260.0]],      [[43261.0, 43262.0, 43263.0, 43264.0, 43265.0, 43266.0],       [43267.0, 43268.0, 43269.0, 43270.0, 43271.0, 43272.0],       [43273.0, 43274.0, 43275.0, 43276.0, 43277.0, 43278.0],       [43279.0, 43280.0, 43281.0, 43282.0, 43283.0, 43284.0],       [43285.0, 43286.0, 43287.0, 43288.0, 43289.0, 43290.0],       [43291.0, 43292.0, 43293.0, 43294.0, 43295.0, 43296.0],       [43297.0, 43298.0, 43299.0, 43300.0, 43301.0, 43302.0]],      [[43303.0, 43304.0, 43305.0, 43306.0, 43307.0, 43308.0],       [43309.0, 43310.0, 43311.0, 43312.0, 43313.0, 43314.0],       [43315.0, 43316.0, 43317.0, 43318.0, 43319.0, 43320.0],       [43321.0, 43322.0, 43323.0, 43324.0, 43325.0, 43326.0],       [43327.0, 43328.0, 43329.0, 43330.0, 43331.0, 43332.0],       [43333.0, 43334.0, 43335.0, 43336.0, 43337.0, 43338.0],       [43339.0, 43340.0, 43341.0, 43342.0, 43343.0, 43344.0]]]],    [[[[43345.0, 43346.0, 43347.0, 43348.0, 43349.0, 43350.0],       [43351.0, 43352.0, 43353.0, 43354.0, 43355.0, 43356.0],       [43357.0, 43358.0, 43359.0, 43360.0, 43361.0, 43362.0],       [43363.0, 43364.0, 43365.0, 43366.0, 43367.0, 43368.0],       [43369.0, 43370.0, 43371.0, 43372.0, 43373.0, 43374.0],       [43375.0, 43376.0, 43377.0, 43378.0, 43379.0, 43380.0],       [43381.0, 43382.0, 43383.0, 43384.0, 43385.0, 43386.0]],      [[43387.0, 43388.0, 43389.0, 43390.0, 43391.0, 43392.0],       [43393.0, 43394.0, 43395.0, 43396.0, 43397.0, 43398.0],       [43399.0, 43400.0, 43401.0, 43402.0, 43403.0, 43404.0],       [43405.0, 43406.0, 43407.0, 43408.0, 43409.0, 43410.0],       [43411.0, 43412.0, 43413.0, 43414.0, 43415.0, 43416.0],       [43417.0, 43418.0, 43419.0, 43420.0, 43421.0, 43422.0],       [43423.0, 43424.0, 43425.0, 43426.0, 43427.0, 43428.0]],      [[43429.0, 43430.0, 43431.0, 43432.0, 43433.0, 43434.0],       [43435.0, 43436.0, 43437.0, 43438.0, 43439.0, 43440.0],       [43441.0, 43442.0, 43443.0, 43444.0, 43445.0, 43446.0],       [43447.0, 43448.0, 43449.0, 43450.0, 43451.0, 43452.0],       [43453.0, 43454.0, 43455.0, 43456.0, 43457.0, 43458.0],       [43459.0, 43460.0, 43461.0, 43462.0, 43463.0, 43464.0],       [43465.0, 43466.0, 43467.0, 43468.0, 43469.0, 43470.0]],      [[43471.0, 43472.0, 43473.0, 43474.0, 43475.0, 43476.0],       [43477.0, 43478.0, 43479.0, 43480.0, 43481.0, 43482.0],       [43483.0, 43484.0, 43485.0, 43486.0, 43487.0, 43488.0],       [43489.0, 43490.0, 43491.0, 43492.0, 43493.0, 43494.0],       [43495.0, 43496.0, 43497.0, 43498.0, 43499.0, 43500.0],       [43501.0, 43502.0, 43503.0, 43504.0, 43505.0, 43506.0],       [43507.0, 43508.0, 43509.0, 43510.0, 43511.0, 43512.0]],      [[43513.0, 43514.0, 43515.0, 43516.0, 43517.0, 43518.0],       [43519.0, 43520.0, 43521.0, 43522.0, 43523.0, 43524.0],       [43525.0, 43526.0, 43527.0, 43528.0, 43529.0, 43530.0],       [43531.0, 43532.0, 43533.0, 43534.0, 43535.0, 43536.0],       [43537.0, 43538.0, 43539.0, 43540.0, 43541.0, 43542.0],       [43543.0, 43544.0, 43545.0, 43546.0, 43547.0, 43548.0],       [43549.0, 43550.0, 43551.0, 43552.0, 43553.0, 43554.0]],      [[43555.0, 43556.0, 43557.0, 43558.0, 43559.0, 43560.0],       [43561.0, 43562.0, 43563.0, 43564.0, 43565.0, 43566.0],       [43567.0, 43568.0, 43569.0, 43570.0, 43571.0, 43572.0],       [43573.0, 43574.0, 43575.0, 43576.0, 43577.0, 43578.0],       [43579.0, 43580.0, 43581.0, 43582.0, 43583.0, 43584.0],       [43585.0, 43586.0, 43587.0, 43588.0, 43589.0, 43590.0],       [43591.0, 43592.0, 43593.0, 43594.0, 43595.0, 43596.0]]],     [[[43597.0, 43598.0, 43599.0, 43600.0, 43601.0, 43602.0],       [43603.0, 43604.0, 43605.0, 43606.0, 43607.0, 43608.0],       [43609.0, 43610.0, 43611.0, 43612.0, 43613.0, 43614.0],       [43615.0, 43616.0, 43617.0, 43618.0, 43619.0, 43620.0],       [43621.0, 43622.0, 43623.0, 43624.0, 43625.0, 43626.0],       [43627.0, 43628.0, 43629.0, 43630.0, 43631.0, 43632.0],       [43633.0, 43634.0, 43635.0, 43636.0, 43637.0, 43638.0]],      [[43639.0, 43640.0, 43641.0, 43642.0, 43643.0, 43644.0],       [43645.0, 43646.0, 43647.0, 43648.0, 43649.0, 43650.0],       [43651.0, 43652.0, 43653.0, 43654.0, 43655.0, 43656.0],       [43657.0, 43658.0, 43659.0, 43660.0, 43661.0, 43662.0],       [43663.0, 43664.0, 43665.0, 43666.0, 43667.0, 43668.0],       [43669.0, 43670.0, 43671.0, 43672.0, 43673.0, 43674.0],       [43675.0, 43676.0, 43677.0, 43678.0, 43679.0, 43680.0]],      [[43681.0, 43682.0, 43683.0, 43684.0, 43685.0, 43686.0],       [43687.0, 43688.0, 43689.0, 43690.0, 43691.0, 43692.0],       [43693.0, 43694.0, 43695.0, 43696.0, 43697.0, 43698.0],       [43699.0, 43700.0, 43701.0, 43702.0, 43703.0, 43704.0],       [43705.0, 43706.0, 43707.0, 43708.0, 43709.0, 43710.0],       [43711.0, 43712.0, 43713.0, 43714.0, 43715.0, 43716.0],       [43717.0, 43718.0, 43719.0, 43720.0, 43721.0, 43722.0]],      [[43723.0, 43724.0, 43725.0, 43726.0, 43727.0, 43728.0],       [43729.0, 43730.0, 43731.0, 43732.0, 43733.0, 43734.0],       [43735.0, 43736.0, 43737.0, 43738.0, 43739.0, 43740.0],       [43741.0, 43742.0, 43743.0, 43744.0, 43745.0, 43746.0],       [43747.0, 43748.0, 43749.0, 43750.0, 43751.0, 43752.0],       [43753.0, 43754.0, 43755.0, 43756.0, 43757.0, 43758.0],       [43759.0, 43760.0, 43761.0, 43762.0, 43763.0, 43764.0]],      [[43765.0, 43766.0, 43767.0, 43768.0, 43769.0, 43770.0],       [43771.0, 43772.0, 43773.0, 43774.0, 43775.0, 43776.0],       [43777.0, 43778.0, 43779.0, 43780.0, 43781.0, 43782.0],       [43783.0, 43784.0, 43785.0, 43786.0, 43787.0, 43788.0],       [43789.0, 43790.0, 43791.0, 43792.0, 43793.0, 43794.0],       [43795.0, 43796.0, 43797.0, 43798.0, 43799.0, 43800.0],       [43801.0, 43802.0, 43803.0, 43804.0, 43805.0, 43806.0]],      [[43807.0, 43808.0, 43809.0, 43810.0, 43811.0, 43812.0],       [43813.0, 43814.0, 43815.0, 43816.0, 43817.0, 43818.0],       [43819.0, 43820.0, 43821.0, 43822.0, 43823.0, 43824.0],       [43825.0, 43826.0, 43827.0, 43828.0, 43829.0, 43830.0],       [43831.0, 43832.0, 43833.0, 43834.0, 43835.0, 43836.0],       [43837.0, 43838.0, 43839.0, 43840.0, 43841.0, 43842.0],       [43843.0, 43844.0, 43845.0, 43846.0, 43847.0, 43848.0]]],     [[[43849.0, 43850.0, 43851.0, 43852.0, 43853.0, 43854.0],       [43855.0, 43856.0, 43857.0, 43858.0, 43859.0, 43860.0],       [43861.0, 43862.0, 43863.0, 43864.0, 43865.0, 43866.0],       [43867.0, 43868.0, 43869.0, 43870.0, 43871.0, 43872.0],       [43873.0, 43874.0, 43875.0, 43876.0, 43877.0, 43878.0],       [43879.0, 43880.0, 43881.0, 43882.0, 43883.0, 43884.0],       [43885.0, 43886.0, 43887.0, 43888.0, 43889.0, 43890.0]],      [[43891.0, 43892.0, 43893.0, 43894.0, 43895.0, 43896.0],       [43897.0, 43898.0, 43899.0, 43900.0, 43901.0, 43902.0],       [43903.0, 43904.0, 43905.0, 43906.0, 43907.0, 43908.0],       [43909.0, 43910.0, 43911.0, 43912.0, 43913.0, 43914.0],       [43915.0, 43916.0, 43917.0, 43918.0, 43919.0, 43920.0],       [43921.0, 43922.0, 43923.0, 43924.0, 43925.0, 43926.0],       [43927.0, 43928.0, 43929.0, 43930.0, 43931.0, 43932.0]],      [[43933.0, 43934.0, 43935.0, 43936.0, 43937.0, 43938.0],       [43939.0, 43940.0, 43941.0, 43942.0, 43943.0, 43944.0],       [43945.0, 43946.0, 43947.0, 43948.0, 43949.0, 43950.0],       [43951.0, 43952.0, 43953.0, 43954.0, 43955.0, 43956.0],       [43957.0, 43958.0, 43959.0, 43960.0, 43961.0, 43962.0],       [43963.0, 43964.0, 43965.0, 43966.0, 43967.0, 43968.0],       [43969.0, 43970.0, 43971.0, 43972.0, 43973.0, 43974.0]],      [[43975.0, 43976.0, 43977.0, 43978.0, 43979.0, 43980.0],       [43981.0, 43982.0, 43983.0, 43984.0, 43985.0, 43986.0],       [43987.0, 43988.0, 43989.0, 43990.0, 43991.0, 43992.0],       [43993.0, 43994.0, 43995.0, 43996.0, 43997.0, 43998.0],       [43999.0, 44000.0, 44001.0, 44002.0, 44003.0, 44004.0],       [44005.0, 44006.0, 44007.0, 44008.0, 44009.0, 44010.0],       [44011.0, 44012.0, 44013.0, 44014.0, 44015.0, 44016.0]],      [[44017.0, 44018.0, 44019.0, 44020.0, 44021.0, 44022.0],       [44023.0, 44024.0, 44025.0, 44026.0, 44027.0, 44028.0],       [44029.0, 44030.0, 44031.0, 44032.0, 44033.0, 44034.0],       [44035.0, 44036.0, 44037.0, 44038.0, 44039.0, 44040.0],       [44041.0, 44042.0, 44043.0, 44044.0, 44045.0, 44046.0],       [44047.0, 44048.0, 44049.0, 44050.0, 44051.0, 44052.0],       [44053.0, 44054.0, 44055.0, 44056.0, 44057.0, 44058.0]],      [[44059.0, 44060.0, 44061.0, 44062.0, 44063.0, 44064.0],       [44065.0, 44066.0, 44067.0, 44068.0, 44069.0, 44070.0],       [44071.0, 44072.0, 44073.0, 44074.0, 44075.0, 44076.0],       [44077.0, 44078.0, 44079.0, 44080.0, 44081.0, 44082.0],       [44083.0, 44084.0, 44085.0, 44086.0, 44087.0, 44088.0],       [44089.0, 44090.0, 44091.0, 44092.0, 44093.0, 44094.0],       [44095.0, 44096.0, 44097.0, 44098.0, 44099.0, 44100.0]]],     [[[44101.0, 44102.0, 44103.0, 44104.0, 44105.0, 44106.0],       [44107.0, 44108.0, 44109.0, 44110.0, 44111.0, 44112.0],       [44113.0, 44114.0, 44115.0, 44116.0, 44117.0, 44118.0],       [44119.0, 44120.0, 44121.0, 44122.0, 44123.0, 44124.0],       [44125.0, 44126.0, 44127.0, 44128.0, 44129.0, 44130.0],       [44131.0, 44132.0, 44133.0, 44134.0, 44135.0, 44136.0],       [44137.0, 44138.0, 44139.0, 44140.0, 44141.0, 44142.0]],      [[44143.0, 44144.0, 44145.0, 44146.0, 44147.0, 44148.0],       [44149.0, 44150.0, 44151.0, 44152.0, 44153.0, 44154.0],       [44155.0, 44156.0, 44157.0, 44158.0, 44159.0, 44160.0],       [44161.0, 44162.0, 44163.0, 44164.0, 44165.0, 44166.0],       [44167.0, 44168.0, 44169.0, 44170.0, 44171.0, 44172.0],       [44173.0, 44174.0, 44175.0, 44176.0, 44177.0, 44178.0],       [44179.0, 44180.0, 44181.0, 44182.0, 44183.0, 44184.0]],      [[44185.0, 44186.0, 44187.0, 44188.0, 44189.0, 44190.0],       [44191.0, 44192.0, 44193.0, 44194.0, 44195.0, 44196.0],       [44197.0, 44198.0, 44199.0, 44200.0, 44201.0, 44202.0],       [44203.0, 44204.0, 44205.0, 44206.0, 44207.0, 44208.0],       [44209.0, 44210.0, 44211.0, 44212.0, 44213.0, 44214.0],       [44215.0, 44216.0, 44217.0, 44218.0, 44219.0, 44220.0],       [44221.0, 44222.0, 44223.0, 44224.0, 44225.0, 44226.0]],      [[44227.0, 44228.0, 44229.0, 44230.0, 44231.0, 44232.0],       [44233.0, 44234.0, 44235.0, 44236.0, 44237.0, 44238.0],       [44239.0, 44240.0, 44241.0, 44242.0, 44243.0, 44244.0],       [44245.0, 44246.0, 44247.0, 44248.0, 44249.0, 44250.0],       [44251.0, 44252.0, 44253.0, 44254.0, 44255.0, 44256.0],       [44257.0, 44258.0, 44259.0, 44260.0, 44261.0, 44262.0],       [44263.0, 44264.0, 44265.0, 44266.0, 44267.0, 44268.0]],      [[44269.0, 44270.0, 44271.0, 44272.0, 44273.0, 44274.0],       [44275.0, 44276.0, 44277.0, 44278.0, 44279.0, 44280.0],       [44281.0, 44282.0, 44283.0, 44284.0, 44285.0, 44286.0],       [44287.0, 44288.0, 44289.0, 44290.0, 44291.0, 44292.0],       [44293.0, 44294.0, 44295.0, 44296.0, 44297.0, 44298.0],       [44299.0, 44300.0, 44301.0, 44302.0, 44303.0, 44304.0],       [44305.0, 44306.0, 44307.0, 44308.0, 44309.0, 44310.0]],      [[44311.0, 44312.0, 44313.0, 44314.0, 44315.0, 44316.0],       [44317.0, 44318.0, 44319.0, 44320.0, 44321.0, 44322.0],       [44323.0, 44324.0, 44325.0, 44326.0, 44327.0, 44328.0],       [44329.0, 44330.0, 44331.0, 44332.0, 44333.0, 44334.0],       [44335.0, 44336.0, 44337.0, 44338.0, 44339.0, 44340.0],       [44341.0, 44342.0, 44343.0, 44344.0, 44345.0, 44346.0],       [44347.0, 44348.0, 44349.0, 44350.0, 44351.0, 44352.0]]]],    [[[[44353.0, 44354.0, 44355.0, 44356.0, 44357.0, 44358.0],       [44359.0, 44360.0, 44361.0, 44362.0, 44363.0, 44364.0],       [44365.0, 44366.0, 44367.0, 44368.0, 44369.0, 44370.0],       [44371.0, 44372.0, 44373.0, 44374.0, 44375.0, 44376.0],       [44377.0, 44378.0, 44379.0, 44380.0, 44381.0, 44382.0],       [44383.0, 44384.0, 44385.0, 44386.0, 44387.0, 44388.0],       [44389.0, 44390.0, 44391.0, 44392.0, 44393.0, 44394.0]],      [[44395.0, 44396.0, 44397.0, 44398.0, 44399.0, 44400.0],       [44401.0, 44402.0, 44403.0, 44404.0, 44405.0, 44406.0],       [44407.0, 44408.0, 44409.0, 44410.0, 44411.0, 44412.0],       [44413.0, 44414.0, 44415.0, 44416.0, 44417.0, 44418.0],       [44419.0, 44420.0, 44421.0, 44422.0, 44423.0, 44424.0],       [44425.0, 44426.0, 44427.0, 44428.0, 44429.0, 44430.0],       [44431.0, 44432.0, 44433.0, 44434.0, 44435.0, 44436.0]],      [[44437.0, 44438.0, 44439.0, 44440.0, 44441.0, 44442.0],       [44443.0, 44444.0, 44445.0, 44446.0, 44447.0, 44448.0],       [44449.0, 44450.0, 44451.0, 44452.0, 44453.0, 44454.0],       [44455.0, 44456.0, 44457.0, 44458.0, 44459.0, 44460.0],       [44461.0, 44462.0, 44463.0, 44464.0, 44465.0, 44466.0],       [44467.0, 44468.0, 44469.0, 44470.0, 44471.0, 44472.0],       [44473.0, 44474.0, 44475.0, 44476.0, 44477.0, 44478.0]],      [[44479.0, 44480.0, 44481.0, 44482.0, 44483.0, 44484.0],       [44485.0, 44486.0, 44487.0, 44488.0, 44489.0, 44490.0],       [44491.0, 44492.0, 44493.0, 44494.0, 44495.0, 44496.0],       [44497.0, 44498.0, 44499.0, 44500.0, 44501.0, 44502.0],       [44503.0, 44504.0, 44505.0, 44506.0, 44507.0, 44508.0],       [44509.0, 44510.0, 44511.0, 44512.0, 44513.0, 44514.0],       [44515.0, 44516.0, 44517.0, 44518.0, 44519.0, 44520.0]],      [[44521.0, 44522.0, 44523.0, 44524.0, 44525.0, 44526.0],       [44527.0, 44528.0, 44529.0, 44530.0, 44531.0, 44532.0],       [44533.0, 44534.0, 44535.0, 44536.0, 44537.0, 44538.0],       [44539.0, 44540.0, 44541.0, 44542.0, 44543.0, 44544.0],       [44545.0, 44546.0, 44547.0, 44548.0, 44549.0, 44550.0],       [44551.0, 44552.0, 44553.0, 44554.0, 44555.0, 44556.0],       [44557.0, 44558.0, 44559.0, 44560.0, 44561.0, 44562.0]],      [[44563.0, 44564.0, 44565.0, 44566.0, 44567.0, 44568.0],       [44569.0, 44570.0, 44571.0, 44572.0, 44573.0, 44574.0],       [44575.0, 44576.0, 44577.0, 44578.0, 44579.0, 44580.0],       [44581.0, 44582.0, 44583.0, 44584.0, 44585.0, 44586.0],       [44587.0, 44588.0, 44589.0, 44590.0, 44591.0, 44592.0],       [44593.0, 44594.0, 44595.0, 44596.0, 44597.0, 44598.0],       [44599.0, 44600.0, 44601.0, 44602.0, 44603.0, 44604.0]]],     [[[44605.0, 44606.0, 44607.0, 44608.0, 44609.0, 44610.0],       [44611.0, 44612.0, 44613.0, 44614.0, 44615.0, 44616.0],       [44617.0, 44618.0, 44619.0, 44620.0, 44621.0, 44622.0],       [44623.0, 44624.0, 44625.0, 44626.0, 44627.0, 44628.0],       [44629.0, 44630.0, 44631.0, 44632.0, 44633.0, 44634.0],       [44635.0, 44636.0, 44637.0, 44638.0, 44639.0, 44640.0],       [44641.0, 44642.0, 44643.0, 44644.0, 44645.0, 44646.0]],      [[44647.0, 44648.0, 44649.0, 44650.0, 44651.0, 44652.0],       [44653.0, 44654.0, 44655.0, 44656.0, 44657.0, 44658.0],       [44659.0, 44660.0, 44661.0, 44662.0, 44663.0, 44664.0],       [44665.0, 44666.0, 44667.0, 44668.0, 44669.0, 44670.0],       [44671.0, 44672.0, 44673.0, 44674.0, 44675.0, 44676.0],       [44677.0, 44678.0, 44679.0, 44680.0, 44681.0, 44682.0],       [44683.0, 44684.0, 44685.0, 44686.0, 44687.0, 44688.0]],      [[44689.0, 44690.0, 44691.0, 44692.0, 44693.0, 44694.0],       [44695.0, 44696.0, 44697.0, 44698.0, 44699.0, 44700.0],       [44701.0, 44702.0, 44703.0, 44704.0, 44705.0, 44706.0],       [44707.0, 44708.0, 44709.0, 44710.0, 44711.0, 44712.0],       [44713.0, 44714.0, 44715.0, 44716.0, 44717.0, 44718.0],       [44719.0, 44720.0, 44721.0, 44722.0, 44723.0, 44724.0],       [44725.0, 44726.0, 44727.0, 44728.0, 44729.0, 44730.0]],      [[44731.0, 44732.0, 44733.0, 44734.0, 44735.0, 44736.0],       [44737.0, 44738.0, 44739.0, 44740.0, 44741.0, 44742.0],       [44743.0, 44744.0, 44745.0, 44746.0, 44747.0, 44748.0],       [44749.0, 44750.0, 44751.0, 44752.0, 44753.0, 44754.0],       [44755.0, 44756.0, 44757.0, 44758.0, 44759.0, 44760.0],       [44761.0, 44762.0, 44763.0, 44764.0, 44765.0, 44766.0],       [44767.0, 44768.0, 44769.0, 44770.0, 44771.0, 44772.0]],      [[44773.0, 44774.0, 44775.0, 44776.0, 44777.0, 44778.0],       [44779.0, 44780.0, 44781.0, 44782.0, 44783.0, 44784.0],       [44785.0, 44786.0, 44787.0, 44788.0, 44789.0, 44790.0],       [44791.0, 44792.0, 44793.0, 44794.0, 44795.0, 44796.0],       [44797.0, 44798.0, 44799.0, 44800.0, 44801.0, 44802.0],       [44803.0, 44804.0, 44805.0, 44806.0, 44807.0, 44808.0],       [44809.0, 44810.0, 44811.0, 44812.0, 44813.0, 44814.0]],      [[44815.0, 44816.0, 44817.0, 44818.0, 44819.0, 44820.0],       [44821.0, 44822.0, 44823.0, 44824.0, 44825.0, 44826.0],       [44827.0, 44828.0, 44829.0, 44830.0, 44831.0, 44832.0],       [44833.0, 44834.0, 44835.0, 44836.0, 44837.0, 44838.0],       [44839.0, 44840.0, 44841.0, 44842.0, 44843.0, 44844.0],       [44845.0, 44846.0, 44847.0, 44848.0, 44849.0, 44850.0],       [44851.0, 44852.0, 44853.0, 44854.0, 44855.0, 44856.0]]],     [[[44857.0, 44858.0, 44859.0, 44860.0, 44861.0, 44862.0],       [44863.0, 44864.0, 44865.0, 44866.0, 44867.0, 44868.0],       [44869.0, 44870.0, 44871.0, 44872.0, 44873.0, 44874.0],       [44875.0, 44876.0, 44877.0, 44878.0, 44879.0, 44880.0],       [44881.0, 44882.0, 44883.0, 44884.0, 44885.0, 44886.0],       [44887.0, 44888.0, 44889.0, 44890.0, 44891.0, 44892.0],       [44893.0, 44894.0, 44895.0, 44896.0, 44897.0, 44898.0]],      [[44899.0, 44900.0, 44901.0, 44902.0, 44903.0, 44904.0],       [44905.0, 44906.0, 44907.0, 44908.0, 44909.0, 44910.0],       [44911.0, 44912.0, 44913.0, 44914.0, 44915.0, 44916.0],       [44917.0, 44918.0, 44919.0, 44920.0, 44921.0, 44922.0],       [44923.0, 44924.0, 44925.0, 44926.0, 44927.0, 44928.0],       [44929.0, 44930.0, 44931.0, 44932.0, 44933.0, 44934.0],       [44935.0, 44936.0, 44937.0, 44938.0, 44939.0, 44940.0]],      [[44941.0, 44942.0, 44943.0, 44944.0, 44945.0, 44946.0],       [44947.0, 44948.0, 44949.0, 44950.0, 44951.0, 44952.0],       [44953.0, 44954.0, 44955.0, 44956.0, 44957.0, 44958.0],       [44959.0, 44960.0, 44961.0, 44962.0, 44963.0, 44964.0],       [44965.0, 44966.0, 44967.0, 44968.0, 44969.0, 44970.0],       [44971.0, 44972.0, 44973.0, 44974.0, 44975.0, 44976.0],       [44977.0, 44978.0, 44979.0, 44980.0, 44981.0, 44982.0]],      [[44983.0, 44984.0, 44985.0, 44986.0, 44987.0, 44988.0],       [44989.0, 44990.0, 44991.0, 44992.0, 44993.0, 44994.0],       [44995.0, 44996.0, 44997.0, 44998.0, 44999.0, 45000.0],       [45001.0, 45002.0, 45003.0, 45004.0, 45005.0, 45006.0],       [45007.0, 45008.0, 45009.0, 45010.0, 45011.0, 45012.0],       [45013.0, 45014.0, 45015.0, 45016.0, 45017.0, 45018.0],       [45019.0, 45020.0, 45021.0, 45022.0, 45023.0, 45024.0]],      [[45025.0, 45026.0, 45027.0, 45028.0, 45029.0, 45030.0],       [45031.0, 45032.0, 45033.0, 45034.0, 45035.0, 45036.0],       [45037.0, 45038.0, 45039.0, 45040.0, 45041.0, 45042.0],       [45043.0, 45044.0, 45045.0, 45046.0, 45047.0, 45048.0],       [45049.0, 45050.0, 45051.0, 45052.0, 45053.0, 45054.0],       [45055.0, 45056.0, 45057.0, 45058.0, 45059.0, 45060.0],       [45061.0, 45062.0, 45063.0, 45064.0, 45065.0, 45066.0]],      [[45067.0, 45068.0, 45069.0, 45070.0, 45071.0, 45072.0],       [45073.0, 45074.0, 45075.0, 45076.0, 45077.0, 45078.0],       [45079.0, 45080.0, 45081.0, 45082.0, 45083.0, 45084.0],       [45085.0, 45086.0, 45087.0, 45088.0, 45089.0, 45090.0],       [45091.0, 45092.0, 45093.0, 45094.0, 45095.0, 45096.0],       [45097.0, 45098.0, 45099.0, 45100.0, 45101.0, 45102.0],       [45103.0, 45104.0, 45105.0, 45106.0, 45107.0, 45108.0]]],     [[[45109.0, 45110.0, 45111.0, 45112.0, 45113.0, 45114.0],       [45115.0, 45116.0, 45117.0, 45118.0, 45119.0, 45120.0],       [45121.0, 45122.0, 45123.0, 45124.0, 45125.0, 45126.0],       [45127.0, 45128.0, 45129.0, 45130.0, 45131.0, 45132.0],       [45133.0, 45134.0, 45135.0, 45136.0, 45137.0, 45138.0],       [45139.0, 45140.0, 45141.0, 45142.0, 45143.0, 45144.0],       [45145.0, 45146.0, 45147.0, 45148.0, 45149.0, 45150.0]],      [[45151.0, 45152.0, 45153.0, 45154.0, 45155.0, 45156.0],       [45157.0, 45158.0, 45159.0, 45160.0, 45161.0, 45162.0],       [45163.0, 45164.0, 45165.0, 45166.0, 45167.0, 45168.0],       [45169.0, 45170.0, 45171.0, 45172.0, 45173.0, 45174.0],       [45175.0, 45176.0, 45177.0, 45178.0, 45179.0, 45180.0],       [45181.0, 45182.0, 45183.0, 45184.0, 45185.0, 45186.0],       [45187.0, 45188.0, 45189.0, 45190.0, 45191.0, 45192.0]],      [[45193.0, 45194.0, 45195.0, 45196.0, 45197.0, 45198.0],       [45199.0, 45200.0, 45201.0, 45202.0, 45203.0, 45204.0],       [45205.0, 45206.0, 45207.0, 45208.0, 45209.0, 45210.0],       [45211.0, 45212.0, 45213.0, 45214.0, 45215.0, 45216.0],       [45217.0, 45218.0, 45219.0, 45220.0, 45221.0, 45222.0],       [45223.0, 45224.0, 45225.0, 45226.0, 45227.0, 45228.0],       [45229.0, 45230.0, 45231.0, 45232.0, 45233.0, 45234.0]],      [[45235.0, 45236.0, 45237.0, 45238.0, 45239.0, 45240.0],       [45241.0, 45242.0, 45243.0, 45244.0, 45245.0, 45246.0],       [45247.0, 45248.0, 45249.0, 45250.0, 45251.0, 45252.0],       [45253.0, 45254.0, 45255.0, 45256.0, 45257.0, 45258.0],       [45259.0, 45260.0, 45261.0, 45262.0, 45263.0, 45264.0],       [45265.0, 45266.0, 45267.0, 45268.0, 45269.0, 45270.0],       [45271.0, 45272.0, 45273.0, 45274.0, 45275.0, 45276.0]],      [[45277.0, 45278.0, 45279.0, 45280.0, 45281.0, 45282.0],       [45283.0, 45284.0, 45285.0, 45286.0, 45287.0, 45288.0],       [45289.0, 45290.0, 45291.0, 45292.0, 45293.0, 45294.0],       [45295.0, 45296.0, 45297.0, 45298.0, 45299.0, 45300.0],       [45301.0, 45302.0, 45303.0, 45304.0, 45305.0, 45306.0],       [45307.0, 45308.0, 45309.0, 45310.0, 45311.0, 45312.0],       [45313.0, 45314.0, 45315.0, 45316.0, 45317.0, 45318.0]],      [[45319.0, 45320.0, 45321.0, 45322.0, 45323.0, 45324.0],       [45325.0, 45326.0, 45327.0, 45328.0, 45329.0, 45330.0],       [45331.0, 45332.0, 45333.0, 45334.0, 45335.0, 45336.0],       [45337.0, 45338.0, 45339.0, 45340.0, 45341.0, 45342.0],       [45343.0, 45344.0, 45345.0, 45346.0, 45347.0, 45348.0],       [45349.0, 45350.0, 45351.0, 45352.0, 45353.0, 45354.0],       [45355.0, 45356.0, 45357.0, 45358.0, 45359.0, 45360.0]]]],    [[[[45361.0, 45362.0, 45363.0, 45364.0, 45365.0, 45366.0],       [45367.0, 45368.0, 45369.0, 45370.0, 45371.0, 45372.0],       [45373.0, 45374.0, 45375.0, 45376.0, 45377.0, 45378.0],       [45379.0, 45380.0, 45381.0, 45382.0, 45383.0, 45384.0],       [45385.0, 45386.0, 45387.0, 45388.0, 45389.0, 45390.0],       [45391.0, 45392.0, 45393.0, 45394.0, 45395.0, 45396.0],       [45397.0, 45398.0, 45399.0, 45400.0, 45401.0, 45402.0]],      [[45403.0, 45404.0, 45405.0, 45406.0, 45407.0, 45408.0],       [45409.0, 45410.0, 45411.0, 45412.0, 45413.0, 45414.0],       [45415.0, 45416.0, 45417.0, 45418.0, 45419.0, 45420.0],       [45421.0, 45422.0, 45423.0, 45424.0, 45425.0, 45426.0],       [45427.0, 45428.0, 45429.0, 45430.0, 45431.0, 45432.0],       [45433.0, 45434.0, 45435.0, 45436.0, 45437.0, 45438.0],       [45439.0, 45440.0, 45441.0, 45442.0, 45443.0, 45444.0]],      [[45445.0, 45446.0, 45447.0, 45448.0, 45449.0, 45450.0],       [45451.0, 45452.0, 45453.0, 45454.0, 45455.0, 45456.0],       [45457.0, 45458.0, 45459.0, 45460.0, 45461.0, 45462.0],       [45463.0, 45464.0, 45465.0, 45466.0, 45467.0, 45468.0],       [45469.0, 45470.0, 45471.0, 45472.0, 45473.0, 45474.0],       [45475.0, 45476.0, 45477.0, 45478.0, 45479.0, 45480.0],       [45481.0, 45482.0, 45483.0, 45484.0, 45485.0, 45486.0]],      [[45487.0, 45488.0, 45489.0, 45490.0, 45491.0, 45492.0],       [45493.0, 45494.0, 45495.0, 45496.0, 45497.0, 45498.0],       [45499.0, 45500.0, 45501.0, 45502.0, 45503.0, 45504.0],       [45505.0, 45506.0, 45507.0, 45508.0, 45509.0, 45510.0],       [45511.0, 45512.0, 45513.0, 45514.0, 45515.0, 45516.0],       [45517.0, 45518.0, 45519.0, 45520.0, 45521.0, 45522.0],       [45523.0, 45524.0, 45525.0, 45526.0, 45527.0, 45528.0]],      [[45529.0, 45530.0, 45531.0, 45532.0, 45533.0, 45534.0],       [45535.0, 45536.0, 45537.0, 45538.0, 45539.0, 45540.0],       [45541.0, 45542.0, 45543.0, 45544.0, 45545.0, 45546.0],       [45547.0, 45548.0, 45549.0, 45550.0, 45551.0, 45552.0],       [45553.0, 45554.0, 45555.0, 45556.0, 45557.0, 45558.0],       [45559.0, 45560.0, 45561.0, 45562.0, 45563.0, 45564.0],       [45565.0, 45566.0, 45567.0, 45568.0, 45569.0, 45570.0]],      [[45571.0, 45572.0, 45573.0, 45574.0, 45575.0, 45576.0],       [45577.0, 45578.0, 45579.0, 45580.0, 45581.0, 45582.0],       [45583.0, 45584.0, 45585.0, 45586.0, 45587.0, 45588.0],       [45589.0, 45590.0, 45591.0, 45592.0, 45593.0, 45594.0],       [45595.0, 45596.0, 45597.0, 45598.0, 45599.0, 45600.0],       [45601.0, 45602.0, 45603.0, 45604.0, 45605.0, 45606.0],       [45607.0, 45608.0, 45609.0, 45610.0, 45611.0, 45612.0]]],     [[[45613.0, 45614.0, 45615.0, 45616.0, 45617.0, 45618.0],       [45619.0, 45620.0, 45621.0, 45622.0, 45623.0, 45624.0],       [45625.0, 45626.0, 45627.0, 45628.0, 45629.0, 45630.0],       [45631.0, 45632.0, 45633.0, 45634.0, 45635.0, 45636.0],       [45637.0, 45638.0, 45639.0, 45640.0, 45641.0, 45642.0],       [45643.0, 45644.0, 45645.0, 45646.0, 45647.0, 45648.0],       [45649.0, 45650.0, 45651.0, 45652.0, 45653.0, 45654.0]],      [[45655.0, 45656.0, 45657.0, 45658.0, 45659.0, 45660.0],       [45661.0, 45662.0, 45663.0, 45664.0, 45665.0, 45666.0],       [45667.0, 45668.0, 45669.0, 45670.0, 45671.0, 45672.0],       [45673.0, 45674.0, 45675.0, 45676.0, 45677.0, 45678.0],       [45679.0, 45680.0, 45681.0, 45682.0, 45683.0, 45684.0],       [45685.0, 45686.0, 45687.0, 45688.0, 45689.0, 45690.0],       [45691.0, 45692.0, 45693.0, 45694.0, 45695.0, 45696.0]],      [[45697.0, 45698.0, 45699.0, 45700.0, 45701.0, 45702.0],       [45703.0, 45704.0, 45705.0, 45706.0, 45707.0, 45708.0],       [45709.0, 45710.0, 45711.0, 45712.0, 45713.0, 45714.0],       [45715.0, 45716.0, 45717.0, 45718.0, 45719.0, 45720.0],       [45721.0, 45722.0, 45723.0, 45724.0, 45725.0, 45726.0],       [45727.0, 45728.0, 45729.0, 45730.0, 45731.0, 45732.0],       [45733.0, 45734.0, 45735.0, 45736.0, 45737.0, 45738.0]],      [[45739.0, 45740.0, 45741.0, 45742.0, 45743.0, 45744.0],       [45745.0, 45746.0, 45747.0, 45748.0, 45749.0, 45750.0],       [45751.0, 45752.0, 45753.0, 45754.0, 45755.0, 45756.0],       [45757.0, 45758.0, 45759.0, 45760.0, 45761.0, 45762.0],       [45763.0, 45764.0, 45765.0, 45766.0, 45767.0, 45768.0],       [45769.0, 45770.0, 45771.0, 45772.0, 45773.0, 45774.0],       [45775.0, 45776.0, 45777.0, 45778.0, 45779.0, 45780.0]],      [[45781.0, 45782.0, 45783.0, 45784.0, 45785.0, 45786.0],       [45787.0, 45788.0, 45789.0, 45790.0, 45791.0, 45792.0],       [45793.0, 45794.0, 45795.0, 45796.0, 45797.0, 45798.0],       [45799.0, 45800.0, 45801.0, 45802.0, 45803.0, 45804.0],       [45805.0, 45806.0, 45807.0, 45808.0, 45809.0, 45810.0],       [45811.0, 45812.0, 45813.0, 45814.0, 45815.0, 45816.0],       [45817.0, 45818.0, 45819.0, 45820.0, 45821.0, 45822.0]],      [[45823.0, 45824.0, 45825.0, 45826.0, 45827.0, 45828.0],       [45829.0, 45830.0, 45831.0, 45832.0, 45833.0, 45834.0],       [45835.0, 45836.0, 45837.0, 45838.0, 45839.0, 45840.0],       [45841.0, 45842.0, 45843.0, 45844.0, 45845.0, 45846.0],       [45847.0, 45848.0, 45849.0, 45850.0, 45851.0, 45852.0],       [45853.0, 45854.0, 45855.0, 45856.0, 45857.0, 45858.0],       [45859.0, 45860.0, 45861.0, 45862.0, 45863.0, 45864.0]]],     [[[45865.0, 45866.0, 45867.0, 45868.0, 45869.0, 45870.0],       [45871.0, 45872.0, 45873.0, 45874.0, 45875.0, 45876.0],       [45877.0, 45878.0, 45879.0, 45880.0, 45881.0, 45882.0],       [45883.0, 45884.0, 45885.0, 45886.0, 45887.0, 45888.0],       [45889.0, 45890.0, 45891.0, 45892.0, 45893.0, 45894.0],       [45895.0, 45896.0, 45897.0, 45898.0, 45899.0, 45900.0],       [45901.0, 45902.0, 45903.0, 45904.0, 45905.0, 45906.0]],      [[45907.0, 45908.0, 45909.0, 45910.0, 45911.0, 45912.0],       [45913.0, 45914.0, 45915.0, 45916.0, 45917.0, 45918.0],       [45919.0, 45920.0, 45921.0, 45922.0, 45923.0, 45924.0],       [45925.0, 45926.0, 45927.0, 45928.0, 45929.0, 45930.0],       [45931.0, 45932.0, 45933.0, 45934.0, 45935.0, 45936.0],       [45937.0, 45938.0, 45939.0, 45940.0, 45941.0, 45942.0],       [45943.0, 45944.0, 45945.0, 45946.0, 45947.0, 45948.0]],      [[45949.0, 45950.0, 45951.0, 45952.0, 45953.0, 45954.0],       [45955.0, 45956.0, 45957.0, 45958.0, 45959.0, 45960.0],       [45961.0, 45962.0, 45963.0, 45964.0, 45965.0, 45966.0],       [45967.0, 45968.0, 45969.0, 45970.0, 45971.0, 45972.0],       [45973.0, 45974.0, 45975.0, 45976.0, 45977.0, 45978.0],       [45979.0, 45980.0, 45981.0, 45982.0, 45983.0, 45984.0],       [45985.0, 45986.0, 45987.0, 45988.0, 45989.0, 45990.0]],      [[45991.0, 45992.0, 45993.0, 45994.0, 45995.0, 45996.0],       [45997.0, 45998.0, 45999.0, 46000.0, 46001.0, 46002.0],       [46003.0, 46004.0, 46005.0, 46006.0, 46007.0, 46008.0],       [46009.0, 46010.0, 46011.0, 46012.0, 46013.0, 46014.0],       [46015.0, 46016.0, 46017.0, 46018.0, 46019.0, 46020.0],       [46021.0, 46022.0, 46023.0, 46024.0, 46025.0, 46026.0],       [46027.0, 46028.0, 46029.0, 46030.0, 46031.0, 46032.0]],      [[46033.0, 46034.0, 46035.0, 46036.0, 46037.0, 46038.0],       [46039.0, 46040.0, 46041.0, 46042.0, 46043.0, 46044.0],       [46045.0, 46046.0, 46047.0, 46048.0, 46049.0, 46050.0],       [46051.0, 46052.0, 46053.0, 46054.0, 46055.0, 46056.0],       [46057.0, 46058.0, 46059.0, 46060.0, 46061.0, 46062.0],       [46063.0, 46064.0, 46065.0, 46066.0, 46067.0, 46068.0],       [46069.0, 46070.0, 46071.0, 46072.0, 46073.0, 46074.0]],      [[46075.0, 46076.0, 46077.0, 46078.0, 46079.0, 46080.0],       [46081.0, 46082.0, 46083.0, 46084.0, 46085.0, 46086.0],       [46087.0, 46088.0, 46089.0, 46090.0, 46091.0, 46092.0],       [46093.0, 46094.0, 46095.0, 46096.0, 46097.0, 46098.0],       [46099.0, 46100.0, 46101.0, 46102.0, 46103.0, 46104.0],       [46105.0, 46106.0, 46107.0, 46108.0, 46109.0, 46110.0],       [46111.0, 46112.0, 46113.0, 46114.0, 46115.0, 46116.0]]],     [[[46117.0, 46118.0, 46119.0, 46120.0, 46121.0, 46122.0],       [46123.0, 46124.0, 46125.0, 46126.0, 46127.0, 46128.0],       [46129.0, 46130.0, 46131.0, 46132.0, 46133.0, 46134.0],       [46135.0, 46136.0, 46137.0, 46138.0, 46139.0, 46140.0],       [46141.0, 46142.0, 46143.0, 46144.0, 46145.0, 46146.0],       [46147.0, 46148.0, 46149.0, 46150.0, 46151.0, 46152.0],       [46153.0, 46154.0, 46155.0, 46156.0, 46157.0, 46158.0]],      [[46159.0, 46160.0, 46161.0, 46162.0, 46163.0, 46164.0],       [46165.0, 46166.0, 46167.0, 46168.0, 46169.0, 46170.0],       [46171.0, 46172.0, 46173.0, 46174.0, 46175.0, 46176.0],       [46177.0, 46178.0, 46179.0, 46180.0, 46181.0, 46182.0],       [46183.0, 46184.0, 46185.0, 46186.0, 46187.0, 46188.0],       [46189.0, 46190.0, 46191.0, 46192.0, 46193.0, 46194.0],       [46195.0, 46196.0, 46197.0, 46198.0, 46199.0, 46200.0]],      [[46201.0, 46202.0, 46203.0, 46204.0, 46205.0, 46206.0],       [46207.0, 46208.0, 46209.0, 46210.0, 46211.0, 46212.0],       [46213.0, 46214.0, 46215.0, 46216.0, 46217.0, 46218.0],       [46219.0, 46220.0, 46221.0, 46222.0, 46223.0, 46224.0],       [46225.0, 46226.0, 46227.0, 46228.0, 46229.0, 46230.0],       [46231.0, 46232.0, 46233.0, 46234.0, 46235.0, 46236.0],       [46237.0, 46238.0, 46239.0, 46240.0, 46241.0, 46242.0]],      [[46243.0, 46244.0, 46245.0, 46246.0, 46247.0, 46248.0],       [46249.0, 46250.0, 46251.0, 46252.0, 46253.0, 46254.0],       [46255.0, 46256.0, 46257.0, 46258.0, 46259.0, 46260.0],       [46261.0, 46262.0, 46263.0, 46264.0, 46265.0, 46266.0],       [46267.0, 46268.0, 46269.0, 46270.0, 46271.0, 46272.0],       [46273.0, 46274.0, 46275.0, 46276.0, 46277.0, 46278.0],       [46279.0, 46280.0, 46281.0, 46282.0, 46283.0, 46284.0]],      [[46285.0, 46286.0, 46287.0, 46288.0, 46289.0, 46290.0],       [46291.0, 46292.0, 46293.0, 46294.0, 46295.0, 46296.0],       [46297.0, 46298.0, 46299.0, 46300.0, 46301.0, 46302.0],       [46303.0, 46304.0, 46305.0, 46306.0, 46307.0, 46308.0],       [46309.0, 46310.0, 46311.0, 46312.0, 46313.0, 46314.0],       [46315.0, 46316.0, 46317.0, 46318.0, 46319.0, 46320.0],       [46321.0, 46322.0, 46323.0, 46324.0, 46325.0, 46326.0]],      [[46327.0, 46328.0, 46329.0, 46330.0, 46331.0, 46332.0],       [46333.0, 46334.0, 46335.0, 46336.0, 46337.0, 46338.0],       [46339.0, 46340.0, 46341.0, 46342.0, 46343.0, 46344.0],       [46345.0, 46346.0, 46347.0, 46348.0, 46349.0, 46350.0],       [46351.0, 46352.0, 46353.0, 46354.0, 46355.0, 46356.0],       [46357.0, 46358.0, 46359.0, 46360.0, 46361.0, 46362.0],       [46363.0, 46364.0, 46365.0, 46366.0, 46367.0, 46368.0]]]],    [[[[46369.0, 46370.0, 46371.0, 46372.0, 46373.0, 46374.0],       [46375.0, 46376.0, 46377.0, 46378.0, 46379.0, 46380.0],       [46381.0, 46382.0, 46383.0, 46384.0, 46385.0, 46386.0],       [46387.0, 46388.0, 46389.0, 46390.0, 46391.0, 46392.0],       [46393.0, 46394.0, 46395.0, 46396.0, 46397.0, 46398.0],       [46399.0, 46400.0, 46401.0, 46402.0, 46403.0, 46404.0],       [46405.0, 46406.0, 46407.0, 46408.0, 46409.0, 46410.0]],      [[46411.0, 46412.0, 46413.0, 46414.0, 46415.0, 46416.0],       [46417.0, 46418.0, 46419.0, 46420.0, 46421.0, 46422.0],       [46423.0, 46424.0, 46425.0, 46426.0, 46427.0, 46428.0],       [46429.0, 46430.0, 46431.0, 46432.0, 46433.0, 46434.0],       [46435.0, 46436.0, 46437.0, 46438.0, 46439.0, 46440.0],       [46441.0, 46442.0, 46443.0, 46444.0, 46445.0, 46446.0],       [46447.0, 46448.0, 46449.0, 46450.0, 46451.0, 46452.0]],      [[46453.0, 46454.0, 46455.0, 46456.0, 46457.0, 46458.0],       [46459.0, 46460.0, 46461.0, 46462.0, 46463.0, 46464.0],       [46465.0, 46466.0, 46467.0, 46468.0, 46469.0, 46470.0],       [46471.0, 46472.0, 46473.0, 46474.0, 46475.0, 46476.0],       [46477.0, 46478.0, 46479.0, 46480.0, 46481.0, 46482.0],       [46483.0, 46484.0, 46485.0, 46486.0, 46487.0, 46488.0],       [46489.0, 46490.0, 46491.0, 46492.0, 46493.0, 46494.0]],      [[46495.0, 46496.0, 46497.0, 46498.0, 46499.0, 46500.0],       [46501.0, 46502.0, 46503.0, 46504.0, 46505.0, 46506.0],       [46507.0, 46508.0, 46509.0, 46510.0, 46511.0, 46512.0],       [46513.0, 46514.0, 46515.0, 46516.0, 46517.0, 46518.0],       [46519.0, 46520.0, 46521.0, 46522.0, 46523.0, 46524.0],       [46525.0, 46526.0, 46527.0, 46528.0, 46529.0, 46530.0],       [46531.0, 46532.0, 46533.0, 46534.0, 46535.0, 46536.0]],      [[46537.0, 46538.0, 46539.0, 46540.0, 46541.0, 46542.0],       [46543.0, 46544.0, 46545.0, 46546.0, 46547.0, 46548.0],       [46549.0, 46550.0, 46551.0, 46552.0, 46553.0, 46554.0],       [46555.0, 46556.0, 46557.0, 46558.0, 46559.0, 46560.0],       [46561.0, 46562.0, 46563.0, 46564.0, 46565.0, 46566.0],       [46567.0, 46568.0, 46569.0, 46570.0, 46571.0, 46572.0],       [46573.0, 46574.0, 46575.0, 46576.0, 46577.0, 46578.0]],      [[46579.0, 46580.0, 46581.0, 46582.0, 46583.0, 46584.0],       [46585.0, 46586.0, 46587.0, 46588.0, 46589.0, 46590.0],       [46591.0, 46592.0, 46593.0, 46594.0, 46595.0, 46596.0],       [46597.0, 46598.0, 46599.0, 46600.0, 46601.0, 46602.0],       [46603.0, 46604.0, 46605.0, 46606.0, 46607.0, 46608.0],       [46609.0, 46610.0, 46611.0, 46612.0, 46613.0, 46614.0],       [46615.0, 46616.0, 46617.0, 46618.0, 46619.0, 46620.0]]],     [[[46621.0, 46622.0, 46623.0, 46624.0, 46625.0, 46626.0],       [46627.0, 46628.0, 46629.0, 46630.0, 46631.0, 46632.0],       [46633.0, 46634.0, 46635.0, 46636.0, 46637.0, 46638.0],       [46639.0, 46640.0, 46641.0, 46642.0, 46643.0, 46644.0],       [46645.0, 46646.0, 46647.0, 46648.0, 46649.0, 46650.0],       [46651.0, 46652.0, 46653.0, 46654.0, 46655.0, 46656.0],       [46657.0, 46658.0, 46659.0, 46660.0, 46661.0, 46662.0]],      [[46663.0, 46664.0, 46665.0, 46666.0, 46667.0, 46668.0],       [46669.0, 46670.0, 46671.0, 46672.0, 46673.0, 46674.0],       [46675.0, 46676.0, 46677.0, 46678.0, 46679.0, 46680.0],       [46681.0, 46682.0, 46683.0, 46684.0, 46685.0, 46686.0],       [46687.0, 46688.0, 46689.0, 46690.0, 46691.0, 46692.0],       [46693.0, 46694.0, 46695.0, 46696.0, 46697.0, 46698.0],       [46699.0, 46700.0, 46701.0, 46702.0, 46703.0, 46704.0]],      [[46705.0, 46706.0, 46707.0, 46708.0, 46709.0, 46710.0],       [46711.0, 46712.0, 46713.0, 46714.0, 46715.0, 46716.0],       [46717.0, 46718.0, 46719.0, 46720.0, 46721.0, 46722.0],       [46723.0, 46724.0, 46725.0, 46726.0, 46727.0, 46728.0],       [46729.0, 46730.0, 46731.0, 46732.0, 46733.0, 46734.0],       [46735.0, 46736.0, 46737.0, 46738.0, 46739.0, 46740.0],       [46741.0, 46742.0, 46743.0, 46744.0, 46745.0, 46746.0]],      [[46747.0, 46748.0, 46749.0, 46750.0, 46751.0, 46752.0],       [46753.0, 46754.0, 46755.0, 46756.0, 46757.0, 46758.0],       [46759.0, 46760.0, 46761.0, 46762.0, 46763.0, 46764.0],       [46765.0, 46766.0, 46767.0, 46768.0, 46769.0, 46770.0],       [46771.0, 46772.0, 46773.0, 46774.0, 46775.0, 46776.0],       [46777.0, 46778.0, 46779.0, 46780.0, 46781.0, 46782.0],       [46783.0, 46784.0, 46785.0, 46786.0, 46787.0, 46788.0]],      [[46789.0, 46790.0, 46791.0, 46792.0, 46793.0, 46794.0],       [46795.0, 46796.0, 46797.0, 46798.0, 46799.0, 46800.0],       [46801.0, 46802.0, 46803.0, 46804.0, 46805.0, 46806.0],       [46807.0, 46808.0, 46809.0, 46810.0, 46811.0, 46812.0],       [46813.0, 46814.0, 46815.0, 46816.0, 46817.0, 46818.0],       [46819.0, 46820.0, 46821.0, 46822.0, 46823.0, 46824.0],       [46825.0, 46826.0, 46827.0, 46828.0, 46829.0, 46830.0]],      [[46831.0, 46832.0, 46833.0, 46834.0, 46835.0, 46836.0],       [46837.0, 46838.0, 46839.0, 46840.0, 46841.0, 46842.0],       [46843.0, 46844.0, 46845.0, 46846.0, 46847.0, 46848.0],       [46849.0, 46850.0, 46851.0, 46852.0, 46853.0, 46854.0],       [46855.0, 46856.0, 46857.0, 46858.0, 46859.0, 46860.0],       [46861.0, 46862.0, 46863.0, 46864.0, 46865.0, 46866.0],       [46867.0, 46868.0, 46869.0, 46870.0, 46871.0, 46872.0]]],     [[[46873.0, 46874.0, 46875.0, 46876.0, 46877.0, 46878.0],       [46879.0, 46880.0, 46881.0, 46882.0, 46883.0, 46884.0],       [46885.0, 46886.0, 46887.0, 46888.0, 46889.0, 46890.0],       [46891.0, 46892.0, 46893.0, 46894.0, 46895.0, 46896.0],       [46897.0, 46898.0, 46899.0, 46900.0, 46901.0, 46902.0],       [46903.0, 46904.0, 46905.0, 46906.0, 46907.0, 46908.0],       [46909.0, 46910.0, 46911.0, 46912.0, 46913.0, 46914.0]],      [[46915.0, 46916.0, 46917.0, 46918.0, 46919.0, 46920.0],       [46921.0, 46922.0, 46923.0, 46924.0, 46925.0, 46926.0],       [46927.0, 46928.0, 46929.0, 46930.0, 46931.0, 46932.0],       [46933.0, 46934.0, 46935.0, 46936.0, 46937.0, 46938.0],       [46939.0, 46940.0, 46941.0, 46942.0, 46943.0, 46944.0],       [46945.0, 46946.0, 46947.0, 46948.0, 46949.0, 46950.0],       [46951.0, 46952.0, 46953.0, 46954.0, 46955.0, 46956.0]],      [[46957.0, 46958.0, 46959.0, 46960.0, 46961.0, 46962.0],       [46963.0, 46964.0, 46965.0, 46966.0, 46967.0, 46968.0],       [46969.0, 46970.0, 46971.0, 46972.0, 46973.0, 46974.0],       [46975.0, 46976.0, 46977.0, 46978.0, 46979.0, 46980.0],       [46981.0, 46982.0, 46983.0, 46984.0, 46985.0, 46986.0],       [46987.0, 46988.0, 46989.0, 46990.0, 46991.0, 46992.0],       [46993.0, 46994.0, 46995.0, 46996.0, 46997.0, 46998.0]],      [[46999.0, 47000.0, 47001.0, 47002.0, 47003.0, 47004.0],       [47005.0, 47006.0, 47007.0, 47008.0, 47009.0, 47010.0],       [47011.0, 47012.0, 47013.0, 47014.0, 47015.0, 47016.0],       [47017.0, 47018.0, 47019.0, 47020.0, 47021.0, 47022.0],       [47023.0, 47024.0, 47025.0, 47026.0, 47027.0, 47028.0],       [47029.0, 47030.0, 47031.0, 47032.0, 47033.0, 47034.0],       [47035.0, 47036.0, 47037.0, 47038.0, 47039.0, 47040.0]],      [[47041.0, 47042.0, 47043.0, 47044.0, 47045.0, 47046.0],       [47047.0, 47048.0, 47049.0, 47050.0, 47051.0, 47052.0],       [47053.0, 47054.0, 47055.0, 47056.0, 47057.0, 47058.0],       [47059.0, 47060.0, 47061.0, 47062.0, 47063.0, 47064.0],       [47065.0, 47066.0, 47067.0, 47068.0, 47069.0, 47070.0],       [47071.0, 47072.0, 47073.0, 47074.0, 47075.0, 47076.0],       [47077.0, 47078.0, 47079.0, 47080.0, 47081.0, 47082.0]],      [[47083.0, 47084.0, 47085.0, 47086.0, 47087.0, 47088.0],       [47089.0, 47090.0, 47091.0, 47092.0, 47093.0, 47094.0],       [47095.0, 47096.0, 47097.0, 47098.0, 47099.0, 47100.0],       [47101.0, 47102.0, 47103.0, 47104.0, 47105.0, 47106.0],       [47107.0, 47108.0, 47109.0, 47110.0, 47111.0, 47112.0],       [47113.0, 47114.0, 47115.0, 47116.0, 47117.0, 47118.0],       [47119.0, 47120.0, 47121.0, 47122.0, 47123.0, 47124.0]]],     [[[47125.0, 47126.0, 47127.0, 47128.0, 47129.0, 47130.0],       [47131.0, 47132.0, 47133.0, 47134.0, 47135.0, 47136.0],       [47137.0, 47138.0, 47139.0, 47140.0, 47141.0, 47142.0],       [47143.0, 47144.0, 47145.0, 47146.0, 47147.0, 47148.0],       [47149.0, 47150.0, 47151.0, 47152.0, 47153.0, 47154.0],       [47155.0, 47156.0, 47157.0, 47158.0, 47159.0, 47160.0],       [47161.0, 47162.0, 47163.0, 47164.0, 47165.0, 47166.0]],      [[47167.0, 47168.0, 47169.0, 47170.0, 47171.0, 47172.0],       [47173.0, 47174.0, 47175.0, 47176.0, 47177.0, 47178.0],       [47179.0, 47180.0, 47181.0, 47182.0, 47183.0, 47184.0],       [47185.0, 47186.0, 47187.0, 47188.0, 47189.0, 47190.0],       [47191.0, 47192.0, 47193.0, 47194.0, 47195.0, 47196.0],       [47197.0, 47198.0, 47199.0, 47200.0, 47201.0, 47202.0],       [47203.0, 47204.0, 47205.0, 47206.0, 47207.0, 47208.0]],      [[47209.0, 47210.0, 47211.0, 47212.0, 47213.0, 47214.0],       [47215.0, 47216.0, 47217.0, 47218.0, 47219.0, 47220.0],       [47221.0, 47222.0, 47223.0, 47224.0, 47225.0, 47226.0],       [47227.0, 47228.0, 47229.0, 47230.0, 47231.0, 47232.0],       [47233.0, 47234.0, 47235.0, 47236.0, 47237.0, 47238.0],       [47239.0, 47240.0, 47241.0, 47242.0, 47243.0, 47244.0],       [47245.0, 47246.0, 47247.0, 47248.0, 47249.0, 47250.0]],      [[47251.0, 47252.0, 47253.0, 47254.0, 47255.0, 47256.0],       [47257.0, 47258.0, 47259.0, 47260.0, 47261.0, 47262.0],       [47263.0, 47264.0, 47265.0, 47266.0, 47267.0, 47268.0],       [47269.0, 47270.0, 47271.0, 47272.0, 47273.0, 47274.0],       [47275.0, 47276.0, 47277.0, 47278.0, 47279.0, 47280.0],       [47281.0, 47282.0, 47283.0, 47284.0, 47285.0, 47286.0],       [47287.0, 47288.0, 47289.0, 47290.0, 47291.0, 47292.0]],      [[47293.0, 47294.0, 47295.0, 47296.0, 47297.0, 47298.0],       [47299.0, 47300.0, 47301.0, 47302.0, 47303.0, 47304.0],       [47305.0, 47306.0, 47307.0, 47308.0, 47309.0, 47310.0],       [47311.0, 47312.0, 47313.0, 47314.0, 47315.0, 47316.0],       [47317.0, 47318.0, 47319.0, 47320.0, 47321.0, 47322.0],       [47323.0, 47324.0, 47325.0, 47326.0, 47327.0, 47328.0],       [47329.0, 47330.0, 47331.0, 47332.0, 47333.0, 47334.0]],      [[47335.0, 47336.0, 47337.0, 47338.0, 47339.0, 47340.0],       [47341.0, 47342.0, 47343.0, 47344.0, 47345.0, 47346.0],       [47347.0, 47348.0, 47349.0, 47350.0, 47351.0, 47352.0],       [47353.0, 47354.0, 47355.0, 47356.0, 47357.0, 47358.0],       [47359.0, 47360.0, 47361.0, 47362.0, 47363.0, 47364.0],       [47365.0, 47366.0, 47367.0, 47368.0, 47369.0, 47370.0],       [47371.0, 47372.0, 47373.0, 47374.0, 47375.0, 47376.0]]]],    [[[[47377.0, 47378.0, 47379.0, 47380.0, 47381.0, 47382.0],       [47383.0, 47384.0, 47385.0, 47386.0, 47387.0, 47388.0],       [47389.0, 47390.0, 47391.0, 47392.0, 47393.0, 47394.0],       [47395.0, 47396.0, 47397.0, 47398.0, 47399.0, 47400.0],       [47401.0, 47402.0, 47403.0, 47404.0, 47405.0, 47406.0],       [47407.0, 47408.0, 47409.0, 47410.0, 47411.0, 47412.0],       [47413.0, 47414.0, 47415.0, 47416.0, 47417.0, 47418.0]],      [[47419.0, 47420.0, 47421.0, 47422.0, 47423.0, 47424.0],       [47425.0, 47426.0, 47427.0, 47428.0, 47429.0, 47430.0],       [47431.0, 47432.0, 47433.0, 47434.0, 47435.0, 47436.0],       [47437.0, 47438.0, 47439.0, 47440.0, 47441.0, 47442.0],       [47443.0, 47444.0, 47445.0, 47446.0, 47447.0, 47448.0],       [47449.0, 47450.0, 47451.0, 47452.0, 47453.0, 47454.0],       [47455.0, 47456.0, 47457.0, 47458.0, 47459.0, 47460.0]],      [[47461.0, 47462.0, 47463.0, 47464.0, 47465.0, 47466.0],       [47467.0, 47468.0, 47469.0, 47470.0, 47471.0, 47472.0],       [47473.0, 47474.0, 47475.0, 47476.0, 47477.0, 47478.0],       [47479.0, 47480.0, 47481.0, 47482.0, 47483.0, 47484.0],       [47485.0, 47486.0, 47487.0, 47488.0, 47489.0, 47490.0],       [47491.0, 47492.0, 47493.0, 47494.0, 47495.0, 47496.0],       [47497.0, 47498.0, 47499.0, 47500.0, 47501.0, 47502.0]],      [[47503.0, 47504.0, 47505.0, 47506.0, 47507.0, 47508.0],       [47509.0, 47510.0, 47511.0, 47512.0, 47513.0, 47514.0],       [47515.0, 47516.0, 47517.0, 47518.0, 47519.0, 47520.0],       [47521.0, 47522.0, 47523.0, 47524.0, 47525.0, 47526.0],       [47527.0, 47528.0, 47529.0, 47530.0, 47531.0, 47532.0],       [47533.0, 47534.0, 47535.0, 47536.0, 47537.0, 47538.0],       [47539.0, 47540.0, 47541.0, 47542.0, 47543.0, 47544.0]],      [[47545.0, 47546.0, 47547.0, 47548.0, 47549.0, 47550.0],       [47551.0, 47552.0, 47553.0, 47554.0, 47555.0, 47556.0],       [47557.0, 47558.0, 47559.0, 47560.0, 47561.0, 47562.0],       [47563.0, 47564.0, 47565.0, 47566.0, 47567.0, 47568.0],       [47569.0, 47570.0, 47571.0, 47572.0, 47573.0, 47574.0],       [47575.0, 47576.0, 47577.0, 47578.0, 47579.0, 47580.0],       [47581.0, 47582.0, 47583.0, 47584.0, 47585.0, 47586.0]],      [[47587.0, 47588.0, 47589.0, 47590.0, 47591.0, 47592.0],       [47593.0, 47594.0, 47595.0, 47596.0, 47597.0, 47598.0],       [47599.0, 47600.0, 47601.0, 47602.0, 47603.0, 47604.0],       [47605.0, 47606.0, 47607.0, 47608.0, 47609.0, 47610.0],       [47611.0, 47612.0, 47613.0, 47614.0, 47615.0, 47616.0],       [47617.0, 47618.0, 47619.0, 47620.0, 47621.0, 47622.0],       [47623.0, 47624.0, 47625.0, 47626.0, 47627.0, 47628.0]]],     [[[47629.0, 47630.0, 47631.0, 47632.0, 47633.0, 47634.0],       [47635.0, 47636.0, 47637.0, 47638.0, 47639.0, 47640.0],       [47641.0, 47642.0, 47643.0, 47644.0, 47645.0, 47646.0],       [47647.0, 47648.0, 47649.0, 47650.0, 47651.0, 47652.0],       [47653.0, 47654.0, 47655.0, 47656.0, 47657.0, 47658.0],       [47659.0, 47660.0, 47661.0, 47662.0, 47663.0, 47664.0],       [47665.0, 47666.0, 47667.0, 47668.0, 47669.0, 47670.0]],      [[47671.0, 47672.0, 47673.0, 47674.0, 47675.0, 47676.0],       [47677.0, 47678.0, 47679.0, 47680.0, 47681.0, 47682.0],       [47683.0, 47684.0, 47685.0, 47686.0, 47687.0, 47688.0],       [47689.0, 47690.0, 47691.0, 47692.0, 47693.0, 47694.0],       [47695.0, 47696.0, 47697.0, 47698.0, 47699.0, 47700.0],       [47701.0, 47702.0, 47703.0, 47704.0, 47705.0, 47706.0],       [47707.0, 47708.0, 47709.0, 47710.0, 47711.0, 47712.0]],      [[47713.0, 47714.0, 47715.0, 47716.0, 47717.0, 47718.0],       [47719.0, 47720.0, 47721.0, 47722.0, 47723.0, 47724.0],       [47725.0, 47726.0, 47727.0, 47728.0, 47729.0, 47730.0],       [47731.0, 47732.0, 47733.0, 47734.0, 47735.0, 47736.0],       [47737.0, 47738.0, 47739.0, 47740.0, 47741.0, 47742.0],       [47743.0, 47744.0, 47745.0, 47746.0, 47747.0, 47748.0],       [47749.0, 47750.0, 47751.0, 47752.0, 47753.0, 47754.0]],      [[47755.0, 47756.0, 47757.0, 47758.0, 47759.0, 47760.0],       [47761.0, 47762.0, 47763.0, 47764.0, 47765.0, 47766.0],       [47767.0, 47768.0, 47769.0, 47770.0, 47771.0, 47772.0],       [47773.0, 47774.0, 47775.0, 47776.0, 47777.0, 47778.0],       [47779.0, 47780.0, 47781.0, 47782.0, 47783.0, 47784.0],       [47785.0, 47786.0, 47787.0, 47788.0, 47789.0, 47790.0],       [47791.0, 47792.0, 47793.0, 47794.0, 47795.0, 47796.0]],      [[47797.0, 47798.0, 47799.0, 47800.0, 47801.0, 47802.0],       [47803.0, 47804.0, 47805.0, 47806.0, 47807.0, 47808.0],       [47809.0, 47810.0, 47811.0, 47812.0, 47813.0, 47814.0],       [47815.0, 47816.0, 47817.0, 47818.0, 47819.0, 47820.0],       [47821.0, 47822.0, 47823.0, 47824.0, 47825.0, 47826.0],       [47827.0, 47828.0, 47829.0, 47830.0, 47831.0, 47832.0],       [47833.0, 47834.0, 47835.0, 47836.0, 47837.0, 47838.0]],      [[47839.0, 47840.0, 47841.0, 47842.0, 47843.0, 47844.0],       [47845.0, 47846.0, 47847.0, 47848.0, 47849.0, 47850.0],       [47851.0, 47852.0, 47853.0, 47854.0, 47855.0, 47856.0],       [47857.0, 47858.0, 47859.0, 47860.0, 47861.0, 47862.0],       [47863.0, 47864.0, 47865.0, 47866.0, 47867.0, 47868.0],       [47869.0, 47870.0, 47871.0, 47872.0, 47873.0, 47874.0],       [47875.0, 47876.0, 47877.0, 47878.0, 47879.0, 47880.0]]],     [[[47881.0, 47882.0, 47883.0, 47884.0, 47885.0, 47886.0],       [47887.0, 47888.0, 47889.0, 47890.0, 47891.0, 47892.0],       [47893.0, 47894.0, 47895.0, 47896.0, 47897.0, 47898.0],       [47899.0, 47900.0, 47901.0, 47902.0, 47903.0, 47904.0],       [47905.0, 47906.0, 47907.0, 47908.0, 47909.0, 47910.0],       [47911.0, 47912.0, 47913.0, 47914.0, 47915.0, 47916.0],       [47917.0, 47918.0, 47919.0, 47920.0, 47921.0, 47922.0]],      [[47923.0, 47924.0, 47925.0, 47926.0, 47927.0, 47928.0],       [47929.0, 47930.0, 47931.0, 47932.0, 47933.0, 47934.0],       [47935.0, 47936.0, 47937.0, 47938.0, 47939.0, 47940.0],       [47941.0, 47942.0, 47943.0, 47944.0, 47945.0, 47946.0],       [47947.0, 47948.0, 47949.0, 47950.0, 47951.0, 47952.0],       [47953.0, 47954.0, 47955.0, 47956.0, 47957.0, 47958.0],       [47959.0, 47960.0, 47961.0, 47962.0, 47963.0, 47964.0]],      [[47965.0, 47966.0, 47967.0, 47968.0, 47969.0, 47970.0],       [47971.0, 47972.0, 47973.0, 47974.0, 47975.0, 47976.0],       [47977.0, 47978.0, 47979.0, 47980.0, 47981.0, 47982.0],       [47983.0, 47984.0, 47985.0, 47986.0, 47987.0, 47988.0],       [47989.0, 47990.0, 47991.0, 47992.0, 47993.0, 47994.0],       [47995.0, 47996.0, 47997.0, 47998.0, 47999.0, 48000.0],       [48001.0, 48002.0, 48003.0, 48004.0, 48005.0, 48006.0]],      [[48007.0, 48008.0, 48009.0, 48010.0, 48011.0, 48012.0],       [48013.0, 48014.0, 48015.0, 48016.0, 48017.0, 48018.0],       [48019.0, 48020.0, 48021.0, 48022.0, 48023.0, 48024.0],       [48025.0, 48026.0, 48027.0, 48028.0, 48029.0, 48030.0],       [48031.0, 48032.0, 48033.0, 48034.0, 48035.0, 48036.0],       [48037.0, 48038.0, 48039.0, 48040.0, 48041.0, 48042.0],       [48043.0, 48044.0, 48045.0, 48046.0, 48047.0, 48048.0]],      [[48049.0, 48050.0, 48051.0, 48052.0, 48053.0, 48054.0],       [48055.0, 48056.0, 48057.0, 48058.0, 48059.0, 48060.0],       [48061.0, 48062.0, 48063.0, 48064.0, 48065.0, 48066.0],       [48067.0, 48068.0, 48069.0, 48070.0, 48071.0, 48072.0],       [48073.0, 48074.0, 48075.0, 48076.0, 48077.0, 48078.0],       [48079.0, 48080.0, 48081.0, 48082.0, 48083.0, 48084.0],       [48085.0, 48086.0, 48087.0, 48088.0, 48089.0, 48090.0]],      [[48091.0, 48092.0, 48093.0, 48094.0, 48095.0, 48096.0],       [48097.0, 48098.0, 48099.0, 48100.0, 48101.0, 48102.0],       [48103.0, 48104.0, 48105.0, 48106.0, 48107.0, 48108.0],       [48109.0, 48110.0, 48111.0, 48112.0, 48113.0, 48114.0],       [48115.0, 48116.0, 48117.0, 48118.0, 48119.0, 48120.0],       [48121.0, 48122.0, 48123.0, 48124.0, 48125.0, 48126.0],       [48127.0, 48128.0, 48129.0, 48130.0, 48131.0, 48132.0]]],     [[[48133.0, 48134.0, 48135.0, 48136.0, 48137.0, 48138.0],       [48139.0, 48140.0, 48141.0, 48142.0, 48143.0, 48144.0],       [48145.0, 48146.0, 48147.0, 48148.0, 48149.0, 48150.0],       [48151.0, 48152.0, 48153.0, 48154.0, 48155.0, 48156.0],       [48157.0, 48158.0, 48159.0, 48160.0, 48161.0, 48162.0],       [48163.0, 48164.0, 48165.0, 48166.0, 48167.0, 48168.0],       [48169.0, 48170.0, 48171.0, 48172.0, 48173.0, 48174.0]],      [[48175.0, 48176.0, 48177.0, 48178.0, 48179.0, 48180.0],       [48181.0, 48182.0, 48183.0, 48184.0, 48185.0, 48186.0],       [48187.0, 48188.0, 48189.0, 48190.0, 48191.0, 48192.0],       [48193.0, 48194.0, 48195.0, 48196.0, 48197.0, 48198.0],       [48199.0, 48200.0, 48201.0, 48202.0, 48203.0, 48204.0],       [48205.0, 48206.0, 48207.0, 48208.0, 48209.0, 48210.0],       [48211.0, 48212.0, 48213.0, 48214.0, 48215.0, 48216.0]],      [[48217.0, 48218.0, 48219.0, 48220.0, 48221.0, 48222.0],       [48223.0, 48224.0, 48225.0, 48226.0, 48227.0, 48228.0],       [48229.0, 48230.0, 48231.0, 48232.0, 48233.0, 48234.0],       [48235.0, 48236.0, 48237.0, 48238.0, 48239.0, 48240.0],       [48241.0, 48242.0, 48243.0, 48244.0, 48245.0, 48246.0],       [48247.0, 48248.0, 48249.0, 48250.0, 48251.0, 48252.0],       [48253.0, 48254.0, 48255.0, 48256.0, 48257.0, 48258.0]],      [[48259.0, 48260.0, 48261.0, 48262.0, 48263.0, 48264.0],       [48265.0, 48266.0, 48267.0, 48268.0, 48269.0, 48270.0],       [48271.0, 48272.0, 48273.0, 48274.0, 48275.0, 48276.0],       [48277.0, 48278.0, 48279.0, 48280.0, 48281.0, 48282.0],       [48283.0, 48284.0, 48285.0, 48286.0, 48287.0, 48288.0],       [48289.0, 48290.0, 48291.0, 48292.0, 48293.0, 48294.0],       [48295.0, 48296.0, 48297.0, 48298.0, 48299.0, 48300.0]],      [[48301.0, 48302.0, 48303.0, 48304.0, 48305.0, 48306.0],       [48307.0, 48308.0, 48309.0, 48310.0, 48311.0, 48312.0],       [48313.0, 48314.0, 48315.0, 48316.0, 48317.0, 48318.0],       [48319.0, 48320.0, 48321.0, 48322.0, 48323.0, 48324.0],       [48325.0, 48326.0, 48327.0, 48328.0, 48329.0, 48330.0],       [48331.0, 48332.0, 48333.0, 48334.0, 48335.0, 48336.0],       [48337.0, 48338.0, 48339.0, 48340.0, 48341.0, 48342.0]],      [[48343.0, 48344.0, 48345.0, 48346.0, 48347.0, 48348.0],       [48349.0, 48350.0, 48351.0, 48352.0, 48353.0, 48354.0],       [48355.0, 48356.0, 48357.0, 48358.0, 48359.0, 48360.0],       [48361.0, 48362.0, 48363.0, 48364.0, 48365.0, 48366.0],       [48367.0, 48368.0, 48369.0, 48370.0, 48371.0, 48372.0],       [48373.0, 48374.0, 48375.0, 48376.0, 48377.0, 48378.0],       [48379.0, 48380.0, 48381.0, 48382.0, 48383.0, 48384.0]]]]],   [[[[[48385.0, 48386.0, 48387.0, 48388.0, 48389.0, 48390.0],       [48391.0, 48392.0, 48393.0, 48394.0, 48395.0, 48396.0],       [48397.0, 48398.0, 48399.0, 48400.0, 48401.0, 48402.0],       [48403.0, 48404.0, 48405.0, 48406.0, 48407.0, 48408.0],       [48409.0, 48410.0, 48411.0, 48412.0, 48413.0, 48414.0],       [48415.0, 48416.0, 48417.0, 48418.0, 48419.0, 48420.0],       [48421.0, 48422.0, 48423.0, 48424.0, 48425.0, 48426.0]],      [[48427.0, 48428.0, 48429.0, 48430.0, 48431.0, 48432.0],       [48433.0, 48434.0, 48435.0, 48436.0, 48437.0, 48438.0],       [48439.0, 48440.0, 48441.0, 48442.0, 48443.0, 48444.0],       [48445.0, 48446.0, 48447.0, 48448.0, 48449.0, 48450.0],       [48451.0, 48452.0, 48453.0, 48454.0, 48455.0, 48456.0],       [48457.0, 48458.0, 48459.0, 48460.0, 48461.0, 48462.0],       [48463.0, 48464.0, 48465.0, 48466.0, 48467.0, 48468.0]],      [[48469.0, 48470.0, 48471.0, 48472.0, 48473.0, 48474.0],       [48475.0, 48476.0, 48477.0, 48478.0, 48479.0, 48480.0],       [48481.0, 48482.0, 48483.0, 48484.0, 48485.0, 48486.0],       [48487.0, 48488.0, 48489.0, 48490.0, 48491.0, 48492.0],       [48493.0, 48494.0, 48495.0, 48496.0, 48497.0, 48498.0],       [48499.0, 48500.0, 48501.0, 48502.0, 48503.0, 48504.0],       [48505.0, 48506.0, 48507.0, 48508.0, 48509.0, 48510.0]],      [[48511.0, 48512.0, 48513.0, 48514.0, 48515.0, 48516.0],       [48517.0, 48518.0, 48519.0, 48520.0, 48521.0, 48522.0],       [48523.0, 48524.0, 48525.0, 48526.0, 48527.0, 48528.0],       [48529.0, 48530.0, 48531.0, 48532.0, 48533.0, 48534.0],       [48535.0, 48536.0, 48537.0, 48538.0, 48539.0, 48540.0],       [48541.0, 48542.0, 48543.0, 48544.0, 48545.0, 48546.0],       [48547.0, 48548.0, 48549.0, 48550.0, 48551.0, 48552.0]],      [[48553.0, 48554.0, 48555.0, 48556.0, 48557.0, 48558.0],       [48559.0, 48560.0, 48561.0, 48562.0, 48563.0, 48564.0],       [48565.0, 48566.0, 48567.0, 48568.0, 48569.0, 48570.0],       [48571.0, 48572.0, 48573.0, 48574.0, 48575.0, 48576.0],       [48577.0, 48578.0, 48579.0, 48580.0, 48581.0, 48582.0],       [48583.0, 48584.0, 48585.0, 48586.0, 48587.0, 48588.0],       [48589.0, 48590.0, 48591.0, 48592.0, 48593.0, 48594.0]],      [[48595.0, 48596.0, 48597.0, 48598.0, 48599.0, 48600.0],       [48601.0, 48602.0, 48603.0, 48604.0, 48605.0, 48606.0],       [48607.0, 48608.0, 48609.0, 48610.0, 48611.0, 48612.0],       [48613.0, 48614.0, 48615.0, 48616.0, 48617.0, 48618.0],       [48619.0, 48620.0, 48621.0, 48622.0, 48623.0, 48624.0],       [48625.0, 48626.0, 48627.0, 48628.0, 48629.0, 48630.0],       [48631.0, 48632.0, 48633.0, 48634.0, 48635.0, 48636.0]]],     [[[48637.0, 48638.0, 48639.0, 48640.0, 48641.0, 48642.0],       [48643.0, 48644.0, 48645.0, 48646.0, 48647.0, 48648.0],       [48649.0, 48650.0, 48651.0, 48652.0, 48653.0, 48654.0],       [48655.0, 48656.0, 48657.0, 48658.0, 48659.0, 48660.0],       [48661.0, 48662.0, 48663.0, 48664.0, 48665.0, 48666.0],       [48667.0, 48668.0, 48669.0, 48670.0, 48671.0, 48672.0],       [48673.0, 48674.0, 48675.0, 48676.0, 48677.0, 48678.0]],      [[48679.0, 48680.0, 48681.0, 48682.0, 48683.0, 48684.0],       [48685.0, 48686.0, 48687.0, 48688.0, 48689.0, 48690.0],       [48691.0, 48692.0, 48693.0, 48694.0, 48695.0, 48696.0],       [48697.0, 48698.0, 48699.0, 48700.0, 48701.0, 48702.0],       [48703.0, 48704.0, 48705.0, 48706.0, 48707.0, 48708.0],       [48709.0, 48710.0, 48711.0, 48712.0, 48713.0, 48714.0],       [48715.0, 48716.0, 48717.0, 48718.0, 48719.0, 48720.0]],      [[48721.0, 48722.0, 48723.0, 48724.0, 48725.0, 48726.0],       [48727.0, 48728.0, 48729.0, 48730.0, 48731.0, 48732.0],       [48733.0, 48734.0, 48735.0, 48736.0, 48737.0, 48738.0],       [48739.0, 48740.0, 48741.0, 48742.0, 48743.0, 48744.0],       [48745.0, 48746.0, 48747.0, 48748.0, 48749.0, 48750.0],       [48751.0, 48752.0, 48753.0, 48754.0, 48755.0, 48756.0],       [48757.0, 48758.0, 48759.0, 48760.0, 48761.0, 48762.0]],      [[48763.0, 48764.0, 48765.0, 48766.0, 48767.0, 48768.0],       [48769.0, 48770.0, 48771.0, 48772.0, 48773.0, 48774.0],       [48775.0, 48776.0, 48777.0, 48778.0, 48779.0, 48780.0],       [48781.0, 48782.0, 48783.0, 48784.0, 48785.0, 48786.0],       [48787.0, 48788.0, 48789.0, 48790.0, 48791.0, 48792.0],       [48793.0, 48794.0, 48795.0, 48796.0, 48797.0, 48798.0],       [48799.0, 48800.0, 48801.0, 48802.0, 48803.0, 48804.0]],      [[48805.0, 48806.0, 48807.0, 48808.0, 48809.0, 48810.0],       [48811.0, 48812.0, 48813.0, 48814.0, 48815.0, 48816.0],       [48817.0, 48818.0, 48819.0, 48820.0, 48821.0, 48822.0],       [48823.0, 48824.0, 48825.0, 48826.0, 48827.0, 48828.0],       [48829.0, 48830.0, 48831.0, 48832.0, 48833.0, 48834.0],       [48835.0, 48836.0, 48837.0, 48838.0, 48839.0, 48840.0],       [48841.0, 48842.0, 48843.0, 48844.0, 48845.0, 48846.0]],      [[48847.0, 48848.0, 48849.0, 48850.0, 48851.0, 48852.0],       [48853.0, 48854.0, 48855.0, 48856.0, 48857.0, 48858.0],       [48859.0, 48860.0, 48861.0, 48862.0, 48863.0, 48864.0],       [48865.0, 48866.0, 48867.0, 48868.0, 48869.0, 48870.0],       [48871.0, 48872.0, 48873.0, 48874.0, 48875.0, 48876.0],       [48877.0, 48878.0, 48879.0, 48880.0, 48881.0, 48882.0],       [48883.0, 48884.0, 48885.0, 48886.0, 48887.0, 48888.0]]],     [[[48889.0, 48890.0, 48891.0, 48892.0, 48893.0, 48894.0],       [48895.0, 48896.0, 48897.0, 48898.0, 48899.0, 48900.0],       [48901.0, 48902.0, 48903.0, 48904.0, 48905.0, 48906.0],       [48907.0, 48908.0, 48909.0, 48910.0, 48911.0, 48912.0],       [48913.0, 48914.0, 48915.0, 48916.0, 48917.0, 48918.0],       [48919.0, 48920.0, 48921.0, 48922.0, 48923.0, 48924.0],       [48925.0, 48926.0, 48927.0, 48928.0, 48929.0, 48930.0]],      [[48931.0, 48932.0, 48933.0, 48934.0, 48935.0, 48936.0],       [48937.0, 48938.0, 48939.0, 48940.0, 48941.0, 48942.0],       [48943.0, 48944.0, 48945.0, 48946.0, 48947.0, 48948.0],       [48949.0, 48950.0, 48951.0, 48952.0, 48953.0, 48954.0],       [48955.0, 48956.0, 48957.0, 48958.0, 48959.0, 48960.0],       [48961.0, 48962.0, 48963.0, 48964.0, 48965.0, 48966.0],       [48967.0, 48968.0, 48969.0, 48970.0, 48971.0, 48972.0]],      [[48973.0, 48974.0, 48975.0, 48976.0, 48977.0, 48978.0],       [48979.0, 48980.0, 48981.0, 48982.0, 48983.0, 48984.0],       [48985.0, 48986.0, 48987.0, 48988.0, 48989.0, 48990.0],       [48991.0, 48992.0, 48993.0, 48994.0, 48995.0, 48996.0],       [48997.0, 48998.0, 48999.0, 49000.0, 49001.0, 49002.0],       [49003.0, 49004.0, 49005.0, 49006.0, 49007.0, 49008.0],       [49009.0, 49010.0, 49011.0, 49012.0, 49013.0, 49014.0]],      [[49015.0, 49016.0, 49017.0, 49018.0, 49019.0, 49020.0],       [49021.0, 49022.0, 49023.0, 49024.0, 49025.0, 49026.0],       [49027.0, 49028.0, 49029.0, 49030.0, 49031.0, 49032.0],       [49033.0, 49034.0, 49035.0, 49036.0, 49037.0, 49038.0],       [49039.0, 49040.0, 49041.0, 49042.0, 49043.0, 49044.0],       [49045.0, 49046.0, 49047.0, 49048.0, 49049.0, 49050.0],       [49051.0, 49052.0, 49053.0, 49054.0, 49055.0, 49056.0]],      [[49057.0, 49058.0, 49059.0, 49060.0, 49061.0, 49062.0],       [49063.0, 49064.0, 49065.0, 49066.0, 49067.0, 49068.0],       [49069.0, 49070.0, 49071.0, 49072.0, 49073.0, 49074.0],       [49075.0, 49076.0, 49077.0, 49078.0, 49079.0, 49080.0],       [49081.0, 49082.0, 49083.0, 49084.0, 49085.0, 49086.0],       [49087.0, 49088.0, 49089.0, 49090.0, 49091.0, 49092.0],       [49093.0, 49094.0, 49095.0, 49096.0, 49097.0, 49098.0]],      [[49099.0, 49100.0, 49101.0, 49102.0, 49103.0, 49104.0],       [49105.0, 49106.0, 49107.0, 49108.0, 49109.0, 49110.0],       [49111.0, 49112.0, 49113.0, 49114.0, 49115.0, 49116.0],       [49117.0, 49118.0, 49119.0, 49120.0, 49121.0, 49122.0],       [49123.0, 49124.0, 49125.0, 49126.0, 49127.0, 49128.0],       [49129.0, 49130.0, 49131.0, 49132.0, 49133.0, 49134.0],       [49135.0, 49136.0, 49137.0, 49138.0, 49139.0, 49140.0]]],     [[[49141.0, 49142.0, 49143.0, 49144.0, 49145.0, 49146.0],       [49147.0, 49148.0, 49149.0, 49150.0, 49151.0, 49152.0],       [49153.0, 49154.0, 49155.0, 49156.0, 49157.0, 49158.0],       [49159.0, 49160.0, 49161.0, 49162.0, 49163.0, 49164.0],       [49165.0, 49166.0, 49167.0, 49168.0, 49169.0, 49170.0],       [49171.0, 49172.0, 49173.0, 49174.0, 49175.0, 49176.0],       [49177.0, 49178.0, 49179.0, 49180.0, 49181.0, 49182.0]],      [[49183.0, 49184.0, 49185.0, 49186.0, 49187.0, 49188.0],       [49189.0, 49190.0, 49191.0, 49192.0, 49193.0, 49194.0],       [49195.0, 49196.0, 49197.0, 49198.0, 49199.0, 49200.0],       [49201.0, 49202.0, 49203.0, 49204.0, 49205.0, 49206.0],       [49207.0, 49208.0, 49209.0, 49210.0, 49211.0, 49212.0],       [49213.0, 49214.0, 49215.0, 49216.0, 49217.0, 49218.0],       [49219.0, 49220.0, 49221.0, 49222.0, 49223.0, 49224.0]],      [[49225.0, 49226.0, 49227.0, 49228.0, 49229.0, 49230.0],       [49231.0, 49232.0, 49233.0, 49234.0, 49235.0, 49236.0],       [49237.0, 49238.0, 49239.0, 49240.0, 49241.0, 49242.0],       [49243.0, 49244.0, 49245.0, 49246.0, 49247.0, 49248.0],       [49249.0, 49250.0, 49251.0, 49252.0, 49253.0, 49254.0],       [49255.0, 49256.0, 49257.0, 49258.0, 49259.0, 49260.0],       [49261.0, 49262.0, 49263.0, 49264.0, 49265.0, 49266.0]],      [[49267.0, 49268.0, 49269.0, 49270.0, 49271.0, 49272.0],       [49273.0, 49274.0, 49275.0, 49276.0, 49277.0, 49278.0],       [49279.0, 49280.0, 49281.0, 49282.0, 49283.0, 49284.0],       [49285.0, 49286.0, 49287.0, 49288.0, 49289.0, 49290.0],       [49291.0, 49292.0, 49293.0, 49294.0, 49295.0, 49296.0],       [49297.0, 49298.0, 49299.0, 49300.0, 49301.0, 49302.0],       [49303.0, 49304.0, 49305.0, 49306.0, 49307.0, 49308.0]],      [[49309.0, 49310.0, 49311.0, 49312.0, 49313.0, 49314.0],       [49315.0, 49316.0, 49317.0, 49318.0, 49319.0, 49320.0],       [49321.0, 49322.0, 49323.0, 49324.0, 49325.0, 49326.0],       [49327.0, 49328.0, 49329.0, 49330.0, 49331.0, 49332.0],       [49333.0, 49334.0, 49335.0, 49336.0, 49337.0, 49338.0],       [49339.0, 49340.0, 49341.0, 49342.0, 49343.0, 49344.0],       [49345.0, 49346.0, 49347.0, 49348.0, 49349.0, 49350.0]],      [[49351.0, 49352.0, 49353.0, 49354.0, 49355.0, 49356.0],       [49357.0, 49358.0, 49359.0, 49360.0, 49361.0, 49362.0],       [49363.0, 49364.0, 49365.0, 49366.0, 49367.0, 49368.0],       [49369.0, 49370.0, 49371.0, 49372.0, 49373.0, 49374.0],       [49375.0, 49376.0, 49377.0, 49378.0, 49379.0, 49380.0],       [49381.0, 49382.0, 49383.0, 49384.0, 49385.0, 49386.0],       [49387.0, 49388.0, 49389.0, 49390.0, 49391.0, 49392.0]]]],    [[[[49393.0, 49394.0, 49395.0, 49396.0, 49397.0, 49398.0],       [49399.0, 49400.0, 49401.0, 49402.0, 49403.0, 49404.0],       [49405.0, 49406.0, 49407.0, 49408.0, 49409.0, 49410.0],       [49411.0, 49412.0, 49413.0, 49414.0, 49415.0, 49416.0],       [49417.0, 49418.0, 49419.0, 49420.0, 49421.0, 49422.0],       [49423.0, 49424.0, 49425.0, 49426.0, 49427.0, 49428.0],       [49429.0, 49430.0, 49431.0, 49432.0, 49433.0, 49434.0]],      [[49435.0, 49436.0, 49437.0, 49438.0, 49439.0, 49440.0],       [49441.0, 49442.0, 49443.0, 49444.0, 49445.0, 49446.0],       [49447.0, 49448.0, 49449.0, 49450.0, 49451.0, 49452.0],       [49453.0, 49454.0, 49455.0, 49456.0, 49457.0, 49458.0],       [49459.0, 49460.0, 49461.0, 49462.0, 49463.0, 49464.0],       [49465.0, 49466.0, 49467.0, 49468.0, 49469.0, 49470.0],       [49471.0, 49472.0, 49473.0, 49474.0, 49475.0, 49476.0]],      [[49477.0, 49478.0, 49479.0, 49480.0, 49481.0, 49482.0],       [49483.0, 49484.0, 49485.0, 49486.0, 49487.0, 49488.0],       [49489.0, 49490.0, 49491.0, 49492.0, 49493.0, 49494.0],       [49495.0, 49496.0, 49497.0, 49498.0, 49499.0, 49500.0],       [49501.0, 49502.0, 49503.0, 49504.0, 49505.0, 49506.0],       [49507.0, 49508.0, 49509.0, 49510.0, 49511.0, 49512.0],       [49513.0, 49514.0, 49515.0, 49516.0, 49517.0, 49518.0]],      [[49519.0, 49520.0, 49521.0, 49522.0, 49523.0, 49524.0],       [49525.0, 49526.0, 49527.0, 49528.0, 49529.0, 49530.0],       [49531.0, 49532.0, 49533.0, 49534.0, 49535.0, 49536.0],       [49537.0, 49538.0, 49539.0, 49540.0, 49541.0, 49542.0],       [49543.0, 49544.0, 49545.0, 49546.0, 49547.0, 49548.0],       [49549.0, 49550.0, 49551.0, 49552.0, 49553.0, 49554.0],       [49555.0, 49556.0, 49557.0, 49558.0, 49559.0, 49560.0]],      [[49561.0, 49562.0, 49563.0, 49564.0, 49565.0, 49566.0],       [49567.0, 49568.0, 49569.0, 49570.0, 49571.0, 49572.0],       [49573.0, 49574.0, 49575.0, 49576.0, 49577.0, 49578.0],       [49579.0, 49580.0, 49581.0, 49582.0, 49583.0, 49584.0],       [49585.0, 49586.0, 49587.0, 49588.0, 49589.0, 49590.0],       [49591.0, 49592.0, 49593.0, 49594.0, 49595.0, 49596.0],       [49597.0, 49598.0, 49599.0, 49600.0, 49601.0, 49602.0]],      [[49603.0, 49604.0, 49605.0, 49606.0, 49607.0, 49608.0],       [49609.0, 49610.0, 49611.0, 49612.0, 49613.0, 49614.0],       [49615.0, 49616.0, 49617.0, 49618.0, 49619.0, 49620.0],       [49621.0, 49622.0, 49623.0, 49624.0, 49625.0, 49626.0],       [49627.0, 49628.0, 49629.0, 49630.0, 49631.0, 49632.0],       [49633.0, 49634.0, 49635.0, 49636.0, 49637.0, 49638.0],       [49639.0, 49640.0, 49641.0, 49642.0, 49643.0, 49644.0]]],     [[[49645.0, 49646.0, 49647.0, 49648.0, 49649.0, 49650.0],       [49651.0, 49652.0, 49653.0, 49654.0, 49655.0, 49656.0],       [49657.0, 49658.0, 49659.0, 49660.0, 49661.0, 49662.0],       [49663.0, 49664.0, 49665.0, 49666.0, 49667.0, 49668.0],       [49669.0, 49670.0, 49671.0, 49672.0, 49673.0, 49674.0],       [49675.0, 49676.0, 49677.0, 49678.0, 49679.0, 49680.0],       [49681.0, 49682.0, 49683.0, 49684.0, 49685.0, 49686.0]],      [[49687.0, 49688.0, 49689.0, 49690.0, 49691.0, 49692.0],       [49693.0, 49694.0, 49695.0, 49696.0, 49697.0, 49698.0],       [49699.0, 49700.0, 49701.0, 49702.0, 49703.0, 49704.0],       [49705.0, 49706.0, 49707.0, 49708.0, 49709.0, 49710.0],       [49711.0, 49712.0, 49713.0, 49714.0, 49715.0, 49716.0],       [49717.0, 49718.0, 49719.0, 49720.0, 49721.0, 49722.0],       [49723.0, 49724.0, 49725.0, 49726.0, 49727.0, 49728.0]],      [[49729.0, 49730.0, 49731.0, 49732.0, 49733.0, 49734.0],       [49735.0, 49736.0, 49737.0, 49738.0, 49739.0, 49740.0],       [49741.0, 49742.0, 49743.0, 49744.0, 49745.0, 49746.0],       [49747.0, 49748.0, 49749.0, 49750.0, 49751.0, 49752.0],       [49753.0, 49754.0, 49755.0, 49756.0, 49757.0, 49758.0],       [49759.0, 49760.0, 49761.0, 49762.0, 49763.0, 49764.0],       [49765.0, 49766.0, 49767.0, 49768.0, 49769.0, 49770.0]],      [[49771.0, 49772.0, 49773.0, 49774.0, 49775.0, 49776.0],       [49777.0, 49778.0, 49779.0, 49780.0, 49781.0, 49782.0],       [49783.0, 49784.0, 49785.0, 49786.0, 49787.0, 49788.0],       [49789.0, 49790.0, 49791.0, 49792.0, 49793.0, 49794.0],       [49795.0, 49796.0, 49797.0, 49798.0, 49799.0, 49800.0],       [49801.0, 49802.0, 49803.0, 49804.0, 49805.0, 49806.0],       [49807.0, 49808.0, 49809.0, 49810.0, 49811.0, 49812.0]],      [[49813.0, 49814.0, 49815.0, 49816.0, 49817.0, 49818.0],       [49819.0, 49820.0, 49821.0, 49822.0, 49823.0, 49824.0],       [49825.0, 49826.0, 49827.0, 49828.0, 49829.0, 49830.0],       [49831.0, 49832.0, 49833.0, 49834.0, 49835.0, 49836.0],       [49837.0, 49838.0, 49839.0, 49840.0, 49841.0, 49842.0],       [49843.0, 49844.0, 49845.0, 49846.0, 49847.0, 49848.0],       [49849.0, 49850.0, 49851.0, 49852.0, 49853.0, 49854.0]],      [[49855.0, 49856.0, 49857.0, 49858.0, 49859.0, 49860.0],       [49861.0, 49862.0, 49863.0, 49864.0, 49865.0, 49866.0],       [49867.0, 49868.0, 49869.0, 49870.0, 49871.0, 49872.0],       [49873.0, 49874.0, 49875.0, 49876.0, 49877.0, 49878.0],       [49879.0, 49880.0, 49881.0, 49882.0, 49883.0, 49884.0],       [49885.0, 49886.0, 49887.0, 49888.0, 49889.0, 49890.0],       [49891.0, 49892.0, 49893.0, 49894.0, 49895.0, 49896.0]]],     [[[49897.0, 49898.0, 49899.0, 49900.0, 49901.0, 49902.0],       [49903.0, 49904.0, 49905.0, 49906.0, 49907.0, 49908.0],       [49909.0, 49910.0, 49911.0, 49912.0, 49913.0, 49914.0],       [49915.0, 49916.0, 49917.0, 49918.0, 49919.0, 49920.0],       [49921.0, 49922.0, 49923.0, 49924.0, 49925.0, 49926.0],       [49927.0, 49928.0, 49929.0, 49930.0, 49931.0, 49932.0],       [49933.0, 49934.0, 49935.0, 49936.0, 49937.0, 49938.0]],      [[49939.0, 49940.0, 49941.0, 49942.0, 49943.0, 49944.0],       [49945.0, 49946.0, 49947.0, 49948.0, 49949.0, 49950.0],       [49951.0, 49952.0, 49953.0, 49954.0, 49955.0, 49956.0],       [49957.0, 49958.0, 49959.0, 49960.0, 49961.0, 49962.0],       [49963.0, 49964.0, 49965.0, 49966.0, 49967.0, 49968.0],       [49969.0, 49970.0, 49971.0, 49972.0, 49973.0, 49974.0],       [49975.0, 49976.0, 49977.0, 49978.0, 49979.0, 49980.0]],      [[49981.0, 49982.0, 49983.0, 49984.0, 49985.0, 49986.0],       [49987.0, 49988.0, 49989.0, 49990.0, 49991.0, 49992.0],       [49993.0, 49994.0, 49995.0, 49996.0, 49997.0, 49998.0],       [49999.0, 50000.0, 50001.0, 50002.0, 50003.0, 50004.0],       [50005.0, 50006.0, 50007.0, 50008.0, 50009.0, 50010.0],       [50011.0, 50012.0, 50013.0, 50014.0, 50015.0, 50016.0],       [50017.0, 50018.0, 50019.0, 50020.0, 50021.0, 50022.0]],      [[50023.0, 50024.0, 50025.0, 50026.0, 50027.0, 50028.0],       [50029.0, 50030.0, 50031.0, 50032.0, 50033.0, 50034.0],       [50035.0, 50036.0, 50037.0, 50038.0, 50039.0, 50040.0],       [50041.0, 50042.0, 50043.0, 50044.0, 50045.0, 50046.0],       [50047.0, 50048.0, 50049.0, 50050.0, 50051.0, 50052.0],       [50053.0, 50054.0, 50055.0, 50056.0, 50057.0, 50058.0],       [50059.0, 50060.0, 50061.0, 50062.0, 50063.0, 50064.0]],      [[50065.0, 50066.0, 50067.0, 50068.0, 50069.0, 50070.0],       [50071.0, 50072.0, 50073.0, 50074.0, 50075.0, 50076.0],       [50077.0, 50078.0, 50079.0, 50080.0, 50081.0, 50082.0],       [50083.0, 50084.0, 50085.0, 50086.0, 50087.0, 50088.0],       [50089.0, 50090.0, 50091.0, 50092.0, 50093.0, 50094.0],       [50095.0, 50096.0, 50097.0, 50098.0, 50099.0, 50100.0],       [50101.0, 50102.0, 50103.0, 50104.0, 50105.0, 50106.0]],      [[50107.0, 50108.0, 50109.0, 50110.0, 50111.0, 50112.0],       [50113.0, 50114.0, 50115.0, 50116.0, 50117.0, 50118.0],       [50119.0, 50120.0, 50121.0, 50122.0, 50123.0, 50124.0],       [50125.0, 50126.0, 50127.0, 50128.0, 50129.0, 50130.0],       [50131.0, 50132.0, 50133.0, 50134.0, 50135.0, 50136.0],       [50137.0, 50138.0, 50139.0, 50140.0, 50141.0, 50142.0],       [50143.0, 50144.0, 50145.0, 50146.0, 50147.0, 50148.0]]],     [[[50149.0, 50150.0, 50151.0, 50152.0, 50153.0, 50154.0],       [50155.0, 50156.0, 50157.0, 50158.0, 50159.0, 50160.0],       [50161.0, 50162.0, 50163.0, 50164.0, 50165.0, 50166.0],       [50167.0, 50168.0, 50169.0, 50170.0, 50171.0, 50172.0],       [50173.0, 50174.0, 50175.0, 50176.0, 50177.0, 50178.0],       [50179.0, 50180.0, 50181.0, 50182.0, 50183.0, 50184.0],       [50185.0, 50186.0, 50187.0, 50188.0, 50189.0, 50190.0]],      [[50191.0, 50192.0, 50193.0, 50194.0, 50195.0, 50196.0],       [50197.0, 50198.0, 50199.0, 50200.0, 50201.0, 50202.0],       [50203.0, 50204.0, 50205.0, 50206.0, 50207.0, 50208.0],       [50209.0, 50210.0, 50211.0, 50212.0, 50213.0, 50214.0],       [50215.0, 50216.0, 50217.0, 50218.0, 50219.0, 50220.0],       [50221.0, 50222.0, 50223.0, 50224.0, 50225.0, 50226.0],       [50227.0, 50228.0, 50229.0, 50230.0, 50231.0, 50232.0]],      [[50233.0, 50234.0, 50235.0, 50236.0, 50237.0, 50238.0],       [50239.0, 50240.0, 50241.0, 50242.0, 50243.0, 50244.0],       [50245.0, 50246.0, 50247.0, 50248.0, 50249.0, 50250.0],       [50251.0, 50252.0, 50253.0, 50254.0, 50255.0, 50256.0],       [50257.0, 50258.0, 50259.0, 50260.0, 50261.0, 50262.0],       [50263.0, 50264.0, 50265.0, 50266.0, 50267.0, 50268.0],       [50269.0, 50270.0, 50271.0, 50272.0, 50273.0, 50274.0]],      [[50275.0, 50276.0, 50277.0, 50278.0, 50279.0, 50280.0],       [50281.0, 50282.0, 50283.0, 50284.0, 50285.0, 50286.0],       [50287.0, 50288.0, 50289.0, 50290.0, 50291.0, 50292.0],       [50293.0, 50294.0, 50295.0, 50296.0, 50297.0, 50298.0],       [50299.0, 50300.0, 50301.0, 50302.0, 50303.0, 50304.0],       [50305.0, 50306.0, 50307.0, 50308.0, 50309.0, 50310.0],       [50311.0, 50312.0, 50313.0, 50314.0, 50315.0, 50316.0]],      [[50317.0, 50318.0, 50319.0, 50320.0, 50321.0, 50322.0],       [50323.0, 50324.0, 50325.0, 50326.0, 50327.0, 50328.0],       [50329.0, 50330.0, 50331.0, 50332.0, 50333.0, 50334.0],       [50335.0, 50336.0, 50337.0, 50338.0, 50339.0, 50340.0],       [50341.0, 50342.0, 50343.0, 50344.0, 50345.0, 50346.0],       [50347.0, 50348.0, 50349.0, 50350.0, 50351.0, 50352.0],       [50353.0, 50354.0, 50355.0, 50356.0, 50357.0, 50358.0]],      [[50359.0, 50360.0, 50361.0, 50362.0, 50363.0, 50364.0],       [50365.0, 50366.0, 50367.0, 50368.0, 50369.0, 50370.0],       [50371.0, 50372.0, 50373.0, 50374.0, 50375.0, 50376.0],       [50377.0, 50378.0, 50379.0, 50380.0, 50381.0, 50382.0],       [50383.0, 50384.0, 50385.0, 50386.0, 50387.0, 50388.0],       [50389.0, 50390.0, 50391.0, 50392.0, 50393.0, 50394.0],       [50395.0, 50396.0, 50397.0, 50398.0, 50399.0, 50400.0]]]],    [[[[50401.0, 50402.0, 50403.0, 50404.0, 50405.0, 50406.0],       [50407.0, 50408.0, 50409.0, 50410.0, 50411.0, 50412.0],       [50413.0, 50414.0, 50415.0, 50416.0, 50417.0, 50418.0],       [50419.0, 50420.0, 50421.0, 50422.0, 50423.0, 50424.0],       [50425.0, 50426.0, 50427.0, 50428.0, 50429.0, 50430.0],       [50431.0, 50432.0, 50433.0, 50434.0, 50435.0, 50436.0],       [50437.0, 50438.0, 50439.0, 50440.0, 50441.0, 50442.0]],      [[50443.0, 50444.0, 50445.0, 50446.0, 50447.0, 50448.0],       [50449.0, 50450.0, 50451.0, 50452.0, 50453.0, 50454.0],       [50455.0, 50456.0, 50457.0, 50458.0, 50459.0, 50460.0],       [50461.0, 50462.0, 50463.0, 50464.0, 50465.0, 50466.0],       [50467.0, 50468.0, 50469.0, 50470.0, 50471.0, 50472.0],       [50473.0, 50474.0, 50475.0, 50476.0, 50477.0, 50478.0],       [50479.0, 50480.0, 50481.0, 50482.0, 50483.0, 50484.0]],      [[50485.0, 50486.0, 50487.0, 50488.0, 50489.0, 50490.0],       [50491.0, 50492.0, 50493.0, 50494.0, 50495.0, 50496.0],       [50497.0, 50498.0, 50499.0, 50500.0, 50501.0, 50502.0],       [50503.0, 50504.0, 50505.0, 50506.0, 50507.0, 50508.0],       [50509.0, 50510.0, 50511.0, 50512.0, 50513.0, 50514.0],       [50515.0, 50516.0, 50517.0, 50518.0, 50519.0, 50520.0],       [50521.0, 50522.0, 50523.0, 50524.0, 50525.0, 50526.0]],      [[50527.0, 50528.0, 50529.0, 50530.0, 50531.0, 50532.0],       [50533.0, 50534.0, 50535.0, 50536.0, 50537.0, 50538.0],       [50539.0, 50540.0, 50541.0, 50542.0, 50543.0, 50544.0],       [50545.0, 50546.0, 50547.0, 50548.0, 50549.0, 50550.0],       [50551.0, 50552.0, 50553.0, 50554.0, 50555.0, 50556.0],       [50557.0, 50558.0, 50559.0, 50560.0, 50561.0, 50562.0],       [50563.0, 50564.0, 50565.0, 50566.0, 50567.0, 50568.0]],      [[50569.0, 50570.0, 50571.0, 50572.0, 50573.0, 50574.0],       [50575.0, 50576.0, 50577.0, 50578.0, 50579.0, 50580.0],       [50581.0, 50582.0, 50583.0, 50584.0, 50585.0, 50586.0],       [50587.0, 50588.0, 50589.0, 50590.0, 50591.0, 50592.0],       [50593.0, 50594.0, 50595.0, 50596.0, 50597.0, 50598.0],       [50599.0, 50600.0, 50601.0, 50602.0, 50603.0, 50604.0],       [50605.0, 50606.0, 50607.0, 50608.0, 50609.0, 50610.0]],      [[50611.0, 50612.0, 50613.0, 50614.0, 50615.0, 50616.0],       [50617.0, 50618.0, 50619.0, 50620.0, 50621.0, 50622.0],       [50623.0, 50624.0, 50625.0, 50626.0, 50627.0, 50628.0],       [50629.0, 50630.0, 50631.0, 50632.0, 50633.0, 50634.0],       [50635.0, 50636.0, 50637.0, 50638.0, 50639.0, 50640.0],       [50641.0, 50642.0, 50643.0, 50644.0, 50645.0, 50646.0],       [50647.0, 50648.0, 50649.0, 50650.0, 50651.0, 50652.0]]],     [[[50653.0, 50654.0, 50655.0, 50656.0, 50657.0, 50658.0],       [50659.0, 50660.0, 50661.0, 50662.0, 50663.0, 50664.0],       [50665.0, 50666.0, 50667.0, 50668.0, 50669.0, 50670.0],       [50671.0, 50672.0, 50673.0, 50674.0, 50675.0, 50676.0],       [50677.0, 50678.0, 50679.0, 50680.0, 50681.0, 50682.0],       [50683.0, 50684.0, 50685.0, 50686.0, 50687.0, 50688.0],       [50689.0, 50690.0, 50691.0, 50692.0, 50693.0, 50694.0]],      [[50695.0, 50696.0, 50697.0, 50698.0, 50699.0, 50700.0],       [50701.0, 50702.0, 50703.0, 50704.0, 50705.0, 50706.0],       [50707.0, 50708.0, 50709.0, 50710.0, 50711.0, 50712.0],       [50713.0, 50714.0, 50715.0, 50716.0, 50717.0, 50718.0],       [50719.0, 50720.0, 50721.0, 50722.0, 50723.0, 50724.0],       [50725.0, 50726.0, 50727.0, 50728.0, 50729.0, 50730.0],       [50731.0, 50732.0, 50733.0, 50734.0, 50735.0, 50736.0]],      [[50737.0, 50738.0, 50739.0, 50740.0, 50741.0, 50742.0],       [50743.0, 50744.0, 50745.0, 50746.0, 50747.0, 50748.0],       [50749.0, 50750.0, 50751.0, 50752.0, 50753.0, 50754.0],       [50755.0, 50756.0, 50757.0, 50758.0, 50759.0, 50760.0],       [50761.0, 50762.0, 50763.0, 50764.0, 50765.0, 50766.0],       [50767.0, 50768.0, 50769.0, 50770.0, 50771.0, 50772.0],       [50773.0, 50774.0, 50775.0, 50776.0, 50777.0, 50778.0]],      [[50779.0, 50780.0, 50781.0, 50782.0, 50783.0, 50784.0],       [50785.0, 50786.0, 50787.0, 50788.0, 50789.0, 50790.0],       [50791.0, 50792.0, 50793.0, 50794.0, 50795.0, 50796.0],       [50797.0, 50798.0, 50799.0, 50800.0, 50801.0, 50802.0],       [50803.0, 50804.0, 50805.0, 50806.0, 50807.0, 50808.0],       [50809.0, 50810.0, 50811.0, 50812.0, 50813.0, 50814.0],       [50815.0, 50816.0, 50817.0, 50818.0, 50819.0, 50820.0]],      [[50821.0, 50822.0, 50823.0, 50824.0, 50825.0, 50826.0],       [50827.0, 50828.0, 50829.0, 50830.0, 50831.0, 50832.0],       [50833.0, 50834.0, 50835.0, 50836.0, 50837.0, 50838.0],       [50839.0, 50840.0, 50841.0, 50842.0, 50843.0, 50844.0],       [50845.0, 50846.0, 50847.0, 50848.0, 50849.0, 50850.0],       [50851.0, 50852.0, 50853.0, 50854.0, 50855.0, 50856.0],       [50857.0, 50858.0, 50859.0, 50860.0, 50861.0, 50862.0]],      [[50863.0, 50864.0, 50865.0, 50866.0, 50867.0, 50868.0],       [50869.0, 50870.0, 50871.0, 50872.0, 50873.0, 50874.0],       [50875.0, 50876.0, 50877.0, 50878.0, 50879.0, 50880.0],       [50881.0, 50882.0, 50883.0, 50884.0, 50885.0, 50886.0],       [50887.0, 50888.0, 50889.0, 50890.0, 50891.0, 50892.0],       [50893.0, 50894.0, 50895.0, 50896.0, 50897.0, 50898.0],       [50899.0, 50900.0, 50901.0, 50902.0, 50903.0, 50904.0]]],     [[[50905.0, 50906.0, 50907.0, 50908.0, 50909.0, 50910.0],       [50911.0, 50912.0, 50913.0, 50914.0, 50915.0, 50916.0],       [50917.0, 50918.0, 50919.0, 50920.0, 50921.0, 50922.0],       [50923.0, 50924.0, 50925.0, 50926.0, 50927.0, 50928.0],       [50929.0, 50930.0, 50931.0, 50932.0, 50933.0, 50934.0],       [50935.0, 50936.0, 50937.0, 50938.0, 50939.0, 50940.0],       [50941.0, 50942.0, 50943.0, 50944.0, 50945.0, 50946.0]],      [[50947.0, 50948.0, 50949.0, 50950.0, 50951.0, 50952.0],       [50953.0, 50954.0, 50955.0, 50956.0, 50957.0, 50958.0],       [50959.0, 50960.0, 50961.0, 50962.0, 50963.0, 50964.0],       [50965.0, 50966.0, 50967.0, 50968.0, 50969.0, 50970.0],       [50971.0, 50972.0, 50973.0, 50974.0, 50975.0, 50976.0],       [50977.0, 50978.0, 50979.0, 50980.0, 50981.0, 50982.0],       [50983.0, 50984.0, 50985.0, 50986.0, 50987.0, 50988.0]],      [[50989.0, 50990.0, 50991.0, 50992.0, 50993.0, 50994.0],       [50995.0, 50996.0, 50997.0, 50998.0, 50999.0, 51000.0],       [51001.0, 51002.0, 51003.0, 51004.0, 51005.0, 51006.0],       [51007.0, 51008.0, 51009.0, 51010.0, 51011.0, 51012.0],       [51013.0, 51014.0, 51015.0, 51016.0, 51017.0, 51018.0],       [51019.0, 51020.0, 51021.0, 51022.0, 51023.0, 51024.0],       [51025.0, 51026.0, 51027.0, 51028.0, 51029.0, 51030.0]],      [[51031.0, 51032.0, 51033.0, 51034.0, 51035.0, 51036.0],       [51037.0, 51038.0, 51039.0, 51040.0, 51041.0, 51042.0],       [51043.0, 51044.0, 51045.0, 51046.0, 51047.0, 51048.0],       [51049.0, 51050.0, 51051.0, 51052.0, 51053.0, 51054.0],       [51055.0, 51056.0, 51057.0, 51058.0, 51059.0, 51060.0],       [51061.0, 51062.0, 51063.0, 51064.0, 51065.0, 51066.0],       [51067.0, 51068.0, 51069.0, 51070.0, 51071.0, 51072.0]],      [[51073.0, 51074.0, 51075.0, 51076.0, 51077.0, 51078.0],       [51079.0, 51080.0, 51081.0, 51082.0, 51083.0, 51084.0],       [51085.0, 51086.0, 51087.0, 51088.0, 51089.0, 51090.0],       [51091.0, 51092.0, 51093.0, 51094.0, 51095.0, 51096.0],       [51097.0, 51098.0, 51099.0, 51100.0, 51101.0, 51102.0],       [51103.0, 51104.0, 51105.0, 51106.0, 51107.0, 51108.0],       [51109.0, 51110.0, 51111.0, 51112.0, 51113.0, 51114.0]],      [[51115.0, 51116.0, 51117.0, 51118.0, 51119.0, 51120.0],       [51121.0, 51122.0, 51123.0, 51124.0, 51125.0, 51126.0],       [51127.0, 51128.0, 51129.0, 51130.0, 51131.0, 51132.0],       [51133.0, 51134.0, 51135.0, 51136.0, 51137.0, 51138.0],       [51139.0, 51140.0, 51141.0, 51142.0, 51143.0, 51144.0],       [51145.0, 51146.0, 51147.0, 51148.0, 51149.0, 51150.0],       [51151.0, 51152.0, 51153.0, 51154.0, 51155.0, 51156.0]]],     [[[51157.0, 51158.0, 51159.0, 51160.0, 51161.0, 51162.0],       [51163.0, 51164.0, 51165.0, 51166.0, 51167.0, 51168.0],       [51169.0, 51170.0, 51171.0, 51172.0, 51173.0, 51174.0],       [51175.0, 51176.0, 51177.0, 51178.0, 51179.0, 51180.0],       [51181.0, 51182.0, 51183.0, 51184.0, 51185.0, 51186.0],       [51187.0, 51188.0, 51189.0, 51190.0, 51191.0, 51192.0],       [51193.0, 51194.0, 51195.0, 51196.0, 51197.0, 51198.0]],      [[51199.0, 51200.0, 51201.0, 51202.0, 51203.0, 51204.0],       [51205.0, 51206.0, 51207.0, 51208.0, 51209.0, 51210.0],       [51211.0, 51212.0, 51213.0, 51214.0, 51215.0, 51216.0],       [51217.0, 51218.0, 51219.0, 51220.0, 51221.0, 51222.0],       [51223.0, 51224.0, 51225.0, 51226.0, 51227.0, 51228.0],       [51229.0, 51230.0, 51231.0, 51232.0, 51233.0, 51234.0],       [51235.0, 51236.0, 51237.0, 51238.0, 51239.0, 51240.0]],      [[51241.0, 51242.0, 51243.0, 51244.0, 51245.0, 51246.0],       [51247.0, 51248.0, 51249.0, 51250.0, 51251.0, 51252.0],       [51253.0, 51254.0, 51255.0, 51256.0, 51257.0, 51258.0],       [51259.0, 51260.0, 51261.0, 51262.0, 51263.0, 51264.0],       [51265.0, 51266.0, 51267.0, 51268.0, 51269.0, 51270.0],       [51271.0, 51272.0, 51273.0, 51274.0, 51275.0, 51276.0],       [51277.0, 51278.0, 51279.0, 51280.0, 51281.0, 51282.0]],      [[51283.0, 51284.0, 51285.0, 51286.0, 51287.0, 51288.0],       [51289.0, 51290.0, 51291.0, 51292.0, 51293.0, 51294.0],       [51295.0, 51296.0, 51297.0, 51298.0, 51299.0, 51300.0],       [51301.0, 51302.0, 51303.0, 51304.0, 51305.0, 51306.0],       [51307.0, 51308.0, 51309.0, 51310.0, 51311.0, 51312.0],       [51313.0, 51314.0, 51315.0, 51316.0, 51317.0, 51318.0],       [51319.0, 51320.0, 51321.0, 51322.0, 51323.0, 51324.0]],      [[51325.0, 51326.0, 51327.0, 51328.0, 51329.0, 51330.0],       [51331.0, 51332.0, 51333.0, 51334.0, 51335.0, 51336.0],       [51337.0, 51338.0, 51339.0, 51340.0, 51341.0, 51342.0],       [51343.0, 51344.0, 51345.0, 51346.0, 51347.0, 51348.0],       [51349.0, 51350.0, 51351.0, 51352.0, 51353.0, 51354.0],       [51355.0, 51356.0, 51357.0, 51358.0, 51359.0, 51360.0],       [51361.0, 51362.0, 51363.0, 51364.0, 51365.0, 51366.0]],      [[51367.0, 51368.0, 51369.0, 51370.0, 51371.0, 51372.0],       [51373.0, 51374.0, 51375.0, 51376.0, 51377.0, 51378.0],       [51379.0, 51380.0, 51381.0, 51382.0, 51383.0, 51384.0],       [51385.0, 51386.0, 51387.0, 51388.0, 51389.0, 51390.0],       [51391.0, 51392.0, 51393.0, 51394.0, 51395.0, 51396.0],       [51397.0, 51398.0, 51399.0, 51400.0, 51401.0, 51402.0],       [51403.0, 51404.0, 51405.0, 51406.0, 51407.0, 51408.0]]]],    [[[[51409.0, 51410.0, 51411.0, 51412.0, 51413.0, 51414.0],       [51415.0, 51416.0, 51417.0, 51418.0, 51419.0, 51420.0],       [51421.0, 51422.0, 51423.0, 51424.0, 51425.0, 51426.0],       [51427.0, 51428.0, 51429.0, 51430.0, 51431.0, 51432.0],       [51433.0, 51434.0, 51435.0, 51436.0, 51437.0, 51438.0],       [51439.0, 51440.0, 51441.0, 51442.0, 51443.0, 51444.0],       [51445.0, 51446.0, 51447.0, 51448.0, 51449.0, 51450.0]],      [[51451.0, 51452.0, 51453.0, 51454.0, 51455.0, 51456.0],       [51457.0, 51458.0, 51459.0, 51460.0, 51461.0, 51462.0],       [51463.0, 51464.0, 51465.0, 51466.0, 51467.0, 51468.0],       [51469.0, 51470.0, 51471.0, 51472.0, 51473.0, 51474.0],       [51475.0, 51476.0, 51477.0, 51478.0, 51479.0, 51480.0],       [51481.0, 51482.0, 51483.0, 51484.0, 51485.0, 51486.0],       [51487.0, 51488.0, 51489.0, 51490.0, 51491.0, 51492.0]],      [[51493.0, 51494.0, 51495.0, 51496.0, 51497.0, 51498.0],       [51499.0, 51500.0, 51501.0, 51502.0, 51503.0, 51504.0],       [51505.0, 51506.0, 51507.0, 51508.0, 51509.0, 51510.0],       [51511.0, 51512.0, 51513.0, 51514.0, 51515.0, 51516.0],       [51517.0, 51518.0, 51519.0, 51520.0, 51521.0, 51522.0],       [51523.0, 51524.0, 51525.0, 51526.0, 51527.0, 51528.0],       [51529.0, 51530.0, 51531.0, 51532.0, 51533.0, 51534.0]],      [[51535.0, 51536.0, 51537.0, 51538.0, 51539.0, 51540.0],       [51541.0, 51542.0, 51543.0, 51544.0, 51545.0, 51546.0],       [51547.0, 51548.0, 51549.0, 51550.0, 51551.0, 51552.0],       [51553.0, 51554.0, 51555.0, 51556.0, 51557.0, 51558.0],       [51559.0, 51560.0, 51561.0, 51562.0, 51563.0, 51564.0],       [51565.0, 51566.0, 51567.0, 51568.0, 51569.0, 51570.0],       [51571.0, 51572.0, 51573.0, 51574.0, 51575.0, 51576.0]],      [[51577.0, 51578.0, 51579.0, 51580.0, 51581.0, 51582.0],       [51583.0, 51584.0, 51585.0, 51586.0, 51587.0, 51588.0],       [51589.0, 51590.0, 51591.0, 51592.0, 51593.0, 51594.0],       [51595.0, 51596.0, 51597.0, 51598.0, 51599.0, 51600.0],       [51601.0, 51602.0, 51603.0, 51604.0, 51605.0, 51606.0],       [51607.0, 51608.0, 51609.0, 51610.0, 51611.0, 51612.0],       [51613.0, 51614.0, 51615.0, 51616.0, 51617.0, 51618.0]],      [[51619.0, 51620.0, 51621.0, 51622.0, 51623.0, 51624.0],       [51625.0, 51626.0, 51627.0, 51628.0, 51629.0, 51630.0],       [51631.0, 51632.0, 51633.0, 51634.0, 51635.0, 51636.0],       [51637.0, 51638.0, 51639.0, 51640.0, 51641.0, 51642.0],       [51643.0, 51644.0, 51645.0, 51646.0, 51647.0, 51648.0],       [51649.0, 51650.0, 51651.0, 51652.0, 51653.0, 51654.0],       [51655.0, 51656.0, 51657.0, 51658.0, 51659.0, 51660.0]]],     [[[51661.0, 51662.0, 51663.0, 51664.0, 51665.0, 51666.0],       [51667.0, 51668.0, 51669.0, 51670.0, 51671.0, 51672.0],       [51673.0, 51674.0, 51675.0, 51676.0, 51677.0, 51678.0],       [51679.0, 51680.0, 51681.0, 51682.0, 51683.0, 51684.0],       [51685.0, 51686.0, 51687.0, 51688.0, 51689.0, 51690.0],       [51691.0, 51692.0, 51693.0, 51694.0, 51695.0, 51696.0],       [51697.0, 51698.0, 51699.0, 51700.0, 51701.0, 51702.0]],      [[51703.0, 51704.0, 51705.0, 51706.0, 51707.0, 51708.0],       [51709.0, 51710.0, 51711.0, 51712.0, 51713.0, 51714.0],       [51715.0, 51716.0, 51717.0, 51718.0, 51719.0, 51720.0],       [51721.0, 51722.0, 51723.0, 51724.0, 51725.0, 51726.0],       [51727.0, 51728.0, 51729.0, 51730.0, 51731.0, 51732.0],       [51733.0, 51734.0, 51735.0, 51736.0, 51737.0, 51738.0],       [51739.0, 51740.0, 51741.0, 51742.0, 51743.0, 51744.0]],      [[51745.0, 51746.0, 51747.0, 51748.0, 51749.0, 51750.0],       [51751.0, 51752.0, 51753.0, 51754.0, 51755.0, 51756.0],       [51757.0, 51758.0, 51759.0, 51760.0, 51761.0, 51762.0],       [51763.0, 51764.0, 51765.0, 51766.0, 51767.0, 51768.0],       [51769.0, 51770.0, 51771.0, 51772.0, 51773.0, 51774.0],       [51775.0, 51776.0, 51777.0, 51778.0, 51779.0, 51780.0],       [51781.0, 51782.0, 51783.0, 51784.0, 51785.0, 51786.0]],      [[51787.0, 51788.0, 51789.0, 51790.0, 51791.0, 51792.0],       [51793.0, 51794.0, 51795.0, 51796.0, 51797.0, 51798.0],       [51799.0, 51800.0, 51801.0, 51802.0, 51803.0, 51804.0],       [51805.0, 51806.0, 51807.0, 51808.0, 51809.0, 51810.0],       [51811.0, 51812.0, 51813.0, 51814.0, 51815.0, 51816.0],       [51817.0, 51818.0, 51819.0, 51820.0, 51821.0, 51822.0],       [51823.0, 51824.0, 51825.0, 51826.0, 51827.0, 51828.0]],      [[51829.0, 51830.0, 51831.0, 51832.0, 51833.0, 51834.0],       [51835.0, 51836.0, 51837.0, 51838.0, 51839.0, 51840.0],       [51841.0, 51842.0, 51843.0, 51844.0, 51845.0, 51846.0],       [51847.0, 51848.0, 51849.0, 51850.0, 51851.0, 51852.0],       [51853.0, 51854.0, 51855.0, 51856.0, 51857.0, 51858.0],       [51859.0, 51860.0, 51861.0, 51862.0, 51863.0, 51864.0],       [51865.0, 51866.0, 51867.0, 51868.0, 51869.0, 51870.0]],      [[51871.0, 51872.0, 51873.0, 51874.0, 51875.0, 51876.0],       [51877.0, 51878.0, 51879.0, 51880.0, 51881.0, 51882.0],       [51883.0, 51884.0, 51885.0, 51886.0, 51887.0, 51888.0],       [51889.0, 51890.0, 51891.0, 51892.0, 51893.0, 51894.0],       [51895.0, 51896.0, 51897.0, 51898.0, 51899.0, 51900.0],       [51901.0, 51902.0, 51903.0, 51904.0, 51905.0, 51906.0],       [51907.0, 51908.0, 51909.0, 51910.0, 51911.0, 51912.0]]],     [[[51913.0, 51914.0, 51915.0, 51916.0, 51917.0, 51918.0],       [51919.0, 51920.0, 51921.0, 51922.0, 51923.0, 51924.0],       [51925.0, 51926.0, 51927.0, 51928.0, 51929.0, 51930.0],       [51931.0, 51932.0, 51933.0, 51934.0, 51935.0, 51936.0],       [51937.0, 51938.0, 51939.0, 51940.0, 51941.0, 51942.0],       [51943.0, 51944.0, 51945.0, 51946.0, 51947.0, 51948.0],       [51949.0, 51950.0, 51951.0, 51952.0, 51953.0, 51954.0]],      [[51955.0, 51956.0, 51957.0, 51958.0, 51959.0, 51960.0],       [51961.0, 51962.0, 51963.0, 51964.0, 51965.0, 51966.0],       [51967.0, 51968.0, 51969.0, 51970.0, 51971.0, 51972.0],       [51973.0, 51974.0, 51975.0, 51976.0, 51977.0, 51978.0],       [51979.0, 51980.0, 51981.0, 51982.0, 51983.0, 51984.0],       [51985.0, 51986.0, 51987.0, 51988.0, 51989.0, 51990.0],       [51991.0, 51992.0, 51993.0, 51994.0, 51995.0, 51996.0]],      [[51997.0, 51998.0, 51999.0, 52000.0, 52001.0, 52002.0],       [52003.0, 52004.0, 52005.0, 52006.0, 52007.0, 52008.0],       [52009.0, 52010.0, 52011.0, 52012.0, 52013.0, 52014.0],       [52015.0, 52016.0, 52017.0, 52018.0, 52019.0, 52020.0],       [52021.0, 52022.0, 52023.0, 52024.0, 52025.0, 52026.0],       [52027.0, 52028.0, 52029.0, 52030.0, 52031.0, 52032.0],       [52033.0, 52034.0, 52035.0, 52036.0, 52037.0, 52038.0]],      [[52039.0, 52040.0, 52041.0, 52042.0, 52043.0, 52044.0],       [52045.0, 52046.0, 52047.0, 52048.0, 52049.0, 52050.0],       [52051.0, 52052.0, 52053.0, 52054.0, 52055.0, 52056.0],       [52057.0, 52058.0, 52059.0, 52060.0, 52061.0, 52062.0],       [52063.0, 52064.0, 52065.0, 52066.0, 52067.0, 52068.0],       [52069.0, 52070.0, 52071.0, 52072.0, 52073.0, 52074.0],       [52075.0, 52076.0, 52077.0, 52078.0, 52079.0, 52080.0]],      [[52081.0, 52082.0, 52083.0, 52084.0, 52085.0, 52086.0],       [52087.0, 52088.0, 52089.0, 52090.0, 52091.0, 52092.0],       [52093.0, 52094.0, 52095.0, 52096.0, 52097.0, 52098.0],       [52099.0, 52100.0, 52101.0, 52102.0, 52103.0, 52104.0],       [52105.0, 52106.0, 52107.0, 52108.0, 52109.0, 52110.0],       [52111.0, 52112.0, 52113.0, 52114.0, 52115.0, 52116.0],       [52117.0, 52118.0, 52119.0, 52120.0, 52121.0, 52122.0]],      [[52123.0, 52124.0, 52125.0, 52126.0, 52127.0, 52128.0],       [52129.0, 52130.0, 52131.0, 52132.0, 52133.0, 52134.0],       [52135.0, 52136.0, 52137.0, 52138.0, 52139.0, 52140.0],       [52141.0, 52142.0, 52143.0, 52144.0, 52145.0, 52146.0],       [52147.0, 52148.0, 52149.0, 52150.0, 52151.0, 52152.0],       [52153.0, 52154.0, 52155.0, 52156.0, 52157.0, 52158.0],       [52159.0, 52160.0, 52161.0, 52162.0, 52163.0, 52164.0]]],     [[[52165.0, 52166.0, 52167.0, 52168.0, 52169.0, 52170.0],       [52171.0, 52172.0, 52173.0, 52174.0, 52175.0, 52176.0],       [52177.0, 52178.0, 52179.0, 52180.0, 52181.0, 52182.0],       [52183.0, 52184.0, 52185.0, 52186.0, 52187.0, 52188.0],       [52189.0, 52190.0, 52191.0, 52192.0, 52193.0, 52194.0],       [52195.0, 52196.0, 52197.0, 52198.0, 52199.0, 52200.0],       [52201.0, 52202.0, 52203.0, 52204.0, 52205.0, 52206.0]],      [[52207.0, 52208.0, 52209.0, 52210.0, 52211.0, 52212.0],       [52213.0, 52214.0, 52215.0, 52216.0, 52217.0, 52218.0],       [52219.0, 52220.0, 52221.0, 52222.0, 52223.0, 52224.0],       [52225.0, 52226.0, 52227.0, 52228.0, 52229.0, 52230.0],       [52231.0, 52232.0, 52233.0, 52234.0, 52235.0, 52236.0],       [52237.0, 52238.0, 52239.0, 52240.0, 52241.0, 52242.0],       [52243.0, 52244.0, 52245.0, 52246.0, 52247.0, 52248.0]],      [[52249.0, 52250.0, 52251.0, 52252.0, 52253.0, 52254.0],       [52255.0, 52256.0, 52257.0, 52258.0, 52259.0, 52260.0],       [52261.0, 52262.0, 52263.0, 52264.0, 52265.0, 52266.0],       [52267.0, 52268.0, 52269.0, 52270.0, 52271.0, 52272.0],       [52273.0, 52274.0, 52275.0, 52276.0, 52277.0, 52278.0],       [52279.0, 52280.0, 52281.0, 52282.0, 52283.0, 52284.0],       [52285.0, 52286.0, 52287.0, 52288.0, 52289.0, 52290.0]],      [[52291.0, 52292.0, 52293.0, 52294.0, 52295.0, 52296.0],       [52297.0, 52298.0, 52299.0, 52300.0, 52301.0, 52302.0],       [52303.0, 52304.0, 52305.0, 52306.0, 52307.0, 52308.0],       [52309.0, 52310.0, 52311.0, 52312.0, 52313.0, 52314.0],       [52315.0, 52316.0, 52317.0, 52318.0, 52319.0, 52320.0],       [52321.0, 52322.0, 52323.0, 52324.0, 52325.0, 52326.0],       [52327.0, 52328.0, 52329.0, 52330.0, 52331.0, 52332.0]],      [[52333.0, 52334.0, 52335.0, 52336.0, 52337.0, 52338.0],       [52339.0, 52340.0, 52341.0, 52342.0, 52343.0, 52344.0],       [52345.0, 52346.0, 52347.0, 52348.0, 52349.0, 52350.0],       [52351.0, 52352.0, 52353.0, 52354.0, 52355.0, 52356.0],       [52357.0, 52358.0, 52359.0, 52360.0, 52361.0, 52362.0],       [52363.0, 52364.0, 52365.0, 52366.0, 52367.0, 52368.0],       [52369.0, 52370.0, 52371.0, 52372.0, 52373.0, 52374.0]],      [[52375.0, 52376.0, 52377.0, 52378.0, 52379.0, 52380.0],       [52381.0, 52382.0, 52383.0, 52384.0, 52385.0, 52386.0],       [52387.0, 52388.0, 52389.0, 52390.0, 52391.0, 52392.0],       [52393.0, 52394.0, 52395.0, 52396.0, 52397.0, 52398.0],       [52399.0, 52400.0, 52401.0, 52402.0, 52403.0, 52404.0],       [52405.0, 52406.0, 52407.0, 52408.0, 52409.0, 52410.0],       [52411.0, 52412.0, 52413.0, 52414.0, 52415.0, 52416.0]]]],    [[[[52417.0, 52418.0, 52419.0, 52420.0, 52421.0, 52422.0],       [52423.0, 52424.0, 52425.0, 52426.0, 52427.0, 52428.0],       [52429.0, 52430.0, 52431.0, 52432.0, 52433.0, 52434.0],       [52435.0, 52436.0, 52437.0, 52438.0, 52439.0, 52440.0],       [52441.0, 52442.0, 52443.0, 52444.0, 52445.0, 52446.0],       [52447.0, 52448.0, 52449.0, 52450.0, 52451.0, 52452.0],       [52453.0, 52454.0, 52455.0, 52456.0, 52457.0, 52458.0]],      [[52459.0, 52460.0, 52461.0, 52462.0, 52463.0, 52464.0],       [52465.0, 52466.0, 52467.0, 52468.0, 52469.0, 52470.0],       [52471.0, 52472.0, 52473.0, 52474.0, 52475.0, 52476.0],       [52477.0, 52478.0, 52479.0, 52480.0, 52481.0, 52482.0],       [52483.0, 52484.0, 52485.0, 52486.0, 52487.0, 52488.0],       [52489.0, 52490.0, 52491.0, 52492.0, 52493.0, 52494.0],       [52495.0, 52496.0, 52497.0, 52498.0, 52499.0, 52500.0]],      [[52501.0, 52502.0, 52503.0, 52504.0, 52505.0, 52506.0],       [52507.0, 52508.0, 52509.0, 52510.0, 52511.0, 52512.0],       [52513.0, 52514.0, 52515.0, 52516.0, 52517.0, 52518.0],       [52519.0, 52520.0, 52521.0, 52522.0, 52523.0, 52524.0],       [52525.0, 52526.0, 52527.0, 52528.0, 52529.0, 52530.0],       [52531.0, 52532.0, 52533.0, 52534.0, 52535.0, 52536.0],       [52537.0, 52538.0, 52539.0, 52540.0, 52541.0, 52542.0]],      [[52543.0, 52544.0, 52545.0, 52546.0, 52547.0, 52548.0],       [52549.0, 52550.0, 52551.0, 52552.0, 52553.0, 52554.0],       [52555.0, 52556.0, 52557.0, 52558.0, 52559.0, 52560.0],       [52561.0, 52562.0, 52563.0, 52564.0, 52565.0, 52566.0],       [52567.0, 52568.0, 52569.0, 52570.0, 52571.0, 52572.0],       [52573.0, 52574.0, 52575.0, 52576.0, 52577.0, 52578.0],       [52579.0, 52580.0, 52581.0, 52582.0, 52583.0, 52584.0]],      [[52585.0, 52586.0, 52587.0, 52588.0, 52589.0, 52590.0],       [52591.0, 52592.0, 52593.0, 52594.0, 52595.0, 52596.0],       [52597.0, 52598.0, 52599.0, 52600.0, 52601.0, 52602.0],       [52603.0, 52604.0, 52605.0, 52606.0, 52607.0, 52608.0],       [52609.0, 52610.0, 52611.0, 52612.0, 52613.0, 52614.0],       [52615.0, 52616.0, 52617.0, 52618.0, 52619.0, 52620.0],       [52621.0, 52622.0, 52623.0, 52624.0, 52625.0, 52626.0]],      [[52627.0, 52628.0, 52629.0, 52630.0, 52631.0, 52632.0],       [52633.0, 52634.0, 52635.0, 52636.0, 52637.0, 52638.0],       [52639.0, 52640.0, 52641.0, 52642.0, 52643.0, 52644.0],       [52645.0, 52646.0, 52647.0, 52648.0, 52649.0, 52650.0],       [52651.0, 52652.0, 52653.0, 52654.0, 52655.0, 52656.0],       [52657.0, 52658.0, 52659.0, 52660.0, 52661.0, 52662.0],       [52663.0, 52664.0, 52665.0, 52666.0, 52667.0, 52668.0]]],     [[[52669.0, 52670.0, 52671.0, 52672.0, 52673.0, 52674.0],       [52675.0, 52676.0, 52677.0, 52678.0, 52679.0, 52680.0],       [52681.0, 52682.0, 52683.0, 52684.0, 52685.0, 52686.0],       [52687.0, 52688.0, 52689.0, 52690.0, 52691.0, 52692.0],       [52693.0, 52694.0, 52695.0, 52696.0, 52697.0, 52698.0],       [52699.0, 52700.0, 52701.0, 52702.0, 52703.0, 52704.0],       [52705.0, 52706.0, 52707.0, 52708.0, 52709.0, 52710.0]],      [[52711.0, 52712.0, 52713.0, 52714.0, 52715.0, 52716.0],       [52717.0, 52718.0, 52719.0, 52720.0, 52721.0, 52722.0],       [52723.0, 52724.0, 52725.0, 52726.0, 52727.0, 52728.0],       [52729.0, 52730.0, 52731.0, 52732.0, 52733.0, 52734.0],       [52735.0, 52736.0, 52737.0, 52738.0, 52739.0, 52740.0],       [52741.0, 52742.0, 52743.0, 52744.0, 52745.0, 52746.0],       [52747.0, 52748.0, 52749.0, 52750.0, 52751.0, 52752.0]],      [[52753.0, 52754.0, 52755.0, 52756.0, 52757.0, 52758.0],       [52759.0, 52760.0, 52761.0, 52762.0, 52763.0, 52764.0],       [52765.0, 52766.0, 52767.0, 52768.0, 52769.0, 52770.0],       [52771.0, 52772.0, 52773.0, 52774.0, 52775.0, 52776.0],       [52777.0, 52778.0, 52779.0, 52780.0, 52781.0, 52782.0],       [52783.0, 52784.0, 52785.0, 52786.0, 52787.0, 52788.0],       [52789.0, 52790.0, 52791.0, 52792.0, 52793.0, 52794.0]],      [[52795.0, 52796.0, 52797.0, 52798.0, 52799.0, 52800.0],       [52801.0, 52802.0, 52803.0, 52804.0, 52805.0, 52806.0],       [52807.0, 52808.0, 52809.0, 52810.0, 52811.0, 52812.0],       [52813.0, 52814.0, 52815.0, 52816.0, 52817.0, 52818.0],       [52819.0, 52820.0, 52821.0, 52822.0, 52823.0, 52824.0],       [52825.0, 52826.0, 52827.0, 52828.0, 52829.0, 52830.0],       [52831.0, 52832.0, 52833.0, 52834.0, 52835.0, 52836.0]],      [[52837.0, 52838.0, 52839.0, 52840.0, 52841.0, 52842.0],       [52843.0, 52844.0, 52845.0, 52846.0, 52847.0, 52848.0],       [52849.0, 52850.0, 52851.0, 52852.0, 52853.0, 52854.0],       [52855.0, 52856.0, 52857.0, 52858.0, 52859.0, 52860.0],       [52861.0, 52862.0, 52863.0, 52864.0, 52865.0, 52866.0],       [52867.0, 52868.0, 52869.0, 52870.0, 52871.0, 52872.0],       [52873.0, 52874.0, 52875.0, 52876.0, 52877.0, 52878.0]],      [[52879.0, 52880.0, 52881.0, 52882.0, 52883.0, 52884.0],       [52885.0, 52886.0, 52887.0, 52888.0, 52889.0, 52890.0],       [52891.0, 52892.0, 52893.0, 52894.0, 52895.0, 52896.0],       [52897.0, 52898.0, 52899.0, 52900.0, 52901.0, 52902.0],       [52903.0, 52904.0, 52905.0, 52906.0, 52907.0, 52908.0],       [52909.0, 52910.0, 52911.0, 52912.0, 52913.0, 52914.0],       [52915.0, 52916.0, 52917.0, 52918.0, 52919.0, 52920.0]]],     [[[52921.0, 52922.0, 52923.0, 52924.0, 52925.0, 52926.0],       [52927.0, 52928.0, 52929.0, 52930.0, 52931.0, 52932.0],       [52933.0, 52934.0, 52935.0, 52936.0, 52937.0, 52938.0],       [52939.0, 52940.0, 52941.0, 52942.0, 52943.0, 52944.0],       [52945.0, 52946.0, 52947.0, 52948.0, 52949.0, 52950.0],       [52951.0, 52952.0, 52953.0, 52954.0, 52955.0, 52956.0],       [52957.0, 52958.0, 52959.0, 52960.0, 52961.0, 52962.0]],      [[52963.0, 52964.0, 52965.0, 52966.0, 52967.0, 52968.0],       [52969.0, 52970.0, 52971.0, 52972.0, 52973.0, 52974.0],       [52975.0, 52976.0, 52977.0, 52978.0, 52979.0, 52980.0],       [52981.0, 52982.0, 52983.0, 52984.0, 52985.0, 52986.0],       [52987.0, 52988.0, 52989.0, 52990.0, 52991.0, 52992.0],       [52993.0, 52994.0, 52995.0, 52996.0, 52997.0, 52998.0],       [52999.0, 53000.0, 53001.0, 53002.0, 53003.0, 53004.0]],      [[53005.0, 53006.0, 53007.0, 53008.0, 53009.0, 53010.0],       [53011.0, 53012.0, 53013.0, 53014.0, 53015.0, 53016.0],       [53017.0, 53018.0, 53019.0, 53020.0, 53021.0, 53022.0],       [53023.0, 53024.0, 53025.0, 53026.0, 53027.0, 53028.0],       [53029.0, 53030.0, 53031.0, 53032.0, 53033.0, 53034.0],       [53035.0, 53036.0, 53037.0, 53038.0, 53039.0, 53040.0],       [53041.0, 53042.0, 53043.0, 53044.0, 53045.0, 53046.0]],      [[53047.0, 53048.0, 53049.0, 53050.0, 53051.0, 53052.0],       [53053.0, 53054.0, 53055.0, 53056.0, 53057.0, 53058.0],       [53059.0, 53060.0, 53061.0, 53062.0, 53063.0, 53064.0],       [53065.0, 53066.0, 53067.0, 53068.0, 53069.0, 53070.0],       [53071.0, 53072.0, 53073.0, 53074.0, 53075.0, 53076.0],       [53077.0, 53078.0, 53079.0, 53080.0, 53081.0, 53082.0],       [53083.0, 53084.0, 53085.0, 53086.0, 53087.0, 53088.0]],      [[53089.0, 53090.0, 53091.0, 53092.0, 53093.0, 53094.0],       [53095.0, 53096.0, 53097.0, 53098.0, 53099.0, 53100.0],       [53101.0, 53102.0, 53103.0, 53104.0, 53105.0, 53106.0],       [53107.0, 53108.0, 53109.0, 53110.0, 53111.0, 53112.0],       [53113.0, 53114.0, 53115.0, 53116.0, 53117.0, 53118.0],       [53119.0, 53120.0, 53121.0, 53122.0, 53123.0, 53124.0],       [53125.0, 53126.0, 53127.0, 53128.0, 53129.0, 53130.0]],      [[53131.0, 53132.0, 53133.0, 53134.0, 53135.0, 53136.0],       [53137.0, 53138.0, 53139.0, 53140.0, 53141.0, 53142.0],       [53143.0, 53144.0, 53145.0, 53146.0, 53147.0, 53148.0],       [53149.0, 53150.0, 53151.0, 53152.0, 53153.0, 53154.0],       [53155.0, 53156.0, 53157.0, 53158.0, 53159.0, 53160.0],       [53161.0, 53162.0, 53163.0, 53164.0, 53165.0, 53166.0],       [53167.0, 53168.0, 53169.0, 53170.0, 53171.0, 53172.0]]],     [[[53173.0, 53174.0, 53175.0, 53176.0, 53177.0, 53178.0],       [53179.0, 53180.0, 53181.0, 53182.0, 53183.0, 53184.0],       [53185.0, 53186.0, 53187.0, 53188.0, 53189.0, 53190.0],       [53191.0, 53192.0, 53193.0, 53194.0, 53195.0, 53196.0],       [53197.0, 53198.0, 53199.0, 53200.0, 53201.0, 53202.0],       [53203.0, 53204.0, 53205.0, 53206.0, 53207.0, 53208.0],       [53209.0, 53210.0, 53211.0, 53212.0, 53213.0, 53214.0]],      [[53215.0, 53216.0, 53217.0, 53218.0, 53219.0, 53220.0],       [53221.0, 53222.0, 53223.0, 53224.0, 53225.0, 53226.0],       [53227.0, 53228.0, 53229.0, 53230.0, 53231.0, 53232.0],       [53233.0, 53234.0, 53235.0, 53236.0, 53237.0, 53238.0],       [53239.0, 53240.0, 53241.0, 53242.0, 53243.0, 53244.0],       [53245.0, 53246.0, 53247.0, 53248.0, 53249.0, 53250.0],       [53251.0, 53252.0, 53253.0, 53254.0, 53255.0, 53256.0]],      [[53257.0, 53258.0, 53259.0, 53260.0, 53261.0, 53262.0],       [53263.0, 53264.0, 53265.0, 53266.0, 53267.0, 53268.0],       [53269.0, 53270.0, 53271.0, 53272.0, 53273.0, 53274.0],       [53275.0, 53276.0, 53277.0, 53278.0, 53279.0, 53280.0],       [53281.0, 53282.0, 53283.0, 53284.0, 53285.0, 53286.0],       [53287.0, 53288.0, 53289.0, 53290.0, 53291.0, 53292.0],       [53293.0, 53294.0, 53295.0, 53296.0, 53297.0, 53298.0]],      [[53299.0, 53300.0, 53301.0, 53302.0, 53303.0, 53304.0],       [53305.0, 53306.0, 53307.0, 53308.0, 53309.0, 53310.0],       [53311.0, 53312.0, 53313.0, 53314.0, 53315.0, 53316.0],       [53317.0, 53318.0, 53319.0, 53320.0, 53321.0, 53322.0],       [53323.0, 53324.0, 53325.0, 53326.0, 53327.0, 53328.0],       [53329.0, 53330.0, 53331.0, 53332.0, 53333.0, 53334.0],       [53335.0, 53336.0, 53337.0, 53338.0, 53339.0, 53340.0]],      [[53341.0, 53342.0, 53343.0, 53344.0, 53345.0, 53346.0],       [53347.0, 53348.0, 53349.0, 53350.0, 53351.0, 53352.0],       [53353.0, 53354.0, 53355.0, 53356.0, 53357.0, 53358.0],       [53359.0, 53360.0, 53361.0, 53362.0, 53363.0, 53364.0],       [53365.0, 53366.0, 53367.0, 53368.0, 53369.0, 53370.0],       [53371.0, 53372.0, 53373.0, 53374.0, 53375.0, 53376.0],       [53377.0, 53378.0, 53379.0, 53380.0, 53381.0, 53382.0]],      [[53383.0, 53384.0, 53385.0, 53386.0, 53387.0, 53388.0],       [53389.0, 53390.0, 53391.0, 53392.0, 53393.0, 53394.0],       [53395.0, 53396.0, 53397.0, 53398.0, 53399.0, 53400.0],       [53401.0, 53402.0, 53403.0, 53404.0, 53405.0, 53406.0],       [53407.0, 53408.0, 53409.0, 53410.0, 53411.0, 53412.0],       [53413.0, 53414.0, 53415.0, 53416.0, 53417.0, 53418.0],       [53419.0, 53420.0, 53421.0, 53422.0, 53423.0, 53424.0]]]],    [[[[53425.0, 53426.0, 53427.0, 53428.0, 53429.0, 53430.0],       [53431.0, 53432.0, 53433.0, 53434.0, 53435.0, 53436.0],       [53437.0, 53438.0, 53439.0, 53440.0, 53441.0, 53442.0],       [53443.0, 53444.0, 53445.0, 53446.0, 53447.0, 53448.0],       [53449.0, 53450.0, 53451.0, 53452.0, 53453.0, 53454.0],       [53455.0, 53456.0, 53457.0, 53458.0, 53459.0, 53460.0],       [53461.0, 53462.0, 53463.0, 53464.0, 53465.0, 53466.0]],      [[53467.0, 53468.0, 53469.0, 53470.0, 53471.0, 53472.0],       [53473.0, 53474.0, 53475.0, 53476.0, 53477.0, 53478.0],       [53479.0, 53480.0, 53481.0, 53482.0, 53483.0, 53484.0],       [53485.0, 53486.0, 53487.0, 53488.0, 53489.0, 53490.0],       [53491.0, 53492.0, 53493.0, 53494.0, 53495.0, 53496.0],       [53497.0, 53498.0, 53499.0, 53500.0, 53501.0, 53502.0],       [53503.0, 53504.0, 53505.0, 53506.0, 53507.0, 53508.0]],      [[53509.0, 53510.0, 53511.0, 53512.0, 53513.0, 53514.0],       [53515.0, 53516.0, 53517.0, 53518.0, 53519.0, 53520.0],       [53521.0, 53522.0, 53523.0, 53524.0, 53525.0, 53526.0],       [53527.0, 53528.0, 53529.0, 53530.0, 53531.0, 53532.0],       [53533.0, 53534.0, 53535.0, 53536.0, 53537.0, 53538.0],       [53539.0, 53540.0, 53541.0, 53542.0, 53543.0, 53544.0],       [53545.0, 53546.0, 53547.0, 53548.0, 53549.0, 53550.0]],      [[53551.0, 53552.0, 53553.0, 53554.0, 53555.0, 53556.0],       [53557.0, 53558.0, 53559.0, 53560.0, 53561.0, 53562.0],       [53563.0, 53564.0, 53565.0, 53566.0, 53567.0, 53568.0],       [53569.0, 53570.0, 53571.0, 53572.0, 53573.0, 53574.0],       [53575.0, 53576.0, 53577.0, 53578.0, 53579.0, 53580.0],       [53581.0, 53582.0, 53583.0, 53584.0, 53585.0, 53586.0],       [53587.0, 53588.0, 53589.0, 53590.0, 53591.0, 53592.0]],      [[53593.0, 53594.0, 53595.0, 53596.0, 53597.0, 53598.0],       [53599.0, 53600.0, 53601.0, 53602.0, 53603.0, 53604.0],       [53605.0, 53606.0, 53607.0, 53608.0, 53609.0, 53610.0],       [53611.0, 53612.0, 53613.0, 53614.0, 53615.0, 53616.0],       [53617.0, 53618.0, 53619.0, 53620.0, 53621.0, 53622.0],       [53623.0, 53624.0, 53625.0, 53626.0, 53627.0, 53628.0],       [53629.0, 53630.0, 53631.0, 53632.0, 53633.0, 53634.0]],      [[53635.0, 53636.0, 53637.0, 53638.0, 53639.0, 53640.0],       [53641.0, 53642.0, 53643.0, 53644.0, 53645.0, 53646.0],       [53647.0, 53648.0, 53649.0, 53650.0, 53651.0, 53652.0],       [53653.0, 53654.0, 53655.0, 53656.0, 53657.0, 53658.0],       [53659.0, 53660.0, 53661.0, 53662.0, 53663.0, 53664.0],       [53665.0, 53666.0, 53667.0, 53668.0, 53669.0, 53670.0],       [53671.0, 53672.0, 53673.0, 53674.0, 53675.0, 53676.0]]],     [[[53677.0, 53678.0, 53679.0, 53680.0, 53681.0, 53682.0],       [53683.0, 53684.0, 53685.0, 53686.0, 53687.0, 53688.0],       [53689.0, 53690.0, 53691.0, 53692.0, 53693.0, 53694.0],       [53695.0, 53696.0, 53697.0, 53698.0, 53699.0, 53700.0],       [53701.0, 53702.0, 53703.0, 53704.0, 53705.0, 53706.0],       [53707.0, 53708.0, 53709.0, 53710.0, 53711.0, 53712.0],       [53713.0, 53714.0, 53715.0, 53716.0, 53717.0, 53718.0]],      [[53719.0, 53720.0, 53721.0, 53722.0, 53723.0, 53724.0],       [53725.0, 53726.0, 53727.0, 53728.0, 53729.0, 53730.0],       [53731.0, 53732.0, 53733.0, 53734.0, 53735.0, 53736.0],       [53737.0, 53738.0, 53739.0, 53740.0, 53741.0, 53742.0],       [53743.0, 53744.0, 53745.0, 53746.0, 53747.0, 53748.0],       [53749.0, 53750.0, 53751.0, 53752.0, 53753.0, 53754.0],       [53755.0, 53756.0, 53757.0, 53758.0, 53759.0, 53760.0]],      [[53761.0, 53762.0, 53763.0, 53764.0, 53765.0, 53766.0],       [53767.0, 53768.0, 53769.0, 53770.0, 53771.0, 53772.0],       [53773.0, 53774.0, 53775.0, 53776.0, 53777.0, 53778.0],       [53779.0, 53780.0, 53781.0, 53782.0, 53783.0, 53784.0],       [53785.0, 53786.0, 53787.0, 53788.0, 53789.0, 53790.0],       [53791.0, 53792.0, 53793.0, 53794.0, 53795.0, 53796.0],       [53797.0, 53798.0, 53799.0, 53800.0, 53801.0, 53802.0]],      [[53803.0, 53804.0, 53805.0, 53806.0, 53807.0, 53808.0],       [53809.0, 53810.0, 53811.0, 53812.0, 53813.0, 53814.0],       [53815.0, 53816.0, 53817.0, 53818.0, 53819.0, 53820.0],       [53821.0, 53822.0, 53823.0, 53824.0, 53825.0, 53826.0],       [53827.0, 53828.0, 53829.0, 53830.0, 53831.0, 53832.0],       [53833.0, 53834.0, 53835.0, 53836.0, 53837.0, 53838.0],       [53839.0, 53840.0, 53841.0, 53842.0, 53843.0, 53844.0]],      [[53845.0, 53846.0, 53847.0, 53848.0, 53849.0, 53850.0],       [53851.0, 53852.0, 53853.0, 53854.0, 53855.0, 53856.0],       [53857.0, 53858.0, 53859.0, 53860.0, 53861.0, 53862.0],       [53863.0, 53864.0, 53865.0, 53866.0, 53867.0, 53868.0],       [53869.0, 53870.0, 53871.0, 53872.0, 53873.0, 53874.0],       [53875.0, 53876.0, 53877.0, 53878.0, 53879.0, 53880.0],       [53881.0, 53882.0, 53883.0, 53884.0, 53885.0, 53886.0]],      [[53887.0, 53888.0, 53889.0, 53890.0, 53891.0, 53892.0],       [53893.0, 53894.0, 53895.0, 53896.0, 53897.0, 53898.0],       [53899.0, 53900.0, 53901.0, 53902.0, 53903.0, 53904.0],       [53905.0, 53906.0, 53907.0, 53908.0, 53909.0, 53910.0],       [53911.0, 53912.0, 53913.0, 53914.0, 53915.0, 53916.0],       [53917.0, 53918.0, 53919.0, 53920.0, 53921.0, 53922.0],       [53923.0, 53924.0, 53925.0, 53926.0, 53927.0, 53928.0]]],     [[[53929.0, 53930.0, 53931.0, 53932.0, 53933.0, 53934.0],       [53935.0, 53936.0, 53937.0, 53938.0, 53939.0, 53940.0],       [53941.0, 53942.0, 53943.0, 53944.0, 53945.0, 53946.0],       [53947.0, 53948.0, 53949.0, 53950.0, 53951.0, 53952.0],       [53953.0, 53954.0, 53955.0, 53956.0, 53957.0, 53958.0],       [53959.0, 53960.0, 53961.0, 53962.0, 53963.0, 53964.0],       [53965.0, 53966.0, 53967.0, 53968.0, 53969.0, 53970.0]],      [[53971.0, 53972.0, 53973.0, 53974.0, 53975.0, 53976.0],       [53977.0, 53978.0, 53979.0, 53980.0, 53981.0, 53982.0],       [53983.0, 53984.0, 53985.0, 53986.0, 53987.0, 53988.0],       [53989.0, 53990.0, 53991.0, 53992.0, 53993.0, 53994.0],       [53995.0, 53996.0, 53997.0, 53998.0, 53999.0, 54000.0],       [54001.0, 54002.0, 54003.0, 54004.0, 54005.0, 54006.0],       [54007.0, 54008.0, 54009.0, 54010.0, 54011.0, 54012.0]],      [[54013.0, 54014.0, 54015.0, 54016.0, 54017.0, 54018.0],       [54019.0, 54020.0, 54021.0, 54022.0, 54023.0, 54024.0],       [54025.0, 54026.0, 54027.0, 54028.0, 54029.0, 54030.0],       [54031.0, 54032.0, 54033.0, 54034.0, 54035.0, 54036.0],       [54037.0, 54038.0, 54039.0, 54040.0, 54041.0, 54042.0],       [54043.0, 54044.0, 54045.0, 54046.0, 54047.0, 54048.0],       [54049.0, 54050.0, 54051.0, 54052.0, 54053.0, 54054.0]],      [[54055.0, 54056.0, 54057.0, 54058.0, 54059.0, 54060.0],       [54061.0, 54062.0, 54063.0, 54064.0, 54065.0, 54066.0],       [54067.0, 54068.0, 54069.0, 54070.0, 54071.0, 54072.0],       [54073.0, 54074.0, 54075.0, 54076.0, 54077.0, 54078.0],       [54079.0, 54080.0, 54081.0, 54082.0, 54083.0, 54084.0],       [54085.0, 54086.0, 54087.0, 54088.0, 54089.0, 54090.0],       [54091.0, 54092.0, 54093.0, 54094.0, 54095.0, 54096.0]],      [[54097.0, 54098.0, 54099.0, 54100.0, 54101.0, 54102.0],       [54103.0, 54104.0, 54105.0, 54106.0, 54107.0, 54108.0],       [54109.0, 54110.0, 54111.0, 54112.0, 54113.0, 54114.0],       [54115.0, 54116.0, 54117.0, 54118.0, 54119.0, 54120.0],       [54121.0, 54122.0, 54123.0, 54124.0, 54125.0, 54126.0],       [54127.0, 54128.0, 54129.0, 54130.0, 54131.0, 54132.0],       [54133.0, 54134.0, 54135.0, 54136.0, 54137.0, 54138.0]],      [[54139.0, 54140.0, 54141.0, 54142.0, 54143.0, 54144.0],       [54145.0, 54146.0, 54147.0, 54148.0, 54149.0, 54150.0],       [54151.0, 54152.0, 54153.0, 54154.0, 54155.0, 54156.0],       [54157.0, 54158.0, 54159.0, 54160.0, 54161.0, 54162.0],       [54163.0, 54164.0, 54165.0, 54166.0, 54167.0, 54168.0],       [54169.0, 54170.0, 54171.0, 54172.0, 54173.0, 54174.0],       [54175.0, 54176.0, 54177.0, 54178.0, 54179.0, 54180.0]]],     [[[54181.0, 54182.0, 54183.0, 54184.0, 54185.0, 54186.0],       [54187.0, 54188.0, 54189.0, 54190.0, 54191.0, 54192.0],       [54193.0, 54194.0, 54195.0, 54196.0, 54197.0, 54198.0],       [54199.0, 54200.0, 54201.0, 54202.0, 54203.0, 54204.0],       [54205.0, 54206.0, 54207.0, 54208.0, 54209.0, 54210.0],       [54211.0, 54212.0, 54213.0, 54214.0, 54215.0, 54216.0],       [54217.0, 54218.0, 54219.0, 54220.0, 54221.0, 54222.0]],      [[54223.0, 54224.0, 54225.0, 54226.0, 54227.0, 54228.0],       [54229.0, 54230.0, 54231.0, 54232.0, 54233.0, 54234.0],       [54235.0, 54236.0, 54237.0, 54238.0, 54239.0, 54240.0],       [54241.0, 54242.0, 54243.0, 54244.0, 54245.0, 54246.0],       [54247.0, 54248.0, 54249.0, 54250.0, 54251.0, 54252.0],       [54253.0, 54254.0, 54255.0, 54256.0, 54257.0, 54258.0],       [54259.0, 54260.0, 54261.0, 54262.0, 54263.0, 54264.0]],      [[54265.0, 54266.0, 54267.0, 54268.0, 54269.0, 54270.0],       [54271.0, 54272.0, 54273.0, 54274.0, 54275.0, 54276.0],       [54277.0, 54278.0, 54279.0, 54280.0, 54281.0, 54282.0],       [54283.0, 54284.0, 54285.0, 54286.0, 54287.0, 54288.0],       [54289.0, 54290.0, 54291.0, 54292.0, 54293.0, 54294.0],       [54295.0, 54296.0, 54297.0, 54298.0, 54299.0, 54300.0],       [54301.0, 54302.0, 54303.0, 54304.0, 54305.0, 54306.0]],      [[54307.0, 54308.0, 54309.0, 54310.0, 54311.0, 54312.0],       [54313.0, 54314.0, 54315.0, 54316.0, 54317.0, 54318.0],       [54319.0, 54320.0, 54321.0, 54322.0, 54323.0, 54324.0],       [54325.0, 54326.0, 54327.0, 54328.0, 54329.0, 54330.0],       [54331.0, 54332.0, 54333.0, 54334.0, 54335.0, 54336.0],       [54337.0, 54338.0, 54339.0, 54340.0, 54341.0, 54342.0],       [54343.0, 54344.0, 54345.0, 54346.0, 54347.0, 54348.0]],      [[54349.0, 54350.0, 54351.0, 54352.0, 54353.0, 54354.0],       [54355.0, 54356.0, 54357.0, 54358.0, 54359.0, 54360.0],       [54361.0, 54362.0, 54363.0, 54364.0, 54365.0, 54366.0],       [54367.0, 54368.0, 54369.0, 54370.0, 54371.0, 54372.0],       [54373.0, 54374.0, 54375.0, 54376.0, 54377.0, 54378.0],       [54379.0, 54380.0, 54381.0, 54382.0, 54383.0, 54384.0],       [54385.0, 54386.0, 54387.0, 54388.0, 54389.0, 54390.0]],      [[54391.0, 54392.0, 54393.0, 54394.0, 54395.0, 54396.0],       [54397.0, 54398.0, 54399.0, 54400.0, 54401.0, 54402.0],       [54403.0, 54404.0, 54405.0, 54406.0, 54407.0, 54408.0],       [54409.0, 54410.0, 54411.0, 54412.0, 54413.0, 54414.0],       [54415.0, 54416.0, 54417.0, 54418.0, 54419.0, 54420.0],       [54421.0, 54422.0, 54423.0, 54424.0, 54425.0, 54426.0],       [54427.0, 54428.0, 54429.0, 54430.0, 54431.0, 54432.0]]]]],   [[[[[54433.0, 54434.0, 54435.0, 54436.0, 54437.0, 54438.0],       [54439.0, 54440.0, 54441.0, 54442.0, 54443.0, 54444.0],       [54445.0, 54446.0, 54447.0, 54448.0, 54449.0, 54450.0],       [54451.0, 54452.0, 54453.0, 54454.0, 54455.0, 54456.0],       [54457.0, 54458.0, 54459.0, 54460.0, 54461.0, 54462.0],       [54463.0, 54464.0, 54465.0, 54466.0, 54467.0, 54468.0],       [54469.0, 54470.0, 54471.0, 54472.0, 54473.0, 54474.0]],      [[54475.0, 54476.0, 54477.0, 54478.0, 54479.0, 54480.0],       [54481.0, 54482.0, 54483.0, 54484.0, 54485.0, 54486.0],       [54487.0, 54488.0, 54489.0, 54490.0, 54491.0, 54492.0],       [54493.0, 54494.0, 54495.0, 54496.0, 54497.0, 54498.0],       [54499.0, 54500.0, 54501.0, 54502.0, 54503.0, 54504.0],       [54505.0, 54506.0, 54507.0, 54508.0, 54509.0, 54510.0],       [54511.0, 54512.0, 54513.0, 54514.0, 54515.0, 54516.0]],      [[54517.0, 54518.0, 54519.0, 54520.0, 54521.0, 54522.0],       [54523.0, 54524.0, 54525.0, 54526.0, 54527.0, 54528.0],       [54529.0, 54530.0, 54531.0, 54532.0, 54533.0, 54534.0],       [54535.0, 54536.0, 54537.0, 54538.0, 54539.0, 54540.0],       [54541.0, 54542.0, 54543.0, 54544.0, 54545.0, 54546.0],       [54547.0, 54548.0, 54549.0, 54550.0, 54551.0, 54552.0],       [54553.0, 54554.0, 54555.0, 54556.0, 54557.0, 54558.0]],      [[54559.0, 54560.0, 54561.0, 54562.0, 54563.0, 54564.0],       [54565.0, 54566.0, 54567.0, 54568.0, 54569.0, 54570.0],       [54571.0, 54572.0, 54573.0, 54574.0, 54575.0, 54576.0],       [54577.0, 54578.0, 54579.0, 54580.0, 54581.0, 54582.0],       [54583.0, 54584.0, 54585.0, 54586.0, 54587.0, 54588.0],       [54589.0, 54590.0, 54591.0, 54592.0, 54593.0, 54594.0],       [54595.0, 54596.0, 54597.0, 54598.0, 54599.0, 54600.0]],      [[54601.0, 54602.0, 54603.0, 54604.0, 54605.0, 54606.0],       [54607.0, 54608.0, 54609.0, 54610.0, 54611.0, 54612.0],       [54613.0, 54614.0, 54615.0, 54616.0, 54617.0, 54618.0],       [54619.0, 54620.0, 54621.0, 54622.0, 54623.0, 54624.0],       [54625.0, 54626.0, 54627.0, 54628.0, 54629.0, 54630.0],       [54631.0, 54632.0, 54633.0, 54634.0, 54635.0, 54636.0],       [54637.0, 54638.0, 54639.0, 54640.0, 54641.0, 54642.0]],      [[54643.0, 54644.0, 54645.0, 54646.0, 54647.0, 54648.0],       [54649.0, 54650.0, 54651.0, 54652.0, 54653.0, 54654.0],       [54655.0, 54656.0, 54657.0, 54658.0, 54659.0, 54660.0],       [54661.0, 54662.0, 54663.0, 54664.0, 54665.0, 54666.0],       [54667.0, 54668.0, 54669.0, 54670.0, 54671.0, 54672.0],       [54673.0, 54674.0, 54675.0, 54676.0, 54677.0, 54678.0],       [54679.0, 54680.0, 54681.0, 54682.0, 54683.0, 54684.0]]],     [[[54685.0, 54686.0, 54687.0, 54688.0, 54689.0, 54690.0],       [54691.0, 54692.0, 54693.0, 54694.0, 54695.0, 54696.0],       [54697.0, 54698.0, 54699.0, 54700.0, 54701.0, 54702.0],       [54703.0, 54704.0, 54705.0, 54706.0, 54707.0, 54708.0],       [54709.0, 54710.0, 54711.0, 54712.0, 54713.0, 54714.0],       [54715.0, 54716.0, 54717.0, 54718.0, 54719.0, 54720.0],       [54721.0, 54722.0, 54723.0, 54724.0, 54725.0, 54726.0]],      [[54727.0, 54728.0, 54729.0, 54730.0, 54731.0, 54732.0],       [54733.0, 54734.0, 54735.0, 54736.0, 54737.0, 54738.0],       [54739.0, 54740.0, 54741.0, 54742.0, 54743.0, 54744.0],       [54745.0, 54746.0, 54747.0, 54748.0, 54749.0, 54750.0],       [54751.0, 54752.0, 54753.0, 54754.0, 54755.0, 54756.0],       [54757.0, 54758.0, 54759.0, 54760.0, 54761.0, 54762.0],       [54763.0, 54764.0, 54765.0, 54766.0, 54767.0, 54768.0]],      [[54769.0, 54770.0, 54771.0, 54772.0, 54773.0, 54774.0],       [54775.0, 54776.0, 54777.0, 54778.0, 54779.0, 54780.0],       [54781.0, 54782.0, 54783.0, 54784.0, 54785.0, 54786.0],       [54787.0, 54788.0, 54789.0, 54790.0, 54791.0, 54792.0],       [54793.0, 54794.0, 54795.0, 54796.0, 54797.0, 54798.0],       [54799.0, 54800.0, 54801.0, 54802.0, 54803.0, 54804.0],       [54805.0, 54806.0, 54807.0, 54808.0, 54809.0, 54810.0]],      [[54811.0, 54812.0, 54813.0, 54814.0, 54815.0, 54816.0],       [54817.0, 54818.0, 54819.0, 54820.0, 54821.0, 54822.0],       [54823.0, 54824.0, 54825.0, 54826.0, 54827.0, 54828.0],       [54829.0, 54830.0, 54831.0, 54832.0, 54833.0, 54834.0],       [54835.0, 54836.0, 54837.0, 54838.0, 54839.0, 54840.0],       [54841.0, 54842.0, 54843.0, 54844.0, 54845.0, 54846.0],       [54847.0, 54848.0, 54849.0, 54850.0, 54851.0, 54852.0]],      [[54853.0, 54854.0, 54855.0, 54856.0, 54857.0, 54858.0],       [54859.0, 54860.0, 54861.0, 54862.0, 54863.0, 54864.0],       [54865.0, 54866.0, 54867.0, 54868.0, 54869.0, 54870.0],       [54871.0, 54872.0, 54873.0, 54874.0, 54875.0, 54876.0],       [54877.0, 54878.0, 54879.0, 54880.0, 54881.0, 54882.0],       [54883.0, 54884.0, 54885.0, 54886.0, 54887.0, 54888.0],       [54889.0, 54890.0, 54891.0, 54892.0, 54893.0, 54894.0]],      [[54895.0, 54896.0, 54897.0, 54898.0, 54899.0, 54900.0],       [54901.0, 54902.0, 54903.0, 54904.0, 54905.0, 54906.0],       [54907.0, 54908.0, 54909.0, 54910.0, 54911.0, 54912.0],       [54913.0, 54914.0, 54915.0, 54916.0, 54917.0, 54918.0],       [54919.0, 54920.0, 54921.0, 54922.0, 54923.0, 54924.0],       [54925.0, 54926.0, 54927.0, 54928.0, 54929.0, 54930.0],       [54931.0, 54932.0, 54933.0, 54934.0, 54935.0, 54936.0]]],     [[[54937.0, 54938.0, 54939.0, 54940.0, 54941.0, 54942.0],       [54943.0, 54944.0, 54945.0, 54946.0, 54947.0, 54948.0],       [54949.0, 54950.0, 54951.0, 54952.0, 54953.0, 54954.0],       [54955.0, 54956.0, 54957.0, 54958.0, 54959.0, 54960.0],       [54961.0, 54962.0, 54963.0, 54964.0, 54965.0, 54966.0],       [54967.0, 54968.0, 54969.0, 54970.0, 54971.0, 54972.0],       [54973.0, 54974.0, 54975.0, 54976.0, 54977.0, 54978.0]],      [[54979.0, 54980.0, 54981.0, 54982.0, 54983.0, 54984.0],       [54985.0, 54986.0, 54987.0, 54988.0, 54989.0, 54990.0],       [54991.0, 54992.0, 54993.0, 54994.0, 54995.0, 54996.0],       [54997.0, 54998.0, 54999.0, 55000.0, 55001.0, 55002.0],       [55003.0, 55004.0, 55005.0, 55006.0, 55007.0, 55008.0],       [55009.0, 55010.0, 55011.0, 55012.0, 55013.0, 55014.0],       [55015.0, 55016.0, 55017.0, 55018.0, 55019.0, 55020.0]],      [[55021.0, 55022.0, 55023.0, 55024.0, 55025.0, 55026.0],       [55027.0, 55028.0, 55029.0, 55030.0, 55031.0, 55032.0],       [55033.0, 55034.0, 55035.0, 55036.0, 55037.0, 55038.0],       [55039.0, 55040.0, 55041.0, 55042.0, 55043.0, 55044.0],       [55045.0, 55046.0, 55047.0, 55048.0, 55049.0, 55050.0],       [55051.0, 55052.0, 55053.0, 55054.0, 55055.0, 55056.0],       [55057.0, 55058.0, 55059.0, 55060.0, 55061.0, 55062.0]],      [[55063.0, 55064.0, 55065.0, 55066.0, 55067.0, 55068.0],       [55069.0, 55070.0, 55071.0, 55072.0, 55073.0, 55074.0],       [55075.0, 55076.0, 55077.0, 55078.0, 55079.0, 55080.0],       [55081.0, 55082.0, 55083.0, 55084.0, 55085.0, 55086.0],       [55087.0, 55088.0, 55089.0, 55090.0, 55091.0, 55092.0],       [55093.0, 55094.0, 55095.0, 55096.0, 55097.0, 55098.0],       [55099.0, 55100.0, 55101.0, 55102.0, 55103.0, 55104.0]],      [[55105.0, 55106.0, 55107.0, 55108.0, 55109.0, 55110.0],       [55111.0, 55112.0, 55113.0, 55114.0, 55115.0, 55116.0],       [55117.0, 55118.0, 55119.0, 55120.0, 55121.0, 55122.0],       [55123.0, 55124.0, 55125.0, 55126.0, 55127.0, 55128.0],       [55129.0, 55130.0, 55131.0, 55132.0, 55133.0, 55134.0],       [55135.0, 55136.0, 55137.0, 55138.0, 55139.0, 55140.0],       [55141.0, 55142.0, 55143.0, 55144.0, 55145.0, 55146.0]],      [[55147.0, 55148.0, 55149.0, 55150.0, 55151.0, 55152.0],       [55153.0, 55154.0, 55155.0, 55156.0, 55157.0, 55158.0],       [55159.0, 55160.0, 55161.0, 55162.0, 55163.0, 55164.0],       [55165.0, 55166.0, 55167.0, 55168.0, 55169.0, 55170.0],       [55171.0, 55172.0, 55173.0, 55174.0, 55175.0, 55176.0],       [55177.0, 55178.0, 55179.0, 55180.0, 55181.0, 55182.0],       [55183.0, 55184.0, 55185.0, 55186.0, 55187.0, 55188.0]]],     [[[55189.0, 55190.0, 55191.0, 55192.0, 55193.0, 55194.0],       [55195.0, 55196.0, 55197.0, 55198.0, 55199.0, 55200.0],       [55201.0, 55202.0, 55203.0, 55204.0, 55205.0, 55206.0],       [55207.0, 55208.0, 55209.0, 55210.0, 55211.0, 55212.0],       [55213.0, 55214.0, 55215.0, 55216.0, 55217.0, 55218.0],       [55219.0, 55220.0, 55221.0, 55222.0, 55223.0, 55224.0],       [55225.0, 55226.0, 55227.0, 55228.0, 55229.0, 55230.0]],      [[55231.0, 55232.0, 55233.0, 55234.0, 55235.0, 55236.0],       [55237.0, 55238.0, 55239.0, 55240.0, 55241.0, 55242.0],       [55243.0, 55244.0, 55245.0, 55246.0, 55247.0, 55248.0],       [55249.0, 55250.0, 55251.0, 55252.0, 55253.0, 55254.0],       [55255.0, 55256.0, 55257.0, 55258.0, 55259.0, 55260.0],       [55261.0, 55262.0, 55263.0, 55264.0, 55265.0, 55266.0],       [55267.0, 55268.0, 55269.0, 55270.0, 55271.0, 55272.0]],      [[55273.0, 55274.0, 55275.0, 55276.0, 55277.0, 55278.0],       [55279.0, 55280.0, 55281.0, 55282.0, 55283.0, 55284.0],       [55285.0, 55286.0, 55287.0, 55288.0, 55289.0, 55290.0],       [55291.0, 55292.0, 55293.0, 55294.0, 55295.0, 55296.0],       [55297.0, 55298.0, 55299.0, 55300.0, 55301.0, 55302.0],       [55303.0, 55304.0, 55305.0, 55306.0, 55307.0, 55308.0],       [55309.0, 55310.0, 55311.0, 55312.0, 55313.0, 55314.0]],      [[55315.0, 55316.0, 55317.0, 55318.0, 55319.0, 55320.0],       [55321.0, 55322.0, 55323.0, 55324.0, 55325.0, 55326.0],       [55327.0, 55328.0, 55329.0, 55330.0, 55331.0, 55332.0],       [55333.0, 55334.0, 55335.0, 55336.0, 55337.0, 55338.0],       [55339.0, 55340.0, 55341.0, 55342.0, 55343.0, 55344.0],       [55345.0, 55346.0, 55347.0, 55348.0, 55349.0, 55350.0],       [55351.0, 55352.0, 55353.0, 55354.0, 55355.0, 55356.0]],      [[55357.0, 55358.0, 55359.0, 55360.0, 55361.0, 55362.0],       [55363.0, 55364.0, 55365.0, 55366.0, 55367.0, 55368.0],       [55369.0, 55370.0, 55371.0, 55372.0, 55373.0, 55374.0],       [55375.0, 55376.0, 55377.0, 55378.0, 55379.0, 55380.0],       [55381.0, 55382.0, 55383.0, 55384.0, 55385.0, 55386.0],       [55387.0, 55388.0, 55389.0, 55390.0, 55391.0, 55392.0],       [55393.0, 55394.0, 55395.0, 55396.0, 55397.0, 55398.0]],      [[55399.0, 55400.0, 55401.0, 55402.0, 55403.0, 55404.0],       [55405.0, 55406.0, 55407.0, 55408.0, 55409.0, 55410.0],       [55411.0, 55412.0, 55413.0, 55414.0, 55415.0, 55416.0],       [55417.0, 55418.0, 55419.0, 55420.0, 55421.0, 55422.0],       [55423.0, 55424.0, 55425.0, 55426.0, 55427.0, 55428.0],       [55429.0, 55430.0, 55431.0, 55432.0, 55433.0, 55434.0],       [55435.0, 55436.0, 55437.0, 55438.0, 55439.0, 55440.0]]]],    [[[[55441.0, 55442.0, 55443.0, 55444.0, 55445.0, 55446.0],       [55447.0, 55448.0, 55449.0, 55450.0, 55451.0, 55452.0],       [55453.0, 55454.0, 55455.0, 55456.0, 55457.0, 55458.0],       [55459.0, 55460.0, 55461.0, 55462.0, 55463.0, 55464.0],       [55465.0, 55466.0, 55467.0, 55468.0, 55469.0, 55470.0],       [55471.0, 55472.0, 55473.0, 55474.0, 55475.0, 55476.0],       [55477.0, 55478.0, 55479.0, 55480.0, 55481.0, 55482.0]],      [[55483.0, 55484.0, 55485.0, 55486.0, 55487.0, 55488.0],       [55489.0, 55490.0, 55491.0, 55492.0, 55493.0, 55494.0],       [55495.0, 55496.0, 55497.0, 55498.0, 55499.0, 55500.0],       [55501.0, 55502.0, 55503.0, 55504.0, 55505.0, 55506.0],       [55507.0, 55508.0, 55509.0, 55510.0, 55511.0, 55512.0],       [55513.0, 55514.0, 55515.0, 55516.0, 55517.0, 55518.0],       [55519.0, 55520.0, 55521.0, 55522.0, 55523.0, 55524.0]],      [[55525.0, 55526.0, 55527.0, 55528.0, 55529.0, 55530.0],       [55531.0, 55532.0, 55533.0, 55534.0, 55535.0, 55536.0],       [55537.0, 55538.0, 55539.0, 55540.0, 55541.0, 55542.0],       [55543.0, 55544.0, 55545.0, 55546.0, 55547.0, 55548.0],       [55549.0, 55550.0, 55551.0, 55552.0, 55553.0, 55554.0],       [55555.0, 55556.0, 55557.0, 55558.0, 55559.0, 55560.0],       [55561.0, 55562.0, 55563.0, 55564.0, 55565.0, 55566.0]],      [[55567.0, 55568.0, 55569.0, 55570.0, 55571.0, 55572.0],       [55573.0, 55574.0, 55575.0, 55576.0, 55577.0, 55578.0],       [55579.0, 55580.0, 55581.0, 55582.0, 55583.0, 55584.0],       [55585.0, 55586.0, 55587.0, 55588.0, 55589.0, 55590.0],       [55591.0, 55592.0, 55593.0, 55594.0, 55595.0, 55596.0],       [55597.0, 55598.0, 55599.0, 55600.0, 55601.0, 55602.0],       [55603.0, 55604.0, 55605.0, 55606.0, 55607.0, 55608.0]],      [[55609.0, 55610.0, 55611.0, 55612.0, 55613.0, 55614.0],       [55615.0, 55616.0, 55617.0, 55618.0, 55619.0, 55620.0],       [55621.0, 55622.0, 55623.0, 55624.0, 55625.0, 55626.0],       [55627.0, 55628.0, 55629.0, 55630.0, 55631.0, 55632.0],       [55633.0, 55634.0, 55635.0, 55636.0, 55637.0, 55638.0],       [55639.0, 55640.0, 55641.0, 55642.0, 55643.0, 55644.0],       [55645.0, 55646.0, 55647.0, 55648.0, 55649.0, 55650.0]],      [[55651.0, 55652.0, 55653.0, 55654.0, 55655.0, 55656.0],       [55657.0, 55658.0, 55659.0, 55660.0, 55661.0, 55662.0],       [55663.0, 55664.0, 55665.0, 55666.0, 55667.0, 55668.0],       [55669.0, 55670.0, 55671.0, 55672.0, 55673.0, 55674.0],       [55675.0, 55676.0, 55677.0, 55678.0, 55679.0, 55680.0],       [55681.0, 55682.0, 55683.0, 55684.0, 55685.0, 55686.0],       [55687.0, 55688.0, 55689.0, 55690.0, 55691.0, 55692.0]]],     [[[55693.0, 55694.0, 55695.0, 55696.0, 55697.0, 55698.0],       [55699.0, 55700.0, 55701.0, 55702.0, 55703.0, 55704.0],       [55705.0, 55706.0, 55707.0, 55708.0, 55709.0, 55710.0],       [55711.0, 55712.0, 55713.0, 55714.0, 55715.0, 55716.0],       [55717.0, 55718.0, 55719.0, 55720.0, 55721.0, 55722.0],       [55723.0, 55724.0, 55725.0, 55726.0, 55727.0, 55728.0],       [55729.0, 55730.0, 55731.0, 55732.0, 55733.0, 55734.0]],      [[55735.0, 55736.0, 55737.0, 55738.0, 55739.0, 55740.0],       [55741.0, 55742.0, 55743.0, 55744.0, 55745.0, 55746.0],       [55747.0, 55748.0, 55749.0, 55750.0, 55751.0, 55752.0],       [55753.0, 55754.0, 55755.0, 55756.0, 55757.0, 55758.0],       [55759.0, 55760.0, 55761.0, 55762.0, 55763.0, 55764.0],       [55765.0, 55766.0, 55767.0, 55768.0, 55769.0, 55770.0],       [55771.0, 55772.0, 55773.0, 55774.0, 55775.0, 55776.0]],      [[55777.0, 55778.0, 55779.0, 55780.0, 55781.0, 55782.0],       [55783.0, 55784.0, 55785.0, 55786.0, 55787.0, 55788.0],       [55789.0, 55790.0, 55791.0, 55792.0, 55793.0, 55794.0],       [55795.0, 55796.0, 55797.0, 55798.0, 55799.0, 55800.0],       [55801.0, 55802.0, 55803.0, 55804.0, 55805.0, 55806.0],       [55807.0, 55808.0, 55809.0, 55810.0, 55811.0, 55812.0],       [55813.0, 55814.0, 55815.0, 55816.0, 55817.0, 55818.0]],      [[55819.0, 55820.0, 55821.0, 55822.0, 55823.0, 55824.0],       [55825.0, 55826.0, 55827.0, 55828.0, 55829.0, 55830.0],       [55831.0, 55832.0, 55833.0, 55834.0, 55835.0, 55836.0],       [55837.0, 55838.0, 55839.0, 55840.0, 55841.0, 55842.0],       [55843.0, 55844.0, 55845.0, 55846.0, 55847.0, 55848.0],       [55849.0, 55850.0, 55851.0, 55852.0, 55853.0, 55854.0],       [55855.0, 55856.0, 55857.0, 55858.0, 55859.0, 55860.0]],      [[55861.0, 55862.0, 55863.0, 55864.0, 55865.0, 55866.0],       [55867.0, 55868.0, 55869.0, 55870.0, 55871.0, 55872.0],       [55873.0, 55874.0, 55875.0, 55876.0, 55877.0, 55878.0],       [55879.0, 55880.0, 55881.0, 55882.0, 55883.0, 55884.0],       [55885.0, 55886.0, 55887.0, 55888.0, 55889.0, 55890.0],       [55891.0, 55892.0, 55893.0, 55894.0, 55895.0, 55896.0],       [55897.0, 55898.0, 55899.0, 55900.0, 55901.0, 55902.0]],      [[55903.0, 55904.0, 55905.0, 55906.0, 55907.0, 55908.0],       [55909.0, 55910.0, 55911.0, 55912.0, 55913.0, 55914.0],       [55915.0, 55916.0, 55917.0, 55918.0, 55919.0, 55920.0],       [55921.0, 55922.0, 55923.0, 55924.0, 55925.0, 55926.0],       [55927.0, 55928.0, 55929.0, 55930.0, 55931.0, 55932.0],       [55933.0, 55934.0, 55935.0, 55936.0, 55937.0, 55938.0],       [55939.0, 55940.0, 55941.0, 55942.0, 55943.0, 55944.0]]],     [[[55945.0, 55946.0, 55947.0, 55948.0, 55949.0, 55950.0],       [55951.0, 55952.0, 55953.0, 55954.0, 55955.0, 55956.0],       [55957.0, 55958.0, 55959.0, 55960.0, 55961.0, 55962.0],       [55963.0, 55964.0, 55965.0, 55966.0, 55967.0, 55968.0],       [55969.0, 55970.0, 55971.0, 55972.0, 55973.0, 55974.0],       [55975.0, 55976.0, 55977.0, 55978.0, 55979.0, 55980.0],       [55981.0, 55982.0, 55983.0, 55984.0, 55985.0, 55986.0]],      [[55987.0, 55988.0, 55989.0, 55990.0, 55991.0, 55992.0],       [55993.0, 55994.0, 55995.0, 55996.0, 55997.0, 55998.0],       [55999.0, 56000.0, 56001.0, 56002.0, 56003.0, 56004.0],       [56005.0, 56006.0, 56007.0, 56008.0, 56009.0, 56010.0],       [56011.0, 56012.0, 56013.0, 56014.0, 56015.0, 56016.0],       [56017.0, 56018.0, 56019.0, 56020.0, 56021.0, 56022.0],       [56023.0, 56024.0, 56025.0, 56026.0, 56027.0, 56028.0]],      [[56029.0, 56030.0, 56031.0, 56032.0, 56033.0, 56034.0],       [56035.0, 56036.0, 56037.0, 56038.0, 56039.0, 56040.0],       [56041.0, 56042.0, 56043.0, 56044.0, 56045.0, 56046.0],       [56047.0, 56048.0, 56049.0, 56050.0, 56051.0, 56052.0],       [56053.0, 56054.0, 56055.0, 56056.0, 56057.0, 56058.0],       [56059.0, 56060.0, 56061.0, 56062.0, 56063.0, 56064.0],       [56065.0, 56066.0, 56067.0, 56068.0, 56069.0, 56070.0]],      [[56071.0, 56072.0, 56073.0, 56074.0, 56075.0, 56076.0],       [56077.0, 56078.0, 56079.0, 56080.0, 56081.0, 56082.0],       [56083.0, 56084.0, 56085.0, 56086.0, 56087.0, 56088.0],       [56089.0, 56090.0, 56091.0, 56092.0, 56093.0, 56094.0],       [56095.0, 56096.0, 56097.0, 56098.0, 56099.0, 56100.0],       [56101.0, 56102.0, 56103.0, 56104.0, 56105.0, 56106.0],       [56107.0, 56108.0, 56109.0, 56110.0, 56111.0, 56112.0]],      [[56113.0, 56114.0, 56115.0, 56116.0, 56117.0, 56118.0],       [56119.0, 56120.0, 56121.0, 56122.0, 56123.0, 56124.0],       [56125.0, 56126.0, 56127.0, 56128.0, 56129.0, 56130.0],       [56131.0, 56132.0, 56133.0, 56134.0, 56135.0, 56136.0],       [56137.0, 56138.0, 56139.0, 56140.0, 56141.0, 56142.0],       [56143.0, 56144.0, 56145.0, 56146.0, 56147.0, 56148.0],       [56149.0, 56150.0, 56151.0, 56152.0, 56153.0, 56154.0]],      [[56155.0, 56156.0, 56157.0, 56158.0, 56159.0, 56160.0],       [56161.0, 56162.0, 56163.0, 56164.0, 56165.0, 56166.0],       [56167.0, 56168.0, 56169.0, 56170.0, 56171.0, 56172.0],       [56173.0, 56174.0, 56175.0, 56176.0, 56177.0, 56178.0],       [56179.0, 56180.0, 56181.0, 56182.0, 56183.0, 56184.0],       [56185.0, 56186.0, 56187.0, 56188.0, 56189.0, 56190.0],       [56191.0, 56192.0, 56193.0, 56194.0, 56195.0, 56196.0]]],     [[[56197.0, 56198.0, 56199.0, 56200.0, 56201.0, 56202.0],       [56203.0, 56204.0, 56205.0, 56206.0, 56207.0, 56208.0],       [56209.0, 56210.0, 56211.0, 56212.0, 56213.0, 56214.0],       [56215.0, 56216.0, 56217.0, 56218.0, 56219.0, 56220.0],       [56221.0, 56222.0, 56223.0, 56224.0, 56225.0, 56226.0],       [56227.0, 56228.0, 56229.0, 56230.0, 56231.0, 56232.0],       [56233.0, 56234.0, 56235.0, 56236.0, 56237.0, 56238.0]],      [[56239.0, 56240.0, 56241.0, 56242.0, 56243.0, 56244.0],       [56245.0, 56246.0, 56247.0, 56248.0, 56249.0, 56250.0],       [56251.0, 56252.0, 56253.0, 56254.0, 56255.0, 56256.0],       [56257.0, 56258.0, 56259.0, 56260.0, 56261.0, 56262.0],       [56263.0, 56264.0, 56265.0, 56266.0, 56267.0, 56268.0],       [56269.0, 56270.0, 56271.0, 56272.0, 56273.0, 56274.0],       [56275.0, 56276.0, 56277.0, 56278.0, 56279.0, 56280.0]],      [[56281.0, 56282.0, 56283.0, 56284.0, 56285.0, 56286.0],       [56287.0, 56288.0, 56289.0, 56290.0, 56291.0, 56292.0],       [56293.0, 56294.0, 56295.0, 56296.0, 56297.0, 56298.0],       [56299.0, 56300.0, 56301.0, 56302.0, 56303.0, 56304.0],       [56305.0, 56306.0, 56307.0, 56308.0, 56309.0, 56310.0],       [56311.0, 56312.0, 56313.0, 56314.0, 56315.0, 56316.0],       [56317.0, 56318.0, 56319.0, 56320.0, 56321.0, 56322.0]],      [[56323.0, 56324.0, 56325.0, 56326.0, 56327.0, 56328.0],       [56329.0, 56330.0, 56331.0, 56332.0, 56333.0, 56334.0],       [56335.0, 56336.0, 56337.0, 56338.0, 56339.0, 56340.0],       [56341.0, 56342.0, 56343.0, 56344.0, 56345.0, 56346.0],       [56347.0, 56348.0, 56349.0, 56350.0, 56351.0, 56352.0],       [56353.0, 56354.0, 56355.0, 56356.0, 56357.0, 56358.0],       [56359.0, 56360.0, 56361.0, 56362.0, 56363.0, 56364.0]],      [[56365.0, 56366.0, 56367.0, 56368.0, 56369.0, 56370.0],       [56371.0, 56372.0, 56373.0, 56374.0, 56375.0, 56376.0],       [56377.0, 56378.0, 56379.0, 56380.0, 56381.0, 56382.0],       [56383.0, 56384.0, 56385.0, 56386.0, 56387.0, 56388.0],       [56389.0, 56390.0, 56391.0, 56392.0, 56393.0, 56394.0],       [56395.0, 56396.0, 56397.0, 56398.0, 56399.0, 56400.0],       [56401.0, 56402.0, 56403.0, 56404.0, 56405.0, 56406.0]],      [[56407.0, 56408.0, 56409.0, 56410.0, 56411.0, 56412.0],       [56413.0, 56414.0, 56415.0, 56416.0, 56417.0, 56418.0],       [56419.0, 56420.0, 56421.0, 56422.0, 56423.0, 56424.0],       [56425.0, 56426.0, 56427.0, 56428.0, 56429.0, 56430.0],       [56431.0, 56432.0, 56433.0, 56434.0, 56435.0, 56436.0],       [56437.0, 56438.0, 56439.0, 56440.0, 56441.0, 56442.0],       [56443.0, 56444.0, 56445.0, 56446.0, 56447.0, 56448.0]]]],    [[[[56449.0, 56450.0, 56451.0, 56452.0, 56453.0, 56454.0],       [56455.0, 56456.0, 56457.0, 56458.0, 56459.0, 56460.0],       [56461.0, 56462.0, 56463.0, 56464.0, 56465.0, 56466.0],       [56467.0, 56468.0, 56469.0, 56470.0, 56471.0, 56472.0],       [56473.0, 56474.0, 56475.0, 56476.0, 56477.0, 56478.0],       [56479.0, 56480.0, 56481.0, 56482.0, 56483.0, 56484.0],       [56485.0, 56486.0, 56487.0, 56488.0, 56489.0, 56490.0]],      [[56491.0, 56492.0, 56493.0, 56494.0, 56495.0, 56496.0],       [56497.0, 56498.0, 56499.0, 56500.0, 56501.0, 56502.0],       [56503.0, 56504.0, 56505.0, 56506.0, 56507.0, 56508.0],       [56509.0, 56510.0, 56511.0, 56512.0, 56513.0, 56514.0],       [56515.0, 56516.0, 56517.0, 56518.0, 56519.0, 56520.0],       [56521.0, 56522.0, 56523.0, 56524.0, 56525.0, 56526.0],       [56527.0, 56528.0, 56529.0, 56530.0, 56531.0, 56532.0]],      [[56533.0, 56534.0, 56535.0, 56536.0, 56537.0, 56538.0],       [56539.0, 56540.0, 56541.0, 56542.0, 56543.0, 56544.0],       [56545.0, 56546.0, 56547.0, 56548.0, 56549.0, 56550.0],       [56551.0, 56552.0, 56553.0, 56554.0, 56555.0, 56556.0],       [56557.0, 56558.0, 56559.0, 56560.0, 56561.0, 56562.0],       [56563.0, 56564.0, 56565.0, 56566.0, 56567.0, 56568.0],       [56569.0, 56570.0, 56571.0, 56572.0, 56573.0, 56574.0]],      [[56575.0, 56576.0, 56577.0, 56578.0, 56579.0, 56580.0],       [56581.0, 56582.0, 56583.0, 56584.0, 56585.0, 56586.0],       [56587.0, 56588.0, 56589.0, 56590.0, 56591.0, 56592.0],       [56593.0, 56594.0, 56595.0, 56596.0, 56597.0, 56598.0],       [56599.0, 56600.0, 56601.0, 56602.0, 56603.0, 56604.0],       [56605.0, 56606.0, 56607.0, 56608.0, 56609.0, 56610.0],       [56611.0, 56612.0, 56613.0, 56614.0, 56615.0, 56616.0]],      [[56617.0, 56618.0, 56619.0, 56620.0, 56621.0, 56622.0],       [56623.0, 56624.0, 56625.0, 56626.0, 56627.0, 56628.0],       [56629.0, 56630.0, 56631.0, 56632.0, 56633.0, 56634.0],       [56635.0, 56636.0, 56637.0, 56638.0, 56639.0, 56640.0],       [56641.0, 56642.0, 56643.0, 56644.0, 56645.0, 56646.0],       [56647.0, 56648.0, 56649.0, 56650.0, 56651.0, 56652.0],       [56653.0, 56654.0, 56655.0, 56656.0, 56657.0, 56658.0]],      [[56659.0, 56660.0, 56661.0, 56662.0, 56663.0, 56664.0],       [56665.0, 56666.0, 56667.0, 56668.0, 56669.0, 56670.0],       [56671.0, 56672.0, 56673.0, 56674.0, 56675.0, 56676.0],       [56677.0, 56678.0, 56679.0, 56680.0, 56681.0, 56682.0],       [56683.0, 56684.0, 56685.0, 56686.0, 56687.0, 56688.0],       [56689.0, 56690.0, 56691.0, 56692.0, 56693.0, 56694.0],       [56695.0, 56696.0, 56697.0, 56698.0, 56699.0, 56700.0]]],     [[[56701.0, 56702.0, 56703.0, 56704.0, 56705.0, 56706.0],       [56707.0, 56708.0, 56709.0, 56710.0, 56711.0, 56712.0],       [56713.0, 56714.0, 56715.0, 56716.0, 56717.0, 56718.0],       [56719.0, 56720.0, 56721.0, 56722.0, 56723.0, 56724.0],       [56725.0, 56726.0, 56727.0, 56728.0, 56729.0, 56730.0],       [56731.0, 56732.0, 56733.0, 56734.0, 56735.0, 56736.0],       [56737.0, 56738.0, 56739.0, 56740.0, 56741.0, 56742.0]],      [[56743.0, 56744.0, 56745.0, 56746.0, 56747.0, 56748.0],       [56749.0, 56750.0, 56751.0, 56752.0, 56753.0, 56754.0],       [56755.0, 56756.0, 56757.0, 56758.0, 56759.0, 56760.0],       [56761.0, 56762.0, 56763.0, 56764.0, 56765.0, 56766.0],       [56767.0, 56768.0, 56769.0, 56770.0, 56771.0, 56772.0],       [56773.0, 56774.0, 56775.0, 56776.0, 56777.0, 56778.0],       [56779.0, 56780.0, 56781.0, 56782.0, 56783.0, 56784.0]],      [[56785.0, 56786.0, 56787.0, 56788.0, 56789.0, 56790.0],       [56791.0, 56792.0, 56793.0, 56794.0, 56795.0, 56796.0],       [56797.0, 56798.0, 56799.0, 56800.0, 56801.0, 56802.0],       [56803.0, 56804.0, 56805.0, 56806.0, 56807.0, 56808.0],       [56809.0, 56810.0, 56811.0, 56812.0, 56813.0, 56814.0],       [56815.0, 56816.0, 56817.0, 56818.0, 56819.0, 56820.0],       [56821.0, 56822.0, 56823.0, 56824.0, 56825.0, 56826.0]],      [[56827.0, 56828.0, 56829.0, 56830.0, 56831.0, 56832.0],       [56833.0, 56834.0, 56835.0, 56836.0, 56837.0, 56838.0],       [56839.0, 56840.0, 56841.0, 56842.0, 56843.0, 56844.0],       [56845.0, 56846.0, 56847.0, 56848.0, 56849.0, 56850.0],       [56851.0, 56852.0, 56853.0, 56854.0, 56855.0, 56856.0],       [56857.0, 56858.0, 56859.0, 56860.0, 56861.0, 56862.0],       [56863.0, 56864.0, 56865.0, 56866.0, 56867.0, 56868.0]],      [[56869.0, 56870.0, 56871.0, 56872.0, 56873.0, 56874.0],       [56875.0, 56876.0, 56877.0, 56878.0, 56879.0, 56880.0],       [56881.0, 56882.0, 56883.0, 56884.0, 56885.0, 56886.0],       [56887.0, 56888.0, 56889.0, 56890.0, 56891.0, 56892.0],       [56893.0, 56894.0, 56895.0, 56896.0, 56897.0, 56898.0],       [56899.0, 56900.0, 56901.0, 56902.0, 56903.0, 56904.0],       [56905.0, 56906.0, 56907.0, 56908.0, 56909.0, 56910.0]],      [[56911.0, 56912.0, 56913.0, 56914.0, 56915.0, 56916.0],       [56917.0, 56918.0, 56919.0, 56920.0, 56921.0, 56922.0],       [56923.0, 56924.0, 56925.0, 56926.0, 56927.0, 56928.0],       [56929.0, 56930.0, 56931.0, 56932.0, 56933.0, 56934.0],       [56935.0, 56936.0, 56937.0, 56938.0, 56939.0, 56940.0],       [56941.0, 56942.0, 56943.0, 56944.0, 56945.0, 56946.0],       [56947.0, 56948.0, 56949.0, 56950.0, 56951.0, 56952.0]]],     [[[56953.0, 56954.0, 56955.0, 56956.0, 56957.0, 56958.0],       [56959.0, 56960.0, 56961.0, 56962.0, 56963.0, 56964.0],       [56965.0, 56966.0, 56967.0, 56968.0, 56969.0, 56970.0],       [56971.0, 56972.0, 56973.0, 56974.0, 56975.0, 56976.0],       [56977.0, 56978.0, 56979.0, 56980.0, 56981.0, 56982.0],       [56983.0, 56984.0, 56985.0, 56986.0, 56987.0, 56988.0],       [56989.0, 56990.0, 56991.0, 56992.0, 56993.0, 56994.0]],      [[56995.0, 56996.0, 56997.0, 56998.0, 56999.0, 57000.0],       [57001.0, 57002.0, 57003.0, 57004.0, 57005.0, 57006.0],       [57007.0, 57008.0, 57009.0, 57010.0, 57011.0, 57012.0],       [57013.0, 57014.0, 57015.0, 57016.0, 57017.0, 57018.0],       [57019.0, 57020.0, 57021.0, 57022.0, 57023.0, 57024.0],       [57025.0, 57026.0, 57027.0, 57028.0, 57029.0, 57030.0],       [57031.0, 57032.0, 57033.0, 57034.0, 57035.0, 57036.0]],      [[57037.0, 57038.0, 57039.0, 57040.0, 57041.0, 57042.0],       [57043.0, 57044.0, 57045.0, 57046.0, 57047.0, 57048.0],       [57049.0, 57050.0, 57051.0, 57052.0, 57053.0, 57054.0],       [57055.0, 57056.0, 57057.0, 57058.0, 57059.0, 57060.0],       [57061.0, 57062.0, 57063.0, 57064.0, 57065.0, 57066.0],       [57067.0, 57068.0, 57069.0, 57070.0, 57071.0, 57072.0],       [57073.0, 57074.0, 57075.0, 57076.0, 57077.0, 57078.0]],      [[57079.0, 57080.0, 57081.0, 57082.0, 57083.0, 57084.0],       [57085.0, 57086.0, 57087.0, 57088.0, 57089.0, 57090.0],       [57091.0, 57092.0, 57093.0, 57094.0, 57095.0, 57096.0],       [57097.0, 57098.0, 57099.0, 57100.0, 57101.0, 57102.0],       [57103.0, 57104.0, 57105.0, 57106.0, 57107.0, 57108.0],       [57109.0, 57110.0, 57111.0, 57112.0, 57113.0, 57114.0],       [57115.0, 57116.0, 57117.0, 57118.0, 57119.0, 57120.0]],      [[57121.0, 57122.0, 57123.0, 57124.0, 57125.0, 57126.0],       [57127.0, 57128.0, 57129.0, 57130.0, 57131.0, 57132.0],       [57133.0, 57134.0, 57135.0, 57136.0, 57137.0, 57138.0],       [57139.0, 57140.0, 57141.0, 57142.0, 57143.0, 57144.0],       [57145.0, 57146.0, 57147.0, 57148.0, 57149.0, 57150.0],       [57151.0, 57152.0, 57153.0, 57154.0, 57155.0, 57156.0],       [57157.0, 57158.0, 57159.0, 57160.0, 57161.0, 57162.0]],      [[57163.0, 57164.0, 57165.0, 57166.0, 57167.0, 57168.0],       [57169.0, 57170.0, 57171.0, 57172.0, 57173.0, 57174.0],       [57175.0, 57176.0, 57177.0, 57178.0, 57179.0, 57180.0],       [57181.0, 57182.0, 57183.0, 57184.0, 57185.0, 57186.0],       [57187.0, 57188.0, 57189.0, 57190.0, 57191.0, 57192.0],       [57193.0, 57194.0, 57195.0, 57196.0, 57197.0, 57198.0],       [57199.0, 57200.0, 57201.0, 57202.0, 57203.0, 57204.0]]],     [[[57205.0, 57206.0, 57207.0, 57208.0, 57209.0, 57210.0],       [57211.0, 57212.0, 57213.0, 57214.0, 57215.0, 57216.0],       [57217.0, 57218.0, 57219.0, 57220.0, 57221.0, 57222.0],       [57223.0, 57224.0, 57225.0, 57226.0, 57227.0, 57228.0],       [57229.0, 57230.0, 57231.0, 57232.0, 57233.0, 57234.0],       [57235.0, 57236.0, 57237.0, 57238.0, 57239.0, 57240.0],       [57241.0, 57242.0, 57243.0, 57244.0, 57245.0, 57246.0]],      [[57247.0, 57248.0, 57249.0, 57250.0, 57251.0, 57252.0],       [57253.0, 57254.0, 57255.0, 57256.0, 57257.0, 57258.0],       [57259.0, 57260.0, 57261.0, 57262.0, 57263.0, 57264.0],       [57265.0, 57266.0, 57267.0, 57268.0, 57269.0, 57270.0],       [57271.0, 57272.0, 57273.0, 57274.0, 57275.0, 57276.0],       [57277.0, 57278.0, 57279.0, 57280.0, 57281.0, 57282.0],       [57283.0, 57284.0, 57285.0, 57286.0, 57287.0, 57288.0]],      [[57289.0, 57290.0, 57291.0, 57292.0, 57293.0, 57294.0],       [57295.0, 57296.0, 57297.0, 57298.0, 57299.0, 57300.0],       [57301.0, 57302.0, 57303.0, 57304.0, 57305.0, 57306.0],       [57307.0, 57308.0, 57309.0, 57310.0, 57311.0, 57312.0],       [57313.0, 57314.0, 57315.0, 57316.0, 57317.0, 57318.0],       [57319.0, 57320.0, 57321.0, 57322.0, 57323.0, 57324.0],       [57325.0, 57326.0, 57327.0, 57328.0, 57329.0, 57330.0]],      [[57331.0, 57332.0, 57333.0, 57334.0, 57335.0, 57336.0],       [57337.0, 57338.0, 57339.0, 57340.0, 57341.0, 57342.0],       [57343.0, 57344.0, 57345.0, 57346.0, 57347.0, 57348.0],       [57349.0, 57350.0, 57351.0, 57352.0, 57353.0, 57354.0],       [57355.0, 57356.0, 57357.0, 57358.0, 57359.0, 57360.0],       [57361.0, 57362.0, 57363.0, 57364.0, 57365.0, 57366.0],       [57367.0, 57368.0, 57369.0, 57370.0, 57371.0, 57372.0]],      [[57373.0, 57374.0, 57375.0, 57376.0, 57377.0, 57378.0],       [57379.0, 57380.0, 57381.0, 57382.0, 57383.0, 57384.0],       [57385.0, 57386.0, 57387.0, 57388.0, 57389.0, 57390.0],       [57391.0, 57392.0, 57393.0, 57394.0, 57395.0, 57396.0],       [57397.0, 57398.0, 57399.0, 57400.0, 57401.0, 57402.0],       [57403.0, 57404.0, 57405.0, 57406.0, 57407.0, 57408.0],       [57409.0, 57410.0, 57411.0, 57412.0, 57413.0, 57414.0]],      [[57415.0, 57416.0, 57417.0, 57418.0, 57419.0, 57420.0],       [57421.0, 57422.0, 57423.0, 57424.0, 57425.0, 57426.0],       [57427.0, 57428.0, 57429.0, 57430.0, 57431.0, 57432.0],       [57433.0, 57434.0, 57435.0, 57436.0, 57437.0, 57438.0],       [57439.0, 57440.0, 57441.0, 57442.0, 57443.0, 57444.0],       [57445.0, 57446.0, 57447.0, 57448.0, 57449.0, 57450.0],       [57451.0, 57452.0, 57453.0, 57454.0, 57455.0, 57456.0]]]],    [[[[57457.0, 57458.0, 57459.0, 57460.0, 57461.0, 57462.0],       [57463.0, 57464.0, 57465.0, 57466.0, 57467.0, 57468.0],       [57469.0, 57470.0, 57471.0, 57472.0, 57473.0, 57474.0],       [57475.0, 57476.0, 57477.0, 57478.0, 57479.0, 57480.0],       [57481.0, 57482.0, 57483.0, 57484.0, 57485.0, 57486.0],       [57487.0, 57488.0, 57489.0, 57490.0, 57491.0, 57492.0],       [57493.0, 57494.0, 57495.0, 57496.0, 57497.0, 57498.0]],      [[57499.0, 57500.0, 57501.0, 57502.0, 57503.0, 57504.0],       [57505.0, 57506.0, 57507.0, 57508.0, 57509.0, 57510.0],       [57511.0, 57512.0, 57513.0, 57514.0, 57515.0, 57516.0],       [57517.0, 57518.0, 57519.0, 57520.0, 57521.0, 57522.0],       [57523.0, 57524.0, 57525.0, 57526.0, 57527.0, 57528.0],       [57529.0, 57530.0, 57531.0, 57532.0, 57533.0, 57534.0],       [57535.0, 57536.0, 57537.0, 57538.0, 57539.0, 57540.0]],      [[57541.0, 57542.0, 57543.0, 57544.0, 57545.0, 57546.0],       [57547.0, 57548.0, 57549.0, 57550.0, 57551.0, 57552.0],       [57553.0, 57554.0, 57555.0, 57556.0, 57557.0, 57558.0],       [57559.0, 57560.0, 57561.0, 57562.0, 57563.0, 57564.0],       [57565.0, 57566.0, 57567.0, 57568.0, 57569.0, 57570.0],       [57571.0, 57572.0, 57573.0, 57574.0, 57575.0, 57576.0],       [57577.0, 57578.0, 57579.0, 57580.0, 57581.0, 57582.0]],      [[57583.0, 57584.0, 57585.0, 57586.0, 57587.0, 57588.0],       [57589.0, 57590.0, 57591.0, 57592.0, 57593.0, 57594.0],       [57595.0, 57596.0, 57597.0, 57598.0, 57599.0, 57600.0],       [57601.0, 57602.0, 57603.0, 57604.0, 57605.0, 57606.0],       [57607.0, 57608.0, 57609.0, 57610.0, 57611.0, 57612.0],       [57613.0, 57614.0, 57615.0, 57616.0, 57617.0, 57618.0],       [57619.0, 57620.0, 57621.0, 57622.0, 57623.0, 57624.0]],      [[57625.0, 57626.0, 57627.0, 57628.0, 57629.0, 57630.0],       [57631.0, 57632.0, 57633.0, 57634.0, 57635.0, 57636.0],       [57637.0, 57638.0, 57639.0, 57640.0, 57641.0, 57642.0],       [57643.0, 57644.0, 57645.0, 57646.0, 57647.0, 57648.0],       [57649.0, 57650.0, 57651.0, 57652.0, 57653.0, 57654.0],       [57655.0, 57656.0, 57657.0, 57658.0, 57659.0, 57660.0],       [57661.0, 57662.0, 57663.0, 57664.0, 57665.0, 57666.0]],      [[57667.0, 57668.0, 57669.0, 57670.0, 57671.0, 57672.0],       [57673.0, 57674.0, 57675.0, 57676.0, 57677.0, 57678.0],       [57679.0, 57680.0, 57681.0, 57682.0, 57683.0, 57684.0],       [57685.0, 57686.0, 57687.0, 57688.0, 57689.0, 57690.0],       [57691.0, 57692.0, 57693.0, 57694.0, 57695.0, 57696.0],       [57697.0, 57698.0, 57699.0, 57700.0, 57701.0, 57702.0],       [57703.0, 57704.0, 57705.0, 57706.0, 57707.0, 57708.0]]],     [[[57709.0, 57710.0, 57711.0, 57712.0, 57713.0, 57714.0],       [57715.0, 57716.0, 57717.0, 57718.0, 57719.0, 57720.0],       [57721.0, 57722.0, 57723.0, 57724.0, 57725.0, 57726.0],       [57727.0, 57728.0, 57729.0, 57730.0, 57731.0, 57732.0],       [57733.0, 57734.0, 57735.0, 57736.0, 57737.0, 57738.0],       [57739.0, 57740.0, 57741.0, 57742.0, 57743.0, 57744.0],       [57745.0, 57746.0, 57747.0, 57748.0, 57749.0, 57750.0]],      [[57751.0, 57752.0, 57753.0, 57754.0, 57755.0, 57756.0],       [57757.0, 57758.0, 57759.0, 57760.0, 57761.0, 57762.0],       [57763.0, 57764.0, 57765.0, 57766.0, 57767.0, 57768.0],       [57769.0, 57770.0, 57771.0, 57772.0, 57773.0, 57774.0],       [57775.0, 57776.0, 57777.0, 57778.0, 57779.0, 57780.0],       [57781.0, 57782.0, 57783.0, 57784.0, 57785.0, 57786.0],       [57787.0, 57788.0, 57789.0, 57790.0, 57791.0, 57792.0]],      [[57793.0, 57794.0, 57795.0, 57796.0, 57797.0, 57798.0],       [57799.0, 57800.0, 57801.0, 57802.0, 57803.0, 57804.0],       [57805.0, 57806.0, 57807.0, 57808.0, 57809.0, 57810.0],       [57811.0, 57812.0, 57813.0, 57814.0, 57815.0, 57816.0],       [57817.0, 57818.0, 57819.0, 57820.0, 57821.0, 57822.0],       [57823.0, 57824.0, 57825.0, 57826.0, 57827.0, 57828.0],       [57829.0, 57830.0, 57831.0, 57832.0, 57833.0, 57834.0]],      [[57835.0, 57836.0, 57837.0, 57838.0, 57839.0, 57840.0],       [57841.0, 57842.0, 57843.0, 57844.0, 57845.0, 57846.0],       [57847.0, 57848.0, 57849.0, 57850.0, 57851.0, 57852.0],       [57853.0, 57854.0, 57855.0, 57856.0, 57857.0, 57858.0],       [57859.0, 57860.0, 57861.0, 57862.0, 57863.0, 57864.0],       [57865.0, 57866.0, 57867.0, 57868.0, 57869.0, 57870.0],       [57871.0, 57872.0, 57873.0, 57874.0, 57875.0, 57876.0]],      [[57877.0, 57878.0, 57879.0, 57880.0, 57881.0, 57882.0],       [57883.0, 57884.0, 57885.0, 57886.0, 57887.0, 57888.0],       [57889.0, 57890.0, 57891.0, 57892.0, 57893.0, 57894.0],       [57895.0, 57896.0, 57897.0, 57898.0, 57899.0, 57900.0],       [57901.0, 57902.0, 57903.0, 57904.0, 57905.0, 57906.0],       [57907.0, 57908.0, 57909.0, 57910.0, 57911.0, 57912.0],       [57913.0, 57914.0, 57915.0, 57916.0, 57917.0, 57918.0]],      [[57919.0, 57920.0, 57921.0, 57922.0, 57923.0, 57924.0],       [57925.0, 57926.0, 57927.0, 57928.0, 57929.0, 57930.0],       [57931.0, 57932.0, 57933.0, 57934.0, 57935.0, 57936.0],       [57937.0, 57938.0, 57939.0, 57940.0, 57941.0, 57942.0],       [57943.0, 57944.0, 57945.0, 57946.0, 57947.0, 57948.0],       [57949.0, 57950.0, 57951.0, 57952.0, 57953.0, 57954.0],       [57955.0, 57956.0, 57957.0, 57958.0, 57959.0, 57960.0]]],     [[[57961.0, 57962.0, 57963.0, 57964.0, 57965.0, 57966.0],       [57967.0, 57968.0, 57969.0, 57970.0, 57971.0, 57972.0],       [57973.0, 57974.0, 57975.0, 57976.0, 57977.0, 57978.0],       [57979.0, 57980.0, 57981.0, 57982.0, 57983.0, 57984.0],       [57985.0, 57986.0, 57987.0, 57988.0, 57989.0, 57990.0],       [57991.0, 57992.0, 57993.0, 57994.0, 57995.0, 57996.0],       [57997.0, 57998.0, 57999.0, 58000.0, 58001.0, 58002.0]],      [[58003.0, 58004.0, 58005.0, 58006.0, 58007.0, 58008.0],       [58009.0, 58010.0, 58011.0, 58012.0, 58013.0, 58014.0],       [58015.0, 58016.0, 58017.0, 58018.0, 58019.0, 58020.0],       [58021.0, 58022.0, 58023.0, 58024.0, 58025.0, 58026.0],       [58027.0, 58028.0, 58029.0, 58030.0, 58031.0, 58032.0],       [58033.0, 58034.0, 58035.0, 58036.0, 58037.0, 58038.0],       [58039.0, 58040.0, 58041.0, 58042.0, 58043.0, 58044.0]],      [[58045.0, 58046.0, 58047.0, 58048.0, 58049.0, 58050.0],       [58051.0, 58052.0, 58053.0, 58054.0, 58055.0, 58056.0],       [58057.0, 58058.0, 58059.0, 58060.0, 58061.0, 58062.0],       [58063.0, 58064.0, 58065.0, 58066.0, 58067.0, 58068.0],       [58069.0, 58070.0, 58071.0, 58072.0, 58073.0, 58074.0],       [58075.0, 58076.0, 58077.0, 58078.0, 58079.0, 58080.0],       [58081.0, 58082.0, 58083.0, 58084.0, 58085.0, 58086.0]],      [[58087.0, 58088.0, 58089.0, 58090.0, 58091.0, 58092.0],       [58093.0, 58094.0, 58095.0, 58096.0, 58097.0, 58098.0],       [58099.0, 58100.0, 58101.0, 58102.0, 58103.0, 58104.0],       [58105.0, 58106.0, 58107.0, 58108.0, 58109.0, 58110.0],       [58111.0, 58112.0, 58113.0, 58114.0, 58115.0, 58116.0],       [58117.0, 58118.0, 58119.0, 58120.0, 58121.0, 58122.0],       [58123.0, 58124.0, 58125.0, 58126.0, 58127.0, 58128.0]],      [[58129.0, 58130.0, 58131.0, 58132.0, 58133.0, 58134.0],       [58135.0, 58136.0, 58137.0, 58138.0, 58139.0, 58140.0],       [58141.0, 58142.0, 58143.0, 58144.0, 58145.0, 58146.0],       [58147.0, 58148.0, 58149.0, 58150.0, 58151.0, 58152.0],       [58153.0, 58154.0, 58155.0, 58156.0, 58157.0, 58158.0],       [58159.0, 58160.0, 58161.0, 58162.0, 58163.0, 58164.0],       [58165.0, 58166.0, 58167.0, 58168.0, 58169.0, 58170.0]],      [[58171.0, 58172.0, 58173.0, 58174.0, 58175.0, 58176.0],       [58177.0, 58178.0, 58179.0, 58180.0, 58181.0, 58182.0],       [58183.0, 58184.0, 58185.0, 58186.0, 58187.0, 58188.0],       [58189.0, 58190.0, 58191.0, 58192.0, 58193.0, 58194.0],       [58195.0, 58196.0, 58197.0, 58198.0, 58199.0, 58200.0],       [58201.0, 58202.0, 58203.0, 58204.0, 58205.0, 58206.0],       [58207.0, 58208.0, 58209.0, 58210.0, 58211.0, 58212.0]]],     [[[58213.0, 58214.0, 58215.0, 58216.0, 58217.0, 58218.0],       [58219.0, 58220.0, 58221.0, 58222.0, 58223.0, 58224.0],       [58225.0, 58226.0, 58227.0, 58228.0, 58229.0, 58230.0],       [58231.0, 58232.0, 58233.0, 58234.0, 58235.0, 58236.0],       [58237.0, 58238.0, 58239.0, 58240.0, 58241.0, 58242.0],       [58243.0, 58244.0, 58245.0, 58246.0, 58247.0, 58248.0],       [58249.0, 58250.0, 58251.0, 58252.0, 58253.0, 58254.0]],      [[58255.0, 58256.0, 58257.0, 58258.0, 58259.0, 58260.0],       [58261.0, 58262.0, 58263.0, 58264.0, 58265.0, 58266.0],       [58267.0, 58268.0, 58269.0, 58270.0, 58271.0, 58272.0],       [58273.0, 58274.0, 58275.0, 58276.0, 58277.0, 58278.0],       [58279.0, 58280.0, 58281.0, 58282.0, 58283.0, 58284.0],       [58285.0, 58286.0, 58287.0, 58288.0, 58289.0, 58290.0],       [58291.0, 58292.0, 58293.0, 58294.0, 58295.0, 58296.0]],      [[58297.0, 58298.0, 58299.0, 58300.0, 58301.0, 58302.0],       [58303.0, 58304.0, 58305.0, 58306.0, 58307.0, 58308.0],       [58309.0, 58310.0, 58311.0, 58312.0, 58313.0, 58314.0],       [58315.0, 58316.0, 58317.0, 58318.0, 58319.0, 58320.0],       [58321.0, 58322.0, 58323.0, 58324.0, 58325.0, 58326.0],       [58327.0, 58328.0, 58329.0, 58330.0, 58331.0, 58332.0],       [58333.0, 58334.0, 58335.0, 58336.0, 58337.0, 58338.0]],      [[58339.0, 58340.0, 58341.0, 58342.0, 58343.0, 58344.0],       [58345.0, 58346.0, 58347.0, 58348.0, 58349.0, 58350.0],       [58351.0, 58352.0, 58353.0, 58354.0, 58355.0, 58356.0],       [58357.0, 58358.0, 58359.0, 58360.0, 58361.0, 58362.0],       [58363.0, 58364.0, 58365.0, 58366.0, 58367.0, 58368.0],       [58369.0, 58370.0, 58371.0, 58372.0, 58373.0, 58374.0],       [58375.0, 58376.0, 58377.0, 58378.0, 58379.0, 58380.0]],      [[58381.0, 58382.0, 58383.0, 58384.0, 58385.0, 58386.0],       [58387.0, 58388.0, 58389.0, 58390.0, 58391.0, 58392.0],       [58393.0, 58394.0, 58395.0, 58396.0, 58397.0, 58398.0],       [58399.0, 58400.0, 58401.0, 58402.0, 58403.0, 58404.0],       [58405.0, 58406.0, 58407.0, 58408.0, 58409.0, 58410.0],       [58411.0, 58412.0, 58413.0, 58414.0, 58415.0, 58416.0],       [58417.0, 58418.0, 58419.0, 58420.0, 58421.0, 58422.0]],      [[58423.0, 58424.0, 58425.0, 58426.0, 58427.0, 58428.0],       [58429.0, 58430.0, 58431.0, 58432.0, 58433.0, 58434.0],       [58435.0, 58436.0, 58437.0, 58438.0, 58439.0, 58440.0],       [58441.0, 58442.0, 58443.0, 58444.0, 58445.0, 58446.0],       [58447.0, 58448.0, 58449.0, 58450.0, 58451.0, 58452.0],       [58453.0, 58454.0, 58455.0, 58456.0, 58457.0, 58458.0],       [58459.0, 58460.0, 58461.0, 58462.0, 58463.0, 58464.0]]]],    [[[[58465.0, 58466.0, 58467.0, 58468.0, 58469.0, 58470.0],       [58471.0, 58472.0, 58473.0, 58474.0, 58475.0, 58476.0],       [58477.0, 58478.0, 58479.0, 58480.0, 58481.0, 58482.0],       [58483.0, 58484.0, 58485.0, 58486.0, 58487.0, 58488.0],       [58489.0, 58490.0, 58491.0, 58492.0, 58493.0, 58494.0],       [58495.0, 58496.0, 58497.0, 58498.0, 58499.0, 58500.0],       [58501.0, 58502.0, 58503.0, 58504.0, 58505.0, 58506.0]],      [[58507.0, 58508.0, 58509.0, 58510.0, 58511.0, 58512.0],       [58513.0, 58514.0, 58515.0, 58516.0, 58517.0, 58518.0],       [58519.0, 58520.0, 58521.0, 58522.0, 58523.0, 58524.0],       [58525.0, 58526.0, 58527.0, 58528.0, 58529.0, 58530.0],       [58531.0, 58532.0, 58533.0, 58534.0, 58535.0, 58536.0],       [58537.0, 58538.0, 58539.0, 58540.0, 58541.0, 58542.0],       [58543.0, 58544.0, 58545.0, 58546.0, 58547.0, 58548.0]],      [[58549.0, 58550.0, 58551.0, 58552.0, 58553.0, 58554.0],       [58555.0, 58556.0, 58557.0, 58558.0, 58559.0, 58560.0],       [58561.0, 58562.0, 58563.0, 58564.0, 58565.0, 58566.0],       [58567.0, 58568.0, 58569.0, 58570.0, 58571.0, 58572.0],       [58573.0, 58574.0, 58575.0, 58576.0, 58577.0, 58578.0],       [58579.0, 58580.0, 58581.0, 58582.0, 58583.0, 58584.0],       [58585.0, 58586.0, 58587.0, 58588.0, 58589.0, 58590.0]],      [[58591.0, 58592.0, 58593.0, 58594.0, 58595.0, 58596.0],       [58597.0, 58598.0, 58599.0, 58600.0, 58601.0, 58602.0],       [58603.0, 58604.0, 58605.0, 58606.0, 58607.0, 58608.0],       [58609.0, 58610.0, 58611.0, 58612.0, 58613.0, 58614.0],       [58615.0, 58616.0, 58617.0, 58618.0, 58619.0, 58620.0],       [58621.0, 58622.0, 58623.0, 58624.0, 58625.0, 58626.0],       [58627.0, 58628.0, 58629.0, 58630.0, 58631.0, 58632.0]],      [[58633.0, 58634.0, 58635.0, 58636.0, 58637.0, 58638.0],       [58639.0, 58640.0, 58641.0, 58642.0, 58643.0, 58644.0],       [58645.0, 58646.0, 58647.0, 58648.0, 58649.0, 58650.0],       [58651.0, 58652.0, 58653.0, 58654.0, 58655.0, 58656.0],       [58657.0, 58658.0, 58659.0, 58660.0, 58661.0, 58662.0],       [58663.0, 58664.0, 58665.0, 58666.0, 58667.0, 58668.0],       [58669.0, 58670.0, 58671.0, 58672.0, 58673.0, 58674.0]],      [[58675.0, 58676.0, 58677.0, 58678.0, 58679.0, 58680.0],       [58681.0, 58682.0, 58683.0, 58684.0, 58685.0, 58686.0],       [58687.0, 58688.0, 58689.0, 58690.0, 58691.0, 58692.0],       [58693.0, 58694.0, 58695.0, 58696.0, 58697.0, 58698.0],       [58699.0, 58700.0, 58701.0, 58702.0, 58703.0, 58704.0],       [58705.0, 58706.0, 58707.0, 58708.0, 58709.0, 58710.0],       [58711.0, 58712.0, 58713.0, 58714.0, 58715.0, 58716.0]]],     [[[58717.0, 58718.0, 58719.0, 58720.0, 58721.0, 58722.0],       [58723.0, 58724.0, 58725.0, 58726.0, 58727.0, 58728.0],       [58729.0, 58730.0, 58731.0, 58732.0, 58733.0, 58734.0],       [58735.0, 58736.0, 58737.0, 58738.0, 58739.0, 58740.0],       [58741.0, 58742.0, 58743.0, 58744.0, 58745.0, 58746.0],       [58747.0, 58748.0, 58749.0, 58750.0, 58751.0, 58752.0],       [58753.0, 58754.0, 58755.0, 58756.0, 58757.0, 58758.0]],      [[58759.0, 58760.0, 58761.0, 58762.0, 58763.0, 58764.0],       [58765.0, 58766.0, 58767.0, 58768.0, 58769.0, 58770.0],       [58771.0, 58772.0, 58773.0, 58774.0, 58775.0, 58776.0],       [58777.0, 58778.0, 58779.0, 58780.0, 58781.0, 58782.0],       [58783.0, 58784.0, 58785.0, 58786.0, 58787.0, 58788.0],       [58789.0, 58790.0, 58791.0, 58792.0, 58793.0, 58794.0],       [58795.0, 58796.0, 58797.0, 58798.0, 58799.0, 58800.0]],      [[58801.0, 58802.0, 58803.0, 58804.0, 58805.0, 58806.0],       [58807.0, 58808.0, 58809.0, 58810.0, 58811.0, 58812.0],       [58813.0, 58814.0, 58815.0, 58816.0, 58817.0, 58818.0],       [58819.0, 58820.0, 58821.0, 58822.0, 58823.0, 58824.0],       [58825.0, 58826.0, 58827.0, 58828.0, 58829.0, 58830.0],       [58831.0, 58832.0, 58833.0, 58834.0, 58835.0, 58836.0],       [58837.0, 58838.0, 58839.0, 58840.0, 58841.0, 58842.0]],      [[58843.0, 58844.0, 58845.0, 58846.0, 58847.0, 58848.0],       [58849.0, 58850.0, 58851.0, 58852.0, 58853.0, 58854.0],       [58855.0, 58856.0, 58857.0, 58858.0, 58859.0, 58860.0],       [58861.0, 58862.0, 58863.0, 58864.0, 58865.0, 58866.0],       [58867.0, 58868.0, 58869.0, 58870.0, 58871.0, 58872.0],       [58873.0, 58874.0, 58875.0, 58876.0, 58877.0, 58878.0],       [58879.0, 58880.0, 58881.0, 58882.0, 58883.0, 58884.0]],      [[58885.0, 58886.0, 58887.0, 58888.0, 58889.0, 58890.0],       [58891.0, 58892.0, 58893.0, 58894.0, 58895.0, 58896.0],       [58897.0, 58898.0, 58899.0, 58900.0, 58901.0, 58902.0],       [58903.0, 58904.0, 58905.0, 58906.0, 58907.0, 58908.0],       [58909.0, 58910.0, 58911.0, 58912.0, 58913.0, 58914.0],       [58915.0, 58916.0, 58917.0, 58918.0, 58919.0, 58920.0],       [58921.0, 58922.0, 58923.0, 58924.0, 58925.0, 58926.0]],      [[58927.0, 58928.0, 58929.0, 58930.0, 58931.0, 58932.0],       [58933.0, 58934.0, 58935.0, 58936.0, 58937.0, 58938.0],       [58939.0, 58940.0, 58941.0, 58942.0, 58943.0, 58944.0],       [58945.0, 58946.0, 58947.0, 58948.0, 58949.0, 58950.0],       [58951.0, 58952.0, 58953.0, 58954.0, 58955.0, 58956.0],       [58957.0, 58958.0, 58959.0, 58960.0, 58961.0, 58962.0],       [58963.0, 58964.0, 58965.0, 58966.0, 58967.0, 58968.0]]],     [[[58969.0, 58970.0, 58971.0, 58972.0, 58973.0, 58974.0],       [58975.0, 58976.0, 58977.0, 58978.0, 58979.0, 58980.0],       [58981.0, 58982.0, 58983.0, 58984.0, 58985.0, 58986.0],       [58987.0, 58988.0, 58989.0, 58990.0, 58991.0, 58992.0],       [58993.0, 58994.0, 58995.0, 58996.0, 58997.0, 58998.0],       [58999.0, 59000.0, 59001.0, 59002.0, 59003.0, 59004.0],       [59005.0, 59006.0, 59007.0, 59008.0, 59009.0, 59010.0]],      [[59011.0, 59012.0, 59013.0, 59014.0, 59015.0, 59016.0],       [59017.0, 59018.0, 59019.0, 59020.0, 59021.0, 59022.0],       [59023.0, 59024.0, 59025.0, 59026.0, 59027.0, 59028.0],       [59029.0, 59030.0, 59031.0, 59032.0, 59033.0, 59034.0],       [59035.0, 59036.0, 59037.0, 59038.0, 59039.0, 59040.0],       [59041.0, 59042.0, 59043.0, 59044.0, 59045.0, 59046.0],       [59047.0, 59048.0, 59049.0, 59050.0, 59051.0, 59052.0]],      [[59053.0, 59054.0, 59055.0, 59056.0, 59057.0, 59058.0],       [59059.0, 59060.0, 59061.0, 59062.0, 59063.0, 59064.0],       [59065.0, 59066.0, 59067.0, 59068.0, 59069.0, 59070.0],       [59071.0, 59072.0, 59073.0, 59074.0, 59075.0, 59076.0],       [59077.0, 59078.0, 59079.0, 59080.0, 59081.0, 59082.0],       [59083.0, 59084.0, 59085.0, 59086.0, 59087.0, 59088.0],       [59089.0, 59090.0, 59091.0, 59092.0, 59093.0, 59094.0]],      [[59095.0, 59096.0, 59097.0, 59098.0, 59099.0, 59100.0],       [59101.0, 59102.0, 59103.0, 59104.0, 59105.0, 59106.0],       [59107.0, 59108.0, 59109.0, 59110.0, 59111.0, 59112.0],       [59113.0, 59114.0, 59115.0, 59116.0, 59117.0, 59118.0],       [59119.0, 59120.0, 59121.0, 59122.0, 59123.0, 59124.0],       [59125.0, 59126.0, 59127.0, 59128.0, 59129.0, 59130.0],       [59131.0, 59132.0, 59133.0, 59134.0, 59135.0, 59136.0]],      [[59137.0, 59138.0, 59139.0, 59140.0, 59141.0, 59142.0],       [59143.0, 59144.0, 59145.0, 59146.0, 59147.0, 59148.0],       [59149.0, 59150.0, 59151.0, 59152.0, 59153.0, 59154.0],       [59155.0, 59156.0, 59157.0, 59158.0, 59159.0, 59160.0],       [59161.0, 59162.0, 59163.0, 59164.0, 59165.0, 59166.0],       [59167.0, 59168.0, 59169.0, 59170.0, 59171.0, 59172.0],       [59173.0, 59174.0, 59175.0, 59176.0, 59177.0, 59178.0]],      [[59179.0, 59180.0, 59181.0, 59182.0, 59183.0, 59184.0],       [59185.0, 59186.0, 59187.0, 59188.0, 59189.0, 59190.0],       [59191.0, 59192.0, 59193.0, 59194.0, 59195.0, 59196.0],       [59197.0, 59198.0, 59199.0, 59200.0, 59201.0, 59202.0],       [59203.0, 59204.0, 59205.0, 59206.0, 59207.0, 59208.0],       [59209.0, 59210.0, 59211.0, 59212.0, 59213.0, 59214.0],       [59215.0, 59216.0, 59217.0, 59218.0, 59219.0, 59220.0]]],     [[[59221.0, 59222.0, 59223.0, 59224.0, 59225.0, 59226.0],       [59227.0, 59228.0, 59229.0, 59230.0, 59231.0, 59232.0],       [59233.0, 59234.0, 59235.0, 59236.0, 59237.0, 59238.0],       [59239.0, 59240.0, 59241.0, 59242.0, 59243.0, 59244.0],       [59245.0, 59246.0, 59247.0, 59248.0, 59249.0, 59250.0],       [59251.0, 59252.0, 59253.0, 59254.0, 59255.0, 59256.0],       [59257.0, 59258.0, 59259.0, 59260.0, 59261.0, 59262.0]],      [[59263.0, 59264.0, 59265.0, 59266.0, 59267.0, 59268.0],       [59269.0, 59270.0, 59271.0, 59272.0, 59273.0, 59274.0],       [59275.0, 59276.0, 59277.0, 59278.0, 59279.0, 59280.0],       [59281.0, 59282.0, 59283.0, 59284.0, 59285.0, 59286.0],       [59287.0, 59288.0, 59289.0, 59290.0, 59291.0, 59292.0],       [59293.0, 59294.0, 59295.0, 59296.0, 59297.0, 59298.0],       [59299.0, 59300.0, 59301.0, 59302.0, 59303.0, 59304.0]],      [[59305.0, 59306.0, 59307.0, 59308.0, 59309.0, 59310.0],       [59311.0, 59312.0, 59313.0, 59314.0, 59315.0, 59316.0],       [59317.0, 59318.0, 59319.0, 59320.0, 59321.0, 59322.0],       [59323.0, 59324.0, 59325.0, 59326.0, 59327.0, 59328.0],       [59329.0, 59330.0, 59331.0, 59332.0, 59333.0, 59334.0],       [59335.0, 59336.0, 59337.0, 59338.0, 59339.0, 59340.0],       [59341.0, 59342.0, 59343.0, 59344.0, 59345.0, 59346.0]],      [[59347.0, 59348.0, 59349.0, 59350.0, 59351.0, 59352.0],       [59353.0, 59354.0, 59355.0, 59356.0, 59357.0, 59358.0],       [59359.0, 59360.0, 59361.0, 59362.0, 59363.0, 59364.0],       [59365.0, 59366.0, 59367.0, 59368.0, 59369.0, 59370.0],       [59371.0, 59372.0, 59373.0, 59374.0, 59375.0, 59376.0],       [59377.0, 59378.0, 59379.0, 59380.0, 59381.0, 59382.0],       [59383.0, 59384.0, 59385.0, 59386.0, 59387.0, 59388.0]],      [[59389.0, 59390.0, 59391.0, 59392.0, 59393.0, 59394.0],       [59395.0, 59396.0, 59397.0, 59398.0, 59399.0, 59400.0],       [59401.0, 59402.0, 59403.0, 59404.0, 59405.0, 59406.0],       [59407.0, 59408.0, 59409.0, 59410.0, 59411.0, 59412.0],       [59413.0, 59414.0, 59415.0, 59416.0, 59417.0, 59418.0],       [59419.0, 59420.0, 59421.0, 59422.0, 59423.0, 59424.0],       [59425.0, 59426.0, 59427.0, 59428.0, 59429.0, 59430.0]],      [[59431.0, 59432.0, 59433.0, 59434.0, 59435.0, 59436.0],       [59437.0, 59438.0, 59439.0, 59440.0, 59441.0, 59442.0],       [59443.0, 59444.0, 59445.0, 59446.0, 59447.0, 59448.0],       [59449.0, 59450.0, 59451.0, 59452.0, 59453.0, 59454.0],       [59455.0, 59456.0, 59457.0, 59458.0, 59459.0, 59460.0],       [59461.0, 59462.0, 59463.0, 59464.0, 59465.0, 59466.0],       [59467.0, 59468.0, 59469.0, 59470.0, 59471.0, 59472.0]]]],    [[[[59473.0, 59474.0, 59475.0, 59476.0, 59477.0, 59478.0],       [59479.0, 59480.0, 59481.0, 59482.0, 59483.0, 59484.0],       [59485.0, 59486.0, 59487.0, 59488.0, 59489.0, 59490.0],       [59491.0, 59492.0, 59493.0, 59494.0, 59495.0, 59496.0],       [59497.0, 59498.0, 59499.0, 59500.0, 59501.0, 59502.0],       [59503.0, 59504.0, 59505.0, 59506.0, 59507.0, 59508.0],       [59509.0, 59510.0, 59511.0, 59512.0, 59513.0, 59514.0]],      [[59515.0, 59516.0, 59517.0, 59518.0, 59519.0, 59520.0],       [59521.0, 59522.0, 59523.0, 59524.0, 59525.0, 59526.0],       [59527.0, 59528.0, 59529.0, 59530.0, 59531.0, 59532.0],       [59533.0, 59534.0, 59535.0, 59536.0, 59537.0, 59538.0],       [59539.0, 59540.0, 59541.0, 59542.0, 59543.0, 59544.0],       [59545.0, 59546.0, 59547.0, 59548.0, 59549.0, 59550.0],       [59551.0, 59552.0, 59553.0, 59554.0, 59555.0, 59556.0]],      [[59557.0, 59558.0, 59559.0, 59560.0, 59561.0, 59562.0],       [59563.0, 59564.0, 59565.0, 59566.0, 59567.0, 59568.0],       [59569.0, 59570.0, 59571.0, 59572.0, 59573.0, 59574.0],       [59575.0, 59576.0, 59577.0, 59578.0, 59579.0, 59580.0],       [59581.0, 59582.0, 59583.0, 59584.0, 59585.0, 59586.0],       [59587.0, 59588.0, 59589.0, 59590.0, 59591.0, 59592.0],       [59593.0, 59594.0, 59595.0, 59596.0, 59597.0, 59598.0]],      [[59599.0, 59600.0, 59601.0, 59602.0, 59603.0, 59604.0],       [59605.0, 59606.0, 59607.0, 59608.0, 59609.0, 59610.0],       [59611.0, 59612.0, 59613.0, 59614.0, 59615.0, 59616.0],       [59617.0, 59618.0, 59619.0, 59620.0, 59621.0, 59622.0],       [59623.0, 59624.0, 59625.0, 59626.0, 59627.0, 59628.0],       [59629.0, 59630.0, 59631.0, 59632.0, 59633.0, 59634.0],       [59635.0, 59636.0, 59637.0, 59638.0, 59639.0, 59640.0]],      [[59641.0, 59642.0, 59643.0, 59644.0, 59645.0, 59646.0],       [59647.0, 59648.0, 59649.0, 59650.0, 59651.0, 59652.0],       [59653.0, 59654.0, 59655.0, 59656.0, 59657.0, 59658.0],       [59659.0, 59660.0, 59661.0, 59662.0, 59663.0, 59664.0],       [59665.0, 59666.0, 59667.0, 59668.0, 59669.0, 59670.0],       [59671.0, 59672.0, 59673.0, 59674.0, 59675.0, 59676.0],       [59677.0, 59678.0, 59679.0, 59680.0, 59681.0, 59682.0]],      [[59683.0, 59684.0, 59685.0, 59686.0, 59687.0, 59688.0],       [59689.0, 59690.0, 59691.0, 59692.0, 59693.0, 59694.0],       [59695.0, 59696.0, 59697.0, 59698.0, 59699.0, 59700.0],       [59701.0, 59702.0, 59703.0, 59704.0, 59705.0, 59706.0],       [59707.0, 59708.0, 59709.0, 59710.0, 59711.0, 59712.0],       [59713.0, 59714.0, 59715.0, 59716.0, 59717.0, 59718.0],       [59719.0, 59720.0, 59721.0, 59722.0, 59723.0, 59724.0]]],     [[[59725.0, 59726.0, 59727.0, 59728.0, 59729.0, 59730.0],       [59731.0, 59732.0, 59733.0, 59734.0, 59735.0, 59736.0],       [59737.0, 59738.0, 59739.0, 59740.0, 59741.0, 59742.0],       [59743.0, 59744.0, 59745.0, 59746.0, 59747.0, 59748.0],       [59749.0, 59750.0, 59751.0, 59752.0, 59753.0, 59754.0],       [59755.0, 59756.0, 59757.0, 59758.0, 59759.0, 59760.0],       [59761.0, 59762.0, 59763.0, 59764.0, 59765.0, 59766.0]],      [[59767.0, 59768.0, 59769.0, 59770.0, 59771.0, 59772.0],       [59773.0, 59774.0, 59775.0, 59776.0, 59777.0, 59778.0],       [59779.0, 59780.0, 59781.0, 59782.0, 59783.0, 59784.0],       [59785.0, 59786.0, 59787.0, 59788.0, 59789.0, 59790.0],       [59791.0, 59792.0, 59793.0, 59794.0, 59795.0, 59796.0],       [59797.0, 59798.0, 59799.0, 59800.0, 59801.0, 59802.0],       [59803.0, 59804.0, 59805.0, 59806.0, 59807.0, 59808.0]],      [[59809.0, 59810.0, 59811.0, 59812.0, 59813.0, 59814.0],       [59815.0, 59816.0, 59817.0, 59818.0, 59819.0, 59820.0],       [59821.0, 59822.0, 59823.0, 59824.0, 59825.0, 59826.0],       [59827.0, 59828.0, 59829.0, 59830.0, 59831.0, 59832.0],       [59833.0, 59834.0, 59835.0, 59836.0, 59837.0, 59838.0],       [59839.0, 59840.0, 59841.0, 59842.0, 59843.0, 59844.0],       [59845.0, 59846.0, 59847.0, 59848.0, 59849.0, 59850.0]],      [[59851.0, 59852.0, 59853.0, 59854.0, 59855.0, 59856.0],       [59857.0, 59858.0, 59859.0, 59860.0, 59861.0, 59862.0],       [59863.0, 59864.0, 59865.0, 59866.0, 59867.0, 59868.0],       [59869.0, 59870.0, 59871.0, 59872.0, 59873.0, 59874.0],       [59875.0, 59876.0, 59877.0, 59878.0, 59879.0, 59880.0],       [59881.0, 59882.0, 59883.0, 59884.0, 59885.0, 59886.0],       [59887.0, 59888.0, 59889.0, 59890.0, 59891.0, 59892.0]],      [[59893.0, 59894.0, 59895.0, 59896.0, 59897.0, 59898.0],       [59899.0, 59900.0, 59901.0, 59902.0, 59903.0, 59904.0],       [59905.0, 59906.0, 59907.0, 59908.0, 59909.0, 59910.0],       [59911.0, 59912.0, 59913.0, 59914.0, 59915.0, 59916.0],       [59917.0, 59918.0, 59919.0, 59920.0, 59921.0, 59922.0],       [59923.0, 59924.0, 59925.0, 59926.0, 59927.0, 59928.0],       [59929.0, 59930.0, 59931.0, 59932.0, 59933.0, 59934.0]],      [[59935.0, 59936.0, 59937.0, 59938.0, 59939.0, 59940.0],       [59941.0, 59942.0, 59943.0, 59944.0, 59945.0, 59946.0],       [59947.0, 59948.0, 59949.0, 59950.0, 59951.0, 59952.0],       [59953.0, 59954.0, 59955.0, 59956.0, 59957.0, 59958.0],       [59959.0, 59960.0, 59961.0, 59962.0, 59963.0, 59964.0],       [59965.0, 59966.0, 59967.0, 59968.0, 59969.0, 59970.0],       [59971.0, 59972.0, 59973.0, 59974.0, 59975.0, 59976.0]]],     [[[59977.0, 59978.0, 59979.0, 59980.0, 59981.0, 59982.0],       [59983.0, 59984.0, 59985.0, 59986.0, 59987.0, 59988.0],       [59989.0, 59990.0, 59991.0, 59992.0, 59993.0, 59994.0],       [59995.0, 59996.0, 59997.0, 59998.0, 59999.0, 60000.0],       [60001.0, 60002.0, 60003.0, 60004.0, 60005.0, 60006.0],       [60007.0, 60008.0, 60009.0, 60010.0, 60011.0, 60012.0],       [60013.0, 60014.0, 60015.0, 60016.0, 60017.0, 60018.0]],      [[60019.0, 60020.0, 60021.0, 60022.0, 60023.0, 60024.0],       [60025.0, 60026.0, 60027.0, 60028.0, 60029.0, 60030.0],       [60031.0, 60032.0, 60033.0, 60034.0, 60035.0, 60036.0],       [60037.0, 60038.0, 60039.0, 60040.0, 60041.0, 60042.0],       [60043.0, 60044.0, 60045.0, 60046.0, 60047.0, 60048.0],       [60049.0, 60050.0, 60051.0, 60052.0, 60053.0, 60054.0],       [60055.0, 60056.0, 60057.0, 60058.0, 60059.0, 60060.0]],      [[60061.0, 60062.0, 60063.0, 60064.0, 60065.0, 60066.0],       [60067.0, 60068.0, 60069.0, 60070.0, 60071.0, 60072.0],       [60073.0, 60074.0, 60075.0, 60076.0, 60077.0, 60078.0],       [60079.0, 60080.0, 60081.0, 60082.0, 60083.0, 60084.0],       [60085.0, 60086.0, 60087.0, 60088.0, 60089.0, 60090.0],       [60091.0, 60092.0, 60093.0, 60094.0, 60095.0, 60096.0],       [60097.0, 60098.0, 60099.0, 60100.0, 60101.0, 60102.0]],      [[60103.0, 60104.0, 60105.0, 60106.0, 60107.0, 60108.0],       [60109.0, 60110.0, 60111.0, 60112.0, 60113.0, 60114.0],       [60115.0, 60116.0, 60117.0, 60118.0, 60119.0, 60120.0],       [60121.0, 60122.0, 60123.0, 60124.0, 60125.0, 60126.0],       [60127.0, 60128.0, 60129.0, 60130.0, 60131.0, 60132.0],       [60133.0, 60134.0, 60135.0, 60136.0, 60137.0, 60138.0],       [60139.0, 60140.0, 60141.0, 60142.0, 60143.0, 60144.0]],      [[60145.0, 60146.0, 60147.0, 60148.0, 60149.0, 60150.0],       [60151.0, 60152.0, 60153.0, 60154.0, 60155.0, 60156.0],       [60157.0, 60158.0, 60159.0, 60160.0, 60161.0, 60162.0],       [60163.0, 60164.0, 60165.0, 60166.0, 60167.0, 60168.0],       [60169.0, 60170.0, 60171.0, 60172.0, 60173.0, 60174.0],       [60175.0, 60176.0, 60177.0, 60178.0, 60179.0, 60180.0],       [60181.0, 60182.0, 60183.0, 60184.0, 60185.0, 60186.0]],      [[60187.0, 60188.0, 60189.0, 60190.0, 60191.0, 60192.0],       [60193.0, 60194.0, 60195.0, 60196.0, 60197.0, 60198.0],       [60199.0, 60200.0, 60201.0, 60202.0, 60203.0, 60204.0],       [60205.0, 60206.0, 60207.0, 60208.0, 60209.0, 60210.0],       [60211.0, 60212.0, 60213.0, 60214.0, 60215.0, 60216.0],       [60217.0, 60218.0, 60219.0, 60220.0, 60221.0, 60222.0],       [60223.0, 60224.0, 60225.0, 60226.0, 60227.0, 60228.0]]],     [[[60229.0, 60230.0, 60231.0, 60232.0, 60233.0, 60234.0],       [60235.0, 60236.0, 60237.0, 60238.0, 60239.0, 60240.0],       [60241.0, 60242.0, 60243.0, 60244.0, 60245.0, 60246.0],       [60247.0, 60248.0, 60249.0, 60250.0, 60251.0, 60252.0],       [60253.0, 60254.0, 60255.0, 60256.0, 60257.0, 60258.0],       [60259.0, 60260.0, 60261.0, 60262.0, 60263.0, 60264.0],       [60265.0, 60266.0, 60267.0, 60268.0, 60269.0, 60270.0]],      [[60271.0, 60272.0, 60273.0, 60274.0, 60275.0, 60276.0],       [60277.0, 60278.0, 60279.0, 60280.0, 60281.0, 60282.0],       [60283.0, 60284.0, 60285.0, 60286.0, 60287.0, 60288.0],       [60289.0, 60290.0, 60291.0, 60292.0, 60293.0, 60294.0],       [60295.0, 60296.0, 60297.0, 60298.0, 60299.0, 60300.0],       [60301.0, 60302.0, 60303.0, 60304.0, 60305.0, 60306.0],       [60307.0, 60308.0, 60309.0, 60310.0, 60311.0, 60312.0]],      [[60313.0, 60314.0, 60315.0, 60316.0, 60317.0, 60318.0],       [60319.0, 60320.0, 60321.0, 60322.0, 60323.0, 60324.0],       [60325.0, 60326.0, 60327.0, 60328.0, 60329.0, 60330.0],       [60331.0, 60332.0, 60333.0, 60334.0, 60335.0, 60336.0],       [60337.0, 60338.0, 60339.0, 60340.0, 60341.0, 60342.0],       [60343.0, 60344.0, 60345.0, 60346.0, 60347.0, 60348.0],       [60349.0, 60350.0, 60351.0, 60352.0, 60353.0, 60354.0]],      [[60355.0, 60356.0, 60357.0, 60358.0, 60359.0, 60360.0],       [60361.0, 60362.0, 60363.0, 60364.0, 60365.0, 60366.0],       [60367.0, 60368.0, 60369.0, 60370.0, 60371.0, 60372.0],       [60373.0, 60374.0, 60375.0, 60376.0, 60377.0, 60378.0],       [60379.0, 60380.0, 60381.0, 60382.0, 60383.0, 60384.0],       [60385.0, 60386.0, 60387.0, 60388.0, 60389.0, 60390.0],       [60391.0, 60392.0, 60393.0, 60394.0, 60395.0, 60396.0]],      [[60397.0, 60398.0, 60399.0, 60400.0, 60401.0, 60402.0],       [60403.0, 60404.0, 60405.0, 60406.0, 60407.0, 60408.0],       [60409.0, 60410.0, 60411.0, 60412.0, 60413.0, 60414.0],       [60415.0, 60416.0, 60417.0, 60418.0, 60419.0, 60420.0],       [60421.0, 60422.0, 60423.0, 60424.0, 60425.0, 60426.0],       [60427.0, 60428.0, 60429.0, 60430.0, 60431.0, 60432.0],       [60433.0, 60434.0, 60435.0, 60436.0, 60437.0, 60438.0]],      [[60439.0, 60440.0, 60441.0, 60442.0, 60443.0, 60444.0],       [60445.0, 60446.0, 60447.0, 60448.0, 60449.0, 60450.0],       [60451.0, 60452.0, 60453.0, 60454.0, 60455.0, 60456.0],       [60457.0, 60458.0, 60459.0, 60460.0, 60461.0, 60462.0],       [60463.0, 60464.0, 60465.0, 60466.0, 60467.0, 60468.0],       [60469.0, 60470.0, 60471.0, 60472.0, 60473.0, 60474.0],       [60475.0, 60476.0, 60477.0, 60478.0, 60479.0, 60480.0]]]]],   [[[[[60481.0, 60482.0, 60483.0, 60484.0, 60485.0, 60486.0],       [60487.0, 60488.0, 60489.0, 60490.0, 60491.0, 60492.0],       [60493.0, 60494.0, 60495.0, 60496.0, 60497.0, 60498.0],       [60499.0, 60500.0, 60501.0, 60502.0, 60503.0, 60504.0],       [60505.0, 60506.0, 60507.0, 60508.0, 60509.0, 60510.0],       [60511.0, 60512.0, 60513.0, 60514.0, 60515.0, 60516.0],       [60517.0, 60518.0, 60519.0, 60520.0, 60521.0, 60522.0]],      [[60523.0, 60524.0, 60525.0, 60526.0, 60527.0, 60528.0],       [60529.0, 60530.0, 60531.0, 60532.0, 60533.0, 60534.0],       [60535.0, 60536.0, 60537.0, 60538.0, 60539.0, 60540.0],       [60541.0, 60542.0, 60543.0, 60544.0, 60545.0, 60546.0],       [60547.0, 60548.0, 60549.0, 60550.0, 60551.0, 60552.0],       [60553.0, 60554.0, 60555.0, 60556.0, 60557.0, 60558.0],       [60559.0, 60560.0, 60561.0, 60562.0, 60563.0, 60564.0]],      [[60565.0, 60566.0, 60567.0, 60568.0, 60569.0, 60570.0],       [60571.0, 60572.0, 60573.0, 60574.0, 60575.0, 60576.0],       [60577.0, 60578.0, 60579.0, 60580.0, 60581.0, 60582.0],       [60583.0, 60584.0, 60585.0, 60586.0, 60587.0, 60588.0],       [60589.0, 60590.0, 60591.0, 60592.0, 60593.0, 60594.0],       [60595.0, 60596.0, 60597.0, 60598.0, 60599.0, 60600.0],       [60601.0, 60602.0, 60603.0, 60604.0, 60605.0, 60606.0]],      [[60607.0, 60608.0, 60609.0, 60610.0, 60611.0, 60612.0],       [60613.0, 60614.0, 60615.0, 60616.0, 60617.0, 60618.0],       [60619.0, 60620.0, 60621.0, 60622.0, 60623.0, 60624.0],       [60625.0, 60626.0, 60627.0, 60628.0, 60629.0, 60630.0],       [60631.0, 60632.0, 60633.0, 60634.0, 60635.0, 60636.0],       [60637.0, 60638.0, 60639.0, 60640.0, 60641.0, 60642.0],       [60643.0, 60644.0, 60645.0, 60646.0, 60647.0, 60648.0]],      [[60649.0, 60650.0, 60651.0, 60652.0, 60653.0, 60654.0],       [60655.0, 60656.0, 60657.0, 60658.0, 60659.0, 60660.0],       [60661.0, 60662.0, 60663.0, 60664.0, 60665.0, 60666.0],       [60667.0, 60668.0, 60669.0, 60670.0, 60671.0, 60672.0],       [60673.0, 60674.0, 60675.0, 60676.0, 60677.0, 60678.0],       [60679.0, 60680.0, 60681.0, 60682.0, 60683.0, 60684.0],       [60685.0, 60686.0, 60687.0, 60688.0, 60689.0, 60690.0]],      [[60691.0, 60692.0, 60693.0, 60694.0, 60695.0, 60696.0],       [60697.0, 60698.0, 60699.0, 60700.0, 60701.0, 60702.0],       [60703.0, 60704.0, 60705.0, 60706.0, 60707.0, 60708.0],       [60709.0, 60710.0, 60711.0, 60712.0, 60713.0, 60714.0],       [60715.0, 60716.0, 60717.0, 60718.0, 60719.0, 60720.0],       [60721.0, 60722.0, 60723.0, 60724.0, 60725.0, 60726.0],       [60727.0, 60728.0, 60729.0, 60730.0, 60731.0, 60732.0]]],     [[[60733.0, 60734.0, 60735.0, 60736.0, 60737.0, 60738.0],       [60739.0, 60740.0, 60741.0, 60742.0, 60743.0, 60744.0],       [60745.0, 60746.0, 60747.0, 60748.0, 60749.0, 60750.0],       [60751.0, 60752.0, 60753.0, 60754.0, 60755.0, 60756.0],       [60757.0, 60758.0, 60759.0, 60760.0, 60761.0, 60762.0],       [60763.0, 60764.0, 60765.0, 60766.0, 60767.0, 60768.0],       [60769.0, 60770.0, 60771.0, 60772.0, 60773.0, 60774.0]],      [[60775.0, 60776.0, 60777.0, 60778.0, 60779.0, 60780.0],       [60781.0, 60782.0, 60783.0, 60784.0, 60785.0, 60786.0],       [60787.0, 60788.0, 60789.0, 60790.0, 60791.0, 60792.0],       [60793.0, 60794.0, 60795.0, 60796.0, 60797.0, 60798.0],       [60799.0, 60800.0, 60801.0, 60802.0, 60803.0, 60804.0],       [60805.0, 60806.0, 60807.0, 60808.0, 60809.0, 60810.0],       [60811.0, 60812.0, 60813.0, 60814.0, 60815.0, 60816.0]],      [[60817.0, 60818.0, 60819.0, 60820.0, 60821.0, 60822.0],       [60823.0, 60824.0, 60825.0, 60826.0, 60827.0, 60828.0],       [60829.0, 60830.0, 60831.0, 60832.0, 60833.0, 60834.0],       [60835.0, 60836.0, 60837.0, 60838.0, 60839.0, 60840.0],       [60841.0, 60842.0, 60843.0, 60844.0, 60845.0, 60846.0],       [60847.0, 60848.0, 60849.0, 60850.0, 60851.0, 60852.0],       [60853.0, 60854.0, 60855.0, 60856.0, 60857.0, 60858.0]],      [[60859.0, 60860.0, 60861.0, 60862.0, 60863.0, 60864.0],       [60865.0, 60866.0, 60867.0, 60868.0, 60869.0, 60870.0],       [60871.0, 60872.0, 60873.0, 60874.0, 60875.0, 60876.0],       [60877.0, 60878.0, 60879.0, 60880.0, 60881.0, 60882.0],       [60883.0, 60884.0, 60885.0, 60886.0, 60887.0, 60888.0],       [60889.0, 60890.0, 60891.0, 60892.0, 60893.0, 60894.0],       [60895.0, 60896.0, 60897.0, 60898.0, 60899.0, 60900.0]],      [[60901.0, 60902.0, 60903.0, 60904.0, 60905.0, 60906.0],       [60907.0, 60908.0, 60909.0, 60910.0, 60911.0, 60912.0],       [60913.0, 60914.0, 60915.0, 60916.0, 60917.0, 60918.0],       [60919.0, 60920.0, 60921.0, 60922.0, 60923.0, 60924.0],       [60925.0, 60926.0, 60927.0, 60928.0, 60929.0, 60930.0],       [60931.0, 60932.0, 60933.0, 60934.0, 60935.0, 60936.0],       [60937.0, 60938.0, 60939.0, 60940.0, 60941.0, 60942.0]],      [[60943.0, 60944.0, 60945.0, 60946.0, 60947.0, 60948.0],       [60949.0, 60950.0, 60951.0, 60952.0, 60953.0, 60954.0],       [60955.0, 60956.0, 60957.0, 60958.0, 60959.0, 60960.0],       [60961.0, 60962.0, 60963.0, 60964.0, 60965.0, 60966.0],       [60967.0, 60968.0, 60969.0, 60970.0, 60971.0, 60972.0],       [60973.0, 60974.0, 60975.0, 60976.0, 60977.0, 60978.0],       [60979.0, 60980.0, 60981.0, 60982.0, 60983.0, 60984.0]]],     [[[60985.0, 60986.0, 60987.0, 60988.0, 60989.0, 60990.0],       [60991.0, 60992.0, 60993.0, 60994.0, 60995.0, 60996.0],       [60997.0, 60998.0, 60999.0, 61000.0, 61001.0, 61002.0],       [61003.0, 61004.0, 61005.0, 61006.0, 61007.0, 61008.0],       [61009.0, 61010.0, 61011.0, 61012.0, 61013.0, 61014.0],       [61015.0, 61016.0, 61017.0, 61018.0, 61019.0, 61020.0],       [61021.0, 61022.0, 61023.0, 61024.0, 61025.0, 61026.0]],      [[61027.0, 61028.0, 61029.0, 61030.0, 61031.0, 61032.0],       [61033.0, 61034.0, 61035.0, 61036.0, 61037.0, 61038.0],       [61039.0, 61040.0, 61041.0, 61042.0, 61043.0, 61044.0],       [61045.0, 61046.0, 61047.0, 61048.0, 61049.0, 61050.0],       [61051.0, 61052.0, 61053.0, 61054.0, 61055.0, 61056.0],       [61057.0, 61058.0, 61059.0, 61060.0, 61061.0, 61062.0],       [61063.0, 61064.0, 61065.0, 61066.0, 61067.0, 61068.0]],      [[61069.0, 61070.0, 61071.0, 61072.0, 61073.0, 61074.0],       [61075.0, 61076.0, 61077.0, 61078.0, 61079.0, 61080.0],       [61081.0, 61082.0, 61083.0, 61084.0, 61085.0, 61086.0],       [61087.0, 61088.0, 61089.0, 61090.0, 61091.0, 61092.0],       [61093.0, 61094.0, 61095.0, 61096.0, 61097.0, 61098.0],       [61099.0, 61100.0, 61101.0, 61102.0, 61103.0, 61104.0],       [61105.0, 61106.0, 61107.0, 61108.0, 61109.0, 61110.0]],      [[61111.0, 61112.0, 61113.0, 61114.0, 61115.0, 61116.0],       [61117.0, 61118.0, 61119.0, 61120.0, 61121.0, 61122.0],       [61123.0, 61124.0, 61125.0, 61126.0, 61127.0, 61128.0],       [61129.0, 61130.0, 61131.0, 61132.0, 61133.0, 61134.0],       [61135.0, 61136.0, 61137.0, 61138.0, 61139.0, 61140.0],       [61141.0, 61142.0, 61143.0, 61144.0, 61145.0, 61146.0],       [61147.0, 61148.0, 61149.0, 61150.0, 61151.0, 61152.0]],      [[61153.0, 61154.0, 61155.0, 61156.0, 61157.0, 61158.0],       [61159.0, 61160.0, 61161.0, 61162.0, 61163.0, 61164.0],       [61165.0, 61166.0, 61167.0, 61168.0, 61169.0, 61170.0],       [61171.0, 61172.0, 61173.0, 61174.0, 61175.0, 61176.0],       [61177.0, 61178.0, 61179.0, 61180.0, 61181.0, 61182.0],       [61183.0, 61184.0, 61185.0, 61186.0, 61187.0, 61188.0],       [61189.0, 61190.0, 61191.0, 61192.0, 61193.0, 61194.0]],      [[61195.0, 61196.0, 61197.0, 61198.0, 61199.0, 61200.0],       [61201.0, 61202.0, 61203.0, 61204.0, 61205.0, 61206.0],       [61207.0, 61208.0, 61209.0, 61210.0, 61211.0, 61212.0],       [61213.0, 61214.0, 61215.0, 61216.0, 61217.0, 61218.0],       [61219.0, 61220.0, 61221.0, 61222.0, 61223.0, 61224.0],       [61225.0, 61226.0, 61227.0, 61228.0, 61229.0, 61230.0],       [61231.0, 61232.0, 61233.0, 61234.0, 61235.0, 61236.0]]],     [[[61237.0, 61238.0, 61239.0, 61240.0, 61241.0, 61242.0],       [61243.0, 61244.0, 61245.0, 61246.0, 61247.0, 61248.0],       [61249.0, 61250.0, 61251.0, 61252.0, 61253.0, 61254.0],       [61255.0, 61256.0, 61257.0, 61258.0, 61259.0, 61260.0],       [61261.0, 61262.0, 61263.0, 61264.0, 61265.0, 61266.0],       [61267.0, 61268.0, 61269.0, 61270.0, 61271.0, 61272.0],       [61273.0, 61274.0, 61275.0, 61276.0, 61277.0, 61278.0]],      [[61279.0, 61280.0, 61281.0, 61282.0, 61283.0, 61284.0],       [61285.0, 61286.0, 61287.0, 61288.0, 61289.0, 61290.0],       [61291.0, 61292.0, 61293.0, 61294.0, 61295.0, 61296.0],       [61297.0, 61298.0, 61299.0, 61300.0, 61301.0, 61302.0],       [61303.0, 61304.0, 61305.0, 61306.0, 61307.0, 61308.0],       [61309.0, 61310.0, 61311.0, 61312.0, 61313.0, 61314.0],       [61315.0, 61316.0, 61317.0, 61318.0, 61319.0, 61320.0]],      [[61321.0, 61322.0, 61323.0, 61324.0, 61325.0, 61326.0],       [61327.0, 61328.0, 61329.0, 61330.0, 61331.0, 61332.0],       [61333.0, 61334.0, 61335.0, 61336.0, 61337.0, 61338.0],       [61339.0, 61340.0, 61341.0, 61342.0, 61343.0, 61344.0],       [61345.0, 61346.0, 61347.0, 61348.0, 61349.0, 61350.0],       [61351.0, 61352.0, 61353.0, 61354.0, 61355.0, 61356.0],       [61357.0, 61358.0, 61359.0, 61360.0, 61361.0, 61362.0]],      [[61363.0, 61364.0, 61365.0, 61366.0, 61367.0, 61368.0],       [61369.0, 61370.0, 61371.0, 61372.0, 61373.0, 61374.0],       [61375.0, 61376.0, 61377.0, 61378.0, 61379.0, 61380.0],       [61381.0, 61382.0, 61383.0, 61384.0, 61385.0, 61386.0],       [61387.0, 61388.0, 61389.0, 61390.0, 61391.0, 61392.0],       [61393.0, 61394.0, 61395.0, 61396.0, 61397.0, 61398.0],       [61399.0, 61400.0, 61401.0, 61402.0, 61403.0, 61404.0]],      [[61405.0, 61406.0, 61407.0, 61408.0, 61409.0, 61410.0],       [61411.0, 61412.0, 61413.0, 61414.0, 61415.0, 61416.0],       [61417.0, 61418.0, 61419.0, 61420.0, 61421.0, 61422.0],       [61423.0, 61424.0, 61425.0, 61426.0, 61427.0, 61428.0],       [61429.0, 61430.0, 61431.0, 61432.0, 61433.0, 61434.0],       [61435.0, 61436.0, 61437.0, 61438.0, 61439.0, 61440.0],       [61441.0, 61442.0, 61443.0, 61444.0, 61445.0, 61446.0]],      [[61447.0, 61448.0, 61449.0, 61450.0, 61451.0, 61452.0],       [61453.0, 61454.0, 61455.0, 61456.0, 61457.0, 61458.0],       [61459.0, 61460.0, 61461.0, 61462.0, 61463.0, 61464.0],       [61465.0, 61466.0, 61467.0, 61468.0, 61469.0, 61470.0],       [61471.0, 61472.0, 61473.0, 61474.0, 61475.0, 61476.0],       [61477.0, 61478.0, 61479.0, 61480.0, 61481.0, 61482.0],       [61483.0, 61484.0, 61485.0, 61486.0, 61487.0, 61488.0]]]],    [[[[61489.0, 61490.0, 61491.0, 61492.0, 61493.0, 61494.0],       [61495.0, 61496.0, 61497.0, 61498.0, 61499.0, 61500.0],       [61501.0, 61502.0, 61503.0, 61504.0, 61505.0, 61506.0],       [61507.0, 61508.0, 61509.0, 61510.0, 61511.0, 61512.0],       [61513.0, 61514.0, 61515.0, 61516.0, 61517.0, 61518.0],       [61519.0, 61520.0, 61521.0, 61522.0, 61523.0, 61524.0],       [61525.0, 61526.0, 61527.0, 61528.0, 61529.0, 61530.0]],      [[61531.0, 61532.0, 61533.0, 61534.0, 61535.0, 61536.0],       [61537.0, 61538.0, 61539.0, 61540.0, 61541.0, 61542.0],       [61543.0, 61544.0, 61545.0, 61546.0, 61547.0, 61548.0],       [61549.0, 61550.0, 61551.0, 61552.0, 61553.0, 61554.0],       [61555.0, 61556.0, 61557.0, 61558.0, 61559.0, 61560.0],       [61561.0, 61562.0, 61563.0, 61564.0, 61565.0, 61566.0],       [61567.0, 61568.0, 61569.0, 61570.0, 61571.0, 61572.0]],      [[61573.0, 61574.0, 61575.0, 61576.0, 61577.0, 61578.0],       [61579.0, 61580.0, 61581.0, 61582.0, 61583.0, 61584.0],       [61585.0, 61586.0, 61587.0, 61588.0, 61589.0, 61590.0],       [61591.0, 61592.0, 61593.0, 61594.0, 61595.0, 61596.0],       [61597.0, 61598.0, 61599.0, 61600.0, 61601.0, 61602.0],       [61603.0, 61604.0, 61605.0, 61606.0, 61607.0, 61608.0],       [61609.0, 61610.0, 61611.0, 61612.0, 61613.0, 61614.0]],      [[61615.0, 61616.0, 61617.0, 61618.0, 61619.0, 61620.0],       [61621.0, 61622.0, 61623.0, 61624.0, 61625.0, 61626.0],       [61627.0, 61628.0, 61629.0, 61630.0, 61631.0, 61632.0],       [61633.0, 61634.0, 61635.0, 61636.0, 61637.0, 61638.0],       [61639.0, 61640.0, 61641.0, 61642.0, 61643.0, 61644.0],       [61645.0, 61646.0, 61647.0, 61648.0, 61649.0, 61650.0],       [61651.0, 61652.0, 61653.0, 61654.0, 61655.0, 61656.0]],      [[61657.0, 61658.0, 61659.0, 61660.0, 61661.0, 61662.0],       [61663.0, 61664.0, 61665.0, 61666.0, 61667.0, 61668.0],       [61669.0, 61670.0, 61671.0, 61672.0, 61673.0, 61674.0],       [61675.0, 61676.0, 61677.0, 61678.0, 61679.0, 61680.0],       [61681.0, 61682.0, 61683.0, 61684.0, 61685.0, 61686.0],       [61687.0, 61688.0, 61689.0, 61690.0, 61691.0, 61692.0],       [61693.0, 61694.0, 61695.0, 61696.0, 61697.0, 61698.0]],      [[61699.0, 61700.0, 61701.0, 61702.0, 61703.0, 61704.0],       [61705.0, 61706.0, 61707.0, 61708.0, 61709.0, 61710.0],       [61711.0, 61712.0, 61713.0, 61714.0, 61715.0, 61716.0],       [61717.0, 61718.0, 61719.0, 61720.0, 61721.0, 61722.0],       [61723.0, 61724.0, 61725.0, 61726.0, 61727.0, 61728.0],       [61729.0, 61730.0, 61731.0, 61732.0, 61733.0, 61734.0],       [61735.0, 61736.0, 61737.0, 61738.0, 61739.0, 61740.0]]],     [[[61741.0, 61742.0, 61743.0, 61744.0, 61745.0, 61746.0],       [61747.0, 61748.0, 61749.0, 61750.0, 61751.0, 61752.0],       [61753.0, 61754.0, 61755.0, 61756.0, 61757.0, 61758.0],       [61759.0, 61760.0, 61761.0, 61762.0, 61763.0, 61764.0],       [61765.0, 61766.0, 61767.0, 61768.0, 61769.0, 61770.0],       [61771.0, 61772.0, 61773.0, 61774.0, 61775.0, 61776.0],       [61777.0, 61778.0, 61779.0, 61780.0, 61781.0, 61782.0]],      [[61783.0, 61784.0, 61785.0, 61786.0, 61787.0, 61788.0],       [61789.0, 61790.0, 61791.0, 61792.0, 61793.0, 61794.0],       [61795.0, 61796.0, 61797.0, 61798.0, 61799.0, 61800.0],       [61801.0, 61802.0, 61803.0, 61804.0, 61805.0, 61806.0],       [61807.0, 61808.0, 61809.0, 61810.0, 61811.0, 61812.0],       [61813.0, 61814.0, 61815.0, 61816.0, 61817.0, 61818.0],       [61819.0, 61820.0, 61821.0, 61822.0, 61823.0, 61824.0]],      [[61825.0, 61826.0, 61827.0, 61828.0, 61829.0, 61830.0],       [61831.0, 61832.0, 61833.0, 61834.0, 61835.0, 61836.0],       [61837.0, 61838.0, 61839.0, 61840.0, 61841.0, 61842.0],       [61843.0, 61844.0, 61845.0, 61846.0, 61847.0, 61848.0],       [61849.0, 61850.0, 61851.0, 61852.0, 61853.0, 61854.0],       [61855.0, 61856.0, 61857.0, 61858.0, 61859.0, 61860.0],       [61861.0, 61862.0, 61863.0, 61864.0, 61865.0, 61866.0]],      [[61867.0, 61868.0, 61869.0, 61870.0, 61871.0, 61872.0],       [61873.0, 61874.0, 61875.0, 61876.0, 61877.0, 61878.0],       [61879.0, 61880.0, 61881.0, 61882.0, 61883.0, 61884.0],       [61885.0, 61886.0, 61887.0, 61888.0, 61889.0, 61890.0],       [61891.0, 61892.0, 61893.0, 61894.0, 61895.0, 61896.0],       [61897.0, 61898.0, 61899.0, 61900.0, 61901.0, 61902.0],       [61903.0, 61904.0, 61905.0, 61906.0, 61907.0, 61908.0]],      [[61909.0, 61910.0, 61911.0, 61912.0, 61913.0, 61914.0],       [61915.0, 61916.0, 61917.0, 61918.0, 61919.0, 61920.0],       [61921.0, 61922.0, 61923.0, 61924.0, 61925.0, 61926.0],       [61927.0, 61928.0, 61929.0, 61930.0, 61931.0, 61932.0],       [61933.0, 61934.0, 61935.0, 61936.0, 61937.0, 61938.0],       [61939.0, 61940.0, 61941.0, 61942.0, 61943.0, 61944.0],       [61945.0, 61946.0, 61947.0, 61948.0, 61949.0, 61950.0]],      [[61951.0, 61952.0, 61953.0, 61954.0, 61955.0, 61956.0],       [61957.0, 61958.0, 61959.0, 61960.0, 61961.0, 61962.0],       [61963.0, 61964.0, 61965.0, 61966.0, 61967.0, 61968.0],       [61969.0, 61970.0, 61971.0, 61972.0, 61973.0, 61974.0],       [61975.0, 61976.0, 61977.0, 61978.0, 61979.0, 61980.0],       [61981.0, 61982.0, 61983.0, 61984.0, 61985.0, 61986.0],       [61987.0, 61988.0, 61989.0, 61990.0, 61991.0, 61992.0]]],     [[[61993.0, 61994.0, 61995.0, 61996.0, 61997.0, 61998.0],       [61999.0, 62000.0, 62001.0, 62002.0, 62003.0, 62004.0],       [62005.0, 62006.0, 62007.0, 62008.0, 62009.0, 62010.0],       [62011.0, 62012.0, 62013.0, 62014.0, 62015.0, 62016.0],       [62017.0, 62018.0, 62019.0, 62020.0, 62021.0, 62022.0],       [62023.0, 62024.0, 62025.0, 62026.0, 62027.0, 62028.0],       [62029.0, 62030.0, 62031.0, 62032.0, 62033.0, 62034.0]],      [[62035.0, 62036.0, 62037.0, 62038.0, 62039.0, 62040.0],       [62041.0, 62042.0, 62043.0, 62044.0, 62045.0, 62046.0],       [62047.0, 62048.0, 62049.0, 62050.0, 62051.0, 62052.0],       [62053.0, 62054.0, 62055.0, 62056.0, 62057.0, 62058.0],       [62059.0, 62060.0, 62061.0, 62062.0, 62063.0, 62064.0],       [62065.0, 62066.0, 62067.0, 62068.0, 62069.0, 62070.0],       [62071.0, 62072.0, 62073.0, 62074.0, 62075.0, 62076.0]],      [[62077.0, 62078.0, 62079.0, 62080.0, 62081.0, 62082.0],       [62083.0, 62084.0, 62085.0, 62086.0, 62087.0, 62088.0],       [62089.0, 62090.0, 62091.0, 62092.0, 62093.0, 62094.0],       [62095.0, 62096.0, 62097.0, 62098.0, 62099.0, 62100.0],       [62101.0, 62102.0, 62103.0, 62104.0, 62105.0, 62106.0],       [62107.0, 62108.0, 62109.0, 62110.0, 62111.0, 62112.0],       [62113.0, 62114.0, 62115.0, 62116.0, 62117.0, 62118.0]],      [[62119.0, 62120.0, 62121.0, 62122.0, 62123.0, 62124.0],       [62125.0, 62126.0, 62127.0, 62128.0, 62129.0, 62130.0],       [62131.0, 62132.0, 62133.0, 62134.0, 62135.0, 62136.0],       [62137.0, 62138.0, 62139.0, 62140.0, 62141.0, 62142.0],       [62143.0, 62144.0, 62145.0, 62146.0, 62147.0, 62148.0],       [62149.0, 62150.0, 62151.0, 62152.0, 62153.0, 62154.0],       [62155.0, 62156.0, 62157.0, 62158.0, 62159.0, 62160.0]],      [[62161.0, 62162.0, 62163.0, 62164.0, 62165.0, 62166.0],       [62167.0, 62168.0, 62169.0, 62170.0, 62171.0, 62172.0],       [62173.0, 62174.0, 62175.0, 62176.0, 62177.0, 62178.0],       [62179.0, 62180.0, 62181.0, 62182.0, 62183.0, 62184.0],       [62185.0, 62186.0, 62187.0, 62188.0, 62189.0, 62190.0],       [62191.0, 62192.0, 62193.0, 62194.0, 62195.0, 62196.0],       [62197.0, 62198.0, 62199.0, 62200.0, 62201.0, 62202.0]],      [[62203.0, 62204.0, 62205.0, 62206.0, 62207.0, 62208.0],       [62209.0, 62210.0, 62211.0, 62212.0, 62213.0, 62214.0],       [62215.0, 62216.0, 62217.0, 62218.0, 62219.0, 62220.0],       [62221.0, 62222.0, 62223.0, 62224.0, 62225.0, 62226.0],       [62227.0, 62228.0, 62229.0, 62230.0, 62231.0, 62232.0],       [62233.0, 62234.0, 62235.0, 62236.0, 62237.0, 62238.0],       [62239.0, 62240.0, 62241.0, 62242.0, 62243.0, 62244.0]]],     [[[62245.0, 62246.0, 62247.0, 62248.0, 62249.0, 62250.0],       [62251.0, 62252.0, 62253.0, 62254.0, 62255.0, 62256.0],       [62257.0, 62258.0, 62259.0, 62260.0, 62261.0, 62262.0],       [62263.0, 62264.0, 62265.0, 62266.0, 62267.0, 62268.0],       [62269.0, 62270.0, 62271.0, 62272.0, 62273.0, 62274.0],       [62275.0, 62276.0, 62277.0, 62278.0, 62279.0, 62280.0],       [62281.0, 62282.0, 62283.0, 62284.0, 62285.0, 62286.0]],      [[62287.0, 62288.0, 62289.0, 62290.0, 62291.0, 62292.0],       [62293.0, 62294.0, 62295.0, 62296.0, 62297.0, 62298.0],       [62299.0, 62300.0, 62301.0, 62302.0, 62303.0, 62304.0],       [62305.0, 62306.0, 62307.0, 62308.0, 62309.0, 62310.0],       [62311.0, 62312.0, 62313.0, 62314.0, 62315.0, 62316.0],       [62317.0, 62318.0, 62319.0, 62320.0, 62321.0, 62322.0],       [62323.0, 62324.0, 62325.0, 62326.0, 62327.0, 62328.0]],      [[62329.0, 62330.0, 62331.0, 62332.0, 62333.0, 62334.0],       [62335.0, 62336.0, 62337.0, 62338.0, 62339.0, 62340.0],       [62341.0, 62342.0, 62343.0, 62344.0, 62345.0, 62346.0],       [62347.0, 62348.0, 62349.0, 62350.0, 62351.0, 62352.0],       [62353.0, 62354.0, 62355.0, 62356.0, 62357.0, 62358.0],       [62359.0, 62360.0, 62361.0, 62362.0, 62363.0, 62364.0],       [62365.0, 62366.0, 62367.0, 62368.0, 62369.0, 62370.0]],      [[62371.0, 62372.0, 62373.0, 62374.0, 62375.0, 62376.0],       [62377.0, 62378.0, 62379.0, 62380.0, 62381.0, 62382.0],       [62383.0, 62384.0, 62385.0, 62386.0, 62387.0, 62388.0],       [62389.0, 62390.0, 62391.0, 62392.0, 62393.0, 62394.0],       [62395.0, 62396.0, 62397.0, 62398.0, 62399.0, 62400.0],       [62401.0, 62402.0, 62403.0, 62404.0, 62405.0, 62406.0],       [62407.0, 62408.0, 62409.0, 62410.0, 62411.0, 62412.0]],      [[62413.0, 62414.0, 62415.0, 62416.0, 62417.0, 62418.0],       [62419.0, 62420.0, 62421.0, 62422.0, 62423.0, 62424.0],       [62425.0, 62426.0, 62427.0, 62428.0, 62429.0, 62430.0],       [62431.0, 62432.0, 62433.0, 62434.0, 62435.0, 62436.0],       [62437.0, 62438.0, 62439.0, 62440.0, 62441.0, 62442.0],       [62443.0, 62444.0, 62445.0, 62446.0, 62447.0, 62448.0],       [62449.0, 62450.0, 62451.0, 62452.0, 62453.0, 62454.0]],      [[62455.0, 62456.0, 62457.0, 62458.0, 62459.0, 62460.0],       [62461.0, 62462.0, 62463.0, 62464.0, 62465.0, 62466.0],       [62467.0, 62468.0, 62469.0, 62470.0, 62471.0, 62472.0],       [62473.0, 62474.0, 62475.0, 62476.0, 62477.0, 62478.0],       [62479.0, 62480.0, 62481.0, 62482.0, 62483.0, 62484.0],       [62485.0, 62486.0, 62487.0, 62488.0, 62489.0, 62490.0],       [62491.0, 62492.0, 62493.0, 62494.0, 62495.0, 62496.0]]]],    [[[[62497.0, 62498.0, 62499.0, 62500.0, 62501.0, 62502.0],       [62503.0, 62504.0, 62505.0, 62506.0, 62507.0, 62508.0],       [62509.0, 62510.0, 62511.0, 62512.0, 62513.0, 62514.0],       [62515.0, 62516.0, 62517.0, 62518.0, 62519.0, 62520.0],       [62521.0, 62522.0, 62523.0, 62524.0, 62525.0, 62526.0],       [62527.0, 62528.0, 62529.0, 62530.0, 62531.0, 62532.0],       [62533.0, 62534.0, 62535.0, 62536.0, 62537.0, 62538.0]],      [[62539.0, 62540.0, 62541.0, 62542.0, 62543.0, 62544.0],       [62545.0, 62546.0, 62547.0, 62548.0, 62549.0, 62550.0],       [62551.0, 62552.0, 62553.0, 62554.0, 62555.0, 62556.0],       [62557.0, 62558.0, 62559.0, 62560.0, 62561.0, 62562.0],       [62563.0, 62564.0, 62565.0, 62566.0, 62567.0, 62568.0],       [62569.0, 62570.0, 62571.0, 62572.0, 62573.0, 62574.0],       [62575.0, 62576.0, 62577.0, 62578.0, 62579.0, 62580.0]],      [[62581.0, 62582.0, 62583.0, 62584.0, 62585.0, 62586.0],       [62587.0, 62588.0, 62589.0, 62590.0, 62591.0, 62592.0],       [62593.0, 62594.0, 62595.0, 62596.0, 62597.0, 62598.0],       [62599.0, 62600.0, 62601.0, 62602.0, 62603.0, 62604.0],       [62605.0, 62606.0, 62607.0, 62608.0, 62609.0, 62610.0],       [62611.0, 62612.0, 62613.0, 62614.0, 62615.0, 62616.0],       [62617.0, 62618.0, 62619.0, 62620.0, 62621.0, 62622.0]],      [[62623.0, 62624.0, 62625.0, 62626.0, 62627.0, 62628.0],       [62629.0, 62630.0, 62631.0, 62632.0, 62633.0, 62634.0],       [62635.0, 62636.0, 62637.0, 62638.0, 62639.0, 62640.0],       [62641.0, 62642.0, 62643.0, 62644.0, 62645.0, 62646.0],       [62647.0, 62648.0, 62649.0, 62650.0, 62651.0, 62652.0],       [62653.0, 62654.0, 62655.0, 62656.0, 62657.0, 62658.0],       [62659.0, 62660.0, 62661.0, 62662.0, 62663.0, 62664.0]],      [[62665.0, 62666.0, 62667.0, 62668.0, 62669.0, 62670.0],       [62671.0, 62672.0, 62673.0, 62674.0, 62675.0, 62676.0],       [62677.0, 62678.0, 62679.0, 62680.0, 62681.0, 62682.0],       [62683.0, 62684.0, 62685.0, 62686.0, 62687.0, 62688.0],       [62689.0, 62690.0, 62691.0, 62692.0, 62693.0, 62694.0],       [62695.0, 62696.0, 62697.0, 62698.0, 62699.0, 62700.0],       [62701.0, 62702.0, 62703.0, 62704.0, 62705.0, 62706.0]],      [[62707.0, 62708.0, 62709.0, 62710.0, 62711.0, 62712.0],       [62713.0, 62714.0, 62715.0, 62716.0, 62717.0, 62718.0],       [62719.0, 62720.0, 62721.0, 62722.0, 62723.0, 62724.0],       [62725.0, 62726.0, 62727.0, 62728.0, 62729.0, 62730.0],       [62731.0, 62732.0, 62733.0, 62734.0, 62735.0, 62736.0],       [62737.0, 62738.0, 62739.0, 62740.0, 62741.0, 62742.0],       [62743.0, 62744.0, 62745.0, 62746.0, 62747.0, 62748.0]]],     [[[62749.0, 62750.0, 62751.0, 62752.0, 62753.0, 62754.0],       [62755.0, 62756.0, 62757.0, 62758.0, 62759.0, 62760.0],       [62761.0, 62762.0, 62763.0, 62764.0, 62765.0, 62766.0],       [62767.0, 62768.0, 62769.0, 62770.0, 62771.0, 62772.0],       [62773.0, 62774.0, 62775.0, 62776.0, 62777.0, 62778.0],       [62779.0, 62780.0, 62781.0, 62782.0, 62783.0, 62784.0],       [62785.0, 62786.0, 62787.0, 62788.0, 62789.0, 62790.0]],      [[62791.0, 62792.0, 62793.0, 62794.0, 62795.0, 62796.0],       [62797.0, 62798.0, 62799.0, 62800.0, 62801.0, 62802.0],       [62803.0, 62804.0, 62805.0, 62806.0, 62807.0, 62808.0],       [62809.0, 62810.0, 62811.0, 62812.0, 62813.0, 62814.0],       [62815.0, 62816.0, 62817.0, 62818.0, 62819.0, 62820.0],       [62821.0, 62822.0, 62823.0, 62824.0, 62825.0, 62826.0],       [62827.0, 62828.0, 62829.0, 62830.0, 62831.0, 62832.0]],      [[62833.0, 62834.0, 62835.0, 62836.0, 62837.0, 62838.0],       [62839.0, 62840.0, 62841.0, 62842.0, 62843.0, 62844.0],       [62845.0, 62846.0, 62847.0, 62848.0, 62849.0, 62850.0],       [62851.0, 62852.0, 62853.0, 62854.0, 62855.0, 62856.0],       [62857.0, 62858.0, 62859.0, 62860.0, 62861.0, 62862.0],       [62863.0, 62864.0, 62865.0, 62866.0, 62867.0, 62868.0],       [62869.0, 62870.0, 62871.0, 62872.0, 62873.0, 62874.0]],      [[62875.0, 62876.0, 62877.0, 62878.0, 62879.0, 62880.0],       [62881.0, 62882.0, 62883.0, 62884.0, 62885.0, 62886.0],       [62887.0, 62888.0, 62889.0, 62890.0, 62891.0, 62892.0],       [62893.0, 62894.0, 62895.0, 62896.0, 62897.0, 62898.0],       [62899.0, 62900.0, 62901.0, 62902.0, 62903.0, 62904.0],       [62905.0, 62906.0, 62907.0, 62908.0, 62909.0, 62910.0],       [62911.0, 62912.0, 62913.0, 62914.0, 62915.0, 62916.0]],      [[62917.0, 62918.0, 62919.0, 62920.0, 62921.0, 62922.0],       [62923.0, 62924.0, 62925.0, 62926.0, 62927.0, 62928.0],       [62929.0, 62930.0, 62931.0, 62932.0, 62933.0, 62934.0],       [62935.0, 62936.0, 62937.0, 62938.0, 62939.0, 62940.0],       [62941.0, 62942.0, 62943.0, 62944.0, 62945.0, 62946.0],       [62947.0, 62948.0, 62949.0, 62950.0, 62951.0, 62952.0],       [62953.0, 62954.0, 62955.0, 62956.0, 62957.0, 62958.0]],      [[62959.0, 62960.0, 62961.0, 62962.0, 62963.0, 62964.0],       [62965.0, 62966.0, 62967.0, 62968.0, 62969.0, 62970.0],       [62971.0, 62972.0, 62973.0, 62974.0, 62975.0, 62976.0],       [62977.0, 62978.0, 62979.0, 62980.0, 62981.0, 62982.0],       [62983.0, 62984.0, 62985.0, 62986.0, 62987.0, 62988.0],       [62989.0, 62990.0, 62991.0, 62992.0, 62993.0, 62994.0],       [62995.0, 62996.0, 62997.0, 62998.0, 62999.0, 63000.0]]],     [[[63001.0, 63002.0, 63003.0, 63004.0, 63005.0, 63006.0],       [63007.0, 63008.0, 63009.0, 63010.0, 63011.0, 63012.0],       [63013.0, 63014.0, 63015.0, 63016.0, 63017.0, 63018.0],       [63019.0, 63020.0, 63021.0, 63022.0, 63023.0, 63024.0],       [63025.0, 63026.0, 63027.0, 63028.0, 63029.0, 63030.0],       [63031.0, 63032.0, 63033.0, 63034.0, 63035.0, 63036.0],       [63037.0, 63038.0, 63039.0, 63040.0, 63041.0, 63042.0]],      [[63043.0, 63044.0, 63045.0, 63046.0, 63047.0, 63048.0],       [63049.0, 63050.0, 63051.0, 63052.0, 63053.0, 63054.0],       [63055.0, 63056.0, 63057.0, 63058.0, 63059.0, 63060.0],       [63061.0, 63062.0, 63063.0, 63064.0, 63065.0, 63066.0],       [63067.0, 63068.0, 63069.0, 63070.0, 63071.0, 63072.0],       [63073.0, 63074.0, 63075.0, 63076.0, 63077.0, 63078.0],       [63079.0, 63080.0, 63081.0, 63082.0, 63083.0, 63084.0]],      [[63085.0, 63086.0, 63087.0, 63088.0, 63089.0, 63090.0],       [63091.0, 63092.0, 63093.0, 63094.0, 63095.0, 63096.0],       [63097.0, 63098.0, 63099.0, 63100.0, 63101.0, 63102.0],       [63103.0, 63104.0, 63105.0, 63106.0, 63107.0, 63108.0],       [63109.0, 63110.0, 63111.0, 63112.0, 63113.0, 63114.0],       [63115.0, 63116.0, 63117.0, 63118.0, 63119.0, 63120.0],       [63121.0, 63122.0, 63123.0, 63124.0, 63125.0, 63126.0]],      [[63127.0, 63128.0, 63129.0, 63130.0, 63131.0, 63132.0],       [63133.0, 63134.0, 63135.0, 63136.0, 63137.0, 63138.0],       [63139.0, 63140.0, 63141.0, 63142.0, 63143.0, 63144.0],       [63145.0, 63146.0, 63147.0, 63148.0, 63149.0, 63150.0],       [63151.0, 63152.0, 63153.0, 63154.0, 63155.0, 63156.0],       [63157.0, 63158.0, 63159.0, 63160.0, 63161.0, 63162.0],       [63163.0, 63164.0, 63165.0, 63166.0, 63167.0, 63168.0]],      [[63169.0, 63170.0, 63171.0, 63172.0, 63173.0, 63174.0],       [63175.0, 63176.0, 63177.0, 63178.0, 63179.0, 63180.0],       [63181.0, 63182.0, 63183.0, 63184.0, 63185.0, 63186.0],       [63187.0, 63188.0, 63189.0, 63190.0, 63191.0, 63192.0],       [63193.0, 63194.0, 63195.0, 63196.0, 63197.0, 63198.0],       [63199.0, 63200.0, 63201.0, 63202.0, 63203.0, 63204.0],       [63205.0, 63206.0, 63207.0, 63208.0, 63209.0, 63210.0]],      [[63211.0, 63212.0, 63213.0, 63214.0, 63215.0, 63216.0],       [63217.0, 63218.0, 63219.0, 63220.0, 63221.0, 63222.0],       [63223.0, 63224.0, 63225.0, 63226.0, 63227.0, 63228.0],       [63229.0, 63230.0, 63231.0, 63232.0, 63233.0, 63234.0],       [63235.0, 63236.0, 63237.0, 63238.0, 63239.0, 63240.0],       [63241.0, 63242.0, 63243.0, 63244.0, 63245.0, 63246.0],       [63247.0, 63248.0, 63249.0, 63250.0, 63251.0, 63252.0]]],     [[[63253.0, 63254.0, 63255.0, 63256.0, 63257.0, 63258.0],       [63259.0, 63260.0, 63261.0, 63262.0, 63263.0, 63264.0],       [63265.0, 63266.0, 63267.0, 63268.0, 63269.0, 63270.0],       [63271.0, 63272.0, 63273.0, 63274.0, 63275.0, 63276.0],       [63277.0, 63278.0, 63279.0, 63280.0, 63281.0, 63282.0],       [63283.0, 63284.0, 63285.0, 63286.0, 63287.0, 63288.0],       [63289.0, 63290.0, 63291.0, 63292.0, 63293.0, 63294.0]],      [[63295.0, 63296.0, 63297.0, 63298.0, 63299.0, 63300.0],       [63301.0, 63302.0, 63303.0, 63304.0, 63305.0, 63306.0],       [63307.0, 63308.0, 63309.0, 63310.0, 63311.0, 63312.0],       [63313.0, 63314.0, 63315.0, 63316.0, 63317.0, 63318.0],       [63319.0, 63320.0, 63321.0, 63322.0, 63323.0, 63324.0],       [63325.0, 63326.0, 63327.0, 63328.0, 63329.0, 63330.0],       [63331.0, 63332.0, 63333.0, 63334.0, 63335.0, 63336.0]],      [[63337.0, 63338.0, 63339.0, 63340.0, 63341.0, 63342.0],       [63343.0, 63344.0, 63345.0, 63346.0, 63347.0, 63348.0],       [63349.0, 63350.0, 63351.0, 63352.0, 63353.0, 63354.0],       [63355.0, 63356.0, 63357.0, 63358.0, 63359.0, 63360.0],       [63361.0, 63362.0, 63363.0, 63364.0, 63365.0, 63366.0],       [63367.0, 63368.0, 63369.0, 63370.0, 63371.0, 63372.0],       [63373.0, 63374.0, 63375.0, 63376.0, 63377.0, 63378.0]],      [[63379.0, 63380.0, 63381.0, 63382.0, 63383.0, 63384.0],       [63385.0, 63386.0, 63387.0, 63388.0, 63389.0, 63390.0],       [63391.0, 63392.0, 63393.0, 63394.0, 63395.0, 63396.0],       [63397.0, 63398.0, 63399.0, 63400.0, 63401.0, 63402.0],       [63403.0, 63404.0, 63405.0, 63406.0, 63407.0, 63408.0],       [63409.0, 63410.0, 63411.0, 63412.0, 63413.0, 63414.0],       [63415.0, 63416.0, 63417.0, 63418.0, 63419.0, 63420.0]],      [[63421.0, 63422.0, 63423.0, 63424.0, 63425.0, 63426.0],       [63427.0, 63428.0, 63429.0, 63430.0, 63431.0, 63432.0],       [63433.0, 63434.0, 63435.0, 63436.0, 63437.0, 63438.0],       [63439.0, 63440.0, 63441.0, 63442.0, 63443.0, 63444.0],       [63445.0, 63446.0, 63447.0, 63448.0, 63449.0, 63450.0],       [63451.0, 63452.0, 63453.0, 63454.0, 63455.0, 63456.0],       [63457.0, 63458.0, 63459.0, 63460.0, 63461.0, 63462.0]],      [[63463.0, 63464.0, 63465.0, 63466.0, 63467.0, 63468.0],       [63469.0, 63470.0, 63471.0, 63472.0, 63473.0, 63474.0],       [63475.0, 63476.0, 63477.0, 63478.0, 63479.0, 63480.0],       [63481.0, 63482.0, 63483.0, 63484.0, 63485.0, 63486.0],       [63487.0, 63488.0, 63489.0, 63490.0, 63491.0, 63492.0],       [63493.0, 63494.0, 63495.0, 63496.0, 63497.0, 63498.0],       [63499.0, 63500.0, 63501.0, 63502.0, 63503.0, 63504.0]]]],    [[[[63505.0, 63506.0, 63507.0, 63508.0, 63509.0, 63510.0],       [63511.0, 63512.0, 63513.0, 63514.0, 63515.0, 63516.0],       [63517.0, 63518.0, 63519.0, 63520.0, 63521.0, 63522.0],       [63523.0, 63524.0, 63525.0, 63526.0, 63527.0, 63528.0],       [63529.0, 63530.0, 63531.0, 63532.0, 63533.0, 63534.0],       [63535.0, 63536.0, 63537.0, 63538.0, 63539.0, 63540.0],       [63541.0, 63542.0, 63543.0, 63544.0, 63545.0, 63546.0]],      [[63547.0, 63548.0, 63549.0, 63550.0, 63551.0, 63552.0],       [63553.0, 63554.0, 63555.0, 63556.0, 63557.0, 63558.0],       [63559.0, 63560.0, 63561.0, 63562.0, 63563.0, 63564.0],       [63565.0, 63566.0, 63567.0, 63568.0, 63569.0, 63570.0],       [63571.0, 63572.0, 63573.0, 63574.0, 63575.0, 63576.0],       [63577.0, 63578.0, 63579.0, 63580.0, 63581.0, 63582.0],       [63583.0, 63584.0, 63585.0, 63586.0, 63587.0, 63588.0]],      [[63589.0, 63590.0, 63591.0, 63592.0, 63593.0, 63594.0],       [63595.0, 63596.0, 63597.0, 63598.0, 63599.0, 63600.0],       [63601.0, 63602.0, 63603.0, 63604.0, 63605.0, 63606.0],       [63607.0, 63608.0, 63609.0, 63610.0, 63611.0, 63612.0],       [63613.0, 63614.0, 63615.0, 63616.0, 63617.0, 63618.0],       [63619.0, 63620.0, 63621.0, 63622.0, 63623.0, 63624.0],       [63625.0, 63626.0, 63627.0, 63628.0, 63629.0, 63630.0]],      [[63631.0, 63632.0, 63633.0, 63634.0, 63635.0, 63636.0],       [63637.0, 63638.0, 63639.0, 63640.0, 63641.0, 63642.0],       [63643.0, 63644.0, 63645.0, 63646.0, 63647.0, 63648.0],       [63649.0, 63650.0, 63651.0, 63652.0, 63653.0, 63654.0],       [63655.0, 63656.0, 63657.0, 63658.0, 63659.0, 63660.0],       [63661.0, 63662.0, 63663.0, 63664.0, 63665.0, 63666.0],       [63667.0, 63668.0, 63669.0, 63670.0, 63671.0, 63672.0]],      [[63673.0, 63674.0, 63675.0, 63676.0, 63677.0, 63678.0],       [63679.0, 63680.0, 63681.0, 63682.0, 63683.0, 63684.0],       [63685.0, 63686.0, 63687.0, 63688.0, 63689.0, 63690.0],       [63691.0, 63692.0, 63693.0, 63694.0, 63695.0, 63696.0],       [63697.0, 63698.0, 63699.0, 63700.0, 63701.0, 63702.0],       [63703.0, 63704.0, 63705.0, 63706.0, 63707.0, 63708.0],       [63709.0, 63710.0, 63711.0, 63712.0, 63713.0, 63714.0]],      [[63715.0, 63716.0, 63717.0, 63718.0, 63719.0, 63720.0],       [63721.0, 63722.0, 63723.0, 63724.0, 63725.0, 63726.0],       [63727.0, 63728.0, 63729.0, 63730.0, 63731.0, 63732.0],       [63733.0, 63734.0, 63735.0, 63736.0, 63737.0, 63738.0],       [63739.0, 63740.0, 63741.0, 63742.0, 63743.0, 63744.0],       [63745.0, 63746.0, 63747.0, 63748.0, 63749.0, 63750.0],       [63751.0, 63752.0, 63753.0, 63754.0, 63755.0, 63756.0]]],     [[[63757.0, 63758.0, 63759.0, 63760.0, 63761.0, 63762.0],       [63763.0, 63764.0, 63765.0, 63766.0, 63767.0, 63768.0],       [63769.0, 63770.0, 63771.0, 63772.0, 63773.0, 63774.0],       [63775.0, 63776.0, 63777.0, 63778.0, 63779.0, 63780.0],       [63781.0, 63782.0, 63783.0, 63784.0, 63785.0, 63786.0],       [63787.0, 63788.0, 63789.0, 63790.0, 63791.0, 63792.0],       [63793.0, 63794.0, 63795.0, 63796.0, 63797.0, 63798.0]],      [[63799.0, 63800.0, 63801.0, 63802.0, 63803.0, 63804.0],       [63805.0, 63806.0, 63807.0, 63808.0, 63809.0, 63810.0],       [63811.0, 63812.0, 63813.0, 63814.0, 63815.0, 63816.0],       [63817.0, 63818.0, 63819.0, 63820.0, 63821.0, 63822.0],       [63823.0, 63824.0, 63825.0, 63826.0, 63827.0, 63828.0],       [63829.0, 63830.0, 63831.0, 63832.0, 63833.0, 63834.0],       [63835.0, 63836.0, 63837.0, 63838.0, 63839.0, 63840.0]],      [[63841.0, 63842.0, 63843.0, 63844.0, 63845.0, 63846.0],       [63847.0, 63848.0, 63849.0, 63850.0, 63851.0, 63852.0],       [63853.0, 63854.0, 63855.0, 63856.0, 63857.0, 63858.0],       [63859.0, 63860.0, 63861.0, 63862.0, 63863.0, 63864.0],       [63865.0, 63866.0, 63867.0, 63868.0, 63869.0, 63870.0],       [63871.0, 63872.0, 63873.0, 63874.0, 63875.0, 63876.0],       [63877.0, 63878.0, 63879.0, 63880.0, 63881.0, 63882.0]],      [[63883.0, 63884.0, 63885.0, 63886.0, 63887.0, 63888.0],       [63889.0, 63890.0, 63891.0, 63892.0, 63893.0, 63894.0],       [63895.0, 63896.0, 63897.0, 63898.0, 63899.0, 63900.0],       [63901.0, 63902.0, 63903.0, 63904.0, 63905.0, 63906.0],       [63907.0, 63908.0, 63909.0, 63910.0, 63911.0, 63912.0],       [63913.0, 63914.0, 63915.0, 63916.0, 63917.0, 63918.0],       [63919.0, 63920.0, 63921.0, 63922.0, 63923.0, 63924.0]],      [[63925.0, 63926.0, 63927.0, 63928.0, 63929.0, 63930.0],       [63931.0, 63932.0, 63933.0, 63934.0, 63935.0, 63936.0],       [63937.0, 63938.0, 63939.0, 63940.0, 63941.0, 63942.0],       [63943.0, 63944.0, 63945.0, 63946.0, 63947.0, 63948.0],       [63949.0, 63950.0, 63951.0, 63952.0, 63953.0, 63954.0],       [63955.0, 63956.0, 63957.0, 63958.0, 63959.0, 63960.0],       [63961.0, 63962.0, 63963.0, 63964.0, 63965.0, 63966.0]],      [[63967.0, 63968.0, 63969.0, 63970.0, 63971.0, 63972.0],       [63973.0, 63974.0, 63975.0, 63976.0, 63977.0, 63978.0],       [63979.0, 63980.0, 63981.0, 63982.0, 63983.0, 63984.0],       [63985.0, 63986.0, 63987.0, 63988.0, 63989.0, 63990.0],       [63991.0, 63992.0, 63993.0, 63994.0, 63995.0, 63996.0],       [63997.0, 63998.0, 63999.0, 64000.0, 64001.0, 64002.0],       [64003.0, 64004.0, 64005.0, 64006.0, 64007.0, 64008.0]]],     [[[64009.0, 64010.0, 64011.0, 64012.0, 64013.0, 64014.0],       [64015.0, 64016.0, 64017.0, 64018.0, 64019.0, 64020.0],       [64021.0, 64022.0, 64023.0, 64024.0, 64025.0, 64026.0],       [64027.0, 64028.0, 64029.0, 64030.0, 64031.0, 64032.0],       [64033.0, 64034.0, 64035.0, 64036.0, 64037.0, 64038.0],       [64039.0, 64040.0, 64041.0, 64042.0, 64043.0, 64044.0],       [64045.0, 64046.0, 64047.0, 64048.0, 64049.0, 64050.0]],      [[64051.0, 64052.0, 64053.0, 64054.0, 64055.0, 64056.0],       [64057.0, 64058.0, 64059.0, 64060.0, 64061.0, 64062.0],       [64063.0, 64064.0, 64065.0, 64066.0, 64067.0, 64068.0],       [64069.0, 64070.0, 64071.0, 64072.0, 64073.0, 64074.0],       [64075.0, 64076.0, 64077.0, 64078.0, 64079.0, 64080.0],       [64081.0, 64082.0, 64083.0, 64084.0, 64085.0, 64086.0],       [64087.0, 64088.0, 64089.0, 64090.0, 64091.0, 64092.0]],      [[64093.0, 64094.0, 64095.0, 64096.0, 64097.0, 64098.0],       [64099.0, 64100.0, 64101.0, 64102.0, 64103.0, 64104.0],       [64105.0, 64106.0, 64107.0, 64108.0, 64109.0, 64110.0],       [64111.0, 64112.0, 64113.0, 64114.0, 64115.0, 64116.0],       [64117.0, 64118.0, 64119.0, 64120.0, 64121.0, 64122.0],       [64123.0, 64124.0, 64125.0, 64126.0, 64127.0, 64128.0],       [64129.0, 64130.0, 64131.0, 64132.0, 64133.0, 64134.0]],      [[64135.0, 64136.0, 64137.0, 64138.0, 64139.0, 64140.0],       [64141.0, 64142.0, 64143.0, 64144.0, 64145.0, 64146.0],       [64147.0, 64148.0, 64149.0, 64150.0, 64151.0, 64152.0],       [64153.0, 64154.0, 64155.0, 64156.0, 64157.0, 64158.0],       [64159.0, 64160.0, 64161.0, 64162.0, 64163.0, 64164.0],       [64165.0, 64166.0, 64167.0, 64168.0, 64169.0, 64170.0],       [64171.0, 64172.0, 64173.0, 64174.0, 64175.0, 64176.0]],      [[64177.0, 64178.0, 64179.0, 64180.0, 64181.0, 64182.0],       [64183.0, 64184.0, 64185.0, 64186.0, 64187.0, 64188.0],       [64189.0, 64190.0, 64191.0, 64192.0, 64193.0, 64194.0],       [64195.0, 64196.0, 64197.0, 64198.0, 64199.0, 64200.0],       [64201.0, 64202.0, 64203.0, 64204.0, 64205.0, 64206.0],       [64207.0, 64208.0, 64209.0, 64210.0, 64211.0, 64212.0],       [64213.0, 64214.0, 64215.0, 64216.0, 64217.0, 64218.0]],      [[64219.0, 64220.0, 64221.0, 64222.0, 64223.0, 64224.0],       [64225.0, 64226.0, 64227.0, 64228.0, 64229.0, 64230.0],       [64231.0, 64232.0, 64233.0, 64234.0, 64235.0, 64236.0],       [64237.0, 64238.0, 64239.0, 64240.0, 64241.0, 64242.0],       [64243.0, 64244.0, 64245.0, 64246.0, 64247.0, 64248.0],       [64249.0, 64250.0, 64251.0, 64252.0, 64253.0, 64254.0],       [64255.0, 64256.0, 64257.0, 64258.0, 64259.0, 64260.0]]],     [[[64261.0, 64262.0, 64263.0, 64264.0, 64265.0, 64266.0],       [64267.0, 64268.0, 64269.0, 64270.0, 64271.0, 64272.0],       [64273.0, 64274.0, 64275.0, 64276.0, 64277.0, 64278.0],       [64279.0, 64280.0, 64281.0, 64282.0, 64283.0, 64284.0],       [64285.0, 64286.0, 64287.0, 64288.0, 64289.0, 64290.0],       [64291.0, 64292.0, 64293.0, 64294.0, 64295.0, 64296.0],       [64297.0, 64298.0, 64299.0, 64300.0, 64301.0, 64302.0]],      [[64303.0, 64304.0, 64305.0, 64306.0, 64307.0, 64308.0],       [64309.0, 64310.0, 64311.0, 64312.0, 64313.0, 64314.0],       [64315.0, 64316.0, 64317.0, 64318.0, 64319.0, 64320.0],       [64321.0, 64322.0, 64323.0, 64324.0, 64325.0, 64326.0],       [64327.0, 64328.0, 64329.0, 64330.0, 64331.0, 64332.0],       [64333.0, 64334.0, 64335.0, 64336.0, 64337.0, 64338.0],       [64339.0, 64340.0, 64341.0, 64342.0, 64343.0, 64344.0]],      [[64345.0, 64346.0, 64347.0, 64348.0, 64349.0, 64350.0],       [64351.0, 64352.0, 64353.0, 64354.0, 64355.0, 64356.0],       [64357.0, 64358.0, 64359.0, 64360.0, 64361.0, 64362.0],       [64363.0, 64364.0, 64365.0, 64366.0, 64367.0, 64368.0],       [64369.0, 64370.0, 64371.0, 64372.0, 64373.0, 64374.0],       [64375.0, 64376.0, 64377.0, 64378.0, 64379.0, 64380.0],       [64381.0, 64382.0, 64383.0, 64384.0, 64385.0, 64386.0]],      [[64387.0, 64388.0, 64389.0, 64390.0, 64391.0, 64392.0],       [64393.0, 64394.0, 64395.0, 64396.0, 64397.0, 64398.0],       [64399.0, 64400.0, 64401.0, 64402.0, 64403.0, 64404.0],       [64405.0, 64406.0, 64407.0, 64408.0, 64409.0, 64410.0],       [64411.0, 64412.0, 64413.0, 64414.0, 64415.0, 64416.0],       [64417.0, 64418.0, 64419.0, 64420.0, 64421.0, 64422.0],       [64423.0, 64424.0, 64425.0, 64426.0, 64427.0, 64428.0]],      [[64429.0, 64430.0, 64431.0, 64432.0, 64433.0, 64434.0],       [64435.0, 64436.0, 64437.0, 64438.0, 64439.0, 64440.0],       [64441.0, 64442.0, 64443.0, 64444.0, 64445.0, 64446.0],       [64447.0, 64448.0, 64449.0, 64450.0, 64451.0, 64452.0],       [64453.0, 64454.0, 64455.0, 64456.0, 64457.0, 64458.0],       [64459.0, 64460.0, 64461.0, 64462.0, 64463.0, 64464.0],       [64465.0, 64466.0, 64467.0, 64468.0, 64469.0, 64470.0]],      [[64471.0, 64472.0, 64473.0, 64474.0, 64475.0, 64476.0],       [64477.0, 64478.0, 64479.0, 64480.0, 64481.0, 64482.0],       [64483.0, 64484.0, 64485.0, 64486.0, 64487.0, 64488.0],       [64489.0, 64490.0, 64491.0, 64492.0, 64493.0, 64494.0],       [64495.0, 64496.0, 64497.0, 64498.0, 64499.0, 64500.0],       [64501.0, 64502.0, 64503.0, 64504.0, 64505.0, 64506.0],       [64507.0, 64508.0, 64509.0, 64510.0, 64511.0, 64512.0]]]],    [[[[64513.0, 64514.0, 64515.0, 64516.0, 64517.0, 64518.0],       [64519.0, 64520.0, 64521.0, 64522.0, 64523.0, 64524.0],       [64525.0, 64526.0, 64527.0, 64528.0, 64529.0, 64530.0],       [64531.0, 64532.0, 64533.0, 64534.0, 64535.0, 64536.0],       [64537.0, 64538.0, 64539.0, 64540.0, 64541.0, 64542.0],       [64543.0, 64544.0, 64545.0, 64546.0, 64547.0, 64548.0],       [64549.0, 64550.0, 64551.0, 64552.0, 64553.0, 64554.0]],      [[64555.0, 64556.0, 64557.0, 64558.0, 64559.0, 64560.0],       [64561.0, 64562.0, 64563.0, 64564.0, 64565.0, 64566.0],       [64567.0, 64568.0, 64569.0, 64570.0, 64571.0, 64572.0],       [64573.0, 64574.0, 64575.0, 64576.0, 64577.0, 64578.0],       [64579.0, 64580.0, 64581.0, 64582.0, 64583.0, 64584.0],       [64585.0, 64586.0, 64587.0, 64588.0, 64589.0, 64590.0],       [64591.0, 64592.0, 64593.0, 64594.0, 64595.0, 64596.0]],      [[64597.0, 64598.0, 64599.0, 64600.0, 64601.0, 64602.0],       [64603.0, 64604.0, 64605.0, 64606.0, 64607.0, 64608.0],       [64609.0, 64610.0, 64611.0, 64612.0, 64613.0, 64614.0],       [64615.0, 64616.0, 64617.0, 64618.0, 64619.0, 64620.0],       [64621.0, 64622.0, 64623.0, 64624.0, 64625.0, 64626.0],       [64627.0, 64628.0, 64629.0, 64630.0, 64631.0, 64632.0],       [64633.0, 64634.0, 64635.0, 64636.0, 64637.0, 64638.0]],      [[64639.0, 64640.0, 64641.0, 64642.0, 64643.0, 64644.0],       [64645.0, 64646.0, 64647.0, 64648.0, 64649.0, 64650.0],       [64651.0, 64652.0, 64653.0, 64654.0, 64655.0, 64656.0],       [64657.0, 64658.0, 64659.0, 64660.0, 64661.0, 64662.0],       [64663.0, 64664.0, 64665.0, 64666.0, 64667.0, 64668.0],       [64669.0, 64670.0, 64671.0, 64672.0, 64673.0, 64674.0],       [64675.0, 64676.0, 64677.0, 64678.0, 64679.0, 64680.0]],      [[64681.0, 64682.0, 64683.0, 64684.0, 64685.0, 64686.0],       [64687.0, 64688.0, 64689.0, 64690.0, 64691.0, 64692.0],       [64693.0, 64694.0, 64695.0, 64696.0, 64697.0, 64698.0],       [64699.0, 64700.0, 64701.0, 64702.0, 64703.0, 64704.0],       [64705.0, 64706.0, 64707.0, 64708.0, 64709.0, 64710.0],       [64711.0, 64712.0, 64713.0, 64714.0, 64715.0, 64716.0],       [64717.0, 64718.0, 64719.0, 64720.0, 64721.0, 64722.0]],      [[64723.0, 64724.0, 64725.0, 64726.0, 64727.0, 64728.0],       [64729.0, 64730.0, 64731.0, 64732.0, 64733.0, 64734.0],       [64735.0, 64736.0, 64737.0, 64738.0, 64739.0, 64740.0],       [64741.0, 64742.0, 64743.0, 64744.0, 64745.0, 64746.0],       [64747.0, 64748.0, 64749.0, 64750.0, 64751.0, 64752.0],       [64753.0, 64754.0, 64755.0, 64756.0, 64757.0, 64758.0],       [64759.0, 64760.0, 64761.0, 64762.0, 64763.0, 64764.0]]],     [[[64765.0, 64766.0, 64767.0, 64768.0, 64769.0, 64770.0],       [64771.0, 64772.0, 64773.0, 64774.0, 64775.0, 64776.0],       [64777.0, 64778.0, 64779.0, 64780.0, 64781.0, 64782.0],       [64783.0, 64784.0, 64785.0, 64786.0, 64787.0, 64788.0],       [64789.0, 64790.0, 64791.0, 64792.0, 64793.0, 64794.0],       [64795.0, 64796.0, 64797.0, 64798.0, 64799.0, 64800.0],       [64801.0, 64802.0, 64803.0, 64804.0, 64805.0, 64806.0]],      [[64807.0, 64808.0, 64809.0, 64810.0, 64811.0, 64812.0],       [64813.0, 64814.0, 64815.0, 64816.0, 64817.0, 64818.0],       [64819.0, 64820.0, 64821.0, 64822.0, 64823.0, 64824.0],       [64825.0, 64826.0, 64827.0, 64828.0, 64829.0, 64830.0],       [64831.0, 64832.0, 64833.0, 64834.0, 64835.0, 64836.0],       [64837.0, 64838.0, 64839.0, 64840.0, 64841.0, 64842.0],       [64843.0, 64844.0, 64845.0, 64846.0, 64847.0, 64848.0]],      [[64849.0, 64850.0, 64851.0, 64852.0, 64853.0, 64854.0],       [64855.0, 64856.0, 64857.0, 64858.0, 64859.0, 64860.0],       [64861.0, 64862.0, 64863.0, 64864.0, 64865.0, 64866.0],       [64867.0, 64868.0, 64869.0, 64870.0, 64871.0, 64872.0],       [64873.0, 64874.0, 64875.0, 64876.0, 64877.0, 64878.0],       [64879.0, 64880.0, 64881.0, 64882.0, 64883.0, 64884.0],       [64885.0, 64886.0, 64887.0, 64888.0, 64889.0, 64890.0]],      [[64891.0, 64892.0, 64893.0, 64894.0, 64895.0, 64896.0],       [64897.0, 64898.0, 64899.0, 64900.0, 64901.0, 64902.0],       [64903.0, 64904.0, 64905.0, 64906.0, 64907.0, 64908.0],       [64909.0, 64910.0, 64911.0, 64912.0, 64913.0, 64914.0],       [64915.0, 64916.0, 64917.0, 64918.0, 64919.0, 64920.0],       [64921.0, 64922.0, 64923.0, 64924.0, 64925.0, 64926.0],       [64927.0, 64928.0, 64929.0, 64930.0, 64931.0, 64932.0]],      [[64933.0, 64934.0, 64935.0, 64936.0, 64937.0, 64938.0],       [64939.0, 64940.0, 64941.0, 64942.0, 64943.0, 64944.0],       [64945.0, 64946.0, 64947.0, 64948.0, 64949.0, 64950.0],       [64951.0, 64952.0, 64953.0, 64954.0, 64955.0, 64956.0],       [64957.0, 64958.0, 64959.0, 64960.0, 64961.0, 64962.0],       [64963.0, 64964.0, 64965.0, 64966.0, 64967.0, 64968.0],       [64969.0, 64970.0, 64971.0, 64972.0, 64973.0, 64974.0]],      [[64975.0, 64976.0, 64977.0, 64978.0, 64979.0, 64980.0],       [64981.0, 64982.0, 64983.0, 64984.0, 64985.0, 64986.0],       [64987.0, 64988.0, 64989.0, 64990.0, 64991.0, 64992.0],       [64993.0, 64994.0, 64995.0, 64996.0, 64997.0, 64998.0],       [64999.0, 65000.0, 65001.0, 65002.0, 65003.0, 65004.0],       [65005.0, 65006.0, 65007.0, 65008.0, 65009.0, 65010.0],       [65011.0, 65012.0, 65013.0, 65014.0, 65015.0, 65016.0]]],     [[[65017.0, 65018.0, 65019.0, 65020.0, 65021.0, 65022.0],       [65023.0, 65024.0, 65025.0, 65026.0, 65027.0, 65028.0],       [65029.0, 65030.0, 65031.0, 65032.0, 65033.0, 65034.0],       [65035.0, 65036.0, 65037.0, 65038.0, 65039.0, 65040.0],       [65041.0, 65042.0, 65043.0, 65044.0, 65045.0, 65046.0],       [65047.0, 65048.0, 65049.0, 65050.0, 65051.0, 65052.0],       [65053.0, 65054.0, 65055.0, 65056.0, 65057.0, 65058.0]],      [[65059.0, 65060.0, 65061.0, 65062.0, 65063.0, 65064.0],       [65065.0, 65066.0, 65067.0, 65068.0, 65069.0, 65070.0],       [65071.0, 65072.0, 65073.0, 65074.0, 65075.0, 65076.0],       [65077.0, 65078.0, 65079.0, 65080.0, 65081.0, 65082.0],       [65083.0, 65084.0, 65085.0, 65086.0, 65087.0, 65088.0],       [65089.0, 65090.0, 65091.0, 65092.0, 65093.0, 65094.0],       [65095.0, 65096.0, 65097.0, 65098.0, 65099.0, 65100.0]],      [[65101.0, 65102.0, 65103.0, 65104.0, 65105.0, 65106.0],       [65107.0, 65108.0, 65109.0, 65110.0, 65111.0, 65112.0],       [65113.0, 65114.0, 65115.0, 65116.0, 65117.0, 65118.0],       [65119.0, 65120.0, 65121.0, 65122.0, 65123.0, 65124.0],       [65125.0, 65126.0, 65127.0, 65128.0, 65129.0, 65130.0],       [65131.0, 65132.0, 65133.0, 65134.0, 65135.0, 65136.0],       [65137.0, 65138.0, 65139.0, 65140.0, 65141.0, 65142.0]],      [[65143.0, 65144.0, 65145.0, 65146.0, 65147.0, 65148.0],       [65149.0, 65150.0, 65151.0, 65152.0, 65153.0, 65154.0],       [65155.0, 65156.0, 65157.0, 65158.0, 65159.0, 65160.0],       [65161.0, 65162.0, 65163.0, 65164.0, 65165.0, 65166.0],       [65167.0, 65168.0, 65169.0, 65170.0, 65171.0, 65172.0],       [65173.0, 65174.0, 65175.0, 65176.0, 65177.0, 65178.0],       [65179.0, 65180.0, 65181.0, 65182.0, 65183.0, 65184.0]],      [[65185.0, 65186.0, 65187.0, 65188.0, 65189.0, 65190.0],       [65191.0, 65192.0, 65193.0, 65194.0, 65195.0, 65196.0],       [65197.0, 65198.0, 65199.0, 65200.0, 65201.0, 65202.0],       [65203.0, 65204.0, 65205.0, 65206.0, 65207.0, 65208.0],       [65209.0, 65210.0, 65211.0, 65212.0, 65213.0, 65214.0],       [65215.0, 65216.0, 65217.0, 65218.0, 65219.0, 65220.0],       [65221.0, 65222.0, 65223.0, 65224.0, 65225.0, 65226.0]],      [[65227.0, 65228.0, 65229.0, 65230.0, 65231.0, 65232.0],       [65233.0, 65234.0, 65235.0, 65236.0, 65237.0, 65238.0],       [65239.0, 65240.0, 65241.0, 65242.0, 65243.0, 65244.0],       [65245.0, 65246.0, 65247.0, 65248.0, 65249.0, 65250.0],       [65251.0, 65252.0, 65253.0, 65254.0, 65255.0, 65256.0],       [65257.0, 65258.0, 65259.0, 65260.0, 65261.0, 65262.0],       [65263.0, 65264.0, 65265.0, 65266.0, 65267.0, 65268.0]]],     [[[65269.0, 65270.0, 65271.0, 65272.0, 65273.0, 65274.0],       [65275.0, 65276.0, 65277.0, 65278.0, 65279.0, 65280.0],       [65281.0, 65282.0, 65283.0, 65284.0, 65285.0, 65286.0],       [65287.0, 65288.0, 65289.0, 65290.0, 65291.0, 65292.0],       [65293.0, 65294.0, 65295.0, 65296.0, 65297.0, 65298.0],       [65299.0, 65300.0, 65301.0, 65302.0, 65303.0, 65304.0],       [65305.0, 65306.0, 65307.0, 65308.0, 65309.0, 65310.0]],      [[65311.0, 65312.0, 65313.0, 65314.0, 65315.0, 65316.0],       [65317.0, 65318.0, 65319.0, 65320.0, 65321.0, 65322.0],       [65323.0, 65324.0, 65325.0, 65326.0, 65327.0, 65328.0],       [65329.0, 65330.0, 65331.0, 65332.0, 65333.0, 65334.0],       [65335.0, 65336.0, 65337.0, 65338.0, 65339.0, 65340.0],       [65341.0, 65342.0, 65343.0, 65344.0, 65345.0, 65346.0],       [65347.0, 65348.0, 65349.0, 65350.0, 65351.0, 65352.0]],      [[65353.0, 65354.0, 65355.0, 65356.0, 65357.0, 65358.0],       [65359.0, 65360.0, 65361.0, 65362.0, 65363.0, 65364.0],       [65365.0, 65366.0, 65367.0, 65368.0, 65369.0, 65370.0],       [65371.0, 65372.0, 65373.0, 65374.0, 65375.0, 65376.0],       [65377.0, 65378.0, 65379.0, 65380.0, 65381.0, 65382.0],       [65383.0, 65384.0, 65385.0, 65386.0, 65387.0, 65388.0],       [65389.0, 65390.0, 65391.0, 65392.0, 65393.0, 65394.0]],      [[65395.0, 65396.0, 65397.0, 65398.0, 65399.0, 65400.0],       [65401.0, 65402.0, 65403.0, 65404.0, 65405.0, 65406.0],       [65407.0, 65408.0, 65409.0, 65410.0, 65411.0, 65412.0],       [65413.0, 65414.0, 65415.0, 65416.0, 65417.0, 65418.0],       [65419.0, 65420.0, 65421.0, 65422.0, 65423.0, 65424.0],       [65425.0, 65426.0, 65427.0, 65428.0, 65429.0, 65430.0],       [65431.0, 65432.0, 65433.0, 65434.0, 65435.0, 65436.0]],      [[65437.0, 65438.0, 65439.0, 65440.0, 65441.0, 65442.0],       [65443.0, 65444.0, 65445.0, 65446.0, 65447.0, 65448.0],       [65449.0, 65450.0, 65451.0, 65452.0, 65453.0, 65454.0],       [65455.0, 65456.0, 65457.0, 65458.0, 65459.0, 65460.0],       [65461.0, 65462.0, 65463.0, 65464.0, 65465.0, 65466.0],       [65467.0, 65468.0, 65469.0, 65470.0, 65471.0, 65472.0],       [65473.0, 65474.0, 65475.0, 65476.0, 65477.0, 65478.0]],      [[65479.0, 65480.0, 65481.0, 65482.0, 65483.0, 65484.0],       [65485.0, 65486.0, 65487.0, 65488.0, 65489.0, 65490.0],       [65491.0, 65492.0, 65493.0, 65494.0, 65495.0, 65496.0],       [65497.0, 65498.0, 65499.0, 65500.0, 65501.0, 65502.0],       [65503.0, 65504.0, 65505.0, 65506.0, 65507.0, 65508.0],       [65509.0, 65510.0, 65511.0, 65512.0, 65513.0, 65514.0],       [65515.0, 65516.0, 65517.0, 65518.0, 65519.0, 65520.0]]]],    [[[[65521.0, 65522.0, 65523.0, 65524.0, 65525.0, 65526.0],       [65527.0, 65528.0, 65529.0, 65530.0, 65531.0, 65532.0],       [65533.0, 65534.0, 65535.0, 65536.0, 65537.0, 65538.0],       [65539.0, 65540.0, 65541.0, 65542.0, 65543.0, 65544.0],       [65545.0, 65546.0, 65547.0, 65548.0, 65549.0, 65550.0],       [65551.0, 65552.0, 65553.0, 65554.0, 65555.0, 65556.0],       [65557.0, 65558.0, 65559.0, 65560.0, 65561.0, 65562.0]],      [[65563.0, 65564.0, 65565.0, 65566.0, 65567.0, 65568.0],       [65569.0, 65570.0, 65571.0, 65572.0, 65573.0, 65574.0],       [65575.0, 65576.0, 65577.0, 65578.0, 65579.0, 65580.0],       [65581.0, 65582.0, 65583.0, 65584.0, 65585.0, 65586.0],       [65587.0, 65588.0, 65589.0, 65590.0, 65591.0, 65592.0],       [65593.0, 65594.0, 65595.0, 65596.0, 65597.0, 65598.0],       [65599.0, 65600.0, 65601.0, 65602.0, 65603.0, 65604.0]],      [[65605.0, 65606.0, 65607.0, 65608.0, 65609.0, 65610.0],       [65611.0, 65612.0, 65613.0, 65614.0, 65615.0, 65616.0],       [65617.0, 65618.0, 65619.0, 65620.0, 65621.0, 65622.0],       [65623.0, 65624.0, 65625.0, 65626.0, 65627.0, 65628.0],       [65629.0, 65630.0, 65631.0, 65632.0, 65633.0, 65634.0],       [65635.0, 65636.0, 65637.0, 65638.0, 65639.0, 65640.0],       [65641.0, 65642.0, 65643.0, 65644.0, 65645.0, 65646.0]],      [[65647.0, 65648.0, 65649.0, 65650.0, 65651.0, 65652.0],       [65653.0, 65654.0, 65655.0, 65656.0, 65657.0, 65658.0],       [65659.0, 65660.0, 65661.0, 65662.0, 65663.0, 65664.0],       [65665.0, 65666.0, 65667.0, 65668.0, 65669.0, 65670.0],       [65671.0, 65672.0, 65673.0, 65674.0, 65675.0, 65676.0],       [65677.0, 65678.0, 65679.0, 65680.0, 65681.0, 65682.0],       [65683.0, 65684.0, 65685.0, 65686.0, 65687.0, 65688.0]],      [[65689.0, 65690.0, 65691.0, 65692.0, 65693.0, 65694.0],       [65695.0, 65696.0, 65697.0, 65698.0, 65699.0, 65700.0],       [65701.0, 65702.0, 65703.0, 65704.0, 65705.0, 65706.0],       [65707.0, 65708.0, 65709.0, 65710.0, 65711.0, 65712.0],       [65713.0, 65714.0, 65715.0, 65716.0, 65717.0, 65718.0],       [65719.0, 65720.0, 65721.0, 65722.0, 65723.0, 65724.0],       [65725.0, 65726.0, 65727.0, 65728.0, 65729.0, 65730.0]],      [[65731.0, 65732.0, 65733.0, 65734.0, 65735.0, 65736.0],       [65737.0, 65738.0, 65739.0, 65740.0, 65741.0, 65742.0],       [65743.0, 65744.0, 65745.0, 65746.0, 65747.0, 65748.0],       [65749.0, 65750.0, 65751.0, 65752.0, 65753.0, 65754.0],       [65755.0, 65756.0, 65757.0, 65758.0, 65759.0, 65760.0],       [65761.0, 65762.0, 65763.0, 65764.0, 65765.0, 65766.0],       [65767.0, 65768.0, 65769.0, 65770.0, 65771.0, 65772.0]]],     [[[65773.0, 65774.0, 65775.0, 65776.0, 65777.0, 65778.0],       [65779.0, 65780.0, 65781.0, 65782.0, 65783.0, 65784.0],       [65785.0, 65786.0, 65787.0, 65788.0, 65789.0, 65790.0],       [65791.0, 65792.0, 65793.0, 65794.0, 65795.0, 65796.0],       [65797.0, 65798.0, 65799.0, 65800.0, 65801.0, 65802.0],       [65803.0, 65804.0, 65805.0, 65806.0, 65807.0, 65808.0],       [65809.0, 65810.0, 65811.0, 65812.0, 65813.0, 65814.0]],      [[65815.0, 65816.0, 65817.0, 65818.0, 65819.0, 65820.0],       [65821.0, 65822.0, 65823.0, 65824.0, 65825.0, 65826.0],       [65827.0, 65828.0, 65829.0, 65830.0, 65831.0, 65832.0],       [65833.0, 65834.0, 65835.0, 65836.0, 65837.0, 65838.0],       [65839.0, 65840.0, 65841.0, 65842.0, 65843.0, 65844.0],       [65845.0, 65846.0, 65847.0, 65848.0, 65849.0, 65850.0],       [65851.0, 65852.0, 65853.0, 65854.0, 65855.0, 65856.0]],      [[65857.0, 65858.0, 65859.0, 65860.0, 65861.0, 65862.0],       [65863.0, 65864.0, 65865.0, 65866.0, 65867.0, 65868.0],       [65869.0, 65870.0, 65871.0, 65872.0, 65873.0, 65874.0],       [65875.0, 65876.0, 65877.0, 65878.0, 65879.0, 65880.0],       [65881.0, 65882.0, 65883.0, 65884.0, 65885.0, 65886.0],       [65887.0, 65888.0, 65889.0, 65890.0, 65891.0, 65892.0],       [65893.0, 65894.0, 65895.0, 65896.0, 65897.0, 65898.0]],      [[65899.0, 65900.0, 65901.0, 65902.0, 65903.0, 65904.0],       [65905.0, 65906.0, 65907.0, 65908.0, 65909.0, 65910.0],       [65911.0, 65912.0, 65913.0, 65914.0, 65915.0, 65916.0],       [65917.0, 65918.0, 65919.0, 65920.0, 65921.0, 65922.0],       [65923.0, 65924.0, 65925.0, 65926.0, 65927.0, 65928.0],       [65929.0, 65930.0, 65931.0, 65932.0, 65933.0, 65934.0],       [65935.0, 65936.0, 65937.0, 65938.0, 65939.0, 65940.0]],      [[65941.0, 65942.0, 65943.0, 65944.0, 65945.0, 65946.0],       [65947.0, 65948.0, 65949.0, 65950.0, 65951.0, 65952.0],       [65953.0, 65954.0, 65955.0, 65956.0, 65957.0, 65958.0],       [65959.0, 65960.0, 65961.0, 65962.0, 65963.0, 65964.0],       [65965.0, 65966.0, 65967.0, 65968.0, 65969.0, 65970.0],       [65971.0, 65972.0, 65973.0, 65974.0, 65975.0, 65976.0],       [65977.0, 65978.0, 65979.0, 65980.0, 65981.0, 65982.0]],      [[65983.0, 65984.0, 65985.0, 65986.0, 65987.0, 65988.0],       [65989.0, 65990.0, 65991.0, 65992.0, 65993.0, 65994.0],       [65995.0, 65996.0, 65997.0, 65998.0, 65999.0, 66000.0],       [66001.0, 66002.0, 66003.0, 66004.0, 66005.0, 66006.0],       [66007.0, 66008.0, 66009.0, 66010.0, 66011.0, 66012.0],       [66013.0, 66014.0, 66015.0, 66016.0, 66017.0, 66018.0],       [66019.0, 66020.0, 66021.0, 66022.0, 66023.0, 66024.0]]],     [[[66025.0, 66026.0, 66027.0, 66028.0, 66029.0, 66030.0],       [66031.0, 66032.0, 66033.0, 66034.0, 66035.0, 66036.0],       [66037.0, 66038.0, 66039.0, 66040.0, 66041.0, 66042.0],       [66043.0, 66044.0, 66045.0, 66046.0, 66047.0, 66048.0],       [66049.0, 66050.0, 66051.0, 66052.0, 66053.0, 66054.0],       [66055.0, 66056.0, 66057.0, 66058.0, 66059.0, 66060.0],       [66061.0, 66062.0, 66063.0, 66064.0, 66065.0, 66066.0]],      [[66067.0, 66068.0, 66069.0, 66070.0, 66071.0, 66072.0],       [66073.0, 66074.0, 66075.0, 66076.0, 66077.0, 66078.0],       [66079.0, 66080.0, 66081.0, 66082.0, 66083.0, 66084.0],       [66085.0, 66086.0, 66087.0, 66088.0, 66089.0, 66090.0],       [66091.0, 66092.0, 66093.0, 66094.0, 66095.0, 66096.0],       [66097.0, 66098.0, 66099.0, 66100.0, 66101.0, 66102.0],       [66103.0, 66104.0, 66105.0, 66106.0, 66107.0, 66108.0]],      [[66109.0, 66110.0, 66111.0, 66112.0, 66113.0, 66114.0],       [66115.0, 66116.0, 66117.0, 66118.0, 66119.0, 66120.0],       [66121.0, 66122.0, 66123.0, 66124.0, 66125.0, 66126.0],       [66127.0, 66128.0, 66129.0, 66130.0, 66131.0, 66132.0],       [66133.0, 66134.0, 66135.0, 66136.0, 66137.0, 66138.0],       [66139.0, 66140.0, 66141.0, 66142.0, 66143.0, 66144.0],       [66145.0, 66146.0, 66147.0, 66148.0, 66149.0, 66150.0]],      [[66151.0, 66152.0, 66153.0, 66154.0, 66155.0, 66156.0],       [66157.0, 66158.0, 66159.0, 66160.0, 66161.0, 66162.0],       [66163.0, 66164.0, 66165.0, 66166.0, 66167.0, 66168.0],       [66169.0, 66170.0, 66171.0, 66172.0, 66173.0, 66174.0],       [66175.0, 66176.0, 66177.0, 66178.0, 66179.0, 66180.0],       [66181.0, 66182.0, 66183.0, 66184.0, 66185.0, 66186.0],       [66187.0, 66188.0, 66189.0, 66190.0, 66191.0, 66192.0]],      [[66193.0, 66194.0, 66195.0, 66196.0, 66197.0, 66198.0],       [66199.0, 66200.0, 66201.0, 66202.0, 66203.0, 66204.0],       [66205.0, 66206.0, 66207.0, 66208.0, 66209.0, 66210.0],       [66211.0, 66212.0, 66213.0, 66214.0, 66215.0, 66216.0],       [66217.0, 66218.0, 66219.0, 66220.0, 66221.0, 66222.0],       [66223.0, 66224.0, 66225.0, 66226.0, 66227.0, 66228.0],       [66229.0, 66230.0, 66231.0, 66232.0, 66233.0, 66234.0]],      [[66235.0, 66236.0, 66237.0, 66238.0, 66239.0, 66240.0],       [66241.0, 66242.0, 66243.0, 66244.0, 66245.0, 66246.0],       [66247.0, 66248.0, 66249.0, 66250.0, 66251.0, 66252.0],       [66253.0, 66254.0, 66255.0, 66256.0, 66257.0, 66258.0],       [66259.0, 66260.0, 66261.0, 66262.0, 66263.0, 66264.0],       [66265.0, 66266.0, 66267.0, 66268.0, 66269.0, 66270.0],       [66271.0, 66272.0, 66273.0, 66274.0, 66275.0, 66276.0]]],     [[[66277.0, 66278.0, 66279.0, 66280.0, 66281.0, 66282.0],       [66283.0, 66284.0, 66285.0, 66286.0, 66287.0, 66288.0],       [66289.0, 66290.0, 66291.0, 66292.0, 66293.0, 66294.0],       [66295.0, 66296.0, 66297.0, 66298.0, 66299.0, 66300.0],       [66301.0, 66302.0, 66303.0, 66304.0, 66305.0, 66306.0],       [66307.0, 66308.0, 66309.0, 66310.0, 66311.0, 66312.0],       [66313.0, 66314.0, 66315.0, 66316.0, 66317.0, 66318.0]],      [[66319.0, 66320.0, 66321.0, 66322.0, 66323.0, 66324.0],       [66325.0, 66326.0, 66327.0, 66328.0, 66329.0, 66330.0],       [66331.0, 66332.0, 66333.0, 66334.0, 66335.0, 66336.0],       [66337.0, 66338.0, 66339.0, 66340.0, 66341.0, 66342.0],       [66343.0, 66344.0, 66345.0, 66346.0, 66347.0, 66348.0],       [66349.0, 66350.0, 66351.0, 66352.0, 66353.0, 66354.0],       [66355.0, 66356.0, 66357.0, 66358.0, 66359.0, 66360.0]],      [[66361.0, 66362.0, 66363.0, 66364.0, 66365.0, 66366.0],       [66367.0, 66368.0, 66369.0, 66370.0, 66371.0, 66372.0],       [66373.0, 66374.0, 66375.0, 66376.0, 66377.0, 66378.0],       [66379.0, 66380.0, 66381.0, 66382.0, 66383.0, 66384.0],       [66385.0, 66386.0, 66387.0, 66388.0, 66389.0, 66390.0],       [66391.0, 66392.0, 66393.0, 66394.0, 66395.0, 66396.0],       [66397.0, 66398.0, 66399.0, 66400.0, 66401.0, 66402.0]],      [[66403.0, 66404.0, 66405.0, 66406.0, 66407.0, 66408.0],       [66409.0, 66410.0, 66411.0, 66412.0, 66413.0, 66414.0],       [66415.0, 66416.0, 66417.0, 66418.0, 66419.0, 66420.0],       [66421.0, 66422.0, 66423.0, 66424.0, 66425.0, 66426.0],       [66427.0, 66428.0, 66429.0, 66430.0, 66431.0, 66432.0],       [66433.0, 66434.0, 66435.0, 66436.0, 66437.0, 66438.0],       [66439.0, 66440.0, 66441.0, 66442.0, 66443.0, 66444.0]],      [[66445.0, 66446.0, 66447.0, 66448.0, 66449.0, 66450.0],       [66451.0, 66452.0, 66453.0, 66454.0, 66455.0, 66456.0],       [66457.0, 66458.0, 66459.0, 66460.0, 66461.0, 66462.0],       [66463.0, 66464.0, 66465.0, 66466.0, 66467.0, 66468.0],       [66469.0, 66470.0, 66471.0, 66472.0, 66473.0, 66474.0],       [66475.0, 66476.0, 66477.0, 66478.0, 66479.0, 66480.0],       [66481.0, 66482.0, 66483.0, 66484.0, 66485.0, 66486.0]],      [[66487.0, 66488.0, 66489.0, 66490.0, 66491.0, 66492.0],       [66493.0, 66494.0, 66495.0, 66496.0, 66497.0, 66498.0],       [66499.0, 66500.0, 66501.0, 66502.0, 66503.0, 66504.0],       [66505.0, 66506.0, 66507.0, 66508.0, 66509.0, 66510.0],       [66511.0, 66512.0, 66513.0, 66514.0, 66515.0, 66516.0],       [66517.0, 66518.0, 66519.0, 66520.0, 66521.0, 66522.0],       [66523.0, 66524.0, 66525.0, 66526.0, 66527.0, 66528.0]]]]],   [[[[[66529.0, 66530.0, 66531.0, 66532.0, 66533.0, 66534.0],       [66535.0, 66536.0, 66537.0, 66538.0, 66539.0, 66540.0],       [66541.0, 66542.0, 66543.0, 66544.0, 66545.0, 66546.0],       [66547.0, 66548.0, 66549.0, 66550.0, 66551.0, 66552.0],       [66553.0, 66554.0, 66555.0, 66556.0, 66557.0, 66558.0],       [66559.0, 66560.0, 66561.0, 66562.0, 66563.0, 66564.0],       [66565.0, 66566.0, 66567.0, 66568.0, 66569.0, 66570.0]],      [[66571.0, 66572.0, 66573.0, 66574.0, 66575.0, 66576.0],       [66577.0, 66578.0, 66579.0, 66580.0, 66581.0, 66582.0],       [66583.0, 66584.0, 66585.0, 66586.0, 66587.0, 66588.0],       [66589.0, 66590.0, 66591.0, 66592.0, 66593.0, 66594.0],       [66595.0, 66596.0, 66597.0, 66598.0, 66599.0, 66600.0],       [66601.0, 66602.0, 66603.0, 66604.0, 66605.0, 66606.0],       [66607.0, 66608.0, 66609.0, 66610.0, 66611.0, 66612.0]],      [[66613.0, 66614.0, 66615.0, 66616.0, 66617.0, 66618.0],       [66619.0, 66620.0, 66621.0, 66622.0, 66623.0, 66624.0],       [66625.0, 66626.0, 66627.0, 66628.0, 66629.0, 66630.0],       [66631.0, 66632.0, 66633.0, 66634.0, 66635.0, 66636.0],       [66637.0, 66638.0, 66639.0, 66640.0, 66641.0, 66642.0],       [66643.0, 66644.0, 66645.0, 66646.0, 66647.0, 66648.0],       [66649.0, 66650.0, 66651.0, 66652.0, 66653.0, 66654.0]],      [[66655.0, 66656.0, 66657.0, 66658.0, 66659.0, 66660.0],       [66661.0, 66662.0, 66663.0, 66664.0, 66665.0, 66666.0],       [66667.0, 66668.0, 66669.0, 66670.0, 66671.0, 66672.0],       [66673.0, 66674.0, 66675.0, 66676.0, 66677.0, 66678.0],       [66679.0, 66680.0, 66681.0, 66682.0, 66683.0, 66684.0],       [66685.0, 66686.0, 66687.0, 66688.0, 66689.0, 66690.0],       [66691.0, 66692.0, 66693.0, 66694.0, 66695.0, 66696.0]],      [[66697.0, 66698.0, 66699.0, 66700.0, 66701.0, 66702.0],       [66703.0, 66704.0, 66705.0, 66706.0, 66707.0, 66708.0],       [66709.0, 66710.0, 66711.0, 66712.0, 66713.0, 66714.0],       [66715.0, 66716.0, 66717.0, 66718.0, 66719.0, 66720.0],       [66721.0, 66722.0, 66723.0, 66724.0, 66725.0, 66726.0],       [66727.0, 66728.0, 66729.0, 66730.0, 66731.0, 66732.0],       [66733.0, 66734.0, 66735.0, 66736.0, 66737.0, 66738.0]],      [[66739.0, 66740.0, 66741.0, 66742.0, 66743.0, 66744.0],       [66745.0, 66746.0, 66747.0, 66748.0, 66749.0, 66750.0],       [66751.0, 66752.0, 66753.0, 66754.0, 66755.0, 66756.0],       [66757.0, 66758.0, 66759.0, 66760.0, 66761.0, 66762.0],       [66763.0, 66764.0, 66765.0, 66766.0, 66767.0, 66768.0],       [66769.0, 66770.0, 66771.0, 66772.0, 66773.0, 66774.0],       [66775.0, 66776.0, 66777.0, 66778.0, 66779.0, 66780.0]]],     [[[66781.0, 66782.0, 66783.0, 66784.0, 66785.0, 66786.0],       [66787.0, 66788.0, 66789.0, 66790.0, 66791.0, 66792.0],       [66793.0, 66794.0, 66795.0, 66796.0, 66797.0, 66798.0],       [66799.0, 66800.0, 66801.0, 66802.0, 66803.0, 66804.0],       [66805.0, 66806.0, 66807.0, 66808.0, 66809.0, 66810.0],       [66811.0, 66812.0, 66813.0, 66814.0, 66815.0, 66816.0],       [66817.0, 66818.0, 66819.0, 66820.0, 66821.0, 66822.0]],      [[66823.0, 66824.0, 66825.0, 66826.0, 66827.0, 66828.0],       [66829.0, 66830.0, 66831.0, 66832.0, 66833.0, 66834.0],       [66835.0, 66836.0, 66837.0, 66838.0, 66839.0, 66840.0],       [66841.0, 66842.0, 66843.0, 66844.0, 66845.0, 66846.0],       [66847.0, 66848.0, 66849.0, 66850.0, 66851.0, 66852.0],       [66853.0, 66854.0, 66855.0, 66856.0, 66857.0, 66858.0],       [66859.0, 66860.0, 66861.0, 66862.0, 66863.0, 66864.0]],      [[66865.0, 66866.0, 66867.0, 66868.0, 66869.0, 66870.0],       [66871.0, 66872.0, 66873.0, 66874.0, 66875.0, 66876.0],       [66877.0, 66878.0, 66879.0, 66880.0, 66881.0, 66882.0],       [66883.0, 66884.0, 66885.0, 66886.0, 66887.0, 66888.0],       [66889.0, 66890.0, 66891.0, 66892.0, 66893.0, 66894.0],       [66895.0, 66896.0, 66897.0, 66898.0, 66899.0, 66900.0],       [66901.0, 66902.0, 66903.0, 66904.0, 66905.0, 66906.0]],      [[66907.0, 66908.0, 66909.0, 66910.0, 66911.0, 66912.0],       [66913.0, 66914.0, 66915.0, 66916.0, 66917.0, 66918.0],       [66919.0, 66920.0, 66921.0, 66922.0, 66923.0, 66924.0],       [66925.0, 66926.0, 66927.0, 66928.0, 66929.0, 66930.0],       [66931.0, 66932.0, 66933.0, 66934.0, 66935.0, 66936.0],       [66937.0, 66938.0, 66939.0, 66940.0, 66941.0, 66942.0],       [66943.0, 66944.0, 66945.0, 66946.0, 66947.0, 66948.0]],      [[66949.0, 66950.0, 66951.0, 66952.0, 66953.0, 66954.0],       [66955.0, 66956.0, 66957.0, 66958.0, 66959.0, 66960.0],       [66961.0, 66962.0, 66963.0, 66964.0, 66965.0, 66966.0],       [66967.0, 66968.0, 66969.0, 66970.0, 66971.0, 66972.0],       [66973.0, 66974.0, 66975.0, 66976.0, 66977.0, 66978.0],       [66979.0, 66980.0, 66981.0, 66982.0, 66983.0, 66984.0],       [66985.0, 66986.0, 66987.0, 66988.0, 66989.0, 66990.0]],      [[66991.0, 66992.0, 66993.0, 66994.0, 66995.0, 66996.0],       [66997.0, 66998.0, 66999.0, 67000.0, 67001.0, 67002.0],       [67003.0, 67004.0, 67005.0, 67006.0, 67007.0, 67008.0],       [67009.0, 67010.0, 67011.0, 67012.0, 67013.0, 67014.0],       [67015.0, 67016.0, 67017.0, 67018.0, 67019.0, 67020.0],       [67021.0, 67022.0, 67023.0, 67024.0, 67025.0, 67026.0],       [67027.0, 67028.0, 67029.0, 67030.0, 67031.0, 67032.0]]],     [[[67033.0, 67034.0, 67035.0, 67036.0, 67037.0, 67038.0],       [67039.0, 67040.0, 67041.0, 67042.0, 67043.0, 67044.0],       [67045.0, 67046.0, 67047.0, 67048.0, 67049.0, 67050.0],       [67051.0, 67052.0, 67053.0, 67054.0, 67055.0, 67056.0],       [67057.0, 67058.0, 67059.0, 67060.0, 67061.0, 67062.0],       [67063.0, 67064.0, 67065.0, 67066.0, 67067.0, 67068.0],       [67069.0, 67070.0, 67071.0, 67072.0, 67073.0, 67074.0]],      [[67075.0, 67076.0, 67077.0, 67078.0, 67079.0, 67080.0],       [67081.0, 67082.0, 67083.0, 67084.0, 67085.0, 67086.0],       [67087.0, 67088.0, 67089.0, 67090.0, 67091.0, 67092.0],       [67093.0, 67094.0, 67095.0, 67096.0, 67097.0, 67098.0],       [67099.0, 67100.0, 67101.0, 67102.0, 67103.0, 67104.0],       [67105.0, 67106.0, 67107.0, 67108.0, 67109.0, 67110.0],       [67111.0, 67112.0, 67113.0, 67114.0, 67115.0, 67116.0]],      [[67117.0, 67118.0, 67119.0, 67120.0, 67121.0, 67122.0],       [67123.0, 67124.0, 67125.0, 67126.0, 67127.0, 67128.0],       [67129.0, 67130.0, 67131.0, 67132.0, 67133.0, 67134.0],       [67135.0, 67136.0, 67137.0, 67138.0, 67139.0, 67140.0],       [67141.0, 67142.0, 67143.0, 67144.0, 67145.0, 67146.0],       [67147.0, 67148.0, 67149.0, 67150.0, 67151.0, 67152.0],       [67153.0, 67154.0, 67155.0, 67156.0, 67157.0, 67158.0]],      [[67159.0, 67160.0, 67161.0, 67162.0, 67163.0, 67164.0],       [67165.0, 67166.0, 67167.0, 67168.0, 67169.0, 67170.0],       [67171.0, 67172.0, 67173.0, 67174.0, 67175.0, 67176.0],       [67177.0, 67178.0, 67179.0, 67180.0, 67181.0, 67182.0],       [67183.0, 67184.0, 67185.0, 67186.0, 67187.0, 67188.0],       [67189.0, 67190.0, 67191.0, 67192.0, 67193.0, 67194.0],       [67195.0, 67196.0, 67197.0, 67198.0, 67199.0, 67200.0]],      [[67201.0, 67202.0, 67203.0, 67204.0, 67205.0, 67206.0],       [67207.0, 67208.0, 67209.0, 67210.0, 67211.0, 67212.0],       [67213.0, 67214.0, 67215.0, 67216.0, 67217.0, 67218.0],       [67219.0, 67220.0, 67221.0, 67222.0, 67223.0, 67224.0],       [67225.0, 67226.0, 67227.0, 67228.0, 67229.0, 67230.0],       [67231.0, 67232.0, 67233.0, 67234.0, 67235.0, 67236.0],       [67237.0, 67238.0, 67239.0, 67240.0, 67241.0, 67242.0]],      [[67243.0, 67244.0, 67245.0, 67246.0, 67247.0, 67248.0],       [67249.0, 67250.0, 67251.0, 67252.0, 67253.0, 67254.0],       [67255.0, 67256.0, 67257.0, 67258.0, 67259.0, 67260.0],       [67261.0, 67262.0, 67263.0, 67264.0, 67265.0, 67266.0],       [67267.0, 67268.0, 67269.0, 67270.0, 67271.0, 67272.0],       [67273.0, 67274.0, 67275.0, 67276.0, 67277.0, 67278.0],       [67279.0, 67280.0, 67281.0, 67282.0, 67283.0, 67284.0]]],     [[[67285.0, 67286.0, 67287.0, 67288.0, 67289.0, 67290.0],       [67291.0, 67292.0, 67293.0, 67294.0, 67295.0, 67296.0],       [67297.0, 67298.0, 67299.0, 67300.0, 67301.0, 67302.0],       [67303.0, 67304.0, 67305.0, 67306.0, 67307.0, 67308.0],       [67309.0, 67310.0, 67311.0, 67312.0, 67313.0, 67314.0],       [67315.0, 67316.0, 67317.0, 67318.0, 67319.0, 67320.0],       [67321.0, 67322.0, 67323.0, 67324.0, 67325.0, 67326.0]],      [[67327.0, 67328.0, 67329.0, 67330.0, 67331.0, 67332.0],       [67333.0, 67334.0, 67335.0, 67336.0, 67337.0, 67338.0],       [67339.0, 67340.0, 67341.0, 67342.0, 67343.0, 67344.0],       [67345.0, 67346.0, 67347.0, 67348.0, 67349.0, 67350.0],       [67351.0, 67352.0, 67353.0, 67354.0, 67355.0, 67356.0],       [67357.0, 67358.0, 67359.0, 67360.0, 67361.0, 67362.0],       [67363.0, 67364.0, 67365.0, 67366.0, 67367.0, 67368.0]],      [[67369.0, 67370.0, 67371.0, 67372.0, 67373.0, 67374.0],       [67375.0, 67376.0, 67377.0, 67378.0, 67379.0, 67380.0],       [67381.0, 67382.0, 67383.0, 67384.0, 67385.0, 67386.0],       [67387.0, 67388.0, 67389.0, 67390.0, 67391.0, 67392.0],       [67393.0, 67394.0, 67395.0, 67396.0, 67397.0, 67398.0],       [67399.0, 67400.0, 67401.0, 67402.0, 67403.0, 67404.0],       [67405.0, 67406.0, 67407.0, 67408.0, 67409.0, 67410.0]],      [[67411.0, 67412.0, 67413.0, 67414.0, 67415.0, 67416.0],       [67417.0, 67418.0, 67419.0, 67420.0, 67421.0, 67422.0],       [67423.0, 67424.0, 67425.0, 67426.0, 67427.0, 67428.0],       [67429.0, 67430.0, 67431.0, 67432.0, 67433.0, 67434.0],       [67435.0, 67436.0, 67437.0, 67438.0, 67439.0, 67440.0],       [67441.0, 67442.0, 67443.0, 67444.0, 67445.0, 67446.0],       [67447.0, 67448.0, 67449.0, 67450.0, 67451.0, 67452.0]],      [[67453.0, 67454.0, 67455.0, 67456.0, 67457.0, 67458.0],       [67459.0, 67460.0, 67461.0, 67462.0, 67463.0, 67464.0],       [67465.0, 67466.0, 67467.0, 67468.0, 67469.0, 67470.0],       [67471.0, 67472.0, 67473.0, 67474.0, 67475.0, 67476.0],       [67477.0, 67478.0, 67479.0, 67480.0, 67481.0, 67482.0],       [67483.0, 67484.0, 67485.0, 67486.0, 67487.0, 67488.0],       [67489.0, 67490.0, 67491.0, 67492.0, 67493.0, 67494.0]],      [[67495.0, 67496.0, 67497.0, 67498.0, 67499.0, 67500.0],       [67501.0, 67502.0, 67503.0, 67504.0, 67505.0, 67506.0],       [67507.0, 67508.0, 67509.0, 67510.0, 67511.0, 67512.0],       [67513.0, 67514.0, 67515.0, 67516.0, 67517.0, 67518.0],       [67519.0, 67520.0, 67521.0, 67522.0, 67523.0, 67524.0],       [67525.0, 67526.0, 67527.0, 67528.0, 67529.0, 67530.0],       [67531.0, 67532.0, 67533.0, 67534.0, 67535.0, 67536.0]]]],    [[[[67537.0, 67538.0, 67539.0, 67540.0, 67541.0, 67542.0],       [67543.0, 67544.0, 67545.0, 67546.0, 67547.0, 67548.0],       [67549.0, 67550.0, 67551.0, 67552.0, 67553.0, 67554.0],       [67555.0, 67556.0, 67557.0, 67558.0, 67559.0, 67560.0],       [67561.0, 67562.0, 67563.0, 67564.0, 67565.0, 67566.0],       [67567.0, 67568.0, 67569.0, 67570.0, 67571.0, 67572.0],       [67573.0, 67574.0, 67575.0, 67576.0, 67577.0, 67578.0]],      [[67579.0, 67580.0, 67581.0, 67582.0, 67583.0, 67584.0],       [67585.0, 67586.0, 67587.0, 67588.0, 67589.0, 67590.0],       [67591.0, 67592.0, 67593.0, 67594.0, 67595.0, 67596.0],       [67597.0, 67598.0, 67599.0, 67600.0, 67601.0, 67602.0],       [67603.0, 67604.0, 67605.0, 67606.0, 67607.0, 67608.0],       [67609.0, 67610.0, 67611.0, 67612.0, 67613.0, 67614.0],       [67615.0, 67616.0, 67617.0, 67618.0, 67619.0, 67620.0]],      [[67621.0, 67622.0, 67623.0, 67624.0, 67625.0, 67626.0],       [67627.0, 67628.0, 67629.0, 67630.0, 67631.0, 67632.0],       [67633.0, 67634.0, 67635.0, 67636.0, 67637.0, 67638.0],       [67639.0, 67640.0, 67641.0, 67642.0, 67643.0, 67644.0],       [67645.0, 67646.0, 67647.0, 67648.0, 67649.0, 67650.0],       [67651.0, 67652.0, 67653.0, 67654.0, 67655.0, 67656.0],       [67657.0, 67658.0, 67659.0, 67660.0, 67661.0, 67662.0]],      [[67663.0, 67664.0, 67665.0, 67666.0, 67667.0, 67668.0],       [67669.0, 67670.0, 67671.0, 67672.0, 67673.0, 67674.0],       [67675.0, 67676.0, 67677.0, 67678.0, 67679.0, 67680.0],       [67681.0, 67682.0, 67683.0, 67684.0, 67685.0, 67686.0],       [67687.0, 67688.0, 67689.0, 67690.0, 67691.0, 67692.0],       [67693.0, 67694.0, 67695.0, 67696.0, 67697.0, 67698.0],       [67699.0, 67700.0, 67701.0, 67702.0, 67703.0, 67704.0]],      [[67705.0, 67706.0, 67707.0, 67708.0, 67709.0, 67710.0],       [67711.0, 67712.0, 67713.0, 67714.0, 67715.0, 67716.0],       [67717.0, 67718.0, 67719.0, 67720.0, 67721.0, 67722.0],       [67723.0, 67724.0, 67725.0, 67726.0, 67727.0, 67728.0],       [67729.0, 67730.0, 67731.0, 67732.0, 67733.0, 67734.0],       [67735.0, 67736.0, 67737.0, 67738.0, 67739.0, 67740.0],       [67741.0, 67742.0, 67743.0, 67744.0, 67745.0, 67746.0]],      [[67747.0, 67748.0, 67749.0, 67750.0, 67751.0, 67752.0],       [67753.0, 67754.0, 67755.0, 67756.0, 67757.0, 67758.0],       [67759.0, 67760.0, 67761.0, 67762.0, 67763.0, 67764.0],       [67765.0, 67766.0, 67767.0, 67768.0, 67769.0, 67770.0],       [67771.0, 67772.0, 67773.0, 67774.0, 67775.0, 67776.0],       [67777.0, 67778.0, 67779.0, 67780.0, 67781.0, 67782.0],       [67783.0, 67784.0, 67785.0, 67786.0, 67787.0, 67788.0]]],     [[[67789.0, 67790.0, 67791.0, 67792.0, 67793.0, 67794.0],       [67795.0, 67796.0, 67797.0, 67798.0, 67799.0, 67800.0],       [67801.0, 67802.0, 67803.0, 67804.0, 67805.0, 67806.0],       [67807.0, 67808.0, 67809.0, 67810.0, 67811.0, 67812.0],       [67813.0, 67814.0, 67815.0, 67816.0, 67817.0, 67818.0],       [67819.0, 67820.0, 67821.0, 67822.0, 67823.0, 67824.0],       [67825.0, 67826.0, 67827.0, 67828.0, 67829.0, 67830.0]],      [[67831.0, 67832.0, 67833.0, 67834.0, 67835.0, 67836.0],       [67837.0, 67838.0, 67839.0, 67840.0, 67841.0, 67842.0],       [67843.0, 67844.0, 67845.0, 67846.0, 67847.0, 67848.0],       [67849.0, 67850.0, 67851.0, 67852.0, 67853.0, 67854.0],       [67855.0, 67856.0, 67857.0, 67858.0, 67859.0, 67860.0],       [67861.0, 67862.0, 67863.0, 67864.0, 67865.0, 67866.0],       [67867.0, 67868.0, 67869.0, 67870.0, 67871.0, 67872.0]],      [[67873.0, 67874.0, 67875.0, 67876.0, 67877.0, 67878.0],       [67879.0, 67880.0, 67881.0, 67882.0, 67883.0, 67884.0],       [67885.0, 67886.0, 67887.0, 67888.0, 67889.0, 67890.0],       [67891.0, 67892.0, 67893.0, 67894.0, 67895.0, 67896.0],       [67897.0, 67898.0, 67899.0, 67900.0, 67901.0, 67902.0],       [67903.0, 67904.0, 67905.0, 67906.0, 67907.0, 67908.0],       [67909.0, 67910.0, 67911.0, 67912.0, 67913.0, 67914.0]],      [[67915.0, 67916.0, 67917.0, 67918.0, 67919.0, 67920.0],       [67921.0, 67922.0, 67923.0, 67924.0, 67925.0, 67926.0],       [67927.0, 67928.0, 67929.0, 67930.0, 67931.0, 67932.0],       [67933.0, 67934.0, 67935.0, 67936.0, 67937.0, 67938.0],       [67939.0, 67940.0, 67941.0, 67942.0, 67943.0, 67944.0],       [67945.0, 67946.0, 67947.0, 67948.0, 67949.0, 67950.0],       [67951.0, 67952.0, 67953.0, 67954.0, 67955.0, 67956.0]],      [[67957.0, 67958.0, 67959.0, 67960.0, 67961.0, 67962.0],       [67963.0, 67964.0, 67965.0, 67966.0, 67967.0, 67968.0],       [67969.0, 67970.0, 67971.0, 67972.0, 67973.0, 67974.0],       [67975.0, 67976.0, 67977.0, 67978.0, 67979.0, 67980.0],       [67981.0, 67982.0, 67983.0, 67984.0, 67985.0, 67986.0],       [67987.0, 67988.0, 67989.0, 67990.0, 67991.0, 67992.0],       [67993.0, 67994.0, 67995.0, 67996.0, 67997.0, 67998.0]],      [[67999.0, 68000.0, 68001.0, 68002.0, 68003.0, 68004.0],       [68005.0, 68006.0, 68007.0, 68008.0, 68009.0, 68010.0],       [68011.0, 68012.0, 68013.0, 68014.0, 68015.0, 68016.0],       [68017.0, 68018.0, 68019.0, 68020.0, 68021.0, 68022.0],       [68023.0, 68024.0, 68025.0, 68026.0, 68027.0, 68028.0],       [68029.0, 68030.0, 68031.0, 68032.0, 68033.0, 68034.0],       [68035.0, 68036.0, 68037.0, 68038.0, 68039.0, 68040.0]]],     [[[68041.0, 68042.0, 68043.0, 68044.0, 68045.0, 68046.0],       [68047.0, 68048.0, 68049.0, 68050.0, 68051.0, 68052.0],       [68053.0, 68054.0, 68055.0, 68056.0, 68057.0, 68058.0],       [68059.0, 68060.0, 68061.0, 68062.0, 68063.0, 68064.0],       [68065.0, 68066.0, 68067.0, 68068.0, 68069.0, 68070.0],       [68071.0, 68072.0, 68073.0, 68074.0, 68075.0, 68076.0],       [68077.0, 68078.0, 68079.0, 68080.0, 68081.0, 68082.0]],      [[68083.0, 68084.0, 68085.0, 68086.0, 68087.0, 68088.0],       [68089.0, 68090.0, 68091.0, 68092.0, 68093.0, 68094.0],       [68095.0, 68096.0, 68097.0, 68098.0, 68099.0, 68100.0],       [68101.0, 68102.0, 68103.0, 68104.0, 68105.0, 68106.0],       [68107.0, 68108.0, 68109.0, 68110.0, 68111.0, 68112.0],       [68113.0, 68114.0, 68115.0, 68116.0, 68117.0, 68118.0],       [68119.0, 68120.0, 68121.0, 68122.0, 68123.0, 68124.0]],      [[68125.0, 68126.0, 68127.0, 68128.0, 68129.0, 68130.0],       [68131.0, 68132.0, 68133.0, 68134.0, 68135.0, 68136.0],       [68137.0, 68138.0, 68139.0, 68140.0, 68141.0, 68142.0],       [68143.0, 68144.0, 68145.0, 68146.0, 68147.0, 68148.0],       [68149.0, 68150.0, 68151.0, 68152.0, 68153.0, 68154.0],       [68155.0, 68156.0, 68157.0, 68158.0, 68159.0, 68160.0],       [68161.0, 68162.0, 68163.0, 68164.0, 68165.0, 68166.0]],      [[68167.0, 68168.0, 68169.0, 68170.0, 68171.0, 68172.0],       [68173.0, 68174.0, 68175.0, 68176.0, 68177.0, 68178.0],       [68179.0, 68180.0, 68181.0, 68182.0, 68183.0, 68184.0],       [68185.0, 68186.0, 68187.0, 68188.0, 68189.0, 68190.0],       [68191.0, 68192.0, 68193.0, 68194.0, 68195.0, 68196.0],       [68197.0, 68198.0, 68199.0, 68200.0, 68201.0, 68202.0],       [68203.0, 68204.0, 68205.0, 68206.0, 68207.0, 68208.0]],      [[68209.0, 68210.0, 68211.0, 68212.0, 68213.0, 68214.0],       [68215.0, 68216.0, 68217.0, 68218.0, 68219.0, 68220.0],       [68221.0, 68222.0, 68223.0, 68224.0, 68225.0, 68226.0],       [68227.0, 68228.0, 68229.0, 68230.0, 68231.0, 68232.0],       [68233.0, 68234.0, 68235.0, 68236.0, 68237.0, 68238.0],       [68239.0, 68240.0, 68241.0, 68242.0, 68243.0, 68244.0],       [68245.0, 68246.0, 68247.0, 68248.0, 68249.0, 68250.0]],      [[68251.0, 68252.0, 68253.0, 68254.0, 68255.0, 68256.0],       [68257.0, 68258.0, 68259.0, 68260.0, 68261.0, 68262.0],       [68263.0, 68264.0, 68265.0, 68266.0, 68267.0, 68268.0],       [68269.0, 68270.0, 68271.0, 68272.0, 68273.0, 68274.0],       [68275.0, 68276.0, 68277.0, 68278.0, 68279.0, 68280.0],       [68281.0, 68282.0, 68283.0, 68284.0, 68285.0, 68286.0],       [68287.0, 68288.0, 68289.0, 68290.0, 68291.0, 68292.0]]],     [[[68293.0, 68294.0, 68295.0, 68296.0, 68297.0, 68298.0],       [68299.0, 68300.0, 68301.0, 68302.0, 68303.0, 68304.0],       [68305.0, 68306.0, 68307.0, 68308.0, 68309.0, 68310.0],       [68311.0, 68312.0, 68313.0, 68314.0, 68315.0, 68316.0],       [68317.0, 68318.0, 68319.0, 68320.0, 68321.0, 68322.0],       [68323.0, 68324.0, 68325.0, 68326.0, 68327.0, 68328.0],       [68329.0, 68330.0, 68331.0, 68332.0, 68333.0, 68334.0]],      [[68335.0, 68336.0, 68337.0, 68338.0, 68339.0, 68340.0],       [68341.0, 68342.0, 68343.0, 68344.0, 68345.0, 68346.0],       [68347.0, 68348.0, 68349.0, 68350.0, 68351.0, 68352.0],       [68353.0, 68354.0, 68355.0, 68356.0, 68357.0, 68358.0],       [68359.0, 68360.0, 68361.0, 68362.0, 68363.0, 68364.0],       [68365.0, 68366.0, 68367.0, 68368.0, 68369.0, 68370.0],       [68371.0, 68372.0, 68373.0, 68374.0, 68375.0, 68376.0]],      [[68377.0, 68378.0, 68379.0, 68380.0, 68381.0, 68382.0],       [68383.0, 68384.0, 68385.0, 68386.0, 68387.0, 68388.0],       [68389.0, 68390.0, 68391.0, 68392.0, 68393.0, 68394.0],       [68395.0, 68396.0, 68397.0, 68398.0, 68399.0, 68400.0],       [68401.0, 68402.0, 68403.0, 68404.0, 68405.0, 68406.0],       [68407.0, 68408.0, 68409.0, 68410.0, 68411.0, 68412.0],       [68413.0, 68414.0, 68415.0, 68416.0, 68417.0, 68418.0]],      [[68419.0, 68420.0, 68421.0, 68422.0, 68423.0, 68424.0],       [68425.0, 68426.0, 68427.0, 68428.0, 68429.0, 68430.0],       [68431.0, 68432.0, 68433.0, 68434.0, 68435.0, 68436.0],       [68437.0, 68438.0, 68439.0, 68440.0, 68441.0, 68442.0],       [68443.0, 68444.0, 68445.0, 68446.0, 68447.0, 68448.0],       [68449.0, 68450.0, 68451.0, 68452.0, 68453.0, 68454.0],       [68455.0, 68456.0, 68457.0, 68458.0, 68459.0, 68460.0]],      [[68461.0, 68462.0, 68463.0, 68464.0, 68465.0, 68466.0],       [68467.0, 68468.0, 68469.0, 68470.0, 68471.0, 68472.0],       [68473.0, 68474.0, 68475.0, 68476.0, 68477.0, 68478.0],       [68479.0, 68480.0, 68481.0, 68482.0, 68483.0, 68484.0],       [68485.0, 68486.0, 68487.0, 68488.0, 68489.0, 68490.0],       [68491.0, 68492.0, 68493.0, 68494.0, 68495.0, 68496.0],       [68497.0, 68498.0, 68499.0, 68500.0, 68501.0, 68502.0]],      [[68503.0, 68504.0, 68505.0, 68506.0, 68507.0, 68508.0],       [68509.0, 68510.0, 68511.0, 68512.0, 68513.0, 68514.0],       [68515.0, 68516.0, 68517.0, 68518.0, 68519.0, 68520.0],       [68521.0, 68522.0, 68523.0, 68524.0, 68525.0, 68526.0],       [68527.0, 68528.0, 68529.0, 68530.0, 68531.0, 68532.0],       [68533.0, 68534.0, 68535.0, 68536.0, 68537.0, 68538.0],       [68539.0, 68540.0, 68541.0, 68542.0, 68543.0, 68544.0]]]],    [[[[68545.0, 68546.0, 68547.0, 68548.0, 68549.0, 68550.0],       [68551.0, 68552.0, 68553.0, 68554.0, 68555.0, 68556.0],       [68557.0, 68558.0, 68559.0, 68560.0, 68561.0, 68562.0],       [68563.0, 68564.0, 68565.0, 68566.0, 68567.0, 68568.0],       [68569.0, 68570.0, 68571.0, 68572.0, 68573.0, 68574.0],       [68575.0, 68576.0, 68577.0, 68578.0, 68579.0, 68580.0],       [68581.0, 68582.0, 68583.0, 68584.0, 68585.0, 68586.0]],      [[68587.0, 68588.0, 68589.0, 68590.0, 68591.0, 68592.0],       [68593.0, 68594.0, 68595.0, 68596.0, 68597.0, 68598.0],       [68599.0, 68600.0, 68601.0, 68602.0, 68603.0, 68604.0],       [68605.0, 68606.0, 68607.0, 68608.0, 68609.0, 68610.0],       [68611.0, 68612.0, 68613.0, 68614.0, 68615.0, 68616.0],       [68617.0, 68618.0, 68619.0, 68620.0, 68621.0, 68622.0],       [68623.0, 68624.0, 68625.0, 68626.0, 68627.0, 68628.0]],      [[68629.0, 68630.0, 68631.0, 68632.0, 68633.0, 68634.0],       [68635.0, 68636.0, 68637.0, 68638.0, 68639.0, 68640.0],       [68641.0, 68642.0, 68643.0, 68644.0, 68645.0, 68646.0],       [68647.0, 68648.0, 68649.0, 68650.0, 68651.0, 68652.0],       [68653.0, 68654.0, 68655.0, 68656.0, 68657.0, 68658.0],       [68659.0, 68660.0, 68661.0, 68662.0, 68663.0, 68664.0],       [68665.0, 68666.0, 68667.0, 68668.0, 68669.0, 68670.0]],      [[68671.0, 68672.0, 68673.0, 68674.0, 68675.0, 68676.0],       [68677.0, 68678.0, 68679.0, 68680.0, 68681.0, 68682.0],       [68683.0, 68684.0, 68685.0, 68686.0, 68687.0, 68688.0],       [68689.0, 68690.0, 68691.0, 68692.0, 68693.0, 68694.0],       [68695.0, 68696.0, 68697.0, 68698.0, 68699.0, 68700.0],       [68701.0, 68702.0, 68703.0, 68704.0, 68705.0, 68706.0],       [68707.0, 68708.0, 68709.0, 68710.0, 68711.0, 68712.0]],      [[68713.0, 68714.0, 68715.0, 68716.0, 68717.0, 68718.0],       [68719.0, 68720.0, 68721.0, 68722.0, 68723.0, 68724.0],       [68725.0, 68726.0, 68727.0, 68728.0, 68729.0, 68730.0],       [68731.0, 68732.0, 68733.0, 68734.0, 68735.0, 68736.0],       [68737.0, 68738.0, 68739.0, 68740.0, 68741.0, 68742.0],       [68743.0, 68744.0, 68745.0, 68746.0, 68747.0, 68748.0],       [68749.0, 68750.0, 68751.0, 68752.0, 68753.0, 68754.0]],      [[68755.0, 68756.0, 68757.0, 68758.0, 68759.0, 68760.0],       [68761.0, 68762.0, 68763.0, 68764.0, 68765.0, 68766.0],       [68767.0, 68768.0, 68769.0, 68770.0, 68771.0, 68772.0],       [68773.0, 68774.0, 68775.0, 68776.0, 68777.0, 68778.0],       [68779.0, 68780.0, 68781.0, 68782.0, 68783.0, 68784.0],       [68785.0, 68786.0, 68787.0, 68788.0, 68789.0, 68790.0],       [68791.0, 68792.0, 68793.0, 68794.0, 68795.0, 68796.0]]],     [[[68797.0, 68798.0, 68799.0, 68800.0, 68801.0, 68802.0],       [68803.0, 68804.0, 68805.0, 68806.0, 68807.0, 68808.0],       [68809.0, 68810.0, 68811.0, 68812.0, 68813.0, 68814.0],       [68815.0, 68816.0, 68817.0, 68818.0, 68819.0, 68820.0],       [68821.0, 68822.0, 68823.0, 68824.0, 68825.0, 68826.0],       [68827.0, 68828.0, 68829.0, 68830.0, 68831.0, 68832.0],       [68833.0, 68834.0, 68835.0, 68836.0, 68837.0, 68838.0]],      [[68839.0, 68840.0, 68841.0, 68842.0, 68843.0, 68844.0],       [68845.0, 68846.0, 68847.0, 68848.0, 68849.0, 68850.0],       [68851.0, 68852.0, 68853.0, 68854.0, 68855.0, 68856.0],       [68857.0, 68858.0, 68859.0, 68860.0, 68861.0, 68862.0],       [68863.0, 68864.0, 68865.0, 68866.0, 68867.0, 68868.0],       [68869.0, 68870.0, 68871.0, 68872.0, 68873.0, 68874.0],       [68875.0, 68876.0, 68877.0, 68878.0, 68879.0, 68880.0]],      [[68881.0, 68882.0, 68883.0, 68884.0, 68885.0, 68886.0],       [68887.0, 68888.0, 68889.0, 68890.0, 68891.0, 68892.0],       [68893.0, 68894.0, 68895.0, 68896.0, 68897.0, 68898.0],       [68899.0, 68900.0, 68901.0, 68902.0, 68903.0, 68904.0],       [68905.0, 68906.0, 68907.0, 68908.0, 68909.0, 68910.0],       [68911.0, 68912.0, 68913.0, 68914.0, 68915.0, 68916.0],       [68917.0, 68918.0, 68919.0, 68920.0, 68921.0, 68922.0]],      [[68923.0, 68924.0, 68925.0, 68926.0, 68927.0, 68928.0],       [68929.0, 68930.0, 68931.0, 68932.0, 68933.0, 68934.0],       [68935.0, 68936.0, 68937.0, 68938.0, 68939.0, 68940.0],       [68941.0, 68942.0, 68943.0, 68944.0, 68945.0, 68946.0],       [68947.0, 68948.0, 68949.0, 68950.0, 68951.0, 68952.0],       [68953.0, 68954.0, 68955.0, 68956.0, 68957.0, 68958.0],       [68959.0, 68960.0, 68961.0, 68962.0, 68963.0, 68964.0]],      [[68965.0, 68966.0, 68967.0, 68968.0, 68969.0, 68970.0],       [68971.0, 68972.0, 68973.0, 68974.0, 68975.0, 68976.0],       [68977.0, 68978.0, 68979.0, 68980.0, 68981.0, 68982.0],       [68983.0, 68984.0, 68985.0, 68986.0, 68987.0, 68988.0],       [68989.0, 68990.0, 68991.0, 68992.0, 68993.0, 68994.0],       [68995.0, 68996.0, 68997.0, 68998.0, 68999.0, 69000.0],       [69001.0, 69002.0, 69003.0, 69004.0, 69005.0, 69006.0]],      [[69007.0, 69008.0, 69009.0, 69010.0, 69011.0, 69012.0],       [69013.0, 69014.0, 69015.0, 69016.0, 69017.0, 69018.0],       [69019.0, 69020.0, 69021.0, 69022.0, 69023.0, 69024.0],       [69025.0, 69026.0, 69027.0, 69028.0, 69029.0, 69030.0],       [69031.0, 69032.0, 69033.0, 69034.0, 69035.0, 69036.0],       [69037.0, 69038.0, 69039.0, 69040.0, 69041.0, 69042.0],       [69043.0, 69044.0, 69045.0, 69046.0, 69047.0, 69048.0]]],     [[[69049.0, 69050.0, 69051.0, 69052.0, 69053.0, 69054.0],       [69055.0, 69056.0, 69057.0, 69058.0, 69059.0, 69060.0],       [69061.0, 69062.0, 69063.0, 69064.0, 69065.0, 69066.0],       [69067.0, 69068.0, 69069.0, 69070.0, 69071.0, 69072.0],       [69073.0, 69074.0, 69075.0, 69076.0, 69077.0, 69078.0],       [69079.0, 69080.0, 69081.0, 69082.0, 69083.0, 69084.0],       [69085.0, 69086.0, 69087.0, 69088.0, 69089.0, 69090.0]],      [[69091.0, 69092.0, 69093.0, 69094.0, 69095.0, 69096.0],       [69097.0, 69098.0, 69099.0, 69100.0, 69101.0, 69102.0],       [69103.0, 69104.0, 69105.0, 69106.0, 69107.0, 69108.0],       [69109.0, 69110.0, 69111.0, 69112.0, 69113.0, 69114.0],       [69115.0, 69116.0, 69117.0, 69118.0, 69119.0, 69120.0],       [69121.0, 69122.0, 69123.0, 69124.0, 69125.0, 69126.0],       [69127.0, 69128.0, 69129.0, 69130.0, 69131.0, 69132.0]],      [[69133.0, 69134.0, 69135.0, 69136.0, 69137.0, 69138.0],       [69139.0, 69140.0, 69141.0, 69142.0, 69143.0, 69144.0],       [69145.0, 69146.0, 69147.0, 69148.0, 69149.0, 69150.0],       [69151.0, 69152.0, 69153.0, 69154.0, 69155.0, 69156.0],       [69157.0, 69158.0, 69159.0, 69160.0, 69161.0, 69162.0],       [69163.0, 69164.0, 69165.0, 69166.0, 69167.0, 69168.0],       [69169.0, 69170.0, 69171.0, 69172.0, 69173.0, 69174.0]],      [[69175.0, 69176.0, 69177.0, 69178.0, 69179.0, 69180.0],       [69181.0, 69182.0, 69183.0, 69184.0, 69185.0, 69186.0],       [69187.0, 69188.0, 69189.0, 69190.0, 69191.0, 69192.0],       [69193.0, 69194.0, 69195.0, 69196.0, 69197.0, 69198.0],       [69199.0, 69200.0, 69201.0, 69202.0, 69203.0, 69204.0],       [69205.0, 69206.0, 69207.0, 69208.0, 69209.0, 69210.0],       [69211.0, 69212.0, 69213.0, 69214.0, 69215.0, 69216.0]],      [[69217.0, 69218.0, 69219.0, 69220.0, 69221.0, 69222.0],       [69223.0, 69224.0, 69225.0, 69226.0, 69227.0, 69228.0],       [69229.0, 69230.0, 69231.0, 69232.0, 69233.0, 69234.0],       [69235.0, 69236.0, 69237.0, 69238.0, 69239.0, 69240.0],       [69241.0, 69242.0, 69243.0, 69244.0, 69245.0, 69246.0],       [69247.0, 69248.0, 69249.0, 69250.0, 69251.0, 69252.0],       [69253.0, 69254.0, 69255.0, 69256.0, 69257.0, 69258.0]],      [[69259.0, 69260.0, 69261.0, 69262.0, 69263.0, 69264.0],       [69265.0, 69266.0, 69267.0, 69268.0, 69269.0, 69270.0],       [69271.0, 69272.0, 69273.0, 69274.0, 69275.0, 69276.0],       [69277.0, 69278.0, 69279.0, 69280.0, 69281.0, 69282.0],       [69283.0, 69284.0, 69285.0, 69286.0, 69287.0, 69288.0],       [69289.0, 69290.0, 69291.0, 69292.0, 69293.0, 69294.0],       [69295.0, 69296.0, 69297.0, 69298.0, 69299.0, 69300.0]]],     [[[69301.0, 69302.0, 69303.0, 69304.0, 69305.0, 69306.0],       [69307.0, 69308.0, 69309.0, 69310.0, 69311.0, 69312.0],       [69313.0, 69314.0, 69315.0, 69316.0, 69317.0, 69318.0],       [69319.0, 69320.0, 69321.0, 69322.0, 69323.0, 69324.0],       [69325.0, 69326.0, 69327.0, 69328.0, 69329.0, 69330.0],       [69331.0, 69332.0, 69333.0, 69334.0, 69335.0, 69336.0],       [69337.0, 69338.0, 69339.0, 69340.0, 69341.0, 69342.0]],      [[69343.0, 69344.0, 69345.0, 69346.0, 69347.0, 69348.0],       [69349.0, 69350.0, 69351.0, 69352.0, 69353.0, 69354.0],       [69355.0, 69356.0, 69357.0, 69358.0, 69359.0, 69360.0],       [69361.0, 69362.0, 69363.0, 69364.0, 69365.0, 69366.0],       [69367.0, 69368.0, 69369.0, 69370.0, 69371.0, 69372.0],       [69373.0, 69374.0, 69375.0, 69376.0, 69377.0, 69378.0],       [69379.0, 69380.0, 69381.0, 69382.0, 69383.0, 69384.0]],      [[69385.0, 69386.0, 69387.0, 69388.0, 69389.0, 69390.0],       [69391.0, 69392.0, 69393.0, 69394.0, 69395.0, 69396.0],       [69397.0, 69398.0, 69399.0, 69400.0, 69401.0, 69402.0],       [69403.0, 69404.0, 69405.0, 69406.0, 69407.0, 69408.0],       [69409.0, 69410.0, 69411.0, 69412.0, 69413.0, 69414.0],       [69415.0, 69416.0, 69417.0, 69418.0, 69419.0, 69420.0],       [69421.0, 69422.0, 69423.0, 69424.0, 69425.0, 69426.0]],      [[69427.0, 69428.0, 69429.0, 69430.0, 69431.0, 69432.0],       [69433.0, 69434.0, 69435.0, 69436.0, 69437.0, 69438.0],       [69439.0, 69440.0, 69441.0, 69442.0, 69443.0, 69444.0],       [69445.0, 69446.0, 69447.0, 69448.0, 69449.0, 69450.0],       [69451.0, 69452.0, 69453.0, 69454.0, 69455.0, 69456.0],       [69457.0, 69458.0, 69459.0, 69460.0, 69461.0, 69462.0],       [69463.0, 69464.0, 69465.0, 69466.0, 69467.0, 69468.0]],      [[69469.0, 69470.0, 69471.0, 69472.0, 69473.0, 69474.0],       [69475.0, 69476.0, 69477.0, 69478.0, 69479.0, 69480.0],       [69481.0, 69482.0, 69483.0, 69484.0, 69485.0, 69486.0],       [69487.0, 69488.0, 69489.0, 69490.0, 69491.0, 69492.0],       [69493.0, 69494.0, 69495.0, 69496.0, 69497.0, 69498.0],       [69499.0, 69500.0, 69501.0, 69502.0, 69503.0, 69504.0],       [69505.0, 69506.0, 69507.0, 69508.0, 69509.0, 69510.0]],      [[69511.0, 69512.0, 69513.0, 69514.0, 69515.0, 69516.0],       [69517.0, 69518.0, 69519.0, 69520.0, 69521.0, 69522.0],       [69523.0, 69524.0, 69525.0, 69526.0, 69527.0, 69528.0],       [69529.0, 69530.0, 69531.0, 69532.0, 69533.0, 69534.0],       [69535.0, 69536.0, 69537.0, 69538.0, 69539.0, 69540.0],       [69541.0, 69542.0, 69543.0, 69544.0, 69545.0, 69546.0],       [69547.0, 69548.0, 69549.0, 69550.0, 69551.0, 69552.0]]]],    [[[[69553.0, 69554.0, 69555.0, 69556.0, 69557.0, 69558.0],       [69559.0, 69560.0, 69561.0, 69562.0, 69563.0, 69564.0],       [69565.0, 69566.0, 69567.0, 69568.0, 69569.0, 69570.0],       [69571.0, 69572.0, 69573.0, 69574.0, 69575.0, 69576.0],       [69577.0, 69578.0, 69579.0, 69580.0, 69581.0, 69582.0],       [69583.0, 69584.0, 69585.0, 69586.0, 69587.0, 69588.0],       [69589.0, 69590.0, 69591.0, 69592.0, 69593.0, 69594.0]],      [[69595.0, 69596.0, 69597.0, 69598.0, 69599.0, 69600.0],       [69601.0, 69602.0, 69603.0, 69604.0, 69605.0, 69606.0],       [69607.0, 69608.0, 69609.0, 69610.0, 69611.0, 69612.0],       [69613.0, 69614.0, 69615.0, 69616.0, 69617.0, 69618.0],       [69619.0, 69620.0, 69621.0, 69622.0, 69623.0, 69624.0],       [69625.0, 69626.0, 69627.0, 69628.0, 69629.0, 69630.0],       [69631.0, 69632.0, 69633.0, 69634.0, 69635.0, 69636.0]],      [[69637.0, 69638.0, 69639.0, 69640.0, 69641.0, 69642.0],       [69643.0, 69644.0, 69645.0, 69646.0, 69647.0, 69648.0],       [69649.0, 69650.0, 69651.0, 69652.0, 69653.0, 69654.0],       [69655.0, 69656.0, 69657.0, 69658.0, 69659.0, 69660.0],       [69661.0, 69662.0, 69663.0, 69664.0, 69665.0, 69666.0],       [69667.0, 69668.0, 69669.0, 69670.0, 69671.0, 69672.0],       [69673.0, 69674.0, 69675.0, 69676.0, 69677.0, 69678.0]],      [[69679.0, 69680.0, 69681.0, 69682.0, 69683.0, 69684.0],       [69685.0, 69686.0, 69687.0, 69688.0, 69689.0, 69690.0],       [69691.0, 69692.0, 69693.0, 69694.0, 69695.0, 69696.0],       [69697.0, 69698.0, 69699.0, 69700.0, 69701.0, 69702.0],       [69703.0, 69704.0, 69705.0, 69706.0, 69707.0, 69708.0],       [69709.0, 69710.0, 69711.0, 69712.0, 69713.0, 69714.0],       [69715.0, 69716.0, 69717.0, 69718.0, 69719.0, 69720.0]],      [[69721.0, 69722.0, 69723.0, 69724.0, 69725.0, 69726.0],       [69727.0, 69728.0, 69729.0, 69730.0, 69731.0, 69732.0],       [69733.0, 69734.0, 69735.0, 69736.0, 69737.0, 69738.0],       [69739.0, 69740.0, 69741.0, 69742.0, 69743.0, 69744.0],       [69745.0, 69746.0, 69747.0, 69748.0, 69749.0, 69750.0],       [69751.0, 69752.0, 69753.0, 69754.0, 69755.0, 69756.0],       [69757.0, 69758.0, 69759.0, 69760.0, 69761.0, 69762.0]],      [[69763.0, 69764.0, 69765.0, 69766.0, 69767.0, 69768.0],       [69769.0, 69770.0, 69771.0, 69772.0, 69773.0, 69774.0],       [69775.0, 69776.0, 69777.0, 69778.0, 69779.0, 69780.0],       [69781.0, 69782.0, 69783.0, 69784.0, 69785.0, 69786.0],       [69787.0, 69788.0, 69789.0, 69790.0, 69791.0, 69792.0],       [69793.0, 69794.0, 69795.0, 69796.0, 69797.0, 69798.0],       [69799.0, 69800.0, 69801.0, 69802.0, 69803.0, 69804.0]]],     [[[69805.0, 69806.0, 69807.0, 69808.0, 69809.0, 69810.0],       [69811.0, 69812.0, 69813.0, 69814.0, 69815.0, 69816.0],       [69817.0, 69818.0, 69819.0, 69820.0, 69821.0, 69822.0],       [69823.0, 69824.0, 69825.0, 69826.0, 69827.0, 69828.0],       [69829.0, 69830.0, 69831.0, 69832.0, 69833.0, 69834.0],       [69835.0, 69836.0, 69837.0, 69838.0, 69839.0, 69840.0],       [69841.0, 69842.0, 69843.0, 69844.0, 69845.0, 69846.0]],      [[69847.0, 69848.0, 69849.0, 69850.0, 69851.0, 69852.0],       [69853.0, 69854.0, 69855.0, 69856.0, 69857.0, 69858.0],       [69859.0, 69860.0, 69861.0, 69862.0, 69863.0, 69864.0],       [69865.0, 69866.0, 69867.0, 69868.0, 69869.0, 69870.0],       [69871.0, 69872.0, 69873.0, 69874.0, 69875.0, 69876.0],       [69877.0, 69878.0, 69879.0, 69880.0, 69881.0, 69882.0],       [69883.0, 69884.0, 69885.0, 69886.0, 69887.0, 69888.0]],      [[69889.0, 69890.0, 69891.0, 69892.0, 69893.0, 69894.0],       [69895.0, 69896.0, 69897.0, 69898.0, 69899.0, 69900.0],       [69901.0, 69902.0, 69903.0, 69904.0, 69905.0, 69906.0],       [69907.0, 69908.0, 69909.0, 69910.0, 69911.0, 69912.0],       [69913.0, 69914.0, 69915.0, 69916.0, 69917.0, 69918.0],       [69919.0, 69920.0, 69921.0, 69922.0, 69923.0, 69924.0],       [69925.0, 69926.0, 69927.0, 69928.0, 69929.0, 69930.0]],      [[69931.0, 69932.0, 69933.0, 69934.0, 69935.0, 69936.0],       [69937.0, 69938.0, 69939.0, 69940.0, 69941.0, 69942.0],       [69943.0, 69944.0, 69945.0, 69946.0, 69947.0, 69948.0],       [69949.0, 69950.0, 69951.0, 69952.0, 69953.0, 69954.0],       [69955.0, 69956.0, 69957.0, 69958.0, 69959.0, 69960.0],       [69961.0, 69962.0, 69963.0, 69964.0, 69965.0, 69966.0],       [69967.0, 69968.0, 69969.0, 69970.0, 69971.0, 69972.0]],      [[69973.0, 69974.0, 69975.0, 69976.0, 69977.0, 69978.0],       [69979.0, 69980.0, 69981.0, 69982.0, 69983.0, 69984.0],       [69985.0, 69986.0, 69987.0, 69988.0, 69989.0, 69990.0],       [69991.0, 69992.0, 69993.0, 69994.0, 69995.0, 69996.0],       [69997.0, 69998.0, 69999.0, 70000.0, 70001.0, 70002.0],       [70003.0, 70004.0, 70005.0, 70006.0, 70007.0, 70008.0],       [70009.0, 70010.0, 70011.0, 70012.0, 70013.0, 70014.0]],      [[70015.0, 70016.0, 70017.0, 70018.0, 70019.0, 70020.0],       [70021.0, 70022.0, 70023.0, 70024.0, 70025.0, 70026.0],       [70027.0, 70028.0, 70029.0, 70030.0, 70031.0, 70032.0],       [70033.0, 70034.0, 70035.0, 70036.0, 70037.0, 70038.0],       [70039.0, 70040.0, 70041.0, 70042.0, 70043.0, 70044.0],       [70045.0, 70046.0, 70047.0, 70048.0, 70049.0, 70050.0],       [70051.0, 70052.0, 70053.0, 70054.0, 70055.0, 70056.0]]],     [[[70057.0, 70058.0, 70059.0, 70060.0, 70061.0, 70062.0],       [70063.0, 70064.0, 70065.0, 70066.0, 70067.0, 70068.0],       [70069.0, 70070.0, 70071.0, 70072.0, 70073.0, 70074.0],       [70075.0, 70076.0, 70077.0, 70078.0, 70079.0, 70080.0],       [70081.0, 70082.0, 70083.0, 70084.0, 70085.0, 70086.0],       [70087.0, 70088.0, 70089.0, 70090.0, 70091.0, 70092.0],       [70093.0, 70094.0, 70095.0, 70096.0, 70097.0, 70098.0]],      [[70099.0, 70100.0, 70101.0, 70102.0, 70103.0, 70104.0],       [70105.0, 70106.0, 70107.0, 70108.0, 70109.0, 70110.0],       [70111.0, 70112.0, 70113.0, 70114.0, 70115.0, 70116.0],       [70117.0, 70118.0, 70119.0, 70120.0, 70121.0, 70122.0],       [70123.0, 70124.0, 70125.0, 70126.0, 70127.0, 70128.0],       [70129.0, 70130.0, 70131.0, 70132.0, 70133.0, 70134.0],       [70135.0, 70136.0, 70137.0, 70138.0, 70139.0, 70140.0]],      [[70141.0, 70142.0, 70143.0, 70144.0, 70145.0, 70146.0],       [70147.0, 70148.0, 70149.0, 70150.0, 70151.0, 70152.0],       [70153.0, 70154.0, 70155.0, 70156.0, 70157.0, 70158.0],       [70159.0, 70160.0, 70161.0, 70162.0, 70163.0, 70164.0],       [70165.0, 70166.0, 70167.0, 70168.0, 70169.0, 70170.0],       [70171.0, 70172.0, 70173.0, 70174.0, 70175.0, 70176.0],       [70177.0, 70178.0, 70179.0, 70180.0, 70181.0, 70182.0]],      [[70183.0, 70184.0, 70185.0, 70186.0, 70187.0, 70188.0],       [70189.0, 70190.0, 70191.0, 70192.0, 70193.0, 70194.0],       [70195.0, 70196.0, 70197.0, 70198.0, 70199.0, 70200.0],       [70201.0, 70202.0, 70203.0, 70204.0, 70205.0, 70206.0],       [70207.0, 70208.0, 70209.0, 70210.0, 70211.0, 70212.0],       [70213.0, 70214.0, 70215.0, 70216.0, 70217.0, 70218.0],       [70219.0, 70220.0, 70221.0, 70222.0, 70223.0, 70224.0]],      [[70225.0, 70226.0, 70227.0, 70228.0, 70229.0, 70230.0],       [70231.0, 70232.0, 70233.0, 70234.0, 70235.0, 70236.0],       [70237.0, 70238.0, 70239.0, 70240.0, 70241.0, 70242.0],       [70243.0, 70244.0, 70245.0, 70246.0, 70247.0, 70248.0],       [70249.0, 70250.0, 70251.0, 70252.0, 70253.0, 70254.0],       [70255.0, 70256.0, 70257.0, 70258.0, 70259.0, 70260.0],       [70261.0, 70262.0, 70263.0, 70264.0, 70265.0, 70266.0]],      [[70267.0, 70268.0, 70269.0, 70270.0, 70271.0, 70272.0],       [70273.0, 70274.0, 70275.0, 70276.0, 70277.0, 70278.0],       [70279.0, 70280.0, 70281.0, 70282.0, 70283.0, 70284.0],       [70285.0, 70286.0, 70287.0, 70288.0, 70289.0, 70290.0],       [70291.0, 70292.0, 70293.0, 70294.0, 70295.0, 70296.0],       [70297.0, 70298.0, 70299.0, 70300.0, 70301.0, 70302.0],       [70303.0, 70304.0, 70305.0, 70306.0, 70307.0, 70308.0]]],     [[[70309.0, 70310.0, 70311.0, 70312.0, 70313.0, 70314.0],       [70315.0, 70316.0, 70317.0, 70318.0, 70319.0, 70320.0],       [70321.0, 70322.0, 70323.0, 70324.0, 70325.0, 70326.0],       [70327.0, 70328.0, 70329.0, 70330.0, 70331.0, 70332.0],       [70333.0, 70334.0, 70335.0, 70336.0, 70337.0, 70338.0],       [70339.0, 70340.0, 70341.0, 70342.0, 70343.0, 70344.0],       [70345.0, 70346.0, 70347.0, 70348.0, 70349.0, 70350.0]],      [[70351.0, 70352.0, 70353.0, 70354.0, 70355.0, 70356.0],       [70357.0, 70358.0, 70359.0, 70360.0, 70361.0, 70362.0],       [70363.0, 70364.0, 70365.0, 70366.0, 70367.0, 70368.0],       [70369.0, 70370.0, 70371.0, 70372.0, 70373.0, 70374.0],       [70375.0, 70376.0, 70377.0, 70378.0, 70379.0, 70380.0],       [70381.0, 70382.0, 70383.0, 70384.0, 70385.0, 70386.0],       [70387.0, 70388.0, 70389.0, 70390.0, 70391.0, 70392.0]],      [[70393.0, 70394.0, 70395.0, 70396.0, 70397.0, 70398.0],       [70399.0, 70400.0, 70401.0, 70402.0, 70403.0, 70404.0],       [70405.0, 70406.0, 70407.0, 70408.0, 70409.0, 70410.0],       [70411.0, 70412.0, 70413.0, 70414.0, 70415.0, 70416.0],       [70417.0, 70418.0, 70419.0, 70420.0, 70421.0, 70422.0],       [70423.0, 70424.0, 70425.0, 70426.0, 70427.0, 70428.0],       [70429.0, 70430.0, 70431.0, 70432.0, 70433.0, 70434.0]],      [[70435.0, 70436.0, 70437.0, 70438.0, 70439.0, 70440.0],       [70441.0, 70442.0, 70443.0, 70444.0, 70445.0, 70446.0],       [70447.0, 70448.0, 70449.0, 70450.0, 70451.0, 70452.0],       [70453.0, 70454.0, 70455.0, 70456.0, 70457.0, 70458.0],       [70459.0, 70460.0, 70461.0, 70462.0, 70463.0, 70464.0],       [70465.0, 70466.0, 70467.0, 70468.0, 70469.0, 70470.0],       [70471.0, 70472.0, 70473.0, 70474.0, 70475.0, 70476.0]],      [[70477.0, 70478.0, 70479.0, 70480.0, 70481.0, 70482.0],       [70483.0, 70484.0, 70485.0, 70486.0, 70487.0, 70488.0],       [70489.0, 70490.0, 70491.0, 70492.0, 70493.0, 70494.0],       [70495.0, 70496.0, 70497.0, 70498.0, 70499.0, 70500.0],       [70501.0, 70502.0, 70503.0, 70504.0, 70505.0, 70506.0],       [70507.0, 70508.0, 70509.0, 70510.0, 70511.0, 70512.0],       [70513.0, 70514.0, 70515.0, 70516.0, 70517.0, 70518.0]],      [[70519.0, 70520.0, 70521.0, 70522.0, 70523.0, 70524.0],       [70525.0, 70526.0, 70527.0, 70528.0, 70529.0, 70530.0],       [70531.0, 70532.0, 70533.0, 70534.0, 70535.0, 70536.0],       [70537.0, 70538.0, 70539.0, 70540.0, 70541.0, 70542.0],       [70543.0, 70544.0, 70545.0, 70546.0, 70547.0, 70548.0],       [70549.0, 70550.0, 70551.0, 70552.0, 70553.0, 70554.0],       [70555.0, 70556.0, 70557.0, 70558.0, 70559.0, 70560.0]]]],    [[[[70561.0, 70562.0, 70563.0, 70564.0, 70565.0, 70566.0],       [70567.0, 70568.0, 70569.0, 70570.0, 70571.0, 70572.0],       [70573.0, 70574.0, 70575.0, 70576.0, 70577.0, 70578.0],       [70579.0, 70580.0, 70581.0, 70582.0, 70583.0, 70584.0],       [70585.0, 70586.0, 70587.0, 70588.0, 70589.0, 70590.0],       [70591.0, 70592.0, 70593.0, 70594.0, 70595.0, 70596.0],       [70597.0, 70598.0, 70599.0, 70600.0, 70601.0, 70602.0]],      [[70603.0, 70604.0, 70605.0, 70606.0, 70607.0, 70608.0],       [70609.0, 70610.0, 70611.0, 70612.0, 70613.0, 70614.0],       [70615.0, 70616.0, 70617.0, 70618.0, 70619.0, 70620.0],       [70621.0, 70622.0, 70623.0, 70624.0, 70625.0, 70626.0],       [70627.0, 70628.0, 70629.0, 70630.0, 70631.0, 70632.0],       [70633.0, 70634.0, 70635.0, 70636.0, 70637.0, 70638.0],       [70639.0, 70640.0, 70641.0, 70642.0, 70643.0, 70644.0]],      [[70645.0, 70646.0, 70647.0, 70648.0, 70649.0, 70650.0],       [70651.0, 70652.0, 70653.0, 70654.0, 70655.0, 70656.0],       [70657.0, 70658.0, 70659.0, 70660.0, 70661.0, 70662.0],       [70663.0, 70664.0, 70665.0, 70666.0, 70667.0, 70668.0],       [70669.0, 70670.0, 70671.0, 70672.0, 70673.0, 70674.0],       [70675.0, 70676.0, 70677.0, 70678.0, 70679.0, 70680.0],       [70681.0, 70682.0, 70683.0, 70684.0, 70685.0, 70686.0]],      [[70687.0, 70688.0, 70689.0, 70690.0, 70691.0, 70692.0],       [70693.0, 70694.0, 70695.0, 70696.0, 70697.0, 70698.0],       [70699.0, 70700.0, 70701.0, 70702.0, 70703.0, 70704.0],       [70705.0, 70706.0, 70707.0, 70708.0, 70709.0, 70710.0],       [70711.0, 70712.0, 70713.0, 70714.0, 70715.0, 70716.0],       [70717.0, 70718.0, 70719.0, 70720.0, 70721.0, 70722.0],       [70723.0, 70724.0, 70725.0, 70726.0, 70727.0, 70728.0]],      [[70729.0, 70730.0, 70731.0, 70732.0, 70733.0, 70734.0],       [70735.0, 70736.0, 70737.0, 70738.0, 70739.0, 70740.0],       [70741.0, 70742.0, 70743.0, 70744.0, 70745.0, 70746.0],       [70747.0, 70748.0, 70749.0, 70750.0, 70751.0, 70752.0],       [70753.0, 70754.0, 70755.0, 70756.0, 70757.0, 70758.0],       [70759.0, 70760.0, 70761.0, 70762.0, 70763.0, 70764.0],       [70765.0, 70766.0, 70767.0, 70768.0, 70769.0, 70770.0]],      [[70771.0, 70772.0, 70773.0, 70774.0, 70775.0, 70776.0],       [70777.0, 70778.0, 70779.0, 70780.0, 70781.0, 70782.0],       [70783.0, 70784.0, 70785.0, 70786.0, 70787.0, 70788.0],       [70789.0, 70790.0, 70791.0, 70792.0, 70793.0, 70794.0],       [70795.0, 70796.0, 70797.0, 70798.0, 70799.0, 70800.0],       [70801.0, 70802.0, 70803.0, 70804.0, 70805.0, 70806.0],       [70807.0, 70808.0, 70809.0, 70810.0, 70811.0, 70812.0]]],     [[[70813.0, 70814.0, 70815.0, 70816.0, 70817.0, 70818.0],       [70819.0, 70820.0, 70821.0, 70822.0, 70823.0, 70824.0],       [70825.0, 70826.0, 70827.0, 70828.0, 70829.0, 70830.0],       [70831.0, 70832.0, 70833.0, 70834.0, 70835.0, 70836.0],       [70837.0, 70838.0, 70839.0, 70840.0, 70841.0, 70842.0],       [70843.0, 70844.0, 70845.0, 70846.0, 70847.0, 70848.0],       [70849.0, 70850.0, 70851.0, 70852.0, 70853.0, 70854.0]],      [[70855.0, 70856.0, 70857.0, 70858.0, 70859.0, 70860.0],       [70861.0, 70862.0, 70863.0, 70864.0, 70865.0, 70866.0],       [70867.0, 70868.0, 70869.0, 70870.0, 70871.0, 70872.0],       [70873.0, 70874.0, 70875.0, 70876.0, 70877.0, 70878.0],       [70879.0, 70880.0, 70881.0, 70882.0, 70883.0, 70884.0],       [70885.0, 70886.0, 70887.0, 70888.0, 70889.0, 70890.0],       [70891.0, 70892.0, 70893.0, 70894.0, 70895.0, 70896.0]],      [[70897.0, 70898.0, 70899.0, 70900.0, 70901.0, 70902.0],       [70903.0, 70904.0, 70905.0, 70906.0, 70907.0, 70908.0],       [70909.0, 70910.0, 70911.0, 70912.0, 70913.0, 70914.0],       [70915.0, 70916.0, 70917.0, 70918.0, 70919.0, 70920.0],       [70921.0, 70922.0, 70923.0, 70924.0, 70925.0, 70926.0],       [70927.0, 70928.0, 70929.0, 70930.0, 70931.0, 70932.0],       [70933.0, 70934.0, 70935.0, 70936.0, 70937.0, 70938.0]],      [[70939.0, 70940.0, 70941.0, 70942.0, 70943.0, 70944.0],       [70945.0, 70946.0, 70947.0, 70948.0, 70949.0, 70950.0],       [70951.0, 70952.0, 70953.0, 70954.0, 70955.0, 70956.0],       [70957.0, 70958.0, 70959.0, 70960.0, 70961.0, 70962.0],       [70963.0, 70964.0, 70965.0, 70966.0, 70967.0, 70968.0],       [70969.0, 70970.0, 70971.0, 70972.0, 70973.0, 70974.0],       [70975.0, 70976.0, 70977.0, 70978.0, 70979.0, 70980.0]],      [[70981.0, 70982.0, 70983.0, 70984.0, 70985.0, 70986.0],       [70987.0, 70988.0, 70989.0, 70990.0, 70991.0, 70992.0],       [70993.0, 70994.0, 70995.0, 70996.0, 70997.0, 70998.0],       [70999.0, 71000.0, 71001.0, 71002.0, 71003.0, 71004.0],       [71005.0, 71006.0, 71007.0, 71008.0, 71009.0, 71010.0],       [71011.0, 71012.0, 71013.0, 71014.0, 71015.0, 71016.0],       [71017.0, 71018.0, 71019.0, 71020.0, 71021.0, 71022.0]],      [[71023.0, 71024.0, 71025.0, 71026.0, 71027.0, 71028.0],       [71029.0, 71030.0, 71031.0, 71032.0, 71033.0, 71034.0],       [71035.0, 71036.0, 71037.0, 71038.0, 71039.0, 71040.0],       [71041.0, 71042.0, 71043.0, 71044.0, 71045.0, 71046.0],       [71047.0, 71048.0, 71049.0, 71050.0, 71051.0, 71052.0],       [71053.0, 71054.0, 71055.0, 71056.0, 71057.0, 71058.0],       [71059.0, 71060.0, 71061.0, 71062.0, 71063.0, 71064.0]]],     [[[71065.0, 71066.0, 71067.0, 71068.0, 71069.0, 71070.0],       [71071.0, 71072.0, 71073.0, 71074.0, 71075.0, 71076.0],       [71077.0, 71078.0, 71079.0, 71080.0, 71081.0, 71082.0],       [71083.0, 71084.0, 71085.0, 71086.0, 71087.0, 71088.0],       [71089.0, 71090.0, 71091.0, 71092.0, 71093.0, 71094.0],       [71095.0, 71096.0, 71097.0, 71098.0, 71099.0, 71100.0],       [71101.0, 71102.0, 71103.0, 71104.0, 71105.0, 71106.0]],      [[71107.0, 71108.0, 71109.0, 71110.0, 71111.0, 71112.0],       [71113.0, 71114.0, 71115.0, 71116.0, 71117.0, 71118.0],       [71119.0, 71120.0, 71121.0, 71122.0, 71123.0, 71124.0],       [71125.0, 71126.0, 71127.0, 71128.0, 71129.0, 71130.0],       [71131.0, 71132.0, 71133.0, 71134.0, 71135.0, 71136.0],       [71137.0, 71138.0, 71139.0, 71140.0, 71141.0, 71142.0],       [71143.0, 71144.0, 71145.0, 71146.0, 71147.0, 71148.0]],      [[71149.0, 71150.0, 71151.0, 71152.0, 71153.0, 71154.0],       [71155.0, 71156.0, 71157.0, 71158.0, 71159.0, 71160.0],       [71161.0, 71162.0, 71163.0, 71164.0, 71165.0, 71166.0],       [71167.0, 71168.0, 71169.0, 71170.0, 71171.0, 71172.0],       [71173.0, 71174.0, 71175.0, 71176.0, 71177.0, 71178.0],       [71179.0, 71180.0, 71181.0, 71182.0, 71183.0, 71184.0],       [71185.0, 71186.0, 71187.0, 71188.0, 71189.0, 71190.0]],      [[71191.0, 71192.0, 71193.0, 71194.0, 71195.0, 71196.0],       [71197.0, 71198.0, 71199.0, 71200.0, 71201.0, 71202.0],       [71203.0, 71204.0, 71205.0, 71206.0, 71207.0, 71208.0],       [71209.0, 71210.0, 71211.0, 71212.0, 71213.0, 71214.0],       [71215.0, 71216.0, 71217.0, 71218.0, 71219.0, 71220.0],       [71221.0, 71222.0, 71223.0, 71224.0, 71225.0, 71226.0],       [71227.0, 71228.0, 71229.0, 71230.0, 71231.0, 71232.0]],      [[71233.0, 71234.0, 71235.0, 71236.0, 71237.0, 71238.0],       [71239.0, 71240.0, 71241.0, 71242.0, 71243.0, 71244.0],       [71245.0, 71246.0, 71247.0, 71248.0, 71249.0, 71250.0],       [71251.0, 71252.0, 71253.0, 71254.0, 71255.0, 71256.0],       [71257.0, 71258.0, 71259.0, 71260.0, 71261.0, 71262.0],       [71263.0, 71264.0, 71265.0, 71266.0, 71267.0, 71268.0],       [71269.0, 71270.0, 71271.0, 71272.0, 71273.0, 71274.0]],      [[71275.0, 71276.0, 71277.0, 71278.0, 71279.0, 71280.0],       [71281.0, 71282.0, 71283.0, 71284.0, 71285.0, 71286.0],       [71287.0, 71288.0, 71289.0, 71290.0, 71291.0, 71292.0],       [71293.0, 71294.0, 71295.0, 71296.0, 71297.0, 71298.0],       [71299.0, 71300.0, 71301.0, 71302.0, 71303.0, 71304.0],       [71305.0, 71306.0, 71307.0, 71308.0, 71309.0, 71310.0],       [71311.0, 71312.0, 71313.0, 71314.0, 71315.0, 71316.0]]],     [[[71317.0, 71318.0, 71319.0, 71320.0, 71321.0, 71322.0],       [71323.0, 71324.0, 71325.0, 71326.0, 71327.0, 71328.0],       [71329.0, 71330.0, 71331.0, 71332.0, 71333.0, 71334.0],       [71335.0, 71336.0, 71337.0, 71338.0, 71339.0, 71340.0],       [71341.0, 71342.0, 71343.0, 71344.0, 71345.0, 71346.0],       [71347.0, 71348.0, 71349.0, 71350.0, 71351.0, 71352.0],       [71353.0, 71354.0, 71355.0, 71356.0, 71357.0, 71358.0]],      [[71359.0, 71360.0, 71361.0, 71362.0, 71363.0, 71364.0],       [71365.0, 71366.0, 71367.0, 71368.0, 71369.0, 71370.0],       [71371.0, 71372.0, 71373.0, 71374.0, 71375.0, 71376.0],       [71377.0, 71378.0, 71379.0, 71380.0, 71381.0, 71382.0],       [71383.0, 71384.0, 71385.0, 71386.0, 71387.0, 71388.0],       [71389.0, 71390.0, 71391.0, 71392.0, 71393.0, 71394.0],       [71395.0, 71396.0, 71397.0, 71398.0, 71399.0, 71400.0]],      [[71401.0, 71402.0, 71403.0, 71404.0, 71405.0, 71406.0],       [71407.0, 71408.0, 71409.0, 71410.0, 71411.0, 71412.0],       [71413.0, 71414.0, 71415.0, 71416.0, 71417.0, 71418.0],       [71419.0, 71420.0, 71421.0, 71422.0, 71423.0, 71424.0],       [71425.0, 71426.0, 71427.0, 71428.0, 71429.0, 71430.0],       [71431.0, 71432.0, 71433.0, 71434.0, 71435.0, 71436.0],       [71437.0, 71438.0, 71439.0, 71440.0, 71441.0, 71442.0]],      [[71443.0, 71444.0, 71445.0, 71446.0, 71447.0, 71448.0],       [71449.0, 71450.0, 71451.0, 71452.0, 71453.0, 71454.0],       [71455.0, 71456.0, 71457.0, 71458.0, 71459.0, 71460.0],       [71461.0, 71462.0, 71463.0, 71464.0, 71465.0, 71466.0],       [71467.0, 71468.0, 71469.0, 71470.0, 71471.0, 71472.0],       [71473.0, 71474.0, 71475.0, 71476.0, 71477.0, 71478.0],       [71479.0, 71480.0, 71481.0, 71482.0, 71483.0, 71484.0]],      [[71485.0, 71486.0, 71487.0, 71488.0, 71489.0, 71490.0],       [71491.0, 71492.0, 71493.0, 71494.0, 71495.0, 71496.0],       [71497.0, 71498.0, 71499.0, 71500.0, 71501.0, 71502.0],       [71503.0, 71504.0, 71505.0, 71506.0, 71507.0, 71508.0],       [71509.0, 71510.0, 71511.0, 71512.0, 71513.0, 71514.0],       [71515.0, 71516.0, 71517.0, 71518.0, 71519.0, 71520.0],       [71521.0, 71522.0, 71523.0, 71524.0, 71525.0, 71526.0]],      [[71527.0, 71528.0, 71529.0, 71530.0, 71531.0, 71532.0],       [71533.0, 71534.0, 71535.0, 71536.0, 71537.0, 71538.0],       [71539.0, 71540.0, 71541.0, 71542.0, 71543.0, 71544.0],       [71545.0, 71546.0, 71547.0, 71548.0, 71549.0, 71550.0],       [71551.0, 71552.0, 71553.0, 71554.0, 71555.0, 71556.0],       [71557.0, 71558.0, 71559.0, 71560.0, 71561.0, 71562.0],       [71563.0, 71564.0, 71565.0, 71566.0, 71567.0, 71568.0]]]],    [[[[71569.0, 71570.0, 71571.0, 71572.0, 71573.0, 71574.0],       [71575.0, 71576.0, 71577.0, 71578.0, 71579.0, 71580.0],       [71581.0, 71582.0, 71583.0, 71584.0, 71585.0, 71586.0],       [71587.0, 71588.0, 71589.0, 71590.0, 71591.0, 71592.0],       [71593.0, 71594.0, 71595.0, 71596.0, 71597.0, 71598.0],       [71599.0, 71600.0, 71601.0, 71602.0, 71603.0, 71604.0],       [71605.0, 71606.0, 71607.0, 71608.0, 71609.0, 71610.0]],      [[71611.0, 71612.0, 71613.0, 71614.0, 71615.0, 71616.0],       [71617.0, 71618.0, 71619.0, 71620.0, 71621.0, 71622.0],       [71623.0, 71624.0, 71625.0, 71626.0, 71627.0, 71628.0],       [71629.0, 71630.0, 71631.0, 71632.0, 71633.0, 71634.0],       [71635.0, 71636.0, 71637.0, 71638.0, 71639.0, 71640.0],       [71641.0, 71642.0, 71643.0, 71644.0, 71645.0, 71646.0],       [71647.0, 71648.0, 71649.0, 71650.0, 71651.0, 71652.0]],      [[71653.0, 71654.0, 71655.0, 71656.0, 71657.0, 71658.0],       [71659.0, 71660.0, 71661.0, 71662.0, 71663.0, 71664.0],       [71665.0, 71666.0, 71667.0, 71668.0, 71669.0, 71670.0],       [71671.0, 71672.0, 71673.0, 71674.0, 71675.0, 71676.0],       [71677.0, 71678.0, 71679.0, 71680.0, 71681.0, 71682.0],       [71683.0, 71684.0, 71685.0, 71686.0, 71687.0, 71688.0],       [71689.0, 71690.0, 71691.0, 71692.0, 71693.0, 71694.0]],      [[71695.0, 71696.0, 71697.0, 71698.0, 71699.0, 71700.0],       [71701.0, 71702.0, 71703.0, 71704.0, 71705.0, 71706.0],       [71707.0, 71708.0, 71709.0, 71710.0, 71711.0, 71712.0],       [71713.0, 71714.0, 71715.0, 71716.0, 71717.0, 71718.0],       [71719.0, 71720.0, 71721.0, 71722.0, 71723.0, 71724.0],       [71725.0, 71726.0, 71727.0, 71728.0, 71729.0, 71730.0],       [71731.0, 71732.0, 71733.0, 71734.0, 71735.0, 71736.0]],      [[71737.0, 71738.0, 71739.0, 71740.0, 71741.0, 71742.0],       [71743.0, 71744.0, 71745.0, 71746.0, 71747.0, 71748.0],       [71749.0, 71750.0, 71751.0, 71752.0, 71753.0, 71754.0],       [71755.0, 71756.0, 71757.0, 71758.0, 71759.0, 71760.0],       [71761.0, 71762.0, 71763.0, 71764.0, 71765.0, 71766.0],       [71767.0, 71768.0, 71769.0, 71770.0, 71771.0, 71772.0],       [71773.0, 71774.0, 71775.0, 71776.0, 71777.0, 71778.0]],      [[71779.0, 71780.0, 71781.0, 71782.0, 71783.0, 71784.0],       [71785.0, 71786.0, 71787.0, 71788.0, 71789.0, 71790.0],       [71791.0, 71792.0, 71793.0, 71794.0, 71795.0, 71796.0],       [71797.0, 71798.0, 71799.0, 71800.0, 71801.0, 71802.0],       [71803.0, 71804.0, 71805.0, 71806.0, 71807.0, 71808.0],       [71809.0, 71810.0, 71811.0, 71812.0, 71813.0, 71814.0],       [71815.0, 71816.0, 71817.0, 71818.0, 71819.0, 71820.0]]],     [[[71821.0, 71822.0, 71823.0, 71824.0, 71825.0, 71826.0],       [71827.0, 71828.0, 71829.0, 71830.0, 71831.0, 71832.0],       [71833.0, 71834.0, 71835.0, 71836.0, 71837.0, 71838.0],       [71839.0, 71840.0, 71841.0, 71842.0, 71843.0, 71844.0],       [71845.0, 71846.0, 71847.0, 71848.0, 71849.0, 71850.0],       [71851.0, 71852.0, 71853.0, 71854.0, 71855.0, 71856.0],       [71857.0, 71858.0, 71859.0, 71860.0, 71861.0, 71862.0]],      [[71863.0, 71864.0, 71865.0, 71866.0, 71867.0, 71868.0],       [71869.0, 71870.0, 71871.0, 71872.0, 71873.0, 71874.0],       [71875.0, 71876.0, 71877.0, 71878.0, 71879.0, 71880.0],       [71881.0, 71882.0, 71883.0, 71884.0, 71885.0, 71886.0],       [71887.0, 71888.0, 71889.0, 71890.0, 71891.0, 71892.0],       [71893.0, 71894.0, 71895.0, 71896.0, 71897.0, 71898.0],       [71899.0, 71900.0, 71901.0, 71902.0, 71903.0, 71904.0]],      [[71905.0, 71906.0, 71907.0, 71908.0, 71909.0, 71910.0],       [71911.0, 71912.0, 71913.0, 71914.0, 71915.0, 71916.0],       [71917.0, 71918.0, 71919.0, 71920.0, 71921.0, 71922.0],       [71923.0, 71924.0, 71925.0, 71926.0, 71927.0, 71928.0],       [71929.0, 71930.0, 71931.0, 71932.0, 71933.0, 71934.0],       [71935.0, 71936.0, 71937.0, 71938.0, 71939.0, 71940.0],       [71941.0, 71942.0, 71943.0, 71944.0, 71945.0, 71946.0]],      [[71947.0, 71948.0, 71949.0, 71950.0, 71951.0, 71952.0],       [71953.0, 71954.0, 71955.0, 71956.0, 71957.0, 71958.0],       [71959.0, 71960.0, 71961.0, 71962.0, 71963.0, 71964.0],       [71965.0, 71966.0, 71967.0, 71968.0, 71969.0, 71970.0],       [71971.0, 71972.0, 71973.0, 71974.0, 71975.0, 71976.0],       [71977.0, 71978.0, 71979.0, 71980.0, 71981.0, 71982.0],       [71983.0, 71984.0, 71985.0, 71986.0, 71987.0, 71988.0]],      [[71989.0, 71990.0, 71991.0, 71992.0, 71993.0, 71994.0],       [71995.0, 71996.0, 71997.0, 71998.0, 71999.0, 72000.0],       [72001.0, 72002.0, 72003.0, 72004.0, 72005.0, 72006.0],       [72007.0, 72008.0, 72009.0, 72010.0, 72011.0, 72012.0],       [72013.0, 72014.0, 72015.0, 72016.0, 72017.0, 72018.0],       [72019.0, 72020.0, 72021.0, 72022.0, 72023.0, 72024.0],       [72025.0, 72026.0, 72027.0, 72028.0, 72029.0, 72030.0]],      [[72031.0, 72032.0, 72033.0, 72034.0, 72035.0, 72036.0],       [72037.0, 72038.0, 72039.0, 72040.0, 72041.0, 72042.0],       [72043.0, 72044.0, 72045.0, 72046.0, 72047.0, 72048.0],       [72049.0, 72050.0, 72051.0, 72052.0, 72053.0, 72054.0],       [72055.0, 72056.0, 72057.0, 72058.0, 72059.0, 72060.0],       [72061.0, 72062.0, 72063.0, 72064.0, 72065.0, 72066.0],       [72067.0, 72068.0, 72069.0, 72070.0, 72071.0, 72072.0]]],     [[[72073.0, 72074.0, 72075.0, 72076.0, 72077.0, 72078.0],       [72079.0, 72080.0, 72081.0, 72082.0, 72083.0, 72084.0],       [72085.0, 72086.0, 72087.0, 72088.0, 72089.0, 72090.0],       [72091.0, 72092.0, 72093.0, 72094.0, 72095.0, 72096.0],       [72097.0, 72098.0, 72099.0, 72100.0, 72101.0, 72102.0],       [72103.0, 72104.0, 72105.0, 72106.0, 72107.0, 72108.0],       [72109.0, 72110.0, 72111.0, 72112.0, 72113.0, 72114.0]],      [[72115.0, 72116.0, 72117.0, 72118.0, 72119.0, 72120.0],       [72121.0, 72122.0, 72123.0, 72124.0, 72125.0, 72126.0],       [72127.0, 72128.0, 72129.0, 72130.0, 72131.0, 72132.0],       [72133.0, 72134.0, 72135.0, 72136.0, 72137.0, 72138.0],       [72139.0, 72140.0, 72141.0, 72142.0, 72143.0, 72144.0],       [72145.0, 72146.0, 72147.0, 72148.0, 72149.0, 72150.0],       [72151.0, 72152.0, 72153.0, 72154.0, 72155.0, 72156.0]],      [[72157.0, 72158.0, 72159.0, 72160.0, 72161.0, 72162.0],       [72163.0, 72164.0, 72165.0, 72166.0, 72167.0, 72168.0],       [72169.0, 72170.0, 72171.0, 72172.0, 72173.0, 72174.0],       [72175.0, 72176.0, 72177.0, 72178.0, 72179.0, 72180.0],       [72181.0, 72182.0, 72183.0, 72184.0, 72185.0, 72186.0],       [72187.0, 72188.0, 72189.0, 72190.0, 72191.0, 72192.0],       [72193.0, 72194.0, 72195.0, 72196.0, 72197.0, 72198.0]],      [[72199.0, 72200.0, 72201.0, 72202.0, 72203.0, 72204.0],       [72205.0, 72206.0, 72207.0, 72208.0, 72209.0, 72210.0],       [72211.0, 72212.0, 72213.0, 72214.0, 72215.0, 72216.0],       [72217.0, 72218.0, 72219.0, 72220.0, 72221.0, 72222.0],       [72223.0, 72224.0, 72225.0, 72226.0, 72227.0, 72228.0],       [72229.0, 72230.0, 72231.0, 72232.0, 72233.0, 72234.0],       [72235.0, 72236.0, 72237.0, 72238.0, 72239.0, 72240.0]],      [[72241.0, 72242.0, 72243.0, 72244.0, 72245.0, 72246.0],       [72247.0, 72248.0, 72249.0, 72250.0, 72251.0, 72252.0],       [72253.0, 72254.0, 72255.0, 72256.0, 72257.0, 72258.0],       [72259.0, 72260.0, 72261.0, 72262.0, 72263.0, 72264.0],       [72265.0, 72266.0, 72267.0, 72268.0, 72269.0, 72270.0],       [72271.0, 72272.0, 72273.0, 72274.0, 72275.0, 72276.0],       [72277.0, 72278.0, 72279.0, 72280.0, 72281.0, 72282.0]],      [[72283.0, 72284.0, 72285.0, 72286.0, 72287.0, 72288.0],       [72289.0, 72290.0, 72291.0, 72292.0, 72293.0, 72294.0],       [72295.0, 72296.0, 72297.0, 72298.0, 72299.0, 72300.0],       [72301.0, 72302.0, 72303.0, 72304.0, 72305.0, 72306.0],       [72307.0, 72308.0, 72309.0, 72310.0, 72311.0, 72312.0],       [72313.0, 72314.0, 72315.0, 72316.0, 72317.0, 72318.0],       [72319.0, 72320.0, 72321.0, 72322.0, 72323.0, 72324.0]]],     [[[72325.0, 72326.0, 72327.0, 72328.0, 72329.0, 72330.0],       [72331.0, 72332.0, 72333.0, 72334.0, 72335.0, 72336.0],       [72337.0, 72338.0, 72339.0, 72340.0, 72341.0, 72342.0],       [72343.0, 72344.0, 72345.0, 72346.0, 72347.0, 72348.0],       [72349.0, 72350.0, 72351.0, 72352.0, 72353.0, 72354.0],       [72355.0, 72356.0, 72357.0, 72358.0, 72359.0, 72360.0],       [72361.0, 72362.0, 72363.0, 72364.0, 72365.0, 72366.0]],      [[72367.0, 72368.0, 72369.0, 72370.0, 72371.0, 72372.0],       [72373.0, 72374.0, 72375.0, 72376.0, 72377.0, 72378.0],       [72379.0, 72380.0, 72381.0, 72382.0, 72383.0, 72384.0],       [72385.0, 72386.0, 72387.0, 72388.0, 72389.0, 72390.0],       [72391.0, 72392.0, 72393.0, 72394.0, 72395.0, 72396.0],       [72397.0, 72398.0, 72399.0, 72400.0, 72401.0, 72402.0],       [72403.0, 72404.0, 72405.0, 72406.0, 72407.0, 72408.0]],      [[72409.0, 72410.0, 72411.0, 72412.0, 72413.0, 72414.0],       [72415.0, 72416.0, 72417.0, 72418.0, 72419.0, 72420.0],       [72421.0, 72422.0, 72423.0, 72424.0, 72425.0, 72426.0],       [72427.0, 72428.0, 72429.0, 72430.0, 72431.0, 72432.0],       [72433.0, 72434.0, 72435.0, 72436.0, 72437.0, 72438.0],       [72439.0, 72440.0, 72441.0, 72442.0, 72443.0, 72444.0],       [72445.0, 72446.0, 72447.0, 72448.0, 72449.0, 72450.0]],      [[72451.0, 72452.0, 72453.0, 72454.0, 72455.0, 72456.0],       [72457.0, 72458.0, 72459.0, 72460.0, 72461.0, 72462.0],       [72463.0, 72464.0, 72465.0, 72466.0, 72467.0, 72468.0],       [72469.0, 72470.0, 72471.0, 72472.0, 72473.0, 72474.0],       [72475.0, 72476.0, 72477.0, 72478.0, 72479.0, 72480.0],       [72481.0, 72482.0, 72483.0, 72484.0, 72485.0, 72486.0],       [72487.0, 72488.0, 72489.0, 72490.0, 72491.0, 72492.0]],      [[72493.0, 72494.0, 72495.0, 72496.0, 72497.0, 72498.0],       [72499.0, 72500.0, 72501.0, 72502.0, 72503.0, 72504.0],       [72505.0, 72506.0, 72507.0, 72508.0, 72509.0, 72510.0],       [72511.0, 72512.0, 72513.0, 72514.0, 72515.0, 72516.0],       [72517.0, 72518.0, 72519.0, 72520.0, 72521.0, 72522.0],       [72523.0, 72524.0, 72525.0, 72526.0, 72527.0, 72528.0],       [72529.0, 72530.0, 72531.0, 72532.0, 72533.0, 72534.0]],      [[72535.0, 72536.0, 72537.0, 72538.0, 72539.0, 72540.0],       [72541.0, 72542.0, 72543.0, 72544.0, 72545.0, 72546.0],       [72547.0, 72548.0, 72549.0, 72550.0, 72551.0, 72552.0],       [72553.0, 72554.0, 72555.0, 72556.0, 72557.0, 72558.0],       [72559.0, 72560.0, 72561.0, 72562.0, 72563.0, 72564.0],       [72565.0, 72566.0, 72567.0, 72568.0, 72569.0, 72570.0],       [72571.0, 72572.0, 72573.0, 72574.0, 72575.0, 72576.0]]]]]],  [[[[[[72577.0, 72578.0, 72579.0, 72580.0, 72581.0, 72582.0],       [72583.0, 72584.0, 72585.0, 72586.0, 72587.0, 72588.0],       [72589.0, 72590.0, 72591.0, 72592.0, 72593.0, 72594.0],       [72595.0, 72596.0, 72597.0, 72598.0, 72599.0, 72600.0],       [72601.0, 72602.0, 72603.0, 72604.0, 72605.0, 72606.0],       [72607.0, 72608.0, 72609.0, 72610.0, 72611.0, 72612.0],       [72613.0, 72614.0, 72615.0, 72616.0, 72617.0, 72618.0]],      [[72619.0, 72620.0, 72621.0, 72622.0, 72623.0, 72624.0],       [72625.0, 72626.0, 72627.0, 72628.0, 72629.0, 72630.0],       [72631.0, 72632.0, 72633.0, 72634.0, 72635.0, 72636.0],       [72637.0, 72638.0, 72639.0, 72640.0, 72641.0, 72642.0],       [72643.0, 72644.0, 72645.0, 72646.0, 72647.0, 72648.0],       [72649.0, 72650.0, 72651.0, 72652.0, 72653.0, 72654.0],       [72655.0, 72656.0, 72657.0, 72658.0, 72659.0, 72660.0]],      [[72661.0, 72662.0, 72663.0, 72664.0, 72665.0, 72666.0],       [72667.0, 72668.0, 72669.0, 72670.0, 72671.0, 72672.0],       [72673.0, 72674.0, 72675.0, 72676.0, 72677.0, 72678.0],       [72679.0, 72680.0, 72681.0, 72682.0, 72683.0, 72684.0],       [72685.0, 72686.0, 72687.0, 72688.0, 72689.0, 72690.0],       [72691.0, 72692.0, 72693.0, 72694.0, 72695.0, 72696.0],       [72697.0, 72698.0, 72699.0, 72700.0, 72701.0, 72702.0]],      [[72703.0, 72704.0, 72705.0, 72706.0, 72707.0, 72708.0],       [72709.0, 72710.0, 72711.0, 72712.0, 72713.0, 72714.0],       [72715.0, 72716.0, 72717.0, 72718.0, 72719.0, 72720.0],       [72721.0, 72722.0, 72723.0, 72724.0, 72725.0, 72726.0],       [72727.0, 72728.0, 72729.0, 72730.0, 72731.0, 72732.0],       [72733.0, 72734.0, 72735.0, 72736.0, 72737.0, 72738.0],       [72739.0, 72740.0, 72741.0, 72742.0, 72743.0, 72744.0]],      [[72745.0, 72746.0, 72747.0, 72748.0, 72749.0, 72750.0],       [72751.0, 72752.0, 72753.0, 72754.0, 72755.0, 72756.0],       [72757.0, 72758.0, 72759.0, 72760.0, 72761.0, 72762.0],       [72763.0, 72764.0, 72765.0, 72766.0, 72767.0, 72768.0],       [72769.0, 72770.0, 72771.0, 72772.0, 72773.0, 72774.0],       [72775.0, 72776.0, 72777.0, 72778.0, 72779.0, 72780.0],       [72781.0, 72782.0, 72783.0, 72784.0, 72785.0, 72786.0]],      [[72787.0, 72788.0, 72789.0, 72790.0, 72791.0, 72792.0],       [72793.0, 72794.0, 72795.0, 72796.0, 72797.0, 72798.0],       [72799.0, 72800.0, 72801.0, 72802.0, 72803.0, 72804.0],       [72805.0, 72806.0, 72807.0, 72808.0, 72809.0, 72810.0],       [72811.0, 72812.0, 72813.0, 72814.0, 72815.0, 72816.0],       [72817.0, 72818.0, 72819.0, 72820.0, 72821.0, 72822.0],       [72823.0, 72824.0, 72825.0, 72826.0, 72827.0, 72828.0]]],     [[[72829.0, 72830.0, 72831.0, 72832.0, 72833.0, 72834.0],       [72835.0, 72836.0, 72837.0, 72838.0, 72839.0, 72840.0],       [72841.0, 72842.0, 72843.0, 72844.0, 72845.0, 72846.0],       [72847.0, 72848.0, 72849.0, 72850.0, 72851.0, 72852.0],       [72853.0, 72854.0, 72855.0, 72856.0, 72857.0, 72858.0],       [72859.0, 72860.0, 72861.0, 72862.0, 72863.0, 72864.0],       [72865.0, 72866.0, 72867.0, 72868.0, 72869.0, 72870.0]],      [[72871.0, 72872.0, 72873.0, 72874.0, 72875.0, 72876.0],       [72877.0, 72878.0, 72879.0, 72880.0, 72881.0, 72882.0],       [72883.0, 72884.0, 72885.0, 72886.0, 72887.0, 72888.0],       [72889.0, 72890.0, 72891.0, 72892.0, 72893.0, 72894.0],       [72895.0, 72896.0, 72897.0, 72898.0, 72899.0, 72900.0],       [72901.0, 72902.0, 72903.0, 72904.0, 72905.0, 72906.0],       [72907.0, 72908.0, 72909.0, 72910.0, 72911.0, 72912.0]],      [[72913.0, 72914.0, 72915.0, 72916.0, 72917.0, 72918.0],       [72919.0, 72920.0, 72921.0, 72922.0, 72923.0, 72924.0],       [72925.0, 72926.0, 72927.0, 72928.0, 72929.0, 72930.0],       [72931.0, 72932.0, 72933.0, 72934.0, 72935.0, 72936.0],       [72937.0, 72938.0, 72939.0, 72940.0, 72941.0, 72942.0],       [72943.0, 72944.0, 72945.0, 72946.0, 72947.0, 72948.0],       [72949.0, 72950.0, 72951.0, 72952.0, 72953.0, 72954.0]],      [[72955.0, 72956.0, 72957.0, 72958.0, 72959.0, 72960.0],       [72961.0, 72962.0, 72963.0, 72964.0, 72965.0, 72966.0],       [72967.0, 72968.0, 72969.0, 72970.0, 72971.0, 72972.0],       [72973.0, 72974.0, 72975.0, 72976.0, 72977.0, 72978.0],       [72979.0, 72980.0, 72981.0, 72982.0, 72983.0, 72984.0],       [72985.0, 72986.0, 72987.0, 72988.0, 72989.0, 72990.0],       [72991.0, 72992.0, 72993.0, 72994.0, 72995.0, 72996.0]],      [[72997.0, 72998.0, 72999.0, 73000.0, 73001.0, 73002.0],       [73003.0, 73004.0, 73005.0, 73006.0, 73007.0, 73008.0],       [73009.0, 73010.0, 73011.0, 73012.0, 73013.0, 73014.0],       [73015.0, 73016.0, 73017.0, 73018.0, 73019.0, 73020.0],       [73021.0, 73022.0, 73023.0, 73024.0, 73025.0, 73026.0],       [73027.0, 73028.0, 73029.0, 73030.0, 73031.0, 73032.0],       [73033.0, 73034.0, 73035.0, 73036.0, 73037.0, 73038.0]],      [[73039.0, 73040.0, 73041.0, 73042.0, 73043.0, 73044.0],       [73045.0, 73046.0, 73047.0, 73048.0, 73049.0, 73050.0],       [73051.0, 73052.0, 73053.0, 73054.0, 73055.0, 73056.0],       [73057.0, 73058.0, 73059.0, 73060.0, 73061.0, 73062.0],       [73063.0, 73064.0, 73065.0, 73066.0, 73067.0, 73068.0],       [73069.0, 73070.0, 73071.0, 73072.0, 73073.0, 73074.0],       [73075.0, 73076.0, 73077.0, 73078.0, 73079.0, 73080.0]]],     [[[73081.0, 73082.0, 73083.0, 73084.0, 73085.0, 73086.0],       [73087.0, 73088.0, 73089.0, 73090.0, 73091.0, 73092.0],       [73093.0, 73094.0, 73095.0, 73096.0, 73097.0, 73098.0],       [73099.0, 73100.0, 73101.0, 73102.0, 73103.0, 73104.0],       [73105.0, 73106.0, 73107.0, 73108.0, 73109.0, 73110.0],       [73111.0, 73112.0, 73113.0, 73114.0, 73115.0, 73116.0],       [73117.0, 73118.0, 73119.0, 73120.0, 73121.0, 73122.0]],      [[73123.0, 73124.0, 73125.0, 73126.0, 73127.0, 73128.0],       [73129.0, 73130.0, 73131.0, 73132.0, 73133.0, 73134.0],       [73135.0, 73136.0, 73137.0, 73138.0, 73139.0, 73140.0],       [73141.0, 73142.0, 73143.0, 73144.0, 73145.0, 73146.0],       [73147.0, 73148.0, 73149.0, 73150.0, 73151.0, 73152.0],       [73153.0, 73154.0, 73155.0, 73156.0, 73157.0, 73158.0],       [73159.0, 73160.0, 73161.0, 73162.0, 73163.0, 73164.0]],      [[73165.0, 73166.0, 73167.0, 73168.0, 73169.0, 73170.0],       [73171.0, 73172.0, 73173.0, 73174.0, 73175.0, 73176.0],       [73177.0, 73178.0, 73179.0, 73180.0, 73181.0, 73182.0],       [73183.0, 73184.0, 73185.0, 73186.0, 73187.0, 73188.0],       [73189.0, 73190.0, 73191.0, 73192.0, 73193.0, 73194.0],       [73195.0, 73196.0, 73197.0, 73198.0, 73199.0, 73200.0],       [73201.0, 73202.0, 73203.0, 73204.0, 73205.0, 73206.0]],      [[73207.0, 73208.0, 73209.0, 73210.0, 73211.0, 73212.0],       [73213.0, 73214.0, 73215.0, 73216.0, 73217.0, 73218.0],       [73219.0, 73220.0, 73221.0, 73222.0, 73223.0, 73224.0],       [73225.0, 73226.0, 73227.0, 73228.0, 73229.0, 73230.0],       [73231.0, 73232.0, 73233.0, 73234.0, 73235.0, 73236.0],       [73237.0, 73238.0, 73239.0, 73240.0, 73241.0, 73242.0],       [73243.0, 73244.0, 73245.0, 73246.0, 73247.0, 73248.0]],      [[73249.0, 73250.0, 73251.0, 73252.0, 73253.0, 73254.0],       [73255.0, 73256.0, 73257.0, 73258.0, 73259.0, 73260.0],       [73261.0, 73262.0, 73263.0, 73264.0, 73265.0, 73266.0],       [73267.0, 73268.0, 73269.0, 73270.0, 73271.0, 73272.0],       [73273.0, 73274.0, 73275.0, 73276.0, 73277.0, 73278.0],       [73279.0, 73280.0, 73281.0, 73282.0, 73283.0, 73284.0],       [73285.0, 73286.0, 73287.0, 73288.0, 73289.0, 73290.0]],      [[73291.0, 73292.0, 73293.0, 73294.0, 73295.0, 73296.0],       [73297.0, 73298.0, 73299.0, 73300.0, 73301.0, 73302.0],       [73303.0, 73304.0, 73305.0, 73306.0, 73307.0, 73308.0],       [73309.0, 73310.0, 73311.0, 73312.0, 73313.0, 73314.0],       [73315.0, 73316.0, 73317.0, 73318.0, 73319.0, 73320.0],       [73321.0, 73322.0, 73323.0, 73324.0, 73325.0, 73326.0],       [73327.0, 73328.0, 73329.0, 73330.0, 73331.0, 73332.0]]],     [[[73333.0, 73334.0, 73335.0, 73336.0, 73337.0, 73338.0],       [73339.0, 73340.0, 73341.0, 73342.0, 73343.0, 73344.0],       [73345.0, 73346.0, 73347.0, 73348.0, 73349.0, 73350.0],       [73351.0, 73352.0, 73353.0, 73354.0, 73355.0, 73356.0],       [73357.0, 73358.0, 73359.0, 73360.0, 73361.0, 73362.0],       [73363.0, 73364.0, 73365.0, 73366.0, 73367.0, 73368.0],       [73369.0, 73370.0, 73371.0, 73372.0, 73373.0, 73374.0]],      [[73375.0, 73376.0, 73377.0, 73378.0, 73379.0, 73380.0],       [73381.0, 73382.0, 73383.0, 73384.0, 73385.0, 73386.0],       [73387.0, 73388.0, 73389.0, 73390.0, 73391.0, 73392.0],       [73393.0, 73394.0, 73395.0, 73396.0, 73397.0, 73398.0],       [73399.0, 73400.0, 73401.0, 73402.0, 73403.0, 73404.0],       [73405.0, 73406.0, 73407.0, 73408.0, 73409.0, 73410.0],       [73411.0, 73412.0, 73413.0, 73414.0, 73415.0, 73416.0]],      [[73417.0, 73418.0, 73419.0, 73420.0, 73421.0, 73422.0],       [73423.0, 73424.0, 73425.0, 73426.0, 73427.0, 73428.0],       [73429.0, 73430.0, 73431.0, 73432.0, 73433.0, 73434.0],       [73435.0, 73436.0, 73437.0, 73438.0, 73439.0, 73440.0],       [73441.0, 73442.0, 73443.0, 73444.0, 73445.0, 73446.0],       [73447.0, 73448.0, 73449.0, 73450.0, 73451.0, 73452.0],       [73453.0, 73454.0, 73455.0, 73456.0, 73457.0, 73458.0]],      [[73459.0, 73460.0, 73461.0, 73462.0, 73463.0, 73464.0],       [73465.0, 73466.0, 73467.0, 73468.0, 73469.0, 73470.0],       [73471.0, 73472.0, 73473.0, 73474.0, 73475.0, 73476.0],       [73477.0, 73478.0, 73479.0, 73480.0, 73481.0, 73482.0],       [73483.0, 73484.0, 73485.0, 73486.0, 73487.0, 73488.0],       [73489.0, 73490.0, 73491.0, 73492.0, 73493.0, 73494.0],       [73495.0, 73496.0, 73497.0, 73498.0, 73499.0, 73500.0]],      [[73501.0, 73502.0, 73503.0, 73504.0, 73505.0, 73506.0],       [73507.0, 73508.0, 73509.0, 73510.0, 73511.0, 73512.0],       [73513.0, 73514.0, 73515.0, 73516.0, 73517.0, 73518.0],       [73519.0, 73520.0, 73521.0, 73522.0, 73523.0, 73524.0],       [73525.0, 73526.0, 73527.0, 73528.0, 73529.0, 73530.0],       [73531.0, 73532.0, 73533.0, 73534.0, 73535.0, 73536.0],       [73537.0, 73538.0, 73539.0, 73540.0, 73541.0, 73542.0]],      [[73543.0, 73544.0, 73545.0, 73546.0, 73547.0, 73548.0],       [73549.0, 73550.0, 73551.0, 73552.0, 73553.0, 73554.0],       [73555.0, 73556.0, 73557.0, 73558.0, 73559.0, 73560.0],       [73561.0, 73562.0, 73563.0, 73564.0, 73565.0, 73566.0],       [73567.0, 73568.0, 73569.0, 73570.0, 73571.0, 73572.0],       [73573.0, 73574.0, 73575.0, 73576.0, 73577.0, 73578.0],       [73579.0, 73580.0, 73581.0, 73582.0, 73583.0, 73584.0]]]],    [[[[73585.0, 73586.0, 73587.0, 73588.0, 73589.0, 73590.0],       [73591.0, 73592.0, 73593.0, 73594.0, 73595.0, 73596.0],       [73597.0, 73598.0, 73599.0, 73600.0, 73601.0, 73602.0],       [73603.0, 73604.0, 73605.0, 73606.0, 73607.0, 73608.0],       [73609.0, 73610.0, 73611.0, 73612.0, 73613.0, 73614.0],       [73615.0, 73616.0, 73617.0, 73618.0, 73619.0, 73620.0],       [73621.0, 73622.0, 73623.0, 73624.0, 73625.0, 73626.0]],      [[73627.0, 73628.0, 73629.0, 73630.0, 73631.0, 73632.0],       [73633.0, 73634.0, 73635.0, 73636.0, 73637.0, 73638.0],       [73639.0, 73640.0, 73641.0, 73642.0, 73643.0, 73644.0],       [73645.0, 73646.0, 73647.0, 73648.0, 73649.0, 73650.0],       [73651.0, 73652.0, 73653.0, 73654.0, 73655.0, 73656.0],       [73657.0, 73658.0, 73659.0, 73660.0, 73661.0, 73662.0],       [73663.0, 73664.0, 73665.0, 73666.0, 73667.0, 73668.0]],      [[73669.0, 73670.0, 73671.0, 73672.0, 73673.0, 73674.0],       [73675.0, 73676.0, 73677.0, 73678.0, 73679.0, 73680.0],       [73681.0, 73682.0, 73683.0, 73684.0, 73685.0, 73686.0],       [73687.0, 73688.0, 73689.0, 73690.0, 73691.0, 73692.0],       [73693.0, 73694.0, 73695.0, 73696.0, 73697.0, 73698.0],       [73699.0, 73700.0, 73701.0, 73702.0, 73703.0, 73704.0],       [73705.0, 73706.0, 73707.0, 73708.0, 73709.0, 73710.0]],      [[73711.0, 73712.0, 73713.0, 73714.0, 73715.0, 73716.0],       [73717.0, 73718.0, 73719.0, 73720.0, 73721.0, 73722.0],       [73723.0, 73724.0, 73725.0, 73726.0, 73727.0, 73728.0],       [73729.0, 73730.0, 73731.0, 73732.0, 73733.0, 73734.0],       [73735.0, 73736.0, 73737.0, 73738.0, 73739.0, 73740.0],       [73741.0, 73742.0, 73743.0, 73744.0, 73745.0, 73746.0],       [73747.0, 73748.0, 73749.0, 73750.0, 73751.0, 73752.0]],      [[73753.0, 73754.0, 73755.0, 73756.0, 73757.0, 73758.0],       [73759.0, 73760.0, 73761.0, 73762.0, 73763.0, 73764.0],       [73765.0, 73766.0, 73767.0, 73768.0, 73769.0, 73770.0],       [73771.0, 73772.0, 73773.0, 73774.0, 73775.0, 73776.0],       [73777.0, 73778.0, 73779.0, 73780.0, 73781.0, 73782.0],       [73783.0, 73784.0, 73785.0, 73786.0, 73787.0, 73788.0],       [73789.0, 73790.0, 73791.0, 73792.0, 73793.0, 73794.0]],      [[73795.0, 73796.0, 73797.0, 73798.0, 73799.0, 73800.0],       [73801.0, 73802.0, 73803.0, 73804.0, 73805.0, 73806.0],       [73807.0, 73808.0, 73809.0, 73810.0, 73811.0, 73812.0],       [73813.0, 73814.0, 73815.0, 73816.0, 73817.0, 73818.0],       [73819.0, 73820.0, 73821.0, 73822.0, 73823.0, 73824.0],       [73825.0, 73826.0, 73827.0, 73828.0, 73829.0, 73830.0],       [73831.0, 73832.0, 73833.0, 73834.0, 73835.0, 73836.0]]],     [[[73837.0, 73838.0, 73839.0, 73840.0, 73841.0, 73842.0],       [73843.0, 73844.0, 73845.0, 73846.0, 73847.0, 73848.0],       [73849.0, 73850.0, 73851.0, 73852.0, 73853.0, 73854.0],       [73855.0, 73856.0, 73857.0, 73858.0, 73859.0, 73860.0],       [73861.0, 73862.0, 73863.0, 73864.0, 73865.0, 73866.0],       [73867.0, 73868.0, 73869.0, 73870.0, 73871.0, 73872.0],       [73873.0, 73874.0, 73875.0, 73876.0, 73877.0, 73878.0]],      [[73879.0, 73880.0, 73881.0, 73882.0, 73883.0, 73884.0],       [73885.0, 73886.0, 73887.0, 73888.0, 73889.0, 73890.0],       [73891.0, 73892.0, 73893.0, 73894.0, 73895.0, 73896.0],       [73897.0, 73898.0, 73899.0, 73900.0, 73901.0, 73902.0],       [73903.0, 73904.0, 73905.0, 73906.0, 73907.0, 73908.0],       [73909.0, 73910.0, 73911.0, 73912.0, 73913.0, 73914.0],       [73915.0, 73916.0, 73917.0, 73918.0, 73919.0, 73920.0]],      [[73921.0, 73922.0, 73923.0, 73924.0, 73925.0, 73926.0],       [73927.0, 73928.0, 73929.0, 73930.0, 73931.0, 73932.0],       [73933.0, 73934.0, 73935.0, 73936.0, 73937.0, 73938.0],       [73939.0, 73940.0, 73941.0, 73942.0, 73943.0, 73944.0],       [73945.0, 73946.0, 73947.0, 73948.0, 73949.0, 73950.0],       [73951.0, 73952.0, 73953.0, 73954.0, 73955.0, 73956.0],       [73957.0, 73958.0, 73959.0, 73960.0, 73961.0, 73962.0]],      [[73963.0, 73964.0, 73965.0, 73966.0, 73967.0, 73968.0],       [73969.0, 73970.0, 73971.0, 73972.0, 73973.0, 73974.0],       [73975.0, 73976.0, 73977.0, 73978.0, 73979.0, 73980.0],       [73981.0, 73982.0, 73983.0, 73984.0, 73985.0, 73986.0],       [73987.0, 73988.0, 73989.0, 73990.0, 73991.0, 73992.0],       [73993.0, 73994.0, 73995.0, 73996.0, 73997.0, 73998.0],       [73999.0, 74000.0, 74001.0, 74002.0, 74003.0, 74004.0]],      [[74005.0, 74006.0, 74007.0, 74008.0, 74009.0, 74010.0],       [74011.0, 74012.0, 74013.0, 74014.0, 74015.0, 74016.0],       [74017.0, 74018.0, 74019.0, 74020.0, 74021.0, 74022.0],       [74023.0, 74024.0, 74025.0, 74026.0, 74027.0, 74028.0],       [74029.0, 74030.0, 74031.0, 74032.0, 74033.0, 74034.0],       [74035.0, 74036.0, 74037.0, 74038.0, 74039.0, 74040.0],       [74041.0, 74042.0, 74043.0, 74044.0, 74045.0, 74046.0]],      [[74047.0, 74048.0, 74049.0, 74050.0, 74051.0, 74052.0],       [74053.0, 74054.0, 74055.0, 74056.0, 74057.0, 74058.0],       [74059.0, 74060.0, 74061.0, 74062.0, 74063.0, 74064.0],       [74065.0, 74066.0, 74067.0, 74068.0, 74069.0, 74070.0],       [74071.0, 74072.0, 74073.0, 74074.0, 74075.0, 74076.0],       [74077.0, 74078.0, 74079.0, 74080.0, 74081.0, 74082.0],       [74083.0, 74084.0, 74085.0, 74086.0, 74087.0, 74088.0]]],     [[[74089.0, 74090.0, 74091.0, 74092.0, 74093.0, 74094.0],       [74095.0, 74096.0, 74097.0, 74098.0, 74099.0, 74100.0],       [74101.0, 74102.0, 74103.0, 74104.0, 74105.0, 74106.0],       [74107.0, 74108.0, 74109.0, 74110.0, 74111.0, 74112.0],       [74113.0, 74114.0, 74115.0, 74116.0, 74117.0, 74118.0],       [74119.0, 74120.0, 74121.0, 74122.0, 74123.0, 74124.0],       [74125.0, 74126.0, 74127.0, 74128.0, 74129.0, 74130.0]],      [[74131.0, 74132.0, 74133.0, 74134.0, 74135.0, 74136.0],       [74137.0, 74138.0, 74139.0, 74140.0, 74141.0, 74142.0],       [74143.0, 74144.0, 74145.0, 74146.0, 74147.0, 74148.0],       [74149.0, 74150.0, 74151.0, 74152.0, 74153.0, 74154.0],       [74155.0, 74156.0, 74157.0, 74158.0, 74159.0, 74160.0],       [74161.0, 74162.0, 74163.0, 74164.0, 74165.0, 74166.0],       [74167.0, 74168.0, 74169.0, 74170.0, 74171.0, 74172.0]],      [[74173.0, 74174.0, 74175.0, 74176.0, 74177.0, 74178.0],       [74179.0, 74180.0, 74181.0, 74182.0, 74183.0, 74184.0],       [74185.0, 74186.0, 74187.0, 74188.0, 74189.0, 74190.0],       [74191.0, 74192.0, 74193.0, 74194.0, 74195.0, 74196.0],       [74197.0, 74198.0, 74199.0, 74200.0, 74201.0, 74202.0],       [74203.0, 74204.0, 74205.0, 74206.0, 74207.0, 74208.0],       [74209.0, 74210.0, 74211.0, 74212.0, 74213.0, 74214.0]],      [[74215.0, 74216.0, 74217.0, 74218.0, 74219.0, 74220.0],       [74221.0, 74222.0, 74223.0, 74224.0, 74225.0, 74226.0],       [74227.0, 74228.0, 74229.0, 74230.0, 74231.0, 74232.0],       [74233.0, 74234.0, 74235.0, 74236.0, 74237.0, 74238.0],       [74239.0, 74240.0, 74241.0, 74242.0, 74243.0, 74244.0],       [74245.0, 74246.0, 74247.0, 74248.0, 74249.0, 74250.0],       [74251.0, 74252.0, 74253.0, 74254.0, 74255.0, 74256.0]],      [[74257.0, 74258.0, 74259.0, 74260.0, 74261.0, 74262.0],       [74263.0, 74264.0, 74265.0, 74266.0, 74267.0, 74268.0],       [74269.0, 74270.0, 74271.0, 74272.0, 74273.0, 74274.0],       [74275.0, 74276.0, 74277.0, 74278.0, 74279.0, 74280.0],       [74281.0, 74282.0, 74283.0, 74284.0, 74285.0, 74286.0],       [74287.0, 74288.0, 74289.0, 74290.0, 74291.0, 74292.0],       [74293.0, 74294.0, 74295.0, 74296.0, 74297.0, 74298.0]],      [[74299.0, 74300.0, 74301.0, 74302.0, 74303.0, 74304.0],       [74305.0, 74306.0, 74307.0, 74308.0, 74309.0, 74310.0],       [74311.0, 74312.0, 74313.0, 74314.0, 74315.0, 74316.0],       [74317.0, 74318.0, 74319.0, 74320.0, 74321.0, 74322.0],       [74323.0, 74324.0, 74325.0, 74326.0, 74327.0, 74328.0],       [74329.0, 74330.0, 74331.0, 74332.0, 74333.0, 74334.0],       [74335.0, 74336.0, 74337.0, 74338.0, 74339.0, 74340.0]]],     [[[74341.0, 74342.0, 74343.0, 74344.0, 74345.0, 74346.0],       [74347.0, 74348.0, 74349.0, 74350.0, 74351.0, 74352.0],       [74353.0, 74354.0, 74355.0, 74356.0, 74357.0, 74358.0],       [74359.0, 74360.0, 74361.0, 74362.0, 74363.0, 74364.0],       [74365.0, 74366.0, 74367.0, 74368.0, 74369.0, 74370.0],       [74371.0, 74372.0, 74373.0, 74374.0, 74375.0, 74376.0],       [74377.0, 74378.0, 74379.0, 74380.0, 74381.0, 74382.0]],      [[74383.0, 74384.0, 74385.0, 74386.0, 74387.0, 74388.0],       [74389.0, 74390.0, 74391.0, 74392.0, 74393.0, 74394.0],       [74395.0, 74396.0, 74397.0, 74398.0, 74399.0, 74400.0],       [74401.0, 74402.0, 74403.0, 74404.0, 74405.0, 74406.0],       [74407.0, 74408.0, 74409.0, 74410.0, 74411.0, 74412.0],       [74413.0, 74414.0, 74415.0, 74416.0, 74417.0, 74418.0],       [74419.0, 74420.0, 74421.0, 74422.0, 74423.0, 74424.0]],      [[74425.0, 74426.0, 74427.0, 74428.0, 74429.0, 74430.0],       [74431.0, 74432.0, 74433.0, 74434.0, 74435.0, 74436.0],       [74437.0, 74438.0, 74439.0, 74440.0, 74441.0, 74442.0],       [74443.0, 74444.0, 74445.0, 74446.0, 74447.0, 74448.0],       [74449.0, 74450.0, 74451.0, 74452.0, 74453.0, 74454.0],       [74455.0, 74456.0, 74457.0, 74458.0, 74459.0, 74460.0],       [74461.0, 74462.0, 74463.0, 74464.0, 74465.0, 74466.0]],      [[74467.0, 74468.0, 74469.0, 74470.0, 74471.0, 74472.0],       [74473.0, 74474.0, 74475.0, 74476.0, 74477.0, 74478.0],       [74479.0, 74480.0, 74481.0, 74482.0, 74483.0, 74484.0],       [74485.0, 74486.0, 74487.0, 74488.0, 74489.0, 74490.0],       [74491.0, 74492.0, 74493.0, 74494.0, 74495.0, 74496.0],       [74497.0, 74498.0, 74499.0, 74500.0, 74501.0, 74502.0],       [74503.0, 74504.0, 74505.0, 74506.0, 74507.0, 74508.0]],      [[74509.0, 74510.0, 74511.0, 74512.0, 74513.0, 74514.0],       [74515.0, 74516.0, 74517.0, 74518.0, 74519.0, 74520.0],       [74521.0, 74522.0, 74523.0, 74524.0, 74525.0, 74526.0],       [74527.0, 74528.0, 74529.0, 74530.0, 74531.0, 74532.0],       [74533.0, 74534.0, 74535.0, 74536.0, 74537.0, 74538.0],       [74539.0, 74540.0, 74541.0, 74542.0, 74543.0, 74544.0],       [74545.0, 74546.0, 74547.0, 74548.0, 74549.0, 74550.0]],      [[74551.0, 74552.0, 74553.0, 74554.0, 74555.0, 74556.0],       [74557.0, 74558.0, 74559.0, 74560.0, 74561.0, 74562.0],       [74563.0, 74564.0, 74565.0, 74566.0, 74567.0, 74568.0],       [74569.0, 74570.0, 74571.0, 74572.0, 74573.0, 74574.0],       [74575.0, 74576.0, 74577.0, 74578.0, 74579.0, 74580.0],       [74581.0, 74582.0, 74583.0, 74584.0, 74585.0, 74586.0],       [74587.0, 74588.0, 74589.0, 74590.0, 74591.0, 74592.0]]]],    [[[[74593.0, 74594.0, 74595.0, 74596.0, 74597.0, 74598.0],       [74599.0, 74600.0, 74601.0, 74602.0, 74603.0, 74604.0],       [74605.0, 74606.0, 74607.0, 74608.0, 74609.0, 74610.0],       [74611.0, 74612.0, 74613.0, 74614.0, 74615.0, 74616.0],       [74617.0, 74618.0, 74619.0, 74620.0, 74621.0, 74622.0],       [74623.0, 74624.0, 74625.0, 74626.0, 74627.0, 74628.0],       [74629.0, 74630.0, 74631.0, 74632.0, 74633.0, 74634.0]],      [[74635.0, 74636.0, 74637.0, 74638.0, 74639.0, 74640.0],       [74641.0, 74642.0, 74643.0, 74644.0, 74645.0, 74646.0],       [74647.0, 74648.0, 74649.0, 74650.0, 74651.0, 74652.0],       [74653.0, 74654.0, 74655.0, 74656.0, 74657.0, 74658.0],       [74659.0, 74660.0, 74661.0, 74662.0, 74663.0, 74664.0],       [74665.0, 74666.0, 74667.0, 74668.0, 74669.0, 74670.0],       [74671.0, 74672.0, 74673.0, 74674.0, 74675.0, 74676.0]],      [[74677.0, 74678.0, 74679.0, 74680.0, 74681.0, 74682.0],       [74683.0, 74684.0, 74685.0, 74686.0, 74687.0, 74688.0],       [74689.0, 74690.0, 74691.0, 74692.0, 74693.0, 74694.0],       [74695.0, 74696.0, 74697.0, 74698.0, 74699.0, 74700.0],       [74701.0, 74702.0, 74703.0, 74704.0, 74705.0, 74706.0],       [74707.0, 74708.0, 74709.0, 74710.0, 74711.0, 74712.0],       [74713.0, 74714.0, 74715.0, 74716.0, 74717.0, 74718.0]],      [[74719.0, 74720.0, 74721.0, 74722.0, 74723.0, 74724.0],       [74725.0, 74726.0, 74727.0, 74728.0, 74729.0, 74730.0],       [74731.0, 74732.0, 74733.0, 74734.0, 74735.0, 74736.0],       [74737.0, 74738.0, 74739.0, 74740.0, 74741.0, 74742.0],       [74743.0, 74744.0, 74745.0, 74746.0, 74747.0, 74748.0],       [74749.0, 74750.0, 74751.0, 74752.0, 74753.0, 74754.0],       [74755.0, 74756.0, 74757.0, 74758.0, 74759.0, 74760.0]],      [[74761.0, 74762.0, 74763.0, 74764.0, 74765.0, 74766.0],       [74767.0, 74768.0, 74769.0, 74770.0, 74771.0, 74772.0],       [74773.0, 74774.0, 74775.0, 74776.0, 74777.0, 74778.0],       [74779.0, 74780.0, 74781.0, 74782.0, 74783.0, 74784.0],       [74785.0, 74786.0, 74787.0, 74788.0, 74789.0, 74790.0],       [74791.0, 74792.0, 74793.0, 74794.0, 74795.0, 74796.0],       [74797.0, 74798.0, 74799.0, 74800.0, 74801.0, 74802.0]],      [[74803.0, 74804.0, 74805.0, 74806.0, 74807.0, 74808.0],       [74809.0, 74810.0, 74811.0, 74812.0, 74813.0, 74814.0],       [74815.0, 74816.0, 74817.0, 74818.0, 74819.0, 74820.0],       [74821.0, 74822.0, 74823.0, 74824.0, 74825.0, 74826.0],       [74827.0, 74828.0, 74829.0, 74830.0, 74831.0, 74832.0],       [74833.0, 74834.0, 74835.0, 74836.0, 74837.0, 74838.0],       [74839.0, 74840.0, 74841.0, 74842.0, 74843.0, 74844.0]]],     [[[74845.0, 74846.0, 74847.0, 74848.0, 74849.0, 74850.0],       [74851.0, 74852.0, 74853.0, 74854.0, 74855.0, 74856.0],       [74857.0, 74858.0, 74859.0, 74860.0, 74861.0, 74862.0],       [74863.0, 74864.0, 74865.0, 74866.0, 74867.0, 74868.0],       [74869.0, 74870.0, 74871.0, 74872.0, 74873.0, 74874.0],       [74875.0, 74876.0, 74877.0, 74878.0, 74879.0, 74880.0],       [74881.0, 74882.0, 74883.0, 74884.0, 74885.0, 74886.0]],      [[74887.0, 74888.0, 74889.0, 74890.0, 74891.0, 74892.0],       [74893.0, 74894.0, 74895.0, 74896.0, 74897.0, 74898.0],       [74899.0, 74900.0, 74901.0, 74902.0, 74903.0, 74904.0],       [74905.0, 74906.0, 74907.0, 74908.0, 74909.0, 74910.0],       [74911.0, 74912.0, 74913.0, 74914.0, 74915.0, 74916.0],       [74917.0, 74918.0, 74919.0, 74920.0, 74921.0, 74922.0],       [74923.0, 74924.0, 74925.0, 74926.0, 74927.0, 74928.0]],      [[74929.0, 74930.0, 74931.0, 74932.0, 74933.0, 74934.0],       [74935.0, 74936.0, 74937.0, 74938.0, 74939.0, 74940.0],       [74941.0, 74942.0, 74943.0, 74944.0, 74945.0, 74946.0],       [74947.0, 74948.0, 74949.0, 74950.0, 74951.0, 74952.0],       [74953.0, 74954.0, 74955.0, 74956.0, 74957.0, 74958.0],       [74959.0, 74960.0, 74961.0, 74962.0, 74963.0, 74964.0],       [74965.0, 74966.0, 74967.0, 74968.0, 74969.0, 74970.0]],      [[74971.0, 74972.0, 74973.0, 74974.0, 74975.0, 74976.0],       [74977.0, 74978.0, 74979.0, 74980.0, 74981.0, 74982.0],       [74983.0, 74984.0, 74985.0, 74986.0, 74987.0, 74988.0],       [74989.0, 74990.0, 74991.0, 74992.0, 74993.0, 74994.0],       [74995.0, 74996.0, 74997.0, 74998.0, 74999.0, 75000.0],       [75001.0, 75002.0, 75003.0, 75004.0, 75005.0, 75006.0],       [75007.0, 75008.0, 75009.0, 75010.0, 75011.0, 75012.0]],      [[75013.0, 75014.0, 75015.0, 75016.0, 75017.0, 75018.0],       [75019.0, 75020.0, 75021.0, 75022.0, 75023.0, 75024.0],       [75025.0, 75026.0, 75027.0, 75028.0, 75029.0, 75030.0],       [75031.0, 75032.0, 75033.0, 75034.0, 75035.0, 75036.0],       [75037.0, 75038.0, 75039.0, 75040.0, 75041.0, 75042.0],       [75043.0, 75044.0, 75045.0, 75046.0, 75047.0, 75048.0],       [75049.0, 75050.0, 75051.0, 75052.0, 75053.0, 75054.0]],      [[75055.0, 75056.0, 75057.0, 75058.0, 75059.0, 75060.0],       [75061.0, 75062.0, 75063.0, 75064.0, 75065.0, 75066.0],       [75067.0, 75068.0, 75069.0, 75070.0, 75071.0, 75072.0],       [75073.0, 75074.0, 75075.0, 75076.0, 75077.0, 75078.0],       [75079.0, 75080.0, 75081.0, 75082.0, 75083.0, 75084.0],       [75085.0, 75086.0, 75087.0, 75088.0, 75089.0, 75090.0],       [75091.0, 75092.0, 75093.0, 75094.0, 75095.0, 75096.0]]],     [[[75097.0, 75098.0, 75099.0, 75100.0, 75101.0, 75102.0],       [75103.0, 75104.0, 75105.0, 75106.0, 75107.0, 75108.0],       [75109.0, 75110.0, 75111.0, 75112.0, 75113.0, 75114.0],       [75115.0, 75116.0, 75117.0, 75118.0, 75119.0, 75120.0],       [75121.0, 75122.0, 75123.0, 75124.0, 75125.0, 75126.0],       [75127.0, 75128.0, 75129.0, 75130.0, 75131.0, 75132.0],       [75133.0, 75134.0, 75135.0, 75136.0, 75137.0, 75138.0]],      [[75139.0, 75140.0, 75141.0, 75142.0, 75143.0, 75144.0],       [75145.0, 75146.0, 75147.0, 75148.0, 75149.0, 75150.0],       [75151.0, 75152.0, 75153.0, 75154.0, 75155.0, 75156.0],       [75157.0, 75158.0, 75159.0, 75160.0, 75161.0, 75162.0],       [75163.0, 75164.0, 75165.0, 75166.0, 75167.0, 75168.0],       [75169.0, 75170.0, 75171.0, 75172.0, 75173.0, 75174.0],       [75175.0, 75176.0, 75177.0, 75178.0, 75179.0, 75180.0]],      [[75181.0, 75182.0, 75183.0, 75184.0, 75185.0, 75186.0],       [75187.0, 75188.0, 75189.0, 75190.0, 75191.0, 75192.0],       [75193.0, 75194.0, 75195.0, 75196.0, 75197.0, 75198.0],       [75199.0, 75200.0, 75201.0, 75202.0, 75203.0, 75204.0],       [75205.0, 75206.0, 75207.0, 75208.0, 75209.0, 75210.0],       [75211.0, 75212.0, 75213.0, 75214.0, 75215.0, 75216.0],       [75217.0, 75218.0, 75219.0, 75220.0, 75221.0, 75222.0]],      [[75223.0, 75224.0, 75225.0, 75226.0, 75227.0, 75228.0],       [75229.0, 75230.0, 75231.0, 75232.0, 75233.0, 75234.0],       [75235.0, 75236.0, 75237.0, 75238.0, 75239.0, 75240.0],       [75241.0, 75242.0, 75243.0, 75244.0, 75245.0, 75246.0],       [75247.0, 75248.0, 75249.0, 75250.0, 75251.0, 75252.0],       [75253.0, 75254.0, 75255.0, 75256.0, 75257.0, 75258.0],       [75259.0, 75260.0, 75261.0, 75262.0, 75263.0, 75264.0]],      [[75265.0, 75266.0, 75267.0, 75268.0, 75269.0, 75270.0],       [75271.0, 75272.0, 75273.0, 75274.0, 75275.0, 75276.0],       [75277.0, 75278.0, 75279.0, 75280.0, 75281.0, 75282.0],       [75283.0, 75284.0, 75285.0, 75286.0, 75287.0, 75288.0],       [75289.0, 75290.0, 75291.0, 75292.0, 75293.0, 75294.0],       [75295.0, 75296.0, 75297.0, 75298.0, 75299.0, 75300.0],       [75301.0, 75302.0, 75303.0, 75304.0, 75305.0, 75306.0]],      [[75307.0, 75308.0, 75309.0, 75310.0, 75311.0, 75312.0],       [75313.0, 75314.0, 75315.0, 75316.0, 75317.0, 75318.0],       [75319.0, 75320.0, 75321.0, 75322.0, 75323.0, 75324.0],       [75325.0, 75326.0, 75327.0, 75328.0, 75329.0, 75330.0],       [75331.0, 75332.0, 75333.0, 75334.0, 75335.0, 75336.0],       [75337.0, 75338.0, 75339.0, 75340.0, 75341.0, 75342.0],       [75343.0, 75344.0, 75345.0, 75346.0, 75347.0, 75348.0]]],     [[[75349.0, 75350.0, 75351.0, 75352.0, 75353.0, 75354.0],       [75355.0, 75356.0, 75357.0, 75358.0, 75359.0, 75360.0],       [75361.0, 75362.0, 75363.0, 75364.0, 75365.0, 75366.0],       [75367.0, 75368.0, 75369.0, 75370.0, 75371.0, 75372.0],       [75373.0, 75374.0, 75375.0, 75376.0, 75377.0, 75378.0],       [75379.0, 75380.0, 75381.0, 75382.0, 75383.0, 75384.0],       [75385.0, 75386.0, 75387.0, 75388.0, 75389.0, 75390.0]],      [[75391.0, 75392.0, 75393.0, 75394.0, 75395.0, 75396.0],       [75397.0, 75398.0, 75399.0, 75400.0, 75401.0, 75402.0],       [75403.0, 75404.0, 75405.0, 75406.0, 75407.0, 75408.0],       [75409.0, 75410.0, 75411.0, 75412.0, 75413.0, 75414.0],       [75415.0, 75416.0, 75417.0, 75418.0, 75419.0, 75420.0],       [75421.0, 75422.0, 75423.0, 75424.0, 75425.0, 75426.0],       [75427.0, 75428.0, 75429.0, 75430.0, 75431.0, 75432.0]],      [[75433.0, 75434.0, 75435.0, 75436.0, 75437.0, 75438.0],       [75439.0, 75440.0, 75441.0, 75442.0, 75443.0, 75444.0],       [75445.0, 75446.0, 75447.0, 75448.0, 75449.0, 75450.0],       [75451.0, 75452.0, 75453.0, 75454.0, 75455.0, 75456.0],       [75457.0, 75458.0, 75459.0, 75460.0, 75461.0, 75462.0],       [75463.0, 75464.0, 75465.0, 75466.0, 75467.0, 75468.0],       [75469.0, 75470.0, 75471.0, 75472.0, 75473.0, 75474.0]],      [[75475.0, 75476.0, 75477.0, 75478.0, 75479.0, 75480.0],       [75481.0, 75482.0, 75483.0, 75484.0, 75485.0, 75486.0],       [75487.0, 75488.0, 75489.0, 75490.0, 75491.0, 75492.0],       [75493.0, 75494.0, 75495.0, 75496.0, 75497.0, 75498.0],       [75499.0, 75500.0, 75501.0, 75502.0, 75503.0, 75504.0],       [75505.0, 75506.0, 75507.0, 75508.0, 75509.0, 75510.0],       [75511.0, 75512.0, 75513.0, 75514.0, 75515.0, 75516.0]],      [[75517.0, 75518.0, 75519.0, 75520.0, 75521.0, 75522.0],       [75523.0, 75524.0, 75525.0, 75526.0, 75527.0, 75528.0],       [75529.0, 75530.0, 75531.0, 75532.0, 75533.0, 75534.0],       [75535.0, 75536.0, 75537.0, 75538.0, 75539.0, 75540.0],       [75541.0, 75542.0, 75543.0, 75544.0, 75545.0, 75546.0],       [75547.0, 75548.0, 75549.0, 75550.0, 75551.0, 75552.0],       [75553.0, 75554.0, 75555.0, 75556.0, 75557.0, 75558.0]],      [[75559.0, 75560.0, 75561.0, 75562.0, 75563.0, 75564.0],       [75565.0, 75566.0, 75567.0, 75568.0, 75569.0, 75570.0],       [75571.0, 75572.0, 75573.0, 75574.0, 75575.0, 75576.0],       [75577.0, 75578.0, 75579.0, 75580.0, 75581.0, 75582.0],       [75583.0, 75584.0, 75585.0, 75586.0, 75587.0, 75588.0],       [75589.0, 75590.0, 75591.0, 75592.0, 75593.0, 75594.0],       [75595.0, 75596.0, 75597.0, 75598.0, 75599.0, 75600.0]]]],    [[[[75601.0, 75602.0, 75603.0, 75604.0, 75605.0, 75606.0],       [75607.0, 75608.0, 75609.0, 75610.0, 75611.0, 75612.0],       [75613.0, 75614.0, 75615.0, 75616.0, 75617.0, 75618.0],       [75619.0, 75620.0, 75621.0, 75622.0, 75623.0, 75624.0],       [75625.0, 75626.0, 75627.0, 75628.0, 75629.0, 75630.0],       [75631.0, 75632.0, 75633.0, 75634.0, 75635.0, 75636.0],       [75637.0, 75638.0, 75639.0, 75640.0, 75641.0, 75642.0]],      [[75643.0, 75644.0, 75645.0, 75646.0, 75647.0, 75648.0],       [75649.0, 75650.0, 75651.0, 75652.0, 75653.0, 75654.0],       [75655.0, 75656.0, 75657.0, 75658.0, 75659.0, 75660.0],       [75661.0, 75662.0, 75663.0, 75664.0, 75665.0, 75666.0],       [75667.0, 75668.0, 75669.0, 75670.0, 75671.0, 75672.0],       [75673.0, 75674.0, 75675.0, 75676.0, 75677.0, 75678.0],       [75679.0, 75680.0, 75681.0, 75682.0, 75683.0, 75684.0]],      [[75685.0, 75686.0, 75687.0, 75688.0, 75689.0, 75690.0],       [75691.0, 75692.0, 75693.0, 75694.0, 75695.0, 75696.0],       [75697.0, 75698.0, 75699.0, 75700.0, 75701.0, 75702.0],       [75703.0, 75704.0, 75705.0, 75706.0, 75707.0, 75708.0],       [75709.0, 75710.0, 75711.0, 75712.0, 75713.0, 75714.0],       [75715.0, 75716.0, 75717.0, 75718.0, 75719.0, 75720.0],       [75721.0, 75722.0, 75723.0, 75724.0, 75725.0, 75726.0]],      [[75727.0, 75728.0, 75729.0, 75730.0, 75731.0, 75732.0],       [75733.0, 75734.0, 75735.0, 75736.0, 75737.0, 75738.0],       [75739.0, 75740.0, 75741.0, 75742.0, 75743.0, 75744.0],       [75745.0, 75746.0, 75747.0, 75748.0, 75749.0, 75750.0],       [75751.0, 75752.0, 75753.0, 75754.0, 75755.0, 75756.0],       [75757.0, 75758.0, 75759.0, 75760.0, 75761.0, 75762.0],       [75763.0, 75764.0, 75765.0, 75766.0, 75767.0, 75768.0]],      [[75769.0, 75770.0, 75771.0, 75772.0, 75773.0, 75774.0],       [75775.0, 75776.0, 75777.0, 75778.0, 75779.0, 75780.0],       [75781.0, 75782.0, 75783.0, 75784.0, 75785.0, 75786.0],       [75787.0, 75788.0, 75789.0, 75790.0, 75791.0, 75792.0],       [75793.0, 75794.0, 75795.0, 75796.0, 75797.0, 75798.0],       [75799.0, 75800.0, 75801.0, 75802.0, 75803.0, 75804.0],       [75805.0, 75806.0, 75807.0, 75808.0, 75809.0, 75810.0]],      [[75811.0, 75812.0, 75813.0, 75814.0, 75815.0, 75816.0],       [75817.0, 75818.0, 75819.0, 75820.0, 75821.0, 75822.0],       [75823.0, 75824.0, 75825.0, 75826.0, 75827.0, 75828.0],       [75829.0, 75830.0, 75831.0, 75832.0, 75833.0, 75834.0],       [75835.0, 75836.0, 75837.0, 75838.0, 75839.0, 75840.0],       [75841.0, 75842.0, 75843.0, 75844.0, 75845.0, 75846.0],       [75847.0, 75848.0, 75849.0, 75850.0, 75851.0, 75852.0]]],     [[[75853.0, 75854.0, 75855.0, 75856.0, 75857.0, 75858.0],       [75859.0, 75860.0, 75861.0, 75862.0, 75863.0, 75864.0],       [75865.0, 75866.0, 75867.0, 75868.0, 75869.0, 75870.0],       [75871.0, 75872.0, 75873.0, 75874.0, 75875.0, 75876.0],       [75877.0, 75878.0, 75879.0, 75880.0, 75881.0, 75882.0],       [75883.0, 75884.0, 75885.0, 75886.0, 75887.0, 75888.0],       [75889.0, 75890.0, 75891.0, 75892.0, 75893.0, 75894.0]],      [[75895.0, 75896.0, 75897.0, 75898.0, 75899.0, 75900.0],       [75901.0, 75902.0, 75903.0, 75904.0, 75905.0, 75906.0],       [75907.0, 75908.0, 75909.0, 75910.0, 75911.0, 75912.0],       [75913.0, 75914.0, 75915.0, 75916.0, 75917.0, 75918.0],       [75919.0, 75920.0, 75921.0, 75922.0, 75923.0, 75924.0],       [75925.0, 75926.0, 75927.0, 75928.0, 75929.0, 75930.0],       [75931.0, 75932.0, 75933.0, 75934.0, 75935.0, 75936.0]],      [[75937.0, 75938.0, 75939.0, 75940.0, 75941.0, 75942.0],       [75943.0, 75944.0, 75945.0, 75946.0, 75947.0, 75948.0],       [75949.0, 75950.0, 75951.0, 75952.0, 75953.0, 75954.0],       [75955.0, 75956.0, 75957.0, 75958.0, 75959.0, 75960.0],       [75961.0, 75962.0, 75963.0, 75964.0, 75965.0, 75966.0],       [75967.0, 75968.0, 75969.0, 75970.0, 75971.0, 75972.0],       [75973.0, 75974.0, 75975.0, 75976.0, 75977.0, 75978.0]],      [[75979.0, 75980.0, 75981.0, 75982.0, 75983.0, 75984.0],       [75985.0, 75986.0, 75987.0, 75988.0, 75989.0, 75990.0],       [75991.0, 75992.0, 75993.0, 75994.0, 75995.0, 75996.0],       [75997.0, 75998.0, 75999.0, 76000.0, 76001.0, 76002.0],       [76003.0, 76004.0, 76005.0, 76006.0, 76007.0, 76008.0],       [76009.0, 76010.0, 76011.0, 76012.0, 76013.0, 76014.0],       [76015.0, 76016.0, 76017.0, 76018.0, 76019.0, 76020.0]],      [[76021.0, 76022.0, 76023.0, 76024.0, 76025.0, 76026.0],       [76027.0, 76028.0, 76029.0, 76030.0, 76031.0, 76032.0],       [76033.0, 76034.0, 76035.0, 76036.0, 76037.0, 76038.0],       [76039.0, 76040.0, 76041.0, 76042.0, 76043.0, 76044.0],       [76045.0, 76046.0, 76047.0, 76048.0, 76049.0, 76050.0],       [76051.0, 76052.0, 76053.0, 76054.0, 76055.0, 76056.0],       [76057.0, 76058.0, 76059.0, 76060.0, 76061.0, 76062.0]],      [[76063.0, 76064.0, 76065.0, 76066.0, 76067.0, 76068.0],       [76069.0, 76070.0, 76071.0, 76072.0, 76073.0, 76074.0],       [76075.0, 76076.0, 76077.0, 76078.0, 76079.0, 76080.0],       [76081.0, 76082.0, 76083.0, 76084.0, 76085.0, 76086.0],       [76087.0, 76088.0, 76089.0, 76090.0, 76091.0, 76092.0],       [76093.0, 76094.0, 76095.0, 76096.0, 76097.0, 76098.0],       [76099.0, 76100.0, 76101.0, 76102.0, 76103.0, 76104.0]]],     [[[76105.0, 76106.0, 76107.0, 76108.0, 76109.0, 76110.0],       [76111.0, 76112.0, 76113.0, 76114.0, 76115.0, 76116.0],       [76117.0, 76118.0, 76119.0, 76120.0, 76121.0, 76122.0],       [76123.0, 76124.0, 76125.0, 76126.0, 76127.0, 76128.0],       [76129.0, 76130.0, 76131.0, 76132.0, 76133.0, 76134.0],       [76135.0, 76136.0, 76137.0, 76138.0, 76139.0, 76140.0],       [76141.0, 76142.0, 76143.0, 76144.0, 76145.0, 76146.0]],      [[76147.0, 76148.0, 76149.0, 76150.0, 76151.0, 76152.0],       [76153.0, 76154.0, 76155.0, 76156.0, 76157.0, 76158.0],       [76159.0, 76160.0, 76161.0, 76162.0, 76163.0, 76164.0],       [76165.0, 76166.0, 76167.0, 76168.0, 76169.0, 76170.0],       [76171.0, 76172.0, 76173.0, 76174.0, 76175.0, 76176.0],       [76177.0, 76178.0, 76179.0, 76180.0, 76181.0, 76182.0],       [76183.0, 76184.0, 76185.0, 76186.0, 76187.0, 76188.0]],      [[76189.0, 76190.0, 76191.0, 76192.0, 76193.0, 76194.0],       [76195.0, 76196.0, 76197.0, 76198.0, 76199.0, 76200.0],       [76201.0, 76202.0, 76203.0, 76204.0, 76205.0, 76206.0],       [76207.0, 76208.0, 76209.0, 76210.0, 76211.0, 76212.0],       [76213.0, 76214.0, 76215.0, 76216.0, 76217.0, 76218.0],       [76219.0, 76220.0, 76221.0, 76222.0, 76223.0, 76224.0],       [76225.0, 76226.0, 76227.0, 76228.0, 76229.0, 76230.0]],      [[76231.0, 76232.0, 76233.0, 76234.0, 76235.0, 76236.0],       [76237.0, 76238.0, 76239.0, 76240.0, 76241.0, 76242.0],       [76243.0, 76244.0, 76245.0, 76246.0, 76247.0, 76248.0],       [76249.0, 76250.0, 76251.0, 76252.0, 76253.0, 76254.0],       [76255.0, 76256.0, 76257.0, 76258.0, 76259.0, 76260.0],       [76261.0, 76262.0, 76263.0, 76264.0, 76265.0, 76266.0],       [76267.0, 76268.0, 76269.0, 76270.0, 76271.0, 76272.0]],      [[76273.0, 76274.0, 76275.0, 76276.0, 76277.0, 76278.0],       [76279.0, 76280.0, 76281.0, 76282.0, 76283.0, 76284.0],       [76285.0, 76286.0, 76287.0, 76288.0, 76289.0, 76290.0],       [76291.0, 76292.0, 76293.0, 76294.0, 76295.0, 76296.0],       [76297.0, 76298.0, 76299.0, 76300.0, 76301.0, 76302.0],       [76303.0, 76304.0, 76305.0, 76306.0, 76307.0, 76308.0],       [76309.0, 76310.0, 76311.0, 76312.0, 76313.0, 76314.0]],      [[76315.0, 76316.0, 76317.0, 76318.0, 76319.0, 76320.0],       [76321.0, 76322.0, 76323.0, 76324.0, 76325.0, 76326.0],       [76327.0, 76328.0, 76329.0, 76330.0, 76331.0, 76332.0],       [76333.0, 76334.0, 76335.0, 76336.0, 76337.0, 76338.0],       [76339.0, 76340.0, 76341.0, 76342.0, 76343.0, 76344.0],       [76345.0, 76346.0, 76347.0, 76348.0, 76349.0, 76350.0],       [76351.0, 76352.0, 76353.0, 76354.0, 76355.0, 76356.0]]],     [[[76357.0, 76358.0, 76359.0, 76360.0, 76361.0, 76362.0],       [76363.0, 76364.0, 76365.0, 76366.0, 76367.0, 76368.0],       [76369.0, 76370.0, 76371.0, 76372.0, 76373.0, 76374.0],       [76375.0, 76376.0, 76377.0, 76378.0, 76379.0, 76380.0],       [76381.0, 76382.0, 76383.0, 76384.0, 76385.0, 76386.0],       [76387.0, 76388.0, 76389.0, 76390.0, 76391.0, 76392.0],       [76393.0, 76394.0, 76395.0, 76396.0, 76397.0, 76398.0]],      [[76399.0, 76400.0, 76401.0, 76402.0, 76403.0, 76404.0],       [76405.0, 76406.0, 76407.0, 76408.0, 76409.0, 76410.0],       [76411.0, 76412.0, 76413.0, 76414.0, 76415.0, 76416.0],       [76417.0, 76418.0, 76419.0, 76420.0, 76421.0, 76422.0],       [76423.0, 76424.0, 76425.0, 76426.0, 76427.0, 76428.0],       [76429.0, 76430.0, 76431.0, 76432.0, 76433.0, 76434.0],       [76435.0, 76436.0, 76437.0, 76438.0, 76439.0, 76440.0]],      [[76441.0, 76442.0, 76443.0, 76444.0, 76445.0, 76446.0],       [76447.0, 76448.0, 76449.0, 76450.0, 76451.0, 76452.0],       [76453.0, 76454.0, 76455.0, 76456.0, 76457.0, 76458.0],       [76459.0, 76460.0, 76461.0, 76462.0, 76463.0, 76464.0],       [76465.0, 76466.0, 76467.0, 76468.0, 76469.0, 76470.0],       [76471.0, 76472.0, 76473.0, 76474.0, 76475.0, 76476.0],       [76477.0, 76478.0, 76479.0, 76480.0, 76481.0, 76482.0]],      [[76483.0, 76484.0, 76485.0, 76486.0, 76487.0, 76488.0],       [76489.0, 76490.0, 76491.0, 76492.0, 76493.0, 76494.0],       [76495.0, 76496.0, 76497.0, 76498.0, 76499.0, 76500.0],       [76501.0, 76502.0, 76503.0, 76504.0, 76505.0, 76506.0],       [76507.0, 76508.0, 76509.0, 76510.0, 76511.0, 76512.0],       [76513.0, 76514.0, 76515.0, 76516.0, 76517.0, 76518.0],       [76519.0, 76520.0, 76521.0, 76522.0, 76523.0, 76524.0]],      [[76525.0, 76526.0, 76527.0, 76528.0, 76529.0, 76530.0],       [76531.0, 76532.0, 76533.0, 76534.0, 76535.0, 76536.0],       [76537.0, 76538.0, 76539.0, 76540.0, 76541.0, 76542.0],       [76543.0, 76544.0, 76545.0, 76546.0, 76547.0, 76548.0],       [76549.0, 76550.0, 76551.0, 76552.0, 76553.0, 76554.0],       [76555.0, 76556.0, 76557.0, 76558.0, 76559.0, 76560.0],       [76561.0, 76562.0, 76563.0, 76564.0, 76565.0, 76566.0]],      [[76567.0, 76568.0, 76569.0, 76570.0, 76571.0, 76572.0],       [76573.0, 76574.0, 76575.0, 76576.0, 76577.0, 76578.0],       [76579.0, 76580.0, 76581.0, 76582.0, 76583.0, 76584.0],       [76585.0, 76586.0, 76587.0, 76588.0, 76589.0, 76590.0],       [76591.0, 76592.0, 76593.0, 76594.0, 76595.0, 76596.0],       [76597.0, 76598.0, 76599.0, 76600.0, 76601.0, 76602.0],       [76603.0, 76604.0, 76605.0, 76606.0, 76607.0, 76608.0]]]],    [[[[76609.0, 76610.0, 76611.0, 76612.0, 76613.0, 76614.0],       [76615.0, 76616.0, 76617.0, 76618.0, 76619.0, 76620.0],       [76621.0, 76622.0, 76623.0, 76624.0, 76625.0, 76626.0],       [76627.0, 76628.0, 76629.0, 76630.0, 76631.0, 76632.0],       [76633.0, 76634.0, 76635.0, 76636.0, 76637.0, 76638.0],       [76639.0, 76640.0, 76641.0, 76642.0, 76643.0, 76644.0],       [76645.0, 76646.0, 76647.0, 76648.0, 76649.0, 76650.0]],      [[76651.0, 76652.0, 76653.0, 76654.0, 76655.0, 76656.0],       [76657.0, 76658.0, 76659.0, 76660.0, 76661.0, 76662.0],       [76663.0, 76664.0, 76665.0, 76666.0, 76667.0, 76668.0],       [76669.0, 76670.0, 76671.0, 76672.0, 76673.0, 76674.0],       [76675.0, 76676.0, 76677.0, 76678.0, 76679.0, 76680.0],       [76681.0, 76682.0, 76683.0, 76684.0, 76685.0, 76686.0],       [76687.0, 76688.0, 76689.0, 76690.0, 76691.0, 76692.0]],      [[76693.0, 76694.0, 76695.0, 76696.0, 76697.0, 76698.0],       [76699.0, 76700.0, 76701.0, 76702.0, 76703.0, 76704.0],       [76705.0, 76706.0, 76707.0, 76708.0, 76709.0, 76710.0],       [76711.0, 76712.0, 76713.0, 76714.0, 76715.0, 76716.0],       [76717.0, 76718.0, 76719.0, 76720.0, 76721.0, 76722.0],       [76723.0, 76724.0, 76725.0, 76726.0, 76727.0, 76728.0],       [76729.0, 76730.0, 76731.0, 76732.0, 76733.0, 76734.0]],      [[76735.0, 76736.0, 76737.0, 76738.0, 76739.0, 76740.0],       [76741.0, 76742.0, 76743.0, 76744.0, 76745.0, 76746.0],       [76747.0, 76748.0, 76749.0, 76750.0, 76751.0, 76752.0],       [76753.0, 76754.0, 76755.0, 76756.0, 76757.0, 76758.0],       [76759.0, 76760.0, 76761.0, 76762.0, 76763.0, 76764.0],       [76765.0, 76766.0, 76767.0, 76768.0, 76769.0, 76770.0],       [76771.0, 76772.0, 76773.0, 76774.0, 76775.0, 76776.0]],      [[76777.0, 76778.0, 76779.0, 76780.0, 76781.0, 76782.0],       [76783.0, 76784.0, 76785.0, 76786.0, 76787.0, 76788.0],       [76789.0, 76790.0, 76791.0, 76792.0, 76793.0, 76794.0],       [76795.0, 76796.0, 76797.0, 76798.0, 76799.0, 76800.0],       [76801.0, 76802.0, 76803.0, 76804.0, 76805.0, 76806.0],       [76807.0, 76808.0, 76809.0, 76810.0, 76811.0, 76812.0],       [76813.0, 76814.0, 76815.0, 76816.0, 76817.0, 76818.0]],      [[76819.0, 76820.0, 76821.0, 76822.0, 76823.0, 76824.0],       [76825.0, 76826.0, 76827.0, 76828.0, 76829.0, 76830.0],       [76831.0, 76832.0, 76833.0, 76834.0, 76835.0, 76836.0],       [76837.0, 76838.0, 76839.0, 76840.0, 76841.0, 76842.0],       [76843.0, 76844.0, 76845.0, 76846.0, 76847.0, 76848.0],       [76849.0, 76850.0, 76851.0, 76852.0, 76853.0, 76854.0],       [76855.0, 76856.0, 76857.0, 76858.0, 76859.0, 76860.0]]],     [[[76861.0, 76862.0, 76863.0, 76864.0, 76865.0, 76866.0],       [76867.0, 76868.0, 76869.0, 76870.0, 76871.0, 76872.0],       [76873.0, 76874.0, 76875.0, 76876.0, 76877.0, 76878.0],       [76879.0, 76880.0, 76881.0, 76882.0, 76883.0, 76884.0],       [76885.0, 76886.0, 76887.0, 76888.0, 76889.0, 76890.0],       [76891.0, 76892.0, 76893.0, 76894.0, 76895.0, 76896.0],       [76897.0, 76898.0, 76899.0, 76900.0, 76901.0, 76902.0]],      [[76903.0, 76904.0, 76905.0, 76906.0, 76907.0, 76908.0],       [76909.0, 76910.0, 76911.0, 76912.0, 76913.0, 76914.0],       [76915.0, 76916.0, 76917.0, 76918.0, 76919.0, 76920.0],       [76921.0, 76922.0, 76923.0, 76924.0, 76925.0, 76926.0],       [76927.0, 76928.0, 76929.0, 76930.0, 76931.0, 76932.0],       [76933.0, 76934.0, 76935.0, 76936.0, 76937.0, 76938.0],       [76939.0, 76940.0, 76941.0, 76942.0, 76943.0, 76944.0]],      [[76945.0, 76946.0, 76947.0, 76948.0, 76949.0, 76950.0],       [76951.0, 76952.0, 76953.0, 76954.0, 76955.0, 76956.0],       [76957.0, 76958.0, 76959.0, 76960.0, 76961.0, 76962.0],       [76963.0, 76964.0, 76965.0, 76966.0, 76967.0, 76968.0],       [76969.0, 76970.0, 76971.0, 76972.0, 76973.0, 76974.0],       [76975.0, 76976.0, 76977.0, 76978.0, 76979.0, 76980.0],       [76981.0, 76982.0, 76983.0, 76984.0, 76985.0, 76986.0]],      [[76987.0, 76988.0, 76989.0, 76990.0, 76991.0, 76992.0],       [76993.0, 76994.0, 76995.0, 76996.0, 76997.0, 76998.0],       [76999.0, 77000.0, 77001.0, 77002.0, 77003.0, 77004.0],       [77005.0, 77006.0, 77007.0, 77008.0, 77009.0, 77010.0],       [77011.0, 77012.0, 77013.0, 77014.0, 77015.0, 77016.0],       [77017.0, 77018.0, 77019.0, 77020.0, 77021.0, 77022.0],       [77023.0, 77024.0, 77025.0, 77026.0, 77027.0, 77028.0]],      [[77029.0, 77030.0, 77031.0, 77032.0, 77033.0, 77034.0],       [77035.0, 77036.0, 77037.0, 77038.0, 77039.0, 77040.0],       [77041.0, 77042.0, 77043.0, 77044.0, 77045.0, 77046.0],       [77047.0, 77048.0, 77049.0, 77050.0, 77051.0, 77052.0],       [77053.0, 77054.0, 77055.0, 77056.0, 77057.0, 77058.0],       [77059.0, 77060.0, 77061.0, 77062.0, 77063.0, 77064.0],       [77065.0, 77066.0, 77067.0, 77068.0, 77069.0, 77070.0]],      [[77071.0, 77072.0, 77073.0, 77074.0, 77075.0, 77076.0],       [77077.0, 77078.0, 77079.0, 77080.0, 77081.0, 77082.0],       [77083.0, 77084.0, 77085.0, 77086.0, 77087.0, 77088.0],       [77089.0, 77090.0, 77091.0, 77092.0, 77093.0, 77094.0],       [77095.0, 77096.0, 77097.0, 77098.0, 77099.0, 77100.0],       [77101.0, 77102.0, 77103.0, 77104.0, 77105.0, 77106.0],       [77107.0, 77108.0, 77109.0, 77110.0, 77111.0, 77112.0]]],     [[[77113.0, 77114.0, 77115.0, 77116.0, 77117.0, 77118.0],       [77119.0, 77120.0, 77121.0, 77122.0, 77123.0, 77124.0],       [77125.0, 77126.0, 77127.0, 77128.0, 77129.0, 77130.0],       [77131.0, 77132.0, 77133.0, 77134.0, 77135.0, 77136.0],       [77137.0, 77138.0, 77139.0, 77140.0, 77141.0, 77142.0],       [77143.0, 77144.0, 77145.0, 77146.0, 77147.0, 77148.0],       [77149.0, 77150.0, 77151.0, 77152.0, 77153.0, 77154.0]],      [[77155.0, 77156.0, 77157.0, 77158.0, 77159.0, 77160.0],       [77161.0, 77162.0, 77163.0, 77164.0, 77165.0, 77166.0],       [77167.0, 77168.0, 77169.0, 77170.0, 77171.0, 77172.0],       [77173.0, 77174.0, 77175.0, 77176.0, 77177.0, 77178.0],       [77179.0, 77180.0, 77181.0, 77182.0, 77183.0, 77184.0],       [77185.0, 77186.0, 77187.0, 77188.0, 77189.0, 77190.0],       [77191.0, 77192.0, 77193.0, 77194.0, 77195.0, 77196.0]],      [[77197.0, 77198.0, 77199.0, 77200.0, 77201.0, 77202.0],       [77203.0, 77204.0, 77205.0, 77206.0, 77207.0, 77208.0],       [77209.0, 77210.0, 77211.0, 77212.0, 77213.0, 77214.0],       [77215.0, 77216.0, 77217.0, 77218.0, 77219.0, 77220.0],       [77221.0, 77222.0, 77223.0, 77224.0, 77225.0, 77226.0],       [77227.0, 77228.0, 77229.0, 77230.0, 77231.0, 77232.0],       [77233.0, 77234.0, 77235.0, 77236.0, 77237.0, 77238.0]],      [[77239.0, 77240.0, 77241.0, 77242.0, 77243.0, 77244.0],       [77245.0, 77246.0, 77247.0, 77248.0, 77249.0, 77250.0],       [77251.0, 77252.0, 77253.0, 77254.0, 77255.0, 77256.0],       [77257.0, 77258.0, 77259.0, 77260.0, 77261.0, 77262.0],       [77263.0, 77264.0, 77265.0, 77266.0, 77267.0, 77268.0],       [77269.0, 77270.0, 77271.0, 77272.0, 77273.0, 77274.0],       [77275.0, 77276.0, 77277.0, 77278.0, 77279.0, 77280.0]],      [[77281.0, 77282.0, 77283.0, 77284.0, 77285.0, 77286.0],       [77287.0, 77288.0, 77289.0, 77290.0, 77291.0, 77292.0],       [77293.0, 77294.0, 77295.0, 77296.0, 77297.0, 77298.0],       [77299.0, 77300.0, 77301.0, 77302.0, 77303.0, 77304.0],       [77305.0, 77306.0, 77307.0, 77308.0, 77309.0, 77310.0],       [77311.0, 77312.0, 77313.0, 77314.0, 77315.0, 77316.0],       [77317.0, 77318.0, 77319.0, 77320.0, 77321.0, 77322.0]],      [[77323.0, 77324.0, 77325.0, 77326.0, 77327.0, 77328.0],       [77329.0, 77330.0, 77331.0, 77332.0, 77333.0, 77334.0],       [77335.0, 77336.0, 77337.0, 77338.0, 77339.0, 77340.0],       [77341.0, 77342.0, 77343.0, 77344.0, 77345.0, 77346.0],       [77347.0, 77348.0, 77349.0, 77350.0, 77351.0, 77352.0],       [77353.0, 77354.0, 77355.0, 77356.0, 77357.0, 77358.0],       [77359.0, 77360.0, 77361.0, 77362.0, 77363.0, 77364.0]]],     [[[77365.0, 77366.0, 77367.0, 77368.0, 77369.0, 77370.0],       [77371.0, 77372.0, 77373.0, 77374.0, 77375.0, 77376.0],       [77377.0, 77378.0, 77379.0, 77380.0, 77381.0, 77382.0],       [77383.0, 77384.0, 77385.0, 77386.0, 77387.0, 77388.0],       [77389.0, 77390.0, 77391.0, 77392.0, 77393.0, 77394.0],       [77395.0, 77396.0, 77397.0, 77398.0, 77399.0, 77400.0],       [77401.0, 77402.0, 77403.0, 77404.0, 77405.0, 77406.0]],      [[77407.0, 77408.0, 77409.0, 77410.0, 77411.0, 77412.0],       [77413.0, 77414.0, 77415.0, 77416.0, 77417.0, 77418.0],       [77419.0, 77420.0, 77421.0, 77422.0, 77423.0, 77424.0],       [77425.0, 77426.0, 77427.0, 77428.0, 77429.0, 77430.0],       [77431.0, 77432.0, 77433.0, 77434.0, 77435.0, 77436.0],       [77437.0, 77438.0, 77439.0, 77440.0, 77441.0, 77442.0],       [77443.0, 77444.0, 77445.0, 77446.0, 77447.0, 77448.0]],      [[77449.0, 77450.0, 77451.0, 77452.0, 77453.0, 77454.0],       [77455.0, 77456.0, 77457.0, 77458.0, 77459.0, 77460.0],       [77461.0, 77462.0, 77463.0, 77464.0, 77465.0, 77466.0],       [77467.0, 77468.0, 77469.0, 77470.0, 77471.0, 77472.0],       [77473.0, 77474.0, 77475.0, 77476.0, 77477.0, 77478.0],       [77479.0, 77480.0, 77481.0, 77482.0, 77483.0, 77484.0],       [77485.0, 77486.0, 77487.0, 77488.0, 77489.0, 77490.0]],      [[77491.0, 77492.0, 77493.0, 77494.0, 77495.0, 77496.0],       [77497.0, 77498.0, 77499.0, 77500.0, 77501.0, 77502.0],       [77503.0, 77504.0, 77505.0, 77506.0, 77507.0, 77508.0],       [77509.0, 77510.0, 77511.0, 77512.0, 77513.0, 77514.0],       [77515.0, 77516.0, 77517.0, 77518.0, 77519.0, 77520.0],       [77521.0, 77522.0, 77523.0, 77524.0, 77525.0, 77526.0],       [77527.0, 77528.0, 77529.0, 77530.0, 77531.0, 77532.0]],      [[77533.0, 77534.0, 77535.0, 77536.0, 77537.0, 77538.0],       [77539.0, 77540.0, 77541.0, 77542.0, 77543.0, 77544.0],       [77545.0, 77546.0, 77547.0, 77548.0, 77549.0, 77550.0],       [77551.0, 77552.0, 77553.0, 77554.0, 77555.0, 77556.0],       [77557.0, 77558.0, 77559.0, 77560.0, 77561.0, 77562.0],       [77563.0, 77564.0, 77565.0, 77566.0, 77567.0, 77568.0],       [77569.0, 77570.0, 77571.0, 77572.0, 77573.0, 77574.0]],      [[77575.0, 77576.0, 77577.0, 77578.0, 77579.0, 77580.0],       [77581.0, 77582.0, 77583.0, 77584.0, 77585.0, 77586.0],       [77587.0, 77588.0, 77589.0, 77590.0, 77591.0, 77592.0],       [77593.0, 77594.0, 77595.0, 77596.0, 77597.0, 77598.0],       [77599.0, 77600.0, 77601.0, 77602.0, 77603.0, 77604.0],       [77605.0, 77606.0, 77607.0, 77608.0, 77609.0, 77610.0],       [77611.0, 77612.0, 77613.0, 77614.0, 77615.0, 77616.0]]]],    [[[[77617.0, 77618.0, 77619.0, 77620.0, 77621.0, 77622.0],       [77623.0, 77624.0, 77625.0, 77626.0, 77627.0, 77628.0],       [77629.0, 77630.0, 77631.0, 77632.0, 77633.0, 77634.0],       [77635.0, 77636.0, 77637.0, 77638.0, 77639.0, 77640.0],       [77641.0, 77642.0, 77643.0, 77644.0, 77645.0, 77646.0],       [77647.0, 77648.0, 77649.0, 77650.0, 77651.0, 77652.0],       [77653.0, 77654.0, 77655.0, 77656.0, 77657.0, 77658.0]],      [[77659.0, 77660.0, 77661.0, 77662.0, 77663.0, 77664.0],       [77665.0, 77666.0, 77667.0, 77668.0, 77669.0, 77670.0],       [77671.0, 77672.0, 77673.0, 77674.0, 77675.0, 77676.0],       [77677.0, 77678.0, 77679.0, 77680.0, 77681.0, 77682.0],       [77683.0, 77684.0, 77685.0, 77686.0, 77687.0, 77688.0],       [77689.0, 77690.0, 77691.0, 77692.0, 77693.0, 77694.0],       [77695.0, 77696.0, 77697.0, 77698.0, 77699.0, 77700.0]],      [[77701.0, 77702.0, 77703.0, 77704.0, 77705.0, 77706.0],       [77707.0, 77708.0, 77709.0, 77710.0, 77711.0, 77712.0],       [77713.0, 77714.0, 77715.0, 77716.0, 77717.0, 77718.0],       [77719.0, 77720.0, 77721.0, 77722.0, 77723.0, 77724.0],       [77725.0, 77726.0, 77727.0, 77728.0, 77729.0, 77730.0],       [77731.0, 77732.0, 77733.0, 77734.0, 77735.0, 77736.0],       [77737.0, 77738.0, 77739.0, 77740.0, 77741.0, 77742.0]],      [[77743.0, 77744.0, 77745.0, 77746.0, 77747.0, 77748.0],       [77749.0, 77750.0, 77751.0, 77752.0, 77753.0, 77754.0],       [77755.0, 77756.0, 77757.0, 77758.0, 77759.0, 77760.0],       [77761.0, 77762.0, 77763.0, 77764.0, 77765.0, 77766.0],       [77767.0, 77768.0, 77769.0, 77770.0, 77771.0, 77772.0],       [77773.0, 77774.0, 77775.0, 77776.0, 77777.0, 77778.0],       [77779.0, 77780.0, 77781.0, 77782.0, 77783.0, 77784.0]],      [[77785.0, 77786.0, 77787.0, 77788.0, 77789.0, 77790.0],       [77791.0, 77792.0, 77793.0, 77794.0, 77795.0, 77796.0],       [77797.0, 77798.0, 77799.0, 77800.0, 77801.0, 77802.0],       [77803.0, 77804.0, 77805.0, 77806.0, 77807.0, 77808.0],       [77809.0, 77810.0, 77811.0, 77812.0, 77813.0, 77814.0],       [77815.0, 77816.0, 77817.0, 77818.0, 77819.0, 77820.0],       [77821.0, 77822.0, 77823.0, 77824.0, 77825.0, 77826.0]],      [[77827.0, 77828.0, 77829.0, 77830.0, 77831.0, 77832.0],       [77833.0, 77834.0, 77835.0, 77836.0, 77837.0, 77838.0],       [77839.0, 77840.0, 77841.0, 77842.0, 77843.0, 77844.0],       [77845.0, 77846.0, 77847.0, 77848.0, 77849.0, 77850.0],       [77851.0, 77852.0, 77853.0, 77854.0, 77855.0, 77856.0],       [77857.0, 77858.0, 77859.0, 77860.0, 77861.0, 77862.0],       [77863.0, 77864.0, 77865.0, 77866.0, 77867.0, 77868.0]]],     [[[77869.0, 77870.0, 77871.0, 77872.0, 77873.0, 77874.0],       [77875.0, 77876.0, 77877.0, 77878.0, 77879.0, 77880.0],       [77881.0, 77882.0, 77883.0, 77884.0, 77885.0, 77886.0],       [77887.0, 77888.0, 77889.0, 77890.0, 77891.0, 77892.0],       [77893.0, 77894.0, 77895.0, 77896.0, 77897.0, 77898.0],       [77899.0, 77900.0, 77901.0, 77902.0, 77903.0, 77904.0],       [77905.0, 77906.0, 77907.0, 77908.0, 77909.0, 77910.0]],      [[77911.0, 77912.0, 77913.0, 77914.0, 77915.0, 77916.0],       [77917.0, 77918.0, 77919.0, 77920.0, 77921.0, 77922.0],       [77923.0, 77924.0, 77925.0, 77926.0, 77927.0, 77928.0],       [77929.0, 77930.0, 77931.0, 77932.0, 77933.0, 77934.0],       [77935.0, 77936.0, 77937.0, 77938.0, 77939.0, 77940.0],       [77941.0, 77942.0, 77943.0, 77944.0, 77945.0, 77946.0],       [77947.0, 77948.0, 77949.0, 77950.0, 77951.0, 77952.0]],      [[77953.0, 77954.0, 77955.0, 77956.0, 77957.0, 77958.0],       [77959.0, 77960.0, 77961.0, 77962.0, 77963.0, 77964.0],       [77965.0, 77966.0, 77967.0, 77968.0, 77969.0, 77970.0],       [77971.0, 77972.0, 77973.0, 77974.0, 77975.0, 77976.0],       [77977.0, 77978.0, 77979.0, 77980.0, 77981.0, 77982.0],       [77983.0, 77984.0, 77985.0, 77986.0, 77987.0, 77988.0],       [77989.0, 77990.0, 77991.0, 77992.0, 77993.0, 77994.0]],      [[77995.0, 77996.0, 77997.0, 77998.0, 77999.0, 78000.0],       [78001.0, 78002.0, 78003.0, 78004.0, 78005.0, 78006.0],       [78007.0, 78008.0, 78009.0, 78010.0, 78011.0, 78012.0],       [78013.0, 78014.0, 78015.0, 78016.0, 78017.0, 78018.0],       [78019.0, 78020.0, 78021.0, 78022.0, 78023.0, 78024.0],       [78025.0, 78026.0, 78027.0, 78028.0, 78029.0, 78030.0],       [78031.0, 78032.0, 78033.0, 78034.0, 78035.0, 78036.0]],      [[78037.0, 78038.0, 78039.0, 78040.0, 78041.0, 78042.0],       [78043.0, 78044.0, 78045.0, 78046.0, 78047.0, 78048.0],       [78049.0, 78050.0, 78051.0, 78052.0, 78053.0, 78054.0],       [78055.0, 78056.0, 78057.0, 78058.0, 78059.0, 78060.0],       [78061.0, 78062.0, 78063.0, 78064.0, 78065.0, 78066.0],       [78067.0, 78068.0, 78069.0, 78070.0, 78071.0, 78072.0],       [78073.0, 78074.0, 78075.0, 78076.0, 78077.0, 78078.0]],      [[78079.0, 78080.0, 78081.0, 78082.0, 78083.0, 78084.0],       [78085.0, 78086.0, 78087.0, 78088.0, 78089.0, 78090.0],       [78091.0, 78092.0, 78093.0, 78094.0, 78095.0, 78096.0],       [78097.0, 78098.0, 78099.0, 78100.0, 78101.0, 78102.0],       [78103.0, 78104.0, 78105.0, 78106.0, 78107.0, 78108.0],       [78109.0, 78110.0, 78111.0, 78112.0, 78113.0, 78114.0],       [78115.0, 78116.0, 78117.0, 78118.0, 78119.0, 78120.0]]],     [[[78121.0, 78122.0, 78123.0, 78124.0, 78125.0, 78126.0],       [78127.0, 78128.0, 78129.0, 78130.0, 78131.0, 78132.0],       [78133.0, 78134.0, 78135.0, 78136.0, 78137.0, 78138.0],       [78139.0, 78140.0, 78141.0, 78142.0, 78143.0, 78144.0],       [78145.0, 78146.0, 78147.0, 78148.0, 78149.0, 78150.0],       [78151.0, 78152.0, 78153.0, 78154.0, 78155.0, 78156.0],       [78157.0, 78158.0, 78159.0, 78160.0, 78161.0, 78162.0]],      [[78163.0, 78164.0, 78165.0, 78166.0, 78167.0, 78168.0],       [78169.0, 78170.0, 78171.0, 78172.0, 78173.0, 78174.0],       [78175.0, 78176.0, 78177.0, 78178.0, 78179.0, 78180.0],       [78181.0, 78182.0, 78183.0, 78184.0, 78185.0, 78186.0],       [78187.0, 78188.0, 78189.0, 78190.0, 78191.0, 78192.0],       [78193.0, 78194.0, 78195.0, 78196.0, 78197.0, 78198.0],       [78199.0, 78200.0, 78201.0, 78202.0, 78203.0, 78204.0]],      [[78205.0, 78206.0, 78207.0, 78208.0, 78209.0, 78210.0],       [78211.0, 78212.0, 78213.0, 78214.0, 78215.0, 78216.0],       [78217.0, 78218.0, 78219.0, 78220.0, 78221.0, 78222.0],       [78223.0, 78224.0, 78225.0, 78226.0, 78227.0, 78228.0],       [78229.0, 78230.0, 78231.0, 78232.0, 78233.0, 78234.0],       [78235.0, 78236.0, 78237.0, 78238.0, 78239.0, 78240.0],       [78241.0, 78242.0, 78243.0, 78244.0, 78245.0, 78246.0]],      [[78247.0, 78248.0, 78249.0, 78250.0, 78251.0, 78252.0],       [78253.0, 78254.0, 78255.0, 78256.0, 78257.0, 78258.0],       [78259.0, 78260.0, 78261.0, 78262.0, 78263.0, 78264.0],       [78265.0, 78266.0, 78267.0, 78268.0, 78269.0, 78270.0],       [78271.0, 78272.0, 78273.0, 78274.0, 78275.0, 78276.0],       [78277.0, 78278.0, 78279.0, 78280.0, 78281.0, 78282.0],       [78283.0, 78284.0, 78285.0, 78286.0, 78287.0, 78288.0]],      [[78289.0, 78290.0, 78291.0, 78292.0, 78293.0, 78294.0],       [78295.0, 78296.0, 78297.0, 78298.0, 78299.0, 78300.0],       [78301.0, 78302.0, 78303.0, 78304.0, 78305.0, 78306.0],       [78307.0, 78308.0, 78309.0, 78310.0, 78311.0, 78312.0],       [78313.0, 78314.0, 78315.0, 78316.0, 78317.0, 78318.0],       [78319.0, 78320.0, 78321.0, 78322.0, 78323.0, 78324.0],       [78325.0, 78326.0, 78327.0, 78328.0, 78329.0, 78330.0]],      [[78331.0, 78332.0, 78333.0, 78334.0, 78335.0, 78336.0],       [78337.0, 78338.0, 78339.0, 78340.0, 78341.0, 78342.0],       [78343.0, 78344.0, 78345.0, 78346.0, 78347.0, 78348.0],       [78349.0, 78350.0, 78351.0, 78352.0, 78353.0, 78354.0],       [78355.0, 78356.0, 78357.0, 78358.0, 78359.0, 78360.0],       [78361.0, 78362.0, 78363.0, 78364.0, 78365.0, 78366.0],       [78367.0, 78368.0, 78369.0, 78370.0, 78371.0, 78372.0]]],     [[[78373.0, 78374.0, 78375.0, 78376.0, 78377.0, 78378.0],       [78379.0, 78380.0, 78381.0, 78382.0, 78383.0, 78384.0],       [78385.0, 78386.0, 78387.0, 78388.0, 78389.0, 78390.0],       [78391.0, 78392.0, 78393.0, 78394.0, 78395.0, 78396.0],       [78397.0, 78398.0, 78399.0, 78400.0, 78401.0, 78402.0],       [78403.0, 78404.0, 78405.0, 78406.0, 78407.0, 78408.0],       [78409.0, 78410.0, 78411.0, 78412.0, 78413.0, 78414.0]],      [[78415.0, 78416.0, 78417.0, 78418.0, 78419.0, 78420.0],       [78421.0, 78422.0, 78423.0, 78424.0, 78425.0, 78426.0],       [78427.0, 78428.0, 78429.0, 78430.0, 78431.0, 78432.0],       [78433.0, 78434.0, 78435.0, 78436.0, 78437.0, 78438.0],       [78439.0, 78440.0, 78441.0, 78442.0, 78443.0, 78444.0],       [78445.0, 78446.0, 78447.0, 78448.0, 78449.0, 78450.0],       [78451.0, 78452.0, 78453.0, 78454.0, 78455.0, 78456.0]],      [[78457.0, 78458.0, 78459.0, 78460.0, 78461.0, 78462.0],       [78463.0, 78464.0, 78465.0, 78466.0, 78467.0, 78468.0],       [78469.0, 78470.0, 78471.0, 78472.0, 78473.0, 78474.0],       [78475.0, 78476.0, 78477.0, 78478.0, 78479.0, 78480.0],       [78481.0, 78482.0, 78483.0, 78484.0, 78485.0, 78486.0],       [78487.0, 78488.0, 78489.0, 78490.0, 78491.0, 78492.0],       [78493.0, 78494.0, 78495.0, 78496.0, 78497.0, 78498.0]],      [[78499.0, 78500.0, 78501.0, 78502.0, 78503.0, 78504.0],       [78505.0, 78506.0, 78507.0, 78508.0, 78509.0, 78510.0],       [78511.0, 78512.0, 78513.0, 78514.0, 78515.0, 78516.0],       [78517.0, 78518.0, 78519.0, 78520.0, 78521.0, 78522.0],       [78523.0, 78524.0, 78525.0, 78526.0, 78527.0, 78528.0],       [78529.0, 78530.0, 78531.0, 78532.0, 78533.0, 78534.0],       [78535.0, 78536.0, 78537.0, 78538.0, 78539.0, 78540.0]],      [[78541.0, 78542.0, 78543.0, 78544.0, 78545.0, 78546.0],       [78547.0, 78548.0, 78549.0, 78550.0, 78551.0, 78552.0],       [78553.0, 78554.0, 78555.0, 78556.0, 78557.0, 78558.0],       [78559.0, 78560.0, 78561.0, 78562.0, 78563.0, 78564.0],       [78565.0, 78566.0, 78567.0, 78568.0, 78569.0, 78570.0],       [78571.0, 78572.0, 78573.0, 78574.0, 78575.0, 78576.0],       [78577.0, 78578.0, 78579.0, 78580.0, 78581.0, 78582.0]],      [[78583.0, 78584.0, 78585.0, 78586.0, 78587.0, 78588.0],       [78589.0, 78590.0, 78591.0, 78592.0, 78593.0, 78594.0],       [78595.0, 78596.0, 78597.0, 78598.0, 78599.0, 78600.0],       [78601.0, 78602.0, 78603.0, 78604.0, 78605.0, 78606.0],       [78607.0, 78608.0, 78609.0, 78610.0, 78611.0, 78612.0],       [78613.0, 78614.0, 78615.0, 78616.0, 78617.0, 78618.0],       [78619.0, 78620.0, 78621.0, 78622.0, 78623.0, 78624.0]]]]],   [[[[[78625.0, 78626.0, 78627.0, 78628.0, 78629.0, 78630.0],       [78631.0, 78632.0, 78633.0, 78634.0, 78635.0, 78636.0],       [78637.0, 78638.0, 78639.0, 78640.0, 78641.0, 78642.0],       [78643.0, 78644.0, 78645.0, 78646.0, 78647.0, 78648.0],       [78649.0, 78650.0, 78651.0, 78652.0, 78653.0, 78654.0],       [78655.0, 78656.0, 78657.0, 78658.0, 78659.0, 78660.0],       [78661.0, 78662.0, 78663.0, 78664.0, 78665.0, 78666.0]],      [[78667.0, 78668.0, 78669.0, 78670.0, 78671.0, 78672.0],       [78673.0, 78674.0, 78675.0, 78676.0, 78677.0, 78678.0],       [78679.0, 78680.0, 78681.0, 78682.0, 78683.0, 78684.0],       [78685.0, 78686.0, 78687.0, 78688.0, 78689.0, 78690.0],       [78691.0, 78692.0, 78693.0, 78694.0, 78695.0, 78696.0],       [78697.0, 78698.0, 78699.0, 78700.0, 78701.0, 78702.0],       [78703.0, 78704.0, 78705.0, 78706.0, 78707.0, 78708.0]],      [[78709.0, 78710.0, 78711.0, 78712.0, 78713.0, 78714.0],       [78715.0, 78716.0, 78717.0, 78718.0, 78719.0, 78720.0],       [78721.0, 78722.0, 78723.0, 78724.0, 78725.0, 78726.0],       [78727.0, 78728.0, 78729.0, 78730.0, 78731.0, 78732.0],       [78733.0, 78734.0, 78735.0, 78736.0, 78737.0, 78738.0],       [78739.0, 78740.0, 78741.0, 78742.0, 78743.0, 78744.0],       [78745.0, 78746.0, 78747.0, 78748.0, 78749.0, 78750.0]],      [[78751.0, 78752.0, 78753.0, 78754.0, 78755.0, 78756.0],       [78757.0, 78758.0, 78759.0, 78760.0, 78761.0, 78762.0],       [78763.0, 78764.0, 78765.0, 78766.0, 78767.0, 78768.0],       [78769.0, 78770.0, 78771.0, 78772.0, 78773.0, 78774.0],       [78775.0, 78776.0, 78777.0, 78778.0, 78779.0, 78780.0],       [78781.0, 78782.0, 78783.0, 78784.0, 78785.0, 78786.0],       [78787.0, 78788.0, 78789.0, 78790.0, 78791.0, 78792.0]],      [[78793.0, 78794.0, 78795.0, 78796.0, 78797.0, 78798.0],       [78799.0, 78800.0, 78801.0, 78802.0, 78803.0, 78804.0],       [78805.0, 78806.0, 78807.0, 78808.0, 78809.0, 78810.0],       [78811.0, 78812.0, 78813.0, 78814.0, 78815.0, 78816.0],       [78817.0, 78818.0, 78819.0, 78820.0, 78821.0, 78822.0],       [78823.0, 78824.0, 78825.0, 78826.0, 78827.0, 78828.0],       [78829.0, 78830.0, 78831.0, 78832.0, 78833.0, 78834.0]],      [[78835.0, 78836.0, 78837.0, 78838.0, 78839.0, 78840.0],       [78841.0, 78842.0, 78843.0, 78844.0, 78845.0, 78846.0],       [78847.0, 78848.0, 78849.0, 78850.0, 78851.0, 78852.0],       [78853.0, 78854.0, 78855.0, 78856.0, 78857.0, 78858.0],       [78859.0, 78860.0, 78861.0, 78862.0, 78863.0, 78864.0],       [78865.0, 78866.0, 78867.0, 78868.0, 78869.0, 78870.0],       [78871.0, 78872.0, 78873.0, 78874.0, 78875.0, 78876.0]]],     [[[78877.0, 78878.0, 78879.0, 78880.0, 78881.0, 78882.0],       [78883.0, 78884.0, 78885.0, 78886.0, 78887.0, 78888.0],       [78889.0, 78890.0, 78891.0, 78892.0, 78893.0, 78894.0],       [78895.0, 78896.0, 78897.0, 78898.0, 78899.0, 78900.0],       [78901.0, 78902.0, 78903.0, 78904.0, 78905.0, 78906.0],       [78907.0, 78908.0, 78909.0, 78910.0, 78911.0, 78912.0],       [78913.0, 78914.0, 78915.0, 78916.0, 78917.0, 78918.0]],      [[78919.0, 78920.0, 78921.0, 78922.0, 78923.0, 78924.0],       [78925.0, 78926.0, 78927.0, 78928.0, 78929.0, 78930.0],       [78931.0, 78932.0, 78933.0, 78934.0, 78935.0, 78936.0],       [78937.0, 78938.0, 78939.0, 78940.0, 78941.0, 78942.0],       [78943.0, 78944.0, 78945.0, 78946.0, 78947.0, 78948.0],       [78949.0, 78950.0, 78951.0, 78952.0, 78953.0, 78954.0],       [78955.0, 78956.0, 78957.0, 78958.0, 78959.0, 78960.0]],      [[78961.0, 78962.0, 78963.0, 78964.0, 78965.0, 78966.0],       [78967.0, 78968.0, 78969.0, 78970.0, 78971.0, 78972.0],       [78973.0, 78974.0, 78975.0, 78976.0, 78977.0, 78978.0],       [78979.0, 78980.0, 78981.0, 78982.0, 78983.0, 78984.0],       [78985.0, 78986.0, 78987.0, 78988.0, 78989.0, 78990.0],       [78991.0, 78992.0, 78993.0, 78994.0, 78995.0, 78996.0],       [78997.0, 78998.0, 78999.0, 79000.0, 79001.0, 79002.0]],      [[79003.0, 79004.0, 79005.0, 79006.0, 79007.0, 79008.0],       [79009.0, 79010.0, 79011.0, 79012.0, 79013.0, 79014.0],       [79015.0, 79016.0, 79017.0, 79018.0, 79019.0, 79020.0],       [79021.0, 79022.0, 79023.0, 79024.0, 79025.0, 79026.0],       [79027.0, 79028.0, 79029.0, 79030.0, 79031.0, 79032.0],       [79033.0, 79034.0, 79035.0, 79036.0, 79037.0, 79038.0],       [79039.0, 79040.0, 79041.0, 79042.0, 79043.0, 79044.0]],      [[79045.0, 79046.0, 79047.0, 79048.0, 79049.0, 79050.0],       [79051.0, 79052.0, 79053.0, 79054.0, 79055.0, 79056.0],       [79057.0, 79058.0, 79059.0, 79060.0, 79061.0, 79062.0],       [79063.0, 79064.0, 79065.0, 79066.0, 79067.0, 79068.0],       [79069.0, 79070.0, 79071.0, 79072.0, 79073.0, 79074.0],       [79075.0, 79076.0, 79077.0, 79078.0, 79079.0, 79080.0],       [79081.0, 79082.0, 79083.0, 79084.0, 79085.0, 79086.0]],      [[79087.0, 79088.0, 79089.0, 79090.0, 79091.0, 79092.0],       [79093.0, 79094.0, 79095.0, 79096.0, 79097.0, 79098.0],       [79099.0, 79100.0, 79101.0, 79102.0, 79103.0, 79104.0],       [79105.0, 79106.0, 79107.0, 79108.0, 79109.0, 79110.0],       [79111.0, 79112.0, 79113.0, 79114.0, 79115.0, 79116.0],       [79117.0, 79118.0, 79119.0, 79120.0, 79121.0, 79122.0],       [79123.0, 79124.0, 79125.0, 79126.0, 79127.0, 79128.0]]],     [[[79129.0, 79130.0, 79131.0, 79132.0, 79133.0, 79134.0],       [79135.0, 79136.0, 79137.0, 79138.0, 79139.0, 79140.0],       [79141.0, 79142.0, 79143.0, 79144.0, 79145.0, 79146.0],       [79147.0, 79148.0, 79149.0, 79150.0, 79151.0, 79152.0],       [79153.0, 79154.0, 79155.0, 79156.0, 79157.0, 79158.0],       [79159.0, 79160.0, 79161.0, 79162.0, 79163.0, 79164.0],       [79165.0, 79166.0, 79167.0, 79168.0, 79169.0, 79170.0]],      [[79171.0, 79172.0, 79173.0, 79174.0, 79175.0, 79176.0],       [79177.0, 79178.0, 79179.0, 79180.0, 79181.0, 79182.0],       [79183.0, 79184.0, 79185.0, 79186.0, 79187.0, 79188.0],       [79189.0, 79190.0, 79191.0, 79192.0, 79193.0, 79194.0],       [79195.0, 79196.0, 79197.0, 79198.0, 79199.0, 79200.0],       [79201.0, 79202.0, 79203.0, 79204.0, 79205.0, 79206.0],       [79207.0, 79208.0, 79209.0, 79210.0, 79211.0, 79212.0]],      [[79213.0, 79214.0, 79215.0, 79216.0, 79217.0, 79218.0],       [79219.0, 79220.0, 79221.0, 79222.0, 79223.0, 79224.0],       [79225.0, 79226.0, 79227.0, 79228.0, 79229.0, 79230.0],       [79231.0, 79232.0, 79233.0, 79234.0, 79235.0, 79236.0],       [79237.0, 79238.0, 79239.0, 79240.0, 79241.0, 79242.0],       [79243.0, 79244.0, 79245.0, 79246.0, 79247.0, 79248.0],       [79249.0, 79250.0, 79251.0, 79252.0, 79253.0, 79254.0]],      [[79255.0, 79256.0, 79257.0, 79258.0, 79259.0, 79260.0],       [79261.0, 79262.0, 79263.0, 79264.0, 79265.0, 79266.0],       [79267.0, 79268.0, 79269.0, 79270.0, 79271.0, 79272.0],       [79273.0, 79274.0, 79275.0, 79276.0, 79277.0, 79278.0],       [79279.0, 79280.0, 79281.0, 79282.0, 79283.0, 79284.0],       [79285.0, 79286.0, 79287.0, 79288.0, 79289.0, 79290.0],       [79291.0, 79292.0, 79293.0, 79294.0, 79295.0, 79296.0]],      [[79297.0, 79298.0, 79299.0, 79300.0, 79301.0, 79302.0],       [79303.0, 79304.0, 79305.0, 79306.0, 79307.0, 79308.0],       [79309.0, 79310.0, 79311.0, 79312.0, 79313.0, 79314.0],       [79315.0, 79316.0, 79317.0, 79318.0, 79319.0, 79320.0],       [79321.0, 79322.0, 79323.0, 79324.0, 79325.0, 79326.0],       [79327.0, 79328.0, 79329.0, 79330.0, 79331.0, 79332.0],       [79333.0, 79334.0, 79335.0, 79336.0, 79337.0, 79338.0]],      [[79339.0, 79340.0, 79341.0, 79342.0, 79343.0, 79344.0],       [79345.0, 79346.0, 79347.0, 79348.0, 79349.0, 79350.0],       [79351.0, 79352.0, 79353.0, 79354.0, 79355.0, 79356.0],       [79357.0, 79358.0, 79359.0, 79360.0, 79361.0, 79362.0],       [79363.0, 79364.0, 79365.0, 79366.0, 79367.0, 79368.0],       [79369.0, 79370.0, 79371.0, 79372.0, 79373.0, 79374.0],       [79375.0, 79376.0, 79377.0, 79378.0, 79379.0, 79380.0]]],     [[[79381.0, 79382.0, 79383.0, 79384.0, 79385.0, 79386.0],       [79387.0, 79388.0, 79389.0, 79390.0, 79391.0, 79392.0],       [79393.0, 79394.0, 79395.0, 79396.0, 79397.0, 79398.0],       [79399.0, 79400.0, 79401.0, 79402.0, 79403.0, 79404.0],       [79405.0, 79406.0, 79407.0, 79408.0, 79409.0, 79410.0],       [79411.0, 79412.0, 79413.0, 79414.0, 79415.0, 79416.0],       [79417.0, 79418.0, 79419.0, 79420.0, 79421.0, 79422.0]],      [[79423.0, 79424.0, 79425.0, 79426.0, 79427.0, 79428.0],       [79429.0, 79430.0, 79431.0, 79432.0, 79433.0, 79434.0],       [79435.0, 79436.0, 79437.0, 79438.0, 79439.0, 79440.0],       [79441.0, 79442.0, 79443.0, 79444.0, 79445.0, 79446.0],       [79447.0, 79448.0, 79449.0, 79450.0, 79451.0, 79452.0],       [79453.0, 79454.0, 79455.0, 79456.0, 79457.0, 79458.0],       [79459.0, 79460.0, 79461.0, 79462.0, 79463.0, 79464.0]],      [[79465.0, 79466.0, 79467.0, 79468.0, 79469.0, 79470.0],       [79471.0, 79472.0, 79473.0, 79474.0, 79475.0, 79476.0],       [79477.0, 79478.0, 79479.0, 79480.0, 79481.0, 79482.0],       [79483.0, 79484.0, 79485.0, 79486.0, 79487.0, 79488.0],       [79489.0, 79490.0, 79491.0, 79492.0, 79493.0, 79494.0],       [79495.0, 79496.0, 79497.0, 79498.0, 79499.0, 79500.0],       [79501.0, 79502.0, 79503.0, 79504.0, 79505.0, 79506.0]],      [[79507.0, 79508.0, 79509.0, 79510.0, 79511.0, 79512.0],       [79513.0, 79514.0, 79515.0, 79516.0, 79517.0, 79518.0],       [79519.0, 79520.0, 79521.0, 79522.0, 79523.0, 79524.0],       [79525.0, 79526.0, 79527.0, 79528.0, 79529.0, 79530.0],       [79531.0, 79532.0, 79533.0, 79534.0, 79535.0, 79536.0],       [79537.0, 79538.0, 79539.0, 79540.0, 79541.0, 79542.0],       [79543.0, 79544.0, 79545.0, 79546.0, 79547.0, 79548.0]],      [[79549.0, 79550.0, 79551.0, 79552.0, 79553.0, 79554.0],       [79555.0, 79556.0, 79557.0, 79558.0, 79559.0, 79560.0],       [79561.0, 79562.0, 79563.0, 79564.0, 79565.0, 79566.0],       [79567.0, 79568.0, 79569.0, 79570.0, 79571.0, 79572.0],       [79573.0, 79574.0, 79575.0, 79576.0, 79577.0, 79578.0],       [79579.0, 79580.0, 79581.0, 79582.0, 79583.0, 79584.0],       [79585.0, 79586.0, 79587.0, 79588.0, 79589.0, 79590.0]],      [[79591.0, 79592.0, 79593.0, 79594.0, 79595.0, 79596.0],       [79597.0, 79598.0, 79599.0, 79600.0, 79601.0, 79602.0],       [79603.0, 79604.0, 79605.0, 79606.0, 79607.0, 79608.0],       [79609.0, 79610.0, 79611.0, 79612.0, 79613.0, 79614.0],       [79615.0, 79616.0, 79617.0, 79618.0, 79619.0, 79620.0],       [79621.0, 79622.0, 79623.0, 79624.0, 79625.0, 79626.0],       [79627.0, 79628.0, 79629.0, 79630.0, 79631.0, 79632.0]]]],    [[[[79633.0, 79634.0, 79635.0, 79636.0, 79637.0, 79638.0],       [79639.0, 79640.0, 79641.0, 79642.0, 79643.0, 79644.0],       [79645.0, 79646.0, 79647.0, 79648.0, 79649.0, 79650.0],       [79651.0, 79652.0, 79653.0, 79654.0, 79655.0, 79656.0],       [79657.0, 79658.0, 79659.0, 79660.0, 79661.0, 79662.0],       [79663.0, 79664.0, 79665.0, 79666.0, 79667.0, 79668.0],       [79669.0, 79670.0, 79671.0, 79672.0, 79673.0, 79674.0]],      [[79675.0, 79676.0, 79677.0, 79678.0, 79679.0, 79680.0],       [79681.0, 79682.0, 79683.0, 79684.0, 79685.0, 79686.0],       [79687.0, 79688.0, 79689.0, 79690.0, 79691.0, 79692.0],       [79693.0, 79694.0, 79695.0, 79696.0, 79697.0, 79698.0],       [79699.0, 79700.0, 79701.0, 79702.0, 79703.0, 79704.0],       [79705.0, 79706.0, 79707.0, 79708.0, 79709.0, 79710.0],       [79711.0, 79712.0, 79713.0, 79714.0, 79715.0, 79716.0]],      [[79717.0, 79718.0, 79719.0, 79720.0, 79721.0, 79722.0],       [79723.0, 79724.0, 79725.0, 79726.0, 79727.0, 79728.0],       [79729.0, 79730.0, 79731.0, 79732.0, 79733.0, 79734.0],       [79735.0, 79736.0, 79737.0, 79738.0, 79739.0, 79740.0],       [79741.0, 79742.0, 79743.0, 79744.0, 79745.0, 79746.0],       [79747.0, 79748.0, 79749.0, 79750.0, 79751.0, 79752.0],       [79753.0, 79754.0, 79755.0, 79756.0, 79757.0, 79758.0]],      [[79759.0, 79760.0, 79761.0, 79762.0, 79763.0, 79764.0],       [79765.0, 79766.0, 79767.0, 79768.0, 79769.0, 79770.0],       [79771.0, 79772.0, 79773.0, 79774.0, 79775.0, 79776.0],       [79777.0, 79778.0, 79779.0, 79780.0, 79781.0, 79782.0],       [79783.0, 79784.0, 79785.0, 79786.0, 79787.0, 79788.0],       [79789.0, 79790.0, 79791.0, 79792.0, 79793.0, 79794.0],       [79795.0, 79796.0, 79797.0, 79798.0, 79799.0, 79800.0]],      [[79801.0, 79802.0, 79803.0, 79804.0, 79805.0, 79806.0],       [79807.0, 79808.0, 79809.0, 79810.0, 79811.0, 79812.0],       [79813.0, 79814.0, 79815.0, 79816.0, 79817.0, 79818.0],       [79819.0, 79820.0, 79821.0, 79822.0, 79823.0, 79824.0],       [79825.0, 79826.0, 79827.0, 79828.0, 79829.0, 79830.0],       [79831.0, 79832.0, 79833.0, 79834.0, 79835.0, 79836.0],       [79837.0, 79838.0, 79839.0, 79840.0, 79841.0, 79842.0]],      [[79843.0, 79844.0, 79845.0, 79846.0, 79847.0, 79848.0],       [79849.0, 79850.0, 79851.0, 79852.0, 79853.0, 79854.0],       [79855.0, 79856.0, 79857.0, 79858.0, 79859.0, 79860.0],       [79861.0, 79862.0, 79863.0, 79864.0, 79865.0, 79866.0],       [79867.0, 79868.0, 79869.0, 79870.0, 79871.0, 79872.0],       [79873.0, 79874.0, 79875.0, 79876.0, 79877.0, 79878.0],       [79879.0, 79880.0, 79881.0, 79882.0, 79883.0, 79884.0]]],     [[[79885.0, 79886.0, 79887.0, 79888.0, 79889.0, 79890.0],       [79891.0, 79892.0, 79893.0, 79894.0, 79895.0, 79896.0],       [79897.0, 79898.0, 79899.0, 79900.0, 79901.0, 79902.0],       [79903.0, 79904.0, 79905.0, 79906.0, 79907.0, 79908.0],       [79909.0, 79910.0, 79911.0, 79912.0, 79913.0, 79914.0],       [79915.0, 79916.0, 79917.0, 79918.0, 79919.0, 79920.0],       [79921.0, 79922.0, 79923.0, 79924.0, 79925.0, 79926.0]],      [[79927.0, 79928.0, 79929.0, 79930.0, 79931.0, 79932.0],       [79933.0, 79934.0, 79935.0, 79936.0, 79937.0, 79938.0],       [79939.0, 79940.0, 79941.0, 79942.0, 79943.0, 79944.0],       [79945.0, 79946.0, 79947.0, 79948.0, 79949.0, 79950.0],       [79951.0, 79952.0, 79953.0, 79954.0, 79955.0, 79956.0],       [79957.0, 79958.0, 79959.0, 79960.0, 79961.0, 79962.0],       [79963.0, 79964.0, 79965.0, 79966.0, 79967.0, 79968.0]],      [[79969.0, 79970.0, 79971.0, 79972.0, 79973.0, 79974.0],       [79975.0, 79976.0, 79977.0, 79978.0, 79979.0, 79980.0],       [79981.0, 79982.0, 79983.0, 79984.0, 79985.0, 79986.0],       [79987.0, 79988.0, 79989.0, 79990.0, 79991.0, 79992.0],       [79993.0, 79994.0, 79995.0, 79996.0, 79997.0, 79998.0],       [79999.0, 80000.0, 80001.0, 80002.0, 80003.0, 80004.0],       [80005.0, 80006.0, 80007.0, 80008.0, 80009.0, 80010.0]],      [[80011.0, 80012.0, 80013.0, 80014.0, 80015.0, 80016.0],       [80017.0, 80018.0, 80019.0, 80020.0, 80021.0, 80022.0],       [80023.0, 80024.0, 80025.0, 80026.0, 80027.0, 80028.0],       [80029.0, 80030.0, 80031.0, 80032.0, 80033.0, 80034.0],       [80035.0, 80036.0, 80037.0, 80038.0, 80039.0, 80040.0],       [80041.0, 80042.0, 80043.0, 80044.0, 80045.0, 80046.0],       [80047.0, 80048.0, 80049.0, 80050.0, 80051.0, 80052.0]],      [[80053.0, 80054.0, 80055.0, 80056.0, 80057.0, 80058.0],       [80059.0, 80060.0, 80061.0, 80062.0, 80063.0, 80064.0],       [80065.0, 80066.0, 80067.0, 80068.0, 80069.0, 80070.0],       [80071.0, 80072.0, 80073.0, 80074.0, 80075.0, 80076.0],       [80077.0, 80078.0, 80079.0, 80080.0, 80081.0, 80082.0],       [80083.0, 80084.0, 80085.0, 80086.0, 80087.0, 80088.0],       [80089.0, 80090.0, 80091.0, 80092.0, 80093.0, 80094.0]],      [[80095.0, 80096.0, 80097.0, 80098.0, 80099.0, 80100.0],       [80101.0, 80102.0, 80103.0, 80104.0, 80105.0, 80106.0],       [80107.0, 80108.0, 80109.0, 80110.0, 80111.0, 80112.0],       [80113.0, 80114.0, 80115.0, 80116.0, 80117.0, 80118.0],       [80119.0, 80120.0, 80121.0, 80122.0, 80123.0, 80124.0],       [80125.0, 80126.0, 80127.0, 80128.0, 80129.0, 80130.0],       [80131.0, 80132.0, 80133.0, 80134.0, 80135.0, 80136.0]]],     [[[80137.0, 80138.0, 80139.0, 80140.0, 80141.0, 80142.0],       [80143.0, 80144.0, 80145.0, 80146.0, 80147.0, 80148.0],       [80149.0, 80150.0, 80151.0, 80152.0, 80153.0, 80154.0],       [80155.0, 80156.0, 80157.0, 80158.0, 80159.0, 80160.0],       [80161.0, 80162.0, 80163.0, 80164.0, 80165.0, 80166.0],       [80167.0, 80168.0, 80169.0, 80170.0, 80171.0, 80172.0],       [80173.0, 80174.0, 80175.0, 80176.0, 80177.0, 80178.0]],      [[80179.0, 80180.0, 80181.0, 80182.0, 80183.0, 80184.0],       [80185.0, 80186.0, 80187.0, 80188.0, 80189.0, 80190.0],       [80191.0, 80192.0, 80193.0, 80194.0, 80195.0, 80196.0],       [80197.0, 80198.0, 80199.0, 80200.0, 80201.0, 80202.0],       [80203.0, 80204.0, 80205.0, 80206.0, 80207.0, 80208.0],       [80209.0, 80210.0, 80211.0, 80212.0, 80213.0, 80214.0],       [80215.0, 80216.0, 80217.0, 80218.0, 80219.0, 80220.0]],      [[80221.0, 80222.0, 80223.0, 80224.0, 80225.0, 80226.0],       [80227.0, 80228.0, 80229.0, 80230.0, 80231.0, 80232.0],       [80233.0, 80234.0, 80235.0, 80236.0, 80237.0, 80238.0],       [80239.0, 80240.0, 80241.0, 80242.0, 80243.0, 80244.0],       [80245.0, 80246.0, 80247.0, 80248.0, 80249.0, 80250.0],       [80251.0, 80252.0, 80253.0, 80254.0, 80255.0, 80256.0],       [80257.0, 80258.0, 80259.0, 80260.0, 80261.0, 80262.0]],      [[80263.0, 80264.0, 80265.0, 80266.0, 80267.0, 80268.0],       [80269.0, 80270.0, 80271.0, 80272.0, 80273.0, 80274.0],       [80275.0, 80276.0, 80277.0, 80278.0, 80279.0, 80280.0],       [80281.0, 80282.0, 80283.0, 80284.0, 80285.0, 80286.0],       [80287.0, 80288.0, 80289.0, 80290.0, 80291.0, 80292.0],       [80293.0, 80294.0, 80295.0, 80296.0, 80297.0, 80298.0],       [80299.0, 80300.0, 80301.0, 80302.0, 80303.0, 80304.0]],      [[80305.0, 80306.0, 80307.0, 80308.0, 80309.0, 80310.0],       [80311.0, 80312.0, 80313.0, 80314.0, 80315.0, 80316.0],       [80317.0, 80318.0, 80319.0, 80320.0, 80321.0, 80322.0],       [80323.0, 80324.0, 80325.0, 80326.0, 80327.0, 80328.0],       [80329.0, 80330.0, 80331.0, 80332.0, 80333.0, 80334.0],       [80335.0, 80336.0, 80337.0, 80338.0, 80339.0, 80340.0],       [80341.0, 80342.0, 80343.0, 80344.0, 80345.0, 80346.0]],      [[80347.0, 80348.0, 80349.0, 80350.0, 80351.0, 80352.0],       [80353.0, 80354.0, 80355.0, 80356.0, 80357.0, 80358.0],       [80359.0, 80360.0, 80361.0, 80362.0, 80363.0, 80364.0],       [80365.0, 80366.0, 80367.0, 80368.0, 80369.0, 80370.0],       [80371.0, 80372.0, 80373.0, 80374.0, 80375.0, 80376.0],       [80377.0, 80378.0, 80379.0, 80380.0, 80381.0, 80382.0],       [80383.0, 80384.0, 80385.0, 80386.0, 80387.0, 80388.0]]],     [[[80389.0, 80390.0, 80391.0, 80392.0, 80393.0, 80394.0],       [80395.0, 80396.0, 80397.0, 80398.0, 80399.0, 80400.0],       [80401.0, 80402.0, 80403.0, 80404.0, 80405.0, 80406.0],       [80407.0, 80408.0, 80409.0, 80410.0, 80411.0, 80412.0],       [80413.0, 80414.0, 80415.0, 80416.0, 80417.0, 80418.0],       [80419.0, 80420.0, 80421.0, 80422.0, 80423.0, 80424.0],       [80425.0, 80426.0, 80427.0, 80428.0, 80429.0, 80430.0]],      [[80431.0, 80432.0, 80433.0, 80434.0, 80435.0, 80436.0],       [80437.0, 80438.0, 80439.0, 80440.0, 80441.0, 80442.0],       [80443.0, 80444.0, 80445.0, 80446.0, 80447.0, 80448.0],       [80449.0, 80450.0, 80451.0, 80452.0, 80453.0, 80454.0],       [80455.0, 80456.0, 80457.0, 80458.0, 80459.0, 80460.0],       [80461.0, 80462.0, 80463.0, 80464.0, 80465.0, 80466.0],       [80467.0, 80468.0, 80469.0, 80470.0, 80471.0, 80472.0]],      [[80473.0, 80474.0, 80475.0, 80476.0, 80477.0, 80478.0],       [80479.0, 80480.0, 80481.0, 80482.0, 80483.0, 80484.0],       [80485.0, 80486.0, 80487.0, 80488.0, 80489.0, 80490.0],       [80491.0, 80492.0, 80493.0, 80494.0, 80495.0, 80496.0],       [80497.0, 80498.0, 80499.0, 80500.0, 80501.0, 80502.0],       [80503.0, 80504.0, 80505.0, 80506.0, 80507.0, 80508.0],       [80509.0, 80510.0, 80511.0, 80512.0, 80513.0, 80514.0]],      [[80515.0, 80516.0, 80517.0, 80518.0, 80519.0, 80520.0],       [80521.0, 80522.0, 80523.0, 80524.0, 80525.0, 80526.0],       [80527.0, 80528.0, 80529.0, 80530.0, 80531.0, 80532.0],       [80533.0, 80534.0, 80535.0, 80536.0, 80537.0, 80538.0],       [80539.0, 80540.0, 80541.0, 80542.0, 80543.0, 80544.0],       [80545.0, 80546.0, 80547.0, 80548.0, 80549.0, 80550.0],       [80551.0, 80552.0, 80553.0, 80554.0, 80555.0, 80556.0]],      [[80557.0, 80558.0, 80559.0, 80560.0, 80561.0, 80562.0],       [80563.0, 80564.0, 80565.0, 80566.0, 80567.0, 80568.0],       [80569.0, 80570.0, 80571.0, 80572.0, 80573.0, 80574.0],       [80575.0, 80576.0, 80577.0, 80578.0, 80579.0, 80580.0],       [80581.0, 80582.0, 80583.0, 80584.0, 80585.0, 80586.0],       [80587.0, 80588.0, 80589.0, 80590.0, 80591.0, 80592.0],       [80593.0, 80594.0, 80595.0, 80596.0, 80597.0, 80598.0]],      [[80599.0, 80600.0, 80601.0, 80602.0, 80603.0, 80604.0],       [80605.0, 80606.0, 80607.0, 80608.0, 80609.0, 80610.0],       [80611.0, 80612.0, 80613.0, 80614.0, 80615.0, 80616.0],       [80617.0, 80618.0, 80619.0, 80620.0, 80621.0, 80622.0],       [80623.0, 80624.0, 80625.0, 80626.0, 80627.0, 80628.0],       [80629.0, 80630.0, 80631.0, 80632.0, 80633.0, 80634.0],       [80635.0, 80636.0, 80637.0, 80638.0, 80639.0, 80640.0]]]],    [[[[80641.0, 80642.0, 80643.0, 80644.0, 80645.0, 80646.0],       [80647.0, 80648.0, 80649.0, 80650.0, 80651.0, 80652.0],       [80653.0, 80654.0, 80655.0, 80656.0, 80657.0, 80658.0],       [80659.0, 80660.0, 80661.0, 80662.0, 80663.0, 80664.0],       [80665.0, 80666.0, 80667.0, 80668.0, 80669.0, 80670.0],       [80671.0, 80672.0, 80673.0, 80674.0, 80675.0, 80676.0],       [80677.0, 80678.0, 80679.0, 80680.0, 80681.0, 80682.0]],      [[80683.0, 80684.0, 80685.0, 80686.0, 80687.0, 80688.0],       [80689.0, 80690.0, 80691.0, 80692.0, 80693.0, 80694.0],       [80695.0, 80696.0, 80697.0, 80698.0, 80699.0, 80700.0],       [80701.0, 80702.0, 80703.0, 80704.0, 80705.0, 80706.0],       [80707.0, 80708.0, 80709.0, 80710.0, 80711.0, 80712.0],       [80713.0, 80714.0, 80715.0, 80716.0, 80717.0, 80718.0],       [80719.0, 80720.0, 80721.0, 80722.0, 80723.0, 80724.0]],      [[80725.0, 80726.0, 80727.0, 80728.0, 80729.0, 80730.0],       [80731.0, 80732.0, 80733.0, 80734.0, 80735.0, 80736.0],       [80737.0, 80738.0, 80739.0, 80740.0, 80741.0, 80742.0],       [80743.0, 80744.0, 80745.0, 80746.0, 80747.0, 80748.0],       [80749.0, 80750.0, 80751.0, 80752.0, 80753.0, 80754.0],       [80755.0, 80756.0, 80757.0, 80758.0, 80759.0, 80760.0],       [80761.0, 80762.0, 80763.0, 80764.0, 80765.0, 80766.0]],      [[80767.0, 80768.0, 80769.0, 80770.0, 80771.0, 80772.0],       [80773.0, 80774.0, 80775.0, 80776.0, 80777.0, 80778.0],       [80779.0, 80780.0, 80781.0, 80782.0, 80783.0, 80784.0],       [80785.0, 80786.0, 80787.0, 80788.0, 80789.0, 80790.0],       [80791.0, 80792.0, 80793.0, 80794.0, 80795.0, 80796.0],       [80797.0, 80798.0, 80799.0, 80800.0, 80801.0, 80802.0],       [80803.0, 80804.0, 80805.0, 80806.0, 80807.0, 80808.0]],      [[80809.0, 80810.0, 80811.0, 80812.0, 80813.0, 80814.0],       [80815.0, 80816.0, 80817.0, 80818.0, 80819.0, 80820.0],       [80821.0, 80822.0, 80823.0, 80824.0, 80825.0, 80826.0],       [80827.0, 80828.0, 80829.0, 80830.0, 80831.0, 80832.0],       [80833.0, 80834.0, 80835.0, 80836.0, 80837.0, 80838.0],       [80839.0, 80840.0, 80841.0, 80842.0, 80843.0, 80844.0],       [80845.0, 80846.0, 80847.0, 80848.0, 80849.0, 80850.0]],      [[80851.0, 80852.0, 80853.0, 80854.0, 80855.0, 80856.0],       [80857.0, 80858.0, 80859.0, 80860.0, 80861.0, 80862.0],       [80863.0, 80864.0, 80865.0, 80866.0, 80867.0, 80868.0],       [80869.0, 80870.0, 80871.0, 80872.0, 80873.0, 80874.0],       [80875.0, 80876.0, 80877.0, 80878.0, 80879.0, 80880.0],       [80881.0, 80882.0, 80883.0, 80884.0, 80885.0, 80886.0],       [80887.0, 80888.0, 80889.0, 80890.0, 80891.0, 80892.0]]],     [[[80893.0, 80894.0, 80895.0, 80896.0, 80897.0, 80898.0],       [80899.0, 80900.0, 80901.0, 80902.0, 80903.0, 80904.0],       [80905.0, 80906.0, 80907.0, 80908.0, 80909.0, 80910.0],       [80911.0, 80912.0, 80913.0, 80914.0, 80915.0, 80916.0],       [80917.0, 80918.0, 80919.0, 80920.0, 80921.0, 80922.0],       [80923.0, 80924.0, 80925.0, 80926.0, 80927.0, 80928.0],       [80929.0, 80930.0, 80931.0, 80932.0, 80933.0, 80934.0]],      [[80935.0, 80936.0, 80937.0, 80938.0, 80939.0, 80940.0],       [80941.0, 80942.0, 80943.0, 80944.0, 80945.0, 80946.0],       [80947.0, 80948.0, 80949.0, 80950.0, 80951.0, 80952.0],       [80953.0, 80954.0, 80955.0, 80956.0, 80957.0, 80958.0],       [80959.0, 80960.0, 80961.0, 80962.0, 80963.0, 80964.0],       [80965.0, 80966.0, 80967.0, 80968.0, 80969.0, 80970.0],       [80971.0, 80972.0, 80973.0, 80974.0, 80975.0, 80976.0]],      [[80977.0, 80978.0, 80979.0, 80980.0, 80981.0, 80982.0],       [80983.0, 80984.0, 80985.0, 80986.0, 80987.0, 80988.0],       [80989.0, 80990.0, 80991.0, 80992.0, 80993.0, 80994.0],       [80995.0, 80996.0, 80997.0, 80998.0, 80999.0, 81000.0],       [81001.0, 81002.0, 81003.0, 81004.0, 81005.0, 81006.0],       [81007.0, 81008.0, 81009.0, 81010.0, 81011.0, 81012.0],       [81013.0, 81014.0, 81015.0, 81016.0, 81017.0, 81018.0]],      [[81019.0, 81020.0, 81021.0, 81022.0, 81023.0, 81024.0],       [81025.0, 81026.0, 81027.0, 81028.0, 81029.0, 81030.0],       [81031.0, 81032.0, 81033.0, 81034.0, 81035.0, 81036.0],       [81037.0, 81038.0, 81039.0, 81040.0, 81041.0, 81042.0],       [81043.0, 81044.0, 81045.0, 81046.0, 81047.0, 81048.0],       [81049.0, 81050.0, 81051.0, 81052.0, 81053.0, 81054.0],       [81055.0, 81056.0, 81057.0, 81058.0, 81059.0, 81060.0]],      [[81061.0, 81062.0, 81063.0, 81064.0, 81065.0, 81066.0],       [81067.0, 81068.0, 81069.0, 81070.0, 81071.0, 81072.0],       [81073.0, 81074.0, 81075.0, 81076.0, 81077.0, 81078.0],       [81079.0, 81080.0, 81081.0, 81082.0, 81083.0, 81084.0],       [81085.0, 81086.0, 81087.0, 81088.0, 81089.0, 81090.0],       [81091.0, 81092.0, 81093.0, 81094.0, 81095.0, 81096.0],       [81097.0, 81098.0, 81099.0, 81100.0, 81101.0, 81102.0]],      [[81103.0, 81104.0, 81105.0, 81106.0, 81107.0, 81108.0],       [81109.0, 81110.0, 81111.0, 81112.0, 81113.0, 81114.0],       [81115.0, 81116.0, 81117.0, 81118.0, 81119.0, 81120.0],       [81121.0, 81122.0, 81123.0, 81124.0, 81125.0, 81126.0],       [81127.0, 81128.0, 81129.0, 81130.0, 81131.0, 81132.0],       [81133.0, 81134.0, 81135.0, 81136.0, 81137.0, 81138.0],       [81139.0, 81140.0, 81141.0, 81142.0, 81143.0, 81144.0]]],     [[[81145.0, 81146.0, 81147.0, 81148.0, 81149.0, 81150.0],       [81151.0, 81152.0, 81153.0, 81154.0, 81155.0, 81156.0],       [81157.0, 81158.0, 81159.0, 81160.0, 81161.0, 81162.0],       [81163.0, 81164.0, 81165.0, 81166.0, 81167.0, 81168.0],       [81169.0, 81170.0, 81171.0, 81172.0, 81173.0, 81174.0],       [81175.0, 81176.0, 81177.0, 81178.0, 81179.0, 81180.0],       [81181.0, 81182.0, 81183.0, 81184.0, 81185.0, 81186.0]],      [[81187.0, 81188.0, 81189.0, 81190.0, 81191.0, 81192.0],       [81193.0, 81194.0, 81195.0, 81196.0, 81197.0, 81198.0],       [81199.0, 81200.0, 81201.0, 81202.0, 81203.0, 81204.0],       [81205.0, 81206.0, 81207.0, 81208.0, 81209.0, 81210.0],       [81211.0, 81212.0, 81213.0, 81214.0, 81215.0, 81216.0],       [81217.0, 81218.0, 81219.0, 81220.0, 81221.0, 81222.0],       [81223.0, 81224.0, 81225.0, 81226.0, 81227.0, 81228.0]],      [[81229.0, 81230.0, 81231.0, 81232.0, 81233.0, 81234.0],       [81235.0, 81236.0, 81237.0, 81238.0, 81239.0, 81240.0],       [81241.0, 81242.0, 81243.0, 81244.0, 81245.0, 81246.0],       [81247.0, 81248.0, 81249.0, 81250.0, 81251.0, 81252.0],       [81253.0, 81254.0, 81255.0, 81256.0, 81257.0, 81258.0],       [81259.0, 81260.0, 81261.0, 81262.0, 81263.0, 81264.0],       [81265.0, 81266.0, 81267.0, 81268.0, 81269.0, 81270.0]],      [[81271.0, 81272.0, 81273.0, 81274.0, 81275.0, 81276.0],       [81277.0, 81278.0, 81279.0, 81280.0, 81281.0, 81282.0],       [81283.0, 81284.0, 81285.0, 81286.0, 81287.0, 81288.0],       [81289.0, 81290.0, 81291.0, 81292.0, 81293.0, 81294.0],       [81295.0, 81296.0, 81297.0, 81298.0, 81299.0, 81300.0],       [81301.0, 81302.0, 81303.0, 81304.0, 81305.0, 81306.0],       [81307.0, 81308.0, 81309.0, 81310.0, 81311.0, 81312.0]],      [[81313.0, 81314.0, 81315.0, 81316.0, 81317.0, 81318.0],       [81319.0, 81320.0, 81321.0, 81322.0, 81323.0, 81324.0],       [81325.0, 81326.0, 81327.0, 81328.0, 81329.0, 81330.0],       [81331.0, 81332.0, 81333.0, 81334.0, 81335.0, 81336.0],       [81337.0, 81338.0, 81339.0, 81340.0, 81341.0, 81342.0],       [81343.0, 81344.0, 81345.0, 81346.0, 81347.0, 81348.0],       [81349.0, 81350.0, 81351.0, 81352.0, 81353.0, 81354.0]],      [[81355.0, 81356.0, 81357.0, 81358.0, 81359.0, 81360.0],       [81361.0, 81362.0, 81363.0, 81364.0, 81365.0, 81366.0],       [81367.0, 81368.0, 81369.0, 81370.0, 81371.0, 81372.0],       [81373.0, 81374.0, 81375.0, 81376.0, 81377.0, 81378.0],       [81379.0, 81380.0, 81381.0, 81382.0, 81383.0, 81384.0],       [81385.0, 81386.0, 81387.0, 81388.0, 81389.0, 81390.0],       [81391.0, 81392.0, 81393.0, 81394.0, 81395.0, 81396.0]]],     [[[81397.0, 81398.0, 81399.0, 81400.0, 81401.0, 81402.0],       [81403.0, 81404.0, 81405.0, 81406.0, 81407.0, 81408.0],       [81409.0, 81410.0, 81411.0, 81412.0, 81413.0, 81414.0],       [81415.0, 81416.0, 81417.0, 81418.0, 81419.0, 81420.0],       [81421.0, 81422.0, 81423.0, 81424.0, 81425.0, 81426.0],       [81427.0, 81428.0, 81429.0, 81430.0, 81431.0, 81432.0],       [81433.0, 81434.0, 81435.0, 81436.0, 81437.0, 81438.0]],      [[81439.0, 81440.0, 81441.0, 81442.0, 81443.0, 81444.0],       [81445.0, 81446.0, 81447.0, 81448.0, 81449.0, 81450.0],       [81451.0, 81452.0, 81453.0, 81454.0, 81455.0, 81456.0],       [81457.0, 81458.0, 81459.0, 81460.0, 81461.0, 81462.0],       [81463.0, 81464.0, 81465.0, 81466.0, 81467.0, 81468.0],       [81469.0, 81470.0, 81471.0, 81472.0, 81473.0, 81474.0],       [81475.0, 81476.0, 81477.0, 81478.0, 81479.0, 81480.0]],      [[81481.0, 81482.0, 81483.0, 81484.0, 81485.0, 81486.0],       [81487.0, 81488.0, 81489.0, 81490.0, 81491.0, 81492.0],       [81493.0, 81494.0, 81495.0, 81496.0, 81497.0, 81498.0],       [81499.0, 81500.0, 81501.0, 81502.0, 81503.0, 81504.0],       [81505.0, 81506.0, 81507.0, 81508.0, 81509.0, 81510.0],       [81511.0, 81512.0, 81513.0, 81514.0, 81515.0, 81516.0],       [81517.0, 81518.0, 81519.0, 81520.0, 81521.0, 81522.0]],      [[81523.0, 81524.0, 81525.0, 81526.0, 81527.0, 81528.0],       [81529.0, 81530.0, 81531.0, 81532.0, 81533.0, 81534.0],       [81535.0, 81536.0, 81537.0, 81538.0, 81539.0, 81540.0],       [81541.0, 81542.0, 81543.0, 81544.0, 81545.0, 81546.0],       [81547.0, 81548.0, 81549.0, 81550.0, 81551.0, 81552.0],       [81553.0, 81554.0, 81555.0, 81556.0, 81557.0, 81558.0],       [81559.0, 81560.0, 81561.0, 81562.0, 81563.0, 81564.0]],      [[81565.0, 81566.0, 81567.0, 81568.0, 81569.0, 81570.0],       [81571.0, 81572.0, 81573.0, 81574.0, 81575.0, 81576.0],       [81577.0, 81578.0, 81579.0, 81580.0, 81581.0, 81582.0],       [81583.0, 81584.0, 81585.0, 81586.0, 81587.0, 81588.0],       [81589.0, 81590.0, 81591.0, 81592.0, 81593.0, 81594.0],       [81595.0, 81596.0, 81597.0, 81598.0, 81599.0, 81600.0],       [81601.0, 81602.0, 81603.0, 81604.0, 81605.0, 81606.0]],      [[81607.0, 81608.0, 81609.0, 81610.0, 81611.0, 81612.0],       [81613.0, 81614.0, 81615.0, 81616.0, 81617.0, 81618.0],       [81619.0, 81620.0, 81621.0, 81622.0, 81623.0, 81624.0],       [81625.0, 81626.0, 81627.0, 81628.0, 81629.0, 81630.0],       [81631.0, 81632.0, 81633.0, 81634.0, 81635.0, 81636.0],       [81637.0, 81638.0, 81639.0, 81640.0, 81641.0, 81642.0],       [81643.0, 81644.0, 81645.0, 81646.0, 81647.0, 81648.0]]]],    [[[[81649.0, 81650.0, 81651.0, 81652.0, 81653.0, 81654.0],       [81655.0, 81656.0, 81657.0, 81658.0, 81659.0, 81660.0],       [81661.0, 81662.0, 81663.0, 81664.0, 81665.0, 81666.0],       [81667.0, 81668.0, 81669.0, 81670.0, 81671.0, 81672.0],       [81673.0, 81674.0, 81675.0, 81676.0, 81677.0, 81678.0],       [81679.0, 81680.0, 81681.0, 81682.0, 81683.0, 81684.0],       [81685.0, 81686.0, 81687.0, 81688.0, 81689.0, 81690.0]],      [[81691.0, 81692.0, 81693.0, 81694.0, 81695.0, 81696.0],       [81697.0, 81698.0, 81699.0, 81700.0, 81701.0, 81702.0],       [81703.0, 81704.0, 81705.0, 81706.0, 81707.0, 81708.0],       [81709.0, 81710.0, 81711.0, 81712.0, 81713.0, 81714.0],       [81715.0, 81716.0, 81717.0, 81718.0, 81719.0, 81720.0],       [81721.0, 81722.0, 81723.0, 81724.0, 81725.0, 81726.0],       [81727.0, 81728.0, 81729.0, 81730.0, 81731.0, 81732.0]],      [[81733.0, 81734.0, 81735.0, 81736.0, 81737.0, 81738.0],       [81739.0, 81740.0, 81741.0, 81742.0, 81743.0, 81744.0],       [81745.0, 81746.0, 81747.0, 81748.0, 81749.0, 81750.0],       [81751.0, 81752.0, 81753.0, 81754.0, 81755.0, 81756.0],       [81757.0, 81758.0, 81759.0, 81760.0, 81761.0, 81762.0],       [81763.0, 81764.0, 81765.0, 81766.0, 81767.0, 81768.0],       [81769.0, 81770.0, 81771.0, 81772.0, 81773.0, 81774.0]],      [[81775.0, 81776.0, 81777.0, 81778.0, 81779.0, 81780.0],       [81781.0, 81782.0, 81783.0, 81784.0, 81785.0, 81786.0],       [81787.0, 81788.0, 81789.0, 81790.0, 81791.0, 81792.0],       [81793.0, 81794.0, 81795.0, 81796.0, 81797.0, 81798.0],       [81799.0, 81800.0, 81801.0, 81802.0, 81803.0, 81804.0],       [81805.0, 81806.0, 81807.0, 81808.0, 81809.0, 81810.0],       [81811.0, 81812.0, 81813.0, 81814.0, 81815.0, 81816.0]],      [[81817.0, 81818.0, 81819.0, 81820.0, 81821.0, 81822.0],       [81823.0, 81824.0, 81825.0, 81826.0, 81827.0, 81828.0],       [81829.0, 81830.0, 81831.0, 81832.0, 81833.0, 81834.0],       [81835.0, 81836.0, 81837.0, 81838.0, 81839.0, 81840.0],       [81841.0, 81842.0, 81843.0, 81844.0, 81845.0, 81846.0],       [81847.0, 81848.0, 81849.0, 81850.0, 81851.0, 81852.0],       [81853.0, 81854.0, 81855.0, 81856.0, 81857.0, 81858.0]],      [[81859.0, 81860.0, 81861.0, 81862.0, 81863.0, 81864.0],       [81865.0, 81866.0, 81867.0, 81868.0, 81869.0, 81870.0],       [81871.0, 81872.0, 81873.0, 81874.0, 81875.0, 81876.0],       [81877.0, 81878.0, 81879.0, 81880.0, 81881.0, 81882.0],       [81883.0, 81884.0, 81885.0, 81886.0, 81887.0, 81888.0],       [81889.0, 81890.0, 81891.0, 81892.0, 81893.0, 81894.0],       [81895.0, 81896.0, 81897.0, 81898.0, 81899.0, 81900.0]]],     [[[81901.0, 81902.0, 81903.0, 81904.0, 81905.0, 81906.0],       [81907.0, 81908.0, 81909.0, 81910.0, 81911.0, 81912.0],       [81913.0, 81914.0, 81915.0, 81916.0, 81917.0, 81918.0],       [81919.0, 81920.0, 81921.0, 81922.0, 81923.0, 81924.0],       [81925.0, 81926.0, 81927.0, 81928.0, 81929.0, 81930.0],       [81931.0, 81932.0, 81933.0, 81934.0, 81935.0, 81936.0],       [81937.0, 81938.0, 81939.0, 81940.0, 81941.0, 81942.0]],      [[81943.0, 81944.0, 81945.0, 81946.0, 81947.0, 81948.0],       [81949.0, 81950.0, 81951.0, 81952.0, 81953.0, 81954.0],       [81955.0, 81956.0, 81957.0, 81958.0, 81959.0, 81960.0],       [81961.0, 81962.0, 81963.0, 81964.0, 81965.0, 81966.0],       [81967.0, 81968.0, 81969.0, 81970.0, 81971.0, 81972.0],       [81973.0, 81974.0, 81975.0, 81976.0, 81977.0, 81978.0],       [81979.0, 81980.0, 81981.0, 81982.0, 81983.0, 81984.0]],      [[81985.0, 81986.0, 81987.0, 81988.0, 81989.0, 81990.0],       [81991.0, 81992.0, 81993.0, 81994.0, 81995.0, 81996.0],       [81997.0, 81998.0, 81999.0, 82000.0, 82001.0, 82002.0],       [82003.0, 82004.0, 82005.0, 82006.0, 82007.0, 82008.0],       [82009.0, 82010.0, 82011.0, 82012.0, 82013.0, 82014.0],       [82015.0, 82016.0, 82017.0, 82018.0, 82019.0, 82020.0],       [82021.0, 82022.0, 82023.0, 82024.0, 82025.0, 82026.0]],      [[82027.0, 82028.0, 82029.0, 82030.0, 82031.0, 82032.0],       [82033.0, 82034.0, 82035.0, 82036.0, 82037.0, 82038.0],       [82039.0, 82040.0, 82041.0, 82042.0, 82043.0, 82044.0],       [82045.0, 82046.0, 82047.0, 82048.0, 82049.0, 82050.0],       [82051.0, 82052.0, 82053.0, 82054.0, 82055.0, 82056.0],       [82057.0, 82058.0, 82059.0, 82060.0, 82061.0, 82062.0],       [82063.0, 82064.0, 82065.0, 82066.0, 82067.0, 82068.0]],      [[82069.0, 82070.0, 82071.0, 82072.0, 82073.0, 82074.0],       [82075.0, 82076.0, 82077.0, 82078.0, 82079.0, 82080.0],       [82081.0, 82082.0, 82083.0, 82084.0, 82085.0, 82086.0],       [82087.0, 82088.0, 82089.0, 82090.0, 82091.0, 82092.0],       [82093.0, 82094.0, 82095.0, 82096.0, 82097.0, 82098.0],       [82099.0, 82100.0, 82101.0, 82102.0, 82103.0, 82104.0],       [82105.0, 82106.0, 82107.0, 82108.0, 82109.0, 82110.0]],      [[82111.0, 82112.0, 82113.0, 82114.0, 82115.0, 82116.0],       [82117.0, 82118.0, 82119.0, 82120.0, 82121.0, 82122.0],       [82123.0, 82124.0, 82125.0, 82126.0, 82127.0, 82128.0],       [82129.0, 82130.0, 82131.0, 82132.0, 82133.0, 82134.0],       [82135.0, 82136.0, 82137.0, 82138.0, 82139.0, 82140.0],       [82141.0, 82142.0, 82143.0, 82144.0, 82145.0, 82146.0],       [82147.0, 82148.0, 82149.0, 82150.0, 82151.0, 82152.0]]],     [[[82153.0, 82154.0, 82155.0, 82156.0, 82157.0, 82158.0],       [82159.0, 82160.0, 82161.0, 82162.0, 82163.0, 82164.0],       [82165.0, 82166.0, 82167.0, 82168.0, 82169.0, 82170.0],       [82171.0, 82172.0, 82173.0, 82174.0, 82175.0, 82176.0],       [82177.0, 82178.0, 82179.0, 82180.0, 82181.0, 82182.0],       [82183.0, 82184.0, 82185.0, 82186.0, 82187.0, 82188.0],       [82189.0, 82190.0, 82191.0, 82192.0, 82193.0, 82194.0]],      [[82195.0, 82196.0, 82197.0, 82198.0, 82199.0, 82200.0],       [82201.0, 82202.0, 82203.0, 82204.0, 82205.0, 82206.0],       [82207.0, 82208.0, 82209.0, 82210.0, 82211.0, 82212.0],       [82213.0, 82214.0, 82215.0, 82216.0, 82217.0, 82218.0],       [82219.0, 82220.0, 82221.0, 82222.0, 82223.0, 82224.0],       [82225.0, 82226.0, 82227.0, 82228.0, 82229.0, 82230.0],       [82231.0, 82232.0, 82233.0, 82234.0, 82235.0, 82236.0]],      [[82237.0, 82238.0, 82239.0, 82240.0, 82241.0, 82242.0],       [82243.0, 82244.0, 82245.0, 82246.0, 82247.0, 82248.0],       [82249.0, 82250.0, 82251.0, 82252.0, 82253.0, 82254.0],       [82255.0, 82256.0, 82257.0, 82258.0, 82259.0, 82260.0],       [82261.0, 82262.0, 82263.0, 82264.0, 82265.0, 82266.0],       [82267.0, 82268.0, 82269.0, 82270.0, 82271.0, 82272.0],       [82273.0, 82274.0, 82275.0, 82276.0, 82277.0, 82278.0]],      [[82279.0, 82280.0, 82281.0, 82282.0, 82283.0, 82284.0],       [82285.0, 82286.0, 82287.0, 82288.0, 82289.0, 82290.0],       [82291.0, 82292.0, 82293.0, 82294.0, 82295.0, 82296.0],       [82297.0, 82298.0, 82299.0, 82300.0, 82301.0, 82302.0],       [82303.0, 82304.0, 82305.0, 82306.0, 82307.0, 82308.0],       [82309.0, 82310.0, 82311.0, 82312.0, 82313.0, 82314.0],       [82315.0, 82316.0, 82317.0, 82318.0, 82319.0, 82320.0]],      [[82321.0, 82322.0, 82323.0, 82324.0, 82325.0, 82326.0],       [82327.0, 82328.0, 82329.0, 82330.0, 82331.0, 82332.0],       [82333.0, 82334.0, 82335.0, 82336.0, 82337.0, 82338.0],       [82339.0, 82340.0, 82341.0, 82342.0, 82343.0, 82344.0],       [82345.0, 82346.0, 82347.0, 82348.0, 82349.0, 82350.0],       [82351.0, 82352.0, 82353.0, 82354.0, 82355.0, 82356.0],       [82357.0, 82358.0, 82359.0, 82360.0, 82361.0, 82362.0]],      [[82363.0, 82364.0, 82365.0, 82366.0, 82367.0, 82368.0],       [82369.0, 82370.0, 82371.0, 82372.0, 82373.0, 82374.0],       [82375.0, 82376.0, 82377.0, 82378.0, 82379.0, 82380.0],       [82381.0, 82382.0, 82383.0, 82384.0, 82385.0, 82386.0],       [82387.0, 82388.0, 82389.0, 82390.0, 82391.0, 82392.0],       [82393.0, 82394.0, 82395.0, 82396.0, 82397.0, 82398.0],       [82399.0, 82400.0, 82401.0, 82402.0, 82403.0, 82404.0]]],     [[[82405.0, 82406.0, 82407.0, 82408.0, 82409.0, 82410.0],       [82411.0, 82412.0, 82413.0, 82414.0, 82415.0, 82416.0],       [82417.0, 82418.0, 82419.0, 82420.0, 82421.0, 82422.0],       [82423.0, 82424.0, 82425.0, 82426.0, 82427.0, 82428.0],       [82429.0, 82430.0, 82431.0, 82432.0, 82433.0, 82434.0],       [82435.0, 82436.0, 82437.0, 82438.0, 82439.0, 82440.0],       [82441.0, 82442.0, 82443.0, 82444.0, 82445.0, 82446.0]],      [[82447.0, 82448.0, 82449.0, 82450.0, 82451.0, 82452.0],       [82453.0, 82454.0, 82455.0, 82456.0, 82457.0, 82458.0],       [82459.0, 82460.0, 82461.0, 82462.0, 82463.0, 82464.0],       [82465.0, 82466.0, 82467.0, 82468.0, 82469.0, 82470.0],       [82471.0, 82472.0, 82473.0, 82474.0, 82475.0, 82476.0],       [82477.0, 82478.0, 82479.0, 82480.0, 82481.0, 82482.0],       [82483.0, 82484.0, 82485.0, 82486.0, 82487.0, 82488.0]],      [[82489.0, 82490.0, 82491.0, 82492.0, 82493.0, 82494.0],       [82495.0, 82496.0, 82497.0, 82498.0, 82499.0, 82500.0],       [82501.0, 82502.0, 82503.0, 82504.0, 82505.0, 82506.0],       [82507.0, 82508.0, 82509.0, 82510.0, 82511.0, 82512.0],       [82513.0, 82514.0, 82515.0, 82516.0, 82517.0, 82518.0],       [82519.0, 82520.0, 82521.0, 82522.0, 82523.0, 82524.0],       [82525.0, 82526.0, 82527.0, 82528.0, 82529.0, 82530.0]],      [[82531.0, 82532.0, 82533.0, 82534.0, 82535.0, 82536.0],       [82537.0, 82538.0, 82539.0, 82540.0, 82541.0, 82542.0],       [82543.0, 82544.0, 82545.0, 82546.0, 82547.0, 82548.0],       [82549.0, 82550.0, 82551.0, 82552.0, 82553.0, 82554.0],       [82555.0, 82556.0, 82557.0, 82558.0, 82559.0, 82560.0],       [82561.0, 82562.0, 82563.0, 82564.0, 82565.0, 82566.0],       [82567.0, 82568.0, 82569.0, 82570.0, 82571.0, 82572.0]],      [[82573.0, 82574.0, 82575.0, 82576.0, 82577.0, 82578.0],       [82579.0, 82580.0, 82581.0, 82582.0, 82583.0, 82584.0],       [82585.0, 82586.0, 82587.0, 82588.0, 82589.0, 82590.0],       [82591.0, 82592.0, 82593.0, 82594.0, 82595.0, 82596.0],       [82597.0, 82598.0, 82599.0, 82600.0, 82601.0, 82602.0],       [82603.0, 82604.0, 82605.0, 82606.0, 82607.0, 82608.0],       [82609.0, 82610.0, 82611.0, 82612.0, 82613.0, 82614.0]],      [[82615.0, 82616.0, 82617.0, 82618.0, 82619.0, 82620.0],       [82621.0, 82622.0, 82623.0, 82624.0, 82625.0, 82626.0],       [82627.0, 82628.0, 82629.0, 82630.0, 82631.0, 82632.0],       [82633.0, 82634.0, 82635.0, 82636.0, 82637.0, 82638.0],       [82639.0, 82640.0, 82641.0, 82642.0, 82643.0, 82644.0],       [82645.0, 82646.0, 82647.0, 82648.0, 82649.0, 82650.0],       [82651.0, 82652.0, 82653.0, 82654.0, 82655.0, 82656.0]]]],    [[[[82657.0, 82658.0, 82659.0, 82660.0, 82661.0, 82662.0],       [82663.0, 82664.0, 82665.0, 82666.0, 82667.0, 82668.0],       [82669.0, 82670.0, 82671.0, 82672.0, 82673.0, 82674.0],       [82675.0, 82676.0, 82677.0, 82678.0, 82679.0, 82680.0],       [82681.0, 82682.0, 82683.0, 82684.0, 82685.0, 82686.0],       [82687.0, 82688.0, 82689.0, 82690.0, 82691.0, 82692.0],       [82693.0, 82694.0, 82695.0, 82696.0, 82697.0, 82698.0]],      [[82699.0, 82700.0, 82701.0, 82702.0, 82703.0, 82704.0],       [82705.0, 82706.0, 82707.0, 82708.0, 82709.0, 82710.0],       [82711.0, 82712.0, 82713.0, 82714.0, 82715.0, 82716.0],       [82717.0, 82718.0, 82719.0, 82720.0, 82721.0, 82722.0],       [82723.0, 82724.0, 82725.0, 82726.0, 82727.0, 82728.0],       [82729.0, 82730.0, 82731.0, 82732.0, 82733.0, 82734.0],       [82735.0, 82736.0, 82737.0, 82738.0, 82739.0, 82740.0]],      [[82741.0, 82742.0, 82743.0, 82744.0, 82745.0, 82746.0],       [82747.0, 82748.0, 82749.0, 82750.0, 82751.0, 82752.0],       [82753.0, 82754.0, 82755.0, 82756.0, 82757.0, 82758.0],       [82759.0, 82760.0, 82761.0, 82762.0, 82763.0, 82764.0],       [82765.0, 82766.0, 82767.0, 82768.0, 82769.0, 82770.0],       [82771.0, 82772.0, 82773.0, 82774.0, 82775.0, 82776.0],       [82777.0, 82778.0, 82779.0, 82780.0, 82781.0, 82782.0]],      [[82783.0, 82784.0, 82785.0, 82786.0, 82787.0, 82788.0],       [82789.0, 82790.0, 82791.0, 82792.0, 82793.0, 82794.0],       [82795.0, 82796.0, 82797.0, 82798.0, 82799.0, 82800.0],       [82801.0, 82802.0, 82803.0, 82804.0, 82805.0, 82806.0],       [82807.0, 82808.0, 82809.0, 82810.0, 82811.0, 82812.0],       [82813.0, 82814.0, 82815.0, 82816.0, 82817.0, 82818.0],       [82819.0, 82820.0, 82821.0, 82822.0, 82823.0, 82824.0]],      [[82825.0, 82826.0, 82827.0, 82828.0, 82829.0, 82830.0],       [82831.0, 82832.0, 82833.0, 82834.0, 82835.0, 82836.0],       [82837.0, 82838.0, 82839.0, 82840.0, 82841.0, 82842.0],       [82843.0, 82844.0, 82845.0, 82846.0, 82847.0, 82848.0],       [82849.0, 82850.0, 82851.0, 82852.0, 82853.0, 82854.0],       [82855.0, 82856.0, 82857.0, 82858.0, 82859.0, 82860.0],       [82861.0, 82862.0, 82863.0, 82864.0, 82865.0, 82866.0]],      [[82867.0, 82868.0, 82869.0, 82870.0, 82871.0, 82872.0],       [82873.0, 82874.0, 82875.0, 82876.0, 82877.0, 82878.0],       [82879.0, 82880.0, 82881.0, 82882.0, 82883.0, 82884.0],       [82885.0, 82886.0, 82887.0, 82888.0, 82889.0, 82890.0],       [82891.0, 82892.0, 82893.0, 82894.0, 82895.0, 82896.0],       [82897.0, 82898.0, 82899.0, 82900.0, 82901.0, 82902.0],       [82903.0, 82904.0, 82905.0, 82906.0, 82907.0, 82908.0]]],     [[[82909.0, 82910.0, 82911.0, 82912.0, 82913.0, 82914.0],       [82915.0, 82916.0, 82917.0, 82918.0, 82919.0, 82920.0],       [82921.0, 82922.0, 82923.0, 82924.0, 82925.0, 82926.0],       [82927.0, 82928.0, 82929.0, 82930.0, 82931.0, 82932.0],       [82933.0, 82934.0, 82935.0, 82936.0, 82937.0, 82938.0],       [82939.0, 82940.0, 82941.0, 82942.0, 82943.0, 82944.0],       [82945.0, 82946.0, 82947.0, 82948.0, 82949.0, 82950.0]],      [[82951.0, 82952.0, 82953.0, 82954.0, 82955.0, 82956.0],       [82957.0, 82958.0, 82959.0, 82960.0, 82961.0, 82962.0],       [82963.0, 82964.0, 82965.0, 82966.0, 82967.0, 82968.0],       [82969.0, 82970.0, 82971.0, 82972.0, 82973.0, 82974.0],       [82975.0, 82976.0, 82977.0, 82978.0, 82979.0, 82980.0],       [82981.0, 82982.0, 82983.0, 82984.0, 82985.0, 82986.0],       [82987.0, 82988.0, 82989.0, 82990.0, 82991.0, 82992.0]],      [[82993.0, 82994.0, 82995.0, 82996.0, 82997.0, 82998.0],       [82999.0, 83000.0, 83001.0, 83002.0, 83003.0, 83004.0],       [83005.0, 83006.0, 83007.0, 83008.0, 83009.0, 83010.0],       [83011.0, 83012.0, 83013.0, 83014.0, 83015.0, 83016.0],       [83017.0, 83018.0, 83019.0, 83020.0, 83021.0, 83022.0],       [83023.0, 83024.0, 83025.0, 83026.0, 83027.0, 83028.0],       [83029.0, 83030.0, 83031.0, 83032.0, 83033.0, 83034.0]],      [[83035.0, 83036.0, 83037.0, 83038.0, 83039.0, 83040.0],       [83041.0, 83042.0, 83043.0, 83044.0, 83045.0, 83046.0],       [83047.0, 83048.0, 83049.0, 83050.0, 83051.0, 83052.0],       [83053.0, 83054.0, 83055.0, 83056.0, 83057.0, 83058.0],       [83059.0, 83060.0, 83061.0, 83062.0, 83063.0, 83064.0],       [83065.0, 83066.0, 83067.0, 83068.0, 83069.0, 83070.0],       [83071.0, 83072.0, 83073.0, 83074.0, 83075.0, 83076.0]],      [[83077.0, 83078.0, 83079.0, 83080.0, 83081.0, 83082.0],       [83083.0, 83084.0, 83085.0, 83086.0, 83087.0, 83088.0],       [83089.0, 83090.0, 83091.0, 83092.0, 83093.0, 83094.0],       [83095.0, 83096.0, 83097.0, 83098.0, 83099.0, 83100.0],       [83101.0, 83102.0, 83103.0, 83104.0, 83105.0, 83106.0],       [83107.0, 83108.0, 83109.0, 83110.0, 83111.0, 83112.0],       [83113.0, 83114.0, 83115.0, 83116.0, 83117.0, 83118.0]],      [[83119.0, 83120.0, 83121.0, 83122.0, 83123.0, 83124.0],       [83125.0, 83126.0, 83127.0, 83128.0, 83129.0, 83130.0],       [83131.0, 83132.0, 83133.0, 83134.0, 83135.0, 83136.0],       [83137.0, 83138.0, 83139.0, 83140.0, 83141.0, 83142.0],       [83143.0, 83144.0, 83145.0, 83146.0, 83147.0, 83148.0],       [83149.0, 83150.0, 83151.0, 83152.0, 83153.0, 83154.0],       [83155.0, 83156.0, 83157.0, 83158.0, 83159.0, 83160.0]]],     [[[83161.0, 83162.0, 83163.0, 83164.0, 83165.0, 83166.0],       [83167.0, 83168.0, 83169.0, 83170.0, 83171.0, 83172.0],       [83173.0, 83174.0, 83175.0, 83176.0, 83177.0, 83178.0],       [83179.0, 83180.0, 83181.0, 83182.0, 83183.0, 83184.0],       [83185.0, 83186.0, 83187.0, 83188.0, 83189.0, 83190.0],       [83191.0, 83192.0, 83193.0, 83194.0, 83195.0, 83196.0],       [83197.0, 83198.0, 83199.0, 83200.0, 83201.0, 83202.0]],      [[83203.0, 83204.0, 83205.0, 83206.0, 83207.0, 83208.0],       [83209.0, 83210.0, 83211.0, 83212.0, 83213.0, 83214.0],       [83215.0, 83216.0, 83217.0, 83218.0, 83219.0, 83220.0],       [83221.0, 83222.0, 83223.0, 83224.0, 83225.0, 83226.0],       [83227.0, 83228.0, 83229.0, 83230.0, 83231.0, 83232.0],       [83233.0, 83234.0, 83235.0, 83236.0, 83237.0, 83238.0],       [83239.0, 83240.0, 83241.0, 83242.0, 83243.0, 83244.0]],      [[83245.0, 83246.0, 83247.0, 83248.0, 83249.0, 83250.0],       [83251.0, 83252.0, 83253.0, 83254.0, 83255.0, 83256.0],       [83257.0, 83258.0, 83259.0, 83260.0, 83261.0, 83262.0],       [83263.0, 83264.0, 83265.0, 83266.0, 83267.0, 83268.0],       [83269.0, 83270.0, 83271.0, 83272.0, 83273.0, 83274.0],       [83275.0, 83276.0, 83277.0, 83278.0, 83279.0, 83280.0],       [83281.0, 83282.0, 83283.0, 83284.0, 83285.0, 83286.0]],      [[83287.0, 83288.0, 83289.0, 83290.0, 83291.0, 83292.0],       [83293.0, 83294.0, 83295.0, 83296.0, 83297.0, 83298.0],       [83299.0, 83300.0, 83301.0, 83302.0, 83303.0, 83304.0],       [83305.0, 83306.0, 83307.0, 83308.0, 83309.0, 83310.0],       [83311.0, 83312.0, 83313.0, 83314.0, 83315.0, 83316.0],       [83317.0, 83318.0, 83319.0, 83320.0, 83321.0, 83322.0],       [83323.0, 83324.0, 83325.0, 83326.0, 83327.0, 83328.0]],      [[83329.0, 83330.0, 83331.0, 83332.0, 83333.0, 83334.0],       [83335.0, 83336.0, 83337.0, 83338.0, 83339.0, 83340.0],       [83341.0, 83342.0, 83343.0, 83344.0, 83345.0, 83346.0],       [83347.0, 83348.0, 83349.0, 83350.0, 83351.0, 83352.0],       [83353.0, 83354.0, 83355.0, 83356.0, 83357.0, 83358.0],       [83359.0, 83360.0, 83361.0, 83362.0, 83363.0, 83364.0],       [83365.0, 83366.0, 83367.0, 83368.0, 83369.0, 83370.0]],      [[83371.0, 83372.0, 83373.0, 83374.0, 83375.0, 83376.0],       [83377.0, 83378.0, 83379.0, 83380.0, 83381.0, 83382.0],       [83383.0, 83384.0, 83385.0, 83386.0, 83387.0, 83388.0],       [83389.0, 83390.0, 83391.0, 83392.0, 83393.0, 83394.0],       [83395.0, 83396.0, 83397.0, 83398.0, 83399.0, 83400.0],       [83401.0, 83402.0, 83403.0, 83404.0, 83405.0, 83406.0],       [83407.0, 83408.0, 83409.0, 83410.0, 83411.0, 83412.0]]],     [[[83413.0, 83414.0, 83415.0, 83416.0, 83417.0, 83418.0],       [83419.0, 83420.0, 83421.0, 83422.0, 83423.0, 83424.0],       [83425.0, 83426.0, 83427.0, 83428.0, 83429.0, 83430.0],       [83431.0, 83432.0, 83433.0, 83434.0, 83435.0, 83436.0],       [83437.0, 83438.0, 83439.0, 83440.0, 83441.0, 83442.0],       [83443.0, 83444.0, 83445.0, 83446.0, 83447.0, 83448.0],       [83449.0, 83450.0, 83451.0, 83452.0, 83453.0, 83454.0]],      [[83455.0, 83456.0, 83457.0, 83458.0, 83459.0, 83460.0],       [83461.0, 83462.0, 83463.0, 83464.0, 83465.0, 83466.0],       [83467.0, 83468.0, 83469.0, 83470.0, 83471.0, 83472.0],       [83473.0, 83474.0, 83475.0, 83476.0, 83477.0, 83478.0],       [83479.0, 83480.0, 83481.0, 83482.0, 83483.0, 83484.0],       [83485.0, 83486.0, 83487.0, 83488.0, 83489.0, 83490.0],       [83491.0, 83492.0, 83493.0, 83494.0, 83495.0, 83496.0]],      [[83497.0, 83498.0, 83499.0, 83500.0, 83501.0, 83502.0],       [83503.0, 83504.0, 83505.0, 83506.0, 83507.0, 83508.0],       [83509.0, 83510.0, 83511.0, 83512.0, 83513.0, 83514.0],       [83515.0, 83516.0, 83517.0, 83518.0, 83519.0, 83520.0],       [83521.0, 83522.0, 83523.0, 83524.0, 83525.0, 83526.0],       [83527.0, 83528.0, 83529.0, 83530.0, 83531.0, 83532.0],       [83533.0, 83534.0, 83535.0, 83536.0, 83537.0, 83538.0]],      [[83539.0, 83540.0, 83541.0, 83542.0, 83543.0, 83544.0],       [83545.0, 83546.0, 83547.0, 83548.0, 83549.0, 83550.0],       [83551.0, 83552.0, 83553.0, 83554.0, 83555.0, 83556.0],       [83557.0, 83558.0, 83559.0, 83560.0, 83561.0, 83562.0],       [83563.0, 83564.0, 83565.0, 83566.0, 83567.0, 83568.0],       [83569.0, 83570.0, 83571.0, 83572.0, 83573.0, 83574.0],       [83575.0, 83576.0, 83577.0, 83578.0, 83579.0, 83580.0]],      [[83581.0, 83582.0, 83583.0, 83584.0, 83585.0, 83586.0],       [83587.0, 83588.0, 83589.0, 83590.0, 83591.0, 83592.0],       [83593.0, 83594.0, 83595.0, 83596.0, 83597.0, 83598.0],       [83599.0, 83600.0, 83601.0, 83602.0, 83603.0, 83604.0],       [83605.0, 83606.0, 83607.0, 83608.0, 83609.0, 83610.0],       [83611.0, 83612.0, 83613.0, 83614.0, 83615.0, 83616.0],       [83617.0, 83618.0, 83619.0, 83620.0, 83621.0, 83622.0]],      [[83623.0, 83624.0, 83625.0, 83626.0, 83627.0, 83628.0],       [83629.0, 83630.0, 83631.0, 83632.0, 83633.0, 83634.0],       [83635.0, 83636.0, 83637.0, 83638.0, 83639.0, 83640.0],       [83641.0, 83642.0, 83643.0, 83644.0, 83645.0, 83646.0],       [83647.0, 83648.0, 83649.0, 83650.0, 83651.0, 83652.0],       [83653.0, 83654.0, 83655.0, 83656.0, 83657.0, 83658.0],       [83659.0, 83660.0, 83661.0, 83662.0, 83663.0, 83664.0]]]],    [[[[83665.0, 83666.0, 83667.0, 83668.0, 83669.0, 83670.0],       [83671.0, 83672.0, 83673.0, 83674.0, 83675.0, 83676.0],       [83677.0, 83678.0, 83679.0, 83680.0, 83681.0, 83682.0],       [83683.0, 83684.0, 83685.0, 83686.0, 83687.0, 83688.0],       [83689.0, 83690.0, 83691.0, 83692.0, 83693.0, 83694.0],       [83695.0, 83696.0, 83697.0, 83698.0, 83699.0, 83700.0],       [83701.0, 83702.0, 83703.0, 83704.0, 83705.0, 83706.0]],      [[83707.0, 83708.0, 83709.0, 83710.0, 83711.0, 83712.0],       [83713.0, 83714.0, 83715.0, 83716.0, 83717.0, 83718.0],       [83719.0, 83720.0, 83721.0, 83722.0, 83723.0, 83724.0],       [83725.0, 83726.0, 83727.0, 83728.0, 83729.0, 83730.0],       [83731.0, 83732.0, 83733.0, 83734.0, 83735.0, 83736.0],       [83737.0, 83738.0, 83739.0, 83740.0, 83741.0, 83742.0],       [83743.0, 83744.0, 83745.0, 83746.0, 83747.0, 83748.0]],      [[83749.0, 83750.0, 83751.0, 83752.0, 83753.0, 83754.0],       [83755.0, 83756.0, 83757.0, 83758.0, 83759.0, 83760.0],       [83761.0, 83762.0, 83763.0, 83764.0, 83765.0, 83766.0],       [83767.0, 83768.0, 83769.0, 83770.0, 83771.0, 83772.0],       [83773.0, 83774.0, 83775.0, 83776.0, 83777.0, 83778.0],       [83779.0, 83780.0, 83781.0, 83782.0, 83783.0, 83784.0],       [83785.0, 83786.0, 83787.0, 83788.0, 83789.0, 83790.0]],      [[83791.0, 83792.0, 83793.0, 83794.0, 83795.0, 83796.0],       [83797.0, 83798.0, 83799.0, 83800.0, 83801.0, 83802.0],       [83803.0, 83804.0, 83805.0, 83806.0, 83807.0, 83808.0],       [83809.0, 83810.0, 83811.0, 83812.0, 83813.0, 83814.0],       [83815.0, 83816.0, 83817.0, 83818.0, 83819.0, 83820.0],       [83821.0, 83822.0, 83823.0, 83824.0, 83825.0, 83826.0],       [83827.0, 83828.0, 83829.0, 83830.0, 83831.0, 83832.0]],      [[83833.0, 83834.0, 83835.0, 83836.0, 83837.0, 83838.0],       [83839.0, 83840.0, 83841.0, 83842.0, 83843.0, 83844.0],       [83845.0, 83846.0, 83847.0, 83848.0, 83849.0, 83850.0],       [83851.0, 83852.0, 83853.0, 83854.0, 83855.0, 83856.0],       [83857.0, 83858.0, 83859.0, 83860.0, 83861.0, 83862.0],       [83863.0, 83864.0, 83865.0, 83866.0, 83867.0, 83868.0],       [83869.0, 83870.0, 83871.0, 83872.0, 83873.0, 83874.0]],      [[83875.0, 83876.0, 83877.0, 83878.0, 83879.0, 83880.0],       [83881.0, 83882.0, 83883.0, 83884.0, 83885.0, 83886.0],       [83887.0, 83888.0, 83889.0, 83890.0, 83891.0, 83892.0],       [83893.0, 83894.0, 83895.0, 83896.0, 83897.0, 83898.0],       [83899.0, 83900.0, 83901.0, 83902.0, 83903.0, 83904.0],       [83905.0, 83906.0, 83907.0, 83908.0, 83909.0, 83910.0],       [83911.0, 83912.0, 83913.0, 83914.0, 83915.0, 83916.0]]],     [[[83917.0, 83918.0, 83919.0, 83920.0, 83921.0, 83922.0],       [83923.0, 83924.0, 83925.0, 83926.0, 83927.0, 83928.0],       [83929.0, 83930.0, 83931.0, 83932.0, 83933.0, 83934.0],       [83935.0, 83936.0, 83937.0, 83938.0, 83939.0, 83940.0],       [83941.0, 83942.0, 83943.0, 83944.0, 83945.0, 83946.0],       [83947.0, 83948.0, 83949.0, 83950.0, 83951.0, 83952.0],       [83953.0, 83954.0, 83955.0, 83956.0, 83957.0, 83958.0]],      [[83959.0, 83960.0, 83961.0, 83962.0, 83963.0, 83964.0],       [83965.0, 83966.0, 83967.0, 83968.0, 83969.0, 83970.0],       [83971.0, 83972.0, 83973.0, 83974.0, 83975.0, 83976.0],       [83977.0, 83978.0, 83979.0, 83980.0, 83981.0, 83982.0],       [83983.0, 83984.0, 83985.0, 83986.0, 83987.0, 83988.0],       [83989.0, 83990.0, 83991.0, 83992.0, 83993.0, 83994.0],       [83995.0, 83996.0, 83997.0, 83998.0, 83999.0, 84000.0]],      [[84001.0, 84002.0, 84003.0, 84004.0, 84005.0, 84006.0],       [84007.0, 84008.0, 84009.0, 84010.0, 84011.0, 84012.0],       [84013.0, 84014.0, 84015.0, 84016.0, 84017.0, 84018.0],       [84019.0, 84020.0, 84021.0, 84022.0, 84023.0, 84024.0],       [84025.0, 84026.0, 84027.0, 84028.0, 84029.0, 84030.0],       [84031.0, 84032.0, 84033.0, 84034.0, 84035.0, 84036.0],       [84037.0, 84038.0, 84039.0, 84040.0, 84041.0, 84042.0]],      [[84043.0, 84044.0, 84045.0, 84046.0, 84047.0, 84048.0],       [84049.0, 84050.0, 84051.0, 84052.0, 84053.0, 84054.0],       [84055.0, 84056.0, 84057.0, 84058.0, 84059.0, 84060.0],       [84061.0, 84062.0, 84063.0, 84064.0, 84065.0, 84066.0],       [84067.0, 84068.0, 84069.0, 84070.0, 84071.0, 84072.0],       [84073.0, 84074.0, 84075.0, 84076.0, 84077.0, 84078.0],       [84079.0, 84080.0, 84081.0, 84082.0, 84083.0, 84084.0]],      [[84085.0, 84086.0, 84087.0, 84088.0, 84089.0, 84090.0],       [84091.0, 84092.0, 84093.0, 84094.0, 84095.0, 84096.0],       [84097.0, 84098.0, 84099.0, 84100.0, 84101.0, 84102.0],       [84103.0, 84104.0, 84105.0, 84106.0, 84107.0, 84108.0],       [84109.0, 84110.0, 84111.0, 84112.0, 84113.0, 84114.0],       [84115.0, 84116.0, 84117.0, 84118.0, 84119.0, 84120.0],       [84121.0, 84122.0, 84123.0, 84124.0, 84125.0, 84126.0]],      [[84127.0, 84128.0, 84129.0, 84130.0, 84131.0, 84132.0],       [84133.0, 84134.0, 84135.0, 84136.0, 84137.0, 84138.0],       [84139.0, 84140.0, 84141.0, 84142.0, 84143.0, 84144.0],       [84145.0, 84146.0, 84147.0, 84148.0, 84149.0, 84150.0],       [84151.0, 84152.0, 84153.0, 84154.0, 84155.0, 84156.0],       [84157.0, 84158.0, 84159.0, 84160.0, 84161.0, 84162.0],       [84163.0, 84164.0, 84165.0, 84166.0, 84167.0, 84168.0]]],     [[[84169.0, 84170.0, 84171.0, 84172.0, 84173.0, 84174.0],       [84175.0, 84176.0, 84177.0, 84178.0, 84179.0, 84180.0],       [84181.0, 84182.0, 84183.0, 84184.0, 84185.0, 84186.0],       [84187.0, 84188.0, 84189.0, 84190.0, 84191.0, 84192.0],       [84193.0, 84194.0, 84195.0, 84196.0, 84197.0, 84198.0],       [84199.0, 84200.0, 84201.0, 84202.0, 84203.0, 84204.0],       [84205.0, 84206.0, 84207.0, 84208.0, 84209.0, 84210.0]],      [[84211.0, 84212.0, 84213.0, 84214.0, 84215.0, 84216.0],       [84217.0, 84218.0, 84219.0, 84220.0, 84221.0, 84222.0],       [84223.0, 84224.0, 84225.0, 84226.0, 84227.0, 84228.0],       [84229.0, 84230.0, 84231.0, 84232.0, 84233.0, 84234.0],       [84235.0, 84236.0, 84237.0, 84238.0, 84239.0, 84240.0],       [84241.0, 84242.0, 84243.0, 84244.0, 84245.0, 84246.0],       [84247.0, 84248.0, 84249.0, 84250.0, 84251.0, 84252.0]],      [[84253.0, 84254.0, 84255.0, 84256.0, 84257.0, 84258.0],       [84259.0, 84260.0, 84261.0, 84262.0, 84263.0, 84264.0],       [84265.0, 84266.0, 84267.0, 84268.0, 84269.0, 84270.0],       [84271.0, 84272.0, 84273.0, 84274.0, 84275.0, 84276.0],       [84277.0, 84278.0, 84279.0, 84280.0, 84281.0, 84282.0],       [84283.0, 84284.0, 84285.0, 84286.0, 84287.0, 84288.0],       [84289.0, 84290.0, 84291.0, 84292.0, 84293.0, 84294.0]],      [[84295.0, 84296.0, 84297.0, 84298.0, 84299.0, 84300.0],       [84301.0, 84302.0, 84303.0, 84304.0, 84305.0, 84306.0],       [84307.0, 84308.0, 84309.0, 84310.0, 84311.0, 84312.0],       [84313.0, 84314.0, 84315.0, 84316.0, 84317.0, 84318.0],       [84319.0, 84320.0, 84321.0, 84322.0, 84323.0, 84324.0],       [84325.0, 84326.0, 84327.0, 84328.0, 84329.0, 84330.0],       [84331.0, 84332.0, 84333.0, 84334.0, 84335.0, 84336.0]],      [[84337.0, 84338.0, 84339.0, 84340.0, 84341.0, 84342.0],       [84343.0, 84344.0, 84345.0, 84346.0, 84347.0, 84348.0],       [84349.0, 84350.0, 84351.0, 84352.0, 84353.0, 84354.0],       [84355.0, 84356.0, 84357.0, 84358.0, 84359.0, 84360.0],       [84361.0, 84362.0, 84363.0, 84364.0, 84365.0, 84366.0],       [84367.0, 84368.0, 84369.0, 84370.0, 84371.0, 84372.0],       [84373.0, 84374.0, 84375.0, 84376.0, 84377.0, 84378.0]],      [[84379.0, 84380.0, 84381.0, 84382.0, 84383.0, 84384.0],       [84385.0, 84386.0, 84387.0, 84388.0, 84389.0, 84390.0],       [84391.0, 84392.0, 84393.0, 84394.0, 84395.0, 84396.0],       [84397.0, 84398.0, 84399.0, 84400.0, 84401.0, 84402.0],       [84403.0, 84404.0, 84405.0, 84406.0, 84407.0, 84408.0],       [84409.0, 84410.0, 84411.0, 84412.0, 84413.0, 84414.0],       [84415.0, 84416.0, 84417.0, 84418.0, 84419.0, 84420.0]]],     [[[84421.0, 84422.0, 84423.0, 84424.0, 84425.0, 84426.0],       [84427.0, 84428.0, 84429.0, 84430.0, 84431.0, 84432.0],       [84433.0, 84434.0, 84435.0, 84436.0, 84437.0, 84438.0],       [84439.0, 84440.0, 84441.0, 84442.0, 84443.0, 84444.0],       [84445.0, 84446.0, 84447.0, 84448.0, 84449.0, 84450.0],       [84451.0, 84452.0, 84453.0, 84454.0, 84455.0, 84456.0],       [84457.0, 84458.0, 84459.0, 84460.0, 84461.0, 84462.0]],      [[84463.0, 84464.0, 84465.0, 84466.0, 84467.0, 84468.0],       [84469.0, 84470.0, 84471.0, 84472.0, 84473.0, 84474.0],       [84475.0, 84476.0, 84477.0, 84478.0, 84479.0, 84480.0],       [84481.0, 84482.0, 84483.0, 84484.0, 84485.0, 84486.0],       [84487.0, 84488.0, 84489.0, 84490.0, 84491.0, 84492.0],       [84493.0, 84494.0, 84495.0, 84496.0, 84497.0, 84498.0],       [84499.0, 84500.0, 84501.0, 84502.0, 84503.0, 84504.0]],      [[84505.0, 84506.0, 84507.0, 84508.0, 84509.0, 84510.0],       [84511.0, 84512.0, 84513.0, 84514.0, 84515.0, 84516.0],       [84517.0, 84518.0, 84519.0, 84520.0, 84521.0, 84522.0],       [84523.0, 84524.0, 84525.0, 84526.0, 84527.0, 84528.0],       [84529.0, 84530.0, 84531.0, 84532.0, 84533.0, 84534.0],       [84535.0, 84536.0, 84537.0, 84538.0, 84539.0, 84540.0],       [84541.0, 84542.0, 84543.0, 84544.0, 84545.0, 84546.0]],      [[84547.0, 84548.0, 84549.0, 84550.0, 84551.0, 84552.0],       [84553.0, 84554.0, 84555.0, 84556.0, 84557.0, 84558.0],       [84559.0, 84560.0, 84561.0, 84562.0, 84563.0, 84564.0],       [84565.0, 84566.0, 84567.0, 84568.0, 84569.0, 84570.0],       [84571.0, 84572.0, 84573.0, 84574.0, 84575.0, 84576.0],       [84577.0, 84578.0, 84579.0, 84580.0, 84581.0, 84582.0],       [84583.0, 84584.0, 84585.0, 84586.0, 84587.0, 84588.0]],      [[84589.0, 84590.0, 84591.0, 84592.0, 84593.0, 84594.0],       [84595.0, 84596.0, 84597.0, 84598.0, 84599.0, 84600.0],       [84601.0, 84602.0, 84603.0, 84604.0, 84605.0, 84606.0],       [84607.0, 84608.0, 84609.0, 84610.0, 84611.0, 84612.0],       [84613.0, 84614.0, 84615.0, 84616.0, 84617.0, 84618.0],       [84619.0, 84620.0, 84621.0, 84622.0, 84623.0, 84624.0],       [84625.0, 84626.0, 84627.0, 84628.0, 84629.0, 84630.0]],      [[84631.0, 84632.0, 84633.0, 84634.0, 84635.0, 84636.0],       [84637.0, 84638.0, 84639.0, 84640.0, 84641.0, 84642.0],       [84643.0, 84644.0, 84645.0, 84646.0, 84647.0, 84648.0],       [84649.0, 84650.0, 84651.0, 84652.0, 84653.0, 84654.0],       [84655.0, 84656.0, 84657.0, 84658.0, 84659.0, 84660.0],       [84661.0, 84662.0, 84663.0, 84664.0, 84665.0, 84666.0],       [84667.0, 84668.0, 84669.0, 84670.0, 84671.0, 84672.0]]]]],   [[[[[84673.0, 84674.0, 84675.0, 84676.0, 84677.0, 84678.0],       [84679.0, 84680.0, 84681.0, 84682.0, 84683.0, 84684.0],       [84685.0, 84686.0, 84687.0, 84688.0, 84689.0, 84690.0],       [84691.0, 84692.0, 84693.0, 84694.0, 84695.0, 84696.0],       [84697.0, 84698.0, 84699.0, 84700.0, 84701.0, 84702.0],       [84703.0, 84704.0, 84705.0, 84706.0, 84707.0, 84708.0],       [84709.0, 84710.0, 84711.0, 84712.0, 84713.0, 84714.0]],      [[84715.0, 84716.0, 84717.0, 84718.0, 84719.0, 84720.0],       [84721.0, 84722.0, 84723.0, 84724.0, 84725.0, 84726.0],       [84727.0, 84728.0, 84729.0, 84730.0, 84731.0, 84732.0],       [84733.0, 84734.0, 84735.0, 84736.0, 84737.0, 84738.0],       [84739.0, 84740.0, 84741.0, 84742.0, 84743.0, 84744.0],       [84745.0, 84746.0, 84747.0, 84748.0, 84749.0, 84750.0],       [84751.0, 84752.0, 84753.0, 84754.0, 84755.0, 84756.0]],      [[84757.0, 84758.0, 84759.0, 84760.0, 84761.0, 84762.0],       [84763.0, 84764.0, 84765.0, 84766.0, 84767.0, 84768.0],       [84769.0, 84770.0, 84771.0, 84772.0, 84773.0, 84774.0],       [84775.0, 84776.0, 84777.0, 84778.0, 84779.0, 84780.0],       [84781.0, 84782.0, 84783.0, 84784.0, 84785.0, 84786.0],       [84787.0, 84788.0, 84789.0, 84790.0, 84791.0, 84792.0],       [84793.0, 84794.0, 84795.0, 84796.0, 84797.0, 84798.0]],      [[84799.0, 84800.0, 84801.0, 84802.0, 84803.0, 84804.0],       [84805.0, 84806.0, 84807.0, 84808.0, 84809.0, 84810.0],       [84811.0, 84812.0, 84813.0, 84814.0, 84815.0, 84816.0],       [84817.0, 84818.0, 84819.0, 84820.0, 84821.0, 84822.0],       [84823.0, 84824.0, 84825.0, 84826.0, 84827.0, 84828.0],       [84829.0, 84830.0, 84831.0, 84832.0, 84833.0, 84834.0],       [84835.0, 84836.0, 84837.0, 84838.0, 84839.0, 84840.0]],      [[84841.0, 84842.0, 84843.0, 84844.0, 84845.0, 84846.0],       [84847.0, 84848.0, 84849.0, 84850.0, 84851.0, 84852.0],       [84853.0, 84854.0, 84855.0, 84856.0, 84857.0, 84858.0],       [84859.0, 84860.0, 84861.0, 84862.0, 84863.0, 84864.0],       [84865.0, 84866.0, 84867.0, 84868.0, 84869.0, 84870.0],       [84871.0, 84872.0, 84873.0, 84874.0, 84875.0, 84876.0],       [84877.0, 84878.0, 84879.0, 84880.0, 84881.0, 84882.0]],      [[84883.0, 84884.0, 84885.0, 84886.0, 84887.0, 84888.0],       [84889.0, 84890.0, 84891.0, 84892.0, 84893.0, 84894.0],       [84895.0, 84896.0, 84897.0, 84898.0, 84899.0, 84900.0],       [84901.0, 84902.0, 84903.0, 84904.0, 84905.0, 84906.0],       [84907.0, 84908.0, 84909.0, 84910.0, 84911.0, 84912.0],       [84913.0, 84914.0, 84915.0, 84916.0, 84917.0, 84918.0],       [84919.0, 84920.0, 84921.0, 84922.0, 84923.0, 84924.0]]],     [[[84925.0, 84926.0, 84927.0, 84928.0, 84929.0, 84930.0],       [84931.0, 84932.0, 84933.0, 84934.0, 84935.0, 84936.0],       [84937.0, 84938.0, 84939.0, 84940.0, 84941.0, 84942.0],       [84943.0, 84944.0, 84945.0, 84946.0, 84947.0, 84948.0],       [84949.0, 84950.0, 84951.0, 84952.0, 84953.0, 84954.0],       [84955.0, 84956.0, 84957.0, 84958.0, 84959.0, 84960.0],       [84961.0, 84962.0, 84963.0, 84964.0, 84965.0, 84966.0]],      [[84967.0, 84968.0, 84969.0, 84970.0, 84971.0, 84972.0],       [84973.0, 84974.0, 84975.0, 84976.0, 84977.0, 84978.0],       [84979.0, 84980.0, 84981.0, 84982.0, 84983.0, 84984.0],       [84985.0, 84986.0, 84987.0, 84988.0, 84989.0, 84990.0],       [84991.0, 84992.0, 84993.0, 84994.0, 84995.0, 84996.0],       [84997.0, 84998.0, 84999.0, 85000.0, 85001.0, 85002.0],       [85003.0, 85004.0, 85005.0, 85006.0, 85007.0, 85008.0]],      [[85009.0, 85010.0, 85011.0, 85012.0, 85013.0, 85014.0],       [85015.0, 85016.0, 85017.0, 85018.0, 85019.0, 85020.0],       [85021.0, 85022.0, 85023.0, 85024.0, 85025.0, 85026.0],       [85027.0, 85028.0, 85029.0, 85030.0, 85031.0, 85032.0],       [85033.0, 85034.0, 85035.0, 85036.0, 85037.0, 85038.0],       [85039.0, 85040.0, 85041.0, 85042.0, 85043.0, 85044.0],       [85045.0, 85046.0, 85047.0, 85048.0, 85049.0, 85050.0]],      [[85051.0, 85052.0, 85053.0, 85054.0, 85055.0, 85056.0],       [85057.0, 85058.0, 85059.0, 85060.0, 85061.0, 85062.0],       [85063.0, 85064.0, 85065.0, 85066.0, 85067.0, 85068.0],       [85069.0, 85070.0, 85071.0, 85072.0, 85073.0, 85074.0],       [85075.0, 85076.0, 85077.0, 85078.0, 85079.0, 85080.0],       [85081.0, 85082.0, 85083.0, 85084.0, 85085.0, 85086.0],       [85087.0, 85088.0, 85089.0, 85090.0, 85091.0, 85092.0]],      [[85093.0, 85094.0, 85095.0, 85096.0, 85097.0, 85098.0],       [85099.0, 85100.0, 85101.0, 85102.0, 85103.0, 85104.0],       [85105.0, 85106.0, 85107.0, 85108.0, 85109.0, 85110.0],       [85111.0, 85112.0, 85113.0, 85114.0, 85115.0, 85116.0],       [85117.0, 85118.0, 85119.0, 85120.0, 85121.0, 85122.0],       [85123.0, 85124.0, 85125.0, 85126.0, 85127.0, 85128.0],       [85129.0, 85130.0, 85131.0, 85132.0, 85133.0, 85134.0]],      [[85135.0, 85136.0, 85137.0, 85138.0, 85139.0, 85140.0],       [85141.0, 85142.0, 85143.0, 85144.0, 85145.0, 85146.0],       [85147.0, 85148.0, 85149.0, 85150.0, 85151.0, 85152.0],       [85153.0, 85154.0, 85155.0, 85156.0, 85157.0, 85158.0],       [85159.0, 85160.0, 85161.0, 85162.0, 85163.0, 85164.0],       [85165.0, 85166.0, 85167.0, 85168.0, 85169.0, 85170.0],       [85171.0, 85172.0, 85173.0, 85174.0, 85175.0, 85176.0]]],     [[[85177.0, 85178.0, 85179.0, 85180.0, 85181.0, 85182.0],       [85183.0, 85184.0, 85185.0, 85186.0, 85187.0, 85188.0],       [85189.0, 85190.0, 85191.0, 85192.0, 85193.0, 85194.0],       [85195.0, 85196.0, 85197.0, 85198.0, 85199.0, 85200.0],       [85201.0, 85202.0, 85203.0, 85204.0, 85205.0, 85206.0],       [85207.0, 85208.0, 85209.0, 85210.0, 85211.0, 85212.0],       [85213.0, 85214.0, 85215.0, 85216.0, 85217.0, 85218.0]],      [[85219.0, 85220.0, 85221.0, 85222.0, 85223.0, 85224.0],       [85225.0, 85226.0, 85227.0, 85228.0, 85229.0, 85230.0],       [85231.0, 85232.0, 85233.0, 85234.0, 85235.0, 85236.0],       [85237.0, 85238.0, 85239.0, 85240.0, 85241.0, 85242.0],       [85243.0, 85244.0, 85245.0, 85246.0, 85247.0, 85248.0],       [85249.0, 85250.0, 85251.0, 85252.0, 85253.0, 85254.0],       [85255.0, 85256.0, 85257.0, 85258.0, 85259.0, 85260.0]],      [[85261.0, 85262.0, 85263.0, 85264.0, 85265.0, 85266.0],       [85267.0, 85268.0, 85269.0, 85270.0, 85271.0, 85272.0],       [85273.0, 85274.0, 85275.0, 85276.0, 85277.0, 85278.0],       [85279.0, 85280.0, 85281.0, 85282.0, 85283.0, 85284.0],       [85285.0, 85286.0, 85287.0, 85288.0, 85289.0, 85290.0],       [85291.0, 85292.0, 85293.0, 85294.0, 85295.0, 85296.0],       [85297.0, 85298.0, 85299.0, 85300.0, 85301.0, 85302.0]],      [[85303.0, 85304.0, 85305.0, 85306.0, 85307.0, 85308.0],       [85309.0, 85310.0, 85311.0, 85312.0, 85313.0, 85314.0],       [85315.0, 85316.0, 85317.0, 85318.0, 85319.0, 85320.0],       [85321.0, 85322.0, 85323.0, 85324.0, 85325.0, 85326.0],       [85327.0, 85328.0, 85329.0, 85330.0, 85331.0, 85332.0],       [85333.0, 85334.0, 85335.0, 85336.0, 85337.0, 85338.0],       [85339.0, 85340.0, 85341.0, 85342.0, 85343.0, 85344.0]],      [[85345.0, 85346.0, 85347.0, 85348.0, 85349.0, 85350.0],       [85351.0, 85352.0, 85353.0, 85354.0, 85355.0, 85356.0],       [85357.0, 85358.0, 85359.0, 85360.0, 85361.0, 85362.0],       [85363.0, 85364.0, 85365.0, 85366.0, 85367.0, 85368.0],       [85369.0, 85370.0, 85371.0, 85372.0, 85373.0, 85374.0],       [85375.0, 85376.0, 85377.0, 85378.0, 85379.0, 85380.0],       [85381.0, 85382.0, 85383.0, 85384.0, 85385.0, 85386.0]],      [[85387.0, 85388.0, 85389.0, 85390.0, 85391.0, 85392.0],       [85393.0, 85394.0, 85395.0, 85396.0, 85397.0, 85398.0],       [85399.0, 85400.0, 85401.0, 85402.0, 85403.0, 85404.0],       [85405.0, 85406.0, 85407.0, 85408.0, 85409.0, 85410.0],       [85411.0, 85412.0, 85413.0, 85414.0, 85415.0, 85416.0],       [85417.0, 85418.0, 85419.0, 85420.0, 85421.0, 85422.0],       [85423.0, 85424.0, 85425.0, 85426.0, 85427.0, 85428.0]]],     [[[85429.0, 85430.0, 85431.0, 85432.0, 85433.0, 85434.0],       [85435.0, 85436.0, 85437.0, 85438.0, 85439.0, 85440.0],       [85441.0, 85442.0, 85443.0, 85444.0, 85445.0, 85446.0],       [85447.0, 85448.0, 85449.0, 85450.0, 85451.0, 85452.0],       [85453.0, 85454.0, 85455.0, 85456.0, 85457.0, 85458.0],       [85459.0, 85460.0, 85461.0, 85462.0, 85463.0, 85464.0],       [85465.0, 85466.0, 85467.0, 85468.0, 85469.0, 85470.0]],      [[85471.0, 85472.0, 85473.0, 85474.0, 85475.0, 85476.0],       [85477.0, 85478.0, 85479.0, 85480.0, 85481.0, 85482.0],       [85483.0, 85484.0, 85485.0, 85486.0, 85487.0, 85488.0],       [85489.0, 85490.0, 85491.0, 85492.0, 85493.0, 85494.0],       [85495.0, 85496.0, 85497.0, 85498.0, 85499.0, 85500.0],       [85501.0, 85502.0, 85503.0, 85504.0, 85505.0, 85506.0],       [85507.0, 85508.0, 85509.0, 85510.0, 85511.0, 85512.0]],      [[85513.0, 85514.0, 85515.0, 85516.0, 85517.0, 85518.0],       [85519.0, 85520.0, 85521.0, 85522.0, 85523.0, 85524.0],       [85525.0, 85526.0, 85527.0, 85528.0, 85529.0, 85530.0],       [85531.0, 85532.0, 85533.0, 85534.0, 85535.0, 85536.0],       [85537.0, 85538.0, 85539.0, 85540.0, 85541.0, 85542.0],       [85543.0, 85544.0, 85545.0, 85546.0, 85547.0, 85548.0],       [85549.0, 85550.0, 85551.0, 85552.0, 85553.0, 85554.0]],      [[85555.0, 85556.0, 85557.0, 85558.0, 85559.0, 85560.0],       [85561.0, 85562.0, 85563.0, 85564.0, 85565.0, 85566.0],       [85567.0, 85568.0, 85569.0, 85570.0, 85571.0, 85572.0],       [85573.0, 85574.0, 85575.0, 85576.0, 85577.0, 85578.0],       [85579.0, 85580.0, 85581.0, 85582.0, 85583.0, 85584.0],       [85585.0, 85586.0, 85587.0, 85588.0, 85589.0, 85590.0],       [85591.0, 85592.0, 85593.0, 85594.0, 85595.0, 85596.0]],      [[85597.0, 85598.0, 85599.0, 85600.0, 85601.0, 85602.0],       [85603.0, 85604.0, 85605.0, 85606.0, 85607.0, 85608.0],       [85609.0, 85610.0, 85611.0, 85612.0, 85613.0, 85614.0],       [85615.0, 85616.0, 85617.0, 85618.0, 85619.0, 85620.0],       [85621.0, 85622.0, 85623.0, 85624.0, 85625.0, 85626.0],       [85627.0, 85628.0, 85629.0, 85630.0, 85631.0, 85632.0],       [85633.0, 85634.0, 85635.0, 85636.0, 85637.0, 85638.0]],      [[85639.0, 85640.0, 85641.0, 85642.0, 85643.0, 85644.0],       [85645.0, 85646.0, 85647.0, 85648.0, 85649.0, 85650.0],       [85651.0, 85652.0, 85653.0, 85654.0, 85655.0, 85656.0],       [85657.0, 85658.0, 85659.0, 85660.0, 85661.0, 85662.0],       [85663.0, 85664.0, 85665.0, 85666.0, 85667.0, 85668.0],       [85669.0, 85670.0, 85671.0, 85672.0, 85673.0, 85674.0],       [85675.0, 85676.0, 85677.0, 85678.0, 85679.0, 85680.0]]]],    [[[[85681.0, 85682.0, 85683.0, 85684.0, 85685.0, 85686.0],       [85687.0, 85688.0, 85689.0, 85690.0, 85691.0, 85692.0],       [85693.0, 85694.0, 85695.0, 85696.0, 85697.0, 85698.0],       [85699.0, 85700.0, 85701.0, 85702.0, 85703.0, 85704.0],       [85705.0, 85706.0, 85707.0, 85708.0, 85709.0, 85710.0],       [85711.0, 85712.0, 85713.0, 85714.0, 85715.0, 85716.0],       [85717.0, 85718.0, 85719.0, 85720.0, 85721.0, 85722.0]],      [[85723.0, 85724.0, 85725.0, 85726.0, 85727.0, 85728.0],       [85729.0, 85730.0, 85731.0, 85732.0, 85733.0, 85734.0],       [85735.0, 85736.0, 85737.0, 85738.0, 85739.0, 85740.0],       [85741.0, 85742.0, 85743.0, 85744.0, 85745.0, 85746.0],       [85747.0, 85748.0, 85749.0, 85750.0, 85751.0, 85752.0],       [85753.0, 85754.0, 85755.0, 85756.0, 85757.0, 85758.0],       [85759.0, 85760.0, 85761.0, 85762.0, 85763.0, 85764.0]],      [[85765.0, 85766.0, 85767.0, 85768.0, 85769.0, 85770.0],       [85771.0, 85772.0, 85773.0, 85774.0, 85775.0, 85776.0],       [85777.0, 85778.0, 85779.0, 85780.0, 85781.0, 85782.0],       [85783.0, 85784.0, 85785.0, 85786.0, 85787.0, 85788.0],       [85789.0, 85790.0, 85791.0, 85792.0, 85793.0, 85794.0],       [85795.0, 85796.0, 85797.0, 85798.0, 85799.0, 85800.0],       [85801.0, 85802.0, 85803.0, 85804.0, 85805.0, 85806.0]],      [[85807.0, 85808.0, 85809.0, 85810.0, 85811.0, 85812.0],       [85813.0, 85814.0, 85815.0, 85816.0, 85817.0, 85818.0],       [85819.0, 85820.0, 85821.0, 85822.0, 85823.0, 85824.0],       [85825.0, 85826.0, 85827.0, 85828.0, 85829.0, 85830.0],       [85831.0, 85832.0, 85833.0, 85834.0, 85835.0, 85836.0],       [85837.0, 85838.0, 85839.0, 85840.0, 85841.0, 85842.0],       [85843.0, 85844.0, 85845.0, 85846.0, 85847.0, 85848.0]],      [[85849.0, 85850.0, 85851.0, 85852.0, 85853.0, 85854.0],       [85855.0, 85856.0, 85857.0, 85858.0, 85859.0, 85860.0],       [85861.0, 85862.0, 85863.0, 85864.0, 85865.0, 85866.0],       [85867.0, 85868.0, 85869.0, 85870.0, 85871.0, 85872.0],       [85873.0, 85874.0, 85875.0, 85876.0, 85877.0, 85878.0],       [85879.0, 85880.0, 85881.0, 85882.0, 85883.0, 85884.0],       [85885.0, 85886.0, 85887.0, 85888.0, 85889.0, 85890.0]],      [[85891.0, 85892.0, 85893.0, 85894.0, 85895.0, 85896.0],       [85897.0, 85898.0, 85899.0, 85900.0, 85901.0, 85902.0],       [85903.0, 85904.0, 85905.0, 85906.0, 85907.0, 85908.0],       [85909.0, 85910.0, 85911.0, 85912.0, 85913.0, 85914.0],       [85915.0, 85916.0, 85917.0, 85918.0, 85919.0, 85920.0],       [85921.0, 85922.0, 85923.0, 85924.0, 85925.0, 85926.0],       [85927.0, 85928.0, 85929.0, 85930.0, 85931.0, 85932.0]]],     [[[85933.0, 85934.0, 85935.0, 85936.0, 85937.0, 85938.0],       [85939.0, 85940.0, 85941.0, 85942.0, 85943.0, 85944.0],       [85945.0, 85946.0, 85947.0, 85948.0, 85949.0, 85950.0],       [85951.0, 85952.0, 85953.0, 85954.0, 85955.0, 85956.0],       [85957.0, 85958.0, 85959.0, 85960.0, 85961.0, 85962.0],       [85963.0, 85964.0, 85965.0, 85966.0, 85967.0, 85968.0],       [85969.0, 85970.0, 85971.0, 85972.0, 85973.0, 85974.0]],      [[85975.0, 85976.0, 85977.0, 85978.0, 85979.0, 85980.0],       [85981.0, 85982.0, 85983.0, 85984.0, 85985.0, 85986.0],       [85987.0, 85988.0, 85989.0, 85990.0, 85991.0, 85992.0],       [85993.0, 85994.0, 85995.0, 85996.0, 85997.0, 85998.0],       [85999.0, 86000.0, 86001.0, 86002.0, 86003.0, 86004.0],       [86005.0, 86006.0, 86007.0, 86008.0, 86009.0, 86010.0],       [86011.0, 86012.0, 86013.0, 86014.0, 86015.0, 86016.0]],      [[86017.0, 86018.0, 86019.0, 86020.0, 86021.0, 86022.0],       [86023.0, 86024.0, 86025.0, 86026.0, 86027.0, 86028.0],       [86029.0, 86030.0, 86031.0, 86032.0, 86033.0, 86034.0],       [86035.0, 86036.0, 86037.0, 86038.0, 86039.0, 86040.0],       [86041.0, 86042.0, 86043.0, 86044.0, 86045.0, 86046.0],       [86047.0, 86048.0, 86049.0, 86050.0, 86051.0, 86052.0],       [86053.0, 86054.0, 86055.0, 86056.0, 86057.0, 86058.0]],      [[86059.0, 86060.0, 86061.0, 86062.0, 86063.0, 86064.0],       [86065.0, 86066.0, 86067.0, 86068.0, 86069.0, 86070.0],       [86071.0, 86072.0, 86073.0, 86074.0, 86075.0, 86076.0],       [86077.0, 86078.0, 86079.0, 86080.0, 86081.0, 86082.0],       [86083.0, 86084.0, 86085.0, 86086.0, 86087.0, 86088.0],       [86089.0, 86090.0, 86091.0, 86092.0, 86093.0, 86094.0],       [86095.0, 86096.0, 86097.0, 86098.0, 86099.0, 86100.0]],      [[86101.0, 86102.0, 86103.0, 86104.0, 86105.0, 86106.0],       [86107.0, 86108.0, 86109.0, 86110.0, 86111.0, 86112.0],       [86113.0, 86114.0, 86115.0, 86116.0, 86117.0, 86118.0],       [86119.0, 86120.0, 86121.0, 86122.0, 86123.0, 86124.0],       [86125.0, 86126.0, 86127.0, 86128.0, 86129.0, 86130.0],       [86131.0, 86132.0, 86133.0, 86134.0, 86135.0, 86136.0],       [86137.0, 86138.0, 86139.0, 86140.0, 86141.0, 86142.0]],      [[86143.0, 86144.0, 86145.0, 86146.0, 86147.0, 86148.0],       [86149.0, 86150.0, 86151.0, 86152.0, 86153.0, 86154.0],       [86155.0, 86156.0, 86157.0, 86158.0, 86159.0, 86160.0],       [86161.0, 86162.0, 86163.0, 86164.0, 86165.0, 86166.0],       [86167.0, 86168.0, 86169.0, 86170.0, 86171.0, 86172.0],       [86173.0, 86174.0, 86175.0, 86176.0, 86177.0, 86178.0],       [86179.0, 86180.0, 86181.0, 86182.0, 86183.0, 86184.0]]],     [[[86185.0, 86186.0, 86187.0, 86188.0, 86189.0, 86190.0],       [86191.0, 86192.0, 86193.0, 86194.0, 86195.0, 86196.0],       [86197.0, 86198.0, 86199.0, 86200.0, 86201.0, 86202.0],       [86203.0, 86204.0, 86205.0, 86206.0, 86207.0, 86208.0],       [86209.0, 86210.0, 86211.0, 86212.0, 86213.0, 86214.0],       [86215.0, 86216.0, 86217.0, 86218.0, 86219.0, 86220.0],       [86221.0, 86222.0, 86223.0, 86224.0, 86225.0, 86226.0]],      [[86227.0, 86228.0, 86229.0, 86230.0, 86231.0, 86232.0],       [86233.0, 86234.0, 86235.0, 86236.0, 86237.0, 86238.0],       [86239.0, 86240.0, 86241.0, 86242.0, 86243.0, 86244.0],       [86245.0, 86246.0, 86247.0, 86248.0, 86249.0, 86250.0],       [86251.0, 86252.0, 86253.0, 86254.0, 86255.0, 86256.0],       [86257.0, 86258.0, 86259.0, 86260.0, 86261.0, 86262.0],       [86263.0, 86264.0, 86265.0, 86266.0, 86267.0, 86268.0]],      [[86269.0, 86270.0, 86271.0, 86272.0, 86273.0, 86274.0],       [86275.0, 86276.0, 86277.0, 86278.0, 86279.0, 86280.0],       [86281.0, 86282.0, 86283.0, 86284.0, 86285.0, 86286.0],       [86287.0, 86288.0, 86289.0, 86290.0, 86291.0, 86292.0],       [86293.0, 86294.0, 86295.0, 86296.0, 86297.0, 86298.0],       [86299.0, 86300.0, 86301.0, 86302.0, 86303.0, 86304.0],       [86305.0, 86306.0, 86307.0, 86308.0, 86309.0, 86310.0]],      [[86311.0, 86312.0, 86313.0, 86314.0, 86315.0, 86316.0],       [86317.0, 86318.0, 86319.0, 86320.0, 86321.0, 86322.0],       [86323.0, 86324.0, 86325.0, 86326.0, 86327.0, 86328.0],       [86329.0, 86330.0, 86331.0, 86332.0, 86333.0, 86334.0],       [86335.0, 86336.0, 86337.0, 86338.0, 86339.0, 86340.0],       [86341.0, 86342.0, 86343.0, 86344.0, 86345.0, 86346.0],       [86347.0, 86348.0, 86349.0, 86350.0, 86351.0, 86352.0]],      [[86353.0, 86354.0, 86355.0, 86356.0, 86357.0, 86358.0],       [86359.0, 86360.0, 86361.0, 86362.0, 86363.0, 86364.0],       [86365.0, 86366.0, 86367.0, 86368.0, 86369.0, 86370.0],       [86371.0, 86372.0, 86373.0, 86374.0, 86375.0, 86376.0],       [86377.0, 86378.0, 86379.0, 86380.0, 86381.0, 86382.0],       [86383.0, 86384.0, 86385.0, 86386.0, 86387.0, 86388.0],       [86389.0, 86390.0, 86391.0, 86392.0, 86393.0, 86394.0]],      [[86395.0, 86396.0, 86397.0, 86398.0, 86399.0, 86400.0],       [86401.0, 86402.0, 86403.0, 86404.0, 86405.0, 86406.0],       [86407.0, 86408.0, 86409.0, 86410.0, 86411.0, 86412.0],       [86413.0, 86414.0, 86415.0, 86416.0, 86417.0, 86418.0],       [86419.0, 86420.0, 86421.0, 86422.0, 86423.0, 86424.0],       [86425.0, 86426.0, 86427.0, 86428.0, 86429.0, 86430.0],       [86431.0, 86432.0, 86433.0, 86434.0, 86435.0, 86436.0]]],     [[[86437.0, 86438.0, 86439.0, 86440.0, 86441.0, 86442.0],       [86443.0, 86444.0, 86445.0, 86446.0, 86447.0, 86448.0],       [86449.0, 86450.0, 86451.0, 86452.0, 86453.0, 86454.0],       [86455.0, 86456.0, 86457.0, 86458.0, 86459.0, 86460.0],       [86461.0, 86462.0, 86463.0, 86464.0, 86465.0, 86466.0],       [86467.0, 86468.0, 86469.0, 86470.0, 86471.0, 86472.0],       [86473.0, 86474.0, 86475.0, 86476.0, 86477.0, 86478.0]],      [[86479.0, 86480.0, 86481.0, 86482.0, 86483.0, 86484.0],       [86485.0, 86486.0, 86487.0, 86488.0, 86489.0, 86490.0],       [86491.0, 86492.0, 86493.0, 86494.0, 86495.0, 86496.0],       [86497.0, 86498.0, 86499.0, 86500.0, 86501.0, 86502.0],       [86503.0, 86504.0, 86505.0, 86506.0, 86507.0, 86508.0],       [86509.0, 86510.0, 86511.0, 86512.0, 86513.0, 86514.0],       [86515.0, 86516.0, 86517.0, 86518.0, 86519.0, 86520.0]],      [[86521.0, 86522.0, 86523.0, 86524.0, 86525.0, 86526.0],       [86527.0, 86528.0, 86529.0, 86530.0, 86531.0, 86532.0],       [86533.0, 86534.0, 86535.0, 86536.0, 86537.0, 86538.0],       [86539.0, 86540.0, 86541.0, 86542.0, 86543.0, 86544.0],       [86545.0, 86546.0, 86547.0, 86548.0, 86549.0, 86550.0],       [86551.0, 86552.0, 86553.0, 86554.0, 86555.0, 86556.0],       [86557.0, 86558.0, 86559.0, 86560.0, 86561.0, 86562.0]],      [[86563.0, 86564.0, 86565.0, 86566.0, 86567.0, 86568.0],       [86569.0, 86570.0, 86571.0, 86572.0, 86573.0, 86574.0],       [86575.0, 86576.0, 86577.0, 86578.0, 86579.0, 86580.0],       [86581.0, 86582.0, 86583.0, 86584.0, 86585.0, 86586.0],       [86587.0, 86588.0, 86589.0, 86590.0, 86591.0, 86592.0],       [86593.0, 86594.0, 86595.0, 86596.0, 86597.0, 86598.0],       [86599.0, 86600.0, 86601.0, 86602.0, 86603.0, 86604.0]],      [[86605.0, 86606.0, 86607.0, 86608.0, 86609.0, 86610.0],       [86611.0, 86612.0, 86613.0, 86614.0, 86615.0, 86616.0],       [86617.0, 86618.0, 86619.0, 86620.0, 86621.0, 86622.0],       [86623.0, 86624.0, 86625.0, 86626.0, 86627.0, 86628.0],       [86629.0, 86630.0, 86631.0, 86632.0, 86633.0, 86634.0],       [86635.0, 86636.0, 86637.0, 86638.0, 86639.0, 86640.0],       [86641.0, 86642.0, 86643.0, 86644.0, 86645.0, 86646.0]],      [[86647.0, 86648.0, 86649.0, 86650.0, 86651.0, 86652.0],       [86653.0, 86654.0, 86655.0, 86656.0, 86657.0, 86658.0],       [86659.0, 86660.0, 86661.0, 86662.0, 86663.0, 86664.0],       [86665.0, 86666.0, 86667.0, 86668.0, 86669.0, 86670.0],       [86671.0, 86672.0, 86673.0, 86674.0, 86675.0, 86676.0],       [86677.0, 86678.0, 86679.0, 86680.0, 86681.0, 86682.0],       [86683.0, 86684.0, 86685.0, 86686.0, 86687.0, 86688.0]]]],    [[[[86689.0, 86690.0, 86691.0, 86692.0, 86693.0, 86694.0],       [86695.0, 86696.0, 86697.0, 86698.0, 86699.0, 86700.0],       [86701.0, 86702.0, 86703.0, 86704.0, 86705.0, 86706.0],       [86707.0, 86708.0, 86709.0, 86710.0, 86711.0, 86712.0],       [86713.0, 86714.0, 86715.0, 86716.0, 86717.0, 86718.0],       [86719.0, 86720.0, 86721.0, 86722.0, 86723.0, 86724.0],       [86725.0, 86726.0, 86727.0, 86728.0, 86729.0, 86730.0]],      [[86731.0, 86732.0, 86733.0, 86734.0, 86735.0, 86736.0],       [86737.0, 86738.0, 86739.0, 86740.0, 86741.0, 86742.0],       [86743.0, 86744.0, 86745.0, 86746.0, 86747.0, 86748.0],       [86749.0, 86750.0, 86751.0, 86752.0, 86753.0, 86754.0],       [86755.0, 86756.0, 86757.0, 86758.0, 86759.0, 86760.0],       [86761.0, 86762.0, 86763.0, 86764.0, 86765.0, 86766.0],       [86767.0, 86768.0, 86769.0, 86770.0, 86771.0, 86772.0]],      [[86773.0, 86774.0, 86775.0, 86776.0, 86777.0, 86778.0],       [86779.0, 86780.0, 86781.0, 86782.0, 86783.0, 86784.0],       [86785.0, 86786.0, 86787.0, 86788.0, 86789.0, 86790.0],       [86791.0, 86792.0, 86793.0, 86794.0, 86795.0, 86796.0],       [86797.0, 86798.0, 86799.0, 86800.0, 86801.0, 86802.0],       [86803.0, 86804.0, 86805.0, 86806.0, 86807.0, 86808.0],       [86809.0, 86810.0, 86811.0, 86812.0, 86813.0, 86814.0]],      [[86815.0, 86816.0, 86817.0, 86818.0, 86819.0, 86820.0],       [86821.0, 86822.0, 86823.0, 86824.0, 86825.0, 86826.0],       [86827.0, 86828.0, 86829.0, 86830.0, 86831.0, 86832.0],       [86833.0, 86834.0, 86835.0, 86836.0, 86837.0, 86838.0],       [86839.0, 86840.0, 86841.0, 86842.0, 86843.0, 86844.0],       [86845.0, 86846.0, 86847.0, 86848.0, 86849.0, 86850.0],       [86851.0, 86852.0, 86853.0, 86854.0, 86855.0, 86856.0]],      [[86857.0, 86858.0, 86859.0, 86860.0, 86861.0, 86862.0],       [86863.0, 86864.0, 86865.0, 86866.0, 86867.0, 86868.0],       [86869.0, 86870.0, 86871.0, 86872.0, 86873.0, 86874.0],       [86875.0, 86876.0, 86877.0, 86878.0, 86879.0, 86880.0],       [86881.0, 86882.0, 86883.0, 86884.0, 86885.0, 86886.0],       [86887.0, 86888.0, 86889.0, 86890.0, 86891.0, 86892.0],       [86893.0, 86894.0, 86895.0, 86896.0, 86897.0, 86898.0]],      [[86899.0, 86900.0, 86901.0, 86902.0, 86903.0, 86904.0],       [86905.0, 86906.0, 86907.0, 86908.0, 86909.0, 86910.0],       [86911.0, 86912.0, 86913.0, 86914.0, 86915.0, 86916.0],       [86917.0, 86918.0, 86919.0, 86920.0, 86921.0, 86922.0],       [86923.0, 86924.0, 86925.0, 86926.0, 86927.0, 86928.0],       [86929.0, 86930.0, 86931.0, 86932.0, 86933.0, 86934.0],       [86935.0, 86936.0, 86937.0, 86938.0, 86939.0, 86940.0]]],     [[[86941.0, 86942.0, 86943.0, 86944.0, 86945.0, 86946.0],       [86947.0, 86948.0, 86949.0, 86950.0, 86951.0, 86952.0],       [86953.0, 86954.0, 86955.0, 86956.0, 86957.0, 86958.0],       [86959.0, 86960.0, 86961.0, 86962.0, 86963.0, 86964.0],       [86965.0, 86966.0, 86967.0, 86968.0, 86969.0, 86970.0],       [86971.0, 86972.0, 86973.0, 86974.0, 86975.0, 86976.0],       [86977.0, 86978.0, 86979.0, 86980.0, 86981.0, 86982.0]],      [[86983.0, 86984.0, 86985.0, 86986.0, 86987.0, 86988.0],       [86989.0, 86990.0, 86991.0, 86992.0, 86993.0, 86994.0],       [86995.0, 86996.0, 86997.0, 86998.0, 86999.0, 87000.0],       [87001.0, 87002.0, 87003.0, 87004.0, 87005.0, 87006.0],       [87007.0, 87008.0, 87009.0, 87010.0, 87011.0, 87012.0],       [87013.0, 87014.0, 87015.0, 87016.0, 87017.0, 87018.0],       [87019.0, 87020.0, 87021.0, 87022.0, 87023.0, 87024.0]],      [[87025.0, 87026.0, 87027.0, 87028.0, 87029.0, 87030.0],       [87031.0, 87032.0, 87033.0, 87034.0, 87035.0, 87036.0],       [87037.0, 87038.0, 87039.0, 87040.0, 87041.0, 87042.0],       [87043.0, 87044.0, 87045.0, 87046.0, 87047.0, 87048.0],       [87049.0, 87050.0, 87051.0, 87052.0, 87053.0, 87054.0],       [87055.0, 87056.0, 87057.0, 87058.0, 87059.0, 87060.0],       [87061.0, 87062.0, 87063.0, 87064.0, 87065.0, 87066.0]],      [[87067.0, 87068.0, 87069.0, 87070.0, 87071.0, 87072.0],       [87073.0, 87074.0, 87075.0, 87076.0, 87077.0, 87078.0],       [87079.0, 87080.0, 87081.0, 87082.0, 87083.0, 87084.0],       [87085.0, 87086.0, 87087.0, 87088.0, 87089.0, 87090.0],       [87091.0, 87092.0, 87093.0, 87094.0, 87095.0, 87096.0],       [87097.0, 87098.0, 87099.0, 87100.0, 87101.0, 87102.0],       [87103.0, 87104.0, 87105.0, 87106.0, 87107.0, 87108.0]],      [[87109.0, 87110.0, 87111.0, 87112.0, 87113.0, 87114.0],       [87115.0, 87116.0, 87117.0, 87118.0, 87119.0, 87120.0],       [87121.0, 87122.0, 87123.0, 87124.0, 87125.0, 87126.0],       [87127.0, 87128.0, 87129.0, 87130.0, 87131.0, 87132.0],       [87133.0, 87134.0, 87135.0, 87136.0, 87137.0, 87138.0],       [87139.0, 87140.0, 87141.0, 87142.0, 87143.0, 87144.0],       [87145.0, 87146.0, 87147.0, 87148.0, 87149.0, 87150.0]],      [[87151.0, 87152.0, 87153.0, 87154.0, 87155.0, 87156.0],       [87157.0, 87158.0, 87159.0, 87160.0, 87161.0, 87162.0],       [87163.0, 87164.0, 87165.0, 87166.0, 87167.0, 87168.0],       [87169.0, 87170.0, 87171.0, 87172.0, 87173.0, 87174.0],       [87175.0, 87176.0, 87177.0, 87178.0, 87179.0, 87180.0],       [87181.0, 87182.0, 87183.0, 87184.0, 87185.0, 87186.0],       [87187.0, 87188.0, 87189.0, 87190.0, 87191.0, 87192.0]]],     [[[87193.0, 87194.0, 87195.0, 87196.0, 87197.0, 87198.0],       [87199.0, 87200.0, 87201.0, 87202.0, 87203.0, 87204.0],       [87205.0, 87206.0, 87207.0, 87208.0, 87209.0, 87210.0],       [87211.0, 87212.0, 87213.0, 87214.0, 87215.0, 87216.0],       [87217.0, 87218.0, 87219.0, 87220.0, 87221.0, 87222.0],       [87223.0, 87224.0, 87225.0, 87226.0, 87227.0, 87228.0],       [87229.0, 87230.0, 87231.0, 87232.0, 87233.0, 87234.0]],      [[87235.0, 87236.0, 87237.0, 87238.0, 87239.0, 87240.0],       [87241.0, 87242.0, 87243.0, 87244.0, 87245.0, 87246.0],       [87247.0, 87248.0, 87249.0, 87250.0, 87251.0, 87252.0],       [87253.0, 87254.0, 87255.0, 87256.0, 87257.0, 87258.0],       [87259.0, 87260.0, 87261.0, 87262.0, 87263.0, 87264.0],       [87265.0, 87266.0, 87267.0, 87268.0, 87269.0, 87270.0],       [87271.0, 87272.0, 87273.0, 87274.0, 87275.0, 87276.0]],      [[87277.0, 87278.0, 87279.0, 87280.0, 87281.0, 87282.0],       [87283.0, 87284.0, 87285.0, 87286.0, 87287.0, 87288.0],       [87289.0, 87290.0, 87291.0, 87292.0, 87293.0, 87294.0],       [87295.0, 87296.0, 87297.0, 87298.0, 87299.0, 87300.0],       [87301.0, 87302.0, 87303.0, 87304.0, 87305.0, 87306.0],       [87307.0, 87308.0, 87309.0, 87310.0, 87311.0, 87312.0],       [87313.0, 87314.0, 87315.0, 87316.0, 87317.0, 87318.0]],      [[87319.0, 87320.0, 87321.0, 87322.0, 87323.0, 87324.0],       [87325.0, 87326.0, 87327.0, 87328.0, 87329.0, 87330.0],       [87331.0, 87332.0, 87333.0, 87334.0, 87335.0, 87336.0],       [87337.0, 87338.0, 87339.0, 87340.0, 87341.0, 87342.0],       [87343.0, 87344.0, 87345.0, 87346.0, 87347.0, 87348.0],       [87349.0, 87350.0, 87351.0, 87352.0, 87353.0, 87354.0],       [87355.0, 87356.0, 87357.0, 87358.0, 87359.0, 87360.0]],      [[87361.0, 87362.0, 87363.0, 87364.0, 87365.0, 87366.0],       [87367.0, 87368.0, 87369.0, 87370.0, 87371.0, 87372.0],       [87373.0, 87374.0, 87375.0, 87376.0, 87377.0, 87378.0],       [87379.0, 87380.0, 87381.0, 87382.0, 87383.0, 87384.0],       [87385.0, 87386.0, 87387.0, 87388.0, 87389.0, 87390.0],       [87391.0, 87392.0, 87393.0, 87394.0, 87395.0, 87396.0],       [87397.0, 87398.0, 87399.0, 87400.0, 87401.0, 87402.0]],      [[87403.0, 87404.0, 87405.0, 87406.0, 87407.0, 87408.0],       [87409.0, 87410.0, 87411.0, 87412.0, 87413.0, 87414.0],       [87415.0, 87416.0, 87417.0, 87418.0, 87419.0, 87420.0],       [87421.0, 87422.0, 87423.0, 87424.0, 87425.0, 87426.0],       [87427.0, 87428.0, 87429.0, 87430.0, 87431.0, 87432.0],       [87433.0, 87434.0, 87435.0, 87436.0, 87437.0, 87438.0],       [87439.0, 87440.0, 87441.0, 87442.0, 87443.0, 87444.0]]],     [[[87445.0, 87446.0, 87447.0, 87448.0, 87449.0, 87450.0],       [87451.0, 87452.0, 87453.0, 87454.0, 87455.0, 87456.0],       [87457.0, 87458.0, 87459.0, 87460.0, 87461.0, 87462.0],       [87463.0, 87464.0, 87465.0, 87466.0, 87467.0, 87468.0],       [87469.0, 87470.0, 87471.0, 87472.0, 87473.0, 87474.0],       [87475.0, 87476.0, 87477.0, 87478.0, 87479.0, 87480.0],       [87481.0, 87482.0, 87483.0, 87484.0, 87485.0, 87486.0]],      [[87487.0, 87488.0, 87489.0, 87490.0, 87491.0, 87492.0],       [87493.0, 87494.0, 87495.0, 87496.0, 87497.0, 87498.0],       [87499.0, 87500.0, 87501.0, 87502.0, 87503.0, 87504.0],       [87505.0, 87506.0, 87507.0, 87508.0, 87509.0, 87510.0],       [87511.0, 87512.0, 87513.0, 87514.0, 87515.0, 87516.0],       [87517.0, 87518.0, 87519.0, 87520.0, 87521.0, 87522.0],       [87523.0, 87524.0, 87525.0, 87526.0, 87527.0, 87528.0]],      [[87529.0, 87530.0, 87531.0, 87532.0, 87533.0, 87534.0],       [87535.0, 87536.0, 87537.0, 87538.0, 87539.0, 87540.0],       [87541.0, 87542.0, 87543.0, 87544.0, 87545.0, 87546.0],       [87547.0, 87548.0, 87549.0, 87550.0, 87551.0, 87552.0],       [87553.0, 87554.0, 87555.0, 87556.0, 87557.0, 87558.0],       [87559.0, 87560.0, 87561.0, 87562.0, 87563.0, 87564.0],       [87565.0, 87566.0, 87567.0, 87568.0, 87569.0, 87570.0]],      [[87571.0, 87572.0, 87573.0, 87574.0, 87575.0, 87576.0],       [87577.0, 87578.0, 87579.0, 87580.0, 87581.0, 87582.0],       [87583.0, 87584.0, 87585.0, 87586.0, 87587.0, 87588.0],       [87589.0, 87590.0, 87591.0, 87592.0, 87593.0, 87594.0],       [87595.0, 87596.0, 87597.0, 87598.0, 87599.0, 87600.0],       [87601.0, 87602.0, 87603.0, 87604.0, 87605.0, 87606.0],       [87607.0, 87608.0, 87609.0, 87610.0, 87611.0, 87612.0]],      [[87613.0, 87614.0, 87615.0, 87616.0, 87617.0, 87618.0],       [87619.0, 87620.0, 87621.0, 87622.0, 87623.0, 87624.0],       [87625.0, 87626.0, 87627.0, 87628.0, 87629.0, 87630.0],       [87631.0, 87632.0, 87633.0, 87634.0, 87635.0, 87636.0],       [87637.0, 87638.0, 87639.0, 87640.0, 87641.0, 87642.0],       [87643.0, 87644.0, 87645.0, 87646.0, 87647.0, 87648.0],       [87649.0, 87650.0, 87651.0, 87652.0, 87653.0, 87654.0]],      [[87655.0, 87656.0, 87657.0, 87658.0, 87659.0, 87660.0],       [87661.0, 87662.0, 87663.0, 87664.0, 87665.0, 87666.0],       [87667.0, 87668.0, 87669.0, 87670.0, 87671.0, 87672.0],       [87673.0, 87674.0, 87675.0, 87676.0, 87677.0, 87678.0],       [87679.0, 87680.0, 87681.0, 87682.0, 87683.0, 87684.0],       [87685.0, 87686.0, 87687.0, 87688.0, 87689.0, 87690.0],       [87691.0, 87692.0, 87693.0, 87694.0, 87695.0, 87696.0]]]],    [[[[87697.0, 87698.0, 87699.0, 87700.0, 87701.0, 87702.0],       [87703.0, 87704.0, 87705.0, 87706.0, 87707.0, 87708.0],       [87709.0, 87710.0, 87711.0, 87712.0, 87713.0, 87714.0],       [87715.0, 87716.0, 87717.0, 87718.0, 87719.0, 87720.0],       [87721.0, 87722.0, 87723.0, 87724.0, 87725.0, 87726.0],       [87727.0, 87728.0, 87729.0, 87730.0, 87731.0, 87732.0],       [87733.0, 87734.0, 87735.0, 87736.0, 87737.0, 87738.0]],      [[87739.0, 87740.0, 87741.0, 87742.0, 87743.0, 87744.0],       [87745.0, 87746.0, 87747.0, 87748.0, 87749.0, 87750.0],       [87751.0, 87752.0, 87753.0, 87754.0, 87755.0, 87756.0],       [87757.0, 87758.0, 87759.0, 87760.0, 87761.0, 87762.0],       [87763.0, 87764.0, 87765.0, 87766.0, 87767.0, 87768.0],       [87769.0, 87770.0, 87771.0, 87772.0, 87773.0, 87774.0],       [87775.0, 87776.0, 87777.0, 87778.0, 87779.0, 87780.0]],      [[87781.0, 87782.0, 87783.0, 87784.0, 87785.0, 87786.0],       [87787.0, 87788.0, 87789.0, 87790.0, 87791.0, 87792.0],       [87793.0, 87794.0, 87795.0, 87796.0, 87797.0, 87798.0],       [87799.0, 87800.0, 87801.0, 87802.0, 87803.0, 87804.0],       [87805.0, 87806.0, 87807.0, 87808.0, 87809.0, 87810.0],       [87811.0, 87812.0, 87813.0, 87814.0, 87815.0, 87816.0],       [87817.0, 87818.0, 87819.0, 87820.0, 87821.0, 87822.0]],      [[87823.0, 87824.0, 87825.0, 87826.0, 87827.0, 87828.0],       [87829.0, 87830.0, 87831.0, 87832.0, 87833.0, 87834.0],       [87835.0, 87836.0, 87837.0, 87838.0, 87839.0, 87840.0],       [87841.0, 87842.0, 87843.0, 87844.0, 87845.0, 87846.0],       [87847.0, 87848.0, 87849.0, 87850.0, 87851.0, 87852.0],       [87853.0, 87854.0, 87855.0, 87856.0, 87857.0, 87858.0],       [87859.0, 87860.0, 87861.0, 87862.0, 87863.0, 87864.0]],      [[87865.0, 87866.0, 87867.0, 87868.0, 87869.0, 87870.0],       [87871.0, 87872.0, 87873.0, 87874.0, 87875.0, 87876.0],       [87877.0, 87878.0, 87879.0, 87880.0, 87881.0, 87882.0],       [87883.0, 87884.0, 87885.0, 87886.0, 87887.0, 87888.0],       [87889.0, 87890.0, 87891.0, 87892.0, 87893.0, 87894.0],       [87895.0, 87896.0, 87897.0, 87898.0, 87899.0, 87900.0],       [87901.0, 87902.0, 87903.0, 87904.0, 87905.0, 87906.0]],      [[87907.0, 87908.0, 87909.0, 87910.0, 87911.0, 87912.0],       [87913.0, 87914.0, 87915.0, 87916.0, 87917.0, 87918.0],       [87919.0, 87920.0, 87921.0, 87922.0, 87923.0, 87924.0],       [87925.0, 87926.0, 87927.0, 87928.0, 87929.0, 87930.0],       [87931.0, 87932.0, 87933.0, 87934.0, 87935.0, 87936.0],       [87937.0, 87938.0, 87939.0, 87940.0, 87941.0, 87942.0],       [87943.0, 87944.0, 87945.0, 87946.0, 87947.0, 87948.0]]],     [[[87949.0, 87950.0, 87951.0, 87952.0, 87953.0, 87954.0],       [87955.0, 87956.0, 87957.0, 87958.0, 87959.0, 87960.0],       [87961.0, 87962.0, 87963.0, 87964.0, 87965.0, 87966.0],       [87967.0, 87968.0, 87969.0, 87970.0, 87971.0, 87972.0],       [87973.0, 87974.0, 87975.0, 87976.0, 87977.0, 87978.0],       [87979.0, 87980.0, 87981.0, 87982.0, 87983.0, 87984.0],       [87985.0, 87986.0, 87987.0, 87988.0, 87989.0, 87990.0]],      [[87991.0, 87992.0, 87993.0, 87994.0, 87995.0, 87996.0],       [87997.0, 87998.0, 87999.0, 88000.0, 88001.0, 88002.0],       [88003.0, 88004.0, 88005.0, 88006.0, 88007.0, 88008.0],       [88009.0, 88010.0, 88011.0, 88012.0, 88013.0, 88014.0],       [88015.0, 88016.0, 88017.0, 88018.0, 88019.0, 88020.0],       [88021.0, 88022.0, 88023.0, 88024.0, 88025.0, 88026.0],       [88027.0, 88028.0, 88029.0, 88030.0, 88031.0, 88032.0]],      [[88033.0, 88034.0, 88035.0, 88036.0, 88037.0, 88038.0],       [88039.0, 88040.0, 88041.0, 88042.0, 88043.0, 88044.0],       [88045.0, 88046.0, 88047.0, 88048.0, 88049.0, 88050.0],       [88051.0, 88052.0, 88053.0, 88054.0, 88055.0, 88056.0],       [88057.0, 88058.0, 88059.0, 88060.0, 88061.0, 88062.0],       [88063.0, 88064.0, 88065.0, 88066.0, 88067.0, 88068.0],       [88069.0, 88070.0, 88071.0, 88072.0, 88073.0, 88074.0]],      [[88075.0, 88076.0, 88077.0, 88078.0, 88079.0, 88080.0],       [88081.0, 88082.0, 88083.0, 88084.0, 88085.0, 88086.0],       [88087.0, 88088.0, 88089.0, 88090.0, 88091.0, 88092.0],       [88093.0, 88094.0, 88095.0, 88096.0, 88097.0, 88098.0],       [88099.0, 88100.0, 88101.0, 88102.0, 88103.0, 88104.0],       [88105.0, 88106.0, 88107.0, 88108.0, 88109.0, 88110.0],       [88111.0, 88112.0, 88113.0, 88114.0, 88115.0, 88116.0]],      [[88117.0, 88118.0, 88119.0, 88120.0, 88121.0, 88122.0],       [88123.0, 88124.0, 88125.0, 88126.0, 88127.0, 88128.0],       [88129.0, 88130.0, 88131.0, 88132.0, 88133.0, 88134.0],       [88135.0, 88136.0, 88137.0, 88138.0, 88139.0, 88140.0],       [88141.0, 88142.0, 88143.0, 88144.0, 88145.0, 88146.0],       [88147.0, 88148.0, 88149.0, 88150.0, 88151.0, 88152.0],       [88153.0, 88154.0, 88155.0, 88156.0, 88157.0, 88158.0]],      [[88159.0, 88160.0, 88161.0, 88162.0, 88163.0, 88164.0],       [88165.0, 88166.0, 88167.0, 88168.0, 88169.0, 88170.0],       [88171.0, 88172.0, 88173.0, 88174.0, 88175.0, 88176.0],       [88177.0, 88178.0, 88179.0, 88180.0, 88181.0, 88182.0],       [88183.0, 88184.0, 88185.0, 88186.0, 88187.0, 88188.0],       [88189.0, 88190.0, 88191.0, 88192.0, 88193.0, 88194.0],       [88195.0, 88196.0, 88197.0, 88198.0, 88199.0, 88200.0]]],     [[[88201.0, 88202.0, 88203.0, 88204.0, 88205.0, 88206.0],       [88207.0, 88208.0, 88209.0, 88210.0, 88211.0, 88212.0],       [88213.0, 88214.0, 88215.0, 88216.0, 88217.0, 88218.0],       [88219.0, 88220.0, 88221.0, 88222.0, 88223.0, 88224.0],       [88225.0, 88226.0, 88227.0, 88228.0, 88229.0, 88230.0],       [88231.0, 88232.0, 88233.0, 88234.0, 88235.0, 88236.0],       [88237.0, 88238.0, 88239.0, 88240.0, 88241.0, 88242.0]],      [[88243.0, 88244.0, 88245.0, 88246.0, 88247.0, 88248.0],       [88249.0, 88250.0, 88251.0, 88252.0, 88253.0, 88254.0],       [88255.0, 88256.0, 88257.0, 88258.0, 88259.0, 88260.0],       [88261.0, 88262.0, 88263.0, 88264.0, 88265.0, 88266.0],       [88267.0, 88268.0, 88269.0, 88270.0, 88271.0, 88272.0],       [88273.0, 88274.0, 88275.0, 88276.0, 88277.0, 88278.0],       [88279.0, 88280.0, 88281.0, 88282.0, 88283.0, 88284.0]],      [[88285.0, 88286.0, 88287.0, 88288.0, 88289.0, 88290.0],       [88291.0, 88292.0, 88293.0, 88294.0, 88295.0, 88296.0],       [88297.0, 88298.0, 88299.0, 88300.0, 88301.0, 88302.0],       [88303.0, 88304.0, 88305.0, 88306.0, 88307.0, 88308.0],       [88309.0, 88310.0, 88311.0, 88312.0, 88313.0, 88314.0],       [88315.0, 88316.0, 88317.0, 88318.0, 88319.0, 88320.0],       [88321.0, 88322.0, 88323.0, 88324.0, 88325.0, 88326.0]],      [[88327.0, 88328.0, 88329.0, 88330.0, 88331.0, 88332.0],       [88333.0, 88334.0, 88335.0, 88336.0, 88337.0, 88338.0],       [88339.0, 88340.0, 88341.0, 88342.0, 88343.0, 88344.0],       [88345.0, 88346.0, 88347.0, 88348.0, 88349.0, 88350.0],       [88351.0, 88352.0, 88353.0, 88354.0, 88355.0, 88356.0],       [88357.0, 88358.0, 88359.0, 88360.0, 88361.0, 88362.0],       [88363.0, 88364.0, 88365.0, 88366.0, 88367.0, 88368.0]],      [[88369.0, 88370.0, 88371.0, 88372.0, 88373.0, 88374.0],       [88375.0, 88376.0, 88377.0, 88378.0, 88379.0, 88380.0],       [88381.0, 88382.0, 88383.0, 88384.0, 88385.0, 88386.0],       [88387.0, 88388.0, 88389.0, 88390.0, 88391.0, 88392.0],       [88393.0, 88394.0, 88395.0, 88396.0, 88397.0, 88398.0],       [88399.0, 88400.0, 88401.0, 88402.0, 88403.0, 88404.0],       [88405.0, 88406.0, 88407.0, 88408.0, 88409.0, 88410.0]],      [[88411.0, 88412.0, 88413.0, 88414.0, 88415.0, 88416.0],       [88417.0, 88418.0, 88419.0, 88420.0, 88421.0, 88422.0],       [88423.0, 88424.0, 88425.0, 88426.0, 88427.0, 88428.0],       [88429.0, 88430.0, 88431.0, 88432.0, 88433.0, 88434.0],       [88435.0, 88436.0, 88437.0, 88438.0, 88439.0, 88440.0],       [88441.0, 88442.0, 88443.0, 88444.0, 88445.0, 88446.0],       [88447.0, 88448.0, 88449.0, 88450.0, 88451.0, 88452.0]]],     [[[88453.0, 88454.0, 88455.0, 88456.0, 88457.0, 88458.0],       [88459.0, 88460.0, 88461.0, 88462.0, 88463.0, 88464.0],       [88465.0, 88466.0, 88467.0, 88468.0, 88469.0, 88470.0],       [88471.0, 88472.0, 88473.0, 88474.0, 88475.0, 88476.0],       [88477.0, 88478.0, 88479.0, 88480.0, 88481.0, 88482.0],       [88483.0, 88484.0, 88485.0, 88486.0, 88487.0, 88488.0],       [88489.0, 88490.0, 88491.0, 88492.0, 88493.0, 88494.0]],      [[88495.0, 88496.0, 88497.0, 88498.0, 88499.0, 88500.0],       [88501.0, 88502.0, 88503.0, 88504.0, 88505.0, 88506.0],       [88507.0, 88508.0, 88509.0, 88510.0, 88511.0, 88512.0],       [88513.0, 88514.0, 88515.0, 88516.0, 88517.0, 88518.0],       [88519.0, 88520.0, 88521.0, 88522.0, 88523.0, 88524.0],       [88525.0, 88526.0, 88527.0, 88528.0, 88529.0, 88530.0],       [88531.0, 88532.0, 88533.0, 88534.0, 88535.0, 88536.0]],      [[88537.0, 88538.0, 88539.0, 88540.0, 88541.0, 88542.0],       [88543.0, 88544.0, 88545.0, 88546.0, 88547.0, 88548.0],       [88549.0, 88550.0, 88551.0, 88552.0, 88553.0, 88554.0],       [88555.0, 88556.0, 88557.0, 88558.0, 88559.0, 88560.0],       [88561.0, 88562.0, 88563.0, 88564.0, 88565.0, 88566.0],       [88567.0, 88568.0, 88569.0, 88570.0, 88571.0, 88572.0],       [88573.0, 88574.0, 88575.0, 88576.0, 88577.0, 88578.0]],      [[88579.0, 88580.0, 88581.0, 88582.0, 88583.0, 88584.0],       [88585.0, 88586.0, 88587.0, 88588.0, 88589.0, 88590.0],       [88591.0, 88592.0, 88593.0, 88594.0, 88595.0, 88596.0],       [88597.0, 88598.0, 88599.0, 88600.0, 88601.0, 88602.0],       [88603.0, 88604.0, 88605.0, 88606.0, 88607.0, 88608.0],       [88609.0, 88610.0, 88611.0, 88612.0, 88613.0, 88614.0],       [88615.0, 88616.0, 88617.0, 88618.0, 88619.0, 88620.0]],      [[88621.0, 88622.0, 88623.0, 88624.0, 88625.0, 88626.0],       [88627.0, 88628.0, 88629.0, 88630.0, 88631.0, 88632.0],       [88633.0, 88634.0, 88635.0, 88636.0, 88637.0, 88638.0],       [88639.0, 88640.0, 88641.0, 88642.0, 88643.0, 88644.0],       [88645.0, 88646.0, 88647.0, 88648.0, 88649.0, 88650.0],       [88651.0, 88652.0, 88653.0, 88654.0, 88655.0, 88656.0],       [88657.0, 88658.0, 88659.0, 88660.0, 88661.0, 88662.0]],      [[88663.0, 88664.0, 88665.0, 88666.0, 88667.0, 88668.0],       [88669.0, 88670.0, 88671.0, 88672.0, 88673.0, 88674.0],       [88675.0, 88676.0, 88677.0, 88678.0, 88679.0, 88680.0],       [88681.0, 88682.0, 88683.0, 88684.0, 88685.0, 88686.0],       [88687.0, 88688.0, 88689.0, 88690.0, 88691.0, 88692.0],       [88693.0, 88694.0, 88695.0, 88696.0, 88697.0, 88698.0],       [88699.0, 88700.0, 88701.0, 88702.0, 88703.0, 88704.0]]]],    [[[[88705.0, 88706.0, 88707.0, 88708.0, 88709.0, 88710.0],       [88711.0, 88712.0, 88713.0, 88714.0, 88715.0, 88716.0],       [88717.0, 88718.0, 88719.0, 88720.0, 88721.0, 88722.0],       [88723.0, 88724.0, 88725.0, 88726.0, 88727.0, 88728.0],       [88729.0, 88730.0, 88731.0, 88732.0, 88733.0, 88734.0],       [88735.0, 88736.0, 88737.0, 88738.0, 88739.0, 88740.0],       [88741.0, 88742.0, 88743.0, 88744.0, 88745.0, 88746.0]],      [[88747.0, 88748.0, 88749.0, 88750.0, 88751.0, 88752.0],       [88753.0, 88754.0, 88755.0, 88756.0, 88757.0, 88758.0],       [88759.0, 88760.0, 88761.0, 88762.0, 88763.0, 88764.0],       [88765.0, 88766.0, 88767.0, 88768.0, 88769.0, 88770.0],       [88771.0, 88772.0, 88773.0, 88774.0, 88775.0, 88776.0],       [88777.0, 88778.0, 88779.0, 88780.0, 88781.0, 88782.0],       [88783.0, 88784.0, 88785.0, 88786.0, 88787.0, 88788.0]],      [[88789.0, 88790.0, 88791.0, 88792.0, 88793.0, 88794.0],       [88795.0, 88796.0, 88797.0, 88798.0, 88799.0, 88800.0],       [88801.0, 88802.0, 88803.0, 88804.0, 88805.0, 88806.0],       [88807.0, 88808.0, 88809.0, 88810.0, 88811.0, 88812.0],       [88813.0, 88814.0, 88815.0, 88816.0, 88817.0, 88818.0],       [88819.0, 88820.0, 88821.0, 88822.0, 88823.0, 88824.0],       [88825.0, 88826.0, 88827.0, 88828.0, 88829.0, 88830.0]],      [[88831.0, 88832.0, 88833.0, 88834.0, 88835.0, 88836.0],       [88837.0, 88838.0, 88839.0, 88840.0, 88841.0, 88842.0],       [88843.0, 88844.0, 88845.0, 88846.0, 88847.0, 88848.0],       [88849.0, 88850.0, 88851.0, 88852.0, 88853.0, 88854.0],       [88855.0, 88856.0, 88857.0, 88858.0, 88859.0, 88860.0],       [88861.0, 88862.0, 88863.0, 88864.0, 88865.0, 88866.0],       [88867.0, 88868.0, 88869.0, 88870.0, 88871.0, 88872.0]],      [[88873.0, 88874.0, 88875.0, 88876.0, 88877.0, 88878.0],       [88879.0, 88880.0, 88881.0, 88882.0, 88883.0, 88884.0],       [88885.0, 88886.0, 88887.0, 88888.0, 88889.0, 88890.0],       [88891.0, 88892.0, 88893.0, 88894.0, 88895.0, 88896.0],       [88897.0, 88898.0, 88899.0, 88900.0, 88901.0, 88902.0],       [88903.0, 88904.0, 88905.0, 88906.0, 88907.0, 88908.0],       [88909.0, 88910.0, 88911.0, 88912.0, 88913.0, 88914.0]],      [[88915.0, 88916.0, 88917.0, 88918.0, 88919.0, 88920.0],       [88921.0, 88922.0, 88923.0, 88924.0, 88925.0, 88926.0],       [88927.0, 88928.0, 88929.0, 88930.0, 88931.0, 88932.0],       [88933.0, 88934.0, 88935.0, 88936.0, 88937.0, 88938.0],       [88939.0, 88940.0, 88941.0, 88942.0, 88943.0, 88944.0],       [88945.0, 88946.0, 88947.0, 88948.0, 88949.0, 88950.0],       [88951.0, 88952.0, 88953.0, 88954.0, 88955.0, 88956.0]]],     [[[88957.0, 88958.0, 88959.0, 88960.0, 88961.0, 88962.0],       [88963.0, 88964.0, 88965.0, 88966.0, 88967.0, 88968.0],       [88969.0, 88970.0, 88971.0, 88972.0, 88973.0, 88974.0],       [88975.0, 88976.0, 88977.0, 88978.0, 88979.0, 88980.0],       [88981.0, 88982.0, 88983.0, 88984.0, 88985.0, 88986.0],       [88987.0, 88988.0, 88989.0, 88990.0, 88991.0, 88992.0],       [88993.0, 88994.0, 88995.0, 88996.0, 88997.0, 88998.0]],      [[88999.0, 89000.0, 89001.0, 89002.0, 89003.0, 89004.0],       [89005.0, 89006.0, 89007.0, 89008.0, 89009.0, 89010.0],       [89011.0, 89012.0, 89013.0, 89014.0, 89015.0, 89016.0],       [89017.0, 89018.0, 89019.0, 89020.0, 89021.0, 89022.0],       [89023.0, 89024.0, 89025.0, 89026.0, 89027.0, 89028.0],       [89029.0, 89030.0, 89031.0, 89032.0, 89033.0, 89034.0],       [89035.0, 89036.0, 89037.0, 89038.0, 89039.0, 89040.0]],      [[89041.0, 89042.0, 89043.0, 89044.0, 89045.0, 89046.0],       [89047.0, 89048.0, 89049.0, 89050.0, 89051.0, 89052.0],       [89053.0, 89054.0, 89055.0, 89056.0, 89057.0, 89058.0],       [89059.0, 89060.0, 89061.0, 89062.0, 89063.0, 89064.0],       [89065.0, 89066.0, 89067.0, 89068.0, 89069.0, 89070.0],       [89071.0, 89072.0, 89073.0, 89074.0, 89075.0, 89076.0],       [89077.0, 89078.0, 89079.0, 89080.0, 89081.0, 89082.0]],      [[89083.0, 89084.0, 89085.0, 89086.0, 89087.0, 89088.0],       [89089.0, 89090.0, 89091.0, 89092.0, 89093.0, 89094.0],       [89095.0, 89096.0, 89097.0, 89098.0, 89099.0, 89100.0],       [89101.0, 89102.0, 89103.0, 89104.0, 89105.0, 89106.0],       [89107.0, 89108.0, 89109.0, 89110.0, 89111.0, 89112.0],       [89113.0, 89114.0, 89115.0, 89116.0, 89117.0, 89118.0],       [89119.0, 89120.0, 89121.0, 89122.0, 89123.0, 89124.0]],      [[89125.0, 89126.0, 89127.0, 89128.0, 89129.0, 89130.0],       [89131.0, 89132.0, 89133.0, 89134.0, 89135.0, 89136.0],       [89137.0, 89138.0, 89139.0, 89140.0, 89141.0, 89142.0],       [89143.0, 89144.0, 89145.0, 89146.0, 89147.0, 89148.0],       [89149.0, 89150.0, 89151.0, 89152.0, 89153.0, 89154.0],       [89155.0, 89156.0, 89157.0, 89158.0, 89159.0, 89160.0],       [89161.0, 89162.0, 89163.0, 89164.0, 89165.0, 89166.0]],      [[89167.0, 89168.0, 89169.0, 89170.0, 89171.0, 89172.0],       [89173.0, 89174.0, 89175.0, 89176.0, 89177.0, 89178.0],       [89179.0, 89180.0, 89181.0, 89182.0, 89183.0, 89184.0],       [89185.0, 89186.0, 89187.0, 89188.0, 89189.0, 89190.0],       [89191.0, 89192.0, 89193.0, 89194.0, 89195.0, 89196.0],       [89197.0, 89198.0, 89199.0, 89200.0, 89201.0, 89202.0],       [89203.0, 89204.0, 89205.0, 89206.0, 89207.0, 89208.0]]],     [[[89209.0, 89210.0, 89211.0, 89212.0, 89213.0, 89214.0],       [89215.0, 89216.0, 89217.0, 89218.0, 89219.0, 89220.0],       [89221.0, 89222.0, 89223.0, 89224.0, 89225.0, 89226.0],       [89227.0, 89228.0, 89229.0, 89230.0, 89231.0, 89232.0],       [89233.0, 89234.0, 89235.0, 89236.0, 89237.0, 89238.0],       [89239.0, 89240.0, 89241.0, 89242.0, 89243.0, 89244.0],       [89245.0, 89246.0, 89247.0, 89248.0, 89249.0, 89250.0]],      [[89251.0, 89252.0, 89253.0, 89254.0, 89255.0, 89256.0],       [89257.0, 89258.0, 89259.0, 89260.0, 89261.0, 89262.0],       [89263.0, 89264.0, 89265.0, 89266.0, 89267.0, 89268.0],       [89269.0, 89270.0, 89271.0, 89272.0, 89273.0, 89274.0],       [89275.0, 89276.0, 89277.0, 89278.0, 89279.0, 89280.0],       [89281.0, 89282.0, 89283.0, 89284.0, 89285.0, 89286.0],       [89287.0, 89288.0, 89289.0, 89290.0, 89291.0, 89292.0]],      [[89293.0, 89294.0, 89295.0, 89296.0, 89297.0, 89298.0],       [89299.0, 89300.0, 89301.0, 89302.0, 89303.0, 89304.0],       [89305.0, 89306.0, 89307.0, 89308.0, 89309.0, 89310.0],       [89311.0, 89312.0, 89313.0, 89314.0, 89315.0, 89316.0],       [89317.0, 89318.0, 89319.0, 89320.0, 89321.0, 89322.0],       [89323.0, 89324.0, 89325.0, 89326.0, 89327.0, 89328.0],       [89329.0, 89330.0, 89331.0, 89332.0, 89333.0, 89334.0]],      [[89335.0, 89336.0, 89337.0, 89338.0, 89339.0, 89340.0],       [89341.0, 89342.0, 89343.0, 89344.0, 89345.0, 89346.0],       [89347.0, 89348.0, 89349.0, 89350.0, 89351.0, 89352.0],       [89353.0, 89354.0, 89355.0, 89356.0, 89357.0, 89358.0],       [89359.0, 89360.0, 89361.0, 89362.0, 89363.0, 89364.0],       [89365.0, 89366.0, 89367.0, 89368.0, 89369.0, 89370.0],       [89371.0, 89372.0, 89373.0, 89374.0, 89375.0, 89376.0]],      [[89377.0, 89378.0, 89379.0, 89380.0, 89381.0, 89382.0],       [89383.0, 89384.0, 89385.0, 89386.0, 89387.0, 89388.0],       [89389.0, 89390.0, 89391.0, 89392.0, 89393.0, 89394.0],       [89395.0, 89396.0, 89397.0, 89398.0, 89399.0, 89400.0],       [89401.0, 89402.0, 89403.0, 89404.0, 89405.0, 89406.0],       [89407.0, 89408.0, 89409.0, 89410.0, 89411.0, 89412.0],       [89413.0, 89414.0, 89415.0, 89416.0, 89417.0, 89418.0]],      [[89419.0, 89420.0, 89421.0, 89422.0, 89423.0, 89424.0],       [89425.0, 89426.0, 89427.0, 89428.0, 89429.0, 89430.0],       [89431.0, 89432.0, 89433.0, 89434.0, 89435.0, 89436.0],       [89437.0, 89438.0, 89439.0, 89440.0, 89441.0, 89442.0],       [89443.0, 89444.0, 89445.0, 89446.0, 89447.0, 89448.0],       [89449.0, 89450.0, 89451.0, 89452.0, 89453.0, 89454.0],       [89455.0, 89456.0, 89457.0, 89458.0, 89459.0, 89460.0]]],     [[[89461.0, 89462.0, 89463.0, 89464.0, 89465.0, 89466.0],       [89467.0, 89468.0, 89469.0, 89470.0, 89471.0, 89472.0],       [89473.0, 89474.0, 89475.0, 89476.0, 89477.0, 89478.0],       [89479.0, 89480.0, 89481.0, 89482.0, 89483.0, 89484.0],       [89485.0, 89486.0, 89487.0, 89488.0, 89489.0, 89490.0],       [89491.0, 89492.0, 89493.0, 89494.0, 89495.0, 89496.0],       [89497.0, 89498.0, 89499.0, 89500.0, 89501.0, 89502.0]],      [[89503.0, 89504.0, 89505.0, 89506.0, 89507.0, 89508.0],       [89509.0, 89510.0, 89511.0, 89512.0, 89513.0, 89514.0],       [89515.0, 89516.0, 89517.0, 89518.0, 89519.0, 89520.0],       [89521.0, 89522.0, 89523.0, 89524.0, 89525.0, 89526.0],       [89527.0, 89528.0, 89529.0, 89530.0, 89531.0, 89532.0],       [89533.0, 89534.0, 89535.0, 89536.0, 89537.0, 89538.0],       [89539.0, 89540.0, 89541.0, 89542.0, 89543.0, 89544.0]],      [[89545.0, 89546.0, 89547.0, 89548.0, 89549.0, 89550.0],       [89551.0, 89552.0, 89553.0, 89554.0, 89555.0, 89556.0],       [89557.0, 89558.0, 89559.0, 89560.0, 89561.0, 89562.0],       [89563.0, 89564.0, 89565.0, 89566.0, 89567.0, 89568.0],       [89569.0, 89570.0, 89571.0, 89572.0, 89573.0, 89574.0],       [89575.0, 89576.0, 89577.0, 89578.0, 89579.0, 89580.0],       [89581.0, 89582.0, 89583.0, 89584.0, 89585.0, 89586.0]],      [[89587.0, 89588.0, 89589.0, 89590.0, 89591.0, 89592.0],       [89593.0, 89594.0, 89595.0, 89596.0, 89597.0, 89598.0],       [89599.0, 89600.0, 89601.0, 89602.0, 89603.0, 89604.0],       [89605.0, 89606.0, 89607.0, 89608.0, 89609.0, 89610.0],       [89611.0, 89612.0, 89613.0, 89614.0, 89615.0, 89616.0],       [89617.0, 89618.0, 89619.0, 89620.0, 89621.0, 89622.0],       [89623.0, 89624.0, 89625.0, 89626.0, 89627.0, 89628.0]],      [[89629.0, 89630.0, 89631.0, 89632.0, 89633.0, 89634.0],       [89635.0, 89636.0, 89637.0, 89638.0, 89639.0, 89640.0],       [89641.0, 89642.0, 89643.0, 89644.0, 89645.0, 89646.0],       [89647.0, 89648.0, 89649.0, 89650.0, 89651.0, 89652.0],       [89653.0, 89654.0, 89655.0, 89656.0, 89657.0, 89658.0],       [89659.0, 89660.0, 89661.0, 89662.0, 89663.0, 89664.0],       [89665.0, 89666.0, 89667.0, 89668.0, 89669.0, 89670.0]],      [[89671.0, 89672.0, 89673.0, 89674.0, 89675.0, 89676.0],       [89677.0, 89678.0, 89679.0, 89680.0, 89681.0, 89682.0],       [89683.0, 89684.0, 89685.0, 89686.0, 89687.0, 89688.0],       [89689.0, 89690.0, 89691.0, 89692.0, 89693.0, 89694.0],       [89695.0, 89696.0, 89697.0, 89698.0, 89699.0, 89700.0],       [89701.0, 89702.0, 89703.0, 89704.0, 89705.0, 89706.0],       [89707.0, 89708.0, 89709.0, 89710.0, 89711.0, 89712.0]]]],    [[[[89713.0, 89714.0, 89715.0, 89716.0, 89717.0, 89718.0],       [89719.0, 89720.0, 89721.0, 89722.0, 89723.0, 89724.0],       [89725.0, 89726.0, 89727.0, 89728.0, 89729.0, 89730.0],       [89731.0, 89732.0, 89733.0, 89734.0, 89735.0, 89736.0],       [89737.0, 89738.0, 89739.0, 89740.0, 89741.0, 89742.0],       [89743.0, 89744.0, 89745.0, 89746.0, 89747.0, 89748.0],       [89749.0, 89750.0, 89751.0, 89752.0, 89753.0, 89754.0]],      [[89755.0, 89756.0, 89757.0, 89758.0, 89759.0, 89760.0],       [89761.0, 89762.0, 89763.0, 89764.0, 89765.0, 89766.0],       [89767.0, 89768.0, 89769.0, 89770.0, 89771.0, 89772.0],       [89773.0, 89774.0, 89775.0, 89776.0, 89777.0, 89778.0],       [89779.0, 89780.0, 89781.0, 89782.0, 89783.0, 89784.0],       [89785.0, 89786.0, 89787.0, 89788.0, 89789.0, 89790.0],       [89791.0, 89792.0, 89793.0, 89794.0, 89795.0, 89796.0]],      [[89797.0, 89798.0, 89799.0, 89800.0, 89801.0, 89802.0],       [89803.0, 89804.0, 89805.0, 89806.0, 89807.0, 89808.0],       [89809.0, 89810.0, 89811.0, 89812.0, 89813.0, 89814.0],       [89815.0, 89816.0, 89817.0, 89818.0, 89819.0, 89820.0],       [89821.0, 89822.0, 89823.0, 89824.0, 89825.0, 89826.0],       [89827.0, 89828.0, 89829.0, 89830.0, 89831.0, 89832.0],       [89833.0, 89834.0, 89835.0, 89836.0, 89837.0, 89838.0]],      [[89839.0, 89840.0, 89841.0, 89842.0, 89843.0, 89844.0],       [89845.0, 89846.0, 89847.0, 89848.0, 89849.0, 89850.0],       [89851.0, 89852.0, 89853.0, 89854.0, 89855.0, 89856.0],       [89857.0, 89858.0, 89859.0, 89860.0, 89861.0, 89862.0],       [89863.0, 89864.0, 89865.0, 89866.0, 89867.0, 89868.0],       [89869.0, 89870.0, 89871.0, 89872.0, 89873.0, 89874.0],       [89875.0, 89876.0, 89877.0, 89878.0, 89879.0, 89880.0]],      [[89881.0, 89882.0, 89883.0, 89884.0, 89885.0, 89886.0],       [89887.0, 89888.0, 89889.0, 89890.0, 89891.0, 89892.0],       [89893.0, 89894.0, 89895.0, 89896.0, 89897.0, 89898.0],       [89899.0, 89900.0, 89901.0, 89902.0, 89903.0, 89904.0],       [89905.0, 89906.0, 89907.0, 89908.0, 89909.0, 89910.0],       [89911.0, 89912.0, 89913.0, 89914.0, 89915.0, 89916.0],       [89917.0, 89918.0, 89919.0, 89920.0, 89921.0, 89922.0]],      [[89923.0, 89924.0, 89925.0, 89926.0, 89927.0, 89928.0],       [89929.0, 89930.0, 89931.0, 89932.0, 89933.0, 89934.0],       [89935.0, 89936.0, 89937.0, 89938.0, 89939.0, 89940.0],       [89941.0, 89942.0, 89943.0, 89944.0, 89945.0, 89946.0],       [89947.0, 89948.0, 89949.0, 89950.0, 89951.0, 89952.0],       [89953.0, 89954.0, 89955.0, 89956.0, 89957.0, 89958.0],       [89959.0, 89960.0, 89961.0, 89962.0, 89963.0, 89964.0]]],     [[[89965.0, 89966.0, 89967.0, 89968.0, 89969.0, 89970.0],       [89971.0, 89972.0, 89973.0, 89974.0, 89975.0, 89976.0],       [89977.0, 89978.0, 89979.0, 89980.0, 89981.0, 89982.0],       [89983.0, 89984.0, 89985.0, 89986.0, 89987.0, 89988.0],       [89989.0, 89990.0, 89991.0, 89992.0, 89993.0, 89994.0],       [89995.0, 89996.0, 89997.0, 89998.0, 89999.0, 90000.0],       [90001.0, 90002.0, 90003.0, 90004.0, 90005.0, 90006.0]],      [[90007.0, 90008.0, 90009.0, 90010.0, 90011.0, 90012.0],       [90013.0, 90014.0, 90015.0, 90016.0, 90017.0, 90018.0],       [90019.0, 90020.0, 90021.0, 90022.0, 90023.0, 90024.0],       [90025.0, 90026.0, 90027.0, 90028.0, 90029.0, 90030.0],       [90031.0, 90032.0, 90033.0, 90034.0, 90035.0, 90036.0],       [90037.0, 90038.0, 90039.0, 90040.0, 90041.0, 90042.0],       [90043.0, 90044.0, 90045.0, 90046.0, 90047.0, 90048.0]],      [[90049.0, 90050.0, 90051.0, 90052.0, 90053.0, 90054.0],       [90055.0, 90056.0, 90057.0, 90058.0, 90059.0, 90060.0],       [90061.0, 90062.0, 90063.0, 90064.0, 90065.0, 90066.0],       [90067.0, 90068.0, 90069.0, 90070.0, 90071.0, 90072.0],       [90073.0, 90074.0, 90075.0, 90076.0, 90077.0, 90078.0],       [90079.0, 90080.0, 90081.0, 90082.0, 90083.0, 90084.0],       [90085.0, 90086.0, 90087.0, 90088.0, 90089.0, 90090.0]],      [[90091.0, 90092.0, 90093.0, 90094.0, 90095.0, 90096.0],       [90097.0, 90098.0, 90099.0, 90100.0, 90101.0, 90102.0],       [90103.0, 90104.0, 90105.0, 90106.0, 90107.0, 90108.0],       [90109.0, 90110.0, 90111.0, 90112.0, 90113.0, 90114.0],       [90115.0, 90116.0, 90117.0, 90118.0, 90119.0, 90120.0],       [90121.0, 90122.0, 90123.0, 90124.0, 90125.0, 90126.0],       [90127.0, 90128.0, 90129.0, 90130.0, 90131.0, 90132.0]],      [[90133.0, 90134.0, 90135.0, 90136.0, 90137.0, 90138.0],       [90139.0, 90140.0, 90141.0, 90142.0, 90143.0, 90144.0],       [90145.0, 90146.0, 90147.0, 90148.0, 90149.0, 90150.0],       [90151.0, 90152.0, 90153.0, 90154.0, 90155.0, 90156.0],       [90157.0, 90158.0, 90159.0, 90160.0, 90161.0, 90162.0],       [90163.0, 90164.0, 90165.0, 90166.0, 90167.0, 90168.0],       [90169.0, 90170.0, 90171.0, 90172.0, 90173.0, 90174.0]],      [[90175.0, 90176.0, 90177.0, 90178.0, 90179.0, 90180.0],       [90181.0, 90182.0, 90183.0, 90184.0, 90185.0, 90186.0],       [90187.0, 90188.0, 90189.0, 90190.0, 90191.0, 90192.0],       [90193.0, 90194.0, 90195.0, 90196.0, 90197.0, 90198.0],       [90199.0, 90200.0, 90201.0, 90202.0, 90203.0, 90204.0],       [90205.0, 90206.0, 90207.0, 90208.0, 90209.0, 90210.0],       [90211.0, 90212.0, 90213.0, 90214.0, 90215.0, 90216.0]]],     [[[90217.0, 90218.0, 90219.0, 90220.0, 90221.0, 90222.0],       [90223.0, 90224.0, 90225.0, 90226.0, 90227.0, 90228.0],       [90229.0, 90230.0, 90231.0, 90232.0, 90233.0, 90234.0],       [90235.0, 90236.0, 90237.0, 90238.0, 90239.0, 90240.0],       [90241.0, 90242.0, 90243.0, 90244.0, 90245.0, 90246.0],       [90247.0, 90248.0, 90249.0, 90250.0, 90251.0, 90252.0],       [90253.0, 90254.0, 90255.0, 90256.0, 90257.0, 90258.0]],      [[90259.0, 90260.0, 90261.0, 90262.0, 90263.0, 90264.0],       [90265.0, 90266.0, 90267.0, 90268.0, 90269.0, 90270.0],       [90271.0, 90272.0, 90273.0, 90274.0, 90275.0, 90276.0],       [90277.0, 90278.0, 90279.0, 90280.0, 90281.0, 90282.0],       [90283.0, 90284.0, 90285.0, 90286.0, 90287.0, 90288.0],       [90289.0, 90290.0, 90291.0, 90292.0, 90293.0, 90294.0],       [90295.0, 90296.0, 90297.0, 90298.0, 90299.0, 90300.0]],      [[90301.0, 90302.0, 90303.0, 90304.0, 90305.0, 90306.0],       [90307.0, 90308.0, 90309.0, 90310.0, 90311.0, 90312.0],       [90313.0, 90314.0, 90315.0, 90316.0, 90317.0, 90318.0],       [90319.0, 90320.0, 90321.0, 90322.0, 90323.0, 90324.0],       [90325.0, 90326.0, 90327.0, 90328.0, 90329.0, 90330.0],       [90331.0, 90332.0, 90333.0, 90334.0, 90335.0, 90336.0],       [90337.0, 90338.0, 90339.0, 90340.0, 90341.0, 90342.0]],      [[90343.0, 90344.0, 90345.0, 90346.0, 90347.0, 90348.0],       [90349.0, 90350.0, 90351.0, 90352.0, 90353.0, 90354.0],       [90355.0, 90356.0, 90357.0, 90358.0, 90359.0, 90360.0],       [90361.0, 90362.0, 90363.0, 90364.0, 90365.0, 90366.0],       [90367.0, 90368.0, 90369.0, 90370.0, 90371.0, 90372.0],       [90373.0, 90374.0, 90375.0, 90376.0, 90377.0, 90378.0],       [90379.0, 90380.0, 90381.0, 90382.0, 90383.0, 90384.0]],      [[90385.0, 90386.0, 90387.0, 90388.0, 90389.0, 90390.0],       [90391.0, 90392.0, 90393.0, 90394.0, 90395.0, 90396.0],       [90397.0, 90398.0, 90399.0, 90400.0, 90401.0, 90402.0],       [90403.0, 90404.0, 90405.0, 90406.0, 90407.0, 90408.0],       [90409.0, 90410.0, 90411.0, 90412.0, 90413.0, 90414.0],       [90415.0, 90416.0, 90417.0, 90418.0, 90419.0, 90420.0],       [90421.0, 90422.0, 90423.0, 90424.0, 90425.0, 90426.0]],      [[90427.0, 90428.0, 90429.0, 90430.0, 90431.0, 90432.0],       [90433.0, 90434.0, 90435.0, 90436.0, 90437.0, 90438.0],       [90439.0, 90440.0, 90441.0, 90442.0, 90443.0, 90444.0],       [90445.0, 90446.0, 90447.0, 90448.0, 90449.0, 90450.0],       [90451.0, 90452.0, 90453.0, 90454.0, 90455.0, 90456.0],       [90457.0, 90458.0, 90459.0, 90460.0, 90461.0, 90462.0],       [90463.0, 90464.0, 90465.0, 90466.0, 90467.0, 90468.0]]],     [[[90469.0, 90470.0, 90471.0, 90472.0, 90473.0, 90474.0],       [90475.0, 90476.0, 90477.0, 90478.0, 90479.0, 90480.0],       [90481.0, 90482.0, 90483.0, 90484.0, 90485.0, 90486.0],       [90487.0, 90488.0, 90489.0, 90490.0, 90491.0, 90492.0],       [90493.0, 90494.0, 90495.0, 90496.0, 90497.0, 90498.0],       [90499.0, 90500.0, 90501.0, 90502.0, 90503.0, 90504.0],       [90505.0, 90506.0, 90507.0, 90508.0, 90509.0, 90510.0]],      [[90511.0, 90512.0, 90513.0, 90514.0, 90515.0, 90516.0],       [90517.0, 90518.0, 90519.0, 90520.0, 90521.0, 90522.0],       [90523.0, 90524.0, 90525.0, 90526.0, 90527.0, 90528.0],       [90529.0, 90530.0, 90531.0, 90532.0, 90533.0, 90534.0],       [90535.0, 90536.0, 90537.0, 90538.0, 90539.0, 90540.0],       [90541.0, 90542.0, 90543.0, 90544.0, 90545.0, 90546.0],       [90547.0, 90548.0, 90549.0, 90550.0, 90551.0, 90552.0]],      [[90553.0, 90554.0, 90555.0, 90556.0, 90557.0, 90558.0],       [90559.0, 90560.0, 90561.0, 90562.0, 90563.0, 90564.0],       [90565.0, 90566.0, 90567.0, 90568.0, 90569.0, 90570.0],       [90571.0, 90572.0, 90573.0, 90574.0, 90575.0, 90576.0],       [90577.0, 90578.0, 90579.0, 90580.0, 90581.0, 90582.0],       [90583.0, 90584.0, 90585.0, 90586.0, 90587.0, 90588.0],       [90589.0, 90590.0, 90591.0, 90592.0, 90593.0, 90594.0]],      [[90595.0, 90596.0, 90597.0, 90598.0, 90599.0, 90600.0],       [90601.0, 90602.0, 90603.0, 90604.0, 90605.0, 90606.0],       [90607.0, 90608.0, 90609.0, 90610.0, 90611.0, 90612.0],       [90613.0, 90614.0, 90615.0, 90616.0, 90617.0, 90618.0],       [90619.0, 90620.0, 90621.0, 90622.0, 90623.0, 90624.0],       [90625.0, 90626.0, 90627.0, 90628.0, 90629.0, 90630.0],       [90631.0, 90632.0, 90633.0, 90634.0, 90635.0, 90636.0]],      [[90637.0, 90638.0, 90639.0, 90640.0, 90641.0, 90642.0],       [90643.0, 90644.0, 90645.0, 90646.0, 90647.0, 90648.0],       [90649.0, 90650.0, 90651.0, 90652.0, 90653.0, 90654.0],       [90655.0, 90656.0, 90657.0, 90658.0, 90659.0, 90660.0],       [90661.0, 90662.0, 90663.0, 90664.0, 90665.0, 90666.0],       [90667.0, 90668.0, 90669.0, 90670.0, 90671.0, 90672.0],       [90673.0, 90674.0, 90675.0, 90676.0, 90677.0, 90678.0]],      [[90679.0, 90680.0, 90681.0, 90682.0, 90683.0, 90684.0],       [90685.0, 90686.0, 90687.0, 90688.0, 90689.0, 90690.0],       [90691.0, 90692.0, 90693.0, 90694.0, 90695.0, 90696.0],       [90697.0, 90698.0, 90699.0, 90700.0, 90701.0, 90702.0],       [90703.0, 90704.0, 90705.0, 90706.0, 90707.0, 90708.0],       [90709.0, 90710.0, 90711.0, 90712.0, 90713.0, 90714.0],       [90715.0, 90716.0, 90717.0, 90718.0, 90719.0, 90720.0]]]]],   [[[[[90721.0, 90722.0, 90723.0, 90724.0, 90725.0, 90726.0],       [90727.0, 90728.0, 90729.0, 90730.0, 90731.0, 90732.0],       [90733.0, 90734.0, 90735.0, 90736.0, 90737.0, 90738.0],       [90739.0, 90740.0, 90741.0, 90742.0, 90743.0, 90744.0],       [90745.0, 90746.0, 90747.0, 90748.0, 90749.0, 90750.0],       [90751.0, 90752.0, 90753.0, 90754.0, 90755.0, 90756.0],       [90757.0, 90758.0, 90759.0, 90760.0, 90761.0, 90762.0]],      [[90763.0, 90764.0, 90765.0, 90766.0, 90767.0, 90768.0],       [90769.0, 90770.0, 90771.0, 90772.0, 90773.0, 90774.0],       [90775.0, 90776.0, 90777.0, 90778.0, 90779.0, 90780.0],       [90781.0, 90782.0, 90783.0, 90784.0, 90785.0, 90786.0],       [90787.0, 90788.0, 90789.0, 90790.0, 90791.0, 90792.0],       [90793.0, 90794.0, 90795.0, 90796.0, 90797.0, 90798.0],       [90799.0, 90800.0, 90801.0, 90802.0, 90803.0, 90804.0]],      [[90805.0, 90806.0, 90807.0, 90808.0, 90809.0, 90810.0],       [90811.0, 90812.0, 90813.0, 90814.0, 90815.0, 90816.0],       [90817.0, 90818.0, 90819.0, 90820.0, 90821.0, 90822.0],       [90823.0, 90824.0, 90825.0, 90826.0, 90827.0, 90828.0],       [90829.0, 90830.0, 90831.0, 90832.0, 90833.0, 90834.0],       [90835.0, 90836.0, 90837.0, 90838.0, 90839.0, 90840.0],       [90841.0, 90842.0, 90843.0, 90844.0, 90845.0, 90846.0]],      [[90847.0, 90848.0, 90849.0, 90850.0, 90851.0, 90852.0],       [90853.0, 90854.0, 90855.0, 90856.0, 90857.0, 90858.0],       [90859.0, 90860.0, 90861.0, 90862.0, 90863.0, 90864.0],       [90865.0, 90866.0, 90867.0, 90868.0, 90869.0, 90870.0],       [90871.0, 90872.0, 90873.0, 90874.0, 90875.0, 90876.0],       [90877.0, 90878.0, 90879.0, 90880.0, 90881.0, 90882.0],       [90883.0, 90884.0, 90885.0, 90886.0, 90887.0, 90888.0]],      [[90889.0, 90890.0, 90891.0, 90892.0, 90893.0, 90894.0],       [90895.0, 90896.0, 90897.0, 90898.0, 90899.0, 90900.0],       [90901.0, 90902.0, 90903.0, 90904.0, 90905.0, 90906.0],       [90907.0, 90908.0, 90909.0, 90910.0, 90911.0, 90912.0],       [90913.0, 90914.0, 90915.0, 90916.0, 90917.0, 90918.0],       [90919.0, 90920.0, 90921.0, 90922.0, 90923.0, 90924.0],       [90925.0, 90926.0, 90927.0, 90928.0, 90929.0, 90930.0]],      [[90931.0, 90932.0, 90933.0, 90934.0, 90935.0, 90936.0],       [90937.0, 90938.0, 90939.0, 90940.0, 90941.0, 90942.0],       [90943.0, 90944.0, 90945.0, 90946.0, 90947.0, 90948.0],       [90949.0, 90950.0, 90951.0, 90952.0, 90953.0, 90954.0],       [90955.0, 90956.0, 90957.0, 90958.0, 90959.0, 90960.0],       [90961.0, 90962.0, 90963.0, 90964.0, 90965.0, 90966.0],       [90967.0, 90968.0, 90969.0, 90970.0, 90971.0, 90972.0]]],     [[[90973.0, 90974.0, 90975.0, 90976.0, 90977.0, 90978.0],       [90979.0, 90980.0, 90981.0, 90982.0, 90983.0, 90984.0],       [90985.0, 90986.0, 90987.0, 90988.0, 90989.0, 90990.0],       [90991.0, 90992.0, 90993.0, 90994.0, 90995.0, 90996.0],       [90997.0, 90998.0, 90999.0, 91000.0, 91001.0, 91002.0],       [91003.0, 91004.0, 91005.0, 91006.0, 91007.0, 91008.0],       [91009.0, 91010.0, 91011.0, 91012.0, 91013.0, 91014.0]],      [[91015.0, 91016.0, 91017.0, 91018.0, 91019.0, 91020.0],       [91021.0, 91022.0, 91023.0, 91024.0, 91025.0, 91026.0],       [91027.0, 91028.0, 91029.0, 91030.0, 91031.0, 91032.0],       [91033.0, 91034.0, 91035.0, 91036.0, 91037.0, 91038.0],       [91039.0, 91040.0, 91041.0, 91042.0, 91043.0, 91044.0],       [91045.0, 91046.0, 91047.0, 91048.0, 91049.0, 91050.0],       [91051.0, 91052.0, 91053.0, 91054.0, 91055.0, 91056.0]],      [[91057.0, 91058.0, 91059.0, 91060.0, 91061.0, 91062.0],       [91063.0, 91064.0, 91065.0, 91066.0, 91067.0, 91068.0],       [91069.0, 91070.0, 91071.0, 91072.0, 91073.0, 91074.0],       [91075.0, 91076.0, 91077.0, 91078.0, 91079.0, 91080.0],       [91081.0, 91082.0, 91083.0, 91084.0, 91085.0, 91086.0],       [91087.0, 91088.0, 91089.0, 91090.0, 91091.0, 91092.0],       [91093.0, 91094.0, 91095.0, 91096.0, 91097.0, 91098.0]],      [[91099.0, 91100.0, 91101.0, 91102.0, 91103.0, 91104.0],       [91105.0, 91106.0, 91107.0, 91108.0, 91109.0, 91110.0],       [91111.0, 91112.0, 91113.0, 91114.0, 91115.0, 91116.0],       [91117.0, 91118.0, 91119.0, 91120.0, 91121.0, 91122.0],       [91123.0, 91124.0, 91125.0, 91126.0, 91127.0, 91128.0],       [91129.0, 91130.0, 91131.0, 91132.0, 91133.0, 91134.0],       [91135.0, 91136.0, 91137.0, 91138.0, 91139.0, 91140.0]],      [[91141.0, 91142.0, 91143.0, 91144.0, 91145.0, 91146.0],       [91147.0, 91148.0, 91149.0, 91150.0, 91151.0, 91152.0],       [91153.0, 91154.0, 91155.0, 91156.0, 91157.0, 91158.0],       [91159.0, 91160.0, 91161.0, 91162.0, 91163.0, 91164.0],       [91165.0, 91166.0, 91167.0, 91168.0, 91169.0, 91170.0],       [91171.0, 91172.0, 91173.0, 91174.0, 91175.0, 91176.0],       [91177.0, 91178.0, 91179.0, 91180.0, 91181.0, 91182.0]],      [[91183.0, 91184.0, 91185.0, 91186.0, 91187.0, 91188.0],       [91189.0, 91190.0, 91191.0, 91192.0, 91193.0, 91194.0],       [91195.0, 91196.0, 91197.0, 91198.0, 91199.0, 91200.0],       [91201.0, 91202.0, 91203.0, 91204.0, 91205.0, 91206.0],       [91207.0, 91208.0, 91209.0, 91210.0, 91211.0, 91212.0],       [91213.0, 91214.0, 91215.0, 91216.0, 91217.0, 91218.0],       [91219.0, 91220.0, 91221.0, 91222.0, 91223.0, 91224.0]]],     [[[91225.0, 91226.0, 91227.0, 91228.0, 91229.0, 91230.0],       [91231.0, 91232.0, 91233.0, 91234.0, 91235.0, 91236.0],       [91237.0, 91238.0, 91239.0, 91240.0, 91241.0, 91242.0],       [91243.0, 91244.0, 91245.0, 91246.0, 91247.0, 91248.0],       [91249.0, 91250.0, 91251.0, 91252.0, 91253.0, 91254.0],       [91255.0, 91256.0, 91257.0, 91258.0, 91259.0, 91260.0],       [91261.0, 91262.0, 91263.0, 91264.0, 91265.0, 91266.0]],      [[91267.0, 91268.0, 91269.0, 91270.0, 91271.0, 91272.0],       [91273.0, 91274.0, 91275.0, 91276.0, 91277.0, 91278.0],       [91279.0, 91280.0, 91281.0, 91282.0, 91283.0, 91284.0],       [91285.0, 91286.0, 91287.0, 91288.0, 91289.0, 91290.0],       [91291.0, 91292.0, 91293.0, 91294.0, 91295.0, 91296.0],       [91297.0, 91298.0, 91299.0, 91300.0, 91301.0, 91302.0],       [91303.0, 91304.0, 91305.0, 91306.0, 91307.0, 91308.0]],      [[91309.0, 91310.0, 91311.0, 91312.0, 91313.0, 91314.0],       [91315.0, 91316.0, 91317.0, 91318.0, 91319.0, 91320.0],       [91321.0, 91322.0, 91323.0, 91324.0, 91325.0, 91326.0],       [91327.0, 91328.0, 91329.0, 91330.0, 91331.0, 91332.0],       [91333.0, 91334.0, 91335.0, 91336.0, 91337.0, 91338.0],       [91339.0, 91340.0, 91341.0, 91342.0, 91343.0, 91344.0],       [91345.0, 91346.0, 91347.0, 91348.0, 91349.0, 91350.0]],      [[91351.0, 91352.0, 91353.0, 91354.0, 91355.0, 91356.0],       [91357.0, 91358.0, 91359.0, 91360.0, 91361.0, 91362.0],       [91363.0, 91364.0, 91365.0, 91366.0, 91367.0, 91368.0],       [91369.0, 91370.0, 91371.0, 91372.0, 91373.0, 91374.0],       [91375.0, 91376.0, 91377.0, 91378.0, 91379.0, 91380.0],       [91381.0, 91382.0, 91383.0, 91384.0, 91385.0, 91386.0],       [91387.0, 91388.0, 91389.0, 91390.0, 91391.0, 91392.0]],      [[91393.0, 91394.0, 91395.0, 91396.0, 91397.0, 91398.0],       [91399.0, 91400.0, 91401.0, 91402.0, 91403.0, 91404.0],       [91405.0, 91406.0, 91407.0, 91408.0, 91409.0, 91410.0],       [91411.0, 91412.0, 91413.0, 91414.0, 91415.0, 91416.0],       [91417.0, 91418.0, 91419.0, 91420.0, 91421.0, 91422.0],       [91423.0, 91424.0, 91425.0, 91426.0, 91427.0, 91428.0],       [91429.0, 91430.0, 91431.0, 91432.0, 91433.0, 91434.0]],      [[91435.0, 91436.0, 91437.0, 91438.0, 91439.0, 91440.0],       [91441.0, 91442.0, 91443.0, 91444.0, 91445.0, 91446.0],       [91447.0, 91448.0, 91449.0, 91450.0, 91451.0, 91452.0],       [91453.0, 91454.0, 91455.0, 91456.0, 91457.0, 91458.0],       [91459.0, 91460.0, 91461.0, 91462.0, 91463.0, 91464.0],       [91465.0, 91466.0, 91467.0, 91468.0, 91469.0, 91470.0],       [91471.0, 91472.0, 91473.0, 91474.0, 91475.0, 91476.0]]],     [[[91477.0, 91478.0, 91479.0, 91480.0, 91481.0, 91482.0],       [91483.0, 91484.0, 91485.0, 91486.0, 91487.0, 91488.0],       [91489.0, 91490.0, 91491.0, 91492.0, 91493.0, 91494.0],       [91495.0, 91496.0, 91497.0, 91498.0, 91499.0, 91500.0],       [91501.0, 91502.0, 91503.0, 91504.0, 91505.0, 91506.0],       [91507.0, 91508.0, 91509.0, 91510.0, 91511.0, 91512.0],       [91513.0, 91514.0, 91515.0, 91516.0, 91517.0, 91518.0]],      [[91519.0, 91520.0, 91521.0, 91522.0, 91523.0, 91524.0],       [91525.0, 91526.0, 91527.0, 91528.0, 91529.0, 91530.0],       [91531.0, 91532.0, 91533.0, 91534.0, 91535.0, 91536.0],       [91537.0, 91538.0, 91539.0, 91540.0, 91541.0, 91542.0],       [91543.0, 91544.0, 91545.0, 91546.0, 91547.0, 91548.0],       [91549.0, 91550.0, 91551.0, 91552.0, 91553.0, 91554.0],       [91555.0, 91556.0, 91557.0, 91558.0, 91559.0, 91560.0]],      [[91561.0, 91562.0, 91563.0, 91564.0, 91565.0, 91566.0],       [91567.0, 91568.0, 91569.0, 91570.0, 91571.0, 91572.0],       [91573.0, 91574.0, 91575.0, 91576.0, 91577.0, 91578.0],       [91579.0, 91580.0, 91581.0, 91582.0, 91583.0, 91584.0],       [91585.0, 91586.0, 91587.0, 91588.0, 91589.0, 91590.0],       [91591.0, 91592.0, 91593.0, 91594.0, 91595.0, 91596.0],       [91597.0, 91598.0, 91599.0, 91600.0, 91601.0, 91602.0]],      [[91603.0, 91604.0, 91605.0, 91606.0, 91607.0, 91608.0],       [91609.0, 91610.0, 91611.0, 91612.0, 91613.0, 91614.0],       [91615.0, 91616.0, 91617.0, 91618.0, 91619.0, 91620.0],       [91621.0, 91622.0, 91623.0, 91624.0, 91625.0, 91626.0],       [91627.0, 91628.0, 91629.0, 91630.0, 91631.0, 91632.0],       [91633.0, 91634.0, 91635.0, 91636.0, 91637.0, 91638.0],       [91639.0, 91640.0, 91641.0, 91642.0, 91643.0, 91644.0]],      [[91645.0, 91646.0, 91647.0, 91648.0, 91649.0, 91650.0],       [91651.0, 91652.0, 91653.0, 91654.0, 91655.0, 91656.0],       [91657.0, 91658.0, 91659.0, 91660.0, 91661.0, 91662.0],       [91663.0, 91664.0, 91665.0, 91666.0, 91667.0, 91668.0],       [91669.0, 91670.0, 91671.0, 91672.0, 91673.0, 91674.0],       [91675.0, 91676.0, 91677.0, 91678.0, 91679.0, 91680.0],       [91681.0, 91682.0, 91683.0, 91684.0, 91685.0, 91686.0]],      [[91687.0, 91688.0, 91689.0, 91690.0, 91691.0, 91692.0],       [91693.0, 91694.0, 91695.0, 91696.0, 91697.0, 91698.0],       [91699.0, 91700.0, 91701.0, 91702.0, 91703.0, 91704.0],       [91705.0, 91706.0, 91707.0, 91708.0, 91709.0, 91710.0],       [91711.0, 91712.0, 91713.0, 91714.0, 91715.0, 91716.0],       [91717.0, 91718.0, 91719.0, 91720.0, 91721.0, 91722.0],       [91723.0, 91724.0, 91725.0, 91726.0, 91727.0, 91728.0]]]],    [[[[91729.0, 91730.0, 91731.0, 91732.0, 91733.0, 91734.0],       [91735.0, 91736.0, 91737.0, 91738.0, 91739.0, 91740.0],       [91741.0, 91742.0, 91743.0, 91744.0, 91745.0, 91746.0],       [91747.0, 91748.0, 91749.0, 91750.0, 91751.0, 91752.0],       [91753.0, 91754.0, 91755.0, 91756.0, 91757.0, 91758.0],       [91759.0, 91760.0, 91761.0, 91762.0, 91763.0, 91764.0],       [91765.0, 91766.0, 91767.0, 91768.0, 91769.0, 91770.0]],      [[91771.0, 91772.0, 91773.0, 91774.0, 91775.0, 91776.0],       [91777.0, 91778.0, 91779.0, 91780.0, 91781.0, 91782.0],       [91783.0, 91784.0, 91785.0, 91786.0, 91787.0, 91788.0],       [91789.0, 91790.0, 91791.0, 91792.0, 91793.0, 91794.0],       [91795.0, 91796.0, 91797.0, 91798.0, 91799.0, 91800.0],       [91801.0, 91802.0, 91803.0, 91804.0, 91805.0, 91806.0],       [91807.0, 91808.0, 91809.0, 91810.0, 91811.0, 91812.0]],      [[91813.0, 91814.0, 91815.0, 91816.0, 91817.0, 91818.0],       [91819.0, 91820.0, 91821.0, 91822.0, 91823.0, 91824.0],       [91825.0, 91826.0, 91827.0, 91828.0, 91829.0, 91830.0],       [91831.0, 91832.0, 91833.0, 91834.0, 91835.0, 91836.0],       [91837.0, 91838.0, 91839.0, 91840.0, 91841.0, 91842.0],       [91843.0, 91844.0, 91845.0, 91846.0, 91847.0, 91848.0],       [91849.0, 91850.0, 91851.0, 91852.0, 91853.0, 91854.0]],      [[91855.0, 91856.0, 91857.0, 91858.0, 91859.0, 91860.0],       [91861.0, 91862.0, 91863.0, 91864.0, 91865.0, 91866.0],       [91867.0, 91868.0, 91869.0, 91870.0, 91871.0, 91872.0],       [91873.0, 91874.0, 91875.0, 91876.0, 91877.0, 91878.0],       [91879.0, 91880.0, 91881.0, 91882.0, 91883.0, 91884.0],       [91885.0, 91886.0, 91887.0, 91888.0, 91889.0, 91890.0],       [91891.0, 91892.0, 91893.0, 91894.0, 91895.0, 91896.0]],      [[91897.0, 91898.0, 91899.0, 91900.0, 91901.0, 91902.0],       [91903.0, 91904.0, 91905.0, 91906.0, 91907.0, 91908.0],       [91909.0, 91910.0, 91911.0, 91912.0, 91913.0, 91914.0],       [91915.0, 91916.0, 91917.0, 91918.0, 91919.0, 91920.0],       [91921.0, 91922.0, 91923.0, 91924.0, 91925.0, 91926.0],       [91927.0, 91928.0, 91929.0, 91930.0, 91931.0, 91932.0],       [91933.0, 91934.0, 91935.0, 91936.0, 91937.0, 91938.0]],      [[91939.0, 91940.0, 91941.0, 91942.0, 91943.0, 91944.0],       [91945.0, 91946.0, 91947.0, 91948.0, 91949.0, 91950.0],       [91951.0, 91952.0, 91953.0, 91954.0, 91955.0, 91956.0],       [91957.0, 91958.0, 91959.0, 91960.0, 91961.0, 91962.0],       [91963.0, 91964.0, 91965.0, 91966.0, 91967.0, 91968.0],       [91969.0, 91970.0, 91971.0, 91972.0, 91973.0, 91974.0],       [91975.0, 91976.0, 91977.0, 91978.0, 91979.0, 91980.0]]],     [[[91981.0, 91982.0, 91983.0, 91984.0, 91985.0, 91986.0],       [91987.0, 91988.0, 91989.0, 91990.0, 91991.0, 91992.0],       [91993.0, 91994.0, 91995.0, 91996.0, 91997.0, 91998.0],       [91999.0, 92000.0, 92001.0, 92002.0, 92003.0, 92004.0],       [92005.0, 92006.0, 92007.0, 92008.0, 92009.0, 92010.0],       [92011.0, 92012.0, 92013.0, 92014.0, 92015.0, 92016.0],       [92017.0, 92018.0, 92019.0, 92020.0, 92021.0, 92022.0]],      [[92023.0, 92024.0, 92025.0, 92026.0, 92027.0, 92028.0],       [92029.0, 92030.0, 92031.0, 92032.0, 92033.0, 92034.0],       [92035.0, 92036.0, 92037.0, 92038.0, 92039.0, 92040.0],       [92041.0, 92042.0, 92043.0, 92044.0, 92045.0, 92046.0],       [92047.0, 92048.0, 92049.0, 92050.0, 92051.0, 92052.0],       [92053.0, 92054.0, 92055.0, 92056.0, 92057.0, 92058.0],       [92059.0, 92060.0, 92061.0, 92062.0, 92063.0, 92064.0]],      [[92065.0, 92066.0, 92067.0, 92068.0, 92069.0, 92070.0],       [92071.0, 92072.0, 92073.0, 92074.0, 92075.0, 92076.0],       [92077.0, 92078.0, 92079.0, 92080.0, 92081.0, 92082.0],       [92083.0, 92084.0, 92085.0, 92086.0, 92087.0, 92088.0],       [92089.0, 92090.0, 92091.0, 92092.0, 92093.0, 92094.0],       [92095.0, 92096.0, 92097.0, 92098.0, 92099.0, 92100.0],       [92101.0, 92102.0, 92103.0, 92104.0, 92105.0, 92106.0]],      [[92107.0, 92108.0, 92109.0, 92110.0, 92111.0, 92112.0],       [92113.0, 92114.0, 92115.0, 92116.0, 92117.0, 92118.0],       [92119.0, 92120.0, 92121.0, 92122.0, 92123.0, 92124.0],       [92125.0, 92126.0, 92127.0, 92128.0, 92129.0, 92130.0],       [92131.0, 92132.0, 92133.0, 92134.0, 92135.0, 92136.0],       [92137.0, 92138.0, 92139.0, 92140.0, 92141.0, 92142.0],       [92143.0, 92144.0, 92145.0, 92146.0, 92147.0, 92148.0]],      [[92149.0, 92150.0, 92151.0, 92152.0, 92153.0, 92154.0],       [92155.0, 92156.0, 92157.0, 92158.0, 92159.0, 92160.0],       [92161.0, 92162.0, 92163.0, 92164.0, 92165.0, 92166.0],       [92167.0, 92168.0, 92169.0, 92170.0, 92171.0, 92172.0],       [92173.0, 92174.0, 92175.0, 92176.0, 92177.0, 92178.0],       [92179.0, 92180.0, 92181.0, 92182.0, 92183.0, 92184.0],       [92185.0, 92186.0, 92187.0, 92188.0, 92189.0, 92190.0]],      [[92191.0, 92192.0, 92193.0, 92194.0, 92195.0, 92196.0],       [92197.0, 92198.0, 92199.0, 92200.0, 92201.0, 92202.0],       [92203.0, 92204.0, 92205.0, 92206.0, 92207.0, 92208.0],       [92209.0, 92210.0, 92211.0, 92212.0, 92213.0, 92214.0],       [92215.0, 92216.0, 92217.0, 92218.0, 92219.0, 92220.0],       [92221.0, 92222.0, 92223.0, 92224.0, 92225.0, 92226.0],       [92227.0, 92228.0, 92229.0, 92230.0, 92231.0, 92232.0]]],     [[[92233.0, 92234.0, 92235.0, 92236.0, 92237.0, 92238.0],       [92239.0, 92240.0, 92241.0, 92242.0, 92243.0, 92244.0],       [92245.0, 92246.0, 92247.0, 92248.0, 92249.0, 92250.0],       [92251.0, 92252.0, 92253.0, 92254.0, 92255.0, 92256.0],       [92257.0, 92258.0, 92259.0, 92260.0, 92261.0, 92262.0],       [92263.0, 92264.0, 92265.0, 92266.0, 92267.0, 92268.0],       [92269.0, 92270.0, 92271.0, 92272.0, 92273.0, 92274.0]],      [[92275.0, 92276.0, 92277.0, 92278.0, 92279.0, 92280.0],       [92281.0, 92282.0, 92283.0, 92284.0, 92285.0, 92286.0],       [92287.0, 92288.0, 92289.0, 92290.0, 92291.0, 92292.0],       [92293.0, 92294.0, 92295.0, 92296.0, 92297.0, 92298.0],       [92299.0, 92300.0, 92301.0, 92302.0, 92303.0, 92304.0],       [92305.0, 92306.0, 92307.0, 92308.0, 92309.0, 92310.0],       [92311.0, 92312.0, 92313.0, 92314.0, 92315.0, 92316.0]],      [[92317.0, 92318.0, 92319.0, 92320.0, 92321.0, 92322.0],       [92323.0, 92324.0, 92325.0, 92326.0, 92327.0, 92328.0],       [92329.0, 92330.0, 92331.0, 92332.0, 92333.0, 92334.0],       [92335.0, 92336.0, 92337.0, 92338.0, 92339.0, 92340.0],       [92341.0, 92342.0, 92343.0, 92344.0, 92345.0, 92346.0],       [92347.0, 92348.0, 92349.0, 92350.0, 92351.0, 92352.0],       [92353.0, 92354.0, 92355.0, 92356.0, 92357.0, 92358.0]],      [[92359.0, 92360.0, 92361.0, 92362.0, 92363.0, 92364.0],       [92365.0, 92366.0, 92367.0, 92368.0, 92369.0, 92370.0],       [92371.0, 92372.0, 92373.0, 92374.0, 92375.0, 92376.0],       [92377.0, 92378.0, 92379.0, 92380.0, 92381.0, 92382.0],       [92383.0, 92384.0, 92385.0, 92386.0, 92387.0, 92388.0],       [92389.0, 92390.0, 92391.0, 92392.0, 92393.0, 92394.0],       [92395.0, 92396.0, 92397.0, 92398.0, 92399.0, 92400.0]],      [[92401.0, 92402.0, 92403.0, 92404.0, 92405.0, 92406.0],       [92407.0, 92408.0, 92409.0, 92410.0, 92411.0, 92412.0],       [92413.0, 92414.0, 92415.0, 92416.0, 92417.0, 92418.0],       [92419.0, 92420.0, 92421.0, 92422.0, 92423.0, 92424.0],       [92425.0, 92426.0, 92427.0, 92428.0, 92429.0, 92430.0],       [92431.0, 92432.0, 92433.0, 92434.0, 92435.0, 92436.0],       [92437.0, 92438.0, 92439.0, 92440.0, 92441.0, 92442.0]],      [[92443.0, 92444.0, 92445.0, 92446.0, 92447.0, 92448.0],       [92449.0, 92450.0, 92451.0, 92452.0, 92453.0, 92454.0],       [92455.0, 92456.0, 92457.0, 92458.0, 92459.0, 92460.0],       [92461.0, 92462.0, 92463.0, 92464.0, 92465.0, 92466.0],       [92467.0, 92468.0, 92469.0, 92470.0, 92471.0, 92472.0],       [92473.0, 92474.0, 92475.0, 92476.0, 92477.0, 92478.0],       [92479.0, 92480.0, 92481.0, 92482.0, 92483.0, 92484.0]]],     [[[92485.0, 92486.0, 92487.0, 92488.0, 92489.0, 92490.0],       [92491.0, 92492.0, 92493.0, 92494.0, 92495.0, 92496.0],       [92497.0, 92498.0, 92499.0, 92500.0, 92501.0, 92502.0],       [92503.0, 92504.0, 92505.0, 92506.0, 92507.0, 92508.0],       [92509.0, 92510.0, 92511.0, 92512.0, 92513.0, 92514.0],       [92515.0, 92516.0, 92517.0, 92518.0, 92519.0, 92520.0],       [92521.0, 92522.0, 92523.0, 92524.0, 92525.0, 92526.0]],      [[92527.0, 92528.0, 92529.0, 92530.0, 92531.0, 92532.0],       [92533.0, 92534.0, 92535.0, 92536.0, 92537.0, 92538.0],       [92539.0, 92540.0, 92541.0, 92542.0, 92543.0, 92544.0],       [92545.0, 92546.0, 92547.0, 92548.0, 92549.0, 92550.0],       [92551.0, 92552.0, 92553.0, 92554.0, 92555.0, 92556.0],       [92557.0, 92558.0, 92559.0, 92560.0, 92561.0, 92562.0],       [92563.0, 92564.0, 92565.0, 92566.0, 92567.0, 92568.0]],      [[92569.0, 92570.0, 92571.0, 92572.0, 92573.0, 92574.0],       [92575.0, 92576.0, 92577.0, 92578.0, 92579.0, 92580.0],       [92581.0, 92582.0, 92583.0, 92584.0, 92585.0, 92586.0],       [92587.0, 92588.0, 92589.0, 92590.0, 92591.0, 92592.0],       [92593.0, 92594.0, 92595.0, 92596.0, 92597.0, 92598.0],       [92599.0, 92600.0, 92601.0, 92602.0, 92603.0, 92604.0],       [92605.0, 92606.0, 92607.0, 92608.0, 92609.0, 92610.0]],      [[92611.0, 92612.0, 92613.0, 92614.0, 92615.0, 92616.0],       [92617.0, 92618.0, 92619.0, 92620.0, 92621.0, 92622.0],       [92623.0, 92624.0, 92625.0, 92626.0, 92627.0, 92628.0],       [92629.0, 92630.0, 92631.0, 92632.0, 92633.0, 92634.0],       [92635.0, 92636.0, 92637.0, 92638.0, 92639.0, 92640.0],       [92641.0, 92642.0, 92643.0, 92644.0, 92645.0, 92646.0],       [92647.0, 92648.0, 92649.0, 92650.0, 92651.0, 92652.0]],      [[92653.0, 92654.0, 92655.0, 92656.0, 92657.0, 92658.0],       [92659.0, 92660.0, 92661.0, 92662.0, 92663.0, 92664.0],       [92665.0, 92666.0, 92667.0, 92668.0, 92669.0, 92670.0],       [92671.0, 92672.0, 92673.0, 92674.0, 92675.0, 92676.0],       [92677.0, 92678.0, 92679.0, 92680.0, 92681.0, 92682.0],       [92683.0, 92684.0, 92685.0, 92686.0, 92687.0, 92688.0],       [92689.0, 92690.0, 92691.0, 92692.0, 92693.0, 92694.0]],      [[92695.0, 92696.0, 92697.0, 92698.0, 92699.0, 92700.0],       [92701.0, 92702.0, 92703.0, 92704.0, 92705.0, 92706.0],       [92707.0, 92708.0, 92709.0, 92710.0, 92711.0, 92712.0],       [92713.0, 92714.0, 92715.0, 92716.0, 92717.0, 92718.0],       [92719.0, 92720.0, 92721.0, 92722.0, 92723.0, 92724.0],       [92725.0, 92726.0, 92727.0, 92728.0, 92729.0, 92730.0],       [92731.0, 92732.0, 92733.0, 92734.0, 92735.0, 92736.0]]]],    [[[[92737.0, 92738.0, 92739.0, 92740.0, 92741.0, 92742.0],       [92743.0, 92744.0, 92745.0, 92746.0, 92747.0, 92748.0],       [92749.0, 92750.0, 92751.0, 92752.0, 92753.0, 92754.0],       [92755.0, 92756.0, 92757.0, 92758.0, 92759.0, 92760.0],       [92761.0, 92762.0, 92763.0, 92764.0, 92765.0, 92766.0],       [92767.0, 92768.0, 92769.0, 92770.0, 92771.0, 92772.0],       [92773.0, 92774.0, 92775.0, 92776.0, 92777.0, 92778.0]],      [[92779.0, 92780.0, 92781.0, 92782.0, 92783.0, 92784.0],       [92785.0, 92786.0, 92787.0, 92788.0, 92789.0, 92790.0],       [92791.0, 92792.0, 92793.0, 92794.0, 92795.0, 92796.0],       [92797.0, 92798.0, 92799.0, 92800.0, 92801.0, 92802.0],       [92803.0, 92804.0, 92805.0, 92806.0, 92807.0, 92808.0],       [92809.0, 92810.0, 92811.0, 92812.0, 92813.0, 92814.0],       [92815.0, 92816.0, 92817.0, 92818.0, 92819.0, 92820.0]],      [[92821.0, 92822.0, 92823.0, 92824.0, 92825.0, 92826.0],       [92827.0, 92828.0, 92829.0, 92830.0, 92831.0, 92832.0],       [92833.0, 92834.0, 92835.0, 92836.0, 92837.0, 92838.0],       [92839.0, 92840.0, 92841.0, 92842.0, 92843.0, 92844.0],       [92845.0, 92846.0, 92847.0, 92848.0, 92849.0, 92850.0],       [92851.0, 92852.0, 92853.0, 92854.0, 92855.0, 92856.0],       [92857.0, 92858.0, 92859.0, 92860.0, 92861.0, 92862.0]],      [[92863.0, 92864.0, 92865.0, 92866.0, 92867.0, 92868.0],       [92869.0, 92870.0, 92871.0, 92872.0, 92873.0, 92874.0],       [92875.0, 92876.0, 92877.0, 92878.0, 92879.0, 92880.0],       [92881.0, 92882.0, 92883.0, 92884.0, 92885.0, 92886.0],       [92887.0, 92888.0, 92889.0, 92890.0, 92891.0, 92892.0],       [92893.0, 92894.0, 92895.0, 92896.0, 92897.0, 92898.0],       [92899.0, 92900.0, 92901.0, 92902.0, 92903.0, 92904.0]],      [[92905.0, 92906.0, 92907.0, 92908.0, 92909.0, 92910.0],       [92911.0, 92912.0, 92913.0, 92914.0, 92915.0, 92916.0],       [92917.0, 92918.0, 92919.0, 92920.0, 92921.0, 92922.0],       [92923.0, 92924.0, 92925.0, 92926.0, 92927.0, 92928.0],       [92929.0, 92930.0, 92931.0, 92932.0, 92933.0, 92934.0],       [92935.0, 92936.0, 92937.0, 92938.0, 92939.0, 92940.0],       [92941.0, 92942.0, 92943.0, 92944.0, 92945.0, 92946.0]],      [[92947.0, 92948.0, 92949.0, 92950.0, 92951.0, 92952.0],       [92953.0, 92954.0, 92955.0, 92956.0, 92957.0, 92958.0],       [92959.0, 92960.0, 92961.0, 92962.0, 92963.0, 92964.0],       [92965.0, 92966.0, 92967.0, 92968.0, 92969.0, 92970.0],       [92971.0, 92972.0, 92973.0, 92974.0, 92975.0, 92976.0],       [92977.0, 92978.0, 92979.0, 92980.0, 92981.0, 92982.0],       [92983.0, 92984.0, 92985.0, 92986.0, 92987.0, 92988.0]]],     [[[92989.0, 92990.0, 92991.0, 92992.0, 92993.0, 92994.0],       [92995.0, 92996.0, 92997.0, 92998.0, 92999.0, 93000.0],       [93001.0, 93002.0, 93003.0, 93004.0, 93005.0, 93006.0],       [93007.0, 93008.0, 93009.0, 93010.0, 93011.0, 93012.0],       [93013.0, 93014.0, 93015.0, 93016.0, 93017.0, 93018.0],       [93019.0, 93020.0, 93021.0, 93022.0, 93023.0, 93024.0],       [93025.0, 93026.0, 93027.0, 93028.0, 93029.0, 93030.0]],      [[93031.0, 93032.0, 93033.0, 93034.0, 93035.0, 93036.0],       [93037.0, 93038.0, 93039.0, 93040.0, 93041.0, 93042.0],       [93043.0, 93044.0, 93045.0, 93046.0, 93047.0, 93048.0],       [93049.0, 93050.0, 93051.0, 93052.0, 93053.0, 93054.0],       [93055.0, 93056.0, 93057.0, 93058.0, 93059.0, 93060.0],       [93061.0, 93062.0, 93063.0, 93064.0, 93065.0, 93066.0],       [93067.0, 93068.0, 93069.0, 93070.0, 93071.0, 93072.0]],      [[93073.0, 93074.0, 93075.0, 93076.0, 93077.0, 93078.0],       [93079.0, 93080.0, 93081.0, 93082.0, 93083.0, 93084.0],       [93085.0, 93086.0, 93087.0, 93088.0, 93089.0, 93090.0],       [93091.0, 93092.0, 93093.0, 93094.0, 93095.0, 93096.0],       [93097.0, 93098.0, 93099.0, 93100.0, 93101.0, 93102.0],       [93103.0, 93104.0, 93105.0, 93106.0, 93107.0, 93108.0],       [93109.0, 93110.0, 93111.0, 93112.0, 93113.0, 93114.0]],      [[93115.0, 93116.0, 93117.0, 93118.0, 93119.0, 93120.0],       [93121.0, 93122.0, 93123.0, 93124.0, 93125.0, 93126.0],       [93127.0, 93128.0, 93129.0, 93130.0, 93131.0, 93132.0],       [93133.0, 93134.0, 93135.0, 93136.0, 93137.0, 93138.0],       [93139.0, 93140.0, 93141.0, 93142.0, 93143.0, 93144.0],       [93145.0, 93146.0, 93147.0, 93148.0, 93149.0, 93150.0],       [93151.0, 93152.0, 93153.0, 93154.0, 93155.0, 93156.0]],      [[93157.0, 93158.0, 93159.0, 93160.0, 93161.0, 93162.0],       [93163.0, 93164.0, 93165.0, 93166.0, 93167.0, 93168.0],       [93169.0, 93170.0, 93171.0, 93172.0, 93173.0, 93174.0],       [93175.0, 93176.0, 93177.0, 93178.0, 93179.0, 93180.0],       [93181.0, 93182.0, 93183.0, 93184.0, 93185.0, 93186.0],       [93187.0, 93188.0, 93189.0, 93190.0, 93191.0, 93192.0],       [93193.0, 93194.0, 93195.0, 93196.0, 93197.0, 93198.0]],      [[93199.0, 93200.0, 93201.0, 93202.0, 93203.0, 93204.0],       [93205.0, 93206.0, 93207.0, 93208.0, 93209.0, 93210.0],       [93211.0, 93212.0, 93213.0, 93214.0, 93215.0, 93216.0],       [93217.0, 93218.0, 93219.0, 93220.0, 93221.0, 93222.0],       [93223.0, 93224.0, 93225.0, 93226.0, 93227.0, 93228.0],       [93229.0, 93230.0, 93231.0, 93232.0, 93233.0, 93234.0],       [93235.0, 93236.0, 93237.0, 93238.0, 93239.0, 93240.0]]],     [[[93241.0, 93242.0, 93243.0, 93244.0, 93245.0, 93246.0],       [93247.0, 93248.0, 93249.0, 93250.0, 93251.0, 93252.0],       [93253.0, 93254.0, 93255.0, 93256.0, 93257.0, 93258.0],       [93259.0, 93260.0, 93261.0, 93262.0, 93263.0, 93264.0],       [93265.0, 93266.0, 93267.0, 93268.0, 93269.0, 93270.0],       [93271.0, 93272.0, 93273.0, 93274.0, 93275.0, 93276.0],       [93277.0, 93278.0, 93279.0, 93280.0, 93281.0, 93282.0]],      [[93283.0, 93284.0, 93285.0, 93286.0, 93287.0, 93288.0],       [93289.0, 93290.0, 93291.0, 93292.0, 93293.0, 93294.0],       [93295.0, 93296.0, 93297.0, 93298.0, 93299.0, 93300.0],       [93301.0, 93302.0, 93303.0, 93304.0, 93305.0, 93306.0],       [93307.0, 93308.0, 93309.0, 93310.0, 93311.0, 93312.0],       [93313.0, 93314.0, 93315.0, 93316.0, 93317.0, 93318.0],       [93319.0, 93320.0, 93321.0, 93322.0, 93323.0, 93324.0]],      [[93325.0, 93326.0, 93327.0, 93328.0, 93329.0, 93330.0],       [93331.0, 93332.0, 93333.0, 93334.0, 93335.0, 93336.0],       [93337.0, 93338.0, 93339.0, 93340.0, 93341.0, 93342.0],       [93343.0, 93344.0, 93345.0, 93346.0, 93347.0, 93348.0],       [93349.0, 93350.0, 93351.0, 93352.0, 93353.0, 93354.0],       [93355.0, 93356.0, 93357.0, 93358.0, 93359.0, 93360.0],       [93361.0, 93362.0, 93363.0, 93364.0, 93365.0, 93366.0]],      [[93367.0, 93368.0, 93369.0, 93370.0, 93371.0, 93372.0],       [93373.0, 93374.0, 93375.0, 93376.0, 93377.0, 93378.0],       [93379.0, 93380.0, 93381.0, 93382.0, 93383.0, 93384.0],       [93385.0, 93386.0, 93387.0, 93388.0, 93389.0, 93390.0],       [93391.0, 93392.0, 93393.0, 93394.0, 93395.0, 93396.0],       [93397.0, 93398.0, 93399.0, 93400.0, 93401.0, 93402.0],       [93403.0, 93404.0, 93405.0, 93406.0, 93407.0, 93408.0]],      [[93409.0, 93410.0, 93411.0, 93412.0, 93413.0, 93414.0],       [93415.0, 93416.0, 93417.0, 93418.0, 93419.0, 93420.0],       [93421.0, 93422.0, 93423.0, 93424.0, 93425.0, 93426.0],       [93427.0, 93428.0, 93429.0, 93430.0, 93431.0, 93432.0],       [93433.0, 93434.0, 93435.0, 93436.0, 93437.0, 93438.0],       [93439.0, 93440.0, 93441.0, 93442.0, 93443.0, 93444.0],       [93445.0, 93446.0, 93447.0, 93448.0, 93449.0, 93450.0]],      [[93451.0, 93452.0, 93453.0, 93454.0, 93455.0, 93456.0],       [93457.0, 93458.0, 93459.0, 93460.0, 93461.0, 93462.0],       [93463.0, 93464.0, 93465.0, 93466.0, 93467.0, 93468.0],       [93469.0, 93470.0, 93471.0, 93472.0, 93473.0, 93474.0],       [93475.0, 93476.0, 93477.0, 93478.0, 93479.0, 93480.0],       [93481.0, 93482.0, 93483.0, 93484.0, 93485.0, 93486.0],       [93487.0, 93488.0, 93489.0, 93490.0, 93491.0, 93492.0]]],     [[[93493.0, 93494.0, 93495.0, 93496.0, 93497.0, 93498.0],       [93499.0, 93500.0, 93501.0, 93502.0, 93503.0, 93504.0],       [93505.0, 93506.0, 93507.0, 93508.0, 93509.0, 93510.0],       [93511.0, 93512.0, 93513.0, 93514.0, 93515.0, 93516.0],       [93517.0, 93518.0, 93519.0, 93520.0, 93521.0, 93522.0],       [93523.0, 93524.0, 93525.0, 93526.0, 93527.0, 93528.0],       [93529.0, 93530.0, 93531.0, 93532.0, 93533.0, 93534.0]],      [[93535.0, 93536.0, 93537.0, 93538.0, 93539.0, 93540.0],       [93541.0, 93542.0, 93543.0, 93544.0, 93545.0, 93546.0],       [93547.0, 93548.0, 93549.0, 93550.0, 93551.0, 93552.0],       [93553.0, 93554.0, 93555.0, 93556.0, 93557.0, 93558.0],       [93559.0, 93560.0, 93561.0, 93562.0, 93563.0, 93564.0],       [93565.0, 93566.0, 93567.0, 93568.0, 93569.0, 93570.0],       [93571.0, 93572.0, 93573.0, 93574.0, 93575.0, 93576.0]],      [[93577.0, 93578.0, 93579.0, 93580.0, 93581.0, 93582.0],       [93583.0, 93584.0, 93585.0, 93586.0, 93587.0, 93588.0],       [93589.0, 93590.0, 93591.0, 93592.0, 93593.0, 93594.0],       [93595.0, 93596.0, 93597.0, 93598.0, 93599.0, 93600.0],       [93601.0, 93602.0, 93603.0, 93604.0, 93605.0, 93606.0],       [93607.0, 93608.0, 93609.0, 93610.0, 93611.0, 93612.0],       [93613.0, 93614.0, 93615.0, 93616.0, 93617.0, 93618.0]],      [[93619.0, 93620.0, 93621.0, 93622.0, 93623.0, 93624.0],       [93625.0, 93626.0, 93627.0, 93628.0, 93629.0, 93630.0],       [93631.0, 93632.0, 93633.0, 93634.0, 93635.0, 93636.0],       [93637.0, 93638.0, 93639.0, 93640.0, 93641.0, 93642.0],       [93643.0, 93644.0, 93645.0, 93646.0, 93647.0, 93648.0],       [93649.0, 93650.0, 93651.0, 93652.0, 93653.0, 93654.0],       [93655.0, 93656.0, 93657.0, 93658.0, 93659.0, 93660.0]],      [[93661.0, 93662.0, 93663.0, 93664.0, 93665.0, 93666.0],       [93667.0, 93668.0, 93669.0, 93670.0, 93671.0, 93672.0],       [93673.0, 93674.0, 93675.0, 93676.0, 93677.0, 93678.0],       [93679.0, 93680.0, 93681.0, 93682.0, 93683.0, 93684.0],       [93685.0, 93686.0, 93687.0, 93688.0, 93689.0, 93690.0],       [93691.0, 93692.0, 93693.0, 93694.0, 93695.0, 93696.0],       [93697.0, 93698.0, 93699.0, 93700.0, 93701.0, 93702.0]],      [[93703.0, 93704.0, 93705.0, 93706.0, 93707.0, 93708.0],       [93709.0, 93710.0, 93711.0, 93712.0, 93713.0, 93714.0],       [93715.0, 93716.0, 93717.0, 93718.0, 93719.0, 93720.0],       [93721.0, 93722.0, 93723.0, 93724.0, 93725.0, 93726.0],       [93727.0, 93728.0, 93729.0, 93730.0, 93731.0, 93732.0],       [93733.0, 93734.0, 93735.0, 93736.0, 93737.0, 93738.0],       [93739.0, 93740.0, 93741.0, 93742.0, 93743.0, 93744.0]]]],    [[[[93745.0, 93746.0, 93747.0, 93748.0, 93749.0, 93750.0],       [93751.0, 93752.0, 93753.0, 93754.0, 93755.0, 93756.0],       [93757.0, 93758.0, 93759.0, 93760.0, 93761.0, 93762.0],       [93763.0, 93764.0, 93765.0, 93766.0, 93767.0, 93768.0],       [93769.0, 93770.0, 93771.0, 93772.0, 93773.0, 93774.0],       [93775.0, 93776.0, 93777.0, 93778.0, 93779.0, 93780.0],       [93781.0, 93782.0, 93783.0, 93784.0, 93785.0, 93786.0]],      [[93787.0, 93788.0, 93789.0, 93790.0, 93791.0, 93792.0],       [93793.0, 93794.0, 93795.0, 93796.0, 93797.0, 93798.0],       [93799.0, 93800.0, 93801.0, 93802.0, 93803.0, 93804.0],       [93805.0, 93806.0, 93807.0, 93808.0, 93809.0, 93810.0],       [93811.0, 93812.0, 93813.0, 93814.0, 93815.0, 93816.0],       [93817.0, 93818.0, 93819.0, 93820.0, 93821.0, 93822.0],       [93823.0, 93824.0, 93825.0, 93826.0, 93827.0, 93828.0]],      [[93829.0, 93830.0, 93831.0, 93832.0, 93833.0, 93834.0],       [93835.0, 93836.0, 93837.0, 93838.0, 93839.0, 93840.0],       [93841.0, 93842.0, 93843.0, 93844.0, 93845.0, 93846.0],       [93847.0, 93848.0, 93849.0, 93850.0, 93851.0, 93852.0],       [93853.0, 93854.0, 93855.0, 93856.0, 93857.0, 93858.0],       [93859.0, 93860.0, 93861.0, 93862.0, 93863.0, 93864.0],       [93865.0, 93866.0, 93867.0, 93868.0, 93869.0, 93870.0]],      [[93871.0, 93872.0, 93873.0, 93874.0, 93875.0, 93876.0],       [93877.0, 93878.0, 93879.0, 93880.0, 93881.0, 93882.0],       [93883.0, 93884.0, 93885.0, 93886.0, 93887.0, 93888.0],       [93889.0, 93890.0, 93891.0, 93892.0, 93893.0, 93894.0],       [93895.0, 93896.0, 93897.0, 93898.0, 93899.0, 93900.0],       [93901.0, 93902.0, 93903.0, 93904.0, 93905.0, 93906.0],       [93907.0, 93908.0, 93909.0, 93910.0, 93911.0, 93912.0]],      [[93913.0, 93914.0, 93915.0, 93916.0, 93917.0, 93918.0],       [93919.0, 93920.0, 93921.0, 93922.0, 93923.0, 93924.0],       [93925.0, 93926.0, 93927.0, 93928.0, 93929.0, 93930.0],       [93931.0, 93932.0, 93933.0, 93934.0, 93935.0, 93936.0],       [93937.0, 93938.0, 93939.0, 93940.0, 93941.0, 93942.0],       [93943.0, 93944.0, 93945.0, 93946.0, 93947.0, 93948.0],       [93949.0, 93950.0, 93951.0, 93952.0, 93953.0, 93954.0]],      [[93955.0, 93956.0, 93957.0, 93958.0, 93959.0, 93960.0],       [93961.0, 93962.0, 93963.0, 93964.0, 93965.0, 93966.0],       [93967.0, 93968.0, 93969.0, 93970.0, 93971.0, 93972.0],       [93973.0, 93974.0, 93975.0, 93976.0, 93977.0, 93978.0],       [93979.0, 93980.0, 93981.0, 93982.0, 93983.0, 93984.0],       [93985.0, 93986.0, 93987.0, 93988.0, 93989.0, 93990.0],       [93991.0, 93992.0, 93993.0, 93994.0, 93995.0, 93996.0]]],     [[[93997.0, 93998.0, 93999.0, 94000.0, 94001.0, 94002.0],       [94003.0, 94004.0, 94005.0, 94006.0, 94007.0, 94008.0],       [94009.0, 94010.0, 94011.0, 94012.0, 94013.0, 94014.0],       [94015.0, 94016.0, 94017.0, 94018.0, 94019.0, 94020.0],       [94021.0, 94022.0, 94023.0, 94024.0, 94025.0, 94026.0],       [94027.0, 94028.0, 94029.0, 94030.0, 94031.0, 94032.0],       [94033.0, 94034.0, 94035.0, 94036.0, 94037.0, 94038.0]],      [[94039.0, 94040.0, 94041.0, 94042.0, 94043.0, 94044.0],       [94045.0, 94046.0, 94047.0, 94048.0, 94049.0, 94050.0],       [94051.0, 94052.0, 94053.0, 94054.0, 94055.0, 94056.0],       [94057.0, 94058.0, 94059.0, 94060.0, 94061.0, 94062.0],       [94063.0, 94064.0, 94065.0, 94066.0, 94067.0, 94068.0],       [94069.0, 94070.0, 94071.0, 94072.0, 94073.0, 94074.0],       [94075.0, 94076.0, 94077.0, 94078.0, 94079.0, 94080.0]],      [[94081.0, 94082.0, 94083.0, 94084.0, 94085.0, 94086.0],       [94087.0, 94088.0, 94089.0, 94090.0, 94091.0, 94092.0],       [94093.0, 94094.0, 94095.0, 94096.0, 94097.0, 94098.0],       [94099.0, 94100.0, 94101.0, 94102.0, 94103.0, 94104.0],       [94105.0, 94106.0, 94107.0, 94108.0, 94109.0, 94110.0],       [94111.0, 94112.0, 94113.0, 94114.0, 94115.0, 94116.0],       [94117.0, 94118.0, 94119.0, 94120.0, 94121.0, 94122.0]],      [[94123.0, 94124.0, 94125.0, 94126.0, 94127.0, 94128.0],       [94129.0, 94130.0, 94131.0, 94132.0, 94133.0, 94134.0],       [94135.0, 94136.0, 94137.0, 94138.0, 94139.0, 94140.0],       [94141.0, 94142.0, 94143.0, 94144.0, 94145.0, 94146.0],       [94147.0, 94148.0, 94149.0, 94150.0, 94151.0, 94152.0],       [94153.0, 94154.0, 94155.0, 94156.0, 94157.0, 94158.0],       [94159.0, 94160.0, 94161.0, 94162.0, 94163.0, 94164.0]],      [[94165.0, 94166.0, 94167.0, 94168.0, 94169.0, 94170.0],       [94171.0, 94172.0, 94173.0, 94174.0, 94175.0, 94176.0],       [94177.0, 94178.0, 94179.0, 94180.0, 94181.0, 94182.0],       [94183.0, 94184.0, 94185.0, 94186.0, 94187.0, 94188.0],       [94189.0, 94190.0, 94191.0, 94192.0, 94193.0, 94194.0],       [94195.0, 94196.0, 94197.0, 94198.0, 94199.0, 94200.0],       [94201.0, 94202.0, 94203.0, 94204.0, 94205.0, 94206.0]],      [[94207.0, 94208.0, 94209.0, 94210.0, 94211.0, 94212.0],       [94213.0, 94214.0, 94215.0, 94216.0, 94217.0, 94218.0],       [94219.0, 94220.0, 94221.0, 94222.0, 94223.0, 94224.0],       [94225.0, 94226.0, 94227.0, 94228.0, 94229.0, 94230.0],       [94231.0, 94232.0, 94233.0, 94234.0, 94235.0, 94236.0],       [94237.0, 94238.0, 94239.0, 94240.0, 94241.0, 94242.0],       [94243.0, 94244.0, 94245.0, 94246.0, 94247.0, 94248.0]]],     [[[94249.0, 94250.0, 94251.0, 94252.0, 94253.0, 94254.0],       [94255.0, 94256.0, 94257.0, 94258.0, 94259.0, 94260.0],       [94261.0, 94262.0, 94263.0, 94264.0, 94265.0, 94266.0],       [94267.0, 94268.0, 94269.0, 94270.0, 94271.0, 94272.0],       [94273.0, 94274.0, 94275.0, 94276.0, 94277.0, 94278.0],       [94279.0, 94280.0, 94281.0, 94282.0, 94283.0, 94284.0],       [94285.0, 94286.0, 94287.0, 94288.0, 94289.0, 94290.0]],      [[94291.0, 94292.0, 94293.0, 94294.0, 94295.0, 94296.0],       [94297.0, 94298.0, 94299.0, 94300.0, 94301.0, 94302.0],       [94303.0, 94304.0, 94305.0, 94306.0, 94307.0, 94308.0],       [94309.0, 94310.0, 94311.0, 94312.0, 94313.0, 94314.0],       [94315.0, 94316.0, 94317.0, 94318.0, 94319.0, 94320.0],       [94321.0, 94322.0, 94323.0, 94324.0, 94325.0, 94326.0],       [94327.0, 94328.0, 94329.0, 94330.0, 94331.0, 94332.0]],      [[94333.0, 94334.0, 94335.0, 94336.0, 94337.0, 94338.0],       [94339.0, 94340.0, 94341.0, 94342.0, 94343.0, 94344.0],       [94345.0, 94346.0, 94347.0, 94348.0, 94349.0, 94350.0],       [94351.0, 94352.0, 94353.0, 94354.0, 94355.0, 94356.0],       [94357.0, 94358.0, 94359.0, 94360.0, 94361.0, 94362.0],       [94363.0, 94364.0, 94365.0, 94366.0, 94367.0, 94368.0],       [94369.0, 94370.0, 94371.0, 94372.0, 94373.0, 94374.0]],      [[94375.0, 94376.0, 94377.0, 94378.0, 94379.0, 94380.0],       [94381.0, 94382.0, 94383.0, 94384.0, 94385.0, 94386.0],       [94387.0, 94388.0, 94389.0, 94390.0, 94391.0, 94392.0],       [94393.0, 94394.0, 94395.0, 94396.0, 94397.0, 94398.0],       [94399.0, 94400.0, 94401.0, 94402.0, 94403.0, 94404.0],       [94405.0, 94406.0, 94407.0, 94408.0, 94409.0, 94410.0],       [94411.0, 94412.0, 94413.0, 94414.0, 94415.0, 94416.0]],      [[94417.0, 94418.0, 94419.0, 94420.0, 94421.0, 94422.0],       [94423.0, 94424.0, 94425.0, 94426.0, 94427.0, 94428.0],       [94429.0, 94430.0, 94431.0, 94432.0, 94433.0, 94434.0],       [94435.0, 94436.0, 94437.0, 94438.0, 94439.0, 94440.0],       [94441.0, 94442.0, 94443.0, 94444.0, 94445.0, 94446.0],       [94447.0, 94448.0, 94449.0, 94450.0, 94451.0, 94452.0],       [94453.0, 94454.0, 94455.0, 94456.0, 94457.0, 94458.0]],      [[94459.0, 94460.0, 94461.0, 94462.0, 94463.0, 94464.0],       [94465.0, 94466.0, 94467.0, 94468.0, 94469.0, 94470.0],       [94471.0, 94472.0, 94473.0, 94474.0, 94475.0, 94476.0],       [94477.0, 94478.0, 94479.0, 94480.0, 94481.0, 94482.0],       [94483.0, 94484.0, 94485.0, 94486.0, 94487.0, 94488.0],       [94489.0, 94490.0, 94491.0, 94492.0, 94493.0, 94494.0],       [94495.0, 94496.0, 94497.0, 94498.0, 94499.0, 94500.0]]],     [[[94501.0, 94502.0, 94503.0, 94504.0, 94505.0, 94506.0],       [94507.0, 94508.0, 94509.0, 94510.0, 94511.0, 94512.0],       [94513.0, 94514.0, 94515.0, 94516.0, 94517.0, 94518.0],       [94519.0, 94520.0, 94521.0, 94522.0, 94523.0, 94524.0],       [94525.0, 94526.0, 94527.0, 94528.0, 94529.0, 94530.0],       [94531.0, 94532.0, 94533.0, 94534.0, 94535.0, 94536.0],       [94537.0, 94538.0, 94539.0, 94540.0, 94541.0, 94542.0]],      [[94543.0, 94544.0, 94545.0, 94546.0, 94547.0, 94548.0],       [94549.0, 94550.0, 94551.0, 94552.0, 94553.0, 94554.0],       [94555.0, 94556.0, 94557.0, 94558.0, 94559.0, 94560.0],       [94561.0, 94562.0, 94563.0, 94564.0, 94565.0, 94566.0],       [94567.0, 94568.0, 94569.0, 94570.0, 94571.0, 94572.0],       [94573.0, 94574.0, 94575.0, 94576.0, 94577.0, 94578.0],       [94579.0, 94580.0, 94581.0, 94582.0, 94583.0, 94584.0]],      [[94585.0, 94586.0, 94587.0, 94588.0, 94589.0, 94590.0],       [94591.0, 94592.0, 94593.0, 94594.0, 94595.0, 94596.0],       [94597.0, 94598.0, 94599.0, 94600.0, 94601.0, 94602.0],       [94603.0, 94604.0, 94605.0, 94606.0, 94607.0, 94608.0],       [94609.0, 94610.0, 94611.0, 94612.0, 94613.0, 94614.0],       [94615.0, 94616.0, 94617.0, 94618.0, 94619.0, 94620.0],       [94621.0, 94622.0, 94623.0, 94624.0, 94625.0, 94626.0]],      [[94627.0, 94628.0, 94629.0, 94630.0, 94631.0, 94632.0],       [94633.0, 94634.0, 94635.0, 94636.0, 94637.0, 94638.0],       [94639.0, 94640.0, 94641.0, 94642.0, 94643.0, 94644.0],       [94645.0, 94646.0, 94647.0, 94648.0, 94649.0, 94650.0],       [94651.0, 94652.0, 94653.0, 94654.0, 94655.0, 94656.0],       [94657.0, 94658.0, 94659.0, 94660.0, 94661.0, 94662.0],       [94663.0, 94664.0, 94665.0, 94666.0, 94667.0, 94668.0]],      [[94669.0, 94670.0, 94671.0, 94672.0, 94673.0, 94674.0],       [94675.0, 94676.0, 94677.0, 94678.0, 94679.0, 94680.0],       [94681.0, 94682.0, 94683.0, 94684.0, 94685.0, 94686.0],       [94687.0, 94688.0, 94689.0, 94690.0, 94691.0, 94692.0],       [94693.0, 94694.0, 94695.0, 94696.0, 94697.0, 94698.0],       [94699.0, 94700.0, 94701.0, 94702.0, 94703.0, 94704.0],       [94705.0, 94706.0, 94707.0, 94708.0, 94709.0, 94710.0]],      [[94711.0, 94712.0, 94713.0, 94714.0, 94715.0, 94716.0],       [94717.0, 94718.0, 94719.0, 94720.0, 94721.0, 94722.0],       [94723.0, 94724.0, 94725.0, 94726.0, 94727.0, 94728.0],       [94729.0, 94730.0, 94731.0, 94732.0, 94733.0, 94734.0],       [94735.0, 94736.0, 94737.0, 94738.0, 94739.0, 94740.0],       [94741.0, 94742.0, 94743.0, 94744.0, 94745.0, 94746.0],       [94747.0, 94748.0, 94749.0, 94750.0, 94751.0, 94752.0]]]],    [[[[94753.0, 94754.0, 94755.0, 94756.0, 94757.0, 94758.0],       [94759.0, 94760.0, 94761.0, 94762.0, 94763.0, 94764.0],       [94765.0, 94766.0, 94767.0, 94768.0, 94769.0, 94770.0],       [94771.0, 94772.0, 94773.0, 94774.0, 94775.0, 94776.0],       [94777.0, 94778.0, 94779.0, 94780.0, 94781.0, 94782.0],       [94783.0, 94784.0, 94785.0, 94786.0, 94787.0, 94788.0],       [94789.0, 94790.0, 94791.0, 94792.0, 94793.0, 94794.0]],      [[94795.0, 94796.0, 94797.0, 94798.0, 94799.0, 94800.0],       [94801.0, 94802.0, 94803.0, 94804.0, 94805.0, 94806.0],       [94807.0, 94808.0, 94809.0, 94810.0, 94811.0, 94812.0],       [94813.0, 94814.0, 94815.0, 94816.0, 94817.0, 94818.0],       [94819.0, 94820.0, 94821.0, 94822.0, 94823.0, 94824.0],       [94825.0, 94826.0, 94827.0, 94828.0, 94829.0, 94830.0],       [94831.0, 94832.0, 94833.0, 94834.0, 94835.0, 94836.0]],      [[94837.0, 94838.0, 94839.0, 94840.0, 94841.0, 94842.0],       [94843.0, 94844.0, 94845.0, 94846.0, 94847.0, 94848.0],       [94849.0, 94850.0, 94851.0, 94852.0, 94853.0, 94854.0],       [94855.0, 94856.0, 94857.0, 94858.0, 94859.0, 94860.0],       [94861.0, 94862.0, 94863.0, 94864.0, 94865.0, 94866.0],       [94867.0, 94868.0, 94869.0, 94870.0, 94871.0, 94872.0],       [94873.0, 94874.0, 94875.0, 94876.0, 94877.0, 94878.0]],      [[94879.0, 94880.0, 94881.0, 94882.0, 94883.0, 94884.0],       [94885.0, 94886.0, 94887.0, 94888.0, 94889.0, 94890.0],       [94891.0, 94892.0, 94893.0, 94894.0, 94895.0, 94896.0],       [94897.0, 94898.0, 94899.0, 94900.0, 94901.0, 94902.0],       [94903.0, 94904.0, 94905.0, 94906.0, 94907.0, 94908.0],       [94909.0, 94910.0, 94911.0, 94912.0, 94913.0, 94914.0],       [94915.0, 94916.0, 94917.0, 94918.0, 94919.0, 94920.0]],      [[94921.0, 94922.0, 94923.0, 94924.0, 94925.0, 94926.0],       [94927.0, 94928.0, 94929.0, 94930.0, 94931.0, 94932.0],       [94933.0, 94934.0, 94935.0, 94936.0, 94937.0, 94938.0],       [94939.0, 94940.0, 94941.0, 94942.0, 94943.0, 94944.0],       [94945.0, 94946.0, 94947.0, 94948.0, 94949.0, 94950.0],       [94951.0, 94952.0, 94953.0, 94954.0, 94955.0, 94956.0],       [94957.0, 94958.0, 94959.0, 94960.0, 94961.0, 94962.0]],      [[94963.0, 94964.0, 94965.0, 94966.0, 94967.0, 94968.0],       [94969.0, 94970.0, 94971.0, 94972.0, 94973.0, 94974.0],       [94975.0, 94976.0, 94977.0, 94978.0, 94979.0, 94980.0],       [94981.0, 94982.0, 94983.0, 94984.0, 94985.0, 94986.0],       [94987.0, 94988.0, 94989.0, 94990.0, 94991.0, 94992.0],       [94993.0, 94994.0, 94995.0, 94996.0, 94997.0, 94998.0],       [94999.0, 95000.0, 95001.0, 95002.0, 95003.0, 95004.0]]],     [[[95005.0, 95006.0, 95007.0, 95008.0, 95009.0, 95010.0],       [95011.0, 95012.0, 95013.0, 95014.0, 95015.0, 95016.0],       [95017.0, 95018.0, 95019.0, 95020.0, 95021.0, 95022.0],       [95023.0, 95024.0, 95025.0, 95026.0, 95027.0, 95028.0],       [95029.0, 95030.0, 95031.0, 95032.0, 95033.0, 95034.0],       [95035.0, 95036.0, 95037.0, 95038.0, 95039.0, 95040.0],       [95041.0, 95042.0, 95043.0, 95044.0, 95045.0, 95046.0]],      [[95047.0, 95048.0, 95049.0, 95050.0, 95051.0, 95052.0],       [95053.0, 95054.0, 95055.0, 95056.0, 95057.0, 95058.0],       [95059.0, 95060.0, 95061.0, 95062.0, 95063.0, 95064.0],       [95065.0, 95066.0, 95067.0, 95068.0, 95069.0, 95070.0],       [95071.0, 95072.0, 95073.0, 95074.0, 95075.0, 95076.0],       [95077.0, 95078.0, 95079.0, 95080.0, 95081.0, 95082.0],       [95083.0, 95084.0, 95085.0, 95086.0, 95087.0, 95088.0]],      [[95089.0, 95090.0, 95091.0, 95092.0, 95093.0, 95094.0],       [95095.0, 95096.0, 95097.0, 95098.0, 95099.0, 95100.0],       [95101.0, 95102.0, 95103.0, 95104.0, 95105.0, 95106.0],       [95107.0, 95108.0, 95109.0, 95110.0, 95111.0, 95112.0],       [95113.0, 95114.0, 95115.0, 95116.0, 95117.0, 95118.0],       [95119.0, 95120.0, 95121.0, 95122.0, 95123.0, 95124.0],       [95125.0, 95126.0, 95127.0, 95128.0, 95129.0, 95130.0]],      [[95131.0, 95132.0, 95133.0, 95134.0, 95135.0, 95136.0],       [95137.0, 95138.0, 95139.0, 95140.0, 95141.0, 95142.0],       [95143.0, 95144.0, 95145.0, 95146.0, 95147.0, 95148.0],       [95149.0, 95150.0, 95151.0, 95152.0, 95153.0, 95154.0],       [95155.0, 95156.0, 95157.0, 95158.0, 95159.0, 95160.0],       [95161.0, 95162.0, 95163.0, 95164.0, 95165.0, 95166.0],       [95167.0, 95168.0, 95169.0, 95170.0, 95171.0, 95172.0]],      [[95173.0, 95174.0, 95175.0, 95176.0, 95177.0, 95178.0],       [95179.0, 95180.0, 95181.0, 95182.0, 95183.0, 95184.0],       [95185.0, 95186.0, 95187.0, 95188.0, 95189.0, 95190.0],       [95191.0, 95192.0, 95193.0, 95194.0, 95195.0, 95196.0],       [95197.0, 95198.0, 95199.0, 95200.0, 95201.0, 95202.0],       [95203.0, 95204.0, 95205.0, 95206.0, 95207.0, 95208.0],       [95209.0, 95210.0, 95211.0, 95212.0, 95213.0, 95214.0]],      [[95215.0, 95216.0, 95217.0, 95218.0, 95219.0, 95220.0],       [95221.0, 95222.0, 95223.0, 95224.0, 95225.0, 95226.0],       [95227.0, 95228.0, 95229.0, 95230.0, 95231.0, 95232.0],       [95233.0, 95234.0, 95235.0, 95236.0, 95237.0, 95238.0],       [95239.0, 95240.0, 95241.0, 95242.0, 95243.0, 95244.0],       [95245.0, 95246.0, 95247.0, 95248.0, 95249.0, 95250.0],       [95251.0, 95252.0, 95253.0, 95254.0, 95255.0, 95256.0]]],     [[[95257.0, 95258.0, 95259.0, 95260.0, 95261.0, 95262.0],       [95263.0, 95264.0, 95265.0, 95266.0, 95267.0, 95268.0],       [95269.0, 95270.0, 95271.0, 95272.0, 95273.0, 95274.0],       [95275.0, 95276.0, 95277.0, 95278.0, 95279.0, 95280.0],       [95281.0, 95282.0, 95283.0, 95284.0, 95285.0, 95286.0],       [95287.0, 95288.0, 95289.0, 95290.0, 95291.0, 95292.0],       [95293.0, 95294.0, 95295.0, 95296.0, 95297.0, 95298.0]],      [[95299.0, 95300.0, 95301.0, 95302.0, 95303.0, 95304.0],       [95305.0, 95306.0, 95307.0, 95308.0, 95309.0, 95310.0],       [95311.0, 95312.0, 95313.0, 95314.0, 95315.0, 95316.0],       [95317.0, 95318.0, 95319.0, 95320.0, 95321.0, 95322.0],       [95323.0, 95324.0, 95325.0, 95326.0, 95327.0, 95328.0],       [95329.0, 95330.0, 95331.0, 95332.0, 95333.0, 95334.0],       [95335.0, 95336.0, 95337.0, 95338.0, 95339.0, 95340.0]],      [[95341.0, 95342.0, 95343.0, 95344.0, 95345.0, 95346.0],       [95347.0, 95348.0, 95349.0, 95350.0, 95351.0, 95352.0],       [95353.0, 95354.0, 95355.0, 95356.0, 95357.0, 95358.0],       [95359.0, 95360.0, 95361.0, 95362.0, 95363.0, 95364.0],       [95365.0, 95366.0, 95367.0, 95368.0, 95369.0, 95370.0],       [95371.0, 95372.0, 95373.0, 95374.0, 95375.0, 95376.0],       [95377.0, 95378.0, 95379.0, 95380.0, 95381.0, 95382.0]],      [[95383.0, 95384.0, 95385.0, 95386.0, 95387.0, 95388.0],       [95389.0, 95390.0, 95391.0, 95392.0, 95393.0, 95394.0],       [95395.0, 95396.0, 95397.0, 95398.0, 95399.0, 95400.0],       [95401.0, 95402.0, 95403.0, 95404.0, 95405.0, 95406.0],       [95407.0, 95408.0, 95409.0, 95410.0, 95411.0, 95412.0],       [95413.0, 95414.0, 95415.0, 95416.0, 95417.0, 95418.0],       [95419.0, 95420.0, 95421.0, 95422.0, 95423.0, 95424.0]],      [[95425.0, 95426.0, 95427.0, 95428.0, 95429.0, 95430.0],       [95431.0, 95432.0, 95433.0, 95434.0, 95435.0, 95436.0],       [95437.0, 95438.0, 95439.0, 95440.0, 95441.0, 95442.0],       [95443.0, 95444.0, 95445.0, 95446.0, 95447.0, 95448.0],       [95449.0, 95450.0, 95451.0, 95452.0, 95453.0, 95454.0],       [95455.0, 95456.0, 95457.0, 95458.0, 95459.0, 95460.0],       [95461.0, 95462.0, 95463.0, 95464.0, 95465.0, 95466.0]],      [[95467.0, 95468.0, 95469.0, 95470.0, 95471.0, 95472.0],       [95473.0, 95474.0, 95475.0, 95476.0, 95477.0, 95478.0],       [95479.0, 95480.0, 95481.0, 95482.0, 95483.0, 95484.0],       [95485.0, 95486.0, 95487.0, 95488.0, 95489.0, 95490.0],       [95491.0, 95492.0, 95493.0, 95494.0, 95495.0, 95496.0],       [95497.0, 95498.0, 95499.0, 95500.0, 95501.0, 95502.0],       [95503.0, 95504.0, 95505.0, 95506.0, 95507.0, 95508.0]]],     [[[95509.0, 95510.0, 95511.0, 95512.0, 95513.0, 95514.0],       [95515.0, 95516.0, 95517.0, 95518.0, 95519.0, 95520.0],       [95521.0, 95522.0, 95523.0, 95524.0, 95525.0, 95526.0],       [95527.0, 95528.0, 95529.0, 95530.0, 95531.0, 95532.0],       [95533.0, 95534.0, 95535.0, 95536.0, 95537.0, 95538.0],       [95539.0, 95540.0, 95541.0, 95542.0, 95543.0, 95544.0],       [95545.0, 95546.0, 95547.0, 95548.0, 95549.0, 95550.0]],      [[95551.0, 95552.0, 95553.0, 95554.0, 95555.0, 95556.0],       [95557.0, 95558.0, 95559.0, 95560.0, 95561.0, 95562.0],       [95563.0, 95564.0, 95565.0, 95566.0, 95567.0, 95568.0],       [95569.0, 95570.0, 95571.0, 95572.0, 95573.0, 95574.0],       [95575.0, 95576.0, 95577.0, 95578.0, 95579.0, 95580.0],       [95581.0, 95582.0, 95583.0, 95584.0, 95585.0, 95586.0],       [95587.0, 95588.0, 95589.0, 95590.0, 95591.0, 95592.0]],      [[95593.0, 95594.0, 95595.0, 95596.0, 95597.0, 95598.0],       [95599.0, 95600.0, 95601.0, 95602.0, 95603.0, 95604.0],       [95605.0, 95606.0, 95607.0, 95608.0, 95609.0, 95610.0],       [95611.0, 95612.0, 95613.0, 95614.0, 95615.0, 95616.0],       [95617.0, 95618.0, 95619.0, 95620.0, 95621.0, 95622.0],       [95623.0, 95624.0, 95625.0, 95626.0, 95627.0, 95628.0],       [95629.0, 95630.0, 95631.0, 95632.0, 95633.0, 95634.0]],      [[95635.0, 95636.0, 95637.0, 95638.0, 95639.0, 95640.0],       [95641.0, 95642.0, 95643.0, 95644.0, 95645.0, 95646.0],       [95647.0, 95648.0, 95649.0, 95650.0, 95651.0, 95652.0],       [95653.0, 95654.0, 95655.0, 95656.0, 95657.0, 95658.0],       [95659.0, 95660.0, 95661.0, 95662.0, 95663.0, 95664.0],       [95665.0, 95666.0, 95667.0, 95668.0, 95669.0, 95670.0],       [95671.0, 95672.0, 95673.0, 95674.0, 95675.0, 95676.0]],      [[95677.0, 95678.0, 95679.0, 95680.0, 95681.0, 95682.0],       [95683.0, 95684.0, 95685.0, 95686.0, 95687.0, 95688.0],       [95689.0, 95690.0, 95691.0, 95692.0, 95693.0, 95694.0],       [95695.0, 95696.0, 95697.0, 95698.0, 95699.0, 95700.0],       [95701.0, 95702.0, 95703.0, 95704.0, 95705.0, 95706.0],       [95707.0, 95708.0, 95709.0, 95710.0, 95711.0, 95712.0],       [95713.0, 95714.0, 95715.0, 95716.0, 95717.0, 95718.0]],      [[95719.0, 95720.0, 95721.0, 95722.0, 95723.0, 95724.0],       [95725.0, 95726.0, 95727.0, 95728.0, 95729.0, 95730.0],       [95731.0, 95732.0, 95733.0, 95734.0, 95735.0, 95736.0],       [95737.0, 95738.0, 95739.0, 95740.0, 95741.0, 95742.0],       [95743.0, 95744.0, 95745.0, 95746.0, 95747.0, 95748.0],       [95749.0, 95750.0, 95751.0, 95752.0, 95753.0, 95754.0],       [95755.0, 95756.0, 95757.0, 95758.0, 95759.0, 95760.0]]]],    [[[[95761.0, 95762.0, 95763.0, 95764.0, 95765.0, 95766.0],       [95767.0, 95768.0, 95769.0, 95770.0, 95771.0, 95772.0],       [95773.0, 95774.0, 95775.0, 95776.0, 95777.0, 95778.0],       [95779.0, 95780.0, 95781.0, 95782.0, 95783.0, 95784.0],       [95785.0, 95786.0, 95787.0, 95788.0, 95789.0, 95790.0],       [95791.0, 95792.0, 95793.0, 95794.0, 95795.0, 95796.0],       [95797.0, 95798.0, 95799.0, 95800.0, 95801.0, 95802.0]],      [[95803.0, 95804.0, 95805.0, 95806.0, 95807.0, 95808.0],       [95809.0, 95810.0, 95811.0, 95812.0, 95813.0, 95814.0],       [95815.0, 95816.0, 95817.0, 95818.0, 95819.0, 95820.0],       [95821.0, 95822.0, 95823.0, 95824.0, 95825.0, 95826.0],       [95827.0, 95828.0, 95829.0, 95830.0, 95831.0, 95832.0],       [95833.0, 95834.0, 95835.0, 95836.0, 95837.0, 95838.0],       [95839.0, 95840.0, 95841.0, 95842.0, 95843.0, 95844.0]],      [[95845.0, 95846.0, 95847.0, 95848.0, 95849.0, 95850.0],       [95851.0, 95852.0, 95853.0, 95854.0, 95855.0, 95856.0],       [95857.0, 95858.0, 95859.0, 95860.0, 95861.0, 95862.0],       [95863.0, 95864.0, 95865.0, 95866.0, 95867.0, 95868.0],       [95869.0, 95870.0, 95871.0, 95872.0, 95873.0, 95874.0],       [95875.0, 95876.0, 95877.0, 95878.0, 95879.0, 95880.0],       [95881.0, 95882.0, 95883.0, 95884.0, 95885.0, 95886.0]],      [[95887.0, 95888.0, 95889.0, 95890.0, 95891.0, 95892.0],       [95893.0, 95894.0, 95895.0, 95896.0, 95897.0, 95898.0],       [95899.0, 95900.0, 95901.0, 95902.0, 95903.0, 95904.0],       [95905.0, 95906.0, 95907.0, 95908.0, 95909.0, 95910.0],       [95911.0, 95912.0, 95913.0, 95914.0, 95915.0, 95916.0],       [95917.0, 95918.0, 95919.0, 95920.0, 95921.0, 95922.0],       [95923.0, 95924.0, 95925.0, 95926.0, 95927.0, 95928.0]],      [[95929.0, 95930.0, 95931.0, 95932.0, 95933.0, 95934.0],       [95935.0, 95936.0, 95937.0, 95938.0, 95939.0, 95940.0],       [95941.0, 95942.0, 95943.0, 95944.0, 95945.0, 95946.0],       [95947.0, 95948.0, 95949.0, 95950.0, 95951.0, 95952.0],       [95953.0, 95954.0, 95955.0, 95956.0, 95957.0, 95958.0],       [95959.0, 95960.0, 95961.0, 95962.0, 95963.0, 95964.0],       [95965.0, 95966.0, 95967.0, 95968.0, 95969.0, 95970.0]],      [[95971.0, 95972.0, 95973.0, 95974.0, 95975.0, 95976.0],       [95977.0, 95978.0, 95979.0, 95980.0, 95981.0, 95982.0],       [95983.0, 95984.0, 95985.0, 95986.0, 95987.0, 95988.0],       [95989.0, 95990.0, 95991.0, 95992.0, 95993.0, 95994.0],       [95995.0, 95996.0, 95997.0, 95998.0, 95999.0, 96000.0],       [96001.0, 96002.0, 96003.0, 96004.0, 96005.0, 96006.0],       [96007.0, 96008.0, 96009.0, 96010.0, 96011.0, 96012.0]]],     [[[96013.0, 96014.0, 96015.0, 96016.0, 96017.0, 96018.0],       [96019.0, 96020.0, 96021.0, 96022.0, 96023.0, 96024.0],       [96025.0, 96026.0, 96027.0, 96028.0, 96029.0, 96030.0],       [96031.0, 96032.0, 96033.0, 96034.0, 96035.0, 96036.0],       [96037.0, 96038.0, 96039.0, 96040.0, 96041.0, 96042.0],       [96043.0, 96044.0, 96045.0, 96046.0, 96047.0, 96048.0],       [96049.0, 96050.0, 96051.0, 96052.0, 96053.0, 96054.0]],      [[96055.0, 96056.0, 96057.0, 96058.0, 96059.0, 96060.0],       [96061.0, 96062.0, 96063.0, 96064.0, 96065.0, 96066.0],       [96067.0, 96068.0, 96069.0, 96070.0, 96071.0, 96072.0],       [96073.0, 96074.0, 96075.0, 96076.0, 96077.0, 96078.0],       [96079.0, 96080.0, 96081.0, 96082.0, 96083.0, 96084.0],       [96085.0, 96086.0, 96087.0, 96088.0, 96089.0, 96090.0],       [96091.0, 96092.0, 96093.0, 96094.0, 96095.0, 96096.0]],      [[96097.0, 96098.0, 96099.0, 96100.0, 96101.0, 96102.0],       [96103.0, 96104.0, 96105.0, 96106.0, 96107.0, 96108.0],       [96109.0, 96110.0, 96111.0, 96112.0, 96113.0, 96114.0],       [96115.0, 96116.0, 96117.0, 96118.0, 96119.0, 96120.0],       [96121.0, 96122.0, 96123.0, 96124.0, 96125.0, 96126.0],       [96127.0, 96128.0, 96129.0, 96130.0, 96131.0, 96132.0],       [96133.0, 96134.0, 96135.0, 96136.0, 96137.0, 96138.0]],      [[96139.0, 96140.0, 96141.0, 96142.0, 96143.0, 96144.0],       [96145.0, 96146.0, 96147.0, 96148.0, 96149.0, 96150.0],       [96151.0, 96152.0, 96153.0, 96154.0, 96155.0, 96156.0],       [96157.0, 96158.0, 96159.0, 96160.0, 96161.0, 96162.0],       [96163.0, 96164.0, 96165.0, 96166.0, 96167.0, 96168.0],       [96169.0, 96170.0, 96171.0, 96172.0, 96173.0, 96174.0],       [96175.0, 96176.0, 96177.0, 96178.0, 96179.0, 96180.0]],      [[96181.0, 96182.0, 96183.0, 96184.0, 96185.0, 96186.0],       [96187.0, 96188.0, 96189.0, 96190.0, 96191.0, 96192.0],       [96193.0, 96194.0, 96195.0, 96196.0, 96197.0, 96198.0],       [96199.0, 96200.0, 96201.0, 96202.0, 96203.0, 96204.0],       [96205.0, 96206.0, 96207.0, 96208.0, 96209.0, 96210.0],       [96211.0, 96212.0, 96213.0, 96214.0, 96215.0, 96216.0],       [96217.0, 96218.0, 96219.0, 96220.0, 96221.0, 96222.0]],      [[96223.0, 96224.0, 96225.0, 96226.0, 96227.0, 96228.0],       [96229.0, 96230.0, 96231.0, 96232.0, 96233.0, 96234.0],       [96235.0, 96236.0, 96237.0, 96238.0, 96239.0, 96240.0],       [96241.0, 96242.0, 96243.0, 96244.0, 96245.0, 96246.0],       [96247.0, 96248.0, 96249.0, 96250.0, 96251.0, 96252.0],       [96253.0, 96254.0, 96255.0, 96256.0, 96257.0, 96258.0],       [96259.0, 96260.0, 96261.0, 96262.0, 96263.0, 96264.0]]],     [[[96265.0, 96266.0, 96267.0, 96268.0, 96269.0, 96270.0],       [96271.0, 96272.0, 96273.0, 96274.0, 96275.0, 96276.0],       [96277.0, 96278.0, 96279.0, 96280.0, 96281.0, 96282.0],       [96283.0, 96284.0, 96285.0, 96286.0, 96287.0, 96288.0],       [96289.0, 96290.0, 96291.0, 96292.0, 96293.0, 96294.0],       [96295.0, 96296.0, 96297.0, 96298.0, 96299.0, 96300.0],       [96301.0, 96302.0, 96303.0, 96304.0, 96305.0, 96306.0]],      [[96307.0, 96308.0, 96309.0, 96310.0, 96311.0, 96312.0],       [96313.0, 96314.0, 96315.0, 96316.0, 96317.0, 96318.0],       [96319.0, 96320.0, 96321.0, 96322.0, 96323.0, 96324.0],       [96325.0, 96326.0, 96327.0, 96328.0, 96329.0, 96330.0],       [96331.0, 96332.0, 96333.0, 96334.0, 96335.0, 96336.0],       [96337.0, 96338.0, 96339.0, 96340.0, 96341.0, 96342.0],       [96343.0, 96344.0, 96345.0, 96346.0, 96347.0, 96348.0]],      [[96349.0, 96350.0, 96351.0, 96352.0, 96353.0, 96354.0],       [96355.0, 96356.0, 96357.0, 96358.0, 96359.0, 96360.0],       [96361.0, 96362.0, 96363.0, 96364.0, 96365.0, 96366.0],       [96367.0, 96368.0, 96369.0, 96370.0, 96371.0, 96372.0],       [96373.0, 96374.0, 96375.0, 96376.0, 96377.0, 96378.0],       [96379.0, 96380.0, 96381.0, 96382.0, 96383.0, 96384.0],       [96385.0, 96386.0, 96387.0, 96388.0, 96389.0, 96390.0]],      [[96391.0, 96392.0, 96393.0, 96394.0, 96395.0, 96396.0],       [96397.0, 96398.0, 96399.0, 96400.0, 96401.0, 96402.0],       [96403.0, 96404.0, 96405.0, 96406.0, 96407.0, 96408.0],       [96409.0, 96410.0, 96411.0, 96412.0, 96413.0, 96414.0],       [96415.0, 96416.0, 96417.0, 96418.0, 96419.0, 96420.0],       [96421.0, 96422.0, 96423.0, 96424.0, 96425.0, 96426.0],       [96427.0, 96428.0, 96429.0, 96430.0, 96431.0, 96432.0]],      [[96433.0, 96434.0, 96435.0, 96436.0, 96437.0, 96438.0],       [96439.0, 96440.0, 96441.0, 96442.0, 96443.0, 96444.0],       [96445.0, 96446.0, 96447.0, 96448.0, 96449.0, 96450.0],       [96451.0, 96452.0, 96453.0, 96454.0, 96455.0, 96456.0],       [96457.0, 96458.0, 96459.0, 96460.0, 96461.0, 96462.0],       [96463.0, 96464.0, 96465.0, 96466.0, 96467.0, 96468.0],       [96469.0, 96470.0, 96471.0, 96472.0, 96473.0, 96474.0]],      [[96475.0, 96476.0, 96477.0, 96478.0, 96479.0, 96480.0],       [96481.0, 96482.0, 96483.0, 96484.0, 96485.0, 96486.0],       [96487.0, 96488.0, 96489.0, 96490.0, 96491.0, 96492.0],       [96493.0, 96494.0, 96495.0, 96496.0, 96497.0, 96498.0],       [96499.0, 96500.0, 96501.0, 96502.0, 96503.0, 96504.0],       [96505.0, 96506.0, 96507.0, 96508.0, 96509.0, 96510.0],       [96511.0, 96512.0, 96513.0, 96514.0, 96515.0, 96516.0]]],     [[[96517.0, 96518.0, 96519.0, 96520.0, 96521.0, 96522.0],       [96523.0, 96524.0, 96525.0, 96526.0, 96527.0, 96528.0],       [96529.0, 96530.0, 96531.0, 96532.0, 96533.0, 96534.0],       [96535.0, 96536.0, 96537.0, 96538.0, 96539.0, 96540.0],       [96541.0, 96542.0, 96543.0, 96544.0, 96545.0, 96546.0],       [96547.0, 96548.0, 96549.0, 96550.0, 96551.0, 96552.0],       [96553.0, 96554.0, 96555.0, 96556.0, 96557.0, 96558.0]],      [[96559.0, 96560.0, 96561.0, 96562.0, 96563.0, 96564.0],       [96565.0, 96566.0, 96567.0, 96568.0, 96569.0, 96570.0],       [96571.0, 96572.0, 96573.0, 96574.0, 96575.0, 96576.0],       [96577.0, 96578.0, 96579.0, 96580.0, 96581.0, 96582.0],       [96583.0, 96584.0, 96585.0, 96586.0, 96587.0, 96588.0],       [96589.0, 96590.0, 96591.0, 96592.0, 96593.0, 96594.0],       [96595.0, 96596.0, 96597.0, 96598.0, 96599.0, 96600.0]],      [[96601.0, 96602.0, 96603.0, 96604.0, 96605.0, 96606.0],       [96607.0, 96608.0, 96609.0, 96610.0, 96611.0, 96612.0],       [96613.0, 96614.0, 96615.0, 96616.0, 96617.0, 96618.0],       [96619.0, 96620.0, 96621.0, 96622.0, 96623.0, 96624.0],       [96625.0, 96626.0, 96627.0, 96628.0, 96629.0, 96630.0],       [96631.0, 96632.0, 96633.0, 96634.0, 96635.0, 96636.0],       [96637.0, 96638.0, 96639.0, 96640.0, 96641.0, 96642.0]],      [[96643.0, 96644.0, 96645.0, 96646.0, 96647.0, 96648.0],       [96649.0, 96650.0, 96651.0, 96652.0, 96653.0, 96654.0],       [96655.0, 96656.0, 96657.0, 96658.0, 96659.0, 96660.0],       [96661.0, 96662.0, 96663.0, 96664.0, 96665.0, 96666.0],       [96667.0, 96668.0, 96669.0, 96670.0, 96671.0, 96672.0],       [96673.0, 96674.0, 96675.0, 96676.0, 96677.0, 96678.0],       [96679.0, 96680.0, 96681.0, 96682.0, 96683.0, 96684.0]],      [[96685.0, 96686.0, 96687.0, 96688.0, 96689.0, 96690.0],       [96691.0, 96692.0, 96693.0, 96694.0, 96695.0, 96696.0],       [96697.0, 96698.0, 96699.0, 96700.0, 96701.0, 96702.0],       [96703.0, 96704.0, 96705.0, 96706.0, 96707.0, 96708.0],       [96709.0, 96710.0, 96711.0, 96712.0, 96713.0, 96714.0],       [96715.0, 96716.0, 96717.0, 96718.0, 96719.0, 96720.0],       [96721.0, 96722.0, 96723.0, 96724.0, 96725.0, 96726.0]],      [[96727.0, 96728.0, 96729.0, 96730.0, 96731.0, 96732.0],       [96733.0, 96734.0, 96735.0, 96736.0, 96737.0, 96738.0],       [96739.0, 96740.0, 96741.0, 96742.0, 96743.0, 96744.0],       [96745.0, 96746.0, 96747.0, 96748.0, 96749.0, 96750.0],       [96751.0, 96752.0, 96753.0, 96754.0, 96755.0, 96756.0],       [96757.0, 96758.0, 96759.0, 96760.0, 96761.0, 96762.0],       [96763.0, 96764.0, 96765.0, 96766.0, 96767.0, 96768.0]]]]],   [[[[[96769.0, 96770.0, 96771.0, 96772.0, 96773.0, 96774.0],       [96775.0, 96776.0, 96777.0, 96778.0, 96779.0, 96780.0],       [96781.0, 96782.0, 96783.0, 96784.0, 96785.0, 96786.0],       [96787.0, 96788.0, 96789.0, 96790.0, 96791.0, 96792.0],       [96793.0, 96794.0, 96795.0, 96796.0, 96797.0, 96798.0],       [96799.0, 96800.0, 96801.0, 96802.0, 96803.0, 96804.0],       [96805.0, 96806.0, 96807.0, 96808.0, 96809.0, 96810.0]],      [[96811.0, 96812.0, 96813.0, 96814.0, 96815.0, 96816.0],       [96817.0, 96818.0, 96819.0, 96820.0, 96821.0, 96822.0],       [96823.0, 96824.0, 96825.0, 96826.0, 96827.0, 96828.0],       [96829.0, 96830.0, 96831.0, 96832.0, 96833.0, 96834.0],       [96835.0, 96836.0, 96837.0, 96838.0, 96839.0, 96840.0],       [96841.0, 96842.0, 96843.0, 96844.0, 96845.0, 96846.0],       [96847.0, 96848.0, 96849.0, 96850.0, 96851.0, 96852.0]],      [[96853.0, 96854.0, 96855.0, 96856.0, 96857.0, 96858.0],       [96859.0, 96860.0, 96861.0, 96862.0, 96863.0, 96864.0],       [96865.0, 96866.0, 96867.0, 96868.0, 96869.0, 96870.0],       [96871.0, 96872.0, 96873.0, 96874.0, 96875.0, 96876.0],       [96877.0, 96878.0, 96879.0, 96880.0, 96881.0, 96882.0],       [96883.0, 96884.0, 96885.0, 96886.0, 96887.0, 96888.0],       [96889.0, 96890.0, 96891.0, 96892.0, 96893.0, 96894.0]],      [[96895.0, 96896.0, 96897.0, 96898.0, 96899.0, 96900.0],       [96901.0, 96902.0, 96903.0, 96904.0, 96905.0, 96906.0],       [96907.0, 96908.0, 96909.0, 96910.0, 96911.0, 96912.0],       [96913.0, 96914.0, 96915.0, 96916.0, 96917.0, 96918.0],       [96919.0, 96920.0, 96921.0, 96922.0, 96923.0, 96924.0],       [96925.0, 96926.0, 96927.0, 96928.0, 96929.0, 96930.0],       [96931.0, 96932.0, 96933.0, 96934.0, 96935.0, 96936.0]],      [[96937.0, 96938.0, 96939.0, 96940.0, 96941.0, 96942.0],       [96943.0, 96944.0, 96945.0, 96946.0, 96947.0, 96948.0],       [96949.0, 96950.0, 96951.0, 96952.0, 96953.0, 96954.0],       [96955.0, 96956.0, 96957.0, 96958.0, 96959.0, 96960.0],       [96961.0, 96962.0, 96963.0, 96964.0, 96965.0, 96966.0],       [96967.0, 96968.0, 96969.0, 96970.0, 96971.0, 96972.0],       [96973.0, 96974.0, 96975.0, 96976.0, 96977.0, 96978.0]],      [[96979.0, 96980.0, 96981.0, 96982.0, 96983.0, 96984.0],       [96985.0, 96986.0, 96987.0, 96988.0, 96989.0, 96990.0],       [96991.0, 96992.0, 96993.0, 96994.0, 96995.0, 96996.0],       [96997.0, 96998.0, 96999.0, 97000.0, 97001.0, 97002.0],       [97003.0, 97004.0, 97005.0, 97006.0, 97007.0, 97008.0],       [97009.0, 97010.0, 97011.0, 97012.0, 97013.0, 97014.0],       [97015.0, 97016.0, 97017.0, 97018.0, 97019.0, 97020.0]]],     [[[97021.0, 97022.0, 97023.0, 97024.0, 97025.0, 97026.0],       [97027.0, 97028.0, 97029.0, 97030.0, 97031.0, 97032.0],       [97033.0, 97034.0, 97035.0, 97036.0, 97037.0, 97038.0],       [97039.0, 97040.0, 97041.0, 97042.0, 97043.0, 97044.0],       [97045.0, 97046.0, 97047.0, 97048.0, 97049.0, 97050.0],       [97051.0, 97052.0, 97053.0, 97054.0, 97055.0, 97056.0],       [97057.0, 97058.0, 97059.0, 97060.0, 97061.0, 97062.0]],      [[97063.0, 97064.0, 97065.0, 97066.0, 97067.0, 97068.0],       [97069.0, 97070.0, 97071.0, 97072.0, 97073.0, 97074.0],       [97075.0, 97076.0, 97077.0, 97078.0, 97079.0, 97080.0],       [97081.0, 97082.0, 97083.0, 97084.0, 97085.0, 97086.0],       [97087.0, 97088.0, 97089.0, 97090.0, 97091.0, 97092.0],       [97093.0, 97094.0, 97095.0, 97096.0, 97097.0, 97098.0],       [97099.0, 97100.0, 97101.0, 97102.0, 97103.0, 97104.0]],      [[97105.0, 97106.0, 97107.0, 97108.0, 97109.0, 97110.0],       [97111.0, 97112.0, 97113.0, 97114.0, 97115.0, 97116.0],       [97117.0, 97118.0, 97119.0, 97120.0, 97121.0, 97122.0],       [97123.0, 97124.0, 97125.0, 97126.0, 97127.0, 97128.0],       [97129.0, 97130.0, 97131.0, 97132.0, 97133.0, 97134.0],       [97135.0, 97136.0, 97137.0, 97138.0, 97139.0, 97140.0],       [97141.0, 97142.0, 97143.0, 97144.0, 97145.0, 97146.0]],      [[97147.0, 97148.0, 97149.0, 97150.0, 97151.0, 97152.0],       [97153.0, 97154.0, 97155.0, 97156.0, 97157.0, 97158.0],       [97159.0, 97160.0, 97161.0, 97162.0, 97163.0, 97164.0],       [97165.0, 97166.0, 97167.0, 97168.0, 97169.0, 97170.0],       [97171.0, 97172.0, 97173.0, 97174.0, 97175.0, 97176.0],       [97177.0, 97178.0, 97179.0, 97180.0, 97181.0, 97182.0],       [97183.0, 97184.0, 97185.0, 97186.0, 97187.0, 97188.0]],      [[97189.0, 97190.0, 97191.0, 97192.0, 97193.0, 97194.0],       [97195.0, 97196.0, 97197.0, 97198.0, 97199.0, 97200.0],       [97201.0, 97202.0, 97203.0, 97204.0, 97205.0, 97206.0],       [97207.0, 97208.0, 97209.0, 97210.0, 97211.0, 97212.0],       [97213.0, 97214.0, 97215.0, 97216.0, 97217.0, 97218.0],       [97219.0, 97220.0, 97221.0, 97222.0, 97223.0, 97224.0],       [97225.0, 97226.0, 97227.0, 97228.0, 97229.0, 97230.0]],      [[97231.0, 97232.0, 97233.0, 97234.0, 97235.0, 97236.0],       [97237.0, 97238.0, 97239.0, 97240.0, 97241.0, 97242.0],       [97243.0, 97244.0, 97245.0, 97246.0, 97247.0, 97248.0],       [97249.0, 97250.0, 97251.0, 97252.0, 97253.0, 97254.0],       [97255.0, 97256.0, 97257.0, 97258.0, 97259.0, 97260.0],       [97261.0, 97262.0, 97263.0, 97264.0, 97265.0, 97266.0],       [97267.0, 97268.0, 97269.0, 97270.0, 97271.0, 97272.0]]],     [[[97273.0, 97274.0, 97275.0, 97276.0, 97277.0, 97278.0],       [97279.0, 97280.0, 97281.0, 97282.0, 97283.0, 97284.0],       [97285.0, 97286.0, 97287.0, 97288.0, 97289.0, 97290.0],       [97291.0, 97292.0, 97293.0, 97294.0, 97295.0, 97296.0],       [97297.0, 97298.0, 97299.0, 97300.0, 97301.0, 97302.0],       [97303.0, 97304.0, 97305.0, 97306.0, 97307.0, 97308.0],       [97309.0, 97310.0, 97311.0, 97312.0, 97313.0, 97314.0]],      [[97315.0, 97316.0, 97317.0, 97318.0, 97319.0, 97320.0],       [97321.0, 97322.0, 97323.0, 97324.0, 97325.0, 97326.0],       [97327.0, 97328.0, 97329.0, 97330.0, 97331.0, 97332.0],       [97333.0, 97334.0, 97335.0, 97336.0, 97337.0, 97338.0],       [97339.0, 97340.0, 97341.0, 97342.0, 97343.0, 97344.0],       [97345.0, 97346.0, 97347.0, 97348.0, 97349.0, 97350.0],       [97351.0, 97352.0, 97353.0, 97354.0, 97355.0, 97356.0]],      [[97357.0, 97358.0, 97359.0, 97360.0, 97361.0, 97362.0],       [97363.0, 97364.0, 97365.0, 97366.0, 97367.0, 97368.0],       [97369.0, 97370.0, 97371.0, 97372.0, 97373.0, 97374.0],       [97375.0, 97376.0, 97377.0, 97378.0, 97379.0, 97380.0],       [97381.0, 97382.0, 97383.0, 97384.0, 97385.0, 97386.0],       [97387.0, 97388.0, 97389.0, 97390.0, 97391.0, 97392.0],       [97393.0, 97394.0, 97395.0, 97396.0, 97397.0, 97398.0]],      [[97399.0, 97400.0, 97401.0, 97402.0, 97403.0, 97404.0],       [97405.0, 97406.0, 97407.0, 97408.0, 97409.0, 97410.0],       [97411.0, 97412.0, 97413.0, 97414.0, 97415.0, 97416.0],       [97417.0, 97418.0, 97419.0, 97420.0, 97421.0, 97422.0],       [97423.0, 97424.0, 97425.0, 97426.0, 97427.0, 97428.0],       [97429.0, 97430.0, 97431.0, 97432.0, 97433.0, 97434.0],       [97435.0, 97436.0, 97437.0, 97438.0, 97439.0, 97440.0]],      [[97441.0, 97442.0, 97443.0, 97444.0, 97445.0, 97446.0],       [97447.0, 97448.0, 97449.0, 97450.0, 97451.0, 97452.0],       [97453.0, 97454.0, 97455.0, 97456.0, 97457.0, 97458.0],       [97459.0, 97460.0, 97461.0, 97462.0, 97463.0, 97464.0],       [97465.0, 97466.0, 97467.0, 97468.0, 97469.0, 97470.0],       [97471.0, 97472.0, 97473.0, 97474.0, 97475.0, 97476.0],       [97477.0, 97478.0, 97479.0, 97480.0, 97481.0, 97482.0]],      [[97483.0, 97484.0, 97485.0, 97486.0, 97487.0, 97488.0],       [97489.0, 97490.0, 97491.0, 97492.0, 97493.0, 97494.0],       [97495.0, 97496.0, 97497.0, 97498.0, 97499.0, 97500.0],       [97501.0, 97502.0, 97503.0, 97504.0, 97505.0, 97506.0],       [97507.0, 97508.0, 97509.0, 97510.0, 97511.0, 97512.0],       [97513.0, 97514.0, 97515.0, 97516.0, 97517.0, 97518.0],       [97519.0, 97520.0, 97521.0, 97522.0, 97523.0, 97524.0]]],     [[[97525.0, 97526.0, 97527.0, 97528.0, 97529.0, 97530.0],       [97531.0, 97532.0, 97533.0, 97534.0, 97535.0, 97536.0],       [97537.0, 97538.0, 97539.0, 97540.0, 97541.0, 97542.0],       [97543.0, 97544.0, 97545.0, 97546.0, 97547.0, 97548.0],       [97549.0, 97550.0, 97551.0, 97552.0, 97553.0, 97554.0],       [97555.0, 97556.0, 97557.0, 97558.0, 97559.0, 97560.0],       [97561.0, 97562.0, 97563.0, 97564.0, 97565.0, 97566.0]],      [[97567.0, 97568.0, 97569.0, 97570.0, 97571.0, 97572.0],       [97573.0, 97574.0, 97575.0, 97576.0, 97577.0, 97578.0],       [97579.0, 97580.0, 97581.0, 97582.0, 97583.0, 97584.0],       [97585.0, 97586.0, 97587.0, 97588.0, 97589.0, 97590.0],       [97591.0, 97592.0, 97593.0, 97594.0, 97595.0, 97596.0],       [97597.0, 97598.0, 97599.0, 97600.0, 97601.0, 97602.0],       [97603.0, 97604.0, 97605.0, 97606.0, 97607.0, 97608.0]],      [[97609.0, 97610.0, 97611.0, 97612.0, 97613.0, 97614.0],       [97615.0, 97616.0, 97617.0, 97618.0, 97619.0, 97620.0],       [97621.0, 97622.0, 97623.0, 97624.0, 97625.0, 97626.0],       [97627.0, 97628.0, 97629.0, 97630.0, 97631.0, 97632.0],       [97633.0, 97634.0, 97635.0, 97636.0, 97637.0, 97638.0],       [97639.0, 97640.0, 97641.0, 97642.0, 97643.0, 97644.0],       [97645.0, 97646.0, 97647.0, 97648.0, 97649.0, 97650.0]],      [[97651.0, 97652.0, 97653.0, 97654.0, 97655.0, 97656.0],       [97657.0, 97658.0, 97659.0, 97660.0, 97661.0, 97662.0],       [97663.0, 97664.0, 97665.0, 97666.0, 97667.0, 97668.0],       [97669.0, 97670.0, 97671.0, 97672.0, 97673.0, 97674.0],       [97675.0, 97676.0, 97677.0, 97678.0, 97679.0, 97680.0],       [97681.0, 97682.0, 97683.0, 97684.0, 97685.0, 97686.0],       [97687.0, 97688.0, 97689.0, 97690.0, 97691.0, 97692.0]],      [[97693.0, 97694.0, 97695.0, 97696.0, 97697.0, 97698.0],       [97699.0, 97700.0, 97701.0, 97702.0, 97703.0, 97704.0],       [97705.0, 97706.0, 97707.0, 97708.0, 97709.0, 97710.0],       [97711.0, 97712.0, 97713.0, 97714.0, 97715.0, 97716.0],       [97717.0, 97718.0, 97719.0, 97720.0, 97721.0, 97722.0],       [97723.0, 97724.0, 97725.0, 97726.0, 97727.0, 97728.0],       [97729.0, 97730.0, 97731.0, 97732.0, 97733.0, 97734.0]],      [[97735.0, 97736.0, 97737.0, 97738.0, 97739.0, 97740.0],       [97741.0, 97742.0, 97743.0, 97744.0, 97745.0, 97746.0],       [97747.0, 97748.0, 97749.0, 97750.0, 97751.0, 97752.0],       [97753.0, 97754.0, 97755.0, 97756.0, 97757.0, 97758.0],       [97759.0, 97760.0, 97761.0, 97762.0, 97763.0, 97764.0],       [97765.0, 97766.0, 97767.0, 97768.0, 97769.0, 97770.0],       [97771.0, 97772.0, 97773.0, 97774.0, 97775.0, 97776.0]]]],    [[[[97777.0, 97778.0, 97779.0, 97780.0, 97781.0, 97782.0],       [97783.0, 97784.0, 97785.0, 97786.0, 97787.0, 97788.0],       [97789.0, 97790.0, 97791.0, 97792.0, 97793.0, 97794.0],       [97795.0, 97796.0, 97797.0, 97798.0, 97799.0, 97800.0],       [97801.0, 97802.0, 97803.0, 97804.0, 97805.0, 97806.0],       [97807.0, 97808.0, 97809.0, 97810.0, 97811.0, 97812.0],       [97813.0, 97814.0, 97815.0, 97816.0, 97817.0, 97818.0]],      [[97819.0, 97820.0, 97821.0, 97822.0, 97823.0, 97824.0],       [97825.0, 97826.0, 97827.0, 97828.0, 97829.0, 97830.0],       [97831.0, 97832.0, 97833.0, 97834.0, 97835.0, 97836.0],       [97837.0, 97838.0, 97839.0, 97840.0, 97841.0, 97842.0],       [97843.0, 97844.0, 97845.0, 97846.0, 97847.0, 97848.0],       [97849.0, 97850.0, 97851.0, 97852.0, 97853.0, 97854.0],       [97855.0, 97856.0, 97857.0, 97858.0, 97859.0, 97860.0]],      [[97861.0, 97862.0, 97863.0, 97864.0, 97865.0, 97866.0],       [97867.0, 97868.0, 97869.0, 97870.0, 97871.0, 97872.0],       [97873.0, 97874.0, 97875.0, 97876.0, 97877.0, 97878.0],       [97879.0, 97880.0, 97881.0, 97882.0, 97883.0, 97884.0],       [97885.0, 97886.0, 97887.0, 97888.0, 97889.0, 97890.0],       [97891.0, 97892.0, 97893.0, 97894.0, 97895.0, 97896.0],       [97897.0, 97898.0, 97899.0, 97900.0, 97901.0, 97902.0]],      [[97903.0, 97904.0, 97905.0, 97906.0, 97907.0, 97908.0],       [97909.0, 97910.0, 97911.0, 97912.0, 97913.0, 97914.0],       [97915.0, 97916.0, 97917.0, 97918.0, 97919.0, 97920.0],       [97921.0, 97922.0, 97923.0, 97924.0, 97925.0, 97926.0],       [97927.0, 97928.0, 97929.0, 97930.0, 97931.0, 97932.0],       [97933.0, 97934.0, 97935.0, 97936.0, 97937.0, 97938.0],       [97939.0, 97940.0, 97941.0, 97942.0, 97943.0, 97944.0]],      [[97945.0, 97946.0, 97947.0, 97948.0, 97949.0, 97950.0],       [97951.0, 97952.0, 97953.0, 97954.0, 97955.0, 97956.0],       [97957.0, 97958.0, 97959.0, 97960.0, 97961.0, 97962.0],       [97963.0, 97964.0, 97965.0, 97966.0, 97967.0, 97968.0],       [97969.0, 97970.0, 97971.0, 97972.0, 97973.0, 97974.0],       [97975.0, 97976.0, 97977.0, 97978.0, 97979.0, 97980.0],       [97981.0, 97982.0, 97983.0, 97984.0, 97985.0, 97986.0]],      [[97987.0, 97988.0, 97989.0, 97990.0, 97991.0, 97992.0],       [97993.0, 97994.0, 97995.0, 97996.0, 97997.0, 97998.0],       [97999.0, 98000.0, 98001.0, 98002.0, 98003.0, 98004.0],       [98005.0, 98006.0, 98007.0, 98008.0, 98009.0, 98010.0],       [98011.0, 98012.0, 98013.0, 98014.0, 98015.0, 98016.0],       [98017.0, 98018.0, 98019.0, 98020.0, 98021.0, 98022.0],       [98023.0, 98024.0, 98025.0, 98026.0, 98027.0, 98028.0]]],     [[[98029.0, 98030.0, 98031.0, 98032.0, 98033.0, 98034.0],       [98035.0, 98036.0, 98037.0, 98038.0, 98039.0, 98040.0],       [98041.0, 98042.0, 98043.0, 98044.0, 98045.0, 98046.0],       [98047.0, 98048.0, 98049.0, 98050.0, 98051.0, 98052.0],       [98053.0, 98054.0, 98055.0, 98056.0, 98057.0, 98058.0],       [98059.0, 98060.0, 98061.0, 98062.0, 98063.0, 98064.0],       [98065.0, 98066.0, 98067.0, 98068.0, 98069.0, 98070.0]],      [[98071.0, 98072.0, 98073.0, 98074.0, 98075.0, 98076.0],       [98077.0, 98078.0, 98079.0, 98080.0, 98081.0, 98082.0],       [98083.0, 98084.0, 98085.0, 98086.0, 98087.0, 98088.0],       [98089.0, 98090.0, 98091.0, 98092.0, 98093.0, 98094.0],       [98095.0, 98096.0, 98097.0, 98098.0, 98099.0, 98100.0],       [98101.0, 98102.0, 98103.0, 98104.0, 98105.0, 98106.0],       [98107.0, 98108.0, 98109.0, 98110.0, 98111.0, 98112.0]],      [[98113.0, 98114.0, 98115.0, 98116.0, 98117.0, 98118.0],       [98119.0, 98120.0, 98121.0, 98122.0, 98123.0, 98124.0],       [98125.0, 98126.0, 98127.0, 98128.0, 98129.0, 98130.0],       [98131.0, 98132.0, 98133.0, 98134.0, 98135.0, 98136.0],       [98137.0, 98138.0, 98139.0, 98140.0, 98141.0, 98142.0],       [98143.0, 98144.0, 98145.0, 98146.0, 98147.0, 98148.0],       [98149.0, 98150.0, 98151.0, 98152.0, 98153.0, 98154.0]],      [[98155.0, 98156.0, 98157.0, 98158.0, 98159.0, 98160.0],       [98161.0, 98162.0, 98163.0, 98164.0, 98165.0, 98166.0],       [98167.0, 98168.0, 98169.0, 98170.0, 98171.0, 98172.0],       [98173.0, 98174.0, 98175.0, 98176.0, 98177.0, 98178.0],       [98179.0, 98180.0, 98181.0, 98182.0, 98183.0, 98184.0],       [98185.0, 98186.0, 98187.0, 98188.0, 98189.0, 98190.0],       [98191.0, 98192.0, 98193.0, 98194.0, 98195.0, 98196.0]],      [[98197.0, 98198.0, 98199.0, 98200.0, 98201.0, 98202.0],       [98203.0, 98204.0, 98205.0, 98206.0, 98207.0, 98208.0],       [98209.0, 98210.0, 98211.0, 98212.0, 98213.0, 98214.0],       [98215.0, 98216.0, 98217.0, 98218.0, 98219.0, 98220.0],       [98221.0, 98222.0, 98223.0, 98224.0, 98225.0, 98226.0],       [98227.0, 98228.0, 98229.0, 98230.0, 98231.0, 98232.0],       [98233.0, 98234.0, 98235.0, 98236.0, 98237.0, 98238.0]],      [[98239.0, 98240.0, 98241.0, 98242.0, 98243.0, 98244.0],       [98245.0, 98246.0, 98247.0, 98248.0, 98249.0, 98250.0],       [98251.0, 98252.0, 98253.0, 98254.0, 98255.0, 98256.0],       [98257.0, 98258.0, 98259.0, 98260.0, 98261.0, 98262.0],       [98263.0, 98264.0, 98265.0, 98266.0, 98267.0, 98268.0],       [98269.0, 98270.0, 98271.0, 98272.0, 98273.0, 98274.0],       [98275.0, 98276.0, 98277.0, 98278.0, 98279.0, 98280.0]]],     [[[98281.0, 98282.0, 98283.0, 98284.0, 98285.0, 98286.0],       [98287.0, 98288.0, 98289.0, 98290.0, 98291.0, 98292.0],       [98293.0, 98294.0, 98295.0, 98296.0, 98297.0, 98298.0],       [98299.0, 98300.0, 98301.0, 98302.0, 98303.0, 98304.0],       [98305.0, 98306.0, 98307.0, 98308.0, 98309.0, 98310.0],       [98311.0, 98312.0, 98313.0, 98314.0, 98315.0, 98316.0],       [98317.0, 98318.0, 98319.0, 98320.0, 98321.0, 98322.0]],      [[98323.0, 98324.0, 98325.0, 98326.0, 98327.0, 98328.0],       [98329.0, 98330.0, 98331.0, 98332.0, 98333.0, 98334.0],       [98335.0, 98336.0, 98337.0, 98338.0, 98339.0, 98340.0],       [98341.0, 98342.0, 98343.0, 98344.0, 98345.0, 98346.0],       [98347.0, 98348.0, 98349.0, 98350.0, 98351.0, 98352.0],       [98353.0, 98354.0, 98355.0, 98356.0, 98357.0, 98358.0],       [98359.0, 98360.0, 98361.0, 98362.0, 98363.0, 98364.0]],      [[98365.0, 98366.0, 98367.0, 98368.0, 98369.0, 98370.0],       [98371.0, 98372.0, 98373.0, 98374.0, 98375.0, 98376.0],       [98377.0, 98378.0, 98379.0, 98380.0, 98381.0, 98382.0],       [98383.0, 98384.0, 98385.0, 98386.0, 98387.0, 98388.0],       [98389.0, 98390.0, 98391.0, 98392.0, 98393.0, 98394.0],       [98395.0, 98396.0, 98397.0, 98398.0, 98399.0, 98400.0],       [98401.0, 98402.0, 98403.0, 98404.0, 98405.0, 98406.0]],      [[98407.0, 98408.0, 98409.0, 98410.0, 98411.0, 98412.0],       [98413.0, 98414.0, 98415.0, 98416.0, 98417.0, 98418.0],       [98419.0, 98420.0, 98421.0, 98422.0, 98423.0, 98424.0],       [98425.0, 98426.0, 98427.0, 98428.0, 98429.0, 98430.0],       [98431.0, 98432.0, 98433.0, 98434.0, 98435.0, 98436.0],       [98437.0, 98438.0, 98439.0, 98440.0, 98441.0, 98442.0],       [98443.0, 98444.0, 98445.0, 98446.0, 98447.0, 98448.0]],      [[98449.0, 98450.0, 98451.0, 98452.0, 98453.0, 98454.0],       [98455.0, 98456.0, 98457.0, 98458.0, 98459.0, 98460.0],       [98461.0, 98462.0, 98463.0, 98464.0, 98465.0, 98466.0],       [98467.0, 98468.0, 98469.0, 98470.0, 98471.0, 98472.0],       [98473.0, 98474.0, 98475.0, 98476.0, 98477.0, 98478.0],       [98479.0, 98480.0, 98481.0, 98482.0, 98483.0, 98484.0],       [98485.0, 98486.0, 98487.0, 98488.0, 98489.0, 98490.0]],      [[98491.0, 98492.0, 98493.0, 98494.0, 98495.0, 98496.0],       [98497.0, 98498.0, 98499.0, 98500.0, 98501.0, 98502.0],       [98503.0, 98504.0, 98505.0, 98506.0, 98507.0, 98508.0],       [98509.0, 98510.0, 98511.0, 98512.0, 98513.0, 98514.0],       [98515.0, 98516.0, 98517.0, 98518.0, 98519.0, 98520.0],       [98521.0, 98522.0, 98523.0, 98524.0, 98525.0, 98526.0],       [98527.0, 98528.0, 98529.0, 98530.0, 98531.0, 98532.0]]],     [[[98533.0, 98534.0, 98535.0, 98536.0, 98537.0, 98538.0],       [98539.0, 98540.0, 98541.0, 98542.0, 98543.0, 98544.0],       [98545.0, 98546.0, 98547.0, 98548.0, 98549.0, 98550.0],       [98551.0, 98552.0, 98553.0, 98554.0, 98555.0, 98556.0],       [98557.0, 98558.0, 98559.0, 98560.0, 98561.0, 98562.0],       [98563.0, 98564.0, 98565.0, 98566.0, 98567.0, 98568.0],       [98569.0, 98570.0, 98571.0, 98572.0, 98573.0, 98574.0]],      [[98575.0, 98576.0, 98577.0, 98578.0, 98579.0, 98580.0],       [98581.0, 98582.0, 98583.0, 98584.0, 98585.0, 98586.0],       [98587.0, 98588.0, 98589.0, 98590.0, 98591.0, 98592.0],       [98593.0, 98594.0, 98595.0, 98596.0, 98597.0, 98598.0],       [98599.0, 98600.0, 98601.0, 98602.0, 98603.0, 98604.0],       [98605.0, 98606.0, 98607.0, 98608.0, 98609.0, 98610.0],       [98611.0, 98612.0, 98613.0, 98614.0, 98615.0, 98616.0]],      [[98617.0, 98618.0, 98619.0, 98620.0, 98621.0, 98622.0],       [98623.0, 98624.0, 98625.0, 98626.0, 98627.0, 98628.0],       [98629.0, 98630.0, 98631.0, 98632.0, 98633.0, 98634.0],       [98635.0, 98636.0, 98637.0, 98638.0, 98639.0, 98640.0],       [98641.0, 98642.0, 98643.0, 98644.0, 98645.0, 98646.0],       [98647.0, 98648.0, 98649.0, 98650.0, 98651.0, 98652.0],       [98653.0, 98654.0, 98655.0, 98656.0, 98657.0, 98658.0]],      [[98659.0, 98660.0, 98661.0, 98662.0, 98663.0, 98664.0],       [98665.0, 98666.0, 98667.0, 98668.0, 98669.0, 98670.0],       [98671.0, 98672.0, 98673.0, 98674.0, 98675.0, 98676.0],       [98677.0, 98678.0, 98679.0, 98680.0, 98681.0, 98682.0],       [98683.0, 98684.0, 98685.0, 98686.0, 98687.0, 98688.0],       [98689.0, 98690.0, 98691.0, 98692.0, 98693.0, 98694.0],       [98695.0, 98696.0, 98697.0, 98698.0, 98699.0, 98700.0]],      [[98701.0, 98702.0, 98703.0, 98704.0, 98705.0, 98706.0],       [98707.0, 98708.0, 98709.0, 98710.0, 98711.0, 98712.0],       [98713.0, 98714.0, 98715.0, 98716.0, 98717.0, 98718.0],       [98719.0, 98720.0, 98721.0, 98722.0, 98723.0, 98724.0],       [98725.0, 98726.0, 98727.0, 98728.0, 98729.0, 98730.0],       [98731.0, 98732.0, 98733.0, 98734.0, 98735.0, 98736.0],       [98737.0, 98738.0, 98739.0, 98740.0, 98741.0, 98742.0]],      [[98743.0, 98744.0, 98745.0, 98746.0, 98747.0, 98748.0],       [98749.0, 98750.0, 98751.0, 98752.0, 98753.0, 98754.0],       [98755.0, 98756.0, 98757.0, 98758.0, 98759.0, 98760.0],       [98761.0, 98762.0, 98763.0, 98764.0, 98765.0, 98766.0],       [98767.0, 98768.0, 98769.0, 98770.0, 98771.0, 98772.0],       [98773.0, 98774.0, 98775.0, 98776.0, 98777.0, 98778.0],       [98779.0, 98780.0, 98781.0, 98782.0, 98783.0, 98784.0]]]],    [[[[98785.0, 98786.0, 98787.0, 98788.0, 98789.0, 98790.0],       [98791.0, 98792.0, 98793.0, 98794.0, 98795.0, 98796.0],       [98797.0, 98798.0, 98799.0, 98800.0, 98801.0, 98802.0],       [98803.0, 98804.0, 98805.0, 98806.0, 98807.0, 98808.0],       [98809.0, 98810.0, 98811.0, 98812.0, 98813.0, 98814.0],       [98815.0, 98816.0, 98817.0, 98818.0, 98819.0, 98820.0],       [98821.0, 98822.0, 98823.0, 98824.0, 98825.0, 98826.0]],      [[98827.0, 98828.0, 98829.0, 98830.0, 98831.0, 98832.0],       [98833.0, 98834.0, 98835.0, 98836.0, 98837.0, 98838.0],       [98839.0, 98840.0, 98841.0, 98842.0, 98843.0, 98844.0],       [98845.0, 98846.0, 98847.0, 98848.0, 98849.0, 98850.0],       [98851.0, 98852.0, 98853.0, 98854.0, 98855.0, 98856.0],       [98857.0, 98858.0, 98859.0, 98860.0, 98861.0, 98862.0],       [98863.0, 98864.0, 98865.0, 98866.0, 98867.0, 98868.0]],      [[98869.0, 98870.0, 98871.0, 98872.0, 98873.0, 98874.0],       [98875.0, 98876.0, 98877.0, 98878.0, 98879.0, 98880.0],       [98881.0, 98882.0, 98883.0, 98884.0, 98885.0, 98886.0],       [98887.0, 98888.0, 98889.0, 98890.0, 98891.0, 98892.0],       [98893.0, 98894.0, 98895.0, 98896.0, 98897.0, 98898.0],       [98899.0, 98900.0, 98901.0, 98902.0, 98903.0, 98904.0],       [98905.0, 98906.0, 98907.0, 98908.0, 98909.0, 98910.0]],      [[98911.0, 98912.0, 98913.0, 98914.0, 98915.0, 98916.0],       [98917.0, 98918.0, 98919.0, 98920.0, 98921.0, 98922.0],       [98923.0, 98924.0, 98925.0, 98926.0, 98927.0, 98928.0],       [98929.0, 98930.0, 98931.0, 98932.0, 98933.0, 98934.0],       [98935.0, 98936.0, 98937.0, 98938.0, 98939.0, 98940.0],       [98941.0, 98942.0, 98943.0, 98944.0, 98945.0, 98946.0],       [98947.0, 98948.0, 98949.0, 98950.0, 98951.0, 98952.0]],      [[98953.0, 98954.0, 98955.0, 98956.0, 98957.0, 98958.0],       [98959.0, 98960.0, 98961.0, 98962.0, 98963.0, 98964.0],       [98965.0, 98966.0, 98967.0, 98968.0, 98969.0, 98970.0],       [98971.0, 98972.0, 98973.0, 98974.0, 98975.0, 98976.0],       [98977.0, 98978.0, 98979.0, 98980.0, 98981.0, 98982.0],       [98983.0, 98984.0, 98985.0, 98986.0, 98987.0, 98988.0],       [98989.0, 98990.0, 98991.0, 98992.0, 98993.0, 98994.0]],      [[98995.0, 98996.0, 98997.0, 98998.0, 98999.0, 99000.0],       [99001.0, 99002.0, 99003.0, 99004.0, 99005.0, 99006.0],       [99007.0, 99008.0, 99009.0, 99010.0, 99011.0, 99012.0],       [99013.0, 99014.0, 99015.0, 99016.0, 99017.0, 99018.0],       [99019.0, 99020.0, 99021.0, 99022.0, 99023.0, 99024.0],       [99025.0, 99026.0, 99027.0, 99028.0, 99029.0, 99030.0],       [99031.0, 99032.0, 99033.0, 99034.0, 99035.0, 99036.0]]],     [[[99037.0, 99038.0, 99039.0, 99040.0, 99041.0, 99042.0],       [99043.0, 99044.0, 99045.0, 99046.0, 99047.0, 99048.0],       [99049.0, 99050.0, 99051.0, 99052.0, 99053.0, 99054.0],       [99055.0, 99056.0, 99057.0, 99058.0, 99059.0, 99060.0],       [99061.0, 99062.0, 99063.0, 99064.0, 99065.0, 99066.0],       [99067.0, 99068.0, 99069.0, 99070.0, 99071.0, 99072.0],       [99073.0, 99074.0, 99075.0, 99076.0, 99077.0, 99078.0]],      [[99079.0, 99080.0, 99081.0, 99082.0, 99083.0, 99084.0],       [99085.0, 99086.0, 99087.0, 99088.0, 99089.0, 99090.0],       [99091.0, 99092.0, 99093.0, 99094.0, 99095.0, 99096.0],       [99097.0, 99098.0, 99099.0, 99100.0, 99101.0, 99102.0],       [99103.0, 99104.0, 99105.0, 99106.0, 99107.0, 99108.0],       [99109.0, 99110.0, 99111.0, 99112.0, 99113.0, 99114.0],       [99115.0, 99116.0, 99117.0, 99118.0, 99119.0, 99120.0]],      [[99121.0, 99122.0, 99123.0, 99124.0, 99125.0, 99126.0],       [99127.0, 99128.0, 99129.0, 99130.0, 99131.0, 99132.0],       [99133.0, 99134.0, 99135.0, 99136.0, 99137.0, 99138.0],       [99139.0, 99140.0, 99141.0, 99142.0, 99143.0, 99144.0],       [99145.0, 99146.0, 99147.0, 99148.0, 99149.0, 99150.0],       [99151.0, 99152.0, 99153.0, 99154.0, 99155.0, 99156.0],       [99157.0, 99158.0, 99159.0, 99160.0, 99161.0, 99162.0]],      [[99163.0, 99164.0, 99165.0, 99166.0, 99167.0, 99168.0],       [99169.0, 99170.0, 99171.0, 99172.0, 99173.0, 99174.0],       [99175.0, 99176.0, 99177.0, 99178.0, 99179.0, 99180.0],       [99181.0, 99182.0, 99183.0, 99184.0, 99185.0, 99186.0],       [99187.0, 99188.0, 99189.0, 99190.0, 99191.0, 99192.0],       [99193.0, 99194.0, 99195.0, 99196.0, 99197.0, 99198.0],       [99199.0, 99200.0, 99201.0, 99202.0, 99203.0, 99204.0]],      [[99205.0, 99206.0, 99207.0, 99208.0, 99209.0, 99210.0],       [99211.0, 99212.0, 99213.0, 99214.0, 99215.0, 99216.0],       [99217.0, 99218.0, 99219.0, 99220.0, 99221.0, 99222.0],       [99223.0, 99224.0, 99225.0, 99226.0, 99227.0, 99228.0],       [99229.0, 99230.0, 99231.0, 99232.0, 99233.0, 99234.0],       [99235.0, 99236.0, 99237.0, 99238.0, 99239.0, 99240.0],       [99241.0, 99242.0, 99243.0, 99244.0, 99245.0, 99246.0]],      [[99247.0, 99248.0, 99249.0, 99250.0, 99251.0, 99252.0],       [99253.0, 99254.0, 99255.0, 99256.0, 99257.0, 99258.0],       [99259.0, 99260.0, 99261.0, 99262.0, 99263.0, 99264.0],       [99265.0, 99266.0, 99267.0, 99268.0, 99269.0, 99270.0],       [99271.0, 99272.0, 99273.0, 99274.0, 99275.0, 99276.0],       [99277.0, 99278.0, 99279.0, 99280.0, 99281.0, 99282.0],       [99283.0, 99284.0, 99285.0, 99286.0, 99287.0, 99288.0]]],     [[[99289.0, 99290.0, 99291.0, 99292.0, 99293.0, 99294.0],       [99295.0, 99296.0, 99297.0, 99298.0, 99299.0, 99300.0],       [99301.0, 99302.0, 99303.0, 99304.0, 99305.0, 99306.0],       [99307.0, 99308.0, 99309.0, 99310.0, 99311.0, 99312.0],       [99313.0, 99314.0, 99315.0, 99316.0, 99317.0, 99318.0],       [99319.0, 99320.0, 99321.0, 99322.0, 99323.0, 99324.0],       [99325.0, 99326.0, 99327.0, 99328.0, 99329.0, 99330.0]],      [[99331.0, 99332.0, 99333.0, 99334.0, 99335.0, 99336.0],       [99337.0, 99338.0, 99339.0, 99340.0, 99341.0, 99342.0],       [99343.0, 99344.0, 99345.0, 99346.0, 99347.0, 99348.0],       [99349.0, 99350.0, 99351.0, 99352.0, 99353.0, 99354.0],       [99355.0, 99356.0, 99357.0, 99358.0, 99359.0, 99360.0],       [99361.0, 99362.0, 99363.0, 99364.0, 99365.0, 99366.0],       [99367.0, 99368.0, 99369.0, 99370.0, 99371.0, 99372.0]],      [[99373.0, 99374.0, 99375.0, 99376.0, 99377.0, 99378.0],       [99379.0, 99380.0, 99381.0, 99382.0, 99383.0, 99384.0],       [99385.0, 99386.0, 99387.0, 99388.0, 99389.0, 99390.0],       [99391.0, 99392.0, 99393.0, 99394.0, 99395.0, 99396.0],       [99397.0, 99398.0, 99399.0, 99400.0, 99401.0, 99402.0],       [99403.0, 99404.0, 99405.0, 99406.0, 99407.0, 99408.0],       [99409.0, 99410.0, 99411.0, 99412.0, 99413.0, 99414.0]],      [[99415.0, 99416.0, 99417.0, 99418.0, 99419.0, 99420.0],       [99421.0, 99422.0, 99423.0, 99424.0, 99425.0, 99426.0],       [99427.0, 99428.0, 99429.0, 99430.0, 99431.0, 99432.0],       [99433.0, 99434.0, 99435.0, 99436.0, 99437.0, 99438.0],       [99439.0, 99440.0, 99441.0, 99442.0, 99443.0, 99444.0],       [99445.0, 99446.0, 99447.0, 99448.0, 99449.0, 99450.0],       [99451.0, 99452.0, 99453.0, 99454.0, 99455.0, 99456.0]],      [[99457.0, 99458.0, 99459.0, 99460.0, 99461.0, 99462.0],       [99463.0, 99464.0, 99465.0, 99466.0, 99467.0, 99468.0],       [99469.0, 99470.0, 99471.0, 99472.0, 99473.0, 99474.0],       [99475.0, 99476.0, 99477.0, 99478.0, 99479.0, 99480.0],       [99481.0, 99482.0, 99483.0, 99484.0, 99485.0, 99486.0],       [99487.0, 99488.0, 99489.0, 99490.0, 99491.0, 99492.0],       [99493.0, 99494.0, 99495.0, 99496.0, 99497.0, 99498.0]],      [[99499.0, 99500.0, 99501.0, 99502.0, 99503.0, 99504.0],       [99505.0, 99506.0, 99507.0, 99508.0, 99509.0, 99510.0],       [99511.0, 99512.0, 99513.0, 99514.0, 99515.0, 99516.0],       [99517.0, 99518.0, 99519.0, 99520.0, 99521.0, 99522.0],       [99523.0, 99524.0, 99525.0, 99526.0, 99527.0, 99528.0],       [99529.0, 99530.0, 99531.0, 99532.0, 99533.0, 99534.0],       [99535.0, 99536.0, 99537.0, 99538.0, 99539.0, 99540.0]]],     [[[99541.0, 99542.0, 99543.0, 99544.0, 99545.0, 99546.0],       [99547.0, 99548.0, 99549.0, 99550.0, 99551.0, 99552.0],       [99553.0, 99554.0, 99555.0, 99556.0, 99557.0, 99558.0],       [99559.0, 99560.0, 99561.0, 99562.0, 99563.0, 99564.0],       [99565.0, 99566.0, 99567.0, 99568.0, 99569.0, 99570.0],       [99571.0, 99572.0, 99573.0, 99574.0, 99575.0, 99576.0],       [99577.0, 99578.0, 99579.0, 99580.0, 99581.0, 99582.0]],      [[99583.0, 99584.0, 99585.0, 99586.0, 99587.0, 99588.0],       [99589.0, 99590.0, 99591.0, 99592.0, 99593.0, 99594.0],       [99595.0, 99596.0, 99597.0, 99598.0, 99599.0, 99600.0],       [99601.0, 99602.0, 99603.0, 99604.0, 99605.0, 99606.0],       [99607.0, 99608.0, 99609.0, 99610.0, 99611.0, 99612.0],       [99613.0, 99614.0, 99615.0, 99616.0, 99617.0, 99618.0],       [99619.0, 99620.0, 99621.0, 99622.0, 99623.0, 99624.0]],      [[99625.0, 99626.0, 99627.0, 99628.0, 99629.0, 99630.0],       [99631.0, 99632.0, 99633.0, 99634.0, 99635.0, 99636.0],       [99637.0, 99638.0, 99639.0, 99640.0, 99641.0, 99642.0],       [99643.0, 99644.0, 99645.0, 99646.0, 99647.0, 99648.0],       [99649.0, 99650.0, 99651.0, 99652.0, 99653.0, 99654.0],       [99655.0, 99656.0, 99657.0, 99658.0, 99659.0, 99660.0],       [99661.0, 99662.0, 99663.0, 99664.0, 99665.0, 99666.0]],      [[99667.0, 99668.0, 99669.0, 99670.0, 99671.0, 99672.0],       [99673.0, 99674.0, 99675.0, 99676.0, 99677.0, 99678.0],       [99679.0, 99680.0, 99681.0, 99682.0, 99683.0, 99684.0],       [99685.0, 99686.0, 99687.0, 99688.0, 99689.0, 99690.0],       [99691.0, 99692.0, 99693.0, 99694.0, 99695.0, 99696.0],       [99697.0, 99698.0, 99699.0, 99700.0, 99701.0, 99702.0],       [99703.0, 99704.0, 99705.0, 99706.0, 99707.0, 99708.0]],      [[99709.0, 99710.0, 99711.0, 99712.0, 99713.0, 99714.0],       [99715.0, 99716.0, 99717.0, 99718.0, 99719.0, 99720.0],       [99721.0, 99722.0, 99723.0, 99724.0, 99725.0, 99726.0],       [99727.0, 99728.0, 99729.0, 99730.0, 99731.0, 99732.0],       [99733.0, 99734.0, 99735.0, 99736.0, 99737.0, 99738.0],       [99739.0, 99740.0, 99741.0, 99742.0, 99743.0, 99744.0],       [99745.0, 99746.0, 99747.0, 99748.0, 99749.0, 99750.0]],      [[99751.0, 99752.0, 99753.0, 99754.0, 99755.0, 99756.0],       [99757.0, 99758.0, 99759.0, 99760.0, 99761.0, 99762.0],       [99763.0, 99764.0, 99765.0, 99766.0, 99767.0, 99768.0],       [99769.0, 99770.0, 99771.0, 99772.0, 99773.0, 99774.0],       [99775.0, 99776.0, 99777.0, 99778.0, 99779.0, 99780.0],       [99781.0, 99782.0, 99783.0, 99784.0, 99785.0, 99786.0],       [99787.0, 99788.0, 99789.0, 99790.0, 99791.0, 99792.0]]]],    [[[[99793.0, 99794.0, 99795.0, 99796.0, 99797.0, 99798.0],       [99799.0, 99800.0, 99801.0, 99802.0, 99803.0, 99804.0],       [99805.0, 99806.0, 99807.0, 99808.0, 99809.0, 99810.0],       [99811.0, 99812.0, 99813.0, 99814.0, 99815.0, 99816.0],       [99817.0, 99818.0, 99819.0, 99820.0, 99821.0, 99822.0],       [99823.0, 99824.0, 99825.0, 99826.0, 99827.0, 99828.0],       [99829.0, 99830.0, 99831.0, 99832.0, 99833.0, 99834.0]],      [[99835.0, 99836.0, 99837.0, 99838.0, 99839.0, 99840.0],       [99841.0, 99842.0, 99843.0, 99844.0, 99845.0, 99846.0],       [99847.0, 99848.0, 99849.0, 99850.0, 99851.0, 99852.0],       [99853.0, 99854.0, 99855.0, 99856.0, 99857.0, 99858.0],       [99859.0, 99860.0, 99861.0, 99862.0, 99863.0, 99864.0],       [99865.0, 99866.0, 99867.0, 99868.0, 99869.0, 99870.0],       [99871.0, 99872.0, 99873.0, 99874.0, 99875.0, 99876.0]],      [[99877.0, 99878.0, 99879.0, 99880.0, 99881.0, 99882.0],       [99883.0, 99884.0, 99885.0, 99886.0, 99887.0, 99888.0],       [99889.0, 99890.0, 99891.0, 99892.0, 99893.0, 99894.0],       [99895.0, 99896.0, 99897.0, 99898.0, 99899.0, 99900.0],       [99901.0, 99902.0, 99903.0, 99904.0, 99905.0, 99906.0],       [99907.0, 99908.0, 99909.0, 99910.0, 99911.0, 99912.0],       [99913.0, 99914.0, 99915.0, 99916.0, 99917.0, 99918.0]],      [[99919.0, 99920.0, 99921.0, 99922.0, 99923.0, 99924.0],       [99925.0, 99926.0, 99927.0, 99928.0, 99929.0, 99930.0],       [99931.0, 99932.0, 99933.0, 99934.0, 99935.0, 99936.0],       [99937.0, 99938.0, 99939.0, 99940.0, 99941.0, 99942.0],       [99943.0, 99944.0, 99945.0, 99946.0, 99947.0, 99948.0],       [99949.0, 99950.0, 99951.0, 99952.0, 99953.0, 99954.0],       [99955.0, 99956.0, 99957.0, 99958.0, 99959.0, 99960.0]],      [[99961.0, 99962.0, 99963.0, 99964.0, 99965.0, 99966.0],       [99967.0, 99968.0, 99969.0, 99970.0, 99971.0, 99972.0],       [99973.0, 99974.0, 99975.0, 99976.0, 99977.0, 99978.0],       [99979.0, 99980.0, 99981.0, 99982.0, 99983.0, 99984.0],       [99985.0, 99986.0, 99987.0, 99988.0, 99989.0, 99990.0],       [99991.0, 99992.0, 99993.0, 99994.0, 99995.0, 99996.0],       [99997.0, 99998.0, 99999.0, 100000.0, 100001.0, 100002.0]],      [[100003.0, 100004.0, 100005.0, 100006.0, 100007.0, 100008.0],       [100009.0, 100010.0, 100011.0, 100012.0, 100013.0, 100014.0],       [100015.0, 100016.0, 100017.0, 100018.0, 100019.0, 100020.0],       [100021.0, 100022.0, 100023.0, 100024.0, 100025.0, 100026.0],       [100027.0, 100028.0, 100029.0, 100030.0, 100031.0, 100032.0],       [100033.0, 100034.0, 100035.0, 100036.0, 100037.0, 100038.0],       [100039.0, 100040.0, 100041.0, 100042.0, 100043.0, 100044.0]]],     [[[100045.0, 100046.0, 100047.0, 100048.0, 100049.0, 100050.0],       [100051.0, 100052.0, 100053.0, 100054.0, 100055.0, 100056.0],       [100057.0, 100058.0, 100059.0, 100060.0, 100061.0, 100062.0],       [100063.0, 100064.0, 100065.0, 100066.0, 100067.0, 100068.0],       [100069.0, 100070.0, 100071.0, 100072.0, 100073.0, 100074.0],       [100075.0, 100076.0, 100077.0, 100078.0, 100079.0, 100080.0],       [100081.0, 100082.0, 100083.0, 100084.0, 100085.0, 100086.0]],      [[100087.0, 100088.0, 100089.0, 100090.0, 100091.0, 100092.0],       [100093.0, 100094.0, 100095.0, 100096.0, 100097.0, 100098.0],       [100099.0, 100100.0, 100101.0, 100102.0, 100103.0, 100104.0],       [100105.0, 100106.0, 100107.0, 100108.0, 100109.0, 100110.0],       [100111.0, 100112.0, 100113.0, 100114.0, 100115.0, 100116.0],       [100117.0, 100118.0, 100119.0, 100120.0, 100121.0, 100122.0],       [100123.0, 100124.0, 100125.0, 100126.0, 100127.0, 100128.0]],      [[100129.0, 100130.0, 100131.0, 100132.0, 100133.0, 100134.0],       [100135.0, 100136.0, 100137.0, 100138.0, 100139.0, 100140.0],       [100141.0, 100142.0, 100143.0, 100144.0, 100145.0, 100146.0],       [100147.0, 100148.0, 100149.0, 100150.0, 100151.0, 100152.0],       [100153.0, 100154.0, 100155.0, 100156.0, 100157.0, 100158.0],       [100159.0, 100160.0, 100161.0, 100162.0, 100163.0, 100164.0],       [100165.0, 100166.0, 100167.0, 100168.0, 100169.0, 100170.0]],      [[100171.0, 100172.0, 100173.0, 100174.0, 100175.0, 100176.0],       [100177.0, 100178.0, 100179.0, 100180.0, 100181.0, 100182.0],       [100183.0, 100184.0, 100185.0, 100186.0, 100187.0, 100188.0],       [100189.0, 100190.0, 100191.0, 100192.0, 100193.0, 100194.0],       [100195.0, 100196.0, 100197.0, 100198.0, 100199.0, 100200.0],       [100201.0, 100202.0, 100203.0, 100204.0, 100205.0, 100206.0],       [100207.0, 100208.0, 100209.0, 100210.0, 100211.0, 100212.0]],      [[100213.0, 100214.0, 100215.0, 100216.0, 100217.0, 100218.0],       [100219.0, 100220.0, 100221.0, 100222.0, 100223.0, 100224.0],       [100225.0, 100226.0, 100227.0, 100228.0, 100229.0, 100230.0],       [100231.0, 100232.0, 100233.0, 100234.0, 100235.0, 100236.0],       [100237.0, 100238.0, 100239.0, 100240.0, 100241.0, 100242.0],       [100243.0, 100244.0, 100245.0, 100246.0, 100247.0, 100248.0],       [100249.0, 100250.0, 100251.0, 100252.0, 100253.0, 100254.0]],      [[100255.0, 100256.0, 100257.0, 100258.0, 100259.0, 100260.0],       [100261.0, 100262.0, 100263.0, 100264.0, 100265.0, 100266.0],       [100267.0, 100268.0, 100269.0, 100270.0, 100271.0, 100272.0],       [100273.0, 100274.0, 100275.0, 100276.0, 100277.0, 100278.0],       [100279.0, 100280.0, 100281.0, 100282.0, 100283.0, 100284.0],       [100285.0, 100286.0, 100287.0, 100288.0, 100289.0, 100290.0],       [100291.0, 100292.0, 100293.0, 100294.0, 100295.0, 100296.0]]],     [[[100297.0, 100298.0, 100299.0, 100300.0, 100301.0, 100302.0],       [100303.0, 100304.0, 100305.0, 100306.0, 100307.0, 100308.0],       [100309.0, 100310.0, 100311.0, 100312.0, 100313.0, 100314.0],       [100315.0, 100316.0, 100317.0, 100318.0, 100319.0, 100320.0],       [100321.0, 100322.0, 100323.0, 100324.0, 100325.0, 100326.0],       [100327.0, 100328.0, 100329.0, 100330.0, 100331.0, 100332.0],       [100333.0, 100334.0, 100335.0, 100336.0, 100337.0, 100338.0]],      [[100339.0, 100340.0, 100341.0, 100342.0, 100343.0, 100344.0],       [100345.0, 100346.0, 100347.0, 100348.0, 100349.0, 100350.0],       [100351.0, 100352.0, 100353.0, 100354.0, 100355.0, 100356.0],       [100357.0, 100358.0, 100359.0, 100360.0, 100361.0, 100362.0],       [100363.0, 100364.0, 100365.0, 100366.0, 100367.0, 100368.0],       [100369.0, 100370.0, 100371.0, 100372.0, 100373.0, 100374.0],       [100375.0, 100376.0, 100377.0, 100378.0, 100379.0, 100380.0]],      [[100381.0, 100382.0, 100383.0, 100384.0, 100385.0, 100386.0],       [100387.0, 100388.0, 100389.0, 100390.0, 100391.0, 100392.0],       [100393.0, 100394.0, 100395.0, 100396.0, 100397.0, 100398.0],       [100399.0, 100400.0, 100401.0, 100402.0, 100403.0, 100404.0],       [100405.0, 100406.0, 100407.0, 100408.0, 100409.0, 100410.0],       [100411.0, 100412.0, 100413.0, 100414.0, 100415.0, 100416.0],       [100417.0, 100418.0, 100419.0, 100420.0, 100421.0, 100422.0]],      [[100423.0, 100424.0, 100425.0, 100426.0, 100427.0, 100428.0],       [100429.0, 100430.0, 100431.0, 100432.0, 100433.0, 100434.0],       [100435.0, 100436.0, 100437.0, 100438.0, 100439.0, 100440.0],       [100441.0, 100442.0, 100443.0, 100444.0, 100445.0, 100446.0],       [100447.0, 100448.0, 100449.0, 100450.0, 100451.0, 100452.0],       [100453.0, 100454.0, 100455.0, 100456.0, 100457.0, 100458.0],       [100459.0, 100460.0, 100461.0, 100462.0, 100463.0, 100464.0]],      [[100465.0, 100466.0, 100467.0, 100468.0, 100469.0, 100470.0],       [100471.0, 100472.0, 100473.0, 100474.0, 100475.0, 100476.0],       [100477.0, 100478.0, 100479.0, 100480.0, 100481.0, 100482.0],       [100483.0, 100484.0, 100485.0, 100486.0, 100487.0, 100488.0],       [100489.0, 100490.0, 100491.0, 100492.0, 100493.0, 100494.0],       [100495.0, 100496.0, 100497.0, 100498.0, 100499.0, 100500.0],       [100501.0, 100502.0, 100503.0, 100504.0, 100505.0, 100506.0]],      [[100507.0, 100508.0, 100509.0, 100510.0, 100511.0, 100512.0],       [100513.0, 100514.0, 100515.0, 100516.0, 100517.0, 100518.0],       [100519.0, 100520.0, 100521.0, 100522.0, 100523.0, 100524.0],       [100525.0, 100526.0, 100527.0, 100528.0, 100529.0, 100530.0],       [100531.0, 100532.0, 100533.0, 100534.0, 100535.0, 100536.0],       [100537.0, 100538.0, 100539.0, 100540.0, 100541.0, 100542.0],       [100543.0, 100544.0, 100545.0, 100546.0, 100547.0, 100548.0]]],     [[[100549.0, 100550.0, 100551.0, 100552.0, 100553.0, 100554.0],       [100555.0, 100556.0, 100557.0, 100558.0, 100559.0, 100560.0],       [100561.0, 100562.0, 100563.0, 100564.0, 100565.0, 100566.0],       [100567.0, 100568.0, 100569.0, 100570.0, 100571.0, 100572.0],       [100573.0, 100574.0, 100575.0, 100576.0, 100577.0, 100578.0],       [100579.0, 100580.0, 100581.0, 100582.0, 100583.0, 100584.0],       [100585.0, 100586.0, 100587.0, 100588.0, 100589.0, 100590.0]],      [[100591.0, 100592.0, 100593.0, 100594.0, 100595.0, 100596.0],       [100597.0, 100598.0, 100599.0, 100600.0, 100601.0, 100602.0],       [100603.0, 100604.0, 100605.0, 100606.0, 100607.0, 100608.0],       [100609.0, 100610.0, 100611.0, 100612.0, 100613.0, 100614.0],       [100615.0, 100616.0, 100617.0, 100618.0, 100619.0, 100620.0],       [100621.0, 100622.0, 100623.0, 100624.0, 100625.0, 100626.0],       [100627.0, 100628.0, 100629.0, 100630.0, 100631.0, 100632.0]],      [[100633.0, 100634.0, 100635.0, 100636.0, 100637.0, 100638.0],       [100639.0, 100640.0, 100641.0, 100642.0, 100643.0, 100644.0],       [100645.0, 100646.0, 100647.0, 100648.0, 100649.0, 100650.0],       [100651.0, 100652.0, 100653.0, 100654.0, 100655.0, 100656.0],       [100657.0, 100658.0, 100659.0, 100660.0, 100661.0, 100662.0],       [100663.0, 100664.0, 100665.0, 100666.0, 100667.0, 100668.0],       [100669.0, 100670.0, 100671.0, 100672.0, 100673.0, 100674.0]],      [[100675.0, 100676.0, 100677.0, 100678.0, 100679.0, 100680.0],       [100681.0, 100682.0, 100683.0, 100684.0, 100685.0, 100686.0],       [100687.0, 100688.0, 100689.0, 100690.0, 100691.0, 100692.0],       [100693.0, 100694.0, 100695.0, 100696.0, 100697.0, 100698.0],       [100699.0, 100700.0, 100701.0, 100702.0, 100703.0, 100704.0],       [100705.0, 100706.0, 100707.0, 100708.0, 100709.0, 100710.0],       [100711.0, 100712.0, 100713.0, 100714.0, 100715.0, 100716.0]],      [[100717.0, 100718.0, 100719.0, 100720.0, 100721.0, 100722.0],       [100723.0, 100724.0, 100725.0, 100726.0, 100727.0, 100728.0],       [100729.0, 100730.0, 100731.0, 100732.0, 100733.0, 100734.0],       [100735.0, 100736.0, 100737.0, 100738.0, 100739.0, 100740.0],       [100741.0, 100742.0, 100743.0, 100744.0, 100745.0, 100746.0],       [100747.0, 100748.0, 100749.0, 100750.0, 100751.0, 100752.0],       [100753.0, 100754.0, 100755.0, 100756.0, 100757.0, 100758.0]],      [[100759.0, 100760.0, 100761.0, 100762.0, 100763.0, 100764.0],       [100765.0, 100766.0, 100767.0, 100768.0, 100769.0, 100770.0],       [100771.0, 100772.0, 100773.0, 100774.0, 100775.0, 100776.0],       [100777.0, 100778.0, 100779.0, 100780.0, 100781.0, 100782.0],       [100783.0, 100784.0, 100785.0, 100786.0, 100787.0, 100788.0],       [100789.0, 100790.0, 100791.0, 100792.0, 100793.0, 100794.0],       [100795.0, 100796.0, 100797.0, 100798.0, 100799.0, 100800.0]]]],    [[[[100801.0, 100802.0, 100803.0, 100804.0, 100805.0, 100806.0],       [100807.0, 100808.0, 100809.0, 100810.0, 100811.0, 100812.0],       [100813.0, 100814.0, 100815.0, 100816.0, 100817.0, 100818.0],       [100819.0, 100820.0, 100821.0, 100822.0, 100823.0, 100824.0],       [100825.0, 100826.0, 100827.0, 100828.0, 100829.0, 100830.0],       [100831.0, 100832.0, 100833.0, 100834.0, 100835.0, 100836.0],       [100837.0, 100838.0, 100839.0, 100840.0, 100841.0, 100842.0]],      [[100843.0, 100844.0, 100845.0, 100846.0, 100847.0, 100848.0],       [100849.0, 100850.0, 100851.0, 100852.0, 100853.0, 100854.0],       [100855.0, 100856.0, 100857.0, 100858.0, 100859.0, 100860.0],       [100861.0, 100862.0, 100863.0, 100864.0, 100865.0, 100866.0],       [100867.0, 100868.0, 100869.0, 100870.0, 100871.0, 100872.0],       [100873.0, 100874.0, 100875.0, 100876.0, 100877.0, 100878.0],       [100879.0, 100880.0, 100881.0, 100882.0, 100883.0, 100884.0]],      [[100885.0, 100886.0, 100887.0, 100888.0, 100889.0, 100890.0],       [100891.0, 100892.0, 100893.0, 100894.0, 100895.0, 100896.0],       [100897.0, 100898.0, 100899.0, 100900.0, 100901.0, 100902.0],       [100903.0, 100904.0, 100905.0, 100906.0, 100907.0, 100908.0],       [100909.0, 100910.0, 100911.0, 100912.0, 100913.0, 100914.0],       [100915.0, 100916.0, 100917.0, 100918.0, 100919.0, 100920.0],       [100921.0, 100922.0, 100923.0, 100924.0, 100925.0, 100926.0]],      [[100927.0, 100928.0, 100929.0, 100930.0, 100931.0, 100932.0],       [100933.0, 100934.0, 100935.0, 100936.0, 100937.0, 100938.0],       [100939.0, 100940.0, 100941.0, 100942.0, 100943.0, 100944.0],       [100945.0, 100946.0, 100947.0, 100948.0, 100949.0, 100950.0],       [100951.0, 100952.0, 100953.0, 100954.0, 100955.0, 100956.0],       [100957.0, 100958.0, 100959.0, 100960.0, 100961.0, 100962.0],       [100963.0, 100964.0, 100965.0, 100966.0, 100967.0, 100968.0]],      [[100969.0, 100970.0, 100971.0, 100972.0, 100973.0, 100974.0],       [100975.0, 100976.0, 100977.0, 100978.0, 100979.0, 100980.0],       [100981.0, 100982.0, 100983.0, 100984.0, 100985.0, 100986.0],       [100987.0, 100988.0, 100989.0, 100990.0, 100991.0, 100992.0],       [100993.0, 100994.0, 100995.0, 100996.0, 100997.0, 100998.0],       [100999.0, 101000.0, 101001.0, 101002.0, 101003.0, 101004.0],       [101005.0, 101006.0, 101007.0, 101008.0, 101009.0, 101010.0]],      [[101011.0, 101012.0, 101013.0, 101014.0, 101015.0, 101016.0],       [101017.0, 101018.0, 101019.0, 101020.0, 101021.0, 101022.0],       [101023.0, 101024.0, 101025.0, 101026.0, 101027.0, 101028.0],       [101029.0, 101030.0, 101031.0, 101032.0, 101033.0, 101034.0],       [101035.0, 101036.0, 101037.0, 101038.0, 101039.0, 101040.0],       [101041.0, 101042.0, 101043.0, 101044.0, 101045.0, 101046.0],       [101047.0, 101048.0, 101049.0, 101050.0, 101051.0, 101052.0]]],     [[[101053.0, 101054.0, 101055.0, 101056.0, 101057.0, 101058.0],       [101059.0, 101060.0, 101061.0, 101062.0, 101063.0, 101064.0],       [101065.0, 101066.0, 101067.0, 101068.0, 101069.0, 101070.0],       [101071.0, 101072.0, 101073.0, 101074.0, 101075.0, 101076.0],       [101077.0, 101078.0, 101079.0, 101080.0, 101081.0, 101082.0],       [101083.0, 101084.0, 101085.0, 101086.0, 101087.0, 101088.0],       [101089.0, 101090.0, 101091.0, 101092.0, 101093.0, 101094.0]],      [[101095.0, 101096.0, 101097.0, 101098.0, 101099.0, 101100.0],       [101101.0, 101102.0, 101103.0, 101104.0, 101105.0, 101106.0],       [101107.0, 101108.0, 101109.0, 101110.0, 101111.0, 101112.0],       [101113.0, 101114.0, 101115.0, 101116.0, 101117.0, 101118.0],       [101119.0, 101120.0, 101121.0, 101122.0, 101123.0, 101124.0],       [101125.0, 101126.0, 101127.0, 101128.0, 101129.0, 101130.0],       [101131.0, 101132.0, 101133.0, 101134.0, 101135.0, 101136.0]],      [[101137.0, 101138.0, 101139.0, 101140.0, 101141.0, 101142.0],       [101143.0, 101144.0, 101145.0, 101146.0, 101147.0, 101148.0],       [101149.0, 101150.0, 101151.0, 101152.0, 101153.0, 101154.0],       [101155.0, 101156.0, 101157.0, 101158.0, 101159.0, 101160.0],       [101161.0, 101162.0, 101163.0, 101164.0, 101165.0, 101166.0],       [101167.0, 101168.0, 101169.0, 101170.0, 101171.0, 101172.0],       [101173.0, 101174.0, 101175.0, 101176.0, 101177.0, 101178.0]],      [[101179.0, 101180.0, 101181.0, 101182.0, 101183.0, 101184.0],       [101185.0, 101186.0, 101187.0, 101188.0, 101189.0, 101190.0],       [101191.0, 101192.0, 101193.0, 101194.0, 101195.0, 101196.0],       [101197.0, 101198.0, 101199.0, 101200.0, 101201.0, 101202.0],       [101203.0, 101204.0, 101205.0, 101206.0, 101207.0, 101208.0],       [101209.0, 101210.0, 101211.0, 101212.0, 101213.0, 101214.0],       [101215.0, 101216.0, 101217.0, 101218.0, 101219.0, 101220.0]],      [[101221.0, 101222.0, 101223.0, 101224.0, 101225.0, 101226.0],       [101227.0, 101228.0, 101229.0, 101230.0, 101231.0, 101232.0],       [101233.0, 101234.0, 101235.0, 101236.0, 101237.0, 101238.0],       [101239.0, 101240.0, 101241.0, 101242.0, 101243.0, 101244.0],       [101245.0, 101246.0, 101247.0, 101248.0, 101249.0, 101250.0],       [101251.0, 101252.0, 101253.0, 101254.0, 101255.0, 101256.0],       [101257.0, 101258.0, 101259.0, 101260.0, 101261.0, 101262.0]],      [[101263.0, 101264.0, 101265.0, 101266.0, 101267.0, 101268.0],       [101269.0, 101270.0, 101271.0, 101272.0, 101273.0, 101274.0],       [101275.0, 101276.0, 101277.0, 101278.0, 101279.0, 101280.0],       [101281.0, 101282.0, 101283.0, 101284.0, 101285.0, 101286.0],       [101287.0, 101288.0, 101289.0, 101290.0, 101291.0, 101292.0],       [101293.0, 101294.0, 101295.0, 101296.0, 101297.0, 101298.0],       [101299.0, 101300.0, 101301.0, 101302.0, 101303.0, 101304.0]]],     [[[101305.0, 101306.0, 101307.0, 101308.0, 101309.0, 101310.0],       [101311.0, 101312.0, 101313.0, 101314.0, 101315.0, 101316.0],       [101317.0, 101318.0, 101319.0, 101320.0, 101321.0, 101322.0],       [101323.0, 101324.0, 101325.0, 101326.0, 101327.0, 101328.0],       [101329.0, 101330.0, 101331.0, 101332.0, 101333.0, 101334.0],       [101335.0, 101336.0, 101337.0, 101338.0, 101339.0, 101340.0],       [101341.0, 101342.0, 101343.0, 101344.0, 101345.0, 101346.0]],      [[101347.0, 101348.0, 101349.0, 101350.0, 101351.0, 101352.0],       [101353.0, 101354.0, 101355.0, 101356.0, 101357.0, 101358.0],       [101359.0, 101360.0, 101361.0, 101362.0, 101363.0, 101364.0],       [101365.0, 101366.0, 101367.0, 101368.0, 101369.0, 101370.0],       [101371.0, 101372.0, 101373.0, 101374.0, 101375.0, 101376.0],       [101377.0, 101378.0, 101379.0, 101380.0, 101381.0, 101382.0],       [101383.0, 101384.0, 101385.0, 101386.0, 101387.0, 101388.0]],      [[101389.0, 101390.0, 101391.0, 101392.0, 101393.0, 101394.0],       [101395.0, 101396.0, 101397.0, 101398.0, 101399.0, 101400.0],       [101401.0, 101402.0, 101403.0, 101404.0, 101405.0, 101406.0],       [101407.0, 101408.0, 101409.0, 101410.0, 101411.0, 101412.0],       [101413.0, 101414.0, 101415.0, 101416.0, 101417.0, 101418.0],       [101419.0, 101420.0, 101421.0, 101422.0, 101423.0, 101424.0],       [101425.0, 101426.0, 101427.0, 101428.0, 101429.0, 101430.0]],      [[101431.0, 101432.0, 101433.0, 101434.0, 101435.0, 101436.0],       [101437.0, 101438.0, 101439.0, 101440.0, 101441.0, 101442.0],       [101443.0, 101444.0, 101445.0, 101446.0, 101447.0, 101448.0],       [101449.0, 101450.0, 101451.0, 101452.0, 101453.0, 101454.0],       [101455.0, 101456.0, 101457.0, 101458.0, 101459.0, 101460.0],       [101461.0, 101462.0, 101463.0, 101464.0, 101465.0, 101466.0],       [101467.0, 101468.0, 101469.0, 101470.0, 101471.0, 101472.0]],      [[101473.0, 101474.0, 101475.0, 101476.0, 101477.0, 101478.0],       [101479.0, 101480.0, 101481.0, 101482.0, 101483.0, 101484.0],       [101485.0, 101486.0, 101487.0, 101488.0, 101489.0, 101490.0],       [101491.0, 101492.0, 101493.0, 101494.0, 101495.0, 101496.0],       [101497.0, 101498.0, 101499.0, 101500.0, 101501.0, 101502.0],       [101503.0, 101504.0, 101505.0, 101506.0, 101507.0, 101508.0],       [101509.0, 101510.0, 101511.0, 101512.0, 101513.0, 101514.0]],      [[101515.0, 101516.0, 101517.0, 101518.0, 101519.0, 101520.0],       [101521.0, 101522.0, 101523.0, 101524.0, 101525.0, 101526.0],       [101527.0, 101528.0, 101529.0, 101530.0, 101531.0, 101532.0],       [101533.0, 101534.0, 101535.0, 101536.0, 101537.0, 101538.0],       [101539.0, 101540.0, 101541.0, 101542.0, 101543.0, 101544.0],       [101545.0, 101546.0, 101547.0, 101548.0, 101549.0, 101550.0],       [101551.0, 101552.0, 101553.0, 101554.0, 101555.0, 101556.0]]],     [[[101557.0, 101558.0, 101559.0, 101560.0, 101561.0, 101562.0],       [101563.0, 101564.0, 101565.0, 101566.0, 101567.0, 101568.0],       [101569.0, 101570.0, 101571.0, 101572.0, 101573.0, 101574.0],       [101575.0, 101576.0, 101577.0, 101578.0, 101579.0, 101580.0],       [101581.0, 101582.0, 101583.0, 101584.0, 101585.0, 101586.0],       [101587.0, 101588.0, 101589.0, 101590.0, 101591.0, 101592.0],       [101593.0, 101594.0, 101595.0, 101596.0, 101597.0, 101598.0]],      [[101599.0, 101600.0, 101601.0, 101602.0, 101603.0, 101604.0],       [101605.0, 101606.0, 101607.0, 101608.0, 101609.0, 101610.0],       [101611.0, 101612.0, 101613.0, 101614.0, 101615.0, 101616.0],       [101617.0, 101618.0, 101619.0, 101620.0, 101621.0, 101622.0],       [101623.0, 101624.0, 101625.0, 101626.0, 101627.0, 101628.0],       [101629.0, 101630.0, 101631.0, 101632.0, 101633.0, 101634.0],       [101635.0, 101636.0, 101637.0, 101638.0, 101639.0, 101640.0]],      [[101641.0, 101642.0, 101643.0, 101644.0, 101645.0, 101646.0],       [101647.0, 101648.0, 101649.0, 101650.0, 101651.0, 101652.0],       [101653.0, 101654.0, 101655.0, 101656.0, 101657.0, 101658.0],       [101659.0, 101660.0, 101661.0, 101662.0, 101663.0, 101664.0],       [101665.0, 101666.0, 101667.0, 101668.0, 101669.0, 101670.0],       [101671.0, 101672.0, 101673.0, 101674.0, 101675.0, 101676.0],       [101677.0, 101678.0, 101679.0, 101680.0, 101681.0, 101682.0]],      [[101683.0, 101684.0, 101685.0, 101686.0, 101687.0, 101688.0],       [101689.0, 101690.0, 101691.0, 101692.0, 101693.0, 101694.0],       [101695.0, 101696.0, 101697.0, 101698.0, 101699.0, 101700.0],       [101701.0, 101702.0, 101703.0, 101704.0, 101705.0, 101706.0],       [101707.0, 101708.0, 101709.0, 101710.0, 101711.0, 101712.0],       [101713.0, 101714.0, 101715.0, 101716.0, 101717.0, 101718.0],       [101719.0, 101720.0, 101721.0, 101722.0, 101723.0, 101724.0]],      [[101725.0, 101726.0, 101727.0, 101728.0, 101729.0, 101730.0],       [101731.0, 101732.0, 101733.0, 101734.0, 101735.0, 101736.0],       [101737.0, 101738.0, 101739.0, 101740.0, 101741.0, 101742.0],       [101743.0, 101744.0, 101745.0, 101746.0, 101747.0, 101748.0],       [101749.0, 101750.0, 101751.0, 101752.0, 101753.0, 101754.0],       [101755.0, 101756.0, 101757.0, 101758.0, 101759.0, 101760.0],       [101761.0, 101762.0, 101763.0, 101764.0, 101765.0, 101766.0]],      [[101767.0, 101768.0, 101769.0, 101770.0, 101771.0, 101772.0],       [101773.0, 101774.0, 101775.0, 101776.0, 101777.0, 101778.0],       [101779.0, 101780.0, 101781.0, 101782.0, 101783.0, 101784.0],       [101785.0, 101786.0, 101787.0, 101788.0, 101789.0, 101790.0],       [101791.0, 101792.0, 101793.0, 101794.0, 101795.0, 101796.0],       [101797.0, 101798.0, 101799.0, 101800.0, 101801.0, 101802.0],       [101803.0, 101804.0, 101805.0, 101806.0, 101807.0, 101808.0]]]],    [[[[101809.0, 101810.0, 101811.0, 101812.0, 101813.0, 101814.0],       [101815.0, 101816.0, 101817.0, 101818.0, 101819.0, 101820.0],       [101821.0, 101822.0, 101823.0, 101824.0, 101825.0, 101826.0],       [101827.0, 101828.0, 101829.0, 101830.0, 101831.0, 101832.0],       [101833.0, 101834.0, 101835.0, 101836.0, 101837.0, 101838.0],       [101839.0, 101840.0, 101841.0, 101842.0, 101843.0, 101844.0],       [101845.0, 101846.0, 101847.0, 101848.0, 101849.0, 101850.0]],      [[101851.0, 101852.0, 101853.0, 101854.0, 101855.0, 101856.0],       [101857.0, 101858.0, 101859.0, 101860.0, 101861.0, 101862.0],       [101863.0, 101864.0, 101865.0, 101866.0, 101867.0, 101868.0],       [101869.0, 101870.0, 101871.0, 101872.0, 101873.0, 101874.0],       [101875.0, 101876.0, 101877.0, 101878.0, 101879.0, 101880.0],       [101881.0, 101882.0, 101883.0, 101884.0, 101885.0, 101886.0],       [101887.0, 101888.0, 101889.0, 101890.0, 101891.0, 101892.0]],      [[101893.0, 101894.0, 101895.0, 101896.0, 101897.0, 101898.0],       [101899.0, 101900.0, 101901.0, 101902.0, 101903.0, 101904.0],       [101905.0, 101906.0, 101907.0, 101908.0, 101909.0, 101910.0],       [101911.0, 101912.0, 101913.0, 101914.0, 101915.0, 101916.0],       [101917.0, 101918.0, 101919.0, 101920.0, 101921.0, 101922.0],       [101923.0, 101924.0, 101925.0, 101926.0, 101927.0, 101928.0],       [101929.0, 101930.0, 101931.0, 101932.0, 101933.0, 101934.0]],      [[101935.0, 101936.0, 101937.0, 101938.0, 101939.0, 101940.0],       [101941.0, 101942.0, 101943.0, 101944.0, 101945.0, 101946.0],       [101947.0, 101948.0, 101949.0, 101950.0, 101951.0, 101952.0],       [101953.0, 101954.0, 101955.0, 101956.0, 101957.0, 101958.0],       [101959.0, 101960.0, 101961.0, 101962.0, 101963.0, 101964.0],       [101965.0, 101966.0, 101967.0, 101968.0, 101969.0, 101970.0],       [101971.0, 101972.0, 101973.0, 101974.0, 101975.0, 101976.0]],      [[101977.0, 101978.0, 101979.0, 101980.0, 101981.0, 101982.0],       [101983.0, 101984.0, 101985.0, 101986.0, 101987.0, 101988.0],       [101989.0, 101990.0, 101991.0, 101992.0, 101993.0, 101994.0],       [101995.0, 101996.0, 101997.0, 101998.0, 101999.0, 102000.0],       [102001.0, 102002.0, 102003.0, 102004.0, 102005.0, 102006.0],       [102007.0, 102008.0, 102009.0, 102010.0, 102011.0, 102012.0],       [102013.0, 102014.0, 102015.0, 102016.0, 102017.0, 102018.0]],      [[102019.0, 102020.0, 102021.0, 102022.0, 102023.0, 102024.0],       [102025.0, 102026.0, 102027.0, 102028.0, 102029.0, 102030.0],       [102031.0, 102032.0, 102033.0, 102034.0, 102035.0, 102036.0],       [102037.0, 102038.0, 102039.0, 102040.0, 102041.0, 102042.0],       [102043.0, 102044.0, 102045.0, 102046.0, 102047.0, 102048.0],       [102049.0, 102050.0, 102051.0, 102052.0, 102053.0, 102054.0],       [102055.0, 102056.0, 102057.0, 102058.0, 102059.0, 102060.0]]],     [[[102061.0, 102062.0, 102063.0, 102064.0, 102065.0, 102066.0],       [102067.0, 102068.0, 102069.0, 102070.0, 102071.0, 102072.0],       [102073.0, 102074.0, 102075.0, 102076.0, 102077.0, 102078.0],       [102079.0, 102080.0, 102081.0, 102082.0, 102083.0, 102084.0],       [102085.0, 102086.0, 102087.0, 102088.0, 102089.0, 102090.0],       [102091.0, 102092.0, 102093.0, 102094.0, 102095.0, 102096.0],       [102097.0, 102098.0, 102099.0, 102100.0, 102101.0, 102102.0]],      [[102103.0, 102104.0, 102105.0, 102106.0, 102107.0, 102108.0],       [102109.0, 102110.0, 102111.0, 102112.0, 102113.0, 102114.0],       [102115.0, 102116.0, 102117.0, 102118.0, 102119.0, 102120.0],       [102121.0, 102122.0, 102123.0, 102124.0, 102125.0, 102126.0],       [102127.0, 102128.0, 102129.0, 102130.0, 102131.0, 102132.0],       [102133.0, 102134.0, 102135.0, 102136.0, 102137.0, 102138.0],       [102139.0, 102140.0, 102141.0, 102142.0, 102143.0, 102144.0]],      [[102145.0, 102146.0, 102147.0, 102148.0, 102149.0, 102150.0],       [102151.0, 102152.0, 102153.0, 102154.0, 102155.0, 102156.0],       [102157.0, 102158.0, 102159.0, 102160.0, 102161.0, 102162.0],       [102163.0, 102164.0, 102165.0, 102166.0, 102167.0, 102168.0],       [102169.0, 102170.0, 102171.0, 102172.0, 102173.0, 102174.0],       [102175.0, 102176.0, 102177.0, 102178.0, 102179.0, 102180.0],       [102181.0, 102182.0, 102183.0, 102184.0, 102185.0, 102186.0]],      [[102187.0, 102188.0, 102189.0, 102190.0, 102191.0, 102192.0],       [102193.0, 102194.0, 102195.0, 102196.0, 102197.0, 102198.0],       [102199.0, 102200.0, 102201.0, 102202.0, 102203.0, 102204.0],       [102205.0, 102206.0, 102207.0, 102208.0, 102209.0, 102210.0],       [102211.0, 102212.0, 102213.0, 102214.0, 102215.0, 102216.0],       [102217.0, 102218.0, 102219.0, 102220.0, 102221.0, 102222.0],       [102223.0, 102224.0, 102225.0, 102226.0, 102227.0, 102228.0]],      [[102229.0, 102230.0, 102231.0, 102232.0, 102233.0, 102234.0],       [102235.0, 102236.0, 102237.0, 102238.0, 102239.0, 102240.0],       [102241.0, 102242.0, 102243.0, 102244.0, 102245.0, 102246.0],       [102247.0, 102248.0, 102249.0, 102250.0, 102251.0, 102252.0],       [102253.0, 102254.0, 102255.0, 102256.0, 102257.0, 102258.0],       [102259.0, 102260.0, 102261.0, 102262.0, 102263.0, 102264.0],       [102265.0, 102266.0, 102267.0, 102268.0, 102269.0, 102270.0]],      [[102271.0, 102272.0, 102273.0, 102274.0, 102275.0, 102276.0],       [102277.0, 102278.0, 102279.0, 102280.0, 102281.0, 102282.0],       [102283.0, 102284.0, 102285.0, 102286.0, 102287.0, 102288.0],       [102289.0, 102290.0, 102291.0, 102292.0, 102293.0, 102294.0],       [102295.0, 102296.0, 102297.0, 102298.0, 102299.0, 102300.0],       [102301.0, 102302.0, 102303.0, 102304.0, 102305.0, 102306.0],       [102307.0, 102308.0, 102309.0, 102310.0, 102311.0, 102312.0]]],     [[[102313.0, 102314.0, 102315.0, 102316.0, 102317.0, 102318.0],       [102319.0, 102320.0, 102321.0, 102322.0, 102323.0, 102324.0],       [102325.0, 102326.0, 102327.0, 102328.0, 102329.0, 102330.0],       [102331.0, 102332.0, 102333.0, 102334.0, 102335.0, 102336.0],       [102337.0, 102338.0, 102339.0, 102340.0, 102341.0, 102342.0],       [102343.0, 102344.0, 102345.0, 102346.0, 102347.0, 102348.0],       [102349.0, 102350.0, 102351.0, 102352.0, 102353.0, 102354.0]],      [[102355.0, 102356.0, 102357.0, 102358.0, 102359.0, 102360.0],       [102361.0, 102362.0, 102363.0, 102364.0, 102365.0, 102366.0],       [102367.0, 102368.0, 102369.0, 102370.0, 102371.0, 102372.0],       [102373.0, 102374.0, 102375.0, 102376.0, 102377.0, 102378.0],       [102379.0, 102380.0, 102381.0, 102382.0, 102383.0, 102384.0],       [102385.0, 102386.0, 102387.0, 102388.0, 102389.0, 102390.0],       [102391.0, 102392.0, 102393.0, 102394.0, 102395.0, 102396.0]],      [[102397.0, 102398.0, 102399.0, 102400.0, 102401.0, 102402.0],       [102403.0, 102404.0, 102405.0, 102406.0, 102407.0, 102408.0],       [102409.0, 102410.0, 102411.0, 102412.0, 102413.0, 102414.0],       [102415.0, 102416.0, 102417.0, 102418.0, 102419.0, 102420.0],       [102421.0, 102422.0, 102423.0, 102424.0, 102425.0, 102426.0],       [102427.0, 102428.0, 102429.0, 102430.0, 102431.0, 102432.0],       [102433.0, 102434.0, 102435.0, 102436.0, 102437.0, 102438.0]],      [[102439.0, 102440.0, 102441.0, 102442.0, 102443.0, 102444.0],       [102445.0, 102446.0, 102447.0, 102448.0, 102449.0, 102450.0],       [102451.0, 102452.0, 102453.0, 102454.0, 102455.0, 102456.0],       [102457.0, 102458.0, 102459.0, 102460.0, 102461.0, 102462.0],       [102463.0, 102464.0, 102465.0, 102466.0, 102467.0, 102468.0],       [102469.0, 102470.0, 102471.0, 102472.0, 102473.0, 102474.0],       [102475.0, 102476.0, 102477.0, 102478.0, 102479.0, 102480.0]],      [[102481.0, 102482.0, 102483.0, 102484.0, 102485.0, 102486.0],       [102487.0, 102488.0, 102489.0, 102490.0, 102491.0, 102492.0],       [102493.0, 102494.0, 102495.0, 102496.0, 102497.0, 102498.0],       [102499.0, 102500.0, 102501.0, 102502.0, 102503.0, 102504.0],       [102505.0, 102506.0, 102507.0, 102508.0, 102509.0, 102510.0],       [102511.0, 102512.0, 102513.0, 102514.0, 102515.0, 102516.0],       [102517.0, 102518.0, 102519.0, 102520.0, 102521.0, 102522.0]],      [[102523.0, 102524.0, 102525.0, 102526.0, 102527.0, 102528.0],       [102529.0, 102530.0, 102531.0, 102532.0, 102533.0, 102534.0],       [102535.0, 102536.0, 102537.0, 102538.0, 102539.0, 102540.0],       [102541.0, 102542.0, 102543.0, 102544.0, 102545.0, 102546.0],       [102547.0, 102548.0, 102549.0, 102550.0, 102551.0, 102552.0],       [102553.0, 102554.0, 102555.0, 102556.0, 102557.0, 102558.0],       [102559.0, 102560.0, 102561.0, 102562.0, 102563.0, 102564.0]]],     [[[102565.0, 102566.0, 102567.0, 102568.0, 102569.0, 102570.0],       [102571.0, 102572.0, 102573.0, 102574.0, 102575.0, 102576.0],       [102577.0, 102578.0, 102579.0, 102580.0, 102581.0, 102582.0],       [102583.0, 102584.0, 102585.0, 102586.0, 102587.0, 102588.0],       [102589.0, 102590.0, 102591.0, 102592.0, 102593.0, 102594.0],       [102595.0, 102596.0, 102597.0, 102598.0, 102599.0, 102600.0],       [102601.0, 102602.0, 102603.0, 102604.0, 102605.0, 102606.0]],      [[102607.0, 102608.0, 102609.0, 102610.0, 102611.0, 102612.0],       [102613.0, 102614.0, 102615.0, 102616.0, 102617.0, 102618.0],       [102619.0, 102620.0, 102621.0, 102622.0, 102623.0, 102624.0],       [102625.0, 102626.0, 102627.0, 102628.0, 102629.0, 102630.0],       [102631.0, 102632.0, 102633.0, 102634.0, 102635.0, 102636.0],       [102637.0, 102638.0, 102639.0, 102640.0, 102641.0, 102642.0],       [102643.0, 102644.0, 102645.0, 102646.0, 102647.0, 102648.0]],      [[102649.0, 102650.0, 102651.0, 102652.0, 102653.0, 102654.0],       [102655.0, 102656.0, 102657.0, 102658.0, 102659.0, 102660.0],       [102661.0, 102662.0, 102663.0, 102664.0, 102665.0, 102666.0],       [102667.0, 102668.0, 102669.0, 102670.0, 102671.0, 102672.0],       [102673.0, 102674.0, 102675.0, 102676.0, 102677.0, 102678.0],       [102679.0, 102680.0, 102681.0, 102682.0, 102683.0, 102684.0],       [102685.0, 102686.0, 102687.0, 102688.0, 102689.0, 102690.0]],      [[102691.0, 102692.0, 102693.0, 102694.0, 102695.0, 102696.0],       [102697.0, 102698.0, 102699.0, 102700.0, 102701.0, 102702.0],       [102703.0, 102704.0, 102705.0, 102706.0, 102707.0, 102708.0],       [102709.0, 102710.0, 102711.0, 102712.0, 102713.0, 102714.0],       [102715.0, 102716.0, 102717.0, 102718.0, 102719.0, 102720.0],       [102721.0, 102722.0, 102723.0, 102724.0, 102725.0, 102726.0],       [102727.0, 102728.0, 102729.0, 102730.0, 102731.0, 102732.0]],      [[102733.0, 102734.0, 102735.0, 102736.0, 102737.0, 102738.0],       [102739.0, 102740.0, 102741.0, 102742.0, 102743.0, 102744.0],       [102745.0, 102746.0, 102747.0, 102748.0, 102749.0, 102750.0],       [102751.0, 102752.0, 102753.0, 102754.0, 102755.0, 102756.0],       [102757.0, 102758.0, 102759.0, 102760.0, 102761.0, 102762.0],       [102763.0, 102764.0, 102765.0, 102766.0, 102767.0, 102768.0],       [102769.0, 102770.0, 102771.0, 102772.0, 102773.0, 102774.0]],      [[102775.0, 102776.0, 102777.0, 102778.0, 102779.0, 102780.0],       [102781.0, 102782.0, 102783.0, 102784.0, 102785.0, 102786.0],       [102787.0, 102788.0, 102789.0, 102790.0, 102791.0, 102792.0],       [102793.0, 102794.0, 102795.0, 102796.0, 102797.0, 102798.0],       [102799.0, 102800.0, 102801.0, 102802.0, 102803.0, 102804.0],       [102805.0, 102806.0, 102807.0, 102808.0, 102809.0, 102810.0],       [102811.0, 102812.0, 102813.0, 102814.0, 102815.0, 102816.0]]]]],   [[[[[102817.0, 102818.0, 102819.0, 102820.0, 102821.0, 102822.0],       [102823.0, 102824.0, 102825.0, 102826.0, 102827.0, 102828.0],       [102829.0, 102830.0, 102831.0, 102832.0, 102833.0, 102834.0],       [102835.0, 102836.0, 102837.0, 102838.0, 102839.0, 102840.0],       [102841.0, 102842.0, 102843.0, 102844.0, 102845.0, 102846.0],       [102847.0, 102848.0, 102849.0, 102850.0, 102851.0, 102852.0],       [102853.0, 102854.0, 102855.0, 102856.0, 102857.0, 102858.0]],      [[102859.0, 102860.0, 102861.0, 102862.0, 102863.0, 102864.0],       [102865.0, 102866.0, 102867.0, 102868.0, 102869.0, 102870.0],       [102871.0, 102872.0, 102873.0, 102874.0, 102875.0, 102876.0],       [102877.0, 102878.0, 102879.0, 102880.0, 102881.0, 102882.0],       [102883.0, 102884.0, 102885.0, 102886.0, 102887.0, 102888.0],       [102889.0, 102890.0, 102891.0, 102892.0, 102893.0, 102894.0],       [102895.0, 102896.0, 102897.0, 102898.0, 102899.0, 102900.0]],      [[102901.0, 102902.0, 102903.0, 102904.0, 102905.0, 102906.0],       [102907.0, 102908.0, 102909.0, 102910.0, 102911.0, 102912.0],       [102913.0, 102914.0, 102915.0, 102916.0, 102917.0, 102918.0],       [102919.0, 102920.0, 102921.0, 102922.0, 102923.0, 102924.0],       [102925.0, 102926.0, 102927.0, 102928.0, 102929.0, 102930.0],       [102931.0, 102932.0, 102933.0, 102934.0, 102935.0, 102936.0],       [102937.0, 102938.0, 102939.0, 102940.0, 102941.0, 102942.0]],      [[102943.0, 102944.0, 102945.0, 102946.0, 102947.0, 102948.0],       [102949.0, 102950.0, 102951.0, 102952.0, 102953.0, 102954.0],       [102955.0, 102956.0, 102957.0, 102958.0, 102959.0, 102960.0],       [102961.0, 102962.0, 102963.0, 102964.0, 102965.0, 102966.0],       [102967.0, 102968.0, 102969.0, 102970.0, 102971.0, 102972.0],       [102973.0, 102974.0, 102975.0, 102976.0, 102977.0, 102978.0],       [102979.0, 102980.0, 102981.0, 102982.0, 102983.0, 102984.0]],      [[102985.0, 102986.0, 102987.0, 102988.0, 102989.0, 102990.0],       [102991.0, 102992.0, 102993.0, 102994.0, 102995.0, 102996.0],       [102997.0, 102998.0, 102999.0, 103000.0, 103001.0, 103002.0],       [103003.0, 103004.0, 103005.0, 103006.0, 103007.0, 103008.0],       [103009.0, 103010.0, 103011.0, 103012.0, 103013.0, 103014.0],       [103015.0, 103016.0, 103017.0, 103018.0, 103019.0, 103020.0],       [103021.0, 103022.0, 103023.0, 103024.0, 103025.0, 103026.0]],      [[103027.0, 103028.0, 103029.0, 103030.0, 103031.0, 103032.0],       [103033.0, 103034.0, 103035.0, 103036.0, 103037.0, 103038.0],       [103039.0, 103040.0, 103041.0, 103042.0, 103043.0, 103044.0],       [103045.0, 103046.0, 103047.0, 103048.0, 103049.0, 103050.0],       [103051.0, 103052.0, 103053.0, 103054.0, 103055.0, 103056.0],       [103057.0, 103058.0, 103059.0, 103060.0, 103061.0, 103062.0],       [103063.0, 103064.0, 103065.0, 103066.0, 103067.0, 103068.0]]],     [[[103069.0, 103070.0, 103071.0, 103072.0, 103073.0, 103074.0],       [103075.0, 103076.0, 103077.0, 103078.0, 103079.0, 103080.0],       [103081.0, 103082.0, 103083.0, 103084.0, 103085.0, 103086.0],       [103087.0, 103088.0, 103089.0, 103090.0, 103091.0, 103092.0],       [103093.0, 103094.0, 103095.0, 103096.0, 103097.0, 103098.0],       [103099.0, 103100.0, 103101.0, 103102.0, 103103.0, 103104.0],       [103105.0, 103106.0, 103107.0, 103108.0, 103109.0, 103110.0]],      [[103111.0, 103112.0, 103113.0, 103114.0, 103115.0, 103116.0],       [103117.0, 103118.0, 103119.0, 103120.0, 103121.0, 103122.0],       [103123.0, 103124.0, 103125.0, 103126.0, 103127.0, 103128.0],       [103129.0, 103130.0, 103131.0, 103132.0, 103133.0, 103134.0],       [103135.0, 103136.0, 103137.0, 103138.0, 103139.0, 103140.0],       [103141.0, 103142.0, 103143.0, 103144.0, 103145.0, 103146.0],       [103147.0, 103148.0, 103149.0, 103150.0, 103151.0, 103152.0]],      [[103153.0, 103154.0, 103155.0, 103156.0, 103157.0, 103158.0],       [103159.0, 103160.0, 103161.0, 103162.0, 103163.0, 103164.0],       [103165.0, 103166.0, 103167.0, 103168.0, 103169.0, 103170.0],       [103171.0, 103172.0, 103173.0, 103174.0, 103175.0, 103176.0],       [103177.0, 103178.0, 103179.0, 103180.0, 103181.0, 103182.0],       [103183.0, 103184.0, 103185.0, 103186.0, 103187.0, 103188.0],       [103189.0, 103190.0, 103191.0, 103192.0, 103193.0, 103194.0]],      [[103195.0, 103196.0, 103197.0, 103198.0, 103199.0, 103200.0],       [103201.0, 103202.0, 103203.0, 103204.0, 103205.0, 103206.0],       [103207.0, 103208.0, 103209.0, 103210.0, 103211.0, 103212.0],       [103213.0, 103214.0, 103215.0, 103216.0, 103217.0, 103218.0],       [103219.0, 103220.0, 103221.0, 103222.0, 103223.0, 103224.0],       [103225.0, 103226.0, 103227.0, 103228.0, 103229.0, 103230.0],       [103231.0, 103232.0, 103233.0, 103234.0, 103235.0, 103236.0]],      [[103237.0, 103238.0, 103239.0, 103240.0, 103241.0, 103242.0],       [103243.0, 103244.0, 103245.0, 103246.0, 103247.0, 103248.0],       [103249.0, 103250.0, 103251.0, 103252.0, 103253.0, 103254.0],       [103255.0, 103256.0, 103257.0, 103258.0, 103259.0, 103260.0],       [103261.0, 103262.0, 103263.0, 103264.0, 103265.0, 103266.0],       [103267.0, 103268.0, 103269.0, 103270.0, 103271.0, 103272.0],       [103273.0, 103274.0, 103275.0, 103276.0, 103277.0, 103278.0]],      [[103279.0, 103280.0, 103281.0, 103282.0, 103283.0, 103284.0],       [103285.0, 103286.0, 103287.0, 103288.0, 103289.0, 103290.0],       [103291.0, 103292.0, 103293.0, 103294.0, 103295.0, 103296.0],       [103297.0, 103298.0, 103299.0, 103300.0, 103301.0, 103302.0],       [103303.0, 103304.0, 103305.0, 103306.0, 103307.0, 103308.0],       [103309.0, 103310.0, 103311.0, 103312.0, 103313.0, 103314.0],       [103315.0, 103316.0, 103317.0, 103318.0, 103319.0, 103320.0]]],     [[[103321.0, 103322.0, 103323.0, 103324.0, 103325.0, 103326.0],       [103327.0, 103328.0, 103329.0, 103330.0, 103331.0, 103332.0],       [103333.0, 103334.0, 103335.0, 103336.0, 103337.0, 103338.0],       [103339.0, 103340.0, 103341.0, 103342.0, 103343.0, 103344.0],       [103345.0, 103346.0, 103347.0, 103348.0, 103349.0, 103350.0],       [103351.0, 103352.0, 103353.0, 103354.0, 103355.0, 103356.0],       [103357.0, 103358.0, 103359.0, 103360.0, 103361.0, 103362.0]],      [[103363.0, 103364.0, 103365.0, 103366.0, 103367.0, 103368.0],       [103369.0, 103370.0, 103371.0, 103372.0, 103373.0, 103374.0],       [103375.0, 103376.0, 103377.0, 103378.0, 103379.0, 103380.0],       [103381.0, 103382.0, 103383.0, 103384.0, 103385.0, 103386.0],       [103387.0, 103388.0, 103389.0, 103390.0, 103391.0, 103392.0],       [103393.0, 103394.0, 103395.0, 103396.0, 103397.0, 103398.0],       [103399.0, 103400.0, 103401.0, 103402.0, 103403.0, 103404.0]],      [[103405.0, 103406.0, 103407.0, 103408.0, 103409.0, 103410.0],       [103411.0, 103412.0, 103413.0, 103414.0, 103415.0, 103416.0],       [103417.0, 103418.0, 103419.0, 103420.0, 103421.0, 103422.0],       [103423.0, 103424.0, 103425.0, 103426.0, 103427.0, 103428.0],       [103429.0, 103430.0, 103431.0, 103432.0, 103433.0, 103434.0],       [103435.0, 103436.0, 103437.0, 103438.0, 103439.0, 103440.0],       [103441.0, 103442.0, 103443.0, 103444.0, 103445.0, 103446.0]],      [[103447.0, 103448.0, 103449.0, 103450.0, 103451.0, 103452.0],       [103453.0, 103454.0, 103455.0, 103456.0, 103457.0, 103458.0],       [103459.0, 103460.0, 103461.0, 103462.0, 103463.0, 103464.0],       [103465.0, 103466.0, 103467.0, 103468.0, 103469.0, 103470.0],       [103471.0, 103472.0, 103473.0, 103474.0, 103475.0, 103476.0],       [103477.0, 103478.0, 103479.0, 103480.0, 103481.0, 103482.0],       [103483.0, 103484.0, 103485.0, 103486.0, 103487.0, 103488.0]],      [[103489.0, 103490.0, 103491.0, 103492.0, 103493.0, 103494.0],       [103495.0, 103496.0, 103497.0, 103498.0, 103499.0, 103500.0],       [103501.0, 103502.0, 103503.0, 103504.0, 103505.0, 103506.0],       [103507.0, 103508.0, 103509.0, 103510.0, 103511.0, 103512.0],       [103513.0, 103514.0, 103515.0, 103516.0, 103517.0, 103518.0],       [103519.0, 103520.0, 103521.0, 103522.0, 103523.0, 103524.0],       [103525.0, 103526.0, 103527.0, 103528.0, 103529.0, 103530.0]],      [[103531.0, 103532.0, 103533.0, 103534.0, 103535.0, 103536.0],       [103537.0, 103538.0, 103539.0, 103540.0, 103541.0, 103542.0],       [103543.0, 103544.0, 103545.0, 103546.0, 103547.0, 103548.0],       [103549.0, 103550.0, 103551.0, 103552.0, 103553.0, 103554.0],       [103555.0, 103556.0, 103557.0, 103558.0, 103559.0, 103560.0],       [103561.0, 103562.0, 103563.0, 103564.0, 103565.0, 103566.0],       [103567.0, 103568.0, 103569.0, 103570.0, 103571.0, 103572.0]]],     [[[103573.0, 103574.0, 103575.0, 103576.0, 103577.0, 103578.0],       [103579.0, 103580.0, 103581.0, 103582.0, 103583.0, 103584.0],       [103585.0, 103586.0, 103587.0, 103588.0, 103589.0, 103590.0],       [103591.0, 103592.0, 103593.0, 103594.0, 103595.0, 103596.0],       [103597.0, 103598.0, 103599.0, 103600.0, 103601.0, 103602.0],       [103603.0, 103604.0, 103605.0, 103606.0, 103607.0, 103608.0],       [103609.0, 103610.0, 103611.0, 103612.0, 103613.0, 103614.0]],      [[103615.0, 103616.0, 103617.0, 103618.0, 103619.0, 103620.0],       [103621.0, 103622.0, 103623.0, 103624.0, 103625.0, 103626.0],       [103627.0, 103628.0, 103629.0, 103630.0, 103631.0, 103632.0],       [103633.0, 103634.0, 103635.0, 103636.0, 103637.0, 103638.0],       [103639.0, 103640.0, 103641.0, 103642.0, 103643.0, 103644.0],       [103645.0, 103646.0, 103647.0, 103648.0, 103649.0, 103650.0],       [103651.0, 103652.0, 103653.0, 103654.0, 103655.0, 103656.0]],      [[103657.0, 103658.0, 103659.0, 103660.0, 103661.0, 103662.0],       [103663.0, 103664.0, 103665.0, 103666.0, 103667.0, 103668.0],       [103669.0, 103670.0, 103671.0, 103672.0, 103673.0, 103674.0],       [103675.0, 103676.0, 103677.0, 103678.0, 103679.0, 103680.0],       [103681.0, 103682.0, 103683.0, 103684.0, 103685.0, 103686.0],       [103687.0, 103688.0, 103689.0, 103690.0, 103691.0, 103692.0],       [103693.0, 103694.0, 103695.0, 103696.0, 103697.0, 103698.0]],      [[103699.0, 103700.0, 103701.0, 103702.0, 103703.0, 103704.0],       [103705.0, 103706.0, 103707.0, 103708.0, 103709.0, 103710.0],       [103711.0, 103712.0, 103713.0, 103714.0, 103715.0, 103716.0],       [103717.0, 103718.0, 103719.0, 103720.0, 103721.0, 103722.0],       [103723.0, 103724.0, 103725.0, 103726.0, 103727.0, 103728.0],       [103729.0, 103730.0, 103731.0, 103732.0, 103733.0, 103734.0],       [103735.0, 103736.0, 103737.0, 103738.0, 103739.0, 103740.0]],      [[103741.0, 103742.0, 103743.0, 103744.0, 103745.0, 103746.0],       [103747.0, 103748.0, 103749.0, 103750.0, 103751.0, 103752.0],       [103753.0, 103754.0, 103755.0, 103756.0, 103757.0, 103758.0],       [103759.0, 103760.0, 103761.0, 103762.0, 103763.0, 103764.0],       [103765.0, 103766.0, 103767.0, 103768.0, 103769.0, 103770.0],       [103771.0, 103772.0, 103773.0, 103774.0, 103775.0, 103776.0],       [103777.0, 103778.0, 103779.0, 103780.0, 103781.0, 103782.0]],      [[103783.0, 103784.0, 103785.0, 103786.0, 103787.0, 103788.0],       [103789.0, 103790.0, 103791.0, 103792.0, 103793.0, 103794.0],       [103795.0, 103796.0, 103797.0, 103798.0, 103799.0, 103800.0],       [103801.0, 103802.0, 103803.0, 103804.0, 103805.0, 103806.0],       [103807.0, 103808.0, 103809.0, 103810.0, 103811.0, 103812.0],       [103813.0, 103814.0, 103815.0, 103816.0, 103817.0, 103818.0],       [103819.0, 103820.0, 103821.0, 103822.0, 103823.0, 103824.0]]]],    [[[[103825.0, 103826.0, 103827.0, 103828.0, 103829.0, 103830.0],       [103831.0, 103832.0, 103833.0, 103834.0, 103835.0, 103836.0],       [103837.0, 103838.0, 103839.0, 103840.0, 103841.0, 103842.0],       [103843.0, 103844.0, 103845.0, 103846.0, 103847.0, 103848.0],       [103849.0, 103850.0, 103851.0, 103852.0, 103853.0, 103854.0],       [103855.0, 103856.0, 103857.0, 103858.0, 103859.0, 103860.0],       [103861.0, 103862.0, 103863.0, 103864.0, 103865.0, 103866.0]],      [[103867.0, 103868.0, 103869.0, 103870.0, 103871.0, 103872.0],       [103873.0, 103874.0, 103875.0, 103876.0, 103877.0, 103878.0],       [103879.0, 103880.0, 103881.0, 103882.0, 103883.0, 103884.0],       [103885.0, 103886.0, 103887.0, 103888.0, 103889.0, 103890.0],       [103891.0, 103892.0, 103893.0, 103894.0, 103895.0, 103896.0],       [103897.0, 103898.0, 103899.0, 103900.0, 103901.0, 103902.0],       [103903.0, 103904.0, 103905.0, 103906.0, 103907.0, 103908.0]],      [[103909.0, 103910.0, 103911.0, 103912.0, 103913.0, 103914.0],       [103915.0, 103916.0, 103917.0, 103918.0, 103919.0, 103920.0],       [103921.0, 103922.0, 103923.0, 103924.0, 103925.0, 103926.0],       [103927.0, 103928.0, 103929.0, 103930.0, 103931.0, 103932.0],       [103933.0, 103934.0, 103935.0, 103936.0, 103937.0, 103938.0],       [103939.0, 103940.0, 103941.0, 103942.0, 103943.0, 103944.0],       [103945.0, 103946.0, 103947.0, 103948.0, 103949.0, 103950.0]],      [[103951.0, 103952.0, 103953.0, 103954.0, 103955.0, 103956.0],       [103957.0, 103958.0, 103959.0, 103960.0, 103961.0, 103962.0],       [103963.0, 103964.0, 103965.0, 103966.0, 103967.0, 103968.0],       [103969.0, 103970.0, 103971.0, 103972.0, 103973.0, 103974.0],       [103975.0, 103976.0, 103977.0, 103978.0, 103979.0, 103980.0],       [103981.0, 103982.0, 103983.0, 103984.0, 103985.0, 103986.0],       [103987.0, 103988.0, 103989.0, 103990.0, 103991.0, 103992.0]],      [[103993.0, 103994.0, 103995.0, 103996.0, 103997.0, 103998.0],       [103999.0, 104000.0, 104001.0, 104002.0, 104003.0, 104004.0],       [104005.0, 104006.0, 104007.0, 104008.0, 104009.0, 104010.0],       [104011.0, 104012.0, 104013.0, 104014.0, 104015.0, 104016.0],       [104017.0, 104018.0, 104019.0, 104020.0, 104021.0, 104022.0],       [104023.0, 104024.0, 104025.0, 104026.0, 104027.0, 104028.0],       [104029.0, 104030.0, 104031.0, 104032.0, 104033.0, 104034.0]],      [[104035.0, 104036.0, 104037.0, 104038.0, 104039.0, 104040.0],       [104041.0, 104042.0, 104043.0, 104044.0, 104045.0, 104046.0],       [104047.0, 104048.0, 104049.0, 104050.0, 104051.0, 104052.0],       [104053.0, 104054.0, 104055.0, 104056.0, 104057.0, 104058.0],       [104059.0, 104060.0, 104061.0, 104062.0, 104063.0, 104064.0],       [104065.0, 104066.0, 104067.0, 104068.0, 104069.0, 104070.0],       [104071.0, 104072.0, 104073.0, 104074.0, 104075.0, 104076.0]]],     [[[104077.0, 104078.0, 104079.0, 104080.0, 104081.0, 104082.0],       [104083.0, 104084.0, 104085.0, 104086.0, 104087.0, 104088.0],       [104089.0, 104090.0, 104091.0, 104092.0, 104093.0, 104094.0],       [104095.0, 104096.0, 104097.0, 104098.0, 104099.0, 104100.0],       [104101.0, 104102.0, 104103.0, 104104.0, 104105.0, 104106.0],       [104107.0, 104108.0, 104109.0, 104110.0, 104111.0, 104112.0],       [104113.0, 104114.0, 104115.0, 104116.0, 104117.0, 104118.0]],      [[104119.0, 104120.0, 104121.0, 104122.0, 104123.0, 104124.0],       [104125.0, 104126.0, 104127.0, 104128.0, 104129.0, 104130.0],       [104131.0, 104132.0, 104133.0, 104134.0, 104135.0, 104136.0],       [104137.0, 104138.0, 104139.0, 104140.0, 104141.0, 104142.0],       [104143.0, 104144.0, 104145.0, 104146.0, 104147.0, 104148.0],       [104149.0, 104150.0, 104151.0, 104152.0, 104153.0, 104154.0],       [104155.0, 104156.0, 104157.0, 104158.0, 104159.0, 104160.0]],      [[104161.0, 104162.0, 104163.0, 104164.0, 104165.0, 104166.0],       [104167.0, 104168.0, 104169.0, 104170.0, 104171.0, 104172.0],       [104173.0, 104174.0, 104175.0, 104176.0, 104177.0, 104178.0],       [104179.0, 104180.0, 104181.0, 104182.0, 104183.0, 104184.0],       [104185.0, 104186.0, 104187.0, 104188.0, 104189.0, 104190.0],       [104191.0, 104192.0, 104193.0, 104194.0, 104195.0, 104196.0],       [104197.0, 104198.0, 104199.0, 104200.0, 104201.0, 104202.0]],      [[104203.0, 104204.0, 104205.0, 104206.0, 104207.0, 104208.0],       [104209.0, 104210.0, 104211.0, 104212.0, 104213.0, 104214.0],       [104215.0, 104216.0, 104217.0, 104218.0, 104219.0, 104220.0],       [104221.0, 104222.0, 104223.0, 104224.0, 104225.0, 104226.0],       [104227.0, 104228.0, 104229.0, 104230.0, 104231.0, 104232.0],       [104233.0, 104234.0, 104235.0, 104236.0, 104237.0, 104238.0],       [104239.0, 104240.0, 104241.0, 104242.0, 104243.0, 104244.0]],      [[104245.0, 104246.0, 104247.0, 104248.0, 104249.0, 104250.0],       [104251.0, 104252.0, 104253.0, 104254.0, 104255.0, 104256.0],       [104257.0, 104258.0, 104259.0, 104260.0, 104261.0, 104262.0],       [104263.0, 104264.0, 104265.0, 104266.0, 104267.0, 104268.0],       [104269.0, 104270.0, 104271.0, 104272.0, 104273.0, 104274.0],       [104275.0, 104276.0, 104277.0, 104278.0, 104279.0, 104280.0],       [104281.0, 104282.0, 104283.0, 104284.0, 104285.0, 104286.0]],      [[104287.0, 104288.0, 104289.0, 104290.0, 104291.0, 104292.0],       [104293.0, 104294.0, 104295.0, 104296.0, 104297.0, 104298.0],       [104299.0, 104300.0, 104301.0, 104302.0, 104303.0, 104304.0],       [104305.0, 104306.0, 104307.0, 104308.0, 104309.0, 104310.0],       [104311.0, 104312.0, 104313.0, 104314.0, 104315.0, 104316.0],       [104317.0, 104318.0, 104319.0, 104320.0, 104321.0, 104322.0],       [104323.0, 104324.0, 104325.0, 104326.0, 104327.0, 104328.0]]],     [[[104329.0, 104330.0, 104331.0, 104332.0, 104333.0, 104334.0],       [104335.0, 104336.0, 104337.0, 104338.0, 104339.0, 104340.0],       [104341.0, 104342.0, 104343.0, 104344.0, 104345.0, 104346.0],       [104347.0, 104348.0, 104349.0, 104350.0, 104351.0, 104352.0],       [104353.0, 104354.0, 104355.0, 104356.0, 104357.0, 104358.0],       [104359.0, 104360.0, 104361.0, 104362.0, 104363.0, 104364.0],       [104365.0, 104366.0, 104367.0, 104368.0, 104369.0, 104370.0]],      [[104371.0, 104372.0, 104373.0, 104374.0, 104375.0, 104376.0],       [104377.0, 104378.0, 104379.0, 104380.0, 104381.0, 104382.0],       [104383.0, 104384.0, 104385.0, 104386.0, 104387.0, 104388.0],       [104389.0, 104390.0, 104391.0, 104392.0, 104393.0, 104394.0],       [104395.0, 104396.0, 104397.0, 104398.0, 104399.0, 104400.0],       [104401.0, 104402.0, 104403.0, 104404.0, 104405.0, 104406.0],       [104407.0, 104408.0, 104409.0, 104410.0, 104411.0, 104412.0]],      [[104413.0, 104414.0, 104415.0, 104416.0, 104417.0, 104418.0],       [104419.0, 104420.0, 104421.0, 104422.0, 104423.0, 104424.0],       [104425.0, 104426.0, 104427.0, 104428.0, 104429.0, 104430.0],       [104431.0, 104432.0, 104433.0, 104434.0, 104435.0, 104436.0],       [104437.0, 104438.0, 104439.0, 104440.0, 104441.0, 104442.0],       [104443.0, 104444.0, 104445.0, 104446.0, 104447.0, 104448.0],       [104449.0, 104450.0, 104451.0, 104452.0, 104453.0, 104454.0]],      [[104455.0, 104456.0, 104457.0, 104458.0, 104459.0, 104460.0],       [104461.0, 104462.0, 104463.0, 104464.0, 104465.0, 104466.0],       [104467.0, 104468.0, 104469.0, 104470.0, 104471.0, 104472.0],       [104473.0, 104474.0, 104475.0, 104476.0, 104477.0, 104478.0],       [104479.0, 104480.0, 104481.0, 104482.0, 104483.0, 104484.0],       [104485.0, 104486.0, 104487.0, 104488.0, 104489.0, 104490.0],       [104491.0, 104492.0, 104493.0, 104494.0, 104495.0, 104496.0]],      [[104497.0, 104498.0, 104499.0, 104500.0, 104501.0, 104502.0],       [104503.0, 104504.0, 104505.0, 104506.0, 104507.0, 104508.0],       [104509.0, 104510.0, 104511.0, 104512.0, 104513.0, 104514.0],       [104515.0, 104516.0, 104517.0, 104518.0, 104519.0, 104520.0],       [104521.0, 104522.0, 104523.0, 104524.0, 104525.0, 104526.0],       [104527.0, 104528.0, 104529.0, 104530.0, 104531.0, 104532.0],       [104533.0, 104534.0, 104535.0, 104536.0, 104537.0, 104538.0]],      [[104539.0, 104540.0, 104541.0, 104542.0, 104543.0, 104544.0],       [104545.0, 104546.0, 104547.0, 104548.0, 104549.0, 104550.0],       [104551.0, 104552.0, 104553.0, 104554.0, 104555.0, 104556.0],       [104557.0, 104558.0, 104559.0, 104560.0, 104561.0, 104562.0],       [104563.0, 104564.0, 104565.0, 104566.0, 104567.0, 104568.0],       [104569.0, 104570.0, 104571.0, 104572.0, 104573.0, 104574.0],       [104575.0, 104576.0, 104577.0, 104578.0, 104579.0, 104580.0]]],     [[[104581.0, 104582.0, 104583.0, 104584.0, 104585.0, 104586.0],       [104587.0, 104588.0, 104589.0, 104590.0, 104591.0, 104592.0],       [104593.0, 104594.0, 104595.0, 104596.0, 104597.0, 104598.0],       [104599.0, 104600.0, 104601.0, 104602.0, 104603.0, 104604.0],       [104605.0, 104606.0, 104607.0, 104608.0, 104609.0, 104610.0],       [104611.0, 104612.0, 104613.0, 104614.0, 104615.0, 104616.0],       [104617.0, 104618.0, 104619.0, 104620.0, 104621.0, 104622.0]],      [[104623.0, 104624.0, 104625.0, 104626.0, 104627.0, 104628.0],       [104629.0, 104630.0, 104631.0, 104632.0, 104633.0, 104634.0],       [104635.0, 104636.0, 104637.0, 104638.0, 104639.0, 104640.0],       [104641.0, 104642.0, 104643.0, 104644.0, 104645.0, 104646.0],       [104647.0, 104648.0, 104649.0, 104650.0, 104651.0, 104652.0],       [104653.0, 104654.0, 104655.0, 104656.0, 104657.0, 104658.0],       [104659.0, 104660.0, 104661.0, 104662.0, 104663.0, 104664.0]],      [[104665.0, 104666.0, 104667.0, 104668.0, 104669.0, 104670.0],       [104671.0, 104672.0, 104673.0, 104674.0, 104675.0, 104676.0],       [104677.0, 104678.0, 104679.0, 104680.0, 104681.0, 104682.0],       [104683.0, 104684.0, 104685.0, 104686.0, 104687.0, 104688.0],       [104689.0, 104690.0, 104691.0, 104692.0, 104693.0, 104694.0],       [104695.0, 104696.0, 104697.0, 104698.0, 104699.0, 104700.0],       [104701.0, 104702.0, 104703.0, 104704.0, 104705.0, 104706.0]],      [[104707.0, 104708.0, 104709.0, 104710.0, 104711.0, 104712.0],       [104713.0, 104714.0, 104715.0, 104716.0, 104717.0, 104718.0],       [104719.0, 104720.0, 104721.0, 104722.0, 104723.0, 104724.0],       [104725.0, 104726.0, 104727.0, 104728.0, 104729.0, 104730.0],       [104731.0, 104732.0, 104733.0, 104734.0, 104735.0, 104736.0],       [104737.0, 104738.0, 104739.0, 104740.0, 104741.0, 104742.0],       [104743.0, 104744.0, 104745.0, 104746.0, 104747.0, 104748.0]],      [[104749.0, 104750.0, 104751.0, 104752.0, 104753.0, 104754.0],       [104755.0, 104756.0, 104757.0, 104758.0, 104759.0, 104760.0],       [104761.0, 104762.0, 104763.0, 104764.0, 104765.0, 104766.0],       [104767.0, 104768.0, 104769.0, 104770.0, 104771.0, 104772.0],       [104773.0, 104774.0, 104775.0, 104776.0, 104777.0, 104778.0],       [104779.0, 104780.0, 104781.0, 104782.0, 104783.0, 104784.0],       [104785.0, 104786.0, 104787.0, 104788.0, 104789.0, 104790.0]],      [[104791.0, 104792.0, 104793.0, 104794.0, 104795.0, 104796.0],       [104797.0, 104798.0, 104799.0, 104800.0, 104801.0, 104802.0],       [104803.0, 104804.0, 104805.0, 104806.0, 104807.0, 104808.0],       [104809.0, 104810.0, 104811.0, 104812.0, 104813.0, 104814.0],       [104815.0, 104816.0, 104817.0, 104818.0, 104819.0, 104820.0],       [104821.0, 104822.0, 104823.0, 104824.0, 104825.0, 104826.0],       [104827.0, 104828.0, 104829.0, 104830.0, 104831.0, 104832.0]]]],    [[[[104833.0, 104834.0, 104835.0, 104836.0, 104837.0, 104838.0],       [104839.0, 104840.0, 104841.0, 104842.0, 104843.0, 104844.0],       [104845.0, 104846.0, 104847.0, 104848.0, 104849.0, 104850.0],       [104851.0, 104852.0, 104853.0, 104854.0, 104855.0, 104856.0],       [104857.0, 104858.0, 104859.0, 104860.0, 104861.0, 104862.0],       [104863.0, 104864.0, 104865.0, 104866.0, 104867.0, 104868.0],       [104869.0, 104870.0, 104871.0, 104872.0, 104873.0, 104874.0]],      [[104875.0, 104876.0, 104877.0, 104878.0, 104879.0, 104880.0],       [104881.0, 104882.0, 104883.0, 104884.0, 104885.0, 104886.0],       [104887.0, 104888.0, 104889.0, 104890.0, 104891.0, 104892.0],       [104893.0, 104894.0, 104895.0, 104896.0, 104897.0, 104898.0],       [104899.0, 104900.0, 104901.0, 104902.0, 104903.0, 104904.0],       [104905.0, 104906.0, 104907.0, 104908.0, 104909.0, 104910.0],       [104911.0, 104912.0, 104913.0, 104914.0, 104915.0, 104916.0]],      [[104917.0, 104918.0, 104919.0, 104920.0, 104921.0, 104922.0],       [104923.0, 104924.0, 104925.0, 104926.0, 104927.0, 104928.0],       [104929.0, 104930.0, 104931.0, 104932.0, 104933.0, 104934.0],       [104935.0, 104936.0, 104937.0, 104938.0, 104939.0, 104940.0],       [104941.0, 104942.0, 104943.0, 104944.0, 104945.0, 104946.0],       [104947.0, 104948.0, 104949.0, 104950.0, 104951.0, 104952.0],       [104953.0, 104954.0, 104955.0, 104956.0, 104957.0, 104958.0]],      [[104959.0, 104960.0, 104961.0, 104962.0, 104963.0, 104964.0],       [104965.0, 104966.0, 104967.0, 104968.0, 104969.0, 104970.0],       [104971.0, 104972.0, 104973.0, 104974.0, 104975.0, 104976.0],       [104977.0, 104978.0, 104979.0, 104980.0, 104981.0, 104982.0],       [104983.0, 104984.0, 104985.0, 104986.0, 104987.0, 104988.0],       [104989.0, 104990.0, 104991.0, 104992.0, 104993.0, 104994.0],       [104995.0, 104996.0, 104997.0, 104998.0, 104999.0, 105000.0]],      [[105001.0, 105002.0, 105003.0, 105004.0, 105005.0, 105006.0],       [105007.0, 105008.0, 105009.0, 105010.0, 105011.0, 105012.0],       [105013.0, 105014.0, 105015.0, 105016.0, 105017.0, 105018.0],       [105019.0, 105020.0, 105021.0, 105022.0, 105023.0, 105024.0],       [105025.0, 105026.0, 105027.0, 105028.0, 105029.0, 105030.0],       [105031.0, 105032.0, 105033.0, 105034.0, 105035.0, 105036.0],       [105037.0, 105038.0, 105039.0, 105040.0, 105041.0, 105042.0]],      [[105043.0, 105044.0, 105045.0, 105046.0, 105047.0, 105048.0],       [105049.0, 105050.0, 105051.0, 105052.0, 105053.0, 105054.0],       [105055.0, 105056.0, 105057.0, 105058.0, 105059.0, 105060.0],       [105061.0, 105062.0, 105063.0, 105064.0, 105065.0, 105066.0],       [105067.0, 105068.0, 105069.0, 105070.0, 105071.0, 105072.0],       [105073.0, 105074.0, 105075.0, 105076.0, 105077.0, 105078.0],       [105079.0, 105080.0, 105081.0, 105082.0, 105083.0, 105084.0]]],     [[[105085.0, 105086.0, 105087.0, 105088.0, 105089.0, 105090.0],       [105091.0, 105092.0, 105093.0, 105094.0, 105095.0, 105096.0],       [105097.0, 105098.0, 105099.0, 105100.0, 105101.0, 105102.0],       [105103.0, 105104.0, 105105.0, 105106.0, 105107.0, 105108.0],       [105109.0, 105110.0, 105111.0, 105112.0, 105113.0, 105114.0],       [105115.0, 105116.0, 105117.0, 105118.0, 105119.0, 105120.0],       [105121.0, 105122.0, 105123.0, 105124.0, 105125.0, 105126.0]],      [[105127.0, 105128.0, 105129.0, 105130.0, 105131.0, 105132.0],       [105133.0, 105134.0, 105135.0, 105136.0, 105137.0, 105138.0],       [105139.0, 105140.0, 105141.0, 105142.0, 105143.0, 105144.0],       [105145.0, 105146.0, 105147.0, 105148.0, 105149.0, 105150.0],       [105151.0, 105152.0, 105153.0, 105154.0, 105155.0, 105156.0],       [105157.0, 105158.0, 105159.0, 105160.0, 105161.0, 105162.0],       [105163.0, 105164.0, 105165.0, 105166.0, 105167.0, 105168.0]],      [[105169.0, 105170.0, 105171.0, 105172.0, 105173.0, 105174.0],       [105175.0, 105176.0, 105177.0, 105178.0, 105179.0, 105180.0],       [105181.0, 105182.0, 105183.0, 105184.0, 105185.0, 105186.0],       [105187.0, 105188.0, 105189.0, 105190.0, 105191.0, 105192.0],       [105193.0, 105194.0, 105195.0, 105196.0, 105197.0, 105198.0],       [105199.0, 105200.0, 105201.0, 105202.0, 105203.0, 105204.0],       [105205.0, 105206.0, 105207.0, 105208.0, 105209.0, 105210.0]],      [[105211.0, 105212.0, 105213.0, 105214.0, 105215.0, 105216.0],       [105217.0, 105218.0, 105219.0, 105220.0, 105221.0, 105222.0],       [105223.0, 105224.0, 105225.0, 105226.0, 105227.0, 105228.0],       [105229.0, 105230.0, 105231.0, 105232.0, 105233.0, 105234.0],       [105235.0, 105236.0, 105237.0, 105238.0, 105239.0, 105240.0],       [105241.0, 105242.0, 105243.0, 105244.0, 105245.0, 105246.0],       [105247.0, 105248.0, 105249.0, 105250.0, 105251.0, 105252.0]],      [[105253.0, 105254.0, 105255.0, 105256.0, 105257.0, 105258.0],       [105259.0, 105260.0, 105261.0, 105262.0, 105263.0, 105264.0],       [105265.0, 105266.0, 105267.0, 105268.0, 105269.0, 105270.0],       [105271.0, 105272.0, 105273.0, 105274.0, 105275.0, 105276.0],       [105277.0, 105278.0, 105279.0, 105280.0, 105281.0, 105282.0],       [105283.0, 105284.0, 105285.0, 105286.0, 105287.0, 105288.0],       [105289.0, 105290.0, 105291.0, 105292.0, 105293.0, 105294.0]],      [[105295.0, 105296.0, 105297.0, 105298.0, 105299.0, 105300.0],       [105301.0, 105302.0, 105303.0, 105304.0, 105305.0, 105306.0],       [105307.0, 105308.0, 105309.0, 105310.0, 105311.0, 105312.0],       [105313.0, 105314.0, 105315.0, 105316.0, 105317.0, 105318.0],       [105319.0, 105320.0, 105321.0, 105322.0, 105323.0, 105324.0],       [105325.0, 105326.0, 105327.0, 105328.0, 105329.0, 105330.0],       [105331.0, 105332.0, 105333.0, 105334.0, 105335.0, 105336.0]]],     [[[105337.0, 105338.0, 105339.0, 105340.0, 105341.0, 105342.0],       [105343.0, 105344.0, 105345.0, 105346.0, 105347.0, 105348.0],       [105349.0, 105350.0, 105351.0, 105352.0, 105353.0, 105354.0],       [105355.0, 105356.0, 105357.0, 105358.0, 105359.0, 105360.0],       [105361.0, 105362.0, 105363.0, 105364.0, 105365.0, 105366.0],       [105367.0, 105368.0, 105369.0, 105370.0, 105371.0, 105372.0],       [105373.0, 105374.0, 105375.0, 105376.0, 105377.0, 105378.0]],      [[105379.0, 105380.0, 105381.0, 105382.0, 105383.0, 105384.0],       [105385.0, 105386.0, 105387.0, 105388.0, 105389.0, 105390.0],       [105391.0, 105392.0, 105393.0, 105394.0, 105395.0, 105396.0],       [105397.0, 105398.0, 105399.0, 105400.0, 105401.0, 105402.0],       [105403.0, 105404.0, 105405.0, 105406.0, 105407.0, 105408.0],       [105409.0, 105410.0, 105411.0, 105412.0, 105413.0, 105414.0],       [105415.0, 105416.0, 105417.0, 105418.0, 105419.0, 105420.0]],      [[105421.0, 105422.0, 105423.0, 105424.0, 105425.0, 105426.0],       [105427.0, 105428.0, 105429.0, 105430.0, 105431.0, 105432.0],       [105433.0, 105434.0, 105435.0, 105436.0, 105437.0, 105438.0],       [105439.0, 105440.0, 105441.0, 105442.0, 105443.0, 105444.0],       [105445.0, 105446.0, 105447.0, 105448.0, 105449.0, 105450.0],       [105451.0, 105452.0, 105453.0, 105454.0, 105455.0, 105456.0],       [105457.0, 105458.0, 105459.0, 105460.0, 105461.0, 105462.0]],      [[105463.0, 105464.0, 105465.0, 105466.0, 105467.0, 105468.0],       [105469.0, 105470.0, 105471.0, 105472.0, 105473.0, 105474.0],       [105475.0, 105476.0, 105477.0, 105478.0, 105479.0, 105480.0],       [105481.0, 105482.0, 105483.0, 105484.0, 105485.0, 105486.0],       [105487.0, 105488.0, 105489.0, 105490.0, 105491.0, 105492.0],       [105493.0, 105494.0, 105495.0, 105496.0, 105497.0, 105498.0],       [105499.0, 105500.0, 105501.0, 105502.0, 105503.0, 105504.0]],      [[105505.0, 105506.0, 105507.0, 105508.0, 105509.0, 105510.0],       [105511.0, 105512.0, 105513.0, 105514.0, 105515.0, 105516.0],       [105517.0, 105518.0, 105519.0, 105520.0, 105521.0, 105522.0],       [105523.0, 105524.0, 105525.0, 105526.0, 105527.0, 105528.0],       [105529.0, 105530.0, 105531.0, 105532.0, 105533.0, 105534.0],       [105535.0, 105536.0, 105537.0, 105538.0, 105539.0, 105540.0],       [105541.0, 105542.0, 105543.0, 105544.0, 105545.0, 105546.0]],      [[105547.0, 105548.0, 105549.0, 105550.0, 105551.0, 105552.0],       [105553.0, 105554.0, 105555.0, 105556.0, 105557.0, 105558.0],       [105559.0, 105560.0, 105561.0, 105562.0, 105563.0, 105564.0],       [105565.0, 105566.0, 105567.0, 105568.0, 105569.0, 105570.0],       [105571.0, 105572.0, 105573.0, 105574.0, 105575.0, 105576.0],       [105577.0, 105578.0, 105579.0, 105580.0, 105581.0, 105582.0],       [105583.0, 105584.0, 105585.0, 105586.0, 105587.0, 105588.0]]],     [[[105589.0, 105590.0, 105591.0, 105592.0, 105593.0, 105594.0],       [105595.0, 105596.0, 105597.0, 105598.0, 105599.0, 105600.0],       [105601.0, 105602.0, 105603.0, 105604.0, 105605.0, 105606.0],       [105607.0, 105608.0, 105609.0, 105610.0, 105611.0, 105612.0],       [105613.0, 105614.0, 105615.0, 105616.0, 105617.0, 105618.0],       [105619.0, 105620.0, 105621.0, 105622.0, 105623.0, 105624.0],       [105625.0, 105626.0, 105627.0, 105628.0, 105629.0, 105630.0]],      [[105631.0, 105632.0, 105633.0, 105634.0, 105635.0, 105636.0],       [105637.0, 105638.0, 105639.0, 105640.0, 105641.0, 105642.0],       [105643.0, 105644.0, 105645.0, 105646.0, 105647.0, 105648.0],       [105649.0, 105650.0, 105651.0, 105652.0, 105653.0, 105654.0],       [105655.0, 105656.0, 105657.0, 105658.0, 105659.0, 105660.0],       [105661.0, 105662.0, 105663.0, 105664.0, 105665.0, 105666.0],       [105667.0, 105668.0, 105669.0, 105670.0, 105671.0, 105672.0]],      [[105673.0, 105674.0, 105675.0, 105676.0, 105677.0, 105678.0],       [105679.0, 105680.0, 105681.0, 105682.0, 105683.0, 105684.0],       [105685.0, 105686.0, 105687.0, 105688.0, 105689.0, 105690.0],       [105691.0, 105692.0, 105693.0, 105694.0, 105695.0, 105696.0],       [105697.0, 105698.0, 105699.0, 105700.0, 105701.0, 105702.0],       [105703.0, 105704.0, 105705.0, 105706.0, 105707.0, 105708.0],       [105709.0, 105710.0, 105711.0, 105712.0, 105713.0, 105714.0]],      [[105715.0, 105716.0, 105717.0, 105718.0, 105719.0, 105720.0],       [105721.0, 105722.0, 105723.0, 105724.0, 105725.0, 105726.0],       [105727.0, 105728.0, 105729.0, 105730.0, 105731.0, 105732.0],       [105733.0, 105734.0, 105735.0, 105736.0, 105737.0, 105738.0],       [105739.0, 105740.0, 105741.0, 105742.0, 105743.0, 105744.0],       [105745.0, 105746.0, 105747.0, 105748.0, 105749.0, 105750.0],       [105751.0, 105752.0, 105753.0, 105754.0, 105755.0, 105756.0]],      [[105757.0, 105758.0, 105759.0, 105760.0, 105761.0, 105762.0],       [105763.0, 105764.0, 105765.0, 105766.0, 105767.0, 105768.0],       [105769.0, 105770.0, 105771.0, 105772.0, 105773.0, 105774.0],       [105775.0, 105776.0, 105777.0, 105778.0, 105779.0, 105780.0],       [105781.0, 105782.0, 105783.0, 105784.0, 105785.0, 105786.0],       [105787.0, 105788.0, 105789.0, 105790.0, 105791.0, 105792.0],       [105793.0, 105794.0, 105795.0, 105796.0, 105797.0, 105798.0]],      [[105799.0, 105800.0, 105801.0, 105802.0, 105803.0, 105804.0],       [105805.0, 105806.0, 105807.0, 105808.0, 105809.0, 105810.0],       [105811.0, 105812.0, 105813.0, 105814.0, 105815.0, 105816.0],       [105817.0, 105818.0, 105819.0, 105820.0, 105821.0, 105822.0],       [105823.0, 105824.0, 105825.0, 105826.0, 105827.0, 105828.0],       [105829.0, 105830.0, 105831.0, 105832.0, 105833.0, 105834.0],       [105835.0, 105836.0, 105837.0, 105838.0, 105839.0, 105840.0]]]],    [[[[105841.0, 105842.0, 105843.0, 105844.0, 105845.0, 105846.0],       [105847.0, 105848.0, 105849.0, 105850.0, 105851.0, 105852.0],       [105853.0, 105854.0, 105855.0, 105856.0, 105857.0, 105858.0],       [105859.0, 105860.0, 105861.0, 105862.0, 105863.0, 105864.0],       [105865.0, 105866.0, 105867.0, 105868.0, 105869.0, 105870.0],       [105871.0, 105872.0, 105873.0, 105874.0, 105875.0, 105876.0],       [105877.0, 105878.0, 105879.0, 105880.0, 105881.0, 105882.0]],      [[105883.0, 105884.0, 105885.0, 105886.0, 105887.0, 105888.0],       [105889.0, 105890.0, 105891.0, 105892.0, 105893.0, 105894.0],       [105895.0, 105896.0, 105897.0, 105898.0, 105899.0, 105900.0],       [105901.0, 105902.0, 105903.0, 105904.0, 105905.0, 105906.0],       [105907.0, 105908.0, 105909.0, 105910.0, 105911.0, 105912.0],       [105913.0, 105914.0, 105915.0, 105916.0, 105917.0, 105918.0],       [105919.0, 105920.0, 105921.0, 105922.0, 105923.0, 105924.0]],      [[105925.0, 105926.0, 105927.0, 105928.0, 105929.0, 105930.0],       [105931.0, 105932.0, 105933.0, 105934.0, 105935.0, 105936.0],       [105937.0, 105938.0, 105939.0, 105940.0, 105941.0, 105942.0],       [105943.0, 105944.0, 105945.0, 105946.0, 105947.0, 105948.0],       [105949.0, 105950.0, 105951.0, 105952.0, 105953.0, 105954.0],       [105955.0, 105956.0, 105957.0, 105958.0, 105959.0, 105960.0],       [105961.0, 105962.0, 105963.0, 105964.0, 105965.0, 105966.0]],      [[105967.0, 105968.0, 105969.0, 105970.0, 105971.0, 105972.0],       [105973.0, 105974.0, 105975.0, 105976.0, 105977.0, 105978.0],       [105979.0, 105980.0, 105981.0, 105982.0, 105983.0, 105984.0],       [105985.0, 105986.0, 105987.0, 105988.0, 105989.0, 105990.0],       [105991.0, 105992.0, 105993.0, 105994.0, 105995.0, 105996.0],       [105997.0, 105998.0, 105999.0, 106000.0, 106001.0, 106002.0],       [106003.0, 106004.0, 106005.0, 106006.0, 106007.0, 106008.0]],      [[106009.0, 106010.0, 106011.0, 106012.0, 106013.0, 106014.0],       [106015.0, 106016.0, 106017.0, 106018.0, 106019.0, 106020.0],       [106021.0, 106022.0, 106023.0, 106024.0, 106025.0, 106026.0],       [106027.0, 106028.0, 106029.0, 106030.0, 106031.0, 106032.0],       [106033.0, 106034.0, 106035.0, 106036.0, 106037.0, 106038.0],       [106039.0, 106040.0, 106041.0, 106042.0, 106043.0, 106044.0],       [106045.0, 106046.0, 106047.0, 106048.0, 106049.0, 106050.0]],      [[106051.0, 106052.0, 106053.0, 106054.0, 106055.0, 106056.0],       [106057.0, 106058.0, 106059.0, 106060.0, 106061.0, 106062.0],       [106063.0, 106064.0, 106065.0, 106066.0, 106067.0, 106068.0],       [106069.0, 106070.0, 106071.0, 106072.0, 106073.0, 106074.0],       [106075.0, 106076.0, 106077.0, 106078.0, 106079.0, 106080.0],       [106081.0, 106082.0, 106083.0, 106084.0, 106085.0, 106086.0],       [106087.0, 106088.0, 106089.0, 106090.0, 106091.0, 106092.0]]],     [[[106093.0, 106094.0, 106095.0, 106096.0, 106097.0, 106098.0],       [106099.0, 106100.0, 106101.0, 106102.0, 106103.0, 106104.0],       [106105.0, 106106.0, 106107.0, 106108.0, 106109.0, 106110.0],       [106111.0, 106112.0, 106113.0, 106114.0, 106115.0, 106116.0],       [106117.0, 106118.0, 106119.0, 106120.0, 106121.0, 106122.0],       [106123.0, 106124.0, 106125.0, 106126.0, 106127.0, 106128.0],       [106129.0, 106130.0, 106131.0, 106132.0, 106133.0, 106134.0]],      [[106135.0, 106136.0, 106137.0, 106138.0, 106139.0, 106140.0],       [106141.0, 106142.0, 106143.0, 106144.0, 106145.0, 106146.0],       [106147.0, 106148.0, 106149.0, 106150.0, 106151.0, 106152.0],       [106153.0, 106154.0, 106155.0, 106156.0, 106157.0, 106158.0],       [106159.0, 106160.0, 106161.0, 106162.0, 106163.0, 106164.0],       [106165.0, 106166.0, 106167.0, 106168.0, 106169.0, 106170.0],       [106171.0, 106172.0, 106173.0, 106174.0, 106175.0, 106176.0]],      [[106177.0, 106178.0, 106179.0, 106180.0, 106181.0, 106182.0],       [106183.0, 106184.0, 106185.0, 106186.0, 106187.0, 106188.0],       [106189.0, 106190.0, 106191.0, 106192.0, 106193.0, 106194.0],       [106195.0, 106196.0, 106197.0, 106198.0, 106199.0, 106200.0],       [106201.0, 106202.0, 106203.0, 106204.0, 106205.0, 106206.0],       [106207.0, 106208.0, 106209.0, 106210.0, 106211.0, 106212.0],       [106213.0, 106214.0, 106215.0, 106216.0, 106217.0, 106218.0]],      [[106219.0, 106220.0, 106221.0, 106222.0, 106223.0, 106224.0],       [106225.0, 106226.0, 106227.0, 106228.0, 106229.0, 106230.0],       [106231.0, 106232.0, 106233.0, 106234.0, 106235.0, 106236.0],       [106237.0, 106238.0, 106239.0, 106240.0, 106241.0, 106242.0],       [106243.0, 106244.0, 106245.0, 106246.0, 106247.0, 106248.0],       [106249.0, 106250.0, 106251.0, 106252.0, 106253.0, 106254.0],       [106255.0, 106256.0, 106257.0, 106258.0, 106259.0, 106260.0]],      [[106261.0, 106262.0, 106263.0, 106264.0, 106265.0, 106266.0],       [106267.0, 106268.0, 106269.0, 106270.0, 106271.0, 106272.0],       [106273.0, 106274.0, 106275.0, 106276.0, 106277.0, 106278.0],       [106279.0, 106280.0, 106281.0, 106282.0, 106283.0, 106284.0],       [106285.0, 106286.0, 106287.0, 106288.0, 106289.0, 106290.0],       [106291.0, 106292.0, 106293.0, 106294.0, 106295.0, 106296.0],       [106297.0, 106298.0, 106299.0, 106300.0, 106301.0, 106302.0]],      [[106303.0, 106304.0, 106305.0, 106306.0, 106307.0, 106308.0],       [106309.0, 106310.0, 106311.0, 106312.0, 106313.0, 106314.0],       [106315.0, 106316.0, 106317.0, 106318.0, 106319.0, 106320.0],       [106321.0, 106322.0, 106323.0, 106324.0, 106325.0, 106326.0],       [106327.0, 106328.0, 106329.0, 106330.0, 106331.0, 106332.0],       [106333.0, 106334.0, 106335.0, 106336.0, 106337.0, 106338.0],       [106339.0, 106340.0, 106341.0, 106342.0, 106343.0, 106344.0]]],     [[[106345.0, 106346.0, 106347.0, 106348.0, 106349.0, 106350.0],       [106351.0, 106352.0, 106353.0, 106354.0, 106355.0, 106356.0],       [106357.0, 106358.0, 106359.0, 106360.0, 106361.0, 106362.0],       [106363.0, 106364.0, 106365.0, 106366.0, 106367.0, 106368.0],       [106369.0, 106370.0, 106371.0, 106372.0, 106373.0, 106374.0],       [106375.0, 106376.0, 106377.0, 106378.0, 106379.0, 106380.0],       [106381.0, 106382.0, 106383.0, 106384.0, 106385.0, 106386.0]],      [[106387.0, 106388.0, 106389.0, 106390.0, 106391.0, 106392.0],       [106393.0, 106394.0, 106395.0, 106396.0, 106397.0, 106398.0],       [106399.0, 106400.0, 106401.0, 106402.0, 106403.0, 106404.0],       [106405.0, 106406.0, 106407.0, 106408.0, 106409.0, 106410.0],       [106411.0, 106412.0, 106413.0, 106414.0, 106415.0, 106416.0],       [106417.0, 106418.0, 106419.0, 106420.0, 106421.0, 106422.0],       [106423.0, 106424.0, 106425.0, 106426.0, 106427.0, 106428.0]],      [[106429.0, 106430.0, 106431.0, 106432.0, 106433.0, 106434.0],       [106435.0, 106436.0, 106437.0, 106438.0, 106439.0, 106440.0],       [106441.0, 106442.0, 106443.0, 106444.0, 106445.0, 106446.0],       [106447.0, 106448.0, 106449.0, 106450.0, 106451.0, 106452.0],       [106453.0, 106454.0, 106455.0, 106456.0, 106457.0, 106458.0],       [106459.0, 106460.0, 106461.0, 106462.0, 106463.0, 106464.0],       [106465.0, 106466.0, 106467.0, 106468.0, 106469.0, 106470.0]],      [[106471.0, 106472.0, 106473.0, 106474.0, 106475.0, 106476.0],       [106477.0, 106478.0, 106479.0, 106480.0, 106481.0, 106482.0],       [106483.0, 106484.0, 106485.0, 106486.0, 106487.0, 106488.0],       [106489.0, 106490.0, 106491.0, 106492.0, 106493.0, 106494.0],       [106495.0, 106496.0, 106497.0, 106498.0, 106499.0, 106500.0],       [106501.0, 106502.0, 106503.0, 106504.0, 106505.0, 106506.0],       [106507.0, 106508.0, 106509.0, 106510.0, 106511.0, 106512.0]],      [[106513.0, 106514.0, 106515.0, 106516.0, 106517.0, 106518.0],       [106519.0, 106520.0, 106521.0, 106522.0, 106523.0, 106524.0],       [106525.0, 106526.0, 106527.0, 106528.0, 106529.0, 106530.0],       [106531.0, 106532.0, 106533.0, 106534.0, 106535.0, 106536.0],       [106537.0, 106538.0, 106539.0, 106540.0, 106541.0, 106542.0],       [106543.0, 106544.0, 106545.0, 106546.0, 106547.0, 106548.0],       [106549.0, 106550.0, 106551.0, 106552.0, 106553.0, 106554.0]],      [[106555.0, 106556.0, 106557.0, 106558.0, 106559.0, 106560.0],       [106561.0, 106562.0, 106563.0, 106564.0, 106565.0, 106566.0],       [106567.0, 106568.0, 106569.0, 106570.0, 106571.0, 106572.0],       [106573.0, 106574.0, 106575.0, 106576.0, 106577.0, 106578.0],       [106579.0, 106580.0, 106581.0, 106582.0, 106583.0, 106584.0],       [106585.0, 106586.0, 106587.0, 106588.0, 106589.0, 106590.0],       [106591.0, 106592.0, 106593.0, 106594.0, 106595.0, 106596.0]]],     [[[106597.0, 106598.0, 106599.0, 106600.0, 106601.0, 106602.0],       [106603.0, 106604.0, 106605.0, 106606.0, 106607.0, 106608.0],       [106609.0, 106610.0, 106611.0, 106612.0, 106613.0, 106614.0],       [106615.0, 106616.0, 106617.0, 106618.0, 106619.0, 106620.0],       [106621.0, 106622.0, 106623.0, 106624.0, 106625.0, 106626.0],       [106627.0, 106628.0, 106629.0, 106630.0, 106631.0, 106632.0],       [106633.0, 106634.0, 106635.0, 106636.0, 106637.0, 106638.0]],      [[106639.0, 106640.0, 106641.0, 106642.0, 106643.0, 106644.0],       [106645.0, 106646.0, 106647.0, 106648.0, 106649.0, 106650.0],       [106651.0, 106652.0, 106653.0, 106654.0, 106655.0, 106656.0],       [106657.0, 106658.0, 106659.0, 106660.0, 106661.0, 106662.0],       [106663.0, 106664.0, 106665.0, 106666.0, 106667.0, 106668.0],       [106669.0, 106670.0, 106671.0, 106672.0, 106673.0, 106674.0],       [106675.0, 106676.0, 106677.0, 106678.0, 106679.0, 106680.0]],      [[106681.0, 106682.0, 106683.0, 106684.0, 106685.0, 106686.0],       [106687.0, 106688.0, 106689.0, 106690.0, 106691.0, 106692.0],       [106693.0, 106694.0, 106695.0, 106696.0, 106697.0, 106698.0],       [106699.0, 106700.0, 106701.0, 106702.0, 106703.0, 106704.0],       [106705.0, 106706.0, 106707.0, 106708.0, 106709.0, 106710.0],       [106711.0, 106712.0, 106713.0, 106714.0, 106715.0, 106716.0],       [106717.0, 106718.0, 106719.0, 106720.0, 106721.0, 106722.0]],      [[106723.0, 106724.0, 106725.0, 106726.0, 106727.0, 106728.0],       [106729.0, 106730.0, 106731.0, 106732.0, 106733.0, 106734.0],       [106735.0, 106736.0, 106737.0, 106738.0, 106739.0, 106740.0],       [106741.0, 106742.0, 106743.0, 106744.0, 106745.0, 106746.0],       [106747.0, 106748.0, 106749.0, 106750.0, 106751.0, 106752.0],       [106753.0, 106754.0, 106755.0, 106756.0, 106757.0, 106758.0],       [106759.0, 106760.0, 106761.0, 106762.0, 106763.0, 106764.0]],      [[106765.0, 106766.0, 106767.0, 106768.0, 106769.0, 106770.0],       [106771.0, 106772.0, 106773.0, 106774.0, 106775.0, 106776.0],       [106777.0, 106778.0, 106779.0, 106780.0, 106781.0, 106782.0],       [106783.0, 106784.0, 106785.0, 106786.0, 106787.0, 106788.0],       [106789.0, 106790.0, 106791.0, 106792.0, 106793.0, 106794.0],       [106795.0, 106796.0, 106797.0, 106798.0, 106799.0, 106800.0],       [106801.0, 106802.0, 106803.0, 106804.0, 106805.0, 106806.0]],      [[106807.0, 106808.0, 106809.0, 106810.0, 106811.0, 106812.0],       [106813.0, 106814.0, 106815.0, 106816.0, 106817.0, 106818.0],       [106819.0, 106820.0, 106821.0, 106822.0, 106823.0, 106824.0],       [106825.0, 106826.0, 106827.0, 106828.0, 106829.0, 106830.0],       [106831.0, 106832.0, 106833.0, 106834.0, 106835.0, 106836.0],       [106837.0, 106838.0, 106839.0, 106840.0, 106841.0, 106842.0],       [106843.0, 106844.0, 106845.0, 106846.0, 106847.0, 106848.0]]]],    [[[[106849.0, 106850.0, 106851.0, 106852.0, 106853.0, 106854.0],       [106855.0, 106856.0, 106857.0, 106858.0, 106859.0, 106860.0],       [106861.0, 106862.0, 106863.0, 106864.0, 106865.0, 106866.0],       [106867.0, 106868.0, 106869.0, 106870.0, 106871.0, 106872.0],       [106873.0, 106874.0, 106875.0, 106876.0, 106877.0, 106878.0],       [106879.0, 106880.0, 106881.0, 106882.0, 106883.0, 106884.0],       [106885.0, 106886.0, 106887.0, 106888.0, 106889.0, 106890.0]],      [[106891.0, 106892.0, 106893.0, 106894.0, 106895.0, 106896.0],       [106897.0, 106898.0, 106899.0, 106900.0, 106901.0, 106902.0],       [106903.0, 106904.0, 106905.0, 106906.0, 106907.0, 106908.0],       [106909.0, 106910.0, 106911.0, 106912.0, 106913.0, 106914.0],       [106915.0, 106916.0, 106917.0, 106918.0, 106919.0, 106920.0],       [106921.0, 106922.0, 106923.0, 106924.0, 106925.0, 106926.0],       [106927.0, 106928.0, 106929.0, 106930.0, 106931.0, 106932.0]],      [[106933.0, 106934.0, 106935.0, 106936.0, 106937.0, 106938.0],       [106939.0, 106940.0, 106941.0, 106942.0, 106943.0, 106944.0],       [106945.0, 106946.0, 106947.0, 106948.0, 106949.0, 106950.0],       [106951.0, 106952.0, 106953.0, 106954.0, 106955.0, 106956.0],       [106957.0, 106958.0, 106959.0, 106960.0, 106961.0, 106962.0],       [106963.0, 106964.0, 106965.0, 106966.0, 106967.0, 106968.0],       [106969.0, 106970.0, 106971.0, 106972.0, 106973.0, 106974.0]],      [[106975.0, 106976.0, 106977.0, 106978.0, 106979.0, 106980.0],       [106981.0, 106982.0, 106983.0, 106984.0, 106985.0, 106986.0],       [106987.0, 106988.0, 106989.0, 106990.0, 106991.0, 106992.0],       [106993.0, 106994.0, 106995.0, 106996.0, 106997.0, 106998.0],       [106999.0, 107000.0, 107001.0, 107002.0, 107003.0, 107004.0],       [107005.0, 107006.0, 107007.0, 107008.0, 107009.0, 107010.0],       [107011.0, 107012.0, 107013.0, 107014.0, 107015.0, 107016.0]],      [[107017.0, 107018.0, 107019.0, 107020.0, 107021.0, 107022.0],       [107023.0, 107024.0, 107025.0, 107026.0, 107027.0, 107028.0],       [107029.0, 107030.0, 107031.0, 107032.0, 107033.0, 107034.0],       [107035.0, 107036.0, 107037.0, 107038.0, 107039.0, 107040.0],       [107041.0, 107042.0, 107043.0, 107044.0, 107045.0, 107046.0],       [107047.0, 107048.0, 107049.0, 107050.0, 107051.0, 107052.0],       [107053.0, 107054.0, 107055.0, 107056.0, 107057.0, 107058.0]],      [[107059.0, 107060.0, 107061.0, 107062.0, 107063.0, 107064.0],       [107065.0, 107066.0, 107067.0, 107068.0, 107069.0, 107070.0],       [107071.0, 107072.0, 107073.0, 107074.0, 107075.0, 107076.0],       [107077.0, 107078.0, 107079.0, 107080.0, 107081.0, 107082.0],       [107083.0, 107084.0, 107085.0, 107086.0, 107087.0, 107088.0],       [107089.0, 107090.0, 107091.0, 107092.0, 107093.0, 107094.0],       [107095.0, 107096.0, 107097.0, 107098.0, 107099.0, 107100.0]]],     [[[107101.0, 107102.0, 107103.0, 107104.0, 107105.0, 107106.0],       [107107.0, 107108.0, 107109.0, 107110.0, 107111.0, 107112.0],       [107113.0, 107114.0, 107115.0, 107116.0, 107117.0, 107118.0],       [107119.0, 107120.0, 107121.0, 107122.0, 107123.0, 107124.0],       [107125.0, 107126.0, 107127.0, 107128.0, 107129.0, 107130.0],       [107131.0, 107132.0, 107133.0, 107134.0, 107135.0, 107136.0],       [107137.0, 107138.0, 107139.0, 107140.0, 107141.0, 107142.0]],      [[107143.0, 107144.0, 107145.0, 107146.0, 107147.0, 107148.0],       [107149.0, 107150.0, 107151.0, 107152.0, 107153.0, 107154.0],       [107155.0, 107156.0, 107157.0, 107158.0, 107159.0, 107160.0],       [107161.0, 107162.0, 107163.0, 107164.0, 107165.0, 107166.0],       [107167.0, 107168.0, 107169.0, 107170.0, 107171.0, 107172.0],       [107173.0, 107174.0, 107175.0, 107176.0, 107177.0, 107178.0],       [107179.0, 107180.0, 107181.0, 107182.0, 107183.0, 107184.0]],      [[107185.0, 107186.0, 107187.0, 107188.0, 107189.0, 107190.0],       [107191.0, 107192.0, 107193.0, 107194.0, 107195.0, 107196.0],       [107197.0, 107198.0, 107199.0, 107200.0, 107201.0, 107202.0],       [107203.0, 107204.0, 107205.0, 107206.0, 107207.0, 107208.0],       [107209.0, 107210.0, 107211.0, 107212.0, 107213.0, 107214.0],       [107215.0, 107216.0, 107217.0, 107218.0, 107219.0, 107220.0],       [107221.0, 107222.0, 107223.0, 107224.0, 107225.0, 107226.0]],      [[107227.0, 107228.0, 107229.0, 107230.0, 107231.0, 107232.0],       [107233.0, 107234.0, 107235.0, 107236.0, 107237.0, 107238.0],       [107239.0, 107240.0, 107241.0, 107242.0, 107243.0, 107244.0],       [107245.0, 107246.0, 107247.0, 107248.0, 107249.0, 107250.0],       [107251.0, 107252.0, 107253.0, 107254.0, 107255.0, 107256.0],       [107257.0, 107258.0, 107259.0, 107260.0, 107261.0, 107262.0],       [107263.0, 107264.0, 107265.0, 107266.0, 107267.0, 107268.0]],      [[107269.0, 107270.0, 107271.0, 107272.0, 107273.0, 107274.0],       [107275.0, 107276.0, 107277.0, 107278.0, 107279.0, 107280.0],       [107281.0, 107282.0, 107283.0, 107284.0, 107285.0, 107286.0],       [107287.0, 107288.0, 107289.0, 107290.0, 107291.0, 107292.0],       [107293.0, 107294.0, 107295.0, 107296.0, 107297.0, 107298.0],       [107299.0, 107300.0, 107301.0, 107302.0, 107303.0, 107304.0],       [107305.0, 107306.0, 107307.0, 107308.0, 107309.0, 107310.0]],      [[107311.0, 107312.0, 107313.0, 107314.0, 107315.0, 107316.0],       [107317.0, 107318.0, 107319.0, 107320.0, 107321.0, 107322.0],       [107323.0, 107324.0, 107325.0, 107326.0, 107327.0, 107328.0],       [107329.0, 107330.0, 107331.0, 107332.0, 107333.0, 107334.0],       [107335.0, 107336.0, 107337.0, 107338.0, 107339.0, 107340.0],       [107341.0, 107342.0, 107343.0, 107344.0, 107345.0, 107346.0],       [107347.0, 107348.0, 107349.0, 107350.0, 107351.0, 107352.0]]],     [[[107353.0, 107354.0, 107355.0, 107356.0, 107357.0, 107358.0],       [107359.0, 107360.0, 107361.0, 107362.0, 107363.0, 107364.0],       [107365.0, 107366.0, 107367.0, 107368.0, 107369.0, 107370.0],       [107371.0, 107372.0, 107373.0, 107374.0, 107375.0, 107376.0],       [107377.0, 107378.0, 107379.0, 107380.0, 107381.0, 107382.0],       [107383.0, 107384.0, 107385.0, 107386.0, 107387.0, 107388.0],       [107389.0, 107390.0, 107391.0, 107392.0, 107393.0, 107394.0]],      [[107395.0, 107396.0, 107397.0, 107398.0, 107399.0, 107400.0],       [107401.0, 107402.0, 107403.0, 107404.0, 107405.0, 107406.0],       [107407.0, 107408.0, 107409.0, 107410.0, 107411.0, 107412.0],       [107413.0, 107414.0, 107415.0, 107416.0, 107417.0, 107418.0],       [107419.0, 107420.0, 107421.0, 107422.0, 107423.0, 107424.0],       [107425.0, 107426.0, 107427.0, 107428.0, 107429.0, 107430.0],       [107431.0, 107432.0, 107433.0, 107434.0, 107435.0, 107436.0]],      [[107437.0, 107438.0, 107439.0, 107440.0, 107441.0, 107442.0],       [107443.0, 107444.0, 107445.0, 107446.0, 107447.0, 107448.0],       [107449.0, 107450.0, 107451.0, 107452.0, 107453.0, 107454.0],       [107455.0, 107456.0, 107457.0, 107458.0, 107459.0, 107460.0],       [107461.0, 107462.0, 107463.0, 107464.0, 107465.0, 107466.0],       [107467.0, 107468.0, 107469.0, 107470.0, 107471.0, 107472.0],       [107473.0, 107474.0, 107475.0, 107476.0, 107477.0, 107478.0]],      [[107479.0, 107480.0, 107481.0, 107482.0, 107483.0, 107484.0],       [107485.0, 107486.0, 107487.0, 107488.0, 107489.0, 107490.0],       [107491.0, 107492.0, 107493.0, 107494.0, 107495.0, 107496.0],       [107497.0, 107498.0, 107499.0, 107500.0, 107501.0, 107502.0],       [107503.0, 107504.0, 107505.0, 107506.0, 107507.0, 107508.0],       [107509.0, 107510.0, 107511.0, 107512.0, 107513.0, 107514.0],       [107515.0, 107516.0, 107517.0, 107518.0, 107519.0, 107520.0]],      [[107521.0, 107522.0, 107523.0, 107524.0, 107525.0, 107526.0],       [107527.0, 107528.0, 107529.0, 107530.0, 107531.0, 107532.0],       [107533.0, 107534.0, 107535.0, 107536.0, 107537.0, 107538.0],       [107539.0, 107540.0, 107541.0, 107542.0, 107543.0, 107544.0],       [107545.0, 107546.0, 107547.0, 107548.0, 107549.0, 107550.0],       [107551.0, 107552.0, 107553.0, 107554.0, 107555.0, 107556.0],       [107557.0, 107558.0, 107559.0, 107560.0, 107561.0, 107562.0]],      [[107563.0, 107564.0, 107565.0, 107566.0, 107567.0, 107568.0],       [107569.0, 107570.0, 107571.0, 107572.0, 107573.0, 107574.0],       [107575.0, 107576.0, 107577.0, 107578.0, 107579.0, 107580.0],       [107581.0, 107582.0, 107583.0, 107584.0, 107585.0, 107586.0],       [107587.0, 107588.0, 107589.0, 107590.0, 107591.0, 107592.0],       [107593.0, 107594.0, 107595.0, 107596.0, 107597.0, 107598.0],       [107599.0, 107600.0, 107601.0, 107602.0, 107603.0, 107604.0]]],     [[[107605.0, 107606.0, 107607.0, 107608.0, 107609.0, 107610.0],       [107611.0, 107612.0, 107613.0, 107614.0, 107615.0, 107616.0],       [107617.0, 107618.0, 107619.0, 107620.0, 107621.0, 107622.0],       [107623.0, 107624.0, 107625.0, 107626.0, 107627.0, 107628.0],       [107629.0, 107630.0, 107631.0, 107632.0, 107633.0, 107634.0],       [107635.0, 107636.0, 107637.0, 107638.0, 107639.0, 107640.0],       [107641.0, 107642.0, 107643.0, 107644.0, 107645.0, 107646.0]],      [[107647.0, 107648.0, 107649.0, 107650.0, 107651.0, 107652.0],       [107653.0, 107654.0, 107655.0, 107656.0, 107657.0, 107658.0],       [107659.0, 107660.0, 107661.0, 107662.0, 107663.0, 107664.0],       [107665.0, 107666.0, 107667.0, 107668.0, 107669.0, 107670.0],       [107671.0, 107672.0, 107673.0, 107674.0, 107675.0, 107676.0],       [107677.0, 107678.0, 107679.0, 107680.0, 107681.0, 107682.0],       [107683.0, 107684.0, 107685.0, 107686.0, 107687.0, 107688.0]],      [[107689.0, 107690.0, 107691.0, 107692.0, 107693.0, 107694.0],       [107695.0, 107696.0, 107697.0, 107698.0, 107699.0, 107700.0],       [107701.0, 107702.0, 107703.0, 107704.0, 107705.0, 107706.0],       [107707.0, 107708.0, 107709.0, 107710.0, 107711.0, 107712.0],       [107713.0, 107714.0, 107715.0, 107716.0, 107717.0, 107718.0],       [107719.0, 107720.0, 107721.0, 107722.0, 107723.0, 107724.0],       [107725.0, 107726.0, 107727.0, 107728.0, 107729.0, 107730.0]],      [[107731.0, 107732.0, 107733.0, 107734.0, 107735.0, 107736.0],       [107737.0, 107738.0, 107739.0, 107740.0, 107741.0, 107742.0],       [107743.0, 107744.0, 107745.0, 107746.0, 107747.0, 107748.0],       [107749.0, 107750.0, 107751.0, 107752.0, 107753.0, 107754.0],       [107755.0, 107756.0, 107757.0, 107758.0, 107759.0, 107760.0],       [107761.0, 107762.0, 107763.0, 107764.0, 107765.0, 107766.0],       [107767.0, 107768.0, 107769.0, 107770.0, 107771.0, 107772.0]],      [[107773.0, 107774.0, 107775.0, 107776.0, 107777.0, 107778.0],       [107779.0, 107780.0, 107781.0, 107782.0, 107783.0, 107784.0],       [107785.0, 107786.0, 107787.0, 107788.0, 107789.0, 107790.0],       [107791.0, 107792.0, 107793.0, 107794.0, 107795.0, 107796.0],       [107797.0, 107798.0, 107799.0, 107800.0, 107801.0, 107802.0],       [107803.0, 107804.0, 107805.0, 107806.0, 107807.0, 107808.0],       [107809.0, 107810.0, 107811.0, 107812.0, 107813.0, 107814.0]],      [[107815.0, 107816.0, 107817.0, 107818.0, 107819.0, 107820.0],       [107821.0, 107822.0, 107823.0, 107824.0, 107825.0, 107826.0],       [107827.0, 107828.0, 107829.0, 107830.0, 107831.0, 107832.0],       [107833.0, 107834.0, 107835.0, 107836.0, 107837.0, 107838.0],       [107839.0, 107840.0, 107841.0, 107842.0, 107843.0, 107844.0],       [107845.0, 107846.0, 107847.0, 107848.0, 107849.0, 107850.0],       [107851.0, 107852.0, 107853.0, 107854.0, 107855.0, 107856.0]]]],    [[[[107857.0, 107858.0, 107859.0, 107860.0, 107861.0, 107862.0],       [107863.0, 107864.0, 107865.0, 107866.0, 107867.0, 107868.0],       [107869.0, 107870.0, 107871.0, 107872.0, 107873.0, 107874.0],       [107875.0, 107876.0, 107877.0, 107878.0, 107879.0, 107880.0],       [107881.0, 107882.0, 107883.0, 107884.0, 107885.0, 107886.0],       [107887.0, 107888.0, 107889.0, 107890.0, 107891.0, 107892.0],       [107893.0, 107894.0, 107895.0, 107896.0, 107897.0, 107898.0]],      [[107899.0, 107900.0, 107901.0, 107902.0, 107903.0, 107904.0],       [107905.0, 107906.0, 107907.0, 107908.0, 107909.0, 107910.0],       [107911.0, 107912.0, 107913.0, 107914.0, 107915.0, 107916.0],       [107917.0, 107918.0, 107919.0, 107920.0, 107921.0, 107922.0],       [107923.0, 107924.0, 107925.0, 107926.0, 107927.0, 107928.0],       [107929.0, 107930.0, 107931.0, 107932.0, 107933.0, 107934.0],       [107935.0, 107936.0, 107937.0, 107938.0, 107939.0, 107940.0]],      [[107941.0, 107942.0, 107943.0, 107944.0, 107945.0, 107946.0],       [107947.0, 107948.0, 107949.0, 107950.0, 107951.0, 107952.0],       [107953.0, 107954.0, 107955.0, 107956.0, 107957.0, 107958.0],       [107959.0, 107960.0, 107961.0, 107962.0, 107963.0, 107964.0],       [107965.0, 107966.0, 107967.0, 107968.0, 107969.0, 107970.0],       [107971.0, 107972.0, 107973.0, 107974.0, 107975.0, 107976.0],       [107977.0, 107978.0, 107979.0, 107980.0, 107981.0, 107982.0]],      [[107983.0, 107984.0, 107985.0, 107986.0, 107987.0, 107988.0],       [107989.0, 107990.0, 107991.0, 107992.0, 107993.0, 107994.0],       [107995.0, 107996.0, 107997.0, 107998.0, 107999.0, 108000.0],       [108001.0, 108002.0, 108003.0, 108004.0, 108005.0, 108006.0],       [108007.0, 108008.0, 108009.0, 108010.0, 108011.0, 108012.0],       [108013.0, 108014.0, 108015.0, 108016.0, 108017.0, 108018.0],       [108019.0, 108020.0, 108021.0, 108022.0, 108023.0, 108024.0]],      [[108025.0, 108026.0, 108027.0, 108028.0, 108029.0, 108030.0],       [108031.0, 108032.0, 108033.0, 108034.0, 108035.0, 108036.0],       [108037.0, 108038.0, 108039.0, 108040.0, 108041.0, 108042.0],       [108043.0, 108044.0, 108045.0, 108046.0, 108047.0, 108048.0],       [108049.0, 108050.0, 108051.0, 108052.0, 108053.0, 108054.0],       [108055.0, 108056.0, 108057.0, 108058.0, 108059.0, 108060.0],       [108061.0, 108062.0, 108063.0, 108064.0, 108065.0, 108066.0]],      [[108067.0, 108068.0, 108069.0, 108070.0, 108071.0, 108072.0],       [108073.0, 108074.0, 108075.0, 108076.0, 108077.0, 108078.0],       [108079.0, 108080.0, 108081.0, 108082.0, 108083.0, 108084.0],       [108085.0, 108086.0, 108087.0, 108088.0, 108089.0, 108090.0],       [108091.0, 108092.0, 108093.0, 108094.0, 108095.0, 108096.0],       [108097.0, 108098.0, 108099.0, 108100.0, 108101.0, 108102.0],       [108103.0, 108104.0, 108105.0, 108106.0, 108107.0, 108108.0]]],     [[[108109.0, 108110.0, 108111.0, 108112.0, 108113.0, 108114.0],       [108115.0, 108116.0, 108117.0, 108118.0, 108119.0, 108120.0],       [108121.0, 108122.0, 108123.0, 108124.0, 108125.0, 108126.0],       [108127.0, 108128.0, 108129.0, 108130.0, 108131.0, 108132.0],       [108133.0, 108134.0, 108135.0, 108136.0, 108137.0, 108138.0],       [108139.0, 108140.0, 108141.0, 108142.0, 108143.0, 108144.0],       [108145.0, 108146.0, 108147.0, 108148.0, 108149.0, 108150.0]],      [[108151.0, 108152.0, 108153.0, 108154.0, 108155.0, 108156.0],       [108157.0, 108158.0, 108159.0, 108160.0, 108161.0, 108162.0],       [108163.0, 108164.0, 108165.0, 108166.0, 108167.0, 108168.0],       [108169.0, 108170.0, 108171.0, 108172.0, 108173.0, 108174.0],       [108175.0, 108176.0, 108177.0, 108178.0, 108179.0, 108180.0],       [108181.0, 108182.0, 108183.0, 108184.0, 108185.0, 108186.0],       [108187.0, 108188.0, 108189.0, 108190.0, 108191.0, 108192.0]],      [[108193.0, 108194.0, 108195.0, 108196.0, 108197.0, 108198.0],       [108199.0, 108200.0, 108201.0, 108202.0, 108203.0, 108204.0],       [108205.0, 108206.0, 108207.0, 108208.0, 108209.0, 108210.0],       [108211.0, 108212.0, 108213.0, 108214.0, 108215.0, 108216.0],       [108217.0, 108218.0, 108219.0, 108220.0, 108221.0, 108222.0],       [108223.0, 108224.0, 108225.0, 108226.0, 108227.0, 108228.0],       [108229.0, 108230.0, 108231.0, 108232.0, 108233.0, 108234.0]],      [[108235.0, 108236.0, 108237.0, 108238.0, 108239.0, 108240.0],       [108241.0, 108242.0, 108243.0, 108244.0, 108245.0, 108246.0],       [108247.0, 108248.0, 108249.0, 108250.0, 108251.0, 108252.0],       [108253.0, 108254.0, 108255.0, 108256.0, 108257.0, 108258.0],       [108259.0, 108260.0, 108261.0, 108262.0, 108263.0, 108264.0],       [108265.0, 108266.0, 108267.0, 108268.0, 108269.0, 108270.0],       [108271.0, 108272.0, 108273.0, 108274.0, 108275.0, 108276.0]],      [[108277.0, 108278.0, 108279.0, 108280.0, 108281.0, 108282.0],       [108283.0, 108284.0, 108285.0, 108286.0, 108287.0, 108288.0],       [108289.0, 108290.0, 108291.0, 108292.0, 108293.0, 108294.0],       [108295.0, 108296.0, 108297.0, 108298.0, 108299.0, 108300.0],       [108301.0, 108302.0, 108303.0, 108304.0, 108305.0, 108306.0],       [108307.0, 108308.0, 108309.0, 108310.0, 108311.0, 108312.0],       [108313.0, 108314.0, 108315.0, 108316.0, 108317.0, 108318.0]],      [[108319.0, 108320.0, 108321.0, 108322.0, 108323.0, 108324.0],       [108325.0, 108326.0, 108327.0, 108328.0, 108329.0, 108330.0],       [108331.0, 108332.0, 108333.0, 108334.0, 108335.0, 108336.0],       [108337.0, 108338.0, 108339.0, 108340.0, 108341.0, 108342.0],       [108343.0, 108344.0, 108345.0, 108346.0, 108347.0, 108348.0],       [108349.0, 108350.0, 108351.0, 108352.0, 108353.0, 108354.0],       [108355.0, 108356.0, 108357.0, 108358.0, 108359.0, 108360.0]]],     [[[108361.0, 108362.0, 108363.0, 108364.0, 108365.0, 108366.0],       [108367.0, 108368.0, 108369.0, 108370.0, 108371.0, 108372.0],       [108373.0, 108374.0, 108375.0, 108376.0, 108377.0, 108378.0],       [108379.0, 108380.0, 108381.0, 108382.0, 108383.0, 108384.0],       [108385.0, 108386.0, 108387.0, 108388.0, 108389.0, 108390.0],       [108391.0, 108392.0, 108393.0, 108394.0, 108395.0, 108396.0],       [108397.0, 108398.0, 108399.0, 108400.0, 108401.0, 108402.0]],      [[108403.0, 108404.0, 108405.0, 108406.0, 108407.0, 108408.0],       [108409.0, 108410.0, 108411.0, 108412.0, 108413.0, 108414.0],       [108415.0, 108416.0, 108417.0, 108418.0, 108419.0, 108420.0],       [108421.0, 108422.0, 108423.0, 108424.0, 108425.0, 108426.0],       [108427.0, 108428.0, 108429.0, 108430.0, 108431.0, 108432.0],       [108433.0, 108434.0, 108435.0, 108436.0, 108437.0, 108438.0],       [108439.0, 108440.0, 108441.0, 108442.0, 108443.0, 108444.0]],      [[108445.0, 108446.0, 108447.0, 108448.0, 108449.0, 108450.0],       [108451.0, 108452.0, 108453.0, 108454.0, 108455.0, 108456.0],       [108457.0, 108458.0, 108459.0, 108460.0, 108461.0, 108462.0],       [108463.0, 108464.0, 108465.0, 108466.0, 108467.0, 108468.0],       [108469.0, 108470.0, 108471.0, 108472.0, 108473.0, 108474.0],       [108475.0, 108476.0, 108477.0, 108478.0, 108479.0, 108480.0],       [108481.0, 108482.0, 108483.0, 108484.0, 108485.0, 108486.0]],      [[108487.0, 108488.0, 108489.0, 108490.0, 108491.0, 108492.0],       [108493.0, 108494.0, 108495.0, 108496.0, 108497.0, 108498.0],       [108499.0, 108500.0, 108501.0, 108502.0, 108503.0, 108504.0],       [108505.0, 108506.0, 108507.0, 108508.0, 108509.0, 108510.0],       [108511.0, 108512.0, 108513.0, 108514.0, 108515.0, 108516.0],       [108517.0, 108518.0, 108519.0, 108520.0, 108521.0, 108522.0],       [108523.0, 108524.0, 108525.0, 108526.0, 108527.0, 108528.0]],      [[108529.0, 108530.0, 108531.0, 108532.0, 108533.0, 108534.0],       [108535.0, 108536.0, 108537.0, 108538.0, 108539.0, 108540.0],       [108541.0, 108542.0, 108543.0, 108544.0, 108545.0, 108546.0],       [108547.0, 108548.0, 108549.0, 108550.0, 108551.0, 108552.0],       [108553.0, 108554.0, 108555.0, 108556.0, 108557.0, 108558.0],       [108559.0, 108560.0, 108561.0, 108562.0, 108563.0, 108564.0],       [108565.0, 108566.0, 108567.0, 108568.0, 108569.0, 108570.0]],      [[108571.0, 108572.0, 108573.0, 108574.0, 108575.0, 108576.0],       [108577.0, 108578.0, 108579.0, 108580.0, 108581.0, 108582.0],       [108583.0, 108584.0, 108585.0, 108586.0, 108587.0, 108588.0],       [108589.0, 108590.0, 108591.0, 108592.0, 108593.0, 108594.0],       [108595.0, 108596.0, 108597.0, 108598.0, 108599.0, 108600.0],       [108601.0, 108602.0, 108603.0, 108604.0, 108605.0, 108606.0],       [108607.0, 108608.0, 108609.0, 108610.0, 108611.0, 108612.0]]],     [[[108613.0, 108614.0, 108615.0, 108616.0, 108617.0, 108618.0],       [108619.0, 108620.0, 108621.0, 108622.0, 108623.0, 108624.0],       [108625.0, 108626.0, 108627.0, 108628.0, 108629.0, 108630.0],       [108631.0, 108632.0, 108633.0, 108634.0, 108635.0, 108636.0],       [108637.0, 108638.0, 108639.0, 108640.0, 108641.0, 108642.0],       [108643.0, 108644.0, 108645.0, 108646.0, 108647.0, 108648.0],       [108649.0, 108650.0, 108651.0, 108652.0, 108653.0, 108654.0]],      [[108655.0, 108656.0, 108657.0, 108658.0, 108659.0, 108660.0],       [108661.0, 108662.0, 108663.0, 108664.0, 108665.0, 108666.0],       [108667.0, 108668.0, 108669.0, 108670.0, 108671.0, 108672.0],       [108673.0, 108674.0, 108675.0, 108676.0, 108677.0, 108678.0],       [108679.0, 108680.0, 108681.0, 108682.0, 108683.0, 108684.0],       [108685.0, 108686.0, 108687.0, 108688.0, 108689.0, 108690.0],       [108691.0, 108692.0, 108693.0, 108694.0, 108695.0, 108696.0]],      [[108697.0, 108698.0, 108699.0, 108700.0, 108701.0, 108702.0],       [108703.0, 108704.0, 108705.0, 108706.0, 108707.0, 108708.0],       [108709.0, 108710.0, 108711.0, 108712.0, 108713.0, 108714.0],       [108715.0, 108716.0, 108717.0, 108718.0, 108719.0, 108720.0],       [108721.0, 108722.0, 108723.0, 108724.0, 108725.0, 108726.0],       [108727.0, 108728.0, 108729.0, 108730.0, 108731.0, 108732.0],       [108733.0, 108734.0, 108735.0, 108736.0, 108737.0, 108738.0]],      [[108739.0, 108740.0, 108741.0, 108742.0, 108743.0, 108744.0],       [108745.0, 108746.0, 108747.0, 108748.0, 108749.0, 108750.0],       [108751.0, 108752.0, 108753.0, 108754.0, 108755.0, 108756.0],       [108757.0, 108758.0, 108759.0, 108760.0, 108761.0, 108762.0],       [108763.0, 108764.0, 108765.0, 108766.0, 108767.0, 108768.0],       [108769.0, 108770.0, 108771.0, 108772.0, 108773.0, 108774.0],       [108775.0, 108776.0, 108777.0, 108778.0, 108779.0, 108780.0]],      [[108781.0, 108782.0, 108783.0, 108784.0, 108785.0, 108786.0],       [108787.0, 108788.0, 108789.0, 108790.0, 108791.0, 108792.0],       [108793.0, 108794.0, 108795.0, 108796.0, 108797.0, 108798.0],       [108799.0, 108800.0, 108801.0, 108802.0, 108803.0, 108804.0],       [108805.0, 108806.0, 108807.0, 108808.0, 108809.0, 108810.0],       [108811.0, 108812.0, 108813.0, 108814.0, 108815.0, 108816.0],       [108817.0, 108818.0, 108819.0, 108820.0, 108821.0, 108822.0]],      [[108823.0, 108824.0, 108825.0, 108826.0, 108827.0, 108828.0],       [108829.0, 108830.0, 108831.0, 108832.0, 108833.0, 108834.0],       [108835.0, 108836.0, 108837.0, 108838.0, 108839.0, 108840.0],       [108841.0, 108842.0, 108843.0, 108844.0, 108845.0, 108846.0],       [108847.0, 108848.0, 108849.0, 108850.0, 108851.0, 108852.0],       [108853.0, 108854.0, 108855.0, 108856.0, 108857.0, 108858.0],       [108859.0, 108860.0, 108861.0, 108862.0, 108863.0, 108864.0]]]]]]] shape=[3, 6, 6, 4, 6, 7, 6], strides=[36288, 6048, 1008, 252, 42, 6, 1], layout=C (0x1)), I32([2, 3, 2] shape=[3], strides=[1], layout=C | F (0x3)), I32([[0, 2],  [0, 3],  [0, 2]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 2987606745 320186837 2185127505 4141730701 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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],     [16.0, 17.0, 18.0, 19.0, 20.0],     [21.0, 22.0, 23.0, 24.0, 25.0]],    [[26.0, 27.0, 28.0, 29.0, 30.0],     [31.0, 32.0, 33.0, 34.0, 35.0],     [36.0, 37.0, 38.0, 39.0, 40.0],     [41.0, 42.0, 43.0, 44.0, 45.0],     [46.0, 47.0, 48.0, 49.0, 50.0]],    [[51.0, 52.0, 53.0, 54.0, 55.0],     [56.0, 57.0, 58.0, 59.0, 60.0],     [61.0, 62.0, 63.0, 64.0, 65.0],     [66.0, 67.0, 68.0, 69.0, 70.0],     [71.0, 72.0, 73.0, 74.0, 75.0]],    [[76.0, 77.0, 78.0, 79.0, 80.0],     [81.0, 82.0, 83.0, 84.0, 85.0],     [86.0, 87.0, 88.0, 89.0, 90.0],     [91.0, 92.0, 93.0, 94.0, 95.0],     [96.0, 97.0, 98.0, 99.0, 100.0]],    [[101.0, 102.0, 103.0, 104.0, 105.0],     [106.0, 107.0, 108.0, 109.0, 110.0],     [111.0, 112.0, 113.0, 114.0, 115.0],     [116.0, 117.0, 118.0, 119.0, 120.0],     [121.0, 122.0, 123.0, 124.0, 125.0]],    [[126.0, 127.0, 128.0, 129.0, 130.0],     [131.0, 132.0, 133.0, 134.0, 135.0],     [136.0, 137.0, 138.0, 139.0, 140.0],     [141.0, 142.0, 143.0, 144.0, 145.0],     [146.0, 147.0, 148.0, 149.0, 150.0]]],   [[[151.0, 152.0, 153.0, 154.0, 155.0],     [156.0, 157.0, 158.0, 159.0, 160.0],     [161.0, 162.0, 163.0, 164.0, 165.0],     [166.0, 167.0, 168.0, 169.0, 170.0],     [171.0, 172.0, 173.0, 174.0, 175.0]],    [[176.0, 177.0, 178.0, 179.0, 180.0],     [181.0, 182.0, 183.0, 184.0, 185.0],     [186.0, 187.0, 188.0, 189.0, 190.0],     [191.0, 192.0, 193.0, 194.0, 195.0],     [196.0, 197.0, 198.0, 199.0, 200.0]],    [[201.0, 202.0, 203.0, 204.0, 205.0],     [206.0, 207.0, 208.0, 209.0, 210.0],     [211.0, 212.0, 213.0, 214.0, 215.0],     [216.0, 217.0, 218.0, 219.0, 220.0],     [221.0, 222.0, 223.0, 224.0, 225.0]],    [[226.0, 227.0, 228.0, 229.0, 230.0],     [231.0, 232.0, 233.0, 234.0, 235.0],     [236.0, 237.0, 238.0, 239.0, 240.0],     [241.0, 242.0, 243.0, 244.0, 245.0],     [246.0, 247.0, 248.0, 249.0, 250.0]],    [[251.0, 252.0, 253.0, 254.0, 255.0],     [256.0, 257.0, 258.0, 259.0, 260.0],     [261.0, 262.0, 263.0, 264.0, 265.0],     [266.0, 267.0, 268.0, 269.0, 270.0],     [271.0, 272.0, 273.0, 274.0, 275.0]],    [[276.0, 277.0, 278.0, 279.0, 280.0],     [281.0, 282.0, 283.0, 284.0, 285.0],     [286.0, 287.0, 288.0, 289.0, 290.0],     [291.0, 292.0, 293.0, 294.0, 295.0],     [296.0, 297.0, 298.0, 299.0, 300.0]]],   [[[301.0, 302.0, 303.0, 304.0, 305.0],     [306.0, 307.0, 308.0, 309.0, 310.0],     [311.0, 312.0, 313.0, 314.0, 315.0],     [316.0, 317.0, 318.0, 319.0, 320.0],     [321.0, 322.0, 323.0, 324.0, 325.0]],    [[326.0, 327.0, 328.0, 329.0, 330.0],     [331.0, 332.0, 333.0, 334.0, 335.0],     [336.0, 337.0, 338.0, 339.0, 340.0],     [341.0, 342.0, 343.0, 344.0, 345.0],     [346.0, 347.0, 348.0, 349.0, 350.0]],    [[351.0, 352.0, 353.0, 354.0, 355.0],     [356.0, 357.0, 358.0, 359.0, 360.0],     [361.0, 362.0, 363.0, 364.0, 365.0],     [366.0, 367.0, 368.0, 369.0, 370.0],     [371.0, 372.0, 373.0, 374.0, 375.0]],    [[376.0, 377.0, 378.0, 379.0, 380.0],     [381.0, 382.0, 383.0, 384.0, 385.0],     [386.0, 387.0, 388.0, 389.0, 390.0],     [391.0, 392.0, 393.0, 394.0, 395.0],     [396.0, 397.0, 398.0, 399.0, 400.0]],    [[401.0, 402.0, 403.0, 404.0, 405.0],     [406.0, 407.0, 408.0, 409.0, 410.0],     [411.0, 412.0, 413.0, 414.0, 415.0],     [416.0, 417.0, 418.0, 419.0, 420.0],     [421.0, 422.0, 423.0, 424.0, 425.0]],    [[426.0, 427.0, 428.0, 429.0, 430.0],     [431.0, 432.0, 433.0, 434.0, 435.0],     [436.0, 437.0, 438.0, 439.0, 440.0],     [441.0, 442.0, 443.0, 444.0, 445.0],     [446.0, 447.0, 448.0, 449.0, 450.0]]],   [[[451.0, 452.0, 453.0, 454.0, 455.0],     [456.0, 457.0, 458.0, 459.0, 460.0],     [461.0, 462.0, 463.0, 464.0, 465.0],     [466.0, 467.0, 468.0, 469.0, 470.0],     [471.0, 472.0, 473.0, 474.0, 475.0]],    [[476.0, 477.0, 478.0, 479.0, 480.0],     [481.0, 482.0, 483.0, 484.0, 485.0],     [486.0, 487.0, 488.0, 489.0, 490.0],     [491.0, 492.0, 493.0, 494.0, 495.0],     [496.0, 497.0, 498.0, 499.0, 500.0]],    [[501.0, 502.0, 503.0, 504.0, 505.0],     [506.0, 507.0, 508.0, 509.0, 510.0],     [511.0, 512.0, 513.0, 514.0, 515.0],     [516.0, 517.0, 518.0, 519.0, 520.0],     [521.0, 522.0, 523.0, 524.0, 525.0]],    [[526.0, 527.0, 528.0, 529.0, 530.0],     [531.0, 532.0, 533.0, 534.0, 535.0],     [536.0, 537.0, 538.0, 539.0, 540.0],     [541.0, 542.0, 543.0, 544.0, 545.0],     [546.0, 547.0, 548.0, 549.0, 550.0]],    [[551.0, 552.0, 553.0, 554.0, 555.0],     [556.0, 557.0, 558.0, 559.0, 560.0],     [561.0, 562.0, 563.0, 564.0, 565.0],     [566.0, 567.0, 568.0, 569.0, 570.0],     [571.0, 572.0, 573.0, 574.0, 575.0]],    [[576.0, 577.0, 578.0, 579.0, 580.0],     [581.0, 582.0, 583.0, 584.0, 585.0],     [586.0, 587.0, 588.0, 589.0, 590.0],     [591.0, 592.0, 593.0, 594.0, 595.0],     [596.0, 597.0, 598.0, 599.0, 600.0]]],   [[[601.0, 602.0, 603.0, 604.0, 605.0],     [606.0, 607.0, 608.0, 609.0, 610.0],     [611.0, 612.0, 613.0, 614.0, 615.0],     [616.0, 617.0, 618.0, 619.0, 620.0],     [621.0, 622.0, 623.0, 624.0, 625.0]],    [[626.0, 627.0, 628.0, 629.0, 630.0],     [631.0, 632.0, 633.0, 634.0, 635.0],     [636.0, 637.0, 638.0, 639.0, 640.0],     [641.0, 642.0, 643.0, 644.0, 645.0],     [646.0, 647.0, 648.0, 649.0, 650.0]],    [[651.0, 652.0, 653.0, 654.0, 655.0],     [656.0, 657.0, 658.0, 659.0, 660.0],     [661.0, 662.0, 663.0, 664.0, 665.0],     [666.0, 667.0, 668.0, 669.0, 670.0],     [671.0, 672.0, 673.0, 674.0, 675.0]],    [[676.0, 677.0, 678.0, 679.0, 680.0],     [681.0, 682.0, 683.0, 684.0, 685.0],     [686.0, 687.0, 688.0, 689.0, 690.0],     [691.0, 692.0, 693.0, 694.0, 695.0],     [696.0, 697.0, 698.0, 699.0, 700.0]],    [[701.0, 702.0, 703.0, 704.0, 705.0],     [706.0, 707.0, 708.0, 709.0, 710.0],     [711.0, 712.0, 713.0, 714.0, 715.0],     [716.0, 717.0, 718.0, 719.0, 720.0],     [721.0, 722.0, 723.0, 724.0, 725.0]],    [[726.0, 727.0, 728.0, 729.0, 730.0],     [731.0, 732.0, 733.0, 734.0, 735.0],     [736.0, 737.0, 738.0, 739.0, 740.0],     [741.0, 742.0, 743.0, 744.0, 745.0],     [746.0, 747.0, 748.0, 749.0, 750.0]]],   [[[751.0, 752.0, 753.0, 754.0, 755.0],     [756.0, 757.0, 758.0, 759.0, 760.0],     [761.0, 762.0, 763.0, 764.0, 765.0],     [766.0, 767.0, 768.0, 769.0, 770.0],     [771.0, 772.0, 773.0, 774.0, 775.0]],    [[776.0, 777.0, 778.0, 779.0, 780.0],     [781.0, 782.0, 783.0, 784.0, 785.0],     [786.0, 787.0, 788.0, 789.0, 790.0],     [791.0, 792.0, 793.0, 794.0, 795.0],     [796.0, 797.0, 798.0, 799.0, 800.0]],    [[801.0, 802.0, 803.0, 804.0, 805.0],     [806.0, 807.0, 808.0, 809.0, 810.0],     [811.0, 812.0, 813.0, 814.0, 815.0],     [816.0, 817.0, 818.0, 819.0, 820.0],     [821.0, 822.0, 823.0, 824.0, 825.0]],    [[826.0, 827.0, 828.0, 829.0, 830.0],     [831.0, 832.0, 833.0, 834.0, 835.0],     [836.0, 837.0, 838.0, 839.0, 840.0],     [841.0, 842.0, 843.0, 844.0, 845.0],     [846.0, 847.0, 848.0, 849.0, 850.0]],    [[851.0, 852.0, 853.0, 854.0, 855.0],     [856.0, 857.0, 858.0, 859.0, 860.0],     [861.0, 862.0, 863.0, 864.0, 865.0],     [866.0, 867.0, 868.0, 869.0, 870.0],     [871.0, 872.0, 873.0, 874.0, 875.0]],    [[876.0, 877.0, 878.0, 879.0, 880.0],     [881.0, 882.0, 883.0, 884.0, 885.0],     [886.0, 887.0, 888.0, 889.0, 890.0],     [891.0, 892.0, 893.0, 894.0, 895.0],     [896.0, 897.0, 898.0, 899.0, 900.0]]]],  [[[[901.0, 902.0, 903.0, 904.0, 905.0],     [906.0, 907.0, 908.0, 909.0, 910.0],     [911.0, 912.0, 913.0, 914.0, 915.0],     [916.0, 917.0, 918.0, 919.0, 920.0],     [921.0, 922.0, 923.0, 924.0, 925.0]],    [[926.0, 927.0, 928.0, 929.0, 930.0],     [931.0, 932.0, 933.0, 934.0, 935.0],     [936.0, 937.0, 938.0, 939.0, 940.0],     [941.0, 942.0, 943.0, 944.0, 945.0],     [946.0, 947.0, 948.0, 949.0, 950.0]],    [[951.0, 952.0, 953.0, 954.0, 955.0],     [956.0, 957.0, 958.0, 959.0, 960.0],     [961.0, 962.0, 963.0, 964.0, 965.0],     [966.0, 967.0, 968.0, 969.0, 970.0],     [971.0, 972.0, 973.0, 974.0, 975.0]],    [[976.0, 977.0, 978.0, 979.0, 980.0],     [981.0, 982.0, 983.0, 984.0, 985.0],     [986.0, 987.0, 988.0, 989.0, 990.0],     [991.0, 992.0, 993.0, 994.0, 995.0],     [996.0, 997.0, 998.0, 999.0, 1000.0]],    [[1001.0, 1002.0, 1003.0, 1004.0, 1005.0],     [1006.0, 1007.0, 1008.0, 1009.0, 1010.0],     [1011.0, 1012.0, 1013.0, 1014.0, 1015.0],     [1016.0, 1017.0, 1018.0, 1019.0, 1020.0],     [1021.0, 1022.0, 1023.0, 1024.0, 1025.0]],    [[1026.0, 1027.0, 1028.0, 1029.0, 1030.0],     [1031.0, 1032.0, 1033.0, 1034.0, 1035.0],     [1036.0, 1037.0, 1038.0, 1039.0, 1040.0],     [1041.0, 1042.0, 1043.0, 1044.0, 1045.0],     [1046.0, 1047.0, 1048.0, 1049.0, 1050.0]]],   [[[1051.0, 1052.0, 1053.0, 1054.0, 1055.0],     [1056.0, 1057.0, 1058.0, 1059.0, 1060.0],     [1061.0, 1062.0, 1063.0, 1064.0, 1065.0],     [1066.0, 1067.0, 1068.0, 1069.0, 1070.0],     [1071.0, 1072.0, 1073.0, 1074.0, 1075.0]],    [[1076.0, 1077.0, 1078.0, 1079.0, 1080.0],     [1081.0, 1082.0, 1083.0, 1084.0, 1085.0],     [1086.0, 1087.0, 1088.0, 1089.0, 1090.0],     [1091.0, 1092.0, 1093.0, 1094.0, 1095.0],     [1096.0, 1097.0, 1098.0, 1099.0, 1100.0]],    [[1101.0, 1102.0, 1103.0, 1104.0, 1105.0],     [1106.0, 1107.0, 1108.0, 1109.0, 1110.0],     [1111.0, 1112.0, 1113.0, 1114.0, 1115.0],     [1116.0, 1117.0, 1118.0, 1119.0, 1120.0],     [1121.0, 1122.0, 1123.0, 1124.0, 1125.0]],    [[1126.0, 1127.0, 1128.0, 1129.0, 1130.0],     [1131.0, 1132.0, 1133.0, 1134.0, 1135.0],     [1136.0, 1137.0, 1138.0, 1139.0, 1140.0],     [1141.0, 1142.0, 1143.0, 1144.0, 1145.0],     [1146.0, 1147.0, 1148.0, 1149.0, 1150.0]],    [[1151.0, 1152.0, 1153.0, 1154.0, 1155.0],     [1156.0, 1157.0, 1158.0, 1159.0, 1160.0],     [1161.0, 1162.0, 1163.0, 1164.0, 1165.0],     [1166.0, 1167.0, 1168.0, 1169.0, 1170.0],     [1171.0, 1172.0, 1173.0, 1174.0, 1175.0]],    [[1176.0, 1177.0, 1178.0, 1179.0, 1180.0],     [1181.0, 1182.0, 1183.0, 1184.0, 1185.0],     [1186.0, 1187.0, 1188.0, 1189.0, 1190.0],     [1191.0, 1192.0, 1193.0, 1194.0, 1195.0],     [1196.0, 1197.0, 1198.0, 1199.0, 1200.0]]],   [[[1201.0, 1202.0, 1203.0, 1204.0, 1205.0],     [1206.0, 1207.0, 1208.0, 1209.0, 1210.0],     [1211.0, 1212.0, 1213.0, 1214.0, 1215.0],     [1216.0, 1217.0, 1218.0, 1219.0, 1220.0],     [1221.0, 1222.0, 1223.0, 1224.0, 1225.0]],    [[1226.0, 1227.0, 1228.0, 1229.0, 1230.0],     [1231.0, 1232.0, 1233.0, 1234.0, 1235.0],     [1236.0, 1237.0, 1238.0, 1239.0, 1240.0],     [1241.0, 1242.0, 1243.0, 1244.0, 1245.0],     [1246.0, 1247.0, 1248.0, 1249.0, 1250.0]],    [[1251.0, 1252.0, 1253.0, 1254.0, 1255.0],     [1256.0, 1257.0, 1258.0, 1259.0, 1260.0],     [1261.0, 1262.0, 1263.0, 1264.0, 1265.0],     [1266.0, 1267.0, 1268.0, 1269.0, 1270.0],     [1271.0, 1272.0, 1273.0, 1274.0, 1275.0]],    [[1276.0, 1277.0, 1278.0, 1279.0, 1280.0],     [1281.0, 1282.0, 1283.0, 1284.0, 1285.0],     [1286.0, 1287.0, 1288.0, 1289.0, 1290.0],     [1291.0, 1292.0, 1293.0, 1294.0, 1295.0],     [1296.0, 1297.0, 1298.0, 1299.0, 1300.0]],    [[1301.0, 1302.0, 1303.0, 1304.0, 1305.0],     [1306.0, 1307.0, 1308.0, 1309.0, 1310.0],     [1311.0, 1312.0, 1313.0, 1314.0, 1315.0],     [1316.0, 1317.0, 1318.0, 1319.0, 1320.0],     [1321.0, 1322.0, 1323.0, 1324.0, 1325.0]],    [[1326.0, 1327.0, 1328.0, 1329.0, 1330.0],     [1331.0, 1332.0, 1333.0, 1334.0, 1335.0],     [1336.0, 1337.0, 1338.0, 1339.0, 1340.0],     [1341.0, 1342.0, 1343.0, 1344.0, 1345.0],     [1346.0, 1347.0, 1348.0, 1349.0, 1350.0]]],   [[[1351.0, 1352.0, 1353.0, 1354.0, 1355.0],     [1356.0, 1357.0, 1358.0, 1359.0, 1360.0],     [1361.0, 1362.0, 1363.0, 1364.0, 1365.0],     [1366.0, 1367.0, 1368.0, 1369.0, 1370.0],     [1371.0, 1372.0, 1373.0, 1374.0, 1375.0]],    [[1376.0, 1377.0, 1378.0, 1379.0, 1380.0],     [1381.0, 1382.0, 1383.0, 1384.0, 1385.0],     [1386.0, 1387.0, 1388.0, 1389.0, 1390.0],     [1391.0, 1392.0, 1393.0, 1394.0, 1395.0],     [1396.0, 1397.0, 1398.0, 1399.0, 1400.0]],    [[1401.0, 1402.0, 1403.0, 1404.0, 1405.0],     [1406.0, 1407.0, 1408.0, 1409.0, 1410.0],     [1411.0, 1412.0, 1413.0, 1414.0, 1415.0],     [1416.0, 1417.0, 1418.0, 1419.0, 1420.0],     [1421.0, 1422.0, 1423.0, 1424.0, 1425.0]],    [[1426.0, 1427.0, 1428.0, 1429.0, 1430.0],     [1431.0, 1432.0, 1433.0, 1434.0, 1435.0],     [1436.0, 1437.0, 1438.0, 1439.0, 1440.0],     [1441.0, 1442.0, 1443.0, 1444.0, 1445.0],     [1446.0, 1447.0, 1448.0, 1449.0, 1450.0]],    [[1451.0, 1452.0, 1453.0, 1454.0, 1455.0],     [1456.0, 1457.0, 1458.0, 1459.0, 1460.0],     [1461.0, 1462.0, 1463.0, 1464.0, 1465.0],     [1466.0, 1467.0, 1468.0, 1469.0, 1470.0],     [1471.0, 1472.0, 1473.0, 1474.0, 1475.0]],    [[1476.0, 1477.0, 1478.0, 1479.0, 1480.0],     [1481.0, 1482.0, 1483.0, 1484.0, 1485.0],     [1486.0, 1487.0, 1488.0, 1489.0, 1490.0],     [1491.0, 1492.0, 1493.0, 1494.0, 1495.0],     [1496.0, 1497.0, 1498.0, 1499.0, 1500.0]]],   [[[1501.0, 1502.0, 1503.0, 1504.0, 1505.0],     [1506.0, 1507.0, 1508.0, 1509.0, 1510.0],     [1511.0, 1512.0, 1513.0, 1514.0, 1515.0],     [1516.0, 1517.0, 1518.0, 1519.0, 1520.0],     [1521.0, 1522.0, 1523.0, 1524.0, 1525.0]],    [[1526.0, 1527.0, 1528.0, 1529.0, 1530.0],     [1531.0, 1532.0, 1533.0, 1534.0, 1535.0],     [1536.0, 1537.0, 1538.0, 1539.0, 1540.0],     [1541.0, 1542.0, 1543.0, 1544.0, 1545.0],     [1546.0, 1547.0, 1548.0, 1549.0, 1550.0]],    [[1551.0, 1552.0, 1553.0, 1554.0, 1555.0],     [1556.0, 1557.0, 1558.0, 1559.0, 1560.0],     [1561.0, 1562.0, 1563.0, 1564.0, 1565.0],     [1566.0, 1567.0, 1568.0, 1569.0, 1570.0],     [1571.0, 1572.0, 1573.0, 1574.0, 1575.0]],    [[1576.0, 1577.0, 1578.0, 1579.0, 1580.0],     [1581.0, 1582.0, 1583.0, 1584.0, 1585.0],     [1586.0, 1587.0, 1588.0, 1589.0, 1590.0],     [1591.0, 1592.0, 1593.0, 1594.0, 1595.0],     [1596.0, 1597.0, 1598.0, 1599.0, 1600.0]],    [[1601.0, 1602.0, 1603.0, 1604.0, 1605.0],     [1606.0, 1607.0, 1608.0, 1609.0, 1610.0],     [1611.0, 1612.0, 1613.0, 1614.0, 1615.0],     [1616.0, 1617.0, 1618.0, 1619.0, 1620.0],     [1621.0, 1622.0, 1623.0, 1624.0, 1625.0]],    [[1626.0, 1627.0, 1628.0, 1629.0, 1630.0],     [1631.0, 1632.0, 1633.0, 1634.0, 1635.0],     [1636.0, 1637.0, 1638.0, 1639.0, 1640.0],     [1641.0, 1642.0, 1643.0, 1644.0, 1645.0],     [1646.0, 1647.0, 1648.0, 1649.0, 1650.0]]],   [[[1651.0, 1652.0, 1653.0, 1654.0, 1655.0],     [1656.0, 1657.0, 1658.0, 1659.0, 1660.0],     [1661.0, 1662.0, 1663.0, 1664.0, 1665.0],     [1666.0, 1667.0, 1668.0, 1669.0, 1670.0],     [1671.0, 1672.0, 1673.0, 1674.0, 1675.0]],    [[1676.0, 1677.0, 1678.0, 1679.0, 1680.0],     [1681.0, 1682.0, 1683.0, 1684.0, 1685.0],     [1686.0, 1687.0, 1688.0, 1689.0, 1690.0],     [1691.0, 1692.0, 1693.0, 1694.0, 1695.0],     [1696.0, 1697.0, 1698.0, 1699.0, 1700.0]],    [[1701.0, 1702.0, 1703.0, 1704.0, 1705.0],     [1706.0, 1707.0, 1708.0, 1709.0, 1710.0],     [1711.0, 1712.0, 1713.0, 1714.0, 1715.0],     [1716.0, 1717.0, 1718.0, 1719.0, 1720.0],     [1721.0, 1722.0, 1723.0, 1724.0, 1725.0]],    [[1726.0, 1727.0, 1728.0, 1729.0, 1730.0],     [1731.0, 1732.0, 1733.0, 1734.0, 1735.0],     [1736.0, 1737.0, 1738.0, 1739.0, 1740.0],     [1741.0, 1742.0, 1743.0, 1744.0, 1745.0],     [1746.0, 1747.0, 1748.0, 1749.0, 1750.0]],    [[1751.0, 1752.0, 1753.0, 1754.0, 1755.0],     [1756.0, 1757.0, 1758.0, 1759.0, 1760.0],     [1761.0, 1762.0, 1763.0, 1764.0, 1765.0],     [1766.0, 1767.0, 1768.0, 1769.0, 1770.0],     [1771.0, 1772.0, 1773.0, 1774.0, 1775.0]],    [[1776.0, 1777.0, 1778.0, 1779.0, 1780.0],     [1781.0, 1782.0, 1783.0, 1784.0, 1785.0],     [1786.0, 1787.0, 1788.0, 1789.0, 1790.0],     [1791.0, 1792.0, 1793.0, 1794.0, 1795.0],     [1796.0, 1797.0, 1798.0, 1799.0, 1800.0]]]],  [[[[1801.0, 1802.0, 1803.0, 1804.0, 1805.0],     [1806.0, 1807.0, 1808.0, 1809.0, 1810.0],     [1811.0, 1812.0, 1813.0, 1814.0, 1815.0],     [1816.0, 1817.0, 1818.0, 1819.0, 1820.0],     [1821.0, 1822.0, 1823.0, 1824.0, 1825.0]],    [[1826.0, 1827.0, 1828.0, 1829.0, 1830.0],     [1831.0, 1832.0, 1833.0, 1834.0, 1835.0],     [1836.0, 1837.0, 1838.0, 1839.0, 1840.0],     [1841.0, 1842.0, 1843.0, 1844.0, 1845.0],     [1846.0, 1847.0, 1848.0, 1849.0, 1850.0]],    [[1851.0, 1852.0, 1853.0, 1854.0, 1855.0],     [1856.0, 1857.0, 1858.0, 1859.0, 1860.0],     [1861.0, 1862.0, 1863.0, 1864.0, 1865.0],     [1866.0, 1867.0, 1868.0, 1869.0, 1870.0],     [1871.0, 1872.0, 1873.0, 1874.0, 1875.0]],    [[1876.0, 1877.0, 1878.0, 1879.0, 1880.0],     [1881.0, 1882.0, 1883.0, 1884.0, 1885.0],     [1886.0, 1887.0, 1888.0, 1889.0, 1890.0],     [1891.0, 1892.0, 1893.0, 1894.0, 1895.0],     [1896.0, 1897.0, 1898.0, 1899.0, 1900.0]],    [[1901.0, 1902.0, 1903.0, 1904.0, 1905.0],     [1906.0, 1907.0, 1908.0, 1909.0, 1910.0],     [1911.0, 1912.0, 1913.0, 1914.0, 1915.0],     [1916.0, 1917.0, 1918.0, 1919.0, 1920.0],     [1921.0, 1922.0, 1923.0, 1924.0, 1925.0]],    [[1926.0, 1927.0, 1928.0, 1929.0, 1930.0],     [1931.0, 1932.0, 1933.0, 1934.0, 1935.0],     [1936.0, 1937.0, 1938.0, 1939.0, 1940.0],     [1941.0, 1942.0, 1943.0, 1944.0, 1945.0],     [1946.0, 1947.0, 1948.0, 1949.0, 1950.0]]],   [[[1951.0, 1952.0, 1953.0, 1954.0, 1955.0],     [1956.0, 1957.0, 1958.0, 1959.0, 1960.0],     [1961.0, 1962.0, 1963.0, 1964.0, 1965.0],     [1966.0, 1967.0, 1968.0, 1969.0, 1970.0],     [1971.0, 1972.0, 1973.0, 1974.0, 1975.0]],    [[1976.0, 1977.0, 1978.0, 1979.0, 1980.0],     [1981.0, 1982.0, 1983.0, 1984.0, 1985.0],     [1986.0, 1987.0, 1988.0, 1989.0, 1990.0],     [1991.0, 1992.0, 1993.0, 1994.0, 1995.0],     [1996.0, 1997.0, 1998.0, 1999.0, 2000.0]],    [[2001.0, 2002.0, 2003.0, 2004.0, 2005.0],     [2006.0, 2007.0, 2008.0, 2009.0, 2010.0],     [2011.0, 2012.0, 2013.0, 2014.0, 2015.0],     [2016.0, 2017.0, 2018.0, 2019.0, 2020.0],     [2021.0, 2022.0, 2023.0, 2024.0, 2025.0]],    [[2026.0, 2027.0, 2028.0, 2029.0, 2030.0],     [2031.0, 2032.0, 2033.0, 2034.0, 2035.0],     [2036.0, 2037.0, 2038.0, 2039.0, 2040.0],     [2041.0, 2042.0, 2043.0, 2044.0, 2045.0],     [2046.0, 2047.0, 2048.0, 2049.0, 2050.0]],    [[2051.0, 2052.0, 2053.0, 2054.0, 2055.0],     [2056.0, 2057.0, 2058.0, 2059.0, 2060.0],     [2061.0, 2062.0, 2063.0, 2064.0, 2065.0],     [2066.0, 2067.0, 2068.0, 2069.0, 2070.0],     [2071.0, 2072.0, 2073.0, 2074.0, 2075.0]],    [[2076.0, 2077.0, 2078.0, 2079.0, 2080.0],     [2081.0, 2082.0, 2083.0, 2084.0, 2085.0],     [2086.0, 2087.0, 2088.0, 2089.0, 2090.0],     [2091.0, 2092.0, 2093.0, 2094.0, 2095.0],     [2096.0, 2097.0, 2098.0, 2099.0, 2100.0]]],   [[[2101.0, 2102.0, 2103.0, 2104.0, 2105.0],     [2106.0, 2107.0, 2108.0, 2109.0, 2110.0],     [2111.0, 2112.0, 2113.0, 2114.0, 2115.0],     [2116.0, 2117.0, 2118.0, 2119.0, 2120.0],     [2121.0, 2122.0, 2123.0, 2124.0, 2125.0]],    [[2126.0, 2127.0, 2128.0, 2129.0, 2130.0],     [2131.0, 2132.0, 2133.0, 2134.0, 2135.0],     [2136.0, 2137.0, 2138.0, 2139.0, 2140.0],     [2141.0, 2142.0, 2143.0, 2144.0, 2145.0],     [2146.0, 2147.0, 2148.0, 2149.0, 2150.0]],    [[2151.0, 2152.0, 2153.0, 2154.0, 2155.0],     [2156.0, 2157.0, 2158.0, 2159.0, 2160.0],     [2161.0, 2162.0, 2163.0, 2164.0, 2165.0],     [2166.0, 2167.0, 2168.0, 2169.0, 2170.0],     [2171.0, 2172.0, 2173.0, 2174.0, 2175.0]],    [[2176.0, 2177.0, 2178.0, 2179.0, 2180.0],     [2181.0, 2182.0, 2183.0, 2184.0, 2185.0],     [2186.0, 2187.0, 2188.0, 2189.0, 2190.0],     [2191.0, 2192.0, 2193.0, 2194.0, 2195.0],     [2196.0, 2197.0, 2198.0, 2199.0, 2200.0]],    [[2201.0, 2202.0, 2203.0, 2204.0, 2205.0],     [2206.0, 2207.0, 2208.0, 2209.0, 2210.0],     [2211.0, 2212.0, 2213.0, 2214.0, 2215.0],     [2216.0, 2217.0, 2218.0, 2219.0, 2220.0],     [2221.0, 2222.0, 2223.0, 2224.0, 2225.0]],    [[2226.0, 2227.0, 2228.0, 2229.0, 2230.0],     [2231.0, 2232.0, 2233.0, 2234.0, 2235.0],     [2236.0, 2237.0, 2238.0, 2239.0, 2240.0],     [2241.0, 2242.0, 2243.0, 2244.0, 2245.0],     [2246.0, 2247.0, 2248.0, 2249.0, 2250.0]]],   [[[2251.0, 2252.0, 2253.0, 2254.0, 2255.0],     [2256.0, 2257.0, 2258.0, 2259.0, 2260.0],     [2261.0, 2262.0, 2263.0, 2264.0, 2265.0],     [2266.0, 2267.0, 2268.0, 2269.0, 2270.0],     [2271.0, 2272.0, 2273.0, 2274.0, 2275.0]],    [[2276.0, 2277.0, 2278.0, 2279.0, 2280.0],     [2281.0, 2282.0, 2283.0, 2284.0, 2285.0],     [2286.0, 2287.0, 2288.0, 2289.0, 2290.0],     [2291.0, 2292.0, 2293.0, 2294.0, 2295.0],     [2296.0, 2297.0, 2298.0, 2299.0, 2300.0]],    [[2301.0, 2302.0, 2303.0, 2304.0, 2305.0],     [2306.0, 2307.0, 2308.0, 2309.0, 2310.0],     [2311.0, 2312.0, 2313.0, 2314.0, 2315.0],     [2316.0, 2317.0, 2318.0, 2319.0, 2320.0],     [2321.0, 2322.0, 2323.0, 2324.0, 2325.0]],    [[2326.0, 2327.0, 2328.0, 2329.0, 2330.0],     [2331.0, 2332.0, 2333.0, 2334.0, 2335.0],     [2336.0, 2337.0, 2338.0, 2339.0, 2340.0],     [2341.0, 2342.0, 2343.0, 2344.0, 2345.0],     [2346.0, 2347.0, 2348.0, 2349.0, 2350.0]],    [[2351.0, 2352.0, 2353.0, 2354.0, 2355.0],     [2356.0, 2357.0, 2358.0, 2359.0, 2360.0],     [2361.0, 2362.0, 2363.0, 2364.0, 2365.0],     [2366.0, 2367.0, 2368.0, 2369.0, 2370.0],     [2371.0, 2372.0, 2373.0, 2374.0, 2375.0]],    [[2376.0, 2377.0, 2378.0, 2379.0, 2380.0],     [2381.0, 2382.0, 2383.0, 2384.0, 2385.0],     [2386.0, 2387.0, 2388.0, 2389.0, 2390.0],     [2391.0, 2392.0, 2393.0, 2394.0, 2395.0],     [2396.0, 2397.0, 2398.0, 2399.0, 2400.0]]],   [[[2401.0, 2402.0, 2403.0, 2404.0, 2405.0],     [2406.0, 2407.0, 2408.0, 2409.0, 2410.0],     [2411.0, 2412.0, 2413.0, 2414.0, 2415.0],     [2416.0, 2417.0, 2418.0, 2419.0, 2420.0],     [2421.0, 2422.0, 2423.0, 2424.0, 2425.0]],    [[2426.0, 2427.0, 2428.0, 2429.0, 2430.0],     [2431.0, 2432.0, 2433.0, 2434.0, 2435.0],     [2436.0, 2437.0, 2438.0, 2439.0, 2440.0],     [2441.0, 2442.0, 2443.0, 2444.0, 2445.0],     [2446.0, 2447.0, 2448.0, 2449.0, 2450.0]],    [[2451.0, 2452.0, 2453.0, 2454.0, 2455.0],     [2456.0, 2457.0, 2458.0, 2459.0, 2460.0],     [2461.0, 2462.0, 2463.0, 2464.0, 2465.0],     [2466.0, 2467.0, 2468.0, 2469.0, 2470.0],     [2471.0, 2472.0, 2473.0, 2474.0, 2475.0]],    [[2476.0, 2477.0, 2478.0, 2479.0, 2480.0],     [2481.0, 2482.0, 2483.0, 2484.0, 2485.0],     [2486.0, 2487.0, 2488.0, 2489.0, 2490.0],     [2491.0, 2492.0, 2493.0, 2494.0, 2495.0],     [2496.0, 2497.0, 2498.0, 2499.0, 2500.0]],    [[2501.0, 2502.0, 2503.0, 2504.0, 2505.0],     [2506.0, 2507.0, 2508.0, 2509.0, 2510.0],     [2511.0, 2512.0, 2513.0, 2514.0, 2515.0],     [2516.0, 2517.0, 2518.0, 2519.0, 2520.0],     [2521.0, 2522.0, 2523.0, 2524.0, 2525.0]],    [[2526.0, 2527.0, 2528.0, 2529.0, 2530.0],     [2531.0, 2532.0, 2533.0, 2534.0, 2535.0],     [2536.0, 2537.0, 2538.0, 2539.0, 2540.0],     [2541.0, 2542.0, 2543.0, 2544.0, 2545.0],     [2546.0, 2547.0, 2548.0, 2549.0, 2550.0]]],   [[[2551.0, 2552.0, 2553.0, 2554.0, 2555.0],     [2556.0, 2557.0, 2558.0, 2559.0, 2560.0],     [2561.0, 2562.0, 2563.0, 2564.0, 2565.0],     [2566.0, 2567.0, 2568.0, 2569.0, 2570.0],     [2571.0, 2572.0, 2573.0, 2574.0, 2575.0]],    [[2576.0, 2577.0, 2578.0, 2579.0, 2580.0],     [2581.0, 2582.0, 2583.0, 2584.0, 2585.0],     [2586.0, 2587.0, 2588.0, 2589.0, 2590.0],     [2591.0, 2592.0, 2593.0, 2594.0, 2595.0],     [2596.0, 2597.0, 2598.0, 2599.0, 2600.0]],    [[2601.0, 2602.0, 2603.0, 2604.0, 2605.0],     [2606.0, 2607.0, 2608.0, 2609.0, 2610.0],     [2611.0, 2612.0, 2613.0, 2614.0, 2615.0],     [2616.0, 2617.0, 2618.0, 2619.0, 2620.0],     [2621.0, 2622.0, 2623.0, 2624.0, 2625.0]],    [[2626.0, 2627.0, 2628.0, 2629.0, 2630.0],     [2631.0, 2632.0, 2633.0, 2634.0, 2635.0],     [2636.0, 2637.0, 2638.0, 2639.0, 2640.0],     [2641.0, 2642.0, 2643.0, 2644.0, 2645.0],     [2646.0, 2647.0, 2648.0, 2649.0, 2650.0]],    [[2651.0, 2652.0, 2653.0, 2654.0, 2655.0],     [2656.0, 2657.0, 2658.0, 2659.0, 2660.0],     [2661.0, 2662.0, 2663.0, 2664.0, 2665.0],     [2666.0, 2667.0, 2668.0, 2669.0, 2670.0],     [2671.0, 2672.0, 2673.0, 2674.0, 2675.0]],    [[2676.0, 2677.0, 2678.0, 2679.0, 2680.0],     [2681.0, 2682.0, 2683.0, 2684.0, 2685.0],     [2686.0, 2687.0, 2688.0, 2689.0, 2690.0],     [2691.0, 2692.0, 2693.0, 2694.0, 2695.0],     [2696.0, 2697.0, 2698.0, 2699.0, 2700.0]]]]] shape=[3, 6, 6, 5, 5], strides=[900, 150, 25, 5, 1], layout=C (0x1)), I32([3, 2, 2] shape=[3], strides=[1], layout=C | F (0x3)), I32([[3, 3],  [0, 2],  [3, 2]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 1690712456 1439893044 2026898631 1081487774 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],     [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0],     [29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],     [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0]],    [[43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],     [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0],     [57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],     [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0],     [71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],     [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0]],    [[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],     [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],     [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0],     [106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0],     [113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],     [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0]]],   [[[127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],     [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0],     [141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],     [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],     [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],     [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0]],    [[169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0],     [176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],     [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],     [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0],     [197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],     [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0]],    [[211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],     [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0],     [225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],     [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],     [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0],     [246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0]]],   [[[253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],     [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],     [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],     [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0],     [281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],     [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0]],    [[295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],     [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0],     [309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0],     [316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],     [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],     [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0]],    [[337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],     [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0],     [351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],     [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0],     [365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],     [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0]]],   [[[379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0],     [386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0],     [393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],     [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],     [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],     [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]],    [[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],     [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],     [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],     [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0],     [449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0],     [456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0]],    [[463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],     [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0],     [477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],     [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0],     [491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],     [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0]]],   [[[505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],     [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],     [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0],     [526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0],     [533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],     [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0]],    [[547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],     [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0],     [561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],     [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],     [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],     [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0]],    [[589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0],     [596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],     [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],     [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0],     [617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],     [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0]]]],  [[[[631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],     [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0],     [645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],     [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0],     [659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0],     [666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0]],    [[673.0, 674.0, 675.0, 676.0, 677.0, 678.0, 679.0],     [680.0, 681.0, 682.0, 683.0, 684.0, 685.0, 686.0],     [687.0, 688.0, 689.0, 690.0, 691.0, 692.0, 693.0],     [694.0, 695.0, 696.0, 697.0, 698.0, 699.0, 700.0],     [701.0, 702.0, 703.0, 704.0, 705.0, 706.0, 707.0],     [708.0, 709.0, 710.0, 711.0, 712.0, 713.0, 714.0]],    [[715.0, 716.0, 717.0, 718.0, 719.0, 720.0, 721.0],     [722.0, 723.0, 724.0, 725.0, 726.0, 727.0, 728.0],     [729.0, 730.0, 731.0, 732.0, 733.0, 734.0, 735.0],     [736.0, 737.0, 738.0, 739.0, 740.0, 741.0, 742.0],     [743.0, 744.0, 745.0, 746.0, 747.0, 748.0, 749.0],     [750.0, 751.0, 752.0, 753.0, 754.0, 755.0, 756.0]]],   [[[757.0, 758.0, 759.0, 760.0, 761.0, 762.0, 763.0],     [764.0, 765.0, 766.0, 767.0, 768.0, 769.0, 770.0],     [771.0, 772.0, 773.0, 774.0, 775.0, 776.0, 777.0],     [778.0, 779.0, 780.0, 781.0, 782.0, 783.0, 784.0],     [785.0, 786.0, 787.0, 788.0, 789.0, 790.0, 791.0],     [792.0, 793.0, 794.0, 795.0, 796.0, 797.0, 798.0]],    [[799.0, 800.0, 801.0, 802.0, 803.0, 804.0, 805.0],     [806.0, 807.0, 808.0, 809.0, 810.0, 811.0, 812.0],     [813.0, 814.0, 815.0, 816.0, 817.0, 818.0, 819.0],     [820.0, 821.0, 822.0, 823.0, 824.0, 825.0, 826.0],     [827.0, 828.0, 829.0, 830.0, 831.0, 832.0, 833.0],     [834.0, 835.0, 836.0, 837.0, 838.0, 839.0, 840.0]],    [[841.0, 842.0, 843.0, 844.0, 845.0, 846.0, 847.0],     [848.0, 849.0, 850.0, 851.0, 852.0, 853.0, 854.0],     [855.0, 856.0, 857.0, 858.0, 859.0, 860.0, 861.0],     [862.0, 863.0, 864.0, 865.0, 866.0, 867.0, 868.0],     [869.0, 870.0, 871.0, 872.0, 873.0, 874.0, 875.0],     [876.0, 877.0, 878.0, 879.0, 880.0, 881.0, 882.0]]],   [[[883.0, 884.0, 885.0, 886.0, 887.0, 888.0, 889.0],     [890.0, 891.0, 892.0, 893.0, 894.0, 895.0, 896.0],     [897.0, 898.0, 899.0, 900.0, 901.0, 902.0, 903.0],     [904.0, 905.0, 906.0, 907.0, 908.0, 909.0, 910.0],     [911.0, 912.0, 913.0, 914.0, 915.0, 916.0, 917.0],     [918.0, 919.0, 920.0, 921.0, 922.0, 923.0, 924.0]],    [[925.0, 926.0, 927.0, 928.0, 929.0, 930.0, 931.0],     [932.0, 933.0, 934.0, 935.0, 936.0, 937.0, 938.0],     [939.0, 940.0, 941.0, 942.0, 943.0, 944.0, 945.0],     [946.0, 947.0, 948.0, 949.0, 950.0, 951.0, 952.0],     [953.0, 954.0, 955.0, 956.0, 957.0, 958.0, 959.0],     [960.0, 961.0, 962.0, 963.0, 964.0, 965.0, 966.0]],    [[967.0, 968.0, 969.0, 970.0, 971.0, 972.0, 973.0],     [974.0, 975.0, 976.0, 977.0, 978.0, 979.0, 980.0],     [981.0, 982.0, 983.0, 984.0, 985.0, 986.0, 987.0],     [988.0, 989.0, 990.0, 991.0, 992.0, 993.0, 994.0],     [995.0, 996.0, 997.0, 998.0, 999.0, 1000.0, 1001.0],     [1002.0, 1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0]]],   [[[1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0, 1015.0],     [1016.0, 1017.0, 1018.0, 1019.0, 1020.0, 1021.0, 1022.0],     [1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0],     [1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0],     [1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0],     [1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]],    [[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0, 1057.0],     [1058.0, 1059.0, 1060.0, 1061.0, 1062.0, 1063.0, 1064.0],     [1065.0, 1066.0, 1067.0, 1068.0, 1069.0, 1070.0, 1071.0],     [1072.0, 1073.0, 1074.0, 1075.0, 1076.0, 1077.0, 1078.0],     [1079.0, 1080.0, 1081.0, 1082.0, 1083.0, 1084.0, 1085.0],     [1086.0, 1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0]],    [[1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0, 1099.0],     [1100.0, 1101.0, 1102.0, 1103.0, 1104.0, 1105.0, 1106.0],     [1107.0, 1108.0, 1109.0, 1110.0, 1111.0, 1112.0, 1113.0],     [1114.0, 1115.0, 1116.0, 1117.0, 1118.0, 1119.0, 1120.0],     [1121.0, 1122.0, 1123.0, 1124.0, 1125.0, 1126.0, 1127.0],     [1128.0, 1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0]]],   [[[1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0, 1141.0],     [1142.0, 1143.0, 1144.0, 1145.0, 1146.0, 1147.0, 1148.0],     [1149.0, 1150.0, 1151.0, 1152.0, 1153.0, 1154.0, 1155.0],     [1156.0, 1157.0, 1158.0, 1159.0, 1160.0, 1161.0, 1162.0],     [1163.0, 1164.0, 1165.0, 1166.0, 1167.0, 1168.0, 1169.0],     [1170.0, 1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0]],    [[1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0, 1183.0],     [1184.0, 1185.0, 1186.0, 1187.0, 1188.0, 1189.0, 1190.0],     [1191.0, 1192.0, 1193.0, 1194.0, 1195.0, 1196.0, 1197.0],     [1198.0, 1199.0, 1200.0, 1201.0, 1202.0, 1203.0, 1204.0],     [1205.0, 1206.0, 1207.0, 1208.0, 1209.0, 1210.0, 1211.0],     [1212.0, 1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0]],    [[1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0, 1225.0],     [1226.0, 1227.0, 1228.0, 1229.0, 1230.0, 1231.0, 1232.0],     [1233.0, 1234.0, 1235.0, 1236.0, 1237.0, 1238.0, 1239.0],     [1240.0, 1241.0, 1242.0, 1243.0, 1244.0, 1245.0, 1246.0],     [1247.0, 1248.0, 1249.0, 1250.0, 1251.0, 1252.0, 1253.0],     [1254.0, 1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]]]]] shape=[2, 5, 3, 6, 7], strides=[630, 126, 42, 7, 1], layout=C (0x1)), I32([1, 3, 3] shape=[3], strides=[1], layout=C | F (0x3)), I32([[2, 1],  [1, 2],  [3, 3]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 1625354339 1507447098 2858653793 3926171498 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],       [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0],       [29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0]],      [[36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0],       [43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],       [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0],       [57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],       [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0]]],     [[[71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],       [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0],       [85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],       [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],       [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0]],      [[106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0],       [113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],       [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0],       [127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],       [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0]]],     [[[141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],       [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],       [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],       [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0],       [169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0]],      [[176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],       [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],       [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0],       [197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],       [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0]]],     [[[211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],       [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0],       [225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],       [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],       [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0]],      [[246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0],       [253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],       [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],       [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],       [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0]]],     [[[281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],       [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0],       [295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],       [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0],       [309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0]],      [[316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],       [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],       [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0],       [337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],       [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0]]],     [[[351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],       [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0],       [365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],       [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0],       [379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0]],      [[386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0],       [393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],       [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],       [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],       [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]]],     [[[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],       [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],       [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],       [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0],       [449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0]],      [[456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0],       [463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],       [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0],       [477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],       [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0]]]],    [[[[491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],       [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0],       [505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],       [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],       [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0]],      [[526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0],       [533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],       [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0],       [547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],       [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0]]],     [[[561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],       [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],       [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],       [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0],       [589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0]],      [[596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],       [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],       [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0],       [617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],       [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0]]],     [[[631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],       [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0],       [645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],       [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0],       [659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0]],      [[666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0],       [673.0, 674.0, 675.0, 676.0, 677.0, 678.0, 679.0],       [680.0, 681.0, 682.0, 683.0, 684.0, 685.0, 686.0],       [687.0, 688.0, 689.0, 690.0, 691.0, 692.0, 693.0],       [694.0, 695.0, 696.0, 697.0, 698.0, 699.0, 700.0]]],     [[[701.0, 702.0, 703.0, 704.0, 705.0, 706.0, 707.0],       [708.0, 709.0, 710.0, 711.0, 712.0, 713.0, 714.0],       [715.0, 716.0, 717.0, 718.0, 719.0, 720.0, 721.0],       [722.0, 723.0, 724.0, 725.0, 726.0, 727.0, 728.0],       [729.0, 730.0, 731.0, 732.0, 733.0, 734.0, 735.0]],      [[736.0, 737.0, 738.0, 739.0, 740.0, 741.0, 742.0],       [743.0, 744.0, 745.0, 746.0, 747.0, 748.0, 749.0],       [750.0, 751.0, 752.0, 753.0, 754.0, 755.0, 756.0],       [757.0, 758.0, 759.0, 760.0, 761.0, 762.0, 763.0],       [764.0, 765.0, 766.0, 767.0, 768.0, 769.0, 770.0]]],     [[[771.0, 772.0, 773.0, 774.0, 775.0, 776.0, 777.0],       [778.0, 779.0, 780.0, 781.0, 782.0, 783.0, 784.0],       [785.0, 786.0, 787.0, 788.0, 789.0, 790.0, 791.0],       [792.0, 793.0, 794.0, 795.0, 796.0, 797.0, 798.0],       [799.0, 800.0, 801.0, 802.0, 803.0, 804.0, 805.0]],      [[806.0, 807.0, 808.0, 809.0, 810.0, 811.0, 812.0],       [813.0, 814.0, 815.0, 816.0, 817.0, 818.0, 819.0],       [820.0, 821.0, 822.0, 823.0, 824.0, 825.0, 826.0],       [827.0, 828.0, 829.0, 830.0, 831.0, 832.0, 833.0],       [834.0, 835.0, 836.0, 837.0, 838.0, 839.0, 840.0]]],     [[[841.0, 842.0, 843.0, 844.0, 845.0, 846.0, 847.0],       [848.0, 849.0, 850.0, 851.0, 852.0, 853.0, 854.0],       [855.0, 856.0, 857.0, 858.0, 859.0, 860.0, 861.0],       [862.0, 863.0, 864.0, 865.0, 866.0, 867.0, 868.0],       [869.0, 870.0, 871.0, 872.0, 873.0, 874.0, 875.0]],      [[876.0, 877.0, 878.0, 879.0, 880.0, 881.0, 882.0],       [883.0, 884.0, 885.0, 886.0, 887.0, 888.0, 889.0],       [890.0, 891.0, 892.0, 893.0, 894.0, 895.0, 896.0],       [897.0, 898.0, 899.0, 900.0, 901.0, 902.0, 903.0],       [904.0, 905.0, 906.0, 907.0, 908.0, 909.0, 910.0]]],     [[[911.0, 912.0, 913.0, 914.0, 915.0, 916.0, 917.0],       [918.0, 919.0, 920.0, 921.0, 922.0, 923.0, 924.0],       [925.0, 926.0, 927.0, 928.0, 929.0, 930.0, 931.0],       [932.0, 933.0, 934.0, 935.0, 936.0, 937.0, 938.0],       [939.0, 940.0, 941.0, 942.0, 943.0, 944.0, 945.0]],      [[946.0, 947.0, 948.0, 949.0, 950.0, 951.0, 952.0],       [953.0, 954.0, 955.0, 956.0, 957.0, 958.0, 959.0],       [960.0, 961.0, 962.0, 963.0, 964.0, 965.0, 966.0],       [967.0, 968.0, 969.0, 970.0, 971.0, 972.0, 973.0],       [974.0, 975.0, 976.0, 977.0, 978.0, 979.0, 980.0]]]]],   [[[[[981.0, 982.0, 983.0, 984.0, 985.0, 986.0, 987.0],       [988.0, 989.0, 990.0, 991.0, 992.0, 993.0, 994.0],       [995.0, 996.0, 997.0, 998.0, 999.0, 1000.0, 1001.0],       [1002.0, 1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0],       [1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0, 1015.0]],      [[1016.0, 1017.0, 1018.0, 1019.0, 1020.0, 1021.0, 1022.0],       [1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0],       [1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0],       [1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0],       [1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]]],     [[[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0, 1057.0],       [1058.0, 1059.0, 1060.0, 1061.0, 1062.0, 1063.0, 1064.0],       [1065.0, 1066.0, 1067.0, 1068.0, 1069.0, 1070.0, 1071.0],       [1072.0, 1073.0, 1074.0, 1075.0, 1076.0, 1077.0, 1078.0],       [1079.0, 1080.0, 1081.0, 1082.0, 1083.0, 1084.0, 1085.0]],      [[1086.0, 1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0],       [1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0, 1099.0],       [1100.0, 1101.0, 1102.0, 1103.0, 1104.0, 1105.0, 1106.0],       [1107.0, 1108.0, 1109.0, 1110.0, 1111.0, 1112.0, 1113.0],       [1114.0, 1115.0, 1116.0, 1117.0, 1118.0, 1119.0, 1120.0]]],     [[[1121.0, 1122.0, 1123.0, 1124.0, 1125.0, 1126.0, 1127.0],       [1128.0, 1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0],       [1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0, 1141.0],       [1142.0, 1143.0, 1144.0, 1145.0, 1146.0, 1147.0, 1148.0],       [1149.0, 1150.0, 1151.0, 1152.0, 1153.0, 1154.0, 1155.0]],      [[1156.0, 1157.0, 1158.0, 1159.0, 1160.0, 1161.0, 1162.0],       [1163.0, 1164.0, 1165.0, 1166.0, 1167.0, 1168.0, 1169.0],       [1170.0, 1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0],       [1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0, 1183.0],       [1184.0, 1185.0, 1186.0, 1187.0, 1188.0, 1189.0, 1190.0]]],     [[[1191.0, 1192.0, 1193.0, 1194.0, 1195.0, 1196.0, 1197.0],       [1198.0, 1199.0, 1200.0, 1201.0, 1202.0, 1203.0, 1204.0],       [1205.0, 1206.0, 1207.0, 1208.0, 1209.0, 1210.0, 1211.0],       [1212.0, 1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0],       [1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0, 1225.0]],      [[1226.0, 1227.0, 1228.0, 1229.0, 1230.0, 1231.0, 1232.0],       [1233.0, 1234.0, 1235.0, 1236.0, 1237.0, 1238.0, 1239.0],       [1240.0, 1241.0, 1242.0, 1243.0, 1244.0, 1245.0, 1246.0],       [1247.0, 1248.0, 1249.0, 1250.0, 1251.0, 1252.0, 1253.0],       [1254.0, 1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]]],     [[[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0, 1267.0],       [1268.0, 1269.0, 1270.0, 1271.0, 1272.0, 1273.0, 1274.0],       [1275.0, 1276.0, 1277.0, 1278.0, 1279.0, 1280.0, 1281.0],       [1282.0, 1283.0, 1284.0, 1285.0, 1286.0, 1287.0, 1288.0],       [1289.0, 1290.0, 1291.0, 1292.0, 1293.0, 1294.0, 1295.0]],      [[1296.0, 1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0],       [1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0, 1309.0],       [1310.0, 1311.0, 1312.0, 1313.0, 1314.0, 1315.0, 1316.0],       [1317.0, 1318.0, 1319.0, 1320.0, 1321.0, 1322.0, 1323.0],       [1324.0, 1325.0, 1326.0, 1327.0, 1328.0, 1329.0, 1330.0]]],     [[[1331.0, 1332.0, 1333.0, 1334.0, 1335.0, 1336.0, 1337.0],       [1338.0, 1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0],       [1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0, 1351.0],       [1352.0, 1353.0, 1354.0, 1355.0, 1356.0, 1357.0, 1358.0],       [1359.0, 1360.0, 1361.0, 1362.0, 1363.0, 1364.0, 1365.0]],      [[1366.0, 1367.0, 1368.0, 1369.0, 1370.0, 1371.0, 1372.0],       [1373.0, 1374.0, 1375.0, 1376.0, 1377.0, 1378.0, 1379.0],       [1380.0, 1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0],       [1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0, 1393.0],       [1394.0, 1395.0, 1396.0, 1397.0, 1398.0, 1399.0, 1400.0]]],     [[[1401.0, 1402.0, 1403.0, 1404.0, 1405.0, 1406.0, 1407.0],       [1408.0, 1409.0, 1410.0, 1411.0, 1412.0, 1413.0, 1414.0],       [1415.0, 1416.0, 1417.0, 1418.0, 1419.0, 1420.0, 1421.0],       [1422.0, 1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0],       [1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0, 1435.0]],      [[1436.0, 1437.0, 1438.0, 1439.0, 1440.0, 1441.0, 1442.0],       [1443.0, 1444.0, 1445.0, 1446.0, 1447.0, 1448.0, 1449.0],       [1450.0, 1451.0, 1452.0, 1453.0, 1454.0, 1455.0, 1456.0],       [1457.0, 1458.0, 1459.0, 1460.0, 1461.0, 1462.0, 1463.0],       [1464.0, 1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0]]]],    [[[[1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0, 1477.0],       [1478.0, 1479.0, 1480.0, 1481.0, 1482.0, 1483.0, 1484.0],       [1485.0, 1486.0, 1487.0, 1488.0, 1489.0, 1490.0, 1491.0],       [1492.0, 1493.0, 1494.0, 1495.0, 1496.0, 1497.0, 1498.0],       [1499.0, 1500.0, 1501.0, 1502.0, 1503.0, 1504.0, 1505.0]],      [[1506.0, 1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0],       [1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0, 1519.0],       [1520.0, 1521.0, 1522.0, 1523.0, 1524.0, 1525.0, 1526.0],       [1527.0, 1528.0, 1529.0, 1530.0, 1531.0, 1532.0, 1533.0],       [1534.0, 1535.0, 1536.0, 1537.0, 1538.0, 1539.0, 1540.0]]],     [[[1541.0, 1542.0, 1543.0, 1544.0, 1545.0, 1546.0, 1547.0],       [1548.0, 1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0],       [1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0, 1561.0],       [1562.0, 1563.0, 1564.0, 1565.0, 1566.0, 1567.0, 1568.0],       [1569.0, 1570.0, 1571.0, 1572.0, 1573.0, 1574.0, 1575.0]],      [[1576.0, 1577.0, 1578.0, 1579.0, 1580.0, 1581.0, 1582.0],       [1583.0, 1584.0, 1585.0, 1586.0, 1587.0, 1588.0, 1589.0],       [1590.0, 1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0],       [1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0, 1603.0],       [1604.0, 1605.0, 1606.0, 1607.0, 1608.0, 1609.0, 1610.0]]],     [[[1611.0, 1612.0, 1613.0, 1614.0, 1615.0, 1616.0, 1617.0],       [1618.0, 1619.0, 1620.0, 1621.0, 1622.0, 1623.0, 1624.0],       [1625.0, 1626.0, 1627.0, 1628.0, 1629.0, 1630.0, 1631.0],       [1632.0, 1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0],       [1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0, 1645.0]],      [[1646.0, 1647.0, 1648.0, 1649.0, 1650.0, 1651.0, 1652.0],       [1653.0, 1654.0, 1655.0, 1656.0, 1657.0, 1658.0, 1659.0],       [1660.0, 1661.0, 1662.0, 1663.0, 1664.0, 1665.0, 1666.0],       [1667.0, 1668.0, 1669.0, 1670.0, 1671.0, 1672.0, 1673.0],       [1674.0, 1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]]],     [[[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0, 1687.0],       [1688.0, 1689.0, 1690.0, 1691.0, 1692.0, 1693.0, 1694.0],       [1695.0, 1696.0, 1697.0, 1698.0, 1699.0, 1700.0, 1701.0],       [1702.0, 1703.0, 1704.0, 1705.0, 1706.0, 1707.0, 1708.0],       [1709.0, 1710.0, 1711.0, 1712.0, 1713.0, 1714.0, 1715.0]],      [[1716.0, 1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0],       [1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0, 1729.0],       [1730.0, 1731.0, 1732.0, 1733.0, 1734.0, 1735.0, 1736.0],       [1737.0, 1738.0, 1739.0, 1740.0, 1741.0, 1742.0, 1743.0],       [1744.0, 1745.0, 1746.0, 1747.0, 1748.0, 1749.0, 1750.0]]],     [[[1751.0, 1752.0, 1753.0, 1754.0, 1755.0, 1756.0, 1757.0],       [1758.0, 1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0],       [1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0, 1771.0],       [1772.0, 1773.0, 1774.0, 1775.0, 1776.0, 1777.0, 1778.0],       [1779.0, 1780.0, 1781.0, 1782.0, 1783.0, 1784.0, 1785.0]],      [[1786.0, 1787.0, 1788.0, 1789.0, 1790.0, 1791.0, 1792.0],       [1793.0, 1794.0, 1795.0, 1796.0, 1797.0, 1798.0, 1799.0],       [1800.0, 1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0],       [1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0, 1813.0],       [1814.0, 1815.0, 1816.0, 1817.0, 1818.0, 1819.0, 1820.0]]],     [[[1821.0, 1822.0, 1823.0, 1824.0, 1825.0, 1826.0, 1827.0],       [1828.0, 1829.0, 1830.0, 1831.0, 1832.0, 1833.0, 1834.0],       [1835.0, 1836.0, 1837.0, 1838.0, 1839.0, 1840.0, 1841.0],       [1842.0, 1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0],       [1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0, 1855.0]],      [[1856.0, 1857.0, 1858.0, 1859.0, 1860.0, 1861.0, 1862.0],       [1863.0, 1864.0, 1865.0, 1866.0, 1867.0, 1868.0, 1869.0],       [1870.0, 1871.0, 1872.0, 1873.0, 1874.0, 1875.0, 1876.0],       [1877.0, 1878.0, 1879.0, 1880.0, 1881.0, 1882.0, 1883.0],       [1884.0, 1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0]]],     [[[1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0, 1897.0],       [1898.0, 1899.0, 1900.0, 1901.0, 1902.0, 1903.0, 1904.0],       [1905.0, 1906.0, 1907.0, 1908.0, 1909.0, 1910.0, 1911.0],       [1912.0, 1913.0, 1914.0, 1915.0, 1916.0, 1917.0, 1918.0],       [1919.0, 1920.0, 1921.0, 1922.0, 1923.0, 1924.0, 1925.0]],      [[1926.0, 1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0],       [1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0, 1939.0],       [1940.0, 1941.0, 1942.0, 1943.0, 1944.0, 1945.0, 1946.0],       [1947.0, 1948.0, 1949.0, 1950.0, 1951.0, 1952.0, 1953.0],       [1954.0, 1955.0, 1956.0, 1957.0, 1958.0, 1959.0, 1960.0]]]]]]] shape=[1, 2, 2, 7, 2, 5, 7], strides=[1960, 980, 490, 70, 35, 7, 1], layout=C (0x1)), I32([2, 2, 1] shape=[3], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [1, 1],  [3, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 3337492897 259912945 3417862821 2209131800 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],      [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0]],     [[29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],      [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0],      [43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],      [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0]],     [[57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],      [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0],      [71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],      [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0]],     [[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],      [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],      [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0],      [106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0]],     [[113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],      [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0],      [127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],      [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0]],     [[141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],      [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],      [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],      [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0]],     [[169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0],      [176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],      [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],      [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0]]],    [[[197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],      [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0],      [211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],      [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0]],     [[225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],      [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],      [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0],      [246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0]],     [[253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],      [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],      [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],      [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0]],     [[281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],      [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0],      [295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],      [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0]],     [[309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0],      [316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],      [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],      [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0]],     [[337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],      [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0],      [351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],      [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0]],     [[365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],      [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0],      [379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0],      [386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0]]]],   [[[[393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],      [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],      [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],      [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]],     [[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],      [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],      [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],      [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0]],     [[449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0],      [456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0],      [463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],      [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0]],     [[477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],      [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0],      [491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],      [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0]],     [[505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],      [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],      [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0],      [526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0]],     [[533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],      [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0],      [547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],      [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0]],     [[561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],      [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],      [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],      [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0]]],    [[[589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0],      [596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],      [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],      [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0]],     [[617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],      [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0],      [631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],      [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0]],     [[645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],      [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0],      [659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0],      [666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0]],     [[673.0, 674.0, 675.0, 676.0, 677.0, 678.0, 679.0],      [680.0, 681.0, 682.0, 683.0, 684.0, 685.0, 686.0],      [687.0, 688.0, 689.0, 690.0, 691.0, 692.0, 693.0],      [694.0, 695.0, 696.0, 697.0, 698.0, 699.0, 700.0]],     [[701.0, 702.0, 703.0, 704.0, 705.0, 706.0, 707.0],      [708.0, 709.0, 710.0, 711.0, 712.0, 713.0, 714.0],      [715.0, 716.0, 717.0, 718.0, 719.0, 720.0, 721.0],      [722.0, 723.0, 724.0, 725.0, 726.0, 727.0, 728.0]],     [[729.0, 730.0, 731.0, 732.0, 733.0, 734.0, 735.0],      [736.0, 737.0, 738.0, 739.0, 740.0, 741.0, 742.0],      [743.0, 744.0, 745.0, 746.0, 747.0, 748.0, 749.0],      [750.0, 751.0, 752.0, 753.0, 754.0, 755.0, 756.0]],     [[757.0, 758.0, 759.0, 760.0, 761.0, 762.0, 763.0],      [764.0, 765.0, 766.0, 767.0, 768.0, 769.0, 770.0],      [771.0, 772.0, 773.0, 774.0, 775.0, 776.0, 777.0],      [778.0, 779.0, 780.0, 781.0, 782.0, 783.0, 784.0]]]],   [[[[785.0, 786.0, 787.0, 788.0, 789.0, 790.0, 791.0],      [792.0, 793.0, 794.0, 795.0, 796.0, 797.0, 798.0],      [799.0, 800.0, 801.0, 802.0, 803.0, 804.0, 805.0],      [806.0, 807.0, 808.0, 809.0, 810.0, 811.0, 812.0]],     [[813.0, 814.0, 815.0, 816.0, 817.0, 818.0, 819.0],      [820.0, 821.0, 822.0, 823.0, 824.0, 825.0, 826.0],      [827.0, 828.0, 829.0, 830.0, 831.0, 832.0, 833.0],      [834.0, 835.0, 836.0, 837.0, 838.0, 839.0, 840.0]],     [[841.0, 842.0, 843.0, 844.0, 845.0, 846.0, 847.0],      [848.0, 849.0, 850.0, 851.0, 852.0, 853.0, 854.0],      [855.0, 856.0, 857.0, 858.0, 859.0, 860.0, 861.0],      [862.0, 863.0, 864.0, 865.0, 866.0, 867.0, 868.0]],     [[869.0, 870.0, 871.0, 872.0, 873.0, 874.0, 875.0],      [876.0, 877.0, 878.0, 879.0, 880.0, 881.0, 882.0],      [883.0, 884.0, 885.0, 886.0, 887.0, 888.0, 889.0],      [890.0, 891.0, 892.0, 893.0, 894.0, 895.0, 896.0]],     [[897.0, 898.0, 899.0, 900.0, 901.0, 902.0, 903.0],      [904.0, 905.0, 906.0, 907.0, 908.0, 909.0, 910.0],      [911.0, 912.0, 913.0, 914.0, 915.0, 916.0, 917.0],      [918.0, 919.0, 920.0, 921.0, 922.0, 923.0, 924.0]],     [[925.0, 926.0, 927.0, 928.0, 929.0, 930.0, 931.0],      [932.0, 933.0, 934.0, 935.0, 936.0, 937.0, 938.0],      [939.0, 940.0, 941.0, 942.0, 943.0, 944.0, 945.0],      [946.0, 947.0, 948.0, 949.0, 950.0, 951.0, 952.0]],     [[953.0, 954.0, 955.0, 956.0, 957.0, 958.0, 959.0],      [960.0, 961.0, 962.0, 963.0, 964.0, 965.0, 966.0],      [967.0, 968.0, 969.0, 970.0, 971.0, 972.0, 973.0],      [974.0, 975.0, 976.0, 977.0, 978.0, 979.0, 980.0]]],    [[[981.0, 982.0, 983.0, 984.0, 985.0, 986.0, 987.0],      [988.0, 989.0, 990.0, 991.0, 992.0, 993.0, 994.0],      [995.0, 996.0, 997.0, 998.0, 999.0, 1000.0, 1001.0],      [1002.0, 1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0]],     [[1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0, 1015.0],      [1016.0, 1017.0, 1018.0, 1019.0, 1020.0, 1021.0, 1022.0],      [1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0],      [1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0]],     [[1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0],      [1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0],      [1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0, 1057.0],      [1058.0, 1059.0, 1060.0, 1061.0, 1062.0, 1063.0, 1064.0]],     [[1065.0, 1066.0, 1067.0, 1068.0, 1069.0, 1070.0, 1071.0],      [1072.0, 1073.0, 1074.0, 1075.0, 1076.0, 1077.0, 1078.0],      [1079.0, 1080.0, 1081.0, 1082.0, 1083.0, 1084.0, 1085.0],      [1086.0, 1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0]],     [[1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0, 1099.0],      [1100.0, 1101.0, 1102.0, 1103.0, 1104.0, 1105.0, 1106.0],      [1107.0, 1108.0, 1109.0, 1110.0, 1111.0, 1112.0, 1113.0],      [1114.0, 1115.0, 1116.0, 1117.0, 1118.0, 1119.0, 1120.0]],     [[1121.0, 1122.0, 1123.0, 1124.0, 1125.0, 1126.0, 1127.0],      [1128.0, 1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0],      [1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0, 1141.0],      [1142.0, 1143.0, 1144.0, 1145.0, 1146.0, 1147.0, 1148.0]],     [[1149.0, 1150.0, 1151.0, 1152.0, 1153.0, 1154.0, 1155.0],      [1156.0, 1157.0, 1158.0, 1159.0, 1160.0, 1161.0, 1162.0],      [1163.0, 1164.0, 1165.0, 1166.0, 1167.0, 1168.0, 1169.0],      [1170.0, 1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0]]]],   [[[[1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0, 1183.0],      [1184.0, 1185.0, 1186.0, 1187.0, 1188.0, 1189.0, 1190.0],      [1191.0, 1192.0, 1193.0, 1194.0, 1195.0, 1196.0, 1197.0],      [1198.0, 1199.0, 1200.0, 1201.0, 1202.0, 1203.0, 1204.0]],     [[1205.0, 1206.0, 1207.0, 1208.0, 1209.0, 1210.0, 1211.0],      [1212.0, 1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0],      [1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0, 1225.0],      [1226.0, 1227.0, 1228.0, 1229.0, 1230.0, 1231.0, 1232.0]],     [[1233.0, 1234.0, 1235.0, 1236.0, 1237.0, 1238.0, 1239.0],      [1240.0, 1241.0, 1242.0, 1243.0, 1244.0, 1245.0, 1246.0],      [1247.0, 1248.0, 1249.0, 1250.0, 1251.0, 1252.0, 1253.0],      [1254.0, 1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]],     [[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0, 1267.0],      [1268.0, 1269.0, 1270.0, 1271.0, 1272.0, 1273.0, 1274.0],      [1275.0, 1276.0, 1277.0, 1278.0, 1279.0, 1280.0, 1281.0],      [1282.0, 1283.0, 1284.0, 1285.0, 1286.0, 1287.0, 1288.0]],     [[1289.0, 1290.0, 1291.0, 1292.0, 1293.0, 1294.0, 1295.0],      [1296.0, 1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0],      [1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0, 1309.0],      [1310.0, 1311.0, 1312.0, 1313.0, 1314.0, 1315.0, 1316.0]],     [[1317.0, 1318.0, 1319.0, 1320.0, 1321.0, 1322.0, 1323.0],      [1324.0, 1325.0, 1326.0, 1327.0, 1328.0, 1329.0, 1330.0],      [1331.0, 1332.0, 1333.0, 1334.0, 1335.0, 1336.0, 1337.0],      [1338.0, 1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0]],     [[1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0, 1351.0],      [1352.0, 1353.0, 1354.0, 1355.0, 1356.0, 1357.0, 1358.0],      [1359.0, 1360.0, 1361.0, 1362.0, 1363.0, 1364.0, 1365.0],      [1366.0, 1367.0, 1368.0, 1369.0, 1370.0, 1371.0, 1372.0]]],    [[[1373.0, 1374.0, 1375.0, 1376.0, 1377.0, 1378.0, 1379.0],      [1380.0, 1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0],      [1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0, 1393.0],      [1394.0, 1395.0, 1396.0, 1397.0, 1398.0, 1399.0, 1400.0]],     [[1401.0, 1402.0, 1403.0, 1404.0, 1405.0, 1406.0, 1407.0],      [1408.0, 1409.0, 1410.0, 1411.0, 1412.0, 1413.0, 1414.0],      [1415.0, 1416.0, 1417.0, 1418.0, 1419.0, 1420.0, 1421.0],      [1422.0, 1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0]],     [[1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0, 1435.0],      [1436.0, 1437.0, 1438.0, 1439.0, 1440.0, 1441.0, 1442.0],      [1443.0, 1444.0, 1445.0, 1446.0, 1447.0, 1448.0, 1449.0],      [1450.0, 1451.0, 1452.0, 1453.0, 1454.0, 1455.0, 1456.0]],     [[1457.0, 1458.0, 1459.0, 1460.0, 1461.0, 1462.0, 1463.0],      [1464.0, 1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0],      [1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0, 1477.0],      [1478.0, 1479.0, 1480.0, 1481.0, 1482.0, 1483.0, 1484.0]],     [[1485.0, 1486.0, 1487.0, 1488.0, 1489.0, 1490.0, 1491.0],      [1492.0, 1493.0, 1494.0, 1495.0, 1496.0, 1497.0, 1498.0],      [1499.0, 1500.0, 1501.0, 1502.0, 1503.0, 1504.0, 1505.0],      [1506.0, 1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0]],     [[1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0, 1519.0],      [1520.0, 1521.0, 1522.0, 1523.0, 1524.0, 1525.0, 1526.0],      [1527.0, 1528.0, 1529.0, 1530.0, 1531.0, 1532.0, 1533.0],      [1534.0, 1535.0, 1536.0, 1537.0, 1538.0, 1539.0, 1540.0]],     [[1541.0, 1542.0, 1543.0, 1544.0, 1545.0, 1546.0, 1547.0],      [1548.0, 1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0],      [1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0, 1561.0],      [1562.0, 1563.0, 1564.0, 1565.0, 1566.0, 1567.0, 1568.0]]]],   [[[[1569.0, 1570.0, 1571.0, 1572.0, 1573.0, 1574.0, 1575.0],      [1576.0, 1577.0, 1578.0, 1579.0, 1580.0, 1581.0, 1582.0],      [1583.0, 1584.0, 1585.0, 1586.0, 1587.0, 1588.0, 1589.0],      [1590.0, 1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0]],     [[1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0, 1603.0],      [1604.0, 1605.0, 1606.0, 1607.0, 1608.0, 1609.0, 1610.0],      [1611.0, 1612.0, 1613.0, 1614.0, 1615.0, 1616.0, 1617.0],      [1618.0, 1619.0, 1620.0, 1621.0, 1622.0, 1623.0, 1624.0]],     [[1625.0, 1626.0, 1627.0, 1628.0, 1629.0, 1630.0, 1631.0],      [1632.0, 1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0],      [1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0, 1645.0],      [1646.0, 1647.0, 1648.0, 1649.0, 1650.0, 1651.0, 1652.0]],     [[1653.0, 1654.0, 1655.0, 1656.0, 1657.0, 1658.0, 1659.0],      [1660.0, 1661.0, 1662.0, 1663.0, 1664.0, 1665.0, 1666.0],      [1667.0, 1668.0, 1669.0, 1670.0, 1671.0, 1672.0, 1673.0],      [1674.0, 1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]],     [[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0, 1687.0],      [1688.0, 1689.0, 1690.0, 1691.0, 1692.0, 1693.0, 1694.0],      [1695.0, 1696.0, 1697.0, 1698.0, 1699.0, 1700.0, 1701.0],      [1702.0, 1703.0, 1704.0, 1705.0, 1706.0, 1707.0, 1708.0]],     [[1709.0, 1710.0, 1711.0, 1712.0, 1713.0, 1714.0, 1715.0],      [1716.0, 1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0],      [1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0, 1729.0],      [1730.0, 1731.0, 1732.0, 1733.0, 1734.0, 1735.0, 1736.0]],     [[1737.0, 1738.0, 1739.0, 1740.0, 1741.0, 1742.0, 1743.0],      [1744.0, 1745.0, 1746.0, 1747.0, 1748.0, 1749.0, 1750.0],      [1751.0, 1752.0, 1753.0, 1754.0, 1755.0, 1756.0, 1757.0],      [1758.0, 1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0]]],    [[[1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0, 1771.0],      [1772.0, 1773.0, 1774.0, 1775.0, 1776.0, 1777.0, 1778.0],      [1779.0, 1780.0, 1781.0, 1782.0, 1783.0, 1784.0, 1785.0],      [1786.0, 1787.0, 1788.0, 1789.0, 1790.0, 1791.0, 1792.0]],     [[1793.0, 1794.0, 1795.0, 1796.0, 1797.0, 1798.0, 1799.0],      [1800.0, 1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0],      [1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0, 1813.0],      [1814.0, 1815.0, 1816.0, 1817.0, 1818.0, 1819.0, 1820.0]],     [[1821.0, 1822.0, 1823.0, 1824.0, 1825.0, 1826.0, 1827.0],      [1828.0, 1829.0, 1830.0, 1831.0, 1832.0, 1833.0, 1834.0],      [1835.0, 1836.0, 1837.0, 1838.0, 1839.0, 1840.0, 1841.0],      [1842.0, 1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0]],     [[1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0, 1855.0],      [1856.0, 1857.0, 1858.0, 1859.0, 1860.0, 1861.0, 1862.0],      [1863.0, 1864.0, 1865.0, 1866.0, 1867.0, 1868.0, 1869.0],      [1870.0, 1871.0, 1872.0, 1873.0, 1874.0, 1875.0, 1876.0]],     [[1877.0, 1878.0, 1879.0, 1880.0, 1881.0, 1882.0, 1883.0],      [1884.0, 1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0],      [1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0, 1897.0],      [1898.0, 1899.0, 1900.0, 1901.0, 1902.0, 1903.0, 1904.0]],     [[1905.0, 1906.0, 1907.0, 1908.0, 1909.0, 1910.0, 1911.0],      [1912.0, 1913.0, 1914.0, 1915.0, 1916.0, 1917.0, 1918.0],      [1919.0, 1920.0, 1921.0, 1922.0, 1923.0, 1924.0, 1925.0],      [1926.0, 1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0]],     [[1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0, 1939.0],      [1940.0, 1941.0, 1942.0, 1943.0, 1944.0, 1945.0, 1946.0],      [1947.0, 1948.0, 1949.0, 1950.0, 1951.0, 1952.0, 1953.0],      [1954.0, 1955.0, 1956.0, 1957.0, 1958.0, 1959.0, 1960.0]]]]]] shape=[1, 5, 2, 7, 4, 7], strides=[1960, 392, 196, 28, 7, 1], layout=C (0x1)), I32([1, 1, 3] shape=[3], strides=[1], layout=C | F (0x3)), I32([[1, 1],  [0, 1],  [3, 2]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 2274221103 807336656 2265941156 751830284 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],     [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0],     [29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],     [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0]],    [[43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],     [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0],     [57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],     [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0],     [71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],     [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0]],    [[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],     [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],     [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0],     [106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0],     [113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],     [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0]],    [[127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],     [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0],     [141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],     [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],     [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],     [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0]],    [[169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0],     [176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],     [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],     [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0],     [197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],     [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0]],    [[211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],     [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0],     [225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],     [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],     [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0],     [246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0]],    [[253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],     [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],     [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],     [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0],     [281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],     [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0]]],   [[[295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],     [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0],     [309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0],     [316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],     [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],     [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0]],    [[337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],     [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0],     [351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],     [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0],     [365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],     [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0]],    [[379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0],     [386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0],     [393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],     [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],     [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],     [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]],    [[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],     [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],     [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],     [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0],     [449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0],     [456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0]],    [[463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],     [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0],     [477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],     [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0],     [491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],     [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0]],    [[505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],     [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],     [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0],     [526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0],     [533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],     [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0]],    [[547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],     [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0],     [561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],     [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],     [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],     [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0]]],   [[[589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0],     [596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],     [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],     [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0],     [617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],     [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0]],    [[631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],     [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0],     [645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],     [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0],     [659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0],     [666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0]],    [[673.0, 674.0, 675.0, 676.0, 677.0, 678.0, 679.0],     [680.0, 681.0, 682.0, 683.0, 684.0, 685.0, 686.0],     [687.0, 688.0, 689.0, 690.0, 691.0, 692.0, 693.0],     [694.0, 695.0, 696.0, 697.0, 698.0, 699.0, 700.0],     [701.0, 702.0, 703.0, 704.0, 705.0, 706.0, 707.0],     [708.0, 709.0, 710.0, 711.0, 712.0, 713.0, 714.0]],    [[715.0, 716.0, 717.0, 718.0, 719.0, 720.0, 721.0],     [722.0, 723.0, 724.0, 725.0, 726.0, 727.0, 728.0],     [729.0, 730.0, 731.0, 732.0, 733.0, 734.0, 735.0],     [736.0, 737.0, 738.0, 739.0, 740.0, 741.0, 742.0],     [743.0, 744.0, 745.0, 746.0, 747.0, 748.0, 749.0],     [750.0, 751.0, 752.0, 753.0, 754.0, 755.0, 756.0]],    [[757.0, 758.0, 759.0, 760.0, 761.0, 762.0, 763.0],     [764.0, 765.0, 766.0, 767.0, 768.0, 769.0, 770.0],     [771.0, 772.0, 773.0, 774.0, 775.0, 776.0, 777.0],     [778.0, 779.0, 780.0, 781.0, 782.0, 783.0, 784.0],     [785.0, 786.0, 787.0, 788.0, 789.0, 790.0, 791.0],     [792.0, 793.0, 794.0, 795.0, 796.0, 797.0, 798.0]],    [[799.0, 800.0, 801.0, 802.0, 803.0, 804.0, 805.0],     [806.0, 807.0, 808.0, 809.0, 810.0, 811.0, 812.0],     [813.0, 814.0, 815.0, 816.0, 817.0, 818.0, 819.0],     [820.0, 821.0, 822.0, 823.0, 824.0, 825.0, 826.0],     [827.0, 828.0, 829.0, 830.0, 831.0, 832.0, 833.0],     [834.0, 835.0, 836.0, 837.0, 838.0, 839.0, 840.0]],    [[841.0, 842.0, 843.0, 844.0, 845.0, 846.0, 847.0],     [848.0, 849.0, 850.0, 851.0, 852.0, 853.0, 854.0],     [855.0, 856.0, 857.0, 858.0, 859.0, 860.0, 861.0],     [862.0, 863.0, 864.0, 865.0, 866.0, 867.0, 868.0],     [869.0, 870.0, 871.0, 872.0, 873.0, 874.0, 875.0],     [876.0, 877.0, 878.0, 879.0, 880.0, 881.0, 882.0]]],   [[[883.0, 884.0, 885.0, 886.0, 887.0, 888.0, 889.0],     [890.0, 891.0, 892.0, 893.0, 894.0, 895.0, 896.0],     [897.0, 898.0, 899.0, 900.0, 901.0, 902.0, 903.0],     [904.0, 905.0, 906.0, 907.0, 908.0, 909.0, 910.0],     [911.0, 912.0, 913.0, 914.0, 915.0, 916.0, 917.0],     [918.0, 919.0, 920.0, 921.0, 922.0, 923.0, 924.0]],    [[925.0, 926.0, 927.0, 928.0, 929.0, 930.0, 931.0],     [932.0, 933.0, 934.0, 935.0, 936.0, 937.0, 938.0],     [939.0, 940.0, 941.0, 942.0, 943.0, 944.0, 945.0],     [946.0, 947.0, 948.0, 949.0, 950.0, 951.0, 952.0],     [953.0, 954.0, 955.0, 956.0, 957.0, 958.0, 959.0],     [960.0, 961.0, 962.0, 963.0, 964.0, 965.0, 966.0]],    [[967.0, 968.0, 969.0, 970.0, 971.0, 972.0, 973.0],     [974.0, 975.0, 976.0, 977.0, 978.0, 979.0, 980.0],     [981.0, 982.0, 983.0, 984.0, 985.0, 986.0, 987.0],     [988.0, 989.0, 990.0, 991.0, 992.0, 993.0, 994.0],     [995.0, 996.0, 997.0, 998.0, 999.0, 1000.0, 1001.0],     [1002.0, 1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0]],    [[1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0, 1015.0],     [1016.0, 1017.0, 1018.0, 1019.0, 1020.0, 1021.0, 1022.0],     [1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0],     [1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0],     [1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0],     [1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]],    [[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0, 1057.0],     [1058.0, 1059.0, 1060.0, 1061.0, 1062.0, 1063.0, 1064.0],     [1065.0, 1066.0, 1067.0, 1068.0, 1069.0, 1070.0, 1071.0],     [1072.0, 1073.0, 1074.0, 1075.0, 1076.0, 1077.0, 1078.0],     [1079.0, 1080.0, 1081.0, 1082.0, 1083.0, 1084.0, 1085.0],     [1086.0, 1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0]],    [[1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0, 1099.0],     [1100.0, 1101.0, 1102.0, 1103.0, 1104.0, 1105.0, 1106.0],     [1107.0, 1108.0, 1109.0, 1110.0, 1111.0, 1112.0, 1113.0],     [1114.0, 1115.0, 1116.0, 1117.0, 1118.0, 1119.0, 1120.0],     [1121.0, 1122.0, 1123.0, 1124.0, 1125.0, 1126.0, 1127.0],     [1128.0, 1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0]],    [[1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0, 1141.0],     [1142.0, 1143.0, 1144.0, 1145.0, 1146.0, 1147.0, 1148.0],     [1149.0, 1150.0, 1151.0, 1152.0, 1153.0, 1154.0, 1155.0],     [1156.0, 1157.0, 1158.0, 1159.0, 1160.0, 1161.0, 1162.0],     [1163.0, 1164.0, 1165.0, 1166.0, 1167.0, 1168.0, 1169.0],     [1170.0, 1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0]]],   [[[1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0, 1183.0],     [1184.0, 1185.0, 1186.0, 1187.0, 1188.0, 1189.0, 1190.0],     [1191.0, 1192.0, 1193.0, 1194.0, 1195.0, 1196.0, 1197.0],     [1198.0, 1199.0, 1200.0, 1201.0, 1202.0, 1203.0, 1204.0],     [1205.0, 1206.0, 1207.0, 1208.0, 1209.0, 1210.0, 1211.0],     [1212.0, 1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0]],    [[1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0, 1225.0],     [1226.0, 1227.0, 1228.0, 1229.0, 1230.0, 1231.0, 1232.0],     [1233.0, 1234.0, 1235.0, 1236.0, 1237.0, 1238.0, 1239.0],     [1240.0, 1241.0, 1242.0, 1243.0, 1244.0, 1245.0, 1246.0],     [1247.0, 1248.0, 1249.0, 1250.0, 1251.0, 1252.0, 1253.0],     [1254.0, 1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]],    [[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0, 1267.0],     [1268.0, 1269.0, 1270.0, 1271.0, 1272.0, 1273.0, 1274.0],     [1275.0, 1276.0, 1277.0, 1278.0, 1279.0, 1280.0, 1281.0],     [1282.0, 1283.0, 1284.0, 1285.0, 1286.0, 1287.0, 1288.0],     [1289.0, 1290.0, 1291.0, 1292.0, 1293.0, 1294.0, 1295.0],     [1296.0, 1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0]],    [[1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0, 1309.0],     [1310.0, 1311.0, 1312.0, 1313.0, 1314.0, 1315.0, 1316.0],     [1317.0, 1318.0, 1319.0, 1320.0, 1321.0, 1322.0, 1323.0],     [1324.0, 1325.0, 1326.0, 1327.0, 1328.0, 1329.0, 1330.0],     [1331.0, 1332.0, 1333.0, 1334.0, 1335.0, 1336.0, 1337.0],     [1338.0, 1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0]],    [[1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0, 1351.0],     [1352.0, 1353.0, 1354.0, 1355.0, 1356.0, 1357.0, 1358.0],     [1359.0, 1360.0, 1361.0, 1362.0, 1363.0, 1364.0, 1365.0],     [1366.0, 1367.0, 1368.0, 1369.0, 1370.0, 1371.0, 1372.0],     [1373.0, 1374.0, 1375.0, 1376.0, 1377.0, 1378.0, 1379.0],     [1380.0, 1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0]],    [[1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0, 1393.0],     [1394.0, 1395.0, 1396.0, 1397.0, 1398.0, 1399.0, 1400.0],     [1401.0, 1402.0, 1403.0, 1404.0, 1405.0, 1406.0, 1407.0],     [1408.0, 1409.0, 1410.0, 1411.0, 1412.0, 1413.0, 1414.0],     [1415.0, 1416.0, 1417.0, 1418.0, 1419.0, 1420.0, 1421.0],     [1422.0, 1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0]],    [[1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0, 1435.0],     [1436.0, 1437.0, 1438.0, 1439.0, 1440.0, 1441.0, 1442.0],     [1443.0, 1444.0, 1445.0, 1446.0, 1447.0, 1448.0, 1449.0],     [1450.0, 1451.0, 1452.0, 1453.0, 1454.0, 1455.0, 1456.0],     [1457.0, 1458.0, 1459.0, 1460.0, 1461.0, 1462.0, 1463.0],     [1464.0, 1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0]]],   [[[1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0, 1477.0],     [1478.0, 1479.0, 1480.0, 1481.0, 1482.0, 1483.0, 1484.0],     [1485.0, 1486.0, 1487.0, 1488.0, 1489.0, 1490.0, 1491.0],     [1492.0, 1493.0, 1494.0, 1495.0, 1496.0, 1497.0, 1498.0],     [1499.0, 1500.0, 1501.0, 1502.0, 1503.0, 1504.0, 1505.0],     [1506.0, 1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0]],    [[1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0, 1519.0],     [1520.0, 1521.0, 1522.0, 1523.0, 1524.0, 1525.0, 1526.0],     [1527.0, 1528.0, 1529.0, 1530.0, 1531.0, 1532.0, 1533.0],     [1534.0, 1535.0, 1536.0, 1537.0, 1538.0, 1539.0, 1540.0],     [1541.0, 1542.0, 1543.0, 1544.0, 1545.0, 1546.0, 1547.0],     [1548.0, 1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0]],    [[1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0, 1561.0],     [1562.0, 1563.0, 1564.0, 1565.0, 1566.0, 1567.0, 1568.0],     [1569.0, 1570.0, 1571.0, 1572.0, 1573.0, 1574.0, 1575.0],     [1576.0, 1577.0, 1578.0, 1579.0, 1580.0, 1581.0, 1582.0],     [1583.0, 1584.0, 1585.0, 1586.0, 1587.0, 1588.0, 1589.0],     [1590.0, 1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0]],    [[1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0, 1603.0],     [1604.0, 1605.0, 1606.0, 1607.0, 1608.0, 1609.0, 1610.0],     [1611.0, 1612.0, 1613.0, 1614.0, 1615.0, 1616.0, 1617.0],     [1618.0, 1619.0, 1620.0, 1621.0, 1622.0, 1623.0, 1624.0],     [1625.0, 1626.0, 1627.0, 1628.0, 1629.0, 1630.0, 1631.0],     [1632.0, 1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0]],    [[1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0, 1645.0],     [1646.0, 1647.0, 1648.0, 1649.0, 1650.0, 1651.0, 1652.0],     [1653.0, 1654.0, 1655.0, 1656.0, 1657.0, 1658.0, 1659.0],     [1660.0, 1661.0, 1662.0, 1663.0, 1664.0, 1665.0, 1666.0],     [1667.0, 1668.0, 1669.0, 1670.0, 1671.0, 1672.0, 1673.0],     [1674.0, 1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]],    [[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0, 1687.0],     [1688.0, 1689.0, 1690.0, 1691.0, 1692.0, 1693.0, 1694.0],     [1695.0, 1696.0, 1697.0, 1698.0, 1699.0, 1700.0, 1701.0],     [1702.0, 1703.0, 1704.0, 1705.0, 1706.0, 1707.0, 1708.0],     [1709.0, 1710.0, 1711.0, 1712.0, 1713.0, 1714.0, 1715.0],     [1716.0, 1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0]],    [[1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0, 1729.0],     [1730.0, 1731.0, 1732.0, 1733.0, 1734.0, 1735.0, 1736.0],     [1737.0, 1738.0, 1739.0, 1740.0, 1741.0, 1742.0, 1743.0],     [1744.0, 1745.0, 1746.0, 1747.0, 1748.0, 1749.0, 1750.0],     [1751.0, 1752.0, 1753.0, 1754.0, 1755.0, 1756.0, 1757.0],     [1758.0, 1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0]]]],  [[[[1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0, 1771.0],     [1772.0, 1773.0, 1774.0, 1775.0, 1776.0, 1777.0, 1778.0],     [1779.0, 1780.0, 1781.0, 1782.0, 1783.0, 1784.0, 1785.0],     [1786.0, 1787.0, 1788.0, 1789.0, 1790.0, 1791.0, 1792.0],     [1793.0, 1794.0, 1795.0, 1796.0, 1797.0, 1798.0, 1799.0],     [1800.0, 1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0]],    [[1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0, 1813.0],     [1814.0, 1815.0, 1816.0, 1817.0, 1818.0, 1819.0, 1820.0],     [1821.0, 1822.0, 1823.0, 1824.0, 1825.0, 1826.0, 1827.0],     [1828.0, 1829.0, 1830.0, 1831.0, 1832.0, 1833.0, 1834.0],     [1835.0, 1836.0, 1837.0, 1838.0, 1839.0, 1840.0, 1841.0],     [1842.0, 1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0]],    [[1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0, 1855.0],     [1856.0, 1857.0, 1858.0, 1859.0, 1860.0, 1861.0, 1862.0],     [1863.0, 1864.0, 1865.0, 1866.0, 1867.0, 1868.0, 1869.0],     [1870.0, 1871.0, 1872.0, 1873.0, 1874.0, 1875.0, 1876.0],     [1877.0, 1878.0, 1879.0, 1880.0, 1881.0, 1882.0, 1883.0],     [1884.0, 1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0]],    [[1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0, 1897.0],     [1898.0, 1899.0, 1900.0, 1901.0, 1902.0, 1903.0, 1904.0],     [1905.0, 1906.0, 1907.0, 1908.0, 1909.0, 1910.0, 1911.0],     [1912.0, 1913.0, 1914.0, 1915.0, 1916.0, 1917.0, 1918.0],     [1919.0, 1920.0, 1921.0, 1922.0, 1923.0, 1924.0, 1925.0],     [1926.0, 1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0]],    [[1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0, 1939.0],     [1940.0, 1941.0, 1942.0, 1943.0, 1944.0, 1945.0, 1946.0],     [1947.0, 1948.0, 1949.0, 1950.0, 1951.0, 1952.0, 1953.0],     [1954.0, 1955.0, 1956.0, 1957.0, 1958.0, 1959.0, 1960.0],     [1961.0, 1962.0, 1963.0, 1964.0, 1965.0, 1966.0, 1967.0],     [1968.0, 1969.0, 1970.0, 1971.0, 1972.0, 1973.0, 1974.0]],    [[1975.0, 1976.0, 1977.0, 1978.0, 1979.0, 1980.0, 1981.0],     [1982.0, 1983.0, 1984.0, 1985.0, 1986.0, 1987.0, 1988.0],     [1989.0, 1990.0, 1991.0, 1992.0, 1993.0, 1994.0, 1995.0],     [1996.0, 1997.0, 1998.0, 1999.0, 2000.0, 2001.0, 2002.0],     [2003.0, 2004.0, 2005.0, 2006.0, 2007.0, 2008.0, 2009.0],     [2010.0, 2011.0, 2012.0, 2013.0, 2014.0, 2015.0, 2016.0]],    [[2017.0, 2018.0, 2019.0, 2020.0, 2021.0, 2022.0, 2023.0],     [2024.0, 2025.0, 2026.0, 2027.0, 2028.0, 2029.0, 2030.0],     [2031.0, 2032.0, 2033.0, 2034.0, 2035.0, 2036.0, 2037.0],     [2038.0, 2039.0, 2040.0, 2041.0, 2042.0, 2043.0, 2044.0],     [2045.0, 2046.0, 2047.0, 2048.0, 2049.0, 2050.0, 2051.0],     [2052.0, 2053.0, 2054.0, 2055.0, 2056.0, 2057.0, 2058.0]]],   [[[2059.0, 2060.0, 2061.0, 2062.0, 2063.0, 2064.0, 2065.0],     [2066.0, 2067.0, 2068.0, 2069.0, 2070.0, 2071.0, 2072.0],     [2073.0, 2074.0, 2075.0, 2076.0, 2077.0, 2078.0, 2079.0],     [2080.0, 2081.0, 2082.0, 2083.0, 2084.0, 2085.0, 2086.0],     [2087.0, 2088.0, 2089.0, 2090.0, 2091.0, 2092.0, 2093.0],     [2094.0, 2095.0, 2096.0, 2097.0, 2098.0, 2099.0, 2100.0]],    [[2101.0, 2102.0, 2103.0, 2104.0, 2105.0, 2106.0, 2107.0],     [2108.0, 2109.0, 2110.0, 2111.0, 2112.0, 2113.0, 2114.0],     [2115.0, 2116.0, 2117.0, 2118.0, 2119.0, 2120.0, 2121.0],     [2122.0, 2123.0, 2124.0, 2125.0, 2126.0, 2127.0, 2128.0],     [2129.0, 2130.0, 2131.0, 2132.0, 2133.0, 2134.0, 2135.0],     [2136.0, 2137.0, 2138.0, 2139.0, 2140.0, 2141.0, 2142.0]],    [[2143.0, 2144.0, 2145.0, 2146.0, 2147.0, 2148.0, 2149.0],     [2150.0, 2151.0, 2152.0, 2153.0, 2154.0, 2155.0, 2156.0],     [2157.0, 2158.0, 2159.0, 2160.0, 2161.0, 2162.0, 2163.0],     [2164.0, 2165.0, 2166.0, 2167.0, 2168.0, 2169.0, 2170.0],     [2171.0, 2172.0, 2173.0, 2174.0, 2175.0, 2176.0, 2177.0],     [2178.0, 2179.0, 2180.0, 2181.0, 2182.0, 2183.0, 2184.0]],    [[2185.0, 2186.0, 2187.0, 2188.0, 2189.0, 2190.0, 2191.0],     [2192.0, 2193.0, 2194.0, 2195.0, 2196.0, 2197.0, 2198.0],     [2199.0, 2200.0, 2201.0, 2202.0, 2203.0, 2204.0, 2205.0],     [2206.0, 2207.0, 2208.0, 2209.0, 2210.0, 2211.0, 2212.0],     [2213.0, 2214.0, 2215.0, 2216.0, 2217.0, 2218.0, 2219.0],     [2220.0, 2221.0, 2222.0, 2223.0, 2224.0, 2225.0, 2226.0]],    [[2227.0, 2228.0, 2229.0, 2230.0, 2231.0, 2232.0, 2233.0],     [2234.0, 2235.0, 2236.0, 2237.0, 2238.0, 2239.0, 2240.0],     [2241.0, 2242.0, 2243.0, 2244.0, 2245.0, 2246.0, 2247.0],     [2248.0, 2249.0, 2250.0, 2251.0, 2252.0, 2253.0, 2254.0],     [2255.0, 2256.0, 2257.0, 2258.0, 2259.0, 2260.0, 2261.0],     [2262.0, 2263.0, 2264.0, 2265.0, 2266.0, 2267.0, 2268.0]],    [[2269.0, 2270.0, 2271.0, 2272.0, 2273.0, 2274.0, 2275.0],     [2276.0, 2277.0, 2278.0, 2279.0, 2280.0, 2281.0, 2282.0],     [2283.0, 2284.0, 2285.0, 2286.0, 2287.0, 2288.0, 2289.0],     [2290.0, 2291.0, 2292.0, 2293.0, 2294.0, 2295.0, 2296.0],     [2297.0, 2298.0, 2299.0, 2300.0, 2301.0, 2302.0, 2303.0],     [2304.0, 2305.0, 2306.0, 2307.0, 2308.0, 2309.0, 2310.0]],    [[2311.0, 2312.0, 2313.0, 2314.0, 2315.0, 2316.0, 2317.0],     [2318.0, 2319.0, 2320.0, 2321.0, 2322.0, 2323.0, 2324.0],     [2325.0, 2326.0, 2327.0, 2328.0, 2329.0, 2330.0, 2331.0],     [2332.0, 2333.0, 2334.0, 2335.0, 2336.0, 2337.0, 2338.0],     [2339.0, 2340.0, 2341.0, 2342.0, 2343.0, 2344.0, 2345.0],     [2346.0, 2347.0, 2348.0, 2349.0, 2350.0, 2351.0, 2352.0]]],   [[[2353.0, 2354.0, 2355.0, 2356.0, 2357.0, 2358.0, 2359.0],     [2360.0, 2361.0, 2362.0, 2363.0, 2364.0, 2365.0, 2366.0],     [2367.0, 2368.0, 2369.0, 2370.0, 2371.0, 2372.0, 2373.0],     [2374.0, 2375.0, 2376.0, 2377.0, 2378.0, 2379.0, 2380.0],     [2381.0, 2382.0, 2383.0, 2384.0, 2385.0, 2386.0, 2387.0],     [2388.0, 2389.0, 2390.0, 2391.0, 2392.0, 2393.0, 2394.0]],    [[2395.0, 2396.0, 2397.0, 2398.0, 2399.0, 2400.0, 2401.0],     [2402.0, 2403.0, 2404.0, 2405.0, 2406.0, 2407.0, 2408.0],     [2409.0, 2410.0, 2411.0, 2412.0, 2413.0, 2414.0, 2415.0],     [2416.0, 2417.0, 2418.0, 2419.0, 2420.0, 2421.0, 2422.0],     [2423.0, 2424.0, 2425.0, 2426.0, 2427.0, 2428.0, 2429.0],     [2430.0, 2431.0, 2432.0, 2433.0, 2434.0, 2435.0, 2436.0]],    [[2437.0, 2438.0, 2439.0, 2440.0, 2441.0, 2442.0, 2443.0],     [2444.0, 2445.0, 2446.0, 2447.0, 2448.0, 2449.0, 2450.0],     [2451.0, 2452.0, 2453.0, 2454.0, 2455.0, 2456.0, 2457.0],     [2458.0, 2459.0, 2460.0, 2461.0, 2462.0, 2463.0, 2464.0],     [2465.0, 2466.0, 2467.0, 2468.0, 2469.0, 2470.0, 2471.0],     [2472.0, 2473.0, 2474.0, 2475.0, 2476.0, 2477.0, 2478.0]],    [[2479.0, 2480.0, 2481.0, 2482.0, 2483.0, 2484.0, 2485.0],     [2486.0, 2487.0, 2488.0, 2489.0, 2490.0, 2491.0, 2492.0],     [2493.0, 2494.0, 2495.0, 2496.0, 2497.0, 2498.0, 2499.0],     [2500.0, 2501.0, 2502.0, 2503.0, 2504.0, 2505.0, 2506.0],     [2507.0, 2508.0, 2509.0, 2510.0, 2511.0, 2512.0, 2513.0],     [2514.0, 2515.0, 2516.0, 2517.0, 2518.0, 2519.0, 2520.0]],    [[2521.0, 2522.0, 2523.0, 2524.0, 2525.0, 2526.0, 2527.0],     [2528.0, 2529.0, 2530.0, 2531.0, 2532.0, 2533.0, 2534.0],     [2535.0, 2536.0, 2537.0, 2538.0, 2539.0, 2540.0, 2541.0],     [2542.0, 2543.0, 2544.0, 2545.0, 2546.0, 2547.0, 2548.0],     [2549.0, 2550.0, 2551.0, 2552.0, 2553.0, 2554.0, 2555.0],     [2556.0, 2557.0, 2558.0, 2559.0, 2560.0, 2561.0, 2562.0]],    [[2563.0, 2564.0, 2565.0, 2566.0, 2567.0, 2568.0, 2569.0],     [2570.0, 2571.0, 2572.0, 2573.0, 2574.0, 2575.0, 2576.0],     [2577.0, 2578.0, 2579.0, 2580.0, 2581.0, 2582.0, 2583.0],     [2584.0, 2585.0, 2586.0, 2587.0, 2588.0, 2589.0, 2590.0],     [2591.0, 2592.0, 2593.0, 2594.0, 2595.0, 2596.0, 2597.0],     [2598.0, 2599.0, 2600.0, 2601.0, 2602.0, 2603.0, 2604.0]],    [[2605.0, 2606.0, 2607.0, 2608.0, 2609.0, 2610.0, 2611.0],     [2612.0, 2613.0, 2614.0, 2615.0, 2616.0, 2617.0, 2618.0],     [2619.0, 2620.0, 2621.0, 2622.0, 2623.0, 2624.0, 2625.0],     [2626.0, 2627.0, 2628.0, 2629.0, 2630.0, 2631.0, 2632.0],     [2633.0, 2634.0, 2635.0, 2636.0, 2637.0, 2638.0, 2639.0],     [2640.0, 2641.0, 2642.0, 2643.0, 2644.0, 2645.0, 2646.0]]],   [[[2647.0, 2648.0, 2649.0, 2650.0, 2651.0, 2652.0, 2653.0],     [2654.0, 2655.0, 2656.0, 2657.0, 2658.0, 2659.0, 2660.0],     [2661.0, 2662.0, 2663.0, 2664.0, 2665.0, 2666.0, 2667.0],     [2668.0, 2669.0, 2670.0, 2671.0, 2672.0, 2673.0, 2674.0],     [2675.0, 2676.0, 2677.0, 2678.0, 2679.0, 2680.0, 2681.0],     [2682.0, 2683.0, 2684.0, 2685.0, 2686.0, 2687.0, 2688.0]],    [[2689.0, 2690.0, 2691.0, 2692.0, 2693.0, 2694.0, 2695.0],     [2696.0, 2697.0, 2698.0, 2699.0, 2700.0, 2701.0, 2702.0],     [2703.0, 2704.0, 2705.0, 2706.0, 2707.0, 2708.0, 2709.0],     [2710.0, 2711.0, 2712.0, 2713.0, 2714.0, 2715.0, 2716.0],     [2717.0, 2718.0, 2719.0, 2720.0, 2721.0, 2722.0, 2723.0],     [2724.0, 2725.0, 2726.0, 2727.0, 2728.0, 2729.0, 2730.0]],    [[2731.0, 2732.0, 2733.0, 2734.0, 2735.0, 2736.0, 2737.0],     [2738.0, 2739.0, 2740.0, 2741.0, 2742.0, 2743.0, 2744.0],     [2745.0, 2746.0, 2747.0, 2748.0, 2749.0, 2750.0, 2751.0],     [2752.0, 2753.0, 2754.0, 2755.0, 2756.0, 2757.0, 2758.0],     [2759.0, 2760.0, 2761.0, 2762.0, 2763.0, 2764.0, 2765.0],     [2766.0, 2767.0, 2768.0, 2769.0, 2770.0, 2771.0, 2772.0]],    [[2773.0, 2774.0, 2775.0, 2776.0, 2777.0, 2778.0, 2779.0],     [2780.0, 2781.0, 2782.0, 2783.0, 2784.0, 2785.0, 2786.0],     [2787.0, 2788.0, 2789.0, 2790.0, 2791.0, 2792.0, 2793.0],     [2794.0, 2795.0, 2796.0, 2797.0, 2798.0, 2799.0, 2800.0],     [2801.0, 2802.0, 2803.0, 2804.0, 2805.0, 2806.0, 2807.0],     [2808.0, 2809.0, 2810.0, 2811.0, 2812.0, 2813.0, 2814.0]],    [[2815.0, 2816.0, 2817.0, 2818.0, 2819.0, 2820.0, 2821.0],     [2822.0, 2823.0, 2824.0, 2825.0, 2826.0, 2827.0, 2828.0],     [2829.0, 2830.0, 2831.0, 2832.0, 2833.0, 2834.0, 2835.0],     [2836.0, 2837.0, 2838.0, 2839.0, 2840.0, 2841.0, 2842.0],     [2843.0, 2844.0, 2845.0, 2846.0, 2847.0, 2848.0, 2849.0],     [2850.0, 2851.0, 2852.0, 2853.0, 2854.0, 2855.0, 2856.0]],    [[2857.0, 2858.0, 2859.0, 2860.0, 2861.0, 2862.0, 2863.0],     [2864.0, 2865.0, 2866.0, 2867.0, 2868.0, 2869.0, 2870.0],     [2871.0, 2872.0, 2873.0, 2874.0, 2875.0, 2876.0, 2877.0],     [2878.0, 2879.0, 2880.0, 2881.0, 2882.0, 2883.0, 2884.0],     [2885.0, 2886.0, 2887.0, 2888.0, 2889.0, 2890.0, 2891.0],     [2892.0, 2893.0, 2894.0, 2895.0, 2896.0, 2897.0, 2898.0]],    [[2899.0, 2900.0, 2901.0, 2902.0, 2903.0, 2904.0, 2905.0],     [2906.0, 2907.0, 2908.0, 2909.0, 2910.0, 2911.0, 2912.0],     [2913.0, 2914.0, 2915.0, 2916.0, 2917.0, 2918.0, 2919.0],     [2920.0, 2921.0, 2922.0, 2923.0, 2924.0, 2925.0, 2926.0],     [2927.0, 2928.0, 2929.0, 2930.0, 2931.0, 2932.0, 2933.0],     [2934.0, 2935.0, 2936.0, 2937.0, 2938.0, 2939.0, 2940.0]]],   [[[2941.0, 2942.0, 2943.0, 2944.0, 2945.0, 2946.0, 2947.0],     [2948.0, 2949.0, 2950.0, 2951.0, 2952.0, 2953.0, 2954.0],     [2955.0, 2956.0, 2957.0, 2958.0, 2959.0, 2960.0, 2961.0],     [2962.0, 2963.0, 2964.0, 2965.0, 2966.0, 2967.0, 2968.0],     [2969.0, 2970.0, 2971.0, 2972.0, 2973.0, 2974.0, 2975.0],     [2976.0, 2977.0, 2978.0, 2979.0, 2980.0, 2981.0, 2982.0]],    [[2983.0, 2984.0, 2985.0, 2986.0, 2987.0, 2988.0, 2989.0],     [2990.0, 2991.0, 2992.0, 2993.0, 2994.0, 2995.0, 2996.0],     [2997.0, 2998.0, 2999.0, 3000.0, 3001.0, 3002.0, 3003.0],     [3004.0, 3005.0, 3006.0, 3007.0, 3008.0, 3009.0, 3010.0],     [3011.0, 3012.0, 3013.0, 3014.0, 3015.0, 3016.0, 3017.0],     [3018.0, 3019.0, 3020.0, 3021.0, 3022.0, 3023.0, 3024.0]],    [[3025.0, 3026.0, 3027.0, 3028.0, 3029.0, 3030.0, 3031.0],     [3032.0, 3033.0, 3034.0, 3035.0, 3036.0, 3037.0, 3038.0],     [3039.0, 3040.0, 3041.0, 3042.0, 3043.0, 3044.0, 3045.0],     [3046.0, 3047.0, 3048.0, 3049.0, 3050.0, 3051.0, 3052.0],     [3053.0, 3054.0, 3055.0, 3056.0, 3057.0, 3058.0, 3059.0],     [3060.0, 3061.0, 3062.0, 3063.0, 3064.0, 3065.0, 3066.0]],    [[3067.0, 3068.0, 3069.0, 3070.0, 3071.0, 3072.0, 3073.0],     [3074.0, 3075.0, 3076.0, 3077.0, 3078.0, 3079.0, 3080.0],     [3081.0, 3082.0, 3083.0, 3084.0, 3085.0, 3086.0, 3087.0],     [3088.0, 3089.0, 3090.0, 3091.0, 3092.0, 3093.0, 3094.0],     [3095.0, 3096.0, 3097.0, 3098.0, 3099.0, 3100.0, 3101.0],     [3102.0, 3103.0, 3104.0, 3105.0, 3106.0, 3107.0, 3108.0]],    [[3109.0, 3110.0, 3111.0, 3112.0, 3113.0, 3114.0, 3115.0],     [3116.0, 3117.0, 3118.0, 3119.0, 3120.0, 3121.0, 3122.0],     [3123.0, 3124.0, 3125.0, 3126.0, 3127.0, 3128.0, 3129.0],     [3130.0, 3131.0, 3132.0, 3133.0, 3134.0, 3135.0, 3136.0],     [3137.0, 3138.0, 3139.0, 3140.0, 3141.0, 3142.0, 3143.0],     [3144.0, 3145.0, 3146.0, 3147.0, 3148.0, 3149.0, 3150.0]],    [[3151.0, 3152.0, 3153.0, 3154.0, 3155.0, 3156.0, 3157.0],     [3158.0, 3159.0, 3160.0, 3161.0, 3162.0, 3163.0, 3164.0],     [3165.0, 3166.0, 3167.0, 3168.0, 3169.0, 3170.0, 3171.0],     [3172.0, 3173.0, 3174.0, 3175.0, 3176.0, 3177.0, 3178.0],     [3179.0, 3180.0, 3181.0, 3182.0, 3183.0, 3184.0, 3185.0],     [3186.0, 3187.0, 3188.0, 3189.0, 3190.0, 3191.0, 3192.0]],    [[3193.0, 3194.0, 3195.0, 3196.0, 3197.0, 3198.0, 3199.0],     [3200.0, 3201.0, 3202.0, 3203.0, 3204.0, 3205.0, 3206.0],     [3207.0, 3208.0, 3209.0, 3210.0, 3211.0, 3212.0, 3213.0],     [3214.0, 3215.0, 3216.0, 3217.0, 3218.0, 3219.0, 3220.0],     [3221.0, 3222.0, 3223.0, 3224.0, 3225.0, 3226.0, 3227.0],     [3228.0, 3229.0, 3230.0, 3231.0, 3232.0, 3233.0, 3234.0]]],   [[[3235.0, 3236.0, 3237.0, 3238.0, 3239.0, 3240.0, 3241.0],     [3242.0, 3243.0, 3244.0, 3245.0, 3246.0, 3247.0, 3248.0],     [3249.0, 3250.0, 3251.0, 3252.0, 3253.0, 3254.0, 3255.0],     [3256.0, 3257.0, 3258.0, 3259.0, 3260.0, 3261.0, 3262.0],     [3263.0, 3264.0, 3265.0, 3266.0, 3267.0, 3268.0, 3269.0],     [3270.0, 3271.0, 3272.0, 3273.0, 3274.0, 3275.0, 3276.0]],    [[3277.0, 3278.0, 3279.0, 3280.0, 3281.0, 3282.0, 3283.0],     [3284.0, 3285.0, 3286.0, 3287.0, 3288.0, 3289.0, 3290.0],     [3291.0, 3292.0, 3293.0, 3294.0, 3295.0, 3296.0, 3297.0],     [3298.0, 3299.0, 3300.0, 3301.0, 3302.0, 3303.0, 3304.0],     [3305.0, 3306.0, 3307.0, 3308.0, 3309.0, 3310.0, 3311.0],     [3312.0, 3313.0, 3314.0, 3315.0, 3316.0, 3317.0, 3318.0]],    [[3319.0, 3320.0, 3321.0, 3322.0, 3323.0, 3324.0, 3325.0],     [3326.0, 3327.0, 3328.0, 3329.0, 3330.0, 3331.0, 3332.0],     [3333.0, 3334.0, 3335.0, 3336.0, 3337.0, 3338.0, 3339.0],     [3340.0, 3341.0, 3342.0, 3343.0, 3344.0, 3345.0, 3346.0],     [3347.0, 3348.0, 3349.0, 3350.0, 3351.0, 3352.0, 3353.0],     [3354.0, 3355.0, 3356.0, 3357.0, 3358.0, 3359.0, 3360.0]],    [[3361.0, 3362.0, 3363.0, 3364.0, 3365.0, 3366.0, 3367.0],     [3368.0, 3369.0, 3370.0, 3371.0, 3372.0, 3373.0, 3374.0],     [3375.0, 3376.0, 3377.0, 3378.0, 3379.0, 3380.0, 3381.0],     [3382.0, 3383.0, 3384.0, 3385.0, 3386.0, 3387.0, 3388.0],     [3389.0, 3390.0, 3391.0, 3392.0, 3393.0, 3394.0, 3395.0],     [3396.0, 3397.0, 3398.0, 3399.0, 3400.0, 3401.0, 3402.0]],    [[3403.0, 3404.0, 3405.0, 3406.0, 3407.0, 3408.0, 3409.0],     [3410.0, 3411.0, 3412.0, 3413.0, 3414.0, 3415.0, 3416.0],     [3417.0, 3418.0, 3419.0, 3420.0, 3421.0, 3422.0, 3423.0],     [3424.0, 3425.0, 3426.0, 3427.0, 3428.0, 3429.0, 3430.0],     [3431.0, 3432.0, 3433.0, 3434.0, 3435.0, 3436.0, 3437.0],     [3438.0, 3439.0, 3440.0, 3441.0, 3442.0, 3443.0, 3444.0]],    [[3445.0, 3446.0, 3447.0, 3448.0, 3449.0, 3450.0, 3451.0],     [3452.0, 3453.0, 3454.0, 3455.0, 3456.0, 3457.0, 3458.0],     [3459.0, 3460.0, 3461.0, 3462.0, 3463.0, 3464.0, 3465.0],     [3466.0, 3467.0, 3468.0, 3469.0, 3470.0, 3471.0, 3472.0],     [3473.0, 3474.0, 3475.0, 3476.0, 3477.0, 3478.0, 3479.0],     [3480.0, 3481.0, 3482.0, 3483.0, 3484.0, 3485.0, 3486.0]],    [[3487.0, 3488.0, 3489.0, 3490.0, 3491.0, 3492.0, 3493.0],     [3494.0, 3495.0, 3496.0, 3497.0, 3498.0, 3499.0, 3500.0],     [3501.0, 3502.0, 3503.0, 3504.0, 3505.0, 3506.0, 3507.0],     [3508.0, 3509.0, 3510.0, 3511.0, 3512.0, 3513.0, 3514.0],     [3515.0, 3516.0, 3517.0, 3518.0, 3519.0, 3520.0, 3521.0],     [3522.0, 3523.0, 3524.0, 3525.0, 3526.0, 3527.0, 3528.0]]]],  [[[[3529.0, 3530.0, 3531.0, 3532.0, 3533.0, 3534.0, 3535.0],     [3536.0, 3537.0, 3538.0, 3539.0, 3540.0, 3541.0, 3542.0],     [3543.0, 3544.0, 3545.0, 3546.0, 3547.0, 3548.0, 3549.0],     [3550.0, 3551.0, 3552.0, 3553.0, 3554.0, 3555.0, 3556.0],     [3557.0, 3558.0, 3559.0, 3560.0, 3561.0, 3562.0, 3563.0],     [3564.0, 3565.0, 3566.0, 3567.0, 3568.0, 3569.0, 3570.0]],    [[3571.0, 3572.0, 3573.0, 3574.0, 3575.0, 3576.0, 3577.0],     [3578.0, 3579.0, 3580.0, 3581.0, 3582.0, 3583.0, 3584.0],     [3585.0, 3586.0, 3587.0, 3588.0, 3589.0, 3590.0, 3591.0],     [3592.0, 3593.0, 3594.0, 3595.0, 3596.0, 3597.0, 3598.0],     [3599.0, 3600.0, 3601.0, 3602.0, 3603.0, 3604.0, 3605.0],     [3606.0, 3607.0, 3608.0, 3609.0, 3610.0, 3611.0, 3612.0]],    [[3613.0, 3614.0, 3615.0, 3616.0, 3617.0, 3618.0, 3619.0],     [3620.0, 3621.0, 3622.0, 3623.0, 3624.0, 3625.0, 3626.0],     [3627.0, 3628.0, 3629.0, 3630.0, 3631.0, 3632.0, 3633.0],     [3634.0, 3635.0, 3636.0, 3637.0, 3638.0, 3639.0, 3640.0],     [3641.0, 3642.0, 3643.0, 3644.0, 3645.0, 3646.0, 3647.0],     [3648.0, 3649.0, 3650.0, 3651.0, 3652.0, 3653.0, 3654.0]],    [[3655.0, 3656.0, 3657.0, 3658.0, 3659.0, 3660.0, 3661.0],     [3662.0, 3663.0, 3664.0, 3665.0, 3666.0, 3667.0, 3668.0],     [3669.0, 3670.0, 3671.0, 3672.0, 3673.0, 3674.0, 3675.0],     [3676.0, 3677.0, 3678.0, 3679.0, 3680.0, 3681.0, 3682.0],     [3683.0, 3684.0, 3685.0, 3686.0, 3687.0, 3688.0, 3689.0],     [3690.0, 3691.0, 3692.0, 3693.0, 3694.0, 3695.0, 3696.0]],    [[3697.0, 3698.0, 3699.0, 3700.0, 3701.0, 3702.0, 3703.0],     [3704.0, 3705.0, 3706.0, 3707.0, 3708.0, 3709.0, 3710.0],     [3711.0, 3712.0, 3713.0, 3714.0, 3715.0, 3716.0, 3717.0],     [3718.0, 3719.0, 3720.0, 3721.0, 3722.0, 3723.0, 3724.0],     [3725.0, 3726.0, 3727.0, 3728.0, 3729.0, 3730.0, 3731.0],     [3732.0, 3733.0, 3734.0, 3735.0, 3736.0, 3737.0, 3738.0]],    [[3739.0, 3740.0, 3741.0, 3742.0, 3743.0, 3744.0, 3745.0],     [3746.0, 3747.0, 3748.0, 3749.0, 3750.0, 3751.0, 3752.0],     [3753.0, 3754.0, 3755.0, 3756.0, 3757.0, 3758.0, 3759.0],     [3760.0, 3761.0, 3762.0, 3763.0, 3764.0, 3765.0, 3766.0],     [3767.0, 3768.0, 3769.0, 3770.0, 3771.0, 3772.0, 3773.0],     [3774.0, 3775.0, 3776.0, 3777.0, 3778.0, 3779.0, 3780.0]],    [[3781.0, 3782.0, 3783.0, 3784.0, 3785.0, 3786.0, 3787.0],     [3788.0, 3789.0, 3790.0, 3791.0, 3792.0, 3793.0, 3794.0],     [3795.0, 3796.0, 3797.0, 3798.0, 3799.0, 3800.0, 3801.0],     [3802.0, 3803.0, 3804.0, 3805.0, 3806.0, 3807.0, 3808.0],     [3809.0, 3810.0, 3811.0, 3812.0, 3813.0, 3814.0, 3815.0],     [3816.0, 3817.0, 3818.0, 3819.0, 3820.0, 3821.0, 3822.0]]],   [[[3823.0, 3824.0, 3825.0, 3826.0, 3827.0, 3828.0, 3829.0],     [3830.0, 3831.0, 3832.0, 3833.0, 3834.0, 3835.0, 3836.0],     [3837.0, 3838.0, 3839.0, 3840.0, 3841.0, 3842.0, 3843.0],     [3844.0, 3845.0, 3846.0, 3847.0, 3848.0, 3849.0, 3850.0],     [3851.0, 3852.0, 3853.0, 3854.0, 3855.0, 3856.0, 3857.0],     [3858.0, 3859.0, 3860.0, 3861.0, 3862.0, 3863.0, 3864.0]],    [[3865.0, 3866.0, 3867.0, 3868.0, 3869.0, 3870.0, 3871.0],     [3872.0, 3873.0, 3874.0, 3875.0, 3876.0, 3877.0, 3878.0],     [3879.0, 3880.0, 3881.0, 3882.0, 3883.0, 3884.0, 3885.0],     [3886.0, 3887.0, 3888.0, 3889.0, 3890.0, 3891.0, 3892.0],     [3893.0, 3894.0, 3895.0, 3896.0, 3897.0, 3898.0, 3899.0],     [3900.0, 3901.0, 3902.0, 3903.0, 3904.0, 3905.0, 3906.0]],    [[3907.0, 3908.0, 3909.0, 3910.0, 3911.0, 3912.0, 3913.0],     [3914.0, 3915.0, 3916.0, 3917.0, 3918.0, 3919.0, 3920.0],     [3921.0, 3922.0, 3923.0, 3924.0, 3925.0, 3926.0, 3927.0],     [3928.0, 3929.0, 3930.0, 3931.0, 3932.0, 3933.0, 3934.0],     [3935.0, 3936.0, 3937.0, 3938.0, 3939.0, 3940.0, 3941.0],     [3942.0, 3943.0, 3944.0, 3945.0, 3946.0, 3947.0, 3948.0]],    [[3949.0, 3950.0, 3951.0, 3952.0, 3953.0, 3954.0, 3955.0],     [3956.0, 3957.0, 3958.0, 3959.0, 3960.0, 3961.0, 3962.0],     [3963.0, 3964.0, 3965.0, 3966.0, 3967.0, 3968.0, 3969.0],     [3970.0, 3971.0, 3972.0, 3973.0, 3974.0, 3975.0, 3976.0],     [3977.0, 3978.0, 3979.0, 3980.0, 3981.0, 3982.0, 3983.0],     [3984.0, 3985.0, 3986.0, 3987.0, 3988.0, 3989.0, 3990.0]],    [[3991.0, 3992.0, 3993.0, 3994.0, 3995.0, 3996.0, 3997.0],     [3998.0, 3999.0, 4000.0, 4001.0, 4002.0, 4003.0, 4004.0],     [4005.0, 4006.0, 4007.0, 4008.0, 4009.0, 4010.0, 4011.0],     [4012.0, 4013.0, 4014.0, 4015.0, 4016.0, 4017.0, 4018.0],     [4019.0, 4020.0, 4021.0, 4022.0, 4023.0, 4024.0, 4025.0],     [4026.0, 4027.0, 4028.0, 4029.0, 4030.0, 4031.0, 4032.0]],    [[4033.0, 4034.0, 4035.0, 4036.0, 4037.0, 4038.0, 4039.0],     [4040.0, 4041.0, 4042.0, 4043.0, 4044.0, 4045.0, 4046.0],     [4047.0, 4048.0, 4049.0, 4050.0, 4051.0, 4052.0, 4053.0],     [4054.0, 4055.0, 4056.0, 4057.0, 4058.0, 4059.0, 4060.0],     [4061.0, 4062.0, 4063.0, 4064.0, 4065.0, 4066.0, 4067.0],     [4068.0, 4069.0, 4070.0, 4071.0, 4072.0, 4073.0, 4074.0]],    [[4075.0, 4076.0, 4077.0, 4078.0, 4079.0, 4080.0, 4081.0],     [4082.0, 4083.0, 4084.0, 4085.0, 4086.0, 4087.0, 4088.0],     [4089.0, 4090.0, 4091.0, 4092.0, 4093.0, 4094.0, 4095.0],     [4096.0, 4097.0, 4098.0, 4099.0, 4100.0, 4101.0, 4102.0],     [4103.0, 4104.0, 4105.0, 4106.0, 4107.0, 4108.0, 4109.0],     [4110.0, 4111.0, 4112.0, 4113.0, 4114.0, 4115.0, 4116.0]]],   [[[4117.0, 4118.0, 4119.0, 4120.0, 4121.0, 4122.0, 4123.0],     [4124.0, 4125.0, 4126.0, 4127.0, 4128.0, 4129.0, 4130.0],     [4131.0, 4132.0, 4133.0, 4134.0, 4135.0, 4136.0, 4137.0],     [4138.0, 4139.0, 4140.0, 4141.0, 4142.0, 4143.0, 4144.0],     [4145.0, 4146.0, 4147.0, 4148.0, 4149.0, 4150.0, 4151.0],     [4152.0, 4153.0, 4154.0, 4155.0, 4156.0, 4157.0, 4158.0]],    [[4159.0, 4160.0, 4161.0, 4162.0, 4163.0, 4164.0, 4165.0],     [4166.0, 4167.0, 4168.0, 4169.0, 4170.0, 4171.0, 4172.0],     [4173.0, 4174.0, 4175.0, 4176.0, 4177.0, 4178.0, 4179.0],     [4180.0, 4181.0, 4182.0, 4183.0, 4184.0, 4185.0, 4186.0],     [4187.0, 4188.0, 4189.0, 4190.0, 4191.0, 4192.0, 4193.0],     [4194.0, 4195.0, 4196.0, 4197.0, 4198.0, 4199.0, 4200.0]],    [[4201.0, 4202.0, 4203.0, 4204.0, 4205.0, 4206.0, 4207.0],     [4208.0, 4209.0, 4210.0, 4211.0, 4212.0, 4213.0, 4214.0],     [4215.0, 4216.0, 4217.0, 4218.0, 4219.0, 4220.0, 4221.0],     [4222.0, 4223.0, 4224.0, 4225.0, 4226.0, 4227.0, 4228.0],     [4229.0, 4230.0, 4231.0, 4232.0, 4233.0, 4234.0, 4235.0],     [4236.0, 4237.0, 4238.0, 4239.0, 4240.0, 4241.0, 4242.0]],    [[4243.0, 4244.0, 4245.0, 4246.0, 4247.0, 4248.0, 4249.0],     [4250.0, 4251.0, 4252.0, 4253.0, 4254.0, 4255.0, 4256.0],     [4257.0, 4258.0, 4259.0, 4260.0, 4261.0, 4262.0, 4263.0],     [4264.0, 4265.0, 4266.0, 4267.0, 4268.0, 4269.0, 4270.0],     [4271.0, 4272.0, 4273.0, 4274.0, 4275.0, 4276.0, 4277.0],     [4278.0, 4279.0, 4280.0, 4281.0, 4282.0, 4283.0, 4284.0]],    [[4285.0, 4286.0, 4287.0, 4288.0, 4289.0, 4290.0, 4291.0],     [4292.0, 4293.0, 4294.0, 4295.0, 4296.0, 4297.0, 4298.0],     [4299.0, 4300.0, 4301.0, 4302.0, 4303.0, 4304.0, 4305.0],     [4306.0, 4307.0, 4308.0, 4309.0, 4310.0, 4311.0, 4312.0],     [4313.0, 4314.0, 4315.0, 4316.0, 4317.0, 4318.0, 4319.0],     [4320.0, 4321.0, 4322.0, 4323.0, 4324.0, 4325.0, 4326.0]],    [[4327.0, 4328.0, 4329.0, 4330.0, 4331.0, 4332.0, 4333.0],     [4334.0, 4335.0, 4336.0, 4337.0, 4338.0, 4339.0, 4340.0],     [4341.0, 4342.0, 4343.0, 4344.0, 4345.0, 4346.0, 4347.0],     [4348.0, 4349.0, 4350.0, 4351.0, 4352.0, 4353.0, 4354.0],     [4355.0, 4356.0, 4357.0, 4358.0, 4359.0, 4360.0, 4361.0],     [4362.0, 4363.0, 4364.0, 4365.0, 4366.0, 4367.0, 4368.0]],    [[4369.0, 4370.0, 4371.0, 4372.0, 4373.0, 4374.0, 4375.0],     [4376.0, 4377.0, 4378.0, 4379.0, 4380.0, 4381.0, 4382.0],     [4383.0, 4384.0, 4385.0, 4386.0, 4387.0, 4388.0, 4389.0],     [4390.0, 4391.0, 4392.0, 4393.0, 4394.0, 4395.0, 4396.0],     [4397.0, 4398.0, 4399.0, 4400.0, 4401.0, 4402.0, 4403.0],     [4404.0, 4405.0, 4406.0, 4407.0, 4408.0, 4409.0, 4410.0]]],   [[[4411.0, 4412.0, 4413.0, 4414.0, 4415.0, 4416.0, 4417.0],     [4418.0, 4419.0, 4420.0, 4421.0, 4422.0, 4423.0, 4424.0],     [4425.0, 4426.0, 4427.0, 4428.0, 4429.0, 4430.0, 4431.0],     [4432.0, 4433.0, 4434.0, 4435.0, 4436.0, 4437.0, 4438.0],     [4439.0, 4440.0, 4441.0, 4442.0, 4443.0, 4444.0, 4445.0],     [4446.0, 4447.0, 4448.0, 4449.0, 4450.0, 4451.0, 4452.0]],    [[4453.0, 4454.0, 4455.0, 4456.0, 4457.0, 4458.0, 4459.0],     [4460.0, 4461.0, 4462.0, 4463.0, 4464.0, 4465.0, 4466.0],     [4467.0, 4468.0, 4469.0, 4470.0, 4471.0, 4472.0, 4473.0],     [4474.0, 4475.0, 4476.0, 4477.0, 4478.0, 4479.0, 4480.0],     [4481.0, 4482.0, 4483.0, 4484.0, 4485.0, 4486.0, 4487.0],     [4488.0, 4489.0, 4490.0, 4491.0, 4492.0, 4493.0, 4494.0]],    [[4495.0, 4496.0, 4497.0, 4498.0, 4499.0, 4500.0, 4501.0],     [4502.0, 4503.0, 4504.0, 4505.0, 4506.0, 4507.0, 4508.0],     [4509.0, 4510.0, 4511.0, 4512.0, 4513.0, 4514.0, 4515.0],     [4516.0, 4517.0, 4518.0, 4519.0, 4520.0, 4521.0, 4522.0],     [4523.0, 4524.0, 4525.0, 4526.0, 4527.0, 4528.0, 4529.0],     [4530.0, 4531.0, 4532.0, 4533.0, 4534.0, 4535.0, 4536.0]],    [[4537.0, 4538.0, 4539.0, 4540.0, 4541.0, 4542.0, 4543.0],     [4544.0, 4545.0, 4546.0, 4547.0, 4548.0, 4549.0, 4550.0],     [4551.0, 4552.0, 4553.0, 4554.0, 4555.0, 4556.0, 4557.0],     [4558.0, 4559.0, 4560.0, 4561.0, 4562.0, 4563.0, 4564.0],     [4565.0, 4566.0, 4567.0, 4568.0, 4569.0, 4570.0, 4571.0],     [4572.0, 4573.0, 4574.0, 4575.0, 4576.0, 4577.0, 4578.0]],    [[4579.0, 4580.0, 4581.0, 4582.0, 4583.0, 4584.0, 4585.0],     [4586.0, 4587.0, 4588.0, 4589.0, 4590.0, 4591.0, 4592.0],     [4593.0, 4594.0, 4595.0, 4596.0, 4597.0, 4598.0, 4599.0],     [4600.0, 4601.0, 4602.0, 4603.0, 4604.0, 4605.0, 4606.0],     [4607.0, 4608.0, 4609.0, 4610.0, 4611.0, 4612.0, 4613.0],     [4614.0, 4615.0, 4616.0, 4617.0, 4618.0, 4619.0, 4620.0]],    [[4621.0, 4622.0, 4623.0, 4624.0, 4625.0, 4626.0, 4627.0],     [4628.0, 4629.0, 4630.0, 4631.0, 4632.0, 4633.0, 4634.0],     [4635.0, 4636.0, 4637.0, 4638.0, 4639.0, 4640.0, 4641.0],     [4642.0, 4643.0, 4644.0, 4645.0, 4646.0, 4647.0, 4648.0],     [4649.0, 4650.0, 4651.0, 4652.0, 4653.0, 4654.0, 4655.0],     [4656.0, 4657.0, 4658.0, 4659.0, 4660.0, 4661.0, 4662.0]],    [[4663.0, 4664.0, 4665.0, 4666.0, 4667.0, 4668.0, 4669.0],     [4670.0, 4671.0, 4672.0, 4673.0, 4674.0, 4675.0, 4676.0],     [4677.0, 4678.0, 4679.0, 4680.0, 4681.0, 4682.0, 4683.0],     [4684.0, 4685.0, 4686.0, 4687.0, 4688.0, 4689.0, 4690.0],     [4691.0, 4692.0, 4693.0, 4694.0, 4695.0, 4696.0, 4697.0],     [4698.0, 4699.0, 4700.0, 4701.0, 4702.0, 4703.0, 4704.0]]],   [[[4705.0, 4706.0, 4707.0, 4708.0, 4709.0, 4710.0, 4711.0],     [4712.0, 4713.0, 4714.0, 4715.0, 4716.0, 4717.0, 4718.0],     [4719.0, 4720.0, 4721.0, 4722.0, 4723.0, 4724.0, 4725.0],     [4726.0, 4727.0, 4728.0, 4729.0, 4730.0, 4731.0, 4732.0],     [4733.0, 4734.0, 4735.0, 4736.0, 4737.0, 4738.0, 4739.0],     [4740.0, 4741.0, 4742.0, 4743.0, 4744.0, 4745.0, 4746.0]],    [[4747.0, 4748.0, 4749.0, 4750.0, 4751.0, 4752.0, 4753.0],     [4754.0, 4755.0, 4756.0, 4757.0, 4758.0, 4759.0, 4760.0],     [4761.0, 4762.0, 4763.0, 4764.0, 4765.0, 4766.0, 4767.0],     [4768.0, 4769.0, 4770.0, 4771.0, 4772.0, 4773.0, 4774.0],     [4775.0, 4776.0, 4777.0, 4778.0, 4779.0, 4780.0, 4781.0],     [4782.0, 4783.0, 4784.0, 4785.0, 4786.0, 4787.0, 4788.0]],    [[4789.0, 4790.0, 4791.0, 4792.0, 4793.0, 4794.0, 4795.0],     [4796.0, 4797.0, 4798.0, 4799.0, 4800.0, 4801.0, 4802.0],     [4803.0, 4804.0, 4805.0, 4806.0, 4807.0, 4808.0, 4809.0],     [4810.0, 4811.0, 4812.0, 4813.0, 4814.0, 4815.0, 4816.0],     [4817.0, 4818.0, 4819.0, 4820.0, 4821.0, 4822.0, 4823.0],     [4824.0, 4825.0, 4826.0, 4827.0, 4828.0, 4829.0, 4830.0]],    [[4831.0, 4832.0, 4833.0, 4834.0, 4835.0, 4836.0, 4837.0],     [4838.0, 4839.0, 4840.0, 4841.0, 4842.0, 4843.0, 4844.0],     [4845.0, 4846.0, 4847.0, 4848.0, 4849.0, 4850.0, 4851.0],     [4852.0, 4853.0, 4854.0, 4855.0, 4856.0, 4857.0, 4858.0],     [4859.0, 4860.0, 4861.0, 4862.0, 4863.0, 4864.0, 4865.0],     [4866.0, 4867.0, 4868.0, 4869.0, 4870.0, 4871.0, 4872.0]],    [[4873.0, 4874.0, 4875.0, 4876.0, 4877.0, 4878.0, 4879.0],     [4880.0, 4881.0, 4882.0, 4883.0, 4884.0, 4885.0, 4886.0],     [4887.0, 4888.0, 4889.0, 4890.0, 4891.0, 4892.0, 4893.0],     [4894.0, 4895.0, 4896.0, 4897.0, 4898.0, 4899.0, 4900.0],     [4901.0, 4902.0, 4903.0, 4904.0, 4905.0, 4906.0, 4907.0],     [4908.0, 4909.0, 4910.0, 4911.0, 4912.0, 4913.0, 4914.0]],    [[4915.0, 4916.0, 4917.0, 4918.0, 4919.0, 4920.0, 4921.0],     [4922.0, 4923.0, 4924.0, 4925.0, 4926.0, 4927.0, 4928.0],     [4929.0, 4930.0, 4931.0, 4932.0, 4933.0, 4934.0, 4935.0],     [4936.0, 4937.0, 4938.0, 4939.0, 4940.0, 4941.0, 4942.0],     [4943.0, 4944.0, 4945.0, 4946.0, 4947.0, 4948.0, 4949.0],     [4950.0, 4951.0, 4952.0, 4953.0, 4954.0, 4955.0, 4956.0]],    [[4957.0, 4958.0, 4959.0, 4960.0, 4961.0, 4962.0, 4963.0],     [4964.0, 4965.0, 4966.0, 4967.0, 4968.0, 4969.0, 4970.0],     [4971.0, 4972.0, 4973.0, 4974.0, 4975.0, 4976.0, 4977.0],     [4978.0, 4979.0, 4980.0, 4981.0, 4982.0, 4983.0, 4984.0],     [4985.0, 4986.0, 4987.0, 4988.0, 4989.0, 4990.0, 4991.0],     [4992.0, 4993.0, 4994.0, 4995.0, 4996.0, 4997.0, 4998.0]]],   [[[4999.0, 5000.0, 5001.0, 5002.0, 5003.0, 5004.0, 5005.0],     [5006.0, 5007.0, 5008.0, 5009.0, 5010.0, 5011.0, 5012.0],     [5013.0, 5014.0, 5015.0, 5016.0, 5017.0, 5018.0, 5019.0],     [5020.0, 5021.0, 5022.0, 5023.0, 5024.0, 5025.0, 5026.0],     [5027.0, 5028.0, 5029.0, 5030.0, 5031.0, 5032.0, 5033.0],     [5034.0, 5035.0, 5036.0, 5037.0, 5038.0, 5039.0, 5040.0]],    [[5041.0, 5042.0, 5043.0, 5044.0, 5045.0, 5046.0, 5047.0],     [5048.0, 5049.0, 5050.0, 5051.0, 5052.0, 5053.0, 5054.0],     [5055.0, 5056.0, 5057.0, 5058.0, 5059.0, 5060.0, 5061.0],     [5062.0, 5063.0, 5064.0, 5065.0, 5066.0, 5067.0, 5068.0],     [5069.0, 5070.0, 5071.0, 5072.0, 5073.0, 5074.0, 5075.0],     [5076.0, 5077.0, 5078.0, 5079.0, 5080.0, 5081.0, 5082.0]],    [[5083.0, 5084.0, 5085.0, 5086.0, 5087.0, 5088.0, 5089.0],     [5090.0, 5091.0, 5092.0, 5093.0, 5094.0, 5095.0, 5096.0],     [5097.0, 5098.0, 5099.0, 5100.0, 5101.0, 5102.0, 5103.0],     [5104.0, 5105.0, 5106.0, 5107.0, 5108.0, 5109.0, 5110.0],     [5111.0, 5112.0, 5113.0, 5114.0, 5115.0, 5116.0, 5117.0],     [5118.0, 5119.0, 5120.0, 5121.0, 5122.0, 5123.0, 5124.0]],    [[5125.0, 5126.0, 5127.0, 5128.0, 5129.0, 5130.0, 5131.0],     [5132.0, 5133.0, 5134.0, 5135.0, 5136.0, 5137.0, 5138.0],     [5139.0, 5140.0, 5141.0, 5142.0, 5143.0, 5144.0, 5145.0],     [5146.0, 5147.0, 5148.0, 5149.0, 5150.0, 5151.0, 5152.0],     [5153.0, 5154.0, 5155.0, 5156.0, 5157.0, 5158.0, 5159.0],     [5160.0, 5161.0, 5162.0, 5163.0, 5164.0, 5165.0, 5166.0]],    [[5167.0, 5168.0, 5169.0, 5170.0, 5171.0, 5172.0, 5173.0],     [5174.0, 5175.0, 5176.0, 5177.0, 5178.0, 5179.0, 5180.0],     [5181.0, 5182.0, 5183.0, 5184.0, 5185.0, 5186.0, 5187.0],     [5188.0, 5189.0, 5190.0, 5191.0, 5192.0, 5193.0, 5194.0],     [5195.0, 5196.0, 5197.0, 5198.0, 5199.0, 5200.0, 5201.0],     [5202.0, 5203.0, 5204.0, 5205.0, 5206.0, 5207.0, 5208.0]],    [[5209.0, 5210.0, 5211.0, 5212.0, 5213.0, 5214.0, 5215.0],     [5216.0, 5217.0, 5218.0, 5219.0, 5220.0, 5221.0, 5222.0],     [5223.0, 5224.0, 5225.0, 5226.0, 5227.0, 5228.0, 5229.0],     [5230.0, 5231.0, 5232.0, 5233.0, 5234.0, 5235.0, 5236.0],     [5237.0, 5238.0, 5239.0, 5240.0, 5241.0, 5242.0, 5243.0],     [5244.0, 5245.0, 5246.0, 5247.0, 5248.0, 5249.0, 5250.0]],    [[5251.0, 5252.0, 5253.0, 5254.0, 5255.0, 5256.0, 5257.0],     [5258.0, 5259.0, 5260.0, 5261.0, 5262.0, 5263.0, 5264.0],     [5265.0, 5266.0, 5267.0, 5268.0, 5269.0, 5270.0, 5271.0],     [5272.0, 5273.0, 5274.0, 5275.0, 5276.0, 5277.0, 5278.0],     [5279.0, 5280.0, 5281.0, 5282.0, 5283.0, 5284.0, 5285.0],     [5286.0, 5287.0, 5288.0, 5289.0, 5290.0, 5291.0, 5292.0]]]]] shape=[3, 6, 7, 6, 7], strides=[1764, 294, 42, 7, 1], layout=C (0x1)), I32([2] shape=[1], strides=[1], layout=C | F (0x3)), I32([[2, 2]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 1863123834 1103973619 2215929888 2362288465 # shrinks to (ref i, ref bs, ref p) = (F32([[[[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, 16.0],    [17.0, 18.0]],   [[19.0, 20.0],    [21.0, 22.0],    [23.0, 24.0]]]] shape=[2, 2, 3, 2], strides=[12, 6, 2, 1], layout=C (0x1)), I32([2] shape=[1], strides=[1], layout=C | F (0x3)), I32([[3, 1]] shape=[1, 2], strides=[2, 1], layout=C (0x1)))\nxs 4117558675 4064208007 2133376722 249049850 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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, 16.0],     [17.0, 18.0, 19.0, 20.0],     [21.0, 22.0, 23.0, 24.0]],    [[25.0, 26.0, 27.0, 28.0],     [29.0, 30.0, 31.0, 32.0],     [33.0, 34.0, 35.0, 36.0],     [37.0, 38.0, 39.0, 40.0],     [41.0, 42.0, 43.0, 44.0],     [45.0, 46.0, 47.0, 48.0]],    [[49.0, 50.0, 51.0, 52.0],     [53.0, 54.0, 55.0, 56.0],     [57.0, 58.0, 59.0, 60.0],     [61.0, 62.0, 63.0, 64.0],     [65.0, 66.0, 67.0, 68.0],     [69.0, 70.0, 71.0, 72.0]],    [[73.0, 74.0, 75.0, 76.0],     [77.0, 78.0, 79.0, 80.0],     [81.0, 82.0, 83.0, 84.0],     [85.0, 86.0, 87.0, 88.0],     [89.0, 90.0, 91.0, 92.0],     [93.0, 94.0, 95.0, 96.0]],    [[97.0, 98.0, 99.0, 100.0],     [101.0, 102.0, 103.0, 104.0],     [105.0, 106.0, 107.0, 108.0],     [109.0, 110.0, 111.0, 112.0],     [113.0, 114.0, 115.0, 116.0],     [117.0, 118.0, 119.0, 120.0]],    [[121.0, 122.0, 123.0, 124.0],     [125.0, 126.0, 127.0, 128.0],     [129.0, 130.0, 131.0, 132.0],     [133.0, 134.0, 135.0, 136.0],     [137.0, 138.0, 139.0, 140.0],     [141.0, 142.0, 143.0, 144.0]],    [[145.0, 146.0, 147.0, 148.0],     [149.0, 150.0, 151.0, 152.0],     [153.0, 154.0, 155.0, 156.0],     [157.0, 158.0, 159.0, 160.0],     [161.0, 162.0, 163.0, 164.0],     [165.0, 166.0, 167.0, 168.0]]],   [[[169.0, 170.0, 171.0, 172.0],     [173.0, 174.0, 175.0, 176.0],     [177.0, 178.0, 179.0, 180.0],     [181.0, 182.0, 183.0, 184.0],     [185.0, 186.0, 187.0, 188.0],     [189.0, 190.0, 191.0, 192.0]],    [[193.0, 194.0, 195.0, 196.0],     [197.0, 198.0, 199.0, 200.0],     [201.0, 202.0, 203.0, 204.0],     [205.0, 206.0, 207.0, 208.0],     [209.0, 210.0, 211.0, 212.0],     [213.0, 214.0, 215.0, 216.0]],    [[217.0, 218.0, 219.0, 220.0],     [221.0, 222.0, 223.0, 224.0],     [225.0, 226.0, 227.0, 228.0],     [229.0, 230.0, 231.0, 232.0],     [233.0, 234.0, 235.0, 236.0],     [237.0, 238.0, 239.0, 240.0]],    [[241.0, 242.0, 243.0, 244.0],     [245.0, 246.0, 247.0, 248.0],     [249.0, 250.0, 251.0, 252.0],     [253.0, 254.0, 255.0, 256.0],     [257.0, 258.0, 259.0, 260.0],     [261.0, 262.0, 263.0, 264.0]],    [[265.0, 266.0, 267.0, 268.0],     [269.0, 270.0, 271.0, 272.0],     [273.0, 274.0, 275.0, 276.0],     [277.0, 278.0, 279.0, 280.0],     [281.0, 282.0, 283.0, 284.0],     [285.0, 286.0, 287.0, 288.0]],    [[289.0, 290.0, 291.0, 292.0],     [293.0, 294.0, 295.0, 296.0],     [297.0, 298.0, 299.0, 300.0],     [301.0, 302.0, 303.0, 304.0],     [305.0, 306.0, 307.0, 308.0],     [309.0, 310.0, 311.0, 312.0]],    [[313.0, 314.0, 315.0, 316.0],     [317.0, 318.0, 319.0, 320.0],     [321.0, 322.0, 323.0, 324.0],     [325.0, 326.0, 327.0, 328.0],     [329.0, 330.0, 331.0, 332.0],     [333.0, 334.0, 335.0, 336.0]]],   [[[337.0, 338.0, 339.0, 340.0],     [341.0, 342.0, 343.0, 344.0],     [345.0, 346.0, 347.0, 348.0],     [349.0, 350.0, 351.0, 352.0],     [353.0, 354.0, 355.0, 356.0],     [357.0, 358.0, 359.0, 360.0]],    [[361.0, 362.0, 363.0, 364.0],     [365.0, 366.0, 367.0, 368.0],     [369.0, 370.0, 371.0, 372.0],     [373.0, 374.0, 375.0, 376.0],     [377.0, 378.0, 379.0, 380.0],     [381.0, 382.0, 383.0, 384.0]],    [[385.0, 386.0, 387.0, 388.0],     [389.0, 390.0, 391.0, 392.0],     [393.0, 394.0, 395.0, 396.0],     [397.0, 398.0, 399.0, 400.0],     [401.0, 402.0, 403.0, 404.0],     [405.0, 406.0, 407.0, 408.0]],    [[409.0, 410.0, 411.0, 412.0],     [413.0, 414.0, 415.0, 416.0],     [417.0, 418.0, 419.0, 420.0],     [421.0, 422.0, 423.0, 424.0],     [425.0, 426.0, 427.0, 428.0],     [429.0, 430.0, 431.0, 432.0]],    [[433.0, 434.0, 435.0, 436.0],     [437.0, 438.0, 439.0, 440.0],     [441.0, 442.0, 443.0, 444.0],     [445.0, 446.0, 447.0, 448.0],     [449.0, 450.0, 451.0, 452.0],     [453.0, 454.0, 455.0, 456.0]],    [[457.0, 458.0, 459.0, 460.0],     [461.0, 462.0, 463.0, 464.0],     [465.0, 466.0, 467.0, 468.0],     [469.0, 470.0, 471.0, 472.0],     [473.0, 474.0, 475.0, 476.0],     [477.0, 478.0, 479.0, 480.0]],    [[481.0, 482.0, 483.0, 484.0],     [485.0, 486.0, 487.0, 488.0],     [489.0, 490.0, 491.0, 492.0],     [493.0, 494.0, 495.0, 496.0],     [497.0, 498.0, 499.0, 500.0],     [501.0, 502.0, 503.0, 504.0]]],   [[[505.0, 506.0, 507.0, 508.0],     [509.0, 510.0, 511.0, 512.0],     [513.0, 514.0, 515.0, 516.0],     [517.0, 518.0, 519.0, 520.0],     [521.0, 522.0, 523.0, 524.0],     [525.0, 526.0, 527.0, 528.0]],    [[529.0, 530.0, 531.0, 532.0],     [533.0, 534.0, 535.0, 536.0],     [537.0, 538.0, 539.0, 540.0],     [541.0, 542.0, 543.0, 544.0],     [545.0, 546.0, 547.0, 548.0],     [549.0, 550.0, 551.0, 552.0]],    [[553.0, 554.0, 555.0, 556.0],     [557.0, 558.0, 559.0, 560.0],     [561.0, 562.0, 563.0, 564.0],     [565.0, 566.0, 567.0, 568.0],     [569.0, 570.0, 571.0, 572.0],     [573.0, 574.0, 575.0, 576.0]],    [[577.0, 578.0, 579.0, 580.0],     [581.0, 582.0, 583.0, 584.0],     [585.0, 586.0, 587.0, 588.0],     [589.0, 590.0, 591.0, 592.0],     [593.0, 594.0, 595.0, 596.0],     [597.0, 598.0, 599.0, 600.0]],    [[601.0, 602.0, 603.0, 604.0],     [605.0, 606.0, 607.0, 608.0],     [609.0, 610.0, 611.0, 612.0],     [613.0, 614.0, 615.0, 616.0],     [617.0, 618.0, 619.0, 620.0],     [621.0, 622.0, 623.0, 624.0]],    [[625.0, 626.0, 627.0, 628.0],     [629.0, 630.0, 631.0, 632.0],     [633.0, 634.0, 635.0, 636.0],     [637.0, 638.0, 639.0, 640.0],     [641.0, 642.0, 643.0, 644.0],     [645.0, 646.0, 647.0, 648.0]],    [[649.0, 650.0, 651.0, 652.0],     [653.0, 654.0, 655.0, 656.0],     [657.0, 658.0, 659.0, 660.0],     [661.0, 662.0, 663.0, 664.0],     [665.0, 666.0, 667.0, 668.0],     [669.0, 670.0, 671.0, 672.0]]],   [[[673.0, 674.0, 675.0, 676.0],     [677.0, 678.0, 679.0, 680.0],     [681.0, 682.0, 683.0, 684.0],     [685.0, 686.0, 687.0, 688.0],     [689.0, 690.0, 691.0, 692.0],     [693.0, 694.0, 695.0, 696.0]],    [[697.0, 698.0, 699.0, 700.0],     [701.0, 702.0, 703.0, 704.0],     [705.0, 706.0, 707.0, 708.0],     [709.0, 710.0, 711.0, 712.0],     [713.0, 714.0, 715.0, 716.0],     [717.0, 718.0, 719.0, 720.0]],    [[721.0, 722.0, 723.0, 724.0],     [725.0, 726.0, 727.0, 728.0],     [729.0, 730.0, 731.0, 732.0],     [733.0, 734.0, 735.0, 736.0],     [737.0, 738.0, 739.0, 740.0],     [741.0, 742.0, 743.0, 744.0]],    [[745.0, 746.0, 747.0, 748.0],     [749.0, 750.0, 751.0, 752.0],     [753.0, 754.0, 755.0, 756.0],     [757.0, 758.0, 759.0, 760.0],     [761.0, 762.0, 763.0, 764.0],     [765.0, 766.0, 767.0, 768.0]],    [[769.0, 770.0, 771.0, 772.0],     [773.0, 774.0, 775.0, 776.0],     [777.0, 778.0, 779.0, 780.0],     [781.0, 782.0, 783.0, 784.0],     [785.0, 786.0, 787.0, 788.0],     [789.0, 790.0, 791.0, 792.0]],    [[793.0, 794.0, 795.0, 796.0],     [797.0, 798.0, 799.0, 800.0],     [801.0, 802.0, 803.0, 804.0],     [805.0, 806.0, 807.0, 808.0],     [809.0, 810.0, 811.0, 812.0],     [813.0, 814.0, 815.0, 816.0]],    [[817.0, 818.0, 819.0, 820.0],     [821.0, 822.0, 823.0, 824.0],     [825.0, 826.0, 827.0, 828.0],     [829.0, 830.0, 831.0, 832.0],     [833.0, 834.0, 835.0, 836.0],     [837.0, 838.0, 839.0, 840.0]]],   [[[841.0, 842.0, 843.0, 844.0],     [845.0, 846.0, 847.0, 848.0],     [849.0, 850.0, 851.0, 852.0],     [853.0, 854.0, 855.0, 856.0],     [857.0, 858.0, 859.0, 860.0],     [861.0, 862.0, 863.0, 864.0]],    [[865.0, 866.0, 867.0, 868.0],     [869.0, 870.0, 871.0, 872.0],     [873.0, 874.0, 875.0, 876.0],     [877.0, 878.0, 879.0, 880.0],     [881.0, 882.0, 883.0, 884.0],     [885.0, 886.0, 887.0, 888.0]],    [[889.0, 890.0, 891.0, 892.0],     [893.0, 894.0, 895.0, 896.0],     [897.0, 898.0, 899.0, 900.0],     [901.0, 902.0, 903.0, 904.0],     [905.0, 906.0, 907.0, 908.0],     [909.0, 910.0, 911.0, 912.0]],    [[913.0, 914.0, 915.0, 916.0],     [917.0, 918.0, 919.0, 920.0],     [921.0, 922.0, 923.0, 924.0],     [925.0, 926.0, 927.0, 928.0],     [929.0, 930.0, 931.0, 932.0],     [933.0, 934.0, 935.0, 936.0]],    [[937.0, 938.0, 939.0, 940.0],     [941.0, 942.0, 943.0, 944.0],     [945.0, 946.0, 947.0, 948.0],     [949.0, 950.0, 951.0, 952.0],     [953.0, 954.0, 955.0, 956.0],     [957.0, 958.0, 959.0, 960.0]],    [[961.0, 962.0, 963.0, 964.0],     [965.0, 966.0, 967.0, 968.0],     [969.0, 970.0, 971.0, 972.0],     [973.0, 974.0, 975.0, 976.0],     [977.0, 978.0, 979.0, 980.0],     [981.0, 982.0, 983.0, 984.0]],    [[985.0, 986.0, 987.0, 988.0],     [989.0, 990.0, 991.0, 992.0],     [993.0, 994.0, 995.0, 996.0],     [997.0, 998.0, 999.0, 1000.0],     [1001.0, 1002.0, 1003.0, 1004.0],     [1005.0, 1006.0, 1007.0, 1008.0]]]],  [[[[1009.0, 1010.0, 1011.0, 1012.0],     [1013.0, 1014.0, 1015.0, 1016.0],     [1017.0, 1018.0, 1019.0, 1020.0],     [1021.0, 1022.0, 1023.0, 1024.0],     [1025.0, 1026.0, 1027.0, 1028.0],     [1029.0, 1030.0, 1031.0, 1032.0]],    [[1033.0, 1034.0, 1035.0, 1036.0],     [1037.0, 1038.0, 1039.0, 1040.0],     [1041.0, 1042.0, 1043.0, 1044.0],     [1045.0, 1046.0, 1047.0, 1048.0],     [1049.0, 1050.0, 1051.0, 1052.0],     [1053.0, 1054.0, 1055.0, 1056.0]],    [[1057.0, 1058.0, 1059.0, 1060.0],     [1061.0, 1062.0, 1063.0, 1064.0],     [1065.0, 1066.0, 1067.0, 1068.0],     [1069.0, 1070.0, 1071.0, 1072.0],     [1073.0, 1074.0, 1075.0, 1076.0],     [1077.0, 1078.0, 1079.0, 1080.0]],    [[1081.0, 1082.0, 1083.0, 1084.0],     [1085.0, 1086.0, 1087.0, 1088.0],     [1089.0, 1090.0, 1091.0, 1092.0],     [1093.0, 1094.0, 1095.0, 1096.0],     [1097.0, 1098.0, 1099.0, 1100.0],     [1101.0, 1102.0, 1103.0, 1104.0]],    [[1105.0, 1106.0, 1107.0, 1108.0],     [1109.0, 1110.0, 1111.0, 1112.0],     [1113.0, 1114.0, 1115.0, 1116.0],     [1117.0, 1118.0, 1119.0, 1120.0],     [1121.0, 1122.0, 1123.0, 1124.0],     [1125.0, 1126.0, 1127.0, 1128.0]],    [[1129.0, 1130.0, 1131.0, 1132.0],     [1133.0, 1134.0, 1135.0, 1136.0],     [1137.0, 1138.0, 1139.0, 1140.0],     [1141.0, 1142.0, 1143.0, 1144.0],     [1145.0, 1146.0, 1147.0, 1148.0],     [1149.0, 1150.0, 1151.0, 1152.0]],    [[1153.0, 1154.0, 1155.0, 1156.0],     [1157.0, 1158.0, 1159.0, 1160.0],     [1161.0, 1162.0, 1163.0, 1164.0],     [1165.0, 1166.0, 1167.0, 1168.0],     [1169.0, 1170.0, 1171.0, 1172.0],     [1173.0, 1174.0, 1175.0, 1176.0]]],   [[[1177.0, 1178.0, 1179.0, 1180.0],     [1181.0, 1182.0, 1183.0, 1184.0],     [1185.0, 1186.0, 1187.0, 1188.0],     [1189.0, 1190.0, 1191.0, 1192.0],     [1193.0, 1194.0, 1195.0, 1196.0],     [1197.0, 1198.0, 1199.0, 1200.0]],    [[1201.0, 1202.0, 1203.0, 1204.0],     [1205.0, 1206.0, 1207.0, 1208.0],     [1209.0, 1210.0, 1211.0, 1212.0],     [1213.0, 1214.0, 1215.0, 1216.0],     [1217.0, 1218.0, 1219.0, 1220.0],     [1221.0, 1222.0, 1223.0, 1224.0]],    [[1225.0, 1226.0, 1227.0, 1228.0],     [1229.0, 1230.0, 1231.0, 1232.0],     [1233.0, 1234.0, 1235.0, 1236.0],     [1237.0, 1238.0, 1239.0, 1240.0],     [1241.0, 1242.0, 1243.0, 1244.0],     [1245.0, 1246.0, 1247.0, 1248.0]],    [[1249.0, 1250.0, 1251.0, 1252.0],     [1253.0, 1254.0, 1255.0, 1256.0],     [1257.0, 1258.0, 1259.0, 1260.0],     [1261.0, 1262.0, 1263.0, 1264.0],     [1265.0, 1266.0, 1267.0, 1268.0],     [1269.0, 1270.0, 1271.0, 1272.0]],    [[1273.0, 1274.0, 1275.0, 1276.0],     [1277.0, 1278.0, 1279.0, 1280.0],     [1281.0, 1282.0, 1283.0, 1284.0],     [1285.0, 1286.0, 1287.0, 1288.0],     [1289.0, 1290.0, 1291.0, 1292.0],     [1293.0, 1294.0, 1295.0, 1296.0]],    [[1297.0, 1298.0, 1299.0, 1300.0],     [1301.0, 1302.0, 1303.0, 1304.0],     [1305.0, 1306.0, 1307.0, 1308.0],     [1309.0, 1310.0, 1311.0, 1312.0],     [1313.0, 1314.0, 1315.0, 1316.0],     [1317.0, 1318.0, 1319.0, 1320.0]],    [[1321.0, 1322.0, 1323.0, 1324.0],     [1325.0, 1326.0, 1327.0, 1328.0],     [1329.0, 1330.0, 1331.0, 1332.0],     [1333.0, 1334.0, 1335.0, 1336.0],     [1337.0, 1338.0, 1339.0, 1340.0],     [1341.0, 1342.0, 1343.0, 1344.0]]],   [[[1345.0, 1346.0, 1347.0, 1348.0],     [1349.0, 1350.0, 1351.0, 1352.0],     [1353.0, 1354.0, 1355.0, 1356.0],     [1357.0, 1358.0, 1359.0, 1360.0],     [1361.0, 1362.0, 1363.0, 1364.0],     [1365.0, 1366.0, 1367.0, 1368.0]],    [[1369.0, 1370.0, 1371.0, 1372.0],     [1373.0, 1374.0, 1375.0, 1376.0],     [1377.0, 1378.0, 1379.0, 1380.0],     [1381.0, 1382.0, 1383.0, 1384.0],     [1385.0, 1386.0, 1387.0, 1388.0],     [1389.0, 1390.0, 1391.0, 1392.0]],    [[1393.0, 1394.0, 1395.0, 1396.0],     [1397.0, 1398.0, 1399.0, 1400.0],     [1401.0, 1402.0, 1403.0, 1404.0],     [1405.0, 1406.0, 1407.0, 1408.0],     [1409.0, 1410.0, 1411.0, 1412.0],     [1413.0, 1414.0, 1415.0, 1416.0]],    [[1417.0, 1418.0, 1419.0, 1420.0],     [1421.0, 1422.0, 1423.0, 1424.0],     [1425.0, 1426.0, 1427.0, 1428.0],     [1429.0, 1430.0, 1431.0, 1432.0],     [1433.0, 1434.0, 1435.0, 1436.0],     [1437.0, 1438.0, 1439.0, 1440.0]],    [[1441.0, 1442.0, 1443.0, 1444.0],     [1445.0, 1446.0, 1447.0, 1448.0],     [1449.0, 1450.0, 1451.0, 1452.0],     [1453.0, 1454.0, 1455.0, 1456.0],     [1457.0, 1458.0, 1459.0, 1460.0],     [1461.0, 1462.0, 1463.0, 1464.0]],    [[1465.0, 1466.0, 1467.0, 1468.0],     [1469.0, 1470.0, 1471.0, 1472.0],     [1473.0, 1474.0, 1475.0, 1476.0],     [1477.0, 1478.0, 1479.0, 1480.0],     [1481.0, 1482.0, 1483.0, 1484.0],     [1485.0, 1486.0, 1487.0, 1488.0]],    [[1489.0, 1490.0, 1491.0, 1492.0],     [1493.0, 1494.0, 1495.0, 1496.0],     [1497.0, 1498.0, 1499.0, 1500.0],     [1501.0, 1502.0, 1503.0, 1504.0],     [1505.0, 1506.0, 1507.0, 1508.0],     [1509.0, 1510.0, 1511.0, 1512.0]]],   [[[1513.0, 1514.0, 1515.0, 1516.0],     [1517.0, 1518.0, 1519.0, 1520.0],     [1521.0, 1522.0, 1523.0, 1524.0],     [1525.0, 1526.0, 1527.0, 1528.0],     [1529.0, 1530.0, 1531.0, 1532.0],     [1533.0, 1534.0, 1535.0, 1536.0]],    [[1537.0, 1538.0, 1539.0, 1540.0],     [1541.0, 1542.0, 1543.0, 1544.0],     [1545.0, 1546.0, 1547.0, 1548.0],     [1549.0, 1550.0, 1551.0, 1552.0],     [1553.0, 1554.0, 1555.0, 1556.0],     [1557.0, 1558.0, 1559.0, 1560.0]],    [[1561.0, 1562.0, 1563.0, 1564.0],     [1565.0, 1566.0, 1567.0, 1568.0],     [1569.0, 1570.0, 1571.0, 1572.0],     [1573.0, 1574.0, 1575.0, 1576.0],     [1577.0, 1578.0, 1579.0, 1580.0],     [1581.0, 1582.0, 1583.0, 1584.0]],    [[1585.0, 1586.0, 1587.0, 1588.0],     [1589.0, 1590.0, 1591.0, 1592.0],     [1593.0, 1594.0, 1595.0, 1596.0],     [1597.0, 1598.0, 1599.0, 1600.0],     [1601.0, 1602.0, 1603.0, 1604.0],     [1605.0, 1606.0, 1607.0, 1608.0]],    [[1609.0, 1610.0, 1611.0, 1612.0],     [1613.0, 1614.0, 1615.0, 1616.0],     [1617.0, 1618.0, 1619.0, 1620.0],     [1621.0, 1622.0, 1623.0, 1624.0],     [1625.0, 1626.0, 1627.0, 1628.0],     [1629.0, 1630.0, 1631.0, 1632.0]],    [[1633.0, 1634.0, 1635.0, 1636.0],     [1637.0, 1638.0, 1639.0, 1640.0],     [1641.0, 1642.0, 1643.0, 1644.0],     [1645.0, 1646.0, 1647.0, 1648.0],     [1649.0, 1650.0, 1651.0, 1652.0],     [1653.0, 1654.0, 1655.0, 1656.0]],    [[1657.0, 1658.0, 1659.0, 1660.0],     [1661.0, 1662.0, 1663.0, 1664.0],     [1665.0, 1666.0, 1667.0, 1668.0],     [1669.0, 1670.0, 1671.0, 1672.0],     [1673.0, 1674.0, 1675.0, 1676.0],     [1677.0, 1678.0, 1679.0, 1680.0]]],   [[[1681.0, 1682.0, 1683.0, 1684.0],     [1685.0, 1686.0, 1687.0, 1688.0],     [1689.0, 1690.0, 1691.0, 1692.0],     [1693.0, 1694.0, 1695.0, 1696.0],     [1697.0, 1698.0, 1699.0, 1700.0],     [1701.0, 1702.0, 1703.0, 1704.0]],    [[1705.0, 1706.0, 1707.0, 1708.0],     [1709.0, 1710.0, 1711.0, 1712.0],     [1713.0, 1714.0, 1715.0, 1716.0],     [1717.0, 1718.0, 1719.0, 1720.0],     [1721.0, 1722.0, 1723.0, 1724.0],     [1725.0, 1726.0, 1727.0, 1728.0]],    [[1729.0, 1730.0, 1731.0, 1732.0],     [1733.0, 1734.0, 1735.0, 1736.0],     [1737.0, 1738.0, 1739.0, 1740.0],     [1741.0, 1742.0, 1743.0, 1744.0],     [1745.0, 1746.0, 1747.0, 1748.0],     [1749.0, 1750.0, 1751.0, 1752.0]],    [[1753.0, 1754.0, 1755.0, 1756.0],     [1757.0, 1758.0, 1759.0, 1760.0],     [1761.0, 1762.0, 1763.0, 1764.0],     [1765.0, 1766.0, 1767.0, 1768.0],     [1769.0, 1770.0, 1771.0, 1772.0],     [1773.0, 1774.0, 1775.0, 1776.0]],    [[1777.0, 1778.0, 1779.0, 1780.0],     [1781.0, 1782.0, 1783.0, 1784.0],     [1785.0, 1786.0, 1787.0, 1788.0],     [1789.0, 1790.0, 1791.0, 1792.0],     [1793.0, 1794.0, 1795.0, 1796.0],     [1797.0, 1798.0, 1799.0, 1800.0]],    [[1801.0, 1802.0, 1803.0, 1804.0],     [1805.0, 1806.0, 1807.0, 1808.0],     [1809.0, 1810.0, 1811.0, 1812.0],     [1813.0, 1814.0, 1815.0, 1816.0],     [1817.0, 1818.0, 1819.0, 1820.0],     [1821.0, 1822.0, 1823.0, 1824.0]],    [[1825.0, 1826.0, 1827.0, 1828.0],     [1829.0, 1830.0, 1831.0, 1832.0],     [1833.0, 1834.0, 1835.0, 1836.0],     [1837.0, 1838.0, 1839.0, 1840.0],     [1841.0, 1842.0, 1843.0, 1844.0],     [1845.0, 1846.0, 1847.0, 1848.0]]],   [[[1849.0, 1850.0, 1851.0, 1852.0],     [1853.0, 1854.0, 1855.0, 1856.0],     [1857.0, 1858.0, 1859.0, 1860.0],     [1861.0, 1862.0, 1863.0, 1864.0],     [1865.0, 1866.0, 1867.0, 1868.0],     [1869.0, 1870.0, 1871.0, 1872.0]],    [[1873.0, 1874.0, 1875.0, 1876.0],     [1877.0, 1878.0, 1879.0, 1880.0],     [1881.0, 1882.0, 1883.0, 1884.0],     [1885.0, 1886.0, 1887.0, 1888.0],     [1889.0, 1890.0, 1891.0, 1892.0],     [1893.0, 1894.0, 1895.0, 1896.0]],    [[1897.0, 1898.0, 1899.0, 1900.0],     [1901.0, 1902.0, 1903.0, 1904.0],     [1905.0, 1906.0, 1907.0, 1908.0],     [1909.0, 1910.0, 1911.0, 1912.0],     [1913.0, 1914.0, 1915.0, 1916.0],     [1917.0, 1918.0, 1919.0, 1920.0]],    [[1921.0, 1922.0, 1923.0, 1924.0],     [1925.0, 1926.0, 1927.0, 1928.0],     [1929.0, 1930.0, 1931.0, 1932.0],     [1933.0, 1934.0, 1935.0, 1936.0],     [1937.0, 1938.0, 1939.0, 1940.0],     [1941.0, 1942.0, 1943.0, 1944.0]],    [[1945.0, 1946.0, 1947.0, 1948.0],     [1949.0, 1950.0, 1951.0, 1952.0],     [1953.0, 1954.0, 1955.0, 1956.0],     [1957.0, 1958.0, 1959.0, 1960.0],     [1961.0, 1962.0, 1963.0, 1964.0],     [1965.0, 1966.0, 1967.0, 1968.0]],    [[1969.0, 1970.0, 1971.0, 1972.0],     [1973.0, 1974.0, 1975.0, 1976.0],     [1977.0, 1978.0, 1979.0, 1980.0],     [1981.0, 1982.0, 1983.0, 1984.0],     [1985.0, 1986.0, 1987.0, 1988.0],     [1989.0, 1990.0, 1991.0, 1992.0]],    [[1993.0, 1994.0, 1995.0, 1996.0],     [1997.0, 1998.0, 1999.0, 2000.0],     [2001.0, 2002.0, 2003.0, 2004.0],     [2005.0, 2006.0, 2007.0, 2008.0],     [2009.0, 2010.0, 2011.0, 2012.0],     [2013.0, 2014.0, 2015.0, 2016.0]]]]] shape=[2, 6, 7, 6, 4], strides=[1008, 168, 24, 4, 1], layout=C (0x1)), I32([3, 2, 1] shape=[3], strides=[1], layout=C | F (0x3)), I32([[2, 1],  [1, 2],  [2, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 2228840940 4243776559 3263077396 412719432 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[[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]]]],    [[[[16.0]]],     [[[17.0]]],     [[[18.0]]]]],   [[[[[19.0]]],     [[[20.0]]],     [[[21.0]]]],    [[[[22.0]]],     [[[23.0]]],     [[[24.0]]]]],   [[[[[25.0]]],     [[[26.0]]],     [[[27.0]]]],    [[[[28.0]]],     [[[29.0]]],     [[[30.0]]]]],   [[[[[31.0]]],     [[[32.0]]],     [[[33.0]]]],    [[[[34.0]]],     [[[35.0]]],     [[[36.0]]]]]],  [[[[[[37.0]]],     [[[38.0]]],     [[[39.0]]]],    [[[[40.0]]],     [[[41.0]]],     [[[42.0]]]]],   [[[[[43.0]]],     [[[44.0]]],     [[[45.0]]]],    [[[[46.0]]],     [[[47.0]]],     [[[48.0]]]]],   [[[[[49.0]]],     [[[50.0]]],     [[[51.0]]]],    [[[[52.0]]],     [[[53.0]]],     [[[54.0]]]]],   [[[[[55.0]]],     [[[56.0]]],     [[[57.0]]]],    [[[[58.0]]],     [[[59.0]]],     [[[60.0]]]]],   [[[[[61.0]]],     [[[62.0]]],     [[[63.0]]]],    [[[[64.0]]],     [[[65.0]]],     [[[66.0]]]]],   [[[[[67.0]]],     [[[68.0]]],     [[[69.0]]]],    [[[[70.0]]],     [[[71.0]]],     [[[72.0]]]]]],  [[[[[[73.0]]],     [[[74.0]]],     [[[75.0]]]],    [[[[76.0]]],     [[[77.0]]],     [[[78.0]]]]],   [[[[[79.0]]],     [[[80.0]]],     [[[81.0]]]],    [[[[82.0]]],     [[[83.0]]],     [[[84.0]]]]],   [[[[[85.0]]],     [[[86.0]]],     [[[87.0]]]],    [[[[88.0]]],     [[[89.0]]],     [[[90.0]]]]],   [[[[[91.0]]],     [[[92.0]]],     [[[93.0]]]],    [[[[94.0]]],     [[[95.0]]],     [[[96.0]]]]],   [[[[[97.0]]],     [[[98.0]]],     [[[99.0]]]],    [[[[100.0]]],     [[[101.0]]],     [[[102.0]]]]],   [[[[[103.0]]],     [[[104.0]]],     [[[105.0]]]],    [[[[106.0]]],     [[[107.0]]],     [[[108.0]]]]]]] shape=[3, 6, 2, 3, 1, 1, 1], strides=[36, 6, 3, 1, 1, 1, 1], layout=C (0x1)), I32([1, 1, 1] shape=[3], strides=[1], layout=C | F (0x3)), I32([[1, 1],  [3, 1],  [0, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 2924052819 2525477587 82501562 1581537078 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[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, 16.0, 17.0, 18.0],     [19.0, 20.0, 21.0, 22.0, 23.0, 24.0],     [25.0, 26.0, 27.0, 28.0, 29.0, 30.0]],    [[31.0, 32.0, 33.0, 34.0, 35.0, 36.0],     [37.0, 38.0, 39.0, 40.0, 41.0, 42.0],     [43.0, 44.0, 45.0, 46.0, 47.0, 48.0],     [49.0, 50.0, 51.0, 52.0, 53.0, 54.0],     [55.0, 56.0, 57.0, 58.0, 59.0, 60.0]],    [[61.0, 62.0, 63.0, 64.0, 65.0, 66.0],     [67.0, 68.0, 69.0, 70.0, 71.0, 72.0],     [73.0, 74.0, 75.0, 76.0, 77.0, 78.0],     [79.0, 80.0, 81.0, 82.0, 83.0, 84.0],     [85.0, 86.0, 87.0, 88.0, 89.0, 90.0]],    [[91.0, 92.0, 93.0, 94.0, 95.0, 96.0],     [97.0, 98.0, 99.0, 100.0, 101.0, 102.0],     [103.0, 104.0, 105.0, 106.0, 107.0, 108.0],     [109.0, 110.0, 111.0, 112.0, 113.0, 114.0],     [115.0, 116.0, 117.0, 118.0, 119.0, 120.0]],    [[121.0, 122.0, 123.0, 124.0, 125.0, 126.0],     [127.0, 128.0, 129.0, 130.0, 131.0, 132.0],     [133.0, 134.0, 135.0, 136.0, 137.0, 138.0],     [139.0, 140.0, 141.0, 142.0, 143.0, 144.0],     [145.0, 146.0, 147.0, 148.0, 149.0, 150.0]],    [[151.0, 152.0, 153.0, 154.0, 155.0, 156.0],     [157.0, 158.0, 159.0, 160.0, 161.0, 162.0],     [163.0, 164.0, 165.0, 166.0, 167.0, 168.0],     [169.0, 170.0, 171.0, 172.0, 173.0, 174.0],     [175.0, 176.0, 177.0, 178.0, 179.0, 180.0]]],   [[[181.0, 182.0, 183.0, 184.0, 185.0, 186.0],     [187.0, 188.0, 189.0, 190.0, 191.0, 192.0],     [193.0, 194.0, 195.0, 196.0, 197.0, 198.0],     [199.0, 200.0, 201.0, 202.0, 203.0, 204.0],     [205.0, 206.0, 207.0, 208.0, 209.0, 210.0]],    [[211.0, 212.0, 213.0, 214.0, 215.0, 216.0],     [217.0, 218.0, 219.0, 220.0, 221.0, 222.0],     [223.0, 224.0, 225.0, 226.0, 227.0, 228.0],     [229.0, 230.0, 231.0, 232.0, 233.0, 234.0],     [235.0, 236.0, 237.0, 238.0, 239.0, 240.0]],    [[241.0, 242.0, 243.0, 244.0, 245.0, 246.0],     [247.0, 248.0, 249.0, 250.0, 251.0, 252.0],     [253.0, 254.0, 255.0, 256.0, 257.0, 258.0],     [259.0, 260.0, 261.0, 262.0, 263.0, 264.0],     [265.0, 266.0, 267.0, 268.0, 269.0, 270.0]],    [[271.0, 272.0, 273.0, 274.0, 275.0, 276.0],     [277.0, 278.0, 279.0, 280.0, 281.0, 282.0],     [283.0, 284.0, 285.0, 286.0, 287.0, 288.0],     [289.0, 290.0, 291.0, 292.0, 293.0, 294.0],     [295.0, 296.0, 297.0, 298.0, 299.0, 300.0]],    [[301.0, 302.0, 303.0, 304.0, 305.0, 306.0],     [307.0, 308.0, 309.0, 310.0, 311.0, 312.0],     [313.0, 314.0, 315.0, 316.0, 317.0, 318.0],     [319.0, 320.0, 321.0, 322.0, 323.0, 324.0],     [325.0, 326.0, 327.0, 328.0, 329.0, 330.0]],    [[331.0, 332.0, 333.0, 334.0, 335.0, 336.0],     [337.0, 338.0, 339.0, 340.0, 341.0, 342.0],     [343.0, 344.0, 345.0, 346.0, 347.0, 348.0],     [349.0, 350.0, 351.0, 352.0, 353.0, 354.0],     [355.0, 356.0, 357.0, 358.0, 359.0, 360.0]]],   [[[361.0, 362.0, 363.0, 364.0, 365.0, 366.0],     [367.0, 368.0, 369.0, 370.0, 371.0, 372.0],     [373.0, 374.0, 375.0, 376.0, 377.0, 378.0],     [379.0, 380.0, 381.0, 382.0, 383.0, 384.0],     [385.0, 386.0, 387.0, 388.0, 389.0, 390.0]],    [[391.0, 392.0, 393.0, 394.0, 395.0, 396.0],     [397.0, 398.0, 399.0, 400.0, 401.0, 402.0],     [403.0, 404.0, 405.0, 406.0, 407.0, 408.0],     [409.0, 410.0, 411.0, 412.0, 413.0, 414.0],     [415.0, 416.0, 417.0, 418.0, 419.0, 420.0]],    [[421.0, 422.0, 423.0, 424.0, 425.0, 426.0],     [427.0, 428.0, 429.0, 430.0, 431.0, 432.0],     [433.0, 434.0, 435.0, 436.0, 437.0, 438.0],     [439.0, 440.0, 441.0, 442.0, 443.0, 444.0],     [445.0, 446.0, 447.0, 448.0, 449.0, 450.0]],    [[451.0, 452.0, 453.0, 454.0, 455.0, 456.0],     [457.0, 458.0, 459.0, 460.0, 461.0, 462.0],     [463.0, 464.0, 465.0, 466.0, 467.0, 468.0],     [469.0, 470.0, 471.0, 472.0, 473.0, 474.0],     [475.0, 476.0, 477.0, 478.0, 479.0, 480.0]],    [[481.0, 482.0, 483.0, 484.0, 485.0, 486.0],     [487.0, 488.0, 489.0, 490.0, 491.0, 492.0],     [493.0, 494.0, 495.0, 496.0, 497.0, 498.0],     [499.0, 500.0, 501.0, 502.0, 503.0, 504.0],     [505.0, 506.0, 507.0, 508.0, 509.0, 510.0]],    [[511.0, 512.0, 513.0, 514.0, 515.0, 516.0],     [517.0, 518.0, 519.0, 520.0, 521.0, 522.0],     [523.0, 524.0, 525.0, 526.0, 527.0, 528.0],     [529.0, 530.0, 531.0, 532.0, 533.0, 534.0],     [535.0, 536.0, 537.0, 538.0, 539.0, 540.0]]],   [[[541.0, 542.0, 543.0, 544.0, 545.0, 546.0],     [547.0, 548.0, 549.0, 550.0, 551.0, 552.0],     [553.0, 554.0, 555.0, 556.0, 557.0, 558.0],     [559.0, 560.0, 561.0, 562.0, 563.0, 564.0],     [565.0, 566.0, 567.0, 568.0, 569.0, 570.0]],    [[571.0, 572.0, 573.0, 574.0, 575.0, 576.0],     [577.0, 578.0, 579.0, 580.0, 581.0, 582.0],     [583.0, 584.0, 585.0, 586.0, 587.0, 588.0],     [589.0, 590.0, 591.0, 592.0, 593.0, 594.0],     [595.0, 596.0, 597.0, 598.0, 599.0, 600.0]],    [[601.0, 602.0, 603.0, 604.0, 605.0, 606.0],     [607.0, 608.0, 609.0, 610.0, 611.0, 612.0],     [613.0, 614.0, 615.0, 616.0, 617.0, 618.0],     [619.0, 620.0, 621.0, 622.0, 623.0, 624.0],     [625.0, 626.0, 627.0, 628.0, 629.0, 630.0]],    [[631.0, 632.0, 633.0, 634.0, 635.0, 636.0],     [637.0, 638.0, 639.0, 640.0, 641.0, 642.0],     [643.0, 644.0, 645.0, 646.0, 647.0, 648.0],     [649.0, 650.0, 651.0, 652.0, 653.0, 654.0],     [655.0, 656.0, 657.0, 658.0, 659.0, 660.0]],    [[661.0, 662.0, 663.0, 664.0, 665.0, 666.0],     [667.0, 668.0, 669.0, 670.0, 671.0, 672.0],     [673.0, 674.0, 675.0, 676.0, 677.0, 678.0],     [679.0, 680.0, 681.0, 682.0, 683.0, 684.0],     [685.0, 686.0, 687.0, 688.0, 689.0, 690.0]],    [[691.0, 692.0, 693.0, 694.0, 695.0, 696.0],     [697.0, 698.0, 699.0, 700.0, 701.0, 702.0],     [703.0, 704.0, 705.0, 706.0, 707.0, 708.0],     [709.0, 710.0, 711.0, 712.0, 713.0, 714.0],     [715.0, 716.0, 717.0, 718.0, 719.0, 720.0]]]],  [[[[721.0, 722.0, 723.0, 724.0, 725.0, 726.0],     [727.0, 728.0, 729.0, 730.0, 731.0, 732.0],     [733.0, 734.0, 735.0, 736.0, 737.0, 738.0],     [739.0, 740.0, 741.0, 742.0, 743.0, 744.0],     [745.0, 746.0, 747.0, 748.0, 749.0, 750.0]],    [[751.0, 752.0, 753.0, 754.0, 755.0, 756.0],     [757.0, 758.0, 759.0, 760.0, 761.0, 762.0],     [763.0, 764.0, 765.0, 766.0, 767.0, 768.0],     [769.0, 770.0, 771.0, 772.0, 773.0, 774.0],     [775.0, 776.0, 777.0, 778.0, 779.0, 780.0]],    [[781.0, 782.0, 783.0, 784.0, 785.0, 786.0],     [787.0, 788.0, 789.0, 790.0, 791.0, 792.0],     [793.0, 794.0, 795.0, 796.0, 797.0, 798.0],     [799.0, 800.0, 801.0, 802.0, 803.0, 804.0],     [805.0, 806.0, 807.0, 808.0, 809.0, 810.0]],    [[811.0, 812.0, 813.0, 814.0, 815.0, 816.0],     [817.0, 818.0, 819.0, 820.0, 821.0, 822.0],     [823.0, 824.0, 825.0, 826.0, 827.0, 828.0],     [829.0, 830.0, 831.0, 832.0, 833.0, 834.0],     [835.0, 836.0, 837.0, 838.0, 839.0, 840.0]],    [[841.0, 842.0, 843.0, 844.0, 845.0, 846.0],     [847.0, 848.0, 849.0, 850.0, 851.0, 852.0],     [853.0, 854.0, 855.0, 856.0, 857.0, 858.0],     [859.0, 860.0, 861.0, 862.0, 863.0, 864.0],     [865.0, 866.0, 867.0, 868.0, 869.0, 870.0]],    [[871.0, 872.0, 873.0, 874.0, 875.0, 876.0],     [877.0, 878.0, 879.0, 880.0, 881.0, 882.0],     [883.0, 884.0, 885.0, 886.0, 887.0, 888.0],     [889.0, 890.0, 891.0, 892.0, 893.0, 894.0],     [895.0, 896.0, 897.0, 898.0, 899.0, 900.0]]],   [[[901.0, 902.0, 903.0, 904.0, 905.0, 906.0],     [907.0, 908.0, 909.0, 910.0, 911.0, 912.0],     [913.0, 914.0, 915.0, 916.0, 917.0, 918.0],     [919.0, 920.0, 921.0, 922.0, 923.0, 924.0],     [925.0, 926.0, 927.0, 928.0, 929.0, 930.0]],    [[931.0, 932.0, 933.0, 934.0, 935.0, 936.0],     [937.0, 938.0, 939.0, 940.0, 941.0, 942.0],     [943.0, 944.0, 945.0, 946.0, 947.0, 948.0],     [949.0, 950.0, 951.0, 952.0, 953.0, 954.0],     [955.0, 956.0, 957.0, 958.0, 959.0, 960.0]],    [[961.0, 962.0, 963.0, 964.0, 965.0, 966.0],     [967.0, 968.0, 969.0, 970.0, 971.0, 972.0],     [973.0, 974.0, 975.0, 976.0, 977.0, 978.0],     [979.0, 980.0, 981.0, 982.0, 983.0, 984.0],     [985.0, 986.0, 987.0, 988.0, 989.0, 990.0]],    [[991.0, 992.0, 993.0, 994.0, 995.0, 996.0],     [997.0, 998.0, 999.0, 1000.0, 1001.0, 1002.0],     [1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0],     [1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0],     [1015.0, 1016.0, 1017.0, 1018.0, 1019.0, 1020.0]],    [[1021.0, 1022.0, 1023.0, 1024.0, 1025.0, 1026.0],     [1027.0, 1028.0, 1029.0, 1030.0, 1031.0, 1032.0],     [1033.0, 1034.0, 1035.0, 1036.0, 1037.0, 1038.0],     [1039.0, 1040.0, 1041.0, 1042.0, 1043.0, 1044.0],     [1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0]],    [[1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0],     [1057.0, 1058.0, 1059.0, 1060.0, 1061.0, 1062.0],     [1063.0, 1064.0, 1065.0, 1066.0, 1067.0, 1068.0],     [1069.0, 1070.0, 1071.0, 1072.0, 1073.0, 1074.0],     [1075.0, 1076.0, 1077.0, 1078.0, 1079.0, 1080.0]]],   [[[1081.0, 1082.0, 1083.0, 1084.0, 1085.0, 1086.0],     [1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0],     [1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0],     [1099.0, 1100.0, 1101.0, 1102.0, 1103.0, 1104.0],     [1105.0, 1106.0, 1107.0, 1108.0, 1109.0, 1110.0]],    [[1111.0, 1112.0, 1113.0, 1114.0, 1115.0, 1116.0],     [1117.0, 1118.0, 1119.0, 1120.0, 1121.0, 1122.0],     [1123.0, 1124.0, 1125.0, 1126.0, 1127.0, 1128.0],     [1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0],     [1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0]],    [[1141.0, 1142.0, 1143.0, 1144.0, 1145.0, 1146.0],     [1147.0, 1148.0, 1149.0, 1150.0, 1151.0, 1152.0],     [1153.0, 1154.0, 1155.0, 1156.0, 1157.0, 1158.0],     [1159.0, 1160.0, 1161.0, 1162.0, 1163.0, 1164.0],     [1165.0, 1166.0, 1167.0, 1168.0, 1169.0, 1170.0]],    [[1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0],     [1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0],     [1183.0, 1184.0, 1185.0, 1186.0, 1187.0, 1188.0],     [1189.0, 1190.0, 1191.0, 1192.0, 1193.0, 1194.0],     [1195.0, 1196.0, 1197.0, 1198.0, 1199.0, 1200.0]],    [[1201.0, 1202.0, 1203.0, 1204.0, 1205.0, 1206.0],     [1207.0, 1208.0, 1209.0, 1210.0, 1211.0, 1212.0],     [1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0],     [1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0],     [1225.0, 1226.0, 1227.0, 1228.0, 1229.0, 1230.0]],    [[1231.0, 1232.0, 1233.0, 1234.0, 1235.0, 1236.0],     [1237.0, 1238.0, 1239.0, 1240.0, 1241.0, 1242.0],     [1243.0, 1244.0, 1245.0, 1246.0, 1247.0, 1248.0],     [1249.0, 1250.0, 1251.0, 1252.0, 1253.0, 1254.0],     [1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]]],   [[[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0],     [1267.0, 1268.0, 1269.0, 1270.0, 1271.0, 1272.0],     [1273.0, 1274.0, 1275.0, 1276.0, 1277.0, 1278.0],     [1279.0, 1280.0, 1281.0, 1282.0, 1283.0, 1284.0],     [1285.0, 1286.0, 1287.0, 1288.0, 1289.0, 1290.0]],    [[1291.0, 1292.0, 1293.0, 1294.0, 1295.0, 1296.0],     [1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0],     [1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0],     [1309.0, 1310.0, 1311.0, 1312.0, 1313.0, 1314.0],     [1315.0, 1316.0, 1317.0, 1318.0, 1319.0, 1320.0]],    [[1321.0, 1322.0, 1323.0, 1324.0, 1325.0, 1326.0],     [1327.0, 1328.0, 1329.0, 1330.0, 1331.0, 1332.0],     [1333.0, 1334.0, 1335.0, 1336.0, 1337.0, 1338.0],     [1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0],     [1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0]],    [[1351.0, 1352.0, 1353.0, 1354.0, 1355.0, 1356.0],     [1357.0, 1358.0, 1359.0, 1360.0, 1361.0, 1362.0],     [1363.0, 1364.0, 1365.0, 1366.0, 1367.0, 1368.0],     [1369.0, 1370.0, 1371.0, 1372.0, 1373.0, 1374.0],     [1375.0, 1376.0, 1377.0, 1378.0, 1379.0, 1380.0]],    [[1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0],     [1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0],     [1393.0, 1394.0, 1395.0, 1396.0, 1397.0, 1398.0],     [1399.0, 1400.0, 1401.0, 1402.0, 1403.0, 1404.0],     [1405.0, 1406.0, 1407.0, 1408.0, 1409.0, 1410.0]],    [[1411.0, 1412.0, 1413.0, 1414.0, 1415.0, 1416.0],     [1417.0, 1418.0, 1419.0, 1420.0, 1421.0, 1422.0],     [1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0],     [1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0],     [1435.0, 1436.0, 1437.0, 1438.0, 1439.0, 1440.0]]]]] shape=[2, 4, 6, 5, 6], strides=[720, 180, 30, 6, 1], layout=C (0x1)), I32([2, 2] shape=[2], strides=[1], layout=C | F (0x3)), I32([[3, 1],  [1, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\nxs 3858399272 3935243603 3174388364 3824137134 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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]]],    [[[16.0, 17.0, 18.0, 19.0, 20.0]],     [[21.0, 22.0, 23.0, 24.0, 25.0]],     [[26.0, 27.0, 28.0, 29.0, 30.0]]],    [[[31.0, 32.0, 33.0, 34.0, 35.0]],     [[36.0, 37.0, 38.0, 39.0, 40.0]],     [[41.0, 42.0, 43.0, 44.0, 45.0]]],    [[[46.0, 47.0, 48.0, 49.0, 50.0]],     [[51.0, 52.0, 53.0, 54.0, 55.0]],     [[56.0, 57.0, 58.0, 59.0, 60.0]]],    [[[61.0, 62.0, 63.0, 64.0, 65.0]],     [[66.0, 67.0, 68.0, 69.0, 70.0]],     [[71.0, 72.0, 73.0, 74.0, 75.0]]]],   [[[[76.0, 77.0, 78.0, 79.0, 80.0]],     [[81.0, 82.0, 83.0, 84.0, 85.0]],     [[86.0, 87.0, 88.0, 89.0, 90.0]]],    [[[91.0, 92.0, 93.0, 94.0, 95.0]],     [[96.0, 97.0, 98.0, 99.0, 100.0]],     [[101.0, 102.0, 103.0, 104.0, 105.0]]],    [[[106.0, 107.0, 108.0, 109.0, 110.0]],     [[111.0, 112.0, 113.0, 114.0, 115.0]],     [[116.0, 117.0, 118.0, 119.0, 120.0]]],    [[[121.0, 122.0, 123.0, 124.0, 125.0]],     [[126.0, 127.0, 128.0, 129.0, 130.0]],     [[131.0, 132.0, 133.0, 134.0, 135.0]]],    [[[136.0, 137.0, 138.0, 139.0, 140.0]],     [[141.0, 142.0, 143.0, 144.0, 145.0]],     [[146.0, 147.0, 148.0, 149.0, 150.0]]]],   [[[[151.0, 152.0, 153.0, 154.0, 155.0]],     [[156.0, 157.0, 158.0, 159.0, 160.0]],     [[161.0, 162.0, 163.0, 164.0, 165.0]]],    [[[166.0, 167.0, 168.0, 169.0, 170.0]],     [[171.0, 172.0, 173.0, 174.0, 175.0]],     [[176.0, 177.0, 178.0, 179.0, 180.0]]],    [[[181.0, 182.0, 183.0, 184.0, 185.0]],     [[186.0, 187.0, 188.0, 189.0, 190.0]],     [[191.0, 192.0, 193.0, 194.0, 195.0]]],    [[[196.0, 197.0, 198.0, 199.0, 200.0]],     [[201.0, 202.0, 203.0, 204.0, 205.0]],     [[206.0, 207.0, 208.0, 209.0, 210.0]]],    [[[211.0, 212.0, 213.0, 214.0, 215.0]],     [[216.0, 217.0, 218.0, 219.0, 220.0]],     [[221.0, 222.0, 223.0, 224.0, 225.0]]]]],  [[[[[226.0, 227.0, 228.0, 229.0, 230.0]],     [[231.0, 232.0, 233.0, 234.0, 235.0]],     [[236.0, 237.0, 238.0, 239.0, 240.0]]],    [[[241.0, 242.0, 243.0, 244.0, 245.0]],     [[246.0, 247.0, 248.0, 249.0, 250.0]],     [[251.0, 252.0, 253.0, 254.0, 255.0]]],    [[[256.0, 257.0, 258.0, 259.0, 260.0]],     [[261.0, 262.0, 263.0, 264.0, 265.0]],     [[266.0, 267.0, 268.0, 269.0, 270.0]]],    [[[271.0, 272.0, 273.0, 274.0, 275.0]],     [[276.0, 277.0, 278.0, 279.0, 280.0]],     [[281.0, 282.0, 283.0, 284.0, 285.0]]],    [[[286.0, 287.0, 288.0, 289.0, 290.0]],     [[291.0, 292.0, 293.0, 294.0, 295.0]],     [[296.0, 297.0, 298.0, 299.0, 300.0]]]],   [[[[301.0, 302.0, 303.0, 304.0, 305.0]],     [[306.0, 307.0, 308.0, 309.0, 310.0]],     [[311.0, 312.0, 313.0, 314.0, 315.0]]],    [[[316.0, 317.0, 318.0, 319.0, 320.0]],     [[321.0, 322.0, 323.0, 324.0, 325.0]],     [[326.0, 327.0, 328.0, 329.0, 330.0]]],    [[[331.0, 332.0, 333.0, 334.0, 335.0]],     [[336.0, 337.0, 338.0, 339.0, 340.0]],     [[341.0, 342.0, 343.0, 344.0, 345.0]]],    [[[346.0, 347.0, 348.0, 349.0, 350.0]],     [[351.0, 352.0, 353.0, 354.0, 355.0]],     [[356.0, 357.0, 358.0, 359.0, 360.0]]],    [[[361.0, 362.0, 363.0, 364.0, 365.0]],     [[366.0, 367.0, 368.0, 369.0, 370.0]],     [[371.0, 372.0, 373.0, 374.0, 375.0]]]],   [[[[376.0, 377.0, 378.0, 379.0, 380.0]],     [[381.0, 382.0, 383.0, 384.0, 385.0]],     [[386.0, 387.0, 388.0, 389.0, 390.0]]],    [[[391.0, 392.0, 393.0, 394.0, 395.0]],     [[396.0, 397.0, 398.0, 399.0, 400.0]],     [[401.0, 402.0, 403.0, 404.0, 405.0]]],    [[[406.0, 407.0, 408.0, 409.0, 410.0]],     [[411.0, 412.0, 413.0, 414.0, 415.0]],     [[416.0, 417.0, 418.0, 419.0, 420.0]]],    [[[421.0, 422.0, 423.0, 424.0, 425.0]],     [[426.0, 427.0, 428.0, 429.0, 430.0]],     [[431.0, 432.0, 433.0, 434.0, 435.0]]],    [[[436.0, 437.0, 438.0, 439.0, 440.0]],     [[441.0, 442.0, 443.0, 444.0, 445.0]],     [[446.0, 447.0, 448.0, 449.0, 450.0]]]]]] shape=[2, 3, 5, 3, 1, 5], strides=[225, 75, 15, 5, 5, 1], layout=C (0x1)), I32([3, 2, 1] shape=[3], strides=[1], layout=C | F (0x3)), I32([[3, 3],  [2, 1],  [1, 1]] shape=[3, 2], strides=[2, 1], layout=C (0x1)))\nxs 2202615424 2184759976 2294289092 2174890675 # shrinks to (ref i, ref bs, ref p) = (F32([[[[[[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, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0],      [22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0]],     [[29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0],      [36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0],      [43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0],      [50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0]],     [[57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0],      [64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0],      [71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0],      [78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0]]],    [[[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0],      [92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0],      [99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0],      [106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0]],     [[113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0],      [120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0],      [127.0, 128.0, 129.0, 130.0, 131.0, 132.0, 133.0],      [134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0]],     [[141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 147.0],      [148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0],      [155.0, 156.0, 157.0, 158.0, 159.0, 160.0, 161.0],      [162.0, 163.0, 164.0, 165.0, 166.0, 167.0, 168.0]]],    [[[169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0],      [176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0],      [183.0, 184.0, 185.0, 186.0, 187.0, 188.0, 189.0],      [190.0, 191.0, 192.0, 193.0, 194.0, 195.0, 196.0]],     [[197.0, 198.0, 199.0, 200.0, 201.0, 202.0, 203.0],      [204.0, 205.0, 206.0, 207.0, 208.0, 209.0, 210.0],      [211.0, 212.0, 213.0, 214.0, 215.0, 216.0, 217.0],      [218.0, 219.0, 220.0, 221.0, 222.0, 223.0, 224.0]],     [[225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0],      [232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0],      [239.0, 240.0, 241.0, 242.0, 243.0, 244.0, 245.0],      [246.0, 247.0, 248.0, 249.0, 250.0, 251.0, 252.0]]],    [[[253.0, 254.0, 255.0, 256.0, 257.0, 258.0, 259.0],      [260.0, 261.0, 262.0, 263.0, 264.0, 265.0, 266.0],      [267.0, 268.0, 269.0, 270.0, 271.0, 272.0, 273.0],      [274.0, 275.0, 276.0, 277.0, 278.0, 279.0, 280.0]],     [[281.0, 282.0, 283.0, 284.0, 285.0, 286.0, 287.0],      [288.0, 289.0, 290.0, 291.0, 292.0, 293.0, 294.0],      [295.0, 296.0, 297.0, 298.0, 299.0, 300.0, 301.0],      [302.0, 303.0, 304.0, 305.0, 306.0, 307.0, 308.0]],     [[309.0, 310.0, 311.0, 312.0, 313.0, 314.0, 315.0],      [316.0, 317.0, 318.0, 319.0, 320.0, 321.0, 322.0],      [323.0, 324.0, 325.0, 326.0, 327.0, 328.0, 329.0],      [330.0, 331.0, 332.0, 333.0, 334.0, 335.0, 336.0]]],    [[[337.0, 338.0, 339.0, 340.0, 341.0, 342.0, 343.0],      [344.0, 345.0, 346.0, 347.0, 348.0, 349.0, 350.0],      [351.0, 352.0, 353.0, 354.0, 355.0, 356.0, 357.0],      [358.0, 359.0, 360.0, 361.0, 362.0, 363.0, 364.0]],     [[365.0, 366.0, 367.0, 368.0, 369.0, 370.0, 371.0],      [372.0, 373.0, 374.0, 375.0, 376.0, 377.0, 378.0],      [379.0, 380.0, 381.0, 382.0, 383.0, 384.0, 385.0],      [386.0, 387.0, 388.0, 389.0, 390.0, 391.0, 392.0]],     [[393.0, 394.0, 395.0, 396.0, 397.0, 398.0, 399.0],      [400.0, 401.0, 402.0, 403.0, 404.0, 405.0, 406.0],      [407.0, 408.0, 409.0, 410.0, 411.0, 412.0, 413.0],      [414.0, 415.0, 416.0, 417.0, 418.0, 419.0, 420.0]]]],   [[[[421.0, 422.0, 423.0, 424.0, 425.0, 426.0, 427.0],      [428.0, 429.0, 430.0, 431.0, 432.0, 433.0, 434.0],      [435.0, 436.0, 437.0, 438.0, 439.0, 440.0, 441.0],      [442.0, 443.0, 444.0, 445.0, 446.0, 447.0, 448.0]],     [[449.0, 450.0, 451.0, 452.0, 453.0, 454.0, 455.0],      [456.0, 457.0, 458.0, 459.0, 460.0, 461.0, 462.0],      [463.0, 464.0, 465.0, 466.0, 467.0, 468.0, 469.0],      [470.0, 471.0, 472.0, 473.0, 474.0, 475.0, 476.0]],     [[477.0, 478.0, 479.0, 480.0, 481.0, 482.0, 483.0],      [484.0, 485.0, 486.0, 487.0, 488.0, 489.0, 490.0],      [491.0, 492.0, 493.0, 494.0, 495.0, 496.0, 497.0],      [498.0, 499.0, 500.0, 501.0, 502.0, 503.0, 504.0]]],    [[[505.0, 506.0, 507.0, 508.0, 509.0, 510.0, 511.0],      [512.0, 513.0, 514.0, 515.0, 516.0, 517.0, 518.0],      [519.0, 520.0, 521.0, 522.0, 523.0, 524.0, 525.0],      [526.0, 527.0, 528.0, 529.0, 530.0, 531.0, 532.0]],     [[533.0, 534.0, 535.0, 536.0, 537.0, 538.0, 539.0],      [540.0, 541.0, 542.0, 543.0, 544.0, 545.0, 546.0],      [547.0, 548.0, 549.0, 550.0, 551.0, 552.0, 553.0],      [554.0, 555.0, 556.0, 557.0, 558.0, 559.0, 560.0]],     [[561.0, 562.0, 563.0, 564.0, 565.0, 566.0, 567.0],      [568.0, 569.0, 570.0, 571.0, 572.0, 573.0, 574.0],      [575.0, 576.0, 577.0, 578.0, 579.0, 580.0, 581.0],      [582.0, 583.0, 584.0, 585.0, 586.0, 587.0, 588.0]]],    [[[589.0, 590.0, 591.0, 592.0, 593.0, 594.0, 595.0],      [596.0, 597.0, 598.0, 599.0, 600.0, 601.0, 602.0],      [603.0, 604.0, 605.0, 606.0, 607.0, 608.0, 609.0],      [610.0, 611.0, 612.0, 613.0, 614.0, 615.0, 616.0]],     [[617.0, 618.0, 619.0, 620.0, 621.0, 622.0, 623.0],      [624.0, 625.0, 626.0, 627.0, 628.0, 629.0, 630.0],      [631.0, 632.0, 633.0, 634.0, 635.0, 636.0, 637.0],      [638.0, 639.0, 640.0, 641.0, 642.0, 643.0, 644.0]],     [[645.0, 646.0, 647.0, 648.0, 649.0, 650.0, 651.0],      [652.0, 653.0, 654.0, 655.0, 656.0, 657.0, 658.0],      [659.0, 660.0, 661.0, 662.0, 663.0, 664.0, 665.0],      [666.0, 667.0, 668.0, 669.0, 670.0, 671.0, 672.0]]],    [[[673.0, 674.0, 675.0, 676.0, 677.0, 678.0, 679.0],      [680.0, 681.0, 682.0, 683.0, 684.0, 685.0, 686.0],      [687.0, 688.0, 689.0, 690.0, 691.0, 692.0, 693.0],      [694.0, 695.0, 696.0, 697.0, 698.0, 699.0, 700.0]],     [[701.0, 702.0, 703.0, 704.0, 705.0, 706.0, 707.0],      [708.0, 709.0, 710.0, 711.0, 712.0, 713.0, 714.0],      [715.0, 716.0, 717.0, 718.0, 719.0, 720.0, 721.0],      [722.0, 723.0, 724.0, 725.0, 726.0, 727.0, 728.0]],     [[729.0, 730.0, 731.0, 732.0, 733.0, 734.0, 735.0],      [736.0, 737.0, 738.0, 739.0, 740.0, 741.0, 742.0],      [743.0, 744.0, 745.0, 746.0, 747.0, 748.0, 749.0],      [750.0, 751.0, 752.0, 753.0, 754.0, 755.0, 756.0]]],    [[[757.0, 758.0, 759.0, 760.0, 761.0, 762.0, 763.0],      [764.0, 765.0, 766.0, 767.0, 768.0, 769.0, 770.0],      [771.0, 772.0, 773.0, 774.0, 775.0, 776.0, 777.0],      [778.0, 779.0, 780.0, 781.0, 782.0, 783.0, 784.0]],     [[785.0, 786.0, 787.0, 788.0, 789.0, 790.0, 791.0],      [792.0, 793.0, 794.0, 795.0, 796.0, 797.0, 798.0],      [799.0, 800.0, 801.0, 802.0, 803.0, 804.0, 805.0],      [806.0, 807.0, 808.0, 809.0, 810.0, 811.0, 812.0]],     [[813.0, 814.0, 815.0, 816.0, 817.0, 818.0, 819.0],      [820.0, 821.0, 822.0, 823.0, 824.0, 825.0, 826.0],      [827.0, 828.0, 829.0, 830.0, 831.0, 832.0, 833.0],      [834.0, 835.0, 836.0, 837.0, 838.0, 839.0, 840.0]]]],   [[[[841.0, 842.0, 843.0, 844.0, 845.0, 846.0, 847.0],      [848.0, 849.0, 850.0, 851.0, 852.0, 853.0, 854.0],      [855.0, 856.0, 857.0, 858.0, 859.0, 860.0, 861.0],      [862.0, 863.0, 864.0, 865.0, 866.0, 867.0, 868.0]],     [[869.0, 870.0, 871.0, 872.0, 873.0, 874.0, 875.0],      [876.0, 877.0, 878.0, 879.0, 880.0, 881.0, 882.0],      [883.0, 884.0, 885.0, 886.0, 887.0, 888.0, 889.0],      [890.0, 891.0, 892.0, 893.0, 894.0, 895.0, 896.0]],     [[897.0, 898.0, 899.0, 900.0, 901.0, 902.0, 903.0],      [904.0, 905.0, 906.0, 907.0, 908.0, 909.0, 910.0],      [911.0, 912.0, 913.0, 914.0, 915.0, 916.0, 917.0],      [918.0, 919.0, 920.0, 921.0, 922.0, 923.0, 924.0]]],    [[[925.0, 926.0, 927.0, 928.0, 929.0, 930.0, 931.0],      [932.0, 933.0, 934.0, 935.0, 936.0, 937.0, 938.0],      [939.0, 940.0, 941.0, 942.0, 943.0, 944.0, 945.0],      [946.0, 947.0, 948.0, 949.0, 950.0, 951.0, 952.0]],     [[953.0, 954.0, 955.0, 956.0, 957.0, 958.0, 959.0],      [960.0, 961.0, 962.0, 963.0, 964.0, 965.0, 966.0],      [967.0, 968.0, 969.0, 970.0, 971.0, 972.0, 973.0],      [974.0, 975.0, 976.0, 977.0, 978.0, 979.0, 980.0]],     [[981.0, 982.0, 983.0, 984.0, 985.0, 986.0, 987.0],      [988.0, 989.0, 990.0, 991.0, 992.0, 993.0, 994.0],      [995.0, 996.0, 997.0, 998.0, 999.0, 1000.0, 1001.0],      [1002.0, 1003.0, 1004.0, 1005.0, 1006.0, 1007.0, 1008.0]]],    [[[1009.0, 1010.0, 1011.0, 1012.0, 1013.0, 1014.0, 1015.0],      [1016.0, 1017.0, 1018.0, 1019.0, 1020.0, 1021.0, 1022.0],      [1023.0, 1024.0, 1025.0, 1026.0, 1027.0, 1028.0, 1029.0],      [1030.0, 1031.0, 1032.0, 1033.0, 1034.0, 1035.0, 1036.0]],     [[1037.0, 1038.0, 1039.0, 1040.0, 1041.0, 1042.0, 1043.0],      [1044.0, 1045.0, 1046.0, 1047.0, 1048.0, 1049.0, 1050.0],      [1051.0, 1052.0, 1053.0, 1054.0, 1055.0, 1056.0, 1057.0],      [1058.0, 1059.0, 1060.0, 1061.0, 1062.0, 1063.0, 1064.0]],     [[1065.0, 1066.0, 1067.0, 1068.0, 1069.0, 1070.0, 1071.0],      [1072.0, 1073.0, 1074.0, 1075.0, 1076.0, 1077.0, 1078.0],      [1079.0, 1080.0, 1081.0, 1082.0, 1083.0, 1084.0, 1085.0],      [1086.0, 1087.0, 1088.0, 1089.0, 1090.0, 1091.0, 1092.0]]],    [[[1093.0, 1094.0, 1095.0, 1096.0, 1097.0, 1098.0, 1099.0],      [1100.0, 1101.0, 1102.0, 1103.0, 1104.0, 1105.0, 1106.0],      [1107.0, 1108.0, 1109.0, 1110.0, 1111.0, 1112.0, 1113.0],      [1114.0, 1115.0, 1116.0, 1117.0, 1118.0, 1119.0, 1120.0]],     [[1121.0, 1122.0, 1123.0, 1124.0, 1125.0, 1126.0, 1127.0],      [1128.0, 1129.0, 1130.0, 1131.0, 1132.0, 1133.0, 1134.0],      [1135.0, 1136.0, 1137.0, 1138.0, 1139.0, 1140.0, 1141.0],      [1142.0, 1143.0, 1144.0, 1145.0, 1146.0, 1147.0, 1148.0]],     [[1149.0, 1150.0, 1151.0, 1152.0, 1153.0, 1154.0, 1155.0],      [1156.0, 1157.0, 1158.0, 1159.0, 1160.0, 1161.0, 1162.0],      [1163.0, 1164.0, 1165.0, 1166.0, 1167.0, 1168.0, 1169.0],      [1170.0, 1171.0, 1172.0, 1173.0, 1174.0, 1175.0, 1176.0]]],    [[[1177.0, 1178.0, 1179.0, 1180.0, 1181.0, 1182.0, 1183.0],      [1184.0, 1185.0, 1186.0, 1187.0, 1188.0, 1189.0, 1190.0],      [1191.0, 1192.0, 1193.0, 1194.0, 1195.0, 1196.0, 1197.0],      [1198.0, 1199.0, 1200.0, 1201.0, 1202.0, 1203.0, 1204.0]],     [[1205.0, 1206.0, 1207.0, 1208.0, 1209.0, 1210.0, 1211.0],      [1212.0, 1213.0, 1214.0, 1215.0, 1216.0, 1217.0, 1218.0],      [1219.0, 1220.0, 1221.0, 1222.0, 1223.0, 1224.0, 1225.0],      [1226.0, 1227.0, 1228.0, 1229.0, 1230.0, 1231.0, 1232.0]],     [[1233.0, 1234.0, 1235.0, 1236.0, 1237.0, 1238.0, 1239.0],      [1240.0, 1241.0, 1242.0, 1243.0, 1244.0, 1245.0, 1246.0],      [1247.0, 1248.0, 1249.0, 1250.0, 1251.0, 1252.0, 1253.0],      [1254.0, 1255.0, 1256.0, 1257.0, 1258.0, 1259.0, 1260.0]]]],   [[[[1261.0, 1262.0, 1263.0, 1264.0, 1265.0, 1266.0, 1267.0],      [1268.0, 1269.0, 1270.0, 1271.0, 1272.0, 1273.0, 1274.0],      [1275.0, 1276.0, 1277.0, 1278.0, 1279.0, 1280.0, 1281.0],      [1282.0, 1283.0, 1284.0, 1285.0, 1286.0, 1287.0, 1288.0]],     [[1289.0, 1290.0, 1291.0, 1292.0, 1293.0, 1294.0, 1295.0],      [1296.0, 1297.0, 1298.0, 1299.0, 1300.0, 1301.0, 1302.0],      [1303.0, 1304.0, 1305.0, 1306.0, 1307.0, 1308.0, 1309.0],      [1310.0, 1311.0, 1312.0, 1313.0, 1314.0, 1315.0, 1316.0]],     [[1317.0, 1318.0, 1319.0, 1320.0, 1321.0, 1322.0, 1323.0],      [1324.0, 1325.0, 1326.0, 1327.0, 1328.0, 1329.0, 1330.0],      [1331.0, 1332.0, 1333.0, 1334.0, 1335.0, 1336.0, 1337.0],      [1338.0, 1339.0, 1340.0, 1341.0, 1342.0, 1343.0, 1344.0]]],    [[[1345.0, 1346.0, 1347.0, 1348.0, 1349.0, 1350.0, 1351.0],      [1352.0, 1353.0, 1354.0, 1355.0, 1356.0, 1357.0, 1358.0],      [1359.0, 1360.0, 1361.0, 1362.0, 1363.0, 1364.0, 1365.0],      [1366.0, 1367.0, 1368.0, 1369.0, 1370.0, 1371.0, 1372.0]],     [[1373.0, 1374.0, 1375.0, 1376.0, 1377.0, 1378.0, 1379.0],      [1380.0, 1381.0, 1382.0, 1383.0, 1384.0, 1385.0, 1386.0],      [1387.0, 1388.0, 1389.0, 1390.0, 1391.0, 1392.0, 1393.0],      [1394.0, 1395.0, 1396.0, 1397.0, 1398.0, 1399.0, 1400.0]],     [[1401.0, 1402.0, 1403.0, 1404.0, 1405.0, 1406.0, 1407.0],      [1408.0, 1409.0, 1410.0, 1411.0, 1412.0, 1413.0, 1414.0],      [1415.0, 1416.0, 1417.0, 1418.0, 1419.0, 1420.0, 1421.0],      [1422.0, 1423.0, 1424.0, 1425.0, 1426.0, 1427.0, 1428.0]]],    [[[1429.0, 1430.0, 1431.0, 1432.0, 1433.0, 1434.0, 1435.0],      [1436.0, 1437.0, 1438.0, 1439.0, 1440.0, 1441.0, 1442.0],      [1443.0, 1444.0, 1445.0, 1446.0, 1447.0, 1448.0, 1449.0],      [1450.0, 1451.0, 1452.0, 1453.0, 1454.0, 1455.0, 1456.0]],     [[1457.0, 1458.0, 1459.0, 1460.0, 1461.0, 1462.0, 1463.0],      [1464.0, 1465.0, 1466.0, 1467.0, 1468.0, 1469.0, 1470.0],      [1471.0, 1472.0, 1473.0, 1474.0, 1475.0, 1476.0, 1477.0],      [1478.0, 1479.0, 1480.0, 1481.0, 1482.0, 1483.0, 1484.0]],     [[1485.0, 1486.0, 1487.0, 1488.0, 1489.0, 1490.0, 1491.0],      [1492.0, 1493.0, 1494.0, 1495.0, 1496.0, 1497.0, 1498.0],      [1499.0, 1500.0, 1501.0, 1502.0, 1503.0, 1504.0, 1505.0],      [1506.0, 1507.0, 1508.0, 1509.0, 1510.0, 1511.0, 1512.0]]],    [[[1513.0, 1514.0, 1515.0, 1516.0, 1517.0, 1518.0, 1519.0],      [1520.0, 1521.0, 1522.0, 1523.0, 1524.0, 1525.0, 1526.0],      [1527.0, 1528.0, 1529.0, 1530.0, 1531.0, 1532.0, 1533.0],      [1534.0, 1535.0, 1536.0, 1537.0, 1538.0, 1539.0, 1540.0]],     [[1541.0, 1542.0, 1543.0, 1544.0, 1545.0, 1546.0, 1547.0],      [1548.0, 1549.0, 1550.0, 1551.0, 1552.0, 1553.0, 1554.0],      [1555.0, 1556.0, 1557.0, 1558.0, 1559.0, 1560.0, 1561.0],      [1562.0, 1563.0, 1564.0, 1565.0, 1566.0, 1567.0, 1568.0]],     [[1569.0, 1570.0, 1571.0, 1572.0, 1573.0, 1574.0, 1575.0],      [1576.0, 1577.0, 1578.0, 1579.0, 1580.0, 1581.0, 1582.0],      [1583.0, 1584.0, 1585.0, 1586.0, 1587.0, 1588.0, 1589.0],      [1590.0, 1591.0, 1592.0, 1593.0, 1594.0, 1595.0, 1596.0]]],    [[[1597.0, 1598.0, 1599.0, 1600.0, 1601.0, 1602.0, 1603.0],      [1604.0, 1605.0, 1606.0, 1607.0, 1608.0, 1609.0, 1610.0],      [1611.0, 1612.0, 1613.0, 1614.0, 1615.0, 1616.0, 1617.0],      [1618.0, 1619.0, 1620.0, 1621.0, 1622.0, 1623.0, 1624.0]],     [[1625.0, 1626.0, 1627.0, 1628.0, 1629.0, 1630.0, 1631.0],      [1632.0, 1633.0, 1634.0, 1635.0, 1636.0, 1637.0, 1638.0],      [1639.0, 1640.0, 1641.0, 1642.0, 1643.0, 1644.0, 1645.0],      [1646.0, 1647.0, 1648.0, 1649.0, 1650.0, 1651.0, 1652.0]],     [[1653.0, 1654.0, 1655.0, 1656.0, 1657.0, 1658.0, 1659.0],      [1660.0, 1661.0, 1662.0, 1663.0, 1664.0, 1665.0, 1666.0],      [1667.0, 1668.0, 1669.0, 1670.0, 1671.0, 1672.0, 1673.0],      [1674.0, 1675.0, 1676.0, 1677.0, 1678.0, 1679.0, 1680.0]]]],   [[[[1681.0, 1682.0, 1683.0, 1684.0, 1685.0, 1686.0, 1687.0],      [1688.0, 1689.0, 1690.0, 1691.0, 1692.0, 1693.0, 1694.0],      [1695.0, 1696.0, 1697.0, 1698.0, 1699.0, 1700.0, 1701.0],      [1702.0, 1703.0, 1704.0, 1705.0, 1706.0, 1707.0, 1708.0]],     [[1709.0, 1710.0, 1711.0, 1712.0, 1713.0, 1714.0, 1715.0],      [1716.0, 1717.0, 1718.0, 1719.0, 1720.0, 1721.0, 1722.0],      [1723.0, 1724.0, 1725.0, 1726.0, 1727.0, 1728.0, 1729.0],      [1730.0, 1731.0, 1732.0, 1733.0, 1734.0, 1735.0, 1736.0]],     [[1737.0, 1738.0, 1739.0, 1740.0, 1741.0, 1742.0, 1743.0],      [1744.0, 1745.0, 1746.0, 1747.0, 1748.0, 1749.0, 1750.0],      [1751.0, 1752.0, 1753.0, 1754.0, 1755.0, 1756.0, 1757.0],      [1758.0, 1759.0, 1760.0, 1761.0, 1762.0, 1763.0, 1764.0]]],    [[[1765.0, 1766.0, 1767.0, 1768.0, 1769.0, 1770.0, 1771.0],      [1772.0, 1773.0, 1774.0, 1775.0, 1776.0, 1777.0, 1778.0],      [1779.0, 1780.0, 1781.0, 1782.0, 1783.0, 1784.0, 1785.0],      [1786.0, 1787.0, 1788.0, 1789.0, 1790.0, 1791.0, 1792.0]],     [[1793.0, 1794.0, 1795.0, 1796.0, 1797.0, 1798.0, 1799.0],      [1800.0, 1801.0, 1802.0, 1803.0, 1804.0, 1805.0, 1806.0],      [1807.0, 1808.0, 1809.0, 1810.0, 1811.0, 1812.0, 1813.0],      [1814.0, 1815.0, 1816.0, 1817.0, 1818.0, 1819.0, 1820.0]],     [[1821.0, 1822.0, 1823.0, 1824.0, 1825.0, 1826.0, 1827.0],      [1828.0, 1829.0, 1830.0, 1831.0, 1832.0, 1833.0, 1834.0],      [1835.0, 1836.0, 1837.0, 1838.0, 1839.0, 1840.0, 1841.0],      [1842.0, 1843.0, 1844.0, 1845.0, 1846.0, 1847.0, 1848.0]]],    [[[1849.0, 1850.0, 1851.0, 1852.0, 1853.0, 1854.0, 1855.0],      [1856.0, 1857.0, 1858.0, 1859.0, 1860.0, 1861.0, 1862.0],      [1863.0, 1864.0, 1865.0, 1866.0, 1867.0, 1868.0, 1869.0],      [1870.0, 1871.0, 1872.0, 1873.0, 1874.0, 1875.0, 1876.0]],     [[1877.0, 1878.0, 1879.0, 1880.0, 1881.0, 1882.0, 1883.0],      [1884.0, 1885.0, 1886.0, 1887.0, 1888.0, 1889.0, 1890.0],      [1891.0, 1892.0, 1893.0, 1894.0, 1895.0, 1896.0, 1897.0],      [1898.0, 1899.0, 1900.0, 1901.0, 1902.0, 1903.0, 1904.0]],     [[1905.0, 1906.0, 1907.0, 1908.0, 1909.0, 1910.0, 1911.0],      [1912.0, 1913.0, 1914.0, 1915.0, 1916.0, 1917.0, 1918.0],      [1919.0, 1920.0, 1921.0, 1922.0, 1923.0, 1924.0, 1925.0],      [1926.0, 1927.0, 1928.0, 1929.0, 1930.0, 1931.0, 1932.0]]],    [[[1933.0, 1934.0, 1935.0, 1936.0, 1937.0, 1938.0, 1939.0],      [1940.0, 1941.0, 1942.0, 1943.0, 1944.0, 1945.0, 1946.0],      [1947.0, 1948.0, 1949.0, 1950.0, 1951.0, 1952.0, 1953.0],      [1954.0, 1955.0, 1956.0, 1957.0, 1958.0, 1959.0, 1960.0]],     [[1961.0, 1962.0, 1963.0, 1964.0, 1965.0, 1966.0, 1967.0],      [1968.0, 1969.0, 1970.0, 1971.0, 1972.0, 1973.0, 1974.0],      [1975.0, 1976.0, 1977.0, 1978.0, 1979.0, 1980.0, 1981.0],      [1982.0, 1983.0, 1984.0, 1985.0, 1986.0, 1987.0, 1988.0]],     [[1989.0, 1990.0, 1991.0, 1992.0, 1993.0, 1994.0, 1995.0],      [1996.0, 1997.0, 1998.0, 1999.0, 2000.0, 2001.0, 2002.0],      [2003.0, 2004.0, 2005.0, 2006.0, 2007.0, 2008.0, 2009.0],      [2010.0, 2011.0, 2012.0, 2013.0, 2014.0, 2015.0, 2016.0]]],    [[[2017.0, 2018.0, 2019.0, 2020.0, 2021.0, 2022.0, 2023.0],      [2024.0, 2025.0, 2026.0, 2027.0, 2028.0, 2029.0, 2030.0],      [2031.0, 2032.0, 2033.0, 2034.0, 2035.0, 2036.0, 2037.0],      [2038.0, 2039.0, 2040.0, 2041.0, 2042.0, 2043.0, 2044.0]],     [[2045.0, 2046.0, 2047.0, 2048.0, 2049.0, 2050.0, 2051.0],      [2052.0, 2053.0, 2054.0, 2055.0, 2056.0, 2057.0, 2058.0],      [2059.0, 2060.0, 2061.0, 2062.0, 2063.0, 2064.0, 2065.0],      [2066.0, 2067.0, 2068.0, 2069.0, 2070.0, 2071.0, 2072.0]],     [[2073.0, 2074.0, 2075.0, 2076.0, 2077.0, 2078.0, 2079.0],      [2080.0, 2081.0, 2082.0, 2083.0, 2084.0, 2085.0, 2086.0],      [2087.0, 2088.0, 2089.0, 2090.0, 2091.0, 2092.0, 2093.0],      [2094.0, 2095.0, 2096.0, 2097.0, 2098.0, 2099.0, 2100.0]]]]],  [[[[[2101.0, 2102.0, 2103.0, 2104.0, 2105.0, 2106.0, 2107.0],      [2108.0, 2109.0, 2110.0, 2111.0, 2112.0, 2113.0, 2114.0],      [2115.0, 2116.0, 2117.0, 2118.0, 2119.0, 2120.0, 2121.0],      [2122.0, 2123.0, 2124.0, 2125.0, 2126.0, 2127.0, 2128.0]],     [[2129.0, 2130.0, 2131.0, 2132.0, 2133.0, 2134.0, 2135.0],      [2136.0, 2137.0, 2138.0, 2139.0, 2140.0, 2141.0, 2142.0],      [2143.0, 2144.0, 2145.0, 2146.0, 2147.0, 2148.0, 2149.0],      [2150.0, 2151.0, 2152.0, 2153.0, 2154.0, 2155.0, 2156.0]],     [[2157.0, 2158.0, 2159.0, 2160.0, 2161.0, 2162.0, 2163.0],      [2164.0, 2165.0, 2166.0, 2167.0, 2168.0, 2169.0, 2170.0],      [2171.0, 2172.0, 2173.0, 2174.0, 2175.0, 2176.0, 2177.0],      [2178.0, 2179.0, 2180.0, 2181.0, 2182.0, 2183.0, 2184.0]]],    [[[2185.0, 2186.0, 2187.0, 2188.0, 2189.0, 2190.0, 2191.0],      [2192.0, 2193.0, 2194.0, 2195.0, 2196.0, 2197.0, 2198.0],      [2199.0, 2200.0, 2201.0, 2202.0, 2203.0, 2204.0, 2205.0],      [2206.0, 2207.0, 2208.0, 2209.0, 2210.0, 2211.0, 2212.0]],     [[2213.0, 2214.0, 2215.0, 2216.0, 2217.0, 2218.0, 2219.0],      [2220.0, 2221.0, 2222.0, 2223.0, 2224.0, 2225.0, 2226.0],      [2227.0, 2228.0, 2229.0, 2230.0, 2231.0, 2232.0, 2233.0],      [2234.0, 2235.0, 2236.0, 2237.0, 2238.0, 2239.0, 2240.0]],     [[2241.0, 2242.0, 2243.0, 2244.0, 2245.0, 2246.0, 2247.0],      [2248.0, 2249.0, 2250.0, 2251.0, 2252.0, 2253.0, 2254.0],      [2255.0, 2256.0, 2257.0, 2258.0, 2259.0, 2260.0, 2261.0],      [2262.0, 2263.0, 2264.0, 2265.0, 2266.0, 2267.0, 2268.0]]],    [[[2269.0, 2270.0, 2271.0, 2272.0, 2273.0, 2274.0, 2275.0],      [2276.0, 2277.0, 2278.0, 2279.0, 2280.0, 2281.0, 2282.0],      [2283.0, 2284.0, 2285.0, 2286.0, 2287.0, 2288.0, 2289.0],      [2290.0, 2291.0, 2292.0, 2293.0, 2294.0, 2295.0, 2296.0]],     [[2297.0, 2298.0, 2299.0, 2300.0, 2301.0, 2302.0, 2303.0],      [2304.0, 2305.0, 2306.0, 2307.0, 2308.0, 2309.0, 2310.0],      [2311.0, 2312.0, 2313.0, 2314.0, 2315.0, 2316.0, 2317.0],      [2318.0, 2319.0, 2320.0, 2321.0, 2322.0, 2323.0, 2324.0]],     [[2325.0, 2326.0, 2327.0, 2328.0, 2329.0, 2330.0, 2331.0],      [2332.0, 2333.0, 2334.0, 2335.0, 2336.0, 2337.0, 2338.0],      [2339.0, 2340.0, 2341.0, 2342.0, 2343.0, 2344.0, 2345.0],      [2346.0, 2347.0, 2348.0, 2349.0, 2350.0, 2351.0, 2352.0]]],    [[[2353.0, 2354.0, 2355.0, 2356.0, 2357.0, 2358.0, 2359.0],      [2360.0, 2361.0, 2362.0, 2363.0, 2364.0, 2365.0, 2366.0],      [2367.0, 2368.0, 2369.0, 2370.0, 2371.0, 2372.0, 2373.0],      [2374.0, 2375.0, 2376.0, 2377.0, 2378.0, 2379.0, 2380.0]],     [[2381.0, 2382.0, 2383.0, 2384.0, 2385.0, 2386.0, 2387.0],      [2388.0, 2389.0, 2390.0, 2391.0, 2392.0, 2393.0, 2394.0],      [2395.0, 2396.0, 2397.0, 2398.0, 2399.0, 2400.0, 2401.0],      [2402.0, 2403.0, 2404.0, 2405.0, 2406.0, 2407.0, 2408.0]],     [[2409.0, 2410.0, 2411.0, 2412.0, 2413.0, 2414.0, 2415.0],      [2416.0, 2417.0, 2418.0, 2419.0, 2420.0, 2421.0, 2422.0],      [2423.0, 2424.0, 2425.0, 2426.0, 2427.0, 2428.0, 2429.0],      [2430.0, 2431.0, 2432.0, 2433.0, 2434.0, 2435.0, 2436.0]]],    [[[2437.0, 2438.0, 2439.0, 2440.0, 2441.0, 2442.0, 2443.0],      [2444.0, 2445.0, 2446.0, 2447.0, 2448.0, 2449.0, 2450.0],      [2451.0, 2452.0, 2453.0, 2454.0, 2455.0, 2456.0, 2457.0],      [2458.0, 2459.0, 2460.0, 2461.0, 2462.0, 2463.0, 2464.0]],     [[2465.0, 2466.0, 2467.0, 2468.0, 2469.0, 2470.0, 2471.0],      [2472.0, 2473.0, 2474.0, 2475.0, 2476.0, 2477.0, 2478.0],      [2479.0, 2480.0, 2481.0, 2482.0, 2483.0, 2484.0, 2485.0],      [2486.0, 2487.0, 2488.0, 2489.0, 2490.0, 2491.0, 2492.0]],     [[2493.0, 2494.0, 2495.0, 2496.0, 2497.0, 2498.0, 2499.0],      [2500.0, 2501.0, 2502.0, 2503.0, 2504.0, 2505.0, 2506.0],      [2507.0, 2508.0, 2509.0, 2510.0, 2511.0, 2512.0, 2513.0],      [2514.0, 2515.0, 2516.0, 2517.0, 2518.0, 2519.0, 2520.0]]]],   [[[[2521.0, 2522.0, 2523.0, 2524.0, 2525.0, 2526.0, 2527.0],      [2528.0, 2529.0, 2530.0, 2531.0, 2532.0, 2533.0, 2534.0],      [2535.0, 2536.0, 2537.0, 2538.0, 2539.0, 2540.0, 2541.0],      [2542.0, 2543.0, 2544.0, 2545.0, 2546.0, 2547.0, 2548.0]],     [[2549.0, 2550.0, 2551.0, 2552.0, 2553.0, 2554.0, 2555.0],      [2556.0, 2557.0, 2558.0, 2559.0, 2560.0, 2561.0, 2562.0],      [2563.0, 2564.0, 2565.0, 2566.0, 2567.0, 2568.0, 2569.0],      [2570.0, 2571.0, 2572.0, 2573.0, 2574.0, 2575.0, 2576.0]],     [[2577.0, 2578.0, 2579.0, 2580.0, 2581.0, 2582.0, 2583.0],      [2584.0, 2585.0, 2586.0, 2587.0, 2588.0, 2589.0, 2590.0],      [2591.0, 2592.0, 2593.0, 2594.0, 2595.0, 2596.0, 2597.0],      [2598.0, 2599.0, 2600.0, 2601.0, 2602.0, 2603.0, 2604.0]]],    [[[2605.0, 2606.0, 2607.0, 2608.0, 2609.0, 2610.0, 2611.0],      [2612.0, 2613.0, 2614.0, 2615.0, 2616.0, 2617.0, 2618.0],      [2619.0, 2620.0, 2621.0, 2622.0, 2623.0, 2624.0, 2625.0],      [2626.0, 2627.0, 2628.0, 2629.0, 2630.0, 2631.0, 2632.0]],     [[2633.0, 2634.0, 2635.0, 2636.0, 2637.0, 2638.0, 2639.0],      [2640.0, 2641.0, 2642.0, 2643.0, 2644.0, 2645.0, 2646.0],      [2647.0, 2648.0, 2649.0, 2650.0, 2651.0, 2652.0, 2653.0],      [2654.0, 2655.0, 2656.0, 2657.0, 2658.0, 2659.0, 2660.0]],     [[2661.0, 2662.0, 2663.0, 2664.0, 2665.0, 2666.0, 2667.0],      [2668.0, 2669.0, 2670.0, 2671.0, 2672.0, 2673.0, 2674.0],      [2675.0, 2676.0, 2677.0, 2678.0, 2679.0, 2680.0, 2681.0],      [2682.0, 2683.0, 2684.0, 2685.0, 2686.0, 2687.0, 2688.0]]],    [[[2689.0, 2690.0, 2691.0, 2692.0, 2693.0, 2694.0, 2695.0],      [2696.0, 2697.0, 2698.0, 2699.0, 2700.0, 2701.0, 2702.0],      [2703.0, 2704.0, 2705.0, 2706.0, 2707.0, 2708.0, 2709.0],      [2710.0, 2711.0, 2712.0, 2713.0, 2714.0, 2715.0, 2716.0]],     [[2717.0, 2718.0, 2719.0, 2720.0, 2721.0, 2722.0, 2723.0],      [2724.0, 2725.0, 2726.0, 2727.0, 2728.0, 2729.0, 2730.0],      [2731.0, 2732.0, 2733.0, 2734.0, 2735.0, 2736.0, 2737.0],      [2738.0, 2739.0, 2740.0, 2741.0, 2742.0, 2743.0, 2744.0]],     [[2745.0, 2746.0, 2747.0, 2748.0, 2749.0, 2750.0, 2751.0],      [2752.0, 2753.0, 2754.0, 2755.0, 2756.0, 2757.0, 2758.0],      [2759.0, 2760.0, 2761.0, 2762.0, 2763.0, 2764.0, 2765.0],      [2766.0, 2767.0, 2768.0, 2769.0, 2770.0, 2771.0, 2772.0]]],    [[[2773.0, 2774.0, 2775.0, 2776.0, 2777.0, 2778.0, 2779.0],      [2780.0, 2781.0, 2782.0, 2783.0, 2784.0, 2785.0, 2786.0],      [2787.0, 2788.0, 2789.0, 2790.0, 2791.0, 2792.0, 2793.0],      [2794.0, 2795.0, 2796.0, 2797.0, 2798.0, 2799.0, 2800.0]],     [[2801.0, 2802.0, 2803.0, 2804.0, 2805.0, 2806.0, 2807.0],      [2808.0, 2809.0, 2810.0, 2811.0, 2812.0, 2813.0, 2814.0],      [2815.0, 2816.0, 2817.0, 2818.0, 2819.0, 2820.0, 2821.0],      [2822.0, 2823.0, 2824.0, 2825.0, 2826.0, 2827.0, 2828.0]],     [[2829.0, 2830.0, 2831.0, 2832.0, 2833.0, 2834.0, 2835.0],      [2836.0, 2837.0, 2838.0, 2839.0, 2840.0, 2841.0, 2842.0],      [2843.0, 2844.0, 2845.0, 2846.0, 2847.0, 2848.0, 2849.0],      [2850.0, 2851.0, 2852.0, 2853.0, 2854.0, 2855.0, 2856.0]]],    [[[2857.0, 2858.0, 2859.0, 2860.0, 2861.0, 2862.0, 2863.0],      [2864.0, 2865.0, 2866.0, 2867.0, 2868.0, 2869.0, 2870.0],      [2871.0, 2872.0, 2873.0, 2874.0, 2875.0, 2876.0, 2877.0],      [2878.0, 2879.0, 2880.0, 2881.0, 2882.0, 2883.0, 2884.0]],     [[2885.0, 2886.0, 2887.0, 2888.0, 2889.0, 2890.0, 2891.0],      [2892.0, 2893.0, 2894.0, 2895.0, 2896.0, 2897.0, 2898.0],      [2899.0, 2900.0, 2901.0, 2902.0, 2903.0, 2904.0, 2905.0],      [2906.0, 2907.0, 2908.0, 2909.0, 2910.0, 2911.0, 2912.0]],     [[2913.0, 2914.0, 2915.0, 2916.0, 2917.0, 2918.0, 2919.0],      [2920.0, 2921.0, 2922.0, 2923.0, 2924.0, 2925.0, 2926.0],      [2927.0, 2928.0, 2929.0, 2930.0, 2931.0, 2932.0, 2933.0],      [2934.0, 2935.0, 2936.0, 2937.0, 2938.0, 2939.0, 2940.0]]]],   [[[[2941.0, 2942.0, 2943.0, 2944.0, 2945.0, 2946.0, 2947.0],      [2948.0, 2949.0, 2950.0, 2951.0, 2952.0, 2953.0, 2954.0],      [2955.0, 2956.0, 2957.0, 2958.0, 2959.0, 2960.0, 2961.0],      [2962.0, 2963.0, 2964.0, 2965.0, 2966.0, 2967.0, 2968.0]],     [[2969.0, 2970.0, 2971.0, 2972.0, 2973.0, 2974.0, 2975.0],      [2976.0, 2977.0, 2978.0, 2979.0, 2980.0, 2981.0, 2982.0],      [2983.0, 2984.0, 2985.0, 2986.0, 2987.0, 2988.0, 2989.0],      [2990.0, 2991.0, 2992.0, 2993.0, 2994.0, 2995.0, 2996.0]],     [[2997.0, 2998.0, 2999.0, 3000.0, 3001.0, 3002.0, 3003.0],      [3004.0, 3005.0, 3006.0, 3007.0, 3008.0, 3009.0, 3010.0],      [3011.0, 3012.0, 3013.0, 3014.0, 3015.0, 3016.0, 3017.0],      [3018.0, 3019.0, 3020.0, 3021.0, 3022.0, 3023.0, 3024.0]]],    [[[3025.0, 3026.0, 3027.0, 3028.0, 3029.0, 3030.0, 3031.0],      [3032.0, 3033.0, 3034.0, 3035.0, 3036.0, 3037.0, 3038.0],      [3039.0, 3040.0, 3041.0, 3042.0, 3043.0, 3044.0, 3045.0],      [3046.0, 3047.0, 3048.0, 3049.0, 3050.0, 3051.0, 3052.0]],     [[3053.0, 3054.0, 3055.0, 3056.0, 3057.0, 3058.0, 3059.0],      [3060.0, 3061.0, 3062.0, 3063.0, 3064.0, 3065.0, 3066.0],      [3067.0, 3068.0, 3069.0, 3070.0, 3071.0, 3072.0, 3073.0],      [3074.0, 3075.0, 3076.0, 3077.0, 3078.0, 3079.0, 3080.0]],     [[3081.0, 3082.0, 3083.0, 3084.0, 3085.0, 3086.0, 3087.0],      [3088.0, 3089.0, 3090.0, 3091.0, 3092.0, 3093.0, 3094.0],      [3095.0, 3096.0, 3097.0, 3098.0, 3099.0, 3100.0, 3101.0],      [3102.0, 3103.0, 3104.0, 3105.0, 3106.0, 3107.0, 3108.0]]],    [[[3109.0, 3110.0, 3111.0, 3112.0, 3113.0, 3114.0, 3115.0],      [3116.0, 3117.0, 3118.0, 3119.0, 3120.0, 3121.0, 3122.0],      [3123.0, 3124.0, 3125.0, 3126.0, 3127.0, 3128.0, 3129.0],      [3130.0, 3131.0, 3132.0, 3133.0, 3134.0, 3135.0, 3136.0]],     [[3137.0, 3138.0, 3139.0, 3140.0, 3141.0, 3142.0, 3143.0],      [3144.0, 3145.0, 3146.0, 3147.0, 3148.0, 3149.0, 3150.0],      [3151.0, 3152.0, 3153.0, 3154.0, 3155.0, 3156.0, 3157.0],      [3158.0, 3159.0, 3160.0, 3161.0, 3162.0, 3163.0, 3164.0]],     [[3165.0, 3166.0, 3167.0, 3168.0, 3169.0, 3170.0, 3171.0],      [3172.0, 3173.0, 3174.0, 3175.0, 3176.0, 3177.0, 3178.0],      [3179.0, 3180.0, 3181.0, 3182.0, 3183.0, 3184.0, 3185.0],      [3186.0, 3187.0, 3188.0, 3189.0, 3190.0, 3191.0, 3192.0]]],    [[[3193.0, 3194.0, 3195.0, 3196.0, 3197.0, 3198.0, 3199.0],      [3200.0, 3201.0, 3202.0, 3203.0, 3204.0, 3205.0, 3206.0],      [3207.0, 3208.0, 3209.0, 3210.0, 3211.0, 3212.0, 3213.0],      [3214.0, 3215.0, 3216.0, 3217.0, 3218.0, 3219.0, 3220.0]],     [[3221.0, 3222.0, 3223.0, 3224.0, 3225.0, 3226.0, 3227.0],      [3228.0, 3229.0, 3230.0, 3231.0, 3232.0, 3233.0, 3234.0],      [3235.0, 3236.0, 3237.0, 3238.0, 3239.0, 3240.0, 3241.0],      [3242.0, 3243.0, 3244.0, 3245.0, 3246.0, 3247.0, 3248.0]],     [[3249.0, 3250.0, 3251.0, 3252.0, 3253.0, 3254.0, 3255.0],      [3256.0, 3257.0, 3258.0, 3259.0, 3260.0, 3261.0, 3262.0],      [3263.0, 3264.0, 3265.0, 3266.0, 3267.0, 3268.0, 3269.0],      [3270.0, 3271.0, 3272.0, 3273.0, 3274.0, 3275.0, 3276.0]]],    [[[3277.0, 3278.0, 3279.0, 3280.0, 3281.0, 3282.0, 3283.0],      [3284.0, 3285.0, 3286.0, 3287.0, 3288.0, 3289.0, 3290.0],      [3291.0, 3292.0, 3293.0, 3294.0, 3295.0, 3296.0, 3297.0],      [3298.0, 3299.0, 3300.0, 3301.0, 3302.0, 3303.0, 3304.0]],     [[3305.0, 3306.0, 3307.0, 3308.0, 3309.0, 3310.0, 3311.0],      [3312.0, 3313.0, 3314.0, 3315.0, 3316.0, 3317.0, 3318.0],      [3319.0, 3320.0, 3321.0, 3322.0, 3323.0, 3324.0, 3325.0],      [3326.0, 3327.0, 3328.0, 3329.0, 3330.0, 3331.0, 3332.0]],     [[3333.0, 3334.0, 3335.0, 3336.0, 3337.0, 3338.0, 3339.0],      [3340.0, 3341.0, 3342.0, 3343.0, 3344.0, 3345.0, 3346.0],      [3347.0, 3348.0, 3349.0, 3350.0, 3351.0, 3352.0, 3353.0],      [3354.0, 3355.0, 3356.0, 3357.0, 3358.0, 3359.0, 3360.0]]]],   [[[[3361.0, 3362.0, 3363.0, 3364.0, 3365.0, 3366.0, 3367.0],      [3368.0, 3369.0, 3370.0, 3371.0, 3372.0, 3373.0, 3374.0],      [3375.0, 3376.0, 3377.0, 3378.0, 3379.0, 3380.0, 3381.0],      [3382.0, 3383.0, 3384.0, 3385.0, 3386.0, 3387.0, 3388.0]],     [[3389.0, 3390.0, 3391.0, 3392.0, 3393.0, 3394.0, 3395.0],      [3396.0, 3397.0, 3398.0, 3399.0, 3400.0, 3401.0, 3402.0],      [3403.0, 3404.0, 3405.0, 3406.0, 3407.0, 3408.0, 3409.0],      [3410.0, 3411.0, 3412.0, 3413.0, 3414.0, 3415.0, 3416.0]],     [[3417.0, 3418.0, 3419.0, 3420.0, 3421.0, 3422.0, 3423.0],      [3424.0, 3425.0, 3426.0, 3427.0, 3428.0, 3429.0, 3430.0],      [3431.0, 3432.0, 3433.0, 3434.0, 3435.0, 3436.0, 3437.0],      [3438.0, 3439.0, 3440.0, 3441.0, 3442.0, 3443.0, 3444.0]]],    [[[3445.0, 3446.0, 3447.0, 3448.0, 3449.0, 3450.0, 3451.0],      [3452.0, 3453.0, 3454.0, 3455.0, 3456.0, 3457.0, 3458.0],      [3459.0, 3460.0, 3461.0, 3462.0, 3463.0, 3464.0, 3465.0],      [3466.0, 3467.0, 3468.0, 3469.0, 3470.0, 3471.0, 3472.0]],     [[3473.0, 3474.0, 3475.0, 3476.0, 3477.0, 3478.0, 3479.0],      [3480.0, 3481.0, 3482.0, 3483.0, 3484.0, 3485.0, 3486.0],      [3487.0, 3488.0, 3489.0, 3490.0, 3491.0, 3492.0, 3493.0],      [3494.0, 3495.0, 3496.0, 3497.0, 3498.0, 3499.0, 3500.0]],     [[3501.0, 3502.0, 3503.0, 3504.0, 3505.0, 3506.0, 3507.0],      [3508.0, 3509.0, 3510.0, 3511.0, 3512.0, 3513.0, 3514.0],      [3515.0, 3516.0, 3517.0, 3518.0, 3519.0, 3520.0, 3521.0],      [3522.0, 3523.0, 3524.0, 3525.0, 3526.0, 3527.0, 3528.0]]],    [[[3529.0, 3530.0, 3531.0, 3532.0, 3533.0, 3534.0, 3535.0],      [3536.0, 3537.0, 3538.0, 3539.0, 3540.0, 3541.0, 3542.0],      [3543.0, 3544.0, 3545.0, 3546.0, 3547.0, 3548.0, 3549.0],      [3550.0, 3551.0, 3552.0, 3553.0, 3554.0, 3555.0, 3556.0]],     [[3557.0, 3558.0, 3559.0, 3560.0, 3561.0, 3562.0, 3563.0],      [3564.0, 3565.0, 3566.0, 3567.0, 3568.0, 3569.0, 3570.0],      [3571.0, 3572.0, 3573.0, 3574.0, 3575.0, 3576.0, 3577.0],      [3578.0, 3579.0, 3580.0, 3581.0, 3582.0, 3583.0, 3584.0]],     [[3585.0, 3586.0, 3587.0, 3588.0, 3589.0, 3590.0, 3591.0],      [3592.0, 3593.0, 3594.0, 3595.0, 3596.0, 3597.0, 3598.0],      [3599.0, 3600.0, 3601.0, 3602.0, 3603.0, 3604.0, 3605.0],      [3606.0, 3607.0, 3608.0, 3609.0, 3610.0, 3611.0, 3612.0]]],    [[[3613.0, 3614.0, 3615.0, 3616.0, 3617.0, 3618.0, 3619.0],      [3620.0, 3621.0, 3622.0, 3623.0, 3624.0, 3625.0, 3626.0],      [3627.0, 3628.0, 3629.0, 3630.0, 3631.0, 3632.0, 3633.0],      [3634.0, 3635.0, 3636.0, 3637.0, 3638.0, 3639.0, 3640.0]],     [[3641.0, 3642.0, 3643.0, 3644.0, 3645.0, 3646.0, 3647.0],      [3648.0, 3649.0, 3650.0, 3651.0, 3652.0, 3653.0, 3654.0],      [3655.0, 3656.0, 3657.0, 3658.0, 3659.0, 3660.0, 3661.0],      [3662.0, 3663.0, 3664.0, 3665.0, 3666.0, 3667.0, 3668.0]],     [[3669.0, 3670.0, 3671.0, 3672.0, 3673.0, 3674.0, 3675.0],      [3676.0, 3677.0, 3678.0, 3679.0, 3680.0, 3681.0, 3682.0],      [3683.0, 3684.0, 3685.0, 3686.0, 3687.0, 3688.0, 3689.0],      [3690.0, 3691.0, 3692.0, 3693.0, 3694.0, 3695.0, 3696.0]]],    [[[3697.0, 3698.0, 3699.0, 3700.0, 3701.0, 3702.0, 3703.0],      [3704.0, 3705.0, 3706.0, 3707.0, 3708.0, 3709.0, 3710.0],      [3711.0, 3712.0, 3713.0, 3714.0, 3715.0, 3716.0, 3717.0],      [3718.0, 3719.0, 3720.0, 3721.0, 3722.0, 3723.0, 3724.0]],     [[3725.0, 3726.0, 3727.0, 3728.0, 3729.0, 3730.0, 3731.0],      [3732.0, 3733.0, 3734.0, 3735.0, 3736.0, 3737.0, 3738.0],      [3739.0, 3740.0, 3741.0, 3742.0, 3743.0, 3744.0, 3745.0],      [3746.0, 3747.0, 3748.0, 3749.0, 3750.0, 3751.0, 3752.0]],     [[3753.0, 3754.0, 3755.0, 3756.0, 3757.0, 3758.0, 3759.0],      [3760.0, 3761.0, 3762.0, 3763.0, 3764.0, 3765.0, 3766.0],      [3767.0, 3768.0, 3769.0, 3770.0, 3771.0, 3772.0, 3773.0],      [3774.0, 3775.0, 3776.0, 3777.0, 3778.0, 3779.0, 3780.0]]]],   [[[[3781.0, 3782.0, 3783.0, 3784.0, 3785.0, 3786.0, 3787.0],      [3788.0, 3789.0, 3790.0, 3791.0, 3792.0, 3793.0, 3794.0],      [3795.0, 3796.0, 3797.0, 3798.0, 3799.0, 3800.0, 3801.0],      [3802.0, 3803.0, 3804.0, 3805.0, 3806.0, 3807.0, 3808.0]],     [[3809.0, 3810.0, 3811.0, 3812.0, 3813.0, 3814.0, 3815.0],      [3816.0, 3817.0, 3818.0, 3819.0, 3820.0, 3821.0, 3822.0],      [3823.0, 3824.0, 3825.0, 3826.0, 3827.0, 3828.0, 3829.0],      [3830.0, 3831.0, 3832.0, 3833.0, 3834.0, 3835.0, 3836.0]],     [[3837.0, 3838.0, 3839.0, 3840.0, 3841.0, 3842.0, 3843.0],      [3844.0, 3845.0, 3846.0, 3847.0, 3848.0, 3849.0, 3850.0],      [3851.0, 3852.0, 3853.0, 3854.0, 3855.0, 3856.0, 3857.0],      [3858.0, 3859.0, 3860.0, 3861.0, 3862.0, 3863.0, 3864.0]]],    [[[3865.0, 3866.0, 3867.0, 3868.0, 3869.0, 3870.0, 3871.0],      [3872.0, 3873.0, 3874.0, 3875.0, 3876.0, 3877.0, 3878.0],      [3879.0, 3880.0, 3881.0, 3882.0, 3883.0, 3884.0, 3885.0],      [3886.0, 3887.0, 3888.0, 3889.0, 3890.0, 3891.0, 3892.0]],     [[3893.0, 3894.0, 3895.0, 3896.0, 3897.0, 3898.0, 3899.0],      [3900.0, 3901.0, 3902.0, 3903.0, 3904.0, 3905.0, 3906.0],      [3907.0, 3908.0, 3909.0, 3910.0, 3911.0, 3912.0, 3913.0],      [3914.0, 3915.0, 3916.0, 3917.0, 3918.0, 3919.0, 3920.0]],     [[3921.0, 3922.0, 3923.0, 3924.0, 3925.0, 3926.0, 3927.0],      [3928.0, 3929.0, 3930.0, 3931.0, 3932.0, 3933.0, 3934.0],      [3935.0, 3936.0, 3937.0, 3938.0, 3939.0, 3940.0, 3941.0],      [3942.0, 3943.0, 3944.0, 3945.0, 3946.0, 3947.0, 3948.0]]],    [[[3949.0, 3950.0, 3951.0, 3952.0, 3953.0, 3954.0, 3955.0],      [3956.0, 3957.0, 3958.0, 3959.0, 3960.0, 3961.0, 3962.0],      [3963.0, 3964.0, 3965.0, 3966.0, 3967.0, 3968.0, 3969.0],      [3970.0, 3971.0, 3972.0, 3973.0, 3974.0, 3975.0, 3976.0]],     [[3977.0, 3978.0, 3979.0, 3980.0, 3981.0, 3982.0, 3983.0],      [3984.0, 3985.0, 3986.0, 3987.0, 3988.0, 3989.0, 3990.0],      [3991.0, 3992.0, 3993.0, 3994.0, 3995.0, 3996.0, 3997.0],      [3998.0, 3999.0, 4000.0, 4001.0, 4002.0, 4003.0, 4004.0]],     [[4005.0, 4006.0, 4007.0, 4008.0, 4009.0, 4010.0, 4011.0],      [4012.0, 4013.0, 4014.0, 4015.0, 4016.0, 4017.0, 4018.0],      [4019.0, 4020.0, 4021.0, 4022.0, 4023.0, 4024.0, 4025.0],      [4026.0, 4027.0, 4028.0, 4029.0, 4030.0, 4031.0, 4032.0]]],    [[[4033.0, 4034.0, 4035.0, 4036.0, 4037.0, 4038.0, 4039.0],      [4040.0, 4041.0, 4042.0, 4043.0, 4044.0, 4045.0, 4046.0],      [4047.0, 4048.0, 4049.0, 4050.0, 4051.0, 4052.0, 4053.0],      [4054.0, 4055.0, 4056.0, 4057.0, 4058.0, 4059.0, 4060.0]],     [[4061.0, 4062.0, 4063.0, 4064.0, 4065.0, 4066.0, 4067.0],      [4068.0, 4069.0, 4070.0, 4071.0, 4072.0, 4073.0, 4074.0],      [4075.0, 4076.0, 4077.0, 4078.0, 4079.0, 4080.0, 4081.0],      [4082.0, 4083.0, 4084.0, 4085.0, 4086.0, 4087.0, 4088.0]],     [[4089.0, 4090.0, 4091.0, 4092.0, 4093.0, 4094.0, 4095.0],      [4096.0, 4097.0, 4098.0, 4099.0, 4100.0, 4101.0, 4102.0],      [4103.0, 4104.0, 4105.0, 4106.0, 4107.0, 4108.0, 4109.0],      [4110.0, 4111.0, 4112.0, 4113.0, 4114.0, 4115.0, 4116.0]]],    [[[4117.0, 4118.0, 4119.0, 4120.0, 4121.0, 4122.0, 4123.0],      [4124.0, 4125.0, 4126.0, 4127.0, 4128.0, 4129.0, 4130.0],      [4131.0, 4132.0, 4133.0, 4134.0, 4135.0, 4136.0, 4137.0],      [4138.0, 4139.0, 4140.0, 4141.0, 4142.0, 4143.0, 4144.0]],     [[4145.0, 4146.0, 4147.0, 4148.0, 4149.0, 4150.0, 4151.0],      [4152.0, 4153.0, 4154.0, 4155.0, 4156.0, 4157.0, 4158.0],      [4159.0, 4160.0, 4161.0, 4162.0, 4163.0, 4164.0, 4165.0],      [4166.0, 4167.0, 4168.0, 4169.0, 4170.0, 4171.0, 4172.0]],     [[4173.0, 4174.0, 4175.0, 4176.0, 4177.0, 4178.0, 4179.0],      [4180.0, 4181.0, 4182.0, 4183.0, 4184.0, 4185.0, 4186.0],      [4187.0, 4188.0, 4189.0, 4190.0, 4191.0, 4192.0, 4193.0],      [4194.0, 4195.0, 4196.0, 4197.0, 4198.0, 4199.0, 4200.0]]]]]] shape=[2, 5, 5, 3, 4, 7], strides=[2100, 420, 84, 28, 7, 1], layout=C (0x1)), I32([3, 1] shape=[2], strides=[1], layout=C | F (0x3)), I32([[2, 2],  [1, 1]] shape=[2, 2], strides=[2, 1], layout=C (0x1)))\n"
  },
  {
    "path": "tensorflow/tests/ops_nn_space_to_batch.rs",
    "content": "#![cfg(feature = \"conform\")]\n#![allow(non_snake_case)]\nextern crate env_logger;\n#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate proptest;\nextern crate tensorflow;\nextern crate tract_tensorflow;\n\nmod utils;\n\nuse crate::utils::*;\nuse proptest::prelude::*;\nuse tract_ndarray::prelude::*;\nuse tract_tensorflow::conform::*;\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tfpb;\nuse tract_tensorflow::tfpb::tensorflow::DataType::DtFloat;\n\nfn space_to_batch_strat() -> BoxedStrategy<(Tensor, Tensor, Tensor)> {\n    use proptest::collection::vec;\n    (1usize..4, vec(1usize..8, 1usize..4), vec(1usize..8, 1usize..4))\n        .prop_flat_map(|(b, spatial_dims, non_spatial_dims)| {\n            (\n                Just(b),\n                Just(spatial_dims.clone()),\n                Just(non_spatial_dims),\n                vec(1usize..4, spatial_dims.len()..spatial_dims.len() + 1),\n                vec(0usize..4, spatial_dims.len()..spatial_dims.len() + 1),\n            )\n        })\n        .prop_filter(\"block < input\", |&(_, ref sd, _, ref bs, _)| {\n            bs.iter().zip(sd.iter()).all(|(bs, is)| bs <= is)\n        })\n        .prop_map(\n            |(b, sd, nsd, bs, left_pad): (\n                usize,\n                Vec<usize>,\n                Vec<usize>,\n                Vec<usize>,\n                Vec<usize>,\n            )| {\n                let mut input_shape = vec![b];\n                input_shape.extend(&sd);\n                input_shape.extend(&nsd);\n                let input = ArrayD::from_shape_vec(\n                    input_shape.clone(),\n                    (0..input_shape.iter().cloned().product()).map(|i| (1 + i) as f32).collect(),\n                )\n                .unwrap();\n                let block_size = Array1::from_shape_fn(sd.len(), |i| bs[i] as i32).into_dyn();\n                let padding = Array2::<i32>::from_shape_fn((sd.len(), 2), |(d, locus)| {\n                    if locus == 0 {\n                        left_pad[d] as i32\n                    } else {\n                        block_size[d] - (sd[d] + left_pad[d]) as i32 % block_size[d]\n                    }\n                });\n                (input.into(), block_size.into(), padding.into_dyn().into())\n            },\n        )\n        .boxed()\n}\n\nproptest! {\n    #[test]\n    fn space_to_batch((ref i, ref bs, ref p) in space_to_batch_strat()) {\n        let graph = tfpb::graph()\n            .node(placeholder_f32(\"input\"))\n            .node(const_i32(\"block_shape\", bs))\n            .node(const_i32(\"paddings\", p))\n            .node(tfpb::node().name(\"op\").op(\"SpaceToBatchND\").input(\"input\")\n            .input(\"block_shape\")\n            .input(\"paddings\")\n            .attr(\"T\", DtFloat)\n            );\n        let graph = graph.write_to_bytes().unwrap();\n        let inputs = vec!((\"input\", i.clone()));\n        compare(&graph, inputs, \"op\")?\n    }\n}\n\nfn batch_to_space_strat() -> BoxedStrategy<(Tensor, Tensor, Tensor)> {\n    use crate::tract_tensorflow::tract_hir::internal::EvalOp;\n    space_to_batch_strat()\n        .prop_map(|(i, bs, p)| {\n            let batches: Tensor =\n                tract_tensorflow::ops::nn::s2b::raw::SpaceToBatch::new(f32::datum_type())\n                    .eval(tvec![i.into(), bs.clone().into(), p.clone().into()])\n                    .unwrap()\n                    .remove(0)\n                    .into_tensor();\n            (batches, bs, p)\n        })\n        .boxed()\n}\n\nproptest! {\n    #[test]\n    fn batch_to_space((ref b, ref bs, ref c) in batch_to_space_strat()) {\n        let graph = tfpb::graph()\n            .node(placeholder_f32(\"input\"))\n            .node(const_i32(\"block_shape\", bs))\n            .node(const_i32(\"crops\", c))\n            .node(tfpb::node().name(\"op\").op(\"BatchToSpaceND\").input(\"input\")\n            .input(\"block_shape\")\n            .input(\"crops\")\n            .attr(\"T\", DtFloat)\n            );\n        let graph = graph.write_to_bytes().unwrap();\n        let inputs = vec!((\"input\", b.clone()));\n        compare(&graph, inputs, \"op\")?\n    }\n}\n\n#[test]\nfn space_to_batch_1() {\n    let graph = tfpb::graph()\n        .node(placeholder_f32(\"input\"))\n        .node(const_i32(\"block_shape\", &Tensor::from(arr1(&[2i32, 2]))))\n        .node(const_i32(\"paddings\", &Tensor::from(arr2(&[[0i32, 0], [0, 0]]))))\n        .node(\n            tfpb::node()\n                .name(\"op\")\n                .op(\"SpaceToBatchND\")\n                .input(\"input\")\n                .input(\"block_shape\")\n                .input(\"paddings\")\n                .attr(\"T\", DtFloat),\n        );\n    let graph = graph.write_to_bytes().unwrap();\n    let i = tensor4(&[[[[1.0f32], [2.0]], [[3.0], [4.0]]]]);\n    let inputs = vec![(\"input\", i)];\n    compare(&graph, inputs, \"op\").unwrap()\n}\n\n#[test]\nfn batch_to_space_1() {\n    let graph = tfpb::graph()\n        .node(placeholder_f32(\"input\"))\n        .node(const_i32(\"block_shape\", &Tensor::from(arr1(&[2i32, 2]))))\n        .node(const_i32(\"crops\", &Tensor::from(arr2(&[[0i32, 0], [0, 0]]))))\n        .node(\n            tfpb::node()\n                .name(\"op\")\n                .op(\"BatchToSpaceND\")\n                .input(\"input\")\n                .input(\"block_shape\")\n                .input(\"crops\")\n                .attr(\"T\", DtFloat),\n        );\n    let graph = graph.write_to_bytes().unwrap();\n    let i = tensor4(&[[[[1.0f32]]], [[[2.0]]], [[[3.0]]], [[[4.0]]]]);\n    let inputs = vec![(\"input\", i)];\n    compare(&graph, inputs, \"op\").unwrap()\n}\n"
  },
  {
    "path": "tensorflow/tests/ops_random_uniform.rs",
    "content": "#![cfg(feature = \"conform\")]\n#![allow(non_snake_case)]\nextern crate env_logger;\n#[macro_use]\nextern crate log;\n#[macro_use]\nextern crate proptest;\nextern crate tract_tensorflow;\n\nmod utils;\n\nuse crate::utils::*;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_tensorflow::conform::*;\nuse tract_tensorflow::prelude::*;\nuse tract_tensorflow::tfpb;\nuse tract_tensorflow::tfpb::tensorflow::DataType;\n\nfn random_uniform_float(shape: &[i32], seed: (i32, i32)) -> proptest::test_runner::TestCaseResult {\n    let graph = tfpb::graph().node(const_i32(\"shape\", &tensor1(&*shape))).node(\n        tfpb::node()\n            .name(\"op\")\n            .op(\"RandomUniform\")\n            .input(\"shape\")\n            .attr(\"T\", DataType::DtInt32)\n            .attr(\"dtype\", DataType::DtFloat)\n            .attr(\"seed\", seed.0)\n            .attr(\"seed2\", seed.1),\n    );\n    let graph = graph.write_to_bytes().unwrap();\n    compare::<&'static str>(&graph, vec![], \"op\")\n}\n\nproptest! {\n    #[test]\n    fn proptest_random_uniform_float(shape in vec(1..5, 0..4), seed in ((1..4),(1..4))) {\n        random_uniform_float(&*shape, seed)?\n    }\n}\n\n#[test]\nfn random_uniform_float_1() {\n    random_uniform_float(&[], (1, 1)).unwrap();\n}\n"
  },
  {
    "path": "tensorflow/tests/utils/mod.rs",
    "content": "use tract_tensorflow::prelude::*;\n\nfn setup_test_logger() {\n    let _ = env_logger::Builder::from_env(\"TRACT_LOG\").try_init();\n}\n\n#[derive(Copy, Clone, PartialEq, Debug)]\npub enum Mode {\n    Infer,\n    Type,\n    Declutter,\n    Opt,\n}\n\npub fn compare<S: AsRef<str>>(\n    graph: &[u8],\n    inputs: Vec<(S, Tensor)>,\n    output: &str,\n) -> std::result::Result<(), ::proptest::test_runner::TestCaseError> {\n    setup_test_logger();\n    for mode in &[Mode::Infer, Mode::Type, Mode::Declutter, Mode::Opt] {\n        debug!(\"mode: {:?}\", mode);\n        compare_optim(graph, &inputs, output, *mode)?;\n    }\n    Ok(())\n}\n\npub fn run_tract<S: AsRef<str>>(\n    graph: &[u8],\n    inputs: &Vec<(S, Tensor)>,\n    output: &str,\n    mode: Mode,\n) -> TractResult<TVec<Arc<Tensor>>> {\n    let mut model = tract_tensorflow::tensorflow().model_for_read(&mut &*graph)?;\n    model.set_input_names(&inputs.iter().map(|pair| pair.0.as_ref()).collect::<Vec<&str>>())?;\n    model.select_outputs_by_name(&[output])?;\n    for (ix, (_, tf)) in inputs.iter().enumerate() {\n        model.set_input_fact(ix, tf.datum_type().fact(tf.shape()).into())?;\n    }\n    debug!(\"analysed\");\n    let inputs = inputs.iter().map(|pair| pair.1.clone()).collect();\n    if mode == Mode::Infer {\n        let plan = SimplePlan::new(&model)?;\n        plan.run(inputs)\n    } else {\n        let mut model = model.into_typed()?;\n        debug!(\"typed\");\n        if mode == Mode::Declutter {\n            model = model.declutter()?;\n            debug!(\"decluttered\");\n        } else if mode == Mode::Opt {\n            model = model.declutter()?.optimize()?;\n            debug!(\"optimized\");\n        };\n        trace!(\"{:#?}\", model);\n        let plan = SimplePlan::new(&model)?;\n        plan.run(inputs)\n    }\n}\n\npub fn compare_optim<S: AsRef<str>>(\n    graph: &[u8],\n    inputs: &Vec<(S, Tensor)>,\n    output: &str,\n    mode: Mode,\n) -> std::result::Result<(), ::proptest::test_runner::TestCaseError> {\n    setup_test_logger();\n    let tf_inputs: Vec<(&str, Tensor)> =\n        inputs.iter().map(|(s, m)| (s.as_ref(), m.clone())).collect();\n    let expected = tract_tensorflow::conform::tf::for_slice(&graph)\n        .unwrap()\n        .run(tf_inputs.clone(), &output)\n        .unwrap();\n    info!(\"Mode: {:?} starting\", mode);\n    info!(\"Tensorflow says: {:?}\", expected);\n\n    let found = run_tract(graph, inputs, output, mode).unwrap();\n\n    if let Err(e) = expected[0].close_enough(&found[0], true) {\n        error!(\"{:?} (mode: {:?})\", e, mode);\n        error!(\"Tensorflow says: {:?}\", expected);\n        error!(\"Tract says     : {:?}\", found);\n        Err(e).unwrap()\n    } else {\n        info!(\"Mode: {:?} passed\", mode);\n        Ok(())\n    }\n}\n\n#[allow(dead_code)]\npub fn infer<S: AsRef<str>>(\n    graph: &[u8],\n    inputs: Vec<(S, Tensor)>,\n    output_str: &str,\n) -> std::result::Result<(), ::proptest::test_runner::TestCaseError> {\n    setup_test_logger();\n    let mut model = tract_tensorflow::tensorflow().model_for_read(&mut &*graph).unwrap();\n    model\n        .set_input_names(&inputs.iter().map(|pair| pair.0.as_ref()).collect::<Vec<&str>>())\n        .unwrap();\n    model.select_outputs_by_name(&[output_str]).unwrap();\n    for (ix, (_, tf)) in inputs.iter().enumerate() {\n        model.set_input_fact(ix, tf.datum_type().fact(tf.shape()).into())?;\n    }\n    let plan = SimplePlan::new(&model).unwrap();\n    let mut state = SimpleState::new(&plan).unwrap();\n    for (ix, (_, t)) in inputs.iter().enumerate() {\n        state.set_input(ix, t.clone()).unwrap();\n    }\n    let output = model.node_by_name(output_str).unwrap();\n    info!(\"Checking {} behaviour against tensorflow\", output.name);\n    state.compute_recursively(output.id).unwrap();\n    let _found = &state.values[output.id].as_ref().unwrap();\n\n    info!(\"Checking inference consistency on {}\", output.name);\n    let input_vectors: TVec<InferenceFact> = output\n        .inputs\n        .iter()\n        .map(|outlet| {\n            state.values[outlet.node].as_ref().unwrap()[outlet.slot]\n                .clone()\n                .into_tensor()\n                .clone()\n                .into()\n        })\n        .collect();\n    let output_vectors: TVec<InferenceFact> =\n        tvec![state.values[output.id].as_ref().unwrap()[0].clone().into_tensor().clone().into(),];\n\n    let input_facts = input_vectors.iter().collect();\n    let output_facts = output_vectors.iter().collect();\n\n    let output = model.node_by_name_mut(output_str).unwrap();\n    let e = output.op.infer_facts(input_facts, output_facts, tvec!());\n    prop_assert!(e.is_ok(), \"{:?}\", e);\n\n    Ok(())\n}\n"
  },
  {
    "path": "test-rt/infra/Cargo.toml",
    "content": "[package]\nname = \"infra\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nanyhow.workspace = true\ndowncast-rs.workspace = true\ndyn-clone.workspace = true\nenv_logger.workspace = true\nitertools.workspace = true\nlazy_static.workspace = true\ntract-core.workspace = true\n\n[target.'cfg(not(target_family = \"wasm\"))'.dependencies]\nproptest.workspace = true\n\n[target.'cfg(target_family = \"wasm\")'.dependencies]\n# Wasm doesn't support the `fork` feature of proptest.\nproptest = { version = \"1.0.0\", default-features = false, features = [\"std\", \"bit-set\"] }\n"
  },
  {
    "path": "test-rt/infra/src/lib.rs",
    "content": "#![allow(clippy::len_zero)]\nuse core::fmt;\nuse std::collections::HashMap;\nuse std::fmt::Debug;\nuse std::io::Write;\n\nuse downcast_rs::Downcast;\nuse dyn_clone::DynClone;\nuse itertools::Itertools;\nuse proptest::prelude::{Arbitrary, any_with};\nuse proptest::strategy::Strategy;\nuse proptest::test_runner::{Config, FileFailurePersistence, TestRunner};\nuse tract_core::internal::Approximation;\nuse tract_core::runtime::Runtime;\nuse tract_core::tract_data::TractResult;\n\npub fn setup_test_logger() {\n    let _ = env_logger::Builder::from_env(\"TRACT_LOG\").try_init();\n}\n\npub type TestResult = anyhow::Result<()>;\n\npub trait Test: Downcast + 'static + Send + Sync + DynClone {\n    fn run(&self, id: &'static str, runtime: &dyn Runtime) -> TestResult {\n        self.run_with_approx(id, runtime, Approximation::Close)\n    }\n    fn run_with_approx(\n        &self,\n        id: &'static str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult;\n}\ndowncast_rs::impl_downcast!(Test);\ndyn_clone::clone_trait_object!(Test);\n\n#[derive(Clone, Debug, Copy, PartialEq, Eq)]\npub enum TestStatus {\n    OK,\n    Ignored,\n    Skipped,\n}\n\n#[derive(Clone)]\npub enum TestSuite {\n    Node(HashMap<String, TestSuite>),\n    Leaf(Box<dyn Test>, TestStatus),\n}\n\nimpl Default for TestSuite {\n    fn default() -> Self {\n        setup_test_logger();\n        TestSuite::Node(Default::default())\n    }\n}\n\nimpl<T: Test> From<T> for TestSuite {\n    fn from(value: T) -> Self {\n        TestSuite::Leaf(Box::new(value), TestStatus::OK)\n    }\n}\n\nimpl TestSuite {\n    pub fn add(&mut self, id: impl ToString, test: impl Into<TestSuite>) {\n        match self {\n            TestSuite::Node(it) => {\n                it.insert(id.to_string(), test.into());\n            }\n            TestSuite::Leaf(..) => panic!(\"Can not add test case to a leaf\"),\n        }\n    }\n\n    pub fn add_arbitrary<A: Arbitrary + Test + Clone>(\n        &mut self,\n        id: impl ToString,\n        params: A::Parameters,\n    ) where\n        A::Parameters: Clone + Send + Sync + Debug,\n    {\n        self.add(id, ProptestWrapper::<A>(params, |_| true));\n    }\n\n    pub fn add_arbitrary_with_filter<A: Arbitrary + Test + Clone>(\n        &mut self,\n        id: impl ToString,\n        params: A::Parameters,\n        filter: fn(&A) -> bool,\n    ) where\n        A::Parameters: Clone + Send + Sync + Debug,\n    {\n        self.add(id, ProptestWrapper::<A>(params, filter));\n    }\n\n    pub fn with(mut self, id: impl ToString, test: impl Into<TestSuite>) -> Self {\n        self.add(id, test);\n        self\n    }\n\n    pub fn add_test(&mut self, id: impl ToString, test: impl Test) {\n        self.add_test_with_status(id, test, TestStatus::OK)\n    }\n\n    pub fn add_test_with_status(&mut self, id: impl ToString, test: impl Test, status: TestStatus) {\n        match self {\n            TestSuite::Node(it) => {\n                it.insert(id.to_string(), TestSuite::Leaf(Box::new(test), status));\n            }\n            TestSuite::Leaf(..) => panic!(\"Can not add test case to a leaf\"),\n        }\n    }\n\n    pub fn get(&self, id: &str) -> &dyn Test {\n        match self {\n            TestSuite::Node(n) => {\n                if let Some((head, tail)) = id.split_once(\"::\") {\n                    n[head].get(tail)\n                } else {\n                    n[id].get(\"\")\n                }\n            }\n            TestSuite::Leaf(test, _) => &**test,\n        }\n    }\n\n    pub fn get_sub(&self, id: &str) -> &TestSuite {\n        match self {\n            TestSuite::Node(n) => {\n                if let Some((head, tail)) = id.split_once(\"::\") {\n                    n[head].get_sub(tail)\n                } else {\n                    n[id].get_sub(\"\")\n                }\n            }\n            TestSuite::Leaf(_, _) => panic!(),\n        }\n    }\n\n    pub fn get_sub_mut(&mut self, id: &str) -> &mut TestSuite {\n        match self {\n            TestSuite::Node(n) => {\n                if let Some((head, tail)) = id.split_once(\"::\") {\n                    n.get_mut(head).unwrap().get_sub_mut(tail)\n                } else {\n                    n.get_mut(id).unwrap()\n                }\n            }\n            TestSuite::Leaf(_, _) => panic!(),\n        }\n    }\n\n    fn ignore_rec(\n        &mut self,\n        prefix: &mut Vec<String>,\n        ignore: &dyn Fn(&[String], &dyn Test) -> bool,\n    ) {\n        match self {\n            TestSuite::Node(n) => {\n                for (id, test) in n.iter_mut().sorted_by_key(|(k, _)| k.to_owned()) {\n                    prefix.push(id.to_owned());\n                    test.ignore_rec(prefix, ignore);\n                    prefix.pop();\n                }\n            }\n            TestSuite::Leaf(case, run) => {\n                if *run == TestStatus::OK && ignore(prefix, &**case) {\n                    *run = TestStatus::Ignored\n                }\n            }\n        }\n    }\n\n    pub fn ignore(&mut self, ign: &dyn Fn(&[String]) -> bool) {\n        self.ignore_rec(&mut vec![], &|name, _| ign(name))\n    }\n\n    pub fn ignore_case(&mut self, ign: &dyn Fn(&[String], &dyn Test) -> bool) {\n        self.ignore_rec(&mut vec![], ign)\n    }\n\n    fn skip_rec(&mut self, prefix: &mut Vec<String>, ign: &dyn Fn(&[String]) -> bool) {\n        match self {\n            TestSuite::Node(n) => {\n                for (id, test) in n.iter_mut().sorted_by_key(|(k, _)| k.to_owned()) {\n                    prefix.push(id.to_owned());\n                    test.skip_rec(prefix, ign);\n                    prefix.pop();\n                }\n            }\n            TestSuite::Leaf(_, run) => {\n                if ign(&*prefix) {\n                    *run = TestStatus::Skipped\n                }\n            }\n        }\n    }\n\n    pub fn skip(&mut self, ign: &dyn Fn(&[String]) -> bool) {\n        self.skip_rec(&mut vec![], ign)\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn dump(\n        &self,\n        test_suite: &str,\n        runtime: &str,\n        prefix: &str,\n        id: &str,\n        rs: &mut impl Write,\n        approx: &str,\n    ) -> TractResult<()> {\n        let full_id = [prefix, id].into_iter().filter(|s| s.len() > 0).join(\"::\");\n        match self {\n            TestSuite::Node(h) => {\n                if id.len() > 0 {\n                    writeln!(rs, \"mod {id} {{\").unwrap();\n                    writeln!(rs, \"#[allow(unused_imports)] use super::*;\").unwrap();\n                }\n                for (id, test) in h.iter().sorted_by_key(|(k, _)| k.to_owned()) {\n                    test.dump(test_suite, runtime, &full_id, id, rs, approx)?;\n                }\n                if id.len() > 0 {\n                    writeln!(rs, \"}}\").unwrap();\n                }\n            }\n            TestSuite::Leaf(_, status) => {\n                if *status != TestStatus::Skipped {\n                    writeln!(rs, \"#[allow(non_snake_case)]\").unwrap();\n                    writeln!(rs, \"#[test]\").unwrap();\n                    if *status == TestStatus::Ignored {\n                        writeln!(rs, \"#[ignore]\").unwrap();\n                    }\n                    writeln!(rs, \"fn {id}() -> TractResult<()> {{\",).unwrap();\n                    writeln!(rs, \"   let id = concat!(module_path!(), \\\"::{id}\\\");\").unwrap();\n                    writeln!(\n                        rs,\n                        \"    {test_suite}.get({full_id:?}).run_with_approx(id, {runtime}, {approx})\",\n                        )\n                        .unwrap();\n                    writeln!(rs, \"}}\").unwrap();\n                }\n            }\n        }\n        Ok(())\n    }\n\n    pub fn test_runtime(&self, name: &str, test_suite: &str, runtime: &str, approx: &str) {\n        let out_dir = std::env::var(\"OUT_DIR\").unwrap();\n        let out_dir = std::path::PathBuf::from(out_dir);\n        let test_dir = out_dir.join(\"tests\");\n        std::fs::create_dir_all(&test_dir).unwrap();\n        let test_file = test_dir.join(name).with_extension(\"rs\");\n        let mut rs = std::fs::File::create(test_file).unwrap();\n        self.dump(test_suite, runtime, \"\", \"\", &mut rs, approx).unwrap();\n    }\n}\n\n#[derive(Clone)]\nstruct ProptestWrapper<A: Arbitrary + Test + Clone>(A::Parameters, fn(&A) -> bool)\nwhere\n    A::Parameters: Clone + Send + Sync + Debug;\n\nimpl<A: Arbitrary + Test + Clone + Send + Sync> Debug for ProptestWrapper<A>\nwhere\n    A::Parameters: Clone + Send + Sync + Debug,\n{\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{:?}\", self.0)\n    }\n}\n\nimpl<A: Arbitrary + Test + Clone> Test for ProptestWrapper<A>\nwhere\n    A::Parameters: Clone + Send + Sync + Debug,\n{\n    fn run_with_approx(\n        &self,\n        id: &'static str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        // let crate_name = std::env::var(\"CARGO_PKG_NAME\").unwrap_or(\"\".to_string());\n        // let name = format!(\"{crate_name}::{suite}::{id}\");\n        let mut runner = TestRunner::new(Config {\n            failure_persistence: Some(Box::new(FileFailurePersistence::Off)),\n            test_name: Some(id),\n            ..Config::default()\n        });\n        runner.run(\n            &any_with::<A>(self.0.clone()).prop_filter(\"Test case filter\", |a| self.1(a)),\n            |v| {\n                v.run_with_approx(id, runtime, approx).map_err(|e| {\n                    proptest::test_runner::TestCaseError::Fail(format!(\"{e:?}\").into())\n                })\n            },\n        )?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "test-rt/suite-onnx/Cargo.toml",
    "content": "[package]\nname = \"suite-onnx\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nanyhow.workspace = true\nbytes.workspace = true\nenv_logger.workspace = true\nfs2.workspace = true\nitertools.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\nprost.workspace = true\nregex.workspace = true \ninfra = { path = \"../infra\" }\ntract-core.workspace = true\ntract-onnx.workspace = true\ntract-hir.workspace = true\n\n[features]\nonnx_1_4_1 = []\nonnx_1_5_0 = []\nonnx_1_6_0 = []\nonnx_1_7_0 = []\nonnx_1_8_1 = []\nonnx_1_9_0 = []\nonnx_1_10_2 = []\nonnx_1_11_0 = []\nonnx_1_12_0 = []\nonnx_1_13_0 = []\nonnx_1_14_1 = []\nonnx_1_15_0 = []\nonnx_1_16_2 = []\nonnx_1_17_0 = []\nonnx_1_18_0 = []\nonnx_1_19_1 = []\ndefault = [ \"onnx_1_13_0\" ]\n"
  },
  {
    "path": "test-rt/suite-onnx/node.txt",
    "content": "# test_.*_expanded\ntest_abs\ntest_acos\ntest_acos_example\ntest_acosh\ntest_acosh_example\ntest_add.*\ntest_and.*\ntest_argmax_default_axis_example\ntest_argmax_default_axis_example_select_last_index\ntest_argmax_default_axis_random\ntest_argmax_default_axis_random_select_last_index\ntest_argmax_keepdims_example\ntest_argmax_keepdims_example_select_last_index\ntest_argmax_keepdims_random\ntest_argmax_keepdims_random_select_last_index\ntest_argmax_negative_axis_keepdims_example\ntest_argmax_negative_axis_keepdims_example_select_last_index\ntest_argmax_negative_axis_keepdims_random\ntest_argmax_negative_axis_keepdims_random_select_last_index\ntest_argmax_no_keepdims_example\ntest_argmax_no_keepdims_example_select_last_index\ntest_argmax_no_keepdims_random\ntest_argmax_no_keepdims_random_select_last_index\ntest_argmin_default_axis_example\ntest_argmin_default_axis_example_select_last_index\ntest_argmin_default_axis_random\ntest_argmin_default_axis_random_select_last_index\ntest_argmin_keepdims_example\ntest_argmin_keepdims_example_select_last_index\ntest_argmin_keepdims_random\ntest_argmin_keepdims_random_select_last_index\ntest_argmin_negative_axis_keepdims_example\ntest_argmin_negative_axis_keepdims_example_select_last_index\ntest_argmin_negative_axis_keepdims_random\ntest_argmin_negative_axis_keepdims_random_select_last_index\ntest_argmin_no_keepdims_example\ntest_argmin_no_keepdims_example_select_last_index\ntest_argmin_no_keepdims_random\ntest_argmin_no_keepdims_random_select_last_index\ntest_asin\ntest_asin_example\ntest_asinh\ntest_asinh_example\ntest_atan\ntest_atan_example\ntest_atanh\ntest_atanh_example\ntest_averagepool_1d_default\ntest_averagepool_2d_ceil not-nnef\ntest_averagepool_2d_default\ntest_averagepool_2d_pads\ntest_averagepool_2d_pads_count_include_pad not-nnef\ntest_averagepool_2d_precomputed_pads\ntest_averagepool_2d_precomputed_pads_count_include_pad not-nnef\ntest_averagepool_2d_precomputed_same_upper\ntest_averagepool_2d_precomputed_strides\ntest_averagepool_2d_same_lower not-nnef\ntest_averagepool_2d_same_upper\ntest_averagepool_2d_strides\ntest_averagepool_3d_default\ntest_basic_convinteger                                                              input:x \ntest_basic_conv_without_padding input:x\ntest_basic_conv_with_padding input:x\ntest_batchnorm_epsilon input:x\ntest_batchnorm_example input:x\ntest_bitshift.*\ntest_bitwise.*\ntest_blackmanwindow_expanded input:\ntest_blackmanwindow input:\ntest_blackmanwindow_symmetric_expanded input:\ntest_blackmanwindow_symmetric input:\ntest_cast_DOUBLE_to_FLOAT\ntest_cast_DOUBLE_to_FLOAT16\ntest_cast_FLOAT16_to_DOUBLE\ntest_cast_FLOAT16_to_FLOAT\ntest_cast_FLOAT_to_DOUBLE\ntest_cast_FLOAT_to_FLOAT16\n# test_cast_FLOAT_to_STRING https://github.com/onnx/onnx/pull/1776 not-nnef\ntest_cast_FLOAT_to_STRING since:10\ntest_castlike_DOUBLE_to_FLOAT\ntest_castlike_DOUBLE_to_FLOAT16\ntest_castlike_DOUBLE_to_FLOAT16_expanded\ntest_castlike_DOUBLE_to_FLOAT_expanded\ntest_castlike_FLOAT16_to_DOUBLE\ntest_castlike_FLOAT16_to_DOUBLE_expanded\ntest_castlike_FLOAT16_to_FLOAT\ntest_castlike_FLOAT16_to_FLOAT_expanded\ntest_castlike_FLOAT_to_DOUBLE\ntest_castlike_FLOAT_to_DOUBLE_expanded\ntest_castlike_FLOAT_to_FLOAT16\ntest_castlike_FLOAT_to_FLOAT16_expanded\ntest_castlike_FLOAT_to_STRING\ntest_castlike_FLOAT_to_STRING_expanded\ntest_castlike_STRING_to_FLOAT\ntest_castlike_STRING_to_FLOAT_expanded not-nnef\ntest_cast_STRING_to_FLOAT not-nnef since:10\ntest_ceil\ntest_ceil_example\ntest_celu\ntest_celu_expanded\ntest_clip.*\ntest_concat.*\ntest_constant\ntest_constant_pad input:x\ntest_constant_pad_axes input:x\ntest_constantlike_ones_with_input not-nnef\ntest_constantlike_threes_with_shape_and_dtype not-nnef\ntest_constantlike_zeros_without_input_dtype not-nnef\ntest_convinteger_with_padding                                                       input:x \ntest_convtranspose_1d input:X\ntest_convtranspose_3d input:X\ntest_convtranspose_dilations input:X\ntest_convtranspose input:X\ntest_convtranspose_kernel_shape input:X\ntest_convtranspose_output_shape input:X\ntest_convtranspose_pad input:X\ntest_convtranspose_pads input:X\ntest_convtranspose_with_kernel input:x\ntest_conv_with_strides_and_asymmetric_padding input:x\ntest_conv_with_strides_no_padding input:x\ntest_conv_with_strides_padding input:x\ntest_cos\ntest_cos_example\ntest_cosh\ntest_cosh_example\ntest_cumsum_1d_exclusive not-nnef input:x since:13\ntest_cumsum_1d input:x since:13\ntest_cumsum_1d_reverse_exclusive not-nnef input:x since:13\ntest_cumsum_1d_reverse input:x since:13\ntest_cumsum_2d_axis_0 input:x since:13\ntest_cumsum_2d_axis_1 input:x since:13\ntest_cumsum_2d_negative_axis input:x since:13\ntest_cumsum_2d not-nnef input:x since:13\ntest_depthtospace.*\ntest_dequantizelinear                                                               input:x not-nnef\n# test_dft\n# test_dft_axis\n# test_dft_inverse\ntest_div.*\ntest_dropout_default\ntest_dropout_default_old\ntest_dropout_random not-nnef\ntest_dropout_random_old\ntest_dynamicquantizelinear_max_adjusted  not-nnef\ntest_dynamicquantizelinear_min_adjusted  not-nnef\ntest_dynamicquantizelinear  not-nnef\ntest_edge_pad input:x\ntest_einsum.*\ntest_elu\ntest_elu_default\ntest_elu_default_expanded_ver18\ntest_elu_example\ntest_elu_example_expanded_ver18\ntest_elu_expanded_ver18\ntest_equal.*\ntest_erf\ntest_exp\ntest_expand_dim_changed input:data\ntest_expand_dim_unchanged input:data\ntest_exp_example\ntest_eyelike_populate_off_main_diagonal\ntest_eyelike_with_dtype\ntest_eyelike_without_dtype\ntest_flatten_axis0\ntest_flatten_axis1\ntest_flatten_axis2\ntest_flatten_axis3\ntest_flatten_default_axis\ntest_flatten_negative_axis1\ntest_flatten_negative_axis2\ntest_flatten_negative_axis3\ntest_flatten_negative_axis4\ntest_floor\ntest_floor_example\ntest_gather_0\ntest_gather_1\ntest_gather_2d_indices\ntest_gather_elements_0\ntest_gather_elements_1\ntest_gather_elements_negative_indices\ntest_gathernd_example_float32\ntest_gathernd_example_int32\ntest_gathernd_example_int32_batch_dim1\ntest_gather_negative_indices\ntest_gemm_.*\ntest_globalaveragepool\ntest_globalaveragepool_precomputed\ntest_globalmaxpool\ntest_globalmaxpool_precomputed\ntest_gridsample.*                                                              since:16\ntest_greater.*\ntest_gru_batchwise\ntest_gru_defaults\ntest_gru_seq_length\ntest_gru_with_initial_bias\ntest_if\ntest_hammingwindow_expanded input:\ntest_hammingwindow input:\ntest_hammingwindow_symmetric_expanded input:\ntest_hammingwindow_symmetric input:\ntest_hannwindow_expanded input:\ntest_hannwindow input:\ntest_hannwindow_symmetric_expanded input:\ntest_hannwindow_symmetric input:\ntest_hardmax_axis_0\ntest_hardmax_axis_1\ntest_hardmax_axis_2\ntest_hardmax_default_axis\ntest_hardmax_example\ntest_hardmax_negative_axis\ntest_hardmax_one_hot\ntest_hardsigmoid\ntest_hardsigmoid_default\ntest_hardsigmoid_default_expanded_ver18\ntest_hardsigmoid_example\ntest_hardsigmoid_example_expanded_ver18\ntest_hardsigmoid_expanded_ver18\ntest_hardswish\ntest_hardswish_expanded\ntest_identity\ntest_instancenorm_example\ntest_isinf.*\ntest_isnan\ntest_layer_normalization.*\ntest_leakyrelu\ntest_leakyrelu_default\ntest_leakyrelu_default_expanded\ntest_leakyrelu_example\ntest_leakyrelu_example_expanded\ntest_leakyrelu_expanded\ntest_less.*\ntest_log\ntest_log_example\ntest_logsoftmax.*\ntest_lrn\ntest_lrn_default\ntest_lstm_batchwise\ntest_lstm_defaults\ntest_lstm_with_initial_bias\ntest_lstm_with_peepholes input:X\ntest_matmul_2d\ntest_matmul_3d\ntest_matmul_4d\ntest_matmulinteger                                                               \ntest_max_.*\ntest_maxpool_1d_default\ntest_maxpool_2d_ceil not-nnef\ntest_maxpool_2d_default\ntest_maxpool_2d_pads\ntest_maxpool_2d_precomputed_pads\ntest_maxpool_2d_precomputed_same_upper\ntest_maxpool_2d_precomputed_strides\ntest_maxpool_2d_same_lower not-nnef\ntest_maxpool_2d_same_upper\ntest_maxpool_2d_strides\ntest_maxpool_2d_uint8\ntest_maxpool_3d_default\ntest_maxpool_with_argmax_2d_precomputed_pads not-nnef\ntest_mean_example\ntest_mean_one_input\ntest_mean_two_inputs\ntest_melweightmatrix input:\ntest_min.*\ntest_mish_expanded\ntest_mod_broadcast not-nnef\ntest_mod_int64_fmod not-nnef\ntest_mod_mixed_sign_float16 not-nnef\ntest_mod_mixed_sign_float32 not-nnef\ntest_mod_mixed_sign_float64 not-nnef\ntest_mod_mixed_sign_int16 not-nnef\ntest_mod_mixed_sign_int32 not-nnef\ntest_mod_mixed_sign_int64 not-nnef\ntest_mod_mixed_sign_int8 not-nnef\ntest_mod_uint16 not-nnef\ntest_mod_uint32 not-nnef\ntest_mod_uint64 not-nnef\ntest_mod_uint8 not-nnef\ntest_mul.*\ntest_mvn_expanded\ntest_neg\ntest_negative_log_likelihood_loss_input_shape_is_NCd1d2d3d4d5_none_no_weight_expanded\ntest_negative_log_likelihood_loss_input_shape_is_NCd1d2d3_none_no_weight_negative_ignore_index_expanded\ntest_negative_log_likelihood_loss_input_shape_is_NCd1d2_expanded\ntest_negative_log_likelihood_loss_input_shape_is_NCd1d2_no_weight_reduction_mean_ignore_index_expanded\ntest_negative_log_likelihood_loss_input_shape_is_NCd1d2_reduction_mean_expanded\ntest_negative_log_likelihood_loss_input_shape_is_NCd1d2_reduction_sum_expanded\ntest_negative_log_likelihood_loss_input_shape_is_NCd1_expanded\ntest_negative_log_likelihood_loss_input_shape_is_NCd1_ignore_index_expanded\ntest_negative_log_likelihood_loss_input_shape_is_NC_expanded\ntest_neg_example\ntest_nllloss_NCd1d2d3d4d5_mean_weight_expanded\ntest_nllloss_NCd1d2d3d4d5_none_no_weight_expanded input:input\ntest_nllloss_NCd1d2d3_none_no_weight_negative_ii_expanded input:input not-nnef\ntest_nllloss_NCd1d2d3_sum_weight_high_ii_expanded\ntest_nllloss_NCd1d2_expanded input:input\ntest_nllloss_NCd1d2_no_weight_reduction_mean_ii_expanded\ntest_nllloss_NCd1d2_reduction_mean_expanded\ntest_nllloss_NCd1d2_reduction_sum_expanded\ntest_nllloss_NCd1d2_with_weight_expanded input:input\ntest_nllloss_NCd1d2_with_weight_reduction_mean_expanded\ntest_nllloss_NCd1d2_with_weight_reduction_sum_expanded\ntest_nllloss_NCd1d2_with_weight_reduction_sum_ii_expanded\ntest_nllloss_NCd1_expanded\ntest_nllloss_NCd1_ii_expanded\ntest_nllloss_NCd1_mean_weight_negative_ii_expanded\ntest_nllloss_NCd1_weight_expanded\ntest_nllloss_NCd1_weight_ii_expanded\ntest_nllloss_NC_expanded input:input\ntest_nonmaxsuppression_center_point_box_format onnx-ignore-output-shape\ntest_nonmaxsuppression_flipped_coordinates onnx-ignore-output-shape\ntest_nonmaxsuppression_identical_boxes onnx-ignore-output-shape\ntest_nonmaxsuppression_limit_output_size onnx-ignore-output-shape\ntest_nonmaxsuppression_single_box onnx-ignore-output-shape\ntest_nonmaxsuppression_suppress_by_IOU_and_scores onnx-ignore-output-shape\ntest_nonmaxsuppression_suppress_by_IOU onnx-ignore-output-shape\ntest_nonmaxsuppression_two_batches onnx-ignore-output-shape\ntest_nonmaxsuppression_two_classes onnx-ignore-output-shape\ntest_nonzero_example not-nnef\ntest_not_2d\ntest_not_3d\ntest_not_4d\ntest_onehot_negative_indices input:indices\ntest_onehot_with_axis input:indices\ntest_onehot_with_negative_axis input:indices\ntest_onehot_without_axis input:indices\ntest_or2d\ntest_or3d\ntest_or4d\ntest_or_bcast3v1d\ntest_or_bcast3v2d\ntest_or_bcast4v2d\ntest_or_bcast4v3d\ntest_or_bcast4v4d\ntest_pow\ntest_pow_bcast_array\ntest_pow_bcast_scalar\ntest_pow_example\ntest_pow_types_float\ntest_pow_types_float32_int32\ntest_pow_types_float32_int64\ntest_pow_types_float32_uint32\ntest_pow_types_float32_uint64\ntest_pow_types_int\ntest_pow_types_int32_float32\ntest_pow_types_int32_int32\ntest_pow_types_int64_float32\ntest_pow_types_int64_int64\ntest_prelu_broadcast\ntest_prelu_broadcast_expanded\ntest_prelu_example\ntest_prelu_example_expanded\ntest_qlinearmatmul_2D                                                                \ntest_qlinearmatmul_3D                                                                \ntest_quantizelinear                                                                 input:x not-nnef\ntest_range_float_type_positive_delta\ntest_range_int32_type_negative_delta\ntest_reciprocal\ntest_reciprocal_example\ntest_reduce_l1_default_axes_keepdims_example_expanded input:data\ntest_reduce_l1_default_axes_keepdims_example input:data\ntest_reduce_l1_default_axes_keepdims_random_expanded input:data\ntest_reduce_l1_default_axes_keepdims_random input:data\ntest_reduce_l1_do_not_keepdims_example_expanded input:data\ntest_reduce_l1_do_not_keepdims_example input:data\ntest_reduce_l1_do_not_keepdims_random_expanded input:data\ntest_reduce_l1_do_not_keepdims_random input:data\ntest_reduce_l1_keep_dims_example_expanded input:data\ntest_reduce_l1_keep_dims_example input:data\ntest_reduce_l1_keep_dims_random_expanded input:data\ntest_reduce_l1_keep_dims_random input:data\ntest_reduce_l1_negative_axes_keep_dims_example_expanded input:data\ntest_reduce_l1_negative_axes_keep_dims_example input:data\ntest_reduce_l1_negative_axes_keep_dims_random_expanded input:data\ntest_reduce_l1_negative_axes_keep_dims_random input:data\ntest_reduce_l2_default_axes_keepdims_example_expanded input:data\ntest_reduce_l2_default_axes_keepdims_example input:data\ntest_reduce_l2_default_axes_keepdims_random_expanded input:data\ntest_reduce_l2_default_axes_keepdims_random input:data\ntest_reduce_l2_do_not_keepdims_example_expanded input:data\ntest_reduce_l2_do_not_keepdims_example input:data\ntest_reduce_l2_do_not_keepdims_random_expanded input:data\ntest_reduce_l2_do_not_keepdims_random input:data\ntest_reduce_l2_keep_dims_example_expanded input:data\ntest_reduce_l2_keep_dims_example input:data\ntest_reduce_l2_keep_dims_random_expanded input:data\ntest_reduce_l2_keep_dims_random input:data\ntest_reduce_l2_negative_axes_keep_dims_example_expanded input:data\ntest_reduce_l2_negative_axes_keep_dims_example input:data\ntest_reduce_l2_negative_axes_keep_dims_random_expanded input:data\ntest_reduce_l2_negative_axes_keep_dims_random input:data\ntest_reduce_log_sum_asc_axes_expanded input:data\ntest_reduce_log_sum_asc_axes input:data\ntest_reduce_log_sum_default_expanded input:data\ntest_reduce_log_sum_default input:data\ntest_reduce_log_sum_desc_axes_expanded input:data\ntest_reduce_log_sum_desc_axes input:data\ntest_reduce_log_sum_exp_default_axes_keepdims_example_expanded input:data\ntest_reduce_log_sum_exp_default_axes_keepdims_example input:data\ntest_reduce_log_sum_exp_default_axes_keepdims_random_expanded input:data\ntest_reduce_log_sum_exp_default_axes_keepdims_random input:data\ntest_reduce_log_sum_exp_do_not_keepdims_example_expanded input:data\ntest_reduce_log_sum_exp_do_not_keepdims_example input:data\ntest_reduce_log_sum_exp_do_not_keepdims_random_expanded input:data\ntest_reduce_log_sum_exp_do_not_keepdims_random input:data\ntest_reduce_log_sum_exp_keepdims_example_expanded input:data\ntest_reduce_log_sum_exp_keepdims_example input:data\ntest_reduce_log_sum_exp_keepdims_random_expanded input:data\ntest_reduce_log_sum_exp_keepdims_random input:data\ntest_reduce_log_sum_exp_negative_axes_keepdims_example_expanded input:data\ntest_reduce_log_sum_exp_negative_axes_keepdims_example input:data\ntest_reduce_log_sum_exp_negative_axes_keepdims_random_expanded input:data\ntest_reduce_log_sum_exp_negative_axes_keepdims_random input:data\ntest_reduce_log_sum input:data\ntest_reduce_log_sum_negative_axes_expanded input:data\ntest_reduce_log_sum_negative_axes input:data\ntest_reduce_max_default_axes_keepdim_example input:data\ntest_reduce_max_default_axes_keepdims_random input:data\ntest_reduce_max_do_not_keepdims_example input:data\ntest_reduce_max_do_not_keepdims_random input:data\ntest_reduce_max_keepdims_example input:data\ntest_reduce_max_keepdims_random input:data\ntest_reduce_max_negative_axes_keepdims_example input:data\ntest_reduce_max_negative_axes_keepdims_random input:data\ntest_reduce_mean_default_axes_keepdims_example input:data\ntest_reduce_mean_default_axes_keepdims_random input:data\ntest_reduce_mean_do_not_keepdims_example input:data\ntest_reduce_mean_do_not_keepdims_random input:data\ntest_reduce_mean_keepdims_example input:data\ntest_reduce_mean_keepdims_random input:data\ntest_reduce_mean_negative_axes_keepdims_example input:data\ntest_reduce_mean_negative_axes_keepdims_random input:data\ntest_reduce_min_default_axes_keepdims_example input:data\ntest_reduce_min_default_axes_keepdims_random input:data\ntest_reduce_min_do_not_keepdims_example input:data\ntest_reduce_min_do_not_keepdims_random input:data\ntest_reduce_min_keepdims_example input:data\ntest_reduce_min_keepdims_random input:data\ntest_reduce_min_negative_axes_keepdims_example input:data\ntest_reduce_min_negative_axes_keepdims_random input:data\ntest_reduce_prod_default_axes_keepdims_example input:data\ntest_reduce_prod_default_axes_keepdims_random input:data\ntest_reduce_prod_do_not_keepdims_example input:data\ntest_reduce_prod_do_not_keepdims_random input:data\ntest_reduce_prod_keepdims_example input:data\ntest_reduce_prod_keepdims_random input:data\ntest_reduce_prod_negative_axes_keepdims_example input:data\ntest_reduce_prod_negative_axes_keepdims_random input:data\ntest_reduce_sum_default_axes_keepdims_example input:data\ntest_reduce_sum_default_axes_keepdims_random input:data\ntest_reduce_sum_do_not_keepdims_example input:data\ntest_reduce_sum_do_not_keepdims_random input:data\ntest_reduce_sum_empty_axes_input_noop_example input:data\ntest_reduce_sum_empty_axes_input_noop_random input:data\ntest_reduce_sum_keepdims_example input:data\ntest_reduce_sum_keepdims_random input:data\ntest_reduce_sum_negative_axes_keepdims_example input:data\ntest_reduce_sum_negative_axes_keepdims_random input:data\ntest_reduce_sum_square_default_axes_keepdims_example_expanded input:data\ntest_reduce_sum_square_default_axes_keepdims_example input:data\ntest_reduce_sum_square_default_axes_keepdims_random_expanded input:data\ntest_reduce_sum_square_default_axes_keepdims_random input:data\ntest_reduce_sum_square_do_not_keepdims_example_expanded input:data\ntest_reduce_sum_square_do_not_keepdims_example input:data\ntest_reduce_sum_square_do_not_keepdims_random_expanded input:data\ntest_reduce_sum_square_do_not_keepdims_random input:data\ntest_reduce_sum_square_keepdims_example_expanded input:data\ntest_reduce_sum_square_keepdims_example input:data\ntest_reduce_sum_square_keepdims_random_expanded input:data\ntest_reduce_sum_square_keepdims_random input:data\ntest_reduce_sum_square_negative_axes_keepdims_example_expanded input:data\ntest_reduce_sum_square_negative_axes_keepdims_example input:data\ntest_reduce_sum_square_negative_axes_keepdims_random_expanded input:data\ntest_reduce_sum_square_negative_axes_keepdims_random input:data\ntest_reflect_pad input:x\ntest_relu\ntest_relu_expanded_ver18\ntest_reshape_extended_dims input:data\ntest_reshape_negative_dim input:data\ntest_reshape_negative_extended_dims input:data\ntest_reshape_one_dim input:data\ntest_reshape_reduced_dims input:data\ntest_reshape_reordered_all_dims input:data\ntest_reshape_reordered_dims                                                         input:data not-nnef\ntest_reshape_reordered_last_dims input:data\ntest_reshape_zero_and_negative_dim input:data\ntest_reshape_zero_dim input:data\ntest_resize_downsample_scales_linear                                                input:X\ntest_resize_upsample_scales_linear_align_corners                                    input:X not-nnef\ntest_rnn_seq_length\ntest_round\ntest_scan9_sum\ntest_scatter_elements_with_axis\ntest_scatter_elements_with_negative_indices\ntest_scatter_elements_without_axis\ntest_scatter_elements_with_reduction_max\ntest_scatter_elements_with_reduction_min\ntest_scatternd\ntest_scatternd_add\ntest_scatternd_max\ntest_scatternd_min\ntest_scatternd_multiply\ntest_scatter_with_axis\ntest_scatter_without_axis\ntest_selu\ntest_selu_default\ntest_selu_default_expanded_ver18\ntest_selu_example\ntest_selu_example_expanded_ver18\ntest_selu_expanded_ver18\ntest_shape_clip_end onnx-ignore-output-type\ntest_shape_clip_start onnx-ignore-output-type\ntest_shape_end_1 onnx-ignore-output-type\ntest_shape_end_negative_1 onnx-ignore-output-type\ntest_shape_example onnx-ignore-output-type\ntest_shape onnx-ignore-output-type\ntest_shape_start_1_end_2 onnx-ignore-output-type\ntest_shape_start_1_end_negative_1 onnx-ignore-output-type\ntest_shape_start_1 onnx-ignore-output-type\ntest_shape_start_negative_1 onnx-ignore-output-type\ntest_shrink_hard\ntest_shrink_hard_expanded_ver18\ntest_shrink_soft\ntest_shrink_soft_expanded_ver18\ntest_sigmoid\ntest_sigmoid_example\ntest_sign\ntest_simple_rnn_batchwise\ntest_simple_rnn_defaults\ntest_simple_rnn_with_initial_bias\ntest_sin\ntest_sin_example\ntest_sinh\ntest_sinh_example\ntest_size_example onnx-ignore-output-type\ntest_size onnx-ignore-output-type\ntest_slice_default_axes input:x\ntest_slice_default_steps input:x\ntest_slice_end_out_of_bounds input:x\ntest_slice input:x\ntest_slice_neg input:x\ntest_slice_neg_steps input:x\ntest_slice_start_out_of_bounds input:x\ntest_softmax_axis_0\ntest_softmax_axis_0_expanded\ntest_softmax_axis_0_expanded_ver18\ntest_softmax_axis_1\ntest_softmax_axis_1_expanded\ntest_softmax_axis_1_expanded_ver18\ntest_softmax_axis_2\ntest_softmax_axis_2_expanded\ntest_softmax_axis_2_expanded_ver18\ntest_softmax_default_axis\ntest_softmax_default_axis_expanded\ntest_softmax_default_axis_expanded_ver18\ntest_softmax_example\ntest_softmax_example_expanded\ntest_softmax_example_expanded_ver18\ntest_softmax_large_number\ntest_softmax_large_number_expanded\ntest_softmax_large_number_expanded_ver18\ntest_softmax_negative_axis\ntest_softmax_negative_axis_expanded\ntest_softmax_negative_axis_expanded_ver18\ntest_softplus.*\ntest_softsign\ntest_softsign_example\ntest_softsign_example_expanded_ver18\ntest_softsign_expanded_ver18\ntest_spacetodepth\ntest_spacetodepth_example\ntest_split_1d_uneven_split_opset18\ntest_split_2d_uneven_split_opset18\ntest_split_equal_parts_1d\ntest_split_equal_parts_1d_opset13\ntest_split_equal_parts_1d_opset18\ntest_split_equal_parts_2d\ntest_split_equal_parts_2d_opset13\ntest_split_equal_parts_default_axis\ntest_split_equal_parts_default_axis_opset13\ntest_split_equal_parts_default_axis_opset18\ntest_split_variable_parts_1d input:input\ntest_split_variable_parts_1d_opset13 input:input\ntest_split_variable_parts_1d_opset18 input:input\ntest_split_variable_parts_2d input:input\ntest_split_variable_parts_2d_opset13 input:input\ntest_split_variable_parts_2d_opset18 input:input\ntest_split_variable_parts_default_axis input:input\ntest_split_variable_parts_default_axis_opset13 input:input\ntest_split_variable_parts_default_axis_opset18 input:input\ntest_split_zero_size_splits input:input\ntest_sqrt\ntest_sqrt_example\ntest_squeeze input:x\ntest_squeeze_negative_axes input:x\ntest_stft input:signal\ntest_stft_with_window input:signal\ntest_sub.*\ntest_sum.*\ntest_tan\ntest_tan_example\ntest_tanh\ntest_tanh_example\ntest_thresholdedrelu\ntest_thresholdedrelu_default\ntest_thresholdedrelu_default_expanded_ver18\ntest_thresholdedrelu_example\ntest_thresholdedrelu_example_expanded_ver18\ntest_thresholdedrelu_expanded_ver18\ntest_tile input:x\ntest_tile_precomputed input:x\ntest_top_k_negative_axis\ntest_top_k since:10\ntest_top_k_smallest\ntest_transpose_all_permutations_0\ntest_transpose_all_permutations_1\ntest_transpose_all_permutations_2\ntest_transpose_all_permutations_3\ntest_transpose_all_permutations_4\ntest_transpose_all_permutations_5\ntest_transpose_default\ntest_tril\ntest_tril_neg\ntest_tril_one_row_neg\ntest_tril_out_neg\ntest_tril_out_pos\ntest_tril_pos\ntest_tril_square\ntest_tril_square_neg\ntest_tril_zero\ntest_triu\ntest_triu_neg\ntest_triu_one_row\ntest_triu_out_neg_out\ntest_triu_out_pos\ntest_triu_pos\ntest_triu_square\ntest_triu_square_neg\ntest_triu_zero\ntest_unsqueeze_axis_0 input:x\ntest_unsqueeze_axis_1 input:x\ntest_unsqueeze_axis_2 input:x\ntest_unsqueeze_axis_3\ntest_unsqueeze_negative_axes input:x\ntest_unsqueeze not-nnef\ntest_unsqueeze_three_axes input:x\ntest_unsqueeze_two_axes input:x\ntest_unsqueeze_unsorted_axes input:x\ntest_where_example\ntest_where_long_example\ntest_xor2d\ntest_xor3d\ntest_xor4d\ntest_xor_bcast3v1d\ntest_xor_bcast3v2d\ntest_xor_bcast4v2d\ntest_xor_bcast4v3d\ntest_xor_bcast4v4d\n"
  },
  {
    "path": "test-rt/suite-onnx/pytorch-converted.txt",
    "content": "test_AvgPool1d\ntest_AvgPool1d_stride\ntest_AvgPool2d\ntest_AvgPool2d_stride\ntest_AvgPool3d\ntest_AvgPool3d_stride\ntest_AvgPool3d_stride1_pad0_gpu_input\ntest_BatchNorm1d_3d_input_eval\ntest_BatchNorm2d_eval\ntest_BatchNorm2d_momentum_eval\ntest_BatchNorm3d_eval\ntest_BatchNorm3d_momentum_eval\ntest_ConstantPad2d\ntest_Conv1d\ntest_Conv1d_dilated\ntest_Conv1d_groups\ntest_Conv1d_pad1\ntest_Conv1d_pad1size1\ntest_Conv1d_pad2\ntest_Conv1d_pad2size1\ntest_Conv1d_stride\ntest_Conv2d\ntest_Conv2d_depthwise\ntest_Conv2d_depthwise_padded\ntest_Conv2d_depthwise_strided\ntest_Conv2d_depthwise_with_multiplier\ntest_Conv2d_dilated\ntest_Conv2d_groups\ntest_Conv2d_groups_thnn\ntest_Conv2d_no_bias\ntest_Conv2d_padding\ntest_Conv2d_strided\ntest_Conv3d\ntest_Conv3d_dilated\ntest_Conv3d_dilated_strided\ntest_Conv3d_groups\ntest_Conv3d_no_bias\ntest_Conv3d_stride\ntest_Conv3d_stride_padding\ntest_ConvTranspose2d\ntest_ConvTranspose2d_no_bias\ntest_ELU\ntest_Embedding\ntest_Embedding_sparse\ntest_GLU\ntest_GLU_dim\ntest_LeakyReLU\ntest_LeakyReLU_with_negval\ntest_Linear\ntest_Linear_no_bias\ntest_LogSoftmax\ntest_MaxPool1d\ntest_MaxPool1d_stride\ntest_MaxPool2d\ntest_MaxPool3d\ntest_MaxPool3d_stride\ntest_MaxPool3d_stride_padding\ntest_PReLU_1d\ntest_PReLU_2d\ntest_PReLU_3d\ntest_PixelShuffle\ntest_PoissonNLLLLoss_no_reduce\ntest_ReLU\ntest_ReflectionPad2d\ntest_ReplicationPad2d\ntest_SELU\ntest_Sigmoid\ntest_Softmax\ntest_Softmin\ntest_Softplus\ntest_Softsign\ntest_Tanh\ntest_ZeroPad2d\ntest_log_softmax_dim3\ntest_log_softmax_lastdim\ntest_softmax_functional_dim3\ntest_softmax_lastdim"
  },
  {
    "path": "test-rt/suite-onnx/pytorch-operator.txt",
    "content": "test_operator_add_broadcast\ntest_operator_add_size1_broadcast\ntest_operator_add_size1_right_broadcast\ntest_operator_add_size1_singleton_broadcast\ntest_operator_addconstant\ntest_operator_addmm\ntest_operator_basic\ntest_operator_chunk\ntest_operator_clip\ntest_operator_concat2\ntest_operator_conv\ntest_operator_convtranspose\ntest_operator_exp\ntest_operator_flatten\ntest_operator_index\ntest_operator_max\ntest_operator_maxpool\ntest_operator_min\ntest_operator_mm\ntest_operator_non_float_params\ntest_operator_pad\ntest_operator_params\ntest_operator_permute2\ntest_operator_pow\ntest_operator_reduced_mean\ntest_operator_reduced_mean_keepdim\ntest_operator_reduced_sum\ntest_operator_reduced_sum_keepdim\ntest_operator_repeat\ntest_operator_repeat_dim_overflow\ntest_operator_selu\ntest_operator_sqrt\ntest_operator_symbolic_override_nested\ntest_operator_view"
  },
  {
    "path": "test-rt/suite-onnx/simple.txt",
    "content": "# test_shrink example shape not consistent with network not-nnef\ntest_expand_shape_model1 input:X\ntest_expand_shape_model2 input:X\ntest_expand_shape_model3 input:X\ntest_expand_shape_model4 input:X\ntest_shrink since:10\ntest_sign_model\ntest_single_relu_model\n"
  },
  {
    "path": "test-rt/suite-onnx/src/lib.rs",
    "content": "use log::*;\nuse prost::Message;\nuse regex::Regex;\nuse std::path::PathBuf;\nuse tract_hir::internal::*;\nuse tract_onnx::data_resolver::FopenDataResolver;\nuse tract_onnx::pb::TensorProto;\nuse tract_onnx::tensor::load_tensor;\n\nuse infra::{Test, TestStatus, TestSuite};\n\npub fn suite() -> &'static TestSuite {\n    lazy_static::lazy_static! {\n        static ref SUITE: TestSuite = full();\n    };\n    &SUITE\n}\n\nconst MANIFEST_NODE: &str = include_str!(\"../node.txt\");\nconst MANIFEST_SIMPLE: &str = include_str!(\"../simple.txt\");\nconst MANIFEST_PYTORCH_CONVERTED: &str = include_str!(\"../pytorch-converted.txt\");\nconst MANIFEST_PYTORCH_OPERATOR: &str = include_str!(\"../pytorch-operator.txt\");\n\n#[derive(Clone, Debug)]\nstruct OnnxTestCase {\n    path: PathBuf,\n    ignore_output_shapes: bool,\n    ignore_output_type: bool,\n    input: Option<String>,\n}\n\nimpl Test for OnnxTestCase {\n    fn run_with_approx(\n        &self,\n        _id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TractResult<()> {\n        setup_test_logger();\n        let model_file = self.path.join(\"model.onnx\");\n        info!(\"Loading {model_file:?}\");\n        let mut onnx = tract_onnx::onnx();\n\n        // hack: some tests (test_nonmaxsuppression_*) include the output shapes in the onnx model\n        // even though there should be no way of knowing them at optimization time. This breaks\n        // the solver.\n        if self.ignore_output_shapes {\n            onnx = onnx.with_ignore_output_shapes(true);\n        }\n        // in some other cases, we need to deal with a tdim vs i64 mismatch (test for Shape, and Size)\n        if self.ignore_output_type {\n            onnx = onnx.with_ignore_output_types(true);\n        }\n\n        trace!(\"Proto Model:\\n{:#?}\", onnx.proto_model_for_path(&model_file));\n        for d in std::fs::read_dir(&self.path)? {\n            let mut model = onnx.model_for_path(&model_file)?;\n            let d = d?;\n            if d.metadata().unwrap().is_dir()\n                && d.file_name().to_str().unwrap().starts_with(\"test_data_set_\")\n            {\n                let data_path = d.path();\n                let mut inputs = load_half_dataset(\"input\", &data_path);\n                if let Some(input) = &self.input {\n                    let mut actual_inputs = vec![];\n                    let mut actual_input_values = tvec![];\n                    let input_outlets = model.input_outlets()?.to_vec();\n                    for (ix, outlet) in input_outlets.iter().enumerate() {\n                        if &model.node(outlet.node).name == input {\n                            actual_inputs.push(*outlet);\n                            actual_input_values.push(inputs[ix].clone());\n                        } else {\n                            model.node_mut(outlet.node).op =\n                                Box::new(tract_hir::ops::konst::Const::new(\n                                    inputs[ix].clone().into_arc_tensor(),\n                                )?);\n                        }\n                    }\n                    model.set_input_outlets(&actual_inputs)?;\n                    inputs = actual_input_values;\n                }\n                info!(\"Analyse\");\n                trace!(\"Model:\\n{model:#?}\");\n                model.analyse(false)?;\n                model = model.incorporate()?;\n                let model = model.into_typed()?.into_decluttered()?;\n                info!(\"Test model (mode: {}) {:#?}\", runtime.name(), self.path);\n                let runnable = runtime.prepare(model)?;\n                run_model(&*runnable, inputs, &data_path, approx)?;\n                info!(\"Test model (mode: {}) {:#?} OK.\", runtime.name(), self.path);\n            }\n        }\n        Ok(())\n    }\n}\n\nfn versions() -> Vec<(&'static str, usize)> {\n    let mut versions = vec![];\n    if cfg!(feature = \"onnx_1_4_1\") {\n        versions.push((\"1.4.1\", 9));\n    }\n    if cfg!(feature = \"onnx_1_5_0\") {\n        versions.push((\"1.5.0\", 10));\n    }\n    if cfg!(feature = \"onnx_1_6_0\") {\n        versions.push((\"1.6.0\", 11));\n    }\n    if cfg!(feature = \"onnx_1_7_0\") {\n        versions.push((\"1.7.0\", 12));\n    }\n    if cfg!(feature = \"onnx_1_8_1\") {\n        versions.push((\"1.8.1\", 13));\n    }\n    if cfg!(feature = \"onnx_1_9_0\") {\n        versions.push((\"1.9.0\", 14));\n    }\n    if cfg!(feature = \"onnx_1_10_2\") {\n        versions.push((\"1.10.2\", 15));\n    }\n    if cfg!(feature = \"onnx_1_11_0\") {\n        versions.push((\"1.11.0\", 16));\n    }\n    if cfg!(feature = \"onnx_1_12_0\") {\n        versions.push((\"1.12.0\", 17));\n    }\n    if cfg!(feature = \"onnx_1_13_0\") {\n        versions.push((\"1.13.0\", 18));\n    }\n    if cfg!(feature = \"onnx_1_14_1\") {\n        versions.push((\"1.14.1\", 19));\n    }\n    if cfg!(feature = \"onnx_1_15_0\") {\n        versions.push((\"1.15.0\", 20));\n    }\n    if cfg!(feature = \"onnx_1_16_2\") {\n        versions.push((\"1.16.2\", 21));\n    }\n    if cfg!(feature = \"onnx_1_17_0\") {\n        versions.push((\"1.17.0\", 22));\n    }\n    if cfg!(feature = \"onnx_1_18_0\") {\n        versions.push((\"1.18.0\", 23));\n    }\n    if cfg!(feature = \"onnx_1_19_1\") {\n        versions.push((\"1.19.1\", 24));\n    }\n    versions\n}\n\npub fn dir() -> PathBuf {\n    let cache = ::std::env::var(\"CACHEDIR\").unwrap_or_else(|_| \"../../.cached\".to_string());\n    std::fs::create_dir_all(&cache).unwrap();\n    PathBuf::from(cache).join(\"onnx\")\n}\n\npub fn ensure_onnx_git_checkout() {\n    use std::sync::Once;\n    static START: Once = Once::new();\n    START.call_once(|| {\n        use fs2::FileExt;\n        std::fs::create_dir_all(dir()).unwrap();\n        let lockpath = dir().join(\".lock\");\n        let lockfile = std::fs::File::create(lockpath).unwrap();\n        lockfile.lock_exclusive().unwrap();\n        for (v, _) in versions() {\n            let wanted = dir().join(format!(\"onnx-{}\", v.replace('.', \"_\")));\n            if !wanted.join(\"onnx/backend/test/data\").exists() {\n                //let df = std::process::Command::new(\"df\").arg(\"-h\").output().unwrap();\n                //dbg!(df);\n                let tmp = wanted.with_extension(\"tmp\");\n                let _ = std::fs::remove_dir_all(&wanted);\n                let _ = std::fs::remove_dir_all(&tmp);\n                let run = std::process::Command::new(\"git\")\n                    .args([\"clone\", \"--depth=1\", \"--branch\"])\n                    .arg(format!(\"v{v}\"))\n                    .arg(\"https://github.com/onnx/onnx\")\n                    .arg(&tmp)\n                    .status()\n                    .unwrap();\n                if !run.success() {\n                    panic!(\"Failed to clone onnx\")\n                }\n                std::fs::rename(tmp, wanted).unwrap();\n            }\n        }\n    });\n}\n\nfn full() -> TestSuite {\n    ensure_onnx_git_checkout();\n    let mut suite = TestSuite::default();\n    for (tests_set, manifest) in [\n        (\"node\", MANIFEST_NODE),\n        (\"simple\", MANIFEST_SIMPLE),\n        (\"pytorch-operator\", MANIFEST_PYTORCH_OPERATOR),\n        (\"pytorch-converted\", MANIFEST_PYTORCH_CONVERTED),\n    ] {\n        let working_list: Vec<(Regex, Vec<String>)> = manifest\n            .split('\\n')\n            .map(|s| s.to_string())\n            .filter(|s| s.trim().len() > 1 && s.trim().as_bytes()[0] != b'#')\n            .map(|s| {\n                let mut splits = s.split_whitespace();\n                let pat = splits.next().unwrap();\n                let re = Regex::new(&format!(\"^{pat}$\")).unwrap();\n                (re, splits.map(|s| s.to_string()).collect())\n            })\n            .collect();\n\n        let mut tags = TestSuite::default();\n        for (onnx_tag, opset) in versions() {\n            let node_tests = dir()\n                .join(format!(\"onnx-{}\", onnx_tag.replace('.', \"_\")))\n                .join(\"onnx/backend/test/data\")\n                .join(tests_set);\n            assert!(node_tests.exists());\n\n            let identifier = \"v\".to_string() + &onnx_tag.replace('.', \"_\");\n\n            let tests: Vec<String> = std::fs::read_dir(&node_tests)\n                .unwrap()\n                .map(|de| de.unwrap().file_name().to_str().unwrap().to_owned())\n                .collect();\n            let mut units = TestSuite::default();\n            for t in &tests {\n                let details =\n                    working_list.iter().find(|pair| pair.0.is_match(t)).map(|pair| &*pair.1);\n                let ignored = details.is_none()\n                    || details.unwrap().iter().any(|s| {\n                        s.strip_prefix(\"since:\")\n                            .map(|since| since.parse::<usize>().unwrap() > opset)\n                            .unwrap_or(false)\n                    });\n                let ignore_output_shapes =\n                    details.unwrap_or_default().iter().any(|s| s == \"onnx-ignore-output-shape\");\n                let ignore_output_type =\n                    details.unwrap_or_default().iter().any(|s| s == \"onnx-ignore-output-type\");\n                let input = details\n                    .unwrap_or_default()\n                    .iter()\n                    .find_map(|s| s.strip_prefix(\"input:\"))\n                    .map(|s| s.to_owned());\n                units.add_test_with_status(\n                    t,\n                    OnnxTestCase {\n                        path: node_tests.join(t),\n                        ignore_output_type,\n                        ignore_output_shapes,\n                        input,\n                    },\n                    if ignored { TestStatus::Ignored } else { TestStatus::OK },\n                );\n            }\n            tags.add(identifier, units);\n        }\n        suite.add(tests_set.replace('-', \"_\"), tags);\n    }\n    suite\n}\n\n#[allow(dead_code)]\nfn setup_test_logger() {\n    let _ = env_logger::Builder::from_env(\"TRACT_LOG\").try_init();\n}\n\npub fn load_half_dataset(prefix: &str, path: &std::path::Path) -> TVec<Tensor> {\n    let mut vec = tvec!();\n    let len = std::fs::read_dir(path)\n        .map_err(|e| format!(\"accessing {path:?}, {e:?}\"))\n        .unwrap()\n        .filter(|d| d.as_ref().unwrap().file_name().to_str().unwrap().starts_with(prefix))\n        .count();\n    for i in 0..len {\n        let filename = path.join(format!(\"{prefix}_{i}.pb\"));\n        let bytes = bytes::Bytes::from(std::fs::read(filename).unwrap());\n        let tensor = TensorProto::decode(bytes).unwrap();\n        let tensor = load_tensor(&FopenDataResolver, &tensor, None).unwrap();\n        vec.push(tensor)\n    }\n    debug!(\"{path:?}: {vec:?}\");\n    vec\n}\n\nfn run_model(\n    model: &dyn Runnable,\n    inputs: TVec<Tensor>,\n    data_path: &std::path::Path,\n    approx: Approximation,\n) -> TractResult<()> {\n    let expected = load_half_dataset(\"output\", data_path);\n    trace!(\"Loaded output asserts: {expected:?}\");\n    let inputs = inputs.into_iter().map(|t| t.into_tvalue()).collect();\n    let computed = model.run(inputs)?;\n    if computed.len() != expected.len() {\n        panic!(\n            \"For {:?}, different number of results: got:{} expected:{}\",\n            data_path,\n            computed.len(),\n            expected.len()\n        );\n    }\n    for (ix, (a, b)) in computed.iter().zip(expected.iter()).enumerate() {\n        //                println!(\"computed: {:?}\", computed[ix].dump(true));\n        //                println!(\"expected: {:?}\", expected[ix].dump(true));\n        if let Err(e) = a.close_enough(b, approx) {\n            bail!(\n                \"For {:?}, different ({approx:?}) result for output #{}:\\ngot:\\n{:?}\\nexpected:\\n{:?} \\n{}\",\n                data_path,\n                ix,\n                a.cast_to::<f32>().unwrap().to_plain_array_view::<f32>().unwrap(),\n                b.cast_to::<f32>().unwrap().to_plain_array_view::<f32>().unwrap(),\n                e //                e.display_chain()\n            );\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "test-rt/suite-unit/Cargo.toml",
    "content": "[package]\nname = \"suite-unit\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ntract-core.workspace = true\ntract-transformers.workspace = true\ninfra = { path = \"../infra\" }\n\n[target.'cfg(not(target_family = \"wasm\"))'.dependencies]\nproptest.workspace = true\n\n[target.'cfg(target_family = \"wasm\")'.dependencies]\n# Wasm doesn't support the `fork` feature of proptest.\nproptest = { version = \"1.0.0\", default-features = false, features = [\"std\", \"bit-set\"] }\n"
  },
  {
    "path": "test-rt/suite-unit/src/apply_rope.rs",
    "content": "use infra::Test;\nuse infra::TestResult;\nuse infra::TestSuite;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ndarray::ArrayD;\nuse tract_core::ndarray::concatenate;\nuse tract_core::num_traits::Float;\nuse tract_transformers::ops::apply_rope::ApplyRope;\n\nuse crate::tensor;\n\n#[derive(Debug, Clone)]\npub struct ApplyRopeProblem<F>\nwhere\n    F: Datum + Float,\n{\n    input: ArrayD<F>,\n    cos: ArrayD<F>,\n    sin: ArrayD<F>,\n}\n\nimpl<F> Arbitrary for ApplyRopeProblem<F>\nwhere\n    F: Datum + Float,\n{\n    type Parameters = ();\n    type Strategy = BoxedStrategy<ApplyRopeProblem<F>>;\n\n    fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {\n        (2usize..5)\n            .prop_flat_map(|rank| {\n                let dim = 1usize..10;\n                (vec(dim.clone(), 2..=2), vec(dim, (rank - 2)..=(rank - 2)))\n            })\n            .prop_flat_map(|(mut cos_sin_shape, extra_shape)| {\n                cos_sin_shape[1] *= 2; // Ensure inner axis dim is multiple of 2\n                (\n                    tensor::<F>(&[extra_shape.clone(), cos_sin_shape.clone()].concat()),\n                    tensor::<F>(&[extra_shape.clone(), cos_sin_shape.clone()].concat()),\n                    tensor::<F>(&[extra_shape, cos_sin_shape.clone()].concat()),\n                )\n                    .prop_map(|(input, cos, sin)| Self { input, cos, sin })\n            })\n            .boxed()\n    }\n}\n\nimpl<F> ApplyRopeProblem<F>\nwhere\n    F: Datum + Float,\n    f32: From<F>,\n{\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n\n        let input = model\n            .add_source(\"input\", TypedFact::shape_and_dt_of(&self.input.clone().into_tensor()))?;\n        let cos =\n            model.add_source(\"cos\", TypedFact::shape_and_dt_of(&self.cos.clone().into_tensor()))?;\n        let sin =\n            model.add_source(\"sin\", TypedFact::shape_and_dt_of(&self.sin.clone().into_tensor()))?;\n\n        let output = model.wire_node(\"apply_rope\", ApplyRope, &[input, cos, sin])?;\n        model.select_output_outlets(&output)?;\n\n        model = model.into_decluttered()?;\n        Ok(model)\n    }\n\n    fn reference(&self) -> ArrayD<F> {\n        let input = self.input.clone();\n\n        let inner_axis = input.shape().len() - 1;\n        let axis = tract_ndarray::Axis(inner_axis);\n        let len = input.shape()[inner_axis];\n        assert!(len % 2 == 0, \"Length of the last axis must be even\");\n\n        let mid = len / 2;\n\n        // Slice and clone the two halves\n        let first_half = input.slice_axis(axis, (..mid).into()).to_owned();\n        let mut second_half = input.slice_axis(axis, (mid..).into()).to_owned();\n        second_half.mapv_inplace(|x| -x);\n\n        // Concatenate in reverse order\n        let rotated_input = concatenate(axis, &[second_half.view(), first_half.view()]).unwrap();\n\n        let brd_cos = self.cos.broadcast(input.raw_dim()).unwrap();\n        let brd_sin = self.sin.broadcast(input.raw_dim()).unwrap();\n\n        (input * brd_cos) + (rotated_input * brd_sin)\n    }\n}\n\nimpl<F> Test for ApplyRopeProblem<F>\nwhere\n    F: Datum + Float,\n    f32: From<F>,\n{\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.reference().into_tensor();\n        let mut model = self.tract()?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n\n        let mut output = runtime.prepare(model)?.run(tvec![\n            self.input.clone().into_tvalue(),\n            self.cos.clone().into_tvalue(),\n            self.sin.clone().into_tvalue()\n        ])?;\n        let output = output.remove(0).into_tensor();\n\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<ApplyRopeProblem<f32>>(\"proptest_f32\", ());\n    suite.add_arbitrary::<ApplyRopeProblem<f16>>(\"proptest_f16\", ());\n\n    suite.add(\n        \"trivial_f32_0\",\n        ApplyRopeProblem {\n            input: tensor2(&[[0f32, 1f32]]).into_plain_array::<f32>().unwrap(),\n            cos: tensor2(&[[0f32, 0f32]]).into_plain_array::<f32>().unwrap(),\n            sin: tensor2(&[[0f32, 0f32]]).into_plain_array::<f32>().unwrap(),\n        },\n    );\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/bin_einsum.rs",
    "content": "use std::{fmt, ops::Mul};\n\nuse infra::{Test, TestResult, TestSuite};\nuse proptest::prelude::*;\nuse proptest::strategy::BoxedStrategy;\nuse tract_core::internal::*;\nuse tract_ndarray::{ArrayD, Axis, Dimension};\n\nuse tract_core::ops::einsum::EinSum;\nuse tract_num_traits::{One, Zero};\n\n#[derive(Debug, Clone)]\npub struct BinEinsumProblemParams {\n    pub force_unique_non_trivial_m_n: bool,\n    pub no_trivial_axes: bool,\n    pub force_max_one_iter_axis: bool,\n    pub max_dims: usize,\n}\n\nimpl Default for BinEinsumProblemParams {\n    fn default() -> BinEinsumProblemParams {\n        BinEinsumProblemParams {\n            force_unique_non_trivial_m_n: false,\n            no_trivial_axes: false,\n            force_max_one_iter_axis: false,\n            max_dims: 8,\n        }\n    }\n}\n\n#[derive(Clone)]\npub struct BinEinsumProblem {\n    expr: AxesMapping,\n    a: Tensor,\n    b: Tensor,\n    a_constant: bool,\n    b_constant: bool,\n    unicast_add_constant: Option<Tensor>,\n}\n\nimpl std::fmt::Debug for BinEinsumProblem {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(\n            f,\n            \"{} A:{:?} B:{:?} a_constant:{:?} b_constant:{:?} unicast_add_constant:{:?}\",\n            self.expr, self.a, self.b, self.a_constant, self.b_constant, self.unicast_add_constant\n        )\n    }\n}\n\nimpl Arbitrary for BinEinsumProblem {\n    type Parameters = BinEinsumProblemParams;\n    type Strategy = BoxedStrategy<BinEinsumProblem>;\n\n    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {\n        let supp_m_n_axes_range =\n            if params.force_unique_non_trivial_m_n { 0..1usize } else { 0..2usize };\n        assert!(params.max_dims >= 3);\n        let remaining = params.max_dims - 3; // At least 1 m, and n\n\n        supp_m_n_axes_range\n            .clone()\n            .prop_flat_map(move |supp_m_axes| {\n                let remaining = remaining - supp_m_axes;\n                let n_axes_range = if remaining < supp_m_n_axes_range.end {\n                    0..(remaining + 1)\n                } else {\n                    supp_m_n_axes_range.clone()\n                };\n                let iter_axes_range =\n                    if params.force_max_one_iter_axis { 0..2usize } else { 0..3usize };\n                n_axes_range.prop_flat_map(move |supp_n_axes| {\n                    let remaining = remaining - supp_n_axes;\n                    let iter_axes_range = if remaining < iter_axes_range.end {\n                        0..(remaining + 1)\n                    } else {\n                        iter_axes_range.clone()\n                    };\n                    iter_axes_range.clone().prop_flat_map(move |iter_axes| {\n                        let remaining = remaining - iter_axes;\n                        let trivial_m_n_axes_range =\n                            if params.no_trivial_axes { 0..1usize } else { 0..2usize };\n                        let trivial_m_axes_range = if remaining < trivial_m_n_axes_range.end {\n                            0..(remaining + 1)\n                        } else {\n                            trivial_m_n_axes_range.clone()\n                        };\n                        trivial_m_axes_range.clone().prop_flat_map(move |trivial_m_axes| {\n                            let remaining = remaining - trivial_m_axes;\n                            let trivial_n_axes_range = if remaining < trivial_m_n_axes_range.end {\n                                0..(remaining + 1)\n                            } else {\n                                trivial_m_n_axes_range.clone()\n                            };\n                            trivial_n_axes_range.clone().prop_flat_map(move |trivial_n_axes| {\n                                Just((\n                                    supp_m_axes,\n                                    supp_n_axes,\n                                    iter_axes,\n                                    trivial_m_axes,\n                                    trivial_n_axes,\n                                ))\n                            })\n                        })\n                    })\n                })\n            })\n            .prop_map(|(supp_m_axes, supp_n_axes, iter_axes, trivial_m_axes, trivial_n_axes)| {\n                let m_axes: String = ('a'..).take(supp_m_axes).collect();\n                let trivial_m_axes: String = ('e'..).take(trivial_m_axes).collect();\n                let n_axes: String = ('h'..).take(supp_n_axes).collect();\n                let trivial_n_axes: String = ('o'..).take(trivial_n_axes).collect();\n                let iter_axes: String = ('w'..).take(iter_axes).collect();\n                let a_axes: Vec<char> =\n                    (m_axes.clone() + \"m\" + &trivial_m_axes + &iter_axes + \"k\").chars().collect();\n                let b_axes: Vec<char> =\n                    (n_axes.clone() + \"n\" + &trivial_n_axes + &iter_axes + \"k\").chars().collect();\n                let c_axes: Vec<char> =\n                    (m_axes + &n_axes + \"mn\" + &trivial_m_axes + &trivial_n_axes + &iter_axes)\n                        .chars()\n                        .collect();\n                (Just(a_axes), Just(b_axes), Just(c_axes))\n            })\n            .prop_flat_map(|(a, b, c)| (a.prop_shuffle(), b.prop_shuffle(), c.prop_shuffle()))\n            .prop_map(|(a, b, c)| {\n                let a: String = a.into_iter().collect();\n                let b: String = b.into_iter().collect();\n                let c: String = c.into_iter().collect();\n                let expr: AxesMapping = format!(\"{a},{b}->{c}\").parse().unwrap();\n                //eprintln!(\"{expr}\");\n                expr\n            })\n            .prop_flat_map(|expr| {\n                let dims = expr.iter_all_axes().count();\n                (Just(expr), proptest::collection::vec(1..4usize, dims..=dims))\n            })\n            .prop_flat_map(|(expr, axis_dims)| {\n                let shape_a: TVec<usize> = expr\n                    .axes(InOut::In(0))\n                    .map(|axis| {\n                        expr.iter_all_axes()\n                            .position(|x| (x == axis) && !('m'..='v').contains(&axis.repr))\n                            .map(|dim| axis_dims[dim])\n                            .unwrap_or(1)\n                    })\n                    .collect();\n                let shape_b: TVec<usize> = expr\n                    .axes(InOut::In(1))\n                    .map(|axis| {\n                        expr.iter_all_axes()\n                            .position(|x| (x == axis) && !('m'..='v').contains(&axis.repr))\n                            .map(|dim| axis_dims[dim])\n                            .unwrap_or(1)\n                    })\n                    .collect();\n                let shape_output: TVec<usize> = expr\n                    .axes(InOut::Out(0))\n                    .map(|axis| {\n                        expr.iter_all_axes()\n                            .position(|x| (x == axis) && !('m'..='v').contains(&axis.repr))\n                            .map(|dim| axis_dims[dim])\n                            .unwrap_or(1)\n                    })\n                    .collect();\n                let unicast_add_constant = proptest::option::of(tensor(&shape_output));\n                (Just(expr), tensor(&shape_a), tensor(&shape_b), 0..3usize, unicast_add_constant)\n            })\n            .prop_map(|(expr, a, b, a_b_constant, unicast_add_constant)| {\n                let a_constant = (a_b_constant & 0x1) != 0;\n                let b_constant = (a_b_constant & 0x2) != 0;\n                BinEinsumProblem { expr, a, b, a_constant, b_constant, unicast_add_constant }\n            })\n            .boxed()\n    }\n}\n\npub fn tensor(shape: &[usize]) -> BoxedStrategy<Tensor> {\n    let len = shape.iter().product::<usize>();\n    let shape: Vec<usize> = shape.into();\n    proptest::collection::vec((-10i8..=10i8).prop_map(|i| i as f32), len..=len)\n        .prop_map(move |vec| ArrayD::from_shape_vec(shape.clone(), vec).unwrap().into_tensor())\n        .boxed()\n}\n\nimpl BinEinsumProblem {\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n        let a = if self.a_constant {\n            model.add_const(\"a\", self.a.clone())?\n        } else {\n            model.add_source(\"a\", TypedFact::shape_and_dt_of(&self.a))?\n        };\n        let b = if self.b_constant {\n            model.add_const(\"b\", self.b.clone())?\n        } else {\n            model.add_source(\"b\", TypedFact::shape_and_dt_of(&self.b))?\n        };\n\n        let mut output = model.wire_node(\n            \"einsum\",\n            EinSum { axes: self.expr.clone(), operating_dt: f32::datum_type(), q_params: None },\n            &[a, b],\n        )?;\n\n        if let Some(c) = &self.unicast_add_constant {\n            let c = model.add_const(\"c\", c.clone())?;\n            output = model.wire_node(\"add\", tract_core::ops::math::add(), &[output[0], c])?;\n        }\n\n        model.select_output_outlets(&output)?;\n\n        //let test = model.node_by_name(\"einsum\")?.op.as_op().downcast_ref::<EinSum>().unwrap();\n\n        model = model.into_decluttered()?;\n        //let test1 = model.node_by_name(\"einsum\")?.op.as_op().downcast_ref::<EinSum>().unwrap();\n        //dbg!(&test1.axes);\n        Ok(model)\n    }\n\n    fn output_shape(&self) -> TVec<usize> {\n        self.expr\n            .axes(InOut::Out(0))\n            .map(|axis| {\n                let dim_in_a = axis.inputs[0].first().map(|pos| self.a.shape()[*pos]).unwrap_or(1);\n                let dim_in_b = axis.inputs[1].first().map(|pos| self.b.shape()[*pos]).unwrap_or(1);\n                dim_in_a.max(dim_in_b)\n            })\n            .collect()\n    }\n\n    fn reference<Acc: Datum + Copy + Zero + One + Mul<Acc, Output = Acc>>(&self) -> ArrayD<Acc> {\n        let output_shape = self.output_shape();\n\n        let a = self.a.cast_to::<Acc>().unwrap();\n        let b = self.b.cast_to::<Acc>().unwrap();\n\n        let a = a.to_plain_array_view::<Acc>().unwrap();\n        let b = b.to_plain_array_view::<Acc>().unwrap();\n\n        let k_axes: TVec<_> = self\n            .expr\n            .iter_all_axes()\n            .filter(|axis| {\n                axis.outputs[0].is_empty() && axis.inputs[0].len() == 1 && axis.inputs[1].len() == 1\n            })\n            .collect();\n\n        let summing_shape: TVec<usize> = k_axes\n            .iter()\n            .map(|axis| {\n                let dim_in_a = axis.inputs[0].first().map(|pos| self.a.shape()[*pos]).unwrap_or(1);\n                let dim_in_b = axis.inputs[1].first().map(|pos| self.b.shape()[*pos]).unwrap_or(1);\n                dim_in_a.max(dim_in_b)\n            })\n            .collect();\n\n        let output = tract_ndarray::ArrayD::<Acc>::from_shape_fn(&*output_shape, |coords| {\n            let coords = coords.as_array_view();\n            let mut a = a.clone();\n            let mut b = b.clone();\n            for (axis, x) in self.expr.axes(InOut::Out(0)).zip(coords.iter()) {\n                if let Some(pos) = axis.inputs[0].first() {\n                    a.collapse_axis(Axis(*pos), if a.shape()[*pos] > 1 { *x } else { 0 });\n                }\n\n                if let Some(pos) = axis.inputs[1].first() {\n                    b.collapse_axis(Axis(*pos), if b.shape()[*pos] > 1 { *x } else { 0 });\n                }\n            }\n\n            let mut sum: Acc = Acc::zero();\n            for sum_coords in tract_ndarray::indices(&*summing_shape) {\n                let mut a = a.clone();\n                let mut b = b.clone();\n\n                let sum_coords = sum_coords.as_array_view();\n                for (axis, x) in k_axes.iter().zip(sum_coords) {\n                    a.collapse_axis(Axis(axis.inputs[0][0]), *x);\n                    b.collapse_axis(Axis(axis.inputs[1][0]), *x);\n                }\n\n                let product = *a.iter().next().unwrap() * *b.iter().next().unwrap();\n                sum = sum + product;\n            }\n            sum\n        });\n        if let Some(unicast_const) = self.unicast_add_constant.clone() {\n            output + unicast_const.into_plain_array::<Acc>().unwrap()\n        } else {\n            output\n        }\n    }\n}\n\nimpl Test for BinEinsumProblem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.reference::<f32>().into_tensor();\n        //dbg!(&reference);\n        let mut model = self.tract()?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        let mut inputs = tvec![];\n        if !self.a_constant {\n            inputs.push(self.a.clone().into());\n        }\n        if !self.b_constant {\n            inputs.push(self.b.clone().into());\n        }\n        let mut output = runtime.prepare(model)?.run(inputs)?;\n        let output = output.remove(0).into_tensor();\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<BinEinsumProblem>(\"proptest\", BinEinsumProblemParams::default());\n\n    suite.add(\n        \"unicast_0\",\n        BinEinsumProblem {\n            expr: \"ak,gk->ag\".parse().unwrap(),\n            a: Tensor::zero::<f32>(&[1, 2]).unwrap(),\n            b: Tensor::zero::<f32>(&[1, 2]).unwrap(),\n            a_constant: false,\n            b_constant: false,\n            unicast_add_constant: Some(Tensor::zero::<f32>(&[1, 1]).unwrap()),\n        },\n    );\n\n    suite.add(\n        \"unicast_1\",\n        BinEinsumProblem {\n            expr: \"ak,gk->ag\".parse().unwrap(),\n            a: Tensor::zero::<f32>(&[2, 1]).unwrap(),\n            b: Tensor::zero::<f32>(&[2, 1]).unwrap(),\n            a_constant: false,\n            b_constant: false,\n            unicast_add_constant: Some(tensor2(&[[0f32, 0.], [0., 1.]])),\n        },\n    );\n\n    suite.add(\n        \"unicast_2\",\n        BinEinsumProblem {\n            expr: \"abk,gk->abg\".parse().unwrap(),\n            a: Tensor::zero::<f32>(&[2, 2, 1]).unwrap(),\n            b: Tensor::zero::<f32>(&[1, 1]).unwrap(),\n            a_constant: false,\n            b_constant: false,\n            unicast_add_constant: Some(tensor3(&[[[0f32], [0.]], [[0.], [1.]]])),\n        },\n    );\n\n    suite.add(\n        \"trivial_0\",\n        BinEinsumProblem {\n            expr: \"ak,gk->ag\".parse().unwrap(),\n            a: tensor2(&[[1f32]]),\n            b: tensor2(&[[0f32], [1f32]]),\n            a_constant: false,\n            b_constant: false,\n            unicast_add_constant: None,\n        },\n    );\n\n    suite.add(\n        \"trivial_1\",\n        BinEinsumProblem {\n            expr: \"akb,gk->gba\".parse().unwrap(),\n            a: tensor3(&[[[0f32], [0f32]]]),\n            b: tensor2(&[[0f32, 0f32]]),\n            a_constant: true,\n            b_constant: false,\n            unicast_add_constant: None,\n        },\n    );\n\n    suite.add(\n        \"supp_axis_bug_0\",\n        BinEinsumProblem {\n            expr: \"bmk, abkn->bn\".parse().unwrap(),\n            a: Tensor::zero::<f32>(&[32, 1, 25]).unwrap(),\n            b: Tensor::zero::<f32>(&[1, 32, 25, 64]).unwrap(),\n            a_constant: false,\n            b_constant: false,\n            unicast_add_constant: None,\n        },\n    );\n\n    suite.add(\n        \"m_axis_select_bug_0\",\n        BinEinsumProblem {\n            expr: \"wmkx,wknx->xmnw\".parse().unwrap(),\n            a: tensor4(&[[[[0f32, 0f32], [0f32, -1f32]]]]),\n            b: tensor4(&[[[[0f32, 0f32]], [[0f32, 1f32]]]]),\n            a_constant: false,\n            b_constant: false,\n            unicast_add_constant: None,\n        },\n    );\n\n    suite.add(\n        \"cuda_binary_bug_with_bias\",\n        BinEinsumProblem {\n            expr: \"mewk,owkn->wnemo\".parse()?,\n            a: Tensor::zero::<f32>(&[1, 3, 1, 2])?,\n            b: Tensor::zero::<f32>(&[1, 1, 2, 1])?,\n            a_constant: false,\n            b_constant: false,\n            unicast_add_constant: Some(tensor1(&[0f32, 0f32, 1f32]).into_shape(&[1, 1, 3, 1, 1])?),\n        },\n    );\n\n    // TODO: fix ensure_mkn() to handle multiple n axes\n    //suite.add(\n    //    \"multiple_n_axes\",\n    //    BinEinsumProblem {\n    //        expr: \"kwa,gkwh->gahw\".parse().unwrap(),\n    //        a: Tensor::zero::<f32>(&[1, 2, 1]).unwrap(),\n    //        b: Tensor::zero::<f32>(&[2, 1, 2, 2]).unwrap(),\n    //        a_constant: false,\n    //        b_constant: false,\n    //        unicast_add_constant: None,\n    //    }\n    //);\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/binary.rs",
    "content": "use std::ops::Div;\n\nuse super::*;\nuse infra::{Test, TestResult};\nuse proptest::collection::vec;\nuse tract_core::ndarray::ArrayD;\nuse tract_core::num_traits::{AsPrimitive, FromPrimitive, Num, ToPrimitive};\nuse tract_core::ops::binary::TypedBinOp;\nuse tract_core::ops::logic;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]\npub enum BinOps {\n    Mul,\n    Add,\n    Div,\n    Sub,\n    Pow,\n    Less,\n    LessEqual,\n    Greater,\n    GreaterEqual,\n    Equals,\n    NotEquals,\n}\n\npub const ALL_OPS: [BinOps; 11] = [\n    BinOps::Mul,\n    BinOps::Add,\n    BinOps::Div,\n    BinOps::Sub,\n    BinOps::Pow,\n    BinOps::Less,\n    BinOps::LessEqual,\n    BinOps::Greater,\n    BinOps::GreaterEqual,\n    BinOps::Equals,\n    BinOps::NotEquals,\n];\n\nfn to_tract_op(op: BinOps) -> Box<dyn TypedOp> {\n    match op {\n        BinOps::Mul => Box::new(TypedBinOp(Box::new(tract_core::ops::math::Mul), None)),\n        BinOps::Add => Box::new(TypedBinOp(Box::new(tract_core::ops::math::Add), None)),\n        BinOps::Div => Box::new(TypedBinOp(Box::new(tract_core::ops::math::Div), None)),\n        BinOps::Sub => Box::new(TypedBinOp(Box::new(tract_core::ops::math::Sub), None)),\n        BinOps::Pow => Box::new(TypedBinOp(Box::new(tract_core::ops::math::Pow), None)),\n        BinOps::Less => Box::new(TypedBinOp(logic::comp_lt(), None)),\n        BinOps::LessEqual => Box::new(TypedBinOp(logic::comp_lte(), None)),\n        BinOps::Greater => Box::new(TypedBinOp(logic::comp_gt(), None)),\n        BinOps::GreaterEqual => Box::new(TypedBinOp(logic::comp_gte(), None)),\n        BinOps::Equals => Box::new(TypedBinOp(logic::comp_eq(), None)),\n        BinOps::NotEquals => Box::new(TypedBinOp(logic::comp_ne(), None)),\n    }\n}\n\npub trait SupportedElement:\n    Datum\n    + Num\n    + Copy\n    + FromPrimitive\n    + ToPrimitive\n    + 'static\n    + Div<Output = Self>\n    + PartialOrd\n    + AsPrimitive<usize>\n    + AsPrimitive<f32>\n    + AsPrimitive<f64>\n{\n}\n\nimpl<T> SupportedElement for T where\n    T: Datum\n        + Num\n        + Copy\n        + FromPrimitive\n        + ToPrimitive\n        + 'static\n        + Div<Output = Self>\n        + PartialOrd\n        + AsPrimitive<usize>\n        + AsPrimitive<f32>\n        + AsPrimitive<f64>\n{\n}\n\n#[derive(Debug, Clone)]\npub struct BinaryOpProblem<T>\nwhere\n    T: SupportedElement,\n{\n    pub op: BinOps,\n    pub lhs: ArrayD<T>,\n    pub rhs: ArrayD<T>,\n}\n\nimpl<T> Arbitrary for BinaryOpProblem<T>\nwhere\n    T: SupportedElement,\n{\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n\n    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {\n        let lhs_shape_strat = prop::collection::vec(1usize..=5, 0..=4);\n        lhs_shape_strat\n            .prop_flat_map(|lhs_shape| {\n                let rank = lhs_shape.len();\n                let rhs_shape_strat = prop::collection::vec(1usize..=5, rank..=rank);\n                (Just(lhs_shape), rhs_shape_strat)\n            })\n            .prop_flat_map(|(mut lhs_shape, rhs_shape)| {\n                for idx in 0..lhs_shape.len() {\n                    if (lhs_shape[idx] != rhs_shape[idx])\n                        && lhs_shape[idx] != 1\n                        && rhs_shape[idx] != 1\n                    {\n                        lhs_shape[idx] = rhs_shape[idx]\n                    }\n                }\n\n                let lhs_len = lhs_shape.iter().product::<usize>();\n                let rhs_len = rhs_shape.iter().product::<usize>();\n                let lhs = vec(\n                    (2u8..=10u8).prop_map(|i| T::from_u8(i).unwrap() / T::from_u8(2).unwrap()),\n                    lhs_len..=lhs_len,\n                )\n                .prop_map(move |vec| ArrayD::from_shape_vec(lhs_shape.to_vec(), vec).unwrap())\n                .boxed();\n                let rhs = vec(\n                    (2u8..=10u8).prop_map(|i| T::from_u8(i).unwrap() / T::from_u8(2).unwrap()),\n                    rhs_len..=rhs_len,\n                )\n                .prop_map(move |vec| ArrayD::from_shape_vec(rhs_shape.to_vec(), vec).unwrap())\n                .boxed();\n\n                let mut ops = ALL_OPS.to_vec();\n\n                // Avoid Sub overfow for uints and Unsupported type for Pow\n                if std::any::TypeId::of::<T>() == std::any::TypeId::of::<u8>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<u16>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<u32>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<u64>()\n                {\n                    ops.retain(|op| !matches!(op, BinOps::Sub | BinOps::Pow));\n                }\n\n                if std::any::TypeId::of::<T>() == std::any::TypeId::of::<i8>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<i16>()\n                {\n                    ops.retain(|op| !matches!(op, BinOps::Pow));\n                }\n                let op_strategy = prop::sample::select(ops);\n\n                (lhs, rhs, op_strategy)\n            })\n            .prop_map(|(lhs, rhs, op)| BinaryOpProblem { lhs, rhs, op })\n            .boxed()\n    }\n}\n\nfn eval_reference<FI: Datum, FO: Datum>(\n    a: &Tensor,\n    b: &Tensor,\n    cab: impl Fn(&mut FO, &FI, &FI),\n) -> TractResult<Tensor> {\n    let out_shape = tract_core::broadcast::multi_broadcast(&[a.shape(), b.shape()])?;\n    let mut out = unsafe { Tensor::uninitialized_dt(FO::datum_type(), &out_shape)? };\n    let a_view = a.to_plain_array_view::<FI>()?;\n    let b_view = b.to_plain_array_view::<FI>()?;\n    let mut c_plain = out.try_as_plain_mut()?;\n    let mut c = c_plain.to_array_view_mut::<FO>()?;\n    tract_core::ndarray::Zip::from(&mut c)\n        .and_broadcast(a_view)\n        .and_broadcast(b_view)\n        .for_each(cab);\n    Ok(out)\n}\n\nimpl<T> BinaryOpProblem<T>\nwhere\n    T: SupportedElement,\n{\n    pub fn reference(&self) -> TractResult<Tensor> {\n        let lhs = self.lhs.clone().into_tensor();\n        let rhs = self.rhs.clone().into_tensor();\n\n        let res = match self.op {\n            BinOps::Add => eval_reference(&lhs, &rhs, |c: &mut T, a: &T, b: &T| *c = *a + *b)?,\n            BinOps::Sub => eval_reference(&lhs, &rhs, |c: &mut T, a: &T, b: &T| *c = *a - *b)?,\n            BinOps::Mul => eval_reference(&lhs, &rhs, |c: &mut T, a: &T, b: &T| *c = *a * *b)?,\n            BinOps::Div => eval_reference(&lhs, &rhs, |c: &mut T, a: &T, b: &T| *c = *a / *b)?,\n            BinOps::Pow => eval_reference(&lhs, &rhs, |c: &mut T, a: &T, b: &T| {\n                *c = T::from_f32(a.to_f32().unwrap().powf(b.to_f32().unwrap())).unwrap()\n            })?,\n            BinOps::Less => eval_reference(&lhs, &rhs, |c: &mut bool, a: &T, b: &T| *c = *a < *b)?,\n            BinOps::LessEqual => {\n                eval_reference(&lhs, &rhs, |c: &mut bool, a: &T, b: &T| *c = *a <= *b)?\n            }\n            BinOps::Greater => {\n                eval_reference(&lhs, &rhs, |c: &mut bool, a: &T, b: &T| *c = *a > *b)?\n            }\n            BinOps::GreaterEqual => {\n                eval_reference(&lhs, &rhs, |c: &mut bool, a: &T, b: &T| *c = *a >= *b)?\n            }\n            BinOps::Equals => {\n                eval_reference(&lhs, &rhs, |c: &mut bool, a: &T, b: &T| *c = *a == *b)?\n            }\n            BinOps::NotEquals => {\n                eval_reference(&lhs, &rhs, |c: &mut bool, a: &T, b: &T| *c = *a != *b)?\n            }\n        };\n        Ok(res)\n    }\n\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n\n        let lhs =\n            model.add_source(\"lhs\", TypedFact::shape_and_dt_of(&self.lhs.clone().into_tensor()))?;\n        let rhs =\n            model.add_source(\"rhs\", TypedFact::shape_and_dt_of(&self.rhs.clone().into_tensor()))?;\n\n        let output = model.wire_node(\"bin_op\", to_tract_op(self.op), &[lhs, rhs])?;\n        model.select_output_outlets(&output)?;\n\n        model = model.into_decluttered()?;\n        Ok(model)\n    }\n}\n\nimpl<T> Test for BinaryOpProblem<T>\nwhere\n    T: SupportedElement,\n{\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.reference()?;\n        let mut model = self.tract()?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n\n        let mut output = runtime\n            .prepare(model)?\n            .run(tvec![self.lhs.clone().into_tvalue(), self.rhs.clone().into_tvalue()])?;\n        let output = output.remove(0).into_tensor();\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<BinaryOpProblem<f32>>(\"proptest_f32\", ());\n    suite.add_arbitrary::<BinaryOpProblem<f16>>(\"proptest_f16\", ());\n    suite.add_arbitrary::<BinaryOpProblem<u8>>(\"proptest_u8\", ());\n    suite.add_arbitrary::<BinaryOpProblem<i8>>(\"proptest_i8\", ());\n    suite.add_arbitrary::<BinaryOpProblem<u32>>(\"proptest_u32\", ());\n    suite.add_arbitrary::<BinaryOpProblem<i16>>(\"proptest_i16\", ());\n    suite.add_arbitrary::<BinaryOpProblem<i64>>(\"proptest_i64\", ());\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/conv_f16.rs",
    "content": "use super::*;\nuse crate::conv_f32::{ConvProblem, ConvProblemParams};\nuse infra::*;\nuse tract_core::tract_data::half::f16;\n\n#[derive(Debug, Clone)]\npub struct ConvProblemF16(pub ConvProblem);\n\nimpl ConvProblemF16 {\n    fn tract(&self) -> TractResult<TypedModel> {\n        let inner = &self.0;\n        assert_eq!(inner.data.shape(), &*inner.shape_in.shape, \"inconsistent shapes in test\");\n        let mut model = TypedModel::default();\n        let wire = model.add_source(\"input\", f16::fact(&inner.shape_in.shape))?;\n        let ci = *inner.shape_in.c();\n        let co = match inner.kernel_format {\n            KernelFormat::OIHW => inner.kernel.shape()[0],\n            KernelFormat::HWIO => inner.kernel.shape()[inner.kernel.ndim() - 1] * inner.group,\n            KernelFormat::OHWI => inner.kernel.shape()[0] * inner.group,\n        };\n        let kernel_f16 = inner.kernel.mapv(f16::from_f32).into_arc_tensor();\n        let kernel = model.add_const(\"kernel\", kernel_f16)?;\n        let bias_f16 = if let Some(bias) = &inner.bias {\n            bias.mapv(f16::from_f32).into_arc_tensor()\n        } else {\n            rctensor0(f16::from_f32(0.0))\n        };\n        let bias = model.add_const(\"bias\", bias_f16)?;\n        let op = Conv::new(\n            PoolSpec::new(\n                inner.shape_in.fmt,\n                inner.geo_ker().into(),\n                inner.pad.clone(),\n                Some(inner.dilations.clone()),\n                Some(inner.strides.clone()),\n                ci,\n                co,\n            ),\n            inner.kernel_format,\n            inner.group,\n            None,\n        );\n        let wire = model.wire_node(\"conv\", op, &[wire, kernel, bias])?[0];\n        model.select_output_outlets(&[wire])?;\n        Ok(model)\n    }\n}\n\nimpl Arbitrary for ConvProblemF16 {\n    type Parameters = ConvProblemParams;\n    type Strategy = BoxedStrategy<ConvProblemF16>;\n    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {\n        ConvProblem::arbitrary_with(params).prop_map(ConvProblemF16).boxed()\n    }\n}\n\nimpl Test for ConvProblemF16 {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.0.reference().into_tensor();\n        let mut model = self.tract()?;\n        model.declutter()?;\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        let input_f16 = self.0.data.mapv(f16::from_f32).into_tensor();\n        let mut output = runtime.prepare(model)?.run(tvec![input_f16.into_tvalue()])?;\n        let output = output.remove(0).into_tensor();\n        // Cast output back to f32 for comparison with f32 reference\n        let output_f32 = output.cast_to::<f32>()?.into_owned();\n        output_f32.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n    suite.add_arbitrary::<ConvProblemF16>(\"proptest\", ConvProblemParams::default());\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/conv_f32.rs",
    "content": "use std::ops::Range;\n\nuse super::*;\nuse infra::*;\nuse proptest::collection::vec;\nuse tract_itertools::izip;\n\n#[derive(Debug, Clone, Default)]\npub struct ConvProblemParams {\n    pub no_group: bool,\n    pub no_stride: bool,\n    pub no_arbitrary_grouping: bool,\n    pub geo_rank: Option<Range<usize>>,\n    pub no_batch: bool,\n    pub no_dilations: bool,\n    pub no_bias: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct ConvProblem {\n    pub shape_in: DataShape,\n    pub kernel_format: KernelFormat,\n    pub group: usize,\n    pub data: ArrayD<f32>,\n    pub kernel: ArrayD<f32>,\n    pub bias: Option<ArrayD<f32>>,\n    pub pad: PaddingSpec,\n    pub strides: TVec<usize>,\n    pub dilations: TVec<usize>,\n}\n\nimpl ConvProblem {\n    pub fn geo_ker(&self) -> &[usize] {\n        &self.kernel.shape()[self.kernel_format.h_axis()..][..self.shape_in.hw_rank()]\n    }\n\n    pub fn reference(&self) -> ArrayD<f32> {\n        // dbg!(self);\n        assert_eq!(self.data.shape(), &*self.shape_in.shape, \"inconsistent shapes in test\");\n        let n = *self.shape_in.n().unwrap_or(&1);\n        let ci_per_g = self.shape_in.c() / self.group;\n        let co_per_g = match self.kernel_format {\n            KernelFormat::OIHW => self.kernel.shape()[0] / self.group,\n            KernelFormat::HWIO => self.kernel.shape()[self.kernel.ndim() - 1],\n            KernelFormat::OHWI => self.kernel.shape()[0],\n        };\n        assert_eq!(self.strides.len(), self.geo_ker().len());\n        assert_eq!(self.dilations.len(), self.geo_ker().len());\n        let (shape_out, left_pads): (TVec<_>, TVec<_>) = match &self.pad {\n            PaddingSpec::Valid => {\n                izip!(self.shape_in.hw_dims(), self.geo_ker(), &self.strides, &self.dilations)\n                    .map(|(i, k, s, d)| {\n                        let kf = (k - 1) * d + 1;\n                        let out = (*i + 1).saturating_sub(kf).divceil(*s);\n                        (out, 0)\n                    })\n                    .unzip()\n            }\n            PaddingSpec::SameUpper => {\n                izip!(self.shape_in.hw_dims(), self.geo_ker(), &self.strides, &self.dilations)\n                    .map(|(input, k, stride, d)| {\n                        let kf = (k - 1) * d + 1;\n                        let out = input.divceil(*stride);\n                        let pad = ((out - 1) * stride + kf).saturating_sub(*input);\n                        (out, pad / 2)\n                    })\n                    .unzip()\n            }\n            PaddingSpec::SameLower => {\n                izip!(self.shape_in.hw_dims(), self.geo_ker(), &self.strides, &self.dilations)\n                    .map(|(input, k, stride, d)| {\n                        let kf = (k - 1) * d + 1;\n                        let out = input.divceil(*stride);\n                        let pad = ((out - 1) * stride + kf).saturating_sub(*input);\n                        (out, pad.divceil(2))\n                    })\n                    .unzip()\n            }\n            PaddingSpec::Explicit(l, r) => {\n                izip!(self.shape_in.hw_dims(), self.geo_ker(), &self.strides, &self.dilations, l, r)\n                    .map(|(input, k, stride, d, l, r)| {\n                        let kf = (k - 1) * d + 1;\n                        let out = (input + l + r).saturating_sub(kf) / *stride + 1;\n                        (out, *l)\n                    })\n                    .unzip()\n            }\n            PaddingSpec::ExplicitOnnxPool(l, r, ceil) => {\n                izip!(self.shape_in.hw_dims(), self.geo_ker(), &self.strides, &self.dilations, l, r)\n                    .map(|(input, k, stride, d, l, r)| {\n                        let kf = (k - 1) * d + 1;\n                        let mut out = if *ceil {\n                            (input + l + r).saturating_sub(kf).divceil(*stride) + 1\n                        } else {\n                            (input + l + r).saturating_sub(kf) / *stride + 1\n                        };\n\n                        // pytorch semantics diverge from onnx (and onnx are super weird)\n                        // https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/Pool.h#L48C2-L54C6\n                        if *ceil && (out - 1) * stride >= input + l {\n                            out -= 1;\n                        }\n                        (out, *l)\n                    })\n                    .unzip()\n            }\n        };\n        let shape_out = self\n            .shape_in\n            .fmt\n            .from_n_c_hw(self.shape_in.n().cloned().unwrap_or(1), co_per_g * self.group, shape_out)\n            .unwrap();\n        // dbg!(&shape_out);\n        let mut out = ArrayD::zeros(&*shape_out.shape);\n        for n in 0..n {\n            for g in 0..self.group {\n                for geo_out in indices(shape_out.hw_dims()) {\n                    let mut output_coords: TVec<usize> = geo_out.slice().into();\n                    if self.shape_in.fmt.has_n() {\n                        output_coords.insert(0, n);\n                    }\n                    output_coords.insert(shape_out.c_axis(), 0);\n                    for geo_ker in indices(self.geo_ker()) {\n                        let input_coords: TVec<isize> = izip!(\n                            geo_out.slice(),\n                            geo_ker.slice(),\n                            &left_pads,\n                            &self.strides,\n                            &self.dilations\n                        )\n                        .map(|(out, ker, pad, stride, dil)| {\n                            *out as isize * *stride as isize + (ker * dil) as isize - *pad as isize\n                        })\n                        .collect();\n                        if izip!(&input_coords, self.shape_in.hw_dims())\n                            .any(|(c, i)| *c < 0 || *c >= *i as isize)\n                        {\n                            continue;\n                        }\n                        let mut input_coords: TVec<usize> =\n                            input_coords.into_iter().map(|d| d as usize).collect();\n                        if self.shape_in.fmt.has_n() {\n                            input_coords.insert(0, n);\n                        }\n                        input_coords.insert(self.shape_in.c_axis(), 0);\n                        for ci in 0..ci_per_g {\n                            input_coords[self.shape_in.c_axis()] = ci + g * ci_per_g;\n                            let i = self.data[&*input_coords];\n                            for co in 0..co_per_g {\n                                output_coords[shape_out.c_axis()] = co + g * co_per_g;\n                                let mut kernel_coords: TVec<usize> = geo_ker.slice().into();\n                                match self.kernel_format {\n                                    KernelFormat::OIHW => {\n                                        kernel_coords.insert(0, ci);\n                                        kernel_coords.insert(0, co + g * co_per_g);\n                                    }\n                                    KernelFormat::HWIO => {\n                                        kernel_coords.push(ci + g * ci_per_g);\n                                        kernel_coords.push(co);\n                                    }\n                                    KernelFormat::OHWI => {\n                                        kernel_coords.insert(0, co);\n                                        kernel_coords.push(ci + g * ci_per_g);\n                                    }\n                                }\n                                let k = self.kernel[&*kernel_coords];\n                                out[&*output_coords] += k * i;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        if let Some(bias) = &self.bias {\n            let mut shape = vec![1; out.ndim()];\n            shape[shape_out.c_axis()] = bias.len();\n            out += &bias.clone().into_shape_with_order(shape).unwrap();\n        }\n        out\n    }\n\n    pub fn tract(&self) -> TractResult<TypedModel> {\n        assert_eq!(self.data.shape(), &*self.shape_in.shape, \"inconsistent shapes in test\");\n        let mut model = TypedModel::default();\n        let wire = model.add_source(\"input\", f32::fact(&self.shape_in.shape))?;\n        let ci = *self.shape_in.c();\n        let co = match self.kernel_format {\n            KernelFormat::OIHW => self.kernel.shape()[0],\n            KernelFormat::HWIO => self.kernel.shape()[self.kernel.ndim() - 1] * self.group,\n            KernelFormat::OHWI => self.kernel.shape()[0] * self.group,\n        };\n        let kernel = model.add_const(\"kernel\", self.kernel.clone().into_arc_tensor())?;\n        let bias = if let Some(bias) = &self.bias {\n            bias.clone().into_arc_tensor()\n        } else {\n            rctensor0(0f32)\n        };\n        let bias = model.add_const(\"bias\", bias)?;\n        let op = Conv::new(\n            PoolSpec::new(\n                self.shape_in.fmt,\n                self.geo_ker().into(),\n                self.pad.clone(),\n                Some(self.dilations.clone()),\n                Some(self.strides.clone()),\n                ci,\n                co,\n            ),\n            self.kernel_format,\n            self.group,\n            None,\n        );\n        let wire = model.wire_node(\"conv\", op, &[wire, kernel, bias])?[0];\n        model.select_output_outlets(&[wire])?;\n        Ok(model)\n    }\n}\n\nimpl Arbitrary for ConvProblem {\n    type Parameters = ConvProblemParams;\n    type Strategy = BoxedStrategy<ConvProblem>;\n    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {\n        let batch_range = if params.no_batch { 1usize..=1 } else { 1usize..=3 };\n        let geo_rank = params.geo_rank.unwrap_or(1..4);\n        (\n            data_format(),\n            kernel_format(),\n            prop_oneof![Just(PaddingSpec::Valid), Just(PaddingSpec::SameUpper)],\n            batch_range,\n            1usize..=4,\n            1usize..=4,\n            1usize..=(if params.no_group { 1 } else { 3 }),\n            geo_rank.prop_flat_map(shapes),\n        )\n            .prop_flat_map(\n                move |(\n                    df,\n                    kf,\n                    pad,\n                    batch,\n                    mut ci0,\n                    mut co0,\n                    group,\n                    (mut ker_shape, data_shape),\n                )| {\n                    // FIXME in HWIO order, only regular and depthwise are supported\n                    if params.no_arbitrary_grouping && group > 1 {\n                        ci0 = 1;\n                        co0 = 1;\n                    }\n                    if kf == KernelFormat::HWIO && group > 1 {\n                        ci0 = 1;\n                    }\n                    let shape_in = df.from_n_c_hw(batch, ci0 * group, data_shape).unwrap();\n                    match kf {\n                        KernelFormat::HWIO => {\n                            ker_shape.push(ci0 * group);\n                            ker_shape.push(co0)\n                        }\n                        KernelFormat::OIHW => {\n                            ker_shape.insert(0, ci0);\n                            ker_shape.insert(0, co0 * group)\n                        }\n                        KernelFormat::OHWI => {\n                            ker_shape.insert(0, co0);\n                            ker_shape.push(ci0 * group);\n                        }\n                    };\n                    let hw_rank = shape_in.hw_rank();\n                    let max_stride = if params.no_stride { 1 } else { 3 };\n                    let strides = vec(1usize..=max_stride, hw_rank..=hw_rank);\n                    let max_dil = if params.no_dilations { 1 } else { 3 };\n                    let dilations = vec(1usize..=max_dil, hw_rank..=hw_rank);\n                    (\n                        Just((kf, pad, shape_in, group)),\n                        Just(ker_shape),\n                        Just(co0),\n                        strides,\n                        dilations,\n                    )\n                },\n            )\n            .prop_flat_map(\n                move |((kf, pad, shape_in, group), ker_shape, co0, strides, dilations)| {\n                    let kernel = tensor(&ker_shape);\n                    let data = tensor(&*shape_in.shape);\n                    let bias = if params.no_bias {\n                        Just(None).boxed()\n                    } else {\n                        proptest::option::of(tensor(&[co0 * group])).boxed()\n                    };\n\n                    (\n                        Just((kf, pad, shape_in, group)),\n                        data,\n                        kernel,\n                        bias,\n                        Just(strides),\n                        Just(dilations),\n                    )\n                },\n            )\n            .prop_map(\n                |(\n                    (kernel_format, pad, shape_in, group),\n                    data,\n                    kernel,\n                    bias,\n                    strides,\n                    dilations,\n                )| {\n                    ConvProblem {\n                        shape_in,\n                        kernel_format,\n                        group,\n                        data,\n                        kernel,\n                        bias,\n                        pad,\n                        strides: strides.into(),\n                        dilations: dilations.into(),\n                    }\n                },\n            )\n            .boxed()\n    }\n}\n\nimpl Test for ConvProblem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.reference().into_tensor();\n        // dbg!(&reference);\n        let mut model = self.tract()?;\n        //       dbg!(&model);\n        model.declutter()?;\n        //       dbg!(&model);\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        let mut output = runtime.prepare(model)?.run(tvec![self.data.clone().into_tvalue()])?;\n        let output = output.remove(0).into_tensor();\n        // dbg!(&output);\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<ConvProblem>(\"proptest\", ConvProblemParams::default());\n\n    suite.add(\n        \"trivial_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1, 1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0f32]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0f32]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"trivial_1\",\n        ConvProblem {\n            shape_in: DataFormat::NHWC.from_n_c_hw(1, 1, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[1.0f32]]]).into_dyn(),\n            kernel: arr3(&[[[1.0f32]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n    suite.add(\n        \"trivial_2\",\n        ConvProblem {\n            shape_in: DataFormat::NHWC.from_n_c_hw(1, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[1.0f32], [0.0]]]).into_dyn(),\n            kernel: arr3(&[[[1.0f32]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"trivial_3\",\n        ConvProblem {\n            shape_in: DataFormat::NHWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0f32, 1.0]]]).into_dyn(),\n            kernel: arr3(&[[[0.0f32], [1.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"ker_3x1\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [3, 3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::zeros(vec![1, 1, 3, 3]),\n            kernel: ArrayD::zeros(vec![1, 1, 1, 3]),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"nchw_0\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0f32, 1.0]]]).into_dyn(),\n            kernel: arr3(&[[[1f32]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"nchw_2d_0\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [1, 2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr4(&[[[[0.0f32, 1.0]]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0f32, 1.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"group_0\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr2(&[[0.0f32], [0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0f32]], [[0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"group_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr2(&[[0.0f32, 1.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0f32]], [[1.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"group_3\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr2(&[[0.0f32, 1.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0f32]], [[1.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n    suite.add(\n        \"group_4\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr2(&[[0.0f32, 1.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0f32]], [[0.0]], [[0.0]], [[1.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n    suite.add(\n        \"group_5\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [1, 1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr3(&[[[0.0f32, 1.0]]]).into_dyn(),\n            kernel: tensor4(&[[[[0.0f32]]], [[[0.0]]], [[[0.0]]], [[[0.0]]]])\n                .into_plain_array::<f32>()\n                .unwrap()\n                .into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n    suite.add(\n        \"group_6\",\n        ConvProblem {\n            shape_in: DataFormat::NHWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr3(&[[[0.0f32, 1.0]]]).into_dyn(),\n            kernel: tensor3(&[[[0.0f32]], [[0.0]], [[0.0]], [[0.0]]])\n                .into_plain_array::<f32>()\n                .unwrap()\n                .into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"group_7\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 2, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr3(&[[[0.0f32, 0.0], [0.0, 1.0]]]).into_dyn(),\n            kernel: tensor3(&[[[0.0f32, 0.0]], [[0.0, 0.0]], [[0.0, 0.0]], [[0.0, 1.0]]])\n                .into_plain_array::<f32>()\n                .unwrap()\n                .into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n    suite.add(\n        \"group_8\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 4, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr2(&[[0.0f32, 0.0, 0.0, 1.0]]).into_dyn(),\n            kernel: tensor3(&[[[0.0f32], [0.0]], [[0.0], [0.0]]])\n                .into_plain_array::<f32>()\n                .unwrap()\n                .into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"group_9\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr2(&[[0.0f32, 0.0], [0.0, 1.0]]).into_dyn(),\n            kernel: tensor3(&[[[0.0f32]], [[0.0]], [[0.0]], [[1.0]]])\n                .into_plain_array::<f32>()\n                .unwrap()\n                .into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n    suite.add(\n        \"group_10\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 2, [2, 1, 4])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: ArrayD::<f32>::zeros(vec![2, 2, 1, 4]),\n            kernel: ArrayD::from_elem(vec![4, 1, 1, 1, 2], 1.0f32),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1, 1),\n            dilations: tvec!(1, 1, 1),\n        },\n    );\n    suite.add(\n        \"group_11\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr2(&[[0.0, 1.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0]], [[0.0]], [[0.0]], [[0.0]], [[0.0]], [[0.0]], [[0.0]], [[1.0]]])\n                .into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"group_12\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::HWIO,\n            group: 2,\n            data: arr2(&[[0.0, 0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0], [0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"group_13\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::HWIO,\n            group: 2,\n            data: arr2(&[[0.0, 1.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0], [1.0, 0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"group_bias_0\",\n        ConvProblem {\n            shape_in: DataFormat::NHWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: ArrayD::<f32>::zeros(vec![1, 1, 2]),\n            kernel: ArrayD::<f32>::zeros(vec![4, 1, 1]),\n            bias: Some(ArrayD::<f32>::zeros(vec![4])),\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"group_bias_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 2,\n            data: arr2(&[[0.0, 0.0]]).into_dyn(),\n            kernel: ArrayD::<f32>::zeros(vec![4, 1, 1]),\n            bias: Some(arr1(&[0.0, 0.0, 0.0, 1.0]).into_dyn()),\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"bias_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::<f32>::zeros(vec![2, 1]),\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 2]),\n            bias: Some(ArrayD::<f32>::zeros(vec![1])),\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"bias_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            kernel: ArrayD::<f32>::zeros(vec![2, 1, 2]),\n            data: ArrayD::<f32>::zeros(vec![2, 1]),\n            bias: Some(arr1(&[0.0f32, 1.0]).into_dyn()),\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"bias_2\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [1, 2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 1, 2]),\n            data: ArrayD::<f32>::zeros(vec![1, 1, 1, 2]),\n            bias: Some(arr1(&[1.0]).into_dyn()),\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"bias_dil_0\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [4, 2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 4, 1]),\n            data: ArrayD::<f32>::zeros(vec![1, 1, 4, 2]),\n            bias: Some(arr1(&[1.0]).into_dyn()),\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(2, 1),\n        },\n    );\n\n    suite.add(\n        \"bias_chw_0\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0f32, 0., 0.]]).into_dyn(),\n            kernel: arr3(&[[[0f32]], [[0.]], [[0.]]]).into_dyn(),\n            bias: Some(arr1(&[0f32, 0., 1.]).into_dyn()),\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"batch_0\",\n        ConvProblem {\n            shape_in: DataFormat::NHWC.from_n_c_hw(2, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::<f32>::zeros(vec![2, 2, 1]),\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 2]),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"batch_1\",\n        ConvProblem {\n            shape_in: DataFormat::NHWC.from_n_c_hw(2, 1, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0f32]], [[1.0]]]).into_dyn(),\n            kernel: arr3(&[[[1.0f32]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"batch_2\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(2, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0f32, 0.0]], [[0.0, 0.0]]]).into_dyn(),\n            kernel: arr3(&[[[0.0f32]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"bias_3d_1\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [1, 1, 2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::<f32>::zeros(vec![1, 1, 1, 2]),\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 1, 1, 1]),\n            bias: Some(ArrayD::<f32>::ones(vec![1])),\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1, 1),\n            dilations: tvec!(1, 1, 1),\n        },\n    );\n\n    suite.add(\n        \"batch_3d\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [2, 2, 1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::<f32>::zeros(vec![1, 1, 2, 2, 1]),\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 1, 1, 1]),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1, 1),\n            dilations: tvec!(1, 1, 1),\n        },\n    );\n\n    suite.add(\n        \"same_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::<f32>::zeros(vec![1, 1]),\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 1]),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"same_1d_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::<f32>::zeros(vec![1, 1]),\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 1]),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"same_1d_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0.0], [1.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 2.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"same_2\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2, 2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0], [0.0]], [[0.0], [1.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0], [0.0]]], [[[0.0], [1.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"same_2d_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1, 3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0], [0.0], [1.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0, 0.0, 0.0]]], [[[0.0, 0.0, 1.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"same_2d_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2, 2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0], [0.0]], [[1.0], [0.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0, 1.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"same_2d_3\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [2, 3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"same_2d_4\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [1, 2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr4(&[[[[0.0, 0.0]]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0, 0.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"strides_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0.0], [0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(2),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"strides_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0.0], [0.0], [1.0]]).into_dyn(),\n            kernel: arr3(&[[[1.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(2),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"strides_2_dnn_padding_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [6])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::arr2(&[[0.0], [0.0], [0.0], [0.0], [0.0], [0.0]]).into_dyn(),\n            kernel: tract_ndarray::arr3(&[[[0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(1), tvec!(1)),\n            strides: tvec!(2),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"strides_2_dnn_padding_2\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::arr2(&[[0.0]]).into_dyn(),\n            kernel: tract_ndarray::arr3(&[[[0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(1), tvec!(1)),\n            strides: tvec!(2),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"strides_2_dnn_padding_ceil_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::arr2(&[[0.0]]).into_dyn(),\n            kernel: tract_ndarray::arr3(&[[[0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(0), tvec!(1)),\n            strides: tvec!(2),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"strides_2_dnn_padding_ceil_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::arr2(&[[0.0], [0.0]]).into_dyn(),\n            kernel: tract_ndarray::arr3(&[[[0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(0), tvec!(0)),\n            strides: tvec!(2),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"strides_2\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0.0], [0.0], [1.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0, 1.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(3),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"strides_2d_same_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1, 3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0], [0.0], [1.0]]]).into_dyn(),\n            kernel: arr4(&[[[[1.0, 0.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 2),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"strides_2d_same_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2, 3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::<f32>::zeros(vec![2, 3, 1]),\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 1, 3]),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 2),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"strides_2d_same_2\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2, 3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0], [0.0], [1.0]], [[0.0], [0.0], [0.0]]]).into_dyn(),\n            kernel: arr4(&[[[[1.0, 0.0, 0.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 2),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"strides_2d_same_3\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [1, 1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr4(&[[[[0.0]]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"strides_two_axes\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1, 1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(2, 2),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"strides_one_axis_explicit\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::ArrayD::<f32>::zeros(vec![3, 1]),\n            kernel: tract_ndarray::ArrayD::<f32>::zeros(vec![1, 1, 3]),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(1), tvec!(0)),\n            strides: tvec!(2),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"strides_one_axis_explicit_prime\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::ArrayD::<f32>::zeros(vec![2, 1]),\n            kernel: tract_ndarray::ArrayD::<f32>::zeros(vec![1, 1, 2]),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(0), tvec!(0)),\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"strides_two_axes_explicit\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2, 3])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::ArrayD::<f32>::zeros(vec![2, 3, 1]),\n            kernel: tract_ndarray::ArrayD::<f32>::zeros(vec![1, 1, 2, 3]),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(0, 1), tvec!(0, 0)),\n            strides: tvec!(1, 2),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"lazy_im2col_0\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0.0, 0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    let mut kernel = ArrayD::<f32>::zeros(vec![1, 4, 1, 3, 2]);\n    let len = kernel.len();\n    kernel.as_slice_mut().unwrap()[len - 1] = 1.0;\n    suite.add(\n        \"lazy_im2col_big\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 4, [2, 5, 4])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::<f32>::zeros(vec![4, 2, 5, 4]),\n            kernel,\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(2, 2, 2),\n            dilations: tvec!(1, 1, 1),\n        },\n    );\n\n    let mut kernel = ArrayD::<f32>::zeros(vec![1, 4, 1, 3, 2]);\n    let len = kernel.len();\n    kernel.as_slice_mut().unwrap()[len - 1] = 1.0;\n    suite.add(\n        \"lazy_im2col_big_2\",\n        ConvProblem {\n            shape_in: DataFormat::NHWC.from_n_c_hw(1, 4, [2, 5, 4])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: ArrayD::<f32>::zeros(vec![1, 2, 5, 4, 4]),\n            kernel,\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(2, 3, 2),\n            dilations: tvec!(1, 1, 1),\n        },\n    );\n\n    let mut kernel = ArrayD::<f32>::zeros(vec![2, 2, 2, 1]);\n    let len = kernel.len();\n    kernel.as_slice_mut().unwrap()[len - 1] = 1.0;\n    let mut data = ArrayD::<f32>::zeros(vec![2, 2, 2]);\n    *data.as_slice_mut().unwrap().last_mut().unwrap() = 1.0;\n    suite.add(\n        \"depthwise_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 2, [2, 2])?,\n            kernel_format: KernelFormat::HWIO,\n            group: 2,\n            data,\n            kernel,\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    let data = ArrayD::zeros(vec![2, 1]);\n    let kernel = ArrayD::zeros(vec![1, 1, 1]);\n    suite.add(\n        \"same_upper\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data,\n            kernel,\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1),\n            dilations: tvec!(1,),\n        },\n    );\n    suite.add(\n        \"explicit_dnn_left\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::arr2(&[[0.0]]).into_dyn(),\n            kernel: tract_ndarray::arr3(&[[[0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(2), tvec!(0)),\n            strides: tvec!(1),\n            dilations: tvec!(1,),\n        },\n    );\n\n    suite.add(\n        \"explicit_dnn_right_0\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::arr2(&[[0.0]]).into_dyn(),\n            kernel: tract_ndarray::arr3(&[[[0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(0), tvec!(2)),\n            strides: tvec!(1),\n            dilations: tvec!(1,),\n        },\n    );\n\n    suite.add(\n        \"explicit_dnn_right_1\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [1])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: tract_ndarray::arr2(&[[0.0]]).into_dyn(),\n            kernel: tract_ndarray::arr3(&[[[0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Explicit(tvec!(0), tvec!(1)),\n            strides: tvec!(1),\n            dilations: tvec!(1,),\n        },\n    );\n\n    suite.add(\n        \"dnn_2d\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 2, [1, 1]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr4(&[[[[0.0]], [[1.0]]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0]], [[1.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"group_owhi_0\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 2, [1]).unwrap(),\n            kernel_format: KernelFormat::OHWI,\n            group: 2,\n            data: arr2(&[[0.0], [0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1,),\n        },\n    );\n\n    suite.add(\n        \"dw_k4_0\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 2, [8]).unwrap(),\n            kernel_format: KernelFormat::OHWI,\n            group: 2,\n            data: arr2(&[\n                [0.0f32, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0],\n                [2.0, 3.0, 4.0, 5.0, 2.0, 3.0, 4.0, 5.0],\n            ])\n            .into_dyn(),\n            kernel: arr3(&[[[1.0f32, 2.0], [-1.0, 1.0], [0.0, 3.0], [3.0, 0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1,),\n        },\n    );\n\n    suite.add(\n        \"dw_k4_d2_0\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 2, [6, 2]).unwrap(),\n            kernel_format: KernelFormat::HWIO,\n            group: 2,\n            data: arr3(&[\n                [[0.0f32, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]],\n                [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [1.0, 0.0], [0.0, 0.0]],\n            ])\n            .into_dyn(),\n            kernel: arr4(&[[[[0.0f32], [0.0]], [[0.0], [0.0]]], [[[0.0], [0.0]], [[0.0], [-1.0]]]])\n                .into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"from_1d_to_2d\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [2]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0.0f32, 0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0f32, 0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"trivial_nchw\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [1, 1]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr4(&[[[[0.0f32]]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0f32]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    let mut data = Tensor::zero::<f32>(&[1, 5, 6]).unwrap();\n    *data.try_as_plain_mut().unwrap().as_slice_mut::<f32>().unwrap().last_mut().unwrap() = 1.0;\n    let mut kernel = Tensor::zero::<f32>(&[1, 1, 3, 2]).unwrap();\n    *kernel.try_as_plain_mut().unwrap().as_slice_mut::<f32>().unwrap().last_mut().unwrap() = 1.0;\n    suite.add(\n        \"pack_0\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [5, 6]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: data.into_plain_array::<f32>().unwrap(),\n            kernel: kernel.into_plain_array::<f32>().unwrap(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n        },\n    );\n\n    suite.add(\n        \"pack_1\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [5]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0.0f32, -2.0, 0.0, 0.0, 0.0]]).into_dyn(),\n            kernel: arr3(&[[[1f32]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"dil_0\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[1.]]).into_dyn(),\n            kernel: arr3(&[[[1.]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(2),\n        },\n    );\n\n    suite.add(\n        \"dil_1\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [2, 3]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0, 0.0, 0.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 2),\n        },\n    );\n\n    suite.add(\n        \"dil_2\",\n        ConvProblem {\n            shape_in: DataFormat::HWC.from_n_c_hw(1, 1, [2]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0.0], [0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(2),\n        },\n    );\n\n    suite.add(\n        \"dil_3\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 2, [1, 4]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0, 0.0, 0.0, 0.0]], [[0.0, 0.0, 0.0, -1.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0, 0.0, 0.0, 0.0]], [[0.0, 0.0, 1.0, 0.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::SameUpper,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 3),\n        },\n    );\n\n    suite.add(\n        \"dil_4\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [2]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0.0, 0.0]]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(2),\n        },\n    );\n\n    suite.add(\n        \"dil_5\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [2]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr2(&[[0.0, 0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(2),\n        },\n    );\n\n    suite.add(\n        \"dil_6\",\n        ConvProblem {\n            shape_in: DataFormat::CHW.from_n_c_hw(1, 1, [1, 2]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[8.0, 3.0]]]).into_dyn(),\n            kernel: arr4(&[[[[1.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 2),\n        },\n    );\n\n    suite.add(\n        \"dil_7\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [1, 1]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr4(&[[[[0f32]]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1, 1),\n            dilations: tvec!(2, 2),\n        },\n    );\n\n    suite.add(\n        \"dil_and_stride_2d\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [1, 1]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr4(&[[[[0f32]]]]).into_dyn(),\n            kernel: arr4(&[[[[1.0]]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(3, 3),\n            dilations: tvec!(2, 2),\n        },\n    );\n\n    suite.add(\n        \"bug_metal_0\",\n        ConvProblem {\n            shape_in: DataFormat::NHWC.from_n_c_hw(2, 1, [4]).unwrap(),\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            data: arr3(&[[[0f32], [0.], [0.], [0.]], [[0.], [0.], [0.], [1.]]]).into_dyn(),\n            kernel: arr3(&[[[0f32]], [[1.]]]).into_dyn(),\n            bias: None,\n            pad: PaddingSpec::Valid,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n        },\n    );\n\n    suite.add(\n        \"bug_cuda_0\",\n        ConvProblem {\n            shape_in: DataFormat::NCHW.from_n_c_hw(1, 1, [3, 2])?,\n            kernel_format: KernelFormat::OIHW,\n            group: 1,\n            kernel: ArrayD::<f32>::zeros(vec![1, 1, 3, 1]),\n            data: ArrayD::<f32>::zeros(vec![1, 1, 3, 2]),\n            bias: Some(arr1(&[1.0]).into_dyn()),\n            pad: PaddingSpec::Valid,\n            strides: tvec!(3, 1),\n            dilations: tvec!(3, 1),\n        },\n    );\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/conv_q.rs",
    "content": "use infra::{Test, TestSuite};\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ops::cnn::KernelFormat::*;\nuse tract_core::ops::cnn::{Conv, KernelFormat, PaddingSpec, PoolSpec};\nuse tract_core::ops::math::round_ties_to_even;\nuse tract_core::ops::nn::DataFormat::*;\nuse tract_core::ops::nn::DataShape;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_itertools::izip;\nuse tract_ndarray::*;\n\nuse crate::conv_f32::ConvProblemParams;\nuse crate::q_helpers::qtensor;\n\n/* https://www.tensorflow.org/lite/performance/quantization_spec\nCONV_2D\nInput 0:\ndata_type  : int8\nrange      : [-128, 127]\ngranularity: per-tensor\nInput 1 (Weight):\ndata_type  : int8\nrange      : [-127, 127]\ngranularity: per-axis (dim = 0)\nrestriction: zero_point = 0\nInput 2 (Bias):\ndata_type  : int32\nrange      : [int32_min, int32_max]\ngranularity: per-axis\nrestriction: (scale, zero_point) = (input0_scale * input1_scale[...], 0)\nOutput 0:\ndata_type  : int8\nrange      : [-128, 127]\ngranularity: per-tensor\n*/\n#[allow(clippy::arc_with_non_send_sync)]\npub fn q_params(\n    params: &QConvProblemParams,\n    co: usize,\n    kdt: DatumType,\n    idt: DatumType,\n    odt: DatumType,\n) -> BoxedStrategy<[Tensor; 6]> {\n    let params = params.clone();\n    let per_channel =\n        if params.tflite_rules && (kdt.is_unsigned() || idt.is_unsigned() || odt.is_unsigned()) {\n            Just(false).boxed()\n        } else {\n            any::<bool>().boxed()\n        };\n    per_channel\n        .prop_flat_map(move |per_channel| {\n            let k0 = if per_channel && params.tflite_rules {\n                Just(0i32).boxed()\n            } else if kdt.is_signed() {\n                (-10..10i32).boxed()\n            } else {\n                (0..20i32).boxed()\n            };\n            let x0 = if idt.is_signed() { -10i32..10i32 } else { 0..20 };\n            let y0 = if odt.is_signed() { -10i32..10i32 } else { 0..20 };\n            let k_scale_len = if per_channel { co } else { 1 };\n            let k_scale = vec(-3..3i32, k_scale_len..=k_scale_len);\n            (Just(per_channel), k0, x0, y0, k_scale, -3..3i32, -3..3i32)\n        })\n        .prop_map(|(per_channel, k0, x0, y0, k_scale, x_scale, y_scale)| {\n            let k_scale_values = k_scale.iter().map(|x| 2f32.powi(*x)).collect_vec();\n            [\n                tensor0(x0),\n                tensor0(2f32.powi(x_scale)),\n                tensor0(k0),\n                if per_channel { tensor1(&k_scale_values) } else { tensor0(k_scale_values[0]) },\n                tensor0(y0),\n                tensor0(2f32.powi(y_scale)),\n            ]\n        })\n        .boxed()\n}\n\n#[derive(Debug, Clone, Default)]\npub struct QConvProblemParams {\n    pub conv: ConvProblemParams,\n    pub tflite_rules: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct QConvProblem {\n    pub shape_in: DataShape,\n    pub kernel_format: KernelFormat,\n    pub co: usize,\n    pub group: usize,\n    pub kernel: Tensor,\n    pub bias: Option<Array1<i32>>,\n    pub data: Tensor,\n    pub qp: [Tensor; 6],\n    pub raw_output_dt: DatumType,\n}\n\nimpl QConvProblem {\n    fn geo_ker(&self) -> &[usize] {\n        &self.kernel.shape()[self.kernel_format.h_axis()..][..self.shape_in.hw_rank()]\n    }\n\n    fn reference(&self) -> Tensor {\n        assert!(self.data.datum_type().size_of() == 1);\n        assert!(self.kernel.datum_type().size_of() == 1);\n        assert_eq!(self.data.shape(), &*self.shape_in.shape);\n        let n = *self.shape_in.n().unwrap_or(&1);\n        let ci_per_g = self.shape_in.c() / self.group;\n        let co_per_g = self.co / self.group;\n        let x0 = *self.qp[0].try_as_plain().unwrap().to_scalar::<i32>().unwrap();\n        let k0 = *self.qp[2].try_as_plain().unwrap().to_scalar::<i32>().unwrap();\n        let y0 = *self.qp[4].try_as_plain().unwrap().to_scalar::<i32>().unwrap();\n        let x_scale = self.qp[1].cast_to_scalar::<f32>().unwrap();\n        let y_scale = self.qp[5].cast_to_scalar::<f32>().unwrap();\n        let kdt = self.kernel.datum_type();\n        let idt = self.data.datum_type();\n        let odt = self.raw_output_dt;\n        assert!(k0 <= kdt.unquantized().max_value().cast_to_scalar::<i32>().unwrap());\n        assert!(k0 >= kdt.unquantized().min_value().cast_to_scalar::<i32>().unwrap());\n        assert!(x0 <= idt.unquantized().max_value().cast_to_scalar::<i32>().unwrap());\n        assert!(x0 >= idt.unquantized().min_value().cast_to_scalar::<i32>().unwrap());\n        assert!(y0 <= odt.unquantized().max_value().cast_to_scalar::<i32>().unwrap());\n        assert!(y0 >= odt.unquantized().min_value().cast_to_scalar::<i32>().unwrap());\n        let shape_out: TVec<usize> = izip!(self.shape_in.hw_dims(), self.geo_ker())\n            .map(|(i, k)| (*i + 1).saturating_sub(*k))\n            .collect();\n        let shape_out = self\n            .shape_in\n            .fmt\n            .from_n_c_hw(self.shape_in.n().cloned().unwrap_or(1), co_per_g * self.group, shape_out)\n            .unwrap();\n        // a is the kernel, it can be quantized per O axis\n        let k_scale = if self.qp[3].len() == 1 {\n            vec![self.qp[3].cast_to_scalar::<f32>().unwrap(); *shape_out.c()]\n        } else {\n            self.qp[3].try_as_plain().unwrap().as_slice::<f32>().unwrap().into()\n        };\n        let mut temp = ArrayD::<i32>::zeros(&*shape_out.shape);\n        let data = self.data.cast_to::<i32>().unwrap();\n        let data = data.to_plain_array_view::<i32>().unwrap();\n        let kernel = self.kernel.cast_to::<i32>().unwrap();\n        let kernel = kernel.to_plain_array_view::<i32>().unwrap();\n        for n in 0..n {\n            for g in 0..self.group {\n                for geo_out in tract_ndarray::indices(shape_out.hw_dims()) {\n                    let mut output_coords: TVec<usize> = geo_out.slice().into();\n                    if self.shape_in.fmt.has_n() {\n                        output_coords.insert(0, n);\n                    }\n                    output_coords.insert(shape_out.c_axis(), 0);\n                    for geo_ker in tract_ndarray::indices(self.geo_ker()) {\n                        let mut input_coords: TVec<usize> =\n                            izip!(geo_out.slice(), geo_ker.slice()).map(|(a, b)| a + b).collect();\n                        if self.shape_in.fmt.has_n() {\n                            input_coords.insert(0, n);\n                        }\n                        input_coords.insert(self.shape_in.c_axis(), 0);\n                        for ci in 0..ci_per_g {\n                            input_coords[self.shape_in.c_axis()] = ci + g * ci_per_g;\n                            let i = data[&*input_coords];\n                            for co in 0..co_per_g {\n                                output_coords[shape_out.c_axis()] = co + g * co_per_g;\n                                let mut kernel_coords: TVec<usize> = geo_ker.slice().into();\n                                match self.kernel_format {\n                                    KernelFormat::OIHW => {\n                                        kernel_coords.insert(0, ci);\n                                        kernel_coords.insert(0, co + g * co_per_g);\n                                    }\n                                    KernelFormat::HWIO => {\n                                        kernel_coords.push(ci + g * ci_per_g);\n                                        kernel_coords.push(co);\n                                    }\n                                    KernelFormat::OHWI => {\n                                        kernel_coords.insert(0, co);\n                                        kernel_coords.push(ci + g * ci_per_g);\n                                    }\n                                }\n                                let k = kernel[&*kernel_coords];\n                                temp[&*output_coords] += (k - k0) * (i - x0);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        if let Some(bias) = &self.bias {\n            let mut shape = vec![1; temp.ndim()];\n            shape[shape_out.c_axis()] = bias.len();\n            temp += &bias.clone().into_shape_with_order(shape).unwrap();\n        }\n        let cdt = self.output_dt();\n        temp.axis_iter_mut(Axis(shape_out.c_axis())).zip(k_scale).for_each(\n            |(mut view, k_scale)| {\n                view.mapv_inplace(|i| {\n                    (round_ties_to_even(i as f32 / y_scale * k_scale * x_scale) as i32 + y0)\n                        .max(cdt.unquantized().min_value().cast_to_scalar::<i32>().unwrap())\n                        .min(cdt.unquantized().max_value().cast_to_scalar::<i32>().unwrap())\n                });\n            },\n        );\n        let mut tensor = temp.into_tensor().cast_to_dt(cdt.unquantized()).unwrap().into_owned();\n        unsafe { tensor.set_datum_type(cdt) };\n        tensor\n    }\n\n    fn output_dt(&self) -> DatumType {\n        self.raw_output_dt.quantize(QParams::ZpScale {\n            zero_point: self.qp[4].cast_to_scalar().unwrap(),\n            scale: *self.qp[5].try_as_plain().unwrap().to_scalar().unwrap(),\n        })\n    }\n\n    fn tract(&self) -> TractResult<TypedModel> {\n        assert!(self.data.shape() == &*self.shape_in.shape);\n        let mut model = TypedModel::default();\n        let idt = self.data.datum_type().quantize(QParams::ZpScale {\n            zero_point: self.qp[0].cast_to::<i32>()?.try_as_plain()?.as_slice::<i32>()?[0],\n            scale: self.qp[1].cast_to::<f32>()?.try_as_plain()?.as_slice::<f32>()?[0],\n        });\n        let kdt = self.kernel.datum_type().quantize(QParams::ZpScale {\n            zero_point: self.qp[2].cast_to::<i32>()?.try_as_plain()?.as_slice::<i32>()?[0],\n            scale: self.qp[3].cast_to::<f32>()?.try_as_plain()?.as_slice::<f32>()?[0],\n        });\n        let wire = model.add_source(\"input\", idt.fact(&self.shape_in.shape))?;\n        let mut inputs = tvec!(wire);\n        let mut kernel = self.kernel.clone().into_tensor();\n        unsafe { kernel.set_datum_type(kdt) };\n        inputs.push(model.add_const(\"kernel\", kernel.into_arc_tensor())?);\n        let bias = if let Some(bias) = &self.bias {\n            bias.clone().into_arc_tensor()\n        } else {\n            rctensor0(0i32)\n        };\n        inputs.push(model.add_const(\"bias\", bias)?);\n        for (ix, qp) in self.qp.iter().enumerate() {\n            inputs.push(model.add_const(format!(\"qp.{ix}\"), qp.clone())?);\n        }\n        let op = Conv::new(\n            PoolSpec::new(\n                self.shape_in.fmt,\n                self.geo_ker().into(),\n                PaddingSpec::Valid,\n                None,\n                None,\n                *self.shape_in.c(),\n                self.co,\n            ),\n            self.kernel_format,\n            self.group,\n            Some(self.output_dt()),\n        );\n        let wire = model.wire_node(\"conv\", op, &inputs)?[0];\n        model.select_output_outlets(&[wire])?;\n        Ok(model)\n    }\n}\n\nimpl Test for QConvProblem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> infra::TestResult {\n        let reference = self.reference();\n        let mut model = self.tract().context(\"Building model\")?;\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        let model = runtime.prepare(model).context(\"Preparing model\")?;\n        let idt = self.data.datum_type().quantize(QParams::ZpScale {\n            zero_point: self.qp[0].cast_to_scalar()?,\n            scale: *self.qp[1].try_as_plain()?.to_scalar()?,\n        });\n        let data = self.data.clone().into_tensor().cast_to_dt(idt)?.into_owned().into_tvalue();\n        let output = model.run(tvec!(data))?.remove(0);\n        //eprintln!(\"reference: {reference:?}\\noutput   : {output:?}\");\n        output.close_enough(&reference, approx)\n    }\n}\n\nimpl Arbitrary for QConvProblem {\n    type Parameters = QConvProblemParams;\n    type Strategy = BoxedStrategy<QConvProblem>;\n    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {\n        let geo_rank = params.conv.geo_rank.clone().unwrap_or(1..4);\n        (\n            crate::data_format(),\n            crate::kernel_format(),\n            1usize..=10,\n            1usize..=8,\n            1usize..=8,\n            1usize..=(if params.conv.no_group { 1 } else { 3 }),\n            geo_rank.prop_flat_map(crate::shapes),\n            prop_oneof![Just(DatumType::I8), Just(DatumType::U8)],\n            prop_oneof![Just(DatumType::I8), Just(DatumType::U8)],\n            prop_oneof![Just(DatumType::I8), Just(DatumType::U8), Just(DatumType::I32)],\n        )\n            .prop_flat_map(\n                move |(\n                    df,\n                    kf,\n                    n,\n                    mut ci0,\n                    mut co0,\n                    group,\n                    (mut ker_shape, data_shape),\n                    kdt,\n                    idt,\n                    odt,\n                )| {\n                    // FIXME in HWIO order, only regular and depthwise are supported\n                    if params.conv.no_arbitrary_grouping && group > 1 {\n                        ci0 = 1;\n                        co0 = 1;\n                    }\n                    if kf == KernelFormat::HWIO && group > 1 {\n                        ci0 = 1;\n                    }\n                    let qp = q_params(&params, co0 * group, kdt, idt, odt);\n                    let shape_in = df.from_n_c_hw(n, ci0 * group, data_shape).unwrap();\n                    let data_in = qtensor(shape_in.shape.iter().cloned().collect(), idt);\n                    match kf {\n                        KernelFormat::HWIO => {\n                            ker_shape.push(ci0 * group);\n                            ker_shape.push(co0)\n                        }\n                        KernelFormat::OIHW => {\n                            ker_shape.insert(0, ci0);\n                            ker_shape.insert(0, co0 * group)\n                        }\n                        KernelFormat::OHWI => {\n                            ker_shape.insert(0, co0);\n                            ker_shape.push(ci0 * group)\n                        }\n                    };\n                    let kernel = qtensor(ker_shape, kdt);\n                    let bias = proptest::option::of(\n                        qtensor(vec![co0 * group], i32::datum_type()).prop_map(|b| {\n                            arr1(\n                                b.cast_to::<i32>()\n                                    .unwrap()\n                                    .try_as_plain()\n                                    .unwrap()\n                                    .as_slice::<i32>()\n                                    .unwrap(),\n                            )\n                        }),\n                    );\n                    (Just((kf, shape_in, co0 * group, group, odt)), data_in, kernel, bias, qp)\n                },\n            )\n            .prop_map(\n                |((kernel_format, shape_in, co, group, raw_output_dt), data, kernel, bias, qp)| {\n                    QConvProblem {\n                        shape_in,\n                        co,\n                        kernel_format,\n                        group,\n                        data: data.into_tensor(),\n                        kernel: kernel.into_tensor(),\n                        bias,\n                        qp,\n                        raw_output_dt,\n                    }\n                },\n            )\n            .boxed()\n    }\n}\n\nfn qp_noop_i8() -> [Tensor; 6] {\n    [tensor0(0i32), tensor0(1f32), tensor0(0i32), tensor0(1f32), tensor0(0i32), tensor0(1f32)]\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<QConvProblem>(\"proptest\", QConvProblemParams::default());\n\n    suite.add(\n        \"trivial_0\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[0i8]]),\n            kernel: tensor3(&[[[0i8]]]),\n            bias: None,\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    suite.add(\n        \"trivial_1\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[2i8]]),\n            kernel: tensor3(&[[[64i8]]]),\n            bias: None,\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    suite.add(\n        \"trivial_2\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [2]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[-13i8], [26]]),\n            kernel: tensor3(&[[[8i8, -2]]]),\n            bias: None,\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    suite.add(\n        \"trivial_3\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 2, [1]).unwrap(),\n            co: 2,\n            kernel_format: HWIO,\n            group: 1,\n            data: tensor2(&[[0i8], [0]]),\n            kernel: tensor3(&[[[0i8, 0], [0, 0]]]),\n            bias: None,\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    suite.add(\n        \"trivial_4\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 2, [1]).unwrap(),\n            co: 8,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[0i8], [1i8]]),\n            kernel: tensor3(&[\n                [[0], [0]],\n                [[0], [0]],\n                [[0], [0]],\n                [[0], [0]],\n                [[0], [0]],\n                [[0], [0]],\n                [[0], [0]],\n                [[0], [1i8]],\n            ]),\n            bias: None,\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(-2i32);\n    qp[3] = tensor1(&[1f32, 0.5]);\n    suite.add(\n        \"scale_per_channel_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            kernel_format: OIHW,\n            co: 2,\n            group: 1,\n            data: tensor2(&[[0i8]]),\n            kernel: tensor3(&[[[0i8]], [[7]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[3] = tensor1(&[1f32, 0.5]);\n    suite.add(\n        \"scale_per_channel_1\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            kernel_format: OIHW,\n            co: 2,\n            group: 1,\n            data: tensor2(&[[0i8]]),\n            kernel: tensor3(&[[[0i8]], [[7]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    suite.add(\n        \"a0_0\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[1i8]]),\n            kernel: tensor3(&[[[0i8]]]),\n            bias: None,\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(-3i32);\n    qp[2] = tensor0(2i32);\n    suite.add(\n        \"a0_b0_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [2]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[0i8, 0]]),\n            kernel: tensor3(&[[[0i8]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[2] = tensor0(1i32);\n    suite.add(\n        \"kernel_zp\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            kernel_format: OIHW,\n            co: 1,\n            group: 1,\n            data: tensor2(&[[1i8]]),\n            kernel: tensor3(&[[[0i8]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[4] = tensor0(-1i32);\n    suite.add(\n        \"c_zp_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [2]).unwrap(),\n            kernel_format: OIHW,\n            co: 1,\n            group: 1,\n            data: tensor2(&[[0i8, 0]]),\n            kernel: tensor3(&[[[0i8, 0]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(1i32);\n    suite.add(\n        \"a0\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            kernel_format: OIHW,\n            co: 1,\n            group: 1,\n            data: tensor2(&[[0i8]]),\n            kernel: tensor3(&[[[-1i8]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[2] = tensor0(1i32);\n    suite.add(\n        \"b0_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [3]).unwrap(),\n            kernel_format: OIHW,\n            co: 1,\n            group: 1,\n            data: tensor2(&[[0i8, 0, 0]]),\n            kernel: tensor3(&[[[0i8, 0]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[2] = tensor0(2i32);\n    suite.add(\n        \"b0_1\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [6]).unwrap(),\n            kernel_format: OIHW,\n            co: 1,\n            group: 1,\n            data: tensor2(&[[0i8, 0, 0, 0, -20, 0]]),\n            kernel: tensor3(&[[[0i8, 0]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    suite.add(\n        \"shape_0\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1, 2]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor3(&[[[0i8], [0]]]),\n            kernel: tensor4(&[[[[0i8]]]]),\n            bias: None,\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    suite.add(\n        \"batch_0\",\n        QConvProblem {\n            shape_in: NHWC.from_n_c_hw(3, 1, [2]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor3(&[[[0i8], [0]], [[0], [0]], [[0], [0]]]),\n            kernel: tensor3(&[[[0i8, 0]]]),\n            bias: None,\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let qp = qp_noop_i8();\n    suite.add(\n        \"batch_1\",\n        QConvProblem {\n            shape_in: NHWC.from_n_c_hw(2, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[2, 1, 1]).unwrap(),\n            kernel: tensor3(&[[[1i8]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[5] = tensor0(9.274534f32);\n    suite.add(\n        \"scale_0\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[-1i8]]),\n            kernel: tensor3(&[[[1i8]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[5] = tensor0(1.1400417f32);\n    suite.add(\n        \"scale_1\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[41i8]]),\n            kernel: tensor3(&[[[1i8]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[1] = tensor0(0.5f32);\n    suite.add(\n        \"scale_2\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[-1i8]]),\n            kernel: tensor3(&[[[2i8]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[1] = tensor0(0.5f32);\n    qp[5] = tensor0(2f32);\n    suite.add(\n        \"scale_3\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[0i8]]),\n            kernel: tensor3(&[[[0i8]]]),\n            bias: Some(arr1(&[35i32])),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[4] = tensor0(1i32);\n    suite.add(\n        \"c0_0\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[0i8]]),\n            kernel: tensor3(&[[[0i8]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    suite.add(\n        \"group_0\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 2, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 2,\n            data: tensor2(&[[0i8, 0]]),\n            kernel: tensor3(&[[[0i8]], [[0]]]),\n            bias: None,\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(1i32);\n    suite.add(\n        \"group_1\",\n        QConvProblem {\n            shape_in: NCHW.from_n_c_hw(1, 2, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 2,\n            data: tensor3(&[[[0i8], [0]]]),\n            kernel: tensor3(&[[[1i8]], [[0]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(1i32);\n    suite.add(\n        \"group_2\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 2, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 2,\n            data: tensor2(&[[0i8, 0]]),\n            kernel: tensor3(&[[[0i8]], [[1]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(2i32);\n    qp[1] = tensor0(2f32);\n    qp[3] = tensor0(0.5f32);\n    qp[5] = tensor0(2f32);\n    suite.add(\n        \"rounding_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[4i8]]),\n            kernel: tensor3(&[[[-5i8]]]),\n            bias: Some(arr1(&[-125i32])),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[5] = tensor0(1.3759452f32);\n    suite.add(\n        \"rounding_on_arm\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: tensor2(&[[1i8]]),\n            kernel: tensor3(&[[[0i8]], [[-15]]]),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    suite.add(\n        \"bias_1\",\n        QConvProblem {\n            shape_in: NHWC.from_n_c_hw(1, 1, [1, 1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1, 1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[2, 1, 1, 1]).unwrap(),\n            bias: Some(arr1(&[1, 2])),\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let qp = qp_noop_i8();\n    suite.add(\n        \"bias_2\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[2, 1, 1]).unwrap(),\n            bias: Some(arr1(&[0, 1])),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(-1i32);\n    let mut kernel = Tensor::zero::<i8>(&[5, 1, 2]).unwrap();\n    *kernel.try_as_plain_mut().unwrap().as_slice_mut::<i8>().unwrap().last_mut().unwrap() = -1;\n    suite.add(\n        \"bias_3\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 1, [2]).unwrap(),\n            co: 5,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[2, 1]).unwrap(),\n            kernel,\n            bias: Some(Array1::zeros([5])),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    suite.add(\n        \"bias_4\",\n        QConvProblem {\n            shape_in: NHWC.from_n_c_hw(1, 1, [1, 1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1, 1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[2, 1, 1, 1]).unwrap(),\n            bias: Some(arr1(&[0, 1])),\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    suite.add(\n        \"bias_5\",\n        QConvProblem {\n            shape_in: NHWC.from_n_c_hw(1, 1, [1, 1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1, 1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[1, 1, 1, 1]).unwrap(),\n            bias: Some(arr1(&[1])),\n            qp: qp_noop_i8(),\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let qp = qp_noop_i8();\n    suite.add(\n        \"bias_in_chw\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[2, 1, 1]).unwrap(),\n            bias: Some(arr1(&[0, 0])),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let qp = qp_noop_i8();\n    suite.add(\n        \"bias_with_batch\",\n        QConvProblem {\n            shape_in: NCHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[1, 1, 1]).unwrap(),\n            bias: Some(arr1(&[1])),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let qp = qp_noop_i8();\n    suite.add(\n        \"bias_vec_with_batch\",\n        QConvProblem {\n            shape_in: NCHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[2, 1, 1]).unwrap(),\n            bias: Some(arr1(&[0, 1])),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let qp = qp_noop_i8();\n    suite.add(\n        \"asan_0\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 2, [1]).unwrap(),\n            co: 5,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 2]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[5, 2, 1]).unwrap(),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[3] = tensor1(&[1f32, 1f32]);\n    suite.add(\n        \"tflite_per_axis_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[2, 1, 1]).unwrap(),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[3] = tensor1(&[1f32, 1f32]);\n    suite.add(\n        \"tflite_per_axis_1\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1, 2]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1, 2]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[2, 1, 1, 2]).unwrap(),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[3] = tensor1(&[1f32, 1f32]);\n    suite.add(\n        \"tflite_per_axis_nchw_0\",\n        QConvProblem {\n            shape_in: NCHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[2, 1, 1]).unwrap(),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[3] = tensor1(&[1f32, 1f32]);\n    suite.add(\n        \"tflite_per_axis_nchw_1\",\n        QConvProblem {\n            shape_in: NCHW.from_n_c_hw(1, 1, [2]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<i8>(&[1, 1, 2]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[2, 1, 2]).unwrap(),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let qp = qp_noop_i8();\n    suite.add(\n        \"i8_u8\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            data: Tensor::zero::<u8>(&[1, 1]).unwrap(),\n            kernel: Tensor::zero::<i8>(&[1, 1, 1]).unwrap(),\n            bias: None,\n            qp,\n            raw_output_dt: DatumType::U8,\n        },\n    );\n    /*\n    let mut qp = qp_noop_i8();\n    qp[2] = tensor0(-1i32);\n    qp[5] = tensor0(0.5f32);\n    suite.add(\n    \"i8_u8_0\",\n    QConvProblem {\n    shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n    co: 1,\n    kernel_format: OIHW,\n    group: 1,\n    data: Tensor::zero::<u8>(&[1, 1]).unwrap(),\n    kernel: tensor3(&[[[1i8]]]),\n    bias: None,\n    qp,\n    },\n    );\n    */\n    let mut qp = qp_noop_i8();\n    qp[3] = tensor0(2f32);\n    suite.add(\n        \"i8_u8_ascale\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: Tensor::zero::<i8>(&[1, 1, 1]).unwrap(),\n            bias: None,\n            data: Tensor::zero::<u8>(&[1, 1]).unwrap(),\n            qp,\n            raw_output_dt: DatumType::U8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(1i32);\n    suite.add(\n        \"i8_u8_d0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[-3i8]]]),\n            bias: None,\n            data: tensor2(&[[1u8]]),\n            qp,\n            raw_output_dt: DatumType::U8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[4] = tensor0(2i32);\n    suite.add(\n        \"i8_u8_c0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[1i8]]]),\n            bias: None,\n            data: tensor2(&[[4u8]]),\n            qp,\n            raw_output_dt: DatumType::U8,\n        },\n    );\n    let qp = qp_noop_i8();\n    suite.add(\n        \"i8_u8_sat_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[-1i8]]]),\n            bias: None,\n            data: tensor2(&[[1u8]]),\n            qp,\n            raw_output_dt: DatumType::U8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(1i32);\n    qp[4] = tensor0(2i32);\n    suite.add(\n        \"i8_u8_weird_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[-1i8]]]),\n            bias: None,\n            data: tensor2(&[[0u8]]),\n            qp,\n            raw_output_dt: DatumType::U8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(1i32);\n    qp[1] = tensor0(4f32);\n    qp[3] = tensor0(2f32);\n    suite.add(\n        \"i8_u8_scales_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[-1i8]]]),\n            bias: None,\n            data: tensor2(&[[0u8]]),\n            qp,\n            raw_output_dt: DatumType::U8,\n        },\n    );\n    let qp = qp_noop_i8();\n    suite.add(\n        \"u8_i8_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[0u8]]]),\n            bias: None,\n            data: tensor2(&[[0i8]]),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let qp = qp_noop_i8();\n    suite.add(\n        \"u8_i8_1\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [2]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[0u8, 0]]]),\n            bias: None,\n            data: tensor2(&[[-9i8, 0]]),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let qp = qp_noop_i8();\n    suite.add(\n        \"u8_i8_2\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[0u8]], [[0]]]),\n            bias: None,\n            data: tensor2(&[[0i8]]),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(6i32);\n    suite.add(\n        \"u8_u8_i8_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[4u8]]]),\n            bias: None,\n            data: tensor2(&[[0i8]]),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(1i32);\n    qp[1] = tensor0(2f32);\n    qp[3] = tensor0(2f32);\n    qp[4] = tensor0(2i32);\n    suite.add(\n        \"many_qps_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[1u8]]]),\n            bias: None,\n            data: tensor2(&[[0u8]]),\n            qp,\n            raw_output_dt: DatumType::U8,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(1i32);\n    suite.add(\n        \"i32_output_0\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[0i8]]]),\n            bias: None,\n            data: tensor2(&[[0i8]]),\n            qp,\n            raw_output_dt: DatumType::I32,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[1] = tensor0(0.25f32);\n    qp[2] = tensor0(1i32);\n    qp[5] = tensor0(0.5f32);\n    suite.add(\n        \"i32_output_1\",\n        QConvProblem {\n            shape_in: CHW.from_n_c_hw(1, 1, [1]).unwrap(),\n            co: 1,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: tensor3(&[[[20i8]]]),\n            bias: None,\n            data: tensor2(&[[94i8]]),\n            qp,\n            raw_output_dt: DatumType::I32,\n        },\n    );\n    let mut qp = qp_noop_i8();\n    qp[0] = tensor0(-3);\n    suite.add(\n        \"bin_by_scalar_and_bin_unicast_selection_0\",\n        QConvProblem {\n            shape_in: NHWC.from_n_c_hw(2, 2, [4, 4]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 2,\n            kernel: tensor4(&[[[[1i8]]], [[[0i8]]]]),\n            bias: None,\n            data: Tensor::zero::<i8>(&[2, 4, 4, 2]).unwrap(),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[3] = tensor1(&[1f32, 1f32]);\n    suite.add(\n        \"batch_vec_scale\",\n        QConvProblem {\n            shape_in: NCHW.from_n_c_hw(2, 1, [2]).unwrap(),\n            co: 2,\n            kernel_format: OIHW,\n            group: 1,\n            kernel: Tensor::zero::<i8>(&[2, 1, 1]).unwrap(),\n            bias: None,\n            data: Tensor::zero::<i8>(&[2, 1, 2]).unwrap(),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n\n    let mut qp = qp_noop_i8();\n    qp[1] = tensor1(&[0.5f32]);\n    qp[3] = tensor1(&[1f32; 18]);\n    qp[5] = tensor1(&[0.5f32]);\n    suite.add(\n        \"timeout_0\",\n        QConvProblem {\n            shape_in: HWC.from_n_c_hw(1, 3, [1]).unwrap(),\n            co: 18,\n            kernel_format: OIHW,\n            group: 3,\n            kernel: Tensor::zero::<i8>(&[18, 1, 1]).unwrap(),\n            bias: None,\n            data: Tensor::zero::<i8>(&[1, 3]).unwrap(),\n            qp,\n            raw_output_dt: DatumType::I8,\n        },\n    );\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/deconv.rs",
    "content": "use DataFormat::*;\nuse KernelFormat::*;\nuse infra::Test;\nuse infra::TestResult;\nuse infra::TestSuite;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::ops::cnn::conv::KernelFormat;\nuse tract_core::ops::cnn::*;\nuse tract_core::ops::nn::*;\nuse tract_ndarray as ndarray;\nuse tract_ndarray::{prelude::*, *};\n\nuse crate::data_format;\nuse crate::kernel_format;\nuse crate::tensor;\n\n#[derive(Debug, Clone, Default)]\npub struct DeconvProblemParams {}\n\n#[derive(Debug, Clone)]\nstruct DeconvProblem {\n    data_format: DataFormat,\n    kernel_format: KernelFormat,\n    padding: PaddingSpec,\n    input: ArrayD<f32>,\n    kernel: ArrayD<f32>,\n    bias: Option<ArrayD<f32>>,\n    strides: TVec<usize>,\n    dilations: TVec<usize>,\n    adjustments: TVec<usize>,\n    group: usize,\n}\n\nimpl Arbitrary for DeconvProblem {\n    type Strategy = BoxedStrategy<DeconvProblem>;\n    type Parameters = DeconvProblemParams;\n    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {\n        (1usize..4)\n            .prop_flat_map(|georank| {\n                (\n                    data_format(),\n                    kernel_format(),\n                    prop_oneof![Just(PaddingSpec::Valid), Just(PaddingSpec::SameUpper)],\n                    1usize..3,                         // n\n                    1usize..4,                         // ci / group\n                    1usize..4,                         // co / group\n                    vec(1usize..4, georank..=georank), // kernel shape\n                    vec(1usize..8, georank..=georank), // image shape\n                    vec(1usize..4, georank..=georank), // strides\n                    vec(1usize..4, georank..=georank), // dilations\n                    1usize..4,                         // group\n                )\n            })\n            .prop_filter(\n                \"dilation, strides and shapes in SAME\",\n                |(_, _, pad, _, _, _, hwk, _, strides, dilations, _)| {\n                    pad == &PaddingSpec::Valid\n                        || tract_itertools::izip!(hwk, dilations, strides)\n                            .all(|(k, d, s)| (k - 1) * d > s - 1)\n                },\n            )\n            .prop_flat_map(\n                |(\n                    df,\n                    kf,\n                    pad,\n                    n,\n                    ci_over_group,\n                    co_over_group,\n                    hwk,\n                    hwi,\n                    strides,\n                    dilations,\n                    group,\n                )| {\n                    let mut kernel_shape = hwk;\n                    match kf {\n                        OIHW => {\n                            kernel_shape.insert(0, co_over_group * group);\n                            kernel_shape.insert(1, ci_over_group);\n                        }\n                        HWIO => {\n                            kernel_shape.push(ci_over_group * group);\n                            kernel_shape.push(co_over_group);\n                        }\n                        OHWI => {\n                            kernel_shape.insert(0, co_over_group);\n                            kernel_shape.push(ci_over_group * group);\n                        }\n                    };\n                    let data_shape = df.from_n_c_hw(n, ci_over_group * group, hwi).unwrap();\n                    (\n                        Just(df),\n                        Just(kf),\n                        Just(pad),\n                        tensor(&data_shape.shape),\n                        tensor(&kernel_shape),\n                        proptest::option::of(tensor(&[co_over_group * group])),\n                        Just(strides),\n                        Just(dilations),\n                        Just(group),\n                    )\n                },\n            )\n            .prop_map(\n                |(\n                    data_format,\n                    kernel_format,\n                    padding,\n                    input,\n                    kernel,\n                    bias,\n                    strides,\n                    dilations,\n                    group,\n                )| {\n                    let adjustments = tvec!(0; kernel.ndim() - 2); // FIXME maybe\n                    DeconvProblem {\n                        data_format,\n                        kernel_format,\n                        padding,\n                        input,\n                        kernel,\n                        bias,\n                        strides: strides.into(),\n                        dilations: dilations.into(),\n                        adjustments,\n                        group,\n                    }\n                },\n            )\n            .boxed()\n    }\n}\n\nimpl DeconvProblem {\n    fn as_op(&self) -> TractResult<Deconv> {\n        let pool_spec = PoolSpec::new(\n            self.data_format,\n            self.kernel_format.spatial_shape(self.kernel.shape()).into(),\n            self.padding.clone(),\n            Some(self.dilations.clone()),\n            Some(self.strides.clone()),\n            self.kernel_format.input_channels(self.kernel.shape(), self.group).into_owned(),\n            self.kernel_format.output_channels(self.kernel.shape(), self.group).into_owned(),\n        );\n        let op = Deconv::new(pool_spec, self.kernel_format, self.adjustments.clone(), self.group);\n        Ok(op)\n    }\n\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n        let src = model.add_source(\"src\", f32::fact(self.input.shape()))?;\n        let kernel = model.add_const(\"kernel\", self.kernel.clone().into_tensor())?;\n        let bias =\n            self.bias.as_ref().map(|b| b.clone().into_tensor()).unwrap_or_else(|| tensor0(0f32));\n        let bias = model.add_const(\"bias\", bias)?;\n        let output = model.wire_node(\n            \"deconv\",\n            self.as_op().context(\"Generating op\")?,\n            &[src, kernel, bias],\n        )?;\n        model.select_output_outlets(&output)?;\n        Ok(model)\n    }\n\n    fn reference(&self) -> TractResult<ArrayD<f32>> {\n        use std::iter::once;\n        let co = match self.kernel_format {\n            KernelFormat::HWIO => self.kernel.shape()[self.kernel.ndim() - 1] * self.group,\n            KernelFormat::OIHW => self.kernel.shape()[0],\n            KernelFormat::OHWI => self.kernel.shape()[0] * self.group,\n        };\n        let input_shape = self.data_format.shape(self.input.shape())?;\n        let n = if self.data_format.has_n() { self.input.shape()[0] } else { 1 };\n        let kernel_hwdims = self.kernel_format.spatial_shape(self.kernel.shape());\n        let valid_output_shape_geo: TVec<usize> = tract_itertools::izip!(\n            input_shape.hw_dims(),\n            kernel_hwdims,\n            self.strides.iter(),\n            self.dilations.iter()\n        )\n        .map(|(i, k, s, d)| (i - 1) * s + (k - 1) * d + 1)\n        .collect();\n        let paddings: TVec<(usize, usize)> = if self.padding == PaddingSpec::Valid {\n            tvec![(0, 0); valid_output_shape_geo.len()]\n        } else {\n            tract_itertools::izip!(input_shape.hw_dims(), &valid_output_shape_geo, &self.strides)\n                .map(|(i, o, s)| o - i * s)\n                .map(|total| (total / 2, total - total / 2))\n                .collect()\n        };\n        let output_shape_geo = if self.padding == PaddingSpec::Valid {\n            valid_output_shape_geo\n        } else {\n            tract_itertools::izip!(input_shape.hw_dims(), &self.strides)\n                .map(|(i, s)| i * s)\n                .collect()\n        };\n        let output_shape = self.data_format.from_n_c_hw(n, co, output_shape_geo)?;\n        let mut output = ArrayD::zeros(&*output_shape.shape);\n        if let Some(b) = &self.bias {\n            let mut bias_shape = tvec!(1; output_shape.rank());\n            bias_shape[output_shape.c_axis()] = co;\n            let b = b.clone().into_shape_with_order(&*bias_shape)?;\n            output += &b;\n        }\n        let co_per_group = co / self.group;\n        let ci_per_group = input_shape.c() / self.group;\n        for n in 0..n {\n            for g in 0..self.group {\n                for co in 0..co_per_group {\n                    for ci in 0..ci_per_group {\n                        for hwi in indices(input_shape.hw_dims()) {\n                            for hwk in indices(kernel_hwdims) {\n                                let hwo: TVec<isize> = tract_itertools::izip!(\n                                    hwi.slice().iter(),\n                                    hwk.slice().iter(),\n                                    self.strides.iter(),\n                                    self.dilations.iter(),\n                                    paddings.iter(),\n                                )\n                                .map(|(i, k, s, d, p)| (i * s + k * d) as isize - p.0 as isize)\n                                .collect();\n                                let hwo: TVec<usize> = if hwo.iter().all(|x| *x >= 0) {\n                                    hwo.iter().map(|x| *x as usize).collect()\n                                } else {\n                                    continue;\n                                };\n                                let i = self.data_format.from_n_c_hw(\n                                    n,\n                                    ci + g * ci_per_group,\n                                    hwi.slice(),\n                                )?;\n                                let o =\n                                    self.data_format.from_n_c_hw(n, co + g * co_per_group, hwo)?;\n                                let k: TVec<usize> = match self.kernel_format {\n                                    OIHW => once(co + co_per_group * g)\n                                        .chain(once(ci))\n                                        .chain(hwk.slice().iter().cloned())\n                                        .collect(),\n                                    HWIO => hwk\n                                        .slice()\n                                        .iter()\n                                        .cloned()\n                                        .chain(once(ci + ci_per_group * g))\n                                        .chain(once(co))\n                                        .collect(),\n                                    OHWI => once(co)\n                                        .chain(hwk.slice().iter().cloned())\n                                        .chain(once(ci + ci_per_group * g))\n                                        .collect(),\n                                };\n                                if let Some(cell) = output.get_mut(&*o.shape) {\n                                    *cell += self.input[&*i.shape] * self.kernel[&*k]\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        Ok(output)\n    }\n}\n\nimpl Test for DeconvProblem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.reference().context(\"Running reference\")?.into_tensor();\n        let mut model = self.tract().context(\"Generating model\")?;\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        let mut output = runtime.prepare(model)?.run(tvec![self.input.clone().into_tvalue()])?;\n        let output = output.remove(0).into_tensor();\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n    suite.add_arbitrary::<DeconvProblem>(\"proptest\", DeconvProblemParams::default());\n\n    suite.add(\n        \"trivial_0\",\n        DeconvProblem {\n            data_format: NCHW,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr4(&[[[[0.0]]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0]]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n            adjustments: tvec!(0, 0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"hwc_0\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr3(&[[[0.0]], [[0.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0]]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n            adjustments: tvec!(0, 0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"geo_0\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr3(&[[[0.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0], [0.0]]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n            adjustments: tvec!(0, 0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"hwio_0\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: HWIO,\n            padding: PaddingSpec::Valid,\n            input: arr3(&[[[0.0]]]).into_dyn(),\n            kernel: arr4(&[[[[0.0, 0.0]]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n            adjustments: tvec!(0, 0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"strides_1\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr2(&[[0.0], [1.0]]).into_dyn(),\n            kernel: arr3(&[[[1.0]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(2),\n            dilations: tvec!(1),\n            adjustments: tvec!(0, 0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"same_upper_1\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::SameUpper,\n            input: arr2(&[[0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0, 0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"same_upper_dil\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::SameUpper,\n            input: arr2(&[[0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(2),\n            adjustments: tvec!(0, 0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"same_upper_strides\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::SameUpper,\n            input: arr2(&[[0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0, 0.0, 0.0]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(2),\n            dilations: tvec!(1),\n            adjustments: tvec!(0, 0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"channel_0\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr2(&[[0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0]], [[0.0]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"group_0\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr2(&[[0.0, 0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0]], [[0.0]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 2,\n        },\n    );\n\n    suite.add(\n        \"group_1\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: HWIO,\n            padding: PaddingSpec::Valid,\n            input: arr2(&[[0.0, 0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0], [0.0]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 2,\n        },\n    );\n\n    suite.add(\n        \"group_2\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: ndarray::arr2(&[[1.0, 0.0]]).into_dyn(),\n            kernel: ndarray::arr3(&[[[1.0]], [[0.0]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 2,\n        },\n    );\n\n    suite.add(\n        \"group_3\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: ndarray::arr2(&[[0.0, 1.0]]).into_dyn(),\n            kernel: ndarray::arr3(&[[[0.0]], [[1.0]], [[0.0]], [[0.0]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 2,\n        },\n    );\n\n    suite.add(\n        \"group_4\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr2(&[[0f32, 1.]]).into_dyn(),\n            kernel: arr3(&[[[0f32]], [[1.]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 2,\n        },\n    );\n\n    suite.add(\n        \"group_hwio_0\",\n        DeconvProblem {\n            data_format: CHW,\n            kernel_format: HWIO,\n            padding: PaddingSpec::Valid,\n            input: Array2::from_shape_vec((4, 1), vec![0f32, 0., 1., 0.]).unwrap().into_dyn(),\n            kernel: Array3::from_shape_vec((1, 4, 1), vec![0f32, 0., 1., 0.]).unwrap().into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 2,\n        },\n    );\n\n    suite.add(\n        \"bias_0\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr2(&[[0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0]]]).into_dyn(),\n            bias: Some(arr1(&[1.0f32]).into_dyn()),\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"bias_1\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr2(&[[0.0], [0.0]]).into_dyn(),\n            kernel: arr3(&[[[0.0]]]).into_dyn(),\n            bias: Some(arr1(&[1.0f32]).into_dyn()),\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"bias_2\",\n        DeconvProblem {\n            data_format: CHW,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr2(&[[0f32, 1.]]).into_dyn(),\n            kernel: arr3(&[[[1f32]], [[0.]]]).into_dyn(),\n            bias: Some(arr1(&[0f32, 0.]).into_dyn()),\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"bias_group_0\",\n        DeconvProblem {\n            data_format: CHW,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr2(&[[0f32], [1.]]).into_dyn(),\n            kernel: arr3(&[[[1f32]], [[0.]]]).into_dyn(),\n            bias: Some(arr1(&[0f32, 0.]).into_dyn()),\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 2,\n        },\n    );\n\n    suite.add(\n        \"rank_5_with_group\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr4(&[[[[0.0, 0.0, 0.0, 1.0]]]]).into_dyn(),\n            kernel: arr1(&[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0])\n                .into_shape_with_order(vec![2, 2, 1, 2, 1])\n                .unwrap()\n                .into_dyn(),\n            bias: None,\n            strides: tvec!(1, 1, 1),\n            dilations: tvec!(1, 1, 1),\n            adjustments: tvec!(0, 0, 0),\n            group: 2,\n        },\n    );\n\n    suite.add(\n        \"issue_512_simplified\",\n        DeconvProblem {\n            data_format: NCHW,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: ndarray::Array4::zeros([1, 4, 1, 1]).into_dyn(),\n            kernel: ndarray::Array4::zeros([2, 2, 1, 1]).into_dyn(),\n            bias: None,\n            strides: tvec!(1, 1),\n            dilations: tvec!(1, 1),\n            adjustments: tvec!(0, 0),\n            group: 2,\n        },\n    );\n\n    suite.add(\n        \"issue_optim_2d\",\n        DeconvProblem {\n            data_format: HWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: ndarray::Array3::zeros([2, 2, 1]).into_dyn(),\n            kernel: ndarray::Array4::zeros([1, 1, 1, 1]).into_dyn(),\n            bias: None,\n            strides: tvec!(1, 2),\n            dilations: tvec!(1, 1),\n            adjustments: tvec!(0, 0),\n            group: 1,\n        },\n    );\n\n    suite.add(\n        \"foo\",\n        DeconvProblem {\n            data_format: NHWC,\n            kernel_format: OIHW,\n            padding: PaddingSpec::Valid,\n            input: arr3(&[[[0f32]], [[1.]]]).into_dyn(),\n            kernel: arr3(&[[[1f32]]]).into_dyn(),\n            bias: None,\n            strides: tvec!(1),\n            dilations: tvec!(1),\n            adjustments: tvec!(0),\n            group: 1,\n        },\n    );\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/downsample.rs",
    "content": "use infra::{Test, TestSuite};\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ops::Downsample;\n\n#[derive(Debug, Clone, Default)]\nstruct DownsampleProblem {\n    input_shape: Vec<usize>,\n    op: Downsample,\n}\n\nimpl Arbitrary for DownsampleProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<DownsampleProblem>;\n    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {\n        vec(1..10usize, 1..5usize)\n            .prop_flat_map(|input_shape| {\n                let rank = input_shape.len();\n                let stride_and_modulo =\n                    (1..4usize).prop_flat_map(|stride| (Just(stride as isize), 0..stride));\n                (Just(input_shape), 0..rank, stride_and_modulo, any::<bool>())\n            })\n            .prop_map(|(input_shape, axis, (stride, modulo), backward)| {\n                let modulo = if backward { 0 } else { modulo.min(input_shape[axis] - 1) };\n                let stride = if backward { -stride } else { stride };\n                DownsampleProblem { input_shape, op: Downsample { axis, stride, modulo } }\n            })\n            .boxed()\n    }\n}\n\nimpl DownsampleProblem {\n    fn reference(&self, input: &Tensor) -> TractResult<Tensor> {\n        let len = input.shape()[self.op.axis];\n        let mut slices = vec![];\n        let mut current = if self.op.stride > 0 {\n            self.op.modulo as isize\n        } else {\n            (len - 1 - self.op.modulo) as isize\n        };\n        while current >= 0 && current < input.shape()[self.op.axis] as isize {\n            slices.push(input.slice(self.op.axis, current as usize, current as usize + 1)?);\n            current += self.op.stride;\n        }\n        Tensor::stack_tensors(self.op.axis, &slices)\n    }\n}\n\nimpl Test for DownsampleProblem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> infra::TestResult {\n        let mut input = Tensor::zero::<f32>(&self.input_shape)?;\n        input\n            .try_as_plain_mut()?\n            .as_slice_mut::<f32>()?\n            .iter_mut()\n            .enumerate()\n            .for_each(|(ix, x)| *x = ix as f32);\n\n        let reference = self.reference(&input).context(\"Computing reference\")?;\n\n        let mut model = TypedModel::default();\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        let wire = model.add_source(\"input\", TypedFact::shape_and_dt_of(&input))?;\n        let output = model.wire_node(\"downsample\", self.op.clone(), &[wire])?;\n        model.select_output_outlets(&output)?;\n        let mut output = runtime.prepare(model)?.run(tvec![input.clone().into_tvalue()])?;\n        let output = output.remove(0).into_tensor();\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n    suite.add_arbitrary::<DownsampleProblem>(\"proptest\", ());\n\n    suite.add_test(\n        \"neg_0\",\n        DownsampleProblem {\n            input_shape: vec![1],\n            op: Downsample { axis: 0, stride: -1, modulo: 0 },\n        },\n    );\n\n    suite.add_test(\n        \"neg_1\",\n        DownsampleProblem {\n            input_shape: vec![2],\n            op: Downsample { axis: 0, stride: -2, modulo: 0 },\n        },\n    );\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/elmwise.rs",
    "content": "use std::ops::Div;\n\nuse infra::{Test, TestResult, TestSuite};\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ndarray::ArrayD;\nuse tract_core::num_traits::{AsPrimitive, FromPrimitive, Num, ToPrimitive};\nuse tract_core::ops::element_wise::ElementWiseOp;\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]\npub enum ElWiseOps {\n    Neg,\n    Abs,\n    Sqr,\n    Sqrt,\n    Rsqrt,\n    Recip,\n    Ceil,\n    Floor,\n    Round,\n    RoundHalfToEven,\n    Exp,\n    Sigmoid,\n    Sin,\n    Sinh,\n    Asin,\n    Asinh,\n    Cos,\n    Cosh,\n    Acos,\n    Acosh,\n    Tan,\n    Tanh,\n    Atan,\n    Atanh,\n    // Erf, Erf is unstable in rust\n    Ln,\n}\n\npub const ALL_OPS: [ElWiseOps; 25] = [\n    ElWiseOps::Neg,\n    ElWiseOps::Abs,\n    ElWiseOps::Sqr,\n    ElWiseOps::Sqrt,\n    ElWiseOps::Rsqrt,\n    ElWiseOps::Recip,\n    ElWiseOps::Ceil,\n    ElWiseOps::Floor,\n    ElWiseOps::Round,\n    ElWiseOps::RoundHalfToEven,\n    ElWiseOps::Exp,\n    ElWiseOps::Sigmoid,\n    ElWiseOps::Sin,\n    ElWiseOps::Sinh,\n    ElWiseOps::Asin,\n    ElWiseOps::Asinh,\n    ElWiseOps::Cos,\n    ElWiseOps::Cosh,\n    ElWiseOps::Acos,\n    ElWiseOps::Acosh,\n    ElWiseOps::Tan,\n    ElWiseOps::Tanh,\n    ElWiseOps::Atan,\n    ElWiseOps::Atanh,\n    //ElWiseOps::Erf,\n    ElWiseOps::Ln,\n];\n\npub trait SupportedElement:\n    Datum\n    + Num\n    + Copy\n    + FromPrimitive\n    + ToPrimitive\n    + 'static\n    + Div<Output = Self>\n    + PartialOrd\n    + AsPrimitive<usize>\n    + AsPrimitive<f32>\n    + AsPrimitive<f64>\n{\n}\n\nimpl<T> SupportedElement for T where\n    T: Datum\n        + Num\n        + Copy\n        + FromPrimitive\n        + ToPrimitive\n        + 'static\n        + Div<Output = Self>\n        + PartialOrd\n        + AsPrimitive<usize>\n        + AsPrimitive<f32>\n        + AsPrimitive<f64>\n{\n}\n\n#[derive(Debug, Clone)]\npub struct ElWiseOpProblem<T>\nwhere\n    T: SupportedElement,\n{\n    pub op: ElWiseOps,\n    pub input: ArrayD<T>,\n}\n\nimpl<T> Arbitrary for ElWiseOpProblem<T>\nwhere\n    T: SupportedElement,\n{\n    type Parameters = ();\n    type Strategy = BoxedStrategy<Self>;\n\n    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {\n        let shape_strategy = prop::collection::vec(1usize..=5, 0..=4);\n\n        shape_strategy\n            .prop_flat_map(|shape| {\n                let len = shape.iter().product::<usize>();\n                let input = vec(\n                    (2u8..=10u8).prop_map(|i| T::from_u8(i).unwrap() / T::from_u8(2).unwrap()),\n                    len..=len,\n                )\n                .prop_map(move |vec| ArrayD::from_shape_vec(shape.to_vec(), vec).unwrap())\n                .boxed();\n\n                let mut ops = ALL_OPS.to_vec();\n\n                if std::any::TypeId::of::<T>() == std::any::TypeId::of::<u8>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<u16>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<u32>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<u64>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<i8>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<i16>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<i32>()\n                    || std::any::TypeId::of::<T>() == std::any::TypeId::of::<i64>()\n                {\n                    ops.retain(|op| {\n                        matches!(op, ElWiseOps::Ceil | ElWiseOps::Floor | ElWiseOps::Round)\n                    });\n                }\n\n                let op_strategy = prop::sample::select(ops);\n                (input, op_strategy)\n            })\n            .prop_map(|(input, op)| ElWiseOpProblem { input, op })\n            .boxed()\n    }\n}\n\npub fn to_tract_op(op: &ElWiseOps) -> Box<dyn TypedOp> {\n    let el_mini_op: Box<dyn ElementWiseMiniOp> = match op {\n        ElWiseOps::Neg => Box::new(tract_core::ops::math::Neg {}),\n        ElWiseOps::Abs => Box::new(tract_core::ops::math::Abs {}),\n        ElWiseOps::Sqr => Box::new(tract_core::ops::math::Square {}),\n        ElWiseOps::Sqrt => Box::new(tract_core::ops::math::Sqrt {}),\n        ElWiseOps::Rsqrt => Box::new(tract_core::ops::math::Rsqrt {}),\n        ElWiseOps::Recip => Box::new(tract_core::ops::math::Recip {}),\n        ElWiseOps::Ceil => Box::new(tract_core::ops::math::Ceil {}),\n        ElWiseOps::Floor => Box::new(tract_core::ops::math::Floor {}),\n        ElWiseOps::Round => Box::new(tract_core::ops::math::Round {}),\n        ElWiseOps::RoundHalfToEven => Box::new(tract_core::ops::math::RoundHalfToEven {}),\n        ElWiseOps::Exp => Box::new(tract_core::ops::math::Exp {}),\n        ElWiseOps::Sigmoid => Box::new(tract_core::ops::nn::Sigmoid {}),\n        ElWiseOps::Sin => Box::new(tract_core::ops::math::Sin {}),\n        ElWiseOps::Sinh => Box::new(tract_core::ops::math::Sinh {}),\n        ElWiseOps::Asin => Box::new(tract_core::ops::math::Asin {}),\n        ElWiseOps::Asinh => Box::new(tract_core::ops::math::Asinh {}),\n        ElWiseOps::Cos => Box::new(tract_core::ops::math::Cos {}),\n        ElWiseOps::Cosh => Box::new(tract_core::ops::math::Cosh {}),\n        ElWiseOps::Acos => Box::new(tract_core::ops::math::Acos {}),\n        ElWiseOps::Acosh => Box::new(tract_core::ops::math::Acosh {}),\n        ElWiseOps::Tan => Box::new(tract_core::ops::math::Tan {}),\n        ElWiseOps::Tanh => Box::new(tract_core::ops::math::Tanh {}),\n        ElWiseOps::Atan => Box::new(tract_core::ops::math::Atan {}),\n        ElWiseOps::Atanh => Box::new(tract_core::ops::math::Atanh {}),\n        //ElWiseOps::Erf => Box::new(tract_core::ops::math::Erf {}),\n        ElWiseOps::Ln => Box::new(tract_core::ops::math::Ln {}),\n    };\n    Box::new(ElementWiseOp(el_mini_op, None))\n}\n\nfn eval_reference<FI: Datum, FO: Datum>(\n    a: &Tensor,\n    func: impl Fn(&mut FO, &FI),\n) -> TractResult<Tensor> {\n    let mut out = unsafe { Tensor::uninitialized_dt(FO::datum_type(), a.shape())? };\n    let a_view = a.to_plain_array_view::<FI>()?;\n    let mut c_plain = out.try_as_plain_mut()?;\n    let mut c = c_plain.to_array_view_mut::<FO>()?;\n    tract_core::ndarray::Zip::from(&mut c).and_broadcast(a_view).for_each(func);\n    Ok(out)\n}\n\nimpl<T> ElWiseOpProblem<T>\nwhere\n    T: SupportedElement,\n{\n    fn f32_elmwise<F>(a: &T, op: F) -> T\n    where\n        T: SupportedElement,\n        F: Fn(f32) -> f32,\n    {\n        let a_f32 = a.to_f32().unwrap();\n        T::from_f32(op(a_f32)).unwrap()\n    }\n\n    pub fn reference(&self) -> TractResult<Tensor> {\n        let inp = self.input.clone().into_tensor();\n\n        let res = match self.op {\n            ElWiseOps::Neg => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, |x| -x))?,\n            ElWiseOps::Abs => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::abs))?,\n            ElWiseOps::Sqr => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, |x| x * x))?,\n            ElWiseOps::Sqrt => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::sqrt))?,\n            ElWiseOps::Rsqrt => {\n                eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, |x| 1.0 / x.sqrt()))?\n            }\n            ElWiseOps::Recip => {\n                eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, |x| 1.0 / x))?\n            }\n            ElWiseOps::Ceil => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::ceil))?,\n            ElWiseOps::Floor => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::floor))?,\n            ElWiseOps::Round => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::round))?,\n            ElWiseOps::RoundHalfToEven => {\n                eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::round_ties_even))?\n            }\n            ElWiseOps::Exp => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::exp))?,\n            ElWiseOps::Sigmoid => {\n                eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, |x| 1. / (1. + (-x).exp())))?\n            }\n            ElWiseOps::Sin => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::sin))?,\n            ElWiseOps::Sinh => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::sinh))?,\n            ElWiseOps::Asin => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::asin))?,\n            ElWiseOps::Asinh => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::asinh))?,\n            ElWiseOps::Cos => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::cos))?,\n            ElWiseOps::Cosh => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::cosh))?,\n            ElWiseOps::Acos => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::acos))?,\n            ElWiseOps::Acosh => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::acosh))?,\n            ElWiseOps::Tan => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::tan))?,\n            ElWiseOps::Tanh => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::tanh))?,\n            ElWiseOps::Atan => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::atan))?,\n            ElWiseOps::Atanh => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::atanh))?,\n            ElWiseOps::Ln => eval_reference(&inp, |c, a| *c = Self::f32_elmwise(a, f32::ln))?,\n        };\n        Ok(res)\n    }\n\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n\n        let input = model\n            .add_source(\"input\", TypedFact::shape_and_dt_of(&self.input.clone().into_tensor()))?;\n\n        let output = model.wire_node(\"bin_op\", to_tract_op(&self.op), &[input])?;\n        model.select_output_outlets(&output)?;\n\n        model = model.into_decluttered()?;\n        Ok(model)\n    }\n}\n\nimpl<T> Test for ElWiseOpProblem<T>\nwhere\n    T: SupportedElement,\n{\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.reference()?;\n        let mut model = self.tract()?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n\n        let mut output = runtime.prepare(model)?.run(tvec![self.input.clone().into_tvalue()])?;\n        let output = output.remove(0).into_tensor();\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<ElWiseOpProblem<f32>>(\"proptest_f32\", ());\n    suite.add_arbitrary::<ElWiseOpProblem<f16>>(\"proptest_f16\", ());\n    suite.add_arbitrary::<ElWiseOpProblem<u8>>(\"proptest_u8\", ());\n    suite.add_arbitrary::<ElWiseOpProblem<i8>>(\"proptest_i8\", ());\n    suite.add_arbitrary::<ElWiseOpProblem<u32>>(\"proptest_u32\", ());\n    suite.add_arbitrary::<ElWiseOpProblem<i16>>(\"proptest_i16\", ());\n    suite.add_arbitrary::<ElWiseOpProblem<i64>>(\"proptest_i64\", ());\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/gelu_approximate.rs",
    "content": "use std::f32::consts::PI;\n\nuse infra::Test;\nuse infra::TestResult;\nuse infra::TestSuite;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ndarray::ArrayD;\nuse tract_core::num_traits::Float;\nuse tract_transformers::ops::gelu_approximate::gelu_approximate;\n\nuse crate::tensor;\n\n#[derive(Debug, Clone)]\npub struct GeluApproximateProblem<F>\nwhere\n    F: Datum + Float,\n{\n    input: ArrayD<F>,\n    fast_impl: bool,\n}\n\nimpl<F> Arbitrary for GeluApproximateProblem<F>\nwhere\n    F: Datum + Float,\n{\n    type Parameters = ();\n    type Strategy = BoxedStrategy<GeluApproximateProblem<F>>;\n\n    fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {\n        (0usize..5)\n            .prop_flat_map(|rank| {\n                let other_dim = 1usize..10;\n                vec(other_dim, rank..=rank)\n            })\n            .prop_flat_map(|shape| {\n                (tensor::<F>(&shape), any::<bool>())\n                    .prop_map(move |(input, fast_impl)| Self { input, fast_impl })\n            })\n            .boxed()\n    }\n}\n\nimpl<F> GeluApproximateProblem<F>\nwhere\n    F: Datum + Float,\n    f32: From<F>,\n{\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n        let input = self.input.clone().into_tensor();\n        let input = model.add_source(\"input\", TypedFact::shape_and_dt_of(&input))?;\n\n        let output = model.wire_node(\"gelu\", gelu_approximate(self.fast_impl), &[input])?;\n        model.select_output_outlets(&output)?;\n\n        model = model.into_decluttered()?;\n        Ok(model)\n    }\n\n    fn reference(&self) -> ArrayD<F> {\n        let input = &self.input;\n        //0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)));\n        input.mapv(|x| {\n            let x_f32 = f32::from(x);\n            let pow = if self.fast_impl { 2 } else { 3 };\n            F::from(\n                0.5 * x_f32\n                    * (1. + ((2. / PI).sqrt() * (x_f32 + 0.044715 * x_f32.powi(pow))).tanh()),\n            )\n            .unwrap()\n        })\n    }\n}\n\nimpl<F> Test for GeluApproximateProblem<F>\nwhere\n    F: Datum + Float,\n    f32: From<F>,\n{\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.reference().into_tensor();\n        let mut model = self.tract()?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n\n        let mut output = runtime.prepare(model)?.run(tvec!(self.input.clone().into_tvalue()))?;\n        let output = output.remove(0).into_tensor();\n\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<GeluApproximateProblem<f32>>(\"proptest_f32\", ());\n    suite.add_arbitrary::<GeluApproximateProblem<f16>>(\"proptest_f16\", ());\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/lib.rs",
    "content": "#![allow(clippy::manual_is_multiple_of)]\nuse infra::TestSuite;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::num_traits::Float;\nuse tract_core::ops::cnn::*;\nuse tract_core::ops::nn::*;\nuse tract_ndarray::*;\n\npub mod apply_rope;\npub mod bin_einsum;\npub mod binary;\npub mod conv_f16;\npub mod conv_f32;\npub mod conv_q;\npub mod deconv;\npub mod downsample;\npub mod elmwise;\npub mod gelu_approximate;\npub mod matmul_q40;\npub mod q_binary;\npub mod q_elmwise;\npub mod q_flavours;\npub mod q_helpers;\npub mod rms_norm;\npub mod scaled_masked_softmax;\npub mod sdpa;\npub mod silu;\npub mod slice;\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite: TestSuite = Default::default();\n    suite.add(\"bin_einsum\", bin_einsum::suite()?);\n    suite.add(\"conv_f16\", conv_f16::suite()?);\n    suite.add(\"conv_f32\", conv_f32::suite()?);\n    suite.add(\"conv_q\", conv_q::suite()?);\n    suite.add(\"deconv\", deconv::suite()?);\n    suite.add(\"downsample\", downsample::suite()?);\n    suite.add(\"matmul_q40\", matmul_q40::suite()?);\n    suite.add(\"q_flavours\", q_flavours::suite()?);\n    suite.add(\"rms_norm\", rms_norm::suite()?);\n    suite.add(\"apply_rope\", apply_rope::suite()?);\n    suite.add(\"gelu_approximate\", gelu_approximate::suite()?);\n    suite.add(\"scaled_masked_softmax\", scaled_masked_softmax::suite()?);\n    suite.add(\"sdpa\", sdpa::suite()?);\n    suite.add(\"silu\", silu::suite()?);\n    suite.add(\"slice\", slice::suite()?);\n    suite.add(\"q_binary\", q_binary::suite()?);\n    suite.add(\"q_elmwise\", q_elmwise::suite()?);\n    suite.add(\"binary\", binary::suite()?);\n    suite.add(\"elmwise\", elmwise::suite()?);\n    Ok(suite)\n}\n\npub fn tensor<'a, F: Datum + Float>(\n    shape: impl IntoIterator<Item = &'a usize>,\n) -> BoxedStrategy<ArrayD<F>> {\n    let shape = shape.into_iter().copied().collect::<Vec<_>>();\n    let len = shape.iter().product::<usize>();\n    vec((-10i8..=10i8).prop_map(|i| F::from(i).unwrap()), len..=len)\n        .prop_map(move |vec| ArrayD::from_shape_vec(shape.to_vec(), vec).unwrap())\n        .boxed()\n}\npub fn qtensor(shape: Vec<usize>) -> BoxedStrategy<ArrayD<i8>> {\n    let len = shape.iter().product::<usize>();\n    vec(any::<i8>(), len..=len)\n        .prop_map(move |vec| ArrayD::from_shape_vec(shape.clone(), vec).unwrap())\n        .boxed()\n}\n\npub fn shapes(rank: usize) -> BoxedStrategy<(Vec<usize>, Vec<usize>)> {\n    vec((1usize..5, 0usize..5).prop_map(|(k, exceed)| (k, k + exceed)), rank..=rank)\n        .prop_map(|v| v.into_iter().unzip())\n        .boxed()\n}\n\npub fn data_format() -> impl Strategy<Value = DataFormat> {\n    prop_oneof![\n        Just(DataFormat::CHW),\n        Just(DataFormat::HWC),\n        Just(DataFormat::NCHW),\n        Just(DataFormat::NHWC)\n    ]\n}\n\npub fn kernel_format() -> impl Strategy<Value = KernelFormat> {\n    prop_oneof![\n        Just(KernelFormat::OIHW),\n        /* Just(KernelFormat::OHWI), */ Just(KernelFormat::HWIO)\n    ]\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/matmul_q40.rs",
    "content": "use std::fmt;\n\nuse infra::{Test, TestResult, TestSuite};\nuse proptest::prelude::*;\nuse proptest::strategy::BoxedStrategy;\nuse tract_core::internal::*;\nuse tract_core::ndarray::Ix2;\nuse tract_core::ops::array::{Pad, PadMode};\nuse tract_core::ops::konst::Const;\nuse tract_core::tract_linalg::block_quant::{\n    BlockQuant, BlockQuantFact, BlockQuantStorage, Q4_0, Q8_1,\n};\nuse tract_ndarray::{ArrayD, Axis};\n\nuse tract_core::ops::einsum::EinSum;\n\n#[derive(Debug, Clone, Default)]\npub struct MatmulQ40ProblemParams {\n    weights_in_b: bool,\n}\n\n#[derive(Clone)]\npub struct MatmulQ40Problem {\n    a: Tensor,\n    b: Tensor,\n    weights_in_b: bool,\n}\n\nimpl std::fmt::Debug for MatmulQ40Problem {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"a:{:?} b:{:?} weights_in_b:{:?}\", self.a, self.b, self.weights_in_b)\n    }\n}\n\nimpl Arbitrary for MatmulQ40Problem {\n    type Parameters = MatmulQ40ProblemParams;\n    type Strategy = BoxedStrategy<MatmulQ40Problem>;\n\n    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {\n        (1..10usize, 1..128usize, 1..10usize)\n            .prop_flat_map(|(m, k, n)| {\n                let a = mm_q40_tensor(&[m, k]);\n                let b = mm_q40_tensor(&[n, k]);\n\n                (a, b)\n            })\n            .prop_map(move |(a, b)| MatmulQ40Problem { a, b, weights_in_b: params.weights_in_b })\n            .boxed()\n    }\n}\n\nfn mm_q40_tensor(shape: &[usize]) -> BoxedStrategy<Tensor> {\n    let len = shape.iter().product::<usize>();\n    let shape: Vec<usize> = shape.into();\n    proptest::collection::vec((-100i8..=100i8).prop_map(|i| i as f32 / 100f32), len..=len)\n        .prop_map(move |vec| ArrayD::from_shape_vec(shape.clone(), vec).unwrap().into_tensor())\n        .boxed()\n}\n\nimpl MatmulQ40Problem {\n    fn pad_tensor(a: &Tensor, k_axis: usize) -> TractResult<Tensor> {\n        let (mn, k) = (a.shape()[1 - k_axis], a.shape()[k_axis]);\n        let shape =\n            if k_axis == 0 { [k.next_multiple_of(32), mn] } else { [mn, k.next_multiple_of(32)] };\n        let mut padded_a = Tensor::zero::<f32>(&shape)?;\n        {\n            let mut padded_a_plain = padded_a.try_as_plain_mut()?;\n            padded_a_plain\n                .to_array_view_mut::<f32>()?\n                .slice_axis_move(Axis(k_axis), (0..k).into())\n                .assign(&a.to_plain_array_view::<f32>()?);\n        };\n\n        Ok(padded_a)\n    }\n\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n\n        let padded_a = Self::pad_tensor(&self.a, 1)?;\n\n        let quant_a = Q4_0.quant_f32(padded_a.try_as_plain()?.as_slice::<f32>()?)?;\n\n        let m = padded_a.shape()[0];\n        let k = padded_a.shape()[1];\n        let bqs = BlockQuantStorage::new(Box::new(Q4_0), m, k, Arc::new(quant_a))?;\n        let bqf = BlockQuantFact::new(Box::new(Q4_0), tvec!(1, m, k));\n        let packed_a = Arc::new(bqs.into_tensor_with_shape(f32::datum_type(), &[1, m, k]));\n\n        let a =\n            model.wire_node(\"a\", Const::new_with_exotic_fact(packed_a, Box::new(bqf))?, &[])?[0];\n        let b = model.add_source(\"b\", TypedFact::shape_and_dt_of(&self.b))?;\n\n        let k = self.b.shape()[1];\n        let padded_b = model.wire_node(\n            \"pad_b\",\n            Pad::new(\n                vec![(0, 0), (0, k.next_multiple_of(32) - k)],\n                PadMode::Constant(rctensor0(0f32)),\n            ),\n            &[b],\n        )?[0];\n\n        let inputs = if !self.weights_in_b { [a, padded_b] } else { [padded_b, a] };\n        // Block-quant tensor is rank 3 [G=1, rows, K]; add group dim to its axes\n        let axes_str = if !self.weights_in_b { \"gmk,nk->mn\" } else { \"mk,gnk->mn\" };\n        let output = model.wire_node(\n            \"einsum\",\n            EinSum { axes: axes_str.parse()?, operating_dt: f32::datum_type(), q_params: None },\n            &inputs,\n        )?;\n\n        model.select_output_outlets(&output)?;\n        //let test = model.node_by_name(\"einsum\")?.op.as_op().downcast_ref::<EinSum>().unwrap();\n\n        //let test1 = model.node_by_name(\"einsum\")?.op.as_op().downcast_ref::<EinSum>().unwrap();\n        //dbg!(&test1.axes);\n        Ok(model)\n    }\n\n    fn reference(&self, simulate_q81_activation_loss: bool) -> TractResult<Tensor> {\n        let padded_a = Self::pad_tensor(&self.a, 1)?;\n        let quant_dequant_a = Q4_0.simulate_precision_loss(padded_a, 1)?;\n\n        // The GGML CUDA kernel internally quantizes activations to Q8_1.\n        // When testing against such a runtime, the reference must account\n        // for that additional precision loss.\n        let quant_dequant_b = if simulate_q81_activation_loss {\n            let padded_b = Self::pad_tensor(&self.b, 1)?;\n            Q8_1.simulate_precision_loss(padded_b, 1)?\n        } else {\n            self.b.clone()\n        };\n\n        let mut a_view = quant_dequant_a\n            .to_plain_array_view::<f32>()?\n            .slice_axis_move(Axis(1), (0..self.a.shape()[1]).into());\n        let mut b_view = quant_dequant_b\n            .to_plain_array_view::<f32>()?\n            .slice_axis_move(Axis(1), (0..self.b.shape()[1]).into());\n\n        if self.weights_in_b {\n            (a_view, b_view) = (b_view, a_view);\n        }\n        let c = a_view.into_dimensionality::<Ix2>()?.dot(&b_view.into_dimensionality::<Ix2>()?.t());\n        Ok(c.into_tensor())\n    }\n}\n\nimpl Test for MatmulQ40Problem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        _approx: Approximation,\n    ) -> TestResult {\n        let uses_q81_activations = runtime.name().contains(\"cuda\");\n        let reference = self.reference(uses_q81_activations)?;\n        //dbg!(&reference);\n        let mut model = self.tract()?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        let mut inputs = tvec![];\n\n        inputs.push(self.b.clone().into());\n\n        let mut output = runtime.prepare(model)?.run(inputs)?;\n        let output = output.remove(0).into_tensor();\n        output.close_enough(&reference, Approximation::SuperApproximate)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<MatmulQ40Problem>(\"proptest\", MatmulQ40ProblemParams::default());\n    suite.add_arbitrary::<MatmulQ40Problem>(\n        \"proptest_weights_in_b\",\n        MatmulQ40ProblemParams { weights_in_b: true },\n    );\n\n    suite.add(\n        \"minimal_inputs\",\n        MatmulQ40Problem { a: tensor2(&[[0f32]]), b: tensor2(&[[0f32]]), weights_in_b: false },\n    );\n\n    suite.add(\n        \"minimal_matvec\",\n        MatmulQ40Problem {\n            a: tensor2(&[[-1f32]]),\n            b: tensor2(&[[0f32], [-1f32]]),\n            weights_in_b: false,\n        },\n    );\n\n    suite.add(\n        \"minimal_matvec_weights_in_b_0\",\n        MatmulQ40Problem {\n            a: tensor2(&[[0f32, 1f32]]),\n            b: tensor2(&[[0f32, 1f32]]),\n            weights_in_b: true,\n        },\n    );\n\n    //  a:1,1,F32 0 b:1,1,F32 0\n    suite.add(\n        \"minimal_matvec_weights_in_b_1\",\n        MatmulQ40Problem { a: tensor2(&[[0f32]]), b: tensor2(&[[0f32]]), weights_in_b: true },\n    );\n\n    suite.add(\n        \"minimal_matvec_weights_in_b_2\",\n        MatmulQ40Problem {\n            a: tensor2(&[[0f32], [0f32]]),\n            b: tensor2(&[[0f32]]),\n            weights_in_b: true,\n        },\n    );\n\n    // Reduced from proptest — k=87 (not a multiple of 32) triggers Q8_1\n    // activation quantization mismatch between CUDA GGML kernel and CPU reference.\n    suite.add(\"proptest_reduced_k87\", {\n        #[rustfmt::skip]\n        let a_row2: &[f32] = &[\n            -0.69, -0.19, 0.0, 0.07, 0.0, 0.19, 0.0, -0.19, -0.94, 0.0,\n            0.82, 0.32, 0.0, 0.32, -0.07, -0.07, 0.69, -0.07, -0.98, 0.19,\n            0.56, -0.56, 0.0, -0.68, 0.68, -0.19, -0.07, 0.19, -0.07, -0.19,\n            0.8, -0.56, 0.57, -0.07, 0.19, 0.82, -0.32, -0.32, 0.0, 0.07,\n            0.0, 0.0, -0.82, 0.07, 0.0, -0.44, 0.44, 0.32, 0.07, 0.57,\n            0.57, 0.0, 0.0, 0.57, 0.44, -0.07, 0.0, 0.0, 0.82, 0.69,\n            0.32, -0.82, 0.44, 0.99, 0.18, 0.42, 0.66, 0.3, 0.0, 0.66,\n            0.78, 0.0, -0.43, 0.18, 0.3, 0.0, 0.0, 0.78, -0.43, 0.66,\n            0.0, -0.78, 0.0, -0.95, 0.18, 0.66, 0.3,\n        ];\n        let mut a_data = vec![0f32; 3 * 87];\n        a_data[2 * 87..].copy_from_slice(a_row2);\n        let a = Tensor::from_shape(&[3, 87], &a_data).unwrap();\n\n        #[rustfmt::skip]\n        let b_row1: &[f32] = &[\n            -0.34, 0.54, -0.3, 0.32, -0.3, -0.2, 0.0, -0.08, -1.0, -0.04,\n            0.91, 0.43, 0.0, 0.65, -0.34, -0.46, 0.76, 0.0, 0.0, 0.0,\n            -0.18, 0.65, 0.1, 0.58, -0.98, 0.54, 0.06, 0.0, 0.0, 0.17,\n            -0.71, 0.0, 0.0, 0.0, 0.0, 0.97, 0.0, 0.21, 0.07, -0.01,\n            -0.54, -0.12, 0.0, 0.0, 0.2, 0.21, -0.6, -0.09, -0.15, -0.41,\n            0.0, 0.0, 0.0, -0.25, 0.0, 0.0, 0.0, 0.0, -0.54, -0.8,\n            0.0, -0.28, 0.0, -0.71, 0.98, -0.21, 0.0, 0.0, 0.0, -0.61,\n            -0.32, 0.19, 0.16, 0.16, 0.0, 0.0, 0.0, 0.06, 0.36, 0.09,\n            0.06, -0.4, -0.04, -0.51, 0.09, 0.73, 0.02,\n        ];\n        let mut b_data = vec![0f32; 7 * 87];\n        b_data[87..2 * 87].copy_from_slice(b_row1);\n        let b = Tensor::from_shape(&[7, 87], &b_data).unwrap();\n\n        MatmulQ40Problem { a, b, weights_in_b: false }\n    });\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/q_binary.rs",
    "content": "use infra::{Test, TestSuite};\nuse proptest::prelude::*;\nuse tract_core::internal::*;\n\nuse crate::q_helpers::*;\n\n#[derive(Debug, Clone)]\nstruct QBinaryOpProblem {\n    operator: tract_core::ops::binary::TypedBinOp,\n    tensor_a: Tensor,\n    tensor_b: Tensor,\n    c_dt: DatumType,\n}\n\nimpl Default for QBinaryOpProblem {\n    fn default() -> QBinaryOpProblem {\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::mul(),\n            tensor_a: Tensor::default(),\n            tensor_b: Tensor::default(),\n            c_dt: DatumType::QU8(QParams::ZpScale { zero_point: 0, scale: 1. }),\n        }\n    }\n}\n\nimpl QOpProblem for QBinaryOpProblem {\n    fn reference_float_ops(&self) -> TractResult<Tensor> {\n        let a = self.tensor_a.cast_to::<f32>()?.clone().into_owned();\n        let b = self.tensor_b.cast_to::<f32>()?.clone().into_owned();\n        Ok(self.operator.eval(tvec![a.into_tvalue(), b.into_tvalue()])?.remove(0).into_tensor())\n    }\n}\n\nimpl Arbitrary for QBinaryOpProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<QBinaryOpProblem>;\n\n    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {\n        let tested_operators = prop_oneof![\n            Just(tract_core::ops::math::mul()),\n            Just(tract_core::ops::math::div()),\n            Just(tract_core::ops::math::add()),\n            Just(tract_core::ops::math::sub()),\n            Just(tract_core::ops::math::min()),\n            Just(tract_core::ops::math::max()),\n        ];\n        (\n            (1..20usize),\n            any::<bool>(),\n            any::<bool>(),\n            any::<bool>(),\n            (1..4usize),\n            (1..4usize),\n            (1..4usize),\n            tested_operators,\n        )\n            .prop_flat_map(|(len, a_signed, b_signed, c_signed, a_scale, b_scale, c_scale, op)| {\n                let a_dt = pick_signed_datum(a_signed);\n                let b_dt = pick_signed_datum(b_signed);\n                let c_dt = pick_signed_datum(c_signed);\n                fn just_scale(scale: usize) -> Just<f32> {\n                    Just(scale as f32 * 0.5)\n                }\n                (\n                    // tensor a\n                    Just(a_dt),\n                    qtensor(vec![1], a_dt),\n                    just_scale(a_scale),\n                    qtensor(vec![len], a_dt),\n                    // tensor b\n                    Just(b_dt),\n                    qtensor(vec![1], b_dt),\n                    just_scale(b_scale),\n                    qtensor(vec![len], b_dt),\n                    // dt of c\n                    Just(c_dt),\n                    qtensor(vec![1], c_dt),\n                    just_scale(c_scale),\n                    Just(op),\n                )\n            })\n            .prop_map(\n                |(\n                    a_dt,\n                    a_zp,\n                    a_scale,\n                    a_values,\n                    b_dt,\n                    b_zp,\n                    b_scale,\n                    b_values,\n                    c_dt,\n                    c_zp,\n                    c_scale,\n                    op,\n                )| {\n                    let tensor_a =\n                        build_qtensor(a_values.into_tensor(), a_dt, a_zp.into_tensor(), a_scale);\n                    let tensor_b =\n                        build_qtensor(b_values.into_tensor(), b_dt, b_zp.into_tensor(), b_scale);\n                    let c_dt = c_dt.quantize(QParams::ZpScale {\n                        zero_point: c_zp.into_tensor().cast_to_scalar::<i32>().unwrap(),\n                        scale: c_scale,\n                    });\n                    QBinaryOpProblem { operator: op.to_owned(), tensor_a, tensor_b, c_dt }\n                },\n            )\n            .prop_filter(\"div does not allow 0 divisor\", |q_prob| {\n                !(q_prob.operator.name().to_string().as_str().to_lowercase() == \"div\"\n                    && q_prob\n                        .tensor_b\n                        .to_owned()\n                        .cast_to_dt(DatumType::F32)\n                        .unwrap()\n                        .try_as_plain()\n                        .unwrap()\n                        .to_array_view()\n                        .unwrap()\n                        .iter()\n                        .any(|x: &f32| *x == 0.0))\n            })\n            .boxed()\n    }\n}\n\nimpl Test for QBinaryOpProblem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> infra::TestResult {\n        let mut model = TypedModel::default();\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n\n        let a = model.add_source(\n            \"a\",\n            TypedFact::dt_shape(self.tensor_a.datum_type(), self.tensor_a.shape()),\n        )?;\n        let b = model.add_const(\"b\", self.tensor_b.clone().into_arc_tensor())?;\n        // we need to wire correctly output to the provided operator {\n        let mut op = self.operator.clone();\n        op.1 = Some(self.c_dt);\n        // }\n        let c = model.wire_node(\"c\", op, &[a, b])?[0];\n        model.select_output_outlets(&[c])?;\n\n        let result = runtime\n            .prepare(model)\n            .context(\"Preparing model for runtime\")?\n            .run(tvec![self.tensor_a.clone().into_tvalue()])\n            .context(\"Running model with runtime\")?\n            .remove(0)\n            .into_tensor();\n        self.check_ref_with_approx(result, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n    suite.add_arbitrary::<QBinaryOpProblem>(\"proptest\", ());\n    // simplification 0 at declutter constant\n    suite.add(\n        \"trivial_mul_0_case\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::mul(),\n            tensor_a: qu8_tensor0(0u8, 0, 1.)?,\n            tensor_b: qu8_tensor0(0u8, 0, 1.)?,\n            c_dt: qu8_dt(0, 1.),\n        },\n    );\n\n    suite.add(\n        \"trivial_mul_as_qu8_overflow_clamp\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::mul(),\n            tensor_a: qu8_tensor1(&[1_u8, 2, 3, 128], 0, 1.)?,\n            tensor_b: qu8_tensor1(&[4u8], 0, 1.)?,\n            c_dt: qu8_dt(0, 1.),\n        },\n    );\n\n    suite.add(\n        \"trivial_mul_as_qu8_non_neutral_scale_and_offset\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::mul(),\n            tensor_a: qu8_tensor1(&[1_u8, 2, 3, 128], 3, 2.)?,\n            tensor_b: qu8_tensor1(&[4u8], 3, 2.)?,\n            c_dt: qu8_dt(3, 2.),\n        },\n    );\n\n    suite.add(\n        \"trivial_mul_as_qu8_non_aligned_scale_and_offset\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::mul(),\n            tensor_a: qu8_tensor1(&[3_u8, 4, 10, 25], 3, 4.5)?,\n            tensor_b: qu8_tensor1(&[6u8], 4, 2.5)?,\n            c_dt: qu8_dt(0, 1.),\n        },\n    );\n\n    suite.add(\n        \"trivial_max_0_as_qu8_non_aligned_scale_and_offset\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::max(),\n            tensor_a: qu8_tensor1(&[100_u8, 5, 110, 99], 100, 4.5)?,\n            tensor_b: qu8_tensor1(&[100u8], 100, 4.5)?,\n            c_dt: qu8_dt(0, 1.),\n        },\n    );\n\n    suite.add(\n        \"trivial_min_15_as_qu8_non_aligned_scale_and_offset\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::min(),\n            tensor_a: qu8_tensor1(&[5_u8, 9, 8, 20], 5, 4.)?,\n            tensor_b: qu8_tensor1(&[15u8], 10, 3.)?,\n            c_dt: qu8_dt(0, 1.),\n        },\n    );\n\n    suite.add(\n        \"trivial_max_15_as_qu8_non_aligned_scale_and_offset\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::max(),\n            tensor_a: qu8_tensor1(&[5_u8, 9, 8, 20], 5, 4.)?,\n            tensor_b: qu8_tensor1(&[15u8], 10, 3.)?,\n            c_dt: qu8_dt(0, 1.),\n        },\n    );\n\n    suite.add(\n        \"trivial_add_as_qu8_non_aligned_scale_and_offset\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::add(),\n            tensor_a: qu8_tensor1(&[3_u8, 4, 10, 25], 3, 4.5)?,\n            tensor_b: qu8_tensor1(&[6u8], 4, 2.5)?,\n            c_dt: qu8_dt(0, 1.),\n        },\n    );\n\n    suite.add(\n        \"trivial_div_as_qu8_non_aligned_scale_and_offset\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::div(),\n            tensor_a: qu8_tensor1(&[3_u8, 4, 10, 25], 3, 4.5)?,\n            tensor_b: qu8_tensor1(&[6u8], 4, 2.5)?,\n            c_dt: qu8_dt(0, 1.),\n        },\n    );\n\n    suite.add(\n        \"bug_invalid_to_scalar_0\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::max(),\n            tensor_a: qu8_tensor1(&[0u8, 0u8], 0, 0.5)?,\n            tensor_b: qu8_tensor1(&[0u8, 0u8], 0, 0.5)?,\n            c_dt: qu8_dt(0, 0.5),\n        },\n    );\n\n    suite.add(\n        \"bug_invalid_to_scalar_1\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::max(),\n            tensor_a: qu8_tensor1(&[0u8, 0u8], 0, 0.5)?,\n            tensor_b: qu8_tensor1(&[0u8, 0u8], 0, 0.5)?,\n            c_dt: qu8_dt(1, 0.5),\n        },\n    );\n\n    suite.add(\n        \"bug_aligned_dt_0\",\n        QBinaryOpProblem {\n            operator: tract_core::ops::math::mul(),\n            tensor_a: qu8_tensor1(&[0u8, 0, 0, 0, 0], 95, 1.5)?,\n            tensor_b: qu8_tensor1(&[0u8, 0, 0, 0, 0], 95, 1.5)?,\n            c_dt: qu8_dt(95, 1.5),\n        },\n    );\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/q_elmwise.rs",
    "content": "use infra::{Test, TestSuite};\nuse proptest::prelude::*;\nuse tract_core::internal::*;\n\nuse crate::q_helpers::*;\n\n#[derive(Debug, Clone)]\nstruct QElmWiseOpProblem {\n    operator: tract_core::ops::element_wise::ElementWiseOp,\n    tensor_input: Tensor,\n    out_dt: DatumType,\n}\n\nimpl QOpProblem for QElmWiseOpProblem {\n    fn reference_float_ops(&self) -> TractResult<Tensor> {\n        let inp = self.tensor_input.cast_to::<f32>()?.clone().into_owned();\n        Ok(self.operator.eval(tvec![inp.into_tvalue()])?.remove(0).into_tensor())\n    }\n}\n\nimpl Arbitrary for QElmWiseOpProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<QElmWiseOpProblem>;\n\n    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {\n        let tested_operators = prop_oneof![\n            Just(tract_core::ops::math::cos()),\n            Just(tract_core::ops::math::tanh()),\n            Just(tract_core::ops::nn::sigmoid())\n        ];\n        ((1..20usize), any::<bool>(), any::<bool>(), (1..10usize), (1..10usize), tested_operators)\n            .prop_flat_map(|(len, inp_signed, out_signed, inp_scale, out_scale, op)| {\n                let inp_dt = pick_signed_datum(inp_signed);\n                let out_dt = pick_signed_datum(out_signed);\n                fn just_scale(scale: usize) -> Just<f32> {\n                    Just(scale as f32 * 0.5)\n                }\n                (\n                    // tensor a\n                    Just(inp_dt),\n                    qtensor(vec![1], inp_dt),\n                    just_scale(inp_scale),\n                    qtensor(vec![len], inp_dt),\n                    // dt of c\n                    Just(out_dt),\n                    qtensor(vec![1], out_dt),\n                    just_scale(out_scale),\n                    Just(op),\n                )\n            })\n            .prop_map(|(inp_dt, inp_zp, inp_scale, inp_values, out_dt, out_zp, out_scale, op)| {\n                let tensor_input = build_qtensor(\n                    inp_values.into_tensor(),\n                    inp_dt,\n                    inp_zp.into_tensor(),\n                    inp_scale,\n                );\n                let out_dt = out_dt.quantize(QParams::ZpScale {\n                    zero_point: out_zp.into_tensor().cast_to_scalar::<i32>().unwrap(),\n                    scale: out_scale,\n                });\n                QElmWiseOpProblem { operator: op.to_owned(), tensor_input, out_dt }\n            })\n            .boxed()\n    }\n}\n\nimpl Test for QElmWiseOpProblem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> infra::TestResult {\n        let mut model = TypedModel::default();\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n\n        let inp = model.add_source(\n            \"inp\",\n            TypedFact::dt_shape(self.tensor_input.datum_type(), self.tensor_input.shape()),\n        )?;\n        // we need to wire correctly output to the provided operator {\n        let mut op = self.operator.clone();\n        op.1 = Some(self.out_dt);\n        // }\n        let out = model.wire_node(\"out\", op, &[inp])?[0];\n        model.select_output_outlets(&[out])?;\n\n        let result = runtime\n            .prepare(model)?\n            .run(tvec![self.tensor_input.clone().into_tvalue()])?\n            .remove(0)\n            .into_tensor();\n        self.check_ref_with_approx(result, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n    suite.add_arbitrary::<QElmWiseOpProblem>(\"proptest\", ());\n\n    suite.add(\n        \"tanh_sweep_case\",\n        QElmWiseOpProblem {\n            operator: tract_core::ops::math::tanh(),\n            tensor_input: qu8_tensor1(&(0u8..=100).collect::<Box<[u8]>>(), 50, 0.05)?,\n            out_dt: qu8_dt(127, 0.001),\n        },\n    );\n\n    suite.add(\n        \"cos_switch_qi8_to_qu8_case\",\n        QElmWiseOpProblem {\n            operator: tract_core::ops::math::cos(),\n            tensor_input: qi8_tensor1(&[-16], 39, 0.5)?,\n            out_dt: qu8_dt(2, 0.5),\n        },\n    );\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/q_flavours.rs",
    "content": "use infra::{Test, TestSuite};\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ops::quant::{offset_i8_as_u8, offset_u8_as_i8};\n\nuse crate::q_helpers::qtensor;\n\n#[derive(Debug, Clone, Default)]\nstruct QFlavoursProblem {\n    input: Tensor,\n}\n\nimpl Arbitrary for QFlavoursProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<QFlavoursProblem>;\n    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {\n        ((1..20usize), any::<bool>())\n            .prop_flat_map(|(len, signed)| {\n                let dt = if signed { DatumType::I8 } else { DatumType::U8 };\n                (Just(dt), qtensor(vec![1], dt), qtensor(vec![len], dt))\n            })\n            .prop_map(|(dt, zp, values)| {\n                let zp = zp.into_tensor().cast_to_scalar::<i32>().unwrap();\n                let mut values = values.into_tensor();\n                let dt = dt.quantize(QParams::ZpScale { zero_point: zp, scale: 1f32 });\n                unsafe {\n                    values.set_datum_type(dt);\n                }\n                QFlavoursProblem { input: values }\n            })\n            .boxed()\n    }\n}\n\nimpl Test for QFlavoursProblem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> infra::TestResult {\n        let mut model = TypedModel::default();\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        let wire = model.add_source(\"input\", TypedFact::shape_and_dt_of(&self.input))?;\n        let output = if self.input.datum_type().is_signed() {\n            model.wire_node(\"flavour\", offset_i8_as_u8(), &[wire])?\n        } else {\n            model.wire_node(\"flavour\", offset_u8_as_i8(), &[wire])?\n        };\n        model.select_output_outlets(&output)?;\n        let output = runtime\n            .prepare(model)?\n            .run(tvec![self.input.clone().into_tvalue()])?\n            .remove(0)\n            .into_tensor();\n        //dbg!(&output);\n        let reference = self.input.cast_to::<f32>()?;\n        let comparison = output.cast_to::<f32>()?;\n        comparison.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n    suite.add_arbitrary::<QFlavoursProblem>(\"proptest\", ());\n    suite.add(\n        \"trivial_0\",\n        QFlavoursProblem {\n            input: tensor0(0u8)\n                .cast_to_dt(\n                    u8::datum_type().quantize(QParams::ZpScale { zero_point: 0, scale: 1. }),\n                )\n                .unwrap()\n                .into_owned(),\n        },\n    );\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/q_helpers.rs",
    "content": "use proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_ndarray::*;\n\npub fn qtensor(shape: Vec<usize>, dt: DatumType) -> BoxedStrategy<Tensor> {\n    assert!(dt.unquantized() == dt);\n    let len = shape.iter().product::<usize>();\n    let range = if dt.is_signed() { -100..100i32 } else { 0..100i32 };\n    vec(range, len..=len)\n        .prop_map(move |v| {\n            ArrayD::from_shape_vec(shape.clone(), v)\n                .unwrap()\n                .into_tensor()\n                .cast_to_dt(dt.unquantized())\n                .unwrap()\n                .into_owned()\n        })\n        .boxed()\n}\n\npub fn build_qtensor(values: Tensor, dt: DatumType, zp: Tensor, scale: f32) -> Tensor {\n    let mut values = values;\n    let zp = zp.cast_to_scalar::<i32>().unwrap();\n    let dt = dt.quantize(QParams::ZpScale { zero_point: zp, scale });\n    unsafe {\n        values.set_datum_type(dt);\n    }\n    values\n}\n\npub fn pick_signed_datum(signed: bool) -> DatumType {\n    if signed { DatumType::I8 } else { DatumType::U8 }\n}\n\npub fn qu8_dt(zp: i32, scale: f32) -> DatumType {\n    u8::datum_type().with_zp_scale(zp, scale)\n}\n\npub fn qi8_dt(zp: i32, scale: f32) -> DatumType {\n    i8::datum_type().with_zp_scale(zp, scale)\n}\n\npub fn qu8_tensor(tensor: Tensor, zp: i32, scale: f32) -> TractResult<Tensor> {\n    Ok(tensor.cast_to_dt(qu8_dt(zp, scale))?.into_owned())\n}\n\npub fn qi8_tensor(tensor: Tensor, zp: i32, scale: f32) -> TractResult<Tensor> {\n    Ok(tensor.cast_to_dt(qi8_dt(zp, scale))?.into_owned())\n}\n\npub fn qu8_tensor0(value: u8, zp: i32, scale: f32) -> TractResult<Tensor> {\n    qu8_tensor(tensor0(value), zp, scale)\n}\n\npub fn qu8_tensor1(values: &[u8], zp: i32, scale: f32) -> TractResult<Tensor> {\n    qu8_tensor(tensor1(values), zp, scale)\n}\n\npub fn qi8_tensor1(values: &[i8], zp: i32, scale: f32) -> TractResult<Tensor> {\n    qi8_tensor(tensor1(values), zp, scale)\n}\n\npub trait QOpProblem {\n    fn reference_float_ops(&self) -> TractResult<Tensor>;\n\n    fn check_ref_with_approx(&self, result: Tensor, approx: Approximation) -> infra::TestResult {\n        let mut reference = self.reference_float_ops()?;\n        let out_dt = result.datum_type();\n        let (zero_point, scale) = out_dt.zp_scale();\n        let min_repr_val =\n            (out_dt.unquantized().min_value().cast_to_scalar::<f32>()? - zero_point as f32) * scale;\n        let max_repr_val =\n            (out_dt.unquantized().max_value().cast_to_scalar::<f32>()? - zero_point as f32) * scale;\n\n        reference\n            .try_as_plain_mut()?\n            .to_array_view_mut()?\n            .iter_mut()\n            .for_each(|x: &mut f32| *x = (*x).clamp(min_repr_val, max_repr_val));\n\n        let mut fp_results = result.cast_to::<f32>()?.into_owned();\n\n        let acceptable_scale_error_ratio = match approx {\n            Approximation::Exact => 0.,\n            Approximation::Approximate => 2.,\n            _ => 3.,\n        };\n        assert!(\n            tract_core::ndarray::Zip::from(fp_results.try_as_plain_mut()?.to_array_view_mut()?)\n                .and(reference.try_as_plain()?.to_array_view()?)\n                .all(|x: &mut f32, xref: &f32| {\n                    let closest_x = (*x).clamp(min_repr_val, max_repr_val);\n                    // core maximal accepted distance by default\n                    (xref - closest_x).abs() <= scale * acceptable_scale_error_ratio\n                })\n        );\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/rms_norm.rs",
    "content": "use std::fmt;\n\nuse infra::Test;\nuse infra::TestResult;\nuse infra::TestSuite;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ndarray::ArrayD;\nuse tract_core::num_traits::Float;\nuse tract_core::num_traits::FromPrimitive;\nuse tract_transformers::ops::rms_norm::RmsNorm;\n\nuse crate::tensor;\n\n#[derive(Clone)]\npub struct RmsNormProblem<F>\nwhere\n    F: Datum + Float,\n{\n    input: Tensor,\n    axis: usize,\n    eps: f32,\n    _phantom: PhantomData<F>,\n}\n\nimpl<F> std::fmt::Debug for RmsNormProblem<F>\nwhere\n    F: Datum + Float,\n{\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"Input:{:?} Axis:{:?} Epsilon:{:?}\", self.input, self.axis, self.eps)\n    }\n}\n\nimpl<F> Arbitrary for RmsNormProblem<F>\nwhere\n    F: Datum + Float,\n{\n    type Parameters = ();\n    type Strategy = BoxedStrategy<RmsNormProblem<F>>;\n\n    fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {\n        (0usize..3, 0usize..3)\n            .prop_flat_map(|(left, right)| {\n                let axis = left;\n                let shape_len = usize::min(left + right, 4);\n                let iter_ax_dim = 1usize..50;\n                let other_dim = 1usize..5;\n                (iter_ax_dim, vec(other_dim, shape_len..=shape_len), Just(axis))\n            })\n            .prop_flat_map(|(iter_dim, mut shape, axis)| {\n                shape.insert(axis, iter_dim);\n                let input = tensor::<F>(&shape);\n                (input, Just(axis), 0f32..=1e6).prop_map(|(input, axis, eps)| Self {\n                    input: input.into(),\n                    axis,\n                    eps: eps / 1e5,\n                    _phantom: PhantomData,\n                })\n            })\n            .boxed()\n    }\n}\n\nimpl<F> RmsNormProblem<F>\nwhere\n    F: Datum + Float + FromPrimitive,\n{\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n        let input = model.add_source(\"input\", TypedFact::shape_and_dt_of(&self.input))?;\n\n        let output = model.wire_node(\n            \"rms_norm\",\n            RmsNorm { axis: self.axis, eps: tensor0(self.eps).into_arc_tensor() },\n            &[input],\n        )?;\n        model.select_output_outlets(&output)?;\n\n        model = model.into_decluttered()?;\n        Ok(model)\n    }\n\n    fn reference(&self) -> ArrayD<F> {\n        let input = self.input.cast_to::<f32>().unwrap();\n\n        let a = input.to_plain_array_view::<f32>().unwrap().to_owned();\n        let mean_square = a.pow2().mean_axis(tract_ndarray::Axis(self.axis)).unwrap();\n\n        let norm = mean_square\n            .mapv(|ms| (ms + self.eps).sqrt())\n            .insert_axis(tract_ndarray::Axis(self.axis));\n        let broadcasted_norm = norm.broadcast(a.raw_dim()).unwrap().to_owned();\n\n        (a / broadcasted_norm).mapv(|x| F::from(x).unwrap())\n    }\n}\n\nimpl<F> Test for RmsNormProblem<F>\nwhere\n    F: Datum + Float + FromPrimitive,\n{\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.reference().into_tensor();\n        //dbg!(&reference);\n        let mut model = self.tract()?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n\n        let mut output = runtime.prepare(model)?.run(tvec!(self.input.clone().into()))?;\n        let output = output.remove(0).into_tensor();\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<RmsNormProblem<f32>>(\"proptest_f32\", ());\n    suite.add_arbitrary::<RmsNormProblem<f16>>(\"proptest_f16\", ());\n\n    suite.add(\n        \"trivial_f32_0\",\n        RmsNormProblem::<f32> {\n            input: tensor1(&[0f32]),\n            axis: 0,\n            eps: 0f32,\n            _phantom: PhantomData,\n        },\n    );\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/scaled_masked_softmax.rs",
    "content": "use core::f32;\n\nuse infra::Test;\nuse infra::TestResult;\nuse infra::TestSuite;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ndarray::{Array5, ArrayD, Axis};\nuse tract_core::num_traits::{Float, Zero};\nuse tract_transformers::ops::scaled_masked_softmax::ScaledMaskedSoftmax;\n\nuse crate::tensor;\n\n#[derive(Debug, Clone)]\npub struct ScaledMaskedSoftmaxProblem<F>\nwhere\n    F: Datum + Float,\n{\n    input: ArrayD<F>,\n    mask: ArrayD<F>,\n    scale: F,\n}\n\nimpl<F> Arbitrary for ScaledMaskedSoftmaxProblem<F>\nwhere\n    F: Datum + Float,\n{\n    type Parameters = ();\n    type Strategy = BoxedStrategy<ScaledMaskedSoftmaxProblem<F>>;\n\n    fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {\n        // ScaledMaskSoftmax accepts ranks 2 to 5\n        (vec(1usize..20, 2..=5), any::<bool>(), any::<bool>())\n            .prop_flat_map(|(q_shape, broadcast_batch, broadcast_head)| {\n                let mut m_shape = q_shape.clone();\n                if broadcast_batch {\n                    m_shape[0] = 1\n                };\n                if broadcast_head {\n                    m_shape[1] = 1\n                };\n                (tensor::<F>(&q_shape), tensor::<f32>(&m_shape), -10..=10i32).prop_map(\n                    |(input, mask, scale)| {\n                        let mask = mask.mapv(|x| {\n                            if x >= 0. {\n                                F::from(0).unwrap()\n                            } else {\n                                F::from(f32::NEG_INFINITY).unwrap()\n                            }\n                        });\n                        let scale = if scale.is_zero() { 1.0 } else { scale as f32 / 10. };\n                        Self {\n                            input: input.into_dimensionality().unwrap(),\n                            mask: mask.into_dimensionality().unwrap(),\n                            scale: F::from(scale).unwrap(),\n                        }\n                    },\n                )\n            })\n            .boxed()\n    }\n}\n\nimpl<F> ScaledMaskedSoftmaxProblem<F>\nwhere\n    F: Datum + Float,\n    f32: From<F>,\n{\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n\n        let input = model\n            .add_source(\"input\", TypedFact::shape_and_dt_of(&self.input.clone().into_tensor()))?;\n        let mask = model\n            .add_source(\"mask\", TypedFact::shape_and_dt_of(&self.mask.clone().into_tensor()))?;\n\n        let output = model.wire_node(\n            \"scaled_masked_softmax\",\n            ScaledMaskedSoftmax {\n                scale: tensor0(self.scale).into_arc_tensor(),\n                post_softmax_mask: false,\n            },\n            &[input, mask],\n        )?;\n        model.select_output_outlets(&output)?;\n\n        model = model.into_decluttered()?;\n        Ok(model)\n    }\n\n    fn softmax(input: &Array5<F>, axis: usize) -> TractResult<Array5<F>> {\n        let axis = tract_ndarray::Axis(axis);\n\n        let max_per_axis = input.map_axis(axis, |lane| {\n            lane.fold(F::from(f32::NEG_INFINITY).unwrap(), |a, &b| a.max(b))\n        });\n\n        let shifted = input - &max_per_axis.insert_axis(axis);\n        let exp = shifted.mapv(F::exp);\n        let sum_exp = exp.sum_axis(axis);\n\n        let norm = sum_exp.insert_axis(axis);\n\n        Ok(&exp / &norm)\n    }\n\n    fn reference(&self) -> TractResult<ArrayD<F>> {\n        ensure!(self.input.ndim() == self.mask.ndim());\n        let mut input = self.input.view();\n        let mut mask = self.mask.clone();\n        while input.ndim() < 5 {\n            input.insert_axis_inplace(Axis(0));\n            mask.insert_axis_inplace(Axis(0));\n        }\n\n        let scaled_input = input.mapv(|x| x * self.scale);\n        let masked_input = scaled_input + mask;\n\n        let mut output = Self::softmax(&masked_input.into_dimensionality()?, 4)?.into_dyn();\n        while output.ndim() > self.input.ndim() {\n            output.index_axis_inplace(Axis(0), 0);\n        }\n        Ok(output)\n    }\n}\n\nimpl<F> Test for ScaledMaskedSoftmaxProblem<F>\nwhere\n    F: Datum + Float,\n    f32: From<F>,\n{\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        ensure!(!self.scale.is_zero());\n        let reference = self.reference()?.into_tensor();\n        let mut model = self.tract()?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        //dbg!(&self.input, &self.mask);\n        let mut output = runtime\n            .prepare(model)?\n            .run(tvec![self.input.clone().into_tvalue(), self.mask.clone().into_tvalue()])?;\n        let output = output.remove(0).into_tensor();\n        // dbg!(&reference, &output);\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<ScaledMaskedSoftmaxProblem<f32>>(\"proptest_f32\", ());\n    suite.add_arbitrary::<ScaledMaskedSoftmaxProblem<f16>>(\"proptest_f16\", ());\n\n    suite.add(\n        \"trivial_f32_0\",\n        ScaledMaskedSoftmaxProblem {\n            input: tensor4(&[[[[0f32]]]]).into_plain_array()?.into_dimensionality()?,\n            mask: tensor4(&[[[[0f32]]]]).into_plain_array()?.into_dimensionality()?,\n            scale: 1f32,\n        },\n    );\n\n    suite.add(\n        \"trivial_f32_1\",\n        ScaledMaskedSoftmaxProblem {\n            input: tensor4(&[[[[0f32, 0f32], [0f32, 0f32]]]])\n                .into_plain_array()?\n                .into_dimensionality()?,\n            mask: tensor4(&[[[[f32::NEG_INFINITY, 0f32], [0f32, 0f32]]]])\n                .into_plain_array()?\n                .into_dimensionality()?,\n            scale: 1f32,\n        },\n    );\n\n    suite.add(\n        \"trivial_f32_2\",\n        ScaledMaskedSoftmaxProblem {\n            input: arr4(&[[[[0f32, 0f32]]]]).into_dimensionality()?,\n            mask: arr4(&[[[[f32::NEG_INFINITY, 0f32]]]]).into_dimensionality()?,\n            scale: 1f32,\n        },\n    );\n\n    suite.add(\n        \"trivial_f32_3\",\n        ScaledMaskedSoftmaxProblem {\n            input: tensor4(&[[[[0f32, 0f32]]]]).into_plain_array()?.into_dimensionality()?,\n            mask: tensor4(&[[[[0f32, 0f32]]]]).into_plain_array()?.into_dimensionality()?,\n            scale: 1f32,\n        },\n    );\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/sdpa.rs",
    "content": "use infra::{Test, TestSuite};\nuse proptest::{\n    prelude::{Arbitrary, BoxedStrategy, Just, Strategy, any},\n    prop_oneof,\n};\nuse tract_core::internal::*;\nuse tract_core::ndarray::{ArrayD, ArrayView4, arr3};\nuse tract_core::num_traits::Float;\nuse tract_ndarray::{Array2, Array4, ArrayView2, Axis, Ix3, Ix4, IxDyn, s};\nuse tract_transformers::ops::sdpa::Sdpa;\n\n#[derive(Debug, Clone)]\npub struct SdpaProblemParams {\n    pub embed_dims: Vec<usize>,\n}\n\nimpl Default for SdpaProblemParams {\n    fn default() -> SdpaProblemParams {\n        SdpaProblemParams { embed_dims: vec![1, 2, 3] }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct SdpaProblem<F>\nwhere\n    F: Datum + Float,\n{\n    q: ArrayD<F>,\n    pub k: ArrayD<F>,\n    v: ArrayD<F>,\n    mask: Option<ArrayD<F>>,\n    scale: Option<f32>,\n    is_causal: bool,\n}\n\nimpl SdpaProblem<f32> {\n    fn to_f16(&self) -> SdpaProblem<f16> {\n        SdpaProblem {\n            q: self.q.mapv(f16::from_f32),\n            k: self.k.mapv(f16::from_f32),\n            v: self.v.mapv(f16::from_f32),\n            mask: self.mask.as_ref().map(|m| m.mapv(f16::from_f32)),\n            scale: self.scale,\n            is_causal: self.is_causal,\n        }\n    }\n}\n\nimpl<F> Arbitrary for SdpaProblem<F>\nwhere\n    F: Datum + Float,\n{\n    type Parameters = SdpaProblemParams;\n    type Strategy = BoxedStrategy<SdpaProblem<F>>;\n\n    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {\n        prop_oneof![\n            generate_3d_single_head::<F>(params.clone()),\n            generate_4d_group_query_att::<F>(params, 4, 4)\n        ]\n        .boxed()\n    }\n}\n\nfn generate_3d_single_head<F: Datum + Float>(\n    params: SdpaProblemParams,\n) -> BoxedStrategy<SdpaProblem<F>> {\n    use tract_ndarray::Axis;\n    generate_4d_group_query_att::<F>(params, 1, 1)\n        .prop_map(|mut gqa| {\n            gqa.q.index_axis_inplace(Axis(1), 0);\n            gqa.k.index_axis_inplace(Axis(1), 0);\n            gqa.v.index_axis_inplace(Axis(1), 0);\n            if let Some(m) = &mut gqa.mask {\n                m.index_axis_inplace(Axis(1), 0);\n            }\n            gqa\n        })\n        .boxed()\n}\n\nfn sdpa_tensor<F: Datum + Float>(shape: &[usize]) -> BoxedStrategy<ArrayD<F>> {\n    let len = shape.iter().product::<usize>();\n    let shape: Vec<usize> = shape.into();\n    proptest::collection::vec(\n        // (-80i8..=80i8).prop_map(|i| F::from(i as f32 / 100f32).unwrap()),\n        (-3..=3).prop_map(|x| F::from(x as f32 / 8.).unwrap()),\n        len..=len,\n    )\n    .prop_map(move |vec| ArrayD::from_shape_vec(shape.clone(), vec).unwrap())\n    .boxed()\n}\n\nfn generate_4d_group_query_att<F: Datum + Float>(\n    params: SdpaProblemParams,\n    max_heads_repeat_factor: usize,\n    max_kv_heads: usize,\n) -> BoxedStrategy<SdpaProblem<F>> {\n    (\n        1..3usize,\n        1..max_heads_repeat_factor + 1,\n        1..max_kv_heads + 1,\n        0..5usize,\n        2..5usize,\n        0..params.embed_dims.len(),\n    )\n        .prop_flat_map(move |(b, repeat_factor, n_kv_heads, past_seq_len, seq_len, embed_idx)| {\n            let embed = params.embed_dims[embed_idx];\n            let n_q_heads = repeat_factor * n_kv_heads;\n            let q = sdpa_tensor::<F>(&[b, n_q_heads, seq_len, embed]);\n            let k = sdpa_tensor::<F>(&[b, n_kv_heads, past_seq_len + seq_len, embed]);\n            let v = sdpa_tensor::<F>(&[b, n_kv_heads, past_seq_len + seq_len, embed]);\n\n            let scale_strategy = prop_oneof![Just(None), (0.1f32..1.0).prop_map(Some)];\n            let mask = (any::<bool>(), any::<bool>(), any::<bool>());\n            (mask, Just(past_seq_len), Just(seq_len), q, k, v, scale_strategy)\n        })\n        .prop_flat_map(|((full_b, full_h, causal), past_seq_len, seq_len, q, k, v, scale)| {\n            let mask_strategy = if causal {\n                Just(None).boxed()\n            } else {\n                let mask_b = if full_b { q.shape()[0] } else { 1 };\n                let mask_h = if full_h { q.shape()[1] } else { 1 };\n                prop_oneof![\n                    Just(None),\n                    sdpa_tensor(&[mask_b, mask_h, seq_len, past_seq_len + seq_len]).prop_map(Some)\n                ]\n                .boxed()\n            };\n\n            (Just(q), Just(k), Just(v), Just(scale), Just(causal), mask_strategy)\n        })\n        .prop_map(|(q, k, v, scale, is_causal, mask)| SdpaProblem {\n            q,\n            k,\n            v,\n            mask: mask.map(|m| m.into_dimensionality().unwrap()),\n            scale,\n            is_causal,\n        })\n        .boxed()\n}\n\nimpl<F> SdpaProblem<F>\nwhere\n    F: Datum + Float + Copy + 'static,\n{\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n\n        let q = self.q.clone().into_tensor();\n        let k = self.k.clone().into_tensor();\n        let v = self.v.clone().into_tensor();\n\n        ensure!(q.rank() == k.rank());\n        ensure!(q.rank() == v.rank());\n\n        let scale = self.scale.map(tensor0);\n\n        let q_in = model.add_source(\"Q\", TypedFact::shape_and_dt_of(&q))?;\n        let k_in = model.add_source(\"K\", TypedFact::shape_and_dt_of(&k))?;\n        let v_in = model.add_source(\"V\", TypedFact::shape_and_dt_of(&v))?;\n        let mut inputs = vec![q_in, k_in, v_in];\n\n        if let Some(mask) = &self.mask {\n            ensure!(mask.ndim() == q.rank());\n            let mask_in = model\n                .add_source(\"mask\", TypedFact::shape_and_dt_of(&mask.clone().into_tensor()))?;\n            inputs.push(mask_in);\n        }\n\n        let dt = q.datum_type();\n        let output = model.wire_node(\n            \"SDPA\",\n            Sdpa {\n                scale,\n                datum_type: dt,\n                acc_datum_type: DatumType::F32,\n                is_causal: self.is_causal,\n            },\n            &inputs,\n        )?;\n        model.select_output_outlets(&output)?;\n        model.into_decluttered()\n    }\n\n    fn softmax(input: &Array2<f32>, axis: usize) -> Array2<f32> {\n        let axis = tract_ndarray::Axis(axis);\n\n        let max_per_axis =\n            input.map_axis(axis, |lane| lane.fold(f32::NEG_INFINITY, |a, &b| a.max(b)));\n\n        let shifted = input - &max_per_axis.insert_axis(axis);\n        let exp = shifted.mapv(f32::exp);\n        let sum_exp = exp.sum_axis(axis);\n\n        let norm = sum_exp.insert_axis(axis);\n\n        &exp / &norm\n    }\n\n    fn scaled_dot_product_attention_2d(\n        queries: &ArrayView2<F>,\n        keys: &ArrayView2<F>,\n        values: &ArrayView2<F>,\n        mask: Option<&ArrayView2<f32>>,\n        scale: Option<f32>,\n        is_causal: bool,\n    ) -> Array2<F> {\n        let d_k = keys.shape()[1] as f32;\n        let scale_factor = scale.unwrap_or(1.0 / d_k.sqrt());\n        let queries = queries.mapv(|q| q * F::from(scale_factor).unwrap());\n\n        let queries_f32 = queries.mapv(|q| q.to_f32().unwrap());\n        let keys_f32 = keys.mapv(|k| k.to_f32().unwrap());\n        let values_f32 = values.mapv(|v| v.to_f32().unwrap());\n\n        let mut scaled_input = queries_f32.dot(&keys_f32.t());\n\n        if is_causal {\n            let (q_len, k_len) = (queries.nrows(), keys.nrows());\n            let p = k_len.saturating_sub(q_len);\n            scaled_input.indexed_iter_mut().for_each(|((r, c), z)| {\n                if c > p + r {\n                    *z = f32::NEG_INFINITY;\n                }\n            });\n        }\n\n        if let Some(m) = mask {\n            scaled_input += m;\n        }\n\n        let att_weights = Self::softmax(&scaled_input, 1);\n        att_weights.dot(&values_f32).mapv(|r| F::from(r).unwrap())\n    }\n\n    fn reference_4d(\n        &self,\n        q: ArrayView4<F>,\n        k: ArrayView4<F>,\n        v: ArrayView4<F>,\n        mask: Option<ArrayView4<f32>>,\n    ) -> Array4<F> {\n        let [b, q_heads, seq_len, _] = q.shape() else { unreachable!() };\n        let [_, kv_heads, _, v_emb] = v.shape() else { unreachable!() };\n        let mut output = Array4::<F>::zeros((*b, *q_heads, *seq_len, *v_emb));\n        let repeat_factor = q_heads / kv_heads;\n\n        for batch_idx in 0..*b {\n            for kv_head_idx in 0..*kv_heads {\n                for q_head_idx_in_group in 0..repeat_factor {\n                    let q_head_idx = q_head_idx_in_group + repeat_factor * kv_head_idx;\n\n                    let q_slice = q.slice(s![batch_idx, q_head_idx, .., ..]);\n                    let k_slice = k.slice(s![batch_idx, kv_head_idx, .., ..]);\n                    let v_slice = v.slice(s![batch_idx, kv_head_idx, .., ..]);\n                    let mask_slice: Option<ArrayView2<f32>> = mask.as_ref().map(|m| {\n                        m.slice(s![\n                            batch_idx.min(m.shape()[0] - 1),\n                            q_head_idx.min(m.shape()[1] - 1),\n                            ..,\n                            ..\n                        ])\n                    });\n\n                    let out2 = Self::scaled_dot_product_attention_2d(\n                        &q_slice,\n                        &k_slice,\n                        &v_slice,\n                        mask_slice.as_ref(),\n                        self.scale, // still f32\n                        self.is_causal,\n                    );\n                    output.slice_mut(s![batch_idx, q_head_idx, .., ..]).assign(&out2);\n                }\n            }\n        }\n        output\n    }\n\n    fn reference(&self) -> TractResult<ArrayD<F>> {\n        let mask: Option<ArrayD<f32>> = self.mask.as_ref().map(|m| m.mapv(|x| x.to_f32().unwrap()));\n\n        match self.q.ndim() {\n            3 => {\n                let q = self.q.view().into_dimensionality::<Ix3>()?.insert_axis(Axis(1));\n                let k = self.k.view().into_dimensionality::<Ix3>()?.insert_axis(Axis(1));\n                let v = self.v.view().into_dimensionality::<Ix3>()?.insert_axis(Axis(1));\n                let mask = mask\n                    .as_ref()\n                    .map(|m| {\n                        TractResult::Ok(m.view().into_dimensionality::<Ix3>()?.insert_axis(Axis(1)))\n                    })\n                    .transpose()?;\n                let out_4d = self.reference_4d(q, k, v, mask);\n                Ok(out_4d.remove_axis(Axis(1)).into_dyn())\n            }\n            4 => {\n                let q = self.q.view().into_dimensionality::<Ix4>().unwrap();\n                let k = self.k.view().into_dimensionality::<Ix4>().unwrap();\n                let v = self.v.view().into_dimensionality::<Ix4>().unwrap();\n                let mask =\n                    mask.as_ref().map(|m| m.view().into_dimensionality::<Ix4>()).transpose()?;\n                Ok(self.reference_4d(q, k, v, mask).into_dyn())\n            }\n            _ => unreachable!(),\n        }\n    }\n}\n\nimpl<F> Test for SdpaProblem<F>\nwhere\n    F: Datum + Float + Copy + 'static,\n{\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn tract_core::runtime::Runtime,\n        approx: Approximation,\n    ) -> infra::TestResult {\n        let reference = self.reference().context(\"Running reference\")?.into_tensor();\n        let mut model = self.tract().context(\"Wiring tract test model\")?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n\n        let mut inputs = tvec![\n            self.q.clone().into_tvalue(),\n            self.k.clone().into_tvalue(),\n            self.v.clone().into_tvalue()\n        ];\n        if let Some(mask) = &self.mask {\n            inputs.push(mask.clone().into_tvalue());\n        }\n        let mut output = runtime.prepare(model)?.run(inputs)?;\n        let output = output.remove(0).into_tensor();\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<SdpaProblem<f32>>(\"proptest_f32\", SdpaProblemParams::default());\n    suite.add_arbitrary::<SdpaProblem<f16>>(\"proptest_f16\", SdpaProblemParams::default());\n    suite.add(\n        \"trivial_f32_0\",\n        SdpaProblem {\n            q: tensor3(&[[[0f32]]]).into_plain_array::<f32>()?,\n            k: tensor3(&[[[0f32]]]).into_plain_array::<f32>()?,\n            v: tensor3(&[[[0f32]]]).into_plain_array::<f32>()?,\n            mask: None,\n            scale: None,\n            is_causal: true,\n        },\n    );\n    suite.add(\n        \"causal_f32_0\",\n        SdpaProblem {\n            q: arr3(&[[[0f32]]]).into_dyn(),\n            k: arr3(&[[[0f32]]]).into_dyn(),\n            v: arr3(&[[[0f32]]]).into_dyn(),\n            mask: None,\n            scale: None,\n            is_causal: true,\n        },\n    );\n    suite.add(\n        \"causal_f32_1\",\n        SdpaProblem {\n            q: arr3(&[[[0f32], [0.]]]).into_dyn(),\n            k: arr3(&[[[0f32], [0.]]]).into_dyn(),\n            v: arr3(&[[[0f32], [0.]]]).into_dyn(),\n            mask: None,\n            scale: None,\n            is_causal: true,\n        },\n    );\n    suite.add(\n        \"causal_f32_2\",\n        SdpaProblem {\n            q: arr3(&[[[0f32], [0f32]]]).into_dyn(),\n            k: arr3(&[[[0f32], [0f32]]]).into_dyn(),\n            v: arr3(&[[[0f32], [0f32]]]).into_dyn(),\n            mask: None,\n            scale: None,\n            is_causal: true,\n        },\n    );\n    suite.add(\n        \"causal_with_s_and_p_0\",\n        SdpaProblem {\n            q: arr3(&[[[0f32], [0.0]]]).into_dyn(),\n            k: arr3(&[[[0f32], [0f32], [0f32]]]).into_dyn(),\n            v: arr3(&[[[0f32], [0f32], [1f32]]]).into_dyn(),\n            mask: None,\n            scale: None,\n            is_causal: true,\n        },\n    );\n    suite.add(\n        \"gqa_f32_0\",\n        SdpaProblem {\n            q: ArrayD::<f32>::zeros(IxDyn(&[1, 2, 1, 1])),\n            k: ArrayD::<f32>::zeros(IxDyn(&[1, 2, 1, 1])),\n            v: arr4(&[[[[0f32]], [[1f32]]]]).into_dyn(),\n            mask: None,\n            scale: None,\n            is_causal: false,\n        },\n    );\n    suite.add(\n        \"gqa_f32_1\",\n        SdpaProblem {\n            q: ArrayD::<f32>::zeros(IxDyn(&[1, 2, 1, 1])),\n            k: ArrayD::<f32>::zeros(IxDyn(&[1, 1, 1, 1])),\n            v: ArrayD::<f32>::zeros(IxDyn(&[1, 1, 1, 1])),\n            mask: None,\n            scale: None,\n            is_causal: false,\n        },\n    );\n    suite.add(\n        \"gqa_f32_big_0\",\n        SdpaProblem {\n            q: ArrayD::<f32>::zeros(IxDyn(&[2, 8, 5, 64])),\n            k: ArrayD::<f32>::zeros(IxDyn(&[2, 4, 5, 64])),\n            v: ArrayD::<f32>::zeros(IxDyn(&[2, 4, 5, 64])),\n            mask: None,\n            scale: None,\n            is_causal: true,\n        },\n    );\n    suite.add(\n        \"gqa_f32_big_1\",\n        SdpaProblem {\n            q: ArrayD::<f32>::zeros(IxDyn(&[2, 8, 5, 64])),\n            k: ArrayD::<f32>::zeros(IxDyn(&[2, 1, 5, 64])),\n            v: ArrayD::<f32>::zeros(IxDyn(&[2, 1, 5, 64])),\n            mask: None,\n            scale: None,\n            is_causal: true,\n        },\n    );\n    suite.add(\n        \"gqa_f32_big_2\",\n        SdpaProblem {\n            q: ArrayD::<f32>::zeros(IxDyn(&[2, 2, 3, 64])),\n            k: ArrayD::<f32>::zeros(IxDyn(&[2, 1, 3, 64])),\n            v: ArrayD::<f32>::zeros(IxDyn(&[2, 1, 3, 64])),\n            mask: None,\n            scale: None,\n            is_causal: true,\n        },\n    );\n    suite.add(\n        \"mask_0\",\n        SdpaProblem {\n            q: ArrayD::<f32>::zeros(IxDyn(&[1, 2, 1])),\n            k: ArrayD::<f32>::zeros(IxDyn(&[1, 2, 1])),\n            v: arr3(&[[[2f32], [0.]]]).into_dyn(),\n            mask: Some(arr3(&[[[0.0f32, 0.0], [0.0, -1.0]]]).into_dyn()),\n            scale: None,\n            is_causal: false,\n        },\n    );\n    suite.add(\n        \"gqa_f32_mask_simple\",\n        SdpaProblem {\n            q: ArrayD::<f32>::zeros(IxDyn(&[1, 1, 1])),\n            k: ArrayD::<f32>::zeros(IxDyn(&[1, 1, 1])),\n            v: ArrayD::<f32>::zeros(IxDyn(&[1, 1, 1])),\n            mask: Some(arr3(&[[[0f32]]]).into_dyn()),\n            scale: None,\n            is_causal: false,\n        },\n    );\n    suite.add(\n        \"gqa_f32_mask_0\",\n        SdpaProblem {\n            q: ArrayD::<f32>::zeros(IxDyn(&[2, 2, 3, 64])),\n            k: ArrayD::<f32>::zeros(IxDyn(&[2, 1, 3, 64])),\n            v: ArrayD::<f32>::zeros(IxDyn(&[2, 1, 3, 64])),\n            mask: Some(ArrayD::<f32>::zeros(vec![1, 1, 3, 3])),\n            scale: None,\n            is_causal: false,\n        },\n    );\n    suite.add(\n        \"gqa_f16_0\",\n        SdpaProblem {\n            q: ArrayD::<f16>::zeros(IxDyn(&[1, 2, 64])),\n            k: ArrayD::<f16>::zeros(IxDyn(&[1, 3, 64])),\n            v: ArrayD::<f16>::zeros(IxDyn(&[1, 3, 64])),\n            mask: None,\n            scale: None,\n            is_causal: true,\n        },\n    );\n\n    suite.add(\n        \"gqa_f32_nocausal_nomask\",\n        SdpaProblem {\n            q: ArrayD::<f32>::zeros(IxDyn(&[1, 1, 2, 1])),\n            k: ArrayD::<f32>::zeros(IxDyn(&[1, 1, 2, 1])),\n            v: arr4(&[[[[0f32], [1f32]]]]).into_dyn(),\n            mask: None,\n            scale: None,\n            is_causal: false,\n        },\n    );\n\n    suite.add(\n        \"f32_nocausal_nomask\",\n        SdpaProblem::<f32> {\n            q: arr3(&[[[0.0], [1.0]]]).into_dyn(),\n            k: arr3(&[[[0.0], [1.0]]]).into_dyn(),\n            v: arr3(&[[[1.0], [0.0]]]).into_dyn(),\n            mask: None,\n            scale: None,\n            is_causal: false,\n        },\n    );\n\n    suite.add(\n        \"gqa_mh_mask\",\n        SdpaProblem::<f32> {\n            q: arr4(&[[[[0.0], [0.0]], [[0.0], [0.0]]]]).into_dyn(),\n            k: arr4(&[[[[0.0], [0.0]]]]).into_dyn(),\n            v: arr4(&[[[[0.0], [0.01]]]]).into_dyn(),\n            mask: Some(arr4(&[[[[0.0, 0.0], [0.0, 0.0]], [[0.0, 0.0], [-2.0, 0.0]]]]).into_dyn()),\n            scale: None,\n            is_causal: false,\n        },\n    );\n\n    let mut v = ArrayD::zeros(IxDyn(&[2, 2, 64]));\n    v[[1, 1, 63]] = -0.25f32;\n    let mask_with_batch_cuda = SdpaProblem::<f32> {\n        q: ArrayD::zeros(IxDyn(&[2, 2, 64])),\n        k: ArrayD::zeros(IxDyn(&[2, 2, 64])),\n        v,\n        mask: Some(arr3(&[[[0.0f32, 0.25], [0.0, 0.0]], [[0.375, -0.25], [0.0, 0.0]]]).into_dyn()),\n        scale: None,\n        is_causal: false,\n    };\n    suite.add(\"f32_mask_with_batch_cuda\", mask_with_batch_cuda.clone());\n    suite.add(\"f16_mask_with_batch_cuda\", mask_with_batch_cuda.to_f16());\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/silu.rs",
    "content": "use infra::Test;\nuse infra::TestResult;\nuse infra::TestSuite;\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ndarray::ArrayD;\nuse tract_core::num_traits::Float;\nuse tract_transformers::ops::silu::silu;\n\nuse crate::tensor;\n\n#[derive(Debug, Clone)]\npub struct SiluProblem<F>\nwhere\n    F: Datum + Float,\n{\n    input: ArrayD<F>,\n}\n\nimpl<F> Arbitrary for SiluProblem<F>\nwhere\n    F: Datum + Float,\n{\n    type Parameters = ();\n    type Strategy = BoxedStrategy<SiluProblem<F>>;\n\n    fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {\n        (0usize..5)\n            .prop_flat_map(|rank| {\n                let other_dim = 1usize..10;\n                vec(other_dim, rank..=rank)\n            })\n            .prop_flat_map(|shape| tensor::<F>(&shape).prop_map(|input| Self { input }))\n            .boxed()\n    }\n}\n\nimpl<F> SiluProblem<F>\nwhere\n    F: Datum + Float,\n    f32: From<F>,\n{\n    fn tract(&self) -> TractResult<TypedModel> {\n        let mut model = TypedModel::default();\n        let input = self.input.clone().into_tensor();\n        let input = model.add_source(\"input\", TypedFact::shape_and_dt_of(&input))?;\n\n        let output = model.wire_node(\"silu\", silu(), &[input])?;\n        model.select_output_outlets(&output)?;\n\n        model = model.into_decluttered()?;\n        Ok(model)\n    }\n\n    fn reference(&self) -> ArrayD<F> {\n        let input = &self.input;\n        input.mapv(|x| F::from(f32::from(x) / (1.0 + f32::from(-x).exp())).unwrap())\n    }\n}\n\nimpl<F> Test for SiluProblem<F>\nwhere\n    F: Datum + Float,\n    f32: From<F>,\n{\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> TestResult {\n        let reference = self.reference().into_tensor();\n        let mut model = self.tract()?;\n\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n\n        let mut output = runtime.prepare(model)?.run(tvec!(self.input.clone().into_tvalue()))?;\n        let output = output.remove(0).into_tensor();\n\n        if F::datum_type() == DatumType::F16 {\n            output.close_enough(&reference, Approximation::VeryApproximate)\n        } else {\n            output.close_enough(&reference, approx)\n        }\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n\n    suite.add_arbitrary::<SiluProblem<f32>>(\"proptest_f32\", ());\n    suite.add_arbitrary::<SiluProblem<f16>>(\"proptest_f16\", ());\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/suite-unit/src/slice.rs",
    "content": "use infra::{Test, TestSuite};\nuse proptest::collection::vec;\nuse proptest::prelude::*;\nuse tract_core::internal::*;\nuse tract_core::ops::array::Slice;\n\n#[derive(Debug, Clone, Default)]\nstruct SliceProblem {\n    input_shape: Vec<usize>,\n    op: Slice,\n}\n\nimpl Arbitrary for SliceProblem {\n    type Parameters = ();\n    type Strategy = BoxedStrategy<SliceProblem>;\n    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {\n        vec(1..10usize, 1..5usize)\n            .prop_flat_map(|input_shape| {\n                let rank = input_shape.len();\n                (Just(input_shape), 0..rank)\n            })\n            .prop_flat_map(|(shape, axis)| {\n                let b0 = 0..=shape[axis];\n                let b1 = 0..=shape[axis];\n                (Just(shape), Just(axis), b0, b1)\n            })\n            .prop_filter(\"non empty slice\", |(_, _, b0, b1)| b0 != b1)\n            .prop_map(|(input_shape, axis, b0, b1)| {\n                let start = b0.min(b1).to_dim();\n                let end = b0.max(b1).to_dim();\n                SliceProblem { input_shape, op: Slice { axis, start, end } }\n            })\n            .boxed()\n    }\n}\n\nimpl Test for SliceProblem {\n    fn run_with_approx(\n        &self,\n        id: &str,\n        runtime: &dyn Runtime,\n        approx: Approximation,\n    ) -> infra::TestResult {\n        let mut input = Tensor::zero::<f32>(&self.input_shape)?;\n        input\n            .try_as_plain_mut()?\n            .as_slice_mut::<f32>()?\n            .iter_mut()\n            .enumerate()\n            .for_each(|(ix, x)| *x = ix as f32);\n        let reference = input.slice(\n            self.op.axis,\n            self.op.start.to_usize().unwrap(),\n            self.op.end.to_usize().unwrap(),\n        )?;\n        let mut model = TypedModel::default();\n        model.properties.insert(\"tract-rt-test.id\".to_string(), rctensor0(id.to_string()));\n        let wire = model.add_source(\"input\", TypedFact::shape_and_dt_of(&input))?;\n        let output = model.wire_node(\"slice\", self.op.clone(), &[wire])?;\n        model.select_output_outlets(&output)?;\n        let prepared = runtime.prepare(model)?;\n        let mut output = prepared.run(tvec![input.clone().into_tvalue()])?;\n        let output = output.remove(0).into_tensor();\n        output.close_enough(&reference, approx)\n    }\n}\n\npub fn suite() -> TractResult<TestSuite> {\n    let mut suite = TestSuite::default();\n    suite.add_arbitrary::<SliceProblem>(\"proptest\", ());\n\n    suite.add(\n        \"full_0\",\n        SliceProblem {\n            input_shape: vec![3],\n            op: Slice { axis: 0, start: 0.to_dim(), end: 3.to_dim() },\n        },\n    );\n\n    suite.add(\n        \"empty_0\",\n        SliceProblem {\n            input_shape: vec![2],\n            op: Slice { axis: 0, start: 0.to_dim(), end: 1.to_dim() },\n        },\n    );\n\n    suite.add(\n        \"full_ax_0\",\n        SliceProblem {\n            input_shape: vec![1, 1, 2, 1],\n            op: Slice { axis: 0, start: 0.to_dim(), end: 1.to_dim() },\n        },\n    );\n\n    Ok(suite)\n}\n"
  },
  {
    "path": "test-rt/test-blas/Cargo.toml",
    "content": "[package]\nname = \"test-blas\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n\n[build-dependencies]\ninfra = { path = \"../infra\" }\nitertools.workspace = true\nlazy_static.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\ntract-core = { workspace = true, features = [ \"blis\" ] }\n\n[dev-dependencies]\ninfra = { path = \"../infra\" }\nitertools.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\ntract-core = { workspace = true, features = [ \"blis\" ] }\ntract-onnx-opl.workspace = true\n"
  },
  {
    "path": "test-rt/test-blas/build.rs",
    "content": "#[path = \"suite.rs\"]\nmod suite;\n\nfn main() {\n    suite::suite().test_runtime(\n        \"as_blas\",\n        \"suite::suite()\",\n        \"as_blas()\",\n        \"Approximation::Approximate\",\n    );\n}\n"
  },
  {
    "path": "test-rt/test-blas/src/lib.rs",
    "content": "#![cfg(test)]\nuse std::fmt::Debug;\n\nuse tract_core::internal::*;\n\n#[path = \"../suite.rs\"]\nmod suite;\n\nmod as_blas {\n    use super::*;\n\n    pub fn as_blas() -> &'static AsBlasRuntime {\n        &AsBlasRuntime\n    }\n\n    #[derive(Debug)]\n    pub struct AsBlasRuntime;\n\n    impl Runtime for AsBlasRuntime {\n        fn name(&self) -> StaticName {\n            Cow::Borrowed(\"as_blas\")\n        }\n        fn prepare_with_options(\n            &self,\n            mut model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            tract_core::transform::get_transform(\"as_blas\")?.unwrap().transform(&mut model)?;\n            Ok(Box::new(model.into_runnable_with_options(options)?))\n        }\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/as_blas.rs\"));\n}\n"
  },
  {
    "path": "test-rt/test-blas/suite.rs",
    "content": "use infra::Test;\n\npub fn suite() -> &'static infra::TestSuite {\n    lazy_static::lazy_static! {\n        static ref SUITE: infra::TestSuite  = mk_suite();\n    };\n    &SUITE\n}\n\n#[allow(clippy::needless_update)]\nfn mk_suite() -> infra::TestSuite {\n    let mut onnx = suite_onnx::suite().clone();\n    onnx.ignore(&ignore_onnx);\n\n    let mut unit = suite_unit::suite().unwrap().clone();\n    unit.ignore_case(&ignore_unit);\n\n    infra::TestSuite::default().with(\"onnx\", onnx).with(\"unit\", unit)\n}\n\nfn ignore_onnx(_t: &[String]) -> bool {\n    false\n}\n\nfn ignore_unit(_t: &[String], _tc: &dyn Test) -> bool {\n    false\n}\n"
  },
  {
    "path": "test-rt/test-cuda/Cargo.toml",
    "content": "[package]\nname = \"test-cuda\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n\n[build-dependencies]\nhome.workspace = true\nlazy_static.workspace = true\nregex.workspace = true\ninfra = { path = \"../infra\" }\ntract-core.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\ntract-gpu.workspace = true\ntract-onnx-opl.workspace = true\npastey.workspace = true\n\n[dev-dependencies]\nhome.workspace = true\nregex.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\ntract-core.workspace = true\ntract-onnx-opl.workspace = true\ninfra = { path = \"../infra\" }\ntract-cuda.workspace = true\ntract-gpu.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\npastey.workspace = true\n"
  },
  {
    "path": "test-rt/test-cuda/build.rs",
    "content": "#[path = \"suite.rs\"]\nmod suite;\n\nfn main() {\n    suite::suite().test_runtime(\n        \"tests\",\n        \"suite::suite()\",\n        \"runtime()\",\n        \"Approximation::VeryApproximate\",\n    );\n}\n"
  },
  {
    "path": "test-rt/test-cuda/src/lib.rs",
    "content": "#![cfg(test)]\n\nuse std::sync::Arc;\nuse tract_core::internal::*;\n\nuse pastey::paste;\nuse tract_core::runtime::Runtime;\nuse tract_core::tract_data::itertools::Itertools;\n\n#[path = \"../suite.rs\"]\nmod suite;\n\n#[derive(Debug)]\nstruct CudaTestTransformState {\n    state: TypedSimpleState,\n    transpose_inputs: bool,\n    use_arena: bool,\n}\n\nimpl State for CudaTestTransformState {\n    fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let mut state = if self.use_arena {\n            let session_handler = tract_gpu::session_handler::DeviceSessionHandler::from_plan(\n                self.state.plan(),\n                &self.state.turn_state.resolved_symbols,\n            )?;\n\n            let plan = Arc::unwrap_or_clone(self.state.plan().clone())\n                .with_session_handler(session_handler);\n            let plan = Arc::new(plan);\n            plan.spawn()?\n        } else {\n            self.state.clone()\n        };\n\n        if self.transpose_inputs {\n            let inputs = inputs\n                .into_iter()\n                .map(|input| {\n                    let input = input.into_tensor();\n                    let rank = input.rank();\n                    let perms = (0..rank).rev().collect_vec();\n                    Ok(input.permute_axes(&perms)?.into_tvalue())\n                })\n                .collect::<TractResult<TVec<TValue>>>()?;\n\n            state\n                .run(inputs)?\n                .into_iter()\n                .map(|t| {\n                    let t = t.into_tensor();\n                    let rank = t.rank();\n                    let perms = (0..rank).rev().collect_vec();\n                    Ok(t.permute_axes(&perms)?.into_tvalue())\n                })\n                .collect()\n        } else {\n            state.run(inputs)\n        }\n    }\n\n    fn input_count(&self) -> usize {\n        self.state.input_count()\n    }\n\n    fn output_count(&self) -> usize {\n        self.state.output_count()\n    }\n\n    fn runnable(&self) -> &dyn Runnable {\n        self.state.runnable()\n    }\n\n    fn freeze(&self) -> Box<dyn FrozenState> {\n        Box::new(self.state.freeze())\n    }\n}\n\n#[derive(Debug)]\nstruct CudaTestTransformRunnable {\n    runnable: Arc<TypedRunnableModel>,\n    transpose_inputs: bool,\n    use_arena: bool,\n}\n\nimpl Runnable for CudaTestTransformRunnable {\n    fn spawn(&self) -> TractResult<Box<dyn State>> {\n        Ok(Box::new(CudaTestTransformState {\n            state: self.runnable.spawn()?,\n            transpose_inputs: self.transpose_inputs,\n            use_arena: self.use_arena,\n        }))\n    }\n\n    fn input_count(&self) -> usize {\n        self.runnable.input_count()\n    }\n\n    fn output_count(&self) -> usize {\n        self.runnable.output_count()\n    }\n\n    fn typed_model(&self) -> Option<&Arc<TypedModel>> {\n        self.runnable.typed_model()\n    }\n\n    fn typed_plan(&self) -> Option<&Arc<TypedSimplePlan>> {\n        self.runnable.typed_plan()\n    }\n}\n\n#[derive(Debug)]\nstruct CudaTestRuntime {\n    name: &'static str,\n    phase: usize,\n    optimize: bool,\n    transpose_inputs: bool,\n    use_arena: bool,\n}\n\nimpl Runtime for CudaTestRuntime {\n    fn name(&self) -> StaticName {\n        self.name.into()\n    }\n\n    fn prepare_with_options(\n        &self,\n        mut model: TypedModel,\n        options: &RunOptions,\n    ) -> TractResult<Box<dyn Runnable>> {\n        if self.transpose_inputs {\n            for ix in 0..model.inputs.len() {\n                let input = model.input_outlets()?[ix];\n                let in_fact = model.outlet_fact(input)?;\n                let rank = in_fact.rank();\n                let shape = in_fact.shape.dims().into_iter().rev().collect::<TVec<_>>();\n                let fact = in_fact.datum_type.fact(shape);\n\n                let transposed_input = model.add_source(format!(\"transposed_input_{ix}\"), fact)?;\n\n                let mut patch = TypedModelPatch::default();\n                let mut wire = patch.tap_model(&model, transposed_input)?;\n\n                let perms = (0..rank).rev().collect_vec();\n                let axis_ops = perm_to_ops(&perms);\n\n                for (ax, op) in axis_ops.into_iter().enumerate() {\n                    wire = patch.wire_node(format!(\"transposed_input.{ix}_{ax}\"), op, &[wire])?[0];\n                }\n                patch.shunt_outside(&model, input, wire)?;\n                patch.apply(&mut model)?;\n            }\n\n            // Delete old inputs\n            for _ in 0..model.inputs.len() / 2 {\n                let input = model.inputs.remove(0);\n                model.node_mut(input.node).op = model.create_dummy();\n            }\n\n            for (ix, output) in model.outputs.clone().iter().enumerate() {\n                let rank = model.outlet_fact(*output)?.rank();\n                let mut wire = *output;\n                let perms = (0..rank).rev().collect_vec();\n                let axis_ops = perm_to_ops(&perms);\n\n                for (ax, op) in axis_ops.into_iter().enumerate() {\n                    wire = model.wire_node(format!(\"transposed_output.{ix}_{ax}\"), op, &[wire])?[0];\n                }\n                model.outputs[ix] = wire;\n            }\n        }\n\n        tract_cuda::CudaTransform.transform_up_to_phase(&mut model, self.phase)?;\n        if self.optimize {\n            model = model.into_optimized()?;\n        }\n\n        let runnable = CudaTestTransformRunnable {\n            runnable: model.into_runnable_with_options(&options)?,\n            transpose_inputs: self.transpose_inputs,\n            use_arena: self.use_arena,\n        };\n        Ok(Box::new(runnable))\n    }\n    fn check(&self) -> TractResult<()> {\n        tract_core::runtime::runtime_for_name(\"cuda\").context(\"No cuda runtime found\")?.check()\n    }\n}\n\nmacro_rules! cuda_test_suite {\n    ($id: ident, $phase: expr, $optimize: expr, $transpose_inputs: ident, $use_arena: ident) => {\n        paste! {\n            mod [<$id>] {\n                use super::*;\n\n                fn runtime() -> &'static CudaTestRuntime {\n                    lazy_static::lazy_static! {\n                        static ref RT: CudaTestRuntime = CudaTestRuntime {\n                            name: stringify!([<$id>]),\n                            phase: $phase,\n                            optimize: $optimize,\n                            transpose_inputs: $transpose_inputs,\n                            use_arena: $use_arena,\n                        };\n                    };\n                    &RT\n                }\n\n                include!(concat!(env!(\"OUT_DIR\"), \"/tests/tests.rs\"));\n            }\n        }\n    };\n}\n\n//cuda_test_suite!(cuda_phase_2_translate, 2, false, , false, false);\n//cuda_test_suite!(cuda_phase_3_post_translate, 3, false, , false, false);\ncuda_test_suite!(optimized_cuda, usize::MAX, true, false, false);\ncuda_test_suite!(optimized_cuda_with_arena, usize::MAX, true, false, true);\ncuda_test_suite!(optimized_cuda_transpose, usize::MAX, true, true, false);\n"
  },
  {
    "path": "test-rt/test-cuda/suite.rs",
    "content": "use std::vec;\n\nuse infra::Test;\nuse suite_unit::bin_einsum::{BinEinsumProblem, BinEinsumProblemParams};\nuse suite_unit::conv_f16::ConvProblemF16;\nuse suite_unit::conv_f32::{ConvProblem, ConvProblemParams};\nuse suite_unit::sdpa::{SdpaProblem, SdpaProblemParams};\nuse tract_core::num_traits::Float;\nuse tract_core::prelude::Datum;\nuse tract_core::tract_data::half;\n\npub fn suite() -> &'static infra::TestSuite {\n    lazy_static::lazy_static! {\n        static ref SUITE: infra::TestSuite  = mk_suite();\n    };\n    &SUITE\n}\n\n#[allow(clippy::needless_update)]\nfn mk_suite() -> infra::TestSuite {\n    let mut onnx = suite_onnx::suite().clone();\n    onnx.ignore(&ignore_onnx);\n\n    let mut unit = suite_unit::suite().unwrap().clone();\n    unit.ignore_case(&ignore_unit);\n\n    unit.get_sub_mut(\"bin_einsum\").add_arbitrary::<BinEinsumProblem>(\n        \"proptest\",\n        BinEinsumProblemParams {\n            force_unique_non_trivial_m_n: true,\n            max_dims: 6,\n            ..BinEinsumProblemParams::default()\n        },\n    );\n\n    unit.get_sub_mut(\"conv_f32\")\n        .add_arbitrary::<ConvProblem>(\"proptest\", ConvProblemParams::default());\n\n    unit.get_sub_mut(\"conv_f16\")\n        .add_arbitrary::<ConvProblemF16>(\"proptest\", ConvProblemParams::default());\n\n    unit.get_sub_mut(\"sdpa\").add_arbitrary::<SdpaProblem<half::f16>>(\n        \"proptest_f16\",\n        SdpaProblemParams { embed_dims: vec![64, 128] },\n    );\n    infra::TestSuite::default().with(\"onnx\", onnx).with(\"unit\", unit)\n}\n\nfn ignore_unit(t: &[String], case: &dyn Test) -> bool {\n    if let Some(sdpab) = case.downcast_ref::<SdpaProblem<f32>>() {\n        return !compatible_sdpa::<f32>(sdpab);\n    }\n\n    if let Some(sdpab) = case.downcast_ref::<SdpaProblem<half::f16>>() {\n        return !compatible_sdpa::<half::f16>(sdpab);\n    }\n    t[0] == \"sdpa\" && t[1] == \"proptest_f32\"\n}\n\nfn compatible_sdpa<F: Datum + Float>(sdpap: &SdpaProblem<F>) -> bool {\n    matches!(sdpap.k.shape().last().unwrap(), 64 | 80 | 96 | 112 | 128 | 256)\n}\n\nfn ignore_onnx(t: &[String]) -> bool {\n    r#\"\n    test_slice_start_out_of_bounds\n    test_nllloss_NCd1d2d3d4d5_mean_weight_expanded\n    test_nllloss_NCd1d2d3d4d5_none_no_weight_expanded\n    test_tril_zero\n    test_triu_zero\n    \"#\n    .trim()\n    .lines()\n    .any(|s| t.last().unwrap() == s.trim())\n}\n"
  },
  {
    "path": "test-rt/test-f16/Cargo.toml",
    "content": "[package]\nname = \"test-f16\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n\n[build-dependencies]\nhome.workspace = true\nlazy_static.workspace = true\nregex.workspace = true\ninfra = { path = \"../infra\" }\ntract-core.workspace = true\ntract-transformers.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\ntract-onnx-opl.workspace = true\n\n[dev-dependencies]\nhome.workspace = true\nregex.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\ntract-core.workspace = true\ntract-transformers.workspace = true\ntract-nnef.workspace = true\ntract-onnx-opl.workspace = true\ninfra = { path = \"../infra\" }\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\n"
  },
  {
    "path": "test-rt/test-f16/build.rs",
    "content": "#[path = \"suite.rs\"]\nmod suite;\n\nfn main() {\n    suite::suite().test_runtime(\n        \"tests\",\n        \"suite::suite()\",\n        \"runtime()\",\n        \"Approximation::SuperApproximate\",\n    );\n}\n"
  },
  {
    "path": "test-rt/test-f16/src/lib.rs",
    "content": "#![cfg(test)]\n\n#[path = \"../suite.rs\"]\nmod suite;\n\nmod run_as_f16 {\n    use super::*;\n    use tract_core::internal::*;\n    use tract_core::model::translator::Translate;\n\n    #[derive(Debug)]\n    struct RunAsF16;\n\n    impl Runtime for RunAsF16 {\n        fn name(&self) -> StaticName {\n            \"run_as_f16\".into()\n        }\n\n        fn prepare_with_options(\n            &self,\n            model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            let outputs_dt =\n                model.outputs.iter().map(|o| model.outlet_fact(*o).unwrap().datum_type).collect();\n            let tr =\n                tract_core::floats::FloatPrecisionTranslator::new(DatumType::F32, DatumType::F16);\n            let model = tr.translate_model(&model)?;\n            Ok(Box::new(RunnableAsF16(\n                model.into_optimized()?.into_runnable_with_options(options)?,\n                outputs_dt,\n            )))\n        }\n\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    #[derive(Debug)]\n    pub struct RunnableAsF16(pub Arc<TypedRunnableModel>, pub TVec<DatumType>);\n\n    impl Runnable for RunnableAsF16 {\n        fn spawn(&self) -> TractResult<Box<dyn State>> {\n            Ok(Box::new(StateAsF16(self.0.spawn()?, self.1.clone())))\n        }\n\n        fn input_count(&self) -> usize {\n            self.0.input_count()\n        }\n\n        fn output_count(&self) -> usize {\n            self.0.output_count()\n        }\n\n        fn typed_model(&self) -> Option<&Arc<TypedModel>> {\n            self.0.typed_model()\n        }\n\n        fn typed_plan(&self) -> Option<&Arc<TypedSimplePlan>> {\n            self.0.typed_plan()\n        }\n    }\n\n    #[derive(Debug)]\n    struct StateAsF16(TypedSimpleState, TVec<DatumType>);\n\n    impl State for StateAsF16 {\n        fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n            let inputs = inputs\n                .into_iter()\n                .map(|v| {\n                    if v.datum_type() == DatumType::F32 {\n                        v.into_tensor()\n                            .cast_to_dt(f16::datum_type())\n                            .unwrap()\n                            .into_owned()\n                            .into_tvalue()\n                    } else {\n                        v\n                    }\n                })\n                .collect();\n            let outputs = self.0.run(inputs)?;\n            Ok(outputs\n                .into_iter()\n                .zip(self.1.iter())\n                .map(|(t, dt)| t.into_tensor().cast_to_dt(*dt).unwrap().into_owned().into_tvalue())\n                .collect())\n        }\n\n        fn input_count(&self) -> usize {\n            self.0.input_count()\n        }\n\n        fn output_count(&self) -> usize {\n            self.0.output_count()\n        }\n\n        fn runnable(&self) -> &dyn Runnable {\n            self.0.runnable()\n        }\n\n        fn freeze(&self) -> Box<dyn FrozenState> {\n            Box::new(self.0.freeze())\n        }\n    }\n\n    fn runtime() -> &'static RunAsF16 {\n        static RUN_AS_F16: RunAsF16 = RunAsF16;\n        &RUN_AS_F16\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/tests.rs\"));\n}\n\nmod nnef_f16 {\n    use std::fmt::Debug;\n\n    use super::run_as_f16::RunnableAsF16;\n    use super::*;\n    use tract_core::internal::*;\n    use tract_core::model::translator::Translate;\n    use tract_nnef::internal::Nnef;\n    use tract_onnx_opl::WithOnnx;\n    use tract_transformers::WithTractTransformers;\n\n    struct NnefF16(Nnef);\n\n    impl Debug for NnefF16 {\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            write!(f, \"NnefF16\")\n        }\n    }\n\n    impl Runtime for NnefF16 {\n        fn name(&self) -> StaticName {\n            \"nnef_f16\".into()\n        }\n\n        fn prepare_with_options(\n            &self,\n            model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            let outputs_dt =\n                model.outputs.iter().map(|o| model.outlet_fact(*o).unwrap().datum_type).collect();\n            let tr =\n                tract_core::floats::FloatPrecisionTranslator::new(DatumType::F32, DatumType::F16);\n            let model = tr.translate_model(&model)?;\n            let mut buf = vec![];\n            self.0.write_to_tar(&model, &mut buf)?;\n            let reloaded = self.0.model_for_read(&mut &*buf)?;\n            Ok(Box::new(RunnableAsF16(\n                reloaded.into_optimized()?.into_runnable_with_options(&options)?,\n                outputs_dt,\n            )))\n        }\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    fn runtime() -> &'static NnefF16 {\n        lazy_static::lazy_static! {\n            static ref RT: NnefF16 = NnefF16(tract_nnef::nnef().with_onnx().with_tract_transformers());\n        };\n        &RT\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/tests.rs\"));\n}\n"
  },
  {
    "path": "test-rt/test-f16/suite.rs",
    "content": "use infra::Test;\nuse suite_unit::bin_einsum::{BinEinsumProblem, BinEinsumProblemParams};\nuse suite_unit::conv_q::{QConvProblem, QConvProblemParams};\n\npub fn suite() -> &'static infra::TestSuite {\n    lazy_static::lazy_static! {\n        static ref SUITE: infra::TestSuite  = mk_suite();\n    };\n    &SUITE\n}\n\n#[allow(clippy::needless_update)]\nfn mk_suite() -> infra::TestSuite {\n    let mut onnx = suite_onnx::suite().clone();\n    onnx.ignore(&ignore_onnx);\n    let mut unit = suite_unit::suite().unwrap().clone();\n    unit.get_sub_mut(\"bin_einsum\").add_arbitrary::<BinEinsumProblem>(\n        \"proptest\",\n        BinEinsumProblemParams { max_dims: 7, ..Default::default() },\n    );\n    unit.ignore_case(&ignore_unit);\n    unit.get_sub_mut(\"conv_q\").add_arbitrary_with_filter::<QConvProblem>(\n        \"proptest\",\n        QConvProblemParams::default(),\n        compatible_conv_q,\n    );\n\n    infra::TestSuite::default().with(\"onnx\", onnx).with(\"unit\", unit)\n}\n\nfn ignore_unit(t: &[String], case: &dyn Test) -> bool {\n    #[allow(clippy::collapsible_if)]\n    if let Some(qcp) = case.downcast_ref::<QConvProblem>() {\n        if !compatible_conv_q(qcp) {\n            return true;\n        }\n    }\n\n    let [section, _unit] = t else { return false };\n    [\"q_flavours\"].contains(&&**section)\n}\n\nfn ignore_onnx(t: &[String]) -> bool {\n    r#\"\ntest_averagepool_2d_ceil\ntest_averagepool_2d_pads_count_include_pad\ntest_averagepool_2d_precomputed_pads_count_include_pad\ntest_averagepool_2d_same_lower\ntest_cast_STRING_to_FLOAT\ntest_castlike_STRING_to_FLOAT_expanded\ntest_constantlike_ones_with_input\ntest_constantlike_threes_with_shape_and_dtype\ntest_constantlike_zeros_without_input_dtype\ntest_cumsum_1d_exclusive\ntest_cumsum_1d_reverse_exclusive\ntest_cumsum_2d\ntest_dequantizelinear\ntest_dropout_random\ntest_dynamicquantizelinear\ntest_dynamicquantizelinear_max_adjusted\ntest_dynamicquantizelinear_min_adjusted\ntest_gemm_broadcast\ntest_gemm_nobroadcast\ntest_if\ntest_maxpool_2d_ceil\ntest_maxpool_2d_same_lower\ntest_maxpool_with_argmax_2d_precomputed_pads\ntest_mod_broadcast\ntest_mod_int64_fmod\ntest_mod_mixed_sign_float16\ntest_mod_mixed_sign_float32\ntest_mod_mixed_sign_float64\ntest_mod_mixed_sign_int16\ntest_mod_mixed_sign_int32\ntest_mod_mixed_sign_int64\ntest_mod_mixed_sign_int8\ntest_mod_uint16\ntest_mod_uint32\ntest_mod_uint64\ntest_mod_uint8\ntest_matmulinteger\ntest_nllloss_NCd1d2d3_none_no_weight_negative_ii_expanded\ntest_nonzero_example\ntest_quantizelinear\ntest_qlinearmatmul_2D\ntest_qlinearmatmul_3D\ntest_reduce_prod_default_axes_keepdims_example\ntest_reshape_reordered_dims\ntest_resize_upsample_scales_linear_align_corners\ntest_resize_downsample_scales_linear\ntest_unsqueeze\n\"#\n    .trim()\n    .lines()\n    .any(|s| t.last().unwrap() == s.trim())\n        || t.last().unwrap().starts_with(\"test_logsoftmax_large_number\")\n        || t.last().unwrap().starts_with(\"test_softmax_large_number\")\n        || t.last().unwrap().starts_with(\"test_resize\")\n}\n\nfn compatible_conv_q(qcp: &QConvProblem) -> bool {\n    qcp.qp.iter().all(|t| t.len() == 1)\n}\n"
  },
  {
    "path": "test-rt/test-metal/Cargo.toml",
    "content": "[package]\nname = \"test-metal\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n\n[build-dependencies]\nhome.workspace = true\nlazy_static.workspace = true\nregex.workspace = true\ninfra = { path = \"../infra\" }\ntract-core.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\ntract-onnx-opl.workspace = true\ntract-metal.workspace = true\npastey.workspace = true\n\n[dev-dependencies]\nhome.workspace = true\nregex.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\ntract-core.workspace = true\ntract-metal.workspace = true\ntract-gpu.workspace = true\ntract-onnx-opl.workspace = true\ninfra = { path = \"../infra\" }\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\npastey.workspace = true\n"
  },
  {
    "path": "test-rt/test-metal/build.rs",
    "content": "#[path = \"ggml_suite.rs\"]\nmod ggml_suite;\n#[path = \"suite.rs\"]\nmod suite;\n\nfn main() {\n    suite::suite().test_runtime(\"mlx\", \"suite::suite()\", \"runtime()\", \"Approximation::Approximate\");\n\n    suite::suite().test_runtime(\"mfa\", \"suite::suite()\", \"runtime()\", \"Approximation::Approximate\");\n\n    ggml_suite::suite().test_runtime(\n        \"ggml\",\n        \"ggml_suite::suite()\",\n        \"runtime()\",\n        \"Approximation::Approximate\",\n    );\n\n    ggml_suite::suite().test_runtime(\n        \"none\",\n        \"ggml_suite::suite()\",\n        \"runtime()\",\n        \"Approximation::Approximate\",\n    );\n}\n"
  },
  {
    "path": "test-rt/test-metal/ggml_suite.rs",
    "content": "use infra::Test;\nuse suite_unit::bin_einsum::{BinEinsumProblem, BinEinsumProblemParams};\nuse suite_unit::conv_f32::{ConvProblem, ConvProblemParams};\n\npub fn suite() -> &'static infra::TestSuite {\n    lazy_static::lazy_static! {\n        static ref SUITE: infra::TestSuite  = mk_suite();\n    };\n    &SUITE\n}\n\n#[allow(clippy::needless_update)]\nfn mk_suite() -> infra::TestSuite {\n    let mut onnx = suite_onnx::suite().clone();\n    onnx.ignore(&ignore_onnx);\n\n    let mut unit = suite_unit::suite().unwrap().clone();\n    unit.ignore_case(&ignore_unit);\n\n    unit.get_sub_mut(\"bin_einsum\").add_arbitrary::<BinEinsumProblem>(\n        \"proptest\",\n        BinEinsumProblemParams {\n            force_unique_non_trivial_m_n: true,\n            max_dims: 5,\n            ..BinEinsumProblemParams::default()\n        },\n    );\n    unit.get_sub_mut(\"conv_f32\").add_arbitrary::<ConvProblem>(\n        \"proptest\",\n        ConvProblemParams { no_batch: true, ..ConvProblemParams::default() },\n    );\n\n    infra::TestSuite::default().with(\"onnx\", onnx).with(\"unit\", unit)\n}\n\nfn ignore_unit(t: &[String], _case: &dyn Test) -> bool {\n    (t[0] == \"bin_einsum\" && t[1] == \"proptest\")\n        || (t[0] == \"conv_f32\" && (t[1] == \"proptest\" || t[1] == \"bug_metal_0\"))\n}\n\nfn ignore_onnx(t: &[String]) -> bool {\n    r#\"\ntest_slice_start_out_of_bounds\ntest_nllloss_NCd1d2d3d4d5_mean_weight_expanded\ntest_nllloss_NCd1d2d3d4d5_none_no_weight_expanded\ntest_tril_zero\ntest_triu_zero\n\"#\n    .trim()\n    .lines()\n    .any(|s| t.last().unwrap() == s.trim())\n}\n"
  },
  {
    "path": "test-rt/test-metal/src/lib.rs",
    "content": "#![cfg(all(test, any(target_os = \"macos\", target_os = \"ios\")))]\n\nuse std::sync::Arc;\nuse tract_core::internal::*;\n\nuse pastey::paste;\nuse tract_core::runtime::Runtime;\nuse tract_core::tract_data::itertools::Itertools;\nuse tract_metal::MetalGemmImplKind;\n\n#[path = \"../ggml_suite.rs\"]\nmod ggml_suite;\n#[path = \"../suite.rs\"]\nmod suite;\n\n#[derive(Debug)]\nstruct MetalTestTransformState {\n    state: TypedSimpleState,\n    transpose_inputs: bool,\n    use_arena: bool,\n}\n\nimpl State for MetalTestTransformState {\n    fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let mut state = if self.use_arena {\n            let session_handler = tract_gpu::session_handler::DeviceSessionHandler::from_plan(\n                self.state.plan(),\n                &self.state.turn_state.resolved_symbols,\n            )?;\n\n            let plan = Arc::unwrap_or_clone(self.state.plan().clone())\n                .with_session_handler(session_handler);\n            Arc::new(plan).spawn()?\n        } else {\n            self.state.clone()\n        };\n\n        if self.transpose_inputs {\n            let inputs = inputs\n                .into_iter()\n                .map(|input| {\n                    let input = input.into_tensor();\n                    let rank = input.rank();\n                    let perms = (0..rank).rev().collect_vec();\n                    Ok(input.permute_axes(&perms)?.into_tvalue())\n                })\n                .collect::<TractResult<TVec<TValue>>>()?;\n\n            state\n                .run(inputs)?\n                .into_iter()\n                .map(|t| {\n                    let t = t.into_tensor();\n                    let rank = t.rank();\n                    let perms = (0..rank).rev().collect_vec();\n                    Ok(t.permute_axes(&perms)?.into_tvalue())\n                })\n                .collect()\n        } else {\n            state.run(inputs)\n        }\n    }\n\n    fn input_count(&self) -> usize {\n        self.state.input_count()\n    }\n\n    fn output_count(&self) -> usize {\n        self.state.output_count()\n    }\n\n    fn runnable(&self) -> &dyn Runnable {\n        self.state.runnable()\n    }\n\n    fn freeze(&self) -> Box<dyn FrozenState> {\n        Box::new(self.state.freeze())\n    }\n}\n\n#[derive(Debug)]\nstruct MetalTestTransformRunnable {\n    runnable: Arc<TypedRunnableModel>,\n    transpose_inputs: bool,\n    use_arena: bool,\n}\n\nimpl Runnable for MetalTestTransformRunnable {\n    fn spawn(&self) -> TractResult<Box<dyn State>> {\n        Ok(Box::new(MetalTestTransformState {\n            state: self.runnable.spawn()?,\n            transpose_inputs: self.transpose_inputs,\n            use_arena: self.use_arena,\n        }))\n    }\n\n    fn input_count(&self) -> usize {\n        self.runnable.input_count()\n    }\n\n    fn output_count(&self) -> usize {\n        self.runnable.output_count()\n    }\n\n    fn typed_plan(&self) -> Option<&Arc<TypedSimplePlan>> {\n        self.runnable.typed_plan()\n    }\n\n    fn typed_model(&self) -> Option<&Arc<TypedModel>> {\n        self.runnable.typed_model()\n    }\n}\n\n#[derive(Debug)]\nstruct MetalTestRuntime {\n    name: &'static str,\n    phase: usize,\n    optimize: bool,\n    gemm_impl: Option<MetalGemmImplKind>,\n    transpose_inputs: bool,\n    use_arena: bool,\n}\n\nimpl Runtime for MetalTestRuntime {\n    fn name(&self) -> StaticName {\n        self.name.into()\n    }\n\n    fn prepare_with_options(\n        &self,\n        mut model: TypedModel,\n        _options: &RunOptions,\n    ) -> TractResult<Box<dyn Runnable>> {\n        if self.transpose_inputs {\n            for ix in 0..model.inputs.len() {\n                let input = model.input_outlets()?[ix];\n                let in_fact = model.outlet_fact(input)?;\n                let rank = in_fact.rank();\n                let shape = in_fact.shape.dims().into_iter().rev().collect::<TVec<_>>();\n                let fact = in_fact.datum_type.fact(shape);\n\n                let transposed_input = model.add_source(format!(\"transposed_input_{ix}\"), fact)?;\n\n                let mut patch = TypedModelPatch::default();\n                let mut wire = patch.tap_model(&model, transposed_input)?;\n\n                let perms = (0..rank).rev().collect_vec();\n                let axis_ops = perm_to_ops(&perms);\n\n                for (ax, op) in axis_ops.into_iter().enumerate() {\n                    wire = patch.wire_node(format!(\"transposed_input.{ix}_{ax}\"), op, &[wire])?[0];\n                }\n                patch.shunt_outside(&model, input, wire)?;\n                patch.apply(&mut model)?;\n            }\n\n            // Delete old inputs\n            for _ in 0..model.inputs.len() / 2 {\n                let input = model.inputs.remove(0);\n                model.node_mut(input.node).op = model.create_dummy();\n            }\n\n            for (ix, output) in model.outputs.clone().iter().enumerate() {\n                let rank = model.outlet_fact(*output)?.rank();\n                let mut wire = *output;\n                let perms = (0..rank).rev().collect_vec();\n                let axis_ops = perm_to_ops(&perms);\n\n                for (ax, op) in axis_ops.into_iter().enumerate() {\n                    wire = model.wire_node(format!(\"transposed_output.{ix}_{ax}\"), op, &[wire])?[0];\n                }\n                model.outputs[ix] = wire;\n            }\n        }\n\n        tract_metal::MetalTransform { gemm_impl: self.gemm_impl }\n            .transform_up_to_phase(&mut model, self.phase)?;\n        if self.optimize {\n            model = model.into_optimized()?;\n        }\n        let runnable = MetalTestTransformRunnable {\n            runnable: model.into_runnable()?,\n            transpose_inputs: self.transpose_inputs,\n            use_arena: self.use_arena,\n        };\n        Ok(Box::new(runnable))\n    }\n\n    fn check(&self) -> TractResult<()> {\n        Ok(())\n    }\n}\n\nmacro_rules! metal_test_suite {\n    ($id: ident, $phase: expr, $optimize: expr, $gemm_impl: expr, $transpose_inputs: ident, $use_arena: ident) => {\n        paste! {\n            mod [<$id _ $gemm_impl:lower>] {\n                use super::*;\n\n                fn runtime() -> &'static MetalTestRuntime {\n                    lazy_static::lazy_static! {\n                        static ref RT: MetalTestRuntime = MetalTestRuntime {\n                            name: stringify!([<$id _ $gemm_impl:lower>]),\n                            phase: $phase,\n                            optimize: $optimize,\n                            gemm_impl: $gemm_impl,\n                            transpose_inputs: $transpose_inputs,\n                            use_arena: $use_arena,\n                        };\n                    };\n                    &RT\n                }\n\n                include!(concat!(env!(\"OUT_DIR\"), \"/tests/\",  stringify!([<$gemm_impl:lower>]), \".rs\"));\n            }\n        }\n    };\n}\n\nmacro_rules! metal_runtime {\n    ($gemm_impl: expr) => {\n        metal_test_suite!(metal_phase_2_translate, 2, false, $gemm_impl, false, false);\n        metal_test_suite!(metal_phase_3_post_translate, 3, false, $gemm_impl, false, false);\n        metal_test_suite!(optimized_metal, usize::MAX, true, $gemm_impl, false, false);\n        metal_test_suite!(optimized_metal_transpose, usize::MAX, true, $gemm_impl, true, false);\n    };\n}\n\nstatic MLX: Option<MetalGemmImplKind> = Some(MetalGemmImplKind::Mlx);\nstatic MFA: Option<MetalGemmImplKind> = Some(MetalGemmImplKind::Mfa);\nstatic GGML: Option<MetalGemmImplKind> = Some(MetalGemmImplKind::Ggml);\n\n// Common transform\nmetal_test_suite!(metal_phase_0_einsum, 0, false, MLX, false, false);\nmetal_test_suite!(metal_phase_1_pre_translate, 1, false, MLX, false, false);\n\nmetal_runtime!(None);\nmetal_runtime!(MLX);\nmetal_runtime!(MFA);\nmetal_runtime!(GGML);\n"
  },
  {
    "path": "test-rt/test-metal/suite.rs",
    "content": "use infra::Test;\nuse suite_unit::bin_einsum::{BinEinsumProblem, BinEinsumProblemParams};\nuse suite_unit::conv_f32::{ConvProblem, ConvProblemParams};\n\npub fn suite() -> &'static infra::TestSuite {\n    lazy_static::lazy_static! {\n        static ref SUITE: infra::TestSuite  = mk_suite();\n    };\n    &SUITE\n}\n\n#[allow(clippy::needless_update)]\nfn mk_suite() -> infra::TestSuite {\n    let mut onnx = suite_onnx::suite().clone();\n    onnx.ignore(&ignore_onnx);\n\n    let mut unit = suite_unit::suite().unwrap().clone();\n    unit.ignore_case(&ignore_unit);\n\n    unit.get_sub_mut(\"bin_einsum\").add_arbitrary::<BinEinsumProblem>(\n        \"proptest\",\n        BinEinsumProblemParams {\n            force_unique_non_trivial_m_n: true,\n            max_dims: 6,\n            ..BinEinsumProblemParams::default()\n        },\n    );\n    unit.get_sub_mut(\"conv_f32\").add_arbitrary::<ConvProblem>(\n        \"proptest\",\n        ConvProblemParams { no_batch: true, ..ConvProblemParams::default() },\n    );\n\n    infra::TestSuite::default().with(\"onnx\", onnx).with(\"unit\", unit)\n}\n\nfn ignore_unit(t: &[String], _case: &dyn Test) -> bool {\n    (t[0] == \"bin_einsum\" && t[1] == \"proptest\")\n        || (t[0] == \"conv_f32\" && (t[1] == \"proptest\" || t[1] == \"bug_metal_0\"))\n        || t[0] == \"matmul_q40\"\n}\n\nfn ignore_onnx(t: &[String]) -> bool {\n    r#\"\ntest_slice_start_out_of_bounds\ntest_nllloss_NCd1d2d3d4d5_mean_weight_expanded\ntest_nllloss_NCd1d2d3d4d5_none_no_weight_expanded\ntest_tril_zero\ntest_triu_zero\n\"#\n    .trim()\n    .lines()\n    .any(|s| t.last().unwrap() == s.trim())\n}\n"
  },
  {
    "path": "test-rt/test-nnef-cycle/Cargo.toml",
    "content": "[package]\nname = \"test-nnef-cycle\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n\n[build-dependencies]\ninfra = { path = \"../infra\" }\nitertools.workspace = true\nlazy_static.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\ntract-core.workspace = true\ntract-transformers.workspace = true\n\n[dev-dependencies]\ninfra = { path = \"../infra\" }\nitertools.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\ntract-core.workspace = true\ntract-transformers.workspace = true\ntract-onnx-opl.workspace = true\ntract-nnef.workspace = true\n"
  },
  {
    "path": "test-rt/test-nnef-cycle/build.rs",
    "content": "#[path = \"suite.rs\"]\nmod suite;\n\nfn main() {\n    suite::suite().test_runtime(\n        \"nnef_cycle\",\n        \"suite::suite()\",\n        \"runtime()\",\n        \"Approximation::Approximate\",\n    );\n}\n"
  },
  {
    "path": "test-rt/test-nnef-cycle/src/lib.rs",
    "content": "#![cfg(test)]\nuse std::fmt::Debug;\n\nuse log::*;\nuse tract_nnef::internal::*;\nuse tract_onnx_opl::*;\n\n#[path = \"../suite.rs\"]\nmod suite;\n\nmod nnef_predump {\n    use super::*;\n\n    #[allow(dead_code)]\n    struct NnefPredumpRuntime(Nnef);\n\n    impl Debug for NnefPredumpRuntime {\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            write!(f, \"NnefPredumpRuntime\")\n        }\n    }\n\n    impl Runtime for NnefPredumpRuntime {\n        fn name(&self) -> StaticName {\n            \"nnef_predump\".into()\n        }\n\n        fn prepare_with_options(\n            &self,\n            mut model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            tract_nnef::ser::rewrite_model(&mut model)?;\n            Ok(Box::new(model.into_optimized()?.into_runnable_with_options(&options)?))\n        }\n\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    fn runtime() -> &'static NnefPredumpRuntime {\n        lazy_static::lazy_static! {\n            static ref RT: NnefPredumpRuntime = NnefPredumpRuntime(tract_nnef::nnef().with_onnx());\n        };\n        &RT\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/nnef_cycle.rs\"));\n}\n\nmod nnef_cycle {\n    use tract_transformers::WithTractTransformers;\n\n    use super::*;\n\n    struct NnefCyclingRuntime(Nnef);\n\n    impl Debug for NnefCyclingRuntime {\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            write!(f, \"NnefCyclingRuntime\")\n        }\n    }\n\n    impl Runtime for NnefCyclingRuntime {\n        fn name(&self) -> StaticName {\n            \"nnef_cycle\".into()\n        }\n\n        fn prepare_with_options(\n            &self,\n            model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            info!(\"Store to NNEF\");\n            let mut buffer = vec![];\n            // eprintln!(\"BEFORE NNEF:\\n{model}\");\n            // dbg!(&model);\n            self.0.write_to_tar(&model, &mut buffer)?;\n            // self.0.write_to_dir(&model, \"foo\")?;\n            info!(\"Reload from NNEF\");\n            let reloaded = self.0.model_for_read(&mut &*buffer)?;\n            // eprintln!(\"RELOADED:\\n{}\", reloaded.clone().into_decluttered().unwrap());\n            // dbg!(reloaded.clone().into_decluttered());\n            Ok(Box::new(reloaded.into_optimized()?.into_runnable_with_options(&options)?))\n        }\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    fn runtime() -> &'static NnefCyclingRuntime {\n        lazy_static::lazy_static! {\n            static ref RT: NnefCyclingRuntime = NnefCyclingRuntime(tract_nnef::nnef().with_onnx().with_tract_transformers());\n        };\n        &RT\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/nnef_cycle.rs\"));\n}\n"
  },
  {
    "path": "test-rt/test-nnef-cycle/suite.rs",
    "content": "use infra::Test;\nuse suite_unit::bin_einsum::{BinEinsumProblem, BinEinsumProblemParams};\nuse suite_unit::conv_q::{QConvProblem, QConvProblemParams};\n\npub fn suite() -> &'static infra::TestSuite {\n    lazy_static::lazy_static! {\n        static ref SUITE: infra::TestSuite  = mk_suite();\n    };\n    &SUITE\n}\n\n#[allow(clippy::needless_update)]\nfn mk_suite() -> infra::TestSuite {\n    let mut onnx = suite_onnx::suite().clone();\n    onnx.ignore(&ignore_onnx);\n\n    let mut unit = suite_unit::suite().unwrap().clone();\n    unit.ignore(&|name| name.len() == 2 && name[0] == \"bin_einsum\" && name[1] == \"proptest\");\n    unit.get_sub_mut(\"bin_einsum\").add_arbitrary::<BinEinsumProblem>(\n        \"proptest\",\n        BinEinsumProblemParams { max_dims: 7, ..Default::default() },\n    );\n    unit.ignore_case(&ignore_unit);\n    unit.get_sub_mut(\"conv_q\").add_arbitrary_with_filter::<QConvProblem>(\n        \"proptest\",\n        QConvProblemParams::default(),\n        compatible_conv_q,\n    );\n\n    infra::TestSuite::default().with(\"onnx\", onnx).with(\"unit\", unit)\n}\n\nfn ignore_onnx(t: &[String]) -> bool {\n    r#\"\ntest_averagepool_2d_ceil\ntest_averagepool_2d_pads_count_include_pad\ntest_averagepool_2d_precomputed_pads_count_include_pad\ntest_averagepool_2d_same_lower\ntest_cast_STRING_to_FLOAT\ntest_castlike_STRING_to_FLOAT_expanded\ntest_constantlike_ones_with_input\ntest_constantlike_threes_with_shape_and_dtype\ntest_constantlike_zeros_without_input_dtype\ntest_cumsum_1d_exclusive\ntest_cumsum_1d_reverse_exclusive\ntest_cumsum_2d\ntest_dequantizelinear\ntest_dropout_random\ntest_dynamicquantizelinear\ntest_dynamicquantizelinear_max_adjusted\ntest_dynamicquantizelinear_min_adjusted\ntest_if\ntest_gemm_broadcast\ntest_gemm_nobroadcast\ntest_maxpool_2d_ceil\ntest_maxpool_2d_same_lower\ntest_maxpool_with_argmax_2d_precomputed_pads\ntest_mod_broadcast\ntest_mod_int64_fmod\ntest_mod_mixed_sign_float16\ntest_mod_mixed_sign_float32\ntest_mod_mixed_sign_float64\ntest_mod_mixed_sign_int16\ntest_mod_mixed_sign_int32\ntest_mod_mixed_sign_int64\ntest_mod_mixed_sign_int8\ntest_mod_uint16\ntest_mod_uint32\ntest_mod_uint64\ntest_mod_uint8\ntest_matmulinteger\ntest_nllloss_NCd1d2d3_none_no_weight_negative_ii_expanded\ntest_nonzero_example\ntest_quantizelinear\ntest_qlinearmatmul_2D\ntest_qlinearmatmul_3D\ntest_reshape_reordered_dims\ntest_unsqueeze\n\"#\n    .trim()\n    .lines()\n    .any(|s| t.last().unwrap() == s.trim())\n}\n\nfn ignore_unit(t: &[String], tc: &dyn Test) -> bool {\n    if t[0] == \"q_flavours\" {\n        return true;\n    }\n    if let Some(qcp) = tc.downcast_ref::<QConvProblem>() {\n        return !compatible_conv_q(qcp);\n    }\n    false\n}\n\nfn compatible_conv_q(qcp: &QConvProblem) -> bool {\n    qcp.qp.iter().all(|qp| qp.len() == 1)\n}\n"
  },
  {
    "path": "test-rt/test-onnx-core/Cargo.toml",
    "content": "[package]\nname = \"test-onnx-core\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2024\"\n\n[dependencies]\nlazy_static.workspace = true\ntract-core.workspace = true\ntract-nnef.workspace = true\ntract-onnx.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\n\n[features]\nonnx_1_4_1 =  [\"suite-onnx/onnx_1_4_1\"]\nonnx_1_5_0 =  [\"suite-onnx/onnx_1_5_0\"]\nonnx_1_6_0 =  [\"suite-onnx/onnx_1_6_0\"]\nonnx_1_7_0 =  [\"suite-onnx/onnx_1_7_0\"]\nonnx_1_8_1 =  [\"suite-onnx/onnx_1_8_1\"]\nonnx_1_9_0 =  [\"suite-onnx/onnx_1_9_0\"]\nonnx_1_10_2 = [\"suite-onnx/onnx_1_10_2\"]\nonnx_1_11_0 = [\"suite-onnx/onnx_1_11_0\"]\nonnx_1_12_0 = [\"suite-onnx/onnx_1_12_0\"]\nonnx_1_13_0 = [\"suite-onnx/onnx_1_13_0\"]\nonnx_1_14_1 = [\"suite-onnx/onnx_1_14_1\"]\nonnx_1_15_0 = [\"suite-onnx/onnx_1_15_0\"]\nonnx_1_16_2 = [\"suite-onnx/onnx_1_16_2\"]\nonnx_1_17_0 = [\"suite-onnx/onnx_1_17_0\"]\nonnx_1_18_0 = [\"suite-onnx/onnx_1_18_0\"]\nonnx_1_19_1 = [\"suite-onnx/onnx_1_19_1\"]\ndefault = [ \"onnx_1_13_0\" ]\n\n[build-dependencies]\nsuite-onnx = { path = \"../suite-onnx\" }\n"
  },
  {
    "path": "test-rt/test-onnx-core/build.rs",
    "content": "fn main() {\n    let suite = suite_onnx::suite();\n    suite.test_runtime(\"default\", \"suite_onnx::suite()\", \"default()\", \"Approximation::Approximate\");\n    suite.test_runtime(\n        \"unoptimized\",\n        \"suite_onnx::suite()\",\n        \"unoptimized()\",\n        \"Approximation::Approximate\",\n    );\n    suite.test_runtime(\"as_blas\", \"suite_onnx::suite()\", \"as_blas()\", \"Approximation::Approximate\");\n}\n"
  },
  {
    "path": "test-rt/test-onnx-core/debug-utils/Cargo.toml",
    "content": "[package]\nname = \"debug-utils\"\nversion = \"0.20.7-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nedition = \"2024\"\n\n[workspace]\nmembers = []\n\n[dependencies]\nprotobuf = \"*\"\ntract-onnx.workspace = true\n"
  },
  {
    "path": "test-rt/test-onnx-core/debug-utils/README.md",
    "content": "\n\ncargo run -- ../../../.cached/onnx/onnx/backend/test/data/real/test_inception_v1/inception_v1/model.onnx inception_v1_all_outputs.onnx\n\nvirtualenv -p python3 ort\nsource ./ort/bin/activate\npip install numpy onnx onnxruntime\n\npython ./save_all.py inception_v1_all_outputs.onnx inception_v1_all_outputs.npz ../../../.cached/onnx/onnx/backend/test/data/real/test_inception_v1/inception_v1/test_data_0.npz\n"
  },
  {
    "path": "test-rt/test-onnx-core/debug-utils/save_all.py",
    "content": "import os\nimport sys\nimport numpy\nimport onnx\nfrom onnx import numpy_helper\nimport onnxruntime as rt\n\nmodel = sys.argv[1]\noutput_name = sys.argv[2]\n\nprint(\"model: \" + model)\nprint(\"output: \" + output_name)\nsess = rt.InferenceSession(model)\n\nknown = {}\nfor i in range(3, len(sys.argv)):\n    input_data = sys.argv[i]\n    if input_data.endswith(\".npz\"):\n        tensors = numpy.load(input_data)\n        for name, array in tensors.items():\n            known[name] = array\n        name = sess.get_inputs()[i-3].name\n    elif input_data.endswith(\".pb\"):\n        new_tensor = onnx.TensorProto()\n        with open(input_data, 'rb') as f:\n            new_tensor.ParseFromString(f.read())\n        name = new_tensor.name\n        input_data = numpy_helper.to_array(new_tensor)\n\nprint(\"known: \", known)\n\ninputs = {}\nfor input in sess.get_inputs():\n    inputs[input.name] = known[input.name]\n\noutputs = inputs.copy()\npred_onnx = sess.run(None, inputs)\n\nfor ix, output in enumerate(sess.get_outputs()):\n    outputs[output.name] = pred_onnx[ix]\n\nprint(\"computed: \", outputs.keys())\n\nfor name, array in outputs.items():\n    print(name)\n    print(array)\n\nos.mkdir(output_name)\n\nfor name in outputs:\n    value = numpy_helper.from_array(outputs[name], name=name)\n    with open(output_name + \"/\" + name + \".pb\", 'wb') as f:\n        f.write(value.SerializeToString())\n"
  },
  {
    "path": "test-rt/test-onnx-core/debug-utils/src/main.rs",
    "content": "use protobuf::Message;\nuse std::fs::File;\nuse tract_onnx::pb::{ModelProto, ValueInfoProto};\n\nfn main() {\n    let input = std::env::args().nth(1).unwrap();\n    let output = std::env::args().nth(2).unwrap();\n    let mut model =\n        protobuf::parse_from_reader::<ModelProto>(&mut File::open(input).unwrap()).unwrap();\n    let mut graph = model.take_graph();\n    let all_outputs: Vec<tract_onnx::pb::ValueInfoProto> = graph\n        .get_node()\n        .iter()\n        .flat_map(|n| {\n            n.get_output().iter().map(|s| {\n                let mut vip = ValueInfoProto::new();\n                vip.set_name(s.to_string());\n                vip\n            })\n        })\n        .collect();\n    graph.set_output(all_outputs.into());\n    model.set_graph(graph);\n    let mut f = File::create(output).unwrap();\n    let mut stream = protobuf::stream::CodedOutputStream::new(&mut f);\n    model.write_to(&mut stream).unwrap();\n    stream.flush().unwrap();\n}\n"
  },
  {
    "path": "test-rt/test-onnx-core/include-passing-ignored.sh",
    "content": "#!/bin/sh\n\ncargo run\n"
  },
  {
    "path": "test-rt/test-onnx-core/src/bin/reset-test-list.rs",
    "content": "use std::collections::HashMap;\nuse std::io::{BufRead, Write};\n\nconst SETS: &[&str] = &[\"node\", \"real\", \"simple\", \"pytorch-operator\", \"pytorch-converted\"];\nconst VERSIONS: &[&str] = &[\n    \"1.4.1\", \"1.5.0\", \"1.6.0\", \"1.7.0\", \"1.8.1\", \"1.9.0\", \"1.10.2\", \"1.11.0\", \"1.12.0\", \"1.13.0\",\n    \"1.14.1\", \"1.15_0\", \"1_16_2\", \"1_17_0\", \"1_18_0\", \"1_19_1\",\n];\n\n// const SETS: &[&str] = &[\"node\"];\n// const VERSIONS: &[&str] = &[\"1.4.1\"];\n\nfn run_set(set: &str, ver: &str) -> HashMap<String, usize> {\n    let filter = format!(\"{set}_{ver}::\").replace(['.', '-'].as_ref(), \"_\");\n    let mut command = std::process::Command::new(\"cargo\");\n    command.arg(\"test\").arg(\"--all-features\");\n    if set == \"real\" {\n        command.arg(\"--release\");\n    }\n    command.arg(\"--\").arg(\"--ignored\").arg(filter);\n    let output = command.output().unwrap();\n    let mut unexpected: HashMap<String, usize> = HashMap::default();\n    for line in std::io::BufReader::new(&mut &*output.stdout).lines() {\n        let line = line.unwrap();\n        if line.ends_with(\"ok\") {\n            let test_id =\n                line.split_whitespace().nth(1).unwrap().split(\"::\").nth(2).unwrap().to_string();\n            let level = line.split_whitespace().nth(1).unwrap().split(\"::\").nth(1).unwrap();\n            let level = match level {\n                \"nnef\" => 3,\n                \"optim\" => 2,\n                \"plain\" => 1,\n                _ => panic!(),\n            };\n            let entry = unexpected.entry(test_id.clone()).or_insert(0);\n            *entry = (*entry).max(level);\n        }\n    }\n    unexpected\n}\n\nfn process_unexpected(set: &str, ver: &str, unexpected: HashMap<String, usize>) {\n    let file = format!(\"{set}-{ver}.txt\");\n    eprintln!(\"## {file} ##\");\n    let mut specs: HashMap<String, String> = HashMap::new();\n    for line in std::fs::read_to_string(&file).unwrap().lines() {\n        let test = line.split_whitespace().next().unwrap().to_string();\n        let entry = specs.entry(test).or_default();\n        if entry.len() < line.len() {\n            *entry = line.to_string();\n        }\n    }\n    for (test_id, level) in unexpected.into_iter() {\n        eprintln!(\"* {test_id} level: {level}\");\n        let spec = specs\n            .entry(test_id.to_string())\n            .or_insert_with(|| format!(\"{test_id} not-nnef not-typable\"));\n        if level >= 3 {\n            *spec =\n                spec.split_whitespace().filter(|t| t != &\"not-nnef\").collect::<Vec<_>>().join(\" \");\n        }\n        if level >= 2 {\n            *spec = spec\n                .split_whitespace()\n                .filter(|t| t != &\"not-typable\")\n                .collect::<Vec<_>>()\n                .join(\" \");\n        }\n    }\n    let mut file = std::fs::OpenOptions::new().write(true).truncate(true).open(file).unwrap();\n    let mut specs: Vec<String> = specs.into_iter().map(|e| e.1).collect();\n    specs.sort();\n    let buffer = specs.join(\"\\n\");\n    file.write_all(buffer.as_bytes()).unwrap();\n}\n\nfn main() {\n    let mut sets: HashMap<(&str, &str), _> = HashMap::default();\n    for &set in SETS {\n        for &ver in VERSIONS {\n            eprintln!(\"Running {set} {ver}\");\n            sets.insert((set, ver), run_set(set, ver));\n        }\n    }\n    for ((set, ver), unexpected) in sets.into_iter() {\n        process_unexpected(set, ver, unexpected);\n    }\n}\n"
  },
  {
    "path": "test-rt/test-onnx-core/src/lib.rs",
    "content": "#![cfg(test)]\nuse tract_core::internal::*;\n\nmod default {\n    use super::*;\n    pub fn default() -> &'static DefaultRuntime {\n        &DefaultRuntime\n    }\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/default.rs\"));\n}\n\nmod unoptimized {\n    use super::*;\n\n    pub fn unoptimized() -> &'static UnoptimizedRuntime {\n        &UnoptimizedRuntime\n    }\n\n    #[derive(Debug)]\n    pub struct UnoptimizedRuntime;\n\n    impl Runtime for UnoptimizedRuntime {\n        fn name(&self) -> StaticName {\n            Cow::Borrowed(\"unoptimized\")\n        }\n        fn prepare_with_options(\n            &self,\n            model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            Ok(Box::new(model.into_runnable_with_options(options)?))\n        }\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/unoptimized.rs\"));\n}\n"
  },
  {
    "path": "test-rt/test-tflite/Cargo.toml",
    "content": "[package]\nname = \"test-tflite\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n\n[build-dependencies]\nhome.workspace = true\nlazy_static.workspace = true\nregex.workspace = true\ninfra = { path = \"../infra\" }\ntract-core.workspace = true\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\n\n[dev-dependencies]\nhome.workspace = true\nregex.workspace = true\nlazy_static.workspace = true\nlog.workspace = true\ntflitec.workspace = true\ntract-core.workspace = true\ntract-tflite.workspace = true\ntract-onnx-opl.workspace = true\ninfra = { path = \"../infra\" }\nsuite-onnx = { path = \"../suite-onnx\" }\nsuite-unit = { path = \"../suite-unit\" }\n"
  },
  {
    "path": "test-rt/test-tflite/build.rs",
    "content": "#[path = \"suite.rs\"]\nmod suite;\n\nfn main() {\n    suite::suite().test_runtime(\n        \"tests\",\n        \"suite::suite()\",\n        \"runtime()\",\n        \"Approximation::Approximate\",\n    );\n}\n"
  },
  {
    "path": "test-rt/test-tflite/src/lib.rs",
    "content": "#![cfg(test)]\nuse log::*;\nuse tract_tflite::Tflite;\nuse tract_tflite::internal::*;\n\n#[path = \"../suite.rs\"]\nmod suite;\n\nmod tflite_runtime;\n\nmod tflite_predump {\n    use super::*;\n    #[derive(Debug)]\n    #[allow(dead_code)]\n    struct TflitePredump(Tflite);\n\n    impl Runtime for TflitePredump {\n        fn name(&self) -> StaticName {\n            \"tflite-predump\".into()\n        }\n\n        fn prepare_with_options(\n            &self,\n            mut model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            tract_tflite::rewriter::rewrite_for_tflite(&mut model).context(\"Preparing model\")?;\n            Ok(Box::new(model.into_runnable_with_options(&options)?))\n        }\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    fn runtime() -> &'static TflitePredump {\n        lazy_static::lazy_static! {\n            static ref RT: TflitePredump = TflitePredump(Tflite::default());\n        };\n        &RT\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/tests.rs\"));\n}\n\nmod tflite_cycle {\n    use tract_tflite::internal::tract_core::ops::dummy::Dummy;\n\n    use super::*;\n    #[derive(Debug)]\n    struct TfliteCyclingRuntime(Tflite);\n\n    impl Runtime for TfliteCyclingRuntime {\n        fn name(&self) -> StaticName {\n            \"tflite-cycle\".into()\n        }\n\n        fn prepare_with_options(\n            &self,\n            model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            info!(\"Store to Tflite\");\n            let mut buffer = vec![];\n            self.0.write(&model, &mut buffer).context(\"Translating model to tflite\")?;\n            info!(\"Reload from Tflite\");\n            let mut reloaded =\n                self.0.model_for_read(&mut &*buffer).context(\"Reloading model from tflite\")?;\n            for i in 0..model.inputs.len() {\n                if model.input_fact(i)? != reloaded.input_fact(i)?\n                    && model.input_fact(i)?.datum_type.unquantized()\n                        == reloaded.input_fact(i)?.datum_type.unquantized()\n                {\n                    let old_source_outlet = reloaded.inputs[i];\n                    let name = reloaded.node(old_source_outlet.node).name.clone();\n                    let new_source = reloaded.add_source(&name, model.input_fact(i)?.clone())?;\n                    let wire = reloaded.wire_node(\n                        format!(\"{name}.qp\"),\n                        tract_core::ops::cast::cast(reloaded.input_fact(i)?.datum_type),\n                        &[new_source],\n                    )?[0];\n                    reloaded.inputs.pop();\n                    reloaded.inputs[i] = new_source;\n                    let succs = reloaded.node(old_source_outlet.node).outputs[0].successors.clone();\n                    for succ in succs {\n                        reloaded.add_edge(wire, succ)?;\n                    }\n                    for output in &mut reloaded.outputs {\n                        if *output == old_source_outlet {\n                            *output = new_source;\n                        }\n                    }\n                    reloaded.nodes[old_source_outlet.node].name.push_str(\".old\");\n                    reloaded.nodes[old_source_outlet.node].op = Box::new(Dummy);\n                }\n            }\n            Ok(Box::new(\n                reloaded\n                    .into_optimized()\n                    .context(\"Optimising post-cycle model\")?\n                    .into_runnable_with_options(options)?,\n            ))\n        }\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    fn runtime() -> &'static TfliteCyclingRuntime {\n        lazy_static::lazy_static! {\n            static ref RT: TfliteCyclingRuntime = TfliteCyclingRuntime(Tflite::default());\n        };\n        &RT\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/tests.rs\"));\n}\n"
  },
  {
    "path": "test-rt/test-tflite/src/tflite_runtime.rs",
    "content": "use std::fmt::Debug;\n\nuse tflitec::interpreter::Interpreter;\nuse tflitec::model::Model;\nuse tflitec::tensor::DataType;\n\nuse super::*;\n\nstruct TfliteRuntime(Tflite);\n\nimpl Debug for TfliteRuntime {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"TfliteRuntime\")\n    }\n}\n\nimpl Runtime for TfliteRuntime {\n    fn name(&self) -> StaticName {\n        \"tflite\".into()\n    }\n\n    fn prepare_with_options(\n        &self,\n        model: TypedModel,\n        _options: &RunOptions,\n    ) -> TractResult<Box<dyn Runnable>> {\n        let mut buffer = vec![];\n        self.0.write(&model, &mut buffer).context(\"Translating model to tflite\")?;\n        // std::fs::write(\"foo.tflite\", &buffer)?;\n        Ok(Box::new(TfliteRunnable(buffer, Arc::new(model))))\n    }\n    fn check(&self) -> TractResult<()> {\n        Ok(())\n    }\n}\n\n#[derive(Clone)]\nstruct TfliteRunnable(Vec<u8>, Arc<TypedModel>);\n\nimpl Runnable for TfliteRunnable {\n    fn spawn(&self) -> TractResult<Box<dyn State>> {\n        Ok(Box::new(TfliteState(self.clone())))\n    }\n\n    fn input_count(&self) -> usize {\n        self.1.inputs.len()\n    }\n\n    fn output_count(&self) -> usize {\n        self.1.outputs.len()\n    }\n\n    fn typed_model(&self) -> Option<&Arc<TypedModel>> {\n        Some(&self.1)\n    }\n\n    fn typed_plan(&self) -> Option<&Arc<TypedSimplePlan>> {\n        None\n    }\n}\n\nimpl Debug for TfliteRunnable {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"TfliteRunnable\")\n    }\n}\n\n#[derive(Debug)]\nstruct TfliteState(TfliteRunnable);\n\nimpl State for TfliteState {\n    fn run(&mut self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let model = Model::from_bytes(&self.0.0)?;\n        let interpreter = Interpreter::new(&model, None)?;\n        interpreter.allocate_tensors()?;\n        ensure!(inputs.len() == interpreter.input_tensor_count());\n        for (ix, input) in inputs.iter().enumerate() {\n            let input_tensor = interpreter.input(ix)?;\n            assert_eq!(input_tensor.shape().dimensions(), input.shape());\n            input_tensor.set_data(input.as_bytes())?;\n        }\n        interpreter.invoke()?;\n        let mut outputs = tvec![];\n        for ix in 0..interpreter.output_tensor_count() {\n            let output_tensor = interpreter.output(ix)?;\n            let dt = match output_tensor.data_type() {\n                DataType::Float32 => f32::datum_type(),\n                DataType::Bool => bool::datum_type(),\n                DataType::Int64 => i64::datum_type(),\n                DataType::Uint8 => {\n                    if let Some(qp) = output_tensor.quantization_parameters() {\n                        u8::datum_type().quantize(QParams::ZpScale {\n                            zero_point: qp.zero_point,\n                            scale: qp.scale,\n                        })\n                    } else {\n                        u8::datum_type()\n                    }\n                }\n                DataType::Int8 => {\n                    if let Some(qp) = output_tensor.quantization_parameters() {\n                        i8::datum_type().quantize(QParams::ZpScale {\n                            zero_point: qp.zero_point,\n                            scale: qp.scale,\n                        })\n                    } else {\n                        i8::datum_type()\n                    }\n                }\n                _ => bail!(\"unknown type in tract tflitec test Runtime\"),\n            };\n            let tensor = unsafe {\n                Tensor::from_raw_dt(dt, output_tensor.shape().dimensions(), output_tensor.data())?\n            };\n            outputs.push(tensor.into_tvalue());\n        }\n        Ok(outputs)\n    }\n\n    fn input_count(&self) -> usize {\n        self.0.input_count()\n    }\n\n    fn output_count(&self) -> usize {\n        self.0.input_count()\n    }\n\n    fn runnable(&self) -> &dyn Runnable {\n        todo!()\n    }\n\n    fn freeze(&self) -> Box<dyn FrozenState> {\n        todo!()\n    }\n}\n\nfn runtime() -> &'static TfliteRuntime {\n    lazy_static::lazy_static! {\n        static ref RT: TfliteRuntime = TfliteRuntime(Tflite::default());\n    };\n    &RT\n}\n\ninclude!(concat!(env!(\"OUT_DIR\"), \"/tests/tests.rs\"));\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_trivial() -> TractResult<()> {\n        let mut model = TypedModel::default();\n        let wire = model.add_source(\"x\", f32::fact([1]))?;\n        model.select_output_outlets(&[wire])?;\n        let out = runtime().prepare(model)?.run(tvec!(tensor1(&[0f32]).into_tvalue()))?.remove(0);\n        assert_eq!(out, tensor1(&[0f32]).into_tvalue());\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "test-rt/test-tflite/suite.rs",
    "content": "use infra::Test;\nuse regex::Regex;\nuse suite_unit::bin_einsum::{BinEinsumProblem, BinEinsumProblemParams};\nuse suite_unit::conv_f32::{ConvProblem, ConvProblemParams};\nuse suite_unit::conv_q::{QConvProblem, QConvProblemParams};\nuse tract_core::internal::*;\n\npub fn suite() -> &'static infra::TestSuite {\n    lazy_static::lazy_static! {\n        static ref SUITE: infra::TestSuite  = mk_suite();\n    };\n    &SUITE\n}\n\n#[allow(clippy::needless_update)]\nfn mk_suite() -> infra::TestSuite {\n    let mut onnx = suite_onnx::suite().clone();\n    onnx.ignore(&ignore_onnx);\n    onnx.skip(&skip_onnx);\n\n    let mut unit = suite_unit::suite().unwrap().clone();\n    unit.ignore_case(&ignore_unit);\n    let cv = ConvProblemParams {\n        no_group: true,\n        no_dilations: true,\n        geo_rank: Some(1..3),\n        ..ConvProblemParams::default()\n    };\n    unit.get_sub_mut(\"conv_f32\").add_arbitrary::<ConvProblem>(\"proptest\", cv.clone());\n    unit.get_sub_mut(\"conv_q\").add_arbitrary_with_filter::<QConvProblem>(\n        \"proptest\",\n        QConvProblemParams { conv: cv, tflite_rules: true, ..QConvProblemParams::default() },\n        compatible_conv_q,\n    );\n\n    let einsum_params = BinEinsumProblemParams { max_dims: 4, ..BinEinsumProblemParams::default() };\n    unit.get_sub_mut(\"bin_einsum\")\n        .add_arbitrary::<BinEinsumProblem>(\"proptest\", einsum_params.clone());\n    infra::TestSuite::default().with(\"onnx\", onnx).with(\"unit\", unit)\n}\n\nfn patterns(s: &str) -> Vec<Regex> {\n    s.trim()\n        .lines()\n        .map(|s| s.split_once('#').map(|(left, _)| left).unwrap_or(s).trim())\n        .filter(|s| !s.is_empty())\n        .map(|pat| Regex::new(pat).unwrap())\n        .collect()\n}\n\nfn ignore_onnx(t: &[String]) -> bool {\n    let name = t.last().unwrap();\n    let included = patterns(\n        \"\n        _conv_\n        Conv1d\n        Conv2d\n\n        test_averagepool_2d\n        test_maxpool_2d\n\n        squeeze\n        _transpose_\n        test_concat\n        test_flatten\n        test_reshape\n        test_slice\n        test_split\n\n        test_where\n        test_less\n        test_greater\n        test_equal\n        test_not\n\n        test_add\n        test_mul\n        test_sub\n        test_div\n        test_and\n        test_or\n\n        test_reduce\n        test_softmax\n\n        test_abs\n        test_ceil\n        test_exp\n        test_floor\n        test_log\n        test_reciprocal\n        test_square\n        test_sqrt\n        test_rsqrt\n\n        test_cos\n        test_sin\n        # lol, no tan :)\n\n        test_clip\n        test_batchnorm\n        test_hardswish\n        test_leakyrelu\n        test_prelu\n        test_relu\n        test_selu\n        test_sigmoid\n        test_tanh\n        test_thresholdrelu\n        \",\n    );\n    let excluded = patterns(\n        \"\n            test_slice_start_out_of_bounds\n            test_Conv1d_groups\n            test_Conv2d_groups\n            test_Conv1d_depthwise_with_multiplier\n            test_Conv2d_depthwise_with_multiplier\n            test_Conv2d_groups_thnn\n            test_reshape_allowzero_reordered\n            test_split_zero_size\n            test_mul_uint8\n            test_div_uint8\n            test_reduce_log_sum_exp.*           # tflite does not support f64 reducers 🤷\n            pool_2d_ceil\n            pool_2d_pads\n            pool_2d_precomputed_pads_count_include_pad\n            pool_2d_same_lower\n            test_cosh.*\n            test_sinh.*\n            \",\n    );\n    !included.iter().any(|pat| pat.is_match(name)) || excluded.iter().any(|pat| pat.is_match(name))\n}\n\n// We must *never* run these, even in --ignored mode, as they trigger buggy aborts in tflite runtime!\nfn skip_onnx(t: &[String]) -> bool {\n    let name = t.last().unwrap();\n    let excluded = \"\n            test_clip_default_int8_max_expanded\n            test_clip_default_int8_min_expanded\n            test_BatchNorm3d_eval\n            test_BatchNorm3d_momentum_eval\n            test_PReLU_3d\n            \";\n    excluded.split_whitespace().any(|s| s == name)\n}\n\nfn ignore_unit(t: &[String], case: &dyn Test) -> bool {\n    #[allow(clippy::collapsible_if)]\n    if let Some(cp) = case.downcast_ref::<ConvProblem>() {\n        if !compatible_conv_f32(cp) {\n            return true;\n        }\n    }\n    #[allow(clippy::collapsible_if)]\n    if let Some(qcp) = case.downcast_ref::<QConvProblem>() {\n        if !compatible_conv_q(qcp) {\n            return true;\n        }\n    }\n\n    if t[0] == \"bin_einsum\" && t[1] == \"proptest\" {\n        return true;\n    }\n\n    let [section, _unit] = t else { return false };\n    [\n        \"apply_rope\",\n        \"binary\",\n        \"conv_f16\",\n        \"deconv\",\n        \"elmwise\",\n        \"gelu_approximate\",\n        \"q_flavours\",\n        \"q_binary\",\n        \"q_elmwise\",\n        \"matmul_q40\",\n        \"rms_norm\",\n        \"scaled_masked_softmax\",\n        \"sdpa\",\n        \"silu\",\n    ]\n    .contains(&&**section)\n}\n\nfn compatible_conv_f32(qcp: &ConvProblem) -> bool {\n    qcp.group == 1\n        && (qcp.kernel.ndim() == 4 || qcp.kernel.ndim() == 3)\n        && qcp.dilations.iter().all(|d| *d == 1)\n}\n\nfn compatible_conv_q(qcp: &QConvProblem) -> bool {\n    if qcp.group != 1 {\n        return false;\n    }\n    let idt = qcp.data.datum_type();\n    let kdt = qcp.kernel.datum_type();\n    let odt = qcp.raw_output_dt;\n    if odt != idt.unquantized() {\n        return false;\n    }\n\n    // all u8 and per-layer\n    if idt.unquantized() == u8::datum_type()\n        && kdt.unquantized() == u8::datum_type()\n        && qcp.qp.iter().all(|qp| qp.is_uniform())\n    {\n        return true;\n    }\n    // all i8 and no zero_point\n    if idt.unquantized() == i8::datum_type()\n        && kdt.unquantized() == i8::datum_type()\n        && qcp.qp[0].is_zero().unwrap()\n        && qcp.qp[2].is_zero().unwrap()\n        && qcp.qp[4].is_zero().unwrap()\n    {\n        return true;\n    }\n    false\n}\n"
  },
  {
    "path": "test-rt/test-unit-core/Cargo.toml",
    "content": "[package]\nname = \"test-unit-core\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ntract-core.workspace = true\n\n[dev-dependencies]\nsuite-unit = { path = \"../suite-unit\" }\n\n[build-dependencies]\nsuite-unit = { path = \"../suite-unit\" }\n"
  },
  {
    "path": "test-rt/test-unit-core/build.rs",
    "content": "fn main() {\n    let suite = suite_unit::suite().unwrap();\n    suite.test_runtime(\n        \"raw\",\n        \"suite_unit::suite().unwrap()\",\n        \"raw()\",\n        \"Approximation::Approximate\",\n    );\n    suite.test_runtime(\n        \"decluttered\",\n        \"suite_unit::suite().unwrap()\",\n        \"decluttered()\",\n        \"Approximation::Approximate\",\n    );\n    suite.test_runtime(\n        \"optimized\",\n        \"suite_unit::suite().unwrap()\",\n        \"optimized()\",\n        \"Approximation::Approximate\",\n    );\n}\n"
  },
  {
    "path": "test-rt/test-unit-core/src/lib.rs",
    "content": "#![cfg(test)]\nuse tract_core::internal::*;\n\nmod raw {\n    use super::*;\n\n    pub fn raw() -> &'static RawRuntime {\n        &RawRuntime\n    }\n\n    #[derive(Debug)]\n    pub struct RawRuntime;\n\n    impl Runtime for RawRuntime {\n        fn name(&self) -> StaticName {\n            Cow::Borrowed(\"raw\")\n        }\n        fn prepare_with_options(\n            &self,\n            model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            Ok(Box::new(model.into_runnable_with_options(options)?))\n        }\n\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/raw.rs\"));\n}\n\nmod decluttered {\n    use super::*;\n\n    pub fn decluttered() -> &'static DeclutteredRuntime {\n        &DeclutteredRuntime\n    }\n\n    #[derive(Debug)]\n    pub struct DeclutteredRuntime;\n\n    impl Runtime for DeclutteredRuntime {\n        fn name(&self) -> StaticName {\n            Cow::Borrowed(\"decluttered\")\n        }\n        fn prepare_with_options(\n            &self,\n            model: TypedModel,\n            options: &RunOptions,\n        ) -> TractResult<Box<dyn Runnable>> {\n            Ok(Box::new(model.into_decluttered()?.into_runnable_with_options(options)?))\n        }\n        fn check(&self) -> TractResult<()> {\n            Ok(())\n        }\n    }\n\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/decluttered.rs\"));\n}\n\nmod optimized {\n    use super::*;\n\n    pub fn optimized() -> &'static DefaultRuntime {\n        &DefaultRuntime\n    }\n    include!(concat!(env!(\"OUT_DIR\"), \"/tests/optimized.rs\"));\n}\n"
  },
  {
    "path": "test-rt/test-unit-core/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello, world!\");\n}\n"
  },
  {
    "path": "test-suite.sh",
    "content": "#!/bin/sh\nexec ./.travis/native.sh\n"
  },
  {
    "path": "tflite/Cargo.toml",
    "content": "[package]\nname = \"tract-tflite\"\nversion = \"0.23.0-pre\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nedition = \"2021\"\n\n[dependencies]\nderive-new.workspace = true\nflatbuffers.workspace = true\ntract-core.workspace = true\n\n[features]\ncomplex = []\n"
  },
  {
    "path": "tflite/Readme.md",
    "content": "# tract-tflite\n\nunimplemented, sausage is being made. If you want to help feel free to open a PR.\n\n## Notes and Relevant Links\n\n[link to the tflite c api](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/c)\n\n[link to the related issue](https://github.com/sonos/tract/issues/1086)\n\nThe generated code handles creating a model from a flatbuffer table. Right now the main task (as far as I understand) is to start adding the code to build a Tract Model from the ModelBuffer.\n\nSo the modelBuffer(the model read from a flatbuffer file) has a few components (with associated functions) worth looking at: operator_codes, subgraphs, and then buffers.\n\n- subgraphs are likely the primary thing needed to create a tract model\n  - composed of tensors, inputs,outputs, operators, and a name\n  - input and output are fairly small vectors, I suspect they may be indices\n- buffers are sometimes empty (why?)\n\n## Metadata\n\n- [tensorflow docs on metadate, has information on subgraphs as well](https://www.tensorflow.org/lite/models/convert/metadata)\n\n## Tensors\n\n- probably need to convert from the generated datatypes to Tract's [DatumType](https://github.com/skewballfox/tract/blob/300db595a1ffe3088658643b694b41aaac71ee76/data/src/datum.rs#L121). it's in the toplevel data crate.\n  - this is part of the depenendency tract-core\n- [SO: what a variant tensor?](https://stackoverflow.com/questions/58899763/what-is-a-dt-variant-tensor)\n\n### Operators\n\n- the list of builtin Operators can be found in the [generated tflite schema](./src/tflite_generated.rs) around line 443 in the const array `ENUM_VALUES_BUILTIN_OPERATOR: [BuiltinOperator; 162]`.\n- the official docs on supported supset of tensorflow operators in [TFLite](https://www.tensorflow.org/lite/guide/op_select_allowlist)\n- the [tflite c code](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/c)\n\n### Subgraphs\n\nRight now, I'm testing with a specific model under test data, so this might not generalize to other models. If you open the model in [netron](netron.app), you'll find 3 separate graphs: main, sequential/net/while_cond, and sequential/net/while_body.\n\nIn the main graph, node 10 is just listed as while, but it's actually composed of the other subgraphs.\n\n### scratchpad\n\nI created a [repository for the sole purpose of poking around with tflite models](https://github.com/skewballfox/tflite_scratch), if you would like to add a model for testing please put it inside test data, and add any test input to lfs. If you write some utility that would be useful for others contributers, feel free to add it. Otherwise just clone it and forget it, it's just trow-away code.\n"
  },
  {
    "path": "tflite/schema/tflite.fbs",
    "content": "// Copyright 2017 The TensorFlow Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Revision History\n// Version 0: Initial version.\n// Version 1: Add subgraphs to schema.\n// Version 2: Rename operators to conform to NN API.\n// Version 3: Move buffer data from Model.Subgraph.Tensors to Model.Buffers.\n// Version 3a: Add new builtin op code field. Has backward compatibility with\n//             version 3.\n// Version 3b: Rename fields in SignatureDef. Has backward compatibility with\n//             version 3 and 3a.\n\nnamespace tflite;\n\n// This corresponds to the version.\nfile_identifier \"TFL3\";\n// File extension of any written files.\nfile_extension \"tflite\";\n\n// IMPORTANT: All new members of tables, enums and unions must be added at the\n// end to ensure backwards compatibility.\n\n// The type of data stored in a tensor.\nenum TensorType : byte {\n  FLOAT32 = 0,\n  FLOAT16 = 1,\n  INT32 = 2,\n  UINT8 = 3,\n  INT64 = 4,\n  STRING = 5,\n  BOOL = 6,\n  INT16 = 7,\n  COMPLEX64 = 8,\n  INT8 = 9,\n  FLOAT64 = 10,\n  COMPLEX128 = 11,\n  UINT64 = 12,\n  // Experimental: Resource and variant types are experimental, that are subject\n  // to change. Do not implement custom kernels using resource & variant types\n  // now.\n  RESOURCE = 13,\n  VARIANT = 14,\n  UINT32 = 15,\n  UINT16 = 16,\n  INT4 = 17,\n}\n\n// Custom quantization parameters for experimenting with new quantization\n// techniques.\ntable CustomQuantization {\n  custom:[ubyte] (force_align: 16);\n}\n\n// Represents a specific quantization technique's parameters.\nunion QuantizationDetails {\n  CustomQuantization,\n}\n\n// Parameters for converting a quantized tensor back to float.\ntable QuantizationParameters {\n  // These four parameters are the asymmetric linear quantization parameters.\n  // Given a quantized value q, the corresponding float value f should be:\n  //   f = scale * (q - zero_point)\n  // For other quantization types, the QuantizationDetails below is used.\n  min:[float];  // For importing back into tensorflow.\n  max:[float];  // For importing back into tensorflow.\n  scale:[float];  // For dequantizing the tensor's values.\n  zero_point:[long];\n\n  // If this is not none, the other quantization parameters (i.e. min, max,\n  // scale, zero_point fields above) are ignored and the value of the\n  // QuantizationDetails union should be used.\n  details:QuantizationDetails;\n\n  // Specifies the dimension of the Tensor's shape that the scales and\n  // zero_points correspond to. For example, a tensor t, with dims=[4, 3, 2, 1]\n  // with quantization params:\n  //   scale=[1.0, 2.0, 3.0], zero_point=[1, 2, 3], quantization_dimension=1\n  // will be quantized across the second dimension of t.\n  //   t[:, 0, :, :] will have scale[0]=1.0, zero_point[0]=1\n  //   t[:, 1, :, :] will have scale[1]=2.0, zero_point[0]=2\n  //   t[:, 2, :, :] will have scale[2]=3.0, zero_point[0]=3\n  quantized_dimension:int;\n}\n\n// Sparse tensors.\n// We use a modification of the TACO format.\n// Reference: http://tensor-compiler.org/kjolstad-oopsla17-tensor-compiler.pdf\n//\n// To encode a conceptual n-dimensional dense tensor with dims (d0, ..., dn-1),\n// potentially with a k-dimensional block (0 <= k <= n) with dims\n// (dn, ..., dn+k-1), the format needs to specify:\n//   1. In what order to traverse these dimensions. For example, to store a 2-D\n//      matrix in row major order, the traversal order would be (d0, d1),\n//      whereas to store it in column major order, the traversal order would be\n//      (d1, d0). If the 2-D matrix has a 2-D inner block, the traversal order\n//      could be (d0, d1, d2, d3).\n//   2. How each block dimension in (dn, ..., dn+k-1) maps to the original\n//      tensor dimension in (d0, ..., dn-1).\n//   3. In the traversal order defined above, the format (dense vs. sparse) and\n//      index metadata for each dimension. For a dense dimension, this is just\n//      the size of that dimension. For a sparse dimension, it's the same as\n//      the compressed index defined in the Compressed Sparse Row (CSR) format.\n//      (http://scipy-lectures.org/advanced/scipy_sparse/csr_matrix.html)\n\n// The storage type for a dimension. Currently we support:\n//   1. DENSE: each coordinate in this dimension is stored implicitly.\n//   2. SPARSE_CSR: only the coordinates with non-zero elements are stored. The\n//      compression technique is the same what CSR uses.\n// More types like a sparse dimension with a different compression technique\n// could be added to the list in the future.\nenum DimensionType : byte {\n  DENSE = 0,\n  SPARSE_CSR = 1,\n}\n\ntable Int32Vector {\n  values:[int];\n}\n\ntable Uint16Vector {\n  values:[ushort] (force_align: 4);\n}\n\ntable Uint8Vector {\n  values:[ubyte] (force_align: 4);\n}\n\n// Variable-typed buffer to store the index metadata for a sparse dimension.\n// The widest type is Int32 instead of UInt32 because tensor's shape is a int32\n// vector. We don't want the per-dimensional index to overflow that range.\nunion SparseIndexVector {\n  Int32Vector,\n  Uint16Vector,\n  Uint8Vector\n}\n\ntable DimensionMetadata {\n  // Whether a dimension is dense or sparse.\n  format:DimensionType;\n  // Index metadata used for a dimension.\n  //   - If format is DimensionType.DENSE then we use the dense_size field to\n  //     store the size of that dimension. Each index in that dimension is\n  //     stored implicitly.\n  //   - If format is DimensionType.SPARSE_CSR then we use array_segments and\n  //     array_indices to encode that dimension. array_segments represents how\n  //     to segment the indices array, each segment corresponds to one element\n  //     in the previous dimension. array_indices represents the index of the\n  //     non-zero elements within this dimension (as those in the CSR matrix\n  //     format, where the first array is row pointers and the second array is\n  //     column indices).\n  dense_size:int;\n  array_segments:SparseIndexVector;\n  array_indices:SparseIndexVector;\n}\n\n// Parameters to encode a sparse TfLite tensor.\ntable SparsityParameters {\n  // The traversal order of the dimensions defined in the `shape` field of the\n  // conceptual dense tensor. For a n-dimensional tensors with dims (d0, d1,\n  // ..., dn-1),\n  //   - if not block sparse, the traversal_order is just a permutation of (d0,\n  //     ..., dn-1). For example, a 2-D matrix stored in row-major order would\n  //     have traversal_order = (d0, d1).\n  //   - if block sparse with a k-dimensional block (0 <= k <= n), the\n  //     traversal_order has n + k elements. The first n elements are still a\n  //     permutation of (d0, ..., dn-1). The lask k elements are a permutation\n  //     of (dn, ..., dn+k-1), defining how to traverse a block internally. For\n  //     example, a 2-D matrix with 2-D blocks, both stored in row-major order\n  //     would have traversal_order = (d0, d1, d2, d3).\n  traversal_order:[int];\n  // For an n-dimensional tensor with a k-dimensional block (0 <= k <= n),\n  // stores how a block dimension in (dn, ..., dn+k-1) maps to the original\n  // tensor dimension in (d0, ..., dn).\n  // It's stored in the order of (dn, ..., dn+k-1).\n  // If not block-sparse, this field is NULL.\n  block_map:[int];\n  // In the traversal order defined above, the metadata needed for\n  // each dimension to locate the non-zero values in the original dense tensor.\n  // The size of the dim_metadata array = the size of the traversal_order array\n  // = n + k.\n  dim_metadata:[DimensionMetadata];\n}\n\n// The nested tensor type for VARIANT type.\ntable VariantSubType {\n  // The tensor shape.\n  shape:[int];\n  type:TensorType;\n  // If false, the rank or the number of tensor dimensions is unknown.\n  // If false, \"shape\" must be [].\n  has_rank: bool = false;\n}\n\ntable Tensor {\n  // The tensor shape. The meaning of each entry is operator-specific but\n  // builtin ops use: [batch size, height, width, number of channels] (That's\n  // Tensorflow's NHWC).\n  shape:[int];\n  type:TensorType;\n  // An index that refers to the buffers table at the root of the model. Or,\n  // if there is no data buffer associated (i.e. intermediate results), then\n  // this is 0 (which refers to an always existent empty buffer).\n  //\n  // The data_buffer itself is an opaque container, with the assumption that the\n  // target device is little-endian. In addition, all builtin operators assume\n  // the memory is ordered such that if `shape` is [4, 3, 2], then index\n  // [i, j, k] maps to data_buffer[i*3*2 + j*2 + k].\n  buffer:uint;\n  name:string;  // For debugging and importing back into tensorflow.\n  quantization:QuantizationParameters;  // Optional.\n\n  is_variable:bool = false;\n\n  // Parameters to encode a sparse tensor. See the example in\n  // tensorflow/lite/testdata/sparse_tensor.json.\n  sparsity:SparsityParameters;  // Optional.\n\n  // Encodes `shape` with unknown dimensions. Unknown dimensions are\n  // represented with -1.\n  shape_signature:[int]; // Optional.\n\n  // If false, the rank or the number of tensor dimensions is unknown.\n  // If false, \"shape\" must be [].\n  has_rank: bool = false;\n\n  // The nested Tensor types for VARIANT type. This is always empty for\n  // non-VARIANT types. This is optional because the nested type can be omitted.\n  // Currently only 1 subtype is supported. The field is defined as an array for\n  // flexibility of supporting multiple subtypes in the future.\n  variant_tensors:[VariantSubType];\n}\n\n// A list of builtin operators. Builtin operators are slightly faster than custom\n// ones, but not by much. Moreover, while custom operators accept an opaque\n// object containing configuration parameters, builtins have a predetermined\n// set of acceptable options.\n// LINT.IfChange\nenum BuiltinOperator : int32 {\n  ADD = 0,\n  AVERAGE_POOL_2D = 1,\n  CONCATENATION = 2,\n  CONV_2D = 3,\n  DEPTHWISE_CONV_2D = 4,\n  DEPTH_TO_SPACE = 5,\n  DEQUANTIZE = 6,\n  EMBEDDING_LOOKUP = 7,\n  FLOOR = 8,\n  FULLY_CONNECTED = 9,\n  HASHTABLE_LOOKUP = 10,\n  L2_NORMALIZATION = 11,\n  L2_POOL_2D = 12,\n  LOCAL_RESPONSE_NORMALIZATION = 13,\n  LOGISTIC = 14,\n  LSH_PROJECTION = 15,\n  LSTM = 16,\n  MAX_POOL_2D = 17,\n  MUL = 18,\n  RELU = 19,\n  // NOTE(aselle): RELU_N1_TO_1 used to be called RELU1, but it was renamed\n  // since different model developers use RELU1 in different ways. Never\n  // create another op called RELU1.\n  RELU_N1_TO_1 = 20,\n  RELU6 = 21,\n  RESHAPE = 22,\n  RESIZE_BILINEAR = 23,\n  RNN = 24,\n  SOFTMAX = 25,\n  SPACE_TO_DEPTH = 26,\n  SVDF = 27,\n  TANH = 28,\n  CONCAT_EMBEDDINGS = 29,\n  SKIP_GRAM = 30,\n  CALL = 31,\n  CUSTOM = 32,\n  EMBEDDING_LOOKUP_SPARSE = 33,\n  PAD = 34,\n  UNIDIRECTIONAL_SEQUENCE_RNN = 35,\n  GATHER = 36,\n  BATCH_TO_SPACE_ND = 37,\n  SPACE_TO_BATCH_ND = 38,\n  TRANSPOSE = 39,\n  MEAN = 40,\n  SUB = 41,\n  DIV = 42,\n  SQUEEZE = 43,\n  UNIDIRECTIONAL_SEQUENCE_LSTM = 44,\n  STRIDED_SLICE = 45,\n  BIDIRECTIONAL_SEQUENCE_RNN = 46,\n  EXP = 47,\n  TOPK_V2 = 48,\n  SPLIT = 49,\n  LOG_SOFTMAX = 50,\n  // DELEGATE is a special op type for the operations which are delegated to\n  // other backends.\n  // WARNING: Experimental interface, subject to change\n  DELEGATE = 51,\n  BIDIRECTIONAL_SEQUENCE_LSTM = 52,\n  CAST = 53,\n  PRELU = 54,\n  MAXIMUM = 55,\n  ARG_MAX = 56,\n  MINIMUM = 57,\n  LESS = 58,\n  NEG = 59,\n  PADV2 = 60,\n  GREATER = 61,\n  GREATER_EQUAL = 62,\n  LESS_EQUAL = 63,\n  SELECT = 64,\n  SLICE = 65,\n  SIN = 66,\n  TRANSPOSE_CONV = 67,\n  SPARSE_TO_DENSE = 68,\n  TILE = 69,\n  EXPAND_DIMS = 70,\n  EQUAL = 71,\n  NOT_EQUAL = 72,\n  LOG = 73,\n  SUM = 74,\n  SQRT = 75,\n  RSQRT = 76,\n  SHAPE = 77,\n  POW = 78,\n  ARG_MIN = 79,\n  FAKE_QUANT = 80,\n  REDUCE_PROD = 81,\n  REDUCE_MAX = 82,\n  PACK = 83,\n  LOGICAL_OR = 84,\n  ONE_HOT = 85,\n  LOGICAL_AND = 86,\n  LOGICAL_NOT = 87,\n  UNPACK = 88,\n  REDUCE_MIN = 89,\n  FLOOR_DIV = 90,\n  REDUCE_ANY = 91,\n  SQUARE = 92,\n  ZEROS_LIKE = 93,\n  FILL = 94,\n  FLOOR_MOD = 95,\n  RANGE = 96,\n  RESIZE_NEAREST_NEIGHBOR = 97,\n  LEAKY_RELU = 98,\n  SQUARED_DIFFERENCE = 99,\n  MIRROR_PAD = 100,\n  ABS = 101,\n  SPLIT_V = 102,\n  UNIQUE = 103,\n  CEIL = 104,\n  REVERSE_V2 = 105,\n  ADD_N = 106,\n  GATHER_ND = 107,\n  COS = 108,\n  WHERE = 109,\n  RANK = 110,\n  ELU = 111,\n  REVERSE_SEQUENCE = 112,\n  MATRIX_DIAG = 113,\n  QUANTIZE = 114,\n  MATRIX_SET_DIAG = 115,\n  ROUND = 116,\n  HARD_SWISH = 117,\n  IF = 118,\n  WHILE = 119,\n  NON_MAX_SUPPRESSION_V4 = 120,\n  NON_MAX_SUPPRESSION_V5 = 121,\n  SCATTER_ND = 122,\n  SELECT_V2 = 123,\n  DENSIFY = 124,\n  SEGMENT_SUM = 125,\n  BATCH_MATMUL = 126,\n  PLACEHOLDER_FOR_GREATER_OP_CODES = 127,\n  CUMSUM = 128,\n  CALL_ONCE = 129,\n  BROADCAST_TO = 130,\n  RFFT2D = 131,\n  CONV_3D = 132,\n  IMAG=133,\n  REAL=134,\n  COMPLEX_ABS=135,\n  HASHTABLE = 136,\n  HASHTABLE_FIND = 137,\n  HASHTABLE_IMPORT = 138,\n  HASHTABLE_SIZE = 139,\n  REDUCE_ALL = 140,\n  CONV_3D_TRANSPOSE = 141,\n  VAR_HANDLE = 142,\n  READ_VARIABLE = 143,\n  ASSIGN_VARIABLE = 144,\n  BROADCAST_ARGS = 145,\n  RANDOM_STANDARD_NORMAL = 146,\n  BUCKETIZE = 147,\n  RANDOM_UNIFORM = 148,\n  MULTINOMIAL = 149,\n  GELU = 150,\n  DYNAMIC_UPDATE_SLICE = 151,\n  RELU_0_TO_1 = 152,\n  UNSORTED_SEGMENT_PROD = 153,\n  UNSORTED_SEGMENT_MAX = 154,\n  UNSORTED_SEGMENT_SUM = 155,\n  ATAN2 = 156,\n  UNSORTED_SEGMENT_MIN = 157,\n  SIGN = 158,\n  BITCAST = 159,\n  BITWISE_XOR = 160,\n  RIGHT_SHIFT = 161,\n}\n// LINT.ThenChange(nnapi_linter/linter.proto)\n\n// Options for the builtin operators.\nunion BuiltinOptions {\n  Conv2DOptions,\n  DepthwiseConv2DOptions,\n  ConcatEmbeddingsOptions,\n  LSHProjectionOptions,\n  Pool2DOptions,\n  SVDFOptions,\n  RNNOptions,\n  FullyConnectedOptions,\n  SoftmaxOptions,\n  ConcatenationOptions,\n  AddOptions,\n  L2NormOptions,\n  LocalResponseNormalizationOptions,\n  LSTMOptions,\n  ResizeBilinearOptions,\n  CallOptions,\n  ReshapeOptions,\n  SkipGramOptions,\n  SpaceToDepthOptions,\n  EmbeddingLookupSparseOptions,\n  MulOptions,\n  PadOptions,\n  GatherOptions,\n  BatchToSpaceNDOptions,\n  SpaceToBatchNDOptions,\n  TransposeOptions,\n  ReducerOptions,\n  SubOptions,\n  DivOptions,\n  SqueezeOptions,\n  SequenceRNNOptions,\n  StridedSliceOptions,\n  ExpOptions,\n  TopKV2Options,\n  SplitOptions,\n  LogSoftmaxOptions,\n  CastOptions,\n  DequantizeOptions,\n  MaximumMinimumOptions,\n  ArgMaxOptions,\n  LessOptions,\n  NegOptions,\n  PadV2Options,\n  GreaterOptions,\n  GreaterEqualOptions,\n  LessEqualOptions,\n  SelectOptions,\n  SliceOptions,\n  TransposeConvOptions,\n  SparseToDenseOptions,\n  TileOptions,\n  ExpandDimsOptions,\n  EqualOptions,\n  NotEqualOptions,\n  ShapeOptions,\n  PowOptions,\n  ArgMinOptions,\n  FakeQuantOptions,\n  PackOptions,\n  LogicalOrOptions,\n  OneHotOptions,\n  LogicalAndOptions,\n  LogicalNotOptions,\n  UnpackOptions,\n  FloorDivOptions,\n  SquareOptions,\n  ZerosLikeOptions,\n  FillOptions,\n  BidirectionalSequenceLSTMOptions,\n  BidirectionalSequenceRNNOptions,\n  UnidirectionalSequenceLSTMOptions,\n  FloorModOptions,\n  RangeOptions,\n  ResizeNearestNeighborOptions,\n  LeakyReluOptions,\n  SquaredDifferenceOptions,\n  MirrorPadOptions,\n  AbsOptions,\n  SplitVOptions,\n  UniqueOptions,\n  ReverseV2Options,\n  AddNOptions,\n  GatherNdOptions,\n  CosOptions,\n  WhereOptions,\n  RankOptions,\n  ReverseSequenceOptions,\n  MatrixDiagOptions,\n  QuantizeOptions,\n  MatrixSetDiagOptions,\n  HardSwishOptions,\n  IfOptions,\n  WhileOptions,\n  DepthToSpaceOptions,\n  NonMaxSuppressionV4Options,\n  NonMaxSuppressionV5Options,\n  ScatterNdOptions,\n  SelectV2Options,\n  DensifyOptions,\n  SegmentSumOptions,\n  BatchMatMulOptions,\n  CumsumOptions,\n  CallOnceOptions,\n  BroadcastToOptions,\n  Rfft2dOptions,\n  Conv3DOptions,\n  HashtableOptions,\n  HashtableFindOptions,\n  HashtableImportOptions,\n  HashtableSizeOptions,\n  VarHandleOptions,\n  ReadVariableOptions,\n  AssignVariableOptions,\n  RandomOptions,\n  BucketizeOptions,\n  GeluOptions,\n  DynamicUpdateSliceOptions,\n  UnsortedSegmentProdOptions,\n  UnsortedSegmentMaxOptions,\n  UnsortedSegmentMinOptions,\n  UnsortedSegmentSumOptions,\n  ATan2Options,\n  SignOptions,\n  BitcastOptions,\n  BitwiseXorOptions,\n  RightShiftOptions,\n}\n\n// LINT.IfChange\nenum Padding : byte { SAME, VALID }\n// LINT.ThenChange(//tensorflow/compiler/mlir/lite/ir/tfl_op_enums.td)\n\n// LINT.IfChange\nenum ActivationFunctionType : byte {\n  NONE = 0,\n  RELU = 1,\n  RELU_N1_TO_1 = 2,\n  RELU6 = 3,\n  TANH = 4,\n  SIGN_BIT = 5,\n}\n// LINT.ThenChange(//tensorflow/compiler/mlir/lite/ir/tfl_op_enums.td)\n\ntable Conv2DOptions {\n  padding:Padding;\n  stride_w:int;\n  stride_h:int;\n  fused_activation_function:ActivationFunctionType;\n  dilation_w_factor:int = 1;\n  dilation_h_factor:int = 1;\n}\n\n// Options for both Conv3D and Conv3DTranspose.\ntable Conv3DOptions {\n  padding:Padding;\n  stride_d:int;\n  stride_w:int;\n  stride_h:int;\n  fused_activation_function:ActivationFunctionType;\n  dilation_d_factor:int = 1;\n  dilation_w_factor:int = 1;\n  dilation_h_factor:int = 1;\n}\n\ntable Pool2DOptions {\n  padding:Padding;\n  stride_w:int;\n  stride_h:int;\n  filter_width:int;\n  filter_height:int;\n  fused_activation_function:ActivationFunctionType;\n}\n\ntable DepthwiseConv2DOptions {\n  // Parameters for DepthwiseConv version 1 or above.\n  padding:Padding;\n  stride_w:int;\n  stride_h:int;\n  // `depth_multiplier` is redundant. It's used by CPU kernels in\n  // TensorFlow 2.0 or below, but ignored in versions above.\n  // See comments in lite/c/builtin_op_data.h for more details.\n  depth_multiplier:int;\n  fused_activation_function:ActivationFunctionType;\n  // Parameters for DepthwiseConv version 2 or above.\n  dilation_w_factor:int = 1;\n  dilation_h_factor:int = 1;\n}\n\ntable ConcatEmbeddingsOptions {\n  num_channels:int;\n  num_columns_per_channel:[int];\n  embedding_dim_per_channel:[int]; // This could be inferred from parameters.\n}\n\nenum LSHProjectionType: byte {\n  UNKNOWN = 0,\n  SPARSE = 1,\n  DENSE = 2,\n}\n\ntable LSHProjectionOptions {\n  type: LSHProjectionType;\n}\n\ntable SVDFOptions {\n  rank:int;\n  fused_activation_function:ActivationFunctionType;\n  // For weights-only quantization, use asymmetric quantization for non\n  // constant inputs at evaluation time.\n  asymmetric_quantize_inputs:bool;\n}\n\n// An implementation of TensorFlow RNNCell.\ntable RNNOptions {\n  fused_activation_function:ActivationFunctionType;\n  asymmetric_quantize_inputs:bool;\n}\n\n// An implementation of TensorFlow dynamic_rnn with RNNCell.\ntable SequenceRNNOptions {\n  time_major:bool;\n  fused_activation_function:ActivationFunctionType;\n  asymmetric_quantize_inputs:bool;\n}\n\n// An implementation of TensorFlow bidrectional_dynamic_rnn with RNNCell.\ntable BidirectionalSequenceRNNOptions {\n  time_major:bool;\n  fused_activation_function:ActivationFunctionType;\n  merge_outputs: bool;\n  asymmetric_quantize_inputs:bool;\n}\n\n// LINT.IfChange\nenum FullyConnectedOptionsWeightsFormat: byte {\n  DEFAULT = 0,\n  SHUFFLED4x16INT8 = 1,\n}\n// LINT.ThenChange(//tensorflow/compiler/mlir/lite/ir/tfl_op_enums.td)\n\n// An implementation of TensorFlow fully_connected (a.k.a Dense) layer.\ntable FullyConnectedOptions {\n  // Parameters for FullyConnected version 1 or above.\n  fused_activation_function:ActivationFunctionType;\n\n  // Parameters for FullyConnected version 2 or above.\n  weights_format:FullyConnectedOptionsWeightsFormat = DEFAULT;\n\n  // Parameters for FullyConnected version 5 or above.\n  // If set to true, then the number of dimension is preserved. Furthermore,\n  // all but the last dimension of the input and output shapes will be equal.\n  keep_num_dims: bool;\n\n  // Parameters for FullyConnected version 7 or above.\n  // If set to true, then weights-only op will use asymmetric quantization for\n  // inputs.\n  asymmetric_quantize_inputs: bool;\n}\n\ntable SoftmaxOptions {\n  beta: float;\n}\n\n// An implementation of TensorFlow concat.\ntable ConcatenationOptions {\n  axis:int;\n  fused_activation_function:ActivationFunctionType;\n}\n\ntable AddOptions {\n  fused_activation_function:ActivationFunctionType;\n  // Parameters supported by version 3.\n  pot_scale_int16:bool = true;\n}\n\ntable MulOptions {\n  fused_activation_function:ActivationFunctionType;\n}\n\ntable L2NormOptions {\n  // This field is currently ignored in the L2 Norm Op.\n  fused_activation_function:ActivationFunctionType;\n}\n\ntable LocalResponseNormalizationOptions {\n  radius:int;\n  bias:float;\n  alpha:float;\n  beta:float;\n}\n\n// LINT.IfChange\nenum LSTMKernelType : byte {\n  // Full LSTM kernel which supports peephole and projection.\n  FULL = 0,\n  // Basic LSTM kernels. Equivalent to TensorFlow BasicLSTMCell.\n  BASIC = 1,\n}\n// LINT.ThenChange(//tensorflow/compiler/mlir/lite/ir/tfl_op_enums.td)\n\n// An implementation of TensorFlow LSTMCell and CoupledInputForgetGateLSTMCell\ntable LSTMOptions {\n  // Parameters for LSTM version 1 or above.\n  fused_activation_function:ActivationFunctionType;\n  cell_clip: float; // Optional, 0.0 means no clipping\n  proj_clip: float; // Optional, 0.0 means no clipping\n\n  // Parameters for LSTM version 2 or above.\n  // Basic kernel is only supported in version 2 or above.\n  kernel_type: LSTMKernelType = FULL;\n\n  // Parameters for LSTM version 4 or above.\n  asymmetric_quantize_inputs: bool;\n}\n\n// An implementation of TensorFlow dynamic_rnn with LSTMCell.\ntable UnidirectionalSequenceLSTMOptions {\n  fused_activation_function:ActivationFunctionType;\n  cell_clip: float; // Optional, 0.0 means no clipping\n  proj_clip: float; // Optional, 0.0 means no clipping\n\n  // If true then first dimension is sequence, otherwise batch.\n  time_major:bool;\n\n  // Parameter for Unidirectional Sequence LSTM version 3.\n  asymmetric_quantize_inputs:bool;\n\n  // Parameter for unidirectional sequence RNN version 4.\n  diagonal_recurrent_tensors:bool;\n}\n\ntable BidirectionalSequenceLSTMOptions {\n  // Parameters supported by version 1:\n  fused_activation_function:ActivationFunctionType;\n  cell_clip: float; // Optional, 0.0 means no clipping\n  proj_clip: float; // Optional, 0.0 means no clipping\n\n  // If true, store the outputs of both directions into the first output.\n  merge_outputs: bool;\n\n  // Parameters supported by version 2:\n  // If true then first dimension is sequence, otherwise batch.\n  // Version 1 implementations assumed time_major to be true, so this default\n  // value should never change.\n  time_major: bool = true;\n\n  // Parameters for version 3 or above.\n  asymmetric_quantize_inputs:bool;\n}\n\ntable ResizeBilinearOptions {\n  new_height: int (deprecated);\n  new_width: int (deprecated);\n  align_corners: bool;\n  half_pixel_centers: bool;\n}\n\ntable ResizeNearestNeighborOptions {\n  align_corners: bool;\n  half_pixel_centers: bool;\n}\n\n// A call operation options\ntable CallOptions {\n  // The subgraph index that needs to be called.\n  subgraph:uint;\n}\n\ntable PadOptions {\n}\n\ntable PadV2Options {\n}\n\ntable ReshapeOptions {\n  new_shape:[int];\n}\n\ntable SpaceToBatchNDOptions {\n}\n\ntable BatchToSpaceNDOptions {\n}\n\ntable SkipGramOptions {\n  ngram_size: int;\n  max_skip_size: int;\n  include_all_ngrams: bool;\n}\n\ntable SpaceToDepthOptions {\n  block_size: int;\n}\n\ntable DepthToSpaceOptions {\n  block_size: int;\n}\n\ntable SubOptions {\n  fused_activation_function:ActivationFunctionType;\n  // Parameters supported by version 5\n  pot_scale_int16:bool = true;\n}\n\ntable DivOptions {\n  fused_activation_function:ActivationFunctionType;\n}\n\ntable TopKV2Options {\n}\n\nenum CombinerType : byte {\n  SUM = 0,\n  MEAN = 1,\n  SQRTN = 2,\n}\n\ntable EmbeddingLookupSparseOptions {\n  combiner:CombinerType;\n}\n\ntable GatherOptions {\n  axis: int;\n  // Parameters for Gather version 5 or above.\n  batch_dims: int = 0;\n}\n\ntable TransposeOptions {\n}\n\ntable ExpOptions {\n}\n\ntable CosOptions {\n}\n\ntable ReducerOptions {\n  keep_dims: bool;\n}\n\ntable SqueezeOptions {\n  squeeze_dims:[int];\n}\n\ntable SplitOptions {\n  num_splits: int;\n}\n\ntable SplitVOptions {\n  num_splits: int;\n}\n\ntable StridedSliceOptions {\n  begin_mask: int;\n  end_mask: int;\n  ellipsis_mask: int;\n  new_axis_mask: int;\n  shrink_axis_mask: int;\n}\n\ntable LogSoftmaxOptions {\n}\n\ntable CastOptions {\n  in_data_type: TensorType;\n  out_data_type: TensorType;\n}\n\ntable DequantizeOptions {\n}\n\ntable MaximumMinimumOptions {\n}\n\ntable TileOptions {\n}\n\ntable ArgMaxOptions {\n  output_type : TensorType;\n}\n\ntable ArgMinOptions {\n  output_type : TensorType;\n}\n\ntable GreaterOptions {\n}\n\ntable GreaterEqualOptions {\n}\n\ntable LessOptions {\n}\n\ntable LessEqualOptions {\n}\n\ntable NegOptions {\n}\n\ntable SelectOptions {\n}\n\ntable SliceOptions {\n}\n\ntable TransposeConvOptions {\n  // Parameters supported by version 1, 2, 3:\n  padding:Padding;\n  stride_w:int;\n  stride_h:int;\n\n  // Parameters supported by version 4:\n  fused_activation_function:ActivationFunctionType = NONE;\n}\n\ntable ExpandDimsOptions {\n}\n\ntable SparseToDenseOptions {\n  validate_indices:bool;\n}\n\ntable EqualOptions {\n}\n\ntable NotEqualOptions {\n}\n\ntable ShapeOptions {\n  // Optional output type of the operation (int32 or int64). Defaults to int32.\n  out_type : TensorType;\n}\n\ntable RankOptions {\n}\n\ntable PowOptions {\n}\n\ntable FakeQuantOptions {\n  // Parameters supported by version 1:\n  min:float;\n  max:float;\n  num_bits:int;\n\n  // Parameters supported by version 2:\n  narrow_range:bool;\n}\n\ntable PackOptions {\n  values_count:int;\n  axis:int;\n}\n\ntable LogicalOrOptions {\n}\n\ntable OneHotOptions {\n  axis:int;\n}\n\ntable AbsOptions {\n}\n\n\ntable HardSwishOptions {\n}\n\ntable LogicalAndOptions {\n}\n\ntable LogicalNotOptions {\n}\n\ntable UnpackOptions {\n  num:int;\n  axis:int;\n}\n\ntable FloorDivOptions {\n}\n\ntable SquareOptions {\n}\n\ntable ZerosLikeOptions {\n}\n\ntable FillOptions {\n}\n\ntable FloorModOptions {\n}\n\ntable RangeOptions {\n}\n\ntable LeakyReluOptions {\n  alpha:float;\n}\n\ntable SquaredDifferenceOptions {\n}\n\n// LINT.IfChange\nenum MirrorPadMode : byte {\n  // Doesn't include borders.\n  REFLECT = 0,\n  // Includes borders.\n  SYMMETRIC = 1,\n}\n// LINT.ThenChange(//tensorflow/compiler/mlir/lite/ir/tfl_op_enums.td)\n\ntable MirrorPadOptions {\n  mode:MirrorPadMode;\n}\n\ntable UniqueOptions {\n  idx_out_type:TensorType = INT32;\n}\n\ntable ReverseV2Options {\n}\n\ntable AddNOptions {\n}\n\ntable GatherNdOptions {\n}\n\ntable WhereOptions {\n}\n\ntable ReverseSequenceOptions {\n  seq_dim:int;\n  batch_dim:int = 0;\n}\n\ntable MatrixDiagOptions {\n}\n\ntable QuantizeOptions {\n}\n\ntable MatrixSetDiagOptions {\n}\n\ntable IfOptions {\n  then_subgraph_index:int;\n  else_subgraph_index:int;\n}\n\ntable CallOnceOptions {\n  init_subgraph_index:int;\n}\n\ntable WhileOptions {\n  cond_subgraph_index:int;\n  body_subgraph_index:int;\n}\n\ntable NonMaxSuppressionV4Options {\n}\n\ntable NonMaxSuppressionV5Options {\n}\n\ntable ScatterNdOptions {\n}\n\ntable SelectV2Options {\n}\n\ntable DensifyOptions {\n}\n\ntable SegmentSumOptions {\n}\n\ntable BatchMatMulOptions {\n  adj_x:bool;\n  adj_y:bool;\n  // Parameters for BatchMatMul version 4 or above.\n  // If set to true, then weights-only op will use asymmetric quantization for\n  // inputs.\n  asymmetric_quantize_inputs: bool;\n}\n\ntable CumsumOptions {\n  exclusive:bool;\n  reverse:bool;\n}\n\ntable BroadcastToOptions {\n}\n\ntable Rfft2dOptions {\n}\n\ntable HashtableOptions {\n  // The identity of hash tables. This identity will be used across different\n  // subgraphs in the same interpreter instance.\n  table_id:int;\n  key_dtype:TensorType;\n  value_dtype:TensorType;\n}\n\ntable HashtableFindOptions {\n}\n\ntable HashtableImportOptions {\n}\n\ntable HashtableSizeOptions {\n}\n\ntable VarHandleOptions {\n  container:string;\n  shared_name:string;\n}\n\ntable ReadVariableOptions {\n}\n\ntable AssignVariableOptions {\n}\n\ntable RandomOptions {\n  seed: long;\n  seed2: long;\n}\n\ntable BucketizeOptions {\n  boundaries: [float];  // The bucket boundaries.\n}\n\ntable GeluOptions {\n  approximate: bool;\n}\n\ntable DynamicUpdateSliceOptions {\n}\n\ntable UnsortedSegmentProdOptions {\n}\n\ntable UnsortedSegmentMaxOptions {\n}\n\ntable UnsortedSegmentSumOptions {\n}\n\ntable ATan2Options {\n}\n\ntable UnsortedSegmentMinOptions{\n}\n\ntable SignOptions {\n}\n\ntable BitcastOptions {\n}\n\ntable BitwiseXorOptions {\n}\n\ntable RightShiftOptions {\n}\n\n// An OperatorCode can be an enum value (BuiltinOperator) if the operator is a\n// builtin, or a string if the operator is custom.\ntable OperatorCode {\n  // This field is for backward compatibility. This field will be used when\n  // the value of the extended builtin_code field has less than\n  // BulitinOperator_PLACEHOLDER_FOR_GREATER_OP_CODES.\n  deprecated_builtin_code:byte;\n  custom_code:string;\n\n  // The version of the operator. The version need to be bumped whenever new\n  // parameters are introduced into an op.\n  version:int = 1;\n\n  // This field is introduced for resolving op builtin code shortage problem\n  // (the original BuiltinOperator enum field was represented as a byte).\n  // This field will be used when the value of the extended builtin_code field\n  // has greater than BulitinOperator_PLACEHOLDER_FOR_GREATER_OP_CODES.\n  builtin_code:BuiltinOperator;\n}\n\nenum CustomOptionsFormat : byte {\n  FLEXBUFFERS = 0,\n}\n\n// An operator takes tensors as inputs and outputs. The type of operation being\n// performed is determined by an index into the list of valid OperatorCodes,\n// while the specifics of each operations is configured using builtin_options\n// or custom_options.\ntable Operator {\n  // Index into the operator_codes array. Using an integer here avoids\n  // complicate map lookups.\n  opcode_index:uint;\n\n  // Optional input are indicated by -1.\n  inputs:[int];\n  outputs:[int];\n\n  builtin_options:BuiltinOptions;\n  custom_options:[ubyte];\n  custom_options_format:CustomOptionsFormat;\n\n  // A list of booleans indicating the input tensors which are being mutated by\n  // this operator.(e.g. used by RNN and LSTM).\n  // For example, if the \"inputs\" array refers to 5 tensors and the second and\n  // fifth are mutable variables, then this list will contain\n  // [false, true, false, false, true].\n  //\n  // If the list is empty, no variable is mutated in this operator.\n  // The list either has the same length as `inputs`, or is empty.\n  mutating_variable_inputs:[bool];\n\n  // A list of indices to the subgraph's \"tensors\" that are internal to an Op.\n  // Internal tensors are those that do not flow in or out of the operation,\n  // but instead are part of internal computation. As such, the operation's\n  // implementation may manage its memory more efficiently. They are needed\n  // however (i.e. not just an implementation detail) since they are part of the\n  // computation, which may require relevant metadata such as quantization\n  // parameters.\n  intermediates:[int];\n}\n\n// The root type, defining a subgraph, which typically represents an entire\n// model.\ntable SubGraph {\n  // A list of all tensors used in this subgraph.\n  tensors:[Tensor];\n\n  // Indices of the tensors that are inputs into this subgraph. Note this is\n  // the list of non-static tensors that feed into the subgraph for inference.\n  inputs:[int];\n\n  // Indices of the tensors that are outputs out of this subgraph. Note this is\n  // the list of output tensors that are considered the product of the\n  // subgraph's inference.\n  outputs:[int];\n\n  // All operators, in execution order.\n  operators:[Operator];\n\n  // Name of this subgraph (used for debugging).\n  name:string;\n}\n\n// Table of raw data buffers (used for constant tensors). Referenced by tensors\n// by index. The generous alignment accommodates mmap-friendly data structures.\ntable Buffer {\n  data:[ubyte] (force_align: 16);\n}\n\ntable Metadata {\n  // A human readable string to uniquely identify a Metadata.\n  name:string;\n  // An index to the buffers table.\n  buffer:uint;\n}\n\n// Map from an alias name of tensor to tensor index in the graph.\n// This is used in Signature def.\ntable TensorMap {\n  // Represents the alias to use for this tensor.\n  name:string;\n\n  // The actual tensor index in the primary graph, that 'name' corresponds to.\n  tensor_index:uint;\n}\n\n// This corresponds to SignatureDef in Tensorflow SavedModel.\n// The SignatureDef will be part of the SavedModel provided for conversion.\ntable SignatureDef {\n  // Named inputs for this signature.\n  inputs:[TensorMap];\n\n  // Named outputs for this signature.\n  outputs:[TensorMap];\n\n  // Key value which was in the Tensorflow SavedModel SignatureDef map.\n  signature_key:string;\n\n  // Model tag, deprecated.\n  deprecated_tag:string (deprecated);\n\n  // Index of subgraphs that corresponds to the exported method.\n  subgraph_index:uint;\n}\n\ntable Model {\n  // Version of the schema.\n  version:uint;\n\n  // A list of all operator codes used in this model. This is\n  // kept in order because operators carry an index into this\n  // vector.\n  operator_codes:[OperatorCode];\n\n  // All the subgraphs of the model. The 0th is assumed to be the main\n  // model.\n  subgraphs:[SubGraph];\n\n  // A description of the model.\n  description:string;\n\n  // Buffers of the model.\n  // Note the 0th entry of this array must be an empty buffer (sentinel).\n  // This is a convention so that tensors without a buffer can provide 0 as\n  // their buffer.\n  buffers:[Buffer];\n\n  // Metadata about the model. Indirects into the existings buffers list.\n  // Deprecated, prefer to use metadata field.\n  metadata_buffer:[int];\n\n  // Metadata about the model.\n  metadata:[Metadata];\n\n  // Optional SignatureDefs for the model.\n  signature_defs:[SignatureDef];\n}\n\nroot_type Model;\n"
  },
  {
    "path": "tflite/src/lib.rs",
    "content": "#![allow(dead_code)]\n#[macro_use]\nextern crate derive_new;\n\nmod model;\nmod ops;\nmod registry;\npub mod rewriter;\nmod ser;\nmod tensors;\n\n#[allow(\n    unused_imports,\n    clippy::extra_unused_lifetimes,\n    clippy::missing_safety_doc,\n    clippy::derivable_impls,\n    clippy::needless_lifetimes,\n    clippy::too_long_first_doc_paragraph,\n    unknown_lints,\n    mismatched_lifetime_syntaxes\n)]\nmod tflite_generated;\npub use tflite_generated::tflite;\n\npub use model::Tflite;\n\npub mod prelude {\n    pub use tract_core::prelude::*;\n}\n\npub mod internal {\n    pub use crate::model::TfliteProtoModel;\n    pub use tract_core;\n    pub use tract_core::internal::*;\n}\n\npub fn tflite() -> Tflite {\n    Tflite::default()\n}\n"
  },
  {
    "path": "tflite/src/model.rs",
    "content": "use std::collections::hash_map::Entry;\nuse std::fmt::Debug;\n\nuse flatbuffers::FlatBufferBuilder;\nuse tract_core::internal::*;\n\nuse crate::registry::Registry;\nuse crate::tensors::{flat_tensor_to_tract_fact, flat_tensor_uses_per_axis_q};\nuse crate::tflite;\nuse crate::tflite::{Buffer, BufferArgs};\n\npub struct Tflite(Registry);\n\nimpl Debug for Tflite {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"tract-TfLite-framework\")\n    }\n}\n\nimpl Default for Tflite {\n    fn default() -> Self {\n        let mut registry = Registry::default();\n        crate::ops::register_all(&mut registry);\n        Tflite(registry)\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct TfliteProtoModel(Vec<u8>);\n\nimpl TfliteProtoModel {\n    fn new(buf: Vec<u8>) -> TractResult<TfliteProtoModel> {\n        let _ = tflite::root_as_model(&buf)?;\n        Ok(TfliteProtoModel(buf))\n    }\n\n    pub fn root(&self) -> tflite::Model<'_> {\n        unsafe { tflite::root_as_model_unchecked(&self.0) }\n    }\n}\n\nfn write_model<'fb>(\n    registry: &Registry,\n    model: &TypedModel,\n) -> TractResult<FlatBufferBuilder<'fb>> {\n    let mut model = model.clone();\n    crate::rewriter::rewrite_for_tflite(&mut model).context(\"Pre-dump rewrite\")?;\n    let mut builder = flatbuffers::FlatBufferBuilder::new();\n    let mut op_codes = vec![];\n    let sentinel = Buffer::create(&mut builder, &BufferArgs { data: None });\n    let mut buffers = vec![sentinel];\n    crate::ser::ModelBuilder {\n        registry,\n        builder: &mut builder,\n        op_codes: &mut op_codes,\n        buffers: &mut buffers,\n    }\n    .write_model(&model)?;\n    Ok(builder)\n}\n\nimpl Tflite {\n    pub fn write(&self, model: &TypedModel, mut w: impl std::io::Write) -> TractResult<()> {\n        let builder = write_model(&self.0, model)?;\n        w.write_all(builder.finished_data())?;\n        Ok(())\n    }\n}\n\nimpl Framework<TfliteProtoModel, TypedModel> for Tflite {\n    fn proto_model_for_read(\n        &self,\n        reader: &mut dyn std::io::Read,\n    ) -> tract_core::prelude::TractResult<TfliteProtoModel> {\n        let mut buf = vec![];\n        reader.read_to_end(&mut buf)?;\n        TfliteProtoModel::new(buf)\n    }\n\n    fn model_for_proto_model_with_model_template(\n        &self,\n        proto: &TfliteProtoModel,\n        mut target: TypedModel,\n    ) -> TractResult<TypedModel> {\n        let root = proto.root();\n        let main = &root.subgraphs().context(\"No subgraphs in Tflite model\")?.get(0);\n        let mut mapping = HashMap::new();\n        for input in main.inputs().context(\"No inputs in Tflite model\")? {\n            if !flat_tensor_uses_per_axis_q(main, input) {\n                let (fact, name) = flat_tensor_to_tract_fact(&root, main, input)?;\n                let it = target.add_source(name, fact)?;\n                mapping.insert(input, it);\n            }\n        }\n        for op in main.operators().context(\"No operators in Tflite model\")? {\n            for input in op.inputs().context(\"No input in Tflite  operator\")? {\n                if let Entry::Vacant(slot) = mapping.entry(input) {\n                    let (fact, name) = flat_tensor_to_tract_fact(&root, main, input)?;\n                    let value = fact.konst.with_context(|| format!(\"Error in TF file for operator {op:?}. No prior computation nor constant for input {input}\"))?;\n                    let konst = target.add_const(name, value)?;\n                    slot.insert(konst);\n                }\n            }\n            self.0.deser_op(&root, main, &op, &mut target, &mut mapping).with_context(|| {\n                format!(\"Translating proto-op from Tflite into tract op: {op:#?}\")\n            })?;\n        }\n        let outputs: TVec<_> = main\n            .outputs()\n            .context(\"No outputs in Tflite model\")?\n            .iter()\n            .map(|o| mapping[&o])\n            .collect();\n        target.select_output_outlets(&outputs)?;\n        Ok(target)\n    }\n}\n"
  },
  {
    "path": "tflite/src/ops/array.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::ops::array::{MultiBroadcastTo, Slice, TypedConcat};\nuse tract_core::ops::cast::wire_cast;\nuse tract_core::ops::Downsample;\nuse tract_core::prelude::tract_itertools::Itertools;\nuse tract_ndarray::ArrayView2;\n\nuse crate::registry::{DeserOp, Registry};\nuse crate::ser::{BuiltinOp, SubgraphBuilder};\nuse crate::tflite::{\n    ActivationFunctionType, BuiltinOperator, BuiltinOptions, ConcatenationOptions,\n    ConcatenationOptionsArgs, ExpandDimsOptions, ExpandDimsOptionsArgs, ReshapeOptions,\n    ReshapeOptionsArgs, SliceOptions, SliceOptionsArgs, SqueezeOptions, SqueezeOptionsArgs,\n    StridedSliceOptions, StridedSliceOptionsArgs, TransposeOptions, TransposeOptionsArgs,\n};\n\nuse super::wire_fused_activation;\n\npub fn register_all(reg: &mut Registry) {\n    reg.reg_to_tflite(ser_axisop);\n    reg.reg_to_tflite(ser_broadcast_to);\n    reg.reg_to_tflite(ser_concat);\n    reg.reg_to_tflite(ser_downsample);\n    reg.reg_to_tflite(ser_slice);\n\n    reg.reg_to_tract(BuiltinOperator::BROADCAST_TO, de_broadcast_to);\n    reg.reg_to_tract(BuiltinOperator::CONCATENATION, de_concat);\n    reg.reg_to_tract(BuiltinOperator::EXPAND_DIMS, de_expand_dims);\n    reg.reg_to_tract(BuiltinOperator::PAD, de_pad);\n    reg.reg_to_tract(BuiltinOperator::PADV2, de_padv2);\n    reg.reg_to_tract(BuiltinOperator::RESHAPE, de_reshape);\n    reg.reg_to_tract(BuiltinOperator::SHAPE, de_shape);\n    reg.reg_to_tract(BuiltinOperator::SLICE, de_slice);\n    reg.reg_to_tract(BuiltinOperator::SQUEEZE, de_squeeze);\n    reg.reg_to_tract(BuiltinOperator::STRIDED_SLICE, de_strided_slice);\n    reg.reg_to_tract(BuiltinOperator::TRANSPOSE, de_transpose);\n}\n\nfn de_broadcast_to(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (_input, shape) = args_2!(op.facts()?);\n    let shape = shape.konst.clone().context(\"Dynamic BROADCAST_TO is not supported\")?;\n    let shape = shape\n        .cast_to::<i32>()?\n        .try_as_plain()?\n        .as_slice::<i32>()?\n        .iter()\n        .map(|d| *d as usize)\n        .collect();\n    op.ctx.target.wire_node(op.prefix, MultiBroadcastTo { shape }, &op.inputs[0..1])\n}\n\nfn de_concat(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_concatenation_options);\n    let rank = op.facts()?[0].rank();\n    let axis =\n        if options.axis() < 0 { rank as i32 + options.axis() } else { options.axis() } as usize;\n    let dt = DatumType::super_type_for(op.facts()?.iter().map(|f| f.datum_type)).unwrap();\n    let inputs = wire_cast(op.prefix, op.ctx.target, op.inputs, dt)?;\n    let wires = op.ctx.target.wire_node(op.prefix, TypedConcat::new(axis), &inputs)?;\n    wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn de_expand_dims(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (input, axes) = args_2!(op.facts()?);\n    let axes = axes.konst.clone().context(\"Dynamic EXPAND_DIMS is not supported\")?;\n    let mut wire = tvec!(op.inputs[0]);\n    let prefix = op.prefix;\n    for (ix, &axis) in axes.try_as_plain()?.as_slice::<i32>()?.iter().sorted().rev().enumerate() {\n        let axis = if axis < 0 { axis + input.rank() as i32 } else { axis };\n        wire =\n            op.ctx.target.wire_node(format!(\"{prefix}.{ix}\"), AxisOp::Add(axis as usize), &wire)?;\n    }\n    Ok(wire)\n}\n\nfn de_pad(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (input, pads) = args_2!(op.facts()?);\n    let pads = pads.konst.as_ref().context(\"Dynamic PAD is not supported\")?;\n    let prefix = op.prefix;\n    let pads: ArrayView2<i32> = pads.to_plain_array_view::<i32>()?.into_dimensionality()?;\n    let pads: Vec<(usize, usize)> =\n        pads.rows().into_iter().map(|row| (row[0] as usize, row[1] as usize)).collect();\n    let mode =\n        tract_core::ops::array::PadMode::Constant(Tensor::zero_scalar_dt(input.datum_type)?.into());\n    op.ctx.target.wire_node(prefix, tract_core::ops::array::Pad { pads, mode }, &op.inputs[0..1])\n}\n\nfn de_padv2(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (_input, pads, value) = args_3!(op.facts()?);\n    let pads = pads.konst.as_ref().context(\"Dynamic PADV2 is not supported\")?;\n    let prefix = op.prefix;\n    let pads: ArrayView2<i32> = pads.to_plain_array_view::<i32>()?.into_dimensionality()?;\n    let pads: Vec<(usize, usize)> =\n        pads.rows().into_iter().map(|row| (row[0] as usize, row[1] as usize)).collect();\n    let mode = tract_core::ops::array::PadMode::Constant(value.konst.context(\"Constant expected\")?);\n    op.ctx.target.wire_node(prefix, tract_core::ops::array::Pad { pads, mode }, &op.inputs[0..1])\n}\n\nfn de_reshape(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let input_shape: TVec<TDim> = op.ctx.target.outlet_fact(op.inputs[0])?.shape.to_tvec();\n    let shape = if let Some(outlet) = op.inputs.get(1) {\n        op.ctx.target.outlet_fact(*outlet)?.konst.clone().unwrap()\n    } else {\n        let options = builtin!(op, builtin_options_as_reshape_options);\n        rctensor1(&options.new_shape().as_ref().unwrap().iter().collect::<Vec<i32>>())\n    };\n    let shape = shape.cast_to::<TDim>()?;\n    let shape = shape.try_as_plain()?.as_slice::<TDim>()?;\n    let mut wire = tvec!(op.inputs[0]);\n    let prefix = op.prefix;\n    for (ix, axis_op) in to_axis_ops_with_tf_rules(&input_shape, shape)?.into_iter().enumerate() {\n        wire = op.ctx.target.wire_node(format!(\"{prefix}.{ix}\"), axis_op, &wire)?;\n    }\n    Ok(wire)\n}\n\nfn de_shape(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let input = args_1!(op.facts()?);\n    let wire = op.ctx.target.add_const(op.prefix, tensor1(&input.shape))?;\n    Ok(tvec!(wire))\n}\n\nfn de_slice(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (input, begins, sizes) = args_3!(op.facts()?);\n    let mut wire = tvec!(op.inputs[0]);\n    if let (Some(begins), Some(sizes)) = (begins.konst, sizes.konst) {\n        for ix in 0..input.rank() {\n            let start = begins.try_as_plain()?.as_slice::<i32>()?[ix] as usize;\n            let size = sizes.try_as_plain()?.as_slice::<i32>()?[ix] as usize;\n            if start > 0 || size.to_dim() != input.shape[ix] {\n                wire = op.ctx.target.wire_node(\n                    format!(\"{}.{ix}\", op.prefix),\n                    Slice { axis: ix, start: start.to_dim(), end: (start + size).to_dim() },\n                    &wire,\n                )?\n            }\n        }\n    }\n    Ok(wire)\n}\n\nfn de_squeeze(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_squeeze_options);\n    let mut wire = tvec!(op.inputs[0]);\n    let prefix = op.prefix;\n    let rank = op.facts()?[0].rank();\n    for (ix, axis) in options.squeeze_dims().unwrap().iter().sorted().enumerate() {\n        let axis = if axis < 0 { rank as i32 + axis } else { axis } as usize;\n        wire = op.ctx.target.wire_node(format!(\"{prefix}.{ix}\"), AxisOp::Rm(axis), &wire)?;\n    }\n    Ok(wire)\n}\n\nfn de_strided_slice(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_strided_slice_options);\n    ensure!(options.new_axis_mask() == 0 && options.shrink_axis_mask() == 0);\n    let slice = tract_core::ops::array::StridedSlice {\n        begin_mask: options.begin_mask() as _,\n        end_mask: options.end_mask() as _,\n        shrink_axis_mask: options.shrink_axis_mask() as _,\n        optional_axes_input: None,\n        optional_steps_input: Some(3),\n    };\n    op.ctx.target.wire_node(op.prefix, slice, op.inputs)\n}\n\nfn de_transpose(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let perm = op\n        .ctx\n        .target\n        .outlet_fact(op.inputs[1])?\n        .konst\n        .as_ref()\n        .context(\"Dynamic TRANSPOSE in not supported by tract\")?;\n    let perm = perm.try_as_plain()?.as_slice::<i32>()?.iter().map(|x| *x as usize).collect_vec();\n    let mut wire = tvec!(op.inputs[0]);\n    let prefix = op.prefix;\n    for (ix, axis_op) in perm_to_ops(&perm).into_iter().enumerate() {\n        wire = op.ctx.target.wire_node(format!(\"{prefix}.{ix}\"), axis_op, &wire)?;\n    }\n    Ok(wire)\n}\n\nfn ser_axisop(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &AxisOp,\n) -> TractResult<()> {\n    let mut inputs = tvec!(builder.outlets_to_tensors[&node.inputs[0]]);\n    let output = builder.outlets_to_tensors[&node.id.into()];\n    match op {\n        AxisOp::Move(from, to) => {\n            let rank = model.node_input_facts(node.id)?[0].rank();\n            let mut permutation: Vec<i32> = (0..rank).map(|d| d as i32).collect();\n            permutation.remove(*from);\n            permutation.insert(*to, *from as _);\n            inputs.push(builder.write_fact(\n                format!(\"{}.perm\", node.name),\n                TypedFact::try_from(tensor1(&permutation))?,\n            )?);\n            let options = TransposeOptions::create(builder.fb(), &TransposeOptionsArgs {});\n            builder.write_op_with_options(\n                &inputs,\n                &[output],\n                BuiltinOp::new(39, 1, BuiltinOperator::TRANSPOSE, BuiltinOptions::TransposeOptions),\n                options.as_union_value(),\n            )\n        }\n        AxisOp::Add(a) => {\n            inputs.push(builder.write_fact(\n                format!(\"{}.axis\", node.name),\n                TypedFact::try_from(tensor0(*a as i32))?,\n            )?);\n            let options = ExpandDimsOptions::create(builder.fb(), &ExpandDimsOptionsArgs {});\n            builder.write_op_with_options(\n                &inputs,\n                &[output],\n                BuiltinOp::new(\n                    70,\n                    1,\n                    BuiltinOperator::EXPAND_DIMS,\n                    BuiltinOptions::ExpandDimsOptions,\n                ),\n                options.as_union_value(),\n            )\n        }\n        AxisOp::Rm(a) => {\n            let axes = builder.fb().create_vector(&[*a as i32]);\n            let options = SqueezeOptions::create(\n                builder.fb(),\n                &SqueezeOptionsArgs { squeeze_dims: Some(axes) },\n            );\n            builder.write_op_with_options(\n                &inputs,\n                &[output],\n                BuiltinOp::new(43, 1, BuiltinOperator::SQUEEZE, BuiltinOptions::SqueezeOptions),\n                options.as_union_value(),\n            )\n        }\n        AxisOp::Reshape(_, _, _) => {\n            let new_shape = node.outputs[0]\n                .fact\n                .shape\n                .iter()\n                .map(|x| x.to_i32())\n                .collect::<TractResult<Vec<i32>>>()?;\n            let new_shape = builder.fb().create_vector(&new_shape);\n            let options = ReshapeOptions::create(\n                builder.fb(),\n                &ReshapeOptionsArgs { new_shape: Some(new_shape) },\n            );\n            builder.write_op_with_options(\n                &inputs,\n                &[output],\n                BuiltinOp::new(22, 1, BuiltinOperator::RESHAPE, BuiltinOptions::ReshapeOptions),\n                options.as_union_value(),\n            )\n        }\n    }\n}\n\nfn ser_broadcast_to(\n    builder: &mut SubgraphBuilder,\n    _model: &TypedModel,\n    node: &TypedNode,\n    _op: &MultiBroadcastTo,\n) -> TractResult<()> {\n    let mut inputs = tvec!(builder.outlets_to_tensors[&node.inputs[0]]);\n    let output = builder.outlets_to_tensors[&node.id.into()];\n    let shape =\n        node.outputs[0].fact.shape.iter().map(|x| x.to_i32()).collect::<TractResult<Vec<i32>>>()?;\n    let shape = builder\n        .write_fact(format!(\"{}.shape\", node.name), TypedFact::try_from(tensor1(&shape))?)?;\n    inputs.push(shape);\n    builder.write_op(&inputs, &[output], 130, 3, BuiltinOperator::BROADCAST_TO)\n}\n\nfn ser_concat(\n    builder: &mut SubgraphBuilder,\n    _model: &TypedModel,\n    node: &TypedNode,\n    op: &TypedConcat,\n) -> TractResult<()> {\n    let options = ConcatenationOptions::create(\n        builder.fb(),\n        &ConcatenationOptionsArgs {\n            axis: op.axis as i32,\n            fused_activation_function: ActivationFunctionType::NONE,\n        },\n    );\n    let inputs = node.inputs.iter().map(|outlet| builder.outlets_to_tensors[outlet]).collect_vec();\n    let output = builder.outlets_to_tensors[&node.id.into()];\n    builder.write_op_with_options(\n        &inputs,\n        &[output],\n        BuiltinOp::new(2, 1, BuiltinOperator::CONCATENATION, BuiltinOptions::ConcatenationOptions),\n        options.as_union_value(),\n    )\n}\n\nfn ser_downsample(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &Downsample,\n) -> TractResult<()> {\n    let input_fact = model.outlet_fact(node.inputs[0])?;\n    let mut begins = tvec!(0i32; input_fact.rank());\n    let mut ends = input_fact\n        .shape\n        .as_concrete()\n        .context(\"Can not serialize symbolic dims to tflite\")?\n        .iter()\n        .map(|d| *d as i32)\n        .collect::<TVec<_>>();\n    let mut strides = tvec!(1; input_fact.rank());\n    strides[op.axis] = op.stride as i32;\n    if op.modulo > 0 {\n        begins[op.axis] = op.modulo as i32;\n    } else if op.stride < 0 {\n        begins[op.axis] = -1;\n        ends[op.axis] = 0;\n    }\n    let mut inputs = tvec!(builder.outlets_to_tensors[&node.inputs[0]]);\n    inputs.push(\n        builder\n            .write_fact(format!(\"{}.begins\", node.name), TypedFact::try_from(tensor1(&begins))?)?,\n    );\n    inputs.push(\n        builder.write_fact(format!(\"{}.ends\", node.name), TypedFact::try_from(tensor1(&ends))?)?,\n    );\n    inputs.push(\n        builder.write_fact(\n            format!(\"{}.strides\", node.name),\n            TypedFact::try_from(tensor1(&strides))?,\n        )?,\n    );\n    let output = builder.outlets_to_tensors[&OutletId::new(node.id, 0)];\n    let options = StridedSliceOptions::create(\n        builder.fb(),\n        &StridedSliceOptionsArgs {\n            begin_mask: 0,\n            end_mask: 1 << op.axis,\n            ellipsis_mask: 0,\n            new_axis_mask: 0,\n            shrink_axis_mask: 0,\n        },\n    );\n    builder.write_op_with_options(\n        &inputs,\n        &[output],\n        BuiltinOp::new(45, 1, BuiltinOperator::STRIDED_SLICE, BuiltinOptions::StridedSliceOptions),\n        options.as_union_value(),\n    )\n}\n\nfn ser_slice(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &Slice,\n) -> TractResult<()> {\n    let input_fact = model.outlet_fact(node.inputs[0])?;\n    let mut begins = tvec!(0i32; input_fact.rank());\n    let mut sizes = input_fact\n        .shape\n        .as_concrete()\n        .context(\"Can not serialize symbolic dims to tflite\")?\n        .iter()\n        .map(|d| *d as i32)\n        .collect::<TVec<_>>();\n    let begin = op.start.as_i64().context(\"Can not serialize symbolic dims to tflite\")? as i32;\n    let end = op.end.as_i64().context(\"Can not serialize symbolic dims to tflite\")? as i32;\n    begins[op.axis] = begin;\n    sizes[op.axis] = end - begin;\n    let begins = tensor1(&begins);\n    let sizes = tensor1(&sizes);\n    let mut inputs = tvec!(builder.outlets_to_tensors[&node.inputs[0]]);\n    inputs.push(builder.write_fact(format!(\"{}.begins\", node.name), TypedFact::try_from(begins)?)?);\n    inputs.push(builder.write_fact(format!(\"{}.sizes\", node.name), TypedFact::try_from(sizes)?)?);\n    let output = builder.outlets_to_tensors[&OutletId::new(node.id, 0)];\n    let options = SliceOptions::create(builder.fb(), &SliceOptionsArgs {});\n    builder.write_op_with_options(\n        &inputs,\n        &[output],\n        BuiltinOp::new(65, 1, BuiltinOperator::SLICE, BuiltinOptions::SliceOptions),\n        options.as_union_value(),\n    )\n}\n"
  },
  {
    "path": "tflite/src/ops/cnn.rs",
    "content": "use super::wire_fused_activation;\nuse crate::registry::{DeserOp, Registry};\nuse crate::ser::{BuiltinOp, SubgraphBuilder};\nuse crate::tflite::{\n    ActivationFunctionType, BuiltinOperator, BuiltinOptions, Conv2DOptions, Conv2DOptionsArgs,\n    DepthwiseConv2DOptions, DepthwiseConv2DOptionsArgs, PadOptions, PadOptionsArgs, Padding,\n    Pool2DOptions, Pool2DOptionsArgs,\n};\nuse flatbuffers::{FlatBufferBuilder, WIPOffset};\nuse tract_core::internal::*;\nuse tract_core::ops as core;\nuse tract_core::ops::array::{Pad, PadMode};\nuse tract_core::ops::cast::cast;\nuse tract_core::ops::cnn::{Conv, MaxPool, PaddingSpec, PoolSpec};\nuse tract_core::ops::cnn::{KernelFormat, SumPool};\nuse tract_core::ops::nn::DataFormat;\nuse tract_core::prelude::tract_itertools::Itertools;\n\npub fn register_all(reg: &mut Registry) {\n    reg.reg_to_tflite(ser_max_pool);\n    reg.reg_to_tflite(ser_sum_pool);\n    reg.reg_to_tract(BuiltinOperator::AVERAGE_POOL_2D, de_average_pool_2d);\n    reg.reg_to_tract(BuiltinOperator::MAX_POOL_2D, de_max_pool_2d);\n    reg.reg_to_tract(BuiltinOperator::CONV_2D, de_conv2d);\n    reg.reg_to_tflite(ser_conv);\n    reg.reg_to_tract(BuiltinOperator::DEPTHWISE_CONV_2D, de_dw_conv2d);\n    reg.reg_to_tflite(ser_pad);\n}\n\nfn pool_2d_options<'fb>(\n    fb: &mut FlatBufferBuilder<'fb>,\n    pool_spec: &PoolSpec,\n) -> TractResult<WIPOffset<Pool2DOptions<'fb>>> {\n    ensure!(pool_spec.data_format == DataFormat::NHWC);\n    ensure!(pool_spec.rank() == 2);\n    ensure!(\n        pool_spec.padding == PaddingSpec::Valid || pool_spec.padding == PaddingSpec::SameUpper,\n        \"unsupported padding {:?}\",\n        pool_spec.padding\n    );\n    let padding =\n        if pool_spec.padding == PaddingSpec::Valid { Padding::VALID } else { Padding::SAME };\n    let options = Pool2DOptions::create(\n        fb,\n        &Pool2DOptionsArgs {\n            padding,\n            stride_h: pool_spec.stride(0) as _,\n            stride_w: pool_spec.stride(1) as _,\n            filter_height: pool_spec.kernel_shape[0] as _,\n            filter_width: pool_spec.kernel_shape[1] as _,\n            fused_activation_function: ActivationFunctionType::NONE,\n        },\n    );\n    Ok(options)\n}\n\nfn ser_max_pool(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &MaxPool,\n) -> TractResult<()> {\n    let inputs = tvec!(builder.map_outlet(model, node.inputs[0])?);\n    let output = builder.outlets_to_tensors[&node.id.into()];\n    let options = pool_2d_options(builder.fb(), &op.pool_spec)?;\n    let op = BuiltinOp::new(17, 1, BuiltinOperator::MAX_POOL_2D, BuiltinOptions::Pool2DOptions);\n    builder.write_op_with_options(&inputs, &[output], op, options.as_union_value())\n}\n\nfn ser_sum_pool(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &SumPool,\n) -> TractResult<()> {\n    ensure!(op.normalize);\n    let inputs = tvec!(builder.map_outlet(model, node.inputs[0])?);\n    let output = builder.outlets_to_tensors[&node.id.into()];\n    let options = pool_2d_options(builder.fb(), &op.pool_spec)?;\n    let op = BuiltinOp::new(1, 1, BuiltinOperator::AVERAGE_POOL_2D, BuiltinOptions::Pool2DOptions);\n    builder.write_op_with_options(&inputs, &[output], op, options.as_union_value())\n}\n\nfn de_pool_2d_options(options: &Pool2DOptions, shape: &ShapeFact) -> TractResult<PoolSpec> {\n    let strides = tvec!(options.stride_h() as usize, options.stride_w() as usize);\n    let kernel_shape = tvec!(options.filter_height() as usize, options.filter_width() as usize);\n    let padding = match options.padding() {\n        Padding::SAME => PaddingSpec::SameUpper,\n        Padding::VALID => PaddingSpec::Valid,\n        _ => todo!(),\n    };\n    let ci =\n        DataFormat::NHWC.shape(&shape)?.c().to_usize().context(\"Except defined integer depth\")?;\n    Ok(core::cnn::PoolSpec {\n        data_format: DataFormat::NHWC,\n        kernel_shape,\n        padding,\n        strides: Some(strides),\n        dilations: None,\n        input_channels: ci,\n        output_channels: ci,\n    })\n}\n\nfn de_average_pool_2d(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_pool_2_doptions);\n    let pool_spec = de_pool_2d_options(&options, &op.output_facts[0].shape)?;\n    let pool = core::cnn::SumPool { pool_spec, normalize: true, count_include_pad: false };\n    let wires = op.ctx.target.wire_node(op.prefix, pool, &op.inputs[0..1])?;\n    wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn de_max_pool_2d(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_pool_2_doptions);\n    let pool_spec = de_pool_2d_options(&options, &op.output_facts[0].shape)?;\n    let pool = core::cnn::MaxPool { pool_spec, with_index_outputs: None };\n    let wires = op.ctx.target.wire_node(op.prefix, pool, &op.inputs[0..1])?;\n    wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn ser_conv(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    conv: &Conv,\n) -> TractResult<()> {\n    ensure!(conv.pool_spec.data_format == DataFormat::NHWC);\n    ensure!(model.node_input_facts(node.id)?[0].rank() == 4);\n    ensure!(conv.kernel_fmt == KernelFormat::OHWI);\n    ensure!(conv.group == 1 || conv.group.to_dim() == model.node_input_facts(node.id)?[0].shape[3]);\n    ensure!(\n        conv.pool_spec.padding == PaddingSpec::Valid\n            || conv.pool_spec.padding == PaddingSpec::SameUpper\n    );\n    let node_name = &node.name;\n    let mut inputs = tvec!(builder.map_outlet(model, node.inputs[0])?);\n    if conv.q_params.is_some() {\n        let facts = model.node_input_facts(node.id)?;\n        let iscale = facts[0].datum_type.zp_scale().1;\n        // 0 1 2 3  4  5  6  7  8\n        // x w b x0 xs k0 ks y0 ys\n        let k0_tract = facts[5].konst.as_ref().unwrap().cast_to_scalar::<i32>()? as i64;\n        let kscale = facts[6].konst.as_ref().unwrap().try_as_plain()?.as_slice::<f32>()?;\n        let per_channel = !kscale.iter().all_equal();\n        if per_channel {\n            let kernel = model\n                .outlet_fact(node.inputs[1])?\n                .konst\n                .as_ref()\n                .context(\"tract TODO: dynamic convolution and per-channel scales\")?;\n            let bias = model\n                .outlet_fact(node.inputs[2])?\n                .konst\n                .as_ref()\n                .context(\"tract TODO: dynamic convolution and per-channel scales\")?;\n            inputs.push(builder.write_fact_with_per_axis_q(\n                format!(\"{node_name}.weights\"),\n                TypedFact::try_from(kernel.clone())?,\n                &vec![k0_tract; conv.output_channels()],\n                kscale,\n                0,\n            )?);\n            let bscale = kscale.iter().map(|k| k * iscale).collect_vec();\n            let bias = bias.clone().into_tensor().cast_to::<i32>()?.into_owned().into_arc_tensor();\n            inputs.push(builder.write_fact_with_per_axis_q(\n                format!(\"{node_name}.bias\"),\n                TypedFact::try_from(bias.clone())?,\n                &vec![0i64; bias.len()],\n                &bscale,\n                0,\n            )?);\n        } else {\n            inputs.push(builder.map_outlet(model, node.inputs[1])?);\n            let bias = facts[2].konst.as_ref().context(\"FIXME: Dumper require constant bias\")?;\n            let bias_qdt = bias\n                .datum_type()\n                .quantize(QParams::ZpScale { zero_point: 0, scale: iscale * kscale[0] });\n            let bias = bias.cast_to_dt(bias_qdt)?.into_owned();\n            inputs\n                .push(builder.write_fact(format!(\"{node_name}.bias\"), TypedFact::try_from(bias)?)?);\n        }\n    } else {\n        inputs.push(builder.map_outlet(model, node.inputs[1])?);\n        ensure!(model.outlet_fact(node.inputs[2])?.rank() == 1);\n        inputs.push(builder.map_outlet(model, node.inputs[2])?);\n    }\n    let output = builder.outlets_to_tensors[&node.id.into()];\n\n    let padding =\n        if conv.pool_spec.padding == PaddingSpec::Valid { Padding::VALID } else { Padding::SAME };\n    if conv.group == 1 {\n        let options = Conv2DOptions::create(\n            builder.fb(),\n            &Conv2DOptionsArgs {\n                padding,\n                stride_h: conv.pool_spec.stride(0) as _,\n                stride_w: conv.pool_spec.stride(1) as _,\n                dilation_h_factor: conv.pool_spec.dilation(0) as _,\n                dilation_w_factor: conv.pool_spec.dilation(1) as _,\n                fused_activation_function: ActivationFunctionType::NONE,\n            },\n        );\n        builder.write_op_with_options(\n            &inputs,\n            &[output],\n            BuiltinOp::new(3, 2, BuiltinOperator::CONV_2D, BuiltinOptions::Conv2DOptions),\n            options.as_union_value(),\n        )\n    } else {\n        let depth_multiplier = (conv.pool_spec.output_channels / conv.group) as i32;\n        let options = DepthwiseConv2DOptions::create(\n            builder.fb(),\n            &DepthwiseConv2DOptionsArgs {\n                padding,\n                depth_multiplier,\n                stride_h: conv.pool_spec.stride(0) as _,\n                stride_w: conv.pool_spec.stride(1) as _,\n                dilation_h_factor: conv.pool_spec.dilation(0) as _,\n                dilation_w_factor: conv.pool_spec.dilation(1) as _,\n                fused_activation_function: ActivationFunctionType::NONE,\n            },\n        );\n        builder.write_op_with_options(\n            &inputs,\n            &[output],\n            BuiltinOp::new(\n                4,\n                2,\n                BuiltinOperator::DEPTHWISE_CONV_2D,\n                BuiltinOptions::DepthwiseConv2DOptions,\n            ),\n            options.as_union_value(),\n        )\n    }\n}\n\nfn de_conv2d(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (input, kernel, bias) = args_3!(op.facts()?);\n    let kernel_full_shape = kernel.shape.as_concrete().context(\"Expect concrete kernel shape\")?;\n    let kernel_spatial_shape = KernelFormat::OHWI.spatial_shape(kernel_full_shape);\n    let options = builtin!(op, builtin_options_as_conv_2_doptions);\n    let padding = match options.padding() {\n        Padding::SAME => PaddingSpec::SameUpper,\n        Padding::VALID => PaddingSpec::Valid,\n        _ => todo!(),\n    };\n    let strides = tvec!(options.stride_h() as usize, options.stride_w() as usize);\n    let dilations =\n        tvec!(options.dilation_h_factor() as usize, options.dilation_w_factor() as usize);\n    let input_channels = *KernelFormat::OHWI.i(kernel_full_shape);\n    let output_channels = *KernelFormat::OHWI.o(kernel_full_shape);\n    let pool_spec = core::cnn::PoolSpec {\n        data_format: tract_core::ops::nn::DataFormat::NHWC,\n        kernel_shape: kernel_spatial_shape.into(),\n        padding,\n        strides: Some(strides),\n        dilations: Some(dilations),\n        input_channels,\n        output_channels,\n    };\n    let mut inputs = tvec!(op.inputs[0], op.inputs[1], op.inputs[2]);\n    let q_params = super::linearops_quantization_suport(op, &input, &mut inputs)?;\n    let bias_dt = bias.datum_type.unquantized();\n    inputs[2] =\n        op.ctx.target.wire_node(format!(\"{}.cast_bias\", op.prefix), cast(bias_dt), &[inputs[2]])?\n            [0];\n    let conv = core::cnn::Conv { pool_spec, kernel_fmt: KernelFormat::OHWI, group: 1, q_params };\n    let wires = op.ctx.target.wire_node(op.prefix, conv, &inputs)?;\n    wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn de_dw_conv2d(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (input, kernel, bias) = args_3!(op.facts()?);\n    let kernel_full_shape: TVec<usize> = kernel.shape.as_concrete().unwrap().into();\n    let kernel_shape: TVec<usize> = KernelFormat::OHWI.spatial_shape(&kernel_full_shape).into();\n    let options = builtin!(op, builtin_options_as_depthwise_conv_2_doptions);\n    let padding = match options.padding() {\n        Padding::SAME => PaddingSpec::SameUpper,\n        Padding::VALID => PaddingSpec::Valid,\n        _ => todo!(),\n    };\n    let strides = tvec!(options.stride_h() as usize, options.stride_w() as usize);\n    let dilations =\n        tvec!(options.dilation_h_factor() as usize, options.dilation_w_factor() as usize);\n    let output_channels = *KernelFormat::OHWI.i(&kernel_full_shape);\n    let pool_spec = core::cnn::PoolSpec {\n        data_format: tract_core::ops::nn::DataFormat::NHWC,\n        kernel_shape,\n        padding,\n        strides: Some(strides),\n        dilations: Some(dilations),\n        input_channels: output_channels,\n        output_channels,\n    };\n    let mut inputs = tvec!(op.inputs[0], op.inputs[1], op.inputs[2]);\n    if bias.datum_type.is_quantized() {\n        inputs[2] = op.ctx.target.wire_node(\n            op.ctx.target.unique_name(format!(\"{}.bias\", &op.prefix)),\n            cast(bias.datum_type.unquantized()),\n            &[inputs[2]],\n        )?[0];\n    }\n    let q_params = super::linearops_quantization_suport(op, &input, &mut inputs)?;\n    let conv = core::cnn::Conv {\n        pool_spec,\n        kernel_fmt: KernelFormat::OHWI,\n        group: output_channels,\n        q_params,\n    };\n    let wires = op.ctx.target.wire_node(op.prefix, conv, &inputs)?;\n    wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn ser_pad(\n    builder: &mut SubgraphBuilder,\n    _model: &TypedModel,\n    node: &TypedNode,\n    pad: &Pad,\n) -> TractResult<()> {\n    let node_name = &node.name;\n    let mut inputs = tvec!(builder.outlets_to_tensors[&node.inputs[0]]);\n    let outputs = (0..node.outputs.len())\n        .map(|o| builder.outlets_to_tensors[&OutletId::new(node.id, o)])\n        .collect_vec();\n    let paddings = tract_ndarray::Array2::<i32>::from_shape_fn((pad.pads.len(), 2), |(d, side)| {\n        (if side == 0 { pad.pads[d].0 } else { pad.pads[d].1 }) as i32\n    });\n    inputs.push(builder.write_fact(\n        format!(\"{node_name}.paddings\"),\n        TypedFact::try_from(paddings.into_tensor())?,\n    )?);\n    let PadMode::Constant(pad_value) = &pad.mode else {\n        bail!(\"Only constant padding is supported by tflite\");\n    };\n    inputs.push(\n        builder.write_fact(\n            format!(\"{node_name}.pad_value\"),\n            TypedFact::try_from(pad_value.clone())?,\n        )?,\n    );\n    let options = PadOptions::create(builder.fb(), &PadOptionsArgs {});\n    builder.write_op_with_options(\n        &inputs,\n        &outputs,\n        BuiltinOp::new(60, 1, BuiltinOperator::PADV2, BuiltinOptions::PadV2Options),\n        options.as_union_value(),\n    )?;\n    Ok(())\n}\n"
  },
  {
    "path": "tflite/src/ops/element_wise.rs",
    "content": "use crate::registry::{DeserOp, Registry};\nuse crate::ser::{BuiltinOp, SubgraphBuilder};\nuse crate::tflite::{\n    AbsOptions, AbsOptionsArgs, BuiltinOperator, BuiltinOptions, CosOptions, CosOptionsArgs,\n    ExpOptions, ExpOptionsArgs, HardSwishOptions, HardSwishOptionsArgs, LeakyReluOptions,\n    LeakyReluOptionsArgs, LogicalNotOptions, LogicalNotOptionsArgs, SquareOptions,\n    SquareOptionsArgs,\n};\nuse tract_core::internal::*;\nuse tract_core::ops::element_wise::ElementWiseOp;\nuse tract_core::ops::logic::{not, Not};\nuse tract_core::ops::math::*;\nuse tract_core::ops::nn::{hard_swish, leaky_relu, sigmoid, HardSwish, LeakyRelu, Sigmoid};\n\npub fn register_all(reg: &mut Registry) {\n    reg.reg_to_tflite(ser);\n\n    reg.reg_to_tract(BuiltinOperator::ABS, |op| deser(op, abs()));\n    reg.reg_to_tract(BuiltinOperator::CEIL, |op| deser(op, ceil()));\n    reg.reg_to_tract(BuiltinOperator::COS, |op| deser(op, cos()));\n    reg.reg_to_tract(BuiltinOperator::EXP, |op| deser(op, exp()));\n    reg.reg_to_tract(BuiltinOperator::FLOOR, |op| deser(op, floor()));\n    reg.reg_to_tract(BuiltinOperator::HARD_SWISH, |op| deser(op, hard_swish()));\n    reg.reg_to_tract(BuiltinOperator::LEAKY_RELU, de_leaky_relu);\n    reg.reg_to_tract(BuiltinOperator::LOG, |op| deser(op, ln()));\n    reg.reg_to_tract(BuiltinOperator::LOGICAL_NOT, |op| deser(op, not()));\n    reg.reg_to_tract(BuiltinOperator::SIN, |op| deser(op, sin()));\n    reg.reg_to_tract(BuiltinOperator::LOGISTIC, |op| deser(op, sigmoid()));\n    reg.reg_to_tract(BuiltinOperator::SQRT, |op| deser(op, sqrt()));\n    reg.reg_to_tract(BuiltinOperator::SQUARE, |op| deser(op, square()));\n    reg.reg_to_tract(BuiltinOperator::RSQRT, |op| deser(op, rsqrt()));\n    reg.reg_to_tract(BuiltinOperator::TANH, |op| deser(op, tanh()));\n}\n\nfn deser(op: &mut DeserOp, ew: ElementWiseOp) -> TractResult<TVec<OutletId>> {\n    op.ctx.target.wire_node(op.prefix, ew, op.inputs)\n}\n\nfn de_leaky_relu(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_leaky_relu_options);\n    op.ctx.target.wire_node(op.prefix, leaky_relu(options.alpha()), op.inputs)\n}\n\nfn ser(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &ElementWiseOp,\n) -> TractResult<()> {\n    let input = builder.map_outlet(model, node.inputs[0])?;\n    let output = builder.map_outlet(model, node.id.into())?;\n    if (*op.0).is::<Abs>() {\n        let options = AbsOptions::create(builder.fb(), &AbsOptionsArgs {});\n        builder.write_op_with_options(\n            &[input],\n            &[output],\n            BuiltinOp::new(101, 1, BuiltinOperator::ABS, BuiltinOptions::AbsOptions),\n            options.as_union_value(),\n        )\n    } else if (*op.0).is::<Cos>() {\n        let options = CosOptions::create(builder.fb(), &CosOptionsArgs {});\n        builder.write_op_with_options(\n            &[input],\n            &[output],\n            BuiltinOp::new(108, 1, BuiltinOperator::COS, BuiltinOptions::CosOptions),\n            options.as_union_value(),\n        )\n    } else if (*op.0).is::<Exp>() {\n        let options = ExpOptions::create(builder.fb(), &ExpOptionsArgs {});\n        builder.write_op_with_options(\n            &[input],\n            &[output],\n            BuiltinOp::new(47, 1, BuiltinOperator::EXP, BuiltinOptions::ExpOptions),\n            options.as_union_value(),\n        )\n    } else if (*op.0).is::<HardSwish>() {\n        let options = HardSwishOptions::create(builder.fb(), &HardSwishOptionsArgs {});\n        builder.write_op_with_options(\n            &[input],\n            &[output],\n            BuiltinOp::new(117, 1, BuiltinOperator::HARD_SWISH, BuiltinOptions::HardSwishOptions),\n            options.as_union_value(),\n        )\n    } else if let Some(leaky) = (*op.0).downcast_ref::<LeakyRelu>() {\n        let options =\n            LeakyReluOptions::create(builder.fb(), &LeakyReluOptionsArgs { alpha: leaky.alpha });\n        builder.write_op_with_options(\n            &[input],\n            &[output],\n            BuiltinOp::new(98, 1, BuiltinOperator::LEAKY_RELU, BuiltinOptions::LeakyReluOptions),\n            options.as_union_value(),\n        )\n    } else if (*op.0).is::<Not>() {\n        let options = LogicalNotOptions::create(builder.fb(), &LogicalNotOptionsArgs {});\n        builder.write_op_with_options(\n            &[input],\n            &[output],\n            BuiltinOp::new(87, 1, BuiltinOperator::LOGICAL_NOT, BuiltinOptions::LogicalNotOptions),\n            options.as_union_value(),\n        )\n    } else if (*op.0).is::<Square>() {\n        let options = SquareOptions::create(builder.fb(), &SquareOptionsArgs {});\n        builder.write_op_with_options(\n            &[input],\n            &[output],\n            BuiltinOp::new(92, 1, BuiltinOperator::SQUARE, BuiltinOptions::SquareOptions),\n            options.as_union_value(),\n        )\n    } else if (*op.0).is::<Ceil>() {\n        builder.write_op(&[input], &[output], 104, 1, BuiltinOperator::CEIL)\n    } else if (*op.0).is::<Floor>() {\n        builder.write_op(&[input], &[output], 8, 1, BuiltinOperator::FLOOR)\n    } else if (*op.0).is::<Sin>() {\n        builder.write_op(&[input], &[output], 66, 1, BuiltinOperator::SIN)\n    } else if (*op.0).is::<Sqrt>() {\n        builder.write_op(&[input], &[output], 75, 1, BuiltinOperator::SQRT)\n    } else if (*op.0).is::<Rsqrt>() {\n        builder.write_op(&[input], &[output], 76, 1, BuiltinOperator::SQRT)\n    } else if (*op.0).is::<Sigmoid>() {\n        builder.write_op(&[input], &[output], 14, 1, BuiltinOperator::LOGISTIC)\n    } else if (*op.0).is::<Tanh>() {\n        builder.write_op(&[input], &[output], 28, 1, BuiltinOperator::TANH)\n    } else if (*op.0).is::<Ln>() {\n        builder.write_op(&[input], &[output], 73, 1, BuiltinOperator::LOG)\n    } else {\n        todo!(\"Serialization of ElementWise op {:?}\", op)\n    }\n}\n"
  },
  {
    "path": "tflite/src/ops/math.rs",
    "content": "use crate::ops::wire_fused_activation;\nuse crate::registry::{DeserOp, Registry};\nuse crate::ser::{BuiltinOp, SubgraphBuilder};\nuse crate::tflite::{\n    ActivationFunctionType, AddOptions, AddOptionsArgs, BuiltinOperator, BuiltinOptions,\n    DivOptions, DivOptionsArgs, MaximumMinimumOptions, MaximumMinimumOptionsArgs, MulOptions,\n    MulOptionsArgs, SubOptions, SubOptionsArgs,\n};\nuse tract_core::internal::*;\nuse tract_core::ops::binary::TypedBinOp;\nuse tract_core::ops::cast::wire_cast;\nuse tract_core::ops::change_axes::wire_rank_broadcast;\nuse tract_core::ops::logic::{self, comp_eq, comp_gt, comp_gte, comp_lt, comp_lte, comp_ne};\n\npub fn register_all(reg: &mut Registry) {\n    reg.reg_to_tflite(ser_bin);\n\n    reg.reg_to_tract(BuiltinOperator::ADD, deser_add);\n    reg.reg_to_tract(BuiltinOperator::SUB, deser_sub);\n    reg.reg_to_tract(BuiltinOperator::MUL, deser_mul);\n    reg.reg_to_tract(BuiltinOperator::DIV, deser_div);\n    reg.reg_to_tract(BuiltinOperator::MAXIMUM, |op| deser_bin(op, tract_core::ops::math::max()));\n    reg.reg_to_tract(BuiltinOperator::MINIMUM, |op| deser_bin(op, tract_core::ops::math::min()));\n\n    reg.reg_to_tract(BuiltinOperator::EQUAL, |op| deser_comp(op, comp_eq()));\n    reg.reg_to_tract(BuiltinOperator::NOT_EQUAL, |op| deser_comp(op, comp_ne()));\n    reg.reg_to_tract(BuiltinOperator::LESS, |op| deser_comp(op, comp_lt()));\n    reg.reg_to_tract(BuiltinOperator::LESS_EQUAL, |op| deser_comp(op, comp_lte()));\n    reg.reg_to_tract(BuiltinOperator::GREATER, |op| deser_comp(op, comp_gt()));\n    reg.reg_to_tract(BuiltinOperator::GREATER_EQUAL, |op| deser_comp(op, comp_gte()));\n    reg.reg_to_tract(BuiltinOperator::LOGICAL_OR, |op| deser_bin(op, logic::or()));\n    reg.reg_to_tract(BuiltinOperator::LOGICAL_AND, |op| deser_bin(op, logic::and()));\n}\n\nfn wire_cast_and_rank_broadcast(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let wire = wire_cast(\n        format!(\"{}.cast\", op.prefix),\n        op.ctx.target,\n        op.inputs,\n        DatumType::super_type_for(op.facts()?.iter().map(|f| f.datum_type))\n            .context(\"No super type\")?,\n    )?;\n    wire_rank_broadcast(op.prefix, op.ctx.target, &wire)\n}\n\nfn deser_bin(op: &mut DeserOp, mini: TypedBinOp) -> TractResult<TVec<OutletId>> {\n    let wires = wire_cast_and_rank_broadcast(op)?;\n    op.ctx.target.wire_node(op.prefix, mini, &wires)\n}\n\nfn deser_comp(\n    op: &mut DeserOp,\n    comp: Box<dyn tract_core::ops::binary::BinMiniOp>,\n) -> TractResult<TVec<OutletId>> {\n    let wires = wire_cast_and_rank_broadcast(op)?;\n    op.ctx.target.wire_node(op.prefix, TypedBinOp(comp, None), &wires)\n}\n\nfn deser_add(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_add_options);\n    let wires = wire_cast_and_rank_broadcast(op)?;\n    let wires = op.ctx.target.wire_node(op.prefix, tract_core::ops::math::add(), &wires)?;\n    wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn deser_sub(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_sub_options);\n    let wires = wire_cast_and_rank_broadcast(op)?;\n    let wires = op.ctx.target.wire_node(op.prefix, tract_core::ops::math::sub(), &wires)?;\n    wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn deser_mul(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_mul_options);\n    let wires = wire_cast_and_rank_broadcast(op)?;\n    let wires = op.ctx.target.wire_node(op.prefix, tract_core::ops::math::mul(), &wires)?;\n    wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn deser_div(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let options = builtin!(op, builtin_options_as_div_options);\n    let wires = wire_cast_and_rank_broadcast(op)?;\n    let wires = op.ctx.target.wire_node(op.prefix, tract_core::ops::math::div(), &wires)?;\n    wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn ser_bin(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &TypedBinOp,\n) -> TractResult<()> {\n    use tract_linalg::BinOp;\n    let inputs = builder.map_outlets(model, &node.inputs)?;\n    let outputs = builder.map_outlets(model, [OutletId::from(node.id)])?;\n\n    macro_rules! ser_logic {\n        ($tract:ty, $code:expr, $version:expr, $builtin:ident) => {\n            if op.0.is::<$tract>() {\n                return builder.write_op(\n                    &inputs,\n                    &outputs,\n                    $code,\n                    $version,\n                    BuiltinOperator::$builtin,\n                );\n            }\n        };\n    }\n\n    ser_logic!(logic::Or, 84, 1, LOGICAL_OR);\n    ser_logic!(logic::And, 86, 1, LOGICAL_AND);\n\n    if op.0.is::<tract_core::ops::math::Div>() {\n        let options = DivOptions::create(\n            builder.fb(),\n            &DivOptionsArgs { fused_activation_function: ActivationFunctionType::NONE },\n        );\n        return builder.write_op_with_options(\n            &inputs,\n            &outputs,\n            BuiltinOp::new(42, 1, BuiltinOperator::DIV, BuiltinOptions::DivOptions),\n            options.as_union_value(),\n        );\n    }\n\n    // Comparison ops\n    match op.0.name() {\n        \"LT\" => return builder.write_op(&inputs, &outputs, 58, 1, BuiltinOperator::LESS),\n        \"GT\" => return builder.write_op(&inputs, &outputs, 61, 1, BuiltinOperator::GREATER),\n        \"GTE\" => return builder.write_op(&inputs, &outputs, 62, 1, BuiltinOperator::GREATER_EQUAL),\n        \"LTE\" => return builder.write_op(&inputs, &outputs, 63, 1, BuiltinOperator::LESS_EQUAL),\n        \"Eq\" => return builder.write_op(&inputs, &outputs, 71, 1, BuiltinOperator::EQUAL),\n        \"NE\" => return builder.write_op(&inputs, &outputs, 72, 1, BuiltinOperator::NOT_EQUAL),\n        _ => {}\n    }\n\n    match op.0.as_linalg_binop().with_context(|| \"Missing implementation for binary\")? {\n        BinOp::Add => {\n            let options = AddOptions::create(\n                builder.fb(),\n                &AddOptionsArgs {\n                    fused_activation_function: ActivationFunctionType::NONE,\n                    pot_scale_int16: false,\n                },\n            );\n            builder.write_op_with_options(\n                &inputs,\n                &outputs,\n                BuiltinOp::new(0, 1, BuiltinOperator::ADD, BuiltinOptions::AddOptions),\n                options.as_union_value(),\n            )\n        }\n        BinOp::Sub => {\n            let options = SubOptions::create(\n                builder.fb(),\n                &SubOptionsArgs {\n                    fused_activation_function: ActivationFunctionType::NONE,\n                    pot_scale_int16: false,\n                },\n            );\n            builder.write_op_with_options(\n                &inputs,\n                &outputs,\n                BuiltinOp::new(41, 1, BuiltinOperator::SUB, BuiltinOptions::SubOptions),\n                options.as_union_value(),\n            )\n        }\n        BinOp::Mul => {\n            let options = MulOptions::create(\n                builder.fb(),\n                &MulOptionsArgs { fused_activation_function: ActivationFunctionType::NONE },\n            );\n            builder.write_op_with_options(\n                &inputs,\n                &outputs,\n                BuiltinOp::new(18, 1, BuiltinOperator::MUL, BuiltinOptions::MulOptions),\n                options.as_union_value(),\n            )\n        }\n        BinOp::Max => {\n            let options =\n                MaximumMinimumOptions::create(builder.fb(), &MaximumMinimumOptionsArgs {});\n            builder.write_op_with_options(\n                &inputs,\n                &outputs,\n                BuiltinOp::new(\n                    55,\n                    1,\n                    BuiltinOperator::MAXIMUM,\n                    BuiltinOptions::MaximumMinimumOptions,\n                ),\n                options.as_union_value(),\n            )\n        }\n        BinOp::Min => {\n            let options =\n                MaximumMinimumOptions::create(builder.fb(), &MaximumMinimumOptionsArgs {});\n            builder.write_op_with_options(\n                &inputs,\n                &outputs,\n                BuiltinOp::new(\n                    57,\n                    1,\n                    BuiltinOperator::MINIMUM,\n                    BuiltinOptions::MaximumMinimumOptions,\n                ),\n                options.as_union_value(),\n            )\n        }\n        it => todo!(\"Missing iplementation for binary {it:?} serialization\"),\n    }\n}\n"
  },
  {
    "path": "tflite/src/ops/mod.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::ops::change_axes::wire_with_rank_broadcast;\nuse tract_core::ops::logic::Iff;\nuse tract_core::prelude::tract_itertools::Itertools;\n\nuse crate::registry::{DeserContext, DeserOp, Registry};\nuse crate::ser::SubgraphBuilder;\nuse crate::tflite::{ActivationFunctionType, BuiltinOperator};\n\n// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/core/c/builtin_op_data.h\n\nmacro_rules! builtin {\n    ($op: expr, $id:ident) => {\n        $op.flat.$id().with_context(|| {\n            format!(\n                \"Wrong option type {:?} for operator {:?}\",\n                $op.flat.builtin_options_type(),\n                $op.flat\n            )\n        })?\n    };\n}\n\nmod array;\nmod cnn;\nmod element_wise;\nmod math;\nmod nn;\n\npub fn register_all(reg: &mut Registry) {\n    array::register_all(reg);\n    cnn::register_all(reg);\n    element_wise::register_all(reg);\n    math::register_all(reg);\n    nn::register_all(reg);\n    reg.reg_to_tflite(ser_iff);\n    reg.reg_to_tract(BuiltinOperator::SELECT, de_iff);\n    reg.reg_to_tract(BuiltinOperator::SELECT_V2, de_iff);\n}\n\nfn wire_fused_activation(\n    op: &mut DeserOp,\n    wires: &[OutletId],\n    activation: &ActivationFunctionType,\n) -> TractResult<TVec<OutletId>> {\n    let prefix = format!(\"{}.fused\", op.prefix);\n    let mut op = DeserOp {\n        ctx: DeserContext { model: op.ctx.model, subgraph: op.ctx.subgraph, target: op.ctx.target },\n        prefix: &prefix,\n        flat: op.flat,\n        inputs: wires,\n        output_facts: op.output_facts,\n    };\n    match *activation {\n        ActivationFunctionType::NONE => Ok(wires.into()),\n        ActivationFunctionType::RELU => nn::de_relu(&mut op),\n        ActivationFunctionType::RELU6 => nn::de_relu6(&mut op),\n        af => bail!(\"Unsupported fused activation type: {af:?}\"),\n    }\n}\n\nfn linearops_quantization_suport(\n    op: &mut DeserOp,\n    input: &TypedFact,\n    inputs: &mut TVec<OutletId>,\n) -> TractResult<Option<DatumType>> {\n    if op.output_facts[0].datum_type.is_quantized() {\n        let p = &op.prefix;\n        let iqp = input.datum_type.qparams().unwrap();\n        let oqp = op.output_facts[0].datum_type;\n        let k_input = op.flat.inputs().unwrap().get(1);\n        let k_tensor = op.ctx.subgraph.tensors().unwrap().get(k_input as usize);\n        let k_qp = k_tensor.quantization().unwrap();\n        let k_scale = if k_qp.scale().unwrap().len() > 1 {\n            rctensor1(&k_qp.scale().unwrap().iter().collect_vec())\n        } else {\n            rctensor0(k_qp.scale().unwrap().get(0))\n        };\n        let k_zp = k_qp.zero_point().unwrap().iter().map(|i| i as i32).collect_vec();\n        let k_zp = if k_zp.iter().all_equal() { tensor0(k_zp[0]) } else { tensor1(&k_zp) };\n        inputs.push(op.ctx.target.add_const(format!(\"{p}.i0\"), rctensor0(iqp.zp_scale().0))?);\n        inputs.push(op.ctx.target.add_const(format!(\"{p}.iscale\"), rctensor0(iqp.zp_scale().1))?);\n        inputs.push(op.ctx.target.add_const(format!(\"{p}.k0\"), k_zp.into_arc_tensor())?);\n        inputs.push(op.ctx.target.add_const(format!(\"{p}.kscale\"), k_scale)?);\n        inputs.push(op.ctx.target.add_const(format!(\"{p}.c0\"), rctensor0(oqp.zp_scale().0))?);\n        inputs.push(op.ctx.target.add_const(format!(\"{p}.cscale\"), rctensor0(oqp.zp_scale().1))?);\n        Ok(Some(oqp))\n    } else {\n        Ok(None)\n    }\n}\n\nfn ser_iff(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    _op: &Iff,\n) -> TractResult<()> {\n    let inputs = builder.map_outlets(model, &node.inputs)?;\n    let outputs = builder.map_outlets(model, [OutletId::new(node.id, 0)])?;\n    builder.write_op(&inputs, &outputs, 123, 1, BuiltinOperator::SELECT_V2)\n}\n\nfn de_iff(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    wire_with_rank_broadcast(op.prefix, op.ctx.target, Iff, op.inputs)\n}\n"
  },
  {
    "path": "tflite/src/ops/nn.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::ops as core;\nuse tract_core::ops::cast::wire_cast;\nuse tract_core::ops::cast::Cast;\nuse tract_core::ops::change_axes::wire_with_rank_broadcast;\nuse tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\nuse tract_core::ops::einsum::EinSum;\nuse tract_core::ops::math::add;\nuse tract_core::ops::nn::Softmax;\nuse tract_core::ops::nn::{Reduce, Reducer};\nuse tract_core::prelude::tract_itertools::Itertools;\n\nuse crate::registry::{DeserOp, Registry};\nuse crate::ser::BuiltinOp;\nuse crate::ser::SubgraphBuilder;\nuse crate::tflite::ArgMaxOptions;\nuse crate::tflite::ArgMaxOptionsArgs;\nuse crate::tflite::BatchMatMulOptions;\nuse crate::tflite::BatchMatMulOptionsArgs;\nuse crate::tflite::BuiltinOptions;\nuse crate::tflite::ExpandDimsOptions;\nuse crate::tflite::ExpandDimsOptionsArgs;\nuse crate::tflite::ReducerOptions;\nuse crate::tflite::ReducerOptionsArgs;\nuse crate::tflite::SoftmaxOptions;\nuse crate::tflite::SoftmaxOptionsArgs;\nuse crate::tflite::TensorType;\nuse crate::tflite::{BuiltinOperator, FullyConnectedOptionsWeightsFormat};\n\npub fn register_all(reg: &mut Registry) {\n    reg.reg_to_tflite(ser_matmul);\n    reg.reg_to_tract(BuiltinOperator::BATCH_MATMUL, de_batch_matmul);\n\n    reg.reg_to_tract(BuiltinOperator::FULLY_CONNECTED, de_fully_connected);\n    reg.reg_to_tract(BuiltinOperator::MEAN, de_reduce_mean);\n    reg.reg_to_tflite(ser_softmax);\n    reg.reg_to_tract(BuiltinOperator::SOFTMAX, de_softmax);\n\n    reg.reg_to_tract(BuiltinOperator::RELU, de_relu);\n    reg.reg_to_tract(BuiltinOperator::RELU6, de_relu6);\n\n    reg.reg_to_tflite(ser_reduce);\n    reg.reg_to_tract(BuiltinOperator::REDUCE_MAX, |op| de_reduce(op, Reducer::Max));\n    reg.reg_to_tract(BuiltinOperator::REDUCE_MIN, |op| de_reduce(op, Reducer::Min));\n    reg.reg_to_tract(BuiltinOperator::SUM, |op| de_reduce(op, Reducer::Sum));\n    reg.reg_to_tract(BuiltinOperator::REDUCE_PROD, |op| de_reduce(op, Reducer::Prod));\n}\n\nfn de_batch_matmul(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (a, b) = args_2!(op.facts()?);\n    let options = builtin!(op, builtin_options_as_batch_mat_mul_options);\n    ensure!(a.datum_type.is_float());\n    ensure!(!options.asymmetric_quantize_inputs());\n    ensure!(a.rank() == b.rank());\n    let rank = a.rank();\n    let mut axes = tvec!(\n        Axis::new('M', 2, 1).input(0, rank - 2 + options.adj_x() as usize).output(0, rank - 2),\n        Axis::new('N', 2, 1).input(1, rank - 1 - options.adj_y() as usize).output(0, rank - 1),\n        Axis::new('K', 2, 1)\n            .input(0, rank - 1 - options.adj_x() as usize)\n            .input(1, rank - 2 + options.adj_y() as usize)\n    );\n    for (ix, repr) in ('a'..).take(rank - 2).enumerate() {\n        axes.push(Axis::new(repr, 2, 1).input(0, ix).input(1, ix).output(0, ix));\n    }\n    let axes: AxesMapping = AxesMapping::new(2, 1, axes)?;\n    let einsum = EinSum { axes, q_params: None, operating_dt: a.datum_type };\n    op.ctx.target.wire_node(op.prefix, einsum, op.inputs)\n}\n\nfn de_fully_connected(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (input, weights, bias) = args_3!(op.facts()?);\n    let options = builtin!(op, builtin_options_as_fully_connected_options);\n    ensure!(options.weights_format() == FullyConnectedOptionsWeightsFormat::DEFAULT);\n    ensure!(!options.asymmetric_quantize_inputs());\n    ensure!(input.rank() == 2);\n    ensure!(weights.rank() == 2);\n    ensure!(bias.rank() == 1);\n    let mut inputs: TVec<OutletId> = op.inputs.into();\n    let wires = if input.datum_type.is_float() {\n        let axes = \"BI,OI->BO\".parse()?;\n        let einsum = EinSum { axes, q_params: None, operating_dt: input.datum_type };\n        let mut wires = op.ctx.target.wire_node(op.prefix, einsum, &inputs[0..2])?;\n        if inputs.len() == 3 {\n            let bias = op.ctx.target.wire_node(\n                format!(\"{}.bias_rank\", op.prefix),\n                AxisOp::Add(0),\n                &inputs[2..3],\n            )?;\n            wires = op.ctx.target.wire_node(\n                format!(\"{}.bias\", op.prefix),\n                add(),\n                &[wires[0], bias[0]],\n            )?;\n        }\n        wires\n    } else {\n        let qp = super::linearops_quantization_suport(op, &input, &mut inputs)?;\n        let axes = \"BI,OI,O,,,,,,->BO\".parse()?;\n        let einsum = EinSum { axes, q_params: qp, operating_dt: i32::datum_type() };\n        op.ctx.target.wire_node(op.prefix, einsum, &inputs)?\n    };\n    super::wire_fused_activation(op, &wires, &options.fused_activation_function())\n}\n\nfn de_reduce(op: &mut DeserOp, reducer: Reducer) -> TractResult<TVec<OutletId>> {\n    let (_, axes) = args_2!(op.facts()?);\n    let options = builtin!(op, builtin_options_as_reducer_options);\n    let axes: TVec<usize> = axes\n        .konst\n        .as_ref()\n        .unwrap()\n        .try_as_plain()?\n        .as_slice::<i32>()?\n        .iter()\n        .map(|d| *d as usize)\n        .sorted()\n        .collect();\n    let p = &op.prefix;\n    let mut wire = op.ctx.target.wire_node(\n        format!(\"{p}.reduce\"),\n        core::nn::Reduce::new(axes.clone(), reducer),\n        &[op.inputs[0]],\n    )?;\n    if !options.keep_dims() {\n        for axis in axes.iter().rev() {\n            wire =\n                op.ctx.target.wire_node(format!(\"{p}.rm_axis_{axis}\"), AxisOp::Rm(*axis), &wire)?;\n        }\n    }\n    Ok(wire)\n}\n\nfn de_reduce_mean(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let (input, axes) = args_2!(op.facts()?);\n    let axes: TVec<usize> = axes\n        .konst\n        .as_ref()\n        .unwrap()\n        .try_as_plain()?\n        .as_slice::<i32>()?\n        .iter()\n        .map(|d| *d as usize)\n        .sorted()\n        .collect();\n    let norm: TDim = axes.iter().map(|d| &input.shape[*d]).product();\n    let wire = de_reduce(op, Reducer::Sum)?;\n    let p = &op.prefix;\n    let norm = op.ctx.target.add_const(format!(\"{p}.card\"), tensor0(norm))?;\n    let norm = op.ctx.target.wire_node(\n        format!(\"{p}.as_float\"),\n        Cast { to: f32::datum_type() },\n        &[norm],\n    )?;\n    let norm = op.ctx.target.wire_node(format!(\"{p}.recip\"), core::math::recip(), &norm)?;\n    wire_with_rank_broadcast(op.prefix, op.ctx.target, core::quant::scale(), &[norm[0], wire[0]])\n}\n\nfn de_softmax(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let input = args_1!(op.facts()?);\n    let options = builtin!(op, builtin_options_as_softmax_options);\n    ensure!(options.beta() == 1.0);\n    let quant_output_dt = Some(input.datum_type).filter(|dt| !dt.is_float());\n    let softmax = Softmax { axes: tvec!(input.rank() - 1), quant_output_dt, ..Softmax::default() };\n    op.ctx.target.wire_node(op.prefix, softmax, op.inputs)\n}\n\npub fn de_relu(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let input = op.inputs[0];\n    let zero = op.ctx.target.add_const(format!(\"{}.zero\", op.prefix), tensor0(0f32))?;\n    let wires = wire_cast(\n        op.prefix,\n        op.ctx.target,\n        &[input, zero],\n        op.ctx.target.outlet_fact(input)?.datum_type,\n    )?;\n    wire_with_rank_broadcast(\n        format!(\"{}.relu\", op.prefix),\n        op.ctx.target,\n        core::math::max(),\n        &wires,\n    )\n}\n\npub fn de_relu6(op: &mut DeserOp) -> TractResult<TVec<OutletId>> {\n    let input = de_relu(op)?[0];\n    let six = op.ctx.target.add_const(format!(\"{}.six\", op.prefix), tensor0(6f32))?;\n    let wires = wire_cast(\n        op.prefix,\n        op.ctx.target,\n        &[input, six],\n        op.ctx.target.outlet_fact(input)?.datum_type,\n    )?;\n    wire_with_rank_broadcast(\n        format!(\"{}.relu6\", op.prefix),\n        op.ctx.target,\n        core::math::min(),\n        &wires,\n    )\n}\n\nfn ser_matmul(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &PrefixMatMul,\n) -> TractResult<()> {\n    let mut inputs =\n        [builder.map_outlet(model, node.inputs[0])?, builder.map_outlet(model, node.inputs[1])?];\n    let (adj_x, adj_y) = if op.transpose_c {\n        inputs.swap(0, 1);\n        (!op.transpose_b, !op.transpose_a)\n    } else {\n        (op.transpose_a, op.transpose_b)\n    };\n    let output = builder.map_outlets(model, [OutletId::from(node.id)])?;\n    let options = BatchMatMulOptions::create(\n        builder.fb(),\n        &BatchMatMulOptionsArgs { adj_x, adj_y, asymmetric_quantize_inputs: false },\n    );\n    builder.write_op_with_options(\n        &inputs,\n        &output,\n        BuiltinOp::new(126, 1, BuiltinOperator::BATCH_MATMUL, BuiltinOptions::BatchMatMulOptions),\n        options.as_union_value(),\n    )?;\n    Ok(())\n}\n\nfn ser_reduce(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &Reduce,\n) -> TractResult<()> {\n    let axes = builder.write_fact(\n        format!(\"{}.axes\", node.name),\n        TypedFact::try_from(tensor1(&op.axes.iter().map(|axis| *axis as i32).collect_vec()))?,\n    )?;\n    let inputs = [builder.map_outlet(model, node.inputs[0])?, axes];\n    let output = builder.map_outlets(model, [OutletId::from(node.id)])?;\n    if matches!(op.reducer, Reducer::ArgMin(_) | Reducer::ArgMax(_)) {\n        let mut intermediate_shape = model.outlet_fact(node.inputs[0])?.shape.to_vec();\n        for axis in op.axes.iter().sorted().rev() {\n            intermediate_shape.remove(*axis);\n        }\n        let intermediate_fact = i32::fact(intermediate_shape);\n        let intermediate_tensor =\n            builder.write_fact(format!(\"{}.removed_axes\", node.name), intermediate_fact)?;\n        let options = ArgMaxOptions::create(\n            builder.fb(),\n            &ArgMaxOptionsArgs { output_type: TensorType::INT64 },\n        );\n        builder.write_op_with_options(\n            &inputs,\n            &[intermediate_tensor],\n            BuiltinOp::new(56, 1, BuiltinOperator::ARG_MAX, BuiltinOptions::ArgMaxOptions),\n            options.as_union_value(),\n        )?;\n        let expand_dim_options = ExpandDimsOptions::create(builder.fb(), &ExpandDimsOptionsArgs {});\n        builder.write_op_with_options(\n            &[intermediate_tensor, axes],\n            &output,\n            BuiltinOp::new(70, 1, BuiltinOperator::EXPAND_DIMS, BuiltinOptions::ExpandDimsOptions),\n            expand_dim_options.as_union_value(),\n        )?;\n        Ok(())\n    } else {\n        let options = ReducerOptions::create(builder.fb(), &ReducerOptionsArgs { keep_dims: true });\n        ensure!(model.outlet_fact(node.inputs[0])?.datum_type != f64::datum_type());\n        match op.reducer {\n            Reducer::Max => builder.write_op_with_options(\n                &inputs,\n                &output,\n                BuiltinOp::new(82, 1, BuiltinOperator::REDUCE_MAX, BuiltinOptions::ReducerOptions),\n                options.as_union_value(),\n            ),\n            Reducer::Min => builder.write_op_with_options(\n                &inputs,\n                &output,\n                BuiltinOp::new(89, 1, BuiltinOperator::REDUCE_MIN, BuiltinOptions::ReducerOptions),\n                options.as_union_value(),\n            ),\n            Reducer::Prod => builder.write_op_with_options(\n                &inputs,\n                &output,\n                BuiltinOp::new(81, 1, BuiltinOperator::REDUCE_PROD, BuiltinOptions::ReducerOptions),\n                options.as_union_value(),\n            ),\n            Reducer::Sum => builder.write_op_with_options(\n                &inputs,\n                &output,\n                BuiltinOp::new(74, 1, BuiltinOperator::SUM, BuiltinOptions::ReducerOptions),\n                options.as_union_value(),\n            ),\n            Reducer::All | Reducer::Any => todo!(),\n            Reducer::ArgMin(_) | Reducer::ArgMax(_) | Reducer::MeanOfSquares => unreachable!(),\n        }\n    }\n}\n\nfn ser_softmax(\n    builder: &mut SubgraphBuilder,\n    model: &TypedModel,\n    node: &TypedNode,\n    op: &Softmax,\n) -> TractResult<()> {\n    let rank = model.outlet_fact(node.inputs[0])?.rank();\n    let input = builder.map_outlet(model, node.inputs[0])?;\n    let output = builder.map_outlet(model, node.id.into())?;\n    ensure!(&*op.axes == &[rank - 1]);\n    let options = SoftmaxOptions::create(builder.fb(), &SoftmaxOptionsArgs { beta: 1f32 });\n    builder.write_op_with_options(\n        &[input],\n        &[output],\n        BuiltinOp::new(25, 1, BuiltinOperator::SOFTMAX, BuiltinOptions::SoftmaxOptions),\n        options.as_union_value(),\n    )\n}\n"
  },
  {
    "path": "tflite/src/registry.rs",
    "content": "use std::any::TypeId;\n\nuse tract_core::internal::*;\n\nuse crate::ser::SubgraphBuilder;\nuse crate::tflite::{BuiltinOperator, Model, Operator, SubGraph};\n\npub type ToTract = Box<dyn Fn(&mut DeserOp) -> TractResult<TVec<OutletId>> + Send + Sync + 'static>;\npub type ToTflite<T> = fn(&mut SubgraphBuilder, &TypedModel, &TypedNode, &T) -> TractResult<()>;\npub type ToTfliteRaw = Box<\n    dyn Fn(&mut SubgraphBuilder, &TypedModel, &TypedNode) -> TractResult<()>\n        + Send\n        + Sync\n        + 'static,\n>;\n\n#[derive(Default)]\npub struct Registry {\n    pub to_tract: HashMap<i32, ToTract>,\n    pub to_tflite: HashMap<TypeId, ToTfliteRaw>,\n}\n\npub struct DeserContext<'ctx> {\n    pub model: &'ctx Model<'ctx>,\n    pub subgraph: &'ctx SubGraph<'ctx>,\n    pub target: &'ctx mut TypedModel,\n}\n\npub struct DeserOp<'op> {\n    pub ctx: DeserContext<'op>,\n    pub prefix: &'op str,\n    pub flat: &'op Operator<'op>,\n    pub inputs: &'op [OutletId],\n    pub output_facts: &'op [TypedFact],\n}\n\nimpl DeserOp<'_> {\n    pub fn facts(&self) -> TractResult<TVec<TypedFact>> {\n        self.inputs\n            .iter()\n            .map(|o| self.ctx.target.outlet_fact(*o).cloned())\n            .collect::<TractResult<TVec<_>>>()\n    }\n}\n\nimpl Registry {\n    pub fn reg_to_tflite<T: Op>(&mut self, tflite: ToTflite<T>) {\n        self.to_tflite.insert(\n            std::any::TypeId::of::<T>(),\n            Box::new(move |b, m, n| tflite(b, m, n, n.op_as::<T>().unwrap())),\n        );\n    }\n\n    pub fn reg_to_tract<T>(&mut self, op: BuiltinOperator, to: T)\n    where\n        T: Fn(&mut DeserOp) -> TractResult<TVec<OutletId>> + Send + Sync + 'static,\n    {\n        self.to_tract.insert(op.0, Box::new(to));\n    }\n\n    pub fn deser_op(\n        &self,\n        model: &Model,\n        subgraph: &SubGraph,\n        flat_op: &Operator,\n        target: &mut TypedModel,\n        mapping: &mut HashMap<i32, OutletId>,\n    ) -> TractResult<()> {\n        let inputs: TVec<OutletId> =\n            flat_op.inputs().unwrap().iter().map(|o| mapping[&o]).collect();\n        let tensors = subgraph.tensors().unwrap();\n        let prefix = tensors.get(flat_op.outputs().unwrap().get(0) as usize).name().unwrap();\n        let opcode_index = flat_op.opcode_index();\n        let operator_code = model.operator_codes().unwrap().get(opcode_index as _);\n        let opcode = if operator_code.deprecated_builtin_code() as i32\n            == BuiltinOperator::PLACEHOLDER_FOR_GREATER_OP_CODES.0\n        {\n            operator_code.builtin_code().0\n        } else {\n            operator_code.deprecated_builtin_code() as i32\n        };\n        let ctx = DeserContext { model, subgraph, target };\n        let results = if let Some(op) = self.to_tract.get(&opcode) {\n            let output_facts = flat_op\n                .outputs()\n                .unwrap()\n                .iter()\n                .map(|t| Ok(crate::tensors::flat_tensor_to_tract_fact(model, subgraph, t)?.0))\n                .collect::<TractResult<TVec<TypedFact>>>()?;\n            (op)(&mut DeserOp {\n                ctx,\n                prefix,\n                flat: flat_op,\n                inputs: &inputs,\n                output_facts: &output_facts,\n            })\n            .with_context(|| format!(\"Opcode is {operator_code:#?}\"))?\n        } else {\n            let facts =\n                inputs.iter().map(|o| target.outlet_fact(*o)).collect::<TractResult<TVec<_>>>()?;\n            bail!(\"Unsupported: {operator_code:#?}, inputs: {facts:#?}\")\n        };\n        for (flat, wire) in flat_op.outputs().unwrap().iter().zip(results.iter()) {\n            mapping.insert(flat, *wire);\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "tflite/src/rewriter.rs",
    "content": "use tract_core::internal::*;\nuse tract_core::ops::array::{Pad, PadMode};\nuse tract_core::ops::cnn::{rewrite_conv_with_n_axis, KernelFormat, MaxPool, PoolSpec, SumPool};\nuse tract_core::ops::cnn::{Conv, PaddingSpec};\nuse tract_core::ops::einsum::prefix_matmul::PrefixMatMul;\nuse tract_core::ops::element_wise::ElementWiseOp;\nuse tract_core::ops::math::Recip;\nuse tract_core::ops::nn::{expand_mean_of_squares, DataFormat, Softmax};\nuse tract_core::tract_data::itertools::Itertools;\n\npub fn rewrite_for_tflite(model: &mut TypedModel) -> TractResult<()> {\n    tract_core::ops::einsum::prefix_matmul::rewrite_einsum_to_prefix_matmul(model, true)?;\n    Rewriter::default()\n        .with_rule_for(\"trivial_axes_around_matmul\", trivial_axes_around_matmul)\n        .with_rule_for(\"kernel_in_ohwi\", kernel_in_ohwi)\n        .with_rule_for(\"bias_as_vector\", bias_as_vector)\n        //        .with_rule_for(\"per_layer_in_u8\", per_layer_in_u8)\n        .with_rule_for(\"make_1d_2d\", make_1d_2d)\n        .with_rule_for(\"rewrite_conv_with_n_axis\", rewrite_conv_with_n_axis)\n        .with_rule_for(\"conv-nchw-to-nhwc\", conv_nchw_to_nhwc)\n        .with_rule_for(\"maxpool-nchw-to-nhwc\", maxpool_nchw_to_nhwc)\n        .with_rule_for(\"sumpool-nchw-to-nhwc\", sumpool_nchw_to_nhwc)\n        .with_rule_for(\"padding\", padding)\n        .with_rule_for(\"manual_recip\", manual_recip)\n        .with_rule_for(\"softmax_on_last_axis\", softmax_on_last_axis)\n        .with_rule_for(\"expand-means-of-square\", expand_mean_of_squares)\n        .rewrite(&(), model)?;\n    tract_core::optim::Optimizer::prop_consts().optimize(model)\n}\n\nfn trivial_axes_around_matmul(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    conv: &PrefixMatMul,\n) -> TractResult<Option<TypedModelPatch>> {\n    let facts = model.node_input_facts(node.id)?;\n    let rank = facts[0].rank();\n    if rank <= 4 {\n        return Ok(None);\n    }\n    let trivial_axes = (0..rank - 2)\n        .filter(|axis| facts[0].shape[*axis].is_one() && facts[1].shape[*axis].is_one())\n        .collect_vec();\n\n    ensure!(!trivial_axes.is_empty(), \"Found Einsum with 4 > axes and no trivial axes\");\n    let mut patch = TypedModelPatch::default();\n    let mut wire = patch.taps(model, &node.inputs)?;\n    for axis in trivial_axes.iter().rev() {\n        wire[0] =\n            patch.wire_node(format!(\"{name}.rm_a_axis_{axis}\"), AxisOp::Rm(*axis), &[wire[0]])?[0];\n        wire[1] =\n            patch.wire_node(format!(\"{name}.rm_b_axis_{axis}\"), AxisOp::Rm(*axis), &[wire[1]])?[0];\n    }\n    let mut out = patch.wire_node(&node.name, *conv, &wire)?;\n    for axis in trivial_axes {\n        out = patch.wire_node(format!(\"{name}.add_axis_{axis}\"), AxisOp::Add(axis), &out)?;\n    }\n    patch.shunt_outside(model, node.id.into(), out[0])?;\n    Ok(Some(patch))\n}\n\nfn kernel_in_ohwi(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    conv: &Conv,\n) -> TractResult<Option<TypedModelPatch>> {\n    if conv.kernel_fmt == KernelFormat::OHWI {\n        return Ok(None);\n    }\n    if conv.group != 1 && conv.group != conv.output_channels() {\n        bail!(\"Arbitrary grouping is not supported in tflite\")\n    }\n    let mut patch = TypedModelPatch::default();\n    let mut wire = patch.taps(model, &node.inputs)?;\n    let prefix = format!(\"{name}.kernel_reorg\");\n    for (ix, op) in conv\n        .kernel_fmt\n        .kernel_as_group_o_i_h_w_ops(&patch.outlet_fact(wire[1])?.shape, conv.group)\n        .into_iter()\n        .enumerate()\n    {\n        wire[1] = patch.wire_node(format!(\"{prefix}.{ix}\"), op, &[wire[1]])?[0];\n    }\n    let geo_rank = conv.pool_spec.kernel_shape.len();\n    // group_o_i_h_w -> o_h_w_gi\n    let ci = conv.input_channels();\n    wire[1] =\n        patch.wire_node(format!(\"{prefix}.mv_g\"), AxisOp::Move(0, geo_rank + 2), &[wire[1]])?[0];\n    wire[1] =\n        patch.wire_node(format!(\"{prefix}.mv_i\"), AxisOp::Move(1, geo_rank + 2), &[wire[1]])?[0];\n    wire[1] = patch.wire_node(\n        format!(\"{prefix}.gi\"),\n        AxisOp::Reshape(\n            geo_rank + 1,\n            tvec!(conv.group.to_dim(), (ci / conv.group).to_dim()),\n            tvec!(ci.to_dim()),\n        ),\n        &[wire[1]],\n    )?[0];\n    let new = Conv { kernel_fmt: KernelFormat::OHWI, ..conv.clone() };\n    wire = patch.wire_node(name, new, &wire)?;\n    patch.shunt_outside(model, node.id.into(), wire[0])?;\n    Ok(Some(patch))\n}\n\nfn bias_as_vector(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    conv: &Conv,\n) -> TractResult<Option<TypedModelPatch>> {\n    let bias_fact = model.outlet_fact(node.inputs[2])?;\n    let co = conv.output_channels();\n    if *bias_fact.shape == [co.to_dim()] {\n        return Ok(None);\n    }\n    let mut patch = TypedModelPatch::default();\n    let mut wire = patch.taps(model, &node.inputs)?;\n    wire[2] = tract_core::ops::cnn::wire_reshape_bias_as_vector(\n        &mut patch,\n        name,\n        wire[2],\n        conv.output_channels(),\n    )?[0];\n    wire = patch.wire_node(name, conv.clone(), &wire)?;\n    patch.shunt_outside(model, node.id.into(), wire[0])?;\n    Ok(Some(patch))\n}\n\n/*\nfn per_layer_in_u8(\n_ctx: &(),\nmodel: &TypedModel,\nnode: &TypedNode,\nname: &str,\nconv: &Conv,\n) -> TractResult<Option<TypedModelPatch>> {\nlet input_fact = model.outlet_fact(node.inputs[0])?;\nlet idt = input_fact.datum_type;\nlet kernel_fact = model.outlet_fact(node.inputs[1])?;\nlet kdt = kernel_fact.datum_type;\nif idt.is_float() || model.outlet_fact(node.inputs[6])?.shape.len() > 1 {\nreturn Ok(None);\n}\nif idt.unquantized() == u8::datum_type() && kdt.unquantized() == u8::datum_type() {\nreturn Ok(None);\n}\nlet mut patch = TypedModelPatch::default();\nlet wire = patch.taps(model, &node.inputs)?;\nlet [mut i, mut k, b, mut i0, is, mut k0, ks, o0, os] = &*wire else {\nbail!(\"Unexpected number of inputs\")\n};\nwire_ensure_q8_flavour(&mut patch, name, &mut i, \"input\", &mut i0, DatumType::U8)?;\nwire_ensure_q8_flavour(&mut patch, name, &mut k, \"kernel\", &mut k0, DatumType::U8)?;\nlet output = patch.wire_node(name, conv.clone(), &[i, k, *b, i0, *is, k0, *ks, *o0, *os])?;\npatch.shunt_outside(model, node.id.into(), output[0])?;\nOk(Some(patch))\n}\n*/\n\nfn make_1d_2d(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    conv: &Conv,\n) -> TractResult<Option<TypedModelPatch>> {\n    if conv.pool_spec.rank() == 1 {\n        let mut new = conv.clone();\n        new.pool_spec = conv.pool_spec.change_geo_axes(&AxisOp::Add(1))?;\n        let mut patch = TypedModelPatch::default();\n        let mut wire = patch.taps(model, &node.inputs)?;\n        let pos_data = conv.pool_spec.data_format.h_axis() + 1;\n        wire[0] = patch.wire_node(format!(\"{name}.add_dim\"), AxisOp::Add(pos_data), &[wire[0]])?[0];\n        let pos_kernel = conv.kernel_fmt.h_axis() + 1;\n        wire[1] =\n            patch.wire_node(format!(\"{name}.add_dim_k\"), AxisOp::Add(pos_kernel), &[wire[1]])?[0];\n        wire = patch.wire_node(name, new, &wire)?;\n        wire = patch.wire_node(format!(\"{name}.rm_dim\"), AxisOp::Rm(pos_data), &wire)?;\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n\nfn conv_nchw_to_nhwc(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    conv: &Conv,\n) -> TractResult<Option<TypedModelPatch>> {\n    nchw_to_nhwc(_ctx, model, node, name, &conv.pool_spec, &|pool_spec| {\n        Box::new(Conv { pool_spec, ..conv.clone() })\n    })\n}\n\nfn maxpool_nchw_to_nhwc(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    op: &MaxPool,\n) -> TractResult<Option<TypedModelPatch>> {\n    nchw_to_nhwc(_ctx, model, node, name, &op.pool_spec, &|pool_spec| {\n        Box::new(MaxPool { pool_spec, ..op.clone() })\n    })\n}\n\nfn sumpool_nchw_to_nhwc(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    op: &SumPool,\n) -> TractResult<Option<TypedModelPatch>> {\n    nchw_to_nhwc(_ctx, model, node, name, &op.pool_spec, &|pool_spec| {\n        Box::new(SumPool { pool_spec, ..op.clone() })\n    })\n}\n\nfn nchw_to_nhwc(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    old: &PoolSpec,\n    op: &dyn Fn(PoolSpec) -> Box<dyn TypedOp>,\n) -> TractResult<Option<TypedModelPatch>> {\n    if !old.data_format.c_is_last() {\n        let mut new = old.clone();\n        new.data_format = match new.data_format {\n            DataFormat::NHWC | DataFormat::HWC => unreachable!(),\n            DataFormat::CHW => DataFormat::HWC,\n            DataFormat::NCHW => DataFormat::NHWC,\n        };\n        let mut patch = TypedModelPatch::default();\n        let fact = model.outlet_fact(node.inputs[0])?;\n        let shape = old.data_format.shape(&fact.shape)?;\n        let before = shape.c_axis();\n        let after = fact.rank() - 1;\n        let mut wire = patch.taps(model, &node.inputs)?;\n        wire[0] =\n            patch.wire_node(format!(\"{name}.nhwc\"), AxisOp::Move(before, after), &[wire[0]])?[0];\n        wire = patch.wire_node(name, op(new), &wire)?;\n        wire = patch.wire_node(format!(\"{name}.nchw\"), AxisOp::Move(after, before), &wire)?;\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n\nfn padding(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    conv: &Conv,\n) -> TractResult<Option<TypedModelPatch>> {\n    if conv.pool_spec.padding != PaddingSpec::Valid\n    // FIXME SameUpper should be usable, but I can't make sense of tflite output\n    // && conv.pool_spec.padding != PaddingSpec::SameUpper\n    {\n        let fact = model.outlet_fact(node.inputs[0])?;\n        let shape = conv.pool_spec.data_format.shape(&fact.shape)?;\n        let actual = conv.pool_spec.computed_padding(shape.hw_dims());\n        #[allow(clippy::single_element_loop)]\n        for pad in [PaddingSpec::Valid /*, PaddingSpec::SameUpper*/] {\n            let found = pad.compute(\n                shape.hw_dims(),\n                &conv.pool_spec.kernel_shape,\n                &conv.pool_spec.dilations(),\n                &conv.pool_spec.strides(),\n            );\n            if actual == found {\n                let mut new = conv.clone();\n                new.pool_spec.padding = pad;\n                return Ok(Some(TypedModelPatch::replace_single_op(\n                    model,\n                    node,\n                    &node.inputs,\n                    new,\n                )?));\n            }\n        }\n        let mut patch = TypedModelPatch::default();\n        let mut wires = patch.taps(model, &node.inputs)?;\n        let mut pads = vec![(0usize, 0usize); fact.rank()];\n        for (padding, axis) in actual.iter().zip(shape.hw_axes()) {\n            pads[axis] = (padding.pad_before.to_usize()?, padding.pad_after.to_usize()?);\n        }\n        wires[0] = patch.wire_node(\n            format!(\"{name}.padding\"),\n            Pad {\n                pads,\n                mode: PadMode::Constant(Tensor::zero_scalar_dt(fact.datum_type)?.into_arc_tensor()),\n            },\n            &wires[0..1],\n        )?[0];\n        let mut new = conv.clone();\n        new.pool_spec.padding = PaddingSpec::Valid;\n        wires = patch.wire_node(&node.name, new, &wires)?;\n        patch.shunt_outside(model, node.id.into(), wires[0])?;\n        return Ok(Some(patch));\n    }\n    Ok(None)\n}\n\nfn manual_recip(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    recip: &ElementWiseOp,\n) -> TractResult<Option<TypedModelPatch>> {\n    if recip.0.is::<Recip>() {\n        let mut patch = TypedModelPatch::default();\n        let input = patch.tap_model(model, node.inputs[0])?;\n        let dt = model.outlet_fact(node.inputs[0])?.datum_type;\n        let one = tensor0(1i32).cast_to_dt(dt)?.into_owned().into_tensor();\n        let one = patch.add_const(format!(\"{name}.one\"), one)?;\n        let wire = wire_with_rank_broadcast(\n            name,\n            &mut patch,\n            tract_core::ops::math::div(),\n            &[one, input],\n        )?;\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        Ok(Some(patch))\n    } else {\n        Ok(None)\n    }\n}\n\nfn softmax_on_last_axis(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    name: &str,\n    softmax: &Softmax,\n) -> TractResult<Option<TypedModelPatch>> {\n    let rank = model.outlet_fact(node.inputs[0])?.rank();\n    ensure!(softmax.axes.len() == 1);\n    if softmax.axes[0] != rank - 1 {\n        let mut patch = TypedModelPatch::default();\n        let mut wire = tvec!(patch.tap_model(model, node.inputs[0])?);\n        wire = patch.wire_node(\n            format!(\"{name}.move_axis\"),\n            AxisOp::Move(softmax.axes[0], rank - 1),\n            &wire,\n        )?;\n        wire = patch.wire_node(\n            format!(\"{name}.softmax\"),\n            Softmax { axes: tvec!(rank - 1), ..*softmax },\n            &wire,\n        )?;\n        wire = patch.wire_node(\n            format!(\"{name}.move_axis_back\"),\n            AxisOp::Move(rank - 1, softmax.axes[0]),\n            &wire,\n        )?;\n        patch.shunt_outside(model, node.id.into(), wire[0])?;\n        Ok(Some(patch))\n    } else {\n        Ok(None)\n    }\n}\n"
  },
  {
    "path": "tflite/src/ser.rs",
    "content": "use std::borrow::Borrow;\n\nuse tract_core::internal::*;\nuse tract_core::ops::konst::Const;\nuse tract_core::ops::source::TypedSource;\nuse tract_core::prelude::tract_itertools::Itertools;\n\nuse crate::registry::Registry;\nuse crate::tflite::{\n    Buffer, BufferArgs, BuiltinOperator, BuiltinOptions, CustomOptionsFormat, Model, ModelArgs,\n    Operator, OperatorArgs, OperatorCode, OperatorCodeArgs, QuantizationDetails,\n    QuantizationParameters, QuantizationParametersArgs, SubGraph, SubGraphArgs, Tensor, TensorArgs,\n};\nuse flatbuffers::{FlatBufferBuilder, UnionWIPOffset, WIPOffset};\n\n#[derive(Debug, PartialEq, Copy, Clone, new)]\npub struct BuiltinOp {\n    deprecated_builtin_code: i8,\n    version: i32,\n    code: BuiltinOperator,\n    options_type: BuiltinOptions,\n}\n\npub struct ModelBuilder<'f, 'b> {\n    pub registry: &'b Registry,\n    pub builder: &'b mut FlatBufferBuilder<'f>,\n    pub op_codes: &'b mut Vec<BuiltinOp>,\n    pub buffers: &'b mut Vec<WIPOffset<Buffer<'f>>>,\n}\n\nimpl ModelBuilder<'_, '_> {\n    pub fn write_model(&mut self, model: &TypedModel) -> TractResult<()> {\n        let mut subgraph = SubgraphBuilder::new(self);\n        subgraph.write_subgraph(model)?;\n        let subgraph = subgraph.finish(model)?;\n        let subgraphs = vec![subgraph];\n        let subgraphs = self.builder.create_vector(&subgraphs);\n        let buffers = self.builder.create_vector(self.buffers);\n        let operator_codes = self\n            .op_codes\n            .iter()\n            .map(|code| {\n                OperatorCode::create(\n                    self.builder,\n                    &OperatorCodeArgs {\n                        deprecated_builtin_code: code.deprecated_builtin_code,\n                        custom_code: None,\n                        version: code.version,\n                        builtin_code: code.code,\n                    },\n                )\n            })\n            .collect_vec();\n        let operator_codes = self.builder.create_vector(&operator_codes);\n        let model = Model::create(\n            self.builder,\n            &ModelArgs {\n                version: 3,\n                operator_codes: Some(operator_codes),\n                subgraphs: Some(subgraphs),\n                description: None,\n                buffers: Some(buffers),\n                metadata_buffer: None,\n                metadata: None,\n                signature_defs: None,\n            },\n        );\n        self.builder.finish(model, Some(\"TFL3\"));\n        Ok(())\n    }\n\n    fn operator_code_index(&mut self, builtin: BuiltinOp) -> u32 {\n        if let Some(found) = self.op_codes.iter().position(|op| op == &builtin) {\n            found as u32\n        } else {\n            self.op_codes.push(builtin);\n            self.op_codes.len() as u32 - 1\n        }\n    }\n}\n\npub struct SubgraphBuilder<'f, 'b, 'mb> {\n    pub model: &'mb mut ModelBuilder<'f, 'b>,\n    pub tensors: Vec<WIPOffset<Tensor<'f>>>,\n    pub const_cache: Vec<(Arc<tract_core::prelude::Tensor>, i32)>,\n    pub operators: Vec<WIPOffset<Operator<'f>>>,\n    pub outlets_to_tensors: HashMap<OutletId, i32>,\n}\n\nimpl<'f, 'b, 'mb> SubgraphBuilder<'f, 'b, 'mb> {\n    fn new(model: &'mb mut ModelBuilder<'f, 'b>) -> SubgraphBuilder<'f, 'b, 'mb> {\n        SubgraphBuilder {\n            model,\n            tensors: vec![],\n            operators: vec![],\n            outlets_to_tensors: HashMap::new(),\n            const_cache: vec![],\n        }\n    }\n\n    pub fn fb<'short>(&'short mut self) -> &'short mut FlatBufferBuilder<'f>\n    where\n        'f: 'short,\n    {\n        self.model.builder\n    }\n\n    pub fn map_outlet(&mut self, model: &TypedModel, outlet: OutletId) -> TractResult<i32> {\n        if let Some(t) = self.outlets_to_tensors.get(&outlet) {\n            Ok(*t)\n        } else {\n            let fact = model.outlet_fact(outlet)?;\n            self.write_fact(format!(\"{}.{}\", model.node(outlet.node).name, outlet.slot), fact)\n        }\n    }\n\n    pub fn map_outlets(\n        &mut self,\n        model: &TypedModel,\n        outlets: impl IntoIterator<Item = impl Borrow<OutletId>>,\n    ) -> TractResult<TVec<i32>> {\n        outlets.into_iter().map(|o| self.map_outlet(model, *o.borrow())).collect()\n    }\n\n    pub fn write_fact(\n        &mut self,\n        name: impl AsRef<str>,\n        fact: impl Into<TypedFact>,\n    ) -> TractResult<i32> {\n        let fact = fact.into();\n        if fact.datum_type.unquantized() == i8::datum_type()\n            || fact.datum_type.unquantized() == u8::datum_type()\n            || fact.datum_type.qparams().is_some()\n        {\n            let qp =\n                fact.datum_type.qparams().unwrap_or(QParams::ZpScale { zero_point: 0, scale: 1. });\n            self.write_fact_with_per_axis_q(\n                name,\n                fact,\n                &[qp.zp_scale().0 as i64],\n                &[qp.zp_scale().1],\n                0,\n            )\n        } else {\n            self.write_fact_with_quantization(name, fact, None)\n        }\n    }\n\n    pub fn write_fact_faking_per_axis_q(\n        &mut self,\n        name: impl AsRef<str>,\n        fact: impl Into<TypedFact>,\n        axis: usize,\n    ) -> TractResult<i32> {\n        let fact = fact.into();\n        if let Some(qp) = fact.datum_type.qparams() {\n            let dim = fact.shape[axis].to_usize()?;\n            self.write_fact_with_per_axis_q(\n                name,\n                fact,\n                &vec![qp.zp_scale().0 as i64; dim],\n                &vec![qp.zp_scale().1; dim],\n                axis,\n            )\n        } else {\n            self.write_fact_with_quantization(name, fact, None)\n        }\n    }\n\n    pub fn write_fact_with_per_axis_q(\n        &mut self,\n        name: impl AsRef<str>,\n        fact: impl Into<TypedFact>,\n        zp: &[i64],\n        scale: &[f32],\n        axis: usize,\n    ) -> TractResult<i32> {\n        let fact = fact.into();\n        let zero_point = self.fb().create_vector(zp);\n        let scale = self.fb().create_vector(scale);\n        let qp = QuantizationParameters::create(\n            self.fb(),\n            &QuantizationParametersArgs {\n                min: None,\n                max: None,\n                zero_point: Some(zero_point),\n                scale: Some(scale),\n                details: None,\n                details_type: QuantizationDetails::NONE,\n                quantized_dimension: axis as i32,\n            },\n        );\n        self.write_fact_with_quantization(name, fact, Some(qp))\n    }\n\n    pub fn write_fact_with_quantization(\n        &mut self,\n        name: impl AsRef<str>,\n        fact: impl Into<TypedFact>,\n        quantization: Option<WIPOffset<QuantizationParameters>>,\n    ) -> TractResult<i32> {\n        let fact = fact.into();\n        let buffer = if let Some(k) = &fact.konst {\n            if let Some(pair) = self.const_cache.iter().find(|(t, _id)| t == k) {\n                return Ok(pair.1);\n            }\n            self.const_cache.push((k.clone(), self.tensors.len() as i32));\n\n            let data = self.fb().create_vector(k.as_bytes());\n            let buffer = Buffer::create(self.fb(), &BufferArgs { data: Some(data) });\n            self.model.buffers.push(buffer);\n            self.model.buffers.len() as u32 - 1\n        } else {\n            0\n        };\n        let shape = fact.shape.as_concrete().unwrap().iter().map(|d| *d as i32).collect_vec();\n        let shape = self.fb().create_vector(&shape);\n        let name = self.fb().create_string(name.as_ref());\n        let tensor = Tensor::create(\n            self.fb(),\n            &TensorArgs {\n                name: Some(name),\n                buffer,\n                is_variable: false,\n                quantization,\n                shape: Some(shape),\n                type_: fact.datum_type.try_into()?,\n                sparsity: None,\n                shape_signature: None,\n                has_rank: true,\n                variant_tensors: None,\n            },\n        );\n        self.tensors.push(tensor);\n        Ok(self.tensors.len() as i32 - 1)\n    }\n\n    fn write_subgraph(&mut self, model: &TypedModel) -> TractResult<()> {\n        for &node_id in &model.eval_order()? {\n            let node = &model.nodes[node_id];\n            // will serialize constants at the demand of operators only\n            if node.op_is::<Const>() {\n                continue;\n            }\n            // create fb tensors for all outputs\n            for (slot, output) in node.outputs.iter().enumerate() {\n                let name = model\n                    .outlet_labels\n                    .get(&OutletId::new(node.id, slot))\n                    .map(Cow::Borrowed)\n                    .unwrap_or_else(|| Cow::Owned(format!(\"outlet_{node_id}_{slot}\")));\n                let tensor = self.write_fact(name.as_str(), &output.fact)?;\n                let outlet = OutletId::new(node.id, slot);\n                self.outlets_to_tensors.insert(outlet, tensor);\n            }\n            // Source inputs are not reified\n            if node.op_is::<TypedSource>() {\n                continue;\n            } else if let Some(to_tflite) =\n                self.model.registry.to_tflite.get(&(*(node.op)).type_id())\n            {\n                to_tflite(self, model, node).with_context(|| format!(\"Translating {node}\"))?;\n            } else {\n                bail!(\"No serializer for op: {}\", node)\n            };\n        }\n        Ok(())\n    }\n\n    pub fn write_op(\n        &mut self,\n        inputs: &[i32],\n        outputs: &[i32],\n        deprecated_builtin_code: i16,\n        version: i32,\n        code: BuiltinOperator,\n    ) -> TractResult<()> {\n        let op = BuiltinOp {\n            deprecated_builtin_code: if deprecated_builtin_code > 127 {\n                127i8\n            } else {\n                deprecated_builtin_code as i8\n            },\n            version,\n            code,\n            options_type: BuiltinOptions::NONE,\n        };\n        let opcode_index = self.model.operator_code_index(op);\n        let inputs = self.fb().create_vector(inputs);\n        let outputs = self.fb().create_vector(outputs);\n        let operator = Operator::create(\n            self.fb(),\n            &OperatorArgs {\n                inputs: Some(inputs),\n                outputs: Some(outputs),\n                opcode_index,\n                builtin_options: None,\n                builtin_options_type: op.options_type,\n                custom_options: None,\n                custom_options_format: CustomOptionsFormat::FLEXBUFFERS,\n                mutating_variable_inputs: None,\n                intermediates: None,\n            },\n        );\n        self.operators.push(operator);\n        Ok(())\n    }\n\n    pub fn write_op_with_options(\n        &mut self,\n        inputs: &[i32],\n        outputs: &[i32],\n        op: BuiltinOp,\n        builtin_options: WIPOffset<UnionWIPOffset>,\n    ) -> TractResult<()> {\n        let opcode_index = self.model.operator_code_index(op);\n        let inputs = self.fb().create_vector(inputs);\n        let outputs = self.fb().create_vector(outputs);\n        let operator = Operator::create(\n            self.fb(),\n            &OperatorArgs {\n                inputs: Some(inputs),\n                outputs: Some(outputs),\n                opcode_index,\n                builtin_options: Some(builtin_options),\n                builtin_options_type: op.options_type,\n                custom_options: None,\n                custom_options_format: CustomOptionsFormat::FLEXBUFFERS,\n                mutating_variable_inputs: None,\n                intermediates: None,\n            },\n        );\n        self.operators.push(operator);\n        Ok(())\n    }\n\n    fn finish(self, model: &TypedModel) -> TractResult<WIPOffset<SubGraph<'f>>> {\n        let Self {\n            model: ModelBuilder { builder, .. },\n            tensors,\n            operators,\n            outlets_to_tensors,\n            ..\n        } = self;\n        let inputs = model.inputs.iter().map(|i| outlets_to_tensors[i]).collect_vec();\n        let outputs = model.outputs.iter().map(|i| outlets_to_tensors[i]).collect_vec();\n        let inputs = builder.create_vector(&inputs);\n        let outputs = builder.create_vector(&outputs);\n        let tensors = builder.create_vector(&tensors);\n        let operators = builder.create_vector(&operators);\n\n        Ok(SubGraph::create(\n            builder,\n            &SubGraphArgs {\n                name: None,\n                tensors: Some(tensors),\n                inputs: Some(inputs),\n                outputs: Some(outputs),\n                operators: Some(operators),\n            },\n        ))\n    }\n}\n"
  },
  {
    "path": "tflite/src/tensors.rs",
    "content": "use crate::tflite::{Model, SubGraph};\nuse crate::tflite_generated::tflite::{TensorType, TensorType as BufferTensorType};\n#[cfg(feature = \"complex\")]\nuse num_complex::Complex;\nuse tract_core::internal::*;\nuse tract_core::prelude::tract_itertools::Itertools;\n\nimpl TryFrom<BufferTensorType> for DatumType {\n    type Error = TractError;\n    fn try_from(t: BufferTensorType) -> TractResult<DatumType> {\n        Ok(match t {\n            BufferTensorType::FLOAT32 => DatumType::F32,\n            BufferTensorType::FLOAT16 => DatumType::F16,\n            BufferTensorType::INT32 => DatumType::I32,\n            BufferTensorType::UINT8 => DatumType::U8,\n            BufferTensorType::INT64 => DatumType::I64,\n            BufferTensorType::STRING => DatumType::String,\n            BufferTensorType::BOOL => DatumType::Bool,\n            BufferTensorType::INT16 => DatumType::I16,\n            #[cfg(feature = \"complex\")]\n            BufferTensorType::COMPLEX64 => DatumType::ComplexF64, // TODO check this\n            TensorType::INT8 => DatumType::I8,\n            TensorType::FLOAT64 => DatumType::F64,\n            //TensorType::COMPLEX128 => DatumType::ComplexF64,\n            TensorType::UINT64 => DatumType::U64,\n            TensorType::RESOURCE => DatumType::Blob, //TODO: check this\n            TensorType::VARIANT => DatumType::Blob,  //TODO: check this\n            TensorType::UINT32 => DatumType::U32,\n            TensorType::UINT16 => DatumType::U16,\n            //TensorType::COMPLEX128 => DatumType::ComplexF64,\n            //TensorType::UINT4 => {DatumType::U4},\n            _ => bail!(\"Unknown DatumType {:?}\", t),\n        })\n    }\n}\n\nimpl TryFrom<DatumType> for BufferTensorType {\n    type Error = TractError;\n    fn try_from(value: DatumType) -> Result<Self, Self::Error> {\n        Ok(match value.unquantized() {\n            DatumType::Bool => BufferTensorType::BOOL,\n            DatumType::U8 => BufferTensorType::UINT8,\n            DatumType::U16 => BufferTensorType::UINT16,\n            DatumType::U32 => BufferTensorType::UINT32,\n            DatumType::U64 => BufferTensorType::UINT64,\n            DatumType::I8 => BufferTensorType::INT8,\n            DatumType::I16 => BufferTensorType::INT16,\n            DatumType::I32 => BufferTensorType::INT32,\n            DatumType::I64 => BufferTensorType::INT64,\n            DatumType::F16 => BufferTensorType::FLOAT16,\n            DatumType::F32 => BufferTensorType::FLOAT32,\n            DatumType::F64 => BufferTensorType::FLOAT64,\n            _ => bail!(\"Unsupported DatumType {:?}\", value),\n        })\n    }\n}\n\n#[allow(dead_code)]\nfn create_tensor(dt: DatumType, shape: &[usize], data: &[u8]) -> TractResult<Tensor> {\n    unsafe {\n        match dt {\n            DatumType::U8 => Tensor::from_raw::<u8>(shape, data),\n            DatumType::U16 => Tensor::from_raw::<u16>(shape, data),\n            DatumType::U32 => Tensor::from_raw::<u32>(shape, data),\n            DatumType::U64 => Tensor::from_raw::<u64>(shape, data),\n            DatumType::I8 => Tensor::from_raw::<i8>(shape, data),\n            DatumType::I16 => Tensor::from_raw::<i16>(shape, data),\n            DatumType::I32 => Tensor::from_raw::<i32>(shape, data),\n            DatumType::I64 => Tensor::from_raw::<i64>(shape, data),\n            DatumType::F16 => Tensor::from_raw::<f16>(shape, data),\n            DatumType::F32 => Tensor::from_raw::<f32>(shape, data),\n            DatumType::F64 => Tensor::from_raw::<f64>(shape, data),\n            #[cfg(feature = \"complex\")]\n            DatumType::ComplexF64 => Tensor::from_raw::<Complex<f64>>(&shape, data), // TODO check this\n            DatumType::Bool => Ok(Tensor::from_raw::<u8>(shape, data)?\n                .into_plain_array::<u8>()?\n                .mapv(|x| x != 0)\n                .into()),\n            _ => unimplemented!(\"FIXME, raw tensor loading\"),\n        }\n    }\n}\n\npub fn flat_tensor_uses_per_axis_q<'m>(graph: &'m SubGraph<'m>, id: i32) -> bool {\n    let flat = graph.tensors().unwrap().get(id as _);\n    if let Some(qp) = flat.quantization() {\n        if let (Some(scale), Some(zp)) = (qp.scale(), qp.zero_point()) {\n            return !scale.iter().all_equal() || !zp.iter().all_equal();\n        }\n    }\n    false\n}\n\npub fn per_axis_q_params<'m>(\n    graph: &'m SubGraph<'m>,\n    id: i32,\n) -> TractResult<(Vec<i32>, Vec<f32>)> {\n    let flat = graph.tensors().unwrap().get(id as _);\n    let Some(qp) = flat.quantization() else { bail!(\"Unquantized value\") };\n    let (Some(scale), Some(zp)) = (qp.scale(), qp.zero_point()) else { bail!(\"No ZP/scale found\") };\n    Ok((zp.iter().map(|i| i as i32).collect_vec(), scale.iter().collect_vec()))\n}\n\npub fn flat_tensor_to_tract_fact<'m>(\n    &model: &'m Model<'m>,\n    graph: &'m SubGraph<'m>,\n    id: i32,\n) -> TractResult<(TypedFact, &'m str)> {\n    let flat = graph.tensors().unwrap().get(id as _);\n    let mut dt: DatumType = flat.type_().try_into()?;\n    if let Some(qp) = flat.quantization() {\n        if let (Some(scale), Some(zp)) = (qp.scale(), qp.zero_point()) {\n            dt = dt.quantize(QParams::ZpScale { zero_point: zp.get(0) as _, scale: scale.get(0) })\n        }\n    }\n    let mut fact = dt.fact(flat.shape().unwrap().iter().map(|d| d as usize).collect_vec());\n    let buffer_ix = flat.buffer() as usize;\n    if buffer_ix != 0 {\n        let buffer = model.buffers().unwrap().get(flat.buffer() as usize);\n        if let Some(data) = buffer.data() {\n            let mut data = create_tensor(\n                fact.datum_type.unquantized(),\n                fact.shape.as_concrete().unwrap(),\n                data.bytes(),\n            )?;\n            unsafe {\n                data.set_datum_type(dt);\n            };\n            fact = TypedFact::try_from(data)?;\n        }\n    }\n    Ok((fact, flat.name().unwrap()))\n}\n\n#[derive(Clone, Debug)]\npub struct PerAxisQ {\n    axis: usize,\n    zp: Vec<i32>,\n    scale: Vec<f32>,\n}\n"
  },
  {
    "path": "tflite/src/tflite_generated.rs",
    "content": "// automatically generated by the FlatBuffers compiler, do not modify\n\n// @generated\n\nuse core::cmp::Ordering;\nuse core::mem;\n\nextern crate flatbuffers;\nuse self::flatbuffers::{EndianScalar, Follow};\n\n#[allow(unused_imports, dead_code)]\npub mod tflite {\n\n    use core::cmp::Ordering;\n    use core::mem;\n\n    extern crate flatbuffers;\n    use self::flatbuffers::{EndianScalar, Follow};\n\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_TENSOR_TYPE: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_TENSOR_TYPE: i8 = 17;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_TENSOR_TYPE: [TensorType; 18] = [\n        TensorType::FLOAT32,\n        TensorType::FLOAT16,\n        TensorType::INT32,\n        TensorType::UINT8,\n        TensorType::INT64,\n        TensorType::STRING,\n        TensorType::BOOL,\n        TensorType::INT16,\n        TensorType::COMPLEX64,\n        TensorType::INT8,\n        TensorType::FLOAT64,\n        TensorType::COMPLEX128,\n        TensorType::UINT64,\n        TensorType::RESOURCE,\n        TensorType::VARIANT,\n        TensorType::UINT32,\n        TensorType::UINT16,\n        TensorType::INT4,\n    ];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct TensorType(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl TensorType {\n        pub const FLOAT32: Self = Self(0);\n        pub const FLOAT16: Self = Self(1);\n        pub const INT32: Self = Self(2);\n        pub const UINT8: Self = Self(3);\n        pub const INT64: Self = Self(4);\n        pub const STRING: Self = Self(5);\n        pub const BOOL: Self = Self(6);\n        pub const INT16: Self = Self(7);\n        pub const COMPLEX64: Self = Self(8);\n        pub const INT8: Self = Self(9);\n        pub const FLOAT64: Self = Self(10);\n        pub const COMPLEX128: Self = Self(11);\n        pub const UINT64: Self = Self(12);\n        pub const RESOURCE: Self = Self(13);\n        pub const VARIANT: Self = Self(14);\n        pub const UINT32: Self = Self(15);\n        pub const UINT16: Self = Self(16);\n        pub const INT4: Self = Self(17);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 17;\n        pub const ENUM_VALUES: &'static [Self] = &[\n            Self::FLOAT32,\n            Self::FLOAT16,\n            Self::INT32,\n            Self::UINT8,\n            Self::INT64,\n            Self::STRING,\n            Self::BOOL,\n            Self::INT16,\n            Self::COMPLEX64,\n            Self::INT8,\n            Self::FLOAT64,\n            Self::COMPLEX128,\n            Self::UINT64,\n            Self::RESOURCE,\n            Self::VARIANT,\n            Self::UINT32,\n            Self::UINT16,\n            Self::INT4,\n        ];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::FLOAT32 => Some(\"FLOAT32\"),\n                Self::FLOAT16 => Some(\"FLOAT16\"),\n                Self::INT32 => Some(\"INT32\"),\n                Self::UINT8 => Some(\"UINT8\"),\n                Self::INT64 => Some(\"INT64\"),\n                Self::STRING => Some(\"STRING\"),\n                Self::BOOL => Some(\"BOOL\"),\n                Self::INT16 => Some(\"INT16\"),\n                Self::COMPLEX64 => Some(\"COMPLEX64\"),\n                Self::INT8 => Some(\"INT8\"),\n                Self::FLOAT64 => Some(\"FLOAT64\"),\n                Self::COMPLEX128 => Some(\"COMPLEX128\"),\n                Self::UINT64 => Some(\"UINT64\"),\n                Self::RESOURCE => Some(\"RESOURCE\"),\n                Self::VARIANT => Some(\"VARIANT\"),\n                Self::UINT32 => Some(\"UINT32\"),\n                Self::UINT16 => Some(\"UINT16\"),\n                Self::INT4 => Some(\"INT4\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for TensorType {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for TensorType {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for TensorType {\n        type Output = TensorType;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for TensorType {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for TensorType {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for TensorType {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_QUANTIZATION_DETAILS: u8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_QUANTIZATION_DETAILS: u8 = 1;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_QUANTIZATION_DETAILS: [QuantizationDetails; 2] =\n        [QuantizationDetails::NONE, QuantizationDetails::CustomQuantization];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct QuantizationDetails(pub u8);\n    #[allow(non_upper_case_globals)]\n    impl QuantizationDetails {\n        pub const NONE: Self = Self(0);\n        pub const CustomQuantization: Self = Self(1);\n\n        pub const ENUM_MIN: u8 = 0;\n        pub const ENUM_MAX: u8 = 1;\n        pub const ENUM_VALUES: &'static [Self] = &[Self::NONE, Self::CustomQuantization];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::NONE => Some(\"NONE\"),\n                Self::CustomQuantization => Some(\"CustomQuantization\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for QuantizationDetails {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for QuantizationDetails {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<u8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for QuantizationDetails {\n        type Output = QuantizationDetails;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<u8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for QuantizationDetails {\n        type Scalar = u8;\n        #[inline]\n        fn to_little_endian(self) -> u8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: u8) -> Self {\n            let b = u8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for QuantizationDetails {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            u8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for QuantizationDetails {}\n    pub struct QuantizationDetailsUnionTableOffset {}\n\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_DIMENSION_TYPE: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_DIMENSION_TYPE: i8 = 1;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_DIMENSION_TYPE: [DimensionType; 2] =\n        [DimensionType::DENSE, DimensionType::SPARSE_CSR];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct DimensionType(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl DimensionType {\n        pub const DENSE: Self = Self(0);\n        pub const SPARSE_CSR: Self = Self(1);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 1;\n        pub const ENUM_VALUES: &'static [Self] = &[Self::DENSE, Self::SPARSE_CSR];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::DENSE => Some(\"DENSE\"),\n                Self::SPARSE_CSR => Some(\"SPARSE_CSR\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for DimensionType {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for DimensionType {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for DimensionType {\n        type Output = DimensionType;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for DimensionType {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for DimensionType {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for DimensionType {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_SPARSE_INDEX_VECTOR: u8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_SPARSE_INDEX_VECTOR: u8 = 3;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_SPARSE_INDEX_VECTOR: [SparseIndexVector; 4] = [\n        SparseIndexVector::NONE,\n        SparseIndexVector::Int32Vector,\n        SparseIndexVector::Uint16Vector,\n        SparseIndexVector::Uint8Vector,\n    ];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct SparseIndexVector(pub u8);\n    #[allow(non_upper_case_globals)]\n    impl SparseIndexVector {\n        pub const NONE: Self = Self(0);\n        pub const Int32Vector: Self = Self(1);\n        pub const Uint16Vector: Self = Self(2);\n        pub const Uint8Vector: Self = Self(3);\n\n        pub const ENUM_MIN: u8 = 0;\n        pub const ENUM_MAX: u8 = 3;\n        pub const ENUM_VALUES: &'static [Self] =\n            &[Self::NONE, Self::Int32Vector, Self::Uint16Vector, Self::Uint8Vector];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::NONE => Some(\"NONE\"),\n                Self::Int32Vector => Some(\"Int32Vector\"),\n                Self::Uint16Vector => Some(\"Uint16Vector\"),\n                Self::Uint8Vector => Some(\"Uint8Vector\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for SparseIndexVector {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for SparseIndexVector {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<u8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for SparseIndexVector {\n        type Output = SparseIndexVector;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<u8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for SparseIndexVector {\n        type Scalar = u8;\n        #[inline]\n        fn to_little_endian(self) -> u8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: u8) -> Self {\n            let b = u8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for SparseIndexVector {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            u8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for SparseIndexVector {}\n    pub struct SparseIndexVectorUnionTableOffset {}\n\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_BUILTIN_OPERATOR: i32 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_BUILTIN_OPERATOR: i32 = 161;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_BUILTIN_OPERATOR: [BuiltinOperator; 162] = [\n        BuiltinOperator::ADD,\n        BuiltinOperator::AVERAGE_POOL_2D,\n        BuiltinOperator::CONCATENATION,\n        BuiltinOperator::CONV_2D,\n        BuiltinOperator::DEPTHWISE_CONV_2D,\n        BuiltinOperator::DEPTH_TO_SPACE,\n        BuiltinOperator::DEQUANTIZE,\n        BuiltinOperator::EMBEDDING_LOOKUP,\n        BuiltinOperator::FLOOR,\n        BuiltinOperator::FULLY_CONNECTED,\n        BuiltinOperator::HASHTABLE_LOOKUP,\n        BuiltinOperator::L2_NORMALIZATION,\n        BuiltinOperator::L2_POOL_2D,\n        BuiltinOperator::LOCAL_RESPONSE_NORMALIZATION,\n        BuiltinOperator::LOGISTIC,\n        BuiltinOperator::LSH_PROJECTION,\n        BuiltinOperator::LSTM,\n        BuiltinOperator::MAX_POOL_2D,\n        BuiltinOperator::MUL,\n        BuiltinOperator::RELU,\n        BuiltinOperator::RELU_N1_TO_1,\n        BuiltinOperator::RELU6,\n        BuiltinOperator::RESHAPE,\n        BuiltinOperator::RESIZE_BILINEAR,\n        BuiltinOperator::RNN,\n        BuiltinOperator::SOFTMAX,\n        BuiltinOperator::SPACE_TO_DEPTH,\n        BuiltinOperator::SVDF,\n        BuiltinOperator::TANH,\n        BuiltinOperator::CONCAT_EMBEDDINGS,\n        BuiltinOperator::SKIP_GRAM,\n        BuiltinOperator::CALL,\n        BuiltinOperator::CUSTOM,\n        BuiltinOperator::EMBEDDING_LOOKUP_SPARSE,\n        BuiltinOperator::PAD,\n        BuiltinOperator::UNIDIRECTIONAL_SEQUENCE_RNN,\n        BuiltinOperator::GATHER,\n        BuiltinOperator::BATCH_TO_SPACE_ND,\n        BuiltinOperator::SPACE_TO_BATCH_ND,\n        BuiltinOperator::TRANSPOSE,\n        BuiltinOperator::MEAN,\n        BuiltinOperator::SUB,\n        BuiltinOperator::DIV,\n        BuiltinOperator::SQUEEZE,\n        BuiltinOperator::UNIDIRECTIONAL_SEQUENCE_LSTM,\n        BuiltinOperator::STRIDED_SLICE,\n        BuiltinOperator::BIDIRECTIONAL_SEQUENCE_RNN,\n        BuiltinOperator::EXP,\n        BuiltinOperator::TOPK_V2,\n        BuiltinOperator::SPLIT,\n        BuiltinOperator::LOG_SOFTMAX,\n        BuiltinOperator::DELEGATE,\n        BuiltinOperator::BIDIRECTIONAL_SEQUENCE_LSTM,\n        BuiltinOperator::CAST,\n        BuiltinOperator::PRELU,\n        BuiltinOperator::MAXIMUM,\n        BuiltinOperator::ARG_MAX,\n        BuiltinOperator::MINIMUM,\n        BuiltinOperator::LESS,\n        BuiltinOperator::NEG,\n        BuiltinOperator::PADV2,\n        BuiltinOperator::GREATER,\n        BuiltinOperator::GREATER_EQUAL,\n        BuiltinOperator::LESS_EQUAL,\n        BuiltinOperator::SELECT,\n        BuiltinOperator::SLICE,\n        BuiltinOperator::SIN,\n        BuiltinOperator::TRANSPOSE_CONV,\n        BuiltinOperator::SPARSE_TO_DENSE,\n        BuiltinOperator::TILE,\n        BuiltinOperator::EXPAND_DIMS,\n        BuiltinOperator::EQUAL,\n        BuiltinOperator::NOT_EQUAL,\n        BuiltinOperator::LOG,\n        BuiltinOperator::SUM,\n        BuiltinOperator::SQRT,\n        BuiltinOperator::RSQRT,\n        BuiltinOperator::SHAPE,\n        BuiltinOperator::POW,\n        BuiltinOperator::ARG_MIN,\n        BuiltinOperator::FAKE_QUANT,\n        BuiltinOperator::REDUCE_PROD,\n        BuiltinOperator::REDUCE_MAX,\n        BuiltinOperator::PACK,\n        BuiltinOperator::LOGICAL_OR,\n        BuiltinOperator::ONE_HOT,\n        BuiltinOperator::LOGICAL_AND,\n        BuiltinOperator::LOGICAL_NOT,\n        BuiltinOperator::UNPACK,\n        BuiltinOperator::REDUCE_MIN,\n        BuiltinOperator::FLOOR_DIV,\n        BuiltinOperator::REDUCE_ANY,\n        BuiltinOperator::SQUARE,\n        BuiltinOperator::ZEROS_LIKE,\n        BuiltinOperator::FILL,\n        BuiltinOperator::FLOOR_MOD,\n        BuiltinOperator::RANGE,\n        BuiltinOperator::RESIZE_NEAREST_NEIGHBOR,\n        BuiltinOperator::LEAKY_RELU,\n        BuiltinOperator::SQUARED_DIFFERENCE,\n        BuiltinOperator::MIRROR_PAD,\n        BuiltinOperator::ABS,\n        BuiltinOperator::SPLIT_V,\n        BuiltinOperator::UNIQUE,\n        BuiltinOperator::CEIL,\n        BuiltinOperator::REVERSE_V2,\n        BuiltinOperator::ADD_N,\n        BuiltinOperator::GATHER_ND,\n        BuiltinOperator::COS,\n        BuiltinOperator::WHERE,\n        BuiltinOperator::RANK,\n        BuiltinOperator::ELU,\n        BuiltinOperator::REVERSE_SEQUENCE,\n        BuiltinOperator::MATRIX_DIAG,\n        BuiltinOperator::QUANTIZE,\n        BuiltinOperator::MATRIX_SET_DIAG,\n        BuiltinOperator::ROUND,\n        BuiltinOperator::HARD_SWISH,\n        BuiltinOperator::IF,\n        BuiltinOperator::WHILE,\n        BuiltinOperator::NON_MAX_SUPPRESSION_V4,\n        BuiltinOperator::NON_MAX_SUPPRESSION_V5,\n        BuiltinOperator::SCATTER_ND,\n        BuiltinOperator::SELECT_V2,\n        BuiltinOperator::DENSIFY,\n        BuiltinOperator::SEGMENT_SUM,\n        BuiltinOperator::BATCH_MATMUL,\n        BuiltinOperator::PLACEHOLDER_FOR_GREATER_OP_CODES,\n        BuiltinOperator::CUMSUM,\n        BuiltinOperator::CALL_ONCE,\n        BuiltinOperator::BROADCAST_TO,\n        BuiltinOperator::RFFT2D,\n        BuiltinOperator::CONV_3D,\n        BuiltinOperator::IMAG,\n        BuiltinOperator::REAL,\n        BuiltinOperator::COMPLEX_ABS,\n        BuiltinOperator::HASHTABLE,\n        BuiltinOperator::HASHTABLE_FIND,\n        BuiltinOperator::HASHTABLE_IMPORT,\n        BuiltinOperator::HASHTABLE_SIZE,\n        BuiltinOperator::REDUCE_ALL,\n        BuiltinOperator::CONV_3D_TRANSPOSE,\n        BuiltinOperator::VAR_HANDLE,\n        BuiltinOperator::READ_VARIABLE,\n        BuiltinOperator::ASSIGN_VARIABLE,\n        BuiltinOperator::BROADCAST_ARGS,\n        BuiltinOperator::RANDOM_STANDARD_NORMAL,\n        BuiltinOperator::BUCKETIZE,\n        BuiltinOperator::RANDOM_UNIFORM,\n        BuiltinOperator::MULTINOMIAL,\n        BuiltinOperator::GELU,\n        BuiltinOperator::DYNAMIC_UPDATE_SLICE,\n        BuiltinOperator::RELU_0_TO_1,\n        BuiltinOperator::UNSORTED_SEGMENT_PROD,\n        BuiltinOperator::UNSORTED_SEGMENT_MAX,\n        BuiltinOperator::UNSORTED_SEGMENT_SUM,\n        BuiltinOperator::ATAN2,\n        BuiltinOperator::UNSORTED_SEGMENT_MIN,\n        BuiltinOperator::SIGN,\n        BuiltinOperator::BITCAST,\n        BuiltinOperator::BITWISE_XOR,\n        BuiltinOperator::RIGHT_SHIFT,\n    ];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct BuiltinOperator(pub i32);\n    #[allow(non_upper_case_globals)]\n    impl BuiltinOperator {\n        pub const ADD: Self = Self(0);\n        pub const AVERAGE_POOL_2D: Self = Self(1);\n        pub const CONCATENATION: Self = Self(2);\n        pub const CONV_2D: Self = Self(3);\n        pub const DEPTHWISE_CONV_2D: Self = Self(4);\n        pub const DEPTH_TO_SPACE: Self = Self(5);\n        pub const DEQUANTIZE: Self = Self(6);\n        pub const EMBEDDING_LOOKUP: Self = Self(7);\n        pub const FLOOR: Self = Self(8);\n        pub const FULLY_CONNECTED: Self = Self(9);\n        pub const HASHTABLE_LOOKUP: Self = Self(10);\n        pub const L2_NORMALIZATION: Self = Self(11);\n        pub const L2_POOL_2D: Self = Self(12);\n        pub const LOCAL_RESPONSE_NORMALIZATION: Self = Self(13);\n        pub const LOGISTIC: Self = Self(14);\n        pub const LSH_PROJECTION: Self = Self(15);\n        pub const LSTM: Self = Self(16);\n        pub const MAX_POOL_2D: Self = Self(17);\n        pub const MUL: Self = Self(18);\n        pub const RELU: Self = Self(19);\n        pub const RELU_N1_TO_1: Self = Self(20);\n        pub const RELU6: Self = Self(21);\n        pub const RESHAPE: Self = Self(22);\n        pub const RESIZE_BILINEAR: Self = Self(23);\n        pub const RNN: Self = Self(24);\n        pub const SOFTMAX: Self = Self(25);\n        pub const SPACE_TO_DEPTH: Self = Self(26);\n        pub const SVDF: Self = Self(27);\n        pub const TANH: Self = Self(28);\n        pub const CONCAT_EMBEDDINGS: Self = Self(29);\n        pub const SKIP_GRAM: Self = Self(30);\n        pub const CALL: Self = Self(31);\n        pub const CUSTOM: Self = Self(32);\n        pub const EMBEDDING_LOOKUP_SPARSE: Self = Self(33);\n        pub const PAD: Self = Self(34);\n        pub const UNIDIRECTIONAL_SEQUENCE_RNN: Self = Self(35);\n        pub const GATHER: Self = Self(36);\n        pub const BATCH_TO_SPACE_ND: Self = Self(37);\n        pub const SPACE_TO_BATCH_ND: Self = Self(38);\n        pub const TRANSPOSE: Self = Self(39);\n        pub const MEAN: Self = Self(40);\n        pub const SUB: Self = Self(41);\n        pub const DIV: Self = Self(42);\n        pub const SQUEEZE: Self = Self(43);\n        pub const UNIDIRECTIONAL_SEQUENCE_LSTM: Self = Self(44);\n        pub const STRIDED_SLICE: Self = Self(45);\n        pub const BIDIRECTIONAL_SEQUENCE_RNN: Self = Self(46);\n        pub const EXP: Self = Self(47);\n        pub const TOPK_V2: Self = Self(48);\n        pub const SPLIT: Self = Self(49);\n        pub const LOG_SOFTMAX: Self = Self(50);\n        pub const DELEGATE: Self = Self(51);\n        pub const BIDIRECTIONAL_SEQUENCE_LSTM: Self = Self(52);\n        pub const CAST: Self = Self(53);\n        pub const PRELU: Self = Self(54);\n        pub const MAXIMUM: Self = Self(55);\n        pub const ARG_MAX: Self = Self(56);\n        pub const MINIMUM: Self = Self(57);\n        pub const LESS: Self = Self(58);\n        pub const NEG: Self = Self(59);\n        pub const PADV2: Self = Self(60);\n        pub const GREATER: Self = Self(61);\n        pub const GREATER_EQUAL: Self = Self(62);\n        pub const LESS_EQUAL: Self = Self(63);\n        pub const SELECT: Self = Self(64);\n        pub const SLICE: Self = Self(65);\n        pub const SIN: Self = Self(66);\n        pub const TRANSPOSE_CONV: Self = Self(67);\n        pub const SPARSE_TO_DENSE: Self = Self(68);\n        pub const TILE: Self = Self(69);\n        pub const EXPAND_DIMS: Self = Self(70);\n        pub const EQUAL: Self = Self(71);\n        pub const NOT_EQUAL: Self = Self(72);\n        pub const LOG: Self = Self(73);\n        pub const SUM: Self = Self(74);\n        pub const SQRT: Self = Self(75);\n        pub const RSQRT: Self = Self(76);\n        pub const SHAPE: Self = Self(77);\n        pub const POW: Self = Self(78);\n        pub const ARG_MIN: Self = Self(79);\n        pub const FAKE_QUANT: Self = Self(80);\n        pub const REDUCE_PROD: Self = Self(81);\n        pub const REDUCE_MAX: Self = Self(82);\n        pub const PACK: Self = Self(83);\n        pub const LOGICAL_OR: Self = Self(84);\n        pub const ONE_HOT: Self = Self(85);\n        pub const LOGICAL_AND: Self = Self(86);\n        pub const LOGICAL_NOT: Self = Self(87);\n        pub const UNPACK: Self = Self(88);\n        pub const REDUCE_MIN: Self = Self(89);\n        pub const FLOOR_DIV: Self = Self(90);\n        pub const REDUCE_ANY: Self = Self(91);\n        pub const SQUARE: Self = Self(92);\n        pub const ZEROS_LIKE: Self = Self(93);\n        pub const FILL: Self = Self(94);\n        pub const FLOOR_MOD: Self = Self(95);\n        pub const RANGE: Self = Self(96);\n        pub const RESIZE_NEAREST_NEIGHBOR: Self = Self(97);\n        pub const LEAKY_RELU: Self = Self(98);\n        pub const SQUARED_DIFFERENCE: Self = Self(99);\n        pub const MIRROR_PAD: Self = Self(100);\n        pub const ABS: Self = Self(101);\n        pub const SPLIT_V: Self = Self(102);\n        pub const UNIQUE: Self = Self(103);\n        pub const CEIL: Self = Self(104);\n        pub const REVERSE_V2: Self = Self(105);\n        pub const ADD_N: Self = Self(106);\n        pub const GATHER_ND: Self = Self(107);\n        pub const COS: Self = Self(108);\n        pub const WHERE: Self = Self(109);\n        pub const RANK: Self = Self(110);\n        pub const ELU: Self = Self(111);\n        pub const REVERSE_SEQUENCE: Self = Self(112);\n        pub const MATRIX_DIAG: Self = Self(113);\n        pub const QUANTIZE: Self = Self(114);\n        pub const MATRIX_SET_DIAG: Self = Self(115);\n        pub const ROUND: Self = Self(116);\n        pub const HARD_SWISH: Self = Self(117);\n        pub const IF: Self = Self(118);\n        pub const WHILE: Self = Self(119);\n        pub const NON_MAX_SUPPRESSION_V4: Self = Self(120);\n        pub const NON_MAX_SUPPRESSION_V5: Self = Self(121);\n        pub const SCATTER_ND: Self = Self(122);\n        pub const SELECT_V2: Self = Self(123);\n        pub const DENSIFY: Self = Self(124);\n        pub const SEGMENT_SUM: Self = Self(125);\n        pub const BATCH_MATMUL: Self = Self(126);\n        pub const PLACEHOLDER_FOR_GREATER_OP_CODES: Self = Self(127);\n        pub const CUMSUM: Self = Self(128);\n        pub const CALL_ONCE: Self = Self(129);\n        pub const BROADCAST_TO: Self = Self(130);\n        pub const RFFT2D: Self = Self(131);\n        pub const CONV_3D: Self = Self(132);\n        pub const IMAG: Self = Self(133);\n        pub const REAL: Self = Self(134);\n        pub const COMPLEX_ABS: Self = Self(135);\n        pub const HASHTABLE: Self = Self(136);\n        pub const HASHTABLE_FIND: Self = Self(137);\n        pub const HASHTABLE_IMPORT: Self = Self(138);\n        pub const HASHTABLE_SIZE: Self = Self(139);\n        pub const REDUCE_ALL: Self = Self(140);\n        pub const CONV_3D_TRANSPOSE: Self = Self(141);\n        pub const VAR_HANDLE: Self = Self(142);\n        pub const READ_VARIABLE: Self = Self(143);\n        pub const ASSIGN_VARIABLE: Self = Self(144);\n        pub const BROADCAST_ARGS: Self = Self(145);\n        pub const RANDOM_STANDARD_NORMAL: Self = Self(146);\n        pub const BUCKETIZE: Self = Self(147);\n        pub const RANDOM_UNIFORM: Self = Self(148);\n        pub const MULTINOMIAL: Self = Self(149);\n        pub const GELU: Self = Self(150);\n        pub const DYNAMIC_UPDATE_SLICE: Self = Self(151);\n        pub const RELU_0_TO_1: Self = Self(152);\n        pub const UNSORTED_SEGMENT_PROD: Self = Self(153);\n        pub const UNSORTED_SEGMENT_MAX: Self = Self(154);\n        pub const UNSORTED_SEGMENT_SUM: Self = Self(155);\n        pub const ATAN2: Self = Self(156);\n        pub const UNSORTED_SEGMENT_MIN: Self = Self(157);\n        pub const SIGN: Self = Self(158);\n        pub const BITCAST: Self = Self(159);\n        pub const BITWISE_XOR: Self = Self(160);\n        pub const RIGHT_SHIFT: Self = Self(161);\n\n        pub const ENUM_MIN: i32 = 0;\n        pub const ENUM_MAX: i32 = 161;\n        pub const ENUM_VALUES: &'static [Self] = &[\n            Self::ADD,\n            Self::AVERAGE_POOL_2D,\n            Self::CONCATENATION,\n            Self::CONV_2D,\n            Self::DEPTHWISE_CONV_2D,\n            Self::DEPTH_TO_SPACE,\n            Self::DEQUANTIZE,\n            Self::EMBEDDING_LOOKUP,\n            Self::FLOOR,\n            Self::FULLY_CONNECTED,\n            Self::HASHTABLE_LOOKUP,\n            Self::L2_NORMALIZATION,\n            Self::L2_POOL_2D,\n            Self::LOCAL_RESPONSE_NORMALIZATION,\n            Self::LOGISTIC,\n            Self::LSH_PROJECTION,\n            Self::LSTM,\n            Self::MAX_POOL_2D,\n            Self::MUL,\n            Self::RELU,\n            Self::RELU_N1_TO_1,\n            Self::RELU6,\n            Self::RESHAPE,\n            Self::RESIZE_BILINEAR,\n            Self::RNN,\n            Self::SOFTMAX,\n            Self::SPACE_TO_DEPTH,\n            Self::SVDF,\n            Self::TANH,\n            Self::CONCAT_EMBEDDINGS,\n            Self::SKIP_GRAM,\n            Self::CALL,\n            Self::CUSTOM,\n            Self::EMBEDDING_LOOKUP_SPARSE,\n            Self::PAD,\n            Self::UNIDIRECTIONAL_SEQUENCE_RNN,\n            Self::GATHER,\n            Self::BATCH_TO_SPACE_ND,\n            Self::SPACE_TO_BATCH_ND,\n            Self::TRANSPOSE,\n            Self::MEAN,\n            Self::SUB,\n            Self::DIV,\n            Self::SQUEEZE,\n            Self::UNIDIRECTIONAL_SEQUENCE_LSTM,\n            Self::STRIDED_SLICE,\n            Self::BIDIRECTIONAL_SEQUENCE_RNN,\n            Self::EXP,\n            Self::TOPK_V2,\n            Self::SPLIT,\n            Self::LOG_SOFTMAX,\n            Self::DELEGATE,\n            Self::BIDIRECTIONAL_SEQUENCE_LSTM,\n            Self::CAST,\n            Self::PRELU,\n            Self::MAXIMUM,\n            Self::ARG_MAX,\n            Self::MINIMUM,\n            Self::LESS,\n            Self::NEG,\n            Self::PADV2,\n            Self::GREATER,\n            Self::GREATER_EQUAL,\n            Self::LESS_EQUAL,\n            Self::SELECT,\n            Self::SLICE,\n            Self::SIN,\n            Self::TRANSPOSE_CONV,\n            Self::SPARSE_TO_DENSE,\n            Self::TILE,\n            Self::EXPAND_DIMS,\n            Self::EQUAL,\n            Self::NOT_EQUAL,\n            Self::LOG,\n            Self::SUM,\n            Self::SQRT,\n            Self::RSQRT,\n            Self::SHAPE,\n            Self::POW,\n            Self::ARG_MIN,\n            Self::FAKE_QUANT,\n            Self::REDUCE_PROD,\n            Self::REDUCE_MAX,\n            Self::PACK,\n            Self::LOGICAL_OR,\n            Self::ONE_HOT,\n            Self::LOGICAL_AND,\n            Self::LOGICAL_NOT,\n            Self::UNPACK,\n            Self::REDUCE_MIN,\n            Self::FLOOR_DIV,\n            Self::REDUCE_ANY,\n            Self::SQUARE,\n            Self::ZEROS_LIKE,\n            Self::FILL,\n            Self::FLOOR_MOD,\n            Self::RANGE,\n            Self::RESIZE_NEAREST_NEIGHBOR,\n            Self::LEAKY_RELU,\n            Self::SQUARED_DIFFERENCE,\n            Self::MIRROR_PAD,\n            Self::ABS,\n            Self::SPLIT_V,\n            Self::UNIQUE,\n            Self::CEIL,\n            Self::REVERSE_V2,\n            Self::ADD_N,\n            Self::GATHER_ND,\n            Self::COS,\n            Self::WHERE,\n            Self::RANK,\n            Self::ELU,\n            Self::REVERSE_SEQUENCE,\n            Self::MATRIX_DIAG,\n            Self::QUANTIZE,\n            Self::MATRIX_SET_DIAG,\n            Self::ROUND,\n            Self::HARD_SWISH,\n            Self::IF,\n            Self::WHILE,\n            Self::NON_MAX_SUPPRESSION_V4,\n            Self::NON_MAX_SUPPRESSION_V5,\n            Self::SCATTER_ND,\n            Self::SELECT_V2,\n            Self::DENSIFY,\n            Self::SEGMENT_SUM,\n            Self::BATCH_MATMUL,\n            Self::PLACEHOLDER_FOR_GREATER_OP_CODES,\n            Self::CUMSUM,\n            Self::CALL_ONCE,\n            Self::BROADCAST_TO,\n            Self::RFFT2D,\n            Self::CONV_3D,\n            Self::IMAG,\n            Self::REAL,\n            Self::COMPLEX_ABS,\n            Self::HASHTABLE,\n            Self::HASHTABLE_FIND,\n            Self::HASHTABLE_IMPORT,\n            Self::HASHTABLE_SIZE,\n            Self::REDUCE_ALL,\n            Self::CONV_3D_TRANSPOSE,\n            Self::VAR_HANDLE,\n            Self::READ_VARIABLE,\n            Self::ASSIGN_VARIABLE,\n            Self::BROADCAST_ARGS,\n            Self::RANDOM_STANDARD_NORMAL,\n            Self::BUCKETIZE,\n            Self::RANDOM_UNIFORM,\n            Self::MULTINOMIAL,\n            Self::GELU,\n            Self::DYNAMIC_UPDATE_SLICE,\n            Self::RELU_0_TO_1,\n            Self::UNSORTED_SEGMENT_PROD,\n            Self::UNSORTED_SEGMENT_MAX,\n            Self::UNSORTED_SEGMENT_SUM,\n            Self::ATAN2,\n            Self::UNSORTED_SEGMENT_MIN,\n            Self::SIGN,\n            Self::BITCAST,\n            Self::BITWISE_XOR,\n            Self::RIGHT_SHIFT,\n        ];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::ADD => Some(\"ADD\"),\n                Self::AVERAGE_POOL_2D => Some(\"AVERAGE_POOL_2D\"),\n                Self::CONCATENATION => Some(\"CONCATENATION\"),\n                Self::CONV_2D => Some(\"CONV_2D\"),\n                Self::DEPTHWISE_CONV_2D => Some(\"DEPTHWISE_CONV_2D\"),\n                Self::DEPTH_TO_SPACE => Some(\"DEPTH_TO_SPACE\"),\n                Self::DEQUANTIZE => Some(\"DEQUANTIZE\"),\n                Self::EMBEDDING_LOOKUP => Some(\"EMBEDDING_LOOKUP\"),\n                Self::FLOOR => Some(\"FLOOR\"),\n                Self::FULLY_CONNECTED => Some(\"FULLY_CONNECTED\"),\n                Self::HASHTABLE_LOOKUP => Some(\"HASHTABLE_LOOKUP\"),\n                Self::L2_NORMALIZATION => Some(\"L2_NORMALIZATION\"),\n                Self::L2_POOL_2D => Some(\"L2_POOL_2D\"),\n                Self::LOCAL_RESPONSE_NORMALIZATION => Some(\"LOCAL_RESPONSE_NORMALIZATION\"),\n                Self::LOGISTIC => Some(\"LOGISTIC\"),\n                Self::LSH_PROJECTION => Some(\"LSH_PROJECTION\"),\n                Self::LSTM => Some(\"LSTM\"),\n                Self::MAX_POOL_2D => Some(\"MAX_POOL_2D\"),\n                Self::MUL => Some(\"MUL\"),\n                Self::RELU => Some(\"RELU\"),\n                Self::RELU_N1_TO_1 => Some(\"RELU_N1_TO_1\"),\n                Self::RELU6 => Some(\"RELU6\"),\n                Self::RESHAPE => Some(\"RESHAPE\"),\n                Self::RESIZE_BILINEAR => Some(\"RESIZE_BILINEAR\"),\n                Self::RNN => Some(\"RNN\"),\n                Self::SOFTMAX => Some(\"SOFTMAX\"),\n                Self::SPACE_TO_DEPTH => Some(\"SPACE_TO_DEPTH\"),\n                Self::SVDF => Some(\"SVDF\"),\n                Self::TANH => Some(\"TANH\"),\n                Self::CONCAT_EMBEDDINGS => Some(\"CONCAT_EMBEDDINGS\"),\n                Self::SKIP_GRAM => Some(\"SKIP_GRAM\"),\n                Self::CALL => Some(\"CALL\"),\n                Self::CUSTOM => Some(\"CUSTOM\"),\n                Self::EMBEDDING_LOOKUP_SPARSE => Some(\"EMBEDDING_LOOKUP_SPARSE\"),\n                Self::PAD => Some(\"PAD\"),\n                Self::UNIDIRECTIONAL_SEQUENCE_RNN => Some(\"UNIDIRECTIONAL_SEQUENCE_RNN\"),\n                Self::GATHER => Some(\"GATHER\"),\n                Self::BATCH_TO_SPACE_ND => Some(\"BATCH_TO_SPACE_ND\"),\n                Self::SPACE_TO_BATCH_ND => Some(\"SPACE_TO_BATCH_ND\"),\n                Self::TRANSPOSE => Some(\"TRANSPOSE\"),\n                Self::MEAN => Some(\"MEAN\"),\n                Self::SUB => Some(\"SUB\"),\n                Self::DIV => Some(\"DIV\"),\n                Self::SQUEEZE => Some(\"SQUEEZE\"),\n                Self::UNIDIRECTIONAL_SEQUENCE_LSTM => Some(\"UNIDIRECTIONAL_SEQUENCE_LSTM\"),\n                Self::STRIDED_SLICE => Some(\"STRIDED_SLICE\"),\n                Self::BIDIRECTIONAL_SEQUENCE_RNN => Some(\"BIDIRECTIONAL_SEQUENCE_RNN\"),\n                Self::EXP => Some(\"EXP\"),\n                Self::TOPK_V2 => Some(\"TOPK_V2\"),\n                Self::SPLIT => Some(\"SPLIT\"),\n                Self::LOG_SOFTMAX => Some(\"LOG_SOFTMAX\"),\n                Self::DELEGATE => Some(\"DELEGATE\"),\n                Self::BIDIRECTIONAL_SEQUENCE_LSTM => Some(\"BIDIRECTIONAL_SEQUENCE_LSTM\"),\n                Self::CAST => Some(\"CAST\"),\n                Self::PRELU => Some(\"PRELU\"),\n                Self::MAXIMUM => Some(\"MAXIMUM\"),\n                Self::ARG_MAX => Some(\"ARG_MAX\"),\n                Self::MINIMUM => Some(\"MINIMUM\"),\n                Self::LESS => Some(\"LESS\"),\n                Self::NEG => Some(\"NEG\"),\n                Self::PADV2 => Some(\"PADV2\"),\n                Self::GREATER => Some(\"GREATER\"),\n                Self::GREATER_EQUAL => Some(\"GREATER_EQUAL\"),\n                Self::LESS_EQUAL => Some(\"LESS_EQUAL\"),\n                Self::SELECT => Some(\"SELECT\"),\n                Self::SLICE => Some(\"SLICE\"),\n                Self::SIN => Some(\"SIN\"),\n                Self::TRANSPOSE_CONV => Some(\"TRANSPOSE_CONV\"),\n                Self::SPARSE_TO_DENSE => Some(\"SPARSE_TO_DENSE\"),\n                Self::TILE => Some(\"TILE\"),\n                Self::EXPAND_DIMS => Some(\"EXPAND_DIMS\"),\n                Self::EQUAL => Some(\"EQUAL\"),\n                Self::NOT_EQUAL => Some(\"NOT_EQUAL\"),\n                Self::LOG => Some(\"LOG\"),\n                Self::SUM => Some(\"SUM\"),\n                Self::SQRT => Some(\"SQRT\"),\n                Self::RSQRT => Some(\"RSQRT\"),\n                Self::SHAPE => Some(\"SHAPE\"),\n                Self::POW => Some(\"POW\"),\n                Self::ARG_MIN => Some(\"ARG_MIN\"),\n                Self::FAKE_QUANT => Some(\"FAKE_QUANT\"),\n                Self::REDUCE_PROD => Some(\"REDUCE_PROD\"),\n                Self::REDUCE_MAX => Some(\"REDUCE_MAX\"),\n                Self::PACK => Some(\"PACK\"),\n                Self::LOGICAL_OR => Some(\"LOGICAL_OR\"),\n                Self::ONE_HOT => Some(\"ONE_HOT\"),\n                Self::LOGICAL_AND => Some(\"LOGICAL_AND\"),\n                Self::LOGICAL_NOT => Some(\"LOGICAL_NOT\"),\n                Self::UNPACK => Some(\"UNPACK\"),\n                Self::REDUCE_MIN => Some(\"REDUCE_MIN\"),\n                Self::FLOOR_DIV => Some(\"FLOOR_DIV\"),\n                Self::REDUCE_ANY => Some(\"REDUCE_ANY\"),\n                Self::SQUARE => Some(\"SQUARE\"),\n                Self::ZEROS_LIKE => Some(\"ZEROS_LIKE\"),\n                Self::FILL => Some(\"FILL\"),\n                Self::FLOOR_MOD => Some(\"FLOOR_MOD\"),\n                Self::RANGE => Some(\"RANGE\"),\n                Self::RESIZE_NEAREST_NEIGHBOR => Some(\"RESIZE_NEAREST_NEIGHBOR\"),\n                Self::LEAKY_RELU => Some(\"LEAKY_RELU\"),\n                Self::SQUARED_DIFFERENCE => Some(\"SQUARED_DIFFERENCE\"),\n                Self::MIRROR_PAD => Some(\"MIRROR_PAD\"),\n                Self::ABS => Some(\"ABS\"),\n                Self::SPLIT_V => Some(\"SPLIT_V\"),\n                Self::UNIQUE => Some(\"UNIQUE\"),\n                Self::CEIL => Some(\"CEIL\"),\n                Self::REVERSE_V2 => Some(\"REVERSE_V2\"),\n                Self::ADD_N => Some(\"ADD_N\"),\n                Self::GATHER_ND => Some(\"GATHER_ND\"),\n                Self::COS => Some(\"COS\"),\n                Self::WHERE => Some(\"WHERE\"),\n                Self::RANK => Some(\"RANK\"),\n                Self::ELU => Some(\"ELU\"),\n                Self::REVERSE_SEQUENCE => Some(\"REVERSE_SEQUENCE\"),\n                Self::MATRIX_DIAG => Some(\"MATRIX_DIAG\"),\n                Self::QUANTIZE => Some(\"QUANTIZE\"),\n                Self::MATRIX_SET_DIAG => Some(\"MATRIX_SET_DIAG\"),\n                Self::ROUND => Some(\"ROUND\"),\n                Self::HARD_SWISH => Some(\"HARD_SWISH\"),\n                Self::IF => Some(\"IF\"),\n                Self::WHILE => Some(\"WHILE\"),\n                Self::NON_MAX_SUPPRESSION_V4 => Some(\"NON_MAX_SUPPRESSION_V4\"),\n                Self::NON_MAX_SUPPRESSION_V5 => Some(\"NON_MAX_SUPPRESSION_V5\"),\n                Self::SCATTER_ND => Some(\"SCATTER_ND\"),\n                Self::SELECT_V2 => Some(\"SELECT_V2\"),\n                Self::DENSIFY => Some(\"DENSIFY\"),\n                Self::SEGMENT_SUM => Some(\"SEGMENT_SUM\"),\n                Self::BATCH_MATMUL => Some(\"BATCH_MATMUL\"),\n                Self::PLACEHOLDER_FOR_GREATER_OP_CODES => Some(\"PLACEHOLDER_FOR_GREATER_OP_CODES\"),\n                Self::CUMSUM => Some(\"CUMSUM\"),\n                Self::CALL_ONCE => Some(\"CALL_ONCE\"),\n                Self::BROADCAST_TO => Some(\"BROADCAST_TO\"),\n                Self::RFFT2D => Some(\"RFFT2D\"),\n                Self::CONV_3D => Some(\"CONV_3D\"),\n                Self::IMAG => Some(\"IMAG\"),\n                Self::REAL => Some(\"REAL\"),\n                Self::COMPLEX_ABS => Some(\"COMPLEX_ABS\"),\n                Self::HASHTABLE => Some(\"HASHTABLE\"),\n                Self::HASHTABLE_FIND => Some(\"HASHTABLE_FIND\"),\n                Self::HASHTABLE_IMPORT => Some(\"HASHTABLE_IMPORT\"),\n                Self::HASHTABLE_SIZE => Some(\"HASHTABLE_SIZE\"),\n                Self::REDUCE_ALL => Some(\"REDUCE_ALL\"),\n                Self::CONV_3D_TRANSPOSE => Some(\"CONV_3D_TRANSPOSE\"),\n                Self::VAR_HANDLE => Some(\"VAR_HANDLE\"),\n                Self::READ_VARIABLE => Some(\"READ_VARIABLE\"),\n                Self::ASSIGN_VARIABLE => Some(\"ASSIGN_VARIABLE\"),\n                Self::BROADCAST_ARGS => Some(\"BROADCAST_ARGS\"),\n                Self::RANDOM_STANDARD_NORMAL => Some(\"RANDOM_STANDARD_NORMAL\"),\n                Self::BUCKETIZE => Some(\"BUCKETIZE\"),\n                Self::RANDOM_UNIFORM => Some(\"RANDOM_UNIFORM\"),\n                Self::MULTINOMIAL => Some(\"MULTINOMIAL\"),\n                Self::GELU => Some(\"GELU\"),\n                Self::DYNAMIC_UPDATE_SLICE => Some(\"DYNAMIC_UPDATE_SLICE\"),\n                Self::RELU_0_TO_1 => Some(\"RELU_0_TO_1\"),\n                Self::UNSORTED_SEGMENT_PROD => Some(\"UNSORTED_SEGMENT_PROD\"),\n                Self::UNSORTED_SEGMENT_MAX => Some(\"UNSORTED_SEGMENT_MAX\"),\n                Self::UNSORTED_SEGMENT_SUM => Some(\"UNSORTED_SEGMENT_SUM\"),\n                Self::ATAN2 => Some(\"ATAN2\"),\n                Self::UNSORTED_SEGMENT_MIN => Some(\"UNSORTED_SEGMENT_MIN\"),\n                Self::SIGN => Some(\"SIGN\"),\n                Self::BITCAST => Some(\"BITCAST\"),\n                Self::BITWISE_XOR => Some(\"BITWISE_XOR\"),\n                Self::RIGHT_SHIFT => Some(\"RIGHT_SHIFT\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for BuiltinOperator {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for BuiltinOperator {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i32>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for BuiltinOperator {\n        type Output = BuiltinOperator;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i32>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for BuiltinOperator {\n        type Scalar = i32;\n        #[inline]\n        fn to_little_endian(self) -> i32 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i32) -> Self {\n            let b = i32::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for BuiltinOperator {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i32::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for BuiltinOperator {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_BUILTIN_OPTIONS: u8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_BUILTIN_OPTIONS: u8 = 126;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_BUILTIN_OPTIONS: [BuiltinOptions; 127] = [\n        BuiltinOptions::NONE,\n        BuiltinOptions::Conv2DOptions,\n        BuiltinOptions::DepthwiseConv2DOptions,\n        BuiltinOptions::ConcatEmbeddingsOptions,\n        BuiltinOptions::LSHProjectionOptions,\n        BuiltinOptions::Pool2DOptions,\n        BuiltinOptions::SVDFOptions,\n        BuiltinOptions::RNNOptions,\n        BuiltinOptions::FullyConnectedOptions,\n        BuiltinOptions::SoftmaxOptions,\n        BuiltinOptions::ConcatenationOptions,\n        BuiltinOptions::AddOptions,\n        BuiltinOptions::L2NormOptions,\n        BuiltinOptions::LocalResponseNormalizationOptions,\n        BuiltinOptions::LSTMOptions,\n        BuiltinOptions::ResizeBilinearOptions,\n        BuiltinOptions::CallOptions,\n        BuiltinOptions::ReshapeOptions,\n        BuiltinOptions::SkipGramOptions,\n        BuiltinOptions::SpaceToDepthOptions,\n        BuiltinOptions::EmbeddingLookupSparseOptions,\n        BuiltinOptions::MulOptions,\n        BuiltinOptions::PadOptions,\n        BuiltinOptions::GatherOptions,\n        BuiltinOptions::BatchToSpaceNDOptions,\n        BuiltinOptions::SpaceToBatchNDOptions,\n        BuiltinOptions::TransposeOptions,\n        BuiltinOptions::ReducerOptions,\n        BuiltinOptions::SubOptions,\n        BuiltinOptions::DivOptions,\n        BuiltinOptions::SqueezeOptions,\n        BuiltinOptions::SequenceRNNOptions,\n        BuiltinOptions::StridedSliceOptions,\n        BuiltinOptions::ExpOptions,\n        BuiltinOptions::TopKV2Options,\n        BuiltinOptions::SplitOptions,\n        BuiltinOptions::LogSoftmaxOptions,\n        BuiltinOptions::CastOptions,\n        BuiltinOptions::DequantizeOptions,\n        BuiltinOptions::MaximumMinimumOptions,\n        BuiltinOptions::ArgMaxOptions,\n        BuiltinOptions::LessOptions,\n        BuiltinOptions::NegOptions,\n        BuiltinOptions::PadV2Options,\n        BuiltinOptions::GreaterOptions,\n        BuiltinOptions::GreaterEqualOptions,\n        BuiltinOptions::LessEqualOptions,\n        BuiltinOptions::SelectOptions,\n        BuiltinOptions::SliceOptions,\n        BuiltinOptions::TransposeConvOptions,\n        BuiltinOptions::SparseToDenseOptions,\n        BuiltinOptions::TileOptions,\n        BuiltinOptions::ExpandDimsOptions,\n        BuiltinOptions::EqualOptions,\n        BuiltinOptions::NotEqualOptions,\n        BuiltinOptions::ShapeOptions,\n        BuiltinOptions::PowOptions,\n        BuiltinOptions::ArgMinOptions,\n        BuiltinOptions::FakeQuantOptions,\n        BuiltinOptions::PackOptions,\n        BuiltinOptions::LogicalOrOptions,\n        BuiltinOptions::OneHotOptions,\n        BuiltinOptions::LogicalAndOptions,\n        BuiltinOptions::LogicalNotOptions,\n        BuiltinOptions::UnpackOptions,\n        BuiltinOptions::FloorDivOptions,\n        BuiltinOptions::SquareOptions,\n        BuiltinOptions::ZerosLikeOptions,\n        BuiltinOptions::FillOptions,\n        BuiltinOptions::BidirectionalSequenceLSTMOptions,\n        BuiltinOptions::BidirectionalSequenceRNNOptions,\n        BuiltinOptions::UnidirectionalSequenceLSTMOptions,\n        BuiltinOptions::FloorModOptions,\n        BuiltinOptions::RangeOptions,\n        BuiltinOptions::ResizeNearestNeighborOptions,\n        BuiltinOptions::LeakyReluOptions,\n        BuiltinOptions::SquaredDifferenceOptions,\n        BuiltinOptions::MirrorPadOptions,\n        BuiltinOptions::AbsOptions,\n        BuiltinOptions::SplitVOptions,\n        BuiltinOptions::UniqueOptions,\n        BuiltinOptions::ReverseV2Options,\n        BuiltinOptions::AddNOptions,\n        BuiltinOptions::GatherNdOptions,\n        BuiltinOptions::CosOptions,\n        BuiltinOptions::WhereOptions,\n        BuiltinOptions::RankOptions,\n        BuiltinOptions::ReverseSequenceOptions,\n        BuiltinOptions::MatrixDiagOptions,\n        BuiltinOptions::QuantizeOptions,\n        BuiltinOptions::MatrixSetDiagOptions,\n        BuiltinOptions::HardSwishOptions,\n        BuiltinOptions::IfOptions,\n        BuiltinOptions::WhileOptions,\n        BuiltinOptions::DepthToSpaceOptions,\n        BuiltinOptions::NonMaxSuppressionV4Options,\n        BuiltinOptions::NonMaxSuppressionV5Options,\n        BuiltinOptions::ScatterNdOptions,\n        BuiltinOptions::SelectV2Options,\n        BuiltinOptions::DensifyOptions,\n        BuiltinOptions::SegmentSumOptions,\n        BuiltinOptions::BatchMatMulOptions,\n        BuiltinOptions::CumsumOptions,\n        BuiltinOptions::CallOnceOptions,\n        BuiltinOptions::BroadcastToOptions,\n        BuiltinOptions::Rfft2dOptions,\n        BuiltinOptions::Conv3DOptions,\n        BuiltinOptions::HashtableOptions,\n        BuiltinOptions::HashtableFindOptions,\n        BuiltinOptions::HashtableImportOptions,\n        BuiltinOptions::HashtableSizeOptions,\n        BuiltinOptions::VarHandleOptions,\n        BuiltinOptions::ReadVariableOptions,\n        BuiltinOptions::AssignVariableOptions,\n        BuiltinOptions::RandomOptions,\n        BuiltinOptions::BucketizeOptions,\n        BuiltinOptions::GeluOptions,\n        BuiltinOptions::DynamicUpdateSliceOptions,\n        BuiltinOptions::UnsortedSegmentProdOptions,\n        BuiltinOptions::UnsortedSegmentMaxOptions,\n        BuiltinOptions::UnsortedSegmentMinOptions,\n        BuiltinOptions::UnsortedSegmentSumOptions,\n        BuiltinOptions::ATan2Options,\n        BuiltinOptions::SignOptions,\n        BuiltinOptions::BitcastOptions,\n        BuiltinOptions::BitwiseXorOptions,\n        BuiltinOptions::RightShiftOptions,\n    ];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct BuiltinOptions(pub u8);\n    #[allow(non_upper_case_globals)]\n    impl BuiltinOptions {\n        pub const NONE: Self = Self(0);\n        pub const Conv2DOptions: Self = Self(1);\n        pub const DepthwiseConv2DOptions: Self = Self(2);\n        pub const ConcatEmbeddingsOptions: Self = Self(3);\n        pub const LSHProjectionOptions: Self = Self(4);\n        pub const Pool2DOptions: Self = Self(5);\n        pub const SVDFOptions: Self = Self(6);\n        pub const RNNOptions: Self = Self(7);\n        pub const FullyConnectedOptions: Self = Self(8);\n        pub const SoftmaxOptions: Self = Self(9);\n        pub const ConcatenationOptions: Self = Self(10);\n        pub const AddOptions: Self = Self(11);\n        pub const L2NormOptions: Self = Self(12);\n        pub const LocalResponseNormalizationOptions: Self = Self(13);\n        pub const LSTMOptions: Self = Self(14);\n        pub const ResizeBilinearOptions: Self = Self(15);\n        pub const CallOptions: Self = Self(16);\n        pub const ReshapeOptions: Self = Self(17);\n        pub const SkipGramOptions: Self = Self(18);\n        pub const SpaceToDepthOptions: Self = Self(19);\n        pub const EmbeddingLookupSparseOptions: Self = Self(20);\n        pub const MulOptions: Self = Self(21);\n        pub const PadOptions: Self = Self(22);\n        pub const GatherOptions: Self = Self(23);\n        pub const BatchToSpaceNDOptions: Self = Self(24);\n        pub const SpaceToBatchNDOptions: Self = Self(25);\n        pub const TransposeOptions: Self = Self(26);\n        pub const ReducerOptions: Self = Self(27);\n        pub const SubOptions: Self = Self(28);\n        pub const DivOptions: Self = Self(29);\n        pub const SqueezeOptions: Self = Self(30);\n        pub const SequenceRNNOptions: Self = Self(31);\n        pub const StridedSliceOptions: Self = Self(32);\n        pub const ExpOptions: Self = Self(33);\n        pub const TopKV2Options: Self = Self(34);\n        pub const SplitOptions: Self = Self(35);\n        pub const LogSoftmaxOptions: Self = Self(36);\n        pub const CastOptions: Self = Self(37);\n        pub const DequantizeOptions: Self = Self(38);\n        pub const MaximumMinimumOptions: Self = Self(39);\n        pub const ArgMaxOptions: Self = Self(40);\n        pub const LessOptions: Self = Self(41);\n        pub const NegOptions: Self = Self(42);\n        pub const PadV2Options: Self = Self(43);\n        pub const GreaterOptions: Self = Self(44);\n        pub const GreaterEqualOptions: Self = Self(45);\n        pub const LessEqualOptions: Self = Self(46);\n        pub const SelectOptions: Self = Self(47);\n        pub const SliceOptions: Self = Self(48);\n        pub const TransposeConvOptions: Self = Self(49);\n        pub const SparseToDenseOptions: Self = Self(50);\n        pub const TileOptions: Self = Self(51);\n        pub const ExpandDimsOptions: Self = Self(52);\n        pub const EqualOptions: Self = Self(53);\n        pub const NotEqualOptions: Self = Self(54);\n        pub const ShapeOptions: Self = Self(55);\n        pub const PowOptions: Self = Self(56);\n        pub const ArgMinOptions: Self = Self(57);\n        pub const FakeQuantOptions: Self = Self(58);\n        pub const PackOptions: Self = Self(59);\n        pub const LogicalOrOptions: Self = Self(60);\n        pub const OneHotOptions: Self = Self(61);\n        pub const LogicalAndOptions: Self = Self(62);\n        pub const LogicalNotOptions: Self = Self(63);\n        pub const UnpackOptions: Self = Self(64);\n        pub const FloorDivOptions: Self = Self(65);\n        pub const SquareOptions: Self = Self(66);\n        pub const ZerosLikeOptions: Self = Self(67);\n        pub const FillOptions: Self = Self(68);\n        pub const BidirectionalSequenceLSTMOptions: Self = Self(69);\n        pub const BidirectionalSequenceRNNOptions: Self = Self(70);\n        pub const UnidirectionalSequenceLSTMOptions: Self = Self(71);\n        pub const FloorModOptions: Self = Self(72);\n        pub const RangeOptions: Self = Self(73);\n        pub const ResizeNearestNeighborOptions: Self = Self(74);\n        pub const LeakyReluOptions: Self = Self(75);\n        pub const SquaredDifferenceOptions: Self = Self(76);\n        pub const MirrorPadOptions: Self = Self(77);\n        pub const AbsOptions: Self = Self(78);\n        pub const SplitVOptions: Self = Self(79);\n        pub const UniqueOptions: Self = Self(80);\n        pub const ReverseV2Options: Self = Self(81);\n        pub const AddNOptions: Self = Self(82);\n        pub const GatherNdOptions: Self = Self(83);\n        pub const CosOptions: Self = Self(84);\n        pub const WhereOptions: Self = Self(85);\n        pub const RankOptions: Self = Self(86);\n        pub const ReverseSequenceOptions: Self = Self(87);\n        pub const MatrixDiagOptions: Self = Self(88);\n        pub const QuantizeOptions: Self = Self(89);\n        pub const MatrixSetDiagOptions: Self = Self(90);\n        pub const HardSwishOptions: Self = Self(91);\n        pub const IfOptions: Self = Self(92);\n        pub const WhileOptions: Self = Self(93);\n        pub const DepthToSpaceOptions: Self = Self(94);\n        pub const NonMaxSuppressionV4Options: Self = Self(95);\n        pub const NonMaxSuppressionV5Options: Self = Self(96);\n        pub const ScatterNdOptions: Self = Self(97);\n        pub const SelectV2Options: Self = Self(98);\n        pub const DensifyOptions: Self = Self(99);\n        pub const SegmentSumOptions: Self = Self(100);\n        pub const BatchMatMulOptions: Self = Self(101);\n        pub const CumsumOptions: Self = Self(102);\n        pub const CallOnceOptions: Self = Self(103);\n        pub const BroadcastToOptions: Self = Self(104);\n        pub const Rfft2dOptions: Self = Self(105);\n        pub const Conv3DOptions: Self = Self(106);\n        pub const HashtableOptions: Self = Self(107);\n        pub const HashtableFindOptions: Self = Self(108);\n        pub const HashtableImportOptions: Self = Self(109);\n        pub const HashtableSizeOptions: Self = Self(110);\n        pub const VarHandleOptions: Self = Self(111);\n        pub const ReadVariableOptions: Self = Self(112);\n        pub const AssignVariableOptions: Self = Self(113);\n        pub const RandomOptions: Self = Self(114);\n        pub const BucketizeOptions: Self = Self(115);\n        pub const GeluOptions: Self = Self(116);\n        pub const DynamicUpdateSliceOptions: Self = Self(117);\n        pub const UnsortedSegmentProdOptions: Self = Self(118);\n        pub const UnsortedSegmentMaxOptions: Self = Self(119);\n        pub const UnsortedSegmentMinOptions: Self = Self(120);\n        pub const UnsortedSegmentSumOptions: Self = Self(121);\n        pub const ATan2Options: Self = Self(122);\n        pub const SignOptions: Self = Self(123);\n        pub const BitcastOptions: Self = Self(124);\n        pub const BitwiseXorOptions: Self = Self(125);\n        pub const RightShiftOptions: Self = Self(126);\n\n        pub const ENUM_MIN: u8 = 0;\n        pub const ENUM_MAX: u8 = 126;\n        pub const ENUM_VALUES: &'static [Self] = &[\n            Self::NONE,\n            Self::Conv2DOptions,\n            Self::DepthwiseConv2DOptions,\n            Self::ConcatEmbeddingsOptions,\n            Self::LSHProjectionOptions,\n            Self::Pool2DOptions,\n            Self::SVDFOptions,\n            Self::RNNOptions,\n            Self::FullyConnectedOptions,\n            Self::SoftmaxOptions,\n            Self::ConcatenationOptions,\n            Self::AddOptions,\n            Self::L2NormOptions,\n            Self::LocalResponseNormalizationOptions,\n            Self::LSTMOptions,\n            Self::ResizeBilinearOptions,\n            Self::CallOptions,\n            Self::ReshapeOptions,\n            Self::SkipGramOptions,\n            Self::SpaceToDepthOptions,\n            Self::EmbeddingLookupSparseOptions,\n            Self::MulOptions,\n            Self::PadOptions,\n            Self::GatherOptions,\n            Self::BatchToSpaceNDOptions,\n            Self::SpaceToBatchNDOptions,\n            Self::TransposeOptions,\n            Self::ReducerOptions,\n            Self::SubOptions,\n            Self::DivOptions,\n            Self::SqueezeOptions,\n            Self::SequenceRNNOptions,\n            Self::StridedSliceOptions,\n            Self::ExpOptions,\n            Self::TopKV2Options,\n            Self::SplitOptions,\n            Self::LogSoftmaxOptions,\n            Self::CastOptions,\n            Self::DequantizeOptions,\n            Self::MaximumMinimumOptions,\n            Self::ArgMaxOptions,\n            Self::LessOptions,\n            Self::NegOptions,\n            Self::PadV2Options,\n            Self::GreaterOptions,\n            Self::GreaterEqualOptions,\n            Self::LessEqualOptions,\n            Self::SelectOptions,\n            Self::SliceOptions,\n            Self::TransposeConvOptions,\n            Self::SparseToDenseOptions,\n            Self::TileOptions,\n            Self::ExpandDimsOptions,\n            Self::EqualOptions,\n            Self::NotEqualOptions,\n            Self::ShapeOptions,\n            Self::PowOptions,\n            Self::ArgMinOptions,\n            Self::FakeQuantOptions,\n            Self::PackOptions,\n            Self::LogicalOrOptions,\n            Self::OneHotOptions,\n            Self::LogicalAndOptions,\n            Self::LogicalNotOptions,\n            Self::UnpackOptions,\n            Self::FloorDivOptions,\n            Self::SquareOptions,\n            Self::ZerosLikeOptions,\n            Self::FillOptions,\n            Self::BidirectionalSequenceLSTMOptions,\n            Self::BidirectionalSequenceRNNOptions,\n            Self::UnidirectionalSequenceLSTMOptions,\n            Self::FloorModOptions,\n            Self::RangeOptions,\n            Self::ResizeNearestNeighborOptions,\n            Self::LeakyReluOptions,\n            Self::SquaredDifferenceOptions,\n            Self::MirrorPadOptions,\n            Self::AbsOptions,\n            Self::SplitVOptions,\n            Self::UniqueOptions,\n            Self::ReverseV2Options,\n            Self::AddNOptions,\n            Self::GatherNdOptions,\n            Self::CosOptions,\n            Self::WhereOptions,\n            Self::RankOptions,\n            Self::ReverseSequenceOptions,\n            Self::MatrixDiagOptions,\n            Self::QuantizeOptions,\n            Self::MatrixSetDiagOptions,\n            Self::HardSwishOptions,\n            Self::IfOptions,\n            Self::WhileOptions,\n            Self::DepthToSpaceOptions,\n            Self::NonMaxSuppressionV4Options,\n            Self::NonMaxSuppressionV5Options,\n            Self::ScatterNdOptions,\n            Self::SelectV2Options,\n            Self::DensifyOptions,\n            Self::SegmentSumOptions,\n            Self::BatchMatMulOptions,\n            Self::CumsumOptions,\n            Self::CallOnceOptions,\n            Self::BroadcastToOptions,\n            Self::Rfft2dOptions,\n            Self::Conv3DOptions,\n            Self::HashtableOptions,\n            Self::HashtableFindOptions,\n            Self::HashtableImportOptions,\n            Self::HashtableSizeOptions,\n            Self::VarHandleOptions,\n            Self::ReadVariableOptions,\n            Self::AssignVariableOptions,\n            Self::RandomOptions,\n            Self::BucketizeOptions,\n            Self::GeluOptions,\n            Self::DynamicUpdateSliceOptions,\n            Self::UnsortedSegmentProdOptions,\n            Self::UnsortedSegmentMaxOptions,\n            Self::UnsortedSegmentMinOptions,\n            Self::UnsortedSegmentSumOptions,\n            Self::ATan2Options,\n            Self::SignOptions,\n            Self::BitcastOptions,\n            Self::BitwiseXorOptions,\n            Self::RightShiftOptions,\n        ];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::NONE => Some(\"NONE\"),\n                Self::Conv2DOptions => Some(\"Conv2DOptions\"),\n                Self::DepthwiseConv2DOptions => Some(\"DepthwiseConv2DOptions\"),\n                Self::ConcatEmbeddingsOptions => Some(\"ConcatEmbeddingsOptions\"),\n                Self::LSHProjectionOptions => Some(\"LSHProjectionOptions\"),\n                Self::Pool2DOptions => Some(\"Pool2DOptions\"),\n                Self::SVDFOptions => Some(\"SVDFOptions\"),\n                Self::RNNOptions => Some(\"RNNOptions\"),\n                Self::FullyConnectedOptions => Some(\"FullyConnectedOptions\"),\n                Self::SoftmaxOptions => Some(\"SoftmaxOptions\"),\n                Self::ConcatenationOptions => Some(\"ConcatenationOptions\"),\n                Self::AddOptions => Some(\"AddOptions\"),\n                Self::L2NormOptions => Some(\"L2NormOptions\"),\n                Self::LocalResponseNormalizationOptions => {\n                    Some(\"LocalResponseNormalizationOptions\")\n                }\n                Self::LSTMOptions => Some(\"LSTMOptions\"),\n                Self::ResizeBilinearOptions => Some(\"ResizeBilinearOptions\"),\n                Self::CallOptions => Some(\"CallOptions\"),\n                Self::ReshapeOptions => Some(\"ReshapeOptions\"),\n                Self::SkipGramOptions => Some(\"SkipGramOptions\"),\n                Self::SpaceToDepthOptions => Some(\"SpaceToDepthOptions\"),\n                Self::EmbeddingLookupSparseOptions => Some(\"EmbeddingLookupSparseOptions\"),\n                Self::MulOptions => Some(\"MulOptions\"),\n                Self::PadOptions => Some(\"PadOptions\"),\n                Self::GatherOptions => Some(\"GatherOptions\"),\n                Self::BatchToSpaceNDOptions => Some(\"BatchToSpaceNDOptions\"),\n                Self::SpaceToBatchNDOptions => Some(\"SpaceToBatchNDOptions\"),\n                Self::TransposeOptions => Some(\"TransposeOptions\"),\n                Self::ReducerOptions => Some(\"ReducerOptions\"),\n                Self::SubOptions => Some(\"SubOptions\"),\n                Self::DivOptions => Some(\"DivOptions\"),\n                Self::SqueezeOptions => Some(\"SqueezeOptions\"),\n                Self::SequenceRNNOptions => Some(\"SequenceRNNOptions\"),\n                Self::StridedSliceOptions => Some(\"StridedSliceOptions\"),\n                Self::ExpOptions => Some(\"ExpOptions\"),\n                Self::TopKV2Options => Some(\"TopKV2Options\"),\n                Self::SplitOptions => Some(\"SplitOptions\"),\n                Self::LogSoftmaxOptions => Some(\"LogSoftmaxOptions\"),\n                Self::CastOptions => Some(\"CastOptions\"),\n                Self::DequantizeOptions => Some(\"DequantizeOptions\"),\n                Self::MaximumMinimumOptions => Some(\"MaximumMinimumOptions\"),\n                Self::ArgMaxOptions => Some(\"ArgMaxOptions\"),\n                Self::LessOptions => Some(\"LessOptions\"),\n                Self::NegOptions => Some(\"NegOptions\"),\n                Self::PadV2Options => Some(\"PadV2Options\"),\n                Self::GreaterOptions => Some(\"GreaterOptions\"),\n                Self::GreaterEqualOptions => Some(\"GreaterEqualOptions\"),\n                Self::LessEqualOptions => Some(\"LessEqualOptions\"),\n                Self::SelectOptions => Some(\"SelectOptions\"),\n                Self::SliceOptions => Some(\"SliceOptions\"),\n                Self::TransposeConvOptions => Some(\"TransposeConvOptions\"),\n                Self::SparseToDenseOptions => Some(\"SparseToDenseOptions\"),\n                Self::TileOptions => Some(\"TileOptions\"),\n                Self::ExpandDimsOptions => Some(\"ExpandDimsOptions\"),\n                Self::EqualOptions => Some(\"EqualOptions\"),\n                Self::NotEqualOptions => Some(\"NotEqualOptions\"),\n                Self::ShapeOptions => Some(\"ShapeOptions\"),\n                Self::PowOptions => Some(\"PowOptions\"),\n                Self::ArgMinOptions => Some(\"ArgMinOptions\"),\n                Self::FakeQuantOptions => Some(\"FakeQuantOptions\"),\n                Self::PackOptions => Some(\"PackOptions\"),\n                Self::LogicalOrOptions => Some(\"LogicalOrOptions\"),\n                Self::OneHotOptions => Some(\"OneHotOptions\"),\n                Self::LogicalAndOptions => Some(\"LogicalAndOptions\"),\n                Self::LogicalNotOptions => Some(\"LogicalNotOptions\"),\n                Self::UnpackOptions => Some(\"UnpackOptions\"),\n                Self::FloorDivOptions => Some(\"FloorDivOptions\"),\n                Self::SquareOptions => Some(\"SquareOptions\"),\n                Self::ZerosLikeOptions => Some(\"ZerosLikeOptions\"),\n                Self::FillOptions => Some(\"FillOptions\"),\n                Self::BidirectionalSequenceLSTMOptions => Some(\"BidirectionalSequenceLSTMOptions\"),\n                Self::BidirectionalSequenceRNNOptions => Some(\"BidirectionalSequenceRNNOptions\"),\n                Self::UnidirectionalSequenceLSTMOptions => {\n                    Some(\"UnidirectionalSequenceLSTMOptions\")\n                }\n                Self::FloorModOptions => Some(\"FloorModOptions\"),\n                Self::RangeOptions => Some(\"RangeOptions\"),\n                Self::ResizeNearestNeighborOptions => Some(\"ResizeNearestNeighborOptions\"),\n                Self::LeakyReluOptions => Some(\"LeakyReluOptions\"),\n                Self::SquaredDifferenceOptions => Some(\"SquaredDifferenceOptions\"),\n                Self::MirrorPadOptions => Some(\"MirrorPadOptions\"),\n                Self::AbsOptions => Some(\"AbsOptions\"),\n                Self::SplitVOptions => Some(\"SplitVOptions\"),\n                Self::UniqueOptions => Some(\"UniqueOptions\"),\n                Self::ReverseV2Options => Some(\"ReverseV2Options\"),\n                Self::AddNOptions => Some(\"AddNOptions\"),\n                Self::GatherNdOptions => Some(\"GatherNdOptions\"),\n                Self::CosOptions => Some(\"CosOptions\"),\n                Self::WhereOptions => Some(\"WhereOptions\"),\n                Self::RankOptions => Some(\"RankOptions\"),\n                Self::ReverseSequenceOptions => Some(\"ReverseSequenceOptions\"),\n                Self::MatrixDiagOptions => Some(\"MatrixDiagOptions\"),\n                Self::QuantizeOptions => Some(\"QuantizeOptions\"),\n                Self::MatrixSetDiagOptions => Some(\"MatrixSetDiagOptions\"),\n                Self::HardSwishOptions => Some(\"HardSwishOptions\"),\n                Self::IfOptions => Some(\"IfOptions\"),\n                Self::WhileOptions => Some(\"WhileOptions\"),\n                Self::DepthToSpaceOptions => Some(\"DepthToSpaceOptions\"),\n                Self::NonMaxSuppressionV4Options => Some(\"NonMaxSuppressionV4Options\"),\n                Self::NonMaxSuppressionV5Options => Some(\"NonMaxSuppressionV5Options\"),\n                Self::ScatterNdOptions => Some(\"ScatterNdOptions\"),\n                Self::SelectV2Options => Some(\"SelectV2Options\"),\n                Self::DensifyOptions => Some(\"DensifyOptions\"),\n                Self::SegmentSumOptions => Some(\"SegmentSumOptions\"),\n                Self::BatchMatMulOptions => Some(\"BatchMatMulOptions\"),\n                Self::CumsumOptions => Some(\"CumsumOptions\"),\n                Self::CallOnceOptions => Some(\"CallOnceOptions\"),\n                Self::BroadcastToOptions => Some(\"BroadcastToOptions\"),\n                Self::Rfft2dOptions => Some(\"Rfft2dOptions\"),\n                Self::Conv3DOptions => Some(\"Conv3DOptions\"),\n                Self::HashtableOptions => Some(\"HashtableOptions\"),\n                Self::HashtableFindOptions => Some(\"HashtableFindOptions\"),\n                Self::HashtableImportOptions => Some(\"HashtableImportOptions\"),\n                Self::HashtableSizeOptions => Some(\"HashtableSizeOptions\"),\n                Self::VarHandleOptions => Some(\"VarHandleOptions\"),\n                Self::ReadVariableOptions => Some(\"ReadVariableOptions\"),\n                Self::AssignVariableOptions => Some(\"AssignVariableOptions\"),\n                Self::RandomOptions => Some(\"RandomOptions\"),\n                Self::BucketizeOptions => Some(\"BucketizeOptions\"),\n                Self::GeluOptions => Some(\"GeluOptions\"),\n                Self::DynamicUpdateSliceOptions => Some(\"DynamicUpdateSliceOptions\"),\n                Self::UnsortedSegmentProdOptions => Some(\"UnsortedSegmentProdOptions\"),\n                Self::UnsortedSegmentMaxOptions => Some(\"UnsortedSegmentMaxOptions\"),\n                Self::UnsortedSegmentMinOptions => Some(\"UnsortedSegmentMinOptions\"),\n                Self::UnsortedSegmentSumOptions => Some(\"UnsortedSegmentSumOptions\"),\n                Self::ATan2Options => Some(\"ATan2Options\"),\n                Self::SignOptions => Some(\"SignOptions\"),\n                Self::BitcastOptions => Some(\"BitcastOptions\"),\n                Self::BitwiseXorOptions => Some(\"BitwiseXorOptions\"),\n                Self::RightShiftOptions => Some(\"RightShiftOptions\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for BuiltinOptions {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for BuiltinOptions {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<u8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for BuiltinOptions {\n        type Output = BuiltinOptions;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<u8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for BuiltinOptions {\n        type Scalar = u8;\n        #[inline]\n        fn to_little_endian(self) -> u8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: u8) -> Self {\n            let b = u8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for BuiltinOptions {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            u8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for BuiltinOptions {}\n    pub struct BuiltinOptionsUnionTableOffset {}\n\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_PADDING: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_PADDING: i8 = 1;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_PADDING: [Padding; 2] = [Padding::SAME, Padding::VALID];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct Padding(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl Padding {\n        pub const SAME: Self = Self(0);\n        pub const VALID: Self = Self(1);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 1;\n        pub const ENUM_VALUES: &'static [Self] = &[Self::SAME, Self::VALID];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::SAME => Some(\"SAME\"),\n                Self::VALID => Some(\"VALID\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for Padding {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for Padding {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for Padding {\n        type Output = Padding;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for Padding {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for Padding {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for Padding {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_ACTIVATION_FUNCTION_TYPE: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_ACTIVATION_FUNCTION_TYPE: i8 = 5;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_ACTIVATION_FUNCTION_TYPE: [ActivationFunctionType; 6] = [\n        ActivationFunctionType::NONE,\n        ActivationFunctionType::RELU,\n        ActivationFunctionType::RELU_N1_TO_1,\n        ActivationFunctionType::RELU6,\n        ActivationFunctionType::TANH,\n        ActivationFunctionType::SIGN_BIT,\n    ];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct ActivationFunctionType(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl ActivationFunctionType {\n        pub const NONE: Self = Self(0);\n        pub const RELU: Self = Self(1);\n        pub const RELU_N1_TO_1: Self = Self(2);\n        pub const RELU6: Self = Self(3);\n        pub const TANH: Self = Self(4);\n        pub const SIGN_BIT: Self = Self(5);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 5;\n        pub const ENUM_VALUES: &'static [Self] =\n            &[Self::NONE, Self::RELU, Self::RELU_N1_TO_1, Self::RELU6, Self::TANH, Self::SIGN_BIT];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::NONE => Some(\"NONE\"),\n                Self::RELU => Some(\"RELU\"),\n                Self::RELU_N1_TO_1 => Some(\"RELU_N1_TO_1\"),\n                Self::RELU6 => Some(\"RELU6\"),\n                Self::TANH => Some(\"TANH\"),\n                Self::SIGN_BIT => Some(\"SIGN_BIT\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for ActivationFunctionType {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for ActivationFunctionType {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for ActivationFunctionType {\n        type Output = ActivationFunctionType;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for ActivationFunctionType {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for ActivationFunctionType {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for ActivationFunctionType {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_LSHPROJECTION_TYPE: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_LSHPROJECTION_TYPE: i8 = 2;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_LSHPROJECTION_TYPE: [LSHProjectionType; 3] =\n        [LSHProjectionType::UNKNOWN, LSHProjectionType::SPARSE, LSHProjectionType::DENSE];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct LSHProjectionType(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl LSHProjectionType {\n        pub const UNKNOWN: Self = Self(0);\n        pub const SPARSE: Self = Self(1);\n        pub const DENSE: Self = Self(2);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 2;\n        pub const ENUM_VALUES: &'static [Self] = &[Self::UNKNOWN, Self::SPARSE, Self::DENSE];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::UNKNOWN => Some(\"UNKNOWN\"),\n                Self::SPARSE => Some(\"SPARSE\"),\n                Self::DENSE => Some(\"DENSE\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for LSHProjectionType {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for LSHProjectionType {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for LSHProjectionType {\n        type Output = LSHProjectionType;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for LSHProjectionType {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for LSHProjectionType {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for LSHProjectionType {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_FULLY_CONNECTED_OPTIONS_WEIGHTS_FORMAT: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_FULLY_CONNECTED_OPTIONS_WEIGHTS_FORMAT: i8 = 1;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_FULLY_CONNECTED_OPTIONS_WEIGHTS_FORMAT:\n        [FullyConnectedOptionsWeightsFormat; 2] = [\n        FullyConnectedOptionsWeightsFormat::DEFAULT,\n        FullyConnectedOptionsWeightsFormat::SHUFFLED4x16INT8,\n    ];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct FullyConnectedOptionsWeightsFormat(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl FullyConnectedOptionsWeightsFormat {\n        pub const DEFAULT: Self = Self(0);\n        pub const SHUFFLED4x16INT8: Self = Self(1);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 1;\n        pub const ENUM_VALUES: &'static [Self] = &[Self::DEFAULT, Self::SHUFFLED4x16INT8];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::DEFAULT => Some(\"DEFAULT\"),\n                Self::SHUFFLED4x16INT8 => Some(\"SHUFFLED4x16INT8\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for FullyConnectedOptionsWeightsFormat {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for FullyConnectedOptionsWeightsFormat {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for FullyConnectedOptionsWeightsFormat {\n        type Output = FullyConnectedOptionsWeightsFormat;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for FullyConnectedOptionsWeightsFormat {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for FullyConnectedOptionsWeightsFormat {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for FullyConnectedOptionsWeightsFormat {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_LSTMKERNEL_TYPE: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_LSTMKERNEL_TYPE: i8 = 1;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_LSTMKERNEL_TYPE: [LSTMKernelType; 2] =\n        [LSTMKernelType::FULL, LSTMKernelType::BASIC];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct LSTMKernelType(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl LSTMKernelType {\n        pub const FULL: Self = Self(0);\n        pub const BASIC: Self = Self(1);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 1;\n        pub const ENUM_VALUES: &'static [Self] = &[Self::FULL, Self::BASIC];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::FULL => Some(\"FULL\"),\n                Self::BASIC => Some(\"BASIC\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for LSTMKernelType {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for LSTMKernelType {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for LSTMKernelType {\n        type Output = LSTMKernelType;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for LSTMKernelType {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for LSTMKernelType {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for LSTMKernelType {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_COMBINER_TYPE: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_COMBINER_TYPE: i8 = 2;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_COMBINER_TYPE: [CombinerType; 3] =\n        [CombinerType::SUM, CombinerType::MEAN, CombinerType::SQRTN];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct CombinerType(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl CombinerType {\n        pub const SUM: Self = Self(0);\n        pub const MEAN: Self = Self(1);\n        pub const SQRTN: Self = Self(2);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 2;\n        pub const ENUM_VALUES: &'static [Self] = &[Self::SUM, Self::MEAN, Self::SQRTN];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::SUM => Some(\"SUM\"),\n                Self::MEAN => Some(\"MEAN\"),\n                Self::SQRTN => Some(\"SQRTN\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for CombinerType {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for CombinerType {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for CombinerType {\n        type Output = CombinerType;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for CombinerType {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for CombinerType {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for CombinerType {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_MIRROR_PAD_MODE: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_MIRROR_PAD_MODE: i8 = 1;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_MIRROR_PAD_MODE: [MirrorPadMode; 2] =\n        [MirrorPadMode::REFLECT, MirrorPadMode::SYMMETRIC];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct MirrorPadMode(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl MirrorPadMode {\n        pub const REFLECT: Self = Self(0);\n        pub const SYMMETRIC: Self = Self(1);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 1;\n        pub const ENUM_VALUES: &'static [Self] = &[Self::REFLECT, Self::SYMMETRIC];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::REFLECT => Some(\"REFLECT\"),\n                Self::SYMMETRIC => Some(\"SYMMETRIC\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for MirrorPadMode {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for MirrorPadMode {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for MirrorPadMode {\n        type Output = MirrorPadMode;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for MirrorPadMode {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for MirrorPadMode {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for MirrorPadMode {}\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MIN_CUSTOM_OPTIONS_FORMAT: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    pub const ENUM_MAX_CUSTOM_OPTIONS_FORMAT: i8 = 0;\n    #[deprecated(\n        since = \"2.0.0\",\n        note = \"Use associated constants instead. This will no longer be generated in 2021.\"\n    )]\n    #[allow(non_camel_case_types)]\n    pub const ENUM_VALUES_CUSTOM_OPTIONS_FORMAT: [CustomOptionsFormat; 1] =\n        [CustomOptionsFormat::FLEXBUFFERS];\n\n    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]\n    #[repr(transparent)]\n    pub struct CustomOptionsFormat(pub i8);\n    #[allow(non_upper_case_globals)]\n    impl CustomOptionsFormat {\n        pub const FLEXBUFFERS: Self = Self(0);\n\n        pub const ENUM_MIN: i8 = 0;\n        pub const ENUM_MAX: i8 = 0;\n        pub const ENUM_VALUES: &'static [Self] = &[Self::FLEXBUFFERS];\n        /// Returns the variant's name or \"\" if unknown.\n        pub fn variant_name(self) -> Option<&'static str> {\n            match self {\n                Self::FLEXBUFFERS => Some(\"FLEXBUFFERS\"),\n                _ => None,\n            }\n        }\n    }\n    impl core::fmt::Debug for CustomOptionsFormat {\n        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {\n            if let Some(name) = self.variant_name() {\n                f.write_str(name)\n            } else {\n                f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))\n            }\n        }\n    }\n    impl<'a> flatbuffers::Follow<'a> for CustomOptionsFormat {\n        type Inner = Self;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            let b = flatbuffers::read_scalar_at::<i8>(buf, loc);\n            Self(b)\n        }\n    }\n\n    impl flatbuffers::Push for CustomOptionsFormat {\n        type Output = CustomOptionsFormat;\n        #[inline]\n        unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {\n            flatbuffers::emplace_scalar::<i8>(dst, self.0);\n        }\n    }\n\n    impl flatbuffers::EndianScalar for CustomOptionsFormat {\n        type Scalar = i8;\n        #[inline]\n        fn to_little_endian(self) -> i8 {\n            self.0.to_le()\n        }\n        #[inline]\n        #[allow(clippy::wrong_self_convention)]\n        fn from_little_endian(v: i8) -> Self {\n            let b = i8::from_le(v);\n            Self(b)\n        }\n    }\n\n    impl<'a> flatbuffers::Verifiable for CustomOptionsFormat {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            i8::run_verifier(v, pos)\n        }\n    }\n\n    impl flatbuffers::SimpleToVerifyInSlice for CustomOptionsFormat {}\n    pub enum CustomQuantizationOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct CustomQuantization<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for CustomQuantization<'a> {\n        type Inner = CustomQuantization<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> CustomQuantization<'a> {\n        pub const VT_CUSTOM: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            CustomQuantization { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args CustomQuantizationArgs<'args>,\n        ) -> flatbuffers::WIPOffset<CustomQuantization<'bldr>> {\n            let mut builder = CustomQuantizationBuilder::new(_fbb);\n            if let Some(x) = args.custom {\n                builder.add_custom(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn custom(&self) -> Option<flatbuffers::Vector<'a, u8>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, u8>>>(\n                    CustomQuantization::VT_CUSTOM,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for CustomQuantization<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, u8>>>(\n                    \"custom\",\n                    Self::VT_CUSTOM,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct CustomQuantizationArgs<'a> {\n        pub custom: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, u8>>>,\n    }\n    impl<'a> Default for CustomQuantizationArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            CustomQuantizationArgs { custom: None }\n        }\n    }\n\n    pub struct CustomQuantizationBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> CustomQuantizationBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_custom(&mut self, custom: flatbuffers::WIPOffset<flatbuffers::Vector<'b, u8>>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                CustomQuantization::VT_CUSTOM,\n                custom,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> CustomQuantizationBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            CustomQuantizationBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<CustomQuantization<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for CustomQuantization<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"CustomQuantization\");\n            ds.field(\"custom\", &self.custom());\n            ds.finish()\n        }\n    }\n    pub enum QuantizationParametersOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct QuantizationParameters<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for QuantizationParameters<'a> {\n        type Inner = QuantizationParameters<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> QuantizationParameters<'a> {\n        pub const VT_MIN: flatbuffers::VOffsetT = 4;\n        pub const VT_MAX: flatbuffers::VOffsetT = 6;\n        pub const VT_SCALE: flatbuffers::VOffsetT = 8;\n        pub const VT_ZERO_POINT: flatbuffers::VOffsetT = 10;\n        pub const VT_DETAILS_TYPE: flatbuffers::VOffsetT = 12;\n        pub const VT_DETAILS: flatbuffers::VOffsetT = 14;\n        pub const VT_QUANTIZED_DIMENSION: flatbuffers::VOffsetT = 16;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            QuantizationParameters { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args QuantizationParametersArgs<'args>,\n        ) -> flatbuffers::WIPOffset<QuantizationParameters<'bldr>> {\n            let mut builder = QuantizationParametersBuilder::new(_fbb);\n            builder.add_quantized_dimension(args.quantized_dimension);\n            if let Some(x) = args.details {\n                builder.add_details(x);\n            }\n            if let Some(x) = args.zero_point {\n                builder.add_zero_point(x);\n            }\n            if let Some(x) = args.scale {\n                builder.add_scale(x);\n            }\n            if let Some(x) = args.max {\n                builder.add_max(x);\n            }\n            if let Some(x) = args.min {\n                builder.add_min(x);\n            }\n            builder.add_details_type(args.details_type);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn min(&self) -> Option<flatbuffers::Vector<'a, f32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, f32>>>(\n                    QuantizationParameters::VT_MIN,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn max(&self) -> Option<flatbuffers::Vector<'a, f32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, f32>>>(\n                    QuantizationParameters::VT_MAX,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn scale(&self) -> Option<flatbuffers::Vector<'a, f32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, f32>>>(\n                    QuantizationParameters::VT_SCALE,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn zero_point(&self) -> Option<flatbuffers::Vector<'a, i64>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i64>>>(\n                    QuantizationParameters::VT_ZERO_POINT,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn details_type(&self) -> QuantizationDetails {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<QuantizationDetails>(\n                        QuantizationParameters::VT_DETAILS_TYPE,\n                        Some(QuantizationDetails::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn details(&self) -> Option<flatbuffers::Table<'a>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Table<'a>>>(\n                    QuantizationParameters::VT_DETAILS,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn quantized_dimension(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<i32>(QuantizationParameters::VT_QUANTIZED_DIMENSION, Some(0))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn details_as_custom_quantization(&self) -> Option<CustomQuantization<'a>> {\n            if self.details_type() == QuantizationDetails::CustomQuantization {\n                self.details().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { CustomQuantization::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for QuantizationParameters<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, f32>>>(\n                    \"min\",\n                    Self::VT_MIN,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, f32>>>(\n                    \"max\",\n                    Self::VT_MAX,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, f32>>>(\n                    \"scale\",\n                    Self::VT_SCALE,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i64>>>(\n                    \"zero_point\",\n                    Self::VT_ZERO_POINT,\n                    false,\n                )?\n                .visit_union::<QuantizationDetails, _>(\n                    \"details_type\",\n                    Self::VT_DETAILS_TYPE,\n                    \"details\",\n                    Self::VT_DETAILS,\n                    false,\n                    |key, v, pos| {\n                        match key {\n                            QuantizationDetails::CustomQuantization => v\n                                .verify_union_variant::<flatbuffers::ForwardsUOffset<\n                                CustomQuantization,\n                            >>(\n                                \"QuantizationDetails::CustomQuantization\",\n                                pos,\n                            ),\n                            _ => Ok(()),\n                        }\n                    },\n                )?\n                .visit_field::<i32>(\"quantized_dimension\", Self::VT_QUANTIZED_DIMENSION, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct QuantizationParametersArgs<'a> {\n        pub min: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, f32>>>,\n        pub max: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, f32>>>,\n        pub scale: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, f32>>>,\n        pub zero_point: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i64>>>,\n        pub details_type: QuantizationDetails,\n        pub details: Option<flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>>,\n        pub quantized_dimension: i32,\n    }\n    impl<'a> Default for QuantizationParametersArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            QuantizationParametersArgs {\n                min: None,\n                max: None,\n                scale: None,\n                zero_point: None,\n                details_type: QuantizationDetails::NONE,\n                details: None,\n                quantized_dimension: 0,\n            }\n        }\n    }\n\n    pub struct QuantizationParametersBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> QuantizationParametersBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_min(&mut self, min: flatbuffers::WIPOffset<flatbuffers::Vector<'b, f32>>) {\n            self.fbb_\n                .push_slot_always::<flatbuffers::WIPOffset<_>>(QuantizationParameters::VT_MIN, min);\n        }\n        #[inline]\n        pub fn add_max(&mut self, max: flatbuffers::WIPOffset<flatbuffers::Vector<'b, f32>>) {\n            self.fbb_\n                .push_slot_always::<flatbuffers::WIPOffset<_>>(QuantizationParameters::VT_MAX, max);\n        }\n        #[inline]\n        pub fn add_scale(&mut self, scale: flatbuffers::WIPOffset<flatbuffers::Vector<'b, f32>>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                QuantizationParameters::VT_SCALE,\n                scale,\n            );\n        }\n        #[inline]\n        pub fn add_zero_point(\n            &mut self,\n            zero_point: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i64>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                QuantizationParameters::VT_ZERO_POINT,\n                zero_point,\n            );\n        }\n        #[inline]\n        pub fn add_details_type(&mut self, details_type: QuantizationDetails) {\n            self.fbb_.push_slot::<QuantizationDetails>(\n                QuantizationParameters::VT_DETAILS_TYPE,\n                details_type,\n                QuantizationDetails::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_details(\n            &mut self,\n            details: flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                QuantizationParameters::VT_DETAILS,\n                details,\n            );\n        }\n        #[inline]\n        pub fn add_quantized_dimension(&mut self, quantized_dimension: i32) {\n            self.fbb_.push_slot::<i32>(\n                QuantizationParameters::VT_QUANTIZED_DIMENSION,\n                quantized_dimension,\n                0,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> QuantizationParametersBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            QuantizationParametersBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<QuantizationParameters<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for QuantizationParameters<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"QuantizationParameters\");\n            ds.field(\"min\", &self.min());\n            ds.field(\"max\", &self.max());\n            ds.field(\"scale\", &self.scale());\n            ds.field(\"zero_point\", &self.zero_point());\n            ds.field(\"details_type\", &self.details_type());\n            match self.details_type() {\n                QuantizationDetails::CustomQuantization => {\n                    if let Some(x) = self.details_as_custom_quantization() {\n                        ds.field(\"details\", &x)\n                    } else {\n                        ds.field(\n                            \"details\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                _ => {\n                    let x: Option<()> = None;\n                    ds.field(\"details\", &x)\n                }\n            };\n            ds.field(\"quantized_dimension\", &self.quantized_dimension());\n            ds.finish()\n        }\n    }\n    pub enum Int32VectorOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Int32Vector<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Int32Vector<'a> {\n        type Inner = Int32Vector<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Int32Vector<'a> {\n        pub const VT_VALUES: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Int32Vector { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args Int32VectorArgs<'args>,\n        ) -> flatbuffers::WIPOffset<Int32Vector<'bldr>> {\n            let mut builder = Int32VectorBuilder::new(_fbb);\n            if let Some(x) = args.values {\n                builder.add_values(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn values(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    Int32Vector::VT_VALUES,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Int32Vector<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"values\",\n                    Self::VT_VALUES,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct Int32VectorArgs<'a> {\n        pub values: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n    }\n    impl<'a> Default for Int32VectorArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            Int32VectorArgs { values: None }\n        }\n    }\n\n    pub struct Int32VectorBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> Int32VectorBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_values(&mut self, values: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Int32Vector::VT_VALUES, values);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> Int32VectorBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            Int32VectorBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Int32Vector<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Int32Vector<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Int32Vector\");\n            ds.field(\"values\", &self.values());\n            ds.finish()\n        }\n    }\n    pub enum Uint16VectorOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Uint16Vector<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Uint16Vector<'a> {\n        type Inner = Uint16Vector<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Uint16Vector<'a> {\n        pub const VT_VALUES: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Uint16Vector { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args Uint16VectorArgs<'args>,\n        ) -> flatbuffers::WIPOffset<Uint16Vector<'bldr>> {\n            let mut builder = Uint16VectorBuilder::new(_fbb);\n            if let Some(x) = args.values {\n                builder.add_values(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn values(&self) -> Option<flatbuffers::Vector<'a, u16>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, u16>>>(\n                    Uint16Vector::VT_VALUES,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Uint16Vector<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, u16>>>(\n                    \"values\",\n                    Self::VT_VALUES,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct Uint16VectorArgs<'a> {\n        pub values: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, u16>>>,\n    }\n    impl<'a> Default for Uint16VectorArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            Uint16VectorArgs { values: None }\n        }\n    }\n\n    pub struct Uint16VectorBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> Uint16VectorBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_values(&mut self, values: flatbuffers::WIPOffset<flatbuffers::Vector<'b, u16>>) {\n            self.fbb_\n                .push_slot_always::<flatbuffers::WIPOffset<_>>(Uint16Vector::VT_VALUES, values);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> Uint16VectorBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            Uint16VectorBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Uint16Vector<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Uint16Vector<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Uint16Vector\");\n            ds.field(\"values\", &self.values());\n            ds.finish()\n        }\n    }\n    pub enum Uint8VectorOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Uint8Vector<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Uint8Vector<'a> {\n        type Inner = Uint8Vector<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Uint8Vector<'a> {\n        pub const VT_VALUES: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Uint8Vector { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args Uint8VectorArgs<'args>,\n        ) -> flatbuffers::WIPOffset<Uint8Vector<'bldr>> {\n            let mut builder = Uint8VectorBuilder::new(_fbb);\n            if let Some(x) = args.values {\n                builder.add_values(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn values(&self) -> Option<flatbuffers::Vector<'a, u8>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, u8>>>(\n                    Uint8Vector::VT_VALUES,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Uint8Vector<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, u8>>>(\n                    \"values\",\n                    Self::VT_VALUES,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct Uint8VectorArgs<'a> {\n        pub values: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, u8>>>,\n    }\n    impl<'a> Default for Uint8VectorArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            Uint8VectorArgs { values: None }\n        }\n    }\n\n    pub struct Uint8VectorBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> Uint8VectorBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_values(&mut self, values: flatbuffers::WIPOffset<flatbuffers::Vector<'b, u8>>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Uint8Vector::VT_VALUES, values);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> Uint8VectorBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            Uint8VectorBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Uint8Vector<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Uint8Vector<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Uint8Vector\");\n            ds.field(\"values\", &self.values());\n            ds.finish()\n        }\n    }\n    pub enum DimensionMetadataOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct DimensionMetadata<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for DimensionMetadata<'a> {\n        type Inner = DimensionMetadata<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> DimensionMetadata<'a> {\n        pub const VT_FORMAT: flatbuffers::VOffsetT = 4;\n        pub const VT_DENSE_SIZE: flatbuffers::VOffsetT = 6;\n        pub const VT_ARRAY_SEGMENTS_TYPE: flatbuffers::VOffsetT = 8;\n        pub const VT_ARRAY_SEGMENTS: flatbuffers::VOffsetT = 10;\n        pub const VT_ARRAY_INDICES_TYPE: flatbuffers::VOffsetT = 12;\n        pub const VT_ARRAY_INDICES: flatbuffers::VOffsetT = 14;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            DimensionMetadata { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args DimensionMetadataArgs,\n        ) -> flatbuffers::WIPOffset<DimensionMetadata<'bldr>> {\n            let mut builder = DimensionMetadataBuilder::new(_fbb);\n            if let Some(x) = args.array_indices {\n                builder.add_array_indices(x);\n            }\n            if let Some(x) = args.array_segments {\n                builder.add_array_segments(x);\n            }\n            builder.add_dense_size(args.dense_size);\n            builder.add_array_indices_type(args.array_indices_type);\n            builder.add_array_segments_type(args.array_segments_type);\n            builder.add_format(args.format);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn format(&self) -> DimensionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<DimensionType>(DimensionMetadata::VT_FORMAT, Some(DimensionType::DENSE))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn dense_size(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(DimensionMetadata::VT_DENSE_SIZE, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn array_segments_type(&self) -> SparseIndexVector {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<SparseIndexVector>(\n                        DimensionMetadata::VT_ARRAY_SEGMENTS_TYPE,\n                        Some(SparseIndexVector::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn array_segments(&self) -> Option<flatbuffers::Table<'a>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Table<'a>>>(\n                    DimensionMetadata::VT_ARRAY_SEGMENTS,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn array_indices_type(&self) -> SparseIndexVector {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<SparseIndexVector>(\n                        DimensionMetadata::VT_ARRAY_INDICES_TYPE,\n                        Some(SparseIndexVector::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn array_indices(&self) -> Option<flatbuffers::Table<'a>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Table<'a>>>(\n                    DimensionMetadata::VT_ARRAY_INDICES,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn array_segments_as_int_32_vector(&self) -> Option<Int32Vector<'a>> {\n            if self.array_segments_type() == SparseIndexVector::Int32Vector {\n                self.array_segments().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Int32Vector::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn array_segments_as_uint_16_vector(&self) -> Option<Uint16Vector<'a>> {\n            if self.array_segments_type() == SparseIndexVector::Uint16Vector {\n                self.array_segments().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Uint16Vector::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn array_segments_as_uint_8_vector(&self) -> Option<Uint8Vector<'a>> {\n            if self.array_segments_type() == SparseIndexVector::Uint8Vector {\n                self.array_segments().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Uint8Vector::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn array_indices_as_int_32_vector(&self) -> Option<Int32Vector<'a>> {\n            if self.array_indices_type() == SparseIndexVector::Int32Vector {\n                self.array_indices().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Int32Vector::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn array_indices_as_uint_16_vector(&self) -> Option<Uint16Vector<'a>> {\n            if self.array_indices_type() == SparseIndexVector::Uint16Vector {\n                self.array_indices().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Uint16Vector::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn array_indices_as_uint_8_vector(&self) -> Option<Uint8Vector<'a>> {\n            if self.array_indices_type() == SparseIndexVector::Uint8Vector {\n                self.array_indices().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Uint8Vector::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for DimensionMetadata<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<DimensionType>(\"format\", Self::VT_FORMAT, false)?\n                .visit_field::<i32>(\"dense_size\", Self::VT_DENSE_SIZE, false)?\n                .visit_union::<SparseIndexVector, _>(\n                    \"array_segments_type\",\n                    Self::VT_ARRAY_SEGMENTS_TYPE,\n                    \"array_segments\",\n                    Self::VT_ARRAY_SEGMENTS,\n                    false,\n                    |key, v, pos| match key {\n                        SparseIndexVector::Int32Vector => v\n                            .verify_union_variant::<flatbuffers::ForwardsUOffset<Int32Vector>>(\n                                \"SparseIndexVector::Int32Vector\",\n                                pos,\n                            ),\n                        SparseIndexVector::Uint16Vector => v\n                            .verify_union_variant::<flatbuffers::ForwardsUOffset<Uint16Vector>>(\n                                \"SparseIndexVector::Uint16Vector\",\n                                pos,\n                            ),\n                        SparseIndexVector::Uint8Vector => v\n                            .verify_union_variant::<flatbuffers::ForwardsUOffset<Uint8Vector>>(\n                                \"SparseIndexVector::Uint8Vector\",\n                                pos,\n                            ),\n                        _ => Ok(()),\n                    },\n                )?\n                .visit_union::<SparseIndexVector, _>(\n                    \"array_indices_type\",\n                    Self::VT_ARRAY_INDICES_TYPE,\n                    \"array_indices\",\n                    Self::VT_ARRAY_INDICES,\n                    false,\n                    |key, v, pos| match key {\n                        SparseIndexVector::Int32Vector => v\n                            .verify_union_variant::<flatbuffers::ForwardsUOffset<Int32Vector>>(\n                                \"SparseIndexVector::Int32Vector\",\n                                pos,\n                            ),\n                        SparseIndexVector::Uint16Vector => v\n                            .verify_union_variant::<flatbuffers::ForwardsUOffset<Uint16Vector>>(\n                                \"SparseIndexVector::Uint16Vector\",\n                                pos,\n                            ),\n                        SparseIndexVector::Uint8Vector => v\n                            .verify_union_variant::<flatbuffers::ForwardsUOffset<Uint8Vector>>(\n                                \"SparseIndexVector::Uint8Vector\",\n                                pos,\n                            ),\n                        _ => Ok(()),\n                    },\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct DimensionMetadataArgs {\n        pub format: DimensionType,\n        pub dense_size: i32,\n        pub array_segments_type: SparseIndexVector,\n        pub array_segments: Option<flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>>,\n        pub array_indices_type: SparseIndexVector,\n        pub array_indices: Option<flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>>,\n    }\n    impl<'a> Default for DimensionMetadataArgs {\n        #[inline]\n        fn default() -> Self {\n            DimensionMetadataArgs {\n                format: DimensionType::DENSE,\n                dense_size: 0,\n                array_segments_type: SparseIndexVector::NONE,\n                array_segments: None,\n                array_indices_type: SparseIndexVector::NONE,\n                array_indices: None,\n            }\n        }\n    }\n\n    pub struct DimensionMetadataBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> DimensionMetadataBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_format(&mut self, format: DimensionType) {\n            self.fbb_.push_slot::<DimensionType>(\n                DimensionMetadata::VT_FORMAT,\n                format,\n                DimensionType::DENSE,\n            );\n        }\n        #[inline]\n        pub fn add_dense_size(&mut self, dense_size: i32) {\n            self.fbb_.push_slot::<i32>(DimensionMetadata::VT_DENSE_SIZE, dense_size, 0);\n        }\n        #[inline]\n        pub fn add_array_segments_type(&mut self, array_segments_type: SparseIndexVector) {\n            self.fbb_.push_slot::<SparseIndexVector>(\n                DimensionMetadata::VT_ARRAY_SEGMENTS_TYPE,\n                array_segments_type,\n                SparseIndexVector::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_array_segments(\n            &mut self,\n            array_segments: flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                DimensionMetadata::VT_ARRAY_SEGMENTS,\n                array_segments,\n            );\n        }\n        #[inline]\n        pub fn add_array_indices_type(&mut self, array_indices_type: SparseIndexVector) {\n            self.fbb_.push_slot::<SparseIndexVector>(\n                DimensionMetadata::VT_ARRAY_INDICES_TYPE,\n                array_indices_type,\n                SparseIndexVector::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_array_indices(\n            &mut self,\n            array_indices: flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                DimensionMetadata::VT_ARRAY_INDICES,\n                array_indices,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> DimensionMetadataBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            DimensionMetadataBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<DimensionMetadata<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for DimensionMetadata<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"DimensionMetadata\");\n            ds.field(\"format\", &self.format());\n            ds.field(\"dense_size\", &self.dense_size());\n            ds.field(\"array_segments_type\", &self.array_segments_type());\n            match self.array_segments_type() {\n                SparseIndexVector::Int32Vector => {\n                    if let Some(x) = self.array_segments_as_int_32_vector() {\n                        ds.field(\"array_segments\", &x)\n                    } else {\n                        ds.field(\n                            \"array_segments\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                SparseIndexVector::Uint16Vector => {\n                    if let Some(x) = self.array_segments_as_uint_16_vector() {\n                        ds.field(\"array_segments\", &x)\n                    } else {\n                        ds.field(\n                            \"array_segments\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                SparseIndexVector::Uint8Vector => {\n                    if let Some(x) = self.array_segments_as_uint_8_vector() {\n                        ds.field(\"array_segments\", &x)\n                    } else {\n                        ds.field(\n                            \"array_segments\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                _ => {\n                    let x: Option<()> = None;\n                    ds.field(\"array_segments\", &x)\n                }\n            };\n            ds.field(\"array_indices_type\", &self.array_indices_type());\n            match self.array_indices_type() {\n                SparseIndexVector::Int32Vector => {\n                    if let Some(x) = self.array_indices_as_int_32_vector() {\n                        ds.field(\"array_indices\", &x)\n                    } else {\n                        ds.field(\n                            \"array_indices\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                SparseIndexVector::Uint16Vector => {\n                    if let Some(x) = self.array_indices_as_uint_16_vector() {\n                        ds.field(\"array_indices\", &x)\n                    } else {\n                        ds.field(\n                            \"array_indices\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                SparseIndexVector::Uint8Vector => {\n                    if let Some(x) = self.array_indices_as_uint_8_vector() {\n                        ds.field(\"array_indices\", &x)\n                    } else {\n                        ds.field(\n                            \"array_indices\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                _ => {\n                    let x: Option<()> = None;\n                    ds.field(\"array_indices\", &x)\n                }\n            };\n            ds.finish()\n        }\n    }\n    pub enum SparsityParametersOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SparsityParameters<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SparsityParameters<'a> {\n        type Inner = SparsityParameters<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SparsityParameters<'a> {\n        pub const VT_TRAVERSAL_ORDER: flatbuffers::VOffsetT = 4;\n        pub const VT_BLOCK_MAP: flatbuffers::VOffsetT = 6;\n        pub const VT_DIM_METADATA: flatbuffers::VOffsetT = 8;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SparsityParameters { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SparsityParametersArgs<'args>,\n        ) -> flatbuffers::WIPOffset<SparsityParameters<'bldr>> {\n            let mut builder = SparsityParametersBuilder::new(_fbb);\n            if let Some(x) = args.dim_metadata {\n                builder.add_dim_metadata(x);\n            }\n            if let Some(x) = args.block_map {\n                builder.add_block_map(x);\n            }\n            if let Some(x) = args.traversal_order {\n                builder.add_traversal_order(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn traversal_order(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    SparsityParameters::VT_TRAVERSAL_ORDER,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn block_map(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    SparsityParameters::VT_BLOCK_MAP,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn dim_metadata(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<DimensionMetadata<'a>>>>\n        {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<DimensionMetadata>>,\n                >>(SparsityParameters::VT_DIM_METADATA, None)\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SparsityParameters<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"traversal_order\",\n                    Self::VT_TRAVERSAL_ORDER,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"block_map\",\n                    Self::VT_BLOCK_MAP,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<DimensionMetadata>>,\n                >>(\"dim_metadata\", Self::VT_DIM_METADATA, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SparsityParametersArgs<'a> {\n        pub traversal_order: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub block_map: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub dim_metadata: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<DimensionMetadata<'a>>>,\n            >,\n        >,\n    }\n    impl<'a> Default for SparsityParametersArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            SparsityParametersArgs { traversal_order: None, block_map: None, dim_metadata: None }\n        }\n    }\n\n    pub struct SparsityParametersBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SparsityParametersBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_traversal_order(\n            &mut self,\n            traversal_order: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                SparsityParameters::VT_TRAVERSAL_ORDER,\n                traversal_order,\n            );\n        }\n        #[inline]\n        pub fn add_block_map(\n            &mut self,\n            block_map: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                SparsityParameters::VT_BLOCK_MAP,\n                block_map,\n            );\n        }\n        #[inline]\n        pub fn add_dim_metadata(\n            &mut self,\n            dim_metadata: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<DimensionMetadata<'b>>>,\n            >,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                SparsityParameters::VT_DIM_METADATA,\n                dim_metadata,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SparsityParametersBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SparsityParametersBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SparsityParameters<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SparsityParameters<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SparsityParameters\");\n            ds.field(\"traversal_order\", &self.traversal_order());\n            ds.field(\"block_map\", &self.block_map());\n            ds.field(\"dim_metadata\", &self.dim_metadata());\n            ds.finish()\n        }\n    }\n    pub enum VariantSubTypeOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct VariantSubType<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for VariantSubType<'a> {\n        type Inner = VariantSubType<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> VariantSubType<'a> {\n        pub const VT_SHAPE: flatbuffers::VOffsetT = 4;\n        pub const VT_TYPE_: flatbuffers::VOffsetT = 6;\n        pub const VT_HAS_RANK: flatbuffers::VOffsetT = 8;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            VariantSubType { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args VariantSubTypeArgs<'args>,\n        ) -> flatbuffers::WIPOffset<VariantSubType<'bldr>> {\n            let mut builder = VariantSubTypeBuilder::new(_fbb);\n            if let Some(x) = args.shape {\n                builder.add_shape(x);\n            }\n            builder.add_has_rank(args.has_rank);\n            builder.add_type_(args.type_);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn shape(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    VariantSubType::VT_SHAPE,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn type_(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<TensorType>(VariantSubType::VT_TYPE_, Some(TensorType::FLOAT32))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn has_rank(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(VariantSubType::VT_HAS_RANK, Some(false)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for VariantSubType<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"shape\",\n                    Self::VT_SHAPE,\n                    false,\n                )?\n                .visit_field::<TensorType>(\"type_\", Self::VT_TYPE_, false)?\n                .visit_field::<bool>(\"has_rank\", Self::VT_HAS_RANK, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct VariantSubTypeArgs<'a> {\n        pub shape: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub type_: TensorType,\n        pub has_rank: bool,\n    }\n    impl<'a> Default for VariantSubTypeArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            VariantSubTypeArgs { shape: None, type_: TensorType::FLOAT32, has_rank: false }\n        }\n    }\n\n    pub struct VariantSubTypeBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> VariantSubTypeBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_shape(&mut self, shape: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>) {\n            self.fbb_\n                .push_slot_always::<flatbuffers::WIPOffset<_>>(VariantSubType::VT_SHAPE, shape);\n        }\n        #[inline]\n        pub fn add_type_(&mut self, type_: TensorType) {\n            self.fbb_.push_slot::<TensorType>(VariantSubType::VT_TYPE_, type_, TensorType::FLOAT32);\n        }\n        #[inline]\n        pub fn add_has_rank(&mut self, has_rank: bool) {\n            self.fbb_.push_slot::<bool>(VariantSubType::VT_HAS_RANK, has_rank, false);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> VariantSubTypeBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            VariantSubTypeBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<VariantSubType<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for VariantSubType<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"VariantSubType\");\n            ds.field(\"shape\", &self.shape());\n            ds.field(\"type_\", &self.type_());\n            ds.field(\"has_rank\", &self.has_rank());\n            ds.finish()\n        }\n    }\n    pub enum TensorOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Tensor<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Tensor<'a> {\n        type Inner = Tensor<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Tensor<'a> {\n        pub const VT_SHAPE: flatbuffers::VOffsetT = 4;\n        pub const VT_TYPE_: flatbuffers::VOffsetT = 6;\n        pub const VT_BUFFER: flatbuffers::VOffsetT = 8;\n        pub const VT_NAME: flatbuffers::VOffsetT = 10;\n        pub const VT_QUANTIZATION: flatbuffers::VOffsetT = 12;\n        pub const VT_IS_VARIABLE: flatbuffers::VOffsetT = 14;\n        pub const VT_SPARSITY: flatbuffers::VOffsetT = 16;\n        pub const VT_SHAPE_SIGNATURE: flatbuffers::VOffsetT = 18;\n        pub const VT_HAS_RANK: flatbuffers::VOffsetT = 20;\n        pub const VT_VARIANT_TENSORS: flatbuffers::VOffsetT = 22;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Tensor { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args TensorArgs<'args>,\n        ) -> flatbuffers::WIPOffset<Tensor<'bldr>> {\n            let mut builder = TensorBuilder::new(_fbb);\n            if let Some(x) = args.variant_tensors {\n                builder.add_variant_tensors(x);\n            }\n            if let Some(x) = args.shape_signature {\n                builder.add_shape_signature(x);\n            }\n            if let Some(x) = args.sparsity {\n                builder.add_sparsity(x);\n            }\n            if let Some(x) = args.quantization {\n                builder.add_quantization(x);\n            }\n            if let Some(x) = args.name {\n                builder.add_name(x);\n            }\n            builder.add_buffer(args.buffer);\n            if let Some(x) = args.shape {\n                builder.add_shape(x);\n            }\n            builder.add_has_rank(args.has_rank);\n            builder.add_is_variable(args.is_variable);\n            builder.add_type_(args.type_);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn shape(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    Tensor::VT_SHAPE,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn type_(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<TensorType>(Tensor::VT_TYPE_, Some(TensorType::FLOAT32)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn buffer(&self) -> u32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<u32>(Tensor::VT_BUFFER, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn name(&self) -> Option<&'a str> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<flatbuffers::ForwardsUOffset<&str>>(Tensor::VT_NAME, None) }\n        }\n        #[inline]\n        pub fn quantization(&self) -> Option<QuantizationParameters<'a>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<QuantizationParameters>>(\n                    Tensor::VT_QUANTIZATION,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn is_variable(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(Tensor::VT_IS_VARIABLE, Some(false)).unwrap() }\n        }\n        #[inline]\n        pub fn sparsity(&self) -> Option<SparsityParameters<'a>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<SparsityParameters>>(\n                    Tensor::VT_SPARSITY,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn shape_signature(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    Tensor::VT_SHAPE_SIGNATURE,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn has_rank(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(Tensor::VT_HAS_RANK, Some(false)).unwrap() }\n        }\n        #[inline]\n        pub fn variant_tensors(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<VariantSubType<'a>>>>\n        {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<VariantSubType>>,\n                >>(Tensor::VT_VARIANT_TENSORS, None)\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Tensor<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"shape\",\n                    Self::VT_SHAPE,\n                    false,\n                )?\n                .visit_field::<TensorType>(\"type_\", Self::VT_TYPE_, false)?\n                .visit_field::<u32>(\"buffer\", Self::VT_BUFFER, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<&str>>(\"name\", Self::VT_NAME, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<QuantizationParameters>>(\n                    \"quantization\",\n                    Self::VT_QUANTIZATION,\n                    false,\n                )?\n                .visit_field::<bool>(\"is_variable\", Self::VT_IS_VARIABLE, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<SparsityParameters>>(\n                    \"sparsity\",\n                    Self::VT_SPARSITY,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"shape_signature\",\n                    Self::VT_SHAPE_SIGNATURE,\n                    false,\n                )?\n                .visit_field::<bool>(\"has_rank\", Self::VT_HAS_RANK, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<VariantSubType>>,\n                >>(\"variant_tensors\", Self::VT_VARIANT_TENSORS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct TensorArgs<'a> {\n        pub shape: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub type_: TensorType,\n        pub buffer: u32,\n        pub name: Option<flatbuffers::WIPOffset<&'a str>>,\n        pub quantization: Option<flatbuffers::WIPOffset<QuantizationParameters<'a>>>,\n        pub is_variable: bool,\n        pub sparsity: Option<flatbuffers::WIPOffset<SparsityParameters<'a>>>,\n        pub shape_signature: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub has_rank: bool,\n        pub variant_tensors: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<VariantSubType<'a>>>,\n            >,\n        >,\n    }\n    impl<'a> Default for TensorArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            TensorArgs {\n                shape: None,\n                type_: TensorType::FLOAT32,\n                buffer: 0,\n                name: None,\n                quantization: None,\n                is_variable: false,\n                sparsity: None,\n                shape_signature: None,\n                has_rank: false,\n                variant_tensors: None,\n            }\n        }\n    }\n\n    pub struct TensorBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> TensorBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_shape(&mut self, shape: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Tensor::VT_SHAPE, shape);\n        }\n        #[inline]\n        pub fn add_type_(&mut self, type_: TensorType) {\n            self.fbb_.push_slot::<TensorType>(Tensor::VT_TYPE_, type_, TensorType::FLOAT32);\n        }\n        #[inline]\n        pub fn add_buffer(&mut self, buffer: u32) {\n            self.fbb_.push_slot::<u32>(Tensor::VT_BUFFER, buffer, 0);\n        }\n        #[inline]\n        pub fn add_name(&mut self, name: flatbuffers::WIPOffset<&'b str>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Tensor::VT_NAME, name);\n        }\n        #[inline]\n        pub fn add_quantization(\n            &mut self,\n            quantization: flatbuffers::WIPOffset<QuantizationParameters<'b>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<QuantizationParameters>>(\n                Tensor::VT_QUANTIZATION,\n                quantization,\n            );\n        }\n        #[inline]\n        pub fn add_is_variable(&mut self, is_variable: bool) {\n            self.fbb_.push_slot::<bool>(Tensor::VT_IS_VARIABLE, is_variable, false);\n        }\n        #[inline]\n        pub fn add_sparsity(&mut self, sparsity: flatbuffers::WIPOffset<SparsityParameters<'b>>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<SparsityParameters>>(\n                Tensor::VT_SPARSITY,\n                sparsity,\n            );\n        }\n        #[inline]\n        pub fn add_shape_signature(\n            &mut self,\n            shape_signature: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                Tensor::VT_SHAPE_SIGNATURE,\n                shape_signature,\n            );\n        }\n        #[inline]\n        pub fn add_has_rank(&mut self, has_rank: bool) {\n            self.fbb_.push_slot::<bool>(Tensor::VT_HAS_RANK, has_rank, false);\n        }\n        #[inline]\n        pub fn add_variant_tensors(\n            &mut self,\n            variant_tensors: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<VariantSubType<'b>>>,\n            >,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                Tensor::VT_VARIANT_TENSORS,\n                variant_tensors,\n            );\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> TensorBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            TensorBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Tensor<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Tensor<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Tensor\");\n            ds.field(\"shape\", &self.shape());\n            ds.field(\"type_\", &self.type_());\n            ds.field(\"buffer\", &self.buffer());\n            ds.field(\"name\", &self.name());\n            ds.field(\"quantization\", &self.quantization());\n            ds.field(\"is_variable\", &self.is_variable());\n            ds.field(\"sparsity\", &self.sparsity());\n            ds.field(\"shape_signature\", &self.shape_signature());\n            ds.field(\"has_rank\", &self.has_rank());\n            ds.field(\"variant_tensors\", &self.variant_tensors());\n            ds.finish()\n        }\n    }\n    pub enum Conv2DOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Conv2DOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Conv2DOptions<'a> {\n        type Inner = Conv2DOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Conv2DOptions<'a> {\n        pub const VT_PADDING: flatbuffers::VOffsetT = 4;\n        pub const VT_STRIDE_W: flatbuffers::VOffsetT = 6;\n        pub const VT_STRIDE_H: flatbuffers::VOffsetT = 8;\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 10;\n        pub const VT_DILATION_W_FACTOR: flatbuffers::VOffsetT = 12;\n        pub const VT_DILATION_H_FACTOR: flatbuffers::VOffsetT = 14;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Conv2DOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args Conv2DOptionsArgs,\n        ) -> flatbuffers::WIPOffset<Conv2DOptions<'bldr>> {\n            let mut builder = Conv2DOptionsBuilder::new(_fbb);\n            builder.add_dilation_h_factor(args.dilation_h_factor);\n            builder.add_dilation_w_factor(args.dilation_w_factor);\n            builder.add_stride_h(args.stride_h);\n            builder.add_stride_w(args.stride_w);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.add_padding(args.padding);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn padding(&self) -> Padding {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<Padding>(Conv2DOptions::VT_PADDING, Some(Padding::SAME)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn stride_w(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv2DOptions::VT_STRIDE_W, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn stride_h(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv2DOptions::VT_STRIDE_H, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        Conv2DOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn dilation_w_factor(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv2DOptions::VT_DILATION_W_FACTOR, Some(1)).unwrap() }\n        }\n        #[inline]\n        pub fn dilation_h_factor(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv2DOptions::VT_DILATION_H_FACTOR, Some(1)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Conv2DOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<Padding>(\"padding\", Self::VT_PADDING, false)?\n                .visit_field::<i32>(\"stride_w\", Self::VT_STRIDE_W, false)?\n                .visit_field::<i32>(\"stride_h\", Self::VT_STRIDE_H, false)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<i32>(\"dilation_w_factor\", Self::VT_DILATION_W_FACTOR, false)?\n                .visit_field::<i32>(\"dilation_h_factor\", Self::VT_DILATION_H_FACTOR, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct Conv2DOptionsArgs {\n        pub padding: Padding,\n        pub stride_w: i32,\n        pub stride_h: i32,\n        pub fused_activation_function: ActivationFunctionType,\n        pub dilation_w_factor: i32,\n        pub dilation_h_factor: i32,\n    }\n    impl<'a> Default for Conv2DOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            Conv2DOptionsArgs {\n                padding: Padding::SAME,\n                stride_w: 0,\n                stride_h: 0,\n                fused_activation_function: ActivationFunctionType::NONE,\n                dilation_w_factor: 1,\n                dilation_h_factor: 1,\n            }\n        }\n    }\n\n    pub struct Conv2DOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> Conv2DOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_padding(&mut self, padding: Padding) {\n            self.fbb_.push_slot::<Padding>(Conv2DOptions::VT_PADDING, padding, Padding::SAME);\n        }\n        #[inline]\n        pub fn add_stride_w(&mut self, stride_w: i32) {\n            self.fbb_.push_slot::<i32>(Conv2DOptions::VT_STRIDE_W, stride_w, 0);\n        }\n        #[inline]\n        pub fn add_stride_h(&mut self, stride_h: i32) {\n            self.fbb_.push_slot::<i32>(Conv2DOptions::VT_STRIDE_H, stride_h, 0);\n        }\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                Conv2DOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_dilation_w_factor(&mut self, dilation_w_factor: i32) {\n            self.fbb_.push_slot::<i32>(Conv2DOptions::VT_DILATION_W_FACTOR, dilation_w_factor, 1);\n        }\n        #[inline]\n        pub fn add_dilation_h_factor(&mut self, dilation_h_factor: i32) {\n            self.fbb_.push_slot::<i32>(Conv2DOptions::VT_DILATION_H_FACTOR, dilation_h_factor, 1);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> Conv2DOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            Conv2DOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Conv2DOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Conv2DOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Conv2DOptions\");\n            ds.field(\"padding\", &self.padding());\n            ds.field(\"stride_w\", &self.stride_w());\n            ds.field(\"stride_h\", &self.stride_h());\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"dilation_w_factor\", &self.dilation_w_factor());\n            ds.field(\"dilation_h_factor\", &self.dilation_h_factor());\n            ds.finish()\n        }\n    }\n    pub enum Conv3DOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Conv3DOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Conv3DOptions<'a> {\n        type Inner = Conv3DOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Conv3DOptions<'a> {\n        pub const VT_PADDING: flatbuffers::VOffsetT = 4;\n        pub const VT_STRIDE_D: flatbuffers::VOffsetT = 6;\n        pub const VT_STRIDE_W: flatbuffers::VOffsetT = 8;\n        pub const VT_STRIDE_H: flatbuffers::VOffsetT = 10;\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 12;\n        pub const VT_DILATION_D_FACTOR: flatbuffers::VOffsetT = 14;\n        pub const VT_DILATION_W_FACTOR: flatbuffers::VOffsetT = 16;\n        pub const VT_DILATION_H_FACTOR: flatbuffers::VOffsetT = 18;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Conv3DOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args Conv3DOptionsArgs,\n        ) -> flatbuffers::WIPOffset<Conv3DOptions<'bldr>> {\n            let mut builder = Conv3DOptionsBuilder::new(_fbb);\n            builder.add_dilation_h_factor(args.dilation_h_factor);\n            builder.add_dilation_w_factor(args.dilation_w_factor);\n            builder.add_dilation_d_factor(args.dilation_d_factor);\n            builder.add_stride_h(args.stride_h);\n            builder.add_stride_w(args.stride_w);\n            builder.add_stride_d(args.stride_d);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.add_padding(args.padding);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn padding(&self) -> Padding {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<Padding>(Conv3DOptions::VT_PADDING, Some(Padding::SAME)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn stride_d(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv3DOptions::VT_STRIDE_D, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn stride_w(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv3DOptions::VT_STRIDE_W, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn stride_h(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv3DOptions::VT_STRIDE_H, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        Conv3DOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn dilation_d_factor(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv3DOptions::VT_DILATION_D_FACTOR, Some(1)).unwrap() }\n        }\n        #[inline]\n        pub fn dilation_w_factor(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv3DOptions::VT_DILATION_W_FACTOR, Some(1)).unwrap() }\n        }\n        #[inline]\n        pub fn dilation_h_factor(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Conv3DOptions::VT_DILATION_H_FACTOR, Some(1)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Conv3DOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<Padding>(\"padding\", Self::VT_PADDING, false)?\n                .visit_field::<i32>(\"stride_d\", Self::VT_STRIDE_D, false)?\n                .visit_field::<i32>(\"stride_w\", Self::VT_STRIDE_W, false)?\n                .visit_field::<i32>(\"stride_h\", Self::VT_STRIDE_H, false)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<i32>(\"dilation_d_factor\", Self::VT_DILATION_D_FACTOR, false)?\n                .visit_field::<i32>(\"dilation_w_factor\", Self::VT_DILATION_W_FACTOR, false)?\n                .visit_field::<i32>(\"dilation_h_factor\", Self::VT_DILATION_H_FACTOR, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct Conv3DOptionsArgs {\n        pub padding: Padding,\n        pub stride_d: i32,\n        pub stride_w: i32,\n        pub stride_h: i32,\n        pub fused_activation_function: ActivationFunctionType,\n        pub dilation_d_factor: i32,\n        pub dilation_w_factor: i32,\n        pub dilation_h_factor: i32,\n    }\n    impl<'a> Default for Conv3DOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            Conv3DOptionsArgs {\n                padding: Padding::SAME,\n                stride_d: 0,\n                stride_w: 0,\n                stride_h: 0,\n                fused_activation_function: ActivationFunctionType::NONE,\n                dilation_d_factor: 1,\n                dilation_w_factor: 1,\n                dilation_h_factor: 1,\n            }\n        }\n    }\n\n    pub struct Conv3DOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> Conv3DOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_padding(&mut self, padding: Padding) {\n            self.fbb_.push_slot::<Padding>(Conv3DOptions::VT_PADDING, padding, Padding::SAME);\n        }\n        #[inline]\n        pub fn add_stride_d(&mut self, stride_d: i32) {\n            self.fbb_.push_slot::<i32>(Conv3DOptions::VT_STRIDE_D, stride_d, 0);\n        }\n        #[inline]\n        pub fn add_stride_w(&mut self, stride_w: i32) {\n            self.fbb_.push_slot::<i32>(Conv3DOptions::VT_STRIDE_W, stride_w, 0);\n        }\n        #[inline]\n        pub fn add_stride_h(&mut self, stride_h: i32) {\n            self.fbb_.push_slot::<i32>(Conv3DOptions::VT_STRIDE_H, stride_h, 0);\n        }\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                Conv3DOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_dilation_d_factor(&mut self, dilation_d_factor: i32) {\n            self.fbb_.push_slot::<i32>(Conv3DOptions::VT_DILATION_D_FACTOR, dilation_d_factor, 1);\n        }\n        #[inline]\n        pub fn add_dilation_w_factor(&mut self, dilation_w_factor: i32) {\n            self.fbb_.push_slot::<i32>(Conv3DOptions::VT_DILATION_W_FACTOR, dilation_w_factor, 1);\n        }\n        #[inline]\n        pub fn add_dilation_h_factor(&mut self, dilation_h_factor: i32) {\n            self.fbb_.push_slot::<i32>(Conv3DOptions::VT_DILATION_H_FACTOR, dilation_h_factor, 1);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> Conv3DOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            Conv3DOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Conv3DOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Conv3DOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Conv3DOptions\");\n            ds.field(\"padding\", &self.padding());\n            ds.field(\"stride_d\", &self.stride_d());\n            ds.field(\"stride_w\", &self.stride_w());\n            ds.field(\"stride_h\", &self.stride_h());\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"dilation_d_factor\", &self.dilation_d_factor());\n            ds.field(\"dilation_w_factor\", &self.dilation_w_factor());\n            ds.field(\"dilation_h_factor\", &self.dilation_h_factor());\n            ds.finish()\n        }\n    }\n    pub enum Pool2DOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Pool2DOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Pool2DOptions<'a> {\n        type Inner = Pool2DOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Pool2DOptions<'a> {\n        pub const VT_PADDING: flatbuffers::VOffsetT = 4;\n        pub const VT_STRIDE_W: flatbuffers::VOffsetT = 6;\n        pub const VT_STRIDE_H: flatbuffers::VOffsetT = 8;\n        pub const VT_FILTER_WIDTH: flatbuffers::VOffsetT = 10;\n        pub const VT_FILTER_HEIGHT: flatbuffers::VOffsetT = 12;\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 14;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Pool2DOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args Pool2DOptionsArgs,\n        ) -> flatbuffers::WIPOffset<Pool2DOptions<'bldr>> {\n            let mut builder = Pool2DOptionsBuilder::new(_fbb);\n            builder.add_filter_height(args.filter_height);\n            builder.add_filter_width(args.filter_width);\n            builder.add_stride_h(args.stride_h);\n            builder.add_stride_w(args.stride_w);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.add_padding(args.padding);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn padding(&self) -> Padding {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<Padding>(Pool2DOptions::VT_PADDING, Some(Padding::SAME)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn stride_w(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Pool2DOptions::VT_STRIDE_W, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn stride_h(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Pool2DOptions::VT_STRIDE_H, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn filter_width(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Pool2DOptions::VT_FILTER_WIDTH, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn filter_height(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(Pool2DOptions::VT_FILTER_HEIGHT, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        Pool2DOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Pool2DOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<Padding>(\"padding\", Self::VT_PADDING, false)?\n                .visit_field::<i32>(\"stride_w\", Self::VT_STRIDE_W, false)?\n                .visit_field::<i32>(\"stride_h\", Self::VT_STRIDE_H, false)?\n                .visit_field::<i32>(\"filter_width\", Self::VT_FILTER_WIDTH, false)?\n                .visit_field::<i32>(\"filter_height\", Self::VT_FILTER_HEIGHT, false)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct Pool2DOptionsArgs {\n        pub padding: Padding,\n        pub stride_w: i32,\n        pub stride_h: i32,\n        pub filter_width: i32,\n        pub filter_height: i32,\n        pub fused_activation_function: ActivationFunctionType,\n    }\n    impl<'a> Default for Pool2DOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            Pool2DOptionsArgs {\n                padding: Padding::SAME,\n                stride_w: 0,\n                stride_h: 0,\n                filter_width: 0,\n                filter_height: 0,\n                fused_activation_function: ActivationFunctionType::NONE,\n            }\n        }\n    }\n\n    pub struct Pool2DOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> Pool2DOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_padding(&mut self, padding: Padding) {\n            self.fbb_.push_slot::<Padding>(Pool2DOptions::VT_PADDING, padding, Padding::SAME);\n        }\n        #[inline]\n        pub fn add_stride_w(&mut self, stride_w: i32) {\n            self.fbb_.push_slot::<i32>(Pool2DOptions::VT_STRIDE_W, stride_w, 0);\n        }\n        #[inline]\n        pub fn add_stride_h(&mut self, stride_h: i32) {\n            self.fbb_.push_slot::<i32>(Pool2DOptions::VT_STRIDE_H, stride_h, 0);\n        }\n        #[inline]\n        pub fn add_filter_width(&mut self, filter_width: i32) {\n            self.fbb_.push_slot::<i32>(Pool2DOptions::VT_FILTER_WIDTH, filter_width, 0);\n        }\n        #[inline]\n        pub fn add_filter_height(&mut self, filter_height: i32) {\n            self.fbb_.push_slot::<i32>(Pool2DOptions::VT_FILTER_HEIGHT, filter_height, 0);\n        }\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                Pool2DOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> Pool2DOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            Pool2DOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Pool2DOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Pool2DOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Pool2DOptions\");\n            ds.field(\"padding\", &self.padding());\n            ds.field(\"stride_w\", &self.stride_w());\n            ds.field(\"stride_h\", &self.stride_h());\n            ds.field(\"filter_width\", &self.filter_width());\n            ds.field(\"filter_height\", &self.filter_height());\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.finish()\n        }\n    }\n    pub enum DepthwiseConv2DOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct DepthwiseConv2DOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for DepthwiseConv2DOptions<'a> {\n        type Inner = DepthwiseConv2DOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> DepthwiseConv2DOptions<'a> {\n        pub const VT_PADDING: flatbuffers::VOffsetT = 4;\n        pub const VT_STRIDE_W: flatbuffers::VOffsetT = 6;\n        pub const VT_STRIDE_H: flatbuffers::VOffsetT = 8;\n        pub const VT_DEPTH_MULTIPLIER: flatbuffers::VOffsetT = 10;\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 12;\n        pub const VT_DILATION_W_FACTOR: flatbuffers::VOffsetT = 14;\n        pub const VT_DILATION_H_FACTOR: flatbuffers::VOffsetT = 16;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            DepthwiseConv2DOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args DepthwiseConv2DOptionsArgs,\n        ) -> flatbuffers::WIPOffset<DepthwiseConv2DOptions<'bldr>> {\n            let mut builder = DepthwiseConv2DOptionsBuilder::new(_fbb);\n            builder.add_dilation_h_factor(args.dilation_h_factor);\n            builder.add_dilation_w_factor(args.dilation_w_factor);\n            builder.add_depth_multiplier(args.depth_multiplier);\n            builder.add_stride_h(args.stride_h);\n            builder.add_stride_w(args.stride_w);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.add_padding(args.padding);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn padding(&self) -> Padding {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<Padding>(DepthwiseConv2DOptions::VT_PADDING, Some(Padding::SAME))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn stride_w(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(DepthwiseConv2DOptions::VT_STRIDE_W, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn stride_h(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(DepthwiseConv2DOptions::VT_STRIDE_H, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn depth_multiplier(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<i32>(DepthwiseConv2DOptions::VT_DEPTH_MULTIPLIER, Some(0)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        DepthwiseConv2DOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn dilation_w_factor(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<i32>(DepthwiseConv2DOptions::VT_DILATION_W_FACTOR, Some(1)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn dilation_h_factor(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<i32>(DepthwiseConv2DOptions::VT_DILATION_H_FACTOR, Some(1)).unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for DepthwiseConv2DOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<Padding>(\"padding\", Self::VT_PADDING, false)?\n                .visit_field::<i32>(\"stride_w\", Self::VT_STRIDE_W, false)?\n                .visit_field::<i32>(\"stride_h\", Self::VT_STRIDE_H, false)?\n                .visit_field::<i32>(\"depth_multiplier\", Self::VT_DEPTH_MULTIPLIER, false)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<i32>(\"dilation_w_factor\", Self::VT_DILATION_W_FACTOR, false)?\n                .visit_field::<i32>(\"dilation_h_factor\", Self::VT_DILATION_H_FACTOR, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct DepthwiseConv2DOptionsArgs {\n        pub padding: Padding,\n        pub stride_w: i32,\n        pub stride_h: i32,\n        pub depth_multiplier: i32,\n        pub fused_activation_function: ActivationFunctionType,\n        pub dilation_w_factor: i32,\n        pub dilation_h_factor: i32,\n    }\n    impl<'a> Default for DepthwiseConv2DOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            DepthwiseConv2DOptionsArgs {\n                padding: Padding::SAME,\n                stride_w: 0,\n                stride_h: 0,\n                depth_multiplier: 0,\n                fused_activation_function: ActivationFunctionType::NONE,\n                dilation_w_factor: 1,\n                dilation_h_factor: 1,\n            }\n        }\n    }\n\n    pub struct DepthwiseConv2DOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> DepthwiseConv2DOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_padding(&mut self, padding: Padding) {\n            self.fbb_.push_slot::<Padding>(\n                DepthwiseConv2DOptions::VT_PADDING,\n                padding,\n                Padding::SAME,\n            );\n        }\n        #[inline]\n        pub fn add_stride_w(&mut self, stride_w: i32) {\n            self.fbb_.push_slot::<i32>(DepthwiseConv2DOptions::VT_STRIDE_W, stride_w, 0);\n        }\n        #[inline]\n        pub fn add_stride_h(&mut self, stride_h: i32) {\n            self.fbb_.push_slot::<i32>(DepthwiseConv2DOptions::VT_STRIDE_H, stride_h, 0);\n        }\n        #[inline]\n        pub fn add_depth_multiplier(&mut self, depth_multiplier: i32) {\n            self.fbb_.push_slot::<i32>(\n                DepthwiseConv2DOptions::VT_DEPTH_MULTIPLIER,\n                depth_multiplier,\n                0,\n            );\n        }\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                DepthwiseConv2DOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_dilation_w_factor(&mut self, dilation_w_factor: i32) {\n            self.fbb_.push_slot::<i32>(\n                DepthwiseConv2DOptions::VT_DILATION_W_FACTOR,\n                dilation_w_factor,\n                1,\n            );\n        }\n        #[inline]\n        pub fn add_dilation_h_factor(&mut self, dilation_h_factor: i32) {\n            self.fbb_.push_slot::<i32>(\n                DepthwiseConv2DOptions::VT_DILATION_H_FACTOR,\n                dilation_h_factor,\n                1,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> DepthwiseConv2DOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            DepthwiseConv2DOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<DepthwiseConv2DOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for DepthwiseConv2DOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"DepthwiseConv2DOptions\");\n            ds.field(\"padding\", &self.padding());\n            ds.field(\"stride_w\", &self.stride_w());\n            ds.field(\"stride_h\", &self.stride_h());\n            ds.field(\"depth_multiplier\", &self.depth_multiplier());\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"dilation_w_factor\", &self.dilation_w_factor());\n            ds.field(\"dilation_h_factor\", &self.dilation_h_factor());\n            ds.finish()\n        }\n    }\n    pub enum ConcatEmbeddingsOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ConcatEmbeddingsOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ConcatEmbeddingsOptions<'a> {\n        type Inner = ConcatEmbeddingsOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ConcatEmbeddingsOptions<'a> {\n        pub const VT_NUM_CHANNELS: flatbuffers::VOffsetT = 4;\n        pub const VT_NUM_COLUMNS_PER_CHANNEL: flatbuffers::VOffsetT = 6;\n        pub const VT_EMBEDDING_DIM_PER_CHANNEL: flatbuffers::VOffsetT = 8;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ConcatEmbeddingsOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ConcatEmbeddingsOptionsArgs<'args>,\n        ) -> flatbuffers::WIPOffset<ConcatEmbeddingsOptions<'bldr>> {\n            let mut builder = ConcatEmbeddingsOptionsBuilder::new(_fbb);\n            if let Some(x) = args.embedding_dim_per_channel {\n                builder.add_embedding_dim_per_channel(x);\n            }\n            if let Some(x) = args.num_columns_per_channel {\n                builder.add_num_columns_per_channel(x);\n            }\n            builder.add_num_channels(args.num_channels);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn num_channels(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<i32>(ConcatEmbeddingsOptions::VT_NUM_CHANNELS, Some(0)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn num_columns_per_channel(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    ConcatEmbeddingsOptions::VT_NUM_COLUMNS_PER_CHANNEL,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn embedding_dim_per_channel(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    ConcatEmbeddingsOptions::VT_EMBEDDING_DIM_PER_CHANNEL,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ConcatEmbeddingsOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"num_channels\", Self::VT_NUM_CHANNELS, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"num_columns_per_channel\",\n                    Self::VT_NUM_COLUMNS_PER_CHANNEL,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"embedding_dim_per_channel\",\n                    Self::VT_EMBEDDING_DIM_PER_CHANNEL,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ConcatEmbeddingsOptionsArgs<'a> {\n        pub num_channels: i32,\n        pub num_columns_per_channel: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub embedding_dim_per_channel: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n    }\n    impl<'a> Default for ConcatEmbeddingsOptionsArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            ConcatEmbeddingsOptionsArgs {\n                num_channels: 0,\n                num_columns_per_channel: None,\n                embedding_dim_per_channel: None,\n            }\n        }\n    }\n\n    pub struct ConcatEmbeddingsOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ConcatEmbeddingsOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_num_channels(&mut self, num_channels: i32) {\n            self.fbb_.push_slot::<i32>(ConcatEmbeddingsOptions::VT_NUM_CHANNELS, num_channels, 0);\n        }\n        #[inline]\n        pub fn add_num_columns_per_channel(\n            &mut self,\n            num_columns_per_channel: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                ConcatEmbeddingsOptions::VT_NUM_COLUMNS_PER_CHANNEL,\n                num_columns_per_channel,\n            );\n        }\n        #[inline]\n        pub fn add_embedding_dim_per_channel(\n            &mut self,\n            embedding_dim_per_channel: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                ConcatEmbeddingsOptions::VT_EMBEDDING_DIM_PER_CHANNEL,\n                embedding_dim_per_channel,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ConcatEmbeddingsOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ConcatEmbeddingsOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ConcatEmbeddingsOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ConcatEmbeddingsOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ConcatEmbeddingsOptions\");\n            ds.field(\"num_channels\", &self.num_channels());\n            ds.field(\"num_columns_per_channel\", &self.num_columns_per_channel());\n            ds.field(\"embedding_dim_per_channel\", &self.embedding_dim_per_channel());\n            ds.finish()\n        }\n    }\n    pub enum LSHProjectionOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LSHProjectionOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LSHProjectionOptions<'a> {\n        type Inner = LSHProjectionOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LSHProjectionOptions<'a> {\n        pub const VT_TYPE_: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LSHProjectionOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args LSHProjectionOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LSHProjectionOptions<'bldr>> {\n            let mut builder = LSHProjectionOptionsBuilder::new(_fbb);\n            builder.add_type_(args.type_);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn type_(&self) -> LSHProjectionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<LSHProjectionType>(\n                        LSHProjectionOptions::VT_TYPE_,\n                        Some(LSHProjectionType::UNKNOWN),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for LSHProjectionOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<LSHProjectionType>(\"type_\", Self::VT_TYPE_, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct LSHProjectionOptionsArgs {\n        pub type_: LSHProjectionType,\n    }\n    impl<'a> Default for LSHProjectionOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LSHProjectionOptionsArgs { type_: LSHProjectionType::UNKNOWN }\n        }\n    }\n\n    pub struct LSHProjectionOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LSHProjectionOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_type_(&mut self, type_: LSHProjectionType) {\n            self.fbb_.push_slot::<LSHProjectionType>(\n                LSHProjectionOptions::VT_TYPE_,\n                type_,\n                LSHProjectionType::UNKNOWN,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> LSHProjectionOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LSHProjectionOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LSHProjectionOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LSHProjectionOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LSHProjectionOptions\");\n            ds.field(\"type_\", &self.type_());\n            ds.finish()\n        }\n    }\n    pub enum SVDFOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SVDFOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SVDFOptions<'a> {\n        type Inner = SVDFOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SVDFOptions<'a> {\n        pub const VT_RANK: flatbuffers::VOffsetT = 4;\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 6;\n        pub const VT_ASYMMETRIC_QUANTIZE_INPUTS: flatbuffers::VOffsetT = 8;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SVDFOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SVDFOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SVDFOptions<'bldr>> {\n            let mut builder = SVDFOptionsBuilder::new(_fbb);\n            builder.add_rank(args.rank);\n            builder.add_asymmetric_quantize_inputs(args.asymmetric_quantize_inputs);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn rank(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(SVDFOptions::VT_RANK, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        SVDFOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn asymmetric_quantize_inputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(SVDFOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS, Some(false))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SVDFOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"rank\", Self::VT_RANK, false)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<bool>(\n                    \"asymmetric_quantize_inputs\",\n                    Self::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SVDFOptionsArgs {\n        pub rank: i32,\n        pub fused_activation_function: ActivationFunctionType,\n        pub asymmetric_quantize_inputs: bool,\n    }\n    impl<'a> Default for SVDFOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SVDFOptionsArgs {\n                rank: 0,\n                fused_activation_function: ActivationFunctionType::NONE,\n                asymmetric_quantize_inputs: false,\n            }\n        }\n    }\n\n    pub struct SVDFOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SVDFOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_rank(&mut self, rank: i32) {\n            self.fbb_.push_slot::<i32>(SVDFOptions::VT_RANK, rank, 0);\n        }\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                SVDFOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_asymmetric_quantize_inputs(&mut self, asymmetric_quantize_inputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                SVDFOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                asymmetric_quantize_inputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> SVDFOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SVDFOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SVDFOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SVDFOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SVDFOptions\");\n            ds.field(\"rank\", &self.rank());\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"asymmetric_quantize_inputs\", &self.asymmetric_quantize_inputs());\n            ds.finish()\n        }\n    }\n    pub enum RNNOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct RNNOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for RNNOptions<'a> {\n        type Inner = RNNOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> RNNOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n        pub const VT_ASYMMETRIC_QUANTIZE_INPUTS: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            RNNOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args RNNOptionsArgs,\n        ) -> flatbuffers::WIPOffset<RNNOptions<'bldr>> {\n            let mut builder = RNNOptionsBuilder::new(_fbb);\n            builder.add_asymmetric_quantize_inputs(args.asymmetric_quantize_inputs);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        RNNOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn asymmetric_quantize_inputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(RNNOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS, Some(false))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for RNNOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<bool>(\n                    \"asymmetric_quantize_inputs\",\n                    Self::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct RNNOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n        pub asymmetric_quantize_inputs: bool,\n    }\n    impl<'a> Default for RNNOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            RNNOptionsArgs {\n                fused_activation_function: ActivationFunctionType::NONE,\n                asymmetric_quantize_inputs: false,\n            }\n        }\n    }\n\n    pub struct RNNOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> RNNOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                RNNOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_asymmetric_quantize_inputs(&mut self, asymmetric_quantize_inputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                RNNOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                asymmetric_quantize_inputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> RNNOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            RNNOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<RNNOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for RNNOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"RNNOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"asymmetric_quantize_inputs\", &self.asymmetric_quantize_inputs());\n            ds.finish()\n        }\n    }\n    pub enum SequenceRNNOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SequenceRNNOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SequenceRNNOptions<'a> {\n        type Inner = SequenceRNNOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SequenceRNNOptions<'a> {\n        pub const VT_TIME_MAJOR: flatbuffers::VOffsetT = 4;\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 6;\n        pub const VT_ASYMMETRIC_QUANTIZE_INPUTS: flatbuffers::VOffsetT = 8;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SequenceRNNOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SequenceRNNOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SequenceRNNOptions<'bldr>> {\n            let mut builder = SequenceRNNOptionsBuilder::new(_fbb);\n            builder.add_asymmetric_quantize_inputs(args.asymmetric_quantize_inputs);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.add_time_major(args.time_major);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn time_major(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<bool>(SequenceRNNOptions::VT_TIME_MAJOR, Some(false)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        SequenceRNNOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn asymmetric_quantize_inputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(SequenceRNNOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS, Some(false))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SequenceRNNOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<bool>(\"time_major\", Self::VT_TIME_MAJOR, false)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<bool>(\n                    \"asymmetric_quantize_inputs\",\n                    Self::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SequenceRNNOptionsArgs {\n        pub time_major: bool,\n        pub fused_activation_function: ActivationFunctionType,\n        pub asymmetric_quantize_inputs: bool,\n    }\n    impl<'a> Default for SequenceRNNOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SequenceRNNOptionsArgs {\n                time_major: false,\n                fused_activation_function: ActivationFunctionType::NONE,\n                asymmetric_quantize_inputs: false,\n            }\n        }\n    }\n\n    pub struct SequenceRNNOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SequenceRNNOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_time_major(&mut self, time_major: bool) {\n            self.fbb_.push_slot::<bool>(SequenceRNNOptions::VT_TIME_MAJOR, time_major, false);\n        }\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                SequenceRNNOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_asymmetric_quantize_inputs(&mut self, asymmetric_quantize_inputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                SequenceRNNOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                asymmetric_quantize_inputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SequenceRNNOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SequenceRNNOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SequenceRNNOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SequenceRNNOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SequenceRNNOptions\");\n            ds.field(\"time_major\", &self.time_major());\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"asymmetric_quantize_inputs\", &self.asymmetric_quantize_inputs());\n            ds.finish()\n        }\n    }\n    pub enum BidirectionalSequenceRNNOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct BidirectionalSequenceRNNOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for BidirectionalSequenceRNNOptions<'a> {\n        type Inner = BidirectionalSequenceRNNOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> BidirectionalSequenceRNNOptions<'a> {\n        pub const VT_TIME_MAJOR: flatbuffers::VOffsetT = 4;\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 6;\n        pub const VT_MERGE_OUTPUTS: flatbuffers::VOffsetT = 8;\n        pub const VT_ASYMMETRIC_QUANTIZE_INPUTS: flatbuffers::VOffsetT = 10;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            BidirectionalSequenceRNNOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args BidirectionalSequenceRNNOptionsArgs,\n        ) -> flatbuffers::WIPOffset<BidirectionalSequenceRNNOptions<'bldr>> {\n            let mut builder = BidirectionalSequenceRNNOptionsBuilder::new(_fbb);\n            builder.add_asymmetric_quantize_inputs(args.asymmetric_quantize_inputs);\n            builder.add_merge_outputs(args.merge_outputs);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.add_time_major(args.time_major);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn time_major(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(BidirectionalSequenceRNNOptions::VT_TIME_MAJOR, Some(false))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        BidirectionalSequenceRNNOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn merge_outputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(BidirectionalSequenceRNNOptions::VT_MERGE_OUTPUTS, Some(false))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn asymmetric_quantize_inputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(\n                        BidirectionalSequenceRNNOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                        Some(false),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for BidirectionalSequenceRNNOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<bool>(\"time_major\", Self::VT_TIME_MAJOR, false)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<bool>(\"merge_outputs\", Self::VT_MERGE_OUTPUTS, false)?\n                .visit_field::<bool>(\n                    \"asymmetric_quantize_inputs\",\n                    Self::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct BidirectionalSequenceRNNOptionsArgs {\n        pub time_major: bool,\n        pub fused_activation_function: ActivationFunctionType,\n        pub merge_outputs: bool,\n        pub asymmetric_quantize_inputs: bool,\n    }\n    impl<'a> Default for BidirectionalSequenceRNNOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            BidirectionalSequenceRNNOptionsArgs {\n                time_major: false,\n                fused_activation_function: ActivationFunctionType::NONE,\n                merge_outputs: false,\n                asymmetric_quantize_inputs: false,\n            }\n        }\n    }\n\n    pub struct BidirectionalSequenceRNNOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> BidirectionalSequenceRNNOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_time_major(&mut self, time_major: bool) {\n            self.fbb_.push_slot::<bool>(\n                BidirectionalSequenceRNNOptions::VT_TIME_MAJOR,\n                time_major,\n                false,\n            );\n        }\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                BidirectionalSequenceRNNOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_merge_outputs(&mut self, merge_outputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                BidirectionalSequenceRNNOptions::VT_MERGE_OUTPUTS,\n                merge_outputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn add_asymmetric_quantize_inputs(&mut self, asymmetric_quantize_inputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                BidirectionalSequenceRNNOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                asymmetric_quantize_inputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> BidirectionalSequenceRNNOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            BidirectionalSequenceRNNOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<BidirectionalSequenceRNNOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for BidirectionalSequenceRNNOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"BidirectionalSequenceRNNOptions\");\n            ds.field(\"time_major\", &self.time_major());\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"merge_outputs\", &self.merge_outputs());\n            ds.field(\"asymmetric_quantize_inputs\", &self.asymmetric_quantize_inputs());\n            ds.finish()\n        }\n    }\n    pub enum FullyConnectedOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct FullyConnectedOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for FullyConnectedOptions<'a> {\n        type Inner = FullyConnectedOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> FullyConnectedOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n        pub const VT_WEIGHTS_FORMAT: flatbuffers::VOffsetT = 6;\n        pub const VT_KEEP_NUM_DIMS: flatbuffers::VOffsetT = 8;\n        pub const VT_ASYMMETRIC_QUANTIZE_INPUTS: flatbuffers::VOffsetT = 10;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            FullyConnectedOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args FullyConnectedOptionsArgs,\n        ) -> flatbuffers::WIPOffset<FullyConnectedOptions<'bldr>> {\n            let mut builder = FullyConnectedOptionsBuilder::new(_fbb);\n            builder.add_asymmetric_quantize_inputs(args.asymmetric_quantize_inputs);\n            builder.add_keep_num_dims(args.keep_num_dims);\n            builder.add_weights_format(args.weights_format);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        FullyConnectedOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn weights_format(&self) -> FullyConnectedOptionsWeightsFormat {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<FullyConnectedOptionsWeightsFormat>(\n                        FullyConnectedOptions::VT_WEIGHTS_FORMAT,\n                        Some(FullyConnectedOptionsWeightsFormat::DEFAULT),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn keep_num_dims(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<bool>(FullyConnectedOptions::VT_KEEP_NUM_DIMS, Some(false)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn asymmetric_quantize_inputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(FullyConnectedOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS, Some(false))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for FullyConnectedOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<FullyConnectedOptionsWeightsFormat>(\n                    \"weights_format\",\n                    Self::VT_WEIGHTS_FORMAT,\n                    false,\n                )?\n                .visit_field::<bool>(\"keep_num_dims\", Self::VT_KEEP_NUM_DIMS, false)?\n                .visit_field::<bool>(\n                    \"asymmetric_quantize_inputs\",\n                    Self::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct FullyConnectedOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n        pub weights_format: FullyConnectedOptionsWeightsFormat,\n        pub keep_num_dims: bool,\n        pub asymmetric_quantize_inputs: bool,\n    }\n    impl<'a> Default for FullyConnectedOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            FullyConnectedOptionsArgs {\n                fused_activation_function: ActivationFunctionType::NONE,\n                weights_format: FullyConnectedOptionsWeightsFormat::DEFAULT,\n                keep_num_dims: false,\n                asymmetric_quantize_inputs: false,\n            }\n        }\n    }\n\n    pub struct FullyConnectedOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> FullyConnectedOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                FullyConnectedOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_weights_format(&mut self, weights_format: FullyConnectedOptionsWeightsFormat) {\n            self.fbb_.push_slot::<FullyConnectedOptionsWeightsFormat>(\n                FullyConnectedOptions::VT_WEIGHTS_FORMAT,\n                weights_format,\n                FullyConnectedOptionsWeightsFormat::DEFAULT,\n            );\n        }\n        #[inline]\n        pub fn add_keep_num_dims(&mut self, keep_num_dims: bool) {\n            self.fbb_.push_slot::<bool>(\n                FullyConnectedOptions::VT_KEEP_NUM_DIMS,\n                keep_num_dims,\n                false,\n            );\n        }\n        #[inline]\n        pub fn add_asymmetric_quantize_inputs(&mut self, asymmetric_quantize_inputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                FullyConnectedOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                asymmetric_quantize_inputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> FullyConnectedOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            FullyConnectedOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<FullyConnectedOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for FullyConnectedOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"FullyConnectedOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"weights_format\", &self.weights_format());\n            ds.field(\"keep_num_dims\", &self.keep_num_dims());\n            ds.field(\"asymmetric_quantize_inputs\", &self.asymmetric_quantize_inputs());\n            ds.finish()\n        }\n    }\n    pub enum SoftmaxOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SoftmaxOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SoftmaxOptions<'a> {\n        type Inner = SoftmaxOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SoftmaxOptions<'a> {\n        pub const VT_BETA: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SoftmaxOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SoftmaxOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SoftmaxOptions<'bldr>> {\n            let mut builder = SoftmaxOptionsBuilder::new(_fbb);\n            builder.add_beta(args.beta);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn beta(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<f32>(SoftmaxOptions::VT_BETA, Some(0.0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SoftmaxOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.visit_field::<f32>(\"beta\", Self::VT_BETA, false)?.finish();\n            Ok(())\n        }\n    }\n    pub struct SoftmaxOptionsArgs {\n        pub beta: f32,\n    }\n    impl<'a> Default for SoftmaxOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SoftmaxOptionsArgs { beta: 0.0 }\n        }\n    }\n\n    pub struct SoftmaxOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SoftmaxOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_beta(&mut self, beta: f32) {\n            self.fbb_.push_slot::<f32>(SoftmaxOptions::VT_BETA, beta, 0.0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SoftmaxOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SoftmaxOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SoftmaxOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SoftmaxOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SoftmaxOptions\");\n            ds.field(\"beta\", &self.beta());\n            ds.finish()\n        }\n    }\n    pub enum ConcatenationOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ConcatenationOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ConcatenationOptions<'a> {\n        type Inner = ConcatenationOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ConcatenationOptions<'a> {\n        pub const VT_AXIS: flatbuffers::VOffsetT = 4;\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ConcatenationOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ConcatenationOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ConcatenationOptions<'bldr>> {\n            let mut builder = ConcatenationOptionsBuilder::new(_fbb);\n            builder.add_axis(args.axis);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn axis(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(ConcatenationOptions::VT_AXIS, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        ConcatenationOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ConcatenationOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"axis\", Self::VT_AXIS, false)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ConcatenationOptionsArgs {\n        pub axis: i32,\n        pub fused_activation_function: ActivationFunctionType,\n    }\n    impl<'a> Default for ConcatenationOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ConcatenationOptionsArgs {\n                axis: 0,\n                fused_activation_function: ActivationFunctionType::NONE,\n            }\n        }\n    }\n\n    pub struct ConcatenationOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ConcatenationOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_axis(&mut self, axis: i32) {\n            self.fbb_.push_slot::<i32>(ConcatenationOptions::VT_AXIS, axis, 0);\n        }\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                ConcatenationOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ConcatenationOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ConcatenationOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ConcatenationOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ConcatenationOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ConcatenationOptions\");\n            ds.field(\"axis\", &self.axis());\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.finish()\n        }\n    }\n    pub enum AddOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct AddOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for AddOptions<'a> {\n        type Inner = AddOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> AddOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n        pub const VT_POT_SCALE_INT16: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            AddOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args AddOptionsArgs,\n        ) -> flatbuffers::WIPOffset<AddOptions<'bldr>> {\n            let mut builder = AddOptionsBuilder::new(_fbb);\n            builder.add_pot_scale_int16(args.pot_scale_int16);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        AddOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn pot_scale_int16(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(AddOptions::VT_POT_SCALE_INT16, Some(true)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for AddOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<bool>(\"pot_scale_int16\", Self::VT_POT_SCALE_INT16, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct AddOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n        pub pot_scale_int16: bool,\n    }\n    impl<'a> Default for AddOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            AddOptionsArgs {\n                fused_activation_function: ActivationFunctionType::NONE,\n                pot_scale_int16: true,\n            }\n        }\n    }\n\n    pub struct AddOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> AddOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                AddOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_pot_scale_int16(&mut self, pot_scale_int16: bool) {\n            self.fbb_.push_slot::<bool>(AddOptions::VT_POT_SCALE_INT16, pot_scale_int16, true);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> AddOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            AddOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<AddOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for AddOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"AddOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"pot_scale_int16\", &self.pot_scale_int16());\n            ds.finish()\n        }\n    }\n    pub enum MulOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct MulOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for MulOptions<'a> {\n        type Inner = MulOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> MulOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            MulOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args MulOptionsArgs,\n        ) -> flatbuffers::WIPOffset<MulOptions<'bldr>> {\n            let mut builder = MulOptionsBuilder::new(_fbb);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        MulOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for MulOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct MulOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n    }\n    impl<'a> Default for MulOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            MulOptionsArgs { fused_activation_function: ActivationFunctionType::NONE }\n        }\n    }\n\n    pub struct MulOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> MulOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                MulOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> MulOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            MulOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<MulOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for MulOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"MulOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.finish()\n        }\n    }\n    pub enum L2NormOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct L2NormOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for L2NormOptions<'a> {\n        type Inner = L2NormOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> L2NormOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            L2NormOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args L2NormOptionsArgs,\n        ) -> flatbuffers::WIPOffset<L2NormOptions<'bldr>> {\n            let mut builder = L2NormOptionsBuilder::new(_fbb);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        L2NormOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for L2NormOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct L2NormOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n    }\n    impl<'a> Default for L2NormOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            L2NormOptionsArgs { fused_activation_function: ActivationFunctionType::NONE }\n        }\n    }\n\n    pub struct L2NormOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> L2NormOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                L2NormOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> L2NormOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            L2NormOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<L2NormOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for L2NormOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"L2NormOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.finish()\n        }\n    }\n    pub enum LocalResponseNormalizationOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LocalResponseNormalizationOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LocalResponseNormalizationOptions<'a> {\n        type Inner = LocalResponseNormalizationOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LocalResponseNormalizationOptions<'a> {\n        pub const VT_RADIUS: flatbuffers::VOffsetT = 4;\n        pub const VT_BIAS: flatbuffers::VOffsetT = 6;\n        pub const VT_ALPHA: flatbuffers::VOffsetT = 8;\n        pub const VT_BETA: flatbuffers::VOffsetT = 10;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LocalResponseNormalizationOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args LocalResponseNormalizationOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LocalResponseNormalizationOptions<'bldr>> {\n            let mut builder = LocalResponseNormalizationOptionsBuilder::new(_fbb);\n            builder.add_beta(args.beta);\n            builder.add_alpha(args.alpha);\n            builder.add_bias(args.bias);\n            builder.add_radius(args.radius);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn radius(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<i32>(LocalResponseNormalizationOptions::VT_RADIUS, Some(0)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn bias(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<f32>(LocalResponseNormalizationOptions::VT_BIAS, Some(0.0)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn alpha(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<f32>(LocalResponseNormalizationOptions::VT_ALPHA, Some(0.0))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn beta(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<f32>(LocalResponseNormalizationOptions::VT_BETA, Some(0.0)).unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for LocalResponseNormalizationOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"radius\", Self::VT_RADIUS, false)?\n                .visit_field::<f32>(\"bias\", Self::VT_BIAS, false)?\n                .visit_field::<f32>(\"alpha\", Self::VT_ALPHA, false)?\n                .visit_field::<f32>(\"beta\", Self::VT_BETA, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct LocalResponseNormalizationOptionsArgs {\n        pub radius: i32,\n        pub bias: f32,\n        pub alpha: f32,\n        pub beta: f32,\n    }\n    impl<'a> Default for LocalResponseNormalizationOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LocalResponseNormalizationOptionsArgs { radius: 0, bias: 0.0, alpha: 0.0, beta: 0.0 }\n        }\n    }\n\n    pub struct LocalResponseNormalizationOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LocalResponseNormalizationOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_radius(&mut self, radius: i32) {\n            self.fbb_.push_slot::<i32>(LocalResponseNormalizationOptions::VT_RADIUS, radius, 0);\n        }\n        #[inline]\n        pub fn add_bias(&mut self, bias: f32) {\n            self.fbb_.push_slot::<f32>(LocalResponseNormalizationOptions::VT_BIAS, bias, 0.0);\n        }\n        #[inline]\n        pub fn add_alpha(&mut self, alpha: f32) {\n            self.fbb_.push_slot::<f32>(LocalResponseNormalizationOptions::VT_ALPHA, alpha, 0.0);\n        }\n        #[inline]\n        pub fn add_beta(&mut self, beta: f32) {\n            self.fbb_.push_slot::<f32>(LocalResponseNormalizationOptions::VT_BETA, beta, 0.0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> LocalResponseNormalizationOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LocalResponseNormalizationOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LocalResponseNormalizationOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LocalResponseNormalizationOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LocalResponseNormalizationOptions\");\n            ds.field(\"radius\", &self.radius());\n            ds.field(\"bias\", &self.bias());\n            ds.field(\"alpha\", &self.alpha());\n            ds.field(\"beta\", &self.beta());\n            ds.finish()\n        }\n    }\n    pub enum LSTMOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LSTMOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LSTMOptions<'a> {\n        type Inner = LSTMOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LSTMOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n        pub const VT_CELL_CLIP: flatbuffers::VOffsetT = 6;\n        pub const VT_PROJ_CLIP: flatbuffers::VOffsetT = 8;\n        pub const VT_KERNEL_TYPE: flatbuffers::VOffsetT = 10;\n        pub const VT_ASYMMETRIC_QUANTIZE_INPUTS: flatbuffers::VOffsetT = 12;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LSTMOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args LSTMOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LSTMOptions<'bldr>> {\n            let mut builder = LSTMOptionsBuilder::new(_fbb);\n            builder.add_proj_clip(args.proj_clip);\n            builder.add_cell_clip(args.cell_clip);\n            builder.add_asymmetric_quantize_inputs(args.asymmetric_quantize_inputs);\n            builder.add_kernel_type(args.kernel_type);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        LSTMOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn cell_clip(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<f32>(LSTMOptions::VT_CELL_CLIP, Some(0.0)).unwrap() }\n        }\n        #[inline]\n        pub fn proj_clip(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<f32>(LSTMOptions::VT_PROJ_CLIP, Some(0.0)).unwrap() }\n        }\n        #[inline]\n        pub fn kernel_type(&self) -> LSTMKernelType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<LSTMKernelType>(LSTMOptions::VT_KERNEL_TYPE, Some(LSTMKernelType::FULL))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn asymmetric_quantize_inputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(LSTMOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS, Some(false))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for LSTMOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<f32>(\"cell_clip\", Self::VT_CELL_CLIP, false)?\n                .visit_field::<f32>(\"proj_clip\", Self::VT_PROJ_CLIP, false)?\n                .visit_field::<LSTMKernelType>(\"kernel_type\", Self::VT_KERNEL_TYPE, false)?\n                .visit_field::<bool>(\n                    \"asymmetric_quantize_inputs\",\n                    Self::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct LSTMOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n        pub cell_clip: f32,\n        pub proj_clip: f32,\n        pub kernel_type: LSTMKernelType,\n        pub asymmetric_quantize_inputs: bool,\n    }\n    impl<'a> Default for LSTMOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LSTMOptionsArgs {\n                fused_activation_function: ActivationFunctionType::NONE,\n                cell_clip: 0.0,\n                proj_clip: 0.0,\n                kernel_type: LSTMKernelType::FULL,\n                asymmetric_quantize_inputs: false,\n            }\n        }\n    }\n\n    pub struct LSTMOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LSTMOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                LSTMOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_cell_clip(&mut self, cell_clip: f32) {\n            self.fbb_.push_slot::<f32>(LSTMOptions::VT_CELL_CLIP, cell_clip, 0.0);\n        }\n        #[inline]\n        pub fn add_proj_clip(&mut self, proj_clip: f32) {\n            self.fbb_.push_slot::<f32>(LSTMOptions::VT_PROJ_CLIP, proj_clip, 0.0);\n        }\n        #[inline]\n        pub fn add_kernel_type(&mut self, kernel_type: LSTMKernelType) {\n            self.fbb_.push_slot::<LSTMKernelType>(\n                LSTMOptions::VT_KERNEL_TYPE,\n                kernel_type,\n                LSTMKernelType::FULL,\n            );\n        }\n        #[inline]\n        pub fn add_asymmetric_quantize_inputs(&mut self, asymmetric_quantize_inputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                LSTMOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                asymmetric_quantize_inputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> LSTMOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LSTMOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LSTMOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LSTMOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LSTMOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"cell_clip\", &self.cell_clip());\n            ds.field(\"proj_clip\", &self.proj_clip());\n            ds.field(\"kernel_type\", &self.kernel_type());\n            ds.field(\"asymmetric_quantize_inputs\", &self.asymmetric_quantize_inputs());\n            ds.finish()\n        }\n    }\n    pub enum UnidirectionalSequenceLSTMOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct UnidirectionalSequenceLSTMOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for UnidirectionalSequenceLSTMOptions<'a> {\n        type Inner = UnidirectionalSequenceLSTMOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> UnidirectionalSequenceLSTMOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n        pub const VT_CELL_CLIP: flatbuffers::VOffsetT = 6;\n        pub const VT_PROJ_CLIP: flatbuffers::VOffsetT = 8;\n        pub const VT_TIME_MAJOR: flatbuffers::VOffsetT = 10;\n        pub const VT_ASYMMETRIC_QUANTIZE_INPUTS: flatbuffers::VOffsetT = 12;\n        pub const VT_DIAGONAL_RECURRENT_TENSORS: flatbuffers::VOffsetT = 14;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            UnidirectionalSequenceLSTMOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args UnidirectionalSequenceLSTMOptionsArgs,\n        ) -> flatbuffers::WIPOffset<UnidirectionalSequenceLSTMOptions<'bldr>> {\n            let mut builder = UnidirectionalSequenceLSTMOptionsBuilder::new(_fbb);\n            builder.add_proj_clip(args.proj_clip);\n            builder.add_cell_clip(args.cell_clip);\n            builder.add_diagonal_recurrent_tensors(args.diagonal_recurrent_tensors);\n            builder.add_asymmetric_quantize_inputs(args.asymmetric_quantize_inputs);\n            builder.add_time_major(args.time_major);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        UnidirectionalSequenceLSTMOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn cell_clip(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<f32>(UnidirectionalSequenceLSTMOptions::VT_CELL_CLIP, Some(0.0))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn proj_clip(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<f32>(UnidirectionalSequenceLSTMOptions::VT_PROJ_CLIP, Some(0.0))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn time_major(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(UnidirectionalSequenceLSTMOptions::VT_TIME_MAJOR, Some(false))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn asymmetric_quantize_inputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(\n                        UnidirectionalSequenceLSTMOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                        Some(false),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn diagonal_recurrent_tensors(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(\n                        UnidirectionalSequenceLSTMOptions::VT_DIAGONAL_RECURRENT_TENSORS,\n                        Some(false),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for UnidirectionalSequenceLSTMOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<f32>(\"cell_clip\", Self::VT_CELL_CLIP, false)?\n                .visit_field::<f32>(\"proj_clip\", Self::VT_PROJ_CLIP, false)?\n                .visit_field::<bool>(\"time_major\", Self::VT_TIME_MAJOR, false)?\n                .visit_field::<bool>(\n                    \"asymmetric_quantize_inputs\",\n                    Self::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                    false,\n                )?\n                .visit_field::<bool>(\n                    \"diagonal_recurrent_tensors\",\n                    Self::VT_DIAGONAL_RECURRENT_TENSORS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct UnidirectionalSequenceLSTMOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n        pub cell_clip: f32,\n        pub proj_clip: f32,\n        pub time_major: bool,\n        pub asymmetric_quantize_inputs: bool,\n        pub diagonal_recurrent_tensors: bool,\n    }\n    impl<'a> Default for UnidirectionalSequenceLSTMOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            UnidirectionalSequenceLSTMOptionsArgs {\n                fused_activation_function: ActivationFunctionType::NONE,\n                cell_clip: 0.0,\n                proj_clip: 0.0,\n                time_major: false,\n                asymmetric_quantize_inputs: false,\n                diagonal_recurrent_tensors: false,\n            }\n        }\n    }\n\n    pub struct UnidirectionalSequenceLSTMOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> UnidirectionalSequenceLSTMOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                UnidirectionalSequenceLSTMOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_cell_clip(&mut self, cell_clip: f32) {\n            self.fbb_.push_slot::<f32>(\n                UnidirectionalSequenceLSTMOptions::VT_CELL_CLIP,\n                cell_clip,\n                0.0,\n            );\n        }\n        #[inline]\n        pub fn add_proj_clip(&mut self, proj_clip: f32) {\n            self.fbb_.push_slot::<f32>(\n                UnidirectionalSequenceLSTMOptions::VT_PROJ_CLIP,\n                proj_clip,\n                0.0,\n            );\n        }\n        #[inline]\n        pub fn add_time_major(&mut self, time_major: bool) {\n            self.fbb_.push_slot::<bool>(\n                UnidirectionalSequenceLSTMOptions::VT_TIME_MAJOR,\n                time_major,\n                false,\n            );\n        }\n        #[inline]\n        pub fn add_asymmetric_quantize_inputs(&mut self, asymmetric_quantize_inputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                UnidirectionalSequenceLSTMOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                asymmetric_quantize_inputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn add_diagonal_recurrent_tensors(&mut self, diagonal_recurrent_tensors: bool) {\n            self.fbb_.push_slot::<bool>(\n                UnidirectionalSequenceLSTMOptions::VT_DIAGONAL_RECURRENT_TENSORS,\n                diagonal_recurrent_tensors,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> UnidirectionalSequenceLSTMOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            UnidirectionalSequenceLSTMOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<UnidirectionalSequenceLSTMOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for UnidirectionalSequenceLSTMOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"UnidirectionalSequenceLSTMOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"cell_clip\", &self.cell_clip());\n            ds.field(\"proj_clip\", &self.proj_clip());\n            ds.field(\"time_major\", &self.time_major());\n            ds.field(\"asymmetric_quantize_inputs\", &self.asymmetric_quantize_inputs());\n            ds.field(\"diagonal_recurrent_tensors\", &self.diagonal_recurrent_tensors());\n            ds.finish()\n        }\n    }\n    pub enum BidirectionalSequenceLSTMOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct BidirectionalSequenceLSTMOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for BidirectionalSequenceLSTMOptions<'a> {\n        type Inner = BidirectionalSequenceLSTMOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> BidirectionalSequenceLSTMOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n        pub const VT_CELL_CLIP: flatbuffers::VOffsetT = 6;\n        pub const VT_PROJ_CLIP: flatbuffers::VOffsetT = 8;\n        pub const VT_MERGE_OUTPUTS: flatbuffers::VOffsetT = 10;\n        pub const VT_TIME_MAJOR: flatbuffers::VOffsetT = 12;\n        pub const VT_ASYMMETRIC_QUANTIZE_INPUTS: flatbuffers::VOffsetT = 14;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            BidirectionalSequenceLSTMOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args BidirectionalSequenceLSTMOptionsArgs,\n        ) -> flatbuffers::WIPOffset<BidirectionalSequenceLSTMOptions<'bldr>> {\n            let mut builder = BidirectionalSequenceLSTMOptionsBuilder::new(_fbb);\n            builder.add_proj_clip(args.proj_clip);\n            builder.add_cell_clip(args.cell_clip);\n            builder.add_asymmetric_quantize_inputs(args.asymmetric_quantize_inputs);\n            builder.add_time_major(args.time_major);\n            builder.add_merge_outputs(args.merge_outputs);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        BidirectionalSequenceLSTMOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn cell_clip(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<f32>(BidirectionalSequenceLSTMOptions::VT_CELL_CLIP, Some(0.0))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn proj_clip(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<f32>(BidirectionalSequenceLSTMOptions::VT_PROJ_CLIP, Some(0.0))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn merge_outputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(BidirectionalSequenceLSTMOptions::VT_MERGE_OUTPUTS, Some(false))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn time_major(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(BidirectionalSequenceLSTMOptions::VT_TIME_MAJOR, Some(true))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn asymmetric_quantize_inputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(\n                        BidirectionalSequenceLSTMOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                        Some(false),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for BidirectionalSequenceLSTMOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<f32>(\"cell_clip\", Self::VT_CELL_CLIP, false)?\n                .visit_field::<f32>(\"proj_clip\", Self::VT_PROJ_CLIP, false)?\n                .visit_field::<bool>(\"merge_outputs\", Self::VT_MERGE_OUTPUTS, false)?\n                .visit_field::<bool>(\"time_major\", Self::VT_TIME_MAJOR, false)?\n                .visit_field::<bool>(\n                    \"asymmetric_quantize_inputs\",\n                    Self::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct BidirectionalSequenceLSTMOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n        pub cell_clip: f32,\n        pub proj_clip: f32,\n        pub merge_outputs: bool,\n        pub time_major: bool,\n        pub asymmetric_quantize_inputs: bool,\n    }\n    impl<'a> Default for BidirectionalSequenceLSTMOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            BidirectionalSequenceLSTMOptionsArgs {\n                fused_activation_function: ActivationFunctionType::NONE,\n                cell_clip: 0.0,\n                proj_clip: 0.0,\n                merge_outputs: false,\n                time_major: true,\n                asymmetric_quantize_inputs: false,\n            }\n        }\n    }\n\n    pub struct BidirectionalSequenceLSTMOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> BidirectionalSequenceLSTMOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                BidirectionalSequenceLSTMOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_cell_clip(&mut self, cell_clip: f32) {\n            self.fbb_.push_slot::<f32>(\n                BidirectionalSequenceLSTMOptions::VT_CELL_CLIP,\n                cell_clip,\n                0.0,\n            );\n        }\n        #[inline]\n        pub fn add_proj_clip(&mut self, proj_clip: f32) {\n            self.fbb_.push_slot::<f32>(\n                BidirectionalSequenceLSTMOptions::VT_PROJ_CLIP,\n                proj_clip,\n                0.0,\n            );\n        }\n        #[inline]\n        pub fn add_merge_outputs(&mut self, merge_outputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                BidirectionalSequenceLSTMOptions::VT_MERGE_OUTPUTS,\n                merge_outputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn add_time_major(&mut self, time_major: bool) {\n            self.fbb_.push_slot::<bool>(\n                BidirectionalSequenceLSTMOptions::VT_TIME_MAJOR,\n                time_major,\n                true,\n            );\n        }\n        #[inline]\n        pub fn add_asymmetric_quantize_inputs(&mut self, asymmetric_quantize_inputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                BidirectionalSequenceLSTMOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                asymmetric_quantize_inputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> BidirectionalSequenceLSTMOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            BidirectionalSequenceLSTMOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<BidirectionalSequenceLSTMOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for BidirectionalSequenceLSTMOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"BidirectionalSequenceLSTMOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"cell_clip\", &self.cell_clip());\n            ds.field(\"proj_clip\", &self.proj_clip());\n            ds.field(\"merge_outputs\", &self.merge_outputs());\n            ds.field(\"time_major\", &self.time_major());\n            ds.field(\"asymmetric_quantize_inputs\", &self.asymmetric_quantize_inputs());\n            ds.finish()\n        }\n    }\n    pub enum ResizeBilinearOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ResizeBilinearOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ResizeBilinearOptions<'a> {\n        type Inner = ResizeBilinearOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ResizeBilinearOptions<'a> {\n        pub const VT_ALIGN_CORNERS: flatbuffers::VOffsetT = 8;\n        pub const VT_HALF_PIXEL_CENTERS: flatbuffers::VOffsetT = 10;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ResizeBilinearOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ResizeBilinearOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ResizeBilinearOptions<'bldr>> {\n            let mut builder = ResizeBilinearOptionsBuilder::new(_fbb);\n            builder.add_half_pixel_centers(args.half_pixel_centers);\n            builder.add_align_corners(args.align_corners);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn align_corners(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<bool>(ResizeBilinearOptions::VT_ALIGN_CORNERS, Some(false)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn half_pixel_centers(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(ResizeBilinearOptions::VT_HALF_PIXEL_CENTERS, Some(false))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ResizeBilinearOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<bool>(\"align_corners\", Self::VT_ALIGN_CORNERS, false)?\n                .visit_field::<bool>(\"half_pixel_centers\", Self::VT_HALF_PIXEL_CENTERS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ResizeBilinearOptionsArgs {\n        pub align_corners: bool,\n        pub half_pixel_centers: bool,\n    }\n    impl<'a> Default for ResizeBilinearOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ResizeBilinearOptionsArgs { align_corners: false, half_pixel_centers: false }\n        }\n    }\n\n    pub struct ResizeBilinearOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ResizeBilinearOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_align_corners(&mut self, align_corners: bool) {\n            self.fbb_.push_slot::<bool>(\n                ResizeBilinearOptions::VT_ALIGN_CORNERS,\n                align_corners,\n                false,\n            );\n        }\n        #[inline]\n        pub fn add_half_pixel_centers(&mut self, half_pixel_centers: bool) {\n            self.fbb_.push_slot::<bool>(\n                ResizeBilinearOptions::VT_HALF_PIXEL_CENTERS,\n                half_pixel_centers,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ResizeBilinearOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ResizeBilinearOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ResizeBilinearOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ResizeBilinearOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ResizeBilinearOptions\");\n            ds.field(\"align_corners\", &self.align_corners());\n            ds.field(\"half_pixel_centers\", &self.half_pixel_centers());\n            ds.finish()\n        }\n    }\n    pub enum ResizeNearestNeighborOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ResizeNearestNeighborOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ResizeNearestNeighborOptions<'a> {\n        type Inner = ResizeNearestNeighborOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ResizeNearestNeighborOptions<'a> {\n        pub const VT_ALIGN_CORNERS: flatbuffers::VOffsetT = 4;\n        pub const VT_HALF_PIXEL_CENTERS: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ResizeNearestNeighborOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ResizeNearestNeighborOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ResizeNearestNeighborOptions<'bldr>> {\n            let mut builder = ResizeNearestNeighborOptionsBuilder::new(_fbb);\n            builder.add_half_pixel_centers(args.half_pixel_centers);\n            builder.add_align_corners(args.align_corners);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn align_corners(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(ResizeNearestNeighborOptions::VT_ALIGN_CORNERS, Some(false))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn half_pixel_centers(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(ResizeNearestNeighborOptions::VT_HALF_PIXEL_CENTERS, Some(false))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ResizeNearestNeighborOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<bool>(\"align_corners\", Self::VT_ALIGN_CORNERS, false)?\n                .visit_field::<bool>(\"half_pixel_centers\", Self::VT_HALF_PIXEL_CENTERS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ResizeNearestNeighborOptionsArgs {\n        pub align_corners: bool,\n        pub half_pixel_centers: bool,\n    }\n    impl<'a> Default for ResizeNearestNeighborOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ResizeNearestNeighborOptionsArgs { align_corners: false, half_pixel_centers: false }\n        }\n    }\n\n    pub struct ResizeNearestNeighborOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ResizeNearestNeighborOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_align_corners(&mut self, align_corners: bool) {\n            self.fbb_.push_slot::<bool>(\n                ResizeNearestNeighborOptions::VT_ALIGN_CORNERS,\n                align_corners,\n                false,\n            );\n        }\n        #[inline]\n        pub fn add_half_pixel_centers(&mut self, half_pixel_centers: bool) {\n            self.fbb_.push_slot::<bool>(\n                ResizeNearestNeighborOptions::VT_HALF_PIXEL_CENTERS,\n                half_pixel_centers,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ResizeNearestNeighborOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ResizeNearestNeighborOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ResizeNearestNeighborOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ResizeNearestNeighborOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ResizeNearestNeighborOptions\");\n            ds.field(\"align_corners\", &self.align_corners());\n            ds.field(\"half_pixel_centers\", &self.half_pixel_centers());\n            ds.finish()\n        }\n    }\n    pub enum CallOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct CallOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for CallOptions<'a> {\n        type Inner = CallOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> CallOptions<'a> {\n        pub const VT_SUBGRAPH: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            CallOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args CallOptionsArgs,\n        ) -> flatbuffers::WIPOffset<CallOptions<'bldr>> {\n            let mut builder = CallOptionsBuilder::new(_fbb);\n            builder.add_subgraph(args.subgraph);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn subgraph(&self) -> u32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<u32>(CallOptions::VT_SUBGRAPH, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for CallOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.visit_field::<u32>(\"subgraph\", Self::VT_SUBGRAPH, false)?.finish();\n            Ok(())\n        }\n    }\n    pub struct CallOptionsArgs {\n        pub subgraph: u32,\n    }\n    impl<'a> Default for CallOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            CallOptionsArgs { subgraph: 0 }\n        }\n    }\n\n    pub struct CallOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> CallOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_subgraph(&mut self, subgraph: u32) {\n            self.fbb_.push_slot::<u32>(CallOptions::VT_SUBGRAPH, subgraph, 0);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> CallOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            CallOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<CallOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for CallOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"CallOptions\");\n            ds.field(\"subgraph\", &self.subgraph());\n            ds.finish()\n        }\n    }\n    pub enum PadOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct PadOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for PadOptions<'a> {\n        type Inner = PadOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> PadOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            PadOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args PadOptionsArgs,\n        ) -> flatbuffers::WIPOffset<PadOptions<'bldr>> {\n            let mut builder = PadOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for PadOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct PadOptionsArgs {}\n    impl<'a> Default for PadOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            PadOptionsArgs {}\n        }\n    }\n\n    pub struct PadOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> PadOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> PadOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            PadOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<PadOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for PadOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"PadOptions\");\n            ds.finish()\n        }\n    }\n    pub enum PadV2OptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct PadV2Options<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for PadV2Options<'a> {\n        type Inner = PadV2Options<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> PadV2Options<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            PadV2Options { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args PadV2OptionsArgs,\n        ) -> flatbuffers::WIPOffset<PadV2Options<'bldr>> {\n            let mut builder = PadV2OptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for PadV2Options<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct PadV2OptionsArgs {}\n    impl<'a> Default for PadV2OptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            PadV2OptionsArgs {}\n        }\n    }\n\n    pub struct PadV2OptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> PadV2OptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> PadV2OptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            PadV2OptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<PadV2Options<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for PadV2Options<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"PadV2Options\");\n            ds.finish()\n        }\n    }\n    pub enum ReshapeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ReshapeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ReshapeOptions<'a> {\n        type Inner = ReshapeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ReshapeOptions<'a> {\n        pub const VT_NEW_SHAPE: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ReshapeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ReshapeOptionsArgs<'args>,\n        ) -> flatbuffers::WIPOffset<ReshapeOptions<'bldr>> {\n            let mut builder = ReshapeOptionsBuilder::new(_fbb);\n            if let Some(x) = args.new_shape {\n                builder.add_new_shape(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn new_shape(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    ReshapeOptions::VT_NEW_SHAPE,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ReshapeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"new_shape\",\n                    Self::VT_NEW_SHAPE,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ReshapeOptionsArgs<'a> {\n        pub new_shape: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n    }\n    impl<'a> Default for ReshapeOptionsArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            ReshapeOptionsArgs { new_shape: None }\n        }\n    }\n\n    pub struct ReshapeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ReshapeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_new_shape(\n            &mut self,\n            new_shape: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                ReshapeOptions::VT_NEW_SHAPE,\n                new_shape,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ReshapeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ReshapeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ReshapeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ReshapeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ReshapeOptions\");\n            ds.field(\"new_shape\", &self.new_shape());\n            ds.finish()\n        }\n    }\n    pub enum SpaceToBatchNDOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SpaceToBatchNDOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SpaceToBatchNDOptions<'a> {\n        type Inner = SpaceToBatchNDOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SpaceToBatchNDOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SpaceToBatchNDOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args SpaceToBatchNDOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SpaceToBatchNDOptions<'bldr>> {\n            let mut builder = SpaceToBatchNDOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for SpaceToBatchNDOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct SpaceToBatchNDOptionsArgs {}\n    impl<'a> Default for SpaceToBatchNDOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SpaceToBatchNDOptionsArgs {}\n        }\n    }\n\n    pub struct SpaceToBatchNDOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SpaceToBatchNDOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SpaceToBatchNDOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SpaceToBatchNDOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SpaceToBatchNDOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SpaceToBatchNDOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SpaceToBatchNDOptions\");\n            ds.finish()\n        }\n    }\n    pub enum BatchToSpaceNDOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct BatchToSpaceNDOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for BatchToSpaceNDOptions<'a> {\n        type Inner = BatchToSpaceNDOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> BatchToSpaceNDOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            BatchToSpaceNDOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args BatchToSpaceNDOptionsArgs,\n        ) -> flatbuffers::WIPOffset<BatchToSpaceNDOptions<'bldr>> {\n            let mut builder = BatchToSpaceNDOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for BatchToSpaceNDOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct BatchToSpaceNDOptionsArgs {}\n    impl<'a> Default for BatchToSpaceNDOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            BatchToSpaceNDOptionsArgs {}\n        }\n    }\n\n    pub struct BatchToSpaceNDOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> BatchToSpaceNDOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> BatchToSpaceNDOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            BatchToSpaceNDOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<BatchToSpaceNDOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for BatchToSpaceNDOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"BatchToSpaceNDOptions\");\n            ds.finish()\n        }\n    }\n    pub enum SkipGramOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SkipGramOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SkipGramOptions<'a> {\n        type Inner = SkipGramOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SkipGramOptions<'a> {\n        pub const VT_NGRAM_SIZE: flatbuffers::VOffsetT = 4;\n        pub const VT_MAX_SKIP_SIZE: flatbuffers::VOffsetT = 6;\n        pub const VT_INCLUDE_ALL_NGRAMS: flatbuffers::VOffsetT = 8;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SkipGramOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SkipGramOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SkipGramOptions<'bldr>> {\n            let mut builder = SkipGramOptionsBuilder::new(_fbb);\n            builder.add_max_skip_size(args.max_skip_size);\n            builder.add_ngram_size(args.ngram_size);\n            builder.add_include_all_ngrams(args.include_all_ngrams);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn ngram_size(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(SkipGramOptions::VT_NGRAM_SIZE, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn max_skip_size(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(SkipGramOptions::VT_MAX_SKIP_SIZE, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn include_all_ngrams(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<bool>(SkipGramOptions::VT_INCLUDE_ALL_NGRAMS, Some(false)).unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SkipGramOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"ngram_size\", Self::VT_NGRAM_SIZE, false)?\n                .visit_field::<i32>(\"max_skip_size\", Self::VT_MAX_SKIP_SIZE, false)?\n                .visit_field::<bool>(\"include_all_ngrams\", Self::VT_INCLUDE_ALL_NGRAMS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SkipGramOptionsArgs {\n        pub ngram_size: i32,\n        pub max_skip_size: i32,\n        pub include_all_ngrams: bool,\n    }\n    impl<'a> Default for SkipGramOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SkipGramOptionsArgs { ngram_size: 0, max_skip_size: 0, include_all_ngrams: false }\n        }\n    }\n\n    pub struct SkipGramOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SkipGramOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_ngram_size(&mut self, ngram_size: i32) {\n            self.fbb_.push_slot::<i32>(SkipGramOptions::VT_NGRAM_SIZE, ngram_size, 0);\n        }\n        #[inline]\n        pub fn add_max_skip_size(&mut self, max_skip_size: i32) {\n            self.fbb_.push_slot::<i32>(SkipGramOptions::VT_MAX_SKIP_SIZE, max_skip_size, 0);\n        }\n        #[inline]\n        pub fn add_include_all_ngrams(&mut self, include_all_ngrams: bool) {\n            self.fbb_.push_slot::<bool>(\n                SkipGramOptions::VT_INCLUDE_ALL_NGRAMS,\n                include_all_ngrams,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SkipGramOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SkipGramOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SkipGramOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SkipGramOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SkipGramOptions\");\n            ds.field(\"ngram_size\", &self.ngram_size());\n            ds.field(\"max_skip_size\", &self.max_skip_size());\n            ds.field(\"include_all_ngrams\", &self.include_all_ngrams());\n            ds.finish()\n        }\n    }\n    pub enum SpaceToDepthOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SpaceToDepthOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SpaceToDepthOptions<'a> {\n        type Inner = SpaceToDepthOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SpaceToDepthOptions<'a> {\n        pub const VT_BLOCK_SIZE: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SpaceToDepthOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SpaceToDepthOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SpaceToDepthOptions<'bldr>> {\n            let mut builder = SpaceToDepthOptionsBuilder::new(_fbb);\n            builder.add_block_size(args.block_size);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn block_size(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(SpaceToDepthOptions::VT_BLOCK_SIZE, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SpaceToDepthOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"block_size\", Self::VT_BLOCK_SIZE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SpaceToDepthOptionsArgs {\n        pub block_size: i32,\n    }\n    impl<'a> Default for SpaceToDepthOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SpaceToDepthOptionsArgs { block_size: 0 }\n        }\n    }\n\n    pub struct SpaceToDepthOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SpaceToDepthOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_block_size(&mut self, block_size: i32) {\n            self.fbb_.push_slot::<i32>(SpaceToDepthOptions::VT_BLOCK_SIZE, block_size, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SpaceToDepthOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SpaceToDepthOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SpaceToDepthOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SpaceToDepthOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SpaceToDepthOptions\");\n            ds.field(\"block_size\", &self.block_size());\n            ds.finish()\n        }\n    }\n    pub enum DepthToSpaceOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct DepthToSpaceOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for DepthToSpaceOptions<'a> {\n        type Inner = DepthToSpaceOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> DepthToSpaceOptions<'a> {\n        pub const VT_BLOCK_SIZE: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            DepthToSpaceOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args DepthToSpaceOptionsArgs,\n        ) -> flatbuffers::WIPOffset<DepthToSpaceOptions<'bldr>> {\n            let mut builder = DepthToSpaceOptionsBuilder::new(_fbb);\n            builder.add_block_size(args.block_size);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn block_size(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(DepthToSpaceOptions::VT_BLOCK_SIZE, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for DepthToSpaceOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"block_size\", Self::VT_BLOCK_SIZE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct DepthToSpaceOptionsArgs {\n        pub block_size: i32,\n    }\n    impl<'a> Default for DepthToSpaceOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            DepthToSpaceOptionsArgs { block_size: 0 }\n        }\n    }\n\n    pub struct DepthToSpaceOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> DepthToSpaceOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_block_size(&mut self, block_size: i32) {\n            self.fbb_.push_slot::<i32>(DepthToSpaceOptions::VT_BLOCK_SIZE, block_size, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> DepthToSpaceOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            DepthToSpaceOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<DepthToSpaceOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for DepthToSpaceOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"DepthToSpaceOptions\");\n            ds.field(\"block_size\", &self.block_size());\n            ds.finish()\n        }\n    }\n    pub enum SubOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SubOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SubOptions<'a> {\n        type Inner = SubOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SubOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n        pub const VT_POT_SCALE_INT16: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SubOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SubOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SubOptions<'bldr>> {\n            let mut builder = SubOptionsBuilder::new(_fbb);\n            builder.add_pot_scale_int16(args.pot_scale_int16);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        SubOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn pot_scale_int16(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(SubOptions::VT_POT_SCALE_INT16, Some(true)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SubOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .visit_field::<bool>(\"pot_scale_int16\", Self::VT_POT_SCALE_INT16, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SubOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n        pub pot_scale_int16: bool,\n    }\n    impl<'a> Default for SubOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SubOptionsArgs {\n                fused_activation_function: ActivationFunctionType::NONE,\n                pot_scale_int16: true,\n            }\n        }\n    }\n\n    pub struct SubOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SubOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                SubOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_pot_scale_int16(&mut self, pot_scale_int16: bool) {\n            self.fbb_.push_slot::<bool>(SubOptions::VT_POT_SCALE_INT16, pot_scale_int16, true);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> SubOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SubOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SubOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SubOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SubOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.field(\"pot_scale_int16\", &self.pot_scale_int16());\n            ds.finish()\n        }\n    }\n    pub enum DivOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct DivOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for DivOptions<'a> {\n        type Inner = DivOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> DivOptions<'a> {\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            DivOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args DivOptionsArgs,\n        ) -> flatbuffers::WIPOffset<DivOptions<'bldr>> {\n            let mut builder = DivOptionsBuilder::new(_fbb);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        DivOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for DivOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct DivOptionsArgs {\n        pub fused_activation_function: ActivationFunctionType,\n    }\n    impl<'a> Default for DivOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            DivOptionsArgs { fused_activation_function: ActivationFunctionType::NONE }\n        }\n    }\n\n    pub struct DivOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> DivOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                DivOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> DivOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            DivOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<DivOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for DivOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"DivOptions\");\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.finish()\n        }\n    }\n    pub enum TopKV2OptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct TopKV2Options<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for TopKV2Options<'a> {\n        type Inner = TopKV2Options<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> TopKV2Options<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            TopKV2Options { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args TopKV2OptionsArgs,\n        ) -> flatbuffers::WIPOffset<TopKV2Options<'bldr>> {\n            let mut builder = TopKV2OptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for TopKV2Options<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct TopKV2OptionsArgs {}\n    impl<'a> Default for TopKV2OptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            TopKV2OptionsArgs {}\n        }\n    }\n\n    pub struct TopKV2OptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> TopKV2OptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> TopKV2OptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            TopKV2OptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<TopKV2Options<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for TopKV2Options<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"TopKV2Options\");\n            ds.finish()\n        }\n    }\n    pub enum EmbeddingLookupSparseOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct EmbeddingLookupSparseOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for EmbeddingLookupSparseOptions<'a> {\n        type Inner = EmbeddingLookupSparseOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> EmbeddingLookupSparseOptions<'a> {\n        pub const VT_COMBINER: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            EmbeddingLookupSparseOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args EmbeddingLookupSparseOptionsArgs,\n        ) -> flatbuffers::WIPOffset<EmbeddingLookupSparseOptions<'bldr>> {\n            let mut builder = EmbeddingLookupSparseOptionsBuilder::new(_fbb);\n            builder.add_combiner(args.combiner);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn combiner(&self) -> CombinerType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<CombinerType>(\n                        EmbeddingLookupSparseOptions::VT_COMBINER,\n                        Some(CombinerType::SUM),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for EmbeddingLookupSparseOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<CombinerType>(\"combiner\", Self::VT_COMBINER, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct EmbeddingLookupSparseOptionsArgs {\n        pub combiner: CombinerType,\n    }\n    impl<'a> Default for EmbeddingLookupSparseOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            EmbeddingLookupSparseOptionsArgs { combiner: CombinerType::SUM }\n        }\n    }\n\n    pub struct EmbeddingLookupSparseOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> EmbeddingLookupSparseOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_combiner(&mut self, combiner: CombinerType) {\n            self.fbb_.push_slot::<CombinerType>(\n                EmbeddingLookupSparseOptions::VT_COMBINER,\n                combiner,\n                CombinerType::SUM,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> EmbeddingLookupSparseOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            EmbeddingLookupSparseOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<EmbeddingLookupSparseOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for EmbeddingLookupSparseOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"EmbeddingLookupSparseOptions\");\n            ds.field(\"combiner\", &self.combiner());\n            ds.finish()\n        }\n    }\n    pub enum GatherOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct GatherOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for GatherOptions<'a> {\n        type Inner = GatherOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> GatherOptions<'a> {\n        pub const VT_AXIS: flatbuffers::VOffsetT = 4;\n        pub const VT_BATCH_DIMS: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            GatherOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args GatherOptionsArgs,\n        ) -> flatbuffers::WIPOffset<GatherOptions<'bldr>> {\n            let mut builder = GatherOptionsBuilder::new(_fbb);\n            builder.add_batch_dims(args.batch_dims);\n            builder.add_axis(args.axis);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn axis(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(GatherOptions::VT_AXIS, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn batch_dims(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(GatherOptions::VT_BATCH_DIMS, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for GatherOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"axis\", Self::VT_AXIS, false)?\n                .visit_field::<i32>(\"batch_dims\", Self::VT_BATCH_DIMS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct GatherOptionsArgs {\n        pub axis: i32,\n        pub batch_dims: i32,\n    }\n    impl<'a> Default for GatherOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            GatherOptionsArgs { axis: 0, batch_dims: 0 }\n        }\n    }\n\n    pub struct GatherOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> GatherOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_axis(&mut self, axis: i32) {\n            self.fbb_.push_slot::<i32>(GatherOptions::VT_AXIS, axis, 0);\n        }\n        #[inline]\n        pub fn add_batch_dims(&mut self, batch_dims: i32) {\n            self.fbb_.push_slot::<i32>(GatherOptions::VT_BATCH_DIMS, batch_dims, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> GatherOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            GatherOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<GatherOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for GatherOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"GatherOptions\");\n            ds.field(\"axis\", &self.axis());\n            ds.field(\"batch_dims\", &self.batch_dims());\n            ds.finish()\n        }\n    }\n    pub enum TransposeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct TransposeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for TransposeOptions<'a> {\n        type Inner = TransposeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> TransposeOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            TransposeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args TransposeOptionsArgs,\n        ) -> flatbuffers::WIPOffset<TransposeOptions<'bldr>> {\n            let mut builder = TransposeOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for TransposeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct TransposeOptionsArgs {}\n    impl<'a> Default for TransposeOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            TransposeOptionsArgs {}\n        }\n    }\n\n    pub struct TransposeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> TransposeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> TransposeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            TransposeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<TransposeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for TransposeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"TransposeOptions\");\n            ds.finish()\n        }\n    }\n    pub enum ExpOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ExpOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ExpOptions<'a> {\n        type Inner = ExpOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ExpOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ExpOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args ExpOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ExpOptions<'bldr>> {\n            let mut builder = ExpOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for ExpOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct ExpOptionsArgs {}\n    impl<'a> Default for ExpOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ExpOptionsArgs {}\n        }\n    }\n\n    pub struct ExpOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ExpOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> ExpOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ExpOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ExpOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ExpOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ExpOptions\");\n            ds.finish()\n        }\n    }\n    pub enum CosOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct CosOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for CosOptions<'a> {\n        type Inner = CosOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> CosOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            CosOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args CosOptionsArgs,\n        ) -> flatbuffers::WIPOffset<CosOptions<'bldr>> {\n            let mut builder = CosOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for CosOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct CosOptionsArgs {}\n    impl<'a> Default for CosOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            CosOptionsArgs {}\n        }\n    }\n\n    pub struct CosOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> CosOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> CosOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            CosOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<CosOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for CosOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"CosOptions\");\n            ds.finish()\n        }\n    }\n    pub enum ReducerOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ReducerOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ReducerOptions<'a> {\n        type Inner = ReducerOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ReducerOptions<'a> {\n        pub const VT_KEEP_DIMS: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ReducerOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ReducerOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ReducerOptions<'bldr>> {\n            let mut builder = ReducerOptionsBuilder::new(_fbb);\n            builder.add_keep_dims(args.keep_dims);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn keep_dims(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(ReducerOptions::VT_KEEP_DIMS, Some(false)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ReducerOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<bool>(\"keep_dims\", Self::VT_KEEP_DIMS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ReducerOptionsArgs {\n        pub keep_dims: bool,\n    }\n    impl<'a> Default for ReducerOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ReducerOptionsArgs { keep_dims: false }\n        }\n    }\n\n    pub struct ReducerOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ReducerOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_keep_dims(&mut self, keep_dims: bool) {\n            self.fbb_.push_slot::<bool>(ReducerOptions::VT_KEEP_DIMS, keep_dims, false);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ReducerOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ReducerOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ReducerOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ReducerOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ReducerOptions\");\n            ds.field(\"keep_dims\", &self.keep_dims());\n            ds.finish()\n        }\n    }\n    pub enum SqueezeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SqueezeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SqueezeOptions<'a> {\n        type Inner = SqueezeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SqueezeOptions<'a> {\n        pub const VT_SQUEEZE_DIMS: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SqueezeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SqueezeOptionsArgs<'args>,\n        ) -> flatbuffers::WIPOffset<SqueezeOptions<'bldr>> {\n            let mut builder = SqueezeOptionsBuilder::new(_fbb);\n            if let Some(x) = args.squeeze_dims {\n                builder.add_squeeze_dims(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn squeeze_dims(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    SqueezeOptions::VT_SQUEEZE_DIMS,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SqueezeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"squeeze_dims\",\n                    Self::VT_SQUEEZE_DIMS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SqueezeOptionsArgs<'a> {\n        pub squeeze_dims: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n    }\n    impl<'a> Default for SqueezeOptionsArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            SqueezeOptionsArgs { squeeze_dims: None }\n        }\n    }\n\n    pub struct SqueezeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SqueezeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_squeeze_dims(\n            &mut self,\n            squeeze_dims: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                SqueezeOptions::VT_SQUEEZE_DIMS,\n                squeeze_dims,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SqueezeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SqueezeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SqueezeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SqueezeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SqueezeOptions\");\n            ds.field(\"squeeze_dims\", &self.squeeze_dims());\n            ds.finish()\n        }\n    }\n    pub enum SplitOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SplitOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SplitOptions<'a> {\n        type Inner = SplitOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SplitOptions<'a> {\n        pub const VT_NUM_SPLITS: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SplitOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SplitOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SplitOptions<'bldr>> {\n            let mut builder = SplitOptionsBuilder::new(_fbb);\n            builder.add_num_splits(args.num_splits);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn num_splits(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(SplitOptions::VT_NUM_SPLITS, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SplitOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"num_splits\", Self::VT_NUM_SPLITS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SplitOptionsArgs {\n        pub num_splits: i32,\n    }\n    impl<'a> Default for SplitOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SplitOptionsArgs { num_splits: 0 }\n        }\n    }\n\n    pub struct SplitOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SplitOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_num_splits(&mut self, num_splits: i32) {\n            self.fbb_.push_slot::<i32>(SplitOptions::VT_NUM_SPLITS, num_splits, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SplitOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SplitOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SplitOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SplitOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SplitOptions\");\n            ds.field(\"num_splits\", &self.num_splits());\n            ds.finish()\n        }\n    }\n    pub enum SplitVOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SplitVOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SplitVOptions<'a> {\n        type Inner = SplitVOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SplitVOptions<'a> {\n        pub const VT_NUM_SPLITS: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SplitVOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SplitVOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SplitVOptions<'bldr>> {\n            let mut builder = SplitVOptionsBuilder::new(_fbb);\n            builder.add_num_splits(args.num_splits);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn num_splits(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(SplitVOptions::VT_NUM_SPLITS, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SplitVOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"num_splits\", Self::VT_NUM_SPLITS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SplitVOptionsArgs {\n        pub num_splits: i32,\n    }\n    impl<'a> Default for SplitVOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SplitVOptionsArgs { num_splits: 0 }\n        }\n    }\n\n    pub struct SplitVOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SplitVOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_num_splits(&mut self, num_splits: i32) {\n            self.fbb_.push_slot::<i32>(SplitVOptions::VT_NUM_SPLITS, num_splits, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SplitVOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SplitVOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SplitVOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SplitVOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SplitVOptions\");\n            ds.field(\"num_splits\", &self.num_splits());\n            ds.finish()\n        }\n    }\n    pub enum StridedSliceOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct StridedSliceOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for StridedSliceOptions<'a> {\n        type Inner = StridedSliceOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> StridedSliceOptions<'a> {\n        pub const VT_BEGIN_MASK: flatbuffers::VOffsetT = 4;\n        pub const VT_END_MASK: flatbuffers::VOffsetT = 6;\n        pub const VT_ELLIPSIS_MASK: flatbuffers::VOffsetT = 8;\n        pub const VT_NEW_AXIS_MASK: flatbuffers::VOffsetT = 10;\n        pub const VT_SHRINK_AXIS_MASK: flatbuffers::VOffsetT = 12;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            StridedSliceOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args StridedSliceOptionsArgs,\n        ) -> flatbuffers::WIPOffset<StridedSliceOptions<'bldr>> {\n            let mut builder = StridedSliceOptionsBuilder::new(_fbb);\n            builder.add_shrink_axis_mask(args.shrink_axis_mask);\n            builder.add_new_axis_mask(args.new_axis_mask);\n            builder.add_ellipsis_mask(args.ellipsis_mask);\n            builder.add_end_mask(args.end_mask);\n            builder.add_begin_mask(args.begin_mask);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn begin_mask(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(StridedSliceOptions::VT_BEGIN_MASK, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn end_mask(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(StridedSliceOptions::VT_END_MASK, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn ellipsis_mask(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(StridedSliceOptions::VT_ELLIPSIS_MASK, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn new_axis_mask(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(StridedSliceOptions::VT_NEW_AXIS_MASK, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn shrink_axis_mask(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<i32>(StridedSliceOptions::VT_SHRINK_AXIS_MASK, Some(0)).unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for StridedSliceOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"begin_mask\", Self::VT_BEGIN_MASK, false)?\n                .visit_field::<i32>(\"end_mask\", Self::VT_END_MASK, false)?\n                .visit_field::<i32>(\"ellipsis_mask\", Self::VT_ELLIPSIS_MASK, false)?\n                .visit_field::<i32>(\"new_axis_mask\", Self::VT_NEW_AXIS_MASK, false)?\n                .visit_field::<i32>(\"shrink_axis_mask\", Self::VT_SHRINK_AXIS_MASK, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct StridedSliceOptionsArgs {\n        pub begin_mask: i32,\n        pub end_mask: i32,\n        pub ellipsis_mask: i32,\n        pub new_axis_mask: i32,\n        pub shrink_axis_mask: i32,\n    }\n    impl<'a> Default for StridedSliceOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            StridedSliceOptionsArgs {\n                begin_mask: 0,\n                end_mask: 0,\n                ellipsis_mask: 0,\n                new_axis_mask: 0,\n                shrink_axis_mask: 0,\n            }\n        }\n    }\n\n    pub struct StridedSliceOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> StridedSliceOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_begin_mask(&mut self, begin_mask: i32) {\n            self.fbb_.push_slot::<i32>(StridedSliceOptions::VT_BEGIN_MASK, begin_mask, 0);\n        }\n        #[inline]\n        pub fn add_end_mask(&mut self, end_mask: i32) {\n            self.fbb_.push_slot::<i32>(StridedSliceOptions::VT_END_MASK, end_mask, 0);\n        }\n        #[inline]\n        pub fn add_ellipsis_mask(&mut self, ellipsis_mask: i32) {\n            self.fbb_.push_slot::<i32>(StridedSliceOptions::VT_ELLIPSIS_MASK, ellipsis_mask, 0);\n        }\n        #[inline]\n        pub fn add_new_axis_mask(&mut self, new_axis_mask: i32) {\n            self.fbb_.push_slot::<i32>(StridedSliceOptions::VT_NEW_AXIS_MASK, new_axis_mask, 0);\n        }\n        #[inline]\n        pub fn add_shrink_axis_mask(&mut self, shrink_axis_mask: i32) {\n            self.fbb_.push_slot::<i32>(\n                StridedSliceOptions::VT_SHRINK_AXIS_MASK,\n                shrink_axis_mask,\n                0,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> StridedSliceOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            StridedSliceOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<StridedSliceOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for StridedSliceOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"StridedSliceOptions\");\n            ds.field(\"begin_mask\", &self.begin_mask());\n            ds.field(\"end_mask\", &self.end_mask());\n            ds.field(\"ellipsis_mask\", &self.ellipsis_mask());\n            ds.field(\"new_axis_mask\", &self.new_axis_mask());\n            ds.field(\"shrink_axis_mask\", &self.shrink_axis_mask());\n            ds.finish()\n        }\n    }\n    pub enum LogSoftmaxOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LogSoftmaxOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LogSoftmaxOptions<'a> {\n        type Inner = LogSoftmaxOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LogSoftmaxOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LogSoftmaxOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args LogSoftmaxOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LogSoftmaxOptions<'bldr>> {\n            let mut builder = LogSoftmaxOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for LogSoftmaxOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct LogSoftmaxOptionsArgs {}\n    impl<'a> Default for LogSoftmaxOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LogSoftmaxOptionsArgs {}\n        }\n    }\n\n    pub struct LogSoftmaxOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LogSoftmaxOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> LogSoftmaxOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LogSoftmaxOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LogSoftmaxOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LogSoftmaxOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LogSoftmaxOptions\");\n            ds.finish()\n        }\n    }\n    pub enum CastOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct CastOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for CastOptions<'a> {\n        type Inner = CastOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> CastOptions<'a> {\n        pub const VT_IN_DATA_TYPE: flatbuffers::VOffsetT = 4;\n        pub const VT_OUT_DATA_TYPE: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            CastOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args CastOptionsArgs,\n        ) -> flatbuffers::WIPOffset<CastOptions<'bldr>> {\n            let mut builder = CastOptionsBuilder::new(_fbb);\n            builder.add_out_data_type(args.out_data_type);\n            builder.add_in_data_type(args.in_data_type);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn in_data_type(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<TensorType>(CastOptions::VT_IN_DATA_TYPE, Some(TensorType::FLOAT32))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn out_data_type(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<TensorType>(CastOptions::VT_OUT_DATA_TYPE, Some(TensorType::FLOAT32))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for CastOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<TensorType>(\"in_data_type\", Self::VT_IN_DATA_TYPE, false)?\n                .visit_field::<TensorType>(\"out_data_type\", Self::VT_OUT_DATA_TYPE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct CastOptionsArgs {\n        pub in_data_type: TensorType,\n        pub out_data_type: TensorType,\n    }\n    impl<'a> Default for CastOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            CastOptionsArgs {\n                in_data_type: TensorType::FLOAT32,\n                out_data_type: TensorType::FLOAT32,\n            }\n        }\n    }\n\n    pub struct CastOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> CastOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_in_data_type(&mut self, in_data_type: TensorType) {\n            self.fbb_.push_slot::<TensorType>(\n                CastOptions::VT_IN_DATA_TYPE,\n                in_data_type,\n                TensorType::FLOAT32,\n            );\n        }\n        #[inline]\n        pub fn add_out_data_type(&mut self, out_data_type: TensorType) {\n            self.fbb_.push_slot::<TensorType>(\n                CastOptions::VT_OUT_DATA_TYPE,\n                out_data_type,\n                TensorType::FLOAT32,\n            );\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> CastOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            CastOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<CastOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for CastOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"CastOptions\");\n            ds.field(\"in_data_type\", &self.in_data_type());\n            ds.field(\"out_data_type\", &self.out_data_type());\n            ds.finish()\n        }\n    }\n    pub enum DequantizeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct DequantizeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for DequantizeOptions<'a> {\n        type Inner = DequantizeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> DequantizeOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            DequantizeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args DequantizeOptionsArgs,\n        ) -> flatbuffers::WIPOffset<DequantizeOptions<'bldr>> {\n            let mut builder = DequantizeOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for DequantizeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct DequantizeOptionsArgs {}\n    impl<'a> Default for DequantizeOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            DequantizeOptionsArgs {}\n        }\n    }\n\n    pub struct DequantizeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> DequantizeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> DequantizeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            DequantizeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<DequantizeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for DequantizeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"DequantizeOptions\");\n            ds.finish()\n        }\n    }\n    pub enum MaximumMinimumOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct MaximumMinimumOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for MaximumMinimumOptions<'a> {\n        type Inner = MaximumMinimumOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> MaximumMinimumOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            MaximumMinimumOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args MaximumMinimumOptionsArgs,\n        ) -> flatbuffers::WIPOffset<MaximumMinimumOptions<'bldr>> {\n            let mut builder = MaximumMinimumOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for MaximumMinimumOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct MaximumMinimumOptionsArgs {}\n    impl<'a> Default for MaximumMinimumOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            MaximumMinimumOptionsArgs {}\n        }\n    }\n\n    pub struct MaximumMinimumOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> MaximumMinimumOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> MaximumMinimumOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            MaximumMinimumOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<MaximumMinimumOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for MaximumMinimumOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"MaximumMinimumOptions\");\n            ds.finish()\n        }\n    }\n    pub enum TileOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct TileOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for TileOptions<'a> {\n        type Inner = TileOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> TileOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            TileOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args TileOptionsArgs,\n        ) -> flatbuffers::WIPOffset<TileOptions<'bldr>> {\n            let mut builder = TileOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for TileOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct TileOptionsArgs {}\n    impl<'a> Default for TileOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            TileOptionsArgs {}\n        }\n    }\n\n    pub struct TileOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> TileOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> TileOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            TileOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<TileOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for TileOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"TileOptions\");\n            ds.finish()\n        }\n    }\n    pub enum ArgMaxOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ArgMaxOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ArgMaxOptions<'a> {\n        type Inner = ArgMaxOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ArgMaxOptions<'a> {\n        pub const VT_OUTPUT_TYPE: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ArgMaxOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ArgMaxOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ArgMaxOptions<'bldr>> {\n            let mut builder = ArgMaxOptionsBuilder::new(_fbb);\n            builder.add_output_type(args.output_type);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn output_type(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<TensorType>(ArgMaxOptions::VT_OUTPUT_TYPE, Some(TensorType::FLOAT32))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ArgMaxOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<TensorType>(\"output_type\", Self::VT_OUTPUT_TYPE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ArgMaxOptionsArgs {\n        pub output_type: TensorType,\n    }\n    impl<'a> Default for ArgMaxOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ArgMaxOptionsArgs { output_type: TensorType::FLOAT32 }\n        }\n    }\n\n    pub struct ArgMaxOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ArgMaxOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_output_type(&mut self, output_type: TensorType) {\n            self.fbb_.push_slot::<TensorType>(\n                ArgMaxOptions::VT_OUTPUT_TYPE,\n                output_type,\n                TensorType::FLOAT32,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ArgMaxOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ArgMaxOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ArgMaxOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ArgMaxOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ArgMaxOptions\");\n            ds.field(\"output_type\", &self.output_type());\n            ds.finish()\n        }\n    }\n    pub enum ArgMinOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ArgMinOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ArgMinOptions<'a> {\n        type Inner = ArgMinOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ArgMinOptions<'a> {\n        pub const VT_OUTPUT_TYPE: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ArgMinOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ArgMinOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ArgMinOptions<'bldr>> {\n            let mut builder = ArgMinOptionsBuilder::new(_fbb);\n            builder.add_output_type(args.output_type);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn output_type(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<TensorType>(ArgMinOptions::VT_OUTPUT_TYPE, Some(TensorType::FLOAT32))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ArgMinOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<TensorType>(\"output_type\", Self::VT_OUTPUT_TYPE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ArgMinOptionsArgs {\n        pub output_type: TensorType,\n    }\n    impl<'a> Default for ArgMinOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ArgMinOptionsArgs { output_type: TensorType::FLOAT32 }\n        }\n    }\n\n    pub struct ArgMinOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ArgMinOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_output_type(&mut self, output_type: TensorType) {\n            self.fbb_.push_slot::<TensorType>(\n                ArgMinOptions::VT_OUTPUT_TYPE,\n                output_type,\n                TensorType::FLOAT32,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ArgMinOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ArgMinOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ArgMinOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ArgMinOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ArgMinOptions\");\n            ds.field(\"output_type\", &self.output_type());\n            ds.finish()\n        }\n    }\n    pub enum GreaterOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct GreaterOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for GreaterOptions<'a> {\n        type Inner = GreaterOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> GreaterOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            GreaterOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args GreaterOptionsArgs,\n        ) -> flatbuffers::WIPOffset<GreaterOptions<'bldr>> {\n            let mut builder = GreaterOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for GreaterOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct GreaterOptionsArgs {}\n    impl<'a> Default for GreaterOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            GreaterOptionsArgs {}\n        }\n    }\n\n    pub struct GreaterOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> GreaterOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> GreaterOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            GreaterOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<GreaterOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for GreaterOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"GreaterOptions\");\n            ds.finish()\n        }\n    }\n    pub enum GreaterEqualOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct GreaterEqualOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for GreaterEqualOptions<'a> {\n        type Inner = GreaterEqualOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> GreaterEqualOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            GreaterEqualOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args GreaterEqualOptionsArgs,\n        ) -> flatbuffers::WIPOffset<GreaterEqualOptions<'bldr>> {\n            let mut builder = GreaterEqualOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for GreaterEqualOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct GreaterEqualOptionsArgs {}\n    impl<'a> Default for GreaterEqualOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            GreaterEqualOptionsArgs {}\n        }\n    }\n\n    pub struct GreaterEqualOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> GreaterEqualOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> GreaterEqualOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            GreaterEqualOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<GreaterEqualOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for GreaterEqualOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"GreaterEqualOptions\");\n            ds.finish()\n        }\n    }\n    pub enum LessOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LessOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LessOptions<'a> {\n        type Inner = LessOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LessOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LessOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args LessOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LessOptions<'bldr>> {\n            let mut builder = LessOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for LessOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct LessOptionsArgs {}\n    impl<'a> Default for LessOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LessOptionsArgs {}\n        }\n    }\n\n    pub struct LessOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LessOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> LessOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LessOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LessOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LessOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LessOptions\");\n            ds.finish()\n        }\n    }\n    pub enum LessEqualOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LessEqualOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LessEqualOptions<'a> {\n        type Inner = LessEqualOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LessEqualOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LessEqualOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args LessEqualOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LessEqualOptions<'bldr>> {\n            let mut builder = LessEqualOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for LessEqualOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct LessEqualOptionsArgs {}\n    impl<'a> Default for LessEqualOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LessEqualOptionsArgs {}\n        }\n    }\n\n    pub struct LessEqualOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LessEqualOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> LessEqualOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LessEqualOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LessEqualOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LessEqualOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LessEqualOptions\");\n            ds.finish()\n        }\n    }\n    pub enum NegOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct NegOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for NegOptions<'a> {\n        type Inner = NegOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> NegOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            NegOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args NegOptionsArgs,\n        ) -> flatbuffers::WIPOffset<NegOptions<'bldr>> {\n            let mut builder = NegOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for NegOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct NegOptionsArgs {}\n    impl<'a> Default for NegOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            NegOptionsArgs {}\n        }\n    }\n\n    pub struct NegOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> NegOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> NegOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            NegOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<NegOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for NegOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"NegOptions\");\n            ds.finish()\n        }\n    }\n    pub enum SelectOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SelectOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SelectOptions<'a> {\n        type Inner = SelectOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SelectOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SelectOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args SelectOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SelectOptions<'bldr>> {\n            let mut builder = SelectOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for SelectOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct SelectOptionsArgs {}\n    impl<'a> Default for SelectOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SelectOptionsArgs {}\n        }\n    }\n\n    pub struct SelectOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SelectOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SelectOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SelectOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SelectOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SelectOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SelectOptions\");\n            ds.finish()\n        }\n    }\n    pub enum SliceOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SliceOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SliceOptions<'a> {\n        type Inner = SliceOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SliceOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SliceOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args SliceOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SliceOptions<'bldr>> {\n            let mut builder = SliceOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for SliceOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct SliceOptionsArgs {}\n    impl<'a> Default for SliceOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SliceOptionsArgs {}\n        }\n    }\n\n    pub struct SliceOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SliceOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SliceOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SliceOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SliceOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SliceOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SliceOptions\");\n            ds.finish()\n        }\n    }\n    pub enum TransposeConvOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct TransposeConvOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for TransposeConvOptions<'a> {\n        type Inner = TransposeConvOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> TransposeConvOptions<'a> {\n        pub const VT_PADDING: flatbuffers::VOffsetT = 4;\n        pub const VT_STRIDE_W: flatbuffers::VOffsetT = 6;\n        pub const VT_STRIDE_H: flatbuffers::VOffsetT = 8;\n        pub const VT_FUSED_ACTIVATION_FUNCTION: flatbuffers::VOffsetT = 10;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            TransposeConvOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args TransposeConvOptionsArgs,\n        ) -> flatbuffers::WIPOffset<TransposeConvOptions<'bldr>> {\n            let mut builder = TransposeConvOptionsBuilder::new(_fbb);\n            builder.add_stride_h(args.stride_h);\n            builder.add_stride_w(args.stride_w);\n            builder.add_fused_activation_function(args.fused_activation_function);\n            builder.add_padding(args.padding);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn padding(&self) -> Padding {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<Padding>(TransposeConvOptions::VT_PADDING, Some(Padding::SAME))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn stride_w(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(TransposeConvOptions::VT_STRIDE_W, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn stride_h(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(TransposeConvOptions::VT_STRIDE_H, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn fused_activation_function(&self) -> ActivationFunctionType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<ActivationFunctionType>(\n                        TransposeConvOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                        Some(ActivationFunctionType::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for TransposeConvOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<Padding>(\"padding\", Self::VT_PADDING, false)?\n                .visit_field::<i32>(\"stride_w\", Self::VT_STRIDE_W, false)?\n                .visit_field::<i32>(\"stride_h\", Self::VT_STRIDE_H, false)?\n                .visit_field::<ActivationFunctionType>(\n                    \"fused_activation_function\",\n                    Self::VT_FUSED_ACTIVATION_FUNCTION,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct TransposeConvOptionsArgs {\n        pub padding: Padding,\n        pub stride_w: i32,\n        pub stride_h: i32,\n        pub fused_activation_function: ActivationFunctionType,\n    }\n    impl<'a> Default for TransposeConvOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            TransposeConvOptionsArgs {\n                padding: Padding::SAME,\n                stride_w: 0,\n                stride_h: 0,\n                fused_activation_function: ActivationFunctionType::NONE,\n            }\n        }\n    }\n\n    pub struct TransposeConvOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> TransposeConvOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_padding(&mut self, padding: Padding) {\n            self.fbb_.push_slot::<Padding>(\n                TransposeConvOptions::VT_PADDING,\n                padding,\n                Padding::SAME,\n            );\n        }\n        #[inline]\n        pub fn add_stride_w(&mut self, stride_w: i32) {\n            self.fbb_.push_slot::<i32>(TransposeConvOptions::VT_STRIDE_W, stride_w, 0);\n        }\n        #[inline]\n        pub fn add_stride_h(&mut self, stride_h: i32) {\n            self.fbb_.push_slot::<i32>(TransposeConvOptions::VT_STRIDE_H, stride_h, 0);\n        }\n        #[inline]\n        pub fn add_fused_activation_function(\n            &mut self,\n            fused_activation_function: ActivationFunctionType,\n        ) {\n            self.fbb_.push_slot::<ActivationFunctionType>(\n                TransposeConvOptions::VT_FUSED_ACTIVATION_FUNCTION,\n                fused_activation_function,\n                ActivationFunctionType::NONE,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> TransposeConvOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            TransposeConvOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<TransposeConvOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for TransposeConvOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"TransposeConvOptions\");\n            ds.field(\"padding\", &self.padding());\n            ds.field(\"stride_w\", &self.stride_w());\n            ds.field(\"stride_h\", &self.stride_h());\n            ds.field(\"fused_activation_function\", &self.fused_activation_function());\n            ds.finish()\n        }\n    }\n    pub enum ExpandDimsOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ExpandDimsOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ExpandDimsOptions<'a> {\n        type Inner = ExpandDimsOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ExpandDimsOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ExpandDimsOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args ExpandDimsOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ExpandDimsOptions<'bldr>> {\n            let mut builder = ExpandDimsOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for ExpandDimsOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct ExpandDimsOptionsArgs {}\n    impl<'a> Default for ExpandDimsOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ExpandDimsOptionsArgs {}\n        }\n    }\n\n    pub struct ExpandDimsOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ExpandDimsOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ExpandDimsOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ExpandDimsOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ExpandDimsOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ExpandDimsOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ExpandDimsOptions\");\n            ds.finish()\n        }\n    }\n    pub enum SparseToDenseOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SparseToDenseOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SparseToDenseOptions<'a> {\n        type Inner = SparseToDenseOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SparseToDenseOptions<'a> {\n        pub const VT_VALIDATE_INDICES: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SparseToDenseOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SparseToDenseOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SparseToDenseOptions<'bldr>> {\n            let mut builder = SparseToDenseOptionsBuilder::new(_fbb);\n            builder.add_validate_indices(args.validate_indices);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn validate_indices(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(SparseToDenseOptions::VT_VALIDATE_INDICES, Some(false))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SparseToDenseOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<bool>(\"validate_indices\", Self::VT_VALIDATE_INDICES, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SparseToDenseOptionsArgs {\n        pub validate_indices: bool,\n    }\n    impl<'a> Default for SparseToDenseOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SparseToDenseOptionsArgs { validate_indices: false }\n        }\n    }\n\n    pub struct SparseToDenseOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SparseToDenseOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_validate_indices(&mut self, validate_indices: bool) {\n            self.fbb_.push_slot::<bool>(\n                SparseToDenseOptions::VT_VALIDATE_INDICES,\n                validate_indices,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SparseToDenseOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SparseToDenseOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SparseToDenseOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SparseToDenseOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SparseToDenseOptions\");\n            ds.field(\"validate_indices\", &self.validate_indices());\n            ds.finish()\n        }\n    }\n    pub enum EqualOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct EqualOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for EqualOptions<'a> {\n        type Inner = EqualOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> EqualOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            EqualOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args EqualOptionsArgs,\n        ) -> flatbuffers::WIPOffset<EqualOptions<'bldr>> {\n            let mut builder = EqualOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for EqualOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct EqualOptionsArgs {}\n    impl<'a> Default for EqualOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            EqualOptionsArgs {}\n        }\n    }\n\n    pub struct EqualOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> EqualOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> EqualOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            EqualOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<EqualOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for EqualOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"EqualOptions\");\n            ds.finish()\n        }\n    }\n    pub enum NotEqualOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct NotEqualOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for NotEqualOptions<'a> {\n        type Inner = NotEqualOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> NotEqualOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            NotEqualOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args NotEqualOptionsArgs,\n        ) -> flatbuffers::WIPOffset<NotEqualOptions<'bldr>> {\n            let mut builder = NotEqualOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for NotEqualOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct NotEqualOptionsArgs {}\n    impl<'a> Default for NotEqualOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            NotEqualOptionsArgs {}\n        }\n    }\n\n    pub struct NotEqualOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> NotEqualOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> NotEqualOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            NotEqualOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<NotEqualOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for NotEqualOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"NotEqualOptions\");\n            ds.finish()\n        }\n    }\n    pub enum ShapeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ShapeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ShapeOptions<'a> {\n        type Inner = ShapeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ShapeOptions<'a> {\n        pub const VT_OUT_TYPE: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ShapeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ShapeOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ShapeOptions<'bldr>> {\n            let mut builder = ShapeOptionsBuilder::new(_fbb);\n            builder.add_out_type(args.out_type);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn out_type(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<TensorType>(ShapeOptions::VT_OUT_TYPE, Some(TensorType::FLOAT32))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ShapeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<TensorType>(\"out_type\", Self::VT_OUT_TYPE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ShapeOptionsArgs {\n        pub out_type: TensorType,\n    }\n    impl<'a> Default for ShapeOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ShapeOptionsArgs { out_type: TensorType::FLOAT32 }\n        }\n    }\n\n    pub struct ShapeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ShapeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_out_type(&mut self, out_type: TensorType) {\n            self.fbb_.push_slot::<TensorType>(\n                ShapeOptions::VT_OUT_TYPE,\n                out_type,\n                TensorType::FLOAT32,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ShapeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ShapeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ShapeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ShapeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ShapeOptions\");\n            ds.field(\"out_type\", &self.out_type());\n            ds.finish()\n        }\n    }\n    pub enum RankOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct RankOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for RankOptions<'a> {\n        type Inner = RankOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> RankOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            RankOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args RankOptionsArgs,\n        ) -> flatbuffers::WIPOffset<RankOptions<'bldr>> {\n            let mut builder = RankOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for RankOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct RankOptionsArgs {}\n    impl<'a> Default for RankOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            RankOptionsArgs {}\n        }\n    }\n\n    pub struct RankOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> RankOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> RankOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            RankOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<RankOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for RankOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"RankOptions\");\n            ds.finish()\n        }\n    }\n    pub enum PowOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct PowOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for PowOptions<'a> {\n        type Inner = PowOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> PowOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            PowOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args PowOptionsArgs,\n        ) -> flatbuffers::WIPOffset<PowOptions<'bldr>> {\n            let mut builder = PowOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for PowOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct PowOptionsArgs {}\n    impl<'a> Default for PowOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            PowOptionsArgs {}\n        }\n    }\n\n    pub struct PowOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> PowOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> PowOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            PowOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<PowOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for PowOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"PowOptions\");\n            ds.finish()\n        }\n    }\n    pub enum FakeQuantOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct FakeQuantOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for FakeQuantOptions<'a> {\n        type Inner = FakeQuantOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> FakeQuantOptions<'a> {\n        pub const VT_MIN: flatbuffers::VOffsetT = 4;\n        pub const VT_MAX: flatbuffers::VOffsetT = 6;\n        pub const VT_NUM_BITS: flatbuffers::VOffsetT = 8;\n        pub const VT_NARROW_RANGE: flatbuffers::VOffsetT = 10;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            FakeQuantOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args FakeQuantOptionsArgs,\n        ) -> flatbuffers::WIPOffset<FakeQuantOptions<'bldr>> {\n            let mut builder = FakeQuantOptionsBuilder::new(_fbb);\n            builder.add_num_bits(args.num_bits);\n            builder.add_max(args.max);\n            builder.add_min(args.min);\n            builder.add_narrow_range(args.narrow_range);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn min(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<f32>(FakeQuantOptions::VT_MIN, Some(0.0)).unwrap() }\n        }\n        #[inline]\n        pub fn max(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<f32>(FakeQuantOptions::VT_MAX, Some(0.0)).unwrap() }\n        }\n        #[inline]\n        pub fn num_bits(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(FakeQuantOptions::VT_NUM_BITS, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn narrow_range(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<bool>(FakeQuantOptions::VT_NARROW_RANGE, Some(false)).unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for FakeQuantOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<f32>(\"min\", Self::VT_MIN, false)?\n                .visit_field::<f32>(\"max\", Self::VT_MAX, false)?\n                .visit_field::<i32>(\"num_bits\", Self::VT_NUM_BITS, false)?\n                .visit_field::<bool>(\"narrow_range\", Self::VT_NARROW_RANGE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct FakeQuantOptionsArgs {\n        pub min: f32,\n        pub max: f32,\n        pub num_bits: i32,\n        pub narrow_range: bool,\n    }\n    impl<'a> Default for FakeQuantOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            FakeQuantOptionsArgs { min: 0.0, max: 0.0, num_bits: 0, narrow_range: false }\n        }\n    }\n\n    pub struct FakeQuantOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> FakeQuantOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_min(&mut self, min: f32) {\n            self.fbb_.push_slot::<f32>(FakeQuantOptions::VT_MIN, min, 0.0);\n        }\n        #[inline]\n        pub fn add_max(&mut self, max: f32) {\n            self.fbb_.push_slot::<f32>(FakeQuantOptions::VT_MAX, max, 0.0);\n        }\n        #[inline]\n        pub fn add_num_bits(&mut self, num_bits: i32) {\n            self.fbb_.push_slot::<i32>(FakeQuantOptions::VT_NUM_BITS, num_bits, 0);\n        }\n        #[inline]\n        pub fn add_narrow_range(&mut self, narrow_range: bool) {\n            self.fbb_.push_slot::<bool>(FakeQuantOptions::VT_NARROW_RANGE, narrow_range, false);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> FakeQuantOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            FakeQuantOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<FakeQuantOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for FakeQuantOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"FakeQuantOptions\");\n            ds.field(\"min\", &self.min());\n            ds.field(\"max\", &self.max());\n            ds.field(\"num_bits\", &self.num_bits());\n            ds.field(\"narrow_range\", &self.narrow_range());\n            ds.finish()\n        }\n    }\n    pub enum PackOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct PackOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for PackOptions<'a> {\n        type Inner = PackOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> PackOptions<'a> {\n        pub const VT_VALUES_COUNT: flatbuffers::VOffsetT = 4;\n        pub const VT_AXIS: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            PackOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args PackOptionsArgs,\n        ) -> flatbuffers::WIPOffset<PackOptions<'bldr>> {\n            let mut builder = PackOptionsBuilder::new(_fbb);\n            builder.add_axis(args.axis);\n            builder.add_values_count(args.values_count);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn values_count(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(PackOptions::VT_VALUES_COUNT, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn axis(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(PackOptions::VT_AXIS, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for PackOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"values_count\", Self::VT_VALUES_COUNT, false)?\n                .visit_field::<i32>(\"axis\", Self::VT_AXIS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct PackOptionsArgs {\n        pub values_count: i32,\n        pub axis: i32,\n    }\n    impl<'a> Default for PackOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            PackOptionsArgs { values_count: 0, axis: 0 }\n        }\n    }\n\n    pub struct PackOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> PackOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_values_count(&mut self, values_count: i32) {\n            self.fbb_.push_slot::<i32>(PackOptions::VT_VALUES_COUNT, values_count, 0);\n        }\n        #[inline]\n        pub fn add_axis(&mut self, axis: i32) {\n            self.fbb_.push_slot::<i32>(PackOptions::VT_AXIS, axis, 0);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> PackOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            PackOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<PackOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for PackOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"PackOptions\");\n            ds.field(\"values_count\", &self.values_count());\n            ds.field(\"axis\", &self.axis());\n            ds.finish()\n        }\n    }\n    pub enum LogicalOrOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LogicalOrOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LogicalOrOptions<'a> {\n        type Inner = LogicalOrOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LogicalOrOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LogicalOrOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args LogicalOrOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LogicalOrOptions<'bldr>> {\n            let mut builder = LogicalOrOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for LogicalOrOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct LogicalOrOptionsArgs {}\n    impl<'a> Default for LogicalOrOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LogicalOrOptionsArgs {}\n        }\n    }\n\n    pub struct LogicalOrOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LogicalOrOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> LogicalOrOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LogicalOrOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LogicalOrOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LogicalOrOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LogicalOrOptions\");\n            ds.finish()\n        }\n    }\n    pub enum OneHotOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct OneHotOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for OneHotOptions<'a> {\n        type Inner = OneHotOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> OneHotOptions<'a> {\n        pub const VT_AXIS: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            OneHotOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args OneHotOptionsArgs,\n        ) -> flatbuffers::WIPOffset<OneHotOptions<'bldr>> {\n            let mut builder = OneHotOptionsBuilder::new(_fbb);\n            builder.add_axis(args.axis);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn axis(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(OneHotOptions::VT_AXIS, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for OneHotOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.visit_field::<i32>(\"axis\", Self::VT_AXIS, false)?.finish();\n            Ok(())\n        }\n    }\n    pub struct OneHotOptionsArgs {\n        pub axis: i32,\n    }\n    impl<'a> Default for OneHotOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            OneHotOptionsArgs { axis: 0 }\n        }\n    }\n\n    pub struct OneHotOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> OneHotOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_axis(&mut self, axis: i32) {\n            self.fbb_.push_slot::<i32>(OneHotOptions::VT_AXIS, axis, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> OneHotOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            OneHotOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<OneHotOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for OneHotOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"OneHotOptions\");\n            ds.field(\"axis\", &self.axis());\n            ds.finish()\n        }\n    }\n    pub enum AbsOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct AbsOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for AbsOptions<'a> {\n        type Inner = AbsOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> AbsOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            AbsOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args AbsOptionsArgs,\n        ) -> flatbuffers::WIPOffset<AbsOptions<'bldr>> {\n            let mut builder = AbsOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for AbsOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct AbsOptionsArgs {}\n    impl<'a> Default for AbsOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            AbsOptionsArgs {}\n        }\n    }\n\n    pub struct AbsOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> AbsOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> AbsOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            AbsOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<AbsOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for AbsOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"AbsOptions\");\n            ds.finish()\n        }\n    }\n    pub enum HardSwishOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct HardSwishOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for HardSwishOptions<'a> {\n        type Inner = HardSwishOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> HardSwishOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            HardSwishOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args HardSwishOptionsArgs,\n        ) -> flatbuffers::WIPOffset<HardSwishOptions<'bldr>> {\n            let mut builder = HardSwishOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for HardSwishOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct HardSwishOptionsArgs {}\n    impl<'a> Default for HardSwishOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            HardSwishOptionsArgs {}\n        }\n    }\n\n    pub struct HardSwishOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> HardSwishOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> HardSwishOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            HardSwishOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<HardSwishOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for HardSwishOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"HardSwishOptions\");\n            ds.finish()\n        }\n    }\n    pub enum LogicalAndOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LogicalAndOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LogicalAndOptions<'a> {\n        type Inner = LogicalAndOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LogicalAndOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LogicalAndOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args LogicalAndOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LogicalAndOptions<'bldr>> {\n            let mut builder = LogicalAndOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for LogicalAndOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct LogicalAndOptionsArgs {}\n    impl<'a> Default for LogicalAndOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LogicalAndOptionsArgs {}\n        }\n    }\n\n    pub struct LogicalAndOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LogicalAndOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> LogicalAndOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LogicalAndOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LogicalAndOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LogicalAndOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LogicalAndOptions\");\n            ds.finish()\n        }\n    }\n    pub enum LogicalNotOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LogicalNotOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LogicalNotOptions<'a> {\n        type Inner = LogicalNotOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LogicalNotOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LogicalNotOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args LogicalNotOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LogicalNotOptions<'bldr>> {\n            let mut builder = LogicalNotOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for LogicalNotOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct LogicalNotOptionsArgs {}\n    impl<'a> Default for LogicalNotOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LogicalNotOptionsArgs {}\n        }\n    }\n\n    pub struct LogicalNotOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LogicalNotOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> LogicalNotOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LogicalNotOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LogicalNotOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LogicalNotOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LogicalNotOptions\");\n            ds.finish()\n        }\n    }\n    pub enum UnpackOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct UnpackOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for UnpackOptions<'a> {\n        type Inner = UnpackOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> UnpackOptions<'a> {\n        pub const VT_NUM: flatbuffers::VOffsetT = 4;\n        pub const VT_AXIS: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            UnpackOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args UnpackOptionsArgs,\n        ) -> flatbuffers::WIPOffset<UnpackOptions<'bldr>> {\n            let mut builder = UnpackOptionsBuilder::new(_fbb);\n            builder.add_axis(args.axis);\n            builder.add_num(args.num);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn num(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(UnpackOptions::VT_NUM, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn axis(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(UnpackOptions::VT_AXIS, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for UnpackOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"num\", Self::VT_NUM, false)?\n                .visit_field::<i32>(\"axis\", Self::VT_AXIS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct UnpackOptionsArgs {\n        pub num: i32,\n        pub axis: i32,\n    }\n    impl<'a> Default for UnpackOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            UnpackOptionsArgs { num: 0, axis: 0 }\n        }\n    }\n\n    pub struct UnpackOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> UnpackOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_num(&mut self, num: i32) {\n            self.fbb_.push_slot::<i32>(UnpackOptions::VT_NUM, num, 0);\n        }\n        #[inline]\n        pub fn add_axis(&mut self, axis: i32) {\n            self.fbb_.push_slot::<i32>(UnpackOptions::VT_AXIS, axis, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> UnpackOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            UnpackOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<UnpackOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for UnpackOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"UnpackOptions\");\n            ds.field(\"num\", &self.num());\n            ds.field(\"axis\", &self.axis());\n            ds.finish()\n        }\n    }\n    pub enum FloorDivOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct FloorDivOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for FloorDivOptions<'a> {\n        type Inner = FloorDivOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> FloorDivOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            FloorDivOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args FloorDivOptionsArgs,\n        ) -> flatbuffers::WIPOffset<FloorDivOptions<'bldr>> {\n            let mut builder = FloorDivOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for FloorDivOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct FloorDivOptionsArgs {}\n    impl<'a> Default for FloorDivOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            FloorDivOptionsArgs {}\n        }\n    }\n\n    pub struct FloorDivOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> FloorDivOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> FloorDivOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            FloorDivOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<FloorDivOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for FloorDivOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"FloorDivOptions\");\n            ds.finish()\n        }\n    }\n    pub enum SquareOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SquareOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SquareOptions<'a> {\n        type Inner = SquareOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SquareOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SquareOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args SquareOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SquareOptions<'bldr>> {\n            let mut builder = SquareOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for SquareOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct SquareOptionsArgs {}\n    impl<'a> Default for SquareOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SquareOptionsArgs {}\n        }\n    }\n\n    pub struct SquareOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SquareOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SquareOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SquareOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SquareOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SquareOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SquareOptions\");\n            ds.finish()\n        }\n    }\n    pub enum ZerosLikeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ZerosLikeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ZerosLikeOptions<'a> {\n        type Inner = ZerosLikeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ZerosLikeOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ZerosLikeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args ZerosLikeOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ZerosLikeOptions<'bldr>> {\n            let mut builder = ZerosLikeOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for ZerosLikeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct ZerosLikeOptionsArgs {}\n    impl<'a> Default for ZerosLikeOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ZerosLikeOptionsArgs {}\n        }\n    }\n\n    pub struct ZerosLikeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ZerosLikeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ZerosLikeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ZerosLikeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ZerosLikeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ZerosLikeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ZerosLikeOptions\");\n            ds.finish()\n        }\n    }\n    pub enum FillOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct FillOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for FillOptions<'a> {\n        type Inner = FillOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> FillOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            FillOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args FillOptionsArgs,\n        ) -> flatbuffers::WIPOffset<FillOptions<'bldr>> {\n            let mut builder = FillOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for FillOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct FillOptionsArgs {}\n    impl<'a> Default for FillOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            FillOptionsArgs {}\n        }\n    }\n\n    pub struct FillOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> FillOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> FillOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            FillOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<FillOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for FillOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"FillOptions\");\n            ds.finish()\n        }\n    }\n    pub enum FloorModOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct FloorModOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for FloorModOptions<'a> {\n        type Inner = FloorModOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> FloorModOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            FloorModOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args FloorModOptionsArgs,\n        ) -> flatbuffers::WIPOffset<FloorModOptions<'bldr>> {\n            let mut builder = FloorModOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for FloorModOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct FloorModOptionsArgs {}\n    impl<'a> Default for FloorModOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            FloorModOptionsArgs {}\n        }\n    }\n\n    pub struct FloorModOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> FloorModOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> FloorModOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            FloorModOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<FloorModOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for FloorModOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"FloorModOptions\");\n            ds.finish()\n        }\n    }\n    pub enum RangeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct RangeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for RangeOptions<'a> {\n        type Inner = RangeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> RangeOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            RangeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args RangeOptionsArgs,\n        ) -> flatbuffers::WIPOffset<RangeOptions<'bldr>> {\n            let mut builder = RangeOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for RangeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct RangeOptionsArgs {}\n    impl<'a> Default for RangeOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            RangeOptionsArgs {}\n        }\n    }\n\n    pub struct RangeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> RangeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> RangeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            RangeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<RangeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for RangeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"RangeOptions\");\n            ds.finish()\n        }\n    }\n    pub enum LeakyReluOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct LeakyReluOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for LeakyReluOptions<'a> {\n        type Inner = LeakyReluOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> LeakyReluOptions<'a> {\n        pub const VT_ALPHA: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            LeakyReluOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args LeakyReluOptionsArgs,\n        ) -> flatbuffers::WIPOffset<LeakyReluOptions<'bldr>> {\n            let mut builder = LeakyReluOptionsBuilder::new(_fbb);\n            builder.add_alpha(args.alpha);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn alpha(&self) -> f32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<f32>(LeakyReluOptions::VT_ALPHA, Some(0.0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for LeakyReluOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.visit_field::<f32>(\"alpha\", Self::VT_ALPHA, false)?.finish();\n            Ok(())\n        }\n    }\n    pub struct LeakyReluOptionsArgs {\n        pub alpha: f32,\n    }\n    impl<'a> Default for LeakyReluOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            LeakyReluOptionsArgs { alpha: 0.0 }\n        }\n    }\n\n    pub struct LeakyReluOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> LeakyReluOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_alpha(&mut self, alpha: f32) {\n            self.fbb_.push_slot::<f32>(LeakyReluOptions::VT_ALPHA, alpha, 0.0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> LeakyReluOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            LeakyReluOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<LeakyReluOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for LeakyReluOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"LeakyReluOptions\");\n            ds.field(\"alpha\", &self.alpha());\n            ds.finish()\n        }\n    }\n    pub enum SquaredDifferenceOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SquaredDifferenceOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SquaredDifferenceOptions<'a> {\n        type Inner = SquaredDifferenceOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SquaredDifferenceOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SquaredDifferenceOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args SquaredDifferenceOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SquaredDifferenceOptions<'bldr>> {\n            let mut builder = SquaredDifferenceOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for SquaredDifferenceOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct SquaredDifferenceOptionsArgs {}\n    impl<'a> Default for SquaredDifferenceOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SquaredDifferenceOptionsArgs {}\n        }\n    }\n\n    pub struct SquaredDifferenceOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SquaredDifferenceOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SquaredDifferenceOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SquaredDifferenceOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SquaredDifferenceOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SquaredDifferenceOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SquaredDifferenceOptions\");\n            ds.finish()\n        }\n    }\n    pub enum MirrorPadOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct MirrorPadOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for MirrorPadOptions<'a> {\n        type Inner = MirrorPadOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> MirrorPadOptions<'a> {\n        pub const VT_MODE: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            MirrorPadOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args MirrorPadOptionsArgs,\n        ) -> flatbuffers::WIPOffset<MirrorPadOptions<'bldr>> {\n            let mut builder = MirrorPadOptionsBuilder::new(_fbb);\n            builder.add_mode(args.mode);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn mode(&self) -> MirrorPadMode {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<MirrorPadMode>(MirrorPadOptions::VT_MODE, Some(MirrorPadMode::REFLECT))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for MirrorPadOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<MirrorPadMode>(\"mode\", Self::VT_MODE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct MirrorPadOptionsArgs {\n        pub mode: MirrorPadMode,\n    }\n    impl<'a> Default for MirrorPadOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            MirrorPadOptionsArgs { mode: MirrorPadMode::REFLECT }\n        }\n    }\n\n    pub struct MirrorPadOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> MirrorPadOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_mode(&mut self, mode: MirrorPadMode) {\n            self.fbb_.push_slot::<MirrorPadMode>(\n                MirrorPadOptions::VT_MODE,\n                mode,\n                MirrorPadMode::REFLECT,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> MirrorPadOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            MirrorPadOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<MirrorPadOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for MirrorPadOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"MirrorPadOptions\");\n            ds.field(\"mode\", &self.mode());\n            ds.finish()\n        }\n    }\n    pub enum UniqueOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct UniqueOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for UniqueOptions<'a> {\n        type Inner = UniqueOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> UniqueOptions<'a> {\n        pub const VT_IDX_OUT_TYPE: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            UniqueOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args UniqueOptionsArgs,\n        ) -> flatbuffers::WIPOffset<UniqueOptions<'bldr>> {\n            let mut builder = UniqueOptionsBuilder::new(_fbb);\n            builder.add_idx_out_type(args.idx_out_type);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn idx_out_type(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<TensorType>(UniqueOptions::VT_IDX_OUT_TYPE, Some(TensorType::INT32))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for UniqueOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<TensorType>(\"idx_out_type\", Self::VT_IDX_OUT_TYPE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct UniqueOptionsArgs {\n        pub idx_out_type: TensorType,\n    }\n    impl<'a> Default for UniqueOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            UniqueOptionsArgs { idx_out_type: TensorType::INT32 }\n        }\n    }\n\n    pub struct UniqueOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> UniqueOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_idx_out_type(&mut self, idx_out_type: TensorType) {\n            self.fbb_.push_slot::<TensorType>(\n                UniqueOptions::VT_IDX_OUT_TYPE,\n                idx_out_type,\n                TensorType::INT32,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> UniqueOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            UniqueOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<UniqueOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for UniqueOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"UniqueOptions\");\n            ds.field(\"idx_out_type\", &self.idx_out_type());\n            ds.finish()\n        }\n    }\n    pub enum ReverseV2OptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ReverseV2Options<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ReverseV2Options<'a> {\n        type Inner = ReverseV2Options<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ReverseV2Options<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ReverseV2Options { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args ReverseV2OptionsArgs,\n        ) -> flatbuffers::WIPOffset<ReverseV2Options<'bldr>> {\n            let mut builder = ReverseV2OptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for ReverseV2Options<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct ReverseV2OptionsArgs {}\n    impl<'a> Default for ReverseV2OptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ReverseV2OptionsArgs {}\n        }\n    }\n\n    pub struct ReverseV2OptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ReverseV2OptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ReverseV2OptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ReverseV2OptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ReverseV2Options<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ReverseV2Options<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ReverseV2Options\");\n            ds.finish()\n        }\n    }\n    pub enum AddNOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct AddNOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for AddNOptions<'a> {\n        type Inner = AddNOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> AddNOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            AddNOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args AddNOptionsArgs,\n        ) -> flatbuffers::WIPOffset<AddNOptions<'bldr>> {\n            let mut builder = AddNOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for AddNOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct AddNOptionsArgs {}\n    impl<'a> Default for AddNOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            AddNOptionsArgs {}\n        }\n    }\n\n    pub struct AddNOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> AddNOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> AddNOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            AddNOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<AddNOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for AddNOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"AddNOptions\");\n            ds.finish()\n        }\n    }\n    pub enum GatherNdOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct GatherNdOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for GatherNdOptions<'a> {\n        type Inner = GatherNdOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> GatherNdOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            GatherNdOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args GatherNdOptionsArgs,\n        ) -> flatbuffers::WIPOffset<GatherNdOptions<'bldr>> {\n            let mut builder = GatherNdOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for GatherNdOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct GatherNdOptionsArgs {}\n    impl<'a> Default for GatherNdOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            GatherNdOptionsArgs {}\n        }\n    }\n\n    pub struct GatherNdOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> GatherNdOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> GatherNdOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            GatherNdOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<GatherNdOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for GatherNdOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"GatherNdOptions\");\n            ds.finish()\n        }\n    }\n    pub enum WhereOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct WhereOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for WhereOptions<'a> {\n        type Inner = WhereOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> WhereOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            WhereOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args WhereOptionsArgs,\n        ) -> flatbuffers::WIPOffset<WhereOptions<'bldr>> {\n            let mut builder = WhereOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for WhereOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct WhereOptionsArgs {}\n    impl<'a> Default for WhereOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            WhereOptionsArgs {}\n        }\n    }\n\n    pub struct WhereOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> WhereOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> WhereOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            WhereOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<WhereOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for WhereOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"WhereOptions\");\n            ds.finish()\n        }\n    }\n    pub enum ReverseSequenceOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ReverseSequenceOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ReverseSequenceOptions<'a> {\n        type Inner = ReverseSequenceOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ReverseSequenceOptions<'a> {\n        pub const VT_SEQ_DIM: flatbuffers::VOffsetT = 4;\n        pub const VT_BATCH_DIM: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ReverseSequenceOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ReverseSequenceOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ReverseSequenceOptions<'bldr>> {\n            let mut builder = ReverseSequenceOptionsBuilder::new(_fbb);\n            builder.add_batch_dim(args.batch_dim);\n            builder.add_seq_dim(args.seq_dim);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn seq_dim(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(ReverseSequenceOptions::VT_SEQ_DIM, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn batch_dim(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(ReverseSequenceOptions::VT_BATCH_DIM, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for ReverseSequenceOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"seq_dim\", Self::VT_SEQ_DIM, false)?\n                .visit_field::<i32>(\"batch_dim\", Self::VT_BATCH_DIM, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ReverseSequenceOptionsArgs {\n        pub seq_dim: i32,\n        pub batch_dim: i32,\n    }\n    impl<'a> Default for ReverseSequenceOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ReverseSequenceOptionsArgs { seq_dim: 0, batch_dim: 0 }\n        }\n    }\n\n    pub struct ReverseSequenceOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ReverseSequenceOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_seq_dim(&mut self, seq_dim: i32) {\n            self.fbb_.push_slot::<i32>(ReverseSequenceOptions::VT_SEQ_DIM, seq_dim, 0);\n        }\n        #[inline]\n        pub fn add_batch_dim(&mut self, batch_dim: i32) {\n            self.fbb_.push_slot::<i32>(ReverseSequenceOptions::VT_BATCH_DIM, batch_dim, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ReverseSequenceOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ReverseSequenceOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ReverseSequenceOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ReverseSequenceOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ReverseSequenceOptions\");\n            ds.field(\"seq_dim\", &self.seq_dim());\n            ds.field(\"batch_dim\", &self.batch_dim());\n            ds.finish()\n        }\n    }\n    pub enum MatrixDiagOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct MatrixDiagOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for MatrixDiagOptions<'a> {\n        type Inner = MatrixDiagOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> MatrixDiagOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            MatrixDiagOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args MatrixDiagOptionsArgs,\n        ) -> flatbuffers::WIPOffset<MatrixDiagOptions<'bldr>> {\n            let mut builder = MatrixDiagOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for MatrixDiagOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct MatrixDiagOptionsArgs {}\n    impl<'a> Default for MatrixDiagOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            MatrixDiagOptionsArgs {}\n        }\n    }\n\n    pub struct MatrixDiagOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> MatrixDiagOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> MatrixDiagOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            MatrixDiagOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<MatrixDiagOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for MatrixDiagOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"MatrixDiagOptions\");\n            ds.finish()\n        }\n    }\n    pub enum QuantizeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct QuantizeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for QuantizeOptions<'a> {\n        type Inner = QuantizeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> QuantizeOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            QuantizeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args QuantizeOptionsArgs,\n        ) -> flatbuffers::WIPOffset<QuantizeOptions<'bldr>> {\n            let mut builder = QuantizeOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for QuantizeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct QuantizeOptionsArgs {}\n    impl<'a> Default for QuantizeOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            QuantizeOptionsArgs {}\n        }\n    }\n\n    pub struct QuantizeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> QuantizeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> QuantizeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            QuantizeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<QuantizeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for QuantizeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"QuantizeOptions\");\n            ds.finish()\n        }\n    }\n    pub enum MatrixSetDiagOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct MatrixSetDiagOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for MatrixSetDiagOptions<'a> {\n        type Inner = MatrixSetDiagOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> MatrixSetDiagOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            MatrixSetDiagOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args MatrixSetDiagOptionsArgs,\n        ) -> flatbuffers::WIPOffset<MatrixSetDiagOptions<'bldr>> {\n            let mut builder = MatrixSetDiagOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for MatrixSetDiagOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct MatrixSetDiagOptionsArgs {}\n    impl<'a> Default for MatrixSetDiagOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            MatrixSetDiagOptionsArgs {}\n        }\n    }\n\n    pub struct MatrixSetDiagOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> MatrixSetDiagOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> MatrixSetDiagOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            MatrixSetDiagOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<MatrixSetDiagOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for MatrixSetDiagOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"MatrixSetDiagOptions\");\n            ds.finish()\n        }\n    }\n    pub enum IfOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct IfOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for IfOptions<'a> {\n        type Inner = IfOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> IfOptions<'a> {\n        pub const VT_THEN_SUBGRAPH_INDEX: flatbuffers::VOffsetT = 4;\n        pub const VT_ELSE_SUBGRAPH_INDEX: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            IfOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args IfOptionsArgs,\n        ) -> flatbuffers::WIPOffset<IfOptions<'bldr>> {\n            let mut builder = IfOptionsBuilder::new(_fbb);\n            builder.add_else_subgraph_index(args.else_subgraph_index);\n            builder.add_then_subgraph_index(args.then_subgraph_index);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn then_subgraph_index(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(IfOptions::VT_THEN_SUBGRAPH_INDEX, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn else_subgraph_index(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(IfOptions::VT_ELSE_SUBGRAPH_INDEX, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for IfOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"then_subgraph_index\", Self::VT_THEN_SUBGRAPH_INDEX, false)?\n                .visit_field::<i32>(\"else_subgraph_index\", Self::VT_ELSE_SUBGRAPH_INDEX, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct IfOptionsArgs {\n        pub then_subgraph_index: i32,\n        pub else_subgraph_index: i32,\n    }\n    impl<'a> Default for IfOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            IfOptionsArgs { then_subgraph_index: 0, else_subgraph_index: 0 }\n        }\n    }\n\n    pub struct IfOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> IfOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_then_subgraph_index(&mut self, then_subgraph_index: i32) {\n            self.fbb_.push_slot::<i32>(IfOptions::VT_THEN_SUBGRAPH_INDEX, then_subgraph_index, 0);\n        }\n        #[inline]\n        pub fn add_else_subgraph_index(&mut self, else_subgraph_index: i32) {\n            self.fbb_.push_slot::<i32>(IfOptions::VT_ELSE_SUBGRAPH_INDEX, else_subgraph_index, 0);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> IfOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            IfOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<IfOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for IfOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"IfOptions\");\n            ds.field(\"then_subgraph_index\", &self.then_subgraph_index());\n            ds.field(\"else_subgraph_index\", &self.else_subgraph_index());\n            ds.finish()\n        }\n    }\n    pub enum CallOnceOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct CallOnceOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for CallOnceOptions<'a> {\n        type Inner = CallOnceOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> CallOnceOptions<'a> {\n        pub const VT_INIT_SUBGRAPH_INDEX: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            CallOnceOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args CallOnceOptionsArgs,\n        ) -> flatbuffers::WIPOffset<CallOnceOptions<'bldr>> {\n            let mut builder = CallOnceOptionsBuilder::new(_fbb);\n            builder.add_init_subgraph_index(args.init_subgraph_index);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn init_subgraph_index(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<i32>(CallOnceOptions::VT_INIT_SUBGRAPH_INDEX, Some(0)).unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for CallOnceOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"init_subgraph_index\", Self::VT_INIT_SUBGRAPH_INDEX, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct CallOnceOptionsArgs {\n        pub init_subgraph_index: i32,\n    }\n    impl<'a> Default for CallOnceOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            CallOnceOptionsArgs { init_subgraph_index: 0 }\n        }\n    }\n\n    pub struct CallOnceOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> CallOnceOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_init_subgraph_index(&mut self, init_subgraph_index: i32) {\n            self.fbb_.push_slot::<i32>(\n                CallOnceOptions::VT_INIT_SUBGRAPH_INDEX,\n                init_subgraph_index,\n                0,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> CallOnceOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            CallOnceOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<CallOnceOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for CallOnceOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"CallOnceOptions\");\n            ds.field(\"init_subgraph_index\", &self.init_subgraph_index());\n            ds.finish()\n        }\n    }\n    pub enum WhileOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct WhileOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for WhileOptions<'a> {\n        type Inner = WhileOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> WhileOptions<'a> {\n        pub const VT_COND_SUBGRAPH_INDEX: flatbuffers::VOffsetT = 4;\n        pub const VT_BODY_SUBGRAPH_INDEX: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            WhileOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args WhileOptionsArgs,\n        ) -> flatbuffers::WIPOffset<WhileOptions<'bldr>> {\n            let mut builder = WhileOptionsBuilder::new(_fbb);\n            builder.add_body_subgraph_index(args.body_subgraph_index);\n            builder.add_cond_subgraph_index(args.cond_subgraph_index);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn cond_subgraph_index(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(WhileOptions::VT_COND_SUBGRAPH_INDEX, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn body_subgraph_index(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(WhileOptions::VT_BODY_SUBGRAPH_INDEX, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for WhileOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"cond_subgraph_index\", Self::VT_COND_SUBGRAPH_INDEX, false)?\n                .visit_field::<i32>(\"body_subgraph_index\", Self::VT_BODY_SUBGRAPH_INDEX, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct WhileOptionsArgs {\n        pub cond_subgraph_index: i32,\n        pub body_subgraph_index: i32,\n    }\n    impl<'a> Default for WhileOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            WhileOptionsArgs { cond_subgraph_index: 0, body_subgraph_index: 0 }\n        }\n    }\n\n    pub struct WhileOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> WhileOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_cond_subgraph_index(&mut self, cond_subgraph_index: i32) {\n            self.fbb_.push_slot::<i32>(\n                WhileOptions::VT_COND_SUBGRAPH_INDEX,\n                cond_subgraph_index,\n                0,\n            );\n        }\n        #[inline]\n        pub fn add_body_subgraph_index(&mut self, body_subgraph_index: i32) {\n            self.fbb_.push_slot::<i32>(\n                WhileOptions::VT_BODY_SUBGRAPH_INDEX,\n                body_subgraph_index,\n                0,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> WhileOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            WhileOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<WhileOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for WhileOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"WhileOptions\");\n            ds.field(\"cond_subgraph_index\", &self.cond_subgraph_index());\n            ds.field(\"body_subgraph_index\", &self.body_subgraph_index());\n            ds.finish()\n        }\n    }\n    pub enum NonMaxSuppressionV4OptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct NonMaxSuppressionV4Options<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for NonMaxSuppressionV4Options<'a> {\n        type Inner = NonMaxSuppressionV4Options<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> NonMaxSuppressionV4Options<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            NonMaxSuppressionV4Options { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args NonMaxSuppressionV4OptionsArgs,\n        ) -> flatbuffers::WIPOffset<NonMaxSuppressionV4Options<'bldr>> {\n            let mut builder = NonMaxSuppressionV4OptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for NonMaxSuppressionV4Options<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct NonMaxSuppressionV4OptionsArgs {}\n    impl<'a> Default for NonMaxSuppressionV4OptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            NonMaxSuppressionV4OptionsArgs {}\n        }\n    }\n\n    pub struct NonMaxSuppressionV4OptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> NonMaxSuppressionV4OptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> NonMaxSuppressionV4OptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            NonMaxSuppressionV4OptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<NonMaxSuppressionV4Options<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for NonMaxSuppressionV4Options<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"NonMaxSuppressionV4Options\");\n            ds.finish()\n        }\n    }\n    pub enum NonMaxSuppressionV5OptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct NonMaxSuppressionV5Options<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for NonMaxSuppressionV5Options<'a> {\n        type Inner = NonMaxSuppressionV5Options<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> NonMaxSuppressionV5Options<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            NonMaxSuppressionV5Options { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args NonMaxSuppressionV5OptionsArgs,\n        ) -> flatbuffers::WIPOffset<NonMaxSuppressionV5Options<'bldr>> {\n            let mut builder = NonMaxSuppressionV5OptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for NonMaxSuppressionV5Options<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct NonMaxSuppressionV5OptionsArgs {}\n    impl<'a> Default for NonMaxSuppressionV5OptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            NonMaxSuppressionV5OptionsArgs {}\n        }\n    }\n\n    pub struct NonMaxSuppressionV5OptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> NonMaxSuppressionV5OptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> NonMaxSuppressionV5OptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            NonMaxSuppressionV5OptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<NonMaxSuppressionV5Options<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for NonMaxSuppressionV5Options<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"NonMaxSuppressionV5Options\");\n            ds.finish()\n        }\n    }\n    pub enum ScatterNdOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ScatterNdOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ScatterNdOptions<'a> {\n        type Inner = ScatterNdOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ScatterNdOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ScatterNdOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args ScatterNdOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ScatterNdOptions<'bldr>> {\n            let mut builder = ScatterNdOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for ScatterNdOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct ScatterNdOptionsArgs {}\n    impl<'a> Default for ScatterNdOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ScatterNdOptionsArgs {}\n        }\n    }\n\n    pub struct ScatterNdOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ScatterNdOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ScatterNdOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ScatterNdOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ScatterNdOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ScatterNdOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ScatterNdOptions\");\n            ds.finish()\n        }\n    }\n    pub enum SelectV2OptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SelectV2Options<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SelectV2Options<'a> {\n        type Inner = SelectV2Options<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SelectV2Options<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SelectV2Options { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args SelectV2OptionsArgs,\n        ) -> flatbuffers::WIPOffset<SelectV2Options<'bldr>> {\n            let mut builder = SelectV2OptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for SelectV2Options<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct SelectV2OptionsArgs {}\n    impl<'a> Default for SelectV2OptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SelectV2OptionsArgs {}\n        }\n    }\n\n    pub struct SelectV2OptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SelectV2OptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SelectV2OptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SelectV2OptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SelectV2Options<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SelectV2Options<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SelectV2Options\");\n            ds.finish()\n        }\n    }\n    pub enum DensifyOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct DensifyOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for DensifyOptions<'a> {\n        type Inner = DensifyOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> DensifyOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            DensifyOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args DensifyOptionsArgs,\n        ) -> flatbuffers::WIPOffset<DensifyOptions<'bldr>> {\n            let mut builder = DensifyOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for DensifyOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct DensifyOptionsArgs {}\n    impl<'a> Default for DensifyOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            DensifyOptionsArgs {}\n        }\n    }\n\n    pub struct DensifyOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> DensifyOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> DensifyOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            DensifyOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<DensifyOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for DensifyOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"DensifyOptions\");\n            ds.finish()\n        }\n    }\n    pub enum SegmentSumOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SegmentSumOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SegmentSumOptions<'a> {\n        type Inner = SegmentSumOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SegmentSumOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SegmentSumOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args SegmentSumOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SegmentSumOptions<'bldr>> {\n            let mut builder = SegmentSumOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for SegmentSumOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct SegmentSumOptionsArgs {}\n    impl<'a> Default for SegmentSumOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SegmentSumOptionsArgs {}\n        }\n    }\n\n    pub struct SegmentSumOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SegmentSumOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SegmentSumOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SegmentSumOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SegmentSumOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SegmentSumOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SegmentSumOptions\");\n            ds.finish()\n        }\n    }\n    pub enum BatchMatMulOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct BatchMatMulOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for BatchMatMulOptions<'a> {\n        type Inner = BatchMatMulOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> BatchMatMulOptions<'a> {\n        pub const VT_ADJ_X: flatbuffers::VOffsetT = 4;\n        pub const VT_ADJ_Y: flatbuffers::VOffsetT = 6;\n        pub const VT_ASYMMETRIC_QUANTIZE_INPUTS: flatbuffers::VOffsetT = 8;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            BatchMatMulOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args BatchMatMulOptionsArgs,\n        ) -> flatbuffers::WIPOffset<BatchMatMulOptions<'bldr>> {\n            let mut builder = BatchMatMulOptionsBuilder::new(_fbb);\n            builder.add_asymmetric_quantize_inputs(args.asymmetric_quantize_inputs);\n            builder.add_adj_y(args.adj_y);\n            builder.add_adj_x(args.adj_x);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn adj_x(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(BatchMatMulOptions::VT_ADJ_X, Some(false)).unwrap() }\n        }\n        #[inline]\n        pub fn adj_y(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(BatchMatMulOptions::VT_ADJ_Y, Some(false)).unwrap() }\n        }\n        #[inline]\n        pub fn asymmetric_quantize_inputs(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<bool>(BatchMatMulOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS, Some(false))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for BatchMatMulOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<bool>(\"adj_x\", Self::VT_ADJ_X, false)?\n                .visit_field::<bool>(\"adj_y\", Self::VT_ADJ_Y, false)?\n                .visit_field::<bool>(\n                    \"asymmetric_quantize_inputs\",\n                    Self::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct BatchMatMulOptionsArgs {\n        pub adj_x: bool,\n        pub adj_y: bool,\n        pub asymmetric_quantize_inputs: bool,\n    }\n    impl<'a> Default for BatchMatMulOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            BatchMatMulOptionsArgs { adj_x: false, adj_y: false, asymmetric_quantize_inputs: false }\n        }\n    }\n\n    pub struct BatchMatMulOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> BatchMatMulOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_adj_x(&mut self, adj_x: bool) {\n            self.fbb_.push_slot::<bool>(BatchMatMulOptions::VT_ADJ_X, adj_x, false);\n        }\n        #[inline]\n        pub fn add_adj_y(&mut self, adj_y: bool) {\n            self.fbb_.push_slot::<bool>(BatchMatMulOptions::VT_ADJ_Y, adj_y, false);\n        }\n        #[inline]\n        pub fn add_asymmetric_quantize_inputs(&mut self, asymmetric_quantize_inputs: bool) {\n            self.fbb_.push_slot::<bool>(\n                BatchMatMulOptions::VT_ASYMMETRIC_QUANTIZE_INPUTS,\n                asymmetric_quantize_inputs,\n                false,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> BatchMatMulOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            BatchMatMulOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<BatchMatMulOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for BatchMatMulOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"BatchMatMulOptions\");\n            ds.field(\"adj_x\", &self.adj_x());\n            ds.field(\"adj_y\", &self.adj_y());\n            ds.field(\"asymmetric_quantize_inputs\", &self.asymmetric_quantize_inputs());\n            ds.finish()\n        }\n    }\n    pub enum CumsumOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct CumsumOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for CumsumOptions<'a> {\n        type Inner = CumsumOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> CumsumOptions<'a> {\n        pub const VT_EXCLUSIVE: flatbuffers::VOffsetT = 4;\n        pub const VT_REVERSE: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            CumsumOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args CumsumOptionsArgs,\n        ) -> flatbuffers::WIPOffset<CumsumOptions<'bldr>> {\n            let mut builder = CumsumOptionsBuilder::new(_fbb);\n            builder.add_reverse(args.reverse);\n            builder.add_exclusive(args.exclusive);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn exclusive(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(CumsumOptions::VT_EXCLUSIVE, Some(false)).unwrap() }\n        }\n        #[inline]\n        pub fn reverse(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(CumsumOptions::VT_REVERSE, Some(false)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for CumsumOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<bool>(\"exclusive\", Self::VT_EXCLUSIVE, false)?\n                .visit_field::<bool>(\"reverse\", Self::VT_REVERSE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct CumsumOptionsArgs {\n        pub exclusive: bool,\n        pub reverse: bool,\n    }\n    impl<'a> Default for CumsumOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            CumsumOptionsArgs { exclusive: false, reverse: false }\n        }\n    }\n\n    pub struct CumsumOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> CumsumOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_exclusive(&mut self, exclusive: bool) {\n            self.fbb_.push_slot::<bool>(CumsumOptions::VT_EXCLUSIVE, exclusive, false);\n        }\n        #[inline]\n        pub fn add_reverse(&mut self, reverse: bool) {\n            self.fbb_.push_slot::<bool>(CumsumOptions::VT_REVERSE, reverse, false);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> CumsumOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            CumsumOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<CumsumOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for CumsumOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"CumsumOptions\");\n            ds.field(\"exclusive\", &self.exclusive());\n            ds.field(\"reverse\", &self.reverse());\n            ds.finish()\n        }\n    }\n    pub enum BroadcastToOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct BroadcastToOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for BroadcastToOptions<'a> {\n        type Inner = BroadcastToOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> BroadcastToOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            BroadcastToOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args BroadcastToOptionsArgs,\n        ) -> flatbuffers::WIPOffset<BroadcastToOptions<'bldr>> {\n            let mut builder = BroadcastToOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for BroadcastToOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct BroadcastToOptionsArgs {}\n    impl<'a> Default for BroadcastToOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            BroadcastToOptionsArgs {}\n        }\n    }\n\n    pub struct BroadcastToOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> BroadcastToOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> BroadcastToOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            BroadcastToOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<BroadcastToOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for BroadcastToOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"BroadcastToOptions\");\n            ds.finish()\n        }\n    }\n    pub enum Rfft2dOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Rfft2dOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Rfft2dOptions<'a> {\n        type Inner = Rfft2dOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Rfft2dOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Rfft2dOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args Rfft2dOptionsArgs,\n        ) -> flatbuffers::WIPOffset<Rfft2dOptions<'bldr>> {\n            let mut builder = Rfft2dOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for Rfft2dOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct Rfft2dOptionsArgs {}\n    impl<'a> Default for Rfft2dOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            Rfft2dOptionsArgs {}\n        }\n    }\n\n    pub struct Rfft2dOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> Rfft2dOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> Rfft2dOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            Rfft2dOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Rfft2dOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Rfft2dOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Rfft2dOptions\");\n            ds.finish()\n        }\n    }\n    pub enum HashtableOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct HashtableOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for HashtableOptions<'a> {\n        type Inner = HashtableOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> HashtableOptions<'a> {\n        pub const VT_TABLE_ID: flatbuffers::VOffsetT = 4;\n        pub const VT_KEY_DTYPE: flatbuffers::VOffsetT = 6;\n        pub const VT_VALUE_DTYPE: flatbuffers::VOffsetT = 8;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            HashtableOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args HashtableOptionsArgs,\n        ) -> flatbuffers::WIPOffset<HashtableOptions<'bldr>> {\n            let mut builder = HashtableOptionsBuilder::new(_fbb);\n            builder.add_table_id(args.table_id);\n            builder.add_value_dtype(args.value_dtype);\n            builder.add_key_dtype(args.key_dtype);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn table_id(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(HashtableOptions::VT_TABLE_ID, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn key_dtype(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<TensorType>(HashtableOptions::VT_KEY_DTYPE, Some(TensorType::FLOAT32))\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn value_dtype(&self) -> TensorType {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<TensorType>(HashtableOptions::VT_VALUE_DTYPE, Some(TensorType::FLOAT32))\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for HashtableOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i32>(\"table_id\", Self::VT_TABLE_ID, false)?\n                .visit_field::<TensorType>(\"key_dtype\", Self::VT_KEY_DTYPE, false)?\n                .visit_field::<TensorType>(\"value_dtype\", Self::VT_VALUE_DTYPE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct HashtableOptionsArgs {\n        pub table_id: i32,\n        pub key_dtype: TensorType,\n        pub value_dtype: TensorType,\n    }\n    impl<'a> Default for HashtableOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            HashtableOptionsArgs {\n                table_id: 0,\n                key_dtype: TensorType::FLOAT32,\n                value_dtype: TensorType::FLOAT32,\n            }\n        }\n    }\n\n    pub struct HashtableOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> HashtableOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_table_id(&mut self, table_id: i32) {\n            self.fbb_.push_slot::<i32>(HashtableOptions::VT_TABLE_ID, table_id, 0);\n        }\n        #[inline]\n        pub fn add_key_dtype(&mut self, key_dtype: TensorType) {\n            self.fbb_.push_slot::<TensorType>(\n                HashtableOptions::VT_KEY_DTYPE,\n                key_dtype,\n                TensorType::FLOAT32,\n            );\n        }\n        #[inline]\n        pub fn add_value_dtype(&mut self, value_dtype: TensorType) {\n            self.fbb_.push_slot::<TensorType>(\n                HashtableOptions::VT_VALUE_DTYPE,\n                value_dtype,\n                TensorType::FLOAT32,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> HashtableOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            HashtableOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<HashtableOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for HashtableOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"HashtableOptions\");\n            ds.field(\"table_id\", &self.table_id());\n            ds.field(\"key_dtype\", &self.key_dtype());\n            ds.field(\"value_dtype\", &self.value_dtype());\n            ds.finish()\n        }\n    }\n    pub enum HashtableFindOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct HashtableFindOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for HashtableFindOptions<'a> {\n        type Inner = HashtableFindOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> HashtableFindOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            HashtableFindOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args HashtableFindOptionsArgs,\n        ) -> flatbuffers::WIPOffset<HashtableFindOptions<'bldr>> {\n            let mut builder = HashtableFindOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for HashtableFindOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct HashtableFindOptionsArgs {}\n    impl<'a> Default for HashtableFindOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            HashtableFindOptionsArgs {}\n        }\n    }\n\n    pub struct HashtableFindOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> HashtableFindOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> HashtableFindOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            HashtableFindOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<HashtableFindOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for HashtableFindOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"HashtableFindOptions\");\n            ds.finish()\n        }\n    }\n    pub enum HashtableImportOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct HashtableImportOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for HashtableImportOptions<'a> {\n        type Inner = HashtableImportOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> HashtableImportOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            HashtableImportOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args HashtableImportOptionsArgs,\n        ) -> flatbuffers::WIPOffset<HashtableImportOptions<'bldr>> {\n            let mut builder = HashtableImportOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for HashtableImportOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct HashtableImportOptionsArgs {}\n    impl<'a> Default for HashtableImportOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            HashtableImportOptionsArgs {}\n        }\n    }\n\n    pub struct HashtableImportOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> HashtableImportOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> HashtableImportOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            HashtableImportOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<HashtableImportOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for HashtableImportOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"HashtableImportOptions\");\n            ds.finish()\n        }\n    }\n    pub enum HashtableSizeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct HashtableSizeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for HashtableSizeOptions<'a> {\n        type Inner = HashtableSizeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> HashtableSizeOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            HashtableSizeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args HashtableSizeOptionsArgs,\n        ) -> flatbuffers::WIPOffset<HashtableSizeOptions<'bldr>> {\n            let mut builder = HashtableSizeOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for HashtableSizeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct HashtableSizeOptionsArgs {}\n    impl<'a> Default for HashtableSizeOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            HashtableSizeOptionsArgs {}\n        }\n    }\n\n    pub struct HashtableSizeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> HashtableSizeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> HashtableSizeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            HashtableSizeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<HashtableSizeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for HashtableSizeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"HashtableSizeOptions\");\n            ds.finish()\n        }\n    }\n    pub enum VarHandleOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct VarHandleOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for VarHandleOptions<'a> {\n        type Inner = VarHandleOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> VarHandleOptions<'a> {\n        pub const VT_CONTAINER: flatbuffers::VOffsetT = 4;\n        pub const VT_SHARED_NAME: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            VarHandleOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args VarHandleOptionsArgs<'args>,\n        ) -> flatbuffers::WIPOffset<VarHandleOptions<'bldr>> {\n            let mut builder = VarHandleOptionsBuilder::new(_fbb);\n            if let Some(x) = args.shared_name {\n                builder.add_shared_name(x);\n            }\n            if let Some(x) = args.container {\n                builder.add_container(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn container(&self) -> Option<&'a str> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<flatbuffers::ForwardsUOffset<&str>>(VarHandleOptions::VT_CONTAINER, None)\n            }\n        }\n        #[inline]\n        pub fn shared_name(&self) -> Option<&'a str> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<&str>>(\n                    VarHandleOptions::VT_SHARED_NAME,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for VarHandleOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<&str>>(\n                    \"container\",\n                    Self::VT_CONTAINER,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<&str>>(\n                    \"shared_name\",\n                    Self::VT_SHARED_NAME,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct VarHandleOptionsArgs<'a> {\n        pub container: Option<flatbuffers::WIPOffset<&'a str>>,\n        pub shared_name: Option<flatbuffers::WIPOffset<&'a str>>,\n    }\n    impl<'a> Default for VarHandleOptionsArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            VarHandleOptionsArgs { container: None, shared_name: None }\n        }\n    }\n\n    pub struct VarHandleOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> VarHandleOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_container(&mut self, container: flatbuffers::WIPOffset<&'b str>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                VarHandleOptions::VT_CONTAINER,\n                container,\n            );\n        }\n        #[inline]\n        pub fn add_shared_name(&mut self, shared_name: flatbuffers::WIPOffset<&'b str>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                VarHandleOptions::VT_SHARED_NAME,\n                shared_name,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> VarHandleOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            VarHandleOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<VarHandleOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for VarHandleOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"VarHandleOptions\");\n            ds.field(\"container\", &self.container());\n            ds.field(\"shared_name\", &self.shared_name());\n            ds.finish()\n        }\n    }\n    pub enum ReadVariableOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ReadVariableOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ReadVariableOptions<'a> {\n        type Inner = ReadVariableOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ReadVariableOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ReadVariableOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args ReadVariableOptionsArgs,\n        ) -> flatbuffers::WIPOffset<ReadVariableOptions<'bldr>> {\n            let mut builder = ReadVariableOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for ReadVariableOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct ReadVariableOptionsArgs {}\n    impl<'a> Default for ReadVariableOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ReadVariableOptionsArgs {}\n        }\n    }\n\n    pub struct ReadVariableOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ReadVariableOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ReadVariableOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ReadVariableOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ReadVariableOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ReadVariableOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ReadVariableOptions\");\n            ds.finish()\n        }\n    }\n    pub enum AssignVariableOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct AssignVariableOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for AssignVariableOptions<'a> {\n        type Inner = AssignVariableOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> AssignVariableOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            AssignVariableOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args AssignVariableOptionsArgs,\n        ) -> flatbuffers::WIPOffset<AssignVariableOptions<'bldr>> {\n            let mut builder = AssignVariableOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for AssignVariableOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct AssignVariableOptionsArgs {}\n    impl<'a> Default for AssignVariableOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            AssignVariableOptionsArgs {}\n        }\n    }\n\n    pub struct AssignVariableOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> AssignVariableOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> AssignVariableOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            AssignVariableOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<AssignVariableOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for AssignVariableOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"AssignVariableOptions\");\n            ds.finish()\n        }\n    }\n    pub enum RandomOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct RandomOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for RandomOptions<'a> {\n        type Inner = RandomOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> RandomOptions<'a> {\n        pub const VT_SEED: flatbuffers::VOffsetT = 4;\n        pub const VT_SEED2: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            RandomOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args RandomOptionsArgs,\n        ) -> flatbuffers::WIPOffset<RandomOptions<'bldr>> {\n            let mut builder = RandomOptionsBuilder::new(_fbb);\n            builder.add_seed2(args.seed2);\n            builder.add_seed(args.seed);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn seed(&self) -> i64 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i64>(RandomOptions::VT_SEED, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn seed2(&self) -> i64 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i64>(RandomOptions::VT_SEED2, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for RandomOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i64>(\"seed\", Self::VT_SEED, false)?\n                .visit_field::<i64>(\"seed2\", Self::VT_SEED2, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct RandomOptionsArgs {\n        pub seed: i64,\n        pub seed2: i64,\n    }\n    impl<'a> Default for RandomOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            RandomOptionsArgs { seed: 0, seed2: 0 }\n        }\n    }\n\n    pub struct RandomOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> RandomOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_seed(&mut self, seed: i64) {\n            self.fbb_.push_slot::<i64>(RandomOptions::VT_SEED, seed, 0);\n        }\n        #[inline]\n        pub fn add_seed2(&mut self, seed2: i64) {\n            self.fbb_.push_slot::<i64>(RandomOptions::VT_SEED2, seed2, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> RandomOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            RandomOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<RandomOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for RandomOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"RandomOptions\");\n            ds.field(\"seed\", &self.seed());\n            ds.field(\"seed2\", &self.seed2());\n            ds.finish()\n        }\n    }\n    pub enum BucketizeOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct BucketizeOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for BucketizeOptions<'a> {\n        type Inner = BucketizeOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> BucketizeOptions<'a> {\n        pub const VT_BOUNDARIES: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            BucketizeOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args BucketizeOptionsArgs<'args>,\n        ) -> flatbuffers::WIPOffset<BucketizeOptions<'bldr>> {\n            let mut builder = BucketizeOptionsBuilder::new(_fbb);\n            if let Some(x) = args.boundaries {\n                builder.add_boundaries(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn boundaries(&self) -> Option<flatbuffers::Vector<'a, f32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, f32>>>(\n                    BucketizeOptions::VT_BOUNDARIES,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for BucketizeOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, f32>>>(\n                    \"boundaries\",\n                    Self::VT_BOUNDARIES,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct BucketizeOptionsArgs<'a> {\n        pub boundaries: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, f32>>>,\n    }\n    impl<'a> Default for BucketizeOptionsArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            BucketizeOptionsArgs { boundaries: None }\n        }\n    }\n\n    pub struct BucketizeOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> BucketizeOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_boundaries(\n            &mut self,\n            boundaries: flatbuffers::WIPOffset<flatbuffers::Vector<'b, f32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                BucketizeOptions::VT_BOUNDARIES,\n                boundaries,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> BucketizeOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            BucketizeOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<BucketizeOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for BucketizeOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"BucketizeOptions\");\n            ds.field(\"boundaries\", &self.boundaries());\n            ds.finish()\n        }\n    }\n    pub enum GeluOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct GeluOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for GeluOptions<'a> {\n        type Inner = GeluOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> GeluOptions<'a> {\n        pub const VT_APPROXIMATE: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            GeluOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args GeluOptionsArgs,\n        ) -> flatbuffers::WIPOffset<GeluOptions<'bldr>> {\n            let mut builder = GeluOptionsBuilder::new(_fbb);\n            builder.add_approximate(args.approximate);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn approximate(&self) -> bool {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<bool>(GeluOptions::VT_APPROXIMATE, Some(false)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for GeluOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<bool>(\"approximate\", Self::VT_APPROXIMATE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct GeluOptionsArgs {\n        pub approximate: bool,\n    }\n    impl<'a> Default for GeluOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            GeluOptionsArgs { approximate: false }\n        }\n    }\n\n    pub struct GeluOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> GeluOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_approximate(&mut self, approximate: bool) {\n            self.fbb_.push_slot::<bool>(GeluOptions::VT_APPROXIMATE, approximate, false);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> GeluOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            GeluOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<GeluOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for GeluOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"GeluOptions\");\n            ds.field(\"approximate\", &self.approximate());\n            ds.finish()\n        }\n    }\n    pub enum DynamicUpdateSliceOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct DynamicUpdateSliceOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for DynamicUpdateSliceOptions<'a> {\n        type Inner = DynamicUpdateSliceOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> DynamicUpdateSliceOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            DynamicUpdateSliceOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args DynamicUpdateSliceOptionsArgs,\n        ) -> flatbuffers::WIPOffset<DynamicUpdateSliceOptions<'bldr>> {\n            let mut builder = DynamicUpdateSliceOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for DynamicUpdateSliceOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct DynamicUpdateSliceOptionsArgs {}\n    impl<'a> Default for DynamicUpdateSliceOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            DynamicUpdateSliceOptionsArgs {}\n        }\n    }\n\n    pub struct DynamicUpdateSliceOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> DynamicUpdateSliceOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> DynamicUpdateSliceOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            DynamicUpdateSliceOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<DynamicUpdateSliceOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for DynamicUpdateSliceOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"DynamicUpdateSliceOptions\");\n            ds.finish()\n        }\n    }\n    pub enum UnsortedSegmentProdOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct UnsortedSegmentProdOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for UnsortedSegmentProdOptions<'a> {\n        type Inner = UnsortedSegmentProdOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> UnsortedSegmentProdOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            UnsortedSegmentProdOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args UnsortedSegmentProdOptionsArgs,\n        ) -> flatbuffers::WIPOffset<UnsortedSegmentProdOptions<'bldr>> {\n            let mut builder = UnsortedSegmentProdOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for UnsortedSegmentProdOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct UnsortedSegmentProdOptionsArgs {}\n    impl<'a> Default for UnsortedSegmentProdOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            UnsortedSegmentProdOptionsArgs {}\n        }\n    }\n\n    pub struct UnsortedSegmentProdOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> UnsortedSegmentProdOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> UnsortedSegmentProdOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            UnsortedSegmentProdOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<UnsortedSegmentProdOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for UnsortedSegmentProdOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"UnsortedSegmentProdOptions\");\n            ds.finish()\n        }\n    }\n    pub enum UnsortedSegmentMaxOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct UnsortedSegmentMaxOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for UnsortedSegmentMaxOptions<'a> {\n        type Inner = UnsortedSegmentMaxOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> UnsortedSegmentMaxOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            UnsortedSegmentMaxOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args UnsortedSegmentMaxOptionsArgs,\n        ) -> flatbuffers::WIPOffset<UnsortedSegmentMaxOptions<'bldr>> {\n            let mut builder = UnsortedSegmentMaxOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for UnsortedSegmentMaxOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct UnsortedSegmentMaxOptionsArgs {}\n    impl<'a> Default for UnsortedSegmentMaxOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            UnsortedSegmentMaxOptionsArgs {}\n        }\n    }\n\n    pub struct UnsortedSegmentMaxOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> UnsortedSegmentMaxOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> UnsortedSegmentMaxOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            UnsortedSegmentMaxOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<UnsortedSegmentMaxOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for UnsortedSegmentMaxOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"UnsortedSegmentMaxOptions\");\n            ds.finish()\n        }\n    }\n    pub enum UnsortedSegmentSumOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct UnsortedSegmentSumOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for UnsortedSegmentSumOptions<'a> {\n        type Inner = UnsortedSegmentSumOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> UnsortedSegmentSumOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            UnsortedSegmentSumOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args UnsortedSegmentSumOptionsArgs,\n        ) -> flatbuffers::WIPOffset<UnsortedSegmentSumOptions<'bldr>> {\n            let mut builder = UnsortedSegmentSumOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for UnsortedSegmentSumOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct UnsortedSegmentSumOptionsArgs {}\n    impl<'a> Default for UnsortedSegmentSumOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            UnsortedSegmentSumOptionsArgs {}\n        }\n    }\n\n    pub struct UnsortedSegmentSumOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> UnsortedSegmentSumOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> UnsortedSegmentSumOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            UnsortedSegmentSumOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<UnsortedSegmentSumOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for UnsortedSegmentSumOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"UnsortedSegmentSumOptions\");\n            ds.finish()\n        }\n    }\n    pub enum ATan2OptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct ATan2Options<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for ATan2Options<'a> {\n        type Inner = ATan2Options<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> ATan2Options<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            ATan2Options { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args ATan2OptionsArgs,\n        ) -> flatbuffers::WIPOffset<ATan2Options<'bldr>> {\n            let mut builder = ATan2OptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for ATan2Options<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct ATan2OptionsArgs {}\n    impl<'a> Default for ATan2OptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            ATan2OptionsArgs {}\n        }\n    }\n\n    pub struct ATan2OptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ATan2OptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> ATan2OptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ATan2OptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<ATan2Options<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for ATan2Options<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"ATan2Options\");\n            ds.finish()\n        }\n    }\n    pub enum UnsortedSegmentMinOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct UnsortedSegmentMinOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for UnsortedSegmentMinOptions<'a> {\n        type Inner = UnsortedSegmentMinOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> UnsortedSegmentMinOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            UnsortedSegmentMinOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args UnsortedSegmentMinOptionsArgs,\n        ) -> flatbuffers::WIPOffset<UnsortedSegmentMinOptions<'bldr>> {\n            let mut builder = UnsortedSegmentMinOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for UnsortedSegmentMinOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct UnsortedSegmentMinOptionsArgs {}\n    impl<'a> Default for UnsortedSegmentMinOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            UnsortedSegmentMinOptionsArgs {}\n        }\n    }\n\n    pub struct UnsortedSegmentMinOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> UnsortedSegmentMinOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> UnsortedSegmentMinOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            UnsortedSegmentMinOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<UnsortedSegmentMinOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for UnsortedSegmentMinOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"UnsortedSegmentMinOptions\");\n            ds.finish()\n        }\n    }\n    pub enum SignOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SignOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SignOptions<'a> {\n        type Inner = SignOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SignOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SignOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args SignOptionsArgs,\n        ) -> flatbuffers::WIPOffset<SignOptions<'bldr>> {\n            let mut builder = SignOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for SignOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct SignOptionsArgs {}\n    impl<'a> Default for SignOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            SignOptionsArgs {}\n        }\n    }\n\n    pub struct SignOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SignOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> SignOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SignOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SignOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SignOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SignOptions\");\n            ds.finish()\n        }\n    }\n    pub enum BitcastOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct BitcastOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for BitcastOptions<'a> {\n        type Inner = BitcastOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> BitcastOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            BitcastOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args BitcastOptionsArgs,\n        ) -> flatbuffers::WIPOffset<BitcastOptions<'bldr>> {\n            let mut builder = BitcastOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for BitcastOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct BitcastOptionsArgs {}\n    impl<'a> Default for BitcastOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            BitcastOptionsArgs {}\n        }\n    }\n\n    pub struct BitcastOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> BitcastOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> BitcastOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            BitcastOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<BitcastOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for BitcastOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"BitcastOptions\");\n            ds.finish()\n        }\n    }\n    pub enum BitwiseXorOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct BitwiseXorOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for BitwiseXorOptions<'a> {\n        type Inner = BitwiseXorOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> BitwiseXorOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            BitwiseXorOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args BitwiseXorOptionsArgs,\n        ) -> flatbuffers::WIPOffset<BitwiseXorOptions<'bldr>> {\n            let mut builder = BitwiseXorOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for BitwiseXorOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct BitwiseXorOptionsArgs {}\n    impl<'a> Default for BitwiseXorOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            BitwiseXorOptionsArgs {}\n        }\n    }\n\n    pub struct BitwiseXorOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> BitwiseXorOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> BitwiseXorOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            BitwiseXorOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<BitwiseXorOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for BitwiseXorOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"BitwiseXorOptions\");\n            ds.finish()\n        }\n    }\n    pub enum RightShiftOptionsOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct RightShiftOptions<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for RightShiftOptions<'a> {\n        type Inner = RightShiftOptions<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> RightShiftOptions<'a> {\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            RightShiftOptions { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            _args: &'args RightShiftOptionsArgs,\n        ) -> flatbuffers::WIPOffset<RightShiftOptions<'bldr>> {\n            let mut builder = RightShiftOptionsBuilder::new(_fbb);\n            builder.finish()\n        }\n    }\n\n    impl flatbuffers::Verifiable for RightShiftOptions<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?.finish();\n            Ok(())\n        }\n    }\n    pub struct RightShiftOptionsArgs {}\n    impl<'a> Default for RightShiftOptionsArgs {\n        #[inline]\n        fn default() -> Self {\n            RightShiftOptionsArgs {}\n        }\n    }\n\n    pub struct RightShiftOptionsBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> RightShiftOptionsBuilder<'a, 'b> {\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> RightShiftOptionsBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            RightShiftOptionsBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<RightShiftOptions<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for RightShiftOptions<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"RightShiftOptions\");\n            ds.finish()\n        }\n    }\n    pub enum OperatorCodeOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct OperatorCode<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for OperatorCode<'a> {\n        type Inner = OperatorCode<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> OperatorCode<'a> {\n        pub const VT_DEPRECATED_BUILTIN_CODE: flatbuffers::VOffsetT = 4;\n        pub const VT_CUSTOM_CODE: flatbuffers::VOffsetT = 6;\n        pub const VT_VERSION: flatbuffers::VOffsetT = 8;\n        pub const VT_BUILTIN_CODE: flatbuffers::VOffsetT = 10;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            OperatorCode { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args OperatorCodeArgs<'args>,\n        ) -> flatbuffers::WIPOffset<OperatorCode<'bldr>> {\n            let mut builder = OperatorCodeBuilder::new(_fbb);\n            builder.add_builtin_code(args.builtin_code);\n            builder.add_version(args.version);\n            if let Some(x) = args.custom_code {\n                builder.add_custom_code(x);\n            }\n            builder.add_deprecated_builtin_code(args.deprecated_builtin_code);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn deprecated_builtin_code(&self) -> i8 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<i8>(OperatorCode::VT_DEPRECATED_BUILTIN_CODE, Some(0)).unwrap()\n            }\n        }\n        #[inline]\n        pub fn custom_code(&self) -> Option<&'a str> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<flatbuffers::ForwardsUOffset<&str>>(OperatorCode::VT_CUSTOM_CODE, None)\n            }\n        }\n        #[inline]\n        pub fn version(&self) -> i32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<i32>(OperatorCode::VT_VERSION, Some(1)).unwrap() }\n        }\n        #[inline]\n        pub fn builtin_code(&self) -> BuiltinOperator {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<BuiltinOperator>(\n                        OperatorCode::VT_BUILTIN_CODE,\n                        Some(BuiltinOperator::ADD),\n                    )\n                    .unwrap()\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for OperatorCode<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<i8>(\n                    \"deprecated_builtin_code\",\n                    Self::VT_DEPRECATED_BUILTIN_CODE,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<&str>>(\n                    \"custom_code\",\n                    Self::VT_CUSTOM_CODE,\n                    false,\n                )?\n                .visit_field::<i32>(\"version\", Self::VT_VERSION, false)?\n                .visit_field::<BuiltinOperator>(\"builtin_code\", Self::VT_BUILTIN_CODE, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct OperatorCodeArgs<'a> {\n        pub deprecated_builtin_code: i8,\n        pub custom_code: Option<flatbuffers::WIPOffset<&'a str>>,\n        pub version: i32,\n        pub builtin_code: BuiltinOperator,\n    }\n    impl<'a> Default for OperatorCodeArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            OperatorCodeArgs {\n                deprecated_builtin_code: 0,\n                custom_code: None,\n                version: 1,\n                builtin_code: BuiltinOperator::ADD,\n            }\n        }\n    }\n\n    pub struct OperatorCodeBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> OperatorCodeBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_deprecated_builtin_code(&mut self, deprecated_builtin_code: i8) {\n            self.fbb_.push_slot::<i8>(\n                OperatorCode::VT_DEPRECATED_BUILTIN_CODE,\n                deprecated_builtin_code,\n                0,\n            );\n        }\n        #[inline]\n        pub fn add_custom_code(&mut self, custom_code: flatbuffers::WIPOffset<&'b str>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                OperatorCode::VT_CUSTOM_CODE,\n                custom_code,\n            );\n        }\n        #[inline]\n        pub fn add_version(&mut self, version: i32) {\n            self.fbb_.push_slot::<i32>(OperatorCode::VT_VERSION, version, 1);\n        }\n        #[inline]\n        pub fn add_builtin_code(&mut self, builtin_code: BuiltinOperator) {\n            self.fbb_.push_slot::<BuiltinOperator>(\n                OperatorCode::VT_BUILTIN_CODE,\n                builtin_code,\n                BuiltinOperator::ADD,\n            );\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> OperatorCodeBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            OperatorCodeBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<OperatorCode<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for OperatorCode<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"OperatorCode\");\n            ds.field(\"deprecated_builtin_code\", &self.deprecated_builtin_code());\n            ds.field(\"custom_code\", &self.custom_code());\n            ds.field(\"version\", &self.version());\n            ds.field(\"builtin_code\", &self.builtin_code());\n            ds.finish()\n        }\n    }\n    pub enum OperatorOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Operator<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Operator<'a> {\n        type Inner = Operator<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Operator<'a> {\n        pub const VT_OPCODE_INDEX: flatbuffers::VOffsetT = 4;\n        pub const VT_INPUTS: flatbuffers::VOffsetT = 6;\n        pub const VT_OUTPUTS: flatbuffers::VOffsetT = 8;\n        pub const VT_BUILTIN_OPTIONS_TYPE: flatbuffers::VOffsetT = 10;\n        pub const VT_BUILTIN_OPTIONS: flatbuffers::VOffsetT = 12;\n        pub const VT_CUSTOM_OPTIONS: flatbuffers::VOffsetT = 14;\n        pub const VT_CUSTOM_OPTIONS_FORMAT: flatbuffers::VOffsetT = 16;\n        pub const VT_MUTATING_VARIABLE_INPUTS: flatbuffers::VOffsetT = 18;\n        pub const VT_INTERMEDIATES: flatbuffers::VOffsetT = 20;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Operator { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args OperatorArgs<'args>,\n        ) -> flatbuffers::WIPOffset<Operator<'bldr>> {\n            let mut builder = OperatorBuilder::new(_fbb);\n            if let Some(x) = args.intermediates {\n                builder.add_intermediates(x);\n            }\n            if let Some(x) = args.mutating_variable_inputs {\n                builder.add_mutating_variable_inputs(x);\n            }\n            if let Some(x) = args.custom_options {\n                builder.add_custom_options(x);\n            }\n            if let Some(x) = args.builtin_options {\n                builder.add_builtin_options(x);\n            }\n            if let Some(x) = args.outputs {\n                builder.add_outputs(x);\n            }\n            if let Some(x) = args.inputs {\n                builder.add_inputs(x);\n            }\n            builder.add_opcode_index(args.opcode_index);\n            builder.add_custom_options_format(args.custom_options_format);\n            builder.add_builtin_options_type(args.builtin_options_type);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn opcode_index(&self) -> u32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<u32>(Operator::VT_OPCODE_INDEX, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn inputs(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    Operator::VT_INPUTS,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn outputs(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    Operator::VT_OUTPUTS,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn builtin_options_type(&self) -> BuiltinOptions {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<BuiltinOptions>(\n                        Operator::VT_BUILTIN_OPTIONS_TYPE,\n                        Some(BuiltinOptions::NONE),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn builtin_options(&self) -> Option<flatbuffers::Table<'a>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Table<'a>>>(\n                    Operator::VT_BUILTIN_OPTIONS,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn custom_options(&self) -> Option<flatbuffers::Vector<'a, u8>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, u8>>>(\n                    Operator::VT_CUSTOM_OPTIONS,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn custom_options_format(&self) -> CustomOptionsFormat {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<CustomOptionsFormat>(\n                        Operator::VT_CUSTOM_OPTIONS_FORMAT,\n                        Some(CustomOptionsFormat::FLEXBUFFERS),\n                    )\n                    .unwrap()\n            }\n        }\n        #[inline]\n        pub fn mutating_variable_inputs(&self) -> Option<flatbuffers::Vector<'a, bool>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, bool>>>(\n                    Operator::VT_MUTATING_VARIABLE_INPUTS,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn intermediates(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    Operator::VT_INTERMEDIATES,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_conv_2_doptions(&self) -> Option<Conv2DOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::Conv2DOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Conv2DOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_depthwise_conv_2_doptions(\n            &self,\n        ) -> Option<DepthwiseConv2DOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::DepthwiseConv2DOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { DepthwiseConv2DOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_concat_embeddings_options(\n            &self,\n        ) -> Option<ConcatEmbeddingsOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ConcatEmbeddingsOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ConcatEmbeddingsOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_lshprojection_options(&self) -> Option<LSHProjectionOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LSHProjectionOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LSHProjectionOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_pool_2_doptions(&self) -> Option<Pool2DOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::Pool2DOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Pool2DOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_svdfoptions(&self) -> Option<SVDFOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SVDFOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SVDFOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_rnnoptions(&self) -> Option<RNNOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::RNNOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { RNNOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_fully_connected_options(\n            &self,\n        ) -> Option<FullyConnectedOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::FullyConnectedOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { FullyConnectedOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_softmax_options(&self) -> Option<SoftmaxOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SoftmaxOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SoftmaxOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_concatenation_options(&self) -> Option<ConcatenationOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ConcatenationOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ConcatenationOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_add_options(&self) -> Option<AddOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::AddOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { AddOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_l2_norm_options(&self) -> Option<L2NormOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::L2NormOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { L2NormOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_local_response_normalization_options(\n            &self,\n        ) -> Option<LocalResponseNormalizationOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LocalResponseNormalizationOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LocalResponseNormalizationOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_lstmoptions(&self) -> Option<LSTMOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LSTMOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LSTMOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_resize_bilinear_options(\n            &self,\n        ) -> Option<ResizeBilinearOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ResizeBilinearOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ResizeBilinearOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_call_options(&self) -> Option<CallOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::CallOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { CallOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_reshape_options(&self) -> Option<ReshapeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ReshapeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ReshapeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_skip_gram_options(&self) -> Option<SkipGramOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SkipGramOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SkipGramOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_space_to_depth_options(&self) -> Option<SpaceToDepthOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SpaceToDepthOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SpaceToDepthOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_embedding_lookup_sparse_options(\n            &self,\n        ) -> Option<EmbeddingLookupSparseOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::EmbeddingLookupSparseOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { EmbeddingLookupSparseOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_mul_options(&self) -> Option<MulOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::MulOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { MulOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_pad_options(&self) -> Option<PadOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::PadOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { PadOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_gather_options(&self) -> Option<GatherOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::GatherOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { GatherOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_batch_to_space_ndoptions(\n            &self,\n        ) -> Option<BatchToSpaceNDOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::BatchToSpaceNDOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { BatchToSpaceNDOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_space_to_batch_ndoptions(\n            &self,\n        ) -> Option<SpaceToBatchNDOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SpaceToBatchNDOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SpaceToBatchNDOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_transpose_options(&self) -> Option<TransposeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::TransposeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { TransposeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_reducer_options(&self) -> Option<ReducerOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ReducerOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ReducerOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_sub_options(&self) -> Option<SubOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SubOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SubOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_div_options(&self) -> Option<DivOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::DivOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { DivOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_squeeze_options(&self) -> Option<SqueezeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SqueezeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SqueezeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_sequence_rnnoptions(&self) -> Option<SequenceRNNOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SequenceRNNOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SequenceRNNOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_strided_slice_options(&self) -> Option<StridedSliceOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::StridedSliceOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { StridedSliceOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_exp_options(&self) -> Option<ExpOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ExpOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ExpOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_top_kv2_options(&self) -> Option<TopKV2Options<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::TopKV2Options {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { TopKV2Options::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_split_options(&self) -> Option<SplitOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SplitOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SplitOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_log_softmax_options(&self) -> Option<LogSoftmaxOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LogSoftmaxOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LogSoftmaxOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_cast_options(&self) -> Option<CastOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::CastOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { CastOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_dequantize_options(&self) -> Option<DequantizeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::DequantizeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { DequantizeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_maximum_minimum_options(\n            &self,\n        ) -> Option<MaximumMinimumOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::MaximumMinimumOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { MaximumMinimumOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_arg_max_options(&self) -> Option<ArgMaxOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ArgMaxOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ArgMaxOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_less_options(&self) -> Option<LessOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LessOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LessOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_neg_options(&self) -> Option<NegOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::NegOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { NegOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_pad_v2_options(&self) -> Option<PadV2Options<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::PadV2Options {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { PadV2Options::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_greater_options(&self) -> Option<GreaterOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::GreaterOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { GreaterOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_greater_equal_options(&self) -> Option<GreaterEqualOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::GreaterEqualOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { GreaterEqualOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_less_equal_options(&self) -> Option<LessEqualOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LessEqualOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LessEqualOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_select_options(&self) -> Option<SelectOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SelectOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SelectOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_slice_options(&self) -> Option<SliceOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SliceOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SliceOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_transpose_conv_options(\n            &self,\n        ) -> Option<TransposeConvOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::TransposeConvOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { TransposeConvOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_sparse_to_dense_options(\n            &self,\n        ) -> Option<SparseToDenseOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SparseToDenseOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SparseToDenseOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_tile_options(&self) -> Option<TileOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::TileOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { TileOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_expand_dims_options(&self) -> Option<ExpandDimsOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ExpandDimsOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ExpandDimsOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_equal_options(&self) -> Option<EqualOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::EqualOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { EqualOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_not_equal_options(&self) -> Option<NotEqualOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::NotEqualOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { NotEqualOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_shape_options(&self) -> Option<ShapeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ShapeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ShapeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_pow_options(&self) -> Option<PowOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::PowOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { PowOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_arg_min_options(&self) -> Option<ArgMinOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ArgMinOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ArgMinOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_fake_quant_options(&self) -> Option<FakeQuantOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::FakeQuantOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { FakeQuantOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_pack_options(&self) -> Option<PackOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::PackOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { PackOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_logical_or_options(&self) -> Option<LogicalOrOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LogicalOrOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LogicalOrOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_one_hot_options(&self) -> Option<OneHotOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::OneHotOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { OneHotOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_logical_and_options(&self) -> Option<LogicalAndOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LogicalAndOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LogicalAndOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_logical_not_options(&self) -> Option<LogicalNotOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LogicalNotOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LogicalNotOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_unpack_options(&self) -> Option<UnpackOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::UnpackOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { UnpackOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_floor_div_options(&self) -> Option<FloorDivOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::FloorDivOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { FloorDivOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_square_options(&self) -> Option<SquareOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SquareOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SquareOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_zeros_like_options(&self) -> Option<ZerosLikeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ZerosLikeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ZerosLikeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_fill_options(&self) -> Option<FillOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::FillOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { FillOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_bidirectional_sequence_lstmoptions(\n            &self,\n        ) -> Option<BidirectionalSequenceLSTMOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::BidirectionalSequenceLSTMOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { BidirectionalSequenceLSTMOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_bidirectional_sequence_rnnoptions(\n            &self,\n        ) -> Option<BidirectionalSequenceRNNOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::BidirectionalSequenceRNNOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { BidirectionalSequenceRNNOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_unidirectional_sequence_lstmoptions(\n            &self,\n        ) -> Option<UnidirectionalSequenceLSTMOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::UnidirectionalSequenceLSTMOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { UnidirectionalSequenceLSTMOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_floor_mod_options(&self) -> Option<FloorModOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::FloorModOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { FloorModOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_range_options(&self) -> Option<RangeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::RangeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { RangeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_resize_nearest_neighbor_options(\n            &self,\n        ) -> Option<ResizeNearestNeighborOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ResizeNearestNeighborOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ResizeNearestNeighborOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_leaky_relu_options(&self) -> Option<LeakyReluOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::LeakyReluOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { LeakyReluOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_squared_difference_options(\n            &self,\n        ) -> Option<SquaredDifferenceOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SquaredDifferenceOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SquaredDifferenceOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_mirror_pad_options(&self) -> Option<MirrorPadOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::MirrorPadOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { MirrorPadOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_abs_options(&self) -> Option<AbsOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::AbsOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { AbsOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_split_voptions(&self) -> Option<SplitVOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SplitVOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SplitVOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_unique_options(&self) -> Option<UniqueOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::UniqueOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { UniqueOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_reverse_v2_options(&self) -> Option<ReverseV2Options<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ReverseV2Options {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ReverseV2Options::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_add_noptions(&self) -> Option<AddNOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::AddNOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { AddNOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_gather_nd_options(&self) -> Option<GatherNdOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::GatherNdOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { GatherNdOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_cos_options(&self) -> Option<CosOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::CosOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { CosOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_where_options(&self) -> Option<WhereOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::WhereOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { WhereOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_rank_options(&self) -> Option<RankOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::RankOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { RankOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_reverse_sequence_options(\n            &self,\n        ) -> Option<ReverseSequenceOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ReverseSequenceOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ReverseSequenceOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_matrix_diag_options(&self) -> Option<MatrixDiagOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::MatrixDiagOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { MatrixDiagOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_quantize_options(&self) -> Option<QuantizeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::QuantizeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { QuantizeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_matrix_set_diag_options(\n            &self,\n        ) -> Option<MatrixSetDiagOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::MatrixSetDiagOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { MatrixSetDiagOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_hard_swish_options(&self) -> Option<HardSwishOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::HardSwishOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { HardSwishOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_if_options(&self) -> Option<IfOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::IfOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { IfOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_while_options(&self) -> Option<WhileOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::WhileOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { WhileOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_depth_to_space_options(&self) -> Option<DepthToSpaceOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::DepthToSpaceOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { DepthToSpaceOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_non_max_suppression_v4_options(\n            &self,\n        ) -> Option<NonMaxSuppressionV4Options<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::NonMaxSuppressionV4Options {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { NonMaxSuppressionV4Options::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_non_max_suppression_v5_options(\n            &self,\n        ) -> Option<NonMaxSuppressionV5Options<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::NonMaxSuppressionV5Options {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { NonMaxSuppressionV5Options::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_scatter_nd_options(&self) -> Option<ScatterNdOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ScatterNdOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ScatterNdOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_select_v2_options(&self) -> Option<SelectV2Options<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SelectV2Options {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SelectV2Options::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_densify_options(&self) -> Option<DensifyOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::DensifyOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { DensifyOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_segment_sum_options(&self) -> Option<SegmentSumOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SegmentSumOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SegmentSumOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_batch_mat_mul_options(&self) -> Option<BatchMatMulOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::BatchMatMulOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { BatchMatMulOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_cumsum_options(&self) -> Option<CumsumOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::CumsumOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { CumsumOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_call_once_options(&self) -> Option<CallOnceOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::CallOnceOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { CallOnceOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_broadcast_to_options(&self) -> Option<BroadcastToOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::BroadcastToOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { BroadcastToOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_rfft_2d_options(&self) -> Option<Rfft2dOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::Rfft2dOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Rfft2dOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_conv_3_doptions(&self) -> Option<Conv3DOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::Conv3DOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { Conv3DOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_hashtable_options(&self) -> Option<HashtableOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::HashtableOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { HashtableOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_hashtable_find_options(\n            &self,\n        ) -> Option<HashtableFindOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::HashtableFindOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { HashtableFindOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_hashtable_import_options(\n            &self,\n        ) -> Option<HashtableImportOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::HashtableImportOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { HashtableImportOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_hashtable_size_options(\n            &self,\n        ) -> Option<HashtableSizeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::HashtableSizeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { HashtableSizeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_var_handle_options(&self) -> Option<VarHandleOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::VarHandleOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { VarHandleOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_read_variable_options(&self) -> Option<ReadVariableOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ReadVariableOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ReadVariableOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_assign_variable_options(\n            &self,\n        ) -> Option<AssignVariableOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::AssignVariableOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { AssignVariableOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_random_options(&self) -> Option<RandomOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::RandomOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { RandomOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_bucketize_options(&self) -> Option<BucketizeOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::BucketizeOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { BucketizeOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_gelu_options(&self) -> Option<GeluOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::GeluOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { GeluOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_dynamic_update_slice_options(\n            &self,\n        ) -> Option<DynamicUpdateSliceOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::DynamicUpdateSliceOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { DynamicUpdateSliceOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_unsorted_segment_prod_options(\n            &self,\n        ) -> Option<UnsortedSegmentProdOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::UnsortedSegmentProdOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { UnsortedSegmentProdOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_unsorted_segment_max_options(\n            &self,\n        ) -> Option<UnsortedSegmentMaxOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::UnsortedSegmentMaxOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { UnsortedSegmentMaxOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_unsorted_segment_min_options(\n            &self,\n        ) -> Option<UnsortedSegmentMinOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::UnsortedSegmentMinOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { UnsortedSegmentMinOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_unsorted_segment_sum_options(\n            &self,\n        ) -> Option<UnsortedSegmentSumOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::UnsortedSegmentSumOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { UnsortedSegmentSumOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_atan_2_options(&self) -> Option<ATan2Options<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::ATan2Options {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { ATan2Options::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_sign_options(&self) -> Option<SignOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::SignOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { SignOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_bitcast_options(&self) -> Option<BitcastOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::BitcastOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { BitcastOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_bitwise_xor_options(&self) -> Option<BitwiseXorOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::BitwiseXorOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { BitwiseXorOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n\n        #[inline]\n        #[allow(non_snake_case)]\n        pub fn builtin_options_as_right_shift_options(&self) -> Option<RightShiftOptions<'a>> {\n            if self.builtin_options_type() == BuiltinOptions::RightShiftOptions {\n                self.builtin_options().map(|t| {\n                    // Safety:\n                    // Created from a valid Table for this object\n                    // Which contains a valid union in this slot\n                    unsafe { RightShiftOptions::init_from_table(t) }\n                })\n            } else {\n                None\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Operator<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n     .visit_field::<u32>(\"opcode_index\", Self::VT_OPCODE_INDEX, false)?\n     .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\"inputs\", Self::VT_INPUTS, false)?\n     .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\"outputs\", Self::VT_OUTPUTS, false)?\n     .visit_union::<BuiltinOptions, _>(\"builtin_options_type\", Self::VT_BUILTIN_OPTIONS_TYPE, \"builtin_options\", Self::VT_BUILTIN_OPTIONS, false, |key, v, pos| {\n        match key {\n          BuiltinOptions::Conv2DOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<Conv2DOptions>>(\"BuiltinOptions::Conv2DOptions\", pos),\n          BuiltinOptions::DepthwiseConv2DOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<DepthwiseConv2DOptions>>(\"BuiltinOptions::DepthwiseConv2DOptions\", pos),\n          BuiltinOptions::ConcatEmbeddingsOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ConcatEmbeddingsOptions>>(\"BuiltinOptions::ConcatEmbeddingsOptions\", pos),\n          BuiltinOptions::LSHProjectionOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LSHProjectionOptions>>(\"BuiltinOptions::LSHProjectionOptions\", pos),\n          BuiltinOptions::Pool2DOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<Pool2DOptions>>(\"BuiltinOptions::Pool2DOptions\", pos),\n          BuiltinOptions::SVDFOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SVDFOptions>>(\"BuiltinOptions::SVDFOptions\", pos),\n          BuiltinOptions::RNNOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<RNNOptions>>(\"BuiltinOptions::RNNOptions\", pos),\n          BuiltinOptions::FullyConnectedOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<FullyConnectedOptions>>(\"BuiltinOptions::FullyConnectedOptions\", pos),\n          BuiltinOptions::SoftmaxOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SoftmaxOptions>>(\"BuiltinOptions::SoftmaxOptions\", pos),\n          BuiltinOptions::ConcatenationOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ConcatenationOptions>>(\"BuiltinOptions::ConcatenationOptions\", pos),\n          BuiltinOptions::AddOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<AddOptions>>(\"BuiltinOptions::AddOptions\", pos),\n          BuiltinOptions::L2NormOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<L2NormOptions>>(\"BuiltinOptions::L2NormOptions\", pos),\n          BuiltinOptions::LocalResponseNormalizationOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LocalResponseNormalizationOptions>>(\"BuiltinOptions::LocalResponseNormalizationOptions\", pos),\n          BuiltinOptions::LSTMOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LSTMOptions>>(\"BuiltinOptions::LSTMOptions\", pos),\n          BuiltinOptions::ResizeBilinearOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ResizeBilinearOptions>>(\"BuiltinOptions::ResizeBilinearOptions\", pos),\n          BuiltinOptions::CallOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<CallOptions>>(\"BuiltinOptions::CallOptions\", pos),\n          BuiltinOptions::ReshapeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ReshapeOptions>>(\"BuiltinOptions::ReshapeOptions\", pos),\n          BuiltinOptions::SkipGramOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SkipGramOptions>>(\"BuiltinOptions::SkipGramOptions\", pos),\n          BuiltinOptions::SpaceToDepthOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SpaceToDepthOptions>>(\"BuiltinOptions::SpaceToDepthOptions\", pos),\n          BuiltinOptions::EmbeddingLookupSparseOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<EmbeddingLookupSparseOptions>>(\"BuiltinOptions::EmbeddingLookupSparseOptions\", pos),\n          BuiltinOptions::MulOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<MulOptions>>(\"BuiltinOptions::MulOptions\", pos),\n          BuiltinOptions::PadOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<PadOptions>>(\"BuiltinOptions::PadOptions\", pos),\n          BuiltinOptions::GatherOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<GatherOptions>>(\"BuiltinOptions::GatherOptions\", pos),\n          BuiltinOptions::BatchToSpaceNDOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<BatchToSpaceNDOptions>>(\"BuiltinOptions::BatchToSpaceNDOptions\", pos),\n          BuiltinOptions::SpaceToBatchNDOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SpaceToBatchNDOptions>>(\"BuiltinOptions::SpaceToBatchNDOptions\", pos),\n          BuiltinOptions::TransposeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<TransposeOptions>>(\"BuiltinOptions::TransposeOptions\", pos),\n          BuiltinOptions::ReducerOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ReducerOptions>>(\"BuiltinOptions::ReducerOptions\", pos),\n          BuiltinOptions::SubOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SubOptions>>(\"BuiltinOptions::SubOptions\", pos),\n          BuiltinOptions::DivOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<DivOptions>>(\"BuiltinOptions::DivOptions\", pos),\n          BuiltinOptions::SqueezeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SqueezeOptions>>(\"BuiltinOptions::SqueezeOptions\", pos),\n          BuiltinOptions::SequenceRNNOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SequenceRNNOptions>>(\"BuiltinOptions::SequenceRNNOptions\", pos),\n          BuiltinOptions::StridedSliceOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<StridedSliceOptions>>(\"BuiltinOptions::StridedSliceOptions\", pos),\n          BuiltinOptions::ExpOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ExpOptions>>(\"BuiltinOptions::ExpOptions\", pos),\n          BuiltinOptions::TopKV2Options => v.verify_union_variant::<flatbuffers::ForwardsUOffset<TopKV2Options>>(\"BuiltinOptions::TopKV2Options\", pos),\n          BuiltinOptions::SplitOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SplitOptions>>(\"BuiltinOptions::SplitOptions\", pos),\n          BuiltinOptions::LogSoftmaxOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LogSoftmaxOptions>>(\"BuiltinOptions::LogSoftmaxOptions\", pos),\n          BuiltinOptions::CastOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<CastOptions>>(\"BuiltinOptions::CastOptions\", pos),\n          BuiltinOptions::DequantizeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<DequantizeOptions>>(\"BuiltinOptions::DequantizeOptions\", pos),\n          BuiltinOptions::MaximumMinimumOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<MaximumMinimumOptions>>(\"BuiltinOptions::MaximumMinimumOptions\", pos),\n          BuiltinOptions::ArgMaxOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ArgMaxOptions>>(\"BuiltinOptions::ArgMaxOptions\", pos),\n          BuiltinOptions::LessOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LessOptions>>(\"BuiltinOptions::LessOptions\", pos),\n          BuiltinOptions::NegOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<NegOptions>>(\"BuiltinOptions::NegOptions\", pos),\n          BuiltinOptions::PadV2Options => v.verify_union_variant::<flatbuffers::ForwardsUOffset<PadV2Options>>(\"BuiltinOptions::PadV2Options\", pos),\n          BuiltinOptions::GreaterOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<GreaterOptions>>(\"BuiltinOptions::GreaterOptions\", pos),\n          BuiltinOptions::GreaterEqualOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<GreaterEqualOptions>>(\"BuiltinOptions::GreaterEqualOptions\", pos),\n          BuiltinOptions::LessEqualOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LessEqualOptions>>(\"BuiltinOptions::LessEqualOptions\", pos),\n          BuiltinOptions::SelectOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SelectOptions>>(\"BuiltinOptions::SelectOptions\", pos),\n          BuiltinOptions::SliceOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SliceOptions>>(\"BuiltinOptions::SliceOptions\", pos),\n          BuiltinOptions::TransposeConvOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<TransposeConvOptions>>(\"BuiltinOptions::TransposeConvOptions\", pos),\n          BuiltinOptions::SparseToDenseOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SparseToDenseOptions>>(\"BuiltinOptions::SparseToDenseOptions\", pos),\n          BuiltinOptions::TileOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<TileOptions>>(\"BuiltinOptions::TileOptions\", pos),\n          BuiltinOptions::ExpandDimsOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ExpandDimsOptions>>(\"BuiltinOptions::ExpandDimsOptions\", pos),\n          BuiltinOptions::EqualOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<EqualOptions>>(\"BuiltinOptions::EqualOptions\", pos),\n          BuiltinOptions::NotEqualOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<NotEqualOptions>>(\"BuiltinOptions::NotEqualOptions\", pos),\n          BuiltinOptions::ShapeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ShapeOptions>>(\"BuiltinOptions::ShapeOptions\", pos),\n          BuiltinOptions::PowOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<PowOptions>>(\"BuiltinOptions::PowOptions\", pos),\n          BuiltinOptions::ArgMinOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ArgMinOptions>>(\"BuiltinOptions::ArgMinOptions\", pos),\n          BuiltinOptions::FakeQuantOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<FakeQuantOptions>>(\"BuiltinOptions::FakeQuantOptions\", pos),\n          BuiltinOptions::PackOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<PackOptions>>(\"BuiltinOptions::PackOptions\", pos),\n          BuiltinOptions::LogicalOrOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LogicalOrOptions>>(\"BuiltinOptions::LogicalOrOptions\", pos),\n          BuiltinOptions::OneHotOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<OneHotOptions>>(\"BuiltinOptions::OneHotOptions\", pos),\n          BuiltinOptions::LogicalAndOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LogicalAndOptions>>(\"BuiltinOptions::LogicalAndOptions\", pos),\n          BuiltinOptions::LogicalNotOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LogicalNotOptions>>(\"BuiltinOptions::LogicalNotOptions\", pos),\n          BuiltinOptions::UnpackOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<UnpackOptions>>(\"BuiltinOptions::UnpackOptions\", pos),\n          BuiltinOptions::FloorDivOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<FloorDivOptions>>(\"BuiltinOptions::FloorDivOptions\", pos),\n          BuiltinOptions::SquareOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SquareOptions>>(\"BuiltinOptions::SquareOptions\", pos),\n          BuiltinOptions::ZerosLikeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ZerosLikeOptions>>(\"BuiltinOptions::ZerosLikeOptions\", pos),\n          BuiltinOptions::FillOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<FillOptions>>(\"BuiltinOptions::FillOptions\", pos),\n          BuiltinOptions::BidirectionalSequenceLSTMOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<BidirectionalSequenceLSTMOptions>>(\"BuiltinOptions::BidirectionalSequenceLSTMOptions\", pos),\n          BuiltinOptions::BidirectionalSequenceRNNOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<BidirectionalSequenceRNNOptions>>(\"BuiltinOptions::BidirectionalSequenceRNNOptions\", pos),\n          BuiltinOptions::UnidirectionalSequenceLSTMOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<UnidirectionalSequenceLSTMOptions>>(\"BuiltinOptions::UnidirectionalSequenceLSTMOptions\", pos),\n          BuiltinOptions::FloorModOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<FloorModOptions>>(\"BuiltinOptions::FloorModOptions\", pos),\n          BuiltinOptions::RangeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<RangeOptions>>(\"BuiltinOptions::RangeOptions\", pos),\n          BuiltinOptions::ResizeNearestNeighborOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ResizeNearestNeighborOptions>>(\"BuiltinOptions::ResizeNearestNeighborOptions\", pos),\n          BuiltinOptions::LeakyReluOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<LeakyReluOptions>>(\"BuiltinOptions::LeakyReluOptions\", pos),\n          BuiltinOptions::SquaredDifferenceOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SquaredDifferenceOptions>>(\"BuiltinOptions::SquaredDifferenceOptions\", pos),\n          BuiltinOptions::MirrorPadOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<MirrorPadOptions>>(\"BuiltinOptions::MirrorPadOptions\", pos),\n          BuiltinOptions::AbsOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<AbsOptions>>(\"BuiltinOptions::AbsOptions\", pos),\n          BuiltinOptions::SplitVOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SplitVOptions>>(\"BuiltinOptions::SplitVOptions\", pos),\n          BuiltinOptions::UniqueOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<UniqueOptions>>(\"BuiltinOptions::UniqueOptions\", pos),\n          BuiltinOptions::ReverseV2Options => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ReverseV2Options>>(\"BuiltinOptions::ReverseV2Options\", pos),\n          BuiltinOptions::AddNOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<AddNOptions>>(\"BuiltinOptions::AddNOptions\", pos),\n          BuiltinOptions::GatherNdOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<GatherNdOptions>>(\"BuiltinOptions::GatherNdOptions\", pos),\n          BuiltinOptions::CosOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<CosOptions>>(\"BuiltinOptions::CosOptions\", pos),\n          BuiltinOptions::WhereOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<WhereOptions>>(\"BuiltinOptions::WhereOptions\", pos),\n          BuiltinOptions::RankOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<RankOptions>>(\"BuiltinOptions::RankOptions\", pos),\n          BuiltinOptions::ReverseSequenceOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ReverseSequenceOptions>>(\"BuiltinOptions::ReverseSequenceOptions\", pos),\n          BuiltinOptions::MatrixDiagOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<MatrixDiagOptions>>(\"BuiltinOptions::MatrixDiagOptions\", pos),\n          BuiltinOptions::QuantizeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<QuantizeOptions>>(\"BuiltinOptions::QuantizeOptions\", pos),\n          BuiltinOptions::MatrixSetDiagOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<MatrixSetDiagOptions>>(\"BuiltinOptions::MatrixSetDiagOptions\", pos),\n          BuiltinOptions::HardSwishOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<HardSwishOptions>>(\"BuiltinOptions::HardSwishOptions\", pos),\n          BuiltinOptions::IfOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<IfOptions>>(\"BuiltinOptions::IfOptions\", pos),\n          BuiltinOptions::WhileOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<WhileOptions>>(\"BuiltinOptions::WhileOptions\", pos),\n          BuiltinOptions::DepthToSpaceOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<DepthToSpaceOptions>>(\"BuiltinOptions::DepthToSpaceOptions\", pos),\n          BuiltinOptions::NonMaxSuppressionV4Options => v.verify_union_variant::<flatbuffers::ForwardsUOffset<NonMaxSuppressionV4Options>>(\"BuiltinOptions::NonMaxSuppressionV4Options\", pos),\n          BuiltinOptions::NonMaxSuppressionV5Options => v.verify_union_variant::<flatbuffers::ForwardsUOffset<NonMaxSuppressionV5Options>>(\"BuiltinOptions::NonMaxSuppressionV5Options\", pos),\n          BuiltinOptions::ScatterNdOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ScatterNdOptions>>(\"BuiltinOptions::ScatterNdOptions\", pos),\n          BuiltinOptions::SelectV2Options => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SelectV2Options>>(\"BuiltinOptions::SelectV2Options\", pos),\n          BuiltinOptions::DensifyOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<DensifyOptions>>(\"BuiltinOptions::DensifyOptions\", pos),\n          BuiltinOptions::SegmentSumOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SegmentSumOptions>>(\"BuiltinOptions::SegmentSumOptions\", pos),\n          BuiltinOptions::BatchMatMulOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<BatchMatMulOptions>>(\"BuiltinOptions::BatchMatMulOptions\", pos),\n          BuiltinOptions::CumsumOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<CumsumOptions>>(\"BuiltinOptions::CumsumOptions\", pos),\n          BuiltinOptions::CallOnceOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<CallOnceOptions>>(\"BuiltinOptions::CallOnceOptions\", pos),\n          BuiltinOptions::BroadcastToOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<BroadcastToOptions>>(\"BuiltinOptions::BroadcastToOptions\", pos),\n          BuiltinOptions::Rfft2dOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<Rfft2dOptions>>(\"BuiltinOptions::Rfft2dOptions\", pos),\n          BuiltinOptions::Conv3DOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<Conv3DOptions>>(\"BuiltinOptions::Conv3DOptions\", pos),\n          BuiltinOptions::HashtableOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<HashtableOptions>>(\"BuiltinOptions::HashtableOptions\", pos),\n          BuiltinOptions::HashtableFindOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<HashtableFindOptions>>(\"BuiltinOptions::HashtableFindOptions\", pos),\n          BuiltinOptions::HashtableImportOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<HashtableImportOptions>>(\"BuiltinOptions::HashtableImportOptions\", pos),\n          BuiltinOptions::HashtableSizeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<HashtableSizeOptions>>(\"BuiltinOptions::HashtableSizeOptions\", pos),\n          BuiltinOptions::VarHandleOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<VarHandleOptions>>(\"BuiltinOptions::VarHandleOptions\", pos),\n          BuiltinOptions::ReadVariableOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ReadVariableOptions>>(\"BuiltinOptions::ReadVariableOptions\", pos),\n          BuiltinOptions::AssignVariableOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<AssignVariableOptions>>(\"BuiltinOptions::AssignVariableOptions\", pos),\n          BuiltinOptions::RandomOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<RandomOptions>>(\"BuiltinOptions::RandomOptions\", pos),\n          BuiltinOptions::BucketizeOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<BucketizeOptions>>(\"BuiltinOptions::BucketizeOptions\", pos),\n          BuiltinOptions::GeluOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<GeluOptions>>(\"BuiltinOptions::GeluOptions\", pos),\n          BuiltinOptions::DynamicUpdateSliceOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<DynamicUpdateSliceOptions>>(\"BuiltinOptions::DynamicUpdateSliceOptions\", pos),\n          BuiltinOptions::UnsortedSegmentProdOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<UnsortedSegmentProdOptions>>(\"BuiltinOptions::UnsortedSegmentProdOptions\", pos),\n          BuiltinOptions::UnsortedSegmentMaxOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<UnsortedSegmentMaxOptions>>(\"BuiltinOptions::UnsortedSegmentMaxOptions\", pos),\n          BuiltinOptions::UnsortedSegmentMinOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<UnsortedSegmentMinOptions>>(\"BuiltinOptions::UnsortedSegmentMinOptions\", pos),\n          BuiltinOptions::UnsortedSegmentSumOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<UnsortedSegmentSumOptions>>(\"BuiltinOptions::UnsortedSegmentSumOptions\", pos),\n          BuiltinOptions::ATan2Options => v.verify_union_variant::<flatbuffers::ForwardsUOffset<ATan2Options>>(\"BuiltinOptions::ATan2Options\", pos),\n          BuiltinOptions::SignOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<SignOptions>>(\"BuiltinOptions::SignOptions\", pos),\n          BuiltinOptions::BitcastOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<BitcastOptions>>(\"BuiltinOptions::BitcastOptions\", pos),\n          BuiltinOptions::BitwiseXorOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<BitwiseXorOptions>>(\"BuiltinOptions::BitwiseXorOptions\", pos),\n          BuiltinOptions::RightShiftOptions => v.verify_union_variant::<flatbuffers::ForwardsUOffset<RightShiftOptions>>(\"BuiltinOptions::RightShiftOptions\", pos),\n          _ => Ok(()),\n        }\n     })?\n     .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, u8>>>(\"custom_options\", Self::VT_CUSTOM_OPTIONS, false)?\n     .visit_field::<CustomOptionsFormat>(\"custom_options_format\", Self::VT_CUSTOM_OPTIONS_FORMAT, false)?\n     .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, bool>>>(\"mutating_variable_inputs\", Self::VT_MUTATING_VARIABLE_INPUTS, false)?\n     .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\"intermediates\", Self::VT_INTERMEDIATES, false)?\n     .finish();\n            Ok(())\n        }\n    }\n    pub struct OperatorArgs<'a> {\n        pub opcode_index: u32,\n        pub inputs: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub outputs: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub builtin_options_type: BuiltinOptions,\n        pub builtin_options: Option<flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>>,\n        pub custom_options: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, u8>>>,\n        pub custom_options_format: CustomOptionsFormat,\n        pub mutating_variable_inputs: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, bool>>>,\n        pub intermediates: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n    }\n    impl<'a> Default for OperatorArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            OperatorArgs {\n                opcode_index: 0,\n                inputs: None,\n                outputs: None,\n                builtin_options_type: BuiltinOptions::NONE,\n                builtin_options: None,\n                custom_options: None,\n                custom_options_format: CustomOptionsFormat::FLEXBUFFERS,\n                mutating_variable_inputs: None,\n                intermediates: None,\n            }\n        }\n    }\n\n    pub struct OperatorBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> OperatorBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_opcode_index(&mut self, opcode_index: u32) {\n            self.fbb_.push_slot::<u32>(Operator::VT_OPCODE_INDEX, opcode_index, 0);\n        }\n        #[inline]\n        pub fn add_inputs(&mut self, inputs: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Operator::VT_INPUTS, inputs);\n        }\n        #[inline]\n        pub fn add_outputs(\n            &mut self,\n            outputs: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Operator::VT_OUTPUTS, outputs);\n        }\n        #[inline]\n        pub fn add_builtin_options_type(&mut self, builtin_options_type: BuiltinOptions) {\n            self.fbb_.push_slot::<BuiltinOptions>(\n                Operator::VT_BUILTIN_OPTIONS_TYPE,\n                builtin_options_type,\n                BuiltinOptions::NONE,\n            );\n        }\n        #[inline]\n        pub fn add_builtin_options(\n            &mut self,\n            builtin_options: flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                Operator::VT_BUILTIN_OPTIONS,\n                builtin_options,\n            );\n        }\n        #[inline]\n        pub fn add_custom_options(\n            &mut self,\n            custom_options: flatbuffers::WIPOffset<flatbuffers::Vector<'b, u8>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                Operator::VT_CUSTOM_OPTIONS,\n                custom_options,\n            );\n        }\n        #[inline]\n        pub fn add_custom_options_format(&mut self, custom_options_format: CustomOptionsFormat) {\n            self.fbb_.push_slot::<CustomOptionsFormat>(\n                Operator::VT_CUSTOM_OPTIONS_FORMAT,\n                custom_options_format,\n                CustomOptionsFormat::FLEXBUFFERS,\n            );\n        }\n        #[inline]\n        pub fn add_mutating_variable_inputs(\n            &mut self,\n            mutating_variable_inputs: flatbuffers::WIPOffset<flatbuffers::Vector<'b, bool>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                Operator::VT_MUTATING_VARIABLE_INPUTS,\n                mutating_variable_inputs,\n            );\n        }\n        #[inline]\n        pub fn add_intermediates(\n            &mut self,\n            intermediates: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                Operator::VT_INTERMEDIATES,\n                intermediates,\n            );\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> OperatorBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            OperatorBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Operator<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Operator<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Operator\");\n            ds.field(\"opcode_index\", &self.opcode_index());\n            ds.field(\"inputs\", &self.inputs());\n            ds.field(\"outputs\", &self.outputs());\n            ds.field(\"builtin_options_type\", &self.builtin_options_type());\n            match self.builtin_options_type() {\n                BuiltinOptions::Conv2DOptions => {\n                    if let Some(x) = self.builtin_options_as_conv_2_doptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::DepthwiseConv2DOptions => {\n                    if let Some(x) = self.builtin_options_as_depthwise_conv_2_doptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ConcatEmbeddingsOptions => {\n                    if let Some(x) = self.builtin_options_as_concat_embeddings_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LSHProjectionOptions => {\n                    if let Some(x) = self.builtin_options_as_lshprojection_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::Pool2DOptions => {\n                    if let Some(x) = self.builtin_options_as_pool_2_doptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SVDFOptions => {\n                    if let Some(x) = self.builtin_options_as_svdfoptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::RNNOptions => {\n                    if let Some(x) = self.builtin_options_as_rnnoptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::FullyConnectedOptions => {\n                    if let Some(x) = self.builtin_options_as_fully_connected_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SoftmaxOptions => {\n                    if let Some(x) = self.builtin_options_as_softmax_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ConcatenationOptions => {\n                    if let Some(x) = self.builtin_options_as_concatenation_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::AddOptions => {\n                    if let Some(x) = self.builtin_options_as_add_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::L2NormOptions => {\n                    if let Some(x) = self.builtin_options_as_l2_norm_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LocalResponseNormalizationOptions => {\n                    if let Some(x) = self.builtin_options_as_local_response_normalization_options()\n                    {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LSTMOptions => {\n                    if let Some(x) = self.builtin_options_as_lstmoptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ResizeBilinearOptions => {\n                    if let Some(x) = self.builtin_options_as_resize_bilinear_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::CallOptions => {\n                    if let Some(x) = self.builtin_options_as_call_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ReshapeOptions => {\n                    if let Some(x) = self.builtin_options_as_reshape_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SkipGramOptions => {\n                    if let Some(x) = self.builtin_options_as_skip_gram_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SpaceToDepthOptions => {\n                    if let Some(x) = self.builtin_options_as_space_to_depth_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::EmbeddingLookupSparseOptions => {\n                    if let Some(x) = self.builtin_options_as_embedding_lookup_sparse_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::MulOptions => {\n                    if let Some(x) = self.builtin_options_as_mul_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::PadOptions => {\n                    if let Some(x) = self.builtin_options_as_pad_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::GatherOptions => {\n                    if let Some(x) = self.builtin_options_as_gather_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::BatchToSpaceNDOptions => {\n                    if let Some(x) = self.builtin_options_as_batch_to_space_ndoptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SpaceToBatchNDOptions => {\n                    if let Some(x) = self.builtin_options_as_space_to_batch_ndoptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::TransposeOptions => {\n                    if let Some(x) = self.builtin_options_as_transpose_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ReducerOptions => {\n                    if let Some(x) = self.builtin_options_as_reducer_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SubOptions => {\n                    if let Some(x) = self.builtin_options_as_sub_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::DivOptions => {\n                    if let Some(x) = self.builtin_options_as_div_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SqueezeOptions => {\n                    if let Some(x) = self.builtin_options_as_squeeze_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SequenceRNNOptions => {\n                    if let Some(x) = self.builtin_options_as_sequence_rnnoptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::StridedSliceOptions => {\n                    if let Some(x) = self.builtin_options_as_strided_slice_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ExpOptions => {\n                    if let Some(x) = self.builtin_options_as_exp_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::TopKV2Options => {\n                    if let Some(x) = self.builtin_options_as_top_kv2_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SplitOptions => {\n                    if let Some(x) = self.builtin_options_as_split_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LogSoftmaxOptions => {\n                    if let Some(x) = self.builtin_options_as_log_softmax_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::CastOptions => {\n                    if let Some(x) = self.builtin_options_as_cast_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::DequantizeOptions => {\n                    if let Some(x) = self.builtin_options_as_dequantize_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::MaximumMinimumOptions => {\n                    if let Some(x) = self.builtin_options_as_maximum_minimum_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ArgMaxOptions => {\n                    if let Some(x) = self.builtin_options_as_arg_max_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LessOptions => {\n                    if let Some(x) = self.builtin_options_as_less_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::NegOptions => {\n                    if let Some(x) = self.builtin_options_as_neg_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::PadV2Options => {\n                    if let Some(x) = self.builtin_options_as_pad_v2_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::GreaterOptions => {\n                    if let Some(x) = self.builtin_options_as_greater_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::GreaterEqualOptions => {\n                    if let Some(x) = self.builtin_options_as_greater_equal_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LessEqualOptions => {\n                    if let Some(x) = self.builtin_options_as_less_equal_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SelectOptions => {\n                    if let Some(x) = self.builtin_options_as_select_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SliceOptions => {\n                    if let Some(x) = self.builtin_options_as_slice_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::TransposeConvOptions => {\n                    if let Some(x) = self.builtin_options_as_transpose_conv_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SparseToDenseOptions => {\n                    if let Some(x) = self.builtin_options_as_sparse_to_dense_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::TileOptions => {\n                    if let Some(x) = self.builtin_options_as_tile_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ExpandDimsOptions => {\n                    if let Some(x) = self.builtin_options_as_expand_dims_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::EqualOptions => {\n                    if let Some(x) = self.builtin_options_as_equal_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::NotEqualOptions => {\n                    if let Some(x) = self.builtin_options_as_not_equal_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ShapeOptions => {\n                    if let Some(x) = self.builtin_options_as_shape_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::PowOptions => {\n                    if let Some(x) = self.builtin_options_as_pow_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ArgMinOptions => {\n                    if let Some(x) = self.builtin_options_as_arg_min_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::FakeQuantOptions => {\n                    if let Some(x) = self.builtin_options_as_fake_quant_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::PackOptions => {\n                    if let Some(x) = self.builtin_options_as_pack_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LogicalOrOptions => {\n                    if let Some(x) = self.builtin_options_as_logical_or_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::OneHotOptions => {\n                    if let Some(x) = self.builtin_options_as_one_hot_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LogicalAndOptions => {\n                    if let Some(x) = self.builtin_options_as_logical_and_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LogicalNotOptions => {\n                    if let Some(x) = self.builtin_options_as_logical_not_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::UnpackOptions => {\n                    if let Some(x) = self.builtin_options_as_unpack_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::FloorDivOptions => {\n                    if let Some(x) = self.builtin_options_as_floor_div_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SquareOptions => {\n                    if let Some(x) = self.builtin_options_as_square_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ZerosLikeOptions => {\n                    if let Some(x) = self.builtin_options_as_zeros_like_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::FillOptions => {\n                    if let Some(x) = self.builtin_options_as_fill_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::BidirectionalSequenceLSTMOptions => {\n                    if let Some(x) = self.builtin_options_as_bidirectional_sequence_lstmoptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::BidirectionalSequenceRNNOptions => {\n                    if let Some(x) = self.builtin_options_as_bidirectional_sequence_rnnoptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::UnidirectionalSequenceLSTMOptions => {\n                    if let Some(x) = self.builtin_options_as_unidirectional_sequence_lstmoptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::FloorModOptions => {\n                    if let Some(x) = self.builtin_options_as_floor_mod_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::RangeOptions => {\n                    if let Some(x) = self.builtin_options_as_range_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ResizeNearestNeighborOptions => {\n                    if let Some(x) = self.builtin_options_as_resize_nearest_neighbor_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::LeakyReluOptions => {\n                    if let Some(x) = self.builtin_options_as_leaky_relu_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SquaredDifferenceOptions => {\n                    if let Some(x) = self.builtin_options_as_squared_difference_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::MirrorPadOptions => {\n                    if let Some(x) = self.builtin_options_as_mirror_pad_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::AbsOptions => {\n                    if let Some(x) = self.builtin_options_as_abs_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SplitVOptions => {\n                    if let Some(x) = self.builtin_options_as_split_voptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::UniqueOptions => {\n                    if let Some(x) = self.builtin_options_as_unique_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ReverseV2Options => {\n                    if let Some(x) = self.builtin_options_as_reverse_v2_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::AddNOptions => {\n                    if let Some(x) = self.builtin_options_as_add_noptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::GatherNdOptions => {\n                    if let Some(x) = self.builtin_options_as_gather_nd_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::CosOptions => {\n                    if let Some(x) = self.builtin_options_as_cos_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::WhereOptions => {\n                    if let Some(x) = self.builtin_options_as_where_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::RankOptions => {\n                    if let Some(x) = self.builtin_options_as_rank_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ReverseSequenceOptions => {\n                    if let Some(x) = self.builtin_options_as_reverse_sequence_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::MatrixDiagOptions => {\n                    if let Some(x) = self.builtin_options_as_matrix_diag_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::QuantizeOptions => {\n                    if let Some(x) = self.builtin_options_as_quantize_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::MatrixSetDiagOptions => {\n                    if let Some(x) = self.builtin_options_as_matrix_set_diag_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::HardSwishOptions => {\n                    if let Some(x) = self.builtin_options_as_hard_swish_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::IfOptions => {\n                    if let Some(x) = self.builtin_options_as_if_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::WhileOptions => {\n                    if let Some(x) = self.builtin_options_as_while_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::DepthToSpaceOptions => {\n                    if let Some(x) = self.builtin_options_as_depth_to_space_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::NonMaxSuppressionV4Options => {\n                    if let Some(x) = self.builtin_options_as_non_max_suppression_v4_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::NonMaxSuppressionV5Options => {\n                    if let Some(x) = self.builtin_options_as_non_max_suppression_v5_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ScatterNdOptions => {\n                    if let Some(x) = self.builtin_options_as_scatter_nd_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SelectV2Options => {\n                    if let Some(x) = self.builtin_options_as_select_v2_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::DensifyOptions => {\n                    if let Some(x) = self.builtin_options_as_densify_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SegmentSumOptions => {\n                    if let Some(x) = self.builtin_options_as_segment_sum_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::BatchMatMulOptions => {\n                    if let Some(x) = self.builtin_options_as_batch_mat_mul_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::CumsumOptions => {\n                    if let Some(x) = self.builtin_options_as_cumsum_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::CallOnceOptions => {\n                    if let Some(x) = self.builtin_options_as_call_once_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::BroadcastToOptions => {\n                    if let Some(x) = self.builtin_options_as_broadcast_to_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::Rfft2dOptions => {\n                    if let Some(x) = self.builtin_options_as_rfft_2d_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::Conv3DOptions => {\n                    if let Some(x) = self.builtin_options_as_conv_3_doptions() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::HashtableOptions => {\n                    if let Some(x) = self.builtin_options_as_hashtable_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::HashtableFindOptions => {\n                    if let Some(x) = self.builtin_options_as_hashtable_find_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::HashtableImportOptions => {\n                    if let Some(x) = self.builtin_options_as_hashtable_import_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::HashtableSizeOptions => {\n                    if let Some(x) = self.builtin_options_as_hashtable_size_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::VarHandleOptions => {\n                    if let Some(x) = self.builtin_options_as_var_handle_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ReadVariableOptions => {\n                    if let Some(x) = self.builtin_options_as_read_variable_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::AssignVariableOptions => {\n                    if let Some(x) = self.builtin_options_as_assign_variable_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::RandomOptions => {\n                    if let Some(x) = self.builtin_options_as_random_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::BucketizeOptions => {\n                    if let Some(x) = self.builtin_options_as_bucketize_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::GeluOptions => {\n                    if let Some(x) = self.builtin_options_as_gelu_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::DynamicUpdateSliceOptions => {\n                    if let Some(x) = self.builtin_options_as_dynamic_update_slice_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::UnsortedSegmentProdOptions => {\n                    if let Some(x) = self.builtin_options_as_unsorted_segment_prod_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::UnsortedSegmentMaxOptions => {\n                    if let Some(x) = self.builtin_options_as_unsorted_segment_max_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::UnsortedSegmentMinOptions => {\n                    if let Some(x) = self.builtin_options_as_unsorted_segment_min_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::UnsortedSegmentSumOptions => {\n                    if let Some(x) = self.builtin_options_as_unsorted_segment_sum_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::ATan2Options => {\n                    if let Some(x) = self.builtin_options_as_atan_2_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::SignOptions => {\n                    if let Some(x) = self.builtin_options_as_sign_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::BitcastOptions => {\n                    if let Some(x) = self.builtin_options_as_bitcast_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::BitwiseXorOptions => {\n                    if let Some(x) = self.builtin_options_as_bitwise_xor_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                BuiltinOptions::RightShiftOptions => {\n                    if let Some(x) = self.builtin_options_as_right_shift_options() {\n                        ds.field(\"builtin_options\", &x)\n                    } else {\n                        ds.field(\n                            \"builtin_options\",\n                            &\"InvalidFlatbuffer: Union discriminant does not match value.\",\n                        )\n                    }\n                }\n                _ => {\n                    let x: Option<()> = None;\n                    ds.field(\"builtin_options\", &x)\n                }\n            };\n            ds.field(\"custom_options\", &self.custom_options());\n            ds.field(\"custom_options_format\", &self.custom_options_format());\n            ds.field(\"mutating_variable_inputs\", &self.mutating_variable_inputs());\n            ds.field(\"intermediates\", &self.intermediates());\n            ds.finish()\n        }\n    }\n    pub enum SubGraphOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SubGraph<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SubGraph<'a> {\n        type Inner = SubGraph<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SubGraph<'a> {\n        pub const VT_TENSORS: flatbuffers::VOffsetT = 4;\n        pub const VT_INPUTS: flatbuffers::VOffsetT = 6;\n        pub const VT_OUTPUTS: flatbuffers::VOffsetT = 8;\n        pub const VT_OPERATORS: flatbuffers::VOffsetT = 10;\n        pub const VT_NAME: flatbuffers::VOffsetT = 12;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SubGraph { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SubGraphArgs<'args>,\n        ) -> flatbuffers::WIPOffset<SubGraph<'bldr>> {\n            let mut builder = SubGraphBuilder::new(_fbb);\n            if let Some(x) = args.name {\n                builder.add_name(x);\n            }\n            if let Some(x) = args.operators {\n                builder.add_operators(x);\n            }\n            if let Some(x) = args.outputs {\n                builder.add_outputs(x);\n            }\n            if let Some(x) = args.inputs {\n                builder.add_inputs(x);\n            }\n            if let Some(x) = args.tensors {\n                builder.add_tensors(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn tensors(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Tensor<'a>>>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Tensor>>,\n                >>(SubGraph::VT_TENSORS, None)\n            }\n        }\n        #[inline]\n        pub fn inputs(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    SubGraph::VT_INPUTS,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn outputs(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    SubGraph::VT_OUTPUTS,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn operators(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Operator<'a>>>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Operator>>,\n                >>(SubGraph::VT_OPERATORS, None)\n            }\n        }\n        #[inline]\n        pub fn name(&self) -> Option<&'a str> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<flatbuffers::ForwardsUOffset<&str>>(SubGraph::VT_NAME, None) }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SubGraph<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<Tensor>>,\n                >>(\"tensors\", Self::VT_TENSORS, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"inputs\",\n                    Self::VT_INPUTS,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"outputs\",\n                    Self::VT_OUTPUTS,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<Operator>>,\n                >>(\"operators\", Self::VT_OPERATORS, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<&str>>(\"name\", Self::VT_NAME, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SubGraphArgs<'a> {\n        pub tensors: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Tensor<'a>>>,\n            >,\n        >,\n        pub inputs: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub outputs: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub operators: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Operator<'a>>>,\n            >,\n        >,\n        pub name: Option<flatbuffers::WIPOffset<&'a str>>,\n    }\n    impl<'a> Default for SubGraphArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            SubGraphArgs { tensors: None, inputs: None, outputs: None, operators: None, name: None }\n        }\n    }\n\n    pub struct SubGraphBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SubGraphBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_tensors(\n            &mut self,\n            tensors: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<Tensor<'b>>>,\n            >,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(SubGraph::VT_TENSORS, tensors);\n        }\n        #[inline]\n        pub fn add_inputs(&mut self, inputs: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(SubGraph::VT_INPUTS, inputs);\n        }\n        #[inline]\n        pub fn add_outputs(\n            &mut self,\n            outputs: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(SubGraph::VT_OUTPUTS, outputs);\n        }\n        #[inline]\n        pub fn add_operators(\n            &mut self,\n            operators: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<Operator<'b>>>,\n            >,\n        ) {\n            self.fbb_\n                .push_slot_always::<flatbuffers::WIPOffset<_>>(SubGraph::VT_OPERATORS, operators);\n        }\n        #[inline]\n        pub fn add_name(&mut self, name: flatbuffers::WIPOffset<&'b str>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(SubGraph::VT_NAME, name);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> SubGraphBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SubGraphBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SubGraph<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SubGraph<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SubGraph\");\n            ds.field(\"tensors\", &self.tensors());\n            ds.field(\"inputs\", &self.inputs());\n            ds.field(\"outputs\", &self.outputs());\n            ds.field(\"operators\", &self.operators());\n            ds.field(\"name\", &self.name());\n            ds.finish()\n        }\n    }\n    pub enum BufferOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Buffer<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Buffer<'a> {\n        type Inner = Buffer<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Buffer<'a> {\n        pub const VT_DATA: flatbuffers::VOffsetT = 4;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Buffer { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args BufferArgs<'args>,\n        ) -> flatbuffers::WIPOffset<Buffer<'bldr>> {\n            let mut builder = BufferBuilder::new(_fbb);\n            if let Some(x) = args.data {\n                builder.add_data(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn data(&self) -> Option<flatbuffers::Vector<'a, u8>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, u8>>>(\n                    Buffer::VT_DATA,\n                    None,\n                )\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Buffer<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, u8>>>(\n                    \"data\",\n                    Self::VT_DATA,\n                    false,\n                )?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct BufferArgs<'a> {\n        pub data: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, u8>>>,\n    }\n    impl<'a> Default for BufferArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            BufferArgs { data: None }\n        }\n    }\n\n    pub struct BufferBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> BufferBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_data(&mut self, data: flatbuffers::WIPOffset<flatbuffers::Vector<'b, u8>>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Buffer::VT_DATA, data);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> BufferBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            BufferBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Buffer<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Buffer<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Buffer\");\n            ds.field(\"data\", &self.data());\n            ds.finish()\n        }\n    }\n    pub enum MetadataOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Metadata<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Metadata<'a> {\n        type Inner = Metadata<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Metadata<'a> {\n        pub const VT_NAME: flatbuffers::VOffsetT = 4;\n        pub const VT_BUFFER: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Metadata { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args MetadataArgs<'args>,\n        ) -> flatbuffers::WIPOffset<Metadata<'bldr>> {\n            let mut builder = MetadataBuilder::new(_fbb);\n            builder.add_buffer(args.buffer);\n            if let Some(x) = args.name {\n                builder.add_name(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn name(&self) -> Option<&'a str> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<flatbuffers::ForwardsUOffset<&str>>(Metadata::VT_NAME, None) }\n        }\n        #[inline]\n        pub fn buffer(&self) -> u32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<u32>(Metadata::VT_BUFFER, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Metadata<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<&str>>(\"name\", Self::VT_NAME, false)?\n                .visit_field::<u32>(\"buffer\", Self::VT_BUFFER, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct MetadataArgs<'a> {\n        pub name: Option<flatbuffers::WIPOffset<&'a str>>,\n        pub buffer: u32,\n    }\n    impl<'a> Default for MetadataArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            MetadataArgs { name: None, buffer: 0 }\n        }\n    }\n\n    pub struct MetadataBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> MetadataBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_name(&mut self, name: flatbuffers::WIPOffset<&'b str>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Metadata::VT_NAME, name);\n        }\n        #[inline]\n        pub fn add_buffer(&mut self, buffer: u32) {\n            self.fbb_.push_slot::<u32>(Metadata::VT_BUFFER, buffer, 0);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> MetadataBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            MetadataBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Metadata<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Metadata<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Metadata\");\n            ds.field(\"name\", &self.name());\n            ds.field(\"buffer\", &self.buffer());\n            ds.finish()\n        }\n    }\n    pub enum TensorMapOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct TensorMap<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for TensorMap<'a> {\n        type Inner = TensorMap<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> TensorMap<'a> {\n        pub const VT_NAME: flatbuffers::VOffsetT = 4;\n        pub const VT_TENSOR_INDEX: flatbuffers::VOffsetT = 6;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            TensorMap { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args TensorMapArgs<'args>,\n        ) -> flatbuffers::WIPOffset<TensorMap<'bldr>> {\n            let mut builder = TensorMapBuilder::new(_fbb);\n            builder.add_tensor_index(args.tensor_index);\n            if let Some(x) = args.name {\n                builder.add_name(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn name(&self) -> Option<&'a str> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<flatbuffers::ForwardsUOffset<&str>>(TensorMap::VT_NAME, None) }\n        }\n        #[inline]\n        pub fn tensor_index(&self) -> u32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<u32>(TensorMap::VT_TENSOR_INDEX, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for TensorMap<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<&str>>(\"name\", Self::VT_NAME, false)?\n                .visit_field::<u32>(\"tensor_index\", Self::VT_TENSOR_INDEX, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct TensorMapArgs<'a> {\n        pub name: Option<flatbuffers::WIPOffset<&'a str>>,\n        pub tensor_index: u32,\n    }\n    impl<'a> Default for TensorMapArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            TensorMapArgs { name: None, tensor_index: 0 }\n        }\n    }\n\n    pub struct TensorMapBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> TensorMapBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_name(&mut self, name: flatbuffers::WIPOffset<&'b str>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(TensorMap::VT_NAME, name);\n        }\n        #[inline]\n        pub fn add_tensor_index(&mut self, tensor_index: u32) {\n            self.fbb_.push_slot::<u32>(TensorMap::VT_TENSOR_INDEX, tensor_index, 0);\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> TensorMapBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            TensorMapBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<TensorMap<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for TensorMap<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"TensorMap\");\n            ds.field(\"name\", &self.name());\n            ds.field(\"tensor_index\", &self.tensor_index());\n            ds.finish()\n        }\n    }\n    pub enum SignatureDefOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct SignatureDef<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for SignatureDef<'a> {\n        type Inner = SignatureDef<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> SignatureDef<'a> {\n        pub const VT_INPUTS: flatbuffers::VOffsetT = 4;\n        pub const VT_OUTPUTS: flatbuffers::VOffsetT = 6;\n        pub const VT_SIGNATURE_KEY: flatbuffers::VOffsetT = 8;\n        pub const VT_SUBGRAPH_INDEX: flatbuffers::VOffsetT = 12;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            SignatureDef { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args SignatureDefArgs<'args>,\n        ) -> flatbuffers::WIPOffset<SignatureDef<'bldr>> {\n            let mut builder = SignatureDefBuilder::new(_fbb);\n            builder.add_subgraph_index(args.subgraph_index);\n            if let Some(x) = args.signature_key {\n                builder.add_signature_key(x);\n            }\n            if let Some(x) = args.outputs {\n                builder.add_outputs(x);\n            }\n            if let Some(x) = args.inputs {\n                builder.add_inputs(x);\n            }\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn inputs(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<TensorMap<'a>>>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<TensorMap>>,\n                >>(SignatureDef::VT_INPUTS, None)\n            }\n        }\n        #[inline]\n        pub fn outputs(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<TensorMap<'a>>>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<TensorMap>>,\n                >>(SignatureDef::VT_OUTPUTS, None)\n            }\n        }\n        #[inline]\n        pub fn signature_key(&self) -> Option<&'a str> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab\n                    .get::<flatbuffers::ForwardsUOffset<&str>>(SignatureDef::VT_SIGNATURE_KEY, None)\n            }\n        }\n        #[inline]\n        pub fn subgraph_index(&self) -> u32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<u32>(SignatureDef::VT_SUBGRAPH_INDEX, Some(0)).unwrap() }\n        }\n    }\n\n    impl flatbuffers::Verifiable for SignatureDef<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<TensorMap>>,\n                >>(\"inputs\", Self::VT_INPUTS, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<TensorMap>>,\n                >>(\"outputs\", Self::VT_OUTPUTS, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<&str>>(\n                    \"signature_key\",\n                    Self::VT_SIGNATURE_KEY,\n                    false,\n                )?\n                .visit_field::<u32>(\"subgraph_index\", Self::VT_SUBGRAPH_INDEX, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct SignatureDefArgs<'a> {\n        pub inputs: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<TensorMap<'a>>>,\n            >,\n        >,\n        pub outputs: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<TensorMap<'a>>>,\n            >,\n        >,\n        pub signature_key: Option<flatbuffers::WIPOffset<&'a str>>,\n        pub subgraph_index: u32,\n    }\n    impl<'a> Default for SignatureDefArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            SignatureDefArgs { inputs: None, outputs: None, signature_key: None, subgraph_index: 0 }\n        }\n    }\n\n    pub struct SignatureDefBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> SignatureDefBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_inputs(\n            &mut self,\n            inputs: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<TensorMap<'b>>>,\n            >,\n        ) {\n            self.fbb_\n                .push_slot_always::<flatbuffers::WIPOffset<_>>(SignatureDef::VT_INPUTS, inputs);\n        }\n        #[inline]\n        pub fn add_outputs(\n            &mut self,\n            outputs: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<TensorMap<'b>>>,\n            >,\n        ) {\n            self.fbb_\n                .push_slot_always::<flatbuffers::WIPOffset<_>>(SignatureDef::VT_OUTPUTS, outputs);\n        }\n        #[inline]\n        pub fn add_signature_key(&mut self, signature_key: flatbuffers::WIPOffset<&'b str>) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                SignatureDef::VT_SIGNATURE_KEY,\n                signature_key,\n            );\n        }\n        #[inline]\n        pub fn add_subgraph_index(&mut self, subgraph_index: u32) {\n            self.fbb_.push_slot::<u32>(SignatureDef::VT_SUBGRAPH_INDEX, subgraph_index, 0);\n        }\n        #[inline]\n        pub fn new(\n            _fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        ) -> SignatureDefBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            SignatureDefBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<SignatureDef<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for SignatureDef<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"SignatureDef\");\n            ds.field(\"inputs\", &self.inputs());\n            ds.field(\"outputs\", &self.outputs());\n            ds.field(\"signature_key\", &self.signature_key());\n            ds.field(\"subgraph_index\", &self.subgraph_index());\n            ds.finish()\n        }\n    }\n    pub enum ModelOffset {}\n    #[derive(Copy, Clone, PartialEq)]\n\n    pub struct Model<'a> {\n        pub _tab: flatbuffers::Table<'a>,\n    }\n\n    impl<'a> flatbuffers::Follow<'a> for Model<'a> {\n        type Inner = Model<'a>;\n        #[inline]\n        unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {\n            Self { _tab: flatbuffers::Table::new(buf, loc) }\n        }\n    }\n\n    impl<'a> Model<'a> {\n        pub const VT_VERSION: flatbuffers::VOffsetT = 4;\n        pub const VT_OPERATOR_CODES: flatbuffers::VOffsetT = 6;\n        pub const VT_SUBGRAPHS: flatbuffers::VOffsetT = 8;\n        pub const VT_DESCRIPTION: flatbuffers::VOffsetT = 10;\n        pub const VT_BUFFERS: flatbuffers::VOffsetT = 12;\n        pub const VT_METADATA_BUFFER: flatbuffers::VOffsetT = 14;\n        pub const VT_METADATA: flatbuffers::VOffsetT = 16;\n        pub const VT_SIGNATURE_DEFS: flatbuffers::VOffsetT = 18;\n\n        #[inline]\n        pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self {\n            Model { _tab: table }\n        }\n        #[allow(unused_mut)]\n        pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(\n            _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,\n            args: &'args ModelArgs<'args>,\n        ) -> flatbuffers::WIPOffset<Model<'bldr>> {\n            let mut builder = ModelBuilder::new(_fbb);\n            if let Some(x) = args.signature_defs {\n                builder.add_signature_defs(x);\n            }\n            if let Some(x) = args.metadata {\n                builder.add_metadata(x);\n            }\n            if let Some(x) = args.metadata_buffer {\n                builder.add_metadata_buffer(x);\n            }\n            if let Some(x) = args.buffers {\n                builder.add_buffers(x);\n            }\n            if let Some(x) = args.description {\n                builder.add_description(x);\n            }\n            if let Some(x) = args.subgraphs {\n                builder.add_subgraphs(x);\n            }\n            if let Some(x) = args.operator_codes {\n                builder.add_operator_codes(x);\n            }\n            builder.add_version(args.version);\n            builder.finish()\n        }\n\n        #[inline]\n        pub fn version(&self) -> u32 {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe { self._tab.get::<u32>(Model::VT_VERSION, Some(0)).unwrap() }\n        }\n        #[inline]\n        pub fn operator_codes(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<OperatorCode<'a>>>>\n        {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<OperatorCode>>,\n                >>(Model::VT_OPERATOR_CODES, None)\n            }\n        }\n        #[inline]\n        pub fn subgraphs(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<SubGraph<'a>>>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<SubGraph>>,\n                >>(Model::VT_SUBGRAPHS, None)\n            }\n        }\n        #[inline]\n        pub fn description(&self) -> Option<&'a str> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<&str>>(Model::VT_DESCRIPTION, None)\n            }\n        }\n        #[inline]\n        pub fn buffers(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Buffer<'a>>>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Buffer>>,\n                >>(Model::VT_BUFFERS, None)\n            }\n        }\n        #[inline]\n        pub fn metadata_buffer(&self) -> Option<flatbuffers::Vector<'a, i32>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(\n                    Model::VT_METADATA_BUFFER,\n                    None,\n                )\n            }\n        }\n        #[inline]\n        pub fn metadata(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Metadata<'a>>>> {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Metadata>>,\n                >>(Model::VT_METADATA, None)\n            }\n        }\n        #[inline]\n        pub fn signature_defs(\n            &self,\n        ) -> Option<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<SignatureDef<'a>>>>\n        {\n            // Safety:\n            // Created from valid Table for this object\n            // which contains a valid value in this slot\n            unsafe {\n                self._tab.get::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<SignatureDef>>,\n                >>(Model::VT_SIGNATURE_DEFS, None)\n            }\n        }\n    }\n\n    impl flatbuffers::Verifiable for Model<'_> {\n        #[inline]\n        fn run_verifier(\n            v: &mut flatbuffers::Verifier,\n            pos: usize,\n        ) -> Result<(), flatbuffers::InvalidFlatbuffer> {\n            use self::flatbuffers::Verifiable;\n            v.visit_table(pos)?\n                .visit_field::<u32>(\"version\", Self::VT_VERSION, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<OperatorCode>>,\n                >>(\"operator_codes\", Self::VT_OPERATOR_CODES, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<SubGraph>>,\n                >>(\"subgraphs\", Self::VT_SUBGRAPHS, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<&str>>(\n                    \"description\",\n                    Self::VT_DESCRIPTION,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<Buffer>>,\n                >>(\"buffers\", Self::VT_BUFFERS, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(\n                    \"metadata_buffer\",\n                    Self::VT_METADATA_BUFFER,\n                    false,\n                )?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<Metadata>>,\n                >>(\"metadata\", Self::VT_METADATA, false)?\n                .visit_field::<flatbuffers::ForwardsUOffset<\n                    flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<SignatureDef>>,\n                >>(\"signature_defs\", Self::VT_SIGNATURE_DEFS, false)?\n                .finish();\n            Ok(())\n        }\n    }\n    pub struct ModelArgs<'a> {\n        pub version: u32,\n        pub operator_codes: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<OperatorCode<'a>>>,\n            >,\n        >,\n        pub subgraphs: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<SubGraph<'a>>>,\n            >,\n        >,\n        pub description: Option<flatbuffers::WIPOffset<&'a str>>,\n        pub buffers: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Buffer<'a>>>,\n            >,\n        >,\n        pub metadata_buffer: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,\n        pub metadata: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<Metadata<'a>>>,\n            >,\n        >,\n        pub signature_defs: Option<\n            flatbuffers::WIPOffset<\n                flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<SignatureDef<'a>>>,\n            >,\n        >,\n    }\n    impl<'a> Default for ModelArgs<'a> {\n        #[inline]\n        fn default() -> Self {\n            ModelArgs {\n                version: 0,\n                operator_codes: None,\n                subgraphs: None,\n                description: None,\n                buffers: None,\n                metadata_buffer: None,\n                metadata: None,\n                signature_defs: None,\n            }\n        }\n    }\n\n    pub struct ModelBuilder<'a: 'b, 'b> {\n        fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,\n    }\n    impl<'a: 'b, 'b> ModelBuilder<'a, 'b> {\n        #[inline]\n        pub fn add_version(&mut self, version: u32) {\n            self.fbb_.push_slot::<u32>(Model::VT_VERSION, version, 0);\n        }\n        #[inline]\n        pub fn add_operator_codes(\n            &mut self,\n            operator_codes: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<OperatorCode<'b>>>,\n            >,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                Model::VT_OPERATOR_CODES,\n                operator_codes,\n            );\n        }\n        #[inline]\n        pub fn add_subgraphs(\n            &mut self,\n            subgraphs: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<SubGraph<'b>>>,\n            >,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Model::VT_SUBGRAPHS, subgraphs);\n        }\n        #[inline]\n        pub fn add_description(&mut self, description: flatbuffers::WIPOffset<&'b str>) {\n            self.fbb_\n                .push_slot_always::<flatbuffers::WIPOffset<_>>(Model::VT_DESCRIPTION, description);\n        }\n        #[inline]\n        pub fn add_buffers(\n            &mut self,\n            buffers: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<Buffer<'b>>>,\n            >,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Model::VT_BUFFERS, buffers);\n        }\n        #[inline]\n        pub fn add_metadata_buffer(\n            &mut self,\n            metadata_buffer: flatbuffers::WIPOffset<flatbuffers::Vector<'b, i32>>,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                Model::VT_METADATA_BUFFER,\n                metadata_buffer,\n            );\n        }\n        #[inline]\n        pub fn add_metadata(\n            &mut self,\n            metadata: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<Metadata<'b>>>,\n            >,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(Model::VT_METADATA, metadata);\n        }\n        #[inline]\n        pub fn add_signature_defs(\n            &mut self,\n            signature_defs: flatbuffers::WIPOffset<\n                flatbuffers::Vector<'b, flatbuffers::ForwardsUOffset<SignatureDef<'b>>>,\n            >,\n        ) {\n            self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(\n                Model::VT_SIGNATURE_DEFS,\n                signature_defs,\n            );\n        }\n        #[inline]\n        pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> ModelBuilder<'a, 'b> {\n            let start = _fbb.start_table();\n            ModelBuilder { fbb_: _fbb, start_: start }\n        }\n        #[inline]\n        pub fn finish(self) -> flatbuffers::WIPOffset<Model<'a>> {\n            let o = self.fbb_.end_table(self.start_);\n            flatbuffers::WIPOffset::new(o.value())\n        }\n    }\n\n    impl core::fmt::Debug for Model<'_> {\n        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n            let mut ds = f.debug_struct(\"Model\");\n            ds.field(\"version\", &self.version());\n            ds.field(\"operator_codes\", &self.operator_codes());\n            ds.field(\"subgraphs\", &self.subgraphs());\n            ds.field(\"description\", &self.description());\n            ds.field(\"buffers\", &self.buffers());\n            ds.field(\"metadata_buffer\", &self.metadata_buffer());\n            ds.field(\"metadata\", &self.metadata());\n            ds.field(\"signature_defs\", &self.signature_defs());\n            ds.finish()\n        }\n    }\n    #[inline]\n    /// Verifies that a buffer of bytes contains a `Model`\n    /// and returns it.\n    /// Note that verification is still experimental and may not\n    /// catch every error, or be maximally performant. For the\n    /// previous, unchecked, behavior use\n    /// `root_as_model_unchecked`.\n    pub fn root_as_model(buf: &[u8]) -> Result<Model, flatbuffers::InvalidFlatbuffer> {\n        flatbuffers::root::<Model>(buf)\n    }\n    #[inline]\n    /// Verifies that a buffer of bytes contains a size prefixed\n    /// `Model` and returns it.\n    /// Note that verification is still experimental and may not\n    /// catch every error, or be maximally performant. For the\n    /// previous, unchecked, behavior use\n    /// `size_prefixed_root_as_model_unchecked`.\n    pub fn size_prefixed_root_as_model(\n        buf: &[u8],\n    ) -> Result<Model, flatbuffers::InvalidFlatbuffer> {\n        flatbuffers::size_prefixed_root::<Model>(buf)\n    }\n    #[inline]\n    /// Verifies, with the given options, that a buffer of bytes\n    /// contains a `Model` and returns it.\n    /// Note that verification is still experimental and may not\n    /// catch every error, or be maximally performant. For the\n    /// previous, unchecked, behavior use\n    /// `root_as_model_unchecked`.\n    pub fn root_as_model_with_opts<'b, 'o>(\n        opts: &'o flatbuffers::VerifierOptions,\n        buf: &'b [u8],\n    ) -> Result<Model<'b>, flatbuffers::InvalidFlatbuffer> {\n        flatbuffers::root_with_opts::<Model<'b>>(opts, buf)\n    }\n    #[inline]\n    /// Verifies, with the given verifier options, that a buffer of\n    /// bytes contains a size prefixed `Model` and returns\n    /// it. Note that verification is still experimental and may not\n    /// catch every error, or be maximally performant. For the\n    /// previous, unchecked, behavior use\n    /// `root_as_model_unchecked`.\n    pub fn size_prefixed_root_as_model_with_opts<'b, 'o>(\n        opts: &'o flatbuffers::VerifierOptions,\n        buf: &'b [u8],\n    ) -> Result<Model<'b>, flatbuffers::InvalidFlatbuffer> {\n        flatbuffers::size_prefixed_root_with_opts::<Model<'b>>(opts, buf)\n    }\n    #[inline]\n    /// Assumes, without verification, that a buffer of bytes contains a Model and returns it.\n    /// # Safety\n    /// Callers must trust the given bytes do indeed contain a valid `Model`.\n    pub unsafe fn root_as_model_unchecked(buf: &[u8]) -> Model {\n        flatbuffers::root_unchecked::<Model>(buf)\n    }\n    #[inline]\n    /// Assumes, without verification, that a buffer of bytes contains a size prefixed Model and returns it.\n    /// # Safety\n    /// Callers must trust the given bytes do indeed contain a valid size prefixed `Model`.\n    pub unsafe fn size_prefixed_root_as_model_unchecked(buf: &[u8]) -> Model {\n        flatbuffers::size_prefixed_root_unchecked::<Model>(buf)\n    }\n    pub const MODEL_IDENTIFIER: &str = \"TFL3\";\n\n    #[inline]\n    pub fn model_buffer_has_identifier(buf: &[u8]) -> bool {\n        flatbuffers::buffer_has_identifier(buf, MODEL_IDENTIFIER, false)\n    }\n\n    #[inline]\n    pub fn model_size_prefixed_buffer_has_identifier(buf: &[u8]) -> bool {\n        flatbuffers::buffer_has_identifier(buf, MODEL_IDENTIFIER, true)\n    }\n\n    pub const MODEL_EXTENSION: &str = \"tflite\";\n\n    #[inline]\n    pub fn finish_model_buffer<'a, 'b>(\n        fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        root: flatbuffers::WIPOffset<Model<'a>>,\n    ) {\n        fbb.finish(root, Some(MODEL_IDENTIFIER));\n    }\n\n    #[inline]\n    pub fn finish_size_prefixed_model_buffer<'a, 'b>(\n        fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,\n        root: flatbuffers::WIPOffset<Model<'a>>,\n    ) {\n        fbb.finish_size_prefixed(root, Some(MODEL_IDENTIFIER));\n    }\n} // pub mod tflite\n"
  },
  {
    "path": "transformers/Cargo.toml",
    "content": "[package]\nname = \"tract-transformers\"\nversion = \"0.23.0-pre\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Mathieu Poumeyrol <kali@zoy.org>\", \"Louis Chouraki <louis.chouraki@sonos.com>\"]\ndescription = \"Tiny, no-nonsense, self contained, TensorFlow and ONNX inference\"\nrepository = \"https://github.com/snipsco/tract\"\nkeywords = [ \"TensorFlow\", \"NeuralNetworks\", \"Transformers\" ]\ncategories = [ \"science\" ]\nautobenches = false\nedition = \"2024\"\nrust-version.workspace = true\n\n[badges]\nmaintenance = { status = \"actively-developed\" }\n\n[dependencies]\nfloat-ord.workspace = true\ntract-nnef.workspace = true\n"
  },
  {
    "path": "transformers/src/lib.rs",
    "content": "pub mod ops;\nmod rewriter;\nuse std::collections::HashSet;\n\nuse rewriter::*;\nuse tract_nnef::internal::*;\n\nregister_simple_model_transform!(\"detect_apply_rope\", ApplyRopeTransform);\nregister_simple_model_transform!(\"detect_scaled_masked_softmax\", ScaledMaskedSoftmaxTransform);\nregister_simple_model_transform!(\"detect_kv_cache\", KeyValueCacheTransform);\nregister_simple_model_transform!(\n    \"detect_sdpa_kv_cache_broadcast\",\n    SdpaFuseKvCacheBroadcastTransform\n);\nregister_simple_model_transform!(\"unfold_kv_cache\", UnfoldKeyValueCacheTransform);\nregister_simple_model_transform!(\"transformers_detect_all\", TransformersTransform);\n\npub fn register(registry: &mut Registry) {\n    ops::apply_rope::register(registry);\n    ops::scaled_masked_softmax::register(registry);\n    ops::sdpa::register(registry);\n    ops::dyn_kv_cache::register(registry);\n}\n\npub trait WithTractTransformers {\n    fn enable_tract_transformers(&mut self);\n    fn with_tract_transformers(self) -> Self;\n}\n\nimpl WithTractTransformers for tract_nnef::framework::Nnef {\n    fn enable_tract_transformers(&mut self) {\n        self.enable_tract_core();\n        self.registries.push(tract_transformers_registry());\n    }\n\n    fn with_tract_transformers(mut self) -> Self {\n        self.enable_tract_transformers();\n        self\n    }\n}\n\npub fn tract_transformers_registry() -> Registry {\n    let mut reg = Registry::new(\"tract_transformers\")\n        .with_doc(\"Extension `tract_transformers` extends NNEF with operators\")\n        .with_doc(\"for transformer networks.\")\n        .with_doc(\"\")\n        .with_doc(\"Add `extension tract_transformers` to `graph.nnef`\");\n\n    register(&mut reg);\n    reg\n}\n\npub fn figure_out_causal_llm_b_s_p(\n    model: &TypedModel,\n) -> TractResult<(Option<Symbol>, Option<Symbol>, Option<Symbol>)> {\n    // expectations:\n    // - one input is for tokens, so integer dt (i64 ?) and typically of shape S or 1,S, or B,S\n    // - other inputs are kv cache, some kind of float. shape features both S and P, and B if B is present in tokens\n    let token_input = model\n        .inputs\n        .iter()\n        .position(|i| model.outlet_fact(*i).unwrap().datum_type.is_integer())\n        .context(\"No token input found\")?;\n    let tokens_symbols = model.input_fact(token_input)?.shape.volume().symbols();\n    let kv_symbols = if let Some(kv_input) =\n        model.inputs.iter().position(|i| model.outlet_fact(*i).unwrap().datum_type.is_float())\n    {\n        model.input_fact(kv_input)?.shape.volume().symbols()\n    } else {\n        // Look for KVCache Op\n        let dummy_session_state = TurnState::default();\n        let mut symbols = HashSet::new();\n        for node in &model.nodes {\n            if let Some((_, fact)) =\n                node.op.state(&dummy_session_state, 0)?.and_then(|state| state.init_tensor_fact())\n            {\n                symbols = fact.shape.volume().symbols();\n                break;\n            }\n        }\n        symbols\n    };\n\n    let b = tokens_symbols.intersection(&kv_symbols).cloned().collect::<HashSet<_>>();\n    let s = tokens_symbols.difference(&b).cloned().collect::<HashSet<_>>();\n    let p = kv_symbols.difference(&b).cloned().collect::<HashSet<_>>();\n    Ok((b.into_iter().next(), s.into_iter().next(), p.into_iter().next()))\n}\n\npub fn memory_arena_hints_for_causal_llm(model: &TypedModel) -> TractResult<SymbolValues> {\n    let (b, s, p) = figure_out_causal_llm_b_s_p(model)?;\n    let mut values = SymbolValues::default()\n        .with(&s.context(\"Could not determine sequence_len (S)\")?, 1024)\n        .with(&p.context(\"Could not determine past_sequence_len (P)\")?, 0);\n    if let Some(b) = b {\n        values = values.with(&b, 1);\n    }\n    Ok(values)\n}\n"
  },
  {
    "path": "transformers/src/ops/apply_rope.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef::tract_core::ops::array::{Slice, TypedConcat};\nuse tract_nnef::tract_core::ops::binary::BinMiniOp;\nuse tract_nnef::tract_core::ops::binary::TypedBinOp;\nuse tract_nnef::tract_core::ops::element_wise::ElementWiseOp;\nuse tract_nnef::tract_core::ops::math::{Add, Mul, Neg};\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_apply_rope);\n    registry.register_primitive(\n        \"tract_transformers_apply_rope\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Scalar.tensor().named(\"cos\"),\n            TypeName::Scalar.tensor().named(\"sin\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_apply_rope,\n    );\n}\n\nfn de_apply_rope(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let cos = invocation.named_arg_as(builder, \"cos\")?;\n    let sin = invocation.named_arg_as(builder, \"sin\")?;\n    builder.wire(ApplyRope, &[input, cos, sin])\n}\n\nfn ser_apply_rope(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    _op: &ApplyRope,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let cos: Arc<RValue> = ast.mapping[&node.inputs[1]].clone();\n    let sin: Arc<RValue> = ast.mapping[&node.inputs[2]].clone();\n    Ok(Some(invocation(\"tract_transformers_apply_rope\", &[input, cos, sin], &[])))\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct RotateHalf;\n\nimpl Op for RotateHalf {\n    fn name(&self) -> StaticName {\n        \"RotateHalf\".to_string().into()\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for RotateHalf {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        let shape: TVec<_> = input.shape().into();\n        let mut tensor = Tensor::zero_dt(input.datum_type(), &shape)?;\n\n        let axis = shape.len() - 1;\n        ensure!(\n            shape[axis] % 2 == 0,\n            \"RotateHalf possible only if the most inner dimension of the shape {:?} is divible by 2\",\n            shape\n        );\n        let half = shape[axis] / 2;\n        unsafe { tensor.assign_slice_unchecked(0..half, &input, half.., axis) };\n        Neg {}.eval_in_place(&mut tensor, None)?;\n        unsafe { tensor.assign_slice_unchecked(half.., &input, 0..half, axis) };\n        Ok(tvec![tensor.into()])\n    }\n}\n\nimpl TypedOp for RotateHalf {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let dt = inputs[0].datum_type;\n        let fact = dt.fact(inputs[0].shape.clone());\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n}\n\n/// Search pattern:\n/// Y = Concat(Neg(Slice(X, X.shape[-1]/2.., -1)), Slice(X, ..X.shape[-1]/2, -1))\npub fn rotate_half_rule(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &TypedConcat,\n) -> TractResult<Option<TypedModelPatch>> {\n    let out_fact = model.node_output_facts(node.id)?[0];\n    let dt = out_fact.datum_type;\n    rule_if!(dt.is_float() || dt.is_integer());\n    rule_if!(op.axis == out_fact.rank() - 1);\n\n    let in_concat = model.previous_nodes(node);\n    rule_if!(in_concat.len() == 2);\n\n    let neg_half = in_concat[0];\n    rule_if_some!(neg_half_op = neg_half.op_as::<ElementWiseOp>());\n    rule_if!(neg_half_op.0.is::<Neg>());\n\n    rule_if_some!(neg_half_slice = model.previous_node(neg_half));\n    rule_if_some!(neg_half_slice_op = neg_half_slice.op_as::<Slice>());\n\n    rule_if!(neg_half_slice_op.axis == op.axis);\n\n    let pos_half = in_concat[1];\n    rule_if_some!(pos_half_op = pos_half.op_as::<Slice>());\n\n    rule_if!(pos_half_op.axis == op.axis);\n    rule_if!(pos_half_op.end == neg_half_slice_op.start);\n    rule_if!(neg_half_slice_op.end == out_fact.shape[op.axis].clone());\n\n    // Ensure it is a half rotation\n    rule_if_some!(pos_half_slice_end = pos_half_op.end.as_i64());\n    rule_if_some!(concatenated_last_dim = out_fact.shape[op.axis].as_i64());\n    rule_if!(pos_half_slice_end * 2 == concatenated_last_dim);\n\n    let in_fact = model.node_input_facts(neg_half_slice.id)?[0];\n\n    let mut patch = TypedModelPatch::default();\n    let mut inputs = patch.taps(model, &neg_half_slice.inputs)?;\n\n    if pos_half_op.start != 0.into() || neg_half_slice_op.end != in_fact.shape[op.axis] {\n        inputs = patch.wire_node(\n            format!(\"{node_name}.rotate_half.slice\"),\n            Slice {\n                start: pos_half_op.start.clone(),\n                end: neg_half_slice_op.end.clone(),\n                axis: op.axis,\n            },\n            &inputs,\n        )?;\n    }\n\n    let out = patch.wire_node(format!(\"{node_name}.rotate_half\"), RotateHalf, &inputs)?;\n    patch.shunt_outside(model, node.id.into(), out[0])?;\n\n    Ok(Some(patch))\n}\n\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct ApplyRope;\n\nimpl ApplyRope {\n    pub fn is_supported_dt(dt: DatumType) -> bool {\n        matches!(dt, DatumType::F32 | DatumType::F16)\n    }\n}\n\nimpl Op for ApplyRope {\n    fn name(&self) -> StaticName {\n        \"ApplyRope\".to_string().into()\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for ApplyRope {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (input, cos, sin) = args_3!(inputs);\n        let rotated_input = args_1!(RotateHalf.eval(tvec![input.clone()])?);\n        let mul_with_cos = Mul.eval(input.clone(), cos, input.datum_type())?;\n        let mul_with_sin = Mul.eval(rotated_input, sin, input.datum_type())?;\n        let output = Add.eval(mul_with_cos.into(), mul_with_sin.into(), input.datum_type())?;\n        Ok(tvec![output.into()])\n    }\n}\n\nimpl TypedOp for ApplyRope {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let dt = inputs[0].datum_type;\n        let fact = dt.fact(inputs[0].shape.clone());\n        Ok(tvec!(fact))\n    }\n\n    as_op!();\n}\n\n/// Search pattern:\n/// Y = X * Cos + RotateHalf(X) * Sin\npub fn apply_rope_rule(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &TypedBinOp,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if!(op.0.is::<Add>());\n\n    let in_add = model.previous_nodes(node);\n    rule_if!(in_add.len() == 2);\n\n    let cos_mul = in_add[0];\n    rule_if_let!(Some(cos_mul_op) = cos_mul.op_as::<TypedBinOp>());\n    rule_if!(cos_mul_op.0.is::<Mul>());\n\n    let sin_mul = in_add[1];\n    rule_if_let!(Some(sin_mul_op) = sin_mul.op_as::<TypedBinOp>());\n    rule_if!(sin_mul_op.0.is::<Mul>());\n\n    rule_if_let!(\n        Some((rotate_half_in_idx, rotate_half)) = model.single_prev_node_as::<RotateHalf>(sin_mul)\n    );\n\n    // If cos and rotate half don't share the same input, we check if they don't\n    // input node that are the same.\n    let (apply_rope_in, cos) = if !cos_mul.inputs.contains(&rotate_half.inputs[0]) {\n        let Some(rotate_half_prev) = model.previous_node(rotate_half) else { return Ok(None) };\n        let Some((cos_common_input_idx, _)) = model\n            .previous_nodes(cos_mul)\n            .iter()\n            .enumerate()\n            .find(|(_, n)| n.same_as(rotate_half_prev))\n        else {\n            return Ok(None);\n        };\n        (rotate_half.inputs[0], cos_mul.inputs[1 - cos_common_input_idx])\n    } else {\n        let apply_rope_in = rotate_half.inputs[0];\n        let cos =\n            if cos_mul.inputs[0] == apply_rope_in { cos_mul.inputs[1] } else { cos_mul.inputs[0] };\n        (apply_rope_in, cos)\n    };\n\n    let sin = sin_mul.inputs[1 - rotate_half_in_idx];\n\n    rule_if!(ApplyRope::is_supported_dt(model.outlet_fact(apply_rope_in)?.datum_type));\n    rule_if!(ApplyRope::is_supported_dt(model.outlet_fact(cos)?.datum_type));\n    rule_if!(ApplyRope::is_supported_dt(model.outlet_fact(sin)?.datum_type));\n\n    let mut patch = TypedModelPatch::default();\n    let input = patch.tap_model(model, apply_rope_in)?;\n    let cos = patch.tap_model(model, cos)?;\n    let sin = patch.tap_model(model, sin)?;\n    let out = patch.wire_node(format!(\"{node_name}.apply_rope\"), ApplyRope, &[input, cos, sin])?;\n    patch.shunt_outside(model, node.id.into(), out[0])?;\n\n    Ok(Some(patch))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use tract_nnef::tract_core::ops::math::Neg;\n    use tract_num_traits::AsPrimitive;\n    use tract_num_traits::Zero;\n\n    fn run_test_case<F: Datum + Zero + Copy>(a_shape: &[usize]) -> TractResult<()>\n    where\n        usize: AsPrimitive<F>,\n    {\n        let a_len = a_shape.iter().product::<usize>();\n        let input = Tensor::from_shape(a_shape, &(0..a_len).map(|f| f.as_()).collect::<Vec<F>>())?;\n        let rotated = RotateHalf.eval(tvec![input.clone().into()])?;\n        let mut back = args_1!(RotateHalf.eval(rotated)?).into_tensor();\n        Neg {}.eval_in_place(&mut back, None)?;\n        back.close_enough(&input, Approximation::Close)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_rotate_half() -> TractResult<()> {\n        run_test_case::<f32>(&[2, 2])?;\n        run_test_case::<f32>(&[512, 512])?;\n        run_test_case::<f32>(&[10, 512, 1024])?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "transformers/src/ops/dyn_kv_cache.rs",
    "content": "use std::str::FromStr;\n\nuse tract_nnef::internal::*;\nuse tract_nnef::prelude::tract_itertools::Itertools;\nuse tract_nnef::ser::{datum_type, tdims};\nuse tract_nnef::tract_core::ops::OpStateFreeze;\nuse tract_nnef::tract_core::ops::array::TypedConcat;\nuse tract_nnef::tract_core::ops::source::TypedSource;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_dyn_kv_cache);\n    registry.register_primitive(\n        \"tract_transformers_dyn_kv_cache\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::String.named(\"name\"),\n            TypeName::Integer.named(\"axis\"),\n            TypeName::String.named(\"datum_type\"),\n            TypeName::Integer.array().named(\"past_sequence_shape\"),\n            TypeName::Integer.array().named(\"input_sequence_shape\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_dyn_kv_cache,\n    );\n}\n\nfn ser_dyn_kv_cache(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &DynKeyValueCache,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    Ok(Some(invocation(\n        \"tract_transformers_dyn_kv_cache\",\n        &[input],\n        &[\n            (\"name\", string(&op.name)),\n            (\"axis\", numeric(op.axis)),\n            (\"datum_type\", datum_type(op.past_sequence_fact.datum_type)),\n            (\"past_sequence_shape\", tdims(op.past_sequence_fact.shape.dims())),\n            (\"input_sequence_shape\", tdims(op.input_sequence_fact.shape.dims())),\n        ],\n    )))\n}\n\nfn de_dyn_kv_cache(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let name: String = invocation.named_arg_as(builder, \"name\")?;\n    let axis: usize = invocation.named_arg_as(builder, \"axis\")?;\n    let dt = DatumType::from_str(&invocation.named_arg_as::<String>(builder, \"datum_type\")?)?;\n    let past_sequence_shape: TVec<TDim> = builder\n        .allowing_new_symbols(|builder| invocation.named_arg_as(builder, \"past_sequence_shape\"))?;\n    let input_sequence_shape: TVec<TDim> = builder\n        .allowing_new_symbols(|builder| invocation.named_arg_as(builder, \"input_sequence_shape\"))?;\n    builder.wire(\n        DynKeyValueCache {\n            name,\n            axis,\n            past_sequence_fact: dt.fact(&*past_sequence_shape),\n            input_sequence_fact: dt.fact(&*input_sequence_shape),\n        },\n        &[input],\n    )\n}\n\n#[derive(Debug, Clone)]\npub struct DynKeyValueCacheState {\n    name: String,\n    axis: usize,\n    past_sequence_fact: TypedFact,\n    kv_cache: Option<TValue>,\n}\n\nimpl DynKeyValueCacheState {\n    pub fn resolve_symbols(\n        state: &mut TurnState,\n        fact: TypedFact,\n        concrete_shape: Option<&[usize]>,\n    ) -> TractResult<()> {\n        let unresolved = fact\n            .shape\n            .iter()\n            .enumerate()\n            .filter_map(|(ax, symb)| match symb {\n                TDim::Sym(s) if state.resolved_symbols.get(s).is_none() => Some((ax, s)),\n                _ => None,\n            })\n            .collect_vec();\n\n        if unresolved.is_empty() {\n            return Ok(());\n        }\n\n        ensure!(unresolved.len() == 1);\n        let (ax, sym) = unresolved[0];\n        if let Some(shape) = concrete_shape {\n            ensure!(ax < shape.len());\n            state.resolved_symbols.set(sym, shape[ax] as i64);\n        } else {\n            state.resolved_symbols.set(sym, 0);\n        }\n\n        if state.scenario.is_none() {\n            state.scenario = sym.scope().unwrap().guess_scenario(&state.resolved_symbols)?;\n        }\n        Ok(())\n    }\n\n    pub fn truncate(&mut self, len: usize) -> TractResult<()> {\n        if let Some(t) = self.kv_cache.as_mut() {\n            *t = t.slice(self.axis, 0, len)?.into_tvalue();\n        } else {\n            bail!(\"Can not truncate a zero-len kv-cache value\");\n        }\n        Ok(())\n    }\n}\n\nimpl OpState for DynKeyValueCacheState {\n    fn load_from(\n        &mut self,\n        state: &mut TurnState,\n        states: &mut dyn Iterator<Item = tract_nnef::prelude::TValue>,\n    ) -> TractResult<()> {\n        // KV Cache fact is always at index 0\n        let kv_cache_init = states.next().context(\"Not enough state initializers\")?;\n        Self::resolve_symbols(state, self.past_sequence_fact.clone(), Some(kv_cache_init.shape()))?;\n        self.kv_cache = Some(kv_cache_init.clone());\n\n        Ok(())\n    }\n\n    fn save_to(&self, states: &mut Vec<TValue>) -> TractResult<()> {\n        if let Some(kv_cache) = &self.kv_cache {\n            states.push(kv_cache.clone());\n            Ok(())\n        } else {\n            bail!(\"KV cache {} was never initialized\", self.name)\n        }\n    }\n\n    fn init_tensor_fact(&self) -> Option<(String, TypedFact)> {\n        Some((self.name.clone(), self.past_sequence_fact.clone()))\n    }\n\n    fn resolve_symbols(&mut self, state: &mut TurnState) -> TractResult<()> {\n        let shape = self.kv_cache.as_ref().map(|kv_cache| kv_cache.shape());\n        Self::resolve_symbols(state, self.past_sequence_fact.clone(), shape)\n    }\n\n    fn eval(\n        &mut self,\n        _state: &mut TurnState,\n        _op: &dyn Op,\n        inputs: TVec<TValue>,\n    ) -> TractResult<TVec<TValue>> {\n        let input = args_1!(inputs);\n        // build output\n        let output = if let Some(curr) = self.kv_cache.take() {\n            TypedConcat { axis: self.axis }.eval(tvec![curr, input])?.remove(0)\n        } else {\n            input\n        };\n        self.kv_cache = Some(output.clone());\n\n        Ok(tvec!(output))\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct DynKeyValueCache {\n    pub name: String,\n    pub axis: usize,\n    pub past_sequence_fact: TypedFact,\n    pub input_sequence_fact: TypedFact,\n}\n\nimpl Op for DynKeyValueCache {\n    fn name(&self) -> StaticName {\n        \"DynamicKeyValueCache\".to_string().into()\n    }\n\n    op_as_typed_op!();\n}\n\nimpl EvalOp for DynKeyValueCache {\n    fn is_stateless(&self) -> bool {\n        false\n    }\n\n    fn state(\n        &self,\n        _session: &TurnState,\n        _node_id: usize,\n    ) -> TractResult<Option<Box<dyn OpState>>> {\n        Ok(Some(Box::new(DynKeyValueCacheState {\n            name: self.name.clone(),\n            axis: self.axis,\n            past_sequence_fact: self.past_sequence_fact.clone(),\n            kv_cache: None,\n        })))\n    }\n}\n\nimpl TypedOp for DynKeyValueCache {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(inputs.len() == 1);\n        let input = inputs[0];\n        let mut fact = input.without_value();\n\n        fact.shape.set(\n            self.axis,\n            self.past_sequence_fact.shape.dims()[self.axis].clone()\n                + self.input_sequence_fact.shape.dims()[self.axis].clone(),\n        );\n        Ok(tvec!(fact))\n    }\n\n    fn cost(&self, _inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let token_volume = self\n            .past_sequence_fact\n            .shape\n            .iter()\n            .enumerate()\n            .filter(|(axis, _d)| *axis != self.axis)\n            .map(|(_axis, d)| d)\n            .product::<TDim>();\n        Ok(tvec!((Cost::Custom(false, \"KVCacheValuesPerToken\".to_string()), token_volume)))\n    }\n\n    as_op!();\n}\n\n#[derive(Debug, Clone)]\npub struct FrozenDynKeyValueCacheState {\n    name: String,\n    axis: usize,\n    past_sequence_fact: TypedFact,\n    kv_cache: Option<Tensor>,\n}\n\nimpl OpStateFreeze for DynKeyValueCacheState {\n    fn freeze(&self) -> Box<dyn FrozenOpState> {\n        Box::new(FrozenDynKeyValueCacheState {\n            name: self.name.clone(),\n            axis: self.axis,\n            past_sequence_fact: self.past_sequence_fact.clone(),\n            kv_cache: self.kv_cache.clone().map(|t| t.into_tensor()),\n        })\n    }\n}\n\nimpl FrozenOpState for FrozenDynKeyValueCacheState {\n    fn unfreeze(&self) -> Box<dyn OpState> {\n        Box::new(DynKeyValueCacheState {\n            axis: self.axis,\n            name: self.name.clone(),\n            past_sequence_fact: self.past_sequence_fact.clone(),\n            kv_cache: self.kv_cache.clone().map(|t| t.into_tvalue()),\n        })\n    }\n}\n\n/// Reverse of `replace_kv_cache`: replaces a DynKeyValueCache node with Source + Concat,\n/// restoring KV cache state as explicit model inputs and outputs.\npub fn unfold_kv_cache(target: &mut TypedModel, kv_node_id: usize) -> TractResult<()> {\n    let node = target.node(kv_node_id);\n    let op = node.op_as::<DynKeyValueCache>().context(\"Not a DynKeyValueCache node\")?;\n    let name = op.name.clone();\n    let axis = op.axis;\n    let past_fact = op.past_sequence_fact.clone();\n    let input_fact = op.input_sequence_fact.clone();\n    let existing_input = node.inputs[0];\n\n    // Add a new Source node for the past KV cache\n    let source_outlet = target.add_source(&name, past_fact)?;\n\n    // Compute output fact for the Concat\n    let mut output_fact = input_fact.clone();\n    output_fact.shape.set(\n        axis,\n        target.outlet_fact(source_outlet)?.shape.dims()[axis].clone()\n            + input_fact.shape.dims()[axis].clone(),\n    );\n\n    // Replace DynKeyValueCache op with TypedConcat\n    let kv_node = target.node_mut(kv_node_id);\n    kv_node.name = format!(\"{name}_concat\");\n    kv_node.op = Box::new(TypedConcat { axis });\n    kv_node.outputs[0].fact = output_fact;\n\n    // Rewire: Concat takes [source, existing_input] as inputs\n    // Currently the node has [existing_input] at slot 0\n    // We need [source_outlet, existing_input] at slots [0, 1]\n    kv_node.inputs = vec![source_outlet, existing_input];\n\n    // Update successor info on the source node\n    target.nodes[source_outlet.node].outputs[source_outlet.slot]\n        .successors\n        .push(InletId::new(kv_node_id, 0));\n\n    // Update the existing input's successor slot from 0 to 1\n    target.nodes[existing_input.node].outputs[existing_input.slot].successors.iter_mut().for_each(\n        |succ| {\n            if succ.node == kv_node_id && succ.slot == 0 {\n                succ.slot = 1;\n            }\n        },\n    );\n\n    // Add the Concat output to model outputs and label it so runtimes preserve the name\n    let concat_outlet = OutletId::new(kv_node_id, 0);\n    target.outputs.push(concat_outlet);\n    target.set_outlet_label(concat_outlet, format!(\"{name}_concat\"))?;\n\n    Ok(())\n}\n\n/// Search pattern => Input -> Concat -> Output\n/// Return type is for using rule-ensure macro\npub fn replace_kv_cache(target: &mut TypedModel, source_node_id: usize) -> TractResult<Option<()>> {\n    assert!(target.node(source_node_id).op_is::<TypedSource>());\n    let (concat_node_id, non_source_input_id, axis, input_facts) = {\n        rule_if_some!(concat_node = target.next_node(target.node(source_node_id)));\n\n        // Check KV Cache Pattern\n        rule_if!(\n            concat_node.op_is::<TypedConcat>()\n                && concat_node.inputs.len() == 2\n                && concat_node.outputs.len() == 1\n                && target.outputs.contains(&concat_node.id.into())\n        );\n\n        let concat_in_facts = target.node_input_facts(concat_node.id)?;\n\n        // Check on shapes\n        let concat_in_shapes = [concat_in_facts[0].shape.dims(), concat_in_facts[1].shape.dims()];\n        let rank = concat_in_shapes[0].len();\n        let axes = (0..rank)\n            .filter(|ax| concat_in_shapes[0][*ax] != concat_in_shapes[1][*ax])\n            .collect_vec();\n        ensure!(axes.len() == 1);\n\n        let axis = axes[0];\n        rule_if!(\n            matches!(concat_in_shapes[0][axis], TDim::Sym(_))\n                && matches!(concat_in_shapes[1][axis], TDim::Sym(_))\n        );\n        let mut facts = [concat_in_facts[0].clone(), concat_in_facts[1].clone()];\n        if concat_node.inputs[0].node == source_node_id {\n            (concat_node.id, concat_node.inputs[1].node, axis, facts)\n        } else if concat_node.inputs[1].node == source_node_id {\n            facts.swap(0, 1);\n            (concat_node.id, concat_node.inputs[0].node, axis, facts)\n        } else {\n            return Ok(None);\n        }\n    };\n\n    {\n        // Replace Concat by KVCache\n        let name = target.node_names().collect_vec()[source_node_id].to_string();\n        let concat_node = target.node_mut(concat_node_id);\n        concat_node.op = Box::new(DynKeyValueCache {\n            name: name.clone(),\n            axis,\n            past_sequence_fact: input_facts[0].clone(),\n            input_sequence_fact: input_facts[1].clone(),\n        });\n        concat_node.name = name;\n        concat_node.inputs.retain(|input| input != &source_node_id.into());\n    }\n\n    {\n        // Replace Source by Dummy Op for it to be cleaned later\n        let dummy_op = target.create_dummy();\n        let source_node = target.node_mut(source_node_id);\n        source_node.outputs[0].successors.clear();\n        source_node.op = dummy_op;\n    }\n    {\n        // Non-source input is usually the second input of Concat. Rewire it to the only input of the new KVCache Op\n        let non_source_input = target.node_mut(non_source_input_id);\n        non_source_input.outputs.iter_mut().for_each(|output| {\n            output.successors.iter_mut().for_each(|succ| {\n                if succ.node == concat_node_id {\n                    succ.slot = 0\n                }\n            })\n        });\n    }\n\n    // Clean model I/Os\n    target.outputs.retain(|output| output.node != concat_node_id);\n    target.inputs.retain(|input| input.node != source_node_id);\n    target.outlet_labels.remove(&concat_node_id.into());\n    Ok(None)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use tract_num_traits::AsPrimitive;\n    use tract_num_traits::Zero;\n\n    fn run_test_case<F: Datum + Zero + Copy>(\n        input_shapes: &[Vec<usize>],\n        axis: usize,\n    ) -> TractResult<()>\n    where\n        usize: AsPrimitive<F>,\n    {\n        let first_shape = &input_shapes[0];\n        ensure!(input_shapes.iter().all(|shape| (shape.len() == first_shape.len())\n            && (shape[..axis] == first_shape[..axis])\n            && (if axis != (shape.len() - 1) {\n                shape[(axis + 1)..] == first_shape[(axis + 1)..]\n            } else {\n                true\n            })));\n\n        let op_name = \"test\".to_string();\n        let dummy_model = TypedModel::default();\n\n        let make_shape =\n            |sym: &str| {\n                input_shapes[0]\n                    .iter()\n                    .enumerate()\n                    .map(|(i, &dim)| {\n                        if i == axis {\n                            TDim::Sym(dummy_model.sym(sym))\n                        } else {\n                            TDim::Val(dim as _)\n                        }\n                    })\n                    .collect::<TVec<TDim>>()\n            };\n\n        let past_shape = make_shape(\"P\");\n        let input_shape = make_shape(\"S\");\n\n        let op = DynKeyValueCache {\n            name: op_name.clone(),\n            past_sequence_fact: TypedFact::dt_shape(F::datum_type(), past_shape),\n            input_sequence_fact: TypedFact::dt_shape(F::datum_type(), input_shape),\n            axis,\n        };\n\n        let mut session_state = TurnState::default();\n        let mut state = op.state(&mut session_state, 0)?.unwrap();\n\n        let mut inputs = tvec![];\n\n        // Init state with first shape\n        let shape = &input_shapes[0];\n        let len = shape.iter().product::<usize>();\n        let input = Tensor::from_shape(shape, &(0..len).map(|f| f.as_()).collect::<Vec<F>>())?;\n        inputs.push(input.clone().into_tvalue());\n\n        let mut state_initializers = vec![input.into()].into_iter();\n\n        state.load_from(&mut session_state, &mut state_initializers)?;\n\n        for shape in input_shapes {\n            let len = shape.iter().product::<usize>();\n            let input = Tensor::from_shape(&shape, &(0..len).map(|f| f.as_()).collect::<Vec<F>>())?;\n            inputs.push(input.clone().into_tvalue());\n            state.eval(&mut session_state, &op, tvec!(input.clone().into()))?[0]\n                .clone()\n                .into_tensor();\n        }\n\n        let mut curr_states = vec![];\n        state.save_to(&mut curr_states)?;\n        let output = curr_states.remove(0);\n\n        let reference = &TypedConcat { axis }.eval(inputs)?[0];\n        output.close_enough(&reference.clone().into_tensor(), Approximation::Close)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_dyn_kv_cache() -> TractResult<()> {\n        run_test_case::<f32>(&[vec![2, 2]], 0)?;\n        run_test_case::<f32>(&[vec![2, 2], vec![4, 2]], 0)?;\n        run_test_case::<f32>(&[vec![2, 2], vec![2, 1], vec![2, 3]], 1)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_unfold_kv_cache() -> TractResult<()> {\n        // Build a model with DynKeyValueCache\n        let mut model = TypedModel::default();\n        let s = model.sym(\"S\");\n        let p = model.sym(\"P\");\n\n        let input_shape: TVec<TDim> = tvec![1.to_dim(), s.into(), 64.to_dim()];\n        let past_shape: TVec<TDim> = tvec![1.to_dim(), p.into(), 64.to_dim()];\n\n        let input = model.add_source(\"input\", f32::fact(&input_shape))?;\n        let op = DynKeyValueCache {\n            name: \"kv_cache_0\".to_string(),\n            axis: 1,\n            past_sequence_fact: f32::fact(&past_shape),\n            input_sequence_fact: f32::fact(&input_shape),\n        };\n        let out = model.wire_node(\"kv_cache\", op, &[input])?;\n        model.select_output_outlets(&out)?;\n\n        // Model should have 1 input (input), 1 output (kv_cache)\n        assert_eq!(model.inputs.len(), 1);\n        assert_eq!(model.outputs.len(), 1);\n        assert!(model.node(1).op_is::<DynKeyValueCache>());\n\n        // Unfold\n        unfold_kv_cache(&mut model, 1)?;\n\n        // After unfold: 2 inputs (input + kv_cache_0 source), 2 outputs (original + concat)\n        assert_eq!(model.inputs.len(), 2);\n        assert_eq!(model.outputs.len(), 2);\n\n        // The KV cache node should now be a Concat\n        assert!(model.node(1).op_is::<TypedConcat>());\n        let concat = model.node(1).op_as::<TypedConcat>().unwrap();\n        assert_eq!(concat.axis, 1);\n\n        // The new source node should exist\n        let source_node_id = model.inputs[1].node;\n        assert!(model.node(source_node_id).op_is::<TypedSource>());\n        assert_eq!(model.node(source_node_id).name, \"kv_cache_0\");\n\n        // Concat should have 2 inputs: [source, input]\n        assert_eq!(model.node(1).inputs.len(), 2);\n        assert_eq!(model.node(1).inputs[0].node, source_node_id);\n        assert_eq!(model.node(1).inputs[1].node, 0); // original input\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_fold_unfold_round_trip() -> TractResult<()> {\n        use crate::rewriter::KeyValueCacheTransform;\n        use tract_nnef::tract_core::transform::ModelTransform;\n\n        // Build a model with Source + Concat (the pre-fold pattern)\n        let mut model = TypedModel::default();\n        let s = model.sym(\"S\");\n        let p = model.sym(\"P\");\n\n        let input_shape: TVec<TDim> = tvec![1.to_dim(), s.into(), 64.to_dim()];\n        let past_shape: TVec<TDim> = tvec![1.to_dim(), p.into(), 64.to_dim()];\n\n        let past = model.add_source(\"kv_past\", f32::fact(&past_shape))?;\n        let input = model.add_source(\"input\", f32::fact(&input_shape))?;\n        let concat = model.wire_node(\"concat\", TypedConcat { axis: 1 }, &[past, input])?;\n        model.select_output_outlets(&concat)?;\n\n        let orig_input_count = model.inputs.len();\n        let orig_output_count = model.outputs.len();\n\n        // Fold: Source + Concat -> DynKeyValueCache\n        KeyValueCacheTransform.transform(&mut model)?;\n        assert_eq!(model.inputs.len(), orig_input_count - 1); // past source removed\n        assert_eq!(model.outputs.len(), orig_output_count - 1); // concat output removed\n\n        // Find the DynKeyValueCache node\n        let kv_node_id = model.nodes().iter().find(|n| n.op_is::<DynKeyValueCache>()).unwrap().id;\n\n        // Unfold: DynKeyValueCache -> Source + Concat\n        unfold_kv_cache(&mut model, kv_node_id)?;\n\n        // Should be back to original structure\n        assert_eq!(model.inputs.len(), orig_input_count);\n        assert_eq!(model.outputs.len(), orig_output_count);\n\n        // Verify it's a Concat again\n        let concat_node = model.nodes().iter().find(|n| n.op_is::<TypedConcat>()).unwrap();\n        assert_eq!(concat_node.op_as::<TypedConcat>().unwrap().axis, 1);\n        assert_eq!(concat_node.inputs.len(), 2);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_dyn_kv_cache_nnef_round_trip() -> TractResult<()> {\n        use crate::WithTractTransformers;\n\n        let mut model = TypedModel::default();\n        let s = model.sym(\"S\");\n        let p = model.sym(\"P\");\n\n        let input_shape: TVec<TDim> = tvec![1.to_dim(), s.into(), 64.to_dim()];\n        let past_shape: TVec<TDim> = tvec![1.to_dim(), p.into(), 64.to_dim()];\n\n        let input = model.add_source(\"input\", f32::fact(&input_shape))?;\n        let op = DynKeyValueCache {\n            name: \"kv_cache_0\".to_string(),\n            axis: 1,\n            past_sequence_fact: f32::fact(&past_shape),\n            input_sequence_fact: f32::fact(&input_shape),\n        };\n        let out = model.wire_node(\"kv_cache\", op, &[input])?;\n        model.select_output_outlets(&out)?;\n\n        let nnef = tract_nnef::nnef().with_tract_transformers();\n        let mut buffer = vec![];\n        nnef.write_to_tar(&model, &mut buffer)?;\n        let reloaded = nnef.model_for_read(&mut &*buffer)?;\n\n        assert_eq!(reloaded.nodes().len(), model.nodes().len());\n        let reloaded_kv = reloaded.node(1);\n        let reloaded_op = reloaded_kv.op_as::<DynKeyValueCache>().unwrap();\n        assert_eq!(reloaded_op.name, \"kv_cache_0\");\n        assert_eq!(reloaded_op.axis, 1);\n        assert_eq!(reloaded_op.past_sequence_fact.datum_type, DatumType::F32);\n        assert_eq!(reloaded_op.past_sequence_fact.shape.rank(), 3);\n        assert_eq!(reloaded_op.input_sequence_fact.datum_type, DatumType::F32);\n        assert_eq!(reloaded_op.input_sequence_fact.shape.rank(), 3);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "transformers/src/ops/flash_sdpa.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef::prelude::tract_itertools::Itertools;\nuse tract_nnef::tract_ndarray::{Array2, Array4, ArrayView2, ArrayView4, ArrayViewMut2, Ix4, s};\n\n/// Tract operator wrapper.\n#[derive(Clone, Debug, PartialEq)]\npub struct FlashSdpaOp {\n    pub causal: bool,\n    pub scale: Option<f32>,\n}\nimpl Eq for FlashSdpaOp {}\n\nimpl Op for FlashSdpaOp {\n    fn name(&self) -> StaticName {\n        \"FlashSDPA\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"causal={}, scale={:?}\", self.causal, self.scale)])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl TypedOp for FlashSdpaOp {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let (q, k, v, opt_m) = match inputs.len() {\n            3 => (inputs[0], inputs[1], inputs[2], None),\n            4 => (inputs[0], inputs[1], inputs[2], Some(inputs[3])),\n            _ => bail!(\"FlashSDPA expects 3 or 4 inputs (Q,K,V, optional mask), got {inputs:?}\"),\n        };\n\n        // dtype checks\n        ensure!(q.datum_type.is_float(), \"Q must be floating point\");\n        ensure!(k.datum_type.is_float(), \"K must be floating point\");\n        ensure!(v.datum_type.is_float(), \"V must be floating point\");\n        if let Some(m) = opt_m {\n            ensure!(m.datum_type.is_float(), \"Mask must be floating point\");\n        }\n\n        // rank checks\n        ensure!(\n            q.rank() == k.rank()\n                && q.rank() == v.rank()\n                && opt_m.as_ref().is_none_or(|m| m.rank() == q.rank()),\n            \"Q, K, V, mask must have the same rank, found {}, {}, {}, {:?} resp.\",\n            q.rank(),\n            k.rank(),\n            v.rank(),\n            opt_m.as_ref().map(|m| m.rank().to_string())\n        );\n        ensure!(\n            q.rank() == 3 || q.rank() == 4,\n            \"Inputs must be of rank 3 or 4, found {}\",\n            q.rank()\n        );\n\n        let one = 1.to_dim();\n\n        let ((bq, hq, _tq, dq), (bk, hkv, tk, dk), (bv, hkv2, tk2, dv)) = if q.rank() == 4 {\n            (\n                (&q.shape[0], &q.shape[1], &q.shape[2], &q.shape[3]),\n                (&k.shape[0], &k.shape[1], &k.shape[2], &k.shape[3]),\n                (&v.shape[0], &v.shape[1], &v.shape[2], &v.shape[3]),\n            )\n        } else {\n            (\n                (&q.shape[0], &one, &q.shape[1], &q.shape[2]),\n                (&k.shape[0], &one, &k.shape[1], &k.shape[2]),\n                (&v.shape[0], &one, &v.shape[1], &v.shape[2]),\n            )\n        };\n\n        ensure!(bq == bk && bq == bv, \"Batch dims must match for Q/K/V\");\n        ensure!(hkv == hkv2, \"K/V head counts must match\");\n        ensure!(tk == tk2, \"K/V lengths must match\");\n        ensure!(dq == dk && dq == dv, \"Head dims (D) must match across Q/K/V\");\n\n        // If heads are fully known, check GQA divisibility (best-effort).\n        if let (Ok(hq), Ok(hkv)) = (hq.to_usize(), hkv.to_usize()) {\n            ensure!(hq % hkv == 0, \"num_q_heads must be a multiple of num_kv_heads for GQA\");\n        }\n\n        // Output has same shape and dtype as Q.\n        Ok(tvec!(q.without_value()))\n    }\n\n    as_op!();\n}\n\nimpl EvalOp for FlashSdpaOp {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        ensure!(inputs.len() == 3 || inputs.len() == 4);\n        let [q, k, v] = &inputs[0..3] else {\n            bail!(\"Expects 3 or 4 inptus (Q, K, V, optional mask)\")\n        };\n\n        let input_dt = q.datum_type();\n\n        let (q, k, v) = (q.cast_to::<f32>()?, k.cast_to::<f32>()?, v.cast_to::<f32>()?);\n        let mut q = q.to_plain_array_view::<f32>()?;\n        let mut k = k.to_plain_array_view::<f32>()?;\n        let mut v = v.to_plain_array_view::<f32>()?;\n\n        let is_3d_case = q.ndim() == 3;\n\n        if is_3d_case {\n            q.insert_axis_inplace(tract_ndarray::Axis(1));\n            k.insert_axis_inplace(tract_ndarray::Axis(1));\n            v.insert_axis_inplace(tract_ndarray::Axis(1));\n        }\n\n        let (q, k, v) = (\n            q.into_dimensionality::<Ix4>()?,\n            k.into_dimensionality::<Ix4>()?,\n            v.into_dimensionality::<Ix4>()?,\n        );\n\n        let (batch_size, num_q_heads, query_len, head_dim) =\n            (q.shape()[0], q.shape()[1], q.shape()[2], q.shape()[3]);\n        let (bk, num_kv_heads, kv_len, hd_k) =\n            (k.shape()[0], k.shape()[1], k.shape()[2], k.shape()[3]);\n\n        ensure!(batch_size == bk, \"Batch mismatch between Q and K\");\n        ensure!(head_dim == hd_k, \"Head dim mismatch between Q and K\");\n        ensure!(num_kv_heads == v.shape()[1], \"K/V head mismatch\");\n        ensure!(kv_len == v.shape()[2] && head_dim == v.shape()[3], \"K/V shape mismatch\");\n\n        // Views as fixed 4-D\n        let q4 = q.to_shape((batch_size, num_q_heads, query_len, head_dim))?;\n        let k4 = k.to_shape((batch_size, num_kv_heads, kv_len, head_dim))?;\n        let v4 = v.to_shape((batch_size, num_kv_heads, kv_len, head_dim))?;\n\n        let m = if let Some(m) = inputs.get(3) {\n            Some(\n                m.cast_to::<f32>()?.into_owned().into_plain_array::<f32>()?.into_shape_with_order(\n                    (m.shape()[0], if m.rank() == 3 { 1 } else { m.shape()[1] }, query_len, kv_len),\n                )?,\n            )\n        } else {\n            None\n        };\n\n        let mut out = self\n            .flash_attention_gqa(q4.view(), k4.view(), v4.view(), m.as_ref().map(|m| m.view()))\n            .into_dyn();\n        if is_3d_case {\n            out.index_axis_inplace(tract_ndarray::Axis(1), 0);\n        }\n\n        Ok(tvec!(out.into_tensor().cast_to_dt(input_dt)?.into_owned().into_tvalue()))\n    }\n}\n\nimpl FlashSdpaOp {\n    /// Flash Attention forward with Grouped-Query Attention (GQA).\n    ///\n    /// Shapes:\n    ///   Q: (batch_size, num_q_heads, query_len, head_dim)\n    ///   K: (batch_size, num_kv_heads, kv_len,   head_dim)\n    ///   V: (batch_size, num_kv_heads, kv_len,   head_dim)\n    /// Returns O: (batch_size, num_q_heads, query_len, head_dim)\n    ///\n    /// GQA mapping: each query head qh maps to a key/value head index kh = qh / group_size\n    /// where group_size = num_q_heads / num_kv_heads (must divide exactly).\n    pub fn flash_attention_gqa(\n        &self,\n        q: ArrayView4<f32>,\n        k: ArrayView4<f32>,\n        v: ArrayView4<f32>,\n        mask: Option<ArrayView4<f32>>,\n    ) -> Array4<f32> {\n        // Explicit dimensions\n        let (batch_size, num_q_heads, q_len, head_dim) = q.dim();\n        let (_, num_kv_heads, kv_len, _) = k.dim();\n        let scale = self.scale.unwrap_or((head_dim as f32).recip().sqrt());\n        let group_size = num_q_heads / num_kv_heads;\n\n        let block_kv_len = 32;\n        let block_q_len = 32;\n\n        let mut out = Array4::<f32>::zeros((batch_size, num_q_heads, q_len, head_dim));\n\n        for b in 0..batch_size {\n            let mb = b.min(mask.as_ref().map(|m| m.shape()[0] - 1).unwrap_or(0));\n            for kvh in 0..num_kv_heads {\n                for g in 0..group_size {\n                    let qh = kvh * group_size + g;\n                    let mh = qh.min(mask.as_ref().map(|m| m.shape()[1] - 1).unwrap_or(0));\n                    let mut l = vec![0f32; q_len];\n                    let mut m = vec![f32::NEG_INFINITY; q_len];\n                    for kbix in 0..kv_len.div_ceil(block_kv_len) {\n                        for qbix in 0..q_len.div_ceil(block_q_len) {\n                            let kv_range =\n                                (kbix * block_kv_len)..((kbix + 1) * block_kv_len).min(kv_len);\n                            let q_range =\n                                (qbix * block_q_len)..((qbix + 1) * block_q_len).min(q_len);\n                            if let Some(mask) = &mask {\n                                if mask\n                                    .slice(s!(mb, mh, q_range.clone(), kv_range.clone()))\n                                    .iter()\n                                    .all(|x| *x < -65503.0)\n                                {\n                                    continue;\n                                }\n                            }\n                            let m = &mut m[q_range.clone()];\n                            let l = &mut l[q_range.clone()];\n                            let qblock: ArrayView2<f32> = q.slice(s!(b, qh, q_range.clone(), ..));\n                            let kblock: ArrayView2<f32> = k.slice(s!(b, kvh, kv_range.clone(), ..));\n                            let vblock: ArrayView2<f32> = v.slice(s!(b, kvh, kv_range.clone(), ..));\n                            let mut oblock: ArrayViewMut2<f32> =\n                                out.slice_mut(s!(b, qh, q_range.clone(), ..));\n                            // Sij <- QiKTj\n                            let mut s = qblock.dot(&kblock.t()) * scale;\n                            if let Some(mask) = &mask {\n                                s += &mask.slice(s!(mb, mh, q_range.clone(), kv_range.clone()));\n                            } else if self.causal {\n                                let mask = Array2::from_elem(\n                                    (q_range.len(), kv_range.len()),\n                                    f32::NEG_INFINITY,\n                                );\n                                let mask = mask.triu(\n                                    q_range.start as isize\n                                        - kv_range.start as isize\n                                        - q_len as isize\n                                        + kv_len as isize\n                                        + 1,\n                                );\n                                s += &mask;\n                            };\n                            let tile_m: Vec<f32> = s\n                                .rows()\n                                .into_iter()\n                                .map(|row| {\n                                    row.iter().copied().map(float_ord::FloatOrd).max().unwrap().0\n                                })\n                                .collect_vec();\n                            for (row_ix, max) in tile_m.iter().enumerate() {\n                                if max.is_finite() {\n                                    s.row_mut(row_ix).iter_mut().for_each(|x| *x -= max);\n                                }\n                            }\n                            // Sij <- exp(Sij * scale - max_of_row)\n                            s.mapv_inplace(f32::exp);\n                            let tile_l = s\n                                .sum_axis(tract_ndarray::Axis(1))\n                                .insert_axis(tract_ndarray::Axis(1));\n                            // m_new = max(maxes, row_maxs)\n                            let m_new =\n                                (0..q_range.len()).map(|i| m[i].max(tile_m[i])).collect_vec();\n                            // l_new = exp(m[i] - m_new[i]) * l[i] - exp(tile_m[i] - m_new[i]) * tile_l[i]\n                            let l_new = (0..q_range.len())\n                                .map(|i| {\n                                    (m[i] - m_new[i]).exp() * l[i]\n                                        + (tile_m[i] - m_new[i]).exp() * tile_l[(i, 0)]\n                                })\n                                .collect_vec();\n                            for i in 0..q_range.len() {\n                                let r_l_new = l_new[i].recip();\n                                let mul_o = ((m[i] - m_new[i]).exp()) * l[i] * r_l_new;\n                                let mul_sv = ((tile_m[i] - m_new[i]).exp()) * r_l_new;\n                                for j in 0..head_dim {\n                                    let sv = s.row(i).dot(&vblock.column(j));\n                                    oblock[(i, j)] = oblock[(i, j)] * mul_o + sv * mul_sv;\n                                }\n                            }\n                            l.copy_from_slice(&l_new);\n                            m.copy_from_slice(&m_new);\n                        }\n                    }\n                }\n            }\n        }\n        out\n    }\n}\n"
  },
  {
    "path": "transformers/src/ops/mod.rs",
    "content": "pub mod apply_rope;\npub mod dyn_kv_cache;\npub mod flash_sdpa;\npub mod scaled_masked_softmax;\npub mod sdpa;\npub mod streamed_sdpa;\n\n// Re-export ops that moved to core\npub mod rms_norm {\n    pub use tract_nnef::tract_core::ops::nn::RmsNorm;\n    pub use tract_nnef::tract_core::ops::nn::rms_norm::*;\n}\npub mod silu {\n    pub use tract_nnef::tract_core::ops::nn::Silu;\n    pub use tract_nnef::tract_core::ops::nn::silu::*;\n}\npub mod gelu_approximate {\n    pub use tract_nnef::tract_core::ops::nn::GeluApproximate;\n    pub use tract_nnef::tract_core::ops::nn::gelu_approximate::*;\n}\n\npub use apply_rope::{apply_rope_rule, rotate_half_rule};\npub use dyn_kv_cache::{DynKeyValueCache, replace_kv_cache, unfold_kv_cache};\npub use scaled_masked_softmax::scaled_masked_softmax_rule;\npub use sdpa::fuse_kv_cache_broadcast_rule;\n"
  },
  {
    "path": "transformers/src/ops/scaled_masked_softmax.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef::tract_core::ops::binary::{BinMiniOp, TypedBinOp};\nuse tract_nnef::tract_core::ops::logic::Iff;\nuse tract_nnef::tract_core::ops::math::{Add, Mul};\nuse tract_nnef::tract_core::ops::nn::{Softmax, SoftmaxExp, SoftmaxKind};\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_scaled_masked_softmax);\n    registry.register_primitive(\n        \"tract_transformers_scaled_masked_softmax\",\n        &[\n            TypeName::Scalar.tensor().named(\"input\"),\n            TypeName::Scalar.tensor().named(\"mask\"),\n            TypeName::Scalar.named(\"scale\"),\n            TypeName::Logical.named(\"post_softmax_mask\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        de_scaled_masked_softmax,\n    );\n}\n\nfn de_scaled_masked_softmax(\n    builder: &mut ModelBuilder,\n    invocation: &ResolvedInvocation,\n) -> TractResult<Value> {\n    let input = invocation.named_arg_as(builder, \"input\")?;\n    let mask = invocation.named_arg_as(builder, \"mask\")?;\n    let scale = invocation.named_arg_as(builder, \"scale\")?;\n    let post_softmax_mask: bool =\n        invocation.get_named_arg_as(builder, \"post_softmax_mask\")?.unwrap_or(false);\n    builder.wire(ScaledMaskedSoftmax { scale, post_softmax_mask }, &[input, mask])\n}\n\nfn ser_scaled_masked_softmax(\n    ast: &mut IntoAst,\n    node: &TypedNode,\n    op: &ScaledMaskedSoftmax,\n) -> TractResult<Option<Arc<RValue>>> {\n    let input = ast.mapping[&node.inputs[0]].clone();\n    let mask = ast.mapping[&node.inputs[1]].clone();\n    Ok(Some(invocation(\n        \"tract_transformers_scaled_masked_softmax\",\n        &[input, mask],\n        &[\n            (\"scale\", numeric(op.scale.cast_to_scalar::<f32>()?)),\n            (\"post_softmax_mask\", logical(op.post_softmax_mask)),\n        ],\n    )))\n}\n\n/// Fused scale + mask + softmax over the last axis, with optional post-softmax zeroing.\n///\n/// - Float mask: `A = SOFTMAX(INPUT * SCALE + MASK, axis=-1)`\n/// - Bool mask:  `A = SOFTMAX(IFF(MASK, INPUT * SCALE, -inf), axis=-1)`\n///\n/// If `post_softmax_mask` is true (bool mask only), also applies:\n/// `A = IFF(MASK, A, 0)` — zeros out positions where the mask is false.\n///\n/// The mask dtype determines which mode is used at eval time.\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct ScaledMaskedSoftmax {\n    pub scale: Arc<Tensor>,\n    pub post_softmax_mask: bool,\n}\n\nimpl Op for ScaledMaskedSoftmax {\n    fn name(&self) -> StaticName {\n        \"ScaledMaskedSoftmax\".to_string().into()\n    }\n    fn info(&self) -> TractResult<Vec<String>> {\n        let mut v = vec![format!(\"scale: {:?}\", self.scale)];\n        if self.post_softmax_mask {\n            v.push(\"post_softmax_mask: true\".to_string());\n        }\n        Ok(v)\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for ScaledMaskedSoftmax {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let (input, mask) = args_2!(inputs);\n        let softmax_axis = tvec!(input.rank() - 1);\n        let dt = input.datum_type();\n        let scale = self.scale.cast_to_dt(dt)?.into_owned();\n        let scaled = Mul.eval(input, scale.into_tvalue(), dt)?;\n\n        let pre_softmax: TValue = if mask.datum_type() == bool::datum_type() {\n            // Boolean mask: keep score where mask=true, replace with -inf where mask=false.\n            let fill = tensor0(-f32::INFINITY).cast_to_dt(dt)?.into_owned();\n            Iff.eval(tvec![mask.clone(), scaled.into(), fill.into_tvalue()])?.remove(0)\n        } else {\n            Add.eval(scaled.into(), mask.clone(), dt)?.into()\n        };\n\n        let softmax_out = Softmax::new(softmax_axis, None, SoftmaxKind::Softmax(SoftmaxExp::Libc))\n            .eval(tvec![pre_softmax])?[0]\n            .clone();\n\n        if self.post_softmax_mask {\n            // Zero out positions where the bool mask is false.\n            let zero = tensor0(0f32).cast_to_dt(dt)?.into_owned();\n            Ok(Iff.eval(tvec![mask, softmax_out, zero.into_tvalue()])?)\n        } else {\n            Ok(tvec![softmax_out])\n        }\n    }\n}\n\nimpl TypedOp for ScaledMaskedSoftmax {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        ensure!(!self.scale.is_zero()?);\n        ensure!(inputs.len() == 2);\n        let (input, mask) = (inputs[0], inputs[1]);\n        ensure!(\n            input.datum_type == mask.datum_type || mask.datum_type == bool::datum_type(),\n            \"mask must be same dtype as input or bool\"\n        );\n        ensure!(\n            input.rank() == mask.rank() || mask.datum_type == bool::datum_type(),\n            \"float mask must have same rank as input\"\n        );\n        let dt = input.datum_type;\n        let fact = dt.fact(input.shape.clone());\n        Ok(tvec!(fact))\n    }\n\n    fn input_roi(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TVec<Option<TDim>>>> {\n        // Introduction: mask's uniform_tdim defines which positions matter for scores.\n        let mask_fact = model.outlet_fact(node.inputs[1])?;\n        if let Some(mask_expr) = &mask_fact.uniform_tdim {\n            return Ok(Some(tvec![Some(mask_expr.clone()), None]));\n        }\n        // Bubbling: delegate to the natural blanket implementation.\n        tract_nnef::tract_core::optim::propagate_roi::bubble_roi(model, node)\n    }\n\n    as_op!();\n}\n\n/// Search pattern => A = SOFTMAX(A * SCALE + MASK, AXIS=-1)\npub fn scaled_masked_softmax_rule(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &Softmax,\n) -> TractResult<Option<TypedModelPatch>> {\n    let rank = node.outputs[0].fact.rank();\n    rule_if!(op.axes.as_slice() == [rank - 1]);\n\n    let in_fact = model.node_input_facts(node.id)?[0];\n    let dt = in_fact.datum_type;\n    // Only F16 and F32 is supported.\n    rule_if!(matches!(dt, DatumType::F32 | DatumType::F16));\n\n    // Try boolean-mask pattern first: Softmax(Iff(bool_mask, [Mul(x,scale) | x], fill))\n    if let Some(patch) = try_match_bool_iff_softmax(model, node, node_name, op, dt)? {\n        return Ok(Some(patch));\n    }\n\n    // Identify Add operator (Mask)\n    rule_if_some!(add_prev = model.previous_node(node));\n    rule_if_some!(add_prev_op = add_prev.op_as::<TypedBinOp>());\n    rule_if!(add_prev_op.0.is::<Add>());\n\n    let mut in_add = model.previous_nodes(add_prev);\n    rule_if!(in_add.len() == 2);\n\n    in_add.reverse();\n    let (left, right) = (in_add.pop().unwrap(), in_add.pop().unwrap());\n\n    let (scale_node, mask_outlet) = if left.op_is::<TypedBinOp>() {\n        (left, add_prev.inputs[1])\n    } else {\n        (right, add_prev.inputs[0])\n    };\n\n    rule_if_some!(scale_op = scale_node.op_as::<TypedBinOp>());\n    rule_if!(scale_op.0.is::<Mul>());\n\n    // Retrieve Scale\n    let mul_consts = model.collect_const_inputs(scale_node);\n    rule_if!(mul_consts.len() == 1);\n    let scale = mul_consts[0].val().clone();\n\n    rule_if!(scale.len() == 1);\n    rule_if!(scale.datum_type() == dt);\n\n    let mut patch = TypedModelPatch::default();\n    let input = patch.taps(model, &scale_node.inputs)?[0];\n    let mask = patch.taps(model, &[mask_outlet])?[0];\n\n    let out = patch.wire_node(\n        format!(\"{node_name}.scaled_masked_softmax\"),\n        ScaledMaskedSoftmax { scale, post_softmax_mask: false },\n        &[input, mask],\n    )?;\n\n    patch.shunt_outside(model, node.id.into(), out[0])?;\n    Ok(Some(patch))\n}\n\n/// Pattern: Softmax(Iff(cond, A, B)) where exactly one of A/B is the fill (uniform/const)\n/// and the other is the attention scores.\n///\n/// Two sub-cases handled:\n///   - `Iff(mask,  scores, fill)` — cond=True means \"valid, keep score\"\n///   - `Iff(~mask, fill,   scores)` — cond=True means \"masked, replace with fill\";\n///     we look through a BitNot/Not predecessor to recover the non-negated mask.\n///\n/// Also detects a downstream post-softmax Iff of the form:\n///   `Iff(~mask, 0, softmax_out)` and folds it into `post_softmax_mask=true`.\nfn try_match_bool_iff_softmax(\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    _op: &Softmax,\n    dt: DatumType,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if_some!(iff_node = model.previous_node(node));\n    rule_if!(iff_node.op_is::<Iff>());\n\n    let cond_outlet = iff_node.inputs[0];\n    let branch_t = iff_node.inputs[1]; // true branch\n    let branch_f = iff_node.inputs[2]; // false branch\n\n    rule_if!(model.outlet_fact(cond_outlet)?.datum_type == bool::datum_type());\n\n    // Decide which branch is the fill (uniform value) and which is the scores.\n    let t_is_fill = outlet_is_uniform(model, branch_t);\n    let f_is_fill = outlet_is_uniform(model, branch_f);\n    // Require exactly one side to be fill.\n    rule_if!(t_is_fill ^ f_is_fill);\n\n    let (scores_outlet, bool_mask_outlet) = if f_is_fill {\n        // Iff(mask, scores, fill) — condition is the direct \"attend\" mask (True=valid).\n        (branch_t, cond_outlet)\n    } else {\n        // Iff(~mask, fill, scores) — condition is negated; unwrap the Not/BitNot.\n        let original_mask = unwrap_bool_not(model, cond_outlet);\n        rule_if_some!(original_mask = original_mask);\n        (branch_f, original_mask)\n    };\n\n    // Optionally unwrap Mul(x, scale); fall back to identity scale (1.0).\n    let (scores_outlet, scale) = try_extract_scale(model, scores_outlet, dt).unwrap_or_else(|| {\n        let one = tensor0(1f32).cast_to_dt(dt).unwrap().into_owned().into_arc_tensor();\n        (scores_outlet, one)\n    });\n\n    // Detect optional downstream post-softmax Iff: Iff(~mask, 0, softmax_out).\n    let post_mask_succ = try_detect_post_softmax_iff(model, node)?;\n\n    let mut patch = TypedModelPatch::default();\n    let scores = patch.tap_model(model, scores_outlet)?;\n    let bool_mask = patch.tap_model(model, bool_mask_outlet)?;\n\n    let out = patch.wire_node(\n        format!(\"{node_name}.scaled_masked_softmax\"),\n        ScaledMaskedSoftmax { scale, post_softmax_mask: post_mask_succ.is_some() },\n        &[scores, bool_mask],\n    )?;\n\n    // Shunt the Softmax node, and also the downstream post-softmax Iff if present.\n    patch.shunt_outside(model, node.id.into(), out[0])?;\n    if let Some(post_iff_outlet) = post_mask_succ {\n        patch.shunt_outside(model, post_iff_outlet, out[0])?;\n    }\n    Ok(Some(patch))\n}\n\n/// Checks whether the single successor of `softmax_node` is a post-softmax masking Iff:\n///   `Iff(bool_cond, fill=0, softmax_out)` or `Iff(bool_cond, softmax_out, fill=0)`\n///\n/// Returns the outlet id of that Iff node if the pattern matches, `None` otherwise.\nfn try_detect_post_softmax_iff(\n    model: &TypedModel,\n    softmax_node: &TypedNode,\n) -> TractResult<Option<OutletId>> {\n    rule_if_some!(succ = model.single_succ(softmax_node.id)?);\n    rule_if!(succ.op_is::<Iff>());\n\n    let cond_outlet = succ.inputs[0];\n    let branch_t = succ.inputs[1];\n    let branch_f = succ.inputs[2];\n\n    rule_if!(model.outlet_fact(cond_outlet)?.datum_type == bool::datum_type());\n\n    // Exactly one branch must be uniform (the zero fill).\n    let t_is_fill = outlet_is_uniform(model, branch_t);\n    let f_is_fill = outlet_is_uniform(model, branch_f);\n    rule_if!(t_is_fill ^ f_is_fill);\n\n    // The non-fill branch must be the softmax output.\n    let softmax_outlet = OutletId::new(softmax_node.id, 0);\n    let data_branch = if f_is_fill { branch_t } else { branch_f };\n    rule_if!(data_branch == softmax_outlet);\n\n    Ok(Some(OutletId::new(succ.id, 0)))\n}\n\n/// Returns true if the outlet carries a uniform (all-same-value) tensor —\n/// i.e. it is a constant or its `uniform` field is set.\nfn outlet_is_uniform(model: &TypedModel, outlet: OutletId) -> bool {\n    model.outlet_fact(outlet).map(|f| f.konst.is_some() || f.uniform.is_some()).unwrap_or(false)\n}\n\n/// Walk the graph upstream from `outlet`, passing through shape-only ops\n/// (AddAxis / RemoveAxis / MultiBroadcastTo), looking for a logical/bitwise NOT.\n/// If found, returns the input to that NOT (the non-negated bool wire).\n/// Returns `None` if no such NOT is reachable.\nfn unwrap_bool_not(model: &TypedModel, outlet: OutletId) -> Option<OutletId> {\n    use tract_nnef::tract_core::ops::array::MultiBroadcastTo;\n    use tract_nnef::tract_core::ops::change_axes::AxisOp;\n    use tract_nnef::tract_core::ops::element_wise::ElementWiseOp;\n    use tract_nnef::tract_core::ops::logic::{BitNot, Not};\n\n    let node = model.node(outlet.node);\n\n    // Direct Not/BitNot on bool\n    if let Some(ew) = node.op_as::<ElementWiseOp>()\n        && (ew.0.is::<Not>() || ew.0.is::<BitNot>())\n    {\n        return Some(node.inputs[0]);\n    }\n\n    // Look through shape-transparent ops (AddAxis, RemoveAxis, broadcast)\n    if node.op_is::<AxisOp>() || node.op_is::<MultiBroadcastTo>() {\n        return unwrap_bool_not(model, node.inputs[0]);\n    }\n\n    None\n}\n\n/// If the node at `outlet` is `Mul(x, const_scale)`, return `(x_outlet, scale_tensor)`.\nfn try_extract_scale(\n    model: &TypedModel,\n    outlet: OutletId,\n    dt: DatumType,\n) -> Option<(OutletId, Arc<Tensor>)> {\n    let node = model.node(outlet.node);\n    let bin = node.op_as::<TypedBinOp>()?;\n    if !bin.0.is::<Mul>() {\n        return None;\n    }\n    let consts = model.collect_const_inputs(node);\n    if consts.len() != 1 {\n        return None;\n    }\n    let scale = consts[0].val().clone();\n    if scale.len() != 1 || scale.datum_type() != dt {\n        return None;\n    }\n    // The non-const input is the scores.\n    let scores_outlet = node\n        .inputs\n        .iter()\n        .copied()\n        .find(|o| model.outlet_fact(*o).map(|f| f.konst.is_none()).unwrap_or(false))?;\n    Some((scores_outlet, scale))\n}\n"
  },
  {
    "path": "transformers/src/ops/sdpa.rs",
    "content": "use std::str::FromStr;\n\nuse tract_core::ops::array::{MultiBroadcastTo, TypedConcat};\nuse tract_core::ops::cast::Cast;\nuse tract_core::ops::einsum::EinSum;\nuse tract_core::ops::source::TypedSource;\nuse tract_core::ops::{change_axes, math};\nuse tract_nnef::internal::*;\nuse tract_nnef::ser::datum_type;\nuse tract_nnef::tract_core::ops::math::mul;\nuse tract_nnef::tract_core::ops::nn::{Softmax, SoftmaxExp, SoftmaxKind};\n\nuse crate::ops::dyn_kv_cache::DynKeyValueCache;\nuse crate::ops::flash_sdpa::FlashSdpaOp;\n\nuse super::scaled_masked_softmax::ScaledMaskedSoftmax;\n\npub fn register(registry: &mut Registry) {\n    registry.register_dumper(ser_sdpa);\n    registry.register_primitive(\n        \"tract_transformers_sdpa\",\n        &[\n            TypeName::Scalar.tensor().named(\"q\"),\n            TypeName::Scalar.tensor().named(\"k\"),\n            TypeName::Scalar.tensor().named(\"v\"),\n            TypeName::Scalar.tensor().named(\"mask\"),\n            TypeName::Scalar.named(\"scale\"),\n            TypeName::String.named(\"datum_type\"),\n            TypeName::String.named(\"acc_datum_type\"),\n            TypeName::Logical.named(\"is_causal\"),\n        ],\n        &[(\"output\", TypeName::Scalar.tensor())],\n        deser_spda,\n    );\n}\n\nfn ser_sdpa(ast: &mut IntoAst, node: &TypedNode, op: &Sdpa) -> TractResult<Option<Arc<RValue>>> {\n    // Inputs settings\n    let q = ast.mapping[&node.inputs[0]].clone();\n    let k = ast.mapping[&node.inputs[1]].clone();\n    let v = ast.mapping[&node.inputs[2]].clone();\n    let mut inputs = vec![q, k, v];\n    if let Some(mask) = node.inputs.get(3).as_ref().map(|it| ast.mapping[it].clone()) {\n        inputs.push(mask);\n    }\n\n    // Attributes settings\n    let mut attrs = vec![\n        (\"is_causal\", logical(op.is_causal)),\n        (\"datum_type\", datum_type(op.datum_type)),\n        (\"acc_datum_type\", datum_type(op.acc_datum_type)),\n    ];\n    if let Some(scale) = op.scale.as_ref() {\n        attrs.push((\"scale\", numeric(scale.cast_to_scalar::<f32>()?)));\n    }\n\n    Ok(Some(invocation(\"tract_transformers_sdpa\", &inputs, &attrs)))\n}\n\nfn deser_spda(builder: &mut ModelBuilder, invocation: &ResolvedInvocation) -> TractResult<Value> {\n    let q = invocation.named_arg_as(builder, \"q\")?;\n    let k = invocation.named_arg_as(builder, \"k\")?;\n    let v = invocation.named_arg_as(builder, \"v\")?;\n    let mut inputs = vec![q, k, v];\n    let q_rank = builder.model.outlet_fact(q)?.rank();\n    if let Some(mut mask) = invocation.get_named_arg_as(builder, \"mask\")? {\n        let mask_fact = builder.model.outlet_fact(mask)?;\n        ensure!(mask_fact.rank() <= q_rank);\n        for _ in mask_fact.rank()..q_rank {\n            mask = builder.wire_as_outlets(AxisOp::Add(0), &[mask])?[0];\n        }\n        inputs.push(mask);\n    };\n    let scale: Option<f32> = invocation.get_named_arg_as(builder, \"scale\")?;\n    let datum_type =\n        DatumType::from_str(&invocation.named_arg_as::<String>(builder, \"datum_type\")?)?;\n    let acc_datum_type =\n        DatumType::from_str(&invocation.named_arg_as::<String>(builder, \"acc_datum_type\")?)?;\n    let is_causal = invocation.named_arg_as(builder, \"is_causal\")?;\n    builder.wire(Sdpa { scale: scale.map(tensor0), datum_type, acc_datum_type, is_causal }, &inputs)\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct Sdpa {\n    pub scale: Option<Tensor>,\n    pub datum_type: DatumType,\n    pub acc_datum_type: DatumType,\n    pub is_causal: bool,\n}\n\nimpl Sdpa {\n    fn wire_softmax(\n        &self,\n        graph: &mut TypedModel,\n        scores: OutletId,\n        mask: Option<OutletId>,\n        scale: f32,\n    ) -> TractResult<OutletId> {\n        let scores_fact = graph.outlet_fact(scores)?.clone();\n        let rank = scores_fact.rank();\n        ensure!(rank == 5);\n\n        let scale = tensor0(scale).cast_to_dt(self.acc_datum_type)?.into_owned();\n        if let Some(mask) = mask {\n            ensure!(graph.outlet_fact(mask)?.rank() == 5);\n            graph\n                .wire_node(\n                    \"att_scaled_masked_softmax\",\n                    ScaledMaskedSoftmax { scale: scale.into(), post_softmax_mask: false },\n                    &[scores, mask],\n                )\n                .map(|o| o[0])\n        } else {\n            let scale_const = graph.add_const(\"scale\", scale)?;\n            let scaled_scores = wire_with_rank_broadcast(\n                \"scale_scores\",\n                graph,\n                math::mul(),\n                &[scores, scale_const],\n            )?[0];\n            graph\n                .wire_node(\n                    \"att_softmax\",\n                    Softmax::new(tvec![rank - 1], None, SoftmaxKind::Softmax(SoftmaxExp::Libc)),\n                    &[scaled_scores],\n                )\n                .map(|o| o[0])\n        }\n    }\n\n    fn build_sdpa_graph(&self, input_facts: TVec<&TypedFact>) -> TractResult<TypedModel> {\n        use change_axes::AxisOp::*;\n        let mut graph = TypedModel::default();\n        let mut q = graph.add_source(\"q\", input_facts[0].clone())?;\n        let mut k = graph.add_source(\"k\", input_facts[1].clone())?;\n        let mut v = graph.add_source(\"v\", input_facts[2].clone())?;\n        let mut mask =\n            input_facts.get(3).map(|m| graph.add_source(\"mask\", (*m).clone())).transpose()?;\n\n        if input_facts[0].rank() == 3 {\n            q = graph.wire_node(\"reshape_q_heads\", Add(1), &[q])?[0];\n            k = graph.wire_node(\"reshape_k_heads\", Add(1), &[k])?[0];\n            v = graph.wire_node(\"reshape_v_heads\", Add(1), &[v])?[0];\n            if let Some(m) = &mut mask {\n                *m = graph.wire_node(\"reshape_m_heads\", Add(1), &[*m])?[0];\n            }\n        }\n\n        let [_, qh, att_rows, _qd] = &*graph.outlet_fact(q)?.shape.clone() else { unreachable!() };\n        let [_b, kh, att_cols, kd] = &*graph.outlet_fact(k)?.shape.clone() else { unreachable!() };\n\n        let num_qh = qh.to_usize()?;\n        let num_kh = kh.to_usize()?;\n        let num_kd = kd.to_usize()?;\n\n        let g = num_qh / num_kh;\n\n        q = graph.wire_node(\n            \"reshape_q_gha\",\n            Reshape(1, tvec!(qh.clone()), tvec!(kh.clone(), g.to_dim())),\n            &[q],\n        )?[0];\n        k = graph.wire_node(\"reshape_k_gha\", change_axes::AxisOp::Add(2), &[k])?[0];\n        v = graph.wire_node(\"reshape_v_gha\", change_axes::AxisOp::Add(2), &[v])?[0];\n        if let Some(m) = &mut mask {\n            if graph.outlet_fact(*m)?.shape[1].is_one() {\n                *m = graph.wire_node(\"reshape_m_heads_groups\", Add(2), &[*m])?[0];\n            } else {\n                *m = graph.wire_node(\n                    \"reshape_m_head_groups\",\n                    Reshape(1, tvec!(qh.clone()), tvec!(kh.clone(), g.to_dim())),\n                    &[*m],\n                )?[0];\n            }\n        }\n\n        let scale = self\n            .scale\n            .as_ref()\n            .map(|t| *t.try_as_plain().unwrap().to_scalar::<f32>().unwrap())\n            .unwrap_or_else(|| (num_kd as f32).sqrt().recip());\n\n        if self.is_causal {\n            mask = Some(\n                wire_attention_mask(\n                    &mut graph,\n                    \"sdpa\",\n                    self.acc_datum_type,\n                    SdpaMaskMode::Causal,\n                    5,\n                    att_rows,\n                    att_cols,\n                )\n                .context(\"Wiring causal mask\")?,\n            );\n        };\n\n        let scores_einsum = EinSum::new(\"bhgmk,bhgnk->bhgmn\".parse().unwrap(), self.acc_datum_type);\n        let scores = graph.wire_node(\"scores\", scores_einsum, &[q, k])?[0];\n        if let Some(m) = &mut mask {\n            if graph.outlet_fact(*m)?.datum_type != self.acc_datum_type {\n                *m = graph.wire_node(\"cast_mask\", Cast::new(self.acc_datum_type), &[*m])?[0];\n            }\n        }\n\n        let attention_weights =\n            self.wire_softmax(&mut graph, scores, mask, scale).context(\"In wire_softmax\")?;\n        let mut output = graph.wire_node(\n            \"att_out\",\n            EinSum::new(\"bhgmn,bhgnv->bhgmv\".parse().unwrap(), self.acc_datum_type),\n            &[attention_weights, v],\n        )?[0];\n        output = graph.wire_node(\n            \"reshape_out_gha\",\n            Reshape(1, tvec!(kh.clone(), g.to_dim()), tvec!(qh.clone())),\n            &[output],\n        )?[0];\n        if input_facts[0].rank() == 3 {\n            output = graph.wire_node(\"reshape_out_heads\", Rm(1), &[output])?[0];\n        }\n        if graph.outlet_fact(output)?.datum_type != input_facts[0].datum_type {\n            output =\n                graph.wire_node(\"cast_output\", Cast::new(input_facts[0].datum_type), &[output])?[0];\n        }\n        graph.select_output_outlets(&[output])?;\n        Ok(graph)\n    }\n\n    pub fn patch_sdpa(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        let input_facts = model.node_input_facts(node.id)?;\n        let subgraph = self.build_sdpa_graph(input_facts)?;\n\n        let mut patch = TypedModelPatch::new(format!(\"Explode SDPA node {}\", node.name));\n        patch.model = subgraph.into_decluttered()?;\n\n        let body_inputs = patch.model.input_outlets()?;\n        for (i, body_input_outlet) in body_inputs.iter().enumerate() {\n            patch.taps.insert(*body_input_outlet, node.inputs[i]);\n        }\n\n        let body_outputs = patch.model.output_outlets()?;\n        patch.shunt_outside(model, node.id.into(), body_outputs[0])?;\n        //println!(\"{}\",&patch.model);\n        Ok(Some(patch))\n    }\n}\n\nimpl Op for Sdpa {\n    fn name(&self) -> StaticName {\n        \"SDPA\".into()\n    }\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\"scale: {:?}\", self.scale)])\n    }\n    op_as_typed_op!();\n}\n\nimpl EvalOp for Sdpa {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        let input_facts: TVec<TypedFact> = inputs\n            .iter()\n            .map(|tv| TypedFact::try_from(tv.clone().into_arc_tensor()))\n            .collect::<TractResult<_>>()?;\n        let input_fact_refs: TVec<&TypedFact> = input_facts.iter().collect();\n        let body =\n            self.build_sdpa_graph(input_fact_refs).context(\"Wiring adhoc fallback graph \")?;\n        let plan = TypedSimplePlan::new(body)?;\n        plan.run(inputs)\n    }\n}\n\nimpl TypedOp for Sdpa {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        if self.is_causal {\n            ensure!(inputs.len() == 3, \"Mask cannot be provided if is_causal=true\")\n        };\n        let rank = inputs[0].rank();\n        ensure!(rank == 3 || rank == 4, \"Input tensors must be 3D or 4D\");\n        ensure!(\n            inputs[..3].iter().map(|it| it.rank()).all(|r| r == rank),\n            \"Q, K and V should have the same rank {}\",\n            rank\n        );\n        let mask = inputs.get(3);\n        ensure!(mask.is_none_or(|m| m.rank() == rank));\n\n        let q_shape = &inputs[0].shape.dims();\n        let k_shape = &inputs[1].shape.dims();\n        let v_shape = &inputs[2].shape.dims();\n\n        ensure!(\n            q_shape[0] == k_shape[0]\n                && q_shape[0] == v_shape[0]\n                && mask.as_ref().is_none_or(|m| m.shape[0].is_one() || m.shape[0] == q_shape[0])\n        );\n\n        if rank == 4 {\n            let q_heads = q_shape[1].to_i64()?;\n            let k_heads = k_shape[1].to_i64()?;\n            let v_heads = v_shape[1].to_i64()?;\n            ensure!(k_heads == v_heads, \"K and V must have the same number of heads.\");\n            ensure!(\n                q_heads % k_heads == 0,\n                \"Q heads ({}) must be a multiple of K/V heads ({})\",\n                q_heads,\n                k_heads\n            );\n            ensure!(\n                mask.as_ref().is_none_or(|m| m.shape[1].is_one() || m.shape[1] == q_heads.into())\n            );\n        }\n\n        let output_shape = match rank {\n            3 => {\n                if let (&[b, seq_len, _], &[_, _, out_dim]) = (q_shape, v_shape) {\n                    tvec!(b.clone(), seq_len.clone(), out_dim.clone())\n                } else {\n                    unreachable!()\n                }\n            }\n            4 => {\n                if let (&[b, n_heads, seq_len, _], &[_, _, _, out_dim]) = (q_shape, v_shape) {\n                    tvec!(b.clone(), n_heads.clone(), seq_len.clone(), out_dim.clone())\n                } else {\n                    unreachable!()\n                }\n            }\n            _ => unreachable!(),\n        };\n\n        let out_fact = inputs[0].datum_type().unwrap().fact(output_shape);\n        Ok(tvec!(out_fact))\n    }\n\n    fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {\n        let dt = inputs[0].datum_type;\n        let rank = inputs[0].rank();\n        let q = &inputs[0].shape;\n        let k = &inputs[1].shape;\n        let v = &inputs[2].shape;\n        // rank 3: [B, S_q, D_k], rank 4: [B, H, S_q, D_k]\n        let (batch, heads, s_q, d_k) = if rank == 4 {\n            (q[0].clone(), q[1].clone(), q[2].clone(), q[3].clone())\n        } else {\n            (q[0].clone(), 1.to_dim(), q[1].clone(), q[2].clone())\n        };\n        let s_k = k[rank - 2].clone();\n        let d_v = v[rank - 1].clone();\n        // Q*K^T: B*H*S_q*S_k*D_k, attn*V: B*H*S_q*S_k*D_v\n        let fma = batch * &heads * &s_q * &s_k * (d_k + d_v);\n        Ok(tvec!((Cost::FMA(dt), fma)))\n    }\n\n    fn codegen(\n        &self,\n        model: &TypedModel,\n        node: &TypedNode,\n    ) -> TractResult<Option<TypedModelPatch>> {\n        if self.acc_datum_type.is::<f32>() {\n            let scale = self.scale.as_ref().map(|t| t.cast_to_scalar()).transpose()?;\n            let op = FlashSdpaOp { causal: self.is_causal, scale };\n            TypedModelPatch::replace_single_op(model, node, &node.inputs, op).map(Some)\n        } else {\n            self.patch_sdpa(model, node).context(\"Wiring fallback SDPA\")\n        }\n    }\n    as_op!();\n}\n\n// KV cache broadcast is: input -> concat -> AddAxis -> Broadcast -> Reshape\npub fn match_broadcast_kv_cache_pattern(\n    model: &TypedModel,\n    start_outlet: OutletId,\n) -> TractResult<Option<OutletId>> {\n    // Find Reshape node\n    let reshape_node = model.node(start_outlet.node);\n    rule_if!(\n        reshape_node.op_is::<change_axes::AxisOp>()\n            && matches!(\n                reshape_node.op_as::<change_axes::AxisOp>().unwrap(),\n                change_axes::AxisOp::Reshape(1, _, _)\n            )\n    );\n\n    // Find broadcast node\n    rule_if_some!(broadcast_node = model.previous_node(reshape_node));\n    rule_if!(broadcast_node.op_is::<MultiBroadcastTo>());\n\n    // Find add axis node\n    rule_if_some!(unsqueeze_node = model.previous_node(broadcast_node));\n    rule_if!(\n        unsqueeze_node.op_is::<change_axes::AxisOp>()\n            && matches!(\n                unsqueeze_node.op_as::<change_axes::AxisOp>().unwrap(),\n                change_axes::AxisOp::Add(2)\n            )\n    );\n\n    fn is_concat(model: &TypedModel, n: &Node<TypedFact, Box<dyn TypedOp>>) -> bool {\n        n.op_is::<TypedConcat>()\n            && n.inputs.len() == 2\n            && n.outputs.len() == 1\n            && model.outputs.contains(&n.id.into())\n    }\n\n    fn is_dynkv(n: &Node<TypedFact, Box<dyn TypedOp>>) -> bool {\n        n.op_is::<DynKeyValueCache>() && n.inputs.len() == 1 && n.outputs.len() == 1\n    }\n\n    // Find concat or dyn kvcache node\n    rule_if_some!(node = model.previous_node(unsqueeze_node));\n    rule_if!(is_concat(model, node) || is_dynkv(node));\n\n    let kv_outlet = unsqueeze_node.inputs[0];\n    if is_dynkv(node) {\n        return Ok(Some(kv_outlet));\n    }\n\n    // node is concat, we need to check one input is a source\n    let input0_node = model.node(node.inputs[0].node);\n    let input1_node = model.node(node.inputs[1].node);\n    if input0_node.op_is::<TypedSource>() || input1_node.op_is::<TypedSource>() {\n        return Ok(Some(kv_outlet));\n    }\n\n    Ok(None)\n}\n\npub fn fuse_kv_cache_broadcast_rule(\n    _ctx: &(),\n    model: &TypedModel,\n    node: &TypedNode,\n    node_name: &str,\n    op: &Sdpa,\n) -> TractResult<Option<TypedModelPatch>> {\n    rule_if_some!(new_k_outlet = match_broadcast_kv_cache_pattern(model, node.inputs[1])?);\n    rule_if_some!(new_v_outlet = match_broadcast_kv_cache_pattern(model, node.inputs[2])?);\n\n    let mut patch = TypedModelPatch::default();\n    let mut new_sdpa_inputs = node.inputs.clone();\n    new_sdpa_inputs[1] = new_k_outlet;\n    new_sdpa_inputs[2] = new_v_outlet;\n\n    let tapped_inputs = patch.taps(model, &new_sdpa_inputs)?;\n\n    let new_sdpa_node = patch.wire_node(\n        format!(\"{}.sdpa_fused_kv_broadcast\", node_name),\n        op.clone(),\n        &tapped_inputs,\n    )?;\n\n    patch.shunt_outside(model, node.id.into(), new_sdpa_node[0])?;\n\n    Ok(Some(patch))\n}\n\npub enum SdpaMaskMode {\n    Neutral,\n    Causal,\n}\n\npub fn wire_attention_mask(\n    model: &mut TypedModel,\n    prefix: &str,\n    dt: DatumType,\n    mode: SdpaMaskMode,\n    rank: usize,\n    q_len: &TDim,\n    kv_len: &TDim,\n) -> TractResult<OutletId> {\n    let s_plus_p_outlet = model.add_const(prefix.to_string() + \".S_P\", tensor0(kv_len.clone()))?;\n    let p_outlet = model.add_const(\"P\", tensor0(kv_len.clone() - q_len.clone()))?;\n\n    let zero = model.add_const(prefix.to_string() + \".P\", tensor0(TDim::Val(0)))?;\n    let range_increment = model.add_const(prefix.to_string() + \".mask_s\", tensor0(TDim::Val(1)))?;\n    let s_range = model.wire_node(\n        prefix.to_string() + \".mask_s_range\",\n        tract_core::ops::array::Range::new(q_len.clone()),\n        &[p_outlet, s_plus_p_outlet, range_increment],\n    )?;\n    let s_plus_p_range = model.wire_node(\n        prefix.to_string() + \".mask_s+p_range\",\n        tract_core::ops::array::Range::new(kv_len.clone()),\n        &[zero, s_plus_p_outlet, range_increment],\n    )?;\n    let s_range_add_axis =\n        model.wire_node(prefix.to_string() + \".mask_s_range.add_axis\", AxisOp::Add(1), &s_range)?\n            [0];\n    let s_plus_p_range_add_axis = model.wire_node(\n        prefix.to_string() + \".mask_s_plus_p_range.add_axis\",\n        AxisOp::Add(0),\n        &s_plus_p_range,\n    )?[0];\n\n    let greater = model.wire_node(\n        prefix.to_string() + \".mask.greater\",\n        tract_core::ops::binary::TypedBinOp(tract_core::ops::logic::comp_gt(), None),\n        &[s_plus_p_range_add_axis, s_range_add_axis],\n    )?[0];\n    let cast_greater =\n        model.wire_node(prefix.to_string() + \".mask.greater.cast\", Cast::new(dt), &[greater])?[0];\n\n    let multiplier = match mode {\n        SdpaMaskMode::Causal => model.add_const(\"P\", dt.min_value())?,\n        SdpaMaskMode::Neutral => model.add_const(\"P\", Tensor::zero_scalar_dt(dt)?)?,\n    };\n    let mask = wire_with_rank_broadcast(\n        prefix.to_string() + \".2d\",\n        model,\n        mul(),\n        &[cast_greater, multiplier],\n    )?[0];\n    let reshaped_mask = model.wire_node(\n        prefix,\n        AxisOp::Reshape(0, tvec![], tvec![TDim::Val(1); rank - 2]),\n        &[mask],\n    )?[0];\n    Ok(reshaped_mask)\n}\n"
  },
  {
    "path": "transformers/src/ops/streamed_sdpa.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef::tract_ndarray::{Array4, ArrayView1, ArrayView2, ArrayView4, Ix4, s};\n\n/// Tract operator wrapper.\n#[derive(Clone, Debug, PartialEq)]\npub struct StreamedSdpaOp {\n    pub causal: bool,\n    pub block_k: usize,\n    pub scale: Option<f32>,\n}\nimpl Eq for StreamedSdpaOp {}\n\nimpl Op for StreamedSdpaOp {\n    fn name(&self) -> StaticName {\n        \"StreamedSDPA\".into()\n    }\n\n    fn info(&self) -> TractResult<Vec<String>> {\n        Ok(vec![format!(\n            \"causal={}, block_k={}, scale={:?}\",\n            self.causal, self.block_k, self.scale\n        )])\n    }\n\n    op_as_typed_op!();\n}\n\nimpl TypedOp for StreamedSdpaOp {\n    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {\n        let (q, k, v, opt_m) = match inputs.len() {\n            3 => (inputs[0], inputs[1], inputs[2], None),\n            4 => (inputs[0], inputs[1], inputs[2], Some(inputs[3])),\n            _ => bail!(\"StreamSDPA expects 3 or 4 inputs (Q,K,V, optional mask), got {inputs:?}\"),\n        };\n\n        // dtype checks\n        ensure!(q.datum_type.is_float(), \"Q must be floating point\");\n        ensure!(k.datum_type.is_float(), \"K must be floating point\");\n        ensure!(v.datum_type.is_float(), \"V must be floating point\");\n        if let Some(m) = opt_m {\n            ensure!(m.datum_type.is_float(), \"M must be floating point\");\n        }\n\n        // rank checks\n        ensure!(\n            q.rank() == k.rank() && q.rank() == v.rank(),\n            \"Q, K and V must have the same rank, found {}, {}, {} resp.\",\n            q.rank(),\n            k.rank(),\n            v.rank()\n        );\n        ensure!(\n            q.rank() == 3 || q.rank() == 4,\n            \"Inputs must be of rank 3 or 4, found {}\",\n            q.rank()\n        );\n\n        if let Some(m) = opt_m {\n            ensure!(m.rank() == 2, \"Mask must be of rank 2, found mask {:?}\", m,);\n        }\n\n        let one = 1.to_dim();\n\n        let ((bq, hq, _tq, dq), (bk, hkv, tk, dk), (bv, hkv2, tk2, dv)) = if q.rank() == 4 {\n            (\n                (&q.shape[0], &q.shape[1], &q.shape[2], &q.shape[3]),\n                (&k.shape[0], &k.shape[1], &k.shape[2], &k.shape[3]),\n                (&v.shape[0], &v.shape[1], &v.shape[2], &v.shape[3]),\n            )\n        } else {\n            (\n                (&q.shape[0], &one, &q.shape[1], &q.shape[2]),\n                (&k.shape[0], &one, &k.shape[1], &k.shape[2]),\n                (&v.shape[0], &one, &v.shape[1], &v.shape[2]),\n            )\n        };\n\n        ensure!(bq == bk && bq == bv, \"Batch dims must match for Q/K/V\");\n        ensure!(hkv == hkv2, \"K/V head counts must match\");\n        ensure!(tk == tk2, \"K/V lengths must match\");\n        ensure!(dq == dk && dq == dv, \"Head dims (D) must match across Q/K/V\");\n\n        // If heads are fully known, check GQA divisibility (best-effort).\n        if let (Ok(hq), Ok(hkv)) = (hq.to_usize(), hkv.to_usize()) {\n            ensure!(hq % hkv == 0, \"num_q_heads must be a multiple of num_kv_heads for GQA\");\n        }\n\n        // Output has same shape and dtype as Q.\n        Ok(tvec!(q.without_value()))\n    }\n\n    as_op!();\n}\n\nimpl EvalOp for StreamedSdpaOp {\n    fn is_stateless(&self) -> bool {\n        true\n    }\n\n    fn eval(&self, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {\n        ensure!(inputs.len() == 3 || inputs.len() == 4);\n        let [q, k, v] = &inputs[0..3] else {\n            bail!(\"Expects 3 or 4 inptus (Q, K, V, optional mask)\")\n        };\n\n        let input_dt = q.datum_type();\n\n        let (q, k, v) = (q.cast_to::<f32>()?, k.cast_to::<f32>()?, v.cast_to::<f32>()?);\n        let mut q = q.to_plain_array_view::<f32>()?;\n        let mut k = k.to_plain_array_view::<f32>()?;\n        let mut v = v.to_plain_array_view::<f32>()?;\n\n        let is_3d_case = q.ndim() == 3;\n\n        if is_3d_case {\n            q.insert_axis_inplace(tract_ndarray::Axis(1));\n            k.insert_axis_inplace(tract_ndarray::Axis(1));\n            v.insert_axis_inplace(tract_ndarray::Axis(1));\n        }\n\n        let (q, k, v) = (\n            q.into_dimensionality::<Ix4>()?,\n            k.into_dimensionality::<Ix4>()?,\n            v.into_dimensionality::<Ix4>()?,\n        );\n\n        let (batch_size, num_q_heads, query_len, head_dim) =\n            (q.shape()[0], q.shape()[1], q.shape()[2], q.shape()[3]);\n        let (bk, num_kv_heads, kv_len, hd_k) =\n            (k.shape()[0], k.shape()[1], k.shape()[2], k.shape()[3]);\n\n        ensure!(batch_size == bk, \"Batch mismatch between Q and K\");\n        ensure!(head_dim == hd_k, \"Head dim mismatch between Q and K\");\n        ensure!(num_kv_heads == v.shape()[1], \"K/V head mismatch\");\n        ensure!(kv_len == v.shape()[2] && head_dim == v.shape()[3], \"K/V shape mismatch\");\n\n        // Views as fixed 4-D\n        let q4 = q.to_shape((batch_size, num_q_heads, query_len, head_dim))?;\n        let k4 = k.to_shape((batch_size, num_kv_heads, kv_len, head_dim))?;\n        let v4 = v.to_shape((batch_size, num_kv_heads, kv_len, head_dim))?;\n\n        let m = if let Some(m) = inputs.get(3) {\n            Some(\n                m.cast_to::<f32>()?\n                    .into_owned()\n                    .into_plain_array::<f32>()?\n                    .into_shape_with_order((query_len, kv_len))?,\n            )\n        } else {\n            None\n        };\n\n        let mut out = self\n            .flash_attention_gqa(q4.view(), k4.view(), v4.view(), m.as_ref().map(|m| m.view()))\n            .into_dyn();\n        if is_3d_case {\n            out.index_axis_inplace(tract_ndarray::Axis(1), 0);\n        }\n\n        Ok(tvec!(out.into_tensor().cast_to_dt(input_dt)?.into_owned().into_tvalue()))\n    }\n}\n\nimpl StreamedSdpaOp {\n    /// Flash Attention forward with Grouped-Query Attention (GQA).\n    ///\n    /// Shapes:\n    ///   Q: (batch_size, num_q_heads, query_len, head_dim)\n    ///   K: (batch_size, num_kv_heads, kv_len,   head_dim)\n    ///   V: (batch_size, num_kv_heads, kv_len,   head_dim)\n    /// Returns O: (batch_size, num_q_heads, query_len, head_dim)\n    ///\n    /// GQA mapping: each query head qh maps to a key/value head index kh = qh / group_size\n    /// where group_size = num_q_heads / num_kv_heads (must divide exactly).\n    pub fn flash_attention_gqa(\n        &self,\n        q: ArrayView4<f32>,\n        k: ArrayView4<f32>,\n        v: ArrayView4<f32>,\n        mask: Option<ArrayView2<f32>>,\n    ) -> Array4<f32> {\n        // Explicit dimensions\n        let (batch_size, num_q_heads, query_len, head_dim) = q.dim();\n        let (_, num_kv_heads, kv_len, _) = k.dim();\n        let scale = self.scale.unwrap_or((head_dim as f32).recip().sqrt());\n        let block_k = self.block_k.max(1);\n\n        let mut out = Array4::<f32>::zeros((batch_size, num_q_heads, query_len, head_dim));\n        let group_size = num_q_heads / num_kv_heads;\n\n        for b in 0..batch_size {\n            for kh in 0..num_kv_heads {\n                let k_bh = k.slice(s![b, kh, .., ..]); // (kv_len, head_dim)\n                let v_bh = v.slice(s![b, kh, .., ..]); // (kv_len, head_dim)\n                for inner_qh in 0..group_size {\n                    let qh_ix = kh * group_size + inner_qh;\n                    let qh = q.slice(s![b, qh_ix, .., ..]);\n\n                    for t_q in 0..query_len {\n                        let q_vec = qh.slice(s![t_q, ..]);\n\n                        // Streaming softmax state\n                        let mut m = f32::NEG_INFINITY; // running max\n                        let mut l = 0.0f32; // running sum of exp(scores - m)\n                        let mut acc = vec![0.0f32; head_dim];\n\n                        // Process in KV tiles\n                        let mut kb = 0usize;\n                        while kb < kv_len {\n                            let kend = (kb + block_k).min(kv_len);\n\n                            let mut block_max = f32::NEG_INFINITY;\n                            let mut scores: Vec<f32> = Vec::with_capacity(kend - kb);\n                            for i_k in kb..kend {\n                                let mask = if let Some(mask) = mask {\n                                    mask[(t_q, i_k)]\n                                } else if self.causal && i_k > t_q {\n                                    f32::NEG_INFINITY\n                                } else {\n                                    0.0f32\n                                };\n                                let s = (dot1d(q_vec.view(), k_bh.row(i_k).view()) * scale) + mask;\n                                if s > block_max {\n                                    block_max = s;\n                                }\n                                scores.push(s);\n                            }\n\n                            if !block_max.is_finite() {\n                                kb = kend;\n                                continue;\n                            }\n\n                            let new_m = if m > block_max { m } else { block_max };\n                            let alpha = if m.is_finite() { (m - new_m).exp() } else { 0.0 };\n\n                            if alpha != 1.0 {\n                                acc.iter_mut().for_each(|a| *a *= alpha);\n                                l *= alpha;\n                            }\n\n                            let mut block_l = 0.0f32;\n                            for (off, i_k) in (kb..kend).enumerate() {\n                                let s = scores[off];\n                                if !s.is_finite() {\n                                    continue;\n                                }\n                                let w = (s - new_m).exp();\n                                block_l += w;\n                                let v_row = v_bh.row(i_k);\n                                for d_idx in 0..head_dim {\n                                    acc[d_idx] += w * v_row[d_idx];\n                                }\n                            }\n\n                            l += block_l;\n                            m = new_m;\n                            kb = kend;\n                        }\n\n                        let inv_l = if l > 0.0 { 1.0 / l } else { 0.0 };\n                        for d_idx in 0..head_dim {\n                            out[[b, qh_ix, t_q, d_idx]] = acc[d_idx] * inv_l;\n                        }\n                    }\n                }\n            }\n        }\n\n        out\n    }\n}\n\n#[inline]\nfn dot1d(a: ArrayView1<f32>, b: ArrayView1<f32>) -> f32 {\n    debug_assert_eq!(a.len(), b.len());\n    let mut s = 0.0f32;\n    for i in 0..a.len() {\n        s += a[i] * b[i];\n    }\n    s\n}\n"
  },
  {
    "path": "transformers/src/rewriter.rs",
    "content": "use tract_nnef::internal::*;\nuse tract_nnef::tract_core::transform::ModelTransform;\n\nuse crate::ops;\n\n#[derive(Debug, Default)]\npub struct ApplyRopeTransform;\n\nimpl ModelTransform for ApplyRopeTransform {\n    fn name(&self) -> StaticName {\n        \"detect_apply_rope\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        Rewriter::default()\n            .with_rule_for(\"detect-rotate-half\", ops::rotate_half_rule)\n            .with_rule_for(\"detect-apply-rope\", ops::apply_rope_rule)\n            .rewrite(&(), model)\n    }\n}\n\n#[derive(Debug, Default)]\npub struct ScaledMaskedSoftmaxTransform;\n\nimpl ModelTransform for ScaledMaskedSoftmaxTransform {\n    fn name(&self) -> StaticName {\n        \"detect_scaled_masked_softmax\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        Rewriter::default()\n            .with_rule_for(\"detect-scaled-masked-softmax\", ops::scaled_masked_softmax_rule)\n            .rewrite(&(), model)\n    }\n}\n\n#[derive(Debug, Default)]\npub struct KeyValueCacheTransform;\n\nimpl ModelTransform for KeyValueCacheTransform {\n    fn name(&self) -> StaticName {\n        \"detect_kv_cache\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        let inputs = model.inputs.clone();\n\n        for input in inputs {\n            ops::replace_kv_cache(model, input.node)?;\n        }\n        Ok(())\n    }\n}\n\n#[derive(Debug, Default)]\npub struct SdpaFuseKvCacheBroadcastTransform;\n\nimpl ModelTransform for SdpaFuseKvCacheBroadcastTransform {\n    fn name(&self) -> StaticName {\n        \"detect_sdpa_kv_cache_broadcast\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        Rewriter::default()\n            .with_rule_for(\"detect-sdpa-kv-cache-broadcast\", ops::fuse_kv_cache_broadcast_rule)\n            .rewrite(&(), model)\n    }\n}\n\n#[derive(Debug, Default)]\npub struct UnfoldKeyValueCacheTransform;\n\nimpl ModelTransform for UnfoldKeyValueCacheTransform {\n    fn name(&self) -> StaticName {\n        \"unfold_kv_cache\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        let kv_node_ids: Vec<usize> = model\n            .nodes()\n            .iter()\n            .filter(|n| n.op_as::<ops::DynKeyValueCache>().is_some())\n            .map(|n| n.id)\n            .collect();\n        for id in kv_node_ids {\n            ops::unfold_kv_cache(model, id)?;\n        }\n        Ok(())\n    }\n}\n\n// TODO: This is why Transform should be renamed to Remodel\n#[derive(Debug, Default)]\npub struct TransformersTransform;\n\nimpl ModelTransform for TransformersTransform {\n    fn name(&self) -> StaticName {\n        \"transformers_detect_all\".into()\n    }\n\n    fn transform(&self, model: &mut TypedModel) -> TractResult<()> {\n        KeyValueCacheTransform.transform(model)?;\n\n        Rewriter::default()\n            .with_rule_for(\"detect-rotate-half\", ops::rotate_half_rule)\n            .with_rule_for(\"detect-apply-rope\", ops::apply_rope_rule)\n            .with_rule_for(\"detect-scaled-masked-softmax\", ops::scaled_masked_softmax_rule)\n            .with_rule_for(\"detect-sdpa-kv-cache-broadcast\", ops::fuse_kv_cache_broadcast_rule)\n            .rewrite(&(), model)\n    }\n}\n"
  },
  {
    "path": "yank.sh",
    "content": "#!/bin/sh\n\nVERSION=$1\n. ./.all_crates.sh\n\nif [ `uname` = \"Darwin\" ]\nthen\n    SED=gsed\nelse\n    SED=sed\nfi\n\nif [ -z \"$VERSION\" ]\nthen\n    echo \"Usage: $0 <version>\" \n    exit 1\nfi\n\nfor path in $ALL_CRATES_PATH\ndo\n    crate=$(tomato get package.name $path/Cargo.toml)\n    cargo yank --version $VERSION $crate\ndone\n"
  }
]